pax_global_header00006660000000000000000000000064125664244050014522gustar00rootroot0000000000000052 comment=73ce7bad6c4d8bf71b4ab8b5d87cf92c2e01c868 oolite-1.82/000077500000000000000000000000001256642440500127475ustar00rootroot00000000000000oolite-1.82/DebugOXP/000077500000000000000000000000001256642440500143645ustar00rootroot00000000000000oolite-1.82/DebugOXP/Debug.oxp/000077500000000000000000000000001256642440500162175ustar00rootroot00000000000000oolite-1.82/DebugOXP/Debug.oxp/Config/000077500000000000000000000000001256642440500174245ustar00rootroot00000000000000oolite-1.82/DebugOXP/Debug.oxp/Config/debugConfig.plist000066400000000000000000000167201256642440500227230ustar00rootroot00000000000000/* JavaScript console configuration. This plist is merged and overrideable in the usual fashion. Additionally, settings may be changed from within JavaScript using syntax such as: console.settings["font-face"] = "Palatino"; console.settings["command-background-color"] = [0.9, 0.9, 1]; console.settings["show-console-on-log"] = true; or using the predefined console macros fgColor and bgColor, like: :fgColor general "redColor" :bgColor general "0.8 0.8 0.9" Settings like this are saved in Oolite's preferences and always override settings in jsConsoleConfig.plist. An override can be removed in JavaScript like this: delete console.settings["font-face"]; */ { /* Settings for external TCP debug console support. Under Mac OS X, the integrated debug console will be used if console-host is not specified. Set console-host to "127.0.0.1" to use an external debug console running on the same machine. On other systems, not specifying console-host is equivalent to setting it to 127.0.0.1. If console-port is not specified, 8563 will be used. NOTE: console-host and console-port overrides set in JavaScript will be ignored. This is by design. */ // console-host = "127.0.0.1"; // console-port = 8563; // Automatic showing of console for various types of message show-console-on-warning = true; // JavaScript warning show-console-on-error = true; // JavaScript error or exception show-console-on-log = false; // log() called // Show where an error or warning occurred. show-error-locations = true; show-error-locations-during-console-eval = false; // Log stack trace when reporting errors/warnings. dump-stack-for-errors = true; dump-stack-for-warnings = true; // Macro definitions for the console macro system (implemented in oolite-debug-console.js). // Note that these call simple functions in oolite-debug-console.js, not magic functions provided by Oolite. default-macros = { // Manipulating macros "setM" = "setMacro(PARAM)"; "delM" = "deleteMacro(PARAM)"; "showM" = "showMacro(PARAM)"; "listM" = "listMacro()"; // Reload macros from plist, removing any custom ones. "resetM" = "macros = console.settings.macros = {}; undefined"; // Examining (“dumping”) objects "d" = "dumpObject(eval(PARAM))"; "proto" = "consoleMessage('dumpObject', protoChain(eval(PARAM)))"; // Clearing the console "clr" = "console.clearConsole()"; "clear" = "console.clearConsole()"; /* For creating/testing colour sets. Syntax is flexible, as in plists, for example: :fgColor general "redColor" :bgColor command { hue: 60, saturation: 0.1 } :bgColor general [0, 0, 1, 1] rmFgColor/rmBgColor remove the specified setting from config overrides, returning to whatever is set in the plist. */ "fgColor" = "setColorFromString(PARAM, 'foreground')"; "bgColor" = "setColorFromString(PARAM, 'background')"; "rmFgColor" = "void delete console.settings[PARAM + '-foreground-color']"; "rmBgColor" = "void delete console.settings[PARAM + '-background-color']"; // ":nearest role" -- find nearest ship with specified role. // ":tnearest role" -- target nearest ship with specified role. "nearest" = "this.result = system.shipsWithRole(PARAM, player.ship)[0]"; "tnearest" = "player.ship.target = system.shipsWithRole(PARAM, player.ship)[0]"; // ":find expr" -- find entities matching expr; for example, ":find entity.isShip" to find all ships. // ":findS expr" -- find ships matching expr; for example, ":find ship.scanClass == 'CLASS_BUOY'". // ":target expr" -- target closest ship matching expr. "find" = "this.result = system.filteredEntities(player.ship, function(entity) { return eval(PARAM); })"; "findS" = "this.result = system.filteredEntities(player.ship, function(ship) { return ship.isShip && eval(PARAM); })"; "target" = "player.ship.target = system.filteredEntities(player.ship, function(ship) { return ship.isShip && eval(PARAM); }, player.ship)[0]"; // ":logOn className" -- enable logging for log message class className. // ":logOff className" -- disable logging for log message class className. "logOn" = "console.setDisplayMessagesInClass(PARAM, true)"; "logOff" = "console.setDisplayMessagesInClass(PARAM, false)"; // ":spawn foo" -- create a ship with role "foo" near the player. "spawn" = "this.T = system.addShips(PARAM, 1, player.ship.position, 10000); if (this.T) this.T = this.T[0]; else consoleMessage('command-error', 'Could not spawn \"' + PARAM + '\".');"; // ":qotd" -- quote of the day. Or the moment, anyway. "qotd" = "expandDescription('[thargoid_curses]')"; // ":test" -- display a ship of the specified role, assuming you're docked. "test" = "mission.runScreen({model:PARAM})"; // ":time " -- time a JavaScript expression. "time" = "console.profile(eval(\"(function codeToBeProfiled() { (\" + PARAM + \") })\"), this)"; // ":trace " -- trace a JavaScript expression. "trace" = "console.trace(eval(\"(function codeToBeTraced() { (\" + PARAM + \") })\"), this)"; }; /* Colours for various types of console messages. These are standard Oolite colour specifiers. Each colour key comes in a pair, foo-foreground-color and foo-background-color. A class of messages can be hidden by giving its colour key a fully transparent colour (such as clearColor). */ general-foreground-color = blackColor; general-background-color = whiteColor; command-background-color = { hue = 240; brightness = 1.0; saturation = 0.12; }; // Pale blue warning-background-color = { hue = 60; brightness = 1.0; saturation = 0.25; }; // Pale yellow error-background-color = { hue = 0; brightness = 1.0; saturation = 0.25; }; // Pale red exception-background-color = { hue = 320; brightness = 1.0; saturation = 0.25; }; // Pale magentaish // These colours are used by oolite-debug-console.js command-result-background-color = { hue = 120; brightness = 1.0; saturation = 0.2; }; // Pale green command-error-background-color = { hue = 0; brightness = 1.0; saturation = 0.12; }; // Very pale red macro-expansion-background-color = { hue = 240; brightness = 1.0; saturation = 0.06; }; // Very pale blue macro-expansion-foreground-color = darkGrayColor; macro-warning-background-color = { hue = 60; brightness = 1.0; saturation = 0.12; }; // Very pale yellow unknown-macro-background-color = { hue = 60; brightness = 1.0; saturation = 0.12; }; // Very pale yellow macro-error-background-color = { hue = 0; brightness = 1.0; saturation = 0.12; }; // Very pale red macro-info-background-color = { hue = 120; brightness = 1.0; saturation = 0.2; }; // Pale green command-exception-background-color = { hue = 320; brightness = 1.0; saturation = 0.25; }; // Pale magentaish // Alternative configuration for people who find black on white insufficiently l33t: /* general-background-color = blackColor; general-foreground-color = "0.8 0.8 0.8"; command-foreground-color = cyanColor; warning-foreground-color = yellowColor; error-foreground-color = redColor; exception-foreground-color = orangeColor; command-result-foreground-color = greenColor; macro-expansion-foreground-color = "0 0.8 0.8"; macro-warning-foreground-color = "1 0.5 1"; unknown-macro-foreground-color = "1 0.5 1"; macro-error-foreground-color = "1 0.5 0.5"; macro-info-foreground-color = greenColor; command-exception-foreground-color = "1 0.75 0.5"; */ font-face = "Courier"; font-size = "12"; } oolite-1.82/DebugOXP/Debug.oxp/DebugOXPLocatorBeacon.magic000066400000000000000000000001521256642440500232700ustar00rootroot00000000000000This file is needed for Oolite to find the debug OXP. Apart from that, it doesn’t actually do anything. oolite-1.82/DebugOXP/Debug.oxp/Scripts/000077500000000000000000000000001256642440500176465ustar00rootroot00000000000000oolite-1.82/DebugOXP/Debug.oxp/Scripts/oolite-debug-console.js000066400000000000000000000577241256642440500242420ustar00rootroot00000000000000/* oolite-debug-console.js JavaScript section of JavaScript console implementation. This script is attached to a one-off JavaScript object of type Console, which represents the Objective-C portion of the implementation. Commands entered into the console are passed to this script’s consolePerformJSCommand() function. Since console commands are performed within the context of this script, they have access to any functions in this script. You can therefore add debugger commands using a customized version of this script. The following properties are predefined for the script object: console: the console object. The console object has the following properties and methods: debugFlags : Number (integer, read/write) An integer bit mask specifying various debug options. The flags vary between builds, but at the time of writing they are: console.DEBUG_LINKED_LISTS console.DEBUG_COLLISIONS console.DEBUG_DOCKING console.DEBUG_OCTREE_LOGGING console.DEBUG_BOUNDING_BOXES console.DEBUG_OCTREE_DRAW console.DEBUG_DRAW_NORMALS console.DEBUG_NO_DUST console.DEBUG_NO_SHADER_FALLBACK console.DEBUG_SHADER_VALIDATION The current flags can be seen in OODebugFlags.h in the Oolite source code, for instance at: http://svn.berlios.de/svnroot/repos/oolite-linux/trunk/src/Core/Debug/OODebugFlags.h For example, to enable rendering of bounding boxes and surface normals, you might use: console.debugFlags ^= console.DEBUG_BOUNDING_BOXES console.debugFlags ^= console.DEBUG_DRAW_NORMALS Explaining bitwise operations is beyond the scope of this comment, but the ^= operator (XOR assign) can be thought of as a “toggle option” command. dumpStackForErrors If true, when an error or exception is reported a stack trace will be written to the log (if possible). Ignored if not showing error locations. dumpStackForWarnings If true, when an warning is reported a stack trace will be written to the log (if possible). Ignored if not showing error locations. platformDescription : String (read-only) Information about the system Oolite is running on. The format of this string is not guaranteed, do not attempt to parse it. settings : Object A key-value store that is saved persistently. Values from debugConfig.plist are used as defaults, and any changed values are stored with the game’s preferences. showErrorLocations true if file and line should be shown when reporting JavaScript errors and warnings. Default: true. showErrorLocationsDuringConsoleEval Override value for showErrorLocations used while evaluating code entered in the console. Default: false. (This information is generally not useful for code passed to eval().) shaderMode : String (read/write) A string specifying the current shader mode. One of the following: "SHADERS_NOT_SUPPORTED" "SHADERS_OFF" "SHADERS_SIMPLE" "SHADERS_FULL" If it is SHADERS_NOT_SUPPORTED, it cannot be set to any other value. If it is not SHADERS_NOT_SUPPORTED, it can be set to SHADERS_OFF, SHADERS_SIMPLE or SHADERS_FULL, unless maximumShaderMode (see below) is SHADERS_SIMPLE, in which case SHADERS_FULL is not allowed. NOTE: this is equivalent to oolite.gameSettings.shaderEffectsLevel, which is available even when the debug console is not active, but is read-only. maximumShaderMode: String (read-only) A string specifying the fanciest available shader mode. One of the following: "SHADERS_NOT_SUPPORTED" "SHADERS_SIMPLE" "SHADERS_FULL" reducedDetailMode: Boolean (read/write) Whether reduced detail mode is in effect (simplifies graphics in various ways). displayFPS : Boolean (read/write) Boolean specifying whether FPS (and associated information) should be displayed. glVendorString : String (read-only) glRendererString : String (read-only) Information about the OpenGL renderer. function consoleMessage(colorCode : String, message : String [, emphasisStart : Number, emphasisLength : Number]) Similar to log(), but takes a colour code which is looked up in debugConfig.plist. null is equivalent to "general". It can also optionally take a range of characters that should be emphasised. function clearConsole() Clear the console. function inspectEntity(entity : Entity) Show inspector palette for entity (Mac OS X only). function displayMessagesInClass(class : String) : Boolean Returns true if the specified log message class is enabled, false otherwise. function setDisplayMessagesInClass(class : String, flag : Boolean) Enable or disable logging of the specified log message class. For example, the equivalent of the legacy command debugOn is: console.setDisplayMessagesInClass("$scriptDebugOn", true); Metaclasses and inheritance work as in logcontrol.plist. function isExecutableJavaScript(code : String) : Boolean Used to test whether code is runnable as-is. Returns false if the code has unbalanced braces or parentheses. (Used in consolePerformJSCommand() below.) function profile(func : function [, this : Object]) : String Time the specified function, report the time spent in various Oolite functions and how much time is excluded from the time limiter mechanism. NOTE: while profile() is running, the time limiter is effectively disabled (specifically, it's set to ten million seconds). function getProfile(func : function [, this : Object]) : Object Like profile(), but returns an object, which is more amenable to processing in scripts. To see the structure of the object, run: console.getProfile(function(){PS.position.add([0, 0, 0])}) function writeLogMarker() Writes a separator to the log. Useful properties of the console script (which can be used directly in the console, e.g. “log($)”): $ The value of the last interesting (non-null, non-undefined) expression evaluated by the console. This includes values generated by macros. result Set by some macros, such as :find. The console script also adds two methods to all entities: function dumpState() Writes information about the entity to the log. (This is the same stuff you get if you press 0 while paused, but for a single entity.) function inspect() Calls console.inspectEntity() on the entity (see above; only useful in Mac OS X). Oolite Debug OXP Copyright © 2007-2014 the Oolite team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ this.name = "oolite-debug-console"; this.author = "Jens Ayton"; this.copyright = "© 2007-2013 the Oolite team."; this.description = "Debug console script."; this.version = "1.82"; (function() { "use strict"; this.inputBuffer = ""; this.$ = null; // **** Macros // Normally, these will be overwritten with macros from the config plist. this.defaultMacros = { setM: "setMacro(PARAM)", delM: "deleteMacro(PARAM)", showM: "showMacro(PARAM)" }; this.macros = {}; // **** Convenience functions -- copy this script and add your own here. // List the properties and values of an object. this.dumpObject = function dumpObject(x) { var description; if (typeof x == "object") { if (Array.isArray(x)) description = prettifyArray(x); else description = prettifyObject(x); } else description = prettify(x); consoleMessage("dumpObject", description); } this.protoChain = function protoChain(object) { /* Box the value if it’s a primitive, because Object.getPrototypeOf() rejects primitives (ECMA-262 Rev. 1, 15.2.3.2, in a flagrant and apparently pointless violation of JavaScript’s normal autoboxing behaviour.) */ object = new Object(object); var result = "", first = true; for (;;) { var proto = Object.getPrototypeOf(object); if (!proto) return result; if (!first) result += ": "; else first = false; result += proto.constructor.name || ""; object = proto; } } this.setColorFromString = function setColorFromString(string, typeName) { // Slice of the first component, where components are separated by one or more spaces. var [key, value] = string.getOneToken(); var fullKey = key + "-" + typeName + "-color"; /* Set the colour. The "var c" stuff is so that JS property lists (like { hue: 240, saturation: 0.12 } will work -- this syntax is only valid in assignments. */ console.settings[fullKey] = eval("var c=" + value + ";c"); consoleMessage("command-result", "Set " + typeName + " colour “" + key + "” to " + value + "."); } // **** Conosole command handler this.consolePerformJSCommand = function consolePerformJSCommand(command) { var originalCommand = command; while (command.charAt(0) == " ") { command = command.substring(1); } while (command.length > 1 && command.charAt(command.length - 1) == "\n") { command = command.substring(0, command.length - 1); } if (command.charAt(0) != ":") { // No colon prefix, just JavaScript code. // Append to buffer, then run if runnable or empty line. this.inputBuffer += "\n" + originalCommand; if (command == "" || console.isExecutableJavaScript(console, this.inputBuffer)) { // Echo input to console, emphasising the command itself. consoleMessage("command", "> " + command, 2, command.length); command = this.inputBuffer; this.inputBuffer = ""; this.evaluate(command); } else { // Echo input to console, emphasising the command itself. consoleMessage("command", "_ " + command, 2, command.length); } } else { // Echo input to console, emphasising the command itself. consoleMessage("command", "> " + command, 2, command.length); // Colon prefix, this is a macro. this.performMacro(command); } } this.prettifyArray = function prettifyArray(value, indent) { // NOTE: value may be an Arguments object. var i, length = value.length; var result = "["; for (i = 0; i < length; i++) { if (i > 0) result += ", "; result += prettifyElement(value[i], indent); } result += "]"; return result; } this.prettifyObject = function prettifyObject(value, indent) { indent = indent || ""; var subIndent = indent + " "; var appendedAny = false; var result = "{"; var separator = ",\n" + subIndent; for (var key in value) { var propVal = value[key]; if (propVal === undefined) continue; if (appendedAny) result += separator; else result += "\n" + subIndent; /* Highlighting inherited properties sounds desireable, but in practice it’s likely to be confusing since most host objects’ apparent instance properties are actually inherited accessor- based properties. */ // if (!value.hasOwnProperty(key)) result += ">> "; // Quote string if necessary. if (isClassicIdentifier(key)) result += key; else result += '"' + key.substituteEscapeCodes() + '"'; result += ": " + prettifyElement(propVal, subIndent); appendedAny = true; } if (appendedAny) result += "\n" + indent; result += "}"; return result; } this.prettifyFunction = function prettifyFunction(value, indent) { var funcDesc = value.toString(); if (indent) { funcDesc = funcDesc.replace(/\n/g, "\n" + indent); } return funcDesc; } this.prettify = function prettify(value, indent) { try { if (value === undefined) return "undefined"; if (value === null) return "null"; var type = typeof value; if (type == "boolean" || type == "number" || type == "xml" || value.constructor === Number || value.constructor === Boolean) { return value.toString(); } if (type == "string" || value.constructor === String) { return value; } if (type == "function") { return prettifyFunction(value, indent); } if (Array.isArray(value)) return prettifyArray(value, indent); var stringValue = value.toString(); if (stringValue == "[object Object]") { return prettifyObject(value, indent); } if (stringValue == "[object Arguments]" && value.length !== undefined) { return prettifyArray(value, indent); } return stringValue; } catch (e) { return value.toString(); } } this.prettifyElement = function prettifyElement(value, indent) { if (value === undefined) return "undefined"; if (value === null) return "null"; if (typeof value == "string" || value.constructor === String) { return '"' + value.substituteEscapeCodes() + '"'; } return prettify(value, indent); } // **** Macro handling this.setMacro = function setMacro(parameters) { if (!parameters) return; // Split at first series of spaces var [name, body] = parameters.getOneToken(); if (defaultMacros[name]) { consoleMessage("macro-error", "Built-in macro " + name + " cannot be replaced."); return; } if (body) { macros[name] = body; console.settings["macros"] = macros; consoleMessage("macro-info", "Set macro :" + name + "."); } else { consoleMessage("macro-error", "setMacro(): a macro definition must have a name and a body."); } } this.deleteMacro = function deleteMacro(parameters) { if (!parameters) return; var [name, ] = parameters.getOneToken(); if (name.charAt(0) == ":" && name != ":") name = name.substring(1); if (defaultMacros[name]) { consoleMessage("macro-error", "Built-in macro " + name + " cannot be deleted."); return; } if (macros[name]) { delete macros[name]; console.settings["macros"] = macros; consoleMessage("macro-info", "Deleted macro :" + name + "."); } else { consoleMessage("macro-info", "Macro :" + name + " is not defined."); } } this.listMacro = function listMacro() { var tmp,l=[]; for (var prop in defaultMacros) { l.push( ":" + prop); } l.sort(); tmp = l.join("\n"); l=[]; for (var prop in macros) { if (!defaultMacros[prop]) l.push( ":" + prop); } l.sort(); consoleMessage("macro-list", tmp + l.join("\n")); } this.resolveMacro = function resolveMacro(name) { if (defaultMacros[name]) return defaultMacros[name]; else if (macros[name]) return macros[name]; else return null; } this.showMacro = function showMacro(parameters) { if (!parameters) return; var [name, ] = parameters.getOneToken(); if (name.charAt(0) == ":" && name != ":") name = name.substring(1); var macro = resolveMacro(name); if (macro) { consoleMessage("macro-info", ":" + name + " = " + macro); } else { consoleMessage("macro-info", "Macro :" + name + " is not defined."); } } this.performMacro = function performMacro(command) { if (!command) return; // Strip the initial colon command = command.substring(1); // Split at first series of spaces var [macroName, parameters] = command.getOneToken(); var expansion = resolveMacro(macroName); if (expansion) { // Show macro expansion. var displayExpansion = expansion; if (parameters) { // Substitute parameter string into display expansion, going from 'foo(PARAM)' to 'foo("parameters")'. displayExpansion = displayExpansion.replace(/PARAM/g, '"' + parameters.substituteEscapeCodes() + '"'); } consoleMessage("macro-expansion", "> " + displayExpansion); // Perform macro. this.evaluate(expansion, parameters); } else { consoleMessage("unknown-macro", "Macro :" + macroName + " is not defined."); } } // **** Utility functions /* Split a string at the first sequence of spaces, returning an array with two elements. If there are no spaces, the first element of the result will be the input string, and the second will be null. Leading spaces are stripped. Examples: "x y" --> ["x", "y"] "x y" --> ["x", "y"] " x y" --> ["x", "y"] "xy" --> ["xy", null] " xy" --> ["xy", null] "" --> ["", null] " " --> ["", null] */ String.prototype.getOneToken = function getOneToken() { var matcher = /\s+/g; // Regular expression to match one or more spaces. matcher.lastIndex = 0; var match = matcher.exec(this); if (match) { var token = this.substring(0, match.index); // Text before spaces var tail = this.substring(matcher.lastIndex); // Text after spaces if (token.length != 0) return [token, tail]; else return tail.getOneToken(); // Handle leading spaces case. This won't recurse more than once. } else { // No spaces return [this, null]; } } /* Replace special characters in string with escape codes, for displaying a string literal as a JavaScript literal. (Used in performMacro() to echo macro expansion.) */ String.prototype.substituteEscapeCodes = function substituteEscapeCodes() { var string = this.replace(/\\/g, "\\\\"); // Convert \ to \\ -- must be first since we’ll be introducing new \s below. string = string.replace(/\x08/g, "\\b"); // Backspace to \b string = string.replace(/\f/g, "\\f"); // Form feed to \f string = string.replace(/\n/g, "\\n"); // Newline to \n string = string.replace(/\r/g, "\\r"); // Carriage return to \r string = string.replace(/\t/g, "\\t"); // Horizontal tab to \t string = string.replace(/\v/g, "\\v"); // Vertical tab to \v string = string.replace(/\'/g, '\\\''); // ' to \' string = string.replace(/\"/g, "\\\""); // " to \" return string; } this.isClassicIdentifier = function isClassicIdentifier(string) { /* JavaScript allows any Unicode letter or digit in an indentifier. However, JavaScript regexps don’t have shortcuts for Unicode letters and digits. Smort! Therefore, this function only returns true for ASCII identifiers. */ if (!string) return false; // Note that the empty string is a falsey value. if (/[^\w\$]/.test(string)) return false; // Contains non-identifier characters. if (/\d/.test(string[0])) return false; // Starts with a digit. var reservedWords = [ // ECMAScript 5 keywords. "break", "case", "catch", "continue", "debugger", "default", "delete", "do", "else", "finally", "for", "function", "if", "in", "instanceof", "typeof", "new", "var", "return", "void", "switch", "while", "this", "with", "throw", "try", // Future reserved words. "class", "enum", "extends", "super", "const", "export", "import", // Strict Mode future reserved words. "implements", "let", "private", "public", "interface", "package", "protected", "static", "yield", // Literals. "null", "true", "false", // Not formally reserved, but potentially confusing or with special rules. "undefined", "eval", "arguments" ]; if (reservedWords.indexOf(string) != -1) return false; return true; } // **** Load-time set-up // Make console globally visible as console (and debugConsole, for backwards compatibility). global.console = this.console; Object.defineProperty(global, "debugConsole", { value: console, enumerable: false, configurable: true, writable: true }); console.script = this; // Load macros. if (console.settings["macros"]) this.macros = console.settings["macros"]; if (console.settings["default-macros"]) this.defaultMacros = console.settings["default-macros"]; // Implement console.showErrorLocations with persistence. Object.defineProperty(console, "showErrorLocations", { get: function () { return console.__showErrorLocations }, set: function (value) { console.settings["show-error-locations"] = console.__showErrorLocations = !!value; }, enumerable: true }); console.__showErrorLocations = console.settings["show-error-locations"]; // Implement console.showErrorLocationsDuringConsoleEval with persistence. Object.defineProperty(console, "showErrorLocationsDuringConsoleEval", { get: function () { return console.settings["show-error-locations-during-console-eval"] ? true : false; }, set: function (value) { console.settings["show-error-locations-during-console-eval"] = !!value; }, enumerable: true }); // Implement console.dumpStackForErrors with persistence. Object.defineProperty(console, "dumpStackForErrors", { get: function () { return console.__dumpStackForErrors }, set: function (value) { console.settings["dump-stack-for-errors"] = console.__dumpStackForErrors = !!value; }, enumerable: true }); console.__dumpStackForErrors = console.settings["dump-stack-for-errors"]; // Implement console.dumpStackForWarnings with persistence. Object.defineProperty(console, "dumpStackForWarnings", { get: function () { return console.__dumpStackForWarnings }, set: function (value) { console.settings["dump-stack-for-warnings"] = console.__dumpStackForWarnings = !!value; }, enumerable: true }); console.__dumpStackForWarnings = console.settings["dump-stack-for-warnings"]; /* As a convenience, make player, player.ship, system and missionVariables available to console commands as short variables: */ this.P = player; this.PS = player.ship; this.S = system; this.M = missionVariables; // Make console.consoleMessage() globally visible Object.defineProperty (global, "consoleMessage", { value: function consoleMessage() { // Call console.consoleMessage() with console as "this" and all the arguments passed to consoleMessage(). console.consoleMessage.apply(console, arguments); }}); // Add inspect() method to all entities, to show inspector palette (Mac OS X only; no effect on other platforms). Object.defineProperty(Entity.prototype, "inspect", { value: function inspect() { console.inspectEntity(this); }}); /* Add callObjC() method to all objects. In debug builds only, this can be used to call an Objective-C method on the Objective-C representation of an object. (For entities and some other Oolite-defined objects, this is the underlying native object. Most other objects will be converted into property list types.) Supported method types are: * Methods taking no parameters and returning nothing * Methods taking no parameter and returning one of: - nothing - an object - a number - a vector or quaternion struct * Methods taking one object parameter and returning nothing * Methods taking one object parameter and returning an object As a special case, if a method’s name ends with _bool and it returns an object, it is parsed as a property list representing a boolean. This handles predicate methods from the legacy script system. */ if (typeof console.__setUpCallObjC == "function") { console.__setUpCallObjC(Object.prototype); //not enumerable. } else { // not enumerable when disabled too. Object.defineProperty(Object.prototype, 'callObjC', {value: function() { throw Error("callObjC() is disabled."); } }); } }).call(this); /* evaluate() is outside the closure specifically to avoid strict mode. If evaluate() is compiled in strict mode, all console input will also be strict. Also, evaluate() should be as close to the bottom as possible because everything from the eval() down is tagged "" in stack dumps, profiles and traces. */ this.evaluate = function evaluate(command, PARAM) { var showErrorLocations = console.__showErrorLocations; console.__showErrorLocations = console.showErrorLocationsDuringConsoleEval; try { var result = eval(command); } catch (e) { // console.__showErrorLocations must be reset _after_ the exception is handed. this.resetErrorLocTimer = new Timer(this, function () { console.__showErrorLocations = showErrorLocations; delete this.resetErrorLocTimer; }, 0); throw e; } console.__showErrorLocations = showErrorLocations; if (result !== undefined) { if (result === null) result = "null"; else this.$ = result; consoleMessage("command-result", prettify(result)); } } // Identify the location of the eval() command above for the debug location formatter. this.markConsoleEntryPoint = special.markConsoleEntryPoint; this.evaluate("console.script.markConsoleEntryPoint()"); delete this.markConsoleEntryPoint; oolite-1.82/DebugOXP/Debug.oxp/manifest.plist000066400000000000000000000004571256642440500211100ustar00rootroot00000000000000{ title = "Oolite Debug OXP"; identifier = "org.oolite.oolite.debug"; version = "1.82"; required_oolite_version = "1.82"; license = "GPL 2+ / CC-BY-NC-SA 3.0 - see LICENSE.TXT for details"; author = "Giles Williams, Jens Ayton and contributors"; information_url = "http://www.oolite.org/"; } oolite-1.82/DebugOXP/Debug.oxp/requires.plist000066400000000000000000000000321256642440500211260ustar00rootroot00000000000000{ version = "1.81"; } oolite-1.82/Doc/000077500000000000000000000000001256642440500134545ustar00rootroot00000000000000oolite-1.82/Doc/AdviceForNewCommanders.doc000066400000000000000000015730001256642440500204760ustar00rootroot00000000000000ࡱ> vxopqrstu'` 27bjbjLULU \.?.?.2dtXXXDL\ts/ ' ' '.......$1hy3b/ '%0' ' '/$ -/`.`.`. '.`. '.`.`.b.r. vX$+,f..C/0s/l.3P-|3r.3r. ' '`. ' ' ' ' '//- ' ' 's/ ' ' ' 'tttXtttXttt File: OAfNC2009 TD Index: 20832312:10:20:00 Source: Security Recording, Dock Masters Office, Galactic Cooperative of Worlds Station Lave 1 Persons Involved: Dock Master Mr. TBF Gimlet, a Commander Jameson Subject: Advice for New Commanders.  Central Archives (the Oolite Wiki). All right there! You just got your pilots ticket. Can I just say that your zip-clip there doesnt do you justice? Youre itching to get off and out into the big black, I can tell; but we just got a few final once-overs before I can stamp that thing legal. Shall we? So. You got yourself a brand and shiny-new Cobra Mark III. Cowell and MgRaths finest, yes siree: moren sixty years since the first one rolled off the line right here on Lave, and its still one of the best. An all-round ship, you get me? It aint the fastest, and it aint the strongest, nor the most killing neither, and it definitely aint the biggest, by a long shot, but a sweet little number in her own right, no error. Lets take a tour around... Hoo boy, she is mint, aint she! I just love that new-ship smell. Take a sniff, go on: yeah, well, most of them long-chain monomers are carcinogenic, so dont you snort too deep... Hah! Im just funnin ya, kid. If pulling a tick from sniffing the command console was all a pilot had to worry about, life would be gravy! No, theres moren enough out there to kill you plenty quick, if you dont watch out, shiny new ship or no. I see a lot of blanks on this here board... Im guessing your ship is, whadda they call it, a basic model, yeah? Legal minimum? Uh-huh, I thought so. Man oh man, they shouldnt oughta let kids out in a machine like this; its a sin, is what it is. Some bandit takes a pop at you, and what you got to hold your end up with? A Pulse Laser. A Pulse Lasers one step up from a  penlight, kiddo. Oh, its a better defence than just harsh language, and theres always a chance you might be attacked by a really nervous pirate but seriously: if you ever want to shift that Harmless tag you better beef up your armaments, and soon! Beam Laser, minimum. Until then youd best stick to the cop-end worlds: Democracies and Corporates, Confederacies maybe if youre feeling lucky, you hear me? You stay sharp, and maybe youll stay alive. See, right here is what Im talking about: this is where you need to fit an ECM. Someone locks a missile on you, you pop that sucker fast. Oh, I know theres Hardheads out there, shielded missiles proofed against countermeasures, but a good ECM can pop those too, if youre lucky. You get one of those running on you, you turn tail and run from it as fast as you can. A warheads nasty, but no sense in giving it a kinetic advantage too, right? Keep slapping the ECM as you go, if youve got the energy for it: if the first burst dont kill it, maybe the next one will. Speaking of running... over here is where youd control your Witchdrive Fuel Injectors, ifn you had em... dumps fuel straight from the tanks into the drive, and shoots you off like an Oresquan on a hot date. Good for whatever ails ya, from pushing past a mass-lock to getting the hell out of town! Down here, now, this is your Fuel Scoop indicator... huh, offline, I see. Sure, sure, you dont think youll ever need to kiss the stars: why bother, when fuels cheaper than Celabiler poetry? Well, maybe its true, and maybe it aint, but anyways this piece of kit scoops up more than just sunshine. Theres scraps and salvage out there, kid, and good money to be had. Skim on over the top and this puppy drops em straight into the cargo bay. Pays for itself in no time. Sweeps up Escape Pods, too: you get the chance to bring someone safe home, you take it Advice for New Commanders by Disembodied 2008 Page 1 of 3 even if it means dumping some of your own payload to take them on board. Look out for the other guys and theyll look out for you. And... sweet Lord Giles on a gyrospider, they didnt even fit you out with a Docking Computer! Optional Extra, my shiny blue a**... Oh, sure, manual dockings easy enough, but theres a knack to it. You gotta get that knack first, though. Practice it. Before you go anywhere, practice it. Fly out to the station buoy, turn around and come back in again, until you got it pat. And match the rotation: you put scrapes or dents or a big long greasy smear all over my bay, and I will NOT be pleased... Oh, theres a whole bunch of other s**t you can stick on here: a Scanner Targeting Enhancement, for one, if you ever get yourself set up right for a firefight. Even before then, maybe: if you can clock pirates before they start their run on you, thats half the battle. Well, quarter of the battle. Or a fifth. Some proportion, anyhow. The Advanced Space Compass, too, now thats a handy doodad to have on board. And an Extra Energy Unit to boost your recharge. And Shield Boosters, now theyre a no-brainer. And okay, most of this junk is too high-tech for Lave: you can get most everything at Zaonce, though, just a wormhole away. Dull kinda burg, Zaonce, but they know their quarks from their quaternions. Shouldnt set you back moren ten, twenty thou. You got how much? One hundred creds. One ... hundred ... creds. Ayoha. All right then. Lets break it down. Your problem here is financial, not technical. Maybe at bottom its psychological, but Ill give you the benefit. Theres two types of money, kid: fast, and slow. Fast money comes easy, and slow money comes hard. The slow is sure and steady, though, and the fast, well, it might make you wish you had waited. Ill run you through them both, and you can make up your own mind.  First of all, for the fast money, theres this sweet and cherry Cobra III: you sell it, right now, youll net yourself enough to buy a second-hand ship with enough scratch left over for some half-decent kit. Course, some of these second-hand numbers are pretty, well, used, if you know what I mean, and come with problems of their own. I mean, you ever try to take a dump in a head designed for some other guys anatomy? And the resale sucks, ifn you ever want to move on up. But its an option.  Slow money, now, thats less chancy. You buy up whats cheap, you take it to where its expensive, and you sell it at a profit. Rinse and repeat. Whats cheap where, and whats expensive? Supply and demand, kid. Like the philosopher said, its the economy, stupid.  Agricultural worlds produce raw materials like minerals, metals and radioactives, and the bio-products like food, textiles, booze and furs, too. Industrial planets make finished goods, like luxuries, computers and machinery. So you take the produce of one and you sell it on the other, and chances are youre making money on the deal. Politics dont matter squat: farmers need harvesters and factories need feedstock! Ocourse, money matters: rich Industrials are rich because theyve got the most efficient processes, so not only do they make the cheapest products, their factories are the hungriest and theyll pay the best prices for raw materials. Poor Agriculturals, on the other hand, theyre most desperate for fine articles and will scrape together whatever they can to pay for em: meanwhile, theyll offer you the cheapest deals anywhere for what they make themselves. Which puts a vicious lock on the poverty trap, Advice for New Commanders by Disembodied 2008 Page 2 of 3 but hey: nobody said life was fair. Folks like you whove climbed up the gravity well, youre just filling a need. Buy and sell between rich Industrials and poor Agriculturals, thats my advice! Theres money to be made elsewhere, no error, but those are the sweetest runs youre likely to hit on. Bulk is the key, kid: the more you carry, the more you make. This Cobra III here can take twenty tons, right now: for just 400 creds more you can get a Cargo Bay Expansion to take you up to thirty-five. That extra fifteen tons of space will pay for itself and more in one good run, if you can fill it up. It aint all bulk, though. Watch the board for cheap deals on precious metals and gemstones: they might not offer the greatest profits, but they dont take up any cargo space at all. See this safe over here, behind this bulkhead? You take on platinum, or gold, or a sack of gemstones when youre docked, they go right in here. You can keep em here as long as you like, until you find somewhere to offload em. Co-op rules stop you dropping too much of em, or too much of anything, come to that, in one station so much for free trade! but as a slow-burn money-maker theres not much to beat it. You can try asteroid mining yourself, ifn you get a Mining Laser to go with your Fuel Scoop, and you dont mind scraping carbon scoring off the scoop every few jumps. Only dont, for any sake, put the Mining Laser on the nose! Its a tool, not a weapon. You can just buy the shinies cheap off the miners direct, if you run across a Rock Hermit. Powerful fond of liquor, Rock Hermits are, too. What other products? What you winking for, kid? You mean slaves, narcotics and firearms? Why dont you just damn well say so? They aint illegal. Theys what we call controlled merchandise. Bring as much of em in as you want... what will get you into trouble with the Blues is shipping them out of a main system station. Theres long-range shipping contracts on offer, too, in some stations: F8-F8 will bring em up, if theres any there. You buy the deal and then get paid a bonus if you make the delivery on time. Theyll be out your price-range just now, and anyway most of em call for a bigger cargo-hold than a Cobra can carry. Keep an eye out for any you might be able to do, though; if you build a rep as a reliable carrier then the jobs can get real juicy.  Thats slow money, kid: work, save, invest, and work again, thats what its all about! It aint pretty but it gets you there in the end. One final tip, kid: Ill say this cos I like ya. It wont save you work but it will save you time, and it might just save your life, too: if you want to get from the Witchpoint to the station fast, without getting your jumpdrive mass-locked by anyone, friendly or otherwise, heres what you do. Line up on the planet; angle up away from it by near enough ninety degrees; then hit the Torus jumpdrive and scoot on out of the main spacelane for a few hundred klicks or so. Then, when youve given yourself enough sky, pull the nose back round and come on down to the station. Chances are you wont meet a soul, whether youre cruising into Ensoreus or creeping into Qudira. The spacelanes is where the action is, where theres help and hostility both; you get nervous, you go off-beam. Most times, youll come through safe. Huh. Anyhow. Im a busy frog, I cant stay here all day filling in every Jameson on what they should have learned in the spawning pond. Gimme your ticket, kid, and Ill stamp it flight-ready, though Giles knows I probly shouldnt... there ya go. Thats you ready to take on the Witch. Jens help us all... dont know enough to keep a level bearing through a wormhole... what they send up here for us to deal with... pick up the pieces more like...  Advice for New Commanders by Disembodied 2008. Images are sourced from the Oolite Wiki, and the Your First Flight project. This version edited and formatted by KZ9999. Creative Commons BY NC SA 3.0 2009 Oolite created by Giles Williams and contributors 2003-2011 GNU General Public License V2 Creative Commons License: BY - NC - SA 3.0 Oolite Website: http://www.oolite.org Oolite Wiki: http://wiki.alioth.net/index.php/Oolite Oolite BBS: http://www.aegidian.org/bb Oolite is inspired by the Elite computer game series, originally created by Ian Bell and David Braben This document was originally created using OpenOffice 3.0, Inkscape, Paint.net and IrfanView. Advice for New Commanders by Disembodied 2008 Page 3 of 3     OOLITE - ADVICE FOR NEW COMMANDERS /189XYbcq ɴɦɃt`RjhxUmHnHu&h{ h,O6CJOJQJ]^JaJhrhCJOJQJ\^JaJ h{ h{ CJOJQJ^JaJ#hrhh,O5CJOJQJ^JaJhrhCJOJQJ^JaJh{ CJOJQJ^JaJ h{ h,O h{ h,OCJOJQJ^JaJ#h{ h,OCJOJQJ\^JaJ&hrhh,O5CJOJQJ\^JaJ01        d81$7$8$H$ dS1$7$8$H$ D1$5$7$8$H$]D d1$7$8$H$ d1$7$8$H$1$7$8$H$617 3 4 5 @ A ~s dH1$7$8$H$$|d1$5$7$8$H$]|a$gd6S dK1$7$8$H$dd1$5$7$8$H$]d dI1$7$8$H$1$5$7$8$H$]gd{ d31$7$8$H$ 1$5$7$8$H$gd6S d1$7$8$H$1$7$8$H$ d1$7$8$H$ 0 2 3 4 5 ? A B > ? Y Z K L -./ƴsdUFh{ h{ CJOJQJaJh,OCJOJQJ\^JaJh6SCJOJQJ\^JaJ#h{ h5;CJOJQJ\^JaJ#h{ h{ CJOJQJ\^JaJjhG|UmHnHuh{ h,OCJOJQJaJ#h{ h,OCJOJQJ\^JaJ h{ h6Sh,Ohrhh,OOJQJ^J hrhhrhCJOJQJ^JaJ hrhh,OCJOJQJ^JaJ ./23456789:;<=>?@AB   d"1$7$8$H$ 1$5$7$8$H$] d1$7$8$H$ d81$7$8$H$d1$5$7$8$H$]/0B   ]^suʼ٭ٞٞr`UCU#h h,OCJ OJQJ\^JaJ h h,OOJQJ#h h,OCJOJQJ\^JaJ)h h,O6CJOJQJ\]^JaJ,h h,O56CJOJQJ\]^JaJh,5CJOJQJ\^JaJh{ CJOJQJ\^JaJjhG|UmHnHuh{ h,OCJOJQJaJ#h{ h,OCJOJQJ\^JaJ h{ h,Ojh@UmHnHu tukl`a{ d"1$7$8$H$ dJ1$7$8$H$xd1$5$7$8$H$]x d1$7$8$H$ d1$7$8$H$1$7$8$H$ 1$5$7$8$H$ d61$7$8$H$ 1$5$7$8$H$] d1$7$8$H$1$5$7$8$H$]gd,5 jl_aWY79>?@N? @ A B L!M!N!\!# #$$%%´ޛލv_,h h,O56CJOJQJ\]^JaJ,h h,O56CJOJQJ\]^JaJjh+}UmHnHu1jh5$CJOJQJU\^JaJmHnHujh UmHnHu h{ h,O)h,5h,O6CJOJQJ\]^JaJh,5h,OCJOJQJaJ#h,5h,OCJOJQJ\^JaJ aXY89?BCDEFGH d81$7$8$H$xd1$5$7$8$H$]x dI1$7$8$H$@d1$5$7$8$H$]@ d1$7$8$H$ d1$7$8$H$ dh1$7$8$H$d1$5$7$8$H$]<d1$5$7$8$H$]<gd,5HIJKLMN@ B M!O!P!Q!R!S!T!U!V!W!X!Y!Z![!d1$5$7$8$H$] d81$7$8$H$gd,5 1$5$7$8$H$ d1$7$8$H$ d81$7$8$H$[!\!$$$8%9%''x+y+,rd1$5$7$8$H$]dgd,5 d)1$7$8$H$<1$5$7$8$H$]<gd,5 d1$7$8$H$ 1$5$7$8$H$gd,51$7$8$H$ d1$7$8$H$ d1$7$8$H$ d81$7$8$H$(d1$5$7$8$H$](gd,5 d!1$7$8$H$ %+%,%7%8%9%'''N)O)))w+y+!,+,,,--z.{.|..//K2M26373 4 44oajhUmHnHu&h,5h,O6CJOJQJ]^JaJh,5CJOJQJ\^JaJjhNUmHnHuh,5h,OCJOJQJaJ#h,5h,OCJOJQJ\^JaJ h{ h,Oh h,O5#h h,OCJ OJQJ\^JaJ h h,OOJQJ#h h,OCJOJQJ\^JaJ!,,{.~.............//L2(d1$5$7$8$H$]( dJ1$7$8$H$d1$5$7$8$H$] d1$7$8$H$ d81$7$8$H$$(d1$5$7$8$H$](a$gd,5 d,1$7$8$H$L2M2 444444D4E44444)5{Pd1$7$8$H$^P do1$7$8$H$ d1$7$8$H$Pd1$5$7$8$H$]^P d1$7$8$H$ P1$7$8$H$^P d1$7$8$H$ d81$7$8$H$xd1$5$7$8$H$]xgd,5 d1$7$8$H$444-4C4E4a4l4v4444444$5(5*5G5H5r5t5555555ĵ|`G`|1hq>*B* CJ OJQJ\^JaJ mHphsH7h{ h,O>*B* CJ OJQJ\^JaJ mHphsHh{ h,OmHsH+h{ h,OCJ OJQJ\^JaJ mHsH/h{ h,O>*B* CJ OJQJ\^JaJ phhqCJ OJQJ\^JaJ #h{ h,OCJ OJQJ\^JaJ )h{ h,O6CJ OJQJ\]^JaJ h{ h,OjhUmHnHu)5*5s5t5555555_6`666x1$7$8$H$Pd1$5$7$8$H$]^P d[1$7$8$H$TPd1$5$7$8$H$]T^P dZ1$7$8$H$ d1$7$8$H$ P1$7$8$H$^P df1$7$8$H$ P1$7$8$H$^P d1$7$8$H$ 55555555555666Q6R6^6`6r6}666666666666©ޔ{lTTTT{Ph,O/h{ h,O>*B* CJ OJQJ\^JaJ phh CJ OJQJ\^JaJ h{ h,O#h{ h,OCJ OJQJ\^JaJ )h{ h,O6CJ OJQJ\]^JaJ 1hq>*B* CJ OJQJ\^JaJ mHphsH7h{ h,O>*B* CJ OJQJ\^JaJ mHphsHh{ h,OmHsH+h{ h,OCJ OJQJ\^JaJ mHsH666666777777 7 7/7071727^gdYF d1$7$8$H$ dm1$7$8$H$666666666777777 7 7 7 77777 7!7$7%7/7071727ɷ{o`To`ToTo`Ph>~h>~CJ6OJQJaJ6hl/h>~CJ6OJQJaJ6h>~CJDOJQJaJD%jh8&hqCJDOJQJUaJDhjhU#h h,OCJ OJQJ\^JaJ h h,OOJQJ#h h,OCJOJQJ\^JaJ)h h,O6CJOJQJ\]^JaJ,h h,O56CJOJQJ\]^JaJ h{ hhK0P:pl/|. A!0"#$v% P0 @d@: 00P|. A! " #$v%0 @lK0P:pl/|. A!0"0#$v% P0 ,x,x: 00P|. A! " #$v%0 ,lE0P|. A!0"l#$v% P0 @d@<: 00P|. A! " #$v%0 @lFlna2(M5\ JFIFC   %# , #&')*)-0-(0%()(C   (((((((((((((((((((((((((((((((((((((((((((((((((((X "^ !1AQ"2aqSTUVf#5BR$'3ru4Cbe7ct%&s6vB!1AQa"Rq2S#B3bcCr‚4 ?Pb}+e2qkSST٣ӷIV̾+UCfUhze6^N ʍzJݩ+؈Utlj8hS[彟l{P_%3^ON2r|{mg%4廹;iv$MW""6.Qj Y1#ȓW7%Nn׻8#јXQ/(ͫk|쮺).rIN9Mn}&EXEndv¦?ʮI4΍rz+ϷHS&E߰ޯ@a3mRWڒTe,L+0MK&'xr`kkUѥ,m+x**k>"mr 82V aT':9otԶV[lv.2|$WG;a},+-nKn[VWD絨ou Lrga"p^-U_7$+]Yei-[FzKN}w)tU4jԪHm&2~PUgi-w_Ui]r^yʧ<%E6jkrƮ=eY/e{ۏ>YjҝQ^WCUG꧒,W7trB٣9p#UQW8.WwG6WYj2JxɆ h#%vęTG'4ԅik)OzxEy.K7h[ki#TSK?9aM4Or_=#V+6QO9he]*'tv2si]45+z)NYp}fM+#j.[$ ulS]kZ.&YY-LuRV5s/fF4? ^EUyT*zpCq0je߰ZkY&N*o/9\оUoLZʳ]ks׃ΎHts"0^R0vyO":UtiMY6ڳ*EN놽Ʉ_1I.W:9UQյSF.]쌤WJU,#rN^ɷǻD C*f7ȍrg [AOn2F+X#Ux~-TRSLs#W-䨼IMQ&Sa-9e[ilhtrjBHmW5#IE:x5 RtczL=/U3wvVݞ[^*ijDDD⻼u+O9Jnm;B'R5VmXG>&U6+et>(fY7exy^U 8`̉QNN˕=erm,ΉݷlR篴CVn)%+'}iJsT^[_qNZiV:ͮL)KkyOK+o-:(DDGe>75$SO|䷱YJfsDX<*Tj/VBO+Ti=^^z^mm΢s:F+Z"kLsN7e+D|Ǧ_W%Ri؉qSw}lQ',+PۺEDEv^gs&>./?+D|Ǧ_W!QmeŪt*6f+z:ziYY\ q+/ PeZc)m>j/VF [ *nNS~rI*RgsBaȋ=&;{)*iiꢍ< r,~~G_|ze5} mY7Sle9G|x?5l+5Z9bH]eݔQ k:ْ۱;X]y.::AAt}LʙH+ݏ2;&zG|R>9t>dUkN9I<-5ȩҘʍun9}z}E$L͝r.S)ESiWH!yӰA,(46Grc- _2#~~G_|NWMoZ}%*rF4b&꺈d^夥% q#-j *iըUT]%/VLqZ#毾DpQүLcj3Wx[b F1GEKO4_/HE4H|e+emSQK6*f7̨.WMʽz6[:U;p+4l->NjʣV%mNrrU_ ̖'lj*LqZ#毾{ŗq ;[}]gn<&ee䢦uB&^d,J.^t#Ây1\K#E9\"%*׵mA66e\FoDK8RQ놼>I3(TwF}zۅ=dDa6Qp\xF ]E#Wc tW=2h0ҎBLkMumJ5¾:d xu9gdms\~-~G_|ze5}#k-0SSyrkbyvXVvFŋ8ENx_!`dU+N)'2h>j^Fl*ʺK}Tnךek1_9 F()j^d_W~G_|<=6m0l*N۞wZpTozY4!!O&vC]IcY⬉μLqZ#毾=2hxzn*-hue^5io=4ᦅN{6TdpōΧ]zT4diJD]*vKo_W~G_|F $,UINsۚ\EJ<mdk ˕kF/VLqZ#毾^0n¶&uԕUbstLqZ#毾=2hcze5}+D|9:_W~G_|ze5}+D|9:_W~G_|ze5}+D|9:_W~G_|ze5}+D|9:_W+R"_ dn6Rn7Xqó+_W:#/:nu\o+MSUQdlmUE^IϾ(RﮭRRAlPx.qōxe tt5)eSIh0oW'"e0wIڅ5J?q")>'<5>ĪeN+3rjwd֦ՕΕ,%]3IQoTSOC4ܑ-z*q4mW=?IpdW-5l1Tx._N ̯|dRZcowO9ʪx>s ک"mb"II~c(H*vi%)煭ǣuGHW{=vPY r0rQ# TzhҶ+-#KpE]ߤW""xmx/^M WH7K}su-{}KU ?o*ayq45j*t%4[mUTMׄb)͸(Mۖ߆ݚ*Qqs:K|V ~9W̘fHhuIvM.ˏs+aE]79Q#gnӓPhw=m;/?NoV{mDpZdZyWE52qkQPit&:G85lٷ:jM.eCY`$tIQ39VH:&3RK@;FЕvhDX}Ce&\^/rΔoUpNȝ[\laN j.?^H4s*k.evf"x :Ι_HԣgRVJ뵵x'tnu+;EW6^kvM'C5Mf~Xi}JUq1[璪exU#^slm? XC38ԬfhӏjE%5=R_o*H7e8&W=l)QS=X`t;_Cmo=QVKQJP=bE9bh@m; 'ua66QeҗKS[K|QwvTVIsَDƊߣ:) .tPɕmC^ݶgiS'`q=)N9YGe)ldw7m#j tfTk$2ENJK?oR-ozJ9UઙDr'.FGmIo酦 jFȏI^;eN)X\G8sSX.vΌ)/Q)ik6^ŸCxuJ.%jFϝ׶#O{ʞSg+)^/G۷kիm%~K5vTZֱQ?";b5-O|Uj{x";UNhԪiӵUts }-\Sj TTJf96e~ӝ֛j 'HZ5S]5bJk )K,ekFƬ^(p[A:[= ]#t㮧@@ mKitwıVZTr.ƺۧ$OT_ ҍk&pNҳ؝vJJˤtGBS1qTv6"%Z*uIyim=\ 7-*H*mӦbX\IW*a}rJ*M=c)i׿W6#$I6Ȋ\1ڧiw Me֒W I=]JXS(pNXʚc:RT'O$o YK{\/svzMؓtknW~"橷 ynVJC-IW5}s$ ecqӒeɎ]d>$Ge\V:p}[{jCL;& XX]U2dž$s:9wa!H项IQ u *%dns31~PKiMYx}^z-eI;v.K[is^JvCMJ觍ØTT_*)֙kS""TCލWRjl̥MP*#h[5 .J UNmk[K۵q_mp8(;>kԗxc(d"ʾ<<'u=U;0Kkn4ŚzX.FlHƴ32>An)'vmodždU#ptyZ#X#]ݔg<i֥6ł{awI$DN| K 1oǨ[5ed ""S-GnQ?VhMeK\23I GGgzrU'˫YSwo˿S_>,i4DԶXm]RJU6ǺHD$JW+7mx6Ox s[y"vM/TjKM2A;Z,O\./yZRͧU> +ØU8Sa:62һWJԌۭ׊II3h+}R2EQ0I;GJ]+vU\lYR˕|^҅T54s>rIwUg¯+{WJsUA"Ut,n2cx\P:ٻx?$$W>qIXj6MTDL&.w;u E5#by9:*p]H'n[KCm%AvZF@?e8p4Vch{V9u'?2rכk|II(4 ޓYZtYX0̏FCnԲ:uڨ9r ]f5}}̴kE i6zSm1UV]wNjrÁQk*)MODT6yV7#v|UU< VwQWH Vm:J}fOe6Tϓ(j~Klz&U#sTqPn{[RQs}+oq'F΋]~rÙ~by觠@۳iUL*+^msÉctUg^,ZkRT[lrHU4-a2gԠ4٥luTfV5<-{)DS}S8)**6k2q[5]:jHmz=cisDޓ74UvܣF3؈N6ZSTXmUӮ$|my:-Oj5 މ3ݪqcnxqNz:du.ו}tЅpZ,T^deSRU*1͹Dʘ,3QjVheck^n!uv--}V2ExntZGZpT>:G1WڍbiFkӍXm$=CVv`j@դXu'>\VFV2<.|Tǯ fuyFj'KY#\=%KfnF"gkpcxPFk6l<&㣶Yw-oK7IZkn7.F{9*kL oK~U,p >?"t\o1iJcHؤ1Fx(QQڶpP5u>QYi5]EAZneۚ⼳ρn֨Uy51$ؒ|{^'?2~TowmmRi %Xj,,tl?r7D"7[keNYoI M~牭\ G{q`^N536ZzJOת-u]v.P:Oԕ-UIANCv:k#b:2z8~ t(pV+)fw&4tRED|rԑ؈C!Nsi/&VzodI^ݖD0ikO嚦hM±O\˖6"x{|oU#cTjg x/ iUyJ_eEֵ{zZveRiimU*DG$mNsZ(&9`^Xm:Yh7X~JתGSRĨHez{itU>)[1VyE_jєV[]ޯUkS=tQ[,W}2[̵W$LMV7"=qernj9tVZuYnH'Q1,Iʨo>p8]E[a6QB69΅>)֒T*f&X8ZVuHR!d80Wjm44։$Z8GO¢FvS=X[l;|ʓ41 L/ZwOYue=kglۑ;(oJZZI]Y}[5:lhvQpD^;=#t-LOOXveMPR+|D\'alyo]5MіڪVKN)#6xVx'k:zBU٢ {niXE~R*fqewz: 5IS$z~Tj]bR]_+.MT㥏w;UWUOoJYVi]vwk]2Z\j+u6֖+3Q)Ɗڕo֧o%5dLR鈩k䎚-rqP$=Ϋf8ms8SWWkMtmK$ ֪=VE˶,zHE\Ҏ]I:i]i>N۳tC-/0{=NܬxX#s.1ωNjε:hISݓ2M͌m*^=X)GTq4p1N};[Rm7zXi|V=EA+ l\lr?{rHt=޶DJ%kh~yiumIkO UM4O|dx\Ǐ_3o۞)nos4]dGǴC0=[E^<qW6ތj*.^U0/z^M][}j"w;FG;eS<-GdzbߙĴwQUWi^{LzDU)}dqibiQSvxAePa[.jh.lm㢦] >R3!դەޙtֺih^ujPGV+%¾~5ܫE7i6|~T7uQ^Vf6gY&zgjaU{kj=Nk*jMAhIo1O R϶$"Tǂ6Ij Kfp}=L{l;S^KR^Γee/-kmpWʭ35I)f#IpTO**. F=UAL˜|Ium:%*Dc|5T1;JST0JI:_div#D:cevWH+\'VO-0@j)榓wS 25Z^)fg:/b*Z#Kimz\DT9IsY\t(aתu쵷>[vkun.wE餴ڊ=lK#ٍ6{9"5|$\*DX5ЗkmZt]%{ ggqKUL" A NQjmV}MqC+!dΉǫUNx^ji7njNl'j_5=;[3ۇ OGH:>R䮖vV-#O_aTUnz_GSg/Dڽo":jdEz)]Dȱ#b9ZZlOYYKqk'bK$S&U!z~G TJWM4+W8(>}MEySAOYj|s*x675p".g ťlz.s5}W\/TUUʛPU}ܒFCغ_[i@ՁTG+.1Ϗj~vw)oqyv`B6 4K[GfVPqvdP*4p\uZ!#QSU)Rj\%:VGR7;rFGc a 9$FG**j?gp} tYڳP˸U nZj16pYN1gҔzOtiIt{AMlN=k&Oix7*^I\ Ӆ\LRG}2\[ӫOA*?5:K2uup$U%DF9UU1%3U+Lzk T2'<0^_HpI=S|?3k5nuR>{GV]57G:L9'玢}f4zI08qd8iug%`:HwNoX˅MHw-Qa:>j-7i\sWGڢZ*+YC3Ho6U|%&yiitEH^1QJN+[t XzE,ZD{ևwjx.FvL/59VjJ+DZDۚ&U\vx"y&q&7G7YtS+i]܏uɜG3%ۣ{ĺl˕}=K2F+ڊi'_)az.3JWnJhk{9Nbx[6yaڕQ\S`#\6Z$YtEtߤ^j)"VmNěRSlLL"*D§v -*;}]:ͮ쪢 )N8 Tdڵ_[̵&">ߛƱ5^H*n%c|Tkr3q-[@Cg޾޹As\|rY#Z0Y^VZ଼-ܹ\Iiޕi*ƭVo\n8v2]zSWtmp#c$1*/\W<-f?Vwg2EjJ[]nۨetMm*sQ%^nuR:*Xq D[Ȍ0pmmr!ɽ {G+%dr9jZHi/{ޱ(V|%g%KdytŨp> u\SsW4jTs(DںGJE4m6X\"4/HzO>z&dXڊ\JŷB{}{c߱EFʉȜrOz+&TyɥeոtwCq(⤆ZvPѣ)G79\2W ]5%?qC 26$sU)]}jWVwRdG66q.0HltzoX's8/Ùy襤iu][nud^d,M&:䇸\]ޫ69biGcEE+Nqs_&MUh{QR᲼JvXXI+uȫ,}Kmk=S-.bI 1y9S5{q 5ZZU&gx?gϗ4?-ߗ[7P^y WW3S+1\O/ 'Pi>o{<:ku:س䈩+pKҵ_mZc-V EM2GYrHe7*R$+\W:iVhCkڝ X H>ZHzYв7%$qOZnj*.&N-ðNeuE2IR˕s5e㶹D` UNN*;qm_kFsnw;]~(Q}͐l,(wϙIM}m5]+LjK3;[hg)9s]u>RXwJYf7?1+3ޙTEGHK _F4 iⶹ\3 <0և!ڞ參eխ++ Ǝ*]]cAt\#Z5UU{pR:8>*;FHUQղHfv2\q6z;eI%4S_["1Gv#g/ SS?4}*}]98$쒽W}*g\Zt})nFM;jdj/fҦ|֬U-6_[FdY<-(D:(;ƢVϪ:S*gH4J9A ;**O"n֯Z_:Ulgvl/WWݕ|s@9+'/ZJ+O\>^,uk5ZMM[o}=lFS"+rUUǑ:OzVt}<[ݑ %%=]r56iE]U^D8$Grea=k^*VȚDTVHNc+@ An3OS ml=1i3pUƪq2Uz".̼:US>W)^VOŭ{cS)Үl;vqԯkܞM"߯VPQNSƈW;5UUUUhL;fɾ-~Ҳwamn{6UT*sgH'CݨSQ5l.n(zp®8H6Q#c*pjHU_&zPHv[ t[#W2UNO:ጥI5\/X$AA歸ZTvhjV)Q""j&x#׹gX9.Ԫ}*h}jzM>]lLXT^ԥoD,5>+*9=GvSJU"e⋄DϑN5ؕ4-+=b֚Cţ'KSUS_%kvr7;(J5fs7ɜ.3ϨBmOXv꤅Er©~?5Wˬk4]RW:UԐL>#W=eE¢"7N|TkYowm8$\msInwr3>3?# z}K.T))|&&vy"aSq;Nu}D,'n25Z#0pEVIE~Vj nZd}GSNS>F'"F*+rDZkO<駥OM ei^N^,:Ijtgp9g9&Ez)tʒ[]LкJʹdhp^(x/5:Cu:Q{GKn+K.rȘܙ54~]#Nভ+=#G'Iҽ!*RpO+䔓庵^҄.q=vet6CC?sTJ]/$_!['_ n*T+VS199TOUH&5s1jtIO\pOk?a\k H EI-_V.nZ.UV{-.겖F(" O*W *etҿOr8'Sog[U_Gt06Τī M9NidNKj[ &JH)orK/.\9?c!g۲|+6&G"VoGkN^5M֮=sK=J* UW;T ¦ BiҮkUje|2˺v 9cNa#{mgn>;quȺ1ìd4ji:G,m\l1?_ ʾ-{\׊kr_{ick"fMTv8yvwٶzH?F%nv7ڱ1=s ^ k;= 0ad,F7,ʢ'<ͨ:؅{7v-ּ|yXnmԻtvǦvYы;ac ETG,]_rCSi}ej^S)+P]#mCm]mV9d#Y Nw[<8lupb*uoyI'fA-q}Қ` HskQrljetUlY⬲[.Vꭽv$TG"=՞]G8NT2ӇTm;\ E+g-Rzj8&},^m֭/A5#sʳ"a|TlD8)Fggy.%]MUKq_픲F"aʬFG"78yTU:m7R8D&xu}r͓$$\ɧ*,u{m:ƻ3cm6U2^ ˭sMVԵav1d9XE+*:ﯮml!ɵb{VjZJBC ]Cmw "9ry)OBtGZw/Wn7GjMGHԻQ[ǯsz9eRPX2RVT:FFaUnDª'ZWY4V$V'< l+sOjonJ E8"*o1o+ TKꊞ cؑO#ڭ\Qy)]<)ʊ-ֻr]DFw{zPm=_gZrYCR 5^5X>*u6z?{(v*4&]ۗmA"jY5]}GFW5{}EdeM\uI$nnliS:MWj *Mi.W1WK"Jy3Q4/ MF}f &N]IQ5bygeUy5 TJ V^U.@/v^ CjU=W\ƪڨa[-&sb1QҒn9- -=*m^OiZMMIQ{])\m7%ђ NJ)=]RSvi*XSI#{vH^Sֶ T >&U:_NYfusPtKZW/_S\??wħxOK9:WGm+KZW/Z#'+ֶGm+p|NjQ[ֶָ~~ {'5JrAQ[\??w}wz_z~GCNȭRİds*K#x/oCJ5taB%ڞݽgpDU_!j$U  b|SRT*yBs4Tj= g*1_}g*?F_ɫz}g*?F g*1_g1ս`gZJO2S3|hO;T˙GF/j4TIQQpP͎ 2>*Ts7ѪDzڃYQ[rj+nˑ^$@^$A9'5OEwh$/>I;/>I3O3TWFKڃڃ4x5OEwh$/>I;/>I3O3TWFKڃڃ4x5OEwh$/>IH\jj$dLm3JΤ&]JgN.r]b ۬J;$Y<.ѭUGUl9ۃc[k襟v##Zit_^/Ue(4UZJ[<[||ʊDtEIq;TU` \_-VZY6"9xڊ ѵ,H{-N9PI)OdɈ¬l]xTT]S(TdkXyմR7QT1(>>WĩEiU-[!JWfE~x*QW)jSS$(HZG=DU8 F*4y԰(UZ=yb8pwm]=6%Fk&q [RϤ7Ic)$]h9F*Csp&7u6iӊ(-oK>?8Mo4X}QKCy?-oK>n|Fa{A?>(-oK>?8Mo4X}QKCy?-oK>n|Fa{A?>(-oK>ݿ,Gה=}k7"dmDnۗ)~08a\F`!+\#{z>O"j:h) LVѳ]f>XlTOR摷 |wҎ-,:U_z_oբ^sۯ*8"69 d܆_.6iLFD؈iMYS7Ȯ\z ܥQ^J+=U,o{z\),#etI6|zv/:'WAWJRwrf 4Nioݗ^}$UQ㋢DyӚigC4חi z>NSo)xUǻbL|-[_i4Xu>ئ3Α1OrKʳ*՚F1d[3Xʩ߯mܧte*lhG"Uki(&Yp3>/"1J9VسeQD^I#rZQ~q>A Z/!.jh*ZHYY8XKD+qjis߱hr'i`^"0Lut]SDivm18o Qhls:Jc8W E_^KelNEahl:z>OOw+`w/g?%oEaKJ,[þ{=y+~u}yD~ '}zV^^~J߶tQ?Gߤhl;ן]/GGOw+dƓj>Wi_s{=y+~ _voUz)ѭz679ZQ9/2jPqj]εiNծv[Jڟw4H 4[@Z5Cm(`QUUJ|R:7>y\e_; t/tn2t~TrZEMY[M~y9#Z '*,\u=rOWOss\⪽g:(sӡM҂w`hj/GkѱXs3Hd;.EG&Q)?Yɍ:SZ8zBJ\)-_1ж׶贎dS Gp"}\ fsv%r"i^pRAOe2͌x>Er'}sjmvQic{W/W,3 _՘KŗYA_]=VMvm4S:ec5";9>k|5z}s~,{҃ƫ*H[-R5= +>845 $G1֊nSIPX0_:c[;jF^G۷~Gr11Dg 3RS$P ;I;CZlsZrן_p/Y/s_ST~>KM*K~|waƱ['x6vB i)ĒXfϒtLc*KU2֓$dnĒUl|3Njf!sʞd>JizE-h9}wtSpy'ʙ=RizL`ZT:.˒|@=v^\TQZT¶T"IB3J1UdjmUJgRȿ9{a|/2-TWL*pr*t,|/8cJrݏݽZ#NVrfUt3op8Gϳ,U6詪XV7RD)O+RJWdXS.G4zZ9`u =ƙj(|&&WN&s: x]}-*>sT2I6X9k֕9$m-88ђQZYĥn?RM'[s Y鑫 gŨ`(/=B|h7QGxk=>-G>t#\ϕ;Yj> gŨ`Tu xk=>-GxT⊟ws>T gŨ5oUQS_.gʝxTԔqgu;k(gƲ+X*b6sCLg?]A/J7uk5ŻK3 %dmeC;)Mr UKΌs^^s+IC)B{WQNVe{zax:SwJ cx"T6w4?f,X[Qի$2kgsO٨j[:FDľ&6w4?f:C5_Qg_(uj74\_X;]~F_Q|KKkgsO٨j3:s_|M`liu5]~FuQ.k/ ?fsO٨ξP%~%1C4= 'Z)ڊjlkVD[}_QԬ~&k-\M{Vܴ2=RK4=or d󴟐qsh7ahٶEW=i:u$Tb6;*r󴿐s=K =O&riNz:m̒ƫRr&۹ޯr&N:rnJ2|Vn\QF;fD_4Iޯ{T˹niu54UYnK5niu5-|\_X;]~F_Q|KKkgsO٨j3:s_|M`liu5]~FuQ.k/ ?fsO٨ξP%~%5niu5Dľ&6w4?f:C5_Qg_(uĝ'M+9CWsO٨jVyfxl5EVk㱳{mC]M;rTlNk^^r 2n:пI҅:_nV壼&9ZFjeZ5$=4T>$ȔnrGUEE^]HX]s[|.#%Rob๳T)7k_IZ5WYFۼu5}_/][-n5~j;ֳKx뽮kod;]s[|.!볽|FZݞ%6 k9oTSNءyN Ԝwmo7nZkI%%TЭd-z 4Ope*bHJ|W-xաV%M[&R>9C4tlmn&;+OTYKlMZo8\RTK-<̍qs{UNhCaZ2X: Z;vXotJ,,2?gkmW &B]x3n<*牤 )Ќ$Җ|{lt Q2ToWgnL@!,H%4 ή')*vLd:Vij{pt*Sv6[.̝ctbp±x%J?N-Z"WegUqwT(u5ݱTI[vw-jfU?9 ?L?g<1S8Z</JyT6M-m_9:mZ}] 4zJ-{rru*p]ѳMw|᪣d̍$J"U뿟6R/R/(^S-E aRa|_AZ>GdK~_}>[/ Z>C3'=_l7K~r9Ra|_=_l70ѩ7mɌ\gڏz5\gܐru?NϤx6Xzr4 %53R/0/k1Ov}&Jj9R%ڃOrVGM:Rο4Zoܭ?SG*JPxsqgЩJYz㭼lOv}#ӳ/ Y;c(5f WR'inOU&=?58y tiEo1姫G(EWy࿪]~{5 Tt4r'7u>g8dSxV%^Ni9ϱtOtѾ5/0&δ㕭QÛW](މ5/0&o7ξS/ z$֞п|D~}^qpC:LZ{B=kOh_ay 2 7Mi ?@I=~W3(މ5/0&}_W?PξS/'*:I=~( ܯW ]TosH Ϟ|X:*rq|lI'_Q%%DqTLlG2IF%N 5ꛚQS~*b$w2EWrDL5TXjPYd F4Mi{[;O@nZ(]rHǤk+w&2dB%%Ee%KdnYڮ_U]xގZ4;#l+] _bKԵ;P"'3]),}'wD1ȏ8gS6]:YγNI޶]i eޟ:$$9gP:0Խǩ- CLg?]ANjc9 *~Y+7 *Ȩ`EIYJUQ94N-UU´_=}DQX~B;Q=_ꦛo?fX4oݎLh[(ʖS,HhZWƹLОqQ+jѬdzD0&O7;U⍓S J]WRUx=/o^'ј8h=.%^"8\\]zk\ZD+y=z26^HʨIon7 E˨;ꙛ*k?S%5 ZTDM2"9?!{M]ԞLI1~ :uyJ64<{j%TQ\>Oʘz-ԞB<{7dsɅCu'Q){&x5\9C%q${ױ/bT\P+DvXת7Lie*qV)Fb8e\HawlwcS$OhZȸL-eYLةGY9}L5-kVO*x)7GKqUz UkTO"s-Tɶ=ɝϊ9+tluMi~o[CX}0~]_W^V&ai;1Զ_+BG)'tݻ>zUyz7J;,FQY=Sg3]91ΞQ.7/:J0:QqG?E{vQqG?/#3vQqG?/#3vQqG?/#3vQqG?/#3vtxN9^~}裼o}h8ǔz+ T-e+bpl554[iw-uS&]Q#F)p!]j6"tn8[#*|2sz572f̊Lu&8䏯`a\Ecr*e9e Nd_?=ư=L隚::swpsD^\C E3MUٗ:mWe'|#mDT\ #r=\WJ v=XLrG#0HeO|,cK Zm+[sY~7Π NRM+b}wpS1ܮx-D7&Cg˧̷TH袨ZOo4Eϟχc\gNsK}ǫV6o6S"Ȅ6oz"Ȅ6oz"Ȅ6oz"Ȅ6oz"Ȅ'51u6wtu.(!e4[=n/=jdk"<tNG##o7,jRC"7DEED6,RG%z6E]EH㡛me45UC45ʆCIBr])H6w<"Q jLRV.Kr]eJ*-#H$ݒD:m''ԿĥkhO.4^;eOV/{?"@e~_j^?2E[1J=}cyO516vcbiXr*xsU^$`t7ƊDO,{r;_)DGND69.(U&Ggy~:ow|f5h*}3MUQ?H#a$89MNsQQQ2.%VUXjcxv$z\x305QNV{+=sI$9vP6qT1o?g{x8Q'hm8#şTHދq "pNgπI)$}Ys=;M'_"1sTƷ<=KWf'ݓ{Fgsc=Sg3]9oi4?IjP8樣6l9&vQv^AF7='y%/tAg^AF7=#)}{g^AF7=#)}{g^AF7=#)}{g^AF7=#)}{g^NU?{u!9oZ{}QAޭ{g/[lbWS,yOGRXZ,/R)Quײ$DQ"k\HHѪeQԵ0Jw1أcvX5IÑde$p6:\@\;QSѻ:_%+djr+QS Yñ=\Ki.Y;O@hU5,c_ck)[hک䅋+**ZV\2LgDSZVfk6cdܪFssnW1cIjaF>*|M**᧫t+EQ7>}>dZlmsvjiX݄TGc(ETRFûV Um+WuyM~uN-Vi S> <QW8*8L\U+΂(r@MFR; 9AŽ%4AZ<-(='iĶ\FQU9rw'Zy;E{ *FM3؞U!zHmQAQ˳OxF 尴 Q=S MXګl<|UxV)sK/7UǙ.ZFJ2Ll&\jGrQ VKjECTUF"3ڲ P,j$mo:&/A׭9N2;ߓ&IN;ߓѦ^ㇺgp;;\ycecjL5뇵?%B?k䎢6OW"""c8(_Y0>=Zh ltBUwʣ|YO[K{J)^"5p8/.ұmZ*(tNFHUNY/.KvRfGex֔W81P)?ն.W5*dA<1Zċz2pf}mɕ2ʐ飦UʪԈ+O;'DNligȃVᬞvԻ+貨W&2*x=xs|̩c-x.=}=Qƽӥtkɰf1Hr{ix~'B\VDS ֤k5K-͹e:*m"s=zTc"GXjW nW[g>ks3l4ZʧIH9kZӗ*t nOT+(O|Ե_U++U_P\_FHvUU1B%'3| iIuݛ^;rӔo *C 9WMLW,Zz[R5̏{LN juqU+5j4SKqK;Qr5E9'$ZAM h9Q8zQC_WQa= 5M,t:<8呑5EWQ\s+5#jZ9z*/^zԊ5W˿[FaV2nn =ɄqӶ66g9xd9G#Q]#yQ vzlET7egcVz>%/ْW5riة<{EUS:Nʧ~^UFo5-enqվ)%Ezf W/w*gꪪW*~W8Jr_--{Fgsc}9UE7z{Ce"@"$V'N(Nz DsyԿl|_MtTVv|y.æL`Su/DsyԿlx>e4#|o:퇢9_>ez}V=RWG?K_^l\9_~+ uX>TG?K_u/zyr>9WL׏)G?K_ǖ)]4EyHJ r#Үetzײze˴URV5<+%/QZ͕TKtWnfgZ֫:XhRwQSӏ e5HiږgLN*fHImW1|8|N#[^jZzaQgV)6')*4A;exr*.1¼rTWFUꫣ_Vi$Ϟ(7 sQ#U\S|-tOlU N)ëSMRyzN9Djv>π3U\ S\HwrDDriS?&dhƵUy9u@٠FGj'}kB6jJFIF``C   %# , #&')*)-0-(0%()(C   (((((((((((((((((((((((((((((((((((((((((((((((((((" t   !1"AQa27q#BVWu368RTUv$5brt%'4CSfsEXcde&g(DFH1!1AQ2q"aBR# ?ڧT6pj &U֐*l* qDn֫T&ku)*tKHH* gzq']:bBt'PD@g8Izqmc=4uw|5vR<^7 dž9 qӫdfffU䅶+ BzpGwi[J%d%iuW=|$9EKMSL&$;7f'ˊg(J9cjNf7PSIvbegBܛ*iJR“@k{N&ov٧9τMR;GڭIT1d3#:LtΤ_4==yʘSɗmfd;Q a'4-0G.7f,%2@f:ƻ/ZLvVe͸(dz A #/)M♦ШKKst2 #Vdg `z~*mX8jpFA/vero̸PzxƘEEJ2ʚRm(( BH.(+4fA_֭2u:Ch6222(Uls}0ONKSޛfVUBiT`ϱPDRJJ#jAR;N3CV 4UI:-&rS{iO<ҭH8H$0uuD&<2 i݊FRxPrQ bԪ"jAZjCP0Ki*_}-7^tNrPM99IyŸu`\$ϧu_Τ5f1SgTb%V{ qӬU+qIPqSޣ7k(vϻ<\[xj#B8!+NR ԍDEV\mH_e1,냎#(pGзA ˣ[۠v9?;%, 7ϩ 4; Q*l@:Se[iiPni JB@NRH]P" -Iʽ10͆P^3 Ob2ҹe79٩H=G xG>ԫx"ͪ9@/zV3rU/MMJ,:SBs6!eڞV*i My¼ԞdcyzTm?bۢZ;~}jr ;AR9Z@$c{ wXjvjNWPvBd%-S6R$y*0ݨNNNθeSθB`6`VkS6v) fzb0Tv)*Iڣ$ VMbfd Djd(9#6gjԜpqp8P  '| 0!oZ5[bCMH}RΩ٧d4v~Y~30Or;BMUkݖe l6J@Y./nRi"50~Ztkf#IeӅxTqqP tiD U+ZRP(qppՂU>qppՂU>qppՂU>qppՂU>qppՂU>qppՂU>qppՂU>qppՂU>qppՂU>qppՂU>qppՂU>qppՂU>qppՂU>qppՂU>qppՂU>qppՂU>qppՂU>qppՂU>qppՂU>qppՂU>qppՂTtyqzq~p@*=Ǟ&?q55T{=?qzqsӲMiTsNʲGGua'չ?D4r,-:f;xyEՠRI5iחڝhUi< 0XST ( R}79jS,0M5ٮxi]IWrH>sչ}Ky7>Q:ANMvQv%#'\> )_f׭NTLvMR\R\[HQ)Ow Fɪzx܅Pnm )IGlVÛ?Kk|40ۚV|N/veմ:-h %X |k-BGel3N>( @gHzM̄q [)$Ĥ=H8Ys}Ɍnzū3nMi{I-框KH 7a%8`ֻoao/vӌc9ɏomg쿜v]j1qn^3 *iZGry>W~V+څ 9šMaR[j^Q?fT? vtjAD+4:G+Gk)Pڮ\2T9C)S@i-SO!MV(` < _ {#(3Z-!)Zw%i/J=AՄZ[xGHedB^hJ46;9-%V.M;.CHTrq!ri-(ˏ̽ImIRYe`% rI$Ebҟ;4"^X o>mpɵߞbԩv!D~HیbmGSd}ˮ:o&k`'n;u>V[7-Lf )n=>ئU޳Aq RBIe#13Zqɋ W%*T c)-APp,rwH8<Mi"׶f;~*֭ByhD-+JRURAP %C$ ds싋Yv+fU4\ffjU<$Бd*t|[AAL{c1'lѽE T5n{^WKn% KJӒHcz-..۳ڼԫn<͡n)[ԒU,x+6 A,LU8;U0CUyPBGeVXJSgS-WKfiGy[A8xӏvmN:B2V6<$tN!%\y(rAPZL]4ft~aŸEk9RYA$ԓ Hݺd~ԯ(-j5C/JI8PjDt$-G$RQ)$r̥JB|xNeozfeAҹ0PЊ-VZ5RKNL0{Y(aI9d$Wnk8~paU*o%uN~(F<*O[mհË dm'SڋT6ZM̢ۊjMq}b vAxd+T*dFd,*fq]*9@z0غqZ([.MRPp0fT(M!)WK14ʚp I佢NH򼩖%})QnԽKq--brU[rM;؀xoRHuZTO,XgE-Tpg;v%xOt/sij2n rfNfVT*Nzs>I+jMXVirE+; 껨 ZַJ$!IF9%Jmj'š@J3A` Md4j7ZjAŴ ANU V#wHyv}h3sqd'aP8*Q14` mhmEOP)j=HcԫNHp*jg` vΩN/<Hj"}[n=V 00:@A%j(SiW4$,578+)$P8#>FYKV]W6y3]?-O_k_I bIUj-ztuRRFt?Dpxz}܏κEvԶkwj8k!@!XPcvemjmЦmhQ=b&X   @Ht(NnNqyڙ*(lzc% \UuKksH{vN3=pc|1풏?FȲ{+LI!;p$|iےoEzMZHdgn3# j5 +8۫1mI'}qYC;}Q)ɕ.tF";^oS84vf@DDA79    '`A AqkT&ڑ\@QBtg^K}\%RB>؉IgٚjbYaq%i#!@#jj3 #+N}ݢRFce@mKM}*R0%6AbeZrVځ @ #bӈyRNAcU>> RšJ:@HH <s <A:**SJFTf&:#>BGAǶw?l}rթw<1u :TKi@',0FImFjFqWT˨դt~A *r>YXjG/C'$Ԯ rKC$;GEM?D=8:s HYwR*Ēt-O]NBPas6^.AA@A@A@AL;zzJjY>dzDTE yiv^z+9yWjI)N9rjnzޥyZva@*N$`<`$` Yy@M:Iy'_"QIBԀۊ$'9@    ܷMlMN_a-)Ge4I)C=8@EJ~R"djQ6BC*AIݻ?EOTM     "QrK^UV,fuAa(Åہ$% 3<`u6bqMb&[L0[3fzAsx}<@VʔӈPG߳=G r @A@*fy0o)YQ[>r8BpkCY;%#":nE/3ޖS ix|PM=- :a2HPy)O_J؛O%fC L)ukJ:oڔD>6OϓMMs09L&lj޹"6 ˼ Jk(jN!P:e *<`8`Jޕ"ΦTL=>Y[ip֮C}I "9O>_G" Msrp:r︠lKKmr HS/wʪ)ۭT/.V67n3[znRh4 et-Gq$8ܚ?#C)>Evm$䀮*9+觩0ǸBVjU-4%BB.cGŘh,ٵ;O$̳-,4u! ;0>X*NPokr!H-zgLJ1qƫƢۤ6uR{܌ČIJRҶ!s/#yO|P!ōbO'p0Ӹ!et #]Go:c=AS[l˶PSXn*MR)g03=1}s[[q?.i +$ImpL+,ZP13\ru#Vu3Rפ,KSY v6˅ +QS݂8c,Ẃ)>ɽE P(z]29DIJrYkq+زp} zc]-&DӲ90>īxB*EXNֱ^N8<Lh(8uIC2[TN $0GSD)*jz3uOXzTێ̴㬦a(YUJH9@#T=Lʉzǹ;Xenz{@yH^~RTztMeHW.o:>գZSsVZZ͡[ġ0莓;Q;OԹғ8p98yŧA|VwYiIzy֊$Z:瞐ZS8T+ZR2]6&ZB7AqmI='Hsy.iauŹI6H!Ձ}XrJJvO/_ ?7uS>19_E-7~yj}bsWo|%c1ثMV͟r7;Ρ#/xyT[ Je)qjIYʓt6^63%s2ϭ#KMg;<#~jWu-s-7:XQIB9䘻TKt˪-4˩B RҴEV-i]J~JxTBҿ|jjZU|.NEz7!/#|^U$流7ɶ?|ߓ̕U.u]N0B0evA+At6R%@Ý)OڈTԍCiF͞l# vV%1/y%[d ;T$qBc[.9SZYmC;PND zD~ɧ>l?g#o}8w}ޑ̣ʎ "YH>feu@>B;ydkO|ڡk7OvυHqABՕ.6h ;ϪY3)%=(c;py!]?bRj\rm܋UffZ(K +%MpТ(g#?WKT*<9qi8BN02N< f]!.4}rЯ0~ð667'VOy@xEʝY);Iy{Bw-MI<"Y ߱Ď|d&|(ZxWl[{Y,İi) S9J~ڰѵCX vgaDJ㸌nQJ~XyBܴ¥,hgd? T%En6w0:%KH(gVlϦ<V TXC*!JS|wJ@ wsغ_h>*yLMFah7mNP:d j% R5r;zZ)rs+5w-IZ**H"믴M]Qe%*fOe[Q Zo-bjm^J=,VRbMAO "UCԩ< TǝjAڦZޔTy87(m1{T;V*X]FʶE<▔8  *LlZ'Tj%&_Ŝm(QZHHqO( XǧӘ߰gsfL3M}JV~B>ħE.mZ=pN\ԇ|Zzau[j$(< Ot1ΞZIf)G14FmIV} AoۮQ]@v߷Í9#/!/ q%cv@tR4Ru_~יZ3(v^a;@%kR (:q]aPTE:$'r%{xY Vƻ(\i4ʕY+|$-=sǛ'xơFmQL.2*B\xQ Ė(j16J=&YNK%ʒB $فwGHgj%Ik.\KuI$AF6齝U51is3mRIBT6HDy[TXSZ>Uf6Cq G#~*:Ku{ᛖnVfm ҉@ZSde~ŽS) /|Hq oo'j};1Ѷ].Iq{`Sh6#j@FQ^TC DAU6e f\($ONI䄂pqP,Oj])l>J[axR7FNEjVI_ 1;.ԙ|ޠSpڴRWLҖM[ڠv8 IH AeKT DߕJ8SͲN7)*,g90\5Z5i[NX`WJңRs)*HLUl;$StٙvSnZRR>KJh_az)e~-H>A`ݷMmi<+(iU]KBPv4n^A@gEc3:ˌd8K!=BqHjPX{&ҰJR C8=1w/=%düKCjFS/y}B_lNHr֑}2pG0zvez5)f&gU*U|<|"?rٿ).I 9ⷯztם-lUe={frB:ȣ\t^ؖIe^5)؁'vr:Ǯwz|fC-?B/ˢQE>Uo,2roT(Z1A0hljKۚ N1:*NrԵ+޶|F3w.EIZ-:o6e/MPBA= Hܞ R kNӥ]bnjwruz7-{Nх Ϧ${蒩hS2*'6Sl  \+)<|!mcNݴ]k\Q>9]S3zq UIgM?3 Yv_T?jK JSf^--I '5,A{PO7${9{x.ulTjrtsӯYAq6mH3B'G{2*n☳NZSnV8%JTz fҲ̮qp/-JZ#'du /+}*跮eǞ X>Jc*ax og撏7m)^9ߪ(-Z9/@Ave fU;g%ʈ}gƮڡ)k7=yЧXe*(m!!+'8Ѻw2֕~nMن̰e)V0'zP˺hr f:]zT?tB"m">#ޤ_ f[$+T2.jWw%'cYv豥'd}MlyAx NLBc}F3On`C8RV׾ոVҰ=gIQuOԴ5Tyק^ [ Q@m;ɂg3_'ֵn_}>*Ҟ=2MJaBFs;SX׏4dJbZ$Τܒwkk̘Ŀj$2J(M!>Q@:f 7tx5Ǐì2l МməZ0eT(JNrsuLԚdGieRZ7a*`cUNM\d+=|<G@PRhrˡy'zOX]h;,yM8tZLځ&Y}=?>mڇkF0<U~m؞Bo59xSRg?7Lz[%+eDs{Qя:On[ԓf{L$kG\Se ]vaRC>,9KO?D&m-;.+S !%e7<"#ϔU5hi6aj/.~**~.)I:)Y*3*SlO ' g_jzZݟMĜ4KEʱ񎹢S4:t+(d-ED! NI݂c ىϟ\uq)Ibb(q;18ʉfd\jwPzh e[sgFD?Hd( 38yt쳨: eh'#':S]>/ZhEɵKLhLYYVNyJ%oM4|U +rOwr,E;rmiRO% zM=%ҍ-kJ斕/*VNJR$@Q5KM)̋M+4)dNЄ!Y#a}Jb@$%iNV! p9HitA^2ήTe,<ӋAe)Azź~iQ) H 5.'0 [G7)f}KWcۭ-KZҠl82bzdUJvk67Ys8JT @ZYIf&I\7Sss^mYn.̦rlԐvI`z'-YԞ̳'9\GT ~ MJ-HK5*ҝ #Q·~?L?#S8<}'q`+ pFNd,GڣshqVkn}tc9Ӂx 3^_-{jצ柒R@)Rpp*' ^X]Q@HPѽ++*9Dدޖ϶k2~(ݛڑ76X9O9Ǥ 5T~{$hՓږ<<=a%Щ%l5YU%yf3}CBTFN:ݰY*ԑN̽''\ʒU *OԪ/1:JY24zVW]a+B5'MLx^ROMC S+˺]ui%ryvLBBH;;A@A@*Mk¡Dր= x $p #P=:GMANEdz` 9    28dc9 #G` #zAyqR H 3S%f̭6I]mG=y>&$ s|RzI@A@A@A@AGW~ ߱30EGW~ ߱30@h5ZYzJIuVQ;$_m"eSnELm=d=Nz0]FF"*rZvMo:^  I >0ahWMz79 u)2$% V1КzfyIo.FԨ%+rFHi9&/^Tu忌7I~;zzjķ&&qi8*RZI$$ĕNO!'daL<Î+8Bwvع_LP_OMo=1/ٸyimT$I'F&OI&r=+?(B_y.p@RIF=j;!Ts>H\b3BZr"d-m}+mh )+ N0t>jvqH JR)ivYԸX(#"%WCzBVʴÓseO:Kؽ #3'K+ķsʜ+ /˶یBm`p6q 5&5ONKӦZt,Ӹ :Vƪ&!sSި%) ![pQ*%*$Q4spkmHAaةѼEUgM3T,&I\=n0#q=@#8}?W2.?)g5*go.c0wWEҥmuZT9ɧTR8NđXsiۊ÷*Jg$u⑁vaOq~|_*3]}x>b*i-ʍQɣ?D}a;IVԎQ 8JA'ɓu\kT琊] tץijBT P9# Mo 63EE)XS.p%JIqEkIggfNuSfU6YޝGJxO ֕iiʄjm'!Ңf(" eѪVM⛬ܒ > +amc[m)=<@j][9Z %L8PH%i`@!iwť^j闡*8DĒ PIwV[`G9asg׭g<'ZjSqeJu--)[G}*ZڥFF"VrZJMv̺FH*Qd>2!e[ՋNnZW#jfi)K<qAME;IsqpawXhjhԦJ;r;RR!C*,:ߦvۚa])TC ڄ @MN [&/)vZᩴJrbrz`$wG<I'%YyB\i֔FBGABn7wy ;mCXYCL&aXc--X #YqY~YV}KĴ2ۏ-7ҭz5b\3TZFX,^}/ (Jw$GUh[irPUzQ39;./%NAQ8ORIe6֖妦6L#RRR# Ou8txV ij{K.H;NTnNvrp#͙z[VQPK!H[d#t^ӻW ݫԦ)rN7-.G AAJ6dX--zT3WN"#ζ+u塶g HI'FꜲhEvRۛ yIFHQRI(@`}N$hXP<GpUɧ S(& ja2mFՄnNiV"䎬UZ[PZx;،9PN!r4bFSё>IVԎQ 8JA'6ؤNrnQdgK-4r0pHH|(:-Rh4e%\ B;pr ve6e-pu2*;J ;A9sBkf#CQ&jbeԠ) 䍼c'.{.vϡU=?"Џz-Kӥ-DG(yׄcq@;RSȶkR4 [;Tӡ#VŀN3B-r랥ԤB'R}L-LKkRu[P_ W:f[TJj4Z6:\rJ],nmJBP@\ 8NB~       Cր; xT?h׀"*VVɼbV!/̅s`ZRws6ڒ9x{jBB>OUmSI< ~t$!Xק-iN)Ʈ GN2)Lq$#yڪ`pO^<)86ꂰM ^?5psRF匝wO^}~?ڏ)F3჏r7#sڐ:#Tܫ #F0|>k@Ğ`686r9WTk{ucF`3DzN=|~~_WX^VHGH@I_l ejz_#ë)#>χ:='H ֐sr6?&#K$Oz?|`6ڬ$ig98?#*XǏ3lS N̓z?ǷPp'nzq㍍я䤫< j*<A>];:L[6Ayr|yӲr,Xv.toѠ]^U?ESA־$%d_ -}]+h;~WUOTu+'j 䃭}IY?sWH _bJ*߮US4U?$kJA _RVOػҽʪFw{TOZ~~H:ԕ5]uJ*gУ ;UPI[v}HyjY+T,DSMo㢏S苢[J |ШiokrgqhN30 'עMJ@hx2|6hm禶ЩI!ٶK͢iNY[c [p }$C"^Ŵ[-My\BPPNAMTd$rNԥe%qÈXpRA"5;O*kڄ䒛x-qBK@O*9oksH{qi831 j+GRtLԥ(*qJRRHG.I)’cvG;}ʡnvqLz(4dkJwZ VܜaD0FLέQ\Qa8H2rqJ~A6$bVMy6[bUM4608twOn:䍭OZLTtڕm~ ^lڔynI_L]j۩%*Բ%eଡ଼2.WV]mܘ)jdh8!J)%AU]<ۆ-38=l8 6$81VnJnVKfjI̫6˶wePJ{`wZ5ÿgQRک% !;_RG'V"\56oL+**Q\rsH}[VY$'36UY9i7q12\mx @6=Q4*i˦`dRԫ PQ ҟ;4"9ѽO*ng{nmJRC+/GTK1'*̴-,M4608#MvMr3{fqp:zC@-qjiMQmM5=…vXP?LA'.{ئ"zR貓hLO4ۉr2DZ"eigjvqv4eJI' VuvGy(ݶM&ekUE:dԝXjnqVRPqatY[RCK%U6).( 7}άsc{fj]ٗm 2B n?Zoz}\R+4t̜ehv$A'`FH9AlkVnbߨ3PT!$P’w' W~WK 0㌭|7ԝUz5C2Zt:T(ZAQ' gϪ%s.HYMju>BEM1%PK4LR N#Cb5Srnae?cuI[c~Ѵddt-&%nJLA% ucvŌ))#CM=az'+0a@JB\YR9mP!I x}ƄÊtscÃm89 ~^"LdnkWzGbѲ$f&2cV})WSȍ1g<@#am~H0TzsqC $>?T|8:z<#hz3%8~`a ǽo9h*#|ONo|=Q7RJ{N#``1WBz|ӓf$@ 1JdQ@_\}T1cy ~8 S,\s1\qԞ1J0A*:F1{H}qls[$G<9z< mAϮ<z}9vsiH|K? Q?zt)k*BdU[w0/ݸ23ı5dC5C2S!}\G7_L0~)(jn/{E}7n~ؗxgD'tGqUin/{E}7n~ؗxgD'tGphO_'vtGpgD'in/{E}7n~ؗxgD'tGphO_'vtGy/_ :J[6NHn2i>DwWEFi\뚫R$%* ۲iTFxuOho`w__w~9k)SҥfSէmCt*#15odܝս4MKWk4RrR]K3,p%;O 9]V]K-RWnw4*:eI0}R7<;ejJ7ާvbGHiҦ؜{=8$pAeIgATuw7 tTuw7 t.뚑MjfߙYL! B׽(2Һ|ڣx,vBra!ƤdEgB/ʩvdRy?R]ujw=Aܕ%Yݓh݂+Fͪ׼Şװ;=80k%b㣢z˱'+e>ϡ( [ZJFblPR%u<0~V?NV2I;N @ Ca\vBMg[S7EmI50%ۖ H衒pTIM~RQ]M Is.%de{0;|xlr7-ݽ=oWiƒ[ju Jt'X՚nY!lnq$!I ab$U z  Tmw,[ NHiٗ56dkUrSh*Hq;VBGPB.sWFjnsU+[LsCa@J2N2 H6/6o:FT$V[$w?LAHi#p f"L;Sͻ7PQjB.;tzJrd`@4ɍ^hSW-镶 TS%Bs .Wە6T,'S$lr^0k%b㣢z˱'+e>ϡqH [RJF9`jDRҪ {ܦI'i݁vcLnjmVն3W-Bt4Z𭧔@*T2MzuAV_~e2L@$c>>G#yI_UMrfmˊVe\xL6e!䀓+iBzVR&yJJ6]y HId2bBMxНR3C^UA(y\ !@!INH2ĻŸjrUP,+Pp A`HWf[ݥZ3:2Bi!ն/9'w. 7qt]KZAn9Ve|<6xNOxAN .:4(]vUS4JFY+-G3=b(]bD*$}Sr͈YZlic $L8|lɹ &ծIg' `v_Dh[4'$&f}GsEdA:߀ (6_VF(\򶽵CgJT씳eV0ڠ|*)_RszZSJrAMi eh BBBy}sFluU+Ƿ>M~ӳAV81Bw5"m;:n] rjEĥ;RvYJTz)tyKКߐ&DSaDq) V 'pxtU.*증[nTW:ܣ ZU%IP CB$I{µt=Wm 3Oq-L,J-!@NF1v (z FnZ;NM2 N셨) EA[RJ*3(DfeZIsnӸ`8q<!ev-OWoZ FҞtLit8Ql. $:f>SXgim>Vլ3ak'T$R8'V;7`j+X/>qv;w!kݻjAc*pI]*bfR++x4P@=XRwvg=)Qh;b=14j2HRj}2Jw>h۝\(2TҹNJ7F:o\5iz=L5DY_m0`WDxy]H%wV&+V-Nۖe3jYCrFѹsOH[ڵg&hF3]j$7۳P}=M M5麅o9X38̲&,Q4{IaC-kvYOQ+vYh@Fܞlwט{7q Orsba4tOYM@_bIy@D]tfk4Wd VTˠQs’Atx[=٫Hrb6Z - YJRFrHL=-$I Qh<ٞ:5'`ʱ#3Q +c\>=8N132As\k4^91,c#V3N}q8gqqfLF[xDH3'<|Hi'<⌏Ǡ氹>% Q?JC__fjvEv# nI:⃠o gIyl86%tR(zuK;W܉Ob_S>߄S>߄Ni]e}ɔi]e}ɔVϩ| ϩ|"ӚY_re?AY_re?@U53k-H=53k-HWܙOfWܙOop L~p L~-9u&SDu&SD[S>߄S>߄Ni]c&SD}uO܉O/&>gZIr>g2awOT(Oۋ—7Nn|̴iqj°JIQ$zy,iu<}g<W_K.[i)j s7"kۊöLU$\7A*= giPuk ܘ4 MZ!RyjR0_ѢӴ$Z,9N%(L +BTTFzu1/[Ժ=yVI&eV k[qIOÓ_n5|UfLTf e[)_ūL W/ND3L8J=!]YD D${i-?fQ.N䨐JYdajIO Q}8ݡ2)M'x!!TԂ9RC}ţ^39X{Rү˹\3N[6+*sEﳵ>;Gk2R:JP<S-+I{V7Q}C)6H<<>+ZaմWU\S33ꛗDwJRd}5bcM'Wa g~zuc>yFwP]VbaO̘%YRN%*3w)| .!GŒ|xG2WS:0#`I"-TpYZܣdL&Y*mW@zrvUSRIbRNkNKK jU!^FGzJ fveR6* JYߤ1 ۮZՊ7ܿoJv>ϴANh3DU+5MeL-wђR]?CkpXwH)u~ԌO iuGj$C `g exEt/.Rh^LrrBܫaDڐA u' MZ!RyjR1P(5֋QW.GͲiImwmG HTN]QzRM 4EL W/ND3L8J=!]YD DMdݶL^o!)TSX*E)JN c㿂ӓ]@ZGR[WoDDľ4ëm-<ua'7!'HY:Tg(RIL$mβ)vaonJOy(HH0TYwݝ?A_d& O)Rr}$df/x~8h[jWsUUDܔ=帉\¤Gm ΂".MgRdj%6RksLU2N %4Ue@mF">G~b9ƥ2[o@9ʷXX*eZS iŴ8R7%*~d%)bY ;X;'?hEE~eF#usm}}³8PQ볞Rv-##L\lLaVb9}F*F?/m=Yd)%6pP4\ qFx)78LIIA4kl7-UrYMx''os=-ռ*_rTpKMd{inubUK`(gGr oB3nb[J%qHnj/hq`V `FǬ*dLT]I/RBKI%IˆPi0BV|f(\L2HRTFFs +oHJH[|AmAiHJz&ElC*VmK=(KjI?,KSz-iZ.Iȵ^UEde(ZY)NNܩ$"tK!ԦR+G[@NnPqg8ǍuJUA;IqZbZ붑eSm!MSTmTB()iC u9Dz@򬥢U 5IZۘnӲRn);N}X &ה렕oȵER{K_)u%Q1.r!,CТ)JC$ɆEFR{YJ,Fr F|gy_64r[eh'N2>BGgfa+kD=܅,^Qfӗ)v쟞O vo3<}1dm7jK2s('IV)88yIlxV}?qn2rp.#HG_^FE\$U`7}hZYbJm,"ĩfX$* [ʀ۵[ANLX|,jUWndݜU&/٦ҵIZӡUJ4%E-JW+8Je~⤵jBӣ10PjqɖQ}z<5|:cw}E:/!L&xT CiL1o|ZŸYٺt 7'siA#$2H"+ om*ҾiQ/^ϸI x0JOmMEl6Hݔw}1:dTf ~㎰:$k:yL/kSfP27a,G #z ӽSkv⒖LԔA;A@V(: VoZ~YJU*)) T2C( 6Zz+AI#׃52;X\<ʜKw=yۄˍ!$w= `9>QZg=b+iiW <90x]({{I\͙:jQTvu ,%AHQ:GśɺɮZt:,n u-< TTFJc\lmjMb⺫6ƛf*-NꮩIwb[)Zxs/ߺ)nԽI؏9^gn/ZwƞrcR%*'d\n]֝Y ^︲0$Jnje)<=YVKV҅ouҠBR9•*V/-_ٖafjMuKm˱EKm% !)K 鈩yNY[2>{8M6 :rI9/!5V:m96f]ZRR2x$H"l 982EZ6@4w_'" 4J5v[jR=Ws,rA1Z癿(݌G4c.d; $'9q!]IǢ p3 =4tbm@Tj*eE)n@u|m:5Qכj÷)*W-Rܜp9[<;g'TvɄ֚5Oh>1,W54җfCR)uH*T䐺ZRT+?W+H1#r!+ORV~5=INh噭x $q$}fe&vV2f~VMkpV8􃈨y>ZUNNE#抝.f\vs{d`>XiSZ*,Jjc{RY ҇2{sURAS'Lnvg_u[K޺&\zTTP@ھXc+wEswT8:,gvM Nx:x"~iztdOɥ*Rc9OAڶ9\p}1C¥2˒˸p D}{Vī)eאBc+.WVxF-8pʎs7vVyi)A' 玆>- 3<荸9I'wχ^|)Ϧ=b=Y5k#[9Tl i:>j.rA;4?iY T`V1jd>jޞ2䑏 jO{ݏX#'_-6=^ H?6&] ݀zS1f**Iֳ3aRG>$ A玿v<}xo ;"6>;#/"}!gr"0ʱ8˜i^76Bj+VOu1Q?o p'<0Q*'X7"udG[s=άsc{ uܙf~HjY?Q>s':«3]s&sD럹3 VOu1>1\ɜgLՓn}c`:~Ϲ}*??rg?A9"udG[s=άsc{ uܙt_Q7"G(*{'r!IR~ل?T):]zz%ܣW=RP*I8bZn^4vl8+kQuXJ-2Xq:(g|:g4>Yj9)/v[AVp>@.M]Ґ;p{}{fumm:HPP<GPӁ@Tuw7 tTuw7 thתQ(u \T.% P$ x#{ڲuSS,"e)KbԃGTAAAAbJ\-UߒyU 2RPGEnJp~,p˂YjTo;MvZYL!jZ2WPzAAAQ]]iTy֦(>k4!(Z8FZWP:@BJ/y􊔼R@Jʪ] J֎dpz *?xSWaRds nma)C;NT29A :DyqE9 gqTf̝@r'&ٜN&RU  =ܤ |`g>1Ku]-Q+Q6s 9*ްm7#rLTM>=]y.a u{JIJG$C!qz U+A٦QT,8JVI$>9Aly'(/;}WP \giv{0qgĤs̫mL- QY1Z L#}&.IZ$Vy:{]9$A/q8?H=N"jm&^WODN`28 g,=ً5U#ʇ8O|JJA#'pH3<~<8=ԨQ}1|oӌy$+w_Ow( >e3uM԰'*(Pf5.',j]; Y+gϰ&'Ia 0wπԗ@Nyvg$I\۞Nm?EPJBJH:S-yܸ biJyo`!@3 rT3b=nHvY]Z[q\I n![H֗bz}\K95Kd'>ML3kpT~<Ǒj!#:یe@M `> dgg OǤ_t_?c蟏Dc?,w5DA?/"NԟNٴrs|=BbywT4%$¦xQ<zg" ӬM 9&%zB㮻}PNo_bۑ}o___7//:/k0ve~6_pMK C:? ~Y_͹&%zB㮻}PNo_anD Ip{^ߤ?=8T?STSfMVѵ<`mPwOoTo^Qe 70Yy`' ^amBnfimMx {8iI֕OѩMxL.U89'OROu8^ʓmvU.yYP@6UHR Icsaw aR5)W%&{]ՠg c% ȤQ-$|Jemvp@s9Yy)dKɰ #;[i)NNN㩆jNAGW~ ߱30EGW~ ߱30@#On '~iƩqTv 8t /=^Ζ&RꔭKP.쐖qJJ:?R[wt4w8\RI-+,4N,(=hy[x]Bɰg\CoN)A#I9q)[-Dܗ>Uf% K$RS[S$fq`2今~Hi,S:]hд{* KIJ> 杢WuW`^ P[ja!J8M(s$P򶿧$m*52u)K('ӹr}J!G)5NTD"$)RC(QTA(*9܅ߥ,3t--m9.Lߗ)MLN`⒆$gSһTn+YUrY'.8R3UYJ ( "!ρ}}kq@^Lk; )nVBFI%evsUuHYyKDK#wq=TaARI$I i֝]+ʑڄpʒI*Q=՛7*\.Ժ|Q/,Pgb8VJ#lkVeyTh66AKcj<ēR]tkXZt.ӧ miMrʰ{$Hi];b8)M|V..147ggD*eKrY!`2Fst#hRf)v%ifix2j$s0\Nѵ&2;ҽ)zq;SPn &beO:PJBY*9RO8\vzхw_%:n&ehfYT)9H)A #H~sGaD"NmUmi?$xtRkjtC4MJHnP#tdž1 ;hhqS/N/MSYDԍEHcdXZ;=*P L.m%wzn_gZP0}IĿvq!ZK/!DmR· IlZ P=~qAqi_/nUa55.>é9y*ON饱S)rTžaAiI `=!_7XLjZW(i5_TTj:K=RiA/lel*` ohu~X\=K7\q$ €NH^N :d|ı9 lOu],[+k_u;XJJH-L9@ԻnD\Ғ Ka+ v+G)[!3*Fzeȿ:!0p\m$c]G'i. \*mNJovi!!6=XP#r׽t֛JS:fS,Z/>NRg=qxLvjHf^ٮeIS{YHLBn}Gͮ.7Ox$|K~ƣѪL--q8CXK J-?ꝉ9JStd7JNnE29F{M8 F{ftڕؐVjO$է y!Gbv$A`"AK[^f%ZnqxQm8{(a)rwN]ldjFg*#ra{)ޠÊ*R#sP*&xt;˟œo?k*ݬ)1nҷ%*pĐ[BA .'S=lMԘ7?c fꜼʍX) 0$$)hZT@w?n*QB_ȭUz>R4w{zK}ĸnÉ䐓HtEWU o2XһӚEV$kK{6vmy#~8|_SVⓙrq{~a&\甙eNIzO&3Lv-]xB#C쫢k[v*)+-)թ(򂲰Vϻ9%W' V-eS{jpJ.lwhc*!M/y ` ?]Ž?5c\5;S}\rnSJ=xp넕 mA]:>{ƅvW4fKMx+JN{4#p'9)%1]ӫ?c tqɨm)3jVޱH hzTU[w5tAJ ~0")A}T%:cw- RVDڒC+)LJm}ڬ+D!#$/*2SҦm)L=y0_UZ&\޹I%KϢ/^FmY 32a斲}!DkQmIy)%,'AG }4 *FeKXn%$mneǓd=\aBis2bX{D)J'J_A^MF:fU{&@Ox>T,tZ0-)Ӓʏ$ǚUNN&Tqpp""7Cր= xT?ǡDu1r[<~s&%#f]x ,yvH:őy܏>?V;g]'(g0vv`uAqj'LgvFDdtM뎑HHN1#Q€Lht%CΘa皙XKOٟP$g" gs/)]TV=1bIeU9.;z i/_ΪNMQ$THnAEute̅=0ش䭅[ mJYU$x|(mTibQ/C$t29tIw!QR57y0 zHv^xFgp#,tkT+,)Ea :X6;)"gl٥fnJSg&.-yS)Čf[w3rRl)M90+xǚ]\s%C'vYٷZawVrA<}[ѹu%S-1M^vJԂRHŅ[E<$q@=zzD@8gBO~OYl}̺iĶt u,$6*J;Hb\!jS^*ꄍ=ma)&).ͼ^JruS݌sIQl,n$\+c=OK? z^z锔.M*B\0xNȅj}Ve (1'; XtJ-11nM.ڞ@9NNs䎹u3~9I4&*3Ӏ30ÄRYQy{f]K3,oǯx|4memz)b=usHKO(mO)8}&<ɭ^rremvfi6:$=Y])%Tp*Ͼ9|"+%/%܌2e-B|{=˳K/{ZA?x?;VG_Gy9,z#HzMIcaxG1z͜u<)pyI1{>3;B9$TNDjȈժM3X[ ZVhg$1+HSviᦐV3a"c}ή>ˀ{+i_G$Uدo݊\q*#uu~VbWGv+uq->JB?_ ZW:ov+uqbWG#uu~VbWGv+uq->G3uuov+uqbWGg> Ϛ>0NWNQ) e)Hi^Z(+PPr@=7j5%s5,! Mʪ]GDy魹wԛV%3ͲK<)Jۏ{DznNdȡ%}jN$Yc9hh5x">3}}@hU9KoRR{@pT;}o9yǷ Q6EnWMXS̽O8ﴽ c8>͎* hqmsnFJUC Y2EsTڸKJ/.W.˔poV㌍dc Chk7aNDHIqxH$c%Df@-Lm{n]UK ̍6–J QRIA8V6ɂ N^Z9pZUVթϨpɒPpNTT7|^4ةv9q̪aO ɰp0O_kNmy=lSo~nwV4-b_~v~IS|=Cwi˺m> =C"_mp:̺e^;I` רQ}u/b}*^k)`}׵N:qx Ov%Wfdș6n,a*ٴ<W D$kNmZKDܳ 7:.Fil\TYJ6R3A݋%$w<9z:};?c|u3'n7'|:EAAoj]py߲s潟eڲss:ecD탳.qS~{Yr^;{>ӏ˺>ÒC՝7)yT*Lt!P$gA 9$U'-}jJJӑ(T>= Qx Y9p-#"oj;EO-Kn~|c#O=*i씒e @!S8n4*J2i+PJS:>K9&UuꆟM[S5~bs JR-JJI'v7|1a[nyykv[=c:iMXS}N ?7ﺽߖ8i$WbK۷C MԤTARzw I;J T`iEq8fէ)[mZ|r-8)R J0Ap_ vt\nYٗܳl waR07,۰UC}v\X)\KY U* #Mk-tu=Si]5jr ;)J1I ~ʪv֟ǦɟJ{<ڽ>#[$LHV \b>^M^psq^9`?"xyRGSe $#@㑘 ;OC댪q[O'CcyY̿풔Uyu$wvu=aohyR]"kU)NJ.v;9NyvT޺۩է?4ӿn-j?*'LsqZBZr$ٚm 'N}ex%ʴV ѢP٬Tn*jiioA*Ҍ$x6l޼5lelģ)q(';N2qa4/q]22w^%dB^jZY.0].NN:c*ڃjm鹩 "Jm&e +)v9dMAc^oj^` Uߛʁ` T2RJH>N<*ntfSsgob0lʦmK̹D=6չKs{UM~PaɉdBA<pnHR̕L&Y\ɀB[S#zA=콟6<@fb>nQ_TڔJ  -``+tMZY:yEᙔ'qA!@>LDkD?Hicg,;Mmݴ 'w*mOJ&.z3kЪ~N7NHKbiZ(jpO+'6ːJѦJ{u I2N8;ܜA ̍gЯZ[TGdxL!JPz)\g1آ#\U&@_x1;1;`?S-ʄ˓3TgY)afonF^su>ERRoqV%ґI8ry7wV~8+?/z͑LVx S`*^7';A98Իhtҕa =(9gNKu\7vN˿RM;4ܴ AA]U]dӵͫi[vUGE2%/,+R)݄e$tr뷨S)W4镠8ejA$30~զkdƠݔ꩟[mW؆#r7O@(1ozkժ[)T2K}7wwW })F]R%C56hZZ3(g~ݷqzGz0=jݨ-*Y^RTA HV%X ~^oNZTuERY3rx;GO@Fr׷])>qrq9)`QJGukA8*(͵ ]Bرu$v8*1yj%f)(+o^ {F<3ȉzI)zQJ\p,%X ڬ{a@D$tT[zlrZy^& @qIߑRTU=!nZ4;fr1@1O̺̾v5J 8<z-kU Փ󛊭'Nhk@}HA##yвս{Ql֥]k%laMەvk[{g1a\Zt]>sͨ2`l!C!;Nc8"qZm%tZM2vRVQiiJ2 IBR $xuA^KrΕ󋖱+ 7%s 6X$gh8x馤dQK,6V@q/'Kbf/Y)ZJv}l6}L%*;RF;HNCͿk+nVuԂ0 m@*-ʀ݌s&+}Gfۓʣy :I$$&! V[ҨTlp6 cpZv, #9UK|>'-YeϿ&{7)+Np6#0! CSl[Y2 eTv!/vs:jBDn%NZivm2,BAQ8a%`Ш}٦HIf0u()ģf!JB9#ogQYiRsKJ=TGT@bɻdNOTnIQ#bPV!XÛR ; N3Mt/fRHР dr8.#RU%/=S,M4IJòj0 MLPo YJk.m(c$p9y1)r4bFSџ>$jGU(% ;CSlgͭQJe˫!%Gb R@8sh59"Cut]5g&Z,$2{@2B`kIggfNuS&U:pN٥<'Qԃ'uζ+u奶IRֳ9$% &8ˁj=EH4!lK30o;GIܠԜ<O g84*%á<ܝVmyZy vϺI˛#;XNQғ M51,aԅIY \=l4H$ Orcn     z$~k¡D@WgV JM?j԰KʁU˫W,Ψ$: *sK|<1HY8h-qG>ЏI1ɎjۄHB}<VAS37j}{Rܶ%7pShRy$(We$GC"6Z`VLq  =S9)cJй9 U@/,$oPHڱU]_fI 1HX9鈆~UuZ3.a-I%@8R1'snʪS _gǡˡWQڒKa9HcNLS6MT{L%|=^YdXtηz] 0W.?A>#g90_DBr{BYR)Ɔqp70@h%G>n;FL%H[$rm!2rG5~d6ϤG` te>ٽj绳4{ xKm!J_q=g1!r c!cI,>.jLRZvI/sqJ#l()$"]$1*԰18 ^)~#r˔R^]-: V:qqvQ%*hKʕm$sݘ w q{RJIqtL.pϣS^S&tiqh)+S*$aR f[r5rZBx9+'j 71 - Q**JJԕ8Gcz_U:o?dNQ3(P_XF΄qrDa6s*u)RTX;*e[++ARxۂr>#yq]:+u_ȩ|JF ǯÈ^-c)>yHBrUӦtpׄyI_DeX?bB Ofl̼TND%Id_per FM=e<% d9f>c,zbk"s"f6ɧt\Ñ#ͣ)?#Dbh,Z쯻Py}]_v&JSwc)K1EuC5ݕbj*ԥ?z~ئ?jRTlSiPMA:we}ؚ)Oު_)ڔU/@Z|T>쯻Py}]_v&JSwc)K11;MA:ye}ؚh{Rbh=RL4uG5y^Y_v&&,= j+lv2!D{j[2!H9RF1a/Zh\ޚTh:Ԍl;$vUk9<|QEwi&/S֨:٫L7’rI95 n:KrxpσC<̢,E-sEtSVu JSZM uVⱔpGRH# ]BP|3{R7mQJRG"%`kVVꉉjiUdu;R-0ʠkUr]~ܙMuU;TwO mBJR$FYG},z Owe璨{ݍwgiHBBRR_5&yW5TTppOxGZkvŞzVq Crn5.T.%#pIGAD]ݢP;o`go9T3m7m8}&4mAb ?]U'Qǩ,$/I ٖ;PO}|uݛRjtKˇ7'vmϡ"5Sm Ei[*Kvk͞s)V䃔 #_`w'Ti+:̰&)-çW>5y̠uI>їb$eO_-Gu{Nt#Y]nNilJD JqʜqľҖdgWҐ9,}ӤC"uƔZA@) 3ժ-.*j6J,y`#>r5+.6 $!HJP09ە;Rj]֝v`S-2 D$ڽ/VuVJZ35BqdīhQB<TrNJ-6o<6N)  A DvQokrgۜnqL>R <<em: TЃG3iEƈT+f&fJAST̪p6n@F##VNH%Uw-%ׂT8 AG5vջni:%31T}yQ,m!r$$`#i&m=gu"}nJ}AeT8v zePitṚ eG;RTl19*-,ۭ:80Rx AaթF嬝:uV4n%6m d>)I %+*#;>TT42ӣi󲲁0Wʼ0[j E O C!dt'hqo/G–}WJg~| ;_p9Oے^]uԚiNyRPt{(( mS'N$Rn &ZVIqh'Ci}N$hXP<GC)Pb8d[d v3Ȍ+aR*U2$&)0̨(P;@I'>a)ץ9+J Йi7Ou/7<'*S'bK~zEIμhq̝r9 b*/Iu &۴ʔgCWV -9QPAAAAA =z#?C^#\?jxFm_@dPPa{‰+SH'ht$K'piH#'#vvDhIOS艹K l#9kGy Ѷ{}Q9L.2F[V |U+2=8`<=Xhдg ʂ߻nYHq:  [L.gLSTmQy!h$(ryQ( ȩeʆÕr10q9s7$Q+/&/9dž#ztθ)'s.Uv8V}(3'|=GR]Ou,r3Dd d>.$cnj)ok]Us>nÍ#)NP>^{;;Ӡ1ƛ o'M74V10\ccv4z]%T >1ٗn:d'ЄyrmHW*SHT ʒ#$-(1@ItCd$pbo%^pu;ʆ3}МmJE!FF/thCyZ[yA.K\4k(vGqkH)rz(7'ʷ'S=>Nf2sA*ms%ҥ(@#ÈP^x8?$}.p25$s2aD:FINyj2?Oڈ l̡ Y+Yɉ#'*՜ `z$LSjʙm; \p_1/RD#ѷT~""6a֦P)@{-妦mcsN!S,~wBQH~#п԰2ꟹdg]J( Bv!) QQ߂L.-Q߂L.-SVj6Nf=;MwnF74^Hڥ6Ҁ9J_1]v0\2 }jSV}Ճ9ȄxSW WQkYU!͠d,eX9w'+TZ%9ZVВWoY7 hk nz]/нᥑovJTp9pE?fçz{6yŞrɈEvF]w<.II)[%=Ӓ9|ջѝ'W- ze23H<N3;B D=Akw\wY6S+F;8*V7HBGs+P*jKR)gd i@') ^QX'b4AQ"Ҳ&zrS.6iTR7gN3j[6fjFM$g HIi{zr$XeR( 9妒@aGXA +9,v.zקJ6qengJU7!$y$5v˒,2ܯdV6 `RE{ɚٕ0ܳh ̼yWg| @3ԒZtQn:js%jo@RJVRTz8 s!/v2ԚfYJ(tcJCǢ:.E+Wi>^M>lLv=UYlN1ި I2+%&D+7!i IDE'K5 d^oI͙Yj.&ww6 Ѕc'߈V=ki.˘uЉYU;N{FCR+ݭDܳ2rO]K6 0 >%^O+ᠮůj=qMO)Ԩ&`0ܾkRJ@<%9#$.2k] ]}tٔZW 75!Yh:ڸ)8AxD+rv%9?0kc ogNH*8A' $]lZ";lJ!HqE.+x@VU$pNR A*ޡ;[ku&6VԀVGtm %I ‚V߫UeQxX210̺M$*X-t)'8@,@BP5^噦$vfKi¹HWtJ@ʔJFKP]򖶥rJH=eXD2 U5"֦\[;\m!jpm_JAm{-,^)ud쓎s\ u*gBfMYLyx@qC Bstxۨ(IznqŠ17&1*=qfuZJ.[ @Me /zF=^      Cր; xT?h׀"&VDB5@seAJ1L2_}%rJ _N-;5N f:e 'u7yϮ**bzRT۝P:r5CjON#$ʔ]V8^lKvBS[o/hʀ=WŒfЫjFB-0PV RBfQeS;M3HOnBy/9gVS5WXKe`-CxPꓜ򈉫PDhu7<ln=Ye<{'z&fx! oiH#SJߝ4s}2wE2:S';A6Dx}gBcy}K-6VRm\ cj-$dt1–` ㈒8yaD8~ӾccvJicg 1N'YXyD$Aqs!RA~-8Rx#n'1CN"]$nŲEMEAYW)QT.tgrY̶{~9)t3,xk79"e˃< *Sjޯ{Ꮞ%j`2s"qPԜc ?Ng$K%|I]|JcG;V>VksN1z}IxVS66V'hݘSIK *FqV}nL,̭T8X #>WN',ڿ9-6LfRAa~'8?n,%oe.ucô/+OXϹEpͽB2eጓO*ӎ>4OujBKs|QrEAÈ7IQ{-e(R9{DҀ Q1aNvx[NZ#mM c# 93_b5J}*.;٤r11?lK<KT9{>|M۟%hOA?U}6Ͻo_'v/sĿ4mA{ ߽o_'v/sĿ;h̹~lmO z %k7ʗ''.RoR'XMǯ0zMH- $7I4|;j+#fjJ'޹-#nuODYlj^J֠-$vKkjm'<':ըsO;HݵE'G)#+HZ T20DcjR] KcIq; *:]vź*:]vź=!~ͻCNvrϠW)/3zj)P9!ڎx{aiժAl!Y&Rij:Z3gmRU7TC._jjp:!ݎ|f6j ]Pov`5hޚ}{äpP Oӗ6eJJP %PtYĤ&iۣ )IrE[*œ+ؾ^𷙼--J50ʪ ͪiyϡwGh+;)L0 Nz}X)ԯd<#XJ[d88c*g~|;_^Nkdz9|NVrKBfdvPv͝ l 9m|tfѮ[֥T+2>m'Xċk[f%D@}F5,ڂ#Im5*?%rLșQ䙅^$CgU ӟ0ŃXf-K*hSuAap8Rz _=":oo5aAnIyy Ccޓ'NǕD[򵖃? 7X -TA&Eh\3!Zn:^t@;RAfP]IP)"KHK7,ڜ% Xd' uMYziu/ٖYE ! gۻlI)%'麯JDP_`2Nc!qiTu`Թ MN&v! @i?bZufDZNԸ2rⳮ7Rf 7JV6Fve,ec !KQ흯r-i ^ȡ)2PA(L=:t&:ĔY*MF^.Ru-$,A;_U/ [zQMtRNlrǡ_o7X! eGc)ݙےĶ>e #Ok.AW8kH%vT=MF:[@6+oތ)^)FkSg޶>lUqs o͞k I9Za /)>ztu#糍T0faRNV:IWyʞAB۬KV*Q.p2p'UBimPlKjYjr}Fr~yćJNK$ VyWCj=N1F6Lv*q쾢b#j[w]hWySJ.uI;RHIJ8 k\ZT)U,yJ vaʛSnQb;)W*z5jTVI5-*Nidd2T9^ u^vŋ/")N2ٚ3 YQZAF2:ĎbGVjeYR Ui͙9{Ӽ+'$PBHj_:{* SsRf32ObKܮYQΆ~3eئ[y,˙ї$dڲĻd(|k~kiU^ZgJ}I+vЮII]TĈEvv&fe?+_$c" lͩsX([W̬$ g мI8skEFݔy-V)wAnQLvd)|As e/ufOjo]J]AJBӻk#3݇sޤQ5VDUl{b蚛rڒ$mJ0z)|`E<c"A>y3}P5beͮ7r0HmצЙ8J@ni.|@MZ7j^,+s͢Ym=k?R-%# THS/;QmEoP`O6fg2 0@a.՞5vPқBB CdޟUCnWj ;RG~mA@A@A@A@A@A@*HhׅCր; x l[?4Kp@qr~>{8g9($zd2c J4O>1{qBsHro_]oQug O @=xfM72뿖,1?#3LggޟO.~XL[};5#{{ͺp#ռKHyKZHRPq@S133YJL!.?y22O&> stHH[;wd|\ªZzNiهwO&%K%iK>?@º,K1*&N\yӱJIzqvJ+{{r !8QV LnSHx)Gt/l:+8glp \ :9V/~>_S'ۚ.cbg&.NhZUT00yǣz]<6麸ξb &QDEUέDUg.cG~&37fWY%(-BA~UFzzj2.J|Cn%ڎH_g֒[cc`RFS% eXs36ub,ȼ i:ӊ 󘦑` ӘtYƱ4O !N+?JP&PhFH>08WFܻʑظS xGV9={;F9'> (?)r!cZZM͟)9*ǧGOnތ:d5/6~\bfJDŽxcM:uBr'  fm2j4{I) 2:ZUɞVWd̰Je䒅HG`)Onv+'umcb~VF>:?gsl諷,Y?[o#1-;} g0~@ۖ}جշ݊[qoh`Ͼ?gsm>VOdn}u7 g0~AL6v+'umcb~VF>:?gs??S zrkmٚ5Jqe%PO}$cuܕK -NTQKHPہ(#g=F.Xش~zO>{Ŭw̬1M}{)gޗpoV;x#RD"kLsʚ; =ќ Y>0y+֒RâNo~6 dbvCQd*&ju*BQ2T ˤ(A#>Y6ԕlIPr >0}i9DUGW~ ߱30EGW~ ߱30@A@A@A@A:7'    #*T+~WBbBU٧nR[AQ>8tIT49Z _j{)6Le~R yBۨknٹۂޢҧ jhJpR~#AKӋZ&=PV] -B?p7H &֮_ZmLɳ!=%Y%!G Nd'AAPuϵi_s/Ϟ#wwlɘ  gAAAAPP\׍#ؿ29杯vqu;*2}^9 BɎ޶FƪnKŶi$W80   9j/{e۔ 4Yew- 8 HA"in[OQmmDۤrrH K䔕9h7>)a9ϣ0`G  xǎ8aKmj:}H*^V[s_j*I#"$}|#   Po?C^z$~k~("ǸTz tDqBWMI#3 7t9]!hJ>3CdʗѦ IC]¯ccˎc'>&?U3uFa'wtc1k6㏷&O9Rf?AOMHZI5qXޖFD'yzx9ZklsL3R0"Ҕ4$ 돪o##])fi29)cEJϢ2]٫MO3Q 8R8RWz1q]B?패IcއW۔Yw-ړRԝj ,,{SO|~?ԚKbڜz,4oe )RG{mѐpt֥Ǧ#AЦfPWL@pWјP=h֣YAF~~//z%_Wm3{{nџG߄KUtvmj?u$۴gkQ"?"_U]@ݣ?[Zg ~=HDȗ=_@P=h֣YAF>~//z%_W 'RrϡG˳ x*Q=B/}eZۍv䴪TIRIP;NI{М{)}aKWM:E)W4n.jiGl JQ -*yF͇{0{I u))'AG4m:ظZ)c-AgnGU2eXRTyڂit76˪+⋙ ȆRC{,ATuw7 tTuw7 toWNӋ4.TIːs!x2')-[LnjVf)JRu0yHRA9VpSއf_;ۼ(5u[DqI=s IIgfؗer2 dD$ŝSa:zNFYrr eSÜ)9(з(^(SU&;5S U^ hʊSjӡ%nrssU^s}f}̵O@Kͧtsy$N7 ^Nܛz5J&u=?.SC[籒`[: +wyDݭLjT*&_CRA2pZ98NyNMB۝Iiɧ),;;O! )LӵmzFUb'iq%*R N!/'_]d1的Kx556ʅzmZfJLw~+ PWᵤGZszUVa3tڒ>rOy'C@&*Uֽvڶ9NvAqN"E̺-*!;#Q<}h]]#/'p&YN W٭ߟ $:ŢZOݾd*ajVvd 9!DrNRɖk~.( ppTO.kה "j47v RZqIڃKUCM.+7]R)&ѹnڠI8ېvJa靿^h/.K˅TYגRdjI$/\N˸$R[(yHP% X1*/y7R[QHVI8> !GuIwO^lÔՖgVwpw(6U;J5Fm\5׹\Zdf .LK?%-eJP4S8P4o'61m*yiq6d0S,":52z^ԥA9&yq;R0 O L .i[:ϪSK,# nYJs3& 2*+e|4jöN)eT0BG8Rq}Ub薓HSDӈuPɠ5Znɭ%Ry@(pH@p ] P:m{TMVfQ8\8XjDA\ 6iP.Y[%JL#aHn_ oBpERno+ڜܒҹZ <;R PSnjus<7R*L;nΟ,qJJT~80'0b ?]['PwޫDj3iWҩJP 1P&۔ۂ^35WLe)Y mmZ% ƆUeLt[;J䉨SV̲C׽ZAJsoB5ooijyI%պ=ͤRnrե%>:fp(+$'8T wVM1M/|"fR(;bBBCH!^YR-4 J6=R,))>!z#[W~/c5p7Bh ێj۫e@Iؠs%\JI8&1{>JCnչ3JB:8IH(r[Ӌٲ=ZOeSs zU򑸶=V7@FF^UMKNW#GN7TӉ_C^$iZ{Vae=Ǚy.tC Vp12mw6Kݕ7(qu E,u;p7pGXZYR׎.״୬=yQ^*7f\Rէ'=qAʳv35{19 $m+*ʇfrl&ZM.4PqdJ}$OSKy JO\NJOwFTU SYHL` Cր; xT?h׀"(۝`m.dTXiqRf\\~֥JxChzYNN޾̸D6l9~f3ە|6nJ$g#) z]UnHx&[X^|<#3qH9#HJc" P0c^{3xV||cXs鍙NS#QzFo-FdGF(GޜGjbl$dzcNzxFSz#y1sXrz2ɃKW'1}Dw۟9un:xG8f9b7{c<9dz32M):W.zc-=#ipT661?.Z+}FDx"I10Ɂ9KN{  tmG=#F3;nQD{mqY?@Jʑꏋja;v2!=^|Hnjd}giRL-fwvD@Xdԑ'']_/}L{F V*majAI>1^6XEqPb7o)u97L ؐi35WC8T]MbsqO?,r׻~*h߫ܥ4].MbsqO?,rϻ~?rݿWSܕ44iԾ*W7{X}\S,)_~?rѧS{X}\SbsqO߫J>{)_ u7?,*W7^~细~U+NLH0[fT9?Mx<1=X=$ꖁPI '*Zu\w]e6`yg)LhWlJ;N4O6^.lHۀ2O1;w4ȪڌnZTeVSY(rN21: bJIbKx&e9ŠO髗E?MH0Ĝ˗m,N) x$I<>ѨVyļMj] HZ%E݁9 O'"zGP СkZ2GTݪڑӺTR'{j^T-(Z.I~ 30[P H ^:.RMN>0h9xA%Pgt{fE(ϳۂOAzKRj}gh(Q(i͏Gwz2yOÛu*8rM6Yb4euR?ؼvgnr r8±mH݁hhn]frvY vsY g1dA)&Jr[Tt԰‡9N݇ʎds,Iʽ36lK28뮨% (ITZET웹ߖu.6A|`Wi%vYH/(*beoyV82H'3)HSvU4@XJRJIg@@ؖjHf^ٮeIS{YHH U4.ԫk5ijԟDȘmr$ʂ2:t8KYzYC׌Z\qV&р6qdgVisr2U))BĻ/n3S'PFt]H~]jvAG #?ј/w^VHS@`{pIrAjmM~èZte˝S)ƃks*  |!klKl(=FOޜ٢eڐr=|z6's-ف?+( OdPF3OC}Fi-a%KxUiJ%C"bhH :VY]q;i)d>kTh9٩7 2ÌL6Yq% mi JF A5smm]*Է2Y%Md/"_=W암AGy%Sg%tr?.Y5Zڄ+zEwO[ڥ7ο7(e/-6 %)" !]:g_UڝH^nJuL4`iLy=0KܠFRwGx4EbugU4N- XYB@)JF0tبԬ]CRi7e*HZ A'|ͥHɊ-~_w\jU zH A:Pf$dj5RI5Or `$$ mrpD5ZE*NNke 0⭈H H$9'1@$&B^UUL̻4Z}TBAhdd_ido҉4}ju99' Ԅ ]`ЯѩnZnr4\r9# Sy:ڨfNRrsT)R)3<%INOR 9+(ĜrlĻHKmP$a)JG !sshZSpI5qstyn)I $m۝s ؏TFrRmmФPؚ[GiJ8asu)au s!tT˖3Q/Kˉ DI'-Ø!b|Z~\iKqIq7Vvr3H;bݑNNN1&dӓjJ9H=@DAAA#?C^z&Z5jrtRJi3d\Yb4A=BqKJ-~[ddC(%*(:q!IXܔk缫MOF#o=!Nw0?Z%҅ sOn8%'# dDd"_o1Ҟ\ɤ08qfHc#ᨹ/K564_|9>#ӏƏ]TƹF2卍cw`t;sNUO'~KwdjӲԲ-R:(z_uǏoO$QݷRӭysz }z_M¯ 豘E{^?)qrK1-cLLEƙ}\Q>뗏oi/xƒ K{X/?`_W?&D^?)qrK1-cLLEƙ}\xƒ 뗏oi/ķ2A3kesg,OrK1v;M%_W?&kgsk,A~LwNÏ'n R0?,ttm)zQRfÈ2A=݊Kcp +SW,{"]DE6ʥhhZ'g<rfQۦ-)Id;īGX?M(4nQ*m.msHp#dw{zb[4"RC$9, Fq9PErdͦbi]C`aA*}D2@܅i@#t"inU.A=8<7[-6Rc}Ei)zOO&4Ļ6ed-B#PhA's.^3XL:[ojf6PHyPWurŠS^\ f]qjiI J n9ږra3U &2ܛo-(T I8Cy%ږwMU&26XHi 8' |:PhQIP&fsiN.$FAYhZVFZ2=e)M8AYHtn)BTR 88Z,Ğ]rҌij49i u@Nit/YlժJ|4$iV2$nz"gʘg3QBBd G2Ew5V^әҼ0@;TnNp3 ,;FN81zX 8a=nYvBa7x;U60w#eUY*RZS)mD3b\kKXVqTLH*Q+R x'O@aej%{:VeŴOpBTS@Hӫ^B={NeYf:nrR򔐕 z/)n֍6)ͦ& Ìm:s8L9._U:.k;Mn=櫨%Gdw{8981;2̔S+S+H'T=96D8/?̍o TLn-;vћ[~wOqJB]JN Ej ڪMrηѶZrc rA#$C8XhMnizү6ި?(^RPk grpq+'#=,f]*ȿ-᩷-jgġJ^n܅zpc͋^ڪ~c:w{īޞ,J/5Y=q3MH6Ց[䐾sčrݢWghڟaT=p3@!gSִ;UקRٶ @-3_cU0̳);jҊ%6^o#y|Ĭ93 &۵jZfQD䲶:y)V9GBD^)95&q-ЬADsz5jRg̫iv1y!JPx~K5jz.oL4h XB}ߝA]xkMڽ٩{w($wR =tGj4Gc/?.Nwd]J~ws1UY9i7q12\mx @947uݩ3fP%񹝹QʚiEm-ͮ֩ye Cǰqߥ9 +ԷY),i-ҟd&a)m'HQ莁Z^M N% NY 8ݴ '?%VUqpt'v%@q#US:߯13>we璨{ݍwgp!CeRE*kͮv;yOg NԌ߶ۖ+V>dӻ ACQo"ju:XXDf[%EͪHI*dwPS-S%$!m#8^UltsU)(JRv,HEҍق5t-JwrR Z[ J@*QA8&[AR-mLѭ$n)IB8A!hjwT>[bjwiƖJwp 8^96ZnBF՟)u(SSA$|bܧ)^b[I ƱXI 녆d?/qe)HJJ#:[5.VGjj48#A#9B֧Ӵ?9Vq;9 YRa!={?$`kZ妅(%Yq 獈9ɍTAAD@*Hh׀"o؉\Y:mvd{7J%H|c57lP̹rKcc1I y͠}^SnPX?|]+ܷo~tL{|gcY啼9m% d{1dcz#ʖ@O0<+$ 감Y|lt pbVG8ѓ!1y'E_Hx}qL|'$f\d=c@>Wj1{+3N1>qQd֛t]Ү,2]Q7K &W*Գ(A[g*H;1oA玝I1PrVi*S+VnO GN L{q>ۍ=/<9l5܆#Q;hcۍ=/<oO71Cf؝46o݉@1n7!H~Nr7Glz}Oi{M_xsٿj?v'  Fbw6L{q|-8 wN_6o݉ACf؝4 zMI,IvBԼWGO܆#Q;h=l5/ !#?z  *:]vź*:]vź `zs_|^?a>aqi'0S:ǑtYD˴q"BRI3 M3{ tIIJ(bQhRS8TۂPU|/ȳđpKM7ٺElIZy»=qW)\jˬ*LiIOfG1H!Cͮ\VS[۫ϴnj%hAZ~2^o''8 )='q[']Ydf|:ԔlAψq0Uhͧ甹KvVhjĝ^< nlOT\>ADA-ZOjmF L&d)y ))9y>;3Pjm*4{i9m)Ԥ#3L˰҂g~|K3sZOjVeyudP*N:䣌+!tKVTCϼBwGM#8N@8#> = l[F72+SS3aKjQeI;{*,5?WIT&HB-e!ȸSk8\㡠?I^m$]>mzK~utZuRRR;1f'nO*{V]>+US/'"uʁGh|IP*R=$ZpDd<eL.^To 'uiѐp;tLwvi:JN2=-2!7P4 Giʰے8۽5>вSzN:ȘC~lARP:\gLrHG1DHx;}Szzz"72^޳Zِ-%Z0YEHC٭۳ &j}6Ck5Y)Ru-%Q *8<`uS[6ijMR\su)5&ꛙa€HI"P`z~JEr̙i{oj۞#6Ѽ$WKTCΏ6yء<1Fc03ACunʻ+ R5>*C^h2- zZ (SRӎ[Y$Ԧڗ.P-Ote }r3A;A j.F̱\Uny֜zAyvJK)Cv+87jf|)!%) 0:` Gsb^*`Z` =Ө %z`kn>H|`g1PAA7a =zV5K2HŞ+wt 4Jxl*T%c+ۉ{qI e.vouNbA!`~cq˳#*8A}SF(%D$#!?_ty=y>0m}#{c19=s[TR~35>Jp<~|q9r#Pǂpv'q2#|GNryӎ?r/#(W8*)J0!X\kr;G#^P?(%(}G>cڕk3Y<PeYbnsWqɏ 9ad6GVP3|hυKM1tߝ\Y&pn]AA@A@A l٪,MSI,%9SNÖgeYGbFʸ󬱻jP+;Tr@>%M. u&ou&o*6]K5"/:Ϳ3JB{Q$eu6jH]kbyŔײ vI Lh)tW9tVV2u.neⷂN!|YN} ׂ+Fͪ׼Ůװ;=80N_uJ#s 3IK[wT԰gJ\h>J]JZYI(mQܐ $K̢kW)A{"ܧb0rwg1/F똢Ԥ,6ܔ}/%+RHzo(47*ͪmXʮܘY^Ͱ[ڰI{3lN4)i#jY9Le>:g*OoeQC4G| N厥;T[c4j˾aP jvifxYRARPJ8Q\)ZQk;V&vR\u<܌#p!x5jL%$d5ے{JK>tC`mVpVINXNy(ۉrٞDr6+ wJO@\4Z- |SoJUU4JA*IIRv03 RkԋT 8) %$-]pqꦠZtD]z詥jbUty9@jHJbA}Y-u9u&uȩӝ D\H[䏦IQDbz*o\O%* 0F=r$"NA^vӾkr74[ANPݐ n_ARRMrEeW|R} Ao>pZ Bf\t5,{NJi y*9*H$#Qn{Rܭ DuӍ-!a2NrA2]^ "7:ģ736'*3k)~_xrq2dpIJJW69sofgvp8Vێv;<H"b]ԛۗО.K;8˃k(d|`2&rRh_۷&zQ2nyHmaJ@KQVNڎH]lZ";lJ!HqE.+x@VU$pNRv\w= k2Uv=ڡ*S2)8ߎQ)^1cv橋CeWnٜj}#=X+7%]tڞVHe>ȵ7(n0:Ϫ.IR:JP\iL;}%fДrBI8J11~\wet5o~٣UfT!YP(ذI 9wc#t]K -'nK"ul9B2rO'0ET55 hM*cDJ{ZYr-FTKR|OEM.˦MEL)#RYlrP'!cD1BTA)-&~Qd`nm ;H"x!mVP.*BDMK.4 zd`2AYAF\5 nWD ;R8)$9QHjnj5/yJKBӗ[%$cc 'L fNJؘIsp})#j ǐ̬jym Q?rUڷRL:Ytĭ?tHmNjD-^ ⓐT2gDzk:gxM Z &&ZpOa9&EOurnb*J~ivT [{<.ۣ餵'=lV(MU\ ʎe$lG':~fs5;֢Smʴ-O:KejJ G'A%6 )ͺiם ړO!Qo[^zJ&O}֑J@ H$Nbӡ[}܌=M8ʘd~_ɥٔ'w>i'w>ܔJZ*3' qKAӐ=[W-9Gy5dATwe)I.{N:u=3Pgm:eh)[A`dR>WSJvfaBBbN6Q F]mwW~ɨAfPq1?+}(}J2t+.ڝqkP% (A}߷=7%wڎQ*[]=qڅ(HpD?3/W=}ɷϾ=|ߴ{w;;}l{y#;}H!V[ϱ"}NE{5i6}ɷ35hw>>&?3}8"{5iV;} o}X|ߴ{?3}=[ߗɨ{E3N@,[s;3}X!ҰP?gs;3ڮg='=g4wغ?Ȉz@U%ffl3БV&OW˪z[YZ~AC*=SK4i%k,6xH9ZQ8%-;&XSEC$dz!]z6_wfUe>G=h?gc%J +]meUY8TA u\$.:̝CԞmYVԵ9DH' HyN:(ۋjU;AŔdnPq#uf^^[ά6*Q8L_6[ oؙ\[oؙ\[A:|(/ّPɽ=kO?c{/kYgݻ}yϪ6]zf,Wfn :LԄ/{H eu'AwW'rc6tKIIB=*'$}[ՋKgo9Oj;$Z İ{WU c1#T9jŢQg˓R2a2IAP$O?O_ZLߏPUKwSoKvbIrURij.$})u&11~DA!|+fazB.wW4nB0YYQ#hlnw ߵ [Wcw;v{ݹώ| 3HWYZT$qqSN荇T%.?G+&$pzFTGki{O%kڌ0T*t6X3 R0V⺑GEBkVR.ZEoҝRze.n+ X!%NstO} zt۔kZo)IE%ofi2qcGO'6|&1%10搩EH-(>cHyg|SxtzQV2Nh3Ϊ_ڶf2NR_A!즵EW-_zDL V $z~]Űٖ-a75r -G #TT1 zZzƜצkzmҟrnIʊSh>8"oX,N^$Ģ.Z3jo4C`Zpv_PuR-E؋~ّu*b0 a$`Vbco{P6|c%?4yevs;UcwggbvU!%B)f$iJ:(RRq dKi&rTJäV NR'~TQSgjN*Qm"!8A{ЮP1dKV^nuҠ+d%$,#|aehŝCkjNrŜm,6ǝv) 0Ѻ}j{1>v7 ۶wqFK?>%!6?f Wԩ+QU2i+ HXwg~ #ۏa5m>o?mnLc'ݫVg>+gc۽;*HOW/}F^BnzVqJy@@(HESu7SUKRV;V"rZIŏ6bleÀrs8qass~t2ZA)Y B@:[2ycݮel}U+MRk Pv^,WWB$#yHA9*p)Ol "qm3! īh6@8 @'υe}sFv|[׭Q)iu7Q ,:|)Er,n}߷kSv;[W7D08*(͵+Q~<նlEX d<0R7ndž"&2U6/rFRwGLx/hdkMo 3ҚA\c H*/6w#=7ϡF5ݠM k&ڣ<sH}Tϩj5V޲M*m鉙 Fw4F6 9 ' _z6\2U+'o0ڔrJQ.9< K*l+Ɓ+JrE2 E%Y9PN2ry15i z*zPArJm˼(ZJT3ס<>Jխ3"EYF=+lcqW,#姦5bx_u-K朗*܇6)RPt1[-Sd/$1MSRޒNvՔ:23)<˃E+}6?l6H6JThRyr9PI P>靧٤Jc<أ*< YZU]%*O#<|F":}&ݻB4f|vax[M. `aA!@`p{:k9Ut&8[R2眕+ ݈+ϔ@ƌ\qĦ?b=ꭹ9wi^MuZJP-'R!C- f]4{RR #2w-E z&Z5PG@g>!H1⳴82WCIKT68l$ 8; 9#Rg4RAwqᅫtʁ)yJ<#+F򦪜 2q@ >cӝuUF+ˈ۔hAQ񊩍IB˹W4 M4PZY% (>L5d=!զ:oG'%]lL:Jo RojG^mAkTCA!D5\QIpړ8=@"ltBL`ݪӝ vYRHJ ROyx A@A@A@A@A@xZI9==?8F}T)Rcu5J 3$9y|AQM]J+$ÓO!q+ŋnϘҪ5`W4/pd{1 E*-k {&Ya{kNq<:IX>BYu,Y/cR<._jb]0Te;: L(ڝ8QG,ye4!, 4.OO\v,q疇5zP>Ogc??>Ogc-Z *Z}SqڭHvN;9d8});AO[Qlڽqf}rDkBvՓJ9چt@A.Vųq{_Kk@b$w0Bs+tUy21Tv'f]Ǽ*ֳKLr;a\g      !{p{35w7;}gw9H\U_i;SWVDme+(;Y'q 6:"/*N]U#U9)G) qM !T1zЋtM]oڽ٥u H AAAA-f?s[fR_~q8N;nݵYq"I>;AA֏o>ե}̿>x6/w}?&`/@30@APzX=Ly{Lo}NxA^Svʰ*6N;Sn]nv-N-$uJyx@800:A@A>m}1A@*Mk¡D@蕴mӍMJHRJА:$ qQmsRSIjmv~ -0H\1[N2+,})# p mkZi2be@r;D#J'zmO3k(!T=:->1V o[&nV[-$$.DbMS jY-Ґ) s=OXQĄδyoJYIP=ڡos%Wl}0z@ӻ"jfenLUʒz[TDf)=.ZJ>r'z~~=)]^KbggŴ;6w/h\Dy)ZՊZ^K[n%'mc%M~-Fx$$M홊1V|ۗ 5rps @'U,,s㔤}1Y$ƤS,SI>YsoFNҬA"&ؙBpK=p@1"E2ܗzq]RSؔgs }leǛ=>'NfғUՍ$rO%} Z-2 wRgjU+zsP*Tw"ܷmڝݎl$}.^Oec;J2Sʜ.KsIPnmMnb:T)9HWFTֶic5hKeqg;VU $xDCQhչ+Kc| zS g?D"ַeoٯCI>-ԗJAe;%j&V݉PI eϓ:S-=%xpqo5iuu`ӵ‰T zéIijI=DJT.잠Td*I޶ZmMshX\I訄M3m˷M8*h~|5v=Nx=(`P9Ļ@(ulm;+1t~=NF|#銥:IBXJPYpw J@b^^|3lR@}""(} ˞R2$ݦdbQ SAn0tkʃ@(񖟕e-8%CpN3Ԙt2Ҝ <S _2ݸ+tL:ږmN2е'CaA%)<>u ݚKPqʼn^I2OҘNzNDAA]23.fyж3BsMS~fBVuV¼%D`@kڜB@M9HC?O, v쩘2Ax~甞V:~7 O6`tïlK c#=a|/t32ilipoa;{>0Wqy=Wå4W0iq ?ޔF4j7%gRy010:s#"8CuJνy}o^/'k{Y'{{YV( GvTܚEW%Grݸn##Uz-@]"_>># =g*7 1i\'%)e)NrO#}J{9#H$׿kMING# P/}?)sgjxfjVLL,6SHX$ иM93ܬ$zOIIRGRp!KC w•ADTuw7 tTuw7 t?̽(5"*8HZř+Gp38$~ak 3!)WlOjyFUhjRjVQn]5J Z(QR܍{w|zk\:UjnM*Kn8q&JPpPN;Gg zvj^T[mgД0 Ǿ*v;3oN3+OLӤV ;>Ԑ<Q>I=!uiKMK z|ᅅv.nm@{AL]O \ӫ1U3)Oܓ^u=t$Cma,6J`goHKSd&]mfԔ\VI9Q9'SAo&ά: JֵtJ@&O/'گLq,Ϯ_mոj})'$;U;V)IΓ#N U䠍-#xDC%YM֩%GJ$'H;hv%/ҭ M}9z6t;HJyRb"ȫV.IT̴ܟALK-a wGԗSeߔW?fԚѠקTIlq!m R;B`Bq n{-{NG\NK:?m(^(V0Fy.kZ,ˎHLҌ)m [B# OMF-JƦ)ҳP($#jVJBrڕ+wD ͽsӭKͥJJFT_i ]?civ̫ӌ-)R!N%'!*IO^EU-WFr\{ߒK?4tTk2v[beR4-(( HRsDpvwuS˿0n" nzH7h}K=|%q{otjus=׵IfeMSR8V뻒@p6N]x[Ƀ2v2گKcT6T7Oraa6`lu2sjX-T(օ]T\p%}0tm+Ω5;vFќg#E -*:cKPAؼ2~R!#9-?&܌32$-XZХC=b.~WuNfbKM.7ݗf0O4ڈRT9 -0k:3W]M̬2*y/;JAXtjU&k>M p<ӹN-ngYT,<]TӁ% $cs&d1r7]Vۓ/MvE)$$=OX`k%QʿX,HF̩iAs *cK>F榮zZ]w ((}8ʟ2;@q䫧Aw"U]5DG59Lީ rTgvm4N`;n^C{<4iHRP,(`sNIvZ5ȤSkbzLO(*#zsV: 8o)Z.,%JڐzQI><IzONGba\ӍڃJ!IPAȎ~KZ+zNU%Om>k[AiNRqsU> nU"lB/c:Ǧg˱` 9ӴJ5YSot,K-*pH9 9$=AX6%zfju2]e!HugjRF 􎛄u/_0O$>rT!wKZ,n߸ԥ]%;VV[N2CF,?cNgXLK,!RJHO犸vV5Nj`\rSQ6bJR ]s2j5JVhO]@R7vexapTN֧#OI¦Bz:KRG'v)3^NjjbL44k*)vmJJrI qGQ* nnNRia!EUpFps#Fm БVaX rP%IBw KfHջOYԤg%''+u)%J=NN0#W*j-gHVg-g]--BRB%iU4:Vޑ򗺥lʨ-RJ%/)d*[~JG&ZMl; 9IBeENV)?k\ DOK,ͶIihh6b:ͥm-+B$zJf>QϘvzZWu UIɆ T򒓂A| 9ȿ~'CV.u&eN0hU XޛP ])u= dHLwu/ }bsvVܠ$뒲!*R3@Mfk':>/E9u"s (ȶZF 9-( ܵ(8kjr9^[-ڟĊcsw:ڹJf8NƐNzx"DHnnjtңӺ;GQ O_9HUj2D*sR$¥anŶP\WU1ťq ^uVI۵>g.qd .9L4YJ&JPħjqӵ%4+j(S;CH pFRRTaD8^lתrZHip݉^)Wu\{* R~GT/QJ(ߧoTg.p'rT,1lRQB)TS5(0T@H'DD-]VbߣL,ۻA#(R|5?>q1eI_SjSȸj+Tx$RqбjDE?W7:v4GWB\'"ek5&SRx #&˷z&~NL)˫HmD䄨rG{۾f&}R+ 8*r麔 K:A9WO&' XTMT)grdJYRڤ9Ԕ09`mz-GM*ېnFE+S TuRTd1\v} Ѫ39G{$\VVpPOAAJ«Bjf|!O!YRnԝ :Z2֡QRkM3)ͻT{$Ĭ kElζZ"3ݢV->JvӶ+PhNTIm;!6m$UI$`[z*W,9,Q,O\$H84;ȵԛMN9.o.rgB%I +pXoAEZbṩ ^g5(1i4R߬?If$V]Ǥ ͠ Ry<RPIjUi%դegAS-%AT8 FS+rjQʅK ܒFyzzudG[sؚit)UQ)TeC(+ ߂ Eo굪E8KSov/RTN(w<H72Ĝ3o6ij(SPR$ Uj|Z9N5I2wSC 2 ƭo-j-mݓ=ryDQ<ߔbrUG~Y%ƝiAHqd)$pADeRw-#<ȗ; [Ghxվ jmʚeD;IiIp"bۧ\*mLz; vzC`^6uru{!6 iFA)RD^A_S <*(>i̸8Pr F AStȵ+lO42R{Y|"65+J*rRkiL>P†ApzmЩVDjt!{AQQD=LIAnYWmgiԅ!0W^矆*i> {uk׽+ζ+u奶IRֳ9$Ԛ>&D ~U亂AI$u׷ktJKru'e&q{KI;7m3^LJ_{iϙ(l۝>DBimSi=i0gÁ*H9Bqs5so:LYT; R$K7է魙3S\I9Sg8@xiSyy<!-US vsl]mi)Pg5$4AcZj )mݓvIAdxU"ä֥S+ r9 s~NKTߧ;QJJhA YH p|LfV]vXwiyY8LK(g^ AaUo]>s.pۻӉނG'qcn˳VU-u#Rnflp)'+Q=3"~ ,KjsHO 78 RAE+㘓ӥٺ|eIV͂F)%*FA7 Z N@6̜Qu%+%KI TzaG Qa{7oQ &qNU!G<[oWQi-TLӎmB ?+G s$~im~?7ʞ6nmօpvTxE-jRNUX@q$>h;{@;'>zc~c`hcN tI%]띢R9ۻg  KBy#[k,8A%J R9Yu/ZHL8&iONpʔQV|rd![}8oi5iSbJ&ZB~ FԆH $dg"e4Q90ԭeTVe'ޒ7x AAAAAAAAAAAAAAAAAAAAAAAAAjeFͰq}dBsQ6LIWB\iHuT I9F֠jJfGmcTu͊S$$L}VG[Y z/KXcwfZ*mVO> V]ffܫI[JR\mgn{€<չj؛ՙj~e6̛IS)y -,AD)sץ-B;ERQ hGrJ|LRvCvڗ]z~~uM11M*$EECi[J< !hR S̪fJ.Vpx9KZoק3HeEs=qs\3 ¿dJͽER c(EIu\x>yN˪W*n$ hAc7t򭪶u&Og*{LJ-^>j<Ž#1pOJT䚜17(o˸Bqb鈗mLyBR H:`EQ廢,J}@g6ς!@G+t f)*t٥ٷT 'x1SNٞe#:Jj^:XM3,NlCV.T[^,UbP%$r_p z~AhWŦԷkez-ZSB%%XY A.-)AZQ tadu,BY*ޤH, zDdn雛UZzzQkm7P&( +,P9G7=ޭӅrTm_/Ria l;IHbvԮ}fn;vɼ)sITiKi]kJzJʎ0qNn:5M\R$-T|/uQj\\rCrDI&YV IKL$N|9kLբYN ;sW8r\VZrNcfXzfmؖeua(BRxL,~mOb 1!P *,>ꀕǣb;f۵G;2}4J)aE8̌V8]FJ"*rZvMo:^  I >0b*ڶԛ"FD.SZ4'k)ʀTU%DaZEn8)53NP) ]f{۶6N᩷ EJLjVݻJ9QV٩)WTDD.|#aW7K>'[.JNO-RTڝ  GA%*=-K04L[zm鈓6 "BOɦKqޘ_IK6RN6TG$aydkO|ڡCwJ)!:Yq)I>wџLL~oi4ٻÂݬKN:Kr4H#vU)=RN<%d2Jz%^Cf$Rp4H o lk6 e덧SNj/73PǜLrTp+(WuD:DpJ j6dQm@!*)VB#$r<#@:UA Crk)HՎS]A$Gr#IB}!XojՔ{%+ae%N MPG(U):Vq)VH<+yZӳ)Y=8Yt%ųC=;\k9Td/K"߭ѭ蝑b$TJ G)# B}uΏ7-;…v`gi]a~RЌC8sԫb^MÌriRyNJPTrvMl/*)$ʏyJ&Szr]](458ضUkL8PBTz tA\Sg ˽AruRӫWW-_6LK-a Fi'9CLz=8AZc 8@츫tr5Vy>U!O;K A':Zh띷R3bIhB0K/cs;AQʛiq0?=ŋ-MBw;Nk{:&amaII9Zw}Ngru=/.Xs (8WNP_b( 0TqeV+IT*/%9VΫ~hw%*좵VL.fꔖS4RppHFqہM2Ф)ꭝTS+Tr}4=O~mRU:09jNx68T}$+'<) {*4Idji*q>U|0<#7oPY^ɹiG(ߗu.!xI#㛴'oU^cijM4+8G *uk9^*z(yL2q+M F[Jc;VzSJ}5TrRlr{-O+)ւ nZWS`Z~~CaSSJ}ܵ?TZl*}j֩_9_ۖjckA O-O+){rRlr{ h!Z~>nZWSa6>ܵ?TSJ}5§ۖjc=j֩_9_TrRlr{-O+)ւ nZWW`Z~>CaSSJ}ܵ?TZl*}j֩_9OۖjckA O-O++{rRlr{ h!Z~>nZWWa6>ܵ?TSJ}5§ۖjc=j֩_9OTrRlr{-O++ւ nZWS`Z~>CaSSJܵ?TZl*}j֩_9OۖjckA O-O+){rRlr{ h!ZcT~J5T{q_R{QE+ׂQT~JxUOTꀴ{2o粳=~n;\6\Q*ڂn.TdɒBIb ssǠEډBИS4Zl={2p9䟶bF _(rzbNPݞJf[hSᵕ%)#$OV>{u'?Qm32K)I!%g'H% FzVM--o}7gEeɾڦ$jUY #=H'HؚGYu%3%wK#i( 9 @vRHPӍVnVC&,j(J;$JPV>r+u){s%[ܙGTI(Rs):6gSIYr5K%i9g!J^u)_%߂5ZC_?I= _$wC8abqm[̂ o)1#2r .$sW 7$ TlEؓt^]Y HnEJZC$ yfu#GrP6eQRH8 HJ< 6Ef"j.6TBR{4lYJNA` |.AuFj̛oaYM!^@9"/:V]u ӓS2қ%m =\iV]+6K6\l˘N! PP!lk-٢ZTڵR\/5A,XAik;RR e Dk*oVeE&^IܤKwXmqTNtɈPje㧵>;QN~x)اJO)ҡ@HYpAviOuO jus L?˱LjBr rxBp2FH0ł_(*E.;{7ORÊFP Pr Hʓ dej6}1X̙PèA!.u߂CvM| "E8>$AyshG38\gy"vaɚ}L4QCJTp0OXAM[~W|iζ+꾼3}MJ[I#+*;|uAuÑ Dwm4e}A<> fZᥪ /A#>N|Uz MYZJyF]z_Hϫ'Dk5*ӴQm_Ti ]2je)SIH=[v 9IqA8Is<2I?z;TI]3!=5JSlZIOfQ>@Qnq           פ@Ay9          =}PfA` AAAAAAMvӚKjY0fMΡ!$>#W~zmijl4&+pfJ]Dm{TA<vAT(Fb JQgqXSm• $c@mTؚT ro(C٥<_H$vjT琑`t"/]tmZYzgU4.,. @WrzL[4QtRm@ԟ$*ZRqK'][p,=Sjޥ;.eZnٽ[sgykƞjRμɕg;y@8;FNpzp 7)<źI[y5ny( {قr|qH{yT{Y8ӍyoLGsi;O)P8 urSfV+4LC IJT6OT5+zU:Hy'f\mn.]8* T < AQ6|H [{AL-fiBZBvޒI*A8<O^dRxmi]Q~͕%!xuYW+ JA)Tjm3kfLL2rATڎG\Hؿ&euiLοY%* =uZ#ĵnB᧵O>o:G*X9$ 2Gw >ΦiTvIUj沛Kn'5 !3;\M2Z5nZu6&ihFҥT$`(C:PI7}r@JR8)DO$`& .pYmid(b4H%<+d5EbCWTàS 9\  ꆣR4ޙ'=\fihQ O;!o)ԻݏC9M?^.3wE,4)+B^e)L̷*ҝa$ d}P*_gdf32v{x}|_RQz{]]7)#,V7ږe0Idg!9K#+}7M_R)> *.w22A;T!x\tJڨWk Zd޾9R %)w'14& l۶m)πJt*q·!' zTһCQ-TV).Y8XpL:%90A |  &4[λ,gn$<9! u JGbT5l"[v,vBYnXNJOhz7S€=` ꑽ-:}z/4Kj,8"ch"I ˴)Rf&VUN!+qEE 탾TSg+ZE)bMnVUˊ%nl%##WbgU*zM/Mvy{,lytE.ך{ga8N;mB;Scì_c5ΝVؑbeʥ2hRPRm@(+>>]m+eRMW HHHFZWp(2$VejA_S"٠Q[zQ1&ɖT>jBT R{ޯXvqt}fqc81wqyyk68{vYFۨ&kr;lI˓K;IDh={i&વ,{z%6: z$xx] nj<7n?(Ԛg{L640TSC~(=]&.jݕ!3|\ VgqH FAW4ꗽ]&۝l2n-*)Zs@RH#8 .s0YI~Ж:<$'PᅎUjĜi4&^eL:ֆ'!d40I$qR*˒hV\ehp-$7+<0@"oU:]w{zyG͎)jsU/|[a Ȃ YΦhԩIUj$^49 q7Y*N2agkIKU&N:mT PT14U"r&Zi^V֐0THG*O$rzzVBg)$;9'Nq0JQGt04w29(dO^6C xg m+XI<ԍGJ Ee!OTD)Юm! .~VBxy}§Fu*vԫvMftRUk!#qFww$Z\^Ԭxؑk,q8/4vLL%^ikf[ͼY;˛7q8Dsv\XS6= ]2M2f78 Rkқ%RʐjSsPJrSN =@֤vUKEPnYjRC,8z;S99Q@v|=>8>zO*K%u*L¤]Uveո`cA 'w @87H*z⏧5 nbHI*]"Ez0ZAN8DfhJjj%]͉͒@$xqVHf zOH=zBQT *(\%Nnu_JPQJR@=B0YqmifKL]*i|211/̌<ijj蕙0q $cJ¹kRufR)r)Y^Tr{F"81( nԹ=>Ԧ\MAن4Rv@gz'3U[6KRH $=q7:zSx4BefmsRQi$(saoݺwSܬm^J|vLTy %  i^VYsqBԝ#;OCɊ"缮7Jhө}ɺUNtOGv>qQK x*Rk37͙hiSJyOt>YF#Y5v$:V$*7]/NQ!%}7()Hr &B=R|3fĿmu(=cpVG>ol;}3|QΚ]}4ijeR˒IuS7 9/}+ 酞@O@Z  3oyvNQm5}M7lҥ&c'hI"52~EJ%Lͬ%it)AIJ<p9J;zӊJnU>cs(RVPJz(b?wj s[ @~FRxsﱵ;+fJI; (硏MҫՍ@&]ũm?.O#8E@+<z=:g MNe:(ޞ۴ڬ%'{˨lo Ozz۝V^2/[{\ )'G'X Tg˲gggrVuBknU*}eHR^؀P^FRzcL2ܽMt.uRg+H<g&&\M^߬JolmQݵ=qb.)!C#?(srӹ:-M)r.:ʅ]n `-D2*^ U M$jrW.ZB⧶&?Cn4JzJ9"j6 $9$¯S6~\lzZk7s 8n].r:7%ꗭ Q7f(Jt9-QT2"95,ɳ5*O˾ͬ-. 8 dF V*/K՗Ԑ}=@gwxqQc/u~]QKLN!-< kP/ʄs5Y(LS @Ih*(#;oIPP$-3?0$$VO9$zzRsAbq_>}Ĥ [А@y)08>)%]yV+}II)@'$!?S5mۖ%ٹVmD(rޠ7mKJ OK74NJIPN zn%U*)&Ugf\@$2Ndf dt.!Xϲ+O~$9ԛffXyJ|nݴ~}IT%Q*&f6RPy~̝֘baʥ2RPPm@QVyϢn O#1!p'J4iQ[;0w7# Rgn}r*j+ Gghr E3M(oWDq= '$ǠE;Lg aAҪde2V:EJl0UҪ;8i NT 0Rd$B5PMSe\g1k+xpvr(poA@As:ZbGIKm'n -GRcצV(eٜ 0%,'őJ~fPikms2 J(*CiI)$2!ٶ~a2]a8ovGl1*^tәibL=Py!qQdn*"f$g-:ڇ=AD(+TP*ӮuKqq/O$Ir8mRc]zT.ہg)Bamq|~n7ؿ$9u1PS.YdVVΉc O(*-#w T,ηE A JA$g9q^Zmwk 5w/E<8jDKh^xǎ">ѱuvةR)*nҐ}^d* vq0WP-bߑy5+4ǾeG^81N8W?߳6=h-[hՙ*ʼn8tܐ;{ԁׁ"eiZ-C4f (MAԿp9A3VjeSn ]mI}ԵKe Q(zTmM縯]EJ˕IJIv n];r8#~6ZuN7znZJ$K-26[yUHJeR8ekǁR}f$*ډ^_T;&ŦIN\y˯T ݆{'j6)D *]j [$zoQ&2( qy=#9m6֯MbVe$&Te;*aj)9ciO1WVj5L)Y!.|֛GKe[,o Jѹ'hjwuQRBRzR1eЙvzmdJ%J?)$j=6ʊvR;ДrNNif۱hYԌqLPT;I-O炟F;iO /Hw/kPRזMgZ)h2]2n.aU-M.)ى%VK ZbQ6UTkl܇@ʒۃ瑅,81_U3a 圵'Ki$AX=Gb@Q);Cʵ2Z7qtNj43kFҕ̈́0R!x[ڨPZ[ؾXRH I<##G_qz%a\vf]4"m-SVm{RZF'Hu&1K:IܵF BJ J7wCiQʴK]l h)'!)Q% g};Pi.ʳ922%,*=|"~˥CT6 %]SD!GLAsM?_tt)N+^К2afӋIPR#nԫ'j뎢5RԽA,K#ITO׫zbIT=ˌ*|UCCທe~=yg-IhJ 4v/(+d"1$5M)SZPPq$+>*)/V)RO=2Beie44ٶ"tz#XWYjWD)+HKTո^TDs"hT۶ki%=Tm0BR hV0:a!4[F|^g*Ou-vm4'l %DNj-PAA~PqZǤMJ3}'PZSn+mCjA1F9{y'fLBQ[ަUG!'DpZW-ԋgyHRؔ h\2N4^FO.nI5t%)hy~eddK֐J}eQ{VNT$"CjpLI*QQRP)ЕG5P̂YuTjwtҩiJ]fqj{rP%u;ȿƫaϙf%."eFTinII[x.ee)I9\=3!循 ,LȢ^i\w` qу$`1.Vq^LQN УN'bǸ}^vM?$}hL=>ZTT%E P+krSFO"2y`Wa*DCIPPX|k:Ԑo4.*K3eخnWfrwmeW[bYe@!Ja!DcSMWQ)h $撷NR*_vNUAY<ڛyzq 褒Gڇ%PuNݥ?$ФS6I܀Sv㈾\nThU&JĻ @ϫr,K'| P|U)Os.%*)BԢ ' LFQZѩTIz7n]w͚u%Ǟov68v.e2z]2^rIdJIZGxx銥cB5I@Z$mԽ.Tw$%g*\t1T,n ZH*TBPRPG9Ўa7_PRזUgZ)h2],VTt,kL]4ުuAYR[puq<0gvjd.n錝iͼf֍* )!G; `E iGJm.^"XZ|JuR܂wx0(^\4H=-[Q[I=9 *H;%to3ލZ^G,)`Cîbof|v'Xl:?^K%RRi-E% -+=D3H (ۧ 0Š[wUXSe{$-N>irL2JUC="\NjH2ɫz_nOŐWjN( S-J1&ZH|R n6j9Ⓜ#pJ?CB'>oڇR'r!\z.Zdo5eVlvUɴ:bjܝ⫿LrTz¿妒D*;A rNBQYqJn)IK8gú5ޜnmH\J]RI 3DG0[Vx>8DOzYUW6)Hu] .:I IHg8L># f֪T3 rQo6 $IQ8}R z?ĖPV X PI<)$Q{EbZ#bi qò'*Qp]8[ʥxUU*/:v@)!v`8$ 4vvZE.褨)4[ܕmX W& CGD.4ҥx }G:JnJs?Co//oLȄ8)+6x+srS)%Cp;S%&$Zݞ?)e,2)mIBx8#9M µK@kInxPUͩD(R !YmZiR\ѮZIdvRny1%E`WSLZWFm'`%#ϨQξOr֜,fcN-d0B>BH^P 7]oيΗ}-hFR٠6^bu?Q3T.M+ si*kNGUq0=OibjST˾vhR) [J ai9 -XcFm]q(RIRҒ27͐@tdhm%fh@ G ?JΤn]䜒/.9\?J>$cyOrjF3ESmjߤVfdfad)hۍÐy(V8"@)'v `3?.? COSN=e) JR[iIP z3*nQ˃(P@!<=$qɡçq c IH0c:+ʉFЦR)a' ~1Qt¹)z "6*2s>k4 Q I!DxHRNw<4l=Qk6u%DL.Hm.p; ğ@0u^tӧ.LIʤhHw*B-%[r'p7bAA@A@A@A@A@A@A@A@A@A@A@A|–f[ R RJN3p`&]b*ATb{ߦ 45d=n81e[uAQeļvII$ԒI'`5*R       yY9x% zzA>ב菢 >` F8 xŒCt+ls|A >W= ޏ@%@<'=A_8mG #,@z#90NHPSA %@iQA+rI=H|Yڅ }ͻxB~/=FLG9J >c½$@{G)Fc@ s@GLc Gž#@|)'>'(?` rzmH󌨃 V)`wW\AM$IAA@A@٠FDå\L;4*{JFIF``C   %# , #&')*)-0-(0%()(C   ((((((((((((((((((((((((((((((((((((((((((((((((((("L !1AQa"q2#B3Rr$b%Cs&45STc-!1"AQa23#4q$ ?aDDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@ERMzFݽۡ h3J* \DÇXS{_-V-&qT$V"aDDED@DDD@DDD@DDER$EQ\Zci`Z 6?~q-h4Aw՞nD~Z"mr鞫{9'M:W@UL>ԯKiR s1BȦVKrns6!5<›5)&cpDpS>(բkl@Wv Gv#@+$(_X2'uXޝ?>Y~'}6+4s]f>KGO3ھnc.c `PփxByZ{cOܬ_ d =JׯPSR-0ok) h;*u+&i(1 /-%}*!#P>?obNq .45KH'67@"$y|K"XRsO]՝Jbu/wu۱+q*Ҩ2.lu_io7e \R8i6ks̛_$oƻ :Ə,sXl +m1jZ4W31U2^""" """ """ """ ")UPvQ,"&xbxhQ^ei3<9l6V ،[@Ý[; qX=`𭥴AfsH?~c-%^jCZ+IiO?^ZֆNE2ԙDAF77j > !י_̆Ur *yD=us[O'q F*73\][l|-*Ji w.߳siw 'o`WFv]|TS9kCtvvҧ0t{3{%qF,?OsOIM;M.R8fD0:Z?yrSio#'aSK¹?xCkO%c\s1`9ꤦL- g$RV]Q89pc45k+0#Ji`TL(YJD5XCDYTZ &ͮ~Vl7f>wئ*\8Hn)Լhy(\xS-/7v[lpqiusf?x!:_oEݟ)lc{,kM*fpF~7t~0f\D'[5[LoQ  ]DD@DDD@DDD@DDD@DDD@DDD@DDD@DDDAQ&eV4R<O=\d!k+.<}[['u09$-ͧ!b!`qX9̳]Vc`ԓ%_7ZevׁUPj>XhzkO( gaUlA% s1x uG%r<=vF9E %i `Z)+ V~E*8gnJe׬2wuPS kS12&b4}!a+ <9Nĕ[U)ǖ͈R"*Xp`悸wCȁ" cn$J [3ՇLh"!eF Ǫ2OO6x󕗈:8DdVֵƲ2Q\gvCwt8!\]G=> 1vnkwgVtgROzv/`qGv'ZG^#?ڎ7~X#0Ìp˟|ݭ[gm}ZdsgHէڥǓcaO%B ]DD@DDD@DDD@DDD@DDD@DDD@DDDA^ Kl$Ϛ68H;6@@9imvU i*4tG٘JLO tk-i&{?Eڎv+k4D9Z :GZmٙX}eS#Vܘ1tX;hM]oiӞs$-X)lƀZԩi\BR"\P'2d6<ȂjElA`ɧSU}Qd[WgX'*orսNU3횵DDx}<ꀞ"+ ܻ;>G +aq1- '[uƯέn"9o{GLMgP5z:hՍUY7˜f_ˢi'DcHtS625xuQS`'1'8:MC4" 5*s2g44N.+Z`5#[Fu¿xƫf}@BU$C"ê`]H>ټ:Bꌹ5ps!eԨ X]/, }Ɖt>+.!'ĭNY9:"XS3[\W*1&{CZ\_s  Tߐ#d钶1[7Fqxwn4  IbA_cL X Xvړ=:mkh;)$oϒ?x63`߄4RN34wd [Kr"tRyZ$&D""" """ """ """ """KĎ 軝ءN!>%xݭ^!&D=yEa&XOuXK[56ng :>ElXCZt"e1=HL"Cij:[dV>2?5ns]Cie; ޮTi/WjcXᙻMc߮bOʤkc,cIUg+Ȟѿ IIuZ(-E xX?IFsM([MV#g?dr\9&<(8* ʖ:a rT$]6;ä́4[S 0xz^DݍqN eHe00tiRӚFBnav#ʬa"&|oT !v&cxMCHT]͗T,,CW;AJXvU2z|ʋZ+:& o1u4Z؁-y4TŶ(cˡYB0cD2  3g\V@Hk* n)RAb%b_tLJbAڭ-m9x?L̹ζ!yUqvxks4fvj7'tMOsY[ovp'iـ&s@'RV^şU~j۬(={[C+ҨH2AZe7}nnf\{PxoyKA5Vxv@SpthHЅ[DwH5ȮRfGVeT\D$+10_EįاKvJZKVigvXE}j"yA=8-;S E_1+uM2m0Y9YQ1<8 \;N1ruVʽ'S%nP+o#R _)ؘR˃'(drP:(JtN6r+4c2lԓg*Kxt ݣ nF5ù ޮh>Bqe}ɛxbUXgkL /'mX D GɧMd_z*S0im'Rl|USvbDkpV1,r.e|d L^XsJ+=#qgn7)6U&ac`6Jq+im'žI,5'EV,fIV2=LˊvF캠{@FUmc-QlJXC=<cגhjꮬlRro ԏmj1ng1i0o]Sy%k\+0NVTubV'T]*qk者W՞7 >JGz%ٮs |õ0/{[Ďx빍ߴ0Q^a 32}}YU: """ """ ""AqunHVcKu qL[[mݍ0uZ!8͸t]\w8,zv,8,h#kvi{N׉/LrWgFQ|+STaikTL~K'x*`1NBcl ~e˧ǸXbsmFNϠ\f8T`6wT2]itx!2OUiEkZs~ ҸcOu\Z[#("s+p꠮|ߪp9]X E]ê\x^%D<PU NϚ@t(qe7TzD DkNp:MVJp69)Аy\ų( #èښk\o2 \vuSY2Zm ZAPB{w )ǬGPv,5$kWO(}:L] K %ԒU؈`㨙V&jZ)9Y|‰IJW݈nq[-TCbsvJۖv}q c2)37d+uhz }ڔ{\u61N$b2CnM<=YZt@W!""" """ ""*+:A.W%wXj&jB244vp}K@& u;$/[[%)34>x(MR5%` ^`ca.=T+;i/hhYR& y=b+mmwV8چ@l_OR ʄ0706xe^XM!b6Q5ML3ysh$|Wඖi0$pEO-x[RotW5Qqhh؇xgbo.jhkN ^O5uRy4<t:n-'-5#8gNv0Sqi2{Y(lr11JodПVl-3;:1t; .yHЏͺjk塛t˘UXjS|Uf3ǢkaD<㒏0E,1i -2cS |MZ0uV6@'$4PEee3 \ K%aJ|X([RRݼf4]o}2  -6 ;3pG+=Oپ`Sh5lD'{,.< 㔝7cxuU]f/s-{o,ݟ1h 1«ϪԺVznX;l0,sEM}>6>8vRCX=塃/s-w>MvX+*_sQSųWcfU>"Y|774i^SheS-?yҳqm5Ko?7W\^jLjX=LwYrQ%a=ˬI>\DDD@DDEYAE]G/bYs 7ja(0sffL^TIp5hu, C8 9=bR yh*RڽgYOh]>ݥ_,1kyI`:~k-;JJi=Mpc?B&Vt`?\{Ɏ>ZL,}-8̷֧RlG#f铏B[`=Jo$Gzʖ`~++fPU:U͇:htETˆW3+A=>(P Q8phRWT)+_Io_AJ0%L F VQ{Fwz ij̬OFt޾ b. {ѧh*ѭZ2v9G]}FT:4 zUX81dQiǮ#LTVZmIߓb**D'2՚R&f'_+DO ٱ 7&X"E36A3cx=v-~c,i7?$KfiVգhB(t-Ӛr=<ڵvkQs򚴍?vu_$ͩ7jRs5h8_5Y>\)+jD5DDD@DDDA&fLwba)6u;;CyN֦k\` P Wm,n݅\1fJ4>dG9ғ{^_Oyčz&%`r+ ڌ-jV]^}3&<3ke(:xϚ'6+Lӎ+l<eJnw o: &uLk0 Z%7JXMMLҫAi"cـΜu[bkvmRØ@Xih97?UػGmF  $uxq8B)KAX"<1ѽL'ia&NASna ?U`\n*l9o6ۏc*6ucWOw~^Lv2l=_aTf'k){%ݠ;K)Wbs_S;/ TϺ~ M5C٦n.Ǖ/*g;[1Q{.SUznY'OD潛w _Lxu8_sMVqgZA&i?7 Ogtu;hn7Xj1v.+LN -[cƐlM+wbj{q`8{<[um}^-x/̑OlY5㱥i3Ygnb]X:xWИmaiZT-l _=:zۧm/j|ZsieR_b!U~lj+j=' -{Ok,gK'wV6V)OjdchO|3*vb]kq)/ Kսb{(WshS1T ڽxţ"{7Y?aֶOԹt{< _~ Sٸ:9jkGzµsoK??򜵟ļ-'UaMљu730XQx.mGkg~X/q[_^'V\^0^+ʿ?hӸuHu*3*x&Li3^?%h b!V?{a؊_P"aׇ੟T _f4w*uXgB;WUg(.öka}a;㳘ֺ*w;;EmOoj}v\0xg^q;狩e: ?\)fЬD3 R?ѯvEnOHgP<%9ugkXMLM~ֻQR]P"ĻP""5iBǶ\2S nA?且MʳHl!q+Δz&Gو`E겓_j&]qwV?={pd@Uf7]&f7_Roޜ ]::o_gUƖ &ݗVpϞvf#s U*'X}N w.2EAQ0iDwgp[P9"UvsiX?l꽋n*{ LLƺۮH8cCOh +6vXJ:# $GMF p&F+{ƍ9FôJ@3|PT95( -Tad_z*qKjVǘyϷ}RcC([LEi~Kʻ_:4^ڻ!uYcަڭ~(m;8<mc?3U^q?qUPwl\uL7W.m__/ɫ|</67cq;?=FI`c՗UFIf,nl=%nG$uV!PX:50d#ࣧs<BRQ+G|5%8+C碝-3-m}Ze'ɉ8,0Imrof`c4.9e[yW{@2|魘xeIx/?h};6DEz@tUhZ}J*4*;ʘm4]mŶͪ7q[V7geD}~q[wV-ʌym [Wp9rNި H fnhmQdS5u擩ki55A iIjŊon-kLt0::լ\9'B^g.ӹcCIw*rD/!н~];R~g^1V3%~b*Eݡ" """ """ """ """ """ """ )hj 6qԸ}V\-3tFldjV`[U{괻;iޙ!lWϊC쭠12GhG ߇E<@a)iݎh25Vv[p=ցy-OP s |k\Z! kq5NL5Sfsc{6h="MwфLφm1c5^) qb>I$ݷi<29e@tX}R&Ӣ8p؝;?ˍB:݋zǵ~oؼx].j~]B; '/<+=Le,1c6VwvIu8MEQg JLSpX/pغdLz\=f2+͛PMa*~rgL2qq,%\O_kF9ژR//qWUp|ncͤè/\˫9..sɹ*V/h4+yYtziMF<Trq}p;'N/%Яk;/V""Ȉc qY<2>6*kNXSSkvc1_P+pry''1eg1٩wJϯ_d{Jzm6m=ڲs\e<קJuC 3nE=&$<@}L0 \NT{E[-$k+^OE^<^$ys.3,W.q*oEb>W J)Ba_Ip;JrA>%auAPq>J&w02C׏5@Zi-O׮4/xhlqTz9j}z(#Q=RcQ28i'g3= p˪ݮرZGW^wɳǍRnXn&?WMuii *nJhz *&$D_Enku}oo!4+dqTa*ʡVNT:dηI s*GDBGUF{}e2D>mp8B]q7E\Z'^z fnuo&Xr8z8xzChqpngEt>Ƿ G~P,K@{O1n=o {*R`cX`͜x/;[~83hDDD@DDD@DDD@DDD@DDD@DDD@DDUgH#ר nL\8rBF]⣨0t(C x4G,ys)-\Q׹+Q +i51'b_%^rf˵5X -47:Lj c6[c)5S$nS.)T(N߿5*y[yG0Tάq뢴RX3I5SIsT$3(zI>'5[F2˅ݮҩ"2ρ+A[mڽĸVtx/r&apFGӜ'[#4I_VpN˥BDD@DDD@DDD@DDD@DDD@DDD@DDD@U:*Tqj!o.4 >PǗ0bXOڻ kL@h>^/OBr]^zVl .N<b;-c;/iZ>n{K./6yS9UTCw{K˜<Duy+Y<%N JOhA$x06[N˛}=G%[)*tt_)*IM#QL Ux`Ǘ;O)C1ϊt:WUvj:I+߽^aXDDD@DDD@DDD@DDD@DDD@DDD@DDD@U:"pme,ԝ{WsLMJnJCCv}htAXZ{,t:5{+1Mgk<ÓvA-c9Dk> <8lٰD4YE_Ъ3J̨]d#rUv?' MӹMgnd~k A=P0-vmOϪ䷲lU0,2 y639|*6vW0UnBLry[.+GU'grq[_eRr+Jʰ5 BQ[=~)7deRDjDRUM,$i:j&ZV6kěhdM;⨪u*B"(D@DDD@DDD@DDD@DDD@DDD@DDD@DDUESS6۳_tɂ \.LD6EWa\Ui#]I\L,Te=X{!@lrZdƼbyȷKzDEeFEPX.gVqgSrUص!o Wjr:o[]m۟8Ҏ ,,-鷱moکG'Fs[>J;`,<$r>iRZ8ŔqkzGӜ$i`Խ-nJ5iaRv^0wNϝ{*eW$Ksn/u 5mRt_~FFGŅնzzK3TҀ^96Q 'JTgj[YPQ:O|F5ڵ3ا%M|LT{pAnN(ԯfhCGu-m1ky RӶ(>89]UFUh4{,ztίjΛymzc9K+pޒ> l=E=FK}LxcqFKի|y"MN{>sɿ#%/e2? zsZ1|5*ivU?yeC j?#n:IqM`{q|sݎ>]^ΦeFJ/qz3yo N~} kt6YGIqS72}|ޭJ9me)Ŵa>RqKX$y}CP4nndܻy`eVګ^rҀ2Ps^hj>BV*PRy}N7V6!+aE$)|y sճve缉> #%,8nJxX+O8%i{0-oh9~D4 &Wr[ey'9͵JOlbPYd6FYei_;w-[oAoA~`X'cnm_MY-jvt>?>hYZX.2HgF5ГY-%pfi\v}IGfjhbm;zYc6.,k+BƊq[5\XR[m*_25Vq9iTu5{8=JYJ}ѣW:D糼>*2sJ[閞--fYwЍeQeq0†+ktե'9뎖us:tSnGGls[}7 W=)Ej8G5;ךpj9Iےfo44jUrRk9<|ZK/ѧJj^W[8SzW]*o%\I:JQw)<MIU!w 絆UP$@ywsP;=T)=-7ZagR#Z'ɒ%}6mO97h[TeO=Uiߤ2XGPQqҽv5 Ҋy'rvҾUָX|ToRt =[*{O^*&>ۉկ{uq5rG%*vJJ$ml0V(j͕8} ?:e]RRW&)9$:6%1TnN._Z>9h76Z^wcu[W%:jK6̚N^(}o {6BN{7Ù >>tz0Zo5?ҍw$ߤx=B.Eh{Jr|ң[1y9r_\hڎym:Mv?&_^+>?!'߻t ;l+1e|ʗ׉>/LIi.Q5ҝ^4Qu^>lAzEB捝#On_ѩ/zSTSXzG:P51lթ^TԵ:Wp CQٸFq=1ה~ 5V5;+={9|-r mFWJ6Ǘgq8)I O N栧&sg8-N;rQcrx@T,)z6߰ͿgNU I5RYY6ntzr躶Z)>sͫ;j*cۧ7ø f:.C5Peէ Ks{yv|w@V.*tSo|g;]u갯[X > 6oiF*+F ʣ#X)ѽQѪڻKI7?-d-Ӓ K; @YJ1K%e'ێw6@-8UTBM'7SRI5̨٠FFD҄Df=V JFIFC   %# , #&')*)-0-(0%()(C   ((((((((((((((((((((((((((((((((((((((((((((((((((( "W  !1"AQaq2#BfR$36bru%457CFs&STeDV<!1AQa"q2BR#br3 ?jJ)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRDVC2W{x Gֵn)eRm2at fmt3Rt; CquGI jLX1$Ž''+q14ąKXʕ3ŭ7W5@8Wq1'+864>|gqa=UH\q1\)#Jq{ZJ\G*|JY |;m|tN# FODgYOZ%SS 'ۃH0sǡQvl[ވXo/),RKt2iv:֯P> 1v-;<L[sR"d7B&?z#;\1VeC X"|q`|3q?[-Rw;$}:5/;\@akzjf*o%p [ꢰeK d)1=7RixʀJ ry?ƵRfX҉19I_\ڴew&;mO o\Shu+Q2Zs>qչ1MMZb6--g5M&¦}8ڲNyU5OsL.OФnDL3 5.i[M˹nx G#>Gj$xۭ 4@@k]Q|(Tuv ԁV܃hEuPGDXO6ƸI{±[Xl[/:[c:1u*ΈIiQrI"3v]vcK0A!GUf)<v_7ԚkWRvʩ 'XH8H+kgWxky8^Ρ^$9H r=VL%=q},9%I6*DgJSx>U'5u?X˺NN,!K6mTŀ7OtbsPAE/8Y. GxFzfmO qd('qq𭚞b\cOyji_Cnb蚵)T| ؞˝TP eٙDv]njC7U*Jdt>/KpAuƒyI.pVr^3'kGzUlPsvIWLb"DHQH9$}G\t͕( 篕ri_G~ i|{<-d ƕ%}1R[6T'jMp={y*ɒ$qϷ?,u/qpjҦo$q1uQtZ6QkÊm8Nc:Ԥ3*Hy-V%Gz$ h41ļL>bW!GkX|dt"3Q#OVKӎ=:q%@Awktv݊Yz}s$nKL<%$:#GpSyideN=e1\(W9nn6tÿDWxw8rxid~O>Dh{JΟcThD Nm Pw\ٍ)!K ROST%Hs[pT9G^k"s-=%QQ  hq\h8sX JRBT|ؼY 1zjbI۱'vgC,Gu>KIϥR\/mZ Rؾm)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)[+''.r1[O^tEO9?z+Wu]@iSO^t_DPTu]?9?z+W>_OO^tEO9?z+Wu]@iSO^t_DPTu]?9?z+W>_OO^tEխ%jX_+bO?Ut`z'W\[PSOPT`:B*=9I$L 6' I}G?ܽ& =Y,t\&.WjVۭ@>\c^t=gROm4 :SWYj睱.P#?[vC^ 5!=q_COz'_ԾjpY[HhꇋJnvu( ! .wT4Vu+/D @[TmWsRO?^t=_V4I$ebmzmkZ2;[*eCkIQN\FEiKBR!׏:r>]x{Js4P'J㈈^8iС]J9ڶT(8_?Ʒؽ{J?u:$aq⚺;_ߚr_gt RIiSj+/DҿbWm.WW1wf{JH` V#+?u]?9_z'Wѭ 0gyP<֖>Ye(%ޤR>h|u+W뢻J3Eq_Ywu[jSGZmVa) o N=yMO9_z'Wu]hֆ T2@P]MҶ~T2 n*" rrO+/D󸋮7ycpN^eqDҿҤ p=['u:֠b TarBx+IpQzBy'ڦh=uUC(Z_OO^uPTu]?9?z+W>_OO^tEO9?z+Wu]@iSO^t_DPTu]?9?z+W>_OO^tEO9?z+Wu]@iSO^uZ5oɑ;v>'8>E)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJREk44(%oO@IM/Zԍl|u5l7^D"݀J5u |v'L}-u?,ZHunצnTH]`Zqq.zlv_qK^Ҵ? ^k+Omm *LI'8)pFUVvZHm0m߼!kI s{u\_ !p ['jI<kpjSxf7~(mAj({EקMJ)!3Z_Rв܄č܅pG|]u4Kco'.Ejs<F'Ec5R!dwE2vF*c3Ji,,xcRiF?%CȌZЦøkiB)d$(|'YUwf JuJhA xILN:sΧQ\97_EHYup[B[K>銇^ eL2oRmYJ>,-: [~2#eXNJGrqwwD$}q )ϼSN=j7;ɛ"q&`Ҕ%)J"h ơ)e*qJq Hb&+Oi: $POO`hwgP݀%q?zW= {s ^Pzq^fZuوxjF-a/.:bnfLpksI:fDQ C!0zudŶ R˻2~1Ķ0IX)XG0B>wRj aqAjq!k[ Fp\yV-$C&yke8Űk_{r:fԋn!ͼ`$uΝӧ2xEۛrƥ@υ&KCeJT6gjp|W޻/Q"Xjxw!]) MP.;`ǵo7q-[Nͤ#޷-Af8ÉPm’:ZsE3rB6nSP<!+Kq')["$wZIv]0d}ż׵KAPX?[UZ-1r$ [^i :@ m9/P7jR۪t< ӿ5-MINF2r=+C|"*f_x>6J=_6u x|%1,cp;_YWU3iNsE"f BO: K]I}iq7\ިԺJ=Ы'4+lR㓑Zo h]{ܻ9 qjj'&kyMd0 7z˘ >ٽ |T5:=߶o{Q, %{pAN.o<(AtGFTN|NIΕLjzKK UhܧDžF aаcHGJQnǺޮʬk[{Œ]ܸ'qAX q~j>zS&AaCRc٫3sP#]zGt.R\씥+u%)J"R%)J"R%)J"R%)J"R%)J"R%)J"R%dLJ&RTXuuڒƮз7M`;5osjMɭ;MK{D3\˅R+.tf{c#kjk7K`[J[S <$y'ʱS.4rCM> !*s\s@2 MVaddZduyvBPCAkY2I|Eob~̈Py/2sң]Y"‚k\z|.1uo ]__- ?R}V|jncV{);=##Vb]DKûb(/#hm9O=G>WYI#1M&IDzPriJR%)J"R%)J"R%)J"R%)J"W'S5ЫHlA)=e?MQ7OGz}hj8W<B}oqr|m哋D)ӥn~/$w1Ӯ:u-nǵspn JR(JR(JR(O,RDRE#No$r?F }j;ݞO9)Te62v%fL;")JQ)JQFwyj J8#YYWO+yeŨeD ť* %u#k=aPݦb,hJqֹuu)S5 (!i.mGK <m}k7Hm}R}G5t4 ;%Wɓ wHQ#iA>+K~ZpiĹglv.'$qבXS*/67+Y˗pq xeڄQsE&mBPCY0 vaW[|J2 [-ĽM.[,t9?V.`#ɾ#9?Ufi)1s;8 ¶ߑ:"?#h2$ I<$RPStLI=$H%s:T~Ŗ>)ii;) &,zrdK}"ri!D:5m9qK\ 8*f{?ܵMͺBRր (k+R_̏hʐ%m668$n}Կ˥ٷCdT^W!lۥ- 8ZJP k4{Q3:UhƹO_v˩(()䡒|h$ǝ?{#n[c 4hq)R-:-u[iY*bOp*i^S䚚i lip};2b̦^RPv6~hőZ.d!E} ܕ¸*ݔ)'>XAHҖR$O?f6ۼg8V#[_J֬>0vαg?CSJʻjV3^D $}ʁ1GГ&XŎRS,R@8(k9eZJiNkVpqPܖHI J,#8|N?W9/jd-Ai]mo8iZ7.$-IP8`5o?Sq'ykWo:Zs^9 q~{JքUNyϾ+Rݥ3JsU$)JP(JRbm7D!8jc>gkvTa݈ZТIq|_zNҖ9wt09%$y7n;QooJ28h-7QVF]ml1puw ak FV*%G>^;RS}Oh~TgRHJRdIO\u)9$;()J%)J"R%)J"R%)J"R%)J"R%)J"R%)J"R%)J"R%)J"R%)J"R%)J"R%)J"R%)J"R%)J"R%)J"R%)J"R%)J"R-ލZzO-%㚆G+$}jm5VًB5n+W"0qN?"wV$ڡVJ9 jdt9/TޗvKV KJCrxnz=kضҹqNjs\L^VTpMkKH d_rVɗ̾$2TǽytZm!d ǭ]ͭ{[Gԛaa1nARF|KI9%1YXd$W,s\܄vݹyVUQJ (G/z4 d ms~S 9]Hy<Vm")+td7fvܕP/S^n+V&[jF‚x>˝' qpgԁk Dr%AO6+#5]&DU*͌ժ;iʂ xzVh -B3$o_ZcK ah$z>ޡ!ɰ#Oïz2@VќZ>ܲ\a B [ݻ'pyA=Vͥ/g)9.9M٫t+shVk[d}8-*iakDXolʄRi~d(JR(JR(JR(JTOt[.)#8[#8"٧g3nz-e)Sx<X<rSL4n*E-ax<}Fe4i!)H8*9r5gMT]bӟt_fz5*g[2:շV␑8ϯݗYTgHL? -sb7|BLc#|ݹ{EN%]YtEԆsT1+:̦ڎm?EVӜ>?&髣Kc:z: cR=y Z+jfK e)Z NȈBF8R@ 㴝 +G7W"$Aݯnp@jO?T\ ź[߃qa2b>_E)r*•4+BJ .Hkc$Ĵ"+)Ws\XhA)s- b4$'HV:'ls*bmF\-AJڂjFښ QpN9l% '6m:DkO5oS1_QBh'ƒ >) Yl3z>WVWoCRpuI`GQ_jܻ:K=ٵLș1ԶMҥGʐֳ;p. iDZ[$|,Gu66҂τnc ֦ R-j]F:f%Cȩ!@|]>`!3n*I 7f/iEgwpeKx!iR=@y>L[q}: pVz2E[ sYu8c3V՗2I"G]0.gZ=H<۬V IZD)'׺^j[fn6S}܄Ha O<?bwB-ڇN^n [1_fS >Y㞜jH˽pݪyA䌭QϭtNKF3lУ`7b;)h<$'O$űZ!S6K d\ej>j80SB<ʈjU)X 9>PQǑJZIcʼ)SI ϭU!f!Vo!H|HcӃYIR i["n»$3jA"2aE.Gṁ _i Wn9 ɴ6r|nOXE60MUwYJUAiݏCКᬑ)>p2隴]Ertȍ!;i}#@#_5vRZ_VG)8vE^#m<{ qŹ[ރ=8 ArsR /iSNҴ4!?lEBA=EЁ R%)J"R%)J"R%)J"R%)J"R%)J"R%)J"R%)J"R%)J"R!_L ;_mX.2b,䴀T2}UgQdI$&RK!cie [8JR2IinwjZ泄h˃Ms~ dzj eEj3E{MO:_Vdr?EF\x+sSp*(`CvU4ֈaM>Ccaݞ@<);jcO?,B;PPm9;yT3" H EG%; *I nC<!$:vǥrUC}A"JXQI t}?tV7bOOrV~[^VXt4K^c?hP2bHIaJSXuz].$V p1HCDZ235e1 4<% q<\>ŸZA 3p ˥m}8+fIu5Ggb˭ΠX~"WHjQSBP8=7( k% (!QS@xr<Uק,3>'}Kaie)[Ni)# [__F+WVEb7}Ĩ" %)JBR%)J"R%)J"RzbS.BElIJJ]:ׅ* )!'kwe.CP\_0;zg \..WlXv){(I' rI$ZYó l[j.4Ғ)Exր-*JT2GQ_Uj}ŶmMnE-1(֦k[UHrPAb6%k|$ ?q{uͧha?N![guZZ)*JUIʼSkJ!A*H_HynUk5bk6PSڂH.c>pCFD1nl4ürƮ.9,ovZN9Ads_=0òC1[(BrIUsbH%LMgu*O]wԻJk+ͳcw8aja/;jHus#ڬ^gB}uV-)ϸc';$NDLd&b".r|)J)JQ)JQ)J˵\ +zSbOU'MdiCtjʔ65 WӚrM#[ !H.8S޹g#`;Lt0aOZp} $ PF:¨JJPRԎVX ))T m'Ϊ_/T/ϡU%jm[pνl8 8*H`* ~sQ---! (@*VNԂk]W2]n9ZB-ȴ/>+qjZrJIeF9DN$ǗO֯ 7qiVu&җYR <9'eI%(A qRʸ)WВ@J8[q|pF YFDW/UA=9[lmUrhԖ"aȃqe!GvK(x 90E|^Gw2ɴ>qI׎ I)㓁*]E@z8XA5 1DJSU"Ă9I{(>#uhJR(JR(JR(JR(JR(JR(JR(JR(jWH6<6`η Ogaž#QLq-n.:T\ܷX aPBAIdsI%L#_i[P!qBc-d9bõ;ԙgC[?1Ïݰ]=V>[ ] E*[<8} Woƴ$Isx6pǙI8ϝqƃhy@0fٵuVaUP\ːaAS5~ ǁ1&~R7ev{ɶ:p]LN@ ˢZQw' =$cuDN hmpƐ䖢î$%JWڪuzJaऴ)]ʩJR(JR(JUq.S,6PZ 8G= [KΡxY?jiL8%*VrEU""))nsy'ךϜq*E?Bh@JG~u^і-8jܮW*;H)R(p3QvƶI s`1ǸA:O'E"z*JR(JR(JU̻"ClmnT9$*"hKܘn2VGR|$+;qC7b3@?UA3#rKnOI'Q_AJrFzR*A)5RۄT+ ^(n*^FzqU+$,'jJ^ਏ V$%*Q*!^qG9I'U-ROa^o^2|XJEXu+<զ 9 P$*vo Ġt歅@ZrAw)aĀ2}r=蠜}y[9c 8HAZh85JV@qpI5Cbw$ROĚ$y;iΊNpA=zN`q޴>|g^:+}GB9?µn^]2)PSjZOAPIo/< y>q{ V(JR(JR(JR(JR(JR(JR(JR(]OA9j]]4+DQs;;;!Jz~93%QސH+Q SHUdLA} = _۔b] C!p:d.ksg-r%b#;Jv1wc,v;fCKi) IJ}=+[[BqG JFI-No'>vuhz4 ˗R7ᎄN9l% ʪ^޻iϿqv5Դ C!e+**ʡ"MfWSm1kk`)Qop|#wO^4КN=mWh7NP"ξxˋ!ȐwRÇuH!*>ǡSSuP.>RH)Y7'AN3VkpZU"bWS G2{h!7+u9% ƹٯ%7f49w* Q>e4RڋI;J>:vQSY5lzG\J[j[$c]"8gL.AR}@snP^&%3.4 RKG{3  jԆꚒӍ:q%$} gԴcF.%[)^R%)J"R%)J"R.{@vْY6Ð)Iy v{q;[ xYHQ(_C[,BJW6$2lwcۨ(PZ~Lk>$1jA^N+?UQZ8?JKg"WzQAJ1UGH$ !J4ְk|MҢND0r ǘ9G#### Ry"/x7[.Mw2Sj䡟 VkA_)ҷLLCX^UBFC#)%)J"R%)J"R%)J"R%)J"R%)J"R%)J"R%)J"R%up_?ɺrҀT! gȪYuI52hۃ-H'ڶ6`szLklŽO>_JҰ9]l BڢG4fY X 9bõKԙwõ[X>e1rJNy?_3^eM"0't~QhpᒥM{QlÝi!e緦R]t vx]Czi_~^e! pJ @mY'P]C}o܍ldBNH>.yըc뮡,@yJ.;2d$8:z-cG6yQ$&M˲gl0}l "JXQIç ؝OضԦ@3Tˇ~ŵnE,"%)o9 qc~-ʌo66NPIxum5F>*t-)JViJR(JR(WuPZTZ^vN8>Ƭ҄J. r${t@Łʏ2Nky=EazP~k 1AQ( Ppvs9#jiԥM47NȆނ皒f^J uS҂H.dB= |bh$E湎>mk!tR.DF;eH){Pj[3l[Lȍzalx =T«06IB%zSY^m-ޘ"Ɠ݅)թ sݽͲoLMt'eWQm8)iN}+$ A=IYt]v2>R )9Cׯ5\ȎFe7ACiRRI$ԓ]__dP#ʮF{jJR(JRջ.KK@b.,Eގ,c~Sܦ]tuPNҨl@܏Rp8U-djZPyuꑑk R. 'fwW%jBOs*%N1U*>]<⒋XUӸ*ZUTJ&*'TO%<׈ݟWZ`%$VlVIFUQ28B7xm0W0qM*@=^RFSɫAĩ= ?*ҽSGJ>U% )^y4+52SyarT QNk, p9@EPJ*S(ox7j{\<) ROA]yb:ֶB)9 ǖO{}wW|{ԕ) qAGB<˵O RJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRDރѶ뭒㨵Eu `} $tÖ'u-PvgtpE+\t, θs)y @3T+IS,],뀿Ļ (lTArpP{8v=KE\ !3S q1S t>D݀ywVպI1Ojd@D Liˀ{9%T8Ŏx'Zh-K?'4w;pǙP"Wj~v ͻĠHPN{/sN+5O"2ʗ݆!iR=ϵF-SŞER./ZdH$Z;Si9SLq6=wqj ۩og7K,v'% ڐVsz W0 s7 9c sʝ%qvwqcM\efSWx#L*_|go U͕Fۍ uǟ=+AOs5L2}*y9% ƹٯe7fޠ()J>GKԺJu0zLzʤض" DF[o''~kGk{<$7î$$siWAjε)i4Ftϙm\]ޥw}ܡ]SεfT7{Ta=nTyO.x JRD)DJRD)DJRDTXl)եVNTx՚P]ӯixG"%pβuV~{0լy3ᤅZl4H[\jozْY )TC(́\TOg-Qm97$؊ Tv@;MY VM0nH0"AZCn7,X%sm\yJhRI8STjˣ-V Zpc;RRN󫚧R۬BǴVf3 KP>}fꋔ[ݺocGm. xO&]&q0CکX8Jj|JϵR>X# sa[$ҳKm2$.T!X@^MR²@ˊWROҴ%BQآ3D>}- \ q8>Y䞴ˌSh{Rf,(U |8KJ cΊe\ Wmxd/2TAOz^N4* e\*v'COYJ\w -d)eF[H=0rG_c5Ă" Z#g  pGN]C JR)JQ)JQ)JQ)JQ)JQ)JQ=j8rgHDxQݐm G*xuqlf&%X qgs:%I 0$ʐ%rY ;3!8RIM-(RYRMvKsRV'5'ޞE\kI#VFXl.Z+Wi2-mmy+ O*7y[T!E+*F+9GvD m&)wRt:/^OiNU,[y`BڂiܳOʡ^ZA-hb=y)W U"Hn+rap)$%G=-mӒXPŠHK'k¶z}̵- 2?Jn*SYZBِxD]KV7$8^j}#Hq%JK(*Bp '*'׵ٖ4)n- (mmV? '熞PiKʝWt隠Nɯ;>]oo@ J2 yHCjt\D1l7CL}EN!!XyW}J@Af}O|p[j\19D2 LթތꚐu=PJT>)o^s+al7ǖ?4S['/65)^R%)J"R%)J"RzbS.CkJKՀyIТPTf|)ѻ-8 d"E9QII7 .]k1TJ 眞~(@as A$H2w_:- @IRH hր!C##6RPrj$]S<~Y-RqI$'#kmZIrؔw BH4ŝ&DZTw_5hRRH~UZP) WBGZGT{SgUt%"2v{F˻Y;OmȆ-͆Dal08ܟSykKDߑka8vCf;ku՜%NI> lId݌~nIkaMeyln4-L%v-I㟧ήv{UxO*8॥9`2RA'ĉ|0hDESoW)^R%)J"R%eZmCB@XJF}ZQ12,yQǐ]BF#^x  9yLpGOQXMrxOJŐu=2kaJi =8P%9GNjFT欭Gc:YH4c#9 ԣGWeX+(('%DV*2HuԷ7('5[d%8g]> v?*ٶUnP͈G2 ԣnBSj)*۔B%`pps_+NOF*zls$沕+իhW^G9?:.S*%J>YI"NGWZEp,~갎@AT**YSAtWP @䌌V=ݣOMP~;J xQ*8$uP~.郣$Ou(ؤ{(!Aϒc,W䇜y(n-EJZ$n)JQ)JQ)JQ)JQ)JQ)JQ)JQ J^idž=)8SДr>*)3S-[%Qj,5:@1I?̎}[6Ķ։6pvT?Q=`j.# 5Lv윞> ]vz:Hvs&<8q Ԝ~gֱVaptEK`pPGϡ^X֖;".o) 8m `L6.l*jݼCr5#kr 8є9 p矝YE]v b.d[PH)ۜ:@ߴ̢vH1BӀB~DZcSBr10H˅o##f [<-?a>cYk [pb Ҙa1ZFr  q; ks;A֢]7?P$,!c$ dz3eUAs^KoD(m*[Jʐ֦6$Flүv6[n *C̬gok7k4cZU/56:0F|O?\ghOj;EO".{*^Ķޕ)*iJZ-<4_mNI)9]pMb><͓Wlw-ejwx79su~%L}󁔐zMH>̘ĦfLjT%IzqQyviv\Tiiq- <]4{pO29{U2,w^/4L@/nzICDaLfx suny̑xe%)$`qEO5emIE;ܓ;L3CZnm*?V:7 LtCBg )J)JQ)JQ\Xl)Մ$qʏ{(iЅҬ#pA߼zڝ{eͺ?t_CUyo .E0P˧ (Y'vӥV^K6Xc؛armgtw7Z *KOG}u9 N|Cׯ5\ɎEaQ6PT+IQ=I5wΥEvm+Kir\zG JW5ڭƘrmGZzIȁ=zI|\18(m+UJRD)DJRD)D]kK&l+tLӜ!'j3ŽyrA]ݛ sfx6gi8h yn<_IskEd}'βk $ߧ*TgZ<_ExocJYKA-Bu& ӑV $8n\ѷRQU.+95K#kР7+OCHgrq׏WI㊩t<J0Z8fuPQ VqTl|I_a%g;#>p(5U/Hߴ|7 qm*t@c*B9~m,n*)ZB.g0eZR֡wqQRPH1U="BJTRH3ҢQg!cgT'9B=j*BjUJ<sT+Ѕ մJJpHnsIE:eDgHǐ󭕂ԫO44Q[F֖ZQA @gp^i[x7:R7T4W#VkuKCQ +PJHz5h^gK}ДnٹEXqtafV)JBR%)J"R%)J"R%)J"R%)J"R%)J"R%)J"R%)J"WSNZfM!: =7\?5NHR2~޹e_L bw< ~YAE z9zOCklh<ؗuBFHAlY?{˚}) Eiܥ|Eq_XҝÌd<]ِu BRBJPQRk mS&E{폝wAtZ4ޤeMԨ|c.oSg H򪗨wuC*`ݼ;9ME}E(-'ƒ$2G%HYJJʼo$2An{EY7q[n iDZX a[+m<׭M4&ӧOi[U; S榥!Cȩ *d7Xs:c麃()$rg(fW|KRYslu6=n-A\O(I=?m V~5o=?,Rvq|&$SV7'AN3VkpZU"bWS G2{h!7+u9% ƹٯ%7f49w* Q>ex6x隷+ϬG+c?URWzR壴χpΙm\PAR}@snP^'[%3*4 RKG{3  jԆꚒӍ:q%$} gԴcF.%[)^R%)J"R%)J"R.'S['H܃iOy-ո#zRB]@urmJ; k[)p6_;!%I ) WxU'9zs3?1p7`ݖ̇Iŝo5$-rԤeI&j K.qoV۱bNi/(1?_>ڛ dWmiJTC ֳo nDb7ߵE`cW-3nD! ޯN<P oNUҧ[ pqAkiiKIJUБ~UZ) WBG_K8KAM?T/%uNt[j.f+Jy[[ e|NgmpU=8--jgtq}U2ioKhʁy`yWw5fmi5 em<_Sh1=ev׌)cڶ¼VyP4+: Hx}V}ԩ%G֠PG#νBʲΫY+ĨyV… 2t W9ϙW^5L0y.+<nBŖI=6+ҽ`ۢGcUZmzI!ftRxgrWy?ZXec΁ 8PPG** !Tq*D'ךAaJxp}*vִ89' jT8m/]㶰 AAYcldRR[FJ\FY !QMdu;;[Labv3( 6@U؃ *mJUh$h^P4Y}NCwb؂-?^ZU%(%* YYӌ2'+K{\Mւ;%B+%E#)j=<}w I*)JQ)JQ)JQ)JQ)JQ)JQ)JQKޯ[i@*e_ʎ3U,:礛+PD)SДtpMmVAH0T5z1==2q}o,6 wM6<|n + yGB؅!%j 6rw,Ai׫Cv5NILnt:AsέE"Ưj3VĸTˍ$8:Sk.``n9 'BnoEvWld(maE'c ؍OضY,oh9V~[^\[X6\-LFN3kkPQ-p8s$( jm{L@ 躾 Z"&D.dRZNrs}izHRߌ% q<\>涞үv6Ǜn xi抐+$+$b^=ֶWhm梴- sNFO=ldS+}Mj ] Xuoܖc1C%xN =H& 6 -v;g(H u_@[l9֡+oxv{P )IS8ٷ=kI5[e^ne3$ܤ>OEs[MQDX>*^ZZRҔ)JQ)JQCu%2묡6Xp}Y]{7#k}n{y=yO;r:$vn.a+qY9#« yݤ`hyo nQn] 5R#!Oj p +[] |bl$E&5u7jCk\) ۢC$Cݻ-h8G^1MUڅR_be)̑?v7 =]a H=* “!Meyz`DOvVqүv6=17П}]DU@॥9`2RA'hmgtw'aFH9Iz}MdDw.3 VJʕ$ |E&gNFTnaDiJW(JR(JRл6\5)ݓXR| PϒR'kOܜYUPr:2"B//QDw>C`HYj7 >?Ke[n$(8aw (\PIǟZjS BhnKz>Kqo0ؑUA>*` 5ݴ+>R T*9I+oC$`9]y :elJ??TSE*Sj劋+[|Uxqow36OUhm/P< : q=U4y(i>,Jh_* WOzHn^łB=^\pѵu#6`z)S aigOw&B;>խk Zd|'=9kXNgcE֣n ]@ kg `@|i[.%R\d+J< P99RzzȔ(JR(JR(JR(JR(JR(JR(JR(S=-Xi{RT܈]a gˀj]/~KSi%w%Ԯ{+\ze)\;/M).O,!oQ{l#n :c]'+J5-6Ķڋ g"A>kJ[%k-k[J'jZ؉9?eCYuV1@&ǜORMzJR(JR(JR(JR(JRsֳZNeFq\RN3.+>{Ը-d$/;N?ϭHר' ZcnR'|+j~E^VC1?A&$50چz[g+xJFJִ^9 ޅGT`s۴JR)JQ)JQ)JQ)JQw> ݍ6R:$ =n>__ΰQgڗ+O.J_ΰSRDrMyJQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)R+@O VL&R}>ʆ=EmZNsnJO8ZAѳiIyŲM@H')R"&3EMU^?*VߤcFRi[F-W@\qPђ\GBUo|bF ZE!uVDYH|BVq$Hm -%h06Bsސ ְIŮ7{,kkqc0+yK%= gƑHʽrb5QDiPN) hu4D˅gOUHuks-!/珩MƉZRuSKm @{y#Ci^Z² 51! KKj>+oei2=Q[SNvA?eZTc\ބRt-돽ٲdH1֘Y vVev:૳QMZi[Zc4.ӇK3}ktgϹz30={ψU~YLZV)"@cu=xHnvCu3cFh8kaFN?b#O]z vZ VCڬ"IUq^LzjI^Tף+Q&vikSN:2%CȭvYwU`dxUN3`$&}/׸{ԷQج@LV9JB\gxOҵ3xn\{̰*ZxRCF)<1 ;s-*mjo8Ϯ>uowktEKpBI=8Z|M/iqt gRp194-JJ3ݐy띙 9q-ϥRGmc?ΖWlc?Z>t$-KʼX.F2>i ۝浩UڭfQnQ*yVzZݩ7#wyi\{GI% "s'ATsQ J%J~L>O7w|}qҽfOI~uǿJ ySf3ϧqJ˃o=kDVVm q@y%#,I qPV>>UgTkra]@ZVO 2p}~յj"ulZN{r'2A dnX$ PU'?"#/VQɀi;~<`zjR lh848 R54LyoWg"-ΪBBp}9*7ڷ n{m)}Ot_:R7-fl8Puv\TGq5iny5醇=˅t:Mpox4ݎڛ)R$zvvsvawYNGO*m&ov-JE#l_&ҷ-$ڳ~Ke2[9H-^ v9* !pTA,kߔ_Q1o}5Zj2(%*RgĻXb5nzԦh.\G}9cP} kE2OQSq ?i ,;u_hC^"8C.6%^;R̊oL?4Z!{܂f!{Y3U*$.nRvf6qt~͜2_LKw~SMZd%X"}N5Lހ\C2[mGĠr@񨫬JVѿ27Q*9kOFRl>~Z<>:"OGn5.\Z'?H1y]l+;Vdu[BaN) y4q((e$::fU xZS*\7t9}=,H fs<)VOZϚӦ7_Y2{x8N>Umm8[Z WEa֚m)$*:s?w\gO꺟h4"\5Nu)/uʖxδ'PťmJ*PAXp AsL Y ^ 2$b zY]:hx2$ ǥC7_]>;2FKhyFRR/xw+|S:# =^|H8ez֭iKD.MFD] aێw zfTbKml 8>8j4IU?iuNW'ןuy2\-(h9 #LŗQTp*䷿ }F3\zRjiF<WSJڬ y M^߽ɾc]mYiĂ:v0<[.K bwOwhnRF8?¸k-t[5]+\]1jE۠rDz["$JeHmlJxA:Oqӯw[3wیX#fLHy'ÜǷ_qhpB+ި9TƟ8?ov;z]z+7K\ˎ)[7uJBI$zz/ q2,QϿZ͵\ڦ&a)ZW]2|eLY GCZwE[GVVl×5d.Kӿ…B y3t%ͻuRm>47{yLȍyAǟo\W! A ЇIY8sjQKIu׭HBZo-[f[[K !HOsWwK47 d_ӿ uhJ[%#yҹZ͝ -*ue%# zrzV LGCrN:J+ppsIBI]vT}ݻx_71,>{p0G<ٯVVیa˒2VRsS#θQڞTxUk ij FNEX+ݛ.Juwwxt0BCMH'dq}1lځ,]|<%DA㧝sL#"~>Mrt6&9GsVg^APAN+=ƭNْ;|sjaHVܤI8rrrp09^n8#!!: qaߒhk@t1jIpZ6u !G@8]n\&]IPֶ;%?.+жoqk66U;שyy(iJmJ  Vk@@CO)ߢ(7_'"S-[c+/wmNtoT8=N_Ukk̑aSIV[]+QJ]Y H'nH$*:B"ci ?NѐE8%8\ KyQI+8'mpUbŽ!neʆyj lɟ* >q YťdƴHLM[{;qבs6A>2*IZumc '0ŽR <<*yR$|R JҫIqzЭEq$yݚޛisGĴ y[䶕hjfNQ*.(p\MizJe[$N=TI5=T K|[~j>x6${sA\umW @|kn2u Dxͷ&SKWb6)yzTa^jȺŖs.+ey1\֊.1.1""P*o<5^`BC GCJZlQ 灓皏ӘԵĴ0Hr'Mi7hξ%H}Jn}HkX֟YLvfLd6 F2sNFv'rwAG<\\bbG%H(yۧC\ik0$ۢjXHˉK%j]q!!-+iPR@ׁZ8zR$fBZ^Q̚#>0sJ%I@c#e[}yFh]q|)HcA| ]Lm:2Ӭ0%+ O< }kStXm{q=Im#DVet^0'nЏݻH5up#čB[BrzOֶCR*uz-ibj=XfdwȏO~b}Yr1S,w*-vF^F6 'G%Ȱ´oEZ֌'?JΰjٶkcYf+XwkX N|yKuFdB%V*l։1xlCqarC y4lKU[1ot=((+X8$pH9k\[%<9jyͨa'#?:Ùn셥B]JOzɺmQ#{yy-Y7I$xдmF#GA 4s2ā:4c6=;h׷DXeڂw%Xyy}WvtÌ~TJӽvgֵ\2 t=A[k5!0dt##v,up-e+3 ٳ푮<¥GhaK@xE8ԉ+:b OR$w-=6ؖ=BR/ZH˲[.qr}ٞsqNsZ9\)(b w$!Hqc@jNmK1v8RcyY<%YϥrSj)-m ^;pڪ4XbzDx ilSs%Lv&[wjFWګ,Fmm=N;>_Pztf!! '%N}G{W\3ɿrQKuqy[D^ivkڝL7( V1gW+JTARƺ]-#8F~%[uVBT|kAw3M+bQ>ַӫIjcy] Ue\zX}. u:΋cERxvq ijvۄyO1Rkvva8ޠW̫YFC )}{m|ۏ ICks}9oNS738b_?JR>c3.YYYv-Uɧ20R@Oj߲kQ{Yq.*]$c:PXXOҷ;-oaנ*jiV,uGV}h=RKv;tG#6W$4q;*VF@*ʴ݆u Ob6IPV?OFm:}6*{aec?jVN7qmԶNu;8$`c<ڝn۟H%GjB7m͈Įm{5rҢbF w!XN<_"]usn)Q򖌤AVm ,6G4 G>sQx35qPNG" Br t\`@37_EKgBҲ<sxҮ۴=Y"nŮR[ T<"#7\]%uK.{N~R&F 2Rd)֒sIְmamy`7۾ױ?Bc*Қ6r"$JK[);rT8 >-zlܢIydvqz>uk˔ІȈSm, Ü:f3nDIwwTW)$zTGWLh'*$A6.\ˆ[p%)<>zUtȽL_յq C KDqQ>ۙ}9/UG5'V\G!m5!ǕPx[6˭iqU &F+;ʽ1WQEr%!C|KkrTeWœNsҠ̋;,#{-%R0+ P` oNkJZp7,k u8aĔW`PԴ |Zh^~[&+ke{CiBc$/JpݳOY,q+Sd I8,vmK(P_-IH' Ž.€75KHJTU@ rJ8NOyn&t,'k_Ymdž\n?wk+-kA2c V 4;s6?Y0kE[\w`o  ێx}kˤ-2-Q \]) w J9u+]G\nq ?6qq 0ԶT!jd?>|j퐨kcntȷe'iE^͕2zKJJk54(SĢVv*ϭؽu@C{$sh'7uej)g]44՛TikX̂&{( MI.sb[ q|8C> 0͗j7{ԴmY!C>c5nv<KIjCJk9 PWi-Vm%i!(m#ЯMl^߽FM>P`"=o=,+^R%)J"R%)J"R%)J"R%)J"R%)J"R%)J"R%)J";C1VHgHE1ֿJmEmy2 (&W TpǮa@_l(ۄ51ey齭9#=99jtxr܇irUZqT'[V^{ ⱬ 'F;&` %ŝ<:E #Y`/G sPKΛZ.\`ǿƟKEWvZ4v~y,qvN"!EIo;A?ۭyt7[M9: f[Y'mܛê}[sY>%y ]]bk[Lˈ|g*R<k띲̓۟'l$;}W0[չoyKIT$Mm p_}L4-JZF0zV"*NrsJ#)حg7fSص$Mq s>%YmI7;Զ-h6sSX s(Ӝ4Wğ@9$X04fĨ娮>d/RcϿ@c :` z=V5ػŸm=+B>Yzo6dL*֟#: aFz:Ĵ+v[iլnFFS[Pn1oRt`:m{N}X*.[$nI;JH Swr@(LkW#J ($dbզEncbW%$'#8j@-$hL\CUdIhI>"V_g΢nE.0?rHo7gF>ui:.6ms[R)( ~SΥGV֣Jh13J8 OXlWJcJiM/shSw큟e3; mG_-ܻۊ^OKLS7,ykS4g[扐&o ( @Shw+efqYjRʷ6~r==e5#)^r: o#hρ[ѭ\kI1 OԺH,5ͳfuEKۍx[(޵gIl]UM`.4?Yqt͍EHAnI.>}yխ@ n,_~Z@( {SSCQT\ lty5;D`E_<.zJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJRD)DJ˶\$[%*m<QX A _55ڞTd! z$zҊRk4@Ue6X ')JUҔ)JQX(JR(JRzӯZRJR(JR(JR(JR(JR(JR(JR(JR(JR(JR(JR(JR(JR(JR(JR(JR(JR(JR(JR(J˵"+8ܝ[0RNT<9te.Si0ÒteJ#8zڪTW,IMO#NŖR;K R=qcKMnyI)I'o>Y M0kdg${$HI6.t[L!S}keV_g:E|M}R)P|<|GJi1yu۟d(1Scl Ya4uwUŒDA>OHk/Siq_,a}a q` VTSx`c#AQzWEƕ\݇\"lw!*$ #Lv,FS{sޗA( 'QK[BARZBҷGX7TI=J2 R1ٻ7o:L  T% Q'Rҕ5)U.ɎJI0+Qe~cW7mPB `zt Skj9P-tFAQLIMM"呋<l:q|aHonZ㥢T  E"`8bs˯AZjy{VNY޾ݙz#;},:qSi=NIƂI4K^ ]].-yO' κ潙,J]c)7cP{Ty$|^-}aiX5"$Fێ8ӱ{()H`ֱKM89e;J]"lR]֞ٞz)m!LF}kc/WV5V ʭdA˻T@3CI?b7MrjTZج6EEMPx[]98|DOOOz"_,vw[ʽ>>S|6MmtghS]@\uC[VrQM쟅)W+vӜys|JɪzginnK26pPxnju81 SN')]*)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQjv=bY/c`$j4[((@$Jq|!#%Ja@q\Ӫ92=T [Uo fMnMeI)-%DӚ덆gtGkOȓq;|n#d>ƾ|y炰X !]wSjhmvJe?: 8’pHPN=VN^f5o}nc%PIF䓓+ƎRfutCi*'*u|? g&g̏UfWSjզ4%LLx7tnDnp=P=@^yꙖ6+j)l VKen!$ {bM*8Z缘=1 }[|Ǿk}FSl[VnofA[„x:mq:n>yTZNc=Ŗ*d.JC #jK]ۡK.[wS x)|8u%g-wKxMHekSEjG>MHR֣#$-BRpRG ֿS4>uŻddݚ.@B{ j+l<9zjA`boƗLNX5oeY%GDp8_e - IICV!ͮ0>!]LQ{LrǛe"$=: m=A *ȷܵMŹѮ7/R㨤ǑN8UBJ(<)a**<5Jo߲DC:Uv6hCgH_vtr@̅Ų2dNRC\IRTPpAתmi^$t9x и4U^6`Z"%DIOz2q]ح{tơ6V$9>\Pu!IYJBQ31*OgH]?Pޑ yiKn,Mn#Ah +ܓ#=kkKkGkXЮY.Nj"8ˑ*XmIt(N玕QuKk-a (Uֳ©.%qC_H^oVu\_mry<;hKWttAP}uu:r%;M7Xd<˜K!A%$V1_6V\t-rcڝh,zG5Ԁk ͽ.T+vm[bcj_iq SÏg2.4K۲&/cG~y |"k,p![NF2##Ss;"Fmo!A7GduҤzjL?t Cݛ]\BPvtP=⏡ - (ZT^`֕t,;q=}} €a}6&,[sW!nŬ̄}mTAl7ffǑ)ݭcP+U4Ľum8_,_968$GdO̷8~+l+w38;6Bjmש&P[nW:.s(wHaNfTO *8>+.U2%+pl>vlگ_-OҜc޺ptٰN2m1 Į֭%:pH!W(Jg83DNMhW% \i)W)/l[TǏ8+˱-q=R5GSe[M!M H3%;*su0;^oUrrJь╻Wcwm+!rPK-\iVA(@꾄>G:!JJ=H SVR6GK}OWSVbq2K 9kP5k-pU?֤)a (OPHX\i^)I* %#yF@8D{88yDJUN6) P/0#?%gc-!PHQcRq mjC)ZN#Vx݊r-# T4dRq mjC)ZN#J]u+SM-ilnYJI=O(JWaeմե$ 'j)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQe.r>̎5;Mzg@y3r+ zq}+wzԳo%RYL{CJf9m$(JwsVWJL>VBm 7Ri[t),75$8%KAoJN?Ũn6iS0Ki4҇9}pgp-XgWxB@rId=_XGjl c6v%!\:yj-51Xۃ*B’RqA5Iڅ\[/""P ܴf~ f$ڒz5?jNƶCjD*RG)r UfZ\sAȑsc]VM\k;0fFEND4PT8umJW-&dF8 JT#*VB;YdȖ۲"K\cz%$Zн'HHeN0[5E=c>x$etuF1I ѐ@N03]nm?>[dCl\6[IJ v=WZfLjbJ`pJl%A#gˤkڑ-Hۢ*CD'9[WZ"#ă-楮 o.3Zܧm,*Bdq->i5 $oh@9 b.lԳg =zrnGpy؈{>t!!R;:ұCBxv$ Z\R)\zhI 8ˋ ";(Vs드ҮYOZtVi&2y*ViBHt%1H@ mYi.mRRWAovcp.("ATEAWxBpU¼CelylLqyk׵*-t8m6JIQq'[WVʬ,DLXF C\H+rq_@YibYȍ ~L{lAYeN/!\OPޞnL"SlBF#+ba->4ݎn%H<zjWhldQj@]V.-.r nE}( OZ%ܵuTɾP"|bBC mxRT(QL#_jѨݘfѕ {16[ zzW;NGjDb hq ^wfy 5^&wd #Y͵2W;I/)rSgUePԻjKi9{I-d#ֹ,/66 n$HGBWfO<׳{Q}ܡn1d _ao ytZ~+#`Jm+s3>1|MUg_\n-JaYKϻY*hf58#[ڋ;vO$=zUi/z~(:r59gßpk7,\bl't r~QJntL-\]H %a)9 Hdh s$d\(D-+O 3_:ۮ6.[Sp<۩<`ƺSjM:Sv< _r+: IQkt)dȂI"6edvb._jI*NH*Ǚ5/VmcŲE(EG+v2@SwgCIjKt)C#*`z#|}ѭvХ&kl[S'qWM]+ݩ5CAMN-;I&ˆE[ ,pD8*ugbP5hZ OtʮZ~Ə!1u=Z@Wz zmd㌊{Sό!O*,mSVVI$jA}jv2Gw h<=+KpշnwuR چБR<-K:C?9x<txjTKKs|12[Q6-`@J×}edŴJ 2cgxO}cKAit\Mɒͱt.j-nMP1$BKjX#Ѕ!$jNq]Ikxh[}Ш4P_xVJ$8Ϋעyop+U)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ)JQ٠Fc{axM̛^·JFIFC   %# , #&')*)-0-(0%()(C   ((((((((((((((((((((((((((((((((((((((((((((((((((( " O !1AQa"q2f#BR$%356Fb4CSr&DVce9!1AQa"q2BRb#3 ?"!!D@DDD@DDD@DDD@DD?V":PV?Ϊan8OsP˯ 4r7N 2V$qgoQڎ-ZwnӍ+aK͍s?iGQSK~C&=gz6Ģ4{vUuQ a6]s3b'6Grk]*LT@pI xj4[ikOmn/xt4ar(}~3&M 2*mesSN7VoK .#e<=ck{Hpzӝ.cZw(麥\zO^2OGSQ2= l2>@3Ņhүt6:iqo-#q/2 ;u3=M%H4}2N]YSjWGJ_kh\9Qh4{<[,KP.rtG R*r2m.ƚBܼrOLۓ9|*npJ-[Dht kIx\xzVvMG^,~J\""" """ """ """ """ """ zOt?twßxS޲~4 Oؽ_ƞ~4Oؽ_ƞ~4Oؽ_ƞ~4Oؽ_ƞ~4Oؽ_ƞ~4Oؽ_ƞ~4Oؽ_ƞ~4Oؽ_ƞ~4Oؽ_ƞ~4Oؽ_ƞ~4Oؽ_ƞ~4I.Z{`qx UO<skv/E~U*jm>M>Eڀrj~/P+EnwLrr3E~W񧬟z+RqVN5zekȃ$ mE,J7?Źj.4lf3 VY_bW>Y_bWi̱pMSjmRkxZ\7x\u$6zxWrVVWƇz$tx1>-MM쫷^rQ>13 ےҳt^>_ƾzz(!*SͩjY[++vPyʊ~x;<'`<NfOY;+U[+ŵYMWy_5Umgse%ۑ\mYmе27?[Gi@?Z+=?'o3E}Yヴ<_ÏB+̒9盎T5ls\sg7z+=dg?NئWOK̇:b{b C8zcczzbOz_ƨ럌gNYdDK-OL|;._- 퓶c[#6`{suQ6Z*ZYkO+Ce 8%xܵ.:B'򦁚a!Ó =:RwtMЎ=AŎ-vW5vz_Z QzB" :Mm憚N =$`߻3'~}|)jh@Dy*ZJ5]+l墨EQ1&v`oiMZXM*ꪆK08s|_F U/f[[5 +t.#̟rdq86 Kn;)l74qR\,S۠㝃 b7Ԍoϰ6]AV=so='-#JZKI 0wq1H1 UQM{5xu5;*]sC}!sFrCKr \t$R^8nb%PhmKW,T,`2O 櫠e!^ ms:OQXu)fJ:㥫}D6@K{U,jhOjH.T{ee,epiTx\}ME [~{[菙fO654;8v S187`ehίZvP25S)c \P:CT:L-ԚpSUl(,~v<\]Uli[C<9&X<X!R7[Kj}oǜ%qTV: yqQn ev[+mtvzQ`Tǀ;yI'k_c:bo:oUs&B;#<Nvܤ(_eNkaR45dUz`´KQ{wO%MKj7fwRLd-dψm-n|{{Ͳ]4QsƗ7gr@'K \[(dlvF=t2vۛzz. :d٫[4OmF:댶 =ee$O,gwc<-jgM SkCc.)c~|#9w ǯ=wKnv犺gtSZeZ&},hmu7dtT&Y\e81O!j8SsW\L[XzUlk\[̴sp EaKET>>CR a qϑ a?Ks1tUWAEMUYc썎,9F8JWGaS4Bx|.;I$Tnz˟fAo[-Z:]#-G֖XԷ敵t x%WLɻOװQl=Q]SY$sRZ^\fk!b\=vi'VC\'9e#>>$Mr=(tNvPbFkٍݹKrO15rx,q( pg$ZyiHD[DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DD6ɩ7Ge\Ucy1~~4jiO~iX&.!~?qмVmS~LeFV46ImTXT +]'%@L^UOaO<ǁ .yxF9u\^ԿUĩ:7X5.tԷZѹ_. 0ۗ݅Mcj[$w*&'Hk ' wϚhF>;|_|=:پS׊xhm2l2A 9,=+4B0U=S;-.= ZE1Iݿo흞+w[_Wq]p7~kz#u:[u )rN`:/; L'[uFj}c{`A0}1\xr酁}uVP[_%:sX*K]I$pɍŞ㒴+bbwGL&ϯ||D^Pt͖]QhH=UqT9C(Q('V!]~j[A= k,-<##$rSz^gv}xf)E3͐=s1"'x%e5.N͹uWfRSZ+;5UTQ-h?W~\tV-SE}([ht3NO\sט]Ip1 kC[l QQuzݴ9uN]\4NoY--1cȿx)q 鿂".x%_r}6'm7hWdUjE)xKcmKEVYcnvixț[={"Ɇ8[z'#ݛkֆSp9Ų29p8o6% *-n3粮];S3c {@(:vf.+}D :3xr<%D@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDhV*5O6  ԓ {???5AGN`:W0^ւN\v̮F {VmƒqBa@w-?[uj#U*z1yI'r6Ջ{k.TOW#1M9>ab]դiI[+iLelo8ĆQՊ2JMoUUyYP""" """ """ ""۽S^c.aӸ=:{|Ct`pVtϖ[MđAH4eZr2Ύ%ia-e"9 `h, QͰ!vyU4N;y,&rvw5݇7#uUFmu<3!3Q -k6hWJɋ5 C2i҆G!EHȇʧ+fJ4XoZ0cK6e+{9\$uŗ;e>meuF;oyq7|5FmgSp8uv2NU2vs˪Tgl2RVGHJ)[ tIle$vq܎]}h];~q>) pĵ u=j΢ղBgI\3A x^owOKeԵy-%KŮ-ܸG|i^7(/wQEO /b7jC6{&Uc./S_LoU2IAy/KWMgSUs!8>j%"[,( 5U=Mm-< .2FN: bZV3Yd .->o*knw[||WQf]v*y)w H9aSRI" """ ""Ophω@mN,dҁmϒhx@ZX'8[ 1iH7pKfw%DpV*83VYq&핔S1.|p1KV2 3'˻A\]29$[ ˰`r矟z,uw 96qc]틏moLy.m#unGrhJtg $%qHu !uc۹ՏR۹YNZ˦" <QbsGH$osO$ _1acR ~9X˝G{9e|q!1'g["/2&٤H%h>ʘ\Zj/!D)TitJN'{+D%$LV'vϦ{L.o灌st#H$}m䰯p2{ApBMl`n~xl#Yis's qd}ʸjDP" """ """ """ EM,H#*^5FOݕ;VN{梵[jb}Š d:L wEɗG6e"U_4Jm*=)kY?jOmef].* E&so afQhlVIL33!ZGR_7+M]QQ[ZG݇]9 s=3ۇuI*wr7TWӕ }n:טiݸh loL@k$/ωh`0꾧Wiyn=54N瞮jI)TTU5 W<l&7jorn7w>\^{'xyy"/Ǐ=+ARZ55YK9b'4غ"ydn Ql7hbJV͵8twg]Ry7R_?]/tK=-P:`.%b}Q@)M/;i%i+ YUXIagp\(Q%io~nYںU-%\2,`L7ݝ3Of%jV^mX4A8} Sc|26q|Cu: OHZ@v9^l4)aw8̿SvWj Wcђ99gw '$y xcT+n[" 1`4P Ǩ#'S]޷{rRhQ٨{+m]֖@w!P9uGfhjt +J75.ш1M_ mNۯcI2T[,X.3ZʩN:`ÑGk]nzzJ*Z` q8y4-6mRTT(p6 X)mwy&ˏnW*+ieTUTK&qT2 y/"Ns'w`İb%R_BgZ"-H}FOGŌqu{X$>",oWG+p`yJʇC -l'c#<2?DR" """ {C^i- yT vM $#FZzՠH q%j4'lT>HMR\YȸE%:$4UD5YSmYl57?naq땪N_&K)q9+3#% K>pxBQ|`po%1OiѕԱ)Xv^d<OYVZƆ55JPDE$h2 |T[)wA30y.s G.M;+ƐmަmCc 0 =.Ł]Rvuۮݿ ۃ.NKt cϻX7g]Zm>%072 QRF{UXhL> :@DDD@DDD@DDD@DDD@DDD@RJT٭"sÞYvwJ:b2vg\?P;qir 󦦢Q/Foi3GH40c8 kW)移1RWs\3x.;˸Ք}>ӨDX{L=ǚj;}б:U?qNj}@xs^鶚[moO׃N-phC}5#mdcYZC@[idDdb(Kl 3<>i sj%+:IO錮-o\n:pRP)oE5gãwx7g޵zMfF&Kqc'R6Wp۩bԶ6N&pc?q_k;=M=&*-TVHEPs[ 14?[涶W8c'ջ9o˒ù|O_5C(Df1 m9pM!dyߟSOmQiەZZ jI |WMٮI'%mLѰFLC構Xw /haoYCM_OiZmB]peQ'C$ݠ쵌]"c^:aϤxs_ͻvwQP \{Jc3~#ds$5-}N?QԍM,Z\kY+`wmHn5q.۳6VL] dP}u !,yi-,ptUj7zZ6-{+MVR)3~%wM]Sd=uQG+[xAxZ:4碧uqtݯr{4U]v3""" --2v܂3V[/=*=!ԭ1~bQ<)!,!ޠnv< FH)+E䌺r2sSP;y'~j5! MNBiճKc.{yRu8b#*Emc&9B8IR6vvThxnw ynVKKc8&fԗ{>pH(v]%x@Vy% Z),2j Xw(tN+6W\WV9GGL$mF>ßW}}.ާud G"cpIF]8ho& d>]:pw>~^; r/ew_ Tsò^G祆",DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@Wjr@``N芟EԔV z^8> F:<YZsͣW o!vZȅY2q=̩YKL\>8UJ(Ḧ0~yZ޾k >+<3NsB N#nyr n?9:Üϯ+](K.F3*9čBz]4]ƚ$"+P09.]r P0'皂8K 4:Qnkz`)UmGin!EI"6-jKh.UVk`m+}\sdsx[8 @:,uL/K\燺/ULI\u4קf|(tolO px=WRu=MUiTCsS0#!E` @=@T +h59C[q:$Cvo,QqN-4w_XpfwEM<2>q=%'08 5]OY㩏.FG]5TNvjzJ&Lɿg9q w tUSXںGEgtq' C n!ѽIQ6؃|6TAS$][nwh=4mԠOӦ|"㌭]r-GϥeC>^y껰k!'WUD]DD@_2=\쁅܁qOQ&`G8xO\/h- Gf4BWt|1Q _c@́EEmq)Lm Ii''$^&C%Ȼ_i#\pF!zk ~[.tն'Q9J'iogNwa*QjnT\M]v*qG޴F F-.@02=)ZwsA'spk }˶Y̎"!mhlP6kkmiw2{߃L1Rfjqo I(nUoW)vw)SR&4<&^.\ IlT|cIsU6'Ak#e=LoGIX3&S{s{[^iK!`r%z WxrQ['h8SHuu4.jy9XX:;-x6[EU+r4Ѵ9>-#vu/Uek+T"" """ /sy1SKMR w\PXZ#;e$32升/y[TDܝڶRT),TLvZ;*rGM> hhN% 睖gYਕgF{_y|w4dcnjǂ<^ԙ폧={ei;|$c}II|N\'\h8L}捆>{xw뺕k8d660jSLJ;~tϞ~sy.cgoz?ަH%b^!_]??-k^L*#%eԿBZGOo ]ϗ#w[ Sſߕ^7[) Ҟ|,Ķ>Uk8$%|D\DD@DDD@DDD@DDD@DD8d|2X^akpA+g3J'VV\Eޏ~y0pW"6zNM ˆӔYD^KS%SxNyl֯z)iLu&RW48gpCGECa\Vwbٰ)TC Q}ˏfsA9=ygmV5kZ妧)+).@ :{%.#'RQfoq}/AoFۖU &(&گS6ݪ o+[$*j`Ș6s smw\._.1u;}`e(ˏ 9=6T$TZ:i-_Վ_uj7ؤ ۅ=hEL#)dC 'TIp-%4TZQF=6,u/ZơTm[SKL#;껅wD7@pWB=|*x_/)GdpYþQ:#X]4mJCw{hggrkWy"-&P]/ۊ.]vzדS =+ q8IܕMDWÇ,xWdB"-""" γ\%U#u +|QAaB»{ -K(j$!8<ے@2HMFy*E8{'cU~2E^Ėd2Nlos9^XZG%= GN+Ft4t r1AuUsegƒ#Ye-$n 6U\y3y96B{%MZ xNTF~J[v$F1?/;bA%3]4mGs\3ӟW&F>RJ̱zNuQ|` Bbrz7>%q uUyeT׈(fC.sp]%DjZ-9`3y~䚉䩨y&{'rU}[.>fFDKiq̪.U """ """ """ """  dVXC]SN@5 tDR" """ =f8qc-G4qE9={1܌i`⋱J+"{o $7[[F\-潧!8 u|G{c ϯ`PJ\Z|:)|rhoǪT.Q\HS1p;-@R1Uc8vV$s/滩9 [yp~~B `iho]X<zeCPBY{EhuCG]Oe$??r}CuO3V{D|+;`UiaM\バ4HS,QU.#;,:h V_i)䚪vEA$* ]os፣$յ2[moc#?c MqhY:a%*ߐDEP" """ """ """ ""L|F :qjT4ȷI>$n=}9^hWj\)7p##V.5-.:TUމi N[&r8y~#Ϩ%s}_x-gAL^}lבޞ&N/iKi4ĺZoE]TQ(tdn~FkkF43.$9 9ϒvxXiiF=UtYN:`lMt}߶|izG'ktƙvgkz3wN]h9V c٭z֪)- :Z`&dHُѷ"#Bc5FCtME#Y=B]M;Qި4SZ衸K{k.,3 dZ3owÖʼ{y([ދMY[(:rTQTl`bv]ړ3hӕs=](.QM?yijm:87{Ζ:v-.`_3A-n@ki׋;TN)q4: 1^O|6/Ki&۷w'W{UoMuf:>n]wa-t'vW=mlJ7R%M`I3I+S}M]uma-Ioaw쟎.Oӯ65MKk=4I`.9pi;C[drxeWUTӽ%Ϥ55^tb?Fsb<e]OjylX1GEmV薪+_jDw ۧ[聎Y%ꮾY#);ۥ m3C@c1v0 ..o+&qP" """ #Iki  2*6hϐvM'z>ywQ%H?y Hdq%:=]Os]rϧ lk/ht2.}f;˚5G#t,[0U` Y$n٘fk,eIw?+U$l\Y]y*3|Ak8J:\y=p}v~ϨsDyWaMq i.~׾.erTdqrh=Zo79ǷXg,\Ǭ{IZԑ=Ǽ]B7-C;[1l9ao^\y.idr*݄DT """ """ """ """ """ """ """ """ """ """ """ Sƍ8uE- ZnH~%U.53T)~`EEl c9yDP" """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ >|YvfAWCJ\ϩoOp\)WaNdԻ QYjeD2b׳e|EJWWd-N'>_*6mk+`|5 8,w5#>AjmZ EhwyGyȬi7HeJI%CiP`+69+RLsⒸ?n]/x..eO#mVԯL#o!7S`Ӓj>ltԩsre[6}WMh-kp@i9%IotNzf*~'R7g7]K rۦ/7*)+((%r淨?-bri !h#<~/65w%wĮ~4B$aD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDjy) Cf_ =}$;Ae}A7eAtyd kdXp'kN\WZ)!~U^!};C#o'g=v-'g\.WW SF!`0z'(GV ׊TVﺣbX6YQZvE=Sᓃ\7ढ़tST |~W! `8eie.9q$a'}OLrm>.-XMtn}[USMUTU2H+ڂ^V[TtuN\1`.i9怐r +OEB0o_4(mO[F&;GyZh _pe ){hdR>g>iHgptnV;ʉ?wW'Fp'}];3u:oJ3Zq{k-;@wһ>9M WQ.6m?_oLU2KFkd!k/^ w'#g-C_N$*áow[m{ͱwYoIpUs^\\z˿[KqG(S1De1ۗ5lxtm>D/ j]n~TnK}}/S-]u:9@}?# 俱m~sZ椭wz\UM2;тxy6 oJ:4ΡlQy-sQPY%K(yq9d4y䴳q.qL3^ EݙO..O~~3i[% Q'{cć5pKXT[CV.$iq-61|/9wo{<&9>[䚾SD=uHM<{9A@sB.ǥQ=*"+""" """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ ""٠FFTxD"u(V"JFIFC   %# , #&')*)-0-(0%()(C   ((((((((((((((((((((((((((((((((((((((((((((((((((( "T !1AQa"2q#fBR$%36bru45FCs&8SVct9!1AQaq"2#BRbr$ ?iDDD@DDD@DDD@DD?tg$;E4NÿIedM˯ޞ+rsޭ;[^7J؆].T>ϢqOioN޶pE>C]kM\:JѳIR㷧?=/~'\Wb{k?VvA.?xN*bwD5gok1.)Ꭷ>i.=ԥbus>AvQ؁i#bC ⌔M] avФDMmt>:['Fu.y*RRŨ)2OqzQu *:?]u մ\ZyWWL\kc9fq]qy$wrgqgs:雷Vlbk>v{夻NϺ ]䒒Tk /2:k`%&B9Sw弨^hhoH^)Y6 noUc*Qe)ubzuog˷m=GbkIl7^[n٧Rο]v=A~x0岿ga]-4g=eMN_RnNnawrHc8$tr@RH]7+t#e`t<}ӹS8/"#"6J4AxtfH*b2VwM%ᱲ= J~8íZU^OԇU=W;ՑkzfI뽬M.M?-]]v[\{x$J)T_tXrJ]y_"^'G mo09F(5 p'g`z-8'ʾLo2l >F~諗Q9)(v_=ξ:|S-KmNIS5h3%4؞˭|LwsW)bӢon[:~d=R g56=ǩ=iSMsyi%u|ס%W1߀vZ2͔v>HPKoKOޖ9np~?E:B1s}L|6hmJn{2j?2E ^a/yWN?{<ש7ϺХ)b{:K]ZNfI \ӭ0iK'TTߵtxh0Hlf>j7{S| UlNJOn뮖s_0xOɍiWDZ""" """ """ """ """ Ww+eeq=]CjoJ_֟g Tc(ժMVi{w4-TyZ;<奪d6IohKO/gkW_ Ui-ڽD>I?f$Cs5];ޞ?EG550V{ZFuԫ_ kW_ UjV(~!rI?T"sYGI`a]&~!16w|'WO=_ Uhvs{b_;^8ke c N'WJ|+W(k_ԟ ]jODS/'8v[ 4NUK-0m+Gܾؾe\^-Ëm.ޞϔDqYK>tx5)׫ Dq-wV{{}kO_ Wi,0xOm+gE#L2w$v]V<޵KI?|'W'K?ʿ[C˵|_qWWo2-?.LͅㅶFv{~vN_ kLyW C_Y4Q\#~ri4䚫ݕ'ߺ`ֳl #)'/*%~_ִ5j2qRϟ.NF4\6J+h4o^ĝ_ vZ|(+?gW̊˙ȴ5/(6X ?M+/o %~_֡קȴ<[Wowo܊ɚL28ĭ;&B{4;3$_ֹ$bOW8E2ks勄o[y!022-ηW{yVhH~&$֟ؾ|(m߈j]ꟲAPW vb޿~/SJfQW~O+@Yiƶ>wqce3%)E>co1S#RޥqsZ}<.K! a5SyqGmGW7'wڹ)e{?+kڹ"ǂIN$عa Y4Bߞ VFѮ47jf1gVdtdcIw>u~:Эa:X_9߿*1j5uVéJ꺚]l՚hQ@@7 9&o˰nQ,RٿG rUw/:"-" """ ""3P_V٤k :r.N޶48;dtr@+η']9\zt{{k k{w#嗣uL\&5s5qrӿ,$hƛ\:\YRj-=/_⸾_(K4/;?E>d@&#NO?5l2XZ܅اrY- #n2:,Q Cfu4NK l:%I?'2J3$g_~Wr,=0" ,=_2j"iZq~1'@vU26)4J9OۯVۯV%'WKv%hg⹨1MK>aR.}̯^ݠˬֶ6؁o2XflVFjsZsǛQݽ*{l5Xqí[~[l7oVZXvI" _^5גlu'{7oljEd`>zwv0׻ocdtsJ`M&40|JW|Woh˪c|^|SU};io~MO~{hцF$w;{!e/cy zq4cZD`5۟4V^32)248v'?Bi٣;܁Lu2@ZF+/dn jb6ᵤ|=򞡛MnFM7\\{^kTrnjrgy= :"-" 3tِT14{lo^[PW 5;j8A1LGasp=yeĝ,wwGtGl=,Zc(w_ɃS43(-䮮ўԷ7 TpYVB4So'[^^#ܯw\Vh?0$1S |~7U~'Ɉ9 뾯\5cW*گo9OQS6v@q|ՌIABgSz>]Bɵ^0x;糑|} o̕ -`lVBH\8t`೘+Mn8\hvi#lZj_:,x!°9;#ƿ3Z<֖>~C9GXlFF-q=t{]-zL~}͓ۢ"Q}Ԣ"o""" """ """ "" 5.xvT_ֆ>Y|;W*I͚[GOѾ^ns*|M~,q)˯]+DvoƽvI=7ӝY)ԩib3_v|}IhfY?~\)WIҺwOGK:\o_I|āu.[ fsf·a1^na#$"4k}sΓn|}"o inݭO)Ō ߸kNwܭ0,m0wϩ2rt%5:jRRi^ײ΃cz~'SO|Q5`CA:UF*_aqn'd ɚI}m8-:jT۾-ϟq㎹#ZU(ʋ_M'f 0}ϡmTU*_t9xj}趰aF2`zF@+:FatN- zl,ͨsPrj/xLpOQdJxBzCPm{+/`H7nx*z 6sZL:]T#):i!D@DDD@DDD@DDD@DDȳ>GsN_6=#mF Goۺ-c53K'_6y#pϑޕ>|~~Xԕo9u-Ϲ]_obQ&]_=kmq|PLv`!nw}vq|T;iőqظz(bҊ\:{5|Z{l]rs-S~dCyryGuY0GI/8udeזstZ =KQz. \5ǥ6zK;_ߵ"?⎉FQ8!FKװi!j-`d&)/6͎=KGCG丫9%ʸkRb?k{=H@/J*=km\{1-Fiky6i8DDD@DDD@DD~'#"|kJڗnY8ka^ `T ٬j\Mb|u]aJ[I5J֛j_^iXԓmH6LsKHan^Tٍ+dm9 &}>JY[UkڎQJ2xc'^?1l2h#zRΓv.M}s%/=L22@#-p#{=纍$ōc|λW-fyDEDDtv=Vȳ#ƀiUzwz|̹OZVP%tH=n޸l|+됗DIn=֛~ 74ݓJ W#$|Ϡ^#sٙ1XYm(WXt5Oxǧ7qI7cnﳵ7b7.)͏_ˊ] +N(,]ȝJu?}v[xɦ\ykdPޑ#@l#.Gcݫo&-@XoRcRkaԛ>w7D֍썏}޲scԗ6HK Z{5sqȬ<~xa@axE_ܭXlW{iPOs<[YcQQ/KPDDD@DDD@DDD@DDD@V*xx9PկY3[;Uʩq*X蘣. sa)MQ)-qQ.&+W]^HFVwcK\AJ㹎2YIcJ9<8 k\IxomۺxC9״?q]47'4-kXͼD4Ys"ysZa.hrL+%dR1g(YF#ZC'#4 b`nʇ[>̰< n7pͥ47 !Ѳ5w?>LN)XXgs8wEW, 'B0O6t`GrH?қV~#iy y3aoSOU$D@DDD@DDD@W vw]D٫49$eH.c߾hl,4uXnX܂N;?5%6,:q`b-9{0r/<*ܥۭFOp @}vTqRDls!z > ;Vg}{\GT""" ""uJ/2FUkvJ:7: i$> 6 f]|j偮cIg`{I5{ƗFKK롵UǨrr|ד?rTJJcr>"ff>[Eіvֺ>p.br9fBB>[ʼn#wFN>݆Yz44wUc}NZ.9\P 9I6B3y4k29'{%mޫZֈ=Dr~]o}DCFFi;刹@VH7iz3%VuWV8+'c{bQ)RI{ۅ]_/vYϱrGwk\S'?^= #h;vڈdW[w+1qp֎OTq\_VF#.Vzn50>M.ޥZ-P8O`ڭWo33D}ֹĝvt8_>[d}V13aJu9}&Ec0ֹaZ 4#h#\;l+B-/S@I66 qKu\|U%7{Fi6I!s$B'4^t4/4Zݻ6<җt}^4v&F}ߩv""T""" ?J=sXR˅ׇw!>q\Qn  ۠#_u~ʧ 2#)"ԭoKZ{oVqoui+͊P4N?3ĺ~)2J`aLid$nwP vi{-6S[,la Pbs5o+v̆Y}6}{(3G+NkE%#} -xLsb^&V"-B" ""^-oOW0t|e޺zߧ"ԜZ%R7%d̎l98^ޚ m,cyGn=Dh  |>ʴ[~)Ź,,*>m- Q˗M&nW-N|e} T.'X7աfb-^ty *荛k}ZLDE`DDD@DDD@DDD@DDD@xɹ]̄8,"B$[K5|mx=~L-9\gZM\ړ,^!,'N{ha.sW⿏/Z~QH7]z EǏ}0~|Gۣzur_|Z>W{8SpY_=|~/9=9Lu8z?K 荀u?U1}-r7(X}h;M[c`D]:rYCKAyۤz^#+*1ŷ&u0c:7*M_Yxnma4pi5i>벺R8mp|{SِBvk`7C}]RTi}2 ٜ1dm_W#\y@0l}?&ydHKf}x+$T""" """ ^,Ovޖ"+}=3}U/yGZhT:>]"'l/؎B8G/EIuB*l莖GWc'02ӷpՇ  :"hk^1reW[3=[6=-#vR{b;%V٪ꎇz!'_9My[9 N&0Jȩ\ny v0Ԕ=ۋƁo=:>XL)M._gKrDE""" xc¤6h3Z15 s3X; Gs6]Z=7qX5'ym]d5h G*-ήZY7 28=; B [m~EQψR 0:0="VG`ܣj{txT㖴aZwݽ>@'4j޷"U{DZ""" 5H8~ Y;/tg_굿뱽(`7bΡZ76=?zs,M+°5$cgݼ׷9jX_0YRnI,MoTOh!ee|jI6{סeVC7ctVkc{Xak 5s|Z}׭Pm6T""""" """ """ """ ѵuwN؞`i tJV ߓ}NtJVVQ֚X%(͝ m%a fׇ͚v?LT^g<*Oᩑ9[Q\~g~(c,RMo*$'ՔJ+0vƾ+ }%;2R` \FIz'H,|`iGOCjm/޿ʁf3ye;Y'ûluk[?Vx GrBjlMs -p]bGVˬq㑤3;?Zߦ<(\Kdh߃n/Ywzv`h&2簁=[s)U3mI| ͊vŦэk".Q-MOM\qߪƖX8_S_d8QKo.3ļqg5pv ԯ_>&HӕX1D$~Iv2byio˺|QM6녷GC(Wg< 2x |ׂXRXv1] v5I6CT""@DDD@DDD@,c\sT} u۹l!KtH WE'^n֟JVcoc}1܋w'~+P9ц6>8;*a25^ZMi "rComoӔ%Wk%͒JC\lopfxg'e\rkagk7cezǬz]ot/zXDEDDD@ّk$q!>[ oxVZ8CqrC[\< h w6Tٞwc0wfϋ:L^u.v @^^VħsuȺ}ϜX^*d+y[d`:==G"|T2S\0}퇿 ohíYF.o5~riP" ""Wv|?OPmuuo]=DoӾ;*q):@"d:cX6CŔ 4 mEWacd͌h1[Q=C@|>6]f8`x~eޛQwEja]ժ\c,Z/&T""DDD@DDD@DDD@DDD@φx[x)ژO$wèbu졊])͙5;vOࣝZm/%_cSdn8pe+խ4ސ;=X?d9nJl,ː"ũZ#!ۿo\V) 6eh[[[~~IltȆMb7恾K].JOo/)O{?cW#@kCfZtG׽ק}/?1쥉r:ZR\IQ;&7lk~NԎD02y⹻*c|ςhz}6i\s+ڮy:YwݧcoPk|QAq;-r5NeȵSed+^ǶD"3;[~grL&& [j FwCջ^JnMj39"g])FpN߰{XNscV2I>~輳aerx{UH|׷]=^Cջ<:loM_o4!䔳f#ߥ≗2 ɾF4fӾ~'eKNeT#Č;kJIN7L>Lv"o7ܚ_.K%m;#׿`tņWN5H]D]""" ""p|[VF#.zևHopL~g}R{WUICtGK@#uqٽq1JVz'eJzᙣAuZٹbin{|u:JwZ;v>sjRe vntux2>9 ~% ${qkG)߷oؗ[>YfclО2VlJOo;=]j u>9rw JOC$p$8>eE,zeQ&/?J ]k*DDD@M>/Crv\=N:F|׶#̎2& 1וA4ہMfjQ^9fyk: 3%ۄpf{{/K^*Q}wp4̯EdVm!qλl}l}~{EIx}(c6t=ϪQ㓤N+Ď){N\3s#C= u;@T+Yd#8X,hO2ys,?5]5qZ]V\^rkdrlEG!^X~)pQx|y_pϋ{KZq꺮4xv)u;DDD@NSvXY2WtиR/6ᤏ1}=WaD6ɢ\{k=DCMԒ>/ܲxYV[s-߻˨߽m|tN 3!RKx'Xh wi:CKJOEDZH""" sq7:U痢c{EY9L5s,Uu I%c١3uO^eia|QYeZQA$5Q=6B=&9au$Uܛ͇ )fkIH""" """ """ """ z1(fO/ d-ߒ_ܐw  Gv%?`&ێ%H֖h-~,iSzeϛ>w, x*=n 5' xeƮsn kI_U>&tL߾Ug;!dRCh<.8J7ܝH0;feH[[(ӚZCJևRSIԸnWݮ֫Q7|&<kɌ[An,uJ&ח#|p[vo˥.7èy'(C+r*t)K!!s#nD8o:jjCSmJ<۽87cN%ܽ 2l_̅o!@>AlUrQko5km;j' 8򶲔qxWi_N-ꈞַ֟é\(l~:BM~YqFzEvtrfkM+MTo7^JFd<7丽KW7ߥo'$ <[\ѳ[V?V%`lz?뷠BVNs.Ӵ6KM̹R|6*PJ^hc> 'daꡞo$6ߝy[UFQz" """ """ c'pf؜wgKvWU38)*֡c L2tpEӲc? 5,U6gtv\d߇scѽMaP8٢461LǺ;BF i#;~r/?1ϔadk@#yy5E/ XL; 뎛Imf嫷 [^&gr8f %6,1n>->=Py|Mr6`pLi}iAy$M~] _tbqRm.{}ĝ*DDD@o}[OQ;v[\YZ,ܟ!|pFrpCzkA?@hJ\3_͒Y_U8Y3c۴)Ǩ_2d.b@]3q9e~Z 'r [ʮ.g[zvHXy'tׯX!y}~o\KU,ݜRn{QYD@DDo~'c:z2puuo]=DoӿhVjN-Ak)Bp2EfG6/yoMhv q1܃<#k4zdeZ-]܂kXە\][vӶGȂBɨ˦ɉܓK+ݖf>zJnw֪fw,X 31x: !/OnA*ݓ˂_9?}V=| WVҽے[hW [L #Nǰ;w:CvOA=maq> jyq׷իX{HaBn19,G°9Cڃ=jVtz~wE9*ٴ%To}R+&]_%v7/Iޛ=uZ?乛5Y4bg{J̓8{72soן'ގ6܏y4k\,,Ő8lzrx R+zWNOgkWb+pхwnxoITW܎(h{tpVIlMn5,e' ǯz~hx;ffR1Hq':cj0Skf0䨱e"&?rvRX&>Ceccv#'Kbj6G;G`mmsx8SGyd,IBGX?.͵(rՋk Oݳ{ :k-v&GU[Okby0^Do`KA@ 69lCgl[af *lV w$klVML$ƚu]ȬO`yP" """ ""(fxTU"1hq'Dt;jW/635;yn7 Xr Z莀3@C]>G&Lx:~}FsUd[n"L$u| 4ޝ;gبn_d1n2YdhTWi}G[ n$ۓK-qm:﻽}TSqOV5+EH>D3y]VTOԖĴcxmKԱʙ8\ZOPU >/2rr׆rCdt5NTbrV6M]_"${D^P" ""ųȻf<ޟV]-KnP<+RIc,lЙ'M25*Pkaxql~S$oѽ֏-Yz#1R(d첵8#dMoTOhl3W{mg=w'V.Q_Jtn T`""@DDD@ApAs0b`q;:U]g)E\U/}I96y) RQuѽ~Kbmб^$s<&n&l}vRʔtId:kz{Fϰ ..o#RI޽ *dѻ^CPB[C\T^YhG+W\ojn)DDD@DDD@DDD@DDD@^ӱ%y' _ F>}[E su%;>?Xjǯū};n ,lͬXC W;R+㌊nyJUlM;@|r pnWٖ2,S+[Gŋq"m/e[&@ o_+_ +ocsgvmX|±EL\P6Ēya/2ׯ))ӷs uOɺ^`vI=Lr{Ɲy~eˑ/h{CA->} WG.n}+V'yp 4uu֗\w5phfy:siJimҼc U5n.A_î+{q<[OB -0#=C!9pxmlSn5}D6~J=imo潈eAR_O`c{1tOG.߾'58)%VDE`DDD@DDD@  +t3ibxĸy^R6H(KHN₩m^Z|o$coJVcuylsFΆE܇"lE|]b7A;q$|j>1壯Z9\d5fǠp,oY<]=z7K+ c\ꯟsqqAO%j;|Xk| Z[{_eERS{{%N"-eB" ""X<.? 6ktƗ;կ20Aɬc2ةdhǥ{[F=d~sX ]8l/$3Hވ'[#kϮ/'eedR }"lqգJw6cX0G^PnH]78k4+k+D]""" xIk WVF;Ӳqr )Kì>sd0/̎lYH^ރA?@UV[Z^J}B"!@cŢ___B}3V>7wo&MFYv׭la54.d%;q;S贑݂t~4. 8-l-Nωf yZt18$Zռ= 쓿RV o"Xyw]ER8u ->ʵE4rcxi: Mnr8dD[""" """ ) 'q^.A`\; $gqhࡪuxf+l^G.Y2X+p9WgU ORl<6Zzcn]ǿϋ%%%{?} }DtmVbMg]iH#O$`?.u>9\F|eR: ;j]tR,_5~W""T""" qNCsfKL)"ԭ.oKZ{oWO\$c|7Oaa8/8, f&.YJphÏMw#;J5~|kLHs;~KͪEY;/ͳ<,V;۔\N+6#%#} -N:B*QNq'@-|F ,k_="V`ܵJX,b뮝yD]""" -gX.Uqs:ӶGI96y( RQB6odl{~<oN˹*LZ_gDl߳[bvh]fr0eGt؀@DDD@DDD@DDD@DDD@V978"ydeRl u=㦓ҪVUL}ۭ.Ry2?ϫ 2:VN|?kO^'lR271Sp-a]RٖC$PO#OSW~{o*r/4R@ sz:l3OSi*T)}~ufs5Oyϵc{!iw_S;3C 8<>N*-$ı5^A 4fZۮCgF杍zG'l]4:N@]ulvګmdu %QM3 =^vs>#7CQhfJmn݄DZ""" """ ""NH34N7l2942JvA?PW N㺿Jt7˨ڏ?\3 UCe /`3@kww|o`ihpIt]:8h8;WVRvl """ |+#UONsGwN=6ЌNDsvN S+ zJEzr}je*'VNj}㭊w|Hd1ީu9+wi:6t=QjxSk_ШDED@DDxAgp8Ǜ뱽"\~@乖*:hx^lЙ'M_2(,( YިI!AQp i|MfÐUWkP[s|ZpDU$D@DDD@DDD@DDD@DD|7xc| 6ȟqec`lwT2V׫ r1Ԅhhӷa +XokEӲʞI-fZ2kL餸 >_Ҏnߕ47ZI:$~J;&kXEfD˚KM;[ss9[ZPW-`ӚZW E:ٮ_-J}H18> g>QD>T$Q׷rcpb2Zyazw$zoc \@i_07u7^ko\ͫy26ʖc 1hhUժ9\vԫ}8|7~KR|c[mwXc_yu6OUG))_abj_gɐm]Y|avklbύIgwomo؉4""" """ +~I)ڳNQx2)r2\Ɓ95Ȯy]Adqݼ5gtSKSeù2|g,_>A9lM,GZ` ڬ~Vx/ 9f"e!6H$iZ?S]{mjo;qJ*Kz2qBJѡ#FƝًt1 uho,'Ź ,,-q,@ryK7Mz/0֏6o٭ pIj\M4/j0k """ -/usOW[맨w-*IŨ:PMr^e(Qr\HÓ ?@_&;|Gل{: FPp̃E[RbrnvHY5|t19[iv{tIV\ЮL|e~Z ff/RA^bΈٿf,5 ʄDVD@DDD@DDD@DDD@DDX`7%ȹ&^>KMK WjXw3嘞}S(S桨-'_H= *Yby*I# !X4ַ}öfz(ܣ{I=\.8&RPd&L%$!v}`atr{K_^fI29$2ǐꖪo޴oKՓ_BĶԦ$Fu8䑢5I͓6LycᥳO$4QDE""" """ ""p~`'gcšFGUӹ@#MkH$<rG#c.?z`]ejōkIgm=`,q.ţWpߵÑժg]ݮ#wG-喸K0\u1 :cӽ+p$q8Sgř}c,Sh8N / 'A؅WzvwDz,Z]fO* w~oYJjs ~ -_QІ=XOPGد can wjGz/~J$"D@DDno3UЫ}# f@|x߇|k,%4l7J2l͂Ӣw[Vb\ކ db 1 n%[e81PC~Ոݓd:YltBuLr*MGm[}ckب3a-o} ^g1_su%\׬5c ɊE vx|42^}+ӧ1-sO&:q# ~;-B)H|!߯GQCUs":m|Lհh7^XnP|{%䷴,QxQ]#+ίJk[^緯?3{6&"lN^ߺ@?'WiS{'ǝm;AFGC+ad΍';8]z{x&NIa-fȆB`m3L@9huLF2+c6{7d齩~/ItڛŪَVRx;Com\m o`*[ژj{vSD@DDD@DD1 y:%ӖŊ\.f\zY@:umgz|2̕RER9 cK}ꭏiySk0X(qt#z:މU6h<, rBF i>G;Y)?{+k144lzn{!gÛy+Q -hUjeTgi:^%""T""" O޻oˍǥv>jgRy1r+<DXoGHt)r_ÿbX\Mi̋ꑱۉ%u9cx]z7ؿJOX~*a|e@v5+/aktz{N˿b:5Mace[@=@hthk֜:՚Xbq.6ϡ\"T""" "-'|t.[d][OQNʳqN4?}0O̎l98^ޚvO-uGxo==N+  44}<_ iuI]z*DY)7R,TźOb\^gUKb QUfY$Km~}=iWez.%Vƺ;+6>)/p$번.z`D@DDD@DDD@rc.2X"JDZ+N< b>[ŋ{VA$9:67D8r,9:L{ p&CS<Ϋ03q/0vYnY9rʱcd%-:iwkr;.m/#ɆmXyM|@t޻mgm6loIM*\n>{2iq|M6X6`4gOeVO0'a2aF/tB'< IC]5[+襁:eQ" "" 'q^2_"eo8 i/lm*ݵ9 O.tOeNGK)8%ev99r}>o'+CO>aqd8ܻo̍nS!g)!ݙ]48Vכr4r()qѶ9 Xvhoed3 1Xۯn8tWCk~C#^q 4i vYu$[lFU" """ ox!Ŗ ^\vHZ+E,$_V; V48zqK1JYJN4'u3d0gDl߳[ݷgYf2þqm Gz^5٧IOVoql)jJ+aL"@DDD@DDD@DDD@DDD@V~+i <=d[=pJ|-s;Ubʩq)؝:&(˵IJBi~G79G5eOnɣɊL淨]Y՜n6pSy #JGR䕌+Kha'F;O//mngApE۟1؟ˊtdZ{̭ψBRya Z{S 4,? :>aʲFMҪۺJڈrmPDE""" """ "") R>b0\*vA?P8!"69 pa6?Jun{e~=܌1y ^n+V!l"9ή!}ӯɫ9 =Ky?|9==3dO-h'__x;ǁTޔur~d9s""" ųȻf<ޞV]\~@乖*>hx^Bf댑YO܆62pxc[<8#|>cKfˣ<RM=]Mgn ;5!gq67fZ>M݃6Y{ ֹ޾G]T`2YJ,Mս =[nƏb$il9O2E&s2&447c\U.;]]V#ȈD@DDD@DDD@DDD@DD_^ |$g:nQl#QT*q n!'e inka ,X81{'on~73ɹCklWAvSn{ȳnOqI$zȇv.-ɯq8BNFޣ]]=ƏaJu fyl{ڌICM-EKn= u'jb`^Ïg$dCg﵂9>{J!N.rwyEolDN#lr6އdX3x lk4C9N=wjH[ Oge<:>]qvz7|?@ X_d#ɐdĉz:齏U'߈V/нfk%{ep.$wz(N' 6 ȖY$Xwi'Ly;"}Έ0Y߷UZmlJMߵ%%!87+<:yv7W#eea-憻]ӇrIw爙wVeO7+ED:{?twPճ{win*DDD@DDD@Wτj; vuM [Fe/|#)KN x܅G-!_lS4hHN׺:yjpC>ݽ_bt8kTr'2SI#+6/t!!#U94s&''MͶ9+tG$-p?x|R}\eۆ~OuZ|5ߏ ^D\m-+^@a EŖs~e[ym{ooQg$Y/el0ZΆiCӤ8%eN&JG4jJuڑAg駦4m̬l"" D@DD}%ASXNC K;u}+sxT ,G9kؒUa9 1p"-JDZ+q¹OԞz%D9<ֲVl4^FC2Zu~{$~E0CaM64=^oޏwI >)p_߲3'Vl_3^`Gߋ}ӡUY*Xe Խx^b6ֶzat}]Nȫ9O/ lSJ|; lP 8MU]⮭'W}DE""N-okÖ ルz#~z5'k)Cb A͋) t6CZ6 {/UpM{SauL斝G+O6&.Kj|'NB_ ARn@߻{~Kƨ^4WSui]g$@7׆WÔE22 jyޡmz~,s^Sc(:45d\~a ^=F8ͷŴs+ojmni "/9D@DDD@DDD@DDD@DD;Cd9)Cѕȭ|&p؎0{OV~+xm0ْnN_;CMt WVҽܖMhyTceNfSb6'ގpOQ j+WZ2] ~8p{rF͈r3b+qǎs>;_%!ss__}鰴<´<(0w2Oq/`sv=mRNH_כo4xoqc3|Evmybkhuzսo(NU>/- ~9CxrzTg2t:Zgxݝ܏Q w۵ɾ6L|~.f[-+^2ou{mN[*{-Km,J?y7C5V4Hkdlht=x]iVם+4̀A=Y3yrndr;>j `H}k鯒e{fej6O*-1]}{]si(ͺ{NvEZH[9#ό=;Vu֗,N:pN(0g  ׯOZWܻ8lnG1 Ӡ׿b2+< mń5YjV :A>cw}.z?Wϯ؞|舋aD@DDD@DDN<72Ytpsf{έ2-GA,_7eV;,%wKz ],XI-cW߅\~_73}krޮ/ ӣr *渐G~jk+x+T-euS8nz.kayzsoU0RWtTG: 1j$^W./}vZ&i s6#eܵIj1<4*gS9#or\fqυUS\^4 >wrԟ/I+""JD@DD<7YLA-c+|nPt|6q3M=;/=qro0c9+ x]vF@=Fi v?Plnb22Y$v= 9c$R\ߡwT LJ O j8l@c\@ޣj*QB"&Lt?c{3_73sKdSz"J"/DDDD@A- ?f<V]*R~@,^:VqIb & Y>eZ$s<&nv6>Y>e|Ac'`ԆHfY ߒC_H+6]FH`^t)&^x~sf&_$>Mh]ZeWO;L5!0Ys2KZzvۊhu)k\2dDEDDD@DDD@DDD@DDD@^͊RK#K 2/g-*3][R]f][YZl2q/1V詢biA+KzIO1M,Rw4v:>_F aԚq{O9/ _kdlG '6uOײAtKIˤes%^&6 &elZ069pzvP_sxyOqv9&e40'#N/Ŗj޼ҽ|Z+"_<3|_ ~xKBH,kf7:cАKz{=kjz?ԫ\DZ""" """ ' kRyq;LDOΎ^:@v}ͫ->>ǩؔK h.qp峇5գM%.M"@D/y1z[ծBN> 4иꖯC }Hdl~w|} qLcZ1kHWE/O+ǹChz%scؾc`@{GINW_~['.IS""" ҼW9yXǴ贎Z$M+X>\:7wNk)B0/ز5쟠\'|9I_4A'(x1lVW5[^X]|EY_TVq#Gh8kAPL}*XOYhߧVŚZlpQI'%>Dճ¡D@DDo>#c~r ޺zߧ}zvU.Rt[N=q|Ӫބ#cDh F:Pٹ6CAsb;dgowVv]ᯙeϫ|WQMKoDS )mVf L.Q `gCh~vJ.w֪fqX7]0 #Ҽn".D@DDD@DDD@DDD@DD" ;Ke-I,$W874ﯚ ?[+Ya&{A<}u^8ظUMV:.#&;tr"Z'Җ^>Gᨻ$U#K^HdOTix&o4=4CM)ſEK9+u Q߯g#;;@ EL=3V$ey3_9]+t\ekG]XIEX gec{NӢЂqKr\D5q#+g%:TtN:"b 1Ʃ݄D]""" qcI'nv7;TE^[+,&XRu>,cpk\6>Gt9٣e,Q D@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@DDD@n]J.cuUl^)FX#ʶW=-'pI,40u VK XDAV6GCچZڀֳmFs`6XϡIS[ivܲtK83lcGw#^yo25,7@mB}Np:YR|BXyI$02kvvw}|Zib&YHmKW_$lrJU쩿&RM.9apBց dnii{q'}J-eֻ/ǃO.iҀ}fO8؛0c6cWbf#Cd~\3d\qtf#lծ pnhi'dwWsVkxK)bsY_պtrBF!tIwNiҨon)G0_?8Y(:31Ն"!|_y]l{6*6,Gzowx~6*ŕ%&"M7h'|o@Bx/7Ny.5~|eƽ1Mi?0KOQ?ZnفWlY{U%euK98۞X {. OxUɶ#\#ګn6ݚ 鵆ϓy?kiqa)ץW$.$O">w=_{`zu[Gr:)bZvx14EW,~ɪl: E׾YuqҽtӏBQM3ZS,DBybB7Ѐr|%\7(/k. FVk7sgi屏 Uq6=4AG[ngorh׳SB. \|>LMsoͣ=ds*[UkkUK1sc8269bcmHk;gz=Nlj؈a$\A~Ų;*x8qQ 1T߭:D3v֮4iZ)Yv}#\\5&Soԟ/+TTv~op73^cH׿rs@#p 4OkgJ)D9GhWT_6Gi5ڧ8;dfAd [}Gcv=uoUs6E-mk4> 5isx}f,*m?JT$0F9f'!߈jݴրz㓱f~&1SZCz74ۣ~JyS+2&kCZ֏` y'9PucJNt%htl֜Z\jz|D^P" """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ """ ""٠F[=P l ;JFIF``C   %# , #&')*)-0-(0%()(C   ((((((((((((((((((((((((((((((((((((((((((((((((((( "L !1A"Qaq2#BW3Rb$rCSc4%s7 !1AQ"a2qBR#3bC ?mDDD@DDD@DDD@DDD@;.F7 \w ]PĮMe43Թ21kGmU2GB6p~5mk|>7=yR *=Ɛӿp^}^O֩o1S ѝ|V1 C Yg$sO_վH굲R^gu Jmb~cp8s:3TsECTPB_bv?I8/Z O+:~-Cim]ȗD^,ݭOM=!fz.҇HqMim3ndqgc;P4cBC&}Y$1 V`W1vCaLmw؈hpt{ nt56GdY_-I*uFeuiE_nGVA8;E%C!pnv9 (I&٨9N*XS1lq?%}S_ >-*I#)d0GW{ *Anl2Iqa1?Ը} 7'[)&~#c<.3]Kt/M-;vp]5|2wv*}N{3]i^1]#~#~O8dQсS*1vE2iNZ1rpq]$)lx#G) e:wT9Z%wwupL7[H~R2ps+$h_ di=8YQq .n5WվWlk'{ z2r]ⴓ)(3Ů5DcVQ'i v3ѿN][ Z9R0QibJ$285yк[_s5;NP#-Qk7B{rSO8>sW_g [H i8՗KqM,pp ccV-L~hő>S{CZZGW7FsE=lJdž %,rC#㓂>KՎ`7碕ЊuNcnO?gm򋌟OO!}5a'VϦlfWDԄ jD; Aj5 վt6Y#Op)J΅' ЃMM<-Ú}y ka6[з< ऌkh'ܳ鏒..0}rpYOU'\vi.:Bǘ:_KR.x+-}E]'A\>kY\*uS8DEs""" """ """ """ """ """ ?m;]h״4T@_N`LNsA,' tgL~m*$VL~mL~m*$VL~mL~m*$VL~mL~m*$VL~mL~m*$VL~mL~m*$VL~mL~m*$VL~mL~m*$VL~mL~m*$VL~mL~m*!vc^F@VL~mL~m%<<7k[6mqoN5%M})_L~mL~meqeZqO"r:Zv9|Ïl[&{w#3u%i-QTi-QU3)<䮟[Dj) :j0c,hJ =;*HvI0^I[[^4xv'95.ȉV(($8q+5 jI+^\9G)L~mL~mꩍK,7J8^knd6L##賮EQNČSfuOfuVuEWB\_ YlD7sMFȨ)gzL~mL~mGO Rd^+%ƙN1hnp9Eq0SI.~c%L5coU?5coUUiri>|ᆖ*Yyvryl7Sm˚^i-QTi-QU(-iNU4Fu7HBy:B»^)m- mAnܩL~mL~m#IV޹;"&R%̶>g9sCxG[[%y-W "H]MMu`Cv?r^'eU[cӌd7Y?hGSY?hGV1pl ]!=g2jջ[[hu,+wY?hGSY?hGU[[ [[ [[ [[ [[ [[ [[ [[ [ն[&\cCe\l2こВ:@jDDD@DDD@DDD@DD;O'd@DCDDD@DDD@DDAD=DDD@DDp@DDD@DDD@DDD@Ev\ """ """ """ """ """ ;7 pPUoc 'lVj {|w\F6lg$䬑KnmuFv/&Hnfڢ![w˧Z{E~"$]g[`=C2 Ʀc|l>#F>+YzVs%;r8 ѷ7KJ[7=~@;[بߘZ{nDiʧ 11̅I1+r:z+Z.Un4r@~5z܈OA$I\hT܃qVٕti‸$J_еBgtQup]YAqԷ>)75JS若UUE \4ɂq W%t+Rmu{-t^!#[yr9. o[Q D05ؕo[j3ev$?)} +)i|{pmU8<+Kdբ\- B" ""C}`wYvw@Tۼ7ny ̩?GPw=qKM&[j]*~]TƛFW!ue##B]0H9[ >>/qW}ct%ٚT\ZD@DD4@$g FenFFFGY5misA4Dɗ=4,/ƅT4tʿ!NRQ-:ӱ\[K=\Y^AMWB!c$b1ɏUw*Y/t| Ulk Gܭ}Ml]i.0i$xqU֮>ֺOE4ЗJD1 NhW7R8_0 X_ 5,ӢAjlDDD@D\@$ /7'6a!`\;@Fq^M:ߨcSeq =:w\SgWZ}b+&סfڇ =%q%G&Oo>h(6U)8FQJ:*tC4"<268 xa!_+/5/DQE(RK4GAqkE։3;qV\s׎xWdR֍Y[QYmi8GeZk-pU*X]CQZϲWt,&v+\O堑I |͢"X~q(moRTy\[0H9㿢z0q=t|WV;R eM\-dd;}˽^{D#h$$۠Z~fSS$Dlk-sSEYG6v9ps~Hrl{""DDD@DDD@DDD@DD{-UR+X<7s[#ݦe6CmCA#8!{C7 q#sssz*%O]CKB'l5$8⊶ZuueC=Ԇ#c?џ.^c,@8=W%;= >sUc7z/ZydF8+ɳQلy V*[4nny V8栊ISEa@s]!sWk_r<*zvdo4s  9āƾ3g-]!l$QޛQcdw罍 8581@Z\d`ǢwWP3ḂoUm=% -w %\ڲqn+2[ǗROܶm4v#=8%n\HnC*9N;TEl_i0 X-z,v0Hd4{W^)-]F01=$N۶S+ǙgH-]wmݘHdŁUtӶÏv]mӶ:]=" ""׷ާN=x5q8˻(z3'\@|VVTof7RI?'ׂ8j.Kz=6: ^n"0 ǺsA.9' $rsע-9~0y9W8']݀p=v/!Y,8'.@DD HzeY$ZQ3^3s?ʯ]#0amN:ȳmΊ[%eo38gsX [+[_k>ׯ̶nm*3p>ǎ3u4g-(HL< 8+Z􊷔ƧěrpA9' 00x\w\I+Nr.H pxP pP""c.BY"o:MQPF64=u_|]+lWF퉴][1O 〻>/>znyg2筚ʕLU0:62`\ܽ` áН7$,2 ߍeQG'UȤ:TcEm;k&_ nn8?o9tv/EM;v0:/QWGv K+~n4Lcv[^ޜ-U:~IaM&(\dU9ת㿎Ԯ)KJj ˇܶVUdV: sK݇78TP::j_qeS_Y~c=>\z^㜞i΍6i"ΨW v\.AetC'c,{4\ꌞൃ^0 ];$Yia&9gܤ˃Qc¦h,''OȽتi( Ln[9.F'H\?qGB8č֝I)F}6:|H>!h݌tFg=P r:GjORڱB" """ """ """ iG+?>,V-=e{ Ig #$ɣOtv[k*o \.#Nߏ+FF=F&|/ԫ,֊8a8%vְ:8sՂcV$!dAǺ{SSja;cLٽm;Ow}glWK54xE16Sr?]K]A`:zjK%4qĻr@1;˨䥞AIN٪)^_̖2Iow_+m NS%H j<1$1pss'<;5ΕjRm %v4Os?qfŔy15 em¸RVJםÎOog4줮j*YhDstn0 n yZm1Y_Q Ij( :`4=^ޤwZoP[VJEQrc]]Wwk}\mm[pZS!i q9V5Vhz~[* "`n|PGCۺ [M"8鬴]V_ii{wlhf sI|h4ނ^솮 Bj#>#C pnO.>ѩQ\k3[]m ܴAdu9LI!h|3-ovx \ '[UUmlεhM;ǵsķp3'Mުz=]\HƾOw6,;ov?`jQW+4ۅuQI&`mk uUU/CA+VnY%vrwgeMu |L-GU ָIf@cjKMMZ&l5-{ Q?σNNyVSM=:N((_I2w>ھR-^(hMn# Ýy*sM#֎V]LBFGI\e.onumBiɞ][P #';QodzgUɵ9GE.)FکaVd`77V}(4h7;u{8I{0~/ tQ6o'sx8ޤ_Zj4VCWQUQ^$r?cCÃCzdjKq{+$W$KN;q׮sYn]叹zCoWN7EY[qduuaϠ8o⫭>{5ϵ|,qt_h6Kuk}᥵MwڍEVYdԺ0"J|k{U_EB*}>DZDDyadCP0KUq`Tx°4f{;6ܙNKKxOeˬ9_1xd؅H'}-Ίjb{dzG-n/dQG4{fK\{/d:L)*-{t/a xvԏO{%ZoOf4;v2v7?3@s?P˃P&?Ȳ=Ai]R]c1$~#ăai6Zm}T0ALjMoՖJŮJڪu♑Q&ܵluf</vm1sh)^<#'Uw,wVRLa1<@i窻{7-EKWK5<0p-#yw BkG|J8 `o!p<`DDD@DDg-5I<: J`!|V OQd5=m3e]-ia{;VeU{Zn2AQLA1zy-v šh=' GUfvW5 4kh lr9pKG?dv>OeZmv<3Ԇ#3?dSUB=ߙmQᤌ4}FެQUAyw@xuKl:A)e{Oo?d-WR69vcph8ps꾍Ǵ6:iǼ2h-'cq%+`Iok~ZK\8~?W߰ph GR~%u]۽3ϢRD@w< at[]3W zH+kjsl[ML٢᥻Jmm Z*ZyHA>JSGKqiAMPv2ApOLq~Ou5^ ƽ@ x%7~y嶽ˇ:u^Eu3A+)t]zt*tHT)ꢷ\IT$!k88vk3L_p7憖tiu:>;;c(P`c9#YşNn6:Yg$i<ϯܫצQZ c?D["% =h)-|8 ̎'0wU~ ʺR+}2S{9#G?6RVrJIhW>㣙-Gys^poX$w돑_Ah%֚_K>*TS:#&g 46IDcM$UX%pH/>]OUbs 2e.x;Czycaܕ ^6+kIeXDED@̎]%3#t-2h^o'h8y_O6H [G5OvK#;]  o)e_W}5 L.Io|3{caqozIX4TCŧl,sವ.. jOz)Kxlg0Forj-=KO+de?ELЫ#5.(e] &p[I#:cсVU("-@DDD@DDD@DDD@DD¶^l?5)E PrxF>* WM9 xJalhۻ_=..QW>s8Kmet;{Ygᄴ=9Z5 WZD6g8B[_8y#{-n6x9.t~ʃQ$3q -r.Ϩh^/$nݿACGz|Q<m^ QP" ֞C˞X~fmtVi*j0dx WݬS>AkK6Ozm7 F>w&!#GP۾Y,[+)gl1$kH/ZSUqTN8/sxigG_Uidn~dl}kZwS2 qHA`)] LZnb}%0psKð =UL9ŏݖַNܨwzYcqH[psSvO+UwQOi۽5+k${$gqMa=d57Y׎TvC@hu\`sDzm1ڢ<$e,vhO{)%d9-;UYگR%&Zy(h nێ:=CKZywz驘&{}ڭ%5ni}ɲ՞׍'SCn:[+%.qܱtՍߖSap} 6r.Fgelḕnr3ea/m;HLT$:w\M>juhDqUot5fj+-$no(gw 괖&?1)uuEEASE=Iq-h./L㟽Wka˰] 9D@dj&!#c-mkjv-Om|Zi歔K9`-!|*4589jEuo}euF 3r>:uW6'8|L}[[ڎRG^j10pn>ǗEV%Qۃw9޹63yRSmT|1h 7겭:+QhSjґz<,s_J5ZU~T\|3mkW> ]s:/5( """"HgF O\M>ߩ=q ,,݌cbpsSqo߅ӗj=]* k hwN~4Qfw l>xyfP; {gFMvS@?s\sJ'dzF+-VRHH>Ϥ-ډdv&`o1swzMwe-S;V_BidM(Ih `sޣj@4uKjctN廆{ta_W+aRKM6xCAD'( ""DDD@DDD@DDD@ i=-drK ~̭c?_5n-ʺII41R.qOyGE-ROyx<4ݾ]گª!n 2|;(&Ҷ3kdc<ߟb:_|5 @^h($mcc4?U@k+J;.֚Xc |ԯ{\n%=bt^*un/#9qxNK|uq>y#+җ?AsכΖj*6Wl3qTKlwEd kÈeM6jNd@)`$?XZRTj[us-db6CZִg=:X:oNrRpZfM=sXhS6ߙrj RWhX4Μ6rL`ӌ)+j4vGJ=9[+A #w=3Ǘ;{m l'ˀ?pKsW[ e?TۢnNza}TF6{}CoZHXis#zz(L(f{cZYaP@^-RۇME)8`0A; ߠQ^/ ch{XO)-M*O NZIq01ǝޯNʪ[MAzV2zȣndmgBկSM\Fo,k""܀" "" 6P\cr-E,>ՕZD̘Éd_9ݲ8z8zU޶s.85 (ٽ' *]7OuPУk苜w\/A@G^NxmT5^܂z[%+4;gc@VwFRJ ,]ˋ(h 1w}}tntpƎkfgնoI{_5I+b9vTGM<5HOg ]%L^$1ڤ3`EuGk*x$ťҺ($0i4ƥӭg8R:),Ĥyd&DWM+*2REeHPut- is#-$Ĝ uUV\[#]Uy\K -Ko6k*]#$7ͻ-$WbX?Mmyj+=Lv /{%HIz*JunԔL:zmő1q kyr˳C]Pk!<D]dD@C -<񜯡- eO=eU](*T ['OFeh$cáS[QD[hl89$Wt5L^Yu6Wm**z8E*dz2K#99;`ajSUi9첆,W68bg#]s˟R6 _y_tnN@p?[KݦOFٙLKtc\\A/:}.)pz2_r;tSS[ai0 c0FOJ\rT=ϕ˜rN~厽 0t("-H""˳WV=kj}e44͈2CZx*Y%muӹ Z@ +WGM~p; {dKeu- _LBv B[4p:yow+F6<]-bn<`3HWXM{xWOdWVo::ko^ЭO:q}C `[Ɵ\_/!In6g68cdc0ᗷ%Rr#*Ak|"Ils b,4G%G$*@C-4πӱxMv)) p)em%mMpPV\j#ZkI8+utG).1=Kkk9pOsImTQEQ%< D J=6 iUCgLAIemWlq6D\-S՗@CzónyDi*Vǁ#mn-h. HjJگbx {[5k0Z9 9xǘl<ߧE mO\Yd . r_h䆵\G\9஛c@\.A5SҝF!ApeoASkPR37>2yM(W@SHṭy[ 3'=kzqvhJ!0" =ZI7&Bpu+{͏OCtJ%vc]O0?;(dsC;**j\GU,m|ߵh8<.MU88Y dZ}].vZZ{T g@ߞ=:qSVN/|%`28}~*!jY;p;('ڊ9J:q X񜑞Azڌ2~4p>uT[d71d?TeM-_f!=APuS7:7ݤ񀈋PDDuDOgm]lK9hZ| ! . eà=B7rt4X`ܩYAOFҘ$y>8ky?gܘ.i[y2Ji)#ZHg5Jult1>YZݥpˀϑ3[CYu rVN>R :a0n~RAj{/4<^eKXi){W5- GïesQ1G+O}QRȆK8אG-:vmJGl'dxϔ Z\?)ŗʾtZj`Z֜2H[Q캺HګmPT*&9鼠D^jZ#rNYamSm}=tbxAcS 4j'ֹ4ɒɃZE& a Kr@ETTvǸ8'8k/qTwLlUDd2NSǯܿ1DLf?gck(W0NGxVvԔYw%Ź'8qǯa*ع{9NSՊ\<6=cXp`CԨĚS-delTxyi?h;uYWaRzm Md}0iyvey#r3zӘiurDg+Zq˦XedS7dki> XtPI[-zjji'shw-X.\ z[–h)kfo#c=qjIy:.u? NxcVК|i7^ΦG62H2֜?UКl+ KeZ5r'U )bU225FGǒ+uE]wO33~&Bj;VJCdlG̓@yCW#Q\+(UN~ږm ثZn>Ɂ>TJ馺UWDas%8my^M]M 8a%38a932Hߪs0[p*C}]뵅5IUF#t, $8u= ٧_!CrHn7jڈdvY i8#J`tr[uURP3azr>Tt0ɠ!fn9#S-KZI9ԭ_Jg@y#ø < '{c궍<|uֹ۟U3(ߌgmZ` )# 3=ASE/fgWԲi @>^WI>h&G?}V>KV9"4+iOY;O! ן#2G5֚W WvwVmFt6Ʉm3p n $zhQO,0cdn^}UqW?q<椻D"ql3g~Cym=Yc{-qFؼ.}F(4JkUt9 L5Ǯ2~|4˅d2{ˌc }T2u2n;Uǐ5<15#_9%C˪UT.}l͋yp<U]t5\j6Hćv܊8EH ƴO(kNZ7&9p#'*'-};ϫPޥhi8ڈo [zIъ@v3 @~, Uxnk%tg0p+{0tʚ:jMkY/;v_O:ʴ΍o6moa9l徹_0,vrCOyiK'q1KWIwϣ)i5>_ISQύd,˱d&uUEGl꯱GI^].ø~Dxynq8U|07K뫫n֖Wx{~̖_w iy`{LO3*]Ԏ${qs绹qډ+ZrѸԫ˦ԫ_׊T7:29`S-mHBad9>OO|ډ2;瞾Q\A6w sF֝;ƢMx`;v73[G{}cKYXtN8-ѕ oiw;z/ xll,c cULQ<͉!p+h+Jg%[pz ""%ZȫŲשpI"[U\h; n8+WCd {Zs1MKG--LsM"8ۏSa5e-,P{ZC7C\9|J˖][^](n4HUA=Zh2B^YR NbG9jەMi쨞GJ59'c.e,ϩo j3#MJtntbG9!Nr2~PZi ^] P1H'OCԅjj"ŎVHrw58gzWS#xkmY.U;( 14#Fַ$X>cſw*hOqOSPii #p̓xdG < 8 i$13qSR5=;m4a6˸4|:/$Y#X#i}xeI'IrsוItؼ5۶Ye,o| |i N$#Sid- .x.&٪U8*XDE@DDvDՔkCl _EE`5%̷]0zN+3=V얦WpZg~qy>ݫ֯F,v8iI/l^ O$n=B沑ϡ+l,Tx`nz/SPSk!#Ȫ*ގk^Z?J}唖0J)/n+qyCO4ېT1 $䜔^Uk% """ 9U7+*m9TѓKohq5tvS\]4*vܮEm5<gS˂Im 3uTj:;+*[#1yT4501eDd̨F!p˧NV;7󜯇-WdJBmj%oL=N{T6{KHHYI3>`K2gxq\8;YsO*E-%}4`i<}~+rPR 'A Ym I[ndmKϗOT 2BE`È v;*{uMc5hm Zk*ɢK[8$aRi R2[,99 )Z+R:CM/:fz§1+;Iacb{c#<v+QUf'"- " """ """ 9h.8h? cM6̖WU447vq~ ۛP&4#zcSX]MNT5)`BVimE5[-<F3#OEXL\ 9:Yj7BO E?.s""" /hXry J+ȬtE R\5H̀pvy-DN klCVv4W]dҢ":B" ="glfcp<잙\HLSe%l-eHY82<8#2=i!N;rd#%\l—>%]Iwe['1rI Cu\55җ-\d E \rTO;a;kr ??Erӑ?af1܎ ?5eꩩzyY s$skHX܁9t""" ;FSkNe.[&Yp~ y&$8 `ݟVWD.X6zjacD3h$ex}Wm.= fc)۷˟?QUҺ7=rʬtU&V=6XTZeLpƌ`V[*!sl--;I DEt sy[5vAp|@`xwBƤr<0 .v32~ 6=\OKQ4O2>(@2H֒$<'c-5sm,ڭel}{ۀ^w$#};9YSɩkdeŧq׊ C\ϧl{Cۓ꺓X4zj4O{p cz=2S4 aèϦ7觎X1/0!d3 뛲/ x\".""" """ """ ""@@a|+ٵ+ݹ4 ?*痒j!qzeS݉$-ԓ(S|# YڻSqM"_*-Ž˟np?7q߂{"_)mOPW+ ݠppmz/Si{=CGV@i* [}[d708'nN.B9ݏϐd[ N:ᣊXxaぐ10 U6-fhcru?HKǢm ,v\w9Ğqװ[%‰ gk]#; 1EwEj=Ȍ}!/Gj[DN*]'폐0|cFh5Iu¥3{Sit4Јf0q괔򺖲Ps푺6\7?5yd3g}PR0VRVxiWsx9cxPi֩OK=> $$$J}4V5yq#ix8cEm^~SkZ8$>O^:y f8-a4-=](UHå݌\ Ӟ3bxʝ'R w.YnA@?y%'?Q+=SnTGe]&!`=1ZO] KiVL3#,,cO8.w[-¬FrhiOӴT6ª&9Zpq};́ۃd88N|܏ҟX:YG}*;}=s/qI䦍.&~>bRL"0" +suR<,I[ D]aW`uR &ӗo}dq }< k{vc#> T ܶfUSTpecvݿUE4IPSOR(dɌmQ5[M5 qDFwUGk+Z*#.naÓ .FYg/6ZN[,ɡqyG 2CmA }WUKO؁pH3; =޹G"E@CNiI8ߜ_FS6 Iq.vzj8G8=~ۍVkE(4Nt,cdh.}}UhXuO0vs t{v![%KldG,bݟ uzҏjQQMU$ld2#8-$\g[Ȇ23-G EWV4C y]4בY_h,GIRJ92G2ga9t隫Fjiظ: \[ݵ ҢBk 2G8]ge{x_ȪǑb}O=\T d cj:[kl {!q [yt^#z8a۾ϿG99X_O5 \5vK1'IXUiZnU&G618krq'RT}z:JU1CrF>K@DE""" """ """ ""2vm1rRTAt c9phUR\4S3o:\˧M[%]-:6@H:+E^/W Z(vHɌ~ỮqOUmXk֒T=LdяL]3[-ͧ}2ZlUQLj\%pSOCys<󮣳Vi1Pp2츩VQ(@KW>LF}KimqهRA^Oc: 6OR#|7&\\pr ai i lvge]+̲W`G_csd ;C98aEij M+k4grNp =ڝjRB}#ӲzKկP:qxN]Gki6=r0ќdjիlh-O'w.q{z,VcYxm8mximǩWN|t2iw7Kr3X^Ъmv55o,ݴ}U]q)ժw$w;#[~%}%aӶXu>=튢XL9i^pF~@/i+]PRWۤDśqk;ݴUEMS=Xi%ig>p0zA^t$-kmYhajr8I5 KN~䕲t~ԅ>(̭|Fp3{}= 8(\Owqrxg8HcЕ:%i51SDw DpN8Sjʋ},Uu@ _߿ޯ^ʥ<bxv3<0Z:a޾T2vwUWd1kZ*`.&7N<7q#qӃWX8I[;h 'eW:Y*s`A mtƄj* ()Gw  zziwhyL&f{(-u~ݪwVEf%Ǡ=E#dJzE5jJ9B4:V2C~Z֗{a^#9z~GIAIh8DEpS8}_fL|xrnPVintv}G&:JAnd bsKzZDI̕Li/sW7P@xc\F[ ;$LNiP:+Q\}V\jjj*c|cO6$sചZ- sOq!]UGuO*KкƆNm[Kw;~ 7s|T3q4w&&zcpAڵS$C8gsTȩ ~Y``coeQ%rCI$\& /k@DDD@DDD@DDD@DD4.p BZkUGC)k3[=̟p`?췲\bԽνzNO f34 U~JY 4TSfz87^iߔdOl-S*ײGg9-x~[%8kDv:78n ꤴ|X1tf0WkM&ڽw3$lLCہ8.:TaYLdp.qA=O`# l~&Yn*j1RMq++id|^*!`A%w9*?u^.A5k ~84d06a9]H5]} _ֺ_W WXdɏGs+]H롮d219 {Nፀ`;-QYGH>kݲ0 ?%}ym֌+L̆-K~EIp:x"lQc|* `x}>!Z$l{XbvуUرa7߿* 5^Y8•4T gkԦ Gu=jeBỳ:.S""X p '_uRG!lU> [5}])uWQmsEǪ2}E+%,aRov퐿4c惵w->w}V]7Ty7G:owoZFGS>G#5>x9/9'Z> pGT42E >=$drO~,.e-/mȟ}֟NQQ<,t@F?u% Z98Gލk&'De#TCe i@d;H-q}}Zk Jdیn~2z7>k_?|ѱtuTz d} oUEɣczE1E3q$q:ZGE%_к1+y`e`9^e) u~<DRdsUzd xҸ6v &CKq;\G/GRSs[uVT+E#cl/7h`k[-Zj;U5,s#!x!r7`=+sKqf Nڎaog.\n#ڇѝ4uϖ^iKJZX( HF7Y0# X3VƷrR_Cin6TX*&}#9X/7{ +ԦlԪ D >y8q=㷢mJ~Iz}8W#IG_m̧SL6$m, Si5<1_P\e{–VA!- sR ŠH~&5WGzqOMML@qC@@Qt7oNK2Nq"t2q xqa] xY$/Azsʘ1DZփ@‡8`Qg#:h52<ǚ-89p:idn[9Vڣ:LQGH`psE)B#ld '%+s/6Sr۷8>ʟT:al:5D, hB2n2:E Ui퍖tG-3$'>gI^$-i{dWZQL2s7 w?D=})etO,FI8k p>OV^ou׿?5`hh@` :)d(dNd=O, FmwUurU^k^ȉ K:[Ӝ+s>&wOδk}IcLicd>`JT"V55xkA:#iN 9u۹zֺkt&)DL h?8 z#۹=W ׊>)0H@{2)^I_hZ8)%u'mf[+CZ~&#>C7QrgUv7ݻr٨ .URA;Kxcp:e~IS;fd{j"l\\F8;M1ᆸyZ1bGh8?UKZGt53B-ZZ#9,x ޘÊj+QԶdb6262F;~*UNS c %$SޫKEt=/TazNNVR5րs+R*Gj.k""DDD@DDD@DDD@0$gPӼY.GooEҗz)*KЌ }$N8%ɻtmm&:}OW)&*j#ًxfwwp1,iB= k/[pcC_蹤J,(Z+1K)8O mbW;n֙\7ufk$9>XAi$`ΝS8N>4q /v !v3:.wɺ@?-ck:BQ9\w9JWH1q1hpwC> Xuݽ> ɷ[CZmkp[)K{L Z8]/{v8cBpeqds궟}Z@ō]rONƵ얆 O+1fѦ$trש~(tB2Ipږ hw7%ǿYQ]Y^P< PvR]Tq/?]sCN{s 6>7!#ڌj`%nsXz)sL4hh?WΚ-sApGAׅWH+9hg3.q9c# %Um5BhJ2WGv34e6ʈEY#vrƴz]Dls0g^fi3nsrsqF|xf7v;ei#adxq^ݬǹ4v.]ͬ|s௙sç^<{繃^vV&aH#6Q=p{vu n0Wͥ 8YX,{G*(v?/wtuU 3;kÕח?lYvfLeݷ[KdϔfUMsWXĔ233XRGOQG+XsY[ WW_m\q39Ŋv塶jn?CX[IT^ߞ:c[\֒f:j,KO$u\s7C]])фcd-#5G0')>AVȽBs=c>^EƴH-N(I2@^=뷲HpNjrG,WҶJ7|B`w%ntPۛseuK{ykxRڥEJcEa5nhd"Ғa#;z{/ Q gBG#?yY䐸g]H;O喪Zgo'=7Q#c p1#Xjqi 1zʰg.gOBn ;ᝯk?ԀFϠaIt7x=vp>}Qh9GcNHRRcU5U|b =3u\䒸^?-}"!zgeq-`;'RmXqx 6Oe5Ա'>b90 {{%k?F}6ݑOa?WPe,mzm#.-Wfaр6Βj8~[KZL쪬ilx翩`Գ?-') uED98aÃO?%WlM~o8ӢIr/RqNNsԭ:"vǩZұjz=hqD@DDD@DDD@DDD@vcH>miZ $ɗxP===%-!ïYZ6:{UT5wXN w}Ğ9 KT 6Z'B)9$oSclt3uxu NAp$T5WOJZ8H3 ]CSM sI~C7׺nM$;TQS\#& gIs=mZtk{/CHvAu*Xd:w9OQR~N9gƊI,{Cw~=Lܩ(ML02sץW};% | \duybx,n\CKH-Er6w!&nǑQϩ9#v]y ^ .L> E-#s uAUݛur8qp1(ζm+dnq x•MSݥu\H9q68nJdSx-p5]٤lg>$d-duM-A_YITm;.NI ~Flb=37VUT@yx}=LmBqce’X.t69pLc~ CE{ud;]C!9>'FWEpxEv6aܞ=WMhU]۷=0?)54x{M%=06a{\FrKG8)&VұHs tY۝Hy|^!Xj@DD%I?5"DB" ԃLCYQ4X4;s$GIA==Q+!m\lnq4gn9UwJ+ʻCg[]Qho/izέvY!۱鏏ʸh-6*. ҆"sp3q#L/XfYE u;?IC7aܟvDZgulgO0`oR )v+dV#܎d{Q_G'ZW'|y=N%'$/q{X9v08 G5D-vP觷J*eղҲGjyFd8qq^]o`-%LF"Z]_$zrJōIic*\&ZxVTC;m)k|6p~{-H׾&=,}SitzavH8|aH55$O)knr}~˿fAUTGM!,=;>wruv9n ͉Kt'de`iZM!7,pIKB#̱JvǂN?SGFJɼo;8pc'9Oxjai۝L1ҹv`xӌ%H.g\Ƹ.q&UDki@G]U,[k0d'A~*-qldEۭtO絑#.d];L{2a|u-S#fIOTc3H"""" """ """ ""3z6Yͭ{}8]HxN45NZS2}zosON9gE{s(!%3nqEd!{=@$)=nAA\ h8=WKukuT5k:|}[@F'[i[y;v?MS'nt`o|zW.FVVFCNz츨Z(+䮦8TRƋEU)Jv>2ݧ(@DDَs^8v*uIZK㦑8m!|Gݰ[ՑSv82@krRRѶ* <7m9h/ۓLwwswM:joֳKGaXպZj{}-S&3dx93HI~+20pqCMaDV<7q),n5Al2*/xឪ2 sqsg;}r:>@ op[ KupKst,Qg/E#rǧuRۆzEt/5NZ]^w,-k#v1c#l Erq8" < uJlZf.-qxnw#>,? ==2qF+Z:žmU$ǫeOܴLr=>MdٚyKqyd?@#R .$NOM]kEM<63ZӜOXv*-/sk u[_M%X%xhFt[m *JitU> >3Cm5EvC_I R֑h" v^Y6mcRU61}\ cOqPH/s$.#N Y`PXUԂ9֜ ?{fpkkA0@ aC G=%qsRy%YjsjjvN)s鎬Ķ(n}P#N^8P>AE) [CO F.s ," """ """ """ k4VRs)ay#~t@L-*I榆ZJiÙ;7';.z6M-ESdq/|$q8`;5lk#ca-$}:}2br[xx9q.OP9dW]lWj!$_O]~w)CA~nUtUQ(88ai`UTGE37 =Ǝ`uu kta , /VrAeT j*FCd9aN>PׅʌǺꈪm 0pA䎘?? 1ɻݣ(FCOZ[d?Fb:9*v$wພDDD@HP m5£#.&4st֖q|ܷv[@->W SUN$6F83{@,KG[ ULCLlle9()^6F@9ZpzwiQR z+]Wnw^:-6 Ak Q=ws&*_quLyah#Ӑ{jgPېNwc\*w$ {{WdsˈI$x!] Hz ]QuWHcvDpDuw(n5tb4c=r}~DF騄RÖQn.WQW1Q˗ z|z*sż l7Z8 Ljqd4q/;[?7tfFp8e u~j'͸qBʩA5D1:y*,[>(nX3\9\xl i?yJO*I7[<~Ln'^yܦUZ`E9ih݂qWs=M7W6?6\wPj櫩HK0IG^nX&dW-{X<x8(""" y%m3TǼw`$:V%i ĕʒ8<=m7|9*/fxp?W#N9#ig* 0ˀhkFpF;\hݫ9L;AB 2~ $9}M,5nGy=ǡ[ )=K[7 81zrNJeݼ/U $#Ob!DD$\iyIqi'p>DUiQR].-)vôxY^_(RhR&V;^:<NGcFbKW9jh*!x vF ݜcyRPT:MvWc|%[tm~ԚIn1E^BgӪϗ8""" """ """ ""223 "D l4q 8DD u\|\`""DD8&8NrLg\5.N9#Jݼq:"'d """ ^˜1!" ""QDDD@EFJcO""DD;""" "c9@E˱(\ """ """ """ ""ei]e!]t;d ad'2SJCH-ޛt+- R]$g=?q+SOyeL{,imb2b;ǘ:^LZDI sD:װXqQOsvBI{h*Zjdv$gk9`F@ʶvJ^bX0 |YtͶKпݯ{wZ]BrTEw_T޺=[7 ;#$2SI9i LsԢK#r^-+^ѧF257>qtX}ÞS?ݼ`pn:m\kljjJ9IdRI@g(WV|-47.@-v`"l  Ik$1 7ǂa!\*F;sRΣ(w* q8>ϖk4A&2h2{zU5:2(imA;|W֐q*Lg c03KjڡSJ@&O/胀e'衺ez8l`iAkjJoEZGYx=z0n}Ǖa[mqRٍ %HH/cOOBzKPg@7xk\Fe=gagP`ʂzlE޲SMXiL#ui|`@GHr=VPI[AYoRTV$n s@ >l(Q`q׏"Ykm. Ð V,Tsc2ƌ~8e=ՄM(Y |su p0A=J5zvX]nՍ1+8]:=X0=~3ӹyp-a9X5چ^=Ѳ8d5nIǯo~-TFpAW;Vm`ڣl2S9`'' emU۞_FoEw^Sx/ۜ|S~:}ξ٭䤩1x_Ҍʢi {[)\*kgwgO#>ȧOAȯJz9K j:ltlDkG). 30dx/\G[A6S0)#9p겯n-QjXctnn89<~*+8hzs"0NOEè¹ZUMUF~z.<'*ӺY-qحDL4tҏ cUtV>Q>F4`4t>W"Wg\{~ 36~h ʵv~L}VMH#fac8*O_",ph%T\*q 5 2R#CDF8PNڭU/&)yi^6쏑Z]LI!tk2@N0-aY5[2`4{WUTlr3lH07pݎVZM)/qQ`C\3{I9=;_-ӗm\9VnxdRVm.81}=m%uIQ}7sdXݬpPRqE%::2"<>)o Gdt20x;VNOg m7>FIuRdL ?Gnr:2UT#І!K@]q4z-FCq5kD4>puGR?#:.Ȋ@lnp8·-VQUCG#BZtBz<ӊfH: G4Ob]BsSx*$xWMLKc5-ciZ>zZVRQHIY8,n粈u+_VZh &Zf|M"8PVNy椧cȞZgɀ7Ĵ3ď5 BspdGUjGGm6 6T:9 ei'dPn5q奪1UUX 8*@8UIڦ'T6wFx%Ꮏh)Gϵq?]k"Yu9%ܮ1߅=`\H¸,nhhi9?L+5QmS$tW)OeI'$䮜u'pHDDD@DDD@DDD@DDM s:H#S:[+)-~dmsr0J3+ ꔷIrsOMLK]u8i)]px XWSfo_`Q5z=Gѧ9o;-o aR:zSkttQD]ܨ5M-|-W' AW׎'Zm2gI4;׶>)zZ Zkj%^}[&"NkI#3W;-מguVJ A!8YEq]X>“ZIOCfc Do8=QەkT$[h{- - ی秦{ #]֨ҳ( #VKLKHڈo6\8FvZi2FN|]WFz8q^$/Cg^4Ԟ%X5%UGR,s{Pk7Oa2<э!Dwadž?23S7"yt̤m<B|H<lM-;bHg=A8t7xWw@n70x<`YXlt%1T88`okKZzeIeҵM`au^ N iY:iFri|dPJ9sCya I- O*xqy $RO3U42H^XvX.v'hFTՉ,k ,|r3 YeSW,d0A?aq۹?ܣ7M0 .6i,o^ERyii&Ukjiݩ|xR=xG-cY Tc2Rn r]Tin wE=cWv{z5dv1j{ SS4A/O|ܩ^͂[YM(m(.$cqS[t vy|?VG^?f榼QXR)kJKwIMCA;?ݫ. 5Cve~+v$l=O/ odIs5ciTx zwZYYYlHk%7 c%9c#-= ?w$'DtÔOW`X1Ct2Ců8zcxӖf@|USm4Lh`^GVT]WE7$ٽֵX"l"]IC7=;_2\K=utd 1;Ϯ81y$:gUTXi₞h#&Lv#fqQ TU4tHs ܬ621ax9wBâ^滙.9I-fH` cAE-f%0[ SὍBDdgi+ ,OTc $ZΩѺ^Ɲ _Y򲮕񺒝=cnr@9z~ $)8zrnf,&rVY+ m4U24w={g G]*-Tl`S}rr96p @1$ 1QpZ=<TQԵr|6ǯ+FI`cA5N2pq=cUfxo8%]w8c.hYJbA=me5THO:ζJ˅AdEш }jhl﨣 -r}?Ұ+#7@z*$Z?mdUMq9IO%EC``%若66F{|)M.k4r<뤾/1 7o^]eSWLIO dK!f^3q9 >gYfȵ ُ)*jܲ3ѹ6I}׺7n}ܜ;}VYRYn6sHv'a9P.0UhqQDﬦ1d8cYrYkÌ9a6 NNRJ 8C|iq8 i\Rҽ=5+Jmz֢OO){$~ZBEOWs@+2 %{:Zuu1bel:zSzgc ݸqTY~Δ|MVhY4n=$8'W ƞǜJs9;(dG >W6OU}O%"=M5T_枖8Mx{2$;^X%$LI۟NcIWV==tTxnΠTJ]{1 ۪UUjh)b>GweEV(8 &9@; !wSןS)QM/̶5U UO!is7МwPuG 6 ݡs׮q{Kqz먅h?VuhcTt)XL#x;q {rez1ʍy zm+hns*q-Ӆ*.QWR)wH{^gXP=4&'6q 眒{vQoF.GD-58I"GF5rPIBjvB[#A|YOGTc=2[mE:ՑFևI 몚V[88 8g:(\-;iI2dTdުoxr^pzIa" """ """ "" 糁;l;7#>e%3kmbXr jh":w46f~ZFk ^ 1@\س;{j4NGҾ{VC Q6xn-e[ŰҶ9`W*KcP7NAr7%cj+La J.E'e.sY lى3k5Kƙ!|g.ʨnB sw' ҷk&_ 98ɾYGrXU> E&>j)7P0;u0C47" 0H8* h&lZ ?WU\0xl;ocrKX|})Wq}3OIc4qS]\V0u~vd5Ε#Ĭ +枒w9*} NʙhڡmSӶjfvTލ C sOnl7f0ws .Y[+h6`v@u]T:i{ n;GBsnVM#Qӧlfx*u0ñʗjɪti^✇Os*ҹѵ "kT0=0|h]RggZT.*++*cFc"Fd%VT4f/_+5eZ<C%GGO@?8a^ug-Z;)̚VPWx ̒?K}yQhȎm1 շ.75-Q+T*451ypGs/|i ayft;<1orǤdTJj(esd-NVo\ xE 2}_Q5YdRC[Hv{cTpMɔmq?){jvzrn`8%gĢ HߠAjuU΢ah>ü0ĮnrN׆4Vڥ 8ٞƕ}=Alк7g\#V~cE}SMp.zyMV]qxv hk:fKr53O Lgh$mx'OLZ]Φ`\CvVt$^tumꆖTᷟ.+%q^ZJ8^ F<@Z ra~\4RTxM[ԗ KlK lK+7j6 ':gt& _BA*oU$Icvz]TV# 亪8N3;i#c̲LUn(ée{% {GܸqCIks)aLXkHq>QKM ?գ=AAs[|X1YC7cx2M绌5ImO*$c o?Ueɗ]:{Ipoū*!2٤hhn:X.SMKS|e=6տdw+-FsJ&:ڥXl%%NfրOX8)eP66B!~θqP Wr)dn\dx 'j f?#q9<:|1]Zhhoo=?w=-R`s1sEN)Y;[kn۠p 8F M;rwq:j${/{'>t[tNnOb}'6/F4^p@nuPWkCی;(eUpT=nˋuҲ3xkݮ߯Y:KtJZ2ʙ=" Z ~~[O&6h` Q]qeXC}8clj snFhvAvt\A,~g"^};DfQV@ 9F?[=uoe2`9趵:_,RV y;M=m|.w/?ouCgVOe5L6Hv4ZZ/h+4Cscf39?pP5 |/+Cm cWtIƆCI8IUh}{hm]=3g2 4 }~CnSۧ>|kFp~@Y!#sKLmqqwURd3R #UG9͵ٔAe6 ?|m̍vqc-:"#y h2 Rٵ%U5E4!zꫬJfit,!OXAcaoLkc?v–9:zqli^, x[w M `d0t Xm42YؓUнUF׃c# ]<4:` e>{qӶ[%|k`FӻOm,8͊pxp3߷B̪nO苝VEƝ^ )X@$^nWeVmn%sM@SVv8x4t[$q.  eI5 i-ۉdlo݌Gug&3O}۽׏9XlU;4[Z2 ɢiHиw9̧xŪ>NaΨ~C_7?W} =UTsR8oL*:(Pª3+#.8_)uڰlڝύx0ߑVJ*-JK#k%cvؚr:T4fQx#v1m|2]ט )qH0A;;NSl(5?cZHcm_!7ݳ>Cq4Mh3v=+:?FyeCK`;?dEkB뎧஝=QmsR#vd9휭%|r:HYk]:u+싗8T2EM%|kN]#cdMs:`vxuZh0TFnw;H?TW[=ke<"z6Gd;6eGdnzD n-3*E4=e691[oip}CK8 h{3rƊ7g.{>k==;1'1j.wO+ҍ/=^fH pN[,ϊJ V-#g1.qs_2dコ_^(.ڮH!kNl{]mc}IϏ̮ʥӶ$V=<-Di5H?DMfc$WL&|c;p WZ[7''d%M˧ $9smoblky#(V*ivܗډ5}<c'-ڮwֿw5]v9+^lY_i0Ns,Eʲo}f_#ns9RUAXb#>( >þ%,O_ࡈ'xSMW\ p8<=lv\r,-ײ9< uDdpcns aR* 5gOEm|ys6o*5]4MOnTY#,9Kt:1NZGtap<~/hM|*tu> ;FclKS3# r~jNkrTP62fZ6Pgh1Ag]k'*,2(W٫/ gs8]Βӆmpx9ULX)JG 휭dνIu΢!•'rF3OOMvH]Jȼ(U黫c۸sxQpQv>$gT؎f%k ]z`EN3LGcu+jYDDdg˦S\<9h>uVD׽P6,:G91#3ԒOQׂ\d^'4JYb2CimuP6hcyxpg11 k&\DvB0\<Ť~<62FnNqfn i]+oYK^Z2r?OgG|\1~j}⫟L{^#v9!l Π< 9 v;W8onp+,2 `8Y2i3P)s]{eYْZa1.3Ԟ{YTFI!Ӑ`gNRSDjZIw>OZk3u;C#KX[:-CM:xyϮOMVQRq9I-OW#y,>vUm$nd79?{Jt!'(Mnzu٩Ժ9=^(rVJ$m,{a FxTpµiEkծdUISI6OY4rx 9a} KKC[Z {Nx~x)9|UY'g J Zg ǀ@x`'?eMTי5*%ddYE(<+"Oܨ|'Uӈ!|v~䭈P-Twway,}A&kៃ#1'8ElQQ2]V5WRl8p6cQM[(g]+nSq q-v8-w%f =?5s29GMzO+1#w5u؋w^ εi,t9EKt6Z+ݸ` / ;ΑS1_Y5cM.rSm6Hm>Te;'v'W_( gVjfӒSMlrm#Zt[6CLE!|qv=9;ީ{{Fw>!cB78^uR ԨnVE &16ƗH9R;|U5S=w$Skezg$Q+ 1^FPdyxt |cnӑXț#6G5p0q6GЇ\_g{۰#Jw"%FW+acO&wedia>_N8qO3p6/+stAbbTbH5 8;r+,L'ޣd=Uy/\ɰNA\dM#d vS1#=xXH#惄_.tp@~k4{c=)PJ+=D]BxX/ȇMrަg>2&82ahܑ6f=KyX~A/ʒW +% >BGb"lC"Z%-|8G ;`)qˆpAGFМUXXDfC[<%cedLbvE;#NMRp\OuOS%!>#*EPbNY!ߞ?Ouws;8! $NIM#dW3񗓏yC&zgUߦSUEmԷn}F[ZU Gk\ R4.rOO"+ .]OEOVi)땎:>)q .Y[=N;dsyVQXmKj\ݬw][8;UFg>P7òg8cc\Wdp]MY4VM#0vG9;#J朌}Cgs68E.1oqLE=ʆsX(A249%F:,w+ITRJ]")$""" """ """ "" CK]T;du,tN6eny6r[\)--c#xk+!HZpW mu9]j<`ʽ[ʈ28[)*:cYwT@ɧSISNg3V躟iKmH|r<'71y$6;"mnXդ|JI-ߴ;G;kH'7sKx ~+C5iq50yz]sNRͶW:%ᾣa<3_Fm;_}I IAt~V~EwgUvNǾ;xhas. <:ZǟLVL5QTy[M!%-!~Or5U5^<2y8!ZKegm9Ym@ a`v;FV>RrAU^@"_ pFz9^QǾ$`hЯbՔ:'QQJ+3hO.~D `:z5O]I\#qZ0~i8|LRU3a'>IlRQ39s"2@V*-6\lfK.FZ׸#Cb[SQ #BI۴KCZj5 k_7xdnsPPk=?q\)1|1YrUNҾtUbQLi4qg%e=4[iC.bUnąߜt^ӗs&q}Gk)ᒦ4F d80ssGn{7wGQj'jJqI|7/.dU _k_cHRPY.4OmFBmkK?ٺ裆EEgfN\ .YzBUGI :MYA>cU;g8J8CX1**JXj*$.k\B &EO~(+}5s3U+ca|1nnܖ$Dn^+kiBk#F={]Ls*׿v5^9 m1|B%kic#;?g =MSrctT*5B[Hn7OZoQ.8xkjD)^e-;K?zCqc=\n./eNvA{keD `;00@j^O4V\ߏnlt Ci2dI+1a2XI={hhh6U 6Ј~LNv1:3E8KRV=%s^:Ql{m't׻ƖIj{bpc2ݔؽڽ$۸q ߕ9Yqm3U[Lzٖj{i htm/ 9P?g~SE_|j3vs0Aowjӯtb0BbI^;d]-u֚ΒzICs0$\:[OQٮ dto0dCPOln:tݜw_O%Zu"%=UuCaӇHtzwP dZ-{iZ}0Ux\=Vp[de{ct9a# gW{MDқ=ɱPKXnZnjS{<Jzu|VT:[ ?at=VD2$U9;^Ǵ4ثMlt:8!tqmk`89  (Zj]{3mE421Hc~ h;mu0AX],n`xK21]Z=kKU<:wHBi&c0].U֜hu<3"Op\8WMWeOj\H=G-1è}moۮw}B \afѷn~rB}>1MWtm-"&0xAebԥ>лJD ~%W . vRf|z|pV=l{RY[ E=<>3h -qXKKÿT܁-tm'g+%-\ZݍeEW\8Ae_q>@6_2<7]Icf]-F8?fD`Fi]=PI.w&o.rI]:|,88\xW5ZiM#am3bLqx鼯:d=^- <(hk! {͢2[A풖z*XdRѴ0eBGjǴ}+M4-,-4y{*|$`p\I$NVUVqNY:ځK|?w?a.KF6g?8.)+-taѹ6GC*NqL8[CIUk_`nfMx笒X!8’5{=RmK&w;1(׹guXϑR_Ӑ6ms]ۃcu zkmߔ1`IԜp %>uP:ΒA~빎o_߂hmyOa`{ݾhā7 GI@A2xWM]qMp/MBi((\w'psyw˚9[.T 詛Nd88Fp jQ%[s-G6Mm6xse{$<<窏j\/uF:Fm~wyL?w50 ̶bk~X4[ eCdviQ;FP2I)[S4:3;:TD ȍpkgE k!t-81\g a5:ᑹKu{U:haw`gw?Z^JJi!Wd8xvq:]edih6" k⎡ pcc:9/_K[%-KOZ20ͽNz*`c7|9첟Om>~,̸*ilQm@r\no~`6i|w7I=@yּ`a~VĴ4EyHQ~lnerlWVF!\Pсz6⬣ OpZCʇ U4ewL(-h{LpsٜgN@TsuU\X#J z!>ilNu|jS鍼c*uaDŧ]xô&3!TQ0 EK%}nG8{[31 //R*kY;lhџ Guq#oF8U哖OպAl3Z74mNZx‡plkK) Z@prD!؆H5fv 0ó9dmf~ʔih=-UjjW0kIkZp-9+CDUKnYE+|*06<82J~Aݒq;F5.V#s. iT(*h[Whb*"o79<d7j;@4Ol&@65< gqݟ$ӟU}:Gކ:r=U2>a3j E+p۟^U#l1>M!q$z L8oE4j^#s-{gjH(m§O[(-.c{1^ٯrZٍ_O;&,$q-AwL1p\N3EzJkݵw-j'c4wj浓T 6k4;߹P_lV id"&[r_⢼.2T\ ]9)ٜݿ?r_+*+v:gJY+'~4-ӦNxv8!dxd儀KAMڟf{:Yϵh.y};w|y=՚g%6אQ]Mlt8ģ1Hh%tZ;>ixH%=Plq-mǧ˱;dT{K3koߓIEL乯mȴK1Udx9Rڊ9K]X{{‹t# hpCZ9z+ϕ {nΌdnz>9!ѐ_MG <;sRmD׏oE-tt:D1AP4T| [IesHRa ޙ .t裼ve;' NQTe;)RhM\ߘV:sSpnQeG]ڗޟ()_R7p$ E4Ysroeqb$8D:UFY[F[d17:?dt2S$D[!{X}trʶXo~TC)n[RfzG0.;'KJeI{INUƌ7'qͽ}v@u6ar0ODK2<)o )Fn-:00l7?Z-ZFRFF~JgƸ) h3wH/'q b<1ITN#F洴9n? 5+Xes뾺j/>8Z08doc&1NdZE**-xӶV5W3(~9?-/uNN1H5-AX  C8= n4\[M>;gcU4n%b8&쳏U0mnw$f:"uw(`sͬQ_0R3ZMlݷ%6;{GXs[@]y!-BB"X"X BMVXKYHkS*"a>諢=dM'FtNO%  fDF4\kt ;TUhofi-L:ir):3 :e㔋_d}ha(El@<#R095OT6Qۤ˶@ ]Sj8&ުhiNUUvk)OU&',-Vüh _ڋـUG6#}X]4^=8ʑ7Rܴ~`6b%I55| ,v6Ӌ>n񠇑6s vEMUJ6j^y4pZ"Ǔ K H c0ɰBU;n\ 4=Wm..> !!!!!$_Cn)Qܽ6:c xR6 Zc}Qf]`xVhkMř]=3mvRj|7%9 6tB0j9OQmd=NN%V^ׂ,H7S"d[#.뤾KM^ l2ɨ<ǵ%y.>l!ֽC:,=qPXFF:/!829Hh6;E͈J;ARTQoe]Y%^=9hpe4/mu0X9;FkrIXvnU0nj\Wye-$BGB0!)1Zd~ɻ^  QR)x;ꚗG\|)E C\}^dM{F-7:6b@ mjX =#򖓵wh@(FO3L Umqat5(m} ŝ+@i*[+ qMQ$N.kHrIpsX* {.qV: 7|KP7xwn-*&SK5a*ު7]g ${s>ֻ9ưaE">bؗ˚M\Of#Cu:|}ܞ^rPU5G,:6MHY^JnYMW!s e,w$.     M]p~>7tq4wUlyp=WwO1Իᅱn,o=~ 7)NP °ֺ*X[;Z}R%,>;Ww}q=IF/$\))!S4둥&giC׽Fh`W=|k33<$rH:hmT8lج. n=)*$>gT׶ E\"܌7LkrRc`v;,&1qM:+ۚ,vDd?~6z6'8ЧRM$m Hw"O4IA'Qxv!{&gxoL:cp3(Z㪎^#Q6\މHq)#lܛyeflksņދ4):ӑ?"tWCi\Muc*kk6ܵlkk--lX]LN5{UTz/4CǥlR4o_vxc75/գ\-L#Y@ )86!kodwr&T˷$,M)NͿt݀/n_m!!!.8t2 |NG a 5ώ7>GAW{bTm7.m.2.9nu?yOx;Ɩ S0é($BSgc6믕Ydc/VwYmUӾ6AE-nSbZEw]3SoQ)DP_3Ky-f|VK5@; ;t2!_1[)L`\˸SBZgJ =od?UG^dM)@x{+JxfqԖm>\Z;{u]oR̲rD+'c9Fn9{IN{ؓZ02L64+Ot{ִ7oMLoыIe xSDb2ky$F<#;H;ȡΙϮk,/ Kݔ|< Zɠ1Y: yg4X +(c{caܚ"ɡ k#5]n='VD m)d텵Q5ΰΦKh ReA#~۟0#-kYc|;LJּ@ڪ'ppJ\rft]4*) !QQ+Al3>l8GPܯ#6R+L+u[qŨU_g-q m[cF&S`zX ]WLrMH7V{dPTRMo(m}\tg??Usw7$=UFe!n^vT,8؏24#UuvڠV}N? 5xn" ?N^O;m5 ]68yuV}JDsduŵ'WX4D`ȖUكon[}zS)aiH!UB- Aجb@O1q`⯸?v9}+ocȶ;gc>wRGW>>Et \An\QT[sԘӟepMVI9ZnH]" ٪a$&ft_Ř6(ֹ?MUXsK'xrtMwiKk_5RnΚFO{[t񋌢^K>ݵTŝۃs)@i_9BV[j)eF[S3^80#ɖ(Fqk~lYAҸy٭u8 r;:9v[T; Xa֓srw(ϧ˼.и>}~,7H돏xy3Mkh<)ph le,TXnb#{]m_=u\Oh$1JJ8d{NzㇰȰ| 6Ox7JMi=7Ic_.[:Ll:ŗ,#u07 eSRtG^Oɪʤ4~i%٥ƺJ/ę_M9-c6l |TOf)nlƜ*t;;Nn_^cm-.(èKnl"R3m[ 0r Y:Ƃ.VE|}I:vs2R*#}4 `Ô\49QL@4İ ײL{4E@Kl,9P=ٽłA N't@k2DI}NrrhΟCX}&P˹]r)M6KגWWfB,;(1иaHl5uJJ&<ͽκ$ \}BrzX`&HEk7~n(3X sTP8ؑXH%p4RQwZZ[8ptQvMYCU@/hɗs'.zxzqH^E6On|a9-$ ߒG "ܸ]mgycPE}}$w#s|+R0}'EF[`HSi nٍ^~VvE w)o?'[/gb,tG cvṚRGS/?.[wn:2.ЄF#\[ ?Ab2-^-R&nc;螗L@.D >"A e)5F Li"~VR{C< ';t3]t<I6kB45MP#a¸B2?,-6טךWVBo)՘O8۞X)CËP==qꚬk&cX$:we,fjFE3%K n.#vSg8YF[Yq_\p-n>lֵ.˕ (5u 5kdgz7*lцDrM=1< g4=Ew/Q-ΞŰj 㯀܏;_u!Kly6odW ߄KjZ33c@t]ޖ'Hd$eozƽZSKx`u޹6uĸ[-,?^iQ^: x9zSR; 7q~Hd e1n ~6MikhHYѦ5{5kO*.FJ+B   K|#q]k.*cM+0 򓑭4\|w=ۻƝoϗ!s\ֽKH؂{mf`s ö6MHF}hq~Luه/r;|v 2Xw~KL=8:ڍ=(uM s\%I3]G#ue6/DN#䶎b)&F / 6JVP @]߈L1>n\PU0NDQN6O:hƛhP&KčNм[$3)pkuPrxIu+:!/V4H/}_QBl.mЧa̮,`}z'X\5#%&cor٣eU6f zT+zygo\$$mof^Ғy2:asp2Hc qܸ~NY9onam;Kmq]GPOEXU4XAi).hs"tL stDtHb ئ[:6qՙ4M4W\%>n~)i!4Cڦ!TXXY7v7*9Ddj ~R6C?E{XSs%cP7E;ן60N`^g(e-xpv?ςxS%hi.r^!m_|h F:(u@; y ӆquBO.-rIe1]a}>o{Hd!@mQ6&vaH(8p!VM#ۙ+U6fܵQGM]/(.BBBB+~|ݛX..7q$: hB`H3gssH(lZYY Y5J*K}2QFkX7QLQ ?]JO5W-l|J[p9qQ97 p6[L TRhjO+&>elD4Z\CuL7Mb ,~DmcU$M@Iq &7Z>kv%nFm@I< e;;ߚ$sj/qsa*CH!7Ь4kXuslA#B=ۃ+L;=Ut٠xǠuv(E̜r-+.W[%}4XZy\uї%")5OZB٤^;{Ex~Z['+XNlsr52=H[zNݱ$l"a[ s?"*fa0$~Vo[$ƖOY;'O?'ti\FIZd3%LEw{Z5P-}E8Ng9af:Ӯ  YPE18$Z^w$[EfN= &sT嶖Oyu11WL5D>@y.NA|ֵB&R 7!!!!صMplPOi$U9XOzK9*'ռ :kw;<2^rueØ> qycs]~` 9Btk #{,dWc5oh1nSjmȶVw-)誮\<.VXU{5$'YJm=$#7:|NK+o+>iQ1l$wz)tM0uGhvmHǕz2C8s Kݗ4]sqHL9nS2#yT2= 5;_PxsG)n9F9$K͠]]p{\ijM])5jK;9'M1\?u 2)eא3*' #p{nwJ>&骇8#5Պq'#77Q@wK̫MWz)5 4ftf'janͪ7]V$f,}CܵOuUdF-Tv[49wf^xN^nj?Kn\s NWPpc Gs\g|L3\pq\[@kj\f%$X׾[i\MES;*>^񹇇6ےx8$qatbzH>Ԃi_Iu--9\ﴮ6BZgu8o,s yS7ZH!ȫ:Ʊ*l؋4%OtF3kxI/7th;0&pz oo[;^1ə;5B<_ۦF~ sn.o鵹v|#čj~e7=:Bh .i ׷XE%  PB li7R]?YØ1  :;;;Z>3â{Kcĉ=`m]ob}xx^#fM=W2wxX| t Eֿ\SIduٕasHe| 9:+grT BKv_!͍k:y^;ƚ٘lh)J\?_%J$`- Ӎ~v#!e٘ D̮{d5s^bfwMpmMScwNMJAT&VGÛ ڨr]gg6\TXUtݙL_o]?ufIl-YŢd#e\^{ӛA欙rWcCMYP,\yY&K%:ǭՌ5ay{% G-@X^,[<[E;D/Da]&8rZ }Sa/kf8̮6^LR"yGS`9a5i+H @KJ5WMHF9{!%..- \k I`fnkd-aͭseC@e\o-egx)綡3N]H滆>E۽ˉpNq^!c%1 ~lnPCW['4MotE#`HGVN?fw_H]Q NϦ\j(1Y˺755xϕ;2 &*sF#clo]Sl鶫dl/\,&Yñhn-M#>jj)_Jd٭mS\Bڼ+r}F5⦒n!c#F%fmW|o+_b )-dUJ~(+":[7rCl.܎S0|O0Yf9oiml^ |\+Tx#gb&a:>&94з.yf;)%5}M4^ j˸3+[8t7Bn jYưs͎HX[-Fn7d_{mh餍 n|] ]M3Mci˽y ht]`5-LR3T5Lņ;EӪ6+ 7YK_lll ״ &}cpqo]V5g^:rӢ]23O cZ.6^(UFᑰH˶!c;[{kZϪY^?24 m[䗺pums]Sca<1.p + oV=h|op-/;cQE ő<ż^>kn;{ Nz?%};P-<BB=6j۴Eͭsn6[eL,sqh>O0(^%:*7,isIAo6Axߕplzވ77wYeJ -bFn~J.ct"1amƤkb)SF̅sͭ:r\:e9#5)o%k@fOؼl-]55ֳ:fgY)MqWH&nŖ:dNM}VrDRR'M+)deLëkll!6mh*$inWF/rO%ʜKGAổsGP@hy$A-WmiPϒ˘/q|f{#%:YY)5+ ȍuRiI;M3kl>[iqDX4+l;[ P^bq{W¾@+⌚mû,eYAAmQeisr6 esg94")ԩz2 Y$`7(kS;ID\ Qx^9xJM،a!za-{B}]B?bI4е4uncⶊnjKt"8̽11_ W)o|A5j6F:G &)o}u*ܦ-[kieVY5MGqXdhkFk7?x[QÕhH8p Nz|nKo\o\*#2A5VIP ~]<+WmG⺄-s_svOV lG~-h5ǼwYsJd sl08b#o5sڗI^ڜ? |ѳ.i"68\n\zZ - à^ 38r_8K憸m٭~VRYŵY|.tNcf# js`ֲ!f_zj'?PA4]?+yxR6*\YҾ .Zb- CdhE}`s!GFֵs&e|,goQ^;܍+) \F: i 4k[V?ڶ? Hj*dc 1ִ9\m妱γ mҶV-7g\k. 46q2 | ]TDƇ.Mmk"pnI$ӧq s\?惌*(kQ奦9+V|}T;\ zL/s=6ș<{Yk7H4 Al|'KC oKO+{6o9WR{[f)T%k,iGz!Gэ$2wYs.wQ#ml[Y ~/C\&U,I~ {SkdfH&,#nmoo4ɧuh{Xru:[OqÖcW\&,)N${#sna\mTBrN2yfjbh rG]mnt>eoq>!C(_ ly>P]m~}3jԻ=c{FֱZl|\YC& 䕹XnZMn;JÙ)ҽ5% ⪚՘2LKhmo>_60xw3TGoԞ^[GQ@me: ^LVO5}sX/g5s۶Z<py7?uRep \[jg({|I#D 2w$hsÒr]][i`u>♐#(0>fxr#BM,t;Gm6#4R8);T)妰dk56p)4 u>'9jtl: &mnRV[D-5%K;[ok&c\ydC]xMɍ} SgϑCJsl̒!/?o!z,ac$Û FJzY7k[n'h?AU$\? {aO.KXolfUR[_%# :ʺp>'bqE %{NF\[PFQopˋk.eEs`lDE 6ʓ?qw㭁3RH5o]|.KڃĘ ]E4۝ԦIN 5\O;fǍ0FKoHJGlc!X5tXs2ӱ9Cgr18KphbpD1$@p7Yg}H`x#yhIvQk"'s?AJ]!sMM\`t|32FId~peH蛙c:MFs9~ޫ;t#cu?;+iTX_{#uqZ?aXLMbTٚ &aExWI,&o3<,Ϧ[^W/f鷑\;^2>cӣW(+1 -Lܓޙ uw@]l%[.!P骪dgs>zS0g(')dKyy eysVGQyene{2ٺ ÚZxwz-#.-!F٠kArc;q}ԲGF47\}zTR枽@1m-ch%@G Ǥ׻uvG[+wUX=4Ͼ]lG8536&!u=98Yq!e: PР )Ae[f10#>? *i*'}y.Ða٫禁ÙouyFG3t0Jbֽ*XZcooOƮ)P1Hג8rSϕ avwhA%-KB).h-              Z%˖(@'c6c=H@0[6]!@!@!@!@!@!@!@,~wNª?xZGK>p' Xh!@!@!@!@!@!@!@!@!@!@!@!@!@!@!@!@!@!@!@!@!@!@!@!@!@!@!@!@!@!@!@!Aa*Dd ::0  # Ab)%d)D'n)%dPNG  IHDRL\sRGB)ZIDATx^]|s] ł)š-Z+Rk+P$!vsg/B z2coov{d{3y2.a]= d 1|}dqAcgp@|w1|}dqAcgp@|w<_Ç޽{ddL?{ h޽P~߿R/g/r޽xN ر/(J2dH$gV 6۷bIs?l6ѣY +57D'B<a=n5r㻷 ={:tɓ'˔)s`cOuo= y 6LX2"/*W<~x۝n1w:ժU+qBr9OJdM2QK=X4oE*Unݺɇ;?W,]?emfqNPvQ.̫ Qź)| q`tO29sʗ/_>Ji,+Lv@8#MxXbc)(h|[cbbZnݵKꍟ>x"RĎYORm LD! L5nEcϟ?y 7}*UŇǝo^HMD0?8kC`hfr +TtSSv{Zkjׯao4oјv )S R,\Tre=M= a \x[@t9X;;x&cVHE}҉O殀7[E:̡D(sRxûYV>:*;|te*U4efПG+X~N0f~,P(ln7ϲ*!E}Fr&WӋ( ŋ8͹y97ǹ=9Yrv'g0#=EwmA3/_hѢe2/捻T:ADk1X G!BxNRO?2zF?ިQdGPƛ9ό]Z*W jqIxUt?CAAsR>xj_Ԫ]}…Yf :>}ګWGNvi9z6 ˻*WyD VI*̕M{pL̓$Hwo:ӧO:xmЩmiюh| -SR/xmw>\7N|Z{pg~qf]rq/̼ÆQDDBiqk6uQ $Rǻ }͛`sg/wh9D%5x_C L)%RӷԾ}ۙ3gjZ"ݝ nܸ7TtL8IN^`b V/\nJ0 ( =RKk׮'Ptqosad2^z= eO}|RСC rK%_~j0Zꉞ}GM*ʕFSL]VD޼ya=Bq.-Nos>2>Dk֬9b}:ᇨ!j}3tQRϢ 6`gYq$ 3鐅{ ci,L &yO5`x!aBW嵯ޟ(Jnwq ourQoaM˶Ǝ_ߩp"wn<ݫ^sv\Ѣ*Gm0؜0 y2ׇ}cx^q#&,#bFJcAK0x YSN2qB%  k֬y؏= Oi25MF !~zvVXc N(ls8)A᧡)%@ -R#X,Z cdrF"1CE(B>&7c4m^wT[lN!^z 6:lo~7 HDD\ } N}Ҳ/b V5 )4eY @;HZ!uf(Vjb$d` 2D :|+z s%Hkc#l\Շu<_x1"o{f)E%a!,D`+2NiUcy(ʷ5}޽{+59[t\Th0:Sjx֧DL+Ur??ţ;~ߓ q@…^ZVlYݕc~b) •)p.'aJ( ~c:HQ mרQc`mI`vItctB, P-" 6~-Kw1b*`!`A$>)țcH1zܴ5rJ8SnwӠandwZ` %'n\G ݭz|ep89:-FXeLL;-y x nq[u[-o`r8Xa &xWEF-/XwӏCΞ=\r>ʩ3gΌ;bߦLm ˉ:"'q$޿P ߪ ƸLy* r<0gU;$f&10aI!I`\Sᖁ@moe8f)D\X4֡m?N4ðLp2ž?#sUZ w0IHćBFK7 D$i\.gA,b}Q ˫R0nTK$`/Y- εRte #ҋ@Mft*g\4VyLM:?>@ƍ;tha!)l7'~O4`q9cL|-ft3pא.d{LziF,N`rwX#ف9 R?W,m\r61Cy{AmûbttrHdHQtuW ~BTNj@c[ )-L5k&X 9-! "ER>A,xC* $,<ioy1`wx9!?7hH]C~Μ9g̘~vJ OXç0 k3L׺6logot-h+&]j]SdiR1<{pQ L첡;cLxg[${X=}pV z_رc4L"@ 62ZK6! UӆS㧓u )(*M*mB/Fv|+'mݺ54K,3Unک -Zf|h#pS\݇YJ.Dm۶EţOm($/nw )i?R#A%J±5ccʪ ֊[ ɯQ"Y  O0% *t6'O޽|&Hiou7l2r6seS&M={B.Ar]2hS%U@`` kʠ:kD[+Si#(%1׮oԦj޺!VO"TR|8M$Zːvbó#Gi޼Ocmqp'e"Z-ܰ@&ͶmےG=?VwMhh4&^V*j<#kߵ1tѣG#fcK%@ TGf;/ey { 4RV5W G 98\C*u=tW;h*sN >J!nԏ9a4A}b"\|U: 9nd1&ɵ~5$ȡ"Z>rvJf֭[ZOvS+~RUևw6`N:%<aF~(KRx, C`Y*Qqn]9ܶ eKB(˺4D"@"HXGҵ嚵 mQ˷z_a@P:7dzV?~Er.2ɢ6e |mÇK,p؊;F'_׾DCܹ_Vߺ!T@=ݥ9? 3E"Z Sp JEbRq9 [L2Ktl_/_nڜΜ9_@ll,W֟$rgRú4\IDV:^֧44p;|8컶Rdb,0z Wز@H"NnI = /tsJEYx> *PK7E"M1FEӓ^)&G%c]N3f;:ժ-]?Mh6ϲiӦne9c^ۅ$ja!U ,0v 6WD2vEfkΡR6̙v  v9N=Rixl٠@E"N!.X8+e#VPCDfbL?\ڽgزeKѢE?iCvvD?ՊT8@r ynͺ{EEFtjTҩÓlAό MpJxwor7X+Ty"R啩7Ykmv E&Lɒ7Oپ=NϘsE\H񮞜6{V ֫{̄/^,v:Z?2@ubHĞ8c)UfˆV$ h78d%aiAܷjG!yȕ3yĵ[73׬(w\X"(_6n +_fZzYuʨrx_d6gfHswJ$ *vn*fC9jjHƌݫs|ŞGGeϔ%&FҼe2s2Fc{|V|-7Ƞ !RQkśMmbhHrZ)w.>[_e k /^Tі&.@ ɠ N-5Ɨ=>g.(;hgnTjɬi5Fqni!/l|BaeÆl^)h*.i]F=!K} iC1cƬ߲1O".lq{˭Ê1X" vKREh1e;.y%⯇ϕ3=nƉeȑYʬlb: ِ SxY&,D¬lI|ݾ=ߐ:8h`N+9`Ǘ(R j= ~Jكs7qؒ%K>i# uXg֨zT`͐Q10ztU]gǶ={OdaFਂ{Lb=1wFT+!2ₜ-;w+΅[\LO7lвY eK)+qUZ%+Ɯgu\F+EE3p: F')EHB ,ycdcGz1rj‘OlƊrV)Qcr徥s0VxK3 [=qX͢1eM.k&my- \_7bn9{y3PbCF8el{~kny\A%̊PۗK|M5oۍ# l8D"Hw̗/_ZMOKE,61]Ɠ{K`>gx3/ENR.K"7]DSMd_#\m.?Wdu?ơ<(8H~^ )D؄qy+S̺(I2Bsyrfκe7Tp\^>-)sV<[6NQ$ E?^V\$ht[Mv v'N!_kAeeLlN|bc峄aY8!D-Z; >x/ݶGU*FƲDG4Qih (q:Yw\T́9ki9>@fЁ)p};sx.-6`fɋmjQgSMʔjeN\ZU,.LJVjARD jԨ1qDD5\ b#ؕKB] Whvv[bG2o{O~pڗB\wيh|lF  ү9~VTOVCTV}bi|H#/+\Vqj:-,vkTL`pS??}qɷߵ^2'i}k/ea̩;ֿuB C%Nk"2~7 .60 oƍD)ԬYm[9v۰!rA&HaAz)B|;&E&H$KW|m6'q2Ύ]lb1L,Yְ1| ADdRLIQ#(I p/n:u8("mSRGZR&;6"iү[Vt&pN賄Hr]vF%ÉVo;]008l,{e_c, r>y݄Uif7r88?G1GR8C%=7̝;7mIϋ`)߯08)SRm'l2[XHcjE;CF|q"&'{;+jdzakys$aZlam6\=y:N:zP*Mӻի/|HV;3 b2jbk/s;:S/^S{:8FuߑK9Ta]85>&Jؚ4Ӏ:% f߶NSo #Y0Igw.ejSUaeYCԼ1﫵C`HH}\z50-(*Vx֏9Ec' #B vp89 l\D y9(UE*eWn†!CR8'(NtF`:w5Ðl6)LϜ۸X ʕ+?C R:S 8?|{$u+~>ѽ!u9&2P+~n!C#FA\p, +π?:tu(ܯZv(Wo2]R {5Çe84G+>;IK A >>2c  138 >ƀ >>2c  1HhIENDB`@@@ NormalCJ_HaJmH sH tH DA@D Default Paragraph FontRi@R  Table Normal4 l4a (k@(No List 4@4 l/Header  !4 @4 l/Footer  !9.2/`M```aYa01   345@A./23456789:;<=>?@AB t u kl`aXY89?BCDEFGHIJKLMN@BMOPQRSTUVWXYZ[\89x#y#$${&~&&&&&&&&&&&&&''L*M* ,,,,,,D,E,,,,,)-*-s-t-------_.`........////// / ///0/3/0000000000000000000000000000000000000000000000000000000000@00000000000000000000000000000000000000000000000000000000@00000000000000000000000000000000000000000000@000000@00@00@00@00@0@008 111111111111111111111111111111111114 /%45627 "$(+-/ aH[!,L2)5627!#%&')*,.17  #?Db$6bI]'nK|Hm R$lna2(M5\ aR$kB`Ӄ R$芭_O\i=%ؼlTHR$1̀ՎqF&)VR$O@Wc?m9R$Okc+74 8R$é-Cg`JR$_~ɘc"C R$j6+t5V IyN9R$Gj'}kB6jPR$å\L;4*{DCR$9"\*|F R$FD҄Df=V !R${axM̛^· cR$TxD"u(V"N^R$[=P l ;R$+kzU(Jb$LŠuZsL @6  (  t  S 6A  Clipboard01"`v  S 8A StatusScreen"`  c hA . CobraMkIIIppS`TS`Tp#" `  c rA  . ShipOnInjectorsS`TS`T"`z  S <A ShipyardScreen"`  c lA. ZaonceScreenS`TS`T"`v  S 8A MarketScreen"`  S FA( CarrierMarketScreen"`  c hA. RockHermitT`TT`T"`J  # "` B S  ?H0(  A/ ?@M{&,2/9u6 t pf @'tp  @EQ(tY @q.r t@ aS{@(3t@a t4$*}5:OT   %X^jpel  1 3 & / \ a 9>&?Ew| oq -!/!\!^!""# ##$ $$K$M$%%%%&&8';'C'E'''''(((((())))))**"+)+=+?+a,g,,,t--------------X.^.........//////// / /0/3/ BEh*8 BJF J  $ 4Olrsx./]k}~9@9<!! $$$$R*Y*e*j*t------..//////// / /0/3/333333333333333333333333333333333333y##..//////// / /./0/3/$-(-----...////////// / //0/3/5$ { l/YF,O6SG|+}>~|% @N{aq xMA,55;~rh@ 1,2/P@UnknownGz Times New Roman5Symbol3& z Arial95  K @Consolas?5 z Courier New7Bell MT5&  Impact"hMfMfN'U'U9%xxd..2?,O2File: OAfNC2009 Nikos BarkasOh+'0 $ D P \ ht|File: OAfNC2009 Normal.dotNikos Barkas15Microsoft Office Word@T @?A@v@Ne'՜.+,0 hp|  U.' File: OAfNC2009 Title  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-.0123456789:;<=>?@ABCDFGHIJKLMNOPQRSTUVWXYZ[\]^`abcdefhijklmnwRoot Entry FwyData /a*1TableE3WordDocument\SummaryInformation(_DocumentSummaryInformation8gCompObjq  FMicrosoft Office Word Document MSWordDocWord.Document.89qoolite-1.82/Doc/CHANGELOG.TXT000066400000000000000000003775311256642440500153640ustar00rootroot00000000000000Changes between Oolite 1.80 and Oolite 1.82: General: ======== * More AI improvements and adjustments. Extensive rebalancing of combat difficulty for more interesting fights * Laser damage and heating rates adjusted * Missile damage adjusted * Heat cooldown slower * Installation time now on equipment purchase screen * OXZ installer now updates dependency check after each install * Added zooming, scrolling galaxy chart unifying "short range" and "long range" options * Internal damage from missiles now more serious * Damaged equipment now sorts to the top of the status screen * Adjustments to acceleration, deceleration and top speed of torus drive * AI reaction to being attacked by ships they can't scan is more useful * Removed some differences between NPCs and player ship performance * Advanced Navigational Array now allows for jumping to the next system on a long route without having to retarget * Stations will warn if player tries to dock without required clearance * Docking clearance now defaults to 'on' for new games (no effect on existing games) * Keyboard settings can be viewed from game options * Scrape damage now does not automatically mine asteroids * Extra detail graphics mode now has more detailed planet textures * Field of View added as a game option * Windows port: Improved multi-monitor support * Windows port: Added support for grabbing mouse inside the game window when mouse control is active * Windows and Linux ports: V-Sync can now be controlled via user preferences Graphics: ========= * ECM bursts now have an effect on the scanner * Spacedust fades out when entering atmosphere * New and more varied explosions Expansion pack manager: ======================= * Installation status now updated without having to restart manager * Missing requirements will be installed automatically if possible * Ability to filter expansion pack lists by pressing 'f' * Can view additional information on packs by pressing 'i' * Can extract a pack to an OXP in your AddOns folder by pressing 'x' * Update date now visible in OXZ manager Expansion pack development: =========================== General: -------- * mission.setInstructions() can now take an array as its first parameter. The first element will be a heading on the manifest screen, with the remaining elements being mission entries. If multiple entries have the same heading, they are grouped. * Subentities now inherit their parent's entity personality - useful for shader randomisation. * New trade-goods.plist allowing definition of customised trade goods * New market scripts for JS-based modification of markets * OXP Developer builds will now warn about deprecated code in OXPs. * Changes to planet properties made by OXPs will be reverted if the OXP is uninstalled * Scenarios may now exclude OXPs and selected core plists * Maximum star size increased significantly * Implementation of many core equipment items modified to allow greater customisation. No difference to effect. * Waypoints may now have [0,0,0,0] orientation for a non-directional waypoint New Plists: ----------- * global-settings.plist: new plist file to hold some properties which didn't belong in planetinfo.plist * commodities.plist and illegal-goods.plist replaced by trade-goods.plist * gui-settings.plist: allows customisation of various GUI options and chart settings * explosions.plist: allows custom explosion types to be created Modified Plists: ---------------- * oolite-font.plist: new f6KernGovernment and f6KernTechLevel options * equipment.plist: installation_time, repair_time * equipment.plist: laser properties may be customised using weapon_info and new laser weapons may be created * equipment.plist: equipment may 'provide' other equipment * equipment.plist: can_carry_multiple can be set on non-pylon equipment * descriptions.plist: long-range-chart-title-G-S now allows chart title to vary from system to system Numerous changes due to unification of string formatting systems, see separate "Format strings" section below * shipdata.plist: market_broadcast, market_capacity, market_definition, market_monitored, market_script properties for stations * shipdata.plist: weapon_energy property no longer affects lasers * shipdata.plist: model_scale_factor property for easy size variations * shipdata.plist: scanner_hostile_display_color1, scanner_hostile_display_color2 * shipdata.plist: show_damage to control sparks and other damage effects * planetinfo.plist: all major properties are now coded into the main planetinfo.plist rather than being generated using random numbers. * planetinfo.plist: layer property allows overrides of planetinfo to have different priorities and for JS and plist changes to planetinfo to coexist more easily. * planetinfo.plist: coordinates property for system coordinates (may not be set by JS) * planetinfo.plist: random_seed property for aspects still requiring random generation (e.g. the precise detail of planet textures) * planetinfo.plist: several new properties for customisation - planet_distance, station_vector, sun_vector, planet_name, sun_name, population_description, economy_description, government_description, percent_ice * planetinfo.plist: has_atmosphere can apply to main planets * planetinfo.plist: market_script property for systems * hud.plist: MFDs and legends have color property * hud.plist: allow_big_gui general property for HUDs which don't take up the bottom six lines of the screen * hud.plist: new drawCustomImage dial * hud.plist: 'permanent' can be set on message_gui * hud.plist: hyperspace destination can be right-aligned * hud.plist: negative height/width can be applied to HUD bars * shiplibrary.plist: condition_script property added Properties: ----------- * Removed manifest.liquor/wines, manifest.liquorWines, manifest.gemStones manifest.alienItems aliases for trade goods * ship.maxYaw, ship.maxPitch, ship.maxRoll, ship.maxThrust, ship.thrust, ship.maxSpeed, ship.maxEnergy, ship.energyRechargeRate now read/write * ship.collisionExceptions * ship.cargoSpaceCapacity now writable * ship.injectorBurnRate, ship.injectorSpeedFactor properties * ship.scanDescription * ship.hyperspaceSpinTime, now writable, now property of all ships not just player * ship.scannerHostileDisplayColor1, ship.scannerHostileDisplayColor2 * ship.subEntityRotation * ship.entityPersonality is now writable * equipmentInfo.provides * player.ship.forwardShield, player.ship.maxForwardShield, player.ship.forwardShieldRechargeRate, player.ship.aftShield, player.ship.maxAftShield, player.ship.aftShieldRechargeRate are all now writable. * player.ship.injectorsEngaged and player.ship.torusEngaged * player.ship.routeMode * system.ambientLevel Methods: -------- * manifest.comment() * manifest.setComment() * manifest.setShortComment() * manifest.shortComment() * mission.runShipLibrary() * systemInfo.samplePrice() * ship.addCollisionException(ship) * ship.adjustCargo(commodity,amount) * ship.removeCollisionException(ship) * ship.hasEquipmentProviding(eq) * ship.dumpCargo(amount, preferredtype) second parameter * player.ship.equipmentStatus(eqKey,true) second parameter * SystemInfo.setInterstellarProperty(g,s1,s2,l,k,v,[m]) Events: ------- * gamePaused and gameResumed world events (useful for sound OXPs) * equipmentAdded and equipmentRemoved ship events * updateEquipmentPrice(eq,price) condition script event * allowShowLibraryShip(key) condition script event * shipWillEnterWitchspace event has second parameter (destination) * playerRescuedEscapePod(decicredits, reason, rescuee) event * playerCompletedContract(type,result,fee,contract) event * playerEnteredContract(type,info) event * stationReceivedDockingRequest ship event now does what it says * stationAcceptedDockingRequest new ship event * playerBoughtNewShip has second 'price' parameter AI Methods: ----------- * behaviourWaitHere Format strings: --------------- Work has begun on replacing the %@-type formatting strings in descriptions.plist with ones that work the same as format strings used in scripts. In most cases, a placeholder like %@ or %d has been replaced with one like [destination] or [population]. * @-credits -> credits-format * oxp-containing-messages-list-@ -> oxp-containing-messages-list * bounty-@-total-@ -> bounty-awarded * autopilot-station-@-does-not-allow-autodocking -> autopilot-station-does-not-allow-autodocking * witch-to-@-in-f-seconds -> witch-to-x-in-y-seconds * witch-blocked-by-@ -> witch-blocked * witch-galactic-in-f-seconds -> witch-galactic-in-x-seconds * game-paused-@ -> game-paused * game-paused-docked-@ -> game-paused-docked * missile-locked-onto-@ -> missile-locked-onto-target * ident-locked-onto-@ -> ident-locked-onto-target * @-armed -> missile-armed and mine-armed * @-ejected -> commodity-ejected * @-ready-to-eject and ready-to-eject-@ -> ready-to-eject-commodity * @-scooped -> scripted-item-scooped * scoop-captured-@ -> scoop-captured-character * scoop-rescued-@ -> scoop-rescued-character * equipment-primed-@ -> equipment-primed * equipment-primed-hud-@ -> equipment-primed-hud * mfd-d-selected -> mfd-N-selected * station-docking-clearance-abort-cancelled-in-f -> station-docking-clearance-abort-cancelled-in-time * status-hyperspace-system-multi-@-@ -> status-hyperspace-system-multi * equipment-plural-d-@ -> equipment-plural * equipment-plural-d-d-@ -> equipment-plural-some-na * manifest-cargo-quantity-format -> manifest-cargo-quantity * manifest-cargo-quantity-format2 -> manifest-cargo-quantity-extended * manifest-@-travelling-to-@-to-arrive-within-@ -> oolite-manifest-person-travelling * manifest-deliver-@-to-@within-@ -> oolite-manifest-item-delivery * charts-distance-f -> charts-distance * charts-est-travel-time-f -> charts-est-travel-time * short-range-chart-distance-f -> short-range-chart-distance * short-range-chart-est-travel-time-f -> short-range-chart-est-travel-time * long-range-chart-distance-f -> long-range-chart-distance * long-range-chart-est-travel-time-f -> long-range-chart-est-travel-time * @-commodity-market -> system-commodity-market * @-station-commodity-market -> station-commodity-market * cash-@-load-d-of-d -> market-cash-and-load * sysdata-planet-name-@ -> sysdata-data-on-system * sysdata-prod-worth -> sysdata-prod-value * gameoptions-voice-@ -> gameoptions-voice-name * gameoptions-music-mode-@ -> gameoptions-music-mode * gameoptions-fullscreen-mode-d-by-d-at-g-hz -> gameoptions-fullscreen-with-refresh-rate * gameoptions-fullscreen-mode-d-by-d -> gameoptions-fullscreen * equip-cash-@ -> equip-cash-value * @-equip-cash-value -> equip-cash-value * shipyard-price -> shipyard-price-label * shipyard-cargo -> shipyard-cargo-label * shipyard-cargo-d-tc -> shipyard-cargo-value * shipyard-speed -> shipyard-speed-label * shipyard-speed-f-ls -> shipyard-speed-value * shipyard-your-@-trade-in-value-@ -> shipyard-trade-in-value * shipyard-total-available-%@-%@-plus-%@-trade -> shipyard-total-available-with-trade-in * plus-@ -> shipyard-first-extra and shipyard-additional-extra * shipyard-forward-weapon-upgraded-to-@ -> shipyard-forward-weapon-upgraded * shipyard-price-@ -> shipyard-price * character-a-@-from-@ -> character-generic-description * sysdata-billion-word has been replaced with sysdata-pop-value, which allows more flexible formatting of population values * planetname-derivative-suffix has been replaced with planetname-possessive. The default planetname-possessive references planetname-derivative-suffix for compatibility (it isn't used anywhere else). * New: number-negative * New: credits-negative * New: sysdata-tl-value * New: sysdata-radius-value * Removed: extra-@-@-(long-description) * Removed: extra-@-@-@-(passenger-berth-long-description) * Removed: shipyard-forward-weapon-upgraded-long * Removed: shipyard-selling-price-@ OXP Verifier: ------------- * Validates syntax (but not keys) of all plists * Validates syntax of all JS files * Updates to shipdata plist verifier Bug fixes: ========== * Restore something like the 1.76 behaviour in terms of alloy plates in debris * Speech synthesis slightly more conservative in detecting 'credits' abbreviation * Fixes to some core ship role lists * Show JS error locations properly * Better handling of various ZIP formats in the OXZ manager * Fix slowness in market screen * Corona_flare property works more consistently * Better handling of system info changes in Nova mission * Apply higher safety factor to docking * Contracts no longer generated to Nova systems * Suns now display properly at extreme range * Some missing commands added to the Joystick mapper * Fix non-alphanumeric characters not being available on mission screen * Fix problem with large OXZ downloads on Windows * Fix some problems with docking of fast and fast-turning ships * Fix display problem with sun corona in rare cases * Fixed key 5 switching to status screen when used during save game filename entry. * Fix Linux uninstall script error handling * Fix errors with escape pod launch from subentities * Fix consistency errors on ship library screen * Ship registration forms now return to interfaces page * Fix error with "OUTER_SYSTEM" positions in very large systems * Fix bug where ship.checkScanner would detect a docked player and other invalid targets. * Windows and Linux ports: Gamma is now a generic game option, not a save file one. Fixes unexpected gamma resets when starting a new game. * Windows port: Fixed unnecessary double OpenGL initializations under certain resize window conditions. * Windows port: Fixed bugs in handling a maximized game window. * Windows and Linux ports: Fix for inability to unset joystick buttons beyond 16. * Fix return type of EquipmentInfo.requiredCargoSpace * Handle sun glare correctly also for too tall or too wide game windows. * Redefining the currently selected waypoint no longer resets the compass * Assassins no longer chase players who only take low-risk contracts * Zero-distance system doubles now handled better on chart * More docking protocol bug fixes * Various chart view settings now preserved in the save game * Some full-screen and multi-monitor fixes * NPC ships entering hyperspace near massive stations should be clearer * Destination systems for long-range NPCs now set correctly * checkScanner method now correctly excludes unpowered ships * Plasma turrets won't fire when cloaked if cloakPassive is true * AI errors related to system ID 0 fixed * Bug making tutorial extremely difficult to complete if player obtained a bounty fixed. * Fix error with cargo dumping when reloading save after death * Fix NPC turret aim * Some OXZ manager bugs related to duplicate manifest IDs fixed ------------------------------------------------------------------------------- Changes between Oolite 1.78 and Oolite 1.80: General: ======== * Automatic expansion pack installer now available (not all existing expansion packs are available for automatic installation; manual installation still possible as before) * New game start screen, including access to a keyboard control summary and a ship specification library * New Tutorial option for starting a game, for a basic flight training course Gameplay: ========= * Coordinate precision increased, allowing ships to travel much further from the witchpoint without problems * Strict mode now only disables OXPs rather than making other gameplay changes * Ship hostility indicator no longer depends on weapon range * New equipment: Integrated Targeting System * Standard HUDs now include primable equipment dials, compass target naming, and multi-function displays. HUD layouts reorganised slightly to fit the extra components in. ';' and ':' keys manage multi-function display (MFD) selection * Major changes to NPC behaviour and AIs, including new courier, smuggler, assassin, pirate leader, bounty hunter leader, pirate raider, thargoid raider and police blockade roles. Significant increases in diversity of ships used for existing roles. * Widened the difficulty gap between Corporate States (now safer) and Anarchy (now far more dangerous) systems. System danger now also depends to an extent on events in neighbouring systems. * Saving the game at stations other than the main system station is now possible; stations added by expansion packs must be flagged by the expansion pack as valid save locations for this to work. * Save game file name and player commander name are now independent. Interface added on F4 to set player commander name and player ship name. * Docking computers now fly a bit faster * Improved short-term and long-term assessment of player actions for more realistic and varied NPC responses * Parcel and passenger contracts made much more varied. High-price high-risk contracts added. Reputation scale increased. * Station rotation slowed down * Number of cargo containers used by Gold, Platinum, Gems (e.g. after scooping) now shown on the manifest screen. Graphics: ========= * Graphics settings have changed to merge "shader settings" and "reduced detail" into a single option. There is an 'extra detail' option suitable for dedicated graphics cards. * New look for the sun, including improved corona and a glare effect * New explosion graphics * Core ship models and textures replaced with significantly improved ones by Griff/CaptSolo * New planet and atmosphere graphics (requires 'extra detail') * Default HUD adjusted a little to improve appearance of text Sound: ====== * Now uses the OpenAL library to give fully positional sound. Supports stereo speakers up to 7.1 or binaural HRTF with headphones. * iTunes integration works again with iTunes 11 on the Mac. Controls: ========= * Default key for dump cargo now "Shift-D", not "d", to reduce chance of pressing accidentally * Number pad keys can now be bound independently of arrow keys * Number keys can now be bound independently of function keys * GUI direction keys now independent of flight direction keys * With an Advanced Navigation Array, the '?' key now cycles through different colour highlights * New ';' and ':' keys for multi-function displays * 'tab' and '0' keys are now fast activation keys for primable equipment. Equipment previously using those keys has been rewritten to be primable equipment and will automatically be initially assigned to those keys if it is installed. New interface on F4 to rebind these keys. * New joystick axis configuration interface, allowing much more precise control Settings: ========= * --noshaders command line option to temporarily start Oolite without shader support, for debugging graphics card problems. * Windows and Linux ports: Default spoken messages voice changed to female. Expansion pack development: =========================== Packaging: ---------- * New compressed OXZ format for easy - and automatic - expansion pack distribution. * Default Javascript properties set from manifest.plist General: -------- * The system populator is now run via Javascript and can be modified by expansion pack scripts. There is also a system repopulator to allow replacement of expected ship losses or departures. * shipdata.plist reorganised to make heavy use of templates. This allows shipset OXPs to be written to take advantage of future changes to core shipdata. * Javascript may be used as a language for ship AIs. Numerous extra JS properties, methods and handlers have been added to allow this. * Priority-based AI library available for writing Javascript AIs in, with a wide range of predefined behaviours and easy addition of custom behaviours * Multi-function displays for arbitrary persistent in-flight text display * Primable equipment dial added to HUD * Sun colour and background star colour are now separated * Ship cargo is now defined when ships are created * Mission screens can now have an arbitrary text entry prompt * Waypoints can be added - like a compass target, but not attached to any other entity. * [oolite_key_FOO] description expansion describes keyconfig entry key_FOO * Javascript representation for ship exhausts * Wormholes have a read-only Javascript representation * Javascript representation for flashers * Description and mission text expansions now use a much better random number generator New properties: --------------- * entity.isWormhole * oolite.gameSettings.keyConfig * planet.name * player.cargoReputationPrecise * player.parcelReputationPrecise * player.passengerReputationPrecise * player.roleWeights * player.setPlayerRole(role [,index]) * player.ship.multiFunctionDisplays * player.ship.renovationCost * player.ship.renovationMultiplier * player.ship.viewPosition* * ship.AIScript * ship.AIScriptWakeTime * ship.autoWeapons * ship.beaconLabel * ship.cargoList * ship.crew * ship.destinationSystem * ship.dockingInstructions * ship.energyRechargeRate * ship.exhaustEmissiveColor * ship.exhausts * ship.extraCargo * ship.flashers * ship.homeSystem * ship.isFleeing * ship.isMinable * ship.isTurret * ship.markedForFines * ship.shipClassName * ship.shipUniqueName * ship.sunGlareFilter * station.allegiance * station.market[*].legalPenalty * sun.name * system.allDemoShips * system.populatorSettings * system.stations * system.waypoints * system.wormholes * visualeffect.beaconLabel * wormhole.arrivalTime * wormhole.destination * wormhole.expiryTime * wormhole.origin New methods: ------------ * Ship.keys() (static method) * Ship.keysForRole(role) (static method) * Ship.roleIsInCategory(role,category) (static method) * Ship.roles() (static method) * Ship.shipDataForKey(key) (static method) * dock.isQueued(ship) * player.endScenario(key) * player.ship.hideHUDSelector(selector) * player.ship.resetScannerZoom() * player.ship.setMultiFunctionDisplay(index,key) * player.ship.setMultiFunctionText(key, string) * player.ship.showHUDSelector(selector) * ship.becomeCascadeExplosion() * ship.broadcastCascadeImminent() * ship.broadcastDistressMessage() * ship.checkCourseToDestination() * ship.checkScanner() * ship.damageAssessment() * ship.dumpCargo (change to specify how many items to dump) * ship.enterWormhole(hole) * ship.findNearestStation() * ship.getSafeCourseToDestination() * ship.markTargetForFines() * ship.notifyGroupOfWormhole() * ship.offerToEscort(mother) * ship.patrolReportIn(station) * ship.performAttack() * ship.performCollect() * ship.performEscort() * ship.performFaceDestination() * ship.performFlee() * ship.performFlyToRangeFromDestination() * ship.performHold() * ship.performIdle() * ship.performIntercept() * ship.performLandOnPlanet() * ship.performMining() * ship.performScriptedAI() * ship.performScriptedAttackAI() * ship.performStop() * ship.performTumble() * ship.recallDockingInstructions(); * ship.removeDefenseTarget(target) * ship.requestDockingInstructions(); * ship.requestHelpFromGroup() * ship.setCargoType(type) * ship.setCrew(crew) * ship.throwSpark() * station.abortAllDockings() * station.abortDockingForShip(ship) * station.launchEscort() * system.locationFromCode(code) * system.setWaypoint() New handlers: ------------- * ship.cargoDumpedNearby(cargo,dumper) * ship.defenseTargetDestroyed(target) * ship.escortRejected * ship.helpRequestReceived(ally,enemy) * ship.shipAIFrustrated(context) * ship.shipAchievedDesiredRange * ship.shipNowFacingDestination() * ship.shipWasDumped() * ship.shipWitchspaceBlocked(blocker) * ship.stationWithdrewDockingClearance * ship.wormholeSuggested(hole) * station.stationDockingQueuesAreEmpty * station.stationReceivedDockingRequest(ship) * world.guiScreenWillChange fires on changing to GUI_SCREEN_INTERFACES * world.startUpComplete * world.systemInformationChanged(gal,sys,key,newValue) Plists: ------- New plists: * manifest.plist: required for OXZ format distribution * role-categories.plist: supersedes and extends pirate-victim-roles.plist * scenarios.plist: allows alternative game starts to be added * shiplibrary.plist: adds ships to the start game ship database * The demoships.plist file no longer has any effect as there is no longer a demo ships screen. New properties: * equipment.plist: fast_affinity_defensive * equipment.plist: fast_affinity_offensive * equipment.plist: sort_order * hud.plist: alert_conditions * hud.plist: align * hud.plist: color_critical * hud.plist: color_high * hud.plist: color_low * hud.plist: color_medium * hud.plist: color_surround * hud.plist: drawASCTarget: * hud.plist: drawPrimedEquipment: * hud.plist: drawWaypoints: * hud.plist: drawWitchspaceDestination: * hud.plist: multi_function_displays * hud.plist: reticle_scale (scanner targeting enhancement) * hud.plist: scanner_non_linear * hud.plist: scanner_ultra_zoom * planetinfo.plist: nebula_color_1 * planetinfo.plist: nebula_color_2 * planetinfo.plist: planet_name * planetinfo.plist: populator * planetinfo.plist: repopulator * planetinfo.plist: sun_name * shipdata.plist: auto_weapons * shipdata.plist: escort_roles * shipdata.plist: exhaust_emissive_color * shipdata.plist: sun_glare_filter * shipyard.plist: renovation_multiplier * shipyard.plist: ship_name * shipyard.plist: ship_class_name Shaders: -------- * tradeInFactor uniform for player ship * Simplified shader mode no longer exists Bug fixes: ========== * Stop previously unknown targets appearing in the target memory * Reduce need for JS garbage collection while in flight * Fix some bugs with combat flight causing misses on really easy shots * Several Javascript crashes fixed * Errors in NPC escape pod use for lighter ships fixed * Fix bug with station subentity initialisation * Fix bug in ship.forwardWeapon property * Consistently use ship scanner ranges * Asp can now equip a fuel scoop * Core ships with only one missile pylon can no longer buy the Multi-Targeting System as it's only useful to ships with at least two missiles. * Fix some bugs with the escape pod launch sequence * Fix bug with reticle target sensitivity * Fix some bugs with compass dial requirements * Fix bug when pressing opposite movement direction keys together * Fix various random number generation bugs * Clean up momentum transfer in explosions * Fix error in manifest screen paging * Fix problem with duplicate passenger names in contract generation * Fix some issues moving in and out of strict mode System requirements: ==================== General ------- * Use of the "extra detail" graphics setting is likely to require a dedicated graphics card for best performance. Mac-specific ------------ * Oolite now requires Mac OS X 10.6 or later, and is 64-bit only. ------------------------------------------------------------------------------- Changes between Oolite 1.77.1 and Oolite 1.78: * Bug with behaviour of ship.forwardWeapon when ship had no forward laser but subentity did fixed. * Shaders re-enabled on AMD R600/R700 if drivers recent enough * Game over message no longer depends on HUD settings * Cosmetic bugs with player escape pod sequence fixed * Player ship doppelganger no longer persists in obscure cases * New ships always have weapons online * Target-sensitive reticle accounts for weapon offsets * Include fix for build process on lunar-linux Changes between Oolite 1.77 and Oolite 1.77.1: * Bug with planetary cloud display fixed * Cargo/Passenger contracts correctly report premium/advance again * Fix to escort scan class initialisation * Fix for mission screen not allowing exit when HUD off * Fix quaternion constructor to produce identity quaternion as documented when called as new Quaternion(). * guiScreenChanged now fires on transition when launching * Yaw and velocity are now also zeroed when launching from a station * Autopilot status reset on death * Regression: HUD n_bars now works again * Bug with NPCs continuing to lock on dead ships fixed * Laser shot positioning fixed * Bug with manifest checks in guiScreenChanged when launching fixed * Reasonably skilled pilots will now remember to look where they're going when fleeing * Shields now also protect subentities * Edge-case for damage to main station fixed * Frustum culling over-enthusiasm fixed * Some 'ghost' ship bugs fixed * Fix mission screen crash with numeric choice keys * Fix bug with cancelling galactic jump by script * Fix bug in validation for station interface creation * Use correct types in JS Station.market object * Fix bug with docking with stations where dock is very slightly off-axis * Fix contract reputation calculations so that negative rep works properly * Fix inconsistencies with cloaking device behaviour * Fix calculation of heat damage to subentities * Fix updates of energy, laser temp, alert condition related to autopilot * Fix for crash when pressing up on long manifest screen * Fix conflict between "pilot" and "unpiloted" shipdata keys * Fix equipment condition check in player.replaceShip() * Fix flasher scaling in visual effects * Add fuel scoop icon to small HUD * Lighten mouse cursor cross color * Sort save-game list alphabetically * Slightly lengthen deadlines on parcel contracts to avoid impossible ones * Prevent docking with dead stations * Fix memory leaks. * Remove "performBroadside" AI command * Fix scanner display bug * Display correct key in 'pause' message * Fix rotational_velocity for subentities * Fix shipEnergyBecameFull event to fire as documented ------------------------------------------------------------------------------- Changes between Oolite 1.76.1 and Oolite 1.77: Gameplay: ========= General: -------- * Enabled reverse-stepping through list of beacons shown by the Advanced Space Compass. The default key is '|' (Shift-'\'). * Energy bomb made strict mode only. * Ships with a bounty will often avoid the main station * A certain classified device is now possible to repair and has had minor changes to its behaviour. * Improvements to sound effect handling for scoop and laser * Several efficiency improvements for smoother gameplay * New combat behaviour and AI, including: More consistent missile explosion behaviour Smarter Thargoids Smarter use of turret weapons Better reactions against cloaked opponents Better reaction to deployment of Q-mines NPCs can now use side weapons NPC lasers now equivalent to player lasers in terms of rate of fire and overheating. * Gamma control for SDL builds * System populator cleaner, and hermits now semi-persistent * Longer visibility distance * Criminal behaviour sanctions now equivalent for player and NPCs * Witchspace exit speed now depends on the lead ship, and player following NPC through a wormhole now behaves better. * Keyboard precision mode * Stations can now have multiple docks * Docking Clearance Protocol now a Game Option * Interfaces screen on docked F4 for accessing arbitrary station or ship systems. Passenger and cargo contracts moved to this screen; parcel contracts added. * Can switch to custom views while paused * Improved rendering of planetary atmosphere and sun * New 'b' key for activation of secondary function of primable equipment (e.g. a mode switch) * New starfield textures * New laser textures * New exhaust textures * Mac specific: ctrl-cmd-f is the new toggle key for full screen display Expansion Pack Development: =========================== AI: --- * New events: DEFENSE_TARGET_LOST DEFENSE_TARGET_DESTROYED CASCADE_WEAPON_DETECTED ATTACKER_MISSED * New commands: addFoundTargetAsDefenseTarget addPrimaryAggressorAsDefenseTarget broadcastEnergyBlastImminent clearDefenseTargets findNewDefenseTarget performLandOnPlanet performScriptedAI performScriptedCombatAI recallStoredTarget setTargetToLastStation setTargetToRandomStation storeTarget * Higher skill combat AIs available through 'accuracy' setting * Full frame-by-frame control over ship AI now available from script Config files: ------------- * Required space in equipment.plist now works as intended * damage_probability setting in equipment.plist * Ship role list now automatically includes "[shipDataKey]"; roles beginning "[" forbidden. * Shipdata weapon_facings property to set available laser mounts * bright_fraction property for flasher subentities * Crosshair plist files separate from HUD * Several new descriptions.plist and missiontext.plist entries * New effectdata.plist to define visual effects - items with a visual appearance, and perhaps a scanner and compass signal, but no physical presence. These can be used to supplement/replace break patterns, among other effects. * shipdata.plist exhaust z-size now acts as a z-scale within the range 0.5 to 2.0. Out of range values default to 1.0 = standard size * cloud_alpha property in planetinfo.plist to control atmosphere opacity Scripting: ---------- * Various debug console improvements * Additional 'b' key and mode() function for primable equipment * "Dock" entity type, for control over docking and launching, especially in multidock stations. * More parameters for mission.markSystem, to allow custom markers * Condition scripts for equipment, ships, and shipyard to replace legacy conditions. * Numerous additional options for mission.runScreen, and related properties for mission objects * "VisualEffect" entity type, for management of effects defined through effectdata.plist * guiScreenChanged fires for entering mission screens * New properties clock.adjustedSeconds entity.isInSpace player.ship.hyperspaceSpinTime player.ship.missilesOnline player.ship.price player.ship.serviceLevel ship.*Weapon now editable ship.AIFoundTarget ship.AIPrimaryAggressor ship.autoAI ship.beaconCode ship.boundingBox ship.commodity ship.commodityCount ship.currentWeapon ship.dataKey ship.desiredRange ship.destination ship.laserHeatLevel ship.maxEscorts ship.pitch, ship.roll, ship.yaw ship.weaponFacings ship.weaponPosition* station.breakPattern station.hasShipyard station.market system.breakPattern * New methods global.autoAIForRole(role) player.replaceShip(key) player.ship.beginHyperspaceCountdown(delay) player.ship.cancelHyperspaceCountdown([delay]) player.ship.resetCustomView() player.ship.setCustomView(position,orientation) player.ship.takeInternalDamage() ship.dealEnergyDamage() ship.getMaterials() ship.getShaders() ship.setBounty(amount,reason) station.setMarketPrice(cargo,price) station.setMarketQuantity(cargo,price) system.addVisualEffect(key,position) * New events cascadeWeaponDetected(weapon) dayChanged(day) equipmentRepaired(equipment) playerBoughtCargo(commodity, units, price) playerSoldCargo(commodity, units, price) shipBeingAttackedUnsuccessfully(attacker) shipBountyChanged(delta,reason) shipHitByECM {new 'whom' argument} shipLandedOnPlanet(planet) shipScoopedFuel() Miscellaneous: -------------- * TAF changes only possible when FPS display is active * Planettool updated Bug fixes: ========== * 'sendAllShipsAway' no longer tries to send ships without a hyperspace motor away. * Added missing new keyboard controls to the BBC keyconfig.plist * Scoop icon fixed for ships carrying special cargo * Docking behaviour now more reliable at low frame rates and at odd approach angles * Negative ship distances on HUD fixed * Laser position display fix * Target system memory expansion now works properly with cloaked ships * Cargo rounding and save/load bugs fixed * Advanced Navigation Array display bug fixed * Being on board a witchspacing carrier now works more sensibly * Larger witchbuoys no longer a collision risk * Several crash bugs fixed * Turret declarations now work as intended * Planetinfo overrides now cleaned up from savegames * Problems with wormholes in zero-distance doubles fixed * Fines now only applied at main stations * Attempting to calculate a route to or from interstellar space no longer crashes * Scooped and released escape pods work properly System requirements: ==================== Mac-specific ------------ * Oolite now requires an Intel processor and Mac OS X 10.5 or later. ------------------------------------------------------------------------------- Changes between Oolite 1.76 and Oolite 1.76.1: * Fixed potential bug loading player with no mission_variables dictionary. * player.ship.launch() can now also be used within the shipWillDockWithStation and the shipDockedWithStation handler without side effects. * Ships that have a specific commodity defined as cargo, no longer get their bounty reduced to 10% when the pilot ejects. * The scoop icon now correctly shows a full cargo hold when special cargo is transported. * Fixed a problem with the shipyard screen when a ship with chance=1 defined non-installed optional equipment. * Don't disgorge and revive dead ships from wormholes. * 32-bit GL contexts are now strongly preferred on SDL systems, with 16-bit fallback. * Fixed weapon facing not resetting to forward view when launching from station. * takeSnapShot() is excluded from the timelimiter. * Added code to prevent overcorrection pitch and roll with low frame rates. Should lower the change of ships jumping between two headings, without proceeding. * weapon_range and weapon_energy keys in the turret subentity declaration now work as intended. Values are maximised at 7500 and 100 respectively. * Buggy consoleMessage() behaviour when called from JS debug console has been rectified. * JS debug console macros are saved when Oolite exits to desktop in all platforms, as originally intended. * Regression fix: in the log, the searchPaths.dumpAll is shown once every time searchPaths change (strict/unrestricted mode). * No more negative ship distances, see http://aegidian.org/bb/viewtopic.php?t=11785 * Fix for guiScreenWillChange for system data screen not firing when double clicking on systems. * Fixed crash with malformed gui declarations inside hud plists. * Fixed timer related crashes. * The Windows Oolite executable is now large address aware, allowing for more than 2GB of memory to be allocated to the game. * Fix for bug where standard subentities defined after any turret subentity would exhibit turret behaviour. * Savefiles with wrong passengers and/or passengers berths data are now handled a bit better. * Fix bug where equipment depending on currently damaged equipment would be silently removed from the player's ship on game load. * Target system memory expansion doesn't try to restore locks on cloaked ships anymore. * Fixed bug with resetting of passenger contracts on ship change. * Fixed Nav Array not showing distance and time information when target system was selected by using the Find Planet method. * Frame Callbacks now transfer the correct time difference when timeAccelerationFactor is active. * The available_to_all key is now also respected by JS methods that add equipment. * Improved the docking approach when arriving from the backside of a station. * Fixed fuel scoops sound loop issue, discussed in http://www.aegidian.org/bb/viewtopic.php?f=4&t=11611 * Fixed intermittent crash on missile removal. ------------------------------------------------------------------------------- Changes between Oolite 1.75.3 and Oolite 1.76: Gameplay: ========= General: -------- * When using docking clearance, player now gets an extra few seconds when very close to the dock. (Bug #17997) * Enhanced missile logic for missiles approaching head-on to a target. * Enhanced damage calculations for collision and missile damage; damage is no longer dependent on target size. * Changed the manifest screen to better handle situations when too much cargo, passengers, or contracts were being displayed. * All mission screen choices should now also be selectable by mouse. * The game now ignores all keys except 'Space' on the Arrival Reports screen. * Enhanced mouse controls to allow maximum pitch. * It's now possible to pause the game when the autopilot is running. * Improved AI for ships scooping cargo. * Improved sun-skimming NPCs. Bug fixes: ---------- * Non-tonnage commodities are now cleared correctly after a game reset. * Avoid negative cargo capacity under certain conditions, especially in conjunction with passenger cabins. * Various additional checks to ensure ship equipment is not lost when it was already installed (eg, damaged during a fight, loading a Commander) * Fuel price is now displayed correctly for ships which have a fuel charge rate other than 1.0 * Large ships no longer block the launch queue in stations. * Fix NPCs getting far-away holding positions while waiting to dock. * Docking Clearance: Player is no longer given clearance while ships are in the launch queue. * Aft-firing weapons on an NPC no longer trigger forward-facing weapons. * No unnecessary auto-save immediately after a manual save. * Various bug-fixes to better handle entities when the Universe is full which could lead to crashing. * No more double spaces in the randomly generated planet descriptions. * The short range chart no longer shows pre-nova system data for nova systems. * Regression fix: The autopilot disengages when pressing 'fast-dock'. * Escape pods should no longer disappear immediately after they launched. * A damaged Galactic Hyperdrive now correctly aborts the jump. * Fixed the first witchjump after a galactic jump. * Improved handling of ships exiting wormholes; various values are properly reset now and NPCs following the player should no longer crash into the NavBuoy. * It's no longer possible to pause the game in invalid situations (such as the witchspace tunnel). * Fix docking music either ending before docking was complete, or continuing to play after a hyperspace jump. * Sounds no longer continue to play after player death or resetting the game. (Bug #18108) * Various fixes for large escort groups; especially Thargoid/Thargons. * Fix Thargoid message overflow. (Bug #18055) * NPCs now have the same inactive cloaking behaviour as players. * Ignore passengers and contracts in hacked strict mode gamesaves. * Fixed various memory leaks. Mac-specific: ------------- * Fix for incorrect playlist selected in iTunes after startup. * Switching iTunes integration off now pauses iTunes. * iTunes is muted while the Oolite theme music is played. Windows and Linux: ------------------ * Spoken messages should no longer crash the game. * Fixed regression: it should again be possible to select full-screen for computers with non-standard resolutions. * Fixed the game screen getting resized to the size of the splash screen. * Fixed the player ship going crazy when switching to mouse controls. * Various keyboard fixes to ensure keys are correctly mapped. Expansion pack development changes: =================================== Ships: ------ Equipment: ---------- * The plasma cannon now uses the 'weapon_position_' keys from shipdata.plist. * Weapon names are now always displayed as " laser", irrespective of what is defined in equipment.plist and descriptions.plist. Other Configuration Files: -------------------------- * Script names are no longer expanded unless enclosed by [] in mission text or mission info. * Resizing message_gui & comms_log_gui via hud.plists now retains the comms log and the last 2 message lines. * Added the key to turn the HUD on and off to keyconfig.plist (default: 'o'). * A wrongly defined 'titleKey' for missionScreens now logs a warning instead of an internal error. Javascript: ----------- * Fixed rounding errors for the "angleTo" method which sometimes returned a NaN for identical vectors instead of zero. * Fixed the "rotationTo" method which sometimes returned an illegal quaternion for some anti-parallel vectors. * Fixed "isValid" for entities which were destroyed during the current game tick; always returns 'false' now. * system.addShip() & system.addGroup() now only returns the ships actually added to the Universe, instead of the whole group. * viewDirectionChanged() now fires correctly when switching between gui screens. * The "startUp" event should now fire properly after the Universe is fully initialised. * Fixed reliability of the Javascript engine when resetting itself. * Improved logging when loading a commander fails due to JS issues. * The frameCallback is now only called when the game is actually running. * When the player aborts a jump countdown, the type of the jump is now reported. Scripting bug fixes: -------------------- * Regression fix: the legacy_spawnShip function should work again. * system.name, system.population, etc. should now report the correct values when in interstellar space. * Changes to system.name, system.techLevel, etc. are now shown correctly on the short range map. * ships should now always have the correct orientation after being spawned. * restoring sub-entities on the player ship now adjusts the trade-in factor correctly. * The AI commands 'becomeExplosion' and 'becomeEnergyBlast' now also trigger the shipDied event handler. * The game no longer freezes when a script tries to resurrect a ship after it dies; the ship just dies now. (Bug #18054) * Specifying 0Cr mandatory equipments no longer freezes the game. * Specifying illegal flasher declaration no longer causes an exception. New AI properties and methods: ------------------------------ * checkHeatInsulation - returns either INSULATION_OK or INSULATION_POOR. OXP Verifier: ------------- * Fixed bug in the oxp-Verifyer not recognizing 'escorts' in shipdata. * Added CLASS_MINE to the oxpVerifier. ------------------------------------------------------------------------------- Changes between Oolite 1.75.2 and Oolite 1.75.3: * Subentities are now set up correctly when buying a new ship. * Ships now take subentities into account when deciding whether they can dock. * Fixed ghost wormholes. Wormholes can no longer have a jump range of zero. * The fuel indicator is now drawn correctly in interstellar space. * Additional guards against out-of-range targets for the player. * Sounds now stop playing when starting a new game. (Bug #18108) * [Mac] Growl display support is disabled due to upcoming changes to Growl. * Ships with sun skimmer AI in interstellar space no longer cause errors in the log, and AIs can handle the situation by responding to NO_SUN_FOUND. * "sunskim-trader" added to default pirate victim roles. * interstellar_undocking now works when in interstellar space 0 LY from a system. * HUD changes are now deferred, so changing HUDs during HUD drawing is safe. * The debugging function callObjC() has been disabled. ------------------------------------------------------------------------------- Changes between Oolite 1.75.1 and Oolite 1.75.2: * New Linux/POSIX installer. * Disabled shaders on certain ATi GPUs using Gallium drivers because of crashes. * Clock adjustments now accumulate in the obvious way (Bug #17969). * Improved texture filtering for planets and generated emission maps (in non- shader mode). * Fixed bug where a ship was apparently attacking itself whenever one of its subentities was taking energy damage. * Friendly fire between ships in the same group, and between police, military or thargoid vessels, no longer leads to hostilities. * Ships following the player through a wormhole now have jump rings. * Ships with beacon codes now retain them after jumping to a new system. (Bug #18068) * Fixes to mass-dependent fuel pricing. * Fixed mouse control starting in yaw mode. (Bug #18052) * Descriptions.plist strings are now scanned for "%n", which could be used maliciously on GNUstep systems. * New AI message: ACCEPTED_ESCORT. * Various bug fixes, inluding bugs #17916, #17919 and #18083. JavaScript: * Raised certain JavaScript time limits. * The shipSpawned() event now fires for the main station. * Fixed scripted misjumps not working when set from within the shipWillEnterWitchspace handler for wormholes created by NPCs. * NPCs in interstellar space can now jump back to their origin system. (Bug #18082, partial) * Scripts can no longer give the player an out-of-range target. (Bug #18011) * Q-mines awarded to NPCs in flight now work. (More accurately, q-mines for NPCs used to be represented internally as EQ_ENERGY_BOMB instead of EQ_QC_MINE.) Known bugs: * Intermittent crashes when removing pylon-mounted equipment (http://www.aegidian.org/bb/viewtopic.php?f=3&t=9761) * Freeze when script resurrects a ship being killed by a Q-bomb (Bug #18054) * AI crash triggered by large groups of thargoids (Bug #18055) * Bugs related to repeated misjumps (Bug #18082) * Unhelpful docking clearance behaviour changing priorities when a ship is on final approach (Bug #17997) * Fuel indicator is not drawn correctly in interstellar space (Bug #18097) * Q-bomb explosions are not drawn properly on some systems (Bug #17276) * In some conditions, the "break pattern" effect is drawn off-centre. * Problem with roles of tharglets used as subentities (Bug #17979) * Problem with frame callbacks and jumps (Bug #17956) ------------------------------------------------------------------------------- Changes between Oolite 1.75 and Oolite 1.75.1: * Joystick deadzone restored to previous value (Mac OS X not affected). (Bug #17945) * (Mac OS X) Support for game pads that don't pretend to be joysticks. * (Windows) Uninstaller is now more discriminating about which files it deletes. * The fast docking button now defaults to the main station if within the aegis, even if it's outside scanner range, as intended. (Bug #17923) * Ship mass calculations, and thus mass lock behaviour, have been restored (except for very small ships). (Bug #17940) * Thargoids no longer randomly shoot in entirely the wrong direction. * Fixed "jousting" behaviour where attacking ships would veer off after one hit. * Various fixes for wormhole behaviour in interstellar space, post-nova systems and with ships with escorts. * Interim fix for mass-dependent fuel prices not working. (Bug #17947) * Various bug fixes and cosmetic tweaks, including but not limited to bugs #17944, #17968, #17975, #17992, #17993, and #17995. JavaScript: * JavaScript Ship equipmentStatus() now works for primary and secondary weapons. * Calling spawn() on a subentity previously put the spawnees near the witchpoint. It now works like calling spawn() on the parent ship, which is suboptimal but less confusing. Relying on the current behaviour is not recommended as it may be improved in future. * JavaScript system.addShipsToRoute() now adds ships within scanner range of the route, and ships added with system.addGroupToRoute() are clustered. * JavaScript SystemInfo systemsInRange() once again works as an instance method (which is now the recommended usage). * New ship event entityDestroyed(). This fires immediately after the ship becomes invalid, regardless of the reason, except when the game restarts. * New SystemInfo property internalCoordinates and new PlayerShip properties cursorCoordinatesInLY, galacticHyperspaceFixedCoordsInLY and galaxyCoordinatesInLY. This resolves the problem that different coordinate systems were being used for galactic coordinates in different contexts. * Mission screens can now be run in flight without generating warnings about the player being both docked and not docked. (Bug #17953) ------------------------------------------------------------------------------- Changes between Oolite 1.74.2 and Oolite 1.75: Gameplay: * Smarter, more aggressive NPCs - for example, they are now able to shoot you when you cunningly stand still. * New master weapon lock key (default: _) disables primary and secondary weapons, and turrets. * Simplified autopilot controls: C (or key_autopilot) attempts to autopilot- dock with the current target station - your target if any, otherwise a station within scanner range, favouring the main station. Shift-C (now key_autodock instead of key_autopilot_target) is the same, except it will fast-dock if permitted. Shift-D is no longer used. * New "primable equipment" concept: equipment can produce scripted effects without having to be a missile. New keys: Shift-N and Control-Shift-N cycle through the list of primable equipment, and N activates the currently selected equipment. (Key config keys: key_prime_equipment, key_activate_equipment.) * Target-sensitive reticle mode is now more accurate. * Fuel price now depends on the mass of your ship. * Jumping between overlapping systems now uses 0.1 LY worth of fuel. * A hyperspace misjump now takes 3/4 of the time of a successful jump, rather than the full time. (It has to be at least 3/4 to avoid exploits. See http://aegidian.org/bb/viewtopic.php?t=7917 for more information.) * The trumble offer "mission" now repeats as intended, in case you accidentally missed it. * Escape pods now aim for the nearest friendly station/carrier. * The escape pod key/joystick button must now be pressed twice in rapid succession to eject (except in strict mode, or if the escape-pod-activation-immediate hidden setting is set). * Comm log messages are now spoken. (Feature request #4970) Bug fixes: * "Lollipop flashes" eliminated. (Bug #17215) * Hyperspace countdown messages don't repeat themselves. * You can no longer sneak into the contract screen through the back door in secondary stations. * Secondary weapons are now accounted for properly when evaluating your ship. (Bug #17450) * Fixes to joystick configuration (Bug #17482, Bug #17490) * The usual indescribable yet important "minor tweaks and fixes". Miscellaneous: * New icon by seventh. * Reduced memory usage of various special effects and JavaScript objects. On the other hand, the new JavaScript engine uses more memory. Mac-specific: * The Dread Sound Bug of Doom is fixed. (Bug #17214) * Joystick support. * Automatic update support (Sparkle). * The Oolite Screen Shots folder can now be moved from the desktop and Oolite will continue to use it (as long as it isn't renamed or moved to the trash). Windows and Linux: * Due to a bug in GNUstep, older versions cannot read credit balances of more than 429496729.5 credits correctly. 1.75 writes saved games in a way that avoids this problem (even when loaded with old test releases). The Windows version can also read larger credit quantities in old saved games. Expansion pack development changes: Ships: * New shipdata.plist keys: - allows_auto_docking (station only): if true, player autopilot docking is permitted. Default: true. - allows_fast_docking (station only): if true, fast docking is permitted. Default: false, except for main station. - counts_as_kill: if false, the ship does not count as a kill and does not participate in q-mine cascade happy fun time, even if its scan class and other properties would otherwise let it do so. Default: true. - cloak_automatic (NPCs): if true, cloaking device is activated when attacking; otherwise, it must be activated by a script. Default: true. - has_patrol_ships (station only, fuzzy boolean): if true, the station will periodically launch patrols if it has any police ships docked. Default: false, but ignored (treated as always true) for main station. - hyperspace_motor (all ships, including player): required to be able to jump. Default: true. - interstellar_undocking (station only): if true, docking with the station in interstellar space doesn't cause the station to jump to an adjacent system. Default: false. - weapon_range (turret subentities): plasma shot range. Default: 6000. - tunnel_corners, tunnel_start_angle and tunnel_aspect_ratio (station only): these replace docking_pattern_model. See http://www.aegidian.org/bb/viewtopic.php?p=128823#p128823 for more information. * A new ship role, "cinder", is used to generate debris in post-nova systems. The default cinders look like the default asteroids and boulders, but have very high heat shielding. If you make custom asteroids, you probably want to make cinder versions of them. If you're abusing the "asteroid" role to put random stuff in asteroid fields, you don't want to make a cinder version. * Turret weapon_energy now defaults to 25. * Beacon icon data is now handled in a more flexible way (http://aegidian.org/bb/viewtopic.php?p=128487#p128487; Bug #17828). Additionally, it is looked up using the ship's beacon code as a key, instead of its primary role. Equipment: * New equipment.plist extra-dictionary keys: - script: used to implement primable equipment. Equipment scripts have one predefined property, equipmentKey, and one event handler, activated(). - visible: controls visibility on status screen and shipyard blurbs. Default: true. * NPC missiles are now represented as equipment entries instead of ships. For efficiency, it is recommended that all NPC missile types have an equipment.plist entry, even if they're not available to the player. (For example, there is now an EQ_THARGON.) For assistance in setting this up, enable ship.setUp.missiles in logcontrol.plist (or use console.setDisplayMessagesInClass("ship.setUp.missiles", true) in the debug console). Other configuration files: * Sounds that are listed in customsounds.plist are now preloaded to avoid loading delays. Sounds that are played through JavaScript by specifying a file name cannot be preloaded; if you're doing this, and your sound isn't particularly rare, I suggest going through a customsounds.plist entry. * Fortuitously, using customsounds.plist entries from scripts now works properly. * Screen backdrops and overlays can now be scaled, allowing for higher resolutions. (Bug #17201) See http://aegidian.org/bb/viewtopic.php?p=127716 for more information. Additionally, screenbackgrounds.plist entries may be arrays, in which case one element is picked at random. * Planets using cube map textures now rotate around the expected polar axis. Planets using traditional textures don't, but they never did. (Bug #17243) * New HUD dial selector: drawWeaponsOfflineText:. JavaScript: * Upgraded SpiderMonkey to bleeding edge (1.75 uses the same version as FireFox 4.0 beta 11). This adds most ECMAScript 5th Edition features, notably including strict mode, which I recommend all OXPers adopt. * On the other hand, the old SpiderMonkey-specific "strict mode" is disabled by default because it has some false positives. It can be enabled through the debug console by setting console.pedanticMode to true. * Native JSON support may be useful for storing structured data in mission variables. * The JavaScript global and console objects are now replaced when resetting the game, and can no longer be used to smuggle information across sessions. * Vector3D and Quaternion objects can no longer be compared using == and !=, because the wart in the JavaScript engine we were previously exploiting no longer exists. See http://www.aegidian.org/bb/viewtopic.php?f=4&t=8847 for more information. * Scripts are now stopped if they run for more than one second. (Slow Oolite functions are not counted towards the limit. The idea isn't to force a time limit on you but to kill scripts stuck in an infinite loop. If your script is deliberately running for more than a second you probably have problems, though.) During loading and the startUp event, the limit is two seconds. * All Oolite-provided JavaScript methods and properties have been audited for consistent error reporting and sane handling of bad values. In some cases, their error checking has become stricter. In others, they perform normal implicit conversions (like converting a string with a numerical value to a number) instead of failing or crashing. All normal parameter errors should now become JavaScript exceptions; calling an Oolite-defined method with the wrong type of this value will just halt the script, but it's hard to do that by accident. * Oolite now has the ability to write a stack trace when a JavaScript exception goes unhandled or a warning is logged. This helps you find out how you reached the point where the error occurs. This is on by default if the debug OXP is installed, off by default otherwise. You can toggle it by setting console.dumpStackForErrors and console.dumpStackForWarnings if the console is active, or the dump-stack-for-errors and dump-stack-for-warnings hidden settings otherwise. When the console is active, you can also log a stack trace at any point using the special statement debugger;, which will otherwise have no effect. * The JavaScript global object is now exposed as a property of itself, called global. global === global.global. * The player's ship is now treated as invalid after ejecting or dying. * Some exceptions thrown in event handlers, which were mistakenly suppressed before, are now reported. * Frame callbacks: callback functions that are called once per frame, useful for driving animations. See http://aegidian.org/bb/viewtopic.php?f=4&t=8941 for more information. Scripting bug fixes: * The prototype chains for Oolite-defined objects were previously set up incorrectly. This has been fixed. An effect of this is that using methods and properties on singleton constructors instead of their instances stopped working. For the most common cases, System and Player there are compatibility accessors which log warning, much like we've done for deprecated methods in the past. If you want to actually understand what's going on, my best stab at explaining it is at http://www.aegidian.org/bb/viewtopic.php?f=4&t=8968 . * As far as scripts are concerned, the player can now only die once. (Previously, shipDied() would be called twice, once without parameters and once with.) * When the player is killed by a q-bomb, the damageType parameter to shipDied() is now "cascade weapon" (not "energy damage"). * You can now award multiple trumbles, to the delight of customers everywhere. * "Display models" of ships and planets no longer show up in the system's entity lists. You can get at the display model for mission screens with the new mission.displayModel property. The others are intended to be inaccessible. * Sound.load() now handles customsounds.plist keys properly. * Ship exitSystem()/AI performHyperSpaceExit now work in interstellar space. New world events: * playerWillSaveGame(saveType) (Feature request #5095) * escapePodSequenceOver() (called immediately before deciding how to rescue or kill the player after using an escape pod. See player.setEscapePodDestination()). New ship events: * coordinatesForEscortPosition(index) (not exactly an event, but whatever.) * distressMessageReceived(aggressor, sender) * shipTakingDamage(amount, fromEntity, damageType) (Feature request #5077) * shipKilledOther(target, damageType) (fires after target's shipDied(), but before actually blowing up). * shipWillLaunchFromStation(station) (previously restricted to player). Modified ship events: * commsMessageReceived() now takes a second parameter, the sending ship. New JavaScript properties and methods: * Entity collisionRadius * Entity dumpState() (only available if console is active): writes a bunch of junk to the log. * EquipmentInfo canBeDamaged * EquipmentInfo canCarryMultiple * EquipmentInfo isAvailableToNPCs * EquipmentInfo isAvailableToPlayer * EquipmentInfo isVisible * EquipmentInfo scriptName * Global defaultFont (object with one method, measureString()) * Global addFrameCallback() * Global formatCredits() * Global formatInteger() * Global isValidFrameCallback() * Global removeFrameCallback() * Global takeSnapShot() * mission.displayModel: the spinning-ship backdrop, if one is in use. (If you set spinModel to false in the runScreen() parameter object, it will refrain from spinning around for easier manual animation.) * player.setEscapePodDestination() * player.ship.viewDirection * player.ship.weaponsOnline * player.ship.removePassenger() * Ship subEntityCapacity * Ship restoreSubentities() * Ship scriptedMisjump() (previously player-only) * Station allowsAutoDocking * Station allowsFastDocking * System countEntitiesWithScanClass() * System scrambledPseudoRandomNumber() Modified JavaScript properties and methods: * Ship commsMessage() now takes an optional second parameter, the target ship. * Ship equipmentStatus() now returns "EQUIPMENT_UKNOWN" when passed an undefined equipment identifier, instead of throwing an exception. * The Ship property roleProbabilities has been renamed roleWeights. New AI commands: * enterPlayerWormhole * setDesiredRangeForWaypoint * setTargetToNearestFriendlyStation * thargonCheckMother * updateEscortFormation New AI messages: * STATION_LAUNCHED_SHIP Descriptions.plist changes (for translators): * Additions: - number-group-size - number-group-threshold - number-decimal-separator - number-group-separator - autopilot-station-@-does-not-allow-autodocking - autopilot-multiple-targets - autopilot-red-alert - witch-no-motor - witch-user-galactic-abort - weapons-systems-online - weapons-systems-offline - equipment-primed-@ - equipment-primed-none - equipment-primed-none-available - station-docking-clearance-denied - initializing-debug-support - charts-distance-f (short-range-chart-distance-f and long-range-chart-distance-f now reference this; it is not used directly by the game) - charts-est-travel-time-f (short-range-chart-est-travel-time-f and long-range-chart-est-travel-time-f now reference this; it is not used directly by the game) - commodity-market - stickmapper-weapons-online-toggle - stickmapper-snapshot - stickmapper-prime-equipment - stickmapper-activate-equipment - stickmapper-escape-pod - stickmapper-cloak - stickmapper-scanner-zoom - gameoverscreen-escape-pod - snapshots-directory-name - snapshots-directory-name-mac - rescue-reward-for-@@-@-credits-@-alt - capture-reward-for-@@-@-credits-@-alt - speech-synthesis-incoming-message-@ * Deleted/unused: - initialising-universe - speech-synthesis-incoming-message - no longer used directly by the game, used by speech-synthesis-incoming-message-@ so existing localizations will (possibly) work. - autopilot-cannot-dock-with-target - still included, but currently unused. Debug console: * Previously, the console object was available from within the console as console, and anywhere else as debugConsole. Both are now global, and console is preferred. * Profiling support: call :profile to see where spends its time. For more detailed information, as a bunch of JavaScript objects rather than a text dump, use this.profileData = console.getProfile(function() { }). * Tracing support: call :trace to see the hierarchy of function calls made by . (The trace is written to the log.) Tracing and profiling can't be used at the same time. * When the result of an expression is an array or plain object, its structure is now pretty-printed. You can also pretty-print the structure of any object using the :d macro. I suggest getting into the habit of doing this to any object you're curious about. :d player.ship is a good starting point. * The call() method has been renamed callObjC() to avoid confusion with the standard Function.prototype.call(). It now works on more objects - just about everything except vectors and quaternions - and can deal with Objective-C methods that return numbers, vectors or quaternions. ------------------------------------------------------------------------------- Changes between Oolite 1.74.1 and Oolite 1.74.2: * Stopped player from selecting invalid jump destination during jump countdown. * Fixed docking permissions inconsistencies: all hostile stations now refuse docking permissions. * Switching to custom views now allowed in a consistent way. * Weapon repeat rate is now per-weapon. * Current zoom level is now preserved when switching HUDs. * Exhaust plume now resets after hyperspace jumps/scripted teleporting/auto- docking. * Fixed overlapping system confusion when loading games and searching for systems. * All automatically generated sun skimmers should now be adequately shielded. * Fixed occasional “bleached”-looking planets on system info screen. * Fixed wrong distance showing on the short range chart when switching back from the long range chart. * Improved default speech synthesis voice (not Mac OS X). * SystemInfo.systemsInRange() can now be called on a specific SystemInfo object, instead of always applying to the current system. * New JavaScript event: shipAttackedOther() (inverse of shipBeingAttacked(), called immediately after shipBeingAttacked()). * If a script removes a piece of equipment in the equipmentDamaged() event handler, no “equipment damaged” message is generated. * Ship.removeEquipment() now works on damaged equipment. * Fixed substitution priority for expandDescription() and expandMissionText(), the local override dictionary now takes precedence over everything else. * New shipinfo key: station_roll. * Fixed some cases where AIs could break group handling. * Shader errors detected in the link phase (generally, problems involving state shared between the vertex and fragment shader) are now reported properly. ------------------------------------------------------------------------------- Changes between Oolite 1.74 and Oolite 1.74.1: * Scripts can once again award and check for trumbles, and the standard trumble mission once again works. (Limitation: scripts cannot increase the number of trumbles by awarding more. This will be fixed in 1.75.) * The nova mission once again awards the appropriate reward on a successful outcome. * No more "Witchspace engine malfunction" message when entering a post-nova system. * When docked at a secondary station, the default selection on the game menu screen is "Game Options" rather than "Begin New Game". * Fixed annoying audio latency when playing first sound under Mac OS X. * Fixed several JavaScript interface bugs that could cause a faulty script to halt without any error message. * Adjusted graphics settings: shader mode is now restricted to Simple for Intel GMA 950 and ATI Radeon R300 GPU families, and some other tweaks. * Fixed a material synthesis bug where illumination_modulate_color was ignored when using emission_and_illumination_map in shader mode (material test suite test case 7). * Possible fix for random freezes when using speech synthesis in Windows. * Various minor bug fixes and tweaks. New JavaScript methods: * vector3D.fromCoordinateSystem() * vector3D.toCoordinateSystem() ------------------------------------------------------------------------------- Changes between Oolite 1.73.4 and Oolite 1.74: Gameplay * With the Advanced Navigation Array you can now see the fastest (as opposed to shortest) route by pressing control-shift-6 (Mac: control-^). * Mouse control (full screen mode only) can be enabled with a yaw-and-pitch flight model by pressing control-shift-M. (As before, shift-M will use a roll-pitch model.) * The ECM alarm no longer goes of while you’re docked. * The "Missile/Mine armed" message now specifies what type of missile/mine it is. * Greatly improved traffic control dialogues. * Speech synthesis is now available on all platforms. * Improved behaviour on various menu screens. * When dying, the last saved game is now reloaded. (In strict mode, the game restarts.) * Auto-saves are now named "Commander Name (autosave)" instead of just "autosave". There is no auto-save immediately after restarting. Graphics * The default shader mode is now Full except on first-generation shader- capable systems. Full shader mode will be disabled on some or all of those systems in future, but is currently enabled for testing purposes. * [Mac] Enabled "Detailed Planets" option. * Flashers and particle effects look better. Very large flashers no longer pop into view at unexpectedly close range. * Reduced aliasing artefacts ("twinkling") in star field and distant flashers/ particles. * Many of the built-in ships have been edited to fix minor lighting issues. * Light maps are now supported in shaderless mode (on systems that support "texture combiners"). * Improved specular highlights in shaderless mode on most systems. * Hyperspace and dock tunnels look different. * [Linux] Fixed tearing issues (v-sync). * Shader mode changes now take effect immediately. * "Pink oceans" are no longer a lie. Bug fixes * Lasers now hit the right shield, even if they happen to be fired by subentities. * No more messages about broken equipment, lost targets and so forth after you die. * Fixed lighting issues for objects with shaders, notably demo screen and shipyard lighting, and shadows. * Fixed blocked docking queue when ships in the queue die. * The game now distinguishes between overlapping systems properly. * Hot ships now dump hot cargo (and escape pods). * Ships can no longer be sold with both types of energy unit (the regular one is removed). * [Mac] Full screen mode is once again restored when relaunching as appropriate. * [Mac] No longer freezes indefinitely while waiting for iTunes to respond if, say, you have a dialog box open in iTunes. * Collisions are now reported to both ships involved. * It is now possible to use the same mesh with and without smoothing. * Callbacks passed to Oolite JavaScript methods can now access variables in the enclosing context. * JavaScript scripts can no longer survive the transition to strict mode (except by attaching themselves to the debug console). * Lots of boring little crashes, log messages and things. Many lots. Known bugs * Planets' axes of rotation can now be messed up in several new ways. There is still no un-messed-up option. (Bug #17243) * Scooping is too hard. (Bug #17245) * Exhaust plumes jump around stupidly after a jump. (Bug #17246) * New substitution dictionary parameter to expandDescription()/ expandMissionText() has lower priority than description.plist entries, should be higher. (Bug #17193) * Overlays and underlays have a fixed resolution and interact badly with texture rescaling. (Bug #17201, technically old) * Shot cycle times depend on the last weapon fired, not the currently selected weapon. (Bug #17240, old) * Scanner markers appear in the wrong place for one frame when summoning an NPC ship. (Bug #17215, old) * When overriding a market system-wide ("market" entry in planetinfo.plist), the specified market is applied to all stations in the system, not just the main station as was most likely intended. (Bug #17247, old) * [Mac] Occasional bursts of very loud noise, causing the system to compensate by lowering effective volume. (Bug #17214, old) Remember, if it isn’t in the bug tracker, we officially don’t know about it. Miscellaneous * Decreased memory usage, primarily for flashers and textures. * The log file is a bit more readable. * [Mac and Windows] Add-ons, log and screen shots can now be found through menus. * Added support for working around specific 3D hardware issues. In particular, the default shader setting is now hardware-dependent, and point and line smoothing are disabled on some hardware. * [Mac] Oolite now requires Mac OS X 10.4 or later. * [Mac] Oolite is now 64-bit clean in Mac OS X 10.6. * [Windows and Linux]: Screenshots are now saved in PNG format. OXP/scripting * New mission screen model: use mission.runScreen(), ignore everything else. Generally, use missionScreenOpportunity() event to run mission screens. Several of the old mission properties and methods are deprecated. See http://www.aegidian.org/bb/viewtopic.php?t=6860 for details. * All ships now have "real" equipment that can be manipulated through scripts, not just the player. Several new equipment-related methods and properties have been added. Damage status is separated from equipment type (no more _DAMAGED suffixes in JavaScript). ship.hasEquipment() has been deprecated. * Script state no longer persists when the game resets/reloads. In particular, the reset() event is gone; instead, scripts are reloaded from scratch and startUp() is called again. Also, the ability to create "persistent" timers that survived resets is gone. * Background and overlay images: can be set with screenbackground.plist or setScreenBackground() and setScreenOverlay() functions. * Whitelisting is now fully in effect: unpermitted methods in legacy scripts, AIs or HUD dial definitions will be ignored. * The "homebrew" plist parser has been removed. * Ship.remove() now takes an optional boolean parameter, suppressDeathEvent. If true, the ship will not be sent a shipDied() event (and death_actions will not fire.) A new event, shipRemoved(), is sent regardless (before shipDied()). * The shipLaunchedFromStation() event now passes the station as a parameter. * New context string for shipDied(): "cascade weapon". * JS timers stop if their callback’s "this" object (the first parameter to the constructor) dies. * expandDescription() and the new expandMissionText() can optionally be passed a dictionary of substitution pairs, as in: expandDescription("My ball is [my_color].", { "my_color": "red" }); Bug: at the moment, description.plist values take precedence. In 1.74.1, this will be reversed. * Ship models can now include per-vertex normals, so you can import models with smooth parts directly from a real live 3D editor. Unfortunately tangent generation for such files is completely broken, so they can’t be normal mapped at the moment. * Flashers are now taken into account when determining a ship’s cull distance. This means ships with ridiculous giant flashers are now seen from much further away. It also means they affect performance from much further away. Also, distant flashers fade out rather than suddenly disappearing. * Player ships with player-only shader bindings can now be displayed on the demo screen and shipyard screen without causing errors. (The same fix also applies to the doppelganger created when the player ejects.) * Ship "personality" numbers and "random" shader uniforms are now generated in a consistent fashion for the player, and transferred from the display model when buying ships. This means player ship shaders can usefully use "random" parameters. * Cube map textures are now supported - especially useful for, but not limited to, planets. * Greyscale+alpha PNG textures are now supported. * Changes to material model: renamed diffuse, specular, ambient and emission to diffuse_color, specular_color, ambient_color and emission_color. (The old names are still supported for compatibility.) Added specular_modulate_color and emission_modulate_color, and added illumination_modulate_color; these are used to recolour the corresponding texture maps (specular and emission used to do this, but are now only used when there’s no texture map). See http://www.aegidian.org/bb/viewtopic.php?p=109378#109378 for details. * It is now possible for a texture to be specified as a specific channel of a file, as in { name = "my_texture.png"; extract_channel = "r"; };. This comes with some overhead, but makes it easy to reuse channels of shader effect maps as emission or illumination maps in non-shader mode. * Shaders no longer have implicit tex0, tex1 etc. uniforms. Easily fixed: uniforms = ( tex0 = { type = "texture"; value = 0; }, ... ); * Subentities and flashers can now be specified as dictionaries instead of non-descriptive strings. * Flashers with a frequency of 0 now always have full intensity. (Previously, it was always 0.5.) * Using the new dictionary-type definition, flashers can now be any colour. They can also take an array of colours and switch between them in sequence. Use black to create intermittent flashers. * Ship radar marker colours can be specified in shipdata.plist as scanner_display_color1 and scanner_display_color2, and in JavaSCript as scannerDisplayColor1 and scannerDisplayColor2. * The ship property cargo_carried can now be set to "SCARCE_GOODS" or "PLENTIFUL_GOODS". The selected goods then depend on the prices at the local market. * Improved missile HUD icons. Outlines are now of consistent thickness. Arbitrary concave and complex (self-intersecting) polygons are supported, and more than one contour may be used (by specifying an array of arrays). To maximise compatibility with existing missiles, the relatively obscure Positive polygon winding rule is used; you can punch holes by using polygons with opposite winding order to the first one. Convex corners are bevelled at 30° or sharper angles. For troubleshooting, use the polygon-sprite-dump-svg hidden setting (http://wiki.alioth.net/index.php/Hidden_Settings_in_Oolite). * Support for HUD "dial" for joystick sensitivity, drawStickSensitivityIndicator:. See comment in hud.plist. * HUD elements can be adapted to window aspect ratio using the new x_origin and y_origin properties. -1 is bottom/left, 1 is top/right, 0 (default) is centre. * HUD elements can have their alpha modified individually. * New OXPMessages.plist to put notifications on the startup screen. * New generate-ai-graphviz hidden preference (http://wiki.alioth.net/index.php/Hidden_Settings_in_Oolite) to dump flow control graphs for AIs. * NPC missiles are now associated with equipment types (so that appropriate EquipmentInfo objects can be used to describe them). If no equipment.plist entry exists, an EquipmentInfo will be synthesised as needed. * Carrier AIs now receive DOCKING_REQUESTED and DOCKING_COMPLETE messages in connection with docking clearance requests. * New CARGO_DUMPED AI message. * Overheating behaviour changed as per http://aegidian.org/bb/viewtopic.php?t=6922. * Giving player a trumble may fail if they already have some. * Better error reporting in various situations. New world events: * compassTargetChanged() * guiScreenWillChange() * missionScreenOpportunity() * shipSpawned() (Sent immediately after ship event of the same name) New ship events: * shipCloakActivated() * shipCloakDeactivated() * shipRemoved() * shipTargetDestroyed() - replaces shipDestroyedTarget() * shipTargetLost() - replaces shipLostTarget() New JavaScript properties and methods: * expandMissionText() * setScreenBackground() * setScreenOverlay() * clock.addSeconds() * console.displayFPS * console.glVendorString * console.glRendererString * console.glFixedFunctionTextureUnitCount * console.glFragmentShaderTextureUnitCount * console.platformDescription * console.shaderMode * console.writeLogMarker() * equipmentInfo.scriptInfo * EquipmentInfo.allEquipment * mission.setInstructions * mission.runScreen() * oolite.gameSettings * planet.texture * planet.orientation * playerShip.addPassenger() * playerShip.awardContract() * playerShip.awardEquipmentToCurrentPylon() * playerShip.compassMode * playerShip.compassTarget * playerShip.engageAutoPilotToStation() * playerShip.disengageAutoPilot() * playerShip.hud * playerShip.hudHidden * playerShip.manifest * playerShip.targetSystem * ship.abandonShip() * ship.aftWeapon * ship.awardEquipment() (replaces playerShip.awardEquipment()) * ship.canAwardEquipment() * ship.cargoSpaceAvailable * ship.cargoSpaceCapacity * ship.contracts * ship.equipment * ship.equipmentStatus() (replaces playerShip.equipmentStatus()) * ship.exitSystem() * ship.fireMissile() (NPCs only) * ship.forwardWeapon * ship.fuelChargeRate * ship.isCargo * ship.isDerelict * ship.isMissile * ship.isMine * ship.isRock * ship.isBoulder * ship.isWeapon * ship.lightsActive * ship.maxThrust * ship.missileCapacity * ship.missiles * ship.passengers * ship.portWeapon * ship.removeEquipment() (replaces playerShip.removeEquipment()) * ship.savedCoordinates * ship.scannerDisplayColor1 * ship.scannerDisplayColor2 * ship.selectNewMissile() * ship.setCargo() * ship.setEquipmentStatus() (replaces playerShip.setEquipmentStatus()) * ship.setMaterials() * ship.setShaders() * ship.starboardWeapon * ship.thrust * ship.thrustVector * ship.velocity * station.dockedContractors * station.dockedDefenders * station.dockedPolice * station.equipmentPriceFactor * station.launchDefenseShip() * station.launchMiner() * station.launchPatrol() * station.launchPolice() * station.launchScavenger() * station.launchShuttle() * station.launchShipWithRole() * station.suppressArrivalReports * system.addGroup() * system.addGroupToRoute() * system.addShips() * system.addShipsToRoute() * systemInfo.coordinates * systemInfo.galaxyID * systemInfo.systemID * systemInfo.distanceToSystem() * systemInfo.routeToSystem() * SystemInfo.filteredSystems() Deprecated JavaScript properties and methods: * mission.choice - convert to mission.runScreen(). * mission.runMissionScreen() - convert to mission.runScreen(). * mission.showShipModel() - convert to mission.runScreen(). * mission.setBackgroundImage() - convert to mission.runScreen(). * mission.setMusic() - convert to mission.runScreen(). * mission.setChoicesKey() - convert to mission.runScreen(). * mission.clearMissionScreen() - convert to mission.runScreen(). * planet.setTexture() - assign to planet.texture instead. * player.hasEquipment() - use equipmentStatus() instead. * playerShip.awardCargo() - assign to manifest entry instead (e.g. playerShip.manifest["Food"] += 3). * ship.availableCargoSpace - use ship.cargoSpaceAvailable instead. * ship.cargoCapacity - use ship.cargoSpaceCapacity instead. * timer.isPersistent - no replacement. New/modified shipdata keys: * auto_ai (fuzzy boolean, default: true): now applies to escorts. * cloak_passive (boolean, default false): if true, ship’s cloak is disabled when firing weapons. * debris_role (role, default "boulder" or "splinter"): specifies the type of debris generated when an asteroid or boulder breaks up. * docking_pattern_model (DAT file name): replace docking effect model (see the rock hermit for an example). * has_scoop_message (boolean, default true): if false, the standard " scooped." message is suppressed when this ship is scooped. * market (commodities.plist key): for carriers, specifies the commodity market to use. * max_missiles (number, default: same value as "missiles"): now applies to NPC ships. * missile_load_time (number, default 0): specifies the minimum interval between missile shots. * scanner_display_color1, scanner_display_color2 (colour specifiers, default: depends on scan class): see above. * Inconsistent shipdata keys "escort-role", "escort-ship", "hasShipyard", "isCarrier" and "scanClass" have been supplemented with idiomatic "escort_role", "escort_ship", "has_shipyard", "is_carrier" and "scan_class". If both are present, the new one is used. like_ship merging does the right thing. New AI commands: * checkAegis * checkEnergy * setCoordinatesFromPosition * setThrustFactorTo: ------------------------------------------------------------------------------- Changes between Oolite 1.73.3 and Oolite 1.73.4: * Fixed nova mission. * Fixed missing planet name in Constrictor Hunt mission. * Fixed bug where subentities of sold ships could come back to haunt your log. * When buying a ship which does not specify a HUD, the default HUD is now used immediately. * Scooping can no longer overfill your cargo bay. * Fixed some cosmetic issues with equipment list UIs. * removeEquipment scripting method now works with damaged equipment. * awardEquipment scripting method now works with EQ_REMOVE_MISSILES. * Fixed spurious OXP verifier warning about display_name key. * Hyperspeed is now turned off when dying. * [Windows] Installer now uninstalls old versions correctly. * [Mac] Fixed an error where random (often very loud) noise could be played through the right channel at the end of long sounds. * [Mac] Game now starts in full-screen mode if it was in full-screen mode when last quit. * [Non-Mac] Fixed a problem with mouse control. * [Non-Mac] Added option to suppress splash screen, which forces the game to run with software rendering on some systems. ------------------------------------------------------------------------------- Changes between Oolite 1.73.2 and Oolite 1.73.3: * Fixed failure on startup when two OXPs define the same equipment. * Partial fix for bug where the system data screen showed a planet in systems that had gone nova. * Fixed AI habit of trying to steal cargo right out of other ships' scoops, with hilarious results. * [Mac] Docking clearance, the wormhole scanner and targeting of incoming missiles are now enabled. * [Mac] Fixed incompatibility with Debug OXP. * [Mac] Fixed minor leak in Snow Leopard resulting from the recent crash report enhancement. ------------------------------------------------------------------------------- Changes between Oolite 1.73 and Oolite 1.73.2: * Fixed incorrect counting of cargo when docked, which partially resulted in misbehaviour of -availableCargoSpace. * Fixed laser purchasing bug. * Fixed bug whereby after buying an item, the equip ship screen would be refreshed, even if this screen had ceased to be the active one. * Various cosmetic and spelling errors fixed (especially 'targetting'). * Improvements to Mac OS X 10.6 crash reports. * Legacy scripts inside CARGO_SCRIPTED_ITEMs now work again. (bug #15454) * Fixed bug that allowed players to get rich quickly by selling missiles at start of game and then going to the shipyard screen. ------------------------------------------------------------------------------- Changes between Oolite 1.72.2 and Oolite 1.73: Gameplay * Made procedurally textured planets a game option. Can be switched on/off in settings. (This feature is currently not available on the Mac environment.) * Added splash screen and application icon to Windows and Linux versions. * Added support for tangent space effects in shaders, including normal mapping. * Default shader now supports normal mapping and parallax mapping options. * Game over screen now displays ranking as well as credits. * Made passenger carrier market a bit more interesting by rewarding long taxi runs. Credits paid increase exponentially with number of hops required. Reputation of player is also taken into account. * Interface change: While in flight, pressing H from the local galaxy map will initiate hyperspace jumps - before, the player had to exit the map to do so. * Added new equipment: Wormhole Scanner. * Implemented targeting of nearest incoming missile. This is activated using Shift+T or an assigned joystick button. * Sell/buy as much as possible on the F8 screen via Shift+left/right. Prefer to buy with Shift+Return. * Time Acceleration Factor. Activated while in pause mode with left and right arrow keys. Can be from 1/16 to 16 times the normal time. TAF is visible in the FPS display. * Raised the limit on gold, platinum and gemstones to 10000 units. * Various AI changes. Most noticeable is that docking ships are now more likely to react to attacks, and less likely to crash during the final docking stage with bigger ships. * Find systems on the long range chart screen: all possible matches now highlighted while typing. * Last save directory is now saved in prefs; oolite-saves is only created when defaulting to it. * Repositioned message row. Messages are now fully readable in standard Oolite. * Equipment on the status screen now displays on multiple screens when needed, instead of overwriting the message and HUD areas. * Repeating messages: after 6 seconds of displaying a message, the same message is shown again. They're discarded otherwise, as before. * Game settings screen: Mac fullscreen resolution changes are now immediate. * If no external views are defined for player ships, the cobra3 ones are used instead. * The left and right square bracket keys can now be used as controls. * Joystick hats are now supported on the Windows and Linux builds. Expansion packs * Icons and symbols can now be associated to beacons, and will be shown on the advanced compass. * Subentity definitions in shipData can now be dictionaries. * Removed restrictions on vertex and face counts for ship models. * Introduced the cloak_passive ship dictionary key. When set to true (or yes), it will cause the cloaking device to be deactivated whenever a main weapon or missile is fired, making the cloak somewhat less of a superweapon. Default is no. * CLASS_NO_DRAW can be used inside plists for specific (ghost?) ships again, as in 1.65 and before. However, the system populator still overrides CLASS_NO_DRAW for incoming ships. * Customsounds.plist now accepts arrays, in a similar way to descriptions.plist. * Added new system settings to planetinfo.plist: "sun_radius" (float), "sun_color" = (any colour specifier), "corona_shimmer" = (float: 0-1), "corona_hues" = (float: 0-1), "corona_flare" = (float: 0-1). Two more settings: "texture" = (string), "texture_hsb_color" = (hsb color) available when procedural textures are active. * Renamed planets: %H and %I now use scripted planet names, including inside planet descriptions. * Plural handling for languages with more than one plural form. * requires_equipment, requires_any_equipment and incompatible_with_equipment restrictions now work for weapons. * New %J token for string expansion: expands %J000 to %J255 to its corresponding system name. * Implemented the key cloak_indicator_on_status_light in hud.plist (Off by default). * Added example of drawing a yaw indicator bar (disabled by default). Scripting * Work towards securing legacy scripts, AIs and shader uniform bindings by whitelisting methods. Game will now print an error message in the log if non-whitelisted methods are used in legacy scripts or AIs. * Added class ShipGroup. Groups are now defined in a different way than previously. ShipGroups are exposed to JS as a property of ships. * Made time acceleration factor controllable by JavaScript using the read/write timeAccelerationFactor global property for bullet-time-like effects. * PlayerShip.removeEquipment() now works for missiles. Also, awardEquipment()ing a missile when the player has none now causes the new missile to be selected. * Reworked Aegis messages. sun aegis is recognised, approaching planet event is restored, all handlers have a planet or station as argument. * Added the JavaScript read/write player ship property scriptedMisjump. Boolean value, scripted misjumps have a lifespan of one jump only. After that, auto reset occurs and subsequent jumps become normal ones again. * Bounty for script added pirates and traders now follow populator rules when auto_ai is true. * JavaScript system.info.xx & system.infoForSystem([present galaxy],[system number]).xx are read/write now. Reading infoForSystem values from outside the present galaxy is not supported and will return null. However, writing system info anywhere in all galaxies is fully supported at all times. New event handlers: * stationLaunchedShip(ship) * playerWillEnterWitchspace() * playerTargetedMissile(missile) * playerBoughtNewShip(ship) * arrivalReportScreenEnded() New JavaScript methods: * ship.setAIState() * ship.fireECM() * player.ship.galaxyCoordinates() * player.ship.cursorCoordinates() * station.dockPlayer() * player.addMessageToArrivalReport(message) New AI commands: * dropMessages * debugDumpPendingMessages * exitAIWithMessage: * messageSelf: * abortAllDockings * throwSparks * randomPauseAI: * launchIndependentShipWithRole: * checkOwnLegalStatus * setSpeedToCruiseSpeed Changed commands: * Added delay parameter to playerStartedJumpCountdown event. * Made Entity.position and Entity.orienation read/write, and deprecated Entity.setPosition() and Entity.setOrientation() * Ship.exitAI() now can specify a message. Default is RESTARTED. * Deprecated system.goingNova and system.goneNova in favour of existing Sun equivalents. Bug fixes * Fixed far away docking of ships. Ships that start the dockingAI further away than 2.5 times scanner distance from a station are sent back to their original AI. * SDL & Windows fixes: reworked Windows port to resize game view without texture loss. Splash screen for both Linux and Windows. When switching fullscreen resolutions using F11 Oolite now saves the new fullscreen resolution. * Fixed negative cargo bug shown in shipyard for ships with cargo capacity less than 5t that had EQ_PASSENGER_BERTH as optional equipment. * Oolite now takes incompatible equipment into account when generating ships for sale. * Fixed weapon position. It now follows the defined position in shipData instead of always using the default one. * Fixed an inconsistency in oolite-font.plist. * Prevent ships from escorting things with different scan classes. * Pirates returning to their own pirate cove are now correctly added to the station defence ships. * Cloak no longer works as an energy generator when not cloaked. * Escorts cannot have escorts of their own anymore. This avoids memory overflows in cases of recursive escort-ship, escort-role and mutual escort ship references between two or more ships. * Motherships with a clean legal status now only accept clean escorts. * Fixed hasShipyard. * Fixed crash when max_missiles is set to higher than maximum allowed (16). * Fixed bug in performFaceDestination where a ship would hang still in space. * launchTrader now launches traders with correct escorts for that ship type. * sun_radius loop bug: should now cope with all sun_radius values. * Limited checkcourseToDestination to scanner range so it only changes course when it actually "sees" something. * scanForOffenders no longer finds fellow group members. * Minimum player-bouy distance when entering system raised from 500 to 750 metres. * Resolved bug that caused ships with subentities to explode inside closed structures and other sudden collisions near bigger ships. * More systemInfo changes for the current system are now validated and are effective immediately, including most sun settings, main planet texture, and derivative ones (ie commodity market/economy, ship and equipment markets/ techlevel). Properties not yet effective immediately produce a warning. * AI command setCourseToPlanet no longer sets course to moons, only to the closest planet. * Fixed joystick precision mode switch causing continuous yaw to left (Linux and Windows builds only). ------------------------------------------------------------------------------- Changes between Oolite 1.72.1 and Oolite 1.72.2: * Fixed bug in selecting ships after a spawn condition blocked the initial selection. This was causing disappearing buoys and police ships with some OXPs. * Fixed a bug where by flooding the market when selling a ship, cargo remained on the newly acquired ship even if said ship had no cargo carry capability. Now, all cargo that cannot be sold when changing ships due to flooding the market will be lost. * Fixed an AI bug where a police ship that received a distress call while returning to the station could get stuck after dealing with the call. * Fixed a bug where defense ships launched by stations had a patrol AI instead of a combat AI. * Cloaking device mission now works again. * Background images in HUD are now affected by HUD alpha (and thus HUD hiding). * key_docking_clearance_request is now set in keyconfig.plist. * Possible fix for texture corruption bug on Windows machines with more than two processors. * Cargo pods added by scripts now have cargo. * Fixed a problem with JavaScript system property setup. * playerStartedJumpCountdown JS event is now always playerStartedJumpCountdown and never playerStartedJumpCountDown. * Improved various error messages. ------------------------------------------------------------------------------- Changes between Oolite 1.72 and Oolite 1.72.1: Crashes: * (Non-Mac) Fixed crash when no sound device available. * Fixed a bug making random ship selection take unnecessarily long when script conditions failed - sometimes infinitely long. * Strict play mode partially fixed - switching no longer crashes, but OXP world scripts keep running, which they shouldn't. * Fixed crash when colliding with a ship whose model couldn't be loaded, and took steps to ensure that such ships don't exist anyway. Game behaviour: * Fuel now costs money as nature intended. * Thargoid death curses no longer have infinite range. * Tharglets created by scripts are now unpiloted. * Probabilities for high-tech optional extras on ships are now calculated correctly. * Fix for stations/carriers becoming main station in interstellar space. * Docking clearance cancelling works as intended, and docking clearance is not required for escape pods. Technicalities: * (Mac) Enabled docking clearance system. * Trumble sounds fixed. * Fixed bug where going into any of the F6 screens with the injectors key pressed would result in injectors continuing to run even after releasing the key. * SystemDescription.dot file (a tool for people translating Oolite) now appears in the same directory as the log on all systems. * Possible fix for rendering problems with Windows and ATI cards (but it's a long shot). Ship registry: * Ships whose model can't be found are now dropped from the ship registry (with a warning message). * is_template now works properly (specifically, ships inheriting from templates do not inherit the is_template value and get dropped). * Subentities are no longer required to have roles. * Ships with bad subentity definitions are reported and dropped. * New ship key is_external_dependency suppresses warning when like_ships resolution fails. (The ship is silently dropped.) Misc configuration: * "hasShipyard" shipdata key now works. * Setting a shader uniform to a constant float with short syntax (e.g. myUniform = 42;) in OpenStep-format property lists now works. * Overall HUD alpha now affects comm log and message log. This also affects hiding the HUD ("o" key while paused). JavaScript: * Galactic hyperspace behaviour properties updated in accordance with documentation (http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_PlayerShip#galacticHyperspaceBehaviour). * playerBoughtEquipment event now fires even when equipment cost is 0. * player.bounty fixed. * player.spawnOne() now works (with deprecation warning, will be removed for version 1.74). * equipmentDamaged and equipmentDestroyed events now consistently provide the equipment key rather than localized name. * player.ship.setEquipmentStatus() now raises an equipmentDamaged event when appropriate. * Passing invalid parameters to Sound.play(), Sound.playSound(), expandDescription() and displayNameForCommodity() no longer crashes Oolite. * Ship.heatInsulation setter now works properly. * System.infoForSystem() no longer causes delayed crash. ------------------------------------------------------------------------------- Changes between Oolite 1.71.2 and Oolite 1.72: * New "ship registry" handles ship types and ship roles more robustly and efficiently. Benefits include less stutter when generating ships (especially traders and pirates) and various shipdata.plist errors being reported up front (when rebuilding cache). Also adds new shipdata-overrides.plist and shipyard-override.plist, which can be used to selectively override certain properties of ships without redefining them completely. Overrides are silently ignored for ships that don't have a "primary" shipdata.plist/shipyard.plist entry. * New equipment type handling. This is less far-reaching than the ship registry, and isn't used everywhere, but it's exposed to JavaScript as EquipmentInfo objects (see EliteWiki for documentation). * Missiles no longer collide with parent on launch. * Disarmed missiles now stay disarmed when cycling through targets with Target System Memory Expansion. * Workaround for key conflict in old BBC key config files: if both roll and yaw are assigned to ,/. keys, yaw is disabled. * Fix for NPCs becoming offenders when counterattacking player. * Only one energy unit is allowed at a time. Thargoid Plans mission script now reflects this. * Approaching non-main planets (including the sun) no longer points compass at main station. * Player ship turrets now work. * Escort turrets now work. * Carriers can now control their launched defenders. * Miniature planets from F7 screen no longer float around invisibly mass-locking you. * (Non-Mac) Fix failure to launch when cache is corrupt. * Fixed rounding behaviour of some currency displays. * Suppressed pointless "Cannot iterate into directory" messages. * (Non-Mac) Support for joysticks with up to 16 axes and 64 buttons. If you need more than 16 axes, I shall require photo evidence. * More (all?) sound goes through customsounds.plist. * (Non-Mac) SDL sound code rewritten to be more like Mac code. Most noticeable effect is that more than one instance of a sound can be played at once. As a result, afterburner2.ogg is no longer needed. * (Non-Mac) Logs are now written to ~/.Oolite/Logs/under Linux and \Oolite\Logs\ under Windows. (Oh yes, I intended to change it to GNUstep default locations. Oh well.) OXP verifier logs also end up there, named by OXP. * (Mac only) Updated Smart Crash Reports to 1.5 for Leopard compatibility. * Similarly, showShipModel: no longer generates escorts. * More stuff made localizable. * New is_template shipdata.plist property. Set this to yes/ for ships which are only used through like_ships and are not intended to be used directly. If your (otherwise working) OXP generates warnings about ships with no roles or model attribute, you probably need this. * New is_submunition shipdata.plist property for cluster weapons. Missiles with this property inherit their launcher's current target. * New shipdata.plist key hyperspace_motor_spin_time to modify jump countdown time. * New planetinfo.plist keys star_count_multiplier and nebula_count_multiplier. (Can be used in universal section to modify overall star and nebula density.) * Putting planets in interstellar space no longer breaks the game. * Failure to set up a station is now handled somewhat more gracefully, with useful diagnostic log messages. * Added commsMessageByUnpiloted: for AIs and legacy scripts. * switchLightsOn/Off now applies to subentities. * Fixed some spurious errors in OXP verifier. * Condition flags for equipment are now enforced when adding items directly, not only when shopping. More condition flags are now available. The full list is: available_to_all, requires_empty_pylon, requires_mounted_pylon, requires_clean, requires_not_clean, portable_between_ships, requires_free_passenger_berth, requires_full_fuel, requires_non_full_fuel. Other types of condition are: requires_cargo_space, requires_equipment, requires_any_equipment, incompatible_with_equipment. Legacy script conditions can also be used (only apply to player). * New HUD plist key reticle_target_sensitive makes target selection box go red when active target is in position (requires Scanner Targeting Enhancement). Also exposed to JS as player.ship.reticleTargetSensitive. * Fix for the occasional 15-20 seconds slowdowns experienced during gameplay. * Some AI tweaks from Eric, and general cleanup of AI formatting. * Docking clearance support, see http://www.aegidian.org/bb/viewtopic.php?t=5060 * Oolite now builds for Irix, but (last I heard) doesn't run due to threading issues in GNUstep. * New shader uniform types randomFloat, randomUnitVector, randomVectorSpatial, randomVectorRadial and randomQuaternion (which supports asMatrix option, default is true). * Various changes for 64-bit compatibility under OS X and Linux. This release is not 64-bit, though. * Oolite can now correctly count CPUs under Linux, not that this makes much difference. * New web site at oolite.org. (Hey, it's in the subversion log.) * Spot the graphical tweaks! JavaScript changes: * Potential crashing bugs throughout JS code fixed (exceptions weren't being handled properly). Also added more and better JS error checking. * missionVariables object no longer treats strings starting with digits but containing letters as numbers. * AI sendScriptMessage: can now pass parameters. The AI statement "sendScriptMessage: foo bar baz" is equivalent to the JS ship.script.foo(["bar", "baz"]). Note that the parameters are passed as an array of words. * Debug console now allows definitions to be split over several lines; for instance, you can type "this.test = function() {" on one line and "return 3: }" on the next. Enter an empty line to cancel multi-line input. Lines which are considered "incomplete" are echoed with a _ prefix instead of >. (Mac-only: the integrated console also supports multi-line input using option-return, as it always has.) * New global function randomInhabitantsDescription(plural : Boolean) : String. * Entity.call() is now only available when debug OXP is active. * Entity.isValid now works for invalid entities. * New Ship properties and methods: desiredSpeed, cargoSpaceUsed, availableCargoSpace, passengerCount, passengerCapacity, remove(). * Renamed Ship properties: maxCargo -> cargoCapacity, shipDescription -> name, shipDisplayName -> displayName. * player split into player and player.ship. All Entity/Ship methods now live in player.ship. There are compatibility accessors in player, which will be removed in future (and generate warnings). * New Player properties and methods: contractReputation, passengerReputation, increaseContractReputation(), decreaseContractReputation(), increasePassengerReputation(), decreasePassengerReputation(), reticleTargetSensitive, rank, legalStatus, forwardShield, aftShield, maxForwardShield, maxAftShield, forwardShieldRechargeRate, aftShieldRechargeRate. * player.orientation is now right way around. * New System properties and methods: info and infoForSystem(), equivalent to legact setPlanetInfo: and setSpecificPlanetInfo:. For example, system.info["foo"] = "bar" is equivalent to "setPlanetInfo: foo bar". Currently, values can only be written, not read. Also countShipsWithPrimaryRole(), pseudoRandomNumber, pseudoRandom100, pseudoRandom256. * system.setSunNova() deprecated in favour of system.sun.goNova(). * Better error messages for legacy_addShip*() family. * system properties now make some sort of sense in interstellar space. * Vector renamed to Vector3D. * Ability to pass several numbers instead of a vector or quaternion is deprecated. Use an array or Vector3D instead. (For instance, instead of ship.position.add(100, 0, 0) use ship.position.add([100, 0, 0]).) * New script events: playerBoughtEquipment(equipmentKey), equipmentDamaged(eqipmentKey), equipmentDestroyed(equipmentKey). * New events for approaching and leaving planets: shipEnteredPlanetaryVicinity(planet)/shipExitedPlanetaryVicinity(planet) for JS, CLOSE_TO_PLANET/AWAY_FROM_PLANET for AIs. (Actually, I'm not clear on how CLOSE_TO_PLANET differs from AEGIS_CLOSE_TO_PLANET. If none, suggest removing CLOSE_TO_PLANET. Kaks?) * shipLaunchedEscapePod() is now triggered slightly earlier, before resetting legal status and trumbles. NOTE: one warning generated by the ship registry is disabled by default for the benefit of Realistic Shipyards, namely the warning when a shipyard.plist entry does not correspond to a shipdata.plist entry. This warning will be enabled by default in 1.73. To enable it, change shipData.load.shipyard.unknown to yes in logcontrol.plist. ------------------------------------------------------------------------------- Changes between Oolite 1.71.1 and Oolite 1.71.2: * Fixed delayed crash after looking at system info screen in interstellar space. * Fixed crash when an array in descriptions.plist is empty. * Suns, Q-mine explosions and wormholes are now the same size no matter which direction youíre looking at them from. * Fix for ships launching with the wrong AI. * ECM, space compass and ident are now reset properly when restarting the game. * Fixed HUD alpha/hiddenness for empty missile slots. * Fixed glitch where it would become impossible to stop playing music after attempting to play music that does not exist. * Creating a JavaScript timer with a negative delay value now creates a paused timer as documented. * Player.alertEnergy and Player.alertHostiles now work on Windows and Linux. * Squashed spurious ìship generated with infinite top speed!î log messages. ------------------------------------------------------------------------------- Changes between Oolite 1.71 and Oolite 1.71.1: * Fixed hang when entering interstellar space. * Cloaking device mission now works. * Credit quantities now display correctly on big-endian systems (such as PowerPC Macs). * Fixed problem with double-clicking on load/save screen. * Growl mode strings can now be localized (Mac OS X only). ------------------------------------------------------------------------------- Changes between Oolite 1.70 and Oolite 1.71: Stability: * Fixed the nasty bug that would occasionally cause Oolite to crash after having consumed all memory during system population. * Fixed the selling ship with subentities causes crash bug. * Fixed strict mode to/from crashes. * Fixed unmarkSystem crash. * Removed crash when loading save game with 'stale' oxp missiles (see http://aegidian.org/bb/viewtopic.php?p=47921&highlight=#47921) JavaScript: * All internal game scripts are now JavaScript -- adopted somewhat twiddled versions of Eric Walch's scripts. * Implemented JS sound and music interface. * Fixed scriptActionOnTarget: not setting the script target correctly (note: scriptActionOnTarget: is now deprecated, use safeScriptActionOnTarget: instead). * Added shipHitByECM(pulsesRemaining) script event. * Added JS systemNameForID() and systemIDForName() methods. * JS mission.choice is now read-write, mission.resetMissionChoice() deprecated. * Added JS Ship methods dumpCargo(), ejectItem() and ejectSpecificItem(). * Added playerEnteredNewGalaxy(newGalaxyNumber) script event. * Added missionChoiceWasReset() script event. * Most AI reactToMessage: calls now also have a script event; see http://www.aegidian.org/bb/viewtopic.php?p=47620#47620 * Setting the player's target through JS now works as expected (locks on ident, with UI feedback, unless a missile is selected in which case current missile is locked on to the new target). * Added Ship.explode(), which kills any ShipEntity through energy damage including the main station. Once again made blowing up the main station with blowUpMainStation (or explode()) work. * Added experimental planet.setTexture in JavaScript. Usage restricted to shipWillExitWitchspace() & shipWillLaunchFromStation(). Bugfixes: * Fixed a serious memory leak on the short-range chart screen. Other smaller leaks plugged as well. * Linux shader support added (did not work with 1.70). * Fix for trade-ins (like selling missiles or replacing lasers) not working. * Fix for scripting bug, whereby when adding a second ship to the system with the same script that was active before, all references to player, system, mission, worldScript etc. were gone and using them was giving an error in the log. * Fixed the "too many saved games in oolite-saves causes weird behavior" bug. * Fix for OXP verifier turning off log message classes. * Fix for bug where awardEquipment:EQ_TRUMBLE while a trumble infection is in progress would innoculate the player against future trumble infections. Also added code to un-innoculate existing saved games. * Fixed laser purchasing mishandling bug in equip ship screen. * Fixed a small bug that was there since 1.65, whereby switching to equip ship screen would sometimes display information for equipment other than the normally selected missile. * Fixed bug where purchasing a ship with an upgraded laser does not reward the laser (bug #12791). * Fixed bug whereby when docked with a non-primary station, the New Game option would be unreachable in the menu, although it was shown as available. * Fix for Asps not having lasers when bought. * Fuel injectors on NPC ships are not allowed any more if we are on strict mode. * Made newly added ship entities get crews only if they are not scanClass CLASS_CARGO or CLASS_ROCK. This way, when scooping scripted cargopods or boulders, for example, the correct scooped commodity is diplayed, instead of messages of the type 'You have captured a slave'. * Reverted laser rendering to mode where lasers are clearly visible against the sun. * Resolved music not looping problem in SDL builds. * Station defense Viper's AI fixed and deafult maximum number of station defenders launched when station is under attack increased from 3 to 8. Key max_police sets the main stations defenders / number of system patrols. * Sticky messages bug has been most likely fixed. * Fixed bug for APPROACHING_SURFACE returning incorrect results. * Shuttles now take off directly away from the planet (bug #12212). * Bugfix for very long passenger names in the Carrier Market. * Fixed bug where scripted cargo pods do not award cargo as expected when scooped (bug #12659). * Added the CLASS_CARGO scan class to alloy and splinter in shipdata.plist, so that they don't appear unscoopable and with legal status when spawned by scripts. * Better diagnostics for bad AIs. * JavaScript legacy_addShipsWithinRadius method fixed and works as expected. * Fixed Thargoid death broadcast. * Demo screen behavior has now been fully fixed. No more ships going past the screen on game startup. Missing/broken ship models displayed as question marks, missing textures displayed as no-textures-material. No comms message will be received when on Demo2 screen. * Comms log now works as expected. First in, first out. * Repairing a damaged weapon (awarded via oxp) doesn't add an extra weapon anymore (bugs #13471 & #13481). * Shipyard techlevel bugfix. It now works as described in the wiki (i.e. techlevel=5 will allow ships to appear for sale in systems with technological level 6) * JS Bugfix: changes to local system properties (tech level, government, etc...) now take effect immediately. * Oolite now gives the right message when trying to jump more than 7 ly. * Added code to hopefully work around sky rendering glitch on S3 hardware. Improvements: * Improve speed of traversing saved games list by writing the current system name in the save file. When Oolite displays summary information about the saved game, it will try to look for this string. If the current system name string does not exist, it will fall back to using galaxy seed and coordinates to extract the system name. * Great improvement in escort AI. * Oolite's sound system rewritten for maximum flexibility. Most sounds now go through customsounds.plist. * Changed several built-in plists to OpenStep format. * Mission choices are now sorted. * Legacy foo_actions can now have local variables, which are cleared when the _actions completes. * Special cargo expanded and amended: it can now be saved, and no containers can be loaded on board while the special cargo is in the hold - scooping cargo will not work, and purchasing & awardCargo are only allowed for small valuables. * Implemented power braking for ridiculous speeds. * awardEquipment: if undamaged equipment present, trying to assign its damaged equivalent will fail with a warning. Trying to award an undamaged weapon using awardEquipment() will also fail with a warning. New Features: * Added ability to explicitly bind shaders to subentity rather than root entity. * Added support for hull heat glow to default shader (full shader mode only). * Added support for constant vector and quaternion uniforms for shaders. * Implemented analog joystick yaw control code. Many more analog joystick improvements (SDL builds only). * Added Eric Walch's list of pre-existing roles to pirate-victim-roles.plist. * Intergalactic jump destinations have been made user/scripter controllable. * Missiles now use the accuracy variable to determine how well to track targets. Accuracy must be between 0.0 and 10.0. If not specified, it defaults to 0.0, which corresponds to standard missile accuracy. * All game strings are now externalized and easily changeable by the user. * Added support for 8-bit character encoding, thus enabling Oolite's internationalization. Oolite now has built-in support for Western European (Latin-1) text and Greek, Cyrillic, Turkish and Eastern European (Latin-2) "starter kit" OXPs are now available. * Made the equipment.plist array mergeable like a dictionary, thus enabling OXP created equipment to override the standard one instead of being added on top of it. * Added support for 'abs' co-ordinate system. * Added auto_ai support for escort set-up. * Added script events for planet and station proximity events. * Added full support for aft lasers in shipyard. Side lasers should work if defined in a plist, but we don't offer random "upgrades" to them in the shipyard (bugs #012362 and #012363). * Added Autosave option in the game options sub-menu. * Graphics fiddle: scarred alloy is now only scarred on one side, and has a specular intensity map (if shaders enabled); both types of alloy are now shinier. * Oolite now supports multiple textures for nebulae and stars; also uses mix of sun and star colours for ambient light. * Added overall_alpha key to HUD dictionaries, defaulting to 75% opacity (25% transparency). * HUD can now be switched off with the 'o' key and turned on again with the 'n' key while game is paused. Miscellaneous: * Subentities no longer count as kills. * Made ship AI send a beingAttacked message to scripts when a missile is fired against ship. * Most legacy script methods now show the name of the current script if an error occurs, but this only works for world scripts. * Change to Console.consoleMessage: ConsoleMessage('message') is now shorthand for consoleMessage('command-result','message'). * Ejected items and debris are now spawned with 85% of the parent's cabin temperature. * Weapon mounting positions with weapons already mounted are now shown in a darker green color in the equip ship screen. * Game over changes: After the player ship has been destroyed, game restarts at Lave, pulse laser only, 100Cr. * Energy unit changes: energy units recharge rate adjusted to offer a better challenge to the player, and naval/extra energy unit purchase behaviour more flexible (see resolution of Bug #13507, Naval Energy Unit bug in berlios). * Warnings added if energy units are going to be removed. * Made the required Advanced Nav Array tech level equal to 7, as it was meant to be. ------------------------------------------------------------------------------- Changes between Oolite 1.69.1.1 and Oolite 1.70: * Greatly enhanced JavaScript scriptability. Ships and planets are now scriptable objects. Every ship now has an attached JavaScript script. Added timers and clock object. The system object provides flexible methods for searching for entities. * Implemented interactive JavaScript/debug console. Under Mac OS X, this is integrated into the application (when a special OXP is installed). On all platforms, the game can communicate with a separate console program over TCP. Writing a good console is left as a third-party opportunity. * Added -verify-oxp command-line option. * Fixed inconsistent usage of "role" attribute by splitting it into a set of roles and a primary role. * New extendable property list, pirate-victim-roles.plist, specifies which (primary) roles pirates will look for when selecting victims. This allows OXP developers to make their special ships potential victims. * Integrated Commander McLane's external views OXP. It is recommended that the OXP be removed when running Oolite 1.70. * Added travel time estimate for stars within one jump. * Split game options out of menu. * Added option to play in wireframe mode. * Added option to set shader complexity level (Off/Simple/Full) on shader- capable systems. * Added default shader, used for all ships in full-shader mode and for materials with smoothing or options in simple-shader mode. Added material options for illumination mapping, glow mapping and specular mapping, currently only implemented with shaders. Made all ships slightly shiny by default. * New AI methods: scanForNearestShipWithPrimaryRole: (new, preferred name for scanForNearestShipWithRole:) scanForNearestShipWithAnyPrimaryRole: scanForNearestShipWithoutPrimaryRole: scanForNearestShipHavingRole: scanForNearestShipHavingAnyRole: scanForNearestShipNotHavingRole: scanForNearestShipWithScanClass: scanForNearestShipWithoutScanClass: sendScriptMessage: * Converted several plists to OpenStep format for legibility, and cleaned up some redundant information from shipdata.plist. * Updated JavaScript engine. JavaScript 1.7 is now supported. * Legacy script methods awardFuel: and setFuelLeak: now work on the current script target, not always the player. * Fixed bug where local planetinfo overrides were not being restored from saved games properly. * Fixed bug where several shipdata attributes were ignored for stations (bug #11684). Added has_npc_traffic attribute to suppress NPC traffic at carriers. * Fixed bug with stars and nebulae looking wrong when inside an atmosphere. * Fixed trumble-related crash. * Added clearMissionScreen scripting method to simplify mission screens. * Several AI bug fixes contributed by Eric Walch. * Fix for a certain class of AI crashes (recursive reactToMessage:). * Made evil XML DTD swizzling hack work with plists generated under Mac OS X 10.5. * Made asteroids created by scripts behave the same as asteroids created by the system populator. * Fixed bug where cancelling the Open Game dialog caused broken behaviour (Mac OS X only; bug #11691). ------------------------------------------------------------------------------- Changes between Oolite 1.69.1 and 1.69.1.1: * Fixed a consistent crash at startup on x86-based Macs. ------------------------------------------------------------------------------- Changes between Oolite 1.69b and 1.69.1: * Several crashes and other severe bugs fixed. * Fix for a wide class of potential crashes and strange behaviours which were latent in earlier versions but obscured by an otherwise unhelpful caching mechanism. * Fix for ship death_actions and script_actions not working. * Fix for awardCredits: stealing all your money. * Fix for shipyard refusing to sell ships whose price is less than your current trade-in value. * Added notequal operator to legacy scripting system. * Reduced trade-in values by 25% across the board to reduce market exploits. * Temporarily disabled procedurally textured planets to avoid a crashing bug. * Fix for problem with ships that have multiple common roles being created by scripts and having the wrong AI, as discussed at http://www.aegidian.org/bb/viewtopic.php?t=3654 . * Better handling of out-of-range numbers in property lists. * Fix for JavaScript methods randomly not doing anything. Also fixed system.legacyAddShipsWithinRadius() ignoring radius parameter. * Fix for scripts running while paused, if on a menu screen. * Fix for wrong galaxy being shown when switching from Open screen to Map screen. * Fix for out-of-range laser heat value in shaders on demo screen. * Damaged equipment is now shown in orange as intended. * Big yellow question mark is now shown as intended on the Open screen when selecting a saved game using an unavailable ship. ------------------------------------------------------------------------------- Changes between Oolite 1.69 and 1.69b: * Fixed an error that caused certain scripts tied to ships not to run. ------------------------------------------------------------------------------- Changes between Oolite 1.68 and 1.69: * New materials model and more flexibility for shaders. * Lasers can now be any colour, with the limitation that they must be reasonably bright. The new colour spec syntax introduced for lasers is also supported for sky colours, but not planets in this release. * Raised vertex and face counts for meshes, while reducing memory usage. * Reformatted keyconfig.plist to be more legible and easier to edit. * Advanced Navigation Array default key changed to ^ (shift-6 on UK keyboards) to avoid surprise launches. * Save/load screens now show more information, and show the correct ship type even if it isnít currently installed (only applies to games saved with 1.69 or later). Also, a question mark is now shown in place of unknown ships (but looks bad due to bugs in setting up smoothed models). Under Mac OS X, this only applies in full-screen mode as standard system dialogs are used in windowed mode. * The commander status screen now shows damaged equipment in orange. * The appearance of interstellar space is now set in planetinfo.plist. * (Mac OS X only) Support for a new Debug menu added. It is enabled by a special OXP, Debug Menu.oxp. * New debugging key, key_dump_target_state, which dumps a lot of information about the playerís target (or the player, if there is no target) to the log. Not bound to anything by default. * New requires.plist key added: max_version. This is not expected to be useful very often, except for Debug Menu.oxp. Any unknown keys in required.plist will now cause an OXP not to load. * Improved robustness in various ways, especially with regards to property list parsing. * Various redundant bits of code removed; code cleanup; some optimization. * Cache is no longer pruned at all. Since itís rebuilt when OXPs are changed, the maximum size of the cache is limited by the installed OXPs. In practice, sizes of a few MB can be expected. Under GNUstep, cache read/write should be more efficient and the folder the cache lives in will be created if needed. * Under Mac OS X and Windows, you can force a cache rebuild by holding down shift during start-up. This probably sort of works on Linux other systems if you press it just at the right moment. * Fixed various issues under Linux (catching up with changes since pre 1.67). * (Mac OS X only) Spotlight indexer indexes name of ship for games saved with 1.69 or later oolite-1.82/Doc/CHECKLIST.TXT000066400000000000000000000153151256642440500153730ustar00rootroot00000000000000Testing checklist ================= These tests should be run before each release (on every platform), and also before checking in major changes, or whenever you're really bored. This is a work in progress; feel free to add more tests. Support files are found in tests/Checklist-files/. Unless otherwise noted, the tests should be run with no OXPs installed except Debug.oxp. Galaxies -------- This test validates the system and galaxy PRNGs. Starting at Lave, manually make eight galactic hyperspace jumps. (Don't use saved games, as the saved random seeds may mask problems.) Tip: use the Time Acceleration Factor and the console: PS.awardEquipment("EQ_GAL_DRIVE"). Also use PS.awardEquipment("EQ_ADVANCED_NAVIGATIONAL_ARRAY") so you can check the star chart grid easily. The system names should be: Lave-Inoran-Divera-Cebitiza-Zaxeed-Oratedge-Tiared-Soladiar-Quator (Quator is 7.2 LY north of Lave; the sequence of jumps causes some drift.) Checklist-files/Galaxies contains three or four screen shots for each system. The first is immediately after exiting witchspace, with textured planets enabled. The second uses untextured planets. Where necessary, a third includes the sun. The last is of the galactic chart. Check the planet in both modes and the sun. Check the background stars (colours, density, pattern). Check that the player ship and space dust are appropriately lighted (for instance, a distinct blue tint in Inoran system). Repeat the process in Strict mode. Note that you can still use the JS console to get galactic hyperdrives, as long as the game was launched in unrestricted mode. However, you can't get an advanced nav array, so don't bother checking the long-range chart again. Missions -------- Nova: Jump to any system. When you arrive, you should get a fuel leak, followed by a distress message from the witchpoint beacon. (There are four possible distress call texts, see "oolite-nova-distress-call" in descriptions.plist.) Wait half a minute, then query S.allShips in the console. There should only be the player, the main station, the witchpoint and nav beacons, possibly asteroids and possibly ships launched by the station (e.g. a patrol Viper). NOTE: normally, you are expected to leave the nova system using a galactic jump. However, you'll want to go back to the system for testing purposes, so award yourself some fuel (PS.fuel = 7) when you arrive and use a normal jump. There are now several outcomes: * The coward: Leave the system without docking. Confirm that missionVariables.nova is "NOVA_ESCAPE_OTHER". Dock at the main station, and you should get the following message (with solar.png as a background): Commander, you were the last hope for survivival for the remaining refugees. If you hadn't ignored the emergency, they could have escaped the nova explosion. How could you abandon them to their fate... missionVariables.nova should now be "NOVA_HERO". (This indicates the end of the nova mission, regardless of actual heroism.) * The bastard: dock with the main station and get the following message (with solar.png as a background): You've arrived at an unfortunate time, Commander. Our sun has become unstable and is due to go nova at any moment. All our ships are evacuating the system, the last few refugees have come here to find a ship to take them to safety. Please, please help us to escape! [No, sorry.] [Okay, I'll help] Select [No, sorry.] You will get a comms message saying "Coward! You chose not to help the refugees!" (FIXME: this appears behind the station UI. Should it be removed, or deferred until launching?) Immediately leave the station. The station should explode behind you. Leave the system with alacrity. The sun should go nova nine seconds after you selected [No, sorry]. (This is visible as a rising cabin temperature, not to mention a growing sun. In full shader mode, your ship should be glowing pink by the time you jump.) Verify that missionVariables.nova is "NOVA_ESCAPE_COWARD", then dock. You should get the following message (with solar.png as a background): Commander, you should be ashamed. Those people you left behind had placed all their hopes on you and you just left them to their fate. Such cowardly behaviour hasn't gone unnoticed. missionVariables.nova should now be "NOVA_HERO". * The hero: same as above, but select [Okay, I'll help]. You will be launched from the station with 30 seconds to nova (and no fuel, unless you added some). The station should explode behind you. Get out of there. missionVariables.nova should be "NOVA_ESCAPED_SYSTEM". Your Manifest screen should have the message "Get the refugees to safety." Dock at any (other) main station, and get the following message: Commander, we thank you. You've brought us all to safety but we have no money with which we can reward you - our last credits were taken by those who took us to the space station. Instead we offer you undying gratitude... and what few valuable possessions we can spare. You are our hero! You should have 100 g of gemstones (and nothing else) in your cargo hold. missionVariables.nova should now be "NOVA_HERO". * The dawdler: having jumped to any system, wait around for two game hours (7.5 minutes at full TAF). The sun should then go nova. (If you escape the system while it's going nova, you're back on the coward's path.) While you're hanging around, the sun's corona shoud periodically expand and contract, and the witchbuoy will send its distress call every 30 seconds (which also causes NPC ships to jump out). After testing each non-lethal path, return to the nova system. The sun should be overlarge, there should be no ships except you, some asteroids and a witchbuoy, and you should be overheating. Trumbles: Sell 1t of food, then exit station and reenter (using S.mainStation.dockPlayer()) as many times as required for the Trumble to be offered. Constrictor: Launch from station, spawn one thargon with the console and kill it, then reeenter. Mission will be offered. Orrara, the mission's final arrival point is next door. Unfortunately, not all hints can be tested with this savegame, other than the last one (there's a really dangerous pirate here). Thargoid plans: Do a Galactic Hyperspace and dock. The mission will be offered. It's a bit of work to go to Ceerdi and then Birera, so you can edit the save game coords if you want to get fast. Important to make sure that an increased number of Thargoid occurences happen and that the mission objectives are correctly listed in the manifest screen. Cloaking Device: Just jump to the system already targeted in the save game and the three Asps will be waiting for you. oolite-1.82/Doc/ExternalLibrariesSourceCodeChanges.txt000066400000000000000000000204071256642440500231040ustar00rootroot00000000000000Modifications to external libraries' source code for running Oolite ------------------------------------------------------------------- The various ports of Oolite are using a number of external libraries for graphics, sound, input and event handling. Throughout development, certain changes to the source code of these libraries have been deemed necessary, either to enable Oolite to run in a more efficient and independent manner, or simply to fix issues that occurred as a result of these external libraries themselves. Of these libraries, the ones that have to be rebuilt specifically for Oolite, together with the main reasons/areas changed for this reason are: 1. gnustep-base v1.20.1 (Windows) - bug in NSInteger definition, change to dictionary format of NSUserDefaults, fix for integer truncation of integers to 32-bit when parsing XML plists (bug #32495 on the gnustep.org bugtracker) and changes to facilitate native Obj-C exceptions support. Also, fix bug in the stack grabbing code at NSDebug.m so that correct stack traces can be obtained also on Windows XP and later. 2. SDL v1.2.13 - (Windows) window resizing issues, backported fix for setting gamma crash. 3. SpiderMonkey v1.85 (all platforms) - certain JS macro definitions required by Oolite not guaranteed or non-existent in standard library. 4. eSpeak v1.43.03 (Windows) - Special build of the Windows speech synthesis libespeak.dll to enable asynchronous playback. Also, defaults the eSpeak data directory to Resources/espeak-data. The changes made in the source code of each of these libraries are as follows: 1. gnustep-base v1.20.1 (Windows) - GSConfig.h (build generated file): In the section reading /* * Integer type with same size as a pointer */ typedef unsigned int gsuaddr; typedef int gssaddr; typedef gsuaddr gsaddr; Change typedef gsuaddr gsaddr; to typedef gssaddr gsaddr; to fix incorrect definition of NSInteger. - The NSUserDefaults system dictionary (.GNUstepDefaults) is written in XML format in GNUstep 1.20.1. This is inconvenient for Oolite, where the more human-friendly OpenStep format for plists is used. The static BOOL writeDictionary(NSDictionary *dict, NSString *file) function, at around line 157 of the NSUserDefaults.m file is therefore changed to read as below: data = [NSPropertyListSerialization dataFromPropertyList: dict //format: NSPropertyListXMLFormat_v1_0 // no XML format, use OpenStep istead format: NSPropertyListOpenStepFormat errorDescription: &err]; - When parsing XML plists, integers are truncated to 32-bit, effectively prohibiting the correct parsing of long long or unsigned long long values. The fix applied in the gnustep-base-1_20.dll distributed with Oolite in order to address this is: a) Changing line 309 of NSPropertyList.m of the GNUstep base source code distribution from ASSIGN(plist, [NSNumber numberWithInt: [value intValue]]); to ASSIGN(plist, [NSNumber numberWithLongLong: [value longLongValue]]); and b) Changing line 1103 of the same file from result = [[NSNumber alloc] initWithLong: atol(buf)]; to result = [[NSNumber alloc] initWithLongLong: atoll(buf)]; - The stack backtrace grabbing code in NSDebug.m works for Linux but fails on Windows. The GSPrivateStackAddresses function has been modified to use the WinAPI CaptureStackBacktrace method, so that it returns a correct stack backtrace also on Windows (64-bit only, 32-bit version not modified because we want to keep backwards compatibility as much as possible). The modified function is as follows: NSMutableArray * GSPrivateStackAddresses(void) { NSMutableArray *stack; NSAutoreleasePool *pool; #if HAVE_BACKTRACE void *addresses[1024]; int n = backtrace(addresses, 1024); int i; stack = [NSMutableArray arrayWithCapacity: n]; pool = [NSAutoreleasePool new]; for (i = 0; i < n; i++) { [stack addObject: [NSValue valueWithPointer: addresses[i]]]; } #else // Windows code here unsigned i; const int kMaxCallers = 62; void* callers[kMaxCallers]; unsigned n = CaptureStackBackTrace(0, kMaxCallers, callers, NULL); stack = [NSMutableArray arrayWithCapacity: n]; pool = [NSAutoreleasePool new]; for (i = 0; i < n; i++) { [stack addObject: [NSValue valueWithPointer: callers[i]]]; } #endif // HAVE_BACKTRACE RELEASE(pool); return stack; } - The GNUstep objc-1.dll runtime has been rebuilt with native Obj-C exception support. To do this on Windows, the patch which provides the void (*_objc_unexpected_exception) (id exception) callback hook to the runtime is required for versions of gcc older than 4.4.0. The patch can be downloaded from http://gcc.gnu.org/bugzilla/attachment.cgi?id=17365. Also, the gcc source header unwind-pe.h must be visible to exception.c in order for the build of libobjc to succeed. The full source code of GNUstep 1.20.1 is available from ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-base-1.20.1.tar.gz 2. SDL v1.2.13 (Windows) - SDL_resize.c:57: Added the lines #ifdef __WIN32__ SDL_VideoSurface->w = w; SDL_VideoSurface->h = h; #endif to enable window resizing without side effects like texture corruption in the Windows port of Oolite. The entire source of the modified SDL library is included in the source distribution of the game under /deps/Cross-platform-deps/SDL/SDL-1.2.13.zip - SDL_dibvideo.c:768: Changed from /* BitBlt() maps colors for us */ video->flags |= SDL_HWPALETTE; to if ( screen_pal ) { /* BitBlt() maps colors for us */ video->flags |= SDL_HWPALETTE; } to fix crash occuring when attempting to set the screen gamma value. This fix occured in a later version of SDL and was backported to the version used with the Windows port of Oolite. 3. SpiderMonkey v1.85 (all platforms) - Specific build settings for Oolite are required. Library rebuilt with the following macros defined as shown below: JS_THREADSAFE defined on Mac and Linux debug builds. MOZ_TRACE_JSCALLS defined in order to enable full JavaScript profiling. The entire source code of the library can be downloaded from ftp://anonymous@ftp.mozilla.org/pub/firefox/releases/4.0/source/firefox-4.0.source.tar.bz2 4. eSpeak v1.43.03 (Windows) - The libespeak.dll has been custom-built for the Windows port of Oolite to enable asynchronous playback and to also default the eSpeak data files folder to Resources/espeak-data. The source files that need to be changed for this to happen can be found inside deps/Windows-x86-deps/OOeSpeakWin32DLLBuild. The instructions for building this library, for those interested, are as follows: - You will need to have OoliteDevelopmentEnvironment - Light Edition installed, downloadable from http://terrastorage.dyndns.info/Marmagka/c0a11a8fe8b5124823a91ff1d90065ff/OoliteDevelopmentEnvironment_LE_20110212.zip. Instructions for setting it up can be found at http://www.aegidian.org/bb/viewtopic.php?t=5943. - Download espeak-1.43.03-source.zip from http://espeak.sourceforge.net/download.html - Unzip the file to a folder of choice, maintaining the directory structure. We'll refer to this folder as . - Copy the files Makefile gettimeofday.c speak_lib.cpp speech.h from deps/Windows-x86-deps/OOeSpeakWin32DLLBuild to /src. - Rename the file /src/portaudio19.h to portaudio.h. - Copy the file speak_lib.h from /platforms/windows/windows_dll/src to /src. - Start up MinGW/MSYS and change working directory to the /src folder. - Execute 'make libespeak.dll' from the command prompt. - The library should compile and at the end of the build you should have a file named libespeak.dll (the actual binary) and the import library file libespeak.dll.a, for use when you want to link libespeak.dll to your project. Certain other Oolite dependencies are built with specific project settings on the Mac platform, without further changes to their source code. These libraries are libpng (uses also a custom pngusr.h header for the Mac version) and libogg/libvorbis. Also, the Mac debug support uses a modified version of RBSplitView, mostly to enable it to build on 64-bit Mac OS X. oolite-1.82/Doc/FAQ.TXT000066400000000000000000000154421256642440500144720ustar00rootroot00000000000000Oolite-Linux Frequently Asked Questions ======================================= Note: Long answers to questions about installing and gameplay can be found in README.TXT and PLAYING.TXT respectively. General questions about Oolite for any platform ----------------------------------------------- 1. What's the point of the game? To fly from planet to planet, buying and selling goods, shooting pirates or committing acts of piracy. There's no goal other than perhaps to achieve the rank of ELITE. 1.1. I'm still confused, how do I play? Have a look at Ian Bell's Flight Training Manual for the original BBC Elite, some of Oolite's control keys are different from the original, so be sure to read it alongside the Oolite PLAYING.TXT file.. ( http://www.iancgbell.clara.net/elite/ ) 1.2. What do the various colors represent on the radar? White - unpowered items that can't mass-lock the in-system drive. Green/Yellow - navigation buoys. Yellow - powered craft. Red - powered craft identified as hostile. Green - space stations. Green/Red - thargoids Purple - police Blue/Red - police on intercept Red/Yellow - active mine (about to detonate) 1.3. Why are the ships so slow? It takes up to half-an-hour to get to the spacestation when you enter a system! Oolite is a simulation game — one based on a game design that comes from a time before 'twitch' gaming. That said, there are plenty of ways to speed up your game and cut that journey time dramatically. For the full story read this post on the Oolite Bulletin Board. ( http://aegidian.org/bb/viewtopic.php?t=301 ) 1.4. My keyboard doesn't have a particular key used by Oolite, what can I do to change the keys? Oolite reads a key configuration file called keyconfig.plist that (from v1.40) you can find at /AddOns/Config/keyconfig.plist (previous versions of Oolite can also read this file but you have to create it first). You can open this file in any text editor and change the ASCII values of the keys used to suit your own preferences. 2. Does it work on Mac OS X 10.2 (Jaguar)? Yes, from version 1.20 to version 1.51 Oolite works with Mac OS X 10.2.8. There are still has some problems with Speech Synthesis and handling events in full-screen mode though, so you're advised not to use those options. Keeping Oolite compatible with 10.2.8 has been a task with diminishing returns for me, and from v1.52 I will only be supporting Mac OS X 10.3 and higher. 3. Is there a port for the PC or Linux? Yes. Some people are working on this, porting my code to GNUStep and SDL. You can find links to their work under 'Ports' in the nav-bar to the left 4. I have a question not answered here, where can I get help? At the Oolite Bulletin Boards, the Elite Bulletin Board System, or by emailing the author. Oolite BBS: http://aegidian.org/bb Elite BBS: http://www.alioth.net/cgi-bin/bbs.pl?siteId=1&action=show Author: Linux port: / Linux Specific Questions ------------------------ 1. Why does Oolite use GNUstep? Oolite was written for the Macintosh and was at 'production level' for about a year before the Linux port was started. Mac OS X is essentially what NeXTstep/OpenStep became when Apple bought NeXT. Oolite was written in Objective-C using Cocoa (the Macintosh Objective-C libraries). GNUstep provide the same Objective-C classes that are used by Mac OS X for building utilities and applications. To not need GNUstep, Oolite would have to be totally rewritten from scratch. There was a project early on to make a Win32 port by converting objc to C++ automatically, but the project seems to have vanished. It is likely that the mechanical conversion of objc to C++ would be the easy bit - then you still have to re-implement Cocoa/GNUstep as C++ classes; a task of truly epic proportions. Even if it was practical, it would mean that any new features implemented for a C++ version of the game would have to be backported into the ObjC version of the game on the Mac, a much more troublesome task than the simple code merges that are possible now, as the GNUstep code and Mac OS X code are 99% the same. 2. What are the positives of using GNUstep? A well developed object oriented library for Objective-C, plus the just mentioned commonality with the OS X code base which forms the root of Oolite. Oolite probably also has the distinction of being the only proper GNUstep OpenGL game :-) 3. What are the downsides of using GNUstep? Most Linux users don't have GNUstep installed. This is easily solved with the dependency pack that comes with this Oolite binary installer, but it does mean Oolite uses a bit more memory than you'd expect if the game was written just with C and SDL/OpenGL, and the performance probably takes a bit of a hit because Objective-C uses more 'real' message passing than languages like C++. However, I do have a 5 year old laptop I do tests on, and it runs fine on that. 4. What distros will Oolite run on? Hopefully any distro that came out within the last couple of years with a 2.4 or 2.6 kernel. The package you have now is known to run on: - Gentoo - Fedora Core 2 - CentOS 4.1 - Ubuntu 5.04 - Debian Sid (Sept 2005) - Knoppix 3.7 (with the 2.4 kernel) 5. What dependencies do I need? Aside from X, you'll need accelerated OpenGL. The game plays well with relatively modest accelerated 3D hardware - the frame rate is acceptable on thte Radeon Mobility M6 on older laptops, and I've found it playable on HP/Compaq 'business' PCs such as the d510 which has cheap integrated onboard Intel graphics. Of course it plays VERY well on my development system with is a P4 with a GeForce 4 Ti 4200. Aside from that, you shouldn't need any additional software dependencies since most of the libraries are part of the dependencies pack that's included with this package. 6. Where can I get the source code for the game? You can get the full game source from the following two places: FTP: ftp://ftp.alioth.net/oolite (look for the 'src' tarballs) HTTP: http://oolite-linux.berlios.de You can also get the latest source by anonymous SVN - see http://oolite-linux.berlios.de for more details. Source code (mainly tarballs) for the dependency pack can be found at ftp.alioth.net/oolite-depends-source 7. What about other (non x86) architectures? Sadly, I don't have any non-x86 architecture systems I can install Linux on to build the game. However, binaries for other architectures are always welcome - please contact if you've successfully built and run the game on non-x86 platforms. You shouldn't run into endian-ness problems as it already runs on macppc under OS X, but I've heard the odd report that building GNUstep on 64-bit archs failed - but if you have an amd64 system - please try and build it and let me know the results! (Additionally, last time I tried to build GNUstep on Fedora Core 4 it failed due to an internal compiler error in gcc 4) oolite-1.82/Doc/GPL.TXT000066400000000000000000000431031256642440500145000ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. oolite-1.82/Doc/LICENSE.TXT000066400000000000000000000123001256642440500151330ustar00rootroot00000000000000Copyright © 2004-2011 Giles C Williams and contributors. This work is licensed under the GNU General Public License version 2. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Additionally, all artwork – 3D models, images and sounds – included in the work, as well as configuration files, are also licensed under the Creative Commons Attribution-NonCommercial-ShareAlike License version 3.0. This means that these files may be distributed under either license at your discretion. To view a copy of Attribution-NonCommercial-ShareAlike license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA. You are free: • to Share — to copy, distribute and transmit the work • to Remix — to adapt the work Under the following conditions: • Attribution. You must attribute the work in the manner specified by the author or licensor (but not in any way that suggests that they endorse you or your use of the work). • Noncommercial. You may not use this work for commercial purposes. • Share Alike. If you alter, transform, or build upon this work, you may distribute the resulting work only under the same or similar license to this one. For any reuse or distribution, you must make clear to others the license terms of this work. Any of the above conditions can be waived if you get permission from the copyright holder. Apart from the remix rights granted under this license, nothing in this license impairs or restricts the author’s moral rights. Your fair dealing and other rights are in no way affected by the above. This is a human-readable summary of the Legal Code (the full license). The source code distribution and the Mac OS X version of Oolite contain parts subject to the following license: VirtualRingBuffer Copyright © 2002, Kurt Revis. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: • Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. • Neither the name of Snoize nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The MiniZip code used is subject to the following license License ---------------------------------------------------------- Condition of use and distribution are the same than zlib : This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. ----------------------------------------------------------oolite-1.82/Doc/OXP verifier design.txt000066400000000000000000000151331256642440500177140ustar00rootroot00000000000000Oolite: OXP Verifier design notes ================================= The OXP Verifier needs to perform a variety of different checks on an OXP, most of which are independent of each other. However, some checks do need to be performed in specific orders; notably, most checks need to be able to look up files while ensuring case-insensitivity checks. Additionally, some checks need to be performed after an open set of other checks which provide information for them, notably checking for unused files and testing that referenced textures are readable. The general independence and potentially large number of verifier passes suggests a modular design. The partial ordering suggests some form of dependency management. The chosen solution involves objects representing the individual passes, or stages, and a manager object to ensure the stages are executed in an appropriate order. The manager also handles some aspects of resource management, such as autorelease pools and a dictionary of settings read from verifyOXP.plist. Each verifier stage is implemented as a subclass of OXPVerifierStage. Each stage has a unique name, a set of stages that must be executed before it (its dependencies), and a set of stages that should be executed after it (its dependents, or reverse dependencies). The difference between “must” and “should” here is significant: a stage whose dependencies are not fulfilled cannot be run, but the verifier will ignore reverse dependencies if necessary to produce a cycle-free dependency graph. The actual verification process works as follows: first, an OXPVerifier object is created for an OXP. The design allows for multiple OXPs to be verified by separate OXPVerifiers in a serial fashion, although this is not currently done. The OXPVerifier loads verifyOXP.plist, which contains, among other things, a root set of verifier stages (by class name). The OXPVerifier looks up these classes, instantiates them, and registers each one with itself. It then proceeds to build the dependency tree. In order to do this, it asks each registered verifier stage for its dependencies and reverse dependencies. The verifier stages may register additional stages at this time; for instance, the OOFileScannerVerifierStage, which is used by almost all other stages, is initialized at this time. Once there are no registered stages whose dependencies and dependents have not been queried, the OXP verifier iterates over each stage’s dependencies, building the basic dependency graph. If a stage has a dependency that can’t be resolved, or a dependency that would introduce a cycle, that stage is removed. Then, the reverse dependencies are checked; in this case, unresolved or cyclical dependencies are simply ignored. Having built the dependency graph (and optionally written a graphviz file for debugging, see below), the verifier repeatedly looks for verifier stages whose dependencies are resolved, i.e., have run. For each such ready-to-run stage, it queries whether the stage wishes to run (using -shouldRun), runs it if so, then marks it as completed and updates all stages depending on it. (Note that this means dependent stages can run even if -shouldRun returns NO.) The use of -shouldRun is essentially to avoid listing irrelevant stages in the run log, such as the OOCheckRequiresPListVerifierStage for an OXP with no requires.plist. When no stages are ready to run, the verifier exits its stage-running loop. If any stages have not been run for any reason, the verifier lists these. After this, the verification is complete. Debugging ========= For analysis and debugging of OXPVerifier’s dependency management, it can generate a graphviz file showing the dependency graph. This is done by setting the "oxp-verifier-dump-debug-graphviz" default to true. (Under Mac OS X, use “defaults write org.aegidian.oolite oxp-verifier-dump-debug-graphviz -bool yes”; for GNUstep, edit oolite.app/gnustep/defaults/.gnustepdefaults and add “oxp-verifier-dump-debug-graphviz = <*BY>”.) The graphviz file, named OXPVerifierStageDependencies.dot, will be written to the current working directory. There should be no problem viewing the file with the cross-platform graphviz tools (see http://www.graphviz.org/ ); the Mac GUI version (see http://www.pixelglow.com/graphviz/ ) will helpfully update whenever the file is regenerated. The graph consists of two special nodes, Start and End, and a node for each runnable verifier stage. Blue arrows indicate possible orders of execution, starting with Start. Green lines with circles indicate reverse dependencies, terminating in End. Whenever a blue arrow connects two stage nodes, there should be a matching green line and vice versa; if this is not the case, the dependency graph generation is broken. Additionally, the log message class verifyOXP.verbose may be set to cause additional data to be logged, including messages about verifier stages that are skipped (because -shouldRun returned NO) with the more specific message class verifyOXP.verbose.skipStage. The file scanner ================ The file scanner stage, OOFileScannerVerifierStage, is somewhat special in that it is used by almost every other stage. It is responsible for finding the files in an OXP, tracking which are used, ensuring OXPs will work on both case-sensitive and case-insensistive systems, loading files for other stages and warning about unused files. For this last task, a helper stage, the OOListUnusedFilesStage, is used. Every other stage, at the time of writing, has OOFileScannerVerifierStage as a dependency and OOListUnusedFilesStage as a reverse dependency. To assist in implementing this pattern, an abstract class OOFileHandlingVerifierStage is provided. The file scanner is built around dictionaries mapping lowercase file and directory names to actual case names, i.e., names in the case present in the file system. When another stage requests a file, the file scanner converts the name to lowercase, looks up the actual name, and logs a warning if the actual case string is not the same as the requested name (in “canonical case”). It also keeps track of every file that is requested, in order to be able to list the ones that aren’t referenced. Additionally, it checks that the root directory only contains directories with known names and that there are no Read Me files inside the root directory, and warns about junk files like .DS_Store and Thumbs.db. The file scanner is based on Oolite’s specific needs. In particular, it assumes there will be no more than one level of directories inside an OXP. Acknowledgement =============== This design was probably influenced by the video session on the LLVM optimization pass manager I saw recently. oolite-1.82/Doc/OoliteRS.odt000066400000000000000000227032511256642440500157000ustar00rootroot00000000000000PKF^2 ''mimetypeapplication/vnd.oasis.opendocument.textPKFɓH`<<Thumbnails/thumbnail.pngPNG  IHDR?`CIDATx ]EAQ@Ed!!$!a -mC7jڢm;O=VIPADBaAQ=Ou>ܓkgUk^UVl677Jxzy)o^7y7ײ @l4WٶFD?WkB1C4˟/Ϲyyӳxb!?wxb +f÷0}VF:`sՒ["d/N*GpV&gn,f><$-oy?C~zk>+-O?oE]ķo{<0,m{=|_~Wxm*"x%ORQMC /ǯ꧜r~ '+ptU x_}+Y'nB-|–si<)JAx ?﫝|vr^z',mV}*Xޞ}w>h~_2o<ڏ}cn:ছnx[n/|O?†nkqs]w]s5kK/lV;]< l? .w{7|36 V1c:C#3<3q{MM>_}'x|[zϞ={mvmh? :TEr9?qu]?裏]wu5Xhє)S`]vH 7FAwcǎ?q)+)w}۟ԧ]pcx餓NBق"[r47YW\q'y7zGy{ӿۿ'L?J__` um^{Wb˵^xᵷs'faǮQ,%Λ7 &̜9B6mڝw /,~饗vi?g(x_Wr*/G?b|Ws"?~e F=P]z87O~N܀ ~EgM/vm0o O~}i_ȳ+#裏~ _8#~{hU 5DM6B#<9x`C)F+]~`'#9t^{Sh 6^,Xsx'O mz<s][=|jջɖv-qJ̿簋myEi U|GuT<}µ_SO=?蠃p`hD~glsbw 7%\rgB~ig '?yBw 3B6 /_a g=O~@+]*H8QaW83 8GuQLX~GA !Y0Xu( |&F#$.}OS%rbG ['ǁljܹ/}KEtrWy=x»f>\i'el^KbITeJ-ο1hc9&`p'+# a<cnr> '?i$L! . =z47y ?y~yM5^h AW[m5]e)w]=XG3"E.':|mxஅ/5Kއ6E)[4Щ<ÔJ/ GZp'`2{tQx1UcĐиd?Q(ou{ow"v'ւ 1c-YL駟 :RDchH|wlH_J5%^H뮋@,MOěP5! ÇypN >X`yˬ)h4B$oo֨vzO QINdB? C-| Zc8SWMw-w}ࣶ`ݞl^ҹȢd&@W/FPg7 yL n٤ VS FemЮwi'.Ĕh KEVܡpN9\ X b< pp D ){Ƅ pcf1^l@ښ4}zvvx(B_k_fX!'o*+e-6x/o3i^u]T,gv#!% xNap8RNq .\Wя~|=~ ~'%>SEr(`̡`$%š@, Pk/_Puƌ^ ?( h5c_jTn G/: p`d<0pJ!_=5/>/(,X>ODGnp k _KxhRR_B0t#ν;xoEy'}锑U^i,\]{!DSRF!xƏiPH4\gvELvDرc6l,L-??x'e#4qD^~_|Ep0Fmdvr H.x _/BK qJt`]x2A2Q?7z,ƚaC_<8d:ɿ.4X@S냶;)"MbE-NtT$%%-Bb7I"xzYp aQ!W͎J!X+t pɾo,Xǥ+Ʒߢi7| bu3o*v04D'n?{ 0RwNXq >!r3\_cz>m//L- X\k~cϛW Ip9|})|F[ dqm\&k}5aNEi>T>Hb2H#QA raӁ o_5p?E+rD8ldFPَC`\zqMoDZ 6 ]qK7?8{N;b ]Zvhh? (yS|3ם϶1Nۻna kx06 .hX 2bK(cFX7@^jkӡs{<`6P@'1Bk0!k_EAEDt1$卓gkL #t$ĢqѣGwI].lCO?=d`N $`rW^y%"}GBgF)b'@B;7 CbExt0 !?"O>$?!DH>6K`w4%?sL Lo, 7b1) rvԆ{v{5KKET˯N:ggƻNyap>Oe #FϠ*X~r5>;t 7D ;}sޜp r! =#Z°;._ytWRKu!ݶi+8xr6hm#Ҋ'E,r#^1U_((RW^Svhx2&x Q(Sp40 `TeC?lupE9,EDW5Fx3O@tM*uQ$*Jw,kMbރnV b !a4z饗= NO0rĴIQδDp+6jJT7'bnBG:#$NՎfO-]q:MqϰWx…-wQ&$《plBrpӵ4`aB&a>./>H/ԁ ?.(0p<bfJP)QkܸqpIlSl\#ڿ;A KΟ|B,jq0PB_W .\Xec` 1c.=.pͷlm!AȻ VȌ1Grh7(* a 5k־ .9, fn;p3 9!d7o00(D"^ViM>}6N^$5mĉ2L:b 1Mg`P6~ r٥(7u9 x@ _B\Fn<_&`0`*5#ch(THԗ8S~2rT21\ TLQY,OLH^{`6$ Q/2@6"ZnNA06Y!~$'x 4ґ]ЊtG]^|Ο?$ri5ċW.Bo$ώl7Ow@v8Lpڴixqd \x;b !-%вڱ&lq᭠&/A$ɜW ,#%n҅l{U.MSv>dM4y)c,|-Aa* (< =1ǯz=zЀ1`dARN*&=#W*365̙6r^|cҵ;c1Ro~"#GDHSܜs9uHXH>ٯYuioQLPv>?яD9?s= 'aʟg_ם8<9$ï|0Б_i hi -:_:s|E7CUqwi'lnrEÆ !7{64ArF6˼Pf1n>7"0e믿>4eP<>|8>-C dEM AͿi&f(P}BHF*N5C\;GWe%L ۆ }0YY Tl_tëxK{5'Їӌι37_)}fmVqry|43+pOd B :Wa>+IvWu5'Bcƌqĵn  (p0"3A⋉P 10L./,OfR\+8w =Ȍp?җ<'T@Vlq o^`340i K3 `3<{: <Уa{V5S,.*X/6vHn=ôiӌJ9$ͨ E^jlrYB*3 DX^Ήb^&2-3M3%҈fFkRF@ kÍD//*(xB#;It<~3N/Qޛ.M;Wpt-8qAl [7<`ph^,p3v a?w}"r8ZE-@Z HZVTCT$ ;0Q"ES jNvpW!16%a[?S,J%(9 |.od{ ]ΰb#8p!5?~G6r} 'HDǀVP0ݵ  l `, R3 9x"5PI4 u8 >+0-+֧~e@ 0$_yb+I۸{.ZHBMXtS%9SX'C[KWgfZJ26w`^=` aFBX1̓`yСxwQ#[ !5ŖUJp M;E|ȧC=M3G1_iΈS 04Ѹț)RÇfZi("rp*{g֤AZ 9s&qުAP)0 ^Q7ifA'xJ5?DdaApm3_bBrjş-C@G;>~xn"NsyH"+bW2\ZEgyŢ*A ρ "[ Rz x*nMI`͚i^DZ a )+1qԽ{o)=~ΎM[l]*ÁS ,C ]8s|9WY 4,Xo\-\,FZǝzJ +sP(G6V0Ojw (R88k{?H^њ@K4`@ntD7mЅ^|,S$_/JRb-hL1u], E[\tEC q5a@A]dZdMںuFBT84sn!õ \Ggr$k1˵! 7Y`dxw/'[Ǘ{oqѩ }~&):D"oXd/~[GwlHh`sԩ0%C$ϟǙF[mo0KNH*@xQq#nCj =p'gI0MfYϑ.; _o1܌RprbMd6OyMqYZN^(ӦMT`,XʃtGxYS:4m'V s_d\Q/tQ,܁ ?DơtheAM%Gc U5|lh=ͲTȡRd P䉜ʩD!@ AFwyd+J$:U׸(+]`R I:~@_sJmF9݋.' ȍ[ t_QR,d" OtR|i@Ah67T6"'e&N6Ҍdfar<$n\ֆF|;]/ ,}>+->Ft8݄ڂMvW&Lpw,hA\*%ׁR'}h3 +P|up׳vi'EpU-஻j,n h#h)ޝ[WLs O :dLޡO3!' }^ØMW^ 9]21XD 72~7=EIwVueYÜ|ky-y8'ۿs7;Zp $B̙Anp;(?n /x/(m|&mqDx'|.O+\YFOx[o+A믿B }nưVy?fcqrF̙sm~!_vqǛn؝  3~>YF<9]띀d΃!"%zFd) 7A x >+vH1-#Pa.M~10ui.])q!אHLSDl,5@MAdos(P7Ae\:#1r;b1*vjQ..>OxD>"9jUQuf]X'OwY")1n[nI]O$?蠃%@VLsy_O# D&26uFk ^O64?q<jM"ic5$B1âtiƟ8-DZńʳ"2~k `)˟nC5ߙ ד/2e*zs'C vFi'E\!HY^ M4 ,tI^kd;sِ7 W:t0x I@"fk v 'ԍrn#^q,FQp"}w2dyd(N mŶ@0>M\_Z{UrX^߸vſ"pGDa[vmǟցG4rN\lVd:WNWBׅ} x¿r ?mf40˫Se8:$h`*+R9*,ެks=5<44vc^-zaSiV7Rux|7qΜ>>8ӏQ8x0-C) @Ś!W$EeBHk .xg:Y%Ѕ_@J|)*XU+B#ȍEf̩\@ 17na ٰrO .¯ n-*De aӕUeьoo8׉@X;<,;<BC+'aGXΒPYY_s@TCL,RBȯhQ^Em0W=:ؖ1n.G54<A*"+;`,5d)ZX]€Ț1OTJ͛GDlzì'B$!W}ֿ~*DUGf =bx< GΘ1._wuQ?DhiSLWl bsW&"9%8lzB9phy`w t+๨Er,/79`6!(h`:74*J> T4l>.ة*DQrVVpЬp0|j\8F8#rٳgò=D7+XqBsԛ^)u]MZ4=(ɢJ\gFaرg ;w.ìyHfjB Z*ԩSsM7uy'#zaxG5oȄneD~[\cCxc= ca P0ufCpOBkܖPAx;aiP6dC 94WqU]@܄k뭷D܆4j+G{5˃~Ġora@8bN;"-qI']r%'x?y7X pBT+"OExZO[@B2K&J)8nLC䈷v)in.[4q pFҷB0 8ġ.*JBϼnрEFqѿvDssVN>̖P \FC2<$GAGn&: + ڔx]]:qDj ZqUxL2u1{^A5Leһ:69"0\Nu)hwG="x2>lFYQʯ}@hk,)"VD\3;H †LۃWRKjV[X9fz0k^ x͌EO+iIUk ZcV#}/ KW&PF@ZNRvJP _*!\7uNai+^ ^@zl{PEwhhX ow~ 5k9E63hw' 1иa*| `MN3!24wL4Afs=̙sǻɋ'gyzI[:4O70HF 1(`w`SQk(J/dcTZ4!]EwWZtWb٨Y.؂ UJϦra-4:‰[5i$X\Kogq։D!<|;q饗do\[ovx$T1cvϭ2D&Z{uP=f<.kp‚ }e9{UP{qʹ.jjj˓ٌ|Kk\rL\] `o =g"Z*s=xAȡkn^7}Y3מ|(y_@=<p""Td(-wVi_v[Ԃi zw._r<(E`_W=\aljpf8b,|؛NpaQzZO )zꩧlvر<6R(_7Fq!5Pd{yR<`^H57Xz3283xW9S.8 4tg_* *gX ^/Rǘ =}g*:3P ?"/`WOxz!)NG(i_#eUqB7P]x L*a$F/ba(z-(2:t(t6[P=car5L,R~rԀB.nQ[l/V)ï``q֭-]-$zdyj`,)Vʵ? _NK~CZ\kYx N_ĺ@Px\\MxB; $_.&d~i)*CF.^==*Է˖Uv$vk~!DPtErLuH%ѵ)ĉ]lnH轙P 0WZ>O<2OHĈ^r7Q8gʷ\nUsEJMgƯOn'HW|e )<2]XGx4Rf;z',Oh0цZՒw}whZ}w0~\~EXKWq0XV`P%[}:|۲;h{"%qO ZV^{tU -HJ i:蠃05bw`rΜ9+(;kW/FlŇ6% 1b7\|WXkh(~ zM 5{L?$LSE P6֥fhP0p3 7 oN';vOh͜.\%u}X?va0$];Q8ou% g=0E*lHυpH#ˈLmrT3K-9E;mhw!st7st ,Hg;oafUxus |8i9.Gd6K?fa&')9YNQٽ- !0 ?W ՅNk=}s{/+NQ#:$_Եyq䥢 `)v#]LC1dD h:>bӦMt;'# pBM[FlPK v)'E Ema0el. .ӛؔ-cu&.,yeʣ}ٞ"sEG^/`<sαz+>,Vb\E"SyJuĉit8o}Wś&M-f(RgH;#ξ h s={| ,7p);J*2 ON<<`ٮTW rx,f'{O^JWL~uorh!ֿ,{2a6(.R+A܅T D5uoV51hDr_*ˮƴ3WG-HE{ a sK-lfKT/Q00IazTXTqdr00蜾/ tUG(O}SʦiBz#2d[[ڿsFm$M׮P{9,LZ%^F`k,G>D*:BBnjL\+&]/Ȇ !ʜ={vފ^"j yvNcV P 1m}q:b8@Ӳ _"zꩠAq z*"N[^Cՙ­s x#ƛutƌ"\:WhbUd*A|Q\7A!GWoYZnEM(_7Lft^Ne:Q,=.$Hx@ʛrm!UA#e$3t p.ށitY@BN`aޚl!>kK1H/!uSiQj?&fz8(T#'. Xgi{Jc_|8.rx:z/S'4;reC.a((نqQr5L/]a?~<" (889V#uADt!*< Is꽩أŬ:>w6fi"/Uٸꪫ)ٹJP78cn/R6 7'n ) `<'5euqWdTpS^|& tVqk(FnBŦW^n/3b]5f(0nn1ʷ[^ad,*viTf."0#NO͠(Oh{p16ϓtfs_m']\N7xcA2Xˀ'ߓ CX?_I'd*n4V!owY "r*lg#w낛b%&i`5} W~럌uBSLA!t'<[ */v2OEު{z&V ނ;o=y|]Vas RUSoyQ<[o1{tgj^YJ DnI%B*+;b a5zTQn- Ry]v666N<`A9s.fT6dX̩҇m(P̛YɗqTTےswb:p1`eӧ ^n~~22ly -CVӂ N=fW_ [EL2`N%O({n1x׿5+*$k#G.l0cz} D" )B*m.2hSֱ5Gϙz#;I\+&5f*ip` VK R[^KÌ.Od4by!`oemg!0{8ڽKHЃ{A8 yoB5c5?FHWO^T?Xo=yn!#|=X^-5.w3^ P5g̘a8L{9cn!;h  SOY:C ]DX] ']WpZ?>:>*N/#g<Fe+njQX|5{rNu-l,dݓy2[T4#/z!V {fZA1 =bhȻu㉑ͬira^aA0dC0$Oլ 與)xDSU,'  ks_?brsZ|gg4Uhx+믇B.v⎏=z֬Y ߌ_)i8 ӦMs2 yT?Ԥ;#Fh>ƴ,8u:*A.41/6dEoM:U,:@Ÿ`Yt @ž //Ta_zD (]x Qh_6@^yk ='ovj`f!*ҫ@K~I!=a6Ӷ)`@ O+n$<[Z @H#lȑ#ӅE`ӧOe3f N G#E>&̠+NiP(B6 t_wJ-ރi:f \A Hp\HeǏGTzI2(~α[ ^ówI R``nXM(T9˛H R7FЅ rdCr0)$r<.rzSMK`Is5 tA濑9S=y|6R=C"]:ܹltH>F#Dl񐉾vM?puaYMa$]׸WW) 3CY\Qo.U.KU3U |t(Z#Ou Rcy^୭PW$Ոw$/b4s5n\ D![  J;L&wcKgYN T\2!J0ee9s,KEva_*k ߘ)8ຘbwp; ;$78OǷ2`eRQd(hwpB w܌PMA@_0HOHyΛ|.@2A<[nq9*Kj{[^k`eae0%ȷ'#|u$0p]#R-x= TMX]{S"Լ\9|OfޭTaW>g_lKw]Pɖiv),u tbHMV$gB7ޙ"aה>~pfPwsy%a*jdU6%c5f\^ ^4x>wć l.(³u,jd7uc*5,|y32nh6Oii!e${TeEVBY'r;r!0z47;P썸q\8я~ԣr[dH{=*lgQo7}#BȽN0 H#ՠ<*Pcʐ1 Z'x]-+#E&+T.c߬7R xĹfMH/>, aan>W£hN&H:k 1l%U$\tv{R)(i9enoiF1 솥__o&L`_w>7x[oCOF (f1\ӧga~+ 2~aTy-%li_d ܕj3YEvOF@m+ _6Sh?9TYAaÆꮹG ǎ="O7|sx. 5ilw |<W_}u#Ms)CӼn\" wߝ~_g x @`ƌS~zA _aT~{]_5f*"[vy9ɶSWlo=iwur"wyvPSN9?"(-s"$~DIW_7+Sxַ̝형?e-O<1z1(HIQJ?O:$'48,+ews M+.N]S§={6hD N;[h1ݯ9a$weWf8lt; SwK> atLS&>k^}eʨqd"c- oBؘ i kVr磊=œ.E;n5;ȖNQS>U+\:g,`k^C.!g4S+A_NSQY&E8 7y]- hd;!@K0/ѵ4+ 6J|bgqj\2yqv)rGX#z妋4KӵyF*n[+'z?  zgja8X*P6LlZj/ =+\p7`<[ЇyTzoPd:'.&L<%RBIu+Nw33W+7jUƸ\=OLlҾ-AkZ#ʔMQ@GL0rHy\~t fō;WyU-㘝39os511*AzĦ+a kg׉)g,ow3q-JiMOҏU]Xt ";˵0BP$U XaL~u*%^~͢Vo .d PM/{S+s >5`Bz<܃A AtC\ͰfZG?) FX:qtF;#-w9bsT421J{;`, wm7y2`r@Eo!=1rtV-/Sk,ȹsRfx&fDjyL<gt)N j:#ﯢAzYxpZIݘU~Q8v@Fڢ2l_.رV ׉"g]V,{キH(\BWCapGU7jrs`WÅFĦot!x&if5]UWXE& kI|!9Œ{4 'c4͝qoFU fC=dհ-l*0]\KyGU$I%E.5Ch4|/WdFQV q{Һnktu3t瞋 -9Oi<7R2΀V{qTe)Ibj*;'';}$V_.J5^b:.N aWۉ\`n'Á3Y;wfΜv!{EUdgk7Re(]wXbR0!!|oj:<,UGuiCibg3*fR242 Jm1bb4t{@, bРAz3?܊c-F#MY/H.&e-rNКz֦Fէ!`IFJtb)bq:3L%+JMl}r07n(Co_=;e+,_}k_+[փ<g/T[lXR# $tXߋ=:sso:-=eEʸ*!f.*b0M7$,$z%s[Ҝ+*3xlF*:b"tJ48o?(ElH`Vmf-ƈ'H -V[ak)\ l%zaD{Lg_:ȂSrC7tTYEyF$Q7^fVh鶼eu^;_UꍻgÖg{v-gX`*ɚd4f#m7LS]s32c,xQ$Nၻp,@{29q[-WVѹ\fZ 犪^qԓ%d zR )r G?QnUr-ؽ ě6WڽjEQT.JIGWVuө4jhR7@v3&G#G,[r Mj< MO1ZuZQo-Zdu Wcq]5Iha⊾Vyu  Sn5ׅ nf.{zTZu!3hΟstMs-H2s& ;+Xl|ytult1gV߇ۘAR-|L5FCHoyI'jҖYFǿ-WVԞfC|@_ xtcw}AO?t^e] Cp^{Y8~=0L>}$/ _&ʊz(*-oqWZr⍴=PϮHDmg˲Ϫcˈ)TPh+J8mRyev2OmJCWD0+%(s(<#>ZyO`I&yX XV-6֩_> y*] Cϊ3As1h>7x r Áq=1Zr: >1Λ7h v$fZ,YQxuиx.$C L ߻Sn`={6p"F4HXhJՁaEvyk3]+_iY .\ 8 偄ynf_BE( wc.sA{,IKb"cGoq[25Õh`! OX!sa&Awߝ͠lI?(6}Ԁ?mQk-`%za. ҈! 1dj+8X4J kGVf6,lm[#1 s;Ӫ6(>͚c 41VOkFB9]g5t"ZX0.=+E2z)'U(|3GO+2ڏQױ2AK,2(6R}>F5ټF"K̻G>d@h1:B&r-۪PU Fodĉ0Nģ5F&87<*&dx@(6 SLG_B:%t_)=(bcmM쉂|=.5upQװ ^v#~p-+B̯nC+ 0(36[M%W"_qN(m3 R͚pl݈#68l'azTYFx2Y%&pYm@C[u&dX0ߗ.ƿH5A* W[nx *quYxWoiN$ptɓ-Zt 'Ķ<-WD=(U2vKp+j'jC|yHvP=H,jy3s{An<h hMضg|Gyp c@4XzT, XpܹhJr}8HdaPs˓&FuH EZH*<|MO9'fJFDET,x蘾uBuUp;vj~ѣ aSƆ]wݵ'mL! ; XnFm7KuR@Ob*4-L[`SQN-`< ian)g/ہzw \!o&(Cs @A]zM-~ !Pl?\,rɬZͲH[|?{HQ=OͭehxzWn|f97snGDZ?˅3DK;-N1 {`6%]٩կ~U&+`= LqO`+Vd0mhhL'7.(;zg,ae}W(WJ쁈 &|ns9gܭފkqպ<Æ]0QK :O  jeԊ3+ҍ`5jqO8ƙh,x%n_:C1fcȌ'1d9>lԢޖuߩ{(2ܼ>GQM[_` t6rvd$˨Ti L~DHA#ap8ʞ=i`Oᄏe:èz LvB%1{.:={JeM 3#Q c` <nEyQc:zS "08iqr@1جk]?]N:ɼjx@̯˞3WFJpi6) 8DT0JJI|]ّ׈`%'6}C^w֚XdzJ26?EfW@h/DPxZ"1"il¼b@93twHF{~`8+E>TH7nL)|QWR4˫kݐ+jOqRe4h :u*.︙ۦn ?F0hxC+І.i-nk7mj+P*ٴK! %xp4L ,W XǞ@|;p$ɓ'[zI'Z@N?tuV>`MPկqxkȃ$JgO<.&:r%vHqӓ?O0XdթaD#`#|(`z-c@A)M?-\o  S̩a3yǍ'hȀgzh6i!EڢAuZHༀ 98"1 '~@{M〮_p>CyAԧ}Q8]3ۏO=T1AXcexM4(;~0 %I9w^Ԅj)]8ֳ]|[Ap z3veZ쭍IG`kO? ӋLAyv̀aڏ+FI5C} \x'z91|TCi_MH/<S+lb\}kk d I\NS/ 5YnXɼ\"M9<iDWGZd8̎D*[V)սV~E~Lyi6s,nUfѻ,X#k-s*W.rj)It+CbYAPjU)b@&*hEپYpM?Q4B6\$::A \9fKԘ?yc$R+,9$F YX?`'^+~xb#0>8A1|Z0Ywxf'HZb:+hT&}Wk5k֬wwYjaM6LƭxB j7]%Qa63H^[lYv=Ւ8`V -|iNV^q;"ڹx97ap{ Ar^B7--վyl;^1_82hD,Ioͫ%\FCd9 Z.])fԩ0+#W5 sJ \HC<'@! `=,`` +(B`*2@ @gx*OB3dAg̘+{B[_a&gmh"De7G"hG j\" olA>}mFvA]w 7b `x{~<%GsP춄0^N'Z>uWZKh!gA/<.Cb6F괙E<~Qoy#CT`3#~Ϲ>)hX$q~:ӸP$☯6]m&vx`r 9)R~I'XSY6Q ϰ8Ry< wa`<|$p!nNG$ٸ77|3ѭ7pC2^{mlӫ~;u ?ةp}F\.^Zbݿ.o M6hõ^ `R=P'ݦډ L}4҄ 6X1X,WΚ5{K.C={6Ap+8Q"0n6ða[og2KB<*j"UN`;I51J-B) }R9Z0nPdNy#M <E8Y7 ";tݴK2vgyid_uRA_lW9qߗմєp^HQ-Fam3."<1g'Plf.VnˡYNBcF5Puڱ'`jt~ v[ο! V hA;]_f9z!{`Fz{ЀI[( Fss<H2* HuTn8#>8}x|3f a+t 2`,xva~ENTQDٷ 6+HM1 5AV@N;m5{,Mga3e6a#6vXE'A^<^V_*>q=e M qEe2 $"4R^@6S2f7(#1ct~3e;R;kjƼ~7裍T˴9%!94 4eDt &Oܓ |[:@G-8~xOvuWbeFZCZw_B =LW^s a=_]e4W1ŻA@{QGF?#E<1뽑5nyW щ! q'Pw}&4O KFi%-_?Hn.kzQj+McE:2k|k៌ۈUwUmPQV|nSEX.` $1Kn٦㒜W'S`A'ׄixz #G8bM8|bᶞTΉ(\E$ZYCmFk&W0PL fyT0=ø#oNnXvc:q+hWɻ nɺZioN);몑m|Fn?pႚ!D(ҺfMv[ ";'8_nl*R<`|bS#d[6pcU \7-“ѢNv MWL࢏@ؓĕ7Rv底Dи RZݲ"Uu +.]+`[{&MFg~衇Ο?I3SHs9pLo~pѣn'8G9 M0Ey_sˌz 1d&΀|şj7d@"@ךwPaEpee"B<讄M#^ܹć$Oh@SO2'U.s"a 91\p3lVE Ν;ם\rwD/@~I'tZp!?I !^{-`٘bE2嗮%d`e`OnKE3|EK0fZTSF X.ة6[˟rmt1m'GQNi+ [9BџxC]dz;eȐ!zϖQƍ+QͽS (xhirXBן{Bo|DM  baC ¯VDswq#QLVґޘָKVjd+E7ڹz@N_ x5'õ4r޳f:ZvW|.w8w_K TFV$ϼl/qoEqdfl7x p~?6e{>os{x"qu'_&#ᥨJsEy|jT"wu.Jn*.rOGk!Q^o _.xw%>l6%T7(1NO OpL3b=[ʪ;KPit#L%+p3LvaÆ5%qDպt 7-]*zL"<9rzF%x6)-Zį4{% *` ,Ic.{;bj>ڥ5bfMXLX~|O<PްqpIBSB 970|X8G;=e̠}L\=O|Yzᗿe:Ԋ~>|H;\B*ޘd8i6EmQ[ip\f;Xe -Z|ڴiEKΚ~6&]\xsΎ0-{}?ka.I}ž{mf]S^(Ow ϛ,Wx|էzڔn t_UE]|C2aC<*W+e@Ng֧*_ovݚWQM&?-.p_:WrVTYYCgmW]'rZ[LKeLF _r/H:kKtE;D{pjWU_w`B.lV%`SIvl(z_quVW6rmdx?R/ ;m(o=!]Դ[,(Oh$F5}eӶ%x%yiV[6{{]*翾^;߹窫Ŀ%hb+ÿ)eɷr$sU=W_E#imJ ^Fa=Kk? f՗+WeTjomg$eMkt;ZĘ;h7c\y啀^[yeC% ZxꫯZ)d66ӦzZ`D#W^%]3vnK6IϼF_|/@=4U4_|E{u^A.:҄v;w?_yUw47wn{^[yP7W[meyoQk;r_~̙0~;C)㏿EKƋO=SO4h7`ԩS]dYgc h&-kOMY _뮻:*fuގ_f; J7_k'߽*'9Svm p{6q<*;v9u.7j /xUߺ:71cRţ>k$2hXvW{oo&Zy JXS?ʵZkM7Eo$3H6oVscL%]@@5X`,ޢ wOw4Á9D25ᇗ<-za]{zzyCZ~#{^a%Ŋ_Ej:M(_OӍ7><=q}5o>쫰 /N}j`oq:Ν4f^jo[wc^z&]cMSX/}I`4s?<1 _|yWl< A30%'#h5>؜9O=h  ->/L;iIM;x`pbMvNX _|FGC dI…O[ye~]l :}˨ՋkyCT 3JU"\[A:" 4 Pyhq柔`)|ţN%jUj:{XoUQ-<:QZdðv^ /q܍ޔswJʰ}ߵzBL_|sSO?y ,9x`ԫɺfi  6ydfOϜ?>X%\COZL7=7`߿[Κ5=.Tn="Oby6HWf~o,Ȼ3M֏Oׄ%\\i0%GQ6 uNϬWӨFȻ9m e}(Šx>JJzo!V_k^Ach"]y^s 'V$8""(pxJA::װ>l3 ,"kpƛNPЂ[EТnkI˖5ZW}z/HX<Y:WVk{}A/5{yӎ~pvR}޸|l 2b"Y e\u~_xTZMz~G#R8+2f277 % /yKȿE61vTWu=nwU^]){:P-<6o^BL+vsy-n[ƋZRZ|['Qm=஀7zSί4kgeu0o˛F_٬Nџڭ  j ;}k:7 "QOE6aIENDB`PKF layout-cacheuһJA?{Đ BF*DhX R|Tv6APgXfN2Cfva~;?s&n<8KAP |><>"w6IƵ|LP i0(T&njULAkkYn YQc4SM1,1,1Lm4ZNRZ:$!R_4*3u35FuH`Oc;C;Z=9{r0ը$5 P])կ{Ea.eW#F}eI-s{tD5XtNgy/?$H?tq_˷!PKXo!]PKF content.xml_sȒ/On̬[soPg[3ږffφc,8.Jtloč{#숵_g$,$ ( &eeee/3+̕I:ÉH#Ǜpթu>7cgHޏbFt{DN޷C'|39O߼=VK=g7o>oQևٿn|zOY{)S7Ycr}6#\{dEggOOO'3띱+ewg%Lg{g$n-f$;f5|d vY6/We^u̎{:_ֲ̲~ }a33{yWe*ݮv{۟'"ۇ;\qܟbO>wGӕ#=(ghpJffͧFLwY@~3ή0l)+ڦݿI0ѥOC&O+`j#M={V˘Z ڮۜ\]oؔzvmBw!|B*@tO[Go|(aF\;H @ @9b~S_tl c]Y tpާBJ>x;{ly;+#۳w<8\;j C YHJ׾?s?_S #x/n;ѐZxI̴#?ɹ?IP;&c5~h?T%W1E) a-bX'R]K q7<6~WQcR7 @7n1W 4<:GW9?uIQE{=~J&rDC&b٬nOg ~Аp75S*@_\,s(O )%7Ж)yR@OI?i ' xјvexw C.D)^SbgbgZG=0z1^){eR*P)R 5dnWWJ#5Y'r 3} 3ԋ/P;VzTՕ{=^nC*rEpɁKj TAXqMjB\#/Kb?ޓw'RHP)=гBbj9EPbqJh(AR=0tsPճQU*Uqe6J`Tk+l!ks .TLTv[IÖer(wwYejxe}$ƳstP,a/O#lmo6&25E}96x_:GGQ_)A6 6/E}}ͫ=GGQ_A*|{{0 L eL,?gfOz;?ͷp]b"kwm9khx>]7'B(8nȽoW.%ӵ{O׹+t(zGO/ꅒ5Oߧ!~ V+^3 XRSYPuJ4*P+)fe*)u$ wVhG,qTGȝT_4FkE9FkQ*mGOuE Q4]MW08 `=P,tV{,HdSwΙf1$Vnrz)`2)c -B=c@ڎ jN0@sPW(0ñBc-@&>ՍĝY&](gw%YۨBۨ[dȧ-Eh- 7x-~Yh2n #Fjͩ2ZvTw[-%Y`IGRrǰ=Q+` )JEբH,AՑ,tAr=(#Qƹ%8@7 5A5Ľi;TwC\kzAY;6Ļ<,mG Ou[l;M1@D 0 7So⭁75h2nӀYO1O9Oojn=@3n)^*$(-K%|tMQe9));*ˆ}@'%U5tVѪfSݸΪ]U ,iPU)%RJAUJ)5WR j.91B rx+E@u(R8Hc+eSmzY`2JGUT* 7.G0jG1SmbZܸ@qW 2f\`˸SjJn͸RP3.Ԍ (5[T(A5` f`T39Bu66Ev"9/n9M!gӄ"Ͻ E2 Er裿 Er Er8 E2 E2>j&lbh) ̿dE ̿ ELW aDӹ=!S?ߔoG5_7.flvB-TcvJfkej'v;Z@/T|mL}L+ɤ?ӕ_ WK<3tagI&kHi >L@tMEs; ^aw93jCynm@mhPHu{7d56 rІA .2A[/noeMڝ}SLy(N5J Z}z5q?nT zڳ-#ӏPegJrGN,m~)t$rU^YyV-#XϥEʔdwv30n{:Srk>$V։lDX&5#>PʃbK AEXV+K0B`ὧQRtih /0ȚO0'< g_[*9ځ^emh':\rEyنA|6>h3T9 hzі^e5rPp5.J#}nA^am;} AkgvA;ҋ֌5#wLJQGB(G)l!h(/[1} UBkmUvB;ҋ֌5#wbUJQp@G)h(G)4h(/[1}U7Dkm[3REkFzٚ;WR؆ug5sQl(XlKͺc?юTџhGzњ^fNT)](G)4Yh(/[1}VR.pb#~@1ڑ^f3+Umj~5Q ?(/Z1VrqוvmK#юT1hGzњ^fNRT)jݦQ]M _gKI MՎbWu :%U|LZ9HݰZܖ/Tuݘ> ԭi9py T#u@p , YF^(@tT, YFnp(@t'8,Ȫ YFv7(@x,إ qGFn"Kb !R~z=UA]CK%k(NOURQ=U՚QVѨ𝱍JU0UheG*`ّT>vQ|#-lJb[Z4ʲY݊-0Pۗ`(h+Ja %ݤaJ1RmHJ)a4a1 JHQc)4Re=EJT1-Jj"ch'[a<r(*y l"mEڊ h+'0VO`0ђB['n]?QKpS!ܥDf)u:,rYBEXZjFc *"%„7y٩``K6O&fvj>D$.Ķ|.%5wQfvj ^mȊHvKpEW z?֭;g-U`LwRBR>} e+~z-؁ Pm@* B*4( \{wK4&ꍱԚwǢ=li0Vl@[QmE %0Vl@[QmEs/QȠҨ9}v:⑶=})⑶A}n⑶E3ggL{Wז8S-q^[Lęzm3g%kMך8S5q^kLęz3Zg&kOIhGa[d7A+Ԏ3Z͜a4f%|%0]2M9PSnhTybԪvtNiOT"vÌ¢ml߷ݳ=vߡHWj (cWQT~oD|{IPrQO[I,;hORϱnظi/4 3Uo0*3+AF CF%6p5F6.6.tbKː!ljl`.2.2.rk,.c26.c26.c26.c26.c26.c26.c26.c26.c26.#{*Qwxрw xwx[fwxwxwxwxwxwxwxwxwxwx    + 6` 6` 6` 6` 6` 6` 6` 6` 6` 2n4xhxn4          nC]&x┒zXj9^II)krZrMP)>׫*6\b*6\b*6\b*6\b*6\b*6\b*6\b*6\b*6\b*6\[ e wL&Skx2Z9ʑZf!z װ!z װ!z װ!z װ!z װ!z װ!z װ!z װ!z װ!z s52{||=%s/'g|I<{K#ߚtܬt-/cK2t-/cK2t-/cK2t-/cK2t-/cKrt|O]Fyde=!U%{h0=% ,ӃжHa  òF.L2 \dA0=6paz!l C…A Ӄ a#WW$4 yMB^W$$f%l y[B^W%l y[B^W%l y[B^W%l y[B^W%l y[B^Wv;C8+h+x+J + + + + + + + + + 6d8hxj VB8khkxkZ k k k k k k k k k  ^>~.IzѠz z[fzױzױzױzױzױzױzױzױzױz)yx7Ѐw x7wx7Zލfwx7wx7wx7wx7wx7wx7wx7wx7wx7wxョgv0qy@_|- +ji?Z\6U3R)9H-O+H54Ax A5ԮbtszUF”SIA2F*fԶ9 rtA.6O6>b#bTA |2B ^k8^v+#b][DTK"+g7aR_WlCQ{ݴWRS,)MYvl26h{&?0S|?YD~S}'קbvu=m/"W*qlՎjw /vC@ ['mzGrR6ҞߋL-cvsq}yS ۑeYCJF!g@l},N"-}^(vj>^}^PR",%+BIPR" %+ k%W/jWD/yk+BG~tEbNDnǴTA2vzќ5#euԞFb=6deh^jWq TKgSG+2/4S\F9ӥ>!є'9$ah^h\[glN9(;{ SX)Zތ ,Fz2Pv>t^N"2V&twu%]_ ,,wkr]V hĭڕZ%d3{]UWe~J85DSX%D|tQspT|?qAbnm/7"^HF2[#^F"-_E9ӐJ}‹ > һm{JfwWĬn>Cl\Z^П]mӫ'hKW*Hi&v^[fVG7&Uq.<?8av^02}Wss,&C%M-ov3;ߐM+.e|WN%/MJ~x~0W[3vk#d얷t*-m tt]L̼Rfj&n]`k^p= 1(|3$r?;C$Cg=Cy#dD[V xcKD hԴŘe6bRC7h&kv!keQLi]jdW3MvC"!j:Wس0fub^BE.2WO#zȧc[AܵG꒑igduZm+U}N~ ;:sv<{Z`TĬ Af`FVI%oۯgv5<$mܮv/7-j-Ym|A [˭Ȫ cٓp8?c!x9,Oy[ n1!t"8$׼jT>N[-x=XbZnWvPbZJ}5 ^B[/r750kR&S6mI(K5&̺yU]η_STxCh8-!8[醦$SIx vYkR[bd fIy(ӌTL&o(8PΣ,/Sڄv)̞n^BKd4} ߁;!v60 zYҭR(:dN,)sy)oJ7ʣ^ <\^!TS(1F./76ryFȵ˿gW6<ߺFsٝ(Ƴ\1zi*iV S1J"ϴߪMaAd%|`%{2+q-l6*9H,UDl8#,9 CQC*V&70&8*/Ϫ?a/"EAMNEZs^Kl'Y7m&+~D+}Ңu`ݲ S[6U }`a ,LYU^MŽ=|QJ]?$ ߽zz.gfOzޛ}5 M3yfRY RFځap ICӘ嬆 eg^^iU썂qĀ_Y[3zoN|!j2zLf#"A=snI6yib^}&KN)A)$w]gRZp][i]]RApSۤRҦBm?!ae"~pJLlU\*RBWB"mAҹT羨-:mlV0U&s88:瓖wqǂ<;sSLc,yL~5 NWs۾57r?ݶ_-CE)Ga& /*̺}UGq^S"U~̰-]IJɎtU@w5[Q^o mnpֵP0αS?pJZ>< #)Q`?%?s{Dy[Tӗ>9hu0mv3cS.̜ 1G ]b@wtI/>a9 xo0OF?dI@{wĞ%7~pxT(B\₤"hz6#ťQ3eDKgE>5Y.a܊򦻨{Y=o8|o99ޕ}oqNV4ǣ=4[D/p2zϭ-f$8ks2:]MRr:3g<PEb.9}- m1ڊ։#!,lڮի'*D?QocWcqlݓW7Iɿ)QFĉ(#;IK̦S_?1pW_4)@0!p:"C7c]_D,Gwx]w H1 ؎owї\P=I0?gJ7Ơ$m.ʋGbg#x\>ps@\7ed& 4&&< 灪Z5N{hSt=ϙ MmL|pm+bR";vl |7\&s)s˫-G;Jh} ͥW1[K*}:F3-}5's.9giS\*dʁkq|ه+?x.8|6.1("lK[jyMdcY `xF5#GQPڮf@#; QWS\dV ]!͟!(@ϸle6Զ뢺f6n H y|FMه$$>(s5v]7Tmd> z3ߡ ~!ln!0PU?tE-yJB>чR֡Q-jRTZOUVQDé4<黑HΤ\n:v!kmT!Q4qoހ_R+>zqBBͅEb|A;Y#"YyH,4~G{Q*|xfk![':hه?Ow" A =euh,e#юtAM>C%NKCfGVjRMnR-![Kv<" c_\C6lo]aegђ-Y,_߂+€[J휐xÀ,7"_EY?mY6m)i4u\"bt4'Gs͉Be3:md:~ Ղg/dAzzZ` V X&>sByD^GS.S O>&H5{i80f_41GOLc,G ژѠ ѠX-~gڷ}ݾ]|o{,1r9c nhG&wٻ\Ks#ePpG͸&Ѹ2?{`MW5 pF|X.->MQ2}7fiם?i}"LV:磕iI2ɖ=_8jfeY9Cͬe9$hV̅ U$%=R;_* <@pK5ӕ%mrFgi8zzWNRo:d)=ݢÅ`;^5y ]:\'w)pSFoRuFX/u3ZiC( j= qnD~_F bK_Y ૸TDq/D! B+JKЉy9}4\I?3@Fib/DU.-mn-TxՓfᏋ& h;&T6pE6=հӌol1왿أ9jgB;R.βPwێNqux)CCvC-/$75Ƒߩ?p}=moU#&55o\/'(@Zu΢|) и^tҺ:w-ېJ`n#;ZBi.k% d*-/jWF2UAAЕw!Rx(;+9ɇA(Qc5y073g9:+[8?i_VܾB^+!"wDӐgE9x@t5{ &gRm ܽ!,YkȒk:K/"#vc,JZPOٙ)ё::RGG},ړ*G\P(z#tu娵^cTlwUW)oyRN>7<^/$RrG/6͝Hl4jivyAL#t)4pakg7bYbvE~t&6{{)3f]9FH.O,XdqBHVU`_}D{6UhI`jbN4KHUJ{?˺<Öe2 ;ވE0ë̈ 5bшUoTGpQy㈹۩DWۛ.7,bK&9smK>ȮO]3Vu۵P]{S/Ȗ|OqmOY讀3YS˶fB8cE&svVM+U? Ԫ钕kK$$u1~0萧ekBW&"tsh{C26r[Oζߜg)~pj'u:#jO8ʪ^5,YUؒt~nO'PdmChhx˔MERܐ㬮$A>$ԣ9Ғ([k9w&G)88E?<]GWiܑDN{\HNGif}'-BpjC~Vz g]Tn}̗nMQWFHzs|^#rLY=P/CX'*,ujUe4̷GA2ċrN[3q<{@(h^62.'vkv QSM2{ d.8w X:~Rqr;Q7Q9ߜ换v&%WԩW T.m4J#'ƣYYT*{-"R8 N,AЯۣ=Zag¯8/b5Mj)>!wpK&F".2ܮr,t!.0\<:;:.$ߜj$Z/x'aŐ̑9lΕy9:oE DwԷ7)JJ}RYw;ȁl-I.~HggK 0.]5W" o{;O7,:ca !BWuQ?/([%ٌWvٴMlZfߣbV=XUvC. 0 =g+82nѦ६L̃RXE\ U!Ѩ_ :r `$-hFfN%jp2%Tmj\.X }b4ESy<A%<HՒ >::t֪N-"DCJE~iBo)z/+'?r~ҟ_LY]ey5k*BY 8*[hTs j` +DPN> ^U%rWYơ9^^MO8ʤj"4^_Ɣ$I@/"Oeޭ@UiwŖW|4KL+kPxVZɇ;% rd Z)BӞ8kbu2Hv%8X"/i#APj ;ӿJ4ްzۗUX-_{AM6$Q}cE/}n@D'TLK6'XoH6CjI޺35~8hϥ.l.n34 #??|Ԃ&IfצntѨ#t ĚO.I<7&/Ho~Ŝ3f`AY9\V;zqJىlJ \yjgP83P?5 )nvep8A'=.oN5ӟ ^3o[, c+TW<A%L)/;gaB`*|G be1lƓ2])p=RT3˞3UVpjE&V='SDS ,aϿ(Q]t*sj'蓒Ɠ斅V܇t+U !wOc쯛ꏰ_O{N=цlj2)|rFYYӔ@/J~ǐe'~8.qf:)5t!y3񿁩*@V ܸ)G8K l-{_|;NJl~I0fr1!̇kl} SHĥ Nif-E͙-fRR'ʨtw}wfT^b-0"+vCyDiIZͿqZ B#Ǟ(-l1X6Ά?|xrc =q9uCיǴ%*Ό2ধ&a #,0.21Gm3 A6#F?qDTHUSWo uи[h^Yu$ H0ym!}c\eʷ9^˲;^d9t#QɚWJėٱ:Ix#}A0%ʢ*qG`kˁt9f|dF[x/;w ;u`' o}p"BLFȷs(Ǘ =(>ĢI:exlUKdDz^LlnhV8rO`cLe2$8U ޢO-z;z-1wp!rT%sr5?~-΀؁]~yT!$gSd%]HCċuGosډ?PTjP6^xCJNH6{뻨R`:Vp!Q?ghv`/Tbg?0*zGM&&bsm T,b@`EՂ Ru2YV'1$W 0w GJƔ= :jbtxO)bgm嗹wli k@x_PIb#q^,>T\즺ߋ؂>E|^Ep>#3* O|Z?A:3&mњS!A'6$a!{/k"Oūۣl7ˋŘO#߿cr1wIɶ\|]M掾i"}10"kO-K8Б/kmf-n߮L )";K,G 5l87ڮ!}g"U=d7zQ;ڡnGۮdҡ$]^hqeZKDisL^0RZᩂEk{$PeA% 0.K"؏xԶ,u&_]ia|u:LAٚ 1(8rTIk(Ӭ(s[o[eR5YsU8$+u|i%/f'pY&CM4g<.wpކf7/[H;&uՋ;[ [2'`C\6B~G'{Y!y})Pz)#(Ոq? {2!/IkiG%a.WW"*0[`.vLԬގr=w|nv B鈛U},0d$K7zW,YE ONb!f) O.ƽIOp{k-i} UA3Zw k\ҨNg;]n}9UY?zzGrst8d :0tK/0=PC'(+Jz^QIb+BkEU ;yHn5_[d3OHgQ*XFipcZ1v/i5oZW<ŕ4?,\8{vop8<ĽYo.Ixq@Js2}(;0o;M2?$vE>V|d@=3R:qjS+Eњ"˘Fe|OЁ;v/Tib8?2[dxMp[3sշZʍQe]74Y ,n_KtKb|q[*)(}߹dU{A|^wU'c^5ȹ_lÁ NϬ˻/hc2vPѹINLJ;oSl҉ʃމCþ.y{fxS2zgӔ />l {4 5. >AV{kocyr<#:LstٱӹBC$RQpKnjz٤>G12:U[uGu|>$+Ss'ea%GJ}\ b'$%_KpOWjSOo|#Dz2o^YB(7dY4SE,t=llYfhfWV`'ImjͼDXhyWi<>^r(֐ysmeϞx7$GJ/eŒѥ^G t`#kjPt2J~:~\^]G<9 ]ǐƳEٛX!U/T vN+PK]Wԣ` Wv`'NT&kiGyǕXi~j.ˡ/*KyӈZJѓ%%~!dq?EqՇZpb2VTRיiċܕuwQ=H;]r܇phIv=ӇYh#an+k|n$lHwoi= NGMȱ!yqhx\rǬZ,ЌG?"/0)I4!.l/YSϟ-]- vTƵI7޲N]x l qd!nC69u`|KԭTS6U&G&G&GoY0I>͏ gTYn>}ǚ݀#S"}EƠ?҃]`~./qxr ڮYH%N1ʼnDy)zaݻn[-a[ ~ω=a},W1GG^>irl45TAG^ kZ\K`Dm'dVZjc,s(ғ.LĜWܰ6wƁ=X)_޸'8v?5+wjeKggRiX!YUOu{ .EwRlb΁j S<~~LLbAbՕtswND_QTȰI^zueЧ!>"qO!$+&f[&(8(fMk!2ƮkvyO$ @5S"+]'gѩ`nմƑ l0ڕ5W3Ɔ7x" q.Ѕ9B JU+V[7voBNJEY!dRI-ůpg1v8GD=8+47̠Mji .9&k E!kŰI|CD%H`/WmykgBp,_,"7^6Y8Oj)=&=FYifYf^b=>o\rᦷ.í=g3vY]:h%Rb7}6Eq&\X}."%uOwPD`׭2",.J4E?mR\Dx0<~e 6J}?K{lIVUvx y Q8:.ʼnKzhlTyGB̎X)WD!(VGLX%Yyum8ԡHO,:bJc"xzD 6_[ٯD *kji8-D|Y|['U64bbb!@ u ` n8x1-3,0T̄veq0]cưQHQ堘dq hme9߬ BP^veJy: mؔCQ,r[۠8#Ĺu0z8 "n1ߓOaj't8)|rFnG1)q&~'޿:ވ|D87O2:h *]nWOkiٙ'&Lpfs, '-{_|<[D'kn7;܇[>`zJ@-y]$/S;_טg_cm)kP5arOm 3txyujX*^e?[4+ KSzݫ G6&#"N߯٬l{*̾2jx{^lAKVl0ګ-̨u~Ԓ^h :OTڡ{5iړyՊVϛ]H,$VF+,Fۘ( &&{\q^[ZjJ`8|YI:MۛӶC&47IGL'xtv$$s2t"7?Z mE)B,cB =W#r< 1#0 FrJr5q[JKM,9TXxjǜ}uS7Zy ^h:Bv 7 dQJtuaDp,^|=@1{XI![(C.9-`𗆬҆W*B,a+Fmʵ ~J#.?c+!mohKN]AvUKLgK\b?PsYje8ÅK>Ӱ~0=8"Jv# 2ʶ4Ub1LMR (,G\ѳI%GOp5t%c oqdiSg_]]H=9R2#Ej KwaPLH^`JcV݁&D%ɷЬ% 6dr/2Tl3Evfe&Rrg*6ik*lHCۓȷ9ԀRs. 9oFO> \b{+NHΎh;U@ G2d$ g-@ ;ՂR4NE> ~~q$8|Qt> i+3s" fUA;{EϒϚ筗sys^EIZL8jT.(l0L9€m'H@'"eb pGKp,م,RK"F5`\kҹ^ЀՍyNxF8/4$LS>:欈VTG~& bJfB,:CPJS/:ʒGk4}6[TB~'}'MlS0(6PîgdB-P.1O8e,wzS^u)VG;(zrO?=S8:S8#|f+|K=Bz0tZai{̰'嚹bO {Ig [ ?I[T?\0.$plbVn d;e7Lz21dt5MqˆLOrÔ՘$-_'qFԢsPf[I1};7p>*=o"{PyKa :e8,qM3tګ~ۿ8"y,3 3H`/;2ŢKpz%؛"g *&~E:ql6d&`4JHHM(*mPC;-tJZ}S9"t q<p/"6N*-X}lG:$OPo-'m}]jIK] l䘝[F/`ei. J|!E ! jE ĐNBu9>GJ+]0 Z.6A-]1¤$RhP$5 ֐Awc(%; 4& fqþ ؛@ys7uaY)\@ [ecgAϒ/v1{|G; gXqX15bnsOV~/k" `oRy /iR5K0Nsf򷺾oup< 0lf =AxoΟL(6'04tRc CɦP.}|w"\U^}Й{kW' nˏfJ ra)G'\@- 6Kr~LX8;olVK3Q=@j*0 eGJI,|%N"3:cs_©r$gq-_iyN$ؑ>0 g A:N`Dwq̖*1p.O$xp$B/mEYv?"*sJÒ̰'*"pDq ,hGoaB'9d 3wM+]ma)e5{=p~OP8 DG>.ft&Omw\|A^ĈFONX*ʈ'f#P&80v4 ?Z,Y9ً_rYDw&/{giz肃_=ic0V@+*H0yKÌ[2Xv A@/@mԖϮΠ *؇@rֵvW?I+bA|%TbN9Bigɮw_ ^C30 <_tD%#(CW2~ n  v/| sEx/˴ֵC!Pl&?X/?Eg77K7R,MW6k.'s>ĕy?e,>VmR@AkCW}e jTٱs \?vܲkmRm>&-ɠwⱺׄ) Q ɸ!;;c~Cb#~h234HkK 0T͒ٳZTv%=n1m,̂qMVnKbyʯ~HaծTڮ1 z;霔[EF#ʰh;bV'ػb7ro,cT]*oAFR(;q4 Q3@*%ߍE TUөIDSdHrޣ=^Z_ɒ;M&eA;&E%?>z+/R>NVv(~wZ_#I)w3RG+4.\9Fڅv}H>?I]dGriFcơ#f `=-i|`-kPڈmnkUW@=<hyzMXYVX C @z~@\30BVaaP+!GmfB]GLYv8 kWF>XT U-)['#EQ45N{'_qb^QG+|[ewd}ΣxO ?1ڱ=?}A&֫|^=ُ\3&NMB2F߼w;!َjgNJݺբ֡}&˧{u刽c#ro1r #[zθwm's Z}K3XeIFEhdWa_JƔN~gf htX.PX=65`ive\HLP))kTª\H%~0V]iY襆?Wv%*Tu_J֡=h+=Ǔ9S~sMC%;>Mh~\csAP2%ʥL`"YCOwZRoo$7WP;*56>/竬cqc?5LXךP4d -uG,5(*W_]!Q|'1iXr-,*Ԃ /fYsd*we vӻ!r4}%a-Tk{?~IekskXֶ(.Z- ^Y>hƪ$Ko_pͤ}n&s3i_I0i04c xV u{ƨ}IfսΨ0j_F@ʨ]٨è}QFmX EQ >riO|Ӯn|Zu alag [>*Cv39rmr~L1#7ΐ0dW![2hMk +!Sq. fuV% ɐWD gP%"Wl WdR^5Ґ P^5j>QL2ɚ'P%9ٔ5(rwp"\Efx/f3 읯^cwZkYc d@d@d@d9Yzq vM2ypNkh2#t-=`5 L$!F3]t FC~!7Ea ۖz{qw0iMQ_H9)"w5Ikm9<\ȋm _C9ӭAV(ܕ|ra{P/u0fj(ħJBrRk5$o-+ ըG;_(NABk5`LrJk-@Ik(Fߟl)X DX符nmq;{̜4vߩ9}n>SujzsoO>o~=ͼg=[}z9Ovux40 l.Db':2(ZM[7]}E෭\8U[$$E. IwQP8uLhY Rr+ $b4x"ANjQ6NYEQ٤w.i=Q$B8!75qMCnjRPjh K NnaCcv#]h톻$rQj='p7l uJJŶm`NJd$X$$c5qZc5 LzF?&V)j+BBih'V$984^$V6MiCar(|<[$eLjՉٴ)(05fxBLYJdḷMspYǿ 0!.6tڒoځ{yxyy2'O&<|B'ӆЁ'o EYߚ}~dowf0>j8n?Ih5f|%qWpN`t8~@o[!ep^l[8]mu`d$ \6.9v)H6W$ -^7I$ԣZY'+ҏ _L.NWJq8U\,544) .xE:ceEJSq|Z<| m,J<w/A;Hp~..uoq;{܎:{2 ym@yYe&(VHJ\oއHHCG=Pu s۸/ 0Uٲsٸ'TMWF~Iъd$-jUlQ6nE"nV!hd(HC#PN[$QI6re 6li UINg2r~h",r})@^.P.ʘkˣeJ'dI)٧W+٧92m7q|&nmsmnmEMܶ&&x(u-J@Y:*Ra{:uﶆNP~h~=8lpn)n9ŭs'`Oj>n8PQ8Dž9{5oR)UIH6W ^TUHG;3@K>tt<.[$q 3Imnפ u9Yrv?N'y[۶cp671-m=0#x~Eyp9N_ p-=ҙЫ R$AR\A^=S}y%#1'#N0p[huD3pqxGJB2QG~vH:W!)OdRp5 #,S;e)N6rWg?g7YGkkho]2/wxNw|&vL=Vs0h,g[0NMSZwCr;ܐ7$w!#  zntyX^ϏowB{-kw%]o*T->^ŰoÑ i wGA+;Ўořd$ċ ^$$x5mFW$!^@DEG?38Ntt /W81?Ib(iw$"xeN xAJB~Sfx@b8qTƳ-`]>ru`(Xw59Jy3^]{LeuAaZnW? =vFW GwLnk`+nMkhh˧"4Z$$h5pµъD$G]7 jVi)j#G+$h4p"Ѯ8܃.)ʡ?ڣ^Z,4 ,|un%3}dAB 'f-lϦ=>6qiM{lfӞ(`ӿ-z`Uڧ4Ii]G=^,m[ճ``'CZ==<؎B^lk' ESoqKc%!z˃ u$$y+R fHB2Uq w'Co4"Ɇޞ1!* fR"|NȜ8-JsD٫\(~i4)bs6YF*JycT9}nsspN2-qyuL\wCZ:}+ 9֯~?^'~k/µi _L\8Ey} dԼ L\D \Lэ1(t&FAR" djazpU()P\,(; (Hqo٭qg MM #7lq2ҍɸ=PL%,} )^,Jr(Ei{:0sҖ"7A1'>vL=׿fhm^Rs5=.:;;:6uFZcu[:lOs)H~;n '@ ^ɣľtɿC Q$$wD4qrӅ'IH/Hv2*ќbSq+B܊P6J!3V:}hԀjܥRAE_@Ӽ Ѵ<#_X^Fitwxo2d9)R-z-k.[l~ Ë\z]]L.cH=Z%ytG|TsSD%={JxII:͒${YZ|g˜d\(|mcs~{[~l,n`1Q8"I, )b&`MpBk\_쩱VLk}G701J66iDy6#mBJH :4iލn  CߒQG("=c?}2,de:ERoa<+ \4LȦzEY / +L$g5˓ICQnyƌ*PZ$e )YeQ92z]4w(0@ f8+Sfd:trvv}q߼\W},Y{<[=N(.+{ L4!)ΫQB緸& jG? nq6bSф.)<GO}]cкݐ9%=T/<%ʼI~Ϫ҆`OQA+jaL%kINi#J)84O%,ED<Ĭe}3\e-˦a0[H!8MU.4< :u:c "ߡ{:d̘]y3f, ;*"HUt<p$I0HE5 :2̗ -`GBЭ"nU+R+jёini /#d`K#:Ӎf nͿ9Av"OUqM=^6jyؾX^HP\m~2vl㷚);:9IֹNwFσx|V!߫{sF-JT;z)zq&CJa; Yvld=F/|d&{G?d 7v^c?`z1pq~z짞x?;r(cze6D`? L<&Ij{Fu'vWh5޵FԴ}v=NVJZD ?b76t8W2oZٻ3TV22f<| ˯N E84ܐE xqoxu QIkwסc}7<͎3LskҳCk;&>ʖ|ˁv`Pqwў(VZahMi}1j}U"Ō4o WqM‡r *_ȃ}ؿ~9=?S6>_`[]O=)@-9;Á, OL}7w$f{z"P=iiX…lL® KmB]p0&B|k4+Vݽ ˈ}q.Gx~I$[,tK{^ַ1}=a~:vDdi,N>͖G7c/,K ~S4,>]b`E r~@nr- +rߕwγ9N+g7kNqmFc cg[~GS^O*XU4>CǮsڃqȑsDv GmZld9ISd~9ȧ(Ui)p WO2j_)rߓm[}ГgwCrO\pbWqTqTΎ^ A< Ym&^}KpfZ4z}yxRB{q8VsůR]}Q1ޥ^}RE\G'eE~m@ipe$/i_,U~|)JiB"yz>{Od}qA@JhUxH89S q-;kQ,(O ?A* ]&c/ X덪G"%uN8v}NgD3A7ZӜTOm..QL4LBiQ̙42G9YWE5tDWnBTs,AN7tp'oXaǝb?ٵ[xc+ 5^x0o ?u_WiމO ?;i]Wx'>M:+ ħIGt^*MBoj<wM=A>z|;9SOOp L A>z2ROxu40Rz.O HzH=!2Txk>hmM=!U WrB'v֩'VXR n26zBpludҤ#Opmrφ9tTi y , 4鈮+è&9KY3LCtv]υ?4q5ba`*3xvqZeHĢJQEUUbZf}&1]\RbqZ}Z]|b|V,ٶ]ԫ[CF죬_b0R/l-벸p;+Ք g=<_c8%NnvѮ Ek};>K +r atVU8ʽ8(|/mW$~|K˕l {sDgX0a`D 53Gc>D}01G;|177GJʝMy*# Rٺ` >5`\XWqGv,9nj%} B-Іec$K۸>u^|Jd<}S0đAy:ZD~ndI(v!Ie82q{ Ɉw (\8d*&:)NƤLOGM p,zʍ!9iJ6G3 >kgkw?X}4xv_GbĄe4 SF2R G+Nl@'M}J^P*Pd \ #?y@"d:=L"DP(]]y]i #cY}-[EfGЏq@?;4j'ZL8b2F8ܰ6ȊrItJdc|sF,¡K]j 3)@GJBvS otXB1"ʭ4\*Br 5D/,mBpFGH^Bީ k k[}m"tH0$4j;1#eky`GEba"`(Dkh`k'8,R!)5:d&qwss&*h &7S9nl~̅T&5f/t]&bU0Ds4uLl~0L ӵ7E&L9AH`% z7 9c:TW(- c 1 -OҡLuwsܷr&!wx_]1E|p +sλ_nAe*=`2{^kY"2I4 np/V~qS{3_Ďo=G]bvPfoƽEKlX߾Fvː ?a$#:5JdB;oj8I&2L@1i ʆո1^cy`%iܷVqz{l6`L -IHIä$; Gkɺ:IFa<ߕؖpJs)fNej9ӣ\.yI!z¸[4 TU͜ZD2u"ubDV(zdH'6P+y(̜1UŞT+?L&hV&÷&հÔ@ &mEA0=REQTO2{_4]>/Q)Zz ~G^L.ꇻ?NNF0*%85/ /ѥ-uxe*KnllR,9cBÇ=M( (LB3w]^¥ T·:+yNt`D^pnpp.[I-1џsp .fhMZYpp*F+#`>j!C౒K(֕}x[e~\芫MqrjC@Hm`e60Dz`bDeX# K MWNNioNc6A.mFO3,m(B߁v:Cە~'U\R)d٫2՜}hW&!*ٛ\.ּ5mu5Q| ,u Fڱ>h_b)?góp U 7ۧ|#5̙ڰv0t 1Z퇡%rò\̜JI/^tjdֽ"&iu/^DXNud|z)=wkjXN~bZk:3%tW=j|K ۰mX6l`7$PKMod>PKFN'-Pictures/10000000000001F5000001F51F450338.pngPNG  IHDR`*F pHYs  tIME  4p"h IDATxYuqCf}&i-yY | +˴hh$333"|OUöoFFfN/At}t:NNt}t:NNt}t:Nt:Nt:Nt:];Nt:];Nt:];Ntw:N{tw:N{tw:N{t}t:NNt}t:NNt}t:NNt:Nt:Nt:Nt:];Nt:];Nt:];N{tw:N{tw:N{t}t:NNt}t:NNt}t:NNt:Nt:Nt:Nt:];Nt:];Nt:];N{tw:N{tw:N{tw:NNt}t:NNt}t:NNt}t:Nt:Nt:Nt:];Nt:];Nt:];N87/~"n:WWOQs/O/O W  ?cD"DB@$"BWo0""R!Zj-` _oߠNm?@XG?ڧ[я~ODK # 14X332q-"1!3!221) !z׈`)Rp?<J)/_*OϨ>Qp[?𢅮}_Es[:[ V5[CcBzLo 0*4ni}}#\k4~vY9%eWo =FO0]4ڳS?hG@?Ut&55mⵆHOd ֻK$ɊZ|[ƿ)Ycj*qMZ:¥lx*"RŧYK=ړPКc 2aq̡I;""&O$ZUTvZs"%/&́ B`bDD5 f3zߗRj|8x_|aOا؇~x w&W䰉$>F.1)5sIuYܴ_y^_8uVKO<]E|{IFLrf ?u \YfFD"/|IVuDDk `^Ĵ_ xsž0t]y~o)61v'|۳ "LT*fѺ*R)##2"e_ 1`hjf tj?aԲ_~]:w?_={fkl R^jwMi.lQ?;Lz+c`-4kjOJz|O\o\1Li jd0=sUCko-OI\?4$^<_s<jy!w FC˿S۩ep);>3li_>z<,"lwfzenwh>y'Ԗ M_1**f&b""b5*RK310!"!Q-ED֗  D UonnongW~KX?E.0 [%DBU)9ĚSes@"0E'">&7HݯZJkC<DF$$50QQU3m;_**Wz& bSDnI#Dj,1^їJZݴ 0@XEP[A.U֖S0SG\MלQKįrMz4nnY0֚V?=Q;fuqI3<RUDYqeysoy)5QS4`fVJ!@DL9FU[Z 0y*Ef۷owfw?};~K_bϩGO^8 &@`HJ) DD*xl0;8x8*C@BD1%]ԦecV! ^2(&Z UZgqE]B5oM? *@k&w[+E~Ke))*kݴMkњMRS(%44YSej7%ո@O |GӣfZȟ>^/[K(V7]d.jTuZ=n"rɵe^K!?){c%f4\"EԪ 0)gkpz<9x|8G?o?җs`x>m1H]Dc J\Dqz4Mo޼:Oݗ]}nf16яlfG7kʣmZll'[5Rl& ZݞgߚwY_=V\R,eChVx՗Keu@1\XxNy8%/( Os3^tՁ [S`k>-n9B[_(xuƳ3|y ڂVRkUZ<5]jUCZJY2"r#"#$H"*Puéddf& ЮE$Ą`r>?{_|l:?gP"3!1/|>(R1J.fl63}|^rl6JaB`<ͦxˌD]`mO881&)!S_\}:4t+OJsTHUD 斾U8ݡG|9w(XJšKmJkZ~-L^9=yPk5pK1 GL `:=<ܼ.^!1 "1Dlj@L)Ĕ7M1~G?&Nih8- i vߏۭ,2/K; ,ĸ~^W^n}sCDB)J4yմ w14lwW& !$ "˼RxUhmo@7Ϭ7ݵ0f $]|=))z,cү;{2U%" !>o92#S0ZoAV-KZdS[M. ˓NǡCѧ9c]s 8uՌ-N.%۵h9"yYJRKιZK1{yV ]0P%1@њ 3#zQ &S RK$>sP05ws DTդV5LDQZ18 q >/| ]z4HQ6mQjy^eqs)ww8aP5@eϭD&Rr^T"4Zk-JED2&Be9a]Zw"Xb = L(j˲40|OK_%V3Q0R}8ǢE)<b@[GSC?kRѥGFb@4lnVz596*8&1)aS 1jz}sb~q:|>9%/ݺk!ȓ8^^uwcL%~~fS_]he5v#$ * LH00aHM)|5.KRr\z`G2@& MSׯ^=?1F=EfwmavWaSs9MX0n4i)KS6%bPêH"qie"5ƴ[a$gc0#Fo%/ xwKAݫzս)"vW04yI=a6jaкAa'`;' ZgiǏ11UZ ju1Ŕq!0vmrι\291CDTz^ !R!f;e:-K8MSv3`jyYN!)mrEUqO$ LֺI LD0 I|l*0hV]B.OԬuț?FDvn!yWLqjߪi3`7CA5a>OO@V|33@ bdD+>r-M9-IEO2M!fIe.2mɹxʍx#dQ@t{L58U$/KYjy !0 iݕRrUSEjr1 **ECcDՋ""d$ L?K]~6`5 a]]xL}.2v;YEh DXR4\,r>OoWCJө|q1y0a8RrKCNqܸ16SsbfAx_SP|:-9@BE5%"#E$\< Q6uW1Uj-K>8-bR!{?DfҒG>lc.͙>gN'# UǮ." HOv.֖N]}Fy#MMoaHqfw/Ui_]NǻK-&ETJ*kWZ~@CoQJ= ӲȒci6K^H8`X ' LEkAYͪb?!7k_g.PX֪w}ZE_7 Hd"ffUJǀDBQɵSYrL(|RkYTԔ90Ĕ.ƑCpݠKa>CyYTvv"sd5Z ̘Mfp8O)qLi^2ϹW6Gֲy^y`H}ZP?qoMmY ff5KB~ IDATDGSaH1ƔmJ!mԬV9Z=ѫZKi>ڨ5 c*jh*#_B`DYħ{XX˯Zs_μ35`" w#-B&R i DsN1]_m4"Utε,˒˷o^7 j1H-TX L̈9 ץW0)fRk!)n7ZK*h`H)P0}y5KY&$N)jA@QĈRŤπt:0׾UW}Z-( WRnܾMsz?%.O? hf9fbKQSer "\tcaB /˲hMQEi*Hfmc"d+5h;Mj]DlR˲dfbyEsQ{2T`Zr.EV1D)ZkPDk>ADlfZ3"j \nmyX\@ǀp:'@7k%J~BP03!7o&f"i Ԕ, a!0$yy>Ϸ^ʥZ_xaYDb04k%/\_RON!0ÖLLE8uHBB3*.V !fZ͸]{pS"T@ߨAusLiwWvR{ϟkˆBc,7ont>mCJ\5sLlMB4:&BsR[R^ZyRR6>O7oeSJ1%%eYj-R@SLi7nAFbzϏp4眗2bTa=͠V0nvYf{:`frjZ,c !2U|.jgr DNb,"|壏?z~w~Kd}~'#.rOOT5ΓZfC>bj-~L 1,f&1gDyTߎ0 @U5Ij(D 2X!_;碈zf"Rry<> |zʥFmvlH]L&t<'?1w eM~3|_. ~GШ8!h!P-Zh"Z*|,b>MЧ"FDfCJRrxw;/30pv!3%#ԒEjdRJ@ !Bd*jc81C1R] d\#"WvQ +b1bT2@ xr %WS#nڏßW\f4L9h 14r<=<&̔61vq3R@UKY lȴ7\{ 2REr$KP|NtEj'?f﯆qųi^C}(1)fѧ(Z%??@ڌj7qft}} en:O,#0srX%j~RћɁmYqЌ3<-&}!Z)R*w+"院Vr_lb_P]90z~jcrURRLLc <ͧ1RHS|.UPbFvog!R)aH!wc/i:t: `j8r`ST}4#5ZQMEE̤fS Ј WbRTĊjCT5e)fLPC4 Ø4 %B[BTɚpxv}uzxw<||ᇥH)s{6vlf4xx8yNӰv~7nǔr|EL (r܌\ r("Rhm۵6hP+fC0%/9׀Fݾ}#ZScJ>0,Rk)HSi CRRkۻ(jl8!;/.|yj)U@(0>;;-13js!֒b)KɹJEk.5l8R1nc0Si-Ĝ€j>xޛek_{֏hu8Pk@[%vR˲Rt>\ Ќ̌X%`$[l&YQ@d@ bdb"RI>\V8nX$S`jYr)RDq܌øc ?{bb5ɹOE& @PTM}8 ơZURyV!BC3&Lð&@6-TKީ*jRALH1 /^}i2g*ͫ˼ ͳ^<{b}+Z5ג"y)H\KE*= Ln3O"e5@䃚K5|14*Ry" 44l7H*K1H<Ā"p890Ɛ lHiz{}3pw7/HkEk"!3oƱVε·H1%*'<pg&qZMC" Hcu3Ԛ{CJd jbL l׿'廾[W#oL6s0"wf@RkιbF~γZ5S$!M-"sƀ~&Hbe9:91[JD@XJ]JYBRrRjEh%b K9!%??r)ȬHT+1˲L2-s R71Ƙ$Z|>V 3Mf51Cճg΋T5o|W,K.Uİn|ϟ߾OvW_xλ7ϯn!۷i)%e˜ UclI0ZK-fbV ^84*@@M0"N0n@H^2 6s!D2s 29fx]o CLL6 8@q'׾''_~ww}oo~[߾l徝NiVEMHc|?1I[MgĘ;U*!90!M7hS)m0nvo.U4M*"4RJCHC6fH L,*#s! iF`T-u!ĄH}G$1R˴Rڙ8|^RJ6@!ƐBH)]]on0y9i XE5^ܼ|/f^4M}[$ WW~y/^7׷twwvR]yw}݌pww4/4,UaWW{@VRԪժ*/}oHZ.xFXb`~b1ŐIH#I4eY!K^e 9$!}f%u"Kί^䓏anM-x>O糪RtB@@A\%38RypᶱfأxU-QgNTkDD,i |^C5EĀ ů}k_׾/^Юuh`R旸VjDL~8v{n;G"` 1Ƌ7ܐ*`m9_Ĉf6- 1m6͜K)eg$f3"Q$N8C32O)i$na;nCRK^JĜD%h;" TUy^2 C v2WF0pdanƛv3y^ׇx>{oUJUCDBJan/^x/{4׷/_/K^ |'o<CH)o}_1px8Lr>Myy8ðﷻr9缔d[^ݼ[Q|IU0 VSY⌄{ X8p1(jRrx3JιfO@10R#'Orȅ[o)i@ PjRc{^hS*j-6@fBѲ:Y׿Ղۮ]3Hh.04zEU(%_9r] H!21O)!=AifH>?.1"c!D"RR pussus=nJx<i*,UnHJ)rPt>2WWP )<ϵBhj,K.9ƈr-*Epݘ6K Lca!"4E7 s&pA9Bbϟ_?읗/^<7o߼RwCLdϞ\߼?{˲s {3YYn-$TCEʚh*Ҁh٢b Ý9{ֹQ d*r@T9k[[ۿ~>|uZ3k{nauA/,ek˺VuB`^ZMZ&jfK nL116A`B)D\+>y ZCL8Y[k]ę9,˲,̩+%T."r*^|Q2HzZׅsRRvQQi aF\Q"x}YgN/0}0i2/kնh`bB& Ln ,HWhl" p"3Gw;/l]WsW3⍋|Rd`4s|Ck]ZH,9ĬE=hR'o$"UVff*TAM)aM~v"t:eg̜RTRL.Kmrx:?=AT|ѶfC"i̞<:Ib@h,6!(4mMM̕8QJ[QPP113q$yS)}7~~sAaU#ب,fɚ:^ IDAT8N<#nPzY%_o*2/sDNltLL4a܍8!'bFYe]mDFQ=:RrVy*\AQ D$ސB.%z^dI)P@!棣FxǾ'F!˺ˊdphMZSN좢 EZM=fKV}Υb81ZSgfc݂UŕS4Uf`[*pm 3y|IC.Sݞ؀=4AĘ?'||}}_#pZq`2֨: ^ݖ`qUZUu DN5?nm)anx:e^JD}Wit8T&ʹpJH=ҝ( r5$HU55Nơ$&NL9EGDsU3B [WĴ+ "3\Jv Cի/_O3i7/rkkJRnvn?wּ97Q|Mqȩ˺,`ڮIQN뺒KKW]uY#xʈ2ͮp"B DI[sT L/ `50@BdPC^nZSJqC i6Qw ŹoI&(O%YN̖!2aMU!enҢYfqgRJ(Sו9CjkUVUZmH AS)àډEj=W3RD{UݢXRLTf?O>G˹t^vɬ;Y]OWw#&p4b0؈i[*iRJ~8x8 onovn*]b2ϗ|:O}CߏCΥ.K_hnpJqmN!~HRB0lXNxRW9s&X"-FJ9`kvۗwwwww˼<矻ݫ׷/^k\䎺DZoq*X4Č7.%?~.*՚;n[lPŅ('p+]᳉^m3n1Ĥ TĔ!1)ǰMMAc9rh96לܭ{,֘1qvD΁9D`F sBBb0pjXdt}t3]Z Ϙx_麮˲s㘥6sKPk f&u]OqKL)`ӟ4Tk?Wb`*:YN D[f)F"،x% "3tc?41Npx|:}zz}u;M4Nm2N8Dq*@;c|Zi%]j^'fjH8q›ʜ6/*ZJə3f@3u=}}bެFo UojwĄ)!#͋[FRѦ "EZH`qj)DR9kFBJ놡}* Oewo?>Rq̥5Ȕr9o~:|kkk|>LՔ"t2hqN)EEDD`,:'9712$Pݴ{ՋۮUݻo߮f^t07oޞO&{ywܾKƵxQ\[#hZ;Uu]X9Uk1J؟!h?wg6RL~=b̄FM9ql6> 2_  #u{w})98ϗwo)w 宛vHS:*rssD/no_}A).:|:Ou]L(ȕis(T-9Ĝbb[mj9qJ)86Rr 6CKի׿(3~駟.ksı~kD$~틻~ښR%Υۛ8t9$4jk1r cG-9 EN/>uY}^}}|^.|>g`*JkL L 7aG-]"WdD@F4TD#El5]"&T2\Մ@4,Ҥ?oWob&}]wTQH1PDeǷoN݇?RNi'Wi -5rʾb3%D fl1!BEt]q%LD+vxz ebO>?o}Z[߷6 LULꎄ%ha)/qC$NA C;# t]wBߗiv4Ru:Ox8ͼyǡO|yinonnonw<*iiUDhX/K]87U:97o)i."m[.jm'-;WýCoih &p00btp F- T 2[#" A 5~CHLHDjb3%A%DBGg"Ctd*D fla~j&Bۜ .ajͪԖJMygfJTb&uʋwݸ;wWߗW|e_ naL4q-JIZmI2qt񸖜b[„Ąpdbl T a #UF>eۏ?ǟ77ß˩2 b[sYH~ ,nd*uLӴM)k]e>O)aa,5C?#pZ9N@ikC\hNw)113D) fN9gD +])EơĴ/>Ͼ/SyqTuZ1ݽO}?r>ں.m][DCDirsٰtśO߼ûσ?ƮeU4EF9m1P9 0@ *¶(DЦskW:8%.HfƢȈ۲ 1Ĭ^+mʘ;y6DDYZmr!Cx9"/3 a茱EBQ()ܕN.+02Zkb@@nnn~>HyY.Ͼw/_{ۂ̈Su1aoպ˲9&L2%25ij[JgTH&9qvr5UFƱ HD?)O}]efm趉ᷩ9:T-_?6=}L|1՛؏c?ps黮+ۊ " 0MS0cdLT\j]u~? 8N''n1MZFD)'Su5F5ĉM q*]NH53wH̥+)3e9/O3!N^y^/,׶^IUj]k]WsTX)nTJ__<ҵy $[ۊȢfn)v$KDBDioPnWȡgb`tC ]~8F4Q5R 0YRR3$P@e1qוښ%R'QkU7n)p>/ב56)M9*,Wg7p":!ai庮rwMNTU66iݬꪥbڞqno_K䔸54,>>xDp91R)q%} 9w!^j3u]^waa$JhJ쮱ZWH+pQ"R6pBivNsL`t8<8α'{qۗr||2uFu]HKi&yֶ[QaJ;{糬_~矿}rYZS5{ԤzM`fq ^#f)13eȆFmhsU37hؙS.\TT\7HL4КM,匤DDd@bJD U6=[-1m#R& 8Y,c)$:u0"Ro.e feg;yU)E6JkHXǾie^/zrp ȼ"}Jlfy>/_|nooi?PM.pnoo[k|޹5 i1;PpJ)q3qLI o~oOޏk~iw5=l ^Y*C_ɰ?[t`1p/^(޾{ATﺒ 3S;8niu]J h]W3EP"'-}vtcΝ6wp4wVZ5T]y^e~|9U9Rb΄]5>)n؈i@)egY]I;N}/px:ֵș crfEAR*J)Ln˹.gA833\53Ba] ͈n),*I* Ϊ(avp f,#' RUQL'K9_γ#wDhj@ ˜% 8&%#2i_4VN+g ݼ,"U15W)#PU:P#κ]њJVWwp8,:/Қ b)̌CĪB@N\R8DZmᡵ.B 8aBpUzfh  0X.V(0z XAQ@-<(nps[*fꎆHiwP3&(i ~ ʀk.G<j 䄄)Ie^~5q_uYek3##"S ʜeSqq쫴ȗo[>R5@=g"&9ֺ|y^7MXz75-uiW YJ,y&t6y/l*я>?~~}\߃n|57揈'vWAb<(@>U%6{7y7OOӥR H+wSp4%$m:ddOI\SrY^.j]̴VTHaNRhbr"j pEQZm>e,rYK[ښ!N3"ëȎn VQk(r_iݿyr *z>]:/.Myon Bi{N4UEGp`HTteݲ\tQĀ4qJM6ab5P:TG7D@G1#w}^3n;xT9 %6r!bsF].V@P)Hm\ ,Į&b掮@ħȘ/39:D,[kh1yN)<_s62C+0tHfr<Syvau]{w/]J.LR8j;Nr:a=u7Mc֐—TM%v]w;dm IDATK}-aD6/h)l!-rn!#ywey%eJyTtZy>6ʰ|:p/3Hc1Up ' #']c45g$x/!FШʞ,y˺u]i)qI99LۗIU YEZ5Jyܽz/v֚r8ӛLJϾ|xx:tĉЕw)31BbLND.r:/tZ2#{TB@d05BP$slΰu2K!L@PIԐԓX:"hrRB*. ?1:&dK I"f@ĈT3GML#KD+4¦$Hr$wtrWD΋ff?S"2%͠VmMι7yiatyWE8P2a0q)㲜/ڤF4ƶI\Tbb=v$eh w0iG?/}W7I;9P՘q3:;2&@s.Vk70/r!J7SR4}gf~O\[;CmMUBnSb„)@)UڪrōՌy[2[m`]]Jq~C攑/]cn^155|{M<1r#Rmn?r>_Zxx|z/?/޼ywTv0NDALojjK9¬Z% DvZ\L \UY )1'v ~(}c.ԫc2's,xUs.]MRU6b6WB&B'O96sV72pJ9R@dsutRf3@@4 AA ML\9$"Jp]*gNLϝ)]ua8_fw;uaCR:Dk]̑RJ؋OȥGJ}f[JqWiYD$2"J^d K+; " $f[G݀1/SŇ%PtJL?aޟ2{,̆+0"1;%W=41y# P4a2w`N,7tp8(C9Y~Sx<Ot^2c/pO̹rċ&DY̼̜ DDZ(&D.n>ᜉ9%& W#b.u+ 4&,u"8+S8j9Zk]ei7TjfZWipx< nnn?xqwCrRkК4i*V;恸/K)9eOu]j:rDq [t |Db)M%`fK1WbӚeFr:ٕ@4s J<*IA$jt)gNm)kJ.@)$H4RR}zxi b&`D3e{FpBXk[)>8ںD[3u}9aYZaK)Cu3]w<ϋOR1\ Cm!f9 1jaOFw_|?}Mv)x=c6Wb+U kiöfTs nF)Sm@*t}^OC׹|:q;v0IḀ~if)%Q]Okc"R&-H؃ZL_ǿ+o|{>2]>s{ x|М>{Nt8>g@<`@8c`'fJ 023k׭}nΥX4͕KIv>?0Ή0Ј7B2KܴkJk),<}om秗m[= .pGV`]ݘš H4!nۖ[CG5Sa`n IrܫgRLuw'o#j (H-`n 3@٣PqAbsha_Or:4Nrz3B ui<>,2ޛc }D[afe:qvL:7f9s{oǗӶOdzω[w8¼{k-ԓɓfNga.fX(3^r{b&~W__LR 2˩\nU> n w@HƁ;!/(.^#c 7 FK>8DrI1#Zjy/@Hr]r9EP+ I" BDfaF."B$")(nƸ6 n͑DSpa05N|+Ff׭m |<rܮ7@\ 0ϵV"0pGfҀbBz>>?\_#":# u5 DLSJ<2}K1u9!:5҄f*2I] {oDlB$C=2qe1A\%oݕ $,4ͥLr>NKƥ:!yx3t<<\Ng[!ka@""mz=Wk DLgjmc,C[Ӏn~:O39-K~Ae䑹YZ%LO#bcfs7mD,˲3f6TVf˿??_o~__:8!I NQG״ QQ̐!LZuH̭M VjaZK! .]m۶mw&D|ye 9@fd\s#^䒱jF$z7Ֆr@ftK F092avEm}8uȴ˛Ƿo/7o{\@D2͋Je.@ĐC!b @3kG`󜌻ȿ]_^P,$e4.Q"\S\TNafnm:kK֩ISv[{>EHD+ y7`$q0QBԈ :WN0$G ȁRB Ax*zM}"$w0\J9N"2C#4<=!-ƒ׮ۆBLfD(ܻ"͵Aۺ>^ddO r[83# #0D{, Gb6kDy|*a UÐ  =FBHYsx'D>No}t<=?}|~~zj@ws B NӰ[^"yDG؛Yۺxg Pȹu{G+nYp*IGWoYgG4B\9"zwGH9] R.!DIzsEdAh}L|[_eH|9Mw]u۶ȧ 2 r 9fkna1M5b"޴T+y,Ӳimz+EeRZi&L<@uML3 U V,NVliHZD9gžyYnx [ <۱%>dzxxs<,r>]omoڶH@X*Ҿog8 1Rx`DHD@D }om[nfQD Spဆ¾~_om3 XJ @1WJbtϼ@I ,"EYUP1RR,<6GqU= ߡ [7&UE$rwuq1:!R" ~[+,(XSm7pD"nL[W[3%#ie9s.AT>]NruzTNagf#t:?<>mo#"W9H= §_1`]#/D0ϐzF!ʚ/"BDs\󥙿g@̜C0ggC4n)JDD6!5HvRi oZ2UIAkMnmkwf;  !<0a ɓ5rc1"%cD I)L*ϳN^D{)҄RY0B$5ts l}]Çm_o$ $6mvb""ab@D-ܬ}mWM[AyУ8M,FB@,ozt:N'$ iAFdp/tp`"#G0,`,GoRJcc{{0(~=LH 19(\[kE~4Jח}USPXb &.B`n5^,K755Ub/ĵy*Tͭ[~ p߼}{~w??~8-˻yB _\t>[#$aQ^9F'[)d. f=Do|D>3AbJ7" |͙` Ā,#6p19pfp4F"7 w@4U#zoF;"!]8v`"cNiWp&t_enѼxr>q U&DupBI3TjHjYj DU͍?aYN}w?\.t?u_k^ nL ,H)Ȳ:̉kK!a)'>kv7jv'TO‘ĝ{^m۶3R"YkNUs BG`)b."f YGI`YZAJpB9tkd# [̈=D,d߯FT(m8 @;f ݕI>=?M뇇!BED"lv>z_mmN.: ڧxǛУRv7f"2rvLCA)ee&nf͉Teӻ?ki)O?~O??{>z2Ոhy4!`ef!Ϥ ڽw3T"a&,E R GQ6΅DL۾]z>t:ZK X= ^(aHLn9%$53# !#&ʖ,<0FkO9.D|>;bt/7uE2(\RP p<D ?E@q8Oay^yIG住𒃬nkRk{CkU1:Ӳ7R<__^ח?Or.,Z3S6$#0HK);B'^DJS_$r"DpĘ|f2B\+@wUmZK=ELE=C*qBaQEY[|ŸRZ.`A\%?䰄G a!d""ԝȑ6Pab) ]j){12l9䟸P3#%d @oRXp",,,C53`3LӉyu?><><<׵v?&yjhQ ܝ x`lHLB24Rā`F& Zj eϷ&E M;x:DyAB5HdENH pjq-@ȈB\Bۺ&*1Zjڎ+=ri?/Çym4R "o7S77\J}۷o˾oi/_8&* )4%lq'@=sgN/ Kֿ]#h>` =/!^(@i v$|@0A) @pM(@D2O0rٻ/F5YRd#j:99@F*8|uPDf$XJˈ`BSY։X4"wy [j2z;i:"<>eeN0/|:_^>~X khYRNTa!DzMl";e(??ַٸˣ|~bgarP cp=GѓdJR`p'"W)xpYm>>9T[_n&0rH`c 'Zf}܅y``YF0Dw@&)AfF4ӘjYꔫ}}qLe#m3upey_x3]'5=.0omEtݣ@`H"vEϸN< zzknHTj|<<q N$H̀PrJmy0BY K)@]YzWf]]۶G1RNpg3辷 K Hb"<"@%oD~l qBpwU}_^S.Tgb^N<뺯m۷oz{~~޶L|VU=e*RJ-*iwmDAH`[ y޷uuծ4Zh*=h׈Ԟ ٪3 3 2]WЭoo{:Mt>/L ܣ=edf8YM,5_FcJ+AWFH^ E&Dxj=LkZ@XIjUzWW}o8]s_ )zv:-<d]~c'@YE"x)nn-]$";Bm۷m{ޮm4RQkJabvL T kͯ|o߽kxw^u[\4- !`̵9N{FDKK_Ak`Iƈ' 2Dy?rrdRN>z}z>moc$L=APPwjښu튃ȕzN"fE[ڭkW:! Cp'! pbq52ĂB%TEJΧ޶}۶mK)V)E(0f#1wzDH OÑ7G 9 tt3DvБ GYi\G|SO'q,s*L2P)mD-h[kHM2`F/Bbp W[LB|7Mu D4Ӂr`iH1RuBbz{0`DHZeh~deh0͞ n:*m3m=UW=1#@0[c _e3Dhυa݈ Bj)SyLGX3a$y7m z_m}f:6t0 CI4t}13d X9723OH`׸Gb%Ft(]޵?==%@nIJbo(js(=g9 @TD_wSk"̲]HEL)nzM[kvT&64Knnܑ[GDCvfRjj4N|:YXo筵m< G߻=,eaimϴ}߭q͗HzIN#hPXfAF-_Oځ/'f tiqz)d\kkv5Ϙ=qVaXXJN6HAV T R6론HIz-DN$an q0iYN|:].cBq7p}]p+8TIБ^%,44!pJBiD:PR=M$T:])"hDP] (.۾WMon |^a`'9"CGgO/S,&8e5S.*e^2)E*Er֛vݷj %ggM`@EX7 K2"i 9-RR,$[z5r>߼!}B4g3 !bpb.E}#&.wۿmp Sua6s8s sı;3Ik H v H<k*tQcE#3`J,fa\Lz/eS[pz}yyYZRj z߶mY/eY28ʔ\+#!1 :9Y:n-\<ɇFΧ9G]! )1GىDMs 鈱j- \[kj]VRd9Im!bu7h B!39Q>n4ךlA`fmLpC l'EHIyJGrHh;8^MH9EW:1Pj$׮Ltzݛ7崜Z*A[߷XH5u[3s&j9Cb۶n|!,tW c>fRa%i֑PpAdF!Rh8X)TXck$Dh Fpp5nr7m]e;ġ4z]8 [` ! 1 À̃Z1kRy:M|}z~TSh}\N94#b1Ni*y{Yo%ʒI.CN4DK)ui}]o//Ϸ۪!u-4&!-@PԩLfÄ톗~E[TK= eo[oz%NsϖeD"c 5\GF=ϸOhtw'bH'aa|+LA?~@ǡHɩ DfamȌC: RTJ0._S@˄I:~.Z ="Y(%ʄ۾ۦ9 IS}||,K)UUrGᛶmo>>'?]{VZK8"1$=~]כ8j@L@y!= kcprPՀ Ds.%Gp+!R.5m>#%ץnkZv7#gZF 88BB!\礸[d @?"wSӌc)`7#&a΄ 2l=t]fnӣ|J1bfaЛ%(Pk}nTɦnNN"L"ixDXJwmm J$+TE!  cA:4bn秱oǟۊ^k{$ْNPj˥2WxXѻ=_^VgkQ1kwwbh}&~ 2 `ooouq?~٘8"////ڇ N%%>"*"[G[=m_ן?o???ѲkVO-̒`X\1sr5'uj5Fgn]S%9񸎁8% gZTjHn}!VŤ 5 4+q8gRh꾗*8Zo-]_^T9q~}IZTa\V}j~7?If["g{xf9FρiP'Ys60iWu簘g|xo=8#eZjne)̤HD~\~?|""z"Z#1) ~wSnK(Vn/?N Q<1sӟ$GXG5Us쵧f gNk3#14)O!MLB/eBSydW#܋ zRh!ܗ3_4YD HaZP*qL۶[?-)H[;ovSZYΎR*j ھo,//~O^xc@N V6"t`RMrDr/",'6HHfajCPfl]nm&\Pݍ( wy`DXY a1BL„aL$7 "*Er60sHWg27T@y)BLP"%6nrWfʌ\S_D'""E"}_ ]qOV)fQMR5aDg`'U!Ju۪y8 &]=,\k^L@ " "D#L{L(\1FfG`=D9bBĴ"϶O>tl凯[:DUcK}۶RBv{#BJ$o.:3B_ߏO1Aiqz';rџ>PLyR,8k{;Y3f#]ܞ]R萜f{?SPvy> r7w{2  ass۶m9i:zoq?RSC,T!T{kJ",i}ov>j)ReSm\t2PKJއ{HFsM[=GsgEP<=ź&AmaHbfǣ<ס(39t3z"3HX=мrZ[*D=0F'1Du4D`ƹ sWtOi@@>!7NXVk&mM#Dx@~z}ufJ^81l2gۿH-uߙZzh }"ʶm[݅EMSe:Xz+^v6*:s47@Lq I^n1Fu5caLVtf-\A}/a/ԯa{f*Ĥf5R7|93o<{og "EGzω~>c-wD ',AwJZU?&a=V+43"sb}2!\#\9fxV&0Bba! ذ1 /Ke߷ms{kyZjBĈIyfN}x8nlC rf:;NH~LUabI1@IHUmi-t SEQ"\,uEj\`Ƞ;4u؏q"VSz}16TzڄeZevp';3%0*`#2l/1֟nG`p4! /Vk"7TǡjBTde{yy]љU@ ?!lnn&E/)Z"r4åHql/w>t?C{)M`BNb!0ﭷ11m#ĭ&2f: Gvalaj9_f]vZ`xJ"m3*jEJ<pc$Pn[7$POŤ"}"w%)\ @BN!R Wկ~Ċu5fϕ쭳Ji6>W1S1sLL}z,d09qF D,:`e%IG p (HxL5nVi4蓽-MB>A6_{ÆEy掫v"CO@<8 8^.(v"!ɫdI@j04"Բ4^3&RYX|)d`O gF!j~ܵ)hI3pSC~_..!a){&q&<I!n!ē R9>x_I^?3E a&O" pUSa&T yJ]K쵖L**dս]]M_>/z/Jq6F=K5\KItq ^w<:J)\H%YjgHy}}# QzƊy{F泏8z,LR#+ǘ֐ k暸4 o9:Q207чuebW"…M3a3`$! //o~_T~uxZ> қ#2!w)}Z> ? lVя',߹x {,  OK`sn >Sy0v:fFHf Oy11ɋ,2 wSS[~Bo[e+ZfLtmA*RJi8+L&aFiJS*T-= ꈈZ6!|gqWHrbZ!k=:FUpXATCpwBV7󰈑J>C̉,$ C8 ͔`2F@a*3'k0Mџ>HHRjog;|f& ZJP!u~|uʻ81Q8E!L;nJDΜf"lD$%;8OfŎ}9̠5l١މO.IM̻&u1qHoвܨ0*Ǥ<7Ԏ<59@䫗\{]GShLMF!LL% BNr :@ d""&ꝉz"n=iRX^Gf+i7C8fNd \!.×4Oi]= %qpԜȆ8N+-WqXSB橫L<M56Wq,F 28@Ę!Hajv?g;'x/=skfXh5.WSZKEj-IC .Ä dʌajj N@„ =50" ò2U" <"E5YDf ,5kBS}wJ} V>ԯCf>iXme&LA7gRe ʱO5o)"kOD +ya3fAʴ#ƙ>S!Ɯcd&# 20'Gǰ„ Ef:lYrWQkއCU5TM{?Σl>}և8aLze Sy<YHK vbB${7<x,Ȗw|~fK Db B@fffi%G t0gJ"XY`A p11K E:F&Pm6| }`Ï$T Z죩G_]YUt7d"t "*8}"hcI*"$kyxsHgvZ `HUPE*bJP@V vڗZp ptg>10LĒT PWl9\Yf,>mۚ"  1m-i6׿ƟOznQWS~4>"}3/)fi㩐%.dP@8 =2PẐs6 鞁yPU3~wD '8k&bd5^HLVMN>r 5sMn+d v6f>5EB^ܒK :Y ].g lZg?8vy5W7 **@*H&!2IKqC@dG0x{-"dO1X_s;ٔgp7U:4-  ̜MhZs7w> 8pSmFL= $ nk,3q6,8\ذ#SN 3 "|2gZR \Lep1,oY>ӱeWDFEF$%[Dp^AL'7]SIb0P6`pf|fTUI-3 ݃S}'K|LY\.Ej2"hWPNV)}L {M!7e*sśjXu%O^cR3 u7??~/W~' ќr詖ܪ:fD"&*KZƉx"fDrNuR A55 N eHR5L53yWUc`)5HZxTȜ+3Vngܐ 5?h9yq&]umuJ+MpsZlFd&o$z/zÈ`+<0sRg|F0MOZ*}<̍R]dѮgm޻F3z! !s{daә[$Byѕi Υ0|u+ )X eÊPȣD版c<ό6,c !Djd9ij0cm6jDs " L,a8f6 H$L[3S݊BLaazoԖ |+Z  zP!Z 轫D,@kovWS#~;RQ4ļm5ܐ Df2gWӷA48iX7 !"M|p"3Ga ÕXdNw-? D"BisiZ!sVww>>g5ADOˤ!d\ k>ySZ)G<S\9 wJˎ0$?[x{'И)E3&bfmt3S[d'-Nʷ""nK-9A DWض@Mt-~$9~Dc[Y}]ضZKA@"sM1"K¢&>#,sRLY N/c<[#V)bsNg,Wfkʁe[Sj[)ӊT-1Ξ>!?h9#;Re[" !sƓ̫0\.m!gx.BΑsaӞ{:ZkDdf}ׯo ~ErŒiN3}a}N ssU7D,W&Knx'$0xTAf=gN)HŲd4fZmC0f]SSd{L\#F|t<}?=׈<4ED,a&{6.Y{ͻSYug^*чH5f6wzn:Ԁ0̓CചwBPaR*Zj-7K|<&a'`}k^!"Kzӑ~G6td,' R`n<@bbT0H1 onuS5D҄(R t D"3…r9@(7ԑ.Y^ hZs4Uf@¡pAjs}2ڕjP2ċu鎀q\3Ce,\gTwmy{Ӝou8SX ^g%ysm"d0<+m2%Qc]<45B[WewOv2K0sDmBRkjnCqݏG3INxXP{[taf 4WҘVffaZf͗hc |c 5'\ֽB HLqR⍔xD@  R#aƽy d 3<O?ezxb)[`8z07,<ܾ_/m߷}$R7R"K>s]ԬzxZMDh/̍C o{ݷ}]V$EpK$s:mwBw (j.rꖚZ5=ϳTa4 %b cef*1T,n3}ʢ-.hBQ'bL- 15iT!l+#j~x7ٓy  $"u+<%I8Iv+07&^쥏;^"~bOÞέR*ү/m}7Dl|ZMRJ-5UfB5_'.X9HKSUf"B-c'Tx@81Wccm+8H{ L #O61̍⏈4TYU @cHd33]ي:,"vtN`bDZ) ۖE*"}ԭnvl{Y$Om_^DC8#1Ojb7Jr4i3v""p e0u nITՔĘOgy d[O`nR1em'i'+|J&J-/->6??+XCPb9H O/2%FГ**js _w-dz[#8xj,X\ku5DfYs5b mt YO? &v\Oe˧O/[t<81r.e@<[\,׷-iPξ+ 9p{=h"@Z w#TH,E瘋"YJB:#V9];?,DGF Dቐ4g;z{7 װ4m|{sIiD '?KiR+RKr?:tS]ih mJ uy5aۂkz^HU֏L A@z8:C%FK.VZ9wpTP@4DlEBZDSR)Sbs5!E[=]<XEduQ|(BLo?WDj~Z"> NĈ?%@M\M:Zg ѿ_+i9~`~(mE]_O72ٌ/%f=7SI~vh$ ѾJL#-I xlR4R;% 9Awd.QjMĹd: MSJ`s9ϕ;Ղ̴1G YQBD0GrX1_c #_ύ:@Z̵ E4 UM`Ճ4DB,ִ!0CmhD[r^.ff)GqG]Þi&Ժew:FԂRYH)  Ka.R\@DB"ydVB̔Bexlo}QR##@!0p2ݑ< ,`j~*u!F,.l"8OZySFT(}^1/u^XX%(wn 0G?z&rK^'(#93&7~`172akM+K!xקhC=ۃ!fTg|?B,u*@fr <8[jL 1'9~?x>U휄!23\p$2w! "WENPEm0IqxK)p EДKt4 "z$= )|0r߄ h PD0J0AFH6U.6eLI |Sq 9A U#U01TuRk! "m^Vjɹԡγx{C{J)@A  PT{8 3 ZzTqϨ{߷=;(% JqE;aGmCyYK??^˯'d?/+X1pz0{1_tB .%DyMf>։DLnם9PߙDʶmu.[<[k="ӉxooL#_J"^tŵLMxCXjEB@cĀ(g"fDA!bj Rk[%H9nլ37 Y.2K "` "LAq:2N`8E0gI@Z9AUDmh14yy̆3W{":F%zӇ)䖘(~i컻26v}[))5l"Z',@mçq.ZtrsHӁƙ0shjA OcV'-xgr1@q/r )(e Mʹɿ)_A?[uZݥq\9!8>|~U>Qȳ I7Otߤy5mĕ5HsE1ӪHA`OXR6Cf)5w[ݽXw1v?GV3)=\ lpi.㵶p*"~90ZEz{S>˶KЊ`MblBܬqx UGWO#w}fÄT !u A9siH.*R^_.q"V+:C轹 ^Jٶn[x0$uO?Gbl-Ct8Fף} )z 8ۙ*Lc U fCd>&3S>:4"6ZUKS2MB@0զ82ianR[njA+SHPXU0r;)"]dC嬈grǤΈ# 2Q?? XfwCS>#ԞZ'$ ͌{O \'ノ#`X=4p2J߿75)' HQ1t>f s1%DY0CD'lV:yLk)EJIj2fsgܾS{wQİ U*5ߩYc8+X4ZoChZT xf <#Axm8[7b wՊpe)EQal{{Zﭝ'i XʺfU4>UXl3t")NtT*nGsfΡ#ގqZs/_~P5"uޭucm+""QFQv0bbQ5k-l}2 66-"'HU->x<ǝذ>4̈' 3B,h 7*,3IjLw@@ET&jeG-,e;y֠ I$ѾS#xe3/?3%feMsi2C $;ucKJ_q܎81q<[({γ=08n ^k%2| 4l]#|<}\3E,6sINdLȑq\h,1e auT$+͜sfhTS0E C5γRRnMhxo#n\Bp-|Բݎu*loo10~35 R2VDlYgTZVĉ m7TwW׳7baq~y֫ US ;|Vwykp3bpcbhIRY$n,@$D f9C2We4OK e U]l'RQ&bbP-*ÃӯI]\+Xy:fzC"4EV(k#$ *ًbq;_|yyEc6N"O%;)Xb)!fDêi[̀}r2X0\sU&[6Jٷ8ח}gn#YXj)rr]J"fgbUtt@Juxpf̱FDބpٷ؏JlQx{hƙ* |Y3d.%@sb-4NT&vE$tusLS/zƠG9-S!KCjUeNqkRQV[f^M.},a(z:8R3f&͇ {RZo vk6*OSȼ]q 4be|yq 9Fԯv IDATkE2,s3#xGf.EENx<-J))ª&GaINh&0օ˶mVl#̃YX Tc E8fYDEEua|;-{=nc?K8ZmvQBR:aN-:FCq]bw(08gaRq':nEKbur^ݎ[25/ (#V-X(E$DDM(#tıRz樏'pp At*gG!D%™:I'NJGФr'\A-ɴW|=wPt|BIwޙ9;nbVlVsA?{kj$4HhTO8{܎m zy1a&%efQ&!b5{LLk:ƥ|%@1 }vUn7"9zSUZ]e}ZǨg; *`t޻8S0 Lyj)R ÜK,c`l[s"ݪʗmG+?;3;w)rQD/9lwlS #&ʌ@”Hȃ1GAgc޽0Q<ê"%:bpr&q,f~r|J8siUݎ?߿7J•߭g+rYsEHJ˕hybP/Lܳ8X4ġӉ7((ǞU@K..y3rb42 q˓ScZk[BA0*TECe >4A bmT%X)z 3| 4OIG(*!|R.-yT,*kkIMaa&fvxѶGIE[kӦ:*e ܰ *(q#>M1"䘮rkwxI}{lݜKQ3&R15ư,lP'6sr8j;ӲGk.VJZt?{7~8c)&rvnMأҎ%,z^."VQ6QX3ٰsi/"Zjf#6bF:ӥFjdA".O 6n1RxmLUUlY#rcS|?x${qf\23~y^ɜȣ| }}ΣjH831qq[ tEZ(Md#T/-VD%yEN ;ċ1@("uZrl8kl T:!VtJuNtOo'JVf2Dbb=O1lLF􉻏d#8j)̀yP8nJpҞY"H5aҰ!ؕ1؜Ubre;l[#<Bc`aúGZmXe+(M{oxm۷cםDY8L^I>V] T]GGbDۦۮqhy,A֓G(nN{a-#zlvZ9 ʖKnD6Y8E>o L ER*Œd]K힬ʌ²':֙Sg&V@Pf+)$W~AFS <YQRn=gf-EHec,Xh.l n5TY"`K&Ǵ"LzDEY'PrNo.J_оa w@,3o#5F2Qq]ctbc& VRgK1|Tc;#H1k(DFv6T 6r%#e/0ɯ}_^_ 5{ nFnshoj;ZYNl!0Epx7E e)2G82FHn#їm?V`!BXx2ReET)~۷v"E8s͘]X ,DHCNyBصܽ6zSp--Lpa4vBm%Tm*ݾ8a"#M'AcGZDHD~KnI*4uN{}E@vSg8A\R"@>ELŸȔ9^ F"?LN]NgJ=upaNWKD!f}|<"Z[fyEs0Ux!ԉE,Dee'޻97ٶww/{tv07WBDnH~3+ g!"LNi+BpBAA F0iRnmj*rd 3eUR}߶]UG 8B3Y/PA#3g[B 1QֲmuO;31x!U'EBUJ,"UgKN"."ݏlB>٤h&~Ùm>si鲅|ɐXe_YysHL3 e$3OSC3~}O%ʬ/gȌZMqXЌ KYRL%z150!z8Qg27J@ R !)gYX RY$4e5U5"6ci:!6a-qP 9,}8Iv0Od?n^@U ^PC h4{oG{cp"bf|<7bbRx`xkw4ЪD#1Dm9sp b -*¢cTU8~(xvڗwt t2SraNDN+z"C'4{3e/5*U5:ǂPDNYM`X#>`w3DpRO27T5核N_|VzC=>Ÿ.** w܁x*"Ɍ,'Z͠OSTq%#2rߑ;H(=CThɐ ,  sPq+/f"E*>% LtezMoAOBlC4W>XxOfcN"6̭xD}NKt9@|wTr)gRg,,!9kíalE0d\{l/|y~|Rp9emF=O<܋p0$<*EvuloZ"r?FcP&c81J!Vz;/_Kl6胲bɳ*$DLfzG0KeUm\KV~Pe$?$$jH*s0wYbA1߂i62h aS+/:dIH#bix.FsE+S C5 ,:ןbb*aRB0'8<}b )䉞^#1\0cDUUTF]|%NH*ٱ`z"n=c|qN-tPdD"3 ݌Mk-$=OPp8.\M8*<n£hxaҤe }lfy>zpN'o LJm Q>@"a bbkưERʄHضJ3"P6#oJ ". y#$-Fȷ߼;+q*_8xLnsApq|y}U~_{oAfiʩEg,.EJ O@~ ~cJb?ÃHH#c."1{}8n}"-ytI`>046H|8~]$&+5H CyiH9nH)=#M|<.6/\(b3RYU]Whrzl6Lpd`FMen^9]HA#;γcl]JVId."^S4S+n(޾~q裻y)Z 0I%3G;l)8] &% M'iEZH.FQ!5U.S[R$I0_|C4iY^!z$lyfѬ٨\|i<wy< F&9 f]Cإ$xOxߑ=#:ct)\s'̉dއ0\( e҆zYɢ,5'wR)5&3g~rRf½[;`8\^ `z8.*{Kz;ONJ.Dluz",mXq(Tzy31pV\} 5'|, t&U-,GˍYe۴ZX?P)fcZqͽ{P|}ZKE|BvV-PXz .#[8{߇cy8HF7s&r;m?j݈۷oÆ"ZNw@y3EHVt10MD6g!lࡐ q rivOfV Mt0 33&V隈c`iDS w?W,eA՞{0ϹJĚTF!FPT]kfM@0/xo>‰\څ2{`_Dfi.? @ڮYZ4wew*Y&LU GgraD+*kaËIH>r`fY ۾M nډ9 ^^+K1dW'U"H,SQՆa$%xKYD $!\A<0k'R  =?zx&>`cYX1Nޚs測/ʔ$FDw"[0P'*o%l8k+6p Td=A1[&9YQJ5 ¦V ީ=TmXj?ns{ǝa&8uH`7¸3awKzb XPzC r[AN"\kc,^HQɢsМ<[k=Nb.*w6xs (F-}H$΢g <>͡0 H'UK7bq7>lN>`Q! Du3&{olZ6:3B|`"ֶz{13Q)ZR۶r>5$V/ffA>ی0^=9B*9Oz$pY BƽL*3p)V|CJ ∁>>GL9sSyd_{8cRGm>уЊ%`Y <[?sؠ )"쪺բEi`:_ $]1G8EP )M ",sCR'bDޙHE+ ]pfB")%]U͔y`RK=#C`=z$f] IDATq!3Wɕ>8  oU=F8Ƕ۶҇!KU,)DZ}(ׯ}6n,AAu9X-B#RS&N8{G)jf׳m#jV'Ey=.Ğq擁>I>j3DT1N|W_ѳ:2D83''w╞Nq2=?\$wacTp5%" ;FR5ir>߱jyS. BəCrAF,EkJ-p>b@ eJ~lߡ_L|8nu ]1^ZXh=NETG##r^Qr wb3pI(:6%<х-MZm ~= #ChyXkp _B놛l 3SE ZХ1IBZQ<O$oq˱;##l]DE]|+[-jVY8T׿Mk͇g{ׯ6˗>GvF*ڭk >QcgEd ~YAT>lw#,Ȁ as623LBCDT 1Q o˱z#?^W4+h׈x->2ty>,)Fp)Y vtGs ӟ̟̻D%xvS͍2hȣmQEG@"lqscv"v` b)$ %pmXH0R8)d;E f\%7|$y^"\۵ hr'f-JL"nf1y8A `}Itse I5n݆nTZsLB; {yqd"x-e߶cm>ņm7lnWc~~}?ړ}ߣj!*%X(pc I,C$3[6 6Ty"])(XOY'ExoŸ$7a8c:#$a 0| d6 bx؉$87C$_Vf]5':S jZy6rf /yb$o1aWR#=c:r06;G L"*i)|q͢I stjhJ-6u_,^T0ꩵf=cDdf߯,'bR"Hun=&>Yr313@5}nAß'Z(`KgD>$x y/t/hrQW5!̞4<E1B^5julbc`;Z31qZx|~OBXeJmə&xlV2DlmaZjrV0޶<ϏR˾u۶mUT½hf|#lih\”e ԺhUY2s󔤰\7!%'<@l1swCEv$R{cc']m?IIU=1s0w6?||9i0NIp(nD4AیWժfT/ŵyXZH:ibhZJQ!yoE`Z̘iS sw)9TJM;2SRd0 &w؀󋑌)cEՇ%StB3pt%ݺS,Z޳!$:% )lrpYf09 k.9žo+hfL\ O [Ua1{CRKq6zǀX* %Ž)ICcX;[7uv>$BģQZ֭˗~J_~E8v{o6ALЯ.>ΊXp K5K> n4N3]@afI13s*8}/Wd8[)&f9(<Y0yº+>Hkޏ}73gYZ6\& (4 ht 5F*-D`6 rD؎G3:|LQD_^^ZUѢuvDQ< YF!^=g^As}06ZV _UŨmߏ8}߷m6}۷7o~aRTZ] ͚p;7mi KAΉ! f(Rys0MDUKϻ(8;ECcIկ~>}ue2gΟ%nByIs48p\J~(t1h=ڒ96C"ܬ=*L[uJz|c XspeoRT&wc@$RTL.5RD6܀.Y^̋Nϫ8؟*tchh p, pw-{*󨪨6ܟqEUd+-7Rva=)",$I _j7{sOQ-𚼿@& ++fppz(Dو2p =Uvy {$JjD!Rewq"ABE ]"HHa-CKWPuokXΩRjzNsw 'I־$RӶm =Ƚ؁?Jk0=Nw@$$0G(7_923Jb#PRk8uZ(D훇yx,}2z~K(G=QC*sabi@jnB @53C`n|HJdXL9{DDV{vN._LcaUV̜&Z{<>oooǣ1h1y|}Usų(/4yD4̰dg[8!Bhj\ DTJA_xGd sLXΊvz?yk/F֓s%AJD"Q<'Ůo1ɹϵbiO3rGm}4nLeH![goygS%-c6JkO!H Z$}3]yV5g,}PQ&10!=-Qo)ۥml[R]Xˏ?|w2w8<9o!F~ne "4^^'_H}k>{ ! b8{)C%˙{*s(' /usTUb_n82l >;"GsֺXˈy;e)̏Z"WHd&e a!KKbf {&ɚ:?"jCh 2Z`wҨ"_Uy#]7nv 3`fƺ` LwWVMss<{YK Μ x3VQ k-݈ȬOfއ,ձ(-0UKBUu x j:83NI*XTKNFbE^yɧ$Q5TK>} fю1Yo@7~?ꦵm Ѕ(,"$ZgT@h<+E"hY'bwb !ATTn d:T&[h}%}GK~5Ѓ2yu33SH˰$Sq>?q ̰%4DIhh3Si",Cq=yѴ 9멗ɓ OoޯwH_<軌Sk'Nu7uȑ| Øtr l,lI&۷ =>XnYk8yWRT&7#&ټ֠RQ8{oXD8ysvf\ިJLZﵵlmsNJsҭ*E9hϰellRJd|sAfN:$$[-ZtOonOۧm+x]YB[{z}?cVkR Ռ$ߜ=”C|SUfp51f纛RADy+¤5BR ?)g-,P?EwgQ 4j-DmbmolY:ϕ7Z/;k[ԑcLf \,W:&)%nuS iNLJ\4wgZӝ0$bׇ2a '\9!J,Vkhi3>w?}|9ZRU Z=zτnL _3Ow?TXΡDm;3!IU.(>#^c,sq'@J-|MHXgeLrQUprR㞆Rܰ am{ȜwE{C\'MM1{wW>Q*̴>RՊ/ZxU!ˉ b"$HiH/I2h*)2)m۶;ܗYCXTf2xG½vb8.*:(ff<,T¾L69DUb{o;+S'D8x~+vA]vtdcђ止M/]B(19)ѻ!yLp<!@B,\ P؈`r`Rodf~?~i>fZ 9S0-EbHU?o<H*r-B4 CȑR"_gZJD</Ӣ\lnoğk@}r w& /sw_ 9FtOZϰ8l04YDH,:Oϲ`xQu!Ӥ4b3/f~Zƺ84:^m#·ж!4$ M"ȏ' =3+{VB"K%,*%ZKP(J5m=LX12FWϧE8AMZonZ8yP"r&6cmcj)e'ŌZX>XǶ|Ki>z[JmM hq?ǝ̥۾}TK( IU(é0DfÍ&KpХHfQ͈TNpw8NrN鐢}#>2TiDJ& j1Ӥ0;\gPH1CB@9;:zf"cG0qĄ)tRk`"Hk>R%jݶmݶIS",NJf GN.9-Vu GMy3=Ry[`XX[>`&ӭXwRjٶZkx3no޶m+ZTDHz֑>d\ 6 oID 5/No} 2ɜ"z}3"O$4"#Eⳁ[ ?( %xէ3фN!,ס>#oGdj,x% 6`&:ᑃc`KəH{G׭RlQEPXUn?3AE5`_CyI<{cfC1'„P U BUyj8I#} {X-,Aa$9&Y`-#va,RgvU}I3N}vlP,;Y18>(_eR@0͖8#q*] kc~wZYqRS.zV},Hv2$I%c{3!<+8?~H A+ IQE "؉b70=e ]0pr 8 @' nl^ӱ\%.ЬvhY!D1q)E 1i*!tq(yR4N<ݣwshV0` ގ|>|<"-Rx1 {fHNex-Og_X|9"d"E;1!O# oRZ}߷h?qj-*rf1.WWy2%ߴe}qɧ]9fL ]"DLq ԘypNF0[X|EB~U[Ogg.G\YW+ї`gE6xBN=ՉXe9 q`: ,iU<?Uf!$wcss-"G?ݒ1!l?E5Uȓ(i̊OKfL**uUXDHyHE%" KtI p§"\FsKAp;p*E[oanZ{88=%HLL"s;>%'#`2N 1 ܩL\cm2*A@uSQ7ȩYn~'i)ҭ֎糵O?Z+|ڼKݟe)NE47a81[!>SA檖.r]Hb)}˞r=Q)c Nr?!XvgUC%>;bQs~ Οh 4>E[EEEUn:W5t./AccؐIlp8t{p̻-SCVѢȾ*s"Ep#ȪXASU"h ju#LXE֚H`@ G-!(\"GXwP*z.ΘR*(C@C'n=wbeɨYfݣn3ZZ^BLYn=Bșb!q̿3C6\VOI))N#J,)CB>˾m߷Ofnv7߶DT1wj?f@s>3} _(HbcrEҲG3+M4pQ={fԔ G|3Be~`me[qOW'=TjEĎz-~o_?Ͽv)K[lޡ<_6s'P8}=q66x,åiX\f)?8Vhə]ɳ{ؙ{9/MB.90?P~E8En%%bQwHwe#b.Y2 F8i~HW{zZ?(H $ eoZ>֞3L7^-~G7(8 fW$HaC<'-Eͽu`-JQf#$a=c&8"JRD\n%oyUJ;L=ڙ[5O#/2t5Ztqr15/FmNwj"\̯4TYv!_F7O ƴ? Te.%28x Ot)0Tq3':k- EBڡE ,`n>-+:gRzon="rWLPO*8B7̥wU5w^w?p-@#8hG.^Kab'A C 7y|Xhiudr$|" \ WZIL,VU 0+VUCQv"3é2Sv8kAx e)Dǽ㣵fZ#'1&pF#yoBll59-g̙;3y3=pX~i|롏ӥOk;įnhpiU?m[Qu3HœU**hW D W!m҈XynZW`ξ4 d`% /ʡӴz~:~Ճ`L+GV']kϕ;>_%8јǦ1a"9&9>B1~T` ̍`ee_}y1 [cHRkE6nӞne#A[Tm^>}s#y?# î NQ0}n|a|1}:Zєjzn^ %^߅e!^X'a(p^9!"ߟۭ=x4"E“(`:UHiS.pqcc"B,`)dYŽHaU>>.C*#*n[ͩŽJ1ilgowkYxr3*m&6s&vUP*؉,#h%F$R-y䫛)糵v<ͧOﵖM}|qx?pYJ6Y&l]U) _1KonDk-qX)pK\i΄?ǙD}&Ɍq9`eUx>ϻS"z߷MD}ݶ=8hT_x:LZ5 ujp xcg87X=xj]D\=#RiLDB5܎.M(I\ywD\KqZGLӶmõIwZj^y(yP0u2ޭfۄ)8Tl l-Og\ p2w3*Bs#*fY[Z^yO9!YUW}M`\A^O2 GtdU‰Ik)l@iB5an*nFZ3j-[-HW?`Ymz d g&jYa刵NӸ<6ȇ͉L4 EUkdm+؃tR}mj~?9ß~ᅱʾ6ܗ$Ou~c|8gqi>xN>?b>kg4| Dddr,}I4[^%4ux.VoEapzb,[H& yi"IfnX!lA0M`Ȇ D 8Ő0 B]6Eun"Zd m ay93.wR;gϚS+[A#65`gػfo* W̡E8o XoT`jߒxLE㝘>}&󁇟VVk<My C $ŹH&pd;C?, i^v4y ^.bzqT#oo´۾ﵖVܟnngN_已qI}:5iI9ћE6;E~ȩW*8?Y Lg=!E̻ɵ.nDtZ}|1__?RRoȼSg-I| ֐'^C<-p?jv B饸'c /KJN@|sqw!pBq RCYJ0t8@L 4,L IDATsP"B=([mws&AUM޵wU%\ ,+hRI!4ȑ*ZzffH-E)~o?}mw)xI8d%Xx2_>2:?ĸfKF|t2!/g`@ ?>mKU\9qwk}u-UO.Μkf|:/+[9z#S5M.j5ELp/ӑKeETyEyE5i|Ἳh|VuKMFB% @L وGQj)e[kT6Pf@rF St31P0sa޷D g2pya4d )R4?zLTȻJr{l#r8N,Ҭ?dz5BLBZ ?Q݅z$'dAN378ܘĻ#bM~X32ĬvhGk[3cRc 8G;gD$E2DRkݶ"۶mH͵\ 9; 71M˸G|@➹%5u~Jg+'Д=HVH|,QpdN8jݘPz=qR6D/_|ґ.gv\D4tWxI^y}Ni3+bᓝwbbib1i,M_\Ԋ@g,M1}e jxdq $<-0áQ g])( Ά[DlMz $9%8^RX"$2ws[w 1.n|l5xD%PL=(g-n%r1;]ȝŻZE,{2afi=wr<f͏h پmXVTY?G0y fgK;i0Ӕ*\ŠP:%[pZy=YcL"Whjaޏ磰vB&q<~Tk}g.Fl&wtfM4{] ~2٧e{bx7QY9JeIrh y$J|P-J*"ipGufgbJr(gpk{13W]*"42gW󎯵VF01J:ܜZ?#+~<XŻxuZ=LA  Bs,w3gQ5x`+;lhB "eD/d斉w%Ab'#b1=!|fw%z f6Qxop.Uaq a`,{gId ]!Y#mS$hU{| I!߰֍|wwׁSCQ#k`JL܉M>;N{Fd V,3 uGy:"eH:?b?TVFLG7Dَg;Zx"lV7Bw$)`ܺ3+w꠆ ޕ <ã@>|? 1H]0$<2%c B~qX `Z~v oz`Dz4 P<;g~wM6-Tʊa89 T ٤ӌ`lUft[ fٶMT ǴS` /$,&aaEO)@WŸ[qgQ|>v'/s0so]ba°EY3s9%ࡿtzG&TKpa=4/6<0Dxݘ5$Y63 w"PC}7049Yl<LluĊBҽG=۶#JOk}uU|-_̢qC ˹JGXҤ@ F~ 0wم94cl:dd/S>)WGJbdӐHkf ucW9 sj%jP C͉嫨j.D ݂Nm-H3t#&L|{azZQLEEXH]X*yF`ŐsG-g!ؾY#teLyrD!La ƒ#I v6wnznn'"Ln8Yh*;Th&Sm5~n&Ln)Kr{ϼ<א7"4I&vDŽca+1K^ѧO>n#kH_U?s7bVq_}Xkzd9erEf,T} L AK2w180&q܌);G5+`*3b3ϟPS :vHV?-ayb]J)Є>pNC  D 62 ,jɰ1{kxwk37b&;"r#]/P:fb '$3AHSԀ43oxJh:rǠܒfvVJ vFA~uǪ|S-/KjPg:m03Px)lYGq̨&! :#/?be""}7hV)I_~~M˿/aZ 1c8Y3.4#?u^}|N}㜴L I a+~pY@ef\,'OYƗ/gF K;ڶrD4d7O8.>B1lt;Of3Dcdf*gCN˜.ᭂ=2]u!62L}8 d:̬[ct1O,xrI>Kٺw {.BH\ª03al Kdy]>= 71%HG◈XA#Ax b*0-XC:y0Z޶pHR7DFH%㵥1_kVxEMI*bA:s&zLTdEw^.FRٙ~ٜ6OW¿r }gpxTf9/$IiU70Rp% g Ǎs;\p1,-:=WOx70E\U[wwE9>$"0q#و(&ϙ,GDhT\i#nػC`Zpf*"$\\ž9V4 \%h YUQє1kmR::1f.HAB"&YB ]àw:T9&ZԱsoNcV5Wئ:Y^T2CpWѢcX'L\V%Ҟ$_;\̔];;eȪr38 բET_ԏ-̊t#?+x-s]=@2 Z*9L"zE,?tNhg*Xr09^(/ʜǬRTLAW<m7c/` Wefq gfJyL]5qʺYp0=[iIYMV\1&N"EKѺ r.!6Jh)`f$.]iGzmDf=̉uvbv8Si (fER0Bv #30D#Ō7(_I%"=8!# AbLP1]2#$Kc*EU 3r∯7a넖a֚nAfGdrn?9iXea?S1LgI{?9S;R{t % _:x<kNǓwX.G&"|t'.q{У 3J{EX$!30"?ͯd0:8}C[rvb>R|r?E*aĪmyC^tw_oB.cy/ ubyynd)Cls51 0k/D_"aQ-ff,|>b6p0FGeK Dt%^L]3|j:q]W ꥝ `:?姟(^ϔ$[P$ ?%ZRv)ɥbxgln=`#ʄ[ID:%\,29{[88N@0#ʸ5{c4C[FrDR _9Sտ96&Da.^DUci^JUm %Duxj-ED:!Y<cRHJ:A|#^4^kWGq>;c㿘Ť Gq忬1Z%4$CUsLd.#t/ tea uAFs0ȇ^2}4?sS~9ӇDaVs#yo1><LݜUųbdԂC3={YUUڎn;|O_o?ജxȹg&i8.IK4s?߂2 %ɣ w̵ߏWgΑ4;y 1]> #RS|oLB5g_į7"KMc K }(STe<s4 hNE?+t쥔aח*, v<W *gOZjViµdޏx0cј3{Ƣj;^|ͼ槊fL*ذ[Cf3Zqd^`41ve'%n$4P4vR[sB #1>DB:imC\w&U]l0F%_32"8n`q/*b: FR$+[SQ.eʃ0ĝC` 'tΎ_>pW ۹Lu$ Vx5L]L o`.2Jƽ38Y0kᙧ7f;e? o\\ZлS+"L 9ԋB:jy=CHqdZD5"HI<]DvsW ~XpXq<~` #UdDKp 2/`UZף1q<`2 tyuVf!M]N[3^# 9)zq}O,&e>"`ySg3,s鰁FTs{%zcE2-|"f]āܾ~6=%dm߶m)4JZQ?Z5(/185_w~c:OXgvB26^,8O_҈TEb /qIH2aIXއ_f_'^ãzgYT1}Jc4i:c_%]H9cR#,PAbG2,Q^ooHUJfb$ec"!'r{׬~4=k1YsfT+Z19ES=)D>Qo',< v.Ĉd *&EYYѬ)8DV4"S>ά9&&9ɳܲo_š@ {cgJԾ߼J_,D&%,! IDAT!g}ڼ&ͯ/ЯʥU_%t-11"1\YGG. eqfMPLvW6#sm{-2EqLuٿpLsyB0[~45{14JSPDp|00UX#yeg%o]h40| L, U&VQH@ 2 c/K8^J#%Z(߆D0͋\oS0k*0 K&#s2q+n,AuT52q/D|3xwNJbJڳ&0j;\χw"q𴹽03"ޙA@.,Ľ7(ѹv"dN BRkJŇV0K !M2WѢt/64 f%okٛ쌭x3BPðF@XBAKbp\ݏXHO\8n^Hw ~I3{`D,"9FSF^O,5-?ʻ״C1sL9y -l1`g#4]kiFɨS|Ox*Q@#yJ喐EQC&m- T0/fuh"HN".4(%| DxtEMe#YBl'SӍX3t":`v<ךsFJ +PHbH>C8K $+L%tNB;J4S7<"P={DǁS! -P>OeQ%vA5T?3h-]Zp5pR#s~!'AsnK/EΏhvK4XU1q\j7mvM^WzokU\UjaY. 27HuD(KE~psO,!yt2Y㐺IQmi\ZWww*UAK|CY JH ][:Fk=(qXL͞?$!X ޫF U`-PC)Z|⡏eUI`kDXZxZ?^̙W| ࢽ eG*6LN!gm2s>ADsK^Ճ~ָ3%!XwNUQ/247Yy( O5w9(pLYӲZxMA61$9ơ4L{X?"l==o.M$f7( Õ5Gssz7A](BꊦW_mAYh&``*Q -Hh^$y6)á1qMf6ۭZTt_4ifd"%4RGg>\g(>/B{t|n˚m*P*"3Q1m5Ɉ%Hw {'" 0;āOT9\a\¹~G;')(ppEHb'D4XkCDlK?tI[PZ@ҀRgE%Df4$eAVpרq@*f"n ϒJ G^1jS  tL-|mK늆x)C!}7l-3! BOĊ4.fn Z(shV)-B\ q1 4;O^oǷ(q֬nU=B4,Al]d??f*\ĔTP8Cj&7^( 0wDw64 T>lTY( ȪH~goV~D;ם6t@"'7dMvXas& l0B"$šw>("H"g,^PiIy&,ZfnVkԘ2h{ hLت^}BԴdK[dǐx~bVLEǀ0$fC~5P85dYqҜAo@D\}Tba!5e$"|az[\ 8L,I(z ]AX+O3Qmj9h]B`.U [Rh3'[r[=D)Ŧ:s"F4h>\%TlcK 'yhei|6 p% Mq퐢Ȣ<;gDۥjJH&NU>f2aKG&WxS_0 E5f3F+ejIkEJP;x݊(!3]x-0 㮵o!JeX=,C"*f^Js֜[Hko&}"E5@G֠&O\L]S~0>kV@kA mdSںݎs5Ɯs>'v 9֜?}￷&T pŭNwtjDչ?SvE3uyK3G1# >HpXD]~)vOK4C`'%P60߲U]U4},B1"̚b N'jlzCV6 Z_?*T$H.\!JHd:W226f`6g#$$WSCD$#\:j0(F4zU\(`HsLb,BAjt>S0 Ts : "X<}͡-O~Kk[{8?!O>_}8iΌ%S(P m76 ?:`#hw?l,x3^ke>] @֛= %s07Gy6}Cyd_&9Y4cѼ#h Q7W기Q@-⁈8 4YQT[-(<Ş+Ŧ}#ՠH$J!!2.5nDH\Y}iٕh5hȠns.h2RXF&xڢ޴Pb̐WX(R4jZ35HςzG×)ݔ$vZS}wzifqtEfp}?x_^r;^Bw rAbv^)4s5S5h tH@]Ív* Ri3h [/(n)ڝjRK2۸?)1'O%#ٔ7t&&`{MN,{@W@@CXgΗDM,Ј'YlzK7Jٲh$ '<$rIt|\dfOVߡx0I[#fW>," ֒5baBq[`5TO:QPMW$QRl3 2k0-ɺƆrG䦕K4=T:Hoޚ9}GxZc9Z[}0ALdDž#mRlɽ޳O͔kkIc]*+>{bH)wO B#? \)}M~ִƭ5 (TՎKleElG>B)>L]lRƋ%\31a,~Fz`iHKe7tO._2On 5iL&~-4*$"*ٚ.pP"‹ ^!v`зYZs $QH=88zB57 v-/`@{'5K4'VC5/Rؚq7^S믿ts("Z&^__}/~ ?[߷iƩ`j<'_f v;]*OW-~ ɞ -̠F4 )lVzâfgYasqfњs!2x/ u=?s&}W1%"iÏ3d)5`n Xmqѫ;μUrKe.bƫ@:9|y EHMq0 @$4F7n=I;M^ĭ!K2]_9:dwZK硥DNPx1Z,~!sKHяR ۶T3NޕLX ||~B_{g^0q9?B}*d.znK.=x=Fj)A`TQ ,*kPB2 DHJ>ߓb^]q0'(#a؊chȓ*,[Z<<#5"N% )&Xf]{yԨa:s":Ns4=b57nFg16TeFc`ɕV[6Q!P㻣ຒ\kEm54]-`v{8ZS#CDQo5.XlH ]BT`d_}j>)a쀐RX=l.2lͱѡe}| I<9z˷͈ foyJ@/}܊R=jj&;HD}lB @/~ K<.~tfPX&.EAc?ʁ'܎N oie1ljE W̜W1V<a27!P OTe=Q3L8xyyyyyh)Z}Z Y`L֘5vGU?^}\=e: Bi8V̖)W ;(-PkCq%W ߓP3X2%E@jDhk`F@WpZӀeqw`Vf,ÉC\<7_,+C7jeWpUKh{lE2GMg*! 3O 2IwVq}^؝3JO {7L8Xڈ6ƧtV:=70.BycFVy*M'q;nqݻw~=__ߙ?9[S72D;ov]$"%4 4k2meg|47F43LyU%LP2(W!&TaCeyF gn/j4Du߳_ c!6=b~,H@ WS{JR-U/Q$J%O !@I,ZnƽF`aX"I7(nQ['.B&=I7LdERⱆWWrD2KRC̤1PI0bE˨`lGqyEZM-$ÐtRP>"!8~H#7|s{} b"l4L}'Yos:yIh:K ՃbҋH5Qꖔn >#Utk)3xH_Kzw?+=}/qI2F̪_vr5>&K%C=1TW.@TZND wY}aYQc>I(O LH%TT[V+[@\zZV;y$괞o'*a"rwnO)m YL,bBBk()JQ DDW֊(kY} #EdEg~aƜJxӟ7Ix BHa<<1;\z]ȲP0aG~27\׳'_7|S@P,Z:V>[? O%حˠ "o/5e홌|F(dAlUXkGkf-_hnK`u!;ݟIJ5WQ(:7ֺ!2C&Ѫ x6T1v+sR=MC`u7J2#;47)r22B=9TvK.քr*N Mw֧g("NgdXRP m|KlgmhI7kmpX+)?Ly2|w~~sѻzƹ?vpo5D{??{b~FݿK2H#GWEbȄz\2pxA(^-@jk֛~* H/͢4x*HTf8pa^s[q{^(('K#`UA X&QW8} %'ihV6/Y>Fd G$3̳#^.Oi StV(7Zݍ+1Lک;Sl!l Űe-P:CmJ`uVvL6aԀ,<0qy}k2Xo.B֝?t2C;Q>\C,ZVhktMux ` j¶l~N2Op%Ce t3 iڪ-Ts;ŋ5pDžnMV#ai$LD,[x<(nJ-"WiLB)ש0;]VιZ#U0. B&DbFhw=mQ,{xAhb/5mK7Πh<)L XĎcZGx"SrL xXfW.Y!;`"q%>@9 v Šuq,k\u[tsNDwP;*)p]2Ï!?%2)xyqWmB2hpd,B=qɰ9!޻@W"~5&p {@mC]=U YX+~dFwP!Rns0Tf da!d@TO TbJC"plkjy슇1 Eރo؈2MY6b˰酙îjCf P9|tetq]0vܱ9Xuc{cѬ OͧoM.*5is ʹ&I *했aTPexVť %/s}m1;#c 2f5sk$E R?ǿy?4=7gvoCbBXVCh:Q.xdh`S?3ǡ fXZ#6'՝HrcMtͳL̵r  ٞJStsM2P +7|g解,=lOAKǗ9@\ۅ#=D nʑ]xM͘3avI؎@A@p!Z$R ӟxs1`v/'YUC)ȮXK u-Rg4,ҥܕ^ .5D"@Yj6ݡޟyHˎDP'𺛅Nu <8&glH:YJ&j5j%UQؤcJ a YfTjDEB"V-BWf~?k*Òu U}T]|t f-k[޼K=c8:K1R#L[}{|gQE2F#чzl6I@\P ^adzˉPʢ<af nhMW@-|:> 4V]D.DiT!ld+p-b1ƞV3VJcŚC^w 1۱|֠m=!$eۙA;G?{ܡW+ 鮹Z,:OB[Fߣ^w e9pf K@0bVt4X(H&fP_2l1J=rl6K5$|8 wW@>Px>m 0t:^s~Gyg>*W/iXrߢxd ۸[N -Ʒ]GBE@ -\zRe/MpG&L! >baIOk4zUg~Z_n,5t$G߭ʫ \a'=faN9/IKͯST& _̯Qcgo uk5VKcw72X91}sC058f&X:ڛzK"64 Y0]ЛX4eK`RU.\.9a F//H*&BP9+Ox296p+8@;K2[(fA>g+2XJ(")]ox|t^&bJ %D\kFث$D,⌡5 Z}WowoMJR9-Wgf;2f\.q|Y&[6U2_AW 0.l++yc '<>쳯~է%lH])(Q^Tҹo~C0 k1D[$F=Z?q2mVW]HU?ƭ)>[dSZ3Zs$?DRtNjB.>GI%+ fPxç?GEAZ/`VAkYԐ &/QyZqV\dw\Q1zZuk95jAeeB``⥧FL/S/E^[wj |b%5?VP rF;b po6J]O~KϿ0dN O!/<_G=U@"٠㋓Â8Zh!zessXDMGTrq$A^uרqܰqtqAǾ3Œ I!ef~q}.Cp+IW-0?)slK6CJDbJ 8c {^UmQuI=5ˆ:MQzƫٙK{``kpvCK`R*_7Fp>*f," ך]x {k)f&A?[HN#/\ZGmdxn&2/*/_EhdIN+@0u" E((פP /#3ntu9iy]wIdIMGdĬq+>Z{HP)*ᑠ՝am(rX'tYQEdN>z#$Z֜E覷H[匎Ֆmۉ Ca@ZҚ~~yL~%u!'KvwNdb?u\QjPMTٰ.gI9F0_kZ-U] ?`]3| @v̌ T<\tkRf!2$b1HzbWW;pz1Pv~NFh!slh Q EFf^P7'7czCȿ'=02IA#Bcko?dW_}駒N I)C3r`HM5T/4 m4BŌQyiԏ;XAn`pDY=WژhVt,frtp\4[{7y!DÆp(Xzw/Df`O,9$@Y W+rzobz8it1{nu͂ld<}aHZs"~8Zw`ө7RíQxjW.>ZY7[zZ޿g(__懹}Toaus}O6yq 2f87vk39<9׉6,V~K[-]>t‚1`ȕi$2ǜX@}*Ďx݇!x_ P|^ʥыZq;P}.Bk!#iP9Clgh@akV|Jn240"VdOeM4a#j]_a6 譥]@֜pԵGk$k 5ssZovtu.s T5.9HMۈzԻ< \D:&yBN IDATo/[qc6^!ϪzYo~4u5;6e?TXɓYØcW%S6Kҿ3)r n|$;;#5zQo6u _˼_O%Ɯp-mZuN3M(|qo4cg)=Pi-o<`SG~Sm; cY<%V)s K 5ǤlNLz<֩enɑeя(|ܴMB'0r[)nTj{o&$u L;Kq}mp'95*yʜsjMHKZ`5$:ǹ"D7D23_߽s'Ev|cp1)iA<8lY9 Us{㭲Qg~^䓟_ &p_:ِEKۑc 9;BAɝeFC졝DPbB# tԿp;3 *ZT:SXL8=0Hj.Jbr6N%Ubegh}'H/g@ÔӤ!ֶnwܹH#OMK;%'E@ځz.awswcs!Ro}ALa3 ?I:QoQ7|9f5zPIbeNvoR1e kTMi2A.r[hndg?{+?|[R"\E>2V`0'}:ȏBhi}M%5P~fZp-1$nXsII:c0aO'kDքL][ L&ZLfbfcwَ~A8ojNcpHkW(4 l^5XT'/FvSm04"8y7qA.(x+FDwԀޑh"swy}>vjAZ0\ETbxXd߻ݣ(eLD?[Yh_|񹈦}rq֕?)!"-o1 HzcyI= KͿVPtxX*ŐՔ2EM7B + (DY(߈;QR٣Jh'\'J'""qZ/l-ڿ]3c,LBWڵ,C?0XN$ u2>2]1q8@`:9`G`,_cs}&V3X~ÕsjdP|}a-jS3PkjN—PڙvY#%xcͤ1~ Js|^` +0SaKӂ- SQ7sbRry˞R ʯiaAv(wQƜ~?.mPk!9s.=|t#a+7Se gҞI,7F![MO%cjW驏+|ъ\֨r3B5OD\8Tk3)4z|54*H~n0ޑHҬawg~ycSض1%}#k`Dяv*Ox;0اWg`[GO;5U6"nZN]=L֎~hJsƼ;SVђ7c?nW+4s-({Q ~\U?~Ͽ:#fY uiHSUѸ0!p{BՕ#-A9yP7>7>I=. ,4R}=D4%:})FN{h^žrND`p(a `D2ء(Wk>` ,TLIԖ[Y1yǘĬfsi j[y9yX_c!r5UX\zTQM(C %U&p7V&Z_^4"z�|/&{V\>x6ò/]fP2z#։ZdWH2"ab9gBjJq:Aes5ccTqnGV |6ϧ˱S/liZ%T8FP'˒T)M,/Ъ94z~y뷥Fkkcy2" KcIV-l̤qJgPMRgXfe33),R$'5Y+F"S*-h5[XsAup1M<5{1cɤ }X-̋|ҙΒ(jZ 9yF0"yݼTCj:o D>u2X"aM"VD1C05r݃$,3{Њ su {lMƹ?Hoנ:f).II,Vi ϸK3UU~^5l6Zoi h#7x^-RdK*WY{?(fu5T7OT ' S rizeSQϋ:#O:L1xn*/%#?ի/LJ67wqx@sx5CxFg Tf23CA`\%\4a(*Ke}@0%P%PCg>/Ubt:}5WUC&@FSȀE]hQyMnRˉSJG*͉CmDxZ7T7 *p*?c8@GN!~ }/&h0E=̂Vj@{[Fʸߐ"6y3|?~gW~Vׅ+_\l%~d0um5$ï\XZ̢gH3B+&Oi Ӿxn~鳋B֚cG2Aǘގ77' v.a|_^Vnیu~yjdD6ưջ)G81piнv$(疺{813Jg\1=y3a}(GjMaEb; 'gqV~Ԗתh=n {Jg+4@hz?P[s9oo *L'aϬR3 *) R="Pe;;ɕGP߉hR--ՄP]I|}ܜ Rٮ3Jn/b5X$ 1jpN!p*s苫]45 uTx^m-;ԣ<,8n8oĵf نϨM4L?㧟bk/,ʖ)F3*q n/ lI,C#V#m>ƄVS! 4xךsiG=vocgw=bDC+@pJzYϞlj 7yP%gl0c̵fH|7a!oC ⵘJݻ\+67 x=)i 0?DQcza9& s,j1ZXɬ J8-۵/[_|7YX _X:(:k;KW6en2<~vAhڹ_b6 fpsJmv~;{7ܞǣ>z;=0&j P}1iM W=1HATj4we%jz I)R@WvMq Q05ǜc1f: XxB;<[R6,b֥,oMDu4*2\2 2z yg#6_|~+x4P/}~jhc:̄HݎRk[g cppftVS/,c Rz3D s3N B̽Ry V1*j^n GU,*۰o'֔Cˍ%ᰏ5+o:{/Z8 ʣ)y#1Ɯc-ʋS j,σ+%8v\/SU[z]yKW3jQ5. p"m'n*$?1g?3,/?_WȥU!u=k@kqdִ"c񡩵9˼T4K}!T m԰z+ Hk 7#3ɚ OD@nFbmcD&nOLˀ ^{#5Daqx8Y'%*12I8e ǠH\l5EBM ~N>Z-Ƣ!WV:t$_dD"z0!JZ6Yy1G14WIbqpJ[}o>՗_O6Xɢ-%"EGwb>*j f@a%˻N+D MCxpO[v&j<׌}{yAۘ ˼=Re`icY)DE3&Vڻ;}2ru7'D %>b Mι81ŝ(9<{ .nP8{ V@45wdzp1T2"uLb1J_`O[}j`Sw)C$Ada \k8Tk.=< l}5BOU^L NuU ȜK4J䣏>BllWyX1}r DEM IJT^(c_[=tB@ F˝<~r z4wc׫g[h!,%ڥV"$DFAh*Pw{?" Awv4c @[c׏Xq8f\~?ėYz {n ,F=x_Jdl!JumaR\9G}|AdwGه0 ]&ƫ& ߘ52s]mNkz>!u7<)uH5iĥ̜ "&CIѻj4s"ӛJbQ?.8l;B%w:V˰#4̬{WwL~O5=35'߱r)%oJ[oV>쫯OnpØr<9̽Qm*k}Hh ZnYrFdfy [@\?=|_"m $. 霪̌#>2F`0eV>'2rǎ^n'=8u.\}܊>nι1KY:%LB]Dݷmd.H@~[ڝГ;l_f>&0R?B| ܢȲ[uvwZ],JJ|bQUׯ)I8$U,}~M*9osR^G95Dryq$Yvr l;?8IoHȭS| ׫heT5UQvp91)_W 11RKF"9M$6g:Nt k`H46$^]rz02t xleaFw=ocNr9P4 VNvݞL6TFѤ6㻆Hbw\q.]nQ']BD,KYou]eul7,fNԯq:3xRcƖX(RjDك,grk/Oo k:v 3c75PG%{&CN6@2Ԛ\rzͫaV+fXۓs.KfnTruY7K^oQX06ׯ ռ!aG+*21Sn~iB6DFJUH7gei2MX]d:1O9E_k;{!Ŀa&!64td}VX-\iX-I%qU<`ʹpbqj|@lgXkF`xbe5 J ku]W)T ^E  ޶y\M7( @wrݿ' 3$0mvi60a"GeNP>v );٬!LV^{'e9hd$[ XJӞW8} >Y!c&Cq_1iN2%B8lyK$8.`lpXdV4gd)̷1 IDATy~|mzڬ6`װ9 L[ |`Z3T3a#CE1ɵZc?m+e!NM#uvD@o[f5}g0QS¥>KMV8,'\wSdn~pz@Cm=&Y# 'AOS)9?8<S̈́GY@-(?;x]t:;p&ɡD͋ nVu9R(#ѿK!by݇Śt$#I6$&څzl}ߗu]r=qB^ "Օ1dRc;7cלD88}r$yNI [QQV2ϬAGr/rTމcxޓUf[AADzZz74~'0jH߁*~kૄծֵ5ǣ?Wܶ SZom9ŠqLsO%%I-N.O1}n(qCMݖ\ k}>*U?!gNl;B}K>g;ET 4˜ԌCg3?n [ ~ wr91]O:z*BB*O0w`YL*ᘄGB*+16o< N9CnptJ`NLMrF\`!s&6t7(Hh$ ԐA'r W:;1Y=?\l%Hg'DlyAzl1 ða)K.fBߥTmE*lΌ_p&'ӟo?;V824%ȩ!v6ô. xԆ F;CyKDoQ1_o EmĶdN9S˯jEHASZe"8-'pF&΁;"%? 01v]&XM&iVQ'%7rpQ4sekQM+yvļ젍o7+wF;8޾5 wyˇ)}§xCɚ঴tqǮK4LQ(Q߿U)SJӮ˜7"pqMk58A)C:yZ9ú{.jn(|>}I? . }ԸMAIqo|X";sE'kCTљD9݋gl6?L>]? MaHpCincaG\E.ٛnp84Nc;Һl! E3 qI?3tHH&ʕRXXi 5-](.3&ND(N\,$[juSwfV1Rk:%ĕn?9+f.K=X h2_/_EIdtϟ8ɤ=Mj~{ॼF3ρ ]j3i6Jʭ/3 wϦ7WD?43ѿ?zyN {a魭t8MW;{Ϯzc2Lnh ta{b5`RSO8W2*Jh7X;VS9J,xÒp.p|l ^k'⛓L3zNV|1Z9Mp6VYIzBÜe]i0 1S,_g[8hLU[r"]|p+lwA}wϲ 'EE+:~/,ZJVۮC?R[XEaG0^֘o0oZ >YW̓LGTw'UqYJ GѲXL*ӃQ!Gt"{Vjsn4h3]M/MU. 놡G3wﭏA75Y|ֽ !Kk,y\k ˲$v""FAŘIvQ.eB'ZvݺϟÉ ?Yӣ>W'u27:|f'x j  d@֥w_FA{Mʿy MnDZ7j=B"sjche`_2+ qE \g*eypݶs{_uY.l/چxy (h6GX ǧe).6w.cUw7@I N˓&~'q0s儱H!#wwYc֐e)Ӈ/8&9g\򗿊"W^Kwԉ_K)b$G¾_}u˲,,ˆ_3 O3IOKvʿ>wCwƍR^'ʹ,jcc^p6ibN9h"轝 &9 f9;-C-} wH.uXV`t`AdviMW"\s. AՕ ӰD4>ygX[T /ѰteU MCnf'Llv<}O)ժۧe'fw͙T[#3&źIѦ8mJg'jw#I%}+%sZ?%"LoӇqQ g]5Wwm| 39ZػK8Z^ˣ>205 }D$bVZm$Ps-|Nɹ,^=z*k̅Gߤ  2i MdɈmvd< 圳~Й\Ոj&7&/@HTBS!S,s__|ūׯq^kkX*%}>-*ԩos;3_[9l+˝'?YYjl:߷m M9Rr.tɉ˔IiLzfL]/=m{]Ѵ{yK} fʚMn*p~aO?26nN:v@"GXZ+aL@b;,HvZ}5-˺pRF>[S[?8UIJg96|SnX2;fɞ%d?\3]އɢ?cluDSΥF"_kY]Nh/i&plϬ!q?}8'_>Y'Cs9۾m߉p 0V`Bzp)˲,|!A7!XdmA?& 4۶۾k)޶qf0buV"ک2kuth!r]F`P :m .;<+rJ@D9aMV 4М ;QCHћ-vt(-2 UwGE1ZɕYCs9ԥK,_ZO_K)\sJ7a3|~!$c[Rm;%Dir:5W_mƁ!)-l[>`z3&4[hf=yBj<?V{{uE)qQ~P,s9yM6CB7ʸWP!WDk꽵. \]Y4ƃxﺢiZ 9$eMN<]FຳgYʲ.EUR$C3gl{立*nɕ:e>X[۷6]d"i& /a !qpp !kWj7@HişX,#Q(8p Ҙ0:tK΀@X>33#9:in[*W\Z,yIۑf=Qj`SkW4\r)%{l۾lfA 2_ cZu)s;B/B=`68?^&0x䒰&}2}6Kԉ)K\'aM8s9e#;*zpE֝}؝yx<}o,l-ŋu]VZr?5ik`ht7͋hwRj[oϑJ1W8KߥRd-S&D'Pݴ%N (F40Dt"XJXp`)xjl^̝w kD7qM# !HF;ߨ?U^ڈ^բ.IJ)/^XZ3٭{ÃѶW&e0n[ֈ93jM<+FE~Lod I :nc2NxZt9G_3)`XLͻVmC* &{LD۾׽J eZtxK~ŵz#w_X>//Veu ^JU򮬑9)i6QO6V/YjX5aƋ@;>Rb2<7qRĞ}ё?$q]~kuI͇UP((Yf}oMdG{i֟_.v6t2HMMKf5%"NΉاXɺ%%ܶm{l:ǭX"+f)&5w!,uOu>W_G,zyִΦkڞR,vnq,PWe9?==&*@@С3ZsY9D{9rei^ԱnwLg !sQA)R[nj)!!圈r MP1_zz]AslStn~z ًyvaKwЄw6FLѼťގo uA.MAъ{WK5{/oDs/{/^ЭT- HIvN  SJ!Mk,Dve-~%-x/uYxީ37X q[};SJ28s %DCGd銀f "*Dִˬ!gqoج :K*옖V),L\d)PFj`k⋏:NLX'V˲cn[p\{m]ק۲kӹӗUM) p) fu; E7,}Un3KW[b׸i$W4mbUo;Fb$KNf#I9a|ѝ[iM {+Nmcm2,BKnuKe?q0*G`Q2a}?6"ć~d߷m]oORKw=itˇ<#%,ah?NɁPk6vԩJD|E\XX1BE(Ms4o!O.@G+Ϣco6U@39&9B1Ìt._ʾ=._g]7KRr)Kha'o4rɵ}7d( "B8}Q a1ڮ˗XWTJ^׵b}v/9nO|fWyfofkn5x]̓'~r9w9x /2 IVnq]s~ZOzSs |#o4-">b~2=W_YWtrνط^x.+uؾi4y%xuWj/ؘ!0!sJ[g/C]o;`D!MJC苻3 8Z;eϽxf_ߊ9e ΛNng[P8|G 4tZRڲ|*yemu۶V[3eTy͜n-|J)I`^kaVĮz!l2ghMr9d*d!E;mmK# ef!KEpYyrǤYEӧY:} 7@veV 1:rgMl%ʹ|;i&0!`n39C;k1;νc }tzT'nՉ8F>aJS:A &ܶT}v`?pDCi0K}+NtxT[/M)@s^m E!C12;N`z$p 5OdÜ c8&P"^#opHSމ}i@m','^וT0NчטwP;q $ʝiǼ;^ج-s'N)7u:WQp !BbҥsxԖ@tۺ Erxr*Wf6{qē*[偩wRO OgK5P.SmRJA,)%3D嶮%w漕W?/ 7ia;dDH@iA3[9F|h_[Dg#\9[-txx'57i0ڈ4*9@Mw5Og[rS]6 ?V06BДj7~D\RJ!V\r\yrǻǛÊKW.u#ٝmyƮ!\ߒZmK6FRʉc"7t5w.Y3Յ4@nb|Gw*FV䖛1ya I6a0k*BoRV,Vѯx}E}|[X'2Ӻ)eYJ.W@(\:{$X`'X+ݧMf}km9/w.:1 >mԸkN sL c荀ޝC}wҕ1)^g/֫prePpָ0ǡLaS.%@^q? ~&malʆ_0s"~t)o~܉(v6ԫxʧmY.pn.^9h 냩 IFDLeC93mYNcsaz sˆma>[8@I}wQ" 'ِ|U&LB)[QF}|۪<燐`N՗#)xl۾MS0Q'v(nO˺HW1 5I{IDATtdy]sҰGw@SѼv;EF(97խ_YH|6S~qc9,$ a&<=QX*fQ1JR;ư3!3OA4'ĉ~ |K8/2%ڲ,o{-\J!ey/:QkG|6ûXsNGLDDs6LLDT wGId#YFTXI>x}GRfg}uqȲv}|.9E[V+mrM}dூ⍕79InF]uY}߷mc* uT Zkug_3Ԗ\ާ,o២2rx:3 b|L[SV[JO:'I$ 0YgU {7Nv;Wq,?x!:^>3x 59Kk-aZ֥^rNf1R{hhqv~{ɞQ=.MNSu9"&Wَ i>)%:Ιw|uNMC]~;Q?},ٙSsdAꨶt&' ˹ף\2p^K_/x|#s2?W^}^" f/rηR~'ӭZ&b $ZhPHyK?]d.uFaOzk2DN qmާ9,MM]5̣Le)-aڶm6Lt{*ֶm5mޗeɥp)8U,ńZ 5"νP lg{ .T/deg/;~?vq1d^`pBݫ畸gLp#eV;yb6LOh:{O`{+ ےYcNôY׉UKZSUW"%{Vigre^k;wv0SƫP%ID43,1?tހcRJJZ۶f6ƒSKTwR#,˲&* |VWWG|/o?{ֵ5 , ԽޖܞJ)}{ok.SrJ 994<}9I`X+);S ss? ul]"}bޏ߸z[ Gӹ);'f[1ֺj g/8q՘%cSb0șc߾~SLZ[S+$du]}mzoK)1E^¶Q8t7N5&&%Y>슼*/R$ .xsk )n&.O+'GӔ|m#ɡB3hLXxcfb$ŽV*WD3']Ywj S\ǞLbB9\C9zӖOia<#H Q(! u s/^ Jxfp3wd@cR-~qrK,9%l'DDܟu'U7z MDJ)e)z߶=PR˲WA=mi_ϸgTEaJKJ-;QZY]SrղثL"ϣuYQj|*R kyv&Le:imFCs?C *M "TTbsJRk&'w`~BgsX$ϤrɈ *gaJ9R/g*/_>#G.\lUɹRBZ~SZ%F,LהZuuȵ6 9*t>cدߝd򒥹4<|A}֧S~$٨ޠ F,i,dҘ9%꒲7$y9#8zw%=o~sȶtПW9-Z{<۶!ⲮdtWkJ60 `oWicXOah G^󿀋ng/jY#W տ{Ag.72h 2'?;5A`W:[HI)Au`yDr#[zT 0XꦄrF@3[F=NT%\ mZ}{8ָZ;ʱ;9\F^>a">?脦w`k杜]gAi~-jQ2wOs*Jl,3x~kelʏ ~d"^];Kf|;n >]V۾|!X)xlSbɡ޹pٷZ̊F(W=ݗ{rGdۀeIu#C)7Lsҧ&Hw =@06o'?$`Lq&;2]ViYP'Q&t)ϒ6ڣMW? N(΅{r1%2-#Se]Ȧ&dejTkE R )=mS=;þcK9z[Oo |zy)\.e 9I RJm!`)5 ܑN=|uu''2[qNomT)Rt~k̺(!$ÅNID}`s6+@zo׊${&y!s78LXr->ը<'O,NF\Vp)˲,^k[m*K^12XMڗ_~,m]SNt*K(3"?ѽmbr4jر;DEm7x2mp$N|{:Ҹ߰scz~&8^%SEesh*+Fr96Zy{Fk^uYZks_>uX=\>Sw+C D{Ol$ZUI^m}E)%[<۶K*5<ovY;)ٞKo[l:]Pju gI.3i8UwK4XS=ĻSFsG9OA]l=QhȟE|AG |oa4 `Ar-R-丨f$^$VJx&p9|Cvↀ.S}З}aT=w>7>\1 ?faI9eYQ????r:~?s.IԺLRs' >U-[ (^sט'l+0ze~flo6`̄].J/}0UVuyřE,sV^$-s_~]>%<_ݱB5pyqg{յdKa6=36oJih3: 'ô bN=s)5B%wmaxRzV+chҁcq_˗V_"|/|0Лʲ,H` O[~^RCgrLtY8ZM/Pa@B|?æp롌L$0wO#6Y (R{v֜s4~pK/#8{s_c+++++;%XYYYYEyeeeee啕UWVVVVVQ^YYYYEyeeeee啕UWVVVVVQ^YYYYEyeeeee啕UWVVVVVQ^YYYYYEyeeee啕UWVVVVQ^YYYYYEyeeee啕UWVVVVQ^YYYYYEyeeee啕UWVVVVQ^YYYYYEyeeeee啕UWVVVVVQ^YYYYEyeeeee啕UWVVVVVQ^YYYYEyeeeee啕UWVVVVVQ^YYYYEyeeeee啕UWVVVVQ^YYYYYEyeeee啕UWV> <{l}!Kϟ?}'SDt}G/'UXY,=Wӧ񧒘9"=z~ ~~j}VQ^YY^Ϟ>{gO"" ? !" oOGx]Eyee/ӧ= w^#""D@DWi&$ ѣ7XEyeeA;ϟ?w3wS57 $DDHV:C\fa&B"fqpo3WBHO=#>}Zĵ}Lps22UbɀgxoNIoϞ>%FDޕ@dfZܛƌsLDD"rrkRR 1 3"Wx/E*++y |K_wu@r`朳0s/U̬w"྇LzF2@,]sʹZ{aMpi ]bab@D|o]Eyeo}9Iw׿GH]ܝ(< YmX?n_\u90ͽ+F"1 1av%FB' H 1 e8"ңG?z??(mٳgO⏻M A*ʈn^kUզUB"Hy=<meB PG O/3O(!xD?N^(I u"0#4ZUuw15Up=pB_>HD/ 03M/|ѫ(/=?j"Iu>[{ݽ5mZ)sb PKE3zU@D^_u.r|~`jjfVN{ |Uc?&mb%T[k֧LWQw@ # M;yfJe p7'j=*\Eye[ST~ՋI%7 \@p%6$D܏z]KPISl8 smYs8 9g7swuo,eb)mIretfnlL[+jkfjLLZ\{SQVkN)۞!ï] 'fIڀoghUW"7 f!BBhj?ꪵZ3sxh.b,28 M! 31'5U>!̜rJ$֥,RjF@7w07vi34QDhygSF6$"&&wGDf|afrYJaqq9wRZSm7\7) aqst:VܬõG"BbJ,C70<c v#\<|Goo|]]Ey%ϟ?{ӧϟ?tcnvv9h-_.糚v#$f~DfJBޏ=-~>?lD@EzoW{qۅXUQ846XբjfGx-H>L$"6_֪7kI{t1+S^ ef6f_r<<_J)q5ùH(Drʒ{;V[!JJDh0n68nrJn^RkmZhqe.x:/܋t!ƫIX@zWY=\M1YD^pO} _jɟ)xc ]Eyo?ϟ=}Ǖl9PPnxގ`fN"ÐaH)p<ڪC01y9qqFD(r:qyaL)Exm\.RJZk[kD/1L]Ye/in7Mnm6"TmRyeZoݶ̬ ,b KW*&Reovi[xw{ysw5B"8\LS7*!p8\.Zj fHK 'b$ff"f8wN_ۻװfp،ѓ9EDצpS wŒS9$e)ԥM~\PB:`Hhn}πp-z |* :!ԕI_mVJ.hPfpS'!O~OYwvn0ʫ%+ tj?e)5֚ER 9a/r4UDOU`wH%e|-8 㸙q"h ~)@ebL Wz9qD,2˥Vãi f|_O9/|\RJY}^yY$ !JN=ax(xnpp$%yDv3",~!k7׾{H*˲jq0LlD$SI4n[f9'IjԴ|ϗy[Ss\.ñ"q1%a֬{Ori䔈9;vm8MiH)fp< "il7nG̭fzdYD@w T0ϳzGK-ZU "%i<  kbI Y_ 4Nj)MGix88m_}՛qDmr:_ŋ| >ÀA$Ip98"=rz̊|mym-T_Ϭ_A_/3?ԧΗ˲|a)kK4nnpr5#O.{n6/bsfLĪZM% zXm #H ՜E2jx;YV˹0 9vwcT7ZMyiAΧx<8nv}v]v8޼F08Hڽ!z)7RJA5x91[?4GϘsDMExJrJ{(x\^2K-(,< y3!p?ϥ,K)K)ZXb`357S Q(!lU݉~ˏ?z[Eye{//{??M2у|qۏ)"',L$]3&>ޟX[3eMZjC=;xPsRi6aD:QBv $B=MMU7g6sJ9LbNj.-e^%‰33!s򈾓iȹz,[@Rxw'@$D! UiA].g&69yY",1J-292My+""T0,geY w}ϗl浔'!Qv,9p>fm\ZMnv0y`(K9f"an6[wFZ>Q!=9:) i-LuO8 ̒r:a֖ei)"H4$RU3EĜ-HY"魷o寜?˿K { z0iϿ)Նԗa"W>Edia0x|]!)yWRzHTkm-dII8"[^/vی*K1t suw"uO"0 i-q`eYSp"ݹjFӴot<|Tݬ}"YXBoe qnǩ oୖARܬ'4EMkkZ[S&b끦NHDRJIvl|9REͺ-"C!"#v+6]#L55ngf5 p?-_-W~O4ik1DH/sWs1Bh6<< 8MfjeR`Ko$ҫTK}]Niy<>V[p wsn65m9elR lu*y)R)RUz o ]mݬ-Kkpww<ܽ+[)f|ޫ*uSDHn7|hNԦ:E0\E˲O|>4f3C)fiSS7g0oa|~~?]+啕,ۿ˲OG7Ys @ݽBZ={hT@ ܼQJB$ "DȦz<ݽ L1 5US6(DF󼄻jf!,Dֻ\ᵖ< 7fŋsD@`&"!I^rZy>D8s anNLЗYD;$LH}/]@:Ԙ=0nZZqN;Zkm}%+1{+\'$"FNĮMê%AJ svsu y|;v<%|r<6樵zk nvYq<ϧ!9IVaɓ'O>yX,?7_ws>_u!'bȃ}^O_;֦Mualw7r:yA0i> Y#@ջ5a9"J+}8[[n`uDڴ~RCR" M@,"bYwu)a!Ve><,#5Z$四5/y^j~a ]Wuȼw9 3" I+ @Z[)@ DڐI͘$ Q[Z}Ɯaãx"znws[k|>Ny)JkFC/y$V0MԦ62Ǐ __\ud{?7e&q6ט48Hg"D@@qI@n71KeHYSS qHz~G5RT5̛itMEtjaїJ73Zk `ϸ'FDUmji0w+O}6NI1I6;QR!aL59ܯp DBq6H,ȜRJ3#bYJwD8l/Ky>`wD~8U3=dHC@l].|<>dfЏ"ܘUf`MLkH"H$C$%KaΉ$'iH @ 10f?mRh\/i5 vn8tD~ "#3"0 0LZSՔdn|:RO-2/Vj Z%sHdM-%f&S_JozBd I;L@A`s)2RK]qqirN}r!#u]loymfM(ՔM)'L!/f}03LBn}U$3mZ+ut!of}~d5mMfԧ?=<_ WA"D:x!q;mq.si,2Z`_BJLIRJB,ca)H,᭹;xZ JJ9!8!R.\'YqOM23iEp݃'_{0fOԍrn[@08N8n6Rʋ/K##"|a)ɐb(j tfbĬzq^fX2nooW ~am_\ַ/s8̒@D̴a6d]a  BՐ( L#T&"7~Zoµ%ҭ1N0m7,Գ< uAsw^0 3 @Kռ4SpG$$]<;4zrY?BZKYJevC"(t.ԝS$n_}DBBn:/KPR˲,5hړgs#{g{@\4ݣ8"Kͧnו}r9RRz 5*4I9epfv*!Nf3n% ҳ #曯*++rkzQ$€jckW5w95u@D9gi~^Qk10D*HֺikԴBJ #\oa24Ð[3̴D8!`fDr/meYZ X˲,e^j=_ښH7۽\.p:w{xŋVU]~ݩ˜xT ܉C|o|_?C"=gL)"%4˜(H_\<ϪJ-RK񆌫PGo!_CaY.8,@$ =Y0gmiA5"L2{#]JЭ( # R*BR 6uAGgqLHZ5"Dh˲Χy(`JT??.Eu"P?:D%I=CRJya{pJHiy674N)IS$8菺,ڻϟyPuaV~/_~7xpboɿ9oYK,<}SBוkFE\$nD w9*E? 3;ຖ@f"9OmkeTB jZw=e1;z3uUM3o1tF̜$#O}8EvwDWm"anjUqqJ9o͸Zt\zǵ_jTYLuO3}yZz5ByM6U3@$yY.y) З\iaNf@J쪪SWCu!635+Z>iMI$pP*YYF@1:4mf~ss3 Cj NY<=M5yq t:ZkdRJ9889 AV~]nf@@i>|+NZçTǻ;v??0~p:R j'O>y XEyͷOկ<{|S HLj_q4BjACw=bLØRPbbVK-D'\kSUi^꺻 }D/^|T[#5wC> HH@ נf3k/ 2tRf}in}1m6[nl~?-`Դu[wQo9廻CʙnnnRJa|:[-njXRnUTɐ`iRÐhV+0 ia$ɧ|:]4MRYe.epV{(Ro 6]R p~P 4GHww/G~tl. 巾oXVo?GOӔrJC@r7뎫RK)fI8'F$>)G,K9_.M[0NL jv=~!1o֪D2 YD (L@4as+O*`yo1#HU"&Jiу@""wuOU/rjCV"kJjmRd/xww<|8wC]`kz>ϒ4x:͗t>RN<|,6U33D\ε6fljyj7gRT|K)}C\>||:9\M=|۷ U큜)q{cHj繴vڲPR:KD H̽-}u73<_9.֘ER|9NØaL)p3z"#F])[k&_=~~VQ^կ~7_3!0jI755DsHI@ei8ø-i)Ḽ{)Vj`XҴ&>wiHkGj@TȚ_~}80jȈNu"$0^wO91Q==]MqaVri\}f#HEXDҊ!m_w /)<|ŋ%`b70ī#ZS5 sf4˴dD\~1IJr>ы<_n~ UMLK_wi,c?9YUݍ(ӞPHr30Ч)LE8dlD(Hz$d԰D%@?*{=<7ڡt 2ﭻk/(=qo!!t"f "XMDz b$D5)E[kfhݦ\jiRRJMDb̑LU:>BM@UM9pWZn9i7wI7G 8D"B0$FGWz L[-vqx~sB5|«ׯ_|9DR0+&HFSJǛl8^eY4Nye"zH31rdj=V& ']8NS_Ϟpu+!0sdVsĦVʶ?r[_32r4p\tя~ 977twj57}>ŷEp~w~w~SWT= )!cB Rȵi/Yk灐]\́B@Z+3cgÄI/׈L[Kf9Eea82@EM ݥJQ%F)uRA=:W L~:nnzH 1mCXvH:e1Km-t,Υ!`ġjo=O4S8J뺭j';9BU}3t&5R~n[ < ZkuHwwձ6)"mK1">zUv= ~7˟ڢv<5G?g\t֚\.skM;!1q4 7@Dfkf~wv MCDqn̜bL1]vM{dN}yOn@0sfn։!Sayri9fVwaߝNnlj:١/!"rL=TbDŽހ-qp[;8՟;AuBӐOq?ÐsO☼#&z;ҕmVJ{\[C>GZ{-S0f>{f歵e]uY DsD}GvGuxIZh*1כ@O FPx 9Rw?l[ X՘NZk)%,G}SfO᧴Y~)=CBWu-hȹ~Q;HRZ&bd¦F"Bars;Tm]ME'fzZ5ShFLUBp3!nc\m^ZjGCDdT#9 t D DBy !oҤz>SNCizg@p;T&rQs0FwEp$tm[yYD LH6!D77!a`ٶT;<Z'euVrGM8$֫1yYg^~u9_:503zb[97͟/~.;o~ r}neYC 8rJaC)u]׾F1 D:0F 1s;3`zD!"s &wm5=df !HBL p:3`8 hH}  햊("&,&*HH xexiu+]_9p8v>緷99K~c٭8q8eYJ)6uΜsNCJ9CH1mr9_u]ڿb0v~חfV95 ;" C>өo%"<ϗ:h꯼;t˷;CD[)1c9ܱ벼޻벖mDZ3:ۯ/_voO=ag]M k/K'|<4cO^zsw7/k)oۙCJn?< DcpsU㹔r;)`L[sT00Cg X:cNJk{` 0iRJ/!cSVEU6||x)/!f4Ҩ*0Zy>"掄)&pt _;iֲmۺ㘇!1۶@fQUϟv>??߿ˇBDf*ciyY|є4M8vZ'9HdQ% ͬE)4гE t )jUW]BUZzU@T<>2ϚRbfi#Bz9xsrϻݭI18ϳӸnf ^FޯGy[ߞ?o|뭵2SaY7'ׯ˺m%;!krqN\ite^>ӫ6!H12* 3CS4Dp "VJDIu:VSuQ#b肪k;C9FG\U[KC6vݛ6i}d򔕁)qwZHM  ?:C>ԗN]/ 2M7 df~|xxW7777iK1!nZ؁/l&LsM0C͈YTm/z>HLL)PLԠ^LSD|r qndeYuVO.15&MEUi5[KXsRKf۶x IDAT֚@ЯN}Jyߛ9SrWƘ.Ki?>rpcqؽV@pSi*b*L猄 E?'y6Uqb]r:w*[-){k#,b+tƘ7_mQ~{>s?{M)|b!"aZm[eAœJXw9nok9yؕ8̴A:9_]gjU\=h=D̈m33&J93 BPIE mp˂@jqObUpt7xBc\#;PDk!FR˚C)]S\ԭJC䎖.e+t3G&FT"IAa"d9vqwSR@l9緷Э(V6&vii("˺!=Rٚy[ 9C)LÎ9:Ѩ&*@$U_MCMrNf,+8 3_cWKHfjMfݶl&nrvmr".8w?S7?|ucFAR1u];Sp98sWu5Uݶm)Zl\pfe 0sm[j۶Z&@1EBG7wv3piR)Ř(FFvL,k)N7!n&"Ηa&R <7Rz:vSu2n˺R9brRJH]wE@@}!}CD1&b֤aŃu"R`[5-nonRzⅩ|w:(O.pyt'[w u.cJyv6pGqa\׵NnR {]IҒK7Hmkm[עĔb!$7GG{iq2uBٟry]vq&w^裏c Ѻ̇a۶mjhOjk@D5'F3>wKKo8)4*}wR][cML]j)H$f)eÏx^ypwǁS| )pb;!_REK]ZDUq7#dFqKj26U;RwLBp֚J~5:rJ*Zp<u7T^bXuۊ4MyH9IAs.&"Yz8w(yy8v=39sr\拪xr:/˺&ˏ~0nw,"2ӰeA@(CNjC!YY?&t 5Dq?MjkO9]פYbSF30kjZ"O>{~͛׷z=nenO7'U;"Kt<Բ].~O=(t^̿k?/#QUCn9Z6 & MSʶ32r{}:}{ 8i`"s\ݤ ǘq1n[ľyk*BƜSր̈1bD:.Vʶm Ov@$0p/i97e]Ռz Rzt40cSJ($m-c'RFݜC j{3L֚CJ ܉VLLbꪪ63Ɓj` aRq8n0 9Gi5bpY80 BX|>m-Z+t8 k|4\M:lYm[.w4pVJV)P(HsoQa]Ҋsʧ\D}я岞N7MGUhD$~Q@4s}.@)Ā#d%E#80ºf܍L ygDq{G}K4 }(ljQDp:h!˺J ݛpm\І舦bdD-0k_{[ߞ?.jb" PMK!IHaG:;9ȁC =hڐ JkTil0>}VVE}aD[9H5e!L"3ҪBC5t"j8tW@D=wmdU1=cwycq˲BDi7Mܱe-V[ SmMj "P QPa:Q}/SsvWߔm]V*ǎrswgByʣSZJ5!f@Ҋ!_r={SޖETV*1ǔi#м,:X1PU5}n_c!XbB}@nF|S{[B?^JssjLT3vHk0LL"I%VcJĹqWqO{a_~~>k-ϧt=r|͘]WWo8"qPYu]Vb`8!,JW42"P@DpGCO0sf)/ Ue~x2/1< WR~Z?qFzQ '1fL7~o_'_ZU }63WAL ]{^("y8#mۖRzv{fLTJyUpeyf0Mx4J))盛۔2ϵl`"֖ad>0j1pmm۲,"Rk HBLȌfH(EXt@`AMa]rD4a8 RL1)ٲ,e,C`}w{k"B)v3z"b*#Vxy<R18@e!i1x8늈LmZ 8DEsVG=C[e˺z:|s8"t!9JH)Cל)`,"")o O>Eʹ)rP@"L1~\.儈!rgn/e#"/@H Vj1!i\jR+[(T#t.g|(E8wooV[O1 \KpS-fNxMxYu-ͭe+L40/3tlgZxYT;;Se n:X\jETSJ1ee11,jS58#Z`)0z0{pc8Np\DZϦU13"ƄW$3ؤ@`f9g7o"*%1l63Ywo4 ;/iw= 0|YC}5JD)ݴ;1gn7 C\]L4E7 NLL4P&VłRMu< uFG3֤U7"amCĔSm]mWC8/{ݞz!Z^~͛;5#fiRLx٫O?M992IILݎ|?ΟGo_&RJCyȑcaٶ寭=8pYC8&sWs&1p8bgwz[lQmVKٶlWy0B!ø~څ ]U 801txy)ǔSd"0fMG)nDf n1ۛ/^0xNؚ GB\=C۶R805j DUz6G}++o;[z"C!E&fnD&b"h*k8ڞ&ܛ՛׷7)'i M8ty*ՖV"z8N{ݛn&@_OfJ=bӈ!t41uHbV"j BDڄW!kk[)St<ݛPj#GU}xZ`:;w K88|NH)ø-utuwgϞwKt01!\Ln4ϧJ@T1O1aUtu?3" QZ›&f4NSNTݔ9;1#qD5Rڶ|>Z\.!8<㶖RDUByKm/?y|]Whwwwt9MZ裏3_ kkg?4ՔRҚ. }۳kԥſYۢSyͿ;)qcL"Eua c t:6o=(^5RxE6nݶmiu!n21Cdӣ<{U{z~{`M!tf_ ~~~mQ~{=o}KQ[ND$2yFM(q\ HaqQTCO5s1f)u[i9tSD`f͌{1o6R9tOm+TCnGn}n)i,lT5um8jQz}33"Ik]B7́<0Ep}YlYyljbJHy7M^J"UDJVjSu 1Ů3ku2104A"  :wtٶu[mSpU$0olYWpcb9vAVJ_:/1;0Z[%m8 b7Snf ijt?~@ю 1Ncj" fq/|8iܭ_D̜cNz;KD=nӺ."U=_J)1b)[L)p(er>ۺm[1^WL ԰<[֯꯾-o|{_ʗzQևǎ 6RwZc>/^p =!ͤCDD89}k-5܉rk1iDu+!Ű,Kۧ/_nnTö2|ss{8v QZ=3ŘT$HDt';@w@TsU49/}?kCE|_ʗTʦ*1_zu>_Tuv8S a]Be۶ZUE4i2 Zuu~@oSD@u/[A1%@ML8(=L)2_H!Ikb@حϞj .]18 ?fjJDE3lyA! /󹵦-0L)E&p wZJA聈D:g"˼Sksʹ1Nj*Ue^Զn[΃;VJ)aǔ8pt:Jk<>SE IDAT5RTQbRUtӞYX n8XEUs]p*bDۍ!6%R*ЭCϞ=)ClC!c̗fZ./^%]"m۶m>M0p`sJ~_օ1Ub 0E|w~"S<= n|'뺆D!nesgϞۺ.}%]m]Wp)cJ92_J"f=u]fZj=vøTVJorJ1ǔTT@zNM_K^8DRSVj!-\T`R[s|=C-tӥPZkU8 # :;R Sn ̀\"1I&E$&DU۶mV( "ZSJa'MT,!kJ)3H1ei̜r~1̜R8ayYٺ-4ܷ!DI&v~|ܶxw]e/M!UMU L/˙ 4Ӱm9D y>bH9DmaU[:y||xw>Z穭ݽ{+EK)1ƜsVJy?qgK|糶{[?_?ji&0uY˲8DJDDa[׾>XkϗeDIA%WyokmYu]ub< CN)0a@e=jjHy]B)>SM&Ɯc&@]E j)9LaH9d7V)b]z.˒󼸭R] = @P y?@lZh Sdl^l ٶ(C22}Y W#-t]d&2w+}%^~Crݎ%'&c"ͥ`Z>[mH s\JyneryrG777g" "Ǩ}rcl62pX!///ַDR䋛(eblO/><|ǻO>}yyw4 31>Z3!¾I/bD af͇ Ii痗׿?GF{/?=ϧK)ӶOO/V)G1,"Z\rf?__,}uiZj+:sΉ{}XpV{c>qLȄ,Rʜc 1xo w] ¿K)!#ݺ63p:kc SQZkPz!%e6Qwyl{SRy*V[BͶ;{S"dc1?y|I^9ɶnW@}G`bNDnj̢TRJ%j$%"a"!JSqsK>ԑXo90",כqC/ߌ/?/ɟ߫ٱϿ$ݏc6/"Y{˼,Bigt]}ۢ3f1/'[1ה2ND),,)b P3D𣎔PXr1ȆC Iň뽻[I&̒D(LnfGޓp AQ pΙ՝`96ș|YRz|tb;%gwS5"LspeT!4 31qj}rZfrc ilRSbbQJ&fS;jݏԈF&[?Nә\|9_-q=$I_; [6/gӡsC0Z3I(ɑEMZ [*7-ot7# Kʹ"L%(#[D:MeHr0ߒ 7SN",ȌLxt:ΧP;cv4 0۾1%I~AW~闟OˋaIMTǶDVkhthGHdv08-~?'Tow~?_0 SwwWZOK.9Ck~jƑmQ8e)%nz'㼙".ww}YJi]s(7B 3$D4hFLc9$Ĉ UcHB!KhjlC}ķ 3#e0 vo,R&"Awg[a荑dap:zup(B@}:-s};Ln~{G %G0GğPD)OHlHzǾ q#fu=ZV %^#@< 8/KI Z bdCmh7kN'<~w,z}}("b1tַm=,pW53/r>?$ wwo͟~(IR1} 19sJĄH|@%71suo|~F$iހ{$PF$IJq"dFu̒08U Hѵm{wȗb&D2w]UA'7vTʴ,O>=1!ic.ncbCCX8 ,jD$*c>,`O Owp][kǮmGoXrN>lgTܭnL{m 6OQj%%@G4<ͳIcLL %Ǐ f 3}R}b9vh-J;' 3gba1mC$ϼYS_o{sJ9h]חח Zmxcubxx}4Ot: "mW_~m!av8H2bcD1gX0R814t|FhURE$%Сڵw @[?9Бu&I!x:FqXM숤F-\v۱/imBT[룻7uG5ӾLsEByE9ajf$1skU|w)utt4Ms͓$!1)R{L("n L<崌aw'r,)r:Ç;uk֣&Iy"f7JHV$Ms3r:}'B&ډ$z]A%ty}_5d=tK@W5f#h1LМSO{Xwww)^˿wS\"u)۾/ʯ]۱0T<̬c ᴜNcZp߭^DtZRo[^-ukf>B?Y303I>zDa!p㬙ƦI)⬱wP8GB(`j6zzi=aS*2 s$VGw3vl |6 ю2̙ jisnp}yiGyy^fnk-W=zTeYi"DhQ1r2 1̜XBA{y}ZJiq9hIeYܷ6kmvԒsI)t\tؗi} e撳SN"c(8C H|?#r"85%ѡԵ'i$̑ԑ,\r.{[km^^x>hXQyՇ*"+"ZpQE8|UC$wOt^E$|Sw~03I+O?|^[kf#&HAue H~K/ro_)3'Omb 1&JZS V$CT{g'10L-G)n0 ܉(OdChjwB"1zmGAah1a:hN,>sP|ѽE)jHDnhN8Ŵ2gҔwڶ?mqwfE\&$aujvnvi <ݙM1HYdirڇqB)\i^ ا2 "+!|>ݍag' m]Rr#TTmdӜRHˏZ)D ;)/RrZ E^W&.9ҴnnW_QD|!;A#6Q!#)roJ6i"}g>"/of?o՗_ DǏU 8v5uG傈ݻwe*S)|>O?>LHsu\Rd 5[8Y ͏FK}$ ٪[V" 1;@o bq=@Z+"gkkt\~y*QLMS./ND_}d.e U":RJ]) niSJcQSؒ‰$"pi.%\AHA  9X#zb LDiqͽ:(4yoߢ"XXX /\Pc`4O"IkmFd71$7iv9%a@"7057u0@dT!J3Lj{KĒRޙ+3Zv3dS,,R2wF6Fݑ0lJJ#0 >Z;ZF'<Bz9r)wwO/[9dJzh#11sN|8>R6EPSaqǑD@{8 /|^ Oϟ{wwwj۶m13aNIZOGB*9V3f:TX5'A@&nM 议C ,l2"U^Ƣ;o~?(ooIY$HJo}wLn`XU$BrιpضkkRJ:yB5pH6( %^w#iDZ7t\Uq !pv1Dw cyJa!o0!SfF>U  p֎zzk9s][<ֺ??\09tb/gp3 ۹j M sN/)Uu-yb.)jNb45,_ˣBl*$"Rb&O2Z[{#I1m8jmsZ{M7$眑mKr9뺕izގT'n".)zӶC PNĪTP5D@U%b3g.ΩMQKx}[LYrNww㺮ZJO5Ӝ ZZk̦y|\.Luݶ.wwZ;' IDAT,%m1VkH=#RX5K &5Qr)G"y^N4(c?Z2zg|K)9gO)Vc0`$Q1!Z("A@F)fwN)'Ld޺41#fB }Gtĺ,,B1.psU!ȓ[j*;1TM< ![ΩȎKdN$4cfW$$D4?>ޏDZ$@I9O)w? B/"t:\נf<@ȐA[$P,x"G4azSp1Fm|>=>>׵z9%`YuYUE@ ɸEzQFŌ/ۑΈ:tOD?7E}{vf E2cWuݷ)(i^eu][m9É98˷TD֎[|[f|)i~_H)#qi9pN{&N~n+%'G'g|u^~:jmciD8HHy.F:v9@0j+Qj"gIbCuQS&r"_W]1:"L*o)e|sd-oeؿsprQZomC)%$DZ//7`IVID}-6SBD$!pPRTTSDv$"U#$tԂ궮*oLq/}{9i`8z9k:Son>_~hDY8zUUYRz~zڮKJnln5Ƽ@;\N֡֬qeN^T1`mZ5Zjo#&Ֆ4aHBKoK)8)x1=jy.yT{{J)Tv7&`G-f^@x=#$<޷u kJYh8R)v́NV:1|0R?D$N ab|:x@SUtu]cw7_G??D_m] L{=GlϻRJGkө^E[9i!֯>|xyzF, ǁ̎T!Gq#;"jo8u۲; F-@"W00b3ERJ3$2$Ն[{mnft:-7DιRz#`R icj=c?zkB`ѕ{c$sh}D[LD<Vo(@Lr2 4R90Uu߶[k̒KAHIRDUE 2;I":M$a&ُfn̩L5f0[%%")2t:-MH1ycO)se""BG£uCUQeyh)w}{?dָ`ai譛\q-^hDB#07$X HtyZ~[gp] (Dn9ݾtZr:PsDN>"19aC\Tm\?7E/GGߍ#93c_ˆhcGwo[,~ۺ^[O>ݧJ`"w ж///ݻw)>)rN9!72h,)mRkc]G%}cHLcJK&FNAZvԀ^[S)%iI "![G{}y֭odBn {o1tےsI>zo:؄t gG$ rg2ctpi[Ax7w "B&$"<-Y- zhd'f9"z}~y.y\N}tDط1c5\ @yS)w%P[Ǻ///cΉZkPVS0w05,q .5UxjF4N'3'&B)YCtZ|A̤'КDkdiS"7mbya1`,0$ǡ:D^K þ)QPDI/u=P1H{^뵏z}Ą9>/""^+;> 쎉YM4/Ȅ,H<}yz^eH45oǾڇbxԎ\"#ArN4Om۬^ڄͪڎh~:ZØ&PAT0Bu1zULE٠1L(!B O ؐ!Dk$>Fe'w`^s"F4f 􇙥,"ȗ r^zl̉(D[ZKIܝzah K"r,8&fa}9!2ϗz.TU9 /9|۾\kΧtZq%?#SʭRJkLRToo{7MSN [DrA 崔R^^^̎l]Ӳ$Ic}|zz vZNy^P̓pa Dv:\-߭*2 3^[mo>LS^sJI}q.cQ2M"lTWwIwO>InAB8mt%21U:,ww/sI9xtYuoPP8_ṋA*BrtpO. D.x_3P Xg?}[}git>:/a͢YCs.2ziXxly2pxwO€{8y>=h"r:6pJ9#4в9eff wK$Cעo %|}m:y m7ư8Z\z]DZxk}̌CfZr%7c>XR.AmmBLDPg@zKI)P \cz,ܽA2 0cР9tòq{`:n Nq9 s4 z]jkDı~y} \Zo{K[k`?73P?%§p(aEʴ̟|o8ZCU_%Zb))'GmL ĩ$`DDL@D`z0q)e" 8@"Ytއk5Tzr%amnn,˔KЭ^{S9vYU(r޿*qdeY܁!qtMPVfݻr:)8Nh x:}_Z3OΑhfNh`솄EAAzGu6yJe],ɲus̬.9!  &(8 Y7@ )q7L]$ x1s}.,8Ѐ&:JȈpk_t:1ضu=kɏ|80ضR;Fq&1Q#.qI{$6cTY)2"嚘!\w9@B24u[£} "*uԜ;Fn&R"eÛ7)q m[v{1>i,L5~hf "Jd]_}p Uekx|#GPSS+9z+MvkO4RRթH?1BDv8K/)fr#?w{S:F75/%!8f^)q>_Qyx/WNN77%ĩ m˶5S>HL՜BȔjDn* 9YZ'Qs@GT7}2p831k[r.XnF2>{zuY}J)[Mш s %1w~[k{m#@ {uk- 5s ~#!p>pi)- ̱K}i 'Ӫ״vcp033 Ddl#()1(D9 l[*jm[K̬Ժ.KWSr3"RaL~(>??#Ni]#Z!㘆 Fw]0ԙS"*; \ʉsN9g@F@P%s1YMJɜP[c267˩ #S"<̇C뽵qm<)yo*Cd۶:1W䋒\J.NS)KkO4OhaDt{4M)HTO[3w@bB$dĜ"(bx[m> RSZk%aH9~ֶ!S.S͝HDDH;W^DZfGVNRk}~yt>4\ R5ϻT8ϧrT앗YNLD1:G7CE"g6­붞7wG|,"B~S*D"SBI3hSr0x1&&2@g^b&BS5Dj0"S8DNJ,QCdmf2"K-fz+5y9RbnL{NO}w/Ե IDAT_oò.Қ8MS7#$JLw؛$KjTd v+9Oڐ뭏:ȜO>#6MRw_TLPI\ÌDJ 9OS%&%{c ! 9ֻmzY[oTjb:N]"}.'ÜRnݖm#OFRv{ATj9UrPQ][n3<לsusxLEۦJH ZHHUL sNJ.)91m!Z |1@eH!jT܅k|DHnKAm5ss̀Z۱gB1Gl}<=R޾^ Гc['"Tǔ'ö@iF`Ik-wC =OVkJ 歵5%GOe걙HJy=~l1YaP*py BDAts1$̐܁ KCƺ7)9 )S,g,P?Oo~|u LRn 9\aښѷmk["̜J%wp#RkʩNCX3F A`%#!s)AUc^&"edf^$&t>:^WM}YWS;2ހHE:!s͛nmk殇|yz. kf9%s1z ʔ).AuVJ)0qb1>=Ej8Ge[V2f1p:b>)3i<==Tj[5E 3SpT1!qynNzoA-y}Hbp hvHpP[pC!ĩ d)%w[#29|CK.TAN_U9$q?( %ĭm]ܗm0kT٧K DXۍ1FC r<mk ߾)%N9׵.6OS)5su Xf5&J1ޟ_}|wmr;{ѻ:F>߽!\f6|7f6Ղ\ܽY-
C4z'S]v[p8|8G5f20qEE֖뵍񸣝]o/"Rk0`f#D&˶2s߽}̐wԶj`C8!{vs`)Hx:!z%[֖ml]CXS0יO4MS;mYzkc|rǟy&3(q}]#mTek4j.Bہyt1u]С0"X `r)"+g03usB&LD ˆ˺SbAݝsuЮ>D̬R ՝h%8D<י8%H!# |>*c%rx<\i60w 2u]ݖ{|w@˺\_Vjծ!B 7Qi3R)TNSNz>fÁDԐ LjSye C!!28*w 9&B@BBQCRRX+@=qBO 7sD#r|#,l [,x  al9fh똭f+\釢W_o6MQO@Tm//C9TP& єTk ƶ !sY֗Ot:0#3{Gqc)0˝"3Сcx <׶<7۶===y:OOOp81P z9gBegxJ)1$܌_s"b?ѷR]e1R-u"9r8WS@xHfsrY}q ,XP>_#RJL#=7c,˲[|:ݻ[XBci ui'R!лt w1^^^ܽ˥<0Eكx a7>MCC9Q*9Ą rJ "Ľgq KKnSʧ̨g)1u$F|9_Ӷmc,B2tl=5y||ly:ضv;?^m]D42O_۶-eVDpdm)"iyW 3Χc=cމ`)iZ#JxJ( p)81va#箥TDoi97Dcz};F|xzáN%oC^tH9ח])RMY9QZ(!ƶ뺮Z{xxG?M$NaeZ5IW h;]^ yAcucJD.M׆La'a41q.cC XՈPD,p_%`LfÊ`#bx1f2܆?(/)PT4Rn߶&"Vj\9(َSӜl'8111&rWC#^w 圦Z4Mo=<5YSǩLeJ%Z̘Pa*Pj IMRpa9a[v5BdZIe3ro1l<'o߽KWWct5 *aJ)y.tv-mkm.aY7ZkJi*e>x<^Zzjڶ_Y9bX۶\KɥΉm}kRMdu)1q"i۶ԧSvD91YΔsnt׽n[SL-;Hn?l }\K5e2c[tww'MUKRyQt<cSε. mLDHYTB{~~F=x"c6&s=A&H@J4O: fC@p aضJ1"V*8sP13;>:׾x'+So~c@뺽\o9Z"$K))EsK 7Q +:q*9qJL !9(1Sb}j! p8<4isn*njV92r-r)mny\dZkvݴb*lf' 2zkS-|f?яGsmzy|&rnu%ѻ3bM}0чᡔ:FBZ;\S.˶q]׻KJ伶p8>nS)_|i{{p@ٛ1B l?|CRjpG"8DWS2(1!+TC#UcH@n#s.($c NwܡLw:)߯GҫEB9B7]_cqo;& "*mkf~bs%">??Zk}mƐ8386Ƈ~z2J9No\u#F3m[ju$0Ǎ./J0MsỈ*{^7"x<DL Q-1=WNLX6" K&D e9N%/j3#9%U1tB.֖[۶1%'ʹRsP*"!18V̥Ԝk-JVjeWc"qJuȄWQ+bG) o/~0SnR%顑Ts.̌9%L̉sI=ڰDzwF,^ri`jNQLptv܉d{l54MZ1i-|:_.w=3??>щe043A@C.TSʪ[S)2ժ&U;b.mQݍUmBRqRxxfJIռmD8 :OH$bL{|>><土_^TxYFy{庭+3ncB$UwpRp{"fH4k9ev" ? "8 PbE%DLM)XvQq] h%&}YknF:GwkY`P3BdτOȟg?Yo(g?OQ;8R!:R<b,"83Á@*bR.{;EPs4BpLHh9r>J-wr,";/'d˩NMӜseJG~tgacR~_v:ۺ~7%Ed|۷nwb, @HB|H8ӜSڲ20gBVD{-")"B?G1dX4$nfC^ 9ۯЁw(Ґ"OUZZ# (Ã斐_MA T$pWO~_zlb9q✘"N?B`@̔rAч:zJҮeH}$zcDANԞH r`=s Բ wW\) DdYuY"gtRJ 2|( 9Lnbv>ܞeǔ2 D޾9_4G!!7ۖu63QwcR2" tmۖS,z!n f\ OfuuwF N7wwe r{CZo>z]r)iz۶G'$Tu9;!3Z01n[۶!=u]e]V0χmHZHIxeBpUɵ{Q]KJdc8JR `2\'`x>ϡ9bJrwԩ֞^54~f9'@==<>?</!dV.<7땐<%6bv!bt8>⋯ok݇.r:v}kxD$.K=a_?~`yK->|4|1fw:}oQo_+ꗪhR#y~E>{lTUx PJ(2OLU=R L̴m&NLH"J)%'^vܓ; Q5*nOI:'a}x5N IDAT_~st s)Bh7sGĜ sە<K3 uLID5iDseH!^z{wRJ VL ̀x1Wwb); S1JTZB=)9 $Aч`fr2$6U3TTǏt8L4Owbblݾ1 벨d&cYV:d8x.uNH;' dNRJXzo]'!k5gfAYN9frGs~$J|GHkvGMTJ)9ޔs-rڶ }v)^rmWDuRk"u˝91T)°eݿ}㇯_믿>LJm]Eu*rvsRz#{a"L8;@ ,"Y㉣Tz'Hrsw>5 V UBDd>}:9WDžu8$pD3_W^Dru=4MԔ%'{L1a#fLt Zx $N ; *:d1w R2bBʣֆT 60 &R3d6̜ qNXR6p 3\qsJ^KjVC5utpC}k7~7_OݛXk9N9pnZ.M۶<_`LhTjikXT:F#r\)RBDzϷK z[͌S-)-sʏwh]-ϣېT.ut<惚E:x%ضu[{k@6:Ra>Dr}]3@ekё< ϗm1nSm[<~Ͼo~sBdyڽvfg=QN̑M'=#]x5 ӀDȺČӽRJ[ ^#s2!]zaJ >%RD($b{0%a;JE,)$( 784̚} <%XI1SVe4m5 75s b޷Bj{w!RbH#FB@"UĹTGps -R!B` "AӲn۶ٷ6sN( $RKtR)9`m ji۶`?ʣ Vq^6Mӛ8sa w.R뭵#R}ZɩRy~y @m1gcĒr;_.*nfuml˲,Rrp8.>|,3NRKNj.@43!\-죅]~ݶeYdJ`>3tަyBۭZJqB2Ӝt:߿ymqHp:DdnW79gtZi$ӶH2Tq>?ɯ{_nGmۘr}6nVU1@D]:ھ"3spZ5ӸS w RҶ5-tHcD; ѣ>k? ;8m@g{,rw/K@Cd77 9BboBʟ9΢[?<3go4ўELR)39L.c G iC)!2Rv]ϧiRC$ D-; sI͔!Iĉ2jjͨ1k O)!XPh11Qac[mf4UўR9s٬&9qG_nxpgL9gPZu%-"FfJl9f61ԡ#tS=b!VJj-K7 TkNK|G:[TfKo =$Rm9&2eц IJbgZY P @֭}]k+.*ZU*@H9TZ*^Or$ ѐ|Ϗ_/u] ""2cIq[*NUNiJ!:ZK.v.Rڇ>v<߿p~xC:,<=~{io-? Q}||4q|2ǧw^__]vEez뚠Afmc`@K tgi3Q0*DY@{͇ pAU/@Ԕ^!t9#̺:V"*Z;vfYJW?kmE!\x#"8{F50#bjKLFb4B=@eߞ__/+! vV&DDrĎ&3PjoNmOOfjZJE-RJ>Njimw7؅} f1㉨7麬8*Ra608FDN1yXC4!ur^M LUz.qgqBuB4a~y}YmtFD0 8L1.bf<>S'|r>1R쵢 Py5h篗}WC Vk}x| b]Ͽpbq|/>-Zk}&ޗcKQ#5U@l"oRj]֛uw>z{.bZqgz$-k*@p, ]8DB$SȒ!ANVь펶p[8Up0?;?uNt:iJ)UT8p29L {ǜ+iZsi騄cRj3e!XZ^.rݖ8qkR!rL#Q z&n" "t#[ǀfj )F( ˁaĬljuq譛|t ,e[HbB@qq|*֭RmĔc 9Tep|>"fC=>nD&)1sM,!0vaRD|ϗ"""۶Rs|vadƁ1*fBH!qՈt:J-}f6cq[3Uzv֚38 uEBtbb4Zgoi.H1Bv!ޗe})Ф@DSd@Q6aD_H[0>==Voۯo?gdy@ĺ."}V*?O}kߖֲo[N0 Ň޿j?!cpޚZm[׶a1Z̎HOZﯯe<38{77cW֛XbIGD=jy{I/CTu օ1l[i/)]O|z8Ѿo^cvFr[j#a8,˲,~GfC7vUZkTJAܮ 33g9iZ17":~UK)mcL)=<vֈx޾VqSJjZ ew08u[5=viϢxd?<F?ϟtH#CQwQA7= ~2BϷo[>\lGG@((/:lVӃ5cw?2̄Eڶ^}ć/L!n'R֔C yALGb@8龗2۶->hZ\1q}3^?<~'˶y33qifd"BU qLqP31}ֻYkڵs!Zk۶v0GϾs^UbB "gUߴԻ3g2GXe71?+?"*X@l*Go*s_ jB/Dw  +;0-nRGZ,~臞RQ"$b0ED%p:.DD`  K9U K!&jRfzޞe)`z jjrs6R 9&&`N5b.Ȅkk*l"#u1W"6Smc&*RJE5t';;]JJ9ٲN9Wb^۶8ݝ7C9?P 84ØՆ)RC^ |FQJپܘHMf0eYoe߇GLmۘti>Nti۾ڮ׫6[qćosL.Z˾4S)a9(@ﵗ&CkOӜSj rΧv}WǾx~Ǻ6P 0|a??R8צڤENr/嗿oֻwq4j˾'fDFssﻪM1;VM4s`:ĚJf{L|)@b:^7f\goꁛ;}/x&3#t#z?Gq B`?E mH"8DԙdwxC}7)_B |: @i}6Ge)]nri0)j*M"R}ݶuA#5Z`TxSK/m)puoōMR0Tct>n+~՗H"%@@⻡z%{r sk&ӻPC!" !|KP$̀Q |ǀbLp IDAT;xG~m6 6z#B"jD jn4s]1'U&Pf/ߛaNqirZ|QлE#R}ofj!h`Q;r[VSq>kU|8DB΃U8R$hi b`Lf8=CWgw`h޺:}+j_.|.[j9C`怈Ð{m])o3C&8< KHapzc Cf[ j\sJJL4T23#)}>yZI1EV&RޞaDRRyr Z!0KC@ ).^{tmDrR^ @)F6C)F !9~VbCʹ2 m]4/DO?| G|W0Qض%TCz'73[VUWUz4qm+21F38Off BS10E 8!P诩 >|ٷ#pn蒋v`]vxc 8m?SvZP@hBT_}6rU X"vClf]h1 i#C1XӦM>O0Roɩ0OҘ #-c+Sʞ?;toţ^nˢ)%- 4BD},~@C)('Pm\.yokmKٷqiBDF+0aP%"'m[Y =Pv(bE`" _y9+3GCdT-e )QorB 50s{Ub$"꽻 GDklL"!AUcp:^Z0 cΘZ%u9 Y bHa4^{y8ΏԲ֒sLNjbBaRy&/k|_ i:;ſa)B@051D<cwKH~S_ u*c8݈<cG.b ^1eON%Z/9[?馪w190 xx獗FJSAr{_JЫ cʵBR̭Zk)Z`֚9gU瓙T|>۾5r]RSo?! תH"j63Cai9v-i2;`Z80.8>>=e'ǘS:N|ޚ))qZe/L)Ŝbd-M>"Z=jmE;9Sj٧y95p.Q-eNGĘCpJ yBs)^^_8!A/}@o.t.DCˀL;uUAUZC9~dG#zs[EAEÊL| OP APTT[@m@ EJnjFrzw! `ayiDpXT0EB"F}4Rk-EzvmTHԽ}/=RѳynTU$4.] bH1a OR P|۫1/8ͼAze,dw38[mZPUk! [WQ6C}{kSh>MDEQmMv>ㄈBq||8Rn.)PJYz5f!@US)RB#3uY˺\nV[ EZN4 YFaN>~Im]m;1p"sPqe=q|4Nm/iy&[DmmH Co^Jk]/@TP\:0j3F*`^Tdtx\.%F"NV(e+x\It=@@fb\y""#3p L ̈Wz`A 5wB0#>.Y[s'{E[m"}:&B[;/x诵|w(cyZkqӮGC5mZkRF4$[k][#k-5bC1; tio68Χl];wM@J T^vW8&jsI6ϳn=zgt,M[5vhsv/UMD͍'IyȞ}*Ej-vkպH0Gff1DԋPB'm T#@|:!ű֤9DZ-R{x>c˺ 4O)yrN arΦm8$!E "!0}:O[ 2Ik 0 ]⮜lֻ0XDLy";!T:q$j1q0LDնzUqk\7&z||ddֺ߱{œf݀kA-csӣ.ȈV+e!DDRLZm#޻H=AZz7ZqZD`UxLCM\QU"18ObJ*[u?2-)f)҆bGt}Hg퀴3vxSߌ"juf1#4|݇"J?0`l]m~D՟I~?O~r`ٽn7 k.3/!4L?1%=x}]!4lD@>ef0@u-5|Bd7ܛZ9CNZG|C4N< YEED@t5!~!ۊGC@br*w70e񮿐4MA ^/W֙pD=Nk>ǐ4)R9&ֵ&1F$ҲU+C>{/ߔRuoIdt1rNi뭛}!pBL)'o]FbonRDzz0d_AW| GO@4-QD~x%BTJuu_@zWUm{DTz[;c ZCdTR/[rxn)GYcZ)99} ۾]/}یH$Ð*yRL۶ns<G1u'%_՟]u/ a]m `'RqzSr#= H@U/ΟґPCߏYH̬ZﭧP8ۋ8{ 6q YM{"08*t U1S=QK?ڎUR@C" UEZICksqmM'ŴۈsMXMQ[BBֻl_/ÇC̔32hB & CrzYn[7{<8BID)22)^Ku߫s`C4.۶ms1p>t:{2!2qp!ŁvV,{l )@[3tᕙSJlx^mo9'f ݦD ^J̜A!4mc0mYVQGKRL)$ĜSZSJ8ûLJRv}޻rD_!d^h@^BD~B` ØƜBO~~믿Ϸu۶RKi  M(egoI1,NOW@&8ޏ=nA/ 8t}BJa[ /oJK룷jfG=uA$a&SLwdWZGlD^.뺮H4S$d 'ۆ&D$d@,DXLHb)LiNt43y yw )H&;1b8SUL1nVIu][kab]GPr~y~}~}!}pCP]UB'_˺’h&Bp ?]>|r.DTkK@9@xTmm|^K)pcgWy@@LjpCRJzXJQx܂C)˲\ymP/b+}\k=h$gdB#D73M)Gܱ:i;?'(SYy \ԏY a@ ą#_`yl!Ԉc l3f+ba7KDJN„Cpcw-!'a^kYKN1LJ)9޺i3BETSJm^+G@ݙ|Ө򄘉"g""aDCw?|U[oS(IDmc ]F㇏5,wBpL,rJ:tt +6B. WOZkqert" 8ELf8@Ԏھc9zݐ00ݏMXȾ{zM)^E{[^1 IDAT̄q]/ק秧xXCZ)u<<}5DGk pPÜss//!Cc۶j1申ԣ +")S) ֻGﵵ eɈ>6z9SN5SOø )%۶?|` t臏 k:/MmDffEDO:% !vݷmwSί  1N4!H&Iry.+ZCtq\$ЁC#JfNhIF@l&bbՑS˺Om\xeef?'APRJOϟ>}c~G@{i"/jΧd,_q2S3+:8 '<+̄SD27vWSpb@FD6AdFM1| z7ߘPZ`f8ѫH/tD}O)2p:8"菱):v3$9m۶5Tr`x1>h2q4rJEQUYGXBDRJy u}9je-8~"ƌ9_R.%KI\J HTq$@=CʗSvZI$h((ݙ)᪡PctsΥKc"w .ŪZ[:B"]{""'doޟ_skkGU1t<r9[RrT<1󲮭ثȸ, U4+ R~wIfRJ) s)1ߏ8*"fKYXDr}}}=nCEWr!ooav ^2ppYץI$V4d̷z p<S)}+K~^zo)$P;p|~y&b$lCUPԀ1j-|P#Uy I,v#DCDO!M^$!!4DutWI;jnH}Hy_̍#\׽\QXJ Q칭1 8oxߊH)yiܵw&.˒R?‚ıO) UP 9Įs{D+>"W@#[kjD"5˲^kk}䜁DdCdBjmnǾoO,Oh=4O!5s޻IkMuѾZ.q~FXJiQNkkmm|bgf Z"dI˲.Ezﵶ{~f.9}^j8e8Lx>}ڎ}OO9gwI}m,Mn~o,P3+Mȶm'Tnק'p~lG;ErYc魇[o~SL|Y>ƨ"i]}{(q*ގc:hNm,8R !"j!gHI%zٺ;2RLf,HLgH#" ο8GD'yp2Ç;:f0n"39-Ω(bQsiСw=;h /Y$&2 >{rD9r\f:IS.̄DD̂DNa&aRu ~=&1Zom  __F`Rp6L|]JYkkGe1>t@Pp!\ʗ |̝{< #O]NHncJ)Rw"=+)3B) funoA}T=jGp ngʥRY46HL\J9/K5iT8ZC-RnSJ)1b,jAckG;#|.BH3 ѺcOc L{#ϟ>mݻZ{Һvォ*,ƮCKy]foom/R3 Y$vw|Y4FォqCkdIQ[x|f轙<phon:zgޜrρ?FhsHuB<[3'8QM=N-ĩYç1'½V=Wf a{9Ʉ>L͵6/XqsEoo'0CPr" ,  A0DwǏ'}w\U EDa֪p4q;n)! 4-PRx]qbpwxyOq3n۶ou)!KG'#o7ffn5)vRJ14ЌBP}.2c>X`:lJM[=Zt\iOH(35s%Io 8cZAbC)U_{WĘO%唟K}6w$m"o,ef7zJ%DUc[&@\RHYGlhmwՁH,DwD%ĢKJrYmӯu]˅kX"6%{2Ch!Kj'@'W3?|Isf߼wvDXHT7߼d(P:\kX5y# {ReYLV"1G8 {\"hob q0⛝3!<ܰma:S#9c"#1?')D{摿˿ۿۘ#`PD=2juH5B@KY"DNRzzzL%14DiQI8ښCC'Cd]xrGsk5VyAD4뺶ڄ/ۉmQJJ,$}ȄIq?=eYن"@"VUpt:"1trYDfsY'ڎ}]/ѫ?" Pqj}tL0הfr>nVJ)03<۽qu3q)K&rjuYuMK>ƸK,˾aڷ~`-EϽmN"fjq{8Z\{?ɷ)I.K)K늈8ƹɭ+RQ4VaDBR>t6r7ACN L!Q DU0h*8n4Dg23Lڦ-Ԧ0`JT8Qkc"R]+{=$wʘGG+E//ER,úC>q2jqL1AY"3}{{[Lb P.&SJa2Ԓ\zc`~8J@D!h脔K, +D̲e) Dn1t^)C'9S PlU:)`] t[)=6,$HZ`Bvu(LE#뒄"-TB,,D0Ͻ$s ]D:'VQ=z2A=I>fcjc u ,{뽵C)ZmF,\~̗'v{ vKe8̹ mnk"HQ&p>z rбݶtu5?w]OtG=v@( 岄_NCެ}Kyj4fg) m bh(<ͧk}+ƆyV3,z73NzZ<~ugq(8_ؤc`O!TѿS/E9._;#__LلD'cB,Xk &Fˆ"֣n75[zCi۶R20$x-9e%1M QDtn{mzG%oywKc RuZ/aط-^ 2fs9XNDym*^,QM4 :0x"!S#!R>zC1*:ks\@&fl,%3T)]p桽գ޴Q[SPY,|pH%m"2n,d4jY5%޵6\`ITrR]ZGG˹,choT) e^JY麮8~oO>'}weYsL,,?R%S1rJ‰sJK6u#p.KY P7fլժf1\R$sEƇ<'uÀH 0QyŒ!EK Dr&fǩۃ%8o)kJs g 6-9f6S¹Sm!LGU'GBi e/_`LN̍!&10[hWOބ^աz޻,0. =nBRJ1~1HlQǰq^gi0{K)K118%`cPI+Bփ @j2b,O%w !"( Kۺ)3p0uҮ1cWUbΥp*y)ZoTZcYeYbo6IGR^JA dzK~u^.K|)yy{{;ꮣ߷m}\"MucsYuE\T%6}^˲z}~3!e)?]?ܽquRA",thIbJ/{=;Kce< L@X`ϟ?]~u?3 Ix2FC\rmr 0p:h̐Yz%$2'0._fynZ3:07wb0WPw8!L}а"%Ϳ}u{ext~׏?p__?\7O#D%}ۆŏEDsH\uÇo߿8墦ooo{r~c'ʄHΒ|2sND.}km1FYJFc Dm B$@nu𜓹P !Kك%2ݴ\.K 4B !YhqH,K*KYCh<~Ҭߋ|kYl!kVc3%GOI;@<=?{oպoz!v} V &[˫m",JgB%bAJZLqLN>"TTJLFfc!"udu]N);)pJc/g\V9`D4FofeIfiX8T78LL¹;i{̛NB%Tmb Р  G|i;Us7E 3e˜l(?hJS%9Mr'bA8"?cP?o@u}M0 7$G}ܷ}O߾SJYRt\e)n7.9 q߶8-f"I)%IJm۱q<==?== mFx,B&l]u 56ZJΒEmwS_W(N?OE$ n6 {W=Б  sΘL1?g 97;݀BZf_vф Vrn}|[^󟇤&6}gR@dPkqJaP-$Rk u-@03 ;`@QBZ/%#Q8EX4 =aY么1֥D> K Uh8 %IIBUL@xn3Qr:6L,f1U6UBzث(NwZ2: pf?D #YLagf$ a}ւ De25;Rfj±m2ljrş _N.%Gm8Y5C1p})J=C$L<1 ֜YqG5\Q{7cڈǶ?ڏoOOKYͭ8 Racх)h۶~uU-9lÃZk-^$"f?6Z&2DGNȒ%{ {BP;!qtTX5t("^2)U CJvP;iDcw8̞71:$" 5wȢ<#Rg[w较ˆz ^\s /wQijYQ?ٟϿ7Plpg\"ǧOGi)vlQX$R[ {B::jhnv^ÇRV0) "Af?Uj?sKIz#N=!"QS"!I)"€HAw nH_J)N9%{*$!ŏ5(7ae{g^h)0|SڦQb?S(%ɄWE G㡑3@YU~#oBWۇ(g?RdG H~߬tuslmK3-ۍ(Aߊ$_f{s:C-Q毤aUWGdS^7D2"Qg6; EgH p˲^F.IK>ZY)Lk !јc{/$9c߷O1|)#ڢcAU5{Ե1Hi,e}y~k~Nc{@j:E֗ 5;9z&߁u}<7///ۻp1Q1@덈[,栭2?D\P< 2@k-"a3i) YGfInn, JbyfcDNͧJRP%}]xƛL˹8nG;0" Bk!l SblDqe PfnKS%,n6FFY/u P&@o'Xd]8V۾5uU3f$JW"c:%ƣ<}XFRb!3&C),-^ q9?ss""a"S+%3a aeJq F)k%0)GO lSSȭ`P\=&BS"VںKCQhncYPf6:fQHRyx,~R$,66oޏd@Q9~]1SJ s%#l⺮CjT9O zXnnN @NBU5Hd \p?X`Qrч&AN~>PW-ͤ('f|1LOz`fҺ, :"O)J>EgnDĄ:BeylG@ĜYU]r.2"3E"SsdĠGF_|R=bkRRz`Gkq\Gj6zwCGWU5/R̔02'\׵vݎ@u]P2G̔#jkj$)bсaN6n<ڡBu'=F7/bj:Fwϒ.KD8U9Zk-:pFzĝtq<B'!́ф?sÙ?=Q_"j;~8GNx }>۶}z0qb8jpKo]CC5vt2݉XQ 16q|I^^^X<׆m,!Ⲯp߶\cmv[mq9秔rM rabHm2#2chJ91?|: "NV>{j:=K0sP9ƜԠ^F*"8~Ԙ'\/m#N'SBrOpRJ\| b3"Ar}/L_Ss%2zo} ?cHI$@193ǚ#Yz0""b6":6s/T%G]}{JRBµUe5m}# K.۶8˻xO>nO렔uS&wZߘ`$43X bn`$4F'!Ę5tyNcVk٘ Uĕ[͖e5w(fPffB zDuRWęҡ ۃcZ\Uզ:˦xnBZ_(K_/T3QH<-)W((xvJ=O\\uh`@q?chsJ{)?-d9eJ;^E\J>zΔBÜG)LD$eYrN8F%R;HU%rfF,]pXRJ9&ס"GCDiK'=NЙrZ$-*́}LhFozZJAXLTb$ 4\l<B^KY.Lk'k)Dhke✓7NFe13pK)\/땐| 1`>[6cfbqG= "KDjY{|44CH޽{?AD? ~?}"岮f;Aƹ;FF|O΄nhgzmvS8.uAaĂćnR Q 57\.!yqZ@s+d|L=#dPSU$ &L!jSwioWVUWwoSQ/yn^j1>`: X2kjz3%>5PH"|6#F'?~>m__]8e;I,]˻wÙK)[ks ,E$E)"$fK!벮+!X26eO,S޻TxwDJ#Q%=cOwRJ)뵵{u# Y45) OUth G?_ĭ!  F&3ur56)eDKkΑnn;8U0KIMDR1W{kSJ"\1; #9((a!!v^d! %n#Hq&8WRJs)7J?~.e]"^Vx] "]!P컹ijH"GGBsp3&@OZ-3&:5%VUO0qEzkRwBYD1q 3\Ad5;ө)B_qOIfL?T|]jfX13 )xHQTJ0`!5pZr^^__Z=~_ʲďQkݶ}ngND)岬Lʢ)3n!GRҡyWJuarmTDz)%&'6_5ީ & 1FKg,OЮIrZ5snȩ[o܂e9eYuD91Spm'V9R!Q$Ӑ 7jbȋ$D,:GkZ bĩue&1ga~3I"Gmޛ }񞒻h@TG}gu# #`{m-ImBT8}pG AfZ{1T򲮅{QN`V,D.%&;joQCGJ) ^0so/vDv$qk? Yc RqJ:l @fF\GEͭȵeX dF4jrJY޹VaGlEtC9ju!Fd <]Pth[bDMTsG07&ɉBH~\b,>#soB` phF6Zn@Δ?s>xQSoi%͡ 973P`fT#y.9w}þ-R6$žzۭn^E}9bZUֆh":!}~{)qպ(LrBJ侷8ť9ҜX/ 93Sʙ#dHD*8|kmTm<&)%鹔U9)s D;)0SJ|y^kLrT{y9^UȁhwQo1S69+Tz^۾,|:պqzGc\lի1`*-b#9@lo=̾37uQ8/<ϭ[̮s "aMK&fU*bH(wݶ %"؝DABpzD;rcL$\a" 3;Va߶|gq.x>zHwQ!| e@-i̅U ah:h1D PKpD= ćyoQF9EJ>+N 9jYx,YpCP̆=Udf!ٲ,OOO2m{Tj3g3k~'IrΥ__fQp=̤׺亮s΄ c)HWL  )DJ)![̇W}Ki.%( 0S#tSoJ-5_6)QJ)!P"8"bn{CR|!}vD̹˜C5qtK{mZ - ȱn*2ϋXiS9/̉LJCQPt̥MYS zp9%"g#w>2qf! , #Ȓe^@&݈3[D)b ~秧KK-LSC2s,KIca"=DfF"Dڂl#>yh([DiYr)~n 6wp[13@dzrɀ$R #"rQ8>>]e6tmQ(]8dR.tBRyz ĩLCV̥Kk; .W@ieO2_R 9rJ8apJy9M3ǭv|њ JB% [4\3&NS.jk[ysOO_iH7N׽Z #A0Uzk@K!Z~LB[}f"P1MJtu]SfN mfvH9s2 {)uk8RɈ]jk˥CWP}!/۾R0t{kL}o_^Ns"2QDL݂ѥ|.s,KkU;3Unzߏ9EOaL?$ ϺoYDj&Q(d&bU[U9*rg͍S᪳MENR :"v;58rw;31zpQGlq|;2X|:xа1l|!QR'Ӝu(WCS]p(DV9|>,)e@XSJ,wq\rrKj."uODUb"P6:`要fr]}Vl*JH9H8d'a:U B)ꎀ"hb?H) ?p"f{M[ZmEnl%߶5LK (M%fb^뾹زL$}TrazH_wjzk@Uyf`p8.Z"E9Fb)i0'io$@ܘi.ez{}\l}\NLqW]DLR`I{5Uf$*fUzh!A@B版UE0A8T#Y[Ԉ(:K0I=1 D`΍(ԑWp})*K\"/6;=FGLįuчt@,g NcfR`x<62^%7rbƹJw?$NS)Wj)ŀ~w.jzpt^i^n+2JW=Hė˓6 rу40D&ݡr{JHN@ޥ'ɥ$DV榪;4OP sʹ뾭M7 FL U<9i*%n)(XiYfĸmGeYlu_{k-hs"q]M֖a`vNSWmo^K6]$k %=VI{ sиs)jVNz=NnoӒݼT1?p^k%y^Bq_*'iػ#G#Y#Sr.":9!55 6somR&Q3@G!3@#m1%M{+`%GNG'A灁Gx?{ ڄ֍ьR0҇9qcO G5 :OrΎv8Q_[Q;H!8nR ZAaCLf+9(#b\ eb&+ `nۺm)\14 7UN}6N a<""]ЎqB6%G@͙OZ,i>]$*mt<'ڏ7Ocx7K/SR}uߘ;,eАMhD9nm˲2/<D{e("R[i*wzMMejl9d912"X%X%f):岜N93OT [kz\%fS X;@N)٬L[#~+Xv.9%Dztå] tB)5QQ!b]49O|ZD#mvsZmݶe.+H8I[#cr8}Tm_mYN*;>==};4/Rױ۾؅S'b$@dUmSJvU3D}]Syz.Lc@q-χ^[663[5SbS 5H!,3q'\4Ȝ%GGz8`"aH_}< ' vq~z<1sH 3?cڊ_,$QG[jG$hKc`#Y!rm̰#~̙D݊#3y(:r4O[#pݮtf;լ).]<-{{R !93)-OC[Hjm۶ l{}^J!yJεhb 9E ECI+C[3\rpy܌hmJ% tSuSFKs` xZy:st2H}rWi|t2O\ҾH&Ce^RJ{7`jWc\'ΐS2RwQrIyYPE@p@7pwdr31%p&dJp붺}'yCako- u뺦LLAHLfqidn۾A̘ݴȱ]k[h"PMsF6 +f,Rʜ)%VXљ{kӲ6tě!1Äq[]XGc⿣&p>TB)PB'm??:gAaã㘸?"::>3$tFıΆCtP"I n8`x>2}Ӳm[=YWܧeYrۺ&Nox2/e:f)ʦ1!vj11I!aN۾ !g5'yYֺn~IEk[mU3E- tW"4M( \uouM%[f9B9evv/%?۶۠?'Ag6VgZ|}@<:CZNPCKP}ŴqB0Agb][m9q6w5F#IFDb)vhc_mry߶\ʲ,4[Niz/R +HZw5 ut4pJSk-뺝N'N~=Zr63Ft rOD)Ȉ^NLfZt|=Jj[W5+jCE`@Ո= 廉<3l#+(,q4Û ^`r>N)vtbw_޻KAjUm#tAUN&fι0cJ,)$ټ,%alz~~_~tWrru]7fЂ!s.Y=v8n[r:-J``hڥ3V?.ۈ`6S}﭅3s~}}?ov\x?ONe齷jl!Sfug,Q^GkJ9gt'z2Mre2䆈S %wom*b9eF7k5^݁[ Emޥe3R!1l[n)LL9L*=D{1pgZf-{""kk+bg[3{kOK-#S/e.S.}sЍsNV-BuE]@.*J)!?M4TrꭕRyΥl*)l ˉ&,pDȜ9 jP1L="=,ex]H qj-]5Ciy"?EHZaf㱆C@G#СOX}*x?M@#"?w?a>3zdsbr0TLU9j SG*yh>CS'BjC3E݃R~}nV|~1(!_~})e_KwY֕u=_~ۯ)QL<Km+sZN'.ԏbă:SN^(snyK̪>Xi#mDT3"&LDLf C4>dyIS]RJ) =҂@ΰM _t0O>XGO !G=D_>@q<9w?&ZiIc p <Ȅ@CE+3"8|G$9j"4$qb"5txv7?}yya@O?m:0u3f"ZMa0f^\.~[ glnukjBDܶ}۷oO)Zk sLu!3}c[-1T5q0"q6D)E1!X(cwfHz](d B034~У>8 Z3" 3(=D@ ,.뭅(l n!HY=kaN{=D_00h2/){p XLC?#61ᫀJY4͑M?\Ykݶ~NVa [_W˗4%FaA UkTf#̍s<96 Wp#BSNs3Grr?oodsp b)團<-zOS9-gNizo{%2 I2~uK)3#R)*]DZ62H[ 5ZN"#DZ穐+2"Xkñn"qK@>0h$;1fV8q3O%Qp?Ǫܼ8E:qT>dDH$*df4?9G⇹PZI>1AwcHkՇ!t~ י<1JVs:W!4ISN!x2:1:E- T7hS&9 hJWy}}D|rn7$$*Qsί?v\1R*j,˲,m+Sa&2,n*x9ECU2imo߾Nwqg3Kh(n0%673׵򥔩gf QJN2%DLDMeF;3)9LȀ8OS&Ϲ{ irA4+Jzy9V}}{r98ӴlM0vp@eZkE*ZEz]G&@bpi>:BDz:۟Zk%$Q7Dy rN! ͳMG%pp⎞r"YJD@]=$pmvQU9hr4CkEP2BƮ@ h`hlGZ[D>f5.BLF8"Fp裔3:;fΥ}B5Dop=xh?!쐓~ ']Q߱(?8!Lf9j̐  q*DRS>1UT52O@zkH<T[뱗]oOϧ;"V[ ㆙q1)yxI͝#w$6:!P5NB,s1XF 1S)Rqc~=GЏOmA(#P_-+ߺ(m>N zh)@G|P\mq4WsGuU'"=(ԔH^w|fy>f~]@kNP֚v{yy/s X(EC>bmKB43m}w,sk9 o//t"bwa5w84ϗ˥4iio۾dB"zKHTk].r<=ͷͷ͐h9L5l2!'!"CLHodf湤ZED8\"0f妠A2R1!)9g\se6qilw3 9%yQ5sYRt'a8 L{%\k}ZfbWLV<%nkjm1R*.[Ժ 847]2zcG ĉC84VkѬCMݠ&]dmZp@kt:>NHpDsg&@v!&d/__RrJ\ChjGAdJ2ŧ'"LDM$E.unkJ<9N!ND.Ѫ3m=0Q|r[={HGiÕ^kM)IKPI}t-r:Hou^/p1?պsv7ҹM8(:>/KHN\rv%D?l|8 hHb@/tG x꭫Jh/e:7RpU%'fj4n82IeаGh ~ȍ018pgޝ8yl N1҇{b"6f[}C9p$ny$)M5E>]E$nĥ{9z }o16ՔS$e^_{ r.9es]#tK8|Cࠂk2R"=wU]yvncq9At9(v[Mr<{kWe&w>C9<.6<~bb LD CLL֍sΪfr%sw> x8粊|l<0a0y11\pc*0jDW\yLʄsYzrJR[oꁏUGŒin:o)%ӌu]e)3nT9,EטOr[Q0,iY|=OuE?d %P# #YjkG׺Fr)~nׯ/|ޮ/OESUA$ *ji^MTIi)9Me Ggf20)!B}zRRyJ!kw39+ vs#oMDN)5`kma^D\';=ˈR]"ac+jmjKb*f*"A%IJK]]q,D$Ҙ!G<֮BJHhjƄ:>*4&6Z{2/SdLxRRRhzP >jcc,f&ç{d [:hL#"< Y82;x<ȡǟD=*|ׇ"afJtzքСEQn.*1V>dNxDںbuEb߷}۷)ZjLe*q<==o}&' 3HV6S(Cc;eĜ #F4ĉύݲ/ !^׷MwSʥLy[)e{PLcʬ۶o~iv|9ϥY?Dk~5B@y*fФJ $ضMURrKkiC"ԽnnE Z#i92N3" 1XAD1q.iYDڶ=1%DHdTn"9{  ;E P5ANGn8Dq@!#>8f1PwQ#^kJ n?9uHOd v4;gP:?>I8"č;'L Sb#R&B i Qr}]T0 %J9秧mۙ)ri"vnnDc9H8i*DDμ 熇jZ24M{k)/G!nˑI=䡪e_5/X {Ӳ]nfz 5 kgoI .JX$ #Rx$\1{GfT^ӕ. ptղTB'$9vՑhvj?Z%v1>@cTY֙K]R!ȦnKjt!PMwGpzf8#(3fk&=[@  .+YJW5B^.,XJR}J  "ݧ$eDGw͈ߑws:B2g*SH %qS ]mR:myH`"I2:tHɱcfmftZJeb3[5}Dl}J۶{SD3zӳ0}7pҗZ@Pu;Ns+`1zJO?zMn1LWA."1z^ cm#"u?֟rk-QU"f~Pk!vUZ8zVFrynq$"Rz-Cu{>tۖ˶xSDP(u)r"TG'La: @N K]8IXr[C )KCup@I. E#H Cgbbn9 rFĜ'Eɒ @&!bĮ&Ymfi##!uxٕbF `0|C9a~t|ȓ{C3xz/ߩg.t{<_S+'ľD[b䙆9r-"KSV3͹K$RMfD"ʻ?DԺZnC1!du$Lj"$5E=/ژ+AR*% PEʲ,Nlcsז+r#mbJf9Xн3-Du]t(!GK kkւn~qOTI)HD?eI}8f :pK=:G h<dgtGg k6Go)mL8M5th.LL}.0I-E%$E5ۮCee.lT2~T___?¼vN/*Z) <5^AstƠe'"%"教M^.ϟ?# n5 ~Qw[Favy"BC׺'@ tZJyzծ:0{}y]~}#iu벙jo]"LzPܬrR+F#' 7"޶뺮8ơcRq;H=%,k?@X‚R &IRMVɪD "fKc*RhnN檽",Rކ3wS@ {TD>r7p"2@3A&*"{_֭.U!v$b "%CEj)WmzX,!i >8g%_߸=>9z r`,j"%&L%egfeoǑ  }z^^j哎qfN)_S3w;).|rNȥvy %C8Rj)e\Zcwr^.8Pav8c 麮~߷ \/)V͘-U 恴8Tu]G]͊u]k-@D1 E-cԌovJHe˷kCu: R]" f늹 Qu͵anr}zBc?zeXE Q;!'1f:˚AsgRneMg"t`Pp#6Nx&l+BKEOq0wF&&r){3@#!;)2 Z)52ppJnH,%"Ca K^0&x-;7zssvb).P3(}px$#w^Sggü. J֩z:"g4y0G"j3,N;UnqBmٿ'E'Xy#Ti;H6tGlDL-u4*%8!]ZR{T͌R-D%$Ej) UշWbnƲ,iwLǑyK-R1l,]LTDZg/tԿYܼ]z8Ͱ^`Ly fD8;HZ)e.K؉PͼCɠ>?"Z0? > &aY/=.Rߨ0/),ek,_v^}öm,_6aZs˲l۶oo̟번1t mG諸&y# S-ENP=gQq6)ʩ|OS>v$RJo#brQ͘(iu] zD8zenE(4"$˰$(*qhoYb rIR?j{GӝyqCu5rRc8jҦ,"H z쇚1џgfx?y?p'z-|Y{d53-di:N4)]D 'DbҔCG>EsqGiޏƆgn9>|K'ݴБNH<;~l.m q^߹(XczHoqwm)%'nsFw #Kz,`XuRADuhnC%̵r\#IjfC8B!>=o6HmБCPG*' ALs[x\.D¸!#o(B@D,E\M e[BL:twQ IDATEXe^tE 1Ou]LG؃aRAJnD2;.734#RJ&iRE v{y۶ sfcaG3t^DRC  ܆CR꺮o>)$fG4ӸXjYE;OO}n uRD:`Web2zoct!B Lv437%SVGRdAmoD>qBC" 4*RS.ld&`ʱx",W?%ɣd̂c*ON]BRv&9E9:;߻-画}|X>bG)/E.O>S&t]O W)Ӵ,ϟ>}JBfc:Ah|hO\3#/a0UZʲTCDJFP?ce6f-UJMpjjw7K{^)ILM}X*ouiL|NQX`"c!vJ?s}( k)cG ZjVjȋ[k1G[R)~ RDh]jC*KKJW"'\e%HE7*LOZ?c@tB2S:z_.8~2\/OI_SաN#sx1:F:x`D-%afA4Ajf: ᡃ5ssHEnkiJMm]O>=?=Im]&p@c?rؕ\55[R_5>vѺcA#n=]- +F>3ңfj|ct1 wcc]=dۘrXkU ;= s#Od@D7;+'އ %eYZcTs沮˺MwӦ+BMxӵ hٵqqVnjD]M@\ icTfR[o!B>vp'RCmY ק.bD=e[pK<(KTef~nfG;-A@@0DmwfHIN.XJڔ`X\EdL8"bY"Y3kpRJ}!}'$seݥ2KX !Ruv1V4Aj67uc P,EX'-{4;Dѩyu{lRbl97i>YicE}|{$+cJE(7Cc]x1çO(o}3QskuHVyZ3R/zv(@=g df3ŗ#R)$,Ex#`]ץ֡}@7WCZkNlfCgwϖm֙,ͬ%7h?̌Ku]o)/cևZYd.̆Ӝ$`Zl x}߉덅ֲD0!_uYiKS&RΞഴ]$F\Oy1c?!ny@V, rth@Љi$RK_hvW~u%;Q)F6"]ϟq`k f,Vke|DS^ME(!˺ZH0IJe)Rj9!L=8u],i@gpuu4>޳vo-@D)5܃{kNSdER$꽥ρ4 )&a4 fL"奐Y!03}~+E%q f!B&̔$(YhD&u]UʍYy " ",O`i'7O[H01 *q"Ӡ ` (Gvfn)Dpˬޛ%|Sq3T1QZ:0[AS C8Їᴈfi\ִަ594w0V0#471TGxe|=HGi,r] ?B+oW32얿 ;>R1~ԇOҹgZ6),6HxAJ<Z;R1(|9"rSuY(#q]ue]Zk1գՠ1Rr9ܠ `#y15",K&a@{m}uڀl,y8! &9{q'/+{oK]"}b$g v##LK]~{SlmZʲV)3JO %YvAĺ-}9v\/Xk2R=R|f{B?gHz`92fkOOO~G]p! IWE)BH0Bt7`!,XJI|Dg:t=̄Ij 3>NLjAAO=Y)<'~)d"c#P)⮮cL1:K#DF0I^^]= @ͦy[LAq2z̈́{8DynsnFZ62<Ëo"d +$n !s)U]_/b*|fQn;弤^NSI,f:ŽZ3sC&,RRhK11fA@wY3OEXRbn?3DRp^'- ⺮S(ui-"G]$ F侀I'w'f<:-uYJUzH-,+ Q8}DhAm &rZP@WU6x ^ٶEJ%CH!B92}vrY ̔&WSסDfwp)̴`2"."Js$=U^^^O߾~րG`-5uYjǡі뺰Ⱥ.صk)2 ұAfpiHYJ!uG.I柊{ `" Jp3FmʺTL\<,@4s2g.cLT@X嗿1߾S~=c?~)9zPwwgޚHNL6eYś;̭*T tُb9q]hG;nC>uݶ˔Nj "ՑTRXdގ#R2 )3w)>QuHr9!X 󺮥l&DJDA7 m (}1دRzhUk۶:R+{ //DZmۆ$snn8Ypp"* Pn듔lD[|8vv˶,u)Hzk-L˺/_~e&澮UunGxԲB¹ޯO?^@̣ccYeYYHĢCUdo;@޻F'x)R]ØG/{`I)Ĝgu7ε9 xO!A3a~#ɿ]Q^s3wiJMd:×py7Qfj:9WSI+D#B̖҇%z%_-!a)I\$R GۛG Zr %Nl0S ܧq1̐HYz^..D fCMZu~:nH*) 7{ݖeӟ~+ﷷ[)Oz}~K)ȶmO?I)-Os͌A[;}]RkZV.kϣ,%,֛Q$Ͼ,] @Br.sN` n?U67mC/߾O?R_ұVԚ\HRa* FF"7,1x6)c!$f@'xf(;d~r[kZLḦdÌ=#'8",8 8ČHy1 ppiH;$9 D~+!N*z.\;szH%S?xEsR;_-r]S5"̥ҺFU3Sl]x6 c URfb#< >²m[1buY)u˲Mf5^xe̸d& ="3Z[{9!D cl?_~>}8ՁXu!П/DRʺf:hCv\겦!_><=]k)Zx8"gpݜkzkMض ÙKю4esck~߯OO"A,f"k2MRp-;;0Ǵ#;[܅iHs{n19 B{Dm[I$^jYj%XM/cs:$-," W{}}=n su[SI5F|x{:4'~'PQҨǺm?_rzFn~vz^la6-E6 }G[fްe1֥.Rkz_U9d""7}X{lxkݨHeimP.u]!P)M̴Th{;z27 2ֲL@I;U&L/TSD#"IKۘ6Œjn6Np9;x̘Hkx1I !|1eQ@+Nq9̅S!S){{Ojc "e5%;,܉8m~%ooE#H))#>)|VЍ몦=L u#sbOT ###۶{9cyZاWk6DK8@1$@= mN*¥.3{rr"RHDZT׫q'}QXԁ` 1tө"#BԩKOA1^^_~|2h!ܚHzZr S${hԩֲ, $ m".kZo%, hwYn 1.SCD1  &YbQ;3B-󿿬j7x˺V5Պ~{M8 032T) 3C5R b"Ru̫j)5%ϩXJ<\9"0# $g|eHf^.$Y}1;F·>H:.|2E/5Nw3hx'O ּ9iAĶfQOL$rS01SK-I v1S1yݶly&K,!A"pf!"ZƥHJ)<)~qG3w6EZD8Ujv{; pcu톈E!$sI SóK,&ژXZkC\Dm'|aj&~~V gj3Ce1z|Tjٖ&Ժ8!2bbfjU.Aq K?ں.R#I" ZkB>l"׹j3U3ֻ0y, c S„' RLYtb44w9wHf2/R՞T͝`.No1>@T.ܑSUMeD| ؔG12"wj$\+8XL[JЙ<70?'90NEDQdNM_?P(߹(Oypw>< ~ab2(4ԥa;zRӺ9嗈 Sr0yYO_ğۀGRҢdX)ߥs2=; #zuQ IDAT+D̔G @ ٜI&ҩqDI}Z`yx1p)2C*( pF΢k1͉g?˟?==_~nfCJ)*eaIHBH`숄HP HJ)"OvL )j ,T uZcHk]UUs(\. &"qV|˶]Ƕmyz"}v x5wtg^ې#e议`Co pu F*Dӽ]1 B?,M{]eC&J8'VtZR+1RL< 9AgF&͓ef*rNta&0Z|"1OyZ e倩/R{kꉘlC=7 ?R3ՄADo0HM|iyG50D"1T!*3E)ȥ9ZHѰn*f!-ze+fzLOm\jnTǶ]j]dG2j v50Qh pU8Z:M~-G,2p8<#?G}yiJn۶%;" e$9+M1s"4k^mx?KΣĽp73UȢ 낂f\91;7"B3}8+3x֌=E6 Ț zZKq\0{f }TՉ`ޟ3$ӧqST=&% ruY轷'uO çD-%~ɊQ|gq"T$TunOc`;I "/{yϯvR\=UnJ77;<80a, )񁺄sE'1̢}z]mrxHeMŚiPP-u]߱9,5eB7RhIȖp)E؁a)EAR{ /ysNط`(* 9_֎?iߍGx}yEs{1UDcX-޸,q4s[Uvܙz}Hh#F,*BR'Y稜\<d"뺌uu]Ǿm ~;3mlQ%Zʶhjts c7\|-hL} !l0JQ0 1>F,qaoRhAGWh~Ofcou-A&U=sq9(ǙMi 0Rc1O䆙'+bHR#\f♾o< qyGYT< >:֗aQ,Ac~3m2}328ueDI+|$}~}m H"= 1:z80[ 1K>ƈaf@E)iD28N^U˲ZFj\Z`_/kvRJђ]QnZk| Qz{_n.ǧOX2v$IoDeQ01j#}?ZkuY,eSQ0*73.m\,:zќ`1ZB_knRi)dy]jA.x@2W[*"{CgAӮU{jwBL1yn@3GKy(yw3/">SjA.TP?3ŗx:|$ a ?֖LI ה[>-Sfo O!?oη|Q|K?!Ur(RkmaVpv2 ~$Rp:k ꭏat^eEZ;i>,UUw,fh\go</+Zk]Kuovߏ=TRj-o&f"ʪU2!|3zZP兙zͻG뷣5˺@+%k륏qmZEEUZ=k)̽s˲2Q5'z@[jfa} \MDV|}y9Z[ߏ٠ բEE M* yP8vP@O~铄 -}!xR\ܑA6㑕vgp# l8o!3 _.Cl l+IF~yHo6l݅yq>?}U3~>ǃy"' ~ļxrw{ˎ8.2?߇E=^@{c1xp)뙲ϑ6?_lnZ wx5ykbZx]o͜BDKE "@#T\?KZ >fIr&FSya!"E&/uQVhY ϼC"Eb9Br\׿z{{}`PlqݧɄ},ч\6|rR }[;.+Q\!"@Cccz6c =ͭEQEIEWEjaaağ"m]ZhZԵn7 fǁ`}ʵ`Y.D>lFVh-E*@J$ FBU` lfP))i͉A*A2Zߏƨԥʣ3K6RtAUA-91!?GVb6[+l&NU8("Z2~LfE ,0v}3OHU8PxQT u8j!v4J q~Ks AU/˅??o~oqA"&JhU^_~o7!~-'׷uR_T˟'mm2KV q-z̯뺭9PQ>6Yff<ȪzٶeYYQ}ZPѲŶmӲX}t C}ᭁ@ӝ ) :XIM6c,LmYŲ1aWI"&ysYrnOqp2τ\‰@PVyFex_4~hfZʬK9TKP犐'9?&̴昋sΛȉ_zQ4!QJ8B{YLvJ-0$8CKi]˥TceYۥۑ1!b*f~z]u63nR+>G2CYy犢*̏쑇s*"}y@v.uY֚ܔ4ptDmp>{aeu]W(#ֵY)L‚"mßeطuU^c[`w+eRrNlۥ{P%=Cꭙ#\N-ccށ3Tskc]Owgq{wR fLX$UxxP:mDd] =T,؎flSV b&ATbDD'H:l2L_(;SVgtu|^O  5y ,!6IF는t]lc ga>l|O:eOB) S}7?}T0MGʙZKomYRʺm0[*S$It( {12ĪyVo`i[Ǫg]mE<~RD{.K,oGkPNq S2lXQu0o͌LM*-܌gofóDDَ:ݢp^„f篿ػ ?U)T"RK]@)܈=#DN &u9~O^=ޭVv#k}GYU,B#T*HsqnALE* ϊtv Q2`cFAt~̛MY ӛ5G ۶f%G&!,un޹j]e6"SEADr!~ALxn9Rvz`n!+g|Y"P,٠ Z90\ rAN㔊q&c)%?{O9)S?qL>7Wz}ZAzDԂ7j-X 3 &S[7tx3Qna>J-R#譛[~9I$"bG-sʏSx4省U"aU)$")**ZJ \ DB*ĤZT-ZA.zッr!=u9} "8]t6Ы$R<:^eYFo?Vե..VJu Q.F`A>Н 0z5@YJQE^w"cvfY=x]B]Ҏm oz\,Ȫ4rhE;bV5yL8e0 cCÞN!RڰDhΓ*DdČcA! =' ipYґ͂;Ċ \m2 HR,K=rZs %yGNY9(I ӥ?g<3$_L:×+]uYf=4M+YdPJu?J)Хٙap 4T&W.cf,b±,XYS8Z-ZJE\^M<g&qs9J砃O:v³2$'ݢ&WJ"56 l'aY6쫯~wc [Emd1a2!Z>}z-Z[,0h]k%z[RtceE1p"eY`uۘhD-8iebH(;Z;ZRUm]\~׈,88 uCcDHiYDI8^H`/H "*c>cpK5?x`φImx_ ,bav:KLџD ae͑aֳ s)p'>J!"w)ET'^Tιv؀х G7 tS3 {ƔOt(#9_JS rw }0B@L"/Ңq&5 ogIj]JCE'(=<} Pc)(-H@]쥈jlX=|xJ-FD~^‰ i,;DI,8#8`^\)cZ8!tĪF iasxԺmtbAOUee0"^ku9,1M2Oj.])ILMKIK`*ʲ&>69f3ns?&cؾ.+,V]Tvm,bG;ÅZ>TEIHrb1}?wI]}*Eu" k[b&]DE,i[t>߷ZU,8RDj£FZPZk2*?~7_no{Z#D{MT'U%psH ũ:ysY׫{߽ ie`|hV?/GaaV-r#lt`8cY7R+5 FȌienybX!NLRZO.mߏv7H%VUAm$rqVJ  >< ?8v ⥀ {*Sa^ᙏD;6l% D0hr nGiZkgCEB msڝv3i xS1K]qcbVF ԏ ^go:4gNu,׎t.zx|"np ]ݏSxgOHOEz1t_LO/0~.qRuY 1B43Ce].eF=Ff% τ@m [|=v5xsۏ8% t", ,"?̍((_w`M%DXTgN&!E<Wl3wy%a~*A$V'Re𑕀6+̽~1ě =:A=*:`lm "[z7"Z_۾~ٮ{Qjvfih4#s*2pi#cFYIȤ6cPj]xAY>KlV>Eބ<}MZ%.j=VI#qpo-ab("z$=pVOQywRK)(1^ma\lܔK {T pCۤ=(Dm\>+?g! u}wH 1 mZR1]c֡X9fQ*kBqr\iAEJΨC ,տd⑂JAyD[ ѠZ~﷢E,r}Z);f#K9rVRe8"Kb𺊈=\/חe:űDREr_eKܖZERqGm e-KVTT~K.h/'+\b³3cr7@ТB‚碹gWh"OmwfT23@ET1F`G  F) s9i.f水Xщ#c aYߪX|Q9]L̵ cD{NΜoe?ɤMYxdVOV2Z)-Gv\ױ\1BS@@UBUű>cowh-eT;jUd[8co݆C=z(] nflfݲX(A`µE%2#20 9 r=̍7R'g`*%$<ZX˴HR!$h >X͝2 %Z zlcnX@ DEf*0+RZ/E7f U%wd/8¼,hz!Sd~,QpcK}рP0|fFQy.P}}`wD@$11@(M%2=<IUYr\vǰ^jǓfĢK{f3ceYuMÂ(TΫ,BIQ)rn @,TynCXuA+nn}B$UYO]RZBOC0T &(*-qUX˦N"P'"ե^??iw˅>}n"B Dj]F$Bhŭ%AHLt3c#BI8Pz2BK0LaఠЯ+S?=GZ7DK4'\9OuP{)I8"[`?`xfr@ 6* p=3vpS7 ?L=BY>V4@*NeX$+u#Uڲ'27&iYo}/~>}^/XKaD#m|R,$d}x EK-Pj.f+[)ꐜH(EUk 74_pptt'u3G܅G Mu 6r+ K!Ċ2%DaHpl)]vw:3vT$y{7C@; yw>lɰz=~Ϲmݣ?/D) _J~6!4 3\n8SNcvlِ9"| dwvR+:12P,60B4$!/s JV9N'wz3y|\'?܍">i<_QO'E<"JR* 6G [E.sζWuٶy!ȹi]JM++EK)͏kџ<}D|~{{?_ŬլQ 2Rsmgp?@5.J5t%PiO. gL܂XH wn`Tm{="(CL4Ig#az}Jh ERxW3 bd?N`YuUr J?QkX<[fj>΍ީ^`xDD~ ~tΧ7dǤM&X$QQABTȳ܋'P4fiqf.[@q4xq@;wjE`ڝPA%0 If5Ox!rf*ɟ`NP 9NFu)}DZ{].t#13ؐW|XSQf 3(}Ln""d|.v\~aח_a 3GD PUrg Xbc/$YåDK'c6,U}"D*\D3xr`1F\IRO<7l|/PHg`1l88\-'V q`.$&fĄ4w;B%}p)%3 0=p;"΄Y/g2<.YGW\>z53R9 K)6Fo,rҙӟXEw a8bsߑ{dywU~yt\~_pRkclX8Ad޺'ffuu}zw9S͜W 4]JyW"3@ĞXB+|V' ԁYe}s}2e=ңuݑLm$-'B; xp֑E:zUmZ sQE,b3򣣃[嬁BX"c~ /1'J6QzDw#ΎCa {{חJ7U1 3۶UU*YGpl*X뷏[&H.BFooox܊趮_}5;sF";,*v&wfZ8G%0d2c<>RAOޚL k0 GvĠ㙔D4-Dp;vI]H|"4UyoG$蔆qȝ>OK8m%OCEV^U9"?~_3ų!_9x"kNSvM.‚rv4Q-`ܾ5߷m/:$r m~1ibΙOJgINb}R!~쏈[~#c|*)6Ϯ*ΧZzah~ю:qc 7mԀjJ kd 0`>8:ZgJ8+B9[*z^_v},˲.M (K˲ѱ8Yj)੒"5D.i-,d 66F;v,юAfa"3/Khqރ+9-sy>1@B[].˘ 4f[f)*uɧ|)[$0*s:ʆ0F1I,*<C1ƩxO6:e/=΅/͘7gshʏLF3Ǭd<]X.`V@ y((ay I>YH)̾?O|0۰u]0s;7Z Z 1#/o-wI 5HĜҭdDJ6BFws]#/>sXT2Gİ,+v|n7@ TR&$eY~0v҇27,W ,2ac|woooۺ`pYm[o|r Ip6 lj`ɐ=xiVA'X6-EIn:gAoc[k'?;K7.X; >M>9(B7E64C.'i-8[A4r0Zt:'353NBBUiTo旀{G_#g/fO&$AD|U}ML^S2oU ͐kP?1 03Z+mnJRt6< UCO dCD1f$aOdDeΉ/4@s?.z}Yq'kR3gIq*V}X&z/3ǓLiX {XgB6p-E !Tțw}<[ʄ%44d.<YVEI{>Tg)&K)II s?y;C,8b S}tc"_p ED E|=};#Ƿ#C:&+Gc21rR>߰/"w.$gR"E>@ t"@A3% 4 N&OeA}^_K*_<G7_al?\0b ab4d!u:uiALE$*pD ZT/ۖw1'ӖSd 3W5suVUeNlٙζw&Y9 Q@wnV}6FxEjhMsv ܏c dEo*E,f3('p .Ҥ5' [OH |B3́ǯJ"1J6:>gxoH[ZzưEK#p.e1@g+C7>(ӕSxLy ?,N ͏F8 be ?e=ӓ_<쑇F#ȉy& \g8ԙK9t@@7QEVVv6[N<%Q횙CyLo`|Cdy.-N\y :m9UHXS8_y_Qڔ]KY%@)ȃ]z7S Wpcjˢ,țFϽ™oKQ9=RqrQ&(T!RT5SWɈ LD|/~L??ۏN_?7ϻuNu*9@\3is{̊ܳATRatRv;cVT/n.J:r 7)Գ/Ha56DV fDU% E0J1{f u"qg#!QЧ%A$|OR*I,$>/ By cQSE!iQ 85~,H}Y֏>̴NÂ%•I1F8ǠR*[ lg꽹rz~iZJHr 猝|hXp`^:@mi5ctf-l“⒚t%ӼCEkX9*5jn_3d,,.q_>l2nj5WsX*M@=/f(93rL&p"޳S OGd (5NMCR5}#­}?z"t/:Z;.K-5mJ,*dÌ ߻̼$LNPFΧo\n.PeAq|@YS)^Ѽ? ]l:>Z֐ gM <f6ZȌKNfH%"Hu2.yj>a5ѥhRJK)EӁ,K.Ąac NfPPZ f ovKm,TK,&E-NfEzg P OwOo_'V=p$SXjŝ >}}; G;ޮ˶]Φ0B~*u(ח㴆@L¤tfb/6tvn>zhdu᏿.Aj>D@l~^Χ oI>2KG%O9!!m(}!vvnF^CNa6dt5"$aKZQc`$1ERͅ@<̆]p6rzPh'˅n IDATqL7*Hg!&Sn8*l.# @1LcD thhu-ST$W]$B#(oPy 3'%uYe]60_ZDtS2JcRj]3"!!N*bHIaUuun;-!iâ[<ꄷx^x "4^DZU.VKKDͅy]ȞPvΊq#.qmF4%q͵\ _|59D.ݳ3-D|NvsGI<l'Gd%3h?Is"r|Ώ|h5=*$C=fgA)mQ,EA22#@==Gۄo`y3YQgu ~nSãI?/MfSOFV\`yg'w>퉌0QQ~F۱~Ql Ͱ"Bs(>;ŐdRb8܏͢:NчJR_VswkpȣȬ;Ii+Fjy6wxN7HD0/2>T\.ydzOi$11޺ꪪˇcsπ*///ӟ_^^i^8%HYF5%wO"{s>sC>HnDLc&,%I52 vs̀Lf$85 wbix(wJϽS[8f3d"1&`~[˔DO$hs|Liι9݃RJ:)ؙA`79gRupLCa1:X0j7%lHm)a1+ ,PR&YAr!d x?!#j[:r}y)ﷻ0u=8+gl-˲s 'x}7ܒ中uDdV7)/fX\~ֹ42DdutR"efo'06n.v1_v֩\[-% v8kE^mg&0` 0 r٧חAt܏qf P̭j8|h͐0(in_S L ^p._;/߯_(_.LEFjT਼oI$Dds$j`97Ks*"jð$-6HBa-9Xǀ`ÆaX5l ~'?hy`w tCM|tn猅(1TAhܼn9[gb'B ؓi`6uw\ yUjwն.xx3g>X1Fڹlۆ{'s3 u5πvc1s=leҒ6%EZD8Zo}[k///amO;qWՆ @G-ɮI29D.e0cOMhɚi vuTLD=ܫpQ/ӧOoQ\\p57z2h]pBp17㓙OAPܔeb1V HqcQ49@lrzbҖ $X75caWy֣0Kc79@); c`?8<[z9q?ֶ ȷ~&L+8/4eTXB[8H<a-pzwe$1)êC h!'zicBѿ?ȶive K}Mԑ7VA!U&0M4T2iNE\cmo>m98yq!q.RT_bXwf.:\]DXk0ch&ZE̦ ՗Owy]{3_X'!-ժiZx0dZ'GI81*Q--P6)c[T[?ch#Cn]U[kčO| ha#j27PQ`(T5n[#!l=$=4^\~X\ W,2w}[P,]ɺtMR, KRl@Pp.9Ifi"& L7ؐ!Sۺi Y$Z%'zLdȇID@HȖ^KQ/Kn/OÅ=6=N/F"yq"X{뽵㼿8Ͼm; ) WADSef:̍&l53#㺽Pѵ(f7Q *RdynO&"۶ ﻙ7i9y^.\D^ܜ%.@ wL7:ϓ;<0ejcpZx0%6:u}clc߶tLq i =֡7w}b/:%ߪ3PSk9[͂:̖N *|yb9| {=-jNȿ\Q3M&KM|IK7g7\1%'>ڊYLjd0?$*I , T`DءM׊_- ܦj sXB"tMýR*8J.ƋӺ0/?a///?a\ 4Jpr-Dܨq"|YQ3VFue4Hc!rn۶m훙8y;, !Bg:{Onzߡ8acF-k9hv{+S%ݻ؛cm͈߱#3s?CDae +۶y23gzc@tĉ@/d;V Ol*1?~}qJy^}4,һeu_ene]-Ql{ iƢD(z.q${;)3)iaZ[s")2^ӸlLjz1al*|FvJD۶3^<;i֤!)->8mAqw.FT[fW,~GჯAїԣIlGmS@ RK_~ǵ|nb|OZ-X"aPhYK N2Ͼ#nYT @JQpPA-!ZMLvwrB.'o 2KnDB3ϧ뫚nOz'Hiua qY(PG8hlw>3G:WnGxBA"Yj&{ke"B[Gǹ^83=";^MLԛ`K91cJtyT& EݙUHB ;I! CWVm\j v2 *S.ϮWMe]ջZ*.K|+榳H̓E3BeB0nF$HgK&D i?spDhrg {LI[m=piɜ 䙮&ҰH y1By\JǙ=>,!ᣫE҇p՞u`0x5i(o^Y9\!yٶuZbBeC8Zq3CV@UH@n:;~jx9f5l7a lقyF _dMpQ|='1miPiÇha UFtd!}QyֻQ5"*Ec(qZ2-d<4 qH*½Tnj 65|5ƈX1RThcdے_q!s!hצr1(Fӟ?;u'_ g#w22rRpytڒ'2pPc7'U-ݚHlݤ&O &h̼r6\GLYAFL`Rg:4|"–rcc)uvsnukj_^^n7A1s&܈j!P<]tfB.`#&a ƀf4{!%ƉڊQju@bjkዂΟ>*?(cV =p鍮\&}Z2't"& X$= HP:2I Fo"ӊ4ES.IbUb,[hSfK E&ǟm8`0'vH9 T'Zn/<|yE&֚HˠkwSɛ4RHDbb 4jYXrzIΛr7ɡ_|N۶*?[\l%nU2j j iᦪql[Ê:j3iDpP|"­I]PitVFXsFs}qڻAA;^I # IDATNWq+cht#Mr;?Īf#"HCmncf\-ӝFdpys2rrc艱0L׀%ωMw %@U?M Tu8c/ZFӼb>0Vg C<ɷ08 "W`dG3dk}-:t5/v/ rD1^@Xe/5\9"*לع0ƥ LCio̥j\9 }n/ բshbO4˪oY癘꾌/˷|>:sbZc2#$r fP424I;9TQ!؟ )$zCzzH.ԏA_F WBsuWa]~(v%u(3ܒM>K,hu#S)G+hT uccN5qs3-ɐ9?5nhx߽zry"'_";eut-%`./*Z+y5%ƥfWܸ A}7f dn޺`]X0josgIP'Z cnQ AT1a IIӬ0uy3 ILخG沄c1MEsoCMhj,?759OmMFz L)P7*"SӁj҈+N]mhf8hq:-n[`4"@"4Ba1]Dejv*ܳc(}[sz. Kqt֚mW P*|yJX0_ķu}+Uy%2GEg/ʟ>=,qh񞞼|谧k">A$}?؆ Z]NTi@s_9sΉ(r& 573MbC0N"rI*Y%V01WvJ֕! T_@),o h&ӛ2[&R"G^4z)R/*RzM^lubYyZ":c:b†ehw|?ͨg:|:RHƋ̜ gOTsajJ.,9pQomR|?+e@{Tھ{nY^ET葠|moēxe㣕NwTK) Zb_@-" ]Jv:_#FSAc|iN :R@\'ùr1|}~ؘ͙r1Rʙ lL_Wk'J4qv6P1nxsS;3apVE,ω.Q|/y&BZc \ACv"fSS)|o5ʌA s)зN=cP^/_y&IBb-m SÉPlK"B^G[7as7:xk|c;d{ (2ԟ?*YSLm>Ή4D4%W >rL(DP.NHBK09@9j-.I%dVp éfvIDUNޛXrX&sC""{sA'm d}2K9–/T1jv=KMK&im"L}8уUcڸYMc C[tskXuIF>DD[߶4@AD8 2[{a޷N_|y}yaۭ'q"xEDwc&R% V>f Å7fڶ-PT7Dݙ& rCam Ro[Ɵ ҉enkgY=wdTb9c<VnC1xyͧ$vK3hooC ;VS@qmcW8fzpuiE6LO 6'R0drwW31ldR-Yf\h|&7H@jl,/z #w_s[I`n"_GOģn~{OB+k?xʣ0{ rtnL59\rQJgp9ܽ^P>Vf1;,4e ܢ1B$0<\md,вר[{w37k`03' v{z~f40s5 g^!7i6GN>-VrXx1KnH5%YA_UvD!Ҍ3h- # iĕy sƿJ :%" 8Kqm'J4N|ew8mrB}'jYsH1ԛb@Mu+Z ?ڝofHՌ[W.:Y=PȫisZdE띷t`8 qzm,/Q)ui_-N2_]fU]HsGm&۶PZ4zmanZbY~ zHga") L#.-JCͬ uei-V)i.f֛6ч*щYN#9mZ9%b1"VuXq$pN4b釂y۷}#1ߺq=Jǣ5 ΃2q Ng$nBb6ׯ(mW̋f֠9 :٫RӺNe|Gӌ0 +)ۮ,u\c@.Sܺ/Ədcl/rE]0>xmu/b)ǹ:V͝vnxA< (!<<о nDga HU*u"R7EJ3#gvdI{43zpaŕx[+JQq򅋐{\mkB)9?ҽo"X`}KL:~ͥmmá{Kkd8syσh["D\svf[c޺ # S='m 3bz0{<-9TA.ſRUN%"?TFofNulmȴTnQkEv˖/mZD'Uk{ؾ*p$1.kusA'K[M8B7J@2s,ХUJ[tFjb5q_2Ud\oW ߾?!~ a~zzo7"_|95 >W̐RM]5B)tʍ/t{j$nvGVtpjW37Wrډ]֬ -BxJ=WGEw|yL<[>ryq̽YPe`5":5*U}y=rTm K-/ Y*@X9$͇8 $@[?8k`aQStUB$Mt(j19LOӖ oECsHKXwMj֩QDqj ENͷ :B#yh[oнo0ƀU!>y&mL8ͦae+%W=%$O8u{ZB,SfӲ^ K:Տ/ʟ?Sl@OLu#&F7zPzE,.$OLTs?;fªHl:`)P5nÆ1 `vj|432|ׯVS.9< kh>G16{m#g 5ް#:üztZk 3C,®gv{ IDATDIa$:|Qi3|2/qY&R k`@Hs Nլm;ehgNq *tLJ+\X4͆/;Ӕ/ _!5P^16ܥ;HCX8^bj ԉĠ|Ku&w@k_|G%rga*S]K?\蹀19ǝSt@5pTx.4 ^"O~Uh֩&Jڷ8O3󈻯,CNb:ULU]ֈR.~U~  Pq4$Os 0brػDJrP ]j:|.,J ?bniř2sשּҢ4ZSQERro+_4 oXy}T'.\d2f5kÉm>i/Ch) uk͎8[el#g`V{ހ v926ԭF]:>ę`!R5LNQ@ؒ4erDȱ&W@wB;3!e]yy&Pi]EC򪋰BքEE!#i[k۾oc͌ k,9U9hݾb()[ߢK\]^_o-_p o deF;ɌCytn+PrnKS;=áIHCŔϹRM/ '쥟/WUzјɬmv6FfJG72gm:6$Vm-Vf.w]H2POVjlX쮓E|\QJX5{3وSrFzH\3~/'B n»搢jܫ~=w )MnZG ͰG{UKCG˕5r΁q}nnO>G-Ũ~sFޙ|V7F_.mx"HӴ3[oR [*9/+ryg4arEG1T . ;8d@Y'^1b]PD| c\Vxꂟ ^᭵8so wB˅1 "{Xr=:69$^_?P0Y˲5cX|]gZ' 8,KEYQEӧO)Ь8KD-nmj_QI_ kmiތ:LYcָWb>d_`a&r"393 ֥b%ɼd`-w=dy/ $)K{ALm2 ھՏjLBPJyWvp<}nu M %ċpuͪ"lvB璤%^wڶo2pW5LF*- z ]e_=*3}|uV?}Tݠ/.EKK.t+GkcZ1 *tsBgBƩUYr=%L3,Զ7tȅ&4ڒ~UO@wCt`jtcF_U?TMkh4793>IfKr(9EL! A7"b8`%"\nƏ#ܨ1y'rBTWSRoeRGꉷ| pru7 b HȝV8βWX;;nEnT¤-a=i$3⋼<&{jeoA%3[\m{ |` >*S>5e,u(_i+Ǭ u ҌGnmf])}`dN2ENlD˪ȢDK>3D‚+s\hx.f g{ĵ@$1M%(ש`:nn$TU&wcG/Fwܙq$2G޶}oi[q&s µovf+8~}۶58BƲ"魵rWyށ,|Ryy8%~I p_Qh(dqx HRGE/ֺ;Mfg~~)s<`wrhr!y,V\Vpg= 貟6n-= a?z lCL+op m}&?&'a!D"o/Ke)dp&3KlMFL bK"cq?FbgX%"}/HY>n}':ӯrys5I&>VB.s׃Q+G6G FF8ȿӢKܣžFdI+C!vvA>[4Mz?bZzٯWKzg#d#GkD4AG#Ƕ`TzE,|A&9:Bx~UeT 3s(|xe1dmE/Fm᮪̤ 9Su֧fї`}co[60S=V]2F$ eDDH獣"In$%LnTV /]q#`D !֔"rZHIWȿߢ6Epξ`/D1a2海~[ɘJ7&9]2TJ|ui"~΢3Mfbp W.>9SɯJ81wR#69:JMYbEYuy}y:.`ك?[_Ls@ThC?*GE(I߻Cd b5~1[%`BpqkxxW;fKd:{ !g~7S3/pqU-]bPS?1 9]R&ΘY͗lh,K_k`8{o#i殦̹R=S5JHZ[iS δh,`ؼh۶3`1+9v&#K̎4E3';V|oEU8~@̈f4T&髾1 U#/ )G9BrkU{寧f3c\rQYu7iz"6dQ4xD8RAX-%r(u(ʼK_ ~9WBvtջp /\,z33"Y%FSxn}iyDt>Hb吷!SCQzcS嬢f~mxWJThsehy+-(,S dr%* tN~cJ~ZPNFDh(MMo{Wq#>QΉD;$輀/yGrހ? }:vOlK3T *|zIK~-iۆ1XagXJ7.qhD00g(ģ-@F ezn1)mW] @ΤmUs;ff=S<@"oi D3en/itMb`,ImQ n%||g'MD #k)ZB6q@<  5C2 $LI`~ SVq OUA!7.h]|zqA /" {jm[JSb]EWL%lI74cIz r]H-;3 ɲ,OeH *M-8"h[}>MS2`TS<̭fN#>eXi-io5‡;h̢af`c2_zo̪>CF+-=sc8S[!;lk&z!Rnt,fU xP:Hܡf*_ٶvjHf^@uQGel MX$v N2oQ7yh*M~vg3k;'{vv[;VΤXYu1 З`t4Xgb*$}l:ҀD"<^/fv̺^E`HY}+g\,vM Mؤ[̬x cѷrliU(z|||zz$⻻[Xf~8;-;٣}!b}-E  ߙNhB7{Xqf}6ubPskR鹑/a|Ha߷mG0nnM;'B{?iRk:kkK㯃#.žǃi' h_%6RYqֺbwycȬњg41b3nK9Y 憈>|34,]$30UF#K %|oG#9Uj]ţ8}YՈbQst~:!jLF 'y=R_p3fnZJ7m'ڷͿ]Rm~n *.f5!#DТF8EaY #Ú9Z;iO6vqx?TCks|Vc7<1q޴F~m!r?|Hj@t>;8aS(R?Pе #}S G^ Z ߦ}jZ#[LPepM 7|mn 45Ft{f&þ7ValwF}vӧuP%'ߖ8QU|Q[kŐyi1L0PΊL茎;tlN:Ygx%$"OD4dW.5G#2F\'-XQWLJOM-,>{wx/<9}pwoV\ m?<=}Xk y59LOiXeڑSUNmq9k%.ڔߺv=M T}gj{?POޔJn==]ؗt8, ŏ{̮B6! 5^S9ɟK[;$/' 78"ؕS sdG!όk毛@" ؿE_;B_4@C M}`g=㑦I9Ko-ei"/k]FUs0ocS6Fst>)Ҵ"-i]/Hd}me 1eVTZh7{p! ݨC`vmw Ԉ5?l7mzsDȤ̞=fXր~b[a掠(P6l.f?ou!Ao%QGmF,σyza]WQa):;˥O74ti̼m'7+&CCqfZU }gX -VXiԩxB8z Τm3MmIxRQ6b%39^\菔W|ߓ[_FaE[s3%l~9#T}3 WiRsгܢ XSeg!nb6hn@?08mQn&*\Kblc-i|.qKyO 4a3y@H[,<`uG]Pפ=_Q?uܣ#8aOzyyݻBˡ˄x-ՀjˌLn;tCgI&؋ t):oZhq&i[DyLOb[&.Gg,mY֥$hi-e K6³XQ5z8p7?w+"%u!N6&>%]F#2˫B@?==1ݝ.G[&0a.L IhtQ]<,Lm+qwt>"*9+| xƒ0PAP/WsYP7QҠ.XZ[eDmkiHbߧ|4*}td8q9{<\Q?P#h/i!A][xXCBCp@+!<Ƈwr+|yy@^~}ssLM֋>{s?4"* ǰu^/....$niUtPše>vkv+̒ J\  [3^p?]S6I0]$;R}k lw Ȑe?}@ IǛ7f>K)&;ziNݶL$&1Uix)Ĺwv}Oz:3_7s]>hkru"VI Rʱ &ݫQrWH e^W3 m(2G([*^ O`apeiscM'}۰@깖9.Ĉvԁ9.qPD8N܆Wuld줶̸p!&=h؋,jmɞɭbekе754H)O)*`dg*"w۷OwS^Q޽/ cbAK4"M e2?$v4Z:DdAb7e4b$7 xUZ12$@̃ Ɯp|O>W%É Pי93qvBCzt33:Z*xz|ٺڶXlK0d 8MYik7xӎo߶~0L#̐F:v7/U"P]&!%_އ`&w,'n/}x]n벰%˨3g'CJ:]7NB ~ μ,fkNktkPjX >>&K=d8iu+9Rs+`G6Ԑ@8 "(SEq@).G{ء8O$4_&N#Ϲ1Ԕ+͒ܧ v^}SOĈ9,Ozv;y Q8"nk৪W^1o-XvcX)ÞWwBu$ Xc+*$)^9AΥC.]5p.VoړoM24;IOE3=`vKCzp@76=fȟ3e]>Yc;Nn|~4i_ p >O דYZ|Aˬkh9K8@ļ:m۶Yj|15>N=!tQ60tAM"?J݈PMsQ1dj4z:z = %z\Ia!6.HT8PMX!rUW_ׯ+=,+ƍglQC Fh1}cƇSχ @><~E_v(3U|̧;xOTU%(v\kCb@6UB\ ߡYrw=@gl ޸uKu>SG' @pv4 Wlʑ 8e7=chxNz8XƶW]Js3r_]Pʶo|@plf6*0h?w Eq lN.tNbt\g"G:I4 f;@gaJRwUʿ{.z9:Ck{J'``Z>h .k86Ҏ'$?'YY m/2fu^"Cke&W(qS jcg/yr-ꞣق6W,40GĶ0$C P½>4mSghWI1OƘj<${txȁ%i%w@$B..O?y*vHImh,uli _H`G>/>`3xrdcMU۱|̔L?bL،|D9Rzh/^^U!rUo52,@ 4OѻrSM޻rdӾ}=cX͛";goUuij-"DCAAkYӟzx.Ϟ*_BY&nm q{wGV*ůÃg]Ƽ,_V-_;J{ ZX_wCDfs07븍ζ#1!b1%[Z#%i1SH,4,燧}f KNʰ.0KzjT!7֍C~Q\UewEE-6t{߽!֠ 2Ȩ.]M^Xm{c殺 l|COb t/Qy+:Y. o4 }P9dQR_>OcG!rUuʿϖ83WSq/]k!"{Gwwd o[c&)hV!Egc-zot})fK/KYo'"&r9Li ĪZV\Ur=<zhPB}ۂx̘%;gs=kf{o]o߾El1zh(![V8G-|:G˺}Ze<7S-sIa+4.W(๵ *P?N* ?9{}7z a+3N'T(Lֆ v%%#&PhFۄ}^x1OI}<jrUUUzK/}T CT}TŅMڂC۶ /ÜwV7O#ah=#!vBnnnẗ́PEe5޾۶m}߉YSPqP!|mwS)DJ0}}N@?HЋܳ#d0؛!W׿/UUrտUwwwoQb}6eŸ:GS?[;cIG`vDZ,sb3r=.Sl> Ns_"W(WdJE:4$D75'-Zk?i ֪CO6I=)/$‘=hrf +$9V5"ǿ_T=!Ӫ媟noo܌_m'?Se-|BJ %ijZɌ I0s,_ع &?,L$xb% 4ܪXy"O8Tjd1n[f;: p 쯬uvL-3K삫 ~ts{뺮*Bj˩۶~v`3 !FW7(u ܸ>@c=<ŋJ2T[U\77̼,jk E'wbපqxEڽS]bB/_KUUro>|G۶.:phcV>e]-hj,$IDAT3:S1۾+Vpd κW&/ҋ/DR`@Ǐ߼ya77ML.XȾz^'lUUUU?e|{{?A㔮pp桪@wP^媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪媪 TFKIENDB`PKF@ֵ-Pictures/100000000000025600000256E51A1ABA.pngPNG  IHDRVV^H pHYs  tIME  IDATxkliV߷.eu L% ( 7`f3G30lq0n9Bఄ9U{UF+,`y~}✪ʪϵ/43 `0 `0$p0 !`0  `H`0 C`08 `0 `0$p0 !`0  `H`0 C`08 `0 `0$p0 !`0  `H`0 C`08 !`0  `H`0 C`08 `0 `0$p0 !`0  `H`0 C`08 `0 `0$p0 !ot|  ~d-_ ffЇCMgx`g>@|Mծ?uvU]A>@$Ŀ,# }|K_}xͷTUMTLT=OZDtC"r#D7>DD$TUcT/ɤm >?{$Cף Coڗ%US3SMMMU+{T>=vU23UUUʩ{ \Zõ|U2}C*||_`08›[o+Wz"BUWʻMTs ᪠̈H̄Ȯb*b``~? "y}=܅k*Z~xz Cx뭷3oAZٵ#dEj".0N}٘TEDU Tk3u$!bL"Z-.R"{ou1f1Rj5I)90"K 3 |W{ooo8׶?4zVDTV5S"&!$B xJ)S 81yYjoT #IHo.""}S眓.if"93Hkb&DcH)y饗>Ꮜ`0$puV[oA[{(F"^@ZRZbJPkmy/򑏎`0$p5믿?_r9x:^'M[BD!srEL!b)< wۺTpӔstyYu\.ykybJ)8p,ײ{a:47B ӧ!в,fp8,/Mrh|FDc!0stJZ齫(2b譛AmqJ)9ĔLeu_>x Cc|+/}۞>뽟NG7nnRJHt}1sJ^kUxJ3^J1SXr4G)'f&u]EKwc 1Dbf۶^J)MY3V p|ooJ/̬V/ﺪ.RC!p)RZPk3V03ӭz){WS?c< xW^җPJ)岮"1}AE}1l}]mRB"3So}o1_ҟ212c DDǍ!PkQ"$CJY>L|XZϗ}zsscףy>s>fZw=;.j-3kשjӫ)y^)bo֪fZ/eov0ɏ|'!J//77J-y۶R|w^N_|x<0MKK)pyԚ3)J)͏zHD4CH{HYQH)ƘyeK~H`Hyhf_;_˲Pk%_|1d&oN[[|ffUe̥vG.~I4:e/B b1Ɯm~>E"+"OLJp08Ϯ| ?瑘CATu-ZnϷϞmۚRJ)|8OaY(0wD[kwww|&w x?\gGzas;hLL̞&"]č9S @UjU  s!u]8"ɓw[UO<{̕/4MyʦFL3HܭTu-{7WH\.R $DTKFE8 vyED&2b)X^k=OyVkD,4M˲`iOg)e\RJHG~>A>>4>,cJ)1& \Nc 8cmk]@1E"6,ӧOyJ){5Uo2S!B甗E"tRK);1E&ƚ0ck)x~ Ru[k1ysNE&*TAJcK/a`H?7|^~MFbr^zտ70q' Je྅Z饔yN1UDx1p`0S4Af6]h0OG0(۾m z"R jhfC!ƛG!D3Yo{Y/M5)F!34)OSse-(S sG># 0೟쫯m{k M͘0sѧ<܄1.=!O^=ޅC Zᡏ?Dto.rSέu}7>˿c`0|ӟ~_t  | sJM a˶ܛ=T =e߻Y{ dH[pvD<!Dwg; 8MsT34]O=>zi"`j"ZEzoIrG*"^uM9/:MǏo=ELrJ!^nƔr!²,һ)'Ui}kvDضͻfC]DcqRk  Dj!ޗIZ랚}#777777s@feS]T94@K)^`cg_h' \-wZgJ̇OjmU10SkJ)!Ǩ ߫ËXUB̤jLsN9113|{7_| /Kc0$p7x~]%B oz~4y Xkٳg۾Z 6 !}QDL-N)e[w2S]MU}æ@vaQS <;6ܳ_k)Zm!4MO<_\U9t}g!JL7a )~\Ddam /x8801ymsF4ј}/;UU,R꽩)w}w d Ȱ'?SVUA"f&?ZsO0鵶]{z> Zk4tyXB|yʼ~ s@tJ)D4?Q")1UTM!Dϲ1."]f1" xҦI!ij櫎TA[k)iUw1LSF|7 ̀֟)ӔkPB0Ŕ Jk"*O{jW Ӓ7DFm[Ud03q.}߷2xXLJɿ U/=aL g?{>;y7~?Zo"sԶ}71RfVJ\.J{owUKDi% ۶\ >A׭~L1yL!uDthL T)k|?BĤ۾REZ7?;)1"0{34C?K)2a|m!uU4O)%d$$BC^@dwݿe9Oy],/3V/n[)}Lj0O^axR)ǔqМ9ƈ݅4U2Ma03#1 ["q F8[O)tgF~V(Oz3n975U5BLL8q[R.EDb @|2cҚ.zfR"ΗAJ-BK^WКw{7QjM u!^Dl'7EoBL*w8w!RTuwqsښN7[L۶{= fj`l31RJ"B)%Qը`̔r2fI1ADsJvw{RyZ$$sq ?]>3$RDޚ"X}$QBL14b!C-^&:Z"ڻxrRD:}"RS3m]̊i1猈DRjo)Ŕ""sNugZKQM =` O mYͣӼe9կ/`33=ܓc1HfEEI0p8N"=0Xi]"vwwy~{/ljy2P0f<x\B@Q@U`1Db9!DT_< ee oGkg0{'?QKj b`fR3Q5@ ff>ZoӼ*=H`J)2Z d`{HWs,`ό1]RN0MKLD!R qY"Ƚ֤ &bc$ f ʹz>Kܶa E!4MtiWD=1ѓ8!tE3 T)嚈N'D\.f̧qݭ>#Q5i(KO)Zk\Gj3yC`眳GM4OSAJ)1}_x +3Y +oww۾E4U7x~I+"זcjc~TR"$_aTN@TkE#3JD3TsJXKAE fȁ= EEUUK<WI|(z[=0j>CāK#`Jm%) *1".<&>OG){ 1ŘbLtz^kS塵>Bw$X)tabRkta ڞ{;_ÑEN f!1RJe!x߶&fts<|[iC}'033iLDHH,`rejJ)# #RBz~7;qp0$p'~^{55mX`&"3 fuԎ1FLNDĥTD5$)2ZDy\J=_ε9 Ⱥ۩48 L%0۾ۦ 5izHX1ƨ`3"=/YdytFm[{/79,˜'Ӕ=$E*i^<1&@nZw\SJ<|k7/̳FB #i북_srY{ e=w1.>"1rQ}-.ӧϞZqSC)3 Zi^vR zo۶@L'j6 mZou)B(e80܆)-rJU黙U14MrfRk;kea.ܥ~];\\QxDw_WuOmmd\BܻjTTU!@7jq&&VR|s9LvYR+3z٘x:rN1ptQ0BwjnrYBPhޤ7i1&0 dbkZ}Ѷm"c$MDSޙ`9gbMRNa1y}E܃g!DڷMU> (0_ms{}KRzm=|;!.t:cL!i}+ |[!"RNZ@L18yݼmޭ 3N`oq F8Zտ׾pݰFԬn[ |L֊)^ N4ń"jTu7b^9m+3|9_VfnEm?jХ'h˔S)|{wWy2-JEQ`#=U ǀo9Vɬu3cF7Jkx<Ę>}b:9$ƴ,~bx?O~;;_xq%꿗_~'_ZoUU!&`o]U S1845Hsc`z#@C405@B2C@! TRJ9/!p-SL!/ev5eB.볊Vjm3MS~4. 9~'SJ4)#LӔRVkEۃ |2Ǐ@Ep8}ϔ aﰽ&nj"/ӔRz' ^>}Zk%t:hwEC&yYr)@!" o1@J9URCa&DPu{lX^/cL@#<)e"11{`q8~`:kW_}@T{o&v8 "1qxH !PukLf! !z"/]uߋJ'j]L9!vhDZis$ֺ e Cw@;rJٻ"C??C?CFV^}U0z-E|A#t0@ 3E"3SQqP?X~\ZpYקyz>!p*aFDri@;ޜN!˺QJT8ooo[M ZSiJ1}zĘr@Z|ω^>/kmcﭤcmYyZd vy"nj#"@ rvH"}]XN3_i lnޛSIKAA1#oa 8c,4&d"ϧyݼ%8E$R!*K~\;#1ID1vf(KD|̥@&V$zDdݻ珀<.R0燇i?K)]__?Vp wUeFᨺ,𴜉H)?ooݿws?A<Wwc\Rϧ5!;EDzjV0d93d6uaj6"r,WbחgӹkkmD(Ru}yy=ٕIJktT#&fqݽB31L< jm[Jna~ݯADj򁠔k;3),DTd3'bUU֖mZLOHsBMȊqaR[mJ%#n|B8-3\k9_#{?x?>HTw?aj"j%,­5 f1T9iHmZ% ܝHNG&Vڲ.cqx^ׅz: G[ZܲZm] B190Z̩سEҘt rzZ޽{ǘ{x)̴K".ڻ2όKHs*"%>^D|>)7GU":Uujx $(n~=0GI.8+"EdoM'".˒fp0g PjA~쩹53WE\;)%KcV>w#4XhR%{V nmYZ*lץ֥"E0Ѳn]oki[g??~%~}{)03թs wD J8)h uV\cYJzLIEژT̄(%e;RjkŒu]Z "-N \GzMy昉R6u݈/Ϭgh1r? *1 rs9~fƷr͏|>TE;1 ܍朥 =뚝t"LqPI)sac#=eY)B(p!)DTg}NDoK=~ "?Ǻ,t9pv1u]&\.nZ}8=Ed[ۺ6 D"w iΩ: K$ &:DDf2 w0mJ)GZ#%-;K)˲M͉Lt469"1wr@AD1<ifA!n}BJ9۶z{tKuQU33c33gၐ2d}"8ԡjm1 ݣcܢkYDjY;E #frޛfGe1{ HfuYV>0g.Kktjm$b&dV09~"pr~ZKYMR"bNGDtw5G櫄C!-l:^ukADGo"啙!t"د1ҪHEfQkb f_HD{|[ڮ#V"@DRc6bYW)eYWSu7[aJ 09稵齧0j T߯>FRKa_ZO ֖uef1SY\ӝ"r[IyC-QUFZ$,7E@884"r'9u+o0S};T~*im#2b>0i gfp1#`sYu2>8LJǺKksp9=#cz:o@Yw=s/V~?/ 3!4uwp"MD<|K <# 2oF,RkRH:p&cfWD@`f"ST.d})U̢E b{L3T\C-i.3kRЖEt"/w^'r=^R-,sal*sIDj]TqH>Nei) n>UⓔTDRS$̯s3,"H֜~Dj-ɫ wc2s[mYp"fBƂR9˵ޏpT`eiv"; I)~NTH{-Dt@dB HX[#ai6BzcVfZJq!ȶmo͟,[uM ~??';Vr2c@DTS $LMyK@|A4*۶.Kk1F?Ǘ8QN':r2WGe]DHZ !l۩jnc 0<}@4ibiy:mkK&؏jkjÃYݳ2`<=\Jb c1giYFRG?j˰r͡jٶM"6D w6yܞHJI{d-yXZm+"|^Q>d}9us2qzBy k IDATò,/DZd Y_[HYP)c)ڦ[n,mm^ IXDH0 ZտfGȯ^V"})&2@u s=O+ݒʴ}}?c)w_HetV1W@@Χu7霉BbRK0=# Hĥ0Q)B,P߿+MZʺnA #ѲHN4/E@$eia!bus!2B~:`[0/mQ:{IBuE$UZuuZ"K{7 x1Ԕ"SԖ-#iG*,ҖR ~j$+Ixzzȇ&l" ^:u۶{fD䍻e,sN3w"$̍,o??g1Ͻ'?\J-RJeGǘ#^l 9T7 CԈЁ"h]*3x-DSW3SW7;N"z>Zkx"BTK6Hs}[[tʡD%LuLMf)r)RʶEm˲qif,B,Y "rėRʺy3RZm7YgH)|#u.iCei>=!߿_Җ1 όYtnE>f+"ΡAǡf汮#R~ s"* zPJ!p8ơ {zcykO>#_R~%I)cĪL?)n"ȥE?#@0wpg!HWDєfmoރB\>y0~"mZk9֝Ancz`N>v# &f"uiq>H [ɘsZnf~8o"ʔZY*Nu]K@ t'7 L؏ip7"$Z*Ûr("n#N{ҹ%#"Cz%FrudN!Tm[30W@3iqВ$IHdf@nF$4>Ts]u[b9ux{zokU?%8DT&RsDXm] '? prsc6qjo6pwD ~Aóȶ8 1RFH:d `~,+@"Du9eYЬ۾_ReR˜AMw0.|\*c'[ nʺ63\d;ct9D|WTaG,L;bDQ’Zi3׮s@(jƜs\t:1{ X zV;GソLIIS3%ٛ2*Ej\DĬgwk//////"&LF uqp,Ucuy~:K)V9P-yR.Dt·7UEr5{0Fẅ́D,pJտ///_ ?3D ߼ sA .B@@33 aN,s-BCXF 'CZͼ.IsR2#`1`Z<$0I*JL^Jy-KcDzD$0-_.uι,k5}ـ~1"3WSZzy}~9zb (-.qj, 1"e)Ym&5>R.))E[JaG-afa0M.?Oض-\uHGQk]%љSWX2 BHg ~g~%L>LE$!H Z/8ZE BUY ,ۚ."$1J3&)tnႂSFWG*Hۏz:MX{֪"D[33/3>Qk]V[OޙtD 1ZczZ_nf@("~~^9_}eҎ2+C"Bmz^dOͷ 4wR+"ou\^3r fqzǁi;?ry=jJ@Q0kSX|i=J\q̯5ZY)i%@tk)߿;m DLi!0s@Daκu;={N'"z}}y~_~%y۶zǠZYXgotuYp ںkvln"ZKDF.8qԲ,,3r?3t@c&E"6s)۶-Ⱦ߾R$ojo=3{͍#<է'jHZK [+dJ3)[&B>V1,m)o *ٿٿ7'"ޫK_?}Wq"3Ө[Υ/RJJ,诪&1i;1!ќfR0?>ORCr~{^jkxvYK=m{\tpY"z=֖TdGت,m)&3mphc-O󻧇p?zWlR5Dx~8 z9ݥ.,E38T5"If΄HD>cq3@$C/z vsM-L3כd%#HVBsz# R9#Y!c"Ҷtp *~e\*\ G}cb@ٶԊ9%"7cmKDn wt7j̄Hf>FR]23\.Sc0wn D Ρn-5f??}"_/^2?OSR1T-7^"jx1K+n Dy%pioYjk&Y`LK_m<1dNӃ^/ޗH70sƘAn̬~|z|̌fVj 1胙۶a䲲8"eN%#݇۶k}QJi޲)sΈXVk}, /bf05KXF5UG?ԔǾ?>>HH޽o=Ggt >FU{H q)GȲfɾx.Ժ1u?75K ->^R ޑ)aZPmYJ) B \bxj"[&ϵV &vΈ9Mt0Z[ܚq왒j 3严C )xBwW?ݕ2s/ Ͽ $97JGr !9uND$fB8yHɣ~t@~#\Z;7Ǒ-Ĕ)ۥ9_^HjeДxdDHϵռXt:-̈́iް{-Ɯj?^kYhZo9/9DeiD㳻v1anDD@nz,9ԬI9E:-Z#&s/,Y^?i85գ"%ofJ_VFj}zztLx}f+{'eYZkzgkD"0KCxbaoN|̽VZ@!#ǘS3MU#jZ3a̱WS=#3 tb&̥9FK)2PQ|fNY/X__?u/[Z%u@>|/~;~%/goS#RJjS5, 3gCĄ,V$ Hsűr%ĶwUNDz\E$uǺe:{u[ ecߏBBjֵp)R;akYuΌ[5z%%D&N& b4Nۚu1+_*Y1[Jfsx1S/R<`?r:{^Jˁ^sjm@DeY2W,C^ΧӺDljjJܖҦ'y"Jz˥ݒ89E"T3I'x'XD" dsgT&0'VVQ r^_^^THpĭNKM1Hc&zk)oo2TটbTΩ#nyfݝn} Z1!!sN0x-?_F=P~%/_~G)"RJV1T@HzN-L4#3oL"fADi}ߗuc\Ȁ|ErRj T||~5ws95W)[=!>B 7 1f#q-m%Z-¥D"usRw>sB-& @HED1dZmȺ7m]{q$Ԛpmڲ'Lb#_X*ܭ."t^dM;; S2H(s9VRzq\ǘZaV, S{13M h:4%!錈f.²nzsRnmlkm˲~Iո-;ӔNě ޿#~cZj1F[ x1^?|_|f֌oOzR[-C";J:%ݬ-'8zu]4MaD8#㻲 5ƪUȲ,hm)&Rq5,0sNȷ@"BA0K{a3܉h]Z \rWJ.):c@ =<ڹlKe|tZnNz>sH!-?9aps㜞_=ZEU@ЩghTuZLK)BBIŒ ^)-G6쳙,sCDʅf@f~UL"jm1KʦsLT,ԋ@6ӽw3s[g_nq#Ǖt_~Ͻ=??Czj̜ysݢ42qHΩo7uU7tLf~\kkZiNKz.زNúJfLوlRqYj)5GEXDPDT-zOWާX|9t͏1Zkvʜ1A:{s14դc9?8f"wS@Щc8D QAdf&,oH 51cd$Gkc"Ų!PVq3M3 S'!|>cV)\/WUs;NO%0H2NM8z~j)}Y$,uWWeҚ#.I,,0T3S{cRLk9R9n%a@B[Rg.Dn ,Us,Z-%m=P: 1v"BB L$v`Dy97QBy2KHs%1i-L,"svTx}'H0 w/?D{"><<X!`-= EйYu]"B>&1"bll 9#`T^csmk0q PZ jKBi !hҺsNKjy{~%/sDH)fsG"3,5XA~P . R zȥ~$iI$VvZk9£ښrU545Bb" D̙14<0"EXmf ،00wD*)erdstpq\-҃~LHH4Uk%J)@49qsبj#?t =nRzgRJ  Mĵb-Oa݌:ǭC#@k- bAĵs~"%"k5u@7SuZ>Fj)"|G'D:a]p>:;Z*D1|sXdy}3rm>G?N㷹,{~~Na iZA@u۾}~fVR/VzQUfsf_3oo/B ٗ?)1RCUǘ} DTNU~ܰ>8| D~^DgћjokۆZ[pN'1 IDAT()<9}L[1'f;"7sZn"Ajz<DZ{5۶q\mf-Ӝ)Z"-K3SD}?Ro y3̀t("ݒtq3EQ<&d%iޚ,ڄRKɡnZܲ fnKsLn;ඉT}lyaΙkpp3bhV2j`[W&@ UA"RRj`,%RodNUxD@>ѧ'P6O3 ͦH)_[K?E,pSӃ&c* ҶmǡTj15SK P1.7`%-N譵IޙӮ= i[֪NP}c*|7I9jx"R<"ôRx7Y3s@&1sK)5z~~!eYD Zrc{A2FO,bܪ#q<Ƙj kHDtkdeEdJY2DXn~;3{*2{ E %E$& Ha捠.{muY@c||ޏ#cWSkjf_{A̜,{$LE]R` KY&$@5}\ YO\$5jUt3ץSNȆژcN58RͫuYe&-v/j|:><>||c c!RըZUH"9>8W ;}~%?΋i9未7= ~R˶HoExL}c{wR! sjm7qk5sLD|^_wOOVwo?~Zݶ33i`e]}?.%r.LUD>g`dRʹemHlgM#xۏ#ED$,+Kf qGabY?~q]M?"43FqFV"d߮rf$ 29է~-Z.{}}$*՞i^NӋIsKJ`"IZˬ|ajsm)z#e2K5}Һ#`T,;j"PSeQJ)OiN͟{)"8f NB@k "Mʡ2X)$׾?2s>?E$c rw:"m7[./g[l[YӫVݼ ~3ifsV߿{"6KRҶމ)7{ol[̜sOF!?,kz Yº_ƖpM~^@Ȁ\Uu^ssnv {^tTk}ʑc/VЬT3{&aZ;ץ*ЏjR2],ĶjD5K铫eWǖҜB٢5hN{zI,9~1<}rȇcм, 0Ċg攈DZ,F ;=~꧄yRʾRd AR6wwC~X`~@,n`mB p@J|׌Ȕ.}/ϗV9s;$4C$ &f^ץaR ,Аv(BbC3R*nc`/k0;bgLĺ. fscCC#wjl8EܕI ==Qp3 wɆ'܄grܜpyfAgj66\ )[4=Ho pfJk|G1eL,ܖv43pUD33ڈ5}߶h1eͭnc;zQ7B9Zf?2)T#"sbL@Fnɱ#Zm?//Bc$$? L˥2ǑbS= [T9Rk-o=e)cZ,iB߷8:VKc {deyW/ZyR%,cO.INX%s4'zY.97{t{5yVć O@N>݄*f8rP|:,poן^ȓ 37Ed]<ޓ/, hbe0t[2sq9!e ~fIa-B-5}y-@"̊L@!j)rY/e5Zʲ.M"ldr^U/0S~fqf?%=<\}YքԘ#"D$Ľ-sy#!t۶~_ ܇O[Zn//ctadf@lkm”jAG/_77SM9" CFRDҗ'N:,O|83@JkUHDUJyJ\9V[6F{\‰3a u=ac~.Xѡ>a94`\%_@ D17t4vKY8r}XOA=6g54\-Kb,JB tX~Ng#Zka%YdrB28^_"Z+i:5gc}&jMF+ 0;Z5%D@ppC/̩YjkVOj;P3=@3cC.\HXͼEXlQu0L:Uj9}WO73 gZkuݶωKS30Ȫ.6gdf " { |OcZ8=^.R-q44GyZ[]5 u]b!0W#1cZzMHBGf9uH+Eyz>&8 sRL׬OYB)0g_n1#8sSd9`Lui/&S&ħ$M5D pӕṊ&IGO駙xg[k|6F@gRZJ\.bN:r.pG9J)nz) 1DR!,,4Z䰤a0T[Zu>۶jD)cBK*#rJmK93@! b̪ci! (5̖z~[ks9iߎc?<\XJ3pDZmrH>|8ERvcL팴mY*32+޶uy$rV\k W{?q-<r\̵},Z[l9\U OI6[NKmc9F[ZqjM2RZ<;!ID0ؿ3 "26G'%UÇ>1ƘJ)kkMRH5F#N_R+ؙz".#0V%=9硩8=sHd#4h0SJp6ĥH)ΡjQc)ҸMwDR-&B$.pSmZ1Ӄʾ1:s{#Y`jV\y_ȉuIqಶZ qL2sc)U2P.4Vsz-E m3qlt]`"n~KsZJ[t19oKmE2g?p|{5 ?z"\v"D һ |Z_54&#R̩Sge]/D~@㘐D{-$_?Ӳ1@"%u21Y3ۏݏ/۲:5fl?D^bS}FJu]ۦ:D094rj4_f×Z& ˲,D4Hչ#9&}'֒a= IɊu ~,3ҖV3gV"ɖHgE9EՒ!\Y̬=R6ODDF81D B%mit@,ص. f&-6L޹DNxW_}ufpLsEu)H=R:a@l\s>B69p3 tH&0y',!iK 9v~VHEs>},ɂY׵U]/Z#=0Hh1$59&L|92/jD"Sj91_gB"=sQ鴖23[%"C@;[=t?_/џ(cDkm] UWQ R*3 Wv"Q؏4(DRk)^`eM3~*0!C d('#%wxON >qَeYsc7Ev\muͱd27v"Q9{z$|l`X'yip}c&z-kjDZg>O`eGy9aa1Zg}&"]B4sSRgSU2)fZ}Da^pRR$—Ļd$ӛ'zTofY!S\jgRIם8lFxDJ)R$[m0}?97G/n 7Ӕ&~q^82UQUӚ˲~S 1jmP- ĨSfAq\y8B,gN7?Z/T{RbũNHsK4@xii7a H TS8cJVJIx~֊"gbez-YksGDZd>-k)Bon//////ap6(ojTN !MDb< ZkgӳRǞTĈyiz^zr-)8c{8GHSMd57P5;B%0}STRjk%u1Y2! a#1f@an {;07@LV%CuG:8-~}j.2st9/x-L4U̓;Vk$/9i$p7#G#Sm53opVǩ6dǏ?v9{fզP3ͽ=lZw`3/G1qsnE$~DJGc)51יQKaZk@TD1{WU 3+Tuw/E:HHH8jp:)LuUJAzG gf$$>'u[) "<95Yϰ{sPֵ<,N/HiFKI5˕ۏIrq_dWȌP~3Y$D^qPgȌ/˲4թR{~ooonX׵-o1\̚o݁L1c|/{HD???z?K|gl:eNS'U""y|rf3~3}Fre6GkH1/gKDDT)ƌTD}fÇꇗHlǭ-9ɀR; LTg)XKl5۶mTc:N雎$h$8`^^^>\[[5@@!RNoc|"Lm"`)H ǘ82rY!}o"񠄣d$\O1 M)C-l!#9E]#('9$DwKzw7[1e#`xLy/25@hK&ӎ(9>@"nSGފuE rwa>K~1Hwʟ^nmo)Ǿ,i[kfvlۺ.L,nۘCX ӧrEcvҖ0L|wg~q=t*xR"TGܔRmYEdQjU+v>sG{zYOP !"x}}Mr}!A(̭q~ <-}vB "%5^JAG?H>ltA*- 1f@gDN$Ew۶l|~jܶ-gcL LA#m뺊MT0TC.USQy1~N58麮hi#D"N'"J3RiV*2>>2TBjnDT?OA#4n&x6S0N{W s뉨 n&R݌/KyG3sя#`f_/RJ\}'l( I-|ׅ9-^"@ O8 Qg^G&9 mKD dGh?گL?[/~"SakU{ER0D$WI£q\F[Z ,eEWyyY2"0)`3.=2jMOX0#a1!y1a:!o)nL %%c {۶!5Jϼ'-"TcgsC{=k0u0gH E咐gӶ$rl{&ruAO؎C|-əY<HGo[@LG5o 8f^V&xL8.xt@Dn[Hr£$c̙Oי܈07Uқ *x2ۖ\T53Qx<#Q`fkČ~&33-n:,,Dd6!uHm^ژIJq.7ܕ? IDAT0JctD^o^Z~{prvoFSHw~w"{ |mSHm D `Y2 !뺪Zv6"ӧG0Gv[{/Jֆץ1Ӿ2=ڋ֖ddd"Q)"ĉ:nfjdj .S,rK?RdN}')EUK}H~&A|S^9GD !8SٶmQJyeL3w"r]ǶGeYZ+R[s7ztZ5YLe]t s>2lÇ+>9$O.+b Smkz8#a>uF`R@Vtvsv[MvȜض\9OΗG"^kKkG̣gFRU3`[-@4} tcUkmauY9(Z3e.EJm-m<} R-,k*E2)_[}߷R꺮LȣR ls7fcYNYA)7ax@Rr=(]jLmc۶ޏL)J@8Gt!4M&sP xTg *,}%"a< Mu+w)!ƘiwUlcDcGj} 5Z}1g|ͧ8j)Kr4<^^+"~+LLT15_־ϿrUTD61&R_|[D0>"C_s@0x_ I!|ww7D e),$Ru~p5}ߦN1F[- OND}֤_Q[^[DVsդZB07/Eb:'3N;>z|B9܍Ic:xZD3=aNd6TgNl#kI3"" sFD2ǙQ$!59=zshǜCR},߶̐- ԖS !h9[[DN+sJǗdY.p\RtC"-o6QkKkO. Jm'|s$̰ όGuie mKu=jUUG zD_ʲǑI3cÐm)IO&JJɔ|i Vk-6-sfB B<+Lv#GS&qGOI˹{$~bxkfE L{*Z+3Sl|M1wQҖu*sί*!C3iY.?~Ç?@ZXi"% 1f\ 4HEeӶLT[[B)Kg*% $DUgWUILra>#0u* ")jibdDnq l."Md,EbS=S8H$ero۞00@,R.zY}#b.UK7[j-VkCo/~콏R${=հ+;?s?>}/Nې: @|y-@NfQu]}Ezq]8Rbfoo̴˶P+s):˲$tZs,^kRO`V`k-h%-K;}|Ӕ 0efJtv9E TsAS,Dn#bZ0ieYKI@Sg?~fM5yYjkFGRw31"@T2iq b5gm9ǜ\8ؗ%w?V2%Mm?@)iaė=ꙟ_A'#"*b"8ȎLu7579hUKD9!Bk-LNhԥ-:uُ="H)X9!@omY'slQK^VB"z)u9Ji}~Lp]V  n0Tul"#(?ga^:s7@.LpdsV@*ZcY8V[nk[½Ժm%JD4/ZtGR(vC؏~9Q5mAF#El[oDM>f$&0u2j6딶y:mGjgbe!驙jddAMg 9Rcr0WV@gVT pݲMDɯ |xyy/13=ߣ{0fIRYxMȘxomQ71##=*Eu/ 1##?H.3#_Z9qaDe} Dfm?u13Y$T+fv7OsyƘIu > "JuY`t",JV٣wCp~ץZx?u\ETeY>aۧOv]] a]fRua xNe>UGK@uA@+_~g { U)e8IHn:fv`ǜ#bYQj(8x)rf& R%[ô?~("=00cٷ!~߶8PRqz"n[)TpNCZʜ8aNzY)@0~f2ӜcH|ﻇ3K=cZ#Ryǜ#e)sybE9 )fEOxW2jɐ誓9Ns`"d^#Rb'@D`iR1Rݚom]RRX!4jmÍPңSf6uG.OM$k+ES#z@L/ r$ ,ĥ,ͧ1ƜCU[kf}"™ɜ>Ɖ ?9I&!=2{lM bG`^R[̟>}\.Dh`wW­VHKjR'e)$Lg@fGq]0uA}\쥔}K\2uMՃasx"EPhQd+7 Y8HmSpfڑ)<ǘG7R2N<xUPM$;: '{]W=Ն'SMQW#zq^2'<_)EV)h4S32as!9Te0ofvN5w"PT4DL?!&8r)"zևNu5nGJ M:0ѳVixus$D%#5ZvHh XXQn\oOD\{' ǑkL>ɛ\G!ӎeqcX1FL "e\z~5=IfH7|Kq]+5<r "\_b4u#d_~ͷve< cY-DRk ;T++SwOsruZGG9Z]zߞX)iND?dkna^-s9qy>cLDi-H`{z;<(̽9_%"5{‘SϙM1rx0 ѡܐ:j5WBLR}Llj)WwiGBWK NHH \"`[kMO}bZW%T z<\̘uIaA0;1uD8aE3 "zT=-D-2fQǞ \J<4Sjdv<U5ޏbԴzd{t@ꜹ~Ko)-fn1ڦ>{fPNu .*v'.E= 9ضZ~0ץPwxyٷ}j7LnLu9?|콿zZ/ٟϻ5/rU`X$ 9ŶH˲lv[:R-Gj*RP139c,˜#ˆpPlaiPJjz|*P|?w>Dj Sh_g8ʺ6wK" u]K,0Y6jG?NL>:e3A7 R6 Dwo*H:0Hc~Nxr OI%lD􄼄6PUJI K[ g.<ζnS͌YYճ ֜:gjP-MN@'yut#\ #|уDLL^]lH:ê-rEb\S}?-HS~T"pCd stzo _k-`}Fj38o4twhK|39_7O|:K)iA1'".뒑&!̎fcf"ܘ㍬w_Uf~Ϛx/5O?͑&eyc(9Us׷e]@K-R"H@]u*U0gO= s\(C|J2=/wO7ٺ19G^??,lR݃D~qS)Sjԛnn5."9|̬:ң99S 3IfnbxD^aQ9GXx<DhE2 0Sz !͔i$,ΏCJiu齫d40@b(f@˷E9$H6ZK9W9?:Bk) 9٬եy@,mIտ*n@3$Ug};R8# 1Iʓ{s桙K76#W\c2; J6xܧѿ)KTtTU}/yGR%ߙvzd35!.HҼq:gFZ[5gӜ?5#Bk-wx15Δ:H*&!`JLϥlR͐4_?qpN͊9qDZgsDo"lK^.eyЬ)CMDYbj7UpcߏooO>e>FyQwTS# sy5%_Ԗ s)VRWKiҞ>(0*1ԌȆ2tC",jfD)1d,"ʁ1յqDx"r mۇ/MD\1"{Gr>mg} v" jj3Ĥ$V[]l8F^"o}Ň6U/O_}?6wu/\Q7{L &R5*G%~Lt\֩5ڷs980w'jD:JN}UZ{'L/u)S8rlFSeKa!4PUf>N^x""7'xFϋED0SO;xN5Ya\rf؛#IF@dyU#]5jhvk˺f&E~y?߉fr]uckkDxrquj }۾/~;W{ ݝg^h>:5 q'`҈I[ƮZ#G8M+ɭ50Y~҈̈Gk-I܏#ccemie"қ;#h7{Of?4y_.zp7Ց8RJ҇cs[.&F #O$| //)Ɯg7'R z89f n-"<`&&r" 4if9 N @Gc735mǶmsΌ"v,*Mǁ缎H^a{ݜA"$6 (b9Z 3's./"DX9: p)IJD@)Z57GPsNw+3u8=g #\MGW0ڜLĠ9?xHH\xF!' ȉqzAmceiDto je86׸o̴fl{o,KvZsfh!=>ue]Z7 e-̀&+HVXd9gZ鋬j$Y`n >ݽW{mLi-D\橵\.u:K$QUgz_k"}lm]n3Dl!1O?{{ɼ?͐#yJAP7w8wfQ4eJ놀cXʉN?x֠9={efS5N\F5|9#boTROt&㶭t:&'u.iwU5t:a J)#tB$q{tXJcHn<&mHh8rDѯC+{s3IH")`{oXu߷ZMwAC[f$79GSʦDL"q:r!Mo.[Lv_[kCfba7}QJ&30@);; aV`b"2pXr^kuR푓cm3"#9,)e]-պH"Sk۶Q[mڎ!]mpe&[)r20o͟}W"T{^E} IDAT><|cKbV$p ۾׈KY:H[@sNR&B$BU >g ՞RÂzܣz,OXW+%G..O,9>"Ch]GC|g"R?`P#\p BJX?lUMI)V, DSѯz,5%3`[pfu'S"bȓEsJ%Z۶#h:M5#{5=^`Tv'J9e7޺=g.Ń5gfl{k`6L ;B $5#O Hج>lt$ALEuMSSȲ飏>R8Z2s\/}۞tX:t  ſmۧgB# b"jfُhCDq# pR\r4d1|]= r6zO~bem#wBj]U \DlDH^7,$9Ub<~hL(o kkV7 i(Tݬ)I'Lvup|Fw,5yemmy!7o=2-PdcJB)ߓs$ ZIR lkCLwJFszHdSfrVZĶ8|@9I<^__#YzYܬm4M\V_|aft}[|4A$7%&:( `-ZQtt5GyZx:TۡLmЭo!=R.ecw/e~Er KL=T$}#Gwd 3,:F'{z}ZJ)˲7?PH^TCIpj?("2!rb?*zbQtsXC9)er|駱*9co{,#b0" rk"%!v_41$jfAl"gБ ~•RYFh=~G` >{k.L0H$l뾿~$œ @,|)zu+)V5&bbgс #yiZ31ۺeC4-q ДKoܯKȗH$OS&oSJ<_K$ ukcnxխˆ8ϓ!-f'b tv#mokKƝO?.$&&CHȀa}w]w($!Qjbv???RR9 >sEᮽ&ZL#0Q{Q^_?EǾo~Ԛy2BaC&5$}oѱeQ,KmzTPM xvp n3Ҕ$0 |TD K>62Ɯ01n1dGDn[s5)%ѓ9r&g_ 1Lr&GBIG 4 LJr?{0fO"IX(~l3< x@_L$LgVt-3oޛN@{ qpS?nf0')%< uhr}twu[m{ eV9Ir'=Vǂ0ȵ)Β9g鉐}0y}}}jv$4 ԝ ۖsw?ۧO{oLn^kסmoDy*0X} #i*ww 祪 cΩ.$n//sHrz[NRN_oooaVZA$5"tu}o~rLƩMCHqq /˲;1q=VwCpGS1N\y?C1D s08 i<Fua}ۺ98"Mugbk$iUHSݺ#ZȒbu:0Z(։IXRum26D8?s!2'3>Wz}jGHRTG`{m̭n[cHHRUú%`C%`Z{kz$JP0܃u /g@# PB"`bCԍ u&bC"sd w kO849ܛ7"/sd>zx "۰  Cvȋ5X. e_Z7wci_^?n }?pz4oqZ!]uߐ9E@ r&2zcY7.:Pcw_we,"L<Ĥ 5M/>g/~oG[ zsJ@-Y"n1vp"眘KZ?{`._.J9Wa X}4vL$"UR .Ǽ+k=% uM4ͳ "%O&$A̒DR&VǾ\Dd{U"bXVuwDtZmwp?.980'B.J,}(vتpG! | CjD%:N龭} !m۶G>yB=LrF ""}agx޻ڢf湤0~GUHkmC4wKInj!QcP3Zu'I914"a55bj1 @逩X誓dL`Reg2{x+s,P[ĔKI"5܉P#!E"Z ;8! Yjݑ$bbƃ( GfzwN$cضU&aa::Sw?ͬDO'8r ؉|I$jw@̹@p0sqdzcp57 I۾z_u}~~+Wں;#$CFDϡD5n;!+ yh-3skãBBuۈK.p_e\NSQ57GO[mPr{}|wK ">U! ~ƹ>ض PR$֭Fvj}]c>`cenW140qo6(xnniv7EaHc zI~'[,Y fC$B$1 K:c[}Y4 (֩3>}cJ%A~4j'ܲ"RN)團vBbBw"C֋xOi.hࣵEKI)es}U$p0X| HJc adbpa [pc >Tiƈ]bpX Ծ9aUL)pR.H u7UW_SΙ NnJ籃/ db֔23m*湷s9Ic0 $r6$2?}; E\{.\$W=g靷}W59'C/y[a_ Ky73 tjfAUo%?W?yЪ;6jkm];2sNĤQJolʅeDэIJNfJ t@mw_XǀaTY~_PD%)IJlL3X^2sk4Q BJ =綯}1D-sY˃pBS), G`*Db[(uۺ Hd%<8IvI:h2*^JK8v#ZAE ^w ,Z;'4b=ррZfjz8 R61Dd!a l ؆7: J[o5)eɉy>~T/"rrGGg0;әa0}kDɷ3P‚w"=Щj)\&$pup0@7"30P"&6e__>rJ4 U0C5O)1^RJSmM%`ߗec[r닚9V Mսm "2%7luo|)ު$DL)ըobY 6y2cl_wFOu^̝Bm.Si1#&9 ֣9'6tDv{I=N7DSLp/xr\r%0MDH;ZGX&1_mXu]7G܌ujj3$c"y飇03Z73cNgJ*.Dh0ޛefBh>J) D~E* Jax1x34M<4s@۩fC{i Ut;g]ռAm $#5UÙO f:t$ʱ"dBaܳ <;"`Nmk^Muf}0I" #m*\))~L7[j#$ Jzc6ٔ֝y"WsNO5#zX7.Nʡ ~Nu9iZU!z[K)%STUX"~%a j1=0[z!sE,\Vo6p"ubw8`ٗ0%GQDRζmC1ls!|_x{gb@D]{o,QU5Cu("iwJcĜEsId63njz1,:Rk&VQNgP4yJxz'!nU3GBWj6)+D "!BTM7%%`6D4C61u¹dFR. |Ӈ"Xm*i\p'ϵVc۶m\VɉE͙Iw[uu^im}`YYZSl?՟jݙO) > Ϡm|{ _84s$PF;f2B/@2)1M%9g)R[աֺJ1n78XT"¡*"LcMj(朧iEƷJB[ 3'яGn/˜s[z}E$q{ޏlHzx͏($S)=pwV}w7?YmMu{7PۑX (ZWHlHԃu]۶n뾷h4FV[ku}oIiA`T2/9CD"itDxJ.9`#{Eir&37w/mk5^ɀєӜ%%!iw}5~Gd Dw5!J ;TI9gI `j$nܐPH‘{w Lvmcm]8)L%t7mn֏m^dkxgbzzoVX8F5Ak:ƶ01E>RGmR$l 7d~A&P&jqj$)*"ڦe#jnETs$Wp)1LS=V@[Y"Ҷmq{*>t0,)b1$[UM,83ڷmKIzuk stctD G.9&>dvPU("q/iy)EhmIX|m!B˲;8;sBfm@S_Dꨭ-}2q  ~>~HIr}У3INX[w}[.yaY.˾!qQ,)bo׆|gpha(,1qIX7$_>rN~w7~O7rؽCH9;fVyYrn88!Gn^+ IyZ ~3 IDATې}WnqhD# vTvcXYc[Q!( K@j0"ɫ=L} i~}{ɻPF$yG$ǖ2E`,K)B,RJ_Vee<۩I$vRJIH`"#!֖d$I7 Z[pT5))2#1F~`FJL œ#~YaLW&xi:"m?Ё8]8iw/"|!"8 \s|zjvf' K ij!M{;`<!Y$=]DlV=!!pzA̵6"EЅ r|x]uuw(S>LTn~.Hiŗ #!k-L$sɥnI .KKb} 2ɟ[|lY8"!n#4%Daޮn^33SbSe P`9!b{j:SJD]GV7#.RУlfu#^$*><- AI)"+i2 6ZmyY}GB^/ йC7}-W!jcyD8LTӀDwД$ԶbWIG0Gܭ4"("Y"X{6B>v<..Ir,);h{kCc6pR1fN)gI11UMec 2OOiZ$%O js X+`NeZ'|{gz#L}oۚ$M&03:*%~0waUO|\rIXD(TJqgaciZr޿WJ90 ͭ!I>`;Exc0DWGosѷA<*e~c1DA$b>O 1dR8Sp!)[f^k*x>ĵCJ,N?YLLRJ)YVzL.菾~;?*lj԰ "!Cw'bH)PrRJ63t>x |}"KTE؏j/TS&noH֭ubly\bE# 8i/d~4-˅BDx>2=rYa9K#>I$PLyáagM(6ErJ9H:23n}]"`Lfr7pIFz\DbTx B5kCQ# !l'9-9R\Q0W^ VJA<1P9MbhO%" ",xp[%{s.!\RwCI3p9QSfG#<zk#R$aT ຮ}Rz8j"IR&&t8<\޽{Z]]p|Y$꣙j;><0=^SfSޱuY.u]71S I$!v;/ aCp#qe~1"EJǿ4Mq}yj#պ⪀f)syFs룙ikiLB9 RspdA7|ݏl "ԽFll=MfVm %RJ)Nj"rZy ,D\kcD$#195ƀP]"r^0ryB}kk)y""&a1{o]MPs;q&HT=q Ga.9E30SJ)A! {JL9"!0s81d#`yk-͌1bIU4{wmIJ1ү\jv{Fn}I%^~MC@8`.&DZ[bD7u흙SN4]]r>E(𘠚1m3FRd LXJI)%"~zZ,dlmԺսn,~ f6M倶Gޡs Լ,q:Djm1g1lU5W)ӱ^mY_[)gXwpG>zEa`nfnB!o[ __Mm=6HۺN)jtZCKBR橔1Z]=Lh\iU5@|m۷DH(XRkkJY|]R23oۆ( pFbd1 ˶mL4řĵ3 zrf=0G TC&3z[K,~Y鲬{L$,yI>pEp"].Kh|=I :L]޽{w}nv_9C՛ 1H[X??cxkv/|Y.Dx  @t7 q^sn1NbfH4@S.AD䔲S:֦h4X5d6S)v?dfApd:pa3ȕR.c+9)???E"k e<"պmƦp۷1FJ]${Mj\+3AR 9_}+3Qַm^%yEsG2=H Afc8x=c]sjoi`{ [L7z{HHB3iomNeS?3G]?pWLEfbI,A U 0U2nLjx6s1Zs7,w`c{m7EJJG裹ǢxA K.\eT rt>P{CBFA,_Xպ3 Qp6(1*}sꯈH΅hI{\YD6j#u]^/! !i9<zefQf,̈nZ[U>==!sBӗ:www_5ˋHm[otꄕ??4DINgo"?")%@?҇q]W4D2q`c$qp<)`|#$!g魶>jjnC]F,^oW5˕Zko1z2D!K0S:kQ+R ۾m}vBZb@N$sM 'u%"heatAi!/!2)Låw-_tQ>Ida 9@5z"ThcycЁ >p5mv1&D4%oEK<[pYStHfo 5\=1ɂ&t)_k@y@1L- w>tFZPTAdι0q';8:2uk- [y_4)I.e'S> G D R؜񖞛ԣ%)ŤHED(QdPĘ1Jcp_Gq2#dctds6EВ3o_wJX)D)"裵&IC"콷b)X+',%&Gԋ,zRs"Bb!iPuz 0s}hk:]|6N-"'ٽp^2p%pDiƥAu#:O7}+cSe\+.l'peYv=n ԗR-hǢZֽUMFI)9&ުٵlKoε>'RdZ3H,6*HA~H6`-K`=Փfך?ƘkK6UU[̳>kϘ17ZKUSPЋJ9!X2xD.˺?__/g*' ܈~[HbbVOm;we~~3!~71u>LoH߶8YJ[dN9&9O WD._.,6a[ ]E x$zoooc,js1n <_4dO&✗\Z38N$BIfz/Y ߮lk0}h%mF EHDhlȈ۳P삮n0T81cL@Mh!z.YaW}}iYJTvqaB`B~Yd~mΉ6l KLr}^M Z 1b^!d* >%-13DD~Ҁ1|^2ص&V ^4{)}mX =EharϠz2Rˇ|òmcv}qG7:")ւ'YLl-#n۾71,,FZ(K)` >uCo"//%?k-] SR3lhHR{Fb&۶gn{Zk=Qv*g",,2H<[լH#ftveD#[l?Ĝ5C Um̱m5 w꫏ctlQ0B ^P>œdEd*ƒk-@QƼ8:u3sPWluvK-Dyr"ɨ= B[2c^2(^wPWϵB-Dqt4 om[f`I3qxqMr@ֈtPޓIhKk!L4}A/tԭN*ʪvf~ݐlk &^q#=BdD"pZ(+ \6b">ǜuu+ Vb13I.KGҮvN"R}۞MNyiVK"g;&js]qfRR}kms#2%NU~gȵV8Σ&,{VU>Ts_E0"2[08pCA-/tds5KUwo|9aZ@ hb?WG2 RљA)ķ6Pb8=U+ljӬZ2w_{=Ϧª BL !TEDD̵y:d;1%{oŊYkAmI9g220%}n"<'\[r{aL=y^2i'"aۈ޽5!q_ARdƜGxx QR;ԭ;MFGFx;t"Zq<ۙժ(@VZ2 a)QгZieAĵ̜mS3S5-ӟzϙsJVU[\9[eqZKkm̙p3_{@ٶm6FG;(%>*Rǝf φ>ܳcZZEA+HWev'9zo[oq3AueqGFf̉ j^!{.q^whǡZJ<9 R^_?`LחO_}Yiy,R1AxDBtsח'~?G;vMal1\ XkQN|4i۶Zl {rcvϟߘin'%K O~!hCp[1xs§߶`$5%:/$ɑ*{cQI8A, 1ھƘ,\9/T9EP#{-T#:- cT;{Lov߷RmATM)J*p8#q9ǘNWf:7wRc8p nT2,{[-H]csADe \hD5H{&y(LszdP}ߏx[Qx>oz,`ww'Zm*}ͨRX5h %3Ў3\v>g^v1UU(((b x<Dm7"=HHA#d(ʢ"/~`ݷ= :bEɈ#2(rF_qDoDۥv# hmnuu}ejxUeRLcLJ7bM}Vljhf՜Lj硪^Saf?~<<4Js^zy+WMKmm2m붡[ Um۶vzGZ,fRɌcտff 'u" #hXH'y:_ԥV>gNZu‘E`HbIKT 6{^o̤ fUfL IDATpǓ3Ǽ=7|R$ W=iʙp>W@j6tx y yORST7Chz ZbGkv "2y$ x>"\E芭x{{y ۜr*gJƨ8ߪr Qg51+ ~EDR6/}å#aե̈sdQkd|>1z[{EfgK W~ӟn7L0\g3da[)je+tWmj>_Hm# #|;o(N0"! sYv.:R,=2 r+/c刺 $aJÃ]xesDvJvb}Zkkʋr'$Emn07yaV)<fwLj銶Ϲ12 wf߷lSޗ6 W>B\|朽ϤAҩb.z6ZkEACAn 7kJZ]` ŬVk9,VݶZ+53Ěe&"QR SD;ځ 82@ZU-ňrN~;sgUD숮1-L91U21"9;NrNO3kDdbWsvdbu^o!PڜsIG2TTlFrY99b?7ƌ\qR 9brW f}e D3ܷg:3"x<~O4I̎"PA`I=&ْƶ" y K5M;\A.6Du>gfTPK-fzw."RX&Zx<sv|$̭//! 957(KNIj:'#]sy}ۈ\_"'e1") qQacO7)t&Α$D8[lL.?ǜQ n [?G~'!rz᩠df{̊{>4DǑydj3(<|"j׭SOU۶ݬDľߠ 콷v!  BC$G=bX +$V o[/fD/K)2nYkCKD'WChHD9Ϝ+E}3 #Y> lwlj[.zh֭DgdjgRF\ADfr9˄E,™[ cjN(3bD#˃2ojG _++"#b. HR ԉy cf{)#S2M-&~k!N+IH͈go ks~9FR 'IJ*Ugg1#䎹zlW?F\.T}̑|go"Tvעe+b嶫ٻN\>Ijij@ J&9I4@@gb{&Ȝ#UԧO̴x89ORkRWZ R{1ZiJZN"O~I;ʷj DGe>Ldιm;̤}twWQ7j!cG I )L}: K)g)DNI*SR k(* fXMmuPPQQQ1F3> rkӧOyڶm)?CD6T?EXD7bý!85z3hL<}3+ Gs<ޏqFSуC$SPw/CʻMX wb12r QcsoխZ*-;6g&򛶭2 -pB6$+AHZ6pDS^ݳ]yqTk-s}p6F}RFEoqc9W6^I@-2}yB(<;'V3=fU3gR.O;nw>F<mT'r3@oJ ^SxT)Tu &~zIHDχkD0k&FG=$Q݅{4ADZzoK21gxd) k)}ZO;JT|zohe@{ 1ݫVf3RL &0Avf{2x%vW*7Rj1DQ9“m+I9} "~ 05+ʜ|ӠRv쪦j؞j~>=q\WJ1 QdUk*9FGbe5h@y88 |CyGk~u#lgG$}AU9sB5ACb%1г6Da##ƜW}ƃx"mBx}(>Wms`] >x̬j;>rA݈sR2QPp @pe X33#=LEY !0jQ/%0i*[K ') ddez$dpw| @j3R//yJ]5KU"k)cZ1D9V=#&3#}~ 3 ބ(JiɏLDRUK +- ,Z.xiN+[0Hjzz0YzE %SdV1h+ȊnyP$"'>BybC$0ѵR[2"2$yoT-cKIh&@zsyV_ff "" 5cF13}+E+Ǩ}$F\m|}B#\ϼ|RO)D̟̊!һHHTe"UD*0/vy%xL:9 RPg]C ȡbl>,Zj8{?qX}^$ s;.ZI9qr)lIǓfrAU+ 8pg;M(o{YJ&`R>zmbG}p5D+̘*B3Uؔӄ9EGI.>99S};ftJ7e=γb__`mٶMT0_{K`jI,j Q50طmv*8wٿ~+r|C^yD9gyJA୔Zj8Q91Ze- G[51KD Q}>/˾ߞ;0W_"/( 楪Kr0f*1c9g, E c XZ˶է=%jhȀv cVWK 5M U 9?e.+} T>TJE!gRQftr\ Ed_>h@SD~}\1+[<2X42煖TFX}bVm۰l=kGD[;m߷|zy3˜#Q+}hO&3>w$  L| ru뫙1؈w KA*: ljQy+#JV\g{cDDٶZm'Dp*XPfb=H3K)l͗yeռiCЊC 71Oj}$e`NL̜L>lH0w|RİmN4}p壏{WJt[O|M,uֻg>} hkYd{^_?jБb kH6c[~ly뛪m&&Lx3@XC]Jr^EHC=1f+rnYq)-(D"D$fxdIhG` :_ 0yƗ 3&b 9F nTU6ZCu-VØf D.i՜ruӪ"R ANxgzxy#BYVX cGlPwh[[;C&3A;GPU}.1Q Hܻj5(X-Y߷molq<0Pַ)yeQ1jPhC9]"rg9$f \K)ɬJ.lv<@4t"/]3K*#bxQeON"O9̊Q`#q{+tFoRxwm0"&z0E0aOPȂD뽏͊ U\Lp7QcAYtL^byXDȈ,eHYJm]Pџw"p<<<\ D`3sXRDŽ#uʺC~#i^{rYjYYWr-%KVu38a g ?O7#H'Z2Z?O0l`;C'p-S-7PVtGы0+tY |%X-* ixDJ$szb.,>,y@|ƥaI38gx93ӊ,ԢsiCsLvBzVLc"Wsl[L-2V{oXb!"ҼjS,x QxĜjlV4Vf}2o2?޶(%fSN'EM JT)JbI˱Fx)3Iu7PW7|ZUZ+Ŷ" ~ؙC{r]ʼPn8 rjjf}r.c̸W 4ZtfLZP 3t.`|״Wi'>uT~%P2Uoekgk Ld9}|JL\Mw:!"ns2᭭J̬(Č<%]Tb1c!ǘY(<vMXQq@*pe&RA=sL%WVSU toXn[EgFnm=(W1z LFu(2hZLʬmt>g뽍TT<\UVK $c}߶K?\fV沱3WZr.j$KN$|xyҸ0Q#ax(قMmoog,KF$PLGo)6F&9=2)f2rEh>^3K)<}ife$zHbe6&>#|~kD&DX-ȸ߶*«1K̜= ePH'S톈`1NuLL>ǜ3#bZD9h2).#rjNf(2ՠ 3S1Ⱥzk dbVԦѻ1WVLj )l%b)Aj{)%™3RF%"VS%2ҳ2OjU\'-.A<(Ag* @ɪ۽4++-9kq3V[EU00Z79;Q)en~gk]D!RV/%~o᪒y SwW YՂhH'l JeZ,חϟ>vKl3"#'@BW ֐Ü}>'gn۾mUx!ܶZxLAfr+vWIh 4?̵ZjޱTUVA]A5zn"&3[;h۶?ڭ۶@O0i*4 lkE]SUNLA,.R"L"lR43ZL,h/t l{)AȞd>EZ 1$B><x(MumО9Ӊr RRFs3 9Bw4=FDF$py"A9!sSɀ29BNUx޽ ^j<@fRƪ{W3I)!<ӘKRBmcH_W"a_Olu3H.HO%Zo*2{X2f IDATjqG׺!Buqᷭ^ptg1 Xgq"iVKp-2;2gx[M"ecOG??|)t- V*ZhxQ‚\fc9RLi$OGdDG&,"-g|YTnx_s׺Q8@JY7Oi REJĕx6SsrQD}a*̗vNtΕG dԺ: ijbEVck9Rы?|aTA܂( #("K#PbFoD1QsRJYLMPDD,ED̴.RT1:l[08'ZdᒔR}.NYd@!490UümȠo2) ,J) S* \z4焲#0 I'BҡD1wϘ通q)e8e)+zN˙OܙDY޻z!84~3"޷2ЈݴVaQ5ATѧoO^Y1G.䑔Ҧ8$!  )#/ mjrW:y‚L1"mU.NS0 FTT 333+<12XRA2$&j0syrigb_y~Sڜ8HDcR #h(9),Lnmp̱Zi%G?0Q,\BAuI\+7٤׫0 `+J)y(̚*fZ˧|GJS[CpL։XnB>/%lvʅ~'(j+%R5x[g)UP`xAm RϢPFPmЌ+˱XO-ߊ)w$Rr01K=|{F""T5&c>gRqx6o0%yMkopy0Ԭ@_'bD<.ʊ3fI̢ƁM!A#,TTmI¸ |NcN|c҄w۪G֓1b]#D^KlW+b܉79cX"ΨԚ "{жH)v> Z=fxp{F"ԔЎ̔iȎv/ (ۥ/)"P3PZgﳵvUD Gh"!2}1F̚Eȃم$vo{<E0'VӈuA1!FGオ oB tGzIɋ)?γ!M0rѫ&)" J/&&¦[=HXRH9B{3a% L[d"LDK ?OS,bD$`D~9:gdF[̨[m}jʜ#=*JSSb֧Gj5Ic.VLK#AG@fmQ)%ʐzk'ā@Vpp Y`sv>))<ܺ fy䜡D8Δly^"H)U4@ qWد\Qb}TM=<#ǫ(6AldKj`~3XafQO^Rt{Z֞L4pMC1Y&W>G0"2[ǓF,P%!dZ++[cm۠@veL$fIK3Y*j6C6 iVX/Q)[5waG:ͦ1KD<3(0˰֘ljf9KVǾ;0(bsUv_x6CZW(Uy_C Y p WIUl{xo*۾ﻨ0{U#Z H13Yqk ?;FG -k"WbNZf$UDNnۆ:qط~ _v%2paG8JQ5HxHs21 j9|9y[Vkuٱge9IYgu>"ٰWN"S.VJ)}LlmAsvPvNBgO1׶U53C󭖸g-,2Jxh S]gw"LH*d)e-0lܿ h fդ\u}DkxCa ZfCj&3PfBGR W/10jsNa X{U)Ä)|fݭ`?E"!ʬ*J`"9*$LNeCm~2(OO?*+Z?eVĊ0< !8Z QrZ+hQ A²l:D=zeޒXLsqufxBM >ϑ1Ls~G(}Lz_ڦ&Ѻl^NgN,3Knraz9)Pwb=Jdvs ,=behxdD[\83­n/^rF//wDd FLLV+ŞbyDxN=|=pABYjU{v˖%u̵S  IaR=dBz 2ERL ෰I]UVf9}1rn=`$Μ?c|ÂFL1x'>{1SNf'DN}b>瘣1f)j R9α.;#3#*!s *dZPe cˆ9o籿|:y*(cs(̔.9BKyZ\ N`f LWA R\r+Ą|=:s $`RFq [&Mwcmۿ~ 0SDޙh߶Gq9_4r3"~x&[^Dƥ 'NZ=V7V2cXT_ ;kDF Y0d|GSV[ٓhR“BպA&Ys`1bD䞈>MKϿO޶oy("MW \@T6P8~y]ڶOV&X.~<1Aǀd&*%w3}eV$RjLZ P@QbRk8)Dq>lcwq<(̔(zΘ0Hm7-++Posg1X.&7׈dE~g^KDDp.|)h2 M`Tr4)inD݉**s|xEL~0F֚Zx0$D5/_,,&bTE С3+2<9fDDZm ,+n3%l5Z1_~a+EwUޏWĝt7+e0{ wn$vEC RX8=<G*_yTDD ɋhx1I5OFDd`>䘎HKj Є c =s4Z[k5Ɍ8;@{RJ?{#=UDLo##]|r^,i'`a)2U6SDY}Xiq^^J)>S19: OE KD}">=(7:(/I/b*WĜ??ODSh#L(죏=> VcdMHUsZb@U{D+k"fXt$ԴDdۛZDe_y9!:x6FT i(= d)S1S zAڽn̜0AލVj5 ϥ<:곻OQUS3m7*}f)zLDBɳw]=s?Ht=7Z*{A[jW" hkqg$N1#F* bU}8ygomb -""ayBXsX-w$7qd4#~G-DLQKus;LZq2tag_??;G?@}9UDV!?iˋ{afŚ Hp{9{<3Qk2!!qdѻlȘG?sǩ&oϟ??^Ex3<5wfb;Bg)+3D1˖ǐK+I @.020̔Q+P=|s\)>7zGD`k U{)Z3y HS8c!rl|bLZJI\s'k>H:Kwkt".aAG̈paj((u=ٳqg9ΓU <նvښ=""4p`O r] ={-u{/VɧF^H+@x t9g6+E8}LSJe&1C?l O<_?c #fͬl .S!"V\gCVm^Lbq1N7Yʪ0R ^><<8#[)̸ː09g?89,\S("nszr!sj)*yaJͳhђAcN 6-{k)f:|s&QQ[uj*r}ݲafoYbs̟?+7cQD!_(l7>!Hݶ}{cN9=[^[ (c:->;Xq'*;3m?UT1{E¦YMBDy-3cl?D2KxLyx}}WG I p!xa@ݏY(to9tt,AD9 zGqEeasnV(D=A5>wfdrkγR0f~LTԧ/"ߝ[ "]ZXp/{Oc8n/vE/>.iJU3+wXQ!VtE. P! &anW<# [a^LBHLm[mDf[LUͤVCOb+acʕxE᚛s̳W;,=$Mf b!DžfNGBcD "2FCŒsx(,̐.:~!93: @!=ƄD|f@0xmsy5Ǿ,Oӟ4-u<=^:@8 *>ۍ|֦ʵm,* fF&EJrm[1}ί_ߓ˾_|a~PR2XLMM&RQVmg֯o}A,N.$>WVp\ִZ[5爈~FR]Dt7Kt gk|U (e yk:&2ld G|2f=ZaP轔BL4N;?yboq+<"ϳ_u=JZ,3R| c&rے"#B$!{vЍ!qO_̢2b(?+☉SBŔX 0ljTD2\|BSd="íulv9L 2Dd6S" V3蜑sR+":1>Z2b 0+máԊ~:޶fe f1$S.j悋֢}1+3T";L@,eDB5ZD47J`_|#jj,V[kXJD}W-UQJFTUX}ge#jKZD><5ga]G /*?G~wa`Zbġ^?Ȍxyp}}/y'"E}3}kٮe}km,v_nZDIۍ2Km8£I4.Q?º76=YNJR`Iiq6ytJP$b ƑkjEalŴtl1}y_ };_KCVm@6ԈߏSU29”>rl{?8χY6.`lBbS1e `^Xd`|q *Qd>g0Qu߷0e"OΤzCC^RJߘ)3>t61u7lOSEŬ@p\,E_̑}>.,!BkX96}1gdt9J%fbS0/]DL($YEk2L`7x |^8$ZuvjU42D`| Wʰ0ŵY)u2@б3+C;TΌ|ZEA%Jb%"$`#1T3/wET +8t>f?qL`()S _DCL&8׏q/p&b%tiؒp>O tSf70ͱ Vd/XLWbV`,ViqU.Մ7ccJ4  idNN-jYm)#GH1~6-qR니R)o鳘ӿ?>I"{t (E'? C[2<)dkؘDCVK-VJb-@73" +PbAN*K-"ϩLc/SsS昣U13$q̹+6<>LʠLN.mTE*/E}Ffట~gbZ)f t@ޯL 4Tb>{ 6 k2݅?u5?!Lљlv}痯O_sqVqۭm~x<Gxlք380"&|J) C Ʌj9cs& "33 D9Ӄ2)ZjczG?9phIL"F~Ž"c'eaBðsfןqߏ抏`&&JaxA^IQ$Ҵ!m$38ӧi+M7sNejaf$BQ"r?#Fn򤅼B(>}`ŠQYAŪ"^>|(fq +h"bЅ n9'RJ==ώ`Ast`gs هJ2 s1$@!"jUZ4. F_Q)Dc3L1YjVD @wĔ8D7YM^N!XE[Jy.AK=B3L>Mw1-,(0;>u*s!IJ/!(0l̔|, #e(.31&9';q:P''Iڼ.3QBS|t.js~C{ߖ+OO`W3v۾[|?ݾo9"bՈX4Ǐ?U>"฾g.sU;^T,8rMHHN fȃ&߸p Beyç/\LUӳqϤ88ZU(8<xowox)uwx{-%XN”fe OAZ) (]昘KktÓϿ~)!Dĥ֗$e$!QlN$RX>LIqߙRk%q9JGg}۶m/ՎTU5crjjk*tH1Ӿo'xn[4F7q1FRE"TE #Z(Ј}f^JAj)އNɸVNyN"RkY1$8m6TФV?c9AL=@ ab)/X1.ӒxHō%"?9=FIٚZIJL3] ZE*X/LIĒ1u]Psj7A/9TԴO=| S#Ӛ>=/{Jʌ(ň[,yVݣW"kpNlp:C6Yj|%Hsmպycy%)bILT"ݯj3#~?ZkZږ>VEr23\˹D"W4QZapV+"0?HdN4P}P{FiZm9Y,@@=ͅz%k~swƿg)03gsZ{bcN!<~__',IdeKDLfyJ$w߽|x`-$qM>݋Oo~Ԛet9Gk;~~5_wzTyG &&n֕@˗+[?O~x<1hb)anXQ0kkf~Ĝ۾) {`I"ZKkE%Gc $!n-?NgW1Qe۳r<Ԋ^UQZ˗_~=ϞYn^xz iZ pI!g= Bc$g) IF$$"B&Er"잕G$* =̜IbٱAQ k8NS͈89(˺$`@ O ~LA|π0q0`y9knDrgR嚋x9֒ϮbK+ER,R-UDu!½BI%ǹ6 (!YSֈߺ2MSTm۷ $ I1> !̵mj> ә炍#(tUfx@%&sdbhsӹ:\%z~v[PVfZ,1YE1d&1fW`~ܫj(gvт7Yc 359 )Eo"N*BDYJŵ?wW௭f8I= BRm۷"nZe//Mne9E%&ݶ__G4//8D?~Lwb{{YjM+QҕJ4RV)kH4<˗/mkþЀ'82ą$'q|W5"pF+'scWEs|=V=nV*էbZ.jTK%ƖU$x' }K) fZ 2~NH 8hd)`LwqR@ $Ԑ̄vY}\*#尤1q^p!< p`X0\UJ'>1<9*Es]Jf{N2!=G$ʂ33}>39ciwi8E +H^e[jE D]r۠:$nj<S4ۇ]o.+,xt㑑$t=;ZrMR±gVp蘗屮, D1'=-kxiU3[le+s{cwǣvgdJ?]sϳZY&IĘ/~wj@ E:W8O~\J9e÷^?>%6ͷmf}LcRǏrk^2}d82[-Ս(UlG?zG@^;6YE0CB,SߡuxԖs3q9*-M]eœ&epDG@i R,j":<1gX%?pw*mߑ;녈~g~XŬZw4kE -%c/K? v=Q]~GdzLa&%3CZDJZMrV}/pA* ے7ǥ69 <ǁc <#E$9I8 A_4'F3{?y|y>VͬHƲЌ1EyN+iVH0LLJ+F0*ͦ0⿉jFjZgg֪2'G1q01 e.`Wqt $SFzzDxn/LjⷋHQ,"&BQe9|4>"E [1]c҃SW Vjc:v Bm*?F  ]%0s3jcQ&C(owė$+7^_?m~Bos9U-ُ8|чeRkwoCT#B~}83,Պ)^{ 0/GLН 8lk_slC}9 ~K/;HyRr)eNyDR/9,b a_u>3Ã3IBh2nLe9t)+Y$ K@@@ \0j#pgB{:" Lzrc)qԊ;Jj3rt/1.f`!Ee֜~sf?/plD(R3ôZ<<Gk~RIݕLJo^~3K%􊿧_?oXq$bhm{ct"~yyy<m |˗/V?~bc>O1H8`NT Gvz~#qĨq}@|bot+C\9Z1 V7Z.L*!\"BR%&gZTL̴25e1XU#їef/#”1"= ӳkm#*E4#602wDYl "C ]RRpi?ЮF.3.޽R<Ƙn̹2KcL3W478N9IGa!iD0FjǠ%B2裋ZZ)Yg3W-@) DK(ȉKlYJfž{LPʶm?%R6fBPJpq}bZDtyYk v>'Sj)z:`TH u{@IIYKA; _~w`H{|KfwdM3ɑV_?}!̈́5s$ABnM3sj}qJ9q?}eR[E>rV‰~Gd =kLyPx@PJ5Z 3"k*Xk8p1tp\CI!b,"BT2U!bֺ빐cb1ZP2wXmZ35`ۈ@#nS>'ꆻf 9\ C?oA8aOYLާ;hDpu3P/~6jjs_1,ek EUڈ *-2 z J>y&gӧ!5wlf9DM <S80j.E/a ϗRT ޵Ԫa,)=&°3qwxV+.5H-"g$i*Zk!;7I)0Z/[,sֲWs&4i3fVpsn阓EDw_f>{7w3CUfFqyLDaдm;|hst<~>}__ R{<^xi/C[qr9 WN^]0y WmH!dCA+1l5WE{s$[xxԭID6XbN9<Dm;pA]|0N#Il|q>j*b*S`` Qmf0޳YA;.z1S-x%%9 !U..0GOx=r[QK?.*K-V<\E"h|s0Q]i"!9ziVmk[-ˇWi\sTmx̏_)x@[|~7%*']3+AYdSa3=,ftbKtPJ1Ȥ "d P♡"c9fc93HIPɜ >ryוü sӟ& [_XUS*,>8#~#O*Oq{+%D-Sٟ{k|a2NA@Jy+Xsˮ$u.f/$j z%鉪FШI I#,"K RQ9{w7V~3Ԭtk&;"|GALo_Rd+a7J._oۆo̭H1RL|}}]DRRȋimN[Ժک̍YwDigD ݜ·fQ۔;Zܻ*^k_CU?*Zog Sx,D #nFf1.a'myh"݃91L;|`afZzdEi@sONFDFVf&9 O:1u{UbKWTkť>#[-ܳB$V8 3󀐒4 tC3ia 34nXea&?ψ|ćU27sGGع 0'A $dcO]b3ӈ U"F ؼ;?2ZORHCD$ hd K6Y;;*G~'9[WTJɹ@o_96[ ΐsf1Ek5ZURԃ8EJ)0關ё_.k :nDPZ o3_{:z+ Rooo~~+_Q vk<w+ qۏz]~7&Zʒakm^xߗͮ m[~_ooA۱r\nWկN;ZXc裏'7ὅ[vq˵ ޷c=TqN5,̠)GP{cpPEJX@`/q8r)%Vpj΂iFm;pb o]Dy 9@b fb/i[?ZxHpT!^N#SxP,=bW8 !3N[khNYUcL]5Y/"녙?=^KI4ŭP0ȫ7 G(t71g9VmeqhY$%dS܀a-ͭ?IKNw<岘Y.%e)_}0{ֱHF9#8z77`Tq躸"k}aafVaL#cƯP. Bp[լV{4h*tH{GfXkv#u%ӝAX{:)bݑv>4RӄD\Spf}'uC!cwp.%%幥&ER1" &$ˆb VS9|>@NŪN-CT#wK<==ܶ;䔗eβ~q~B@@譯IU)(\ "@dUiցG udΌnf)R>zo4Ry\ԔJD;䂼\3@#]JƜ;4 ̿܃H:RfD-dAqfXXCVkY]Y&z`!U!$3%m nzᘞaCcpJYzosJ_j0K![r-Kɹ`SQ[k՛3qNh׆(Fi!fV 7۶ӕ˺朷ضCX<\pB>i A2Ky.>P84̌[OQ9*'f=~.zSTҿ,q'\G爸^9c?u]rJۍ5Mo׷@*tY=t\Yxv־m~_Q&PgxED˾c=i/PaƈYph`2ExYQ‰O{2BI)eMm咗Y(Kg >fJ7بm6lUnnhEGt"2 DFhlgfϡCBy3aefpZk=rV9e9r ȧY?LJIT=hX$'F%={AaΒ$EU- VkGaD8@aƙtkPQ2RbZֲ%Ew~ԜTE(tgǣ:GOqIPjJD m2݃TJwߏnrFia2gfGhʂca#)|NfOH,$K ٨c< &@Daİ]RnZ?h)̒(>V=7wPE0yYZXT4{#WX_DD$URq>b4t&M~GJAvX@øink/EX,e1*'XB JLg/.= 7!'- 3Qyz|ܷ孶[ӧW_ofͺ=?=~5Ǘ׷ח8jmyfGa{GHHH[>f} ߷vޑg>i00p DT?UiB m۬۲,D4HSIR kyY"!}SfO{;դS TPc3d |S!Nr"֔ZovG-p 9z5(=koA4[YZATJar*jnRQ%&[<9PcL\wnG)kZe]VLޛ5 7`c޵9.”sRM~ZzmXQAo_iFqlP푨Ha>9:Biݺ{2lyr. \G {VED÷Ar=͠ WMpem5%'u$_%,V+.)%g\E1D\|dz DW!LM)Ё& cUADЭDt"qhZ[h_o- g 9{~'䧘~~?◿&9O>^_?}TrUݻw }Sv_±F?_ADņ۶Y.s4Y`š&ҡQ"zEb8(kZk8{ X(BJQ3VЪBEhӤt);&,C.\?o&bRڹ)4{nר6(TGTYֲm$u󆱛Ǻ%{llD^0Ĵ c7`fFR2}4I0Mc>a5%nfv<56_3,h  :˺䔙V.A{37O DdffDĥ@N60=w~H$ĬiFyR%li~nn 2w "ljn,#[#CK)))*!E, j[[ g +`S4K!fhhA}ttK)a6DEZoR y̪"TZy&qĺ|^fv,!)#cixZaD,˾~GLzikn/l'ً$%NESY{,^__ey~~YOͷ/?HᗗOfv +oooPT(TkǏ?|" Q"h$MٕW@/b&1QqTaVUlnfm*-=CC q=qM  2wDsJܾ˲ RfY[y)&_ptL<={z{(EÃx\9QYQ81OO~+_ !" L$vRRO۾}W1wÍ@J2絶˲ i}zy}9eZۉR톁'I?<(܎Z_^_>|pgG{}{{E5ѠbYM$,AV2֜|otɗ ub6?~>_/XiBZ㸷ޙwR@U1 *bf;ŚM5NUQnfTI?\-,b)h"ZkE.,e9__Tz< Px& ޔzђDa5kt(3AT{J.FzF-0U+fpyUA$z/ƪ &n`0iTr_!5` lz6cVy9~no[7Rkv~WoOלb֖5}O///~uTU曜3f9~kz|xXLMZՀgcgm>9- }p^\\!>}?,eu\.dɊP7(,岮.6 xpo0q+A xRy~ 7njZNq,eTgh":D\ɥf3IvwhDQ.?vRJkv~{!{Yj) en$f$(̛ZJqA:8,nب1˺ʾq`7d 5ň{/XeBTq$gl}EL1>LGMI/J o Y$%# &wy] jK )] $ؔ>9gȴ&k ,ɎA2ksT+*+fM4Ge-D՝q\g 1GI)qjW([asQ 7 nzhbS:T{Qk.?j $sx֒jxGLӔbneR}}'??+_C3d#d3jIER)}뛊b ݝҺU7_#Uu]zȶmErcJiwPpw0:̰'Øk"3FU4<4,̤"iY0A*mtò,p!m}j뺮X /qm[o"uH]HUNa ~Ȥ>[L&"1aK-X׋vl#PtX&6~ighfw0F8{S%gaPfGp.n84x"K骢)y8icQk0qn=juQ\PsL% :LD,``@eX$PD#T8ʼFXm7<YɦѰ0Epddb0 c!9fy^Lh{3&VR0Pެu.PoNŸ'X.D$aL#'SR2|tnFi |Z=iR꽧qZ)5*/|4۰[{A'V9u/A"˕9i:,E^kR5}ﭥ}ǧQ,zn "۾]v,,k^?~%mƘp3JYZ9,>.)=UPr\D\rq Y 2 iq>,D1mCLĩtAR|" QFT}6RrYr{tP*ǾH8%@{CüR>e'"˲䲨JuYrΰ8 ~Z+,i Gh$lzFf.[)Kʹ, ,܀QX2Ғ)[ަf_5MA*Bѓra x qFk,R,)'["z)K;Bv3HSRgO,eƒxi'\fS\Jom^YTÆ[;:Sza"$7q<<0/g>E\{wr], /CҼ,&͚{5rN"w} "b݈Y4R< 79> SNھDM=T :Dn]Ҁv hR.7 Ҕ# xSlۿjO!}W_}u\N7nDZ"rYկ ,9גsmDz[.~{q[n9mBoL45j]Dl/ <@!ڢsNS#eIa7Ƥ(*J9)U@/ݻ^ EMG=l)ZJ1!seGGK)N74"C2n3B.Iz7j! YTҺriә!XA0SbI9[׋\kҙG͏$r,ZoVgF T%)k˺^T NlՀ_O5ܡ 8$t L$e]c33 D(dVl۶뺬kI)Z#Xfr/ b1.(VMdFBD43 [)1<_}³ef?>=䔱9w"w߶"yͭW")Qnr`3OgM 0:4~ΆFWssN4ݱ1ގZ+rmeqHfOaYVw}Kz27hRdyA0LmLeE)@G#1_{۷5z0KJ2ȴvwB2mRL9\{7g)Puk\u3ˇhDއG QxX!x>{nFвt?˒0AE{m@UE0Ƭ9 }` Rt/ˊNB̎yBznpYXQ%%a=Ay iHBRJ^ku}||Ȓ)Qo7ͳ]Bdq|*Na8Cڠ(dQQ(OYWUVfzٶ}?4TBVJ’sY/ O\FP`lnA#?Rί#E܀Ib8YHIZoBF||u8dN6aP'wI)^o>*W攈>mðVSD#\ dnaYE%zOOeۭf 3AuΉ==X4q-:0aϲ}\FbGYvg%`y3%!D݀ɀR.FZ^3K*s wRI/ BAZa(t0LnMGs0v/ܚ Cnc2ߕ` $[oV`a )( z.qcݡ2m 3WBc)kA}9UWu]eNT*tԭQ•qV b6«'s/82dfB\ҲBpT#%uar87>dl(wAp󠮵wW? o`kaL//Eqo}S+k.eY <خEDEVkͽZUe]/P{ݶՊnvt5$:3~v*Aح[Ι$ Eֆ&1{‚ԕضm&9(}7>OKp8= ,ᦅAT-Rq&8jDZ[XR+*L!§0D=;mCg>\/z^{|mnZ3M\ܩ$|ͬR 32Dc6:ݺu֪YG5#"BBC`O)K^jZjkI@> 8KNI >Lp"tgN)ޛy3LB>[|ܚSC13n, cn6hژ 3#ȼK}߷ޫm2I/޶mKans4j_L杉9Dw!+KՁ&Ɩ_3 5 ɉ4ioqPs]T87xTrƇQkՔk(xkKj=+Pn ㋇23ï޿ۿ,^k{z0Us p/eL7bn/>8oK^^_.o|qzJ)޽/[pڎzr,fє>4Z_xK׹̘ IFY,9igd585s7YO' 57 >;L_ z r:! $ĉ 5o)bT~m5!w\&m)JHu5'OR{RՌJ) ~"thʘHxDuĦUF }Ci#1ZA3Ă1oՀPRZo$2U=U5ior_ ̈@'W{#<!_fypӋC_D-ӑO6F7<,(q "NA˥o,D, PJӁ܆21ECb6Tp&;O23 0뽇GJ9L"9QZc:7Ёq'U֛T#ńԗܬ KNZuYՄ iS3.#͙GXwswmZMk\E̤$9SJP7a2vpOIUэg,Ö̪4 0s|"X1x;}?1aTkY 8B`a:a w 9D,Lt/+i;zCMt4DąxV%$/*#OĢ&n"S@تg̗{OSVϫ2TIT}۽{ Q)R^;Ҙ?op1Sd60fIPw,9AvI9\Ckispk.\rQ`uY3IT5 ~ToES hѠ ǎ<S#l6fzuYViqxxd&᡻Z}pR"Q3H4Bp8Ce@v82KJPptԶ.+Y@v 2"<>~ˊ݌Jqt8y!YIG^ADC$m(ۈ,yY}K)DZm)L)f[I$0.4ӓ"f07aI9d]}ܼYO"7 {f#O0zYeGܦiLXww2\E9HQ VmgK-!FE1bpрRt8[RjKzuYx IDATusZ#&N$*JA̼.KtWOTLhG?}ws>u"}w^LnK))TRNe?GmppUDݣsn"Sc_WH<˲L^~xx@5~“lsnRJU5c/:dpbBqsZz/ f)Hc}k%Iz0So4"rOOOOns$rj,38;[TӺ^x@foohvźDJy2#>XAFDˤ[Q ˿{>q_)%  Rޱ: 9Ĉ "jɥ5@Ge>#NO93͊Y%G8!֫xx-xXPnGk$.˗e:RJN9hOju]%/Ky}}{yy+wVbám9'fKR3 "hJY}6F;6PH_!݈ҹGRf#1hk65Nĭpd0ziZrzMDl"L4`3>b\qĂ+VD]MUEG#&w=+ M$D~tyu(fBD335>XpdVE8D`8M!IH)q r& F68P觙q?-zpO *J1˺Gݏ*k.y654Imhι5{ zzy;b_^^>|p^X~ "/K) ~S9]`?R)K.s@ !рQ媪SPzb4x-8e"g8|?o}qbmopDA([z;͎_I8)L\X8 ^JPf8L"h79:'$ ZQM4,zC%1rꆃ*Y }q6U)<$ x?,3_`̄K9gaI̺ȃ%}z0#J%`a#>kI3ݗhe)_^^PLNq4kVvuێE|جf aEUUXR.DМ ɤtujmz1J)'Ms:E X0ݘ#z7&5CXHZmkkx/8r`#4ӡtw'94*4Uv&WM>F c)AazDP:#Zʭs9S'ʣV&c ScﶔXۆ۫>w?я+ =+`.߾{ *DB!DJp}kv޷mߏVz9Z}ݮ׫jڶƖe ؇a4Z#}O_4hj*fVk7) ci,범23zmfHIdM9$x}9Y'sp_9yxC{Wa>{ZSǾ|%,[?HpSR&D>1@!}vndGo%gnX!D2֫[S)FxeQ[ٝgSʢZݒ$Ir?f=|BdDŽOv|/@W{0=]Ufj8枹) 2.t7"#=LMUr]JE1pH?c)'NCH4"#&"pa y[W44A;aAPrA=0xL"Xs.)ڶS)i#{vڽX7MT[Om)""]hͱ;v<*ɠNjύ V)'͉Q5ƲL]xJڎt LUYa1hmG' fY5%:ȱƮVI|V Nx (rf=4%gg✓( QZ;wxG2,pgaޙ fZz |L*g|?D4aB<ۺ攞_^hdre]-,y*%4v^? .<|ߣC˗8q?9$S[S̲,eG` L(e||||xxpmo}#dD;lґtur9i rAaOEq۶i*q&9̘ƲHycd96<%{{3=UGx?`:G$<1CD<-"" et3I77f_D ᢇtUd8Rk۽['┋u7mZ흂XSڶMDR%^^|}3\Xhn i'ҥTU)*f:"=;czEmn{Lf sFPFP׷׺>/ ˀDJ[V&;80B= z}{C1?eGG9w\!j\JK(1g=Y-S8tPd8u\DIDY: [C 8)f?v4 /!,ܽ{5b,sy)Y֜zwfƲ(X1 cR[k<ћnۑM QSJ8NZdRft([Ƹ8}x֩L[[HrU:Mnə[k>m$ _ѶZ / d#8\v}a2Ms"u068a6WMf;ZcW+G(QGX︞I\D<0\D;F*ܽu wBLlwhQL$?ngoXR0/aDY띨{4kͺFA펐^k'pw1J>R,Ř'0xFûr"hU_X0` i9)OקǧWPpڥՕz|x&I~x}AJa[ϟ<wyxx@* Xa ]=y#vw$ˌ;yBku'΀ ama ͈LRcC33|C!^'r- Ƙ)RfIwU<{E;X!@~G<,Y |.霭0 әE ~o#Ciu lS({)^O:_w0:_K__Mƚ<sL\r.S]oL 99{JI;|2콛{,Deyzz"2+|߽=>>뚅Y?=SV¬o%<4,wʂEzR#tzߡ^-OD}[kaAX T|F#OG̒* ]씴{^D؆b#c1:Yԃ|gq, 9M:69-쎨#N)S. BGɃac_xǜɏQe=TIL9Ƥa]$ H9Q_A/sst`qЫ*vpOrA$IR 5ﵶu .x||Jyjvm <)Im}}#b\LZ.9)HX}IDSHāfǍ 6A43ÿRlf fj*G5=)_sRb4o+\Jk* Ka$@G2,4oϟ?sxN^^^Sy^^>,ORBLcx (H ~y.̘q֊Odlۆ04MŶmS)ӄA'u`鹻[i"" 6fS GG?b˲Vk[# m7/fFmq6Ecn\BAf$/klWzȑm*!$>>.rUL,}QH[Ҷ"K\fw9OS>|CJ#zu߉HRR}}' eiG\*a,TohPvl<0%SHb4⇡WMU) A"O`xdfX{a:/Iw<Ӧ0Mbp Џ8P"E5]߾~})dّQ PPucL]ݺն^kMSsVEhkkZURɒ IDATM2kAj°'"jx mU}~~XȬQPJI%%[kd Gcif|dg]0_9A͌SN$'3?p{=ncL#m(D>XDD)vS-#^0 a? G)^mHϹBsyv$%LVC<s|HT%q n!<i-~>x̬Kcpz4Iլ]_J WuӤ\kmJ.dCXHHӈ Vt .UN7ucsCj8uhR>܁I1#H>l9m/a m]E5% Ixt](">2쉀ϵ޽G> g41{Z]d {RepY-> 룓DGQ?b̑2JDx|0cE Z@O"t5 %X\X8Keye~_yJQuݟ^ަiwzFfmoϏ)zT{.Ӄӓnۆn@%Z{)DcG yFCʼm~nEmË~0=έiJa:qeU0@уQ*br.2[Z+\+g=G'`'gK뚒 4M<@N`=?`+m]u]f- 4b V=ȓҏ@G{ `s)re&;@AAeN#Z#so۾mYM)mЦ$|ExZZ>613L%c[kb|МӾ;"R 2{/Dx33D"ֻ\NNiuu -xPZ u%)+MӔ9}\wK&ΜD\ 'g/̶Qa>uZT2V[3Lѽ[m RJLtII)c0^Ko'"8QtE2MGi*Sm7)E0Q玍'1KPx{mvWf \StHo"XA>ԛl'="TJq0% <)#wOA"JlZUfeY կ~ldh`iHx]eYz?|nY).@T=b_s_>-eYny~Kk{t_.{e* | njbtdY ]0Vwl+_~ft]w`C1EČງm[k ,P]N/m[O0o^G!ZG9)c)}qkr.Q0Z3aEׅrLRε5>FAXJ.cJAzkfqtR@ƼIˍ wh<}0ë}Z{kͻf ,N&@Skvoޟg(Ta({w7L8RB4i>[ݯͦ# w4))QO:9ufZxW3DSk=OUI*LxE3ZR&PS?L#j:x `\߭A$AD(0Q{ґLĽZn{vLa#,X}|}Dћ_!B*Bk+:D\ʄśmHN\#hEHJ  y}ĸ2|j;+u$cLԂmx(4>`|"4$FVn<,t31zG.22O_wwk"Pٱ(zKNvy&Rr"Bn¼,~4M]o>}C^_efݬ;_ڠ2!>°Z!!eYБ\qͧde1I?qfRq^X,ف]{>'Tf=DB$d>`Ƈ#&bZ.2\I=9xs)@X`Zr0N/TyҔvz/sA2 A*LC+*Vexƙ d+ytk 뽠ތgYw~?[0jkP K b1L{8Ϩ lGhi$&a=@hx!A1;Ve/ AQINErE xOK}Mй ILw|hlOSJ뺞_xB?{ O`"j豮2y86$ oFԔTZ߳b6`p WUN9EiYrݝל2o7۶i~}S}ۦyɷ?_ښU" ~?=?_˶yY6_3̑%r '!JIe9ywQz7ltpqbY2ϡp jIfDG팕ޝܣiXm>.xkXivjf9srbboceb) yڶ M>(`0:{ߏ0ŊsYUV?&|hӑpSvr8y0]42)F 9ܯ@%C;Q_ґ@o&Քp O!S[  |jb324M㋯Ncl<|XTh&׺i"oC׵#$C/ZfS)Q@8ذK3"rZuN} 4r)=1ؽww .붮~7( B-,\Jv"\rɗ˂yhja9@p\tƩMiN{߶j֮׫kQUq OSBf3nqA,3:Qr]Y2ʤ}of uBAIUzuR"}z'MM( 3`\0$$!"C}7zO9eΙJY5!s‘{~ Ӽ2z8V'|$#3jԻx(AVؙxdv>,(D5k6x+h*&:nN\<8߻ء |sQw)pEt-z ԚaɧCx3#@fU$sl=?c~ ߏUXM ?skS9/_22vETt۶o2Oe4M۶//_|u۶O9>i>\.V }v[R2:f)yкnhMC1t SGsʥw>9ն4ރ<8sX뾮av\"D Ttng'64‘ۻV=!U39hOe'ԭ",4MQ,/ |km֚Y 9 &a#QeJL\r\.fMSJGQ!^-| n+q8p=J($P̰C& eǛs)e9PO| zﯯjݬ\2>Rtvwzwݺ >L*SrYz`` Y֚*; c)%UDU͌YUKJ$xKɚR?̄aSId)Z`@NA<Y&EUGjn|rε5=ԋǾӉBzxP 1]HdS l 5)CQM0!\Gu\#ibh͐$*{rlO4A$D2$?:{w['v.XYp'#C`=6>daWrtpw2gMqNX1#n$G@$)=sh3ODANnP'w[y޾OH[w~m}~y)m{ŝq[7p5 åcs?6FlnxhFmĩ#ˎh]ʌ4_mYvI}^DL\r)*r E3)x^ș,f#roxp"(7VnGIIEQ1aF\ ^̛혫Hb(Dԑs W"ʺn95"OI|bŕS9uS3ުn;1vo~8Vg9n߁RD¢;E` e`k ĵIo]܏ay{轧(\ UZPߥ |wEJr x{ /qJ{<-2ϓH6M{ry|<_+r_~)VY$ir:Me i&mSɐ~us*zֆZGgr$){x#Ewlv|}߰ 9/yd'"vZaSW'@urk lvpe5 3K)6,3s0%ptkj=|)"l*S)4M?6.ۺnۆ@GHi$q'fjhm :0bTʄlۊǼy P͘|G)e,yz}sL1珲PPw'F=W6VcMZJF),))35k)``Ұ^c8ҦÚE0u]t$# /+,Bܡ!R\KJ {[}V=cv y3k%%9M4r2 ߎhɥLELha ffNYS:jsЊHfkNdZw0G:kmx['fC̨u`ld>AhoF5" _4S71.g"h g]TȺ$$,:pHUtdn0;sF5 1JN~_mta}HP?њ = 0[Y-"$c,@Ã("K ٟA>1=oնCWxz|V֙h[)z߈x7___[k@H}ODS)Y {m²,:(,3(۠{ݶmg3䤛%O//˲|u'@CzGrlѵ{ z3bÅ{A $)acU'KJv1G0kjFЯ䠼ׯI [ZqJ)%Ml]W0uͷ4LJu]kKm{ 8Ƭ}?OOC? #n:,4Mq@3|:Z뙍ҵVX2OR>o/0s)f~ dؽRFWެwk6MS8d/{{guߝHjpl{R`4"nHz7u];Y͉' >ukVH9O D0Ms"NR2RJ{5kֽ;vr0skfVm4qxnPXkAFB8 kN˒onɒ$3s{Gd4 gS <偮~*F~"#ef[DSMVTuom#"r.hS5 "y^ʹ"q^pX w 1Hɹ(@/aH=K9'3hKwKIsNNQ[3 p JCeb7z k: Gjw"ƒSj}k98Zj# Xdz}A?rAB yK 'f~\W;2zR"(|dP[acC6Scos;LYvKD4$R bhfF&"Iźįza_XQمq3Wў" l+y&u] _wCUH RR/Z38;Cs2 =-a1*O_D#Gw bٙs6څ8n< tpZk)݃,nh>uf3ϳ>/߾0Tk~O$tIPC*]J B2 ?Ðj|>kYrN?@;-,ugD=V /ML\r.0O@EwßXBiԊq1Q%{4~S{7 e&abE_C/IS `ګ>nqo4W94)OjYغ,kd:2;4'C+.Wy&߿' rXTmWݼC͌kηWGJivV?'[*hzH#뼳3tXMSJA#e :,g%-4u`誋)~Eư'm"Z{}}mԐ1^殃0rђy]f*ˤ?w32b§MP㑁)iR|]JƷ`1Sd?QNp 'VQ Zُxl?qt )f-۶2x~<=BP-}oͽ׆=(l$ 3f+7Ab*M&E mYC`o3!+ƈX\4X9 aF>Ar. ahxm?3,0x#o}+ED|d]f՜Uk8¡iW1i&f)|D"TT&.[lN[ޯڙsENM ݽ|n{D|}bO0{ݿpbއ|2'b`3 c.Oz mw̧ w Ry<^^^o{AV& !۶q vy{> gBfHkB{*#巻jLfKJuz7 $qO^8S*9wキ#r*9]ѳ~WNh0m<^^^^y^<}#m􁪩ˬ_uQ e͹\2Pu[oN<|CG)X6Xbi4ɔi+sAG49YQ"afK4ZU鶕Ҿ" Y۾6`.:82HAՎ{߶ EI4 ϤYUx-9\2Thf En19\=TARp e߅˹(6r"9[4n[)[R%:6 ϴ ׬a&NLU[ lȆޕɡ{+s:1J#B84īFA<7]?cA :d^[eZ KJja=ڄy{J)o8ޏVaoa4<4| '~%IJ*}0G38tY}m9)`Yu@$=qƋӯķ˷N36i`GRJ*pym1ʾܻ;k(s0mb8:4;sOt*H-@HSͼw(-ZL y<\R_t'"$!~qm۶0I)ix3B$`^[um˾5`fDOo۞sJ:γwC;ćlv2(;68N.c"Zu7,"HEk.@+7;҄hLY:)"yޥzo 9R9&yk-sΊTZ䋁< ;o7 !bq,;UrΒٴPy`e!ROX;71Isc*B9#k ,~Ai,@1\<琌N#n-m񶂀ĢK)FSN9tJq)`͈XTǤ˯DhC:¤)C0Ҭ֫YD|p=xL?`p/h ˚nun@R~Uի^z˷T)% :ƤyYH< 0mpϏYkp{cLQndIj݆<'W.9FC2 fb)z۶wff@3@``|>h۷~s |>'١/umJ>FVC/Heꨧx/HP1gp]~deTb  YKO* _1SGZ7#sh:KU(1&ÉeܟJ^^_EHrNY$Mw0cHLYlzm<$g&f%$"4C͎8uN]:t8ښwcT*S`c*)6*'y˪8oEapkISƶY:_@///zA|(L~~'ƛojb\Ndʢ<)L$cgX2NනأAPat63(3³a"CM:H8\RV"V=;pxлUS ]GtWB<^LG؄1Y`W]4YjQ7+$Dq1sE:EUHjSNxr)3,ȬO8swpU?ôkm pex$ 2wy!! U!pU[VĽrD0_vK?t=G9i#(M^z4)Gf{))VCfn =~۳&դIΫhp@av{z<( P/$%E6n=EzgIJHIJapښ,,KA`r#4ʙ{7CbJ^u۶Vk+`>F0 w!LcF$C D`\r [g2oe"½Ҋ}4 U7SaCsX­)S^JIEdOxmDNDuV=K -!Je a ;InR1#ǃsy^S{!ak<'b^ V?@ƤC7KBWzUc C촖 03LcBZP"  W?$NC īle}&V!){-)y,aIg# :t'9XQΙNpFf۶P *cbS]* SP81Ql%1q%m7VR{\H9E\[MԿ:?>x3 VE.SJƩ4}OH纮ܸpToD<0Vp/}DqR,{ Y$y40/ي:=ai_TkumqáAywC]p&¢al5 0Y^[o$% fV 9PJ)<$2#O)s.f^J"i}߈N]OS58 )ρvX%MUQ\Bc=Hu(V6Nm w\Ȟs(xJD$%lp~ֳAeۃn/o ɜ&n41rĤ LL!7sMK4NAWG9x">Q[hssG;`EYi0x!aMeeD[Bܚxٓn#;1cdĪ!dfuxa Y'f z+z$ŵbXO:<-*/h9xKO'-2u s+D[H`̹!w )nĵQ$s5@`!P3e3MD/Ž[RQC+/b\hpʣ}4"P?NLW z9 RJJz`*9G+1}Fxm; {q/BǃV(ۯNڎCGZˡ3E5Ӕpi3˶m)m}}!kuo6Б됖}n1TX3::㈁)㫸Gٲ$cNf9qwTG>sK)C"j[ծ5 (;3v]٥·AV>HbxK1E@ pUiw'A6Р)l"yZ㮌;\:m ZAx ֜Ɨ˃Y{kw;ivk1 ҏ#8BA "޻wm*5?1rhct M퇇3"_4LL-ӏP˗ཡ37=nU/)J.HڐAooTNܯW4]rN{y}}m &P j/iB,-ڟ۫7 SmQ8PbnR1~gg;-њ[>_ϘKh7Xp/۾ɶ^S=\*Fk_sԂ0$9]ǀjW>!"4)+r199qDCݶ!A6]E,iZpE>&͠oVpJ{xpر#ږvZ+ I>' n~]%ICf^mn)oۖe_TL%gszqG~m[V-3ﷇYmQZ{~| >n(S=-{{ND^ruU3ӤZR+1A8\L#9۾%]wvKm>W,likٶ.F (޲3Fi0LXRĀRbX_DFCfGy+{ͧMTSa^y7hs="b8̈–C,Z;T ΔzĄ!3m^'4J<bQ**Bl~ ڢLZDl[9Պ.B,sN`nƬCлbmKo/1 Ѭֽ[]M Taٷl|?z6c>p 9#HUs,jid8lAb∀ :`yeKnX`M2wL2((S,%n-H\Ej܅}[ Q4&BJb2Ȇ9g4.hZah Qϸ5dEWxU<̝ am?NWC 8 .[ٷ :7g뺰j?u^j=j>߿_/[k={٩Y <cz)̗Pa&p.A3~H0jkrD=^^P0HxU.֘{zeoKKl`ZV'bFWh|h8F|5;]ime۶ Ҕ4bۚu=jm5`^k攘5'6k Fa8jXD@'fQfr(ڀGk?@\Y>?zS}oޫ֜RJiqU)6`TSIe۠(`5$B^SǜE[F$$ wėpZ͍H6۾ޭ 2 ^RkB2l2`Cz>b%TNJq-R>_3E*RAkRl+u1pYfU7 e 0ciҴLQkTJISN)3sF/ I7Ѝan# 9̄Onki"c~ lniZ`8ODDwK6-VGXjYr[JZ{;۷[֭m헒z{Dm/j)qO*QJy:~~<~]K1w(DjxD'F;8q12<-"֊DQW51PE p/-d%sG](c6E'tֆv 0Wi_&EYJ]Ҟ,š2 `+#uŎZT𑅔KN}O*:h}gQNYk(-LTKYEJZ߇Q2FAޑ*V84m = *YD'Zy!SA. _Ǘ4GJzN|ߑ[,`7o NA݉8Y^KΪªu'!Z8~֚ w)U(#~>'Tk뽓R),"SC,<" ّ)PS A| 8ֽn}1WXFy;y6H>'#?_r >E']8NX1]BzT޾hٷ~xRΪ6،tHBySMIDnv=ipYUo}zP<',SB2N6aCm9o)g7;ELTUvA%[PG1_ &urgodXb|D{B! 1Z:7sG^wm,a^@Se !Hnڷ4i⻚B$o,:;6)i.y6I* GLa0>߱TVI:a\>B9fڭh*Ž:e3W&m۶mWU~[2\4]N('ɒ0,1 LAO0ُɕ@= -@OSֱML3E(%[E7oKKITU+Da>2((Ӡg J}jt=H/7?zħzݭ;)u߿oWϿԻ.Oqw??y^Lk"E>à'<ϧ{DvC'i>soD۞0{l]\pzu]q(naP1- IQD|geKht<̋4R:f-♉'t1RrV3ziڪ*l墖-Q GE\BAa窜|b[7s 8!`B $:TsI Up4l t3<V;BM:8p{E^;+MKF)<S%}`=Df=zulm/%42S֬>v2)\;&jڹh*۶P-uk)̪,U/7c.@"J,f;锳잯@?.DRʥy\y]R֨` ZsHN+$>/zґq1,igbC(K;(3x {ff ^ϑ5 ػ3G){bRy^BDfy])".Q0 io7 az0{ّ QDld3&y!žW[QQk.-xGR3y {OQ9'3wN' o~8㧟~?_yg񇿺Dspm'Zkxg&\^̽ x֫p*1j 0$Xmw'㣶fNbALa/D`M| b%!}߱EK֚X0i`c_W,f1jap3}L28l.< 0ngS8"C&h|U$>Cx6a0_z 7g⒒$ܞ;]sK@TѹFQ[`um0dV0@iMb~1MYp3TWsww\+Xɭ Aq?R캈9~l2۾}9gkׯ|9Pr駟[k:n۶\ @2 8XdvpxufDXbr֧# >*SY0$EV\q]S\6tiSycO"o8{@kŸwiDF*Fd8ikuUs,%%Ykxb^z][x䒑0Lk6IBA{_O`AuI i(t'[BuC{oA?Ę>0\C%"21 7u˾r lm+eEž%c'K:%E`2S.Ѵm:X&H+Dޫ\y^'#*)\hs #("q#7y713(9($EhBbkF ;K.C̢!# $q]Z2.|2l}bۊ<5Oc&"uAHV3֯Z{PD#d qW5hA:$˧obaw?yRDP`k>6莡*QH.9:Gkb)y,: x&/;ïzZ@~’SjjJL))+#uL'<^).Hպ<}I?w{LA2IP}! Q/[no _ bgL}˚/m#^-_j4cvu[7v:slm0Nqn91sw;<+3=^Q03H1p֡Taq]>u ?}ߙxu#lժI[ˆ!ׅ( "f^\LETtX(#dxFG5[Zkm>$0^6q?z`=~]A~A7 s* &4jassm۾L !Ӓ""⛭ IDATہ߂"Etnx;"J"PpoZ0)LBÆXi?1MF#vxqNɇЬcF@4"ֺ)l*@y۶-]a6 ˨H\sx BDP[wZ>Wz*sl;5IJ \Q#*j5MY:aRpAT\LhCLj67F|/LƴUćtﻻg^kJL<kmͺJ=j(V8RJ=1m߶ M Hm+нnLjeUqRV.fg,1%"й<^U+\񌦤`|189ԌN>X2홒A )-ǞŴT&27,9xo4xQN"\ʶmS!d%Rhb+U%Up0Řj~a4AVݜ_߾_*[Tc-QUBLA}}׏?3MP/{U^88dhvm6A,Υ!x ԜxL-,l v?8# ΁S_~)J)}ZP9֎vx}ݢLĀ磵J$GJמ{[mHnZr(1 شM l)XKrb۟t>5֪v7|"pg=bU#0~\ -1&BYю5}]7@=_}/38XJ6ۛ/EZѤ>_!>]ZZj=ΓU" %y$;!Oi(1[nM0yO}WkG-EXfOOe0P^4=d/J9m!ٰV3++2~J"f{NUs'Bs3 4Xbo UM'=eg{Q_B#%B7/w൹@j1r03*LPͤ}ÖqqԈ-ؾ G)*"6skيa"bښ{GrnQqnj0B ;~ ra:1.cܽVڐa<@&.!7DEsk- 7;sm;lJAB˶BLx<̦-S[R洟߿q_i~p70ʯtΉ򷿔Zm"x9%"(#1\j5=O@ l Qfhk/cX*aoTlz+8֔.sPk^΍)o!94l8G͝09#9pB,Z%ӕb&ZG !LK W>F*#,IcUV3?/94􍬏&^Ze,[EĹXyJ.hX{qd5c{d&aEk}1:g1ǜ}N}=_n-cOc6L (a;V[ی(9 r'syGUju)w~?'.̥"MvYE^a j4眢 MҫX;Qj$Fr(T$x3 $>ǜ"Jx?_RTHTUhǘc Em_ۊǢ0 9{8Rcu}1 Y@o0lT+]DK-O6nr>.):ȵd`, ~p`)qN+'K\޵Za8d8PL рa*b3]VCUfŐyq X%paT7=< 0pҮl g~G]ۿ.E(Y;j<f*:xEhku.ORl'&&E,YݑHD`.JXnUCFFkfᠱ >{"TrӃ6"`'º)>'>8q uu{f ;.k{d5Vr7zc 37ObAPh{h8;2~(İ`RARXϟ?nJaK-8Ki\aԞy W-yb|_2cv8 ̀A= 698JRrGj/f:O3.C3?HG>s,'aҚcZC6uڜ61g,q?~(Iv.gO2qF2ĺE9\9}sDsVY@mE݊@Z*1x?0GؿƆjbOfsL0Ԍ;*֖*96 ^Q\.&*tU4q%"-"?bh6{9+WiP -g6Fc²?[YJA,`%9Y J*;e!s9}w̿{ ]]2*tU881k2wg;/c*9"C 4}GXfçf%Վ,R_k4XnGGPy]/Bv*c>[;P4x<Ӻ뺯녟;g̾{n4 +/*뾮X۸X,9珯X`inzMXܭҎܜ!cwwt-"* z< A0`P5'~J&\2CYp)\bӪJ7BAEY.w$Ui] XR[$Rk$6!7#}QQJUo ;-db/En1R[kH69G.e16-Z[+@HGx2ps-xζA_[>Ku5sǰ% 9;aSsҐMmŧ_U!c"E(17da .L}S)y>c|A"KD\y68:8),٘&hHTΣؖ)m5aJBu1X2+*k;Aqo P,m9ci,laT h1kţZ,ôhf~#r*n#D91fZU >o,BԎD ]5X} b_PbRpt] ..1z) \)» {L0N,t̔a_0lD))sV%ICq'NsZPwpyMeUZŨY>}||bA"čW?Qbwf;(=3aH9'.$,tݽ{6k(R4;;?kʔcKQ ~"!TYno'qbs]PAbI7ʾ9Σ֦E "R`4)0qV`|FArz>2֚qMuGbWӤy>p 2mYJP[K)ń'yq*p:FRdŨGPxA= \G< w?j=`󷤂-b~#'ɬ$(2q=hSɥ "nbWQ?Ƙ4'm֥WR~N֨HnusAV,e)wsh y& ~o}z3>vW{G#%33QsA{D8eV Ђ·ʗLnNLRqCD!"-%ŮشV8H5 Ck ƄRRK ݃mc&w 4}JQ 8>B';c"|>LRtfEiLǂd)9bA}$@GGQ֠s9=<<-@;UP 赟+ fLoҷ/֪;|6Fӌ .*랙|F{MJP{b! A'9o{>ۀ ` H2z{=hgkT7Y"y$3ږ3[6%~J+{c!yVՏl'pZɉipdZ2SIAR0~\$RkHKcbdKڂ٧$$+#o/`#U 9)2"DTaGxk>2Mݯk ̵`;]WnD|}5/`gI9E+ZnYc?3w8ghʷs!].WAωeIck1|:Bд-S5d(e< ϱ+xYq6s,yK M# c-ȅMX hrfHZs|G}xj#aƉF|"%E.L\Z331WVK)gߜ1pYs(Ds!@ޖ# X#81  Ҋ4Cv %Y̒5UV9i/s=\Ka3܎v(E)Z:jc E|x: V@6乛#5f! SB@ ZY-؜"!ĶPh+ iD)bN e!EO?)Z={nw ]9Spc?mw' =VfJ96r$xQϷ>E"rLQ{L MλXUStN^HUf__eTRflex5ȪIZQUsszf 0 uӬxqFϟb#Hɷ?+_܀QF|4ÔƮBvd{_q}\U7poR,̾ #0^O ]D|i$vQQ}We4g{>~^+0- IBUJ[oaz^}_dB䥴1u %9mqפ$~m~W2T*52ѼO@*~suΪ3gFD WZRJ-5x21mD \R(N"Rw 39󺮕!="|N[?ǔ1lHhRoef"ERf<-"A1m10KTT6\J)sRȐȤrN 'ףf3{<cV%/> z%1E=0 >H Qn^6'ߏ]Jm8xȦFl;~N'G܃XkC`3eKp@'a&+;(n2ʑ! "Wz7b0͑X/̉DiRtKq +Ѓ] rhA؈<2(7kH)Xiѥw4=Bl3-!J.?1g2.{yi[pgm&Խ8x`BƇi"1֛f͏9ȓcβH)ȷz黯tcNye޹(CMkTWr߷}*$1~og\%Hw< + D 5YV!\K6(zW6*t*/TcQDkm;ߠ(FD(RE@ *}u[wiW):GP)1fGw>E) `\dGyRS,}[v$,A,EDyNͅ1|}'+`}[K"e 6|r5]ND%>m̉~6 \ToYkm*1e8o6AG l%Z sYA,BwO;6.(hܦwQSB~}0zse1{mxf@MPLc+*< mŔE(BA5Z^0=rԋkVfu 2AUE"* .L A*n["8SmJђ|s?I^ayeZ mQpYx愝V\M@pȬc.ʻˆEha5,/myo˶m Џ7f>szzRgIhD;Lj%s^=bI #a6v7HdZz\&ީ"u1E}L0>J  sU/"^}־ןqUo<[ùҾ;9h̶as8!BP"#tM3~",FV _X ϲZ65L ޴E H npipR%% MF}|#MyPerG6~A4ǘD1195>dHR@P=]D4b,"s`vVj7VJM9 >eɹq7Ɂ!d$%[U=A%}Y 57 5Xd[0RQ^Xw[P\H<fxk;N$z^1nNK1s]l+>ND 1'`s10O(qAob( n4 \H٧9KN"uwcJ)Ј0$;:^9E "+f^ȅe0 wDn87~l1jsޣ+4<Lza1:ǘ;BwF~?4ϿOGk׏'AèVb兰G@iٓ=~MGoV`=xc'%Cn^Ba3~o`[mS(10LI+8v;c -qPͤv<(" 0*EXDRH7,Ln¥-+E@y)k_K>61 8qeyn9uznVjUKxDْR@a%2\ЂVw]iYV eH|>T=v*PIrF>e-Zu-sdNd_{gATюC4gR[n+Z'HFfHH-y]،{, .dIqTё~1D }QXdV[ ӣ֦poGe"p,f2w`mu$r^[>i_ZA[{bof`YR~#r-,Ǵ Fg%N̙WX\!Y11B=Z^iwG6sьB6Y\RKH&}ƵÊBLT()>vZG?!Th&|=%bVEkٳk?Fwq8d{5ƌmgQQQa2VLzQ\`\6hqsoC >z!2}N}0ׂe}n(HHDuSÈcd@h*1h{VmIZ0gZ;" oZ ǜ0Zhi!R"DJɔ`&ZUUwd``+"EINZ  d_Vlg+*>ez9ADS8gPcV4zUG2HZ78aHh_T;nZԖ-؃R9͝DAnFs"  . $QDSGzLº,fXOĶ;mrFD0K"us#z>b>k']2C!#Lv*KEzm8҆>f]Dk6y}tic(??%ZV13P{~,z>ݙ]Dw!8 6vޮ߿&>A#pýT[Yi |>7t d^xh]OfPvFYm᪸v)Ɯ%܁XJQ~]9R}}yDR[;*fQJRiLoݗlzŸ䣼ڜu]w7>Teh[Kaw9N^wsbY4pQG&\<}0ly@>6ǮBʎ8OU_~`# I@EugU|iz@*-)el]CV#RTKi[`YJE~Mׄ$ODMVDxvUsR `kNru]@W(.v`bJq"4\iDL}ݗϫ6\zDS3 Hf{D |R8F3~y%] hYQMrg]){Wm$ !xY@eExǺ;73W:A"2:%0À4Wo>Sӈi㔢p6"z=b/zt_F9(zĵ0^ȾLz\"I UǏR0]D4e^O>OO_#e]חΖ#-lN^`@W)Q,}T~hx{dxC |o f¬,\t-<1*nӵG;Ԃ>< 3]5tTK+,;XJ9uP{hkYJ  < xȬi@KlJ;h'ڃcƘLq)C =!}zQ\ͣz~3q5Dk-c ) C\* Ь뎝w)p.`;7p{3@n9DDf .%<_'0m0MV\ѵ ݈wWڪ 4Ԁҏ,H-|(-I?,t"" j _fDV?e3Bey2E QRD8tLA(tBvI θ9p\bGc7&[o.;ẁi)2NHfa _}IfSX>6ׁ{)-@,lBID$E9GZ*C_+9($(ZmE+Ԋ5Ǐ;us,BaU9zFJf)`ks~ }>Dzv빷d8w {e{ZDO?Y\mEXmARTK-w@  +@(/*d18]x}@;Ǹ^טsqʂۜ72 nA˾?ybf|w $4~L(j)'Υ%mix!>HUm`^Pc8pӋ@PHO51=j-OkH/ϽSJ"z^Ѩ 1*~ 77v5ęYpuf&`SDTeBl^7llׅ.:g Rֈ;g]g#7QO֝7Y=ײ3lqXaocp ZjwmP$M%U1)u2͘XJZn3K ##O!QL2rD͉Uބ—]`9@*n~zYx7)HxwisѴqߗ ^K%8萙pkG5eWx uSiVh \Dp$c`ꥪY]洖< d}2>jNs' $|Z7YfK3[1x}#S)1p<6т=^D<֢+ =XeyJnSѳ q<l>}/A7ݧMQKiU!u7Cvm21fIGh-3ji*\)LUTX^[$3R#nx3S+9{dcl>t+6o~ǿARy6At^3o_Ir&UiտZ\ D) f"-XEeOD!xSbsj^`CE"7:P (۟6>m|G__nc^Z=o6}T8sQߠ}`-R>d֔E 0urDct'- xqA{ﵖw1{ׅ;Д@js*ۜ}2F5=|hs !OP)i.;A 1Ii_)v {FtLl䱼4gga~ؒx|o}-'-{Y΍᫕IuA{>C{-TiIA an+Z0CF7P}7`"B&pl59cآ<ߦt4Q,[~I,SJu 7 IDATt}3m7LrLD4v #B/Qک{UZST.J)2r:;gk)=8N &t\`8K1ٸ8Z@aČ׫4(|JIZk!"OcN0q^A'{v YU9w& onk0!e͜s6w‚!njWHaD8t`cTg P_g)Q G1`\hSAӝs߲ʝ(溆f(K”AKd9M91ğE~:Te@V.GƔ*ȼqf8Q|Ds΂FԊ́}6qNU"$щ(ck KUA?e7 V@}~YrJŮrI6%#ntx xN |-isGq)4ި?$sk|$GSV2DJ1Ve)zZ֪j!7^݅@۪ ݱ׭^ ڷqea+oԭYZE&0Okkܓ c7-k al! bvۃ½-[fkNmj*Y|:J CaGga-E@cIrg8[[HE=\RJq0La˿МRJvq⩂f6}XJōi)ȗ.k y~ٺ/-R&//)yg nsF: ȅڞenNPМSI#9ҟZK}"X0!-# DMx|.wOa VFPn+v-XEeiGQaSTLZ6BF.=v4MU]BC΍u#"} nT1+\0AZVo ALO Е+_ߋ&131YZ뺈|"(`ܣ");Ew68W{N+33UGA;Q*, MtZ4T ~hn6a0ې⻕2?;I=yZa9ڈlȄ =|N?pJ[BYv9`q'=ȝNK|+Wui.-ƝE}Z7fy K(OD܎GskC|n-Qg`6 q.iI@9AG_o)> MAd8ꖠapA[":3]+>3JmQ<=b%2]_s&#_<-HPoqnY,PEA $D^<>/Qi(ڒ􃓔cZsR2Cq'ܢٜ6ǘcX \JYvgF)I}l6t;V/5wxg6G%Ƕ}r=BaoF;˵htƭ}4w5~hYr$ wZy(Ƕȷ;1_EA9څӓ˲@?Ȼ[NT#B7֑> ?~/c}RQD44<)0c JXB<Su_8ϣ٭_qR5~m s YO 1(f89C׊v-oa͋P]@V2!}/o=<61|ݕ BYvMw5t>HPk{_ܙ-M&#jͣq7H3(*2 pRUu9沢K@ww6,ژa-%}nn !qsND=&>[zQ@q‚>d aYfxnG!ogJ)%$9zZe7ttsLKC){c 8Z;|NGY=ͷ_Evdۼ(YhÔ2> !XyćMYƐ#j ,"Rnj,JH DEAas,˧p;SI0e(no_{|??]O(Lxmod g$9+MQ5sk0Wm58<@QS_8LdFF]Es>g@-s,8 QoNsL06{%CXofFĄZv{b7"c#oZ#Ǚ wk`D約}Z&XY9]&G3?Ncjbcu8Z{׵`r=3=Z^qcI~ݾ]-zڥ _S]K$L,SPըއ;|< 'm:+h)xu̾ uXOUmt/ۢ5N4z+h)bYkd601j齫RbZU-g֌p%Nil(D-V en ȶ7VQuZ .Ҳ;KMUKf e$3Sɜ|S3mu\rqK}ՌиOZAK>*̯XDqt*`IFm̍ntO6q/N_{Q6??~Jig[3'7gm0z!O[Ts c]xtachVUF?^<:pA3}X!ѺPTn1ASUa9=]LT_vk^VӼm>??pKhxlVd9'"F*5(s>mj=b1;.4F[Wfﻖ,}8P 3,@"R9vKtMj%mF8~(Y ҏ\F4,x>;aM^mGM#`U' NU71BK(}\2}xj)3r7tբAq<ȉ}c>lS?;bx'sI`딈~͸n,ZYJQ-PDhm[u6tZk NwݥV%d<3ہiL.S\V[(#퐏" k $nK4VqaU\7"c !r < f ;}2(X5j4w {q=WN !m:[s;9BEXe!sNEpPTjRl !; ziᅯtnDޖkp<73Suo;G)`< }Lj HLA _KL5@ 4E,D6lp^ɗ;jseۂ ;Wwn[AJ(Jj1ZkLbKJ5/b3Pk[UCљ[ohCwkB 0QqhBzE+zôDCFOGO?^fv0*̭0яJ=D~LDxw,:S33γ^ jїZ 7 !gԀDL6Ʒ! <>&'З*cFq L#( smC;rsQa0;+R$񪧮5݌aƵnA4ndl 28.A(hKS. ?T"T1sLS%<wb2aJG_ Zh`&am%,I%:$q+s;xߋ/0`A>XۗN"CZ-+P-jo Z;,_UmwFV;(zc?~:s۶7~9%V2friq *2r y v>??{of|~ w_|>Z뭝NԕRq[v9ai pn[# Q\DhlT'΋|A@ah`K>զ.1f.O8o|]gk3n֗UaYuNu6݀5O?_m 8$L8$ή<(h\Fm :ϫld lw}ߙ#RX;`x8@{6C6L N\ N:Ҍ+118Zk}tl9׶ +jMD yv(SJ)V 1>+Bi׬y=71[00։LWZ,0s@AIk@DF6lTKh:1Qsu2RR5iTNC2K&g&1hV *QQcj+哻8E : 6|b5yRk}eL}J`o]+R<!qfJ*s Yu{{N>r8J|c_8qsQqi6Dy$u-I\(|~|m+lX9qÆhբVkEt҉۾GQL!ӽH)[5XQ\A۶0'q^.LZǵivXBeQ-EkJ̸?=\Mێ!&jcvoPD[1R-Rvm`ӕJM4tSs(13*ʑ~DH)U6+bAqns;*l7r:ҐGEE'sl TaTEڵ\ӀOK5k9w"8cz =h-(0-fџE sٟJ/SP"2Jota.FLzc!4Z' WoRrn #D֊nax LgpJ\h*¢z͛݊=Dh'8Wky,/.wڶ֪ZYQJElu%ij5s\3ڛN|qIQ֤!DJb MtOu]xGƸ&aDJ1R6(z!d1a\iLvd)mC¢fBxXMѾ=u:hT8-1T713B^ꥈJᆧb:> ZjWRFuy]W)0eB4N)lìRj)Wk>/dy[lZ3@7$̩ɇqQct$u"i҉[ιfǜp̃[ VzsZH3;΅f=2CbM)0bg&~"tqMԔ07E>%/3 [yY#r;; (hT;|/~o]W;~|J8oUNV4`,Ä-tef}Qj[ɨVKjk6,"!zfzza!I1rHFaox"e_ &]x=A_1"(?9$\foUӿrsq(*Lʬ 1Ҏm2\Y>R:ؑN ȥ(s΄(,0]!9उcwtcRk4܎閍qީj)u18lд`Kh\!1Lv櫵heֆAt3{8ڋܝG>aبBG#i6*΄jy>Gp8^Zk+T!GOl胈 $Fw, )&)'e$MmTz.T]3i}$#CԜ"$0g9r2_1 .E{=@JWQw{& +\;3_~_@J~6֢$r{$!16$?Wc/ ֱfG,wfNUj)L|.b۾>h90Nkxr vuXM-v^kQQ~(5;UQnu%dywD1Sb|K+`!*h*D5kⓟd5!ΡW]LX {vuf&L۶ `fT9cP˷m<W4R H0vVkz1j!&q/@fca"XL.qVV>}0oayXOjEv<+.70; drcEK,TchNW<úfp.ZKɥ[Q mjTuda`uѯ+ևz1]שygEqib؝jBaV">htW Cb稧*ineHar jƼ;G|hLI{-B!%x:86##1"lkZ5&҅&M0.BK}ωJdcZ߯)kNw`ˈma}IF V ʼY=]T_-/W԰azXN>H̆221z_XO$Na8x<'311 o|v&hc!P) ZPZ'QWh2YKaYqMv r2xpaBJ7 |uǶ?<]gC6z|2.EUގh|I[}aet)0A #jDyLb3'?^Z{}D#":9wIvKf/NndSZ^18І?=$dxS*X2BBO GL)(߫M+hNH7/~3-.O3 hiboڡt`R̆Ռ4CWUhf0^C1F`߾} ~> <)i@6tYek v3hb*njE]1}XjEzk& |`3=aXoܬ)VIi~ZiP݉Ka4S3gAyLF Fvz3'Ka^i :dCMگfnN#a<jٶ mÜNJylݘ9 3q8Xo"ys9y߷q#[6cm|AόE4(%E9)Bs S Uuy (Df9v̽ϿrC&)gD{x0||onwjTl/iggM)A9S loB]!C̢ U܎\2\Kd! 6b2s͏Ua4O33%tn23f.4eފ|"ݍʹ__tfhS:L 粨0on.%c;̝5/Velo[vqE6\"S&x B!ܖϓ<%S q3|  \Zgjk۾?ZqQtQMf rĉZ6" 9h :.X+ #I[*yf]l;5娻UKR;so:F,ogV|_yl}<rSȚ d<,b>hv]x[ ok >{3{6MU#@]j&8RD&} 9 P:+[Cb]@a,*K!D؜[Wg(yRXCfct~gN&׃X6%`cq8ћQ 7\iP7DVJ|\lڹ]UB广6׻ |'X[JE&VSkbhoѴf)oلh,1<܅F|#7Qs~T?3o.y, ˡ_w%pAt_3(74̬-?WN^,t4 e"#7 ܃1 E<*-ϑL@8*R%Lªn=^<'&EwH`4yiDRo.{D/}?>۶ZEuU/8?>>,wZ;Cjƾ?kǎF\]}Aq_u;+䃘[o ʸRJ&~R܃^שH. 5&:ކY? HS"zhj2֮Dqcb^o{*0q]u<n5ETXSDxvydwdx5fB&WOaJ'[O)E}|Xw'($ԝ-U~TqDZF䬗Q w{Cq\{gS/,!쳁؁<P櫌b)w7 Y ^d)MSOOⱛ; >|Mdhrଣtl ]8y`mXy|j͉$*#;f"r`0Ԛ#3K!Q/AgxNZ.`ł]~T~s[ɜ$u8,3VU٫H\I| 80gQU|>??-*>Z-'cQVqm۷G>|j&@0 %cZ "fv.4}ʋXk)$ ^y^_c]ID`3q"@xAmn.pA f?󝹷4S ){yZ'_ZG̉E 1D)_0Z`|b[[ׅbtg1k.~:F%Ơ< }9u]WNQk '#"`:əHb2GG\LQv'nٕƅfw`&* \YC tdOF :jsU2Iќ1n| aaĽgģS=g@)_ M)M^v[defDh[p8DęLX[w8zZBPkM@!}#vl(QT谳[ (iUo\/L4̇ %D |K:0U$Hyћ"E*E`f)_m#&yN$C5^qN1fvnjo>31c9yu2'1 {w-8.pz^^ԎAh:ܯt D Af4 I$"!!saȈ" H KIin{C#m۟Ͻu0<,3 RLdXٌj<L \W06b}~~~^൑ N&f e}/y-c«5cvȶԺqlJZojp zyۊpb‹=77fCdcܨ!A:@|6hhhsoֿCPIok^떰VKdb"&v@OG, eNh6Rqmg 2s{ U,_¸[-Rl5Xܠa*ܜ$Tb172X0}TNG|\"߯Y4uɾf=/8x+eNBÇu1skGa{r/³dٰ|B"wURAImwj z>?$H){)JWG.Ntn۶{qo0BUk6+FaI!3lcf39=.nR>])EDj)dR13x ,&<4f^AԏΡx%& A5R͝ZGk]'FfVroWCzKBZDKݶ/AF|^o߾Kf?Zg]wkȠTG)u&ǩE1"BSMDJ)>c6ā!w^y1IM>F,hU}5v4;.71YPn88K!/9qY@3&srBV=g>˨ӿN=FLmNFgtҞ_DƑ!h)=Kq00Ewѥ3\!;6)cI6/op ;,G3Αgoݩ`4R$q6f.svE^dA |f%oF漒[NIG궁RZyU?`X??}ju?D=bqԻWcW۰6TI|HDnu>0@ADZy$ȑ& X;8HXPV̌\>FG2mwʉ9mmijVpEM҇L@xub8FK)nb("p|'KXwgO$ZCw靚h #9ن"\qz}\u54) {ocɜh?vwa7H/Te[8z1'M|G&* c(0+EE>@p"v48>ۅ1F4b{ㄻ K5Dgf@2r0DXF1;P>[iѤ4mYb^*5CW )PHE6/|<Wb?JEr4ʸ[@β&Fɹ$@XP[CweHF. 2G1RPc.XBߟSAFgkp<] U$:@b'kAȂ0GLr0|=_8ԙ8= !m;`YMy^ֺUk˔mRkb @x'd|Ъ \1\$`7V*Z}gu -fb*nh; yy,j#[Ub6d\cs32G~Wdjjm@CD} ]T*ѯ8;'c3kȻ>uyp&@Ya/5 vk P|}Ot2JQyPN~B ZX4Xc<]x Ee֕8U$dccTssaԳcE? ['fcmk~ۨt/+ST_'75p2׵ TZkŊcl(߲<yj}ߔG&96Iy{e_C嶺 Sთ0od=߉hl:o%p_0N޶J1E$)yq*a=1oŒm1[uwu]o;vW"fs~>ϫ0#OȤޤHH\l5=8m ;)ɼ^/x QGX;|!<[m32rLj]J"[-f>E"e"E瑼fFF rJh n##ofLYRVJhaƐ/JĶ^彙c9 <_L"}b룛c<2X"Qu|Zm6]%]"9,Rŭw4׺cX #/t)wo'"Yn@0L{??!4B`_,o"'_zp)%i{ę3-Sw av#{ϗԭX?=e[R!K6^U]#.[o9s~'ʴ+,)K`*yوLW@'0Y!jݝN E~_/"bx~Ngxexga6J(iIO< :c GFQA`!i FTevFK1!܁`.H0k1܀~Rd&O? s圭OuF$}KZJ m(Q`òYDG-j۶0 #:k.} ^y'PM{Ir ԃn󺳠VpKē1Lz7䡛پ?0լi0̋eR5nmX!$2~"9k7 Zvt so"q(p뺮vZ==Q#L11 <_[,IJkrwu#7u9`3M I`,Vٻ4 #zN#TЈ񄩌>܍.r?TBVy1x6-{Z_\+A&&[Z Gu^3iG4(Dlh܆Ȟ#;TL@3Vʳ<5D.|x{!WXEԬ zAwS۶}||@ \clEDo.תE3Z@r@; uýl&"BD***@ƅfclL} 7C螪ZX; (NY'\mFΛv?{S wBa:|Z`Y P,xDe*(E\\¤x 51W`HT`ϧmju]=Moړ/1VysS=߶mh{KEa,*$, >̒>57$a!JkCN\ u}_k dz>xarj ;Z7@bU,}NSEqs"걱 QtUk1j%U-ejk,v\ZƻZʶAI\("`mB% 0 kyK-dZTl#3KtoqnE>ȣ?5yiEtwf`D8ϰ:3GXD1M!n}=5{y}d;_'*QZ @%H {:O\kE,*3}(N8/LI( nunuʁpJZM0.k8Ul\ ߘ h^kMEF@;e)ZNYRo cg!aщ]v[{asBN*%:-uC[F2!sw BedIe|yA _\3p;xޤsW$C s1c ( |ZUE>%90TzԸ/ ;/WLcJDnoFiÓ[YAT[a3m`ӇIThgҬv uc*HND52\@)*O`qt;u(ZILV+LedD ]<8!ܯ3kA5"]ӡ}$2&Cd9 ihFjmm{D797p*j]nUKL\u]w::+iܶzC"9I)lrb\"F zw.myPSSaDx`-W`̋R @FvӢa)iB;d$3- j )麺ǀnKz]Wk=6D2wp6 0 ߫wcElRZ~Ek) 6ffVET3=[k58XC g4<1 ЁFK} a5,X]TɽTݶ]KRafpYUbz9)"[9; pئ ZJW R˶m(d. l@* ćl!br ^,S'ivn1ɒ2vޖt{fΞ01Bݍpfou [bDo_nM}-zZ&/2)7T^[H4 'Hjgs,0\2d}mAw) 7v[3ygn6j-Iq([\ *>sዪ> m^J\6nCRuSUa5;-/чAZcP년R &qA;@bʼҌGr0ߴg/9eA鷗 FkK'+5 5p-*s)q3e]Ͳ?P/xq"Mz3ڦe(47wm2x!m#coad@2F_R^\-cX1r,D/N4pЗf)T k7w>paVJUs۪?^K1?zZ MY? ncR Hn EBGV O02FyZ WW&:mD'1qǩ*<:% ,hnĴ~6-FLɽGVk[) D@D"pII@Dj{ﭷVwd8(LlZp݉l~WHBww"-%ժ`w'|F'ZT<1]| 12$0 'jqq߾}Z㜍;oTbd3ZX [aSV6"RZP{<&lntJ/ցg_#UDv(#Aɱ$zbrw]PfWSOU֡#'Kwպ0RXɭ5&z kf/H,a͌ړ2i;a96 KCa$23K-eamC{aNB;9}00\Ý~1۷!h#6fgiSGT২,pj1탫u,m3W"(,.Xߧ .7Z"RU(`+NCmA.L##kOm! c'ڋ>Zf"xYas3Cj'we9)&z)af^s55@urpW'v/N*o NV'g=V"^4yb*o~_ I/Sdn!ycl 01JcU !# U&d0oG uݼFHF׻JabSBavG^ZN RQwRe`.쭰!# \/S"tc9Ѳ6h5mk8YfuۀՐ;}'3 Ƌu,jyg2n|Gӿv]nz$P㌘W@o{*@80^7W6nNFZj tE({ Yk ]uao#.DNufyCkgSX1yT \]^\o^0 ,"rEKh%+m{}z[ΩοsH>h:8C̝mύ-TLn/#KZy[2oT/J ND FKUj[l| OjI_Ezha&.N>͎̤Zcj<{p3C^- irHUbS#>K\J12Z&ruiGS˸9,(6| ! FxIRvI|,4SZK⊻\|rפL0"nSԶBgҚzA(`~cy]XiZgiᩚ#M^m6.u+ʯMT_rSIra[;B:4䉐+ 2'#U?Pb&l\U&:9NDZ}ڮvv].9P37 6Z t`;yQ 3|R\`.Xc???_PE:8fHA ,r ĭ9kPϰ\3\Xiǁ&Z[?FmۘvJ-Um%HݰD f=*5U1[c֮b7,­isyp);6U̥-gu@x۶bw')0'F\- Ʃ (l",Eyu äWD}l %HYYTJŴՙXZk8a>|!ނE>ceaW@#qvY$ TAګїK/i rTC8ֽȠY8i*__g#p-w>ܿMMMS~%< 2Y.bZMMy%\ϻrJ|LA .N}>/Jl}b)9e(I* m٨7P"/lA??W 55^q<T8' ke߷}?`[6`fq3K3:*RKz6j:MIỳ^<49ɣ:Ưqz yyӶm="~84UORZИ1>W Q2H:pٶM眣  Q?Og =cc1u=}Ο͢k!4׵9Km:ǐZtNە@syɨ֡Z5=O%|X}Veq.Vx\b!f\ =. h+--Zd1Bc荄"LUK#U.\ 9K)j4 *mens]jkas~B mkiEJ8gА=Oc9qW":$LvEsy][!.d+g$3[hhqG8O|o^_)^^_M cٗ¹ ¦XivdS\tʈ+U< U[ #룛 gXlO_K/~u0 fs*~81l[3 c@m>:⏷}'x:XK۶#L8a6sώ@R+(ظ=b."s۶mv"F{W׿b*᤻4̪/*2mQ,3b)ݾe$E4C.WAmkaYhjmX1)5E$f1RvACuVk)oOEԾ?fXÝKz>2@)[['Jz^d$9(h(!,$GNOJA zs]6:!4#jIHv+RP.hN,-{ߌ|˯P,{ǥ]nɈg#vA@t]"P92EGh1oo:s9eXrNȖN옏c~]&j=HvL3~3Q7HRO2Gbӛ'>͕K̈+PRoEZAq<< l0T[Exjr %2uj*\4ckm"@b1RN:5 N3Q?Tl{\n?D3jk$"$'<1sex14imks'Zu!öh]AE$7xZcP1Zr#ƔXt+--M~kSdt䳛~d9aIG}e[-nH#l뮚6.f+/p\UǍ~k^6E8 W>+Y4?k(o IDAT{Z^x/4]\& Ԧ{KKKu-WT"[f٤zq5<¾;qL A=ּȌEꫫ 6:69?ZM|&YM ]pm*c<WX^-.5.Ervo˟Zk4!3r?/C sqr,GM?ڙ 4yba x ?~+*/0J+M02ҰtTϟ3T~Bx{{s.r8Zk(c ^&Q/N:@2zǩf5bR~>&8%,"j35c2؆:|qxC5lI| ǰ3yBHbDL(3ꗃl$ u5B *ڋ_hTҭWFkhk ˇl_ҳ`t]]zK䭗!VxTx[H wjĖ*(-z! ˈ|,GkDO)*2#ޏf~|#a2.D²x9ؖ_ᅳcsKYGPӜe5Ly59oosN2 n<˟? 9Zy'9T<&fm bƜ5>֐`Hq1~C-NJ>`|>nL#㺲(#bREV'Z8ܶj-E B#$/{*#Q1-`{0M駌Vuq)iYH(z?8?na<'d7\qyc 2[mZ0Ed4]̄ͱ& A9Cm|@ʕunO-_9V ҅5=;{ Ld B^>_ZX ';Pt"/YZ"䶷_!//:T5 jMޭ/\Ljo!@E '07S6x4Js32֯?Ǝ gL: B5na쵵;~c hLJ cZ]i3d8;pH\.1+49m6Y/ÌɘBs3O:8`Ø@>6ꤸ1FmmcTխm9;3aVx`yZKAQ-4RzGu5ϩO0JfDʍ9@m[Z۷<^7Rj%~d3(AWtj3Nf|>qz>ߑ4ט3~\4FJ)N qs`cS?{L _!sZu1Ɍ,1k-}!{02}Bxu~<ŠE^M4[$@a퍍@p5#⦺ Qnf'wsoXd\`Ubu _ _>z5u0rmWEKoYtgdsȿܭ@ƻTzr> Y`̃bAYȗZ{X:h)=_ɯ_Wÿ@a5ZQ \!]5K8{åG ??r9.S9/fDcZmm{,%njR90VBGH~9oT`Ao33ZYZkǣ:>'Bҝctp^XJooZk<3oc:<Cü1#,W69nvlTu\^pppg-Iuu\gRI//}۷Čggb~f͔i1:NzѪ{n> &ծYa"$X H^"7QWjWU)XJ Pňt#r<ǟ?9J_"u)RkADV;͙+CU@=пZ8aR1լT Ryu6qP%lȸˤ&gC,o9)N9'%#cO!}oc34)o 9TFZ&*;|uk U {&"*ƽ[kx5Ğ>h2! 1m)+nO=3!f\qcpCS@APSǹ{O=<ǘTn+\"]mٜRy}m[D ?1Y53@f`fp^+= wnꁗf^:KlG9:#gPu>"^# d7~u%?_#ݝ~ھ!{5xϳm PkS?~XK×tYjD~gLJ-9R{)9徜V 32i,qxLv C8XqR} Z?0M"8НS-\ U"Bsϟ>)YULGW' 4Jb E 5a!V3Z;`54 }Ns 7s85R$ buGOn:vUy9TIS#Ch3H.60C=Yt̅n_K&VT-%5n*݌rq4JK~*Z@ʵ2_ŏϓ4wYK*Y #,CKŊ~Ր%u!c.Yeqk-m ߰W=?ރz n8;5!U~O]wB/6Vk | ǞAŊdb`{۶&%>ijQұ#ӥ]bob'wwY{U7d7~%pmyͩ$;ۖY24FPIY08`f"5bksNԵZ dJRjfpmۆ+5‚)%/ 1FŘabSj;ΆϏV^!BYHsaf`E;%<3(Y Oz,MӔVzUȚmh7]bb̎㿗k9ӫ]c%g˜y 8<;^fGaS e~wUJXfdLEg@J4IB˖+r凿5h ̝jgf۶<9< (6 ?~yQ11lN猨s-BoɄ< E>9VBOiֳEog7Oo=/Uheт\Kza]*E,,k&'Sb=,iVygz6RN]y-/pZ99R du WpMj`:_; <صk)eL D#_ _ _W9[lYS)`@c96`$`e0IW>')8Nӧe{(~,yg'r{s`+nZǘ*֓ 㒨`1AB, %FIĨDrzEXam8KU.,&sj^kyD4R#ԔT fR<e LF*^23#!@Y}ԡ)=ݽU_ 23 \D9QZkRlBra UCq,fn*9MUmƼ*qt 5%qIٹь-ᵮ[8MϪBbEtIi"^{, Q de}jX3.aK犞:]kR= .c1ʂ%Cllʉs BN-Ֆ{2!h"hO0eTrч[I,^ ܒ77|>~dI¥Z9x{{CNBdwb!]-4Nᑅfw}=4d~z0H|<ЁsTaVk9-QfH1qBfS}>sN)5!};Y8Bx@߲M^*D=?MxSF`+)smuE{?S|[[+0vshH }].45%|%Ft5<[=g=|fK!֋v`&8eĺ-PLx "bM"P{OVŗɷ7WW~ _*>'hS'DoHANGmk<:ܶ 2l >̃QTC>.XhMŴ&Mч I9'̉'#)7RQ]-E4,4JFxRCfhKuR{Bôt~?󐒛t__$]5Ԝ(kryoK5// ʆLq';i^k3 v^5xJ^Lf*oؤ)JBTS^zK௯ $N" dže;cRL ]`(.cǾ[t<8ek)}S!0֊xN[Cܵ!"gi:] ̣/ӢH$SてDjs݁xx.AKXy NsW%jh01:rlV"$nq~s*ZIyc9?|1LȀd㈂9;|H]UxZkuv5}~!"%>goѤlD& =e,cC IBԞNwC/D36+KSW`_0@Bꠀ+.ZIY "yX2s"/Ԕsp.b ǫ5W | bNGMW/*Jޮ!+yyI*{$bkuCxYgF>e{B W@6`K;覗. z x<0d2</89UUejZk='c\|>ϳǾh.M"pYk<v*}쫬 Kp4D-3sX(x-)h=OpL/g!HFbTuHj>B5xLhX+ jFt(jnx:&z IDAT OL6Pa(J0&`m!uZf[k5T[~q\|&F mE69}äzgDž72D<9<<5z97: I@)O"fD3pK& jpيURSqMz5i7RV]ILx)e (0 BA+&;s,+-:^DVZ2IJk.A1uN/Bə֩rVJ׀{ҩW-SZmhCڅK?_:w0m?,R:g\ϔ!NyˊtQ„fIVLЉl9ႏR4E䢮֚R >?ɶ5[Z Q4'OP#R O-\_Kվć.5f/d;tͅ`3~6OkϑJML?o@4ɯXey#J= 106BuzgI9x}L>mx a]}DZ c8z#&eN&{hXD̐ZXl'mk^CD2'8ǧOJXZe29wDZCdRU87}hؽI{=k)ëuu'i$s1^u)uyvJ dEڵem}uBMB92c4TϧN4tv#%|+B,SB8#bLTpW2`Um ]Oλ;TTG6h r̒ `mPS}X"/{S& JARlac.>wS-#h +脒_!BCU(c*"[c@ 3m"a:DT 1EߚoooId+@M:s ^)2'jq9o1*EdLUtkzs9R ˶opQ L|~2Rb|0 'h0v+81=s߷9g)2U(las11͐njuS瓚IC)pQ }j纀@qyӯcZh{-Ƃ{1UbH x-fp@ ʝhH$/2PʘaupWR0ꀇ/UxȗX>.8jW1 $"6jX>jꙥHn,[H 2'+pT<ֳwLZۤ _Sd&zy<<Σx6C Z\rl\Oa+k@}uw>=ͷ`!deyuɲd_Zٮ`%q9))CdFȃe$uey{Q~fç/̹rZ/'j6˾D,R'Xj y:W̨|j)FƮ ̯ڌao͌IB7P}F.ѝ콟۶}b,F#Z:ÄE WϚ݌/f1Zez~f9:(&x<({{{+"R */l0JQ \am۶5@;9uuABt+c A чNE:(P3-ITb*5L "R20nEԥx>^ێywCqZ$3R8[mo@  :gkuHR[Lwo&yB&fDG0"} *b[R#Rؙ'-c, Q HƋ|&#AR8@ʫKeܥp}n~拡_P\g8TuSID@iIaLNbyQU&w?}:E eHm .f!Zh?o}_wJRݖ\j9ydz.n!rG㱷V:4sR*j=Ǽ ԇi lԘֺm;1: z]O }mE?KH8J)"sN5c9m3LBzXjӧO娥9` 4p%[OȠPY?~Hem>}G[[khG!֑N &i&`nЈ_)@E_Ю"G"+s1!`zi/^!n"?BuK7|1Q]ЀYMk5EC_dY6GDZm'޳24 ?~]R`ȏs!,ƙ$.]{^¥4~""hE/~1l-H?m<;Rnޚ~S^0I6ćmV,PqH_Ji۶^ZbH RV+x"fby:P?D=j0J-TM}!۾=(7o.os5vn.h$m,p^s2#c>{g޶PXd"|>ck)1&kT>{W X[/Gpː!׻Я=keRZØl,JNFBuHm T5ZsQF@Hd.9.y6qc&PаNeq_G\p TKF$DWGdMR,ۻoW{@խ3Q"bLAoH"&(rpn(>gHƃӒNfT v}aDmbWwlV(ҷw}穰~;Sa0߇~ 7hq!s1l< !9>}Zt*c>;N$bQ mi-U}o6MpxxC" ө fIۄ(83ˆ\{ E_6vh)\e!}u^j"-F#y1!`x=~V8{Z 8":1_O\\8IdXUMXI@$? u-m3k8Io}tht|mx K) {mͣ5ljAFz1yCJѩ B58|GD#Q3ć-A|Gmy&ɫ%Y]d}}mmv(ODֆ.I((!s hSjBm89\8?aGS1Ë9s:pJ"! 2FWR 9eU]aF#aO+sMCv߮3 ŵasj:!(KJ]NkeuQ-[Lfv6ٜZR9?(@@A^%bmv\Q[5-d2V\CZO(-#e&#ܾGf\(!oEѠE-`:o.~_O@@X$,۶Q@2x"2!1aIU sQDlUgt^JM0@cNQљRTI1& SF†dTtS[/hbXqޟϧ}6#!gJkM횶qSݵ2qD?'v0; f0FVR6J(-q\,b2Q)Ԭ0pm ~O%,;! ̆wSZRJ?OL튔:Vz 4sNZ39bu=)5|neĆ8+VjfTo|Q^vxz LAcuIo S\X1ښ)77B@P  |SG)@HI%Xi Hs?%鯊hȀ: v_PmF:E{]M"7~W~P)ᦷcFG5:i ,Ч Y FǐQƓi9rem32p uE%qZ̾e)xN|[\̂bF*bօKk%e59"8J8 ּy\46|Ǻ~a똮\\1q.)VođooS?mߌ I]37Cbu 3]>,8$PsR0ѯ9'6} 4J\񘃶Rg٘۶?}+/E|u`UI',yP4b^Qߏ (80gV3aөwɭmmksL}tT9g- )Lꆝ0F@r-EPxX >WEG`u}R6F&Cqw#s,ԺgԒ&nHiC.~ǕaڹZ$"16-t0-u $H_Ur)]’= H-kV[cx%/Yf=vg]p{DCÁf5BXC̹,LLpgY#_1KH7ฑ|v db™f)\B'~c]7o Re\ r hhPp&bA 0/gz7˂0#ČYeZTEܮQXW֤[q%ŷ)CO;mïւشci0]X0P¨F(NTZƉÑ2>:¡T<{mMCJ3`LZ1&Eȟ1*Șcb*.(;d:F;t9 ;M h kNjHDRHDyls4_S^>3)OA#YƀN:,:HrtB'($|xr9A_ L%xc*˕66<{Wk-wD""K>q5Z[ǛSw=nooM>6{?p/y l[s~Yê"PRH1nB8S3ڀb9ApZ.NߔGzlH#hw1]9ꎅAl,'K,P?kN͙3Y2/5L3aDeٞK~GtH/h/vKΚ&Tbbįq5%z!vtf=%{QfŸD׊So}bϩre3p E"WDMmáޮ|Ru#&ҁXy&~l:h)u'9%Ϋ:%07 %KDf>| .yξ| IDATž1@  vƊ@Z*zgأCRaW#Sf*qM1k4&588\:4eCD,FbѶq8S@z0P dbFYb| +HT!"X0dRE Õx,$Ifƅt晚wJIlȵ%XyH`dpw])ekɥ]mY o0emo9PQ3wnU6ٴ;5tB9k\^mZ\Fs[=>N3/9AiHS)nE8s'r-SRdn"B.+ B~S~%Ko} cT-T"Qo@EW K-WBRUA×y(/e2V-8 Il$Ւ5 5n _%Џ-[@DDgi# 8/˔0Lmfl1ˢ|TJKn A IHFdnէR `&<}OOnm"DR<-F 8)bHS\G𘥐]TtRšf^ DSsý#z]ԟp7_Le=|/}ܞ*eV5}T, =ߟR|9u,JW ?.&CUH iүGsLZqE]q mf :KRW8G3ncbAq2&7?n#HJ&m?x1I!{2MPs2g}31deGaAP00e\  P2!٪3WϝI DN{_{jO۶Ѽx4v@RJ!VR0 uNj} "]D `|jMG g_*6gfc #  g$] #2XuidķBfs,\]ݜB@8vZl9 G=r;ZW^/kJ Œv!Zb}[+q 2M_v4z!,SL뷼nV.^Թ;1@g)7ϧ>ؐ\+[@sKUH/:pH\GJq0w=~F~rSg,lmL6V/~w[-D_wmJY9` T DcRx!eSOe-|/d^z%WXQt@c C2/Dx"s~q^z]ji,'4Q`BWg!O}"C#Y)ämoVt)ɷ k,L#V['4A6cVkq0~jL}N(4iGjvʵ!M[#sa*5`&3`hwP4& e2mYi[gd|%,f'y}-]>YX[ك$duZA ,iÄ?7v m%ׯ^)ْLeY:݄uDig2{q*~tT#ZS_`T'Zi2cc@asfT󬳅Q KD?30֙H &՗ RN\q긮"0xВ/hu ʄBNK:x>y~9:hqhyl%I~i6E:ÑTK8{u۷Rxfx 9ԉ櫭5"yb"I@m[O>x@۾Z?rRJq1@TOjqpu֛,n Y<;< ',sW\kw`=X#\Y. /F/PY/\OKr"CrQ7埤M|Rkβ!ֵ?7ueNWM*W}]tm fUNY9m,I~KrDpYwFg\8ZKqgu'/+1߇ߑUe__?K/mY0%q1j\aZ/E"-3iۿ\Qr J"yA BXD(JcBV-B1Ւ5ľ<u ¡^#R΀ΈoUER`5j yrQ62&6-.NlIUys [kS啠cϕhujHҞ8jjcY)uFS]$W ~0k,BP,]` BH%əjfS},,J6UmQwD (=щgjd08_TȁKê{yyZ~#r,IYɅ43B~^B]*j$yKe{WSt!QEmBHĜ"|ReZ vIf 1hV!?ޣwwސ vKo~w$b J8&RĦf&XN.ٲv}Sa0iPYzGY %e/<QLU%=We&3m ʳ#ܿɂC:GfR)wCc"e*"PVi2 hE7) 2q_sjz,;Cܿ r=YM' nM.ҒJI|xYq(Ǣ4 G0o;1z jpmf*" mtNfMO'{$e8j>ѠHwqmenD+};os.@\ig0>;7=ӕŤ׭yPX:mՓ=oSU?׌,~.Z}0#lB pXiCRrp5{U_E$wF۴y4bݣ$ R$8-lJ:`JE\I`.WBN+˅ݡ K5BEҦfr7)?V܍$s j(=]=FW2*W漥wg4{T@n*./Cϟꫯ]dhjQ B Xߥlsӎ'$}X~n#l8fX[~<1F5q:ݑփr%8"4lI9y<[ Tk5OOwwn֛{X*D^C4n1꣓,kS~+w<:v!!_1+,G,c|;ywm ŕIL3haY5xsIטR U{MXO14'+&†tΦ6{wM=S`]59OT)]./cHst^2w¨ڢ10!X9ϳd3sjAXAmw?,qo[C}ޚ: |*)*i46bĵQּAA"c:cz0Ǽ{ՖDhy0~*V"C/N9NM.E#Esb wxD#\vR']Mn.TJolYQDZ)ҕ\y)zǥr\J[R Ƭ@5y`C_X'LJSW 8^E#6P\ˌTXy[Fm l&8-fkN) $h@bZ x"_WuKj*Tmb nW3?O%W^~I }|+Zt~6t(9ܬf|;_.+|#xA9$z[Ȭxk+w>vpsaM`<[znYm~tVGlKbac1EHf&*m 8n )[QH>.2z`),^aRo&2pXc=bwDوd`pQX+FqdS`IH>n{?7ǥAZܟ#ZIC)w0.p 82x$o3<4Eq\tZ>/ c hYApd5ׯd&xGQd^0͙$%~3Ȕ1tU@MB>.!sGcHZ=S"AxLpj@^3kI$%@J,R9-dߏ=4bC-Ge؅,Z|4+켙nJI$13+XFSǢfV00. [8FòJZb(nבIRm|G k8Ͳ yFWBx/Ԓ,/6ەPr3WR3K,J=jRjY(5$É%{3;cPd0GKGnzF)lػw$Ma0@7XXnPyF4_t0\~m??)PDhfRfܴmuʭijkl]6Ecy =fBW9q ac",s1>M9el3;Zs.--k(yj}t +7Vq{DOOOqcnۻw 2RcU15qZZ$ !~q thTCҍu{gre5Gc_p uoCb &r ]Jb:dSk/%X >2׿OD__~}v[ 'gHČ$JuχBZ8Y!Fӂ9m-: )nxdB^qQlY|o3ˬ V%jtB8?d^eIBֿ B K0PdMRv Zk{A`Ēx B;5Q$9ij̞iRSqCѩ:H/F J,5~#Ɯ9ge B5o$=/f&Vv}{T׿j |j]uO#;|U]mLdPU;ib6qz_]1{S|l϶2#RBM:R9";fM gylθ7gϞ ,RScUCBŜ==NV IDATXaʺ$]ec晌>k29VUG8$=_kUHsySb*Sr+Vc1s&ReB[ 44B<+a)9QWLXIfS@XRˋ -gw?%0_)u.yH4!tS% X٭rq2No/? TIeTndm5ٍFΠ.6 ^ mo]d6T`b.k\%"T^ dc$}[2⣇#dҀa3+A@~+a {x>n70jp j(]nY,3W߁kP$=aJKwd`⤑cO&-JVڊhpe"CFI29M@O`2:H}NS}9JzaD?@2ә[[Qr4אw[\!sEJeL_n/HW\C3la>ʻ"8.^P2Q,_|ìƌһk{Pم87sH7Q6ǤZ5*hU[kϞq79zkڕJ{ sf۶zf(NꪽnǚMuGbAkI@_i4M=;7D51n׏>?Կ G?_]r]`݌iL1\-,.(T~nl5nBwHLO]N61XF5lϞ}'ثV J+Rc$7ʰ':۝0""+%Utd`Ou-pw)f2FM]1sj8O3_Hm×9g~2g2ʱ8P0p9W2-Rt{z\2C&K .z6`E]Jɏ;z{FUD5-(ls^,;D: ZJ*He2w^3G2'l-0+ 0FCFҪW7+祗sޟ3:kGfe!C4Jӆ =~0,LL%UXx#}lT0"Zr1WI@Bk]L(KH㚟`vљ_Q>G}_2,86SVA8A7 n֙X%烾t~jw׍:y]$#vDDӶ)XAL ۋ,\V^-/&OCw[C|8P2W%/$Ss J07ЭCJ;]c (Kεl[b-`xзQmΓۚV&?z;?-U^g0@aY0XΉ__uny9aڶVaƒRq:'B#NKhQ Y{;dt]V+Yeuk^|j*Q߼🷜6\Vañ1犞&ZH95VyP c5a'%\xnQF2[ !TۄSZ0r-2 4k`) o7elS\`;W=|< į"鎯ڸvE`sH 7.D'K5d;pzG:xVU-+srC={=Srh bE#7"\1 Udy ML kHgQi̔27k/3ػGY !]D!H"{Oz_M~zbr鴛}%\k/3{.䙢pM+7 zI#}lwGh5&)dIcaR%Akıcn_O+VΝ&ݞ:Hb%!+PRMh.X3o"Z]@Fr{mʊQŽ#3}?v-%4ؕ訴TZMu7,Ǝ㨚H4cX nu"2ZkgMk`o5rĶco>t2qmſ3FW]ʆ`)n8z0T}~Կo//_/~6!Dy[ًs oz^:vܽ>y ."]Öb}^R=]]ė" 5EDԻ !4d]֊]`lz0%hc J)\₮,EPx^R<7V}޷}JFj+tZvن?&st̹Ķ)U#z@8 $RlԟkbʏE6#ד603ĈEH6[!Lw9B#o3!e`ƜGYy:}Ή><ێcld#l#660:Lvf&u m?XS+b"pmk+~Vt- wUBW߱o{WQU4"(KA-ըe^w9Oջ-jòG4*@M(zWIƕ M^!=J'?_)NK*M jq(\ 1ڠQXy5驷R7y&MG !bcxՋq6dr iSE"WIJ8x} Wƒ^>;fmYKթ 2oY՞Őv+#|7pг8aAlٍQHC* q]7[$u I^km 君3G'zT3Ȃ}d`Nޔ} /urwljqkM Tetzi r%pPټ4!e[轥 R &WU9Y&%d]J}|y#{O~W_B?q)PEw/<c ?\ yj]>"*'e;2h Axסid EMgF'szczsvC*u{4l|O5>R] IDAT38 0ryԿO?_ KZᙛs^$$$ K8EZb%F<;Zk8֜[z1w?M-WY{an2ךu̒k՚%-Ĩ=S5U HTbj3WTY 4"u> ^أhѻ+y_UV>+IPNQKݶ]L\E!Js8&Zm* bEHDuk!sMؤz!KpdJ3@:lB6m&7ֿ+>;$!<8#lfseKt17EVIJ$9[jH&4EW;>G3;irFA_b˗G^?я+z ևZS]n^]%ý H֏@p>Tp%mN(uXh9ntnVv;è"eVԿ> 5tVy\uѶX-JQxmɨ7dc M)'9l,AHawz+?qp\K JJ֚,"+Fk4q -ǫehYU CbEGK|us ިVwz}ؔ7/q(a[ƓΒᮩ ;h;Ke ħ1pūITk4\-p##!a)Ɏ2sPc__Mbٟ˗/_z(c|Qx]Nބ|PT"BtTÕޛ :o~mпbtfk- 9q]ϩYUilOHeRy sTtmz'H^$r{K[m3E PNT5 U=/Q(/X--e"Z3zkH9jDjy3KE (q'vy0QPYWMZkEz=8@^48`be)R%J'eJLʛlOe[:HN_'`g<9ؑ4#a-YսN+%Q@Ͽxm;q `@P1:c ylgi Ih%S4~0b罉޵Ԁ<05)N}OPBDUB 2= .UE[-W.rf2`\[J]ᙖ@ͤvyH&ŌC4h=v6eGa< UfદeKg=ԊÓ2j۾%Hb6ʤfQ/ϱj9sTc|p LڡKaGc;js[)iHh j%//QuAWKD153sRI)R 4T ZBbeNW]*&޴IE~\[aK D]f۲."_<?#o޼_]Q\9HBľ mZA6! ay47$`\i 0\UDw+TVG^Ҕ0̑{;42hQF1n-8Lhp8&T[-$"I0fu 4 oD%gFz4 {Rn@[ǎoM]@)Hc-##N^vuձGڦN9L%[}J\W*:ps)r_zAd im1( ޴1&Ix*#XRۂ|zת~{4(`sgDw'|1??}{{TO+X9sx\0ɳoV('hzdnAs&8Cض_"D6)7T@esC ZTLl#Q{msu;8gN2i>|۪/'Ո\֓̚ȁ5X 7&\&T8e2?LoI*e\2 l M)ܢ]RUܢEX)eP54i6 sABgiMk9CH8po#rpP8L3C%I@kqǞ,Sl[b[@/a˲"xͪpd)%z|_s=EjR#V[yE~%S#? ȳfe1F-X ED| ,c 5^2p8/[k:1+e(0>?(xGף)KSZSٍ©C@6 g*5aZf*Nces)C?C>#:jWo"c {jWP0!\S'Kkx%ի©ĎP*S\ah &yrkٜg GfdՓt(Tn?\gHy8ۿ?iY<},oGFIXkYM>M!1N[O{?qaD7t*)9\TMD\˦gbh}i,_x)_GJ4€X؊W#sdpHOV;@/\' \tq,h^l#3% E3b>g\ZN_5xu(em[6Fd{޻gmSqٳ_ y"9 MY{'B0T8 & QA+qa#D%#S/ov5T֜MNa%rI)=lkiG=d=J~sNjHʹ!i0>IT"NcQj[v]4L<ȻH1* avV R-K2΅4D*V''Wo2͜jKb6(Wat@eM[2/uB8My=~XƄ sf9c I洜 %4M7\p@o.rn^ OmKA5/ej%/&bUcpFbwV 26)7Ms a۝l,Naxv"#?_eD< VpR ?G4(A %vgJ<uW]{˃D?h1)%OfEM,~Qe.ø14mںɂ!LH9 ɒ=[4 >U^dڂSS1D6k L)rKR]LMPm]滪24.ŞK /yUdΥs(Ned{$G6( 3䩎c5F(wU%$?L몺<00,7<ܦJŒ;z αjK@ć+?7vWWt1WS/cDom7tΞLa# B7+:MqH!W?S$57F;O!֌qzY* EJm>Y] ӆVѵ|Ȃ ] #f3.iR|si-ET)WNs.90M":pp 0nC#UTm\s.(f;~3}8@pPɬԼ {UT틧ys.&Y(aDto@J*P|*?O/8}H]M4.%ާ4?>gT(Pcs[k8v|U 3HG9Sc{/<'66ʋ_>G ] ŋ\1U :a\ڶ"aDџ}( ^#9CH)ԥGL30ߊsG+,zol8eRR0[h)t'XW82"$]RqzV[m%5;Wv'?UsaWW2i MeS\"a)q̐Z6vٻYoI@3X0ڤ"J) -4iy+=08D]S}hz!8-75O`˲ eiOwZ_r 2Oxo'^~ >%{?\qvfy%Jp?6 ljs,ýK|V1}C"}1P9~D=<ߛ׋/O_R#n!FXk~鿵Ji)} (z8vjIᶒ΀-Hl|O}l =&`ޑLNjlpX c?SRe,1J#'ndϩOOO^8^U ĥйxjܽ~GM^(??$AQZpֻ}i5UcU:5\lPx4ckg<Q 5`;@ SR6(|r1cl* VH%(*[KS׷珄:Ɲ t.P7>N^<6gdvbKsRHzr"E.#םe҈D**ptQ=Zc\%$Hc2d2לG&g @,6zI ;Pʽk ";L9~=aYզ;/(r"7K$*z!\,T7  v%,[ǴVJMܓ,F-0|p.90pFFwc톐O׏ =~}߽.%"͝#Ft-v̄ s!ȿ -9H@2ض'N{Dn)|xz_2ebJX )RBeiޒJQJlIW\rjN_ˊ$3TkQ\& uw<\TDD>[Y} Q-o ;Ĭl[ wkjn赥Vone%^ɺTج`H( O&hښ.$/L\歲cq!T;V}Yk 8_cC]kOpsΰPU!8RUI$y\ IDATnoIy{뀦ۿ}Կ{zP/_/Td5< Z׮{!(>!~%zsnp]-O/9*;A |N Mb4[\mfiSmLl2jjryL6ciB˗87.yO~:T{A_A0#63,о7 #lIɥZMe*.!t9c!' RI` ,ku(xnSVEČ!A^RX@@oHqTY3̗-tvb䭕AmDLz喝/0]9{eZ/b^ߗ,J1̍v66bM"VT( pXM+%en+~&}=4)[O^~֚ Gپ-%x7{YYpZ@K+[V7p}DL <+{ GƠ1I]rYrvBl  "*ۤ1\|*HhaLI%ԾC ir ̘_.A]Ra{ -jT5]Wkg@f5<3s ;t77/Ԥ@|B#NX=% ]UA{OǛ+Dº:깃mYZNup6JgfOdhKeLW ˒J)k+2`s }홮i +nDn\o}4hX*Zp[\#y+`XQ⇎>zrH6Ʊ|8C$^|Ƀ'ݍM(?Z8l-A-?D-:,[kqcN̳X3S I Qqpwbᄾsb0N"s.նO> hU] ҮHJ8t3Sʿz jף~8.z9\!O-DW(\YUv&iBV؆ -rpݷ S&T{;M*.8.H~g 3ɄS:WDĒ*r|Kt[LP`29Vf>YMDc(>*eB_rMWL&Nv-\ [ :FwPǶns g҈-P2 1S䅅\j6̹ N]ew 1#\I{"dv<=-G8?hD8u{>Y &^M"">1=v7o0džSĖq TO`@yH8k&,I `*Y4F޻:Nh-bw.b'Vff)'S30]E5)i~)ۭk|KBcֲB?kw / $6ޕ`$ߪzrVwX KnQBgu{ >ݩdv_jUW}=O/PofYі q7I1]B4u_WOjXh~]vE@Ne'ח"5K/9l}8I3{$Z[81c{cH%TGr"Vb0mb!(_4]0$Z\a^mZFSMN#̵ܖg ~,B{>ϐ?Rk(!,Sjڤnbuݓ;j Ͷ(```i֫Do|{: >*%Ulқ۪]Zڋn?1=k^xkIi(4VL"h0t"Zqh邗J2nCKjR9: EIÌ#mMvox@uéWaG>-k]mEhvy9|e^5>\Ю|x87ÿ'>R[ıytT皤@IZH~6cm mC/+[RB@_jd>@ל&fk<ϺBfJ_4s9n*mD9ݭ![L82o#CiAn).W]4[=3P&PY"OhG |0ΨqHFjVkd(#d{)|.H6Y\Ԗ,r ȋA%.ik=q$iz)_m"Mct\Uwleii_{_%q+ ;z jIbfa~;̠of,ȸդ=PIEnUw#/<[6%[[wB;¢mJ.*1kT0Vf}!A헿%3c9U)"9!~ "iAvI[؈~摍ڜ$@^.r<-"abLfsJ-Ӱ͢g+pJX: 0!57\9Z}]V}? I&2\<-7 DfZVrHyplA 8q ȤJ ;tX>轙J)~yf ֦8岛vgm\qpJ t˘5R$c("k&zG1#w9hyw U& y[ ~$?qN Y- +;ʺA?0ɮ_rp2ೈ]jEnQ2Ogu<}*E  3txR5Hf]{*&ޙL"]E,0?L%jK}ݢ&oMRv([kLPDÛS7)D3&~A2SosNngϭ,SD ' %L=4woXtUY&\1L/}^z/dfDž۔yY01m'dd{F9'V-'l?3M}sMg,PHd{$<"YQX^8s 7<Ȗ1` ']0_fF0Z^%`ɛruOS|q}6Wly_zre NO?6^Y~ @uqs/Z8@;ussəyb]FRx[7ԗ8!-aY YT:fKNb(b?;S>71'HYJyߜ3`K4d`Q|^|([jҕbZ*-Hdse,icnw3d zY+{ łԤ\\}!9n~1;3kLNGk:!3hIn׽1C Z 5jaU8dǥLLuE󐵈rsz5\Vx1LF\R׮l_,=ca9S!{"y`0v-777efNdK1?MP[Ď-9 dk6ZC9DMqs`0Ed6+ %N/l4x ;yu!c<nM,ߑzkAL&jYhD}1_ kÇۿEXpnz UkS X21ld(;h?nu2϶iv4ԍ8ũ4kƣL<a3# +aNm%ҶJ"+n1ӟ`Fky1+kι ?+e\Zyݦ9]kdƇrs0n)dXDXZd m"fb _6K!MDǸqm=F26DԸ+۸xZkXnn~8qPCi}ɔGi߷~[_ ?_"wR61uU{3T`H@Q iN0b뉮gD6%t$%9I,&G>=f_2 ˴c(WАCJg~Q[hKK[i<>j~g*|s P [% ҍѾ1࿵MT'\e`R9عFnCFWթ'S0Xb0{Uik͍oƥ: *]ŕ! i ;N u̗/_?sb=U0z*}Df5;S閲 L 9,Gu@- qÉKfDoq$g;dya}Y]Cf/-'ԸXSf@>]Fs"'ZMtbZs˸v磟mZrss#{NoLM1 -/.ޭ1vWjYX|>\؍ܩ16Cif VQmvfj/ͧ*PUmFq8!ICPg&M{HD|$!ٴpuj&@ êXΧWQ'ڕ<4L'GdwG"o4.N%$FM0В14=vd2sL}C{;yð܍729+9W zZ,D- bt=>1lP ~<>4Y1q۞Dez}t/^毪 χ_281d<!&_UTQ?Ȋ-+q d7<\j_f9DT-5ZG lFV똡=Ztfo2rIsED l`Met%o”/M @~G&&=VRF;P6aISq7v3,cIegGZm5 a bfԨ/lR 9͕Fw2,^O >zCbf57TWU)l򭏪2VZa$W9w8!ሇ0Pz|T?y IDATnyy7[bI T{ V :r3Bc04D6"szmCv`f-G7zf,ư^dݤ0C!r;zeOG3 f($n1ld.zqZcizEn=U%2tc>%~q%B,V=D33/%oZɍdxRaFFSԍ2;/%,lXW%Rgo0 Vnv3%`VA;].~sN| ȗNon~x_Ϫ0 ><}rDY%z%= sZN\PP ut-MJ*22s}HOJx0'kcNxlk8ĸ֢HX)%ۺg7^p]sV'{{zL=*ݖ&aaID"ˢ[O?{muhV`k朡r@O9yqxuu}؉ݞGyfk?~|s^lb}d8E֚s 7ݶv#`9}&ٞ?ݙ ~j (_:(2i bA8S+k3솆 OAb:(T=":!qn,ǎotLBS|Xk} PG$lC2BrzRaS;X |\wPQUsW8ИC.wN. VsWG.Eop$1z*^Raf6z||f? s>r]`119Wsk5/u=ќ+qE7Ӫy/⽮n=?V/U_u̩8 ;Ӭ𨒵 $w6*`,m>N;0z'QhY#-`>ZCy+ݩ(6r^MڸGc `E kK(x99G4Vw A r5=c曛 8pIP{f8oҠtǺ50hc,<"\>,L7b%qmƄ='lWh">pİFF1t(=3֕ș##Q`Ϛɞ-1(3m ˆ.4k1NNWQs-ZS߈ ~[^q<)/BϣzE_=/Ϫ2 kwxl9zb,|5O`GWa2$t )I$sN[9x37Fݶ;Y7]Cןǐ$|R}*}`usD4&|c1p"`}7k Hfwkk?5qq=to/=k\3,Oj>Uh=^7{n~ "-[{ڵclTto9N==1b䉈EWK͵VVE6.=f$<ݔzp|{Zo=R),<NSoo*Y,Z5|3 2ŕ-mR0!i ss+EXzCߘ윹mRb1"u۫G| 08 NGp<7v`k; J@ Hp4Pz脹lC+)=wf1ML֌鱨7ͽ71kN.٩>>|0x.D }e褙ӝM\gts!WR\h&-T-n68N$ RfKe毪 _o޼ꫯR/)%OÌan'+ O!:Ʌ<1OYhvN1>M¸AY4(5[^33B=:q5l)"7 \Z1G Ŝ%-HkNIj`wo囥_r\v&s^;hlT?w7G)N4@Kٹ}ps?<~}aA*[w<زPK؍òPF?w"ʶ ϋYCV%Ex˗DۯXmN8ךs\Zo`]DoADл/$G.Kk|$}@L][s_8ikp.%^u"5eThj"$1akv. uKSbn<9"˸p B,޻E&}פBTqSg ӹ:k[([N."q7xAĢ" CHLS[lW])7#-e-uj]k5[kn1o{LOϷ)eΙ7l> ZQ.ZRk"@ $HX q㡧fO*q2iDo;27976"έ "̜i;6qc2x}"zG:K籡 v yL kݒogÕ .K3#:>ܚYa#^];wb@AOa`~&FT}k5)IJ'GSحɯ\V}![OV5L7N.٨ a dٳU z"pW[;mjp:s^ˆr{dg;ѣ,HJcaԖkkhx1j~f}ɲ5Lڑ2 ֑AUDXv ʷY|n=@=/zY# 5WO`֌ѓRdoCUl0'N2Ŗ{zyγJd|*~ذ𞢠$ (@qi|gU ~Mv0 n/xBIMW‰,künG@-O. _d.YL LM `{D1za~%v wZiDD20[A$ԛ"ow`"/- fS~;N Ymmdkfobj N(bξ:X7p1 (y\aO@Y~֪&ry;=8!ЈF%?#.nBBǭ1<1wLO|6}m"f\Z0f'q<R&E}Y*$c1j6F"#rHQFRr=|ٳgo ٳq'N:jG ~z^d zm,ff~`+O̜*>4m \"&b$Y|lħFg]yX *@U)]Ȭ,VO(KUmL{xxj95&dQFNQ&Uo-WvQ-80!p5 PJho0CžG߳z>Cs6Ile6tx}FƸqn6~rlO]GkHndf"f ietŋwww^UX(J9ȧm,4OOf-"mZ,I}æɳw-S./LRnNR5sJ01,,HqXyo,:љ!V1E<mtmIܘhɓB@*A=u 7pifgFt"6~)2sCxb+x)qj TXlW$t4mqYvKE7=zťڏ`I3UWUXo߾%$UeK'HfVrGsXtI2pڼ 䔮ͲC&͚fK֙BG^Ѩl%_xP0S5Ť-c#OAt"“dV><Ï5LZ}dğI*uDy|n Xɬp E;` z7/MzNcmy7a_o?s.Z_}UVk!4M 9 o$|Goꖪc>-&Ļ<-wY#M|{,Wq(1=xl قr< Xx_(1v9Q1i- >nĚxA]PA0&!M,۬+M;cE0Zcb_[=l tq3f!M*`|)[0WOc{ ?ŋj ~vO+r23mܼ]fiF]'d9B` b'6s3P,bJ~7*KM\=V\\:X L]6"bMTFid̙c!Kvve].Id5bNyzXdth i-dQd\^JS8JJ;2*1_]_UA`Y߿۷oS'WAq<ӧ'*;,"6r.O4ޕMh"Ճsw wEӢ|-hhF[iEӏ[< +C>eXC`q!z:NaT [3]eˆ>xLtߐGfNloq<.u-=cMxz`#;;1|T"~>uwwN7X۠QL}fZ@P!vZn+σ&* Riir0ڐL _U ٛ`*fCMI}:D1 ho:?G穴l5B YH=9sf,5i5|E/7q]T6[,)͹tKDG"f[i$CL^xQsѷo9%1lVXs6  ˫sqbIDAT;-D)^u('AZ3o""l fq*jf^Ϝnd^fq~O`S4±,'FFDd*E ۴4î{06@Nt#c*!@[#-a'W_fsPn=tK5;yOLww_|q{[?/ZWav26 |eva)Qxԋi|ۇX86 39^5%) 9<(/㇯bgN7.f;1zHlS /* Û_8tRjycM">I;-LA\0QI{ q>*>tu*VîJ:4Μ!%pћ4rp|\$ Sچ>iFtfqBWHd ` 9&t7L(j`]dYi%pHw [?Պ<_ ~,aF0XI~1#48))lvb3ۉIсZ+?Y!bڌҦ xq}Y B?32QC\Şpd]H\gLM]?`C 09'+Fa|AWc u_&uMSDWWE}N7|SzU?PWᯡ_kXۯ]n8&1 ~tO;+9lA@C|ϙ&+Lގ&/lJeT n5* sX9DV$=Gpo{@xVg[*~$;*_޽ոY=oCu]ArG{iD߻~\_}֜ijX[CW읖 DFtjNJ!om"d|Y1qv4X3qT#5%=)о/WCsb)dN[[-|A6n^9y$qQTlNafr+/T{@4kոnƸΗED[kbL\"1ژDyH } ? ~sчX(} %sR|fg[>-;ͤ5! |%FE7{kJs-vV"YZS.q#y̶Bs Z[#f3iQ"6l=TWe;Lxqٹq ~i jnzp[7n4-ǏJ=g5!pP{1}8bg߿U_UU? }|.r8՗]# y;tJܑf&%0.ޓBB'j1$K;ؖ"ɇa s{le{$yRt"QyWN+. [ cȗ_l:LWqJO /R&`Ds!e7<~ ~Z@!qDd '6hpHμ%'CÎ(ȇ.}ۣWqe~˕QĭP=S!X~lgP/.ebd-i y BL#"k h5`!-lGDۦ"IdkM@Xk5eIB%S&Zۊ:\܀TTo_d}F#r{|S M<\R6*Y]4nǃ#"5\K\R{-2Z3|>Z%fsGmꈜ ǿA<ɾna2={T婫?84nJ7&. 7NhN h5US:c-/hKz7Oш`B&i'"m٥h1jLl\@|Z{-Wney\ku31C[ 8jр\^ThBy# #Y֠%15^s}l^,KUA` vz19-)]Y_܏ѯbww ǸY3pC  #"7p}nBk<|HkT@1qýkK"h8LOfR%wךzklT9{オ1fTq$XLb dj7itŋj ~@FdkiVrNcPlҰqjr{FOp7ݯ)גd#& qMa Q'SOps>Uף=L"#D aR7շ@k}6g!\KM$s=5BޯYzQ/UU?z7pTdeN\e(0$67#Z@q'N|s)kϜSHo1+2r?Y at&3ԣR%Bĭ%?WD!-cސCFYh6qvE y'k#P UQ˸bN(KUuUsч|m&9yOC?,?9ULhTElOIǮN)H͙,sN{djs矘]9ѶC[N ͹vdrxZ]}a@ $Ǐ,999oԳg+_UA`_=FgATZo~.fv~ bcx[V"J~1o8*} *A9fS'Glhj6"X#`8;6L"777c\̗ů O/LsNhxA}ի|*"z7ScvG$phAy3s#){m)Kk-Ǥ6gDď___y̩uvHu9J aF,Gm :,5ƭK=8q%i,wv|qI_|*z ot*y)t39(F(̆?uD#wՅRlmB5ՎՄ _ w=04#~ MLȣ)&8Icd*D{3?(3ݽx ;>| 0Z rҖНG#y+OBy7&) ))7ZL@{XBVbryAn >y9A'7|$b\[%\H*qHE&k=#5~/UUUI%)9ZM7vl͙B=s'8\N+,դX}aB b{ͧ.SZ*ܛ1dNWKCC*ay: i3ɳ/Ɵ!q|U _ Wm~&=b L 5G-Aڶ)ax!3M#z`-T{:|&"_ Omd-8y͜љ`*/{؝tDr!Oςwbw]}  |&):w;PuN+]#CC>ZLSy fDQZ ¹jsLMh|,Synɑ9aj#޵b³g).} ?  j5̗2N>֧bϏi4"Ӧޗ|ڸ7K7~{yRC/\Mx̤lUՁv)6NT|Qn  2`MUtAN+̵ի~VVU@_yWO=O)\c W4B! zKNc"l1&(?6GK)"I|1 &YXϳ5"jre ~vݻwKҒ_E%Ly:їYff =t%>z,c5n8py")&iTLNcDf~j .b"d[3WTyJ?N+j%{*S??y ]IsnLE|sx'̫ 6H1h/i0 )&jXᬆ6?1lٿAppweBs^:ք~/ Ϟ=_Ϫ?"**w_ywt(1TVmr޴mߖ֙R0qa}eHwmNmcs̑a Q?;Yث`ѯ~j _|vO{SGo|:,m,DN%_4" Zxك,fO=!8]Œ+6l]kېFmsݺ,'23Ba fr}eq^ ~@x,%~҉QU=~$+@lD۷{ks^2% rDAҧKie Xo}!D^097WWU*//P9>&mΥs_;JV<=FKW"A)3$Dz#/斘?B =$r $ϲ,E7|>ȽX^.U!_UUuU?zݻwQ/"OIk->$-*c #7{~Lm$YaH)1ZZ͍F)q>q.K 3^=_UUA`Ow&zʎ(X?wĐd{ŌWLm|&ɭGϝ[I3E&Fc3C؇^ Bcٳ7BC~*ݻwQ% 'ΩgK:cN?y[SX = *7Cs0a@2{~!D,>{D' Y_~b`JAzʼnќKIW@W5 ꈲ|sTі2¹;2^O[XQZ'|/ģ jpjOӓ\;{ "KUdҕvX1FV%m>j 6{r>/{pjz>1\5Tx"H)+Aޱ $cKbo _UUVU}o{ݻwD/S }c;dV%~W;z%pn=|UUUU~M; C'm l0QY~=~o|UUUU?˿ x43?zǞ~'_lpfX}㴵63|||xxxbbc-?=T|f4#5Ptl$WdZjtA73c8uKNoS׮.r[  W΂Xq~Q.U `hԠ'ΈE'+j }{/e_X?ՙy+V+6ơa^*kw-N, '4r ZZ34e0:|N04|@u@0zڎ7. 9ŋS\('{[ L^+ )#VPVGX~E `iJ&FG`5*RT] {zʖHGcNTd"Qc3.,ŵ?sߘ;cv-`oFҥ )Lh`xxP$PL476~&# @ʈ,IG S:jN:835eggۓO>CY)XuVSM1(Kin/ac!dz 4#rX)U2F*:nfV7;4==,^#\X39>>55XC]ܦ4-V[fr?995440>>hly;22/\_T۠MrRTI`juVKh3Z|8ɞQ |o}'\59996626:Q4H wx('+z%tcORɵZ  bh z !.d2.ɤҺƷz끐q𗏟GS/hеk*\.|~hO" z܎_ԩCWk4 179C̚k2NA|/fZmÙٯ~?/իh dghG rKJ)Fafh4ꡑy8y, $tz%+3.:ߗӉLe3ϛL9~V\\8>6V$g[6'#):$PT&cfd4f:17J?*+O7kٵspd{"y9L_owUR>_|iII!B*UBL\e2RȄduRL&@or>1,Z% $%')IO韜F07^X",a0 ; ӨB3󂂂;\b@ & =FmoWg՞$}Nބ38ERaMzzN:qN[a&< YB!`rL O<F+5 V9::t7e ekpPyImYwjDsbW53񱱚GnBnOTFB|\D<F.~iT(f{LBbP8`$;08: #-72%;ݵVj}D֔|1l)-u5q s=vawpvkё2H R3Ctx5r~xl kZłĈ1*BI4E* d͚ 0$4rqC@顦L燃#H:`*'uwW77.6m x' ) ˤ#~Q)ʶƵnH;O<4OƢ EA%tR ˦Y"1ʊc]a>UUc;l*MIddΛhD566\R_]qʅ,?\#\BBM LJ=rɩŇV¢sWCCgcEC\8%p3d2!x Ѽ;94uumݾ#.=B⺆ 9a@O&3B~EEyaa+;aQ1vi7P*B`Ecw3J,:|4d>=X]%r82Bid_TPx'M`F?4omcdaw|<8E0Hb`$B3xWY3,rvt!Py٥#C"NfdN;RT l f/`lPbVxY % wOI8.IgpR,"kR&׀/.+(~:DW2d\ XO8w_ь Q`O;9ҐiTWMMIKV앤pJ{jH:fB! J( i|h: Orv'UWW꫟ M,1 C!* rDB>)ob5,HU`c\KnRdœzit|W J[* _ $RFO~R3xT/F#=!,_OH}9[xopp;_^Zr/wهZUAx Ebն\ Q{/gx O~zwy}0 >a! VP?EqǮd3IBVcvK}}s5L6W#! t`Hj Zy ("%;8/stVVkcVW 0eغ% ήvq-L ՗}BCG `򼋁 C窪w'BbjjI L8Ԉ%ZE7üRqِ@%?h /‡trY\l}]a˼7$&NhH-Dec7O$ÃLs;tX09hn MNP+pAG4f fF>լt\?cmzj5NN{ /Ixw,𪯩;\Xwc a4HQ΋ޡmmg##kZxDε4J4HQXH ^HߑI&1jU:ƨy")y(8<@ c{= TmS{EecHDj $wK\ >??ή];mlBCYEy%ewUM౼{ U^ z2T⾕ -pR$yRY}sC#WRsq b9EFw,,ptux?+>*,pws^pH KMMdtVUfK=NO(],/!.%[LJZhQ||Znɳ! EPQX">Zr}}=eee%%E_,/~p vێgNGdaJbڦKK dLaDX|?u;]{[T6`8\:Tq hcߕG+N^|񅸸3l۾j׮`8/+$$8''`Rr'76566F!bJYTLrbʉ!Wm<Od,7l7c/7??MJ%#~RLpXEup˧WaHaY[ra rh?habIoc]V\IeDp<5x=jycmXy^-[2=$ GB6T,t"(/P[Y9)c+aC 245vܾmێq [,xN X-ĭk_T<_UwɗbusI;28 DO:f;")br ZOTGGD?[;g7{;!^WCCoXҡ0u֯mNŅHQ)H%$j2 05^vgʲ,UEo ,;H:+ـŲrH Y|)~G)J Pdb< </sNNg[aMd?Ȫ/FF~Hąu%XLkB$ѼTV)$@V_sNlz4$dN,C.K촱``'#FOO oEibRa5ԂםW˹J4 =3.y,LP^,~ B&.]֝-LEx=r ^?H0ny{z[8 S:iVֶnt4{`] LjFPIhq !mqMӇO5aX[U޻Xbo&p@\rml7XfVL##b'2)\L˜X1!)V eb8r*+plF2xljt(5bt].>ٖ\\gXn^2q@* Imee}w4;K-2/?W45y29 V X jbB 2,N}&8.`۰ lX>3IUrP Mh|gcT.(rnF\\,XED{zyZ9sCj|TTd~l"q['bNa7obR"5Ej} v IF@ JV&+.Y0m^'hM ѥ .\N$wqqA n5,f#Q[ԪoN+{R dǚh./+F}5R|a-|OL^zYZBYJġDbO^W(MX$0#0 C\\PЁM[6mʂO˺짭򢺂M?[f7T.t.+86\|)3Fj%&J&/O%í-9YKj;yyeXx.ŠltK4[blppW}yK榭ʝs|{>3hnD X A47&-̞聆zb vkU^㥫%SgEs +//;7/{yy`G:mpHbBӾ=}VJmvWIT,Gz%: GfQP"%mp04W8Pr@j7 p7 )a qV wYVܼL:#y}e2tVV&0DEEĆHYVV~!| Ma[m!3=BM8r59]9 ݹ g`% 5^vqddQ7BZ(`4d,ɜII=V9H蛚"疖?n /^/ڐ+&""kDYi>{ `IOuR{ЖaȜ(&hbn*<:>Cd8f9Az?I # 9x1Xx>zȇ볅F7""-;W MHïG XpHkbWn0t-CSXR#hJn/An8Hř 0a]Ra,v"0&22u÷([WLLT]]5uiFjrıvuwQG1:&"+)#s".)]".PUYR;d'X,d~ɲ0 FW򉎊Lneŕdc~D3hddG=ċcVg nr_"T҂$'l,ah!6 @}3Ljd3o0Ly3>sG^ &@0BamdHAz  }S%Lrg60-0eAG| J &4hZX(6> Fd %iB,8P[O$XV9xG]q|a9RXHgnAN I jDڿT9{2Tm]~>QkbWWnD +;9W9>k>8 tXZ'E&$OI{;n q=j9n/XgÃ<,l]4&a$ܭ,I|ؐV&;UH;<웰Ɂ'!.3˯ӯ÷ݝGX k供 CkOTx\ss4қ)jBM=R#5W76t-o0Sw6-|RYiIYiqIaA}m5x!}\ @PY_ΉA$揊 uA!I1DTV*zge 7tw2-W#imm-Sy8Fz_b_?Zi7h>:;Z,bwbk~|g%>E=1|]]GKK!{wO,O ̓*$v'${K͸fcXX&-8u=$ÍV888ؐ5x%&&!OĢV6}`Lww'\!e]s$MWL}+P~;{;;;[goo^z ׯ_iV䉍h'p6 $XQ ,lm&;{qdヒsX`VV>oij>{-ợ2%LJRTYhYH=;y뭷`B0CZGy#S퐱'{Ωl*))@ Z-I}A`wRtT\dT.VeAvwKHѓ#w$ohh_E4USSx}mv/ؼzç/ 2M0RxA w:/(s+<|P-]s"gzmVes4"NA i?b{O:6ׯ2}3Tׄn 7]J<#%}aa}~eNTp3+.K凤d|b5ѺÇ_z嗧K+.g+1%HKKsqq~?~߸tyZ,"C\Q]9Do ,{ǯ^¡[ϝ;+|$#7:zbIENDB`PKFC499-Pictures/10000000000001AF000001AFD32F629D.pngPNG  IHDRc V pHYs  tIME +֜|^ IDATxyYyMF H[cr>$$H#cƐi@x YzzIR]{կ~߻{'2 43sL]\H$G$I4L$Ii$I0I$a$IJ$II$) $IR&I4L$Ii$I0I$a$IJ$II$) $IR&I4L$Ii$I0I$a$IJ$II$) $IR&I4L$Ii$I0I$a$IJ$II$) $IR&I4L$Ii$I0I$a$IJ$II$) $IR&I4L$Ii$I0I$a$IJ$II$) $IR&I4L$Ii$I0I$a$IJ$II$) $IR&I4L$Ii$I0I$a$IJ$II$) $IR&I$) $IR&I4L$Ii$I0IN I^nno/}0IE}{ 9׽.=?Hz IQ?k Ebۼ׿!=46Lo//b0X2֑(Գ"R$a=ǟ|2 u==:{3&ժ чF^|^WJ$y zgŢ(<'$,p~h8潢),W7鹥4L?2Y_LAf5vpYC eSE5׿sW;+a$/P}Ax:=>F奕R?˕uSOwvǣx<ƸƷ\E(_W~sm}}[w_tK) $yAvG75}$9237m!Hd왛W_|GAE8Y?:gmնb=/p8VX6|V~gt6-fMӶMa:=!Df@fD}]mYƷ̂",X|۟1a^~lHiHkCSVWV!"r e[^3x2rōed*KEUUTm/o{ӛ$ C`ǶbD{eZҍ`` " mbJ$yx{⊲mZ9kJka^.nnޯL,/[k*=ޙxiG2-lzc^ˬ4ͥsx݈HrL,$̎hj%Zp}V*264mۥaf>/ǛN%ɋo~G91e)c͝SDͬ۾VϬjr|h}s>hN@$^7wQgϮ5/;&@Cri{1- jga8׏D!F[A+2 b1 v S&Igtsg;NiҤT/suZRJ+1ݸv(4XE/\uswo/p2/rG6J&c_rkm2 HS,qzP]v>s]5 9Z\潯j>/2Y/~J$I裏=6Zk͜˝*"m0 drʕ'~yٕ,5u=2t6VY%YA(z'StU)>H Z=g7dUhGdGDTZQaYgA2Ɯ%PiN!vw+8T(26*3 HS/ݵKDD@{@n >7汧 $yOqVZK6s=I+5҆6Tu3[,wnlo_qU;] t">ݛ ƃٕջ/l;#FEU\7ۛ[j# NY-)@#@` "R #z^e!"RHB뛦F~SWnsc3a| c>(1Z;猵Zk1JeUm{t|ڵ*m29JdYDޛ_X"R] 7 3/Nb1rXrB|K@zNyI}P(1@4*4Nws"yּsh4ɍUJ)BBBE i}Ӷe]Ve]U]k6SfI-awo+wQHZcyjE1pz8-9:߿zF@ 0a %<+  D6W(RWپwsLO 8:`E HI"جgTGGJh4' @c}"o۶ip4u] rqxsƹIؕ !9gruVkk"ED@x:;m<<¡1 G1H1hW;6SZDmD -f$-4 hH\ME, K09@d`誷 c0hcIip0.:wYGHh qUU Y^ZnCpM<߀(Li$df.sDhNYg5hMHJ"BReYNdz͝C<:p8l>,]0pm}뽶88܍n@s/}7]$&xc|Z+ `ֽzx4M[7Y?w񠗷Mx}}};NS&7cO\98>B"P+cF.sƢRZ cjڲ*{oZ )%"QXX9 ųk^gsZl__^W{z6 %(äP"(.óRþ6C(F9KY"Y#byg|WWW'K(cAbƇCBVuTU9^ZFMVV7oui$!OZe "aUI)M"1 aQeUͪjwowwodJX]L?9r`ͥ卵3E]y{_쎪ٸ;n0x٬b;LTl-z9xg`d,rd"`[a~ap4.,/Ii_uc6uӔu]UUUU]#ڪ2/oȞI&7#W.O3cR2DNU @)?/'G;ۻ{PX)m%3KD΍=fmy2F86{8?^Œhh.ޅ@v`o[.K+{_y7B@DD}"",({+O?m ҆6xV眵F@DX[+?^zfnjyyM}ݥ Y( c>姴hY6M[o&i$F1pݐ0L/o(b=類-VcցdL 8܁~Kq ж-(F:\]u~1{13ʢV?Scms:g"ThiynYCJrj67oֿ?ה0In_{bn#vFk5FSׁ6d=&smH)m &TZk묵EY-r^,vvw3m]XgE0{9pz][{~BRZ+e9ozI^HF'|r؀n^~PXV"&ȒvJ;+_y"X2.nl^փ0rh,b]?ֹ,˜˜qBռ9:'GOB9P0Tʌ^v蕯n@T({a1R`6{GOKzJD3BE 2/nlޱq~44iZ_MYeUWVŢm仿{s}L9In._~$EoդhDhj(gE9_̷zmz̉s:0Cl}-Eѓ| *N^y?zs/6dq3fҽ=~K" "rք8Wt>qkcյ 66Vo6MSEYẖmں.sgξp^YJ$m]Z ish=JTqY7Fe=+b<3kL3c(s[ Q#(dMr^LJfyyfht:sUԔKZ {}@"mQqN Wg>[TXkE 7/llޱ!Mx_WUQWU]<>!ૢm]^PQ0In\)"7<񂵖ք僾"zQgrxZ,<\)EXCcVzrf֗Rp8lo ",<2Jü7.3N8<8h{= !)2 |c_i}3i`g̅;.mniڶ,-imb(y2?O6In"B"VZ߶mΌ<9rl('%]Q!pmXctKDEEP`CMݔ!^?\ܹ<˲ ,($@Ec-"fG77/nn.M6mS7MQUEQ!LV1u1_ł$g 0 S&킄"Z)/^Aִ.s6ZC %f,,Q~ 5("(@Bnz.E@r烕3.;ڑ7-%Dc×|d>3jkՄDxfmsuӴfQ+XVUh[4y{) 9-sgQжA 3RJ`v|Ơ҉,Q8r>auQB "Q@AQz:w2Yb7ly("""ȕ'?.:g3V@(@66ί߱oe]E(KmCB7MS6?{/(Li$c_.$zGQrJb+6YS2"s >t pl}Kσ<+bn2sgmH* $a>Mc!vDHâXuE n}giuuVs3g[﫺*mCCi~or4L64d&D!4Mth ƈqD$x[C B!2w'Bb蔹x Gr|8=;>:ڗf1S\߹D+MkA@BQX"cn'Z j6 !g Y猳!?ccrݶmmVuYVUC8TJ!`hm=?o?/W0InӺ! olF!Z3@@bp1p gWV/m^x|׽=ٹq`7r:.6&SDt}(@  ANOey6M]g˺tyf#;_ܼpnmۆPuYEQ AZZuU?xc~QIr[<g3 XeuSUJw=/G{ZB$ҤBghr5M}}sw[tS۲d\/fZEX@-'|<ẚs;7hc V;7.ol;o!uUEQY/{}ч[_ l?J]~ޖ0Iu""imH+$DE1x(Ţ,Rɇ`#oZX$ GCP-!umJm^=9> I Xc2k3c00D.&.fqP0 w=oc!,ʲ,6VD*޴'b?w??bye) v5!ŮmWG Q IDAT*˺*n}6Wz`4Fh"”Ir[\yJT(m["DJ󪺪MGva߭HSb~p̺`4~o0 GDO BEZgQJqK;qmG??DfFqXkQ.ml\8!Tu],9 pm4MSW~=R*D/}2ANi$wTvZUx﫺kuuZ6Ah u1?^w?Gx`]NCmgJ5: rw Fxy%TƗ9 DD>8>nCHHK67.mn>teY/bQƺxHbw/N>_}yRw]4LcD"i9Īiʪj}[u4eRW,]ݡtp bXJa0{?gJmz"JVWl*rP(/NDd1hRu3M6M[բ,˲{}g8iJo-~C/ҷ0InO n C(bQeSMS%FEH@D4AFm],hoK# x0'ɸz6 !xhlKhkc,8NXQ]>sasư߫Q(ˢGyEm]*EJz~񾲔Iw'*$m!P5D모iY]f HJ%"Dp M9ǃѠ˲~/ C0<3ַM~|xTԕqqZ;km=vfmeY)Uumʲ.*8Y"h.w>?/Q)|GaJ$[mf0-O-wo"St N6A@P*l0Clb1ߺFl8MneYu1Dã@{B4c2疇3+k++s![﫦..kq0,ۦ.ʦi?s?sϢ"R?R&mѝNMˑuuR-ssG[c""O|ʕwwwYJ$ YF+^yڒ6uH!xiezpս<M]LӈD @@rQ^Ӿwyꙵ^/ ʲ}ٛ7nMR~ A]6Ddu4gMQ]j0/`v$zkh@M]L<"mRBݲ av2,f'`-eV&nM(N׈D מ}]/36di4'C۶jkW~ _<:<0X4*̧5";/"# )fk0I?qlAsKcqZk1ECPDDvyym캀,d:1BDۡH_OI!=w#&p4iBƍ[[;7e1n`E WS? @xR&dmy4M}QNVWX) 8ymkWΜ]o};V:Q֧ ]/.TH* Q A'Zuuc{;6sƘ<)D)D*|D|w^vEDAtELfI|W> c04 GKK(2+TjC۴އ wQicu|UGG|vFC{]'.$ :QHe^e n]stp JFkݝFA_*`// RZ[<"4il$_O^x?`} k[s 'Gy/ƃh8*j$$ADBEK "&z]=sfG&upP1sN$]ԕ+JkmLR%w[_g._vO@B DM ~INS&?O5(z(Z6Hqqt{yoiee2-MݶH.C"ґa<a&2 Ȫ(`6=9996dyNDZkuMMvA@$rQ8^~oO B""E*2kBf>]d!H3$I?/>siC['D9]䇰p~o0c\LV+ 3O>UDFce-h!(ʢꗿ2;k먐*E 5,]Ĵ޸lx$=?"B"Dڰ0k}]wqƍ46L̵?H܍ [5Է> i107e= ]j'IZǕ3kk"" lvtpLZeL):-F*cf;t~vPHȒY{ z*^2a>noޕ?rȮtΌDcw""UY{piI cVZp̹ݝ&h{{(3֊9=(H] ꙥQ]x_X 1YaDx{ߛ0IkQtQ4n][0 `ׇ11bt`dYh,dn_٩bHu9a6 t3G92ƛkO>F}n??EҪAzV 3w;'1FxK7 S&ɟS_o, \ vG"GZX ;_(]"@B\@BZIҺdiiww'qYFRxzfkcf C~߅K@`Qp|n}gO!Q!"ED$r|{Ҏ”I_fs\6|` xBHxzNGt1, "MOtY1ssGQib{|΀mJ)(jX<ĕ0""Qzg-DNj֧Ir}䡇{;6/yQ.Z|^L&ϳ{Zږ#Jwk"]kخ+!iزTbla"1;z'bD8nO)"1v1|e>Xm??sgYƆ9r|۷y۾0a}~ǓԪ*C3^d 9)b/N @I6vF(2@f CwO22LƣA.5vFD#]/߱QN"?u !9MYk톓Uo}[y}{_$~\| AiEA9k`ܒ@̱c @@3{+n |4ԈTu@,m ι.nrgwOi2PwהHWP͓s_~ضmUZcQk?w4XD10 !1x-0I}衝=kLO2 u11b6ͦMۄ:<9{.k!t7HW,4 :]vDTpr|GJ(`-靿x<# ݞ5!b!B>J"0?pZfFkU{$2R3Ii$ 7o~W~Qk2\A@vd/|V,uMheR !bb Z86e b̭+TTo0L&{!c YǝP$ADÏ_E63 =V”Iz/| #i6 hm2PNHD4)(-Y1?OjM;/)e~^>IpFᭃ+lo۶zxicm8~aK} 9v ! ħ"GD4XmHn]&cd__3Ii$ݽ݇yxww 1-xŒViG!H@!)c㥺Y5-f'mhEQ3)e *tzr>3"QK~'E՟.h]WQ8rD{o$uy^fV֚ @n/jǸ#&d ){#[Ɩ%2Rۖ-Y )Ɏ۽"d$.Qs{9* [ DPUK|{~1dq5XZk׮}_n~|ZiѨ|@${ Y$8k GиSy1>LF{ٶRXbD Pp8Xfe W$Y?y"/ ;1EE_i~>!,βϵk_|G_aM{7o1d  3}ZK"T9IȰX?5ssAbn瓾J\Zֳ#|F$BI^ăo` F- _eb|+4֘Kحg~~׾vx|LS$D!K"1!!Vxz@@1$ xU&F"acҐYZZY_iY̼s&œIJJA9)P|bxe3*"޹?ʟ1Q c=g-|^+p:/YH4IfgX̄̈ A$L"Z*J@" A%˩d*j, ٸL ѳDQ`LQxbS&!&teIf鱲Mw'q8 @b5b4󙽡ܹs VÚ,M޾3/h>"Kfj$SU12 @E+`LDk$啕\BQΥ+ @hؿx "L*B{XaN|ӿL$*A`>ƘRfU]~Z k5YwN'Sk1&:'iW'^u4,KZd=cAA:_:ECi %| ,/|h/Vwx=Y``0(fkBWe9$FXjeXJ?!D) DXkM5B\~矯_NgݎU@ޥr/ npZKl4hBD$|A+iK iD+W,ՊQAG=G Gb*mWza= ,">V_~\F/ח_A s\۰/hE0N bw88AQ&IhXf#3 ^%""hk-ATݽoTd%26_  ^zNlfEQy9Eg"(J, y1\^֭[97̚"?cvזW;kkt |fZ_\[!hdz% \U9 e{ѣv(gFgv8wi#K-[b@PEՐa އ{Nkj<ǍJ5@=)Գ5DfM!.)M *=Z kj"~ݻǀ!E1Oi/-]|$DVot;K-)DAk!VkK0 )'E;\^URciR}l#*)a7c A$6aԇLl!$ILl"@Un}/3kVÚlbmb$lXK,˲յ+Wnmm}LFb|Jf4McZ& )'i]L&ټRP@HaȪGMdYfiەw.\sRbMp!(w{j6>bFq|3Kk+)h! (^22|>w}+΅/R(:WL~;zsyFfIDb`oFsFgRaMd2y7e1+E9/,hTUUu:k+y|tZ8&өaInui=vƆJUZk9 !֛Ǘ:[4!D` j:Bt"}Xl}?O|9;/E-6e2QvO+w/(5އ \=ۛv$GѠ IDATwd:LѸ$rgs(f'ǽ^o6Ve$iN(zT}Y]y_$EF *6f#N$i,@%Z^xLH\n1qqy ڵk/<_Lj5Kh0dAlF9|oXټxqj[$ G|#F{i^^[[k2r"|2X7x6͚ ,r:Ą`, RT $˚+i>LEL!x A,3(Bق3'hu3>VWjXSLJ{UdT@Cmkl3kpXv}m}s<|{;;[[[vuVy>)f4MLlB%f:͍u €DH,KȲ^U٢1,QEl̊i16 > ۟CEYFB@X ݜՅjXS}̊Y HFh4d<IXn]LLd<NfgsceyXJ%K{j&fsqOy0+e%"&뼏dD)k-+l>$iX/~wSbĭ8{#g&uVÚNGGG(x_UլL&|6NOC;;v;mkjx<ΧpIe++ˮdzxttptX[YZ rUU3%hB;1HD^<:>n7[@T @dF"bC 4]Z^ͺl^O_PˬH"84;w{&%ᡈʹ,I>gɼw:އY1{[k6667777NgsBx<i$*l P٘*TD&e䤳FkFVޥiJċZH&&$@f3[jO SOxcDÝ^~__krtttpx4HQfF*ǣd:U$Ivk(t^Ιh"QV6F&"e利jeF"qYN$fD_zV29&Yd6kf"!bL!~6x_zZ 0Lq/7z꥗^Zf_ 7GGGGAy UAwy9_ZZj5[f;?MiYU[Nnv٠*+!%6ZUY*`Q9 HHPV`4G'_F$cE;{^}ޥfL@"d$k4ѕesϋx:鏆˿" g@Wӟ_|/޾u|VÚƒѣG!*UY97L̴"*l: c3y`ضZfd鬘gt"";;Ngb%&C$UM{b7^TED(h7l,IWW4@ؘ4͘9x?YN>)ggNb_:޼Y{sjXS*_UY|^d:fd}mc}c}}}#r5Np8GQL5~_U,4m !xt$!A*QUAoZ镋Σ1{cpgsn{48Q_(fa(#}_.J? x<&GFA>QB,'$YKVVWp z^H!Pmf#Zk "!""2QlE)"*Q*aVki2w[kJ $ PU5 3 47ُRX׆55ߑ}\uމ?Ay5ʹ@Ub|ZZXX___[[*P97&DDicyy9˲$M$M{jHDXTUQLskF#[][ʲ<:>:<:\][[Y^^Y^]n/3qY)Ik,#sLH(@dUYzPU P]FN~myc]Xq{s^c5H9?WᗂH{g]aM͇d2!"fR|8Yt2Fp0b4,mdF,<wFksn $ ZB%^v&$cm4?Nd$'Ng͵,<_i/ܻ|_ ZKa55}bQ ( liIp/5Zkk!q>{jɲ̢ftl8I8ɲ,6ISoZ{Dd|@f"ep<yՂh0h,nb8Nӯ~o@/hk5Rx|x||3cL6R"2S+k Y# 4]i n0{h4qSPXZj5FX2dXkR  r\0V-d "bU w1)7]1F3jBgy|]~w~g6 ͛?쳵jXSL&d2"cCXTjB,X@/0XkVŮ|5j5f#M-ACHl 89y 2&!D@c 2DĠ„p|as}=F KRt?[%DUpݼqOZLj51,ZeIUTw5lS%nqTBb6$Hb؟CPu1uDD01it.f3PeܣKNpآ(D>[%RyՏVÚݻycpn|!bUDBPgD.""PG"-id$dmBD CŌ   ^9VD&!eReutrHl^~\ }智gRaMw:O˲LӔ  BPP(Hz4^"xcx>'$O%߆1i p%DQYTp(H~3Y!aD tOWVVzo=%@U>|/|8GGG_4*+- !hDD5f+(#*8"z,-8Mf3@ c]$Gx@T|@Q 4LÃSdk>L-|8ݛNgJyyUi$IbzJ* @" bb[?Z kj>㣣ÃxEBL!g$\r}e а""(pQສi-iDDTᅚ)""3j=J  U^7PTcn=L-|o{=Mc >ҀDvϏ;yQf,7T5* @"!w~̪*!{!@U$Pf26#9Wog'޹|mHSaM͇M9R#"t>㽃̯`bs~Nyհۙf'l hLIfUPPG] A ʋ*bdbd@: AQIζLh1OL"h%*Ѫ|6|s>AΧpΧv"`7n>>7ZӵjXS󡜞($l,BTUʇGz0 'hlԢ\C4s=(VoHhghX[|aX(7Wgdp/\BI?Ai^x7Z kjOP &aW-!pωA+UPbh1+$ji6J(Ib".$;zsη+Xy19~TD{﫹ZsBI`g~_̦M׮=}Z k5y~|tPSd$b%D3#гp=Z V'a&D@lM,l6 +++N{>oE$Nw"|hb1\Yׯ?_Lj5v푡4M*[EBX!PQиw q@ ,-*gT\%u^6WTAYOvww[l6;>>؈?zлR$iwa5XNb<BQ1ܺ}OVÚ˃wQ8/lädT@AE*Pw"HDHh:D'$8 ^ CD$O4)9g===fι>%IJg !LY{Deo?}Z kjt>_ QE7b-maD5 "̢AՉs DbD1S[ow`%pxţ#}eY0RO DqOEoT-|{^g֘8^Ar6ꃈVF "WDC"@Q}:HAɪ,{''ӥ,˺xЙS?*HPUGl,bYM D,"@D <|:λDVUi\#)rkkk<NN4|7|:j>G7_Rn߮ ij5vOH@@&4 PEPcY|pla^`60(â+ l 211Ihc;F{o6عlvh4ʲ̲ʕ+kkkPu9犪ommzzFpe9LO}Ty6G5/&_ϳIN̄ ⼌x6`SN3 "u*b.B a4PU9 NPe"bkmfmBH&'EQy5$;|^$-a,}6հ惌W^yclzK0!IjXM 0+%B@ZhAT1xbW20Ӵl5| ~wzz#u땻Z kj>Ç$#q 0%  xQJ@jAYDRU1aADHUEUTPJq\1D`LbUMy9?66N`ii(LJݭ=E?;5d""O(j !13cD"U$u= 03A]ֲIeX zXY]=GDb;΅w~=\xГ.]0l\UHQ{Kp*o?}jXS1`f8Wf gX@Ȫ,NT!&fC6ɒ4#|$A=²A/",Kd:+,9nnnF~Bvy7YQvvGGk뫃:ᠷBUBE1''QKUQQDsNmHSSa{ʼn\]] y~$#R{6+O7.1V#/g8HPqΝjXoEfI& kPLbMBdU$EVAc *PDE"˄`jPy \`|?S+k$\ZjǓqy||^Z.%3---v?ffVLfn?kJH"To߾uV}QXSawwww*2DC)Y6" @PP2LhHթR@"@{/0n Vgf|lt64=<'$wrq|x*Wڣa>vO@]\nәNfI,<,l^tb oݺ}֭jX0<HB9!"$K UDbcDE@tL@U$ "/ L Sؘ %SU[on-ŋNx on>>莎ONJ7/_MkeYw;issnZO'*" /}驧_5N`4o~P!ƠAQRl)x)"UPQ~ z$ 0HFH^ A50ca&|w,76:n1ad\Y[ORNdϽu.eYdxa^|w ESׯj)kÚ3޹PEk-e )A2ԫPTK !ǷEC<)™P 2!@f HBh/5i`uJQ޻K;w{ɥce ;iy=Y~m͌dzͭ-DZ]]VP9 AOGq>ic׿G_~M5Aw}"BPl-U Ȁ*TH,z$ AI",֍a Ɍꥊoo~;{i ;m;1Kfswv=df" "e0_zE@G(O5 z_zncmBh4w(bX\4 3QC4 *TTÕ$}$@l1B JAP)%+Vs88PॕAokUf+ֺ1C!HUU|rptϾkGj)kÚs֛oA2 ((F$&2 :QU$FT DBBˈ 8FL DR +W_o?| [3vO/?v%kfr, d'Ɖ/|EaM]t{Ko&+13&@Hh ZK z ^ד%Hpĉx "e.H8ތn XU殚/5Z+Hpܟug | 1}(հ}& !16DHDQIT@BRX)T@BP"]34ĀƠEXش"3'N1&(vݓ?KH$;[+G [?誊x≷zK$4${\Ytw?-fݥB-5֜uQak b!#xnE Hj) JTAP(* YDX" @RBK.1fYceufYFh\ ">Wz8vUV?8"H}?:_?ЪuϤ uo^/^ԋz`H{(ˮsM1gd<+5SȆjVj蚠ZX6m 3cMwa`Ii ۲m%R9(9"czpH{|Uy%##Xg^t<@ ^L5GAnt2.G"D4$|r{psW !QEy4p'N=ySUQ3ݻgr۽{'piiф1fΝȬ{?ޙ ~g~.9RTy6,\?uz N 0S"@@FXODRhAI3ѩW!4Ȣ y^DY@2!R1 1(֐UYmuyo\ZٹsKW]k 6491ښn4Wmێk I"`fĢ:6ݯ}O|Ϭ1E6#GʞIVV ZAfcE!Ab$#Dy).!S\P6H8PDk c= X"@Ty]$Im]YYY][;}vDԨO4ldEykLV;UHr\s ? V>;(UҰc'_ØM xQ$ 12(1ⱊU hFUr*^6Ȫ0""R@Pf+.,,ܖko,-0^pi۶mZFQ{T8WֈTK$KV_K*iX;+֚kAr{ իQ $ެ[62:@3((! bD @} Ġ}@Y AE@BMlH!~.^gFE:Fc"(!0bPɲ{DT;=Ea^yWn*)xXF@D,IBe6$H (^ 2 wZWMIU ~":<_^^Vv{yiȃo>hLn߶*cfUH{G"Fn/xu7~*o|(,UҰ|󥗾}%D$$qŃ9AB "D8b&Q" CaErEXAСo0"T=YFx=RCeYvĉkMRs玵յNsٹJ\ q0xAHp8j۟}ӟ1Ba3)UҰ\_\|ر++W2 /MQ9(dHLHX "¼CEp^ *!lY{Lkz1^z⥋[fn.ݜ#3AXo 3QYt/3(z[yQXa)x<'Sa%BbDP^GR"`6lIA0xTP'taU*0@@Hŋ*'*4pB$Դa`K3ׯ+W.l: Ȱ%@}."yt__̒IUK*iX g맟}&vZ2gDA(+rfF"I%= 2j#4@@Vp Egx]q[榧 QNMN4[Z{w;vol&;]I2zז~?b`m)?`yQX3gN?7|qd֚ {acz:1a$A^AU;Q b@(K0dXD"  ^}^1PbK c@/~ĉm 3ӃpٜjW_onfg3-s+KK=}^ՋH {n=oo"MG}||J4_x_({z~ceuqU 5o7Wn'&g(*{5D LW$F! +f. "Vxp@F9MGNVVn.oAfaV*Ucml` ] ݍ?;ұ7y'Ǡ-K _ʄk0@WN?BUlQ3* 1YJQ" C@UacLP1hhSbdA3U5  +++`߾drsu]& IgλAP(Oj8Hr~lnk緽ѷcP<ֺt Tm4;1e0`Anߓ хm0;7\aobcz65 ÀQ@@-U"DQhVALMNF b` vۭg/"`ӝ2u«Ǐ6۶oonlll[؄^v"x*>|Fll̻! J*iXjH}y_n 1;F(!ĨO W48j&\omljjK$9 '^DU5AT YbXke0ȠZBDDryLF]{wHsT?rטh5U6P4S)zO E|N_ZY}"(,UҰD ϾSؽ[EqdÐ)MUbFH4;3ΟaHAaAdpzT}b* B"h^<89wBy^d@᙭PEIHd4LF$ ¨Ri;QLNM]rMMOoW\][]7D)zPOD*"7:N̅K@c 9rG^TIZ_ _9dXپekTXk\t6Bb`>;vsys|Q1) o/-OLLĕċ˝(jXAK(>$  Ka<;7Yje@` !kc٘.ߵoW*!(X>Fôi>he#G<[> JW_=at"s8 .*JT*BlRS3{NA!lhhبԪ0C 6^rEa ^dcc嫌{v¶02;\[GP>1=;֚رgWOP JOU%KFAK·mo{9\]m 67=bUɲԉϲ,&PVJL؋c6 uMPٲ1ΧQpX#qhD6a6 *89xul8| sGZ#OQa`zcbvfF;w " fƀx@,~o_p@44}GK*ix{W׮|/_zVl X >]2J~EqEq:Ndb"$bōoT>0j.]QQ$W[&g;;" yTتsjUQ:j[]DT-^ohm_m!B"PWd0+׮e'(LJ4 __01=՘ 0UuY G$ èZÆfX#F#]1"!jc4m޼nnh@jk<ð\W,2:ZkL8>ˇIP̭Z+/N4SSZB^<1]|^rw*>O\: Ϋ23">񎷗(,UGXq$DĐ٨VX r}_Y_[@AعTրs~ ;lTlNNY&DB"Yw!C""*B;ګՉ(2^[ٲc/b4a@Fh42?wҵ4IȚ^V*4jDڰp&'^$L|y?ꪪ"B'?d0VT~ ;TǟKiB@R3/< JXS,ωzDb6hH\^7(őPC!ܹ\da>ްOqucyIUPRm bgs¨Zm`ժ:/KׯLs߽ոl-ۀ02 WWVD*c;JՏ<׮ T PQɞ{b$" baPˋ~z'vh^DāJ\Uau\@{4mGN28n-/APWIQ|?"A`8kSJ%c)1 g92!z-,Z.MN:D(idlWB,6V9y^ sҨUgáD&她8 J5N+ŕZw[Vwp3"$|,I$InfuO~ɇz|JgT_|/HGd?s?ұ+"plK7 x(7V.SPyqUDŽE;o*x4ٶ`[tTE"q?8 IDAT@kҼVlRܳo!BdE2%h0{3|Gy˓O=UTy6MuWͥkY]^uR$q1Gq3Ppc(nU@B/4w™T h}zRQQŕJ&kͭ۶6 @g(x@Uh0JFtm"*>w?P.DO6o.:9vZ>#*WQ`b >rQV¶mw0,7Vz)U7dQP(Rp|9H0D$DěĢy+D@tn~ըIRO۝JFVUcыkL'̆ y;˽˓p4Jnu4gT@/]<ګQL;?s&5u!s:GnރU <¶W"Q10Y$U[u1M |`U5Q/_9{EO[S5\­AF"2Ga#ny2PG>A="}hyQXmO<0ºsysl5ϟ?e-;x$MΉׂk T357+ SEQU(Њ H@OEM|{1Z\?Q߀{7hmD5ԀV@%w.\}OXo~;K*ix?3g_n7Y6zw?==A "k'ϝ?7e6SY{]y $5X&")D"& bX(?DiQ|QX]زcDcrzaG\Z5˅u|hQd4un>mo~>}|J4=K΋x >wNkc];c'Ye3ϝ!]I,K\xQd^U 6 j" [ũPE E=}"R*((xJHpp۬:w jE^TY&QlcwD>NDfE@UNv@!Vo_;cmན.~37:]{gJ]s9'N\Ea"@(vZ8 W`ͭZ"@ɯeme1&lTݱ{Vf6rD}N룿{f~/QX__]\\"UP !" # w:4(E͝]lO>q-lݵW4K̉NWOJ<.j8aQY@HύyX8!!2Q_ BcҊ?pLUkn7χH?'E }왔*ix[ p>jL6 efD/4kw۽^/Fvyp4z3'N8}Turf~׾RM|;睪" #0|aO(q2Xp5"rч!.I6_Lw &l}jR QFyry˞Inܸz¥WN8}}>YjLEahf}ˀ .ovVT{wûw ]=/<~jܮ=siIsS3*LcԪu fEǧ67ѢR 7Xœ/~ ݓanwqO%D=P3)U6N5[qY;}+';,*L`k .\`QTQow^GT(P/yo>}biymzŽw,ρXyDC e|#P ee_-23Z5L\4gafε6v8D|"y{,/ K4uNr*'pdDA\nF#񓯝ZY[ϲZ33==Ә%8PBEY= 64 ;p^~ yur\ Gh.U6W.?Dd8 QA|;R@\/]~ԉ+׮FLMOOM#[,ta70AQc{:0?o #9MZ$OKDayQQ mNX~!bF6DUc<5;Q1 ɧz(,U6ԫw[Gb5"ZT2{Ec0ύw >,?{kz~lTYkEE$\i:\;!M z (ȌMAT m@:-"qG >(Pu>0V\:ҵ!^;wjz/{&U6#rꗿeNՉxU!<,@Tmf=wDQ; Vs #)30ZDhzkdw[cPHFs ;|sIUUw g_"@"-@Q)AUјtgSOܹ|0J4ɿ|7ZV%2"rF%fGE2VYTw,IF("bcI *2$M46عm{I>s/b!2zkhhTMTAc}g `Ջ w}w>P>Jv7͍N\qrQR!9EÞ 2# Àwv=w͊`8tͦ8$E%/'  l47=wQXtN;57NA!6 H@Pa.>S U)'E$&V?3R`?w?^晔* o7]xw?"^TEdifY>O'''sq`@ xOLFՓ* 0bJxGI_t.eSSS3q" x$|%Q?:hQ i~_9!f5&Zղe@CZ2V԰HSCq@5 {p8XoI" qo~PgL2SQAM'Wؼ@>(@2MW.*ɿ J*ix׾#*Rql46 9 QK_ַt1mMć!2"\LčWLS]oZ`*:DHvi֪YpVY"=1qx"*ʙP* N$" ɣ,{&J^zīڪS%?)v4;`"( 2~WĄOY2IFޣ8DQPQ4b(0 l`L@{wmWKW{"{4^=ƘF~k\5Zoa|q<6hc(2SsBX $qfET$cLLd3ϟ<{F-lGD20mTDu֚`phNd%{#Q<|N`KT^P C͟x@痮x䑇]LtC _~$2KPwرs0^_8LHhQUb;X5 L.._/~L2B0MEHA2_+zt׫UІ>"L촳QgF@@s[w\6o7Ͳ[A "^X<[~O}}{x>/C /_k^I `ؘɉÇu]Y]r/8 Pq.aaCחoF^#eBp7X^YIs7QoUfdZc8tܱ]dٜ'݊*|DwN}(Gᱯ}cڵ^{ɟܹk[??-{&JF4l6gϞ]^^FDp^՛5Z޹ =@D7'"0^@PT* a`cW~+ <7=6wމP{PW'_kFP 6\d AfY~ꕹ;/PP)bx}..UEtw6'qx?g?ݏqT נTۅΟ?vSU*3*0I„FH`J"* @Ԡ UkLAdrhZWID4w*RWյuzPh4KyXgv&ߺ{ݭ0v,**@~wY^9wO[ZUZ :| J-h''񛿶YvZa8LLWYUEP8 ز۶oLMN5[͕H*[F&,2J e AcACAx(\2*5%^ai" N{zj S1 lZ}aQWק/x(E^WU/we|3;T'~?pZgDw3S3Z|J^?zN 0:7tdNˈl1L=;v7x'Ć-\PUPUPa͍{KA么%kP87=smZ "J03I 0j%޾UDj_>3'(bZԋzQѰAk -}ޫy$ھw?wO~ǝ\~A_T0T9y^yBF#HZUaSV3WZ׾rrbc² ,Es\~s{_9  i4梩S޴ /}>l<*}Xs` ,F~ԘeY96֝) zzoumm/vF!֕Ee3 eQjFˇ?xpt:jCf!ˈhbb?~򓥕'3 fFIC.k}1n/F٧?}Ϋwvƻj̹3cQ: z}S;O=7ig%J>.Lm4#IDATdILHI8DٱcǕW^9999 Z!5@͘Ո;`80R5 2ɂ&;e)|yfB!jxNUUΫwvBTcL)%_xgáe"'bffAE$ZJI7kj?ݹsz~<gc#G?Oך9畕(L2\$ ٦dB02_57Bu_o*1qǟ}٧RPᗾ%׎3VE$fC~|xǏF$6UiQUՐI&&XJdY2b2K>ˢ(;AfmJL!+zr%;.2"Ɉ%El)%MU5/~Xg'}G@+++/k6uYj0cy<cC ݃fRRS#eߞD,֨Ѓ8#h0B9SM)iҦ,k܃1Ɣ S~ڙb59>"l6>DrxQ?yYZZ^fbLv~S`'{ղ,Cq󍱰mCm'Os+~\sT{?6|hf桇t~ψ=4Qo4a2Vc&6c"b?h ͔ňbI //zX}f~K&Bfd,y +Nge11>^r+ɆK&bRLyٳ'~WOV\͙h6Ԭ f|+.g?G <Di-4_Sk.+Sgyۉ8-H$$jԒqn;1>1ĩ1 ¼}۶gYuNՠdFL!%TuX`PWa/]8}'O=sI(ldzi&j5~{:Lx_oҋp_MdFܜl!JMdbfӋJ5q43MfLɸ(`P)tr;3e!IFw̙<+ʲj|X5Y`>}#* Ucz⫯O}g,Y|#r/vڵ[n?fft)5on 5ٻ5G~qb"4p0 uULF,IS26_ɤf>UUds+ g/)s&N?zFQv,#35 ,"t󢓕?{ٳ7X׃Aoxnuunn'^> y[Uч}tJO_W]n:z(l*l[K8~w6f{߇۲3߅HX$ġIcTM~HDL`)-g|]WH$K˯qxwB̌T$ 1-=r|?E/9s!~nf&]7ݯS!}˿|]U3>_fˤ4{^M=_Ӎ: eU5zKkk!NY6fjzӮP3yg>#SҘbRu$#RSU6#3V2UM)jL\.ᣮm5ީ#ShfIU5%#uYj5:1#$xlWϭźc?zk~zjv/(<ĆM7y ;aQR21ݔL`Ll>w%  L|}SeYVzs۷m떥d)%4VUӰ? 09Tuݎ3 ȚVfx!y$(\o.=@bjїH)J}צD5&jMFӿ(YQS&2(Qj)Uu {^vdF/geѣGqPl*y߿ϳἠ(ͬ⭁^k ߦvpED̀_oDfcɌ3)i)έ͟^vdR1TjcwX ndDĈn  ߌo{a~̲Ɉԗ4Ĥ_bjP-+*15LQ*bf/d[9HcUU~uuuܼ&ͲLB&Y}Í7<|a<oƽ߼Tُ6>eKHDS`ll$Q#KH,W݌Hym9Krg{Ɯqd޻cfFE/27?_񪎽|Yհ22R[p|oMSRROx=3 !z+AL۳B%%3fjp0j_&RƁ`SEulf#g,yy')BˢRzrJZU gY̦ G 뮻.˲㫅I켅 U'"Y}bf&D> ^D3nK>FgO}ב͈nQE/=Bnumb.DKK+skkkuဈ,LufȮ]pwDSȼ5ț˞D %>&$ eOmDt`NQ1N2{Kj͗%#3򲲙jRDy+Vu]Qg }B`]wuwED6Z7wNQ%H|(QCFƤԌIF|hlօdf^K1sjz~57UUŘ,ƨI^9&B,L| f& ISSUpDeu1,dYP![Ï?x))5ލY(hTVvy䡽OTˡچwR4RlJ,$QԖG6qjDS1ź4x,cJ1F A9rM8(H,˲\O/ZbB9I2%"J͘Ȓ4gڴn[OJ c1ձu=\E1l޽LC 6|kٳرc5Ifcc'(0y]j J2M[mUifhG|:yᠫ076Rs8N4:FUݻw/pر#<ˋȋ\< "$B37x5[sIԏt4v P/ 5z@biφdz,TU]6 6|LNM`QyYH0L9lan7rsԯ陪_) -8155'n­?.n;;,:ƺovdǞeDfvr2x-bvm%:7RZ[i jj٨![)QWN裏b ߁dy߾}fBȋ³aoe !vCYۋzD^m&1 ֪5դiT| nagcݮ]Ć*| ~ jon]͡rI(vh!m`{TLs7 tL@lr󾛏=~B ےЈp3(Q哅qF+6R1n%yzBĆET5Q6!J~n&,ֆ%utNY4II.fq ,, wl.;v}7:6?::utdLgr+JfkG3Џ*nM\Ȩ(n@`SSfwYh3?295kȱ٤ܺ0i+NQ;~lxFwY7_­P!ȔٳߚdJm,|ʡ`ps,ŃĶՃOm~33G,|ֵ'b$|ؘ wx}2<ٱsz&''/fhͶ{A[ u_o N Gߎ S%{x $v^G@|LE${Q=L/pF ol*["޵{l/:t ̸}1ݻx^D4Nu ǖڽ{w@:t[ܵ0EO}ݻ<3A`w:8;_޽{O `w" ha1Aۆo:FF xwIGH8 }gíNt <xl!l!l!l!l!l!l!l!l!l!l!l!l!l!l!l!`C 6`C 6`C -^)dǍRIENDB`PKF(``-Pictures/10000000000002360000023540E72DF7.pngPNG  IHDR65J@' pHYs  tIME +r IDATxٲlu6wS@DR2"ZH @ $ZO)Ȕ,~ T*%M[Wꞓl0 vX 4k Pq9y\c9~ ,,,,,,ZXXXXXX(jaaaaaaQ¢EQ E-,,,,,,ZXXXXX(jaaaaaaQ¢EQ E-,,,,,ZXXXXXX(jaaaaaQ¢EQ E-,,,,,ZXXXXXX(jaaaaaaQ¢EQ .u ~;o<{._ݷΨ=320wwgjTu~hU$wwa6zPQeU%USYDYYU"03 -̬ʌ03E%=YDYt{ 3}zE-,ҿAGU$DEDLU?>tUWe!'_Do5ޅYY4*ANĔYU$j",\D,ĭֶm߷)<ѫ޻?_O|S]Xw}_O}=4UKy8* *b"bba&bt*l 33 *&\d@Qe100RpY `85SSU5mkUDp }>я{aQ3WA"Y{LĪjfC`B}5z~T fA/+*2DRbafUQ[‰YYEya )h0}"2# I}#/ϭ7¢:|w^{x*jp&̬qrdޜNioֈ8+#+G}DDw<O"(MTs4UX(+TRQaQ5aOL":Ԣ`H75k5C3=_LJ?&G> ~ݯx0Zd{UErܟѻ<[vLZ5GsQ) !&SU!*VefE#hLb O`)ߋJ骦*%&_|luE-,Ewï*>|hǡ_WE9""rw>]DO=Զœj&"YUhw̼{QMMGCLEL;(bMM #dRfmdy@%1 >*"ʨȈLai ZSe/r}ZXg}k_[oFDDdDD q:2r9.K}j |LUE%Oޘ?'"U0*BZkayQJBDچm4d $!*fvᢪʪ@0<2+rLkfMZӦVDQ-,ZX_w3 8zeBN@TQYD\Ε%*4"*|A" R9Ĥ<󴩉 _lDUTDd[fMq&k$k7cfy"T.X AUTTߜQ* )33}Ї?N k_{ѣQE"RUr>G IFĕO2WD| km6k&""|ͩ2R83waU=tJp qͩRqQKNh׉^ %*2s4Q Sp ()=DTYSa~pSD G?ڇ׻kaQ 3_ݱr\.ET%s"fZSUkv"jZfUxD0Za3U#2QdB ̳fUywwWTT`<UQJ*"%@N(j4Y(32QJ53UBUdExx+C$/<[4Y(2CDC>f[XL_ymrG?{}(+[kfZEz%r\wi[km"nfDkMMLE2!K(f~~T Kb7?"ifb+W10gEe|s&%D{(kbbڬٶ5SzI (MN!h"]DE>]oEQ z-!*|p'bbE}1Q13S-/|w۶mD15YE۾A-jS*?1}y#xww_Uܬn1+̏H̊S4ႭD#߶MULE (!b" \UE*3 SQV權~~>_ZƅEQ DD믿G̤ 'w\.c TIԪcdq~W:Tm} 3VR)&7"N7[kclDe/Y"0-zǏͿɪmk۶mۦl"CMC1~%ʪ!I45k֌ RvrITfYUimpj\"Ggf&*˿+_\XLok=zн3U}=#so2z9:J1nɌ~m'i*DFL`f&!i}߷}6&Ry","fDoUeݿ{,*ٶm, :Ǥ萞بC2m&CNS:HTȖ`F;jb,¦Yࡒry㪂EUe"#?^oׅEQ ?x+v1G)~`{$33<{x@>k͢Ǧ͈ufYNf\p*HȉE}kfO?tfB7h"$ YkLfMTQs1|ݻ(jǖ^yz XxD/qLc4RERǼH `̈*M8QkmwVS3!"YkmS@W`sU5QA(J*?F fMլ0| e"b妬BDWsYըFĜ \EUYE"D_d)q p(G$?Oz3/,Za&"z嗿oqC#yޏ8XzeD$""C᮵v7%ڱ[&DBv{{N* rZ3!I@9 Xػ=63&fZ+=IΡaq*34"c,S ֢40AU)UYALZw!Ί~t'V\&tb_u"_z{/,ZfW_yG8ѻN)bVQU(2Qr0*4(<W kM۶[kWITS(i[}tsRUSV{433$0U1CFqNMU1QDA,"ê:% a]: Q]1KkULc-#2LDTUyMfHϨ̈~d'?_"EQ ?B_zQI{?"jߦ**CH-'mރDw4"EU%<`#CXk5Umf|]&`m۞),ѺUm-ĪbUTLحmVH4LiaZ,E(qU kf,Db="꾚mԬ535Ht*T ݽ> 5BCDdJJp*9Y#rTo~baQ_$/·MGяp4ATL8SI)nR)Tf2 K81skMD.ʕE̊.h1QFg&qуt("z bA|# C" !F[.‰@-Ƣޱ`*bf[ oy&lH͘HT )ߊOOno͔hl6!d53Ԑ[PI'ûQeK[&zxg!aWr\N2)FbPE1cƙ@5Hk7mf"u\~\ΗW̪l͈#DD>C>eT /,ZX3G_֏wwQH:gU`ǩ"}+DU* #!5Ҳ @g"j{ĸO hCED̲N"\DL"*&a5JU1iy߳ު Tm۶03p&LC[Atux&}>H#2EGx{s+:ˌ\OcUxV8z?/"awȇGf4 [ {|wǑ͍ۛ i(f>d^%۾m֠cp&Ȣg>OUߟDYŸ-^|/3tb"Dz'n`dTE G,"Ų˘5ׄ +43TԽWWFV|Wl[dJ,f`#QA=OmN_%7v,i]7m*Q*0mTuˍPZUF,9ԻqSO]_Y+wGZAbFcVU)Sf_.Ը,(˜9"T1Ĭ,KZN'QIլ,-wdo,EQ jÇ_o@/w\P-#I9sTZ {#"&*۶%3D,U>3c/a'ñi%+j 7N;U~4TokwDL兯-%eLZۯUEՏ.N/85b#FRx]DŔ堃 م03KRqR" *`2ڢ%J$mO?lk;3oنۅ9e cDݏ~xOm2D8^}5}5c_??#b23!" 18"Ԅ17XUD|u([k,ZD:T RUQЄ W|w8Gj嘥;x;#)TECG5$s,̞}Y&1{뾟D93&Fi~-e<.۾a׬QWۚV&4/*bDXH=p8#9;qF)ӫ>WjFFxeJezDo."<.gO-ZXW3B H(43(#kȽ,5bsNSp2@4!`e*{ȡkS%fd&6EvsAa6UbL`b3{g$&_v:T* ^kͬm$6 LL:uEDP[ O5];UfJ"SQS9Op&~=,1z=sSU)Ĉ6f8"BêD$"|:rhRGޏ?q}E-,O_/ѿטi4S:bš~[*J*"G&MTSEu"% yjD݇ n`=kCbITmŕQĵvs{SUp(!Td@c LǬ7uj#hkؼ CUgI4>454pYk[בH㍊"bfsakwサ{d!fF`)֘* F"%_w 4H"bw3powDws_{YŸo|>-TɞYc UY#aG!Ɩy.gRP~H?cbȡ(؞2 fC5TY*{k?6;=6̍Q!,"=\~{slA3k}IVBTEQAweUP:Xa88Pȉ"&m0+P<:HTfn`;R*5tstQ?]23UZS ,G ώ.Bjƀ =) IR2'Xh_z)~>zw/$Ɛp[8 YwP40B36TlYVJI5$]W~ GB3f+jB̿0[)ʹZ"*K0Aʶ5L\ ~FDCQ\E\cDTL_Ql`S3dMM FxFQEFľ3*J<*ʱ $"xע1Vgq_BRDRZ;1i L+a>7'Q]; "iU[EPH'7W^~>ۿ>ѷG~_ַ5&Qx8z ތgf_4ZGA\)HT͘xHB|q`O޽"~70.ZXxᅯ|_UKSiL~EP]"zix[,"*9FعL5VX' IDATYb^d.qG^cBAձ)ʌJO|T UeTB<1ӧgS?=VFhcb4Hz,KgUDYd$̬:B!֙28/8{xI/z"4k(( TϱPt)*۾O+*488*Y}[?nXW7!I[bhL0ddD5<6,+k2dďߜN., VCJU2R1ig2kF\JR9րz?ŕT2  9Ɋk A̪,jSFDqm֌d84̃,civ*SQH \%F4>Sk?km{M7tb` flqfɰJ8ÿpk6f.!b]+L'E/S%8xbJ*Hw/pMCDm%,LW9bEQ ?x/<ʼn(##x#NPG?ǽaA!vW2_S/DF|S-%5LzR 5nCE#3>p2(vwfM ڐȸ+W8j4aFD y$+}\o! M~AzdEHTrm~zK=(2pg@Yr51tQMS f*Q%q֐~6I*#rfSWy+{'"eb(ߐ!WgYEDG^JU["EUkj{GVJZj5~⊧_|ޏ8 E=3 ^UﻨŒam*ݻZ:1#{F2Ӷ~z۷m K5z֚):.*f+uU]/UE{65Qa^H,bb3ҫ8 tFtH DAYI&cqTÁik3j)GR6S˥ȄVUmӎt9.bmw7Ѩ&}$x #q|7W"j-Xw^x+ѽ)5̘"rxOgp6;,b,DuxsښT'eze+fQkM(20@qPڶm۾ sFvtOO"O77c*f"fEjjNN~qD+0E 5 3w1=94e("dМ33uH>0xfLXRNW^H9Ⱦqt|fI/qB*S*A+čU,2G_W<y>_gn̈́rA ^6&VP][7\bC 0:H _116$b*EGx?'p}{."VwUgt$Umٚ!f_mmGDL9#4W\"z13/pVbP ö̸Ma^wUU~*5(PwjSU34Q šEml]_aa8b釯Re" bE:hnj';2k_qh0* 2W`TS vz1*k!洟Dԏ."j`DXTZ0k"ŸϬO_<||9q` *K޳~%̈bK|$-Y6FBUu\:cxWeT1¦-@mքkm1ѽ3䋢*c3h?m S,$޶ W"" 3RDUHxH0TAhzVpF=LVeU3k0UJ}RN9%XDRNG$ L.'5*#I<ꭌO1ʪrRYMw~y\HQVtWx^086i2QF2Eenf<ڙBT,"k1ˊC\ҋ/𼇟"^|,D*.牎bDjB:qѣcAG?3a׀[ap+1*۶a&fF2$tԃ*~լfq<#bmoۦr,NDE}ƋdP ߰#䫯,2)x`bMq1BԯTBD}Njgф579$aU{DDۚX3fVQa]MDxE("qq զfU/"RUCkp׶5r^P$?/Zҋ/<|q/4Ag@<E T\v NIG쀦͏jMD{?zxrUW _΄ݵMQHfy?2fͮaQmk(2ɚ=x@źwuPi?vUݭY}?Y30ՂYO9C,Rfh@W2 d'ҵJ16 ֚㺩 RY#, ĠzRf"J PXGQ=ԑ}R -߱ #q!,[۴)deE~?gxm8K'"?gF?5x_u.X#OƧ?UNF\K/G\"R>$U?0r>J9lLa~"a"43QDDFtW2(()zKSqEW&3$%$EE\jvOt>}R#*wf}U~:N'pQ!mִ9kkea9"dk]mO]PXEwɜҰwT0jҘ\TAWTf*fZDcDӴ$Uf6L*Q RN r{<4 l0g3Ek1$@TK4ЈLU8)_aJ\5ЩH$?û O֚qx:E8qgUbmU#29pJx3)vm۶fuvssIYF4Ls6r[$Db24$\1 On2sHL*d(ܻ%U15m nUL8,*a!TDY gXf(Lk~N}8lHڶ5!鴡3YywDF8".ET?6S\&Lݏd4@w-;ՙEQ ?^|{?G}x0rD?Ĭ"e@qTQj\fꄧOj&;#FSF"lDTë &o<,ld,qL$<%S(nXR+U&H{}Wٶ}mۦ' Z}#{Gspd(jTZÆAYW1fa[eWC^UPГz-3F)LjFaմ[AW5Mx" {e#HdqYm6mۆh[${4>E^o d{i=KRxO쌫~sNEQ ?LӣG/⣇oDя>32* B̀ķְky:\¬qw"z4MpSzft^Egdv͚ k75SȾqxYT BӧRY_VXGqڶYSլbF21m"pd9rvFx 'Xe6+7k5~wT;v}G)A pK;s!;gSmM`iYAfFdFDU$n HDm{ZRf5-eeV D4ǧ[ŢN5SY\T&Y@ͭ9@A E{:4t ^>PLLvPH31I?N:Gsڶچ;RcaƯMݱ!?VR3W{fJBѮC7T 3xYHo&*(Iz{`Wkg OOH,ѡ7,"U襯﯃bQ_9=W=z("ُrDĐhᨽNrmѴ7']}bf7 w4LGe `3HῐL fB4PаQB; 䞑YBDizg"a15fZtm1a+Tm5 {Ld?Lw2 K 3 3p܆8JM)w2pf1F"vLed s<[Y`L/NɪGJQea bSE()ЉN$p3x!QdV-YYXY@EldZ"Q02,9*292A]J$9bBB{S}f!zW_g>NEQ ~xWpy{8rg*NXF!MҜC^#Mq?Rh3bld=iAq 1m1{jQD\"ʪmgk"k #Pڶmi۶޽* 4Q< A<} GLm_hCP4Z+EGX+l\5,W"O $qrAf)*<*vDDER{RJ&%LJ"Qɭ"ᡙ"".KGGm*"3eΈ'VH5ܶ.k oli]]W~׹(jϧx7 & !S,Z'IB*5GP͢fݖe͎%_)Q+r'Vm)=)Kf魹.rϞSA!Ec5]YZboMDP<} "aLD$3'SVhD[r^DLW6kw]*'(8PL e|(Qxg| +ST)]P&%E1O)uZ ՚r/"\Dw aJA#c`qh0H;2哊|Zl7 X?>D7^ޚ9'*JVR81fS[wc؜u~+ Q9ݸ\Gy ?QWvSb28-^ /_)! #&CtSHq"} rl1zKT5?̨Jk'+j RN~lF̃" -5wsn'sZE FW(w%~ڦY[޶UJ IDAT~:]/ZB/sNsU{Ac\C֫b Z/)*F RIYJ٦Pmnфe|вܵ}p~78fFV= ɡe l񯄥5Qֻ-5afehWRiΑf|zٖ2™7URk> _oW"zTGx'~?1IU̓-ȍޔ@bI ={26QrN0QDn#|:2nV7qq׾0zf.D&QxkML)Ca L&?O x Xn ;EJݹoqRZA}z.1q7I^9 - 8kqVt%@p浂qwf;GFM2qw"Kb o&^>&`p gC2&n61$6"rOlaڜ~ Oq4~\?ly?7 0<u{&-hVB*RV-un2| O %[ʋ (8tP LET{kEV53/ EuW6c]k[o&rOqOoj+w f&emaG4"+0W4lcr8Rcn3;LnrDZ8[O-zSc ġcU}r\%wkڦM5)Zk\YUqEӘ㶝aVʹ9:b"|iؙ(h; "M "B[HIda9.yicNU qOܷ^*&lƑYه8*:~r:->tPy'|" fzj, 7%0pw,8 +g`DDr.$KVAn^b4kSe1*ŒcMr):>Yq\AH^ 9~27mIfr ́ #Χ bJpp(I@uh[k@++ִbg~;NɁ|OrX5tsZpI)C[.xtf|9 G:H߶!GPksHb"鶎|u,dL<1պRmjNio{︴1olx0 j1k6v\MTcq<~{Xr{8|"Awqy0\('y ǿk݆T*K-Ĭ[ Q$6Q"$ӮfDD52qYU- $T[mlNUBb8[G@Ć?BTNN )2"(<]C?Y9S. D%Xq&aW"DImIPNzk,*)+m0{qn300ٳ !)J,ub.Qr |Vp͖:E}z@pcvwfxARdtЊDAnAf"bTV'bn Uk5℩+h#. |>kai*D,*!n~Ly(7YMܛʷ>_cQn t 3l,!QмREGqSG´E {l{c*Y`x:f1E1%ɠLc /U x:GO@N(֓\{ oJ|#->7 5.@yFU,*"uDG {, lc/DbN>o;}c\MXkt ZӶt>u301`}w| 2 mNDqS1ڤQ6"bc1&੝c$gtyQ0GV6B!uTvH&UI z0  mDDĭ KeӲ7U`Hoo1(5d/l=R,B"lgg{\ mA{me5?Ll>yӖic[Dk TP7~q=J|h-&)*Mo="N/1/YqH#Yd>"IʹV[Zʪ,-W:zgi²tDO8Ǒ8>0skumEYtWc1m۶m;39q1ڶIYsfNDRȳC ,89 %̐U22TBr몈"Խ`YUfkQNƘ"훻O>gALz=lII]lGŞ1za 8=JԣD=/-C\.x.+Pe+b9;y=65mW_ ZpYk(9}CFLmӘ#D 4oF\b 6sU+u)@+S؛f۾Jɢ*zצnsNkZ5Oī"=2*+[FfM"q(Jqyh`;8jO Cn~⼚*o4]lZ\p.M3%iO$r\̬2i7d^0my eu`_ j9cΡ@ӶG'qߣD=p_Ct R(Jo]hlL+hQe/P9˄D:2 ">kT~= |c`e_0Jb# ޷m7iy*OsS=}۲B'bsA^Gv;ܞQn[M[M@ >W.5\o sgDsRp7^.+_om =.T <L:&wdj \R[Euc%0 Ӻ;FզJL[&Vł#1Qfn,)U"A\QZE$Y=G0jpl크J=WܚD{0+@20*,I$"7t7r p"aso*h??ǡ(Q}{=lNFѦ5"Ot0oM<  v!i lj~R f}n0\q2 "@]= 21MWmҴmز Q9Jrx}z'4cQMl#3ǘXŋȶ^F8}b(gnV=oraZeiT߄KQih`6;+'W0sw/4,+P3kp˲0̈́L .-Nԛ:nN `?1~5011}߂B7^ǘ۶ip2{EMINCv_ޑ%ݳi%#s"J/Sd`0 HcnM}[ >-b-$u+'!{qC^N^;qh/^3xI)lM &,b*i D ֑zOLjt9, {1<&_> @l9Q;› s`Ȱ;ܤAʊF!Ӿnzy3 2u= H+ ߲mA$ =cWn ZJ,ˆ- ج%X謬cN.*sN1|f2dZfO= s ̚_uַ6hR1.K[k󻓙PBNd޶sS՟?yؤ%2x'fE[k|>1F9![r% jpx=5ZI}q^'Oq 1-Q'?$Q[("̯u<㛅l/ 5bkp4Zn8o]U!PDis^ܶӶuP^~#!a6!jN܈X%iybs/f.kkkD!|) JI~(POMpߜ "ҷmc@}6&*xUq!-#D,8n՛ZĪ D/ Rfxկzl]5A>%9 yF҂|;͇MQ>>=|>zΧhfO$ʝJa^.0|KuDl ִ943H}5o y. ]3yrb\XYt4rz~5ƁgA3µ5mAsT$O۶Fx\&*n&"9SDˆefee"Pơ\sc\W&n۾mYƴׯ^]/qA{włʠ*50R}퍄!$ex>Q9Ѯe9;KTv0BBgy0KrJz,ǂGbZC6՗ N,L΢ZB yi+#Yerh1j  $Fӟ>==%oWk:޵ڷMqʪ/y8ޠqݙdDCaV%Q>>>x~k{lasXv@Tq\&q3Fc%Ex?Bĭ5!J\9«| C`#UYLĴN~Gpz9i*2pC6F۶m5mk޴#2=O-zDi~/sm߶}zjBL }s1B>ܽ0oz"^q\ CLSo۶md6YRƈpxZ^L69κUH/gh1`b{$ /ٶ<1UT7%9fbQ1w Wp`8Ǵm{ߺqׁ2IĭM!0R f(Y¼DU]v3+sQ-3%Є7 +,aN!+QLv+)y[%IL37 R3G .J!@$3 ㆴ|1YDŽ+żLf &2׃Y*fTG*oZD&aL*0eU79@|s J!sSZ@9HOxH:flύBDW*!>%UTY6\EłK)$Ðg6* 07]qPlN rxbP#b>%U6=ri=&vW)hdRDt:ef!B.NXѷ%Q~ux??m9mc2D>(Wh(I#u7 ʦHAEpnf"u]DDz9Nz1iky%B& @eʆCyqmuh9d}inӮ ].zpɺ"Щuqc0QA^2wQJhm [ŀ)rnDș@n řaF+Zp.}Sa!(,YkH=0X.9aQ[K_xW֭7БǩV]9@2Ɣgz$*pVTz\/ϗi| #lŴm<9<~6~ye"ܤ"l?>7A_˗[PQV^.c` .6m 66+ɑ7 CZpv9gn`B! X@ 1J3WsE,!6^:j9޷O  #^!{Eb9e÷" iԷ]T^|K_ض}>0[P9ḇ|шmK5[ozitB9[fY a ھLHxG4 r -9/J&Y&~=j~.I= "p(Qda -ސLd='c &VAQbT*860& !m5SeW[%hҺVaI'"Үft:"J~\ipiQ=qNb)7?z/o>}KxO𩊸\.0#.|Zo6 tCs[ ErRt%3ȱGY*/8HQa/9&J}w۵˂[8 m .  Rnԟ^<}K_O'371}fӦctZ;6Վ{*6aYGí7w[;:dw@zNYWg/VS ٣: v$J_ZHD:)_rtf-yk- CdyRk)l[ܶ\eAn3-esmۈ)\YME.ɾ 6=5/D4v[ Ua‘+-`DzžH a⦢v:ϗ2'֣D=J//wJ9Yzwuu"m1nZ|V.~SɝDeK0J?R90'#ٕD8<otN4:<# (3Ny}= py:_OSD Q@?mYXRv*5#rݠT"mbKe.#OC"b5u"ʭre|$XsnAƷSŖeU") %ِ۶VXbA}v QQR\ K8,3kca>J|ԡ0\:{Ly=s R%fq 3[ŜHBZ}sXn3O s'_2*?9?d:nmq]ܶG3"UfFS ⭺M=]""vO 𳁾^r(QK~{6+~㦓\kn=9y߫W(HDI s\ Y"%{8YA@Tj*_6λeOb^Tyb4wuX(A  ,6:EL/8]/ `<ݵϮjow-glř"DVE~3[ {˸Ƹ}u<"˼vI|Z1)gGkJ&ȍZĕޛGS%hm Ă&~KZ#F4eU]ĹuW60/44m8mhYض-€;qh&A7mz-}O;;#0= s&LA;( -;Q8Ը?nE7Hb,nLPMH2-ھ%D0cQ𝐃9n%`EUޔYPJ0'& I!cHRM7Zs}[*KΖ 5kGTkM 'CV݃gjht5)5+- ٴ ]?4e6 PBѦ> 6XV "?xQ%o}o۶$R'{oӝ@+hq]qsXsn,U6)O"X (9XrԇYnt["nQ0JOJ%}s--(ܷY)H[ ahhNJbbd͏qWshP h0-!)լn5(GP=UCZԼl!^JТ3`ߘfK0%|3{2B'n\{hl <}eȍEt˷B+鞉o "JwPyܮKR("ʟ'~Fo=̴u5]Um۶Z][_`1m0.96厫"JJNwE4Ad̦hҍJLtDxQE8[T1*91qQʻEQ(…H譩j+#n<iB[0DZ/|J]Zʊ;97y"jMكAy;R8<'h_9ڒÊ%@^KH) 3n(tKR^U9S߈!-g0;Ǻiz)aNI K,27aOo^@/"d1B4eȏOPկ=G{޻h|'אm}۶m뽷Z30:&Z[Yw_uyOgI\r٨M36c۾!!Vav3OsL'.ܔ8,IV`Veg%ֻ6Zc18Nq dJUX!3wG[1Ɣ'PV@D4n>Hw~1c\T۴."w8҅-̹3#" T1Xk\W:7LiCikX[j=6 ovxnM:6i˞NtrsIGRP,gJ߾sAoۦE%fs'sd ̥Q֫Pt@77_a`OúZtзELO(Q?wyg;o]i-E Gb^EKSUk 3K#;Zk+H\#8}hadvi1EPU#EiM{lJo7ut>ĘgQS[6̲EME3^dH2J!Mrӑq6qHJaޚ-ZT5d=}y\U5Z?βY0""{`IM¦V1QS^󒹙rU &1\\&[veG.*(5XQ#0wۛI4H+zk 38.%hY bJY$;8*s9rbma#mXF0f1URO~oo?Gx|{{/dlDZքEqևymp9dO _joqPE(4fRUXe]\4=AN6_, zڥޙ !"tf|$紶Ӌ,6ZC5)6dwm]T"\;4}wIRU} :4fjC06-fcF2{D5y %&m῏becEx^$HBD[R>Aupw-c"Mg1.rVSeGMs 0AA)81\ s^rBዷ2HZ"HZ;S"݊@MD!LSNr~/ǁlTAPT!!.|:q]Ҩ֊s:ǔOoq6>J{׾'pV#5^5QnV nMxz\2 od ?ez\_M2euލH2.Vy.^|rs29f}Bl!rz:y6j2Pݯ 敭k{wnۜzc} #S#ye۶VhC-@>xw9--{Vx^G@9 )PGϰ+LTxb-}kG@0)qzVTjN%WI k=,XLշw9/<,`Ķ S<T˝E 19MȲJ9%nˋ@T[L$*a΂`d UY[ʸz0&T)Ur ӦXIx_|8%?m߷gE.Y"lQ.w+` uGvxbT1b?<TL%Hɒ!┟b19&97fv9n]6-n>oȀ{o"ep:̼.‡|\qq^f֦,l F-0D<#̤ga4XJ9ujiC ֔Eָ7=pbZ)/yRJ &I ]#V5+Udbܴ5crTQc̈N_;]CЕմ[Z̭hJ72s0BzQT.$P=28X֖( b6 m {ڤbӔˌ6&wn*=A4Ƙcޣ D;B8A|zbFknts0u"")Z11iǸ}q>ќޚ`~G}Zmۤ_zu\h;Ɯv5S{WQo[)3l-V [.Kj#mQu"toĀnd!yevXT)"n,MR؅9u+01*YU.Dv\[Iȥ[wG=-f.VM+~>s1ٞ3g2 ^+}?3%'![7V:!*&+9R[#zN[`{$R,7=}Hf.FKTuh|w%Q.}?{k- sK|uq#M`ǜI@fCBLlB n~AR %15Նo4,[I[' =̦{ V[1nºuz׶}?icİ"Q3ɂC,62[/W=:q\WICX!5XR#;8e0ƷG8z@‰ƒnb|KG$TiX[-u6Y([P¹lh)ZF@RB.$pya31 kI ɸ]K$Dz 'FtsHQ^9YHmĤ2T_&,g~8$%w} rLbM4F{֢ [S(E5lq Ut¸ rRY)ɨNL'Ƅj!pPC5-\]ʲO󾟘zEk¬bL{۶q[:|+6>c%PD) eApxK$^ IDAT|T˄es ;8T@'"I-ө!\Gx5jSG7[,;l{d^BM Ț1E}A2}Pg.$R PDfmfk 8Wp&pDىE$ |8*QxmNܭYEN(""7jLX[`G uD F$`p!p[Ǥ'(K[8*;âҼ 9¹ڼ̒t%𐩐ʜ`QCCv (+ݥ|.:͏~{)Q?ԧ/_m8dEl_ʶs^.* '.A^Kv!pj20AZjU sINn bwD"9uYG AjttcO7)ݨCgfFj ضmޮs̵֘Ej\̀1U!sS@'v=aw B`4i/w]\ޜ,aQIC2Mrp$]Ď;?%șRB ["pO>Z|NoCϟHՈ0)pZ qjsMZ=w u/H(9ǀg3LF~Q؁ƆP %EZQ0OA_~RW_9CYZ0Qz9EuP? G-3sbd|#H#/8kU]Cn 2hu$s"-smR־ڂ_ۦs`k#>VZ4a:m3U&6Fgkcv1mRBN Qɠp #h I=(AP=R*yÎ)mdk\TI;4;gav%wp/(7$6A'Bᅘ:7= %Y@e9/SRzyR9Hy`PNizl0U͇$ "x0[0Q7/=87F)Yh-=K\Bg:f&(|o_/%__bۦ-tW}wme %́FOyHfUSE` JM-|y8}bЀy ym^͍`}<nGỷVNHْ9%qc̹mQny;E\WԊ[՞nJHgH 4͏p `rI:𷤞AIX ܝWd"0L kJVJh1ZWd(Np:HpF',KWn209Lv6( VPكЦ8(k3H`DWUyZ+Li-UDێ 3`ЛE$Ơ\Hn?9J_0c[☙UkDo_JKWzy?jΙvAt?Ӓ'9Tz9[grAɞs9Ĝ 1yJڢ;GeNes@n&F&amMq!t4F 1{Tޡ'I YE*N[pCreX۴iK gLbhdzCgVXF[AYAT`SbKfw)3tQNB&6O:. /fЁ PREPs;rvPK9FJbS)7)}dB ڽ6FZcL @`,c7o^&\0_/4t fLѶ]^=z$:Ș7 ~D{<wTJ෪!6'fC@kߗˤ^ԜjH{xIk͜W>bv<==QzQfҰ-g~#2.Pq. F}O`9>ct+g]r#"X=Us-P콸3/x':6G\tJ訉~UĢ7g}!R9TU Yo4ۊh,<<ϙL)UPM1U RSŠQm\slT6-_ ܦ9\IT\ꌾ 2*dzy:־;6UR{s·Esb֑'nI" D,iD~ŗ/GK/־Dو&8ͷ624֊[BپSTAtlzs"Cv[6B Zfk;k[DGmHDNnl>sne\.:Fg|Wk_h9Eul˰6%釈E[ڳUJ8ܝ4f tev`m[l3N1E R|BQt>l~!0{(+WUnS 6PLf nLŰ6_zcxLPw lAsN@Pg{CeBSp!CFƇLXi{5uf0ɫuUYd !ێiUī*U@iM޻}l1siM҈~RO?rzA$=r)T[Miqz"cEsZ}WSt!P60ji@|J|LAHkm(&73ӭӱpG{0Y`fZޞG¬B W*Pt5GJx樔>I U8+,D[ erWNyjt,9ƘO 1$A$8RA: l8,틇~=աR bLV)r4;ApZUr`VNA$! ¶^ž7"3̻h-p@Ƙ+?@) C_/%_駟+H\nOe9n[FRwr.N>"s*`cL3ncHmT>UsLmى*MluC-VD'9*r sD\L&h6TI u62 +! _nnC@ .^&\ۣcPOX~&?U#(O)H)6J*8jw15^9܂} ͛] ku.t`Gh MsQ$'?1yD6R!9THhwlP&SJ  QtWK{U"\8߅)}dfj_{ M+۶'bkadP1Qo.zS̭>i@.9R\E!,M[g"݀4͝s{# tUIը#$!&R!9s*9hN[KEM!&%_ b_@ Go*T1Q>&U[bEVNap-K7ħ^%hRKdF;y#tE)B1< F\dT&l1dۆ ngRH_۷?_5ZqȐ?z\.rcCUX(b:$nn,te˯wX!ʨ*l]hꒅ|8о?϶m۶M. ^9 t}k"r}bk ˾v׾wV`ЪgnCB89|yBЈ% !F:vtDx<kY0-pIdM/Nt5^!DPwمEN#$/ dC'ʑSQb!p[!,nN斱 uZr,ǥ v@\/3] e?W,؜tU/U! )agZ=Vܑ!h;Z r#`;k +K cQkt'GJrHؐS%i*v*"9C3J`I/erK*ᒕJC#VGigryf"g[K+zU?247tL >2P;??wX㉊ 3(~d?O_֗gO?OYN·GX傎9v.\떭pWa p[G1/iʘ@y>h i$-{؃ǜe'gN}UHE#3]o>HANTZ *"5~fIgWRZ9GrO$-sn ܗEsNHQ29}VaΩLGT-"9VN«f.@NADn($E[+V2^}X+2 U $p(g\XÖ.#YVK$C#BRڎ־e0:R.1Q4&8A^`!I3* x.\L髋wzBv2FDIHu,-:ԹD?zzy(Ua~ D? \^nl.usx^m㾛P\/im&f6*Qf!)L(f,?Sú9⮦,c߿\.X$/鏵(Ns:ٖH)`Qm{Js;69ةN1T$_[!U8F%5JA5Z:bH0P-Ό`S{c'¬\totamC&#O1Dk; {IF>''\@>?Rp5ys2C7*[9T*B@)'Nsc*^b 1F*#K%," *Ppnev+^aO#> kŕ)6T_P"ǡcan'31o_JNjuG\]/v^WrqG-޶8}}O>;\i/z\/CAx.ė˼\.xW_n! vtH!ܭ"(d&4}n߉UJ].9w[#F+Ƕ== jv$,PKn5d+%8#ƣpʕ!( ϵO2Ǔ>ok'ӃQٗHײ5Bz*]kĂ fX0)H@ t%f&*5N z-"0jjxi:I`nsPњjqWvt* ;ck >}Q>Ҭj(wt/}_Xkm|H,G/GS:#ʶ_ =ECH6ҫaS|\t(%P MեJ^Nu봚IrHmM&o|7?K#Rׯ_v< *y&}_RѧWmC'Dt]oz:tXl=lD@,3s3&;{$.;+s8ED*ضm+u+2)eM'rϸHx;|E*c*UY&gRI"E0Ǻk)~ КS>tN֒[0)x<`d̐iFt81b$l&ӿ6O^pb g_j<=EGICv86-q<WnM&a)1zjeīN)yDɬLp,gBr'Us&g@MAuFabO5vF48c7Az~~z \U5sL~/'K<>~v]U4<~t <z\.oCT2`d}-3133{~>V䱍9ǜ=nT3÷E>((Tt\.\|b}6:>"Tl\'7l-[]sh:a.[ަrw q.oE r̚][=mv*!e:FHv `I)4:ɚD(ZS`D % J=鸱R,ru\gT "P,h42]z#]蒂'w*z; EMbO\>.b޹]:Y$EoYԎ mz2&lTvaycO⃱AHhSETswHcE HZt(bkDeno޼y4/%ztqٶcӫ )X؆sL2~70mӶm>޾?;GRc_k%WL(Z7QB~yTsmQy IDAT CEh60k ehX$*19yGƲ}n[I(ΩUZ)"l d9R .jyV/Sܕ8l)SYUp=Y*愕W)XT@Q(5 "E8,B ˧|%{ajƐu3#DSBfq#b9LzREB UŲG=:S\fwaPj$nW"Q(N=O}IJR;nR_G -4G\8np'Hj5(*&NyfsF88|}DZF,l/ӟԛ?׿xIAc^)c\.12/nrk=-s˭ܶ?wenz컭3.,E)CT0%GfF?MT%DcNakszOS"̜+n}nV[%VѪW+Z\G;OG„Tloꐽ$AU_EU6RDr?` 't4 X޺D\t2S),耺h[~L3 : G2U)YkגM>YKH15ݎԏ I"BTfEQD qKv=i~,li.Rf}_g%푛aLЦ19/УxDvc>/楋=޼y]6k_"r=]mۮ]u^.vCudt!e~}1۶˜w߿w!Z̗PQ[VC͑3 Ǩ8&I?;v(sRl.r%K}?Gece-L[Gw+R4,^Mdؽ3\pAkTD pDNiaW=D2x^FDOдy&Z P}BǹtdzKT˨X.,:ţəꢖ ro >O:#ZqC;=mDЗpm ?=$O7kHz")Nk!Ywȵ4|X‹(߁|(ͥQ 9JAgL9#99o޼_伔_}y]׾m{ݞ.6 >Ǩ UU 3<-#톬z%w-[aZm_C5D#b7,@/0{Ę8LSڷDEP8޿{ﻹmsB2ק_2sb&@bcp=X"bǰ|֑%'.uP= 'O_9̿(C4"STN`i² qoP3ʴۤ,$b>"T2Ha q#S]X)c> R KBɘ)UmBd9 6wf8>V?P =l,ɚŏܓn [tBMm DRjcp S/_)^OV#wezC\7̡n9hr0BEV|]-`:l_W_}ŗ/%D_~SXg".v]o7Y1mSD)i-lQvlۅ} y9y?־? c"1nR7k8hmmW$E0OET̞CD.v.k+n+~#fjLy 9+:.!CRb$|۳ieb>M-<:%4 ND?U='ajHkROX(w99CHC͈'FNb{U~a x%LkDŎUM8Nc>7D(bAA}Ã՝EuH`P&xeJSb&c$Op1 O!6lVӹ3oQth3[0j2k޺G;LsRropbNW\k-Dά'"sszA"^~㟼Tz|ms tޞncL\=w s91yP*:m0}#~v?hQ7K,}*p^a 6%L "PS>Xk}y.^z6`nk([e۟9MG<<59c|˗ Hć@RW&A^yqҝ+z+x 1T$}|"k䷙H˚ 94bI[J]8R\*"zav`5^evh@/\HNOü a^LU QokS^e4gø[f &k5]]W ZݬLPv)⢑2 @gB 1d^]e^̠-J[X̲?v4l,n7/_iޗ|t۶ q[ܶ9!jZ?bR&+"߼y?~i^J?fʷ͉v7w"2Cb7# _X!mIk_?o{m@ԙ–kOvXe&0c &xf& ^H5YhCf܍ЮKz>rT$= '={pWC)'d^knD)F[G }dAMؙlڒN\Oe*{ vŀMpwι-t۵$&sw~W/%DwG|_b sd̴VfTh;̣HC)2׻{~6)8q pEtnfУ ˶MQ}9f֌ceCXӚsxΉj~<޽{[[ȼk^ׂbꊊԱN(j>;6W'}?V攜/CRbRbiq$~7b>;x.RMC4lO9iVfw^:R92ӎԂ\8ڠZ@JlSxT}VБ*$+[fD@98{fYWr]0nBC e5&BY$ O}dxrN$F㐎qMMx̔9t3qT3zw߼꼔^ g\BMYm1,vZf*?;7w3Qy9cNsflVs[Tڸ~UCDVXĝD#Jbּ|P;vVM,ˬ#xX# R"d;Tzb0a\;њs¸T4NʊM_н{8Dd#;1"D!d43-W8 课}=/%^m,81z5h0za۷{[=o`= V:12XamA s|ǜ㣏_]W[m>==a(V,keb.  c%A־ڜ5{Bd־2{[#'4?jTJ3_ H&a% c)J`xp;W<$[I+aTZDOF8D3^KU@/dpi4λa.TS#/ v{VdtHؙ]-aH See`| c7u4"bp%v徒W6ci "ҩB%e[tp }T2*)/t:rGb BhK5%v{mb:CLXqC'Ρ\`JTfڪ Ô26'8gp-CuI}L UaE$b:&Q8Wrd3). t0jZko̰!$n6pvtԥU Oj8@7LR{}Ϫx-,]_GD_o޼O1Rr"se*:TT9|_ljLwIQq܉yBZ{Tc}m[l@sKMZe?DSL3",mICߋ dDs6'w̉yJ'|tK֞!8!zg-vQc`)& %>KdDA>dM]Ċ7+EYħsU1RrGRI>mTnN܈p%=hI~7w[^` wە5H9IUAQ+pf[?)Ha;R郸54Ҟ:HjgeFnk tpSZ\f]>ȁX˴.M8މB̈́ И$ġx&NbR3C,tLaBO=1;?Rkf,>;9,~BԹ/>c8.SZ x\tTUc@Yc,+$8{Q+cʏ[TYIh4$EŌ `T K a!@of;y(q*CM hi ϢgO#2&R!2`'(:0 Ҳ/t"fG㈉H9=?I-9?Ro'ɰwAanR}hm1110Y3#XD!} ]qِskX\^zhba;>#xA*V'ypf^z oSs7Vx\;GKak\,?L !-S^aJyh7o~?ɋD}Oضy@yǾl?e@/aKZԶmcPrt 0K<4"־/0ן~PY#,a.c 69"3GHtpw'k/Ma."H:h~@S `?,bP*UO>Wjj|jU$%S)iɢ*^.z5dw?Ua}r">` f .f+3ecm[D}Ct0$-5grAk.ӀI{}PlEud+3&BTY3M$b!1 <a,pZ$]` xi@kVY.A,*0@0Qp'^8G05Ow)/$Tu#Gr91픩lL͘ecFdG+:9 X$!\olI^g3^B]ԑAVBϰL&Ty70^imо j"58lEn44 k:|X ,2KK.sNs[fLln'/EDDCU.ۖF}־ fwQNbjZ)ZZRv\.:Zݻ}],i!P, yײ>r`*h:UߗAMtT2'Cqu%¢2pfw(D 3qSH 3giHR.@(ϛGQ `D[e7P |/MzlCH9)fQM,RR[I{]q{~u|[+{1,#ߣh`pRZkp@;͂)Q_ !ԈCfU P{w 3N`!uͅ+-eZM#(Dž\r\#b|âR9(@3Tpyl$1Dr(2%I숸vۅl?TU[O?^.L~1-ƾ?OOP=-ڋl͎.s\ ߽?ICe93$XPaȀ~eGO)W!@> ~(JG|+ev{BW;hyKťꐂ a͐o0O=.fv^?_k !LT_z/7WrU ſC ت%VUTFnܐ5Uf{9՜H:Qڂܢ#F U^C͖*h1k=<ˌd l{tx2?801򵅱31Ȫ_Z8/&xz*~0]%BF2#Jaap=H$ƒ ιRQ@¨PܐeChZ_P2[&dxCvy/3sy t|X^ϴOၩ39Z@UQjT^,&AyʪN$JlU )M]d8Ġh1702d4!o3bRHIԹ9zh\Kc'Fc2j%HDMG."݇9;4?/W_^M&6[c}~9Vfzn(Rf`~#-rnO)  åU"b:iTUŖEб]68ʁz./BuƦ.3wdO!7Vyc=P(rRL4`>4#UCl?N8S{LJ CUTbJR_EA/fX:cZBѪS-/FE#o!S6'3'Y!m  o.t%'p!vNRAp~*Xa?!Jzqp3YQa%q!{Y e.u|rjs #*~Ҫ$)z[QT{GShI&'MI@'cxn  @io^͗?t۶mnC1߿_k2U"2 76Y˼\ #)fX%I82<# 2c @}=TJr:1p-hIG:YUWhI 2%c>J턋L\=ØT4 ֛9ŁKv.ULRDi[QU+N)\&r7.rPJA:f\qR==),,̤:*:rBfHQ^}"`HyQ$hA*u+I)K6 QRErdgT#\)ZAn!j -eV$v#l]2d9NjVv\fqyPy5Hg Lz%qfA˩C~2bJhÄDz&FTsU&tt8J)n_r3ۂTr-CҟJ/iNzhQ=?%u!\ Kg$Jscؤo~X(=NbA4s[kGзo|o~R-/sl=ofs},ُU'^s<ѱFu.E֪JIMy :AljV{xӋcrҡ|LClaOwZKXU " kLis n: O9$UfWV=q"-+w\>Q)(Fˮf{ożȀ`5J 9(?8[+~ טб0s|,fű=~aΩJ3iG/G~.HITDlW_|R(uJxtW9w.4iNAS;:$#'rDwz+-N1:\ 9N:!3N*se§JOL¿(\\-(o-jD߉}YΌ>ʟivƉ X''WOa{[Nd5#-urf-* Pw$n ؔ.$usfx pc0 f)v>Ft2fSӸ51kJJC t>3KA 䯬"> ({AD2})yH~׿OKW~_9݄x_xv{ZS-c1s esPUUܜ;b1gPx-~! ֚Sݓc%&BBΨ=Ƙ"+S5c<%"FmJhZG=켆952!EF(Ot3Oӓlǂ*ZhkꠎTnvZZf'sz?=Ew'h*qq,tz,3"7AxG*+ ")-5y ԡǣz10,j4r G1ckv+LJߖ.qH8&4D,A|ǐ&bDBbPDJ#K&ïp#Z>V\vfͱu/?O޾ym'cȹݟkz[6ŋ"!ZpK+"- q*!f:; +XtcTq;!u8&>>Ϸo>?Z|}ɣm`6v;9i."[RZ0(k(t#;#)2B1,*8bNQ_𶖷;`'5n)⇚^}9ڇLC]61 ZA{S80M|D Bnr܅>B0+MTZ t̕S3 tTƎ[ DDQD>iPOWx˂95ЖQg&w#Dwzg0DaF< i @HJytkk ApЗ41E?~KپeoD|BU?32c`>-׹-m;3i"RQI*.;Eq8V(JZ$!,usE^l79m|͛?Oh^.gw_[Tvu,O[k[ $"gYߐ?ІbK* [4eB~dh/ոDcݎcZtdFn(5(^"Pjy<e=[YSPsA!륢CM-]F'X'\ #k`9aD1 XBݜf/{Ypo[3fl܀I4N Bڨ-rjJ^&Zb* Jܮ n( "Z+>X(Xi&ڋI %"od5Qh1%'SDE"DoOOj1 Bbux<3"8?[?@`lM$9 |phtO4Dڄ+W^xD$Ӽy͛9`y<HZ~쟏ǻw5͏m"G2L0p)|<n XL̷m:}LȸS[VA[ـx<߽}t;9S`)K',;/x1Ӫqߥb+V̒p:y3Dj@ͅ[6"w0Uj ±k=nbj&ft#)AI7Kq 4o^)2*s@lά-SZ*Bҁ*Sfh iBBUh#qЈXIIq^Zd6d$X}a F ']Jڗ@I@o5fuP /B&q<,+r:룛2X37z)mɭRNrzy.2ƄF"#!#oujۭ jQ4S)Q"F8UOwQdzϞ)kM޽}1!d$^GL}@ =dr~wPg MIHU|2j__S$0Ty|bnۍyD\l3 PDMSBBudHCiji]A-[l%%/99L^u55)Xo!Ŭ ;2 D}3Ju4^aI1zZ.$B >c$_;w9>RRr%6f+9[NORw(; U0:#/ ~zt@4D0Pkv _,jHO)|tWFXT OuJx7H$jđz~z޽y[dS>6R?-"Ȗ}y"g~gewvg4IǛ75@\yP:"U;$V]Hl/"n[dP&. kd g{C[evcuG@Z?-p Y#m  aCwңBPNJ;gW TɴZ \p&&MoZko<#YUĵ ܏͸h_: :)P68=y(>C|*6T2fn*12x*2Ȁ%ǷHiG#"*RB}Yv ]\qB~Eu'T.3=-3" #LhY0䎄x M{H Y"vđdzN,Z_G`Fh$b\{@Ro>. UUk{Kd1ynb}}m㷵g9BDWۙvw&L̤6!'#$ hng}KԏD}~ !~+8T K8UD"qSt`N$5$4Nu YbQpf9}&h6qVKG#gjfuZ&rTR2>tki{ >i^ڵwf` OQh$QXl͓_yfd1e鹑Ǵu0V˩?(,!l )OM:?EFB4#w4ڨJKnO+I) S| R#(7= +V. }FƼ1]NUa(F2BfcRWѤoR/eG 8xu40*R11ѠŔ./Z{[І9ɪ狗Rwn(} ^T8U8Y ]ŗrZmci5@EHLIV$#)өGyb\ WBS ,4=#%bztEC0Q 7"y*A7]kW n>I>>6OcaH QAEPZO?y?>x%믿VO>y!}`fS}o-vnZ">G8)oh2&ND[(C*a˓ώ&^y$Kf;(0#a⻽7.@!W .$vPgDܵoEEXd VP% V(ZdfZ˔4,Y8Ԝ7Щ (m™:Ԇe IDATParFY-[UɓϵN2{xiĻ4&Xح{tܼXvɇek৕QT&޲:ذ>Q-)>rRu3%K4dNjAsŸcEc̞cF:3"zDyc1 bx>QWUYnݻw-_~dKԗ_ħw^x-x~~BAHd{{ԧ8Kq[<'9U(?5GL`9 b!"پG.nX $$#TTUk>W.<%ͻO5kv^3'}ݻbԏD}T'/[6>f V`3ۏgfKQx݈qn8D\w F$AYJXֶ!@n8az0 y5"Z4cxPǜ(wqZ>@ۡ\_*:SʱvaL#}icXkA%~*VKu j*mtk՘6PmeelG Z'^ n`~(`!o{1kWi/o*y+‚TMb*դQƋ%P]J5%D< a-tnz[ FE*t|) XD1D$o@\\h]g={Eg٣U/X~T%׿̐PD^| N>[dty70tf~CWxy\}Ҙ4g-.OU%,(f](# NBƠőO1EcfcD SKRL~"р_ͣ1Q"X/͔}DkT_h5 kEJz|mkʭT~JjЗ" ԡzM{tYKU(J~!h wXkt)S24JYxRƠ_+ Z5z$A.ݟ'0zk%[N!xU;8tpEȕ92Zj{gJ2jcg,3 !&TLc}#D@U!A" 1XdR$ B Ŀy~>N~꣍GT~_b>浞"jd-{K)kzQJb[!FxhJZ qBJ4d&25 $Z溟+C>xQ5Vٕ ]A,‚٘9œ7TR`(Bw#JlaG>^pS.ml;аkDtj†bMJ\Xyֲ"<3 BʀBufF  i|`YBD1d+ [*h(U6AM}vK{skʬ2D@fT6!I@Iz[x.8vt.\1QHZ U-lQHk"ހ`NY s֝?X|xJ_ @E?8 &">uKdG!vcϒ`}o9ï1"Br 9l[+%;!lI>ſ5SL+NfXɬa*ib,![ȿ+*^:[/KML^ݛ0vtsf/iӮUx{;Xa3*RM?97L A0!uoja`!pFLW4Έ5}RdaUDkP~=gD 7!(Z]#%Vn~h ~umDC[[".+@:c8R(ׅZdW55&etV'q϶Ͻy7D4"0PL׎=L "F/^xwN|돃>Q~ļE<Ecy>$8nς| xXyn]wDަ"vg*7:QCER>,Py|a!WiT1/Nf/,ZlXas[+`xXs[0379=cM8mYߋM__cyx+Y}{{b3\l#awE01-uDZkPQ&qh 3굡\΃F/ڢe"sfU5)JBF9)#+'mɠnL\m*Ȟ'>+-gv*{ 2o'jX^PFi֣/@[,3oqqSj>Q3e^E2 A 7pYٲS2$D P"++01\5 `+ıFj 6,Kt&^;CõVUb}/^O؟ *j~cO")oS 5ͼo%bUcKI7z5X`R|<w9 ,m4Po<P/`~̞o.qx\3Dʙy1kNc5z}r$c2P>i99sS${|U&&F ܓBU#ԙ:6l_l9hr`{)9_fx!eh(tL]yUuˊ38N)=(X`װ9qV.١C4S"ߕۧCGam(KC'V#yLI@EDU/3;c\G` qwVWܣWΝ^RY9tBj,-ǚMW#nx2WEZl/?vEE#mq񯪋D8nOb&D&7_G堯CCs?T wq A)< oC-5eN8)r-mG*xX%غEu`]s)`rV1`өj*B E9g~cVQ<ؿ5L:xq9ף`:G#rV#b馡-laAkwק(b'X~GsDa&"1_BDpڬPF7 ))J HA34ᴜAv9^粜U]?̔ '(JTBɴw@fm,Yu+skZO/0iQqPS,,͓Pu`j^E@1f̐*vt`L[< }wo߽S՟xԇ]~t߃U@-As%ȇ{ޠf"$4pv&&mC뷹pu%JR&tvNJSqF.Z+ HP;ךQ{Mb#۶|z<|1E~t/q̀yE:znaG*jr1C}bZ8U`71Hؤi|!kSpf98q;%ImCe{1b+NXu{|Xҕz/rkέr0ARmPPd/8SS^5{@iAE4yY̦W= 7rn!w,=_,DAM'#1aYE= φFg_)ݡԠ)5_h%>q'$0 t)}@C3dD o}@`ĘhO4MWwE1g P  "g9bѬ@CM{8.$H5rkS6Jwڱv5mE['Gj'?1IT &ll1nCFyCњbF<ϿEo59+/ ^&r[R}0*UJ\PBN Oj`%D qbUREgl.Ϫk1g(S9CKH9UG 8}ݿIVAPRTuQ*y1ηy3{o "fcwצ8nnox<. Q_}+Oyn3!h]^Y<;艸FHB<3*:k-^}#bVW6ETIBȂC9cjg3S6i~HW2I\\O=]ӕBh-VAnV9/ϳ뻜'{~ W0ӗfA_&VG)Z%#mG}H1JU^zH *#] PB0p;r@B0O,s&{;bD:5õ4`K ꘺?Z}WVˏ%/NUoOO1u7Pq3Ǚs{4r9,⹷ܽ66+(z̸V#!E9 5%\M ( `ҹ:Ʌ-@Φ>Fy3`}` t>PKBRYQ~j*XR0yWqgQC,xɬ(DbǬZS^TJWJ] .jv{PO);^-eoXEMQhԒ֘Ze=d^YcKLB@,KL}!iBqpOSA$m us<ۧ0>>KQ=!?}̈́l Q!{DZ # .Qaݮ U]X1BOCiN -X$}{ "q/)Xrq Xf5{}{k JDu;dUWA%ՆH3doR?Dhƀ @ՈČgr.cwz 4)),2-B@Oˋ o.*xn&<0pAAu H6KS tiB҅@z*u;}_ƴVn2z`圀\[Vi[]H5sA,.x\JQPRS:kӒa$,0?!wi >n6BW=&H=sMv "Fz 2?A>Kԇѝeɯ03烙l[UbbVVj:>NuE[z6]>Q-iYJFn55^'"w3QB5fW_f öK%g3BF9֨yLzNo?THr}䔻:r;t\)B¡pj}SM s՝|DH`:D%5E=Owx]aR4tDGTDB/xo쪯h K F* :rYMO4f] Q6p)W+02e_ݡ7*=/m,[X'RiXsHb$ ?W4ƹ޼`ꑲy?JyB̑ ~LlFzvV8ƪe-FJ@X\Ӛ ٰjX#r) @ d,7d1o4w5젒ŢB٠xQ,U]k8n:<_dZ:ZKAFNITjqB0w60_8YjBjdm:cU/kF*tv2=ga9T ,4?[ ; p\ô "9K-ָ7g. PtF0zYZO-lk}C.Ρ*DS9QLAɨ& a LlwٖSEt%~x%~x#q@D${ljmb͓JjF@H#Jt)$phⴎLĽ͊`=^LSPy3xԘAr#BT*P]T8T-czek'P2W %*h'W1ԡW=WEAvn% \8 r *'dqGNuQh4M3Z{˭:r 344vJ *X\D*o7z<]\a`NhJ$DCY:Ը ia 5kر&AE$N@$J-ȕx)ʮ $4V$,fF0 F4ӄ nBh_YBDP96,K)b QE IDATE<k-2aɱ?fO/_+S#BE43u7luZ<pItdF857 _J|qJ-e![cMX`x $AJJf-.e*v14AYɇ^`Y$T2o9$t~nq2HQ0CǃEwQ;mF?@%#* Mr͐r֞[oqC uע8bf#&.*+w2)QhE[XL-&hnf+ltnGBkbmnLQD0/R!ZKj>pL@+4 ~T⌽{S- 0LSl)Iك;Wu SBWF+Qv+t?ۍ{.G#Z+tK,GبsN"Kp '`ĚVSN)D"a ^U1?@Zi 5:/Mމ`9Zl*V]}cڋ-ejA͔%0WPXnAB]D`ȸR"ilq~oJ&Z0_H0:/Qa躹ZU`}TZّg@ %B`n6|*L8T H@SuL(VA#[ l TrjrD0$텍DE'-|3"&\ёՈJ :9gq__o$Ľrp/۷|,[qCʭMq;ǍyŎ#\ļ&q8b X!(T[ǎ)rIUr.^Tzuo{A]!Z,DQ!lbR'q_m`nC9$D[4i}Z7^xcT̀NX\QrjcPv 3S=:[8Q" oUCT`%ب˶r5,])#Q<;mIBIM=2,L:=a4QM䚑-5߷A0ފ* `*1]\$ܻ/s]b ]SЎU+H8m-Fc* )!WD&\#j"^bBŋ_'H}`%,&3ޏG\F0n!ަD_qԳ3 ?% AS!sѧؘK1c鰮Q˝5S/KxJ xWϑ]dp-U5X"r?}WK_~x[x-?y8~GS| ̋@XwfCIkɲw@cImdX8l[%ħ<b(dYAYɪ]C$$y[C7qu`aiTZB3]_佐XQ)IBP&"!yﲨL-"$}|8b`i0mJ(T:d0^h e_q' H6dyWT IP 0̳e0ZrrSDz$Խ֌")'\S#!SVfs CU[]: ]L4J?tU)T} DĄgz>VEˆ$91c럃}0q$efQx91KU]uYח|pᷙNfjH9J$%ςdg-Mkܭ'Ze "0C5zvf60fhCE $E%'#š\@1 )ic[sE: eeF7@KqXU!竐` ӽ5P\|a#r:T jvV#n &2Qjg}z۷^}cwe^sIŶ6-VvxF|֮s5y ) 艨> p zȍ(9V8 P= w`d v ubLn{%!ߓl*4PBRA(u@_"% Iik,ovBh#0s6-mw*mXYNTJu 0%hK8^YtUKNGM;>6?si&n!u!Hl50i93J 3'+( r=EUBÝ}aeـ/Rf(33[Sf%*("Ѣ"aCWwH2 `[yG,m}XD>?3i4jB rH.wXua=wimhdYַrwڪHœ>cU @ ,Y?WKV>kJv!Mr@'~jܭ0P aIlؕ ,b; ++ʊRP׵Q_7ǀ گLdN@Ct?hɭ@mY0]To a>e ?ty~ D7>]ftVz߅*;zT0%LS?e m C-LUEDl>?i>.; 0lqطF&An OQn ցKD3 ƒ ] >txLD_A9X'+<]FĎcww)HES>I9[lJ pW=G]jw~ c8}58L՟HRBQ0>#sL:@mcg8b ή*qPJ!wmAzXg|,qhpԟR{< B\g8"FD%E@%*}߮ղYwVDKm ¦0P–A)ZfW KVnD*6x~wGg{7h}\PPmA3(Lo tuaa>+ {ЙqIH9F%^a!PdǞH;ߵ Bjn۷|D_3o&h k-^LMyT===ϐP͸>: BQ}LEXJ5wHڰ\&L=ecTUo*)6 uIa >.' H ~wQ5aU{q%4vĮX!b/}uY[tR{Mu l~QGIց#bH:jlf Dpz:o~\D"U"<\h#V0^cRHB腑 _!^. ٍz.vπ SnET" ] k^f#i [OD.Yлunze.iP~w0"n4o"~xKeCZF5B$E* OЯJ~ܟRUׯЂaf޲a||LSY 6LrKpU'ڤGM]13`y2Z 1r" `uGyE1Z1q~;|/ˀrn?.ID- L5醪< nԕ.X0h&^HWB%Q+\c 7kgLiHDs;;l)[9 zQJR W_;ƣ Ө)W1M8DD8_aȻ[ǂ;ЉzCm].)b6ɜuҴ_Yb'KEv#wCMHI= C @R,%*HTP#?E<V@~Clݎc-Pnk-"r?/EZ-Z URo K^$Y5ʜM2ϧq' c]T[:T'8 c1cu;VʩB޻̩H)ϻ÷/ '&\ȨM/p?%ޛFMٽV q ]p V\_x )9U&Te+@i: :(^jcPh}] Eи~;!&rDBq)-F-}Fh?zo%_/wY_`x!pm qϬj# cY鄺b<[1YTz:n7 *Mc,(5ogAe>qɐ(6X,-˨PS}Ay$58Ÿ22UW7!4rY9q:7JؠT-C+Om-jD_E FސJ[7*[>鍦8"a$~lJ_h:j'οDF9M-[%z*C-O՜2}& C.zWnUAP39.k Rd9ttX/aqIݺqt FU_=8i3BAQp Bh)BY [2$OK(m"0x(I)+D"2Ws6.}4Y <==fֵ&Jiev48Όy}Y + N2z f ,Cbf %8 yU~ǭ>0J/o]WA8%`f鷈}jC aۄI6ڨ| ^ݽq>NU1i 8qkq "\i lPXCM}h"pa@JX7Y-n2G z!f gh@5z`- 1'AbcaD LD }TCNMl,R@Vu6cMe ]aMfBc3$Gc5ج>aT J=쌸 ' x|<75=u1_)/?eЛ6O뮼j1pR]J\=`*?HlrőC[ŧ.rC7q%1I|@eahX jU/Q#dd D6QE{/;sIzk)/^Q"[b) $e2$zO/cVB΍(MP3W0>_T9E=t ()xQr Kys郩QJI?Zr +s(D,RUʀGC推Ȕal0Dq*E  + @x$W}իH7R@jf|,Dՙ{dvf`e҉T!Nk;ꮋj`&ʆ:>w\D&aPՈbTݏlt^ RsiD[0*{2 neJYuz9_vsuDZ׎SY2IcøRіC+jZB=Ur yhP+qHX4ǷAc;Hc6hݺ[͎2RyqmB3H ϒA}[ DCuJ0MGoc`uo1ugh1Dֵ!?sh``l Jh>\& !aVg@VμlۘU{Q0@f4WxK3CF& 'XCZ>U͎/s- dq{b90KyAe4-{EE 4S7g֥4%- ⺮V u El5z\4rN{#4 q~4hRM-{e'JJqN!m]n$QB)AͿk;iF@:$]؅ƹ.e`ģŝ'Q*<2Ȇ &ꌲJuS&)q.,uDnV[iX>If8Q`gTU,$/iBݸ bWj$L/*0j)70t\Rp)54"n"0Nm^*d&+[Z'R®8RRo,IrHOufg+J ҽ(+gG *Zc9\9[Ix[pErx35S*fqk]2br]X­2y hBl+M5Eރ T)#7KBPt7<7kż`͊LnmQm$:~=慄XFB)G#fpF" POmy5s*RD|iշ<̟aGA9.]A9R R3# yњdAb Jg=gK G`R1H-k-+-"OudkL*lyffeVXysIL]\\ @3@#zl[D,U1`8"wZ>ݬ6r,#YH\]gH_~&Yi$RU}kՔmU!K՗EDWb{q+jfX|АH7We+B*iO̗GR[]efEO}oSհ[CZ*xrgzc"R@S\#:Ȱ O)X̚>uu SCc*V@&v6 ~( ;:;UUz]P#TiQ|Ά梱`M? B{@u ""$WS THk4)?r;-3CO.xz%L?=7mx)Z`5&9[5V[Qf[juY\`bPa"{O/!K3utKo+P`x\9\%{7ܱCt $!hіrUiv^i[iJG}E}oz6m-0ؔp2IsL (Ưds'=wZCWJJKDe.'ܽ K;U{N|])$j(xQ8ޟ4x)P&-nrh+FѤmgBFx{{-WG"ȶ5 qA(1/Jb}T6Q3nz? ҋ;梓9/)H|o4K[h=H:ͭ8"YAۓMi.^8Š~8ծ肒8؈mP[FmyALnnIYǒeSrN þ~MKC, I(&6]X}uswl-[-)~9#K$l{oؾWٿ"$}G;t~蝜*VJWc8DGYQ^Duɕ~bf X*WUuhPSRza%}{;;rXLjXuE  XCV53{Zin(s5PS*W}1Lg8:-QʐWGj #,adSA)?UvpCu;uq1ˉg( ߚ>1%I!ipc$]XP[l):SX׍/Ŋ(,ǝ&oʄ#FP@GZ.]h*|;ojã:D:͔X9+~tEuQ_:@_w*+ r1Fyu g&˪Ϙ 6U*?E :NORrWXlڌ 7 KDZZ׵~ 3vJA; n4Ύ~Xk6D] |wszW/x%+omʲuk1t^+~֟ iT$Pe`WWC.LPa aؽ7r5:"vDHҧef[5וuK_Ӷ*}dc+ֳv ":ŠKnޢc1Ywx!++3诿1qzoW93 7t)eLVaajPQյ;r$dN>"aKL8/5Yx61/6`/3Q˒%+#"*"KWf{E7v.f fq[{_~/~OWԿ/=A,Baa4k"-@B vE\N> ~ XS Bo}XfHg)'jIqv؎ܳ345LA g7H7+Y5ut ۃè`(2NոD*u7kn*TW QR̛l Fz3+,ȨU0kPҰ 9"r\TvG9`i\ ]a3ڤ["Ѱ<ߠsyO Dcx#0|%'TU%CLԊ z{k2rg?4<|{heiؼ*&aBSLu4ڧT3M|Wd8u4<m[2!#Q=(+[;r`ޡEFl6Nx_:+cU&<S*1Ei-+{j+Gd#D%R$TG3q"ޣ 3)UD:(}DNp kqj-F|Q#NPQh7Lt!K fبGXD̰)lG֐oV^MM5w$(sB[Up3M9/pN`OPG& ۈT7ʓ$35>& ۘjoU1ȑJ@&Ң%L{%wU OtGG.|Zc4%l`MXá3+l^38:{loO! GgO2{DJ&]d[ uJLKV`nxJ$dk$ *754ªA RZڞ1^m>0Fջ_=AVN[`>o\x(zD'y RD&In-W}7,=Z΋~O_XW-,pyt‡+YH>&;"(Vh/j{x1h FJJCS. *ĽʳWdXMB-n[DrNcgtlC[> G do`$q]Vby+:gw 3f78j___?tE >3W7 3bXS\OSiCCd1onydff\-[PV}ޓ&Dɳjk٪zӛkQC?]\ 4zwN*O%S&I[!WI .ьpmSaQBCdBK ҨȽL{ϔ X8D*4݉ ]!{tZxO8=\I2T)G(Ti%@ZӷqVYMEnWϤpf x&Zl,eyyfN.avo*+V-'gޞBkC)~*f du9i V>-v,3(gʹCR^flw#BH@}1zC9M>_|w! #8NQ reQvq u߯5%+V_>LΘ#iȴ =Y<B ZKeya:vVm*-s+M{qVXI|pCn0PjV}N#?PXld PεԐC[-eLEêDȻ%7ñGw^\FW_ޏq8%hwW&QeƓ@`AN.W.s5-!:9*m$.c]zܗ)a[O2a L2& qگz8q%#vӞG2 Up̴uVA>)CqI|_C:q* LB0~%*<zbx&ǂ]n ! yg4vV4r!Z{hTF(S /)ZT ju*:RuK;K,^v4̪憚;SZW3{ *4]Cu"gƃT3}H9;yTɯL I0ר*%9}af۟eLW5LrTGž e†%p^TTHu0|64 % "I(U<7E aj,J|֌)HemzY6h^{{HʸGF.}k>=g|u Ϝ3Q|ky1{"ph8_7"Zkae-p;DL?FcɴPXn߻LFM<]5Lua䢡v k)b2. JȒzlWIAc^6.>|xi/h?`vEEdJrXiDu‰Rz]RQ8^ \׫/2V4xYuIOo +hXGPB';b8._˥dzyOVᛊwo7;*y(%'w^Pufcd䏽c-^@fzPv+-!ځ=').nfMH h2qJ^GgĪ !]V*%'8u@׵++]w B8 TZ|l-J{;"ߴ=Ze;[[0Lc>2ܽc'u-9] l35Jb$PGu[A״UW~gH۠ӕaݖ?''E=hȳ*MD~}{#$g *k 9_o޸>Lm:R:M!#\ԞƔ AH$,*>2ʟt@졋"t` Ij*~ l 7tSH# uV– xgOxq]:8f$iKrP2*rnל[P@H06<'sLT}ins IDAT:dv'f0Nr]"; 9M0?*ԩ#餑*-f{0Z m`}ztPue$Ro&Ծ m!8-wY4 s2]0xrmAJr<#_u]G*W# .7wNmu}}ZV]~rUFxGh`GwE}|K6&oBeoԼR _{|\2+(ãkTrkծ&LAsGXDY[-*/q}{wt .q>eho4V45cG# lYn@q?*.' 6PiL~K*QǗ!cQA_r界Ljr2 -kJpgmIM1Y ((um{kbRnbo<-Rv$%XY\71AG8F/By̎n 鼭ezSdd5AYU@MJ*xdN@pĎl 0Jv,n;$^7IW}LlU^a1 մ3z lɫ?+w_EyU$`񄴕AyQH E Bj㴊&.CHeY!`RqUYZ7}DT%4#z}qnb_h6R3]/ىX~d鷵Ro\Ŭud੩ThpӜē`]%T:j, / "Y4vaő )0KMf_hi!^闝[Ȱ@!O%Uh PXW9‚~$.[RT程Y"Xsͥq~(*tߤOw6٤>+x5(,0m8CpQ)"V0k9/T ~jtmkYD n$"Wu&"A;<%GćڢzջFaO2iOfu**e)>ASaw܋s[O Ygؼ٪) dou'p/`WŦzc:UVŦ53 ˧QtaÃg4'΃wDKtJy$謊>@0?txDkv&F]Jgm!}uP)P^uAMMv4EwYY'K p~Kx9 ̤3O5S,;i/!j҂4yf'~`*Ed ϥ!bliʒ4Dq+W1S+--Cj6M(_Yy9,$DҾ{~]{]_oI>+믿I^ oN89b/gV7i!uu-[cE?bf\̌j=%cWA0D4/̂024- lZ W5ٽu:D;ʉCGP,rS,+5BV^Z8F>Gٹ[mxKZsXqVD&z8g@iW*zT\`r.n >h=e".5^=9ȐZ_ \ ulVH^FC3%6ёu -BE_ʰiUy-t XyvMoq9malHܿǀ\Hn[&mxch[sm$JQ&R})8 RA4e뺮e]וџ4)썆ɕ3| { 7q=W h!5͉El(",2 J=GGO=סћ5`ribW Ks#%6=qf$Z <Lg-s1*,[W)Fڝ i+l~t P#Je"(1Py#55 ow\ZY _O#ᡋnP9]|'zdkƁ޽O%V2c &>+ݻw)$F@V] >{'PUJN Y? 2EmD0@oa݁Jx:ָ8!ˮPjSjy `}$&zثJC59΢ i!b6WDOJyuRñ<8{ͧC ӂWV{v3BrsҦ wmQ>G"?$y)%?+'S>ңgyR8F>{FzkT[J?Ći"21]`M2y~ZE0^i`D*Gْ?‚Uط.6Ŏ(`s 9W󽃕/U[g+e tzNe.oS 3lz,d:1Qv'Gzv.YUB2Ng*Yc#:G hHG#BlJXjf÷g-|\#8WYs|0,q]~`)Ve<:l/i}_|geSm }ܷ{AAtgy32=*Y(1S>M޾= Bs6P^5">[O5s7Y5K"i8(oBSxeV'lL`Z㪐Z~TU`CcV8n%M֬ϜhLd3.2ô|JXd}tLq.9t5f:e1z*V_}'$Ż'҂^d4=ҝ$ކ8:DjH r##7;ޠUVǟ4 `cwTJ!3h5;. MXy) D*P\ԲY=L)̾Ω!ۈI\Uzf7[U6kD自.S7 c#]z~_է+OS%nX;s N:P|ze'}]WqrVcn{ѥY[JI*cXW*PuYcs.Ǹ9#5q* =Q8."ir/ %M,&dde&L&[gRMQ`Uàs|G+^Vڛ Dss(r6)sϤb0D=UDifUD~`2vi XŒCB&)xSa ς9{̈ n)to˱B׳_G?+ݻwsNj>.1pwx~wv?NUnw2 Kxv*Z?Y=Y[`1}E͊ #9\ ,Od&)AmizWk]rr5HA; J3%d߬1U:[Sy|>'`|Hc+ϔ1By3dF8E|oA9y[o$N-Ezغ4)U:_ DgcZ%YZp`fwxCTljIKMȥc yA [;^\qƦj˞ux('>a:4&;8]D;fN[8b զ\=2ج_:|r+D){&^|'Ƃ5Z #Q'meQR5s ʩ B{Vi9lFNG_xG?+U"j^sQ; st"P"0͛?, ޶.ETm;Ob=3}D«u=˰\MAK$J'; JZ=?@5s`DEMU`ZɆ9d>z$!(>[qB)o&M=QH>!'εL0ِ6 }rtAIΈƧ+<#?{D edaSn" h 84c(!GO̓Z6,ؤz@}mO*f鱤diȃ\o^QhiBM/]o-l! Y:aZt6??cO_Q޽9IEi)).{B [_Y}HGAarPf#"01yE!&FwI4b\DCZ֐ .=_[?=E!wr8,`lޖs\^4fH`wJwefKFgewW;(9 $} &zέ\Th{BELSFa^whu=_of"A $Wd%gi7<z&JܨJ硸|hF6Ӭy}}WtEww,Q8g`4Ey>Jyw$߂Ʋ^r=R6Z~ճg0)MuSO\v\QC)ȸeW%[5k]kY]mT2l_i`ўǙUQ)?ϴqA1t HdN +:)'z:D+cG^vy7Os ¶(Νg9B2N$irΣ95~?tn(!&KW=Dmg ҔcjkR~,?b.J7`M8ɐѹ5|>&K(8Y''"@l4|@5{XEfZzguAj}GNFm=>D#qtQ]]e9W:7tMT`).t3/fĵAhwPuOg?hq^L/e5Q 9z&"*e$p5|Fu xtXW+-+kecJV O/KBH"N\ IDATn&GۨMiIs)#E=T"L( [I9Yh 琴a2Ls5pS[2?\SsESLIn(>7JCrJyGE*&h<-0eJdABm̴>Ie@y}}~iӟ]t4FlH? :K96xDT7>~& 4?6d{bgR`86 aE3*KtZ.'s°6aE`gr@+ @e s`lܓmB55u?FH{T8ޔGȔcɧ[F^BI<Ա,~B"}z:LJD<^F\8P0$:z{e<׌X:!q$"yv/[nmoJNH;Z`4yD=@ѥ2q̠BS@E vr#0`ގAʹ:Zo(<{Q"mf]y2[jT:>Z;p7<{/~?OWUq."%XF:x户5O>@U8h005hf\K%5} ny&=+]n5DS샷MBxuuu3PwE2tl-`18fASs*xqn9HXK8\>AfC{8m҅WdJI衿mxPtM̕V~?u2֮H\*,Î˽ .ŠXa ؑs?4#?pJ|ja;`7Q#ܱLN$D'_%6@`f!ā}`,[V@,Ћίif'҅,^֧hS59嫪~wէ+뫜 iy\]oU3wikP&Y0֟ҖnrںTڱ2|GZ!JuR,rWLW| s X8HǢɤ7LD5Vvӆ5K4zU$\L-GXEReB[w H @0ք $|srV2 6ަo_[X)PP-qMH$Eaݭ`TjFY:=C >eJr'BקzMIPs#Hɶjbku~iИ#^PwXW"a}sG^2XVgrdPw%fJ*1Yx9Rf$W{ Gb@nOj%Ps:&Ųj~TE~\21`4hbm0$4194AP2sXLQ\B{i=ńG$}4kEx':"K+c}7zxx\uν✃@4Q*Aiﶆu[G1ZԡCAqZ^ݖ}E %ұHMid"`D)Q(k"%n gRS6̏ʽ()jaG9Vm[t{ysJ=&Mu5|H.mz5ؖCEJ8INJ3f:7sM"E9^S! }fXMa[#Uǫ`6l#*p~o< 6#>Gj1hL{qމzxt$}L*yK2TuT!q}%YS򜨬( #tHzoPukOX=%?8R9|$Cڦ={Ҥ m&t,ݰh@P?5. #p? 8ME'*h7L9>Hˣ;>F·'wicc2,`?c=]Cu# "TgpyxPn ܱ*⺓TtȰ_A07;'A z>lX})(XŬhG|OճjyJNBĖzWm'I .*6>A=+PrPT4@>G{}ЁE|20ٵ_pe'="-2G -y\+X~uqc0#ڰC| %c%\4ұ 6(c3 h{&*plB* B*沁lb~Q=cjJ$*t6qb3ۈ E#LG̜ OV_Eqʩ1B8I`3OmMY x۴vl*+cۖTδtG#Oԝ~{,v#%xYj$k)DZ8TD~/ϧ+QwYJ MY`#c8&.WI&ŴD1KJ Ё0&QȸJS]kef̀I3gR NڸL MAv Y5$ TLU2q@9L R9 )7Eݩ%sg{-a@C$fİjAlf+cykӽL8%cZp-d2*byfAtnkQCąa!U m1kKg~qFѮtPpVh71C ,bQF(C3K%dbz1Uhv4w(YVENעu3) Dq8 E2Uz$OJIyx`rO3@ { aH<Z]x5pzƊIEsϘ#2b6't'ĨAG“2Y' E_?BgGYw"SX+6D&>[!XAm ˅e}scTVy1zey'`{!!wC3z{ .4bm-/T?]Qoo!L'[Zq5AmԇX{KCt Lb$T  r΀yoЋ?J1œ!`!*TƠn9RpT ʹI_bN ʣ:khv*PJa:E6uk *lA`vDM.K(y,dj^8zWG+&=?^穚%3쥛!I{n5I 7sEm,|C)}˔aEr;nTsCgck?u>?~ '͡`l{;劶RJ" g zk !g} `+ ~A4X1f'aEedWӮocRb^%|i!RsuQ$HDx]`UdMcsj 6e1깢,qi$qū? ќMAjJᗨCN83V-Ruj}j0(y)N{AΩI%qnF4=k?*4:bc`-v&DմE* SDbʴJS~6,HN`VqU9}#Sp4\LTP`Z$Ȫ'AlYO#cn_!II|Stw}|ĨbrM8O9):wfnvU[v;R3ؕ0,:ѱrWmAEz\>4ѝ".h[G^$|-[*pa+KQfVΧGJ'\>h k#.hi5>Md*eŽUf~J#6Dҗq)b.glG2FVY,_iRBqMH>.=5*Dy%V/z<^:8:UJ#rM-#sX$FIC<$j]n]=]퉐NU!U+b.yq+t0K H"*`B&fVn>6J@E![*\%rbmP}\}ֲ S$Zx @3%Md:i$@/(*8eK(Op \>Gԉ$y5I=,oKc6ej85*S{#J}B:9WAO">\`oYsh؜]V1X&K!%bXp2bP7DFCQ¹[l)U4 XUfbK&*-N;+sۗ!B[+%DaM5>̱*ua9/ǁfVCޯ4U80JN5ٺ&cIc6`FRP#m$|+j4u4s;طқh´Ϥ:Uuٲ>zsrtKnD}wsEQ1`nƉM5J+7 *zwHQ t-O-UͬvcPIlӹ;Z9QioCQk \&* |c/y{N؊f1/17Su1s9Շ`ǰDž*ڥ>,=tW+w ~DIW?u;I0!]'"(!$3Z.6%S燑J #~w^Í+1{ zё0] G<=FpDt $'UO>(Hh<)VN-8[ʪh# [jzwӠ︃ VKГ:K|`Hmu]BX0U}y>],qwwC=FGB-ԑt)U}AH>L6s'[!,zj.u{ pleɨS:ɇ2FA@}_صzBQ1,J}Zz2?D{:0QE^^^~__%~#G4R=Ղc֙S]aR0ә04N(&gu{k+Ѿ:su<$J3Q!S(T MsDHe`z(.z6A)8$ 1%WQ*&>?;Ωfg-W#TCW1X+"-_M`L I`0;2p_1SMMz W˃zy9Hby'Z`@Pv@ Q-L af#z2"Lj~ ZJ4bv#F DqLlX_uֲֺRx8HXÛ'm,1ϙ[W}~O~}8V~BJ`LnX#u]uXjEQU,y5F۞dG+>bwny'BgCB=faa.s=(@(s,N6B|g\ \H7lPQGbq>Cȋj%*_ZK5  2H.)D[9rd>``1+BCcG'#gC1qT%=%|f As2.Dѥr2`gunABZͮ]KVapTyD;G}~TSK:f#gpzZvia'F!ṓ UX:.nU9^[s׿HŌOKK; t$c")M%gaeBJVU"16iްe(Zl^`"^ED$`"HT$ؤ١0)&puaה'҃fI 9H*{]}Ӥq.&&#]HWOma5nF'@+!UJ!{|HQPd̐xZP|fmoD?Vʮ8|1qT΁ 5 WD 65 Y-X!W!ve!5@}i}>1)jJhLRǝD1 Oq)zaRlqI2*ẊDӸF}h* A (Zi**JL6d9+(W@*UkZddܸDE16#~a Q=)TꤨVuiBjD-^P[%'n" ^ գ%KpTM* FGk{@FuWKy-ôVyJm9-)P᷿3k a(Ӡ(f3ǃ4-Q`# Ӧؾ"ŏo"BlmPTJ&*S"&VCbߐ5gb6;SlEQORY ҿfVy(UI5+l1j mDCw|.枞 (5TnΈzՔ0i^|`iP@"WqVTAkŠ @GA~چǽХ-Gz^b/x}rȌ$Ezv H5Rݸ'"FitǏ+epu&}dﰴk8Ї-A,4QؙȀApeX*CGG(Yx~nkHC>Z_~ܳ|EA hu %a?:^z͕H$EyRJ@BDI"7R{; Pl?-+ATkU1:'FHvX?oI!s/t"HHFmjSGh `'!ډ)ha}jyQ?Qt!1^9U7$(åpi;JUT2j͐.dMg \V#G)n3X,E$0z hϣKW!p!^h8*
    tZEE.<%`=sVEV*l |SoHD6Ȧu~+@=c"8R?[x)`F.#6 U 'Zw*[ދh*mQdQh"I^\5Y4N;k/FuC8,@u]ׅDUD퉈HIJ4O)HdUT/3ldzv[ 9oۦnN0N@#i4ćn7wos*8ІO3O~x\noo=y1:rY7gϞ;X(<$͝QRK V-MQ?{4T$+21#+3"c8=Mv*0ZBݺ*q2yd.o)5Iyl,fJ[50᪋///?)U1.T򧟾z Y^NĠ\30RBi%~.̡kڟO5M|ղ>TEpdmw=yhdD`MH@@\GA3"9!2w}D9gEd[\D7ombGH{mV0ciL<4oTP-)Sh"n;ʈWR bx}nSP&$&en2THR 8o+Rs.R EB(/>= BnLLH!Ķmۦa, *_E{ <HMNm@mcrQ]Hv:#2v/㨵GEȡ:͞(r:|&DQުj)d[]{ B?ׯ_p!bz~xt=y/b4 !±Y d[+`' }q32&/R߳0 eo,jUiualkEd>ژB۴))F}2ۈFT'Vu* 41V3> ( &⳧b@5v,<뻴LJ@4.uU86{D\lV5N-,"~'eM͛ysQPǞJtoϕqi:t9NӴSN%db%13˂B1[jben'/Rr5b>Լj6Kd vUID#`[bW>?pz~Y_{6;b(C`^RꚎ<7~yJ)Ȍ'UD6Sw5̤]v103@Keјu^fY T'z.z&HTqZׅϗ󚒅=R9JD|,`Z@H:@8Z$ ~2j10`c 1wmGYV{"l@EIR.<'Хsh3"r~mhVeM˙i̥D̠ir)eY pFeTd4:E攖iZ\>Fpi C~<Ѕy"MlrΠm 4I)H(PJ)?ܿ3ː?6Е ̮*w-9_4O9gCnToPodHr:e]WU)֙m{Uu9B"UNW U!7 ϸ*.prLBL@hbKR s@|eN7;l?blڶ cM=}7ƿ?z<5Q-38rn#6ne6-ug-c Hӊ)jZ"m'ܢ7kI9@LDRM$(˼,i!gU- AERJ9ˏ77벦RJcĮ#sV=-ݱRXSw2b 6NE-2_my$%VK%aͨGD4ӼԼ*6SEL9qrPO)犛t^q=>HHB!#?G1eG(vnZ܈ D iSZyETsR:hEEۦC3/|FB:??ql#!)ff]AT4Ih"VF=LOp6M1Dfd~q:9Qńș٨*&h$2w b PĠ@jz.Չ6Uf H'2 k* ִ *%jpsV۲kZ50s`Br~~]= {3SzᨊҒ9-Jg?àrF4pнu~z|>={} )t{{i^xBG/߾}+r{{(jLBRp[B1}ɲ"ZTUn8hdq9\ru 1ebx@0OԞ__7SM]CMݵmRzxxɓ'T5F[3N {dI^uhx& D^!*2c IݺRycTD,ؽ碪+$p}k3J.r xIijo˯d63lus4\gHUJ9Nyt}GdB -̒QӚ~lt%e]94nW@ E޾y ?72Y ͭz dyзcȊQ0-i,2%eߦР$[E3hD9s`E%`Uwb3%E&KF-YkC@:!rH]5n,p$꬯-t%n"6DLg/`xg|sbq)!~G#1YBPl' %>6#,Ev@LHEMʪH.Bu+i..RRiK\T,-S@՜ T"i҄x~MIR@ Iyً緷wߘet.Ϟ>!;|]Y+l nnrvW>]F׶*Z6T.rp@;mH:$p꺶뺶m>Wcl޽ycǮ뺦mb|#B|8SI˴n0XJbBuWͮU߾2S o8~C DSZerJCڦ#'#T޶@C@xK-,R<{Mc$?XT_7%Q0332K)|+o 6I5:^ɶ#4R}]N?o)(!5Mmۅ@Lv- t{{۵*$p󏔬%Z!)͙gAՒnH!Dj?B-@Dh+5a;*xFg$)9 Yb*QH_DGe\[*)]˳g/nno۶PVRԄTDCɓLJ遈x 1[7qٓH!x:ݻ7c݃Qz<`#p !2DcNi YfMk:%\K1UuS=`u=4˲%+EypdxdY>q30ẓ!2"rW gƞzyN;6' :g#jTE {:D,96_{}ڦivp0v]ף;m;+/s%B y֥Jk1H=إ GT 얯=aBʽRTEuußlj@)\ƑiIX1(tn1sDBfVc<ڴmErQ]b_<O`C"z8 "T2GLmͪ׏w͹Shb!q2iu]K) O)G#vx7uI%oT)R([6MhMijҔzx·3M#+ZJbd h^{%!&j|CW_Ž>/|fqic:5 HDUeFsqZy'a벑  `䒙CJJ9g1T䜛p R$ y捈MtYuMb!b5 ֱKhBΙB16}騠Y2Qq=sw0HD-P 2t+rMogYhha @05"BR60!)* FV|&n'_DJ) *@q6uÑXS#b!"EK$? ;pz^_?e"cm uݰab2V+^A ~YX)ӮnZWyr1f  nK:#¢kDM`ԏH 0Ҹ Z(fl$"P:8kj+DPz܊ 16m] 8\rNm8 CthI90mTH૛`-y&J  BjcY{XjU-21{=V IE dN bHmE6тCʩRtKBG<WWDs c~>~<1ûT8=<Ħmc{ >y~\v: )hg?IPd@ d*QT?TR7bXȸ@JŌi]Ѳ,"vmWd涣!6͓.4.i}iw߷'g!T !rYr/'1aEI'0eJrdkiGaB.9{\mHKjΞA=Nާ;m7?lyR콗-y\ *)1=u0OS۵6v~7몔Cm뫑ܵMLgMaV.wLi9`u˾.%݅Ռs"׎XA^~;GȏfUQ)41ӶFfMZͥO' qY_~?Qq B 鸮ɖ~*[T*PMlB HֵiZ":_ΛjRUr*!p ngk}rP*\X7*S2 Dz(4m3 ;OӴ,Kb]ۅA,ðYIiC(eլ|>O~hHLQ\e#"i)_䉽np~"ҼLφ' ѢP7e^'lMwUQ;=7mx2Axy;g%L!%P'jRu777mCq?l~ښB<֌~E6t;7^2KC!'*%!Tgb_:`\,"U6PPyHDwvr#&J|\ЋiTu*%ٝ)RDf7R%D:/S-j 励 V5NkW/]ek MӴxL"f!&߿}K!H?6FҸt:>S-ԣ&~=Ujjfs*Q]/,"َirUvm׿tM{ RxIC GkY6g֩߇&F} yй7 C!@}?֗vXs?ץެ0݄ )SrZ=M˔ay62UUV !f=QҾmf8MqI{Ϳ8q/NOW晷պi DMUUjH"@xj UٮQN6*D`HYMDP*Mb"S:%)R:9I8xNu3ϫ aaY9M8pmL6 TnI  ,+o777WIϳř|Z&`qn@ 6!Km"(Xpn Ѿ`D)дn./~':թm<[q_l*CJ0w)>ZcqjB!B-Y, x<裂E4"eOI<( YGDD$`YROI4,؞7*l*LP6V, X} ltjxUrΓO]̪^ bh.@@n]w8M`&lQб#{NO@S g.<,*-@Un(;woRS,l_[@yBpɰ4s" mh/ӂ,<<]`u~&DDg_#KjMhE[1Pzyn~rI 3rB\Ofe9x _ 7f,b; O;,aϒ)PQD?0^1v򔆮y楋2)ivV~<"kq~Ri9H8t]׏S8cN$)ѵDP=孖Ɯ H!D ?+'nnҳ6ɴe5dB{"E3\_7-,l ٶ/YjPf^xo=^k_}މf^m].?7/71=| 0,K%`fVq#,$25 n<@k:[qrڞTa8jGnn1軮:D *#ࢵT(eߋMa}!UYdYMzV(purzzqsfhvU0 ̸|wۻbj,Ag&7I:BNB2 O9V"!++h۪꺦@8}/ӣ03HDeXiXUEb 8`γuMіiu-0lu68Ћ_J$?hCK10o;2 i¬+',=Ha_gLm)}Ṅ>X5VSKl}OVelVj."~'>xg?z1C'S23ɉveKu ƀ,v;"u=KxJ3of"2a syuqw{oW_yϟGm5fa] }?'1^U[Z/6xw߶mUF?Y MQS 1ƪza4a! nW^?{E꺪k&#ю{ͦՇ5[f,xt(̱Xb 1~riƱ͜9_>ܑZX^Ejr,ʬQPR/IDgZV|f k,C8#f^b IkClE#5[*ͅUU1sRXubf?~3$g o<|qzH |rE6UU=zû?z\Ya*oƖhV!8):$qi0UC*`/T\L@.!9.)!L)?}'ڦ^z" o/K!H? 4w+F`0MG.\?hf?q}e}z bڰuS}7MaWjS׵(ڙw oߢ%@g~G}z%{׿+PfETUU`Ӵ?O(QU$fd&MU͜-cP H37P'}>+(F`$HS$Ul^>>} )343=~>JdYބ(Ǯ!FĂAV9+YzbM?=#$B؁k:ׄ4u}QD4fCh|xwt'" 2H&wmBٓ'h9gOיA֎(f 1mk"ԴldKl69#noc1ni7#;DXk2碬7@i(\,X}3&23 [o/g唋w@ ;B#(Q OY/!,6.6 uG$\o.C4}q|jt k[dcU fo0Rh{<ﮄ(rNْ!* rVn RaƜ2XW8Ns Jj&1Y zP ੜ,|ϯ*x(kw @`3h~$( G;;p؋ C/Ÿ]8]pR~r O޵Sĺכ~6O91"!UuBދ?Sgaj^-ڶ@X!"#ô*,65SBZ,x "iDPn|jqIb3qLS8 kʸ]/mPc|}yYU Ӑ4 cHqaxpxͨ ]OHbK,uGlfXXĬk4+,(kU>95u3ow[U꺪+SsƅT1TӉ-dB1.wG2aGFWņ4w)x8h 1bǩ9saq l A*򦝪fDK2_ |E2W0@2o'dQ&yॲRXPR*[ cStʷq%(>Ց_,Deல?XDYrt݅g^Ԟ_+]-M6XՍq.&Q3?6M{|ǩ銔>~LZ*G1PL V 3߾ޔ=0NSXXbl}^y?w?WW劐qL9qL𤧱:ͤ@atBRqBGyY֫UJ|ssi }\]0N9ˋzBԢ>]?dJ IDAT:>" ~ZE >e\<%'1Fy}q $l[<C)RTx ؆iڗ-:PQ1ʔ!AMꍳKhgyqӔ8ǨvE'Wꍈ(l F=!LVOX$ j8<UsξyBH%yYM^Jb\,EYPHȖʅV',I>\79#Sb'U$T%ۖy6Z+= 䑜(*2?DD`D !,D88CΓQX5rNJmDC1Daa N#,j#Dyt襱=+hF~Mсk' Bz@5cRgp=+H* (}(0e~ESs"Ggh嵥D6#"PQ4**b6ĉBB1*Q nGd>DAbhOrQ~R{ S$KAMQ%$yRJ9؜H]#Ҥ"QCB$F5KJ"C Q॔˘墁SΝI%fIJa$œe?ĪvzۣR^;[C. )$ 9ZRʧX?I?Zh#G4PͲ ,SvǏUUmHFۻ(wλg_?/ETUrhmtE4Fk!Hqt]XV˫}umhcrzZ?})<94MZVUSVvaNg K0Ġ l7 b*+-9$#z_*I@銖aYQEYA僻re'"zu#1Ryua^_7bu]1B Z7C~^B\\l./qwa͇#...ambZ,,r PkL>>WQ=Q0"DdS TœS>a'3XUOF$ F)rp}o.%ݒBE20{9 +&RӔ$'-=GJ3nCU$hP'Ue.9 Wc91C#)aGe֧`NN*VT n4 @HVY3sZAQ\o[ jjVg)c5eߧ2gjWQ$j3 C),<OԠܴ \w_Ԟ?Wl4ֶfuez)cY 8vz٬ə!RX-WvԶmc\o>w7ѣG{Η97XAiE{0)2< Y\OP̒ *N̔x/=n&̎ s^q~ڃc2ԅ8uS wqM/1`<3!*151H5MR8} 49 gV;E>fp$8Ŕ0 iadU,&ߘf%}v B[L~#vh8~9a@7fGa% H0"ѥ0 sq UJ7 (,iexQp}yJtc8+JRLŁ`@ EfNs3 @P}׽=X:i8i.K#TDf~ZÒ=".W+u9WJ*̓!>|j?ݳp4j2|`@qќ}L Vdഄ/v乨L$X|QXϔ*tqCBpժ-9]gX!~GGu]v~mۦv1p:et̙g|Uc93s$Bd_ˉ 9 Hc,Y$ UUAH$RY'άaR9qƦYP+ѣ.YUcvQf4z`)?{ς-%4yNeD4f]0e0S 0k sQAh{zZq l4<O(gVBXl ұP4_zWff}Y&#Cdb"MdNE`wCu4yXWë< 9..../'wwwφ~XmYEcT…|"$"4B@i2v|3{@ߍ"*~n0ٳy,DZ+sa<֛Mztaι.V!Ɣi Bht#*kUEmVgqPŚ?[waпۮ\2YYqtHrz` 6u$~pIjzk_3V; 񕠰9΋_q<\)CUƱi<쩃C>\Ɇ=}*A{2\-{l19 D˚D خSoh3~{}}@b>^"Ƌ+<ܶ\"VцjY/6>Wênz=R{ꕗ_ Bu>ӧy۶mۦq|Q+((eֲJ>A#!):(άc3|-h6!Dqa="k㑳|s.K1 )?y;7mH1|譢Ų].8ظ֏RWf[+'P`? tW.X,s5@1(TKbAP=A!YbNy!DQ[1b%,Cwa3"uTnoER1F8_KbG󜁎6s7RƪXU\]ilt+g.,Ū껞Um_w |s:;J rYN9f\be &RNC^)Lc#J[U1V+8v P,P__Wu׫բm"Ū~B.// 0l7j4};u]}7;ϼꫯ<h Onw{{aX,W)RgMxwU1b8($37*Zځ-')Bi{8Ms]Ϝ^}v-0 R~Z5U=8*i]XY D0|G\ d?JGFXLy"yoϻ\Û6!ĪXAT'9gIǮq3h몮JTb]!E]0 Of2SB n,(kgҶ>_ĥ?,U[,SwliATH1pnj{JB!]c8M*˹ /NcdynP@xW<;O9iT4M,i릙9apw*Ǯ[H4_|qz_e1n֫jceWW|?~,z┦n/EUEjwC{{/|ӧDS@\VzٳOm۶14Ead,UQz@:8N,몪)'CiʒmDu@wݱmjUYK>\g˂+g`LMTjX@PZ΁H"Yf8.@Rˣ"Ejb ? Oin>PZ?Ws)O (*qw8N"V R_z dZ2<5c9e$gN k"@.r= .u x⑙6uUf ղs=NhL) C? 9sJ bUnz6B5VB'zxNP&*D=Qd}?].L Lei}2_]^SOJΌaѶ~Gr[ z2ʃs60 > eqai NVdo DpRc&b}M*2S-NtZPXe)(Hsӎh9Ó@4LΧxL9n~{. > Mj,y=oxl|F DbU+N!R#Ds/<* e<j UrVK33%P4 ݒxa(ޤ/8)kU6PSuy&pǪe QEY@v*3WTd1X*OOYxhTj\ d~p\.n>kW\~w\6 |;Й6GE {|ϼ&}5.FXVŲUJMӊdh`"B0B_|uZ.~7Mc]UơozMxD,ۛvxc,rv*Y5DCFkA~ħ*V2h{RX6PCvKAB!AVGy];gQr1 CfQ՜, b^xӴ!")1+ R 0MS\u;np3ÞtIb|Zy|x.Ot] , 3wgyH, jn']KIDn~~lx #9,cG+(KʼnݩP̍@5 "@"KƂx <={?!f֤E3_-JrBPBdijZm]T %1ȎZEsD"}L,x)'O3i1'ªʜfc{fE*ݒs)pA@]j g1ό],81 }1p2{²ŵ }Ǣ0kUU,76b)'l6"45?6tG"zS›?яW6YD"ŢN\8WD$b^E[>v>enBxӿC1/ޛ4wwu6mkP@EGS!Pi|^yb ",ݶiڭeo !X..) ';;D\.U]g)e TD`"(RUqL`9X"GavUږXXWsltc"m߃I6몑X :匡iJ)KT Dʜ8' J =fX t^JQtv  }" Xe3+  b3"Mq ?h,C\U0ӷJfߐ+ aj뺊n P5SrDޱ4KO<lBNbqT?|bJD8xxŅuCZi =#xL`P5&\ϙQd"uRH0ZZ$@ *POZCH D%^OjH\/2VY<+Ҁ+94%qq~ôa0OKDxy) C+gf0?O`Ns5+j C֭ՒqC (}S8v2=1Ά= Q#Ījm2ClH/\D4m}94}. @[hq:tiUUUc!R8 ó#9*p;Ûb+2og**cNd< II=?E}><˳4Yȓ9" 5#xgNr|xAHGU"d6|,Ԝ5|՟s)':cμX*'&)yZgI9j[T1&&H@̫'fAB✻^̦xe?Չ(6MUnw;rË.?y71E}yZ]==m _‹O2گ=4McUE9G3daZgzr6k,7-Ж" *"ۛ!MӔnovUu^/(fqi*?GZh0 5Yx; OD~!pH=zX.>hT?Lj\cEC*a@eqVøn~m*k d|CgQU FDrx:4XHyg>u1<`b94NnsbjiɜmPXU!:YȿFtVXn%B y'm-U@$IBCq@͓+@< .u:E\i!Í1p)'O$,c_c0NuPHRa0# `ŨbݺHȒTX$,z^^\,.W/7^}>|xٶǻ݋mzңGP(wh;xsrZXUPzuqi=;kS(B&Z[=!xIOFnJqkYۛ?s?;y(YJG݈ؒ PD tB%/0ᮥw/?}ެB ʬ~~*^^\vuUY 6.$~.۶n{ꪮ+m2?U,MU1.2|䔃cWQyޜ>P\zMR,)q4qf[ sn4M)qe93ݼd !II/̜!9KòVlx**aZjiu \>HKDTtN,"@&J=iYZ\z| )zaZMM^Huvme@L$,*^)(Z*r1PWmE۬㫟_/MS; ٳeq旙k9U  A'P$(J-Iu;l,w?~pa˭n)Β[p-$P@U3Z\ksAtX/M @[Νh|>;SΑ7mw;wn7Mpuv><<\,恤($W^}ݷёsE=n}Q-'j_R!KK诒TYD>O} As1s6!k= 6yՊI3#@h仾 ~&o@6(HGNۍE$h mtGZG4Q4{HF& R5,D~K(,2CۮEՕDJ*k?M-ym3fʙ͊cUܓS$r6&\!99!6iD[ˏN^>ijE="+BȔ(!ѶSZ>n&v䚝^n;CYAR#(19O{@;LWV\@:{0[$ ޅP&٭ӣ}>O|5/].12/]޺w[OkOOWϿ>>9lۖ-fӏ1y)|i@ R޼,`D]C)ˇꩱm+\U%ó'#rymta7#g] " : C?jy98ܻv1Ȇ@:87}CBl^Uy-:e!m!iLie=i!XTs!6+9g,#r齏)sn߱avF *:p N$suɜg/lYaH@- XIqI~c)gJ[15Su0z?rKg{fG=2S;np뉩1P3 XTeiNie+p>Iɲ-z0Rp lJ9}9[1-&-J4_`-1 w˳f]^Αm|n:C@w0僇Wǔ2ܺ5{G1]ݽ @wjݏ^s͚̜m㜳|>GXtJ\d_" gnpY"辍$ɘۆj}~~ziiۏ=4 :h0xyqtݣ>7h:q8C_,y1~d6J5/P^@"΂q/ ZrMg7Ӭr2 9v']qsyĊ EmEsvw]fd:$T]ó8)[oXD78Dz5]8)3 }H1EvkӲN{`"#*,'{MrMDpk.YݑUI'1gy`p}x1%r}ÃY?6UϿՏSO>&̺f(UH+͙^rKhڮ5ƻq*!j-"; ^]Ojvwڦc42 dQUȹp_T5G>~nw?P| {fI1&Βyo~nUUqӓn|uJqMhk:f!4W ,K]KyHq6Mڮ+Fjt$UJmUUAʢjկW;]YDccȫw//0c<\,acB$]b8%/N닢)>|n5N4ܓ+Utf">wHj&]ِ.X\ tjQmH,?Fm %Ͼ)CT.י 'jۡ0[ٔCaQK$=N*ˋl7koaJmif]{֑uW{o‹v;HDcO>q~ɿ΃SP=0/zЪ*C҅kZo/^p & P(4SR ~)䅘~ӟ_5@i0P~1fAۏ|:>8p?yhfn&<ƾ"V //gyƞD^t6Q^]^:!LicJASr&G$ιilvrzkcMSu'5!66ce(%n-RB3n!8!`^6eX%|5͡_,9,1 8ca&O-eYXo +ж)TcæPuB.Q (e*uGiS۝u!ٜX#Y0;Y֔s+vE_}!}i{ ee'(+y*9UN #!Yavw4n_zXVa1 !~Ν/7+&f1o'FtG3Gx0.G Xy+b)ѣ@d|_̭.-(Z~-Uy'W~άs6*n7[D|~[:!5M8{q o{v[s槟~mZa fs7 }yZ]?\yG.s)zٓVX0%@ (LC?ϧyvŨKw#RZ"@quy,,*2h䈼'R9aP΋#anBƸ8rJ9n֫`6\I 4 i086&ʯ2&D;WIꤔ11TԬBof {TV0 ]Ve +(pkM>❇wUri dG͜19sa&HS,+r9n7EV U@u`o ^7˿_.8Yw]3/߼`#Yswi5m8=9xޙ*#j}^|c}c>k7s1jnY;?u*fc SR}nΑAdDwzX ?oӓbIPXV"s,"so,YT8$;98# 2.Q2)ٞdb78ۮ)qf\挅 /1* 8&]Ky}iQfrC>~Je4v3f%TgXBg!ky\YTJ}9 a5M9;6^)pQBDKM2o&)º "x8ózZm~1뚫hn=THOͯ{NWWm?0lCh9KB^xg}}ܚ1V,f)(f~9!m#!('w~A_x QWDrX ȡ]9591>9>" .WPUq9Z)Eq"׫'mS2]DΦvEtF Ęx8BZ1]ۍmY. ]Ӛ3T EEXCۍc:SМ}, &H>}h;wEfymN &vXKR !`[LyTEyz r6S &qk:KM(()h\vz'ϒ=wSVl d9Qi*~5l4EN4besRBM=afSYh8ݿYTh |͚`\+7gj!vDWI2ibg˜z]j^0g>>^49#`ʿ߫' z97!x}MӤ_x^|c~#,v2$i6krȍ`Ӵ <dudնm/.Ϸ !~S}L"^fʘƴI)1\_D[Çw4s4 }?=„̛͊o߾僷jD!U Yot IDAT{{ulfP#`bWE_)?FTlg]Ec0$)Yْ36M4G#s,Gs,[" l]ǔ1+gOl7HlBCNrw]-bCʛ汲'a $ |'2Nā蔌䛐wP nTrOԵja!%c f$EU%)b|"'9) o\qT[caû X}5<,[#1Ӿ1la1&x!b1_/zﻮuN_}vX^^ZQΣ@UɅaxo`H9qӄj8qqlVjK9#@0 Uٜbޮ=<8f3|6-2:XcVFO 7^!tyo)ZWLBDR4pF]h;Lw7TQt"1Ŧiǘ׫qc?8*Y(M4hf&%̙чY\]sLClvyTR!E =8z*=\톱95փ 6Qw ߅P%fy۶|޾}^8zӴo霼wM9ꯗK{~|'E%8Wj 'rSu*$N3y+ E;ē#kRp>텧 q\^^s3Q'I WX =lSjXN sjy5=cY6z19&A\$WN/P5$^ 9#bObT4r@XO݀(!嘵`G@ӑN v/jCPZPQIveX{ raQ 7R&l(+™ux`PbprNq9b+JR@Bԝ J#LDSV (n6/] `9?_z7ή\e>O~p(-Y~;E҅[#pfua)s(q}Co=ֶ]+Ѣ =XU4M8+%VgSIEMsLsSX \N/.T4 ;׫sJ|8lhG~FhA7I 1UuݮɅA^5/jD!P"U7DyA87]n1] #y^|/W WfC餎@Msj$-$0Vl!XF]H # 0ݑ˒KL1`qA[M?1;+N͋ Q۶*i|oTnƮo sКʇx"-[5 DDD1SVu[עY?|>#r'')˫ի/9g fUs)iOܞu! ?rGggy>6zGZc*`ޱ?h"ٞ1[ `K.yסK9"S*ͺ+lH#7AQEPE9PDΛ6۷s}m#BPA,SiI]Һ8κnE>őgEx0iLZN_^3ɱ0zŧXMh8>^-2F)t+T%܈D,U)4MHhv ]BAMҊ) &_9BG"**r9y1ed{v,^ZjtUe~U3QUmѮ{o2!lXr(7{ʷW;'Jz^Z,)eK`[Q;߯'!㣹tKϿ>VqE8e5 3"?<oæ DHÐm+;rX X#uC6LogTY5^.=,7|899vޕiL-=`8Wa1lT DH謢DkZỦv>r6"|xn{[{]_S)oMD!ż;>Zl@g{tj 9GK%D|Sz' ll٤\sY9}EǬ//, 0˫ۏ>8<7=%/-՘PxaŒ (Z6Ou"FeY]`.BMY'ƒ@v%3d V}-"n)9m[H*Gʕ,ĐϺS/#bM71E[Ζ]c't?6d=kDCM._֦ȉ"Ip:$Z*Rv88RŠb6VhB1R"yݬ/7kiܙs{8qo^v"{^zO?ۿHo6Msp1o 4m|y,|ӄ6cۧS*B2G2FyKۡgfWk-../uv 0Ck8gg8X4m ΀ h`$Ѯ-3#NлՕ 3^JkCv1H"PoB=rN1$A"R7D m~syѥڱ t@8ۊt:mXUyx]pMp ײN{p;0X"?a P×2OTWFD%!Юdd) B$<;!ډl\]uͩ&TړNMF]n'"l/Xܬic~E}S=f>k/c8t{M3m{tt ,< Hm/f%ն1M3{>K(T:U9'"BPO( j߮%Cf16f>=99u*7;eGd{ܿ9m[j@P! lɵ '%5.}Lwik~].r:o$8boek(x'" )EVܓe3v-(IAl8,IE40Cմ5 a X 7 U(Z=@\`P6e 9'{e mHO9[VMY(52΋c,DD6оc'ӄ23ӻϒ4U3P/wf\.}sS7H&X3-J|yk[/E7ܬ[/Dι3< (eaf1_^7%q]B]fE[V~#11|g'}48:_mH?aˋPVu1Ãã64S 6yPsgs'DM4}NfqSzNaۄӎkVDho1f`Nrq72sYYkLCDJ G@St9@E˒3y!ؖ@A@B5 s1U@FvL#:*w%ӴZӊ`S&OVT2;y7q ƚAr<I7 *Jd.`#"'*A{ {}ҝ'`V?lA:Q_wug/>cK?}ׅ/_805#w;:Z6ľG/}8l5 ]YDTXNϮ^{{BCH?f 9%2+)@Z~UeZAGsf~nX GaE:G{c˫`1/< Ƞņ n0eR,vE)XHݪ bvroó!+[o"eWd)ܕ{3dT.Z/SH0**iG8̺潞ƙbaF24ڥJmA:i „l*؍ jZ:Ѷ|r%~ЛJo *,5}WmD788`wk9KUo s9"KP]}cnC{~wY׾ADwxBpx|R1TE,[-$4ΝSG%%^fMߏ9gSHZu9٬],:"޵I -4bo_O|1 bE'pbL~.$"C?Crz:Ͻ/dJ PYU^^kgvsO  1Y}*eT3|ëY( } j+vZ8~Z7#*#WD¦!yұ׋dS?λ<8tf_V J^9Ru/~թ Iaý3jwJPQ6y ;*!*#'f|POa@UdFS[8xH`Znfy~A&ݯ>i7~Tg~s?OB_m۾/ ONO᪭TˠvD]6j*\ea8w.zjY$;TD!(D&d7}޽|泂$5EDK9s˫жgg f?4M#Ml\_/fi 靇]B}BVHMc;b7j_Kŀ44bc™s۫'3[d΢\V##J)rN)G;8*@;<J]YD[֐I^; 9Rs}o\%x[+oD&z ̂{ O4ޑVdeP!yefiSRoRd^w9PM歴wmo-Hᝪvû-"$'Rqeei7?6`?Ά_^=o8G0<*M;-ВsS"6?~B`PztI,1aϯ_ywشa"3{[wK}Q-"z,<|x^Ǟ%Փ5dK̫s%B9fsz|n;(-%}o}!TfD ]ݧSjV3 XSt4z^]_[DfE"W↧U qJ]v y+vYJM+Ս՘b4&<` zg EvOўg7!4MJN[9p荦r"+^+o>xX:r[=zDŽ! *1šߦȾ~QFQ~lwF9.~y(7`@{õ]Ɯbw߹YS{N}qslooW~O4g}yW,:j 4"c)9 >̺vM;7JvDy{N}<ޑzBnպX c7};<lz:)t!X 4Y $™ծ&'w ƔRU !Öܿzy n@_Í nYY7:;b~ {~- ~}P=o}it5m`p ,#8o>FVBFΓO}g~߹{7g&Bq1)]1r4(rʙ'*s88xo~c"0&Kx⩧JE WۏnVXBJ, Eie{D,LXw|X'V v)SDT;J9*+YiI;&-!1 ƣۄ0x||c^. ١TM{fѲ%cS u[j ,}A(Uki2"*G}lH+vGA8a'Jy9/"eg]RDtΛ/(Űݔ7F%鐾?I?v+"4׻~J,o߿z~_C82b9iXT<-6+{QF o41gK~c06@g~^wNɴ5T9cQ:Di8 )EDi wGpttxu~Xef=}HURls\^]M[T%PovҌ4-*03ԨX*)Q1@#88Jq̜R2R o0 P b/*}IIvYCþUOMo|kM L vOc >8/( @T+Pf~YrDY7l50R˵((&@0t K!4q/Άw{5o&zo|oniP?D)ڪ痿 Pɜ_&^aͺ^]77rRcti?7L*"mω 1ak$z5A6H//+<߻٬iml]7`ek/~2 cQ3ǟu34#ڡw.K~%MkyYUUSP ԝzͅ/U׏HdOgs^!*3l X %65ID9/';mټ#PSGCYӄ4-9^d:@]9=yݓ—5z+9bXx`!ߌyeᩔ"aMhLY2i&lU-";PAsJ8b0j%DfzoBismM0}{.vs+F_TR?B{M|OG^=寊7˺n6CBac :0궈`I.MN")lu 2DN5(?g~1U)% ds+ fzO>k-Fsއ&HD-+:A:[WWWMh<8?m(ID,y@P`2^7_"" NG*ˍ;N09m6|Z*]hC#F/,_a:S:)ǸJ)=j@o,\щTz*"YZ1ND goj* W[RSyK9G"`$$"9bfIofҔ W/')eιj4?+p=0"@Gsvz+T-[ L@0n,&X?I]^{k(29g?/~᧭nVo}"|7޴}vقق<Wl>Ţ5,*_rN'm۶Fˠ ,iCxgyyVw^.>k ;=99q) TW~O}L@9gˇ*|}ۋC"!C6uə{mD l}eT>NРr^ѽtcbLɱJ΁5Yt'CQl]w! 99 ~)n6f3-w 9Bzs*ϷZܬ)@Aվ?j!!,6r@E7 ĚsGh #`eՀRHv:yyޤ,*D 2(hNˋ8&wG]Nk;߻SORݐ{!;rVnmg>oV?׿實^t]g@XatYYWXVf!xAQ5Ms4b6!5%veN%_Fض-ʙlZHӶDΠQz'y`:\Kj+n6pzr4I5SˈHl hzáPާhTBteI ˟WM,9˼d-AA\ Qd! !Z¬@M2s糅nҥVmqy'7ƁBhܞQkGD15M[wV4OXh6ȻAiRq]<^qݺBYVxi}08nǜE 1S?@WoE;9'kN#]+mB|GTƈA#:u5i=ж9*u?)@Ko8c1dYAW:\Y*,/Ά~Cpa߽RoTuXq4XoVu M:oTΟ!'z~+_c^~;- % @%&k1,EXi 3v-IJFCUxccShoCByj$bmTSn>qnyh\۴gn&plfm 8C (c>9pд]BoP%ζt;'-"bl6l8 5 *dUH۶9am,2ZHXұ+Hh{"@,4hv=!M;U* ;|qΑ.[4e`}bBr7Sj}+7_}{ڮ/Br0}P%:Bs)1猈mNQc~dˮ5}Ή!*Ndny!eZh ol !,Yll[`[rO*Ț9gDi>DKC&H\03#3:k}؀"zE!=pzrTwЏ:s䴯uPCYhYܴĄ_W>gxکWᠦ&9884f{29';yAN%Ab 5"UMD'KyyIXOb]zZ4fE=h>nSvN2V#hf(YzkmUT<3 'C RO6uūwc2,U8'c"s\P2|:>HV|.,b$)@ie'h/ƍpN+*\ hS21W%?uǫz?3Umf!.7L](0Ӧ>Q4`bLId**.$j׫mS;~ކPw1fr](dtcͪ͊Tx]\\<8!!uaX#q1I vXUM>=}jjfb@bBe'HD|F" *fb!* HiTܚ%(gq`X8)R2`>1J c~1VQcmv:V1%NLs)z^ ~e8t=!@a )y9*'$qd "$wI9(1%EC(M&"m{HPN"$E;MlƮ^w=଩8b+ blXQ78DM DIEQE@)xi dlVkE]_禮5o[u9 &U5vh*HH *m[DUDEw!oI;HT*"**9F<2pD"=KY6&-}f*_/$JuwAbd@`eceWﰘSNS|<@b5N9.U)vPQ&$:~GTu4kr,|ª+,3* >U l"f:UL}4Ij8_v]N8zC{iDbm&"0dO : ^<<99uwa7Yb4v~q^ju]e} c*z(9 O !X L)kb A'n$ZJ<ǼZi\厱iqX}ߐCŧ)b9WRRty1%hjceݠXq;pRE49ihBC9rNYY'3qpӱ7_`*ZrۙID@0 iKJ8Jq<&yVR\SFUU ]wsu&M_Xưdl9tF=ᒂ4vzO@wo}^oOtfƝB׷Wv *@l@$@6j U%8$s1onT%? CԈ0jwݡЀyӓl2tJ~?99=y4 8Mp D&nVӗ/>D13SȹxBUUb:"w6B>&?^Y"L%KN 61Mń2+ ƲI9YRJXUUMU!@?(SE5!c) 9 3;0|Y;--+ėcB!7,}׏q !JV{lrw忱?Djv;G/m+pҋߪqQp v(НjoH99LKjj(8>jQnjUÂ]zr72s 1&UHx}u}h;ռ ;vՔ2nVMUcaٲL3Afa/_za"UusuMD&(T*~7îv*@`z:6뺊BUwD* cp+:WܙB"ˁPj"@4q.G4+`t+'dc3l ~T !Аڮg)Ġ<9 0Ef'eZ[X'u!1K b"_ʁBH"QqpFuWqS2ċɽ|L`cjz$ 4Vu ucq(.dcmO*UU ,YWf2Ptxq[:cX׫Hݡ3f Zߛ&Qơ"tq 40pMH%MvrDeb` 2@t))I"2E"BcRyy2CHp8Tt^;YpFOh9޲D=Kn.󢣁M)h-jw,#k:!eBewweFwMw#~V?>ݜ"UT)q֛K)XLe1j YLM㸿=:,O#,ժ뺺jUU'F-Ionwj>/~KbZv` "d{qyyWulPqWf>xuv~i"I"bRS&U@T+L*wG( , UXY21UP{P˨IT p༇P^*5(q萙KnVIf!!&ŁA/eSL9`?Y0%7+E3L VGA\u(w:WY7qf7t\:_-9~)W=ϟ7 \$7UT9f &Ьq̢i*&"v3ItZ 4 e?i !UU׵a8,?)7ˮ>z^7n^Đ=9IuMHH)\uSou-s_#|uWM]iI"IUMIg 2E҅zNN ư٬u1P=溉*fcS,KTr IDAT5S+8Ɨ NNNeA=FCv8|T5cߨz 5[QP~ɘFDBPUSWMWzL_ 򕷾/yo?yc`e̦ H K^ zVt|^!Jc(crJőGܜW"餦]7U$PER!l+׷mg4f"v]UՃ^^__]>~ǦYnITH)m7wݣ31PTMU?/<+/ O3_;$] Yn}jeVVf^VYRzdCP5 QU’]=+ L5J` Fh33VBdd(0mq  0}JBOz=z$lT2 8tsk90}A_B}~~6~阜_/@xR~nXWw~u5Hj\$W+Odng/xw0U ` 8!HѤ Mwݡ)B2cԔ۵gQ^R櫙>z8< oyyq1}`%BI#Қ9@%D@ !xvj]5%x`}x[O t4j3':3eV&TVUB !y#R7~`\"h f)rw_=栒0 1ev Ux% Aq6sj NRT}Sv:wdž(a.fLewayLKB% _!n"HT7_ 7?<%q+{1ӳriw{}[o}cG1UD @ sDL}|,I܎90}=PDZjL@pzr&S.#hIqp8]pymfMꛛk"\u]9:_ms"3 eiaCuc{,3}ZtShJ`c1D1NyȤf!p; Cvv|g!31J% LshYpdc̮S*f$0-Ź)fw֝gR G;LՈsDzLɬnjMYy5Rkg8<^^Gqg(,~/(lwPu'z_k-DR !,2|I7pHK }wK/k4R`%3ZGpC*0v }?à !xCj`>70ҖkAD=%G$}? !x_]7uu"&AONOF"$FcB& aZNGai2O<"4kSȡ T A!T*#j; ܬWuRYĀSb:)oT Y].s֩1sȪy+¬,BJ*}߉hIiM94 d1A0ɉy)L)sr\3n12:spOZlqF? ;]7y"OI+1.,u?}?}[~~*s+Dr}6B=f?BHTUuÐ$<~FQ}v0C_tPL3 _|o$uݴ<Oy㇧ fj3(øn8Axo sl$Gq*4UT]S7R!zq$bbŮm,ȅwĕk 2t}?)ƨ A@Fva/yrДMb yd$-`9~jD=St?ġ>+pDivɇqÛtU8OYȡoP8L9oP˹Л-O,aoOQk[(/ǀ'H !p`T* ܸb ?wO˿/#2s`Ɖ`ff77~dA<0jܦDU >Bb^]]a E\FSqӲ~"۷gps]oDݞ>`d b*G^vm"W7o1^yio_wUiAIT\ 9EJ|2pWf1Mb jԾ:VaI!p4P~&A4BO>&SM:䩂UZB G2OBdU뻮{_}1tR> ̀H`H0"up1b$"& Cf>&  Ў}$: IscIZw4RtpMKu`2:8vf30ocK!Ŵ v؏mۂJ, ,i,fu]#bqAE>7QDlAM'ۓ?aXW1tU}/m`i4@1ui4aE-Ħ[Ζ<+(#I )wy&j&BD1FfJ ,: (*˻\vpnɦe\Hߵ8!sbJSm6.Fiu?|7\~%ox1%Iي Dkq w؉dx7?C׾7)mD1׏cEw8QXҗVЫa6t"-els6ZW{b|w~{f%YqaɫmL^A_{cjj""$ã .>ylRJk#"0riN[GQ1fiCЬ|==1dLfoŊ ǟj# Vnd&$Rd081CrJZ5cwrc^M1[1~GuP4 2XU!0(8M3.p yiku+w@$Iq˳B^+Q DI PB;y>Zh |m:۝UQ ry,]<ü3ro|߹?(TZFdRg4Z9m#E*wfe.puq~y~SpuIଠ\'+pyOePG$nXL^8|afh֛G4"@ȡ*pyy]J $*6$0cQD$ < 8g=f2 wti*"i00P8nW: + h4]͍u<;h/rDђٵXÇ!MD/p<$m4ZѺ!f޺ a%dɇ,1ƺwW{B,uPxqyGh. 4.ӕ:]xtB;o;o?s*smɫG'fp/NܻiVG$zPj9~8_oOB0]7 2^MViELJ"׎I7&X m0쳇@t];CԈ"&l?ٯ7HbaLmLrMnXhu1erOsЧqxz}]jJ cZS4]dFHHiU.]J?fbӂ/*:U+pݫVMߞe HD |'_UQd:{޶ DՌf]?ؼ2Q5aF\&)1N£vi˩dfw<^UV5MJ>/}+F@G/_=xqfjz1.*ԚM8\:%IIZϬ[l`;`&1oVǧiEӪx1Ulx{.o&G!Jд.J$ 8sVwrO @mv U Dߤq(j~Q wЎxZ'FZKNx_}睿|H?a"^c#:`y-[ _rÝ^ӓ*A NxU  s8pUUoeOBřC+bUwjUӄCT՛O~Óc4uwp\rIi44us~qšֹnVL{"PChbD17ȌTجno}K_xvO/>!b DSf^w]Ԕ]/'7G>HoT߶8U-byhNXmBhv};w[A-mf5[꽺6_է7BvlW<*3Y.7󵏿rz$P5صkκLY @Wܯ[F?>4s%4n!D7 -PJ-1" 8=ǀH&Gk lRM/21PUW/_njJHl@쭳$d!O[MόbIigf B WUaUgkή_*h`"Ǫj[Qͧ؊ͩ}2/HRJ&*n@UAP 0+_0(1ƪRaGYvҔx;u ގ%GMflProo/Aʅ;qߴ2J#pv"\,;\0桫( 1ITKHh^ qγ(Iж{3CiLM`q{؇\_?8=EQ9TM1: ֛n_Ս S4e/A-MV񼻱Io?`2|KO_s1W/~J`vbc*F"!:nź% )d("cߏݓR/WXh UX?q;n9ea-nǿfE'+`HH&*Ew޾yW'.G `ΧXXz3z̎[Ip#f"C*!G*·9KrKb%/>ږ+IqtpUXҭ|n?rX%W~ޖ~_==y|;0`@dGf!S\]:22NUdY},VBHfس'rP8 0&TQ4. 50qU7T?kV_ !ccuhfD8# &C늙"" alPEdC{CJL.ӀWLq;0  uG|yS'D="gΩ(Wl]v|WOBBÜFLdǢ%$'ʠ,_s{̉xQ!qn'*KHGWbJC؞۶XrSLi{ }W<UIR9 IDATP @$GGi6fRL=75tx:C}Ȉ Q$벤0'Qi8 8>Xo AD7-!0SӬ=z'fMLhEW˛+؞l9Irh[0kUbξ0VҘڮӳ볓mo=~l>89LuC&M ޻E2a@f0m^SQ9fb&ȦvYY1Q>yw01_0Y&sԕtsƝK8o;S-"u  l k*a5,L)Y1۫G5i"fIt?*Vv461=j"cJcUV=fLzӶp8ٞ25AOyelkK zQ8C`|z1 ɧ'/<|__ꆝNdФ1%߻(k$hDm %yK$@9X1FIyӄAv5obbe dȖ~#<2Yލ0S89w{ֵF6>8{/kpУ](ޱM;1u}GL3<7ØSub# "`"ٜ>XNl[RbdUڶYYǡom6vjF$5$BjB_<jzL3~ "zs}7u35eNgNy]0[o}+o BSMg_\6YI$zwwdNp5M8dB;^gCRvvi fuUXn? Ғ$yf9#N(l|u')V]. X~sqR`b0es"&-} 15{Ll;a/!:O3L x[,1V b1├h'2aIu]j<ԃÇx닄"u݌}`'LH8ԫ&Եm*Ssn&Ϙ1hUUk4c'dLrIn) z{5sv;9eC4M?y+8 c?8Kȸ;򃢚EBbr}q4UsN.%Wh>ws>.?w²2brEh%Yrw:3`Lm;?Eμ`2 $_jf.7GMUU3{)aV5WUU1]u׵770i0"ֱ~pჇ~QTA}A׳:=8b+QxSUKIڶ}vh?>qƔ$KezT㬽1Vծml-3&I(*B!TU닗/?xZ} "ӑ~~+qqk0ǚ^ᏖG?_}tOH`"S>s]r>444<Iv{"PHr2^aQl-_Z]4#jVmX$RϞ=:P!u}u}kO񿘉*w٤qX$)9s8:B<`kDcnoI{?aLfxs}sr (*f*=99ts;"$0:s]ds.iQ+& C`"qx:΃܂BIqoKy5+z|-o,WlAz_\NxWW3H?+e=p;04;ۋeILdş)!zaH;Q]^mR-0]۴]7U, @ OPĪ!'}zȃ../L$IRz ۾kV+?s0uN)b,<-Svw$fO_nGdtSYoVjJMЩxMzB_U3& WMJ>yαvgxm2<U)8xyKg*ũe/*/Éhsa2IcTfqKV`>o=ϝI̔$D%(!S j%xplƅy^gbcDL#-U3Q*iUׇݞج7>?"!ArIvOns<|L)oHIrn0\\T E,Oonno77UjSUMc8pZVg/Sx딐d+?;#\u p[835Ǝ+|#N9:( 3JFw +;., azW#K/cU8R_"4hڄK ԅ.)!NnjQa80jX)2nV?W"}.RW+Wm\_^__qnONN "Ubf1ħϞB 8HҒ]W OG;E@xiSf=rA(SBP$,YlaC5-.4O91C$Ы/7YMK/܄ 1znvw}ssAes5+aiZJ[AL=ꙙ8oqDԇZ$qQ 0, 8\L"_vt8%Tݝ&.C]ocP=7~}Zayϒ4"NpV˿nzPe j8#LM8hp2?ԏަ"}<|fLB83V 1f|٧ۓSTU˗a0G~wq~bbuv}Ņ5Uj~`;rƥ۷E}7LSf''ØFOo#bȱ ) ÓՔ^l=:@~2}Zyw4I?wf?G'.KǏS9I&Ც%}ZPB9ۖa堦XD tsQTTnnnbU*"nw&N.#J +u.HCnZjKiSjmUtv{!=qT$VgeaLMU5U*Ϸ[,]#$YV̮Y!!A;8t$垡UU׫ݮj x0B$1p닗C{Stt+t7c9\HK,[E>JzqX=`*H<b|8|d~{y;>dcIz qN'iF#UI)ٴ!4MC!D'ƄGc H>fƑEj9;;;}(HD|{r./IݬLi5m"(ƀy3J1; FGm%-)JUUmG?PLo!۝\(0hsǖ\@WHpn?~l{?;GYLj8zF&KKD!<q.dS"4i<9b za8RC\jce@H`:xf|%Scp~iV$4 M7NTWf{Bk E:M]l/-m[:2ƛ$kQ5zLh-vcЈc#lԭpvx411#hSU^dU:6 i< A@NVWQ3艼ż5!Yx wl1IPuM4r+7W1MUYKyh(wi Aל0:=h_yS!d :I :=Բ,G))#G j.8ST6YH"`E&@1:f^,7n6*Li>&@ 4/1mۮONO>3(8LNNuoÚNN_v]w7X̚-bYS`?/痵s(K9(^7Q\mLDnqԔԧz](_~k銯{>|?X>D12*̒k079; ιv4rΥAF`gi r:VKsVή8x>A*B./e;뤔j'I'''Rٽvl-gѨ}x4x gԕd.G)Msa!k}g>7%"OZ*'/4+C⤺Mjk& z X(*>'T;ſ5 g761_4r S? ^<ݸ%&#.ҵh^-}ׯwE7lܹ6X(yDwW?Mwv k̺t:Bb<&wLu_٥'1ȕ`Ƣ4|Z.QY=%rT!Ϗy5+/'羳xEp^+/dJ4ygʮ#g8PH{og0e(k@%&{A>cW›\m@mJc*2*LXc#D.o޺mUVe=uqybQ-Xg_<课7Ycu胷 e=n7n:=95)5PCW˅%"7j%XX{,lfYS..NX.Pkz7muP~_co7kkŞǿHn%1ϹH@Xt^q*PjtseU*HS/˶ɀ653M:"f"u !cOvzG0he^Bk0F-:fOh `@@k$U0PAǸ}(⁓p3F0SLyZ/gϏs"Nл[mF%J*DLzh͊%mիXdχ|9xc.Rq!pZ}`_*PbBXxJuVezѠ"g[w8g+ߝ Z͛k  JQDk5Мxyֽtb b^,׮cDjl]Yndx$mj"ȋ0Ebɗy|x1xLȩ2aɯ2\J}Ɩooܪ]ufO)?@tr*ZV[I3ġ^U42Jk9'1kZnlڑ(4SrqY(E*ueu7` 3Lvv"o"YkB>zkrNk[ĺ qI>W5Ȟj. K$^d0([1D0^NN}\,=1eIPx7eUƔp2? ×bjTミÍK\`75ejkLZޮosbߓЀB8_g{߿w秗ӝ[oޑl?oܸ1J7hXrA j !Ku)2TA"*"Tʋų?[-}_ 8]k?,}"` 9nvёSmz"4uUJ"K+7H,4N [)틹4 ͡b&,ЌMJ$Q4#M&w6@R_]$?l,SCѮw;t2mWbh;;;Z7jD0ư\.}mh;)(3kI˳Ϗ,{4j9DLƣuC \n4Spk{Vj{?4KvfRy$]M<,_}ɓO?M)Y#$z6 %H^ۄǾ%b13#StC-5S4hEkmh1~uNS?_:T+y1Xcv^OOO޼{w<H阀bO?Ca4&cL)lyZb#<32%D}CZ.ԆhiFb"9GIOٛ eˡPqWSgI@V\qc)'w7Z{' wZ(lK 1OO_g3c ,kbQNʺRZug87F꼷d:Pd*mlbتC(9/"`;Xcb\3L ]z}q1F1<"r&]dH\5yRNcƬu>H17޽Dq1wxi`(ȳ8}&ebϏ_U.D"֠1Nڗp "@f:_>J߂[X>yקT8¡ԇ(thn*mS8֎Ƙrif24Msy9dg׹&@L#E ␳>&_D)7NԋIimN']We!t~lΛo6'[M$E#sc8yѳA$e>ssiBKY"꼗Pj2[cH\JPwơO|iwلC.Cu(z? MӌF@Lx\i4 1ƈüt,9㕸OrJ[ qү.|o7M훽 $e 1 c|}D:}-_*C:4pq̔.E_Lwϡy6桜L*ivblGd\n=MDc#(q$o~#Qu9 ]?pKjEǓ}ͻwW}:󣧫RNĔHdUD[37>7zu;t2lRzJ@  eϿY79*Q獱hd:1[ֺ>G.E&I:@VtR3j<*,<;Dw;oO&Sç,c |L6r֦cz ">k+!`CαN7E{sOYCBšGr.)`$w4hZcuƠ,ы] Z^4= oy}54?4Q0ӓrI13;gSfe kYV`g"b1`c-$M["*?~@ySPؘ)% ! Pl5Mێu~UoUAOОɍHZ9l1g໵~w~۷or޽bb}\Shf-ț:o_ )/B:bW K/cpa{v eϯJ96DBH֊˯X,*֑ ɬ(9x>,6y˵dh´ 3Ūb^;~~{<p$$E|.S~tZBzþ??@P ֡0ԇr=!ZE!gEQ?,6+;#g#<0h%]c||{;"<>~h>ɆPg9+PއQ8rI9lԘUM-,:x? ^e&_DOPJsh-b?aD0O a%*hopkYU D؜+7Wbj<'?bX bֵmBdquGu^Q?3k*ԢT[Du'b=/"|s>TT({~9tm~;vK[HWlt<e~O!H)D⦅Ni7j#9g]Xc{ 'wX1Ҩm5{"ν+}}M׭94na19Q6 2@%?dFge' ?4,w)c mSWLTW H< J ?~B̡uU"AY٬4 Clu$r~!LCllH huMnh)uHkhμW3zs ޓǕ<$"Bǡ̞#Ek];QgOx6kʫIM+28Klqq "p md }p)`3 nM!xDaGjPrA srފ_ž?1jРi\3J.f)r]e*]bD)bq@{GZo*=zxxxp۲G)u}U_E4kd~p5՗\+{2e{м{{{?~7^UtWextȡUjkn hb_s3ʔBXT({*L2n a/>lW. W_lUmPDpYDY6 95e]upeOBLD$7P({)vez z=U+06YM2zkm8#yE>}YU*E*VT(={2ݴ z/8U CB'J:眑܎Db D$2'D!M# i2˲P({~կ>Id!lT׌)#BM"fY9̑B%Cbƈͳ,}ItB/Bd2=8*+7N[ID'2E|7c5l*MI&EDP({~4*:o_A|jD%d"“1F@dUN)P({~%2K(Za6*5׸aL#L[BRo*o*ʞ_i2=<-oFZe-oJ8@%5ucdhÒyooW*ʞ_'=(ވc1@8.)ʞ2=-ҁE)zi)(Qdf4#vʛ 7LJټ8"kk_<X*ʞג)ƒ/>|Bx5ŧ諡P({* ŗF_BPT( eOBPT( eOBP({* BP({* BP({* BSP(= BSP(= BSP(ʞ BP(ʞ BP(ʞ BPT( eOBPT( eOBPT( BP({* BP({* BP(= BSP(= BSP(= B1|ȝIENDB`PKFXʱʱ-Pictures/10000000000001EC000001ECA691C526.pngPNG  IHDRH 7 pHYs  tIME b IDATxyU59~=wkV,a0TQRD v%'UvIŪ‘3p%8ACk2(02!6fЌRw5w8~Z+ 6{}o9ܟN/sy;w9!s9<ĝsy;w9qs9<ĝsC9眇s9qs9!sC9眇s9qw9!sC9<ĝsy;w9!s9<ĝsy;w9qs9<ĝsC9眇s9qs9!sC9眇sy;w9!sC9<ĝsy;w9!s9<ĝsy;眇s9qs9<ĝsC9眇s9qs9!sC9眇sy;w9!sC9<ĝsy;w9!s9<ĝsy;眇s9qs9<ĝsC9眇s9qw9!sC9眇sy;w9!sC9<ĝsy;w9qs9<ĝsy;眇s9qs9<ĝsC9眇s9qw9!sC9眇sy;w9!sC9<ĝsy;w9qs9<ĝsy;眇s9qs9!sC9眇s9qw9!sC9眇sy;w9!s_K?~_眇~G @L @v˗.\Fx҅ ι?Nhf*ͷ׿H&Ub*f@DLLL/^F.م /9!~'=H`ssΥTZ " \pI.^fW.]z9?*o?|nuZ5Q-RkUZJ%)`jRT)!dG$\@`?9-|現\,˽j}!y9Ӽn-3/3h59>5[]S)x6P1QS*b* Lyj)E3Q10#\xchO\?yU71 )uZ@SLCSO ITJ)q-uS Bfdf l*fbUT@HjƄ&ZMVP3SS131US@!FbmFx¥ /T?u<ĿifO>WG᯼kJ)_Aߚ!yJ]?fH XL HjXȉcdNH`bbbREYUV!t1R\4%ψf`dQUU3kU05D&d@0m35}'L ֨ S+@+@UrW ɫOWXw^;;?xIZjaX`l*v&*~\R@*bU@wYVQ5U0yu|?k_7tpr8[U3! LE뮛[TʜFb Bە @W̓Nv}! >Ri;MHhjerU2Z;fViKZ;ЊOT&f``bi`VUz"QZUP ^/_y3%,G}DҥKfğd_*򥋿o_|>_xng?7ӷy.Os!'.4BPD@"Ĉ1U֬WbRML TLD ƂsALGŴ6a~4o@cdsr|擟؎#/$j3@`1_7a͝O#vA*3u}!aKȭ[ M9FYbTsmmwQE9!YMLDnm9Q>~C]~ż:eU99nsk*b`@h`j`H9$"\j)RkUePQԘ)uC? ð[-{)2jqqlǍt}v}xsü.Ef0_g{^T'gU.^x%r<ė#ّ?7OũS4ӘTA*" tr).1/o}_׾I[gC `&9cr)! O+!EU"NffRH)H."$Dۖqܭ,45vvIFUԀCH~ }D0 13il*by)b Z5V .G#P6a9 C?,W~Xi-R p\rLsnT`bQyʹHw>Y{T,|\xu"CǞ~ǻBP)*eK#fu.("G`ҶR9p7 *&EUDT9p;I#S44JBRE UCH%)Zmy֭9H! %{}S~!>>Tc: dwj"$BD)uʮ-D0S$BbB$$l*'f&*jZTEjWQ$n>QlwqdnXw+ LUTTE R)#9)(0cTJ4FEbL)dV.$T :&{̙a D"X 8T)ԚK)"~130ڎb8JuYU L.1U0N)i4!q?,8DݾISK5q n޾~a^nX|w~c޴_ !&QUCHvb*`"*)%SX <>* g{+_~CIַ[ zUC $&DU38#QcԖU/Ybf̻z)@ V)UڃyF @mb԰-P0eB l jbbAU@ j\bX}RJ)ԅ "f>uԊDZjiK҉8P;#ȪRsVH̡ 8)"USZ<4֜yŮ|roVGƘԒ3SV'RHV3c&>p _x:GX䕯xbo~4q~;0#b & * ;I= 0ҮՈ9!h`@E30 ] p h&Zv!0"A5&fQQRM[%TV1!DA@0"*b7LYl6>Pb"b ]wbo:sj?&1LqٔZk-Ssvـ]'¾âﺮ~Cd&B@wZ,} bBb3-f)ڞ> 3}ܱ;wҶƴ٨uswul홳gx𡃃SAJ5w1uC03)ET `WISm STCXo%率bOկ@q~#F"D<"گaaHhjDDk T "`*)n BkS"&B5A^mnlnڐ b61QhhjUV-fmG-"0h8ŒB"dPЊV=(0RltΞ?wjo}%Ϲ*9v;n4ӔKn*eo??8XVLcf1"ͮwV?u'3m}*֞,J.Zj)>ᡚ>{&gN㣏>uݸݮ7f-e:wC?O1Q PJ^*R,E8r:!Mݬ׮]$ p {+}^7I;tuL>9VأCZ 2dS`f(N!Pd̬**q@DUDBvK mƈv6f3}*``4iHPcP [RR$I***Ejc-yw8إԥ4 ~1n]BZV{j6o649!e:sïxru.QLTa)9%8Diinn޸q bZ OַK-1x Bo}G|Iw"y*yݰH1QW0 fd!YM8u[JUiSPUUBB+v&LLXC6cQD@qAAr1Ѫf߾j-ZCgl0Z oQ E)~29$B8K]uC?tc4]AQKRHye.RTEd.âc!B1 FvRoR5tiE^"ZKn]ԟ1zܳ^ Rv۪Zx|hOdREr@D)uLMeNGn\qMbdٜLc9i}_{{?Lq~C 1ϛJnS9&BT3 ݩ2Z.ݍ0j,m)qe3R6P@$E 1"bVʝyBflTThn.vB`M{ǧfT\ 32M))SuԮZFB@ñXG;HR ~X.b9t}R7즢j-yi9Yߞ%<0~<9zӛ=U}ӛ}'-~{A5>^:}v:@ VJUD$&S!ʦY$R2"Uvej$ +@P 3Ӫd6,Lqj5#U1mmfь9P. L5h\7m LNUQ ZD]i@lZU~#qddV$D6B !WqNMڤba9,O1t]wr6a8Ӵ٬MU5fV21RH)P bdԪd3 zz՚7ӧVw7GgL}殻[>}rd ԅ($Z0/k9ş^xmYJɹ{{f}?u/|3lRh8ؘ[?on<սa!#^Uce9m%I5ZKZ Rj ۱ `1&G+y"fFĈs7BbL)R ИRj&fIܪ2GRETjJ(R Z]XV@Cb -& fLK߈uw;X̥ԗ Wr,("bu@Xn Pk͹N-D90ԥŮK)jv)}_/cb2?wzR׼&q{$b4v,W{?\H0Mۓ@EHRTdyΝcWXTZ@<9Xqsq!?:OP9yqp& }[51-23#%daW5 `V[{mibjEuVQFH(+%!`D`RDjSSH1XKU"UMcS*V JP!HՈH!2bw٪*Ujk!jFbLkͭR06>OT8" !fEΝ;pX,8iNTj4͹d4Sdc)!0Q\@88u6xWc{o/x/ҋ׎7nܪevֳ=s{>!j׮])Z> 7i.<(@u{'_|S} k1h1pU]qLvn 9!^f$j?=0[[d3nbv`gWH`hjɠUKP@33 BYB]!3/CRfҮ3lu>ED@2"`s{ƎVɪإB &NdkT9AUM HE8e0-/Υz- )22!Nd.z3 !_,NZdf;ncf9s`"`f]7|}3DW=X_}իWVY| pמ{~s/~k/y:BX}x {BWE?i]6ĕl7~Oo×N:!NH D[;KEUB46Ryܭ@]SiUUQКMM]@̌Ĉ`dw=B9*֧v\ʍ8`c@F*U ug jՄCs1TԪTBaJ7QZuS"֙:S+U(LnWjR`tg^GPSbupfXJ9}J%vڙ3gOyGI03U>WcdZ6':__r@AVΝ8rx<>Ѓ_ }ァϜO~ꖯ|׿ѣͭ[ZQ:?ڜs)e}t8c]13?O<񔧌;ykZ@,.+jiZmwg!!n;Q>v]1ЀP bb$XS-W@@lH 06҆_``F9h`Sb1u]IVk5.QD(6"ـTKc9"=@\@ @ҒZ )D黚NRJy]9upRnݚ<Yog1NCƠT9}j"f3;9>|ū^n6=wb}ɋtYwiNsݼq|.tc45?<Ƌ/k7{}dRi&jmvޮUD\jy?#! '߷7zX۱_.QL gS21q:wGNxH̜#v;0'zg)KAz%?**mۃQ$dj!ʯ$" # BF#p۱JU6"!F}`flb!a**hbZU BJ R@4D@D3444AdÆ(N={/?u\UpnsbSܬ7G'<烃Ut{xq9t<=|w9 68TG_'?g_=sg~1lǿ/>::nMϟ? K7,b bU$͗!t]Yĸݎ"TOC_]?!F1%NcDb4H7 )nCj+C1D1 j)24kwVj"F$j;PvZU-sbBfn1l_TrUYTb@v3~hw @`XvVH)0wRfi.m bXZJm{`*  A|S˫Vi%@!.p-y7gϞ{W:@tUoιJi$Kk3ϽY8 gOCi*3(w⿗7ZƮ[)0@1vY;Z!Ԫf3DUL 23Wi12$8jmV3ig6LڹCBH#H:NےskwCS`PS@6 !h[),55$()qJ}H1!r.Emz!ؽq*۟*`8RBiۍX Jzs]?+VGb1zSERJfy}n6}? E?']׿5m&e<7x}ϞY |+>3 `/kН{{!DBPNn?]Oҍ7kqO>>^zl֦uH훷n~!Lc4,aZ̔}ގ"~7~z;N~{S ЀB1F@Hnw$d&v(鎶vS$ @6Q@T{fOey;e͡h4M˴eS,;A J$(#w)A_peJ"A=)3|ϴZf3~E:+nϷ1,@[2,gAŀбcY63e%&1є+J= ;Mh@T$PUIek vٯ̈́`j@qhFH")g.BęV&f כ =DSE3x!}ץ> Sws9s @%}ԏ/_Al2 @SB㱤ΕU-e9uBO&t:v<pOl.|xt`8Er!vmѫ/\awuLi[׋o9"fLǣ}P6參FC TOFLJb>_:/7~AWj/B\`H9;l$Q]c!^滨gʉdT@@4$˘l3ѤHh"I2Pŀs t-` Ksf!H=ة%( @^HD‹dDPndF*١Ho ιj EM=쳷pP w~қ4:8Of~~C8!>9V.M=:Dn7|z}bn|𽷉0AU2ч3'f9-3_ԧ>W\ q?pT@j:+$oޘ=)F"n譟ڳ+?W֛ h/f]` b "Bbs۟DܛL&j8X,.}~K_]|ozQ|2+ CT.v0ܻ.uNU$zsIU sMBA }ߋ 0 HߋDFpL@h&jMD , "#!y@ӝ4 $M9?FcO;NlW;:M+ʌيjb$HRJ`giźA¢8f$FF2.i5ܺ7;$bD50"uC(p\v]عƓwm>?=hZAmYg7oU%JwkF믽/ƺ7n_ޫ;ſzzh<4o۳b>[lIRSoWlCԵ+9v@jh<$=}驧nV"[\\M&Cc};xO[fVp4*T5ۿ ?0lwo9N Xel{}pfF=??{^ h>$1FX9vqhT!|>rvZ>w+:D LY #TD1]F2K)*0 d(W?(F!$hIHU$3"dl1d&FRP1@PA5GDv;آ.(C,s5$KI翃*@4UDT,iR?얫Dh老3 HkĮmED$ fX.=!#39$)HrXeYUvˢL&H4M<7nl6VRhX❧Ns aHG_{|gdCo~d m7&0T>X|/.m1ں~aQB,>V>01bbv8er9*_m6o|4}K̟]eE\lkIR ɻ]z'Ih&{9V2"xǜT{``+vL5r֑D R Ss>HT  L9i4S6CPbT`˿Nj`%30jEM"o飨Hf ;o;W69DdrH(7͵BDĪ1j:;HE@J0EZ5nUgH$1uY׫U5VU^yY{qZ}7=~WUJUWܸy*p:P`/b89(nG^8?};oߛ/=sEQRJM|~>7i6yճC ӄj0`FD!P&ʵD%y2UbPRU!vz|rj׾/~S{̕rubPE5$FĄ*Y7ر/vs޻(BSRB0F05%)ҽ|QI\)q," bWnM]׏=ܬfT0)9s9ޕegvޱu7ʽ5@bWJƈuM6gF<Ŀ/|k_)ʲھx02rk:3l/',0Řv HvGDa(dzxp IDATgl^*T$*q1{9μfZ6bfI}`vޱAvJRK*@'v$I. "r!ǔ{;EIMrKd Q컦Fӛ{=罙IYov]>zAUV[w;͗S߬ýt:~/~@'J_~?d0JK|7%n!SŘw{GW۶i7mSoU5u]/t4\Qè,Kbdv.&rDc9Oǔr1&IIU  ͬm'=|d]]S7'~J`:._O3IfFN 8DR #@NQ"$QS&!j&y@cvy504DDs#&8(IUIAIeZ\hj92ud$ hJ@ $TC1P@-IDD9!1)!˫הD5 -%SRwfC_V!ǣh8bU5ƾmz\,bO&{`]7uYEQd|q1ٟ~#?ެãk$-8< 'yᅟp>"G~tydoy ʹͶ+p\bL}ߞϖͶnz>ʲ . HMӸ³z],UU'"]م`mk@Ƀo޸wsȩ%Dەp~6#B6eyxtTdb)o{cWC:RK/]YX.H̜RMzIL,ߨ@)aA0# ) .tEPǀvBb@$&d b 0&BP|+Ĝtt"wDGD̢i2 Ž%5M11 ,O2[*$;|*d.E( Ĩ]F0QpThrpx<ٟj1D,*]bœn<|Y z<~RLٓѰ'.}$[o3zU?W}4e׶m|7홂 i2S}7~zh:U|%"&Rv~QQt\$uY~1!E7}>ό&fHP f)wH$2"&I&?<:LrOmk畜css1jDĜF MٙDw|YW@xʽ3IXB` HgOyaǞ$ET\~0e#eZ̒yhFj ̔o۵!#y|V8m@fY4e\$v}:|StWj8LsČ$If}d,/WU(+f&FSw٬o(Jպznuvw'޽Osuʛ*Djzv~=?tSw|9Sη?GjP9bbU۬כ^۶Eh| 2=~b.fj4kvصv^G^xÃaL|V0C^6u4M1_5WlS'I">~\.bv?K]͵Wo}鴨b䔍(Y6$SI1{0/v%<~ $%˻KN16S!!}@ɘ.oF| !.jR\Yb"Tf?ML#:? wѠJ< aR3bd&]03d@%H( jj\p<] D$bf]% 8VwydtۣѨ* ypE41hx7ٛP U(!? ê*CJ;\/ֳhxz8%Iᄆ\^?v`Z FE5~_~ͷ_.f3h4{9ڵ]۶}91C" 5GLd2>::*T F''$ΐ<[oHÁs2;1Fc|EHl`zt}HQ@/}^^|cW&gcFhrP y$T3!DDc 3UT8kDLG}.?6lgM55#yD9Y%"vm۶mG^/gg'Fx<>88HQ],ٔvjZ,{u@yC(뺶ic}׉Dɭo 32s}{_~SO_ۛLEYukݿ`vqbWUYV9clHRuY*""Hx~v)ln4(ܾ"rI uoGǣ`ZvެMߌ'O~xf3wxIr8&w#Ԕ4OTE)d^ eٛv(y&o*Y甜"`+F"̯`D 94&!&%&|3IRDPR1 rs'ْFI)lܭHJL4aڍxybLl9_d`0KJbZF"c(j>}l''1Ʈmmެ`{w9e bJm]+ӛ{7nݼy`:NG''eUl֛_w}o6;ƓiY p'.ֹ4(FaQ]- γDDjN�#B3s9vIy׵'׮v]n2^Nx< eag]k1J Dh*fc)Msq>8;z˟g&3t_#DUP+0##UK*eTR-L&ˌή]1i 7 !;B[|MrYd!#2""AH_\j!0"!ԫ&+ vΥ$)i\eZ;S%"dg5wD]UI"*bG`=:g)}qX j0'U5,Baf)]mn=,w`b?msrx2GG,jsuom]o׫f]5MN{΅]Li6^9 w t7nL_?eY6M,'Md``B` nraje5Sl,'c¼!P-*`"}Ch<`~Z=~\y}8׫bSKNFAU p8rh~܅r1IJIʢ,"iNEgʲkڳ'O߿ٮ}Wj8__G,b]CWVv{tWlS5cdD<[v@dW`"wf& T 0TUefeԇ s0y(l= 5UsRR͙RK}ʌD@RbB e)bdbd"7lX,rTLT2 R#&W$-%R=:~px |z]\+`A5v\mͧOэ7}d^=yR!?Vt:nVe꣈}ߵjZקׯFCbޞP|~[oNOn߾CM|[b]_,fvɵx<r\ey歪6뺶۾cW@ȅ/NNn\Q4=jsՠ Vo7M]U>{Z;=xcvu]o>_Ի:3JR4+ 9} R=֜B03|y033CΩrJ ,%vL %sC6f@NpTl]R0ps$ Fi7u'@R hjH dH رs^b( !s @c)u:&:vg狐RL1m֫jնM]m[E$! 㘴Tj^-㽓kӃ9<??! WR$!8iA!Ԁ ۶iѣhXUd2 {EYzc~>9nz>X.Ʀ0%[n޾}E%ƔRryv>pYmĤ̤)%}QUSu{qv~~켮fݶ'WnWC_)_o{_ 0>5+0Q3V.ţ˦n bDk hLƪQs`.9ɇF@PRv")fO8"3d'4jI23`!33Ɣ$!Z6إm黆]eA$ Nx0_$wGggNOǓ^xSbfx;j4ò,wm,tΉ({E|-EK8T93!"fX-GۦUdR#牋b Xt;>9=>> Rl6ͷmxnݼg>lwE(w$v!0SFmTm>=xUY?}j_ ($EI)U1\( viIBrrDUzn)exU N:hf^3;BGȀȄwDّF6%f1N]QUJ1$ޑcGHDs$QIlUliG$ʘo|^Dyr ^ϳ~gdMӶ:E1 (}.0=6ٛfjq^- ƘT,aYM1okDrRlS魧~;){rڶ$}zr]8iun7f=vzjfŢ(b6jɟ֋C?#Z\+}{bY B(9`RoEQ3$v}({sQfŠn泋mJU5(ʂs(1!;wgJmӴM/sum5mt{O?}Sw S/%v [ki,TMmMžbw;߻ެvҧFIl~_MbvCB(h25/GP&##p%*r/wX60C8C&%ydP΂&ݏb۵"y@H;󈨨BޒdF޽##5tABBnj]z&b"i IDAT=&͕Lbk닲*R*B1L5.YS8=yY3컾jUo맞ya:^a`}ۊchmM^#",[Q;88`}|Ƈ"LnݸYo7f06[w]{;׮?s)i]7ιG߹[fUU`BbvMJMӔeT9;$B3brDDET9Po6qp}u}y*Yհ(9lQo&񘁆qQq`2j{{ϿptxR$%޲Q,Y e vh$>bO.% &{U5|WsA'?u+>?keYBDhʒ@f]I "䶫7u7;-S4A :OĬg2-&eK b Vzm0ey^ @9} 5U>3sr)[43x")Ŕ4 vnݹ|@cDt]컾m pG{Z2,BM FD\e{ J_ ]?OLӣ IUD$I$-Ǐ`"]DOe_z]\Velvr$|vlXCZyݗ`TTUQ,}u9hBȯDmh?20f:D;D66MVz{)<\%QUawO%)"c u88eQt}/n]W[V[}`in>x63F E(b}민/ᴪJ['oWҞW7Ej9L{_rf!®XSRrWp3D`bIL)OK1\I$AD$*@dS1rQ  aLzyv#$253(3D\r~Hbo]2Emb@45Mb-)@^̪iRd9%ek`J"`g6fl%$ PMOw}۶f6:WPR}ע7_vFTdoo%gbNfڶm6|0,MS6;8ȡKѩ b4cL1GeQJG3H"zӵmYHXey:88:L1{<~LG=",{o|c{.;GNoٶ:lZ;2TknS8 zJ6dAv9?l,K r1D>gl1/j  !8f/wy;O{QŸ}||2/FT @UK9sUu}u]R~b::bUlno/./ns]vmΙ+_}o?̈́ˣfCTՒsCΆyqs8qLaq?O5ι#9"jPrxݜ)'bZ'Ttjjq||zz|^kq^]_wzvҽ'|>n63{@ !t]}NSyo7>c C& BSFvdcG̥vϿ(?:׏;kw]ϔIU0 ]|}GL U @EԊ U`S3fNҨ4!1u⨦TmA9fj͂E;6j BK$jqiwhY<9Rs)VC`ڢJ%*JLZB/a@t49wzk!0t}vnQUw>x34l^=;{ H܎6*E:9O>|GחW7SɥO I? ;|C|wΟ?n9b\;t9Gni,SRvwwvvVr"9O7x<:s! J)vfa60!4T7c{-.cHMӴ8;Ӵ/R@5Ď7DDiζpDL\k11\vܮEe6[}UbL%]SJ΍@Kiݷ~='ayt<:fI0ݯU3%E'z7^3Av|CD>6^OMZ*rPZC݂}?Ç!RKK&b5Uq<ɻ"h\3At qߏnov)MWϟ=w0 O=[gl0 jl6ĸZK-!DkZ{0Zs.崾=;=[,EfxVRJyZ c):S"Uw.符>F"@Ȅ Tw *#>c4TYo6ۛTj]4^^_^R 8B"8>j\y7zѦ9i_~ NAX4-4cm%6$*X-0E h<#BZ-bYr) qATq  AKx6oԜhE8&L9J*iJD35%BBG]b? ڋR38ߔ:VU%);10UH 郗^} r2[!RkulO|'1Ooz}tG>juCJaKjJi}sٙsbq||u9BDb`q>ID_ŷf)eIj}j~#3iK)1D$- 1!ރRѧ|Lekcq?nooUڝDjT=:TTeDܼրmjh*HCjy'?svvh@F6O?6ͺ+-;JȔLnX,RR9cʹ\o7v?/rS_MR6ngKQ7&7S충*L]C65Bc:( \pK"l@9G"Vh qS464:F@"DU4oR̎z3H8M hkR "o"D;0!ڡT5i"r}(U4c51SRR*0huB`fnfY"^Y̿Ѣw齗?韜/Wg ÐTt-]US./?}6_λ3Kfc=w],]8ijRf?CG9Qjzޅ" jamx=0LjUi8vnCv]߫TdN)cDFGS1a*QчHMI9OH;GY;+JW?xvz?ƀ%vs{}ikr QTuVG6N)b!bTz}uuu}}cZw}_t/)4֜/܁.ɚZ&)=P8iB5 txCl?\ͬPX{GG7$cKR c]5Gf2K-UE$2MJ!|Lզ"z7u5h,R90,ceD0އS.9;vу615H0Ϊs0PZJov)nf'?rurzzt|lԠ.F@9\!Ji;B>ؑ6ɶs:M~k\QYofvCW4& r.b#DcL%<$45}.v9+T- @#vk1-[ *#9v)<%hLi}s3xcӊ)*;4U2|;~|hqttt20.t}?M4q}9Q<C@`SFW''?of9OKYՈ]εRJU{:|uyw?k//:ח_K.yb:ns=6nH0><T)"FwY=;TmXDKÉPlF 8RJȁxF! 4=65VU;lDoߪw䙫)REM{NUZcǡeuU ES%J+IB AZKojb]ׅ}Ι8>9YV:MS躮*MdUsݬoooo]JӳsOHAD"`VKܖ))}n/"~͜s\|v^QG?F@ JM] ay'O%vamb2EC`r(7D";T q٤iJ)!Q:C"BUc4l@͔cG-×Y;fJVsۆĹprtrvޣ/{UjySʹPS2)v~6wf|ާW~~K.rq/9"fwpRkmuL.RԦZMM0l8m4%虘jUwDUjsd7>Z#}qUmWE4 Ҹ"JT9Zi9K C"oEƞwWQS+h)ɑ1ĠVsi9jUַ*2|>\Df;2P8x,ZZEsNӸ|.)'ӟh6wÐRj#vF!hU%vݩ׏3c)&B1j-mh("콷Jص1T-Vl4Ѵ\VQąBDbwgEU b%\6jmX˫nj BfH;#fj;^<h 2"@!϶_,]} Fp̎^LDR͹kC-1ho//67V&ԟfbv$!X(!,Fm}):/w("HJT&o.1(̯_1a56h3i0Sn4ݷjf6M}Bp0MVa*Vr;"?Cp!R~Js "4L5:J;Is 0E^:b8)\rə xeJ'Bv DTDL!lMŪ4M]1l>]ln7f6bS[tdWI=3|f6f`G=5!z!J3'?{|v `1ƖvZrV5Բ7(}4ST1mdJDK-7/Ο za"=`\!bRO7a6Į]pL]7x1ZCR%q?jfH1##AXkrSbU̘^]_M)\r![%Mv\S}_UXEE]L*uN>"v.8͠MC׳#QKl\rQUeنDcpj(svxC 6b2&Rv>OM{@U8hJ=u3NmPE DQSwIW^yp-^xzS+;ǎ7nS,8/6Mu>]㸛4Nn**1r{P?Va/{1|_@40b 5T5P1k:f*4*"䈹$.QMYI;y23w1Rj |0[\b l@FSZj90mx؟C[RM;6R:5)41xC!(~@}#Qmhbi1S"Ja={gONOo iDu4ANG?/Cv,ynjj6PkUόDއ&у:H 5RC!jJn"~/t]J~7Nq^_{mRiY__n6 MGAj4n67WikTOcT@vRKRU1@> h "%gp>ync6ŋ ibj7 "{$F@jP^դZLd`77<ƾAy+ٔ HkY̖VU&y./Ο?O$P8Yooq}X,<Ӕ8ni} C|D&+9__җ^/:qbUBmZ#bA"HA{}tM6~!zf8\z޻"&ROAd0Nc) vk$[0C;IKD zm *Fܞ~`0Qi"Ah" 6*VZfEQɤ:d8,_rx>;͗+0%{3+ENV I㮋Ԏ5^sZ^=yS.`G&8:~{0Een/nS!fԤDf䜧i;{brч.x)xO|~|˯.)1wE V0C&}ϰs>jUEb32w9,h-rhyv]4i̥փƚ]^zpDtAn눫X(ZRJ-&YͤHZsgf%%)9rXk4| {d֜4;9l-BwVSj ]c[_I OO=`-aGLMkv}\s%gS?/k/|>9Zc7ž9I-Vn_Dy$*((T0p>w!x&q2vݮ`>KvcxC'g.xF.y[?bC2pXB`",5wg+`6UKHTTG?pt1F%]]^\^i͆vsj] :n77뜦\SΓc]!)T҄8VRޔJEf}$&c-^k*ns=`r""Cn3?>gY.JuXjm&jVZ6 U97}P)4(Z)\kOv$0M4MGH~#g qn"Z5tb9&BB2ϟ<~kz]k b UHKU4Mϗ4>m" bڦ5*R͑ QӰ% #6 VlhN4U4am7i-];0ͿDTD͘O]?zÇq>_7ܬ7ƏErN1vuiDJ$9F 1gbU)Zj͵%s#Qo~__/R7_}kJ~R pɐZAmF/aU2" q{\Js[CTR)@bjMݔ` zpJ5lMV؎-MŴGj#1\ }nu8%6H>S:u7URw Rzi̗spZNa!8Z8M%Ofn9.%ObrbG8G9Ԛ>:M-/@gÇeJ) ڂo, i<;fnhIk/T4Z!b=8b$FZ74K){oyiyg>Z=܍ŀLjR(aNfUĴ"4EHE )[8D &`vwk(U3c׽SsZ+R" #ջ 4DDD S`t͖sM Mj-*YC$Ǧv]^^ ]rzcDGݸۖZy%("U$ g䀹sC碵J0D%/|W7^t"$jl)TDTE)֘TgUЭtܚx%\jɢ&w6j.D,U$fZE!sZ!vYYUk5; ڞa]l:M8x%[6J䎎V]ճas饇g~~C121&R3f"D$,bKU*\9MӟGOb;iZkML`FH,Z}E{Rc!I)JIfPKfS1~>lcDtP!@)e&w:vauߙ4g{*!0j8 D亡wQSf7fW;=^j7bqnkڀU$R389`2*5"6gyaCo|ދRw_[:7;MDص;| Ncq\jQ pR b] 8*t,ߍq[wßlf|o`CBf5)f07TQIRu]ǜ^j!1vt|zzzzFh#~|9,Vc"9;. "e6mI M$p#H5RGӛZk~^~83iwTDUZ#8 8MMuO7WWދ}Rڌ%ӸK~>w\_^mC `Hմ\Kw}?> 3#VP4e!PCfUdGh Ә@sPgfbjU4hy陸%KI9P IDs-)iNCGZ ]` /~6f!F|...ϵc H|齔"h";bDm'Tj@ε0)1ՙ_{+_/ X~_8k)7 @6Z1Sl%6 A-"*o3۴L] CkձdrZ;27<5m1{?DZDRKeT&nҒkEռZE$4ND;c`}};E5O#\%EOo2?|NA!Ud7?sͱЌ=zyy|DĪUr:xv  Ъqlbbq8RQw!w@-hMTAw q1f3hdC@"ľxЬXj)@L*fR*G baa6{̤0\Jglvb5H=zD)./ojM-UJ5PPc;$j8 3@Ƃ?dk)\JZ n _/Ͽ?E˯o|ZFA\~hSjdbUDqW"##jҎ3#sT4o7CCCO4vGD#9ԍM >&Vf`֐iS|0s %ZR.5RZR˥w$FXga6݀Ml艘GB%y(*شr%z >nTBNwb*LW>56SI*Rj-|L$Uj-́ĈNTD(E֛uyۯ7{Kb1ooc1޿#j1#d%glyyw)99Tqv;b0ZC_fϺ>c5ͩY} ;>TU_D@iz4.M"Ҕ0miNNVGGi3R g>;wBȥ8vj)`Yj6T 6P!Xp9gRmNyLw߹O[ǔ͍wGofY-jx)ew{{q`VE=x˹^W-|ofZf[ȷUњ =y@].vTv[6|*AۈjV>2":@ @";/U 09*k12R-EzrJٰ!RRrUIe۩̤q3 AFO=SBH~ܮUi A`]6CdBqEhvZJra|H/?3q!tT#c:dS #90fIiE@`TDpk$A!"ZXDлC햪bbZJi[/cjhdm>|5m&RVFt>}'43;nnoJd@'GGXjZ}"2Ǯkq\DrNTs112pM)  , ×yJstŋg=~MT>4NH9+RՆGZ}Uh@K1M>툱}}W?sمйU5츩1؁ek'@d!TCE2j)uypȵ3P=50,J~i[3\!V_ bТuLT3U!R}+H3U"FScF6$3!޻=ٖ]e~6\ksT$a74P`?9nB ~@#.*U9un{⇹ڏvly;sXs~~Dy~<z~$R]Y F $&"uRR\\\ýW/NW)Ҧ4#DL5-P/Q(,Mcu7O?TR_GW;e7յM]fHtRAY; 4Z-(˖WQ<%@J&umb[/jE9_qr*a.m]\$lՈ0ܓHfd[_*r.ftu?|u>\]]]Oeysw8jx+I 4sTe p’ֻGJwfR<ًA"x-Xkۗi|sď) 3B#dQb95u* %RkuY0g@H0esN@xPRܽ ZD6>陲Hfzy?zԈSnU^Eٺŋt=-Z.$î[~oƎywqq&Wz..ݜr)e"\sJx/G)m MhDL) EKrh.˲݋ϦX=Z_{ӟxpfCL9!qxS GhFH@2"n$"|r>#溮K=WI /y7KJ)ʷeYj;|;?8Pc/o 2#sba轷y,)%IS{%MJB$B070p@.2sPkt w!HFDX8#98")gDU4]b"3!o >?#[71"m$TM[]q"IYVN)D`^[% DIc[6w)SR.A~ᣝ?!o yj T\JReܷLQ5[Ԙ q87 & 9F!0yQ9 f˂6*`!kuR沛 '>ݝ^e2Ͱզ3 #jxуpÜJI4U˼ΦeƖJyYֻf6FngB)S^{km]{WVG;?a]k)0n=i Z+W~͏<^zia5b½;b3 BxE6SJ3뭟Ek}{eUm^|w>'?wnb[De9N<~oLRHsʀa5m=CH$S {gw 0,8M<$,tz` lޣ^֑40"!#1$Fڲ`P1,œniutsGľ,yދ8ЇMJ3j[%lB"8u]K\AhBDõ4`XZĸf]<͜Iz>r"C?~^̢wM).l(GzbɚL KtvlBB$,̛9}:TGݬod"3 W&F*yI)ݞ #5 e`V8Y IDATve˃\<~b е 1!#"6SN)!RwmumMͭL jo벌*iۗɴ9?{B]k]5̮~3WW s@`@'7'ftuY"\$(!@` aF!$]OӺϟ~{ʹu9DZpg>'?ɒ RJ$$ZUG~HfǻCy*)I57^[[הDD!G IL" 2s6@s7I{'b7pPJEܼ`@ 6x吹GmҠ!n FU1"y@3 B0ծ&̂" )<Դ׵g~w{keQݱ,;njfyytuu̎N^< 2IP@%4eɉH0F0i7S\umfi(%/_ֿ?O"y,8I.X`LO}K7q Kv7N4NqwssLiK/eYjT>|퍼 va$"9@ݻ/ӺJxi? 4ޭs4ϓz;[@YJ9z@<$L!@#U(K榽9m`D%00 7IX( ,p}t !XPn=fDFc'յ%º/=j"Ybh {yk]<ћZ=ŋϞX`A$qT_D#]a[ZVCvHyT_/XD5ra ":ĸ3}G1evm"6:DBGrD 6|͍)H+ YJQ'˚\]]HWSVve "bDts}z0 RSN)\J)Sb콮+'j B&&AZemJD9mQIH1&z>\MOu'■H]WjS_/s_|ÔXn#Jͺ0_ߘ#q{US)Rۗ˗?ޛZv^)!<\]_='AϞO"2drx鰭[8IN5#S)yርpT ;Rn !G$"n̄@ $!Pv攅H-K)9bHYHcm<"$Ȕk x} 2Rn[= $r^}JZZi˹֥d!qR0ћAY#i5-%i`<$ "_߇)Ʒ{n;Ҽa<Y܀˸m}TBΜ{>kGD e`F@'~Q@еYoMr:\]XW3֙ LK)ZomY77]Ky`_M=Kv@F0#%`ܒuXW3$$ um $gaՆcMm[;3"!ںaJioktsSCO̧?n;y2BO@Īj0]D !0xwx>Xm90IdpoxHaM~O/90Χ LMϷsJi'sb9p˜g$Af"2ܝ̰,G"撄YD¢[n} }úZme7O=WW|߿@a&J.8&{0380H@H;|s0fIDjMsNERIMܼ4~F!Zz^sDri0ܯpysq}8.)e"Ɣ7x0"潥R8ghG4+Bp!m݌znqcikUk p#6USޝmik5rI (; ?d^ BcDh<4)鸮OxVEX])LEOO>|ѲVB)"O/={JY|XbK^KD:\ػixSB#$DQ;Ps!,% a a,)!|׈(l>*HQ 1@n.\l[Ar'w<[D8x*7X`Fᨦ|",L]x1.e=Owwe $'g@c |m\Wyw=zk=?{"M%bC µACr۲La`h{3Lr/WGC7~N)iWHTvq.-TSʉ\U&m|; cl~6zZ8sF~^}E 1 KK)†JkEIIC;{[-,ps -^/-HNB-#4M)eH9ijo.w~;NȒHFaӼ󫯽w3"|rB" TQӷI]Y) 2MmT]UevCۯ|7? FŬݴ 3@pɉ#Sq$SBa!&B$w4,Ժ02mp?owəd覡=lz_}p4'Iy߹GA&fA"acb)HfAH kJɺ@׮0"xn5" !INk]ZjW?.9_^]\qwxgOBW_O}zGJa9Y =6w!)hSݳ݉hX7RNmV_.]~ӟ+< N)D۲,m]ytDUad* ,5q"@,"P[%N nѬbW%arՔ<>xDUe8RQ(ûWW&J{k )' w~C D{q !robބs dJja  !BrC"|]s m>RK[04 !D. bw>7J O(Q*pg?̻~{'z "P{.ljN$d)!9irZz֥t෿mu|7>᭮e.Y b=IR))T M3V2?mCӀG #h,YદZR׺r)eA"l/yy}u%4ifD]Nʹ9 )f:'9rX7hn)m޺߷RMìBDq;Ix<Oԟ<~RJ9\zkmuO]\^mJTrJ)wn_,|ؗ/.v;k]9e@ffF$2%&`]üs{s)bQ̈́+_͏߶߸pV"bJ`4M{_[ŕ#iIf[;[lT܁P튧d[>.Mf:U5[kVJ\gU%3>쯮\ki5/yGHC9a?V̶$ganL RƐL:툘/^~'?3?[Mk3I XT9\4ֵaE(1#Bprdwf7{8ym(ZL$ w/oe) ~/#cuY05G_ܼ2ӴZj*%ID>$vu55ьR Ll#g ">L n*9efp8o5|ȫB0zAMtrK~{_η ځgߣ#F8sɎaH㧌rQUB# r4MuYw\}i7/?\~}PUM-mQk;oݍ+^^ho?Gj] .u.ԃa`d6!@QBDI9dI`MufHL#LD}@H̔hm"1ic&$Beb4@+ 0t"唻vxÁ\6#Pa1r4m_ nLloVN Sp ŠR$oWׇi)Ӕh%|D8_ BLIr"b I{Wna~G hZ 3o5fZKmu=ND)/~4}vyQCv< 4 p$gڢ@10OH,ǍXf ~h@{x 8xfw߽}BUbp8x{{`{U~!8JDM{sLS ;3Q$`pEdYt:|"VІ"yRɺ#|co>|0x}Y̼wWWWWǧKyӧ_<x{Dեd1x}q;M32@N fɂ8 ]7I=z]f[%tp-0J2hj[jݼ!_対kGCID;b.y(qJ<;D>xiJQHRb7M-u]ۺ2\дe i7Rֵ6^[[T.n)ywHe*,sJDBdD6vCĜSܔ8ZM5S h/}?3Ð_Fgn[\D 94nu]H[LuO>᜖z8Che_^z=%YkVvS]גn\2D0`]wO)O> !k64eXOg*La={W78epfq  >b"&MTţ[C. ܈AFD1E닩nۍ6\YomusUI J`tU`JDDR؈Z7 wB6@ɭlBa% aH µG.YC 5CX5[SZ$SʹHڶH̵6]:Zx]^}Z轅z|EUOˉnΥ#{ h"`3{)~?Or!O~៚VWuT4ܧpOIC{,)2yWXnN9%DfYy󱷞re dumnN)OeY/.yX&Gc2͹L)q&&f jD,B9ݜEiYVf>dHJi4V@X$Th+NwETH<"T[o޺/y]ֻZ7gե~?/|ḘdDI<-ZBQUsJ$DH1ТOc]xw\菞|t^Xr)#  Ÿ[8#[;/<_]^ ).RΥZ;?A*vRVϧ_v.}98%iSr{xs>IY[-%e<>x>u}co7E|T2!['O?}Bi]/n%p| =39? "iV' aZe] a?_oGCҿ_^k8+)%05%D0 c+4e0s̺m-!f# jmJ1#*9?x1!{Ǧ|q9<c2h;R@!F'bnc/nE6|nlhQNju]Uwy'!)-ꮒ K aT켜e53 $%5=wA5T7>/ė^yHwuCbH9-k5E4lff"`mmp֏_|7u]阙i> P՛Z*\d1aM@ JVU7=޹G.%z!$Fbp`؈^(`]=xMJaִ޾||JJ@ѻZZ#K0 : `9iV{ZJRvn/~7~(;?""L{bŴ;$Ii|T`o"[#0@zߗ: B]nyԺ_yyceڧiN0/scb!almPŋxlƭl{o.`NV$ybfmu$;@Dc<@hkfMrj="TUey荟kIH@4Dfj6r:@$DoUpt I, jW{{ޟzŵp&k+?iz72MNwwf/$5Rf$ϧw}St>쀰-2f`M2fF3 2US0Gɹ$T@OYAa|2T{@DlPY"}P BfaEf,;HapD݀yBIKN)tť54;C88øڀAln.?e}{s[ǗvwnӉI Nn܎ q$F2Bb vl7 '  9v_u;=]$05JANZV"BD*D,:hZ ~Q&"ámRj !Sxnʗ,޷g=~RH=_?x{R<}(u>M ̬nuYVIJYv?_?k/?!F&t89%RDĢBm$9J:2 OGxv<44Z͊\4r&=)2;sK7cUs\:Ǐ\O H@hm"s{?s+l=# ƃEtMά݉i[[o6>آ68aaݺm[oum>BƠGOw!$1Ftn9VnSpB 1曉"Af>?g>ZoqO m=Ooo~'؎MKy-: ŇG><{Ӽ\[GyI8{aV_|ч|cGkfD|M;T)~l QPxkaVb"BABVF r,*ܺŲEKYvfz" dUqHch10t, ?  B6E'B0Ar @"LZ d 4 UĎ TId$3 <,ڪHgΥLĀH# Tf@tff" u;xKwW@9woJyTUTel~wo_i|O>|>!RHK)ULf ka3V[oUwݴ,}{xkF"'|k'w& `iDܶZkc)惐a7'1AfWu= Ǐ#/^~twO4i!ReFb=&$#ZDCK#\8Xs̜2w;b)*X"`xS]0  rpl,aL#FǨ)QIqf'13Zmj'=ySY( sߐHB7cLQ! `ֵ ">eeYz[bQ,ew L*^ˋ -*  s7xmѺmcD\UyEVrU **̬d#M`v2B(\xޤLvjYLN@BHQ+H EX=b" !NpI@8uNFLD 2뵇0cfNEr6C@4KPQ p0-k4"%H !ň(*bٯV愈cO4 1Y"O}~GO=yzy}g\pBu'eL>"&vy'䭷:-ro֯y~B,"l3 mk1c"Z-}\|x}FN=kmn}˹q>iZⓧOߙw8-DýaHjQ3SA9{o4zf[עŒJ<̊o@\1#͜\ˤ,n=ݽm*S:VRU uC0fV?ܿ2,zl&2g#-_aDNfDLLo|3I@V7~ۺW жl~!޺S,"n,< #LS-cK˲Xnϟ==.zqe1f%䤢L*=,̅ `D` 1P $XkA$ٍD`9yi׭!ӃSa e`N΀Zׂ֭ZgBLHHW`FB@ IL[(NJ!-a"(,qAX[1Dʤ\&iUlu*mۼ֭d "Hrp(aRE؏^z槿ps(c]e^vϟ??_3!3֝5cŘ8 0rWbnu;>)wn*_k\TOZ VQ!Nsy*ˤ^#<) c#p Y a2M4Xt֗|qv6aѶ5 ٌ2v}BXNĪ^D$ful"RT4#uL!pq2H̲;y[ ƛ:Gzu3VI.fzhZݹ,_ʿz uV`H^uC&-/.4R*,,R"Jxz$b&Qz_ݿɧBv>qw*_^=|n\H8$d9˼xN0Xx9['~xX03z7O`XÑUj﷚Jb{)f$}&DJL !Q 3c" =BT(\Go! 8Z'[BE`Uz)up3$R 1HkQ Af6 $-ڶu()Ue td4Q-+ySB2-d Ql(pR&2':8/ew 簭[kxBV&bLDDeL"#HFHXk{xLK)ӷl2ҟLW<۴-Wٙc$sޣy vUfW ˆ[s7Z(1ںQ?ǭZ4/Hۺi^=!@kdF:e<3Y#HG`LlxEIgf*a[@7TQM7rہ+JEw۶=j9"Hf5w[/?ܿB֭*i?/|_%2AEuuiiwBF$4 QDAh6l}A2m"\{̻?yqyٶu*rvy, XchQ9 &ӉE"3DDe*y{a޿'UᐘYx"Do8 %O./&U1tS-ED ah J"S2 Ƨ?0iJ<`,;{Oz܈9(DNO!ansܜ D(2a\ n1L0T2~󚍌ᓈLT@DIM}!LHf>Z=5pw("aQQV-$j>BɄCz}/^_]{,n9x~=x}ŧZ"E6ܡwD "B&^[ߪ[^O "a[_?};ۿ{>޺,ʲyAe*E[7pHF`h"چ^׵mG4cěy~mw3]1ݼ{7On{;fy.ZI V7 QDԭ ʎ3zgb޶mf$P=ݦifw_;!N(yJ5Dֻh~ ɸZVmjpcnp\'>~ jg$:t-p ܝ{_ו$6s:!xHzb2[miϞrz<$Vw-/UtYi<*҅xҐjp?}r]E㑄.Ϙ)"24"~?2Osin棰@@("c`qQX&OD@DBBLD$[kmzhCy|Pc݈IF!EU>hT @* $@dA1wD {ov3#qnf#Bu۶aabYv"WoMyF[O߾~t~y{uf-2Mˢp^2 zDB[50p]FL;}?Lm]uVn,Saс}&ڊↈ43bFs338Vëϟ Y2p=zx,TdOVL%đ&{k}<>hT>(#m )^>xɸgGn~_[4}3?(0ۺ]Apϻ,㑚%n6MA&`xe҂Ha?^V7f ҭ#Y?/Ut>[(a dcfNtL$GkǃNοͯݯ}o_[Kpo*WWe!LMcm棼L㫏E?yUFd@Zz'ջD"r}ݣlカewZ.ne^ݼ,3$"arKqDo D<Ói" a.Lڭ # ` 3d4z$D!tafuc< 0lf828WWWDR[om?<ߗe.no_տ_?Ç iiVj= 0mHm]LӣG71ie{]\*D #`-H<-;Ub&!H`2P1ܙ [D {ke("̻@f]n2v8 fƬ4YL#=1ap{GY^|0iukW?ӷW(1!5$fD,ea9b #Fq۶wyyjgW=-'}얏?Ѽ̗חR¬o["1q*EEXn-PDQ:Vu9Sv*$q4kxZ9OkJL4- 0yZY IM))2q*Kщ9@2x3@LH1Pm'ysdy" oPtbH7&@/Fұ7S`*h d]Z&vkx"{HH @ 0"xwaEAf$ ,ty`Lha¸YĐ[GH H$N`B2D$A,dD25e[CP0+~֐xyX&xxES‰kH$HeYF>Y)<M=]ca8~4r"FWwp⬥[ټ$H&af1@p[{ #)1KQn}03EDʤeYҶZ_[t!QTHgHK!f_RSOBHj=fNu!Z-쌙OZeىvWW?_x[2-{-Qa4EvmƢ}ێ{{tZeO/Ϯlg)33 =bk xJ; `*tDHdp 12ZH" VkDT` R0!1mun,Rp;3B ػ+~S1=2u1W0L037 A BB}cFzG@Uw}KQ ;ȼ,{FDZagy'FDN$$D?2(< p<.,/g.onk\^ {3 #`=#KD$Uuw!F77\p$RJ֕X6Zmfk\x!!#"pTV#̙HmFd"SF6nc8&c\5FN9@I%ō@&&gdsGL>lD]&=;۵ַz@z2gݎHBB̚ѣER paDF譙7@8`$cY ')13"!rO"nlA޽݇ FyZoBH6KHL1b$,,jo*40̜$cl6ĤPpx|wwvvZj777D"e`bD&"s۪Y=zZՋgrq ~}{^挾Օ!Q0&eazf1M*S?[ke>yK|v~^yZvk3"8anzBJY/Zp~2yRkpda&G!O`[7dVf9!( Bh!D\Kon:P03:#kCJ?"a|iCJQQkLs;lLDjBn#r]kʤE"2N"¯= sH Vm_&A="TJzw0 &BA0&D͝(-#bH`)a-!ve*0im+yj2BQdde`$&$hHĬhFx8'q.|>ZinZwwOLa%Et;| P0 ?'\_]! Fﯮooono10Q00,yw;hu]wa˺_ů;-Yz[v:;4R|s_low\-~j{Vu7O=Rnͺ =}KB204/n9EiD3h\O#?quEday_:/wjfR{'b@фS)`ۘ̔q&>p ֢ۈmfo&Ex١jALJ|/n47~ `"fH2˜(22AFxk~VyY N=s^q(G"3Px2Vsh#89,Wfj)epR:so6ͥ{iT&Rff"' ӊu#b):f\',,w )DBQ".f[Z0 D$IÉ`.X :MdA&!!?CPaDp3<"qZbWfD@pbk@ #Z3xeקo jdBdbUDDD)SfD$B~4" + HP  Z 1G@0d֪ {ȭ1"19yYP1NYQk_tzP8!2\B2AEjfܷ/~gno?;??_};os}s=Үmb;e޿~G/_|:Kv$W~x46濞v;fDҩ{[Ce*<}|yy1c@[oL2vco0/e>EUjx̧<   Z붵 Dg` r6 JB V"*.XDqg&#R2qO> " ?)NtgA[5nf[۶nƾxu*[oMToy:3O4K&D̢44D:MӂL,2Xi$PCDa[/^<Wϟ_n[o,RLŒĐ,PfMe}ߚ>5d=  E>/C߀ey mr( G!Z`DH HH4suWVs^I~ГA Χy{?x17ΰ`L"#!.{nn ̉*q{״yٸi7aՊkAe ̭ZB"!=nnC(Sk-0padaD*7['ni]³AE<C1bOo!Dh5O[/)E,j2 a$ 2  "z@a"T˘`aRy^\ lDY3xJV$JJ\*Vkdd$sU( \+!Q@޻yۭHUawon6eG|]=Cp!?/Oo&'O$k z{sz5T6 "\6&B "j@=P{.&m"Wu6!LbI3CdrSWͮE%X 1#̋凅)ՁU2+ֳyDNX [RNVR0R)u5I p($a@D,A\)e7&b{s}ɉٳ6Vzn֫z]oHJxݣnws_?c@x_ 6ǵTӶ; B> hV<'>xa<37${V  Se@-ٚNVQopJ)"Zkyzvv Sa>MH"4+b(En$g^U݉Y6z-YqH4m:;dJ{yVU𘦃nHh<ˋnDjnjW> /#aCU;3#$H\Dڛ'6uw p֝;ח00?כ0 eZ7pF$"&$ @¡T&rZ,D (+<ֻ֦Mtuq#`v"d*E0JH j`)Q UJA&̈RTS5uWӞ1zR̃&.V*%zڷB"Dp(+i} t7 :R]M=ӻ5T]""Fg EDfSWm A<o!cA `Bsd+pwy !BBlI\D!J|< EdPsyQ"Ā9y1H 9Hc!e3%K@DP+1iRȥ3- -T{)[8ﮋ=DNDV0C01 H.R;'_ﯯ)0/#y7k_G?`>OǛn߻/^n>y⫷>Zov^ uZڬo(Cv0 I)eF@4@i@dZkX{K9Ri6[|7Zk%>O|!\X %ÎSm [*(c&:熄qD,[L2[~9tSjW3mi<]]e6=\Ժ^^'=:#@]պy{]QU3]%n.g{NW52W)?~KjR3ys{!ɆDZHYb&1XYu0c@RW^6BڄDOaRP8TU9 IDAT;8$p+<8"5M{XA\Bg""swwઅtڵG8tե0'Y$ O!3PQ!"%91E7b!(GKHADK`X9"@3!àTbZͪnBDقFk)p+W bT7]_=zU "]\\B[ԻO2eXa)"%i\[)5DȤ c{vq]___Lݽh d_zu20B . @Zk.#3"N)GA2 ƅܟ ! dZ$<aHl3jؓO7* yj> w(EQ yD.hX3U7XH& Q/^n-BqnlR qy3,fqĂp2#p1Ss,M ٛ4E" $]Tuq`bP%O@QTftJ7OCC0fZ  WpeֻbR5wdH\c@P&g@aD`DyFϘA P xWt2pܑp!:@8>8ȂTz=wۼ;}:?;;}ŗ/O/~S$?~>nצȰF !D2ʔt3p. (`0D@B3Sy!L{??_>=}3~ᣇޭ#PGSUn 0!0RHĬJ-v Y愪f3->k{U;G.$̤gO͑MӸ{yp"o߹g8_i9 BjH4 u6#Y`xu~;g?~i:r"esw^2dDekB4Rݽ;K?XաZ3w=#¤*oKd88訬Eւ $Z,TW>:v&.R*x2t~kXX3{ 2Y8wo,lHHh %ލ1֔ x@ff4< Y2#ҡrBO\@HE0k!5H[{HԱ( T.271IQaɺ#81;IC@JQ"C]a<,meNO>9i~ 8.No~7 _>xxyzƏ\^}/j뽛iq]jB@s;Xo p[OY$֋tzMs//ZüCIXSZeD!/z bv~޺s{wuńXÓ=AZ,øWBD!RYl}Ra vTXj)e)Rx:I,E`}&t$6lVD$<$C{a E@P:t &$|"fB5" sHXႌaHRCu.0Y8H2DDns(fP+*"! F!,nRahX{X' Dhnx3^l (CLA˜XAF ,$j:OS0xkL -d˺"ΰH٣kpMj0WnsV.HpgB Cƒ4]HDKI Jjbp@)-3=   [kBl Jڴ Rɔ{5u*{@K-z}|wbna]ٳ='A͗^z ~Q|O?>:"O?7~ެkH9Ie9uOQ,L@0PϟExa}}{^''wܾ=iRflLԵ6ko.̈́eF,D`mmne0R_x,HynSWD)cFۄsg=9=??//f\r8{'f0ub◾Z"bDaZZ)KX-XDaO 1aJT鐙^zyy~uy~쬎>=N>sK/oo'qHP0)oىs)aۜ"f8*)B4 ZJIF81`ߗ.wN=yxNm9"nLơ"}sB]ç/|A_?9so~)/NOރ/ɃIG,H7Լ[uPRsZ/~;?=V(~?30Ec+xW b\!=,RjXHfUm0Ax  uS{3dkk׻ӧǷֻvt_xnݾ+ĚK-"0q-̤Yx"r!%#qȔ$R:]s?Xk!ZkF#1k!Ba`aP&DyN!0GxW>OV)jHqlr!Z1OSUJ%"\{në-<)mgO/.p;v4q ~ga05S};H)uW뱔DM;"rWȰNnj|uu޼]6ܽu}Ǐ[oA!Œ"*^,Rq]miԘED<9 H*Dr<0: 0}q$t}s:rsIXX$’fڻqR8cH{ 2IaF2ĖCṖZ)@3.E" aP n@HKx2@iO@2' Gn!(yHG 2DDY;aFm!Òa)R@\%_?@Wſ{Οo}''wwrj *}<݃I]U@~yu'l7[BCiWZ7뵙֛""n,RE'QbDp"467)K!dc XWxU!yn4OPs"vRZ^zW^{GǷnI)xtx@@s%fD)5*~CF Wհ}7/.xxa{߻`}t{!`~&fBl#GπH]{EzWe22f>T+cF$׈CőZ/=%)f:MS"q) {o<גu@ @mv@޵jQYa"Τ"%;n1LfVL^ke)`m 3$Qa,*,yn̢\4MZ ^={<Mt}/|x|s/xtu[n[bUL#<"(b=&΢SWs#i8a3գ|G)u2PbK |)iA5E2NDBKՙT)U{$rSf >w5m>[?B*a^mkUgg@x '<{N}FaV6ZRkSr͐+8Z(x 2,f 80E(Y*4M!J)L5 0L 3uxA55]ѧ9-iPxPP~Ό6"LLi2Y`N!QD>>Hlm2zÂxD) " 3 @bHs #eZֻGaA*s MK%I1;KCsڴaw%eX Hio Uy>@oG?o4?c˷}'~^xͭpx뗿|-vn=<뫫iiz?|_|奇mG,yqTyd{ZZr>ΜZZʹ#I S(z]# 0ZuH^=?x/.[Ԧv4]wN^'d_xJDGۣQb$l.pJ{~ ][WnU///Cxzy>zɃGQJVhb`$ 0{,sqf[s`#I8YdZ_StWw=̇A!CJk=J)H,jM$̒R " /FMF,%OUu qI<,'|yOL^)R"D6`)EZ;Ha&%݈1("p)ppp0K:BH(Y]CRI :!p:NyJ@1!n=_wK)Bu3 "%2QI?"j,xP`&fF}B Ål C"`(|@ aja0)Hub*%"!0a&~˷~Γ'Mӌ}wTݖ'g׿0E?/}c/vɛ/73nᱟne\wܹ{^Z sp!|-k\b usr3Koֺ-[kZoܵڴ2ZGawUxjz !\]]Ow}]鰻oomomVqvݮ oNOSjfvnEJkjv0ZKcBLmɍHӬͭuS pp 6닧gwc"=&$AbZRb^ !4`:e[#Ȋ+j"s 9t@[`=y7~S,Uj{+ IDAT +f23Zdj]G@LRXR-}-9"FZµD,nI+ceXKQ4^)\f0 U@0ȈZW< RZ `F!Cqsn=Ȟǧ$Hn(hnaR#[a̒Ү`"LPXjVR2 Tu,!a&3s]͉uoDTU >2HaȴRz2P Q-H!&2_X8ZGpTwwZ-B 3fCZ" qz 5֦)w'<=?X^_ǎa}#$'ӟ \^_^޻ŏap5OYۏK/ /jwLH)cUv\ djjøPS["i=`$5c!g8R!8sb֧y5\IӏO4͇y]Gz-jqϯ/ve|qԧøz}||UId”~WDrS ""Wp ]oçҰZݽ;7G>OBE 2 T,Sls-5."Ԓ2 iT29BC[6@S >p{sa~˟; \^_  Hq2[fWe5Aӧ*H(K /70p qqk G@BYD`T0I`b)kVD{s'ILAy܄ "즀.B@E&܅"T#z)yBbZxsEose H jP+6EjS0 3J]`p Xzl,q) u[ܿns'NM|}~Qʄa"L,n~ 0ͷ/S|_˯o#Ϲ/[7/K(">/|K_DsXotd{|ݬ8=ӟgoTD^zfKB72ԡirz{t|k>DմMDzO#6޻yB͋"#vApmNL7 6lLuuM4O0VzRua:./_|[wnWkRkJh w 2 = # E[еv]_g?~}"lVOo=R98cF(E8aڥRd[nHIYAuK4)>u(ݛPrY֊,%KS⭟ þmˏ>[b~]:$3#)sC(lĈHgzA:´l\B,VHJ$ /< c{oȅJSGa^t-nA(hM#o.JSGyfUR--,(JB۝^ky˒ˀ \H,D&gOj.hrUDRR[Eq>胋/^<8\v}'g=z8Yub5t ZKJDH$LԠHY3DDa7 J'f4 ˳<72Al#Y@L՚V|Y?Ϟ<Rn:d"gX傅 qwwUfM,cbBvp Ddy0_RF*P:7@`b5i9Qb '7Q'JL"cg pD`k Gf;,FLN\kuJH N's%4tKh$F5!0M %uQpBTKJNorHi m-œs,.)s ٢dLҚzH'V&LmQĈ2CmZV_oj)#hCIkSEݺ㧟<[T}sw{>zI77חW\rZe^ɓONO?==s aD!@- EZ'-97wtGPs˹'!,  A@*Q@c Z*z#D4Hg xyLA2 j`\nqtuLQk g#H^Tk{W᪍~9=ߌsWlj. =|??8?Y>IݿQ]f}ݏo~ ك{'!=><9>>N]'"ԦVb9 þ#sn֥YX*,nVkalegaoe6fUh>`XECL/^<Vu|u8Ϣ^XZɲx7~I DS̊VmsrwvZjm4US?g/8p9#mj)9noHZTw0ǟ(}לIp!,6f2.70rAф {UAj[1FtqژsT[iD8k[XD%HIѬY32U0T-rhFWh˚ (@E_Si<(zDTMy 8o (1̅Y⺫U+ꐘ9vW5p 0j@ݵా[{+e,9fc(ZR/oon2|qvr&w7eޚsx>x٣G~ +p\"~t}kkj6Z4ðuNGaDZB:88 TTcnn~sj;}"Sbtז ơCttnƑWDŽxtttrfLj\R9#Q\0qB2~7 g}w9|fsc9wY1qPw>~`BbNRJS0I$wi#JQL Ǵą9Hb'd✲t9:?^ؚ6o9O!Rjۮ~g nomoAd$vËϞ.wj!tdo0jSV* 1"N$DY[MY҄BoY] (W %i+L̡ELMG;$eܢ[$djGr ^AjKH9"g&R` A5a00PVS<+p2wk:#c]iAd5)_j \l!`q&Tn"y1dHG] acݏuwVk9 qTwnx,|Trxϴ/!s,%GK_J|k`[onw|zwNNτB2O>ӧ'''g % kdO#ɫV}ۖDdg&Jas>wdM6MwimZAY_[_Kfq>y0þUǧgon~Z???Xpv7Ci@'60I✻nWkfm-yXkpCd5c&`S\KQ%fżc֬aj-}Yv?]vw})ڰ_>{!]3]gp]/tY\67CHIo8N{IBSWPH C\ cF,nm6x<‘GYʰɒ^ST#s@D E#%ɝ{s`>YJZogAP0@ozί#CMÙUkKȜAPZ꺾'ra?R~ToJ"f-w}z}j}/@;qr*y?E(qm^*wW_/ߓ|r꺮 v}f9>>n/?v>fn6'GuL<[%燇bu$;bnL<[,Aϑ01m88q 0!&!du!`<6 abұĖSso_]Wlyx\t}q9!R+unn݌9Lƒ# Ԧ5F K *$Ru3^Ī L-8SӐ$V9,4-B B\KLɑZkU\Y M:F$(2 !;XZL1fc@@L@86u 1h]bڴie&jLWə3M XSPkAQ rGiXa?l)8LBX$no0Vm> t4?P"7UCO_?L|{sF£!fVMO?yI') VZf !w#ǒXMXTXzGðYzhjn2ý{Ykݭ77`ֵVn?PLfX%/J^|Dl\^\lַ_m(%޽RԥٽÓ~DYD"֕s?8+J"H$,P[(8F/._^Jr%H9GTCjbb@s/]_=./j݌lqrwflnu޽x`r*a[zB CSժ1 ANͭj#Vkz&W 8nA@]&Q3&~w{rpN0m./<2o뗯8^_l1̼mCHJ#I h-n^H`jjk3;>V5)PߩZD`5p9pp0Pp⸺#@,Y䩚$!j)sԻM&64u&&F67bB5B5ʛ a|kSp$DJiǀ͝?#:anڪ8 xŷ "sߠ Kk6Puڴ춈pw}| E0IkUBmDW|] _?˿KD[7G~9<:Go}avkZ~?{GJml8n;I2wyM1ŜQ6wXcpxtxtt9VpqVߙZ~ݬwWwgd9 Zż''?fDZonZk\U9\:Q7bZ9lN'ó]7gSEfI0{VksDA)rfP35#"s|c&cӠJ(7H}{qUdFj[ ,~6K}_ZA%}Qgb$PSwg$&NcdH)QsD17Gft\kU #"swK:޿9|~~~վ'nA_~fƱ쇁PO?|trn e1/g$9ZëÃ|_xvt|pJ݂TfX._?ˉБ)KjuPs~F')* nsK9gnL'@R %<P"Ba#JDdjfƈ-vV$2Um:~^o(/ܭ^^5{Lɬ}zJ]ﺼ88HJki@SM#kH"spfEȕDMKP88u%{'s0{c Х7R6p*zuGBvPDR@qQX?D}DDC!#zmԀX8MsRQ8Ɣ Zoi굆IM 8xi!"h8BV+s8X5tAޚPJ%nɧ1_޿\x|`|Lpw}Z,'ϤLͺ(n#"WTǧUjOR>wk#7ն]oGoRn :_-3H nm ]+9,e鄅܆r17}y{㸿|g|]b$i:̑d9_>xD)h!a-S]߇.Ӟ% "@3'"UsKnFM-^d|==Ż{L b 3 gzlT) 30~ާ[Ӳ_<$Ϻ`$HfbfmEiɉTSКRj{8d,Lh,HM5隁!'ѐkia^z5yfY814mn"9qpKm>  Qmz h3BJNw#rA vW"b醆6"G mlf` ]"`kXH㡀Ӆh}e6Ynrp72 VZqi?Ĭbݿ\rߟa_4o7]^__6y9w~7<}ayዿc~ɠNv??4t8f˗_%J9IvxGP?LnVˮlN,Nͧ 0V2"bs֦y1ruzrvrq2Saf "ruBAɞ`+5m&݈:Htt(3@+Yb]ljP[:MԪ*8Vn>lRM.!1Eu˓SkX/__vw^ $b6!,T̡FBs@`Q"UoVu@ݬDlXHjySv&bwg&U Lpx[)<[s1|xBS. <`nZQ+M  #4@Uu2q h0r 3#+VCifDӣUr$Ij-VfY٬|>|rC6w'Oo oTɿ.B߿t_~Ƹ |_WV^ɢn{}6?>JIj&S_'˨Zֆ <~޽GGGHP:~{ S⦭i5 rœ Y^.lDvK"B" $c: ul&|H9 O2$qM4'I~r:$rVH0D4bIIjfF69\^^]J\#ȼXR7ru0_JV&N$)""q$bA ELT!`f6՚Ta yQE{srwSIM "ji AXkEdwgI޴VB`fq`F5"Nz  j,`AVk6pmo*AusަNـL"hj``(S${IHj𬙅o"f5,Ļ:6((Qs0 5o:VO./ 6 z! 88|o|]|˫w^ODZ4iͭ q Gǫ;6x)6p`ra/^z-{C*Hh$N)8q)$؉6hkbut||f-kݹ0U$RnXYKZ. w]fN=RJf IX#'bvU3#!QΜRgunM-488LZK1 {JDHKO[(&fw w(Oh~ߎxt\NN8]]9b>'qk2&˒" j$] zsS7Sʙj0Pӹ7WCd*pN82sT!BO0HUoxIE &Tq B6"Z+94DDgNoΒEX]M&6$Dן8̦jShY N $°jjƤf@(qVi3&Vћ\]!zc!x@S#R* 6aQ/_wG/Cߙ|ߏ$wJ"͡_<;?~fݝ7'''jjin,i_eۍ~Q4v~V<ު4ãl U wLAkqJjz'/Rs .6(V*w}s}$S X$CZsH;Y""Bᇩe "[5xD0۹9yt1s5IH'&DȒp~/Uͪ9\|ɓ.;ln0磓cF5KZ̓pUϞw舦?QEX[YSnܜ #fl* !'JAb VHN'UKCb@E3SBsUc8dgu[93u TL"qfJbg5&HrIdAB7#D덖ܙݪ$VZDnmLzyB:BeF ;7WtdW_/r?/޽ӳ{_/}k_G=È֖R*he?6#vutC7ZߏgOfl?VΎOOK0jD 0 ؊ juXsrϞ>Đ n}jD .Vӳa؛NᴜRu؍ 86s`BfBX|0Z 㨘i5#XS(r-6ua2u@A0N GbUA^_\^]V.^ŋ}?|퇡c}eJA(.Bj"#GLU5sbզHIRՀPA,Zgd5#vPd 44՞QR-![ idkj+@Z`Dj+bצþZǡHN|2qɿ>u?}_}+_׾0Rb7EUqUUm3Rك.nb:zuWG0"B-zD8jijX]|~>3BԚcqr zNgGGsND,E$sZHFBBTwsDJ]RcMD9'7Dp6e}vsI"3UY!"an-2 0jki Mmv7,ִ>TCOͩٚh 0`[(A v"vˎ$(KFAAI`"ŶIs)5$E\5Z+VFXQkTun}:]8NZvv̠lM1  a"k5wEZm}/YǪ[V"Rjn>fH'4i%ML@D!R5G"!0@D"l"7S (DvU(YRZtA8NQЬ "=oYcF@ADr:EO>S6Ą !heܮVs777O19 ZԳ3S_7{G={a(y+>9x/<>a{%z |.\:3^{I 9[m6f 3m3}BH1N[Em] b LH%]p$ZrEu3u;;9z핯\jdGfcJ]bX>4h8LS >19M2rZJ8kjᾟa`"*9Ogs@D c*up'{bn"Ŕ0Tq =[k.RL䀽`elPkJёPJa DKS0#BY3c QS|52#H$ -KVsM}HV+:5Rb4Ys&FB$6Q5OSV@jMaZ (Hd $&!6&&$:vC(`D|1"uvIj$ k%/0ijRJ*o!MǞoWໟ엿q~FD oZf: ]N^}t~7#w=R'c8& uV 8Vk`j3N-vg~6^_iy;\Yn;;+ݽLdz!Ҕԡ!#_hT25#G<,Dw3"+UEh>R cJ",q̍EiSfvSw#ZMDR걣RrK,DA,`)c>>!!1+"0F6Rh1LoW׺% n0Ӂ6v3if$%7B%" z1iMOE c5cFBhnn@"M=W# xc$#f/DLarM,`<21 PH`Z){k>M}&Z1tDC,ܝqp0'@a(h̫"̫Cnbo֦y)v;|㓓i7>ucMRo?A IDATǯ٪Mlp 43{>~{ړ"6FVH: ZJ:YǴۥNON_~Y*Y-.ų'x$D"=D'6kL4E´C5s7Hi7vJ8lIjsBB5³x){}R"4Sv1}@w"1Z0!x祪U@s͍DV8`Ք"c4crGwriJA%TZspugL@jਓxbSkU BZܘȴ5CjX* $GAP(0wf`hS$fT096݁)t(0jfZ+!VP\bRJqf:]M)L<׌@ͪ7A]ku3) L世|[R3uRlf|;Hg=l>뫣7^sϿ_|qqO|ܹt#W;"s Bb47hf@D 烪\\aͯȋk-m6#\cܿxW} 9Fa6bNqMQz ar>^HVȌ}Ǿ"s߮y< 1lvv|: H  "Lj,\k)x3TwH@0F\ciG3wC.m;1u]U[&f##JJ8)ќ͈.r8p55uD≱ fcЪ j bp3p =+8="I)[7g;ngyU5%&g L fܬ(t]`T6w&C"#كOg a@4S0}`ىBM $k hu6)S;"4̈́;&થ,aD[3x~rJ$nzw_tns?Ù{ԟgx?k}K_x򩧚#p g]2ykLioo&"jHK"j_8|V1sz{~yO>qO|_ f}/b)$B Z-N8_XHݰ8L+ eB#b)"i%Lc7sv !Y*%3l9B R xnM<[,D9WF)@?[kԑfj>=%rgʕ!3GE:k-92拘:4<)􈧤U$ #6SlUԉ Ջ1#д Q":Bg5$@дM"Z tDj)ބ02"L"tPrjU8y30-|噱1Mȷ1nP(&Sࢠ]H}b)j(fݜmjf|6;g4\J-{խNPIX~a}__ýf~oև>ا٬7fJ] DS*]OOWGW\Y.Wۼ[Xkot=>3B8j:=>9;;:==1q;~|;}XҪL NcOWCl1okYxwbꉸJ̳M'R5p0W!N)K)8nI1%gt!IKݬb&Oyv a !_2Ͽ͛7xBDbDJ0ho߾弛"r/]3ɍ{vy޶VN߽i %|>^-D\.MB0PIL֚:3r-Ś@l֮ZkݙϘjSVJm|\栊Af6"C(;N/@ 06.,"!z-1H1ƾ'Vk%o R᭷\9r1V+Wխ"zجCl:D5kɈ @LD‘H J Mle 1NLBmn,"%)*hkNLL+"a M%!!ުHp1_@rG ¦o#DSEbTɉ#$! o hwO][,2Ehcc.~t]@:w1߸>GM`R߽s?|G>`AYbv^2s3sA\Ɯ}S/?G?O]vxvJb͵c-Gg[7ۛy!Vޝ-Ji L0(VK.c3\<"zAVB)z!%@)byDgyTq1"4D `uvg}akڧV I A̽Rq9?{{ԁ{5@! AVB”0RjrуW|!bSðݠ@47%Vi(I6gJ[Z<$$Zcᾛ-K tmQbTǜ[53@6PJ9w}bZG7GYXDX}MrT[!fs )[^ZsZ4@l-x9As=?? }?_j֮%u]3UFs̵j1%3)|(!bdV\QL5JRڢO3Qt :%Ab9#w3-b"&Fiu)8@`1nVI CtwShhB P&~Ä@vk1"ȪEjLoߗ;{1Fj)NWg{w/ĂN6?ngٻ~oRJͿ߉1~C裗._MN.8w>wٜ>7{ jͪA]S-$wG$] fr6{wn/v1=RGHXG7C a#9]ݏC 0rg)] !;oY~A T=@ɵT]X{ IDAT>AG]lnO $͚g1IJf7VG(90Akif0H@Rz$aZa /_EDmj#YH\ysUPԠ@<%PUe6%KpVie/݌l͘5# fʹ!K+! '$Qm ʹ M$h|%22V"Dch͚vztݬ]{}m6筵:7^{xE2\~ؾ6o_;?\pe )t)~+0$R<rTO|߯ /ۏ='ݽݼ[Or!ʘXYt}7g'Gbi>_R+'H#$da.&)QC$R*4 \q:5Vu 까1$&M%&mCX덶c *[7oulZ=w޹{曯' 8oɉ!, h`RV+ M7Cn4Mzk^׉b&Hf&FԴtGRf̩f!_9\O ų7}>O|}ӻ=Z˓zsǛ}?gfvu>l7{ݬmPb:p !vc.Pkn-mw% Hd$pvvͺԃv3=O3?/ ]])<CQ08]k9Su CY߹{wkO\{R"kk}gSYP~H[f&1i ZƁ%0Zqօ`Ɯ ! 1ؕ\F]gY)pdBպYkwn&fm|6͖ՙV5!Vs׾9Gv!vʸ\{*vue>_2KhyT7FNHCfh@8oZA&tnZ* ^JN(bL1D}V͑V0` ([-f‎n^T8zDw"!rgݐyrhZ><<~p辈q[W/b Ab07HZzΝ!Wslnn仮j; ?я@qw9ri i6KϞzѫZBܥO\[m7o|Իzq=٢|S""f"B)ִ2~r|XjWWvja>[t}' G&,t1׮֖; vPS1iֶX,1[kf^s׾|0\DݽtE٠  @вF \jsGda"`$Qkn UE;Mzf,1DwuSs#br7WILO4Pjm< C0rUS}[^:#iUsצxrrcl:tM6o'?R )E fT'D Gg}Uuo'c ܯ\c~cNo§?)&|"=ɗ1:;>Rm1ulyWydXko^yǮ]{Gy?-;!% c 1qvrf GAB};/u0@wc]7cRs]1 fdS 2YwoA^aZ{rӗ 悙 #&؊ĮJ*TGŊ20ʥLb9T*U%D+J)!@2EQ$b3\O9eVJYbIS3g{f,˲f Xb>uk;j0Ye59xi\KU""Ak} uIBP,!sAĐA1h s+KX  DDaB ̀h4.꺪*\p}>p(1Ve9LUD@C{w޽'?鈛/ֈ+/~7[u%!2A27C]|;=_ȼ}ޯ꯽+D^xΝ{ YZY:_{eu#s5 +{?޽4aׯ߸^c=ANgduJܥZS%㼜UB]+Z"2%4C̲E1LwvvwL,beeQ {2>u ""ΙuȢ: >ck_"lbs  ib HBacه@Ym4| `[02 4Iso,AD4V ɔ,,.cɹ,ˣι$Mk#f_%@\nu¼$ֶN+y5W_yo:>|fiu4oI孬,,\"Y%Z#}M[ ul:v7sYLӕ(ޗ"#H&f 0p >sqEy;D*:$Ich$EBdl5>c$b-xYsN'} #O'N;Mo\ws#zRNt__j>&hM |:UUhnoY~pDYq;qڻoBS4A~K$!a_/7ɧ$^_O9o_'|ʲ*0@ 5y_'in5օ" u Z$*dumo7..-#b`vhin4u_ 2lM,5Pd.r^$[ѠB>dyhXaF"RǑDLj3 pM֐Yc9I&"|9ĺeuC$!  !1 3#RɣbHW |2]XK~ein}..E@j2 a>+ʢuԹ[[[Ho}fg'3gp2wGǟ\]߈'V+7.,,ܺy;p.wG@"\jCZ#3KPDXk,K+}8[VCDT)!013Xe!U5N7]V yr8cW! K9H͵#I:%}e̾0 uM 8aF(Q, |2r^䭼R5pUU|>B]ƣl -l,{A߼|?lnni]xF (PU{W^}{4G/w_9'2~Ww_˃ɼy2Z'"/???[o^@!Byؑ_:J[Dvw0C<2`kW}dm}uyϲ6$3ktIB \✳W ­7q=Xd9ubY59f'Xk'?})>x ,fY{<_H#uHU΃B`\%֐ >&quQ_um)kuBIHPɈD2 H }=Y6MWW%"|]qxTW: Ⱦ!fރꞕ{;z//ӛϜy@*F~.]fY}(UQ~p⥋Dyٳg|?^ Ǟ􉧷sOo[4_:cB|_zWU '0u޽n;,ˬʥ˗/_\\Z<补ndF.MZY>xCKn4u.qI f3Ch(V~<g[q,H&iiv?ͦWz~di:ֹx@`Cp.#2 " Q]וKbU u"gHZHrҬEI)"c- d0UHg} uUs'J"c}*Oo\{8Bl l|>9!w7\A >b$I߻v^]_ػZƸ*Wͦ?={<diY软k_ukQ!4$b9agU] zW/3 w̰hԗ=x0 3(cOf3,u7@L1Z#3iE!2f4˜30 <)I ׅx4ǕuDLpc K[&J#=~7`v>uvCB a:|&:o=in][[ݷwcm}'52gBG?0n/.-w??˗ܺԳO?s1f}Y+KEP s ͣd02}"}fff."2Ǻ ׮^I,W4 s {_HDX"# 5W @ Ak(..1@HkaHd>Lڙe᫚\|]Wev&RYey !0H׳(uٵ+Wft҈~K_'yY&u]sݻft2OcrIɳV>"ZgI &".D6 !7ä X I2`YV1r@AHv+0gnBo/$IRi];_\+' ?B]Uy]l&$۷o6q78ʹs޼,;rH|UQfqػAQ \d.Izik- aka!ojwklYTenw$MRA921Fak|Qpwڵz;=0 Ѱ"b5,,L*K@BY"ΒAHuƖ>Φ཯*k۾((Kc3 Bbwum {#z4qUY&Yꥫ; *'G?~!~ ѫW֫Ο]?3ooMl}d}ޣ'"2OGd.?pc QUUU{gWVV$CDIY 1c Z#͝@u]#4syOR ,½TIDATpr:{]ygi@t,Wmd5&s鼘 $DX>q967'`G!M%n8 "gXIp0sس.I(a;RpBTî//Dtɓǎ;;@DX"'~?:'Oyȱ#i$OɄ}Hfܸq}}}уVnKtqyY}]"ĈH|df@@"4 hɰH#7[~.b69)Jf뒴%zQb\D1:cXNVX8z"" ,?n-,Ml>B4rlo'δqynϋ9̡jwpwg[Q3;Op10~GDZYb?? 3gܹ[]yw/9ql푍׮?Rw[d2V !L&s0?NʢZ$I!@LR}]`F@!uPYgE*[&tfDيZgge@HЌ("i t:! GҤ.bV&Agk8P}U\,O\z}64 궉҈fe:UB?_7Ξ}sϝpwޑocN,m.ƣ޵ma^^Yk[ }YZ$˲ yoAY5"֙(y-4e ,uRj;p`qi9_U9ci.ŽƟ<5iE|ԙ3o)ϟ_"c=ލ5$쫟쫟կO6|{&?ޅ =O?ZGHYKDcHX""B/{;; " [J/f󬹰 bs峯XN>om]ū^zıՕǟ|ݽqځ6Y߻ϥ G^XP%e 924cnUeCdh˼tc}CUw:P`I켋@0pc<<7)֑a2.s Lb0T߽w/A=K+˵mq!c Yg,AklDf >Xc1" އ(x6Ť.]ԟ.1 鷖|!'KE@pi $d 3KDb RYb (VsG;vv$qeuEЄ \y](nk<_G>҈v/c9Y#<ܑ~ѐ!h0Ml:1zkK\j u ! 1ӌчPOǓb^Ec@d4dy G#4( YagIId! uê h|V.@3ؘCUTe-͛{Qnnn=sm҈䔟 @Eg},h2 6}!: GYkȸlg!Kl6s(1Z{ .,ƁYު&\K8FCcu B`RXdb\v?Y\egD,B"RoGcxxNPM<{[iGGKo;qL@;qԒ.,~O>ى$͌KZY@6xcdb^Tq]Cz糩M3dnekmbDDP\ТYwo/--beŬ*˲(@p<~x֠g oD8}.F\0k˛T?;r<B:$IAbwZcdG|,;w޸^UF|ah8i;,bqi!DlD)wz "b6'1ך|g>7Ξ}ViՏ[oWڤ;vϰ3x2O666>ĸ YYj13sd,yY?q5X}U$]h; ,ˉIF:J,Ž>XX!Vc$!޽!S4O]920+_}3|F*Q`7C=;BhD|2fKKK<}HZk 2s1/YULwwݼu |2dʗI$ n7uQyo߽'Qlbpu}=JU|9e y<ǧD$xzssg>Vq o/\xrxԑg=~㜩f`B|'=vnGf c Ϧo]'^$0ev-&#`PżLlnc>F^w7N:uY4J)[E@@%3Ǐ!ʱ&p0\hV,F lvW0䒴c3&63P5=|2Ni N{;gC:J#?o[o}9@|Y:Rgq1Q$dbuYNFRb%pKUxH({wlG B;}Z *Rܹj 09qO0d8x6d1Ce1OZvg CW{wyw> S.F\Oxe ͹f?VgkAºQg=$[ x8\\\(&h<vlGohF\oy|8Y9vQXb亪&]tMx4͖Gv|4:g^]MF\К_x7ry8XǞ~9wPŕΝ;~ࣧM4JN ['!6A?,Iڝx2 I+a4Wߦ [wp%6QϺ~Dd3ua K̉N>VWiauaSN|BSqR?8HJ)WJ)WJ)WJ)RJ)RJ)RJ)RJiĕRJiĕRJiĕRJ#RJ#RJ#RJ#RqRqRqRq҈+҈+҈+F\)F\)F\)F\)4J)4J)4J)4J)WJ)WJ)WJ)RJ)RJ)RJ)RJiĕRJiĕRJiĕRJ#RJ#RJ#RJ#RqRqRqRq҈+҈+҈+F\)F\)F\)F\)4J)4J)4J)4J)WJ)WJ)WJ)RJ)RJ)RJ)RJiĕRJiĕRJiĕRJiĕRJ#RJ#RJ#RqRqRqRq҈+҈+҈+F\)F\)F\)F\)4J)4J)4J)4J)WJ)WJ)WJ)RJ)RJ)RJ)RJiĕRJiĕRJiĕRJiĕRJ#RJ#RJ#RqRqRqRq҈+҈+҈+҈+F\)F\)F\)4J)4J)4J)4J)WJ)WJ)WJ)RJ)RJ)RJ)RJiĕRJiĕRJiĕRJiĕRJ#RJ#RJ#RqRqR1_eJ۵IENDB`PKF.Kw>I>I-Pictures/10000000000001B1000001B1A9D4F77B.pngPNG  IHDRc pHYs  tIME 1 IDATxkeוZk}C/"^ ;'6Wa8&8HLjHJf23gyUMJ"Gnս眽Zpnkb =㡴$v7}ݏ&wGUUUj&VUULXUUU3fbUUUĪUUU5j&VUULXUUU3fbUUUĪUUU5j&VUULXUUU3fbUUUĪUUU5j&VUUU5j&VUULXUUU3fbUUUĪUUU5j&VUULXUUU3fbUUUĪUUU5j&VUULXUUU3fbUUUĪUUU5j&VUUU5j&VUULXUUU3fbUUUĪUUU5j&VUULXUUU3fbUUUĪUUU5j&VUULXUUU3fbUUUĪUUU5j&VUUU5j&VUULXUUU3fbUUUĪUUU5j&VUULXUUU3fbUUUĪ?B}Unwe=s֧/ܽ>z7ڿ|t^?sSL O<#_k7^M`&3w3+Ewهȇا?j&Vo=o>p9 L4䡻*_ɿ\G5os |_o&|HD6obC3{ipvm3 |SyLwk~WC1K/ 3ϼ[7|oQcfbUfʣ_~W(J {1s0`niFhH}?^Q}G:I |fbU <?M"}]}[&6 Ip5S35;nŊ{Iߗo??uYDrvuko'>k&VGWؗTpG w6L=}C$"`E]Mѷݸƛo7bsr5 /?5UGս_W>@LaSonҥo[outp˯xW[[G$#@]t~^z=,X^yj;!ZۿR'V/Đ!HYDpWW3sS-;ۻ}_Uur_l/>s$גW3;/y+/{Ib7~ׯE'Vտ4O[O|kmf4>& ok`Jw?oX^Ϝ9xW^z\NiljN!@$}t̮ٹ:"sS}'?Uk&Vy#E{O+/_ |vzruݍoxM8u6p}SDN !pXӤIpƫׯ|/@@]-W|1_/'ѸAK.!]yfbĪ#s_zKBl?8/}M\+W;7ށyRr 6 Z~݋)/__,"TDnsdafbf "{T3pG1=܆Q!FdL01} |n\Wxoq{.1H\-k`>|ƹKK}|x IRr|1{f(D`88,5>_tf!+Ix2wΜ훷G'߾|g$3Gb&g-KB,NXsBY,㓓㣓;` 0f_;vU\s_~^y9[ۓd_ѫA^=8wEBđDwaZ15w"RӾSc!]%k4^zw>ɏ(mZ/%$73㳟tRJ?](vgѤY)52TpC f&oW" f8@s.ŚqgC㒺""bwܫ@C"msczSk&V}澷gpda!2g3)\Z)9gs1&y~^}wvu֛o.\p=wsѢ)&alx;'r7wcx,H$0 pjK)}GlGtyR̦cs§dk5oqc0M -$bV<0@">p8{|1[,O%3%Śջĵ^we9SuCVU55uD af0Q$ 닛Ib2Z} t܅O#q A0WS#rn&>"ىY9A9F&,պ%vJ.$\ϫukE(fjfNf&d1|yxyt2H&>'닭fb4<U-ERp'9%I! !Ssp@@)}&1}mMƱ H`3;;gxwn{ ]8/PTAݔ=Ǔu$j-Hc  &SL RTYv<]lYRj& 𧄃Ey*nƻf0 E&]t߅ߘ/f* wwH}Ly|9hbjw.$RABB f[S1"s׵}_8p`ι}Wrtz]frxxLDIM0 klj_?'*Xf&1s)% )FD1JIb"nfjD$x0hRd'/>+''&B19c}NqZ?}ta9Kƌ,sUX}?S_59xC\ʝn CBBf @07 d>]; mK魸kwxhEkɑgt:l>__?oNO['nL4 !7P_B 9%Đ-C\SvuccqfYC/D295H$" *jAN 1NWk rfGYC(@T{)LCp|qʳ9f6T3-F pB pb 1Daa67܁"$׳v2t75-Ŋifk~֜X@nnvPADfԶ~noΎIAz_3O?}.}y!4ja 8sʹHnsgbKQcbISلY 4䙺u]BNOzRӝsgYLDdd0S5sٴ|v Q>']{}}ڌRs)uŒӊ&1cޙuu۪B OqB0P((`(+04L! B)rt4/,NFN!ι5',Y\9Wo_^8zս=+p5f6 ap6H Cq ЩA"gr"%"XE BE}.jLDnVr\i򅝝3݊%06̙asɥ̑7;[ `(xյ]gvՍ&0""!jet]̌FMZ{{ DFbj';(:T7K:urbl2J/_e- m>?О8p',,7 ]Yj3QR2C9 2a:a@A0̐c 0f !I Dp Kf@1Hg{kbcdlj}}(F3T GVrw[ooOב!J33Yzjw5`uEj7[D4lV7`5%3 KL,!L`b~^q]>.ASu;)G˓ph/>k&L9G?EjQslsNS ?bH&r󰺨1B!ewRH}%0YcJ)Ek}y|lB,l mt<f=^9J)"ks#)Lk< C_raRщ:T@ Ad2wrR )iVPH; $̠Prf& 5[9C츒@bQB X`z)()8y (,Rދ>*9ti{1'd؊@PwPרC9[V83' !H85ͰEE҆jo^g p51 !ƔrVPפf/T>O))Qɭ^R#w>4"`4jyyڲ݋Oo uSrב#>ƛo0"va$<"fb\{սk{pw{1f+IPlJ`xHH 2=9,ChuP!0KB@`’B 0oZ`PHBf]TJ=[[|ZuUi(15}B^Lۮ<yf,K1FR,(́IWd:! HDĔKN1kĤF$ kI)u]RB֩P |ؚiRdb>:>a۝+LsRnSӴnwg;4L\WD,3OLPQw~ڵe-:n$y0!u3" L+ D#e܆eD&%5 \!D !ϤΡ=v0$1T-F1}k׶)3Ÿ131d(r4W< IDAT5u--BSbfv=tϥ gݷT]|Bß%C)u_l:5{Ucfawڮ-VRQӮZEP[nU)F)rG)VJV"NXs?\3GR) n70hߗkdD=9M& 7Chᓋ;8Gw_|7?>m6]V|;q6ȆHi@)aE:jf|TDA܄\Bv(Dpb`D^wWqĶ7XJ!%^Nwp07;ՌL"suv<7'7y{Xx SL bϦ505R\` |,3R.,Ot`<K d/f&Mjc…ɍCLSS5unFi6mno-S,1F9NZf1U-4Cfh\T~-Gtum.` fB?+dh?fg`h2L^N64PƊ9ȦQ,$|FcCP=F !>硾ڮ7JtbnD>D-~uoe<&~+==hү[w)Yi.s!xrZ "BcAbbV6 C0NSߕuWi\IÅpi8gW߸D$C;H00$"7WDeC 1a#4 ھe4Icjýmۅ Ō1ȹ7-DC!f-Yݝ ¤d]x}.og>.բsii;6IqĔ$PW].%$p$$C0a"%&{Z`rߗYۓu6q<*{/\Z >ZD*s9ZYkw"!l- Y ;/}+x}DMS&7BJED8(ALԇjLM۵ࡓ;Ds_rY"QR$jC}Fͤ]_ g7Y$,,(99Jb~$#d a{rws+s5/<ؗ~>Us pRu!;?vg7 EwP8 8!@&M0lvD ) Ca1ڮ[&+b?vmvxK`6xX]VUU !Px$23KfB, D쮥Xιى%8}Vw-o|"}ΖK7o{gA,J B BIOdV=6u3UD(_<+|^ g4 -hSe5o}"dݹ;؉iӻqW@bͨԘ,! |(t89 A9OC3bRzbqfgg49q qnthܬ;9ZzR E\8u{6l!kvŬ?񩏯۶kBzWICL-bfpja$LL0cɵ}_R@aacr%]GcQ]%߿}V9B-rb*S.Di3zݶ'q|g]at'&c|_|go&,m.PpY?K}hU`pn`s& rc1%pcv+ aڮ$Hnb3;;;QQD"$jj  Ys3:xܰO&tLp3S\J3[]"\J9]֫D1E$Dc\Z1FLtՎFMHsh! BNf%\b$Fb2 @a׆KuRC e[IGYDa6"i> ]߯Z"%wC%wE/Og~Zn6$iaS^6}0AD`ü BIáF^0L,(.LDN&ƍ/ua:)&A.3u nRf3usrYU0L(('N 6ڞO,Ou^jbJ# h뾘 ۴iGBbu/9/9{8H ec1!Ht G]ۇ5s"# /n^df`'^])UeZC=7я|T73:7GFYspP=Ѱ^NirBRoKM3,Q@ɪ9Ijbڮmd1ba^ٶ]6ں]i4 \7Q eM-g%8g+ZTtWOF"sK!4ͤo(m&Sb)(LAYJq-` $<63aSF*T} sIٓs{Q.@%bZ/x,Ȩ;YomMTB\g^\b &j{wM|'A")6v *VNDxLޭ~gjf6lr wMH_C CM VܜBD`)e442d8T7DY}KUQ34^M "KgwϞuaFC4ԄTgin@"Ĥf @B8|-nmCpl(nBRvv[[SwwVuvhA*gSjd͹ ,Fa!wNs_JL  yMf#iqzfL''4dԌnn&[14%0cs;i\̚:3 Yl1يȋHxڵ{~5w=/bZTW] G` ' 'S fLD)%#Ý`L[L J1g+jy4Numb$:=Yw]f܈;7H\dUb*%{ЌS-fFH n0lŲyk4_c?X߭^y/_{&ٝa2]a>܁ЦlrA"gH69@vslkD8 t]PL! 1:N#k;BnOVbBdxyB33Uyqs\׌`eDZTKK5}!, \p1{ Ea`pȎ ڊတVLT$p![ޫjae݋yo{b}y㵵yRi 0D'$G)d4AqG_<'}:M ̕I +R:!t@ҧ*>~hPX[oa͆ɜD̪5'"[|X 63$޶V:=T&QxQ>d*"e2r|)`$im IHd):׉~կ|+5v/ѿqRet!D"_2EUΗe9[ZX"=zFQ/BUtWPDz{n+cTdN~?/5ym)k-3S-]֛ q*uKk8\ K 8" J  M+[Gܾr:*Dˆp[&)V-+3O5)TU#MY p v?:xln񓕢s_|^{_o@2,mAP"==#!vSU= E$d`6m'hV!shx < $9$݉2P2 xVKa{oݺeo7pd2ezPKyxabfŶXLs3k04$XoαvtUZeb#-ZGWeX𩔪,[չY?].RΧy fvN3umcN"²MV崨^j33YXcq6֥n ލ82YEU0$&-zf7 !Kڬ1$v4 w MxLw>L0g8TZTKx?-K=./^-TwYT$%ÈpaǫWK$qRRF?G5pWzsH1GUR&!R6 1.ǠO *#nK b`sD IdqI>HcH`l)ⴘ(¹´|<;QX!iLjZו [҉x~4kdfR.sHwy=#=#DUL´;hI"ReCDFhU-U{wq0yn*n'eZ.yb?]}Q_}?n>֌ Lu{*Z5ObA(S*¹qRt= Ӹt߹=[7 @dƲyHwϤTNeg 1|p.}{ˈaws"9Tj-w|:1+h!}Qpw]Dni|`^[c~ ?w[o#x֟ZjU/#=-= I*E+a$snv$=eD cttK"aU% + b=ʔ>&0Ji! y>Cm]oWo<W$@ak=CT)p=RT=DUUyPZK:պiemzgZJNe`}Yx9-KKs-gbLP^,aCT LKܱb?HJ])*p~nm#KâY&G%`ys& ?٧Uo:U1Oપ"#XU1G2@⍁>㞤P0 n`*. HטqxyD$Jb`<ݔD.$*n rpxzĀrYϧǏo[EɛO~aF} #p8:QkƠV|us|x} @YD~>_٬4MuEVeOnIBh%(c=܆4R!URI:/w|1JTT̲f֚תEdwaaʵ%.+c#=m K4g"ʌ$J sd@Z("vS:m@5"HAd揮#1#>BrcyM}Mĉ4K)B_?ӊ8lgdLWSvkQ"E]|bDH2L'LIcD93D 4,K--tT8#"fB͛OuhYf6.׻~eFGˤ$%˥֧I~mmDEk}( RVavpf`ZV!nR /ZL;t3{kT֢U+[so%UJȲjiݖނ[>l̷"Tin@<# 1"DZ x;UJUKQK&\]Ja f=:eDݯFiJ#DRe)9BdqELRTdX(ǯsn}2##XIc w&h-@V딑pJϼ\ϽY!jStK)Z-s2,B0UzN<#"B&QDZxHJYusRQIKKk*YYEUe)bHG8F, 7.oA:l4F{V~Cmg0aLb!֧*GfSu)3 x<[w Сn}m"73A^ %r4L @&2SrO :na0#ĨDE 3FL)*܇>蒱\OwD*T6p|/BLG8\F ݼ[$GD&~?gcȘREd]VQkzGkoEDcY`:vGPf< 2锑2MlTEx<^}0e]VXE(S-SwQYzG0{|GUu",Dkk<8nZUʔ o=2E,,E,Ã}n.E D|_^/g$HD9YIĐ32$2||ZAr#KPXl"#eh_ ;1E8I5a&,$l[F7s,efՂ <̛iEGϜܾx'NJe{-IW^_UCbɈn1> DZtw$l!'ݥXȈK&H-Z5s )i@]X0ݜP*{ZnSoͧo<|pUkŋo_޴uie2ke}~駽Yj?>nG_OK ]??<%?>c,:Q󹭁J"(y?/~vW4_aݬԪ*r:h[B2\!0ݞ/$vzb@[~y,aUk*5݄GDJ'II%HYs">t4@"uDA,9N#!p"}pwJa.Ze]e50Hl촳H&2ZO.O|hdvwZgH){#EzI7_.k6>pldZvn1M-_pe0-%y%X@*b/ǸK2iQ%័i@#E[ \^)YKwVx j+fDfd'Q\>xM5uyۿ_(؁?,s G%"=H3G^AJ`h#" R9P>kDDZіuQʽD BC[[0u}MiyEJi93L*m4 8].KkV>[.yNtZ =DX.INu)z"J-T2et>08@1f|O'+fNea¥pf-P0 4'TH" HT! |p>ͫ,2Jx WE.K3p:_n^m^r{̵S4e]Ȥ:M` &@ i]"hoUN0G-<4oLyw5)Rp&(7ee(xrZKU6nyyl->*,9w D߰]c:{tJ$$H@(4 3 x bLdebL)*"4x=C-5˲M\\=z{7_- "j]K\ʕ8O/^ܔe:L }#B0sa+[NR\xiTk2}o]͋r9/uW{Ɉb=~U"9}IX.KXs` D[/$1HH9nA $o3YG# ID(0:)FM3iA |wd[ZZ%I#Dڙ#SL2-mY-4;@ D` L[Q7[}+pdyxJ8#~Y3#yi=g4[ ~[YRJew!vbRLeft|2kD B!cLBATHGMZ Efݷ&D)K"%aR_]nٷ̼9Ȉ1/>Ͻ7r_S}.*pHc$CCNUO`DHb.FI!&*ED! fJpxڽgƾ+`,@4r쎧c-{oo/>):OpUp^Z~ri"♽[o}Y`EwLR9e՞vzY[уae ̚Qb<~S"}9U! NRHvy7'E_xtĪ\I rYDu]Uk:Nq,jLHD0&m!f+ "[ NTGpR2=֬qB,aHĉ(3(K!acӲS>.F, M ,F={/ 7A?4Cv7f g`ƖFJD`!" *@o+D1e*"WgUcn p,6~WwK[~"(Z\.34TZT_||rxtM[/}8:1hSn*CjD[dgwHUbPyEV9#E4ZeZW[ Ȱf!,a>dyk Ofa-JJNe2,gZ13Ie"&p Ӄǿ)Cm޼!ƿ,IE 6pL$ d(d"RD(BŇe8D8GrtU5~K_z7[?s(zGxQýaE&ѶHBCF@EY8ioސ*ʢVDt(a}_5$D$%ڲfmyk骹^2ĆY"#~K_b9ɿH%Y$A# 3E[ZlnUsv9#sBjVSk1Qb WF~~w[s9ǧjۃe<;81J B[omwגZiy>NAj˼GS}| j IDAT2,Ca"fK^RiITKn;`ɹeTvs!m|v{鳷=#W) c8Ĵu\ZS)N:3B*Cd ",JQucysd12-k17U3#"av#0Ps5+ݶ&u)2HI۸̲:saf0 o_GFoA,$\2.9o7_h[ Kj5Z}M9 4\vEF&8tH ][(@! fH)Qq)>aZxeN!}iϟdzF%q2}k.y:N6P\Β8$DF^A,)iaSd$7׺r+\ucD䖀HL9M2)&C1Wbё[CyEt1 H rz6Զ,Clj`~%DDɮߟЅk¬גc!\ؽWY4; hjࠦ)1l",̉IH}/>x ~b[7}տ+L'յc@soFwC@@9CA#&0`B&& Mv&EvbD*9i„&uCz? Q6͓wո֪˒]a9@o4%/e9O'J,\[&vNY>V]߱7U myrg[:z6]Z }I׷Ft:iw?{/$2l$A3#e9x.P5o[F"?7d`2hdGꄌ$cQ$!vbeЅb!băD^JS]ꜗ%8fө{u;l (9;ۿo7_ѿel(f@"D&5 ,#Z,{i8G !2J)`/ M [ɒBǀ~wwmiw]dZ*?Wu\xO( }׏cn8RAa `U慆"&pcB!$ig} qЁ;IaSr} C14(@f xsnwDuLk+'^ݐ)gXrYXmiTj5G$Z̖9€>/s.ef3 1^]]mLԢ: CZ̅Yx# "6UVj^!'"@e ddތ`njH X jZY[tMo@̭0st񖗊}T 5WaR2jL@T-SB#t033o~V!W6i>&"6GgsmM&1%IiY7׵G$ jnN஄MC`qxj[r=O4zqկ|V52?g Aq_޿滻;!x/OD!v'ӉΧv1e3 Rme81^4 nLBZD5!/=^}+SJ]H@DB4b cMzo_yOM}˥\9Wכ'<dz͙y.eYy<|^8L,P2&Oˮi1?+W׆LSRN38퐤$mͷ6cbd`39y5|X5sb03Bn0͹N`! wq^Qk$4Ng-t/m"Ojk-$5}\;Kj6J ^/hyUJDm[7|# bXK&BF@G`\##(`J32Dy>;0%"0h7? _YfjI YZS+mXm]kiF@Ԉ*aX0`IWӟGz|춛2Lb 9Ǐ|O|eӼxUg4i46]f~>'ʹ,(ݓO~z8>vm G dl~M>7*Yds# "#9 1VubBo`*kU7@&A,|>"3j]OgK~y?Ope5\"`[a08Zs'RZ'iNQb 42 V;DZ hX"+j0`A"PWp#F#C&f "1 y"Gլ.57wߜkXH,܉HZ$ 5rc(6A_[Ú=̀CxQk"2K A"% ?ih~#!8>e\VZNt|Ԛ-8 $36XH@k":a[(w)*x*l5;%-SȪe ~qq.n,ӂO"D mN+[\r &ԀQA< Kd7pfj nFV*5 Њ]{=,Ds 0̊Bq0aD0W&\ۜ[̱EFDʱz'Hϟ MF"Bƺv` 윱xQͰfX2w&)qmILrD$ĪD;EFd&+K(LswWw77@aY2K6J.''e1U_=sY\xHnĄe<>>;0!@ XϞpy\ͦſLWݧ>I": ۣ%Zw'BRRVuOӢzQh%f-8uDK5M Q`/-9WhL @nL՛ ܝC X:.u(r'ھk;/O MjWտBI`MG4WBdݣ(DF3@tZ-Wr% %qZb h\DĜK}鼜<=]|>3"w]82-y1ӏSpZ,m??3g>bnPk=|[QB A$WS<Zh=P<İQ4>Z՚K"nK4GDGux )136"j.Dn"ҖDLLAբK^B ԥy^")qZ9`vC# ^mZ !1{i 9 +:֜Usl'\MOms~̚'o u; hN  . SJk&plm+!/|Koj⛳? ! `pa1_O lz4V|"H+~IHh_#2R9NyY'!:p>LJeS]X;R>ɏ>l<.KεwS55ϥ_ُ?w޺)cy?}>}LMo@c)e' mNى+a80#G&"7#4$C dH#0puVr[5WojADE->Ę/?D>F8뿚M,Л 3׳-3 "QZѐQ78Kx 9+VȊBPm7ۘdrY)xU4u"p no/8H jZmCk-0uGC#% RZ$r @2z` X\ب9́\O4˦ieAB TJ&FPPs20[7}Sf̈9QiMkud\6lշBS} rP$0 @)d64WCDnw~9=lv۔R)xUu?OӼF8<2o8͟;?>^4ֹvۛۛq`3_vN@*{ !R֚Bpx~?!@ i1dDNؚ&pC7YӸnCt"&Ԗ_@m)"1RxǍ4ˢU8zs{{m@SH)"P^m?DD+VMMH ZCUPUʹE!"ԔްWKi躋 &!$Vis? y΋Dm;#8>{rw~s8DOky[o?v i^$Çя ו2neEr)̋iJUDB"iDnB\U;VdB UϹ!he^ҐR#|xܷIlMAfd"Uso59OoF$ y7:ܗ IDATױK}mams<=qf ձi͋jfSK-Yu+jȰ; !.Qע~}# :szt"1 xDw d4Xa혁#I 8i)] je"2zҦ4:Eti DpP3DԇR6ۛ'Jav<1c?5 9 (82%JuOtp%Bt"c70ogNk<#2:Am+B@3ssD@@ܨ06B1Lֈm^75{~㷾o~b 10 !q!ĐKQ$"D\ȑr@L!nXΑZn 5 < Z17pE\#o=}zwSڽdz`fcxs?5,;}p5 ݦ~ p8l6÷wwjOA` U !Z\UH btjD솈p_9K],xb <Gz!q%^ZlLt:Zj`!aB0-*Vk 9wӥ $ݝ  #xI0RgS1n{~Szuh|\j)QbOGqUh9hH˝M[gؐLћ=K[[  C| V WO3Y[Of(RZrn+-@t/?9Voj_C.C24[ {132EIb(,bf"BHB2  !!0PC3w VDyђ<}rcWj>49qp9N|j{<1?#>cit¾ԇ6$y#ǗǐbXM[|[+V\NP뻐՜M x7owa`kŮi:3Ex#S51_؂8 7V;U6GED(bZr\_MM#wo{L1jNlb 3!vQ!f7[p軮CLLu)#6RjB B,Kӛ~3RtiSIjѬ{O܎2uq]]o~aW4t ky3mS=ǘK2ƩtIn* AQ6jΈ _ڂ6cBZrBJ`| 8r<"r}A$ke3bh6rZ*; EXɲizĆAsZE>a)utp>{rs}s,xx|`CbD-K^靷~UqӔ_CKP х^Pk YUfnD*"%n+f 0H@,&@ f H@eYJmk.;|戺T2gTb޺tq0#F7D7Fbd$UHݘ&Vo9pXMnDXlMd" Bt8N0$ r<+ZH}H a42 b͵ZKppD<;I5[O# `:O&dY23՜CLD׈m1Љ_5Li)U"> qcj^gj?_/W hphIV3pj"1AՆk ,UupZgV6N)@tb12N}t<b%m6a#hӲQIx 9J@Hn )Yϭ[v59niT))7eWcs BHqS|n"snpFD"H0Q$SqL㪓fs1sn]ũ~>gwg1OMlTHݼ)47guL(5, Ҵ_^\}$(n͒&{zek7ew!Z;\8"@B` M&4rFHpxuwwqmuNF@Q@ 1;$4MI\|#dy([]C@G}w5!Q,;Džz?K?^I]M6 0SsKHݼx) @M.W] r@ZpW`&ugբ2~'޺ ]i]Wġk \Tu[|[l^VSGۺܜF ~h[Vt]F[ ]h+-k M%MiyA,DT7SR !ܴ9`V]Ӹ*arud3QYxEZ@\!_d[ImC6EݕԘгAWeClݫ#;n:'O1܉0Y"ըR-̴W[U솎Cp5UMYg. VM]Ζ[Xi!i零kc1Fۺ4xOz;%=FD$#GGϾ{0p$+A' 5V8ZUrͷTi¹Lzu<u~3ۖDJ@崭˼,t1&& #+84oa{S~Ivy+ Vb-cd4s k*Vbq-fӚ f:2HL[گ""7 \BR!P7k[.+b9˻A=n~]ŶCߎ ⭧n۾)Y0LY 3u`>7k(s^.Zf7(酬Yi;fm:^4xk_M!e iogE+`i$!1׊:4dDfX^!>^_l3t? Q#Bn _$fJas=@Uus5` `ejQKMԡ!@s# *9]k&1,A,i!t?6ՋW2ڮE>cĖev۟x[(ljܡ5mC#41?'v1e/KJ#]^?ryYcNY$.4@D\07mtuTshCHZ-)8:r"ێu1_鴬+!4_!& u+%H෯/hAb7o/7KdmVT,vb>[y-亶`^@ġ(jZM`ֶãӺJZtwg| s80=Jnc6df XǕX)`ع?DWRЫޟ3X |VA8/|IeGJhI$8ÒSIT}rTYTsgdy۱>()A׭zћOOa:9*8%E|6w[׉YAWyy]CH1JS?)n®4.αo B\pCॆ$#08%G\;n˺q1)R=p]EU͕RA2"Bۄm_ ۦ9 (8!#8;t/Qӧ۶nwaJ-]ǫ~;N/u=7C9VY8Ni="ڲu{K*np=x3xD3)< w\s@ga#"29TT<&&6iq~xM]7]`œup[z:88΅ڮ5-5ff4I MZm+Xb`m,ivQ+/L ɓC ^r2St"6ru`e"-+NiiB*7k8JATMKTWe9dtINUɌnHK4OѓK,]v꺝֮mNjzΐ<+4P@DNɶ$@jhP4ZPpBWœ21 Wy3"7d@ IDAT&h9 VYʂh%'IDݕ-[oxm79J`\T" 8*@g9rn;9(4)RԊGPuDb(w]4W/nn_ٟ˯?zMP:^\'2BJCu7;|]5Wu$ճ+difV8m;mk^ϟ^]_]<%'i0̵hq>N׶/_[o~MۢKu҄&H AL䲎sʹu;e><]^Hdp~ԶJ2=WgUCfMh4:AQ 4̪hV,'DWs!H^K@&&!z~wT=";U7vtN[CH5ܩ19p 1$7Crw}(ņ_Umc]ѽ 6MrAsScz&g ЉXunFDtvXG 0k*]ԴiYiu~B^޾ӧOa72OgDž!_1?k! $ Q!7/Ub컶ib۟/u~}&<GHkUѵ qqtSsdSS6HOJ=wj  nwݒy]bpTҼSӷvm4ۚ?,rqoҺYL)FVA8soKFRНFF A`X H0`=V\; yLj:kO%hDn>zmi1dp^=`f ' ( K "SV&\qvˋYB281QK bbF(45^l*mF&xjHZ{-Qj:yNw{2;4Y(4qR~uw+»]H\s>KjVw= ;"2 ;s~5]l'?:[YS'98#huRK(UIcEbF9ݨ e֮z|}ϺKy䑩!B4ifv3S3m]{1F2I4:,]{pK˲[hGEu]lJ3DŻwM) <-9Gh@\)a"x5+!GV{@]WPq'd۶XrfS7 JG :;"WYFu$RjBW Њ:!łhn=F*"\xY7(L 7cɶnqo3@H&ƁS*p㝷+^ԝeUQB|\6#L Tܬl9ˋx#4t,0!y~Ѯu}%3={mt" C: t.! (U>18hVp?S?S?|_ϝTϯ/:3= ՞[}yc Cdss(*N*.S<;|+~KOxfa7E}H2 }7xs.FZřU=i)OiOj7huICh?nQӚZ&BdLK)! lEKIc8ܝP M+Z"q^WWem+U[^+ʪL?}3-YH~HRN4E ŵX.fPr6mIf1b[۶[lBu~76ݙ碹8oqh֜y!!DS=]С0đ/bFcn/O~l_u!*t@ss4 Db-AB&n߼O]^_f!-ZUuۚ퇞 :/ϟ?kb\֭.yYy[зϺ`[NMPpIi)ES88O]u[ӴD8 bK)iΥ1dž бb%]]ETUݤVH LA1suV3R4H?}@u'H7E@5LٲJڻ(YF36r!p(踡ў 2o>#˘.ߎ n Ǐc6fsvB (ԵT\4PHJQw/<mVh60r1bۦm+ǹ횋G!]|򉷗xB엔?}اm{={lxtu:N Dujޚ՝Iա~ׅdY/OO~]'~ϯo0y`R+ݵX)Znjy-138! juBsJּ*(!f7?7ln]Vv= ܿK&m6DYqq'ZԊ(ۜqm\i[džv1D ]qŇ0 ׏/BhCrwJnvߏ󲥭OPQ>D2n1Tgªbt44Y U<@l匼1U$D\!yɎEf(m@洞Mh9Vr-R./w(k.熋EemDWL]UsɚJJ"Ϟ?6.Cqdup Ț41HZ?N.[l"w8i=(fqB$5:+ZX۷жͲ_g~ ?cjz{?/9:+F+f[VUEz sDLbՙ5\%vR"Q9˴\__=y|}}uULo?/_~0ty)ty-(EtwUR^oPu#h匠p@Gt% E%*+B*.HY `.@B_]#rBU OM w0(r%;UWCu+T80V˺&}25w[u]8[b4Ͱ [B@4yՕI[mb&vmȒCn Yϼmob\彯|s'4$ohK;fHXGUs)^WmLD\}gJQGxH#@U[z #0Y)ۺpӴ2/wn^ܲķ?;| ?@?zww|ʜԊ;y]iO'凿>#W|y6Jl yepN0g 2pփǀ@I݀s]yw 1Pa>A$RͤXifJhK>e)::ݫ  F!5т TUz*rPp] mrh ]]93z ZRJL- CN+2_^ ot7-xf/Uq4/A(vTӸ-i_>o%3C13Bqmِ*a t<R1t]#!"ɺi=} trny\ a8~W7 $:s#"B1uwb>גn*, qBn!۲mic䦉[:_.>zpQ4mGD}짿DY`m]5?y7bOGw}юTutKVZnau={4AI32s8T#8@1u$gs""Pp)CŒ0LH#ay=,QћBG0h "Z^ݵ1>ލ2'FǍ5P@{瑨 !!@sP8_ LDHI]ԗ Մ#^  ١37e7&+(83A FCr3DdK.esl{1Ů;L#>}?}BĀ(En/>x<\UeN8% D8Ӓ,8 u= LTTOS?t"4 P׵M;2on9>Mpz vV\N/7N>kא nV':S u恺^PݞJq6u4+ dfaO>y-2î:!ADsX ?e]SlKuJ/|g/嫠w^=ۮkv]W3bn9;u\\MXyfP[RF VW<ضmRxl(B 7m& O_y2<Օ Uc!Su[IP <[AGpkPљa:V@Ular>jݼ Ѫ+0yٮ9u^;c&D"&jbpi\q (;n6 B6@dY A1P޶0'5&DFeMkDdV$Hv!oD&wm%ǯ|)]'s pDOd'bv93dѵHj8ӗg#b@D3#۶)"-)mtQyɶmi-p8ܚ[d7 ˼"wKJb`RJZ|7CƩ|GO{?<}z@i@ M)pmREj!r7GFwuʈ;GHK٘ j%ie W6:k㴻~u\E8+YZe^o?|twXDNJNngFF2B,nJPe5tCK"կhP 3ml`5#l`1b:\WGP04t4'!*t$ (L 5NsQnZRN񛏶[O" $fbE"KHUM1h@0\7x p%lF)m[Cx$Aan'ry\`EhȐi5]()OX>kQ²{˼_|S_׉~͙PjM C !U":RεVl_A3"bӴ2N^0'>r5!ݶ,a>9Q4yM^.뻾 SG_IHliRJW}׮˖skC(kSU~HUBwB2/ 5fbty6@gyZ΀B wAnDD%pgTe,۲P-g"Zj u#/NDZw߻?__g}A3U6\H"A,g@p/3**|AHd.AeOɓۚn_݌CZ۾k۶m;wWo "۶+-j$ C/$lq]n?:L-nn&s7 HȍiTP1VYQbP uF!%0=W"gYyuU|tun[z홄\F"0:ujT)dBXJ2-fn3bl E`GEg BH$@H,@@D",R*Bd7k,,ǩg6q^6컡iIVҖ6ơfgGp:1i#IZq2s_"]];5y]E9/hX:t é^໫; [ݯ|e_=_'ɞ~7~E#0;P5J%+!nQ&:Ӥi:\\ܜQُ{a6QlkbiU"d{C0HD(ժWL*D y4uPXfa ۼ$܃Ax"Y;ՂL(ule{]yӳu%fVL~u*/2ײ;L"ʚTH\ :3RQN8xr9Ex?dVYEZa*^|]?xuû?_uMco97TXÝԶ)%":942nZãeY]R̀q՛wى=jH9v7חojoՋۋø]uݰY}ിƉT?;l5ZW܌; 6>j@uSM%3znQ{.D@f0 f&q7͟B}4sj%s?q~uqeu*E%'ynj!@"~Da ^!٘FTX8af-aYEBD:(!E>"ǶPkR})d KjU;`rrs=dbhHeI9qS, tH( Ae7$*;!"lf; uvX B\kُױzhrIt?מ?{g~ЯG=? ~S5 WD[] QLI#gm#WaTK j, TR(bXw{f= zMˋ˥;w.l7?/e~a'V={O~=l뺔5TvZv3gDۓRJi pJZ:`2"jV!m^yMhUQ&9fSnqk :֛^Tô>y^Jux@=C(F85A9`%lI`¬SMLpo)wZd jU̖RۭQV d"EX((X08GB%[n60svw4N;A*Bԁ:Yx _X!d'DN1Q8!qf!pآIEAX-eEcibHJj%p@&r*h&e, :y(e:nuV@QϻqƐKfN rǡ/RAN>,R 9+7ƒTeR^?ooӧ8}@? 4"3p?9qIRGVロS?b:̷='6+c Q[RUiOrnUcSo$,$6sCqQPy8Ü51..!x_خ6auzon>$lޢM5sN!Ę2܁ĂiFvyKDUc(9@0n@t@ $(BA5B4,"pD0B~.r1!\2NRwЪ;YuT4%Um*S2 KUMJ-wZ2:ĪTl.5ݬ" GjYmjHj8 )ã…Y$u]/Y+ 7Wa~ݲ[ !0Q׫R˥vvpƲ7{٬V<TS]n̔&V]ή.w;aNSpG1kH={0k+uq\n,G4Z+}b 1j8p cb,5`@t0G՝uxd{=|g?lwr+b@4NdN{BH8ȣ e,;3qiq(h jH:Q#pG"m{ŨUN`v8Q$MAD]0 b PR؏ -RD=.ɍղFYL튉8v*JC 4ݖ즒6+ewO?{j?տ4w}w%g[ږ0C#ȓӠ#Œ9iNZ%gj3!Y4I%V2N>szrޝq<}ҪiR8O_~v[l˲\iUwkq}S⥶0ݽRenj ! l!2]]\umO׫a|⹈dՆbbf;yfnQJ=,/n^Vn%KYVCO<9W+O}L%"UkfEIYm!^9຅{uZaLO 3s<K\_^q~Mt|syȅA nZXkGN%ܽs~Ǜ06sLᑙ^²qi%K[47Qp7jnE"4#ef6I@qw/} |#ܭz1&#< }](0S( j儥Woʤ%Hs5;2"$*0\P;yUJNhQo.A'#$& &GKbDAnHU(<فɓR*'#Yȍ0 ;6͜YR(|YWl$I]^_x!rGLfa_ʏhYjT>iw8K]"#k7=;$hίN'm7[C %fLwOူ eVrOfI@]j87I7om3i˽ve6:nv]xcT`eb DD(.9==n)K>zF< Iv10=L8Y-YAjIE֢@ h?+y#u5278Qsa^͜ RrenK E T`a]VYo"X矽_=e}<:}ʿaĜcz^&"| GÌTI.w]cA@y%pb5(ϵ;,WZUuqͥl>+_e*^#"J]yqɊ4jJa6!;8KցR rO bt+D0@i-w[vO~}x_NnBB(V:=I!YZT:0 BBu# EMX2R|)_ KYncVvCׯexZ f|)<z3Z aJe)QR09^jeݨ뎳zmq\{x]w6 6#W* _`ȼ.lf!lAD7Iljۮ3š/D(*[dxYzu~dG SY"“2,q$s D$]px]Y,Эr6~b:$E8 D0hNܤf`!<+7`CІDpx r(|l*XBPp%Zbo>ePu5s0n @>g/_^->x}3H.Љ2z,*h*Xj[_]RLQZkܡD8xsf}7W,tjbfGo<-u\ܬX !U¬p{s۳en ~.eS+b ~*"3̬ݼFΌhۈb8JE0;Us37"JwO;la%KÛ߶NsL@JY:{R6 ,\Jaq:V] gkE,l,uݻy5}׈fzq?R8jZ"Ex:@DfۯWAq=/uf^MVǀ_͉k5AV+1 7N 5'0Yi=(T`F٫kx5>-^9ݾh)S[OÙDYD"bNyo]I<2 K3i|Joj'%&HfHԨpfY+ %ťtc{!+e Bڬ4fI Bu8"4Mdb͙0^|W~Q ??35{j4mRݬ՜uq\ V6.4Is%h{gga؞lW}{6Oggw5Qv.?>s)Tz{bdewZ͸;n5Ak]}sb. bBڼY1EDTB5%3qR _A(;ĢvòecJƢBjъqN"Ym{PDK4KJf Z,-i`Y^m7fXjo./.C K>AK ^o+s (n;&МUgL\ɉ m,Bc4 x5v4 .Uw}~-_7?uzpw(<(K & `]?Z7B9jVa]?%l߯rCw\\\~y3nkY]ߜ IDAT&ż8£Ɋ Yfm=>~$)Y1/HALJٽA KfVjQkbbƆڢ_dAp!e͇dq1no?;ٮ6ս;gW2l!@UX؏ܿv !Kf?RA Ë :5ޘX܃p37Pj"s72O=o~+.$>|_ǯK|6#3nN̢QnPT= 4nǩy o* ֩˳ 7Z j诮<AzDru|;&IfǩzTUPp0+Ӽ,eYjLGTe^ˋɪ8pfIS71GŅӫ߁pWQKafrs/:H`yU(3M>WK$ 3R;{8[y@nE(WDxPILs %C( J!t\c=deޝz= 8y~u!B^qvs+ u)S;r4Q, [A"[dF a[0(@8bEsϟ|Ƅn 3i;=:;=22<΃ jDEbLQ8 trlfP9-KysToyrsjТp cTWkpjb$p0jX,v8V|v^5_ MÉUj*)`62Dk t9*7[^HZ ":pDXq*ܽsٳ㲇ke,^,,A-Aa BN0 *A@0!<'mjK(@C3rsfVϾ͏ϟ_Ɛ?u)|=O=.lQVs,I),Y"a ,øߍ_zd^i?\دWKURw}w^!%\777}7J>)פ"~jX<\2V}non4V볻gúwSkܹjYKSY <\'a:"`{BED-eǨq^" R5$H3pFSZ[E`c+V͂BNAIs$M MFO%Y^M$Ty,},fϞ."9kbnsa"IDVZ v! UwQ*A5, z)O>qJKxx] _&KL@B qeT2(L<fVfArћoW \_]^_:u]^oz,ufyν>{tݬvjO^|wDLv7}ʇaǼVZ޻ÔR~Upoo//n){T[*2GDƏ' "dDb OC<ܜ޹wiY"òj3TWzk JAhV!8\ `A&qr'ဉkIH`95@w3gf s7o&r2lVd7/~)H.ՠ _Wiwc[S4|#lp r!FD8|o>w_^?n _O`&Ue>zDXup:XDjK o;׾6yY.j脅½z{wޫSK}wzyyy}}81ܿR-{7nՕi 6,.-fL"Il)4)Hs^-&$ٮIT@4y-@Ջ#r&rQh닓;=Q2vk[Pft%avDN\ P<03Kgk0+*Ã)DLJ p`qjk!I=2!#J lZJ5$ϯnerv'y6+YR;2+șbsHP A!`Ja?W&=Ͻ#}7u.,Lm.0`U^%kKѩv]u'ouأΦjWNnZmp8q5෷ðz/l軫{;_~f=9דa 2X3Sd].O6,RŒj{a?FooFb%ww+%(V{Xj}f]:Bj,N I#Jx,43ݡQ=ܜBAQ 46!d6 P킫#{eB((S+$l"ڂȵh3y6 x<\XJ@ڪպ=Kpnoon\Hp׊ *ljÂT:x-^{}_<}D'k|te $^I 0"bv*qp"UO]z^p +]b]qQFajV`b0WyV8,-R$ x;NcYNZO~x[u㴴KG 㕃lpG8ا~R2)<(H9Q:I6yJP-~DBbAPGavpPfMDh,@>Z@YpXBAhwk),74ω;x.n\C#U"6սᯌ x!+PnR}*n/_K}~~>"<5ZÏ5Czo]KEd&Z˪o>}v.G ".ϯDR]#}x?<ʸGxL9Ua:R=f dQ>][` IE)S@ʂtzLvºl֫=nyrpr!!?Eɍ_an=VjjmJ[ws81Zd >VX0"5FG4#H`CXr%1 o2J2 3'f8EH( n2?lɽw7~?xyX!0(;BD@uʔAlW[֋g/Rzyz]&r7X{ qtp$_mO Aʢ9iKMN} ߍ˫ҰZ]^\&l7)ur|i7YbMzU$_ޤG,9{s#"9d]$4 iEbCnEV6i!hj.&̌ݿ3zR[Ȣp_=MZwfﺶm%F[+]\X؝UXnԆt]ۤ}- ˋ*ClUzsu-/XXJ.GH"T&" Fu10/fFE?qJ wwJf8}2'l|U9p!g+ԭH]R SEԔ7:8>$!^6*vśA`JIc~nz&?}+Zoo_aGVk 0$̸mZK?mӨNv7캮m<|{qu b "xI/~3R|uجMjEȄK^vgn=~o؆hf\ߒѣMLw9Zk'$nf\ ujMim7C6!Kc|zQUD&m[ѣ\lF' bP'e(!h/Y%QrgV0U#VQЉTC2Q"b׊"h櫛Ikv,rl+4'VDF5v9̧Gmr^5&0<0;O'"}~NkXn8$fb&'P6æ@DyB$IJ<_~p}rz/͋_{U\EۤTMꛯ)X5i_o+oT#Rbœ ʔ7͢U2妍MaRj===)TūyTUs81@r$XJ#s(Uͅ.fv].R۴MSLk-/fTˮ*p\Kv6" DpȌT91+ȵ‰݊ŀF3\ƆyBia~ .fI͌TH] z{Lͅ<ܭ2^n 5f*鶞1q\Fc<}rǟ |p~Nk9$o&R MbC, /Vr9Aaf>{ܕl{ǐh?T=z^NOOL{o[3U)֢d0vy̓z ]wQZ>Di-V>>Ms&b0Gw'/7JN wuriÜ@2R+[j1e{~q q*lwݺ)~k`?aֻqa2 NpX|pZ a fE N#1YeZth^󳯺OA$H(fD3Z DNBwfpbpB0ʐ8Rp2Z&zwy|>`x?'uzLAh>i5 "Ռ޵Q59>6 K$b~UWm8֪냃'/_~'?/%w{75k_䚋u{}͗?7}?0=1v1UL1 e*룓Ɋ Ϟ>xirk?wwdRR~x??<$O߁KͺێwjRU@H跻nkVCjI`"LKUk%"lVgӖkS1 2 fX.!kqW.+?v0Sл\(@n0Ldd @((UW"e@ *\xu& : F!gӨ`䖔9@`a3 7YMɘ0Da2?gϟ>;+郳_׿~3_O^,Z5wUMʹ3*is'  A̼H 35]<:9ZV˴x'O%mu}~_cl=RpJ^nXOuNj.cjZyu3̱GK65 %VTHBNADK1O%ZօRrĝh*=8j,l u 'jB K5̿Qvw ?E{!@nA椵:Q0+$:Ea v*E"ynݡL&nRDpڔU*n,??ϟ;Π}xˮV h*l"ʣEyi"֡LnR Hy9NOeOLblmL SM{nŭUZiݗw|Wﶯ믞==r1uT U_#p>F[xq!R3W&="9""v;n[LeR9y*(QnYNslVD`b_'"ͯMTZ y浊`RgBC@X@w^,T+E5j1QY `-əY 9؈HME ݙf^ (D1sǒ$raG?|Wx=>;{{߼o o n4_90%$$͙ @eG'_&5Zus{c[[#$s,/plv ժUgw98>ڪkef;NC5MBEN~⛯?_%~㋫" ,Tzs#)| 6AB 0mjn*$$pUAJ_>{X-%~%Ww$avD́y52f/%v<=9"U(X浯ɥaPUV-3jĬ[VDE۰ L;ÔԍD*d`3SV mxO?{ɳ9_~NZz! 0jĖ)`VjQI MeZ, 8ַ}+HmJ?0tIjޑ$Ѧ}Td;gϯCLh*:jf>o̙ΜK B𒳐HnrLD$Q'Vjn~X@B)>]a, e Wr6'xuL8νm.ӸD5Qst!{TF281A)52FzUw\\s d*pv % jEY5L)@h-  JN8<:lܒNӓ~tb"q{AĿ_HBȪpAHk-" EݎڦK"a&oHO7zdfC?W_zØv #hEA\x9__&YB ifaW<%3PzslDn ]tww)N_)S̽;HRn$ I /#U)Cd"5y+1v?֝_g9~~'JJM,17IDT8Hf̍8JmE7:::>>=rOƔ֫En\vh2a<9jI1qHma۔ak\o ˏLD"LY0YVw7Hc DXؔ]uV%hND1E7lnJ- sU+_qYdxJD ϟ/j-˯=4mɵm39ͷoq)s3ND #3Y$,AݢNhT)Dw &FrHL[B9]nҶlŲw'D_ ğ? )}˛l IDAT!Ztf!0idPt> 8RLkӤ8c Xί^GCSJbv۶ͷ޼Kz;ð]ߏ:&5MHM !&WWz-q?ruc_ LNŌC`&0Цfs"aDIQiV'c 0u%r5e8 ̡$&ݼ ^Y$Trv7a.Nm0v7\Ei F*,^ʜuDwWYDI51@B"84iΚmԿٓGWwyyrv`x?' b T EͥHtxc-j)nB9Ř(U%6*zq>9>8^,;88y^6%~q۶_K)5)eXJXJhۮ]m 1εns{tE~S=:9^bFR̵f,2oBff&"+ W#; ̅uƉ;QZ7Zf*EH\W"rY y>!rY;f' aZ>\8NËMnD#Bd[Y؃d[j֪~9R {)S^u Qbd~T}r9wRՋjhB<:^bfb77YvMj42rq7u) , ^i89MD8M33a毸ڌSHpwb`B-/nQv #8)HE3"U1\:X(8ѬE6O)ŐNfs,ؠn3X"zۨj`uj"V@:VKPu# C"at/0/>{>~NyjC͛ȪAĽ(u|5}?­ )9Keyy^/f0MO'OO{><>B΋f!Z 4 ]0uA\&8hbtykΓe$!1Dq8 Fݽ䚢(E.Ѵu~nw qdfQbpڼ 6 WprH^e|\,z-le*41T\:1NUY`t1-aӏZ1-F\H(xK?/ˋKE~gggM~NRwԌ 1g))j)prVb?_xjS(c}b^v *&_yK~,Zu,'cjka.;{b臡:=1VSLǮu2bVvqKj3%FJ0v$J.bS}aM1I Sbpͦh-ս9- q)] <Lf-{#<%OWEJWWYk٦96n Hi6)Yݧq0ϰQͤSu|wOf[<@㳳o|}췿mQ-L'%'w@qh\CfL@8Vдn{}筷Eh`O|(C\_=nǧ!jo>J̛fN2u]4̏4AƱH0q:>>\,Ӥ`Q(BbYSs#^(5Xn9q,Af:cHL9f^-NVD\ZgEn8r跻~,ﲀ,YhrZ6܃uRZv BpEO@C\}:Y pur1v!Mኦ!t: Bl_߽rb}-,b@-U,yuQHHS4ZRnXt]LhNr?}r<\٤Oov/?8I`|y *vyXk!yƵCS 1ZstLLfl̳nBc88,b͉G/]]\SMmHͬZq01ZieX/5@Bc3 )h}x><\\oou T`Qxa BCBN; b*s$nu^,]~?T1W^x5CY8D$2g''o[ &`pg,W+uqGѩNc-UXL-[,jQi>j\Aŗ׷1E-eu<|Iko!~7٘f{,P<0d^WM|{ShXd8Y}9  "U徖қfcػnYRIɠ~o7GGkgVUXAŵ:h><$p=5Gs (&Qɫ1+ *`E ޫ (Iwq6iӦ&:K-P˩NQp$0k5?_/}D3dC<{p^V!ZDNkN1ܮÓ,t~cst|/o֫~?ke?7̬65ml䮵zi?Dr:N)0eCC%Ik%b90ܦ̜B3@T#ã5͖Tﶹ 6a (p`!Qj WYͰ\Rmo6SS#bwH!3 UeeBp0B4ӿ{L)uOt G]לp~u K-rr^?/-T}oַ_Rۜ!_-G3[֩Nj $Dӛ͆kGMj7׷)q::9r۶iN'/ Ziv)4'ZdٽItI@Zf+[o]|EۭG䩰ɾ)~j8}SHܤIjb"B&eGF:XaO(*E)p3N+ynovZ'y788Ln<51!D7zpD08~I|lnȥn^\L U`Y%<-D0_93Su -cnQ`!sDV>"IK-A v|b2M\XŢ^_e:9.(lp#8@)c_ɘf2Rs//< 3=>;yr_{"2ApW;94ݼ~w׫jR/xl6NibۛÓ}PԢSo]]4]$ry*9AVK&f1h\Ţ[,XjitE׌I_ @gqubX0~? l3%6#!nP&8(Dsgw$F`[123Pu>)1aSyq=cѴ50$3#*4۩/PnG)//~O.^܉|p`x_s_XAJ W-E-YT]#IZ#C?[ovC~cIcU%o6W_a]WԜ0Midhې$ŭ=3Rmn7n-#v+Z<\bj^< a=SQԱMkV8̬zh NwvE"hVJ = RԉɄf!8& bU$F}޽s_~avCߏE H1 y5vNťv/5 T$n./O^tٽ-{ǐߊAnDፄC#8RAQ"0 >8}z);ls A^ybV/Ukisjc@Ҝ6amL2P-;"^@Ӵ 6$LHfjg Xh[@!R~g9D,n(1U-" -bafՊsAIȫ*X$))Y"N͝\'expۛXnׇuۆL K싏Ϡo|O￑'SϿ`i* IaQжm1ƽ:nXZmvZ:撧Wm7;-ZZԦLBpdbj4v\q̻}tml u9IoZyfonESɹUsbs޶!3kΥf;]YՈP4{uAD !),uEK\ LC&5VeWsp"r }6{@h_l>?|;+}p|_ag]@,J1w[,IdƩ_}zy2?|ۦE6qE=zĩgq!M Ib 8R N}A34:Cӥ*5mGn7a",4Mbjw۽Z:>\7m4C!]mbN 9_<;w~SpG G`!"wR` !"?ۻXKk=|9Uuz )N( 'VF$ `$0($F<$yI$b[rlM8ʤ %R "z`wt3kTzIئ$נּu_okh2PcDѹj6!cJLgH!İlÑ#V)V3ƛ~9`sw_s GW#*E(14C㆑PE! =ytVY#؏I Vk !~5mt۴ sۦ VT4]lr[U 1EZKEeuִmz_Օ5d|^UeYeQ(r;z?=!0" Te+$mvێ*PEbDجޭ (ۡ 0b${.+ )mfP#"цX$FQVyk7 Gǟ|$~EQ93U- A? EV)cA@WVc,iڡqٴ_.)ǻ͠'iaΉC2':Dd*D EpRդ؏FtopR( m؏j]-e=Y-CƇ>"w(&V#a]ӎ1f%~RW`Q$8RQʪy C/WB_,8pc% w Iv(2~pHU5<` @cdR$ ‘41#'ew&0"dV@WhBݶc/˃ G!&7o+g绿z LtNS_9mVJbȌrtY@Ex A:CkfxgYs.lkM`g}]:#a:熮jLqWWt7yngu&% !LAbC(DCWdZG AIk]vpZdBf2McmAvVBDp v8DFFj {v QZ "HF&P#Esmϯ_9xrrS9rΉ; Byaj\x6ZX_鏿}yK]N/v`LˋGMYHD#G1G0 8J*`a'f/'8fE^T2UJ#Iv`Y n6MU78?nu͕&UXq]_MEVA|ӽIfͦ~q6LJ@{/ `QN[V}#mGZQyE^"ˊl~;w +7_]-WvEx䩧R tN;sVΝӾ}]}Q~~p:/~w'ˇ0U ź:jh-b`5]vۮ;"0ZfV*Zr& 4ZcYfQ zf!`VXw~6's nk))smO>zMQ,Jsa-h*jm1,TSvpQAADݾM$ HQwoG?MBQZ dl[Z?C'''O I:'Kkhjϝ?|haƪj[nַ?rmo6+⅛oԓJP{c,yݘYty6Ͷmvm^)VƠhd kmTthI͆ιRZQpA",3Ї Z-l k.4hQa& n@*&(7j0l2?dٶê @ H,H  pt(kr"R$ӦܭW?b%&&͟+<߮7_~ҕȇ7skz޽r~UWZ]v>cȋ\;zim4m*CF!)ExZCT ftd!"D"( "9#81`2-£B !àQDդDYfc8;_@?I Jk$X,@ ,BDIXC8?ϳ|^}b ?_MC wS_H9L}skC2ʺȳbX}lY-7/_}cjt^>jٴvYo(hͻ,}ι"hDуs`|^XlfD<ˋBdBH8v]׶ǐgVD!(0(1(DaJxII Qmն'eVuY cD"DfMSCr4p_ϽxzO'a_R HeyQlf.(,/|0d\:؟̦v=;;ÀD, ox56 _菌onެ7_q@?FDai=-M p@ Z1VUy2:o ",!8C?5j 1>R", 6H"H$9W)B`E6.Km۞_\U'("6D?w޹wν:l>9>_Mj}ާ4)Z`<+`ut2(;?= !VеCUUQf="OHه{o}wJrΉ?ğϫ\Xr!z6JgYٜYn6c?A!{ojR =MGwÏ^Jwsݰ^o g'om;n}8^Wzcs EOZEj*RL|1r2w,p.c؍;$؍af-SwWųh$B,4tݥ!`^^`Zmb/ h]$g>e 햔" oF+mlVdev^oWG}x6/q ST0 0r'= cﺶcмum88!f]ԥRv܄] m날1Dktߴ]M(03Dydݎy "Z=MDD`hnrf1T jc2KssZ CӶ7n4W<>>yd'0IM~엞( TZC#&5)+&Zm6ȣ{{gmӒX]M^ZE۶C~65vtѻ|1|,Wmݾ{QOaP2sL&Yd@EAFE:SvdfDH}$ "C,I|ΓҀb@7˺Ί8Koܺ9"%$n⧟R0b2qda` f`2fSr~~]oR.Gכ~:=zlv;熦 fl@׮^; osxz5?y!z2Yxwpݍ$s`B͖"Q@TP(Q@@Y) 0 {_{;w'x$IH&~ԓxo2чq%p]뛾}*Qb^#`ODAp+ڶ]5!Ua|=t6f6MlLj]?,qv5p"0Mܶ3k#` " ! $%Dh>H0@ƍkؽda@4<󩪮HfbbPF mG.JO|1\|93 5v\fY6ݛmM߶M3CWZe*hMe޾7mۦnmiE)r5{ f,  0/CeCDD͟(DEvciv/C3# 0!DaVDaϞIc\7n:3W~:}FN&~_ȬZMN[3 1L&&ĸ77:=vC z]Ӵ﻾;gruFZL[E$N~ڵ yadY^QP! K -Bh C5Ge&("@(DDݿ A ;1@"$mH}dDhxz|>Xn-4 잞yI>D(QJk@uUcdBJo׫|wmgUEGi}ˁctຮoΨ(ZYcVr9_M;Z\,IݻBEi "u״Ͷ#`e#0@4"xD|!dx Z-,l^Νܸ Óǟ~:=̊`^eZ0STaΏ}7vh2ijWfv0ӊX!E ]Ng/3֌ʦldZej$FяEMftmD˂y~ȚT\OI)8 ""\kPX,YR$h$$2wcQ߬/Nx?~R$?\Mo|:V,BȨcX7۫GʊRܵJcQDͦ(l6wjEݻ(P)DE4 qcU΅&F"Nkt&_}V޾G4KQAbժa$8(QAd䨕EGm`BMѠu&Ɯ/v|;<JiN&W-\+E<ެlb,j^rY@FI$l,sn=_p8zc9Pjn U"|i6rtn]UUY_ɴyH! * "w= BŒ 0QP!! JQ4"m (aDMݳ7nWk@$BΓ$!mg3eYZC"3c"abIEYTFZoWҹޛyFHn'KG_{95VFoE^߮ukB)\G?N:M H) h,ϳ,l~QW~[)>LgiҨ#33:Ei" "$($JYEUmk֍nj~]!!>~S9r&~_rt!%֠uU^gEYfYob9?L!V{m7=8<onpahcLfjA2nnjRBVd@(Q8r="cP?|'~Of,a/˼΍cay(f"RdʋFoj_0I| {kk !] e5ۛXgw;,|>LCW"߬}78 -Z#oKCl66A^] "he,,!hD#0'2DFZ! pښLfv8}絛^_n<mSOIΉW~緘=ő3eV}pp'l."G ϪAnz;c?9"/%7TG%0!HQf""n-ЏM Ar5tR,W˵s a7/K;5ܝ 51&Ij8$~s{}̌YuC/es7 ͫI="vZN\;MqH&a!lb.lt!37qtHB LVEZUl3bWYUw.8V{jp ߺ}+wƋ"Lf,2Y?t )c{79د'؍nt|.2V `(1CMe2G"%FHB̈ YÈ*D6 /}+_ F ?ԓ%$IMW+eͰ72eLv~z4=UӉ& B ȫúF?q8*K,c k>/V REif I۷oxs!IJa&/<]hAnʼM,{3 QXgT{U5~`~ä.ʀ@c:S.@BIQTji|~]g% ;cS $5⥗^__Vt23dUbylMg{ȻoW52$p04yN%cb ]?mWyW 2DTjFimtUUӋy7yO=dU$&0ݫU[<˫,j/zM4Ę$]c7(,Y!1u}״ȫPD< њl|1^ҭA 4:IR˞g3* 3c ;R̍eIDAT j\'eg B,o}yvDifYua2 @XY&A!" U5U10wW^Xjs||IHuCtt衫~!ġe\^UY^"Յ?ճm,4 V@ 80pf "H4 (ym/N/NˋY;>iPM&o<}z>AMwl&8Ǧi"ԓI۷C7 (ZkT$2}wEDBJS^f[/׿}qA0 $5{&rP[ҷ_rtttj׷!$畈Xd٬vMq TdFkD1țvZO'yq kw~[>v|SO H$cp<󼨋z~~f\_Fbv5Nf2fmY}ۮmёQbji&)z{^.Q9DDNNNJ$y43|[٬(lYg$صmg/?²\,;w٤ڌ!?n7z,K7?r|Ot$IuM,j:e1NY-W}?_=*9ׯͶ'GIYy;^9C$W/}0V fZm֧[6Ew5D4+Iwio޼ir[T׿/⿽Y4$+Zٴэ|S5"2vզk_A{:HN]ė^xo}=r?f5y>qrp_TECv^|}!'+@Ҫ/5~7;m;Ms]0ܭOI //ο/I}tZUW^~:xإ+G纡Y@4(^yݻE8rBt:I&| >Xp||;E?^Q]`լGM?{E@XNNNx≔$IϚxq19 ;~'x㍿k#ݻYөֶj?9vmۍ֍sBǎ'Ó$I!ޯ/`曯C?}(C1o|1/ aDKj$?'I޿7G?c!y/./="GD}nc=O&I\]rCo<ēWի/|P`7T$I~w^#kG~_}K_^IJa$E_ #GWx͟G~l7ïVr_Z b$=g,۷/Ͽ7d^$?tou|'?S)IН?Oݾ֯ӻ_~+_w~)I|o}o=?_\w~/ߌ$I~HωI$N$I&&I&&I&&I&&I&&I&&I&&I&&I&&I&&I&&I&&I&&I&&I&&I&&I&&I&&I&&I&&I&&I&&I&&I&&I&&I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$Ijb$Ijb$Ijb$Ijb$Ijb$Ijb$Ijb$Ijb$Ijb$Ijb$Ijb$Ijb$Ijb$Ijb$Ijb$Ijb$Ijb$Ijb$Ijb$Ijb$Ijb$GR};0IENDB`PKF 5T;-Pictures/10000000000001EA000001EA24938CF1.pngPNG  IHDRDm pHYs  tIME ;^/ IDATx[u6k}Tl6[؁ȈX)[ĉ@($RVOyHĖPr9%UXnfwשs[kΙַ)BX!\U]uٷs110000OA} =0000{`````A} =00000{`````A} =00000{````A} =0000{`````A} =00000{`````A} =0000{`````A} =00000{`````A} =00000{````A} =0000{`````A} =00000{`````A} =00000{````A/`<_#H)W_DVjRow~Ƿw|x w@? /?xDD,LIVEKպ|?|S7{``_~3 د#0 MU]U3k<.D段XѪ#?_#xJ} ހ^z3,LDL)'bf&1@ VV)+R?7#9%7>ӟ}ϒ0̭R]7#fbabbba"UfVM͕@jfiJF긿}'>#sh] KW_ko꧉𩇯$rfy7;б+L$aMDHxJdfV ;W"bNٴyx-F=00gןm~Oj-ԈD½PcI%ʹ.Z@P9(403Ҵ/rb<"i>;[W~qXς 7tӟR" FDZEbn80'&"28rX(fJ- Uq/!Te9VdZ7exO1?/ {`k?;~_qӟG] 8ye"Z1ЙSΎZRj5P/)%Γ%o\iTT{Ι rY\ J,Ē,[|ǫ9{`݌Ws~77>π(;]ݺNB@sH<9ONkeu*L9ﯮrx΢JqYJxf 3`D^ 21ܽ ' =OA߃mggWsk Bfw+aF`b8\ PK@Dv3Stkpރۗ=l|]qCYnyy7;n1'JU+p$JWղ,XR̒fIBDD퀈$S74Vw7kz ׈(IinR\e1⋋;7p/vCh-M-%½qeiƭr4b˲ʷ/㯌ܨ2S^u<<3凯LLhL]ӽr gw fWn!mDi8pgA7R7*h3a0Rٳjz'"J9OS&m,4ibbNUKiJI{738+()+p1 l= : -c 97"iLTUV *Z'lhi8$1q ANҽW@LL/ x+5wF[j`8VzO`096O? o/L ԿA<5 KNYK$MLK]G7D$yJ=Sʙ`n*~fW3B<3\wIDs5'kOLR7U9=SuSw77J) /Kq8 OOWwU e3"p+ \I$L$%1NJٚks7s>A^ xBNt8t"j3ɤkq+mOeV2x(5> t 阤W8'OEíj nniX8wBtsGNt|E/}j sQi!9ۨaۑA&Etm9w*8!ffZܔ)OStTo}B q01Z76A]17GuBr"pH,iqsvZ]\WL͉H.q{߃%Ưҟ/hpƽsa!(H,L`>;aŀw5:U+ZR̉c J$NI'>Y>nO',IrN7e? ^k+Apht rJȿ̜x}9m.Lܹy$9M3Kjjڰ8_[k+%~O8Dn*)ÍH=Ƈ[,ZiU$ʒp =gO˽(ۯG`Miޚ\9%01jjA+yٮ})FSBtm'3}CzD(UUWB,")yġz"GaDD&HJ,SnK!pD![U{?o(_]Q;uwww"K8i.Ǜy3ye|ͧ,-l*ړpv+s=sn80'Nj8ME~W,DT,$h-2̍%0'_Oo{j|=|;@1BDp=z[zJ(-^y` _vat2xw':1hQJ@,]uqwz )v) D"%Y9*  &U0q[opu]ۉ/mQ4;yJ4MRr[PbNy[n)kFhoԭ7fSVm[\*'+jd<2l%]Zo'=75b;=u̡WԞޔj5,"iLk- R˴̛;ۋ JCN&yf{U@⺣8ltl; G/6a?jbZ(%j]4zvG.;na[1IJ0rl1s5orp\?~unj0ji(eQS3$ACJ:4'^zyƠ?7:S9/]†|bMf yꦶdO8৞Sj8+u۩@r~pP?P IzǓu])$qS;xgߛPDFZOD $xrǏ4O2M)MiV=\_/ߚ> }hy&in`s7wk6&j|iy*QD;Mt3֭swz=xtZfi3yefAofg!-^ L")1gfFJr|9mYh)-z9]iJiN)OL$(3-s{vDYjQǜgH|^`dBGH,RuIy>ܤݪy~X*afѢfN fģlۛ+6\܋? }IeZl@ p f\k-$ѪZ*S=~}g.:ѡdO^WO\UU5 a&po93m& Dm{#PUh;{ʅ;ψwI/=~3I](uSY a$D$`ϳijEzAt&^ݾ_ڃ3qHH;]_ u(:RRC]x<9ݕ,L&f,ES7yRd>r,/qiyMfl$O'"$0+|LPL`V A5[C"̪50Ʌ$PVwHERVy RZOܴZWS&H养 9inӜ ([s޳o]TA$)njYDg#avN78HD$תeYܓYVK/|ﶌ7v{R-J9]ӹ"at&RsHugSD(Fa6q/M5@HVRgMLaYM|:B( :&^۷mBMՕgCBDve1]?N1'AB ~6+~WH'tNbp^4$O\y0=%Y1Oi xneYWWo~6Osk Idڸi9WV஦*)Ħ?~6wTήF9}]!O%:Evo]=Sy5yf"i^6>aa;auu>ۙb}'^] rM#P!.G;M~ 3!G8\wj %māָW~Tc@:8&YmF|D,qb15ӣbUͪ%9l&vݝynUkZJVbɉsΜXu9oWr$9iHDQ=ruIÚqݽ(>m1:ǁukxb:VVSK9V!fHH3=^L=*§i"v~Ko>~|7o귾i<KS%aQ3Z"%2Y0">V0z}_qM '*ǮKxdcrN=VFO [mQQL#GVjsj/p+{DJUS35n IDATU$3so18)^pbhuΒjGibߵGvn,rRp+TN$gJ?(1 ':ќ$0n巟 fռ1'&E% w|ǠUB9 drpd1iv{f#`uyܝ!OI[N9 ;W-iXH(dfij9Js\8%Neff"fI'yɉԀU{Y3m,miژ`~~?⫿qwW"ܬE0 usRx.VW7o/>KjEF-|l箉T[yg6!6xm0pwr8ݒ3>''UO?8rZՋ_]ȼ<φF013IuqieUV")մjff*"$YH'&$9ek "㜉)VOZkH84X땾#A=VX{(,wD>fhm5>4ܽ&s}ܾlOKRuhXo3XGLUU+L,rJLpr%/x6'n=?)!'oeU/C8NGLk؞"gWoUy)uYj-7׏Ky6NlffB,'"O5u3jfs8IHp$YWY9;eIɡ]u^hh$S"̬˷J9]<ݧL@$ٵ}貔pH͠fnujfͬVsĦOo.`9YZV Ys>s@ZB_}L&&17S%R;mt80C̝oKk[tHrE3>Cv'~?ā_D"xxx6Ξ*u=bc yܟV*[G!;L0"<^hʆ- {i$L bjZ(pLGS8v[Et{k:|P| wNj/T;XΎ. ayj)Gn̻ݔx8<ɼ?8HGvO}O}-Ԇ&Yj)ˢK%zn+ўF`)$ P*sU3Gt=UL?:cik`T\ ba2{OrPփ;mkƭ.Eܶ6op*ﻙZ^)NUU9Kv769ԉ\ib߁vK)W///ͼyTc-SdəRi3D,$BKV֌V_ f9B4MWe:-rH GCDȋ;Z`(T#@Dz\$~?ϛ$`€$mBu`9(%LC-7([ef$S `\{>G wX`y6r3~!k1.6S$1Pl9ZNmŚ[.owLƧ~nmT8^u5t71}c F#A_3/~Kɟy< W$,n 3W=$.Ӧzp+"pfXv)]!ZJh*Oh]yYvӟr OAtKATVԴBլr$B(%J"$mJ|1BI|:YօuL^ZkUKyN9t-^ZxT/ǃi=3p)帿e|i4j ӜmnSfqf0D=T糴=gpC;%r k!wW3}8ΌRf˷"w?~t3Worg%OD$y3hY+Ӫ)Ec;EoZ5w=ULwx^I$ok:A :`j{0'Pskժ,fE-L7[Y\-/>.g~G_]iUkkLr<ժ?gC~'OpoyI$D 6HN))ֈsK`x-G,Q8Z۞ng[iٲvЖ]~֒DV6.AB@ 7,6"zM} cuZ0#wrgodFwyY'86µӼ$S7)gĬdBZ?zΗ(ϛ흧9%[4ovNS)PK!>LD7qseZs[\6/e^h{J*)\af.֢NDJIPU76:VmLb1S4K\ێ :o3YZc=.Vk%0qRʧp{{4no8i&CGlW;D`j?r [).yvNlrD;<*sOm7 Νgyj&Nļݓp 3\.~ß_ ¯o~IBD^|}淪QpEffd|{35"/|o*}UO]=,%ù+3ѹI3W5BY8KJĄ5[~ƌV:5#vj)ꞦY) o;x<(ܰ,cv3uh, YZC2inv8%Ĝ #5:d{ ^t3f IjeDJ _k)tx7=|wSMDVaܳ80N7ii3I4(FoLgD$)1A[j+J)q✄/8O5r+}d\veqfYMy3w4"s3'6Vq9l.]t r發z,rاYҼJua@)Tk)K|w6f춒A&mu_ug1cM;{Z\CF3VU҄ _N"mɛ ERZ{(i%.arx=x\̫鲈K2vr^}N'gYuLN{ayUSNpSGʕ ?z7^W4oHkq fjX\[ސXK˛7NJl94b̳?wwse9.JgwG'/ꯇlPO_}'*_?‹>~ƦzUUNZR*W7w %u!6kq~kfσHMk͚\<'&1cn^%qo:&z@)060ܜ(OSX{Y)k1/CHN!0rC- s\Yf_ARyrusѼͼلϬS6IRS:P25*UkbjcTX#3u C4zs8q7$ݹKIDVy-i]Ѻ\]>yKӖ%qJMZVQXq<iIcbgºpd/iªUyXAffpnV#T-e1*тbͧU٭*$&vz 7O+?YbkT+9oӔS9 ;9Zj)TŊ)qF#>)bjEK?A_}׭ /<7=‹m乽7U1h@:وv0;mi mP$I/RxW<^yba; [: dfjW52%I9'I8mF? 'Sr)gJYw25#GWL ~9t7iJ{-rsxD[]~9hZ yÒ}כKBZusz=|Ŵ"lyNQjKY`=38Gp}xoI:?V tyᛞyEw3SM|pPsfw#))U$kX>: ך:l'IgkVmF4;m?E}~*w%I{F9e&23DtZi{j^Nt+4ߛݞb>3.uFc|ge|K9[-)y͛$&7F̔2፶U*1Gr7W@|s\p;c^Q0 Xh0ha4Sks !h aӽ qfDHtUHw"}ݪD/`QUVoMX3#2ntU#"ë_k4NjwELTbxI4@,t>]Bea K"s"pC3DQb b(jڼ34|u?w~忀{_[O?OZ@ܸfwkF&sagaQFh]b.F!*y<ƅs0:ںd %[BD9>ZSy@,,ց֚ 2nn'"+/SsDV'"aXP]2Hʻ9Yxp"D LH>%6pw7n[g0H ]Hm#1b@u\aZS3pke??qc^`:ܽt{`\E,+"s'q_;->'4E벰<*]qq_3FJ-Ǽ3ֲ_ ۭd5НmHiHnAN/^"~EuZ),)Qb9 1 ]R>-rgI? ox緘]hiK(FpepT>4!\Up`vLnp㔐xɴT0?޾X'IC'B4[?*P#M[S3K9u/9#O./rc`i M2ϦM<pspuUGz7gjSm dV0"bG3.0!*`0yDD%bg#ZD%o~"¯/|V?w/}kw-5υtqqE$&'6ӰB2nHc % &BzOjiȒR$Q%hfnyǛUW.a9q6G K,1 Rhǘ٦$fT$ii ;j-HRqd< y/Ɯ=bEc3ru7D8yyWgJ)Bښ75m`JUBlsAr Kl1Z1 C|[nvww_>_} mߜby^ _De=9 1@+{+|szPvuw2a ܬi cXo~?嗾Yt''ޅrtSQrd: ʐU:fOG*C6jx9ȱTc9yqٽ,b5h,|ȰB@sG3!I"~{P uD7uU[Lh=4xilRp)F"V|<9e$)IJ[Z43'i^CUf[ ?EosSܝ )u.j( "P]#FGzWkؤN:s/D֎H̑. 9b :5 ;^LQ=ζ[sF"05ӇO,=#W ;R\#I>ݾ IsFDo:^k圙I"Oν Dy& \ygϟ t4ܩSdˆJ%}裾fE:Gl*-Up%s>Nfz_NS o_9i)VK-D;@ 5պwn֥k5kfNƒP%:5) m5n)K1_ÐҌ ~3?߼yp8L^ݿ=sk^JL0*1X ñh^@OlX9Y#td ƁYoW?+?{x#}x:n=vJ,32n5p7U",$d jE_dC._9D8Lqrζų5({k#b}1,7YqK@Luƾ_-ݡtzYfԦ0N,i`-:O() JXRb$>k%HLsQGX[/V7oX-VMWjTmNA]OYP|YdezHfBRi+ͰJ_> DnsS40 K-zC֥u#FaG!%o ՑgU \9v☒!gXL KFr@9:#-@"|K~vv;Ox<Υ㱴%hkP=Z<,=9]Y_2$p˕j# .*{~[KX?"W{z~3("@s,{Wtq7Įw1,EH#Od\~ |uNdGAd3vYC ,<SSDogø<|hUA.Hœu:wu: ݸ<Ӱ]oڬͭڪYHlv]# t:yjmUkhz_VХ .u4 ۽4Ll:'"`If}\rh-02qTKeҸ]X[&^"pwGפ>enZ5Mc,$# (gSy0Uars508*qJ!vu!E g)zw?|u==~{{{T5@fXXDj3mZ䀒0;3HwGZk)$a=˨Z6!# 0#1b1-2 8YB056|]apG7PSS\p<>o~_E[}H\{ke"t?{;|{sA9efiH@[sSbAox'ۂg35'Rpr 9Gi+겱w7VX{,'oyiU37k+*e:͇z: mv2vrŚׂNUuBDm筎sys.kޚڴ$L( 1Kkm6NfbGw:OGyi,8)c XȐ=e,9\5se'fQp|~%vz8ޯ֚ny$,ĂA6$4Yԁz+n_NGɻia8Z-ޚ !S$ K2V7V;䬎wZ{s3o lLRSN3R[8ϟߎvM`ZF!BXr'*z@vbrv!Stqh˦m|֟g\1QSP~K͋ye:a}-{/K!^yS"jRHR -mnkA> LD oMe ;ڎ~;F]|΅7EIHY=H4r`W%HYU>6,|J"5(rpZbEotQۗO?xGos+}h#Km-!ikj3eD˖a5}@;K+4cB\+7o .5~F{@jEv 36fǎ$ Cy:J۽ "u$"pN5b,tpƸ 8'V{^5f%Ѣ ;~ԴyZ*vc3D lhf=>.@o?z$n:ϵL3'0wrB<WnR8l2MyY͕眇%$9;0a)}!0 !w4LpsS\ ]G &C $&ˊɬN31"0$H@M{Z'e(g:o}B{wsv݀6DBI±4[ۿ_UrˏޗlyO[o (HkݘRUl!Fk7,n>kB&29v}G2ѦUD&Y؂u>v bv7(^Z\̑Pft8aNJ"sxh1D\[ЎÑ āA"L4SUmtʣita@%6Nc4BRC:oޕrJ\2w%5k#"N2NvQD088y%lff3OJD[u`7ug&I, m1w;#`:jbN[ )!G4rd 1k7Um9b|w>0TśHn8̼R$";p#V.T@(Z (h+]T,~I!wQ $# v 1΢̺YTYx RX9 cgv\Wit8V3G6M[:*45-Zj|li|62܆vo݁"kYR 2Oq5O3lypA o81z)Z˚_={"o7f4H6xC)$R-DQkCN &ỷ! 37k+xve)'20 䝐 LvWwǞ~])s<*ܛU@7CM}B%1DdTdDDD!=pkQʜk/DZR$ BCbnJpP? 0[d@`YZgadZܚy|gΪ{Hb2ǯ;?Y!_}~}zrH>7 7ϟOHIN7qC@-y.8j7q.VQ?#\EXGKZreGfwj?z,)-`Z3H=TL([:n>fZb޳noQ:9YnIO14E\YZɭINz9߾|F0!kmпSexxnF __OUk5~FHHNއNBZ=zTwg'sgīG\evHZ飧2$&y6WC\oeL8d ='wPsBDp}OɐD4coOěo_ ]CPnja*sH݀ ND"B[nfZ) YgCG>\T֊ͬ5$.es 4AJH$ nB505̃d@)[?D,o݁WY}U~]b,?~''\By_|w7¾Zᖠ@"ZEwbk   @oNٻ@-WO˖?=l"lϯI:̆e_; a^X:zZm7h Q_3o9Nԑ5^Ka@&m;N>͛6hMk1S`y$P6\j7F&B7dចL*"E o8!zִ)?ټ}a*"+:#HPa!j,5 pEw;%CaVJeQҀHS*eFƃI'On?ӗ)23VpȌ@Tw_=Oԏ|+UMDXnE!Z*_Wt%gSZM;~y:8<߼暡Hn 攔5ښ 9]z% ɺ(ʸ].qW^Wf{+ ja_r Hr0$A_;81j+/iw0B b$* p:z?w`:nGz$ P3զj-^R+ HbcG0RRi_͛[m.3H)eLr>vOIWקZ+"$䐮wFFtHR+Ŭ1(!`J8QNMٙ5R\0Χi`HJtpS33w~GO?7Ͽ>r)65,x5 BL  \˙ E*?FAw>xLTma9D{h}ey,VKe̮D")NhH)63h){hQJlCf]:AW\ ң'%0oΣ_?u<{llׂݍZrx#Lc+ D~k7`O]_6?_ܾzu<V3v%-w68DxyLL |hZR$%$L=eѤC23xS=gWUmT("\Q88+^Bf@y@ 'bԵ7Y߿^[59]/:mͷ6IV/>x"̝=IMʌڐZ(a323${Y4y=1`#\:#y!!')d CvYg1Ns#Rw(XZDDS2mHkD mϟ1j HXMyNZ+SH@1i0a47g~bbRn1\)MHEv8Owwc8œk;sj:63nxڅ@j`B4ִΥ2|o㷞<:~sv.U^>}I֔(i5@ 7ATVr:z8?FTq։(`W3u׮qA9~i#12 !1!ݼ͘r,sb N9nV6!!]c/˞b ;}@D61tŒ̄H b8] RFydj&52zPQo/~?QR;?xJ[0C2t;z}'qb"brk ' |ĀZx̘HCu3> Pw1"9xbrpBCGsEPBox@hnhT݋W<谧*A͓jr(2'&b湝NTI8BPx3#DnA9+ޗ<~э;dvqܼ|Oh18%$B@SLsٔ1oDnjmH*/?sP0Yht`[D#])"#SM]́] ^X8GXyHEnf\A9P7ohO H^B2EcJf->TSFR "0/&LskKˆ艁E8瑅DRFL̛ff3\b/Dn;[k =ΝQY+UZkUDDR Ռ;O}8d┼֔b=Rslْn`~WC>Lb{ ~0˩Y&ӧ'=ٌpAVBD5fgGٍ-JTF=9GAkBlVSabN2acNK;4i>8Cba"Z޳xp5Vu'IZ:uyz@SB*8i SFr:$Z#WABWW!ʹydk)aqV%4P8dI!ϿUH r:C tOy e9gD2QAX#,$0kbJ(8tfkAGIDZbIYX29%'M/Z `DX&CC,S,rUMUU TmǞWߋcSڥ0#qdfe<%N$ ȂL@Qqpխ(Iз6R 0 whY,2_ڏbϷ񗽸זQо3S/EEq_qjqNf;cX()KqHq &+myM_5nAbb(A獫̂x[i P8wo+7=}mifyOa?8j+pj QZ4-2)aPPXcjss"$ĒFI)^R\'o4f?{XJ6iM[SSGD pCy3Q |>*V5|%$¿jf[n|XHֿeȒj Xn!@HDO ?w4f'}۹sg!0ѓ|94ܺ3$Rʩs-eڒJ,M}0SF@Vqf C@Iu&^$k f:OW7o 3:a *w 8Z)tzf,|}$sDb'rL}?wmY$90ӑ,v<9"{DM+s6p2&d77G7GqLU΃#9J_Z+sփ:j!baV-en/AY17BQZ/ 4kf$af!& Z[N.Zf8)/ཎ6meZZۙSb{ƆsZ"VjH[ ]E$ snV頥HX3ڈ0.crPU$6cŵZݑV;_(Vo!TzՕK.cf]i=}GOz۪ %tmY*sN)yK?&s(Hj}&/|09jn N}758rxr{8dQ@}!vC9܇Ag^ǣ.> Z 1 -NWټn#uB@Rv'Z=i;Rxjw{$w]t xIv_|YҹIyw*"H'fH,0Jn਀jZڝ%:S TݛrJ2ujx9-1'Tm[\q 262QeHDr^D{@u <Cdƚ-Yӄ"i٩5zѻZg&`17PkTՙ8~_rx쏚׿lc3ݿOs{4BoxZ۸0q㏩R\!zp@].gwFwj-8lw"*"f"$`ٴK c?t͇}D5Dn5?N6(j>$<˧1rT 4ySUCFbND..J{@b&Tny6bO! q wăNC>8`uA 4"oǦT)pbZbMb0dd77}w#'jgI5#/9"@fJhc֟Z+^lQBgj߼^ECp$p\:"-bwϚ"uemD)*0I!-*L!aDN2Iޜ_l/ݽR4DĈ!SPkytW%W) |yxux.49u4h_c+ 6Zw33)ZT5qM삖bLB˱~ HZ^abLh^džD!P!ȼ@{uEoQ_;L-5}%%ɫ2.Õ^8]ERR61!v]uZ,:#Zڥxb8|}DD]mw!@:Mʩ1FSܒա,Oǒ38$cfx,9#V'W^4"B)H~t\lθ'()KI :lw]0v>;?nAj+x-P-k<"n)D$ .t]w:PnnFELӬ9p917 J@Ȅ\`̄Ә (2T鷾 ?٣7ܻxD]m9DBǬ˭ڂX\IFc̳9Z`b - 9&$; ~eC[]V [ Ȏj+iYmDl_#^v't6^;)JfYvoE}U>>UQИywShWWٝ5t] 0g}Gx84pF9 mwE1J_r7t-5ȱ@ dΥrzS$ IDATB+닐c3T-i͎Ihk׭ɏ~g0fh~_Mh#aD冓%W !N&~X)vJ+&B8a[+' $i@Wp#^s:,QVǘD f Q-vHZZ`JqTK)%Huؖ5ǧ?_WoiDnQWʵb|sb >~ ` g9"5ps3? 5"@LicλT#1vTTto W/r=VD0l/Q ZK2d9msKk]=hxHH굕4u†?0n 3ʨP"@=txD BU1ќ2!"~o~3i~{_mxk:k{}z_. ׷e<=4 Vӏh퐩g"n!<*t J)9lNy *>c@f+yyI9zk5P!W!pqمl%wLӘk8*eP`Q"@=S- b%= +⇋K J֜ r!Cs֩ɰW4rhUv2 j l7WXtf0~|_箨:*NS*)`N"x_O?I'jՐkV^v_)ˤ=~X]k9#']=xM "w_(\@=): MP$("+2f2ռ(D@fa?MCQt`YP!MG[T^BC4iE 1#12Q ,tD2m!Ef$ad!pp+?KpZU<bGRo;J1C!l6pݹzVr<}C]j\ejux<.j51xܹačf]@`1FTylDͫVќ #1aUU|ݍ׀+Ĩ!XeKk!pa"R*b82=@ .!NTDr6A }v M= Е ΁&JV*c حz˫ѭo!3@՛L5OnTnk[ 1DH񳉏 ⣷jhh[ vxKD׼Js2ܲc /;9ȱsBXuP!~޻{ _ʣ/~qӳ@?~WDf !: Ps9Ms XT% Sj1 /Uu; Jǣ[N2PyrJC|J!/BY' -2ҎQ y)4"2|4UM ۳x#fBS2U?3;%5 Ԍ4|#(L9,GTxecbpL /VDHH1dZx.S\hq:"1HD6@h 2U8b^"31-%C$ j@UQFP:pXN^"0,[˲10NRݹp@djPqQ@!_!0ZNA5?O .y|ئrtߪK]I..]4|e-\ƚz/CPU@;eD᪪w5U|4_.=  ]KiQ^bC~ӄ@fEB)eC7&"Y4Qbf X:+ThjPLkU B7qPj&D u1 "R52B V` Anr3T7WSu$@B&EKR,f FjlSJ2@db@GykA'Y1CPE]%<<ᒗ5#L8z!p "NZxE卮Q`$??}u>1`8]G -DfivU Adff_k*/maN󋿞-q8OSD XLR_[&8Rw@fJ)vߏ MO;MK!4ψuh),dUJP5' ]qP9,bphH%a`D35-**h<`i @.jXm+{?ZVJ/YBTqF3phIQ3YC6P@MRT(FhOBBk zĴ^zBb|/Xg^;-}CU̪rTf-D0?_?[?614I붦mM#O28Plr\)\J'/]Y\: FXfD,"s_ Lr6*2of+7۳34ތ^!b!g,Ŝq# 1Ğ CMWbJ&_KZw0S '|AZ@CN[/2瀈:S  fhIes:ƮR\00qW=_"凪\j"jucQ) >)+*"(Jn㢇McH{;_z T$dBu"3L_ DjNWmj h*JC$ >0BhM%gvm3S )FzBow |c07e(c/>f:R_eeD$5PA5Ϧ??Sj_ SZGWYƗ;8-L{"9R J\d6qcB' )rzDx~y玁b<뺴)ӄLh3lD&gߓѤU&ܼO7yb @HǛ`vոkmK*xM"b"2FD &6IM Gmu'}MHqix:@2>xE"aI!}%r UbŁf!1r jTj0k_qG3%g-9McD!3G*PG„Seb MŗX$fU8 ynug5f |0kߜ*P$s421|]dZc9D^5Ũ ` &gq'jRTV[7(T0c,DbTy (3"B& T ,;zDH @|s1$#6"BQJ]TМJuĕF%̆ @Z;T=!xץ].9 UATj J*h5~> W64Q]Xm?Z̵ 0jqY,dBh[5=-r.AY2X Nc\9fÇ{iwyx90SO|Ǜag_|c 1=.,,I5cssOx_JIաMT`b}Vkwg^鞇ge*BV/1¸CHćo~%t1zgw _<CKCӘDZ9vgwIiDUDH1#' ڜ_v !2=N%Sjzr&;:,~ME\l*UP.)BD̑^ݢ"<(2nFL 1Xg#"RR8D&f`2-*$T*Ԫ38 k_xt@o^^]=1R8B7y uD4t 6r+OA<)fRA29U.Z3ձ~/""φKdgFZʄe:Q5)0>@$#@h3]SP.*(ԹYbD [ DXiV~#m''1F]8S)@?v Qm'ꭞ%avv;:2JœpiS;]4~/_ɓOxPG_4ǫgsߍ&C9;p<^.1 C ?fsqdjȸPZ O`-|?G_- yD7D:M wn IDATRu36`ՓNs `*Ġ9%c*bEbrCvTElgzxπ1ϩ3EQ l!g/ug?嶏߽o9<{u5^_OE5%Q[ݵ̡ JoǥEóS.J]GĵCi,SN;hnɸj!E S/5sQ@cըp`LJUM,.y Y)vD&tTi~)k Ո.xm"p6oؠfݖzf rq!ϗkȁHF~ij@[cvKm#Y'Q/8Ι1,.WW3ٽ_RJg)M\ "Gwi%O;8oNJǛy0nal۞k:)Se?l gr.&y"4t㘦я[j'6V>:yk_BUC!*.ڴSMP \(xHirN f!t&*~y1S"J ~s'PgO;:Vxp748O)R E 1q ë/탱D!1rӲcƄ`mHշDP1}` HjJ @}1nv!FBS3P7SԚMbBdzdiH"$bpH-)aZ"X8ԐG-Yd5Gk3Ӛk©:gݍV .LS]EOeQZ5 G?'&~`_ddV?v~uW4õu1aשRD2_{ifI鸿F/&)tv{clj ^( E0s:/sdf_ݿ7uemn0PA֟Řcf Pu%uy._7(U*<]Ẅ7N0BP-wrF(9_+٨~6yt&M ]`ngY(1䢸. !>Gǟ_yobmw47a)z}s$S:o ʹq~"yдO+@!HAMɹ+"/, D 5xk@""2mϙ8A@"@d},9hG$fXD ($r \ץ"sI8@e7*ζkӅٖvu2T?}vOx빃LaLڋP<+ zJC'5y+466/ LE:?x~Kp``?7G]xq~}s3l0\!րeN))%ڧx K#_dkb3.ȼ&+J!Q,=n1)IU)M7D@8W0(Eѳ̄D/o,r%MSM)d9ymb}a3t}? }`Fͦ:.sIEJ.qy I1"Y2J0ԓ-W^6T}DPQabV ((!:8x Q*^OZK`Tݻi* *W%@)vV J&D!RM!#W>qܽ*:rkT?ZU0ڌzנ覫Sg}z_몈"%)싉l[Bs,'؍ޑf,բbx/)oj\AzrJ(!°DrRdzl_m/yO?1s> d\쏭J%_x&1r ab~7ԤNJeuۋβ)BhlL5iC!\1+˿z/!fPTťQXz#UMADJ"hB0cE5cw]m !p LOTR*qLsyǔ8F<ӘaɉsƅB_[i.;eQO zj6w3UZw-rͱHsiBnc&#QR !vKVӕ.0hՂȁI)RG`V?QtkIܪ38oouj$ V__h @JgYO>a}YU@J6c+1[{Zq lWfE+_Я j 9ʙFr꟫^_)HqBKq&9F?75u3u]f~tهg>s\2VJ 6onjlq/Č Zq^ ٶ;SV}AtqʓdmuZ]iPi>ΆDXAAjU:x-IŠd)#"q`dߡb|ȋ !)]c. 3.0)[CqcL)c7 !pJ%%:P%b\ez0w{ZQ$4rY.n V?b#\OHV𐐸޻6%beX`% !^LAM:3)"H!Z:q7Q;{EQF-25*"y")rVc?o;IYV`?ӡEoO8%ĤV\'yݚףckMQE' T43Yy9G71:0:cs!l iV^@}8C9 .-kv\0'4MJPq^zZ.2sV,iwq%{x՗')*bZ! aGרz-1ɓ*`#X2|LFHCGGP s15KTr)4<A߅@x7Tx#-:_C]Zo-؊Nq!&d0¬BO4;'9ZȬrhR`&g]7Uh/2 Y}ڎ1&/ir021s ڨ0|3 KRJd8f 8r֜PC;YqOz`.9bIQY5[_!yݬ8fhsL)D, 3!ӣ`{~<'ɥ`)D(DH\=ý_bwaľܻ{ϯxΓJu<T˾]sǃb(F C[Ni\QԤU'݂uUUcjh:RcWHfoG Q WCkoo @k_ŰmgC֝NU/FUHEDsR Ld|c C8<%˛'"Rx8# 50̌ NgcBznݩ3FfTX`bf7 }`xz.]OYE!W-`V9pOp~nf !vmNIeeHjQ82Zc@PT&((B5Dz:}v౟;~+f$g?Ʌ|ᐒ9!6[>A:*S7z}[AkK\(Yef?*}W -C)eg3@^TUDY;w../J >>x0GOKB<Zhti9D6PCgb ЛfVZ--z}Ře 5z[nz7XHExyYjjj* U=6d-m5Iolnn'o/v uvh`+6ͨ`DLDJE,]݀9*( %rTK{kbdS@2@8nDvpHE[C@K$bjW8]WW~P]3vYԼHPݟ(wJ1I j h5(gZkJ==C?}V>mMjwwU=-"䄈9;.߯k6xP5dT<,G5#b8azs4r5z8yRQD6P)Y!v|- u*W?ɏ|{_˲/>rάj6!$̩W0˰lP/d'.6&%6IM쮬);a"k}ۆŖ*$Pus+~a7t\7<S&DLl6x{.rTLr118wwo޼gg;ѳJ\9i%Cj+s PkArbm$1mr^ve82Xʐ2' %`m1fKItr!g״pr:|I  ']'Vġ:4 ,E]f)@3r4眥jBK3bOT@12Epf};gﯴp|J x<5R$q& ”16-3z-HĜmZLZ.DhZNEr+!oUk(Y %J=jk_Dž#vZWyj痗WWDj-k;"t_+ M=Rfȩ.a iSCmvpO'6g!]D]'MgTEhjp8^SˋKQ#C4xN.V^Sq}j8.o6;߬5Uz<9$".4L]^'u,P"R:j9ή9P'k!{et.iqpKqbs|2ᘇy$Չ7-i+f&=!+i.\PEc&A p¶gݝl5D3S"oOYcDj rZQUr;^tA)d)^mnMJ`"f BN"R㲥VO ]Q"hK~x<DIDPL՛>XnAfx<OotQ3f7mH)ꑁ*fp-|PsZB 9 sbk~'"*jJn@HTJҴڬt0VNF"Uv=?s"j`[2}l.H9)r&qn,f`f*^Cbe1LjوRb))I^f|W>uK=(;M&GLp@=|D#F-P ߛAf2e8үe BG@ (r&hvv{R=CNČ)qJȐ(! alhHRY$ di3`kV ` *H`'DJ}~-MH vgg`_!SǦnnL*,@9]6URs6$QU sj⺥x~|\;8ŔCD)gU͊hwSݑLYbZƗi=Ց W 8R*UEZ*a"&M@Q',aJYME0LyJ SZeqjB7aNDp~{mIg|1fA3fZʢo]m3MvtPZ߳qnVa4L$)hF&>%D_yopmڬsWѨ=c-: JHVZ(cU7ӭP< 91jT5TWdiI+ RMu8,̒RU,~ZḊ3xED=D@ -b DI$t0 FX(JSm14pPOxj|~g IDATA]BsPidjp-c@c_2#C^h Au~~w_)fJS Bܘ䮫eZ|u! 9'{qmҒ^E` 3_ͲPiTkfd'&0@Fo '4"M{Mp\ HK=^_CJ]$b%/߆Ԕq bIsV|ʼC̢*{¤3aKy3W5`F +z_l4أT\&l & K?3۳L  ɂ`ZR:u{LԔ"M b*M }Ҥe#m&W3FT#롚} 1suƘ:'ձ"cʨb@ HޓFervRAj9Vyae~aS .ڱDฒ&tz@S/"uhVɭ14t:NZ OgJ7r!N"B O#K#H{oTۦ Vpkv~ػ2zK/ 7̝f095g <Ӽ 2&Vi8)bG0NZK5v]`ͦ?d?B0<{P " '?)wDK0Slљjrh͹R؃Ǐmaۨr*Cӧׯ^V` UDtbmQ1O' m+ȦKԱey& %3zbNe(w=3`9Ojz6Bz,}>@??i2)gbch5ZL-23 VI9RT Je2$61\_2C 0%`f$ @HԺAi}k@J)WjRMMںeAv5(#Q‰$n3H";3rD]1gj=CHSZJ")*yN'!9c';H,bګ~V~ʕY(l$!E9?K PB:=FDP Rj-*HMfԍkP$O8#>N3H9:#Ę6o%iа_Ç/./MMQs|5pƾI۶ 1\KQU5=N 8"13!3u̪s 5,g6@fD:V)oo j1(ę[03Y$L{Ws cp䵸E>h 3]ڱq \_\~/޼bEj)%b& wg޽/^UaN/10EBB5ӱ' M|"^*>X]VU!M7gzOlL@8G,RS="36{|de L14uLDJ 3Û7uS'H~*6Ko6ODpMsڝ4`ܖpvMM[E ```t2Eٷ#7-5 VP@N3a/C?o|2IlN|g30y.P@PZJɀf8-#O=z₉o_un6=xx8x:Ӭ?j^VDjݝ*EDZ"RBFR_^ BZoد{.t8 u RN| @[K;GQL &oy Eѓ_7G?˷QJ)ĜrN)SϜhY{?frt'`XS $6>E+ld""03BQ*XG b8ERJ b^lm,?S&x" %u0upvn:M)c= گV _>ɈR"XCZ(b_OhH}pw E>L^MkEsKxg Lޮ*) L| ڒh7/9}^ Ŏ͜T#6\GHfF l1k >08 ]ܻ[=yV[200ħ~L}߹֛mZxbʘh;uIkATbV2E_Q3XowO!.˦&U TU.~2?> ne5DV~m_ Yj e,vllU*pN97)!:o}"A_ws F!h }֒M>g7>|P۷WKN#diʹ'-8PvZoIYrD`b5PSL f"RGK@ nvBZA%{ȕ16Tpz!B C4'n*()8 <{Dr?ߔ_nuy[unj^lsE9)v9Qu-esŘCvP>:?6"RLE֛Og痗]۳sQ5`wvAqy艉}˗֐9ra,1r'l)B2jJhj):>麾_]ux&2zg!m_n.[K:YMK50N1< ]:{pqvqwb;SX080Rnǵ  c*挙n{UoE!, SjV5cXk:b"@U|gB"rpLw.=jӈ qz1]ד85,Ɛ''R3*6'"42笢ir&f 机 e;Dp:S)gt!!TA1 3!44"bt۳1l0?"(/ %3*!Caا-$hHR?n׆yjvw'FRzD)'檋sN`#!f9 1!Y:Ias}kvs}۝_sOb`g痗haQ {o^h:39 1宸Tj-'b+KިdhOt4w3dj )N m9UUN)w}Z{HsCʙ:J)pa10U=j9TIJ)9' P] 72Vƺ:Ӫ+UX^Fa, 0I͐vu ` 8|ga,Xq By7OKgᄙFwhJ@U0k渘y 4P!#eZ<&VwW^\^]_\\"rxHd%N?~:{LDLT$TmcDjX*8[#]߳]MiF-JSZjo&ҥ|RS2S$N)q8+"Ñu -lLx?>շ>Pջn׭v777éߟ"S+Ȓz⁻Üĥ/NDy8yJ RPDͬ˹ﺭmz{-Dc3 NIՊT8;pzSsިJL؅hӅœ IL\w9^ JuU: Vxہ՚Cc+`.{@\ (0GB/6anfPj7}@FHȖX% J-엉O/~7oJBĻ 1,X=OM {Oy~X G.=DG*Up޴o* k3O'UDr~$T3= 'mDt|JQ8x5Dtuq63v332ˁrL¾׍1ˌ=GUqǹp{U`M߯Sz sVU#){DDާXoV"TLv?p[g۫W 7p<W;Љ3՚Xie#Ծ[kzǫUJӦ8ZТ}Ǻ;۪nc=m,7D8kKe91jv=/H$p ;0F@f“/I.V#⫑ `JmV|"Bꤖ!എ%lQ dKV)o6ykQ9@{$JZEV~HD 6=>eJ;QuiT3 )LǨ`wL"bҀ1*T  &) 7ߔ?JsNx|oiM9i hC]-,RD$]I""G/zCnRq)!כ0?ٗ>|cзkAbf*"RA}SEn@m"PK LTSgͣsƇ ʂ^[J4 88z~կք]N]tuU`ݢHjp< n\_\^x5(]t<\N.͆Nbc#jaH9?:C&:Rv7()+tgYelՇH}׭ oaZk1B$hSj$BNh ߹Bu+?}$HLK#*GcCSP|RM(dDcR):J)Z Gd)$jSڼCb)EDnXWϷlGcD#CQc@u! {}(@P8lC_Qף|NMxsl /"2) VHaCaݞ_^"G{8)[j-#4 `z]D>o>>61}bwz&DFhջK9RѪ_9ށ!!v0--7s5aLL|Pryv%jv[M]ޞI~ FT8'50qׇP>ՃˋG7ۛjy~2n&.~}*U۷W,bCf "JA[]p (FLHjn9m 憀on&OLYEՊ:B\ @֊5;YEfDKinBq={i'PP" fEĤ'+(8|~ebH.}Ac*RLDO7HR.CM0iBd X7SMbv.poV|M}$pH"וc5eff8O2MZӡ7>䧀?#/Tj"zqyٯ=~\]]-#S@MMc-kޓ=jEas! ;\3ZXSuy@pNGoM>f[NlV-8t,Xhux{=8;[zwz_mʉ8'whY6Pj,z*c=#fR}J`>Lʞ1'GO d`݌eK11hT9ۅ#2cCA}-uGaSqikM0U@' YA@NjCb$ g c vjJ@3eð#ޏvNkqvu Xo69w53 X"O>ArrtN IDAT"5dIDDI)8n/˽Y-hj&?ꍿQӯ9nō8'D{OD*EA+"VHD2=~=J(32"è7Sg?%.>y6 ׇ߻Dkڳo}Xzw" fb̔W4(f;wLMoo7o߼޷~;IԆۛZG"-7,KIMsKpT9)>^TX3D悿(3z>kaR"8>y?{(=D9]__Ž?y`]XѸɭRwĨ麵gW0NXK &]^ Rf4 +>Ռ M8"Fqzw]fdVEOFԩ2)DeI0<*{\ɴR0,hhj꺔X#(}gI9.nj`t8I߯L 2;>  AEvgg~m1zQWGC3f߽ݝ_\\޳dHTƢ`P&SU==yŗo^]_Iehѡ`O4hJ/N;|JcQ+,ol\#ވL;33{绳f6"ˋgv$U8nᴽp|wuZoRq6O'U=okZ"OT,%& yRђ Fc ka(ðZmc%+ $j Ea`S&uQp ~13vhu`U0v 6Yn!QD J >M:FES IdZC3GE BZsgR<GX3gF p(}ӠXF$HV0 lUQ[x} >ib Hh 8Zd+@"JGOOOFwu[xǝɻnbЇq0_b戒j^6j'ϞO9!j| jRSbB&o![ ," `Nju,ϼLeaL91fwɳ ̆4Re/qm2یm]؜|U3c-M9x#&Vm2i*éG0&HpqcO?``(*T|'x:hmǐ@"NzNRXN ԫT4`>SCj=W|`]虉)?kasfWxrZ3m0Gs@m5tWxŋgOqѿXZ-[يj>18 SO1+LTTŭr6SBH9k67d뵨\}6XN9푎'qϭf5[XK⭙+ب_..}?kMԅ)fTcD2!Ԛ`m!AʀVDlNcymN/bwv۝_aW~էP êP;wܼ~A? tA5DV{$kSOC*it&&XUb<ڎ =0(B|6Jq<ObS Jͳ-Ǡ:_`lȢSXSd343q*TBK̜L:ׄ'[f*E4A%nh5V32_I>usJ)kND& "XU8}Vg1WUɠ]uN0BSe7?q+2ؚFsìGo)my1#znwFD)E$U*j"uszDtq?xL!@H痗YM?o>{30΍|XȻ8gm\˟o"? N~\ނ)F` &:z\hᑻ Gv@V{L^͛o]:*Uj9wp}EП14<_ / ATSsjƠNj&'"r)2KIea˝󮽓EK Le(@>7#0IąaIBD2PTp7%J/nBv{ j4 "%[ͿFiіN `A9Vk4ZTpZi K +zunVJZ_~1offerO?{ϟ=ַRUO/t~4'.xg;eݽnw6ML81afkdX׻!u=%E6`֭WRVʨDl Ax8쎌);elQ@6$ Ev%"/H77asG <ٲby,!B"3&{+Aql>q(#\ l8ࢻn؃&4rg8n}u]vwT]R~AЖKn!B#G.E &B@|W3-=_տfuˍOp* <]q_:b%cšm//A`QCQQ`)w*7g+_SNg}g?xC&tXJ? |"__oՋ/h+FES_BN )8wfx J[4߱že @en})SJMe'?~zË=xpAUBy3}XETZ[xe2i=0T9Q^&N.C62 *%NBWo\oM1@16ۡ8cb?[ٜgb@EJ=?g/>^rjTL)BW ./'O~僝l.%q5Sݜ'4+a&\4d_9fs'nk{qK]4PӔtZODPD[l57߽}O~Lw|קaj`\+C, 2bJΌ 9mk4tP5@”t!M4DOW]ܿjunSi:.ֽ="ڔ_4PZY!w9YfҤ\mt BriZBB'4njUm %v8۟v!&0|߬tG;HE5uaNfF OBzG.NUZϾ! fzWۍ,GϾ0ŧs͏#Nf!1 0j7Ih ZCSļP-mL$<"h IJ""]έkIOj[h3#f~_p~yHϾ!"JeC4LCFLMJ1"g}58oVEBb6\w[NC5__w8үgxqy6q4 m;MzC2io٦-EĔydP $dI_2!(cU|yBxtU191a)68N ekx@* (!"'0wsU;"3 Vk~~ !_~_? 5ayoUm=9p3#+bhjZKN9s"nꦺ@BH/D!*F-!^].et:o^k1c̹>: jWJq,8˘c|㻘zA\T1@s{Dġ2]:_ZHc&Y5UÄD *Jd@͘;FSdnntb,f=쯎ϒM##DE OFNNwZb{f`@xuue" dECU4C.e nq"|y1?.R+X 6!F֓&D]-0 7ㆩ@{4lT_G_HۦED*[Z~'ai"RV>6}۔T 21Sbfɒ3D B*KU8&SC0Q)DD\ZaZ'ڛo  9 >z)@~'M4Lnlv5*\/U-"Վvк]5|@&<."y!-ZRL9jvVŒ5"PX D_?O^{m˯zd01 "2CuB Ec\b7 ȸxtoFl2r \ 5e0NT{.!0M0WHdD )P CG3AUrN+,SȄO]ra'y26 Efz,"?|Œ 0hywHXr/$g0< S/WDL(ž)wCQ*x*h"eJjviՄO~y^}?k) A w2IU14 ww@@ճwKCy w`/'1+02u0ưT UhEtb^yGߪIV}3M(SfANj];ps%-*Xƅ!_Jl f84*ZLxwYmv5flk}g1()%lG?<8<::>9>==:=BTrHdhu 29QEC6@Q?::::4iԍJwa2PSEC#`ouET"0 _...&[$qa'(v D^S$7BA8-JJVFS@g)%4f7[CqTTv8a M;^X+qb1[Qg`2 %n`LRp&b$&~(xR41;R!3xHHC )SH~g{^ 6ŽFEeFTjK?_9{~#"33'bFB0yf۾S:7WUA b8*!^c MEUE|\-GG/ʘ /El}z(H8EW{>qi.V>Mw՟ձv[AiXˌn:̬]Z?5h$i\i׉WtdESD)"hVum8kiֱ]ZC~wjZӨ sz= fʬQddTRgc`N2 U1w~#8QWdj}oDlKJi;Bw#j q #mΓ4Ü UP)g^Q@8:>$'O暀}J xLQ DA<%7?,= ޝ[jhk ̛-{{2 d_<|'p!3Ί1V><Ø..ϸ ;Al{ƲlF [.dj8v](ΖwH4*ny ,۵vcY}upvm &$;[of5BP}bfwZP`v 1'dzbHԧD i $E 9Cr⋈va3 8L#[Cu X(,VInUôSZaN,r|J}LYomZKYYgN|5fϱB_qB-&;PV· IDATwW V!xSJFA HYY/?:SiR7 L]+ft7z=n}w>| 5f#j/|Tui[V4U ~R,mD.a-w= sgM׽Cľ`5X~^i&8_+,+TɢZLZ;z)YFi8nDRoۄr" ' >WG'Gw5ibP\ QBynHULUEAx`Dvj/ՄK㿣jY.=Koa4F҆imH&'>0+f|0#E, 30iD[ ;Z&gz\Ccd!Z ~ FM% \?]__} ~77BSygNt4?c YH5#BV,1 L9Oyܨ4QIu Ë46OQRP{.W˥q3]^*. 7OhINJ@&zj9T+EX'X@3@ݺGd,%z/V6 ^OH I `f** na??ѽ)V̸i슄zXK@M8HΓ1~XjEshmX\)_BinG"X,єSAgS@ȧD1sXGDE֗ϡ$bN)2#qg'ӊ]3!fЦ̻əc 79ʹftX)g⤥ۄ-x}4?[$e4} nV^i-~2'#6vX8xzrzrvcBXPf3`t*"s3BTX Lq; kwLĘJe ^\8G֎'U.^"Zk:a ߱-]ĭH-HF^qE [;=ǓriLPh9O>V.Q3Ocѳ7i&"oXtajM~ 5_ DZKT5 c8ӂ:,/4-zPmZ(9fZg>* gsw\u,!22/K$F,y{smjqR{/m7SuhY77nwW'xt!TԽ|1IJ+z)X쯫W)Q{! ønu~+'vnPTVjðE=Z-SbO:# !ڳ;Fhs"້W^|WRi8᭱ 4ʐJLD&c? vRԿbvqdq]O+GRDY5 $Fųms JX"xN(N ś^O<=^]D|w 9j(jh8c b6y*JU ͈@LR>1x5jb1L1TbL$7K ϯX[ S ot٣|䑙+K,B2f_݃@ ?4b }g u`fB\. |RSD|}],zPB뻣xfU3SJ.t9DŽSN#%͝FQo!Į00\onw]) y6$EX9a~o'__^سDla;ĜZf JkC..hl+C"`*}j8C|bdY:;D mfB Rm\*y"84 B&V6.hd|>1'gG''U;]ADhHF@& MѣDpupL DD*Y42Q Ct|"i>XnZE<>:Ã0lq; 8|"`JrAbid3iA1!BCA,Xki@Ճ}@j)%ts@^`ze PF z_lZv9Yˣ#n6qɜ׃ˍ&˥)u v'WI!!LXTf*_vs#B25GW a-܈w!_U:x֠-f &tΥ-urRͲ-gLyAfۜl3 Dp}jIWrWMB2=1~|5֍wgbF;~3<XD'_|WG'g-j&DϺ4nR{2.q߯@nM5)B4rǛMiUMHYF_.'E]ڎSwV א2v)йOd K<ƓEģɪvo3Ʃwf0n޿E{gwIW*"w^8oޏL&A: B28bMb"5[,v!":]^]Z=3$'v507@1rooawD %٫cXͲhvؤ!<:wA D y╓D5"xlX;ݒ܊x+KndLhaו`!3*7%`\jP(і8a"Ӱt}a SN`ARs-Z#(E^;VrVxG9svxrvx|bU2CĬBN6+XѴfy S31i8gzc%9[# r<:>+0\#$ ?æ4XkG` Suo2ezoHg&( xedwCY_.˽ω_+o>{o}^{?{k:sz}ywᓋ+UV~֝~t:ЈHDi9j}EQG & U14 qqH]\,SU,=5 ǦH>#cJSrإ1= ӳϭR&%o%U]s^`y xc;C"Xn6SN'm4AO̤Y4G6O+:4!2XB1T- 8}0::CHkνQTp& 6S{L[Kyֆ"BαEO_".m9Ocι뗀)%E X%bh$%<'y?//j䏄cnFl Lf'|jF@Kjh; TZ瘡aC#o<}~GǏ7_]B&>yz_}^mny=0x{/{e^38RmJ^Bzpn3g!rbCVQp^ W&b+JhF5P k!S"y"D*12db?T)B TkJ,8ͷQ-KwP}iG[EYע5S T hkնpi}|_~@z]{wİKP,a<,0: dLnoE)O"뵈q3MFO]o;M Aޛ#Jc 8>9na0e\S2D_gmBz4[휑#gAeECS0$0Ef\tHȋʽDik Ѳ-K/-f2 1ی!>}/CB2$3U"dL$akļ^{D1OY,Jܡx>i_dVK[aan;7c^ TU?lVP4?e1d1bvZZlDq4'"߯BG]D YF"Bx>/O9>:==s|vf%B"LK@fNNXb۬-]wȉ"s2"BҒs%0VՔbU{zy W L`$TwD:F92x\GTPIi݂1pϛ}+]f)w ܿ0?g{u ߪjØ1ô;fղWUQ3$4!N5y%D{UVB!k : cYs$O2)ыRw 45 != r53z1 aCv 15\76VrzAՔK#ӟo1;훉}|<W=zĈtuyHZbkf^< /˯1s$2'fvR\4UuR%PK9 d~URDm @@x#*/x)1Zaw%0n$s7S.1nJdhu ef壇a+,;΢w],+J"gu^g=Qrxк,2sUQUT9 [*DLc`%D{X<7az7^2UQ^)mWI՘?Ԫ{KDO: jQɵ3D$"yqEd2Nf)5wLC,vH K fHChjR'O| ho~;j`dTrcVD3ɑLyn-O4<](u;Ѓ%·g4\.? 3BD_ av\`8+vAAC|'$v4pl&`VS翭I^/sq0^\ܜV^{_./~ӿ(k#Yƻ/D/{#4l 8u90;X19 =6sUHTd!ŸO1[ "ǞC\b̅?vl֎u ﲉS GFCWaooBdfyjYh$꺾+_U:.3ƑEHѵ̡-Q&>B% Gehl ehHL&ŋ00+0m'rGʭ^!!k]l9RVT%XFIGx|vMlyT!0eSsBL jQb:Fz b"t@ER'qAͮMUNtMkq?*=x2&^>1%38?6q20|`psux½< LܥE4:גG`M] HL]2F0I P Z2{O8xvrg'z~qn}bqۃ>ZcPs:}֟H&E5Z3"-hcQz[Yon|{^E1[@A`\j'ELSN Cr闏>c5 ]D@6JZU<oIIPY QMwWkU5ڸ4Q8s3kYj}aL)+gB?DvP&s#לT:P3 ݣ.^,%Ь&T8!LXӗ&k@T\.9NԵʹ9͈jDFk* 4ot0Nj!slU(LK5o+F;ᛷ>|η3!Ef#21W!,'d]ɸ0w8qaJh!q0PLDQzx an7i;l8ZPq@"f]!4_'?'\}Ȭa-YDD]}߹p W.TD<͖qss~賻/{k H \uȞBn7ǒ7׏l-2iXѹr[C)ž5:uoG6a,i@KU;ynVbhTR׆ Mqyhh:*̱L24v}]fb^LlhH[8Z<j_PLLUVJ wJ[w1skٱRbY_-.Xw=Ф_;G/==stzfSČ Th'cEI$MM$8l7kU77?3+F~eL բ?=BBS{z~^y49sk/4T?OUɳ/ p`./o"nn6"-yR(bJ 0MЪD"̝8[,b#ӳ*^5YXá 9ݭ0j8r]a6HPmƞ#\3w(O|'fƻnBY7@t&n]-d88m׈̨(u`S[*:" kN\ť"!!)ŝQ"?mK``bhHfvs}3?/$D^W{M*㨰aG}1귢\f<^=}J]컣81__]>3P Ϳ{X["װ,]tSd.VOkg6ޕߓikBP=+Ts{< iy915]N| 2eRqLS:[$˲N Eh| wB@tE=U/m}%YJ)l Q*[.Tleh`̈'~CwyLpB-'wrvΝ;wϚfV#Egq@``K,*ntu]r3&2rYn6!~ <;;;gg`_Ż@T KY`1jٍzW iD!BvCXSlD)L9WѪ躲($zW,%H]̅ibգ"5I"i';. [FUFC픚ڲVT;?H@5nN/xw(vfWbYlPzF(C o]1mwh57f6 5lBX_8ju[ jmk oK0yo.xgݴR9Cv>pc/o\F7E&hCc8brOi\8q܎c.3'7K6a:(?uč0WO' nE6o 2TZu:~^z5?woyOnicC@[i>wIu':J0Nͽo xXi&3G>}rۮZ~&$Q̶b;0er5QpPA7T?V<5[yyýt"ums'$ΤALdd6Gn[uM`ET:(lи[_I(>35E*ٛ RduN]eZ8=VÀc#=YF;Pg*pM!j*?̫`A4|%$Wǿzx;1PpD `BP= ՐWj_8\_iVp çIHwɗi{''(p3l7f3lb=}5$ƫ h3o޻Xa}y/՗reHΐj-;3MŶ` ~߰~!z0ldHko^ry9ӷ:}nyΩm.]f__/~7<8~K_99Cw?J8'xql*h}""tD]A0O`# E/߼{_/&fQGZ>ifftjW`lr1q#ߥe Ujaz7꫑mx_!zߑw] wyET-6}HJFq&QZP)&i|)0srKG)h1%05bI9m).sHb)֠^Hfl+Hl2]9LJnP %;RdszJ{1m<66[F*tի+ ɪ*|E2+SEo4"xDz9GW;AD baX atHÇc2*2+u?K/}|X s?񫯼fQq[`uZ7٩|;GHu p,QbER}w߿}#Uxzz;m,&7g]+(W$"5?d# 0+~ɤPCIMmS usOy_(*:+bj.9]|*PDQ=W "*KFY@瀜u 2 Kc2aIR7肼ۇqj%4B-6yCsa΋O闿EH@EU!zstZ* ;pn*y&Г21:e9 (MH,fhX#vc*ˇ(z8^5 GK{Vw}ϾOG,]߱~㻟 x|2w޲wC;;gޠON 3˞Fk/0O:9q/fSR$MX_%z7eBzK>Y햚\ܚa~R)y֤oQZ 7rK˖sP]Oߙpaau*+GpYxMDǧAVcu9:o B‚ ȯH3:=_`wo K.ƕ).$9UK\KZxZz!]荭V*59Z ORU!ӥza%=Tp+00jl[rȼ椗-Ғ yv/ Ykxᘤ`LZvwCB(a] QUR5>p,̓_7ݤuQ[iv_w&4udtVmby[KWtKW^|ե|4Û |E^* CGðqӓHH" HG'HlJ(ʥ'Gxr@?ӿ0?_/O~d1xБs{s9Bwz~o)m !^t~j7Zqstj}~:d|r:GvzLA109Zn.sEȓZT.(ʚs,p薺YaPܥ׾[}l <=e]I2sudUaߝ#!:wn_Q $?8- ]] 8M0 CZSaai3`U{UXKAkђAIWeT9ض!ܮ;.ikh1"Lcnc/RI(L 22&72 ,muKW^~20mE3D`/v[]QCGɹkt{m=|32$ҹGOᡪ^x~Kw>~|s{i>u;<]#Gu9p G$"] ?+r{oͮw!)8@\ gn1sIE,5竍[OXs⭹lӱ\6;y(ɵ٬D*įI}tEq @*44WZGRPtyQYD3SDK>%\;鮏 ]Rlͷ,Ѵn2ԛ3tqPz^]з淶fg^VE h,0K.RM U ~py;+_+W/]16=p80rF$k̽HM<޻3͛ΗwNO~7"k\5Z7D_sAՌSajyh)Q4 sW_p`o~p9z'IPIA%D0 v'Bvs%H?CEEI~vImo0VfK=wZ9`N , d? /QE ե\2kC$D."09=4h%]7j>,VZ+P{@y]Q1h\/F;hvEy)E%TDjih(FOfŀTCkj*;o}\K/>DO=LCR{< 9E"E'%G ,DS]]<*YD s~۷n֯_fai*._/_Et} Pw>.D=Dx`oo~ֻ%Vb;oRGÕ^O}?qw;),",KHT]"VwaI] 󉭢M^ъ#n+CiĐ5HTy9WT ,BD DuO-SRYs4p9"*'}8"?Arjr}9Ux㞳0&1sXBejvk] iŠ)S89PC#eg;4-\Bec[E򲈔9'[w$OJ`h XŕIӓJjgӯ|D_yT쌐dǏm> NOT4民ߺyͻ?3/u_eUq= 2Eg\,nzG z8Ώ"ǐh=M#̰̈DIsY!v3Z8},[{QfP%⯽.m <@ٽ`YBR9ܰړ"+shv5#JZN0yN-y5 ,{Rt#IG3Λ#6'OW{Nm_+Y"pU`1QUyo 5ی*kHPT5S.A' FҹKU؁ZMnEkX 'zZ[:rˇ^t%c@(HXM5QddZ9 aUcݾu~O}LTPطcC0d,\B{8OhNjL ?x18Eڐ!`AQ ɳ ̮RSEE$]0ϯh̄IFYZ+NͥZabl(-B5lMpc!VS%-ދbBg-8.O\24WjPN!4Ru`E./K{E%At H4MCdLLHΑ_k_oJD1l< &Dq]5 -ID6t=<1#DVE?"P9*y}4ƲE6uwOMXA[FYr4REG+QXȑsTJ>.u2w}~CCH{HDzST e B hU Ͳv9&6"`q6le EF=ZR bDct4CwQJ\^M*bmeFj @ aVa"Rp<#K$91r&yC 1K`ctʉuuZK[HS/jЍz jXAMhp _郀DĝKP,!ףĖTz⨥Ы0yu}Qc#GUQ&k"rɡ҆tw^'ª묂Ef!bw:O .n Kr1h#pUTbmnB*-2 am?YdYkmV夷٬zf^f5g,P"U@(,TI ɍiR@Q׫)ى3WM󣂛0%{?|?W@勗P`AS [U~WoJmV9dYbi;rd%>|3u!ⅽժw?YOS,uX$sf9 OOfJXq p}f;_r5ż ݰPk~%"Y oP.dD Vͼ("- ._ĚDFH/N;]HB0Vs13(Vaj'ǝwQdF(h vKkJ9L U)sƥ{߫S{ aV#:;4ODD]~O弅V+"RR1-R@E+&3;$)EQh q,aҟK'KR,UFM ~ K$iKX(YXѱtK#mۥei[eB\2\zdwBՠMIU.7W>|~o+>9g>nRWU|D$eV:JfVpD{GdU17sտCޏ߻wȸFTF@-cޓ;sc*ݡT rޫdcip#iˌoUuy߲,4_9Ë'^|no{ÇO=:9]Oz:YV`2UaePZL{9t*.wi| *t"㬢18s -%j$,~DDo>-&ڠ*6qڬ_4UT7[{Hw|Ď-"}VsUf4i܁d,%o:-`X}-+56|J7i3nu\GJP|BPPTMS ?|{3O㥏~7= XX"s,tJKFHlLyC0c$ƒW\|#d')"| ")nN@7Qqr6O4Z rLUZ~HeT=i/|k?w=s8߿?u1s E5Qғ"c<7ygRiºR(d`, @ P#à "E%1|G<9@ਥ~g%͟$B֎@mPpr_Z[ .1U]jTIkla&@2@d*WqC^B~aByND<:Yt_n2Y]>j<9(sIo;ƛi QdvK$DU5`a{d|xXsS#E(Тmր 2| в7WZu8f2zI:J!0:g?//|?x]|){,9s4 S#~`d ڬJX$ *G3qj]搁 ;[ +R af/W1gYcY@lMa`UK@٠dJ~CM[fD܈ Uf E^0v 5mg3Mϧ"4Ѡ."R3R"^ K]& näU,X5xZ S ZM00qmS/Ֆ|ګm`QZ\ #nCq-;w!?xֽ{Ns'XdΪEήREM| 嵪Kqh%j '2^!h"< ?O͟j5r|3jAi=8޻2a5@QTcNNO;$'探"eJ@d=~gj9zo1̚%d2BU~fQT=ё'B<]El`)D7$1-?P,5Nu&޶D;-q%mt1Ghh07D[cMnr'Լfo{!A9Fa|t(kcCSRYP9slo|k_/2s R'k%Br)9<.^8d|ܾwѓ0)U\"}̵ ⮍Ԃt[\:opw)l|+}]ݚes@?^f#DzǬ}uy4]Ej蕜(KtIvDV d" ȼ@U) e5"B<0@sE$33@sСK&҈ ơK_5e:B`9q z߹aD`Brzj%}3Tc!&GL`aq]9MC"aG纡J$AAT Wәn$ 9uj !rQ`9oTҫn7ݢ*k@;ưwE\Q;ZշޭЖ.,: Ύ.u ӨuXZ+mjFTSEw~ԹhI' D!D^U|_a߯:NDHD]# IDAT Ғ97+_yVG"}!$=he 4Up6/@rr|Foa-k൦ٖMYV&=G?oHG?zޭ[=:B$ 3"v}罛i~"K` sc`60"UUҔ D:TԅD1]G"a}|w]Z~$!*!"fV D`+ЯV*"yy?%q+I$cm>WJ4֝B dɻӊdҖ֛${J$kYYvĥIo^6a:\ k P2r|Kye}04pޥtV"2d!5ҜPEs(>yby1uDêcª!fU_{-ŔS•|zg^NSq[Mf =O8߹dn;ufQpɮs E@ef@9'Bf%,EDT8* xE$ͻGPw9G"ZsX7H"yѹ(STilCWspt%QDiF 6q4.+t9-K"!L' d,ݶ)[Ra>1l 0cmVo"R͎ff][ ,˵ht^E mJi'aɓ\M5PPq_Vk_tjuN-,$pa(H9a9=<>GOsٲE$6<{O!DfNK9gZªZ"xa"ld<4Iq.Fz2O Zo6 t\Lnots!(!Ag*Q8u5 %1;D. (dxGppa $uʯUK>CKީ[9DUAb~us0G* { =y6b; GQ :`"3.ot<JDHcp ,rHXjK& >h#þ0Jq HjC" rf``E[}l[-~,nrە]|ګl6i9wrF|~Ӽ3fGb]GSv15 aMXI4)`Ę\TTR5WlTB#Yb9Us<=U7 | :fְ:2.d$ū l;D]ry,f>X`KMLNH.7|@Q Pf$dl@s*i8όХZN洵Z#f5{nL :*hv[,{tZ71ܺ7v0Vǰ9T?Te3?'[lA8LChׯWyk:BV\>*fa.饏+'#9Z(4anm9ąHT͗ڨA%{:_UUtpX;a&Bp2VaF$@,9 5hIς[ù3ڣ4΁0sD-c!B xGFN4=E@Dw#1v:T\Tx4Եdc:VA >Tv!T S Aa a@%S¤R.u+9 KCsuWxV٠תVZcʔ@vEÔp׭_x٪;2pZ5AUqN t87^pׯ߸q(O[kbv;Grr2.lYsTan"j՟;r=ho 9syImsWr֒i.(GEZ%%5|g @0WU CUO` RKޜsJ "٨"-鍢yA9_k!(H9wkoĉHp 9-{+m4Eb)oET3|nI&$|%aq.̓Ҹ(bdt AR\A38&;5{[:`&Q \瘑BkN$g,8[/EG\`T06&ߡ1GuGnRSe"ur1vONg|frXM\D>$I*AHfVy膡 aff(!Dl6SrKFN;#ނ4%cޒ SSK(yDETO kg_:PTxA 0j`O5ʢ *SBw%cΚHAfa;+40( 5`>T]bQp H"* ^Nnff Dy3u.6u@r>٠(Q"aRY[ൊ|{8Ϫ:O42 uY"#ah;Zۢ9ckBkyYEĝstQe!JBWp{-5$HO"ibMOcԖ믿o6bdriߋt{ѓ=9}84C@DWvo56T> kvIN$7x$0yt l2 1j*"G! R|2Ϛ{MA!Hη*j4DӛY3.3 `Bdvr醌x.V zH)[EŜ0S(R7R;p]jQ0Mia9|o΍5VpģGm26[HseNқ0ŕܲE!Sa)腚i'MsS6Ƶkn2 K'k۳T̜2ܑNݹ{zTX-!DdQBg ,$",:of'J!' %gsfn@6 M5 fw^X0ê\cáuzUu]iTy< H#纎#"!G(XЉQ*]'"l_5r{-N/|Ci^~^+ E\#XuC].;<O|w?8$[ea8Oe[Tђ4{ȓޤ"\Lxm.вҫ'SQC [sG1hawa(.wg^rST A$h/{D#U` 0m \E~@oW{6x?eiyE{\\NS${1doz# IyPEmEʎ<5]y"+ӸfQg 琰3I+k݅$2֛ZVuZ.Y7+u' ׯ_{Vj pM 9zժ>z…ftx5KqDII^͆nzwhTDE8.Ɠ5\5$jm/U]GA̍ "e y1h8#%a&N5T1Re*`ޖW`w ˞ 'ڊsblݚV$CEj͡:>Gm@q/);దj 5:BMa*YedQ:AM) h*U#@ZB74L {afPn"P9S^k}5i(| ֳoD oṺ6Ƶ}Y~ᜭ.شiU$c'ON4C8`NԖ+[yrK뭰Bksyb쮂cCCGFZyMlߊ٦9$#)虙.Z졬uì_ 6 D̶0kQ\|"* 98Oz5ZUŒќ2/rM5feB\1NJ}FwR#% eZԬڽQE\ yDU3NE9-.%jn jow @N"Ҵ&3ڃC &YqPPK3Խ{b6g,=0o a`D}qؙc ~Rv.jO}FB @lEi6\<[blORhUH}zj#) \p/b ,gl GA}9Cn965:nHYe<y T⌮SURJ!1YG=!8(`>vRlr[bIYi:娼VS3O_p 2|ʜGk_?zV:i79Zmy_"Q%O;G>>ZpzsC @/M'>]oQbgL͝lߪwU[ND~)x!Đ+vP۠<.#ե: r:dAb&P2Db4Ad@{>,p!@|_SZuU|.lPSzuV].& @v)=u,1 ӗ'uogq`qqjt:| OuM t)4 Qt<Z DA"efDUk=2QTЛ{-D1P€L&sΑSDvݎ7C1idpڵ/_~gg|7Y&~cR.E *TqdDi qǠ 1! 8H1uӟdMh v-BKK{h$J"oxʅ'( K BC+G#̾/I3n.؂(IkTF H'ze""5z&/"Tr‚.LcJp˭==Tm%n * ",]aE̡EƢ01yur'6u%E~_UpyQE%F| (Y H}xiG$urSO$GJNU*ׯ]vY^wC;13s!PCwqZ'""[H!ޞBċcwUPVҫe[K:ˆ&vkET$u*_̎[ MϊF" I-]]8Hպu\P+vr, IDATEK$غb3`1n1aˁ1#2O,ٍ?Tzqt"ksZ>Zr DMƭYU$lZ+fa QGDTYb RH]ZXiRk_W||䤘[:0LEw}'*""﻾3GYEE%%9;9BpcP=sw,!.+^ \gS~jԩf^M+d[Qb%l,u0s%L6SKʰ8.'K1cMY|.L 01j/CU@!)ю,U}TVQD /Yv/IPMT@\tp[E uK&QLIXmka yUѓɈ*`ˆW*_<x!2GfF$#!:$ET#"pOčgg{@h$:9Y )*`J`o!yԒw%q"r4<i u"Ɓ_!lKO$Yj!e䚫p5daQ,e#PQř/#05hlQt4?mXm;TE5y5fX4IXmul#䩼NlAX)%͋@-¼6]O"5x('RpX^e j &j{kXU]i^5w-|LU.|#p@8(s4SN67k?곪|hFZG﻽եέVzޭײ:c̹ԩK׽M%Rrl+s:v K!/ yL b7H K-yC"RUBǒ,ɒ,ˤUu.֜c<ڻHZ/k`׹g97˓ʲ:8 ssSL`(pW&$2xDB; f,F-Җj7Ζf3k(LGCL3Ad[[Ά-qX؈0Y/By kN}S4p&N9 n3ĦV\$)enT%XJ{`rI-m,6X^<%"RR*(+W~sMkg}o{OZ>3ECVlӠhZIB m{(*R.i+{VܙR4,Դl7uΖ!+5dH6UU4A -]N/ Hpۀ%ڠHClR2skc df35rH` uXBYcEj)ܤ vcn=B ?盔P"-Y-3inoaǑED вǗ5d;XER딴BDi#<6$mQ#F_, GO3C$mv¬ dLj!3UrV4ԉHp6XO΍c#BLUqXm_9u6u^%dD|+ z/"4ʼnT5QC`¯q (r(2Y(DXn@wVF#e7'A|/[R]3 6 lH&3z3ξxqv| {aUe 02|<@Bc@}Ixl*V9u[Fm-Ӛnż!ihZ$,2PzH@qfe0{aZ#dL5dmRh3CR!Q ̑ B;IF3ȯ>ckHI~XD'%vǝ ) =[ PG"®]u,vlO<=G;YYDE/,5{)? ݸ];ϯ n bT@ەha5W0:B2G"2&b5Bmh왢416AUellojJMZ|rʇK`c}e΢ǔKTϠ$"ĞɠP#}wʭ:$$ }2 _DKd {={W~3=2AJ"@0VZLs""cՋW^ʥ|/g{L %G4Le 4hފj`.|vc@*EH/*O0R$bǀ`mWbN V9HK (UZ%)Q5)1=wɉVlPkR{+`V$zr\Be*fTqg0!GD*GDe2Z]`Iu~Fb!O3:΂~mwJ}mvn폎v{.? 98E;"![ׯ]u˗/-6l}|r\O@DeaMNtNْA.4+XLDXĶgq 3:c%p7:s׀|A a`Rt5MUb('"[@\֩ץ*dCk*ON1.>,i9A2Mv4#a%PYA)1cf#ꆨZFbON!I zjh&}}J&ɑ41;bwEYk_|ŢYoVq)!tqtlM7`R2US+l1'AL%<]Ynᗤ_dX2mV1V*i<"]ק J,[[mۑ3HYjhQ,xItS5 fiV:;,P,7-<5z) gT+זn7NɺN;B*%u WPh(xO.[ '^n+_kyENvR(Ium6 |ӣãwޙsBf2CRWVk,檚*"z3b`gOΗ~ڜY5(B8s&Kن QyVBVv"d)HvPM.wqk!h0 Q2lki)T*B K0kZ)C\>InqcڧMMLnvZؾ$2-~-x&T@& j6uUTM趫  \?V1YXj…n߹{wc=Mz'?vޱgZJ3Ƅx缰&XVBF_wՀk*<1hkeU! Ujz8>1>5g'՘ZE Dc t*i:R? C&T<˲#6VJmu"y-^Hwjوˈ*@2V59U؞>\`kD>{jgS|4ۈi^g{'W$pBJχzv1~!^#o !KƘGw3"Xk?ͩ"("^|qeADPKei(uZc#*`xЀrXj fK .U6>y0dڮzܗoDTuHCCyÉz_QH)BstT6+$-eKR'eqd̛4E jm֪:h[Qyֽ@'i|E.76p4ӑXĻaXsr ͣãùݞJ{zM~kףC$2cL޾s֫7 (dB @T/\RrhQN]>J6#g*lo:@dSHSkH  S24ڔP G7ƀK`名H J zɚR3'u8noR46XQ&yP!BU qbh"u. zRQ#Do@ ȩ2[k($ɫS%dܰ=\?rwnWj.ADg\\GOlyVRNTEo! 8gkq]2 Q[5FƉ.Rc]Iat-6WiQK,/c\J!aX H"R'\PU1JH U͵$4`1U^A 3yL6 V/z!` [k=)B^]Q*d!oZ١'ocZ@^{7Lo\?޽{ٟv֝ohtSݨDY@~G>޻ѹa$2RRDG YZPz#1tֳGĭNbI`O'Yu2SZ(̼8Th@w@A T@hHcXxW~5NÅcՇӨ:$2Dt΍;|Gs1^D iEXW.z4$zYQ'./خ3D̬U Kx_w@Bh*K5YlbJXcfx }Nj?&(X)V&X Udb9$XȘ";_JM IDATudz1Fl,"D4@D<|zv\2D e{/_Ov6q)i=Tau\U^@\BkB[ $O?qA:I T( 2ZH$4ƀ1Fu@NKw!בxQ#R灡xffacLNɶU q@Jȟ(2TXySooSֈ̝&nikRXQ5j[ Vx0p'|{FO~.& WiOz 봧d落g&B1y~5ֻ GD \{{/Y5|X%D8t%8x* { ՋYheON6ɋ5&0ָ jSՊ]i#BW-.SĬ^B.2&k̅{^}=@?S6xz߻v+o 2țyaUQc *BOKDڧo} V%D@(D@(9F ^H)|/^t%PuKU|uhVPN6竊Q}, jаgi~1k>w@H ^ 8܆/%Ck !Cd X`@B+ANFPEEVM5jkh2B^¢)۶B&%<"!<' yQDlO tuv@JENͣhOiT9U5֍0D61drz2*v;/ JHJD`-i~+:9ITau#V)r0JY(V|"m g7Q=٦ }xaq+Ѽ>)7~OǒYŝkT4bRv՛_~3{$j wvxp@"Cր{'"HB "0!PadBp&*.D'"@Yc8 `]1TpQSwًEa/*3 }4x{|XA..8Z5]yɃ*Fa2׭DA׊u div5! F__av㦘n'2S -߻pr?{@#B"% &N=|+7o&[>W@+z~ճE0% (*=BDQ'$8N:fN Ckʨa/c1ڡ"8 DDTzƑ!@,CaQPaVEDqpNlVb[.Mrf6e2(v^u |~0vk*M߫ ?Q28<ɋ>;yQڰV]HWJYpg)q@U2Q~nII{Y,HCH;VfDžE a:Y.,b4 a@eff!Bcj G'´j&6&s,n0@ vA'-ֳ)j"ͦx$$FgN2Wy;_$| c;H[Z`vHdbV3ۗߌX,aPu{/$?I\c~S'BmDljk`Js(w?lRH&Zc1J(*1S$y, ݬhEݬV]9M켱"j C[qZD2 uL'U"p5nXd>y%yi!jyBC+l<fJMz9k-i孚/D(~ ts)&%jp gpcQ"t㨢"k !ƯR,~IdAiܬE,Z*o)b 8Ɉ P h:an`]\5w ?{'P-]5,w[SVlYU@RN|OHޱ:Dʍ6J G]sû.oɕ]ޟ , "1ƚNA=SP罵]`R^sekx? wE#0HZ2i[{ƚ|Hl02PF 'y܉WКEaeUToi%kٟ"9 >@g:I%t{I5;*ݴs:I2vv"1ŘvM;&)0mmT:ɶzQD&eܫ Ay/ Ddf"13'K;*Sob̪/ cD^!{Tq%!01{*H @D!XB!ע7nX웠YޢU$7a.PeUdGº nJMEjuE@kcRa֘%Zw1clD&`3s9Xc׮왭7k D(!$3HReu$d]B$HYQ`Fc4B 0Y/sRe&Pd5UPv']G@b=0 C2Bxܿ臵B'F+:3n57tE; nUx~j>%&'v-m%i0ilב⊈"cH̼1yo2e#4|) }ݸ~W}ӷtg_zECS$$rU xu >'є++ޏøCW>4QɘhH &\JF$־+L=@AcbETر?w( 䜘P owy뭹jk.ܻ}hjT1m6j`Km @M[1)fl0DQa}FCJ`LVhKlsS?|f޿Ջf38ψx~FHBÆ47h:[,˺֚ Y`,+$9 9,AZVP CǾNēHm}40tiνm%{LЅ<ֵ!$oChʥ._|t=0 4D}gT H*yYGb*> .EހanKWE Z/4%₫S%BT-ha/gã7\5kZ{RmCZIv&-[\F4A&Mj QUxS8%A V•0f|N?{~ɷ^, ι`DA//{Ktc"B@KU@(1G#;/Q#j̔/jdpk%Ma@=y{ 5&:STU-zPu6НǂQC]N^<1 R;{mu'_r"8^)&*ɐ@8oBYBȠaD@GЅ!)/va6ֆ1($nJx`)gat,)c^-!1>*|0gG)tk\5Y}]l 2[OugN2@U B1ÖYR(CԐZiD$c6a$ ba`@UTѐ{dA#Z_]:=~RGkN; M%4kfbhcMvK 1D5֍vJDh01"2nλ~I/v3_+Wx /\8Xe''_81{%"" { yH^h9!x`F'Qi3ɖ4yt8G|߽{wC˫}ZغlcI˩!Q8i_^3<I&bF-`ݩFD jct: D+ <D3W^6Ξ@O{˽ /?߸ƒ1{Y,zظZ$D+ba2NRC3)~(qJSIŖ2{읯/8y}wJ( ;/k zkRvf:d1]gU8Hu6T*B Q^NG<{@am9|s_w<=4Z;Ϟ]ʥ+o10l(-f\oB)f1v,bP`Q =!Vُs.p3p^'ֱ4;LTwܧڼBMf&uuGlJ=V4PՍx"4d}TB5lPo@ j3]xnޅau}pG} Rϙ52{@"c46奤 _br )<@эst4WycWwŜ\ls۽x ;Z6xۋ7<|aVc-"A-n@Tp(25LECGwe$C/N?k_X2Hh%B ǫո>\c֧ǪJDH4 #{F$% ;"7:@% ie CTMPjh ٕ8s[oyc ri]m m1'0+J*iVc b֯d_?c(Ai -E^^UUXFݕ徊rot݂D"D {.}IDATAZff)O6 3},XGI$$ QE Z!q/{-a;ѽsYM6&qkU^ t+yt56L^3/Du9c V>ޓ5/^};j1)΅!QvY Ӌ|nX}<+D)wGX[0rFa_:4$8y !Z KxteVyhTުNS\PÌ1VP"11,'s*"A:\ȌXo!Ⰵ#cPÅ+Wm_ڵWqMȆ_#4ZT׫UR>Df{LDw}m 0@\QXUG\!{Skc æV>v /\--mtD8>A *,USYuvȭU{\zSo\],*(8%gy0xV'ǪB af2Vd+1ZePU $< ttx3N2y}{oW4b#o=NpmeU&d$GAECXrFk~즓e2o^63֏KW.#B1Bt Eyc8asz"hyh(P4PEU= ǂ8y xt4'<:VX 6=5n]U bL—4M)q }s0@ǟMf'q^SU=xt ATܰY fq}v* P{fe[ !zf0Dyō蝯yk.ߐ̎Ig:'|"適Aa`Mrz+(>P'$|W}oj7 0wO ] ;w A#2 fu>={?ݮ_"B7]$!r={DD!un5|{MU˥bҶFvV;[@">ڀ0NY#RYm7b,l:߻?yn}_쏾U^08买<}r9n֡EzMM !ga @g?Wy~/^&kjB(r+4#XJp"1$N$&=wb<(U޴HԐII CqF w|駮߹^zP`Tճc^VgϏ(%"c-".Kc2bU2FgjoD<5vR+Nc?*q8nH8K%Ͻ|KIHpk@011k0Ykkpn)s%ٻK5nd+׮ܼ _ofa{drn|a}U2]םun }=ࣣt{^wBN"{e nAtC=ejP4fN*Iʢ#D%4ژĐH"FQ` o#g,cPL3aA]??80lΎA4F0X Xqn 7vco 5nl1 F!cs7V|mZpè]jqf&h)AU ISQ5$TAd=Xہʏ?~"'>~3jMKF"2ƪ "]ǞG!Df2Bw]wyg\I-ɷ NjnԪ`si6 @0hW-o글d*l˒mDTr-޻ZT~orY s?v#!=Gf.""ut\5|om!pWr "X(9BvDU1Ŧ*o /Ĺa/GA:D .]mOݥW>zbX%s®5*"Zky}buWУã+_5{.N瓪*&/AS  DaJPlER]_1`CΜdM%*!Bm,ɘm,|ƿpB- HIe?7ɳ6gPh1dy 8 ƪ ~wz2y_=bUE):Jj.:QS!ZS1ΫB$G+$_hN d@$Jc+wGK|gW}O ̀ίNWѹ曙'D A E`}; ɼ5Y}خz#r"cqgɷ*Bӗo#SZ'%Q5:ʄTTP `yV5?Ƨ(;ĝoNFGtދ 0 4U9:<<::::zݻ%2;7a ~o}m |N )vd:Tqt8ENjn`-p2eG/I}+خR$Z#/??'__i8(xa:yk_Eek@ًpu{(ȱ6F 7`˗: 69m R8f0KZp/;B f]`q8#0ؒo}ks׼ZoW7cHj!6&L%mBIQx/S89;KN`p8+e={26;bvoٿ'_9gxto}7=y}lGq~__UUDCN **!įDTD8 .c`l 7w'֪gBojv}ue#VAa:kƄYk7 ɼ5;w~3j`mˉSDT,uѱ;Toт0L0ð/oY_sԧvUU;o "6@Df"a}n{["3nk^e?__̢Hb¼=q1 Tx< $MI(fk "(RAN3 g(nd +o \?/-kXt TăwĎE0DsNiБJ%hBl[Al(/ *HHDf(ERBfafDL*|0k^3x[˿~g z5{ū*}%^{Y0|NfLF neTNvk|.cL`XxD `|s\dUuUuE*Oy^KE-"-ޱ~FY[uc,Jߗ}3l}"qwBhY ~KݾuR:yp( Rc1zf;= L.쨈[j:c18 Pi\G>:(nbX"[~вoR?jq2 m|]dZ{eC!tWc )YA4fx7_#]+9S 4Yl y*bu}߮=8Nyy7+bhҢya^'dy6dnnm~nGx[Jܿfv`;@m[3,Q"RM{-ï^(_˲o15y/̭yT(K1g?ڝB޻|l=& w6>|g<}󽽲N딚k9JP-bY?:á:8=Tp ""GGG?O?e'c ye;9-/W6ow|{vB w'MJ ߻#>{׿qoduGJnw)EuJum.GOto}IsQL| Iu7-u{:?hKݝm< {Ǫ1~Haqתvzvk1ܽ恷}Mx|w1R ~rBh}%&U5- u(tr%y0.y'f |1-! Qxă ͛`d|>_.E4.CYM۷ |9_,Eqkr~W] IUTD8F|7|7oo @7|7oo @7|7|o @@|7|o @@|7|oo ~FtPMIENDB`PKF'@/u/u-Pictures/10000000000001C9000001C9925AADF8.pngPNG  IHDRa]U& pHYs  tIME :NDE IDATxeup=jꮞn"P 84ْDB?$ 01$ĈbK%R,d"fyp߽ {X+?qДIϏ}("R껊#PJ)VlUJ)VRJ)٪RJ)4[RJU)4[RJi*fRJi*lUJ)VlUJ)٪RJ)٪RJU)4[RJU)4[RJi*fRJi*lUJ)VlUJ)٪RJ)٪RJU)4[RJU)fRJi*fRJ)VlUJ)VRJ)٪RJ)٪RJU)4[RJU)fRJi*fRJ)VlUJ)VRJ)٪RJ)4[RJU)4[RJi*fRJi*lUJ)VlUJ)VRJ)٪RJ)4[RJU)4[RJi*fRJi*lUJ)VlUJ)٪RJ)٪RJU)4[RJU)fRJi*fRJi*lUJ)VlUJ)٪RJ)٪RJU)4[RJU)fRJi*fRJ)VlUJ)VRJ)٪RJ)4[RJU)4[RJU)fRJi*fRJ)VlUJ)VRJ)٪RJ)4[RJU)~@Xԟ7~[f;>{W#B|3?"yk_ڗ7;gOq /1JU?7|+_K=cz ˲pum((jx<lU_P}o?u><|:v;GuY,٬󶭖;ny媮£.^xEgm|cWX1$?%ŸyBCViT}/<;Z}O}祗^!2393!ACEQ|Tm[/(=Ę^yA~?!"JUFW=vӭV+af@$s:}[oۇG|>"a!aA@@2֐u,UE.ڝEӶbŗߪgf;^xG!4[x~>A3ș3gϞZ,mӼkom gMlvFC! c膱bqֶiۺmΫ|ls;/^z '>KGJU}xx;mQJ )qN!甘r~o޸C ٻ>s{"cCH90n!,!c5ЬټE۴MXvsDH߹ _<'OklU{={雷1g!cJ;KuUUO_K/xN $=wr0Yobt9s9c߇c!d ,|4rٶzwwzgszh3''u@i{_}KnmR)S!ZG{c3c5y~!_x4 ,ݝEYzrb>oʪEsr)c0hAc  !cs(\M3ob9]wuByY=}g'~۪4[՟_p7?O}6333b"@9B)Dއn>8<>:^#r,|>*uo|w X1eiҗ+ [b1Ĕ~aHd"Bc,K ,b1ۛZ,ga7/>O=#]V{?k/CC,a XA2c,Ifa{u8ܸpr*[S;how;.M˼톣h^!!s,9kjVj>N)sߏ]7lC n̒1uXgW[ܙ3U˯—>/Ÿ (Vtҳ/^~z::.3LIf Ak1,9竺:^ ADdbh7oC)wlVd":VۣhcDd,C>>1l糺)wO-c htd,CĘq9ez[Vcʟ>>rA{~~Wܹ.G룣10E*g",3֠1XUeSϚi1gH!ŘڶiwϪiժ sDQ@΢^gv6k "2Zufc4d,%.]]55]ohnT$DY0۷UUeWc$ygSY]uy;ՕwySb&@@$c !" e02KfH)'a ׯ9J׶9ur1n8I1d!""!2Usջׯ34[տs=酗?!dΈlcT,]Q]ߥr)2V+cMQ.<\.{;GG@4uisAfa!Tx)+Ko3gc-8c Ȉg)*wDRJ]}qCL!7~!Y9q M4aYs 2!Sb2kW}[n`~GQP}7?kw.Ɛs2 a QD3e=[kΝ;A;v[W{!:cybKܹsĘA!"}s|[GGasRU˶9gbLD`5D$4m@2H@|vsn&AD1v[o#aC`H8e`f̜ǫo~c?1}w twn})#cff6Λrksnirqܹ+o_}W XsϟW^1D9~;]8wv_W7>@ΞSEQ39",9'`ӶCXg/,Rm914=1ftkaqyjEc%uI,C9%1 A97|TCVi>{1ƾ7]7} 4O%lQXC("eQ<ófvʕayWVS>x_zN9}k7ncL,G׮ZGĺ,l/30˴XV @$" YD@M- 9s9Ę6~QrLqLKCz" 8ikPc)C٢ggSA8n7?S8?]ݸq_b:?v ^x!ȥ٪<_g\}3nV!DC 91Tc, tO SS{{ׯ{tZc$4ϜЇ||| 0Ǝ!fFY|owܙͬ98򰆬f.Tk/w6-"CcQ!Y @a<}0cٶm*?O>)}5[տ>ǝkקN.N-95Eכ~CߏGa—e ;31h-i "Hby`n1D_cfq Dk19cJ!$!H̙4ЗuմUբhHRsN1S)>CGg_?dX1iΜ>.;^۶mb7n;bO3HkjK~^>88z-cYr6)!UYxoY޻E !Y~ƍ7@R3g`!c8 9Y3DD 0#G7xwo1cΥmTu#"sʑȎC1Ƙ\,`yt/"cN)cXwU?cH1 CRl 2ra*11&Ʉh,\Y2֕R-Ljr, \F#9sougg[>?,bLhfgg1kv6Mhy `q¥K2؅'tf3y>_d# AcLaL q 8C DaF2f>kUU,_~6fMN<]C0ZYoA"Bd!2t[7u0asJu]-bhnxZ |Vu]B !9X;SUhyop`5\cǰ!dk YBDhu8sQXxN'gkdV3 kM9(ʢp@)yacǾw19eNlNj0?=aqd "g1aqֹZeU,u/[;aۏO?bhCzŋ۲Og>wWכm?t1$ H,<-Ɛ񅳆0ecN&'FdP$BeU⮻ξׯ_!pL9F1ǜ,lQ8$# ,S)sru!8:ZWCx*o1)F)e)Y]1&e*˦겮b1g6,E3 t%0c4ݔ%;f;}4XKXc !X1s<€']2}FΗ9_u1] b&D4eil8t0+C5@Y6% C"CΞztc_J)pLnRN)8xZjcȚq}Luh듵۲+``qH8ɽX}?vC$WX26 cߏHzk EF9g cbS,,<;_9`ڂ $ B9昐%YOD9bN98kȒ䘘 {gބr@$@(~vh:%1䜱εES7M6l6+fe19g֟.M(3 4_ǐ)iiØcd{GӴ'gY:* Yrv DdȈcgmIf2ƐsfD!g5w)3>}׹}_y߸uSԵcF,1IJ3)&k&* #[D aLaHv ]l3{v>kzkbH)ݸv Gdߖs;4t!d!kl)Hu»8g?ff&90o*9 qZ,9Ɣ#YC1"79r>oYbb~n: [ IDAT-J w2cͼif^UU3_4M̊ySBN|kug>i$^QKko_}v;  8B@ g pk8bY*B!2 H9FY9g !y $pu!i63LW01!-SO7.c_T( 4Z`OzrJ'T @,y8RrʲyEaCH?ucm̙Y$"geΒRCJ99kSʺu{S=TrsDPD2"9 )߬cu:bLaL9Z"}YS!*u֔/BDg 83ݰmh(wL cY4ͼgv9ͼlۙ64ܥY\x JϾ4[x7/}Z# !ngpJ" H @"Ɛ!"KΚii3s1N?YD9 #YXO""Slmz~k͛O iy'0] yЍc7B˜irxp|{λnl Y3k˦"CHMs JǘSd睱&bD2eUWƕ/*,@N™IS, "bG9&MݦS568p䊢tS̖*}YXcfjƐ*zVVUis0]mm׳/֗:GӡYUW|^bhۦm| cҥghc{ {GPӗw:M!pH`*, b I0LӤ&{ĜЖg 9G)ǔRL)TWཥi t3Y h3 G!YfusaXCsF2´bEēN֪ӟ0u`!0sm ifVs#a]9?Bc e4ռte!,e@ o,b5֤b/羨ZK`gaA aכ0 ! Ӓ ֹSoěΗ8cN!ZK9Kaڿ:ك!zg7u]4l60cJ~Hm;缛"-Y]E;mX̛qs^jpH? '>ial/1MJ7wHv69ӛʆXB"HdZ%$BcA !ǔ߫g5 "3@jbɂr.GY\z6Р0! Bd=Y4p_'9!rc(pSI)D!,(>ky1E 3 "Nu y֧@Bk N>AC!8 %"(*_A@ e` 0 c'cL!k}YWEYeI/Luv04"JaHqB7LmYBZc37v:JKS}s,lQzcwV(ߗ oVx8_kҕ”ޕe1;YȻS1Bnv;J ESg3!6y3ky3ϛy.cfnO?oy晧2v񞪺4YgYx*8QN:WETӼ+$@3Yrd@CksADH,1Jb8|i_XAq "S^[9o_r( Wg]" D'oh,JB̒3Tu:Mb9 VBA,KC%d!m2zVVU9MgtLȔ!A_zI,fRBg Ĕc(\QtEST["bO H1'"P C !c؎/v/9HZ<k $5uef8:7n\6mfg)+wNrt6GGq']ʽ!$* [cq) [UY].Myg1q nnW3ye ڶmy]}vwS+_4^//=/S|Yc͊v93Zb 3OcNގ0CBΈ;c1G!v}3d,:GEaө#>'I 0gf@@$iT71rsgN=}Z@05YKd,\{% @ qpAB!$AIrE=ofSA c%2LGw&BcC9$"8f1/JWTl2sL#cH8%c%CmoQ7@r@{g!2nf75!v;o:F2)z?ZocmDTN{ݝ()0v mSվ.}3fu5Jc vrʂ,vw9B"@wjwX_z7o2G֓.]~zҳc=+ 81xY@vW!3̀6%"IS3[Gk8S+i O%>YO}r{ 0#'#]8&,)wy2 1!"D+D! p Ȓـ !DlH8ta1PxSޗSHX.`+$! %X`p%R99Z[EZ*8$)av֐1ƠuX$S8 913T%Pֵyo.esjoX̭#qttR!fppMSۺ]4 ͗{sٮãj=:Z'aik7/uYeT; Cpl>12{Nsݝ*Ξ=c\~w?G/WRΟgTwLY LuJSQN|y3LO@9O"9ZC iŚEra aǕo7Џ8} #Y ff dr2 Hr5mzofۇ2K̒fXwÍ\UEmY,gYpt=:\C9Ɓ ݝ(Y[1"s9n,{zΜMYfu:޾kC9wԿ_z|+Dk f~ ki eZ5Ӑ)dA,`Ȱ 20qL" (1{1)3sȉe:X8`%D@ANNA$p^p۹1߽~/%ƼdLUBOW*8R2qb/4 0ŘS'HYdX!8pd Z1Y 䩎1 "an38!d-Z©TP8'Bu&bB!9Ӷd,Moӝ왝~]?l7cNA~߽yPWeE\ΜKߴ{px><<>:ڄ2,m)3ƐSv>}G3h"]7}?_~vv;;+Wo _x >rA`5[>e;AKFEr朲S+@2<9S" :os',0K yOI)3IɋtdPDdg" rb@,ꋏ> ou* Hf&gO'qZO 42 k= cʑa\mu"!麱FDtE0hF 9CL(LZbJ"0uJYuSeHSOQX$3Zcl!.!4pvX'!KfgL]E9e(+if;7ʹsd0 1I7$As}vwOs>pju{ol7[ɘo~ٿl?r3xaxk_ }Lf5[?__St pʜ=9GA0'KTZT%|2J|ak Bx+߼sy6c8@.rkޥ}";wq~G 'QV!+EǔK.HM 1xvݹ Q+ @: dZʭ%AպٞfO)Umt^RG2 l^z3 NL8ű40 Ќ 먙J*"R*X0@-!!T2314n֫U_`DTԊ$Ts䚶a>FlMfheGPUs*f"db,..wwڤoa3d)$YJ.i?;vv!x1%7t2Cr=~ƭ׮拥Ͽo<i¬1h~r{W>7>|鳋gq 3"@%x5̴@ΒrNcɹJlj]U/E&7)1QLjruvNbZ(yRd]l-f{]͚"t@hkYED2&A 7:'Mcr~CBMwPD̦$McsR339$`O4&"uͅ5kyGHJܟqs\RRxn<9q8wy69r !x A.=fĈu>mfS^ prZ`?~([[3oROt990Y&^q[׻er '';;9p6'gO~g߾'/ٿs+???VH#$r^ %RQa7P'P j R5ŠI;1GNTdI`Z1ͺK?3?~w;[&6UIm^*~F#Ԋ fjH8YL܃Z`_lJ䘚6hd @c$AScQEչspM."q&1c c6ga}2aH0cGEKΣ 13ܵm|f]; ޱwT &QW-3{3al"5lժo;wM90~J?b>]~Ï?{B"ILqw{H@dӵx6)]_xr/#8;QK\dQ3m>:@IF`PV&^ZY*@+ʌ%@YKaHM λY}oܽ!61j8oAd*X!f`HTY'kuB$jկφʝ}l!CcaD  H @sYXR"R3\3s!xZ&dEȑѹ0XlVgаو(x`zrn4i91< KAhQ51SE Lȥ䱤TNVfӏØsݝb>_/֎u;BdD4fJDYOR 1?;zoC&ϔ,V9ĭWW>^<|+_T_͢lDuDZ1E T|-ڴ.U͊`w IDAT jv@-5[`2@V/L#0 ~owk{'O4DDi T!`/4%Cd;Eq4cDZ !81QQམ&MJbZDsfrrm.3rnZ%,5" <ʘoX+ DdMh3{=O\TAL^Ɣۦ͚ }?"""Dm..|ҐыS"l͚-5ֱуynlf H 5u|lRY_|`|1ѓG]Q`ͦ1s7oLN_p6J_Vq3F"K"{}{׮]7䢶^9Lg&rD"VDk"Tvq* nZ @0wLW\6L>KXX)0ƄHnk<߻~{MwFU!U$S֚2F@"0@D0Q!W)j>6 S1:Z^R(%bf>8DT1SuǦmYl;raR)xVUPPӒeؔ.9"%a Rsib+7Tw0mEdsC59/CCcfl6+9TNy5a1oyb`DT@E$E@ `Ͱٌgl"Ĭnx|1/f˳; )zټeO޹RfGOS?l.b'뭝,.*uJ*;w9_q\<a/T\l hQSsE1RD$kVLx^AcFiךXCGEtLVR)#;nrMR228?AHΥTS_Ջzn)v@}Zos'D#)hEL.DDeLYUаz|lBƶ,:b_17ETMԪfB)㘆u6%WT fеi|h8&4!D/әf3SђsjD0FK_dS 4imd-%;G;isN+TѮVz1Bliի.:~Ϫ8g9]l9OU4fHժǮmz=V];nr"Z%'vv)gShg>u]U~vD_7D 先1B02;4'ABF.E,GӤ*O &sR+N+V*MT2Dιif}s&3_H<4]jg`uDL20d@ ¾J(jUBPAp6el1ƶMgՀk+Z-bSQYaKC:0/BuM㧄2q,)e91@ojTU63a08el0$3)M?<{6q6;;c5|E jn)4 yHYzbz)e̒x{Haԡa+W/tX9;E-E! T8l}O0NfPD:zP) }\U^ռcDW@<]a&y%K[wfFfEbV7ι1:p/W๘Z9:GG\6yy6bYydg/pJ" "+כn׀jR)i72V XDmcl|)ViR*E&tc$#SUUNZRPũ+!͹k|rc5.w%j^Ͱ 08yow'n~fκ^=}%I4\l6VD'I#͆P1"p`NV̄&\sRa,6 9i:7zE3}|M ߹uk "Jik?Y$ g P/@'dVh2F%*@q ުSM;o *c@4wwv~;#Ģ _aZ"qEzNhD + d<;-rzBތ:3{bysJNbZWii>o`3#2B3Z$ok|Nqܬ0'HhZlV|q9HYJ9uHfĆ>dZ7 dP `@; BMcJj5ٓ/Lt=Ra>Mn1~tGGfՋjY̷f;f,Y)X\ J 'PQ@sԵG^^fX9z_D|c%wyO~?uݻj/3~Q[D6]QD٩U{8 _jbdbhL\S LvթUZulfRT0}Qp;wѳn>f"ZVg|Wj^7Z^ Ц }ĞC|Nf$@\gU'0|4]6!ELJE@om8 !Ķ %X0ԧ4c߻T#4!ش$f䔽sMKAi3W91"L3bĚ9jMpf秫uĦ޼\D ggT296Ãbg{koo]fH9@*eCʳy(뼈1`‰ʈ`Is8fp蘍%|};Uu#LR2Aw'/[zpp/:?.ಈcd9Tp2!9r*Oh*5JhF:`UMn>rvTa*Ht_UL~xLJ5\p>n DpDTŶ?OjC\L 3 }?Mf=$b`jH1"K5ϟ?===Wc(-H14Wf1)_8YD;zrs310!>% KR1gMӸF$D&j[PF"`:Yiھmd@H&R%aH%gNņ!;;ڎMrG\pL`Bekkޙ󞙚@Ո(Llx>A,b*ү'>jկVg &fUֲ_n͑d }4)~?C߷٬>~?y{ѷ5|KMpDUw3HV k| DXgVDEQ&TΧX9Q5\̤H*E#B"-Bd0ڍ_GO2f)LP͙< b3RbFR,ѓ8h91Dc jXoF-u@[Rz퐝|Zښ1~HSZt\<rӠ]ޅW=}:~yvѱMd|>.JʒDD6̺##r")gub 0 @WDKy1"qވh4"&-_[˥ffs{]hXGʫZt1DTvYEDA!.1c*EWĞ8v7޸|W6|+o=zTTڦY.K,9l֒8Zۮ m$JoW^uy}(a\y%d_$P0Ujl2NU^M措HRZ9;,LEo`p7W۲vT!FHf&92|р ð΅b L8lz3JVDjf}?{U)}/?۷7^D3k_~oya;CD rєfJYU}<(@7._94??>z8F"Ϟlfh1)1Zz3F'{>uO忸x/ֿ@I atȕp<1k]WU+UڄMB)M1К& @&aY5 [.mx?!8UJ窂D"SP@5Ip`ؘ*HdQ@&Pm568i*զ9~szΝrq+/_Yk'ks7&FF&3@Gbcp`%kb#$ea(jbUHWmXnHB T|gǕbjEjN)YRRakkѵ]7nݓ㣗/bpٓsJǫ ΋xu9sXKN0%qW] 3@gO>|pf@LY?y9u]iy뽏øZE4QRzaӟ_'ˋ5 昙R){O XDXõT J1 MqkP@RrUS؄ۦi.!<>41pp戌RDf0q,ON| v04J)J;$&-.4Xww/Ձ)`p|Ouo\ʵK{%D>8U }g'Ų͢pWV65M۰c3ЧƜ m6oc ιP f}q"@L A<+ TP2gФaaL7_V|]G~IM.*Ƭ)sx||6B>iм;_,Bc*i 09W.ʨmJཻ?xwލ 3s!uK9cwnZCKz|bi)m6u׵DnJ !!iUܭ_m_XM޻́ՐL?F"f|tֵf ,; w`b[ۗ޸ʭ[7֭O_{?)ebjOh(ӺR!|9Dh)dl(U*u'hj"2,Z9kcۿt{SrUMKEr.f]Ր`&9q,u;bbk[Gm, >fKwo4M`v.DjfbV-fuCٓ*}wi{{Y Dq.#F"zǿ[@wݬMsA;Ȭ >+~UxnbD5P/5lA H0c4^=f'gw߽kV4xv9Ʊ?!ncO^YlEDy[hh j|?Cp5L'o??z֛oooE)YL#2cpdaG):6ij=CAo~ƍ+=yS"b&Wt~f0c0$jwLfAUBU qbI4Sbe#!IԤr)iPJɀ DlADflݾ}G?yyis\C˨Yh.ei!W<b (I`M_=<ۛͻyk< x{cbtdL y%Я^>~,8~V]vm6cJzg`ڮk"jDص[SQ}Դd/F<LZBE r1G !\J֭"v{{k\xswܿ ȄmP^>?!/um"2$#ۻu3] "dO}̭W\N% W<[Rc"\`ˬ$eA9D"a6!w|tֶ^YעXJ1&4*ϏLý/%^׋WpyB 0$Gfhc+$"P SE䜓=\w&HNTtLE7mC5ͺ0b >8Xt?w~sqxJ@OSJJRR60|<.ۯrż#zyw ؐEMĪ6V.iE_yD:WFo\[,Y>x\==@]59kɥ9yvD|&n9?*%*UTNJfp0zׯ.[ig˓{>z3Cl Lc$F@bjgʵK=|<&FB3A^^z:D"/ݯٌ@̀hb`2y223Bd;f u[&f=,^C 6քh P̈́FUod+W?[bw>}1G3W_NN[w;"Y1tRq ͈q>o]QZBrf ")lĔ}λ]=ɓy">6uMtX,:R;}v ` >|~8n;`(|RFW^9>x#"b1;ɧ)M׉g[<hgGwnwzQ[2BL1N""lc4RJR IjeEBЪ%G1S]s$)%>4M9G'>qkuFP)D A*A& v>Fo`)kҥ}3^=3j^o=}W=88h8Sau?dR%P2!l"G`$'/.)nV{=33 gы|vj8_\teݿ!zvT+<g2bxUc\> irДP򰪗PUR.1r^>۽~?|>;c1ذNζwLqhj>nhX2 *SDDn`yz~9~yLe+" ^>=>ya/^GqD֟s_?vf|ѝM0rg]e)'뭭9R*{^Vogw.W.jN/}3TDTҘr)H=Hh DHM9̥ (cUW?ۗ.ܿᇏ?Zo6O5Crt>'lc?V2ˇ?{oYva}|{k3ȦDQE dž)HCyCFɓ$q,-y,$=wuw5ߪ;s5e'P`[y k?P޻>k}+_8<[MY*i"E@m}h|SUW `!¹_C] s~8ϗŢڪPвӓvmHԲ$ҵ]Rg a٩ +`\Zo [v;?iƓaYrE1dqȃ{Eίy8r`M"^Ŭ]TYw% ?qڵke jխˁ+ D. J"BTcI5$D hQV@"t4d~tΈQ$B,6JmaOi$$%a.~ᕗw.=sh-$ple9r~U776MWUt` S 4+"Ғ7okQo:>=o+DQEb׆Cq bv'MmhU82DHbLW Ud~U[8h mZkIR1L< ' >իׂoq&JI v16WGU@ ʻdk) ^xsutx9_.zx{wo0$azC%umsr] hSbu< gFOUUk ,tY-f> W bѴwckrhJ$B2&`=\׶-_UQvDHK JDa8_~g.ݝ{z룮Y4!T Ѡ+-qDEZ8U!tALaGTRM APT@"0AT$6mk,U$#a4ALBjaݡ}կʳ-k52$ͫ~L@X8;@nnMNOv1_ u) 9`Z}p0|= <=[u]Ę"d J)u>Bv4rK!be#Y^m)~"SP j67lLWmlL^{eUTdA@Aՠ4>,VХuPWA]QEc{sh )IJY6~etq];GONEhs$meoXUQ!f.1l,cҪ_IJe1V"%ժ[5-"Y[t]PpH?10G?`FšIURԔ- ln/]޽Hpd\UpKl **ޫ$`K$lژ9̘|} p2k{5Q@T 1rb m@]8<<'$@EU]vO}*Y3SH„lYY"}8;JQU3eQi킳_!}gVtv o|Dzie7~'Amt*S:̄H$7Mo2sAcRTZDT٘/"9K "mveQ'dHr 1OaA&$( Q~Pk@TRkι.ǣhH*Sjۮi}vvw gˢ_綿!emm\y+'-\ Y4!$B$>&ÜBkg14LbVK_RኟljTڋ}zqZEkf-~xxS)_fUH DTB !.d7888@Wo,q̄I\41x@ݘ ʪ֪$!^ R 0"ټn˼^b"IѲɐ0$"CD4lTA%WU4 b/ /eX?;n4˂5DPrP)Tnh\a%Ů PhjWݻ{އ.? <=J)C*.S SQs kRWPlY;JAcJy Re+@O#HLB Tf%G8 1qBb+qêR=eU0g`X&ݽs.i Y=zw]8t+Wol?>|/ڍot. =Z4M 7FjZum~ ׿xc"\aAQ >K,3L˜c&"JE$P$@Z "(Z.)DE>{^7,㳛7onmUٺW"c8i5EaLulQ#33=KKiv( dȨXCD 4vUe]uDJ"@hP/|i,(-W5W#3; QE0e Jl,7(U6Ң{w~Ÿ?>=w~f@LT:[8λ!c])D l#&벪y981] 39de.>c-h8txa{g[g$X ]./ZĈIETs͍p4HI>%!=nL&cgËj>_>llY< A)HxZW3?ȷ~<mf|NL, ]ɘΘ(q$W#@L_|@5@@H[NEY\^ ݳUaBLy22 c $5IUrnFH).!v~%(Y$6%\SE66r\*qɘ (,/}/M}pO S2DZTBu^U`2p1օeƲ`l>_|[o}ҥ?VoK(JGe\a%bL^([.KH1$I)Os1ʲ*]4%@HI(ejtH1Ila&QK̄(.O߼g+eTL9*!&$%1I Κonl?{Zf 1umw5\ɐU b1;D󄅙0Y붶֘әC4CTcLU:mֲ1 Ƌ/%xDDCys/>״;woU)*v$@%- :7U)!"$|NvMױ `,- xk/po~_?e-UÈ]v]UgMY'($Iِa;g!IL)rU9"$!޵]0"ka\=:zct (YS0L1ycMQfNW3\n]><OF}b&$PU][w֧EUr/! *ID!iu^~G(@27@>7`h-ccc0N犲(;8+&u0 0jV}tCg AO_{yܿ9u߾{mb5^+XÌE(/]׿\յe t_ڛ7o:c,eUD)1fmɌ̩=YASTDO)%7/~YU}'KSyMATLUa9$ H$u%$)"0 PE[Db +I5iHꬉI.N" h( 0f0oV͠_1fgF3>BSaR SnE"6IΚͽݝ_zv4`ʺ12b霎SNO'SkǶ(\e)$%kHPUի˺x 7~|yJ &IZ#T%J `nIa瞽t^o0LY泥$AYKY1e- VU}d\.|Q"7U3]բ) :/Iڰc h|/_~s3SLo NAlm oo1w!P2hJ H]׭rgTumbu>:ML(1eK\XwZ֋"[,Q;<)(5NAP,۽fHw=hVK#DQd2JDdX׍mz9Ɣ 3Id cXG D EIQ0l"(AarU^V]𽢞BgÈL@d5e)uO` cI-{W_}qk{׫cDi{gifIYUI97,zvz1<9lc4QǺ[w޹/{PB4d T HD&{)zւeY˲Rfٮlv|rM|夢Xk2[kwwvr8_z艢3;7*m_֕EQB `04.^;8cr0&r9.~xRWDZ7OnWs?۝@Զ!ZS\vW/zU]U yofz6~ӏ~2ϭ5@ {(QBL !RG)NW)@R! EM@-bl]]UU [['Ǐݼq3w̝ck 19WZD,ER&h,6 !2Ll!&`T5bRDL!%߫6 IDATlS nCQ\k⪤΁" [Q#?Wj>_Ua**)&Ekɷ *Djhݝ^{ڱ1>~xѓ'EC֔0]U]?9Gdȕwa^[5>RЋuG?xZ[?!bau!5Ne @Q4iJc}򅝭u:?8J1e(u]ZÄ9r(Ҭڮ 0tֱ1zs/+K=90NgwZߍ5!zwPҽ? o6vvw=C:_ΈmCF%qL)% )%/? _,+WXB]̖ܾ7W sWbcI\20z !3QUPUA4cvG`4Ww[ƐDLDUʁy@b cm)y_" [ʵQIb Ě$+?'C + z:]]v8V%" cN 0&HE"V˹? W5MRHGg|ƍ[]"e< !k]0ڶ߯NOԫ%juٴ+b ;eUlܼ"~?>=;?$Zo#$9!E3Zd-]YUYZs|b"PWJ=XtMآvwo\֑5VDPJ%v7uu. 8$fs{/\yK]n $U [̖7=9;6m zqU&QekAD~CUW_~m}|vz}pܴ +HUыs՗ҫ/- Ll|veZ\܇!]ӫ( +u; kD3Ӛܺ @LQq/\: Ol;-$E#BmZW:dT@TTv (&! Hl|YuDTe5H҈;E֕;<ܽwn];]u"1 a RaZ~+/mm"?:~3έú.ʄ"[)%f^'OaQeflbV ?_~u{H׿>=rίFA1c0)dHJ$ߗPFd2NX&͗7>hD12 ֨+ 16M41Mgs[95)@4Ytdm=3G:k/]=, s,&QC+?~ Hq흍t̴;:;J lA1bjRJ)zYluxwo|>WM8O]58 J᥋¹k-b@ އբ]Λ7?+ +G". !k bRJQpU XCY$Hti9uU/|c<ս 4ggn?x,PP}bJm㍡td h16&08gU%F&$)h8 G[[^S׮ݸOm2KŮcN!>1 ( 6bI, *#&F&aD`HC![Al`YU1)haR#MQ[i$$SL)[j-S]'dQ^E7!@L1¨~'YThXyh:v=DY IBsjPT;Y8z|BqYWvYƒ+3"#(]!襋!&ﺳ>|td "E GF rBcrތ:kE`9_-|, ,1?z=::Nۦ)+E~V޷]xLYXWX D`@J#Q}]L UEa5:?}a#Eћ7?ƵYEe3 RNNEaUewwHy3 ض`0bBE$" sA*4fWƒSb%dEF%0'>"RY(HHQ8Ą4Zﻦ vZGGݝ>2oT1QTo- *0(cLHk7Z%CPզ£G'A5?cW2I 1VyW/\~imoCg Ӻ =}ns| LnosƣABM<^ѢWo| 6c*L :UgEJ I4%0/d<e JtO<~|H't&bfT4yE QD%jU`cDRYWE&༙${pt['f6mYEiYT[5֨OB$tVdEb woָ_zh@Iͷ?9yrrƵ^]–U2"%&նiYal8 WT%@WUU!3H^XQ$ cLY|@I2ǜMQZ+1ƶX!1i( [VəuOZs۶^,bU;H]W:k譓.EE+l hY;W2Y Hh f:.jQQ8$fDu7]s0c/ol|?/Okyw g0ƌ%oZ}n [Q2li2¥ *wOΎONE )%D*ˬ·(D.߿p;=, cm& jL#]ʛ̈cMSbr[[;9[X0@=X$gVXLȺ CTAI U0f>o}^9 9Y!!ݹfjxVuYȪ(Ie\6i*:fSXWsQ"H%kɐCk %(G_yp4{GM3,:Kd#1bX.<;0ǖj2IHE!!-]LbLt SNB H}Tlb Jh,JB6RВ*B\yvonUJM"+{VcR[swyH4{"Z1Y Cupk>B Qzc*ԃ*xjը@Y1Ek T8-'${g7??S_"$Z.S "HPIdv]/pqsc(Yly(ڎ + 05ip^8 Wʮ#;" |Y͆-bR? w77uٯ 4Ȩ-m?9a尴…sM_zׅlfuR D|$qh}WK?`8&&$ѷmwObٴm^J *Br97]*soseG\oBB > z66'{{;^};O߻s l+ g,((l<|p3*b4(U&U#4Vz=N! "!#h XULI!&d ֚$DY5_ X*L`-ub)C$C El $"s^~w{gs8f>[G65Q[v?zpnjW0bGj0EA N?  1JLy0bQty_Vev@j$㜛QUS۷Uyރ׿Ok f`psFAk)^T%Bڴj';w&hvRۅtDc|w|1sTQ ^0"tl )uh8>?=@BJ!.Wѣt>aSRQg]H)mj:;^'d ,m e +? *@J)Ƹj7>v֣Ǐ}׵@J6rXɣE4(+StƘ&PRH>uM{ϟak67ڀ|pݷ>K7w&ZGĈg"ΦpX5] €D I%|5 TFDFM)gY1b Hkd$%&̖E&# (AL**C# IMUgx}I+Ta6]5uQ!T*u%H*Ij2B*YK,BU4*XTD$saS[R5jAggW=럥.w?s Ok ke" Pd!+\?wt] e{;ɭYYJ)6"ý|3瘍3lt!HYpU:bmc߫7'mF~ES )r=:nx2zum5EYsJeF;ރEaRt˕:y d\WeYkHJJ*M\~?>=>!Х(@6Krڒ9sg @DԘO_zq7;zy1)mለc+RsX/ ۟}k߬wJ(I{a˿ctI볣M4/ 0!*dmӤ{Hf}L޻eU\\]: 7(fOuE &Q1fO Ƿnz(Y$ZW](&EDd,~EN?쩈,W+37oƣ8PREU%'|>c0׾`9*}]zτj&o. 9ĔRTB~xwzW|ᗟl\*c$&$vާV|۴\͝.؅k[0h|UzNJhHARz!)@Gx\!3iT3U PDؘXT BV ݤLK #EjOF,],[Sźg^ujLI./'c"a 30E0]9IM G수1'/_F#UޠwM+.AE@t5E{kgȿkߨS y%,;ʲ|@|@dd2g 1+_0{3 ئ}ӴU",1f9d>ZV)F3UDcL]JEBEɈd@#M<`YBQopޝ,vimӧ\0D*χf#!8Ѯ߬CȻMӮmUc.ODL4 p\ﶻ 3_g3M1wFɲP,0b20$ 9.%`)Jy'.$r jd tPfߴ{tq~3=;<ŤL4(PLjb2IHH&V\IDEm=r4v]K[ {J䫲vhw̎?_ֿ1?Mf)%B< Y(wo z^ݻV8_ DZ$Q0X,WDs$iCQRUܹ}ggO^fcYq WEY␤wս;>zuɞ XEDK3;u<{s̹afzU~nR(b6kHߺ|< ^SHvnǿࣦicfV>yNjKj"Z!#!3Eչ0h2Upl~O~\yˤ>vmӵM_F0;BZArWu}3:?ML;A]zS1D#`@m+qIb$`L1`И @QafP1/8Ee&@D qժf@ D0!!c|ѓMM=Y-"b3UjO HLRՃ?T -$CY|Hα$4+ Oא GGtOom4)Po4}l;M B͎?ׯkoS@DG9g``֊eYţw‡!i IDAT֛ n6/RJf4TT$~߆3w[wܾx=r"Bo%n@U 131 $ a[߫w^ͺ^m..!t4 $fFʑA[0c`Tu197;B<9z;om|?qL]U2O."9}pY/@fF 4A2UFzGwGb,*mP%{fc3# z/!J̟gǎ&% GE]J|ow׵W|Ogr;F0@8X փl<ܾizW7]Ĉu]#ȑY)\pē{/xn@޻;rO"uQd6sovdTxF&T:Ym^Ut:UU&CԔ%B\uL@5P*͎&u,;V3PmWO˧QbEBzgjڶ w1 f 1$0AooLWU/o>~ռ7˲ $B4I4n׳)* /)mV*tkǓ U`0B#@c_ϙ`<ꟽwx]'Uo`Α.%%5P(!{R;(̌Dr4 H4y]TEL̐LLf9~uL0$z-{mڪ.05IAX%8J#sD-,1ԱsY !S0(#“Iv}겋rj)V;r>x}_޻㓣_?wum?L)*3PHDHEY̎g'wfGӉ/|QL___L&;\]K~@ib8Fa+&WG$bcϯGhXuN΢),+-. 2D4Ÿכnߜ:EQ1(n7r{ggg`REwgg;)@nܿ{nK}'?0unKA!1*j%wp<\WbYMSa8wɀޔ cTCDvٵ<W7wfń`HԐy6 TUv#1av)19vm ,(%5I4M,!b$ 5'oxUEmw \ 0Z2 BTx3E4Ip!S#]$rCDd}jXj\Neh옹i/^d8=o~'b2DbD'L h 4f{f'PNfX5 1J')aNTǓww_]\(JB3ATM 44n[G<_ΎJ9Ё A:a&"1ZRܣGԔK&gfߕ($&4cǖU$U0DRLoރH&Ρ {xrL3l6BrHk:gȈ`L@RRe@fI rȌmr3t8=^], '6Uݫk(vD[mV_~;7O?j'̄).2xxsh4=>xrz븪+BQڶ횋v9 Ō26#UN?oܺ5Ǐ]@W"3'Kad2 eqqП&; BED}wy9?{=vdIӷQxYP0Cl^cfߺ5FEQpNҔfWTeIJ*3"J2X q^jhӮ['',ժOf5'_{ mVMJꦮfN*U(OXg//u?5$s!ːLq<nCp~/ ^")? W&P@S"v $[ -> Ę$ZLdjHD(x#8#t΃٫f _Vf𠨼!L P!'Y*cPE2MB& t!;HN'ׯhf V~K&],ڏ?;Al?~][5|fUѤ`<";D"D?8:>xt{<PbTn\m|v]-YnצQ,3Rܹw'}PEMcKk|tTt6 ɰ(||Y9bom.v]wqq}3_GA_ץIRbbvVD3VrbDD50Pjf^lVpB`omۮV_R {.GfF1uQ4IYt1 G5AwʢEE}yvǟ~2/,bnYF"؝vwui7}_hg&"/>{v2{ehf h@f@nϞu\yL:QCD `@IWDT);' b:Ej\3D;`$ y"%ZzTC !>)( x-GN֐CYu^m~)jRs_YT# fA9!1")zO'g/L~T!Dz¶QF,Jݮ~_~UU͟~+g˖@5 "Eo2ܻwZuU`DDv뛦mUM۵" jwygwL&"^UM'pv<{c_E.} ")mZW`8z%j׉B`hbf*f"LsEf쯮ol8B $&uq\:/j9ϞP.6M`e#>0hf)Mޣ{˻bz?jy}vU+BVg mk*W U1B}훢,Zj~2os?RrĀL 2ƀ;x\r&(1ec=/D6A mH"Uɜ!@e!xBI19 L:8%)̹Jf`&B1M1SYCt冝Eu Ǩ轳t πb<:τ %*:fJҟ//w.8昨 t~x?I"׵Wg79XU !_;wOz<z#_ܬ^d8Ng~*KK TL Q9>`h5$&IE2aYbJbD.ƙ(宍]UՈ6;Hj@,:`Hύ8 IDDPL3c<;l.!xqi !Ijvݻw(?]o㽯k/|`Nr#{OH!h:߫TAE7|_6QYÇw<}:̦dzd8/H@` FjI4X6mʲf7Eq\/nHTץv2E|iD߷Tou vbRi4O|㯞?{ LÁ NJT5kntU20{։Ӓ*[{6ڻ򣏟~g?tdV >̫E `*C8yY~Qx2`tD\U +Kڙfaf۶^嘀1xeZ!3czcGd𽎆nx:f*鰉f>3wH{æ ͲUE9UM*]y袀 L9]DޥHH`I&<"jJ$C5S<]r6nwm#~_=qhj8F2H1#GtI`dFDbh"DU2 hv]}Y"cٷm@ivݓ O:ݝNԲ֟} %#fD{_t`)Ic_|ZmVRP0Kf샟NOiك~7h^w>p! sxA!ybd2O'anf h4j2Nɰ,*M]^_\1۳^t{_ b䓧}fl@m0HX$XUEY28vHn&bm+;ycVǟI(wT{gfLPۦͪY,_>{u|: {uyXʒ_/T<Sfj^n֫`P#ћRPū Lc7D},Up81JbJbjEU> U5ֻ}ou,|{?ӧ/$5qQ*ęԈlc:y 2^_DL0+%"Ƶ9b2EfMFxP| !IEJb:dCIdގ9ybQe3b>f^xpz x7-|~5oj jfeY{;(zlG`Pw1|ylU ^]mzUE1GAebfXQoK7f:bU% ŤbFAݾp*@Lm~[߹{vQT_=}ɳO^ eB 3;V^^IY~ݯ|t4TEH ('" iy'g2{ҕZ l<)`[ sеWDs2h .IԺWRQn;)i>B#2AQ$MoDPC%"{"DMxҶ|;Na4uEmL(@sm'bo\uUAv"9 MQ UѱG2!ɐ3"`vĎ,w]Ʋ*$&Q_Uɇfp./U=e䓏e?3S]#vRs'GNOPb^.Wg/ϛS5S#p?:ϦG_; E IDATص9f٫ *&s̔/x" (;,7;Ii۳痗V3!ZfF'&bg˛٫*&d)u)n:]":O``H7_NCQ`O=w\,ɾ\ d6H%*{&ijצfۖuhL ~t"&gbHHI ;]Z`!; И$$bB@r @9"4*AwvI$SIJ8Ma1vh뺘rhV1#D!C3جf엱ۙfgbKL` Q_C JI%R QdPK)! #S$@-LzZb9yoj۵〬.~J=5?K~CRL1Ƥ*{[AQDWY.ɓg*ծ_Whz4=~QD]]jv݃w«dfNNyBٿZin?jR7 jx0nؔ!L&6vu]zj0M& ehFb_~ED* Ep.AjcJ;_(|9o &MIR3zݷ~7`8‡믾"2!|z3P1m/Ϯzv "kzgw&aL 0ǏpS"$3ض|y< QFDCUxTu5M#IG#ADF"$nQ/~h1;jdƨf+<&JU3.fkvV Xa+#$1GKl$@.#"x$A&# O7dTvRHsrE#4$6Ebv‡~fƎ 3Q,+j9Y ٫{N@9ak֗QÏPчoџo_q|_oL1";v@TEUz[eU 0Y/cL!GGǓhE&^ $fw5_ܻwz|<% 7JT #GHنD&nvS,ڶsGj8mf\mצ0=MG^bw}DP|]WUU:yس!^]M״] J9dJYv]LWz/u{}cMG}fRx܅wQֽ1126jbO!J0mxq~;!(3@]?AIQ{Ⱦ@gOϪsdi S u]6sSCvQTЊTA}PM] e2 BЮB"$r 'RP&"f3W 1l&Kz~hvә$_^Q~M@2)Pz|A_]2Dd_wL :n Eso;>7MR*BpU5ֻoݺ}=_c?s2 "{Hwɤ6f,W7ϯhԛLQPd"`QUQmv\ d:$$0SD39w ;G@$뮯/zrDWWqQ.\0 @0u=zn}Qԯ_?z|"UQ} am,xZ_|S[pDb݁8#7g'ASMg#b|~Wmd}{<Te A$H9*ܻ[Wt258rrB2"[b"iH@%SӾӾ37g㓓l6,2/=6b= x<*b\UU3d:L;l?GuQWHHȱ 1h>iܮ>(U7}Zm66ge0%sFo׷W8ˇXwznڎv$5PMB ®=:&%&W6D)"\@.Χ|˛G/h>߮6mćw[@R$F6E3pw$BUtdFj\f!@G(Iھ7(UHnF_˳HT3 2LB%tt{a 9QrE%UF܌ ()H$vH@X)$7ִbR}^^_@| Ͼ5ُot_?ʀ9[33\-zp l<6M]UCA>{|.FFHXLBDr/a!2R2n= eUeQe4Ѹ gQYD&vupfd\UQF3NM1-˫rfVHYk]OEc }{2;ydXzqh7U]S.3vz}ʠ֟MMEG4T^/P:1 W1onM1PE)E!3U'Mne&^\6jwi0l_;xX.wh2@cU7]U1pJ-q }DPȮ?r-pa! 88#b\S B;+(0 2A=q@Qr9qyAH\Aj-QáO"1s@]5w %c1yҾUD!KZ^́g6B*zIG7PNú*n^Ǐ~S[p_?AYB|:m".*ʢ(ˢ` aEW@7%3~Xo$3a %"BLrԩ+sGu< ?˪Hf]7tT!;'_^_]ݷv,WkUKggd4TEb|OjwwjӧeB!"=o%H|;?w>YXD ZGv}x%1Dv{ p(!dU` B <TՀICeS_L'͠VW5'7YI5M#1 ܑ )@\p0o, N,26r39 j] bmץ. 3GH TE@)OsByYR1WG,U"&H 1 =fNH9>&K<Bהk <#B25's^ ̀ (Bc}{Ha8\H?b2pwG`nx릩*6{&GḖse: Q8}p0p*cwcb'w z^AASj3 `,m=(6.ĐUwNH3{Br^P*΁͏ 9CN@GW&#e1ǵ|=RsbM’&@jf)%1 e rxC,>Y 0Z(jfy, BhX7֋t:جED8u˯^uy,g??ӿ??Fˋ{[O;oUU9hӳ)!m s(w]zzX]>:.r4g̈(rQJ9 ˢXh4^ݮCD xhfsh8l&NGd4eYS1gfdiڴ\mS*bȱaߍ÷z'OON/^?/1h4̔p,]gnݿ~}}t:#~w DXU!o~ H0D:dIkTuC@3eY-98! r-$twٍ+8caU"fk۾hUUj;4ԒZm]Dʂrn 2%Xc -#jH$ HGeY~Aw Q:y d=] D59]$`8M[kUf`zTI1 FL.,\v@&Al\UBd]W!"⌘~_u>?;Oy|q~:R 2 D0 mӾwH m׭7vIsv6ꢩjY*ѿ+S߶rY sYxX/Vl22kp<4v;<}e3cĜ t ̜4um׷&%prMps0t6?|['t0^_vUQEcs"0p-#d:rt;EZR ^`2CY ?0bc oZr19"8(f~ G,q @,!7ggm*ݕsn`IMCª)cH51H"U d))RnqYojNoHnQ v态=2@>a&݁DTHͅb: ];`IC`@u#$pU&I-fF7BGJw7&Oܐr09E7d2?l21\vX׃z ܻm]'W?7//oxb.1SQ \U;Ƈnw!QbyxxX.A3L7<ۏ8$;ppMIjM}Za4d8 aQE!ľj[l6i~w(-YQA$K~bP " dG-r#zw]u)X"wwO]3h_~~ǿx$P1>w"bݾ/: ;Lxt*,#\ RB:x€Z@4UeD"2&MX x!de#:av 0U>cD]d"LoޓcS:VW i(U;a@SOMwԶ=P 4u^Xh ]o]2LnYdnr3:wI򧫚Z]b掌ܼ7pTF9:5#G$C  9CгYY4)\QsN\@ `Se]7{`^`m,£G/~?}S[ ΋_,&IR" A:}wDBOo~wm{TՔjsBpr29ͪ2pT$p 4̷j|˩-!ĺGjIUY/m:N&ASћr[7!t a<Mg2fH!MB$B&B4!ikRUTfKˋ}[G?88Qo7ca86!r{soz6L#!f!Yu@x/}:9X^pU1yw*" A6mJ c"F$txUQbQ M#ZgT!/ʲ E,lv"(O}[u$"1]i >T9 󍝳IP;vP7h>vBwP'$#e ,"IYW@n Y  ꒁz1sߥd9!ek B@|pB9.,;G!M:q Dbb&_.V̡fLd"zEA}6y||X/OMm?̓Hp@Uw l`KYuv/f}J^EU"2hd)'qSWXWe9s.#2`;ZV2-t<.' Fk7sDOeQl7l4 T7-0,kEMdgn߾zy.XշgtkϿnZuYXnASpf՗ףIU7t:zǦiBlK/'M` p<|82#YJ^UjȽi۵\> IDAT !0\TuD"6ˋ}x2]þcȡԈ22Wfabnr\ow9 pX/7uS Qշ},bw!r'"6$✅*,Dl"Pȑ <ȑ<с8gd3cn?DY#rv;!Fqf]z5`.7耈1JGʠl5%pb&#̜1䌐=)%Lf#P)`Hź4,ۦ,) s "nw[_>t<{7W?>vYe}O}nۻׯ^/6u-"!p8<5WCĶզ)2Bȗrӵ|^_?OϞ?u<匁6Ue@tsh۞ǣcrdz@<b1Wnv6ґ SߦR݁SY/}uv:Gsٸ7s oMd9lC"Vz;rb@v)D7CS a91e|g32$v7BQ,i[AHDn }Έ 2@)_\tDTu#H-x8bUN*$E &c>2[nH @!̯`vB"^Uuhb9`]pكs]gׯP_&"%l7^O~_zyww?_,ӧo>x鳷O&ƘX]7l\Bv%[0o DFtؤ1fPի+bԤXd2NuSrD^߮WmDnêTnɤQ#g&xœ ΕRеvet:N??ۏ?㛫l f"kR׺mZ,V|<4- jO)/$;j^12d:LESwz 1R,)\fIF&G?'.u)_rvs;TfyMGcɒT*"B$"E nޟ-d4jŋӓ) r+ ibv4x, }NH3 Cߛ;";TcӄrDQ{#GG sNp@tE@dr'D  h؈mݡ!'8=8[ ϯ۶5KLHeYxeY!Ez\Xٶ]uf4n2Y3 {2CwjASV%3z]VEJi~*+MGʼ1Qfsf<jvO<իX͠LbbG2JNcpc2 \S!u >WA_~_YX( 2cJvt6wŲɑЅo_.MCAt Pݒp(*l{(F131ɳw>?>}z H?"`:ӻv/b~"nL{sB 0@ک1MoVU7Ժgl/_w냧Pd p0XBhM3WSB!%==2'GS;AB3( KL֓ 7D (E5daKPEphFīe]bԏW_]qY!}M{VMѐ VUHְ#!^\q@,hُjIU)zDԥϿb O_?ַMm>7mTuQד`0N&IUu] Q^[2[:&K!,B MU5/HNrB{UYU!byuu34};'d\%8~^_?lֻԧǏ.""@̕02{n~s ELڧ^)qkGsc}(b|zug~Fӗnl{zeP3 NEhDD)9 eEUo?/>SgϢى!`@% FA~g>"#: ]d<޹<ƜmSo 6FpO?~*̲Ym'f5k`(98 :0 A[Cb$O`D[O*Dp !2 z߻&$6vUH();[683PȵjE3hN.Ox٣mmfy09 2W˞wel 7PZ N QKQߧfPl7B*0`aSVfP sW_|^|y1;9|kA'o?yx<:Ӂ\r8wb%l\Q(DBHfKZe;ú,Kdϗh/Al:p?~uJƣ=YY$A0%8\5呞 bumP~,* nO?ΑnN DnPWADeَc%ܜ2M_|Kt s Ad$"3My{և?xۗ'1RF}!C|PLQ̀Dã,{T($Sphʹi@F4s蝔4w]]Ɠ $7jB9_ =çX"q@B ռ=icp2BdG0j{HݠLVr s_^\&U9ľXJF x䔹9QČǸ5"Ʈ B`BYqđ-e<{5sB!a5Ͱ(b]j~Mm;~ht~yۏ?|b6TU)y{ cLMD@{Ք4alhTՃF8q]$sEXr.;n>_1SUV1BwaVhRN'ɨem˛vWUq4fy"2))o\I5III,D{p::g~u8~|?zw^o0Ȉ!1#:'_"h8#o =k I_V;f*%G#sP_e!I9 XC_a<}g>y|:e)!wό(<%0OKG®kMm~ZCpv ""w'e/FB!{NQ9Op *2KEzp"M)K,#PD,ӿMm/wΓǓx0c)23ӔRVz$,KfF9 DI3W!pU!FD/˦inLɨz{4=Cl0 d+":;"iYLӑ`@Hj4n7պkٴҾm'puo>j?eQDdf7wEx<\ I򘅎*Ҍ77z0(@DrvwjI঩׫-#KI{hTe%DEMU1[N3PGzӿk Ql٣9f$n¦r8h$uiH!*T۝e͡JN,H{FDf3ctCpP@4g0V!HmkNDLd@ )G(281hJ 5Y3ň@ԛd$)ydɛ9o*XjMc4ӶKe4Q LĐ K͗<E4DFDs3t"23{odLߎ%kr("wb@baUEd,Wˋ˪OoMm;?_~zsu~y6 LabbQuZRzSún*fZovɰTTD Qܒvg4q)Ha\1UUV!߯_><,%t4Gp!%kd6;Y3laUÄꈎ-ĀDD(!FYG;Tݒj/x=TEg*$)l6/^ݼx<+$$lS]C0l !|4S q6f QUĮd\Qmv8,"B74eOUY0ǿ'?uyyZWeQD"DsBCDwږ]M3Eb~񫗯n-e^ 0AX‘^Ǫ玄k,mz15TJt* w].ց%AiMR+X L{!#N4 d HGY4!B! J0w]rWsuun& ́ ,k2B TpPGפEUvזU)0)A21 ʛcݼW LT6 p' 8",BHZlE(10"Yn10/rwvlvO~Mm=~a15A"bfࡃe!xF]Y'sX7C;&'㪪a003E$)i0fr]뢬nٍ'y۷uR\iXB^_h4]QggLZd:.@~fXLDYc`9 1 `Kf~˫ǏNFa=Ur8j6/o_xx޳ѳˆ@{s@ @|ֵ< Wd(T7M}7$ @0s r8ٳ{񰈁d2q9 3B2db%}lwO?/VqNaE"T!1rv޶Q `8,-y"4Mɮ"j/cSB%M UëRеx:`` }"FQ5#&vծԃ *NB*!I DjIXB d9q,Bk!F!` k@77yjy@nf` GhBכ)AUƲ53;}LX`r~+ΩJbi7wU4S7z x fSzѰ)` N79"hfDWmYeQm@.&NΧٰ/n/6eU /la {>S1066Z E9lGzǏP( E"8H h4zSa4a:|Cؤ__u>/3p8 MYGt}sZ³c3 #sMMRTdF&[b(3"ֻ'OM5'GwUYEC>\.׫զme˱3e̖Zc1 fhpt8I)#]UL\Dp4 S(Bh4޻{T7x<>Mo{oGcR1终); Q$^\ݾ|5?n CW#mD׵W/>c1!)%UP\V-; &b]m;䜎 qUBFP%EÜ@gfH.%b`xuuˬGpX ꪪ*xU]N'pZoonAYW޻apkM"zzPCcE0`ǚt~ӾOGpRC`&4#䝣dYˍ%"nVw >01iҼڣu$EIh~E(Jݮ &̆HTϊ2oݿw?pMeλ,A%M"`&QS2b`z?/xs}֐Z "{63;5$LB$, s~ȌE"JH`4<{r4$XxBrbfgHN^U9#X̜syjG{",ˋktf+`ͮ/ʚ R\ׁW{&_ŗWDŽL!%^Rjhvp8&@m4]UWÉcWVU]Č2=);r>.Gv`rUY^\\yャpXz9Ulۛ`XfӺl6lzxtDHWWWM;99,;O`93K ?فB"0LQm'gMݿw0A{ث)ew,x2._^uio9N}LfgQ^إ}o櫲ttQ-j{oݿkSX,GGwyc:fl?fQcàٍy2F IDATIU%"!/QE>gWn阘L KN w&&XȬًdX^UsT\Y"zlslcjxt20O%ujH` KeYEAًىќ&Piee2"b1yO3(9FDXZaΓs즓*jRQ:h!Jb9g0@PF=I9oI Vb/}'mwzt9H`1UEEQF{ Nvf"{~{׵7}J]Y"b ~\]n;C d4GGSvihZV̦ yf+zعPFDNU,Iz3{EGǓpT3;"./"L&f6ΐūiO8۾!y/)V7Y$-b^\߸{Rd`>hBȖ _搧`>_zyQU5Ÿ(I#FF7H̆QE9G䋰\lSJIRQ "0 o}o,h8(y:,THݥ>k*cxٓor}u ^;B l\C;U јL:̓cKJ K__|񝃢t 4''.1]n*FfIL= )8C۫ZPAU]`})MF Dp?wY#+ &B6fM]/}|`" É<ËEQ"iak"2#dJlMJY)@~;2T#${? ƣr8*±+R^,]״}SiWzٶMbwO7\o乼z~0sy9nn6bX l8 /hXMC(9hha0ɓ&$U|荠fmyQ88? 4!g vMs}Y"LP|yx8&ٳ|@"jdI2f();9M_0moѠ,RMz#"Vۼ?0B)Gf<@Q2P6$vvpT8}䭷O&WKr c"b/IzITta32yPS$YĶn8('_<~y,>J7FhLs1cur|$5jcvr7ߺPB? Lw,1Ȏr%&Wx`0Q5AДR^De"czJ@f2%U#U,QfM1*d1C,Q(Cݶ:4r7 NBt+˒#@4 {3]:,)>S2o/ί5\__^NPwy(("1n r`42z9'qQ41Η_ 'ѪCf& {}^VEkYU778+i_^/`0 {Ɂmk"so9D׈UUS= M1xs3ãgϮ[<>Vur 9r@7N~#4_l peD̙B 'TfXp>G޼?M\ݷ{xwDvѯ׭w'(ԱdUӔbʂ8"TEQmoI4'?{r}9. uJ"h>PɀxŃ}xL_gLT' U fLJ DGǓgo';Ճt66%1PŪ*#pR•kzb!#2@Gzwd``@`vDqA>>AT1a{@M ={hScb1P4Ӕ}F/NfK}U´l \8v/}މL ƨVm7gv@t./noz~Ȱ_;' ǿum??\/.W!xA5MfӃiuUW%7۝LòR߆ U&d&i۵m=}zjeU&ժxrp8*PU˫r;pz^|d? T4E@߬"yGfT}5?{z_?w˗7}]:O"ckm1D2^tOnyJD`@dMSBA"UDh$ADQȑdjN@H@L&eD|P!hY1>qynpH/قHL0uŋ:&ÃaIYk5"=ϒs HYD]]h#v2Oy *9劈}R1@GeӐFa2 Tk[LhX2{|9U!b$nv흓[FbD|"| S1MӈxR>c0=t|/iy3^=z?B>ǿumXy'p0"e19QmUv#"T>MX"8oFUY3SQ#tUT򑩚]ڮwϞxbݶ9:ϦkG(bM{vvmU'ǻͮi{o%#@Q!vje\.jﳍ71y2C9 JDk5kP5NѰ7~ Em+Ҝt.܌~ի+>*8 &98<îݮݣYQV LJͦ-"O*h]c0NRJ)Uyvv>h!b,sl@ RR H9dDh S"Ef%ˠ@1 `BĖ$XᨨhkNzU5P&÷ި2xΆU8Cf(s]ؙ3Q+rC\TC$Q#vh}D$Njnh6L}t뺘;OȌPz 㓓'O/ƓQc/vm7*]}*)&UKJWW @W׹vuo`#JǏƊ7̿9Oft<;(ωgԴ`RڿeUWհjBrXκm\m_>ꃃ ^,vM{0LѤ!8ՠi/?{q;_]TxTVԲM cSAbD?` D4! Lk.7Mץp8x~Yr/wQ]I-D! I$jw{3?/.']6˭*L=xx{Ae|Y=,)pq$OEY$LTw1;SKQs?ӏ.^|mB;Ģ @ؘ,[@ H1Ȍ%&&DF*8(LjMəFbgD$JӪ=y?s4 8J2Q`rqqg;UYu vhf a@HP={$Lj`ȌkBC&$"l,G<$vb@0B /XSy3*"^V@vJ%$bL`6_4]@>: WEYER5^]o[.#_+_|Gֻl\ef11f6@LQ^/FaQD HvAdDzX׃J^z070|t:Mfvy9c?`TH֫ɗ/ۦz8Wm{4Kg3=,87Wɳ3M)i GY!yb`];]ן_\q;D1 Ϧ &%;3$|5_~Ϟ]LLsJL꒪g7=Caz}oS Qy-@D5ӔeMu1_JNO\fy>.*hd*Mo???9>OFzqy+x:3,ggͮ-b0`\T%d hL`DLL B(L$# 8(l;SxUʽ.]p,wc:ϸ?9$I2xOH$Ue@dU{xzv~x4 "[lb'*`2,}Qh$!!8 vȈyO&~K%BK LM .MAD5J M-'^!dU!;1 DR"P 9e@yyML ohH "([Ji4O>}o5|bD)1+ %C ޡNBT:,ح$X.>cL1 c bjτH0{t JZDvmUD"\pJiū㏿[b3+WK^G=~ߧbumOCU{"1'Df5@*uuiN]pXk41)F@I۶׷קwL'#8p:hX AQ h82LR#s b4qo{ z5BȬylfd & fb-"Y5'+&5|ewv|[F@hQm>޽/CDx]Wqj<'9z UƳ5ݪIzQ)K$qE8xP\< ʸt`PA>rf'9Z+L!lŵ\_=*U鴯wG?D%-5I*˒YH>fyz飯 z eCp9GEY QǾ|u;@\\\@hPۛٓ vᨚ0=L&0>% j"bB|HۻgԈ SU-ƴݴWW󮋗Gdzo!@U*T̹0w>O>yogЛ1e /<&̹5=Jf0 i~**|!O{xdЉٰ:MR'5M*Șl`哋WmQãaLmlAd4 P&{[\|Iw槀 tEGL =dc IDATGbrd], (e0Xr4W3ŮݢvJfB`xwTgbpL\bPVw:`FN_6.rg u]'9gU@2ץc46 Lmu+ѨfI n;3}܇p{u}sskj7 ^u3j6Ci=z?Җ~}'W YcP5$Iϯgl6 EPm.G3ff&yGj>>UeE.oB'Ã.gBlt}xuUTzeϣh2g?rӺGU%!1"g 3#I"jLHH:OuM6_>K xR9= `P!SR+pOF7DIL_,-W"fԕM}wFd2,CU !&21hT$j)_o"m755?Gs}b< 4u}L)e*]^^Gp D|_vWƦ=_~O'ǪERJ*\nq4̦cvRZ,6ͮ{𴨊h8T3Ecݶ5ӧ1{wﰧjfL(]w~~;Zmz2fHdU.kOBg3IBI../.o:tp45W//fOK_U!cv'b>nr˗j8 ,˃ Cv`5AޗզptCU 1!$U$ >dp,v}2<;;7EpӧÿYU#8α&Q4|B52#BPS#@H!%UQcUS6#"U#f$αwwM-#oW.Ap cvBR{QsLԶiZ_ZzLՆȪ+lPA` >0"deۭ͠MńĮi۲ )Fͺ+ zA>umM'חWh?5ۼѣǏ=zD׵WOG|=EfkP!GqLw}ann.oOFC4%b(&)igO/LpZnn/}Jm]_/.ϯCu=JΦRE3-B5S@rfsy٢%ZL]/}b7߾7!,fwyxZ4dIrpsܷ}Ӵ7_Ň)Ńég~aYU. RjǦmjw{s[NE:_o?ޣrh&fPBD$Uu+^\//m;od쯯o=s' fPH(QQs>3Y2 IPTM4/ !*$BpdH.Ul"\ 2AU% 18d$L4i>={v>Q.>Wǜ~I65mg`]4ǸZnaQE(T}U"#Z뫫 \ݼ~x߬zHg>SBzZPTpPn}/Ѷс"$J .5M/dDvGa2Gڶl6/|ZU+$^uUTKT0>I!ﷱPPD ēH)},-޹s Ȯo%O^oeRWv<d\jBQ j8uRkf7 ::{;|EUzPr˫h\!b'>[_RTAQ&TLR4~$4 惛z9~ѧO<+\+2s9 l:C3`EC5532=ZZJƈf@slRP0ޛ=y%g~ywFdIfK-65ք7)&4Gx& 9¾ ;iɞQ[Np 圓鋷mG]y*Xʓ!f} V5,V(2UYQĨI"T̬{vޣe@e=@b,vh( eIs59Ye["VkUPU7r2]Eh6r׾gGp2:㯻ͯ+NRz*LLϴzVh4U,{ֺl"Em'y+cV*.r;_rs랍VbJR]qK8DkִXeieyNѴ(WyE ] Z 8jH" "U@]G"TY؋;GJH.^Fq@oXmh8+^#zeVqQ*vHqg;_U-LO&6A}'Ͳ;ZMJ 6m$-kˊ\Go?w{ɭ25a DLjtpZ&KMbS޸{zNڈJ[L"A薌 u (N8w *tFHsSDtj u:繮j꺿gYd#d&DdUJ51h4009@ &`J1Zquϐp6[)RzM%Hi"¬6ڈ@# ?Ǫ Vd]?,_|ёH4uG"PۭdkMOg"n6$R1*J"׶u6F'GvT/~qwǓϦo|sD:+gAIP"@LHZF[MyS0Q1xjmHx_%~]F_+W7etzo+hYfggg"32HWU۶΅#A(8`"2u;|ݝR 'ժSQY PS&ueͳ{?^Qiıh$ŹЄQRȁHB.DU&'4_؉њH)en[+e^Uem%DIBP8ƷkhU:KuWy|RӴEqZ~X,~*YPP @;3 h@^Z(mfrkw<^[&I)#$EH)i[!REX,gyclCi8oU JcǛ?> )^'^A:eFo8"LLպO~?cG<<6*{E$*xw1r(K;Dxp  {{pz~dKo#0>;&VFANi@HBRf0=Ȏ'$U]8R2.8xBPJBsw Lb` P)R QYVkڷG!m({k yi| nn?q+#:WMCӏ<δo/~ܹ<$ViQ{CH!Tx|>)$NAMSW݃sۛme,,OpڐeI'uݬVWʼόQZDbDXE4YZ5a۴J"CD.x;+.?uis .(1icVJ1 YJ+@DRM= MDhcKRm·6IRe!,K'j<@aQKuS9 GGiQ$Z'qbG]xJIu'"P! tZgP4LFKpfϓ8Vib*K :^f ҒVEI+(˴gY\PDPpl,U֭_I:l}$e,4!0p< G )KS2?}Ĉ\$ -镫 g陶~ihU [֛[=c404M{`X=85[ y> Gc6ij,hVUuI`ɋ4K|^ee^El $ցRH >p1MxՍ,^jkwo[k%>֖:Ψ kMp`A6Z ,C"THHHNdw?[0Mք1lco?Iwv,qNR\2NR߾?sMkŋ[d'GC|EdcKv 45gyD{ ņF"EʁғX5x>/".4~qM m!x Px3Zi4Cp 噩WZĉcZb0Az0[7Lwv( [EDu!]Vvx||<&zo^ۦnkW7Hum|ƻ(YRisZQQg&MdLmaZO!p?|;o?5VLN|qqB$./%`Fe(M׺żD$s2Mge2/,EOHdM,(R(D}U}ΨǁLi[i\K$UĪjBպ ƃaG6e/{ z^EiO ,'ӹk]Y$!^Um֕;wDk:4UW?G脀DEeB7l%EQgm^Q__[y $@p BV9x`/iE.nlmDa'ԝ*tnf$R@ co4Mxso=~:_yDl賛7ʲ蕽~fD1WPE݊Mhx0Nk>w~{>_=ԥ|O2/"'6I&^y&BY_.VE;dOE1{D4]YQiAQ[v&p4)@ 6 պ wQ$ YZNNuT{`t_!|'a^rU@l`zk4z֋bZjcDijON$Mn]w<ϟߎ'6˒H(fq&EE>Be/+"2Dq$mH5'UOͦ>pr|4J{w "$x&EZR"LdDc Anz<5M{^xX' !v$pGbaBz/B0Z Z+$kO(-YDK$`5- rpo"}p@ƣM֞"r%??^zUD|Qg+VHJRky\E&q`GyYfeGMƶiz]߽{.f˓F?4 !jpP[8Ш0Kx''!:҈ޱ ]$1bBR7/\ڌȵ-nZ5 2UVrn&8<p 7@ھ>+^31=_ׁILb>׫^`u]۶mcM,5pww7ƌFl/Lذ@]ytu%IQJ`bWfE/Mqg,Q9 bVsj 0"@Z'I#olȷJkf7Ni@ !CQQhTݼ~c;’I$W$i⃉oZu6{OD`p|Gn_Jqڞ^zuIuIp^f8& -(ib4&{ƚx^Y"-Z:`ZY5 $^[5" )]k|6 +R6Dķ.,Kx2K2FVZ)T;B k%2yf]uny&Q@Zk;.t.⧟~;WX-" VGG'8Opo;t(„P)a\A!!` FI [KϿwY{>y$qb JxeG^)@moLYkIqP$| 16 "`|{"8+UpU@>AGԐWOguut|W+ժQJ($xx'qo_{FSM{E8Ԉ0xv@ߴJ$.y?雋Ŭ{EI)!Eq[F3ȋ]`c#D>w{/_Ƀo8e^`Y\,nHd4ACI%Ԯ>ZF]}*1~up|W8@n%$uM[7~6_)^uݞL'.H=8e,KH(jx0kW̼,Ӽ68JC𚴉 *% HVжmv'';ej;]Jc6(Rڒ":)q/84u U='G?Efk$IB PxnNU/c=sε_?E+b4"Q  u[ 1yz{z٤in?yzDZƉ.N&kEf~/ͳ ĉXD~G/η=Y/#D$ ̍!@=3߼qVw-8ƣ$K \ p:3m^,V>l u[eiK;E 1"yT, nh\MDY7N2^g#DE @;udGy^{{Ud-jIXaWRz>[$5ϟК4 Ii Lɷ-% l:3aZ̧(z{eX4@I @}JƠR֘w|ɻw`ۺ_?'$Z("fgEE>*ҐK?/WGGu3.WjZ֭wޝCǟx< ,I#li'ݽӣ)75δKzuZ5{{!@FM]4Ij4bQ_|l?TۆIe%A`x`.&/b=z2)BjQi$R ! bF6h"Ij^?3Z+Tq*P*4* H\⚆C P{Ƙ4Kl{wj2PQX֖0qpBDzUӾ0)Ɯ{SO_X#T,ݽ"!a[7fK L,c9 '8͓(բ?O\u&RYl'iuovkmd<s> AƣIG"Q"(Iu%.LLL[_Ի ѴϯYӸg{r^iB[,{˱Qպ=`o/,R]\CQyT[^faFR1KDA?-xJEB"MJkӽE#ia  ֋,ɊFñ8ёHDD0 G$ $dNCƚTUŵQ+38>_ MIDa"U[ )ݝwwY 3 şkMG!)BY<͊ZXgtƍ[MO'_}\3}8<%"X:jTWt9zW^67%Cd;jjOTdfk4U]yFY\|j2HeI݋c ̫Uu dY+{zUyEgik#R4qY]ZU6wTӸȭ%E CФPimvoUDv1.Kcb3ݭHmd;L""!EA2``CI/w7KEYo=?q޷q^dv%Ђ<6(!V՝;~8RuA;/ m]^.׭kSgt9@҇D_bJ]"nL[uU4n\ۦAX8! wnrxoo((6X$#fUyY>/͋tcEHu)"!pp޳4u"ǣ;իWez893m[_/EU`tq۴iI'Fk-!^,۶5u{^׆d/2˼̣} nB0l\,ɹ>ĉU(A - )""FD﹪`|΁p쳗,I9UQf" 0Ҿm|Jiҝ@)V̪_{{iټַ?``J访\xH.ND)"$\] '7߻Iht}c[~~[/ p:04DoWM݄H3lWݹ{^7^f_+:ZT!"x|N"/6"k(I\T_yF,'M<^f f4dz; "zK~ ;֚ݠ .!Gu.j{~''zTF BΡ)ؔch۶6tn"( ! DI YfxwT>?EJwIn~+D h0;jq-(qlOB&HdAdKEt A_ haQZ9~ʫg gu/;;Z:>B }O M\/,M42["n= iIa'~oc#˲Tk۱Nh$VmC]շΆ:M":MF$"Trf߶iwm^?(3GFnӺK8qTBE0 D`tT5hk4)mb$MQ,՚L=tvރ^?7Z)^7jD@TE]H8Jxoy>-fϘ_mhF$YE^yBD\z{Vgo8VXx8NJ(O\$5i-Mj2{(1CfW$1KZݻ7`fFy".(!tnu H!Hz}iGG㺪Yfz^4KMݻӽRΟ%Υ.:_DJ3 4@dD$,DtzrBDI/\ؙ /m[ՕV-}?ADuٱzޅ?q~M栏HQQkŁY윋G'+򫯾*ʫg_guj2>wA~GNEuO=/ LUK8O =nwbQ~okph!HZ7z;<8F)QXtgfFڶmGS"5M{,Ȫ,#Ħ̓x Z|SOH[M]W}I" 3pdAB8o|:8x@Xo}nVxqwHNrTOO 1" )byk]d-˯/r&guo>aoϵ13պkn k7^x~,JSrLiqM ><ŭ8,jमb|bgqRJm$tZ5hrsI;SQ 28{=v}}0$,K<;"'/_`d%,1DFD xFDpX*F4~n{pp$]~{7O$_ZMDAч_ADDT DrjGя~tuVgU?'ow|&uf✿{ PZ AD)zw{/p=&:w;Z w~s\>Z\ȗ+ҵ9|X GN:ybJIDATwaa>ql,XT:gGGtz+Ӧզ+*|.,Kw"-~LE"Ihm0ZB=7EQ".MoN&bބQelY7s !KA;S5,u*wM3[mHJAعf 31Y%Gw\ެo (+"dyS3ӣ-%6?ͻ;AJW][ƚgyMۀY8J~qʊŋs'OrR20!7W9:7xǜq…K~+<4xHE:] …sR1 F$xZGlQk{3**SɤK * #C,"fct=WH5[bKB ;q!͟Do'\#I&{HG}&'|ill"/:a+T>Wnil,sҤ#)3>9"r]%0bfI̒H:K6BuRI,iƒBH6~֭6Ff0zT*Y !.tPWw<@K2?|NۺgӛfhsbE}u˖g9aб(1V QF0+ɩʧ"zcLmޚ-LEuuvQd˩Tu'F'FRPQ Ud.// A#G* 6Uܱs3%)GHgڊjו+=ɮs=Y Gq(rrٲ$9-J#;4x\\^*pdS%Da;SKkkk b ^W^8viDFomn.tox-5Mӈu>7^(dzd zr"W̗XL#c95N!+D" J3޽ ni #G7L;D"bфa8|`Q]S12(߄8u}![K1B lI0(BtulOU ˇ1 %VRY.Z^#e"y^yokK+[b J6of%|P k.jZ#~<69/URxh\,c9fBʼngfsrccOUB;~"]%l~kN&ԨGD.)4l\U0B0bAʉ[c5-g&.Za˴n";8x|+!GSLB8cSDhq8+W$,2@[?vιs cRCW=8Է{ò(x,('PڄAd&.dJAtQf|C ={Ml˿#"Jn3VUUhl&j*XCcc:THJEcY%180roР£'\y^>{*; +dWZVIh?K?}ypds|>Rsc_\kz231L>_ˤ+R55 XsP @(xGYILֲ49R)Eta/UJsH$dOܶuQKK+ jkœ)/el&hoo~sʕW 5!%2qEE\*Xkٲ)"J=_8ΖJ)+xg[d&H&XěgϜ' N89D<>2r,Fo{8?RҪrX>rHVJ 9zbg!DiE\n}Y;vxMQrP 5]r5}8zlqIKֲ5QD(rإL,g04^ؾyW=uvvvw<ВޞTm]MhT֘ &'~Q[[̜=s!ԑ.u\F]]y >j+}q.1@[]s_ݹ}7SQzg_rG)KJ)ք!&W0.]$\M]eɬbkIHmmm+Vho'ի##l6-Lڒyl^}U ;x8$IYGS"zѥ߸~[?>xih ?;rq$I%bAZqKZG""cܹmԑR*B,[lժUW=[dP4ƼgƘu֕7'Tֲ1u)-->Nק[^s憍?{DBHrL@M$cl!l65h;xxSa*%=B-Zb+'\{Ϟ<ݥ aӳx'x} zzwH)6mQDBkv%&mYDZAh c-s0l̥su]!D[[5?{ jnKANΨoXu9/_Oؖak:2aF6Xk18I)/[~Ͱjxdχay;M +n=|jeC鈊d<B1`uhXGQ Kf,J ĩ3cR#|W7ضm[gwW"f(:?{j5΄L`կ+ !X,i=OUdP6BmRʑ7|d&qPkּپx#[fƜ>ukmA$wع c JaE޳sSa۳jU@XC)eg4LCXڳm#|H668ODR0ʴڊD;s<82,_'iז[x?+ |Y{ylr}{}L&0QJ74(C)5kq)*~q#ugGύ;nY kśn!AO?4-j[jժ|ޟ[׭_;rlqsft7%/̿C۷>1IAUm ]w !<]&Z[Rb箽vmik?//0u־Kyq/溂\W Z6DJQtdĪU+q)H+<"T,if{~l¹εXa7g{T*N$>d+b#"C:4K?}ۿfڵo\z1Gy|Wdk:5T(n}o˗+ :|Dq}c"mBWL#[0`Sxvtd(溮9[e*WxS͝kBu: T~^x衇hG}CmtΓ̹_OO~ꓟL95M## " "# 1S_s\ryG4=#""Ї~B AU~:O2~K?'~۶.,VWfPCKUEEUC1ƦiDGfb/2R}ќ>,RJ|hKO< O=}Q>/c??}Kݿ"_e"Svd/~cqQEU b5eHbj`G@`bb fHePՔ6# =쓀`f"*"`P0((1Ů?-}SBOܕgEVe6H >Cwd{?~t|8 uݭbyC0/QUU\TTTMkJY*~b &f``eSS03+ @d.p# dIV"DĬ*}70 Gj6 8Ns߽wOIo`~_l7C?,6!x@jZJ)8[3DLe, NgI&4r` *f:J1U+b"`f`fVw39Є QD BQE0}3=::::8d~C0U0vwgwwg5tiѯ.\?%'s||bC@PDqd4)O$>bTWNEMhh"jbb5r%eB !DjjH)LDDfl94NӶ5eRi6mwm,0Vˡvh!Ǿw|'$s7KΣ"S41F`0D01 L4o0΋2PD DRʹ) Ř)p D@ PO7*" L! ʹhJimCB2)vv45Q0j@"8suC߷M"j0=rs۾ß!Γ̹_KO=d͞'h`̥'cIk$!B`C{lh`fj9HD1PlbSWwB43NͶZQQA#@$@∁ $ɹH3*m7M1*b"f5&Rj$31EDPۜOwvvvwvFwyr1ӫo3>of^|5 ʹ)S]9efbf BZ10"0S# "b 3@DF0@E*5YJ2i;NI d+R(A@Bb$X @ p TmnX4mcE-<ҭ*I40rnnF?s|{]듓㧟~?ɜu3gvV; ӔrJ" V4r.R E0p$&$BBcmmc0F/ZK4/\_əժI b UMTf;) ( "2d]hm~d^O% /yDDTD T k֢O$3koyLN9O2~??sQ H9c9֗nGNRk`di3sā10;m  8d:aUZuܦYoc*&fZʼ F1{U})oav0m]ΟLEi]Y" {u_Of[Y,E̗eΓ̹B~2}ɋ }loo""iHm2RJN"M4]cS`ƶm.mb ̈pڞ\JVa "13L8\4OZRDv)"sS3@6Db H!0QYD|7Ŵn=v_]ͫFا_([o^{ͫ!ġMuw#}Ώ~?ܯ~N~cx!瞿Bx+5\D%8n[|NdZԳ*F RRJ)1))"1Dm }׷ 3!!תwZd`&jޢ\E0;P1WD` vS,Ӕ))1"a=#D`3zXf&܆~\,#͘0ġ_4m ` u[s;Ġ~http8SlJvgE<ɜ]cP=SD @UED4 7o)" `vmn/E~'$s]| D|!^xk{3{351Yȥ=H!`-`9R$%51SͥQ͊ E\-80fbL1VEM#0Cb1z)EhD*"R򔥶9@`&j)%"bX7Y@ 1o\/?òo 3+%O)Kc80HQQ46Pwyo.9DlblS:n/>uџ ?'~g_|E{=t굿SO_v;OͪO!!>SgC{VUoMNOd@jf`E4MeaX,r9 4iiN""2ƀDfb$d@dPUDDb@9-aĸn7TT P>Um"#Rv&[u7R(D AEK(*"yj"2CñiMT#0S W}o0tC4D,1i 0B}hV^DCHh&bq:qYq^)`7mnRooowI+<8n>/_/zRzͮkyR-W5k?_%_%>u'Ͽ<\yኙ^|I.4ͧ@R@[wL5gn6Әb1 "9JNN]ʔq04g@ؠu=X0 + #f }7rY'E/aoܼyc&F&DHYR)cm4 31["A V`Skۡol3n7C C1&m[>NH)f63 r x#dl} }/nwwcD"6yNZ7\y&nB? *tx&,vVr5 #6@$OXB]xͩH)nb@i3oJQd V!40^0x|߿oqCھ5~ŖEA qd_W>wRklK9ѹab!w_>?/j3~/}C쉋[O!ư\-%Q@`4 >CJ<~-#/߶fo] ]|Npp'{﮻7dGO= CeFªFEح3TSgPo AHfPHd>[aj[myv<ՁC3DdBB`BI"S Xm"0Є(uRm%Zda״Yx{Y/~9,iB)zl[h06 h:J^#" ET &4mɐHQےдm`DXqQ $zX̊$6MqN%oΞ9s8OהHΪ"V };:81ZB*"]pÀD5 lV0" L)dNSjۦma葀cp\h^YwK/Ό֛1RWj=f`{σYOES-O{}{G_amc!pC޻.|w1qd>?w?S?=s}Ev/_?w̙g>{钪o_'¦i˿|X-ٝ׋_{޼ 1.ԭ;үoX=o Txnf"Z'ÞZ ME*(@iB-w' "BF$fD4˥ح=+EDKLu-sU !f$f{|1CEE%c`$YӴҘA&PCENCu O7k! # 6MRB`%O㘳blb4c!SSH Q!̣ H@$IMS1Mbqfgwf꺶mdi*َӔ4\SJW;{%CǶaT_'r,"M C]S#c BlD޻mFI䥗^x"*iLp _g/Bw}7}zKO=ϟ9߳Xfn姯ٙgͅ05:溣JD `0bMdb SҒAJ@j0@5{9P )Im\\0QS8 sBsTfӜb3͹,oۜfMԶMtdn (*@m@U-R5^s)@ġ&lqŀC lpZ)M9(ԥ|a%[۶[:VlJӸ&M#!vmӘj`CvmӴfM))mq3NE ."&**s3m0,j%RJy"*ü`q-Eo;0mmϿ}x̴oo;‹}o+u2{=}ߗ,p~x/=s)5! e H`D"bNـj l=w?WEyſo~//.r|Fu]);~_x)K?wʫtnqwwCWJ\J EsD|Zu|C)" !ԙx++s9T&\q!Hc &0{D@L@sڡ IDAT1*P_k5Ǽhj*KQi %s `"#L#11aY7d}@1r?mvm;t]}K-Ȍ(&Tdh fRJHLTO ޣ؊-awYf]oG ;-``jiJ9%S뺶;D4s45;;L ¸VM&شü(StoNEu8]uS7>x ?x}}7| 9O,O=Է?\Uޜ #흙َ[)!ٳ\z/=s׽z1QwEb*MlNᐈb!ƺ: NK`Iu T@ ꜥ"M 1[9FD!H)-EUMun #Ct0Pۜ)7еm !4B "$,E1Cە"d&jiNSl8Q4mtIQafD`9ъiɚ405M TJ  YrI9n4MvC]DGǯkoOD!MN CZ]16Hi ,nqʂ!p+O?_ܽbX,>pw!KJIRf0mSۅt2U ~.$sͥ76O9WUbk^e "P 7@LtX""׫NgX#bVW&eiLum2#T<ɔruC7D "RTilN֛v["6&*XMzX%n1TРmb6qLjM8)5)2_)n'#Z-صMcCtp|rx|6g;mt0B04YVLa3N͔RΩ\6lttt\mw<vb9Ou1:WhiaD@HBFMU74ܼ4 cl̎OvoFJIHMĹoKs`:w+󖦪<ȇ?׮_{30Mˁ[L"Ӝp(trx4mǦnnܶwbn'S뻾~\_3I?|{{{ws}o6byקsukW]Z js{iZl(bhbMLDjhCD YKj!pĒ KwelbƵ2l/(";-~NJii;+E)ZSiZ  M 9IUe8Mz;0". TJ. ?ZQo!#05iRsz618I))Mz;nkWb`ivWc8Zon<<qRJҢ_vsNEs焄"s$HHP\JE]kȵLalKMlCN4r\=SUE买pEh Jjh8>qp g)q5;E)2S6!6!03SFIUr݋/")唈(490wʴL!2/85a<u&9""ѭT6/T|ۤ*YDR9'S1Bv͔r.].}@rNq ]Ӵ1IJ)|RedaZ G⦉m`QRS1 7SIJYsŰXMshC0\ʔ8McSf$u#If fZjkZ):4"&b89^A;;;8M8ۮ"̢!Ѣi"&d s DdPono_l6`*2F*Y()H)cl*U*(H.YUn?wZ6DfE?tZ6 *H):%0@@dcD4͢k"fD\oc׷ <ȇC!6 3Y2"~''z y=zpps܎rN_^j x1R"&OׅuFT lF":&,3U#ЄyƄ: F0\t@Nt*P;8Ni٦شm9 ξG+Es0lBlb,9hihU6MӶ4mlYJ.&iطmr.%##q蚶iӸǬ)"KUEAEsΒn9ȌM3ELѡH`ζwJd i ҔLc0B"0m[8 j6oTd8mlBC߅&(Lۭ"bӶ4H6&Ff>kfYJ-4xTQJyyooWsD\,H0ɩ/`zX9#jFMCa&$ScSC vwCvmȈk޾zN軦%s~N{f6Mzn nygg'@4K_տ>$GyOu=}^)q;^kZW&m[s{n' k9aJ)mG%oд 76uNamaezYJʢJC]@y^vOش]w^顝$ϫlн*lzfM4MNkSR.% 1`=ڮ[]4M8 b:J16Mh(*ŲAO3cM5hh&E@m5ŀ'fXJ >ͩ1 }/ڶ!XjcM t K]- HĴ1*0ջQ"31*,o|+mi9,jjvٮ4b5=P}gSͣoqn-d o4p_7j$!AιHR:}쬐e̓e5Ý" H.Pb$J$E@ QQ\JHISGȑA% (qb1|owak}+dK%*T^ݷQ0 CLBL 2mTnTe%>f)$!\4. c [$6;{x|:+I-ft:53wm? *_KK/k>_F'\RUՓxjs*4Mok֙3g~_/>+*f㑀*&DLcz Yl>8vaުBíul>DW6aKJ,!&g+ Bu*"d tZEbm߷m=seUZk8g> *ĤJQI8cxmh4Up,|T7lbv͢>1EH1J [Q $a%I* fӭ#`G4l !kFըU- ifgwk@8DDtA~۷ M WkgSU$*>t1E@]7֦EY_4aCYJҋ`V8k+c9˃5@%ɼvvޓ3pUETvBnM@m;fmsu][g3zw۶?c?~~8?3?я~z}d\_{;^,2s,a6-=xÍ7|nӟt/~엎*bHQ qTUaCl$ <Y2! ! DDIIYY>Y 0 ~EF̈M1Qu*aBB%FBf$1w!AJDeN/f"!CTct"J"ч(++)]-pDƲ$%$QI)*DaP!DId:Ö9Ƙ/ص}Yɨ]!Ɣh2sU֎8emCCڶiX'EQ{[4}CCB)|G*!@uABE"h}mAXGfhY>b&I(M;!F{kѸBv/2,1:D+RB4h۶z4NKIzOeӵBˉc'W.fEQd44Ĝ0 Z[ZSd0!%"dx1_STx<.*Fnkg׿My,ALvmvƚRmcӱaM~K$|G?G}Tn{DR~8g{/fВMk)矿[HIذe}PQ2kCA=k19gLC"*)%M}LJ1}HiI"1CNK1EUAH#&Eb(O$DRyקj<It1oڮEX[=}|9 m7mt &1֜{S')$A" XkDa`ui9w@Jr=qyD^b$hR1O1%Aѐ!h>.E I$jE5O')6/[[{;١zARThbGQARHI(ǏqJՕ=1C "EP2-*LTף\Hq:]ٹx~s9J:"J)!O?BXY[Iiywҋ=kAIJ=vlzl}mRl bagkF?m_+W9>J@d$\X1qkPESAPU׿ ǕDƳp$JJuھcUU6!VWWĻ>s:OO^_={o1k9s斲~|PU&Tum1ozM鏜̫?_󵔖YpYle˄HL QbL)߶}JZQ="f1bH(h] 3ړsXhP6U@BdY@DstY&cu)&H +rۮk{cy" $!#lN1SQ*J* `!^޺E1iw׾m{ d 狎^Sff]fDcpn{::g5q$%>>TuUե+lф܊!52(j4o [eQuSJ:뜵E5.޾h*6'(^t.q<2̕Nk/nm]Ds<:ⵗ0zUє\Q"Pab.FI1ca|9%eÇql%%AT-kk=(AuÏ?7\}hDc|6 Ά9kC_l8w0vgۻ[vʪF)jM*غDzŦ @FZ3t:E6ٰEcɞ8qbss'|%'2E眨M Ѩ(vwʺVDzROOJ=]Z[;>/)ʳ1ƮO>7p{{ٳo/|w?I9fRH9m}WX6(i,hL1C)%I:AF 23[sR%$4f`:30dG<]@5H^h.yƪ )IJ1d&JLa{{{26lhoN<潏! vR7z]b7l+C]X3LFTEA$5Ff?ѸvEaIIL@sk)dd?":犢*@4|vp7of)D)i)(qX[ۄd4ci}}u2\ J13,T -+ƘܚHR!µ]G̸̍fvc#$IbRɊ \pu_{47_X]NDv +k~hZɜ [Z[[U 7o*RA-˺DM; oDG@$1C)%bJ!5Mt:GO}~ 9??~ﮝ ȼ\ )Xcn}cow_w5=sM7qgG|GH&`B\eqPEI4bGɄY2.((âB1DCT!"V"fi "<>$v"b橋jqĈȄCd*aXgR)CI`02hb"Iiksk& 0GEPTDSTf(P1p~ueeTWsZHqt-QY/Bpp9WHCv ElŖBI}'Q4F 6Ȍ:HO} -m~E/_,P ϲĆ7`'"xօ-PWc|;.?fQrkR@Nʫ^LDcL@ 4m_ 1ƃ'Nk9Oa6Z&x2S-_SҾD"Ű6uKIhe:*8++6)*}C@U4YDp-ڦl8 5"c*k@AE%Q M۞p ~:3 ŭFffGLT$IҶm4MX,GuQL,"R%ssolZfd?A+q5'N8~f/~YÎ*O%i.=:  )` -}h>C}L)Z댳ְ)\5&^]%$¸\bV##799 )k |mpX/eL 0C4$!Q5Ӷ0<Uc 1i"ff@>J IEcBdkZ;z\Zk$i ΏcgmaK hyR5F6[$_@<4fS#2DHcS8PeÚ8 Κ횆bQ}20B`_}񪫮zͷo ޏu/}IR3b׽)EQA#DDrGxPJ, !QƔloHYݟmcɊp6W bJI[_8q)Bh^U‚B>m{Lve9t@a͸ruXF̔ elj{G ru]EiЈBHi{wW|k|MƤ9f>gi~>o(qdy{./UϯoTykkǟޅDPE]}׼ SND?|0IJ:GZ\!,(#Cc8w氄[<=$"8tfyvObC.w@ *({'h4 [CܯH,kC#Ƥ"yKZAU W*h}h6њ}ӵCQj[W1طAAD,܀2 yl1h2]Q @=&l^2ƚ\D *O 6l-Ld-$AМ gb>&`ͼHlXpo1^-oۯz八.BhfE '#>WVW+7\uŋB\D}L>! ds$9Hmۦ]]p±N((vMk 5nhf続yDb Y#)"iSB}#2Zks"8SU؜jXaO΁e]We)@6'N"䤡A 9HrL|ȷb]5M㽯Fy_*t7~׾uń;=ŋ[y1'r7t-oo{̭gޘwy7{;e9tˈ-Х Xjzp[yyu#۰v.QDIWB 1̆XW8c<|/+D|mQU%k8B )Yf)$r((I5+bϛ:)*"4D8_%dbCHAs%&ĆXɊq.YLIRhvw$kd.ʲ#]7_S+O{qg|s/nz t2.\$|^M遯?U\{YL2]3%I!WVV@"cL$D_ϛ92TuIчO?:5%l>#B笵L}/ZU7Ew]]{>A#fJlƬ5uaKc!c #g|"rd1 bpݏrEaّrٻן8~2OU 6y]d$Mmdv,4NuI$!}?/ '~…h1=g_>KLrq뭷~?w3;O8O}nw{(a+ńf^UJa!UF)g 6dTAGf7͂y' ҠD@REGaD$XBw!!lTtUQ 22Sv٬mzTebY(CT)dY!izg}ד!Q)D IAQ6N_]̖<:\& KVDre? qs1)<ϭ1餪|!* e_ {b|ĎD9 >4mWѤFEԡNg$2![JR**IcHZ8 #}ȨQ= 03vK2J3h}>kf@69};S'ӠI)h,UE]umwî~ݍתŝE0<1<o~t:8ye=ēynӷ1HԞTU |ִG kc'e[:R K ɷ-:Xqث%}۪*JQt}ucUQ$\gMBh.X h'EӶmEhTZ"A@kq@)D%f2\vTUa p5e~jÉ igghuTEQV.\Wc` J~@ȔtԴ!bfabPյղ.SL>xPw}%J<أo>c]x.\ΞZK)-o;zg>?Ͼ|?Y2fE2啾J P1':d)8+DKBGeAG!ńDgnd]i¥&և>&H*8lqظ(7a/|7aѴ>3O?HXVՉ'RN-ӨZ#c\J $ɐ%"}Q9v&|Pts㪚T#k8P|+ӕz睈fszT&P>v}! 4M4zZe)%6cGj\׵3,37|HcRI'vg}kFa (HdCH^F( A(QB <Tﺮ( P~.r0s.ƘSw^g*ٟy䑇_7Xw1F"]yUQQo'z#>|=~t?cy4>a pԇ ۬a{qDdHyW$Zr`&#ܠdQb]2T( @Ő$)އ8k\YEQ.gCde6mPRΰFc2GIC G D\,-}yO2$2d  3MI!&UE$}%|I!}H(}[*<^6m4M@EN: "7M)ovw?qY(^胨("μ7W]ykz;)uM]ֹS19.UTU%TnT-)'$&:yR>2DB j" }dxR$"auutVVW+ ywۦ15}}D%1+$!!57:dgxR A50Rmw}_였l~8ƈ)R?U-3OCm:|n][[&>={]ړoԅ/ǘR1E6F$>m갧RG>'oss駞-[?R|ѴmΊ<ݫ]p_ɣdy jF xPKdL8xJnQ4ؓau%B!xb9-/~~,f-LQ$c{T$ngD!1QUeYx߷|>0$ 1CE+ rCϚA& Pamv#A0#"bbf.e6) v]o؄ 1N6es][Se鮽$^zϻ듟ދ)%A (8g\&ӫ v݌=+_}E90YayV}kaC8XΨpŘdw7vN޸h<"(xFU6τE $E.1%v֦ !#`E,)l  0kD4egl6[0+1 +Ey1x''slcGhʶL"D7۶m5EQo^,bu}B w]wu}R͸T&xz=ck:{?ڙ/yU?|LJo=_~׼Q EaagrpX/`l%K A5g((pJ AKBK$d0F? g g_91l| UL۴CHR46\u96H}Q,Ө.ǣǘaC-@U}. 1OW&+WXCU4,$"v 9,KkdfKJd7@ͤ[Ttj41( Th6k@0ożUѯ~ɢw?y$*ǐ TRT$4 Y5+z5n?g_|(ʶݷրB3_ǣIU".'K6 ¢kЕu!*U]OW&12FBVO%6fĉY{ǎe\1+hـ"f *IlRK_2FcTF #H.D1hR C':S,O=phWPDl l'/u]UU9_ۦ-LL ⅋t ss[ųg/ͳYc "Nx l8i"svuUF槯Rdw1E$NӢ}t.y/ Ζ"U$ Ch~h9Gxd]17,`qȯkI1 yHyt *0b`&xRqF߫dC @3oon-B_ ]o/nn/_:]Ec$Qa"Dg. 4Ϛ)\>3uQ_41n?rv'w:>>{}]8g>={oij$U]Ѻ,Fp$OڥdIUҫW @CL? Ye3$hE #``,#+0O$m"d$vM۶+x:BT5`6gY]B0.;R㘍IOI* -@3BU1jOǓ8j6m^z\;g 1c۴͢!E#DڪBH #Ax+rE-3>恵7\{Ѩʀ fFH}ϙgc{JW0h$Æ Pf:m?k鸪^f[.?}׽p(rCĶiFd~t7:B"R"ZWbQ`ghRޓxed}W )jR m!6Jed!.&U@zb}$!")ƔǶBČߍ1$y, 䙘KjBԤ"HCl [uPa@ҍcO\76yy-BfJ^SD$h\W|>oְR,?ng?s+;;.KOpxO~.\8_n:lmn=c;ϞUO?ݝ}xM7?[=Ðzsˉpf u}9toH ##."1OꏡuTEEz.z+F T AKZ6RҺ.QY4nU Un4EIsUܰ ؃ (RPRGd$ƭO'zBZ#z N4G$5!C ط=m۶e*8-zj"*ԩ!%黆 oblACUY!*e@l]e3ω+?}'=~jk.饝ɸ4b؈hu J6i a 4u}}^%ʲkr9 ܦjISKXˇ e{ObR0 qwo: cS>13a&Ɍ h<9lcȅeDIŐD x4jm/DEQllS=qy:$BbޏHL2hb9y83L5ϝښL'kkkL|ըnw{Jv /?'~see:vx͋i0*~/\+.t%%U (vvcUF,T` ZX:y![ 9t@5DيR2;dФpFW@`숲>d CԐ"C8iSM оTB]Ccv[gae0zRuBBH1b15Z mv+ֲ 0}4J߈D5Z}ZW)HwٌC ]~䩍utz8{}"=y|zRa D!}H:f>> s/mF;/^J91RQއ]WUe&ZP,)D.VEɖ *d-NݢgLlmIc*EaUPHׇ(I&jBbLYKr*eCFf8YRb茭:b)ŠEY/@"qJQcmв,͝·.¥bFa7[j0NRc1%Hɂcp䉲(y晃bz}٥JGzCU~iج[nwqg~W<;_+/kؑ"8hLNR 4fC$a_%Y/{'ңhes?yu~$2!!.;4ʰ$]Z!~Q5O\D5xT-!.OE@t\˒RֲΑ!P)FzRk>c1G+N[ƭNVڮyyؕkآm{ %C61pE]}HeYYgsk5f~2RI>Ģ0'Og/>u0L^f߷?.v#c(yhEI^:[ζa\i>|nnesh !"b6Y6|dOD#9$8U]cL@ECHI%TUD>dAV-FAtDG:4HE s@ UH$!Em[kmYym?W&QU1 S F@#1ޛ[z]}k7}oOKIGNY(?HVUfDrٮȲ$[)$8I Hp@yn{tJ"s9=kYk3*Pi5Ԥq}"o}./+oI 1Z,ː4R@J/--EQ$I?]t(|eJvkK_z ׯ]=t`Dg7ugcǎvo"BMDB#PAgNb#e%fQ ;FaԤz/gIvy<9+uP$۱pu3./|ʕ#QAY,E^70LH &LH)I+4  g{M/MK/O/DEQHw__*n_s{><EI/%߻๗Oyd~]쒦VMcSHk seS)MmemcőFs) zMy5/4q" `8(0 Q!;o< oէJn# J0Ƴ Ꙑ @q",u "8J3p`@NȲKm: o94uγs]__GD4wyD$ Bֱ{&KPA$ϟcLQٗ_~>pܮd5ƛ 15'N~ m"y /gp:>'_?'糙Rȡ/ tn!vKE3'Bha( nH 5- CE;Q`\t!0X>ͶFLEFY/"#] H+mͣ&a bZjG 88sE{,InZ Dv6dzqnRr;//g*U5QăxǻyAa4VMʪ, yfἡEh4[g+6yQ2ի׮^g}};ؘ-,%I弄~zᕇ3I7ȋޠ7 HQd`bǞuE^Ye4c@7oWە-^3|aks#L?x7E3|O}O;_޿O/7 Ŀ|kw 6%.8A'Gf7fE ':@O=hҭ/9T[ Z U˲(ʴY[YUX UDD*ЋHQ(̨7 8F8 ­84ir5\5ü&RjI-A6ޜ9 h*afu0h4õ+7ΞeȨV>!p4Y(%lo i*ٯWu jX_Z=|z{ Y+qwǎ]B%+O"=̷w/M{잻yWUf  o`GÁISh&Ѩκ)4VJtӆ{˒W^7c @"7 S"zBGS:x`G$/olnW3R?η{,y66*!,O<췞Op⥧?_־JzlU5]0vW("oMut6@SZ,9ey;) !6 | vV 盭$B♝Jc}E~$Ҫ=# "Dr֨z^0k. '{i@agFR iCҭQ!)0b4@,QagI" Tul>w'IfṲ< C85@(,[[[֖0{}U]=;;W7/^v]7av}<л(l^}Of[M!쮻a݉'ȈI0yQe9\dI(YD$4Kwv788sbsk50!AvCc(Zt1@" wW=≳tk]1+zƭ @ ;/$PBl&&uUVXdkg\[Z$&"`@4Sak?}iii|7F~OJk;|ѓ1Ji,{ī /t%{嗾lnn =EWOO fǏkgNK/<Uql'D@8؆VBZ8˴t6 ZUCt&2, qbHҤRfQ@$dAP; 3 \I!"s&"cTan@tӻ͞LR88"}R&VAK$IәR*#T(2w}pI2FDznaٕyUqtaocg>saQ|Lv^{{<]ǟxλX'?ݝ<|ֻ8|h-I"9t:/zkg3FQdF$IGal4^,8,^/"f߼\U;kT "WMZwl*=[&nln7֭[=wō]i&l2 I+""!2C@cFVVѐ2Zjvӭ]6J)J+Ʉ΃ FLc#"uK? 02TK,zi@PS }X+$''o_Jꫯ$i? /.KΞfm3_Rag?~?>~& j ұw&-(ރQ@Zrk`ҢNHkz b_P0)bxí-Lb;f6Rױ/:N*ÐP1[ER0<œ(صZ#0z &0(l$s@b9Mh ]ss0 {V4/,Ib1&M{CDg",-Oꢼvuȍ2,{Os[CzG c ":,b}¥;עȈ8a!s$;pn,=4 EQ:X?T7l61QZw }@._|*K0z c !IĞ릹r}͍m,I@$HM4icbc(2 #]5;A@k@ "׆m""mzYAYW{ͭq]7Qu6@$\%(Y['J"$Apf }TP2NgϜuyanj; Z)pS7??vUg}߼wϿXU֚Y<{aNSO?G{q{wto޻ αPˈ!$EI' #=`$ \n/$h ;n+B i* IDATȂľChzʕRo7>ĝX*+]yuc@a[D B(@RF YxEa&Z+AA`E@@+R$}eD+k=;4e5%qfY% ѳgQei:^!+MIGJ?ݟTqDjeuy6vwY isdV Qh>$EUKبK}><'D:DTOrvӬ8%Qd)I.T;\ڱؼ~'˛C?f\VVxE9ǃ^>+"y^eQ$i*h}x6qM][vs2#k~}m}:&cu7x?~3?[O5UpܹO=˫yvl`-`AvhTB DUEG@BX|0@(]K.X0qD@omwO>"qH@ R 0pG[A"Pj}E {`"hb<{iAUaݫXWk\UIFC8(֩ηXfVjzkZsQG^MU#A(:ƙWxBu]QF86TrDzoO{$IPnysW`_O.-?4IvgqBf ?4?xp7_ۚϊQ>ΦEYV"*Jk-;&j{iNkco]Qxw,gNF::tъ 1'\%y6/_z WVWGwyqڍJiL!B΅i7&IFDb,ZEe_Td:'VJ#8߆(#cٷЇt :wb *Ԡwwwϟ= Mj|2 o8doOywޏ;;O>rțoy}Guu~'8sw;|ӟg>/|嵗>ڣ,5oF#g^@>M3tm\QL+R Gx   W@|-va@B |^DHpo*gҤH˭j>38E),ڋg_Ue$"E@}HZWWUI.8bg=7ls {lwΛHM-#@UT4fFkMU d@9@h[Սmʲij qI=. ?r~ӗVk~:O9쏢hX(i1\f!uKFGM7]|E1YBdtك\UU:k1Vj4\Z[];x4I&{W]VW(^߷q, yA8ccWW6*2FEZaD 8Hq%NXHy('Xb<,^kſQ2EYUIZiT81l2,_r`ݳg6vDċW,<O6vvv$t.$o۞䩓gsg>,~X~W7_Sԧԩӧ?'䉓oć^ ke cO kv|,Ƒ]q Px G'vNdA]%L,f0dwBdFb|IVtH(#T%+ JxAЀ'pvHUnukF  hcBJf/*Bĉ!"9k*NްE&eUjL%qtOE♈RǦxAo)sa+$r߼qy,뱈xώ~?.,R}\QleUy=ޛ4bdV~wy􎃇Fх <`s™73aQ<{pyO i&(2&6J+  6=D^SR C7(y;rBZN:H#~QBA ɱ!X uK>Fp'8|w}f0@ ,<Ϸ6 o7doJvO:?zG>{W7v^Akko'?/?s4o.V -mKXuπޗe! mE(gE@VՖ݋eaȴ} i({&pGZkҊ4KYʐRU yi(҈jhb6UOJ5E^)ڏ~^/K76NWɡM 4S~ƤߏGK"or LkX ڸu򍲪~4M0?xh2||[ϟ/]mߓy7.^=XM$=׷fyYT@i}4H}lVT̋*45Q^&yYycem"QJ û¥ E;/-WAt`E-=GqdF"'̡u4`R F"Ԥ8$ Md|UEJiEX_;R9`c\$="a*Q@X4~w뺪ҋ8y^8wH5x0K36dB4;@,{F(2"Ց&{5%ml>, ZaCJҨI0d\; )oR: 3M BbÞ6 ҝw?x%|E++KJ)twdT_O(|^>|W__Cwso/Nqy<}LL&^YVyHJQJJ+EhbZiӢ͋5HJI%iUWֺ,Չݰ娉٦kБ1A"T s‹<" ,QDӬ8(6dx[[o&E@%j][WMаW5*L3 DJH'Ql^ئqHHe9̬uq!,X=86 ٹrd2QJ)S~g~. +>Sj:NⲨX;eDeiyx]WdeuE+w}._cnY n 0⠞M-bZ8"7$bynCxtY- ulzA li,$R"R9 .$-<i>+n8HKSct^=Ji$~ D4M3͕R+iwsؐifs·͍R`;VUD҄Z3c̾UEh ^+Y5!.-/ =URṂ""B3;Ns"t)ED;3 ql:W<8h$ieln;?MR',H</}ޑﲬ_y4(>x>576vy [{8KL[mlM&)/6EcKX;w(@CDC輝ggm]UQD:!x{#!4u-A@PZ)DeY!MgIY&6M{rA{yHHoǩ G7]5FmٳUQy5VF~'l戝se^U%:/<F@vn_:~²7;d2 ܳsM'NLǓ+D@tkcnuu塇~6F3Wv*c>@e6U$V ޴qry^ԯv I8(,1_ȉ]FknM왝φiEDځadCH@.xd!E/C8['8uxmm X"+zR+"BdaBcm({ncZ{MU[E R 2=vHM9g%.za^Ba;;7n"pi"TF"$ ]H`~QП砷x7"D$(24I$ZPegqrέLl-8UUS|zgO_qUS7N- =oxpvp z(77ƥ QP:_{!'u &IL^e4 ħEHw\'qFQ;VElb(`B2BBABIGJiZeQ؀)$^ނ\]`تTt"!$zggM8҈XʼkoIѶrЁsnƠ峵zVIOv$I??ޣq'? _OP[ھ}q8v۬79P"v\tAb,Cݺ2" ZR8܌n}m]\ope+BE k$f޺ld$< - @a} .YAA(呻. `>;wn2Qd6cǿܮ"?=7N>G?J;# '?Ǐ/<_wg~ |Bx3# ^ia i|g\hBnˤ-B^b jI8$4@"8㞡8xP([TSE 7dAܡ)"lSpqWkQAMSUHdZ " 5̞RD€I(ಬX6v<:q"hZ,)xO2g3xfd†5 jh60 @"W.J⺘ gd:zjڡ"ܒ',~W/~ fǏ_}{×>S/o|{lm<Z) ,ЖuÅi-EJM. wm֖p7-Va!W @A#|;*sAo!*DBA`R@[=<"gtAXƦt̖=iÝ)C`O=f$PD:JietbC&Ua/\|:d44a,2ϋ"G׏$D6Q;(RYZa@ZѬnx]Toj ;iloS䳼Kj.^`-".UU1i87U IDAT{%iB0\SUCWϞ>V{@a,fE1/Z+bz!o֊lUb3$I ,+8DXmm]IbXljgHav  G-(4td\`V֜ "s[mi%Wxo{('xo?N=|kg~駟a5Gݬ474ZrI3"lq|kg&aZep纅 %R32V-m|Q&Qd Dw*H Y$!X#rFnXJZvVHX+!,:gY ]Z%:9Hk0J 켭'M9/8޿"n YYSEU4' NX!@vݛiQ:g ZDzcc/R(H|C5erN><; sJQduD,m3cF-  3BTu\% eJR:UYTUՔU%,Q @SYK4Fkc6"]7|:MgMcٲa!#lɌGĬqdLYV;[eQŜabB&lwN@&u#[آfU q2."{EdxmT>H(RzTi}]YY{W~ß9zZE-d, ʊѸYUھx"18G IU7664~ޚ9s-JU6pطΑºTr&ϖ]j~tWRJHdl،xb`"8#=0xd+p9XP!KUUu4uky&GQ|yo<~kA\I $!bf6.i6֑1 TS@kDm:fk0dm7w[PXG?1"U"E"͊|!*~?=@s*)u\^/+6^{뵏>x|o$mUxU~?U_NSp?/eob{ソg%/Pq$G>nSRdTd,KTÃ2?_]kPƎZIȹԻTƔJTB~D46B%UV/>T$J2|b-akyC1lٚii}xb1Cn>xvvFd@c(*n1|0oRJAkC ^͹csTI A RGXS)@1b/1Rf@k@Ei!n!~^9k\H}~?_7~oa"(?VT'9v<ۜ|ukgÀU״ԏaJ1&Aڽ+gBKEC/^}Y,1h)bQ| j*+*uŔU')863LR`4mCpulYR!$u:k -,? J9J{X%R۶odhii*%$3[׃Cnz4/|_WUk/ܟ3rKW NwL)GeU?e@>6cUnZT f3~fZ*(jhc8)D@(lLu5z5PKKιR7:h#sI*n=1n uBR*ɾ,ʪQ:Kd!beB!@~j@FcЃdz\/>FkmQ|Bw>8ޤSDzU~T>ܟ~W~;sp 0swA%h1?=<y4w[/1Txkm"S #0~ĨEHAf+zTikuI49RI/P؜a۶S055:u-mwan-m68a?DiJJ)vls1#-36~r,C?z9sN*ZBDþ0zu}yvz2"G/@$$R9E2Zw[,q)DL5 c!?>mE1[ _~u/?}>zt 2pv.7-'.FPɱ!*lEJL0b -ܽ}7w7'O{ubh}@aec+kSr;KbBT Aʒ WR'XstӛwoS5ه7Bݸ=JT,"DʈK>.G $RDa'Xbj3,)9E~{&(|/jW;J9vHj)#;X_U_oN8 %}?U" Oj@> և֦ 5+3E-"Wu嬭P@P^+5 j@ F/XsBsxfoz}2W5̥Rr9!ar.1Ɏ}zt}2O~o~^/7'owmwvy;STlxp.(CNfӖR0X@$- *T)HD͢ٳ/>~7~;ۻxgrYksaӘq85nUӥr[t1R(#E Zt6~kl "2' U$bJE5$"2-$Hmw5~b*EHIJӓe~ ],g/_7B1vWOJ_W)b쀪HE'Ň/7d 5HS\{ģGfE-CZ漓s-J @ aVkk 3#FX 95}z Xh:R7ȈB@GI *l2[k3dԘw8Y,2T)~?i!f9ǔsʒSF""X-))u8ARr9Sݞ<:"=gQ! "3Y!r6ܧfӈ4EUR2V4i&X,:v~|\ӧyo|fC0Fr>TBiTGؼce$SW48U睱gȜYDhD@F}XlejBm" *|v)L77Xut Zd&jE>Y;w^۵{)2i-ks)M)48`Y,a +or|i m֫w>FX2ъ&2lak{c4.V~?vR212E!B)lR?*תd)I\_H)k}Oi7 ybqz|i-֝_B! mNRilU sM9(9]B)#8w7eS_#܋ORd2H 1\-sJlRJ z2[4F2@ݢkAu'D 1[wMa Hl5"19;WujYٶ\+fmwxBÔRe^CgMs޹JJKĔRJg'g:~^Q^dk\Qu1D`5I1烢ǭY + TDzW0KDO#X1JD!1dὕvެ23tUr}) P=WWZY!aKN!(X""hiJΩdXfc)b)O4XR6sNf´hMcK%E"v"ON;01 %Ī!5aFBD@~)Ej  1dy"88hG'g+9}7_x}'77g7G:fnSOϖHKr 0XDcʥC `dLrL SMN aO}~_}:DF 4@AMu0- c("k=q1TdADRQցKɼ C "RJeŔa4}ѢlcS)(~|]RI)=}v3 ;oɕ 7Mbw>xqu995EK|k_o}sXW=gگߏ7ηn޸|iW p<"fl1nzcAj\Ἀv͂ 5RgB.e'89_?RGEZeԿ>V #1SP-P9ETcʩRR)DL6%8B*lq15b1 EqQ@| 3c'8/m=|4 eŘʋrr/~IUs`W5{UE~vK$,4KcxrMT/dZ։$::9>AFkwKbӴfM?'NY,7thw5Z1DC$""Ybg&sS ْZ6Ė>Ϟ}XJivhכ%[LCP-79gs)U))\v799;!v&asӶk+ 7ZDA+sF6d͚<`! Dv%q0BwymZ)U[wyvv漛BqO!1.od -"WϺW|o6tz98")OF23-ԧZ jXcvrjAy zaNgKjXCZjbETh 4SV">bbJ)"q080bLR@ِsF밓xs Se]__R4, 8ĜCϞ?)O[ByUU-UTKɉ5~>)gGUHERai!sӹo\Xnќ_Xz0+)][ 1LRJbEcBL7ֺF%!@\QW-&U$LBzY'}?msVc]9DDhK.)%)Ɣc @͖{6Ƽ9tVUk7V+Y5#2UֳbШƗVv>ce˔c9RQ}L$"9v9EUO7מׯwz~zrqCH1>4d:a Q%ZX\5؃-0I =bVABx"b`M$gfGwUUjDHȄ3bAIkth=Jmۭ֫nյ`s9\H)Ƙ~0a }?MSH)K.dBDT)8@ě* ~6YxPRdPv%KO?+9/|"rT얏t}@)l~?>!b(">E{SbU`M4NY,nzr P`:;e-$(l$ٺuvY-!|LHYZ/v! z޺f;|뷿-XÆc,9fBRzКd2L dMm4P**YZ~7_SqduAu!d0BZ-̤)?zX7m]RJXfPM1K)@;T̳ SJRJJYe$ow!H)0*SK 7_m躶i:yq DEs~vX-ڶY}(L#[^]^zֽdg%s+g*<+enP+JWvUp՜L=ҁADUq !n6=T?j" @+ ߏ /!!گ,)kRu:B/P)]cj+jNF$`kղ C8]]eMYhVrq$7|F"\ U"uV>UL5-DPcqD f}n[W'cu~}]a)ykܓ7)ouf[vƚi04LD$U`Ƥ8_Ts.Dt-r.@'EJ.!6s_@tqqa?Ox o{A@ecy瑈R*TDNOKRܼK!]xk@u9L@\RK1*5Cg†pmuBH)r)zlvRLbu]xg I9'l=64S%ן PQ 30wܫb}>;j%sxDɈGQRO#2*X_! "1Z+3 =Om**(Rz*F$:4Z6\ -%K)H.Tk\5θƵmvZ//N"R-SC?oMÆ5u[D6UFg"ny~Se!/"RDDoO6a $Br]WWOٚ"lj'bK)0v[,or ƘXbvk9\J=K.EdH!c1qR"E PDU)< HQUTcicSjY%~3|cURr)EYbZ4%*_+ :7j6fW15ec5\㤏)\-B4Xs ;bsϡQD@=sڌZ%gIS2*"1Ykoew^5!h>caT0>LHzgѶ,!UDQ 0 }{wS>L!Mq Ӥ(_;?;޷>!n` ֥<Fjqwqi T4Q!,@]LMYc2")jQnw[Us.RrN)W:n֫/|w|ʡ?` 0a8[80ɰ;^S:u g "6y~UfJd k0ѱ> "*v]MJ\ a)Ω_ P1*P %gQf#"QձXJ`v "2~Zk'4EgB\{y@Y+!1{2hf&ZvF"qJٺ10!s!!4i֋9H=4MhL߇m-cwY8kP井1PT 9R}"s-u]̦䒓V˓sw7kLΥs gu@f㜟Rb"g0|n5JNZk45jL$CUagf`eeQRR)UX #*g Ykۻ<*8k ø??l +Iգka{w9-S ,"9Kuu+9R5O !+>toۜJ))9'I1KQ8l|f[o{ޯӃsV[ٸ1zG0CHԜt$0NS~l]皪ϒJ4_?o|Avj7zTh[,;f1G$ufH(ED4bi۶ q@UQllwݮc~"E\RLx^z٭[@axrv S,*wos΋8mm*2iߏc@6~{Ο^8gsY4EbDݯk?՟}{U>k+>?dƠJ mL>Y @_VGE]c0_&1@8HZl̼ i d%bMy8hW8: ]S:mZdH 3UdRqXJD3֩@!4v9.ZsHɢk ` 8 20dˋHZAX}`) 1C鋔x>I"l)fowcL5䛦9ttiY!I$q"uΫ!hzo۶)csac@->2j)Kzo-笢DR)5$wvX !NP˜K,͊}rb\%1` ~{ݶ! b=18"n[lG VI6mیx{{WsrJi9g)HjZ]kZf2\J&zm6R٬=8?qM3Qywnhqp8(q6B9i)bSK?W+ʿO?Q{x"!"5l*Q#4Uk*TU턆arڜۢ$JUWq#B@"IDfY_B@;(BJ,l4t{ӋHZ$2 )hK8JNu,Zq`Ȱ!M))mmcƘR8?9>\VaCϖ>m`-?~r}q~*"`tJ"DZcvZ*|C:cL)Ӕ} 2e۵~Qkmxr,siͭo["8쇋զ;=;kƶ]z1$##8b,u&u@\r)l{#Y6S⪕GPֺIa{Uw4\kCPYgo>jX-K\b~-Wn@aR x){ؘjl˶0Nc? ֹ4MӔR)HSA4s'}ra TR>@Ы󋋳}۽zCoM~ࣧz4>\JѬ,ڶ]Ӵ?'}z?ͿTRK6CsEDE1VS*8vyB4 3LTC)ɪU;+зHՈ$@ 8m_{գy9"J@o!"|Qkz̋.d./;!1l]{zZmbA)d[4m,!mNOq&NcHmb4dT8 ak E\2THȢ"%Y㈌-EU\S3ZDk -K YkؐBc){u}1?LU׶.x1ElXs@r*PYE5L\*&< sgvK9RR*):J8;?==10@SG)q }?j8l1bu]#%+3αcڦka߻[T-@}=W٫JYuFWbsyspuX`fOTTq{V5Ulꆍf2`)%M5!,jqqPxlDhVaU1%,]Q ɧ"wl۶ӵg1&F78?%XW 3sJe'zkmLi쇔Sl۵{fCO0=ztu6J~/%81ĻX~G\U:%\0OdTڗSOUDQp̬ojI<~rX-3c!gx$cmkxtX6h["\J&_~ڣS) ,E.*YЂ"RȵDA {dPZ CYHd3Ɲl6Y5#*h'EJmfh1!JrZXr.n>w~7]&rsmlcA DEu{Z1ϞXݢ澦c9RM+#ړ7_)gD,ۻ[Xt Baݫv0 )N:䛆djs:3L?&-%w0ytbvfQ;($&"U*Hpt}1k y;!28gՌ$Ej }Z.E|ۜ_sb C7whPk. rep((֧yz!+ArQf x,L`?$!doyj[rT ca8a&:B}81Zx1ǏYR)xb)m׏_> !g VL?@ ѵ;Rc\ cɕ,)'4HdArݱ$LqTrJ8aɻ_x׶UG|a{iX4s1Rw?:̜b)QE@CF eb))"l@!Ak|5&j.Q7-20y7w']5C? HO}haRiq *ыq vѯf񾳪ti4N90Ŝ #We*Q) 90l9؈PDbL1%) ld}uvZk~o/}w>|Bߍo}/]7l+cMa 1BӓM /O jq֜"=Yo KPDbJl_/z> ?w; Ud(jQsUs1ެUKޛؖdi~k0=ɏw{###3+hU]*!EhBG@zj!JԈjRsVfFpδ'3[kwDǏek}<ۏ]5@*w\T99'W>)/O  c~= fo#pn#MLri׮8Cׇ=iwpSqr8؝4MӴ!T$YWO=qa"( u={u*}0Y@3:0|Rä%3c$_>],ڪ1q,Ë.o.%ȘRM15Gjqq~H1dDĎShޢ 3#3:G'VY@@4"#/|09(BɄH2f8NT׫}_Pp4}o[!bUWbeÕźcoښ=0'ŲFY$3f `?Ff4c~OiqJt޾Dr똧a{&@L1VuJE #aDmSEcoOo>?>Tz\G* yO5.ivэ6章Ic#Jfq "r0Q3D9/ol63UD"(UE~JS@r}ߛbXV;qI&g0Nq:ŜSyw:7W,R٪ZUU,Y$,sˌPUoz fӔLDPB@hE%gOoB]y8}*9W/^V GnRQdb):."0LSi~}ESʑɡBrqh%'CS7D%mǬ E29MjvӿC800`5` M Tr${vݗs~O~̔n#4LVL(jy)Ū1R!2qbN2 z\_\nopVEY~M=bzɞ]ZlE\Q%朻n} B޻i#1Ud&sXۯ_}C%֟?yM \d491?*3FzWxF3vκC Dr`dr$"fGz^rYl Sؑsek`v\KEU5FĮȱYqRwU$N?tSL&)8%C\96{q)mw>0a0 }⣧77b , ι"$»ߎEs;$uHLSI>{VՁq!aSzO,-w>iSQ IDATs.ߙ9TWϯ.(~aXVp☼sE1MMcB5H1gլ}74$ǘ~O158!Nx`}ߝ7_<ٴ6KF UelY0E@fج{R _Xv>{L }}(_Ϯ.|tƩ}gݍAY M[Fshw]HVmWAb C@l/VHn^WkBn6j !,%U퐫&4mݮۥ!$f1RBNIEd9iND4gvhm)q;~廋nHUahew}}hS ίfݰ?2)ݐ!TafMӔrtꪪ|nըn˦10b)~쇇!|}yrsU7 mSY(9r*Δձ%9N)1tF CiG?HP~ubS4*tC%O8c}Lˏ?}zE,3T"E| d1JۊvU$J|jD @6ۋ tsλY1?e]׌w!0( ^>xUi@&cu%ĺq cjdcbUɒDT$;fUh) 0 PZԮ aG5@qCI2CvΗ<cdT}_]lk F͋K_{Џ1д4M_MJb݆EWׯ^ѿ?0a> cwҔ~T5$_ 3O/:N}?8ǯ_cBD"{N!"?`灡碙gvT$# %</1,vC \r 04$¿cv TALTf jS2U4IJs 3r?)V 9WTT*Up94fl*~<%EI; 1?ucgO \1!sh Mi؏CcBPS0Bj\ hrXh*cWu]N=yqu Hr"jnwRCޮ.Yr!HTv"YB"b]?>hf8qnN45ќR!gƔҔbTUCSYY6s1V!0eU EZ+J!]o/0n1K&j*9gF\JvQ7w8L>?}/^?cu?NCL;nڦY4r\qS̢0wSp~X||4/=]/W)g34S?>v8VX1-m[膡ơ,TpG |3Z]W`KVCl:XwUU{>TbJ&)[yO;ߨ2zDGȀ䐸 ;u/7=0SEqS)k0NU.lٓ>E7( UM C"zd̰F#`k$rV؜K8,2U)1101d4E tc&$ γn<{^-Ğb~OYNϟ=Q3& b@i>x\}:VrPKSDDR^m)CUWuSe\\.7_&Ƅ恂Pvo"zusZǔ $<ʕّg͚EUM b8dBe&,f)G7d) J6B BU2JΒ*hp* Ǒ/.Wr Ql2;;Y4#r 甪@~_ώ~ 8ιjqy].~膔nw Oe]USc|oa$qMjhiJTii&fn&;1e%Ԉl'l+T(A[,ھy.\BO_C%O bNYĐpsV/9d@eLh%Ne01@LwuU;ffrLDܟWPW1gIfˢYa!(h0 }|24SC p a#jAeC(([D(t}7MS릲6j0qwHY{EaŘRpG=l U-҇ | UUxLD3"s\ld**Y|OK4`伯V A],sܶzYi O n^]lcrJefX%3WDKd&,Yl&GG`~hj\X-@3XVsd-ιBR.qΉ&Mi6upޭWgoUhH613Ea ˦ *YsJO?cHdT~W>{p>Ŕ5Mb;,c>g۔UT ūOhQE;Iybqun8u]uGRUU-S7uIT 6M1{X뇩lLa5!ޫ*Qq3U"~z_{}q PM<3iV8gÙabR. WO/CUh_YA)d"Rf7,w*F'"fB!3/k.91;$8vЫd"5,YNNDx^ Px;T-{wC }X8bEJHbd!TW닥#Ġ̼FܬKs2qz'Wq5v~u^]Hʩ̱r21)+b s (p,*f3dD"ETB JUR@ (bbE,*QYsXTUO:.\~nvwwO09Qȹ*T>T̄D*&` *9fQ_lp{@fѴ[_\|q1clZY@Eb^5US)fSQnWuiz.W+R?M0LBV*Tml6+p캾zȢH޳c4Ή=Θc6P*ѩy3PɾeOݾ^_Um8aL)NSNHfUfQ3咖iD"ZpkI2@} Bir<-8P 9&R %f%}QJXD{TAP!Ku[7"z:LW+FӘbaG1PirqNݘ3" D4v;WDxS!n͋k g$O~ a㻷ٕmz:(9]o5*I)٨^~XT>/Ζ|e$+/,V(b ``LmVjR"L-ԡܜ2E7L}J!ZR)Wde˫MUWcNÔҗ_~񧟾zw0VKW9cZˋ؟Rľ~!^^l p?ar^*L)cOUiZDT#"zϕUc_4qu$)%gfQݻzIrΪ e]𓏿1}koo~l]7 p1lo"#(yʒgQ~PUu CO}X.Sq{}b&~̣}QI))3c"2C4P3spLɉ@@u]G301I"ym3<8FIUq Ԗrc$g/m.!8M)?iB0)Nz?QU(iPl`חԏ֡|1CT9Y1!1#*:6 lDԏeo9!["9Weo;O}dߎ_"\VfFBYT߮y^-yf;oq wq!*%{rTr;Ŝr+OH\,[7!AP\4/D4e}atB $L䑹4%)Q2iJ3d8qLբ],n)^__WK((r֜xr}KP q$DU#.Qhg(欦@[UU} C^\m.y0(Mۚ[o/^?k vNgp:"cӟËWtDTRZh&3x0SR?%ٹR2܊9hGl^"t$Y#- HgG;T4Df&`L伟l JTv!Z&g*3hN"*Yr2R,<944Q(4dSUhz~7ĬSzQm}ue?jzr9ũj?,]ib $9xԎviz`:\cDR@Pa(I~hB@i&*@B]?tzD"YEk!} ٟ, |A,"i3vkWrt%眲Ǿ7O.Wz1MqZUʦ)LϓrLس E2s  y,ADr+~(nQC)RSK9M1NSqr4M)ūv\ZFnw/"`s>N?.;%gS,Z1FBsޱ#=<ܽ(C@Yd^]^Vj>gFP]݆뗗US; fm Unzǟ|xy4M1%U)>򔣨@@VyPmaYD;fp2ĂKR0+៞g Wɫ5##iU>(қ1T~fcD7ٲ"+x:= иU @,`Lzݎc7snULH}θZL`L`HLy0+G̾@4 JV͚p?)1##"8JM``l*uDٱcD5.v7_ޮ6W_,zEHuSdEêe;N}V ẃه Аj; C%fS圏n#!9K-YLR16GC))HE5+XќH"B U1VU8M]ܦUԝOP>Tݏn.6uU)3 !ξILq0 )͛ n`}iKܬn>Yy1N6#JBȀ@Hy*&tsgYh9@謨/ xfM5kNbu I jpC寯.%"*hN twcûX3AI@40C)ոM]KǬ0WϬիmeSdC%ìU[JۉX6e"b$bɺO)_\nc@YDt**ә lRe*Sd[:τ+h fh)2yBe$\|fC\ʠw8)nvY~N}3z.V0vR'1.AD甈ƾuSו}y*D1@UPe[>\hJ1ș eьJ uۘAYrϮCw)/*(j~// IDAT),C%{޽/տpޫ 0 \,D؜ DAEDr΢b*~tVa$fU䝯{*K3¼(n?A̤EUcE%,dP#fiUL1My1˦iCBn7./Kt< 8ܝ7ﶋf,~?!%Dd(a$GLS~{5ّjPԔw۫Ţmjn0͆ΫjnmqY"Pp-E49v&}M%IDdjLҰg12#Cfi~^șjUce7FP~H wE 8T,V9޻#0Q΢j΋MZ,ƾQwi>d@`]wa""w}SWF徇!f=351c׈-sYFP4ǨM];]2Sr5?~><0V·c)R< mcy Tg=VF@/Fp,Kia8q}ha8N[pP]\l֫#0O*RV9p|ԍ|>:\0%R䘙o>2aadIbdϟP1 (ITukj3moLY̲d$K_?puZZZDhٕ 7bA2H&LK^ӼJ30V@bRIL}3SQrEY Z@\C-iʥC65QrlD;DTSCCh%sPd`q;@PSMD@j`@Ҕqkui~}/~ztlUBDlZbͲqy_!wGF\Wb:M;, PŠg6|$tF* 8'5Kh~б'Wf% P ATL{fcx(l2(B,#0)Sۆ *8q뻋m4Ī//7t8 y~ghzyf5?0#8W%Ѻ λwHiRM%e6x iBo R D,pfb{وfSC׾{&Ea<]lwSNEB#"yZ5B8 19ݍTp&62Q͚@Q"yޔ1cA<  SS1"Bf@d`i~$tL@`9$_~p2F]Մ./6IpbOYuYmիPU?ϧibF$dBYptwwRj+ۦ))xCqjf@fDgXWqcXY|Z<[qVrJ a])9\.^mSV#8#q~ww}u:[f$b03Q{= EKY&8Sً@ 4Ϫ9/TN㤠TB̊PӬLEUEms):b"ҍ WR%lVX5yRj[rIi>{Ҷ< ۫fdtڝc!ͥI1}71WՌ@dWHE30"߽)\h"24NzW^T>_yt<! $mr?D> : X}شSx=EF4s%{~32Z*O17O} qp,J&g:`j"jhh308 8# 3Ѐ"WpB%i~Xoꪺ~zݬ)>ܾ;E(`qiaO.%)#^>ZY03#۷UJVb$U}] w{U5O^>TU)&fUEUݿ}ڬBH݃nl\ȸdbfYL+.P$X/ fYu5,%3 )BQ(B)IU`&Y\DbV4f2SX"s"eZSf'Fι MLA9|_Ŭ9~}MS gb%$n8NjX ] g_7{okYfŋ.#32"iў4aC6<Ȁ$7!ِJa,Z$!@,de%+3#E涧{e <1 UQU2ƽ眵ZכgYҫ^Ĥi0Wph|1"5Ya 91h.bW fs(zW͘0^)) S"٬i6VU4 wwoK"6PU5-Ho}O_>.(Ey+@\GT`f(60os P`!VB$9"R7!HK94m*B7CC5h?՛>$d@Xg%4MꙙcL]̞<8?[@z8)zs~\"az T'JV!*v4" l.lɳ|۪ LD]$yg+dBm8rS~X߯n]YONO$g1&锓:ڊ8ե$M} MLP?x/j䷐Y44r3!1FqSDCu{q}Cf T|69X 1blv|#"a]B8 1(auS߾Ku]Uml ##s sØIҤ*!4u]wyqg}w{D ^|l>;4,u[g4jf( S*$&cr>P J8ă\(aB$"_4uapi_0Vw_+_Cz}TNQ< !2ǀEj*0 N "]&̽jVt IJ3!(\lAʼnJ㔜QlNT M: C7BUGdN<~?ӫ 7w @ aZo?0 !"g{YÒ,&%|^D3P˒X̟<۶j=4 08 p.^.WĀK rN՗Ol9-w4/?}|voSЎ~1ĚوTLE$_!,ᲜMD$g79eKs`vId>f¢vuwdqZwϮƷBe170Gx@Ri)*yfH rv^'X'φd5$ 3|0 S]7$Yhuts{D:^|aqzb*rUœ a(~c@C !DOj3s=cW4TAp*DH65HID"H./$ݦnf1S":t~;b6ߊz҈) ɩlG!E)9Ҽ}")qHBniJGre=a82S6@PS-+KJ-bi;*9 ֳƐ5$}7gϟ_Mmf{'njS}?u]݋\]]<`a\E$K?f!vwn"bGOB, 0L)p p)H&Ѹj~sgjYUu z~{7=[9t<C`g8tY'81IJE`DDVP4N2Wi|Ys`D@@cdv<9š:j㐈=9#wF jS{o|q-dV5'NvG2 @eU@ 7_~|?wyqpxuwdX,_|)j5"ue8}Ӕ0 N@0.\5E lD!O#e j) R`D4N$*ir"D b@#Q]wCԀ9hJSIDmJc}%q/MS0!7JB .΢/t" 'z˕׆fCuU@ 1I$d)hfHèӤb 9YQR(Om$v}C;ƚAH4rY8DJ )O]݋?~HAA`D5T5eyBl]NTIՆ..ϖs5~艘0ׇ󫳳s@|=h^3_ovu۬VH!z·A]L<Oa>*bЏ"1gWS ٴ ʤ⼭R.MDѴgd&ck(gBN9gU@A@(jT= j\X^m+KcNHSvBŬj*O2#0MffZ!0M)KS j Y7!,vݶ'naۻiM'!sgzwc*v(%32Ocnvw0Kp @DB\ձ"s !WUnoMTU(dzs}i{|Z#"laM t>~|8d؈AV+ 0rIL nNҠe=?_>~]>) Vb~r !PˆDP0á7Ȼ7wooW'Ǿ맜#kn|/OjA#Q`aj!xx&pfs=>\ʛ=[&.2Tu!#!LŕS@ s#} <%Hd1F栨%IP@ dS8 n} aQPЬtuR3U# &c<۶!ƿjZ-qvzooyudu~w{c YNisV41*@1d$iVM[9r <8"Yh$@bUUsd&18Zͬ1OSߏyA XSW_[+_O4<.ZtY5Kb=$=B=%pTUUU~5uBL"V~6DHd"Hlr}@ds39R2c+nfMl*HbR ONVK3%vhYX:waxӫ Xnh &~ `U!nQ:](|Z<}~"dFd0t]!'M}^^V'U]s$9)@APl{ݮ/.x:3wڏ#!XrO.1+ ]XLT, Q!(-dW@eDJ 2DblLL>.*zK/pnJL.>:VD.boxfQ\f-FEo =^VE30rnaجw.SHY7~}^JNMS=}A4o#ۺjb=G"$IX.q)4YGdS]d5+8arԎz+UUw]'违:7TT4IL=}%+~# 5 ᔥ!;V%#j~VIyDL^cL ;!V e'9dSYDP eD h*tTQI.XGUSN)FnmմO|ztн}wu~߅g@`Y|vmw*!UЉ\"b"6$S""Q TT"2M CB0$"FռG#@SrM2UwLde?_nzu|E K1p9|Y$Re0ht!s0QUD8CԠi,ri D*ITe,:FBkL,IrҘ!T.et_4LaϟN8 y14Nn7oo?xe;B+]3m"H TJTm77)%De,#SS>=?]g'\P5BM /Vu1}cP|X,1Qyճl^WM"R40[P*/q Q×_vy0 (H0vR!E4v&} #& ̅F/d*2S,/9!cvHTU[# f)RI)/-˅pd\ZNY)iO_n~10SYp6f'% X,t1,sB€XQAƶ~DDl9,'99R?hD<Ûw/^^}0S9Ї/E,!Twͼac"H4o'{wwzoٟ}%_w7ǫ`Y ,e G0x:f22XwơmBB,I)@>$ AAHֱ$u)9b6[Ηd6IøhWV $TN^_;8/EO^ [(N:Y#PUMR>8?=;[c vL<%L' f( X0KӴ)-/N N03 Q%d@b~?v?˯Iۤ]0Y7OnwB1xF@ȷu0 cR@"|4U14+Du-#5c97bSovmg6V1!EB(=>P<8d`RrzAC` /`P8T41Ǘ/_?nw۷U[]ЍjCpGVs1#$U@lgڦF&&b< ]MCgRDonwi 2Ae(pCd4 U> !NS23 Ხy[iBU331U4ɘ{oWwǿo2odCW˵=l@,*G a}"]U{byf?i#C`TAR2$G )Rrۻ4rhߩ 9O V;\Q!~a8_> lmK\i~ ?_%R=æRhU(5s[fB9qu'! SK#B10woߦqX NΖw=G.֗*jBF,Eq~LPUgUSϗbQjuGM#ХD|\Ft &7uSGY_ǵ 2"{R@L@SJ_]{ dW).Ϫ̀XU1#feœONV˶m6횘B$v1#Ɨcp@&,{ )M107V)%;:1z 3)\e<1Oc./y?|)vx)YNÆJFbeD4&d0$8jM%]4M)'u-Y@@EEDBTsN@Cw~sْ(0SIn=D GSF?IƜsr#3\A)DxLID]qR0H(8ݞȜ%;Tѣ[Oø d1.VKۨ!owXU4L";Rw@],BFvbrUIx|ݦ4`bq6ā+Pn7wuۊ$,O1mW_H'>xʁf'6}OcO#~2t&3I3->9fP<c& n*0CJBijŢ=9٦A"wmߩ1ƱOdx䲝5?WUhm v6֛MUU/^~x~y)- `ҘNxM=kӟH`dKck_q%:EUMiS"˫v欿`EQD#71P uUMHu~uzrrGC'"[VɆ 1@h $y#zZ~{f5XjS~8N9P ДYJ\k=3SE9ӝfEQj*wWZ1޽a#a!H=i8V$@w} R2E,^Rξ*m4ёAw+gĉ4&fFTG H& asg iRMf„0v=c_: hYC580r d1$beh/Yeu޽Z . SSׄ&*ٗp4Йţ7=MI 3'ݙPԗXȻjd#yuBA[NYrJC|(qHR@vЩaSgMGELS?PU0_&xA9jL8_֑!XAm}}@pV+0)k<^_oގ:[l0%K \*f +Grщ%P^n?7S7/jiJSq!11EleSD%JSׯ^߾C& 9@rǡѯW;gQns޳cKp CM|8#ʧ䩘\\)„1pT(㝖R]!z Y9YRJ 1ջ5qSR,$9v;vC*A0t{ʯO_~'LjXw׿D| 7qWKaY>>3>=hC@5!:s;cu|/B1ÔSm4ٳ:OfønnnnDZvcÐ!ǟ|rvvzخo޼!gQUP%&bYiixٶ ! 3GEXwn~Tٛb˦d8YXАāb6zl+,HH%S$l81D@L 0R*c݅4`ɦ&YL=G b=n7''Y8^Y7}qV ь@9#`h+IĈ#I a&f>qf(vݫ/^1*Vm):WLj7%BfUH@˾7b#4@ٕ~cF]b03L@>!wSpkĨⴚ(,xpruZnЊr)bXq!TD-K^ՖUTLnPij||I|D8ٔ4~궝b)0,MS˟WJcd$=b]qlF.oO>r˱#G̑] 8RΒIDںZ?]o[VgO>˓Պ8 t]زq_~xyuo$EY3I2 CzgV T9pq.D)|JqUM9 M)=9]Q1"0nqBֳW'K\qX8?BŜ$'Y] ؏C)j9ߛo em=J[MU4Ob3$*j!FצM o79⦟o-H]=<쪬!`p0*S+/z 4#09ܾi\uSU~T` QU*2 :ח1ᬄL">G9 `$$H5S`& - @գ渴zh)!*]}adQ?NB]ǦfMUM[Ĕs78_,r~@c@ߙ}H9OCb XW]Ǫj3SRaGRu~x_u~H*ny7#r>E58 2C@`t=ŋ Hq0S ~*zrnCrJi:BUWJݶM7iݶݾ#Ͼm>H) d: IDATO`><ޤh\~{ވXJ p4N :=9**45ɹcuѳ˓xMiJ)5UMRdi̇]O䜷*B {՜3|}>@@!H3Qױ*' 3~K k! a|9-f^~+@$ͬiچVaa!Su5"\kC9Ԑ]AWhb" 3U2^gb.a&$6 ̀lZ27PB4hE^R%0 u<7E71wԖIEuƶj)4N]wl4"hV9*>T311dobq|6󨗓v~Pf֌]yÄ9ogW?o?BB ̱ HyPEU=`ˌQUys@/3")PwwnmƺFڟ WES2TuϞ]=~dgE7zLof0Y>~)c cA(\wTj;ܼ5SL"b:uΛ)B@Tݡ!PīV$4Ka6[i~6JRNBgd K"e+fm#t1jLc]fh1VDp:f^bFjG>snPn:۲X9DT$owXUH2z$B(fGI=1X2"0aRFX&8 1x4&`fέ2D@McIeV`\U!Ѫɂ*C3*jb@(9%9fg竱wk3E8 fXϛ.M۷1j=r8u]AUX4IN*598DrY64i\8˓l>aв>*T3 1yߟgiJGQ.պoNJbE&M}hd@DfZ@`LHcP7F"Xճnl|UE{f*D$xyyyyyrﮯٴޛzew^kFDFfdDYβnʓx@-h!d@Xb'n5mlUjJrUqaxXH-K+SUZ|:Ui]|ɑH&06˯SMNB|cj#ckҮ!ӣcQjCuֻA9j[=3VLLKBܶ;XYYQŨPԔW*m c#uubB>1k PSRL4 `'fL bF eebS%T% ~;])TE4]GebۈiTqͮ橕ݮ "l k[ KPbb[T 16MV"1庆1; Dݰ}ew%ɣ )1!1my&Bh`MӘYY "tiir8rm'â*c,a+DlՇÃ|>lf#Mb5v8ݎ16EB@IBLu33$:::.`8蚶hX ͶljTi;jtL<|/}K@eTSBDg =[Dd& X29N@͊Gh>[Wp ~f!(h]1Ʒu|r `?ɗ۳{xsqyrztdr..n/Tvpt,,WtFo%^~eZofT,5mL'w"r?2H̫6"UxRR`w̌v榪ǧ:^@fG Ȯ֩n.Mך"()%wyϡlv1rx=1Tt}?Q !P:IlZ;.iZ.YmVdD\@@Y4g׷s.X`]D= QӾ8t HcbQ?"JL{H*ycrnTUBN9jD=Asد|3IMQj"LQ56nK.Dئ1r BG@2PY QL9pax2 n#nРKmZB=0enڴ]ԦzUD(" @`/IM TG~l`uL|]z~WPf#zsoǎOEMU ҀE}q`I)`PF7|^* Cc@QYvm-;֛E $ ڮ˽d ett(u 1"H}sq M1kklԫV~`UC^_\ =q9OE,m|VѨn0=>g{@F~AETrӉFLb`P Jo1۶KnS'q) 2fy>d935kGxdA# eA tQPeѰI ѠJVee"]=( hnZ G]vMSnAŠ,n_=KMwñ$iU ^MϾ~ަNZT,E`5S#31sg2\[UOzP_q=8 2u% =:v"9)@1Fddomrx_g9EǷ7l۬U]2g[V D(1UG& $(umphA70~ f\ A۶ϟ=_.UYP U3RQϾzO@G^;99BfBf h`c"m׽xv)*ajnU\r8PbnBa(CQHĭ"@m\];aLͶ !$ #v`ӕ$ x̘\⮳lCqu>(ZR@&)\ƌ'9T("S{ 23GTnb=^`TEY*9#|ӟ<(CͷnqWSڞáUByDˢ,> ˛Yk0_7'Yf[.tb@-Fgx("dvּ##DT31 i)F;G]|ެhP=xP;ԵFeQ$%uc\Swn4LEeYX$(bD$bw"'jt]Q!HB~Ō|B$ެd7z@@^U?yq¿F&󋊨]JI\j<~wHM>"cQ`eU"vGAA{ " >p0ٓchS׶ "1hvVT|#22aFϖk/tږ8e}`)Xv0:EtVBHn׵ɰNjꎑG/C n9lD2Ss=8'U !X 1)H@1`pS/ַn=2 0}# 4_1"9^]mEt0Ve9L";1/6C42m(7ͳ[sF؀ >ǂi2B !HJ-vmYz^!I+yMjc}GOos ʽ4Q~Wr: XTp8In۶mR[UhY7uE eYB6ӯR*fb ΛhFM*k5N!PC?8D 0r .E뵩e?nJgU%{߻x)GKaRY=33ݐa=RF @l`p؏(;G8`4NGaS77fbd GDSyG "jٓ7`HLU-.0z^.fS3#f/>ۣ݁j΋M>ZJtum 8e,j1tiXZ(G0Ɛɝx8YVuN'Sݭ[Kpt2^{ v)$ߌtM{LĒur8o+˘nx7%Dk%!DHN&NՏ.F$JHB)=>3 ӫ9PVEQF> hDR̓k/şz$Sd} j\+I,OmJT3bQ<3K4o`Od&)q9C1?a%*z?2,*@]J^2S 1pu]-)wfm7M=7^b\cҊ0CEz h<@IEHh>ʢX-(II~}xl)Fbr(Ug5u7?xU^=]^]eCFi30o= d˼\$0fN`ef&RG=Y ^I*cUU{ly!x4g:yaHD!P3]_]^HxxLd/9B/@ۦ{乨ND̀vB<=SyT/îiqD0>FeB&fb0nf3>MF`vmOƓG;VUEm&Z:zYMu/Mr\3f CFʒl*Y?/wG}d&wbD8 r)B3(Y) 0q|_ҷtiPPI Fs?o.?rXP8*?]ϡжD=bb1L{x\b^7J;N=~m[oCUQ<EQi9`8캮iZ$p HEV,fYl؀}?x"wfUOJz%d6*lf!2qPѓCAHf`#Axzu}T: Yx47i,r|F\Z%ƽ;w$%]s*f$IV |BR bஃպ-*mڶVL!w)j5lz2* ̬Z1 RRx0v>Ma׵*YmVWuݺ|CL@s`EN-Pq WT [I80b @T 1D¨@ E"齆1wqH~j]/6v+P +DH /3*o^-,EB6V缻RÀdۄ0dW D2Qm[^;Ca{=OQZ$LX̕)9L}6.2KIf*v ) 903^DAF!A>_>|/NO'G'gOzzs{SeQ"('1Uq\nmUѰlRi1KrTg'77y8!AۤI$Mzqq?Ϝ>ɇzՓz?\^\Kxo,g^ǁmb\/Ź^eCWȧe]ӐaUpǟ-dPTe1VUY7HiPFrb3[ϗպm=|ۄY` H$8Ǫf"~E'IVNS2P;=;5]m \DH0Rv&I*bb7& xYa1IRjeq^GG&Ҵ< bӤ ɴ0S,s5vI\)0"=j)1[įjܛMSo6+$z\>m đɑLwqh\/~ZsL?,מ|g> #SCc<ҽ|ov,I.#*B h41j4"Q1{2j9-) d7KT{F7e](yd,< l/_]r^oE۶v~xX7k5e"p`dȯ V*xqq%EQi'GQJ"]"B|uE(,l8~[kt46zWl\Ϯg~w[U%{|[mW)%iD?z|nf̴qdYZ@ut]/> IDATdv;+EI=a=SX,٬R(HD ,>R7L!C FsEUuJ7]u]mmROΎ`8\.V_|kʪZdو`0'ǃ񠪪 TjXvnSEeTEWr98FfZC0d0PV`%&"59f@Dt}bJ=P2Z 49;n>+}uI<0ʎ{a&:RT]z4e˪B(9Gr,]Xɕ] 9PrJ9&eԄh@DeE.ٹ!Cn.5, DJ,Ru=}̿Z.-,ty_\URR +U۵n٘@q]/{ٵzl$Uuh]v[e*욺(v_QY%0O&p5mꄉTmv;4=8 .L$*2u䘙öiW~~;秗Mɟ,竧O>}dU3jÇF./.8>&dpIA})YOGEGlUUwd_U1wϻ6ۛ"E@$A`5!rZG0r$JgYP-jf(z6A6ŠxkW_|V 4!ZY7=}X.,pWF'?0A ~qD<?YaX h4[Im\noS 3G6v]lF1\;03[Oըg\_4u'"t6`rrlr0w>>9]oh7ޗ$Mjaq=,ףحLjqݮYV ֋z '7&f I%I`bB)"D9 z4aCqpEǻz1|S7R1SEs|>Z$(76(WAVD@/\2eUf42"#?/z99"pKRn6FF G7-P췌(MWex s'?@#'5!)C(#=G|YJeW{2&ɸ>|^a5 L.}4x;4MonoOL䘄fYm=<1ƬQDN0ϖզ$@ZLNNlf6_,vW__^X txzko~|j4ܹwNFT EzfjX"r_/p'- ݁~X`&$iifyJO9]^D PYtؽAA RjEUUEj2L*HH 8F(DH8Swzqs1GrP؇ݱB_]"/?QYpGΒL37x=#/Ϻ5`>dl@ȑ.!EE,` tMK"1=P4W?^y)FDL!2S&KjD͌!Š&Y~%8qBbfQe{gb֩ʲP|^V;c\`+L&XT NwN߻;VWW:1ض&NƇӢ۶__,0.Ǐ߼svzzvbF~s~Ǐ iXGo<`W?t?vMa13Q$]Hຊ坁;5 hL($Ha8GCG6PU5ҩW@'wN^mW 9hJ}t#JR7u״h{oumS!D]AK.yгY,vS_]۝o&`:N.n8FflX$X=;;;lA,?koT!0'v:[.4%]]\?ɳ;S"b D vg`ÍEU5ySZ UW0v/{I&S Tp.=X2B5<D|.!.CUDA!iD"ѮNEbA$-@+̨y[Q̃c]kȹ#,PLqމb-:#o {`8mO ƀD="' ij)bf X)ꉶ"!ײX06uTEb;<A0KIOedNh!FBAXUe՗$gwN?yk"MƏ>,)OqE$CDrz0]>yl4,狮m'f\FIڦmmw.k wD/DQڧ_~OvۭοӃw|PU7ۏϞ6M~')z=yֵo 97^)̓'pǷ؇ 0BDDyU5 }lcUUU];~8;;}qS׷7wc*@ā90bvKl7նS=wc/):'l7rVIKn4 Ǔ 12l6fDD@E۶e$C !;Ϟ_=q}u?x<Mm^#h7Wϟ> fHȞ&9DL=6Q;̭@Hb ݈2Brs-Ȉl~"Q*yo>DT 9E`/BҀDLEQI\l۲ʲP>E%\Q1&Y(z_y7j $4T =8 F zO$1F,Z wg$]cEzBz{GºʼWsq8aMP{%&Ck2ɇZS$"iv4=zMBxqlr"1j\sz,rX ]SP0B BH)ջݰ*x5I/\λmŢJSvh<'>KP֫z|'7ŰL]}ɏCL$"o/Wdnߓb^U?'Ϟt]ԭ|V\ݵBi '8`( :L!B"b`GmUp +1xt||7z Y:bpΒ4n]Ͼד}e=:0u 9f[]u]rDt8GC15$)*!St)I۵1xp~~Yob/:<;-&1/ a0$|x<1|싯 eydf `̐~f@=QЌ%Lԫ6(;݁;<=}e)w%yj>#+\*L5"*DUfv91ՈyftMLz=1Wtߧ -c\0#X>oy Q7́Ĩ!`@YWh{4zfI=+gAf Ho3@(KLfM.΄Ke##Bqj*Rѣ7uӴٌ?fHwNOn/oC S?0Av3Fb10ryo=N0n7[`X_]\wmƣ(VUl%0ܻ7cQ^{__U~Zh۶R&HsS1 M@DYux8aO⳺cUe*5 x$uǣz.{_ΣSlLpVAo7sʼSwjAkY-{ ZAew9 g]=Os+:V̍v(`ң ~y!rLԵk;U=d{CS1"fսvN8mLQMEStO]l9_Z_W/qjԍB!4]jVUݎ82B/>OƣϞٻ/M9v]mu)Mh0PsjnMIvmS%"^w|W7nRfEQzWV>>9>?HCnm&%yiN2CMT,6{ Rr1Sc_L<@$ifI&YfĄ13ϝ03 ST!CikbN9"KigVq0,e1甇lnv7>UEU0.3t$3=7(-OzP!$7^ FH̙rS=!d>/Jdvh}:O"оYյ3;T,j ̄F8$ .ޞNβbrjPIU:xmm`8})r4QQTUlnv;[-עRVEhv~vރv_?|9үr58Z8{zX;\ϷaWڶFWWWWD4O9p4z?'Oo֫JQ=Wр55[&PedHF& p8=ڧ0d4-ep8?|l>m]Ӵ"g\`5vަc8xQg9wK2?ҷEfX)]g $%9>>GeYх]6mwnn%NC=Wio޻ח} sdR`SiSj>bD7"M">$d+xO{n*9$M9фQHI%u EeQ1GMfm̦?HL,Gvв$L}` 05NHHL=ք:y {yJU" p)O!# eأ\җjL L}GO9B0ñ"<,˄k14Q^gȀ<:' *bom_Nk]ޙGQ67`&_of٘t4ʊ]A589="Faj\Mֿl>Ńl7$&H́]B2㛏4?03Vh4m]w]:Nݻub6S/U%dO$$@^*~ez;m~7)fK@4cp4O>)d}sg wyvvquIr<}W籙kSY5ot:mZ =睎gIWlu"(3OjP(dP涋HuCJ]_;9=f+ճ8PVU5bJ%ɧ~v*1IInF`dQ4@@ČHH5 nO)_YPAVH2샻 T-@F{t]bz0[o;'uS_̈6IWHN"^ƈ&Ijz\!x:y?3ct:i6[d,*˛/fW7o<|oٲ,;E5 IDAT9ڷs?'̬TU]nI,+x<Pw b h ,!1 Z/udVfu.s1xsl!]njETETV>g1BUu= Ĕf[;[ܻ}}k}~_T?'>@˒'0?4+SY6,J4 4C D8 bڦ}kJ 3?ѫ'GnQ͈LD1R1\ݲI>=e>pkQh|4,Eo>%03bfyp䙙>ݻ*]L~l4H4!ploH{u1.KҜ +R.A;@ZXIU42(KrXQp3R@*+(,hMB* "rёJhJj6̦kkݲٳgcPtR"*$*ъ @ *+4BDcw1[PgD#ojʚǠ#/l5;\˩=04RF0@ _ +61kB fr {,jI.>. D@}yUf]G}mr_<,XҲ I7>ia:(,ŠX+풉mLgkGg}d:jeb캆6vrJ''nI}y~Yyhk[{{m^qGgUUQt!]|m3#bR󛛶i󛵵__?O<IE=? MQr`|c~t"W0~ոs)$"7}p/vrtrv~JMsC#>]I_}]u^1/2&)eLNӜsu@؋["D!F߱yrS=<8wSWS*1yص 8x<z~3ټsΝ;O>yFH R `"3\adS/1pAk?=d&S]>lfy桙fn~>;OӚ>pÒ Dap8Y޽{Vv[C( s>Lu9&fZO~%c^R 0 B[V@6Dcjy2XLXf$HV!( !J]+5"7]X2c|3蕲hLLWYޚ$39jY2_9h 8ƹb)ζ&=;;rZ,GӑdmKCeӶ/Nf D[@]|IvI5r{xT?b{wǟzm&ݽ[U>uɝl6G.X===8? !~WJܿ;?3~c%[!2jDK`۾+6$Ge!vxx>BU !Ǖ0g<ѽ[LűUl@)W@99>TD-oooc4-BC]7Ͳi_yCWNl rG]jY30w4}j9{[o͚jOsOZ8fCèԫ7!P2TWa-7D ij+W3VH2I5s5:DK_TLS瘀O]_t2(Y6xooL51Df ޻{*mpj.s5)lp%QB3{OIlK9eXߦ@MjeK!ĺO~ Dvwct<겪{w ~+k ƣqUU V8N?:?d?;؍#~/#̌r/s n:Ԍ#x\/4jTQʜ ww_{i=fuD}Arʩ.?xxg` g99iyD/ !HE^MZ]pcc1ȃCļtm?Y;_yM-DU$p!Od0Qr}uu}q[?{CB"߿,!f!lBDEԱey&:<2TU;n\ {aef1TCG'h<MNݑf{Ohswk<3# Ȍnh9L_ m4en ǕAd%(ꕢ9`&gF((jSq3WakV6L_U5ޫyf%3"NAXY7+J?f!#'\UO~YR5"P5#vP.jmZ~SBfO@644UepcCjTQ]xGm>Fq=Mg|l'I=#Xjf|ѣlrv^R/e5ݿpؘouj{{'''''%ҼMOAE9?=w|YQ(7Hl t̊DUgWmSN}ʓd}}< 9&IϹY6"Vޭݿ?u|RJ92AeH Z$ 9G3ɺ8;߻w7GM4]3MnA'Rjx05KmIh`#D |}XFb83k4ӔUr۵1D@ ̐i{?jVUU'EYf777|IJ1XE_% 꺞L_=X4;E% o,L )D!b<+"pE{qt1-"apv޽ӳSy @Twg'm>xtΝ[0(հ tf* œ͢fYt2͐{|Qqof>7,6G=dO>zr}uL]7i7wQHn.1*' Y5L`C1Vu~g&b$BમcU)Ĉ7_>;꺔sѻlyud*sE01ӡllV]%ҹLXƒ\&fq\ d*RRdHw?nIJ?R "E"YW׼3{A.S>ա"CPpJHIA+2!1Ug7;Ve)&&3M.y6큺c?'?ƥ8!6UMZ.ed:T@H\HWWq<*_}`}om}ͅIL hƄnR(P!!p᳣tF׌+طݽwztX.Ӊ%dQ$"sjt\>xt" ÷.ywps,jÓbsN&ӵY1R` J9cۦTu?s}/ԧ\0:oS|;fVPD@L~lw6ף*tqvo|Kw1,S]ollr|ѧOmϦ.. Zؠbb ı1rʢYE(NSf#bQ@b)rDaߒ%TU9KV44RN{{ϟ<,TUEh\$*$D5A9(ZrkLDHq&0!3-2!(I@RQ 6P#~RsIߪm2/Cm|w~cF60}s읮OVW?XE0h.P`seCZ. !:# ҆K xK;(0y 60)EGĥ.Oje@4D3uNj:Dުǣi8` Qxbg'=[>ط,g?y{Rq=:uC)f|4 mUQqu|W_ԛ!*2(4|',&?̘Ar}~~GOG1V+Ոes>|2gɒt3ɪ٘IRמ-ѫwD5 B,O}QA<0 kp/.s0H^d<bklj20Ax4G/տ4M %p) X. Dt"30/6MlצoBioFI)e)宻}/{tftϾ5mOJBw4x2)p۶TƣOuUu9 O!T{ 7M7fٌb6on_^^M.?wޭۻGgt\]_yk?&8 1fVE}uΏܬL! E,Ib Wjw%@* 졒l]W "!Viyy@XFVW T}M ~ . J$\s &8 n]/(}KB+>3+:xb0A^ԗGX!ʀ"_!2P4[h&S`1 /`\0%;Sל*zڮQϝ=I=ozeAb<;=FS7D||j4>#XU/#;#HWWWQ^k|y8ldb۶K}*вY0Q*#;SѨX6ON>|#d 0&0dB&ܻPQEEaDP>mʃkJgH;l@%@/NSJc=U*[((eIvl:r,dJ(BRaSN.%氾&ڦ%h]"#U9}۔a)5m*6}?qپ77;E[M*Uu{;ےTdTm6MuHV3x4"s$&f70`1, }MƩK\EU{xEN}k~#Nf[kZh"Ȩ z}rc7x{E bC8ڊK[60O4/;!O$A+_$Uu99H2P54TTH*hA DDVK5W޸K+<I,{8H* LES^%y:sٓ -EX^C:PWI jDrN}4/+6m1g8:K5 D-^ɹXyS%Ǡ5Ax,&x24gUtc]׿λnov6?7^{?'Ӊ%Ek?OE% DU4xo]Z.>&"+rMgǟ~ #kQfED`xW77{eX[7%,kWܻR’: iўT (,M/Y'x:PN様!2Db_K_1mMTJadZ,,$e&v!W1wim2lRUѫp201FN]sU'1.]ЕT!b1woSǛGE@c F#r$΃h%/fXUlBӶ 9kfo.ݟWFu}oŢY,rNiLEcsX;{k0a^$d!V_e#MCa"{L hE 0`5JD4&CSܬuE-rv,z| i*P}x $=  Q``AnI&23$ٰZ A^|^Xf@Kpέ'ρ]("tr|OOONW2+ 3>q\qd_f@!d:[[sI"ǣQ]_^_^\\]}N]_7i~yy ӧ7~o7|Jߓa3` "qmXN͊ޖx2o>;~yXUř{&$>x}X,kQj8 s,%9?:N]Zߘr I.̍\vj9_˹d:F2eɈ䙍D!r۶W!d2ٿs(!Y }twk PJ=- ,Ƙ$c6E@cgĵ0JH(Rmt21>( X<@ 3"{dYD42ՑMTRNf~4]W+#BJ˅D">}C͝zTWOnllhHR>Zr.)ˀ.qWA$5 6Rq-Et,ꀠ/`*s" j.+b`4oJg9ЉM]R4 IDAT/%ɹ8P8"+T5b5O$D!fl޻50#7LMONOVu#ħ+"bگŁ?~<~ֿ߬h<ף^O"wd<9==9??r!VR08><|o|?99ldQD$e@@dB82'=}BUB]9?M ɻ+Emcаi'gkxKrSAhH$C>FD7W7)Kߦwvh>i !YCQ6i:|+_ЉK(qM\wT eYڶ:"H \-Q/Q م" >,cBBhY@b( !*.>*%'j`Kb%"F-)**ĜxT$ ZE{0!r>}f6@+/Sommq,VV4~8)%301,BWr&(D: nG*anf$B`fkBW.p \f L#Xs(AT!h@hY 44WK:a(LKQ!0l-P1fmRp] +s}Xe3P.Pay9#VO:/qu|tjj/8Tӳs@X)FVӞ9T3.PŊ$e$&ĨfdC>OCchVIb&[['G']߉.XVj"ZpE~GKT $&UҮg\^_ޜ!@߽7L&87K@r;Oח^}X. M:u]M&66gu!|يj[U!FKɷk ;\8{EG*"Y40HP0P &͋ ]ZfJD=SߋZ%z 䬪) NJXU9kWw6/7z\J!3_?` i*z\$MV%ZI s`96>#@M #z9R Jj\Ps$9d>r1C%V2Q CR%Hf0险ZQ-ACAf ^fńt UrJAAt3wD.TAyrxlV:^_Xi<~뱗?_,Ôz'wޙL&˦eµtm踮볳3E777[+~&ؿ}O![Bi}sIm}jRFu鋣ǞB@$vݽ.u!aUsJ9 G&6 5ei.ϮhT}͐ 8K#S@)^^^I߹[Fq0u= P@t4t:'+ܻsppy싋ѩkY O׋*|yyhַmZgZ d\ؐ*9# I"r- FUZ6 4S̘͐(Ɗ3K o Hd7MiU,9p TCFSZvf9KD5lju.[hǦ9'"&0DV)Q]]\\My͍뛤"ɴ$u]s zcOOw?m7^/63"͊LI [TշlYq Ȳ! وX$5MS[F$ԬZ:'UCӰ(/Y4#  hLQ 3"s FP!!h 딳f0͢[֭Ϟ/lm4#RUWS&b@.&dH̲%)LxQ[eow9YF&tP1!oefX◲ `N(@F̒ GؗUX~DL@:! 01 C *Yн!jOӓS,DտU[oViBewonn^>W/⨪.e|J_#"M)HUUgoڷaE% rv(ͦ]2h:\ggӭTs*J9Z%3]\S+_y}h8pd_`3T"Kn ޡƳ1GL("5}O&k2g 뺞LƣfO?zz|x^]ݹ+@ھEL1`f Yaxl T̀ ({&U1J 2H,9Km7 hTWj$hR GDf45I&)ur;5 1٨'G/~Zꭝ )([ fHN A*l ,eja^|pz1Me6ːDшSJEFRB׻~DBw! AU! N-*`&aČLJp|xz|t'G'%L?Co5a_]o2>xS_~UU+mKoڷJYɘ m}S-n}τOϏy"'DN"߿qzz*%'ixe~>oˬ ͦiME  fP<~ )LiSC]on ?2aJrsyɈjwkڔ8،ԐAEd4O^|ڶ޽x4yT>ឪfa9d W )9IJ3Re*J`R5&1QS9$ŲyӨID,)@@ 9]a=8*&]:@U$R-]r\[[SO?yrW1U*֧Tu(` Tl^?FIkbbL9#Gai]?JHO9j.)-cTadjP^ɫ< HN1q(Wܥ!S凘HJ]y HVPS5$S0,opr|fG'Ggpv|2^zp,[o}DžϺngXtmL/=[ݷ9::{W3ϟ3V1vNO__\/*g>?&&3*9a{sKUG*FO.NΫi8 Y.P{N's #FbEIovFUTc3@de }SsqqW`mLkˋ˾k˫k_{^۶)  toEB5G5~Gϟu[ŢkblԷj&Y3( q J"2#"Sr0!Ƃ,F_qBsvu},P%e*T  `Nf4T`hʂX,dwaq*RbdXYBۦS!{d!$HzNfWDf} '1tR%Z%ؓVQk5@bv 3)s̄!зm1Y =BIS,:LZ`s¢"~vif )B^ON`Tl\e[zhyh\9UU_d"f~~v`1X$UU淾oT_T:-|8<<bYDu]d|uu5}#aeUi۾m/xqlB$@TD`P[-'?_[;S>;;H~51dn!:޹w^oܝ+Fč͍Oxvs{goiQ% (f6YRJ`@L>$))-~`!e&TyzdCF%KJ*') ZV@`ͨC놩!3jQ%G7S]S3{TPzT'I9L\, pm}?Vڌpa2YbVU3* U4&1@4( L\G3إ38W(}FWxW$B@$C45A*h4}X6Wjz(>e,Cm]$񩁝"ɟc ?6Ec ת_9*Z "ـ$;M\svv>==ƪ_o}˷b2E%l_"B`9Ve,?Q{{=4WUc٩GoT4gQH2_!3Q(Y캶o7~Q]T"f1*eoĒ!ZBG?<9>E3hi64`EY?~ r}%D% 1h+^}_~/.mG47ϡJ/'Êr|o k<;Y]}|J3S!1 h4Z,M̩DԬ\C f.i7|Qr&HTx𿕊ߪ5+Q2Q!'c`6onH%a> L 4M;>hi[12n22kn2Ui{Ϧ [K##qӐhؠ +1XH>P0 fJH~>rt I!*$H& O!r1`U:u :~` H"bHlؒ%;I$E"ɓ'w//e(&҉q < @K@̀%L|?0J~* 2KEpŇbyOEtȴQ׵Cgʔ3} ,T+8=:=>>ʲ3RaT` <}rkg7/_w~/f"Ƿu)c_Tރ.ZlPM]*MH5S۶Yo|1T{rBf 7_H*:@F90fU:4zs&js?gPtpU-?q7Al@wo=xpi}D!gO]v]l+4s$6sr)(q@ P&&Lr$.lBcgK =s EQ1Ha`B fsra> sF2$:"EUa7.zr &$TE5L!ytcd16O|,Uހ_CԳ|qusc͑ E]fE DA{uBFI9 0)0- DaYaӡ1Ɔx#<}dSq}P} 11{ ,{{7vD;K$Mœ$I2,I\dR"HT/\g^@nHxdmܹ~<|8h)٨ҭX `rjlfn_Z__/TLوR*b\)O Mbfdu @ OXϒ$MӤ7X_ؿwZStL∉t|ʆH͜xSdKKI)%( h^]ĈFĀbA_D qmO$ҭ7E!b(BebrZ\/ vAN37LB`& )xQ$cvqS k.ȐY.v+9  6td a9YVL@MSB(ދJX!0++ 9P&(`)m9,"1CpQ3dqTJ؎ݻ.prRNPb *1DS 02!t АH  =AǴcCL p0GSq qb$*pfgsMb6]Mv wZr aeTfk~aw@ש__F"tMX]N?!! ˲[n_G%6Rv--KurGDt䅣r!.\ GŁ zxeR. H =$t% ZX$vܹ #Iԋ58\ddvtR}s rR!^28vqf(~/rTv$C F''&1;nk4kT. }pLj9ܹ@Q@ &5#d^{9G*[1CDy82jK& A`U jJA8V 1`!v.'!{ (`̈,a&,iF9"bUcBp'i"O6[m۷OLL}z>=;[ H0d/&̙@5@H@Hí(Y6`bǻ H8'c5, ܉T,@ͦggԟ9s.͒o\:5]ԛuK S;^?]_̥CPÝU>O<>__GJfӯmwۅjX(ȡ(D3B) zI2w{C (6b2"60,6[k>d>brQLOQ)+gP IDATc&xTP*3EC |@BBbG2<3DZD$i]SXH7/!g˥N׸m;f+2R4T59#RLH|8<̰,CE=:UͲDP(F lB <"@Bo}'dFgPX)233K h&91 p G (x, USr^C֙H鶩70u8v &@ȝV{mmcb|l Y̒~q; Vq͡$hh3 L2 i0{^rWcs:-;RpڰAULDC!  ʲj..lqmW̆F<庵.խ~ݵ{ȩ]gץťrsoLkxRx_󮑒/R*vgiRbPQf$B6>1],Ao0\a`QC@=[}l>3*(y$63C B-!zKJՊMV9v)syBAHowzY`G7^T%qTV(*vg⒈ص2V N(8 vRр $U AX!e`r!76; V)\(R@5t753F#" \$^D2UFt+,-I1;"!]S&Cbfbf1 9xQKBϜTr|Բd1S\pd:BLijq iem82d 0`rIySB!u4Tf ˍf$f3s: }s܃ gzNZqLXXnO?μ믭m?pm)ɳ4)UKqDĦ̐)r1LMMLo۱Yu;!폚Ap.ƛZ2H^sힽvHTdf"Eb"- X]^^8.+3 -Y},[o1E|=FFr\t zImRf5=9b@Lϼ3Ս C@cB5M2|ˍI`G GXٜxI{is;*Sh*;*difTek0 C >5#Ev2S0%"QEBf4:#5A0ħS & 1#d$!A#rRBrϜAĎ]B `Z 0*Br 03gq@ձ1bmonB16^D&S\lLE,Eqwh-7VVk4Me+9+ hBQm+篜;wc~CX8xG!v} HHf6 ^{7w?RW-WP":1ViNNHI/-5Aq$t&"i{F{ffvjz^9&ƐgLfSCHkrd(\8s50ρ5I [w}{z> a48*`X7;SjyK#$GṪ 9$Pl@5 Ĩb zQc"G.GHCh"!(1"S %(FQ"B |YEEtL,`S%gFAM"7OLR<,4jx Ђa{"c̯4VA6>۹k{Z2F&BL -Sd̐Y[Frm:6UJ N `l{>ŋ??wSRNB!Z^Y_n&tbr*bCK1 CUl|#'4(8RQOOLFQ|x\L{T5/p(F^% 0M!!Le/9y C*"!]xV("LT)G @_ļAp'R\H؈֖ yՌh`ȩpQ.!0ex*LQQU-O l( W&eR Z\Zj\<83=ÑCD4GB[_YqRupNZi.Kzay}ݜi]%_6Q׳=o=j^;uǟ8tmR)T}u^^zx@!bٱcw?;RQ59;9==nwf/v2f[sx β43)zI2# fk;vͺȅw ^pب Qn/tC`!BD$h+h+@͒l}}}Z}fkr5vɉbxt5K2 .0Hq cn 1Y~WM22"HX!jB\H2sQ18f a42T!LMTAk0FE"*LL!tD|&L䢈< p.nmV@EO:BɑxQD`:ՐEL4r"oh@BEBz/D^sFp}۽wgu*f3ok7T:# }k(]-AG6:WN{ߙ3?#sUS?HV+ǎ;)٨7rccc6gQ4hWǫfi?VfIE.?@U}&Ims9!3#eC:#,F'Ƽ*9tq䢰a%ȱ󆙲sVW[vDz⃛PUc]ƪՉKKS3S,M$M5`U 2DWD5K3 $@H E+8![G@y` C $S bDŽ̫s 'i  9,(ODS1s Yh$;"DgD E0NYT!X MRQS ! #`A|r+_8ꘗ.M!X|@5{zl~aᭊ3:t?|];ww}3FSՏ=wTa26RbNS/ޗ*͍~wߞɕn޶QT$B>T=yoIo137cv&25#"$ӣ0$bT=m4V{.MLNxQv `c jDJJTjKK7r{":fH`bQ)*b\)_~cqRZ(2hA5ˉ fT$3BG*_a Wm`Gdiz-$kx15 FKE$QLcCDx :B'.S`7`8YTg~CsfD1I%ፂ#b>̅5gUyQ :"-7Wl,[@O<;9-Uﳱt;5͵ZmCh\". AO /3ƩϺn{}\.޻3^1 6HDrWu*.Ͳ,b2Q2r=cvHV/,qrÍ@:!kX bX ze899Fh C"ax=02 \%iLLA<[xfj9@fJ@V "P3PU1DbfS ,ĀvG`SB4D&Ρ!P%~=F`⍐ EN{JTD!XGESH@Bp}}FhPo,@m\VF}y=T+‚bgo4qblBMC@F rT)QX,.]X<}o77IU w0>>Q(DŸP,/_-]nQRdB?J #Q%̃Cʳ!pxQdJ !; ) XhsNC :"W`F"&TCGW64a$fB,YZbeY%BEJLA,?0G̎ռy%0/ڨ5B|+GO]חJmZmh10?o #;;}_xg~78‹߹vZ^N|:Cޑ26ReFۼfD/$U *^,L\V+>s`}e=*nńLH[1W qX O:纝sԤ!cyh";˼1 TOfk[T,M}] hR FsR)8v,v..IHϯЂ ̛zU #a71;/$$ !dj$ $LAU0խ0u)off9 Ɇbya4aD%xy$/>2fv-(%Z^CFXjZm}_H`Qx)*xaaG7COW>s̹gz?7}hvn[}+i-u A|kmP)'itJDr!,d`yyubfDQX^w;}b(\^w}0k+cRED35NܥB\(JfZ,:t+5y@E@U B\n,5/qi= ^23P0:B 7.,xZ*"Ljf>QEv.B%[*!s 4H$ylH^ I 1Џ,&lLbDz]܌)*1Q<8$!DC*dX]3=@}֬-Bctt 貫ma~~'&l?U.\x[77_x{_K_3OOOOONN>;o%6R}d̮qyqsm} ]vXU_> W6]ݿRP> #,q\id%rcm˥RxH _?}X,Ucc T=4I19WbX.]8{qn̵eYfi` hQQbZ_o_(⫕B膓0U3@HR1ˤ&L J5:dB@q$20@v萘  UՒG1#:sy11C ;UےU%h+KMTD*f!䆚F b}eE RBkFF~jf SKs|(|c[ކCk'^}{뮻=C妕K_?V5xw)[[_=۬7\wMOv3c[{MWՕw.v676W7ڳsrbمiX3cE /R)I^7v~) (DCv\Œ>.Ec(Pch,˚fm13;u~9nA`C1*b}}fSUM%K,#D,R!SQVHb*r|19"UrlTb/!LDB `*b hfL%D.hpKqhP!D9&HLUUo2rec0bZYj {XF+,|s1^^>Hh!c?c?왳Aƾ񍯂zW<~`j)ۡd/dZQT~z玬Bz~g061^4B!(.bqjZt#HT:U@p QP3Qo*;ݻeI h9bD,\ѮsR!MS5PcLAEE"^#s!{ɲtx`74FWD!$# ЃFH 䆇ѪaRK &jF AH[|qsʛ}TZ[[={??<77333.Nɹ7>걌- D$M$}5Us_~\%r8=7=9959S1.\,Ǫrb10߸p166>f`q!!9Fڝk;>\UD Ȑ—(g޻}rbLWq~"DE!be3ѡ%i\DH*kI' `@}5dCffL|"@Ě3 IDATDB/B5@)P}s= 5/7T_/B̏.+]ְj_CcE8B:o>Ϯ,ŗ^?mSSSH^;}ԑoz!̿L"5 oCbrs#w}U&y*v*cn!1zѵj{jfL0pٳ;QT`L3cd*jhbαnn$<<܋!Bd}kߵwԄRYFuQ49,U9b.S!~ G 1:0  1˔r#S~'k[@ )ywcͥe"lԛԂ75bcAp"[Vy٣Ǐm۶_ؘx ٳDy>J-_̧\)U*e{^[;1>. "瘉17dQkm ˲@7&$`UpLq`kYڵmjt6Hb3S;bybf>KER/ rHX\ L ԐHd 0S ΀Ȏ()3r jcijZc z jk^f%q]l `Q̳y+k+/-7g"* x»iI6~٧yÇuR1 q)2cfρkribb q\){z^ <%Zfx&J"v|E BL1HU0?`0BQW1 &r͵VKvپml2d#+0(kpiz1\ OET "3(Dlff"{h>G 135M/.ūe0LIެey R?x{>q/oofifvZ&c#%փ!$ldAT16Z~gٹtC<21 E$"y}i ,xC&i;s5|+:AC YZc!#wkolElu]xeh0peB8:Gz~Gdbcm:nݝ^auynMGg>km^Cp ݽ{O:sLuVʥj1DPUr.^X28)rhiOΕ˥X%^2'!ur}tl3t;Ĉ P@ y |-z…o߾-K_?޽iUؙ*YZ+,]jHuJhc`06;qזf⨀YӫT*J 8 BRBT*4[J\ZBՉR׳ň؈RQ k5%B"B6t!1J*] @E ˼0QE51m,5f躮W2{j=/̛pT?.‹/_x~GYt1;>RY׾|PmOڧ?ЧO>'_=s[nbo^j.rHԢ8^v;0t#;U{ɛvJ:m K~ǝ,b"6Nn:rTJs}uecjT;s1ܶmg8neр 21!f}?=x3UF$&b/5aR}iZ#QqR!q;c>pHF[puug'S~O#=v{ TzO;Ԩ'{?׾u7Νyjw}3yܽOU;`@SPD.bd0p.rQLګOmڶ};t7[nZAnrr6q0\Yk V?DmdXqf}Źm4K667&&Jh/9mV38*ղe٠ݎX.3J@ӧNMOl!Bn4;wn3$ Xou[}d|fllJEL̘]\_lMMGMN]{`jR|9T$I21231xT\_\3˵-`[yÂ-fkTs>y7Ν?vؗe/g-DĤtH@O}S5R7V0. (![o٩q:O~3zk5k4InͅBcw}$A,.=}LeӳS*!Sn5{ٳgw\* 7Hr}ymmc2z_,rp}쬵ʕ䄚vz];kfRXX_@D2] tooQuነ;Pp?Q'|iK"|k_9;ffVW:jTkWO=m:7\7*Xś/Dr:rf+k?bwxxة(r++Vgfv} łqlf<􍷽 8˲Ngj9p`m.^B-ldP8"dNg]۹{{X4 Q&jzk٫^8E[\! yx%l 6(rTRG?O9᧾{n[ssmll\,8ys_yg5RկzjfjlJeq\0ɢB f>Cѧ 4S%Wsky_֨Fwsg|/~wD?s?hV1R?;cw(3䉓|g(į7?`/KG~+g5ROƾ_#$y|uuQ667Q ,@DafU G; w?/ҧ?iD_UFQGw76_z aЇ||Zg^"fBz#h'A-_;ګfV,ggffLP <'ޫs _wE }5#"3233X,̌_Y {zAx駯Z?t(akTQ~o>͓xg±O}їf{ό}{wx\qdoS9z/Vw݌@xrrS;LT$MnGDE޽gU ^r!@ڈm,H[\Pr hWvg.PS!!$*;W-Z-xA I!B"B.!}mH$jٱc|E,p5k }Xװ+swhG>98C#2F7dRR)cyB7S;sOFAAFnys +skNUAOI75 31u1k-iok^lne<Zۺ.Bceٶ%xb aĆ_nҞx¶mT¶̌"N]F)'1sfOCa3ҲR UUP2Jvvd)EjjjÇ55cOcj8Rs ~R.)RRH1gΜUϮ[N)UXPf\`/ 8q p򽃡55_Z MIZVJvTVu5 )XR7tIP___1fM&R(Kٖ{mb<0z54?l4)-mq:004)PwOommŦ'Z|6]{UǪ i#䤄who=毷aPBaJd ~K=݃R )D__bbRڈĤ`ӅdkcMŻ? #ёp$4464j-K)eicbјB)?YE+D"Ñ1"t)t[7iii;@pJ7),|qQ`ϗo:o[v0!(TJiOj>8Ɂ`@),e;7ià T2|m%K;纑ሐ'ub1Ӟ0bSSoo\xeIO14ɾTUǪ֬]#LMM;6|c+KuwԜ쌟+PRu6ZZD`,HZj~s{C]kV=(*r-,^.kڵLx^|a 릌K|c@lА#T2ijTRs 0F N9=<E޾ХP=wO==s@pe )Μ>[=]] MM>_lෛ.z‘p8R?>9)DuYinE(ƿJfdf^ӎpKCCpx^ ]d mm}h4zn{{`ѪSopqoM?a5dEձg~2=id dK030)(%2K!0F"rDž/ΝivDzT}i)B*i++)994}Qx9X]b/YQG>/))I{sRRYt_ikK9FcdF/a"0fﯞ뺏tٲ%([WWڵՄ%9xdZ u'neW,)9ɍBwv,vT, ͊ Mx|-7mr|?zxTT\ )|>_G[[Q.˲1oϚ5뻳j"R֯[gcui=Rm<+r>y<[S(,(,`[0fKvx0!8ISol2!DEyyݩo͜g9cΒaq7ڹsΎY߹Qpx Gmߊ),YęiU[Wy?۷ǿYq6Fsή9soذ_kntRZZgG1G~ ,xѨ"qlﺮ1h32KajNx x͟~bJzFOkDzR%v}ܕyg5~{_R}J)eܧRʸO)qRJ)>R}J)RJ)2SJ)eܧR}J)RJ)2SJ)eܧRʸO)qRJ)>R}J)eܧRʸO)qRJ)>R}J)RJ)2SJ)eܧR}J)RJ)2SJ)eܧRʸO)qRJ)>R}J)eܧRʸO)qRJ)>R}J)RJ)2SJ)eܧR}J)RJ)2SJ)eܧRʸO)qRJ)>R}J)eܧRʸO)qRJ)>R}J)RJ)2SJ)eܧR}J)RJ)2SJ)eܧRʸO)qRJ)>R}J)eܧRʸO)qRJ)>R}J)RJ)2SJ)>R}J_ž?jkK/kfO|_[~~G|W:|ה_=B "<{~S~o*!|?|S}Jow_տ //wwi"<<<"a 'IDY8pҫϽ)>?>ȄzuJ2@1Fo=@p&*;-DJppC >Ow|k2Sz?aj)(0#"F} a"`(" 0}{?Iϗ:eܧ~?;[= 310JaB <"606="" ^~_}ᥗ{|S}JR߼;7G )ToxϳD & E`f pĶ6l8 @0" @pIYI)/OS Wo{]>FC3) YTT$Af> >><BGED0xk/c)>уvm"_""DDAfբEYDĎf[n>E p0=(D"#Gk/jV{ҿLy*}?WA[@'w=3F.篠>̼̈)}~W#a-<*n՞O)~~w.uU㶃k!@DUXZoZUbs `1@ DxnBm>1<4 _@hV-ٰDUSxAEI8<" :b yV։#,(\" SU&"p8w}ɕ2SK?~yxToy߳D3/K-7Of>Faa&("&"R&|pw!0Ȃa6l00 xk/s/e'eܧgycFw\)005LLL̤ ?!AA `F}y l 0U5GZ#"@@ü0q]G/ !`^5_߾G 돾w>I1̻"*ln G00G}!"qD 3Q| ' Ù" F1ܛ0wG>mx7SEu_dQUo*"">_y>ՙ2ט?g~~OϬrD}s7Z fn6mu,LF0lm8iQ"f-B³\a(l1\4>}o?q;,R#nveps1f׋*/2s.5gU ` #<}:*ǥa1ws5|c03]J!3,? &QV`f52`݉fnf2pr 3@ALEDu;o-fe#,þnFކVn8Q YB"\{ oml[g@EHw~wvܵYҘOAD@><~muBZ18㾖Zj-Zm۶ˑCoF}΃ DG6F^|C/sړq?'ћwE;? CU #|@,LJff` &1 ͛ z1[E*EK)fa貫}q^YBdze/ݶz>p/K)D*<;XuADe)** 1u;gw} 3oSP3!, r~U>}yq/g'~J+D^=~߭OV|<0SQe=HD*"LX{w2-F`ADRB"[֛0ܼ1-(@hckcݶ@a6 ĵ znk_׳J"EKB}tz}x8SD{6x#Rp ~}'23Sz{kW?s_wJUGyye)Tb92`1;)+qf&>h< y9 h!&06 e990 k[4L\?xp-;D ~Re]qy]maܖ~x<ַuΧ=0͆{or7=ͱ@s D8(/|[_y-ݧlKuD|{97^7]E HXUD#GeDfQV6ƶ6GpQ|w(smf>E[a?~A †z>qWWW>zY]RJ=^_=xt~w^Rp\J~mk=Ja[{f6"J"*0+o_3@so>ݧ/ݻDDD-}J@XRTYyin"c7vYIlRhַauu]!b&ayonߺ9 ,*pCXӹfZJ]*۶Z|8a-Eܽ޷FPjeexmz\F]ME]ZE-z뺮J4_1sև1藙E8_Kן{7x3SO_ٟzϳX*߼;f"&DhU!O3) wD[>1\vAk |9EsJO[󥱮LEYrsymj)ֲZȷu[uAn'*RTY4ƶm[0+po".eoEYD*"bu][oA$mև\22j)isT'3>?G3>#_O|3?w=Sʜh"7w »{YʬERYnNf010ͣw~kt?7awr<^-anMlf̬ {AEEDTD.ãm뺮,LEnmZUU8@ߝnnuw&T嫪DL'%Dp|?̌so|? N4G(Ǘ#NA`0w bAy` P!ERIڨZvֻv:IQ ,AcWî%|y$17۶G`)UjQlַZDJ-Z{Nz>91Rzo?h~ox]7f[xJafp;HSF/+=伶_7~/]O?X2 2r >,bỶfB Ls7GȓކGxl6Xym}H-,"lcs5??;Wb}:]МO'.uֶ̬5 j$?reܧ/ޝg(AB,d|pfVœ^6GÂxv(2GyrZ7av7PRN!+ 9G7puN8[\f:AU1Aۈ0Ej p`>mk-"ԥ0 @fmkk2)mt>ݟER/"WvZs{2:gO6~E|ѫϿΟqg?xϾShsKs!al@4@8&9k.R1 s7\J(*[kí"6WFm??Wim[3-Z lޛy0AHEm :Hsʥf> -e)*",1m=DDVsaK6u=[j\jf |Ba1?x.nnL( 0V5)&z׳ړq?/}yzѢ(sV1shN>aAD~y bbf34чO|ޝO[^XJu{&̍DhK/( ڶum3Q]j-mH>.\bܝ̎Z;UTDyRJ''1Wa"\@*A?<|"=!}{b̸O韖qN;^jYΊ&Aed%b64HnEA43 1f7s81{`֙QZ*m=ޭww1ڬeϧ bFg"ڈq:{PDz8Rϧ{eK0ֶE1\uwDĶ1:ԢuV/i\D mkݙ|{{z۫~jѵ{qWvBe~472 9.B=0B@ĹRr6|GB5>ǧcfTQڂG%9RSse73!1W5n9gMߝs լպ6O3Ӻ6&z~YE3Ux5guFy=uny`>`m3",V%"!"H#Zk}ֺW!J,,2_82 1 ZTUd& 0 b#a1o1F \'A?k/Ogq2 7=s\Ae7'0DX8Aqy|R' q&Zx| n,-Z[p1 6 o{ '2Q rr[/eNR&kR?zGDA0W$ f`ã[0!̂@}63Φ0<}!=*sHyë2oJ9d>e"܅ Wc^/ym^ 4=1~> {7[.Zݍ,๳yE32~Bnn6Y +|j>m^"ZwD ,RTYD#my=s RYHĈic ;[DcWaWo޺ު*@#EhHd-T[ЙIk)Xkۻ,UG˛eEQFErѻ{̋ã䟍B{F-Ax^''%>}RaѢ,vɸvb"-~`Nd6n .8Fw_z!2+p:N{abJp{a"̽jmGpVKO9s:GDoc[iltZDEG߼ui IDAT/hэ޾y3mQ&ym>׃%@h-Kz{Y2 [oz\^f1,iWu6ʲ gdS[63SUȃQsϏ_| {<(v2Wg?O~3O0e'9V^. 4g@"28e)WWݲĘm4hL>t:SO=3>"dn}4dS_\'m۶m0 >u[ ~/*E]?EIXR$*UYU+{'`IDU@tzF(f"fyXk>o"2O{*qw#3lILȟq ?\{Ƭ v9|rC>ɀ܃"(B<'d9~>Gy" ߜ6bHٛc)So6\ؚF߆3Q]ncRwz<Nl۶ ,ZcӶnۃ:?P,Dww*Զ6s/*eVy5mNaÇ0 Tj;yDEUe_,K[۩5)Z'B)q_; SGyyܘ(uBK[4u֭.Vh; 0SD4"\ 199TXEBB\lne )z)rA,چͩ!1ȽpTqi#5<'>} o2bwbYY*fCf;\Otl1*K"!fabX7mع=xxܕRl BEuv1 "" o}_[q}`3{cnҨB/D붵Y8ݮ֝ܟΧ;9Fǽtw|Qr7Gq6Ϋ>|vmWZ0g@n$ApegD0lwAѣzoq.n6[mManLFCາGx=z->Oֿ7~]ϼc9f{<.3Jbn΄pD]lAee0usmVE)0кC Y96 pn~6>@j-}&yucq-*tТ9ͷu][.̵.RK-tipcqDfѶ{_*Bs\Ͱq ;RB*||:P_]mUQ Py,ȼ VŒp",nٜ91_!LDBwz7sZe̲]$DAևA<܊T x?/>qſm/wS2"쀙?zĜ[@3 -Ua7?t:2תE}VZ[oX@sú֛2R-晭yƩ͸qU'p9\twm}c ;.:cY$lLlCLDsx'sF)O7߼î1(=o?VEI2"r|[͛D;'FavPTUQ+Sn3~;<ϙ.}DL>0ͭ " [m4#&&g9R \>C0@8q{>чWtZܮq~_x>R^O$sǓKN;p9|hRR߶Z"6vu^s%36*"ħc.nƩr-jR1 LʁZ" ֈ"b%&,`Q)mݶm µm*˙@Llnu]u[OѪ\ }|[G0a!NK9C3z'RTE$ܾTk[PZ/Wa~1<ꃫAx'.Jw ݯdܧ?|p=hPD^VT-(h> #273c*6Rc0{_fq:^.sfa,$1.g~=n K];k.{_n"<֖e{imzYQ/00]zYmkz2;2('0v> `&%+XBP!)UwEVO1ja+rZ= 0bV%tST;s1GʜEΛGy2& Ígx~zLl[{7[W}r`OlxY2b;?9^]Rnp3j붪r2\ e#`XH)U&G\ر?vkcpXTqǰgb8kVEYdA@Ha _{QYVXr0@vmݼ ZTDG>o#Ⱥw@0|עT#c.oΛſ7{-7Ztրٟ{N;seܧ/|3K31X.ņeV)$,֢,s,"!0w3£ꢳtBDY4|[[hʄp{ǩs_SkcU*lw*E޻0Wk&nn"b#pySRD%٘JÑfֶvw{ٓ0n)oY(ʾ 6!(u5"GaU%x<~|{[UQȂ(ɁslvS[ko?긔\T<9R̀!*uLEP#luKYtvyۂ_a\&?euϹMDe[$DJXY?6KE43 ɆdadBEVc[}h5E%7ndf~kZ=MX(X=<<St3~=:E#̴E/dFsR '#{WVDX!6k\c^'BD/nRUZXl1n4c]HJj<2eܛ<(Qni$RRvs^[;_Wz.D=aX"3QF97 O=jf|ԓ*\$Hd6'ϑeivi0Mw~L=<}|w_[c84y|q}˻1G=,4>Kߙ楻E5O"U1lnfĤHjfs_@3# tea",Ы^?,.$ff`():́y&󓓓PϞ={;W+i& y-uDQbe!RAjw#A"ZՐTjkbRr-s䲴4GkND*L(£5_".RV"@,\%8^vLXYXJZ{gaY!xZtMwXv0ev1h&fAǯ!5G8R#k3Vgͣug/ nJ-Hf[_^{Ay _f@2l6{pZ6KY[l䥰0'qQqa_UH}` R.u*'3{,x>x9Yc_vxӡa~ ;k̬̪G44-t,(uI2ǡB"!Z Ho6\TGfX)#rJaa)ʁ OQf&$p8*%A}Am1/LX慸yf`lSsAR>lJHЫf Rd" #KTatEdP{bEϞ\UNjD??{|g˜aDԬZ!U򏡰?_o~q^,U=z?FkmZYvsR֢#ڡupaœ 9!jNݭZD2BaّJPyzFx3O88nO0dDҫbݑBUjJŬ u~Imv#Z1:SǼ9E.KkbTIpǶ–T8Mfr(<m֬-<g0Ȉ2>Vw}*j )SPD)3͢k^Q$1((""1u\m6ao5}_OcYx2S#)ȯ~W~aH7YQc5U3a~u}":#TU31Mi&q>}ZdnHGt/Sf2yV-ZUlTmrDRR2[swwZf ( yu9wB6RaU-kY /!T0+yH,STÀ\ ÄXD8S@ՒP%"=b91%F%,Üɋ{.ͮoon<3ܙA&,s?`Zԛ)˫.|u/&z¥a=f??Mfhf*YUe(5e㳓Ck2O !05y;~ZR:C9,!cv-D1-&(2kKY]'ժvKDLVwyw?}񓋢*x) X"ҭS+b H3۹-a"e5nDūݫvӦFx,1 J-5V2bZH$̔nWVnkͺcՈ ! $DFWh3`U3QQ0[JDdZb=h(J hfExF|Uٮro#~t#UQ“|www@ כpH7_Xa>  v[=rKP[JQtݮ[zjεyy}į.oXZ#&".RV0mf'Dz{-UH{H&Kӓq-Z҈ILnx<"Zp-ŋ/_ѣ[Г'8]W* JH8ߡ$!ZBJōDVV#_܊2 wLğ~O_XFD,榵*$Nۛ۳G b&콎c;q?6'_WxokO6]D2݃LPZt5ֱVQ^lr32#3 ],m5*TT<3"yuK/\65XHTE}8ے/Lj,""Œ#$28իº6˗4ϳY#" BUuX qM,ݬWU/S _U=YP"CQyiB0#%jm4ihU# yh tc7?_:TY\03^fGd6aR%^d{~zWW70E'z&a_RGg R$@ G?/a?*Q^d3zx(BJ)$&'&LaE-ҖPt޿R}`̷nWKaeyxV=Hͺd3Wɽܛ̏xfpLOD䑻Zf{s{{s}{ݾ,~F=3$Uj%#p8?Xe E4c cvUp tD$%h*'6As6-2Z$"nV1-L"$͍-A"z PQykϑ{/r{{|E ):Jl?OaC-cUf4"89<"K,sfJN$k)"Y,B v9 &{>B8AΙZgpalZu_+׿'3=Ͼ٬[kd'x[w.m a#8gk^̭' "%&.)=H2D"p|*UDnϒ0/id! 'q8ٮ#0usv*NuED˲LY?8=t_V{PXsEa?ޮƱ{cq"7lY27-؜DExg7~ ^Ue]2'֢Z%,3Z !."u$͛G"2(b!:$7dVl,[w?"W>Vׅ>eV_{zϿc"2t?铋i:e/"BxS II/a_ 3ɣG;?^h3- GzJ :r1@1!$g)_{V_ &dBkLi1o^mADXU&"#N(aKvzgC!.f*}WLk&]lFF{:Z{}!X<5<0œnoVǧ\0Ӌk:2 lNO={ٮkUNeYRw01+3Dy,Kkmqw2s?;?_:?;Ǐ(eRz ]DRHafff3.ZH ($a5Ja &ɣpha EL-#i~$U徦v7Ql~?C:aӀ$~dؐ4ɚG2nj*on6C!{/oU3P C}o~LDH0Nr,:֫]dv )2" "ڗ/_}Xao;z&Z8{k"fJDD3&1veuw0џR P Z5=RV:qyy|a&2bQIdDvfaNlA^1־VD@}2mvaJDgR ⾺E,9]ʑ7}#"R܄㰝Ke [Z0k=P pfvKtgբ#EDe򨙘i iE֢"e~7,<3˛g?#Ds<%3R {?{owwGq^U(S*Dh7*iHN=V0 I`X6KŶdQuG=@|d?rR"N6~CDDCk;og?XpwFzDjat~_xѣaU ,ڏ3 bR{dq﹎{vDf/st;[H$h"Ɗ x{L;'랚7ewwg'H)F@B"]^@DIXn6@aZ23< D,$,Ȉț>37hաP<|i-&<G^ *fޖy:TB̋ݼ]]mDEdK-<-E%c7aU]N{zF"LB,$$A!&&Ҁ! A  68X-DY "NfPMr5k"'u(kO/>[ޏT`_^^vnDgw?m/3ov{k6T!8"]<ei"$fٽ^"H?,Q'~=Җ.0;XJdftހaٖ%2BKs"Dl JͳE炙k-;x#AɲZ.QdH~EV=,"=!g&բP#1 uYW-ee&̪U , $"ffkbPz+enwsj=֪"**,D\Ka& G۱T"͋GB\$T͏dcV ҃(#J C瘟b0q@ό$ PF1eo}I`ʄ=9Ԣ (?O^^\^f[(yeHdfDNVb Bi?mYU~д$yd97yУ'''-zl-DTn$1HDZXUTHɠ#f"G2ZvU 3zEv;dow6PM%f,$ҽ= T2MK):d&=]Y-2FD a%͖v|j)̻wuot8ZËd=UQ0%U!=l Pe04PEbQK2:$ sxxWLuGE y{FI{Lz;S~4l*̂nŧ?֗GǧbPOW4)W6mq\r BjdeZ0#WГHřD/r0ѬWϟ8Põ {uGdDyQMCȹEF0P2Y 2kKyJ\)Bl,v}=$nW߾yb1v-Yt:6\2T^janYp˘cVnva"U)ۗy>2ALsP+3Lt{"peUz)jZE"ӺQX}A)/msGۥty|݈= $Ȃ $ʞ'J04Ee5c(QS;v"{?}Yr_։zeq&)K^^ U!_Xab?պӧUEE eX:gXҭ--2Yy(bX*]PEtrGR[<"qkCe8'ӧK) '8 9i*HNh]%w7w$ +1ُP=,B <~|/0M"Hj)U-c8,3C-䴸TT)<{%3B$%eWq>)2WZd8 H3IE-A$@pETG"i3юJho_]<{[v)E2'QgΖH60 z Xexxf{x4oaZכ-KQ8y뭧հnoe^a5֡"E\<мH&  biHѢC5̓T XG')3QDab #Y@HuU-r(vVײ6$ (EB?~eEeLgv3M~: ÖŦ٣gAHuX:E_ ~a^?ADl"$Hd25bb D 27&-)u,j٭`f[yz""}^殙_g#/tZT<{z~} 5tz#7_֛QJ9]U&LDmA3ꑊ,"fRʗ͋,̧mg?/ҧo'|k)Șô89=9ԌȄ!ZXx˜u(*E2*GT 0 g'yRXfFpb!ɜt4V+p1,A#*zG6&)b}0VV-as;_8 01Xd~r Г9n֊AEYDiFи >,p+ۿGĠEE=IEt[LKsJ7|7@|曏9"O '0wg= X7f2sfRy,2k!KP$,K3XA?/a?Te^˗7ӴܝnEf\_U}w_W!z.$H'd@²,D GNolgOϿwvonownڳsDo9y$*c]~,ێ3/"Z{sNfVսd!5%AjJjRnp%=0`0xf6=G=qڞa˖DH{yvR6~[ˌAP*OI8Z]j6%kdK%pOuyOoBd~4K [t]Ur/K`w3Im'Dŀ,pqZiJV@fTU6HwH7-ڼ6WUMRfRchJ'4~og=i~"D֠$ дn1,oRJ`h}7G^„4M oAAszmR57z|'=>óg7{a0/=GvӨY#)[(`;DMS^k#%FOzy7inO/__ΑӴߙH[~uӼ,K{ː>\tG8Nﯮ\_܃DDDԋP.ȩs zLKN)8w;S)y^QCXgqmM(7Wz/TRR%.5.\ V=DO~UQf_j}`m:/Si,) ,( 뺪 \$K.5NVxcc5H_~U\;u/^{q>/wO{wwޜe푀V[o/;'*W Lf!\~\k 0䒳 s8즡 a(9 ֵ16 Iu][]OV!:X0kffɼy^ښ{Sλn \Www$42Кi"q)xƒ P)/nhۥgXv4tsJy'5s'!i(0AՂBhւFϡ>GOX2o|.g&2:2dе5o=(5(ZRJGhSE:TZ6>kc{ϗ\t>]ju6{!\T[o`#3>B>pj˻CDKeZ$}y/|1*A틮I{KW! *R}]RcPnZ8ZUԚGt؏i]T ~ywPnm&P!Ճx a>}~2ܿǟ|/N~zmN, ECNeYV᰻b#gUkuT:,D/|r?UpDW5]C;sUD! Rޠ-IIMWl\Z]sZS.KWw/^:.,ZsUUٖY+nqO$noN"'|GAY*yMOG%_j?2yYѦ1TMm.T5eL;-V^^݉{n8iI֢].uO\_"p}}VGm.LeKimTr6K%)TU\ rJ3-TPa7%'it.p!T6HWq`dӥ.k?//뺴zeYuUמ\45IzJ.km8)4 |ZߪdO/_zd*0NF#66$Ij}վVaȭZ=BE eH9{;թ">}ٷswǜjҧlyY8L~wy^`J$XNl: Ӱ,UOUOLNcuWpYCݔn9 SCZ*yH`Dj0R;$FB%IsK}3\r@&2%5K&KS"ĖjeC]+77pEzHzϟYVΗV_׶Ī0O2e?jE":fvVz#*BR0W/n]ɹv6D~)ngWz{r2G8"pҽ녍".QKNf=qBeddz2"2ϳj{̸Z]ۺֻ %< C)%Eڸ56)K*Ȧ&"۹E8s6yh"o *PĄ&%U)S4<7$b@*9sWX!af'V{&rl拉B_'~铏_Tk},Oƒդu(D Q AR»_kk%$M& q4 "i7MwI:/Ejkmr:{57ƨw{,p=H۫K=uvs[y˙aURM(LTCT̀POѤ[QBZ2Y"h*н1W$BIf-*U9V5%: X*/zs{>?TמEYJ-yWiFxC*& ,."YZk,-O$*Qx,<8<{zإ F"Zi' #@Fx:oF|[Uu[kUUT'\p{{D(l.r9\狁ԒH$[kނRZǒEKQ ś*A::LU"u&".yɵן2/헯s?(:xqb7B9њvC.ƿU˥ %tː$ws<]!|8  :=:i.YMEI9ŗڮb05Gq>/&UvER S8LBE)QլƍK lJ`x`a/?o־wLU7׽kw ST{k̖\n?e'%$}i˒`erΩF}ۡ\ѧ]rV TLD )DZaZ_[/2326!])"_|gi:fm]k]C1[x]CTLZ k~8nHw@qn|kmn?uy׮㐗utM{zID*"#:a P5!*. x}Mylo&|sϨݥ'Zc~(Lh H0NER[ÃkurewBekJsuI&5", IRҭ8)GP@DUN0 FKJZ 9B~7zw_|見n1tQhSk zu7~j Js͚T,׻ `'5hh`#a=Ӄ*R^@ Hv[ f6&`pk_ӿxf*(UD(λ~J9nIos> BLk5 D $m #]Vy^/s՜Y]iwMIqn`7HEWd@D:LFBT$MK,S`/?뱶>|ug\ש J[fJk7*BAqWHRQK K!&Ȳa/j;XQU@jIS?2ύ9ܺ54Y4'\YOcJkkd2*'DAM`BCI- &S̄ w!%}ޣvX?tu˲\֪S3z? yyYSKRP $A8  a7CL :1ǻu!fVJb"= bPRjm^=Dct8"Cex D7*ÈD[ eɿV<7a~?Dj1l[#tz&VJ R;(T%w輶S U#pP#Q[y1$0@Rő<$M`R_O tRuiZqXjܜ$Hr|Œ-,0}ftW x3ך<km<9] Zџ|Wdg0I Tr&@dV[E}<}lZ`2x$p`T[H|%u.. w"1nxu=͗iƒ%]Ih-SJ>{gޮk'W ˃ZNf@65uNOwTLԉ%"$A]pU n*.fb*[ %-Z63( !zY#5QA@T~'~K_#w (*מèᨫDDDy3ޚ42Z]1a[M!a?j~pPʼSv 46E3 Lp6@I jkf [*F<Ɯ(G~EPv?yVRY"$(=I02 }l,3+˹MSiYv)X*><"b Cfim~yI`%>S*Eklڜ0}$O@TX[#\ds6(R> ձ(!\.Kmp㐳|>OCl7@^Nca Ld BEBziߓ-+ኬͻVT*$Ȟ"S2) 5X[Oi,N2 y Ot?| *Ii!"0@U=˸v}JQvPxkcU:f@dYnVL".].0O;ĦG`o#pB :T=v9a,E~]fwD4wF޼Sp:JBdž5[֚r2( "fFjڕ|r"ݮCb# %)(Q~bc~&<\ߌ%ip@)h6;HKS`F\^E568\_ٓӫ/iiX eyrZ*()dN%q9uJ(%:N KS,vVںR.=psW5r<]j/0ZUx4n`ͱnE޾z`xt TllAUrƼ+Ey]5aKJړCgߌӏeCJm֒{fKx}N"ҪVwbo-qImKE)S^Z wM: $U=sKpߣNxkROz[sRLbf|c7 4NndHkKImBHvvD$yk͛ZWќn@|yӮ՞?~çxuq"J* ۃc%ƫ]H{_g_!U!j9d?"pR|^\_e4,O"<\.Z}ї@ &j]{[S`?fUb&돉RXM@xX ۏ*c1{ea˒J^r:3="2ƾ!#hmξ Lu?:k 0/KU8H'iv.m7*U+7;N/)Bvzl?M "ҙ,$jcÅZW17O'cLh?M~ډ`T!ss D8$Qfcw*$p*B-̴坷^={٤NHoV4ԪӢ]zߍPJ:~*]d7 . R,lm8FEJ.d(a q>hTHxkzru^~8L-AV>~_vuJ ۲hg!4 Ԝt2+(5R)i ҝ@2]6 tGD)kVRƫD4$|^5j6~dWw\ ~l#7?wi4@Njł`z]ItOru5ISnܵs/ ЩP4ŰQW.A#T3y_rc$}` 6D uXlw\+R9׳"oLVo2\c5/_AjnjTcA3۷>K%km8Z2>rT@aұcp]BT06& jeڅuWA4I4Vk)zu=TkhnD({b"f-{tN1}P|Xt@4?JR &>թbں*C"Nww!]-8xgmHV,5#-Z9n7um`鵶{̳N"<$ 1}A.}UJmh_Mf`q7 %y 3/(f?ʗ~>S; H1HDД@#YsR)i)v5LS~E7H]՗Ka y5&iSIZpQ2]CMͬɰ`l~˧ aK&_l k#-IJ-LZ keYnvkWT id~E^F5Qw>yCp΢awm*NzXhad:yFBЮe3` ▒E ]!ceTk@~st'Wݷ|>S]WcƩee!T3 1-*VVi#A&2>u̙7zRaFR5XDcp0xU1-+ uv2a'9`JV%1wWu0:OU"M 0 7t>~>:AQh;N;s 1,17mJJ&b` += ;Q4xb^\^^^^]d#tO iZwKV-`D}Pb AjA܍ي4kJ9 %#eI1JĕFUfv(;uç,w!v">"BU+D0%J {`w(RE-h1XBl;7UzRD d9OӜa^~ʿ7t[sN<췽4~۟ΣD^4}L BY#^DNs)9͘!\ffkm@n;AV*MmVb AkgsPj m pj\Jb} 'v_y^"ǬX!H텺5y_6Į P0ÜtnĖD &SϚ!$@pOjV/^,>}}5LݫWaquyyyE{E۶janwMNYiî!8Wd$jb%^ҜܜLMՖ% 1Y`8Ǔv^հFN ue)CgVHb4ӵVA\1Cb'7QJRfji)Y1 #{4G\__~kqAĈo<{usR)#֐+Q*3ba3#*r q={?O07ע!:O#@ ̭K}47(%EbwҬLݡ~33QmL3Q}#Tʓ wzq8y\1nbwEݍBLI `؊Y= p "5GǶ$uS%:dnKΚvDTspCm\_7O>=l6Ŷ1jŒ~}ӄiNv[B`&b7Us q∩ T-%\Xf&T!L$|:mZuDaAwSXJW2Uahi)A86Ay68T!seӔDvBM5lJ0Y)!H#I iYR.%-!4,RJ9s/O>ybdWmEMtk1JQ*E b Mf&|^dJLhcv͢%j{ C`a'hİOC_ꣿ/}߷M "ACmW (Ðy6՞E Y1c!aNbΨR?U3xdbNcsҥq.MhD^H@UTLSƹt:=;oW^qRF fnnRݮ{ro8;Yɫ k,=^딜(T0%R᥎DnB DsТAϑfbc#iI`2|}S!q\?Op>_ʒJI0Ʀ B R bFT ̝Qo{qs 1 yskcM){i8r{o&CAD0.7Q**N(jul   `͜aCi1̩,"n|N: ]6]<# s^iSqwr_ݟ7?xB+IBT)7U"nn̋UzQha8Ƌ'AĪ B+IMXb6W"'G`aEj@jf?iz.ŵ=Q[_XWׇ/*2OAμPܢ 05fE=sgid]Yl䴤i黶q5khNAX=<$ZOX6Q;&&U'Kq5J¢)*/t.Ksics~Ϲ'/_Eߵ n/e.z-?-^smxwCJ\!P LD$Гpw<~)狫ˋ᰽8<*K">:a^yI,žm"/|sw/LU-y8M7 I+2` ]ZrD#VX`braiI$aJ9eanDZY#^΂bZ1M哛8p~7 Z9p MsUk@Dư;*\/?f4;fVVTqסm׵'zkNѬFT~CgOI"q6Z!n:[m@TL7 ӑ m ML(8 @ +XSY. ]DTN>\Ԅ44m(M rY-rE )-i1ݗ2ݳ< F,0 !v&6wv "xy:_7!vm?Ӄ   ;<zg]߼SWqfaO1pUT9su9; FRc+Ξ Uj8P4 L6; 1/K}&;! %v/t|+-(7l&AXꫯTCZ۾s 50Wۍk ԴMu,^?ݣDb xUo7jZ;DNn8슚%8L$DiŹ\.KIwM %$Hd0; ]`Ø4|{Ӯ6opص]O4O82XeRp}عqnvM׷MP𪨪! #C,@@("Ul Tj !8y._{mI%Ϫ4;{59y|,96:XUeLyfBxN(|9Y6x8 a ԥC(ݶuRoClɉ\Pw0 Jk˃=LFN`?O\׋׀j̝)Xmf\ ~_|w & V7U71P-8چFs^&Ȝ0fS۴"@<ByXxz)4/Ueq6TL ln)-46!Q4Vs1E4ۏ_A WWD4 Y9(븦 ^{l(Q0\Ԃ|L%]#ZV<PJ<ےTMǏ>|u/gZe3jh^P5".LS.IĹBQTYT|M UksO/ ٠2$"E m:9X8@]B9@(A& S `U06py'-͊V?A W3`1C[-UI[%gmԘR+X'R:Dj83Yn<`v5'H`'iupV8 Ux[{\]O^]f~Lf3T&•[V3/Z~ݍ6m㦋mIݪUU6kMm͵Bćv훮w}KӼܟNV b%;55 "68v<f_^]uMw7 146aV-R1b D F\5Rjo}f>zr6kjco ClBE7Av-aMk-KpiD1imf6TO4N \d-`TQS=,JJF@DB h^rZ:OyE܆ax{??tyMZ 9B`Wcfbf rBԼ\4-4GmmSJs 0?i3$H hq-2{}|~0sDV1U F`\1ȩ:c$6"8czTy "UK! WV}]0sfd5"YErN7zl]h4wg7}4+((Y鹫iI:mQj6Q]GS0er?z]o\Yf&]Y2:ܮ@UA kWX usg`5mCǮ4,ZJ|:-/>k)FT=\ fD(j ?V M\_bʫpj:w:9 YMo <ߺ#]I2S53bZTtE\`07;"s%z]G"DP;C&JKEkC@[ٴϔ8/?» 7#e FgO."xr ~r:7w/^!pݶm۾CM_6mǁY Lb UkqUu"@_8&6"1!-yel!><:\0NocFr9 6j+eiƬ 㘗\wwm ٘i^×ϟ?N'0sb^CFF@A; :K¸:E|~Ek^VkPs&RZ@Klj10Q .u؜. fLA!9paS)Bnāa3 6&*RojVſ\̄N0 OG~o^|G͓o/Քټjjc$yw_1tMBj\ -5A'HaIL* KNB v1OT60IĀۻYSg1 ICPTi~ӽ=a>N/?8l6햅yq͙4j;%7ZI]$7U_5NAD=]#q#X׆eR r{og_WWnmwm/y*|>/,͓q\n_}y7,bap-+FD,y7&Vq؝)0رm3XKq B̚_2T b5Or;\lxts{L RDԌz7jrԐ1܅Psrc9y]0^SG 323Ut Vek9kVk DE]4muj+pw20־;97(rib4<}a&KKΙԙKV]U݊6”L 蚬6pÍ #Uaw8aa&hf(yrn{o")վseJJL!Ķ899ॐ梍v߀xYe<Ʀ!㹢՟]v+2y8R ڊU#@p'-x};-)Dvpwwzuw{y͛ׯ!r7&%b HC- @5`2Zŀhwe+]pAJDff9fM%:+U ln| o_,df"V]#09qU6W T?ԆS)!DTY\**M̨! 2+fqY?ߒڿ35ϥ<_fS ,Š(QĊyDs6QĶ]@Ӏ33(T`$0E fv<Wnƨ3Rsh%ƪw%lWO ՑH D̵uL3)Sq̡AۄqQ3cH$5i)ZNiZFfc +ӼblֈF !0M#ola~/jm4/|>߾zi9o Z v Fe3dKfҶ{SqƜYg@,yYDCbjg-؆V.02bYAfݘ%0IqdӜsM`jR3͋jQ`Ne!+)/!~}gs a] v֤r.9"4n'ZJ.,5mbf)%aaR ʔZm2$0"u+ DAso9 q~~ 2F $̯gq߷aHAAWw~/^?ytٵ}kq0)-KR*'i)y)oY^y> cQiv]9x}ܶͶD$( B"׹y R3+NBqz[Mc(f iqVC%n 1'w%K6ݦK)飗7w||,K)pfՒvܴ28g0SO(+ݨjסW%ExLiɥ-zpnn)*^ief?SӯP8R LVx^]h <1f8VPd>aErQW]dqĦIys9mM˲,\Y(z Ń0M2-yw|y[3 af Uxl̀mfZ,mU=_̰j‰,A 斖ew˲j mFACk4(Z23Ss\fP ye0+kb0+V8p0U?Q*6JR,x ݌\鿰.ny;Ƙ󻭵UЖ q#0INigɔE(օD@nAIF$)..sKs5;U(v]k9>/ժq?;[}/Aax 79SϹxO{DJ퉸]lUL$Er^:iLj]Ǜu8??wo8\MWBN9rf&r&{: Kc$|68"QDBO4KWwaG*":?>>N"ѫ)!E"f~<"t{8êK '2@V yDQ#D&n&mDU 81֞k5 $GiYC? ~:-+W"fyޖuRTO?^2W bp88* 1j #܋%xAf6wUkaw')-Ie Р6?.#^mQ9Ʊ["E.K0u"8vBin^h$b#Uss$FJgq67l5BŔY l5lY;|,,SNEնm:n]DI+<|[~X L< ,up"Q-B df3 8 ,jgcvi ~$Ӽd3:IVkYӲz8_k뺖m3$`w73wdi[g!!jApxHn[FLM$޶R֢U$>Oۛl_x'?dz#!<[ph |ăL#zZ/,~OdaN j\FnԄ٫p1 ;¿"(}X=7]-59qa{QMD RZI@8''6)M&gVAk7ZS?]?HKUf(/83Ԁ__[!nND\/ Ҕ9p}|zz˗7q󐉨]^uYie"bi'.J"Im"%V်̪R~^^E|t|:{O~k-wCn$<87Waͼ?_Nine+I%q3U"]:M#DIfU$}&S>1H- cOnȠO "<a"HYt׃(Vfn֭jOe.g+Mk?կ}+Owk-I9H?g1.7&ayH,#{2F&aB^ՙXD4/]Na?3SDqҭ aw7%Lx^Ԅ= ܃"4iI6t!lL(jB@0B_]]23q$D q4煻|2@]j6Ls" wxUm}ϗ;/S8@j15]rBm,By4H/([)e&USJkk-ЬF!*fpUW5S甪t~w2 խF $Y8SIbaGt}w}}ks?+7{J)DaY~Snvyfbd]xƮr8%IIyJ[ XP L5vntY6]bGI 9dy斷mm,dq6R$agqڏlp c0鑉! L<.VUmr/ok*6xs įvCYě ]t8ɁRD[j(4Gܿ{jPm׊h|!7e DP%+!C-MSN뫔R{[F:anռjUM?>ЉYDbfS%/u,pזq>4 Ap͋Y?d3owDZluYND RP۩ڶR6U{B|nVOnop|zi]s)@~6j)IDELp'&j^{HrB$h5ku?A„.l%ğAxSc=4dX9ᾮUՎ3ơ[Χ.gI H%{TPvHpGC8 . D ™)`¡3EHVj=DZω(56{DpSvS16Aox $p0f5_OVc!4{ĥyO ÈUALpw&*jA0E72V~i>O@QYZ_՛0_/[Rw$)pߓ<R$abbs.A\SWZT. Yq!@ *&]y޶m[%I#"M9 ,L8MH ]VKR}aWћo߾]8d3fD$|>/UJRJ溮[an&Z=1Ӡ[`OsơdH |jp"$_ Ws :^HB x#F{u;SH"90dZ.n&&r̰m{qjL"g[4AlaauKarJ$IU ;͈٠rr/x^gOkۂyE nq8B5KƱ-K{\. B.#m=5 `8-%8I MN &ԵPt@L93%G%P¡njo޼9|(ۿkVC^zv4'xA`ԧ,RJ@D@e⋘6ͭD@9jVu[VuHK]J%pCeNHS,#|ϒ2lB,Gj.w%vLRG\^VSsPս- uꥻ "8@0rH*j RW3aFLn {݃hu~ab^Mȉ)%f0#p)^e:WҲQSNq0&GEqۇiW=/0nl@4k;qGoR] ˦%g[@ݫ(n 1̽nYyY´:<"%jy?kVIn,]1̫Vw9!a u-$Z6o)u=H`aZ=›mLIO擏y(mpI?ajZ⠆]ED(119$(XU@t57?qU[KaaG7kZfnVyng0Ƈro}aȇom,x L"fAr`f2fa)!#8H(KmD˫=EMk/W/*fnǧ0üYE)sDn4S2aQEXֺ˦zs}H8Zx 1TVoD|>n,-pw98vھ$Ӌ1S cf0̰,uÅ~I|{ P<<t{s."1- DLDS׆ " 6ȕ ºso!,?r" ܬe'T;7oyn̨B*=0q9t0'et$h`  $2 5j`GDnjV2\ DĀS E{frN~wƾR2~\t:?<'ϧY"9uu'RZiGxpN&zu@"򲥜AኡO}du[x!瞅$ 0N5V9PW$"%]$ nQkD,aw*368# 5wx'u$S kV~يKou?p,H}4׭^{pL $z>}5HP "CRtΈuӼվG&{I" }UU#RIe9fErN,IUKՋ Ԣn)n>1'ap<%$n1dapPÝu_xqs3†qʆpf-!D 8 @kNj@MQ]jDMn y^[Ή$e!kjg<%hڃY0 {P7Rm9DÚEDcm<_y@/(?_~o!%z7/@\w<#bbjIp h@ߍ˲^r1GMR$crޖR ۅIm3ow[0Ӷ}uSHk} (q1p@!Mo )xʶrNp)߼)˺ơ]飢]~[l(8<- ݼаR Xgh”04LИ5H"&t,V= '뤔)6u|3IUju꺫FKCT>mZ8 $ LkHq7M0xx)uY^Xlk,,\V%P79$,FhxL$SZ{( ̒- p8Js -֭$0gq'F0?z1""ϒ>J\^9759ohV#¨;K0J"ZfѠPO޿}נ@/ugIm _ePoOw˸vcz^֧y?t"0Ԃ 5SR;BB6w3JDkݾaNnf N|BE9|..Izxxb($nh3阉.hFScBhܬU-="j e ?iLn2Hd@. n@T#N w#+R,n°iJۺjQVS&"{n.~i[8ǺmZ|Zn^\㸕-,"pVK}d!K$f[KZkYKYל^rJ) {Xj K̈,t$Dqx m<0=-_ֱK?oL"hy= 43YC.FAp@݊6i7NDD j "2WPy^@Dğ }Zo-R=@o}Cp?}7?^)UNoO87Gz8 }޳2%n$j8zHln/nS ^baAf-? x7o DL%槇#]Q)O\Ky? 01 ƒIDf¢eiD<ݜX`Յ9Z2IRzquV{IO܋HbVrW7'$!fk1[bV]O ܭZYH(|yYDhRj9D=`RN]\Dn?9i",rI½s1e~1UnnRNsJ :uxifVSfAF[:2LPUANA'&I^94t,nJAER5JЂZy,Z+ˮ #T?.Fd6i kPb_W84u~z~7 cr^RnZZ8tߏ0 ᗐϔ9?˪v*| e([S#3!4$-Pg!v]_էym&yOM{ՙ Ėh`Pbx\2Z_C@8wꢏn7vYqB/Ë+8 2u`(SUt9uYUፙn0NyV|mNÙ8\R@LHo6=˰28 Eb."0vȓ(xX1Ww3{\>{8Mr뉛&ʙ('K7>?_rJiL̓y})v|5Nx?,8{8~1s [䆀?R^I<$szxf"tQ޼>LV{:2Wm> 0M+E.3y$RWu0;AnѼ8B$p#P)Z)q78[Ba:0'6;\)< !TOraS b6_*{xR\s3Z?"<``@&T ݼM" ]窶[%U[Ճ,Ҡ1¬V=B8Ã"Y% BD]҄ s\_=<Ճx? yqg53H-&mZZ@źZ»)M5v43eۇӼ!.[Xo]Ay/ⷾ˟J|_t]`>ZJ_܌SeIr|9I?xx3n7ZֻaAsO8pJB2V/3a| 0hrZ^OW/f0 ͂fjkw0) ui]V}9mlH<.QDܔ$ ZHĜղtOLX6P74տR[RSS?wOۻR:n 5F;HpL"ae)('zzzmSPP\KI}"ꦖs^l[ %3amme" j) {Z$g+, ;p 'FՎNRw857SkDo~Oyj4"dt)%.eykw;̪fK%0dClr`QdSE [Ǟ&,v9u_Z,RIVA:'+eNJ ˥sιؘ/O +GS;z37"SGGߞZ?!Z5Ͷo NDWzQ-p]ۨjff Ϣ%DeKRr(<%@r\RIwMl P+/ybNYM`QNKDt3$.L\2 PLpl6 L0 lUPԸ̲b[Nl >x-)]|SQ&h+Z(^E<533l]irXJĪEھ9.M8]A41qUwm4]Cjnpw2CkP*K}l-Klg5ÝsV6"tM:j!snw/|4կ_#&GXj!qW ՠq&*9̚看ڐe^=%IlT特_ " V!?Cߟ~] "u(̬Dªj>;.۸]uOp$mnr˥1ovk7?v,V~>2;NX@\>^9ͷmh"R3'\5/nj.*QlI]j5LB=ԕC)We iJn63g/G&brDU]DDP/HDb00\l ` P| M9C79b 'g7?xT+p7kC,9BqΥ"e!@U8]wΤZuɕR0WiV#1ʩrjT NSxlH`WxJhRv+(faxۛ#+.}16Asq/0ÔJҒ2NciZt }Z 0AZ n# r,)*u{_H.#QJDXijUǧt>yw]ۆMR|@MUu. }?٬;T#.LūXCȫT܈Y".aQ3T[<;]o@h5ϥ,gU9R ,FB0JѶeS3Ws'7us{qS1pC+kWdjeLR鶡뼫ibU ÝBp՜QABWUSg'פ|(o~G9nw۾ Is&,KE.řh^w2ϗiLFOC~]s^]4L/~uTYp#!BX``΄bfQx.v11*6U95gf-צ09[H@m}JB`pw7n:@J\'%b5;(7m`85jhL2&a hQ"b dxhxQ) rIEK-fZfW8 3/p@F iZtԂ0U)P=w~fڴmմ'vg39Q@6% U&ie+Ic}@4-0 m<?/hwS Svi|9Mv+pP܉)(;rkMWVU-!_-Ȅ:,; ZN.!h1I"k6sأSC`)7! Z Al25ǩ(b MaE+B(HPRw5fr@l&p."As6Sb4}%mJsҢarxTY){6-z`* ^/13+[1\B䕒df,D,[# ADxRiBD nհ%k"*EgӇe\~&7P]7:YD1f'a0_ܿ??ivt)9mR/|- [Q"8i%1cmWu!2UT<c0y<+K7>L4q^77u8!pIf,2~[] b(Re:N5WJB\35#&<#gڝL\WP`bсי-wR R#R 38usMjs!SJʴ,\hi.+e? &/4gbhl65U6APqS5뒕B-IՖ&c1S.@zblyA8\ ʸru-*AmnՌᨺw[ y.NNDW723eU7])/n}vÕ>VP$-nVyfp39y/aZN vB6Djnf4K@= ʧ_ܿw;?&w~w"p"%H#S"pdW@n)ն@ 8F@Lp+1gwq[~2NӋoM|xؿlz ,a8_fۯzP-$u0@D$zԹ r8ZZlI,0؝j.36C !qS"!xDnd1Lia1P՚,!&Rr"pl_} Dn%Ş& RK e%`Me41995ghp0&jN9ibZNfQdCE?"NZW)Jv sv38zs/58-V: dD4W}^|<Ub#reLzg90S_w7_ɚ" \6\Dig:E ;BD"g krqjR[f-%p1/N!@H ZA#UV'@Ýagsioi(Tg&bF;[1rv0NCJvv}Ӷn٬ɉ!\k;3T:x.@r2C]4_KMMa^9PA ԋ@<&2,b:V>j UPzuYr!X)DȝEHHf[LNS*K)*"09Paq "۾%uT a!Wxեo.{e2ٮ}lݔM%;2\̒J:O0wM+LsҜ 8gϷjx/tM;&8!6j`7Ђ9X<7__F7H`AS:_Fr5ETo znLT2 * ha"usƶg,U s t2331qdZJ@(CH8l1TIu֜K@D!BaCk.%QsQWG+Dļ5FaSR*beMs_,Zk__?)NQvW$*Y(JSI%MɎvtVr~Q;*kG jD'-/UMXZ\#/Ӄ@dWDAnn3- RVOEEw8;Dc6AK2A$JI:Ms*)M܆jFt9)><^zlnnn6۵&V}Ve Կ+!14{</Ӧ %'W8?FťTdO *ԱU3T8Z@L1OM ̲X `"gb7;X鰿 ݍ̬iFZ,!ܙ!KEiѥxv1\42@ qkđ`OM!,G^ 5bj)YNp-7=VƂ jOœk1HCꥩtIDp&vMnr8<%oXi jFLm4%/ӗqµB~m fXoG7Ҷp A/Kcf&㯓C8ꀵq]!T. IDAToD$LY]=Df3tJVNMӶ1sIsic$jT0f5iJircy~/߇y~;)0[<}%ۛ{ZD,DwMEmڒ3jiF"< c\B}^TY`.fQrb+B1 ]JeKt dj*sHx|x,U¨u:DDpu]LjTf 8%J8yl؁3 ܨaPwS#aIl:ÙT-hjɊ;1DT͂S/Z)GYHԱ~^M1'U<7Vi}G!fsQvF,/N_8Tv?cA`*|۶kYvW^~0C6"m*:YYhZ9(pm82IS)nڶa""IIAdP{Cl g0y.J u~^yNpbm}TJ+JޭiiMwD\`yp]{2!sk$\wN Zv7$=)&uV\| t4F̀6P#3ܸAsa*ufMupVI9LK!^\cI SE P-T 846V8Dw5E}.3* U5pe""`fs13`R-j%2 A7]׈p8^7o^Gv"謞sQz٨B\w.C.:3? na-kkj͛4MjwRX8X$gZg?r;?g^5A)2AM ʫ!gF) sΊK}CrVs\Duk.))O)0kG~|9/jws{ p:V;ڀ/_'<~ŗ=֒IMGUM9=<ET]t$"}އ:ܿL]A V\Xwr}\ `w/a]5S1 fP2Q5u(12.9/_nswO0^A$>Rv۫fb4OufwoFivw{kp8Q6n=7̽,WV?p2-jājJS+67 7XF"r<Ë˓W!-Ǖ_)"R07FVbd.09c/R/VYiI.wT_\J P W%3,0SQ0pWØ d8\Szn74q𒼸9)hқ2L0Cnݭ-!X:Zu0Sso )a1 gjr޴EBh60:k;ꘞtT㧫iQ<܄ ̡"SqϚQ 1KRqx׵!ps2}_jW7-|OSZۻ $ncIKmmxvޭ8L?}xMswwCGX$3\+劰WLHz/#C}>DZQ8'5+Ŵ'V ՙ* /9w&䔖 ꐿq7RaS$4g6+\sV#F1ܤRy;CNA@m۔:RuD/ =JxQnD"M3d© *ͬ4Ϗ_ᓏ>^m6vNTKIɴYx~x ӵՋR%zyz؞@2F 2Sn;zֳϯo @w>?Faml#8k}' LAPu""$na\<acՍc8 DJ)uC}Ǧ-4^tHvX8H؟awwR޼z?Y@MŴ=]Rac0)%mꚶi ܛ !U?Tb`&rbuu37R44 vRcR̈:zj"!YkE"Ė0CNm@KJL  -jt03Χ]K9DسYeC!N5#gϚ RN-;L //$RjwkݿK-OZjY=ΜZDI[ Td{03w|(2Kܬ]l}ꮏQ]Wv#31+<咙]dw+Jq'ќKV]03rLL-0mP }oocoB8X=HL9}$Sp_X VDs̷.6QʹaiB1 ,Ed,Z r3P5$ZBͅFH9O3oW0"PCy:^ &9[؉9Y829]08j#ARrn%0.1JsqUf牄a^)pKfVV 2l^@U` w{v'2 ~@$02 u}oV뻛& g+k:Rr> Ws@dաؤ -/vjf/=km\yR]Z033k.0y?ta0NL55zv"̕BK\aeX1uVrJ(JPHL)ǑW뎅YDZJFGwe*9n:sגjv D94mږ%.C߯|攺uQS9\&cߒ_ 'r5]"V/q.DWә\.S D0BBEi hK\ n316wuW8̫~܍ۂ]8sQLH\r12٨H4 .lJIXCJj@<)դ nd¢BزjK5gp=uoiNɝnZm7[fO%YKQ礧q4L$пS۟Vw_㪪@U!x]+uN엑bov=J'|{S~Gz  qJLE 9;!2‚:R.g}3Om9p"Z!B9"!˹1m@VP\ݙ9*I(vC/@< *A 49NKN9جV?].a.eZnV4yz1j7}]b*p^IɵLAk{Nsyz bMLUjY6xRQHJI h:H'3wC*I@ZU8gQkg"232*MbDHKb&_T{!PӶf^ VL] =uB"bňb$Yu_qg1L`\44L۟yk8ƿJ;*ֿ&Xpmid&ג9jiWj='jgC0y_uYU8VK}IVqHX,KPJucLYݖ Nni30g-jm P&cg1s.ZXj؅Ub1jE2LYmնx\K)U$ |Lspߵ}%iquoWM SxA gQׁKwUeNi8H DNtkQ+EDS5bBŝ*^`-k$Vg)[.Ch,VU Bٵ~!XN~9qalx`S55&C$u&ā5q1in7!6x3iNi֤2Nn5_|혽ƛs32t&(;+j}ژgY=v`&g}{Pa>t͆y._&r*Q;I\sJ&JVs@)1MAqݷB hHfVrbmD44ħRnn_Nw 1rYYik!*%8m8;5]߰Xѹd-:Ω9撳ZtY}S:ΧDyv^9Mp6;3Sˇ&uۮM pR'U_}+RI^~R2Ւ3)80bgr5{ ᩏ3땏'E!0iJQJb5"jVTfc+D(s1s\iW.Ӝ]#MkS)pRX# "jk"'' AKRo|znW+ˇǃ\Kc)kOOj16~wԷrn( @p !w5ws~_?~Wi&N4!ƨj]ͪ8F<ίw77]51Ʈ_|<'ԙݪkc߶Lb63-nU_WYX Ugr:e4}(ZjL,A$6Xr)2 9UbÎ|sjBKI6! szy4xv]O 50sp1Nr RRpxEӵWWw ;] F@eSYMojV#Ǜ\v$n_淾O5߼*jiH5O[ckw7^E^z*oևZamhoY!B&'+^7ؒQD9O)?~_?GwOߔj,(N$D:q$u߶}l~NW^|◛fwwt]{g7}{i*S`)'W/i߉+p)c0K. r" ঳mt| a4X nvliY7]ܬx>3cZo! 0E=XD1I5@<PSLa5ŭkBDQ@%R*efWm&P{Woi\VpwT5>0w0ǜEH Sbr# i!F-Iƛ84F,YR/UrM LVr9p|2TGK#+{ wEs"˿zU O5zTg ] =*} UJ_xSgˇr~?X>C]:Qxf{%@ q}A6%JWLWww~Łպ 0ϣu-I!6|w|xz<ӟv0La[qH3L `n $M1R#$4$rD 7#~۟<">X+7ӜO'3YyҴ]D@ r03L>>Ly.fRTDLQ;lI`Q-p8+:YFQ'GJZJY8t3IU)1R<^24) H@dNNuD -HPWU Ls@PfdSN~GV-XrG+y/_=UoyW&qlw_?٭?ap_IrՕ,X_~^63'?w>Hq>d ͦk]sBTC LYA`p`f__0sF3sy8O_W?Lr%ͧq^+ƒ[j7?>y]bZܼ "yY %\lZҔǔ;}6wB.<(&ΥaRmWmYJ8.e"KM̆chWm]u(LP2@Lf9eC@ X(D1U"Fs~8!__%@D,RY$Dg"ra&b0j38]of?eי߷sXYQ@ Tenj / Հa/bjᗆ$~ݶVbr7hK"*YŪʪ̘x{}~1@&3+nDF]{o}I,#TJAҦ\6mc`f&rnx\㛇x<O("[ r!a;,86omϾ?{FE "$|9ݸwg޹t'DWr/!Wk믿~]ހڝu(OLPu$n W| *!PP8;A̤W>ץg 0B||1ynj"il#Q-y}"ЭÃtRh2]jSJAL-%#*JiMۮuvmim^E.QM˺𞮅 E;1; 5|s:b<9_|&XE0Ϧuq4b4HrjmB)lwuUUqlS[5,P1,%Յ%h3*JNB]?]מ],:k`abׯm  ֣ vIݲ@" < ]e IDAT =Fdŏ«/>w[~%3wo]8[鴲8kr25""GIB8Kx5yonߺInz^Ǭ 25sop@RT #A'/6B_zZWud(n~T׫8јDM$XbAsYPI`h&!hL[]9FX ǧ砜ݭK/u ue4JlfjB DrԜeIMռ1(5V8$tCכĬ1zt|g7$1ٙZ2sXR>9弘O/VͮY;w3''O_|79Knl۾]>?9=7?5\VMeKsyLDt]oGXU ^:wg2&JIpS(4;". u^|>{?Tң ωRR>bZ.'7DjoXeԵuSNl&"0_y|:nR-狜! .6\\=R ":tfp 2m`|JuD;F7SSPUPY|(. !\SQ4V0 1BŬQ“R3@קGGm?̝Y"[n,dTU]nڶ>;eYסeWB0W3* (1L3-pf9Lp vnd^hDu4e:~sx2GG弩cvf|Z<>>_oڡ߫T۷~'_~+᷿k>?}?uO?=3W>~ٶXcOL}~*.зN"Wz]' XAJ f T ew!/|v[B29DME^?Դf5ߚM cu黷|\nV+Q&)}TU=%D6;MAq԰/^o֫:ܨCX a FFDL D۶)IhIjs`buCg?\,&jXI(V-3>03w]osds:X],qv7xR͸M&l\fd\YlJ%;Y65Z{uDEr. ZS`fwܽqcڵfytrR2׮Vsߟ]Mz^+_/Ãl6oOs_zꩻnvqx{o?>>-Ўure~ w=ϧxw~_u\?7~~^5l2BN0wb!"ds"R܁b[oSUu߂dsVSgO%XhTOMk ]mz;o2{ MF f =[ys2۴}{KwjFv8u"NŅ"N +H '> QѪPs c{/*wD#{4TU5h2mdLl` Hj}ABpb65U5bѹ:m?wGD@4M|~U7t$%CMꑒvf_h47ib?ꕗ>K:+K  Sכk9j).B@݈N l^r_]ԩ @XJ`uAnu;NQsR?[n ] …Ueeaxȅ0G0/O5lާ3Ou5Ͳz<yʩݴ3|֭iQSW 덻يK݊^6n C<<f"E(00[)F}av67UOYvΏRm_ohT?>:S W^7K]s9UٽwO7{Βȸ_2`Crք_ upu#9dї 4U&*rnE'!N^[bTqgo;Ud]WQMb#a~3ϚD6>eզi(Mž*P,,Ŝ8&Ё^l;!ARIj /%p`ڞ_<:9m*VRJX4k\`d kچK- 1/V^Xߩo:&fI}xxpx` !)6i232(C 283i!EB ʊG5/GIrHiݩ07rmOn\wOV뎊tȕB{[PuSk}ͧS7[rfd:t<|n^ۛni9qyأo>UX׿zMsZ7_uup0UPM@>b19 P T w%oy#<YKKHp40u&M9k jO>! -W_y?޴͕Tfh4nPN l҄rAn5`f@B ljem)P:MbA:{6"pҁsN$;_rT!͸BH)3 )ڂf9 n#nݻ1Cz:eӺH&zRoڔrsi$A9@u5c ](%>C NNĈJsa:` !\,u]Upq0U3Sڴlv;i9RKlj8Ͼ3713;=[|tsݺq|/ߺ|t=7$'d"62 b]tG;񕤒S۝׾גuw=7bu.'L WyV7 \;N){) |e#DJbղb&b3c#30;޾~/X,WO7f%̜kM!LfZP*1[ &0̣H]Us SŒMU<8k ^:Hztr>_[\Yȯ2)9t`W~`Hly[wmWNVrĶ8jƓQ#Q23s Ϛd9^b{"Y(1#d$L LS35EB-͒fĠ*Fbw͛/|{j& Qcp㓟PK}RMrnA$ iɴ.YDœ1b1r.$蓵B8 Ab`cК/ZWaղE`3Uͬ` Dx6nߘ,WKl:;8(uɒl 8X0lCʉナgTQ$6;]XpVs+îuQ\f3b)`6/Od+qpx)ݪ9'sUlu["1R&2qqkRe(̤I- DzXbLfj$` "7ft~ztq~b}x4>垭|C; ]9+!LTRKeB2j<#1 .`K ԜfM; TqvtpOFO?}{&ONҏ0oG;Lh鲯<ݒj\p2f|]^k8|_U<<aȚp 7xveg(y@06SYSi͔K__eLX[7=j^6}C5; LYAXϑ+}f AEWfD3ڗ ܜ/!mfN gvc P> M `'unJ:}ӧNȑx^hCܽlK1sʹFԜ DP3$0S`R>wb᮳i`fyJX͓x2) r#1+ !N{h~@ѣkNfF晘K޵uS[ͥ^F&7/|jՆ q;j+/}?'ڶ3w;e(={ $CcadR'(B_/' #$??*F}փj ؁a ANPdB9[u]Kwd%7Fj>:=#-UU/V"XB0; b,&|ϿgGLxRBQ%Þ (V"ޙ #CY"&bעE|g@ 8$pNF<4*|^,P ܦ4n&C=ĘכeHR 47jlh<F3yrH :cKn<ཚj;;(R}+y"J.[ۜ P"+IȊZ&7U7K}:a˖&o/u_G<~>_}j~w+ oMA1Cmk.p'`nNao}eVcf_'Ư3KfP.&/ydk1X{yu CTJ^;%aH,x29^,t4u]xr^#+C  lj,*|LG^|.9UAJPm3D I'QhĐ!@ IbmV51ȡkRavɎETT d4o̊囅S߇:LKyz\W%5K}vBr!1ѐvUd`g5".Ra>PiP/"P1QCyi]}gN Y@^'tbݞV0ݑ+`/̟yVz|2?nݸKo߂{[]m=ـ/Fjyf]آrSҺa%vy]_ DLgFIXKzEq4{Np)9 F`Rwu$s0PFYAME8Y7fQ4U]U!f_$&Wsv0wT\d{]2+#Iق2`b2+KvuߩZvEDCȤL+YӌBܱeMl.F^Ann!;nLNΌvwEH,攳n-Aƣ}?U˦k)_^Ќ0vBe= Ln(J7 D*AȁG6V-1jʄ[B$D1,T"U78d5Z6 ZZcf/9`{YrSMׯnW٭KݽwW{y{7?kO}Y9g=:>y{'g/ ql)Q2ldCW"Lõ Wm[En=n6K׮kuB_sIbT5fB& L8J!Hr`t +-5 n Ppw*FRE!Bw=jyղcTRn% \PLC'g̃2̷tFrċB!`ԁAY][^uU1 b"DL]*t*wOmМh6ds)5'Eۙg᜵s]G! n)'U5U+Vx4N'eސ39gc2-  rbf)0Wu-{dv4DL[,VH .m(g՜]Se?u~?ó"}~Ͽ4Mn~ݓsȶF>3ȻfWA7]3dQ_k+ubow);q*\EZM ɞ(-Ym'mMYo`61J`&#%0a<ə tt΀𠸺 ay`I[1Fdb.ѰT&h!ըlgU Tz/CB^$0O)muߡ &Q@pҜu U]qÛ7.ӤpuT~ZJىr9՘< QN9 |a+Sd:BID= Q`f8#Sզ}||nKU)y_g~gn6jݦ3}ᵗo}^.|w=99[{!dbO)Hf8#6 {c*|/bND}_'U3W70O&b/1VuUfSB12J06Ԡnj[dJkB$\DUꝧMO>~s=ӌkØ ( *Y%UMM5YPU"Si=AAĬzr"89]. vflN v+6f1fY-u }jSZǧ=~_9<7 >u﮻]̗}o}t'\ga4~.Mmsbx17!T|_']MmI}_; b AٝUs)6>Jhꚉmk!8j""y D1 IDATܳ=w p(2gԓqxHzzjɨVnnqpHT=UM-`bfÄ޲> '[MņUOIB 2l>=ݑ'2T} |嗯8}?W10jꦮ#Iץ*iwsufUMݴ]2t,ܺ\.IS5uӄDѨ]1x+a FX5wY\WQ Lڃ"\`DH r/WKU/ͨ q0CK8X#BNU$pSE權9gsB/ {ArWa+@`%v̅ L8:N[:wC G7/Mק΋FOă e~_9A'x?=}f=;wz_-"=]nfQmᠤ52ɗzԄvɿ:"__׮Kʵ>_RLF(ލ 03g,g͖U=ew˛S4u gՔM]Gp2f1ϻl*U/`vx@$f 1B۬VU8z:id^wqPKIX!l{aي?su%ś=e$l"yʞfZ5!*jP\ ߿0rr $7r*ƛ7u{ ueQҌU69 L  5=w-`f*%{۾4tˮE.O\NT8§dE:+R'o2u[ˏYWy0%m[>&6Pesm[WZ?~!0T`6kL\rKwmSJi^H`GGݸ1;<b1_l±<#iuDÐ>*(hcvlG-]>/#Xfcm9iro {pYۤ/ԡsDM ,dn*DBjNG6'M-{UÙvBoSw0(:\C?^^R+f 2Mp&MTb fl2V6``y~>W㳋K:]^;~:ꖱ;L֭bwpt|Zʕkm跡Zg:voQQJ6!<ÎY |7h$їZ?}fL:WP9lZiRz~%0q-!eF,Z.ϻ)6 գqcő9푲2B$*e@J5=Lf/.E$pb7]Jfx2dNQZUo']nrQK0\CV'*Pd6޺q+%.6YXNXMi yfgGf.P5rd5f&01 ,&efI9[:{`@VMY Y㓋fs5{b|'=9UL޹xr|Zrk7fۗ&}rPj}QxhZ92hO?؝T?t|ڇSzLp2~?׋B5TQLēEPgB`Hs.ݠbd:FGxluwˋkg>&(Db5 U;q#@A'.M6MQ̜uA*aa9y1k:Q>[֛='2tui}Q,&YIqb9A"[p'Xeϣ Smdز,;D&Խ,JBDOEc8qʆsmBRu}?:bJ?oWi DP6T͔/ |YݱS _Y}ͽ4{U˫q%d7V؝OB8mzd:rSŤƥeCl,yLbNBT.D=PJ^[3N뻮tA$H7uy~1?>9G{wjiq3nF 3j dL`fھ; ;DDI"w+v.:0\N9NO`VW=MolY,!ϼdZfz;{ o:c"aNC!e wc hI#`0U2ə܊lYu>_>>9[oZڳ^RJ' L{5pq;_]%>Wmx~G+>ƶ4P wbY-!""A_+ B80ަx8y-b^-dRPPT5 Ԙ*i_ )E!QkLǚX+PJJߏU{<f6Zi}(iG O݌ =WEyJi)DpQ6PlΨښb .6"KDMn>V݇_OӼrjxݠn[Syv(m-Nh-i^hX>Ǻt,SYS.~t-!Dj"O_^ l_s6jO/azB$@cMqR{0;Y xKu7` KmMBinf8n7e(f8Jέwa];!H %&Lef8Pl# 1Gy^ڨNoAd4|tӿHI;)eT* T2_AS'@ ,="D$`fּ VTe_J=3]D ՘6:<׹6vw7?xVTy¨t&$# Efx#XFZ$u5wP൏PNu`D_VZdIdzl-r.9:3/=Hq o|_|C壽)ih$!E[0 w@L`B ׼@gFMQZϨR/oVt^ AurQjq<t;ƋqwqS'lAD$C )痪hr׻W-nZ=LwӴ+f kE7n9 bIxpl!Ŵ[)9][9<}E28=ߣ[+ TUE-h#?K灦g ɲŶjE뱖,X{=.7o]1p "*&4U@D`D Q=H o^1Um0Ρ0\Ն|bnpq/<5ow0~wVf+f]П\ƯfY&bx>t/nn8a@yD <U /}ߝI'PQ"s졶8%k:~"S .i":EʬD!EB|hH$Wϟ3uU.25-XtMT3gx8] z0se [au^@x" %daEgsfXBH Bwz!h%x_*Ѐ!؜܉En4޽*0ǁ1M0;QY!18Q:ƘZ;t񀷜t=i@kJVMً4g,=&, *$C0(eZA֡095&uñ6]廐U,k}bw3v ;`72~dDo:E] +O_r=`iD܌gv:~)ő/!plh==z]*EDɨ-sɵK12~R,W9Cxu(( R"h&P:I$++APLE F,Ppl) 7Q@m㰛iN9~,&hC)1xtȈ" Rٲ*H@s\rl*k TÀ;bĨnetk/J(I+z<43ŖQ_޾;N,VDRٞqqLݨ* a2<:7ե#Vh=;؞sq pCӟ'sqa,hvu\s^ŅqKoo?.x/WB"v&T38*pAD@ 2jK: d"Wm"$:Q4J"2HXFW{3I xLk̆KqׇqP-G%87tcdJFv`Dg5=԰tv'5 i$52+WLDIgya(&h0٠C0BwQZWwiv;&o#bjy1YǤE>"3wz֏oM潬va]JI '1:kRB~_҃ߜǯ~D}QȄzd5 f .' 43p5M" E%܅"Ɗ0Ut-9]XP+ hI;"i;jK% 7dbbusTp& >}|)DhU,)*|?w࣫pwtuXʰ:@EY4B5B|7hQ*(T4U=\E{M*\$nEb.BֈáImx}u{uss9Լ\ߴ"GX]+ ~&V-"=Hq͢q_ةޚ?y|h_.HDVy &Uͺ^c[ !BUQ iZ@iDG(jS,'.0XO 'z{:5-y 0>{*6/ʳ|Z=6IQH8[P}ϯ4?[c` -"}6OWGX3]bA(&fN*%)(кT O3ap ^[[Hn6PC*+#٪O-+%lM#X/*)en3Umc5:GR>?Z˶ mwŖn`=XVÖ{^bj=]>E·'*}FiDx++)1xҗun龙 sQH׿FBFEsDNHPɷpI+PSkEk- (rܿSSKԌSt}whh]@ m\"N8{&J|ihMG 0AҦKls;E5 WH3"C4I㑑E\;DflYMY{ n?GGyn=r=6:`Ҩk=8U18Sf`=ݦb!:s:oxz}pZYnV^2p1afԳ8T'pg,U4NRN5 B߱TP)q|A!*A6'pb* ܩ*PA֟@%ӵ#мAsNapn(>ݏr;>{44SП>yAD4֤89婢B3QNUt{ٝK#`. qnӧcQF1ջ#嗚1"*bN"P+AFBJy3wʝ JBA7*%L O@TIJ 9Om M)'~Ɲ`=J Sy6UG) ^^]Ũ`4YV6s%$I$/8ElMXRA8xƅ !マ!U%b11nww{w}wcq8Cp!GzN2VNڈh=|G""Gϯni~o`$Ž%^.%[iQ2+~{'2v$|~Wgyvy7b:7% hAEM-BPQ(h+•Yġ&#0KZIM#:5\8(Š@/3Ƙn~EV(:23#eLEL7|*"YR>xrPwՌ0^.@=2\n&ڌ>sPm8w(r)"E)ƒ"@#E>yU .*!$M]rb·y;wr%wYμZ0ab|Ym~uu;Ww^]S؏f-Wf_Rt]3Edc#Oq]>E"\;'g)>[iDT7뷾)ܿ9okړiî-&ТZ3(5AF,0CP$m$0Ed/:Xᙣk`x1ƌN4&S d:~CiXJ%ᐈpb]^6ǫg8Uaj*by>3A JXVhIvJQ43?YYHX3%k lR IIPS9B^^]PٟЈDd qϯnnwŋn9ך3Ÿ`(MF^%~r+4o|*{^VeA;-4Y bq77_N))nT=>{gW ƥ#Q[ hj$|H*+PSȀ: ] @LD1u'ݩH,+IФm~<ؗQLK~D Ai\fj~Qk~/g=yumg&J-)+F~;5wVEPz' Bbf{A4P ,Q{pH á([3u=QEvETPhB O($xlwT=hWY܂D ԠgIcn<uhwȏw<_x6P\;kn=2WL]^'"ƱdY$c3ݝ]zn~qYkԫٚeD l\T5">77Wn9;A`pXu?Y߄\fZ/?EI˧U7uOK>A7Ʀ5t`CܿD꩷"=Sak=v_*zSGUc Y BY"PFlSPUf"n`Ls DJD"3lix/+_y@Ÿ(تî@F')*&LKPAS#t„YIhQa"}UH ,h-I fL@LE YWl  cáDy'@݁q0X1QQ ™d`x}sS<ؿzuOzw.;ZD7` ݸZwYJC30T*!`ZˆSx蚚 w_9{fMX)Y!j+y R,SwִzQx[m "s7941tI䔝J]ai޷lמdOSLi{m8 *-g&]F}];ś$_ 7.ca|rL& -'g$њCUFJ3 '6fɲ@ ²{`d"a{AUAQN'ihɆ[,]R ڛtb29K1λ8gx4e Њ R,YaęQ̄G(*h@xd*EO4DJ{xdKm7nqMnSD4E7*g3"#K2Ar"83ͼ<d,"'mde}5ԅ9)7g=EmbNgǚ%=g-A\J]{v}RvEohd(( ab||r{3R$A ق jePT K!FqaAVy=Շ&M6θoz=0aã % 8/\P~?ݓj۽6be%u3,l7Tͷ󇨮%Od|y^b{I0tfz"Ta%zx* -`+Z$u1ߥ!Ad=(jRsn U5eG S gBmh(ю+zeUFP_vq( [ItMyRb?!ﮯzː10ܥY"@UZLHb>t(ѣJ5)x!sm|t}7/&nWf.8#gJ)=g~?m$ѳsCN.jB'Lݫ7Y|y/LGۈqdV5a y7u=K_\zwN ,+Y5dUdTBK]fkW105.TP9$dز5,0@ Ath~0 ПX=-#Ո㰻(llUF8(`B 0s̃P =uYul "vɢv(:V` ӅpբzW׋f/#>\+f!63,9ٰ:D ,MUln+_?9r!ZvˋGG!uE)j2,ȵ6"&(@4w6B1ZhL2]2jP #-1DPDsqxvj(Uz7Wǹ/T ZK)eS n#<- tb$uLJX"@Fc2 U(T%=S0ܿ/~UYWɣJNЙ%B*"0`G ! i0ȩ_2hF$ JZ9Si>:wg8XDEu*M&@Nbd`x@A*jRl42v55%\wEU67eDAD[ wꃗW rie:!,y]OA鷫LJH%+ʟpfb:a}/`U4VO9֯?2a_łk=°I/ {''zZ>~Y_ˠ./ (N%5B%!d %`3!EPsQje(UR( f H2A0kEDJi"-_N @X$|”PQ#舻_Vb&5<ݻ2UBX'.fJq̚ZQ"֣I6g+E$F + y^&AN5q<u};@ >;ε(P__}"13NlBg`>bK;ǽ'?\n)k7N~h),.'9bѶzVhQ<'mYbN/}Avo׿qD)b uGJhҢ[]!(*Id }@l`t%PL"E!G <2k3EZֈZ{S2]Hix!@oqt.;6o^" Y-DnZlsqHEq~vC/zy[gUB<]X)p"z<˛^^]-"u0?'/ϗ\GDlˀS9}r{cJּ{Xeۼdhє961ku`-KNV}IQN}^?^Zk|!췾/sqˡN ADEEM|yog*l@G  9zʷѬH+j']8w;Pe1"cYKD-C) SiWiͳk}y{P~7jDR@* _#ҺƱ6Ocwuet7LKI0(EjK`XGDx%>zu{8t~:OϽS?o={o<_1\l$gFC,'{{4"24}ɗ) 1y tqqSEy?zh±׼r <_nα,OzmD紮d~'[_|ыSTךaM__|LX^ňۤm>ߣeIrôs,e7pa6񀜅_9ayBa$(RTgWy7wax y]މ!9D@w0ElD89E|\0.2 Rk1A[d*EG5UTETtb)`xpq0q"b -b$DjtsqiDh%=Z{6sfo@$5p_-]~(hch/*Fo+xeg>}''6LF;ke "XcvNS7?FԻļ]'e.>sSb]?O|?o 6 \>yri(EZ K~ X uW0޹ R`V2bP35j im8 $<:d'wxA v ([LVVb}*h\[ "/NG)ѵ!(K-ZCb:zu}c)}1 P Auͫo^9N|u}Q]y{{5O]@ #(dDH_ A "8pp_s.+ -Fg^*) NU{-M"jd-ko jv*gBYbBT@KT]\>o?_~źڭ, 392Bri|7_yaHb?n {S %+Z:f#eY;A2,sN!9DUHdDp:04Co$/D 0M9,8"76;xp,MsA/m\4R }CdŽ%1&j6WYlf)  z;ƾCUssi8C,.:}܅Keig/kTWyi/̮pDB$Sfb-qx)<kQR2eFcE\wgQfl1_eQvkyQn1R)[FyFZbVinVm[9oQdE0hobl;Ooh}~i1{G8cjD vi`@T,mԬrP$(2Yoم`M3g{YNB¦AN 4X4qNرlI7N(D86ư", \?Y% Bn"V`eueI34+^F_9\͍?::r¥U^u'?s>Z?T4)g%+ -4CxPopj7ҿ*tmqi˟BFL}Ĩ*\Fx7Jy |c([4a,Q@+ Y*9$|{\$ℴ'=#6 IX1D= b v4YHNʼn-0A`e`@'V/R$,6q',`Ln"6|vgAA 2 s=G7IZowgxӝ$wv/J ?^Y,8tddqy X^Ys-]P̖Ewv=ȖkV-]SH}RŪk#wr;?*cr>9~V6l#RquK*[U̿|, fg32p@X[4s El0|64eh`7B–1β %r)m^3U|` &,Y@sj;ȯQ_%/ISb'4  86.a1(`Fbn"_ Hhwϲ IfW7nj-4㷸255q`ť_vTJ귬PN]Ìw+16}Of&hu̕kY×(_bBI>n {n{(dk#TU\7ՀssGU蕷SZG L%P3br Eg.% 7A#B3<1Nw$F3CpLa`_'@Bl&v'IA+D"1G h1fqέvT:!Z4ABF]@B#ෟ\޳T 1>Z2d,I%YK__^kwcb oO\~կTVA3X]-/]+^\k#}Z/[~)>&5bұ[vw];*gMk_k'7R燯cg *۠n;@p,3;Od1F3]6:l6f!"K(. [";8~X?< ADRP10fK,Dc4 ,--D1=Rk=}ZER%0;YzCb>;v)7Wy 6Ea~`|NݛAoPq+ A} !?ggg=ۢ|[a~an,m\Y61sz[# Dl51[sA!FlvKZQ0܊ WdYɋ\D@1@h"fdq^ZMVڌ X!F2Iю0e&@BbBdyw)BX G7 8CZխt;fwLmlvϜ|Bfu94: КmHoNV7vk.b[P-yU5^78fyimGPg/nj1",@YxiYW^@r:nt1l 9cfrtp`hpB2"@dC"1A 4d:wd7ZAdۅbA00 BiK˜!hF!9IB $sDH̼֍׺m$jFl\ ([BNL ,H K*&YX˙[훫^"{(s1"2}sjz}Y]]TVe ^Kga:_ẉ pOC}Lߥ }`Qw+-}J}W<޻17}KEޣ6dW%t?VOX7v|8.LEϠ04hieĘLO 40e͈(jDa!a1RKND!_gdŚ?c~Ot㬗Z ́es,(!b,ef7 YQqd'lxUG `  J^Llvۻ_/NJ,cONMtO<ڣ@qZ64Xv4) z?U\[jZ_;mNekYi_X(!RT-T=|Ov?:g3Ⱦρ2+kď+o~0F(*e#n 4 ytt©Swݵ]0@. Dvrczqdc`P >$`+~b0cIX$݌j5 X?F?:-bZ:ut6 MqX ȌZ2fD },ADdN!$D,u.x%faн)`aBܱ}oWϝ9wQj+M{*ggSa;vRxCq_kRɿE!Ć6.O>y@шkK 9GׂРA_n t͊dMLmQ@,xr1 BޑR"gij{ q6 hDT A n nf۝6[gˆYv+&_! B`eY& 4TN2gȭέv{) cFۧkCGHK];}J}8VZݣra |a\9^.HЄmf4 6[rek[N]!x(-!_$fqp`H) }WC|F%^Q.<"yƗA`{]wE͛KK1Hd2,s5RN"ΙȐP!l&4DNt9rAZCL BqB/a"š,KoK&",@ESq9hy"ȯ"/@+c뤝Ir}eMʩE*II,h\_+A`!ɲ7n;wqZkKʟϛԧ뱿ShBkx* 0UVC;߷5B\Ϛ}_SG- 0蜫k}~pnWGJ,ȁf3S4C kWG֢1DALuPD퍶e}z| 0(o#D>[XqMHF@"l%H XCAdfF7FYbxsNA@aD̔+ď383F阾l0a&THxi"7.]|mqy%oST5*ŚR5U߼-ewa>S~΃:j6jQ-ir#?ʫcɣLKn~9ۛ71QWTUE['wܵczf:9^Kq;'+&́PiHn,-LFa$J3NbpaH8!F@Fؠ!@@!t"L0p nxxa! Avy8X @i pd -vK ]₳`wʯs 0_oRn`KM+ퟌ9 a//oM4]fn~Kpb6Yퟴ s(@rWs Kwk @ |$hF}w 8;q?J띕ȠL KdLľ CՍ΍ɱVkdl!eӍ00F0Y 4P(ȧ8NxFAd˙̡0"q($H"|fl[ڽ&e:|@&x'eٱs2q\zLw~`m\].%< J{SUz_MZ;a,G2ЉhvnNKxE! 6wfv@ʬcvo\~9c YZo#1ȎGAh4!ĎiovV&:='299fE$l4"y" X((M.qIȰu6c BE'],CX9kҏ]%{7;8ZQT '[1]33g^]\ZN9f6=}wÇ/\.r=Lo!D.`嬎"Y EDnvay79206LCbb $Fnݵc[2!b,$"$ b$tĭw{qD MlQl8Sl $K Dth%;IMlvQ<8t,L] a'_.]8ӓS:+PԭkZü2({׵R+l3o羓Wz߷S e7ƻyDT+Eb?Mh@fʛVg>s :nՓ_ (u:"6*La۞P DH& #s.$Z'"`ɱF#$$g% l$k@`;f C3 3!@D(7'"ֲ0ƕ9d̷6:6ڽ6 ȢI"hCC0 D$Ir2޺x[˥BչA/AZ_ZZoJ4NM﹊@0D1"bc?{%@Nu3/[r3Mĵ]oa$~odvv= hu?|a$7nq#0dmks\C@{з_Kh+Bdc 2rzI:Ќap}qihdB QaX^_ۈpPr?[@&1 _ #Bˀuf8ىc,.6,V SDK0N=tKE~sgW֥ _:|uyԺ$_2'e67z,lF?SqΉڭlff2/6X z{mcԉ1X,^j6.FX)"@`%t%\ۼ%|Nf@{,ۦ&xm$]|komqeG>=&j#^W/ocհ; @`x䪒OʭuM]^-q>7!j"x #Oy8r(+1g>~50h8I_z0lGUf'ɺ J40=#/x˽^ݎp(3GYY$i~L:gfb5Df%FƃF@Fls2C75!$fyc&Z7Y\%iz,: )Xgm[ZZ9uJKX }7 Scݓ|4GD(2LcjVWbSy0TÒ_u:+huc9sNZ"ǿ?nE>'?ÿٹzڸwo߸~޽3yl/}knrJ`1'/X~ŇgX""b8X `+"rւ8bgϷ4sIm$f'N *P8w4ѭvޤ,MM"?srOy"晥F('qk_9XGV^QMekŘH(b_ޑgzRnYoFMd ivvVtVRQ1;~.&8ӹ>v?w;/Oށ mn XVW776:.]߳{fϞݻoZv"r9B k ˜mg@D "a kNF2g3`[N ]HĭltVڝN`9j0;>_O=ś7ׄ%&*)ߞxW^=R]…]֩Dc܆yZ+FO;r\(_#mL+ó2 3  켚)*o>٧?[Aw{8'>鳘 ~-?׉844{mx+W^G FǼ|+ֺPJYEmqꆵv`s @~̆X 3 s63Komt*Zs-eׯ}=~kk4Q 255yCKK:|ӥ\~%z||?t^/PkH} 56P`^| `a*Y=H-!-]g }#G(*o.>GϜ9jŽ8Nc''/,|wt|LF:=(o}xkW3:v&NN+ku8pz6;1-BT#̛G{If@Z, Ԯa 3^V"@ÃW޺|^8n3,ۦީm83>ssE4*pk8z̸.Z_׽oL^N&>OJJ!fggUQ:uU+迻|rh;q/~;Ї/Kٽk "ҹ󯝿x%7.4:_;g`ppeeO_"HѮgͬNCNR(:ǜZKnmv 7['}K 14168mlk/]o{cck "Zg$uk+}syY ^r3aΔˢ 0Pe+C8Ќ00$i648tmSpW@߸u٥[{DYNeR:Sj{D*(kMe}'(D9(PZ@豣"*֒~+=ω#e{=8*Y޹ E./]=jD fYl̍08gp3@S4 C̱{I,qR]QRj e rBm20{pzj\,uSgWVWvxT Erk"3JcrTO~A67f}ԫbKD8"KD(l/Q`Ey5sx'O=&ŰG%7W";=^z.^zƽcZiխ߳sj|xE!sK2ێŵN VRˍ/EZp}C,K̹.gd&WPs١!.f} ȾZg} ,{~%/?fggEر (o3O>tĉrЦZk.!=;E{DQ0>:Nu_{˗oqWYk 21 'fG v,+r}e#NnB^:S#ӉԖc~a_٥3.nT_Ejf/>:SkJ I?/T KѓeCQ:[vggUH9r'p'lb䳚EkϿC ^/ܳwW ҃!N1-ow .BYࡻ/>v:ݤzF?Ff٭[Kg_XYY-v%xMX{o}^J鬌[Z8}K/&.ˁ"jO9HjX[Zw feٹ'OHKX2>>?'_yC7K_{ܵ.޳o/m}98KN/1D$߂  q3WΜ%&';v/2 +ρЫ b')Wzd=\0ln*L9S_QP޲o)V!6yhT~!\ys`A=u@:51<<<%I읞 PI+Ϟ=VCor_\J- 0>˽|&߫ ūȩDNvBioѣ@םEG/uȘ_up.Eк83߻̗#WCʣF ICgmزzyw$°VYF*"Yd!!V?,~Z!$?߷P0 ۫wO_ݻv\_\\Z^1!E?hfސ2G:Aš }yTd oӬxhxСws.. X- }y$2(N)z1\'C0Q1(nk+i YḐV# ٗ\8srYں]"Zpjٳ,}&o){:*wރoX<{Z]VmZƟCT `a PSaq+7KDY/WK(*?ּK|.?751eY&˯Ip[ Sy) |4Pj^r=>-*1K}i\^pZ(RέWQPW*n8oKu !" 2o wI`|I]QT Z?V# /?^XXX|ѓ'N n:6R,5S$/E+)"|I~YmoV{v퍋]Y[[ϳXLXnE#nxP [T\fرǵWoy淞gB9xO؇=eKRȐD o1D !B@ff!CݿמZ!naHe3ZΪ2p^6 oS-EMt/2ԣ?3(*obN}=˗.B}y'|8{ǂ[TebQp`yrbl=l@ZZY9ed}%hIDAT` /XDy&޼~~ 37tE>cbPo??cc#&[k|1@|+>T-wsRAs^'Xdey܅ VYe+Io䷲#e勲yf83# ? ӯ(Zݿ׿?|ّBJxΟOo)2r8 k "b|ϧh q/,<ESK "v'='੧ʷU(뜈&s9b_ĸJ䚟\ha]jBoRt_G&VQV/WV@%><7?}}C%ӧ2Yf;DS"8<4V^aSWrC4*>qf~aAX6Z+֪_}_wLMdO=$Jnm ,~ޡK?";}fXVnJ,\qvnNDQ}W. όN}}?rɏ',W[o;8pFe~̅rɿ133֚|ECk9W}Wm'3#Տ0>ǟz8f #CyfNѮGBm,YDk}C8P  ¼껢(ow_|~q! gw Z~E 䀏g.1,8"VD=\FEQѐ>={w>wpp :/|셫r~|]9Qy] MVEDžGf{Cl}W_|>o[2~ksmaaa~~aa~UE[򝗿ojw5,.& _bUnkg Gx~a~WEǝW'~xb| dݷ||_xdUQ9>=cc1{9O/8~yYxD]Q7?ё!$ӍCH(r7~/βlc@EQ~hyֵ%+6}(ܿ س+XnrVf(rw \黡(ʛO|(ʽ(c [(r(+(*(ʽ(r(+(*(ʽ(ʽ(r(+(*(ʽ(r(+(*(ʽ(ʽ(r(+(*(ʽ(r(+(*(ʽ(ʽ(r(+(*(ʽ(r(+(*(ʽ(ʽ(r(+(*(ʽ(r(+(*(ʽ(ʽ(r(+(*(ʽ(r(+(*(ʽ(ʽ(r(+(*(ʽ(r(+(*(ʽ(ʽ(r(+(*(ʽ(r(+(*(ʽ(ʽ(r(+(*(ʽ(r(+(*(ʽ(ʽ(r(+(*(_j""(矊<'/gg>/ * _%?O~ϵ4)'MI{"X@EsfzdDz<.y@eLBFf &a HhҔS-ժ) 5?<(⎬wSV" w1"fQQu3DӔ׵ 81 jژ DG?D~pDt{u"KYi")0{\"9q)%78ݗq91mn͢"0""ak$h(-$k k(T[C %Pښ8vFU#$ kF&da rBD+$Q0 R@rN"IJNu-໾]~;wDWwn9DDVp 1gU*)OjGL ABPyYBK0p '@|u1kDú;Ƈя kAD!aZjĚXy=VMD52qZJC攘Z{OG?я'4K]Z p !Dz8ReIS֔$ebfmϙ^fN$UpwwRL9L %R A 8 BDB-yp%?|rZ,"w pYln,Fri9eMRkڪWO?=#|gRnmJBJDLNLEץxsQmk 0Nܖ DI$!bpebaV)OE !PXXM`Df7wsTRbB?zDyo H~o!fpG3w`pfN"JjšH eA3tTyIHH TK)Kvltp$e"00fdxZ̚w&X8"E5 3kͼid'[\RZ-ZETT[433wÍV^RެԂ2唒2ӽW[Dw^^.fa~,_eYo4%MYd]r:Iw42&0IG&8RR[xiL`fi"<¢k~tNNo~3ࡇT套fÛx 眓 7_+/ҋ/riҿz0@D$DD(QլZ[ &R䔲jV!017԰o'˟ׄ3O-tqu ,fJbe&f"f"M*BDK#WLP֚rٷjy%4ef0wk5mL7 ` JL`&ia:O_d\]'}|_LӔ_?coxPAoVqjfNp?#z &RI&Ubп pfn-V .xm8JG8⧌?/|R2ú3+"眦5333!")+3܃$}fJLa423yzZ˩єK1ã#V=z67C-5gUea:eX2PNw\]_9?C/s?ru~[DoU~ vW^}ݻt5&BPdBkb4ZZ+1()%֜Ҕ5'}p:BDa羗O}̄x];Ҕ4o|cvٗeMӴmRJX#YZ=}133< ffkYJ99b+J'Ģ"VZId$Z!8jY0d9t^ _bts4%U)'wozwoKwKi/,y}e=uY4~k_#@J#˲EvZN e(PeONw} _V[+I~G~d WG>ܝ"y"~O,v'WL"r0/f^[@`:ee)u3f+9hx\="1 ҳ&Lt274o./v,eY C~5uCfp:o'_'1xw4w+In?h*zlWISYhnRf79Lr/'`@r1 qAXvw#iVV{>2o˺ֵhB`uR4sΙH0k/r/\z׽᛾4 i]W_UVNSNfWEExo}[7O^weY 0u-iyLA_.F9I"xy3D A O=Q2ꎇ'"RvwMLi܉EU0p[MH(HjnvRcުo53 B)ɤXZ"2猠c4fVrlw+Ƶ?ymy1o\N!P8djZ{WA8>{ӿ׻f̛};^}>;u:Mɉ.S)kk@`x*< 1H<+'x ><"813}EqG*e=yy90ìWXk6yfw'A[$ebvsj#t<"OO쳆)'MBZNa(Rk)Xn,ܹbdM)e)a_j! ,nܹ\J]OKYW=7г>7K^=U( .I$Uє57+vD̉ wfC{76LDq 7[E2K7$q7>y@̛u-":oyά)i"N Œ)AILMg"›זӒ)Os+pO9%e&7pIʀ"%h)BI3L2ME[Մ[Ҫ1EKv{wu*~yu9,JB̚=jm[kemTbw*Zܭ_ 4,IKm{j{ʹüH+I̬)1܀G fJªss+N8{--0FAսVë>1nz'톙y6TIƂO!(QTC[Sb b^0{~k6vSTzsSVQ,4SRJS,lޚU&Ju@&͚&XR8}QerVj-MnvSN*)M-L!+}^Tp:-'x0&S5-}>fne x?Ǫ*`ּ&kN"ÃP+`"e]DcA wDX` 1_yoWsS-Zk]Raa w8$ni-,8EA <|Y딧4V˽w4p7$-dU9%M&o氀eA.$z=G, Jmx39Gb_夵CT IDATR4Rt<|x_+fŲhʓ-Džy֔$ X(UXe٘EX-`J9e@,d-eOiJ{To0ꭗ%5 Lj͍Q`iZSmm{yIU̓df$4yil5o "ЄuswV*\^]_^]b{kӉT$(`܋*/ɋv{qyyZDh}H011SQIle90Mf'~ G@~IT5H+լ2AT# fffFe)OIf\^Zq XYSWp:X8sNe]˼n/vbVz=>ؾ,K-s4)@==C ꭵVEaZJ),DL,IзsGwIhvIED 3Yshnͼ'?xSpTD +;WڏփE,.>ߺsK?1 @DS!>2zm19ޚci3HT 7Y= ANHnԶPV:p4NV{jVg7?-˩JAfE_N "KWw^ksnnR9yc˲,˺]\^lۜRtw(:pxO8)(܍XZi4O na}pGrx:R[ŹMW\o LRY8 }U߲.uY@Dxʙ晙.n߾}[Dnݾګ+/ bsu*,B5", 2 $8JY˺V4m<%""Է!{oC4UD?!%e@UZu] \K֦)G`YXKb*וÉ Y΃""xLLLBДu_NphkE@UZNb9O@|{p@@AA8L`XskX3k?&[RIϋ:]uWa)9p8,%LYES瀭,k%MYR9[kw=2:#ozc_[l/=Y /QR}#J)tXH܊oψ'$=GkV*ݬүHT5KD"b&vQK<aviMSV9|]\ڼIw_[jK4%D2 #4yk"ՅunRjmt9D@S9Nk9y3o/.6Y'FID5<(%[bKR_F8)p vhyG×"$Y5kҨ{x,ufUU$b]DJ ͪI@9%ep wkwßyA4)Of u)/Ҽ~AkjI7DYZciy0{XYD3+*afF J,,-Dᰖݹu+%!¢D䤬$ZA}U?O~YJDegz:i7O̒Ty%6ޚLi[tIY&7f-p&|-E&'Mw$VZzUܻrZ$ID~d !IyۈfByw7xB_hc?ǾP9fH!ALj͛_ѮwwwHFjn֚3ͻ]7s5@쭟6sO"2%f lYl M<"(IF" Fc"fa%$9D`̩q&Xq=<,<\=\ ?>eYQ$qfU+D'Β\#߸}pnx|/">?ϒ{vx8:Fx~|x(SyյZ2 `c/yhi*u.oۺ=w{M`'Z" 1|VvAB~D@fB׶KxM<{:ΐ9%BD17.!™%4MDZzYe a)$Ԭjn)̻i7\"!]]mdꨋ1 zbpTpa1qUS#K3L D|] 1G@o ӻÞRk Yːb\ ׿?oٹooW/׷rREH!Wٻm#p"&v BBu@$4n7۲<<<+s9 (?7mw|ӿ{. نǗ]{&dIڔSD [ rp9K$AnW!oW鬦Rvե׵yTVEa@^)@@00#`;ÕYe ڎo=LS%j_. @X[DLdp&eCp a@D$Bn` `DLdnlm6v>/~WDo_;9}w]7A0iS)OjmIҼIJ"HknT" !4"r*Fb.ԽwspojqeLRDde7/^y$fFF+zM˟wFclő0{WsE.Q:H.#޶e4K*Q p"h^7ƪhFc' @Df!ӟ|K߾x|k_owgk9IN$ ,{UԺͻ[P$<o޼pw,S)DO{www$C*9eFLU0"aא ? "0!\̀^u뺞N%Kw7S 'IW'0u]ejWq ,ͬwCL$5V|7OeB``D \ k]  #Ygsfkv:evZDwweNq?AxJ22G EKЮ-DD:NL i5YHRjk.(aryxk&"GZOnˊn׾<Z4MeD!|9lլ5,V V%"dD DB o1?ĜIio"K*XR^[_)n&FHZz_ w7'ܺΥDnL9q2s hkN?V"e[uk$%CZW@  S*nmࣀ#Ln޶NDHN5p"]D$Rk=a$ya"V~vÓ{u]yADHHS$gE@)4ֿ8^(؇I$ clA4_ ΄eW}XҺ,@4s@g$ Fbii݅ua"d \XMػuUJ[kinfk?eEU7#"aAb̈"|~nEGoj$%I\ X,vxr?wx9wfm]ݬa&2b@߈!8"u&ӔF11C8p"wwEaF$av~^VRy?}!dn1,Ȉ[J^rdcwDģe #Tp FXDB48%aP;@0׮oRÁ`$1"ix7kLӓ'w` :%C@)^e p B&ܶ<.g& 0ws}jS"2R26ՔMJw>Wn|w>Tç<G*|9w;S\ 91%rfa o>) dLBځ'!EcP! @D/?gn|Aw$F[]O8 k#$9Mˆ1ɓL"tko~{o! 6<̴mm :; \ƈh g@lAĒssC`u\R*汇 P0B= 9sn8 #]LDNH9'څv90Dm=.e䡎~=SZ/ӫ/e @LӴ1{pwGu>,Z6o3ݝzRш)MrӼDT{k]KJ-kr)Irba@$I)31EZ>1[{G>j AYR!t^v,i.vd5?/6aFƔ}C\.RrJ[NE528,LDąȉM,1K_$>ƈ#DskuPJ\£զmmuR8$ 3D0'0E y.Ag&B!BFa"bq7͏€)|gFÙ߼N<={9V{HvIP4C/< @=Rm~:4M9BAݕ@51 `i=}dx<@@Na+9CGFִ Q'IKx2{Qod_m<%quJ@'N/blp#Y(̌'bҺiozTJ4f1"RJœ1"9^Jqmm1 R@A'|] R)a~z<<2_By⽮=y۴EDkM?yrHBu]NNYB?N7?t VrFbJ a.sݓ;Q_#"P{)ww'{I [`q6`BJBL`rA5-!@}) nh9{"[GL9-},\Cu j׭u;Wd!aa%,j[W ,tQrts73ӆH4V"ˆ4981qF<4SD+y i]KJ"Cü>2G+ 2r['FV$$$ ~ o&U?}t#O}z̔#̴ֺ_B0zy.umswDdu%& g_/M78!^Nm"y%9b$WDX(CHLm8wO0 (l"pֺ!q$[l}yw<޶]9Hnmf!S꽇234MeMMOs) $LeY|[7NDjl92%ϥW$I ebU}xZ['B"fn7}HF/ wES'fPh0UĈ nyB?1X2aOgCݵuݪ0B h90SmxV;3nU/U5DP['$)y\Ą?x74nՏjIr)S.B#>{#fdW/^,BD<So@oz5s$%\YHz4F@pʩ0?<>]=i(c$zuY3la?s)e*sIDVm,x>"\2$Ny7 1"1- }OQnuTBgwD85\KJNEry@{?ykS4y x@R&D BSk(TJDS7k4"A3bŤ p #FYլoբ* '[wDHRQR9!f" {/~ 4M<.lT Y۶2ʔ0"0F@|G;&[s4M8$%wCmY^~b[?ɺ{e$]pNdb # hrNs&"F<Dpw0їL\ֺM\ ȃAE6 yg"Gm9[m)wh "$$ B,[̇Yx<<6@drPA"Hc8N}m%!r^X8q/E$ #(!v|x:U59>\@mrRr G)bY"X/u7?]{]7Zdz?}R ۶[%[c>ܗ([R'Ddf$}wv755 -imt6ϥ%0 qsw#b8OF$9'smp$80!gHf JRH !AluAd.][jBHDr~ļMTJ%1_/4K৥T[ ]XS^w#AAfm r+`H*]9pJlZ]v?!p;43aZ^N穔'$ԷsڻKn*S$.l޵z"n4ƌc+Prp𪫵.)35?yL$|<`mK4Tt:`b)"YZoi,}iO,Z913zu@7L$p"psm !EH8y3d'G۷ 1#\DLáoLƹGը]fڙB}~B]6#$6'ezk*23C0z@V>:Iٳm- =CX,<~/EBnˊoҿpT.%ABaGEtz>[ek[n721m 9v/,ĉEܚ]ϼͻ65 dR2 X벮T}< Qx4a%S!G">zXX@ $an ,a[WU3"$o_!œ#2Z"R:v3-%IY e[7)1 ~|D!&$%!) 'fƄ(N r9;LSeIe?5R޷zz31o+@p qbWDAAA"2͗-(~6o|$J9K(Av#a^GelM%OEʌ"aHHhX'tm܌sJQ5&VSNyBD,"9'I &dXm[7F&>;D^ a}k5,p]4ՔdsUdF#N'˺?}ׯ_?~?yBX32w@b&w3w«aĬ.0[x2p^+5LȚϧc!1@3"cPDs 9 [312J`@XVS_PgIN%oˊ~o}{=3KSi?"|^x3ҭj"6SݪJJidN){7 Gd da`Ddǁu=/HXd[뇟7y"df6% RI~7SĘq,yFHueHD0OyPtLL%/_~6'?Čw>xN4PwY D'-FO* !88'B#D- ,JDD3dө^.!Hz!0fDks$R e0{DTRt;.6POu]+s@] !:jӾLJ[w=vu.@B4L(FD0H",l$30璿~gaqʻ __5fw"M(M]Wϟ/H)z:uH"ft?LH0La,f1< PϏ*"ikPk{O:@S֮  rY/mIpEx,̗9ޥY,zk眼VV)l,BL` p8 D`LwK]ꖄA0 #[-eKHn/UYUy򼷽Zυ:gZ58Uk?xfn\Yh$SU2@@B޺P@#==p --#!G.<ҙGIy]._ܚ׳0ݻEGlEx!1 Oibfw- KJ@{-<\ޚyu~puݺ}h dB`%gdQd hu֭3"Ĥ_폊dWyq0ޫlryܘY7@_]I6"l$$C:0x Lݣn>x /_޾~b.g.3zo y<2STFD!^eݮn* bb"Pz.HHw\y^.뫗斀RN*`"BD{ ϱL,F@!@f9>$BL@Brb䄂̬oMT2=^AB_ۏ_"e-|mC$"$L~ޚ BWG:H nr^ݭf@G,{)\FR#!H",#! Qx CbdڷnO |{?{{;Q/_#BD ZJY1s[.(>|ܬy#ԹI* d@ʰ DL @B IBmi)SM3;OCx~wUjpw,E53^wEE7Fmhxw{}8#0#"0sQTX 1$Ev\o_ޞHi7=3{D-Q30GļG@b2 R2""1<|_LS )/?S?7+aih([^nk&_]ZO"10"2 #dD !U5<#Dak!{vwFD u[ϗn?M^_$ΧuUH7ڒڱRD ;LG6(Zqx;/me@n/ڑ@u&͂X ?GL$,hò-E$- !##|`4ёh,# @UK)twN$%smto|{xtf Z2Ay IDATXDᑈ )lia~Hiw -#G0 @*DĀa11DL\ݺJʰhkQĭE[:[_>(*c?)eZ 3ߧ3t#r~fbgB*ZR*#!p#@m; F.}KLV$$CyBZ1,aڴ_xEį^zaD,v<ʠ-2zȶx>{@̓:O9AHDČ@@3( E@ ;1&&zkR2c4Q(qw)2{/7~[?O$/ۃGs~o_=3Ane@#+&*ZKfj@2 I 2(-2#Ǖכm[zsg&aͻI* -aӭ+AQQfu;O4MWWy" [0##!_xmer}YW"r>pOU=x)P,h@{@06#F$*L2ba Ӣ@ L\*Lw3<#Z#;$0ue?Ϟnn-ͣ[/?_Nڲ,W7OW}ݺ[~f6AbeJLI6c$7PGF A"#Hh[7 ?_}wo ?[>M|uBIx>ޝ'fy=ZLsYw[_3) P@Hd˺uϵjr*RdR$Ga !0{eٺ9 s-H73k\툉КYPkc"+Laq<]noٲ.`$nP5&b&ej¸TbAČ VxwwOFLb_ݼ۲lZ0pLN:3 ?xxsϞ|ORAwn={$(bkv9_}bmkɣGOgrfBDH G$g"`@ 7"Rdd 13#cWsv&T">{o?Gr:=yu2sGU^O;{_jXe\i&uY 2Ff,12Z'Eڲr}vjy{8 NsEb@LĵoݜTT<6#Z΅euk P((QF2Tvݲ(KQDĶnzRLJAm p@"ܿ#bx2 & 3ȱϊLB^vk$U!kQQ:$\/]-"1}LHmA/G}k/?~~< <ݼ {b5eEpLʄ^ˀqpPF("&$ R^V@QW;/_73vw5:#۴11"!af%b  ַ΢\#۶x0 Q`3G )Xk=y$DF@DtYGu[Oò&P;hE!vY4H\DD %3U e̢TFđH%QkHLR r7k*OamHU%3FCQGEJL ֈ{{@3^ 31){mc:O-+zBfFȈi[" #󤪂_ٟa??n{̫ݞ#z$gA F͌@ą :@BRGA39ӌX(%<mkb&;#Wշy櫿̼N38 Fv\N(u¶ 2 dԻe3B"EdƬZP@"D&\ۃGOzv9T4B"i4wZKUD$S'!Fb${ghkQEH $ #oyxlDJYclr&$nm] 4m0YY3c0ep ?[g""9@ݦi9!0-3}E{{i7#QzӐ@!@¶DZ~4 O41_ݟɿL-"WmHNHGR{PabGHpo8QaF¿Ȗ`BfC gݱ'&RJ{QAo|Gn~W$[ujAĄ|su\<Ӥ"Ӥّ9x{[lIJjݏw^y> l [[wiD 2{Z6b>%f5: :lI "bdR" 3oaU`a_)a!EZBZ33@d K)ALD#@|TN(hnx/dMHPHZ#zZm 뤘@Bo6rq5_׼ B4l93 Bx:Tv6z]D$Z4BBDbFxt @H dwW|nOO_7Y%2) s9 *3mQěDAhfn[zIHL4 "(,o"½4Kի?h;l5ںλBBȄ @$S 0s)Z6[@󸤓13RUjv>zL$d̤mTH"@0&Lkʽ:ׄ@$<9b|9WnщzHDFnN:ktKv h #NL@BލT}4"G`(;@jJbf̬ӯK?+oC?_~+_!t",9g.#n^-#I IfތG&1K.4_>}}>N~  RltHRDL@Ha$LpDFLb4CLq?p*"$o6mGhw{wtRK'QE`Bfzt+@w7_@DHED@Zm=8"D8!Q:ZEy\,o??9($:^މp{ED 10(DU)m[ܷ;h1wӲŧ>z飧{a.Uy7TwwXR2 yh1A"rcn 320#03x:>nvY÷mNUUD}~ٚXjIK;'["0YLT%Χ׻n>~n~ ܹ0%8J,n8$ G&d됁1>RrD8q,!fnRt@?x{;~;?+|ɻpV%-q=rɓGR[D*9fuؒXD:{BJDȤDh"ޖ+ylÏ߽|A?N0s]Zy7gwDHDh̀.)+봛/xnveO*6v:̓FBFl Lw ȀdHcw@F"bD0DȄDѓz(SONZԺ 9"io.+K_>3sIsv|0I-ǻt[[?NUݼH b AKܻYO5!2"bKHSUe L1u[' ݞ0iѵ @ Áa2UUH`p%z[xgxrNOˎȸEDwt&&ֹ B[p+j_*MUE"|V aբHZ2 Dzwe!y|wl։Dܚ"uK-ܷOb~ܣWD{bw*eٺS|~vaurSz yyp ZA}[NGUD"Q'`pdLpHBNH2`Je $t 3 {bPQHRz[{RTT7͟?8~;?d?_5R՝p'O" q9W۲EuuO>etoq> 0%$A  ,y(< 0x8~񧇗wiQ@rY#b$4Q)€ {x Y0#23 1][f4MTҬB,,LLz:1jsP`c"ZzoU(`A u@EJO=zm𲛵:M}[A(-kf-t NB&Ee?)R'tܶҶWӻ>~/k3R'HWxλ""a*$:%!!a&`xB P 'ceBLLt$a$`HaLpD vLBDSm:Iԯ~_<'NC>Ȼ S 攅EpU9&AF% ID"qŷά?z3o7y_i &2+ CBTD[@NQ}c ǫ/mCpyϞB(Bju[DXkUULޡ UP0wn)Z{q)T @Q"BȥGbka᪅KBH!&R#B7c$Ew޻m^<}DY7e%V bҕ8=˼9} !@RRЂ2ڶ|<Q&.¥ @ެλ2UZ/mYU432B٦* $b*3<̼/u@Hq9/p|uͮ|GZkD@H?p(a6:%6@д#bbF pO!#eO@B̡42.'"}O?.m][;O$3UK Hu4_s[{ )mD fRX ^C\Ib&;,N2k0lfd&D2 Sn`~9}VwteٳOxL߶,n;Մ; %`fAL$KfLbԩr0X@m]ID tv;&Bol[11 G2s.ތCZW$B%BD7nw/_ǻv_???,n6Mi\N˲뤽)! E T2 s|̌$>;;1p312IEH1Q7D;27 L=oN`&Fg mmuX[b@[J)} h_~|n˲.[.PkRt2G qZ|rZX0·E*:hU5# "n>YZjBy[[۶Ny";x$nq7~;-7{$i.j I$n,I%,R%?2$FCzE5"Yu7ڟm]H_xd:ܽRj D!$XF8&2S8ޖ~;Oi*O^z[}痥wZypS)"=<,7W73튌>ȟ2=x-0!!B 8O컟~r+ Wicw/붍+=-EXH8#& a Qл`$f %g#(9" 1jLDL$""3ì3+kMپls/\m6g. @ݣ|6fR51-=<#2}떞̢ @ɌuJ)Dl'?r<Ѭ_?O2dJT2Gz7 IDAT eG@W;"NaD"UIȾ57f""OF&L o- xm۶DkRi֪<6g Zh -c"ObzYT `1D&$[d*ZGhS牋. 9€,k-#1x"YKnu)ciޭں}ᙇp8d;|׏!r\K-iL*(KT2S pRB꽱{,EX˺^F-)2V.ݻ5=}ٴvi}ቅ!3[kx)DH$j@`a :Y3-T*$bR *Rm0HȶqONfDDA֭$U3#̷m;ܕZݎ0I%ah乷5@D <<̛ $$z,HJ=p Dºo/̄:D$1siF21l\%"}\/ [Ynq)*"(Ӵ~^xʥV!0DHHL$@YT73OdNHb0LQi\Η HKgf·S,L, ж6:W֡A"ZB}و#3OC {ŶlrDXp:'ayghe%lzc̈w,;Zk}wk_-0,QJ#= k!DI ̈ $ER%J]]]U^k v1lM;?>lg=#WzkLL3)1sb"!^},VvBB"@5]L- fݞ6jf歫R9%SMCƈpM[j4Al&qx<N\_N&DBKuM9sf _qʵ6. C!b_7-1< %%Pm^)$Dф )" Z`23-K&%o3Swtk][mj& %7zxywzܜP fn/ߊo?w "@R@&jnj8Ȑ`;#@ 0p7lB譯U+$ fnn[o^I$I"-is~~Kvܿ1jwWj) ikq>;pJ@̄Xͣ iQU; `_8 RbI־2N/>{&KNv:)#23R0CcIH2w@A"& 0 N*(їjwkޛ0lJ̭uSP3FR1 ȂY$ 4hN !DVOD~yqh;읠j KG]2N\)^vj9po=O7M,PR SAZuC=L:@E%($M;{퍘SIjoMggÐ;*X2 "a$$Q,j=X4%k+fw;(t0rk 4>|X&aӸA &D,ŻwGgӪ2) wϵݩLsfYx>Q H!HAܪ@7c` ՗nMFr &RwL|s7#"'o}o }3s ^ Юf_rJ +p@&J BsJC9I! 4Sa,y(fmplrNtfuϟW7yR$aRVr5j74 ӲHϯ/6۳ʩ$`"w DYD@Dw7u LHk$ a jm Җ67 z㋫ ~"m^\81rJBL T:#1YלXwU@#"wL+I%!{'~cD5)C+^1pRUsmj#D$@ a ADIi^*՞9{{<̏~cI]b@B(茹m@)!s"p"'X! ԖMbKș@ܴWd1'5uծ{k ơbd"rCw;6i;b tᰴ*IrSn%)lMy7 t ЙW?2 P+aѝP!R],rFWIzNyga,;B."B0yF `f@&&4S@^;H..ؐ2%wÇW??|^K>y!wNsESN{0d$cW%$ 2/ys6`jH8B118Xw b$3sD$DH25p317<̣B)PV";:L*!'o_|wM "d.ybFJ=l63F$Tޘ0zR2CDxifժde\a>۝2Lc#" rHA2N2 ` bx20My(na+JbQFwg`# 2zw3JJL$'Kr f"d (CAdv-D%]Wͷ~;@|sS9{mh͹N͸( <"jk"#vHj@D< _ X6ܛUHQ1QspwN,9wF'OmgS@,<'s{~<7/oh1ݴGY>zp"lnMug ={u[ޖF"+ٍ1၈8$H81WµQȇݡWqa}x"eAt 3Iڜo`e!@@+A('IȫzYxx0VsN׽.92 + hc,+ 4 u aL0u0BBByHĴ}",_` }Mi8Duk׎H,r$\# c#<" "X8 )Ԕ &) !8e@tB$ 6$E $dvțYo&(D3#fF# 'f)3MjDH&)je,Gbko< KrY@TH*Ñ(0YQIJX #`j0ѱV,:s*p}]s* :%${3aVT`*S?֥uFҪe39@b@etʙ!5gTrz_R>G?/07ųO<Wsj"ntO9<^0A+LiDpw߬JLaffpX%,,LB![d5FP`@w~p2딹&ȼ3?ŋׯ^Lc@ikqUEH$3 w qde^IN4\A{$D7DX_pF$BPZ[o '˲!#"+!rϟ?ID\^_p~u!9hm9OHRN9B¸"0iX&9ןZC`F7Ga1fyW|dsqH0$$A,TB I@Xq:wWwyJH:쏆A9Mۭ9Dn? v dJ0Iڻv4K;(Jv4S{9˃D)9zi$,j!唤x v3Y T$  2W+vv|eGHv֔Tg-.aNAΜy< x~ۢO]];7DF@ݘ|`{WEo BU =s)m?yɸCdmɉỲ܃"2ii{vf^[OI(X@6_/G??"awwO]]__=|e,fZk˲̇Y䡌D؜€a/oNuI,DOkQ!xvnޛ汌!(ܕO IDAT#2ZDvm}c؏v8wGTv2;:tf=w7wfsq}䡤K$ Z!L)pnv~//n^ Z]nonp"@a Q[mq DO%S@b~;i)|Ai3q1"4Px BjUW;]jUu{ԝ%(!n޽. ̈ԹIR~~wNn2e<۰{ 9fm" )LD=t>E )HTXXnfHE[r<̈́=zawEw^/n>|:7^in=yؤMAD͝B aED$J%As8pj 1 0-azӇgcʇOnvO?.SZ{Zݫw?x8- !5TA88):58V^4$h&x뭭QPD H=V* GX;q; 95of57#a 10#V܆䁁d>. 2 "UA < a5t` !LT͔L_G۫w5DZҲdB 7c͍i"ArBAp!qɡul7AѨ[ fBd`Žj8E_!-m{6~K흈荓 cU /wJO_ }8CFpRfusbwe8T֑:blT{ФO?~`9-uYn_:8㇩4Mk05-͘ qb p BD * AYX]|[>|/ɻןuV1&xXQU$OYID$Ek`v}>Dm;/ӗu>ƣGOB-B"8$)g"V{Do~="ݱ$0u ,4(3r +z7H)*h[H8%f) I$pĵPy}>05$pBYAA1փMYƒHH-C9"3s""!;_"S|֚%qbq!"9tui'$8rb"Dz7LhzW7$bxSDnBkm|8erv~'/J.O$Ok b<781 E GxWțYΧm,g ީَe[z7fεf3"k9F+. ؖL,@fEsxIf=nW{Q}N[_[o<,:MFI7}_{0eSz_j!&,(4IP=[s"NPUa[_鳛g7_i͇/ſ_?_%o>|:GJ"RJM8Z6XG@UGH"I$&A* kac #J`mYT:<2|EiM![T[- s`JKp$Q(y`VdbHm I!( }mn!$,Jw_\NG\O}?Oi/_hʦ3j_Oxb=/ '/_|9fTO@uo2Px% OeM]LZbɌ1bDW FXYHUQkZkB03PĞ}糇y:\_nɇ|833qf8{g"| NsoWn]( /xp !}'j0f)er$&#GZoIAA>`% )aRAHOʨ$GDQ[:i$悂yYǮN}A IYgaEf2 |nz  `0PRJKRIӝ3,Z @}?tNjIrF2Ivz8iÞ )$24BHʤ$GhxyWh:"bFC[.VS|ufNSy 1÷RZlMs77~ŗ_˹>ld7oTU 6̘7Rrcc YX!Uo//vcn*BLf-\(Sd 2I9~ހEuAedz{Exj} xp;1tӺ.A,~w?/_=W`{@\/Zu c:$@m:LFzn{ JPFPF.S>^-|3Ͷw*ۇ3@a=1 3CIR "$#A`p*dRJ’ $2YdI@؆IQ*3gg?ӏb~Zq}}6T50崔:Ce'PDNBaޗ1]L2pahXOj񨢥L;d멯-bZJ-o_0g=׿>|1֥e}֢UٳŶAwJy$H V.tᆲgj Nڣ~:npEF͈ʳD.&eR .+#2͉ E(aDZjWQ*S Z_yLӛWo[ږ㻭W{هLJ3"_}ӯ[Dr՗o2*e~ǻM^שÉAB\pwRpd"sؐ:ImLmD H,A d8#@d8l6܆[S4l3lW"0FkݓO>}W㹍{11Bh7OR4R9\lc6~pA$JAМpO`Y0u60HʀK&jHKU=?f|]?yjIĴ%E$9"y9qYW>}\Nc8jBZ_h`%"5$*,a,ID)ऍ=@d27I2Ls%FNtY4"$1K[y#e?wQo~ f&dJQ dB&6_cP266?.L[{5"R*΀$Gs8YoJDĤ>i]prT0?_~'s{"=3,ncòh!X"t$EU.\y)s~ 32bC"@觇nDppO|^t*ˋ=_7ׯ_pzÐt<>X͓+Q%fuQjTɉnKdV)tV0#{x ynR)TJ8,ƈ Br-5)@dJ4'ጄ0nj#~Wa$,|?󲿺Te$qjNea9bOm7om;%< IDATy<[XR,,(J:G s'"qrݚ8r*afl}؉ǰ"*wT c;Ŷ lXID@ǰs_ׅO>},q>@_W2ivaR'RH Db{R(,w|"PS:$3sorlBJZ"#sXF?mR~yU3- fpҖ&%82IFPq&D:"& %DLDN= 2Hdj )[tW/;..Rr= $\"ݳ6//\j9 O B Yxֳ$6޼}K*SE.32|ۛWn,Bo|?FOd Z&('E^hb-HN hQ.BD[68==d$%A Hp&`Ù)=$7wRy^ki9ES([q˃B$IL0 *+Ѧ$(`[V#(<6>H9<9#":l[f{fHw{ T#EK@Cr>/24KC&UAF(nJ1=|R:n` :$2v>p6af7MD+3"_zUzu{Y.sz1W|;ŰNާmwyxo뺬鍰lRtuW,Ay즌*BL"GRU/R425eSHJKa3։LQ:ӄ;sTLWӮ>|8Kv1ڰQn+ jkk*B|VX?U\f) K@>,i$d%?sQ7H&Rtx{UgE81o}$(A…P(;Xuٓ~8܂1Gp&*"2ܘQxjs1ĶA}#Ŧs($Iəm")ec) o$!G qo#7ca-O?(V|]G`V+^wemkfe7 hG,-7A\*2Z;V/pZp{;tz{u'/ԢO<9\\DDThƖƍlK_̚u>|:/͓f-ݦisja o1€PJY@Zt6V"j/ðIBHBZfKyXBq]zRjNS)͇*y7q n"=q:Y ڱg?TKMϻu-SFᾌϾyv4k% ݻ7<9nl0ѝU+@aDUӈE2J&:\i9"DDVRl8q2GDF̡:̼r2M`hæn 4F$…FkE(iK:>y]zPfZwU%0'Bo=-Tg  B6Ùlm40 K#ȄYtLYW &҇ƲC;e%=|--Gs8u_[f`" Q9<Vee]jkWݴJzޢR(21H(82"]T FT[1UkaHdR {)B%K P ,aLޜp&f-nm6D4G&2#H(-U$[XvE"l0`LA92"2a>TBB|\}o~rFkTI$o"pi97.$g$2U%­9xj'7F1`HAI|jx_Xyk..{ "b$˟=Vuz˵gdpuw34ZޓQJRwÆd(BR AGf˞ L} N(0FDy,n?kUMBݛwi%t/9 v[!Շ6 ٰY_=ww0bq}-e{d$X(p0)I8ř|l'{&NU'=)Q>6yą2m fv >%")UTUJo-3X#F~.Q2[%=I&!Ȥdwa殮P2̌yC'[alD"="*%HE܌[v〘9"@7D??G)+E4Z2M'}ϻ]*+gwD2R 9,C"0G)z:.P0~53Kr^yʲw_Mo=3E-5Zssp͛e]͌wwy7,E:L"2o}2M5HeZp.Rj5jKr Z3džr &&![6fBpo<\J$eoQZ&a@_+vӮܝA}a#YCBϧׯ_]+G&N"sO[gPb #("fN8Di#3"! !AHN  "ṞIfIa"Ni)u$  0%e8El($ ;'1h@&BsOGXY}' d = $&$d+B)U̼#ƒYXǿ?¿}oy"i.rQ \yFm+甉f%t ڎO㺞u*r"PZO1v}і>"-9J̔u*/^g>H'-"B$rp̫/ߞνeW\e͇Ӻ62N3Zv3Xt:=<<&0N2# Z*Y&RI4<#cX3>ܴHf> (\U޶AeRRñNbav)[[[k`VYJp<|Pbf6`QRnp@Fr3ܓU9 ᙞpRFncAL`pbNGj=lڅŀ @*s%M*0ZYcAlZ V۩MEĶ ReKNhD_}$y/_k_Fr>pyu%**H:uuQ-eKkRNa1Eޖ-; HDa[ 1"PX<3絨Hl4 o?g~ - 67t-*"kktޔS)&-Ɉtp"Ĭ"B 0H&ualQb!PDPʖ Z8"NpP&pR)3D6e3õ$r3Ħ""Rl32|oAV-[_ϧ e涬ÁYZkeGDFDLO pt~&pN)FH $ N txrDK)g.2WN&`I3K!x,0Jjk V-y윋pbxL:=~|#2rMpqyg/_h{fOѕh6̆H}[k-T$@\u}=uX2h'L%p hG0+nL/C0jQ$K*yBz鱗w;޺ceB󮺊T$7)ZIo^kτeYUcW|v_7o뺚)B*Wp(SBt>¬5=Jz4M}UDU=SU["J1HdڏERG}Y})%\Hw7Rܛ [nmF@*iE|H܃ I}xnuDBBxShi$d ,)ii"J d"n#Yj6 aL-= L PzFN1Dek ޛZeYc}k=y)w $]?OW:$ \,(JvaAyksr1*!.ɒ ر}7j6?2r6kņ-*S CA*RP)jaffb(S'VV3 "#fȠg2Oj[߿Gei =CuY#7ouAElï=nJ*>r9Z 'iaFfa%X$ i**&, d-߂ b@}ۥM!{y_Pd!Jk8;=6yjVcʒ M}_n>{VTeR=,!Eɤ1;=9Hܺ!V%UQD0!*#DJDdFLƘYeYoi$I%=><ίoܐӓSFfRzFIQ]ף!cJjBF,j"P*NU}칉}޴,Ktok{ /Dt6- S2 g(|o<<|xMgDxlmקϞR!0aUESLҬs@j;P#-w+K I )U#%ܯ>F]k,+D+yxW]!O?VV tbXfŠgLnVah##b$޻UmˢeZ,/i~cs+fQJ)%<3[-˲dFdtǻtzR4G1bd(UEfΏ}EEWy&Z})pBM@f1= T3pPR1@'!0F4Vl1(ӑiAd-yKIQFb33E! ˒TbNRFweHAH=g싿- T3k N7ݬ)2|HP2&JD H0l٭QN;bDD;#|0]J9:Gs/o|}?ZdX?{Fwwߖe^c)5z2 DPaR--fe( P,FGZZd!.}u}Ykul=g܂)CI*2BM>՗w=ÇȘ`̄ T%}*d;u#(з}\j[Z]Za]0!t}E->>^kJ}罾;߿48<{,VZDdr ȒEkw:^juOt!f OX !TԄsD)UpLZZ[L%ubw" ќ jtdp*0% v)Ϟ?.{ZdQj){5wʇuoD0A&E ]P)Bk3#1"L鞌b-wj9#T3?/xI'~/rw{Ziޣ>R|޷k]AniRJY"*JSMʹmO4z[_/'ahsddG07/>{5}~pҵu3ٕŪU5$I:#bL#+iƖb 3GF1|VDdڴzF pZDq۷OE]4UբNppcwQ${k-ܷS>}i$9U$@ ѾE%]R-,ENDdH-!tFt{B-&ζԠ3b6qkӇU 3hoJͭC@S1 NSGJQMB3Zl!ɾ񨪱 AdLj"EDkgr@D1Z5,4ESCҘ]!)3 f6 b )Dϯ?{u~| _u]4C},Ϥ'ʳZ{zX"˂%=hBP6Iȭ;2"FHZ(̈#h$_.NǗ86_f"η_|z^{kTUZd06nPj#2Sa0'AFR}LZ^5#F0dDfGO(̔#*{d|xLg]cRb I@F2E&JJc6T?\wLFی%ȸ/rfR "E02P8DŽD9ӄGw&Hf1SD["SuzHAǷzi(j?s?ǃwפiv&gxPޯ׬K>kEAV3'0#Tu.-7I'zbeGj8TG<{w8-/?y !*jfJFmcQ߼p:Si] ĕ]@P53֥9T!I"%*F!af{e5KzbV -=ˇjEhR?}_~ek-#$Anۯֶu5, G 3/S{_RZ5U Si* 3s9$#hbfdV"J1 1*U=fbN>J5@A%c:K1 [D9sC91?]T R{UHNxRTL @ޮ8A2}`Q2C`& A%ĈghFX,js5SDGAz~[? 2!"/?R"d=g%BH\Np8ݟT%mKUbVj[陙0]!$ A0SLL^Fׯe}zwl>n^dJ#<"Ʒ~٫@M߾ܝH1+Im (*E"T33Ξ IJe$%Q"HtDab})Cmi&y|<{xTqyz4SxuϏ>ӻ$$Rju4"ENG3[Ѣڶj b"ã,&~wf0!*hKYH2{)=$rƐ HHJ?"fD=lޔ BN:L1zU U}OZL$a"Ej-#dngO93 "TJuAIktgkd}י9aTA,ZJ .BO@y՗_vc|ckr'z~7~ww'mo#3[)u~83$-r8ߝ_{>7k 0C=4 &I}Lfrn~1FNhљ޷o}ɓWRO﬚XQ-)V=H55a:bE̴!2[)5524+jA`r^nӚb@Yh):Â(<}]^ =\-Zڶk.W-Ǡ7݇(>7̄dBdc5'/_|UVA)6[P $$dJpOWӗ|ԪctUcBR#E%wBX1  ό| q1&W@g)¤@!P64)iJj[ԙP21(31"J$&bpIxH1 Lhi8"3g`0ZZ5c~1EJ@{{燇*?_mAzw^<==T˛/^ӳ鴬=޽W3t#G>6dB L*JAREBJo/LORl8ZHpm>y~珏}#RM.@j]gʏIYJ$ne !B6<:V-zy1Gք"䖌Lr/kE9WO˾]ϗ/uiMDWoj}z&eJ]*A&]!…dVS H2D-A2egb}VJ我~TK "h"˖[c&>)T!hd)͉41 fȩ0EBD2$RZ4 b x*bƏf)d~YP4߉ɼ$dL$E0uI4oL53ˈޜ L"խ{ _ (뵴Z~O}sƓOz1wp|rmۮʲ,K;< {ETd" iV[m׾@J?oeywO *!#^|zqwf8ܟJQw>ZJz<$L 3b^/V #f: LJebmT$:DȠm.q-U[XT@߷mߎjb}ժxfpR@ĻRP pZGuYDC"E!S1%L'i0CHUp=c L96#JkKt2!IIpFΥ+J dL#%"B EI@fhJ&Ej2UR18( *!6=6JXfƞV%GѺXD'C`W M Z#L3S5(VI %32Dd<ի_[!XNR//Q??[ڎ7>{/E/Ƕk)Jnc :%QzO>rRjw>J di+V:!,Ŕ}u?Oy6+QP$CDgw>gy;(fx-,*У_FiK-EaH3*{uqŊYUSp ɛDvhumeQkSQJ$osˈjo]sYKA)TiC"5`*ASHx (Y6CtL1eN ##!59%4zً!#XZDsx!T|4t(TT DRQE5gu:41Q>Y$g)"CX t !}H4˔bVj]>pYjWlN'y*N$y/@==J-6ޖuB>h@<"_v9_Ͽ3ZU+sXCk͝]/}lrXZc3+E*sQZ).snfv8dFjkY?W\J1+|KU^xy8ؾO}g} r^яwbVjfDj]*:\̴6-,s)7Vx?b |j =NNsXZ[E#/R}{jZVH@1O~~Eڱ18R&xT ::D&aE#2‡3ft<N/^ض\hubhܮpJMAZJep$)*LNaVnov@i J db2}UT]LDTG ۘ9]qYix.RKi#gf RL&UD(")S-V_h@,d+D$ۙYE'.m}KZQZgoNO?|"#\f_ Η D}{u듗')*P BVB[߯-K-RRݝ Up"QR:_Vd3TTLv}خ{{rݓ{x-K]j3{x|k-d X)c 9S̄DDf Yڒ]mݭ̔T H ->“;RخsKS (Hݻw~:jm"H:D4I+ AHYsk-%@FQˡJ0O}IH2;堜AϪxz !Ъ**aU{ZEe!H$ L@}bR[EJUU׷ɃōrW?Zqk틅'/Fd_~rkO_.5ҾoZ)UUJtxTDTbksԢӢtZY֞/][iҚ18}]!aI '_9T'Ow3Ʌm|pZN*- ]#"<Ssb"&fbf#ݷfڼR#v=ժh=i-MEjD;e!af5MÇw,#An۬J$]1I&T*!!@kQI"o>"tM(爌o=>z;mx-/XI dJ6BVzCU$$ H1ǡct9XBB27A=QaQ ||d0#%G|m[PӒg`DlN{g4S3-<?<|m,_"akO_ʯ7~ǽ^"UX)fGO}\?|v: :]lSKc-MTI[F!#ɽɈ-Ew*T'Fkgw{x~{_:Smz>?L9Yfxw(VgZڛo**AXcNJ;dNjL.TryZJ5KRJ #!$ hSJ&QgIHMr&`Fk](-'-ZaD}"FJ0$S .h#AJ޺k3;ݳ9n#Ĵ"G@L˹k51?#=]fs$VIO1Q[ G,j?z>?~xn_+c6"rXui_2krcnolӓa]}Lz@YTӡHF}0G@dv>_ؖfLVfR ! ^TzH,mlnt1!Lj}ۧP,#md߶ Dq3u}RZ+ IQ+ED=V!k- 4O8D0"TUQ#zPIHjIUHp:TP SQ.Ȍ - IM$"JLLޅh@#Ҙ0[;ZS!$RD0}%E$C™ېV@d$, ƹ4S35v8fQʍ1 R Sߦ>|l^L1I|D䫈گ)oZJZ-4)&VXT툨JYB/m<<=ZG lKFm .}>Iz%G3/4QZr=N1?> b`?09EoͿ7柎qſY5v>>S=5zlgIw/[@ . |Z޷i`Rx435c{u]J[@DJD-xSle~c-ɓV&BQ%3 Z$rL f%3BN'3## <0Y,um-}ԕ*j:G{LJǽVKC21yRzy$=Gt1RP T )>S @ QUGIf0>,KS}\w^?{ϭ%1ưD12-3N*rRSH醶]j"F!f,jM% 5 d(DE1b TTFDخqu!i-k䳶 HIO?? nŊ;7!/?GOMX,}-&&wO? ·Jc|xp<HK)j3)׿^I5uYQ˾m>NmlŴ,ͤk,g1_Zj)ӓ':o @}(jAc6慵x,Zh@BF4ńk "HJ1*ЌHzb@E`Sg BM'Q@Lľmόvjm]j):O*ccuC}ǶGdK}#LPŊJ"u9=;ܮ`dx^1"p]6CBfE(9dj0* hmd|1#G_bzAĘ>B*83K 9n&$0$FeL @DR܀IS2B(RhD̑Ĕ A;Qpg]=P$Lu=ݝ>nEGrbjff_>w(&{ݟqXzl)LX|Yֵ.V$FB u)9|Wpv=>onkGUjVۯ޼~ݮ5{,UKE]7 䔖H@"c.p\Pgt) f"6E߲3EU͐`p=IϤm%Fdj5Tmvf>'qD</( >LJDfSMMBIf]ڮs ^ZdQR*؉INUGR+S)QNҗHTʱH9)RJK8{4>ȆĐKBS88{5:"ΐ0q}u?\uAz̟rSiX_0QJZu۴_RJ2L$ᡵi@cq5Fxbh HL@MݑIx0nʉ)\EB( uS"ITdHH͖ivհUiseLά*`2w֪0\]=ZݫW<#X( @LQk2w(1Ǹ{!=J'5aHHH -(B* W½F36sT;ޡݪU3Dd)af 1A@Ff}`j(}`fZf!"0C.O<0F翰ެAD^xo|ӧo>,q#`/wL3V=+~ f(>!&@ޛlQ"9a_=>ltf<㐇 -/_vsR\ #´ kdJnjԧlsԪeFaʜXEX/ (ãbaNLJ-܁@!r1psS RpyAjә <߼f2&U;6Dl8i*\]#\`I,CAv@ҝa(mD=1P!gW3LB@V0i!-ZEU.pޮh?PrLfq!|4"٦JDʖ{b4p@fZ $$\w@PG mY|Fu3Z91H::sS˫ˋQ czOtÞv~~hw/?}wSSHNeGiôm] ᮭNK*RRIghfݜe),SD̻cFg'mV@d@$Rx=^\^xV_ߧ"0 -´*NNNYR6"\nBeb93 R00h"3a u;@};`8XS@HӼ7mquBdɜ%"TiEuj@{u  aH(~$ZtHznV)O7mYŒC"X84<<0h  M5: puuwrsDdp W$TS @@S-bFp8A 3AC-1 x5Jҝw@pp^ #HNn< 5j- ̌՘$ uN`"oj͐瞜lOdyu٧O>J{J鸩{UC{KfO?ojf *Lya5"D[T%b<#zH(-=IUI8KJI\#EO|==99Ѧ@Ԁ@h_sیњjAph4j-V[kj6;YǦ~N[?DVkr;?mIaBχyjFɂLDHPX<6xwMjuឥKvd9R|@" <2Ьf}X]Y6r&}mc "WX`I"2y`o EafDB!r67R'NDݡ5-e)-e9L(wx63YJYiȈѴtb 8Iօ$$j E8-]Xs"$}c,DntBشJJ0vi`Ib5S~-)=z~ )DOv@]U[Sեik=_IIY$^<]!Pq/?\o7 2^\:|`t$y뱃iaqYăL?p|ro8#.%$)uZZg"&Ac` @$a"#t`%wU߿z$ !Hִե5$eb"PFb@8&Vf،u6EV! (*A9#poNM!+ka0:s0$Bp"yYf73lfe(ADHhn`NLd8tR"BC>3!#P.\մik``j0DŽ@LH X5es9XmW90o~unh;:X.8ޮ!m|Ďgud~>N/j,f{ \0::SSK[rH T2:5`q,:  j]B][K%Kʂ`pyyULeLj-( lnmcHJ'#xu5wWa$w7^o].-@$+R-[?pxU2 4wwZt(Dv %LbGdй@8)85;v, i0@I`x8J( %SMrIcf"7kҦ f 3'B7}3#{:Yʲ^)1R1+JV#ZJITkV")K@PsnNbfl.Ѩ'EuCPzwFv@YcIvFZ 7dB pj` V=!(&`J-Z;:#Gpcgs53ӪMKF@0{x!0j3'ոz=YI}ϞzCY Aaa 1?mO5N9=]oWK)y79 keEWjhPĉ2~hU,<( D8$.y]Sv֦5j{vb\IsH@\sfv(D#vTI8 ֥iYX:5S!0[3gA!&B,s=,9C fmjemwhјa`# $G0Sڒ?Z~LPbnH,@MmNVJ42瞞PME(' g 4pCd`a,Z=!QxPW嚛AaYpoĉԴi Dn%؟ɅѪBiQ[3|" p w%U=<h]eGSz>^}j6H,?'ן^-G\])`fN(E~Y0"j8fcOwWuo{緵>/˟b~X 瞜,$keJI7JajPPPH p˗>G<"b3w _^"´55xC88:Ո U2͛Re"]z1 )1zjg",]y*wBV+7fZZscj읁٬KqMuI !߃`jq'r06s7pga WWD`4a10~Og![L>[ 1RhLX }Q3PDj@djhAͣ;X*Ufs‰II8\05 4#$  .p@kG1iimjG!Gx*i5عv.S?>=a""D7/Gr7}fCB WDk ^|yT4 i7Cf>xoo^<:|I$Rr3pikM6N,ffH(VӾe!00Ur} xV $pF z}.K.|94<Z "jle$gB\Ԛ5eĉ!R]TȀfs"81 $92aѸM9 K 6ͭÃ+d֣wry\ 5<̉8;(@ Kjpk SyQ$=ÛXtjaALnn ,1 e^ @#̠=zl~>},eX( ˼[0lP"yÀg*PN$R3z!XP@H%u!_fJD½.uOH ^j3r"VS7sp@TJٜfl5 % Ar&qKXx 3uYZ` 1Z~y+cyVa :E5@@$X) ! YDy8$vmH1wE :+d&&*nNy~H-2VG&( :YT !0DHDnͷ''WON*aW/Gߺ;YΣ8>2"bY`wlBبX6 Ӑhܴ#ݱ IDAT,)b,N hnRH[a@2@KOs4GfDt/^?읚k- Fw6Ggw/]?U5Ȼ+#>*)|o~qܬO8q4=z#UyX%CC2t?[Qv;A*pJis}zYq5\\]iι* bٲԳWϮ_vyZ±KKca0D QXz>Zk\ 5Ko+TuYLlG5zo"DLL`uƩX#4#Y-;wId(C)izӥ.vuqyZuX֫4ܬ*YmgO>ZkZ/^:d@>/3122 ''a52s@Թ.ӴMi;8#:Edޜlq?[ (ahl3€!8,"eVU$bZkxppBB0МSY:t  #fGFe!<: 8xSN 짿dsvuv]/c w36]'o^ݼ|Tu^ 8%>`i_yXm 8INᜆ"$[^uļ9]s[6i1 h8f-K%2$.yfW$릋=|depP̩a@RڪFd40$D B G$FHhNI "!ZȆ9GiGݚMFFD}Bˆpor':<0aTģ-ڔ!I.HX4B#0<'aDTW/n__?R;u*IxlrCݏZW!F+? 's~G%")nynuNAʐ{U k$$tw@YlQHCnZ M7tzv _׷zMԴ+d q=ElDnq^գO>7GQfSJC#_//?iV19Hx@> !Gd7mnnf֚iKJY$7kBwOOOί.[f.`HGZyERW''}̮  .)TyD ȭ):40f)%E6Oz,<27gHe$X鴘9 bS{l*8imxzvڬ{WGǗnz8 RB8::5P0n`L D $K77#e\C dA]N1Bx~of1@4 $4m)4Vյg<0R r8E#M{7KK>x"Bˋw]1#m !3_j?/Gv1yȄejo%/ZIQli^U^iX Et@LW'wКw0tUe[͐b ~?Szw1ꢫe^l8"TioF$ K01'WWj.:k5;[o m%'yg9^<2FLD]5"|phLDn`V TG Rj,57DJ"R!"[zs U—e{:H%ٲW ea$bnA$ڪisz\Q]TE::Y{|#tȚ#Hty>$)%:!12"Bb3g&8R1 .H:Ʈln @jdr@Ac]~,;qNDͬWwu5nRFf6 a2nZ^0HJ#מx=xcYs,HNwUfU>+"^7I)c}3+oVw_|S YԴ$ \9d="*2#*=!8r@ Hhj2{hQ̏_W$F^չ[Y|>?Z.f`/u(_/Vo69K&2qA .d!0 [Ze%{,~>dOg[8GU]wm߶9Fwﶻ@nUcUXcY;K}V7v.&V^l;@ qZPj^l̴XLF:%i>A|űu! {pO,JL/.~:9޿|1}/ [lb#Dj{H%$zޭGxĚt4~kxf 2bx"减NӳWYh\El &즎L^(fp2WU@2 |!͜D Kki:x٭Vv8Z~_yfT31+zqv~aXmU F,V:sc ~ۧlfjV aѹwKK;;,z2-'vM] W7!lF%p+n>֑+&Grp,,ƀgG@`F!vjֺn=)VK3 \-u})w 88RInR@$eNr2dɭY3#͑|t'pRG 쾗=b(D8 #K*@C 3W&e@ٜ1',5A  K=g@q lfY. ֌FM8|0te_yѳ'ո.#`j}4ᾘ// ⧟|ŋb`: ݈!FB9u@D?h'`0m>]) 3m2!<{{s^_/o<{U. AiƓh2PH8qn6螳m7zXΧPd~]WF<)%&b-8l]|6?:Z^n)'buuy泣edFDdM3S@`>FBfc$nYz*Iimc`f\tRM˱*},]Fv* Aj33B3ɚ\E>:>/*Ys [6"vbA`#3Iڭ J:jEdU1u21mDh}1GMfz}p~z~@[r|%["/neHeF`fDpsq3PMHăFpowzqwX>7qps&)cf}3lVx:uGUڕZML}g񨮸h6?nh2^>xPU18 "1 L_]WEAs. IDATTu}}}妋E )o:rt02ETRtJX LzTՓߴ-QcMY5RvkGhF dQ-Zf7HB=;swoS#Q@ 2RQ:8B1pR .fHD@d.1%P`FD,ki6$ *nUƊ9s+3Ʌ\s=q8g I^$.d"J]LԦ&W=:zդr뫫棋],ϟ?gx-;5 G\x+0ʁ L[۽xDh7,[ wK=)\ye({)=o'B tVUͮݮ֫nbiH(Wwgwdljֶj94u3N^|nGG'u]]"e$6`4xtt|tuqY$)z%YEoV5 *%@H InTjYI*:o) &[R*qѥu00wq=Uu=j̡Ͻ ;8":(2ᬐ;+0xRb&*&5ZL"x5%B`Dԍ#iq?VQ{W@G#@W3sPq쪆%^SYo;ﻫr _l:G Ah'4 2狷~nO_ެnl q!ٟ8 C1 C-cY^3 D(C X !'Uzw 7){8Gc /2rϩ5`a;k/]v:dⓗGˣMS>a@d9xɣz")nl11ar.'.d)jXX4%]͈o;1Md47h\_8wIs4 ˹hy۵%tx J/Ca2WuCY䬦 0t@3#E$nDdN1AѸ21q- 925T#"s,:O?N{S"<8_}k_}__ 0qLDEav7S MYk[&- }TE9 sHߪjj75 f:Cc@t]lj:4T|>?{yQ|kIѸHAp(zv^^^Kh֛viixuY5's+MH TԷ@PșUݪ 8b67}ru][js=;T1Rn @,5-D4b6u],LHFc9?T|(Х"EH)LA80kQ-$I "ϚixmT8嶏 < eMk^ RNMݺQMC`&W#ʯ<}0MJ'\W?o_<].ϟ?G|iX;o}h9? Ԛ;LSJa%} voSK$a/(tV~7~?5$8y@eV%M*VUU%P%$[:&d:YM&cI*!* I1t7Ce=y$Y_)BRWW758ԫn% PѴ@ڗO+ "C@G,Idb-b6@Mf*1d:C>)9p MUl:-T$NЉ3!Ei NU(&U'"&Ƞ NLUnDVP(Ubn DQ\KP 4Ȩ7rw&**b@ f7$.dor*u}"BcW|$oūs?z>}~G9ȁ=<>W^aXJꊕ3uvw,4z]=(p? |%X!ApyηCdlYݶwDf &GV7UDU!8כgtq&̸#-$A]*%Y8dYgth֛UT,+:982a@'= Tj5:O"F]ov6Ĉh઩1nTh~dMT|C Fb nݶpC* \,"P/r@ccRE!2͈Ÿhn` PHYeWU SQnlDFd$@͙ M('~Ǘ`3/onN tlŇ*j6h+>"܆&2t2>whÄh >Cߦ _c=?fy%Oq麶ocd* 0Æ! GQl6.lZLLnHL̒z1"z2E4Ыd2M9ԣz͆Q fB`W(%#19h@J!z1 !75ulכYq˦)gӬ6md46UuEf%BP0Ofy]9*I#F:Ļ42C`DuHc43$p17 1{L̋t9PՐ M=}ǽLsX {O9en2&՜K\mS.7h;_|aM3n||‹Vy۽|yYqkVD1uuf^8.@1邁-|gk7pHxL49~6tbHƼ]{;y;PUO n{qL&yA@AUt3l>իWg!b\.RΪJ xsp@fќRz㍧Uh^BX}B ! B,}oi]_NUUMMYӖ O?]V"K?]Q-We%h{)h?ǡdPOȷd{ ;@xy??{8o}.|.)(\!2t]o{/R" '+$lFQ3S hT,ԍ IDs'LgӳӳfRtwpԽmTuꦖI"8 H^9@E$@3wGc e BwvRէ~trxs($7/6mcSmky2RdDEL  T(R,͠"󴄫!0/8s1d@b i9}yΆ*aP)^\v~8[CIG@fJP|(| mԃb';>羾w~/4c9 &m Fu6v>h4"9w`VU#$xL"b%RCl'/wuun4s@%{'*6IҪB}W!`DPQa<{]WuBCNrf2"›%7hXWБ183)֫|QlYJ=`(:2w,G7t#"C"K)Fp<ӌ xz+saC"T;'tPtsdDpbA9H"3s&\`Ż_'U >_y|ֳgDŋ??>fY q{à X}>@bnaÎnҾR%.7-Gq}wd{ui_~@l\Զ]ۏfdPƙRO$A  J4ML$TJ?<99}y"ըbVUw' զiB4u;vh4SPc5F&)DWxf梗^zqc<:9c8Fr0\tsVzntq&,!K""!Z.rD44M@HׂĀ532p.IU!a P$݃Z N`@ "R=KNsF77)EQXO6kfKa.o='/^VmTY>했ہpU4pO vn+#j'ܝqm?4;!o|_t,)~N0d ]l M èpw۾۶ٔ B ׯEh2Y<8r p),ˣÓu'uJ) )8GcRײqpb V5!pW`fY)G@NALm:ͮIJjs߶1FQE^rN )` facNAhp 6FH`FRawh" 2p5 ` L4G4Eu1Gd2(O>dM]{fjZzBIY$esM)m7nיJh/>~13IQ}Yy?/_<}rZ% n \ +!^5(p=&n'ڟjwGjV^gA0{,s_kF#$B9vGāPAMۭ6t4 :(WO$ !0G'DrK|rM,9MZ]wm?NAjuA JS=1`q^ 9cvIY0*#IǓ 2nvu=qin7+s&y@ nkF#21Wĥ'T &)挑Ƀ PRP [1{,E,&D0R %B$ϞOۭ6wB B#PD/ʀϟ?{^xpW[r%zBatʣ 7Dó@>SPq.>3`A,I>m܂S`pn?0{X\h:Lr#1:fɀ"3"t@scOM4Vu"IGG T8͡!hp"dOf=:N}TU^m7bU! suܶQndTD*ԻbT f*ԵOպ4u╈EKNJի?Adb+RJZ;Wͨ1D= `Iq U fS` @(}Upb4eb}M#8 Edfb"$dS-BotSĜ'YR6s~ݛkyo>/fu=͹s7mW{^ ϟ5O?tus3ЅQ8Pd {!vؤYgglCaguǖ-eG\$$=B_yO)~ۿߝ.aT[uv델ʚSvULU1VU<}GуBN 1 #Ghŕ$mwx:zEj>}C|Uɭ+ m4Rkx hti}S c`Y,9VT`N KG(:0if4k~ݑ @U%C0a*= jUT921 B M,s}[OGҧW/^=zE0ܜ_\!r[fv}i% Gb~`ާ !pw'&@M-o.5 ;S-+oQb,Œ-T$n?l XwV{>~__Fd]biVWmS=jqC BP$v*ncપOOSM&hRбWUjn=?{R.pSѨjjJS2Pp XfMssײ) r߷0YL%KQ3MDnWXU0XĄ즻\_'ƺ:~(Tc$vmnsێ&tR HHMs2(` IZA4FDHbJ2/#p WdRNb*땤'/FySR a /!|?n+z?wmG ZD1V7)d1 R3Fgtt@!pvx8!+ aR:5kv9Qwvk&z4<P;bTA{0  :'Nrr,R&r];ŋC1aNj\n".kw۪v} uh-2)a@?RGsWsEBv T Z =TdّO d@7uG]Arܧv)<<{t@Bs?]e0ol֛fs+_~+-/V]Va/G};dwmuaKfw82]Iw{أ \-@d!?Ǔ,.ǣmErX*JXfCU"!+ˮۍ'uS˲("då"u}R3f uU =!8M@D$pތ ]+dtheut7Úa<;XmRl4DݼȬLt}<} Op_ב.T 5RsEt:n'9I+&s* Z3uGM ٰdBP('H6tRGȆnX#p;e5C$rݵf,|79>>|}Q||q |:l:n}f=Zl~G'w?n{E *-Qy/Q(>Cߝ/y\r$ygS%?{s]__f|* Lf4n&U4]PWMCvF'N&IzS͜ K!9#Ç'xqv޶]{N^q]U8  :>ȩxyeZ)"dULsNi4"!XWtmYIr)!#)~]ݜ;_B7]_U]?~IUSs *"9]~6_-ǓըvXf0+'$r@w+  Á{+lfhdCzGRtDbb`PL 9Wn jbf]wۛ$9yѓ7"'ÿ=8/y@3uw6o ĈbSni~Z^|PE졸Da~;s0P{S)~ےOcE5u]"I7Mwhz hQjSz}=L''4Tr[(\(.^6t7xLfY//?m=.lٛd۞V1 4UϞ]):AF6n!ҍld×~-; GG'y;3:ԫǏGYm۶ 0b Al\UؘCI)]m`O37lbMj bYlR4kp 뻶P*߭nίjݔ7O>nێb1Nn"n47bD,,k`HT@9F J%3jERoonrʹψ{3*iQI(-^>;;߀O?~ vF]>z|v"n [/&DQe'HO`[]Jr}0,}أ*nm mp HCMq7Q7d4{_ ؑ7I*ip4IQpnyuuvt<땩AoB=<8ޟ>˺L G[c7dr$U5&@P!&CF`E!1K7)3 uZ.p8ٖjTKΎ^]Cbw:dY&J-'N MdZ/RCM~D'SXHҟb pS/uckn×_ @"X`8ڎU%TU 83ϮZ-/Nf;ݭbb$&עe4lzd=_p4~XE'i1~r1{֔"&R%ǟƴ^sl1DI0@*!պl *GQLBU\\]]=ˣX42䣺5b|c:G`4N'Qu^T26oyhػz#5TT@LڐY MU @L1$@R< PjSR֋eiprGqzt6=||hC,ÃgZC[h uХCSpԱ]q>DsHk9{Hnanv@؟ٶ1_2sDo@ytI!E;l9w,Cph8NM4U5&IUU1h4&Pd4oU*T1S{mwR*!ӓ͢iG'gl% FGT"sg'))XK33UP[9X@ba1.↺u(e )p"OOp`4k-4V yٕVz hȭ7В\r>AI%PRI >zHb@T̑ojdH R^D4ARJ^VU]KJ{_WPD.N>yf7S6N_xsfo٦jSʤN_.bMໞPsSR8-oJ[䵏i A>8cw&st׌ *n (9D|)0d65(By7 ATcbv$5nQѠ, 4ϯGl[* :Ƭ9dgg`w<` doG?`DG0k"3s~ %?z W"8֊P!s uP<bJDkBx󓓽݃Cc@bP%pyQoj< :]z5[#@jJTUٗ>'"Q79A 9{ʻ@@s d))cu]-s;^;xt/[}puz Q_Aqrr?b3k֦hcShB;ȫ.}/rT an;f]g#"ݙoֳ_~ىtU9&fPɥNx/mnvmztE4!u͙ɟFQo0~#gD{rrZCg |n}w~o8enbz0f JQ ~QtkkoB.i9O 4hFGGB`4t{6ȓ+ b25b5H YJ2If&j9 ?$z53E뛛t6E4Mz%?ͶPH[<,rBhJU;W7INOx\ `șG" 5N Ihkʀ42 HXTJ1r>omo=~ns\^3Ccvγs]Y˕{kkkoo󕶘ڡV7ZӭLczS$l6u]Z[;i l2ۋu뎽wt6; KAͭm z.pn:wyTIIt\v "%5D;#ٛzb0kDn,r~svvS u,úON/[tDLDyț*&HaBRH)i] `1FI\,ƳfJ1&ڜ gӗ5 jM%9Kܾ5R1mlFjŲ( Qو!4ӠEA%.K<"9̩uŤN42C]bRySW^ye`0K_Y>m> ;O.ώEe2F#=;=/˲r42@S_ LbPU1\\]::Atrz>NƓ #F4%C4DUIhe @ !2{43Ku)i7SU15gWG *ڤ3&nFMr@5L8f1419@gjȴwtǟ,ձ8frUy4xbD"92DV(QB:j^da"2t:}у`25qYc s;c'Z=P g'e\whMdzZ9co#VPhʍɹn`~~|iY?؃ZY?c`~F:sUwpw?o<OvTbUj@D4S=#8=1lF)P1r]W, ?(,I8yEi1iՓt<ޙ9\Vj ovQ]% $«jď ?.:jд*JBҔRNg14Ǣ—|`_yeE4S!4 "0rLD2d/S(|^ud<~/{9ϾpZzyr?{A~``*歷;|%K?\6pEҠ6o;~o4n33!|>)Fcd0D@S5Ec"rœcb;8L)Y5`Kdhh]qAK{ղZUu XOlgZL ,(k|H;/IrmS#e&0kf_ b>r w$1n'_~w fo6 ,R ?3K8OH $q0VUIC]ٳ%SDp2X$MIպRFLFf?<8:"lJLYUd]3"bOM.1f0 2ohh<*c~@7Ok~f>'x=Thͽ"/Y{-+uv$ַ}wp_?^|wu\׎33CϾ3lgK/=8@3%S~i7RQx&\.Z z"UPYQ5vsm(y{wn{29AeqZDL!٩|+\WtPò1#]m,,ܗ07Cnӭ[H_mTfˋ;׈tx" Qh4Hr tuϿӟ x<) 6ZE˜BagopMADup2eC: HyO*IDb]/} )Va<Ǔ;$T!pc ! DR 'WgGG%ff(ϘJpL@LiAVf3FfO $ eVW!ĺR`jUNI$#4I[[?:?@B7\:P R_,/w5Y,gjmz_Q%s7a4)KLׂTh׻mN-B3+sֻG,n={ٗ]~fNv`i7"E|o7#\?rwpG}g=.Pj$dD؉^3=eq`488wxtsD9We:h4>yzv}y*Fj֣ɤ|^ 9D[IDع b0a.QRj'ۡ;2KaJ$r4NQ 1<^`7FА96a1)D`&Mv Pf-'C{GO>a U RuUFFFHӭK/wp̥f 8/>zggi vbƘYUٹVB&,KTkD ]"Fw[4w!ZZ{.̧hףGz ;f?wnM$mθ MM4!ۿw;8|o4 2Y hXޓ^ 1 ,ȳ㣣ã|3*]ߘ+ӧOú1FIh]׋b8% &:uBJB1JB\a&u]__^(C$$P0I)y^W@D H;"d X׋D*9B%!@T2F@ZPdZQ,CU^,a uD$+Tu5}$IbN4@|}'=N妪9X${dMq wM~o:?fHx|f,Yz1fZ&$Rl6tL"@5BV<4ʷxsrly2㎿:w}eZ/A9|YAbLuSH̞}d9_,oyreዢ(!3?xT8Pu= Q1z7> I'MDcQb"lE &̐1! BDUX_]=ώCt FH t.`4J"HhjU9D&4()!eS1:d _01gP8;//;C SVe[VM'hڏhّAJAn{*6q*qSV mk=qrS+$l4؍ ‹@UU[,PO27Bmt\ƛgvwpU~уGfzUU{_EY/ S z9_oLT '곧O(=" CbwxxÃj]_<; !z<EM`,Zi Zr y\.=* WK`_8!T?l$71gbDPC@h @TSVR4 CSAB uvBh0%$rPź@I bX-=2۞=|p{{;E9rs|zr@$V4bKZjbH͹MZE;R!f [n4`c5-cl?śc1:4S?An Aau+vE rnAw4YUxv!2!A$Gٻl4l˳\Ɉ`*^{>I.Ãٖ_^Q15~O?CN1Wk `=IY/CY !s0Nd2&4@PZ/ONGѽGYRkl 11s. Q@>G$l 2$/ԲI$"8 R8$ 뺮*|9(FCϼsp[~$:קWgg c0DD t4yCYUP%S"`ט]Bkmȇ;qk&6!mbg!];fvocbӹL{r>=J`ڰA^7a䐾LaRw~z}/wU՚wBL1'rP|LaS'*BbRT'|~.%3/<72MEY!\Ghl$XCK}^X!oj$@AArV%PuuvvXҠ2=MUZɌ`$uU&@LS>K@o&n"I+ @9爹^?ӳKgG ŘP>G|u'C>{X綂`"^zZ/mCon}wηwwdW?';{ Ɉ\fv@$٠pt2Óg!b1?xȀ و= ZL>|Ϟ>U;>9&.7Xa]'癘rbf1ٙbL҄jL1ͯ?r` 4Jg`DjҌ_f t;Q5%543H-Bu*E9~6r1?֯~pRU%:|>xvj]gDocqHUۛ:45&gy ¦ȹC~e秀z7[8Ǜ`7 S7G6եvm#:)fh^F1ȍSmFlh){K;s}w7C`;/3 EtޥPud}s3 ?;?[׃a%I|>!x0F@3$ DRǰ^?]~5qE#ܣa _PD/aQ X 9[\_/W+v%!q#G3QA]<}vuvǓm 9!QSD$mĀ@s\:f佗J$*bHU=[[( wJkn <ӨjCΟuDe7up{Dϐ[:רӬH~emDcwF.g_ {.5cn3fpk52w!6L=]Ep7~`&n h6苰}BǿYKO`37^7uM0_!rV@ 9("fvs}<ݫS< fpe鐩 KYIR q tz˦yF4* @H̄"j`Z}jJ^UqȕÁSrnvDf4Z/ON 5n@ށQNP<$J&$Z&Fђ `ID%ԁƣ舝Ϛ|///.Fዯ|xp0@M҇uHHE[mvuyHE4cqGF)%"TFZ.ȘZFaf zD7 3襶[^u?T>dM*EG{Xܪ;P^fd[J5yۦٴ3>TE72o}[[uwp4?<NGp q\}'ݔg9nrw<<6]-GnaՄSӊxEIZKZ[3a}mF`$ފ&dC@Aי[eHSDqVQ}7fvwd;ӭl]Ȥ)?[QDF'ס9%$Gh葝+b9MOW =R U]v1G#HU 7ˁ#';"B@dBS=?VãVlrI DyDňHhIT PUPU:m J&f|:zң}.!3;(*\\./:!:vmFDDŝ!aŀ LamES/0s>" j?}хQ}+:wDt5m [vjLg+Q$ztM{<7YUZ]5voG7D6taNv(A3)!cPM@'"7 qBs `T`T ÃçO:˪Nu/=Ht#f2I@رgBrDh_e2$/b8$b"*9V Q"TQ&]MpJ ,H0)lwo21Z}ާחۻ;7b 1CˋOËӳ~n;&w8{^+R&,iK5ᙪJn(+u%Eػl{"a^Nk94Mq2;,E5Ow\Ϛ_TxuSx|ߺwW^e}AUU۾)ԏ_~}!RLQ$ywDBP"uUÇ_8yzzvqfE9XW`<*G3PG jj|Y#I1ԃҋj])e4$Hh"MϞ]m=zliBf@\IZnw]wΙYh|cME0ݚ', ɾbC~4{BSJW胋v'mO- HL)AЫ֎)-_ LdvVvq)`CMئo m HkGxu?EZgm\lFn woo[og//H(WIv"գǏ1"RHv%!C],ʢ`ϡ`̊A^Wp~!*:Fqb^&Cu\UQ`PגeXn&c0"iJ;Ll_DLPÅ+2U$:XhT8 ώ=;V$ǟnAQNG20^^~އgm+WZM [=Aڋ&1%iW)}J "R#$e! 6Xeí\a^'!*7n?yPp1b4|Rbuc_">2hZ{N73@7Y|wp?/H0ESHF`_A1HIX7aw!EښLVB#:NrDN/ W^jU GLC}u}#jHMRJgO^>|n2zLe6ELfF"bc>x9LąsޱW:3e(f. 푤~|~|4ns`Z?ajD5lpv&ܽ$=XD#Yʡ.U PNkqUmVg(Jf0źC}5..G =Q o~_ ~m0ߑد~W|Qv=%5P^^^-gp<*k3+}YKO` fuTعPg U%hJ(+̱3tBjYDY+3R@UT" zŇlg ˗PTD=6C{D Z4IL1d9ﲘcǮ(n>N~""scͫpNk`WaR"OmD]Bi)IfiL-A`1[{+3% C;`wbl\*}ykA1dG`; Sw](횎Ⱦ8; n:M# J+DbfF{'KowfKUs{zzg"EFГoR_@A$$. f!MaR$-!bD$ ĕ3}:Kf̓5)IX陮:+}°MWٝ;/v$I(ʲ,gIfJy0`t3|'f#`b-dH$ M:H,Ӣҕb9YlB-'N{Mc:z['l51F c{-F.+u:.ib Q' jW|q~Q?pwrRth1W#E; q "TiWnh&&b嬨Ƅ(̕;iw~+ 5Z,j]A3_s3ww@` ނԳqK51AK<?n`bPfK֭dv[ 8WY8KWNX,.ÃyMiP$2h R $C@KJeQl|b$YoEt78p(C.w 1;SB",,!MAć9z6kJP9"GN#hi$l5 LhfW&*=`+-%`{ь{M^O#sqmDI 1z{ϲPaX5Pi]}vLǍZ*JO;O˷!x ̿=7UVVSȽ{NNN߿|Lo~3i;أ K~R̗ۣ}@Вe4I$9W.l%iJ-Ǐ7R*c@0,H1F!@&ƚ|򪕓y\2p$±=3³O.|$Mg \dg =7yJ F&I),K5h %i:Ͼx~z&BA0!>bCs-qUvDt5Dcf UߘHOꙴ0&K\\]ԳsxUGP U:1*-́\æ~IWJhRH'e $;bѽ't|~3?߸} $1,fDp|2;7}YbrHACPEfi8eYhd-={z^m'MRh Yab%!'256Md|rcSUguCBιdq[PQ(!ؚ7I* bbKf4MI4K-8b1,!.:?J:U*4*AYu9TzV]xzl'E%q3n7׌(&a8i{?64 ֔nИ*GC%EÇo}#`'vbk/%qb6ܺsΉgW8_x4q :s$>'ݽhΟ]´5Ȑ5IBS@4rtҦv\V Dvw F@,]>yyxxۑ$Vb9tD5(ޗEe` R$4Hd6(@$u7򵋳=ecN1m2TÍ`3:n|[mțfMQg TV`f;" W#,vXi}*x.,74 _v쎃8eJ4.s`9>9 ! ϽuY؇PXƭo@JdI@0 A"C`0<{r:bey^ M$M !@$EpYbj>"ISW\c ``1./wɐN$ Y4$"%S8E2Mޕ qpp0QUlB"LM@//?;7[znsq~tl\ l.,W4zֻW*O2۱yDB ZLHm:S*Puwrߧ+?&%*Û{ґϦk7ݸy,D!ꝭÉ]xwwo8>yr<$M" dĺ J}pł5Ơ%xqs`NUS$r:;{cW֜P0̮t~M4$dRkMjm.I4I&糯~+q;g}"2ۻ}v',|x$%6Zn:nrKjn+E{]!+_1}QM,>h?jkw`=6!ͯkv#QT5}f?wB8>>ꯢ I"!Z/?߸yŻw֛gA"!(@Q@ Y  |gglr9.v;} 0d(IҴCưĤFNE L{);^o֋`r~t0";z~ȕe5AFDDhQ8v9;ytn%2dZkCXlbh6~6>\pK/ vλtvvzX.2jkՂjh\eV!A|)1؀v[R3ʕ]wش7GR-]rYg7ۜV1 ?99::>>>VUt:~?%Y2 HX/ol(|BAr"e^6</.әBMƦYJHhт1󄈥+%I2b^luA_xv,>fMfY7NOI ű|! 55x]h8wwOe@an7L+_ky"_->/xpZk)g,Il@fkxͯk|q€Њ9`8a:=~d\n$۵Ѣ RmZVZ/ۮlc7fq{P]`7\됊⏣+j գb0_NO'txXbBc8_.WZ vDX1Z! e~b:ժvc ai,{ct/($އMYJ{@A@"z]fOD|NghX$%X@0W8|z~'똄I.Gt2y^B:v:8}m k8p8K{=|*+B; ӄmOk}iup&[q \iP{AQR7Z\9f݌vjۣ{) <|])*_yzv8IXy9~sz.ң`w\ɜ#/\bkbXg,0s`*dYo'V$lbEh<7h fAZ5?lb3W:Wcއ{&.~eDh%CqLx& |7{w\Y,I$Mn|ZWb>^Z׌5软LM&×!LǓOOq;\ώFV+CGj۰_8' zMQMPf4b7~%'֒F*uuP|8>>Fu|3of;2I8g%%!xIȕEcR/~Sx4UUagyp?dĿe0hdZ# /R4<.-MX-nV*1ff;>LiX)܊w ܿE_AH ]IbD/I⊂T9!8Ssk9r06!kʲEK&&1(Hh1:3\۬>ӉwbA<z 1sZYJWE7eҕ.`)IIz|oOl"#Vax\;D`ҋwvv|WʟBEm〷>ܶV+DZw@+,7F4"bYubrIj9zO߻wXW.+#?4?vQ@i, ~;K1 0{`zAYyȂcB{~l:Mͺ,cm&IXc@WrSB Co֫:&Z20H\/ Y&Y-6@I;>j& a4ry7NN@;mefl5q۸[/'[5USxbc50N<B8>: B/l4z^Eg޹;/#QmlEL[2$ |?6}l>!5i^6 32%E/;/cHD$"&|U?&W:vmb^ߦY|71Lb4i=BHC` wvCŢyq#:W`cqb]ZlM`dp-5rnCT_\0{"O/%ڍBor'?ۯJ?җ/1J$eGQѓx$U/RB X4MDx.G'I ^B|o+gDB@|pk|6=UmC"ڹa , W_}y0bYV7;&Pnda>|ڕ-T)Ƙ:2 *oF{MT%B<>o>k|7+[/0ycD(jj"XaqΏFݽ>N'qa2dM! 2gv"t4K<"Ud$4HH" C:#tz]$`S;NG zZD h0^ `>??t\Jkۈвgl~.ښ6׭*VFI C^ׄ 7\ȱܿb|u7o@,Gp;[ z6M<J X YDĈXB)>`7lZ>d{H0DH;WyYwvp01Q 1&'` T'{ָ?+֛&jK:j??˃@wl17Dqmjb]T5`΍ lmdO)"18v+_r9W_y bܸ!T?(,zȱGD|ٓeYYW:H%ƒ!4Hׅ+$I;dp53恾MD]l<`aC|7x_ؕbM;WG.>泳bYGV3)չ1slz 1>ɫj7Z& o)kڀQE!WWg>;D&7owy"@ *͌+;?{d6yu;%h)q>_AuDxr9Ĵq@ 0` "NS GA ,ʢ"_^QuZ6^En,+A#QY4&d':u6ܤW-vX\֛XzZ壣a899QU[9g?f\'[ߺ5: !\W Ī!`B\ͦMNyc!V;$t&d,:lL gۜ 2 2 .3cCfgz]pj 4hUg֑FAx)|hڇ Rɔ1I5.W:L@➧i6j߇ QܔXc[  pޏH*ʲw^"r9O|.p]D€Ux-q Opr9NƫX D%JLf(3vXn$8^`И2w;_JL7&vZiFRPn>fq!7Ab+2 Beӳ{6E[4ay+ڻVE+C5[;0 b'''p||(??n'r:;v܍a6XTñp@)IDAT$Mgl2]sclZi&`j.WDDƄ9&s^.I' "@)\5tɤXoD\,^p+x40ppOӧղRVDkivi#Y4Y<| ltP9?rrGn{M/d0{sλJǩ8 a|v($ ky_\/sk0|H]26H(7rd sM&IE˒L,˔h.yMպAyK]KlUgû/vM>ϟ-WQF+fnN2b圼O\k>cq(Nn^~x8ݽ[n"ZS#K&hk(sW7nٓ LK:I3_MRkMsKZ A>1_oiYmЄlu1h%" $1^͛λˋz]o |Wn߰AUhqS|tÇVPb5ݽr)&$a"`B(+ pxHDO?fB&)`:=&f^-1Y2lO )VC 1!S uٸ,6.:zrݾ;\kLb-5,M7]h/kiS=kfvQLDúWil}P:+N>7?iwᄐ\/vwz~ld@cx.|4:zO?^-EYYR:IfS[nh4D$fǫ2wq,|9M90lw[pn'5ʷ&|%KfmT,[D}mSO^xŝ^uCt w~ǘ$ZYi_hwgѻ&Ip>MSXKDeYnEYnƕ XAĻ_fei4߮4]t, !PGlo-[S+nM-F ?wG9+.+)>3 c\wte(!jb+&]Qѷߛ/i ڬ۷d6yEog-EZfg)|1M5 Up+h/ n7*D`nMTu򃈴5&l.# JE>M ڃÃ!x @`h4|ޣ̕ A6y"d6MPpZQ .})ǻzYNEQ}=lX dY&ꃮ>֫xv[)R棺 QE2Ε{gOϦ`?RW9&L.EFޓGO&yQM< |(u(xWXF%Lq>>>Bx)_EQxXz:h@lkfY{`||q1:?8ܿv8z֦M"/ B^6N2b>"$bd(rq]\+YkPxڪ,Mss\IbJv +ivRŰ15+/LjY':]^aY.bwggw7~v9\;qll27&`(]O,E`zz9Ϋ&{nc@c_(OւJh90 3ܿ"|1!EYEe^ɒj练ݽ^6XV4w;YJ.6e $7\Ydz|n h5[ Bʲ&P$ǰOO(C%T,ҬBD\-VuV`8;횄yI,g|G.;8Xm f&tkB+Vj[F'Ac*EK'ǭQ(zBcD6|:en9N,b1]Xk+'󸗨øH"Cمt6mCk1g>>>7И`EQyKǧP`Vq&/nkM `A7M^@XVeYj]eThQvK`; 1,3·+Mrs½{ÇUvNs>'{{qԾ>} @(zxn;ݬ,^ߙMq&BNico%eD9!⽣c+9߼?>oO$"(b)=8gX,DZcAi40DY/34iیa'сEQwo~%/WI' uqqѿ|^{k^#xAP䅀@9a,L}`1RZ&m暈diqH !r|||rrQ (?8ˊ_NgK Y U @jբAD1nk@8>:fNW /|ӧ_yFMxX A}\K =uh 4I+JEQtY{nˏb̃>>;{ FUW!45g'O>o7|6ǖ:IENDB`PKF;>>-Pictures/10000000000000640000005FD3739B8C.pngPNG  IHDRd_=mgAMA a pHYs(JtEXtSoftwarePaint.NET v3.36%>yIDATx^ͽ[u٣g~鈑הe{s2{y,{- ܴF11j$ 04NqAp*U/O|]^{ﵿ_=?~tƝ~~]2`=?k>~OT)L\a']Lɗzm7կ|+};k_~*So}1x. Za;b'v׿; _,ſW_ۏ}7bQn{OþrmGT-{&˞ݥΖ?vl- []!7$>x\)MOo{g?|7_ >ײD6hTmْBes^g{6݄z6PJHYhʑ.'?'"b;WjېU '\H0yvGdL)Soj:BoFhS;Z=zWo+ 6եuxNJUa Ca˭Nӧszv MESDΓ-WOTr$LYE2~WW )`y#6W [AɡUE0%.Jݶޢ=`Ujt^թEP&^)vDšDbN4|Pܖ p'YOgۻa}[4ؕB0]ek|C$# Y!AZ YN.=BGb"JEoZw٪͹h ##$eqzL۩J#~q2t.3$`k0S c[.P_ϭޑQ"rwH4`kaOD"cɕۉ囐?XQ]({v{6Zbؚ+ӌ3P˔}J pτJکF'S?:ab9WV.j)z|P3Qn_+%|X>I;/ɒ@uśdu=BbNŸjQKfruFݮL hJ* V X`wvg/2,B3*TQAjX?PnMTۇzƝҖ]-D$eतB|vGĵ\5۱V?U+j668-~!d,5@)p4ؖ@&^U?~28HpZ,H`/0TꍱrL$&*!Z1QD`7qnKd'+q;KNή-Dr\ 3ƵyPG7\oR|w\rRIRʋFRJ3R-&өk*ҥv͔ba,P5!TRnL5;Sn[[ͰXZ 2kwķMU9XR_+Mf9F7_4Zb{TU`q&j8%0Q!ʈ\9/ϊ :|Sq=5XG';:Nj/WgKt7Fak:PSDU:Z+jv]=d&F@ƕa=7W;j{\j dӽ< ;"+JiJRNkA7)k0-wj;I2kd"E8Jr]h\5:Oٛ,~=XG{2#`bNN ^\'ƚae(qG>(W^4݉qmpTOIm4G%'6qXnMKʍ^5BA|QRFG`ZW/I60gD+7|)Pz ȾMʞnKR# YuC 2a59C38jOz68D,ޤ`:Ґ p,$&qjj4:=mLNӳY{ctޓ)z@oz/&%H42-JBI9*7GPd[.95ǕAIOj=98nk|=8mθ\(tFz;|ڣJ{a%ޑ=:u5°;âdkut\G;`jPBYLʧÓQ}r89ksw^)BJMsx1,2j&a^<^BBt¶kbZEl'+>?Avݹʮ r7+lʇVeCʰwϰЬ|ɡ{dQ7ʮtWG6Ci]oJ{49AJAq|\=< Htє/ &QytTۅ#̚mLVgن/vt6)Z F}pB`TeL\e*t+V$]I>Hq$䗆K`PM,ɢFJMBS|aEE #mVePة;#֋Jh\:Cdvp*2EtGyfTǗӫqroo~{ |;]BiSh7K\#jBa7-*ڣO7BkoU?Q1 7I)Bh!.P J}rqARK7"+%G}w˪|ɦ{!Q)8f$Rc,r7t.Ue@ۅutz^Rm6]wԎI!D)L{mx`ATqImxZoMb`]9 mRWF=koQ |%:kez!Kޅ~^S% 3BjG*` Y5ieU1 Cyv?ChbrҜu;_cEM߻w`G?Rv6Ty(#j:+|va#:(Ms'izbt:Ŭ0@SW#\hBwݽ.ыA&8 ;#U5AfC3?A{K6=4evw~]'erC*YeS"%"i}o Kk_L3DԒF*Ƅp(ʷ*-є;޴0'|\w.[v>> WhE̪.U䚉ddC2\ u p -; (&`=)6^Gbqy`LSóicu0ܨ1xfE}s%c6&=ʪS0_?*b; '¹̍QYdRyɥgf֕ۻd3 v @/ 4rDyقK,\s D6Ÿ)vQFI߹nIxYVq{Aߦ0D6lױ-RL I)u`z""b3}J*ҍ=bMt6]6k0^Q2up|1`zѮdbhCS9,9d5p%AJ\ )7kML4?)gD>qKO^S;?lG9>__T[LD(jaa!q/vK5%Vx%Sp.;=n_W\IkxNEg|~tT-rŀ2S6LA4s4ԄHRQC\*1bTu_h/{WzQp EI*%#W.'Yb>0Ɏ*A9]w¼*bbdQ?x XG쑬' x1G+"9_4MElW{jcj YX޽ZexũydfQva vUzөq #hg9YIuVdw%]b`k]WuTi.+d^3X I*20D"@V#agtVY% #B[ =!U+y.Ugin[[K>Pmøsr5=HM.3bņsI^܎n'w;\z{KZ7ιf\55Mg(hb9h:xpZ@F%g1&2V 4r0oJ99o4烚$`*):M)jDWWn jr". ,-= _rh^rcՆBz7^OSջr;oŪwxA-%vGޘK&w͹Tc.Uhkc ʺoK%XKΈwϣVEމRw>HΎQ+^o'NOo4&q6cP{E2JJ8*Jsw%[SSLpU!bppc:gtwhW3yHE:H38uwڼ`Î2&y)n`WK7ҭL76`:4[gKeP&= YGevb3 `;==xXV[yOE_ rGD1/FU. Scg]侽!"1>EQ:/Z+Ë ^Lw%S@{!7>$4jP+RRB_ ^9AbO`I։jXI 8uܴ[ҧ5Sa=d1ώX`CYLfo!;i{ i=jXȸDY *qƲz)[j.6v; =ϕWZ%L8@{xT?cX3RɋA %- (^qj_vhX!P[;b%hB'R1/!%ɶH jؙЗPx?ڜK6#jTl LK\u۸r֙zCR_?U&a:PKT: R;q;^%i-eK'U 4w cX{\֌פkr^Yw0 fOnҰs'rޢ[T0-7$XȪ=_ߞk.H,jKUZѥn_tv{1|ŭգk`^u<|o-d,;|Xͅ"IK/Dy^B\Ҁmy$E, XYUL}{aǫ͉0;VR~ ]>ٰx'fI&"9o #ǰ˅uՆ$H:¡ʐTC`ws MOŕ>y8FY3:7&k^"%~' +ׁb/-G+KrP^IA1A2y* }H H&fIZaDu6QkP RdؑĿxC~Ba| kzoFLڣgȸk69%#Ck|gՏyֶwM w'b^}T5^__E_.!+lB. ea[Ȋd$و+xVx3"71^ (k.uԳ*Zة #cE)F%cLr^wnx锏B59_uKv55S (xv9p" >X"1>׊u t<+(LXpz^"n1Is#1bQ|+ƻ .+bMU,wU~TRo34š/Xt;QXJE\,"T AQ@pEkʍ#Z-a56PhAV,c'V?پɭ +lpEa\d :GDDh+YV5h^Fڂ})-W;1x;J業*\ÃU,-AaoToR+Â#^§Dςlmz`h(B!ZY cbuݪVti >_)?'ڇS=W펚 ^(0{Cݙ]F\Dk" w =su{MU>ph}ϛRjv`rIF!%VlU/5W:d4]*JY/0 R͝ 5%R4=p԰/@hF9Yɂ ˹N58Ulr&h[j'&ݟxplp Aͯ&z5N*}7Bl%b:)]jz.4',} U~@mHbsJcb࢏ҾԡpǤKeR M_~yŤ % 2Ùn64Q񗖰Ňi5BH{ BcW㚯k]Xu݁vbtYXūxV[}jK(lڝVbf"&v3t=Hwo[> 3)AJfFmb#3=TJ{:2[XD ϒ<~F k,Tլ$E/Sԥ¶^ ӉI`3)D'&? r3,Dd厘\a#sM!Z% k/xWM)=ҳ>g{f$e`'a|~y aTĜ",73ֿڗl rPLD1QMl,l !2zg0<š谩!P%Y)odH Xa Q[IJTZޤj (dD98oqΐ wDLflX(v|U|6tumS)h{I~DYĄJ|ҊTXSs!+QF,1/&(d^3q }Ihk}q²v{8\vS`q=*Np*[UډZ>BNvze,Ƽ /l~=+bA$ 3x ^@7g~'uϥy$`SI+KRl#..(1__Hv0:lcUrh1+/Mpi$Z*/.}ws kǢF8pPM'O?_\W<ET2{ªƝy ;!M]#{e&oj#H# ;he}Ӂ%BE(P{yɦ;܉6ZWf(UaAſh!LJ v 97MWDs,mbz41r|VdFp񚈥b%)+x{R'Dbh_$jS<<`MQ]XK¶ =[L (`4[KD3Aى:"8!$ߘ7ܟiv bP7G\ ~HO s]BP6o0@TzW0&X|@_]_ƶԿ.`i~C>y=t2ҐY4hxض&F+ܸ%E܀26I#n3y-}@~R+)3`2L嫱O77a/|zNJḞ^[\Z^XP F8]7?_g$ڣ^sDɟV)bUwsta[b(!k^gJ_s9r(7.f`:T3&bRx!{)6Ȫޯb3S-)sH,Zm&9_ [?O2e?{&h`dn tvë/ FBGO7Oz=wYPFKsf X_WwT#[/짗)/X4ݢ/K<08j30)HbJ,Y!cAJC$aԮi4U¥ȑj1Qmx9SU<(Ҿ7~Dg [<`Ւ4=#.o.P޻+x|KH,>{wD X.',MgQ98BP3X;En68n/BbWޞ.75;c:y(tTdL,EKZW]I=JqVؖDJ}3WyM'M@9b48D+wX~?~9;>WE@!ɊLTJ{vR2#&KC!IԞ˭Uv[;u0.,V;n=6O78[cv4I)@Ԩ3fy &`,yѤmv?r K7pƊ󏍶䫩oImMO7{ R2Jxi9ÅEH"VX/bs5 Ӧ8#:e;MV]zjj.Zٻc;FӮc!Bn;y>ڐO$H,_C\wrv@T:z0a8WAcv)WaΥ+I'% Ԫgn#MJ?6D]""b#Mb#d[TYiPF^}aǼD&δ\Fe"vmg"V{+u XZׇÊ Q=nM{f !"S}O +e͕rmP@7'"+gBKڏ~@K}_.?x`D D۾]L]*/ד:'eer[ʺ#jk$&VEg XWӥ]i? {=FQr:ZqKRP:5+n|E(yG F$#*+c^QO񐜋{';x&[Y?CuK 놲K䣂|t <sT HxUv~/`!U`1 Tu18zMó^Ոvuq8xPwi5'~j؟xZBWNWWgHUZiSuopxʚ}oU]E6uJadv&4̱u׿xM ]cA+0qYedX (,L&WATttr7Oڒ uKlNC8/>+a^>8ee-3M`1Ǯ׬Wɵ<պGf:, .TvDdղ7HĘI,F aGP߳:>l.]?l1;j!>ߐ1%4s#n.%>y]zgo}L2nE‹ɣڼ2;2)`!+zlD W]D$J-_ox NݞtD@U=56|$FeV]a _h(GԸWR*Ͼ @?%_?ζ4l\¼rF 6I.%5eWTX'sd% @RK* U[k쭵da!dn][(9^lR71>c YPq6*rycvα$Bu\V5'A1b[BY8I\s,Qu`hkM*h"1ESBSV<\x~8T$yY*Q}hɘ)G5m=z0˔]Uq|z4ޘuŖ-ycaKo:ifIURoVzY^lW5;hSe=TMibAY¿AC^P}JX UJ,āVGMzOԻW_'*\ H4( ;MU-"ͧFM(db(E8 B)w4㍥1$ c F`É~_H#@0MyɴE%i3GJ,!hu\|XRj2%f kbYJߍgGzؓT(,-S 79+ %<=8O$—~!Ʌ"y|0zx@u7Q[ -P#n@>aX->ZKRdҕp{*.묟eqqGn"N$l56*ٻx^ O̊dI7f<<C|8^ĊA<ބR^w#f0-X|7,J SkѲ ^D(X8-%qL,yv ?T |^$ a8WKv4ȍ7r8b9|,gFTb4hfߐgτHuLj &L(z5<;I1D<(A*}E> N:UIUy^-v?, _hYMo_y 'Sd F;'bV_N%Nq (< x7<+YR^H!e4L1&P?IJz0~Fh 5rW,Pp5b|L5HӃw= | s_Ps#F*x pq#d1ِ_Bq+PSh>ɇi}BV)NAz b|I1iT`GMʀ燿~_{@(k|XQl$N&\13 [ tGM)a#䓬!/!EsA, 48W$'gC?L)L{Nm\/vrPՇCt}W"}p DBFr4Kez2N'G'^c#~w~C}gu42\OG5 2L_~7QIENDB`PKFևD-Pictures/10000000000001ED000001EEF0A3F508.pngPNG  IHDR=B pHYs  tIME (&i= IDATx%qޙyιC =ڭ"&RG[^^~ ۽l -/"b$@ "$ @U{̽wyN!~KDA.p/wF|*88?888:8:8㸎;8888:8㸎;8888:8㸎;㸎;8888:8㸎;8888:8㸎;㸎;888:8:8㸎;8888:8㸎;㸎;888:8:8㸎;8888:8㸎;8888:8㸎;㸎;8888:8㸎;8888:8㸎;㸎;888:8:8㸎;8888:8㸎;㸎;888:8:8㸎;8888:8㸎;8888:8㸎;㸎;8888:8㸎;8888:8㸎;㸎;888:8:8㸎;8888:8㸎;㸎;88ο(WWUua4qw_/+~K!K/<_JRr)9\ʐRd@&n(կW%"*PUQp~ϳ//}#/%DDDD"@&D|WF $D@TPPP#9wN*O*9,R\D*EJ}A(3!*"}֭[=|롇oqw~=y_x  GQfFBD@@Q Y_c#c~u@R)!I)j !A L!`@//~[=|!qׄ^~o/dRM\݈8-PNLTSMxdP)z,S%EUTTUHRDR%+!3Nbb. :Un+_~ի.{7bHȄ:Vn+Ngzթҍ 1-a"%)EKQU*Z솔jEdNW zI\D~+_ꗿ/ u矋1 ZDDJcPQUEAD?nUt\ǝ_S<_ MC PP#@ ^t$CT?%Rd_UTD ߥzOD$HNeV^DP* `*vW "2UT,3Prz*7$C/ -·m췞_@LD<U5f$b LLr+9XMJT:I_RM bV""%>vȈD~\ڶ;M)~HE ?/}3\_~W/?x_{U!p`D$DA$m:9r`j h$8 ӌs<թMFnTUbmJEc@$b|7 !ϱd4:Vi}bs\ByDF\9}40q?2sw~}TUf$HHa2}[  6|O ȇ#;d*DQ'B(HqREE slM(bRM08^[c 1]41̻~vr\3*ܾ Djt "v|u6pvviHoξ߁I۶>LGU:8Dg7o6b bdb LHDT "s $B&K:RM&XW &Q R'~lRaPUE-"Ztrwg30d:FD9:zVZ0(*V!݋s],Y4{Uy? -^,dG\9rG?QmۖN`wכ&iO'?G>'tqU?Ϋ{7 qT;Q ~"bAiI# RZqSTK) @ĤEKES*9m?J9bFѣ?J/gT"Tr}yy5D~,T\\\Tvpy+c>@ # :;;9==?9=ϻ+WTaj9 4a|}[g'~_|o&!FfB"H P QY8h$PChPpj)L!Y[U){~(5xX%eW}%園X<a5,( r\Z0굫pz2'ӓRJ*e~w߽-òƟ HDя~씈f-~a!n@Ouq|ozd`'(>RjEY'Y<(hxV>N@U * U.CY`˘vpgpH!pTU'@AR~զ8,$sn_GkRJC<] |ܽwqyst"B>GT`qRRʹC?(0$?"ts~ykZ?y7÷~`Q*b mĦItJ 62bɤx)DxWP.&-}^n2HDs_ ToIT)8"­n=!1sI)}'R$ҧUdoifjW[= _}|ONNDȌ'X֖%D|Z3TUe?Xmݺbm"նl8[ަãdAcUBcάhg.͛7ob1Nl'oTUo~~Y9z G> |#0 }R}hAB/s^0n޼9_93!"3n߯6k,I #~إ?FD;*;菦:/Ogԛ?lZ%KAUbd[H10Ҕr1:>OI P22UUE]/}ʄtT̺gҤED :NF1OTJVݛ*4֍q@xq޽;w87`Ǚ섉j߹X;-<FGڵk)k׮Hw>Oz+sN3uoxq=Et  hcٚ8VTH2|_oq_~Ogkc`% *L\, <9uı&TGcCeSRjF-6N)*"{sA`~U.@HM;󺔒EUk)7 +2cpvvL',>xj}fJ%d}MDyX,fkWTuۿ|pbN_jIԖ:]wzӚ@Sx#D2^O]bv#ZJιh΄%\_έ[Mο|99Y1}9&Ekxw-蚖Md){24Jxv;xR@fJz]dE"@飺{ڹQkZ9;ڷ߽wدODggggǤ䦍+s{˻w6C)b4%|>y$(Pei24 !&-"w/}k礻;\zot޴-)JNIBLVxPIChhjf?DNTs¹ v-U.}B*Z3̃ǣ HY@95"BkIQRh=W '!DϪ-֣O6y޹s λ)8^wE!$]r@y`M,EaX./Eݟ?Oh9]VSXl6kۆvov\ @S1{J:L5hI9 -:́C`"D+B#ᗿ/\ǝ2_=̓ͷ~е]WVP%kmC@f+N4'qxʴuXlL"Zh)[*uҟG(c#v9۰ E@jq]rB  m۶@;?y̭Gz+WBkvM)~Lv<$#я44M.pl:#"5$M-hdRAR[DxU>33RIDb!d'Dz OyTϞ~׻,2("( 0E juѱ5;c]A 9 pc+NO~!CD^k@ܫяprEufwHR$kQ+1-Cj%`kſΠڦT$߽{^mG?Q= >9%ě$D9;9W h~"oBnWC$'gmڅM df}].="Rr)uOH ="]EKN B LUͫQc}߉}gOL"K@ JqdiJ=M׏%jiw NU~MO6C8 M'qD&$" uY@I}lc5$>L#ΣXћA+9 S MmS"7fQ?4k_͛xDXdQ< rO~?{Mϯ^mNDUllԷJ0 %5 KH 85H']*͒D@RR16o)c;s~'x|4-ǫH!D !pȱ 2fXԭ:=TADU]8R<ͧ[z)Fr:$ǚ!N:(6RPƔ:-d7 ` B0H"j?3]a%&V, )Є"%uJLv^[?ѧG;x $|:Uu>lrMz0~!e-4eS"V:5 I;YoD4Ɔq1q.5~&O=]ǝ2/7捦 Jd=- M y (8U G U F<ʜO+yVڮQ -}qG'*$"f՛F!p9ʴXT//z399"K0w})9Ʀl6f̴ml׵H[HȴAtLk^ibZ_`P`1TLӡF%8\ ?L@H֮Mץ"pԴmD)}*Y9j^^\N>EdJfl6rzi7!Ci+]X/ۥ뗒Zc}+gk/q`b02cdZ#E A*dG7o|?3FCP IDAT^zcuu5c<<'lo>m6JHc10 0))6|X,YK%K}9 VV%B <jGɔU /xض\f #j)ÐsM̺i BTs.YmbNRU]j vH 0 %ghyb~;?[˵a%|1zJhTu:rgh2q# #)dQ-% 8Pqu4l7 c`&" !X+Vr<0 4e"H#]#'/gS{fBb`qU(P }~Y~cCS] oɐ$*4V xRJb[h7B`" l/-Rb9<d (Fc~F6N$/E*01!p:d͒!:(ɀȸHblb10@̌a(mDB`*7ӽEUP'~‹/|/5Փ5#0VqcDթb<}ydAr7p2j\n}bOflv6RʹK.7f&Ff~g9v])fͺi[$aVk9^Jf't2.%<S14M5Mۀvm>RBɼu]!3N&ล>t]˃j&pRs]RhQ]|RwHu: RT Qӫ@EN}d!M`^QՒjM)Ն!]70&")RʸDE?~'`:k.'!N%ZZQP㱚tv1(te O,L(m\P갎=S3 @  >*[h Z6k^4m۶i~Z\r7]66L%[mћV*{=>Q-cc1"vv'gi$T@U"CSɨƛHT ))!XMž OEu[;=KJwYvC!01E !f9H̖}Jfƒǎ v%[޲ @$Ԭ)#=V_C^zo3{5W\"JN@Gu;zS r&hܗGr)}Z.\@zR,0"F憉cԬP h,pxF|C@ɫ,+)Dд]5~[׫ժhYuMEɼRDHP 2PT@ZY8P&C b141l6nm}JC*Y׵Mc"}?Tly[( ,WUs.CʃH..xc x|'26a,S8R$A'Se:>E R^DBzkc &141'``@.>>H}Wm$*T+Lm@@ 4O|1Ws_K/<ͧ5T.920U&$q1n*.P+chYUp:²J`vX{bPQE$R\9D ]&(䡃|u7o\ğ 7_}w]&Qk ivBWn>km3H"D%'D`1bfD±ٲl61c990#+vl6f)Qig]tMq,, rr4r.%gAEd<8KGZj){M92Hmy `"x4$FdSpflM AB+H)~P}yՆ91C*#>W]u'O>o1 XSR_k]IXwTSmXdlNtiv(&@/DG׃e_ܦ,w5JE9ҐRi[Po};zmX0iF ss mR[` $dHNu yZ7dĝ>fl7*Y7k5 Zrm1!`qm)#1\T4Z]ـ~1HY/Wfۥ! CѶkmX͙h~LG9CrQʆS7ƐãgqiiH8ڈKP)EF36-t~lmCc MDdF10m6"3UzYo2/JRB"R 07C JK$|o|ߍ!f$$j.>-@Sd;Moh S?1}0U$\ݤia~siA94 )2Tr/ٜ Cծ#␇S0wżki~ﶻRԊ&|p*dIYJȡiiڦn6r\*hf&6 Z 6$m B 73S"vTD5kÄ!1ˋhvv7XQEigMlBPDRJ!%Q^,EPjz RQ'A:zup%qˑ=fRr"0j7rE 1CiuE8q~r! %~߯/IJvձQ&*c1h #2Hyqr_7y:Hߧ: F$4[Bٚɽ(6ا[H^e)oY 6mYje\r)ͦ戚C`7QJɎ|aE*ky7k[nɢn6v@H(0"\,gԸZA¦iiBܬ7%vn6A+bR f͔Q~BPVa HLH R4'M9@`|?Q5rn~(cT)%TFމƔ`N;9옘wTySNZL M̎1rlͻս{BMtKA wa"P9!m׻jM G!۞PHuafQy C?<=j:v'w_{iMX $Ys΢RDJfi~ߎ/*\1Dff"aRXI4EZu]yە ʕ+!R;w!YۈV_S!Gb:,5zm-vvm۴~eɐ3pd-KXu\ )`4M4Y/WrE7[ۮ1||%0aTC@Krq`Hi$\ !@LDVsꉁR)國,ֶi5e/ɇJ9~3Nݘ`)h3G]"E@f Cl@1)|ݡϱ !7GOQ8ucBNC@.9ŪHiچB2]M[ b / ()G{̇?]IHԳOKdik~ɩqEj-r=OU@(SwlKtD$;K?ͶD4ڶmONNfl6D|5a%{wZvű~"(%h.b-P`?rM|"[]nK-<DJJ)< C? NHԄ؄&Ƹ^-Wjy;"E ]v(Hӄ&Թ()u7C% U5'xiL5 i.ƵqSU QV LE=kAm זwɪ"']X-#ĺ%֨2.֗.90[~:+X\ohRJ.zƶmsN?<턇L:°R?<O^~ɧ}wlXsQ)ITҶge̍cSP2JDȐv/YThlo۴$" EE 1@QP9#z"1-1PUjH$R84TѢ)nfU<塛.kE$! 08AhWzZ-ڶkb n~XX`]pyf]ҐR p_J]8V?}B> dsA"`'p;sRѓn6kgm 1Y>CJw7,pv:?r>$!EKVDY)bP@,c-&nFE{]R\E_}lٞb!F:Rrɹ0~0q1HxXӖyjSD""e,EHN2 2TBNO"۶1Z{nHI$9+ĬitFJ’ F@c,HLa!Fnb 1LC<@ 3rNblHIJb15NڶiC&W̉ՙMsz)*})D10#ֹYWm(Vz%Ǒ5ڏZ'4(h΢ejF" * !"1l>5a,%3p .޽ jrծi͒wCJ9ȵ++llܧArCmv@%g@1UYП1Dze&P>?//"~ٯ+,"foRJє50 NѩlWAP$=PGxɩʷRJJ ZTc8=~vfضiH@&uŰn/RrΘfdB׶:-UDH9YJ HBmubS=~Lm VA @bcPr%asIXg1ChjZ],/+$캶Zϫg(tc֯ƏF6`y!" 1TR'Wm*ePtڙ v :H%3bκ&nu$fm[EE umiB׶10諺tf0 4׮^Ymwrӓg"4}*aR$0 ~H,ݬCD<@x@$ 1J<_HaG?ϢO>W^}Ų:mhΒR^3 O')rndR/G}'bK nXڵ3fsW}RiNDm&4D8ogg'gawqjΫgm(K]4M;vVA,CD&9d8V1l4_RI9ǩ*[u#bhxyy\Vf]qdPГy'Uޔn}P7PӸwJ< [0X%̂t' xjɳ&2ش]۴H!kږjdL*E ܽX.W$PR\;ֶ9_vzY͕x3 Y4Cymb1yR谷ƙ[5{=mnMUsr~g;P6( \J))` i$r& k ฼f:>a@&ÇuFٻ~(6̞*"^z6;/mγ~lv}cm묦I!SIyHEm&LiҐ7Cr4:&*B*JH&ZIh&41:3$zq-8ֈR$sfȂ) S zif-q&hQuQRUe m_NWg:lEFq8"oT bu {(d 8$&iMtmc@5 DUr٬7s4-JpڵW;w>ί_?o9S*)i! ih !"[@*Eql6CޮfX\N/+~;~ Qv+۝o%*m{_X[iQQf> u/GU3 w"YheOql `L'1zS-zB Z.W)|:EEv-8)\Jfm6R~]ڦGF\R`NiC 1B83Q < Ruq")ȥhI*R,EQC];ږruXq m6.[^ -)`? # )⿏R@RCk#ڶ-Z3h.c4ch30uҸZ W`EhC:撴*ey[fFOHիzX̮R{rzꕳ,>X6)vl63ž }NR$ in[ft̂SG<)ZڽM"q<OYY\Kxɗ_yk|#>! `ABLyt6*0fjuDpMf#eRݜuj <9[4M 4-#v Ys n_R)ETrʩ2BBM !aT16) 0 >(@ź{Ka c BMSWBDd-%rJ%g|QPFi@bZBv]bCPjJ Y8]uwɂgu|EĮȀ $ZCVa`ԺQAr.*"r6狮4Ql4cݘJDƮ ،m,6g$Jh=ܡ߭/mnO; ԟ<ͧCVBXަC 1>75p_4 b X'Lz;TU pq2fRi۶m0 }ߧtAJι 9KRfdn  P~Hf}Aє~ Tc2=(\2DEf&y"pj-MJ Xn, aav<"bɢa$}nF̪ߊw&&nqzeHmt&0պ*H?eZ xSm\F16s9ԥk)9\}tI*v޷5fG#6Toz-vF\=chD)!5[zyF FBpO>ǟF3js@Te͠u]̭//yYܝ>{|Kar{wsw/_}rK vw9 1Ly5KU6O(ZEZ0Z4s?6m#FºTo`uw]iBrgXɥ֢U9Fsk;gw󏭙S-ƤxIr]&03cv@I\0"*cH!>\/iZy 1d*ɹ[Mb}߳G:!DN Tu늀)'DRuR*}UDͼKrndFЉcdfio>kRJALcJ-f$ )uYyT54}v㰬t몦3#Wա}O@ P60FdO>$"Eԁ ] IDAT~xMGӾ8&78t\RH#㪘>ֲLS~ 3"UTt~y]O/ t7.Ӳwwnץ/VkBd%J7bPhs;SI?7!.k?_H1u!0(Ԓj-xz; ^nE4w)]OofCQimUyɏϓT!fj*G1SRߧ\URJ1qR`5%׺yiuW ,qX] ]uo>r~d"BRJɹԊf1u)FBʥ<ͥ"'n R+oDcdQ#j-=R*{Pawno}K"F w7wwUϿ}r;Dk.5Y撗s\]i/dL UKѬ?+1t5_wN'\ Gfd|'j0XLUZ >n^&,ݖdmcZPST}7cN!qq&FURp{&]6`#CR:O˚˚S.Ym)p"dU @T S!tC7c^ׯoBT6H<&R@FIM SVk@(@x3xUGJی aۥK]b_//,*G@R 1RAJ5543F@DH *~@0qm?AUK!R Fb@HKgZ|~yՔm nF=" ,y^sZEr>C˯]o>W>~1\sn~-m,תe- H-EDݳBH~7j&Z?wO?{P+KN]"WY[rq6q:huNCk{1o/; gjۧա$y;ChYUHHdn:>>HsRZ*ʺ.f bjH 벊Hw}kj11eP̌D*lذUۖ/- bڴ}="q|4E΀f˲zgi 0Qv56;H@xsZ~6OCX D˺$<󚏇gǡSs\KEJHYu5hG?;)n"">㟆?q0p`bS59AH"͂'wnSx&4h+V}B}`&U݁jHČHޜ3}d*5>Xs-V W`]ZJU]:4[-@.Ϗv؍awx9މ"fi&"^ۤFU7 t1u)䲮yYTf"˹Z#pS(N1Q81H뒗iZV-p pXhEJ0y+OU("40-"jDֻܤ543$@WЫ *@ b*ENt췂eR@cw+q=bKu^)Db"#$fuZc.8<́)$bVpw:ݾsvyĄ@NxWվek.p8ð K^uul]K:]׵<;7d[fb?O~O__WNݸ楊 8qGD`ؖf[":x2s #hyEb[9 G>l6eS B5QOOd7oRLOvNݷqSLY"U!M`K).53L9ukF4xRrAd_=ثmsF”*{SZ)!&7Tj{<4p8P &wx9oGd{|CY h@nvwnqbcdnO6l! oF(DP%$ZËTHIcHa캔Z|9;0 U-4]www|կc p8}ּqq7a\|ΥԢEIR*;&t> 2 bJVO_g?w)%B,;N;Ny}<TQdpUbm`D;Qܴt{Hf Hy׼(E`U!z'y2LLMxܥ.Kv;[y^%0F>( YY@T ԙfʩ:_ۖRea~T< ǬV -x $eC1nJ*#!qJ1v麮<3]=-3W"Υ 5[k"}c^ 沬:/BJ)J]SHO5%0 363:lQN6m?G9a6@3twZEآn 4-|5 1R(:\?b!1[C 8:˴ȧۛ;5R H=??iwwww7߾g15@ooO7cwTa|Yy-Ur.e͹ԪFJ-vV"+p?g??~gdӚn~ Fk<@T13f&46A[T]l֊^;\T*UL5,/KJA޽\lL .MNJU&<ޕ[k6 @XeN]ߥER!!cJ)}?Ij9~ TJ R?􁃙RKUp 2Q9uRbF*5RU0ƐbG事KFL1"3Ii5{269x}s^WS\fc<.ڌ9xCPrsDgJT\Z˭n8r' 12" HmEQmvo5U}CEz ǦF2ľ$'zTL(2!( 0sbK˴,*!% qЁ.vcBzy9w]=b.UJpww{o=^&lzwss{W.שB`o>;O]ߋu]\Tj-5"ْT_m93Sub\7RsV ~4ʊ*?ݞnJ)nH]p|DťWm(zdh])!{«-DnvӀ𱀠 ˼JB\"͌E|`R:iw~y]G~WBץ-8ZHQ|s:v07oN] avn)~wts7ٝ!:""<ƮK2MS}RH1m:DęPUֲ:r w8r)nݡR1ŚUͿk_1Sv3&~[)h]T\Zni1&_xMV <B dr͆et@d2BC$pԀCv}j"p!RZU9g.}|怩w&Q{|||Zs;OV"%.!KV5HB3@fCaaB&9/CO0 }R: )@܎@ADݨRpߛcD3@j.&"{#tirx{w1ZEDrΗ39Oo=}ͣooO2ןrj0xs=qg/˼sY #C;7Kct@|k 10 0ӟ᳻ux8Sz`2 D_z!+[e̼4 qlAh=[ <h@*fb4{Kr)w~膱[m^'_Z9y8]wO65U/3!xff*:5VzI-Njo>{CJx]"a=sd"L!&"`\i^uyYFx*)w0Dq0Zrw; ).5m% ]KP~ #(Ph&&m}[눭UaC{[KOρR|s.f)%_5i믈QMC.~L' ,Y^ jyxs{{׏_~JUU"LO~\*b8`RK)"B$5E'?xzzz )D LT rVgѸy)? aE:됆\.ʡR4 $p¶j V bni2.ƄWF y]JO\> ̸.7ieyٕ3€̻.10@aK~;&֪UZ}w=~{, |0ݼ,ELKLKuTI۵I7n?59;!v^;́*?T:bL)?rf&d^9KT V \ŷ|8vbFH}MӻA=(ԥZE 9#nqf Zs-pw]/U)=p{;N/~k.HR4-k."0]M*"k%RORZJ)eL2-2/˳={ry\CXŲf8 "Z*襝QRy9{{R]cºRKYi:DLݫ#i3j j) }ǽ2@8ݲ, ˒"~3q fUC黌N9=!DVAns.*ԾocJ)?!?7_|~x<BxyYt7)~"]Eú1s\01XRJ] Q견wV&3"VELr^M 0Rq ~?<:{G"`LQ!pKݷ*0-" 12on?.@4گCҙ𱸋踿ii`ꇮ:/Y԰vwn[l)Lm:1G!K#ə&~cڍ?ޯ7q? r9_U{ d%&C`{)R\eZS:ѹ1V]}^u˼.rr~_ j")at3R >\겮z43S7 8j.Ds"UUC DQ+`*Jh wyb(q)Rⱏhb1Ljdkj!0=?NnH@ %m[P+TQk" !*7  QQ7ko" u]":^uKo&@55r%/v঩zۛa!Q1@w?u9OwyK_|/yqi\&".YZP:JsKD\ =}Loov(1./7? ezR> ^>ZK0l&uʥ:ZKW=7ER.),3g_~p`1#~7 !qrG˹Ru bc !a Dk.x}]TTjZ+o@v&Z5þ-cHnLEEH݌Z4EO#9؅+So $=Ao|&$E os "HdsT1 Mc {\U3$RFE%zH7ۛ~x/!qH* ]?Wߜ_b`ڊFԴy jL˴}4TQPQLoa!_|Uk>㿔CWKݏu]9DPskN1PKUuYۮZDd^3&FƳWMb<Nu=Ͽ tss쇤"]T2bF>1<-&KY*Z~Ŕe(RB`0<ތǛJU)*Db`K]|bO,]R8sDLC"*k^漮"A_UؖUBx&tK{筃YzșfkTS!fR RL1rZTĊh-C ̀!E]x-mbPMP}9Yjbz{3$.Ӳl8m䚡)Z>ӷ/cW!P$-"oFB&bJ00PLFC@6\"0G #Ǟ<iɥs Q1*uapN_{:s0r ?>}W{|ӻkgWk9ݘkߗw"@"^qޫsoP\e +"EB-Vi'UvEdIsALOp<(9?{~R@&F ˒bȨJ00%uM!dP)RJ2%X!@*{XR*KhFOә 0 ʳN p5!r +-@%YZC qߍ6~*55u !Eb5S%@5B+QbAt(8{m  VUNiy1PhRES4jU^RSMfy4M~N<@YWtYѬ!pT)yj2HOޮϏݏYͪ#qT=65¸SgH`uf!k`DLC;o1HU+a0PPh۾mm_| !#n9;M x:>EԔC UPyr̔%!hRrx:wow]C9M+Qu$0mۮwĹRYX4S,钒I50"t:M)I^TqNt:q%? #&7\5K43"mw[$1-i^yIbnwAP^X4t(4NxIJKbfuUӔ發hdc?g~7 Oί/O^Pu׵]9jJ#9>LD*4RZd9 8cNyZdɚsz80峗󏯯B * U]!q@"f-܂lAM e10TŘ,)-IR8P,J cChߵ1<-FZU;GD!u Z}4.3^o77413,auIի U L*{0 Y@ Ĉu OelK -#!JE nnsxXTbj ˩I# 0@6S@5a׶ͫg/e"Ble.j) FEYDlky^Woo/'mS/BMݬ89(7> MJr!1+ym)4+o*6 󓧗@MsJ9/_)R&l7U ƹBUc$, yBaQ5?Wu$ |ɗGw;nQpv(0@@n "W#0\EV3L)j^04_|o槀Dvx3Y4B70Tx!r7PuF/_MSK%+VTP:/L Y.$22iMM /!` g z  P!c_*@vmLOO1Fl1`Njy**%LoQd @1-z%w> &b}{ΟP'ܴFP DӒBYն77ǻ!"0?' L0  f jJw~W/n^]s`rcRV*XQ$JrԌ)(Ex|Aߞǿ_(U9}_[l(x3ƽ6W*>3L),8+h"(]j0ۮ 1gC?計xu]JAv!i@U(a eD^ov{[*|<vK .B  }ݮڦiÓw/;UPa`-C vd(Ŀb;̴%-t<Ï? P2b@fLjRT#25ދhhHUTm3Oi;]c#%JW2ixLd8wA TDM늻:)11؜|sғ@\T)(>~{70}fu}8AH1rXJך CDQW}[ZE0,jCF)Y,%")Kh'?m7W b hL5hydQ4%EU\2><4Me}se5S U 3*iZSZMo5Vc C°v!Z,c/rSc%ЇHfj˼v1,7gMa9C6٩Tw]NU˨LdjnI3!__hlՖ^s,-:>'|lm8ln]\..gnc1RZ\'q+8N_<4t>}d1CQUD-%HH@(A4%rĮk@ĊN3PG͔|V0P[KyK55qD7z3fH{FWG/TYI*!Oæp0!!dH/m(ů!` !RQ+qK IDATPr׊i!w;#nϪN3[ΏL3P3DY90aZB[U1 %M& <#R^ɑk, J"őe"R~ R: [CGz@bAx $zcrUs&SMYL ^υ`'/#eNgRJ!|n_9g绳~麶m25r#9 aYRZx'}BraKP՜P_ qZk3%ڶjiZܿ}%ejF[-PօܥX5P}WM.ꑷ[?@ ȁ7d4d}8MM۾yuoۺbc޿M/_,XFjHS :}sl*".2 j^oB*"fd&f-,ɓcuw˽匬*9Iǔ붪5DM в,i6alasR,s\;YMѫI "|}?osvuKl7u]s`bցjPB*3R#sq۾%{oĴοV8x:m__Ð6۶~߇P-(hW[9 ]W3G%Mu$;.qӼaUJ8ngg˟Դ6 Q{;nw}zcx  c^=tp~_Y: bNLke/~6|ӬT 0/˒Muq$fzEM"(+hqT#X[F3(ryܔw<#; Fp,]!)lk]$$o7$mvo) npj7k6ɐ -CRPuMU̿w#n)26pPk8,cf03P 2hCM4P.Vbo5ryIJd10`6ewiE[ƄH8\2t Ń["]fދ?_yabf׷mCL 4En_8 0>|ޅDXM {1sכ txYF`!U-rﺮm̊DD+B`d^Ɍf*X?p~{uU& <#fx61=,dNN1.UP.!!CjH)]U]ݟ0LF$4S1ER$LeqAC Q"1 S#H7m0p)^Z;&RruU7w'mG&UUXQao3"H }ܶLRleB;sZr'Ͷ;Î/0J>vG"̌tPr.)kV뺪i8AcUQ X{Sn_ #},`J߁lL(f%$EA ƠsӒ<9~ S~Dg;HqcA!&?(JI* |E=wۻI06s8s=2RB⑳'!V7׷=޳F@#O"I9F0{rLÜ%s/T{`k>ط wG? M;ns61#fLH$"#fS4 72^!z#@Q[qnf87M53 xxJu]W}6YEDMV6"zEYUG(" R];]ė/^|8xqS}6Is1cۿ/ﺾzvMDٗ=yH˲7>/#X&e|p9;]P7yݴLC/U*-2UAD2ⴵQ}W7YՇ˪MSUde1*P ) *"W]>+uA<^ e' V3DӦ*83@A@21H>O (0!Wj`Sn&BC6h ><b1]&fv\!(iᤏ_4i"p6-R{MIMv?E1V3)~=w@QB 䂢vC9e9b phs|2#a;GȇviIRXI莲v]TM{n]{,<wWiiwmW`]: Ӓv<O?l\WaiO"q*]PU~?_Wgoߴ1-IN˴,9/N3Ǫ `w٧4wFBLmMuw3s7ã";*ݭi||+ڪDR*U 10ٗɜWr_O"!ݙ>?wK{(`,dYOK 6m~,!0fVDC dbT ;()!10!d%DƱUfADy*jhH9J|rqww?<|X]~4iiBdEPDDiɦeA%@c-`B.;Cm]O\-b A&)__7mMIE = FH%m+(vdUޮ㮌/'dMum8 #2,eQ dei`"gqpXdNzC!zò%˧dr"9-iꪊHrupGf n}#.G[]4cݶ!x,ti9VÙ@j&RJ/?~Ov2W1- X*K4Y7xp@d״ DBQ"9Z,PjV/ip]6Q 6դc@ UU@8!ݠHfdDeXM5FEr6+JѦѐԲ# 1ŗT䩽{vv6a*L8 L1b@ !_S9'4P)b T5 $5!2< M9v͊%t *00L9(s<3k+k%jVXXrvtaoM]}Cn:T2}P'Lf~7&dfu 3ђ{`ŝ%uյu]WfCQ8\ܗ35]M8\Bf6M] `Fๆn Gdb>64nj00dxvviO}j>4L.Oë[X'3Q92fw 1F^5,g0.Y 3mCD\^sV$q4 ُL9eI9g^g̾Jcj3-CKU`@[} 9iʕRӒEM$MmSH@~~e a̎BϜHQ2L|acPOr|8=Bm{gQy^vb1Z6̠_]`A) $xZٷC FՙY)p 鱒iTqx;P$IX,q,[rkYIxy#J)}v~Oݏy$yhEnĝYn w: sAqKNY{_0@"C3Y [W T,Nx<΁B}5H_9 /0eU|(Y\)THLU9%K4{O 1 Uh1r5{ tD㒖v?0;~姟~6i:yip4ώmut8}IEu4^Kr'gQ'r =-2f*a%WL QTC*%/K6ؤ`N*8[)eE%\V+HƑnP7I)'Ch]I|e#%pwpv3;0 ݝbf#f)3A"sRnS#q۴fS^fBWWͶmdCT2dSP ƥW6DHBVߡb4L5_KS zQ15"\$+ !vsN8SZ2>VK> f?rΏ]ǿz̤iJle(x " MI,fnPU_ a|4 LݦZZZ1bpuUWwŜSZR!DgS:Ͱcba//O4 4ܴUv_\Ϟo<.QRSJ)]\_^x|xןcЏqq0QM$jdj21:'Y6ĈDKJbꝸ2޺h2X 7G-SJ SUam7V"~,1p`qCݶu]}cLMۇϯ^r0KN9p^_^\z_ iZ<ݨIEGDsZNң˴QDc;GUEI":NUD%njIiYY!:OOT@@"^Ԥj^Ujx82M}n6ۜEF88G1s]@ $"t)'G`DTĜs23,sRN -iXU!# ͫ6MGjӆ:eU5 YTla3#6C>W!q]5(苸d3V(Y= 9 ;TdQQ˞Wp 5 A-#~E {˳M]N\NDŽuk~p8lۮ5sjVq0@ɂh2%=6O1"@LB(pZʰug3z,E~US"~ !8JMvmWM8]/MS3@q"RS.vvۦ{k"L930LggۮoXK:OKvmch[?xss:Na8qr>}zufiONhOBwHvH~$q!vm1LӘS*g2yiy^L}#o?BFHE|1*{~swp$+BVMt8?8;?#A2G$8ƨ<b*&ZR/c%j$Ycn,^f2gu10">V{U3L |iR4؍ IDATB$ںicTd0 ,%fĵM0̝)P&fYR 1VEȘ:7 QiqNYڦ@ )四[ %)RN\XU&"[8v ||q~ Ex;cqz?&aN_Z4`V76rUUa$1ͦcyyl:3Xwǩ񋀿]ۦϟrqEuoWiQF4pZ晙Ҳ\",' 2 =vWrU/9KJ}?lniHj&S%F֚bi"7g)Ry>޷Mݼc$NZ;͞\ښN<ͳߊ̨b`bD R9U~BDU}aRΘFP)%UNC׷ hpK"kLU[u,c^rVq R4Pթt_"1Rn׵ۨwe>:F$΅t,Xm@cû .-KUW"vv~&9/Kz_9«< )0 >:b\H~Yt?C]ǿO~<="L Пx\ʳ`@*jdOsh].XzwQL-s?lk˔ aۇTnӜTm o!f˴o7XnTMu'L1T.^^|5b*;́ArZ/`(kۊHT _`\UZDٕsoxyC2!a fS17uJ(*&"FC@&ILԟCNp00j}궮0dUnS7XF9 Wu(0.@7ݦ:.)ZRpZOc^BT4SJ]יyqy%;eGhV7iq#b?HBY;Q*j^p16U_~q)e$Ȩ`3#e*s.U103 'Yqi ɻ4n 7T w1ȪKN"DT753)QXrCU&'O.?%~6}{W/_KNC^RyG@D˔ ^9[]SARӦTU6mNBDBAαĀ?cG~8/"VEH9A*W.J%$15)͜sUlt}p.Tn7]U1aY I$Sȏ8\3qjZE۾`f)fm뻏>~q?UÛMf=eui"6yfbHl%KDY 2~*edC YC(U*Gw>Yk釹N,R *ͼU眹so\]lMrvDڦFJ}S1";7)e],?f3<4;E,{ /.// ˜} _Eͻ`Qik qLr{@d3#fӢ^FPbv}˺.̜朶ۍN˗yyq6$ N$ $t:ʚf`b+XH P=c/LNnOg_..;$YJ)yE <9n퀬 J@HSpKEfP%C{}pnۜӺ#Hɹ(8sq)Բ;C sZTJ{J(i*A8FaTğ|on?B~x-h h-s݈\RC,MS* y%ԛ< V SFC5u.yJk|6{piLE^uۮqcVG ]9]=,1ŪVLSXj"x: 3 2^ Yelw?];ϏF޾oxy1mžkIcpYSlbWڒ0{3 ܯ˚4M<v7eY+!&Z*kbqyyUd1i2UÊf>24)tvЗRFr}ϩ)?J*`V^kW)g*} S֢Ŋš4b5B A\RѼ c"VtZRRDQxfO\$"m6;D| Xft]o^^]vCKRxkPSs1__QھIk.84YBX"=]zз-`f-lB!^=3KNKByr6dbaB,9Ca+ZGHh޹# 3OKNyhzBRUߩSutC?1Ǿj|q?g!yfYǑ˲Ӽۻa]'O.N.[+q]RJ}Mo(U}K.%b4)yZ\F͗%Ejȵnd_=zt.hs4k!@J9y~]eYP@"s.W JHd@hVK9=r5 jƣGE@%ywlө~eb4y6jJ.qZhB0#Fdy knoof`@q%Uf @QSIH.k.5[B]0fF "QrFZwDrD*/dѰߡBZ8HV>dX"7GSdhلL Y֔R^"4 4u^)PӵmDF$$D!.Ě! 3{ `1ڗRɇb>t\g d~>- <<"s3SB0?%P: vz,y۞ZH-=XZ<-]41qUUtĔ_Sh뚋ROWon<7OSl鈘}sYt:-ӋW]js4QRfšR.;a\ܴͲ,]2Sy^5}0y;Ľg<&.TjY@ɩ,,"CpssL`Tza1VRrIsFBzT" ʭvuunoBhBs%4K6p{sY~7 0)(\XJK_ ?կ޻z]ק\*Nf@P P!DWWмWDDY^ hQUY)UB`ƳHxnb(+$Ѧ`bVVf~{9̥^PdCD FU)yeM(2q\J.mׅ$$AXMY2DT +0 ޼x%񣢚ճ>6Hx"[m62pRc-bT;Ge?F6տgϟB?lU ,HL  1Dn{WSL* !GiV 5Oyū뗯|PO)Vcӄ b)+už]m?|rM~]n7m>@E02ڼ?y]Ml)?{aĵ,Vke4OR8VV͚I 5 =K^`9PgO/?HzwL4Mr }JɓV$[iB),\!v~<˺`BT 8 Gy8}k?vV@وɑ SJ܄i&.5T0v%O5cD6Zى`fiMO0=艑|fPvV~|~61ҙ 벮K -K+m/Ā0AQgyKVax޾x]2G<}6~ :[Vr{woosJE;a.#{>뺲Z4^hg}6HT= @fL)#k6iڶU)i UG2k=M)ro}{UJIUey@IĀk0l6:'wMiٶMT x_ 1 lB O?yS˫"f4௟IdtZa7}[VEd` 8iDA3 E #3|q稀>-40/\jʠFf_":HrV0m*k%iVii @ Zh&zh12X1P@D6z>wO>/?g#*Yr3"y;~G{f!\g'xpQU!ĬmLL =A6-z:Ηm}@,61x-D}ߴm5~H>,\ sN*QBZ`Ӽ,Ό`Cl~\򲦔 EODpzK!Db mۜNiZR$-4eeIƄČ(6?|7JulQOU&DC*(023pej``Daz CPEbbN* bьXU,ˊq>v3@vE YsY,&4Y5Ҳ&4]ADץC5o0i$d t e8܏r!no(uvM iM7׷m߆ Am^VWO~S$___/׿֟|#He#zyŌN'!CD*%@ĜKEj1eZڶک*l$g@f6mR=;6S*i-9r^j)MYLl6(Arz(uhg뾳%B2RNɴ bQ]S^t:7㋗iG._x2\ `Z<Q۴RWDZT \;T=@xuJd6qN3WP=VS,j? xb?ӟKi-!J PXHf?WݫmHmsNӢE9[M%ueF)9Fai~?CD/_NBb50k%1p: Pf~:qOahD$R39u1cH.V ܼi[rd@%e^K.뼖b"$LnWՒ֔c9% ̆!$RS򫙻ipǦiDfSy512/Zd?+Z]eFg|q{y2 }ly6,6Q^l^#$YHv+p#O}5iєm>f\sNk)Y (,䔝%L MUMy=VEޅϒP]r&Ea3oT5}}v7w{feN"J חseiwIa2t=4L79٩kwZs"2-猯i\^KѢ)򺤴o $fn֬q];|H_?Pp.P[CMplb7 `ƄiIXwP Gۃ",S(S`ܞK9%D!l6ݓ{=9&̓4z 6ͣE|[AHZJVB !YڮϦ15cƶ"c fFnoob18QtM#!:@ >k ] 2oz"ls?M 2Ƭv $mx2пs0庬MCWeDă%H."1ӻv͊ݿ}s}}{% fxj: .W[ZҀ[J QIy]Ҳvo db$ 3#GF$3!pNE0SCX֔֕DJ"ŋ~+iӄGU9iō"'L~4\ܤnTo?z9@⫖JJ0"fS)KlcQDBx(inqж 1 1jcDT_Ozk3rZ.o{fŬLx:Ndnvfڴo >/_lϾ.}U IQ*xBms7Us:'Sx,, ό]=~tqh:P`r6 8Pc&r3UmJ,u]4ArvK.ZZ}wi73#P5VԸ v eoC>ys3x޸`%M ~P`ZS9x0qg콣lUyB75?s>O״)n6}h 8%8c6, |Nಳ-HhZJ(1HL뼎)01ƘSX 8Y7.vٟ޷w8 @6LTy}VED\񵁙^~fK'DJi4dcQZ8JѢZ,\r+@ڶ$,ܷmlb,4ZWՈYb ˦S24=tCK3i$m4oVU_7b%i.By>)xh}$˪9e 92bI)X4[)fKa4 CD 5bfLDg?9 Rz5=xV6hJg))`rCd_ii1PU|i P!!J5@ٞPP68 97R8AkC͚I^g9U>胟4QҲߟ!"8}YA$"yxM=$PyBWZB&kC ~5-N2eG IDm C&yc_[ 9MbD-⏾4f٦i.G?_C -3cf>!ܿ5}!z8jmzm׫. aeH_=m#OAmRL4m`8y = T(K@HfgD#6oIqE,^MZY6CM7~cMKZ̆m] ˼'Ǚ0ޫŰCa6īU48M'>@7%# RSжk_|ņӲ䕈7f^fbƩ| s0wEe |(ir`'CbҢVtFq*ԍQ}Sl"G6bfx4Z8HL+Hv`CoDa{%evCD`,s>%blnn/k@矿y|xe]࣏?\# Xs^|ji[-%Ҫj*D4b7yBa;d:Na\Ke}lbEBlmM?̧q_ʣWsIE!x+\8uɻaM۾}VMa1yY"m.]q 4?s/2_k]jKrb# H,^\JFW}~U_R=' %n'9gUM)ֵN7n#Sl6CDтmWsshN`M -NKOSB_yY64/~p 7H~fR263ECnx8.8#/L%\@fwsYaZRf<_Xkr.n#.RC!Ȏ)0CmerwZ{nG'W*"SN)ܦu%B&c-4.ߏSZj *:K ^)Dn ѻkZn޾!fMX%gow^s7w> hw4M7}.۸v9唗yakJ"~z s8`%[NeUBԶͶciӡۆEHaW~S*Q鴘~7!ƶa ~\Ւ͌9H`![]"Rq ZA!0#!~mۑ0 u/\ɝ^[wa6VMpyqZ-vL㾪`5}v#f2<c,i]߄ ʌZ,Ny]1Ƴ5 3a|6"VfSyM&pw뺞NtQ Mlq4AL s4_+zwsxu)6A̲!zH{łx@ǒQ@Eϡe@ˎJ+_D6mcMl#TTm]4M6۾9%&>*B jQ3GiuD.KeY*i՛$,D~۶in]8m۴mcKFqptګ _|%kOZp?DD.-Z Q-]X۝Ct V✏)3eY> LǼHT2@`h⃺{%("8M0|켔> Vp߈3!li$3]ӂHͮi8/PCҚXc]|=0`!#{L `HжڶUyYq*9 B1k6e"cv;hҫׯ>O/..ߙŢ.4?KdP5-P *fT@KZ3rZӚKIM4m61R`f]7MxppG?_]\B%1_Wv7ww>yuR{X'M%#pg BnG[|Nm߷4. fw?S?S7/akg۵'7M mED͈P!$!EYzԇ<6fk0dR:34NM/߾AtxV mCv@L]VFta9w֌ pCKk(? V=wa W}6LZ|826$Or{u($IДyC  L)8y2Bd&A@0В=^ jgծRc SkZ- Qd[PLjYKlD!GyA4l75\bpN4Oi] B4QBeC0o+onb$VحXGAgۻO^ۨލsEhYK)Eo*M Ĭj}KIHDXDBO>zU@U~IAnw?n7>|'Oq\KF&-ZJKR{b&U20oEQ`72a!&NJB1dMms:NEK}ÿ_='3tm֌@9j*U KɞТDoyr7ױo\Ea$;C-vs:.OlMZ87G6m#Ca80r@}8>Ύ'Z/?GEtme&$)ñi﻾ 1ZJr{Mhޡ$34Upθ jwvp i\XKNUC1,6#OqYxjST{S5}jGuMVJ"DȈ$̂|zmP=(fPJ#a.gUHHL  65 *F0/nw4/˺߯ tM)˲r<mض :_inOK v;iD`4={UM H*8 <ؖz78ֆ%8Ʃhf^5y}`Q/eiOwƀ]׈F/լM͍ZIHu'괖(4i7X6ZfBp.p"^f`PJX失i:7\evmf{y#"6H}?`& Y#JNʈ`@RfX4˜a!M㼮 O>O/~~aad7[4ɮ2TVU[-AxFH `FDk=H AUYy޺VqCfGk-F^ݮ9s?w?__u]Br_~aٽ;Oq79?*4x D0]*@$^hx/~. !1R :򃐢c0c죴2ӴD.R:t]: PJ !wuTrYsj2s"BJ1zU5; ȱ-sw@4I} ))]p y)bb?tȲǟBfp[?|B:DHfkȞ$o'O`RK)VU%Z2/݆˺zFe~>1a< k9ɇ0@`Ғ}qҹu^N˩J`LsUe:O`pssT a5oIIQѯRŐ̀KO~/o|Wx뒪F}7:mR@TETNdz\j)E4}'jL$RW5j`uYS))ƛma˺~tۍl'P~m6׍jXRJ."khRPEZكHˠK/;X!3K^YUݶ Bok1J3_veoub !?dVL֔+<Ze>Χ*l,uk<<*oLЄC:3>vIRXsQBzA@H@p{wDoa+ӺwB$PnW'R_ fz7(v}L]p^sA""@)tZEK}뜗yz:L6n3L9| =C"z_[$Z+JB;VTIG:. !h)^Eu҂6潷m]꺬b"/@&bܜ!y%c:WhD5"9<z(}TͰ &Ol^qFn޴y}zyyʼn_7 yM̯LH60"(E\BC"S{uUL:DχԀh9/:NNp̥\aWgX"0}{N'^ `|LCbDfo.1+U/.~zeӒ׼m&t1 iKU4R@G?fӯC*U 9-՗Lwr(#Dwηot|Ab5Y 7<ĞPI=~hU"F*@RDF7??!p7bZOTL#+( 1pJ#y-DZ54Qj\ʇDZbߎ}λRj)t1c`}RǀTeK143#z A nt%oq\]yB04;ѫb%gW"%&N~"!bsרHɥ伮V1 뻶fZuS<^k"@ YyY{pDT*2ה)z|= |ZvL:OVCOH-XWgdlt35/ F>=D>a(9]\B p ch=^Nj^AybyYہ'`SH|LV[R\z|>:)ZiHMUDAr\dU޼M:Msu q0Vֵ:޳DDrx_l(r:eZطcC q }Zئy-:s72H8:77yW Ʊ7t(7k-OS?tb Fr~KyFwT[\F%S ԁ"KG5˵*Gs]VE&pY ̀zc|oSuRa۷wND߽{8lo^ӼPdd3O)23QH@Lb )4Yjl.DUM<ϫRkE7 F&b 5bq߬Zb1xOt<[ RImeSN^UkZMҵKR h㩔jfQgZ_mFsǞ jW6/8 aRJ~0s10(eE,L 0$G~11 1q>?Gmfnھ UGԳV:g"p1{ !v@Q)j^`&l4SZ3#qZ}ߏ7נw_˥i1Exs2 c)|  㲮]81%J H8RB O)#N8 Dp6G])-NBUU@) 5"1P ??~~/P5PP/\8=iYEzoR `&Av!& /~>ySsUuDb)U4V# /T 7IZjf|5FOχُ0~:-vBvvWlpF5fK, '.N@rˡT1 Qzn[_,!pK.@GÓ$Ӧf8 P1.7L (j֦q)g氮tZR..!DDq+i㹨DAPUM3%j{s[,z:owcɯm@F![|:1xEFH=<@M1\﷛a&+5ԙJ%CTT!H(3 }5@ jlLrb?.uu7›W~GyCRP;|c1 YϣF$%벂!0c*b* s%vcT=ا|]{`R *sW+jF p6l/&6@ݦUZW/Xpkׅkf˖aUB-wR #Rm~E(몀#!H ] yyY#0 a˜.]էDY@5@*ϸI)S`3a55e"@:~?6ĸ $pIhz{DJ2iak*y.M*cH P8@%/t~A~?5^f 1V֊d}1( A`)zUD4ԲS[<^_]<ն!p"]N1)], w}d"%RW,y0|]0eh@ES+r?P*֎lY. <_h Y0kM71W!2GCȑz\QEUoM|>׼8$UiͅZDD15q}R)&ǡ:WHb"ƺx>N'03<*rg83211ۏJϏjuQ/v~w1V3Z|U M K9yJDd' IDATdd@Lh!ǔ*p<ey}w"g:`ƘӼfT !]vMii7t_0&ıԚ|F ll(9@ ;r#yY_WށdAOm'N]2U-\ 9pFi\R,x +L1(!$B9R D!P\*֢6PGKY .p)VDK)OK/5!8Us~9Z2hZimZ.P+\\jz6eE/CH}z/m9fmge^O9o-S|u"i1QJ;CDb@~.f"&`@Lɩp>͇y]24V-]v1d\b[~Y(]v95H5SHal6 13Z˺`R?Tŝyj@chOԇbz|ոf+@5 H\k7s9s)@DWUkU3%@ UjSDB&01ɫ߼mv}D$՛7oq+I^ x9秃W" ]_%W~E|>{ʹPM]%-] DU, UE"!sH!pšAE~|:IZs{zY .5k)Z y5iT-"9YA`29<^k&J]che",>Nd2@ʍuJT:|f~+F* Lsq>'_oL[RN*9r,/jnW'Պ]9.!/b쒿Z݂i:ksf|+4'—mq9++:B|v>V5I7<KF̶46hS;K̾靁k9fD_mcD$<=: }܍fKu:nApM[y;"6aj1" 2!s'?}z80i}Ĉf@6EjY1ȦV 0C]@{ZcH )i&㇇u]Хon3M*5W@bⓘR˯x70@8,˺oSJ:R91IVC15 L4 B1i^Hjh lb+jY灇}{?I`hrO^/&,I.\ek1v{"(K0ͅ}7~Y?ݭ*t}M*[-$ck!84V1.+t|nZkTk⫯%~CZ0yql6RhoM i"a0h;bϥ#ތߪD-RlKХAPE1U iD4jDؤB Pk".?gٙk:TSbD\s]sFnw3sx!8D6^֢*!|n~Wi3v˒8XTUC` eӴAHwMK0 h+"jZ9MїȗV1!3@~x|B}Ot\s^DZG05|\JbBV@ F^TITͪ6E1r;ٞK&a>NB ldD|Vxx~B`BŴO{y)~솮;@w ]ޠQhRY!M 43QdF72_|Ea Fv 1.o8 t99AAT)dD `jRr5@^ ݦB06Go??~u'q.%@ /f;SH$@bCbzEC@@jv|:ckbkYϿx c0n]o^<8 l6# I,OhH؜ҀJsJ gcH~BS _/f壋i^3wfh@~T5b ] 1<򽮫vw5e)'cWSZKVE ~s>90v)2P2SD3̓DčT F/ 39Łc/4 0p:'dtk(A* "19( TQ_fUfu"#bknS*Zٝacq߷0n_uAe]ۏ?}&EB5 /oodVZCrAb$ȹPatT(>T 1no};&J1!sgHāBL1UeJHuWBrM ߬?ѫ7RerXK )JA8yl L)nn" TO oZۀKC!S@Sbd$ u]̧ʹRUԥ-S'{s]r>|}{crLW`$D5cbLfE;q7"J.! *!?>0#Da%Դ]_|=' @Z"!vR#"|⋈^K*UL! v'0ϓnvZyZɧo1C[CIajr<<&1AYO)^v;XJ9M SUE3(b`42Ir4ּ% @j ƈ&mp8ۜ+.9 $l`Ч؍T1ZZK9@@F2VNLc"0PfپO~VE7 3+R*Qh >0d3V0ē b/qN]dF"*f&*rx^O#!q؄EKe @Lfu},@83;IQ. ta$0 N/VAva{L ^l%h/z \..zy&E15rEۑ KD%WKpXQR84Oy[Li]j!pdR f !NusY<[5MY jwe1)ug?}}c@DQ fU \)!t; )(b[ NQͧYCF fHfw]ߧ +R%5Ri5 FSMynl u;re pQAy6U4Ăǃ-jsO:-)DD]^|@@`ڻ %)02o?"O1!H-Yqx2y_8Wpek91q)fTj61E@ATp^*V{R~G}HZ* B$B1"ZrZ?whU)TJ-pBm)C70?{{WkuVd&IR70zQA LA-tc-REsYQ2Suvjej"n&ﺾ#h0AD&8/z{ XKRKdZYsۢ S+7npW"/Ͼ{|Ы.E)N TDiM)pDF2.M7.@:Tyz|뺒![~4ݍJ]s~z>nz쾘u}fUTyS0CW+y4B$_T ԣ~foov%bFzV_\|b;44BA:եb:FgBf5W:UMj]tnD+Xt>1t:x75{%[0N@5YY"τHХ8C^s1[׼t*SX1#(aS& H3U0@a"͸OocDDR re9gADSEsw)2 11Χ3]Ɛ"7LuJDvWWEI- )6ȂR D h\r`28{%d!÷Z><,WjZ͟>FD@ gϞl6;vl@* `6ՕLS9i)g#J)93ZKOdt[S+&_J̘r.'JRJ1XK# 6{-\͜m}`*ug}J^ A̳Vd?Qq^Z 4$j[A-`w;"4S˺9@;eݬ*aO9eVv!"Ls5Esw5#WZ>(nְm0#PnƆ9wеbsTyqv1wSS C%SՂ@D]rL^+̬L|y<NcYoHlM'4OdͲfyG"!9-C( ջba *ZPHQ"/|L7 h\*hتZˡXDL @ĐQze]ʔ2}$3nws Ԍ.79F @ﶻwo^H Tn &B ȬD?o)֛~}!DD)*ubCiL;j9SvE ќ$&bں|:B ;=~\ѯ`.8mϻQd z 0^pQf86ݏt51D}.a?N4[(j(^jBZ 9%yt[#!s꺼\3ΜnszpWOQTĀ(6ƱP0# K^rTtsH6^<#@8{ L&b x|aD !G9 YS;v4VjLK0ZnݺZ E$ <¸|xw_{{oMwo0(}L"O"0zDgf̴^f;Rmw}rRK9Y(1w:Pb*@TTrTUAdO ZʘܻE T4Ooŋ^J[Θ=.1oAi/L Мh?[#t gC7 4Z+ƲL0PW[DTEZ IDAT,!.zy}y$񴣜˾:_@b.1%6ӗ`PT #TiX5L]7XEGͮE43A]BE "mIY"& @-3n@3!#GXP3BbUTH84VDP=O}7HHnnoi9C`j̮ Oq^# qb|xn>~|{ESN!McN\0"^^!b~x@လrJSz lǩcuXu8_̺.9Lץ}ɄDJR4 XN9TT<'bRRNX圽TG㿅ao__^] }?=FNkU@2 %Z#b r"b*ELֲ?Ur!^z.cWU8*+J$P  Hd9=Up݁}3PP (RCpsE 4xxSmĉ|WmD![(.OQ̧qq,v,^RJs"#iUs WU91#wc@Ztnn_lDJ#B Hf%/6Np5|Z@cXݑ+*"Ddx9# ȠfjL:./ìSE**8%S69l&020EluK뀆"Tf>|T|7D̹Oh)Xϟf3fCL9 ݭOL$R;gd^]6Rwח 81C **Ui&3[,;3!'`0jiRRB˙DJP;8ƈ_u0ٓD8_ ]0RyU-ч1֌1` DA0=')j]7sZq*>Ÿaf]x?l1-$3o6\3/=Q!PF-~ Ĉdb(AK_C0Uq ;mn+ؑ)=RTſ<6 K)C?c.Zn)mݭ~xWff]FiˋWˎ9|;MHDT>~|q= ze&*J`$1b3Kg#T(zD );T@bbZ+la]{D#B,:T\j$vlp8v;sfd@B)p?feHfLdTU$"Gz"3qz<5`/drbLGDxjo@ =R$?ɥaSrX,j-Ӥ"i(8gϯ>}i10"w]ʌZtZrO e@RA͙[rJ "ƨUKUdrP{-R59@! fq,c)eVk-~|I)9d_\\v2pz?v~֧ę0$vABuA"l@Nڹ"dV%M6b1)3hwE=E1u_{3}N:Ny8c̈́vӤq*p=) ]ps shLDA5cbU;bD!~f 4@s/Q9*b,yr, [!:(Q"ؐU1"rfPS*6@%b@16G<"!ZxJmaaqSopy5LǑ-1x2UDbbN,j3sN5C[sqM%n>,ˋW^TQXH'f\vlL9jVEbrp@Ȕ/i8d.$Yj?;L)-A<jp*;e9_ɳ+U"01У58#J # NDjo0DRK2χn,5*vRJrJ0ϺDHS宫i<0mKf)맰u\QEGLMdf1Uľ>]폻H%H&$0Q011MQ5)eq ^''ypOX>|3gg.qPx>n{N;6U+X}_x7 ~fjR%}T3T&⧱*kvl_2(`[A?5hYU_+P _+x ja1[$&ow݆SJ)8~Hj.^)$FNX ktfX8*.Uoв]>zWOً {2BVO(x:8T7&SDL`j&p|sS­B5j(:3EͨY|m"DaDb 511S膜;|8T 3s,PlU*n*g݋hX)'EDZow!pztvѣV@S8H }U:򑈙VF5 ϻ nߵb8Lc*J^Gea#$t8!FZ5\?y퇏Dכ8N2afҦۻ9%c-!b@݆: ?22:_04­!սX]f UcU>|z }-!̈-/4]zDݮu9u*f&uR?!lux6=*9E5eBIL*VQ HfoOgs8S5pZ9MG:oD1AcYQlo t֢O=y*|j2*e6TmsL D)ӳٚɡ胹̋^~qvsCT/k0yғ\;;PG_ @ fV N㘸{I?ϵNq'/v%݃D`2bŪEՈyC۔8F1Ǣdpd$9v‡^D}gp&D,9i|) ~M[cSQ-ETJ31B.c͙cߩbAm65E0 ⏣~LL(猀!C9 o߿|2)#3"gUA')rؼC5CK9X8Rn{,L㮷9"})D ѬxUAkύ')tڷ/?N!W`_I55ȧc?w݉ʣ;%~KB̅JwCOWjU<bj)qקK'OGqxɅ0p)S-1w7%_[5icJrfY1pNHDfi9(bܖ۰: `*UÆ٦/D6SEwƚ5) {Td.ݾBnAW!̔ IL$p8O`*sW 9onSvuuJLk_uk>NG-Cp*q'd} =I,Z^ qw;h@TDF$O߼~^".3cmSmhnH֚ AO*@QcETk%BdH9U*a?%٢D *s %W;!VD*6|Ʋ,wYżXG>oĈR͇aޗZb1\jT5-gcg =U h6k,-OPD@)] x gHoR(E1$wG6aVTtC}&R^2Ta/}/.X08!r9 @f)q"$bz m`@@Q\q#{4j5לg^bARȼ.J-b^fB-ȨF뒓D̃U0**wݬۄ!B-nwai*>en"0ˆ(bӇq $H Dϟn/./e@8M6Sk\C ⅡMx[rCT/uۻ_WL] /TB6ٳjzNE?|F@&0TL8MUSXiLF.`L R-1XZ=CAo^?W筛.ꚘFhj bXb*zk<ȟaK:j:fΈ4N@o4̆bKJ*Zjb] e8L åG|!"e4h&VcpAe( ?? -h0?xyuZ-߾}6@AիW"zuu}{w/~rKzcYrbĨ)zG5%F„lL,&3L$>~c_|9)5- )PB)U CL;9@Q-nUNt@fu=yawPLǣ Z+2JR+5tm{2БŖJGj ?Dqz SWoKsog,5DTUu拾]41cUߴ/D#nR~Ϟ]!^J펥r57q$pxǻwEL]Fg{B8Q4T5u'&OgPha=މab ܿĨY^%kHeZUSm (3ݭS9=hqV옝*1#E:\BĪ lf@RºIPU%2nRpo3E#ߡs㔵 Knݾr|rIH1e.S]z//uFRbFu؂/K@;6x< 7XΘ)69qe=0QS[܁ahNlY? Y7|"ݗ i a-D:Mz]DZLLrjc͏0aaYp31s`0\q4["JRʜ(!@3PWR:G⯍g%oQr|E {8y3işĴ~ȫ LQpn6_>7\_]^SI4=-wm4k%~(aS;4< f1wWs9JHRҜJ$*u| n+>Z7i\VUlE)Tnw1B?SBDGNll:ov"^2d`j+TƊ@΋d[4}/o:8#'fb1S]z?Td6v=eHC!Ej) Ôx&U|LKq*b>8'U8102٢UU&KB*d@dԺ@"A @b1ä᷉?_gtpK=:ݗlۅ@XJ+MvOĴq$@EtVnwCetw9xWI y!c-" XjMcyqNt:E39Ԃ`)甙Ǐ8Y4N<*Z#ǞK23Dvઝ3&^ }l(S^MS}V^lEUųʞz|Jj>9ޔ!!wnKrX<6/s4ynM݉^UZE@ڀsJUkLs^*NDn,.D3&Gᶌ7p1"R)9Z&Nܐ˨UǏU4'frMCٵw=C~!ChkJLĉs] ) t8rJD8QF]4!""_ xм[=3( 0!=35| 49gaX^|뒃`Zp,?WwR+v0w]M9v;$Uk-\'3/i!sl2usg9J-~:qi IDATt:zKL '3X#+&[F88BszR`\7YdG#DB(' ` wһM~ m2┾dXfj-t:NmbXRLMTisi6e⯄ڳ9mOr\a%Uԍ޼yTEJYoϞ=Z?|"W_v]w?~ٲH@MD}kHb$Ϣ/hFS53ZM %rRp7!9w +f!*# 5!R"FTD(ǛadfXA1[M_ Ο|ЦMB`.%iU!"SUֆa0WlĬfZDDKKD׵|nZ11l Iڅ+.# wM_+f~';TSNq|ŗ/twOI8g$^_.'o)lqb1RB&UdJD Jm5' R]v+ih ppX+[6nk"rqu= &e^ֆ"Bgb@U8,CDx1sbSG`c|*4h A{&iQE/lv]7]γb6u]*E6XW./Jc<c򝪢8b6|DXӸٳ6w7ųB2}pf!3F90DSq B: b4H MIDE Xuy"lc`a$]?7*"ؗa</mKp<;ں_M=W^UZM*è- 9 fHAscXI\`4Uu% QvowݐA-*VƢwV-$SfܪB}3D@b0>{W ߎeS;ZSj?٠#jt|`!)`q&qS#,IBBY117fӏ?w} -XET B|!n[$zx:*;g$XW49h&$PUiJSd$}oͪv˙Y)ORk)n߼W}h6'ḹwl1_`zL Mx QXpP`f T#4"rgA{:ݸxjP4(H\79Z߷] ̀8ɼځ]Wx:/eTH|4jæZm La *~VmjĉOj5e雇~Zq$~+T-#uɭK{u]EDJ(!ANlՀ=** HawCtwR63֋ zz  m*iǡϢKպnoEsyUD!0#P._.l3cPx}1ɊiUḰ#ĢU{vh I3Q{"pXR~'_ =eydq^zUk bzWL^\=32U5q 8~=n|]#ckqp#i6:6@-#獧_uۖf1D Sgtp} LN`U=NY7뺞@EDJ@;USl>?N.'iU$/^_ѺMo(9~wj1 C\SG0 awZ?.|70/sQW<^9 )y&ߦ!)nl-D/r8b>znZ.e2RÇrJ9'":6M5W?[TQ Zf|Il&f|q^ޘu[{TUU<&ĊjJl@Ơa՟ˋ@,9N?_gJwn!'DQԪQ^i)O^sЇVs*E>>Mĺ._\,T>)Lhxf.3s-eH yPsqx=I(8 ^ VYɴϢL ")φ>i)u;ԪO]oO?ׯ_Wn"LFuU=tQ7avݲ_q Q0oqh3k~!c87x#TULM  *7`6rտ~=v]=|cL($!S%мiMq뜯oM򐼅 vgqˆDaFčRە}"P1?y1 n!(5V ߿*UeJ :hrwHxۿs{I鈊'# iO" xExQvvd}|z,3GWX"^:5p*3h @;_DCbwG#Qw qp*sqdŬEhnTzO/zLǩN8իoQiG4#~ZˈXMh])a]cgUu@&s5Eэ!a[ -qe DN7$Zנ=Sp6J5rm~Vgѡ=J1{_ϓ5C༒~EM*x#hϒY4imIbPDj'Fɱm?b`aq#:t/;[MQ]h6ϊ4{xLؖaND6|D~Tݟ'~տ?\]]0SJ!Cr!2Iq`!B|]o=WDĆȑonm/P- ^\^f]I:3SL4f\- jUQ(uho"447r÷ofeD)"U^}p龔'ϯ3%:H6U\?G,;;ý sVUWu -&)u֠lI/@&Mj1a`72`S 蹺bw/νM Dv222}==T<% [E<{Y_]Tzfh)e kf PfɃ\Bla8<*z7mI~CBVxIHr⢅Pwzl5$VTt҅yl% sV=iG~At݈L9pDrjEMxYL,T.㧣$_6|Qg?jjL|v%B㱋1 [oEwoU !Z&"U<!i`/*eSC'13-`;1t'%g2qSW.7/^,g۳4Mi.Vmj/來297>3 kYi1&QϺ](".XZ~#C23ѭN?U2LlyY x*F%V\UT |sۜAYFGPs^ytOUSw7vSWc4DlNޮzux{?zq>_rd-FCbYL98ܲUqTDHY%-` p~p!@!yśo<>ajC]%iN L:!U9OH (g-"!UuQ.¹Wijp3Byws<," *Re5?EUjc7OxUe+fq:ʈqj*dUQT.W{}09ai7USUDx}yiԫyJVU#νGduѳC"EP%!?_lt>%5|\톝E fL(]i@[&jy>QL&9 w9v]jMm?|rPrV2Fi0*uMБ9 U:hwǣ4mI4't0NYnSl^ԟ{$]__횶=?*sTU $)"ȱ1" D_ u 6d'7j?5(rP1!TՈO3QQ*%!c¥`*yTTd4YR= w7?8VJyfnxͣ,RC DNn2B&Rx0 Hm*:_uA N*2J}@`6]n@Ĕ @T=@YZJRbv6U˛P~ۿ+ E?ڌo/C#Z`,KBv* 1xQzI'x${g~?g9tC׻ÇwwOynWfpaUE [51ɓM-9`~ w γ iTu4^mId@+4W:bYVFrIJ׶4 |xhqAM`ƪURH$s 1 !a8buơU[JcU7QKY۟PwvXU*Vf@pr̔OwWx}{ͶY5M`oeT, ,6SԯvzKUUt,ai`nbOiʡojifX$CN)&u$M d !y"u]]Gϕu<}ɳ]UEb í"K/eeOw9ȏ0f=ql N*)4]-Q>eꂶ t֥r٨(^p5*2a`@EDɷ,e+QNrߣ|752'п|y;0w]WUqTMWU `0̩+ştqcq]MDe 4dj "#g}algN^@08BRv-vL@Bҧ}*"S9`T=<T_U+Bmz;jSE)EGr&zZ^"g8li,ЀyS?^]]b;{4; Mry`AfK.K"I¸'*~rH $ɠ!B9N 'yzqzbqn/K+l^P`觓毼yf|^y'[K+䛰I1 9!i6@F|~͈wCqݡY5]7IBxնH4U]7/D"()9"3-maP +, U]{840_DK4e_0iKegqG97srMIꦮ뺩2a)PZ@Lø9i" d!IU:it𢁡Y$i _ًaqtݬ׭d"Vnj5#8?g=4߹Q SdȈu]ճ˔PqDĮ&0ܗ")QPI]I>"00lJ]GD[PUǗ_JҺT ߽ielLyœ GzK;` IDAT.N 2 /}/o f% Gi"$fv1VWx$Stjвw/;'R9jS7睷}3QifB) P4c9$̳rQӛx;ǔ4M8HҗW7<'36m4u=N؏! lYo>pG۲:]$ DfجVB5MzFqy6+bL˚Db([P-E]9Π 80!Wsӌ8< *iXZ 43:ZJNUCǡ]Ϙi$JD14M۬ m֢9͓4m yA:Ρ>~G͏j^7dÐ_< zk*D4yшtQ̼D'ރpL7g`(ld󜷬M0Q#gϞMbLe|zyy]XWNo@+xK[bkg/._=$TKr-E(vT sYFztS渝|x 7M͟X]\Z915(2+aJ*FǤN`4o֭& ݱ/Byyo?suӶLR5b~|iS9'Yu 4O<ϳ~fٴM]fbC?1s]G#8Jx\I}AC 9OW|yr b4*KݙbLD82TZxe4n٫BJ]5 8+̬XfՌ$"C׍:vk8Q39͙!zqTRJ hj"3>|pl5Ŗ9'q MmC-KMl =;8;ib^ǫm}?|<laLK-lι3ŒjR:U@ @E,J2 "SUI: xt Onc#`V|QDBlj"" >@s`bHqC3BW_{O?yv=|UJh|퍇ϯnr/odrB ęObKqxyVk SE灪D&*z[+ZTYvu)>YʗE¥|G{NȔ-Kl$Z2GB)"~IB],>y*_$T-5Y8{U= 0 Wݱ\H-eUUO{:w?'LaTպ6&ǣ䞁>iϽu?{xWxo՞ʙժmWahKj|ћ&5 rhR̗*ZՎ4U!1nIҬXU84OӜuu4xSؖƓ0j$%DK)I]5M6IGSnH$X#\WM hM C7>cٳ(4c Qԏy1x=bOws<3v|j$f@I"K)9P1埉jxl&hiՋg胏b R vI=!#sR+$"ZtZMU1?8V[ٜA|)hk"33D!eKN\(y>;8̓c"<Nf"3:gy,iE\$aPRh 8![{|Vb9-O`kI5i1 bU1<>}]7!8護/U|n{,g40x' %)!9A,Kjެ4NSJf>|q(0jۋiL}?w1,{5¤ݧC clmDb]CӔqڮDTֈhK=O°Yن.]8_y+ sK\[0AcնmU2SIi) d$i?DНW+abݯ}e<G>(xUnr)7P96&ӑJqfid I$URo.ww- $fFt8r1 P?u%,p/C_4ZN;y6jB-@e@T lxД:5ǟ<>M}JԃER"]X| NAe} 9O~E/A3.ڲ)T/a$_J:Ѕ*P|pN֔DUܘc؅(Y * +4'D³(L9~yy=.S^!T=y3Q?ucb|pt>22-25%oXLTavW;U1bjW8i2;G@{yCz&FӜTfw"U{GdVoU!f 䇪8؀8oVu|"l~XM$ZU`G+itv5 ųc̈́8"QЌRFuzL6CƝ 쾛<*@C@1q^ݬq^$!6Xy'u2S[EL[~Y(Wb1Kk2zT2r><0tXx촟QT-_oOrw:_,[ 獣/ss| ; <Ϟq/ܥS*7ޓ'OU?:ؽwc!P(T!n=|4qbUd7{w4xynf3*ޮ׫j$cеcbCx3<%jbiqHz}uYu*`V41<Dyc!VĽ0ͳHJaR5){;e1; i!&~ ֑ÜtwyxTU@"d)V0@43':ّF7e@$1<4MU] (#t]>:r 9٘9@-D+w%2 }xk3Ğ!f?+J 4yf +_ ]W_yl&nx 0#|)%$!$3HdUPU'$Qڗ^m_^}ɳq=~qnc7ժzS8w b@O B C#q<&ծi+bv*//_Ī5}s^\1ĜY^gEVYI&-KAy(P R|90LԏgՉv Vni31+Xs2^,*MsQ[Ev p(lO8N4cIqAQf>8Yo]i<;@*c]7uY%PI|s}sexEngܜW@DDqcժm۪ dNi`l~0I\lqBqyN!f**rѻj~s=M;͊20eYCi@fjCyUPǦiFVv# (7mJIDrfh`4O(ZU$}W..zЧ^L`L$gwXRQܩ8#"@jAfi$vW;؏?0+QZźww6۳ti Ե7ܘIDET$IUioVmKj~{?f.Z#ҝ2w)GL$b  PLS!bXv^F#ȽV+cA ]"9rf^)2q3S7Ϥ7䮮w׻ioU[+&vW/*gEl3*UcsХK{IlNXwݴĨDQ^b *I ډLDtl֫~cۮV5y l41p  DcU]'/M"s&2e"[!F0-]C,aȱ3H4}?'AiCc\o֛ͦUL"SR@UXڔd&HLFf&cZ,Ɋɨ+$?۵ D' I/a]Lj9dNz9g| 3I:JlpRfg[=dBEӐnu֮6*_O>|UB/?K;n1; e8?+?0$3P1dD3#21VMGFPG| ]DPV0;o]lc ymD-Im9scq(katD Ī{m@ ]wGO_vծc uC }w ˀţ$EN$%$fB0bܽ{ګ ̐9c, ÀMuΉ9ͳԧh$3:ZG<Lm偫ٮBޯ- $@ b932}O۳mk=mIh(W,|%|yL*D점>fWWgNʲnČUdEF噧P. h)/=rTl)~֧4`fP@WdI{U[PH 4LůId'~,gKiF /I=`0fo7 ';|р_y_mꚘ)֕?2pُ%~Gwò 1r`K"F$6|Ō:82&0dB$f`C\J;dM BIxD'/(ڽ_cE  C'qdz8_};FsZ5$1Hø/8I*F3#4zc$Ռ9592$s [Iǧ/MٙOnNbzSNi>QkfTE K&g, IE ]|oY 0w8.˯iO/F(:- ޹syvqHwDJ/85Tj=UuOV4<͙!m/ Y!r@@kJJݞ+l̡ikfvBΕ@DLsV<p)DQUC!MjcO~ 5񆋍̘`ښ0)93e!00LiLLľ//iVVj *^ 3!+mή\-C9g9°ehR69[y1ODfi&:!\OuN*D,oy ls ߋmfhJaws3's= 8OVY]}>iT0ps頻fx9L2;eQnXA0v{B?ߓ ' pt5{,1E+[%ML"Y'P.Ύ{*"_ԥnߋI?2{?t+oϾ~nzrpa6B IDATvv5Էۍn.?qKEVU %00J`{v=;7ϕUaf[u< auӄXqRLUUѤfJ]׍ h\`f;w fŌԔ2mn`JiyDD C@&+`FUjݚdE ؅j"&Mی4sJ ^B z(r).QIVM0V8b 1G7+ rrƟoQpg#S#$.ӆu[H2yijuuy`7ƪipRwiHd_̿,̐RfK"+,<|4F DŽJ-v9埡1O`<ϥznyK9.]` 477i$?_wB _1Uyo-lCxW~.F':(_40Nʌpym2ZE!Cm TnhixSmӌ4cRnv7W۳;TҔv7G3ݬI <3y!g[w 4)h0̼TC9C6|VcJfܼ>:aաY*4OI,bfD( Dɬt~^ L8V0 }Ifmp:!<E@d H1 QhAEiĀP"rEFdV@PEf0Tʊ G"RPP&a9=A,!){ <€C`d l} ~~pfꧻx{O u0="@&f)=S[x0X[Nk>UA>ebHyEyb)nYu…F'it{(01].qAlѠUХq^:e0&u;.*aw7{ N=bsJ̞/㭷b9+_$C2naEx?]],̊$Ө̰$cK+c>+)b9 fAR˝7k~4qЏ"PVlܓ'_T??~D>oo}UO}mx7_;wϟ?D3|Ǐ'O/ޟ/~k_7^/=X=' Ų$>Xv%W&OD4n&H<: Quݱ{9"n7k?=sR<<E9JMiU?L(xFL$Sjn.-͂N<,*oD&9Zo"cxR6䰜o>cZv\e=fNBT\l!!RdaTtफ़)[TUL]: 0"!0B`r(R`B )!َ,05MLmnr;\f!2#1j؉XED5kOE0'/&DíҼL] Gv,X.@XǴ3e xps)"-{@tuuu}yw9o"g !UCnӧ'Oo/_}w}ݷ~7ַi>}q_7|k0>̶~={vw/.6?"vӾ=S3IMmy7>j44#QM]#+DUy5Kp<=}`c$ eTe0!ƪn勗 MHȇ"IɎ5 aVL Hu0PEnp}ف_f ؏1DVjfzo6XL0Hy_=4bͱb !0{02@LcԂ S!4xlR$3I]$ze`8L~tns掘%/x~)"͆Ȩ:d0!R'%-.n#s1ds|YL ԉKCtzRiQ'ڢB 8_x}u}}u$/g5b$䩙=y7~7~ Ǘw}W0߻?v x_]~G|;ڗxկ泋*GDDq!7"URsJ)%DJLQ\ޏ'")fd&/k")P7gqە_<̩*B DHxr!Ji~8;D1nu7!igWq:Bkg&ga5z5@DDѭ*8bQ's$:DUM lmbjV;Ug[ޅ֛]-Hhb]?S"cTŔXm8XbrqɮR, r,61Wf'G2W eNܩFΗ%5hy0U^sJ^[,Yk֔)1'PFhՆ~wq7"V1T1Bp ӷByU^CH(&S)ȁhhYvgg]`D~E8W/<[,T_/K#*"6 s9ƻU5$"IGa j9_.KIisc`QDd+W34&,$6Dj6Nd>_]G&4a5SK !)+DfB&}eӁe8QD .Nu C"2d;P%F7Dů%Oz!zPqD!ů诨Y-Yt0ӋeriV"}AMS>\9r`򐌍{GÆ{,b zcft}{F>< \p ,p1?xcm7и1Q]UۏhH>я~sxg7ԧ>?O<#xlVs{_æ?~~ԡm1.ɖ<G&.F8C)BģO̯= 䜋ӫX[r^S]!#"NflmXqJ\b*fN/MV"jʈ٤T}Vf>_;(у0jXוԫEQ3sNfSU9ٸre>7/ sN!T @ˍY c 9PE(T-T{(AԃjJ4SbY,gD͌D}]FH"fH u9rߙ"dU|lV!U !pnWr5}ȑ0d'#(,AS:%E.ПQF"{d #ZEťCýx ^jmObW{Og54 ,3{[/_.s=/#>)Dӵir< ;|/BY1l.,+7Yλ?GryWkNJr;;[)Vzckk1_[kTj00O=$+~Y ÷q2ibeszz/2 D! @Er]#kW6cn} 1s` 1Qwu5Qt28F"Oz!V) hv;1GvL+*]od hh,;~)$65"v4p<2 eET5&24rZ-WU]&C5FGGG)jOED.j"G¿oJ|.{VGP-IŹsrV`ib3`{qQ)w{xt旆զiv]];e~{qw(ߺu;/># ?^I C?rWs!|^[ߺUR 1܈ srK_4tmrBFR$wa ݽomlg7^{}m}cmc SJ b( ǔa3ª\4k?U+ U$ēɴN Wv^ӻtCd9rPXtCKn l$Ȗ)yߝ*ݩ Hrd@vIԁha *"&EOmlcɔ̔Vj5Ne3ո6,TJMS/ڵ}N3qPfy>C$+NUQ~xaEܯ A-6@@/jL8 AA}M7g"*5]8|Ndz/^N}O!"S<i]hwߺ+]WQU1X8P wZ03s^=\c klڬ Ь;L$9'@}䑫o6s88B\TdmsDLBrD().i*!m:9>Em`~ !H`FY5 +nJUloݹ\/O>Yo>?ɬ:=>_-ϼx&HXf2̔h"BU|՚WTd`]0 f! *9甓>xx6QU4 te墍uD:79I/>l&)>})2 rI280;erRb<-sN 5Q * Ed*m/}/(68_G.ԟ{^s_tO}җ'zʕ/~2z{F׊+VMHyw]dRG!;}ߧXU@˸$/ZOjDT흝DHK]R"*P53's@I"dhFG:uU5.1 `]U)wze{g{MU (5MĢwU5bG? `FZf͔idX$̀ Q՘LMKIP,]w"J T0I+׃ (', ],JZ'$`L`Y-.6113ˆ'+mɈYKAp9ZlպD|1FMOH>R zR{]YäTeh˥'i몦npp7+U'5 IDATvYZ#{cC,)I>mwJG4b(80 !('## z]LT 17SFDuX!HJV1xYԶT9 bI9yk YĉA"4mrZliʅ)On`@='jCwx'Y/ݘTED53\n BJ 7I7F r_U5=?nk'Tҏ0N}7 ,$`~([u6wD/GTDD'fׯouHԥCUEB&́*&I]v]WUq2catG8(x,4U 476HSzegkcykUm`6:C`_E(wG pJ9bFIM@D̼ba$DS%b4"NhSKbg Qlh$" ":P* .]3p)(8 mh{ҫ:l樜rurNkI`bQ 'u,[n$7hWɢ뻑m  {L@{;/xr_?8;``cz^C_ޭZL$k1)Q05Tz5 ![(lw޽ݽYU4jل}dg1ZR߷ TEZ,jYxLHP"WX==׿߾xub'C L)%U7*J 9 |1^ \fP@>ʳ#fL#L,eFq|SȋTE5g0QHDd)+5LXUbMDE41&.*SRϼ~@ b))BuvP+"@ QM%O$KS ( |G-$iNO綹6CY>jn~hv "}y=^Jc0 q=Kbf?/ܾ}֭ ~9d2~`B NUWUm>xPf~q" rf3bNgM/(c|#lfZ22qԵD>72g6˕F31#1etRבb M/K#f@«&k `]׿_Ʒ'Udm9akooN%a6ßrYm55@Ub47Q S"U P\J1TG 9)2jDN``D ]M7(@hH`YM 5HJ顚yV4,y~zC(X@\G8s~IHāHՐp2b@8"QjQ&9g.9u l q Q.M*Viӓ3w3r6[_pxyy9=.Pt:fJ3>|88PKrfֵd:,@;Etp,XԂ*˦˒eZסDTjrLJGG{˦9 V1p*2&®E7N̹.Mc֕CаOI B9X>ɬ5ȁf9Ln` 6`g'c0HbHTNLD,j%zj(5) *6flc(Z\Eʇ ; 2mx//uhhHbM3 K(c*.a2"0HU ",IO)ΰPA4Lc~amן-ڶKUzQ3jn-fu;ry_Bݽ}tҴMZ.u3O"TE&U{k_s5ڇ w>p?07^{^KyOb0cZD$ܧ>cܙnwm}NXUU3Nwvw-gI}꭯I]ɬ̩KŲz7 j hU"V tU+[׮3t6jh}"ĪROW&)(%FⶬTTR/Y`PD"OqH;0 d$ņj`YgMHHM\ʒ`hڶ&9 mZ-}!"Ӕw2z,_JM*"!Din-\L0!9LƊcD8 N8X4KJI@E1ڌWM{zru\ ͛<'x,(7)nݺsK rzڶ7G?̳{tsse׾vaUqmC~zv\,gw7mKOaQ 7!R@ڞ>"hU쫪XSOﻶkwvvwRUMu>e5{)"{ͤ00M+{0j6oLUADI*1UhVfTU4UAM|Ÿ[@tq\*FC밈)"T#8N ED6`@`"*D^LcbY4VQmqRjղZǒ;`(^0/A fp>s6x GDb/q &L# ~ҘYzZuYs]5M߶mJ]}-4ohd"}֥}|g~g^~e0]N>]~ggjӶW+" yt6#]sL[}۞c =\'j*;}8XzZ-բb@f8 @1ZJ}}tZmrjr H\>>tl:yA`^[N&a6NBONBb6bD9!P73"r9w zPԐY 9!xŲ+1 jJUl1R>% BD˩I)bZm4YD-uTUC ||o8\@E4^M'-P=~1@(}^p 46Agg>۩ZUVڮ+*8=n'~xlO~r3g~O|CyΖ]w|r{ӵruS#pt_q1/2% nXnmm?8UbftZuՁN) "r$7O>a1#PQLѬt(՞rY$Gm T2߀ ļ"HG*/"!V.}7}NFhu3R͖%+4M{|t88u=uʤUsAWW7%[lOJ9Qᅲr~G$5g*u}Z,M==9["ܫIbv;Ǻ! Q&Om<;w\NK]{٬zw,oݽRWj޹0@ x'?o}PUV;Hb#'~;wI***UU9)]%Vy(iheb1dãCMDrJ3H 4b(mƵ=$oSK"3*֓*Nc(fTE7Q`FTSB^@CGJ$P@3dU#F"?"d^3f BF$bƢ^nf!`ΞG49mI ^eڋ-NNU)ATZ@0jHLfBnt/k|kP%&4p E"Q#/ 9p؋"D$ "˓㳦mq,x;[cog0&?۷oIwv~Kof*9g}O?Le.uX&K`:ճo_ދ ]CUW˳Et1H饬WE^ۈuUWU\,Mk;; -::5Ғ QqP(6'̞B a}}VEo)L@1PqJ)=ShbA@(*A6JLhA]Mbf4UPT'Sa F6yIM}bUFMO*0@(F!e@D|z  7(/+c9QU (Q TDpSժ9::mv|F}Fbx/|-oݺ wnݺu9.oO+_ݭutO'^~.#]wsk{gr)"Czm')<ezC@F@ͪ&[[[S",L "9;v66(fDwv6u! a`1%D'0>P4P#`Ǩ*EFH,ZCVT%+2UĪ`HD FF&YQw}jD: 9I6lq{!ҫL#)#!w^PKL3ɪf.NM!%iVKS;>:]/.{#Bؽ0-E۷o;>|ӟԧ>=9᷿7?K/} ><88x'I],F@t<tZOg}Zm:믌B-$r1YS.TU [.Ԫ `Ί#"UcfBRSS1"lokcog ?1@V[{b* zeFU%D2T.%@0H(T)UUb%:3i&{bF!BC`C7tdгL8Ea߶[5jb\: d<="f%Qrq&ül*֢ií|l^ࠬv6_ S 䡌' ̱u֭D?|&o~յlw/}\* ǯ]Z6YN\%rU!鴞NC].My >o|s`iKP)"mK(&L .uQ ډ](!b ̑{6CC[ʽ|OOM U͛Oo?ݿ'/~񋄸cڶ"#k̥B6[_giV5 D%ptx8hhj/ \Tqpd5jq9IR4^#IM>A#*Qݭ*F<:99>9Et*PMώ鴲g7Kbf@C/r#`Bi0C@" 2svn!b1P̀=ϊ1Ccb$ O3r\.W}߅0T-Ζw߾2s_]vX[1gV'<&xCp jIrRESL}r;Xr΃-p(.qn3g$= 7uG?ܸqƍg/Kw5̳6!p]Ut6JjڶSSK{ $L5ס_͒%Z__'C&9(+bf"CX,BN&Uӧ6mo gFqQ5E`0,9%fqJUښa|__,7oވUtVb}^ߜc' & AR@ (I*ou 5Pj}Q@ \&3Ώ fT,$bLf'bs Ͳ3{EGt 1),Kw2 Eۛo=?X:]1QY.WO Yx<8j(fqK=Ċsp@tZWuR4]י d$y`C:_=^DD79eJBUt:(OH,&b*Mܽymז 5a"7Oi95@L^ .l1bpXUXTL!`]*i]ܑ V6WVZL>-Vfcc]UHMEDd.W(ÝXaa *)eDlGG$00TD0bUQDUi:X%_4^-(Û G.r=/vQ1D-m+X p:N)u]Ӷ)I=<?xG"7=P-~VUɒ=E/R. '0[zàdh`FjY@E$?w?͵G]vuV$@dբ1lyN0b.}Z2dabl8H fYu['!3Qp3 #~jIBE&Iyv7ZklTLU1DB t5e~%DCoCGVR_ ~}4F&. ۂh[@U sdinX)^Jml}7":Kh$5b {*%ԈI\ /)ǎ<9Y̝;pAD)ǰ&>`R׋#"d&_V!̦t fŢ;u]13R >_ַ^{0/@/;bz"R^!%RH0]SY2*"{gz^dssCۛiuxW^zBRMm`Y&Ӹ0 e`go{?z22  adنA^yRU7*SߩJ8znx1"8x.'gMUSBD<\-1P܎2PKJ*QU4 49\3RKU@dU[,V'g 6=Pr IDAT e_lJ0Lp{9ܺ}\!~嗍`4ׄn>=VdmRW9ժ}g0(1 D$ã|wj#7 Z ڪHDrN9(RSUu!}*\޴]ӵ9ժqɣG_g~2rܽ@o]}*hXZRdmoC DrF"5Wh\D AJw+"1]& ղfjnm}ڥD޾S&d$"Pihjf /֊ x~xҴWa~0y۴#~6\hvp];"ݾ}{},><"`fJDL<֪S˥|룀йw'ֱ.᲼{X7676_+-4+TQL)Y۴Z[@#@ HmڵcU]gȳ0L}?SO__z2rj΂HLd)2c3P( JMZ"]UU3G` Д6}toGcL8= 8+CtB{NPDb-M"dY[F^\lQsʫ?<Bb>." r~6T>Ll~gkssZ)nom~ͽ40I).0TbV3Ba` 5DbGp`B@H $jh`9  F5Tl~-eީýi>AQĬ*>Wޡ4^#b f@9"2SEwttҴxtl"yuJoz[nݺO.9|>?}?N'!t&?'!1of3B[̗)"1t1cX0Q֫kDC8 /~ ʼnV8{;;;UZ߫>jܿhǑ7 v mD>a1*Tvww67?;lwg&m~s 224Z )S(c*jbYbE"*)u9Oβz@D L$ #*!(ퟅ 9+˞RJA7~IkÇM^ĞZ'YLLx N߹s/-/9/|>˿i[?z$XWTԵ'1f\рL{_9;=^>V[.褣`gow:=JNz#AZ^H =ßy1jRk{NN{*D11bBGɋ4LИm[vwH@ ݳ {:M&4߶V<~ ZfP3NG>;w>v\>.awfU=1t[ }XGHt}}bJm>h!G5Pkݻw_ַgcf$2,ܠUWMfD~B@Kկ^PQƒ9ws677Yt8D.z=^nߺߗWﺝ]z2B gzCfX>d$1U,/|pW;79{w﹭!f}s[W:f>;>{>7ww?zus{#XP7{혖b4n&$.FTR**Lj@FI$bbbXDӳ!_kꍝ|Zj Lj|bB7lSQw _M-(3zeMs9L'? 1G6f1FC0$NgڮA@}}䓏[oQ!0|#_Fė? 3r`ߙ|O|׻_Dinf[Ns?2 ~y|O+0 bCcduuuթy ϏT\*0v%噵`6+" 3VXqhmuI#ouYm-ɚ*QcV.n؂qmZCE&Gb*2h@'&(VVmb)΄AP3ьqw.)= @7*L9kmeO !1 tJ?./׭[G疄rخ'w. Dka}c\4w0B`JysaQx췇kZf΅o2E@t:-nܸL6>3EC=-##"S;o5"\{e:1d .]l֜}eʯZc.Zt".9G&4Θ@($T\(NH&Z]R7m}7 `&Vh61&5 `q(Bd 8΅ծݣOyd!2^yhk1s8wnZ[Ueh7PS+WR)2d3R gN۴Sw5TEwwui㛜.z_YPnH]XZۉ XGPǑ1`FV〬\Fy^*%,Gr[ Zx5ܵǺt&o~:477u=6/&JTMⅩ!BS-"&%~؛`ҥ1517Y4IpKoũ:? X 6 K:3`+Zd:P[cKicȸPb>+U&Vبbf0Dmڥ6mc(G9g`6Ap7R1Hg2IeN;>[eFM(Z^,ol6^J?9d`?yd"Yz s XXa̙m“^/ wc*lǎ'OFrUI'6yM3l[[[fGM`E;g LQ}},YScjf{ӝo|ᓷ́MNfd: g3 Xlll ό9zr֦e j8FU+k 7lY kV8-`|DZ1 Tq̸;OOXƹ|.ܺ+rM6F|-lzFoɽ1v|[R|Noow߽i=bx܎/t&t7y *`̙Tg3̀kQؑ#`Ԃ3ǎ qzzzm{zp[[Wz꙾-N 7mmniiim-m-c##hmjݬgW#2V:+r<)?ؓOb/H -e4((W͜J:1X u.90̠A w#-*k0fօ|C@Jrι@̕j3̀7<6JJLVŪ/fkLoUQ|[Kcׅ3mmo/Xݿk~?ȶ+3C D)ѕay#\:vudY<<3| c CVyoo~XGG;}OkkS)݃S!vڵk~+_O566eF+2,^X !V-7u<7Aaj/[_W{bi&oޘHkq!u\c "cL$L:#8B"n1`2\SmւVZ/\"nՌѱl:#8&[S`^@kq]J(dr*VJ\~EWZSi'_޺AĞ}[ow3R./__0? CkDȅq!cǎ|~bђm3bAW6+}^{Y3[[=x&\zOIY*UU,_|'~n8Ji\Gˌ֦ftd&k i ^{u2=// ŶƏv-Z4Kpx˷?xa`<)L2 !L(5bqhx4ưI1*Vn~ Fke,j0fɄhklGL!4@,cZYSqF(֓LDS%v׈xzIO®.Ch=~xf(k@zB^Jjk%GJV%kkkvz}ȑXX|B?G]cCw;6_;8Rͭbp`'<g"L$[X(Vzzq9Bp6<<EB$b1<3222H )CHU$'Np'8& 7h΂:6VkVg'g(`3l6 rg%;(H6h9 ZR)rJuϴ.1gtuum[͉?W:5cǣQű2 ƀғ, |%nw&'&jjjZۭ r9)EPz߸ "1nOz|/Q[[362QSC뮛n36<4/bKkkk{1/XcKY;-ocH3Z-Z>}~]OxVnuc?/ZK Hy @9pY%W8>P([f4760((E׀P[1>Gj `.H3¸ cZc:LFL8봠\P IeA_DjMh=~>ܞy: KL&FbC( F DU&,oqo ]*!nj.8g w;&J&SUCg[gyс? ׯ~X(|~bb8VZw9du!dKk r9_'C3;tRv]yilcC$V\xX˹.cГK.?<4~jL6Mqdmtё)XFqNgټT3֝jZnRȌB1-d3t:ݜ$|{b!ι%sӳ}n[n7)FQGsBp~uMu|1m[ҥjfjjj8y<39B8p*,8罽=?m}W' .z|׷nwwmiwܱ{n;gzޞ^mvyx. 9~ׯ6(c7\;*OۤLJ+ 4!DbmЩ3gϚyj`A)?wޜZ0h'+59g!#kK^3W/֖ ɕJ!Nk>mXkݾcݷ~"rC?~Dq[d2H$~Bz^T*QgF+Lt&[h>es/cJ)wD)eqk | qZ{KÇ߸7o}[|W.K&dsW-̜񛻮 O}vɁk?"eBkm0.b:֖-拃C -JVOf&j''Ri7GGadXG.E7ǸX |eTW:"kAܒ9N>?i02Xy" \6b +t?Fѡ/^SO=31>9Z3Ό6Jk߸KSpGݫڵk{lٲokpK/[L&<{9W|ve]6?߽~|X#…/X.l:[,0b,ijkoVF>1 o֬ڪB"iyZ)6&ry71Ji`nHhk B.W(_ޙ$ L_<3a+u܋/Zdb)|dzhSږ榆t63>:IZ[ Z(]wձJg2\ZB0,Ʊ0\P(aNz4UBڡH):; pr{nOPHpD‹UA)T`\iX+-hUAv\r||kA57] uvlٲy>1oἶfU%Rr!8/gҕߕ?83ƅu7!N:=',a .}/$ȃ(*[c,CVqd\a_wM5mLVB(z{zw|x[lۺm+ϝ!\rYuUBJ!^}wv%ŋ.Z` SW[qMuM}CRzr2](]Xb)d2PUU&ULLd0z۸y,w6tPHX}: E疄\`9~GwX: #42{RZbX, ![w"ҥϞLG%!8,Xck.l/pTd֨|EYκ ('`Yӹn:9!/}@yC'D¯JV lϗJBpBrƕF0޼9ܭcc/[O۲m۶6\x!>=`>7sy],'}Iܾ^*1.ZhkaA DkR;<2ZhrrO]yRvvv!"7!~p`߁ 0VڵR|*RqBP*UlCd`E֚(^z^HO/S߽ok;g`ŗ,SIˡ;fbr%mcRfR| dRtVsyMM>r|?8Q*Z[/!3)Rı*J*V"+k-cD0 K_~ŪO|:-[6]`޲KW'L駞M*whdSSdkL6l6r\[8oW=_]K疄r|'UP ҈Hp! 9L8 a1ڸiXP 3ۚZ?x5u_[6mqEB=rFZk9sfRQ :S*So5=y\v-&{=+/hm(tTRx'yPfsD)/֮uX?E _nK͛nD0s,h/Hdh5ƊJq&jNym۞|5W .|H^"˄I? Qn22& X* y:>ώON, _6oٌS3tl#+O:5rޞB;H;'sm޻uˆVtvM֦SU /|+cTy Zcƕ$ Ϋ'&k秚6mބ}[܍˷:rcs@wwboB(>яmv]t\ygX0cF[m5ZF&* (o_0w͚R ˗S4`XU cswr) k;{aV*Fdc+/1" X)(Zm׷??xukzzz߄#έy@ֺXk`8RwŗgϜlE([+m݄`o=1g·`SSEK>Or]w]w@ȹkZ[躁 (=|n1XRo>tt%KϬEc6V+=>:5'71's眿կ pmZcDG?'9㑊ySe_bťU5W]BB9^ڻwύ7aFnk]K1 !ć%/Tqk\R5u_җqBއέ 6~sn+n\?nzǜ9V!( !?466q\*5wy3<ӫ^յ:_(j6nHB9~ٻw7EqqOι{1D\{M嗯:38~6 !b,7(!2ƥ3ZnRVGHyڰa mVyJi@@RȺT_#s7`uuvmܸ̗/}Bs~-B9osBȻaB8!qB!B9N!rB8!qB'B9N!rB8!PB'B9N!rB( !PB'qB!B( !PB8!qB!B( !rB8!qB!B9N!rB8!qB'B9N!rB( !PB'B9N!B( !PB'qB!B( !PB8!qB!B( !rB8!qB'B9N!rB8!PB'B9N!rB( !PB'B9N!B( !PB'qB!B( !rB8!qB!B9N!rB8!qB'B9N!rB8!PB'B9N!rB( !PB'qB! ,s)XIENDB`PKFUC-Pictures/10000000000001D4000001D425384674.pngPNG  IHDR EN; pHYs  tIME 'LJP# IDATxYuTown9Cr`q%+DQ-0,#`+H NyŲ%LQ4"EbK2G}gnosN~M?ĖELyݸvo׭:uEQ| (JEQ-(JEQ-(JEQ-(JEQ-(JEQ-([EQ-([EQ-([EQ-([EQ-([EQ·(([EQ·(([EQ·(([EQ·(([EQ·(oQEQ·(oQEQ·(oQEQ·(oQEQ·(oQE ߢ(oQE ߢ(oQE ߢ(oQE ߢ(oQE ߢ(EQE ߢ(EQE ߢ(EQE ߢ(EQE ߢ(EQ%|(EQ%|(EQ%|(EQ%|(EQ%|(JEQ%|(JEQ%|(JEQ%|(JEQ%|(JEQ-|S~y~=@3+_W_+>\Sw}>fz}{O=gs?}}׮w=w?Eu[wyEL3J3QPæ{} k|ٓO>,=#"SYLLDUG~}'zɧkUeP·(DywbU5&!HS6Y{_7u"x#!{tw}SO=g9$_ \3UUD$`.Y뚶 ]wu׵{WVE ߢ?O/vB,3q4dqITuWCBnAM,6geќ&1CGc$B 마P HM@{@`kλ8~Gr><"3/{\9ѬU TDDU*"vLTMEU0*RqڦkiW#=w-w}S·(/gC;DQDQMTTUErbޓV7 a׏h֎  Ue6@`"]}0̯~}ϵ=g} 35ӔWDÃ}9dΩLDDLL̎]99cT5fF$Uef爈4TVkfDBb ")[϶m셼(Cbf&Dg5R@@S%UDDMT2"XVu庺~WUeC$fDP0[1Ųev{}nz|H F۳iЛ͛'w]6T'"f73%$3S1USS50@`@sHLI{W~꯾RVΕ/AQ=Ň>Gk\K9v`P ;PWUScYD(,[KD+B)FUp{~&&8BF α,!"fBbĔ_y BwcIs)V u([e9oQ|M>ί'CMhLE)Bp@@dH`6QId3, Αf05#:xEJ(޲w I5'9s/ִ۞ T\]{u~b_yo}[֫WoKYf<ڶQ,TE,gw9$$DbRιi_{'0ujֱ/xfLYN%|k ??[7PgLT0e&@`pF@1w`*bYL @ u)h0S1`$bHE3"^<;=wً=:E0û7 gggrzlowqݬW']zrvvzao}[.)(CF,͌11yyIlGC?wDPLDe)6{U·(h_/'i8yD0B`fDEBtd$ ddFhI  B 0! Ǟn{"ݯ<G{U}篾||N?Kggznyo .'"JH``ƈH4MUU1%! QT{D1AS@% d404QE3@$$R@Bh@$1/`9믟l ҥۮs펺/r?gmiƛ7 (Ȍf ۋm._LM]vWOi'"VHVQc88&5ՓM]BONOqD^ª4*">Kc?sw|y0BRC0% $8ODtBXwϽ /1iΚ Z\23ǷnaC`~k u[ ߢC@mvʒJ%a&^*(l,0!0 1 !UUA bƘc6!2ZɒRY2ŚrȡgϷM;Ǚv%xvz@}4wqًɩ{' arPf~ŗ}ic"9:ժ!"Ҙ @ L D%wsˁo ߢW&OBjG9". y13!Ks3Sc4&H x B6k!$9)f8(ಘCP9QUJQ_bj&s4Mmmw߸;::ڬC? .'"4uUU1PJI[u!TmSk>81qw`u]4p `@c4 ZLL&@9-[_ʗsժġ#v@j@ K'rTY@V[pnib;Li-:L< ~ERSbGyW7v;;퉴b5̸mҜAaNNN|s6f:>aܞjZq8=38,dDtCib̵9OH ˅! ڭV>DYl_XOOG\ ]U?'AʹD\wW^ꇱ*TM[yo;>S/=.P7<.89g0`^BT~zI/~a9MSTÃ[}&%5]fj{1Bʫʓ#$f1,`m&fj?>%|oz>aV]GN< ,ELP`lHƷeE40e 2ˊ}ONy[ׇ,9 Q헷~*TUڦ3ǻ/]'x,1_\}?6هJmjZqs=;ڌHgE{@5SU0_'U2.8r*)"ҭCQ4k?MHV5 # SMBo0s릩UƍӓS LLfv]޹S8߼y6x yfdgQ4,??CѲK,~|ICUUއ|pYXL,yb].VҸKLQhi-h@` |y 40φж5sNdpםo)>Tk[v~7Oiu@US@FTu]y@#rn;=! /G@Ba̸:Vͪn^z姟yNx󦪴M{xxιʅA5,RU!09H9M%yiΒV/QJʈ>p?Nף9{`WvyH o /y?_A^]u'JQT78!Ee2-##PPL."f& )K]׶u8O<9gf"()P9vn99OhַqajY<ź]o*]eD=1;5yMd뮫%yUwO<Ԯ_{뺣v|p[1uS{/@䘑!2{pNDi)!s LX=,U4XLd ܥ򯦊Q TgFT8nvU6Ms10ow(\ cJYwngC^Vιy7߸MY[ڃbUu) t|cS]:dҗ14UUۖ==9$8#!{tY=L4 36UEjc393 ,_W! k_Y  yG̴ Y Q χ'ն31~g)'?-_($$]}Iv][z24جCc̪"H #* :юo;|iN7OO}J}膱4u@>I֫mP 9zqUS~W6nʪM)s< Db€ 9Svm6>{n)}?Vu`f P$]s/Uv;- !1 'P0122e 6cQ!$f*LDPc3 HΉ# A4l8>+w=(Eد}c|zK*ei8ND*1)f #@T3U45[s"*jU_9YMShN갷ՁnnU:v!hdC?
  1. Zͺ{_|y)\Wؑg{RnP;BUHiʰH* 7@a8nOSB K*-DV:, & Cy9&3U$Zԓ@N=qw[_>^·(?2YBѭbd"6f`gBywg$ɓ LUp6j6`*GΣdF`(cR"CbD~7]lwc3R@$`0]_uUn;\?M95mHS$Gz`9VS[FyGqCo^sQ<)NMӬݍ7?<1\>19BU[yQmŤ̶nC]&m#!2"$&C"#$3E$eX* jx@YrazhRIU/+fbgyv2ͳw~!f'"֡fG TYRͦC"U5v\Ԯ8iNq)ꘛ*>5)2-p9-a5KH30$3HsDAW;31{vfEg'{oQ|O~7~cjq<"!3͆,&fMLUfMSnV(6D#Ģ(hI38GfL.]NYn8NˆqYly$"{{{{{+D['1'Q+/!1{vZgԍc"bv+f:ϙ"k)";05@5Sb@@@f@虚`VKI8 Z\1g)*| &=˸M1;+Q &bUM1ŘW~ء([㧘- pBS)6 8ȘMTLA,΀2E]s΄_J?7NR0GUAs\KɃ8æivBK7~Ʃn*@CvunYrvMKs:;@q홹G$[3g~͓Rmbvޱ&3y󘲚z=.x)U\) p hf(5Q44TSPѱ8|EzTR1 9b"GH4䁢u8_Ʊ%i<) $nd*?$'N%? bԄ  U#0SBE@UC6b E5;v0#JԂ|j-W~Ԓ}۠j'h3)/˺mSLUq~wǦm=6T}1I"EJc<ET}.κSb y8o7ׂ& Γ*u[D ALs`8Ήj@YHDq!"GYȐWLLJ$٣.jم[qu[!"$n/q(֫vge[Wyp2anN)\9 S-hfRđ 'F *Yɢ%2"cCS)va2T/^]o6뺮OnvW 1ScUUguꁈmZT)0 e\/qb*SLZhЭ tެe&Oj(1,@zl(,Yei qWB\ @1c3#0//EŔ -zU T05D@B 3d̦ k8ŢӦ7Za*_t|s{s{Y(!"NQe;@V@VmOUf5fF9@D)KVc0F[D 0TTE{oS*N NE^]8xp|5.B1JMW]>8oc0Ւ~whZ.zB\8N)f)XwV~n`ELJ0zyӵW"UMͫz}wE!gtU !@\1z'N IDAT_/\$t쪶6zv4o91K. 3!) 8"$QjTU1ilS,vۣo%<8rJ9?۠)3˦mwĦ\6uS)h6ԑwaV8N4Lt<_xZJWh;㏧EtUv/~yF ln=&Aypjbe&S(e4T@f0w9 i`01gfB#0TEJ,& AnHw]Xrqb=~g?3멻~lSHĒ͕=^\?o8g˿D sƤ*U HTPg{+\@(iY !mpH,T;J(psMIC3@8d)e\Ŕc4}oV{xg$[.9碅hD0PlQs>Z0q4E|Ɠc?dSK)9]!BhIPԆ8u~*?^xS6S͂XL !(I΢LDjl8hݵSg$ǡQd{fzO>~y˟Z, LK)*E:Q=|O7{"!I94up,`g. D @bB @ |7lPDq4qv0,Pcln{<1źŲ gaYVb@s[̹cӄmwsVjffj28'O|lF$@Gox\-'ߋӔ\rID 1#!|kPC:89̌[ *HH̊ꐘ @U T_?I yPajEUA=躎愄X|Q8#B)G)Z,:Y-|UUU*'* @y>2S0b?ĩHۛmfPǔ.ۦ?CU,)[̌4v#(%3Ƥ\mՀɪ؂qbHVr1Y9FCFB0PQUT#4"Ob* @H*.ۦ >αA 7Տ>բMY`Y^\=r}ruvq]U]jrL 19[䜚iT8g;_!F@Rl݅nA ~E@#SB55#TQ)F QUSUiUU$=r̦9USq3UmچJ\JU1Ey3kJY!\*hb`U]-""zqqΎ]!8UVuPP@ST%)v35GC<%s΁s1e00&5c `6wĽ\XGjj(fjj," 6x[.,D<*hJm^ _|ˋ/?,44 Uu޳|}թ=q_{_q `*z^vj_R i1c3YQDUb~<] l&E)vއP@":FME^\itu[1" 0 "GDfDW!g@0Q\?^.L_ CfcǡUsP^?YF{AiV虏8MUUE(@*5 @CTS32N̑H\tݪ+qJLN)bƒY**6\=~[,âmxZǺʪMhn>/_vURȬ\h.9e&RR.T8/}͎Ǩu۶M53sp1RTaB +12A^;HVTfޅ*xR*ΦgxJLr\m UxM=1Έ4E@"rc wmzikڦi.xwyq'0L8"պj ·CqhXŋHa\jWeb=zxz.v4*RT-˦E %!p`NA#2%,:NoC."@a<Ȧ{"8Ozj{L<+r7kB/_ߡEׂj]m[զ!8zo=q⟟9f4BC1M)!;&@Zr^0ϔw ETT PA BpBU(CRb v9_r5{A:g!11!"U5D"9PJ6ki^}'|iED{ۆ *Ě|؋T`e|Gt m]- vAO.>zt~AW國X;y'Ns_7w'C-fC۽շ0 SWbfbX";*6!2haﴪ* aΉ!ʘJtںZVD/m׊S˹NsTUMJQѹo#".Eg[x]{{{TN1:B候c3[.LDl0LEёcLYl &O+b̌M 2 4 }\\]]^\(n8xvdf b #NfP T袪WMA)I~*wx{{_US=@0hJYG~wl}? Xַ/W%"CɄִmjQcFdF~?1s;U'~W#ESTSdW._Fv?BslL?D53OjĠnNlGB` 80;$,jS*k}?iW4c""y x~eNEF3`"BC@@Sȥ*6mS55;OE{8R4ț2Tޱs@hRi.DCCSGA(rT3U,sߖ DPu]o4uAcAG,gTX6 Jibn=T9JR1TJ<̬CjSL~/6o6j^ KuuۛŢzp8f90$D5ͥi.K'(@X^:P縮,bdQu\rq΃P.HǔqXk%aMT#FȱLiLŹjwEL T8 Yx88?cRgԄ8욮e߀ic4:ǡSIYQa*J``DDL*O(1)&f*9$< |Q4U]1 u45^~yf:G;64rlJ @̓[8N蝯2[.#fr,Ve]U,'E @Q C1CSĉAFDQFb(P2I:&7:D:v{iT|k `i2φ} 00"yD2ŔJ29y$)~_Ϟ]W* ʒ&,W>McElj<t'cMH Cb8L/ZUeFow77'wsnS,6<~0YymyY#&fuc欅MJTPȞ]۬vSO(&X g7^񤈌, 1H&x?/onnۋ7J8)lEi<:SP"Q 1c]F-RO}䡈鴻?I$Uc*}SM"G4{qZoo?xO~$'N֯<4#kۦCfĈ!T׮YtJervuwLFt[b7^дVDd$8zbF}UTQQt\][-$%QCb2S@F3Ǩ"])!(32"ZJXijG/_J%ZLe !0*(\6}LX"Z(j}%gUEL# $B#h A 0 H2Ӏ)jiWƇOxͷ߬]4E)] "0TU+ـb C%˄Qb)NmU1uϻ%F]8a)jcTW'=q⟮VL z뺺 T!Ȥ:~wuR8L)z_5yʱSW.?[0a(,ϖ](`;@h6yZ/ kU9g3";g`P"xb9;Y>W/_Mp1#d""]G(*j1124Twy-Ы ESCSC@+*P  ;ʂ*, $R˺o~?ỿ/!˶fE 0 F!=( P ,`7~{/S(:WRR\p3 އ㘵dT]/)$xğ~;Z9P9TmڶfTrUqJA@2I|&^PE<6 C:DB)1ž[pzq1&"nk+AE3ȹ8j"˵i1IJ3)Q&D0+3!kJbM.ևO?裏S)X,fX%źk꠪XLmL~'ݢc`v`D朋.00VK8 < M=7|矷M7 ۔8_]]KH)u]}y ൚;q<"m۶ l\J.J$ð"! DPk<(®Z*V Ŧr F0D$1,a=l7,ȑܥV3 IDATV3p$B: u8[5w'S5UٖkZff2j^7//~KGKS ̀2 :6RR5z\k5/Ձ390A=(M|:A74篾o~/Cپ; yBOQ $Ymƺmc8zGDSR# 3RT̏i0"0-s;Q3pjUk t]ߵWwNBHASzootwDY:"2v`X 0Wb0"s0QÒ&%ő̈<)T+cYccjqUUqi:# xCO|8.Ϟ=g]ן]"q?GZwy: Ͱw?6< "ndW&79#1yJYB4fs=اTvs]\w0vxo|_ߎ1"w{Ӧqʲ`(Q92_ͦPS@ CͲa?q={aX?~̒40 ]/<ϳ%B/^.K6"adfA4뇶k# :(u „A@ՒKj*ru$nn3P-"4@d`NUݡ zepGVh^$M7c;ᶻ?, (f*b AhFC=NBD @`r܏Oϻ%  7s, k-Ս 0 /e',PCпq5\84yѢD8nV 93Fys)'B'uB$Zq^)Jj#Gn6Gmyɛv7/yВs<}58tm\+22" &"jWsu4=U~w@apUhX7If Z K-fD%i ߶q5{/^>z>SͿIa)R׷\4}W X͝ڰpi:Ʊ{! /R\slm@YAU/uLBU1 t8nw&-9fcS{ˮﺮiͷ߹zu})/|ß~mw?uxgLRxe)զyl6mB,ڰF ℤ~\Jۆ69"i)"CU'vDLp<U㽷&-W >`3b_fq&AUPfG=֛g7q뗿/mꯦfU6BAr$rep`5sDr7 ΀筼x QثCɁj),1ώTJefp55COw?!h哋G`02'e6/nZpP,TH2aj[sK(T !&N .yfpKd; <}'7&R՚eSW~_MMH6nϷX@lTCF 1PSV) ;bZLZ0yf$ 2V&WKݞ1@ Aq'K MX ǏMts}۽mz !|m5>~ֿƏ~18ʯڟ~"Vy^UJɓvhјOh!4!,5D@٥dbBjݪWt#W#"Bd PsǼyrf^seYVX "#G 4^|yǻźߔeE!&j H`hH~ʻz}.I6]JM4\+ 3)7O)o~+Aֶq]mnB X Jq<<$BQ#BТRLM N;S!RU#AJa8"0S)oG4)!D.hQW`frrq~?4Mj'Onoﺶ"P52__-"|;YaB񸿹)5ZkZ 1Ͷa9^\mPI Q318ө-i (#@h*hHl(r" #[Q^8LlEm]juW6S"I %[70/+a ԀPUк)VqA"v58j;O8*C0Pv[jB$JU;/U>}Iakڸ^wclu:֢@$8=>[S 3Qu}=LŋW}]Vw^88/pqvSa?%]? jm(V !ɂ'!6ŬZ)n7^ "bVb Dp /lռSeJ1n3eR6۾-$"g-f7mۤԾ5~o, Nq ARef˷yYRT zLmW  %OS l*狾sw0WDA&R`>iЌjfDG.HtJrTo %1-y<M)M~g_SB2WT љ ϹCE $h>߲ZXV&֥H?GSaRӬ& 5hj٩4Rj9@,9n:je~݇"~+_qphb } ^]{hݞO>m"c&T zZ\!bR|u:{74B@7cujSHB<q'R)6}g/ۗ\K՚8"žv&|?|۫㋘Bjzݥ(|ʜDs`"H2M%WjS`<&GDhRUcF7TgUw^tGG&3@BFm33 JM\qtlgs)_P8H׶X'dVUOm((hf|U@ ,Pk) Cۿ _g__`B"f!9֊p4r9RX ZF<I$'EH%Y\ gP^msKt{(K[O1aߥ{t(\~MS?^_1'%*d!Zљ:65,~aZ> p@GdwRCJ1fbb3C@$Rp35$ $EMkyqʹ}Kes;A_dzBjdjb @%|NTIEPݭDq<]WVW2DDIlsT TDi)Zj/!r;=Q 5=X j*O:ݾQ8$֬HXV(@Nv^R5mw_|b h5+8Bj#ڏGSc~ԈH6*1 V ),Êjs4RH>}+Qh}®dtIgmT8x =nfiPE]Rخ$Fdb ~W+fA^G#2sjGE1H3-Rp/(]LKwG}QٰC '  !N"~_6zۮnֱIwr8Svtb'R$e$r5!;WiUz;41YjgWު*1C.ݣsUvM׬6HZ+1^/FHN!?p`k v1DTvm rU \ʜмM{㷟tWޯ۸Jnm6J$"vb1&EVF2 AB@w\t3BǗ~ww{WZ4RUͅCcEbd"7~wlclDB1k 4MHI0 êoZfjD}98FUl"M1 + x9.4Ь8ӜsѺT5wK)$w۶,ݭbLDD~4D"pJ < j3~ݥxin/eخR-|%2ar^jT(@ 0J(8 O[#!EQ5ꁇ)#'|Ҟ_2#EPe! "0ef@6F!qA);p|?Gx|O>ץ0@)qjRnGnڶ.S 1{bZM "n:2˓ uC!aOy/6hD K`1ST,"1#aqXK)77b{n7w}'!)LK/?彧MӸݮmVgERT?81!P-2+S"DӔe@8M#NMv쬖e;\yvCl*bV3@>%Udy1j, a ;'4o]㔛&]{KHcY& 1PǵbFxSmHU5Z-ĮT!a")~g6\<_oWm5W+U}m9"dDv(BZfKιhJah<GaO,_ʗS>#6*"sQ8v1,)zE- 5HZ*i1 )nx uEsy7iRggMLRpdDD7U͖hg=Wmp6 k.5O9_/0tMӶ]k_,s)]_>:OM: MM7+0ǩ AL8ZQb2u7i)sdj'F A˳7*r]]}i&$9:bHժՊ!H#%k.^<u.5#E)R JٽGp 9 ܔĀ]вyB8xD`%3. &\ /^?9R`C&U@(!: ̜A"R\ǥiЫjmZ.IݫV̙__z(d?Y)Plyu[ c*1H\ B嬀DXV)ԫߤmWww]8v^qzbrOQ@ RId]#\U]~?.La5=;?eYe@!l#2q\ϋ\<4R*v` { 1MpX朗b͡qh1,(LgO?zwK)&!X!0yun./MY1oΗ_^u1 SJdKе8 qa8R 1q*(hnF@be֚s1 Iyv2m;IQ@b3HM;TCpr,̵Y7fZ4;: HY\Qxuu}{HCM__織RJ/.ճf`ye΀- ժ819qHyUDVM۴!,Y_\oo""EB>.9O˧$a?E9Kv"$d s)E\4{^0fYnfUp2!mlrqYj#7/g&fZ)lW:q.aGf3)'fdB%Z,g4;`ueg?Q|+gե%!'ځaL$ *1! d"Eq0ggnCqP `@iЉ|v6\ĶESwwbfw|qyZ H䵎4Mye"   Z^]iBzR8ۮ?/_yk3$@,Uz;0V) %b2ju75:%{s·qڶ =>}20jj1 0 |d a~lɓ= >%/CӬef5O}~ص[5č.{F0r$uhMlf])2,SiZP` it "&rʜW/??ƸPuFa׌h϶=VZs"HIRIom:R"tTCg M"&ff6#rNĦd U3U͈S̋2srsjX%ENO2bêkڦo&wgI-ju7&FԦMDSJQ *raQh@*HPKaQ 8)L eyٟ~ۄU (4e!WOHU_wj5/N);=<蓏_?1? 4DA HKmZH,Ab"Re 81#:ټ(c/apLW7׷(W#"n.B b@@535' QRJlfD4-矿8G w9T˫۫黦Gv۶g}]zOgMۄЦ=(X41H@|XBHZյ r>8Cyy)2,CQ/;4Nu">GNH4E:~O`IKѥ{o\ԶVؔ#Z)1RrّZ=i Hp1T="Dt ]wvT #.8ݽ"HYu^M$vs†`̮U(6 >_]LMm79@5!H1aaDtLH ,sѵpf#Af ".Woֻ8//>4m/S20tDZN77]7x8qiHJV5+xzhWC;j5aYzRn7m7mƶ{۳,km^ˌ,]Ą$zPϚALPSTHHMrYa3Lk_Z -1AgxG묳z~ T7"z"Fˣ cuo`Ľ;3&Bd&b252_.]7arJa 6#QVYUǺːns>=X qEe;8:/?|g˔kV]mz ̉)JA"F8h1x)MJcU$1p*$M\DiZoGAUQAyW@4W=wbyפqdvL*젩(!0SjfǏOe}_z %FfA$P "wvgmєa^b4gd#g )iRD4 nH-WGy"k1!d`jFH fL1V3Y)b2yY?;N@2u|9_.fWs]_UUu|C8mŠĄ"0R7MDfj>LufȞ!HiɻR/|]<v=<]6uE@R.7z}8NAv7'E6FY T5fu.FI @\`NĒ瘳!!2sJpRKHO?A &󞽿>r8?^9xvCQYo*%i*ksLb1!3)euxӤ%?saFc3S1픫Xf6u8/;ة" XaN.H{!P(FF2SGu)( ôv<\i9_bp{,E9oH\,HQE 43@E&$&"G8qw|0Mfj1~%!XCafchjگ~6\94c@&4S( =(1B 1G{:QS2.BA(F:>u ٜ ճ'lZyVG^{7~7_o総goΣ+%9Vx7L@zU=k(j@fhۆةnRS r1M>*NyJl6ַO)'ђWK)!X0$Dffs ("!)yiE/GOe}şADs N  "!u]5OnvݣϿRAtk")H)DH)$ÌdD4MyCTĈ{<@SJvko>8<^4&-y QR"ݓ)&VͶ͖e׵Lg!2 Sa\8AOW:qp7aҫ7>8΢;Q`ʻ;GΫcU#dvklH4b ʌISv:_MMe9t;6nrxz/>E8nw[WW/SΪ&& ,N#LZ5QK^/`ZkyaiBNCpLioh("`r!q T7BM魶/H>fkmp Ya25@4@ET̈ЩEfhRo?  AbQcQXe #BDETӃΫo>/m%of*&SJ0yT.TDDAj))Ϊrum]U!zg\=Q)㯾jWkzttFiJHct**"ocw]Xtwۿ)MSsȳs0+%8pr Q1c眪NIyU-hƈf&Zrɥ pR4LHѨXPA|N({@g0#YZ77]6ggA.o9b|GoMC[]gA9&sݴ `LTM(ICPs5@Ppс[Xz|9o^\^ P /kiY,f&9 5+*dh8eH=߼,/8|9+#Q`XNK*ŕK1ff1>|f>dR܍K&*R:2"D1UDМ"S7뼣\W13RPW t}}s~v~vy"G޻hV"E =9{mPWmۺabΞbVU<[ZTKqȊ(E9]q_<~&cf3,%eJT>fa7ĺܣfk, X{>׷rUuwwQgmS;nktg^}x'w_}C|;"S!`Ι&kyR{vl`0 xDE'dcq R91A0"ȞJSgaRʩ#&&zz⦅O?&0!Tv~X-W\1=Blݽw n/?)WUDELL` (0c X2(9*lrL*zUl`hMd,EUmwO]m7Ϻ=9=~?{0NӘr*傘\PUÅ'""t0MӔ ڮ&U$m] { ^}Ȣkzrmه|UըbG˓\ѳlW7>vw!TP^ f_|m0i`W_яۿuzH"fHP7LJӦ5JTU2hΣB v` ٩:U549REێS^on֢d|=;WfmT1!;gLDu J @TdEf;Oϯ="PՊMQSw4 BZ>u1i'ھ@!" ,tvvq^|ubHgjS @Z- 4 39@@d&|!:i6"bZӧ]ݵuK|>>~믾qeJCUg"+9d LI`*0*"ЦibD7,VWUpȠ 9 rL +mygƦUpWլo_W7-mSX 6ULV1z}xRa~z㻷'@,#;`Tj 31h hQ!2@,b#;(u\Pb툜aFM޺UMt`諢ZrC?7Eq$")EHN]]g@#BDC?o?ٟާH bp8U ZpsSn6Xu7v^U wG|Ҵn?nEkvL@"PǼ^w"+(`7oUEDR=ҁ Aw"hYٳg/Ve״9K5mۜ}ɧC?\֛j"XW3OCtxL H!pq< U0iC8ko߽3eaRED$e&i l( `%c+Ǡѡ DhfἽ()Cߜ?=9L.$JƼD}%mwϯͺ/7+}g۷|sq}3?<@dq Fv\'-M2R#@SƮ0bs]^Pޅzű6HLog?7xcJE*!,XhɉDB3=-E9ѳ"H1 "_~|ߏzw?="@v@{'jUw!j2@K1;giVꦉUERJ΅PMٱg&b$"E}ٳym)Xf\^]]}?LۃA]ױ8x.a#Q%4Rl7dQY$r)Yhލy;w="F b:*+woungUxG,Kdh`ΓsPg7|`6:{wOz--d)Ϫ@"hGLS@C, Xw4eDFdFB@IUwiiXggn/+2p :ءUD佡+bj NLv7W/K}o?ayOHTD}l؇${CL R0bRUhe@nY"xYwpx b痪v4 t;{;v1U= ;UulirND$d59oGGiJY;ڦ../>Rik} !_tmY~L1zv͊*bR."L Au4u*Tk*ʇ λŢ}w^_srjf]pnڏP3 d!58<)Y]::==O:d3ٳH$ZLT\XJ"zO`E 3qV%XJ53ȈZ̢#7*DD$ a;~w$NE,FaQ)*J[E0؉Lb(&7[n},/O!!$BuC> Xf`l(S”0Ej1W^AJfhŀ(ʅY4nMLP71!mn6O<-)9s]`7Ͼ!߻u'u]]\_8ۡ qXXc8Դl^)ҏ1UU !d12+HDĈD 39Buΐ͂RBTEO L@pıuXy ƛb=zvvqsvvur6)E )]@Am](Tr^,:l4n`UOfuzC|PD0)$`Dזɛe HB̈RAQuRWUE$b&);+ϞM}v~c31U:41CQ1\M袙1!:̹,,Y"4M!Oe})_O~yB"ff3+"1u!:޺` xCD433E i7/WB*!cEEn.gͬ 2~y:::ʥ Y2۶;><$z[b\pf/>}J?H+mf~q9mwcaZŪ18apƹq r~7m1M3+rq!" 4#&b`cc W {L`[T͛?|| WW!9Q!zN{#@I"a,)a\{?|xrrQI铅&Tb13%6Ldb9T$ qJ36 hD.&9r J)8Ds>xvgͬ !c% )aQAU*>?/_.^{?߉ Rc% V0y329v$br*"jD #Ͷa11K|)Lݬo.Mdf` E贈FwE>vʰj IDATM6l'8N)c >!kDHymל{F| ~}b~ޝq.Ο>ݲj[*"f3`rVCb&@F#1xGc}p'G;Vǵ.Ά!fTP򪭞VD<ā 8 jdHm>?3qo_o?[WE/D2yonR2\7* zI %BLj$JcS*h\R"RDM`٬U)Zg !&~C(hsZm>wY/W{]+I6؇ " V{LT$S10?S͓ x}fs7utݬ z 8kNoߪ:= _. ; .~t})4MZ/_0iw~XB]folvsydBN)9p8Jy* ((hpdjMD$Ðz0av@{ppUH􅟂Y[MSv$`S2D3%QJ l" LL VT40 "9r6S8^,f3|v[uWVx5>y|/C󛺪E{t|677<;#cjU5w>;{z0_ձbn޶_~qL9k9<8$FdR4Td]!:1eU3Sebd.f ljX)ܿK>Oc\3%393T P+EL_vU]7 2\ʝw?w7;GG1甔 sR21@񞩔P \;4CC9PSnFvs0*DtX] vۭ(3@tHQ(01bjj7V:Gu ~gcN&ɧ?p{_^wY_ !1F1t,:=WUL Mi.3Fڧd*E.BD橘sуJh; zin7N.n/7vx}?R̾}WGٜ|<{>nz;:8Tr>w EWWxGqLyگGlbVP!3)֭i/..q*"*_p bo~9{t՝l96ӳD4jc*QAĜf33C4MK41l:C֔<+ow{7TD{rys죻<tF䙀р8 ea)"J1MI\4xI0 G@DL.xDDdZz+͗*DƞS) H`ERw(RTLɹz-Hff ~/7e+?!y44͢Š\]srNm`g!QV唋#a+Ŕ# YQٳc HR9I0d(PϫÓ 9aLLlݦf/oq>n7fmBwGϊhӣy7j\̺Y{uy{}_JlvNay3h$&`Fjt 51 0a"JRJRj0>jiWm{pټl>OiG~J<II]pCȃKSs*H)6a7$;O*j(# !0D8\ܹuw?ƫhx>Lmo[o|' Af}5p(tK ) ǐ]h8' ԍc UB&6ܘ2/ڇɗWorLSv4d%#sL` $Sde҇ɿ|;ɣ9$V|=k*}tc3#:X0\b hjŦ)o;['C\.YԲwn,UaƔRva٭C;8\=xa6ϟ_4˫Qmifw~iYoyau8oHƔzOL`)* }&""01"wL `݃;M6۳o2PZ Ѻyol7~FRb&Cw@:xƓC)HRԅ`$9[mnv1knGrY[e9=/ܿ{q~ُb";cF0v4lAlDj&%"!9AYSi|a ;@TQFzryY7u\-q c؅P͠O W` d|)+y𧿿-(`h*Ķ+4BJ3s1x"I MD$& JǸawWCGgg;:Z޾{:X󓓣mf\=?Ǐ1w^}x?M憉gM7 }Yvϟ=w}?i}bUUq-V .#V@BfLSCDt @*G3M Oc܇)_c&w+?Gx:;nO8C8\HRTv4 9,t92t~QۦuȡaLXכݛo6[֯?SC$uoLq.$yH)C3BK!¥((";vhbɓH"ŶufM.޿[7:伏JUBv>>Ds.sv|_WqO?`fGQM,yUu})! O0eoM8VH g9:Y_fs\UU޹}5nvsgϿ=|üW0L>!Ƌ˒ 4v}W 9=={Holvb!8 xnVwqh)Y9*VLU!H2  h\T*׬dD@&9 !!~-# {-VM6[Lj< \H!2AՒH i9OTG$24Y.(ȡ3%u!|peh w9gGGwN;y~xttsaNn۷olֵmS~a\ov|C{8#%C"_1O4h**'Jc lo Ğ3d)ZU3*ߺ^_@5NMSW~!q%kP(DH lؚCcO}.'˙+`i˳բjjWO߽w?Mu|pptlΆ 5.? c4 2JΥ1UF| RV)]?0ih.`o{/bHEŐ;#اUhEU//ˢ?@F& K1WUScl!*J4*Hii&ބM;a6&Gd<!08`uߏ;wrr4MOAy{Qo<{zt75iiTzt=>zֵɓy87ڶmj>ӟ8z;vղ9v@nճ:F  >r`E4:-+j@*C,,-$!RT w!X7ˋ'OM6n2)%c6yso\:x&vtmY"yUt.W~__{+r ACUBVL; b #DjU `n0K٦!i6a|{u-z~jDͮ.{u1:#d K.^O)b#F!MBP;`r]YSDbӆbUBSRS 0808c0PE$$C^|_OM iۦiR)̄I?_23sDsF."Ȟbf9] Ȕݺ/7|iھNiv2uO*^]]o8}}_~{9'mw'ghnټk>9VE4ݥDjCbUjXqx 1 8F0;  M5gM%/f<0-: !Tw}nŚ&]@NH!jV f${'%Tt[4b@)'  3 ~7pEΪ'g췾jN65/B,̂d! r< ɫϿRe7MŨjau؄,]oo Hm ^$8kR`bq9~) CRsz~Xپ/_Qïš<0LFSqMvݼK)涿ɘk9g۰zМB4(LZ)ds#@oz?><|qyyYo~aLe\xlg/>:/?>:g+P0`iv1_4Mwwᣛ;b^V!qtd"BfdB8{9OpĺJUOd+b.]XMn-gBa,cGҘQUvM&ۛMN`QsRB-%@@¡cƪ>=:ӇFd+cp~U*Bl5F1{.;Ӣi(`3n\oRum&W \*#zi !hѦm䒝(䢾oh 攊28fͽӿ3ԟy;?b&ǩtE6) Dr cF@w{rwH4NSYLb$^b1ons~>t ČuwAK\mq/@x|';bu}uV IDATz_7GhJ0m]׳Y7_͏NOs/WۮSa; zDg$.$T|\O<)5M\,(df腙\"8 < B=뺦aLg)5]=_̙`yt豪1\lnV!GWBv}*P Yrۻ]Q])m}?qU#f K)lJV5)j'/Vh?{z1:czHZY& 5S (AC[q*)x)U Ӥ!+En3c`JmZ3:ncwA23d&0bNQJTJr.$|{w/K}?i7/W 5CAg-WKsb"30u& '2 R U-ݣ l|vV\VsaȦpյ -Wb)ꫡ + #wv53osGln1mǏ۶fpz}qy1868c.) 92 owzrl(GEL‚De͖>{~資ӢMM$u42i)S1UJQB4OS^.mUU44c '@B~ vpMUfZXBP`@63UdZ n|pЭo}?Eog>x@DӘ6]]UJqrE@ ͕QМ͇~*.z5Ҝ۫ۃecş>jk}Qi6* {U7b+݈"s(7kÃU3k>8R7~M1V~p̦ Ĉ{FMEp]MBB#ir)cv EV)9qQ8Td5GAG0n0 v#~_wgԟ_/+(ΛW8VYXB`"C KQ`$wM`%dG}7ddZͺrAGy'LutfZ٬Ki:Xn'@4f O[(zR*9NBdm$pQD0@V_T]&<~sjh齔 nqٌhўvv=#³4y iٗKO *9Dnfj j์ɂfݬ6*f\4QġٷUӶd.d !8BU]J DŦ#sWUnJ^;;{7v32`DJD,nZjfDsr~fupz@\ṫ/jȤcJ%R92 xbn$NUc/a>/t+_𽷣PBR[,rqLZ3YFXaa@ؿHnaNDݬwsUEBa1 eU/Ec 7jfzޫJ_x~~zyy4]ƾPTM͔!*K#V {RDvg0fMS}8[,Ϫ!Oe4TVwؠZLHUUA6e_<}^wֻ7m3oy4C%@E KJ(A@%h&Gj(R&Ջp^i7'  upo: HBVOŗuU3U]t'|eҜH6Cə *R Cj]SŋK7 L|~~ZJQ?o!SE@=zL|6[.%n ɫzzzx~n7yֱm ȅL͊{q߷2 3[1330s35wsfCӴ-fw`8􃻶1gOO)!Ī,PJvѤ+lHn` ࠀq ov7Q!A  C 1hS1jbub$7\MyR{  U5#񳋋#dDq ̦GA9sKV5>XWӧ]19vJΪQ. B\nT =TT!g{?o9}i?('OZ~E1cA!0& rw-ӴW:CNԱVU}rv2_.z-ZnRHG4n>V3 DDl|E>]Y;5Ww}}5qu]Wm뢹Lޑ$u'S}4 ejfYSfmM˪|q$HPsvu%Ә']BL}fv~ڂ&2# 3YBTJѣsz 7Qp\t{GhLMR T>r;9==fL ߭whSYWG&CBW4=xAMNG<~oa7j& űL¸@릩bHUDJ1p0 I W{{N{r"3͗7svrAU Q*TG L6YQǩz~MMwfĈ!R Rd LS.:I !^?o}TXw41>2ehf|Rlu3v9(Q:Hຮ1Pւ$8׻P5рhLբSӲĦf]ܡ8UӘrRU5%p"b]n4nKiZfl7zS?LU|܆q\4oXGAplP #z)Ih)݉M5D0+j2726䷾J,u=|:w $#U5:db  0Pr9 9bu!0xJ3 1T|[vz|2Әa7ukR0 QA؊DYUx$BGO\|i2?9;=(Fiʡ KU E #"+2s$2{xkr&(Thє&f Alf ytՋo}Gy_7vۛ(I=eێV0L!3)ujucuZABy͆C T gR1zUp[߮۶ ݎϟ=7 EtRBXW\l5ٳϢp)%7in au%jVKt#wC0@{zi!4LZTϋ,1Dܭ7lqs6tD(Yn_{~A }mc,`r+ -XWYS2: Rjƌ,B%ˋ~Lս اrvyvpt]oj!,4!a47Bb`"$ȀhUf@l>%{vqqvzeJVꐧCU0&U+jo7sޗ^BxR jbQ2k)X1G %4/.>zX_m:>z[_?um7C "\PDmm%+ 0":8:qR3GPR8жR*3`C$uF'3 8 i!3"DDt?#/Dowf] ygtX.XA00tԢ(QB &۪IPM:%pzrs1F0TaH7Rt])grr}8)7Ӫ b 7brqeu,|pp\_ 6M+Ӳ΀HȈ@nMM1JXYˈ%+`u ai=8%gX cg>R Rk?レKhDC@p"E7w)PaPgGjY[k'ctG(՝+ɉ@ MG_y߯yh;ai 9)0BlJjuxrUnruAB|RP!RQ"Hqq,H$,糋k/f"R*F/DA^(9e b 7o~D8O? SnlYմyl6QIfnv"n={x}lf.I u]MuUiJy!mS/zXagOm23 Y'gbB®oon>vƤ,W̺+-9F4 ꄤE8@ڿ9 R)jf@Gb '9Q0`p\ΚEWcB cι$LԎN..#R kb~u!GS)ZXxDm'O-iu %]yB@u''D.'Ecbpȃ|rvEJ0pM?g i_wf:ZCw+g?y_|󳇏t4j@PWsg,Ax߂7lHh=v|ʡ F5bRuR3w% [UWgܗ_HL٬mǦiW+4onM@L~셠iaIn`u?"zǫ1v= ߻j~ݍ%0z ۶Wt IDAThtlknSuN麣U4|>#\ulwwarttrvmʓ;9~~5TUq}Hf7E&&E1Ugor~ssWty* lӠ6 Cj4~K3 {|uyy[0hh$SP;o "_}DH Ra+A xV#wADDV~JÐl%ĸ/o4Mn_Jm?Qxvc?|U]1 g55w#1RQL9%UwӤ%c;oRJS1qn7&DX4MZfkڦzq6u`bqJݰK۶U]uU%Qa|~qKެW,;ZMg!#"lW 7RiZ[Fa_+લB A45tclP6r5dUVf 9G9a ?*:{?_ɟ$~̫E?J&5:PU}aa<:,ںmƾRS_um?K~~C!hAb몁n@gF-B R[xzzzq6]k,^@ejਖ (#Д Y-OyvJ)l}zܹs)\ (Kf9T 8ezUdq4fni0!ON~?y}yão:8Nf^v,0𢊂\cv77AJ1Wv!d"N4ZΙ}(i)徟}ιR7umcCꪪRɹ؛ݻw6oZ^{.3?SEcf4 01Bñk6$p]< lL% Uu UMK13FVӔ3 H U$&b"&EBĥaHqTB DAU):bqn9hE[<~t}y Tlf_[źk\ܺ-!"1۝w-Om%,*yre*f+g.U(?b ݇8 3v=z4qwc׶:UTVF4)O!8CUWu[Ź S=9HsL,hX$p׶UW{qWC1 u + p~m*@_ܾ=SN!3òkŋxNhH@LU$ dihEwWݜMZJu%=:T; :VV:93S,QT;Y,ђ4@Upo<֫E[URR?==}:05m$$u:q,)c*3# $C !"QThŴ$`2jE:G?/H + }qU+xm[7mb)cBX-f%,vn뺪XRyvuhLc`jhn BՊnN6ǡgzٴUSק˳rCABb]AU8c3"]_]iwfU16M얱i܉AJ8C_$ uTx\."@~LCVe4!ZJ$ $T9^R`je\0C%Tvw}M[7xQ@v%0 ntJg]ՊT \2dxr'< `B "HRvoT4O1Vrq}s}cp\ *wUgt')bQ~D@n!4*+GMuJ[~^O)O9wM'T 㢭1]ΎVr f bQV]Dq+qJ0VJ;GmxQW=#!/nRN´V˥b̦㎐ڳ{a MEUp753DlV ݹ}UUwYcˮ93Xu*pDYUkIm?\nsooVk!4" CH8-Z#i?}ѧ!8z^>ٲ# y3!+7O$4EhڒRΓ 8.[w//ǔ+p&M0_?߻Y%$,ddB@t{2z \f?OCMCYHBEmgd C۳;w.q].]DoӔ!0k:ơmꮭr&0f 1Aڦqu $'$BrwDGa7Hp@F"tS*tCLEǔrVE$|qd jjLtOj`مaVE u]퀻;B5%b!  //.>CkP$'5+ѻ>i'&*BDZ@"9jabbFE)Ą:̡kd`"0bJ (;?Y_\6DU\ߺ|ᓻIDv Us r̥*"3A[Us8MSw~/sKY~S`F؇Pwm Dțˋ;o<r,g礉<4/h-$!"|؈ݭ8JD(Meon94Np}jfM[K@cAX JInY޻w;p@D@ un&lѣ>4| qm B*Bg71zIj*(mS͡Nn8MSBBfBгUVq u3Xl^%|괮ʘJ2}ysD PDx㱘GaAb$/ N>\u1ֱ5sSXJ?孛x$8GX jQI U: 1a&0w%gfdĘC->~y8=Oݝb F~ nfŀR:wrJeS+=#4uwH"+Q_^Zv,DhyXU5U,X5bjHqD~8fJyw_ܾ!]O7x1aVU@pFf4%e"Ta?JkwzQEWzoFw4iJZW1V9OJ*av^iΊmFݵ4e944JDs?f{<YL:┵7u8[?y8U>z8^owR*8sDlڶ7%" 0;Yy4Hh(2@VUdZԴ0#"kVU]nVc)Z>:}c׶ Zx! =!F`"D~kwgW (J%Owx8z-4m?nVXOb)p<}CJȔJq"aLBA\!0!"bps iII3~Ӓcרy 9 ޸GU0 pvw\ )i8ݡm:`%f(39oa%jӔnmILqP4z#ל]캦(CJ0$2s78_nfςt8 01`iZbDS3sע59sa>xnrղ#P'qx7m. j*A)ɁT ’4 2Ea }s~Kl"pÔvmc*PFD74wa6awR !988 !mEH^~[ʑ9ᆜJA$H@/nw/.9=8ĶX}'듍e̊CZ.mj''2qԊTџSh#+w 4~^,y!*TU0TkJqX7Ә8OZQDsvaF&hxnxiE Lb92+: ӘssG=盓C1˩\_oov[ AX')fXvc]O0CH9Rnc9 QYkhj%eD{Y9i2nNNSf[xBP3-ڄ*0if!c )njL=ܾڬ"Q8\nߺzq8V]qp,݊!n~v[n01vR1m`Y"ŪTGtâ&XKA& J,HZ 0 1nRfW6:J@'G(n˦Bxffz7Øb ׻qL:ױ* 8 m]/Gy_gc_794w-`qmk@Pz5])p xʘ&: fPZ)jxkgW>^,1ղ7~p"AjRS +\]jÏ?ΝMݨ٧Ϟ__]&6!D oNNyJUPu) rQ$:ME"XX8P$s/02 6QTbjQRmban~+q\BDА܁h.em6y[Jan^n5Zp5"s>)611<)S"c?Q F,!AKb/i*iʑ0{ H(! ;"1)!\w #z qs)f@C( GE]EW>~X8媪E$6!u izYݟg/+ /)UDwr`۵?/t8]Bڦ[5!rvpA UL9-srWuÈx]׆\@T 铏?V1>~ӳPE"~;i4$V+"^.*fGP uo_y_g`_{!v;S?4Ad߷]s[.Tꦘ5]}ŋ7뽣94.HDn.}\+2NaDDǴ?QM$0SU]NٗcB"ǒϟ޽[ā9=r?f.y~d\PV'[Da-Ƅ'U"A8\1FdZ+ 4STJɩ*rA[99=IxͮyݴuUGFbB5ϟ=w޺uBXi)m󽷾i$$]]U!)MCf>?=/vi29ǃTuuS)HZbDsUups3'&rӴu[k9.}ۥԌؗm'{U4iq\&&p`3"SD-%)z;z놘M LøU4aDܴ0NX-ݽQEDDbY{TXa bd-fmnw)1KTUT媙̃!b$\ڎPsQ%䀄P#Q 30#jj1jE?}⴩ qNZCdP})'WYuz}w<~_◾ㄼ}~z5jS 8d "wl6 |fU"Rh]׷ܩCT$zH)v4NE ڶ9 nb H9Tiw;wgn]2Kx1D U]?|g/~E@Md^rCӦiOΕĉ!xbNjS݊Ip 5'B̎r!mkć!<̌H B= \rcd& H&3 @@o^բ}["xT99Y4)T@DX>㶭%Dw益F.$RkwGvW5@FPgs7t.HDN4L"+.D7rgdNSq Ru@ c HJz>%EEM &iY'~j/~~O~?oWn)4miw*p"`~㡮0ax30%pq9v﮻WMXaunwLd-u*VMl8*̬~;;]߻Hbaxowb=x<>xA.eNA\6m몊Љ80 8N}Nin$U]Īn" bV̴;0 fSrQw7^5(8ǛWUeNVslDv1uEDABLW/>qx1 O44E>n09)dXyn;z`v])6# 5ρf$h>_a^-0͗rQ7$'*̦ 8`drDV-nݾo}w}Y ifVuuzq?W~ɓ_G}?;z)~5u45"a YB亮'+FzG=;[{pq|Oqc6E2FfF$ԋziJSM[]3&0\^V4 :v[t݃wliruM$;<,XJ9=95UYqcL4M9l1έڮtfҙHȌ&UlJ)3=(VB`VR4SI1lՒm;Iͭi9bqPrEF 8D8;2$>OЈb YNO~Ȧv+]׸nkJfBp9!F&@sSkn*ќ :997qM곧BLS@wb.6<45)#F@Y@耎Ђe,K.%RUxmlڮmnQ|+[=y̾O8W8ogTE\1MɄi^"@3o "$L)m 0ӟ7~ܬ^{?zպC]Ţ}?0"bab#p@Ps4awhYc% Gs)~XR]KƗ>G Y2!xum!Z!vn: i4墀HBNԄ(@(R.9Ӽ-F/Zr1՜8 c۱bwj @s(x. D"Z1s8 ^sD2s6C+ (lN66 ܉b5ƀf%esDzn"bf1 b1wbDʙ4aC h12Y Yf4i`r{Mtl\6??W~ɣcJ2=\{41)7ź33  3˫kta̟|"0V8FD64MiU]mV]Et7PE+AbzcnJс݌ Ց8{`-=#RvĆUvqbi*hjpӓv躜87bdPF/):rPfݜ#8H,2k"׫[9Y28߻wVmW j)!,eʉby$J19;[,þ/ÁB@D*AҘ݀$ҭq8^b uiJE*:IЬxfx'3g!c][uuo }n?+_EsMyʪmE@@4uE8TUSWw>o@!MOֱ: ̒IT0e}_J麡;LWR .fw]7|xzzcG79Vm~~Gr$º >8<3 1},jSw azr.X&ͥSeBUHTU56z_2 3$D+c*jT_^r> s!r8l.z|? p r "%GL3~9@P*i{>e&Rt&4c]WuhJ le{X5iO%'4b༪A}dh꺉c&hsDfDhQ66cJ q3 ;v>y)Tib_~;sTN|__\rV5QReٌ9b`hcnn9/ƜG~pqoX8'"Ḣ~n?),SYiKU ws!BUUUĜOcuXu#"UU=Sf],VU.Do@3W 0weCtjVbb\a&3<;!Y"1"v~&1;0srƱ?Ī&?~j;"ZivŢQiLC߭b!>15B#$n_|0b߾Psb9B0UD"jau1HM⣧gD>II5uT#gYDzQ Ch*0@ T m&5eJICC3F\.E[_ @M~woݣ}'7L䲊M^$-*XV-jM})Y-:}Ѫc &qJ~{xq]fj)'BC7vvYJγlEM S"3_wZէCU-;6CDUc!J?|24^ͬj\qy b-:wɱc?.vh d\-A ) DϬwRȎ!ݬmts ng0\ϟ*"e\֋GuHiԍ2Nh9$ iQ@Lhe)9Bp^ /OE iЏS)X4Ȑ& B:r{KO}EWйyS\R\JUrޱrw=1!!sR$ ;)!fBIY yd,SiY-_^}?xjh0 g~+wPߥ^5ErdD6uy"#qwU5Y02 󳺮T 204s#9b s!:ӳNN>Ur5 Ɣ мpn(4KMPr;TU1m_Ԣ+q>81AP SDDXǜCve-*_o?{?G@X);&"}С㦭:-ŕ$(22Ъ^_7}sUQ@P6CÙO)&/?UTlIXB'FPFTg f>|~Rm,vvStoI>mNNݾމh bU#<#sj׷Ϟ='qJj6~RZ,@]?z}yoBt! Dl*L 5dJcs;t/?CGqL_4OޱgvhPH.Ctǩs.3.f'.*NJ}@-LffsqԠPDf-䒪Z,/v@ME\1kr ˼ZM!$eĀdGhRՋ +2wm`@*>ۜM&c60 ι$~d1B2US>EKӶeرsy̓)(Lջ#9=_o~z;w ,hsg?_qP߉q~~ٳPT:ylBaNuTtO?h}xӳ ]~]@dnoqRQ"$&\]W;rs2,Ɉ|8{v?l0c4z ͅD;| o̴WmK&MɆI %у,0}.ꦮ|Umoj½~Cf>:>in٪ZΉ^b]sjj6朴l IDATmw^n|~Y, r)iq!pt 16˅t]Z-f)aiL!Bfn!qǡ[m?X,[C4Mcb>z)aQ8q1)6F؅4DHD< r߿~޴sαcS3(ERBfBHxA9aev]MY < +DE՛bں ڪ<v ͘IL!,"pd`蔊mbC?TU&dB& ΅vƟ|yjh]™>[DA|sN"<c$4Xc*⃘jH)?|Z/f;%uU,M>VUhs9eh`dy*77S>ɷ֫c Η,]6k_Rb~o7Mc?0y6C4Н\ *h@;+ob>x\.niXE4jRTi]u[=H"fb*3!y&@bV%4C$"E4.Xh2, T ?~,`b{E*HT.ɩk1sJ>"!@B}lѤluAq`v2!gPJL~,ٜ+j4¦`&j T3#v"Sjfް#)j|\Xrxۛqr쏎X׈NUi,ʎCG+-2)w<&\m]߁奪zgȺZKJY)1:WΑsȜ 謐LRߋ Lx6Տ{ץD=8E8nr#(@٤y,i.6Ri;Bt.߼ vJR\o9 =ĪKP  wXEY8xS)r{u5J ǺjMw~oVLq6-9K>~3'w{'/W(SqXWnd'@`*6岿;ibUa*R9wvvcAU a.z8aƹ3eCAEE唦iaqJqLCԴ={I?8y65;b@ĩ?1՗W7W_q DE94XUMhi]wPR!/^Y AE-VqZiJ&5QP$ d"FHPY 3qܼ"{o9$WL(@!\,,=*!"FDY% RUE,' QLAxr])ӔTYvw=Jhr"M|.4M?ZJ$Jyؼ&EU(zD0$[:T4'Y*PW<~ƟDt]ÏYv g3Ny/%ί \CSfc &޳g_IDͤjy.LdImM]MHt4GͧuAm&Ʃ*#1jQQ3EE PwsB-f*"ӧ}? 0Ms]׭7|c캫gW}l^_m;8;wzz6i0N0L=s`W{+eFf&4lP "E!"ʗ<K!Vú LA  vCvo>XOE&Ay5qrh@DT)1Hq%{1`DUe\R昇@!s eJ7ˣݮ\?}!~\Oc=siEfI&:?M< gϮLA_z ^,aW;pwHbZU[72XyV(@233Gۛ-#5uhڶDZ;nuvqQ:PPUJ5wʇDu^@ED&3UAq79q9vUBFî1!con`R?V]@n!ʵ듣v4,S^Uq"S)Hx*EUM).13c妶<8/\@H)^8=LQL c;/xђC!""D2 `i0Eft~2+<&\d̚Ɖ|{CQ@_ܻ8_ZZ[nooCXSpO㨐,Dƌ@@ MHݿW>,RTm=^f@L;\ΝfK_ +E75Iմ'DKET4 ̪m3iӶimc'e=fX׍ n^ 9UdhpnCly&ryITDqC7ƾ^X*:TE4`ާR:>Sϯq wERp!FAm;󇡟r-{&r<"{_  `_PכUY EwSwmͫPF@X x礨Hǎut#0\1RRQors*xD@`JoO.GPJܬRPbm_T*<B22"V!fQ uEnn_=zd";2 МG`jw{'5zVQqON0 놬 b1d\IQtuy4m4mC{vGGUs)8#mu dMsqqts{@lnZ4!b3PG4 v[7MP]4 Q32OqS 'M\*)~nu@Ԧi* GǛXr>?88{ŋCYHEBf9gs1LZL@C2oE{QM  zi9r]t1Ӝs.ŐU8f'>qB:);g4Scц,ޫBhyTǔX7nƪ"1,ʎsIf˛L!ЪɏX HhVAUrvɣ%fϯo?*ϴYyf܌r"K"oONy3~;R>=Io* s HH*X>2Mnm.0?wjO?oAѡ㱊t⤪1dEEQDMH4nÃ3^"S5G?ǩۦn>2:"R= y?T:;aGm??}'bU!α_-2`L={FdS'Ab6٫ ` Y{$އƇHMH*h=߻w6g/["o&IsL%;"͓p$,RD,hT-\_Lej~U1 uBjQR f0;tj^׻mՄ( xY~ŲP;! qG?W^3 9dh8/43*kS $@S{]/~7~W LK*bC۶b2 xw~nw8*,׷܃{'U mr8 VEnlbqyqHN rG~ش5޼~s?A~舽w2͠tB7bIGz:M4Iu(J9~{`>>=Ļ,\_Ͳl@KI2 /;C41c  iFLJwuxA "R?^D0xD!B^|TD'I~,a)vBtU¢G#1-%aڦEա]Rgώ")#(SSţ?󛛝ګ"@BD3#Ĺ* ?O?)~o~EϤ"R U. \`DB*0jE0}7 ø,vc$": a?^璉PUoo( 3$U3c$"RyLs6eòi0~첪#3Vw?tC׏uNعPUNOט"3JUᴪd(ZiS*f#vbԻ<<=niD!% rXTݼP^ m x;&D &5Դmd`4E'"8TT?"2) @!OŌ]ǒ'3ywp0iX ԡm#qD}`5Pb D- 3S$Q5NOؙj.eѴ6K}OO_\o|vjj`;-a&/}N H?[oa5<UrfSXJL!%9,Dj7[5rmN:,ۚ zF'U6ULE 喈PJ^[ <>^\~[7s7Dďc5D0{slV޻G}-ϗ+_.W !qN#Y& W pۧUtPcB£rZ|9_յ*ANwaץq*|Q0,: #Dѳ .xkH'Nͅ ʡ+k=}~ |U4+bUĹ4љ#sJ6FhR4ki!}*X,+i(VXȄ͕` E?ND WT @g ysXA y㠫uDo:s#9B2bxx/.ܿ0$ኬ>z0B [_ WZ#8lwċrrZ0Cd.M&ΐ C441w.^{8A?W?)|<+7K^5BACs<^ r;/_W<HEbCߓHL#{ B|8P{BD54޽{WWWQ\XNenw{v9g3U$D,!p]ǫӧv[m~#!M`cϞ0I_yqE0$C 4x( !#1neB!V5A"{ջDfwc020QH>2/<ج*笄R ̪6{Y3LDM 晒ΰ.pDBLs);2Y,E4C6%.goG>mVl&veיjާD&UT:DJ#ˮQ9?HRjTKnĞLvIf">n܇$kPA" 8][_ַ*:i_)a|ȢJ 0*jE`C 3<(3 -7s630UK\y'`LG<1r.7DϿpUT'wD9%zqv1=CMTi9ЧuC\rv<$jtq]T=|sU]hjc`cpUUE]* 맙d‡f\\\ֳ; tbQؙA^7N]z.˲,98o*pPgRmnP3>BbSK1d}.U)|rIfI6<攳bL<,E XĢ6v.$13Ua$vS5$0ˢl H̊]`&D6PNjwr3wn IIٷy2, ӛw>{!tft]ɬﻡ&(u.I el/Y$XUG3YI DMrN@Hx\MkӳX[nw/`c0mk[aE]@b@D篑zO8|)xqv IDATD|>k6bDg秧<;UeOK﫢0$0"p||4`owX0 &6nވJ.;cRCAaKw# !uM{q~YT֍»,nORJGLĀ>9麾ͦ-˲,K\(63QRMjV{b B6v!# j*Ąfz*׫ !V#H@Q۶;;;7n|Xry4"! 0#ks.zSR5ьL'nDD9\M֍cS3@MȕNR^}_^ !LĬ /91w6CBGD4l6Su1s(/'O޻xT!fMDՇ0t}v}Qł"H% DڮX8Ȟ!8fƽD`#*AIrJe(9;Cvs=::|vz>)=er(8LV0V)я}Y]"y捬@IDB YlT@0+I|Pr.Pگs٬>QU\)ϒaR0r~d4*,,{kafgt'!kzM!ԓʗ%24<|\aJS0}/|>U$I %yzefӞC;SK)Kɓ,W,FSL"ʈfZ壇tvgY2s5 4eU$٬e LEC D Z9Tu1iUHd!Ɓ39/f(!9dfL@LD9%S[.]\Q3"夈sQLL@]Ģc :#} II IA АMP!RٌXdf=?㰺X+( 9KvLWmrjjUU>~w[//pmܖ# iQ;"/|*|:~/eT w^^.v<~ż*Дˊ|@rzZG,1E=ϚKC'sBpj&*˺(»mMus$bD,BgYS;:8CFMvw}޻jz|>grRtgZq]'킃jZR ˀH,ԩ9V 21=_Zc+Sc2CMw1YVɽ{rŽLLr*y}p@jdAh}h&bJsB"Bq\2i@]6h>y :dх&R}Y/~wu?dn!ps$%ǰVQ2!(,:.6{Cغ|DSӥw\rqKlFe83¨ئ]Q?'vN8j\E?]o_ϑA-%M/){f]YgdIy[ַ}~qD lV)W_e}=>?nwo``4Ms헎ʲ$"LٳIdoGR? jR24*jzVwLԸ*桮+2D#ʠ}׶/9g|=3Pݛ,Ma//7jF4@i>䓶.mEUeѷIjOv#0FDj X Ё zcR-g1b2mJɅ"8D"=D6 R"BSC3- 3:P3i\Qz֗%)! Hl #QA303(d%F07@@@Q}12 ]0tL֮l?HBNLWm3[,??S :.J = g;U]>~}J0%!n{1mgבwz1"v/qכo|o,r{pP`BVav, CcRѲ(B, $ ,b`EQ!fD!,tZU[o(ͦ="$9gB1[̻)˦TEm'mmvwv E('u߬˂eY%ˈpE`4AVA6DB CL`ط1r'0zDEB zuىd>ԟ|qQٴ, B3A zĹB:B} <ؘc2$2&dh6 dSa/ S7e| ${*P:B&h֠r'Ͼ|4S5wcPuo/iW;/XK>cEԈs O7g_$&ԤLG49?=jR1QN23OgP@eQF9zz,' Ϋ(4fӶeY8&[6uN'DrF'5mCvuUeI]Bˊ6J8!fC$$3Hf9ZYrV0GrJ(cwJ< er1!6ȤY wE3(n5D7M?<<~=b>I14sDL!f̕>JA@*""h#$a4a27HC@̗% e5H&Oj/< usBw߾h+T 8"(a`vJ[^ll+"~ oܽ5^oآ+{S|wEitD԰*qDlbHY֓ }?DXpD3 ދh5f}x衈nf25L,+B2;䨪~a}ۦz\ 0h( W/y@@o+b$`0!9B9Ǩ)F۶4,yz~~.q|oONpU-ⶳJ]F#ɰ mB^ˢ ޽o^[;5B݃C\(|`4jg\WrsΉ~"b9س@CLcN(g!"D)8" Gx睜sw!1n]Lj};:9yi?ľ &u(slrRFu]0!9HeY$f.rfbL,(uap޹uLDV}[!,ƌh`Y`%C$@-gAm*.%M'|jR](jASfKjj}6QIS&>{&D9j:̒$+I= EA*IL$;&TCC"7n<_&ѡJ~dXݭX ^kw#۶$u׮avm/E9\p1e"`Y)kɤtȹ-YiXg3%1Z5g纾Ɩd- u]~?jmۋ{o~}wp'ώw_Nu{׬7OtC\ϧfVU,cr䠏źPpESFB2k=3@v"j>gF!@ĨCpvgX;&b,9!133 z3lfcw%!̣Hc$7u/ݾsrhu;w) a ob !g+bșI 5 arLDm06նɄs1dc >0boQ8wo~?4/,>zqUa-x F x_B2&¯!/vݻw|/|/}y' rg'011]}q֍nyzrܰ',zv],=PbX,F19[o'BdCu ̤̎z˪=;}YA슂'awÐT`@M&ӲssȆs@#ULj RYĀv o@(q?U&UUpx'd bvkg%aQ8Zw=;޽~]Ã=Dxw^gjFA{ٳ"gd#28ZL*QbYC3"C͆`麬VɴoU!lV~5rʀ8 Ȯb`@/٭txK?U0dB|ou;1?[_}pf˅d>iכ6Mrݒ\̈h:̦R% 8kqYz0mVUs㱛y岬...H$;"TX#bu/␘}OX1{_mw 2UIUnā2!yv*` eq&#`{l| Iwv,sYvBF"[ԬpTy6d"1ŪΎoE%y?M^z΃O>K̾(UkYbHt[ sY*r(g M7Mi5id&eQ/gO~m%#91i;pիnxAe/Wu$F0}7xvwtޙ0Y2J]5M^Q'OΞ?g B(Ȟl6S9`Σc״ 2y\pDsZ//WwD2jFQ1hJ)뇘n}֓_=v+g`&LQyywgCf4v sfً*993P䝽>.8窺 f}wn4lֱYǴϞ޾}w~uѝ yf1fp`j"U5e (nw˪뇘sQVffG^^^gݽhvPmUD{6]J9$IbL{b>_^? ]7t0'eUSYڢ IDATi6O>]Pi8 ٬( Q1Q&[筂$+Tbmb1%t.Y6.@{Iy"f bB 4c5CJETǏ8GnB4z5tÏ>NA]8u켇!#q;bL)+h9< b,Q 8$irZ?巾}DF@bw4!~-Wbvw-OYywoẀKB165M&%2ǘ0&^,"rL|Yb N <ӣãorʢNdA]9&30Ęt:Φ?[1۶jXaD䳟}ii<<98_'d2=}QR**,ˢ¦kS C_,:aӄPMg۲]2$MUKL:0ΔIRn6z.S|pZe[u]-nݸɓ[7o<gS8_*y@u=ُT5nɳ1 ӯj#6}ecxD^{wZ:v ~ #¬^E2<\7(gƲ*˅s,^$9{UE9tzVӮR$@nj1SCL|:x/v}UQs@]}Q3pt+ffrٴ,fFFQoDY%ғFI = } &h:aYռ yE$9DY5aRb9Kp>4ޓOfQ..vq>}~0GO_ScbRk>j) sNE($ˆf&~'[c!G ZĖq\iݷpxk%qϵωrQԇj+|F(ƆfәdLt,EL qu! yfbDx nݼ54}IU 1 Ezb>f>{nͦ >gs@(JPsSΐh>_hvb;C(JTSΚ9XrMh2AAm FRUsd.s_*00+X"Z@/7P*KC7_6f}P|6enQ5 E9.?59OCTjRݸy=xϖlEY0/Bb|p` hy椒sHeY. mSPe" l舋*P9ziH{}pE7MSMQ=LbSef~7oWK>|*}TR61L9rs~>Y]~;޻`lP5DuD=|W]BbfbƭJݒh׸hk~_{kؽ|7k_oDd:xfl>+ug1eދ6 (U#@D$٬SG7u10310 Ci6M[|wfݴ}$(}QPfaH.8"0}fwy~ EY.rUVzyQy\`&$||vrrxx4[LYٷ=y*4qH.YiU>;ݬgE B F־'H|`dxL`#R$e`@Z"b R?郎|p]o?~[Ga2U@ pM}F`4޹}Cr~dMS3ѣ>M-WtCwkgϼzppfe v=*1ޤE,D" OѳX11fnэ,%wޱ@u]:X.r~A6MӋ޾!RVb6Ӿk'әcJY4|gӮ~;v f?OW3;əP gcI%#u`"s\0&V]̪J#r VBeRCr;dž# 2`532Qd{ǫb:{fu}8LU@z*.W?z? UObΧ1r,|4*x^=axE5p=Ͽ߾Ϻ.Փ)*2}^?{\PEͺQĎiwow:0ni>1Z6+{ GA0NoTubL)dƮ 6Q$`uIC߯ fl:YV=~fow(\Qհ t}U]_z2CUYoY_Ӿ~ώn>>BBl̚m}bΕjCsPKȰdc2QC|6CDP42sd`=gQL[c,2>O7RY VUQ۴o?+4MWێqfrɮkvW?#"ܽ{/\\s/~aHTUYY sኲ(\EQ0zդ=!rYU Èy,v JCp"tb>o6$*yDTxvebv0N'UuqqIm;tm_% 8Gb˵"|QP,sjmd:g?+8;N'tg?}k>&aw?zoOjw1D 8TMN{u1+y6NPTR;ɍN0D@cgn M 3i6b &&Q@Ӭ6|u~||뻋G7)F3UQ|UCc G^h`/w "fbv[VxU"t*l|ow1z?6?ɏ_$9ÝoF1#眓,f)751N5QTԘ}p=7k&>$`"b;S1'Y$dRӮ4 m|Lj{MӵmWWu]WHf.xvlj0DS{ʪ >Y?>GrBUޛؚ^akw=x̧GDRD<Ȃo `_ Ħ#!lR!ACA+D1g5{8cOgy޵wX+_Uu[ K2Io{?>"ښ ^eYley{KHJ$W:K4p Lw;)ɱl'i ց Q SpB$R!x`$EH(1j"D]rM}aᅬDX~7'm{=xRDDJEڅvco޼\acN?~Cbl\rqJugYMMQ(E $E13"T#[kDc$uS"Nb(," vMpPVk^WEyͷ.,<)bikzPEa8f hqaiyQky^nlmpȑja2\X]vi%n7yt3 j D4h"OP6M"E5Et/B e8FX)R !$V0K CAAs((6!IWWWV恳J""<Ӗ2i<6ǜ|syMN٬Q"d4WUUuM$҇Ⲭ4 hu RT7!JtʥߛM޻YVt^_wpxW׳b4dkk["yds"Pjt2VtPMlW&xLHMAP(Z),Ak@B$1FBv"*CZ@E+{;W\.gob6tEPO#§^ ӽ|Xcp&|/ϭcs?vJŅ^|$f6,ID)]*xBDYZ\vNJ(r綠4QQWZ6r2lloIQtY,..z@!R/D+8 񮚕uSuN%"~]7Udi6 :1Q@ Z@&cBe.g֟<}:}tZU.]JlkD@dDj\spiLV _zoom^ F!Fif(v2Dc@F #:,RrZC@cDeUC<(,6Jơ"L"Fh$$0DWgYK/|gg9g@!*6Ӂ'4Fvmͼ|4O\bW/q5cݸ٬$ݮҺiGK+66'3$\,!"c4" BJ)cy7>>J!s 8bd@\\YճYbN'zӧYYWֶ#a(맄I$;{{M NtkDÝ= ++^;;^a%@@T I{VK˝7o/]_Y[( afF_yh\<C 3!­[9Go|.^\E3F26/.ΪgrVt;90mha4Ҕ$6N/CB8NȋcTm c¢vu:PUU]Izs쟬,.z׌- JTUeyfY }H" ]YF{Z+f$wQ(""tVS[l?j_˜QPbSsΪu>& Q:ssoo7uՐV YM1:vmneLZFŪ,뺉6 IRZj*rDj1Қ#=ֻ9꺞NJŅn{txQWdVҥKhD[}X Bd:QV.dIVNN")(^'wwww-,6UcX ޾wx+֚GOc<7J"ǏF˝'w"_ՊJ Gǡ=!Fʄ?" bX%n;H)j1xf+Mӻ뛻g6“O =g=gaD_?vS;qqe!r> DAHWU9JQd*EDQ I!!`DUkKO[C@JMEꪚU "?>:ڮfVeY-&MR\8 WV'9/Π,tv2VYW^}qfZ+kl27{y,/_^tAjHi1fk7lw{Wf^|7<|v5f | H#d@"DÓg +E |b;G#S?m@<˔F_7׷Q>k>RoU é|8-l3v瘓߶o|[_ί,-c$!pu-Zpx<;Ϭx|2QF$-R#jOBQ$ SDbAYb Hq1pYU/.,tum+Ji"Y1(Qa4l82 Gm{BZ 5b`"M(O{nmnk*-˩l{BOS/wvjh6B|˷nέcso~uxkmbz (OO%5wpч{Qs2!(RB[M kBAM ZDU ȇ#7FaλOaٴ̲_|;77 /<I Cm4EblYEb{p;8{易߱Gߪ+'%&}fDDrN& hqi:6O>vʵW5!$&Y" @R"v &DjQ)quU6"f549f6EnE'{N 4UY }c|ZrV$Z$tzDȑjCZE>@YVdZuexa8 YYK=: t2̒4AB N p2 ݫ׮?{/ͬIӵ kU<{i9*j{oH pH f*1V™&[ !DTQg-罏m nZ?NHVsYisCy?emvDtLBf%W,\~MD1>|TWMb pV @ pj\m_; Rch|ӸȜid7ӦN-(֟OVMd4m"Mgn'ISk1f29I9̩NJ)|Ux/YW|9%rimVV F IDATQ12Y^t4M6ッ~i #!eUNcQJͪ2I##B!/,}[wNƓSm uǾD;yqFoz_{\reNs[oUpq!KYUdRm"R$8Df_F n1"+V?xM4s]vռ?ĺr}y'Oꪞf8(tnYI~$siQ(х4MRXt2C IaUtg+KˆT6F30Ϧµ;{KK6;yY6$Y>ڹtr) wޱ|R(UѐRr2)h@,rNC1FKmlVͭݶ켿c+7;ɰJƒWs?7}INN&*$A Ḯz8tdos{GGvw=}*_z~&ҝ^ ֆASygiaq*thT `l )s"@sy7>=-i,'?1̎ggj'n 1͛Ls(0ը iMFi1̚Ah1J sM1җ> /7y:痖@d}}saaFbcQX!)AE?:{^;++CӔMUYei'Fu8pj] 7Wj˦9TJhmf2ff||,H։0B*҄/3lol࣠s^u&''Ag'SRN$Z^|{'λw"ϲX7j5$0)yΉ6h}(p9A͹cw/87,/\VPADA@R7[X)eh%E Z+ki\S {`+wN&?({CDDFX|G4KFú!rVumw{8>>l|SV,//+L) Mg.ZW/hcI- Ƥ`Uu9TJ,p!2HD{GJҊBadI1VI}wvʲY.,Ba\d8?{JH~9OBI}hjgFkMƒRF$P*Veifm2Lo{f;=YDή@=3tƐ0nc;?b.1|_>..c!D$H8B=,--mm*Kц8pXkF;٥K z6F/.ʲl,Ϗ7suZ\X DcS*ɴ+w\$+tiǓq։avuYU;k5j%AGUnnlkWK rt4N2.pK O?/=܍D!hDBS r2_x>Od@deu1I39>Yfu$"mV @+cRjԨ~ioM{G$ g6*wvߺu^+ s^!&Y}S#@4"7.-.i}g>tqXskL9 >̦t)$I jI;;{Ϟ_zŵCDA_MXt:ehy~gkyWʹ,F IjkME;֡<:9VA' \N4K \ƹ !*$4ȑ;Bemɓ'ڵ( 0Woz'Ihj ENGxVUѥR:MsmHD|JQA=Oczo=`iYY6mYPw4fԚa gt|g2FEe6ϕIe '1"RJi(³X䣇.6,FɬJ>}u^2+BGJ[k5"N._4H.xT>zjho`cs#z/_Z{o|[Oʺ587rnk_ s>~|Y^[QdVHDZ23YEѨ}1٬ ;yɓRjolwm1 6676Wo\פԽ{7uSuS7J?g"e9n/ϳSyv4uf*Mp`$I ;43`c2?:f^<)̓%h4"*EJ@nwy0zwKKGGhsWjQuU}UdIj{pp|r/b,zxeއ1FlbVe;v6vڭ/G~VΨ\^h߶r[nWsgFʲVf2ij6FF9F.y:E`*go-t{""=66|o|+cK/X{wa#U"˳ɣǮquՔU`8PF0w͑i4J LO4:y?ΦQ7{"j #.80D;M'al\ qq8}y6.XsqGMWVW4/kwp<>>6+zi\ȓO}w߽(k9DAN;To^{/W?}?S$xQSܸ~ PA{l6ue\+eu€AE 6> zii)~YYI&F4f FV(4suK ׯ\iᴪYP$K/tswl?t^|9PRk:"S߾׆Z…/+÷vN= 'P|v g o:FU9@$ M\DB,9u*:..*HiE V#!=zxkkREH,_z׶(Ɛx<>><뺬]bpDiim eT`$k@+@ &{VJb"GA5N2'e;d)F+aQ@t:Nogg7M_dSÑ GW^x!hRVku[.|<>:"wj4?܂vBD._\G_iYwTjh tδW=ſ;_W<ǜ|6cbaa ޟ}I+&80isi4M͓ ID,Z+ԇ>\_<>:LϞ_FZݸ~@"EI+AqꅕdbM꺮ꆙ"2ij4VSIYt FyRd) .MnAZ;Ճ&Hj6XxgwN)bDQVW/ƽ;59ool婑(tpC}$1~ѣ޼/m\Z[?B]hٜ/hgSKsszG?0I;imZhhfť%1pSجHQNҬYksi!6Os~MB<>>aa$zsݸqZi ɴ\7DrMYUUU݄+DY|HwE-7!JpU v sZ\^ %I:1aqy4M2("&̀ɲk+᤮;`uEdRE\x;uam 7M㘐%Wkٴz~X)ED,"W.]?A&$> @ؕ%mX)PD뛈tesNRD,C::8@ƻ鴼t2"ԂncX$:d5C60DV5hIZ{RRJyfҪL4!A1ȌJ)IYYV3KiZ=t~HӀ@9-ƸtaqZ 1inRA(ehO(!$I#s[%Yܻ0>6_^^EJ)2Jd~tN/gh!s$K$)DdV 90Ȍh@\i:L Ofe- "8L^wtr|8JU&M$-(A9"#06J E,˯]gBdTyc,rau/|q?y{gwZ,'|g;>goW9ϕ[ JWudi|@D2"13 x覤IJӼ/c$D1f?=XX9lͳ4 ]JkBZ037?^W JYV 4KMjM&(rcD bn,"#Q9VQ'+ˋEjƳMCY5KK!$ PAP!Ǽ1ACEi/<>ؗI"FRs}i8Nmi}RzyK_jdww:vVpvߺu+sa91t;iLgj|ᱩD;ܬ0K$!@(yA?Ӟc+BIR7'taqim!p} !ċ֒hQ,CgP$K5&M tY@tW"EQ)fЄ4%feQ`Z>DlVF9ODn5.,, ɓ R-~r­/~wv=}l{?}yncN?x@ൕũ` na44ưV*clĦ)!BVd:5@k碤ܾ}W]ܧo@AWվ AoHߩ A4O*YbVJ$J'RJgfQ"ԈDIƳRO"z6KlPioTTh !("!` ,=]I])55iH^(wznB<On.6ྲྀ铍ǏyXtFe5:K""(( -Q&@:s-yk׾-/G;ۻD}=֚CNm3ܼysnccN ׿]y9!G~%Ib$T"|&D'E3RKPW[UT~3~(/Ҫ4'N3 GAzyIEJDig*ϲ%iG|ktV$b3m$ɳ$ѨHb;ZC:J.ccޅ)nH^^\L҄9"Ƈ7m|"ϲ4A$mDʊl8h:D@H&EM J4xòʌ$Q$6r5YZ7ac{+s:I (2 ABcȬ V*c ^Hm<, t$HibV~wǫ?" 3CRk}cg?_9J1/(~4 Ic#zp} Abm-$$1@ I4IѮ MոwYs?&pDDmïR{׸͢H,=e^"nK *E1"( Jᴪus;=7.,/náNB=֠6"@,̖ac DQdC\4 IDATJ4;y#KuT{;[߭W*R[4FH5[n:1/׿fdJZYY&I!U]Yk/ݼ95c_p7XZUbtlAťeBbn Mct2$&ljb=iHMMXc]2M3k'{dU ~᥎$F2"i[^ a]b]WmhFBkl@?^ LvYkQ&hɡt~/{-(t͛3w9Y<ϋ,I/E)-ZkL4yR(R)QJ"EJ)A ˊ( [!u&֕ZeA IaY/0RezH P.QyJ :w+zIة%ZyOxExq00m;|cٴi4 ~YA͛6iXWH!Y^؜6Nk^~[wNvPAɲ< lzY_ZFjE!0}tRj"aBH(86Hy< d&YZ:~I-N@GTHB!PVL+<"Rd9k(Y<| ld.]hub}$^\~پq>:K?c[nzEȁ~ӦMQ~y$-raqaQIBE!4F;k\HmǶW*qvIIP\R'Vꑓ##RJQnsf( C-* cL 4YK2dqgYj$u8D{`BTY%Qiʳ(Pk'@Z'$rjnxrty9˻9lxy-{y n7\2G7~}۶s6/+#CQ{"(<2D(G\aW B="YQ"Viu[VnHt6O^;mS))e%Iz詣j%4J)-Z R(%\#&֮y;gRJKr1:*)B$BcZI"t&='$ȑs^ V\;}fr~~7~}!;[,߼o9v{ɇo;f.ʢ('F#B"lV+X%PkhR(6'BKd~趭[GFFzY[mY7/F7yDpD>I'8\QL^ZYJ+XEs%縷(4yW\RxO=iSFJ^yr; 4 JgO..W2s嘛olg_12"pwwkO#ӓ2s@*R$͌Q O\m[Wl(Fc@)\['Zz9- ff.nٹu##FŤ%#FLK!֞6pGF+%I )FB1J&ȑޅ0*bURJ^ {O wNH鉼$K j=vvai@z{\F+pm:.c??ᑤ:%B`tYnn'@yCVHZ,;'uaJKS3[FG(Jp{Y " :rXAq=VRzR+k\akFQ%. diZL˱5$)D(B8"B)EAH "hCԴ\I{\XZW<&;$ `}Ulq׿jse9 ``@-%]OVkmxtVfYÛ#uc LyfaV$zփ3s6BkӨ& <B"cN'LhRzń (ޯO10&J3IZ\˫aTj( R $"(A]yZ_F?I tDjijӐ6 gc1|#CQ>fpp s)dYY<˲leuTkZ8rT歛^^Yzz="4Jj"DMFzN.J Σ@%* ٹNE: RjrYC܌bFj//wڢRGGVVVAV$uD#:Rr(c5/(uO: |VNfp"!m066>66cC_2D׆yyG,KӴFF=^yuWWkGRxkݨUk͛76M5Z(sE];+HܸEWbՁ84K<Q-:ڦCCC[BdQ` (B!ɓSUGR~,l^*Ri%+v8 㸾fZ%:$iT^sK CY:k/#{ܨy,uc*?/]dyVs`aA} Yw^oppw&0++ͩYc :uElazy̥%)R yNDfR/lB(Sӓ.DqF &4&Ye/+)0VZd]\\$P 4XJ5ڼu J]^U$G9}-5r\w\Awa2۸eߪJRJ<~ѡᑡ JadM# {BA&LBi B DA"Xdvqe@@@$R )Vv J@i#@ Hud{%6pEEnV)ͭb3SB-n~' *[n4wVaB)!QGXYnoa/ #y@(~{qwp2ƕW~mSg6mfLl6,ϟxGԇ0Z @Q "()ŋ zQ4zns"kH{h7n;RаTZkD@I>jlP!A ﬉ QyC`Rq޶-[հ_\Zl(X(f/KHT~nv]۪a;~vqqbؽ|?#/+GC%9v6\ b-RM)8o9I!vZZ- cdQKNk}B#ॹykVrΤ׫ 11G) PO=y(MStGRXWh#sG:jDJQzv{ѨimYF;Uj"*qUt{OO/.o,lߘ>i6Gw'~÷ev셙nO mI/߻O=ui+F\ E:k-EQ{pG{7iuW_/-VZ[k6UJD<RJ" y'4 B!P )1 DQ ' ЀV{iQZRشiyLj~{zgߕʩ\Y~."7~''/\ܾ}ɓSǟ9w+ˍFn:yr`nή]TqOz\KK+Ew;pÍ-;j|V//-w]|( |QW9A(!0~DB-( ijB-AX RU$L$_w_SiɧY:2:T8l6 /4rQq> y(7Xυoo~]8|W<,y_{|x'w7~ߦ[z)!?G`,yڤs>T䪢i; '=v(q4b̀e/A Qt܅Z5 C#AJ9R:EZO^ !Z,--]۲m z=O44<<95Fq&Ir詣`/wRsb }cpw+"?qo۶=#k)Q*)<,XZ^yn4I3$ȹn~S᧵VR0 Qj:)%f+qTFD!1RRJ)E(UE˫I8VJZ `+ˋK>͒J۷o;ce9Oy (< ,'3 [+|{8y9/~3Lݭ֣^2"֚ ZmΝ)uvme QW~~W~\qaRs'B AQD f8@)E$Qk%6Ƕ~jWVREV,$Ԫ#DAFa:r}{`v}>wiMo~=2ya*Vv4]iei5/챣GO:EDz{ht[FUW_s|l'~?9/IfY188Z gJDZTSSjǁڑQ u9J;kֹ(8NzVkBH?@46oEAGi{B~nBd@D!׻ &cK^t|??++_ǯfiwvأ/?tCj\s퓏>?xtZv~7Sg/LMN"o7|š زeˡo<28@HqA#DBJ<3F"TJ+79!DeJ9o24aO~fuer03h}cw{}cl|cm=yϋ&y?u:uFh̊lhhxgݻwTj](C//~8;;mۈ|ޘTY]J%e -,ˌQRHayk(uooG^ir5/.g>K y޵#G'2P~[~f>w[r=裿w@:ܱG$1#G: M6'a~R[Oژjꉞ}Ǐ5[1}ڇ{*8>1q=}}Ľ0=3gLMpg>O+اs^Co{u7\ORř驫^Y yLZj+w(Qi?saقrCo\2^=u䅩-xə׾ub[փ77xۛKR$PF+J@ʊL}eQ:N=59Zk_z[.# oB|Q\NSN9v䉓'g|)FGAHy*ݻj gN~<"Vun?'Q%vgO_]]ю\ Xq9D;9sc/?ol_[ny{?ǩӧ> N߼yu7\Գ7]g9Wj&($9wZs '?OՀ<"; ]allO8|ݻ/>7x^<10KLJqqz7ꫯkkYٓ֏+^vyı ]؋=|g.رmۮk۲e];ACOnx5~ֹxq^A@[_^wp1-x}sͳmoW>}kݳwO4/ y^`(?:Go}W=w/Ͽ쪗 >11~lQ\u_驵VJ0\r#Hɺ{:ֽ>o<޻7?CC?{lR HwϞ| naa;o'&&&nOc[v뭷=zthxp+w_y?wo}^{؄>$:N\wzi~>ѣG_7Fq|=_ٵf'>@s]…uNKk)Okx{ۯD*;[8vcv}s/}]xqn˶ s ?񖷼Gs`qwm+Fok1ƕs~< ">{z󱉞xɯ|+i`q~gfyi~hdġzCGVo9`=K'ⓟ_ͯy~3^$Nhm~w9w={#{AQEKPEQ(DmQEQ(DmQEڢ(DmQEڢ(DmQEڢ(EQEڢ(EQ%j(EQ%j(EQ%j(JEQ%j(JEQ-(JEQ-(JEQ-(Q[EQ-(Q[EQ((Q[EQ((Q[EQ(DmQEQ(DmQEڢ(DmQEڢ(DmQEڢ(EQEڢ(EQ%j(EQ%j(EQ%j(JEQ%j(JEQ-(JEQ-(JEQ-(Q[EQ-(Q[EQ((Q[EQ((Q[EQ(DmQEQ(DmQEڢ(DmQEڢ(DmQEڢ(EQEڢ(EQ%j(EQ%j(EQ%j(JEQ%j(\r%(??>Л.{}tyM1W(<_g1 |;K/-Q[?^|k7{ 9L18_.A~~wU^EQW׾r_}N$IR@d&@fjdMۀg?_,w${%j[󣿑$!"#" jQ3Q@DrLjd]M;ÿ/RP׏_ЯHfo4B&<:7F0b'?^^̲-|?g Bfܬ{_{{˺BI1vQR$G{"20ӈx~W_z2ʖnڢf_G~#gVM!0E"jͺkik@H)40f &IT5(]3suf֚$yw~}߿5/Q[\>{ߊt{brCiF諾|`GDfo@b*jj~X澮s!(w.?pR[(]}>+W7Yjoo~if2@ !US7Mvm*g$B ! ~s@DRi M睯{{#!"):U#%j.g_??H1k󪪐pʋD18leݴ̌jB^mR{{k(W6ty3`B"D3KPM]Օig-?]O~G4#fl4m[yBlgϞ:s "T뺒Ʃqlə* WCuĀYPR21@B`<})Q[ O}SW^D@^ff10i`&v /-;=][j@䈝UA CT0R"nMSvCv̔@D&Dnv/ߥ=*Q[r^}E۶HPj`J Zj Ã= ijqնK  WݬaE׿">f_ff >n 'r[lT$c*bf-NS1RUyWλ8~e+益nƳժgn޾]9$!#U]3v -Q['~=W"x3&5&f `ffD0ӴYCD5U3TUD$B"D0){LLDDDM]3;SBIWl>?88XXMЌ:aG V9|2(Q[O=" <'|1S՜αcdq@4QQKIڃIG;# hR9O)ƔD @uw|׷!|T}<"5S ᅼe&f&fBDD_l>ۮ%&H4m4b1IrWWS(^_~;s'| xLDlfO>n L !3c"PPUHNڹA:P3SDĪf@@f3B`1@ST#3 B.;8}HD!!$zǥ}S /^$IblLLHHTM5AJg0Qb9}lQ"1!fT ("`":ꘒ<DmM'{xqD>DO0SMIE([3Ɠߊ!C&DĈ@DHHLF &b*#33T@4`Lt8g.

    }rPyeD2aEFF`BOFO #~BR311UQH 8!)1BRQ,ĸ47;wsO CB4 @`R|2l/aM4Dl3?BحnyuCw~{Brhۙcϛa;>Xkl:1I*>_.b֯}/3<1m]Ϛ!MATA,2 40|ӸS[Y2%Esyn1Q$7lAD& o0P\`J̀s^9=v1)%ILLJTC$fbpfQe{}cةoW_78}궺u^x _2"s"~ Qߘ٢]fMowq;)(q8Ɛ{gO/%joD?Q=1sUA4А!i"yʘ|U]LH=Cݭ"Ѯ@͒f(*H" `BDAAEbq1* Vwn枋{!(!(&bIbfخ\ s.x$9/×;h`Dvتh!Rpz㥋i_ڬ0ũiUuj!rU-z q])QhCCC)vm=]W+gsȞ#Qf*ǻ%. (D AĀ8%3󞮽za)i߾M?8uEB1& o~; ہw1ƦmY@dSH>ŔDPȐ٩Y>e뺛LECqoַ]~׆a+޺0 14[ڮ1yDm Я~$b=1ʑT())1S$BzPS9@D9'@-+I'T PS13P5Q5U#Dtdya̵Hi@AY ޸~\B"hf0]|rWfk׬Va;!9Ljsn{?[͗sv9$ X fUAC0 y41Rb=38굛?|z~;ԝ_.vZ~cRg4Q,=!c&BʣcB $S c)E "1y-jY2r9b#BDF3UɏN&1aL4 WU۵}dS3H$J!ƨ1J&jnݼvc؎bޚ2VL)ɢ9."yʅ]W>g`"N<&KQF$d$dR? ##vulwp`j  "Ixm"dZNӔ`b13W>wyUM Mwxw̟ Oҝ[ lɦ ,]{b9>wqSt-frOBLSL1H SR-O/aHAa{9S !!4,/f{ ]mtڿ{[.ſ4G~KW 1I1]n T줿 ڼ٠d)B1 ؁;WrDyb @X,A)LDɑ#ign֠ ']W= o~Cojj)v7 ٳ/k&"I&543#GDdy`mR1U4DFȕ?/:rt嗉 Cĺi;<8`MSuHDIbɥC!$db칙5hs>y>E%a;#fB`sxsDoKw\H{OdYޖ-l]}/}4FI2Ŕ,?E{Ԉ+ 10e~l@M] M43sLAJRʏs9'"cJ@ c?mbן]9VP:@7mo>u按R^0MS 1Tm{r @%b1򔒈)'urDܮAC p|Zo&8Ͼ|tt|LΧT$I42rJqbC ̚n0NpU1)lֵm"Y+JHaXpG3d[3 }z)iJ)壬10pc 4E]!mǺE T>w $Ę$b\+ODcf*")"V)i~f(il8I gǎxԹ3oZ=@ $3 1amhJQEq8^ykp&"vGHCN!E6l)&])>HE }3\t؈*%Ɛ 35bb睩Lbw L5@d4lraxP7bsu} vj1"3DbYλn;7_UyqkfX,`),$)EaK~!TMU 01vQm 3'U0`U1)M)jԐ`DtnVk"q4h q_oz !\|44jLѱCL.+/>G쑟xߣdy;-W|ⳟx jWO=qnfmQ,&'Xma3@?@J9]pzf5&)02s0hoJRcJ&CrsC\+BDUUQS]PSln!jjPU#fB`_^,{Kd- U1504RdfbWwv:i^=9X.^׶H`1 Ll'EkydHm8~8}ԍ7^|Ūjr\@CB 5djuW1Bؤ4"nGFHBu9;kh* džbjs::ܧ%tss>n>E1m]Ug4I+"u U T51 r<0Q!ۘuryWtw IPq\ s~ cΝ;U.߬C?MU5zVfݢ DqJ"f)2"Y"a@|o޼Rb ,\hfR%jC IDATߚ”D`WKw fG[\m- ނ5ݱ9Z띵mMb_3+]]޻q8iUjw>POT +/|>O S>{jƀ ܎}uE]M]C]-#ԳJno1Ĕ¬/m9̆8MZr"$WJLnXML4*$!n ̘jܬe>[j.]1}"s_[0`CcT+WKO9$*B 1뉈YںCbvI^~zm۹g9!0&Mq/v3gпo1E$˃SaJ{Ͼk1N&8uێs9f@EBFd(jYdc쇩k&I}Wm4sb"3Є!D3sA!d̔RumbfFM5t9[Rq^D\@`$M f@MLN%o*30S?!ka}T4Yn?o5| 11#IbLI5)$qc]5DBR ~!~wkߩS]]6va.MqMKG&m>咝g4Ƙ[%%Ǯkꦭi=h !$IuuMVXV*Q[3G~VޝvͻZxD4hR((f "*&prX6oT *wk\ ;RH{:TM&cwǺ.s+pcLLE4վY@$r!- S21b2TYj5S^# ^<UXG{l ^|ͳ^,$ʭ`HsGד7jr򮨉@h-#8\S>Gc yvkgl%[/zg@Y>1PHxܷM )DG狊 1%0'#x/-.^ϑ(WeIu+-n"퐿, 223gx@TUոv߼~)9KIT;MѢ)TugΞ~3W~C4CB8QbV#j;^x77^zԾmy~B]ۥ$LfDL14y1U1]CP4|$47<*b>k8LW CHwy&,IJõKkѹ+7_Tǹ-(x `ݬx߽D1R7"Yw̩ÃI*!~4LIRn_@"jjԷ>Nb ۀHͼny~}_mfw7-Tc䪪%;hA۷7GwCˣy4)Z5y|]qgH1*0jmSW|uuUϷqۏ4uUULqUɂ0NŦq*c`1zb1D< Wk>~&uO~O_yZ,kzNn9fdJI$|%89ewGCkcJqӹ m;7S]MMB"zw9 oor,狆jfgϞ9<}.88MShb }Siac EQCty\q|W ?+Z|]{=@H&8 ~04ԝWr0 aP 4U_Ջ_[CDF-2q]9U[1Eܝ]&v-pxϯmoAM@4N޹^=IQR0a9gL~ y۴5"# v}h5mu]fnA!H !68ԯ@ 0hU]6jdG7oWm{/߰7oT]PQb*֍[^׮|n*uT*[;vK"ITT4DH}rߡ=sT0{r5EޚX`͹sG)`7g>۟'h:uXi9b DDe}g*" VH̼YmvӺ`ޠ2!Pn 5PIbw{!잆!b]{@ DfOLFIT

    Ƈz^xiڢ9mC-X+/酇OYz ffD y{G5Ȏف\_KZB d.H>=b`Kw6[٢jӯC3{1? 1?'j}w^mL7k5ЪH f)&fwON ^u $fv⁻-4.M'#UvO8?^w;o|bzO\sUUU>ދ4I!Ą]wmZv1sl}IL#D4q}Mo/yupɛڎ=%3S@cm׵ucfSB ahDBH(G"2AD4%Ylƞs S0N]WU~oyڭkզ*F_U fF|r1v+_yj ϫWf" y,B[Ʀc$g͈L~ Ɠ]_wU=oy_?/ߘzK>w`HktO^ITS >`~5؞H-"$<1?&S< &z##2! nnp2h;3ߘ*9|-9Rǿ/uU]w]{&?{_Ͳo {8nuw5{"M,E2l! 1$-j0!%KP%o |125YqbT)Q8]59{V>sHIP]wz9z3DȳP]5,rs95-xb;q=3m&߆syY{qarɒMD DMKEe؍z&~7˹ ˅(Bfvwy~ެ%' zf;]Hږr\ 9׷_*[o_2S$ZDZ:/F#38 "3p! -|w捶 }؋?Px˿K9mG~G993;)%w!([71S2I Sוk\]TU(|S+9MnbU[ٳ6QiWDM9ɻYD8^~Hc:=;;Gmׄ|>mC"$bt>"sC\)p6W,CMOG3Dݬ31MyHJ\rNccZ_\vB!!4!X .mG|InLcIeZ;o6}v$LܸٝO㈄0[D<ڕn}U(Rֽv7._`՞BhS2)ZAAb]RGE8DVk_= gwg1k=02  L+tQ7dB$)Y 6hRbuĀFbhӨ۽NN|uܩYt%a0"m\ŗ!|lǧ(:*[%mo|'g:8_.)i.Hj"BޘRj 8޽w9=+ѬI]ATّ̰FHIKEJɒ\ ~0P(8TRo\߼K_g>Gzݴ׾uU=zfaVK) x:xgs~Ɨ6#0ѵ}"] J.BHɤH!RCwm:|,_6D Ķy{ۿs?Kcm߸u]!*TT)z.EL j`X [nv݃u]fjPu:dTQU@$@0Qf1p㨉,a,bP q6[x,.T j ϼ"y5mkj5-AK) &<1usxvL|"ڦErѢHJR)K*9{9nx隦i\JXMGݺᓓtWX]VpF ;- f0ÐKm:fN߼8;?sTnym1fbb-Zd0cCz3ܽзρc;B\\n4Ώ>oaW]u~e"~7135ji+~!b"UfNӫ2v5|۴[KfG8Ő#0@¢ UbY4&Rjy3&Ƈ7=v z^U8R3w~ȱs̎=(C] hT%31ꔾ4blvǢYE1H)%5\J){i$nя~nD@}iDw~v=OfWqYt)~)RAHs)P3Ԅ VQ{ .B:^;}.e޵a[7ptpV]u>gx=ȍ|-f19!M;_)Ďk,8c4wPw^DHTS_++ };Oȉ!>T\)8d$B,D]6RsЩ912#3"`*r+2qPM&6ڛu5DȈ``9'mEX,R9\0l7zYa(ZyW̗{kZ)ɑkf\4mH)B@UE4r)CTJ):_t0i11>#ow;%Sr|u}덴osMbb чf9,*(juA7a;J3(.Ԥ,S-Eb<&ЇCםy'y;o~ݒA ᛯ~|y~D5=[.ouruv~{>ǫ䬦Dإ(!6;)E$ۤ'Fx ٯ/F@{M3yzdL}_ ܹ.6]mκKv~~ȻzA&"rW2*0S(Iơ(3G Sj&:hCv'8b"ȩ"^mjʕ頨3U55Uֲ:$hq\;6D Tm!0!c˯hN_W5@ր1S6\in/];?ttrj&*kfX]NY"E2ʰKl0hD"xgDŢkǗ˃ۯ8__+c.Q}n=R|ǜ_ů 123s;ODL@̀ IDATLĊTPm|4,x51 )K\B$Gf("(_@L4Mv᪨L TՊJ("5Fr1;9+ؤҭZV @Ŧ8Lw~wo^6[@̈HLl_q`}๔r)޹]'"fK\(1}^vC/"I}XFf?[.O϶Zn<ģʫwܢȇGc?aC. 9j)_]4Mжm=~!KUBmHCv=Oϯ=XOwo >}w}HB)Y%!";0%"ƔѶRʝK3mg3Oڏ*[K~/R|[/}PgΓCGĞİұ }n6:#Zw;vH䘜cJ!K`dh*E@Lk L!Rrcr;;ڿvհA']4YD 44S13"$W~g2p<`L W&wիcxGÿCmS.jgYfqٕ*f*RJC+ ?gۋF|CЊƓo68,ys=>ryz͗NXR\ #!EJdoo\;'ǫ{ڵ) PDrʣgF ~ӛpW1BO^ұoDGd0_?DL Pe^ɸ4q–;_~a}xMϾKHɱS:bf(FfPlrw\4m6Dhf윣JCt)^% "A *&Sjt/,D""VHʥ92v;nvBRB^>5Ỳ,o'Oֶbqh2cg{c?O4d|V7HnIeԷ#LvH@6:_~ 5sDQd +X(VTRUiޢ[yKL59v̄=}p!  "ŝ#vbEL#]"B@)9\nVDL)*y,nHcʹ,Dd-"" hW&=x0UPkFw.t?>p@\qbf܃&̓ƶnOR$"Eb Ŭi2!4:wq}b̐!"R5 ,<%g\Ժ=<7w&v#`Psy&f@4aВ Xo* `B ml_wϓg$Zɇ'<iU3%6ݽa>i\/PJ׶?_nD=,ߑ~^KG碣s Yt}_܊.̖yαwyƺg T"R@5#ftLTݩ\!J.0"%K. eLrY.C&|;kѺWf"XQC|F"51C5ā 䶉ٳ?I> 6MABb~ɧ@91bMf]/8!1^{_?{ytƔ*Xќi&kq66S8&GewpxGV \wJ)fcϡ!4 bSTr\Ͷ9'JJn!#2 0֫W8Jjs.4!땴u'm _h3(G)HȬ$@,+۳ \@A? M??a}x<ۿޱ6 pZeMX.cs) `u]a dvcf2^k9ZP;k D~03w&~K))s.4JDf=K!4"bHAJŃ]*vA5LhQvˠðmT<1 >C LUT("8WXէTT&|~ . {xb9i%q}y9zc~2U_ɏ܇n契o'/P<)zѮ؆ZD<F]٧NZTD Ri.4nW~!qHc?Pu-9]mN>tnaM%99H''LkX)\dLrmM3e֫vq[6|k'`apZ)N/¸Iy}|o{w޾uA#R+Ɔ QT >X@̐ș}|A:\ƬL0BQS î;PI|t{ ";Fs8 c\.eb=;>xʙд4 !im$b 1̢e,E'S+!)RjxE09R$inlvorxELyô"P-XImiHYt&"3~ع6.b RJRKW5SNY@mbyL˓ܝmW׿{XR2"M~޽/53DJB"dvC?,{Ͻf<圢9ƞu|7їťo3t^o7n[~mhb*#z:[tH8o<)%gBR;0\,ۯ}m8r`1м5-]`F:U1V";w3J3!=Ňo~ Q%scﰊ<jjӆn1kbhRnU5s f{`CmXqL&wsnJ.f U5%<99B.%4n׎.jq}0:/`RQU,Ј$g<0 a&L &xd;Ssiۃ"RdG?Ai/w/6ݹ^oG-`6yLͺf_V[oV 6SY'GqwuY>XntmƎ<(".z[o}vCzG80Sqǡ?Ogbd_V[̝#LSJc4<ΉjWFfق6M{ߣqP>ȎԔ+ p33YEj2X y1[v.d20?𼲇۾ifD{bwܔoK̠j%K98l]ч 1˛*bBZ?#>oc% VUIIAs)ErT$Rr)|@rJ#nvL;ϡrvv^+W@# |_6hpW|yװ{"@jXhcÐNn>ȢysZDbwfHuSRICjxpp` 9jcN5w6QTDh 2f]_7jFf={rdU LEL@'~a}QŦJꇱ?Ru4PPy8 M^&ޥfR@YR*ikE Fĵd*k6wXw5lʸUZ=̖\a&EJ,-&G˩J=  \5"Kvۣ* q^nRIݢmG3/qC>>BaBBFǞټiey37`&VwrgFf}qqΝx"5D:1#Xm/>̖s.>xrE19LK)ŤNTnX׾rvPLB.o"33qplc`"fuW.^'w׮͘;|p?3?s?,_y3Ky"=bS`Fh*"9c2]>vc .n̄&wDUo@PwEUH5j]NԖ"„SWZIbwO=y1ygu [Xd*5DKQ{MssJöl6 1=;T҂Ip c}X,*)QDbY~R wlGP0lw;E"b>>H*=;tm"%b5 |];sDcInZo/#?/L2㣣T0E,iMK8Z wvG);nCtb>VcGjP@4Lk[ c6TB?O|^|Xjf^BDsD"Õ-&i4#9jޱwpH\PE뚾֝:eX2!V߁M0bh̴d("fZ5L*"cbzoQ)r;OO;@$6On;1S}0S˥h1ʴ4$3뇡޾C@5ccb>Do%Ա k^%A ͚-ZfgC"䃻qw`6_GlaM3cUljmݻwKI%eTSSd=هw q,ra.2Ħ9:<}hȆ| LĜS:?[!Bl=3Od)+K9''GGȐ4SeFdT]6ߟ;PUQm0a7#9}}ʫ)z;y#3{OL@;v1LƘ 0:lJ1ovYЏ`?LxXjo'?_Ѫ4@dd_``ń`SR)է}rqQ'ӴWSV཯x׊V1@M *Rܴꋝ1=+`"TJF{yCmv<|vr|9@ld6ӬJ.919)Ozٮ9bMG?̠+pYSSm;ݠIrukuR)1BhQz \~=oݹ/,˒m/#s˅]%g4.ˋ4#L5TtoCâjR*W]L&1#"lV4:vH@%-6FvޞJ8)%!}ќ*xn%D jNYrɣ:˷/Oov; G{rUn)v޳cDŽe`2;&&D$ KLrޜwvݸ&z5٭.\;'K◾OEDt\4"+ԪYbVR2wSpĩŦyU*ct]7E!`zY5brĕ>lwJPB@F&H5,WA.jf56)C)ZƔQ CkG|"Ȫ%e>כ\YϹjs!T J{ໂ(gBRt{{nӐL0˃{D)^ hmJ9o|cowbnPJn"/G`ۄYhY{:6۝9|u|LSY,|'~HQ1;?i@h~d]v7+_ɍrNLv;sJ9l >:H9b<>owȵ+RH20W6eքEAfoGlWDꁀ0:B0Um1 ; ޕbDյYw߸.mA @߸ ~/_db^ pRèRٚf:TjFc%j.ѱL8WU 'K]̓N}Uxe:%B&׏n:r$ wQ$k:3"9 igC"6U@411¡z& #!fy.7s3@pxAw.q 2>7?]+wp, ..WիU 3h8|{?_\QkwDEBA4lf  EhIW5:rSޮ7e(MvC𕁆'ZI%(9P\j}~7_zFy$>y#lG .6!zGWbEɊzOrΩ 18oܵ]n.6)<#B."9O?ua_?? Mp!DH9~LC H)*N1H,"Y7FI"h+&h ZTԉo8m+ DLUMjU|*jE0TdD2QQJ IDATy0QM}ícb bwT} 5y;<&&:q#N`&.c7ω@j.EEXJ]u]״W-у )W^ bl=UqǢsr듬,Rr)D1x af{W ~2"O>|6oa7H"֏zeG?~lzt]7s 9e3@fP" !ðdr׎GA'r!)9 1s\J4!uM5bNdl|9J37%$UMy+ޘ8x.kٓO=g[Y>0&faJw7'qhxYēoٹO7~׾Bx8aWu>O}곟zR݈6M7u bر@)j*mZ) kX5`3L+^FV+/$wa8 C n!X)\RrQI1#7͛قVłLe1;lV\؏M;9Hg(ubHSojWPG4ry<UbZL{rHcJ)!#`lb5eHJwoX,bT.חmTJ"(#" CD-j~ӝKR\ݻVe9Kއ?6q78V h-YA+!- OnyyWwLg]JΩS2j"3S@&ebCc0ܮ96 ޕc!MApHfP 8yTeakf{s̫ܠqz=7cPb _|ڿ_|k/]LkJZ15VQRD4aUDr)*fE g3 R`!F['6 iM2ժg$#Nꆺ S%sFS)9? hV+$Fv{W0ĥ4?:8~cXCf,)0MNmzDLfIȡ]1"6SHPpfan&Ȫ@sJ% nۏCTwݿwkZbwqyy{6l>gۦ)@dG0@ 1K`[p\Lk(M5 "b%awܸM%$ (&fZU"&Ye_R cvHe۶m`ލ8K$cŃÃjo\ 2pL\Q"O)b\]k֑5 DŽC$ACv䈀R!@47fֱsJߧպ_zǮiY)c?S߆HO:mkʷNzc3G=߆€վrkV;g$5蜐C*V7HMl-˜iEe]nJ%;UrJ)vw6JQl1 >m 0Jɥ攊<0Ŷiv݆=T.?gGzFoig?}c!3RuiRR:>=}/r5˖h_L @Lл@MOsъ4<'r5M8qGUm隣CSǏL4&ZKRJ)\J!&&~/<"|=V\*Bw1hO\{-dMKcz'J!ecB Rf_{TTnWxm9&؋ɹ:ETi*۝llq[QHؼVִy)@Ed)RBSI057PCOr.If0Ed raŵ<C-HhT͠]tgwϗA-"PӶmyCH'D|ɢ)Uy_Cjj&%g$n>2ãŢ;<:4K13x 9ɹi岋] )O)O8YDw717ISiЩXԱHHHyG?e楳WOyp)YjQSMZkp0ɗc+'#Mx]].:ifu1xZvMN[/g?Yjf7~M^+$t?OUT󦔊vi/f!c*t *VG ,8"Bv3!<=gU[&B`E Lqi~shi/BdƻRf"4'$)qC; &*}\m^SLDj$oJ*a9OC]'ッCClCZRDK& Qy璊hyJ"b$i.ٳ[-Ͳ Mq'Yݸ<\.Ceޤi~W\ps?*((U$hҜd`R efًq7{O1f99Cl"?9y0#5S"JbK.%gk6I)//HgOg~JQ{^Ur398ӜyJak x]h+8;Eg0݊[_;я4CӶMH)옋L<7]H5jO;L(6Qi"QVחk3agAd*\㧢t_[KڜVK3ȩ 0ϳ#ͫc]\Tq׏|{Niw3M՟CN MJ)"ZRjIeg=Sޑ{ju@ f \E"x˃ÓnنZnNy S;&H)Wv.;Gxs*' ~\ߤծ/^ã|ރsR_\55$& %X&>^bN3#3MHEo֛abtmI&2 !9k-(LӬ{0$ej݆kS+sQ Eg>y-.;JΈ1Q\\H5sf[-jE+Jɜ~~? _g?_JݪXT`pL8'"*;:Xvv=8Ħoh}ɡ-D/n 5~jݢQJiF04&FVnugH͂pѡ^\wW.`CgwN Y@̥ZZ946<ܚ߼ nwC).|day4Ӧ]_^"s״8z={? ?wvqV1 9HEy(4sEIeӳǟNO_\>wp;) i1865!fѪi<@`R3 m'G̓G~.{Q}*BdfTprto~GW/5-6c- f]0 0ny^t,R ߌ*+''XJ6df{T朒(# 3OADر٣'i+Gt7š1ńQT-,9K*6c.*}? jل3 )2qAs? gߴo\ۣxaĔ!&j&ܾK&Z;A@8Xٙ:Xve3[#YL9i췻mˮRPyN\MyLHVnXrDi`[f0ru]owRJEۯ!A㣘z3C\ ̬0 {G*Z9Ds }ڗKhX)aa ؏b?7_[3TWz%KRJ&: ¡GCjӋSsLJu8\]EaO_قx~'\|mҜE6D,Ea(9@Ni6ݪ$04D$웶+:gTŬi_o0v3Spo3#333""xk-J"iNyHtptH*2S1bDߎk;Fl&utЀa7nR$4х@ؐBl1IB՟rUg%kkdRtG5[4̚O>CH "1Nۮڼs1L*Jnxvzt~vdZBpEND3de@$G)4 g(f)_ISN)fA$n{uyw^! L=(@Ѩz54Om]1`b4/mǎHNs"UR4{qC3zGD ZMo9yJvw7)pӱg3SUˢE5Rbf">],}^D}`}_җ|Vj7RyRnd i3@@hy.Z~$ 4KxvWk(w_܉wrFC.RyfFŢ 4M7S nBb&h@ d nnqC}ChOOOݻuA"-w딀}5%w<\iԴ6h "CUE~vGm)il4Oqyw;_ GO`oD\\jN 9OÄy]6uaf\gRr9Bln1MY͜DO1IJʋEs{v?/<)`cKGcDRc c )0[?l1f&aǥ.8 'O~ϽHV5C Cd[g>oe~1'rhh*|xpt]"S dXUU,RHP*1KDDݜf^r\@Gi#NԴӜsHɥ$gUu&ct?3#&c5(EoJ_"OӜJ==4Ԁwl*7TQGf'e"Gw3"l%<tqݿw|hbl68v$@7qۮig'bn[0c!.WK"ʩX}Qfz':ojz}u}mmږјb G+}YTy}՘:6H㦒4q^<{K.03D0)ufon9fᷛχM?4 >,;vDٻmWj.Mͦ ߽aΩL89˴O^}8N0<{b^y'"\729Kx5!$}?tvhc>WSo| %mpb.Wx.;t<{="ǪI1stj!aڋ]"土={㋻wSio$+ Fε]撦=q߹{'697<*$#`EhZ˳J/otΠF5x! ͓ƛ_O\^>i39W˄PqTԦIR̤f&tn]l!s Jkπ.:z˹៱R֘ӯ<~D )cSE3$&e%1iRJɥl<}fWk/1,;?L^ߤ4s~Cj\p%ifNIT.ƋHJ)!дm{}Iu,C?=lv>kعWw)k.Z*e"xoܽ:!hf8OCl6)<ͻHݦ>c$$;j)eάf;4OY$t1 }2Ecڦ9 hf뭋ۡID`RNxZ薫e z}>H)%Dh"VJ>>h=l:I 1o;ĈP)"G}~O 9އb1c!{9Bt~廇zq(>iD\SJik1!Wo\s~+a_Gǿ)eU-|60[صhGudH6[(9c.Џpu>Wx}C Aώ`eF*& ECEj *蝫f1ibbl{<}/| 榿)EcCW_Y"%3G{ >J0C31PDB ˜6sڮm&|js%vcO HX綹k\٭O9iVnBEs/&D |Ha0N訔2sLY{UmBlw̎9x@ =i0"`}?4?~TJ_GO9=.^%H B΁c5 M :iDjC8#X- 8'UUp}b!PMX IDATf@ Hp@rl{ɓ|K?a`-v4luˠAd!g18(v;6@ tqӫa763M&ჅlJwEJN٠br܏!96aSY B\sVC30-+۫k31#`O{snх]C G9V;DJeiʚKN922O.61gyoַ~~Rg|o9!wSyОjVLNq( 1Zf,;X \xy@6 `FkcTyy"5&nPG@Tcslbl R)bDœO>y_{u<1 GnAH$rM ВKZJ)R7Y)ps!؄nљf+-㓓nٱc9Tђ|HQȻW')8Рioe{IeȓGO~0Cb8r̥ PhҹIQ'oʗR4 ۾Дnv;K"&vj}?朗[1xj#1|p(4~Zu]svwַ6>8d Wtu dbfqJyJ_r|uyXm3vz9o8 />Z__11$@֪Zc{[7@R0;Cɖlaa\,'TubĮCXsQhf믞߿w}uY"Lm 1T ;Br{qUa.eNT2Myx<<}_Smlgǟ?׿ο.%\3fԌ=n U)ь w)ӏJHFLoBT2%$$fOT@q^!h`O0-p%WP~s^Z]!-um{BLJ)Dy) 6 F ל'5@:FF1%UT"){w;<9MxJԧ1X'r)%H)R^}⒈|Ӽ `8IH96PJޫo//<]JDS}zAW5V. HLDV]ӄ !8C3Q)w.Mi\gC\,]SfJ9:g]>ZZ8derGWHuc P]yL,E>z͟ɣC,VZ#jSf^ q6w> ކѦRAFݝxcב>va#fC< 'kw>} cs #:j.dJ0UR,0Y4!0Wm"٬϶G?ཫPT׏oK/EŲ/ɱt2B6$dD&vD* HD)x7U0`*-g\%KS% j V<)y7Bn;C?O Ď ggRuVRq H dQ"By |eg󜚮mƇ/ q)byrDf&fՋ#j)Yj*`uzv1 M<ڼ0CƜ "fg/NN)[oӴ(6 Y5ёZǁkp݂ɥ)˔ɅP h.em ד9FࢯKS8vѹཫN6v*{ǎyFTvb)1S}1%QSe8eSw/&n[~~m[Kȼ26 fYL`SU'xq[GTR0gDxb,M9 #LV 9gjEJ N2SӶof[Ď{;Tt"RsvdMc>p؝;_W?+o+ fw{~NIl-OYy0B DsĚz\rs>N!ۢP""E̤di<;T}~Abf9S i7Gc?_yӠ4m|â 3hȄ.J`T1Dy*m}44;}Nf jȓw h!ɒV mۮ&XwcO*"{Om0rxr?yn8IXb\nu0N]}C!cNzws9|> cGkg<;Cu{idc~  ~90޽DfoC0&v"ڼX@QfWncXy&{^}itiAThɩbm(҃Cl77_z㈨"Xɖ)YqΥMhbfDp&CO?wRF0FDMQˀCCLViDR414W]4sHg3Vфh?S'(Z.,JA̡P"ճ"U8Z//qub<)BW)ESs9"S $DbL@V)DhR޹4)$JXÄS.S}z]fy+ ﯯ8:^AMJm#"1@'39 e U<#9{Juǀ&ʹ8.כ$ PS`\"Wiկ;=p!#2t^A IR)"49wmc=&aNB@H((LԾ{~) 79Sz|Pךu[6O<]~2fw1P5}l"%g5fTy/-g:{W)j[`ǽlw/ajZ $Lvg|\APguQV)fV4303iA0|Di70hGrqw<::V]\ww)ԍT:"8FC&Ubr}mIKG LIRbP02zr>@,x$Ƌ^ dC%z߆QvyMmk5Ϥ #"H BK.>5}33aTH%J釹5M 9͝37s.;?޷At$lnXzQ0vl<}w8BUA{o XquM W٦ԔC0UR}UZU!m"M] j@$Fhj&E͡U7|*oC`t7|jUS):fZ0S>H~6<(61YTU[(ڟ.]Dž !e.;f*E9,_o64N"XuԶ--b!|WřP%S(w{why|7XءX<Z.bW/e(TUzHX hɼ*7M)9g1mKkzbQy)>g./"ճ:z^yxbH0 &827zcj/'C|ݽ!$6+zX릪\po(Jc "E/k_DU`i/Z`)yUT骪%]R޷n@[:ϒ_ ~εYwx Sl j&OTJ$ XDK~St.0 S}pyjb@b\xzC@hۗח77B/._8_Ǩyav67R{_}O}o;]s>hd]qN$@D0%>!1*vjlM4I1;cUPg5IsqJ3ayڍLSOi7̪ḓr.g7```l`#}w}}g8Oi.*7q@H,m~"z=~Al(MΩ"^ڦqL= >(%coB.GAsgw>4FNѬZ֒J*6sJiC׶aFsF󮈊 )wm1K`13ya5:^BB՚ȑcfvD _շW썐kfHP3 ў{lOWYߴٛ|YmrV$'" (B"\Ju!9V~ݼߞAy;MY$4mbdfx†d➞܇ڶ95DM~{{cg166Wz jƷ08 _{蘒 w,IcQ_ ]@)(6,LLBcLTULVJff;GBv0 jVJ2i6Ɗ%TT5ЄSׯO?g/"R!"\Eɴ.}{ XS?)ջfV4VU{<43 :ZcSSrZW [=mj=^oTՊZQZVBɥ\R\/}ݏl7oNvGd">}c0?_Zw(nrH̺mb۠f5v_]_̖s:`!@Z>j.;!&m$q 1ݭ#ý;qJ.:i'!F*A]QnOk[< ZNRƉ Ow$Ys% Qs6"-W?ӣsz++"ܶ @T\"7Кhn^?Xk;q,޻V~J 8ITkNuU2fjZkIl4M Ī7x}9mc)Gaa]Ķ6kccۄz9ԋ!_ז~s|Zb)֗N!r41My7ol<\sN'M "" g 2ƘS:GbDpUϩ\>a`qQڮ+V=>zf d`H5 yuqÞ8;/Ej{lvLJ6[MW"s(LyL6,-ZZ6\a58:cILT\h.9NMhfַ OCTvժ &)J0 ɜ3p0UhXww8&ŪzZ\_}zuj䧇"p89xEm Y25BVapsm$JKZ\)b n80%aE] *BRJ9$ATX5y?QY֊mJ˶b\5%i=J)hQ+ڴM NIYEH 4kgb9ϻ~g|gj9_bwY7lֶm M) "&v]h H LO+P2B }+痫}Wgiʿw~}]\}Ӥ$(¡$M)viڦ;8hYVr SoqO IDATfVSnV S8Ħ]uSXwӒ0ejvnۭOS"~>-z\-&AjV iH(\O/r®i\}1[l>-%_p?zqv~ !g+ "iz٫i/ps5HIOUm pUm{Ij@ T*]vk;3ZҘK6D"9fݝ#tE(aa(m? C_^]um"$>ͺ㫗Cl}nI Z'̪WXh> Q {|aDŭgHTj TbA* Әs96Da=39k!X}K_#ÒzjLr ~Fwo[*+ڨjyrҊׯyuUwBKV4=ܵmv]<nf@%I@Df2DҢTԀ9p!2D1pRŜ=( x R;ӌDM8 4Xv}lBtU84kW볮k4!!k1EL9A^ 3{lEv4^nlfhxr"ҴMLLT]ݙeu}tyjɑ̉.oJ)oog3J~~7߀rׅKyLCrHD@=^ |k?sێ)ӧݾǼaR{nիAGawQSG(,1ƩFǕZ!J[$x)74&#߾ӂq>7o 6~~}?#ݬ-IAS 'M77~ua۝|m-0GT3Wxsk=_ WgrE֨Ё ܆>( W>k͵4%:"Bb(٭}~GicEiJjRicvűȾ]ӵտONSrWGDՒa{V7mDXP"ҬiL_blY!Ӱp\\^\ȹ*fO04x)3"{U"Rw|XpJ)S xuT穔q݄[wUC]JӁ`EyswOo Re$g^S" z;g, SIɦ:`By{~{w^l(Bf5ݭIrprLјLcb$BF7K՞ͧq)JU+@D@10@cB0 ԦݪNEMo߼}ѫjhn.;\|Vh]cDU0%u[{qmg]' јETicOw^HsBpgn7Eswy7ibd9kDDCQF5f}W~+'{^*w1Cd` *k딩hP''Cqu;xqfFnHn.#ppdUӺPcsZPuV 9H@::~OE:A3"7?`i '-JNyqLEjADVlnOLbZft@U;T@Դ+f: s-Z)Ɲ[q50+蘦sC:LWWj_HC殑& o?rM~o~atU+EQk1 ku&t)D$n]}$Fxp&mlcn6kXw1J$&pd"YӶ "8i9̬d+iДrʙIl:qJaR=l$c 8Wo2( !_<ŵ2MIݙ)(Hfe$a0N 1ZX#>k]Twp\ 77 eώJ@?QNM}6/f[+D!sN#3schD)j XTKNf&,^ _ǟV_|c1$lbAojDNៀL`Ub,!vmDb7MccQÉ]ӧ(0 I%*Rk:bti t(rd%GA"҄Qh#R D25%_)&. H*Ery3/~O{73 ri{6n1p<6aw1_?5tYNtkK1`#n $zfa7wd͂\\]9sts{^sΡk.//Dx<("S*חO]|k|xr}DBRl)=lB/_~L$,4.4jssimxl;/C;f+ruW& &s!H۹Y)Y 3Y-  E)Ʊ䔉`?\p8NsrDXE50uU۴TrqI DHؘ}zū%"'cQz{:2IhG|v ΙةG'@G +^9e5;8ct@ʩhq!!Jl ܬfA2K D3+)yGctտCxĦ!#6ͬfU1Ķm 6Rhڮoc$tmlش14WITc'˖#Y* 3ֳjvV_Ւm~81Oln~{f`hnJI%-lvбnA #թiA%H8+~}PpV"ԑO_]]}z?VBhVb&DtІkspH!)tmӷ5D6B "B\(D3qlQnBǀ'Gu0 EU,Dfj<=٘Lyo5m8[]^_Rw5wB@&`qw~Wpdm$N;, !"DNS" Mlھ[W٬%فr4M!O)R\/^8 ^Bq]ا$1\]_t|=<G+cĨL)?xi40 ͖}wa?oj^̦\AZ4|89bQ˙YW4Mʫ$.T3joƿQ(pZ5m0s.u8͐RAVn1o( Q" IX%0Y=4j6ڦdva `'Iw}5bwG$%aET'p<; Q2BZΏjjxiz_t13a,Hcîh 6FAsR:577ggg\Ҿ Gw4B ilk% H!ARL4SIy< _?q{<V <Sl4$&b3ZFfF,-u/P*&a#/f6㳧:M_r:soa_t{LB *Db\(@:_}a?8KIp. L16fru}2e.gup16UqsJ^* +RP̊>=j////f}?И1grD6y*4% ho[Y̚؈412$6\oFsF͋[IZ\!*jfPI;3Ӕrq5'|ŋYWgϮ>γ'OKyo8J]~s? KAd}ًP 1k4u}|RAt[ 1/ZF~{"% *2iђ!HǓ+XET`=i R>Ci~ͳOë>w-؅ E_Jw[Ksř9?l}̗+ݹ=bwHP:wwuВsγy^/yB>>+VpdT ƬkvwtBjc+0 1k~?|j";݆-nT Q (Aءy)HdDZOCA]J?YuT⦫W01"#J%a`Zmn?S0 U;ڥRS⪒ږN@ E1!:,jU!bib inRa(10AUpe5ՕЪGlw˛/|z^o)% gg6JLHt`1,6ijnf0d~ IDATpNG'a;lZRTPBrR,RRq=NSQ?gyݶn{r8Gnvq),vھkg0!#y8HZ$,CNܘIDWpH(vM;kpxOĘsw>}D]aDjZJ)u׬mw"Mж ?8T#K0)fc9?{|u~bw?n0.7c1R[-a4~ߴ]XrN>o+Z,/Wj2YYq3vèmԟ(UUb݌t(!R >yv:|7bvDP98VB7 uh`hL5V@YaXdj"!ҔcD\;Ǫ[x'Vg,bj;7pF 5#6f V 6YJc bMɔ&-VL sJLdyLHBrRLs9 4O|zS/IIY^-0rXBhH`b)vv7ԙp9kYӴ%`ߨڮ:7GǙlbqOi"Mlg!2S_ɟT~l}|]I)4NM MӴ]wɓgWjj4UA6W'<H6"2}B~އ5%Є&6˞J eddd˚ƤZYmb6 "m;s3/ugVjcI)p< >|,Vq(O/]a}{@~xMBwـV.\QLi̛~r%B8[g%onh.lN(@U5r5[桉17]^RΥ4Lo?ƴX%4f7Ï@s_&2(>]iw1QPv7P+^ZDk1X1~{PuV*Z-iI7 "oc,LB,SzNi]FTJ޾]C5bH s)5PԓDMU[5-uxR;.b@2!MӤ8B)ʧkvpLc!GŌxӻKa?w?0g[/YW\)$r}89I2Arѵm1pH$Hh ֕+D(UA "]lyr?/rYlپy棗};W7MKI(!&&,7j}> IEO|ʒ x13 g=cuw9e֕`b7oC-9ܽmw0@v]9B*Xʶu-Z4Ly!Y7)#Db>a\X4iKZˋYq8|߭C,_ggc\Hm&l[gn1}^K@sɹzZ-( 볳RT5ս./mz\-%nm`:6֝[.9, hD~rJ9N"p{{e5EHHg/V3 4M3 bD'c 贀"sC8p1:ox=j2~~7m'VՍiMx0?Yj'3XRm۩diRjd1U4[ͮWT74Monk|z?D.pԌc#c%ð)֡|}v1t ŖbӴMlZf˘Ƣ"M4s&b =^CDb 52H{؏m)fUJ훷wwwaRLK.e7fM#̸x6/gj)VN^%@t< ¾.3.Cc+90gm׷4N~{܅⬟h)iT ki |(Bhb6'L1qi: WOog|[g~ճ7;\"$8ܽyX-//I=2_fAH 9=z>maF37ՒJzX/m]>g@8iJ%2;Z/N>ض\7""҄iPcSz7b!_o6LB/np1z0&"ZmӖ̲s NjR9BTp\%0Vϕְ$/Y1¾T>sDr$!@'_0Kp8@)@;pN$>{YD4GZN^Xݫ6 BGЫTaYݐ@f]~=ˮ{s~GWWWwHZTdG$evd 482(D%F`gjà d$&H6n_c`Kt7@зnj}~?tEΗw駬(0`f%8ߴeXcGFfeT: R7:9>G?eC;@;&bl}LQN/2i=UU|.qi H.e}b~ih Md ЬiB$)~W YywvvqqW?|WWiH,Yc:#v!0?~0?8v_JC 'G'M\:8Ib<~AvuZ̖s**/VW/Nr*809[W:W75-Ťz&Cywpxt/VϿB]euw3EA HJMZJ2М !+0D3*/;.fIU FN$ﶻ0f%*ԄqD!19\1ѱgcɹ ~U1u0 yvΩ8D)c!dISh*QYM 1v53K}yuT|2!f)򊉄{rXB"(F]6:rb]]ׁѨo5☃ q!u U]׈F bbC9K;UU͚G/~ͷdc1}p~y])5 BpQJ2!tRUU9__~{?m}ALi&LLNhdzdf]) }ڷ.w=!9D뺶mC?#itR?8pnژ}*qɤ~?|wl>{ח)ƺq#!S"$&Ͳyd9lWim6֢0_ɴ]_ 08Pg!I3]DӓӃ!}5֯>9!o~׾?O2bSnyك{olOj~_v<ρpqM|2m F:"*XrGvĨU@\ލFdTwe.FÒ`dzjHųtf5|f=8OOꊫek19$)VGPb1%- ,ArCL1&QYM59kNbf}IHv9O>Cv*vyp]·a,R)eՌG@E96͆@h/-(B%a3 m{nZ0;dxyrt7Ҭ`TLۏ?|_.'>O֫:.%53GXctKg=2BvwCi|5fEsMgHeULJ_~f]`6{<1.njsrr<:l!wMRUf`Q/3QX~s~F}<>;9;;Œ0, @4}7ΧLEm"AV+\̦)3LyR/WWޅW_<\CS2D9з}rۛu3|)Hq+`ιR3L''__o󺷪æjF(*!\w(* C1qETv.8BD(؂jIr9&T8gU>;$2D(z/@blw+bͧIF'&TrL"Z9IiY!4Sfo1R Oq@@W%`XIwwr~9}k`L#@30`w1@|ͷvk_G}sU3qvߚfBvRXV7M]7!8]ov>$9YcC44g>eĄ=1ŘR<aاS`e^\ thXxniHiH)KʩQxv1"w+$*mUl1]NKq4k.sg$BCTSvuu+ڃb曏oSzՋַdg"":BqQT9?th^/ڶo i]UU'i8=Y~joo>|ݮݮw}מ? ue@U.?z ?#}ztGsTs>)LJ Gx!!B釟<ŧbsjn! ]W423P6u# ~աzlBs"6^>Mx2 }HS:qc>ypTWxzC6gx1;8_NL vۛ)0*B!j.$rNӒ*Y66@PJf\D+|N]SU$;fLݮ~^cfWfppp(hd8$Q%55S+fbr>TDdVUef9'& X T1][6D"@8RJC Ծ8=jɫ\UezB%:2z)ˊ 2ZJy2OUam[DPL3Y-s)0ĮDLJ")Xٓ[699%)˃̱=ccKdj,mq\.?|v&jo?/O'M/xsWuCrSO(,ac[f(`DYR:hv0Qi|1T7^tWfjCog?cf&&"[趻4/)R snۗPi:$w狹oBSIM%5VIʪJh U vPIf{}~̚bKVT4t}" !8ٱQ]PTA'ugNDɽ6m{0D߁8ﳤ}r4%03 K(Y4<R{&I4K\P.LgWUJ ל$gwU0l: 1__ģ+_wyѣB]O3"TfFTMG~Ql9S` ̮\Cb6-\U\W 'wu> Uq軮] )IT)&FD`7T3%1:(~䜆bC*]`B;t*90~'M&zZ _𜘝?}wmOY]ӃyW򰹹\߮'e9U| ?U@̛bLjBܭv"q&7^;T>xp};߻_v۬lxxMj}{s_t#al\'/%&$vWx PhmOOZܩ*,HWˣpF\ (DE}žӋva,*YEMa0b:?~Aa eT޻*kJ׻HV5en7J`ٻ.: T3rT9m7(釜UJKMդtQe₈UD0)Ab22| IDAT޻9 f@)r>o™,n\wM3"%ΝUpWc#b@`~P kRQ1jWR;&4(s*$%M]zvf?'GG>*KKp;Ty&1Ʈ-%D`Tfi3#&tDDF\)1Ȑ]INjY$, c Xg6=;@rT9ŜӨ.l~}ryqy gg!5;'̫N{u{]hJ ={ny]עrʕ''gͯ|~'OUmw}psq]67f[t|pp8f>\!->W|\|+[OvOlȒ!!yLڛ˛?quH9R5G@dP@Ibm\O/?$$9e) rT@%KJ90 W =\c+L$;$w ?hyxt{-jR*:5үfT]n.>|t2@-aվ2Sx'߻;?9jٮ7Σ,xvq|nwKOm69eɽ<fG}z jUUuxt@ě͎=H%ѓRdȣU|FaR{..ンedXT]i=lxd{8O& 1%F.O/ £&7˜2 CQ*LKIշ?/̸ٶÐ`m5AqJlP0 3KU\Hf FɌlCn@I}paR#áO.@R*@+x`Bt ų>|<9ͦMz@ 0i29231r`+ r@"Sձc>u۽ ;lV}뉈7?_{kt29::*5$Zudb͗`ۯnnL瘐bIrX(򾮛BIJ%3[%5$ JcUX T4 5LĕgGhf"f @SR9 )^' iqB_jX&2Pd|k端O>|Ϟ=^3-(^^nv8:=A|r@`̘msbm{nzڵl6~oY\ώ5|wZ1vw{i GQ^:ϽJYw O&mG}OߓE3٬an_˥I뻋g޽{}< FCR2 i e3׾L$Yd!!b9 #$f0l2]LY`)Šudb#މ h㬰,y\!?ygʳw9J9]"$T0+=ZZ, UM6ihQ"*jSbĬ0D;&4Ε")L{tvo72FP ԶzYS7Cv.4դiN\a{BB-2"&A#"_Osߛ瑩xpdR.;Gŗ~gGUQAs=t|nw~xyuySU˜oGtp0㋶¯ǜ@﮲54*2MB/L3qӦ/^\_>wvn]k݋YJAĢJIMu .K$&%S=8DIr|Jε]ecjOთ0AYxH}mМ3# V4AͩK4_N|U1!~!|T}l׻ͦe\}v!'8Cb3W0|nszqu#W(`0V Jd4\("!f)GYad 77/(ekrL)!Bյ&U[Wynu}k I3w UUIou̒gi wS1bfJ"Y9D9Cc i!OV,MA{?[Q;_Y.˄;P7J`L潏1`na;3$U ɱ.Dȁ}&E,9jn~躔sH D1 1jr̒iUrdaӇ-YPfm6Ͼ//_?{p ̃I?={pϾu?p뇘7_ӃgϯMGD\ڛh4BЩy+RSQquYm}G/'?Ct)ޜ_n7邾.6qMvإ,/q4~H|^nlDsLy8<<ЬEJDU3i'۶|3Wʳ+55XyZ-Pe*zə#~n6lĕuKv:[\>}v3 ܬvun{N 9fMYs!xfޕ.%jZy7D*oowtҮmL>aQ!a׵[TM஧̄v+-Ѥf.8=aH.xrbRlD{#Iy%js}q;YN's+PPyo4W^y$""*T䰄ذDBt1}WVcCXݮlSfѱj]0\z)[J7n60Ue1ٴAt\R:6HT#KCLk&nw8Fwf5?Xv;"+7 500 Zyb̲"f.c,PUҐiwJ;9!S~|txF]gΘᵚQK%S2mצUJ$̳W0}8 T쮀 Ɣ)`) @]U$T i̖6K/H  sЕZ66xrzpvt;Q#YbN)gYVWvuus~~q~;W|jjaEA8&c;<9^l &ʳ/2w^3x΂!@)j;3Sf{\,rsՓ(M0m׫vYy4 f!$=3v\sDhD*'Idxp/wnM *MW#?c nJjEz\+vfWt~8!D\T*`pnG1 yZ LU Йc.u()\bKb{Bٴ)+ 5B(R#%=fZNC#6DYn6zݮɴQ,Vס+*"v`31US efUNXյ C`Z.t9RƮ`=~9faj<#1q!giP:L]Ȝ䔽 L \UDOTU0//Mc?GĦn߿f̮ib*r,q(hB4CǡĄ HDS)&I)_w>nܮw?Oß|||zr0v&T;/#-i3Qo/oOHi4SsNiݝͮv&b2춗vӮכ[\Wyf`4CUc)+2+-_D,9k"{~<|~5znv>ENfyIC̎bJWWW/g!ܞψKSOg0*֡ yvbڨRFʅ *Sm-懳D4!o/6htrd>vm')P K}أWѬ`>`OC , Y۶Brm`U,Itzuݯt8>ISWfٴMw7 |!EHl6MnٖOvחmۙY]O^}e*#2CfRQ&3(3ٲ"Z(*Y$kPUY,ݮ,"sU]{CؿG~Ow >:<$ռP9BQi4Ժ̮kQ9jq:f﹄l;fGrC $\ֹPNj*iH06MD%[6jUUr]חXd7sJp37p0,;$ Ng8t6q}nC]S$[-@DND_z w,)3Ay{[HdjҔfr}:ª !TgǗO<2?now۬M?}-f"(1W* U@ML*9d8TU>١knHΫM L'GxHIq:=+JcH@YQbUP4r*6Eϼ>xb Lttmx|0[Naq2.n֮ }I= Up!"06PȪVPdcfzެ7jc x |yDFEQ)uD,zx@ &d,w͒Tumfs8Y C3 7|?Owe+-ޭd>Z;c_nJȄ8 i49;RJ)gnk۶{"Pe"f|.TjM&s$?_.];|1WswSOBҫof*4I&"(ЧrJvl6=;Z=<>~ٮۜ2:!eUu쪙i$UU8 g%jF>~MowүܶonVHR|HuC6̗IrqVrpDu]UrŬMRHV>T`dQH)^mRLgCՔ„wv:=ynۥԋX /?P!3j!"$TL1Agz? ("P#76F9uj޳B g`*lqmCvNz6'hfI/[(f(.IMM3!R XCߗ?崽:~iVR\3$UAFD r3q'b_o?ެg%3l^Uݵ4KsHj6%ei2 ؆ ̅KGKɾ2`X,Z8#60!ݬΒ]土/g]7j\-s݋;=lգߟ-v1#ƒŗ.^P_m=*ڀ,IҜsIrN)1rea$~6fl*OgmkG 33INQs.aSWn9kۺvc1o/w!ccab4Ĵ{,d2gGL1/_{?x IDATpClpp*FP9I$H@ nZّkU"ݶ}|8 %fq^5&Qt,I1`Zu3!sRFdRqJ6CP$UiWChȈP. KC`P"T1IvLYSv̜s'A+]\]-˝SB)QE(9%9)G5 U5UcJIEyN9Ɣ VGGxtzX]U̓@L"yqvޖ 94b"5(,gS6\'ݹ`'H'Hcr& 2)!lQt7q_MД&W׻Z $545< pu@sq*Y\K&`!!ȘL $D R`̂1&)h eIh/I륯9:Ǥ4?J"p-+,2Ԍ2f.20@E5A?jjwai "8?>>*>x>7L9*fsNDTu}1w!01RJUڶ&S"jY7; #3/sNYhV,rz7뇔94'%zK;yX>:K]WbpBi ,fѺনET_36mv//gٝ!TRISL7W9J4$IfbJHLiv} )18"H)XЏMU11W놫0&prZͼ;L18ulH@L$ C^-g8aG\ P{fvHnr>=bT3;'Q| v]w>jH!Eє1jBJ+RN;Fc eE$Fn볻=gj1]u]%k.Xv$.tb1!ڮn2uz@&"s.߻~y=9Ffȑ4 9Tw~|8ҍfm|ųjc3 ;cT1ɪbEUj6QI}ۦ@ D SR0%F0aJ1B;k?mXDIA+hU"λ)0)@N:'S>гH)el0H| eXZ,ML0+bBD11 *sW%庭 1(f`TX0bG͙^Esz1&f&CQYΖ}/nMUEA5+"ϜHMpQvj5;;;]׷7jC\*BTUSt~ئ8d@H@@P }]ߛ[6J)9jjBجU]خWm8w>~s2qSxyqߍ>٬IW쨨߱0I̪%#4FHEŎB%}.U60;ފl^uxt;®eO3v = H;\O7?jJc u[㘲!)Q? >rۃv';g}8z:Y(qsɰnu\Λ*,fM]PɊ &ː&zŨ)'1Q<=98=V՜RL)D}`\MgrYiޏxNj#0g؏NƮlWGNj\TrT``8^>h)IYZI'C@PSQ=1Ib?}yO!Y> ]z"`A$N?]pF6$S}V!#fG,&I[_o$F0l uUu%G]̫s<1cCA* :iK9%(XJRَ|5 x~qrsmv]?MZ-1(톨ycH9 NK׮U + MUѠT$'MYw8ƶmbOl*-guh(ч!4vr6_Μw+v@DQ鎁((\TU% (T-O|SH4 QRͻ ƈY` =:P)9"`@LE$ڑi)WM GD|0ݮ7٬ sLQfD` 4DSTPS0PǤjFfL4dMj)ʾ1mSU3jFFcL16"f^Ζf&D/n7~)N9Ǔ ۀ|3WLHH9t[0~_ ״~IJ7 YW̧3$\DE)p4(ԛ%%O LM#3#,K|4鯝1^:l R, G˫r{KaG1Wq$٪!OH%IQP&MCcvfiQDE@ۺ9l|dG#ƕhʦ*FB4v~*5͘eLisyإf1P|sħחfyQ{wϪb*0 )THRS8Lwe؝rV5|bT\9Ksyo4#q嘝ƌ@dGrYvxٴhI}r@B:E$!J8w#$,#xѼ8WqH2wMOD/*+$&̈B@JvPҩEMDO!([ 5Ў SUTWpOv[d4 ?IK CVDh`lEb %g)Ɯ5g}olS#j9*#T5ӓ/@C'«+hcDPlD +DS@BRTd'_>|緋E @h% p(2q\d*cOE@* D"$"WX;)U>7"v~YNN9ӄ#J)?yzwzlQzz,|enzJr.C@$d BiH%#qmӴYfӓsȲŘřk"j % PIeL8(Aj7/!;w8t˗qT$\y\ طLrFf&"V1՜Ƥ)1:լq UL@D`v>8D;>^Ʊ@kVe"ˋݸ^#fW$6SaxӧOݹ{ cvY LM&R͖9/A.p 3IqE\m`Y f+@{L0sQuZQu}*TY$ɉ=#:I;/_Ε96}1N3RCU!$D6-,Ysvwm۴&8"FcPU48f.+!P_X޽ޓKmzx.x󍜥h?f jTC&bb 9a`1soe܌%{9\݌HH%+*sLa0tc;gY;oU|W/9<$OXbKXf0b)&DWJ6)|5WxvyPQ^w͞_{q>?9CLD󾝵;C@z#YrN%|`4f}}kYgY;-Vi۝c]i쑘݇c$@Tͪy B}S/ ,8]omwOe;z-s秫Ӻ9sHH)IuH550JV\̚#RQrG̢0w.?;;K;ԅPtl% {¡3W!$)rͬt]x1l,^ p >x1/T+"=rYK NBBWnQ$O#A6[")3ʊf6myOm68BJzv$dj%reOƥM!8(?y#|U QԦ|,%\ 0eK Sq0nv}ԳzCm)$0PW=+G6)xώ`wl~{.#4C5 {+Q!8ﭸ+L 몮_ooT*G}_sc=$WSGUUpν m3AvTSXO8]t~ IDATi,Wnj+ODd4Ĩӊ)cXTUolj뙯"Ut؁Ƭ`Z ιb>|سv>DH1IZ$/vQ8#&Ynt$ɌIcJ^\e`T (1ˏc;]=8ϣ ano^42;'u]!1"8vDPD$\q'9;9>:Zp9ivΗ,]~S_75˟:7u\YeH}ٻ` n؂.RRf v#XN y9yDZ KY鹺Q9DH6v6UJR4eh9veKM3`U%<}Y6EJC䔧`"GB=@2,)UDRP?dxYo;K$F5U&-H&0+BU]!؂{*L2F@<3AN]וFM[D,O"dBAVU_G~azSY|6tB!\1=9%:D] 1"b1b#NIbE~LZ+W.KNjk 4(9:4 PS( !BFl^*87 bLϯSN:ڦjN*8|pfx xV(.AAՠ$LȦi^\W ŘҘҘaL} :xuwwYw01.ΏϏ23;9FGDL@-,cJ1,@|t\BT2Q5v@TExG3OI6m==|YJeh /t,%3gɊ^?C1Ivt8 q0woi&Krhq3kTE02=iIYQ>ϐ J]XZ9y#y_?Vl`܆=V8k j)ڧ;E|4Ǒ4T d%OERԮnܼm>(L.x9bū"``^|xG1ǒH_V<`35,#fLeJJhy*n-_wʨ"2Qrl2 sQ(/&Wå%,D"K)Ànں{\9=f谦%3$.d0`VLN>4/_^+ J\@옐HؑNcZPdyYM1vټEm~]_ݜ9+QĈŬnfHв|9$ڴS c"vx{~Lc8Jœ'7 {c³w?fRD ,0h6o(g (6];Uˢi1 pZmˎ0KV>K*JTf{[z ^xHGo~7[vrrn8:A|vyԧ57e&gFc\4!74^^ހ9 +bw6{j%P;4F T DKxqUf؏ -F r5Xxĩ00;nCVz.DU J(V (XfkY_H90#&U5S!!%1QM1waV7.z\f yr"cg#*0M6IhQ":r$?bF@o<+_~=yn6x>1`h% k0"bve 8sZ!!!D%KJGnRQ7h]4]U::*V4rR!)"1$UR٬Y\Ә~9=99NN|FȈ{z:S,DЁQɖn7yrܦ~flm^~׏qm榩}~ |n^}m^ )ia޴~O}NAOlL놮7/į~k_ů˗/JK ]@+WѪ , IT km+Dn/C7c>=^vMj;~%Κg?^.L5 %f(Wyhlj1^8FEHApgހ30$oh@0k]* 1xUSS@kg ;&D&9Ž;7zW%&Q̴(t6*gM+fh 9)(`:O屟rr&dI jP|/Q aaED)"G@_W[>Zk"'NCM &d"NntvTWE/Ee#SA:08I٤i@1 nǔ|Us|zl83{*dnZq_uR"))';N1Rv,# ?~D!xb{gB-XC~#|8-nCD+*f48}'g~ۭn~|ɻ}ucp4oC6yZv9lKH-1q1桡0Fwl1sN3RIf7?__mvsݧϞo_ELK_<|_Rq1] {m!UޫdIݐtUE!0>FWvbX2{yyYw.oW rrCJ O*:mLGO)Fz E3 Yp>1;?@H-LLd@Py5ގvZ̵{l`%uSa}~[Up]@!#AJ i%CXj\9bO@ iaYu}L]7ZU0kg[{1 {Ѥ/GcCpůW~^>Z|𤔷i'<MDs~D@&e_De/HD HSdZ˕21 Ƹ^_Wzyz^#,%PB˫-v[HD;Q@EN V&Yjp]/|U",0D34"x~솮2/>|yD^_x1J UH JZ:䈘RΥVO}7W1%`&vYmz\yg!oGoGO~|vpssj梘-o5g"uC/"xhhM'& -33qjs_r>C9!&15眊Ld*|} o"UYmy4GyTAauk۽9g>uX ^Ja`LIL&y"2Rbċ>ǡ3u0(泙 ,ElD;W!D{+C0ˢ I꺾|^e&TL^4 ^?>/엿Q(nROJN ؁N pmceP:PGŕfYN1x)K$r7 ? P0hfUEU4`_WiNj~hD%Psa1O4VB.r,`*5U WBħ?SD٢IYn|')&|U&ǔSFqsq0e3;:nߌX71sv A=퇜Uu[3Q!DIS3 W9YN) ںq\\1g"r:BU׾lh\_gMM{qvͯ'7A CG99JKhq)[7zwN{f?DQPlZXuv9}qӶ=lrVAĒ%l2:ECd³+AYr"1cʸhË" 0p0J!"kz~[;\Sn|4ιMAHMv(jᣫ˛B)eQn:Sk絯+O@E|p!"zb8!Kُ}<9KaRauR 9{V?+Td^c{wcr?v#pL 4(jz$1,rO;,\ET\]@6iWޅRJ Ӧ%`J#0Є|݄f9:j6[i3^ޛ<[v]g~4yelHER*5$TTF˲8B_QMK;\\dEEȮFH6;Zsp3$ L|>k~ߐ~{PӜgZh984L/a9wM])Ųu]{8L=ʣfJIFbLjLC`"Pu*f)f;=Yy6&RD( mTdobvtὟ;M7=;=;ywo6}ǟ|@HCSs3'%i2@GhSyaͿx2N%OdeÁi^Wop\o[[2K`+(&5 lfGgKR%j' 712v \shT@7vOEPmcb $43wjJ9.[?{c AڦIS:&wHQ6eR CLy$WI#ńMV;PJ w7zᓇ$ vӬ8"IsG戯D{Nh,80#lrdeshh#4[u'$mS4*̊U^q)˥fѶtcε\.B 9s~{x]d},>XӢI&&mCPqn%eD)\ qjkaiVXt}ӌ\8W B6nǩ{d$D&bNHIXPj& h%ez}qH`` $<{ogMwrqo7??~ç_'ϘU[])xV9UfdRmΨ"Mpg3ʯ=FO8lwN>{}ۗD@fn~O>=zVԿpgr4" ɱ( B^0h@5| G2piGT1wN%g٠T ݿ//44Nm &/J$"#! r"abaٱ;Q*j߼/&~7tϢ Qyԅ#a !p@?~:wGTUFD }[q gA"bsZa$d^f@@DP-"hCi }#FiiC9@sJi^F,F K=pZv mׂq!R]7P{f\!,1H(E774awpU+a xyqXNNԶa\os8¢VE4yf?N?m0 m۾ ,B6K f%8l^ Jq5;=]v}L\y:z|>AQx}Ʌ|~/.9Gwqw͗}G~ŗtTP 陕DdB!P] "(#67^jJ Ƥ9;=xl7;B"#bb$b`ݻs]t۸ {Dԋ[Ņ0A)pqWb"}ˁ?D@ <#hܑ%k` <6RwBL8{_RLĵ{Tё}0`J Sם0Pfm׶mHnjTH 2*DhMA8F\ow8Ki:P70=zxVR4au\BD6 S#qJa3WƁrIiJ٦aCB۴Mm:`ve쮮qrw &F !@g1ZJők y,!5m{rMSDD@ZJ^'b\ou񃏗۫~rBlٙ?Ͽ|q18 ZN6/RBd(9ϑY3tOqj4!q5"LIld9 LL֚rM oq{.Wx޶ls<ىdH/khٛ_{QPCY.oV"zAaȎ`/4drii7h)!W͇k5d6M[SAB*$K8g" M_|yswjHDLM?~(ZQK5 \h9~E@RԳ ~R&9q[I?kY,* 4kq)w1Fi6"e2Wg>B!Dr"bY>0K.!n]EIyhQb!&v~,c}WBh'4d!Rt{)OCMb Uno4t9[KIPۛͰۻf-b Bh`sdaDND1 RJJa!]lxubl#!AK4۩95}pwL<n{q0}g}W0Ra׷Hi0 \ 1pDhhN1+\r΄]ԧiɵEEYx/?ڦEsu@}soLݙ)08S.=F dq90>4mf1XrrGrF#B'w7uBhZnnc>~wyܽR蘈LH&Ԧ"P]Hj:Kfӫ+@f4Oi/_iji{98U#brv?zW逸y^F˪|RRA"p.q b kk坝hb;#UG+\W Anv{0-)\Md6 9XιbuMn> I*-V!1__UAZC#m80)'#'iHәvmJ~ RcI98S d?\x!#DMچKb LucTām(2O㘦qizƈD$ !`1'BL%^7ǿ/O4mw^ksgPaL=>Q:$ IDAT1 sѬ`M̍jSΓQVO!TOc:Y/d)iČV.Ç )@*v- :__\b@@M.{]M-z  Rɯ`ںTinhEsN~F \,ϟ_:!5MA&dAs$~4`5n#DƑ oY8v5mZµ˭.щу?+zڙ X+:Gt::0HliZ7Smh6Y& U kW[o]_\#pT-D2MՈ5GĦkŒY4iܜXB}U pbMD)'w_ Qӵr%1>| iZ @MUt" 4 'j~ !>8fs=diz_6CYK18 dAp UcT$HɖƤyƜdm(Bc1ժ D*]X d Z)B| !tn:4ai 1}Ngn@QTjkI:qGu(9;0RYЊi݀½ nC |bd?ZO7]234/\-lhEb pqK,jRzC%ۂ0,;bB35e^E@%z5E:!~Դ8$wm(`a0;m<4#218#׷$8R28o8qB` /zcqU[ $,n4'_f0 D8oqvk$ZUk.s f757Gd5WwgktƩ$]ݴX Q); Ӥ]ߵM64k^^3g:˛iM6I$4=Ni0%`bVL+;fv*(Z,eiq< yҧ|o=jĦ7ns~zтm`!'03bR5&^B1MC>&5X,~}۾5p%w‚LU\W6rM%#A,jF@`>_2q7%)`|كխP&6Mcr[OMɡ! 9 ( p@S)cAFb.9i03ʧG2՜ڈ45;VP=0Q׶4ßqsu?+nCSJ1_z O~uBLY??//ݏpӣ́b p1`7;wKrPsrZ`s) ii,ܐ0 |oL7~\߿g_T)q^U[Ne l&sZFD/^`jp#,V?W M~ɟzQǹzMG\I13Id+ȕQQ2s|ΆuG5CHSF46;LҴ͍iMRu` AE萨yJe=9lwfu IMNW1>܊yقojn`Z<98iw!ԦaD4\HL}6Z?p(dYCh|GE¯ǐEgkt)gGgAZu|/бk> /"!V99noSsLb<)U-IKj+[,avض] EonRQp_HJ)%|0 ' *A)+M-DlZDL0q&!4bـ{w}dssԸC7%b@*0#W ^\^~Y  {PCQH N/{^Є 0!6.s,E 8 ei_ :cqK3 D Ѝ0FդwY-D8h HBS) vj'}>|GbzhK?;}$NTq5Y>}pl}dpzw֮XU1iS%C}ȚZ92!ղ_8rST!PuS%@E`d̃ fN@GV%DN[f!#={YP)z?\WE3pU!@3ۜ Jbj k}!{ 2֐8iݱTiEGWWS4eS +Ic,2஦NJ) }ߍ4NpHYB0 S*ﻮ<f- 5_d7A1kD{\m*0McN jp{njSR.ڮkDՀϓ:0 ^\w4)0PD01F b}jsS9 J1g!.NHh@€(MK.U%GDVWu x 3GCsrL`)WUS1@2$jeәTB ,ـC$-4.?|t}~~rص0wZj2f ԕ? שiY}F/78v ~`W3_mƶk `sZlJ;4},)CEΐ'מ<پ" W Ju+|iT#20<:ho _~{H$Rh7ξS%CQcmSs?3'\JׇjɊ: Z}'"jvl̩$R]L݌)Lh%yIYSktZc% ! #T1%7&!"˞0 'SWWSJw]ӵ0lonł$]v0ѸH2\+\  ¦_fCh8iD7piT'M Di0c) "jD( Y`w/ק++9[a7]tvfcbL sI^i%g刂;&@c:Y1eRbkB9&,*a=LH+g6de',#GS&׫Eˋk!'dhD_~EKAg Ih}ꫥk?QuՆ۳ur; Q7j,^f^HhanEbD\TbEXtU|!2-j]UhrNՆcLOԴZ.($GpqZ5m`c]diҜbh"a ayDiy*O8d8"v9tK6 ൾg* KJi9#a B\ v"ӛO4ɳ-]M :95 <; dͳc6d7͢bCv&ivLhl@Dl`F !*L/Sp0 j9G+0w*Ml#2GF"pe]sNmĸQܴf)qae UGp! 4M]]_!q$8(4;֠r餣d_9Gp'34?# Di9Hȩ N;X`pmݲ_J]5Nim!1:6Jp$D(U%$GW07@sg[xŋf'bу;ZzX7G"zg)'@]2S Umq M~E9 :(a*E)Q(V&89(! I5G @_/.!y3c'MJ Oƶ%iDi"~S5MYkf9Xm^c%"̛>p@SS-6c%HI%kFEBsok?]!-Oi-.ZMGpIk9KUӞ&M`Y4/(_#i^"&:b 0 "]r~29{46A49?LM$5Z{fs+Tѝo<ZS6聘#?ŃoB*]5 ! )30xq3j@ûZSURz} G7 1rе]\}E3s$a&BZ6咘qvVT0\̭*jk9s율@jrqy0{p?|nbsΓggZ~u^T_gTsɕTP$R6`/DR aT eN-ZD^յmh1lm9v-bl"9Y ȕIg,*92 04q{K8j\s‘&9y^ɿcjsZ6MMl!:;*PׅsuQ xsNiV؏2 $`TW@Ma\t= ǦAۢ득n%6Xs\`TO[b Y3X$5*g'5T77?wGWnPDԊ1 "c 1Ҽs7'Ë!W%= tm`fZ9i&&e96Iə ݀g#sNys};)wk\ ]z0U\4]ӶrrΦOh󐢦8/{\Tz*vEXoon0kܻ㋋ˋ˫a}k>퓧Dw: ۛDjPQj"skD0RB^q/qnTs !K)y(Lk%<+ 3RJ1IaBhgĭnPhXqVsDQ)`dGВE86að+) B ɪqkrbA4M_Ʒ_w~QKqDQS&0(;C+I7ijV 3qi@,@piHP#ݴ2{Aas85TJ)rˀ#;|Uud&+lšjud*VFjN@h 2ÃxkI&Go~ jW1G %;wkwh3>j8i35PWSp%Dv̩q4\44u#M i[|7]6_g:`- f c(|y3\ܽSJ)uB꥔vjݢkvZ4fCFQX5D Am$!TN֫뻋_\^_]nog+ -vWBsvX~{suuYjV`J`A4v7N)&!n)]in4M'KWC&<dGd75`r"g03M'ӢiZD@4#3 ar#8d 16þdA fP Ҕ*0쩰nüu9ikva7:3b:s= st:O~'+zDZםJQ`Tq\9Gb\D[Mܰ6axEUEkr-tEρD]bDo냺Rz1}uy9L6MzĀZJvMF%sTpdrau}umJ 4ّ_Puqk0ȹZh(5 +@Hi1_\Ln}uB`˥(#Mi eZu &_-0aq4S[j]U0MØJ,4ml16Զq6exzDn¢dL@ЀՙTS TsP@PPU(1C 50p/ \$jy_M!Y@Fq 0u(i i:ӔmFv/x_ޞ?/E1 znoӲsR}`QSpoX4ꘪEG`XfBp1 bK1cs//{7GTq Q+ ^(Xl@ijMvb 4d0"zf''?!7_.|NeJ7[ ?7T\~td{ijoI6JI1ƆܟMTSRPih.9"p/øAE IDATK)Akq67i<{T-3ĪbZt;p̅P݀D!4#ّ6nΝ;Z\l'ɚՌ4M o'<9OE6} 3"[A: a,#(12 .0A4 4^_]{w&DI"Ǜ ln'gĀ; ՂhdXӥBP͐Lj!x4R/կFVǦ:,e$ djv7:@CZ ŕ8;]onhу`O>㟞 ĩsb2B wutش$)aB @({R*nyuEP1 t3i` ^^^juE l0ewD+;u.)G'‡+9W׉a Rq^j%b fvr2^lؼ3wd,! 4m,Oi; ]sr av'10TDs593X}DrlBdzwr.V}y(@gKQ)tz!2R*>NfZBbGqܲ;!JE4f3\\\"bý;g@lY@4YgR-EK[ieL ):0+V@5B^b%{ѬJsl28He}JY10.ONc~yo7=A[D>:) lno?_o0Ct0 :3OW+R@whS0EK9'?ʔM9r 00~uf@%%E7/$Lymc`+ZUӺW@wR ]*~]G RD8vcy @PF(d5wZ9<VVт3}b>jud"dzmKT5Ƙu'MG9dI,ٿ"E&@A ÐX$`{cbt̾ktn9VyI3(uô/LJyY):NӰIV%GdI7kYqnv# si"fA0b-1#Ait88zDːƔq9.Z4zeH82?ץʯշwCZcW~@ (2唵hU]% tݍVSJLJGs#$-Df^,,afK =Njm$<CH=r^74"YoF,jp4$ #dDdfwʹc9έ5p`ᠾvŞH4LJǃp1grsX_!2/V@w@p3+,BY( L[]VW#u^k+ĘQrFbA`=5yټE UP ##3>6BD„ ܷ-yȄ?W~;/,1$Y w՚1"?̙07C0$"2Nv<3X@S w Npэ:N9O8Ld^DJ)jM%1E]leLݶDs3c"5o9'֪Hs{sknn@ai Ñ=?qtt:~{{{_uS="zCy\ K"3IJww{D'"3~Tups%0070qo?CRԁSgIz>/7T͌i;G4g=-!fa]kCyYi.m{ ipSv$va3|8y1H>OE08m'\Jp&u6ܑb=e$njZ0X[MMεۃ0hfvG$ᜳD KNj5fkỵV60d73 Eaft|tW7Ϟ='rGs uΔ c" ;ޚ.bemNJe3Mϟ__/xYZ^5oG&00 N!IJ7o=s}NLa̽v@_xgijzx?!<Pͺ_a`+É9tӧ= A=T Zi& >ٴ#> ^eIͳgtpmH91PØo(v;~S0 Dc"s(l;gS~!=@amm.v}&<צn9ֵTlv,t'J̒y蓷)qY%Q rʈoڦn.pZl]H=* N:%&J?ӘOyךjaHr0հ.+!"",Aɡp̜:% @,frB0wW7`1Ԛ=<"8cmM!4l)>,tqս%jZ兪?<'_~xx$@b`ڌ pB Zʉ#,,Rv>獇K|\žэ dU},V 7766apNN^M"6v5w}/} ~>BT3 \{^4eܱD@ gq =hEH`f]jKDyU7JDdvt:KNzzƖIʛtq}Kaɓ8 wMk7//O8w싿5dULDr d^ BRH*g5"‚ { E}wͽVgI<048 .b88i߇g4=tO1<"1 CQ3 Z8G;B#pVi^`z~J @]BD) 9[5unwssYt\~ri3{~|8-|86,׾եVVM yF͢:: 0"ZQk5irk E둑NrGL8e۶n,jmM C"0s1nx-!Tw_}%5 V$eY fNkmkK_)O 2N;xuPmgk(I sz$ |Os)_߽xqmRFp`Aj4FH<\ n@M2'@`.ñ5E$A`fQ09"0 bjq0| l ND._|sww֞88ֵ7C:8CA#'!ppr@T 6#6w [ p@DqFD #82}ju]=Ί*Bݸ/ G#:2~,~>C?~A~*?K.4^^l7AQ|mq}ݿO[?p(k~xB_^__#ozVi+EY$0qE@usslp{ZC^S21f֏W[?J' DzuyS2hma609z+b><SgW)e$a=I"x< @t<-K)e).D]NH)%F2e>Nc!;󂵩YiTwU!gI@}'֚ RB~Gό`X 1 VՔ!h]Ijg$00r9qC4SSU57 8'KijpXJ@Dqknl6 Cx\"`"x|"Oӛ7owN, fqDb 7U w"Vɂ)ryțqħt< <,g:`ME%u7pv47_ĞwF(άo~wG>;w`%\\(umu #vtK_",t8lҐ´ 8ØSpDTSF@s sӒJ= AQxhmښ6lj"evy^]_^_=))J i,ҵPL=08SO}>4zi mo~Mg6tMUd`f`˘WO1==@3eA0pukڋ*)B)KAdH4N${l^{b/)i'FVU dٖ٬aXR5m jR;3판ܚQ$68E ŢfIqԦWgJ@f&/wcYK7}s{,L x~+_ֻh3 w 9 5b,%؎# S8˲=Zܣ8 Isn8=?0F`RSRs/-կ~~v?)cci\)1nv3D ׇA+YC7qN,Ӽ!]]_楿)DhidDWȁDĮ 1ivVً)I&滷_L[ D8uA;f@͛5fB %Cڔ"TR w%szq}] iݱd)NQu-A,RՋύ!QE?7 jX$!(a2/m)vjE2@~5y0xE?˛8/7#0"8R81qkܝp&jF`Ȣkk˙p9x"DD n iY1C+יQ9l7/_˲0xG38 o߼?__ð?`M331h4rvyfv;aFPtBއp Jlֺ6i%̲9"NӴNHwMC4P4P5Dn u]REwVXFGIHV#ֹcN:$ ɴ;dEۣW޿6d$&G3:lv{DˋuweOo3/7ٜ0f1 ` ZN$)x˩~r{/_X9gH#́RPb & V en)6ERj1'`EýFaB:U-kaBLմ5SBfd3ή\޾[$ tDN2"1_zuoVa`d =Con6 (}O`)%L!DP)"H;LT#EŅ &t{=SiӶ}!Z^ {P0τp DZfy "t<} _ķw?ClǔiSV׳( \;j Ovf- aCB .kR6ksU/o"MI2'!vP02ř1 zwL[Ȑ1: [t BX]CXsՔi\7헾!@9@[/'D@hlj@ 1Z4ǯ?)$~gy#՜$SG7CD(SR"QΩ̵-+m uN@4`O"XxDXGj`%SQ= æC@]"yȒX"b(@H᎐._ZR9Vb7AANObC`ADDwoͿoҷ _!R[YZ0Zz" Q8 0 CVbj~)qv( TLM11"R"fCnEw1Bo|_C|zODP s,"<E%˺RxZ(q3"]^i-X1#{V>@2E@8 Z6o3_ Pt!fjm4N8߿Hz.#Z[38?Z'F{ci \]9g ZE #Q|50`̉1#{{*6攒 z8?1"9z 1q r8zmNA] YXr݃" 0 9V\&X 0)HLfSk >~?4$Ȝ#9XR0`4 ׹#cqC !{ Fm 楬Ewxz8/_\u*!JJ()4Aa{vL&cmh}_|r"uXj)nDpeeDȡv=>:_Eѻȟtة6HWwoɉsq/& IDAT"ـ~7糆O9'R J$aiZ}*GmwC0H)!V%S2Y9eAL=hLM@HM!oxw9W4DiW޲Υ6"iۛ44M w88C-e]}n8 H} 0jz~=CX.k} 9*g&rv&, 1w6:r/I;!8b0s@H10 0;YHDh KKYpa4Hlpyم426ّyLL T3N(%0npRnmxf i!yff@YI] < &'bFqU3e@F,jm>aȀa٨YFDz\DRNfH(hn]__z˗?|GR|sZ>R>W7ooI`sNp8>yK7Xb;lEJYO1<%NރEBpNtNyh8D k;q;> '7ok>m"D2)at :z>9~Brx|\EeDE=A<K6O{ 0"6k]؅l%G)˝ 4l:/~A܀S9a8h-4TҖxe܍ B֦ LDL`P:Ok`pOfxEY L݀aDL {ڼ50u̔{X) [`B@wBpjAjHa߼{˯pZb)7GaNy~vl ֌3%jF0ML!Hmd13`n†Bu1OY{77']!.z5" ^ ;  A򈔅&P}}'ϞHJc AcD hNV1BD2e'0ñm)K*.KpZn'DR4;!R"f]M#xNTUO4_n7vxt0IG/K)K1>晩8/ e hPSN2JRFDGV0 fB]omYHL$栠ZZdp<7ĉk⥹KחӘ_l5 ErC~nj< /.rBDȔ9v;Nǡ+)13`XrG{himn9ݚ3u9\<3<uຬXVu7NB88laZ)D V́7n˲e>!_\Ͳ@ZkH^.E|+h-׿ul%:9`=(" ~[$hN9tյJ(Tnj^T?n7.zX47WPw FpHnS)(WOT=4z}}̀jAM' aa( "!X'!"t"q׵\?ye" :BcFȂQS,8'Dc2'Be2B5Lm>Ϯ.9~3i cDCpJYJ)9v(k]NeM&@Woa4b.ev Cp23d0H,O$l=NPbE%Nu7W"zsgh$O҃::o_9Nv܌27"'W )Nu-Epdx"܁{^HG>8r-rZ˲P) n%@;~Jཧ͚67~)|0u&F)G>CHRyfm"teRzBE tRzLT׸}o}y{wDCYxW_^5n^yY)A]L ёYF]fh[|\YB&HODBA(YD?}:jRϟAi\m9-t:Ȳ,S)T I*sM%YVX(| wg b]ohX }FrűY|~cB PzF{}&̰X#)!pLedLO3bdTE }Wt_Ȓs"R=KJDEu?ye2'i6W2wq3hNJpɊ.$O'@lGWavywޝ T-nMJ#>k֘3qEfD,w'2ii+"P(h*nv;S$* EvpDG"")D3Ca53tB.0&d{K$dmscq>}8hm%"k e,6Wu(2_j~zsբ "L! AQR[ib.GS9y=|:A0T{Hty>[2oO>j͋6Z˕fn:}n7]vR ]iR2@j5ۜ9PMj.&פС YZpXbڎ7?`FX#b9D4Cy<pOJaEa"L8Cs<20m60Nʺf)2 x tK4"k!"6-匌a]kی|' m]2cɌ%AL","#(SZ͛l]nAZ"1#)28I6UKKԩ>)m]WmqZTaaV*p< , BSYTu9ɪȌ0LG?7/;V?}?Ѻx2U()3vr2ݳ]h`JVJ `G&_/Wu˦@ v7XHP 2SfFooek"l$ 8L3 ,F')JDDP.̬"2"1k :<޼z$>胹8;o(DUլFryzR^ psPxg&qf8zg$$: woa#K6]o(a6GA?8L@D$„0o*K$ UqM)e"G}r.bє% RT3$j1:;s>qRl|5@ZT6۱xa1Q 6{zw<.i8 & Z.E&)P 1-HJ/.T 2LEDk6D;7o& aH-j̉HDZ_'=w၉ٍBBRH %pXB[ BfOdFsy˗;}: Eg0p *eAỲQRXdwݯXK Ķk(Y$zy{f~{Oϧ6UkI2 snx<ӯR:*2 xl%!=lG\->;H!}鸶tD0e]:[U\}C3Jݐ03uS&z S&,p 3e~h)k<Xǵ-(̼.>[4k[ CբLS@e=h-xZR=^,Zۻ|F0v=L|8ɴfwYeо "$w6zFɣf֖ `aB61)qQ]Kr#3ӸX^Z%]*##V%l5 V)27U}<t0??GwWYTqsGf'zGv8pFPv#q}}~JJ,a#}~!C\^ 5ˌiܼ \U6p(כ'O. &4bbsQ".څJJ o^jW,ʙ(H{I& s'""{Tr2C"()t/~j)̞-2yf XK*!y"UZH"ztFBzz01$nI}jD A9'KR5 avsO>[_U/]?_QMr77׉dakp,Z|DH:GCAD. $b!C;gHUUd>p(uEtmvi=p{[ a-GĬ4l7,R6%sb*C)o$\E#D8Բߟ&,|\$\y1cQ@T/p\/nON25\X l)24(Ey2’8a+"ml0Ln:R2)@!@*AĜRXbMYs~UBR+ 388_Y:>~r3#z;Czt\61)=MVƁwOϧ{1$@\ 1g.,BBJsV"xKb gE$Hhe0U@AkNkFd C>DRDd5+Z[Ty'wA_8/O~웙 =H3ݚȈP!MJ!N!k}yq@tMzO_ޮ6%uզi !f./_1L$Df_}9fNd'~FOJF:DDYZk uP)\dxϓ==raZ[7G&Q0u3Oi'p;N?+L s:NCa!avQ|^޼\orSo?: ͬyoCX.%ikCݎx&SZ&@Υ2Qsw[0(B֬k!fItxADL;tUE` 8N׏vE(%3G HFD2QCG9%YA$)nT%H<*ޖ`Vwpc{o}cw5%@$Qi8 y[ã5dspPXˬ=sEQAQR -m8|#XrLlV+XY!р,*!Jޜ`H}ej-}QG]u H = "C\ cF#"Ujk~ב?_zRaqf"wXqRzz&uަ.5)P<@(@82DDū̮nq,"nj-Z:"2Sl բT("Op8)XaDϟyͮr\.DŽ{tk" JV 8Fo#ޕD"LtO0-d`a5 HiY ,$aW?a(=좹%3{&GQTP3N39G`X <ٺv8벨v3DDxC82MNFNpX^?'7y,繭r@KAKLF)QY.eChvB[N4 Dx3.L;$ i`֡y9GH!Ky{%sL)QS`0]$SCq\dk;Yr蹏0Z?Wo!L6@ _ODYu8k)yR8MZǔ?W㼽9Ti D`Y7e:N?Ϗǣ6UA~;_{3-,:Vd(Dܥ4\]m%j+f$hʔ)"`وe8U\e$^鴿˪̂l\ 'OSq4Q\z/:jJɭ\Z6ZX}>P뼘Gc hjLځ\9?vvzwhɓ*A-(.zfp'd&S(6EYJy~^e z-:SzZ`czۻfn̅9*Jx@kHgK@a& gdF4YKaqњ6ZagI8"0O=)ZQT(W_[+۱EgƒH3 $+spL쉄eI&a܂ `;2[[PW4# %ʢ$*h2Ke3B~kO?|;NIfkl0)X2sYR3!܈HIɝӲ6f #.*ªP6|Y q ?ſm!a__"EbZ3=Pwd8D_- iP~ꗺ&O\|423t>r] LPN5B뼼x[nC",/MB%Y fJ!$2™A x[> 4DPƓObmjZt%WM`1ksJX~{w?+f&7o]E2#2(LIB2ܭ8p$'e:E 3Ra4?OW%K?Em厩ӥ 3q6pq҂Ri [z'LIu*e:V_7ww7׻i6_e2qޚP: Cr׻g>")1).$Hʤ4 &*ɜLsh CDnyׅUPl9v|Ou\NU%Ovnn<-a\=Aɬg\ih耩`@ 3c҄{ "N&ldQ+.,l: ^|k-if"=S 83Iƒ2Y-:ɪ]ܝ2H i RHL^=R@==~P]Q gG3=[hw!7:fJ60LSvۺJgpnfe^=-<$ SZ{252L%[D駟~6#/#PwծDO?-(PD܇|@td2"y00V5pZng t.gS(Zy}WǏnϟg?$ "%r<}[ LD8yS<+Y2pH"<2JZrFDjo~P$P4T1ks[%HaNm,rAJGI\<2=(؋ K!\V[="D dt}sɳ=Xa㽤0w$4!¥j[3 G>=}bDxjtP-lDAN^%#HY9H~ ٲײrC$Rx:onvrz0ծ&:biӲ?h)Ll ZR1FnDZ鼜%nvk3q*:ɒfB *XR|uER̞>}L*:&8{ :/2;xwqUgVň~;mɲ,U|$"sR)MIȠ ~6ma۲3^_a(_.oym?~x]T,~>~aI@HOk-0֖g3"úIJGTs2 ֶ̫:0'yu zkTbOyA;R1#9&9ZK&rK!X$#RPYH 'ϰ9?~LYXaAz \fv9GO>GfB ?Ч8EGA nHf#iX]dK]^w7UOIUETn&%z9p{+`nst^(&Fs vhJKg.߻ޭ%.ُZ*)P;+ h]vyi ydk͛yF5K)mGbF@c0 ænZoo_nVD%*l_ ܒPxtp:A )<`I P$pf)tD*np|EF W##K=1TS4e?\=ڭ\TQLd#LbN`sU3.~/p^X pC <ܭ 7=}4 s\Kh⹧C7o2;%BG$#7zIf}R@=: @D?o}Ƶ~9 Xd2hif)uLֳP7R04 3;w*f6$Ĕxo_OדD5sRmm}gz,I+efwfֽ> .$  @# "^=pHB I v׽*3%"͖Q5sN6!*P5Q=v1fo'dø p#ηda` -l% 'VgU&;" 6$L󳯞 mRxS]Հ|-g&19eEɏ$:":0 5 "$z@S#%!eI<r~yZ(=ye#:&Z G5E*TBXkJ hS.< 0fS/365U5EΝ1PLݼO"3lO_VoÐyA'Try&$CMLD:#\$sM "j&%Ju 5%B T\6lVVdY K)eA&RP~$:w "z( " $R,|/~E5!4>/@u]$./η2%ZԊ׹ϱ[G%Usk<|e9G΢MjM 9TՕ7ATa??z_@)vapՈ5(;yZ!20fT[+ ! br lHUE|U- e3Ylhl:P&"=f{g"Ф* ,I "OHPJI;L12f>{<7Dufּѡj rC6SE%VɈV *f$F1KTAlb]P΁ivY{dR6s?ۜ.fo\LM+p}.90&Z$cs'DUA(O+R`X-A[L<4S ÀDQ& %<n/^q55qT^ϭBėW77Nh`Ealsva(J6ǜ,ѡbX ZZ],IɃto.zh,] z abf:c?ٮ4+4X V@Dv34-'h>2YWK5w#(Yzőʔ5|b`Y31r\ m2 NK87UFxu<$Br"fp"lN}J*%j%hp7oPJRr䣇ζ0iEU!"e\8 ܽ@!$٦5S($ 0F&L 0MjOo4|qi oJ D֍St_vT!PL1 %'26Um!kcuZObPXWR)h$}ͥ13WϞci e`6ÚR7sx3/_c6cKlS[͚r-w6ު&UaBb} %iܰsPR wTb=n"}b=}㇓YsPGd-[ɥ䃏ޗ//:܀9$kHs*ʐ{X(̆ah-H!e= Tô9d3C9{ϲi9qڜm`DMRI\zqRJY޵!TȒAyxkNGl`َ.ڼӴZR"DGm9]!=I̸=z[HOYk-YD8C [Z IJJANyJwa-ֻo+r_sIMbF]h&om)R ^c=;짲^l6ydpz6 t7`#!K1zlf*RvT߼/>l "[J.e3^`PDt3 L źͰv噮`3$=@zxaqFs2{DX*(b EӶlc圷y ;(YE$"5Z @rSN % S=, PLDtb@TմhГbW<ٽ=]BU4%# Ccp=W?vc$e:5%{rn~\0" !"P1QӴMsؔ;>15S#8lh/HgZbZ>[F.$_<{V:9eg3Q䚜j)Y w q ^uIJ6sAul(8l1dI2w8s2Cw]-cE4h2t9uZ<<ͪHX6OK·Ʋyu_t,*wAUR7?"8X  hj޼~vl@pX<~4n6hmj@6D%HEPPF&6 5ڐ4&PUO&h*T5,,#:FXO36aw7ARFI)Z?D` TE%$`sO&WBU!XVHEX2)i}d%(ɦb JN͖@(&M%gwP'XPp8W?R|{"ME:͢wxВYh< Ua9HٴTI-]Tr2*3t8:a5CtޝoůTp)<}RNy??y_4+buk69#Zī7WOyay,Tm[\J-ᘇ2lň78;?g.;OY\f^ތ4=8abZrYś3pvq>XJZ xR'y3 CA,'SSU౿dm JtXl>|phMBw$) TEDW_Tmћ#)-uU"`iPU>f֍SkI9왎.#˸9 ?d)~ueM)?߽z{6BF#vZz'_y}7IteY/ zu}Wיz²D GxDD@S)F$u@&LS.b^pE$ZRJ)Vؓj ePIǿ^r(߬Ÿ".EosP/_<'=8T !@ Gw3M%Muu9n8n1B C.cis;vkXl@(n^Y ]AHldnwu q1lq*aD-5PKgoܹ(͹5Ԧ6UK)詞+p@yVfI*8)R[ LUP4giT!, Q+THaI)n?Zk8 !EEH(V(HA Z܈ҫɦ N z8SNIkLUϗy64R5_&+˫wgwܨ۷"̺N"z{% 1cDkoB`M nklo>x;c7BTZmi!^WtX8C*gɌdTW['P')<%p? Np/_Swa4P*"ǡ=~_g;,oc}̾q yg>yȧjP{dWqZw,*)!.uaD)tiSJɲz-W//mR GRV~qEjQlN1H`P#"!ݧTɒF*"-B[t #BT)ahѲ*nו,c`'A46˥ο/@eY@RV#C:XTE@}:[q Cc eozjf,YN3 iRՃ<짛]r8͸yY*J&A3Rڎǃ@辸^2A'B^aqo߂<yr6p8cE*՗FpZbHR%xk?/SE_O,%tu,J9MTO;Rj"q\b} V`f ,ik/WbcK$4wxqP ̺a_DEENgmٝxvrVI>CTD"%h]OӔ撓haC9Tr*i]ᚔ:irsXjk} ~TNť:ȟt ͖=()Xb$X&P!|FYJ-m0R6`wuY Go|`5eYD4H44tq^laHj ˮ\f3YX4q_8O 8"+ybTzPJE Q*z!ѣED_o&,>*QѮ%!FfR6CпX"vᓏDh  Z2y9e[d׺}/.={5]ubDpyjRr.iNږ%ڮo"zY Z#x?|;iDZ0gXDl7CDeոX7>ըYAuW袉8vt`ۺ{2!$Ub4б RX}g?ѓO?M: 9(Ѡ"Ph1m7|eiΘTRw)e\^_!f-2"tTN-*T5" PxD7.=KjfTp?5Yi=PTE\4BGBO%"^xzD<;HxHm × 4k3]z)}{u+JgW/^W?qy:-F:I\ £5M9u>S_= <߼zG~a.&j@I jmTxs;;?SUM:#"?n?9;=(PQ)#yޜm̺DkvAi|hL%&wI8%Xp^ܛ~w=/4^r'm:[@T)({tz{0*>}\0f/˒KbS"iIn#po&ƈVE˿ʷz|_O}M.o߬e)~Gy/,H歹P*Sjw_tzܻHfvu^P -;ֱQ[DxD", vdmR-x=n]|ys?!/ 3WNM*4)y UX i+C KU @EӬCRU҇ԞQ j$K0|t_\w;O>lfw#bW?'wW1-o`YO5NC[z}ߝ@;a^DU$A:2"""\(>KXA5evB))&U>^kU"._`7=sپ{7FBPrY{OVỶp?E7xQU}5硾`oiRGM7bt>U#@!3ŅB'c?Mt->ͽp8Hx]7S]2SS@Io}U 2VwfL)SR>"O|ܿ{g&8!H2TR[R]ݼy YT ]Fqwڀ\E٘![D9e0 SJLisdLLiWﮦ="ÿѣ^䜟~4ԃRDRNVt1r ??7sjqr1 3 ԅ I =ZW~4DLXDvSʺ8!/^<铏,\fٌu/^v^`kInUKzU5@dQX ʭ3SD+V*,*NQ-BUr YUvJ3آU">V%LY,jXN] Ojyjfן< 7r%n$ITIǹ&ZeZJP&Ka"$ZG N <jhfF$E/] ޺Nk4gsh( *)0ℨԥQJ$Ya W%n8muy^J)E:]=ʇ|&RjrVo}Xj} و>wxx*SZRdO?u!j[`D,D-j0 \uיJ`I_|իǏ%)ة:m  CK;Kma*8TНޖw8bMfWy̬nB031ğ_a@R؆|duK\x&X M]UyλZk>Ld 7oxnGDpjE ֊λE<}WW~ォ7Pw43n1^'"hQjϟݶޮ"̬:o 9%O O=_<_jE@ `>Z2#]X$){"Qbx3GJY%Z=.fA"j`(pgq<fR4KpMVT}7{+0f^T&b-P1sxxWg>?NA΃Q墬,dݧ#Txk/EEM6NQł護z7_yW,DĹrGD;Xt^zG~γlwu+Z܋;7&zqwj?3;&zO\;Zou=WÅ*[!wzKhw뽙w[g?P~8\}=~z;(G޳Z{R欎h4+ ho7́2*GjfGȬoM>u7E}Iק FXV([7 ǹG!$aöKg\Dq8l1zp$B͈PS:KLaOuF.\XL@2mctgKNE1#6wQU"D)LJHgImr{.75gɾѨMEAj&Ϥg^D<*$NhvT@L$,e+[[o{{ׂ.vsEnB*7x~c?^j?9C]9v27zNzR Dm5S;|IN;vq2)#/?aPK= 1.骢VܐS7z~{o󧷲w\XX^}#wy7"L$o(xKy㏠N$>$.*⇕I݃3ܻHY;4nR9Js_,$Wgu*LTmEa>|%sCѢj:0v^ !rZ=TQ鞜Vr y.|N#\I 4<~L'$"d=n,p͏JhզZ"a+I#wUĵf*bxʗ3?6_=HJI>Ik#x\I"ͅpkt[Eȃ@EN-5s4 Ad 8\@ov8GOZ{D~HZmQ0lJֽ{ۋw>|:1Wë]w-o>Ο鳧m7DLE=N 暉86xS>\)ӡ7QDî%PXm@pbAU 1 + ԙ|Ys9Ww6xz嗊n)Ηj¶hx9 [i#2GQ9F8Y3 a8>Sv,%$xF[N@8qQH bB4~'8,y !l fz~."KD|aZNw;b|raky5gרWf, Sk^J)Zܦ[LC]ܺY7" FV2.iQ8YMTp%HDKo[o?ɟdٶөղZ^Ow7~xŻA#],ϞJQkE~c߮[?7x{oXqo1Nڈ ->%"ɕ,FٌEO`Ʃ`M,=^a-oք򪉟Q|A3-"Yrb=TOe|Ff ʅ@Q@A JTnp|qҊR1)zZg1r)'Al-"!aX$sQf")9Jo?|:æBXz3HdgfkM(Jep3|3tk!"eF1Z,;.!Zoo[Q-"P򐇁AC'URw7k[UX3 KPj 'Ώ]I"z7|W6vSlώ}C5NGiO^R{mRG@/BctȫCVD|_d_1vb3 A-qvÏ/Z qXr֙1HdžHๆY4>d́l@DXTP?_"CT%.pWe.4Uoqrrp"NkBUbֻx#˜$pLa~u! {Qp"G -zҗ~k5jkiPɪì03橡wSmXEALbffXzOtp,"=-<(:&1^l"Jwy~KpPt7_xumn?K"߭[mN"b6!Mùu9R! ^y_0p,xܬҹfaf-ZJ}o;db(<ϳ bI3;c0Mmp0D0(Gx%I~?`Te1Ap(3Ԣ/GƆ|XTc| aqDڍ#RV.E]O#a>g=}/ IDATwֵp8 "З,so~>oFb44nx7wǀ{ QQw'cJx틺UUܦ-PD{sF 6ZtBG;{oR{W~<֭HxWW;OsVe2?(XЧBL=aRUf KW׃ h.eY"B,L|:_|#/'KwxllI -#DX$7e.d$"[_z0ym!!Pa~nZ4_q$nq;z>z$l WQ?,\2Þ;;fz^8Ѭ2eEJBpNڛwPea|[7*;Xsv?o}[ӏFYrKʘ(pwykobA""-[abnn\MUܬ(qrfY%o8"g,5*"lk>ۧϻu\H|4lI&I`Ўƚ+sppVIb6%_(wKSĘMgڛ___Z:!k±3?(TtB4B15ZDTT3exGBXr IQE4ڬ"RpF{͉ և1sw `7F "$GO&cPՀ3-ptGfPYC8ϏFY!~ͺ!"$` > Y>moR1SRƢ䡹0 ' r+xڮ{% Š2&1S˞>ݷC9X@*yk^cbD8 ;'Gxsɜ 0I!o܋ ُ Qy7-3&TJ,£\fPB~Gd bq|ISr1g8f Dq rIIJ5{!~7ĐSǼg1ºR6<(4=3d7xǮ>r_jkbG|+˧Fh, U,<Wdf?&}g&eӍzxZXE)WWWWwww;ɵ*!t?fxuyo/I4zϑ-U VtܯoYNpk~:?,Cex%rw-iWvqu/(#@ApPdZdZvI*-2Un* < hΎ{AGfBbBpa40McLޢ4r[YYD6{ft#VUJߍ+*f",uSG6_03C`?F;|-G=yVA)^H;5BGP}ogwkήQU 1D&ȼwLZXE5n:A6Q0k?3g+㬉H˩3xA$됋Lp)U(C,#tϹЈZJ%aDKG0) ufSFLxTp.ࡘYA/|<[G H^g˯|8[YlG<^ jη锂Ӥ={ ~wr$@1GZffT $I%Ȩ\((JZ{rx!E6Ec$f2x(5h-$LH !s".~1($P(" U~W)>22&ڡnpBgdx7bRg/ŘM~YL<gըxVC0g`f>xD^׆;8[EϦs8fIy9 ǐAEhoꪢ\EE%"~k~l-(*-.:ɶPX ;F[wĬ\[/f~#tjnPD̤"nD$Y4RD 2?Q<7Fwbv,OТZJ B,9!h2Q I%Xw0`V6 (.[m 9rAxuaS8&2ʈ,ɮ-`B%vъE2tl &IIJ:Ȼ*9Etpt=5^09f]F…+84N_GAN枟O#9o|ʻe~ HO䧿5gר>Hl>#] )2us&jǓU"WE~:J)n 4MT81bQ \D"RJ1w`3g<u:"C_@O$"#4LCl*95? x{假.,C?qY"sN~ݱ+P>ȣ{$0ąGRj.0H1$RS1b];b , x]<(Cb8ςIWW[-YOqް\GKB~d#'1 LFsyG3ԧRR"~?kQ_R{S}h]LĽ5&bR Bav8mf ,"R7kZKxZ,\Z('L1O;( =]IjS2 $Yw(qvQ⪢Md6pH=Ew#9!adnG`I)GlM121lj\GW""!A̕Ճܼbng]/xƵ:n:A󙞜Ul}4/^DAbb`&̂ f“_ `D#V̾{3VxKkGX˃ٞ3 L!|~ROobڌ=ZZu:\gkEb4 K% )% 3!l-Ic%JUքE/D֍82@NW,I [~rK"NL)J]O!R>:,vL>VxHp2#3_g2$@3|t:6"<,*b?Ae=N LiɱB<+ >,Cq⭻jO}_5֨X|n.CZsg})BkRѢD%#}9!"1wŒ74j%CLy<4dfW"wU1;BXu0EHV#>MΗ *c(Tx_XDILbn™=`Ƶaw cg.%:ɀ<=Ed.ĒK##1ū92 ~TfcLJr&/2%m2a떬EOc MsDb2 4_:1%|69 ,e$؇σvd??_^kl6)Osd`P4Sj~rJbEk)Fun\Tjs ouUb{7ǻ;3n0[a'237"ZJd?**vx(ozw)0_U2Z$1ȱ c=Fur1xT5V{4[ehl SNxB:N14;>oی%P TNQ:튤.љfAFȇa̢ؔ-+cS`"@ZZ%+s1vf@9k7>$ ye0QJ!LA&s=+wOS?sYsvڿJWÈUEi@*5 TIb[޵V)* yvvsAQj)[ -1ZDvi7fHt  ZP{ͩz8;Sx_26+ML "b%rHC'RPiqClML,ZX _#0GGkʸHP@9+< .qb?rr$ Et?*k.XFC\pUzgw`Xljo)dα-sa*l՟ٟ_Z/D#]^e*<:!J QzkHTDޭ7+EuEpnNq]܍Buқ[ۋVQ61)#EJo\_DUnLjapVAqQ<!*)L"* q#"hcES&22eaDtU4ux󍀨!üqGE"$& qGO $Sogx ,D\UKǻi?ܿI#fnrm/r&4+mhʲ]ftH$΄NRqY-3*4G2ccjAÄ1ʡLN['.3l IIn!ZV AܭSoFk~֨Z?=@C$E *fnx՚PO#Tl<úZV[-v>: %" potKQVIq!gʛIﮪk"s9I͜2欈dZ0 g5Ԙ!A\8.aS%xVƸO'ĤUyV{{ZU rZN+E%gY:GWp,CLX8 92'7qW9aKxǗ\ĒP)RcrQSHKҲhՅK;xaLfa{/"Qs[uڿjXX}gf.*,ZyNBdYv"!=XD$HC89f*\ ۶;ΕHx,wZZGl(Id.a%v{>H}<\]BU[JI@BXI,4dy9lQ2E-ؗIf$1O^\;壃g: ]ֈy[\dp., 9M|^jBk*EwL5Mf/&dsҭ';#g~arsuBWC6_Ls_nD6pk?|0"~g">obODK!nFfTC(Y0PMJX'[8&͗SZfS7ֵVe@CX>ObË*1K{qWR ;Wn}6ȣ(2x*YUŔB*@_+fӕ`"Һ<<uQPq1qaMZ}T n3`P,&ѓDa#A)M%UǡH5PRrIY |-lY8y>4Sdf'{53J%8<4CqێgWrvFLx"7 :{>|Ol]ob¹ 1,73SzT5SGd"C$i &"TD(ڱ{(ΘW]9|aQ1ak"WTAB8!<[ÝaiA vw9^ j.1,#i$paʋ[=m}aJ#E\.z,Lj7N2e^2F<؟#n\ᘔi2Hw(Tx H;tTFxv G8Gw"_E"l]uo=BpS3uJ-֍"6`{IB:UL#ƪ*rOIjQR1ig353-{g 0 #>*IׇN'{ׇR ܜP%0eCjk("4!a]F/HLwǹIZ=v,*,gIL/ޏw{lf>zsG1F(SK@B& -Hs:Wd&r ev7Q}h.EI 4\A3tu{d= dYT~F̓CF*𘴒i؈w'DI`2CAY^)g=R{tVpJ=-/ͺ0NǏ>xɓk. lC#tAf=nD*Li_sM׍4W?2:PDEH gW۽u"C!531uZH:q1E\I"~$/̘<#e7܃"F ͵-LBm)ȀG4%!푅高xHQ(J. Ν -g4Ž1\,YN*L⑑3z`LNYdI.%Nǝg<NG~DO<~d օQ PXnE<=B*F3#^h۶ն57g%):.<<~ b҃!"\Ӷ޺F HiԇonƁIU3~HTaEQȅUF5 T SCJiQ7aj_!wHpLNvL͹lnoZ0ca<\)ۧKzjF*hsoZ8ǟɨL/2c=Dȳܣuc>98?O,ĨEx -sTHp]U&[svjEU4Kg)[]N1%M2HSi[ 3HUK9^g*ɭ04Z֙dM][|cwy!B~HAq&^"ރYIBGflXFRW*~]R:<*6gCkL#xgFRXpYď2٫>2/|ዴd ?amglӔ$}-ΆQ-{^u)Msh:Vih/W`A{]`9VF oUFiNmA<\"e WȔHTލG-v_ X sفp! \EãQa&ʒQM93ε4%S|'}_<{cwZQC"gL+@ RȺ{۫DXVׅQnRS M伡A˄XGup:",!8ׇ33n|5ڢ͐0Nq<E](IrDsRUT)b@.&C5z8iژ0L2ne'b!a3suC fz5wyM)2M#iD F 13pD!G&&VغFqrlؕ ڏZﯾz'[K2w?p Q4J5E`v ݭּZMKd}4LN&(}3"NlLNR+ 3|۶ZUU({цG֖{ 65 쐘E߂LB"!}Pgs)֢A|;#gI42¾ V]q; %,NDdAfѣNjXXɯ׿ifЃNc!)r)Bݨfk-PhJI>5v$@OLL`5YDUD#aDS~ضU q-p:b1(Qf< N#D.SK}"[R!Nybx Ի8,{.ʷ%#s6c"BIҘzKMx0ȞE5nYD<6`օ,p6|_knŁ#A\Ezԛy8(h;l2x6;yoO{>ժEz;1Skv:/|{xwՁ[kyݣ`1@Y80?:W>(;_$ P#ЄoEyrbp\(r~"Rt$/> "S0pyxG5'־XK}W=2Bbz+091 9jYGn^RZD}n9smm̨&ZJ)mC u X8"rj齻qAEZ"’@0t"Eq#4h6~ɜ dk|+Z[/Yw?n"F)xAVjfCF)G<||>XXo _׾.:e>MAb1Yυ1Pk)uSz 6槻c^ւjrI_#7w7ݻG<m;lgmF+bBfőCFf7b3} ˢ,G8kD-zVDon|Rq>ehv{L#9Y=mC??~dY֨8p_{5K77s֙A.S}d*kB94`O(RTý DZ~wp8O\Ke0X@,Ntݿw*>+<5Ig[MɸXg1Jje w/0ԃs"Z&\ 3ҽRkkݳ?;ɰxy;E쭉p=/B`a`&կk Q^*ӵDRtd|vEmuCB>|pQ{cmhD7k{{?[-ҬEo=ooJjR( LchK%· *6 Z>F!55 4lT<r"j]mw/Ϟ޾= cWMP@(,Gٓ'O/=_&!Id6HwxN9v 4Bňq"^Ӭ*EQ=JUCaͯVyУ2 )O͐8r(W$AnU3bQ[ V<VU޻v} !0u{d)~dF-~HU0 C -WA&p}NFadvX8Yk {1RwfRR-E s;3OpضCEC $aEMZ)B!<4 : d{Rt}5p9k bdW2 L+JX9nnZ?zȺ?y\X ה#L#^/DIH}0Ӓ-kP?rи{|K0hK9n7*Y-jT;F.%ib]36Ug\nuذb$re+8݂zG޶[_W}믉I"`*9\9y.^!ZTO"JEgR~lW77蜈HX#&xZhn_ # w(Y]sȘL55p 4#6xA__ k~׾_R.kH1j-u4$ Lg1X0_önF___ZwB\iKteC˦0h0kbq _1bWf@ݏ{u (?4?y}Z*W…%/@F8OX!ytJeYG, vg5.(7Ca# )G+vP GJ5-e`<}:">~̾/,B`aaڿJ V p־YNO=k-cx98Aݶ`ƅCabǥ .I.؆[ʣXDqJx!P5j:??۴{ĤAԛ~甜S:`:Eȣ}d&]Ez8[gAN|ab,2m%DlnaݐesGOѣ2Fk&¿CdEȳg+yQ⒄+#-I#="nuxhI}EFFɹ7p0?_,F(6:)̜>!Q,??6\n).$1Mes۵uÄQޚޙET1W3m#5ݳC,6([jq7#R,,Q_YMꚇbz{3aetӺy@B0 S*.b4 #fc k?zR,,Q/YSW]Ze2BaL6i >^2ˣQLAAc0:]zCf__ 5j;ޭֻFe g**p㶛a Z*zv>|ѣ'O<^uaa $!7JTC%|:yV\ͬg?{ ' < *f?2 o|C&1˒Ĥ{`]NX~>|ɓ'Kֺ |k_ڿW`aUu;zNǻ# zɓ'k.,,Q_Kwq £G֗eaaaڅ%XXXXXvaaaaڅ5j֨]XXXXvaaaaaڅ5j֨]XXXXvaaaaڅ5j֨]XXXXvaaaaaڅ5j֨]XXXXvaaaaڅ5j֨]XXXXvaaaaaڅ5j֨]XXXXvaaaaڅ5j֨]XXXXvaaaaaڅ5j֨]XXXXvaaaaڅ5j֨]XXXXvaaaaaڅ5j֨]XXXXvaaaaڅ5j֨]XXXXvaaaaaڅ5j֨]XXXXvaaaaڅ5j֨]XXXXvaaaaaڅ5j֨]XXXXvaaaaڅ5j֨]XXXXvaaaaaڅ5j֨]XXXXvaaa d4GIENDB`PKFq| | -Pictures/100002010000005F0000008744F2504C.pngPNG  IHDR_p!sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDATxy]UyB0H !$$!&$)2LVcWTjj CA| /spoKBB BYk>so.X|{i?Y=z:x`7ئ2C#7.-js`M~Տ?. s۴}x8{dyxvRsLιIjs خ)b~3{@?V28Cz7ι{8,m{]6Tom%vӍQE7i>{;P^8.y$s*,&#f3^p΍.Q#PP`MloҟXbhEsd#hsa_Ftp3hЗ5z,`l+wZ"M`"pUlh;s~'s8H2RvHgw4{p߷$~],l K tbi"ཿd6p{<8foY pe>2 ;{ߎog~K'um-da^Av= rZֳV!)̫LO^:pwefȒgu0 NO8搟dcv-pjG1ޟUц%1| pMDC).{snTt*íVdz`f^;zvZ Ѭu X(bG2) ۑ4 P?Hi^l^^xHkܮ;AlĀs ymNݫp-0~oMb_>mcB6* k2l6eho:f#GYg Dڇ4|crs"mGbXqLsnLιa9ι]Jcg:/谒eo4=>5_ m' Kk}*us̿I^?YYLH_Bjr6| C^mIz :uk*;6TdmޯҌNl)vؘ)]-:{:{!uΥ`l ){bV]dv xx"-[BsF(O!'}4uycZA4Q-Z }/lmX \m=蛄V_3d{0IN@*7:վo)4{}GO"}UG),5s'f"=}һa.ظQMWn摟tWJv@Jhx[HV|퉭fzl"\#ٰ}3' Q0p"&k{]@ )KY9i=nQߋ^ t/*η.| v#?腃xiw ۢ$FR?pp+%O 6}.!5fO\!OM*|{Y';>K6x]/FkNFVrzOo48҄)vˁY[~$y2ID4{-.8L?oo#=IH6]7aMHаRgg0 7%1Ȯ *<%<-8>J~"G{Z ./ _wg#]UMgEzТH'PG_7~ &2`%ߗȖ#;8(FA;"wy Zlr z>xRG.Dh5@W~2U8900 IAԙa;EqνӞŰ4v&c ]x97CF h/bs3흋 "mbt?I;b3"0 vك$ _L[扣1xh=U܋TKFK:|=O#v$%b/[ Iƌ.߱߄PWpUCԨ)G Yx9#/8zvӝs*grLFNAtlދ:x9i"b9n' s Z霅:mιS@8zygGKj?pC!=?F؃ġWtShވލw/ғE8[̒v^cZ`k h_FjyZF*E4Z>M~a:[}Ȼ]1 ~'lBg:BKp{oDa*䃖* 52x6{36Sӊk֖oCy[nic v{ ІÐAXFg:lckúyQcz{""IQHe4!]_. x 4c:.QF\OI~2 0.A0wbQdҴiT(Y$%psngi2Ԃ?ג#%;K1!FiC3iRtpOѱzf2._|1J&PȈGp̍ٙ|&@c}fU-_.\*f-dFzLD;1P_!({$ycu1~SJCp2#P@z=%q4L_*.q24m@x3b9ιPTAq3 t d6LL/ 2ꎠ`dڅxB"d V\{ 8 Ryso-F y=Hu'&NTEIXRLzI| @D>|i}ֆXEz$iF{gPl냫Nk4Ūg⶙n5a!Qa} ALzꞎGj5+z-EױҜg' H~ۻTwFs A^ h2MEUGkyLlAN]P 0D%Zy"]#yO,(!2VFK*Fxߣ^ 4o:{]cGEyWr? MG&?%z*#KI\4q\hc&Ճ>#h{w@_֢V!*#soOE#W;̢jwHw w(${QT8p,x;^h^I U{y0N6 ?9w&4,OAR,q l/;3u}:TFj:[ a~ sǶЋZ !a8f ZF8ԁw#=h5n)$ދehA;ʼn,U2Fw {s^Yhwy`0@3{"f4êd䁻|f=.BhU'Js B=:Ylf_ODmŴ7#?GctsH_u;H k 9-@+bqҸ4FTBh~K g!r~=9g 9Hm B/:1#TZVt| &x{HHM]BZ]Lk&6}Qp!ɻ%OyIz& oD\\(ۣ ½"#ߨv/xYwChѹh¼"H0ʞyߴQk_ h:>d:/̳r-ҽk4"6F*^H_,_vV1QKH*HB`ߍLr[G*=4ZeT勣|:0v6YcN <2 \R]\VGѾ?`yb^?i Vf B\NʺA@+}W ksSHFఆs<˘m߳h|_# *n]$aP4,])#u=RO"b84٦Ƴir 7޾uzWJT!~2YKIpN'*+%i՝p=|OIb dtc!ݥd>gy=6U,L{޹qʩCL f۸ME]뜨+}!|FsEؽp(KuQ::{x>z7u7BcEPՖ?¹tM/EeVȭp})/-RQjfrxdE2J>a_Di>^(w Sxh(d4y( ֭4VYm1pwpYJ /ٱ)Oem\!ϣgÐګ*rd8.&\h~ȝC6Ch7+aq]hNips?-W^<~K9"Q{_fiVq^_K㻐**"f+ȏZ'{Dfi*+RÛ?EzCp2ߨ ߟ|X [k_@lf̿rCapQYQV:gc{KPd-dtk%üFvͨ?n bN879ʨkבd[dۑZASiSxgPX0[e~XcفV۰ev:m%{Dz~g;0S4Fm.5#[WFzN#jGzqѤY B'vNBk_v""I9˝sjqo;fظ%vdKKj'%!V^3{}PuN_Fc`YfFH5Z RR罿1^Bvn]*! Rj[v/_ h+;Z~Le5H~CK!Ȯxcg~jk#JojƗE6;t@2jǶ֔wӆN;~KePًE9;e@'sP%?ߩ/c~'_ Hɯkڧ_j|ېWujZ2?u(i+? u0jUS;[a~8!E[C"%.®))ک:-Ce̯˼N9S Wl/)f1u"u0h:4;:wN/:񺎹?hߩPMܩT>jZ(%J'0:t2?$]5 NNvaB0G0 |0ې: ;P5UHmOGhN*}̺~3Si:1Szqڈ&[Kbv궡YݯxHS ;W,_?hN*& oF,'}dW:e~q%m\FQ߭p~C;H3N:m<+se@͢kɷtRWVlb݊)jKwBU=y]:Zr/tB6gЌВ_eK~3ɮFWK~YxjC3ɯ 5[b~0=:o-oU|"-U5c`c~@Џ&[]\(vMTE.Tq4?Pr$?S+[ۥd~3 Ws8:gJ_h0ήt ,z)"UN09[0䜻|UF˼c~}VT5U;-nM:jFFיp1_Q(AkG j[W*(OO &7rgU|{DIENDB`PKF22"-Pictures/100000000000021300000213878E5583.pngPNG  IHDR2 s pHYs  tIME *T IDATxYmU'9iFTfJ$$\ a@ 辄Qa#@ZPR673Ձ+ ҽݬf37!vQ(Aw9 sι69's9Os9<9syr8s9<9syr8p9sΓ9p9sΓ9's9OsΓ9's9Os9<9syr8s9<9syr8p9sΓ9p9sΓ9's9OsΓ9's9Os9<9syr8s9<9syr8p9syr8p9sΓ9's9OsΓ9's9Os9<9syr8s9<9syr8p9syr8p9sΓ9's9OsΓ9's9Os9<9s9Os9<9syr8p9syr8p9sΓ9's9OsΓ9's9Os9<9s9Os9<9syr8p9syr8p9sΓ9's9sΓ9's9Os9<9s9Os9<9syr8p9syr8p9.G__C"|g?{ ?|_W҃./vz?[|JΓ9/7b/ñ""&_hs?Iq("wT4:N]߯'_ O77ph! " !"wW?S:O+O~鏾XMP+6C.ԪTZEKyӛ|}uchJu8T(!!(" o{w?p7쩧b]Q0֠f TK|uyy.}Ǻ?As1@""24 ˏΓùo&7Gə cPKbqyu"ijS;ꔭ"Ωa]@~q9's߸G;=m Cy'g~ObZ͘#bmZkmLCuۋ]`V8A,)Df&b03P3C@$$@AC3@@Pm*?r}WOZ2^"?fH}R"1 4?Yx`<_ůa-cƒ57+޴\H@4"Ej!E0S\P[rB 6u/Q.z{hw'*ĄD`f !Q !H|6B.6nnqR5ݷ=o?a[;O}Wk싯1#2"S-:YJ# _,@dC t&-Q_o/wjq A1FP355 DK5f&D9@`XNnmWe0VO4֖.u_~腷[^/pC`үW #CGRAv^"($PESVdt8N7~}'}?yPnOr:URbBB"4EdBR bPP%PS@$ lfu:Ew]\le9<暇KiTꘑ_v?!{}Γùk/=x?{/#!12#:PSI߯9 hپzAڔ\IUHULjHZeOK`S+X#q 11'k@AN)^W"Ct!NҢbچ))\T뺮)ȏGpq`* Ƨ~! )"m2iM:$*RZ.̆VWb!M!Cִ5AmMDHiy()>un;P~UD@HD `jj!;7WJ׻b[/c8ƐsOys͍9!rH)S4sJsK)u)vݻo}m!Γ}ޕz^#}t6Si5 tԜSOh/Py OͽP8F"UlM̔jmRV!C9~h!311!W 8*##4hClm׻bc8ӐSCHĤTDZҪ.E1F,<\K)1?C|!Γ}_ _cf"@@LLF-XcT1ifs9܌ñr\P:\\o h b֪c-52V*4j8ioÃFEby>0PyT"q<1E"Fje&^v}}2MJnBH}߇Y@UK֚8p#RK-Ԓ t}b}?_-+>[b94S1SYZV+Aej 2jk*78]B"!_^L]VƄjSC2ZVn]߻ovn|pxL0܍Ι"b@[:x# @ˉ.zZ ) *Z[+-!!EK9穨r]1",Ww^RfdQ@4u:0D Ԉf*82iJېDLj-E;޽ejfajb\2Z-Rje@FuZhө<F=P$ L k s^" rAAGWYSPNHb}ץHkH-!2ql jmsDSL]?Rjk/E?G_?ᾹO|+C`2׫*f1b LlJh*UkZkKhfb"iw"@8Ej97ZkWMR c=dQ@@f&bDJMLD̄ZNpS7e]\lvחv#s!P橎1S.u LDTZ)DjŲO!Vj9<žEZ,?웞{p߸+_+_DF$33 _ LEYˌhB+o1¹*rw'U"\'€DL P~ơaŐQTE"%|*jw^V)G7_<900S330$CzA0[sM! "1!NuxTNHxuuqy]lv5C1ɘKε!~"45Uf6Z2bsι撉q~{>}!<97_~k_ qazEDdyHZYGI( DS4E/b8L#h@aJՒW^x34sA DUNcNTl7O>kqpȏn y.>N=fP3`fsv ! 3CFnZxݭ./vvCk ZZj19 LYJm)ƮKKJ)]!|vV/=>_E đ)Mq0BI+1DaڤA i~4e!AjLKx{:G5K}1} j0 Cv1<槯BDf9qN @Řq>89P'vn,Gb0S$hulwx6F ß@9c0BET Iex4dNL @I%]mBL`H<=|8W*]@@4S\`"F#( %m.כjBjSlM=|x{wY/wn-=@"&J)̫$>b 1!X)\-w]%s<Ɯs뺘?<ϾُC'^o~ ~735SDD@FDB"sͷa Դf'!uHM ,iԚ:sMK Q>AQ9X[!2J)2J>M0iU}uҔn(! qrT@ Z& @CM`Hnn~NIU8q1)ZGw77w}bݮ6eRH)"XL!pH1Ƙx6 a8LaEmS4MPrέ4b}~3's+} ~ 9 yࠊSD#b0@& Z&r-%C$@UUh*9q? B]iA!Gf&hY],-)Zu"M"bY nv7<:\ӸnyjۚmXK3L" 竜B%wP`%R'[oֻU8XI!@h Nfs{#jm6ecߥ TŚ4d.w] 0Lriק 1v}Ny8SL!FD|Oځ+O!) q :/;1|G<;a>Du !h)\(bzi(ab)2̷"Ann jT1Į+}IZ9vz'%3qNS4hmX @6й*"10'<^KpJm^-EB TDY h-9&2*Z/b^i7onZ)ۋŽ{-3].@S&U .EUTҧ2 CL!KTa*r`wo!xo}U(kd8מL(y'q U- k.* ȹմ6mr8&/'80&m"]e%bۍCJXtmJ&1ƴZ؅w7wG72ZO]y-/[Ipɗ_{uJD@  A *rǼ@ "E"s"8mh[Ln h0_'$q8EDb-+bR_D!,].WK"_oԯsx瘋Pa^%0W(ѹTZt {B캔]^ǀbV\TlS2e_]]{f!iD6LDE V *aҨLPK0ׁ! s3ۨ6/;&RUEis@X.V*Dtnd1jɵfH!PDFjf80LCL_,~\n{{xt{j{}uqu}YB b1jMq஋]K!DPCVJ:u*"j1&L0dB0sj`ɓ_g?kJéLŔQT s!)/F0ofz>@ 0$`L*kV#*SZ]&U~1J!ڤ֪b)לKmj{TdH0z;w??ϗ';/~?_ZܸZ*anSk 6!'@85 RATT$#4qG-|c,5$2ADՓi"HЯq@ Z@EK'F+N>3oyeV[Z3*sỸA C(<n6 DI$7lX0Tnn71vҪ4D$ȁ Ο,@km[k0dbBcx8*XZ]Ǿ뻾Li?jnv"}J)rLfҴB -!UKnhmMVi@|Ȃ:sɭxBZEĿ|߻w:O_yw/rd8@0LF=nVSQ5>w%q_kd&L itk@ FTP2sbVw`zroD_tm/ CV 8oC3sk@* 2:n35&M*<:@~ű;[H"iJtPBX-rDd [ *s`@LU~Rue*ESVQ"`̧IbeZHĊ-zcIxn7mq?a\vۋzJ)R␈"3Q@fj%Mrk0iXT 56/=!@483Pѹ MO +_}?I@xWݕMYk7\13kt^gnS03@B 8oX" Q?4oJ*ѼmƁD-).=D30ZZ5n*vnED &"0 rj-vU _WO\%}gHmf6.c4g(EjO7?pNQsɬ*18 &&"R <[\.``!&DRVpszgw'/]o̴VRJQ1 !1=x>&A" ""EJ-S D13ӤJa *8n* !@|\@ d* [@ xx!@mM#iVM!*-Va~ REUOa`iy-71& q.a2ZV]Z֤J=2ݎK~׭]&J VLy-CҰU2S{[.]qp| +ˈhMLTh>-@S` üPR9}Q.6f*Ti)h0+#r@$S̹ ct R!Ο FDD2xqT0LlDa^q0/)cNSa:7 b"<6ʀu$λ7޸\ T;oOE 0ψQV`іoܨټa`_D?E#x;p.0"PU خbZ-~[6)p^}׾ڃ_yJe~a \*@eU@:y=Wwء=TK&@:s99W ZR$ @[+4sbw} Hsk-RQ̸P s\<*SDZܽiɄ)bU1 }10ժh"oy~]#_G$Um&bfo&Uy?3Fm<6mV.׋| 2_ $b-ӘԦ8Ɣ=s4S,6&]yeRK)%Op8 8ִvˮ_)%10< k܏ǻc*AJ</*1G\v{^PvzQs>;S7 ʈENCӲW}3"O[6Ok0g~w^~2ij1& z|{Ns(3L`` z|c'**Jcb~mDFTSf풤NWelD:86Jy4fmY"ĥ)Zja!`Ds> =nNZWrKZet|}ӨMx%kʳ?_wY|wsw{wocH"|d{~E$U̪"z 1tdZ4y;gɏgYvw{{!"ƮjIMS^ [-‚a -3 2a.mx Hvʴe@[UC]ݕSD~޽ ^ LQVUȈs Wij-iݥ+.K"AYr^~08|kjJڴV!bY20FxuYy].4-#z MWDH)﯎Vϫ8&1q?ʱqSС)lnPMbGM.p℈eqCxއTm2 ")ѐ0etmHI,s-e`꭮U]8sO_߫K].*; Hu.Qp< sA!e IDPtЇC3SumvWv#!Ц=J8{$ػ%v.McrN"@ojr}rJИ aO^l ݽ8RҘ|-J "nR-):nʢ{0X<#LWbe1FJ,@i cAtD K##UwP ,jatw)L#g UsFQBo ' nD@.ԷKn}TUtҥԶ%1;yESJӐXN8P[JZreY$f.?kog_&/+? Ƈen"8#oys[J " ۋbXW2{%͏ZJ]Έc({'YZi1NWs"05h[ ƜR,ev0Ik+k[-!F2]N$Nr&__ٟi՟}wZB*F0d^.@ \G>|3PߟwG_{@ sF4ȐXr8Z%/ ZjFqj=HYt9hko3?x&__kղj3qn!{ ,$N@¸5 n؄")?@p%13w#fBD@mU۬udE8 # xa:<d𪮦\׹󺜋$_M9'ppkVWs?=o:X>攙tͮt%Jy`fD`BV5Qkk֪-k=annK´_,L;- Љ9I#Ie>-`zv\,jkp8쏇̭G`oH0JZ,k=_y]=yJzS2>G?ۼ ~P7`3(qR QEhaj A8,44w"0eƄywZʪ2Gp&wo|{>\2f _[x?_u\,2R]AJc9Cv t*H !ɑ6S AtܞasSvzo{yzlb0kVVӅ9+Lkp-aͼéBYYv)qZ]Mgm!'J;i6 T(83)DD0U}@Ln M\N;ԏk774򲮥hfZ/4ztI/(1ꦎ xI0h[mm<^?<4ZD[i7 2YssCvR۲g?]k1S @A@"_d߾Y>sDn"v~l> N[GրlBUn[we=\B DS"9ۏղNU[Us~ro8Pf!ɳrO+HNn:r]_ZUPik{/?/}/+?9{|6kUF Abt?4 qokk[|[%ὧ 7}<"Qƽ({4SPtUxᆫV#i !Ӂ!ժ(E/wdwҖPʹS$H {GT qNy~h/cD,ao~a7\SZOW~ŗpo|7{=s:b9!> $cEYi@bb@0w'{ 2 !Y~:N]s wwޣ08f$okcHul:$B@4E.yH90X5SK 9eL?!^]Ôvn&SG7ר嵇h6v]i]ZUCުd+e uh;~72ݝ'AΝpIR"dhf ~^LXSI֚qz}HDAі-tu}`JKmY| %I8jfpt5F`iR`$n_:>8=?}ww4*VD wGh')2 !'f!IjSUwfe .y@,> )0H)'w`H߱$f`pDYEܔ8-w<w2#U __7˓yY0>;ҪjM PL }c}VFdt3Us30) 꺕 W' Sn=;\hX!Hb䆀0ӕ% AL.Ô<ԴY]1E5P ]ܔ-)'"Ͽa̭, NrB8͈C_b=%$fUz5?C {0(b"@,%Ss@ D5pp-L-!0QQWϗaPm^RҚ Sf.pEUj-( )OȌLDD#]JڏߞiZDZĈE-XNV1>)9xDBwŌC)̬_u-yH4␦񕩖aCKg4o;$"D`0@f1벱j]hM=0*` um'OL)po1K:$rj(fO?{v}`ڍyZ 1BBtܡuD4f33_=xf"L=n  S$@( k1˜zjxWO?woGOC~JWz铫O=;@exY9<__?hUj\jV<@0Q Ё-thQ %@T0'$Arpp6b1;2zv Gߎ- o I"XzFk.Oؿ $!QC燅 kAxiޭ Oy7?5/7o>|x_WD{Y9a~嬷F!`0J?_<#6gtxro43hí 1vu; `] *urp>>7OI'#̚p@$4dDZ[`Ĕweikaa8 15HI*k3wt8օ/7ocDZm^=aѣ)@`\LɡU[jyʻ|OGʼdu?W76ow `qke|KFbV`!2 $-;.x{qz"!ĕ#<\KDm=+"'B"nlҀIܪRr㰞61BY8EjnMgbZlyz!t>Hs{ D8&n1ILC"RIv (@;3\)֢"<-<$+"a"%k%[w?xt''fY3/˯v|/ro}K ևέq-Y H=  u0hR zzGvvCD3vW\hHV:G$4;Ȭ2 {C#2F43-) A@PHB5 8,k(LJqǜ9 %mj R% C).䡀"4de)K2ze=֥FX+ (!'VV]Vመ'zZ<>zެkD$;Cy6S'Ff@mws]JWjtlwq-ҨoM "25j$-ah35HX=j1yP3RW5lUGy wz#n,Q#O8vq\)"nn Ϟ?K9պtGHemE) $_fa@Dұk!N ALLD!yTD <,RÇ˲ypB`cλׯQ>~o~?zwKo}s?_oլi~Ӻr) E8&(>B Ӽpv%:"#pg-Ƚ_ՈNGܓp\zH)s{l]EXZ93%f4CxsjUȜa8gbs{pe:Lո.إəʹzHՃ4]~c.M[*0!zkRCcz>ݟ;~7\j}z,eGo=A@\O/@=CGJRJ}k  n&{DXY+j,? ĒoYÄ67 W3sDLÐ H(rDrB (h0}&IZi9IsYϗl}{w'bZs2 fE@a5s`bNVFLH VmE}QpuP΃Gˉ j@>>8Q鸿~xVn>9?zo!~|)w˲o^/+ƴ\f/kkHi N I -_ ZqK.`F"[`ܜ]$jMEMo3q `&3oi#KGGB`~G0R.i!,0@jnnZUKmzy~ DN㸿ʻ=1hC\z7f"1C ]K[m<=zmȁ kY[ie-m.eayLCʌ.֚Ճ'>nײ(`/Bϟ?}t>_v73(s-w}]RVG镜f鑰=1AI']] yLR'Is$&UjQ,͡)vw!g`p ɉQNOκpfIW8LVZֹKYʲVϟ<'e7n.D$D"A] 6N!"2NC̋z{$amDV$o2첩{~?*V=>M]|s]`DAB@X}i4$ap[seDܳ=`k)1q@-ee=[I~0 ̢"4<"PDH2X+V KJ=62Ą<40 o;1z%͐UZƝ: .CiQ6]n0EB\! 4R`T/PҖW>F3kڊeĒ쮮 аv纮Dy>z tY l\0]=z7>|E|w-<, p}<b3+uǼ @ 4'fp0`.Rn#F Ӻf 16ON)' !D sg}"B.!c:a$甇i[.ܕtK`@ꁾ)TjQ6 sէpiOZiJC5At &HL %wgp3aY04`pD f -KeJv[d0;@ߞ>ѓ'ϒ8qܻ4R˼^k<]^V=KfZHDd&m歅:C#1 ILDnn[,txߒ 6.lٜ}=xsLQf2H;V[CdN1r5mja´'@$a-;*@`)3G3:u-u=t,"(˭Xwsmot)Lܴ!q5'AĚeUUo0;~~l\/ $‰XROOZVD8\>|$$yȜ؋Rjk2U=v~vM#nW}*y9KSjzEbʙ ;ŏA.!bSLT{DbF9 4^=) tt/ϞԹ|rpC`j=w,Z,)efI]$hY$gyCyr{?oadU$ `o,)D4c=U[OwWuyr_4E bt`jҘ`Ļyk50$LZ>h%k)%%IX=4jqUfL"z(! D D8n`p`3תnKGPoӇn..^K{㲭7zH?{pCxa`€#[. y"B8!39hf(N #LK3mX1d!*=%F&;2k'$$D{x5@Sqnz$cGW~[VSH('/R[̴^W_yGV[.g7F:'`r~SpڹfN8C\.ۛ8>8>xp$)ڴrKY=2 3K?E6ą07޺0 ep} x&&-n5ֹԹ=$ׯ\ 䜳ԥk/r^ϷV×y &32{s3GINDyS;Aa8>:m IDATiO7ǧknB3h '9iHV5S[@oެW+{N眪"YdQlÂ58(1C rc$aHdet%,[SͩfgxZϐ%.kz Zf&N\RK0rJYT[f^t}'@\-׷/xO6ϯM)aޮn'ոz]uudz0e,4p&$wݵV #7v3IRCke1UU2$=  1ə3an{ 传KP|UFfb8.kt#i벳 #]aV3{ ekrs{S%%JժpR["ΖbbGduaCpG膾ӃWi9|?}~ I(W+@"]┥Z09'"t4եz.ץ֥tҪOsߑ#玄 :2su˺M}F` 6U34jp@Dzn.aVܕ~VK,䋟?LZ LCؐ̀^wӉ,2%Z.su:LϾx_1$ \khU-}Z|{nv;JD;!v}@L78Di#XiT>z.eN8x:kU@pPGj`TuwZ<Ϗ+c?<6\ |X,g~ Ԓ0CDL 1voR^~IŅ$l$pW;o//CxܭO~-!!Q#[XU!#%6G88wt7AJ@0v!aVS=)I*er`Dn"$#."#0.EL,89"BwCF i?j?{‚e3O кG(@ EUr«u]r?s{Hl.7 bӳr*Fd+ÚS a5lsFj@ m)Egǿ+ Px?=f~T݊AR HJlVzjo&B4Gk hBagƽpO$A8yUs3XeIaPT}. EC B]NVs= i175# 3\Gvethi7+H E.^69t#AP<`30WoRqnD73 :N/k׫< BHljnZ+T_ype͈,)/j`nq‹:V/twVo/_\\VUi>"Qtl@DZ%jr{ jݬrHDRDx:q{/NM2nVyȈȒ81k]̪A 6^TKR$U?V"0,eag$) dD}ߥ̹Oh5";j^-g"(I;(2"Rdf2~_^n*2#h@.Cr:ܑиԪK"VpEonnF*67rhݠJ=֍z Afu"p/3ڔ]W<)wHBIQUa9/??k/x)0/S wdC"bbFB?? -pnnh 8v$dpphGs΂3ٍCdn\:]NHDK1ZcZf$yČH@D2F ,´6]kăs}{f3/_>>RR[k%ƸUkDeYCkq\I`w8;8imY*`L!W^}\i˃U[z>e.wүat;]w;~я?|qs}7.$MwSpH;:sCdpڬ!i NN,, duOg˯ğO`fnMV}DSJ@v3h.nu$T#E1 ޒU9 0yེK 0b52ŠkxDf$pŠ(N՗ ,Q%ϡ]+0!`-#~ 毾7õG9w L*B2F3\]edq%Lmo nۧ.HbA ҵ^AZh !֛C%m_SY8#3e>O7uYLu^@NyYZ ¿wD;ZܪG$,s)ڹ-kZ D#shg9 pׯ3u744}5~@N$|浃jxUn ޑE 0, z @DJ,9& ݰ Y$!"F RpS<zSGyL2%ѨLvaV,"!Ֆ:1y$#܀كCc#"U02/7^nMNt]77ZtYw7De)Z{08<|1U\qj Gl rS7sj7/(])!pxŗϞ>=z@yECʹvХ̈C?VkfNTiZiҢbUKj&LDu<H$9"XzI""9c^ِLP!K s6k`jZܩX)f^r">yPIVNw=CU l[8=>.nm1|Zx+zz76W[AjE&4t݈̔:pwf waE2FeXROp֏dC!R&?}e՟|;12\4q.qF$v Us4y:| p g v@n.>+:cjnpILmƘ1GZ)@ea$"A=BuR|\ӂFr& $@ЈL05 s.¬Nj R%tƞ;R[z2-u^/oafJČ$@AQˬ9$ɡZ;z0cTku~Pkq6z8<7^Yp:.~w{}E7[UgOrINs@YBkz}qyBeZp תeYax[^D êi uwwEBfVO|?Wk 8N$)%O^<˛a5^^Iuݮp"/nc-EkI)L93 `blA3C> #%d0RJ}L}:Հ aЈ́%$gFzQ3bq*HʮT#Fd aȫqLteǟoiԛfe}|>,^4w铏>gOky C )%nXm D;} wafa nhq\~Ll2\R! kfER\Ke3HW!w׾xOnMʲo}x"к`)hKB߂un~I-aq߸E4$d9`vz"038p3p")f˴S pnmx L!$5,KEx=WMEPi.<։)d]PZ"=SD)1hxYmW@VQiNrSB5itU (z-fRF46yJǏ~<Nz;|Zj' @p $I.Z:^RYKM9.,o9}8Y8 Uu0)De'7Ų7:[ua(Ts7^}{~{Z?HKֽj̎P q4R"i)h굒H3Zs"%VM f^©I HTI=@HX_O_O?7߾oQAb>M7o+lk8?Lq> "Ys E+14A5gfNýČn^U! lA @@A:s"68ub|^m{Y 0cb>@̫ S"C`~S`do;@`P,T-Hd.SwjVck&d\mb>^T]-Զ8ߤWOi{u~r"'.ܭP $<`#в2JKArE9@E!.fp8(pYrfI]21d&@ך#!Z3-T:fVqh]NS̒۩/oo=q[!b^E 3?UfiA8s3 P@yaV}9YIԭ U`JڨhFb]ru?_/?۪t]C@t]&ݱLe،sjgתZEDͷmZ-cE7^x4%)%aZZf!.ښ5G 11"#bf`a^"ԬcB{kiKjX ME!NQN@ǑP/"*K5@m]cuQ-K=-8K1cԺ1HKZ SN%p S| LdZW=f^o6vIcN}7|YGQU  :aQ bREaeF.Zf|w7'_v OZ|9*Ek5/8I:,:wn(Y-")%Gx]J3KR@a (%bYwZ IG7fB@ȝXf^NC0x峻_|g~.)|VOͭ(trp+IYj IDATP7 ki{er db2SJ$LLCZ}0@BP)[k_=.1Hi~ Ix6(SyY{ǾIڣhGqFn#|ND07D3WW[Vh BNY"03SK35G? P#8}9 @nH`gBFCY !A! @Y29+W[1SX{XN$wr3/52ޜ2 8[ b@X6ZVΥγjs[ ``T0V  >`$0w dv$OB,w\]m_ytEw7RtG$9[E(eJ.unKo=x.r&fsyyZ >Ӯe[9c"$]:+UԥSr׏i3/nnX] ųgu.ѵ($C\JTSӱaq̒(,+2؁+ {:M!ȴ'@UE:6E-殈$Z  ̧9B vBi1u%lUu1&/sOg~[ϞvohRR)@m&~bT&Xr/((g 0rFJO !Xc3Eo=0~~'?h撂XJzܣ]&;}S38a' FFqCtp:Z*D$1s&$tTȹ 475G@$mEEخ_-'<H{`l$xVCb00PVwZt"D $bGWw3wĪyn,h`<n;ƕ`8*w=! v}*I >g-(|rSlh9lnw0epbQO<֢6KYTf&&Dw_ɴ٫.hjU a]]?xb)yn~ x*1VKr\mas}nwn3LTkEO?,Tjy{X- j(9ׇ}ny*% <Su:j5.I:d0&yɗ)_p]4#c.%œ  LirCe0LÃCeh[q"CD@p{>|#-zenz]LᚈW@e Vn4̌A6oDS70*z( L$RB B^j[2={wVwF7//3trZvK#&b2|/nK%`C&@lf fEsG{ R1&ܛCMZpYH%04{W5pDv58!y:#aur†(' w^Kjt=`iT~521VՖE,R#XE#@:[!&}\[i{4߰;;# s5/՘ lV\C_K<v#7}Aʫy.`N@R'Q.nu{{{4i*焭*܍-RgOu9./..~ RÏrf_nJ 9{p!kPzZlr#R&@LTG (Ӹ2?$SIj#Hs]I *)'" #H SAl"AlՖjqR`F%Ɗ2F$!Hpm9b8t\Lý'\O/no÷X^?#bwDn-tQ u]Y5}k  Lnڌ=u 9rnJP*½pϞRBsjFкH7Z9HZ%B(' {ZW?'pslKm{ T ][%u0Rk3 @c8K gXJˀYb; i2_5}"ݿb}WB362!! );J;1 @#\p# f[)K9b]aD[\PMw/^@jdWG}ñխ131AJDq#jZk1wf9nwuC18u3ݸ{b<&MKyi*Ec Ĕr)m]v</wGnnG_,՞?y]:pXzje)eY^|.n]7VoZ7^{ 2#$ni07S9sKL]r[T "i6wKwO^=~+T)y53CF@v@P3@$ V-!ALY"Mr Ft  +)=* q7A~w?z/Syys=F\_n_7=`ZQQ4Ks r )/i/"umY~Xql.Pc֗h>ZfplҎ$aDu$HjK jֵ:"H߱H9C~ѿ˿+_SpR'??xc{]~W,hQ@ aԪް=QOQiWHt ki$ lfOY2plS$^[9EP6Hvr0}=9~N'ǯ?@rss]NX[]n46Q37"Z[."Bx۰jp(U#%nw"`Ng=!=}V痾Ɣ<;W^Rs-baĒ<+"7>ðscBi##YXt{ DY`f^JQ 9#;nw/^H@?+%vṱn#B@ RBd Ha.LԼ hܗZUc{$zϯ)t7׭I9qJS$ݐ1ai yVy^hrcߥ7}x:LLK VVU-/_s>`HNŧ?9Nw77(1wkR >9 ~Re 25xtz_Ƅ90t:`6CEwm:^ks @$RDEV>G*JŮ|eRTV*~rX%K$-@č̙s_^kuw^} T漘73޻{u?q"7#h0u$jƪ5(8*з'iUg|]8~a>n~[婴< WrDBnv졚aBȮVjeX7.w?_C ݲ>Fp0Lpaf+#S9t˰XHB'!Cș dHR-^lZBdĵdVaw~/?_T:?||SA&4u㑁am~C^~NJЧ4,c舄8%X.(1rc2C'[3{7MHa; Ašiaڜ~Mw_gsqګ&a Zf&bgw.c4)Q ]X$Uiˠy*!`;CILuVKGf˘=4pXt$M9p9daL``3*9UCd*G/>yr.'f-O|+ zEbi̹)]A(E$js[V9xtB5apIiq8Cbu01H0Uub(ԫiI>JYnL -B8yҠhA[%\ME 3P$`*3+h_o֗ /\p4h$Ibd DਵBɛA٘X Vo@$PVpm80Kbȍ8Dlx61v?]po[=ws5; Q;egUA՛IWJ6_fVZ,I luVkijhd<|8NwGB M>2]jykGDsW')[wðޚvHO_ytvzbVu"w;5 f+>SC՝3-1c"P0sNAB݃GaCbiu5l\ϯ^<!1!ؑ0 ~̍ZŖ }˰^ :(.V:K^HC n $ ת\q븿y|89MM 0ܬ% ivhCi)t,Kf, 88P;uvLxSY+-K=ї W9Zܝy Z29j) QF4yRFbrf]MZ\CnYS+SD ،m܂}盉9g-ժD9Yԕ6m`bq-tܴA811f5RD̊Usf}&,p}nj'8YX!NDAvhѦ k@C~էa8n.:r5->/Dqg7,ClKHBByG 7#&SAPtwq2 B!:X~7bWWfj)&{]vחW(1;+v^#896x6wHb1ܒ /ucLTo'֧jZU>uSH0⪦NU1S$;lP##ZBHսvǩ~g?=S&<<}7Xm{EfHa4$fsA"GcjY5wG2j4l$,V$fwuޯ7rŀ1;*1Xnpe_K ,MoB>թ&I5"( "mlJDbRkK>ֽnV녽 ;HtO6{?;wwpY1D*IbôJ-iUѭ׻;xyi̹qb@P"Jz]'c,77/5PZ8YUe h4[JudL֭\̙Ų̈Vw_<@"g};߮fٛYbwSX8ƱX Gd:$&c iu !18cišyn@K/~s]s^W!FZLs%B Pw1'GOZQǝrXl)I URUefR;}^h0;ş?r|U aiYSɻݝr_`V(Iq9. k7bږ0 ͫjӗރu6%3":B{&<031M[&"faru9Z]f"[@Njp"w?~c@/Ut)@kij.+~Hc"$";&E6>1qhg 1!\! DьȬ`Rc0JV 9D7wRrsDժMGJ ̮fqsyquqZ??|X~ᮀ.+B{kO&֩:e"FVLZU3eiN7B >pyyImʼ}xƷ<NjE,B ~Wpz^Tu:NbYB@1G|V) IDATN`_J)g7jx~sW 4sȌ.Wq-a*k)wR;pXjT)覦ZTQV*SJ?r|6nV(%ZkESf =C~ M+sf{ޔBn& /؀YA3P L+3̎&b՜ɩ9NyHm!~BU#i*MBK3CK=9JZDS@̋bTwqj~w]78uR&4m犹ovaTkEV6 \@DN3*`jħg'˄r nFSB42a$'gC<էRJXjQv"fKǏ_zWxܞ=<{8uKKx<s.!*on Ç^;^>򧟜 a \C?_LRGn*?D¤ZժYZd5ZW񓯽گ=18Siꍶ.N\Zq4lyp٧p|HgޞoEG5Egfa4\YGxcdU1;PA:w/YY:@B 1jU!{aqܜ=Jl ILMݧIS $NաY 4ꄇMS"hlLnnjbBmG̝P*y_Oÿr|Q~"PbH!1)N͈<2Q$AMg6LiM<Ռ-~ DHFq#[kkCSwF:幃Aͧ sws2j BJ OM!ЉFX:KZ[X!; Ca !ZJ9t>J1'09aךɹDz^k! }\O<ˆvٵ$ZY `0M'Ԙ(v"Wz\DXxj nY╬D%|X-~Xt!0U0M1j('6'g~uu/cGO }/8 EVp؏x~}Wt/Ţ}Hz罰nݔbNBN>o}CΑfJELrSU+X-S# N/>{oJZfO//~a1Ra $2k@,Y>yZWj'}a  6DN(#.PUsB .BZt?7;/+bZUjkWKt#1OZ>jіt3He XxC f=nv0kmaAZM@@o1B-F\Hg7ܙ(D ^[* nPs¬bg"qVTT!(WZ꘻Ţ߮Yz`mS_naկq°W{LZaNZMi)F)4+*Ds⑐NisXLw1kU ŋS-p`}tNO1Ff$k MK"Q$q+?5@5L!EUMih)](ݢY_}W'J j[|-t݅jܪ!<@Rsea2?ǧ\oљbxXvl\Y '^ܽOۤ6ggAfxLð%뫝VO}wbP'D{r$O}9 JCY a)ɜ`VݝP*pap )=|Ov7W) ! 93U؛gA%xERؙY  cr3%k-@,^FQ; iq)+'І"Wї {n-Z$Cb_cdt# Q}P[>2HڲXV`Ś DfjsE RLLh:&z"3-Ygb ̌yӂ-=ê HlL Twl^t&ʭ-90k)fS-㔆jRWNPbڂ]nfK!Fe7K|Nڎ58>>H$@kVZMy"LS9FUoC @(G͹]Ϧcrݣpc9./ݍD\ r|4gtc9k("TRJbaX.DbZln;ˋ_o׫%^.)n絶0w?oKIBbIr@{ ٜ@I]"Zy/>_fGy)2sݸW/.ϘE XmOOn1 y<eHŘ:)1{lAn#hSZҚʫYUwCuG%DZ&s u: SΚ""f:=: Mh.N bsXQX}:0Zy.B~X h(Br7 %$fh/E Q08yV2 Df&UijU#4p.K"5G9z"Vgu b$85/GD㠨W!5[f#|h.R99!MM'BU;65~qsZ3_1+)rS7BCקGBX.D=]ۻ|8vbzN]R/$T?OWw7񠇬Y0 j9hXR$0 beڜUmHbw?{.f?_?1=dn~O^Ư}/D9E+xQ&PS75 J7T@)>y!\~1kͪ[Oǽ|O~w?!⫋ 0 jya8=]uSca4,/?!VM&"70Zౣ`s2Pߑ摭#p`: I̢Z4Vn1] ZTW XswyZRrԯX(z8阇N'q@pGuh8eqGN w3ra&@H͛)hDuu~ VH/Z/F"\=:_/+c\R¬j>VbfAjU 92-qݘl G 1R ""s,œi6~37j-sC/ T5 (Np D-+7UՖA`25'rDCnvf]յZ1w(}q`UCM<\jYWM66KJE]1E{&;`Ҵh-)go!YnL@,Us w!v-5ZG/Ukn`JLUv]Vi?]ODIKI!$'Nz&t^<8YB'ZL")E7?]<qPV4pd 1cT"q\z`ExG=y|{ӏ "1 ŋOɓ[NK"̚Ċ1 cXyC_~F.7^jf=BjꞫaCa]]b>ȃ,כا צf 8~&ԼSQ쐍#,}P]n!n1(p"Au*yZrH%jXnn.bd\xtTɦ[dD  ;VQ}pnZvjY5n&lո6 E?}Or4+yFVn(=kuwTY1!íQCDTJYQlS.!q3$͙Ub*B+)>dU|Nm`/H^̬jc=%rbժٜYOMͫZ>^u9y:^:H8Y;`2\9,qtRJDTLTk,!-Ǐb߫#P,gUw՗^ڜE ÇiO?㔺~a4Ϟ_NOnȵp RJ1MrX:D^,)$nƉIwM\Y i*}zN>%Hp=~ǟHO~)plOU7+9ZCh8FSO?' "n8z_@F\ww/n9}t}Ϳo]]J1Zjeo~0+)<ٹ[|+bu*0'905w[勋z}كUٮ֫%i8}q~U腘c^?كGœ.њT9jy()٪)LYq3s.S!~{[/M;p`n*".5qnQݡhHPsm(r> 4TVӬZR-0@!QAJR΋)31pNH\n2)q"]S*cG_V?xM=0j)fhZZL"ҎVچEM\jzFTk&!`S@!2 !IܭNN0(U6(iu!Sf;Lfsw2ZGբ!N!m1{uS/S-q;o٣ʢj DN"Xʪ6 Py5 IҤŃ49]-'X:N6屪1]'{l[-앗_zZlqw}|7>ۯkr;_o|e?n./nvwj Ř$tnn(X21ypY FŰۇg5;|yK"FyjN/Wvl|C]^_ ]'&_~_֖+?O^~2tR꺴\29DAA$-xd2t)9Ե2!H+ jfqpyfN۔uGA:ܩh2 tк6WPd gzo~9fjߧIp殍Xq rՊV&.gjqUUkAD@UӪXc$B L͋C"R[Id&^=eA(͋{kp񝙭V5Uo-e`29CTj-֯~=<pXPqYxH k ^L--%TWqĥQ]H]4% n$qI]\*L@1X^c2jU=!tQbj X/j"193òXoRIjS6~~Ce/1HH91l6<#nf@Z 31896@t3V3(4~Dlf*938#]g?-nU*j3OtӲck%W-:ݲSS6kt]nrI,Z2NDcHM uO۞ZBLu {!׋vn3W[2}gjn/ϟhܰ\.^z}W>~/ߺM T/..+(fg'T*O7/Ӌ?yXw~]b^CeD~?Y>tu3|H1rIUsL"v3 PF]UW݄?xz4G>^o]/DZ[]/ݢz>r t;x8xYͼ9=}ר׿t^})׋n7~g1.گ"tf>븉8 9"2rSGIlMhů6ng_6!nmn,5$JdU,\9Fx={}%zBfF={߯$T`0t70FӢ0CB!O}"b%6'e<`nB<j͌ڮͺqOCoyԈEb\C L'BvfMD$LN ,mltxdfndB$fى9/?_7'ɁX}B!`Q$*lRv܄:M Vԭnk7r%"^cΛZm(Ԋ0:x1'd`F,I+Hܙ0z_ IDATh VW BfS C ^QՊpU uJ7jVԉ],$ 0 H%5i'$8}d,M[vݢrb~. z%" iuڑCI4,騣zw혋jU}9>&ksj6k[~W?Yr.۫~r9g_oz}ޭkٳϿ}ѧWZ yYn!!QKq$!p*D[R0WB,`(?}~ExoM@M5p̾ͷK MϟM^*VGjܬ7ev鳋$\o$&q@Cj@T"JNT>fǢfȎ$TyzWlVE%57Lf $TwTYc`p=[٭nQQeFW@yY Pc(|~vٴq^M 8#Բfqp$EFfijrr3#!êlrfFZ2K }/N/"t \A~=RܭT9   uss+,Me4!dR0Tm,F* Lk8 "bfRּ,R+TX/rx  _.[w 's4-7 EZ4פyi[PhM*]nWP(p(U!u<b9AJux#q̻} X+z>fmo|J%2ME2'OE9ܾwղiZn.mh8̛&ͻۧWW[BϺi*qbdkI9ZmZiǪZ&DeMeg!¨ iw3=˓ۋbC _3_bNBs_z'?>ox+M /dSSJpyz.Z /9auTAud(A5tYVX-!2R`(Ublޕ!#D7؈U@TzԮj&@ IrI8NH5q3=nfcm0by*%K4W›x r 7buQ Q'hrVMùXS}ƄuɊ#Fa]sqD,8S:px)Y~SAGjqhJסg07_ul)'+铧/^\_]^W7_'Mw]b.|vuqlXF6Kn*O ԝ*rHDFPw@ /yQھN CX"R$.]"P5kսaBjIHh#{1iJMq(jbcaV }ʎlrNaB MbzƮ^þ9OÞ3>O7iWI #tifaiq$ō1X-h޿㷾o}qrڳU;@(fD@M& 1iJTyzY#:RP-E60Za6%BB!r7݊JY+[~Ʊ*D49sE Fǁj0Ͳ呦> S5w*J4fHqqpnyN7 ̄koJwWf!8@43fNlӓb޶a8<󋤺ܬ~kB.7LHR9Uu哟3A^{oW_l\iprz@`9u,壧O?x2Ū-E9w^>|{dl; j=*H,!aZ& ƒ:lGko,{7~ß.W}w_<>|j5oFB]IY4΁^_،IՑ 8+W:`CE6G Da+z'a`a(@܅ė@ n,e*eT[( (bݮ CS335J.ZM<ðC5uj\ % G 05q~ʺ = iŧacQG@@Bs >ӰGib{x6F)%O'Ɇ+5.^+*ZJ6r=C+Y-`HABL f,\W9[e+*VǴ>mZ)jZ^ވ^pGNdqRTSh5ZjiHJR1CbeSk [o[@zpG1z9"!8ri6 )C2!A-޺z)Fs|{1wz,;jo7;׿o>w|nlk]]/.:=?=|Nj`H6yˋV~ǟ/㥙 ?Lv5aè8lo.Od7:@$$'"&z BGSGO@8Ĉi{3}_ M~u/?☦fKo;'ͬYr-|qӿ~ۇ6q&h)D"CDR̄HȉJꅧ,׻:8iDd3"*c1ϥoǁHD&sRCgyȺgթ߫YuHBL"-BBA(I< @H*^LKG^TUPEVǧﮮ/? j1Ž$"C#BXElYa`U2f<_|7շ_91TBx五-" 0: z #o Rl=AŬT8H"x ;#aeT"G `1`u 6Uڃj-ODR&fZTADEX4d@SHV, Muq;q nyBSu^S/SHl<㥸92@8rj:S˛vߏcV|K۷6]s95Mz˾fn`o||4O?y_>=&$|gYCna<iWFWnݻu4~tgOnXy*LkV]3x*yMNK5A, ` כYk@TNݺsV;oVD8^vJTچ"JqE`tBH^yLB;M# r_/WjO T`(cr3t,4qDnĄd0rx.f *8iRNEK\2HLxf(3T9~?wь6&j Q//}?Ům]"M `Omwdb$"fC// I$ jo-C=JV-hwt?Tj*:ڀbHZJŝ MRFfVbԦlECS]H航KT}q3[|,7՚1v# 2:%JTQn`^5{nZ[IR?]h8pWOoqq׻n?4M9:nZjuno^QMJr>1SC)v} &T珟R6+w<>bٙO}-bޝ=?{q~~9,WkTx݇Ox`(ŏ77CߗnS"e%ߥa>@DbC!Rܕ%"eǟ?uzdO(9ۧGEx~Ÿ˥/lL%0hADB$^c/HD,uVÌ+QXŖ/3B$Ҷ,fD"A@8D1;f̈./ϟ<'fqrܟ<ӳ-KӔxh/@"(mgiq-XgDD Tc^ 2 vt`λ:0TB"#qp?Fn/}45Tafju{BNb!fk`b&rR!f@YС Cqv5Rr*H0Cr隦3R2K qlf!~wDd  -:S<(\S* ;:xZ|?oջbZkWYQnbX;[bc"/Z A$#r8H@ bJ i 㺹W70SD 0:L\p2 :: 8 8QPUL *\ &]^2XRK ";V\d^TR?ؔ(b]( hK)ԝAUGHDUDX_ʀNufdդ ƛV&lDt짋9& m `N˹~Dps{ӯ~TW_K@.?}P䯿W'O~gȼZ-=ݾwdY Nn>z7s?? Wn&7xyqAKY۵"F@nV`N`ݴ`6ӘJ#x+x|kniE#Bmw/?z^غclg\ZZH]PچKnXEiIU+eJ:PmQ]تP!)ʘ屴+--b# G8i9aA !5"dF/wrU cՄHjc1GoZF5b;QwuDY@4k0}5w^r/8_jkPoE5Ů#ᯚX^TCKr?~qrU1@J\ !qlІ*jGF PY@ɑ(Mc"8L͡.BrVXX͋iuTԟ{qF tsWu02tA?J(Ud(KLA#s@Db9ayq4sz0 Kh(Fwʜ:F&j"6,Z /KndCN14MTqRZǓc%+iX\`u0emeS,ٵɻϞ ڔ㏟^])ٔa1;o.?G?GOW|rժ G}OXo@f?_|68㰽7۫sc$&`n䪊n(޻K) -|$(ݬ>__]E"lYmW^zw] y@-jSnrF^t&0B]"Am3{Ѣ6yEAM)ifcֵ+պփi:KQ%DL"K֜~It-!KMaPe!EHQ+PـIshu$'"H Jep&Qwŋt˦CTAfog rP B4;Y R,$~?_Y|;ӘrJ;#06BB 舮Y~U"pEbPUO@\M6Niղ:#%= Bɡj䆵cuMy;:9ֹrڳՊQG3+nY9P)wƙȡhQS-y,RqI7wjGIBNukpce FF`ZYn՟ ZJ}ۄYCwfEf 6Nis..^3[,bhԘ)W,Ri4&8s(\]Yq?zz6oO6|w<o]]~ӟ~G7cbX̘Hқ돟xWۻYn?]]p|ql{uE&BAL̴$0WG4܊7v˦16M+.e|O?דwf D^w_yRYQDq 0LbiB weBX ;>\ʽ7U-Byu.[6̖N HZR=,r *3; 硷i,%4,rE( 4O8YqW[s@Jb3熸N$H/++9SpusG˖mMӰ k. D>2NSpYߺ}r@WM3w'i@-uâ7C 6M?}qrZ`fVØڙI,DCd\a 12zD9C5P`S}i\9.RNF \9sZA VՅSBp *NƟ:B~iW'@Uuu0/H \&sEs)ts~Eǯ MK!RFLX:f.J9ܫY 3:#W~d}چWOw:4S~I|QL1|yvM#!־`40 w IDATEJIK6Pw4岤Wٓ~+obbg< ̸Z6mX[~b p p /[g~-Vocbq9}q"[1AuJM 66|wMhH-p}8^cTa߯7j͵_cVK4=|8Af˸@n}GrfffLX&FnA*ʌu55Bt3CTLֆ$ƸFA؃;MqDpfV 1]K!* ht P/B"j *ISUfthc)>:m h4ì%04ABږ%3sYHB-)  P^}vojsb>0n H Y 4h5}߼o7ؐ=~ 4o\g aB)4A>ıZ*eLi7Ie֤V{fS4MbX"i8 B2bU RX K}Ԭ !2;R^y7~7ԧaųnVCD^d`P㐷; y)EG'_=sx4i{~vqv7v;ɣO?{>n޵mŲ{5 ߹{ʤO>_"@p%y?O?y&$;wO>8 n%<2/xYmbR#^'.>Yr&n.O>%clo4m׮fvE J˱Dbh 8EE dM'm$t'ʋ!$k'b`vD<@fkWp^I3H0w H`Vn- " nn=cwE(ė)y c%%̑iFXy`njX=E/ev=rK+)tHBC`CbRȏ#Hlfj.8_JĜR,2bΫvQ V*)kE?g?[B e7V.#G@(M$fb pJ07dJFp!6!K٤`v@eWa:PÁjp[bfhʖLE`!J Tz)%~mY7[G(@,D X35$_ԆnنLjZR؍>GJ6-Fh'~w6q'?nw2}جx60B ]>}b9(tX-%rq_)>G|6#fVTfЏ01~?>}zǟ?{~t`Dr1].[nyrCg f&+:\) !v4-QH!5H݊Qp<=[yǪeSkDl(^b00Xm@o}iկ!Z[U1&:ߞ=ޮVۛЈpd!R4RssKnZr RDDK*QN܌+?eP$X9#Էr#01cZU粻 m03P&ZJ: S[H9<&9 : u9:wC^K-9мvgyjkoΪz?ē#ϽV"(ZH\Z@Vܭ72yϏ۰:Sr&1>{lцXnX,zw}G}?Plu)eXټ 4ke>O׶w~4~H)kJԖ8 o~qJ5!6岻hEa LћofmJ. Eg._MkxcK28[z`$nMts?r6'Hhǩ }`Bbpp/HF(xxODEJqwf> MUd91bZi#Cj^J7okbYr JI4 abI8F0˻IWmhst pPي1SPS;wl)5Tm Lܵmatμ=#8"Ӕ'V%ԇ4nwa D[/2쨂"쑫#r ?W5knR7 )e$|΃zGis^f90%S6SU)B7Y6(D"$m0 T U $`!Dd$ ͧ))#3< u5 q 3S5/EqֵL<:*rQ wbxQOnlB"TRnXE`^Hvx"xTBwW4V8yW>{۷./?7ԵV ^>Bd ܞ=O$PB8eGqܿ[2"0VծmWbW9v; p<9Y&@)q?;]8?ܽs+ey?[㘐Q #S6Ӕhwug=n53\qۣi$ozU0%t4iaXo}z"J17- |~ajW11ddBظY.6LSήvϯ7W0av16$lvtIݰ?zYgU%8q\q5AJ@5Y%wٱ,ŏ{#3"*++,޺n hM Y fK` Z"H$Uys{ZK;Qz"P$*A2۶3Hjn$}wrb(v I%ʴ}ƫDV)qr "!@`PV3-5JpuP Ŵ F hnj&kCy~y<9;< ˕>֜뼽O̜>dZs,0M#V-:(adBĽ!(u4O.}cx2wRUkv*%Hl.'+dX j1˜Gpu0UmXFS~[h.L8~3(%Sawyw_RxcӜD6e`7z$Oܝ\]Ңb L*MkQ[\d1HL)DmKҪMUMc&tˬZa|\݈]tZ^\q@!#4y~EݘԈL}rPDI<峋_~ g?_~O=~Pr{%SH]A4KP#;̬=D5KG=! - NQFr檖` rd0*:M^+[rV0 h*?˺gW-"f>ܟjHϟ7__>y~ఽÏn? 7EBqHa DU AurԱn5SZ,9.Hn133RUHrRc ]x-$ m_!iZU qmML;CL:aY-{CjMD0#ugqûLYҰ?6'ǯ)}u<$_ iX&b/uiQՃWCߧ܅i?cBwwjYIc $YܪZk)Z]!€[Wk?-MfZMHUzN >.+Zܛ12nXŪ[dq$u!ХbHcs4 NaT|r D)EPŧ<5xѓgfpq8ϝ'H1j~'b iA0CXc 777J1 ZO%4}O_n6c~>Ĵ D(KIB[$I@ 8oַ{{}gϞS ! Cou"XEYyth98 d`'VE jCp'rw`j պh|x~I,1q\ɳ i2g/y83GgdF^H'TdBܥ0jyxBmwwoQ+$ N8Heh$!Z=t{*bt",dsf&;k"э gD'nIfUy5og+9Wpp2w"p_lǁ%:La!"FFYۻצ?կm/%g/[t`oX W3ĵ83w} qu _hnX?jjژ*@j3Ooޗ޽- >0Yj`IBD/ZVx'ߧ.6nܰ]r-ESa'b(cQ8^=^#y@5\hN'/_p{s_}=!'xi*\804C`"0ofjfM uuR/DVG IDATD1PZhC3kiH ˖KktYAʬ)CZdrx۷8DjVw9F "N!̏_|9꥔4<럿Jtˇ~]9Mfޔ92N'"w#wn_]!v3&Vsb&3o]{8}g}w4OyWľ a=1\@uw8 Z8BO=Syqg>v1rRJ2,$vJB<}}^?x MJV$JvgiSyɎ,RkW3:.N|8L%~:r5iǹ߈ FY$.뵑>_5 g bUbGhܪJCV"M5\/z i zI\VwNjOyi>HϱkRV%[fR̜Sf'60-W#1i$}9yC`&wn?sV֜tɽ Al:.7O6 g@. TUXZ$Z*I݅J.$P` a$"`ZǍ$Vj鴽=)v}OvLR4Oh)l8o> ym'8ZTC|TuT575(\NQJŚxE(;z>6@@l 7H-\5] G>Yy;ݣbwۘNzľz11N~o.Y"]#m/!b]B s իw Z,24FpTI&e__^oi4ժ=.0"yUfSgu LypbyP2/4u)ܼam.ѴDŢݫaIȡZg1Q A%M*ۯAyb7Ʊ039UbU/+P"P nZg<Ŧ#.9)H"m(%Vڢ1R `0}u=ǧ/n<ϔ.9eefg! i-duN[=5ZɥE"&hkw@ 'Hq*ZBj/Kv{""e~GIbu,q:SЭ65"+bvm,uTly2kBZJV3 `jffP֢ZxwؿzFZ+S8H*0{k@da𪦵qC Z=uCy|ժMt{w|Kr X 11ՙ iB&k1r`蓗繞j]!}g sl ayU$45iSuBiVZ `U>m_>1(̠V*aqAy= q eYf(D.r8O :@vW: geP\_tz}0 Wa[sC'NۓJťCnNg%`kljDlBIcjU9TswG=~WejcI$XA܋ֱc%rBJRUER/йޝ{I+%룛P3[BҒ)0dŽj(xV$̛(anapv3Bow'M\Ődiʢwnk$scsc"ff:;W?rxL "SLFB$nUf'arh1%,1fic "2e'n}!syVq{w|,7ePaVk?%Z fS<gC|eRS|nWXQrs|x]]=RvW38\9㐢<;sdR5`|xÀzkO^;½[,Gn|SUyܧ.8e#s"""qgvqMpcR2iU1lhOeO !l)!^>y,@sS)063[)ȑAfWc ,oz͛wo\sn٥ECb4azf\$I[ɣ]ܬ֪UKuZrڝTA$IM1 9JF V՜̝#B{ pVhw>y~u!Zs|tըxBzk\Ye$y Y"Z\ h\}>̵:2./tӘo`[r4y*8|8>F% Sηt "Ȝ͵ Ic?JKXnᥤ>~YdK 1Ltt I$ jLB<&.^ٱZms~o~wO>bV4a)X9L/vsfgl_uJdpҁ v~$6e#j*ѓnuy󋟼ѧ?\>xݠs-z1OS.9cPs9e ɁZΦ4L8%(c$DnUjhĔb?ܥS4ZVK.LVvF;*s6~@k݂R0;1CLCUkMCLۇSL!Z6d@f̌s3gw:V$S}ZRR$fLO YhsJ,KRL?ѓ״CEAGV3Z̉% BNZ]͛LL]dYs%ff&AZ^$)4k5yZ"aMUAb88r +sݏyҪC\]כڥ[ajj^R\O?VدĮAkR`bZM ",؎"jţ=xVisα=0'_~psLBCM%9|?q4 $l W35'&R=ݿÜp̻חVa-ա'8Dp6cg"ӿW߼=NE7ÙJM`RSW|z4hƩy8,}E"vsR;s\p!vO8vqX]3ۼ}gWabi9SVz!Xɼ!YԌs wzqeXmZ r Ft )Ul.j-D"z;!,II֏?- n2q tzXZٔ*fḽqW0b N sft˘ S^K\q&S`* P1|t$966y@dB!0@uj:{;Zv]ogʙ5L;!vB 7QZ.)Y+ȞaAP1S i2qjd< BN"fH?oN_ձAflԤ_m% P%Ue"ۊQq'B%H"HAMczus1{uTypxLZq3odXՃu \Jyw[m1`Xt]l8N 93?`IP ܛm0}h勇r9Ls;Ψ//@PuW9cSY?xnO雟峗OG QKv'FvAd"Ojtyޥ,18 |sS%"1!?}r=_}M_ a?YI\h5A`%1Xtp ݜwݾ~[?˗%H2,DEkanhY!1Tv,/.y-ư7)J^O@Nnf^@FX0r"JZQ>E]*M7?<بbnipueŦ8Dl"a|?q8PJ]fCp,۵03JAТE@A 5ڪܭj]*ąA\kv&v-խk"عFʹT'O4\B] R(P,盷xHD\c3Pxrwظ@)/O7WP9M?x}L9Djp6>I?Y6D6х hHԊ:ZMf-@}6nnUsw,Uj.PDE(rB!r'1RWk؂TlF tV7oasWUXsvO1rR ƹ܁0nVf15fm)u;8\lؤ ]{an+sEGv2.V0wbS@pǧ.^~4 x1|:Ni{{:]=jsvd;S!>p.ۻ{&39YŽC: Ͼً흙.{yuw2)~'jX-_jwNba0E&f"$}W:?y. :]"bn3!R\DBYYZL(m(/ܘ &D!BQ "L,lBNAk-pqnwi_v67=OE!Lvͫ~qHLDS#1qg 7[pFj$' a S\KTfFƔiMHD-X^JYl4[\8BƉTA%chB4yT/{>Yfg&3D"T62yo7חWfY]p `Qkh@yw7Wx20GjVfkB;K|`ЯN͗?xcەp[WW;g'^60>ӟ= ǟy{bIJ{hD4H0vPmR@7UxG!N ֣7O?;\_y>?RgzDb}^&"'p<𐜨DEȔ ȇ *zԱ0;1ZX^%9ͣ Ĕ!j%l@*QAdb omL̕B%FTaH ȄYHɴ]?{L"j֚ԚM Ȗᆒk46vXj]{0ۗoRk_̪ը-̴5%zMĨhRS%*NGTiP2kD-gQn1ZX,Q-s-N;M H?ӡSPߋzV$d&RъxDk5rx~RZDB֯TEȅ}0i"Xyh8?_o7}Αl=z6pyuRY̌fG->oW볳Myȸňry)9gz~7<[ěz=޹s"j4^^mӳnwj).uϟ\>{ޛI,g}y@, L1*},퇉U7۲:" Zu)ED.g5{ Vr1\TM*@,Ӿ" L)WfQ~rQR6`%b]\d!TGN Rx.GDDy: :an|qjaGP *Ю [aDFZK֏޻w~~իSwCw>.|}=9gI/,v&`ioC̹pTCEiޙzRԥtut4MS<+4\mN^x}Plh)Vf/VlTWQ|h6݈% Z;O~k_s~1}élZ>S!F)JdDJD TZn,Jb.% *Gr蓇?{!A)@\ȁDv*D[jܢ]PEB@p/i?<(vm^ۍrgKMlIg .%MBLDETy̾dǚ:bkseUGZ.ZX^HG9?Cp0Sjޱ ͰiZjX稅YY[\jη6ϋ,iyqjiܖ/=ij0jI**m[8'%0yyӐ{7U?S"D* f-$MM?!gV'՜=JN7 CLP4K0lAH+s%*Yۄ+@N$,@TNt, hB-YfJF>D#L4 @T곟.G é$ Swtn@!QGwvPea|8MɓgɻDU"E|712&>C" Pk@4i[j~\C?0Ɇf&~ޜ QR>9=ǏV/w߼/"xT PB tQ9#(avaA \SZhFyIDiͅ ^\_ԻuDdAt#nIXN[h<r+&UG-p@HRw7]=,:951[h%S;>‚/_t$MEQXB|r~u]>;[o~wwsu4հIT`Ԕ ƚ"½]3`[AH%{@ʹLkΦIRAS<:ZϯQ?U RDYxRJH@Ҷ磸F>Ύƶ 8{zt>>]F:2z!14GK4m_{9\yxޚ}s"~eiPMf-Lb"fowKRg8i7qNOd JI~OCxˍ%&r'ab4\Ddf"Q͌5,Ku}E{@Ыnsӓ^ͥz[ 'GWW۫ׯ Sj@(LWfpHd"if e#s0Iz()^z-ӳH*QpQK3uJ#o AIFATcB&VV'>ÿw}oŗ/Wܧ%nfQ3]( ՛Bl}1Ժ J>gT_#uw%_]I1sYQUFV#L|`HITU;Lee&$QT2_PGGl]o<lj3fAx UDZD%jz6L f{䈬#,b(]7/hp7|Cg)i 4G^>};pW卹;cFx;Ҹ9Gk՗N{݆>zl`6?sۋZkcK=@sݻx{ߟ)on7_}$gF3X s %o0h\I2jc>Qݫuߥp[ovjhIνRc[feK5̤z(B( GTWDybR?V '"mYsrvҰݻbO#\8ޫ@= ɘ+Yv! D^"" 5QpZDlh0S{ ڊuO6-! Ze.5ǔ7d؊[-Ωk~3 /*@̰ow>sv `I!#P]IΙlk#WTw2Sȋ 9j0UX;[:$Fw hD9@O@J:C}WsԥK&J 7ei91Zbx# _z뭧D( RTg'wVx?z(kv=zOEV&Lř>\sS9Ue&Φ57"j@ȼ*; Z=O(QEuXWDe}rNjZ~'S4I09LX *fmVD$/'Lp 8HZp-40C7DM^{ VtUZԸٓ|KY j1;HAYՑIZC9ܣVVє<1`irwvIuFUT >"b-zUp"w:HT[^L?}Z񉘒['߻>wH&]}3aRp!kv) yb]4yr- Mʹ!r4uF}opuFfR"UJ@깽pLQ\byI852p0Hm b|m<_|?akg7{bλqu~yp9}އjX ^ am. OLܶejldb-1O.DMٝ7@f`*[%WrS0(nȀ;ko-hABMD^}Ug*jE٧MNJDWz.,mǢZ3JVfcl& +E*.Y#!1+<])R GxT:V&sZ%l RT! ^Q ӜDM5n}̬ %SLkۑDATuUySU׊yxڼxN3ċph{I(1#g4b?{i͇DBZW׏?{z|ܢPPen@9OWW{oy+/nf!e48O] f R:1ixid]j&򚪀fF&5ژLLٓ?ǍM;MgOebCK]#Z'{: ozGy#ӬZ]zvE; q5^LI~W?8_7_}ч(CK7&wQUZԼvt"J`[?L,J$DԨ.DДzeZ}KG=wBo+N0bZa1yJ!z+"XI[r=[aks 0nV:#z!jtF=;B>(@)+AUD\̸ $õvB#{DHU[\JQ{Y׊Lh ^ËtIX6ֺ4\0 -Ƚ3!5JP54G7( P{kdu; "QJj)יj9i'qz~r{ae5W0YMSB^cĤV(0 &?|r|~>o%ލ0zF@nC"p.T%qAW27s8G.e#M=5}¬%,LY/ngk_S|^"&Lg't1 #Oe IDATzؓt #w&i8{髗\ߘڜ\r+7ɧbzཪIDfF,NK@ɉDHtsJDE0!b@ wQbfLl]>>=o6/- iXݺ۬)X~9&.Qj,I 4>+{2oC:n#xQL[4y}4:'iy7s7sDޢ-=P _ '[ahR6Y Ģ."fV_R35j 40( (P]?UZ13E+XCY͒s#1vmM%¢W7 Ji7u]GBPS!nWC6 fa?'<9v??w4%\հY:cTxQ2R6]J!- Vm)x%rb{P87t';#@JmKLAEi>̯^չz LB:eaՓOUYjY̒J|F% |}{NO(2e8yGQwm,KEɘYKߏ;=="+˂QQKurw'Ǐ?}†p{;U ǧg'_pwgk7M4q"I!p39R~bc:nOGQne^qdz޽ Gׯ^WՕ_X/A'$`@Hx83JA_4&Gm B-8۫ע`cV6aԪhڨ:Qr·'؄ML&>9j~N&S)C9jϳR4gxǾ2tQgO,ٸ4H@beead&,IYt$0Gļ|#0!6)n}ӫ$3^̖,5DLiFDψ;(bvHf,F1H/W_go_{wo:Scf:mfoi)uԥ]R0JwN>kF &-鑑ɪ9?yzy=ųtY;\]$4GiD+.H\K! FRaf`@X8< {陈l_$P2}F`fBGNQ]`y)Xs2ypP"H)ILj-e>Pu^HKmPנ[,۟#44+Q2ud5ݹ{g:LiHU;Q&!b6f %XAܢhgDZ:G9E%0@i&R5e!DjT乼luEDPhJ4ג4, @ԛ-Re/:9xJ__\?woTYRFcw|N,J5 X4Ȃd& 섉k_s᧏~OOs.'GAЏxzrgO_gw%€Ef@  f_>GZŔqMJҊō&pr">+i}$0K]?~r<'d*rqq|3+&AF2+{!io6J Sp:b z˸ǽzmOL)"Mj2M2j@Cp 7R.-t&Ԫeڻtɺ*hfvJcLŹ޾tW H;M;q M#Us#5q{B3| !VGxkUXERrs2;}ytb&36$ EA9VEicFSgfQJFB, OZjo{_;yro (5a͛-r%5T4)«"Z1$` )E!F]{*Hك4K= fH9[(S즛k8_hXHBW$YUpTR:V߼[LVVQd"B=ĕ&|M%LW.$r'bnŶ%D6(!bU3UYv̭Ѿ"̼~aA68bWbI" mTvDD˜EX5j4!buF E^!Y%`zj!iRisEA2&MSr Bk Ku(g7[LGK+^r5ۧ;[U9u,"v4__,2{PP^սQuH^ԣsիׯyW3֎8VAYR߉6GgVsp'1S8b1ks.k&6v\7Mjs7iJn96y]b2r<ž{&PSGpcȦ%EZd5hA޼ivx5)Q3{;DsO{tu6s|o{uw{_t<.{dtQ[FP{8\xxg!¬jXv͋O>9w)KSqbUfZ @n|u+8 &I],,; ~EXx# \MK6X w pCf'7 B,*P w5-% W7 8hhl\uZD᪈}j-UZeyݍQ2B;Չ%ZI 唒Th0d"fF@fA`0솗vw lW:j_ &k ((Ddm\$ZM'?qoY"H9)B?v]H/wwPUY"J|p&O_CaeRfY{>Xs7 CYRo Fh/` =zS^̎Sy{$[(bfMgGU!%Q4=D8B{e!+ؿ  (X$oϘUԢVMz#yuX7FˉrMO]]fI!3%NafSѱ˘sBaJ"qEo/7]}-c-n?;O<S@HsfBnVp=3r9cKj$l!3nauM2'o=wu7rp֧Cxh]ȃ[u,L%9p~'WÇ/ۄg۾,% -O`;9Ff章qecU'}?owhs0&ݐ|q⑀)e5LLL) "WbFb3nWbjX->-l[%=i|ϿH"Bt-lO[F℈PCg<<ߝfO2<25=0m9a%u̪<%yU7sɌ L\p_ݿܱ dZ~_}GR̒o{(̣Z/>G,mvKYt$}vp6;#;:&Nu9KjᎰ ~]=2Uso NqJV(f J"q@j-kK{&\2hͶeW @P 7riU@DH$fQK97n]fyDlؔiV]9܂vDA!p̘lQ5XzI"jcRonNfO_ߌ/޾ovTSoS7hբ nHFޖP{FPul2?E5V8q58AA)Xַ}+n9~vyN"N1 7G ΢529@+ZiYL_Ws]MWO %dbDbq3p3EFmKN[#U;8D@n^ԋ(s FZ@o]ǔ{RB$DH@ HL™SGr5Ov>'2?#m/xw-rXMGrŖ2n3 6 5QXFApEDK'0W0t/uΩ?RO?HD]JU/\Km'!˨n#1w]; }uwu w=1%i /Χ  H2/o~|O$QS{~O1 P"Z" XO߃u91v@G7ho $tnLBі+ WaW^*ugV67I3`4xX9 PSbL &NHȊjT,}J]lj0.


    m̔$ei3oPz|k)ZIWVBq㦮e)aXL2NfK|>PNů^ f~n՝'t5/Z+pq~6dE!ɰlX$LtBwh u1 fVW'ng9Ka脑޾{榺v^%m2%B Lyynõ)V8oT/!yS8:?8 'x}5kK!Bx8rj5 hoo2 Q˩y(L%, ^5 gJ !Iy?M9obۜ7%bDbi5 "2χ>/=.u)I`5Gd@,tsUׅlu f ,L -fN.ljUpll*IpNDPF6lu_sr# y´yEJ.ׄȪj`o=L_~UZDj5}jM3 3fZ!!$@^Aʽ. q(Z" CDJH7dŽx =Nδ S✈9X^j- ?jg?z~yq^z{87,y?}K..sGNIDl-T7c?ȑi-pwZSv $NiM8,}ƖY=4ԥ.i]q?~;׿}3/v w7/gS't5jz IDAT>YytNa{)s''PDQx8ߤ.y niNu NK`zz}䓏ηHH`UAxSq) r3\EH[`RۥU=L$poO:$ЪknVC"4b5HG)>Z[ R쬔+˰ |_DB( DRbad 3j9@-wS될)4Ooz;(CB`F+mVtw]O>6]ߵj󴼻/w8 B ܵZ GXcs9̵*1taubΜ2`0skMt@GhϮ fv(76kSúj{YZ-5Xɰ$eqlt!xXNYXAȃ81 |?.wSd7V'.uýiUnFLL$:,MJĒdw]:W]Dܷ;^3baIjhupn5 ]MqLb/YD%I0$d"f9bf;jIr <͘h lx۷Øa=ۦA8 l||XH >OǛ?Ǔ_?Bj v`ij7E` nqfS8- :t@ЙA hඌoASǒ8wu`]#jwxLC]\բKY jW~뻱*m-ZNx~b1ZY̒ɝB?d[PI a]ra?]N]VZj'fffJa||r 0w=՛BڼQf%rJʪ+ HÊp# s(Zu(sr6[b,lm4mLȒnuOE)ٺBPcsfYy>"f[7 @WhvI@jpSs,ݝ<8ULJ! i1V~"vYA9Iʛa{Tt'.e+߾۾u-ۙؔ%b)<~@C<_+I۹g|wkHe]Ż0pX#&Dui;1D(70qׁ)"sݏ dTgIaq9?[]N뷀a^qlV4±!\v/_n3d&Rk=3y5SO/͇ݹ$5A늑t:xJ^ P2wd07H$AoU;nf[t} ۝uq?y2-`wcy,_pĔ&bXU+sz4l$w%H(VzOpݎ dᶆ7CV0<~h74|};*cPsݢj!0ɈDB HT$64\ $`aZBaj(,fPH4ik. *hd9Z{oށI01 $m(nY`_gM&D3cB^U,*Eun跽Vj\"jjY!6^CB^2Yt4 i4q:e&dHqG2tBƄ|-,T (̱ժt:Z$8KS0ZRepwTFjI6`FdI" XBAug, k-?:iI}`DltFw̬U5 I\zǠ)R޽?w+aLB2NaHIS"r$a k2 :pPjfdn &d lAUc*((VEĨw>wλa+3xywB 䮓ajd;?yA?Zշ/Rr6~, ωSv9b-ְP L= 8l±1 ˜גH7tîI0pN"hf#~G}WcDo\;ZM ݱZz]D+a9O>t/M_kgۿ߼9AG锖knV*`QϞg}E}gfL|, ,n(5JK!!{[FY>BZt>> `f@"\;.e>K'H2.yfLisIbhyGFhGV u߂΄,,YR]{ХcN"M.3K=i^%jdI{ٝMtr۟ ("* J mzWкJ14NͦO\!RŽbؔ2f!2ȩߝ#eDp~3p9DB#XXӑ*R`.(!T!woO?<9~@Ķ2~%eAĉKK7 a Zjt&n3(Aiq`/d4#Ax]_0zPy}O)eɽK"DDR]~y9 >|."fna󞖚ro&?RiWJ ЄF]t$vPC8! 6zH$9nF’I,gs-u)s`H$-ȼKYœLo_ݣ<7{gJCZi^&"}x_꽫'OЛfIkWD ZXbd*i ? Leׯ>|GAస:D2c%$eV $$e17Ffʫexd5Fa:#Krx}/wz)]ɢ2 RD@ V#1Qwf.NG{0S063d$ȉbmɣ50p`Bn`&Di. ^:AN#` UPHȴbYDBnoƒ 2rZR, =nemsJ4,ufh` Z[ҝYVB$P jAH̜ v<㡸p`b^!TS]"@>W}@HIưw<Ī*TlE`"Ri_s7j.RsM0A"ZL @@Y*4~EG/A@1$ .Aެ]:3A)e _.8"bN[U+jB7X8YV)h{?* .ޠfHÀ<VL,Az\#;"OnxV#qqyl:nS XO#38gDc#8>d 3s9wh8ш&^"jIumA¨`zs@ř%e"ԪkvРDNmKNEQ xV%Owo10% =xü; teiVht,}LD,DmaR͐2$}N0hn=>v},ahCy%B pFh(¸2 Hf G#5UP':ɶOa?,QqRdPV%".%Z楖L @-L3õ{DB(BY,m0aV׾z~eYTI1# G7-* 6"Ź٢s6 $`GUib58>֮BKxr#^jke $93{Q@ɹY4U!`(NduP `D<4M,UVw6C'Olvߜ '3tQ BuܧO?zW@eMO>vx~y^p)"Xk-=_='HUD B@4:1uGB:B"sL}"pGe7/>h4ON:2PpvB߫) kDC`#[P `&b̒_2S`^눹Ṕe ,g.1%9.q,BݘSN j@ dH ^)FSu9n7 7 Z )c(X Js{WS P )cjvo;rLrrpV,jxuø=12 b ?D'dpȈ$-mթQ#@!$N4\nSt.vǓTi!#N)'&6-U+ 튀9;RBn`9,ɵ3W H.6}t,.5PO:A0q Rt>>1rh޳33`n{#uuk$b V;@[KXU곟xr;<5m79DxadV]1 TN@b'U,م8-ykekxnO% 8~'u2ílPFp2<{v+:xhzfR~t>W(6p(9K}OG4DjxuJe~U1sሩ-] ,RXQ-܃0PߟGEm/bYNGO4y91۷w4  ]ѳ< uM=dڻ=b;;:QRJ);M8?1~ǿ':W%7Tf"f tANa%4c#@*A0A8ci֥=b1SGWt>[.ȹ.t1jdw tt#p^=%ٕŽp̬HVu94:4Ud2af{y04GrB{4Ts /婖J*LD$"VPW/^:9iHRUvw~{9G#G0mPQ#LHnDLsz@0E0q'e臟dwYą_|?߿V9ks jpy][17ʕDVτPyqql5)0O^Y=$nhMFpA\ FL84 g1hOaw[O8;>f˜f;$W^sh1כRrpPDRn^}Ѭ9'7B٣RkYP4XSK[Hj9>oXBSܝ5'pL,XEDDi=^<~Lw߾' IDAT^~lӇsk&#NKsq͢vunO=Ycz7(V(ȉTUTo/ǏWf~Y}q)܉+ܹBZ }T7'pJ#tDVfS<#܍ _ -@Gi}k%9̺λ(:iL%jka0Aτ,T=D$!j!AH ;*D(YJ'Iut]ȉ#̝Y{J( sskHV;p3ky>- ))G%R bZN2O7]JC?RBi5#䵽IFxY5r6R∾Va{rB8 Dak seh@m/'Gd70)1y*.25b^n\@6zu;܉ơlr:-}򵒓≠=\9cpb5T]Ts c $De-^=,`g8C٢ΠLMeY=d%ĩ*X#<vcwFb,NR羣˱v׻W_Kw_[TjVC\/Ǩyw6˜9*IwSFFD%d[9Ҍ*nvvrka?RLTC-rou7t\._6"N߾?˺x |yícL ֚R^!vv5Sx<4W yp{!iǕ_:tTȥKDk^e9M]ͰιT#0Dr@Y RGU˳[ȾPr07V]aiPaagbm/^~ۯDeVͭjyʇirzan5H!|=||Q֬=&j&y#zvS_ A T+ 5O lv2GRnlpT~]\Teo~b{wkYAhx Xu]qM]yf:}?4 ©u ,pWo—@vy?/Wj9LJ/_^s<>ϟ?/D&beG20J;Hº1nWfbxʢ^%Dֆ VQm]O\@q@s"q.+#̢a:t)(oNSD!r>eUCmSц"0‚%zfBh5Q[POD dDL:C@hpb#H C3 8(oJu.8nśoޥ`^<lQxa!J8 ure/~\ZmNA^Tkanژ­FXX l2(EzŶ~tF紒ů黔ux*uERRe#-s> Dn_yYN]J}/auc$e"+g7{xF9E@Q͍# jA  KqlaێNիW!*TR +Jԉ9-\Ƀ[T; oIb'{;)+3'.PU q;I,Mp7 r:,8yueX$HY=GTՀPTqH]"= #:Σ/Kɶ߿w)YA9,F>wfp8i{Ӕa0)yvbJpD "k.rsU^Ԋ/ĄpO;ʹ.y]?yq?-Ab}k^%Zn~a R^dV#(6fCXj[)Γ{fhAawX'QQsꋏnae`ܵ@oҙđ<81s|?La"lI&DJ7eռhԥ~uYi^,6cF$ UжC,@V0leJ5DfQyn/?6iw!ejΒHj]{Ayq1~^rʲK6 XZJXJbs?K)U@ CɣI[/ڄibӬRϰ$Ūb7帮sw3.RK"b fkj g/WRxLtOTQ(q fBJCGr~4ªyi7_v.Mv Vj(\^{,yay|\N5DD4Q^ 0<∮t}vDd93HFqx\MdB,IE5o鷿W_~y_|{"8畕>~]R#91l֮m߈ 9RMk%"elqn7i2֣2%NJ&T4sZ;PeSX3RfVrr'[%Q? o/QoQxm(Bx93%Sg~;rf<xCYBM-D,h3^剧/Ztwo? ^2%HS TI`pn@+E/قfee+t|(˚ϹIqk"[8r~qg?UN˴594Oo\>l/k<6]a8<=_Niy;^0Dm7}9fٍ4ǼuZCv]U~hUT K[f"BxT;~ k-::fu^OHRx=PDLEey<~˯~o؏}Zv ; n$hGg?tb2I\be^5/kP>w@B%uddՈMғRP$fZ>~W_gWTd H*d"xDd3k16wÅM}aD"LfCa"[&]4[/_vL yܚ$"8tIjfIҺ.~S&{ o~\9dKO \W$!3!d%‘+j-bg 98ɥV$Q=' 'veu9rꣾů~frn@Sb(yr3爼m-6=$I_ov)f\j7D2Xr1UDR?Tjinq\7,p1~fcfaIC/>/a8>lLyۏsmsZgke>? N҉['f枆R<#,ҍzS0`=̛mp *Ç͛ׯ^;'껤]"fK%Nϟ]W?GRts) ërBW<9 y8gu))+j5LLڅWQb$ޔe.#Uxk5!I8% I5 G/"NݐY:> QVAZpr]*

    g3r +Z#)4fX1FƩ4ym^~~yi4khR)QY֚K?f]lOy֚M>":aG@IET9Y7LB"R7ZbQjvz~{:.7$xgG Djyg;AK˺H^޼?ckhzoŏ; %!3kr[i-*q%HS &bf-xkEZMz舨%a lZjbmd ibq@G/~o톾Z{8r˚?vݫ+vu]Z!!<%1˫SaszBbe=yY.PvXl%A;ŪYy?r7jl"ʼnd9Mw//sM??E_e>ͥA~v,'"e9`u\=C"@~V2N$IqV9{Pu/&&Dqs'#k$Z]قyMmka7jRJ`TχW/g Ishu {Y0WX^RZĒTR82"`|uh7UfDhsS?zx/A|=u1Q2ZKϗ bLcoRk J"b7C6JAkHܫBS׵OBФ$+X`'B* &k(pż3&,͔ء]XFp D+yvL, 9[)f}ӧiZl%wKI%bR?fXN𸖪}3P`nUE$L 6'zWrkm7R)hvimū b9!-#-[l쮥d] J}Wû[\> 7+ǿc$xJִ(p)r!CIZ|U{/HW}>3;^1n~E7϶_}ś/^2[L&V9o n]hRj%LU~3^t֭߿/kNCt4bquZX$@6/TӀt5kz3 JU$af!Z7鋗<;?}<%MGJk1G\:=waZKg&7ApaA$ (%`*P/^)nhvW8Q4qdo;CTye.}! `q+rzT~!̋{̡̢XD{QRRP& ""+@`" ZŁe цM^Dh@h?Pq)f9I#4 MeM"glSD,*2',~VzS^7버9~˪iլJ')/`&³9@uZ3xX:q>R?~\9 V-aef]^(@Xs% Z>!PKKzJdDUg;z7Rq7^ovGw~_}DLRJ%u?}?ß0lB-`z#Z`Zr:}ucd: 椝WGAL@$ BP+ֵOðMuGu:eYpHz~󗯮7тvWtOyۏ <~ IDAT(aaC醲x<teV[׭`ԑ:y̹9A ^q>>?ϻ)!pZK?iSP‹I8<@^S7aGYg"`%7!9=d0J̬5-:3I戧xX005c"Q)Y阁V$$BD^tc tBڠFlJz4ifNȒ`&BPT`"@8UUhP' 9CL3@^,pa5I(]q# ^XjjƾdNVHl bʼ#;6OxqY93Yay:Ӝk"a0G~# ղrԇ{&fbGY)ƈ> eW#o/.ER-܃K0P@,k4;juw:Nt.ƿ=C= U>h5+s6fsܰE2ʧqwכK7hbv]m痀ln!ǻx{]^HY;-{NR0>IjR%(({DD &wpo N9:ռI'05/@t,[sa܈:>Sد~rI?A("V"V,]~<._q,XyՇGjGАT)#XFX#T;@`~b)5eР?!jc?* b32EŹ&!^ ̜:!I,RY~l *dV&bjGocTq9{zUPgq_LJn]C:/L%:xncZֺ9%M b:j-K;p׏k3"u}o@Mh Kd/v~y<>JWө 4 )잔s Du<矂,!V݊ۙ0 p_qWbh oa u4ۮb+Q`izÇ?~; f i4jg2Mt]ٿv7ݽ%DQV4&XY yeֲ(@ҹQ7^bV7WMFV6Qǂ#?+@pq*/kQp{wtEZfçyyiSw&!AXLG@uoo:q/ BwNSq?\\^H%.877wnrqڑ6hA*kT "l&<8G8J$@.JSp2C8U:KȮׇqyqo?~ґqk$= יU*kStw϶iDE"")<œ|1*h2Mf!bV7_&IH;ǹ/[(q+|`Q9}kiOq5@bu᥵5ً[1:B45'MIE,VRb&"nV~HfS>'􋟿<ܼSn%,rx ʺӲ$iRHk9A s. F5B!q7PKIit8l34Ζ0k|Dw G!/_ n!X?_uNv He gb7Ui)~]T txzx^R)if$ 0tnЫe"O79647,RL["T2_jQ)zZ| QJ56IKByJdKlc 25]NQRR!O˯~o߼}wjDlÒd6Ͼ{)KppRMu) ho+& 4wm  ;}]SiZk:k`#Yv bb9<= TH$hfYks3!]xRzrx!Z\ETAKuX9kҨӤ)^ ABJ [ !!$ bUʼn? ]#i P[<)8G3# .i?th.UYu:HD0T !}RҎ4TEE:y:h"PjHPRqws}bO㰻_mjVE4X(v>Y.k.3hfkb+5o# v(f>l]77?|˝pe)Dtv7ʈjNazzv8&Ǖ_`Tf!9ZůsRĽʦHy}:T QQs|׃7o././^^RJ17[k=NțSE; I n6U+yE(mJ^\%$@0/L,Vj S==vG4A뫫F[:ɉ,zQx)pr*3.M&Y T#ANiX1~חGOG&Miw~G<}jpqu1y*:~{D+њ_ڴS^?bmY:`0(3ޟ'p'B"7ѸrmqD@$I2֫|yO>,Lʄzl͗;{C{H&j.Uᶜ-BD Y~O_|z3#}y_>ӛoݫ8b\o:* 1();™hZK)0@3 0o J@VT߯7șĀz9{=0ߤ:3#Ӡǹ[kIS`׏.W#NFmlglハ<739{85@4ŗtw,qVWiMQDKڎovU^lu ;&jfܾys?}|s8*l~#;v$|SA-޳^39}qv+9R׻*"a":n?}~zulVzm=vlVZ,s9eiot?.z B8Y3ʢ,I8 b?G|Lr$`ueS~l ?鳛$)y=򗿾~ V(=gOH:m4X5j#p $`XFY4G'Éօ FĔD? УO2ݽ{ 8oǴ<"Z}fI9Y-n֖LKNi܍ØbB^ afGRZ@h5Гan粸Yo{0qfعFa a046*=ytyshqÛOc v3䛇Em{UxTS8Ȩ8DI<Mq[oԬ07We껢y>#.Da`N6G70RI~_G/R$4攉w߾3G"9 DZv`dHyD @I%ki͑'y- YXUT[-^ A= ,lO^<?{zI"e J4O)bLlnѼUW0(eߥ 8[ Vhʞzz Aԏ>d'^ĩ Xwq0"<0$%!R"f@ƉXy]Zs3hJj#aRΔ)"c<2nOӸ%3<܍\Y#3~AD bQ'x$2rgD4'!Z9;c-4f˽ǹ'+AM`Z&j=^^65SҔTZ۫{f(t,ƞ>&*cl:q=!H+3@f{@?!XQl;:=G ydd(<< IY͇ZQë6D:OzWC8Vn4Mon7WJs ps}:5TH9q{q^ XHi:<櫫_|ف2Z1:8stP@9yk,4@XCw|G"DeYhƢI8Hέ-)(DKέjRufY1gO={=Aٛ{%X8%00+Yy.t}%+0N{FyJg8ήD,*a&g.vGn> ~Bs ,gBD?u(dr90jm9|l㸁)=Biլ8/rʚe)dM*@ojaHBD0wcN)5&O Yݝp@T|i̬lMɸD7^\V@YD5*=g-Ĕ$o]s닍Y5Ɍ` w :L( j':/ ]7?B999'3GpR(oS#>Yr<q8(~ynZj=[R+++qCv%w{Yp-^{e^]Ik ȡ`珿#ܡ<(^, =< :2G8ɨDz_DBi 2^O_hNAf?vw[oVqytZ *۟Q>k]aW iH:\/l9M4^ߤq7/E H|ϳ&(; .  F*~Ow8$݇݇ñy>Iv*'ən{Es0f;1C ""BlNBf/j9"\Bx7ޭȝ:ւ(rOP1BQ 阂bw\V݊AZ?c Q*s Hk\ ??{)#_TÉ:Wn={aD3j5VD 0JJ+3wB44fwԩ%URФ*Q^DAī՘k0Gk<y/ 8WkjχW!v1iRg]ovۧ!K8wO׫z4~4_;c2fК߻ [$:P[mV0 ).Sfh IDATZ92=GJȝ+<–6﷛nwl4/z"w(G `&zGT cCU [GQK# D q"RZ058 ̬g G/ R@F U4T B/>_飰5q|̑&m]zDpr>cD֫,6o$Nda$󺿛BW2TFlˋ?\^^l[b^KE8!J)!_=GHK_1y8ݼXNkpy;da:.,{ 9gexPRAbwq1O[ia/< Q#P>fi~ُ_\]i߽}_I_|WYtJ4katwR0hfTj[բeڶW|& G,ѓ)hI9R1LA6c~嶵j*i~?yp hNi4ւ N$= 2|'VU׈D<sXc&1(A$p1wQ06/ wQ$!D4 6ܾ =š 8͓+K݃0{0" v.GPCZW<[]*D (􇙱&G ȉUĂ-<,Z3+^ oGnA@[,ebhV"0ᝅaGcLYX(!)iڋqi20ř 8Y9,YN9DrV& A 3q$Aq @*m<%M?|_}xlCV)$¢pH@"=2H"^ L{3zvVZPH)i+fv|ߎLpB`ԟ?ً!i)uijRoei<\?q{,}h9Bon\<|{\l Ww:gAF oyϪvzDěˍ֢΋kF0Ø.o.Npߗb*ET(A0L?^hu'p{oۇzcϛGX|8u=v)USb[*U6IRSZ_dؒ$"^ 8wM9ωF3U̜ r<ܕݏ~/:|N_qzh (v6k#¬Hf=EC7fR!xqsHԣ) h]iO ީAAA㐗C) d3ƒ IҐ<лW5Iss'$4 jG1g=!f."L0AA!j bP󰝢{3A0̣5QFD 11r8AD#R=*dBċ;Y3TјK:9**HdH.><'p<bLwwǥM;J#wQ#`:kՖ- <~4/Mk*ã-D*6oû `fs "aHcX,7bJy|jem8q|8?~tmJWչhN7oεHa_&|SZ$5KtXK ܖvv,qVws&cN4J=rQAxeiyew՗_~_$ AIt8?#0"hP-6 ;";测\)aL֝!\k!I&1y$tT@Uk5"A%kw(qS0;un_-8)Ø<3fA}y)1S 3[O*#| 9❀`И6XY!! qcjAe9dH•=GKm-6)ՙIXV%@IYizV,j52.հbD"ܬ'% ssRJL@Dyi*4 HUf(Yj0MO>ͷwz"\"$e' MQv~gb0hw:Dʼn'ϟNp7]=DɎ榢A! )??ao_0h_["o*s>lr}w{|sa}ZkbM9$At0u.(z  L$UfCݿN<,t+bViϞ_?j5̚RNDzO4_^?zRpps|vg;j9眒`neS4(ZHfœI#g7|Yjx2ss+mYެ sj^<~kM)Ih9Xj /^üϧ`wjs[41Cj3a38:=BZ?: fVbk5"{8ٙfZ$XuV $U$NuT8;K2xaM%o0rpܿ}wvu54&q7-rmNÓ?cI՚ypkJ!AC] D0ix 3װ8Hngygy#YehelŦCDL4nrӟ~ݮ[M)%}= j}v_Z p%$\'닛gŠeK3'탻vpW73p;zȇs!T,ȽT9˧yDz<}{&?}uѓG×}ZJW'ay0݈ # QQDUDkXjisȺ5M[g\{[m8ys&;L<>~.* <N$AuS74ww`Sk=4nz3 !V>S?Á@Ωg+,Gu=BgY _ɍݢL.rN9'y`V$'L",Tp;?VI\|fME D("M!uqb AOCUΛf/)q8ÚKq=X`N=Lf-*p|Lmܢ‰*@`RG]W_ё;qV8"Um>Lf\ן>o~KqYKKOwWvItquJwww7$A, P%DȐ 6zщ9%:w](,ft?1o(m}Pzy}뛝:޾4r,d%Iag[ia| XVj+,xY(HtD|t/h/}2AA=i sӈyj;K_SMn=|/.ov(~ÇRV|AIEI6Gqp./]?u> (la w,@DzsI.^=pԖAA0D7#n^!va=yD9=yvDsie*y*y~XZkn "l{p4 Irŝ !${ Q[mo.wUא0q m] Au$r ~ HNj^[Lᴯlwb4f,7ȝ)#`)k3t^DY4';>ҵy k𬃄S)ū9ÄDHnީ&0s@D{&9`a$Z3we$Z"j["Qkk5$ޚ0%09;1'Ƿ%i0-yK"pD ptKpqFvcJs2)gǟ?w|zj)l:UnHYPɝ/>|#83ogr%SP=Zx|prGP!:0y9y3Zc4PސbO^^_lj)Ԛq??{5:<`DѬ?DUwhVfd\g bVQbȐUv1w}Dԑa3Кkf0DRԩ̚ j.o'/6jX ~5#㬢۷_'/>z,[V#03znwѼ變4"@- VX@y4I ?E|[0 Rj{N,nv.R4L1Tr)[B߈0aTd?xsuhONyiE:[O2 iiYȨ*I:DD4k18f%nˍ5'7Af-zThĢE0\TYݦCiBX! FK* 3K w9HHLhlp Phڨg_OwNj3\\Vf! P˟Z>sHh5wMj˟_ =37# WP)6(唷mő/vO?zjYy"f^e:8jC-b́oB༹x"2,`x3j>8zh͂é.= xrbnid͒)TX`Ֆi)$pG1v3izݻzww`iVOpܽlO_ `>9(Hi53Wͬ#j9Z],,$SGbmjI! C6Q-hL 8ĢZW"i_|.vaKejX. Yw s07Kcrl7ɳ!=rɎ%ٕfi2"{f&Xb jDSwU(BVIfޜj4'qN( 9mwіeFEi{Wq;Ntrp.`^!6^M: #B޽yg՟>}zvv\q7q7`Zv} J J,#i&SEuQQbg\z]Hٛ³5nAL@iR$ZOTJ͘m QNY×GesӢ0PD\mލH(7<$I3gĘ)w2vަ !ڗkN HD0+3=^'fFKGϞ\zw}{t!"u3OOt3UcGCcd%;A1 ן|rGWIhk E}oR;j}z||ԡ^,=;8Bf׉e;Nv7'GFDfJ3wYǣNj<>" (:jI"##$$ rbno116E2"I$¥iv;[,79snh/..O..XLy\~}ԨI[v#O;AĬ76ZG1׮t/,V§ۗzrz!aTSA)])H^;;yy{9Qpb34Ή,7 _"z'܏?~ݸ e29,a :(L숦H:l"0l8:C(@Jȉ$ @pk}%D\ "'++|~GgAo s{"P$ۻi7_|gŊ\~U+ޡpzlc1ڝw]!;{{!:Mn."Zd]1M<Č^:%6b!z%[Yz/vR˟ IDATXk޾{ag>=ywj89>T+\ڗ\3饪 8DhIkALi1ҒTp` AӑY3jTG-mDe:2m[u;łE?bȌaZ赎d굗$sE -#ދ)$f>pP+Ih`>{.gG5}s }뫷i~.MSEk6\r<>dyrS ‚X<ZpXYH(0>SJǔXLJ^C L0m/>__rlח?Rҭu"Q>߾]׃`qPCXխFyFq\>>?^0̓R ʃ-3b7a(ϟ?[,ݰX=~~' ~.I67O.|WeqXki-qNIDzaȬfY@*XBn"d4GO~V y@ɣKuQ5XWF)}q?GE`(+: H9V2}a 5i7w,z\۫vWǘ} ʒh^yGGrOy'LASt-3[q*It8?9=x.5!QVdp6;;]=yrN̥S֧'?UTӻ/WfyvF aXCE,=-YE?ZܓH l-QVUkYÐ 9d"PbfX iPz5P^~*;+=NmV]Xb4w0% hު簰hbpоYT7.DD]`aKGTţB@REz,,t:Y!n"6V"t3$nMl] rVdDf̳Q)9M+Lu""jEBT٫ GG075fX"3r L#"> 8H |b&i$*e*/{U-emYm-G+6NCSU2R :J* ΀GVs哧+㋣qFi|~z#ҮcQ eynn@3ܜnVrnA0XXnRDC 3 Q ݻ5` ovɬ w̞T(I.ui'/WKU v}n?8ѳ7GO2~q`8?|rjFlSkJwֶJ '$|\! G8~3!4af?p6 8t^7"2")I KCPc^R~v ݵLi;88ۊWJQ)@Ñ8ɣtyh bt7r9qb%!f8KLZ03'~["Da(]xHP!UɭD8{D[7HQ$VN _)DBj16Mn^^ǟɥvŇN/ϖE?7woջl6Jן$|L,K\.n~x<\Ώ."'Q|?%j[N'DRAIn[lJ_svOxG"LlqOiMon~Gzv\-GΰIYC &5.LLMF"&jQLV-(7ɰ *"E12B)(-2-ӾL)>CQ~ɾׯ<8;;l9LB$("ڻr nDmp]D\"|hW(HUidbrw^32#|./g`Lp!QJƀt bB{aB̞Hח D-2lޫDR}ҨUA#IQ1Be"! C,䞒`dK̃)Y̡:ۍaGO~b9y <h:ONͰ8=0߳ 2k }j{QAHoFpLv}Ȑa}\-V af⌈H{: UR?0E4jONNVhuW7̰]Y߽yVONOvէ::zrv>;7fykj,kR" tа}n{E."x?e)meS^gyfww?~ǻqBd"wvmGN:Y'sl_y2FuI\3 ost0X3ֈ3*"gw t; DPkn!XNM՞B"pyw{զ N0ۿ~|/~ts}sv~ztr\:%aXڠ"DԞԔ01S L kfWsGąFd)ݪ7PE%dk`;X"3RKf&fd%M9*@tq g:e^}aӫaF-c#(GL1I1{&P"CU#XZ@ sR + ˴ pId64NcI_~v}/^;m^`pO & ai5ů>9?~Hɦ/_}\u:mݨ~y}us~'h)i>՝/`.as!Ѱ!heb:'!,R}P{Pr6޿6hO=u?7~9lZt\mx;wAcqC'<6g=~|X ,>"~UJ QW{sx5NHP"2S-6שYVww}dPf')\HxkSq,a1a3jK$y6o姿_mfuXeYv~np{rHY=2%g[ $U)1n=pX!O0VEz4N>Yݯ׋aPɜ}~/?/�ywv~~r~L p Nd ̈HLI ;9d3Y $45Q#TK:bb$Xa5X[M2V%UUs$u%X,stҮh3TDZ䃙h!"2щI@R2#`H0sF1!=Y牤Ӿ뻢M@DZTӽD=3-Drןo,O7~rgVz i^?DxvvS}x뗯oFn}n>MZgsSyw$M֌̇:0K{x I[ DDudRծ+DQy qDviX-EYq7?|}qqzr.h9jy#0 7Ww$GݣVX*xct]OD>a7,ON.2Bku}'zvb$5b-}9-%r^o'vop'b-/$En'eJ ox:l,Pc4|zZD wB{qD% %ں"[kRp")!)ǽ1b엫NYb5D~w>?9^|&PPѨ֧ ޤ- L|;6yMv/L,tA{&FK2SdSF0'w)VkKXU뜰x#FxfC\UT$ &[MVQ:)*BKduU) m"^z(dIbnJcsژZC?gempעZwUUJzjuwٌD`~o~sPŚ=y_.g}tEv88<ëW7l8y:>ÜC?n6H)^Z)kgYT X)L 6׬ f4N ÍtDP9z YUuhx>[f$aW}oGˣj9 a_~S'=u%AϨû}yy|6,V@KQ"-ep$>.E[SՎDMXZDK#!83<2}"tomV$ pis:y:#o'G3"F"AI>g2g,}i2MX֫CyvyX_˟ݞiרZ 13E $B 'U*La~x=:,@dx8'TNdDPwJ봝ߋPN 4NE4&X4MD ˌ$NAD٘mr [{5JӾX{u'd$XHlF1{fDnL@{SZFxˤf BTn v; -sLjC\L `OaaxY dIPwS)˯?:tZwWi'Q@;<^E2m|Ct8}$;@))<-jӔA SFrl$2*H6F .,L N( [g3ZdLf6?} (Eem6o?uEJe7ijm$Ƹldw޽PʝQKDEc@~4֎YX3MXĢm^=,B(eۻZ+! 5"%z|b[L'W7ۺs.cCѣ~:|\:"Jt"-fJ5LXfjaf3ձ*G/=JBons iỗ-ѥ'y<<݉N^2Co438M2") !Ԛm1,mZEJǑ7u}ZۤlilcpR(BYT)wop>&W{{y}K*"dK*ݰX]2F !0fcz!™b%S"rR: oU«@@LtX5$ Z  j|r:ZN`՜w{73 ,9gGD6{e?"d԰9M,"m o"ܠZ,=I"~ -,f%BJW)ɫ'Ske( ĂE&o ;JGXz$[_d:@&iIe1Ȁt2 *6W'$ ?ɛWo[E1Hғ_Ldӓ?{He?ٿO?To?lO>a1W7kv4͈HDm釟ksZvM.m:,!3!N4ziM$66EcVpIшdU͈2Hb:fuߺmTH3mw_}ѰTu\^]]j*0GnO(Gϣ?# 9#(~(+fqKE{jp4aD jӆL2RprB"#<*(oo޾ǩ tX\V泷XBdORN/O~SH貙$#(,KJT`snJՉI~|~-%21H!jGVHVmǰ(]/* '.$Dh ؃Ҝ(uH GGGoܾ<hw{zL><:cV+aLTRJ Z])7[ *)fN[D$]EE@Q E y뉘 δ٭$IZPfAADu6 YUe-f"ipiFzPaYl&3O ua>9Uj^1ISt$\`t4[2QGJvDz$ 93"pkD`:Ndj<ېV)*"h4}>ľů>9{yqqz^?:<_~*<Ҽgysnuܼ_|wZ",ynsw;WT!r"ҷu[tW P뾵-,(2dZ4L#PH !ӽP΄33aUJs{sݲ?0(۴F RV&(b|/|q=}X \\<~}6B*}%7L w]"T43-"ògnnh$:HٮjfNUIp'<LR)W ngԐiq JL5I;@+Fn  fbJ0Ng5 ]3! xK1q Ur@OE'S:2Z= vʞ$"o|6b08iQ$f4ɋ]VrؓvBD?u~t^.A$ggG'omduҭ2PC&}̜zzA߭ܝ2`ɮtwBm sk 7W00'aPPU\Hrprh$dEÂ.q]'9inΏ׿]1hvdw߾Oτ'q/ztyy'Ï?T4K{LoLfEBD3QJH n9 |("2kLfPTIӓ_~VVm1t(88q'] 4|f-,C[fk7LevpcNAMR0)p jlU3@H#xq6Ŵ:_t]/˜H|A8 iVQ&J)s#ol% sH(1^ĢE3<'lu~s秋~n+M+/R14oTBEK}M;D;LjiPaPQZ5"2L)pwLwӍh;A)EIhF:ht# HQnz#H5݂D>MS:KVI" ZH,]I#՚ SxZT"JGdAIj ڀ!D)Lhp.{%DAՏiǏ˯?[/J)鯭izuZkg5t 2%N[qd;NRg Ȁ EƉݎnwMggkz+VjUtuΪ5<}]*᯿fkϞ<{q=vu*&3kdm>zCy *(Goj[R2f30Nt>)Zcf΄_H歛Y7%U"abwaZkx8є c-Ʊ"iֽau_#*'kUH{ǾDo߿7_[o֥n%Dˏ?|"1Uc"!Ex\k"\80V6KIOu镥dv.WNH2X @edEk]I7wm<_뛛^Ϸj^F֒bRh>;Ѕf(JLâsv ^T)y0X7{I,QH"<֬4CZ"[a]p؞$ aݳ!#sB.**$""2Ji KS p Qrn`D0 =( vk0»a\EDU49df}rb?r9'7;LǧusRfۼ:I4vj}>>XDTH3mq~Y+/E"v_"̟|уVP>M~fGDׯOh(̵yjv_Df os4`P -3yMn,@xZZFJ$mzq-88rN$aȈ1z}ڃ+/݌EE.ǏEtXA" $j}]_DHP֝Wg2nyȅvu]m4A\Gj-&_s#L*,,ȔH2d,LEXәLqWz," 'SJQiG3pqb} ~JU/恨eH(Ex $NXBY^4RY1ޭws3aZ11k`˺G9tT&2N X2ERp( P2H7C e_nD<*q^LHJE3-t Boݻ8rJ1d[ތHpb}T2jZEQ%??GڗWߜ*>XYt,ʀrrCoW: Ѻ!8/D)dQN,]9tynZwH n2"‚V}wu;U)ȃp8nm|_|c}: O4b=~3SasM-asy&a{*(@${sYO* &Ŵ& }?^THY^ a"RhW|ݯ}As0yD/>alE:*8!$9!HKTdY9W<+ҺM[Zd}B"0X۵ 3ոzq 5Q#"#Dk&g VBhi Up0& CrNw~<Zko={x8N w뭵6SEXJ),K%Ո Q*"l=ŰnN^k uI]g@yr3 ŀUcL=1]$YMJ~úeb&yc"O*@͵.y'Gx[}g~p[\gGvǗ/-0icTg;B9≮=4)-ޝcT,\dG3@ ޮ~?=o z|7kJngaLJej?1 q n{>mIUݭG%VfyQ}cJKVa>@AJ 3o׿/~[")9&Lfmw%`!"N.H@a*Rn/_0bpxp3B,,|:yuƕlֱCTw%GxC.Hd)< !飄 Fn}Q6[6g#vo:- %e)*B66[_=p"w"A1T=+Zpz~b,}:Ʒ>9YmNV'jZ=ytZ CF"dsomq>nu:umxfXdt:ER lͻjG.,K/U]:(ZRבhdYwi?x_y*Ʊo'O/?yq*Z2'U[rSpB`<9-d )U'f `ffm[eNú @k)!,QDL²s6 ^7c-VXEօÝ)ʋi'yT[Ľqo齓WWynCYn 8=_?ߵK<9DfݜY8FgH~ H 0&pNK%֔͘lfZ,GܽIDRV%-B(-L @ ὧH.E,Tgf$]D`V!Pna(`RUzqy{}[ꠕݣweVˏOgx~~U&tfS{'XNAwgz (޾"nsw'V@*66Y_‰Ud\t{{O_Ip B`Efa'rPmC܎n}5}>Dƍ!fnsnnamؔ,D"rpjQ9@A3S-, jQV!fD*9mX$K8b?u袘d dYD.Jv;O/7ON_GM"=<"y1M_U>k^>ynj]OnA2a,֣l̺{{-"a3Qa)$OJZYh2M~3B#(o*$W>~w;s;~G?pvo[ooN.:u9r8r`lmQQ_21([vz#o9dXoF.en͵9H6gj] y'ݧ2@uJTơ֡w?ǟ<ߜ1ŏ~MVtk=<­f7HwJ|!XX AHM6*H8[" IYYLwqV4hOB$,M{xcs;\=|xλ_Y׫Qk),)^_<{zɫW['qbeUp2[Z pGA03G `"D?e72]P|Hܯ;7z,> +i:DI؃0hyx@T*,S씪5p7J"Fᖑ;RP::af8t떊pVGp_]^wLJg/~yƺ(1'h/".,D%P=J,aK;,EuADo޻ay~X( KӂE’_B"ɗKLXY ղR?L_쁋/鞣=&,?/ FaIֽl%ۭb'+\8"b#BAF#:Uba>Vg|rs#2(;?N_nj߆CEDHe%b&䁵-.I*{ g,60HJNm򶫫Dql6մ 3*eVP姿?-Zkb8keHoon~?y~s{={KT"mneZOBE3/v1 IDATzAe1,(P10 Z7 ӬI""uO Rqшs>gɁEE̻KA7zR*[Xvϯ^|ushnL KĠ61 $CڃHr aE._vgoYק(ٞңpVe*Z91+sI7Iu7?ȑoARR#- s7˫rPn-!HQDټHb"qWS="}6[~:v?yvw{_<~ܧ>c=: +u"Zy>(#0w! bEMP&Sy: x@$m[ZOGXJ(3oXe`% =F,7 aNjPYH ̔ 7͘YUYJ+@їJfC0b{̺9;1,bntlpYf//oHxjf:q(A];;ypzڙhlv 7f+9[Td/63,\;KA,A,wgfjs$ j6SN9 \y8,OA=o]2Ȗ9$L!DEY>_}pW/?>7UeXzݲ))ߖ]$eqX|Xan=P{[gfan֧Z20apRđrR^@`0% pLN [w:q;qa7{we-Bqm`D 5߸ٓ˧: x^g6bz~u%s;K_?7dƂY"8Cܹl-,;mflKB~;QWV'=ʪPK"=2OnŃFdX-) Gq\ñ1h)}Oۗu@t͝R8URRo^|{$a7*97}vf]U"aAPVəaܬ^<}Dy>x>9Xo˿ލ"B*suP | a@"!dg쮞7wU(܎O^UdW۫} kBs&f'qK*JP t3߼zFĕuH6P;aG0*ZGIZe(JTą,9Axr'UU ׁK]zf e/HAJJ~h$O, 598UExy$Mr)0 qUK ޺yGJg+Y /[:[;F<5P 曳JޚpcMJo><[ 4MsS-C],QJIOI*Uaa02QNܒ7bb l^l w7X3ׯfUomJ)tr8>yuu{s[>ݪRAiF,^t*Yp&ZtP@Vw7=5xp#fx¦C_2WVBU0A,"?͢d;,6;38!gA H-(kvRLyAasI][ '!D¿/nV9= RHilsbeE Q(qqití u^S;o+QOJj `Rpo- yKG80z8̇X{x,⍨EრP!gX8D{Ei -/5D2L %22re&eIj,Lg"&QB"#aom7򅋇ɧO?}Ӎ6>s#uaׅDr iidR#)1]iu|fPމ# *ϡ] w2,)=P)ZѼAB$U@)yXYc&iN\YHf H3~ϖz Y6(áK=€,}>CwjX{r6{ <^Jy>9$=~|3ӯ7CAwG6&&p7̔yv籬ק'u<7}:}$y|j yhsoݤZ+<ܬOOקgnpB/M=֛?ק/fPYdXԬb xD5k&J9av)[.MLق??6fnO=? dE`D~@֏6/ڨr)Ӟ6㳮VUqc&HE&oz0BbAP{ WnqZCKJ :%0<$F`'_jfLlk!DK3UP@8v+$ %>,Dpfe/cީ-4L$ =Ѿ,Zj׾ƛo|G/_nooOOORqhqDt`B AHEITm[؝d}X" kvxnքEprKn.%X$@ѠGR`̛0AP@RVLлX>Y<-T8JLjna!C ڼ1m{|\Xo~,YKow7(;|z_ٯk)L쌁ź5o9bU" q ǦaA?ʸ>^~:#r袮[?lmmj,Bn>O4M''Ðnw=ou3%>|/=zo'63Q( wsc Y6D8QaxiJX,,[ +12E~|z, ,v{J$p wDo ĎnMZt9GLOys 0ޗw>UI$1:,D(U7V͢iv`"! "$6 Q`AьaEZ4MּsQVeF1$-)MaV0p5^ t%_zYbgC8%1y@,1!"-ˆq.0f P@aH%\<8}=,1,(znV;lOE9iwRY I9?%w[G0$R HnE'֍@`F)ˏ񇻛aBF{o>V#Mv&sli8 _\ax:n/I/T=B3|^˸N.d!̹]!£!sj"۲M1+tꐃP%fyy qUWkB*L Q Y1 w/ @` DM(KhA-4G)2G,ow%=kw:seWT$Ѳədz^.֭u6{a:zZNNO,U7q\}qK)@c0^vDXp֬Y82B$"l2 Y3oڸUs򐄔eÚoi-dsfn`B$j)N/dgٛO.AjB48V㊘}:nm߫ʃ|y}z1NJ1Mw[Zf$O#ޢZ`%N; Yó 9IH p6 $,PfRhw}~tvk=;je7n:]9s"Z5f5S"Vy8[0X@ÃG~zqRjmW*Ձ9 e^>yJDo'K?k_{cZNll`w-Kϻ0Ɯ߷:tUwnvH HH DB\p% !($ @Cuuv>Z9{bվBSJuVZk1>g!0'Z:1=y<ĔBu7Sx7=bqm=3At ²a3}8s0oH鑬e8R7PPndk #xg_eĹY<#{!#yg, fIpgYJa  $SXBLu6gEK"0GM(- $2 Փ/=i)lb$Lk ;<#s29'iDB0-k;y_ރ~rPd@&t7ѤtZÇF!?$?;9#X_0<_#({Jvr4J({{ŋB_q%<1|W_#թ/ZL,֫fNY}9,e?7W~WӴWDLE[VJÃ?w:R<#-2Y%3I4 VJ0E6s)5G?.G޽s=u|yA-^H zRUiv&ȡg4ٝwAr\ X=;BUI.͑ a&z'D:{F4NehAJE3xSU,UHAu# L빹R0tEd,$%2yaX8zF_u4#uBdLL{V%ճNv`U!&k="> X\(#[J(MXP Lwr)a]: @d [Dd6'9y1ncބ4c!ƘOY#qk 2(͈ f.<8}(oװʂe7sO"?|w/K7UtL& HuZ-e'E l8ZThPxy7Q3B[3-2͓FL02ZE0{=ИZDDқyؐSVQJZXu fCvS[lZuw{$Rܹwі.EJd"2Xt9\_?{_w Ob$Viiuc\Jƭi*Wt|yZ\Y&b>S?OawS 3MAxs~E/;nV*ʣF#tʍh=$ [rH&*Ja=c{;+N{x_}v8}Pf˺ՌPEX7Mg-pJ9,KTeޓjf',mip iשBԚ1 R ffߔINLKl|-An2Tƞ-Ux  M E& $ p300 "Ӑ D6Hs96dB0Ut86\Ӷab ;lӪL+YVLᦝNg篽+>ų˻ݹwGT)#<QM-\C.}& g} o˘}"ff-Ut""F ZGo7O2,l53F!̈LwIJtᬍy7uwwլ gsRMCAZ$g/=v?w EMDٝsYͿۿ@k{=;7P9ۻHӲprѩ΃{xUDkk77eT#sWFxYf\K"4iV p<\/Fb2OnUF'16(01{s Xe_n:V&ß\ /Ί "޻O?zy L쎟k?f}*9ٽ;i oNU%%&AT4̜}\d!xDXDXS"tB-rTYjAʄAHDsHaamuTkfɠjfɾ?&q 4ZgK[,Vq_ VaQH/n̍$nͮ=h:27zxs7/*gw/Ot]U-)Z eZ*HzZ8O_ϵ։wy%H-!.5ی1 &U$ByUv̶vsiAQ@ڵj[֢B?߻4)k:UT*2O\Nw<8\t1ϟx{?{o~LʌÃido~N?y+_W|DD߹w/|87u]Sk&A6nًM*\ m|uo5p:糇9G\!!đ'ަ `FR1( to6̲І̽  K)X~Goܻ}N-kO?}qhͧ\wwF4Ҭ$WJ_7/e<:Oq)i?x1ݞmgiLT)g(+8Qy76Vf!ahfaY[Hf㪞Pbv#N/E!%m{!"agI'0zD $=&U0'ޥѱYlUctwݻpm~^{}^p{$!i$0}8jͽZV&f;RYzKIVU&62Nw7iD_\RHϨ(SxRIHT`o_9:׏?~,Goܗ"[""N벬C@.ˆ%L)3[?/?{g`?~>]77Շi'~z"&DnJg"nqraL̖d f*Ht*Ovw_y}?n6l) 2% %l;瑜E@T)c=}Gw_ytqqALg̼>>~rx^v˵#Q{8,)iQ&^1P<4̠%dpmvOo6NmB tַ~ؙad==ŋA".ZcTJpO[ÛE\5 l=7/P穲󧸅{wz8%Gg"# i"cdSdMpZ7Hx&j o z]03";7&Ek<N H34'B-3B8T9IHFz3>jz2` f)@ pb+Hc&e<|_~~?x{~ѧ{1miVUóOO/.kݯZn7w ųϞmh€x|zޅ<^^]>w$:[ʊYz|ٮK"̚#L[ !Hsq#yx ,MDH&QP()xJP9(܍{07cqPB"sȋ;Q=C.~mİ4$Fac"IudijwDg#݌YYS) LxĸtĸK0ABFd_{f9h6)SOS"9k_>ů?o}~{?|W_y}AB[VV(YFY}ZBĥbcK!&XiHo>}=9ۗBBA$URiYef~}: $$NhoFJPAt'"-8v'Jja~8{_ݢղYq)N~?[w~ZB@G_%_7Ƣzi+o O//bڴ~9ś|?tII%wm~ =x͒~jY\ƒVd7n/fD?i!)7{d>%n8o}=wa bh)zpm xu x^_bڟq{ aLf(!fI"HC:Դ4*f  4?cDL#Yt=N'fAN x(z뷒ی@DH 4-cf ZxXXvN@pP{o$MA̜I,A2!eH[Y4M q}K_??z×Wy7[wbZrfLgdYJ-*6@3(UKmjGK'jV"*y?+RZ*sH[nK[HQ=9{uY3{-jAqǴ`'`KON29<:2Ҽg:dLBrC~峓gߜ\]SM$w~?~&wzmm<>|(r}}pp3#n7ލړ'җ+͆6F`sp̉~H/ݥFm]8W6\~tf`pdU.͡E»;y[V-E2AiWyߩ/_.nPF8zv9I_xt9+e߼֒,+)"X=b@jo?;9~??Oys7Wקit"o/ruXM3|~t${Exsww/wE$6xAQBpy$>L =Z:1hu7OCCB:bI '(z@RY#(TN;-/eyk?]'tPSIĂu`@`R Q2AK' ϞYF #TJ[c" <,?xۆp נhG#AJ= " !aEPxxGZ|TNL0PN:)2xN񺐄HNXZ -g@E$3<-BEyMW ebJHm=`(F£A᭵HE}ns bfe}ˣW֩D//_Qwgg kݘSEd8G'Gp$a'DQn1,9܈*ݻȫWcs9x$!29kZUTDP"zIvjcTr8Dx2g2c+1A y[<$(bPҷ?+N?{|އ}:rml>pxw_}?=|:!NJiH F"C,,]-UKfR2n! a MjJd&rB4!O;vUҲGzZeD %e@[ԅn>̻H"SѽG?/GbWx3 E*Ὣ LQ81"@'zZ@3N[wBI$T(6y_## )2^="}$d-$R2:%u KV ұ4kKUIDY&k&Ģ*69 "i3amBf?=rGoUtlzWLwl˪ RKN)A{z\@=Y:O"{#I[_J1 ӵTՌ\ex0tMF'|mѪS~=* T}˻"cC(LL>{2.j& [_???/ݹA??.\\\d/p}n׿zRsevj]W|;\@@dn}AqS=]?ϕNsz8D[YeV癐1",*IHdLB9( $  P)==@H GFSP@DDkFZTVNXgZ(EtR_E0" pdH#MVRt#GHL +grp#Q"9a)\fJ*mmWWh;HX͌TŃd›%'6BDU{IhHϵio^0K_497v~quk;\n(R"b]-5/Ǔj8/n$ѻÏxdX-.܀07E%"D,2Rg%fݻuX32ySEs$$[32FqAr(92LBp'0o[~OoٝqxqW^[?}]kt<ۺQ~}LRN|ʃګ= m 9[ezNn) %AɪQQw+")`NQC" T2m7xᖌHCt5)"C:DD/?}ɧ _pFuZ S_݇KIbr7ٽz~uG,xʭ4ALܺJ$Y|+ժL%LfQ"b` 2ˉ(\p`,ѠHxǧ31s_8!iQr\]33=AkX0ktkeMAHe&5nGb`օ)n!JZwbc'澬/.2}l#"Zhh3Fp*Z"YTreYNSJ}tE-[kkFa IDAT,2[\^^I#H$9 `£wK$X?ؙpD#-2IʜټEOa4AlT33U c[$z|8yg'ǟOͿ~Lru~Ob2Mb%h#gv:gs[nWIws&P)ٛݴ^^ e+@ADN)ad IARIx8i>,\EFub݊I'%Ϟo}-!Jw~o}wQ~Ϟ~#NDRzvYӊdfƗ?|!>3;2V|AJR"o\\޻S& IRnvڱ $X9|y#AI 0$(%4e=ܺxƒ ԣYC7 7뺞("Ʌ2яWNDY3Egئ9TX}o0o,d DI21* $SIj#D<\8f8bLKЄGE7OOGBH6/zy=.Y d44Ʌ̞4OlݻEd<[gY7ƭnRUb~W=,P"pn[e1On.#i7,a+z8=GȴA`HA\ ai*,AF[J"d`}[bCVoZ|gؾ|ß1ͻ}m7V=C;Naan]=8;N#Wp N=$ز>zyeRaV K *=Z()֙"af@v}]nS* bk8+T՛O/uЬg{Qyy9N{$6{E\uDSxIiӵ((:* V&QT0QPdgf쯪DTLƠ&EZ7 p~z9XO?տ# :iXFLf׶D S2nUa46('m~Zo"u.N~\ZRĚD2L"n oI^ܣ 2鼛,7W_P OwPwVSv MLDn#|wE* N&V%L#J;[x&(ѭ9f>Oe|J B&3%ܹi<7tԭ,~hr)e %8ɇ &v*TIA eAHf$,u7=gʇT*I#}~j9a*fj4vZ 4p9*^FBA CyP~XUK%¾BhSdx%vQ[7Er1sV DH`+rɪ}%jbU sRb$^/g+4_k L TU  `mwp\99Hކ^ҵcWko<9g# ) ż) Aeo(H|>OFCC_px)03ӄxK^ @$뮻Moz]K܋dL1Xy?[`fb(0*D+c6J~ ʼnc,MdRVQ;G0$]fK.5#p1J}OomO"V AFa<)B"+`2Rz-se4bbh-V]نݪ`%*`530c$2cn02K*V糽x"w17-@2*f ֡\ DR1h*yPU3dJ5W"jVLHRUWŢGԴpRL]1Gf\pZ]X1@'h> &F@V $R*pBjPm!6_QLJ-m"V͵!*j mVX;ڃgcd *QImy dnk'@kGClOg?>!FIdwO~Vy(vf0.f橭 t9n{:;`oc?#pַ͉7x ^j6g|߯+H4uq\]>g-/sQ0 .Q@EfZ鹩Z:hh*D)BD+ˋ͓I%QL1PHRl|.h8$#)2㳝i.B $9sjJJM v@ ȢY5wl,Bh[Q Ujo0&/@VЬf+ YXK{Ct`kCYu{g4ƀ @ʠդM`ZͨMh1( `fEJ6ZͪULLP h΅^ @F˱Kan@qGLu(I; DT0*VDE˃*Fs&侷R QTkcB bhȆJΨj%f(EDJ)jĭǧX_h`ʁIH*hf2y3ETkh_;xbݟ,=~o<Ї9B:Om\J*b*C?nx/ޟ=K++\vz /C61M֎J5uே. ;@B}?53бhnu6ĀUT)X5Em30-|@DDn-MVk7yP5ظ#]$/4/_ƒ>qj+DVRxi0W(0ζ󐭖hx220d6!sD$ն]&Y+BdC"VU`7?yȑ#cI )kAY(آc I)e+A{e)VUQץ&H4˝*"Ǵ<(羋)FbݪF!2C0Q\ q\K Q0Aڊ̈ !nPU3BNjZ;"+9sH@6Ȑ[JZ)U5s"QE!8CۡTs"E3k'PNmP-yr&8IFiϷ5"9\kա_̬ѣ?-bz嫾tgvλbB8g~ױ+#uݷC[+ż:*Z(pH(}5r,*֒. FK"FQ**ȩ;x䝭3^xދ^t)rRL8FFz'ol·!3'f0)R2HniEo FFbedgkȐ<`ZYY^Y]je?L?5s1ŶH 'hmк߹mmlv) >|wQ0Ӱ}`ZREur_T-XUj S#q\RɈJڔ\K΂HRBDC`*V.MZڑ檭Pj['fTbA ch K8!u@@V5E"R"/1(/N۾oBˢ1o$`F"RÀC+_|i׏mlxÿ5<c}^EU4@~ݚVSO-~ڼ;Ξ5󏜿4Y>{2khz/}t2Z(n 0.=12(FT"/P-Ҙb D@̨R~<3vw]~GV,-/u!uGCO?<䓧f"R r 4#_L9.:|h:{Duiiiey܅HUMښ*6)0aY LTcbCUUTI@U(/r/|1]hIBvQZ%Ȩ.*P(u #RLyȃo@15B1Z 0 Q@ۛW F}@UILV06 F]n&̬DE (F `FAZ+1QbĤu#ck[<9ǷSv}jg(23 gC{/y%?:9<]__OG_ï_ӝwKEha8$׽Z3Nj=۶?Mxɂ1C6|) Y@ h9oo_K W'KC؅LwP&'L)(q$LZUmC^%Çx4ݞv]JwDZ*Zi6 V50E܃"MRm>#B0!(Xjeԥ=rJX RF25 LE%`*j Ke>H0H* @GGߪ1ƾ/{PscYU@%_E hiWnHj΋o@ƣ .kT@]&UkJO0ڱ=s7>}oqxYUFTvf{C1Zw=a@ ;|O]{_߀wu.v))ÐKpPXlSN0*M&&"NHSuc/9gyyt㐺.R䏿Auc@0L,JJ9 #T)C:& HJ{HaԲ:,V@*"1uk5Rv#`q+*CK+!ŎU*2r1U5FZkj*kǠLMǚW$DئYY.6yXklo@DPjw0"$*yRHHֲ>m̡BRRn4J Vsr1@b^{1S=z1X;Γjiy.CɋL$i5۝.7^|+'1Űlg7[Yͯ{5]]_y4 )$C8U}<޹g;yqO QmYFCb ;S!^pxڦo1[u8=s`E ~bZ)D ɪr^*A.'.TYVIgnfӤV6C].(u b$:)#dS5CR$]܎ @*D@f  m|,\+,ZkbѤ*hQ,LlP *\ +1g& @!Yo(}mjw4R2k դ3&^_?NDq|eW ϥs}ϏCG}_{9Ysż3;MHk@L}{~|ގG<8%U}9x`eySϜ4 FKE2!C`JpǶWcW)Q.uTL3gϜn9r2xRBD#RVhɜKَЍfy1ͫV8yu2`[6 \E@D9Rka D%2bV1!QDZԐXJH!D$C\iVhdbx" ЌRP?r1uVkB+WR rf\ Đܖ@%SPm(ʗ Uuɓ'=7.LF,/}_#=bUݙ=t#ӝ=hGNx˛o>^ͯͯmw}ׯ~,؅@~/.\9iVRMד|sv{29r*# |y.)$f3DDPM 8?yzΙgNuKݢ jcFX5ZTKшXIEVB.X‰AteI%@E* *Q>WuX_A"~3VkZ+1P\ADU ]Ġh :N fHMĴ*(1b@)#-xUŀkEm:C IDATxڃ_y~Ͽ@a/GtKKKi7:}jk;omgH`ky־_wͯ}戣xwwѡ,ZlL,ysKgӽ\'1~봊^t *ĥIB7R{On.~ooyeB]ݒQg Qf$jf:̘*0hfhH5Wfcj ÌL@YDBI) ZZU  ]hR FڪT4g( aץ9 !#VQS!VؕRb@n LUM#+C՚N]AEtR >vbzUZկ}oySps lX c '<]J)e()#?7׼ux>н;UDC"!x3~Kwy_"Oo|?vahyyilT( "S֋^tY7B)ⶇUSovgj:ݛS< VP2M3Sj$FJ\P̘*wm%AR00@"ȁ BeKԗUԌ# )AD bQ!D"h-EĈ00"ׯZ*vՕGEd: 1U{ov"ZLI)b in>u B?었 oyo{>OpÍߊ`w@V,OFGren+\thmڝ^\] )luf3$0TDf)PCdD)Q VEMM!YZʹX C. ASETRJ"e")h bD0ELm`T DEC`1 !")r`d}<"xKE_[?vt]v?;3UWDž2F]ץ3g}?VMo|[o}s:޻{ Aȴӽ'q汵]v~dWD+W<=/Ҩs޹=Tj]Z]@Y3/lNCPp!)QԪR% hPv- "Uk[NTa$+b"ejm RJ~Y*8q75sΓ'5CD3~w>^^f\͗O\{krts۟N_x^vqQcK˓ʈ!L&mO{9!Kvʼnj`d2TP0ZUEA@ P6PU " *I O8quy:Mo3-Rt-r-x }ѮKOZI8=;'Eo1hCZ!"3wM+Rf@ 'NƉ=xǫ^JUmnx[79 [G>qoD dGyGwUjHQRZD)\Mj[MwqTچml\''ǷMy OOe7lfl_#B3;q'M ̮xpΓ9Տ{ŗ]SZp9w/s9Os9<9syr8p9<9syr8p9sΓ9's9sΓ9's9Os9's9Os9<9syr8p9<9syr8p9sΓ9's9sΓ9's9Os9's9Os9<9syr8p9<9syr8p9sΓ9p9sΓ9's9Os9's9Os9<9syr8p9<9syr8p9sΓ9p9sΓ9's9Os9's9Os9<9syr8s9<9syr8p9sΓ9p9sΓ9's9Os9's9Os9<9syr8s9<9syr8p9sΓ9p9sΓ9's9OsΓ9's9Os9<9syr8s9<9syr8p9sΓ9p9sΓ9's9OsΓ9's9Os9<9syr8s9<9syr8p9sΓ/s9Os9<9syr8p9$}l3IENDB`PKF5.EE-Pictures/10000201000000510000007AC37B12F9.pngPNG  IHDRQzsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDATx{Ueǿ9p݅OTէ*W@`=ՅuUF%d. KС!aWpUЅ[0DX9-MQDjBTHRaQ'si QD(%<; x(-Sa9Js"R_a>j0xIDƗSX 7CTyV@*B!/Hu@窺8">%:EDƩ+c@R}DT@2 %҈Pf.8r|&Kb+"=,hUgڈȨ4c.PU]]"}>@=FkࢼF tzS{sb#'TDt6,,ߓ??Ddl#J|ۤN)9uNJg'Jd㑪^U;`z֌QPՍVOUk+5C98h--} &Ka 'E?K zbm%yx_rZtR=i< N`*ǘuu*?DTPGkc/pu 0,&MJrbo9ܻLwKPHDNq OXD+ܭ7gC~w(n:x*نyb\+`%| 2W 6|k^"C195OD&=."r>4Wv6Rc9"4_&v.T}p>xݚNECDz9N)y-*gΔr~GQڄzp &c>W:?@,Sp!گopnt'I@#hH%iWNVy ,Lks I*02CkVDF@ vX30E lW$2"6r܋aDfN~ϸ ㎉f0`l04)gHG_#K6aCs{~$*N\<qĤ8lqXCKȓwe BmL;zvwӜrE8{jPfKq q} ͞MX-wIj {p1z=7-N *؝BOFLx"rK7B N`7~sNε"ہzUDzqX|u %\ S0KR U ;Dd2FZΟhM*U \0pvci\+p0˽w 0묩U}>5"z(S"rnNNzҗ7B"ֻ>:D$0_:*mbљ71NV{ 3F&:fxuEvj)dWSE>oNDqN \0xQ;hV7DbZ02vD_rsPJ|Og,p=I,[B,"ScNѶ-@_)X'p##KaO@8!K̐3ͥۦ{0CQzØa}a`,.("cvl8HI%6(4%hnr Ĥ8@c.]qa zG+b\IX"x 4/bpoF@?B{)KIHY+6|w3 C`!MX` ͶQo˥=3eܽ)3}^Lt)u FEs^B0'~b ^|aqW:<ܼϋq޻c0 Xb !+&'Ul 48_<`I#Z LSQ"n ߆yk)07i`+}*EOarMD? 3 -D`H mܡ{HHCZ0݃оJ= \ݫø56or%Cָr:d\?%LE/_l%Pc!o`;wPNǸ'H=&a"#XjK':9'o>Kq3G~_QڂiD?yC1vcv[*M}nLj2| hبHςI A5ӼJL-)@oôbW#wfApvV|[< ~ ZhX~Q9z5M( s*D4؆iфcx‚ wM( oPl "_C2O?BzcceXd-S b(q݃qa?{k``) #br13/SN?,.zƕNv#K} , LtE2"+7 b^K08iwxAloc.V0|1!b%BU;0b: %"R(y_~Maq/=7fwcaG3r2F|wqLɬ<{eV-)g%FR' fZ19UGak1t98|GDҦi5}ulZXX)2k3$`~ db 7z菮)6w2Ty3Pl~1(˷cI#~j[֌D\Fh436LL1qi0k\Șo1͹'!-dIE x9 3p.6n )a0xװ0b^e3OS-Kڻٌl&\ ]}zb)nFl qגpġ?Ǧ*̥"\c̈́8"9K=Zh6? *͍˖y(8 I\ID NqiKIUD֌C#vF#}nqISyЗp JĪ ga94r/8:_n7I,cu'Feoմ3$=Qoyq[hvqbҖDZZ`>6pXqm'"W]"7O! Xnc33},?s"ƹgRs0/+(Ɖ (^^S¶hP\ۋ{P̉F"R5 hq-4D񢩪r"V51cLr .EFĔSDTW0: Oc+&brTLa"q&m^OCjTy1T VPdb%OB[lDd?FMDېJD)IfeR53LEfLe)9ے ·Yn\͐Q\grsaF"9GUpK)!{[487\zU;DDyaB~0\?$Ntk/X8[U-yuzD̛ ݁{Ďģ>\;زk3Mu0Ps-*0U7PIG矶kl'*DTq2j8U}p 1"V$K n;f'Pf:&#scL61cЯ_?^{5ijjb…47gݽO/c)S/2%+E}YtժUN}Gtʔ)u_F){/Sz/>}d%,@mD\S=R nMݛ֬YGB78؄\+fmü/m1[3q᭷Z6|lذA{gx}:Ay\m݊f xaN>* 0{jQ6 ̷f4",/W3gά6xŻL`.&V+D{+tҷk^*!ދ"m 2&ml|'u(uRsNޠA.I+"/csL5'OK+*CM%ݻuy睩}Y vr1]r񼄂 "{D5kVbŊy,8]I%+FL_X1{;w䡇PmԨQxRmKĤ8_qȐ䩔={|yt#T1'6c!EWq!vXŏ(qrnMFح[7[XOHhy8U~~pĒ%nL)IrJv֭K}( b*ytv6SK{{ٳl4wRJe[6$7 {:O>^~5BDtͩXx>K/Tnݚ7\Yڑ7([mCUYp!3fD 1vX.]ʼyXd zbĉwq?DGO99ؒr8<У>ZwڕQc؆ ?+ WJ5⦛n*;::o߾yV"^DPKqa3ӭ[7}gF??/ϛG+% 7KDoj8 #FhGGG{%&nj".L4N$ Y*߷o_}"ަM.c@r$!U?vH\pq3g?k\S2k+`{ssB~O &l٢=qzg:l&oannMpm< ;*ëAO-@r /ݓmR$۱NPcU8jNSJGNLron!{"#!}wl'+ty;9)ygxfȧ;>~#<2 6!OyBa=0|6yכFPT4d_Kw؈mx;"5#.My QBy&4dH>xN{e8-|Blzxw[+W9kIAr5$E1$FĤ͏Qe'}p$mDLZqf/U_\(1cSxq'ly1j8^?zLwDs ٧Uub&]DGbz$vV `AIENDB`PKF-Pictures/1000020100000071000000786EE9C279.pngPNG  IHDRqxqsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<YIDATx{E?uIr  !E dDy(Q aE/"Q|w}UtKAy! p f@"`0$D{?j.̝tϣg~7{NMwUMLOwUu*!", `?U x Dd )!%,Wuegtc[*2Ed28 X-"EDCSSUEdZǷ єa\mW祷)%L)rʵՠI%zEd<[TYlkA٩tLYZ`tlN١@BDd` =nt`>miJܹ-+*QDdžRS"2ED/Z9!""r8t~x=|MD_AU[-:$G}%Q&cY2nťoteДT`o6ݱmD`EG-=+꼦/)%زh`.Qd.1Dl_ڒH66OEi`0&*֏E7@j`o`h&PUMA>¥:*<nnS՗28jti9[8jUJp "Mģ+Ktl`peBByhZu;g%0= pWI ۴"Pb`Y ~6BVB-J:]9:h>0ssȷ8ίb#ؒ" oT W(a˂TuU`y:=xUV0f9Cq?kM=9?Y,Y}70$<^3=|@UWY)Tu&f=:0CCx)1`Qm@e+զo ,J=Z߆"2ZDv&MdsLk94  ,CrF"2 \DΌ9G7憦2! +=--hw}9y:"802fGݽRxm.>  |!9`*VGGt;4?LO:ejQo yh,y]ygyq_rrKʐ?̓]8|[97:8 |s/@U7${ mUu}F1w/&wlLGYg˾>1- B>t.I;۔Vba~RA_ -fwy/뀯ShQV*gN;6潧Zsdi5*%,KVs h+DߙbZy#FKu]|u:mHyQ4 O~Ed^$xR0{1Jlr^XlS {21@U "g?KM3K0%eEn^6(5'“GT?W9UiGDX$ ,{PT:`SJwȠ}+%<ʾaUdrMHD^R6&^(I{CGeֿxt袼]e?arȲ2#p8fC_͟:dnZW9hm$6 )gЗxyi /3B[!m6e$f΢Sq=Döq9M+-ExtȸDa*)kR nr0XI \N!c)m*q%C񛄥|=;m9.*5V#MCcF )YOɓT_^萵ҮK5v5XwV  ,;myy!]$o:c}˶9(z7-9N꒲`yNG`pȁ9Vm>vR)pezϧ *O ;"/^/uB?X6-;=UNP>Ńr.%R p G`3" -=#go_5R]t{<ۡWa- (0^\3s' ;/uƊ}/\ƑB8Z O>"N /w}^U8'礿槝}_Ac9͞WNs 2 ػa)?U.!(lYmiN26f)漼p$!;*(gcc~ٷ ,0-%Mno@/2–zc-UmmUG2F ⾦cu|{/:H@¿ݟ{:g v1Eta>SoF3Cipcy6Wu*]xJ[H`G -~B*'cm}^rH,O`ԭsJuTu 6Jb%UpeIl)-8}xghl~fmR.oe05H,|2\_?e)T= } =}.&W7 "1ZjAӏ9L:{ehh׉mwOJV iٕHJ+w&"g>fȬ꘡cH$spnInќ̀HBbf_m׋$UmoPӴP\I"rRtjG^R?TтoP\3" mbX< +=YDe#OMi?Jn/%,q3U`:4F׼yh mZR"9NPn9 +pv+ޱoW; 83/J?|%o%"$ Q%n*=?Dd=[s/^`듘rbM7fk8imD;H&zpw fw awkE71^ V?>/rd͉T@G% 0+Ɖ)ΥZE>R X?VDg^y+bƀKKhfOɭiMDld`'7杆S "Y,h8i3T*GVP.uX]ز栊b:`:;U3v[l쬪ja U[7Ί꤅Eh,BB9<oBżcCzQ%:Qp:q]"#YXռDP%FUv. %CD2GwH0ozٞ s+ciuf8z8=q}I^XXV{PTD8Gd,3S&"x ^d\JrA˜VI(Zuڔ%pȒe#)Ҵgyvt|;b|cpEQe5vƒsiA>Zjm:9,%p%w:e&t^Z1?pT Yu:L"昝Qby{U|#Z{T?+jP;хA|[Ϲ|܉ ;/R,<9oY:t_Rʧ\N"= ḨnG42%FDcUŭ-Waq<㰌e,Q,q[#TD`e,GeX"0v9ݔ 8^Ui@:V[bI$<+)7)+"o kozX,ύ1B/'٧e9zq4h4,qe-mEDaNYr0WUn[-  8"rQd"2c&lmoAhp | ;L!,FXzl$w嶢@h/%&ݾ[UDWj(" >NJekk4< r` [|/h$_$~C 8 %8Uާ*thĦ"2@U7hwZ-.WIENDB`PKF]$  -Pictures/100002010000008300000077C838A317.pngPNG  IHDRw isBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDATxyŕ?E(ӨQ;$*uĕhFeb2Fqqfԃ踜52 =" ?qɣ]ޢsyuWݺ]}[nVLQKj,R@YDdAzV`5K/$b]aZ P-6*"wYg"Z ]j-@>\ "- nov#=y.Ǯ}kZf3lZvi~]kf֭Hͺie4!‹ kDd&08-|YU 2 \Y!9[GYk]U ˫y0wDdM!""jeͮ \$"D<Lz;7Xfi 8E? 짪jPvEhȖAD^"_DN6pFThPe^"r .k;Y~ǩjk']V6 P`& lNdp>1_ݡa06KH @^V/"=1Ӿy@`_,Um8fc/5+ (cK`rv`ZIԐcU Ϙu20DU (M`D(G]ẉrD t۩[> !"7 Rx/Qr-e(AUT俊+,"|'Z./s6YdKԺ*h /Blݠ<@#W.2^qz[9fpseUm+~-=k /y8KD^i͢ wtٗvrƮ-"E\SWhehܟEMcgKc dfI͢ =#-Y i-P]!r_דlho6Σаno"2#8 Ih5QU?Ӏ[X_TuYYY)vד~PL)_o {\`p #P)"̮^:)c`]Fϔdr j̠b[@qq cj>X䔩P-8 [k:; }p)FkJC+pʐ9 +0?ThæGFU%uRm،ٖcʐn|u34:Z}X|pf|ukYGN(Qö5e@U\7$7R0 <Wq-d(!ѬX\n"SCe6Д͹fm"1{D(ڰT%̦1,卬Є-HG߁"p?Ђ{$:fX5EG$5 Hs=Ee Jjn+pfxYU_'^"2DDZ8fv/AUV 0y 6&Y!Nŭ;42Tߘ/?eh&42!ؾ>NQEúUC=3f,!r[_U?,JZ首>C@h#q FnQVDK,Edw|lsU/hn"l/HMcDoik U3:av`/ 5!<`GOҶֺ~*&Dy`DzKՂkxϾnQ (fhĩe|J1T "#]QQ\`дZShDepo<."ӘI't~ЉUMtFkZ2쪪u k@Oa !Z ZD9ւT&D/6Mπ)j`hζmsa9pޗS!@D˩Gl˽`1CQ#駄ڰYV7lKʜkraI!/e*r.,̷r-,uZםڭIZ`_c^ڭkZ_3ZkcƯ¾;◈ %u6l "S<\ m`%{UfYHa q>Eʤ"5ACUg)ȢZ!'hSD0`8m[弄l2AU3ȲZ_Zor^EsrZ;6c^ {Q5k]׉Rk*uπ Mv`Ӏ2~9Ą VDzF4f)/Fr-|o?4\$tüu WC86f48G8-Dt:FwQUE[{,cVYU#r e8UDGgNBU?B]Bz['f `wO2;ePvU8[nN,}ACT5 Ke(AU'I:SߙyC vZ"|Ԏ-cW L۩ "]DHө%ʠHu>e}\stJQ2X)CE)C?"rkʋ%"DGThdoB&X ˰ig0YD6H;3fXJP2Cvdp(J&ac(A4.UU_$hI ("Gzŭ/\'"/JG| %\F ES==XEr ǺR%9ݕMH#vr\ *[sDd[/x㯪a UB0SaTG;g[$G #0)s=G3ICO ZYؚ֯,xlǽ%Qwh*A2d; J-R~`&p@9Yn &^4MRy0*P MVEf{8u{3ʭX" O"eVI6Gitcہkգ). yLyi+"lBX8X-ʳ /~m^>W2T⛘n~YUdl&a~ ׉uYI+Cwg_ȊngE(b!eߑ(ga!0|Ik|ˀ<2>,g^E ;>@^b3:[!a-XU>S{?%X,RnQe^A@njъ$p26kX֙ރkQ'vZhl3Շ -,<_sįx8n^=ML/F1/'\tGM ų}lc%2|Q-Fy3Txl#82^`֚>"?&[*V$Z xG]rτ5u+unuqr\#y}Q`Q{ʶ/W{D@ӣS[l]?oEZ+@9];=S`{kt&(wVzՎTtY =GKXܿ(jVs/L?5|C1RkTqb2@o2,Yp0s ]GxJh1'y"ro>5^v ]I)~u#:ҟ)#S;&dܺthxu~&ZNf![p)Agb7`+7y6`te@ǭ/eǿ&wV^1`iag_alO6F,m糁C_ vȍs26^ݑn.V24|b3u|sȷ>,? /+BtL/Mu9o䨔I~e(;md} .zAM`YbVS+\(c. ({hs_I gLjWw| GW Q)zӷ\!Õ`l pwYkoJ+`٬`a6}Ite`}{tFk[Y»؂SeFx,~t"6P9|X1CN,9Ddfx!8 shfBO0֡n~zdkvaW_uR.'6tT&A3I1'q>6`ZĂ!tEQtF\e}lP%G=~)B $%v2DǚY awVA@9Abn"ǽTq/AR5vItJ_R'*k[pkgG\2wXM9ǻU yB^2(dԗIU_`"wHFyІOۅ5GE|~\ Ü]r"RIENDB`PKF=k55-Pictures/100002010000005100000075D6766D47.pngPNG  IHDRQu6 sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDATx흽 QUVD@FQ($JitFhQPlX*vȽ3{ly{fL""=3)g(2j~f?ޚ&3󫠟&˙o?1TBQ?8 cI,]G/<XK`,Q% D(`%S%z$6XK`,Q% Dbg{q8.P$"n 9R|gSG2ontY~Y`(iQdf`ωK߇톼^7l{8ZS,u7jꗪ>p8"J'?le!օfĿPE]|1ެ*_7cHz'gCwp%XKEGK`,Q,;[amJ?O|%YW$cH܌ KQ$<67%N\WgKr<4Y]]bWͦUEc2jqo+0ġOeD (le[Ij$zuӹ(X(l q."NK"6"GăBI z$) v+W% "#Q% DHUI]՛,S{rgOe.WzJTK`,Q]7ZsP7"w_D5 xʠ"?BIENDB`PKFG[-Pictures/100000000000006400000063E6C47F30.pngPNG  IHDRdc2sRGBgAMA a cHRMz&u0`:pQ< pHYsodtEXtSoftwarePaint.NET v3.36%IDATx^ qF1rD8ȖlɒE-Rˉ9'Ḓ =.ffwda01yhS={:kJϾbʙu ؚueN?Zcy˟|x[Wo{|_e-Ⱏl=AOgr٢y6>w\ H ?|Ht.ϗG^= ep~ẑs249)alS.XG0L4$XboMq˛MY&eZ jJaUd;v+ 8RH^= .[?3X&j**bz.+'Ks/g=nwwX\ #ejf~@s`ݩ[ ""mA`J+_h})$9A;=8s^F~xI;\8 v=Rي/t7Drɜ{k Xj$jm#y~S3g^:q7{=rF\=ʒRr}A@`G53^BYF|, 8+ˊgRvم'\ UY8f̼) C tܯݨ7Tun+/ut1-&މWe)\ 鎦z.s޸=5uyy롩U,M;xZ"uyi6ga˃fwEt^`p{I5Y,40X#_ha߈![n\\P-AyB23Th#aWx}"70RS0_˔nwR^OrJ)8m/`Y3Xj4oɰ$:Jd55Z t%wo^ʿMjeJS3R zJsrA-\йa϶PsiYCs1cF~QL\,\ݍ["[9MIe)REJR=g;㚋9w٠|($D֊EJXbw}`t0Rv7o+#@J~aр ߧgȚw/B7L\|׌*iQ6= Jңh^).ǹ{;hmmD;J_ŁNih&"T="7(+Aէ7{,.Uة5Q*PӝYM VweM\nhs ݩM88d =$.n}{jU .L]t"\z# ̳UG, @nSj taDz0Ygc6@hpԧPqyY=});83btfz/H_t39=QgVf_Y`!R.%5!C2,XFz( }$X"Eb V&VfG'OD=kѤ^Q_SMSsx'w`z3"]ӗ9;y>v#XRGCb4G,E rV  e΃3氲a2%$JW9ٰ+)drw.ws hs/`b%q e0L) \`a5@@Pɖl4 XlؒeyL ..EL."s@ʩne5O=Eî8 \uî;xq2/ݩϕ琉@&}4r@>4%o%׾N dt+!.mY?X`CJ3TG wO\Jh R;0/lI AMPaj)X"рA`*Ykdg{P_+`Wfk*ʛ4@ʩȪ  :'ʐݮBGsأEйọ,񒉡t,t٬,@uRl@`U|[ZeBJ8ѩl4Z5L&;+CG eXCa4hC%)1ʧ\6(<:MctAE!K^N7oBaw挼t`ĶYlODĈv+3\ rt[%w:EVBke%j"@< L Rg6қ`IG 2}$F!ʄVC ^+ k0O7F%We^,#eKꀑL?+BV!.e||XV'R{o\0,gĥ2P~uŮlRἬOewRjh,@@XO6Ug6(A) ~VV;ϸ_:gt%R5ULNA6xa}5`[e qI8t)K*)VZ/_`Lb5,h_ bKZ%Po*|P{}g }(Qt52UXq"beK :"x2PxKq?Uȥ&U gڂ=PeїۚA)NӬbR?ؒbYkd%_& RrOO%cA*Sk` skвN_-ਖ਼ 2@W׿e;IIAŽZKZX\ IA/M1z ve8Sw褞0F[on8o'KۑߊPD߮hCT AE!˴9Z4%UyzN%:é(:}mX|Z\alx0v-ru9 }%bhjccczrLG#RYUew_*k7$:JppP7 ,Fџ%c2s#[B]9źӱ[ =vxZ mĪB&u,G̽Q!X*'RWmFj7E% G^tWU"O!="\^‰֔$֗".98}QtskVRC&1W׫+¨\CI -1\[V=h 5Ϧ@LRz6n,īUhjUy ī8&cRupSorl%sR'dMc¢]o+-#չ)n)%vm-)Յa>Pbs#4so}AZ$d#6^Uӭ<hhKJԥpq"ʹvƗ?6yoTB*+jL#3y..;֊܇IF|^Mr{OeU=`:fWޣy8/dOewTXjuD #T_ 0wj{=ȳ*V0͂S/ 74*PPp,Z`{tlA HԳ趴BqƲʶ:C4=Sj>B;/JkfH);Q\aM9^qoJqji! "vӹz1jr[E[4M/rby 3"֏\O# eUFQB8:]z\a(ժˮI#nUdbT{RhOMY%ÀRш}(t2d\:Zz-|>ɧG|:8N 5I-ݪe9SF_,XyM{tEj9fCe2J殺D?|cMOmv((.]}{T<x+1lS3gqOMFg} ~8jϔGw/G!S)(3^}T)5}xX5Lj'N/y [hBb 6*oޥH&BY/6!aF@kj{*2Ai T ԪsQ 5^`%L;loED?RŴRQ b('G9/ͩe!bQ*lMl#eu#TU}e*,SaP/4hd<EVcTt׸^ۏpVhW7,P3CueO1eMI XaNˋ8E=hs+W',V=4_>uOV8)i2#__8]j Wܘ7|07hjU񹓹[#80rM Me4*dk"-^<]0œs`YH>+S4-tr"nH{łѶhř(4q+Q )fQ+k6^g{2qWG [8'I]nu4[%7^omLڨmGP²ɥs{UXa$> nn>JK^YߋQ >bQSlG?g>F Xl\jëRD&,͑|yBK-n'(x k7RS\s0^f˲lko"!ƲRej>abآb*T&uz+WIK["G҂2'@Jr:.Đ(̆"1L4N 4e#n^sC{"l,Ѷ*Nb{ZRuR:2q_֐2 8bef2 2>a>;/8Tm CI.e6 y"2TZ^5iQD|/4fHYG77'$b o!O_~pyPq|=9/]LDT-.R+?_GPc*c{b6NEG6 R`9nXHټ[ TaMR*:-jClCݥ V*.>WDEUHQHfz<qkekېbMbJ)0X앩x.Wf + 5 'sK$`q4=aUWDPR/D(kdZy'XٖGT]pjQh'u##+#zxU,T._שܙ!1zt͒@|_(bA:#@RVJ?R~;=%k}yev`}.2N}8i3UrCˡh ]H;JAًy|dh*E06loa6X& RoWC{оRgBt "Oe'JOdD+֪%UJG^*p4,XQQGȷ0U޲} ?fcb|J WrI"%-4ZY)HkX1]=\k4 >De : $abW \_TQ+˨,UR%$ Y)kNełƷ,QG1;e VqQs^o}b SFYtW&RBS\@ĥ DJ6(o:,]3%?,aYa f h D ###ke՚Yy 2 le2=˄eOc^컗_kFxړv!n~m]ade H}f XmeXv 3SPy΢>:if=?l"ɩˬBJa#X?6,yB\KҎRtY|1)jSOyciҪ&M=I&qBV-,v\ =@Q~/,lUQpm8P~ ,[l2sOx}AAMn C td`Gi1v1JC1AJZ{׮5Sp^Jd0+/hhnZXBVKhNc#WffK!Љgҟ&_$| W۪#,DD$) tG\(NHw< 5ng)U Dĥ:W-nKw8 5D}.X5)D$%X4X&(Daն,g_".FjaЉG?~`}dGap)= &S(HN%5ae⬍=vmN3䆯yx1տK^䋊#h<\$VLGgӹ|I4vmWK /F'GJ64ͫY UQ U @`TH$ L0eKZ1" g5P@(`ڶnlMb 4@f !SȔ}C ]򐑈C@D6Cӥa?c2yft|oP  LUU0L&hV2!҄d"""ȅ >ħ>):e.t{W?ڛ_R#â f``UMLe&-r@ņjH`fT`@"bFZ lN!hh "(1jX] LJJyod"$*X|//"qxч9 cz &lffa׸p oD_% apDU9/=_я~*L~w~o /%35$A4DeDI}KM&cqw8_ M YWU73PUXNO垇;RNBH!23`Z_51BGo-ݕ˗CTI/vwwjL73gz)W_|!ڛR(M! "*_DR)BDHDD!0 )4XGR !@DkiF)JVcU(2:Lq|.l"Eqժ)0[d'mlۀ{F#Ǡ*j@* U,)0Hʖ2  H1rRHHhD!0qj~F !X.GC(yP3SdU l&d !07j+H[{>W^}|7F,0àjoVNi[Q]t}N>?~Uqgݷ^}K$9SR"@JHddS)Dbbb@K}/9UT #E@2B(Ȱ 9K"xy gfU|$U,!"R р%CӼH Y)a,_++{]r&޾z 777C ÐvwsQ q}o?Y? Or31WkŬyj'R+Ama+a:a4, RcM! )RJl[T@c0-%\mW2k6ԶmqYv09&Ҡ2Ъnj` đ)~+)#^Pcq!db2*@-JiF%$zx}ʋk`!l4ocd d (EJr)rL), MUm}#kkHVr΋ i˒ː 8 CRLۮş"4j-_ۤ)q Z\V5DEfFPr25_U hB4"fDLdbxemum}pwgO4CM)Us7:cZeSUUQMϣtT-F03-* Laeڴ!Q1/"Qﲫno3SJWc(6|%!ѮZ뛡-k%G%7?Ƣd59Mӿό|uZyLPPޱ!#!q ztꀙ(YM)FM#bkc{ϝ7-iuh}AǮDC")B} LEh=sCb!յUəA|bQJr)(h9@ADs=KCr7T<njըcMrcjB QUϝ=s;>vֻwǧ835(WЪ/R b`1ƕRBE#?krO_|Ṕ|.9jd5VgNH[ʰ !t5m`ZKP6LfC6vnln0m8۸?/L0OvR q3 *@e(s3Ԩ& Kԥy&cTB-=.)( "Fj_h8q4v–{+JJܪ; C_;|\ٞLqCxb-SSt|-8cNnl{d8o;}ۭMd)%K=@bͤAĝaH׮_m:fQ)Q/w]Q?̓>;CKme7t L\$kձ Șr 4Ĉjf1+*9(!D$Fkk5[0!-wC+ D?)02ujĔsVɦu4"3 `m=n3E(5mZhciu* 5R C@Ti?ִgua`"R opPjVHaeeG.}[D4La&bR۟f)nPOk!F@f}V՗ yڥq9x|f1/ 9ڕ0^WmRRRD`&$`ˤTLEkabHġڦ!R!ư>NBPYN`popapfHƎ4› Lɒ !f30 D`&u@,1BPC &)},!Fwш @:=|㑟`77yXtG >VzrۘQ1dJOn{$ tJ1@=F,V_vD<R$ i&Z[[U3)޺OBl8->AɥLqfڶ<e:  CK~WLK'}{ɜpؚ.P[7Dˆ]3[6nZK$DD yb16ql0RׁHHd` PԔGY8]{w΁W+7҆TwwwyYh`k׮SJieuu6)嬹* ȌZ2a-!)϶w~}m[o{Νvo;G~v8a=g/ nJ))uF5F1Vk V)AEu@L#T IDAT-8-`eFD"RUl`L)3̪$*#F@hIRz L!duRUgUL0dpts]=08QjTUZnsJNCQm1$65@Uш >Mll-ڶb 1ᵮ|6Rb!4{T,)D̛C=)"@hy8@&J}wmwu)RJJ n9uĉ|9s}?SyDSX"EEL&0i2Jrɵ76c$@7@7։2.&zU'HͭUw\r;ׯ\3MAwZUTsL 64 fl'iJC)ا,G.M4&BUPŢ xs/wWKmR>J.tc5rDf9Q}8|DDRVJP15))BBr얓'767ILL Cm}GXYYpSJ,*zu3z\a0w3A)C wS4$Q%Z{S_Ǐom;wǏom~}8q_ Ԭ1@U-6G`& 0Y/CӴUZJFKt|pI֠lClIJĵNVUnz&Y,J_{麒 2qk) Ͱ f3KI6Ʋ:2ը* ~F%2ā@@hOFQqUU£v́ZխeW/>z%(L>5"Ji*9gsÃbuBۏy\a1iDm~NE7GWO<S)1`yVD#&wys &wug?nD{ٟ,XRHC\J&"@#Ȉ@@&$&fx9a9aY<[W͖+7j,T%VӜ5i&x%()H z6~(l~8`eJ\"9C[ \wݳ[?s,>S=0_AMCubhkO9GRfc#וC9˵F0vmq`k,,W1)ಋ׀>itzmP "&&VwCۍ*Z*%|o[ύMC̸a˚dQ-R$Ć#qڸHI=2ہ̔bKX5-B,1PPj^KA'Ed+j%IQP@qMZZi#D"&d"b)a\0Q "I;κnPDL Ԑu}o;oC(UF9s}?i ԧ+Wlo2yӧO^pz}?1:ZJ6ffTs:+qu-\/}=B:Z("zttmmm%PekEJ)9q|1͑v@fZdDGY61H`juw  Bɖ`Q ۖ&ԫ|۶M$Rs`L5jChB7d"T2d9 7[BZ]62gaޕ"5!L;?2i&61v2 !r =88aDU% "vlbҥ+ܮmdeyXsi1QHb@$)DF~_O$ 'N!|쮻ΞX)'~]V?Or|,9e0cM"0IlfP%؄k.ö@:q="-| ƴe[B vc8ytuHAD jLUjEJ%-w(%dDj|R !pؖ.H+985V"֊N|dPQ0+̘9V-8Ml!ayHÐTrJZ516 j*.9Y'On9'O\heP31dҞ<}jҶ"$b&'"1'FL R*C8-ArEyh1Qb !"e-ZDRΚ뗎:I?>Z^6P XGXd&Rch`d1Ir.RŲZIYSX&@J6)M1Ɯ|m_Fm"[tIcfbE ,sA? J`9OW0A_@e–3< 1"ܶ?K;Y_hFZ ` *e[]ۜ4Mx_է`ifsssmms} 7gZ/pzZra$0D3P$0X@\3uUĵS~o/3wK-mbHյ#p1E|lkckkkid""Q A@d")s p `^`)Zr O`XoEU}ejrtg8.U""e"%#ȑCMJRT;ǹjVT%cЈf)Ј(Pedm"K_ҍQOqC?|m["ֱf5rUk< 9^$%Kh )!χ fAPC!FD&RDSS@޶r-=sFE~kw,ȉ2ìÓL{+|`Č7.yssɓt왳g.'>ɿ{FOOQ<3% Aeؤ&r diڶLMu2)D $&}ͯf|Nɍf6n*{cͭcnRea7bήt}GN+~aPt|gsY@  qlbuj,"%L:@07^-C1Uc\il7F `,rK*bh-'+UQZEf`bP;AyF`bRd臶m&I U咻yf)eͥnEB 8BW!f V{nVv]Pݹv'N!֩ZguQl&DbcL@ "E1PV V+u#"Z4**9q16)'))4m |W:w̝@ f\mEJ"#b%"6ms~ǶPrMb8^8a'泮_t!nٳgr*;; A${I=;o-vbg?jlj'O gϞ=s&opqL" f9rXVH\Ȫ\#Z6hrH^_1KgGZ<&n| :ج?u J:nbv\nB-VoheRZZi)CawJߧ!4 uє!u (RR);._8dee>]]3堤˙3'D ZmJєnBQ1Yz0` `b/̬i[hVWl}uя4M quf!4~{g'}?8φac t򥍍^Wv!u!)ivWEdD*%s8q'Ξ![ T;B bۏBwW?|s 1}d`Ǐos=}71۷vT͊O?C)؄(+6bf x7hV3(!ح[MdPe{QQ"&N&@h4~:2ع=A>MMa\1V@ dFE@!6*Zr50Wchf4Ok+r<`whwtZN uHCPlƀCcbña8YJJ.Ֆ@u֤bJ=7̈́906ӷ:q℈,W =_,;_V{> d0|1|xP 9D`8 JUIFK7RQGE}흯!{{$G ;u8qJWfoUkj9J0 {e#_zxpx}8:rOkmL#ru<@kFQ D 3._~4ugm\4KDT5-Z6!c[!212ަJD*2>A621њ D u} *)2TA{U8b١HAfeՍcNi|!vd&KG)6hQUEDz:9Zk}Os(4IQ@Q`8~ ؉~ I,ǖ8hQ Ad(Jx(K$Ͻ]}Y9ysjIXZ$H>Ue1Gg}s,$c~0C@&iBT`=9Te1X;ϯ}?ڎ]]]{k_zϼm)JJizO {9@󈦒2IP cdA뺋K4ND쳟yql7@bffs}:8*8X5̯8.%?_/\_ӷZo|3%arQ1KfA@T(CR5139)(YtrOd1 W2ۑPbZ Gs٨lZ *a)n>MMș;vUCFcBSvmJ˴|!,˿K$$cUB`9bq!"kff]}צm3ӼYD$Y/Xa1AɃ1P(֚.j*&Y<;fVՔbL)}7\_v*Gw'/FDWWWz3&r{j.@9M1c$GUҟ*˽_>w]g/_D{?ҰٵD|"ӣ;N=?Ƹ\3} /Oľݻ^|~K_A׿r?O_SUb^:I:u/D{s!4e""rnL՝e[iÍYt1;(lv:cĜtQ\6US#!ʴAlϐ1صmy8hGȈ``n7%?6I$"H9&&4w$M:ԍ7u9FPѦY{n3 'T$ƾn..^>7拽%*DRy $=E+B5 k;@G=x{o)IL1KB"n.۶.L,SW˕UTǏ2t{79{@H*R4&}B P/r'2 #0 AM̘<2_%̩I:-UsK[Lr9 Jni@ P򍢰* MxyNT+u )6#ffIL Lt0=[]U{PjfrX Qg˃;g^t[ʃݢ3뻶k7/v]#xDEIJtĄ́ #>L-h-LtS >-0\m; k@"bf[_mO!15DIjc~[ ;"5P"$"3QQS'ؕu|&/эج"e;99y䠆v򒕈م\HQ=~au:8<<{p_./2" wŅND?~ً˫/˷c,}_ggEUƯyĚXe Z}&9P>g3N 4՘k;d&Pã"2 PJs-CϜsS徘rD0QE ̴ rαcs3D0r>;"^g¾@Ύ2&C*2/O`n_H8tv}}yġ/ʸ ]LAluMkyY`1Uܽ[_◜j*Y)`gOݶ4ֳ: 9HjO0jYUC3GfF $r9$ "Ƨ.sūK_BM~z,FAE8=lw(``rn2{fN.^M}||X-Nw V)u=!l6??}&^B#S##Bũm P a CD7z _O3*SSIjr+~הּ|1B B( mۺi\caPv$0$ڷ{ٍ`2PP%4C |*GL89D<0"YtJJT cQ̩'OljS,^֖GDDycU5`8 IDAT|:n6V? WuR)NeTUI1۪{0 '_OUeS7_zwۢ|q]8 8JyͱyŖ I^@T2 v;O4+LiyyyEXtYlӠ/8^f3ej(MCж]UU.IDX"1D4<@=z콯kt·ءQ^Nt>jyW QjgCDw;͊."׉vٽW/^U~DRV&HUtw~w>33`B!x}? }?ﺝ&1QDܹ::<^~ T# #@%i$e߉Wy1j 2LuZvf'oPE7Wk|aT?qwHcqy '^"HݕXUaw=::T__BNo|eHX4m?ܻzU#AQaf[>q$ Lk)w֙҃7EDޅ뙉,n۵9.!qc?Vuc&h%I3sO@4$Df@T֗^oQc`;bfC"$e()+2MS0@λ YG`D#@k1F*`OK)]KM3?PQ"R8jJD+rXs;{SffBcn>j٬۫KUE@Q$*Z.r| !)d¿v“ԴcLS]ӬAvI5ic!Gؓ}m;ꦮfu^o:Ic///SJGoqgn8%9 ];\z5ϛuUj1s(3[S?pΧj~+_ }srGz=6Glov[#✝pJC_l2|#:.bˣ̌ȊܢN}qv "0kz>k֛z6kV5M68:s<'4Q%:T|SӔv]S>#H>xHN,N#^UDa%N_'Q{v7yΡcr.m88}]0k7WEWf4 #׎K󫪞J"BPEq6;;;ZBU#DI/_\\\tmq)S漯Jcr!xN؀!Wuhf!u3 ɸWA0Wy+殮(0c ØC SRٓ%*q=x瑪/F>Ia.޽{صH7*@r+@!jhbdB @ yys.(@y^9n?sC|9B$W'g 3DF1\Uvpy÷5,LAٽ{])m]B._xwvgRBlC.%vHbf%q!!0!1$MPWX.>8D4~dy8ofH]FnѝEd}Iwc}FJ8NfWyݶNR~\vMD~:QVv}GvxzPm-꧇qĉ>)'M"50|_Pr,/DP3Tl*) TVY"yl cc٬98XEI1 ~? 132;@9E⁠`&C=uє1!xtc 9u 0 PE$Ifv3¬-Ҿܫ1<`")0?vsunřַݷ~{ÃsǫW]׷m H("JS?a( Y5r۫r^بg#Xcߎ`X̚iŬ1~cy% fcdIiWQ/j`up|nw07~hu|<|f8y0@pחׄ/m,{ ȭDHG˿_NNNn m9C]ggg뿢<选NYJŋ@yj @'Tq/`*Ԅ$*!DSzt^ha"ʘ$1IҪBM Sv("חz+AD9[):SMUM9jǞCsJc6mCReERCUKc4{(>vJ(hYh:4k7;wϯU/ 9u'[pa7zm]c gf ΫY!m cB1P{gs*1xmW*ʿ(9Ҭ5դ*E6I 1{IJ+mw|1?=;=},U9~x%!v}'o?m1j%љq}_"zsD4I@A_v>>{-X7>~|Vh\UXt^bD,C`h]O2#T۩93"1;BAb.ZJDCGGYͬ& ];7QX ~JX4Ŭm`)1g+d8WUՕKj4(e%6Dps),Wrv}5\ܫprګZu~}Uw;cT fOɱ7"8& ~XR0dFz?g~fݖ?'߫3iubXIsj"%[4}BpAhjqLcu{UEvhbC9.HEFԒN}p9ԏ"׉!^^.(L?$$eB"bF`h"OGsX8{z,!_kN"rL޹39gDPϏVgL|'c:=S6)6_w|t}DꉙrSҶ}U/OȐa3]Dqp$qLWݦDŽ\gZB4f) @Ġ)UIY|D샯*ū$94adTI3HI$j)S`fcǣ㓳;wa0麾q^zi4tQ`ewOb"ͯ .7قg5}G?g?C_\|D4ůՎf&B@s,Fޝ$/Fdu"P̬fy1jP !M B0!@6B dRdI>~Ei9W>D I՞=82c G."TMJHDP׍$U9#KO,(GKrVj3g'dzaX7?3Lt[t[M|gGӚ#hu^I0B$.Lx0\T]!sh )Ev+_7N+'W&))Pl@Te >Ŕ-ͪ&f茘 ȑ䓯J LfR*)vU ]T&"#vDH w 1SIvcI(vu=_ 3 ރ󮮝Nl: &xD,.j'|n^NF{M{ᄋ+]rN˶PLL0tWlM#NHBBT0I7a b UR((Rwp8svڵg6ׯ<ۯ&.OG͏ֻp J[ `fSw. !9ǀbjE &1׈\3vo7z[nal9EԖIHAl7¡'Ѕ'mXS9j"1s29̣S|>Pt 8 @ 7 ȬD(2M)\_Eع@ޫ)9 V_3e!f3"gf!;awx(0{UYql@lc]W8FbPǺjVsO_nO L9kԞ KXF$|u]a$ ڦ X23yo[gnfͫ+h*Irۮ8"R!oB8=n6[9'5)*o3EJ`}]{uq[̗sخj{NZ,/<~~9d40DP1]`bd"vnf c5`fO$榮Ʈݨ cۿ?Km9y\L4,Szi\2u@e xl%3MJ+؃MDa  % 3|ZT>8:rWT?B(Bb*e>Y}NXAgE-F٥b)9"BˮM-]z>l~D(/*t1*6Hw `j0yBzŷO?}n.ϟUR8CLcR>놄i%[;y5h7}~{uq`ɋI "Lsw{!{ّFC!tfqL Foݟgۺv[__y9檇VXv3.ˌJ˂eDd% :o7.I)ő3t6]$I fd C nչɦI SqOOV1梋}C35Ѯ*dD))aBYi9& dҞyc@L*}8趻,+fVUu8!cQQ3(͖O|1>zFUՎGycaG~1INArUu_&$LB8sœLrk`UrHn~1vv\'S 15U}LmnIrI#ChfuM!̡ (9 5OTAߛ|vH3$n۪b\]iJ V/.^@DEB &MC;(",AaH,f]u]w?r[oч}|3oT,m9 qAb樍ǥq.F"84$bXBӘ:G!<2C,i3e&JQmZ/^7AߤC20%$ʼn0eo? ARDGLL]]7GCu4a PH'H9юJJ1X&9Xfu_޷ GG7޳>E n{5mTM@a KҨ㤅 }].R٭[{$r9#5{L3 1TL5 /9$DtprުZ_ IDAT:E$EMQz@ZL,!&SgϞ|j_ѻ'|'Wm7n71kn&G_ڷC3/O4EU !8 qZ˺8b4 r|߱f0z3'V#qz/2p6Pj$6K2Ȉٻ|iRM&@Tss w=䓧}};K_m{q͉?c_>Dw]~{&¨'@Em>y_X뺩g3:zrD0=qZ>t)11[?bۼzk)204-fLld? a#l۱ _xfbm  r,CEB8W1UmCPcR1*S##fdQAݲ81HPbQE11UGo8O81"|ɟYo?'>'2 #@f{!1#C/)ukq9"GhP0q6Pn\Գ`%BDkptr;JA0~ 콙FwhfJ`aj`)}7mճUڃ+M*JC٧*އ4u _znfoq/Çgf*RzukXo{vr|{w~9T⋧|s75/jVSU3_x;D1O1f1Cݼ|)1bIk]n"*`@qpa>Y sD=:bMTTK7%&> 3P QfhBjQ47SFk&R\v#M'I2 vqV}{Tժy$MYƃ;yvY?cEnYE5”Y&*)#rơWI93| ba]Zv^Av@L  ĎcW4LD9%4{QMYwtיM>U#044AԜ (c 9Lm;dmNx5ɤFʲ$1VTyj*q{l>a;Ǐ~tvvR0Fn//6W'_^oZ< x'd/NT̞?{,>޷WHgjv|r8_dݱV)&܌8|,%qYO?ArE9:sʭD $%bb ժmmYg ")Dmi^ӴN{St.C(h\`ĮPϲ<7Eko`*9NLL8s9r|XW> ) Q|pwNVU%1CTPo~;LOxVL&Pfo4UJ 5nfH=3SD춰F<1PWwӡ8_Yى>5M^@KXO>:Ib+-2Ȍ$ȀČJmD*juˣn U:^miqJ 3EC*8bDd$D5[_+"pαwW/㘾㫫 So=g/_wb{zgƿu>J u㗇ӳ;GGuӈh]^g*i_nmœ'M<ô䭚q^ 8_4{M5lڧyM"$B3WզZ՜=3rt=H&)8D, C FjŘxSi7JL B"r 12Ja' ex8 ^.OL)Ku?Y8nDԶ C:C̘TqP!PsriƤIδ5٧75L112O^tfbJ9vMg?5`BC$@qq@4f}LH3ig:xG!0@R^1Ĝ"hhB{D~}';+?>DzJS @˧d0CvUݰc\U7˓+$.~\-.%P12*qq$f>ZW!rTe꥙*ilXef8/'dj`ӱoAKUFȔD5[m4wϬBΧ80&~L;5|=}IJi1{ouvvz潔/\Q^̎yv$v;MU-r2L3 j7^*(cHs7="%7>9G 4WQqUA9Y5׎1)D4D+nwq ;op'F>9<{}EȘyan@W9_?HiőT͖uGb UC&1Οɇ <{kj"doS?z?¿ gx;Me L-9f"4&`r.q=@#R'AIUUgV284gTU' d6މhw}l3&Sbsڶmۖ ؼbfy|yÞYu0jn϶˫l-^V0O[:S?wϟ~8%!1t6VylM3`6=-o8Ͳ\UFtnDQTDV {"e4f-9CuNH YCT8& QPmes՜SjxyEDO׿EL;J_Fл, %B2-)9m6LX^Xq)^#:kNp)pv~vveB'sGbW}͉*zY).F\0}g\30ᅅLbɍ]aaB$5{)"7:Oo*QT\ܬ-6car)i)d$d r4")sAoɓuk}۶m|l_7+m/.[ RZi7ήζO_<}8 6dKl˔pqLI1nnZsSv=b]_=yB|jZh`!͌]3RǻU#0R ĜS5M1q $$D7-A\4GdN͈J"~%MÁ>KZN?{6,Ta Y{uq7|ˢc@@g;_x/@;^ٗAN/)ϩNKXL_9i:2-Y0&__#kZlTQTnDs~ǧ,f'hŝwu7ADdBCUsd`*ګft!MRl,~uT`#iU"@Պi9ﶛQ{bg"9;@` 4C@Vy,)Q.!֘8_|Oo;d _ R]Auax|uնOj A$\""ZausUYsq9P> af&]~J^"b֣'o|Fswh2sZHUHiAb[@i2 Y!RrJq?H"H \]$L̩f-sb5Z bV/HNF"sxjbR, ,H{?+_(_

    ߝn^3bq; ^AU*Re&፷t]@80߭Ϸ!dIFDp s58y,aiʻGҌ  8z@%;R8;dLY9\4sʪx<~P=\Dƍlyfoۆ iznV$$!4_\pؽ;i`JL1vjD40HM<>]Ŷ˿os}v&~=?ߜ?j$ixCr}yq٧Әݰ[5.o.Ts:=~NƚWZy.)m(|v`f'-s .8[oY1f\cO' 06=8sW@(i"N kz$B'9p?j$XiDe;"-/_ žj iNu{q[KLfh8}ߵ]"~(8xԞ JъNC3RCHR_ϪG _E=D1:0G ‹s!t!.S 2x<3Q8IPm^m6kŠ@tRJ)BQ i;BKjNZr~piD,EPP묓جB(xEU)\бys񨤑X6ƢXe)Dn  m.KlD\ JiNӜ.֛augmm۶M۷muf6݋gd !2B1V(Lx{󂥑}q{klд!FD@$qquoEDvwb 'Bx\ZZ<򠑩#16t]vaI 6M_ocm Bi~Hq߭'C?MVJfBFAV$nH)ͩ88"jd"2!"H $$l}%\AݐCM \#MӬ׫۩j+:-yEklvWo|3 iw_M_+kNK;,jLF>J4sfF,^8 Lnί.XJU54 ү"ppTo_헳"K6ʸw\(TWuݩcB& n}Dnisj2P𺏊#2hVgg14 ,6JI|RH01< ~;ZȀrJY:PӪ[&3a{yswrsv>;k| .]K']^_xv*tW,€E8H/a]Z1JyKquBi2بkT߸W(ހ~k'+,~r-"J MO]9x1W\ $ȁEn@ "(ֵ%\V6 5(1R؜YrOuTmi;fFZ)gBYq\mWA_>DVH$D~?_cw++_y@DIsS ,r*^˃/{Vߨib!\=zެjZr{wڦi q-V;^ "t> ~$55Q3ݡ(YeOy`R"Siz M \ՁEp9F1ywڞm9D$ʱ1iLq{~6O#n~ڬWժo%`Ɯ)P5gg|4|{~?Oy1|~۫t]4k!#YC$tf nAĺQvի|0L IսR.o>ᰫ7)xo^K/J&Eh 0Z~hnD$H5Wf[NnE$GIMk:@HM۾!kB!BsĊCvFUv<0hHu yNC 1rarD ADQBp?zJoW_$q?D+_C:<ëxTVJ)'1-"bjfN 4"*ؠ.s9BۮqCa Bjs<&]PCj|.^w_]70).ܳC*Bp&`ꂖ}M1ι8W92K6RZ-כՕ 6,8Oq/C7/>zw<$'y]>zB-EE"u]w~blLBadDڢ!o_>0Q 94g+8 @MpȘmea  1/Qz v*ʕ~YnPjj䬥<[Esvwwp܏ݩ)?=koU@uB`֩quIpAX1sHPK1 n%CwTtvvnZ٭(@ MbOTҠ``8F7upbruDlcCB97A/!4Tӗy[#_WP*xIZ"%|A޺rݧK^'~Uvj+sRnsǩ[kFnUHY -tJi mDHNB E_́ϔsrw ;0q۔UzbWJ!=(j<D^/ QArw?;_w,hjou]?ǻ}lVc'ggTc׬76_>*Es.D/;3Ryan86f1ԏtwh:P.#3d/*ct0%fB1Eu\QR" TiEKJYK9C)a}ɪyefۯ n ^:~s̀lz>p IDAT0."U2)%)W"%`ʊNLRi&d$dtbe'4G"bfsl@H`Z9(9n7 s23YQ^;;} fjy9q|0%?xi^uUp%Ks0bHWi%;O˞-O\G!;M5Fq'>2 `N TlD5dʉ܈̈ "1ya:yl]9C11M!) x9x T1fw}*'iyfSvBcT!44gřشm#Agje@̡ ]5>Llw{g ;ޠYAX1E'$LX!-s AGnDޯMې2 ySnU^C`D$Ñ {/]?>F"C`D mW]+Ϟ~nz䭷>߭'!pγ+5G,ALAR.iDž|s[,im] 1?. <0*#d´  ‚ﱤbqP|@+8sʱmxՋWs >D'wP4yW JIHjՔj,C6Vtmi۾m#!C R+ Ȓِ`u"v1, 5, E]o^vT׿~gUOTjͩBU)N5͈ش+R [5lL7# Jqq8j !02,N8@~pEк3 g5TrGLqR͚뼽xгQG]$LdGvV}z&P!ؐ矂n pq8&[5!P} QX᰿yJdWy=x4nwcɛhJ0͚ݿDd&F%Mkx[Us27-J1Y@5ttnn,{. q3UEGe((Y$XX\ E̋R̬bbBӇmOyRLMG0WvB>' u't9qX%'{TTowq&0ǀnִAC`%͹; ++@<*P3@ ANz:vSs-oV- ˗CӵFXԾ//|Sݿٺ 0zJ23 B)f$$1R,=b|J`Qz*i:RHna{y/(kr{z8 m1BuG3 h Q(`6bՁw7QحY1(<4ݾx>L\\;fwFC׏W~)4VHiJ͘bpMϞ}ER14cQ1SfCu.FBs98͍SRkDE>$[/Xʌn9di;3߽aiX-rF[Wc!0G ,#VQM^*P%rih"MG%Vަ!oKJU|/>yڛa_E>p0̄ (&EjViꖃ%G_vYP^%4姟3i3rz]#"3aGs1mfB9Ǧaws!:$ZJau sq$D9 qgf{.^g{r%M7yr$xmyJ!ETi )%oo_]!K:BT8#HPUD?E?N'Bf0ɬX!DovĶ#q=6cx ErD`^zMK̑Q@pSs]}ͺo(Ӹ8⏬[ߗ%*mGC`O%j%vpg~ύ/p8mW}894q6C# 1Wu0p`r0EbGOy@bT&勛ns>t@Mҭn}8TKq7H ۏUa:jU c&+i;9l=IXvU;S%M&9`\fZUL,d%Ӏcnf^ܭjV#/H-W#cQU"G2F(B1n90k1*Z)4Yy'ER*EĎMqU<@' bY5'Q?%na$"2qtOZ e"PMwM, r*s 1F <?UY[ 5*K2t@&jDLgg"b7#B3g ˫r{iv #aAy4u׷}ZBH9]=~mM{8R.VmfXui#۶w~Ies~ݮ791bNZ Z!DjtT` _9Bd<:MIabib``!(!MˌxaW~H1:1VC-&bN "B @DQƶ mGCB}{I$cC>vͦM $&wٶYl9M)$ 0Σ0rQEp;;ݽ<ٓ~w8˶4brb$%4]&P=jY^   03˗/~9H^?X""evs3"&wBG@p+8笪K\p(󄄦YT-&޸ 5M,A"K+5?p !ƅt9idbhvTi)X KQw31i"B5I8<#y 1 f@MP#<]R[f+ơmb5ʹ2u ¬f<\)kD~N;?Ig:I'̷Ꟗ!0k}F :;wp^j<4g~Ɠ8N+Ƨa325k]j?v?%kA с2_?L, F7g&s-<ųqEZBCխZ;f7[ 3Hh0w7_̺U>ĸꛮmoN8p7gK)!@ikAs[i"7B2jo?胦m$D) 䀦8 ;JA/N ?OTx'!T`6TcلնdU5j5M:HXVڞ(2WK:6M{S_ js9xIONTkSM[%B$DUbdE2*/kU38k-R9+"טTDDDiv-1MM!H()[ǢS P^xz toN  rUE)0]Dmy*<!\^Vr)%:=o|MMSOøS ywwS-YBȋx!-梪*j^;gf24yJ)GVLPD!/Z1M 1?9TGd;mA!zuJ)Ղhu1-cm kXN3C٥*1wdnh6M Mۚ0XĦm=;{wF!R$o~N$2 þ\4}S y7q٧f['&0OSJs4D~oiڞKlxwwexNHҌH1Įwosv~~G=yzxwv/S6YH$Ħh()y84}D!K`\# d!+Zn۳ˋح{ΉCJ3l`hpoT}sMB5sV- ]H1DaAG <%luofnZiUlZMmۢI伻{`p4͎0I2t#1H rU y)L݀Jk~Ց1"sw3d CjDi@l} !A}C{]en,A$ E.vXg ^0e)dKOe1^0 *F$+nŌ_t_Ҥ0 IDATh4EW1xv|S+f8c8V]1)ϔɫ`6uf Bx|u:HsBrjƺ@AUQdj/EJ :%\kHDFKv[]̵HF\u:ܛUXooGc]~mˮs_qUd Ѓ6 t"Gmu?#۲YA:H9R;!Hv؎Hɪb{s?Zs1FcSKTvp֭sko|<.Hy`vefApN8/ #]^_}ͅt>hZ539]rcf yf||p$Os3G ڔ5Ppx۫~+㱖Ki$hG->2 6sCc'7]\4XGB’Sr u]۬uZ eU|WQK)jV..<1K)s7{6ZR1Z9vHKwjvin6TB͢ & UՐS2:mkm#YRi:cj뺞9-h-b_ @8e y;ҺMFKƒ'4s5$Vð1gB ۬_8~}k_wR]dU@%FJ h9r7dF'x99 BmQ>xZkWo[8wCJȊ1AO\)hk2l.RB5'؞bHq!R[+ݛOW};x~\*o۷ξ2ɐ>E^m%ZoNIM7z}򃦭V[ukܯ7?nS}{ZkkmfNC);\j*3Yl3[MM 7o[+g)w7)3YkD7;Nj"y bGeHZd,"w'T"K#J˗ 5r_ӫϫcdL[q4`rGbU0"ɀG`%VfUP0UԴx!Ic pl!.Ylj8g<uY=6u:aNXk]D0NSVN2LIrYXj+,y}{{3OKښݽZ̳k/6+UmRʄֆKZ x:j-ҭC/֖؂/4D0ػ_D<NUWH,1ru5s0S[:dm9HH(,~r#$}6:]䮋"$w)1 =~^]?۾|uj.JJߣթK:!2Du!V"\owIfF,eMLK)Y离2@@- !!`;E 9Zv&D [f[Ȝ" ! Uv%KL5U]]7ZԴ5&h5j%"L.noWϦ?a@V,%xZC+KMHDbTy]Aӂ0b4< lˇ{ݧ%}֢3P'fq{ui,V7J aD[.Ix7o?VYN>VR9K*f9• 5(`ZdB&Bi݀/0Go:ǻty}>;`4./KSU `ͽi֢0E.@W ]FLHZRfpj2)wGrNm/҃(OQ6J6wHHPFCp ,[ϣAP޴!_?垄;xݑ`f.Ʋ8rs } 6^Rg>*w9HkKl/z#hfZ۷慱:Lر-d|01"#/L:F[)tu[mZ-4;!4w8 ):L,$9|n><\<ߦ$f~x<:twonZk)z6g9ǒj5lW}p,/m%$|Vݰ歖Z+U+SvZkHhj>NH9?˰5C ^>GíH/}h,]=?fM͜yBukfH@3yͭ9,DUƄ HV;!~ Ƕ#Q,m$%Oyq( P]55r߽{|`X\q<(̩5(fz]\f)BMSꑑIYGZ\@KxwW Mc4qZBjE*ŗ:ϩ#$_Ϝ{a5sSw3O~c=yt>Q"T7,Z K{[Zk =@d A?ǎ5P|ՀM~o0`NE /tp$8LL:f"HZNnj-ZKk-2)6mÝRCG,EXT罖Ӌo.hz5IaVng<{xX7趹zڊ9Iq{y)VkԯӱdW3CuTYk?!Ln0 z8vWs^n<,5F4GYL KӍ|O{,e9:܌o_bY(}fk$26Z >e<jIсm}/8ҚIʌČ \qlMxJix:Ե@RN)Rk%yel fD51k\,Jf[~e~NwZ37.'Z( 99ġʸ_m7ͮxR|O2KmS+iX% Eĝ|zOM7 &.36GUs^c7w7mJm7חc-Njayf{, qqńRj;OMM%wjJ1gt"qTEI3FFcz6=xY݈bf j4/ʋ'=ܱ+3| mWxg a!%Ϲ&]6:q @I[In/͏H(YBT6t$4N^a!^4KUTJyhc"^Mc7jy:?FĔ$@ )%6@6%.C0+P5o]^#MM js’\5@ ̥Tܭ._6{΅x?Ό#!5U6{kG>XX>ûL"q'@PHΆhcb3J!ZJ@v][y@C݄]A00%H pxbLiisՅtԥNDDԦHs&"p{ww˫LUZu$݋ϥ~ЪBdSoWfoֈjx?w<Ƣ`)[fN`@h6A*^pƬ + Uz ^JY [[#$I9dv' x:{/ŷ6]nW*[57@.kPS7Z*8TwDxB8MShv:n]JN5:A*& jP̥K \RGH,Be<:IQ$ ?g2 쒧M z;,d*x+uw@nZI9~n8-@010x3J@ T bo™@͜Dpi!zx% ̀wL$ 0903cWón" XD^z7>ܮiXxNM5 `4wW:NftC7i@@( ΨBOK3p 缽B@ð}3l<-F0%pni @5#S`:Zr+]F"W5' b1XW`RBF9J,D|-GspfD_{*:!,r 3 12MHZ;bӚZィsey]MAOQ$?< sVkslC߉<"汘5@ oubNяRZgF.I-⯦5E kiitneqjʇK_lx_O?;da~ ܑXU 0RBhf`gۢYg{9[PrkN%VGscbG[ v#aXYԭoo3b>>ZLD,b3HHnlº *,[&DP/8sZhM}>q.V"!*pumw`1+'s#ঊhD1#0csFrp;CCj,' I$9!g٢hHZ4&@DMכ\+m,b4ϥCOaXuCRhSkl/Ӿfrx3l6$]l!C0W,)/:T+cqLQDIH$n5j8s]tNg?3,jN%t F;%w!E"Bp9f ٜc̀ DMOox:R IbDpP*=켽dwӶx֭W8imBc*TSO)KLhLI)_l_\:N~rmXm.е!.DCpwE֩϶>Rj7t89.UFz:|s_͛CKX)NZ p$}jSt̀  ,}n|8]x7kҥiTܗ=3 aB*"NjP[ )jL90lj/^].K{R ~X#0:73ӐAlF`nnhDIMݍ%I+՟Ȥ͜^}dD"g3yL#/z)'dd΀s!5-GI|< #Op^lZ1J8gz4$$q ȎP 䎷['u-}kwqyy x6#PPN">[jBV1r7stb`}OnF,}3f Q@Uݼ#vGs#Jb&Fs3'b0u2ciUw#F!$4cZ (*gag$8 }^zۭf¬MSy,X˴as$Zy"jr44ۇ#8wgB"0w,Mu:CmYr2PmHnN]DE`[imZZasr+tr/5whTcj(pѹ#rĈNXA=.%XkU ;R}ZmVSSsD@_:H2rCE@G;G87 @NB>;"L@ OA$d`%0CfwsrE7u@Gycݽ犝X#Z,eVssvp~6Zt>t*gk$`Q@$V$nDS=iǓ;tcsV͊Պ"H4o_~1oŢ2DcrCpxl5-<uݯzfƳ: H L7;BT[ZPY3 Id0%A ;e㄀n^ԴMZ#l_!Q)`DBXi'4$u}^>]IԥNUw{L fN,Miz/dDz6RTYj`& 4+ΣIS|[uY2i @\QH ,DoȂHo]WݰQU3 Ólz#-I,ˬ 3(іS*\.gf2|n7"q钃[뺼.iՖV] ERF@h"wsFiT IDAT"Ă̭HBܹ7Qߡ3<*C@@Ik(fxOO9dp+?~pw,ê}N)ovgϴi.6uꦎkAsw&^ xOtf[S yE -Iu7`37?_?__W( {-z1,2`<A"R݂@V/FDb}-nD@l. 'ĖSW?pW5"rupmf;H^1> yw{ "#!@z) JCK=69u$)7B059d)}W/_}6 ~H).z~^f-@t: ʪEjDO5^q]s?JOtLŒ.: xǼ0}Γi}<=ߍ%Iu~z۫zYm)  ' -F!6 VXi:^N%tB8=ܼM*6LuMm-itGp7Ɍ @b:^!Kᢻ/pMՊ)HzKfԦV 8)]Jj7C`ZhHSZO˄M %hI+3|DDPwBw]J4zWEtٔ#eÁ"j ,f n<&|_Z6Z WAM4(*c?q᪔9)(@gp4+q@خM91rwVH0!`U#Nx:>iDb=ODެ`{3#r?;x #M][k"i~zAxrڞ$xwŵ c1mVukAPZajOFD$HʬVTK=<ܚðH)I궗ϙjK. Pb뒮}YY&0ל-:%H?L}rU]MIͱ6EB$NYR)_!"x:r@u45dHѕ?NpIk:2!Ho&2: ܜbΒa\ $rUxR"k M`!-KsbI|ÉEpͪ9XN\ji[>s^zb!0a1jJQPk6wbjENir@^ۻw)a ]1 @D]kƅc@r@Vͺ(U3QwFv[,((_N?R,G|S'}̢DLD_ ~|N%Dw`GD ['2}!n8K! \)E3i, ɘ Z-q#Ҧzw35T [j2Z@#C1y80XliSG(OVT<ddf.ĵ5ćh-Z GWkFZYk]JИ 1)[ "Zj]/o_8nNz*"y?2/'wⲪvO|yw!m:'S<4Þs"dd[8bU <#jp^ @C3l$F~8ZCZnmnm\$, hӴj7-= M ̫ۏ8qS7Ap.iQE #{[t YXua!p /r7X-)V7 CɴZy\2n)KD~ռY1rxx{ywCl# QD L-4kEHm;rx ܔ9bZp~|zOrI@Dݑ$5G0 =3NNME Fz ڲ ¨N)8bjIζ>O {?(vA-QX  !o54nL*AFG+bgMUE">%)uZMkEaw4}t_JI,t$LO )Qӊ DSi'$ %D(DFB%DC s;f4t?$+uю __DB"8%\ SGwB s͐XDvf$NBWvߟFO6HOx"F" - tjyVk]NBӵy*ǃ:1w+w i}T.W^_UJ,Z~棛j;0McHgٗ=DNk%^֛stRXͥ" *4L$̩y4!uT#$]ʏo?hY;Ni>Cm''.R%w}N@NDn4Į @e!GW4e 'm n^ DLVOw9>xdm}a(6\ճ7 UHbIzp9EAM4`f!Z^{j;4nw|:"b\oۼlZ "9 \فBrlUq=$j'ݕ:Lw],Hஔ{3wjjӽ|}Z/&dbvvAǻ~)~uՆRZlK`CP3jU΃T4R&Q) :/ 9!x3Dw c}" ,9oޥ,;9Z]-++]iUth#_E`?A-#,,m¢B$˕%efs˾c cp \DרskqynE: 2+mS;]Nr9nɷ(kiCaLMA'Qp*97j""!0fV ^C2f{Y bK9}=DR}-0U$ 2Q%%%p5zE+0rï@HK`1ՎT L 'k{NEVI,CV]UyYXѱBL||֦LPʹ,)GZ]{*j x?pcAd1bL9lڲWvBwoM=Y-g4 z[]뺔7M\[ݍÀDUի8!ږy'^QPC;-)FIUkc˵ eɱ^,Uc_.Z4m%eDVᘇ8v7RB@{úq~1yGpIiyqJ2 "4݋) UΙ ӆ>Hб}聵j+ rB-Eγ )/묭F!Ke 9Ӑ2_Ziun) %Q({:JIvUȒQ#2 R}FDĄ:_ėAאDJ8nƔe_~G7ww>.P!uk}vGD$mZDȄ͸ɛLѫv6i_\(!*^s(q崇OpoC_ؚ04kTـX@RDN R=(ʺl6,YC$GCZmE}ϥ.lɪ_0rRӣ #pm-:pjS!$(tv\-0Ot9w H34ߒI>d;UNԟ $Q@i+Jۗvn$'3u \`cDa݈2$s%f O9r"F ʤea;s8&kZ+R54IXvmk`w^m隖Mp]S]Xl77v,cJ-euw)ķ;vC\@U/6qw_zCGWRngjkYZl,\ &uHrfp.n6 cŋ$m8 =O3:UmQ-j`”lGh*:WzfQ ́:o7;xu4elsOȌD]G@=:ænqyhCyхhf=f @0$UGFCtA6b Ep Jnn?P!A,]4R:ܽ5mUMuYֺVQQA$Ab »ۛZ[7"FFAipXjv0Fn&iZIRL&&nȈf~ |tʕ v~7ϧ~q:%9D0vjZUUq=LCN阯O۷OO!f7$FGDd"&NFLP@ͅhܿqtu-Q&b"@le3瑑$nz87w@8@2||ZWP 90qvn?mzy~OVydfDXawXa:?[+s7mnF-uAaB.Rܧi$]'a7RRwSƔ"Z}eWpbv뢵h ZRfH$LQYbO6X|4{1;*\ Ȉ -u~xfIE:xao_ ΫA'Źxx;8Q$j5Sj1\L[]IKAo?o(CDyNT⫃ԛzg&jTVJsZ[)Z5c! >NCʜs 4! y1aH) kYTӝcE0oaV>n,)J)3|(ރ,0O!9YP-w/_8WAտAwѮEhTV%yN1 Y{ yiiKND@1 !4 .Ys[W-o}'\Ki0'1T|ZGd=eeyp:p7֌+HNOLJR2!`]fZ@qi6;2f7<h-R-\|ZӰ?E8$P[):L̡BDG7sLw==ojMu^Χt.sϧV] H"Z3>1e36 s2sFdFk"! `&pDzy<**ܨc$hNZ%pysMzQs":@p e0E(FCfa!^$DH\.4Jr#$+ {#=gžlw[9mp@Tf1jR麔e-ֲ6u%)ITx@dQ 3ț/{FF$"qn\.$sDqAY3sDUmmED7G⸬?)1x=\D3^5o-_8WW|/|Jwܚոfv>_ͷ^Sf& Rw0^? B߽ d 5k]_RZֹ@3Y+r| Μ' CvK;>!̱Zxye1x_,lε̲nl6voq(޽sfFu!hk?wi&yDA)մxql4vc qJN Y IDAT2/x:˲RmҔX'5mrk3v,O?L>[Yg D&`6M!1'ΌBVonA!RA[p+A˨P_:8VqH@Î5nNs7^p mx]At~5&^Waz)$1kln^ֺ%3IdsJu"Vz(QDLØi-$@{42OҸO;-/IZM 50{+}.ͼ0$D,,IRۿw_l?c1oo)QԠ(sע²;o_ް5}vwqgӼP)yABw- Dr%d-n{ss,:WUv5C@`V׶.u>2aH1 u~o>b07x1 |\Mkg.K̽Kk ܇q$h6ؚ:&ǯ|?_aH2fnmUU7Z}ղh3imB$ ј"W<&eLҚeӴ 0MA]8CJ4rZlonn%tz6GLcĔ( dh.'p:O""546L,<-hWRMwHx) ܫB5(=:oBlD}ӛfyȔ? TG(lwQpa@nڼ칹2֦aFœ0S.]{Xk,xAӺ^@,˺v4F/!eJ2NL UC?,Id~S`!f3t0bŽhf9a1e߭+d`_=vy3@T`P" h\US\(;Ƞ'68M3ul9\\i3#@ ں\^}4HQ Yz)e)X633mR\!Æ ٧_>?+q%8,RJY'uwnWk-'& k-MkloqJR.ۻ19ij keh 񼐸bҐ݀Г2QR(VFyi}2(2l3?|6|.^_}a'>T#؞@ R$iuVi#u/=GGfloԿ3\xw.R;W3 Paf~G*ā@5"[)i՝\ЇLînsh@Yմi+^¬/TYD8ԪqM1GmxI[kB(x1/ ]j& (7>}X{[]&9C--%*pDBUNj-Vd5:/^">[˿|S˚Af*5kD ݀axѭimR.e;1Z^ץ菾886ȤfM%ؚ;0ztʹ5mϧsד3 Zu9[QanR8#zy?R{ qlu~ُOyBwEb&aS(X&&}#ik/< Il… Uy9DZFܵD+(RfĂꆡv7GhZZUհYX[|:M9Ij\S=7/Ne+e,Q1յME8x[̫q;KzGt\nv& SLk ע8P>[Ēv 3808p%;\ 뺤D9kDo(|-LRK)j ZZeYeqZBB9aȒ%0Mat\cC UwM3S.b"I)8ZE$F@A՘i7"i" N qJƌ$(bn^=MGV])}{Oq?ݽȬECfH0}DE4$pCn->>]V=E6_YL!i"T0W'жiwwǍUkj(֖ϭV74f P_0~:cĥnqU{XܭꝤDi:|N2us"3vY٤!^mzf,dLCd >ޚ6u+3Բe0 5όTjϡ$&&kj5\M#"S"3(M8ό>m_rm8+`sE qL`S1,U[uͷ_oRŅ했kNAky' ƹ79UHj՘IAHմy|yqzK'Dq@336DD>:&fwa1kR̂G`֥hkr>30 aaJ@#'~؛yج;Ǒ' Ux{fi,3:/4qnϧ8ϏLuw5甆!($tJ)ga)ˣƘ/,n! >S4p6UKsKAutlI RvEs@5OI]N с73=@XmČWN]9EO6 ha~o/Gqwon{:X;YŽHl:][WyU0] *'A!NOﷻ7.pGW,":_.|&$ voFu NQf֚ROyYXӫ77hhvg0{"&B24rLXzjV޼ S,MX8uZC$Nu Rk6EE$Z 8d5VL\)ZCa8J)غ Դ-EH wDFACGatM t]A怜Yq]V3)!1q$]XaֶZZ,⃨M_._|E~:l7f;쫻4yg76kNpj? 󼸶qHoFqywQLfݍIai9Qm# CLv:&oRsԲaTt2j&#vw3ۓ^"J]ct`jE-#jyDD ;5ADxH#Q84U(МTIn|fy@d!Y e.!֪`dpb0)L9t ^50DHZt\V"$JܚI&b˗/Īj@Ьfv'JV=uYSN~{mUYd,ivq;[LAf !8Ĭ_(tm'/\Dcӱޙ&Ik1zͻ_5kXq?#09~)S:Vl~PYs!Y`-ǞЛW_s_/}_dߤ,@|EV|}viZZb)>GØO7V[8<"u2=eZ.r>3O :mZUֲ\8N7cl~zF\Me|p<=/|`Ih~ȧm RNɠ1!ֲ3H yvs9m{gF s}z. y#mL BnZ+ކ`6mYgtvS]X9#RCJT @c+xō!zebmƁhM]qXA+ܔߎz y!!Q 20"3 "V"""[,g (Zj j1p@Ҵ\+,SZQ8 HLj.e6D/Kq|^Ne]r!q)"yDP`_y=6vmEMXDUيOGonnw2 )%XѦOOV]8Uk$˪syn\./)s7,:ec}z:~>޼^O4N⮄j>[7$e3> BD*c 3A$Hlj2sAY:`83U_C}ys{!ːClEݓEOBEzt=A="L4&sC@z= ̢2$0D!Wu$$< aG=.R,jqj.弘i@sYer>̈́4v;1RIc΃dbmjqZdF+33bS8^]bGDl&7KW\cbC92)"Y*]Rnɏiirs7Gdw_O1F4124R[a`Ji˿_Ϳs:)32t!D?ږˬoZ]b @e]rie~wX2_湛=#qrZ/4n9D$$Ð8x;oj-X]O4a)r:!"粻969Hx~I ѽ%Y8%<˹Lrp;k3Pھ+wjEʦMUVԦnHD୴r~i XQKv]Y2;bIͅ٩LJ'drk>+;;7mhf&G7f2f} =<"\{ڬY]\Ǻ4z |+;TRn#aX#`F0HC6mww;[z|>&!@P)_`#e!M5"7;<0N1!ZF\WOG(hW/?pJP֚Ҩf;^Ώ:/?4LL <ҵ9L IDATYG]bW0'jB[oô5̗^i~_G k3979^I:u-²9H@f槧yI}%DCDbeq]|t/r!G'PvP $Χ2/iq#81IdzP"jDX][+8;-bՁHXZs;Rj)mQP/9 i箖_#J9 1|:4~b[V/?z1NCtiYD,MK²&4>1gB nUkʲD11fC[%0NQx:톶.ɟ 2q6GI6¬hJ!Q/[)`V1bZ$ 1Ј ]SbXRM00,uq$!6ݟ7'ARr$fKH">\ , X7]C(tw=|Ou@Ĉ$1hj5`=ky"v$F<ffVk]e]j]|YvlqAu9Dj2%@dg  C7IzpJ=_(~G=:ͯ\[u)MS 󃃵:+EV:i4|U![pǁ$f[ gfr@{B]ksUIqkg8O?1rƃV Sbۗ/ATr^y^ WCrt̏ow/62sY+ ##Rbw_I.ᔓzǛ77wpPת-L,!t&nRnzDk J 1 ayĚƮR 2լOĝxYץuYZ)eY.뺖BoinoM^r̬U|f݃L e"#Zb)l`AtQqwКY4b(Lbˀ0_f6K2ϧeF$"I"2 ۔a|6yxYjZĆՌ. <+QpK,Zלw_v PT@IJPx\C$$&m!d{X6]@Ur3igfdDxtnύV!^H/=[>>y 6~"~/+{oWWe4u;+WĆ0 O_w ʲO^wW%t(ER!RCYq:(}hChF燶޺ӖDD~@c⾏*0wUjnvZAsID 1ā-cU"\=4mۤRJCf坋L~ mZTE"e @Gf M05"bP̴ Tʼn*`h!F :I)izRq?"vmӯ0i^4H)|M%OkR*c b)R,'U5$P2߭8O̥wިi"zdo}gO{w;;Zi&n7}ߤJin?\=(L`3ǮoWW|AD EMp>$jgrb"(ٓn)*:HDd+g-YNa8}̧Qy$ScۥvTk{]71jyiڦ_ܤjin;.Wxi0CaKV'"zT ov5ⅆ%7d8cj0PJ7ʇƿ0K"v!0k5SK>r>wOw*,*HL15# n,Yr.K&NTJ HbfĆ%iIA*XL -fczO(łgma "XD"f!U\]_K?>ֻul!p6Ike_<{m"` r~)ResRGX t<!!q׵0kyYoCŸ`LC %2e+^ȌАQa<bqQf2N!/ޟXͫ~~ߴaGCĬw2S`yB1IEcCZ8Lehw60(h t`}2gP]rΥ`Ȼe&٬h^&BlJcLa'.=P5ǭ;ޠ,hDh 0-"!M1qPPdR0ρzWtSf5c"!LJ4hi$ii>O<y,y8ߟ<p:x@,4 2.e)(u]_MvoնڮV= \ 0is)Rpm۹7ui;Fx~]{짱;6t\Vh11ɺ ܙ0ےG"fhK-o7(r< Ӓ(.A(S`y<.sjW_s`prJiR#Yc_0Pe)nk:92 HJ~_Ɨ(@(0CUh)ِiAD℥Lc_o6O@y O/t)RDrfTJF1 2j )Ԡ>Y ) -'}b1K )PY XKјX EFe&UY8NH6V;ϦqԒPrAu"Á#u]߷x8U+82U$4cjSQA0Bb@H*6R#{dpfD![ 8(i4ϥ(2PHN8wm'`h%k2fFEh)%D"[VK5Sq/(Q 6Cftbpj8*( >Y #HNPMD*Ĥ98u q88wn "vq)nђRhR)lRB)>"䌀 ƌ=;icv߮TR3|: 0IZ 4 BC!&#m@ 0/Ha EUu- (LӪXϗhJ$2`fb pR^vA@EV[\ 5eUbjEk(Ptvx 7Vj)XzcEX%mǶ]jPDq>rĔ,:p =3*(Q5W" 6{ bEכw~C7kYq8PQnVtO?35je[R"8sVw-{ :x5My9ߊ=ǀu&6d 2SOzJߎ ,9.7MяTTo} _!NÉyU' 2͋+=EU-ĸЭ"؜h]0"jB(-&6#';`Mhir@jI )Gp<ܴ͡#T| A!&UHj K <ƎGA79uEDU)$!n^4LJۮ_Md$2i%g3q"*BD\ߥx:W:9 Cj" SJr}?]{{ ]R6wO*r0RBY,Ly^yY40!]Y!1~} HMbhRN7x eg^yɫ??9ϹmH%պM!6ѢjnS(0oxWm1@-y'+KV [@Dt14R0GlHQXջRL8fD*g#VhΨ nj&JmSx<kT5Vr]nz*d1Bezw5ISU2$5jRX}+E[Cy.@!\ZW;\(DE<%G2`JM<.=򼤸"nidgs#WR)YDDjJkȥcb_i'P)#dU"S% _}$E,+|pz ,yʪr>e4Ohbn4#d /v]D @&r*Ů *fPLe_=۵}VJOY3霯0Jy O0w"|?*r8Gos}D M;OX4s (*bLWC;JNMM"2E I.l!Ԁ8wl~Qo.L"`5\? ȇWRȓ o:\(fiC"t}c@1cS IDATMMx}jZqJSÀut'IVwn)4Mmxxp:Jy\Tgf0eja򮰚>]H070Qer%شC`\=N<3Pu>\ 7 UGDKUrJL }*T(쑸L`ul*"dAd `0b?!Ħif\fEHifO>͋PLߜeb.y~|1e* 񗡪`` "uvyCYrx~~o;맥w~:Υ0b@qXx@,`?q5k ȴ˲ܴ:bf& 3~l۩Kp΃B!f]D;L9:yf-nn(y,bX|:p2ACe2 9#U5e0b3&.D@CU:};ˡ,E,˰wy{kP 4"aOvRfd@ hԤK2syBʅYPac`px<U5^/PG7;]svF*`cM3Ql! `h4ɃLtW!6zA݇&t:ԅ#6&2i$@#"@j OL!;՛N/ozܤaN!|e~Rϡ嘐і6j@pp;nWm|T!51/nFH2x{-dSۀT,r8Q5@ZUD <m DPw_ <{k8t/up>O󼈄1% O3 "fv/(\n_޾ow7abfV20@("fS 2!Zq3E$23%C5z n1U3KASBd!/㋻ݖ"!"!#t2bd}iĉCPӚPqq@Y2Ov;u-r|]hDXOO!*6WoaYqfєB5mrhP5UӴY.؟mpP9ξլeVWL5f.K/,'{zz'ly}sUjvm3iJΠ>J;=s˖8>z _8c3y4<1)ɧӏ^nnh)'d+.$3te10DFR 1hΥY f6&`6vT ɃC&{"QɘǀL$ 2>ދnER_T= R&5˲~z4!MZ.e.۫u$R@C0&Ej q}x*iu^ecWiv?WE$LXtMׅnp%KvfS3Rr)oBmBH`dSTO]땢䙻֨:LM "0  ^Y43j!STETnשYEզ6-Y<:#}e&,Sl<}9%SjRjSǼ܂LETJK<t#r|a@C}!`L15\r6@ 8S*[Dh/-b`K$4?oe~}ߋiB!5]1Vf6xxOo<]?4.gZ'5"FFM) @g) 3"y|ms^D<͇S(MH?me]c"4J"RB"R3CYWjmBH!!X6fmۮi {S+alV[fٔr^J. Ffj8hF*Ĕ?B4@J](1_07g\ *l.6"dHRw*rO#]̊Mq!/kO1oƯ8SdbI]34]ۮ[JIB2z٥'`ڷM2gc FPUYTCLI H j/@YfrϾ!ᴻ 0*BL4nMm8MJMӭ6mZEĦuj)&5 7^ݥ.Ӣ"{xIQuNqE"sjR[mBťU2Ḏo>_nE1e&F HdDh"zȈ 2 'oSJ5Td4̓Ff ]+@}2Mҗq>^Iu@.jyhRwO?4LdUeT%e4 f)8/iۦڮK=y`\L2@\% hAZ}M,VŖxaqe1 JJuN! x1R@ĚXW +/|v3mmqMw5w +T<V%Z_+FLJɢڭ_=@BR+} !aLO䩈l~%]L!Fwn!Y13I^r?!z1p>}?$1wlwðZ_IR2RΞni _ՆK)鋫'67/Y2&yEUE@9EFL5Mꎧ̰V%N> D37+%"3ĜvQԠZ%5<͹d)T76p:7ĩA"!.!g Vjq/+`P{QMџv 2 LH,ޭ;~+tAt< s 3]/KBbym h^ۗ?ypB3T]$R0ƶ B@EdhAJM]E xCHvp6Ք;puyG?;zޭe!ժ T4Oei,xkWTb 1V*d>*#eY^*qS@QgF#.Q ؚRJ@ Y |shE7$uLH ZhlcdK: 0ZH ti!Dpխ$JQU !jԶMD@!R'fӏ8q:dzt jw?ňnc|Hf cAsTPeD#CkN-z6 k2w8|p/2e:?}6 "2rH-Xp7 ݳ|ߵo޼?>HPV" pO bZt4G!sdB)EzjE?=պ9Dvs%+ˢ"b7_՟d?KY(B@'l$ä~ P ZlRWj^)qJ.3b c1]N+h12 *ԔMEBEjHl*Z17`uJPQK%ϩgOOݴ}24W 01P& *\21A @ A bD,kΚlbmyRݮ1!u64q_⽡xjEd02,ff}3ܱnUWuus)Ѷh)L[2؈Èaňű<I8f49 TU &1jr?dMbUS% Ȳi.^1H *w5 hDŽihaTYDC ̑`D;aEųUL,O T<&2Q$L@ ITdGo<8;W@5@{lC$jXU#1".w8x LIJ49!~;T17U<}{+\Ea,VZt2'lU{;I5ror8M?{BC`B!B?(#Њ۔2PQzAgPI> bIuݠaι@H?h&5H IDATuQpw,3;1?g]W?!yNpG467Eӷ'm A U .MonnfeUT>93ldWu^;[ DpP՜1b[?4><}Vԯ~'1 d!|&? Bpvz12?PIWGg, =U+= "T8`fH7H#YS6 jA(dME̻fsMEQ`jه)-~lg~y\E<$ >Oc>Q=kQHHj&$jIj[Q\"꺪br JsCEE_vU̞,vHŧcUc B5uӕ6_sTL<23/Td@U,D'*uc@ |BB#l]TV7K]%C-'jbTՁj̗뫫i<3mW7jAADӇ߼3UUy}f,UUuGEH>DRr [1-Ƙ4lԔl ^cwaHiGo=%ٿsRzq5 ۣgg̟Z7Nܽ}Oݻ{nJMܯgR߭~mWUD| by;b0ƪ 4g4kYbjf跢ORA..~0SD,OOn>i) 4P֫7O7{f朲4eTڍ Hn`UBT T1[GU\,b$YTf5'$HUv0QIԼ D~e$df Y)h8dT=VANNN)ZW `wT \Ŋ4+2R1CHAEmƮlic Mi`!M7l:f8Xb;!/!gijfg'Aq#0vTK$.ƪNb erQCɎXQJ1t9 ի 1 z&.?B T i3 B1@I PX%?B́MS,u[FɊXftejV(4-9=| (9qi-2aliy0\1z/{N-8r:oy;rH.RѲi*[SUb6`Sh`XK@Tg\__^rXu݂!/~3_|oCtY}jg{֭oQ7u5f=eYQ aSF! r?!͖KF >DhH*Ԅuws=seWG_A+)j~$ r`R8:?|)@7Wc?Ձ 5'UiVLH!Dh[JҗE{GIy1;ycJf*jy$gɩ7U5#YӸA#1%4U1yA n؈#))EV1 dla7H) k*)!u}C{4C``c[nVCլ U@D}0 J&.#LGRGzوHZͦVO c2xw҇w& HDADXwU"BsnjTr 1E>Mp.ʫ/>A-Lt°!1Y+"h fL JW=:9||$qY(d.8jB2fi6ضMP͏!hA1<~SOiF``bUA ld7"<fn6ͬnB|{g?:{?G?8fS7yo|׿['y3GLc*^rI8ƦIl?JMWj4mGOn+/M248~;HΫTUl "S2"&C-硉dE5!#_W!j b 8SuĐUpѨ)@U  UsFE!Do"1g6MH,gPf @,?|y;c+'nw|@HK+L#rd SQf3M hҎTQD`+0@db@L~PM T<7Ǭ NJ% ٹ¡O֝[nҰEƈ2k<]\p=7mĀEQK)eؘk//v_o>|?Kn9K_C߽OO?~+_­۷n9F%~&*XA> Tn_13 $K68RŊ+v'2pQ(&3 P?l6]$ĮWBUn\(;~P ɝjHZְwx4 2bUW qp8&`UwssupW)ňGեf7M2 !VqݴAQW/5T]ÐT`lFa? 4uݶ 2?>%ٿ[8v@ޘxqq+?RQ=}c/?'# 2̥Y  "C23.\lön.O{f}Ì}? 7:rxCЏǏ\zvl& ȄL1u8D- $ 4! #ɛO(udDr( Dx d#d@"Ϟh0*LbM <Niw%l+>}]$1ƊBVjffՔл} ޮ8vl3PE1b4EY-2{zp)TC 7Upߙig2P#2JxfSrfw*4KP@Pe4Ռt~vi`u[Քv.D VHfn6 `1 A  dPmz>@)1#:LΈ1TPs<W~@T2to+a| "Qmn7C'SX]-J {)OBXHh\g/ve$&5l{{uu+o~_|/>xfr2cɟI"9CDM~ X$ |/d򀉚f6-vsBAɛuJJN۾#:ƪnDзU1<ʂuo\TU}y}cSsOb3GD/ bTB̛YE <Bt8>`C@49+dбrN>$ŎJ1UreA3 lBRLX!xnrtFwmibjBL!b``&p)"WA:e øn}f~q`QU8PUGLU]5MbO!`DGXwj;;jD8&n/]a(s$GWﶵD?F"BmR 3Lc!H~0v T0NVfUԖ&v͉}{V(G(/R:--EռF1`bZ2[1=hU򐡣; 8U 1c׏0[iPF)!2 j9Ggf,%D2HRw5bzhrN㘈BW>OU ſێg}SÇBp^G~_ 4`vuqvuq_J \_01SPjN4m7UͲ-`Rr`6Ulyp@2ŒۃZL ܟ6lSEˋU8=D10T*ڝnu1,l/DVS'BӴ19rն1%w; sm1XJX;s6w5xAFIIlLH`Ȍ cJpyvs 9 2M3 dH05u[U1x_˒__|!p4e_I"AT75!mVfU9 V 5SMIbSDD8tf!xٱt6n*"LYsY~哿W ~ڻ߼S[I;ɪX,Qf9_\MଷEo>я|c/df{%׾Wn5S*T,#Q! @f/nv3EHO@;?(vvhh`:ߟ#1z2\U A-%Qͫj‰@&Mk( n *缘V `Č=]QD|!m6+bdd¼DN,73 QA@Ujݤ:j1sQEf@aS0ߝkN&T 7MY$Tѐ fjX+ydija<$m*C(Vb'Āds{MҜ5e*Dfk֗Gg4lW7,Yp(T(0 Jfjq*]Y!c^`+ _4:a<7_d/,"XZiTޑ9 i:hdYȝ:D9z6Ǻ_arTKGD`P{UN>sgm{νE6UZ8ˋn\]]q|뛏㣃C BhV2&6ܮEC%Wf o/6u{"b2"2NZ3rBRVrH6UMHh /ø.aFT$HDTSUcn=+JxV}ES b`E9lakNLh!eZ$Bǭg S@Atwԃ,39Nk=owx|X1U@c%x sDP\^.9O Vzv!pL=A=X͈SE IDATO9a:ty~~x|d`I6j KrML$rHd3_2x : |rؿU*@8j*؏,$Qp}q1tDg"6 # >a0ާgyT!0AD娊`L$kJgf}yt1Y< HLAc%Gmoo <ǃ$Y1=9/f UV$w *Sđ`X,fn/$)-=:ş'M_;쥗ʣPrcb~Ν#4yvh@ S)r=^_?f]*h꾰OfuSߍ5P oٳ-8\6b0nuupth8zAxv8=t122bUqsߧ USFowk7kH-DPLDꃨ(4nZY8  D$91Wm$lU*" mZD*)h2#Φy(=ȮlTq4v7*9BTP|x(CSbRNIcOP9ľ\?urSR\]ja^8FQ.V0-cb>QUW5_VU&`FR*e؟ 12f'M&;4ΜQ(p̦L/h5G u'eYWWɢiV |[""$nnx>#}1=~GO{/6 ſߨ>_~ʯ8{Z,o߻C` 6>:+4{|TU1gUSncuxN&H ͢Hjf*@ 4wvuzF::ݖ`$F!D=OA@OMeF]߃fhX6vUIۚb$ (Ѥ8&35@2t?ښ9JJFE=v_Ҽ ';N,,cZTG67ݐ$JYl1m[1XUl'5$t^Z\nMGp^dsᏉK&@ >R9 PMܯc)0**7ãL:m&A& ųfj8=]$d2HaC?l6{b9vh[YyC뙅lF1r!!Ggṱo:؇~`fQ\!S.u4-]ceR }(Z\mܡ?"Uuum;o^{sZ]wADrXGqp߈Y ڬ}Y-^__m=n_׾V~; "U ugl^M×k9roJe¦u:6U!)0bR0$S#˺nw*vXf$ D=̌!Xbݮכ*Gc+d"1bBE (Cqd$ޯDAѲHwrkgo7mcT5DZa"vv" % ƬrXuU13?nJ \^;Elmi8-!Oi.t+Ƞ#CoӻyA&<4L ._ ,# 擺:QU,e^& N'5)rDWa4P5D@#Tl,ķ31b LTaWBH(ůMyD2mv\PșŁ# m"8Xnv?ZS1ۊ {{V?>?o:{O7?:>TJ~;C!@š2ZVErSac:LYjC;^yoboN t#K#d PFt]i& b-hj*Ɓ$'\XxEn+,__ęp׎mX_a@B T\_.C"!rʹ*@$Nh+"? K=s5 sh9q%jg]vRNb,.NRާI)% )=hNX 1\_Z4`kU\̛q kUV䷩ {'-q/#/k_˿_??C'Of&ԖFBc *mq4J0ƽ.o! !1}onLνglDjZ_wWkΆԛ(^NU QCfuqnT۶xqRf̱ 6>N -@2RNʀ6ܹwtp1XF`TaI45&ќWj9UCr!hb膴Mɖz:N[DY~0X S~ 15|qlE=e}dVHDY1 c7PU']%; Ӱ nEq(9)ŀDYL$&^ ~N18Kh2EgBqu%D GB"5hMtsJyx=b j{VG$ KW9%3g|Y5t4oj$G"! v6'fs|gj1 rI8^dDf !q:.71ϼ}cvB `2lW!Y;9.Ŝ'Mi`ԀsC$F3n;nW۱Ʈ ͦI4XZTM3v\mU3O !wdc^G. 9r3NM铜:Q'< 0o$(1L+f¡̦f(tWajz6CPDj>zNc,N/ɮ N VpXET?3@5zm,jD'i969nݏMz1!]uG\X耂,{QwB3uL%__^1:lf Cܛwބn.|᩻wŒ>:g;g(|^g_ ~/|n:3/nC dQ:uPՕ&)0FM5+#}J)fˈMlFr=Kyoo/ͦ!b>* %FtD)2=pyȈ;{ A"Nx{[q!]R\$_J90QrQ7[lZ!es4I$B+]jHnbS; @E orAO } H uS !I  xYwF5+Ů ogQJ= hZQP_||{G of*ziC6M50j3&c7mo{m"짿Ovq/g?>Ok·W7mUCd Uh̆8^o})f7~}x}Cs㓳o'D,*"&ޜvs]]v^;2flFa@o%F\ <_Ͼ/N>¿g_k_'>/hR庻|r% ޫb,vtDǿ|'/~_~ġz?nźjuLJihy4USա0b0'vsݏv}eIJBlNo;؆@ c c2 Xo?V}sZas㊁~T}Z)WQd{lab˘| .y6>"@P2?b!kICb@ 7Ѡd˭';ɓ?4 1 .Ea\@3]>bX>j47_ga%F$ykąO.SLA7s{|BoU<|NqdG'(nm!'Qm$`'B7{"Bd\ɀEO5߈+2i*Zh!3njot:ݨRx?.Dܲe˲5W@?/>#7}{w|&npy.*PlqFqD$En/IӼs _&&,-Vό1=јXkWYzB10,YktZKRZ,V(IT6Y?77$eKaE"P .^f3(*sY鴚Ju2:Zvy|9Qk5TD2ls7ָO?nhaâ4bS*]0l:N2Bj&@VlG֐i@_n!~21]Z4"! v"uw2^ 5w- 11O"? 4If&աBE1M'7H(̱= D_Bo.A 3FRp)6Q7Бe?&==B)| l˘3!΅x%,?1#VP+iY)C@P2SȲ01`F vlL5&uewE~fM>zG?kйgz]gZ 1Ykr{1dXP9RT*8.rr$ b1 ~ w{vxkN=mTIGWW,e#Ru8*G4@LLK]ӦsbfTJ-,eI.#cN5\PK$QO,Iͮd=ɒ-Y|hhBhX]:s{dYݬUb`aLJZ%Y(K,A+qhIB6<3 lL {t1 X'a"D@ MȘO $_d@IC!06"cǞ?"MzBAHG$ft~4e˖#EnI R'ɧ0U'Q ?Hh @`)GEA nby:s!g`drD2 h W$ `m8;e :mPT1.+~+*ydS|=o(1qaˋx2P #'"Y0/9"8~} 0!`o%%SǓEH)T<0oIh":xm .m 7?~N0 WwMB Ï<&—\ta. ;΁MS77;c (Z|{b\݅PPHRgQd9p!pϏ^uiN99oG`a8XJdprFs_!*cRwi&"(Y"Iڷ֌.]0qlbKmK˗V"L\_8RHZ ~7IY `J<0˄ orM",f0d3hb v88&C$o7so=!c;IוGZYp86bA06 qHfg{be!@)=6(?=UF(9ChȄBnKcg!^"fgf Q>o()v )Cn5 0{pի"xm^sDњgZE~zVs ˵rV(WJRZo4Y,3S'Ro~s&^ud$|?!)؁&! Bl?:>wju|?D8АEƁF>Rw[|/XVi rrrw}wV~v;mFxɧУǭ<-׼Y:M"zn{gVD@p-ީ4M4C&,tI33VKD9}[0wݺu͛O-@`ɧ/?'M~\8x^ A.k7ܨXCy(KyCȾ5>Cx oj4͐F]@\R R@2`Aِ,<ݘGÍ)X65>шCjIV?68yt9ڬccG71ªp80\ 4KϷF?5o㖏wēO cD/sX冝;w>7}i&Iꜳ2;g?}Oǻwx#m!aZ{ڳZ2&)dY_O:JVZnwʵR鵲^/6*YNr849w%k-ddhtɈiX(P\hwۭVۥ!Y: u{1- QcF|i*Ed&°W P 2q!%2|GQȗ68>|nV>?U3>Y9D kMd};0X߳0?.@(i><,S [֘ v;8[0XG%jPu7(]jªs|_`&,%zӳ X(ܶz~12z'wxnJ,MeXf%Đ ҍw}>7-_]ҍǙ(ߩk> #!Lcok~^Llu:DqehT&it懆劈봚֡c_C3q %+]|$"6qD#ɒ$I%eX)!Da w^4D {i}[8"Sbd9`1 t Yc !҈/f`-]HO}a۶CbS֞}b,ojLM6:~XEQ$Ed;&ki ̥j r]zɫg ^鴚 r٘P >3 n%~Z*J֛I 4M0m3GՂ٠ىc_2 3|l\|"(2E @'L"2f11@`jr*NMN~059=\C3` bKefFc^4 !-) X.L?O}󟛝=@oSO=VsߛnhϞϓa9_~ƯsΝ;w>]w^ʯo}۟ض ߮rںs6\\,z=;|ql"v ,ňo Qg^\,e+VGq;4;ЪT+#.ug ƥ10o,h̴[BbyX֑("&v"6$C0DV@\3p_z "k7 i%m47~NH:P9K6}aϪ**? W^y}}0=kݺR fgG֖x9fpD_gƍҗ/+z'N|딵g=ƁI`m BlqYj)4筡Zc0d)*v-(&"k ]r{ /4 #Q!N hH#4e0d,\LS!nHV!@crϓaTWdAֿy+mgi00VŲ]E+o{Rck` m۳\駞O~^z饯mp˭|+>>>1é(#KWR\aJmXcM\j :β~/K##C<\N,.m۽v& [$6nry!siWDhɯqӍiaq, 0=dh,.X5^:rbSU>Vu t@Q9ϪvJ|Uv]M'Nox-[+ܾ/ܹη^s[y۷8sX322RT, Kgi^0֮8v pPM7盈,KD^K}8ٙC>]@3Hj1@1 }r6],E9\U} a^;hj $YYO:@@s֪sXZ"/>rO''>'Ȟsy֝3>p-۷oK/xv|Q]~O9T&g؜{9Ps $dd`jr:0;=9-EZg00HX\b뿋a t{UU:]jw,E@.J\r<sI!=w o}ٟJlp\pϳs+kO?˖._ ^xa1+V.o7dldgRKL@8ZEy آY?ۥS1lܸ;|t:^?bccoeVܶm6nM/x>>Q]tEsݺu?'$ݻ;[P62:k=}}[oC(?C<66vEo8àcr-֭[o 6\J<o>v}8^q1wE;Clٺe-cU_w%f pDqi7l#q/MW\tEӸ+^ЯB+tw{J̽!A֭[6+/$;wο|Ʒwy?-oyeAW]uՃ@ͲȡCI'w"r{ާ?2EQ9V^~-[eْK4=SHƘY~{S2/}iu]9vk;匓W~qD>?W-V>V_/[6oF۷?{%oU+N  r}nwW_+EQ9V^Y6o޼bYirNiZYh>.2^oټ^뫤( wf'-z/zXS!Vc`Ϟ}gJʕ+n/>WpUEQTW3Zpo+](ʱbjCUX}+>-[_EQ^9t_N^q]Ŋձz}:a*ʱ( EQcEQEXQEXQEQ9VEQ9VEQTEQTEQcEQcEQEXQEXQEQ9VEQ9VEQTEQTEQcEQcEQEXQEXQEQ9VEQ9VEQTEQTEQcEQcEQEXQEXQEQ9VEQ9VEQTEQTEQcEQEXQEXQEQ9VEQ9VEQTEQTEQcEQcEQEXQEXQEQ9VEQ9VEQTEQTEQcEQcEQEXQEXQEQ9VEQ9VEQTEQTEQcEQcEQEXQEXQEQ9VEQ9VEQTEQTEQcEQcEQEXQEQ9VEQ9VEQTEQTEQcEQcEQEXQEXQEQ9VEQ9VEQTEQTEQcEQcEQEXQEXQEQ9VEQ9VEQTEQTEQcEQcEQEXQEXQEQ9VEQ9VEQTEQTEQcEQcEQEXQEQ9VEQ9VEQTEQTEQcEQcEQEXQEXQEQ9VEQ9VEQTEQTEQcEQcEQEXQEXQEQ9VEQ9VEQTEQTEQcEQcEQEXQEXQEQ9VEQ9VEQTEQTEQWbgۯrIENDB`PKF=-Pictures/10000000000000150000001CA2470136.pngPNG  IHDRzc sRGBgAMA a cHRMz&u0`:pQ< pHYs oytEXtSoftwarePaint.NET v3.36%aIDAT8OK P;YGuDm} *Hݛ8?u Ũa3X<9qۭ~_S(d{IENDB`PKFUNN-Pictures/10000000000001AD000001B2BB0107EB.pngPNG  IHDRش . pHYs  tIME . IDATxiuvι]}"H, pPT.)*+H%U8Q$lW)KY@RR"ʶH @`` 0fo}{9q4 "}z}x9<("ADDDDDT>uf#6#>_\/[t!#6Cԇ?rS#6#YX88y0b8ސȃ" V*ލ:LO(,֚w^yYQFlFwu$Ļ`ĦU^{]#6+  .ފȃ@ǻy0bsAD@Ã#61[D͋D?FDZ#p#6{]Kȃ(>IC?}}JU DDԃS8s\#6k❈<yFDDج$7#"FlJؿ0DDؤ;#"FlZ=x!ȃ q:"`&%znEDؤEqXKREȃ6;vy0b x'""Flκ""W^FDM Ai17#MAk,\{݈<I$M$UJŻͬ !EDؤ@@DD3#6c]X$&vFDԂJ]ě]=?"~MtgΜw#"MZ\#6koEDͩ"\v;݈<%11."`Ħ%A@d⭈<yn|㝈<~A֊< @Z<Iq߁("1."`敃q.ƌJO ZkDKuQFlF8gG=fCI~וVDD=y;y0bs|ꫯw#"`歋ťȃwÍNDD،9g#6%uqn&"`ĦRJiw#"`fķ9nD#~Ro5ĻB/ aa~NigΞw#"D\͆/YgOs?꽿_7DDkN>zÀ;wl}KmEo)|L2h0:۷SN&7?}׻qDԃok{o|>r4;4`~G>Jt\{ODN<fyV?#_40 [<}~^}u]D&^|ğ|Sp[#bss?=ADH@T56 !J|u"wk=׽}׿/"?vs3o,>'O>?=qH8$"`BJ?:Q `hPDdj!JzMVKՉz0o/x ɓ/j>f~啳_WI(DAHBB`Be`DD@$HoO-)Q_ +0LQFMx3O));i%i*!Dɻk@kM gfgRt8Ǔ 0iYZA?HՃXvL L)QFQ_/ܵ#I()"F$BTDJ+`ଛLFϟ8}vu@H:ѡzVV҄h4p: HQ@# Xk1*i5_."`?'O>C޽Soz'"(ˆ :PZi4΄DIkR~e8FhTEI,T+1;Xq3Y0c@Mwv{/_D7.,?sgΜw޹{No`Zef~9@^Ae'H!"$TDH"ڔp8\_f:77)XkuYgm-$,MW]/u|""FWE{M{vܵ{箝;vn+MsNyat}6-g}gzo!>BZ9wv}}uffF+i'iVz0ʈ_FD8x;waumouvcaϟxnNʴ,9$j֏d?&R"⣝|НNi|g_ݺm[ɡq"'cciy0 d:"w.dCDǟ{VA1F}f"B5a p=(Gݒs=D]EQw "UN6k-fkN@RDHHZ)affLiEQ fRZ'ZgO v2gT}(8Y/';+}[gIi}WՌz0AÓYZ+K3%-kgEfVPlUZ) QkN4}(ˢN'p8*I$I㸖oX1sڥ^3\`T .‚ D ;.8ck`a;AY'uOܱ<:` 4]ss`b 0!jXo^ ^NMOw;Ŵ89uku AMֱ{k)}CDGM8?q{{ޱk. &/E'WD-$DA!(쳙0JC"">.e=,y(q@96Z'NGiO dYW4! յ?A¨#"^Y;]ÕyRCwښ7iY@اkJS}*XθhztZҭ ,Ҙ9;L&3V|wEV 8ׁd|U{xVZkt.K=:v5M2:v\u'!f=Yxaa!M(NvZ$N$K;t(H Ż2j=E=:;=yrmmx F*! ֻocOm>a NŸ~sh0ܵur]^Y!E~&X0YGgY67; ɤ4opGzƮqr] " ~>u/_x9_o lT& b% \hi3J#FDp߁I/{?…'BTD~{ 3;[QDNQRJya&Dq֬sUJ7>( "j,+ UYXi5X5wuG|#FDh9ܴ{ /s B_ z IahuDB *TA!̝U[j5lR|BN7/"P@c`f?;;k'Zg]hdvt4<ء׾Gxԙo~DP)UZ(]U' 9T""옅H Ό0v!ݽGdy7g.0ֹ,/,.x:- 9)#O<Kȃ?f?,w<~i/HQ5WQB+8+Ve\ Kw.ɽl18b8RP) tbRx;@H2UZҔY-oYN]f20 8v<⎻ciy0"ҽ[ر㝬1q"NY!"ք؞=Yد9*qK`E0 ꥊ1 smdZYgp8vZ' n,JS ,,9w"FD[.b/ݛȿ҉U?DODeަnA`w>[vfL6Dy& I( $f46u, DͲ,K@,Bb8`D=׿#K i^mCqz( #FDsBK]v?ځs׷ "CnV& uegŔYbvPuPX) -{T&D(I(Klw啥 kή=KgίM'C?@D:y^N (M[z&4I1Z:"u=wE<>IB{vmݳkÏ}Ņ^)z%b0 [$4`X$1J,;'΁-m1uֽa""Deܾ|՗_~mۖ: ͹s]GOxXD1NDs:]f`Re X? 2F`Hy0b{m'?C/ U_%BHŠ(&]D1M RBB[JQ*`#ְj<lǜfRXGJ&PXCJ+sNk]墵:믋0`ĦÏ<{nS ܾPQ4RR* U&$%B*Twuj[:N}a-"BE鶭׼^{EWmXh`2};չs"txaἛEe RM5*Ta y0`&?7\-n۶Ϝ]Ͷ[C{ ?$}  R>Qk]Rк*jLgڱmKu LE W׼j>=α;`m"1Z)zVAncSZ c4sKrB;VΝ_;)SlϯQ1Bk 0 `R Z[?]X9fy-J=77k֝ۗP m?3{VLk4i"Y'ir@PrֲL )Gtwmg鑣/8g?EqPxvPt=/>Ho% IDATfQķwkSOI@kufwR|۷-L+E<+g,ty*' I'אc|O|WD\—n,Mo[>ye-/o[bPV|P$@ *jϮVyX՜L0[0I5iT[@o_:Y(E~,}{nEH2eR1ڲ{჏EIy0bTğۗvI+57;7ί9J A$2#l{j%ؚ{0!TKD!DWHuu[2$:ItetibK:ZP0Nٹ*+9@qח{#`Ħ~[fg'O,"t "H>BTu5Dt*X!m3ƹ;ff0d<:IҊHE qg4͔u6(Tj l(anoNYMy?5I`82ְc3B㼏#FCd\EK1'859+ WjphZgW])LR,,ΓjDTn?sB?CO3#}iNҽN/K "gCE,S>;:Z M1sa@0zoW%"BJ ':Kɋi6n&8$I"Uֲe9v#$fӟY )ٲB$HUb EiIjFl'mR9vu99;k?rLɖܹuO~` XtyQarRn !RN`LJ]3 ޶)Voz1|CɞHFhB1j$JXva`pMxR9vnx`X 9GDPZyGB}^fflp|[)Ƕh@5<ž&)ͦI?;33gϟqα幪"z PYɆM`"XQ!W}pCgbaFA`vֺ4t}}|i3ƒ`3ku ϳxZg绤XB(f!x",,wKȃoϊx)J34Fx4JAa*gp~{ ݀zgσiF@RPpaRe#E6>:z^z<:oHw 08^zh\c@͝*]@ ʹ1re&DDHj9rHDxୟ۫3ڢNM:s1naeN T3B@i5nVJU= s`>? +r0ΞY?z80(c5O_8/&xWǞc*ǭs;P#;vT1V(Ea`Pf}0v^x|reqَKz} PLN'O_]I>oր0lZOϋs$!Db@+]);؏=>ԡΉ<6:miԂΰr}vjWvl 4F"R-m!vIg"94ZHHŠ! 4cVJimO'WVLΝ]{+k ksGuj0FurZ!R$:MEW 6h$B8tP4<!|(h@pϮv" Aw~x2e똃5X' "n(,fAN7\Xƨ\c#ܜ$1"HwuGȃoy[oۧ3ړdYJx -3<-I i +á6Y*,N+HH!NK5"Hv1E9Mg,w;/.G㉳V5v tp0 (MJu.G1̈?ȱc-y0σnѩO$j9LG,K+ Mj1sxAUU@fT6U6_Rקw޾^tNi9XudZ cctZD* fueŴ;"Z2K: JH&-Ngf)Ƒ#bp*Icd |~ : mb[I곸3 1 ,D謑گbp9a@je* ^qvZረ`0[˥a/\!É8!xjIt  &UiDApwG<ݺWgzWY:|Ρa 5 BRaڹMfjԏU1u-,J +qO >c`AX))d溹 %_ih"uuf!Υ)9XX|uN&42BUIs~컅4'tIuizMLE E-ς9B Mjȭ2Vނ5BQKژtAfkskMsƯb^ODSNT&&,G‚Ijt Y$~f I2tLLaVl=(#xQ^0Kv\9F~-w?*#$:#W:=4-tִ~ &82n/O{ژ2C0H'D[JR.Iλȃo1u=4$j!ΖczC./E]KkJZU椺?2LgQFc (;]|]ӱT؄WjGd4K6@,]Ȋb$@l7~G:=[&ȡ#O=w"FHp.:nx`:3[/m\OEXj͉RBLimdAMbġO %QaCT]r÷V}u(E 佌8(E?r͹]yaaEKwFȃoR5IyN!]޺( DJ)՜Uތ%5i%j%@?\Y묗WUZJW͘{G!\5G7x0vN'/$Q:!ZGHcy=w]ccqeS "i4<Q%8J'(g&P4HBqQh4X*j\`U㢡`LЛ>QHa)a։-l幠iRLKK"^j]4=gw/1ܳbe-ٮGGȃo햽 Tm$'2!?+3[/UblEȆXv:{'mY1t6P ,\קuSƭ'E_7d ГT˕>lWaŴL5 T'3̐BaV ]wco5۫"rK:=oCN8)HH^V&桡E7Yk̵IYlUvT *D/+Va/Z S=l1 axN`,Dw 륱hL Ym2$JpvFJkI8'ﱨ#bpN4dz+Ƴ[wn?0H>N[u#tx^e(-s`ڝ)!M3@|ǤVzL*nf[fUl8 avf«31}1;q;.|p Vq0`ěX[o).;PIzOR k"B3KwP#S,"r=aYSm& >@",mWr ?W -i!ƒK3] f"Q3/==J:}jXǺ8M~pm{;yB9OWr7q)=+ɠa0ƠCPdu03\m΅[`GkpVX Pf!l kzfCaO<A1K>bpcd<)"Kz[JHi cLT$&[@v^9g,hb`<|n`ě }wPIs=5re59"n?[?[bf$D/8Nc+8Ȇ+j0Ofnz!%>͗vXdzN d1p8(.řDY$!,MicBC/?=? &`-2%,HēǷ\ԃ8uǓ'?#+⼓iRdт&|l*c6oD=bÖLȩ6wBmB#WZnZq8N0:&9 &}=XvRmV*'6ɠ ( V:!"SZ,  zjZ{Np;foE#o<޷}ifI4M|C?[ݲﶽKsqKKZH,mYCJz6JUPg)ak_YjXjQX}3@8 ӎU,|M Hhiu<Mw$κҔ",FdYocVĎiǙrs]gy0 _ɴ(¢fnnfvvFk? susJQ&љj(&nؕK 2pq֚v Q$ê[S-ҡсRݬEo*%!VyJ>LIm%Ġ"ߚLX{uy>OAz騔W7v4KcVb~][h=plṗx;{6o0'əs$}aǎvX^^{?~g-RIUJ ~Vj{4xm&q3TU&o1IꐼT+?n(el 56DU ThS{Cʞ10*EAʆ„  [ rسegebΙ ЇAȃo4;͓'OFx2OF1va~,x8m߾?E>>p۶-k ZO!x]"VnɆ`HxFH,Uؓ i|ZO[ɲCh.qJ p BKܰcM5;g!Ӷ{eH]ȃoosϿpr2:Jc<Vf2pk׮4KR?V!Ɩ Rjp&TЊ!k-::H g~C`guK GЦbWO& S7 V.XFt11yc'"T_ewI8k1R 0v;׳L˘鎻<y0 ÿW}f:-c&"Xp4eQt4رunnvnnǞ\Zٴ,ؔiꗆغ0+DT-k;L|J|![cEZN US]VBs@f&"67^5GOڬ,-&6U!ǝ>jb@$:uίvg (PD uˢ@r0$]e*=$tƅǴt*1L4F/@"NBue[S]7U2ܯ2KY"΂lb"`׊Y!!59~řQBϞ>LrPa{XG=S/W=r䙵5},ϝ>ʧ?us!/~e4nݲ<%+bzPJ5LAFfi;P{kpS0!]TVsX' Kػ\p5S]46>-[ª$j-O|ر/(]|*RXcaX.<>ˇNέH@ 7O~ӟٓ@ם,3`QL3L*Z;@ğJ݋7bU9EطDSi9E(uOU 熾۪IUڥt5ݮGuɀg @TOg03!90u.>s"hN<V}_'qEaR96'?n?ۧO緮,eyfLiMY!P?:Dm[5x-tRGh8iV mad/uHw8t"λ˴kyĖk/R?Q*R б 1;fu u}v=-E N#"Fn|߾;{fm}DU73C>~m[zn:-F`m}? WթAҢ.Qx5*7<r倪jZZ \"av&~VIŪXwoqfcunÜvKvK_{x\׊ȡ=z<;}UW%cn3}-nY]ZҴ( cDRl(R2HQ0e$__Y^6iE } 'CcPpn*mŻG34zۿ B$$'z{cI#9"қ:$Uf NYg0sBv.ߺvŶK~Ƒ#^7;#GyeY:A~$x',eiZfZ F1؂w\S4gU aBPQUU7h6~qc^k N$֟ χl,]qec@]Swxq_}AUKxRit.Pu s_37:;_:?ٳ2Z;v#^wE|p3ӟ-{ᄑ[W(mi\I 6r%$"RT Vz-­]Qx_j|$"?ƣ[[(*qnmwo<s}͡ /PuxC"َ9WRUMڟf}&=-Z)r<m+[X/8 Eky0u쏟zٳD\ٲ87;f^ } aM(/TM˴J*fBRAsԘis'7^U"S6y!_ֻoͅH(cѧ,9.ȥ˱>a?P𥱰cFR9/BDE1Oǣd<~圝lon0kI,#FC‹u`sO}LeíaZ)-+\+۷H#NYjK>o_Wu9Ӆ^k]uPWu"UXOZ:jPŕ #١\mp{AlԄP 봾|3s"@aǓn IDAT[,j;f4M'`4L[YI'y&/v/M{zꩃ$<#+v,ꭳOx-}m+˝n)˲4gj ]RblVnibsUicaݮC XGi\͍VE$7ǍW$^OCh7]ZF`~C?18fhgʇHOy3,h#sa P#9nci0#b$`떽晧ci!Wӟ7*'=me)"lf1_sjޛdIhb0Ƀ) AC OLC`Q P4y,EJ\$.LYfwWv/g-Ek%mQWnngYqJ/˓ӳYYf/)aACkl[;y4c/Zތ 29ΠжQd,Acd&R=^."ϲׯQgs/~wL bI;rkJL >o[fVg/EUIBo >_|0$fUm7,J % "Zgx:B:kz]. `#Ӄ usC|D@L6Hqa}`ZV[g|:0A&Ľ #2 ;K6DfESan5e6IvB)${kZkm|y^J`;NavcRR9<rYG/&qp}޿7?#ՠ77fh$3ZZkC0#bdOGak)8%:k5kuޕe]e(HHfhlADoVrq~TX dXbס]TIZkK "(s'qU"!Uo:_K@!%d`ؘ$<}ډk3숿_W^wMAp:w&ӉR흫kv wV 1B4MG#u:笳UU.>88R* [ !vtbca!ХeYKv"l) Ā?H g+Yy1:|ca{{{hif#TY㝏 o /69%$@)~?5Oz>ɏҷw+{:~Qy筫t]Օ%ÂEk9@ D ʶ;~+BY<eZk뼵ZEqGRBB̤s}q;^}fK{uPbs0tS1"R@S R9y(j[8 Fne)󓒈tT* f).pywq,~fK) \){G.fk\3|c>_10(xӎ {q>lH)u];Jkk,z.&i},󄞜uB!1JT{g1i86ŲPJqe)(UaŘGdB=b-P3waf&㦄)57Ꮸ+X; nvF X"/:늢ں|rb'q0@gY' «w^]*3Jf5.@(sĺ$\W?_—t5GIUiZ;ИC *8n[2'``)"UD쬷ZcΛ1iYT<_=p cA3~sVͲ1w\3]~9"dHYjkuZclŒƣlwqBި1pY뤐2x=r3?ZV <}nx_v?g͛?8eoGO|`'IlBH>BV>"`!@Hl7ŝj6k5| {0=f?fґ~<|ao;Mxı1[UPйe9\RJ1d2NۏN뺮+罊xolO777'\eQ1֢՞3;-s,y@\EUrQVpye661ra6&cBc>; )Eč@kSJ!3go$Y߂u_#}S򵣣_``җ|?ŽݭY&UUu/jQ. |9xuN_HCiloC r"g眓RHFTDr\k$H*%MA+yeޓZ¶D5 Xmq`9B(0T=rkʢ^ӭ(DU'BD%gPsKM;Ry@Qx?g]='_+K5Ǯ2ۛRtJ3s~Q$TM.T;؁d#10M 4_QbB5#s%&I^JG\.˲-41&IRJ UO31%C`|"B4r&|.⬌ds0OCwL=yX笵D䭻ro;f[ 'aɎt;Uq$_- xsbQJ^n?S?qvB+ 8ww44yUPb.RR$d¶Kl-]zZ"jH!K)T$67GqeԵֵN8I8PJ"Ho7:DF! =0€䑉DH)C|HHB K:l1/AlmLǛlqS]r[kt}7^ttUS84K)Tت 1#a= %C~7_LN)JifOg/|uI㥗p0+/>|z勓ɘ&)9Z.!lĈj]m\SBʽt ڊ[rxMK'HRHmnF(b^Z'IR0ܢ`/hQ {va3 ιYY.+gh6ݘ6㉼{笵Y8gɃݸk쬣YllI"26RjJF3+ce 0<8߿ԟ~9w~G{A=O|lwg6FqdZeU.AJo3|[JagCMHP7"N[ Y|Wl=y)eqeiZEYbiM$bFSoI! -nwֹb^ŲRQ8:}[cw9W$ u"6,,ۜ^' ;5nSLI(p $BlE3x;A Nsعc>^ۙ 03{)Th(FeQ ,(#@ (CJiט4ߖ2{κOg'lvڅQ>䃗yo1֐Zܸ6=60{fH(ܘı =8IDL\KM/KQlɃ'B X9W'}>ї_~`o<6$:{'VQK]HȎư=C>Qݍ *-n&i ( !vO;_qIHLHl, !%x2srt\Օs6IQ+%Cj6OvC#3r,޹+ٔc;eAjSdHJAXҊmHO$ 7g/Dwpa!DLD$'8gYrzrVz,6qgY*RJ6 5O40iB,;􎄭E`GHYkgGӻK%ݽMϤ!LY$йn\~' ;a{A}\<ܜ~ pv[7kDԵX BT{XK3Kn8~~緿_Z<E\r^>l2'cvcY 8:;-M)"dPvgoE0k*خV8hZ ]b6p~Xyآ !67hN#A0D$ PaO WH{N.ώ& P kk1 nnL~WZXN?@d8:;&s$R*ˉf HEw--;Lj50yǡkF}5qwqBO|Q9ͦY6Op<Hjdm/G2jHmAcAƼTjǶla(S9xee诉{4$OLB!"I&EQ,b1_ĤITHVݩNfGL:<;*Ey5%jV9|}7Is4pn /B`<ژ޹WBIWy |Q֣z"T /뛲|"JD|~7x;ۣ(I3k[>MqZ(!U,˃KYI2m11`ŭ>!{![Q8, yBfk6iˢ&I4I8ۙc_27IcqzqR[7.$q`'Gw >xn\a2-h%L bvW jN8g|o"Y뼋D=`,h+G9"pI[50iו??~/>xs4˒,|trzL[\ŲZA@J9!d i+:[׻j>2 Wws@F*a`+Uz"OF !"9ʢ\,eY9k$ B!,E ZW||t3X{촼Ά\z8筳EɁSL[N% y19'qG笋P `y!xuQ|8կs^)!;?/)lD=߻׮\rkkB=\SAvvF=fi5ƭN*J\(,r !56IGg}]k4DJqr|bѺ~e?A %󢙑I}O|쯿Wi19>: O@pѕͳBHmO}fubXi@7]WW j y` @WQ,ʢ<=9Ӻ6vG*I$N %pY뜵g;[{[` 5Gon bko`axҲ@:`s-< Jh9j-R(J g B8='/@3ysf(y?j]Inڇ?PSܸ@Io2|/sVٱV5XYII[GY^ͅ.9PCAݺZ3"+vcnM019;;{ۛl(rYVk>;Ƿ׷wZ㜵ZmI??x 罵x>`-fձO,ؘYc')@{d"g='b(L e1(y~Wf?#B@)eEֺߺq/RϘy󟛌,O4MT(Db !ܒNEWRZ`gelSA%ov_@Е#s.< W uYҋl^,ˣ,w kuֹZU]icg~]?Ieo?"mSQ BSC!*ܾrl[-]EfOrR6Af8m_vF+q1`l𻆶%bJb!"UU Y+Ir \skMM?>BH7 hw}l?!l5D1slRL~#xUι8I,TͫdR2RR*RJVU׾wҥ `?@F&hcc3?_x/$#,9gXDַNnնRu{vYRa@Jk7ݶ-$E+n-:13Y$uN.Lוj=OƓ<yRGHcuim֡ L7_M(!,0uM!}]pSC=u,&TjYdz RE_֭|+qefRʺǓqguYGq1{;ɒ$8LBJEJ)[{?s6o/|~jӄs?&x2T1)r<]=MpΓg`"H]ĘBp}0Lln~c{犦no~\8&[%1(]1(wgf HhfO&:[ehۈѦ`ٓJ"dB=@BsV ű'KQݹ}Ƿ9  FGww0wjoɅ 3?[;W\ƣ񘁕0?;F /zDJ0zO!L$l5#:[]O8ch<-Bة6}Y{_c9D$`k:JgQkgxNUYg=ysڕ͍0XHv+]2b6Ȝ=;iQ-N*&pf8Vãcb%TwJH)ֵ*h(REO#陸lcb&fIAÏvY;ۛgZ yë{ c_nݚo; ],Ņ wFc@;vL \(_橋/zS~d6i֚@ $eUHHXWf`-8Af7.-.g"n6OZV9qfXy8 mhcfg}^muh"eEƤI|ݝm,1h<:M iF 6k ;""B1Yzi%w>]JF;Gw$@3h:;G`;Ӻ|#ƚԧIֵ618f&>33I!__EAD&K:==k}|߽͛xq(\oG>ۿQy> ir3mIq=5̈%v󿦥 k ["]eO}WZh m-WIy3w9Jt41eQ0!"6 Yigssks5xYU`!CDC㜦V%&4ňt`NEwٝ9@KqB)vw˺`(DSymnV9`jkQA 3Hp&&b&-坓;/]|͍㓣FDz(FtkSʂ {Y=FIuI2!u GqJFm/}aXM Rfqt>ǦqBhl1Z[0֦^K}{i ] A.O@_T{AWf"@"8w[SHo}o? _00{reI:@ 31. b9D%OF76gE 6YUJCT.r>ey͛7ے5E'>JgdPu] z BP"y|8)DG[[u-s̀P_UpѺҷ2OvԥJ|00V#&Oyk8&D󞌱֘d2NQGcߜg#g{ $P&!64Bt4iOd9]=;)4.xJJkZ)|P"qƞ{o!緷6(UU (8شMU]9]kzǻ/\8:; 4&޹S Hs( >EZ:dvpah/ⰧQuw;O](xznT6ӳbY"w}8}>GfI*9bat E(mnBH\Ϋ WbnCfۂYr[53B۹"aک/`u?=9;d\5`́L9EDJDd $:K+]qm9&qH)eѦ%@j 2t{g]?w\Y%vnD$y,!h2>z¦@!f$NUл /koPxQ@nỖJB6?\?tSBѦZg֭oy90QLb i=+ϜsMq$eAӺ:_Ȁ V/qŞeA3Ck?ֶlihڗ{ ЃuFuc&Ƃ4l~KGlrxt` ,eעnq9upFD< ,zO`'Rd/ҳ=_P+ um<=Z߽}uI?/~I*%wQHmDtV)9KҸ.&mnjNY z*;0KAl(Ъ3px[uK_w>k8c8Ͳ8IY*KkLﺮ4e c$i,Fi3;oo2--w< ByJgOB"@"v]=0f@^7sNZJ!0cr! I'OR[0hŹ1@!U"FFu]`h=b`@*|q")vnNg< "@!(oͶ" G=Ilks"Z@Ky&~CDTEX%8CxGD҇~jrYZ{7n?pgYB1qv^̵5 CGIƝv8hH;\Gmd-_oP*2@q8\e4@j5`#m1(l;g㑊16TȺh!iĽK>y^U3 C:ڙw P&*>{󁉝joNӛ LDI_p {@b@eU$3R/vJا$7^u\( ; dG /ش폡ZkSrs5p?hc'Fy'RyREBV˪lB8ڤyDe>h+\vm7YZ/s|ö&idV}JI0D`JafOh Q 'f'ik]͍`'0!x&6uͲLHY{^UnS%ewAv!K)/:Eޣ Rdmk0v,[Ԙg#wN)) K^~CֺtQ'eY߾}s}8r>#4K$MRZ&:DXTsm53 9β,l0Lߡ9`?2n%7& nXW.mرR6+z >O ^ÞaZST [ ۖ9=.˚}L#PW.]B6Fr@D|~ugSцJ {F7#:S7ޑw~nw%S3[捣؅֘!V^̗''g\|K幊bdEj,0D\;yD)gFaŸqggƿ2Il p.5jJ {l+^2xEA<(VDD>期*VL䉄iچ|trĎ0ta}k}8}>gx4I4Jb)r\8炙BzAA9q&q܄cĚsSvo@4DXD1  \AN/Di4( f(ްΊEi 'dnI11am7 R$a&a 0Z@@bd|~zW%QD2:@~`x`O-ik(]J!AHDBZ׭c~癊$ $IVwSզRs}L%(R3;l9QoZ =b R= fHFwڱD+Yuؚf`W[f߮K5q,DCc$sdqa`wO*ۦYe8R0#qX̤Ul>ZP5$C{O@ [__~~\KԢ|ρIG=7;1?\J̞#={_2ڔUX.5p߷'>/>1Q8>Ј IDATD!YyV肉ٺ@!$lm"p`bc0Z P "^&ŀt i眹u ՙ3#" yqYV2ޓ5=Yx7ᡈ$2O-#oM"; ,+"jeQ*'wdPB3# XEXgڹ_ݼusMw6v#9+SkOv"t®;17G!:眳 @BIv5# /<:}uUVgѝ׶ k/+{>{nv f)z%"瘉= vmw'k>KDLD6IIDCjg>iY vi]ZB4=ܖ86EYOOXZcg>>ϲ,UJYWeaf;b>+Z,GW!X#fJH07Lr4\T`2 $w!6k&>L4>ZC6}qII,µg$ cbU麨+=[{m0o-cQ<˪"ahn`n)d\~;͗rokwOz墚G?΍-(uA2`;ĝ4Io? HwztyE7 xˢ{t\Vqo|S+OFy$RI)%g:@Vz7yAW,M@n㺢uU^vXXW ҈eXy[ "HasJxڏM;8I+)<3{&glEit87k{ޛsfeUaA (*~sw wK($H $%n;lX@SY9L{orKݒ<* y=uZ$P ^\X;VdUFa4%mCXda! GӣDeir5J~/?ԳܫYqQz!*i͓߿`$9Hl޹{絯V|i'Gђ:z\Ӳ*{7X5f= {nŤXDT$ G>*}~S* cL F46+5/X%9)iRI] k˺t At8oPg6L\dL HbcJqaH!iVсxlP"˲2/1X0ћo*nlyiu?oox_CFBUEu{XU7t B;}z|N D-,3 %Y"C@HDփin\_Xcђ:w&, (Tb0Qs$3wCVDlƇYN&#p$JZK(j$, XC*V@c'IuDž z0E]" }/,’cb"cm7M#' %wI/NWWU۴li\Yr?ʬ|wtoL!;a٭Η~.9Qv mMΦ6:bj{90YD$+@IaJğ5vMժ=?X.WD:r|~OuYU>db1Zy3v. 1"&E՘c|? 6%Fƕ*ŚVYW<1~P*ʂ dUUeJrlN ҹcy¶gG!8? (JUdmہB2>5)Ĥ4뽟/[1eȊ +/9bF,(%+RjƲu:{7ĄB!@a ,Rhm(Q!ހ;Z|!ZVMsy9?8gG3us'RWuVR)T*/Qx["[K)ik-J JN1أaKEŔ6#hX _HxWRB7AZ2ϔI4eIdS_%Yo )ݳ i'>P.i]}^$4~By5(?V" Vn1y~v؋K)`AeevpծhR۷nPrLDfm,9QxD4I]}{oAk~vz;ףu>?Z]M<+Q()VKq,GFA )z nXFط ^Ɏy8"m B`pJ\WR$bfrtL'aeYd K&5}Z6TR QX)F$t֒TVuuh~{ACgtkR[uY f̰jWrg{K/ӷ0 @21 $#[M-y Ng76>6]JȰ1t@aphɓk:y|绿7lTU=4MIl,EIT1{zǧO!Iwtd. &/]zKb.r=e܂c2Lh DsvێKD-aX ]ʊg)Juv.`旿[7A$ !+^ JztO{avQAywai3C6.X1ty[k]{r#d)P i8Yfbzz׾]4уk:|{ߚLgeUDrf~{'/[U$")( D"T.AG}70$7?I6B 3 nNBXpD, ΄cd6R5F/qr&&fjbRL&ΛeZuim !;5Ĥ,s; F( 1"ٍy#FԵrx!DTG興PeӬZ |g`eG^ʬEAdݖڮ~<:Wդ $!k׼F1 uYA/k=XkpzУUT E6~mpDӦղ9?fu靿h2D)um >H]\)ĴlV]1Bk<'] !B% 5˜L 8JGx!dXcOρ8ϋ<τ@+!`ߺ!H*uJq6 v&(ZKdz(ʢ,E7a g*KS$]zƋc-,Nmz@d@_gȄ:9;ր0 ~Ы_|%9||ק" >ؽ65@PJ<̭_WnݼQjZ,] CoifXAIt]e3A)Rw߸;r;Ghu{;7ʲRTJ"@״Z,jl/HH"Pb67 Rt\5Lǀ9h6 X`@0UFnי^f˲(2˔sqEe66FEARtYՓʿ0KZzP,vi ȷ7v&uPE2G9abdڗ6D'inROԖ0!J)89;I0]4{|sys_|KMPZe#F"oݼ|˯|>TU}߯V|/Α+L023zj?ZiӶw^ ~߯*b@!tSh&{\C^zeY7vP ͗<<S0=SxHDc]0Fr4xȒ%b%da)E0hѐdʲ Yʮ4*Sդع9p~ʥȼ얫~B F*1 CQ|f,Wk{v^AL2u$H E?:bgq F_.- dC? bQCgo"Fq#RB*0]Q9H+A FX"=@Dȋl9)RI%D@%x16bAgB !Z fKF)lcC?233Yk1}6~WIKMbuaH1)nY5'PIU-ۿ9z_y˷he$},/'M,*t:n%;\jsc$kr;E Zk,v%DmX?~uãGs'mȳhDO[ e\ wAճz5#)5AJox=)XIEA١:D7y.VF 02"+@yJ1DfBVhu]׮\fEYE{g1C;oD$\e2}Dڑ`#:&8]3)HMLQ草.q"t2â$lԓi=ۮ6w'RmAgRJYpRzZLZy}5ȸ5,% +w0ZP;kjOb2l8ݫIնMΤ?+DdLeųzomM7m۷{7v_W^z7o>nA^~I2fR:.M"DԚwc?~-Y>y|ѷwʬTVb9wߴEM]*`/`28!q&}5Di&d(1ģNבF5s` l\{\ACY҃BeYf\8% l%q5Dc&$u VJ\C  !FCƂa!ӑkգq앓u\L'OP22Iw<9~\f排Yx_~ zCYIJ eWwwu_48M}Qk6#b|:mւ\g*\)\08I|z/킈DfAe2Ut7w ĒEk/7cMJw:H4ZqL1|+lyLAI\:6,\:١z0E)D36qdB=0d27 NJFs8S6V (ZB)LfONkҗ>fwsw:C{ts`- Yyƪ[:+0D|ݝm`.K[R %"!&w}!iˣk"u۹,2eE.Xq7ru9 kIi  BŹ0uR0oChF6et 2DG^iD'K2;=\]B̠[ JƤ21[k`HSVd!X*18%Zoζꪌ\_x|O 󹷕^Գe DY,pRۛvv!P@UU)"Xl&kLn(X!ΆwFYu0񣣮ﮉuo~^xy&3gJdggnzo<.hS <&L]F8b:R`p1=,a?@;;,cdS0a = U?!NS""kg,]{FLC?,\D`QmR( 2[PG. p Ǐi3Ha8)Z{F)UǏ|a{ʇa~0lonT@LZL`$()n x3%mDog˿|bVR!f4d;o8[ӳ㳻9vuo:oi{J2Q ,^Z?F;~5$Liw\"YiT@TIn̥}pT0¿~Ⱦ?y -#KRN Xkm=Icrd6fZO76fФ}sZP\3։L2,8KF 8Y z2m*:ՎCU\?;UYFf0}5m\5KLh vh󘇀Ye,Y%5x f޶GHIֆBf,SwYckm۴G|^=_~_ !<3͛yӷL2gyfm$1= =cevt̖B,e3jӂ6 ŸЋ#,hqrϓbv J!XH$άTЛ,ύ35ovI Mn<(P*)QG1#~Ú}४fۀ~))0Sd =ٜm20{66+=ή6fZ4z5 FܬH%`p.BRhL\DL$/3onV adRȴ@$j&w}ZNNNW6\u0? Rj]ϧ7 f1A߸)%Xfg+4+xs'L(&7 d@J0P"5u SvB!二5$a"ez.?Zc,fӪ*=:tC3&("/dtE bTxxQoWn*,rEL(4!)eQlN6bmto駞{/mnnB3Ӣ7k$(e2&!ɛk -7Jab)~P e/y";^ˣ'רuF{"yvZkfiBƺƟͲӽ19!2jD!fDX)(0[J✚bxذ6G{FSqQtO JY2 U?(`΋\=0h=,SD>N(FHL=k g<ˢv|ї;1ߎ%d`q7H6PL"$%S7~wϽr-1dYofb9_H4ًˋY;@([UQCGYHzd*ܹcL1BeYhd>Uf&1FLțV`7x~0v `(zYE5M\UWҕeEKF)DA崞ݺq> @27n]-)D0GL߇,38F irgu YtM}o}뭷ժ9=={}x]>u'Wg?z™ t]/4xV]%rh$DE X!+Hbqd=)dPӴԏ:VY ` aM<!x;0ӊ^k>yQOljECJ;eC`*L(>ytuk֐DFK<-#b';[B #_]=!͝=U "k`Alnlc2UCҞ@VX), vFؘ 3norJ*=h}nFX2RĔgу?7P)H!ܩ\.?.s~w/fO]c Etم:/ ۡ]u @#RJP8fVe y"ĭ-Ȧ]'SWʄ@狓'F[f 21F*Q׵T"-3ptp4]c1l6ɔ\t d-o6ZH3QǎZC+BjMA tTX:?7Q*W ,JvL"F*Kbߌ !"?9|&(vt2ە(Ⱥf- 8(dhxRMrU{]㜊D\6a^jc2PO,QaXNGà[&(_h?X\fR$"zVSR0pߙ`R%/n0%=3qv4\+Q"jb9&!b2֮5$Py^u=)"2wr\k Bl<~5}aRF& ZݛdJƦ"_^O{[@vFBDd,>2 @UM(5w2Te^̗܅4Eq37$("@`ɀҥK`ѓf,\* чܲȎ՘N&)epf$EYy^3뮻<=RJ'!0]_s]>u_3{2fTa6}ۚ6]jWR @twڈO}N$Č }zP D7Վ.80'2G21[cO/O\0sgYIRyy^ץ54=$k'u[]-UZ-.i lْ{76f8Q@1Sǔao^~7P8z"ԓ:cدjD+)2?9;uΤ2d#`9c0oGOΎݘlyiF~ vLz6Dk-"Xc5NjX0?ȋlho?яkg~ѻO_-*/%{tVAgZKq崦̀O[KQ"tB&2mRkьڡEy.8-xtGH sXMNBthXkCpF2J3B^xEFѝQJAm܎1ŁBNIӶDtUO9N J"+[CkG;" $p'GOʶDvŪYZ)zg#xow[Jo!X`Ntq~a__ Kvtk_|?Ꮾ\:ƛ?\(G!\.PE^nJ`@4,pZ-r2zNMJU[m-0G])gvwtlEdZJdfR)_DA6ok_o'>g/LB)ZB@!҉8If !/3ڈė0ҝ=c{LΠ0GA?kCTd:m( Q/hn]0/O'.GKbX̫1f[(gEYx76 )]O;g;ǧg""àL(i V _Bz.v;,2goĚMKJmB@tc Hs#-В'!n#9ϻXkёd,ՓCNZ,B81V:ɐu C7!e..!EL]M2FSk_FX 0y 8@tx^F&, Fz t$Lm]FBO*TdBaa?:yt|ztzY5˲EEYx緷̅کV6]Dߥ2Vmy6XǸ IDAThgwgt$>>uPdI)%$ )$ 0F"ZKg,H*ڴMR+Sv۠X57hcjy+.=|98ℋQw*˦#ƒ,Y&ɲЕ T9fC[dRsb(l-y̍[R(#V4E7W̼"nՒb `Xo$?vM|;s=W)*ʬxt P6y6#̗ ,6}7*Qq/`p5n+a En& DQc4O77CK0M{g_|fv=÷ RL%+P4|BEjjfReFLNHY^qQJGIĸ0pR6E7`KnJW!sDGBdD4ۨ,HBH)E\fnTF$Mۮ6\0ev"ٶnlnKQ8֓x{jlR϶Cډb){Spp@> d>Oc#"ue^6]{raf)ڜlJڡNfY}e".A"/~1:SY0 3 m?zXiTo~||oQMaTV{\~}ΥQJEϧ} k.A1 uRBJ^B@@\O;bv4,SAyc|$6>N:BJp;B|*qA_slj6"4XU%[!謑$ܜIZV˪L% m;2W;{?[ s0J@GH$BDfE5%Ռ>'t:^%늝֐aoNfdJxY?_!B2` KR+;$A$KiV)};rR%TBJ'۰6BKk7R2͎=@E 5E>/VLDBNy 292!Uʅ-Q~H6F,j@[`c Pe^ZDMaPW 0.[9= fWP1kİh}t ig0ɍGD)VӍV;Ren-p3Ͼ8Lq,1$-"*F) s@!Q)|T';hco=K,Xm'k":b{=_A{" YB-/DàB1 TRE0<03ޮi8ƒOz&RQ#_JCReiC1i X&LU^NȋB$ hX./˓vy}BDAw iĐj7FQ aY.:&Y5-:l[Sq|BmcB$@)0 ];dYFXKqa W"ϳ,lZG-H0߰$Ͼ v3іb_¼qb:$a-do,ϋk;0Bhw `Y4謬6g[=sOx&(Ha r `ˢvE\C^E[PURjw92houi;wZ E)<5ݘA|5 ]3طdV tRbG11+ ُdgvč%3#"3k!,Hd2`C<~1 COiic5~2`[dK^`{[7,[3coI~&YË̪7߶nv3v|Lt<ػ('^q-lPtIݾmٛݏ\RvS5Lfw:6<9ZOI~ǑĐM1Ly{A n)>'0/2"(s%ivuΛ 1ADc Tɣ{P)}>}y!W5ƉPq>7~.[؁pa El>PZ-I˾:-PhMZ)vXԚk 'Ӫv?r&\6s;\ugUL;9a#*eP Jv3DcZrIDK Cu}Y`lfdPfi|޼c{QB&|0BE)>;>R m!˙7 xЈ0OUXQCn~V Rղ cF@ RbQO&CsOg}K ZE KfnT"0Xw׽]IlueW왽1nt7NADW&/eHZ~k@Jhe~ֿP`$s}hZclUyM?Ng@F ab˲ya 7CD_vx?]gOf߶" 5Nn1*O'3bA>z9x>[ ›ݳg%2F}:ݴ[1,Պ\VlE}VcM2vFg?g?zUݾu~N hE} @&»ˮ(+ 2Vb^Ks8gfFLowY3UNrHFqHZF=ڱ*E)R>$";` *DʣSgG(C?DصSKB%I"!)'NezgHm!)IA5dL8Hf@iq RJg V- ǫG_>l aBM]Ug/-&i7tƙTdآ:g߰X/R^RIhcqM"!pfID=힀c1J Y2X&cKz~p8;¨L NB\BfŹ 89Qt061i(Y~H/ˊw;X1@TYQTg%&i,/[3U:]nN[≃y@,052 cܮeʵƊ- UPх~E$6w}JZv gt| b˽PcDtg>$PQ+XffkŲO'ԍNWi8)a,rO hGNvH![Y5cTھmoelr$gnB㓱rQqx^o,n1fwbbd1xR7VdL4~m}\U(#VUQ^]],fksk)(*ɬ&/h" nuFD.FG1q)WK"tTG5lݹe'[h%EȫX XZF'&I#pq}*P[[ AA`Q;h5&!'Þ bލ6H$t.M,+ؽr}Gxiɽ ]%!@,l-g!kjyxH !fs"K?>ģA+_ȹ5u,!qB|՝=t6.E!-9..KaFoSՃ~}4&@M=O^?wUN!0-W,c yldxʁ(1f0 < <>o߷|ݟ}7bfa_nܪ\H&(,f۫=q: NL\ ?:'s> '%H9Sy1H^7y˙H5yH̽9r!͉660 ER3 Tŋpv5 颪N7" vʒVC=},6}7x<:8pvy5@U|BfqH}p`-t \^_c!Ѝ}eY醂*86={mʜ<!:ͤ+s{^R}ou֊臲ݾl>Pǯ4vߪ:OOE@@Ln6v e][\X북IY,7l!}K/4ɢ`"fAx 1L211d(FmF%3vׁntnj54CjUcUp 9&f6a$vM5!sT=L<&BB8JR6]Dwt6ݵ{oHc 'Z+/_8cf\aMϛtlw7[kF[L9C9tZ9utkkO<+JDTw^|tx+"ᷦge%"UYJ2 RכT Azy"EY(}{wnwvow}} ]L!>xD`j_&<\=zμe<ώ=zg\395wABf鍱˪ouyb8Y ]-b~ņA Iɮy6*AQ$wLu^T!&xH)&4'~RjROvtp! RUU|B*J )Qg/?oGd0 OWbq ٧ѧNYDM3ma"~. *ݨ-)~9Eū[Q~-cX)RJYk˲~A:em4>~k@fs{{lw~MGȣK\`\ϋ/2w}6{m1ͼvM)1=2]Y#,,[G|c鼕;(2m\.i lbE$ vS6[ $'ca.y օlm~Oڭ(DABus>"(P9E[ Wh߯o/[ܟJb~ȪEۡnכAY*no6n;*K] HiHJ6?B6ae(S\&'g/? Kr '0# e`Vɰ?e3偀< ;NꪴlN#l:xacmA㓇[η)3VD3m[:~{=GFe2I8d-9-a>a_/$4}_Wue+DJ,1|%KkZ[դ캭h>j*UYu}( I,Xiwj_{J+6l) &eŮaZ wj4V.2v7 pys-d l>gkvZ; =G׏hslv{֏laj+|#RZxXzdVݼCφM\56*0̼Jf$3*a,5X3k0 Έﺶmv,Jq?y=+eiV2( XP0<< #t&TiKN 6/Yv cbZZH9aUT/._/~ɯkdFHnoln.JbA$9Y=6icc7^c(Pb7uwniDgaشʴ1~0PO{e?MOIUVUnnZ*zmm] "j`;z-ˢWGGGnn۾uYEYbiMA4f^LeLxb,^7tm Zf:)R| UBMve@RqW(de33RJޘY1JR,飓G"6p"%> D-\F< %0ZDk'`))$w&]̦_,P%"|o*BڴϞ}q<Χw͍&U]vNЛVD!QXd}eE):-K]':@X.k K8&  ԙ)բ[.I?^ջor$.XD+0)lw j~ ~;`C8Z@Nk],\.""_R^I"RK uL"`.Y\$@3f˻+KniӀHG8{dײ7t6}z"|6?Z0, a988Nfgg1 yȘX#m0eFZ`dzA@AIJWC%a(/ʢm7: 3 Ve]"|y{ bX,˲wAV!5U:XnY37Imǫo>ZF,< CYbnVc| IDAT1v~?w^of]z53ת&R`Ƹ3dqrW3XEfKX.rn6f#ׅVJUUYeɬp#e#p(//nn6J c D{uy[n:EU8]iO )zi4z)}kw]jBL'nfb1IwӦ~<O5HFX(%} ,wʮVZ,a%vcAbOcFRq%v[\L|6v/YjH};A+}8.gyABMg.N֧lPqzOS ݄iFw^=}RX gDe}vSyE0[ΆgP^o~P@ʢB+"b5lwݝ* 3ݮ?8<<7׻ctf4Ɋ@]7FDPx|gY9f)uMoqD({ӗ(2λռ;,W9v:w P+5 \١];DT*bZS/"A:d3 ZLtf qK*.4NV @ޜnHZk4 ) Ȯ5];0Jlr̲uCgDkvzyԕxo<`8O XBLf{ ̣"$想4Xvpj!=G0Ls3E*z#BfDo.@ @Ժ]۱wkP1S{7jdt6h񍼗Ad`Xl],)D1 {aкP=F!ɤE9B!I / w S+TgSv D1.1y+6{n3rMͦ^$eIrm竇'CA7"+N&ٴ #0(v)n0;Ƙ({k )$es}W[2_Fo\Ziw)DZsy}5)9cuʹEqaif?[ImekZ!aWW7 &ˏ\G#h'~2]P\Co 5 'u!S#,a{׊`]WZk0Y,a+_q&$AErLa7ǵΖ(J*~)7G#:2]fvHbO<"v#4~8*8 Ǧs^+".h>z~l;zæn2 $fQ'n}d&:*u4TR+Zf /6g;SaZJYx~Z. "rq}o&a Elk 6DɆSbYӑH tɸ]ν&MSƺzP*I%]"*Mlf0K)M`vqF™=Oz/`ڭ,ekAJ׆d>0*,C>=ȁC9ag~<ůR8o,PT 1n6mv6ysɍ..X $ DzV_#[hw>4h2u]W'nksH0zH VlG^[BX-a\_E1|usb¡.Bxc)$яwZ\ DZH5Xvc˼$}_5˥Bֱ۩cm^mŚ .Wάߠ:orN`YN估@,F"߿DcI'2s YMd^N-[ݾNrJ"e@EfTn%ED5ld)oc$"<#o, [^ť˘睥el)M=D1u 8畬ͫ;W!BNpjG}]WAýfa @b4ECg?Z.3X7r #Ӌ fkA橎ְsis U5udƑqOV}zVZD;e pQ0|Wfߠ:"N[ڰ=lN)*tZPOj@@&Qq Y{W;l`AffQ*nH#Btn\PץV: [.tXR\hncPXc`1kjtGHM_p3^iM΅+Wݫzz$BLSt}.lYH5ˣU]UѮ*7c^|^N:&,$e9v=C9D YN7Gl"1{Ckc64E9!:W2+S{l(&w혣kZ모.^0"X@@|vI dA"@gϞ٭Zrf#"'4XDdcĵOo|398w; DhE?xU!uBEh-__c@)]WzumE"*Xb5$peAn֣Ra?ڮ?=f %@$efpjҎWO*; VMYH`XEeu0Lʹ./7:u510xҏd*c,s{;gA 0Z ֚,IQ[ ez0Lq͒XRo,ܷ7m[$. ZlQ4B R H>L Xb+ p|?W 2EKN"#a~ؗE1{Gqӳg^6w }y[j;)YV?{5?PK{kl{]3>\Nv}nR9}S8a*?_Z3{*Z3 iϾ%"qҔʹQZZ)upy|+xq(3w].-[cy>=~PjVbH"Ƭqqt'e?2?I.A)t͌{~RlӐꖰ..^ RF LAg锭u-a\FzADQ+Usγ`@DG~eE多E=rXN;1]+퀈 5Z mMⲜjٝ+_= –oNGﻏ?}1_Q5,ai_ ~L1Lp~{5G1ï @D s AuIt^f"U(9\-V'֚!92H]ۃd50fa1MJ9R8^F􉮉YU]L:"HjTBD+N"aeRuGE,۴c 6݃B!sT)_Wԓnܖ $H`sOWиXNH$jQ}8cCIL R-Gj6ov7*wMv色P:Lajc}?6{c)Zc`z4u  HBO&iy\ sZDA(Y _a3LRjL H fv"Z5Ji~{ԓl"ˢlPJI,/>/߽m.&֪`nGwrE,jkZ-۾X G`x{ܟ [VJ^~-2o;EHeQ_}~lPADchz־d9ny$1@}Prsex){ *1Ŷ+t(x/7$&`2-unonoo`Ry wWW}7*1"L|kc7(meO|XSO{<.ٚIFH] QYEw)4ܨF4uU_@s&`o7~'DrRMvHo]}k9]Qoz]EFXWK\ `p=^Ks|}V+sɛ?QD Iw6?VJ;{8"pFcQcX a 8 Z:gXF $*з}u8:q1\]@PA8:R\/h$n bL!BJcie|Ð5/&BኙEl$HNL YFg r?d {[Ek+ !`7 Hi&3(CDWg(gmoI5UcL׵x}j2G݆-{^gKƈxb>L&`(}g!R* -~}?ZA{4kW~0bGw^k|뭷DJRX?{믟Ϟ~~p0}~Yȧ$L ٧8 x"hXP,C$'ӱoF(@eSG||=p6QS/wX53Zc-ۦg ) 0en`m80G /س"^qwͶN»VՠHY_yS0iYw}'rYBR/Z6Bt}>}IE]uY+}ov,]vl2EGv=ad4>Ɋ`:"EUZc,30B?"9k +r0ޫ"Dx0I?ɓ|\V x|WJ9C("l]M1I+ 2-XRA8Ak%,lxl] \&dCB@D~m[bAcLjq0'_!sh׉Y$n]Vu)$Ӄe#5V.s W \KƔp'Ru0NNWRh|퍐Ͼ޻˪.I9ʈݶbo?@inL3ҫ-E`0ZZ}+jZ uIJpŁQkUuEd_?ſWo=%S(zd]`Xo$RĜ~j<ݴeO'&0| Y^yHЛ-dEB%o7K` ˆ-ҭ) =6 3wYqL Oz49zߋdx{i2@q⟸7C@(jP<~iFnK(^vZnF¦"eEhRD&u3tǟO?/}R(RWwWv{0?8^uzh̭5#, !`'>~R㞤|F.Y:3ȸ]G/V/}Y~+|MCʵJeÿ~g?y󣺮"_8M?'+Sjıd7y'V,IFG񑾁)+yS 4Z,p^1m` J}絓%=bd^ sBH /.d9 ~6~ _))vwFt20#͙;HHUU*E}M& d?0)-,lYRE}.t( UCw'pzͫ!.fsJ  d|CcWp=s+>{7-jG4v_ù'o"BZwί .-\YFOG0?0gbc?zv(R(ly#9 3ǘU,} hLs5ZӶݰ7VxY 7eS AFt1Q!8F {d2ZEtȀ͒)m  zGD0}xw""$ΟT=cT ޑ!tOs'<Ɠ2,(rQUYUe5Jk}z|z~|>MbZ4^  mYv{, |BR^)Mb/_>ik >:WVD?}WuVH)L7~o/QiVR*HpgQڱ;{ދ49wߞ.7 g(H d edA73I,ƮlI3#D> CQoӢMVZs&3aM;^X }.5k)T(K ;RN؆GKӃ,i&Ƙ*-3qn\?ܥE.r(هK]磓FZdwϯ%>4ԝL1֚A,lz-b31 Yd0OHќWp=C1"MN+FXt"UAD78-RCp -tnN<9}ޕe!1 ^g*l{kgXa .F@1喓ĄtuKҪ8>~mwdH>Z>GG*fH8wޝ햳ɡ)^M34*zu]/>?s_$""B+kH;o:O^֩Qc*&(֪,VJ)!TUB:VCBB]v߷۶k6J0:fh`}?%$3 lƘǏ-2QZ$ -#=+A@+6Zf{=| dt`Z~-Ҟ) ̂i'sp{R#UCf]}6֚,I=a׵ςYIV@À,6A;j]*]ZMI}8lH(ߘ{%ՒZc; 4Y7?v} !*~b/7|t/N\hdD . .}(ry;@%{3x]NAܝݮAQ$/MAvH̑X%x-08ZNԨŠub8A[T; abùE [2HZBSX!"DtziBgtI}0#{tvfVE)S n9*mY_<6_:o!}ڣm45Ұ6#bsē]ˣ:@"jMZͤ5|Vfz舀AX*UQYS*RJk77w}wwl U;FmiCA0xFrc^ֈ5 \,~g1lzxzR:ȟqtI)gY32(Џ|  +E/+1Og`{ ύ "0H E< )SlA+4(ƈL Hu}W  Vկʲ ]T$wEϴ+cOʧgF_r-\gO(.Q! n0~(8(\|?;l ]nw$眈׭wn" $LYelP"xy%k`459ג$h~[̈8yĉV5L/+U]73296?~2Dy{?%ɟ3\[.fg{B KY[>~-ACb(bB Q)2K_oݸrjT9v+w޽{B_ya["Tdpe4.|r2X8:PZIm =Uu捺BT.$"d`A)Iżf3$,r2ֱgƂ,m<8#>G#RbBbagCQ.ތx0 o%/ SD1>{a BO,Vz~9 <˚H# (Q>x烷l_Y^͋=q9@>sZׯ޽w/~뺪!D<:߹?om͟P7ci[oy0$+&E3@ Y42aUVe y|s_Ͼ׮]h g~pڕŢ1α7]o4aw"r42jbQk`H\vYﮙ5$V+,X<9^e)0FD Yap&HDo1VɈbsc s:gu{3ɇXX>R*p]oݮqR{0 -Pb4I&Tcf'KWlL@> Aꝟ~pԫRQZ=%SnNi]u]U۾wژg[Y($?_3\;]o?~@Jr3lHyQq\OdR px) sAN5T7f1O=u{_x~60>y]dv[i^+HZZVSbb/nDQ94 wZygqrȤ6HKJjljp̶GR:6JN] > 74d}d`dTX"^\"2Έ&++~>kfQ0KpԆ B!j\4?\5 RJ=WD: '[z^Iiϒc#ݝmmT:{~pv=32z}ZCJ톹 Vƛow^pE=_>sJ pn{46x0mB&Y3*_$b%mie /<[?ivQ/*Sk;-',9[ϫ3mԾ+mx܉TCWjuJ~׿u TξvRbi\+ى35!JS2CۄRVuoݼן~: @_4P2W拦ջ{ LT 4T{'uz γj؜˙s-€ac4Kӹk AX[fU]e,bS۶bxJy#YF8Nu(Qb="K E(% ̩dH0"Wb@89Yjʕv+W'MNx4,jfׯ,Eu$]1sꯓ"*o}gTf)eެ<ڜm:]MӘnR]/Dz[nݸ~ Nۤ(iKVipؖ!e=G㼡͍GB,GWqIup{g)C~6 2 8uh.dzY罻zJ̼Q+ GR+(ҵ}[5#CU@ OnlVFu(T>`hYs;=ӛdX,ו5>1"uw+h*I)^P0ߟio][u,8rL })``+h0Hg $(7lVfѧaXF`P\31XRPYkuP$"fbϫ{'KQJ-ҪVtzn**fs"xwf0 m|, tG3E=ԪHIu&jd5a htyc =/#ӀE*B7 D:^j5{vf[,8"*Y ^x)wNC0!!"UWû? P(7#_Ok,gJtԦRSC`eGz\pgڝy4Ac o/>A)Wm[f:[zPJ|1MA=%B()7DiF#|TN,]JTpV@ZRza?9.3{v[kïھ<{g0MR45b6uy?ܵW/x8,8cfǕaQ,ԟE_5RFKT芐4f.FB=gw,uS%9M8}F,dzsY͛y @pF(aZ$իLKR!J4O /̬ݻW!כE=oJ\L=d2h{kb[&b4U3ou ^Tu^sVBs6R+t Qo|h|le0(ew{ƔD̅y>BhK]޵m *P" g=>^׻{=$n2H['7>D_@F{fgr9N}yj:G {7 mB,'ԛ\Ñ HW8֔VMms-BaB%h U9 E>O2F̩Pu]{B]iəl*LC4 QlHi5GgoE󦚩R= IDAT蕜< oP*ig޵gEpUg{ffQedfpo{ZkB̚A$EyBy3qt6ؠu\Z 58vww[o@ZkGǧG]Xkd/ U0L Ib溪;l?u~.gk$DTt 9GTu^tuI+t4 C 3)$KJY/EIV R L$']40ؼQ d"b|0ˈ n*|BBѓ{y%T]Ջ`Zו$Ѱٞ kfnj^:/gq!s@%늪DmYF;W;W^)?>9Əw.~(\{b>_B:5D}PcZRA^XqɃ탣 K1@>9zOyFT&y+Ar)FaQ@TUT{iyٶ͙MCZ 2 Dh)BR9UR?|$l;.djN7lz @aYQ:AkXpa&2tŠTL3(DZ̛ySJQ+=( *'%ymكpF7D)6ԕ pl gqfP>7CC)E~L[ϚlA Y]- aұGٳŵͶ;~pP` , owONwmw¬ 4#co9Yk2֍zٺ`ܦ@Jk Z59C4̎~D4K (=+#ZYD!:SAl1 +`$]$ЬDi&ѸlA2D@:&NN + AFP僙rH5rwv\m['^0p4 kWZq]S]ƨA%\ՕwZWzT 2Fiʾ ϟ1d5B<E EByخf;VG$X ey۞IPBEX4ׯ|OƐRw?<Qk][HQN~?~B%?pb+&lh."D B4 DX~lFJa.CnxԵ4kf@-@'{z}9a8 hQPbW73>u_Y~3FKԈ"GUUuzzZՕ A7?mܤęӂ Ȯ>5 Zgv@pӁ#0r8LX:'kavZp`/}K_ky'\c|xrN >cY? ?AS}8 ~B t,%8uc \qн';::9 "읷J+AQ)=-;џ`?Ѷ}<'"!sap Up)l$cj>ID>C(B > Οn,U`ζGG?wrxxrY3+i'0Ŭ31n*"H1  cNz3Q0 oGVs/>li@z$K$Vbߏ)X&:ɲ{`)mkXYgzv0R:`h$$*"-voOkw+$4U3ȦO*r8LjI,}ݶS@ija^| }k/XZ7`9{ފ`DTU;m ӵV1RUՓH8 ~ׯRO=T|SZ0$MF9R @K^[w߶inҮ'gGǧ''vsk.J5t99#{g5uЌȘtuY$Ze%b7ȧj(&$w$3d)r(/[GjM1?G G'CvOͲʉQ*Q) T 5!f<cKD2p֧kCDA <#}t2B.X"YHš {]﬚ #xկ=w3Q{vsLPQ2Ƙ$+`'-3L87۷n>$?` Zh,vd0-Z$aIRuS[*}?O=?8<ݵbZh#`,]!~D,CC@]*7%rDjFdkW{P3nvɺd3]$kdᠩ)qqӤeXFu}/^^ij OWp `qj??{2k2Tt{L)z A+7':%|<_y[w\>rtx|tx|0 dX̄y2&@X)d@k뭈 "2<|dh#&'@T]8BOJ/K+,F) s31ú{{ 0R6]כ>:>1ƜmZvnm6XtXt,\*n,ۮ_-ֹ2TU*9Ha].)ul,RιP Y(@'+,#Bi%F ¬6Sܪf`TFې`3MzIeA$u"5,\SjK%hp~\@\U&euAƍYvvקZw޻ζu|>?~prնMV+eF~b>Z#s^kżmϚ{*Ȳ)DSti/5YPf( $v>0{ MH)J G ͛뵩t$ Eb0k7޺u#롗З)(:&+<,dr 'CqN}/@r<3Eb'y=%F8|hdJ!C{f6}76z0H=}BQ"Xܬyl>3F;}sUujHpxϤfmf{|xpll-W EbϾ2w>֦fTrѴ]?ϭuhv"DLG:m^A"Sewx볺Ur-XdܫF.yɽ\S`{n8p{ôVYk8$`,RZn ޓVfDR}{ 7'KQo\,߼y%sy^YWc~ /+`Aya7x'i{f 0Pذ.{ěKkv$!*D ݯ܄b71SPaɳ{{P:ӑw>yGDIuS\N)kguL =Cv̓۶͚'׮]nfV;kMUue ILHiRIDR6 bϚ4J7L/Ziz6J |LIp8PXǝXSIDFPyu+j>P%8Q"ݮ32Av(V{,po>~<VrQ ! }x"|1˞4_4ٰlwcX`֓ɴ~;W"yxcVfvmێ,:sIǜ م#Wʪ@"LU|сCrXjZEʘyolz,"nQzu]S]7u}^_Y-gMlzurY> aEʳa($N,>uUm7[i:fHzFaÒgԆ=2"#fmv[^gDT7gcoH.h{ZLfjv-uy*4(F k{O^|W?8sCa#~ڮm9({o\.r0/.V)ZFv M]cL__a?qQY/@b?xd49;f >紳.VBi%&b1+|2#(%ct TD0LʛNPta-DvESNIo]X'1}4Q"|BR܂r#<+2ge!W@(7=dcuZ)ֺھ:ZevUJ]WUU@X2lX:gguZkuB¢bB0=X-Y|UZk^,EOBcL_#uvÚo-Tu0"zOo{. LEZ#h۶mfV̊06sYf6i?vӒ l6C"4b ppFSE%HcpRZgOI:LF˳vŞI!^-a,ID? |( SD* 1$R̢դ([mVkvUUm۝&Z{V@V]כnXt]_7Ukz}fsp5uZ馩<|6.vWW۸]{xx|ʕWuUJ/O=w㳟yW_x j hw?~kkmqn+:B泪T)eloO|oN(%}e4NSF (Gݴ;A-ΤiE!"YֆfV3rŌ=}WRuM(R@ LӴɺS92;뺮^]uB)jCYprҿq$HaϾ_'z۵l#ZWue3TU]syγnS>񊗿r^B1uaCrCy?q񣦁%<]8)>uTy?8~xVS'IT)?Z@A-)'eA7h׻iێ}pb5uYo` Q3O|뷞Z~gY.̎oݻ'o{{馷lk{X,o=PyͬgH'T޹wm^NOO~77'xŝ˗QȄYk& "\ d`\s C+#ǏsS=:۱罧P #a rrNć|X?{M\sqCa  zZ Vl0ΦiRr \>R% ˒+y=aQ7P0MIDAT")==ݜ7}gPusRj{ڶg>6l^#P׶9z~O㊃w^4 WGs`w./,GGe^М3۱|NNpPȎ/RNHôJ:i!g`]ZYx9P!CGjQ@lsd~@$$QnZ+DG/$vT4.זbo;#bi}&5)FϚrQUh[kݶmNX<;\hmbֶ݃{MA\Ir`]ic_O ?:X^222]^ަJiS/&<^&kˑ:BXV~7ZqpƶŃ^a_E0R=-4 HV{ ;{ Τ=*FbA, E6bFlpyH@F @dYV3x&dV2R:wgI}% v~7 xqw~%" wnCxzy@X9pA œ,u/xe9w$=yċNM<7$/ِ r1R0+kY~f G!ŹICFVenIs)̈́%%QJ*foHyaEmwuڭ"E$}V]ceA$r_Q)f=5fٳAY"'\U7o7ɈC蒄klytpX9 @<_E s;82>&YKO!Y5'yґIO^rɪHfK< bA!]L"3ȌRbk GRU!|^Htpc/B\ZТEEyHY)x֪[B؊UU]V%sVw}p寿kw@86t o{/ލP.\x+4̶&2M\&xV 14%@mr)~"9؝Lb>vQdg㬳%&^洱bӆ2|pu=q/==6(;rB䜝/c0+_?#^}^':>Ok=Q8H饗^G<$k >WG?xq)ߏ 'K^G-ö> Ea^M.3P|Q.#2<:lsNB[_oB,-Cxٷ$7b)mjOZ]d CzТ >QH/Ak|"*1ژFR*>(.Қ)oR}'gX tsy,̞Rjo|I>?wQ%(`.O.EQa]698\3gyҰK}hTeCPƈ*u'$Q9y~\TĔTy\@k.&U \ljlrT6sَD!#iO| 0W~ۿ O^|Bp^}W_}5]۷\{Ν;{]w'eJ/FrIWp#936*֊Οp[v})xqZ8N:0(/3ȣn/=Z>% <33qtl?4wi˪yc+ Od|rp𯉡{ܾ}{DLg>vqʘ񃏺<.X(Rr'x>#zě ~},%ifeu4>9{a6ZY6b\{h2)->QGq^MG̸Dcoo??A꫷_)8L*ox:MaUי]}I+/\@݀2(IQ{4('/?SiRj$<4ؿyFE rr~q8_~k_{/ H}=UB}vy!ehV+q Lrn]r  V :*;  )9/JB?*/xvT?I Jru]q67*Wo}  c6?8, oko71cP ?ը?,?P,ta_}+XcIP+mm~TE-Y3Y31>ϼެf W:W8D (xiPm կ⯽uo`P4᳟h:ZN,,ff|픅Si%Ʈmv/2;~>?z\g2xw;#!dl>|@=TlfQmW"R" ~U?Hgog -nϪ^E%* A6 z& z&g&ܽfQmTVyز56 &+~w~n9~÷/Y "6nUUE}C?7M?=އ/ŭAO]_%xϏGBO'\~ңy_@Ӊ(@ "PQi>Ͻv/=CֻDo~aliLi;g]V{+jhaFF *ZlFk_ PU.w<3RQ" ALذxI"*Cl?oWo751c*W}Y32:ėzlX퍳lXÆ]TLf{ܘ,m C2l3{%*AU䧞]jAg0k\_  ̡[~-pGE3Ɲ\6*GD[\K/ZE䅚^A_3 /oUD*,OdLppkq[-d{nŻ/ٹy R(N-14[7Q$FȂ|}p߳zv~X,@-Mg²/U]΍W;NXBөz %,~z^%df#eBUQ MXII0Tj7ny@7/0.ީޮgwI[.4?w~CdbSUwyxL'{rم0}iQUP~h$alc7dyw?tO_Jw+.ySىk*_ =Z~3j6j)կ.ap/ܩq[W07DPxJVF]ʭr܍2=7kdȍzyakm,{!}n%WEʼO#$̳ez1TAiڟ2>iV@oQG}Ƨ>dׄ+Ft;17pŠ Cݘ+L//=_n7[()0ِWt0 ac2$,}+`u\Lz:&J@P* XY *Y"`o*"@HF>k10)c*ʷ|B_?}7?~d~i9\RcgS3,)=Ǚr՜4m[㗎h__&y%_;:|@d;7ʺ"Q WM+DԿTU!H WSK@'03O]Unj*=}5KRUX [uO'3xhέ+_5n~ReAd?'>plEOU^-S51F"Dt|x=^֋^ʪH^qEfKt*o^ցӢJpY]~S qy3jrԛ#QC-OVGsFTOrdbO='y kh7GM]5̴`7 -HɆYYhWO-h0uTUDNoJ\Re)QK ĤDd 㺷]D|ɬN(󢩚f&Lנ~z!aR;$m@z~Qtӊ?L%6TCFKra 5=W̟/œObg i$R0X݋hpaw<ګ{1 %Q(Q僣XٻT+SaQt)"U $*PP[`"bTl贠!*3MNP2tÕRȬKs]L2D|3]@<}S/ ^I Q;=~c_3/>U"$rLs._<9~G`(B0H 1&*@ERb Rfeݦg9I ,[( 5(w/ ">uì=KADݠ'!D`b+08(%'$W_W'a3R{=gYc-1% [hh,o{KgǓ~4(u1被0`9 GLJM !Mi^m:FY{ܒTE$QgCormnWh TId$&poWכ[/n馐z}d?nNu޹ymPU[.~ǻڭc(8OTmlWWYݿwS4 z&=}Tg١z-Ha`xrvfI(nͦLf.mcxXFTYؑ۸thղBԇs?|xp6`VET4jTĨbb6ҫ)3(IEiTUIAR #$w$( `[)R8?|lCAvթިH1AQw#hs5 z&ǔbToݶUrTFha^ L\mwƥg쳃;o i7[zs>>LO°+ʁ{q>J、FQQ% $tg S#K Q5AP%BIa-$aEeb\s%UbԐ^dSV?_F 0Zrĩ+b@E.:ɱ/oHh+Tًd5HB siӅmL+#c|r\v6 ugZ2q 0/ڭwxW[QC1fg7Šn1_@EQCerv-d}1FH_+K{,FR2)"~Ĉ [؛ɤ"RbJOB} O}efͧ+Xo.Ha& ?'Md}/Ay;y~267[6\ac@A#AR|~(w6@Ῑ5= z&{p-U9(W]뺣|5޿ve9.GCSrXYw.4rZ,ɘ.u.D:ht-{յ9MVmK@WQqUI#GYʛT *sڵIERR:( Rt"VX "Q*{"}ծzS>OI0 =[ԨuE; ]@AB]UL Ii&ZMɔ94?w&?v0??_tU?cnuO̮uz81TeUF=F< m[U3NC`So7쌶 $Df*ꪬk_l9[D8@EjTVUjZ,%:)] ̜tO:O bPIl/ߜ JPZÆO]2hZ'@_Da!2c(mdkMSZb"iQoL2vvc&ح@\5oE΂)_vJ.<|-jF/=ke;a2Wvvp0WM`0A9lWf"C&!HLivUԁDDAT  4A %MI%5ɍY.c%IMĔZ-T8}D Dl2S?_%-Yh/4ogI3J"PJ+VoySdIW> YU|w; zȻ??K^^n=<32N WI 5ԕz4molr>kTmwbfV>3UfH(a+ BBMZ Q p6{~FŨtKlg6&6QI)&wyn,-$7riY'%e b{+>a&V8tv|؏q&[TQ862ݤ<}Zؑ"^_ Z 3<|񹗝|r,6bPk :UbV۶h+Unau1sWe"_f''tŰ6!niȒ a`<vhbEMr!%b덹;|wMfk&"FSx Xo,B2H!P! b].%6P: d'J疚2ZHT#b0tArޓ^56aymlFPS05/J%:<^ygv}!a ~/#GoGٞ=]y{ogVO?U,6ˍ70ֲTyfCBe8tLorvwfG'b9WpsCbrH2 Ūk[e 3~)+lQ´;AH Pg?ѥ#Sog"QQ~_!PѠ)1Oa,`%WD( q?:گ""YoPb"ӒFI1eau\"1P4'm3k7_5IU0 ԤӨLZak+ ;y}_}[l?}?>ntz0y|?{pЌE1li& Pv֕NDjw(!2p9[0.-j춨(%1N2Gɰ p5(w҅ЯB$+acF' qe+ &ގDS'"fuĬ)DOꜧ fl Y&1MzȠilumfIZ׳4> unylܲyad}U&" $̮̎;nV՞1LJUg?ox? z{g>|7ög>`G~xvXեs}6 ``.Xb MCLD] zuV0۔mkv9FxXJ k& wӭir?to^WK.LʹoC4~Ǿ!νg?M'@CInf&IqH :z$"t`~QnCN$_7m8v&ZҁmcLIl,/"g[SYél'1o|op[~!dA|}O|veSVzyѳHQÝ!D^p e6lɤh c>zIȔA c 1Y]I4y&KEϙȜޢ _E:9y)6:J t 9XYD=qƦEo|QR_eM=sɣ w\i}׮ܮ\1XǤ5mI'3;וּUcÍ-XZڠaفWF@h͍l6_r;;d&W [WS)㎛e@YiPEDui: BH J}bX_;t䇾:[;!vlXLd,n!*J̧):bZ}.( ~[Hhr 6Qaf_f u)] ̬*0 b#J 44jh%_[o/r|nO/Ļ.8;d;UA*]TA@5dX&9g]UV+. yrbJ%sA~eUm~a٢1EUU~h`WXưCӡ10aqjSpDD@a3 ,?IwZD(:21`e>Ĝy#$77jʰ:a*L>@ r)"")Q{<5Su F)=UTE2#m#7=83g^u-sJM3YeHM/\]ٚ#q!P\ Ιx_{yGk$W虯)GvF;ÝÇ=~]$QQ *_lBU!k- c3eQUO\_`1ZЮp{"acׅZU( cLaC=ĦBq-A q n[L[4 #B H9|9w61 麊w?Oݸ[~hnC(ΈjOQ !FAҡkr2QzP 116@I >XDTsXDlYEBJU;Sس/e0UvFJl8j$,-%{6#Clv,w.h{OOKװ?{X7hnq'<“y1$몢&T:Y-V H׶$/yUi״1ulKk%%̔YWJLc;  ffJt60!ΦjPv۶oշ ogY3w|❏=<:;9xtk!jTWyciOEULJGG}hxaqa+- `[u-B-ǥHӱ1 G҉Lbf,ڶmYM<7J_$QEIoSY:]`czFIp7 /OۣFY6p ]lO4"[Z.ty: V{?>ΏQ_JU=仞X\F;\xCI3Z爈-yBf Lfv8; > lj>JX1DL/OQlcАB\ L 27wHDj),ڦXMONbj]lAu @`+CABaļ c ;N4TPV], L!YAңCĭYTT_InuzrY$5hP x(J4ƔH#. ĨJ}$ )7bQ@2)[#1Wi+ӓ˓ \up?+ > %=FU[++ z/ZojP=Gu,ꂭabU-ԋ5]M`oj\Ued|V!,k+S.6\>G3F];"R")Iݼq(jnm3p74bL)NQa lȡ+,Z \jdMoTvcYNUh;'~A]ߌa9i5`XI8"!<$H'4*N $F0%ekc@ Ô"HDa~8cc,YbHGYeDH8v]Daރ??;IJg"xǯ+?6苂<76UN!sL̬}28 SxSW\o|U]0hVF D13dm5f؏SfZ^gAչwEQɥfc1V'"8G5VB -쯰(Q/_}n9,N&p㱞^j7)5@,!G/6`ȓ  K$մaHI89) }C_R/A ìPcF!0rbڡ h$ Wmea@`@me>Y,(%Y *pf~ݿOZ̟[/c|pw.\|z9[g͗nZcLoYVY˵knVU:e\׵q1q;1E&ϩ5:X7ړg&{bVQcLH I ID-{D/b2-hg`jpLp+h@t*rI@`pEn:=3<~;;Yz 1Yg1UQQ"pV6 0Ҷ V\ bAAkntdV5Fcm !  IURgX ccHZ7ETA*~IHsbMZEtf`5N+RMs1FK5lI5(,,xpй DD˂Ob58s3 -:;N1obWVs6洯@ s;lk=  %pAQ@:hkFO0ch%Nj OprMlmb7}t $AňtobHڶC8ɗ'a"B+*aB(D%-OFhX]\̖fI׶su!.7Ez"BaãXb¼a:=vgxs;70kiXlŒ91.Q9g@GA@@l ($6X)-q `ʠآ[aE0 ,Xytẃ\ӴW~k_f9BJ J'''O6>? 1q]aAG*Nde cFY`Hmv"bֱLLʭr,F歛~߾5/,??|{~O̟, 7jrC_EQk֫r(ےx0U0$triVۍlΎCk-b{"} L}Qtܞ'ɢ]И޲uSI*ܦn=4.o{۳<[k 5ܩGvIZJ4Ē)yBH1  Ak@6,,ɒPH+H)$c7{:o{;.pPV:ukql32_,yY7`餞''T`HE4a !IRTE3fêO31amD 3`EY5[͇_;ο: ?>s& lu <ӡ(G%]JF$o=Z *@ "ʥP R@%m~e:{pM" dL@Tsg(^-[OgzC5/q7-4{9ƙq;|6Ma,{̤?yuݞ/_^_]|77gyn^6"zb=LEYzXuh|ey,8г_PUA6gv'& C69XXglyFQQ$AU%@D~v:\ڀlc 52C[Tf~~Z-ftc+4( X-H;oʗn"Α+(5{ۥ[၁!m bEL]J!- Ft @-.!0̀n@P+<1У5  pp+}{ IH1αč^]Rճ(lX} elŕޖ_1[֭/fF*l )I"YhS!ڋH97?׀~||{7^{cfs,뛋.m[wr+?5yWpA6w^NSdtl&ܤ˞ ,*e6D0\iˢ,2av) bk 70?$q9)ğ$cTX5sZUa75*a _!hm]ZrV݇aͭڗXsV,t18g&^f @C ,أ``, Q5{1"(I!& $78b%HX 3"8x2!L 1X%f Rk})or^W K] ~zcMa|W-jz(/*IUb7Y{SQmB]֚TX`2Ӥ_z(t.?Ά~􇯫G}})fUGY*N|s^asah Y켫]=zRJC/|^6ESLK~29T5D#A30&Y?b7iHTЮ[٩w~F~DOSa ;R¡NY%W7%5K뢪2M͖,bvT 4YLңXl'O[_=qfe0iTðqƖ b3 B[  dQ h d`2(#b@Ov3LV 2PB$ 0&[؄%#zx@>Bb7p=ڈ!A*Om[W9X0u QM R4Jo|yU>!<)R4GmԌ2ILc+hP? (~^c5_?Ͽ~뵯nV5[[Gݘ-n. Wp~;$MJ$T㌯<Iv]Vk>֜| -VU&cn?:*J*p).\u٭ЅPƈ(I|šs;< ѤLceAy%{G"*zh m//@:{WEQn箚b h<|Ηvǖ{g &-q{ox{Caq0C=va,\DPD0,FŖa$bE0G̮^QfEfB 56բCۥƶۮ]mꦎ(hdv)*FUɿ E *gݯ)z뛵DEnWE㽩OjllCYqӌ|SMYW6_NwWF .xT%4 23El7Ζ'gu37r]U67fdn|뽷?z_xfvsV8?GUg]Y"gUX&D!7~&RA et/:x@#CH;  B G @EA;"vp#F k}2!^Ro/>=mOVwO8Ro\":Ű[wTm{oHӒ,؍n'yGQH3Sx@wq:wmڥ_ÿ} O|/ԋjydjX=_/ $VQe؅؅ :%wYX ;ݙs:D?M9 f"'*$ǕP.$bs-)ݶ)g;;3L2=W]i*)!M_Mf"]ѳ+q6!z0HѪH ]^n<gvl;f.B?\pQ/j#2&O'"eaPjS_5_~o(gd~i>:N+k, 5i)cJ$$ƝrYU|eAUF) 0NO"e ᢯O孥XcKX=6)F0BUU!6() ٷi:fi$ "('2 &*˲Mꜻʇm;Ówqima݋{n}߭ s_z DtYlA(~R~l+{|zBoP3LD00fD 6!0v>`'Sh^-V9=Z ¡СiZt=:tժhJ[:&N}H+`҄.J֏BX)Xy;^uT@MUΩ3ƌØRZߚs9hd:)/I>>c?%P^W߸|9i۟[ٍ/=j. aRL"SZ733e3]%3#430Nz6"jYNculD]e@sЎ[?CQSI`\Lt'ѡ-O됤1Fţ@ڸ+|g!~߮=1^|`^ nxw|dW$Gp1q3DŖa֧9_QF\9X?u 5yO槿?x{H,b30HL<,2ks4$8 )80<53EatʤMwmB/msS?W[L>q}]McG%O?ed*|.$Ŕe(DB=LQ s>!bhF@;p=V3`A(GtV>{[0$Ō 㔦*lLQ f{ccr0;O؎"R~Gz\ "_U#߸k@kg9{o݋>U/V7n(;?]`i2ݨD|2iΝ l)lyF{j :Qz80rA<jG %)D# ODI4j^|Q.:lNt $Qd۵7C!hJc;NcTL`YLX\a5뇫nqpVH NE!HEԞS08A40j c#ЈqDǾŖ GK`EV2`Ftw0BtyU~[=|L|^KSAq~dm*^rˁXn;l$bMF$l4Mh 1rU: G(*F'3{4LT4 -$It[5Wُ,P0; !! HA,Dt(&[:b`htu#`P b>U 0DD#b4v&quVR` }1DtaDX`@ CD ))[2Я]̙y7tSdRetTՄ3|W?ﻮ?W^}鬞WO޸{G?kfU]HGI7@2S,P %3E&"8pf${b|z̙P!onahh]Y6O w꓅*)yEQ%Dm`z^)."* 'Nf@k 9@-a/#:IHimBtp: 4J]oc趻.XKjcYBX$7d=þ,u*crƊŊ= '>1R")BdBimnƘߞp5RDS~gW>+p ӿ<+mvi?Ko k}5Xp)?B!G_Z*Msج蝫 E@zC( Qe'*=,>2Ma KM"lɸ,DՔeS,njCv}7 w.aI5\.c.:LI&>0TItؙfiMf޳Sv۞>;ݚga1gI-%H 9\?-_&q$O,5vo3onPHfIQ"=FӨhhlCn[Q2˜Rhu @Sz"ڽ+c9ol0"FN1-94Dٲj&JIPJaW}4ѥ<1X`0nݰ!C aeI!m vinR%1 J !m$u;p.qLўڏ8~YѮcS"GEYXo]v>jV2,r!"@%&bMr5Tu 2 ՍjQE$98" <-??:S__/~Nbw^9SqN1Pta3m\តif {R&0 Iqx #>jd6xQ!QnʦL)X6v4&x*lݟMLeg٬OߴŇg͋M9+sh)vPMD Ir@2(^_jQn׷nmDB+X*M)E ƺ$q[_dx܏l9AdcR$N}Ԩly;QIqڢ)E.v։JWqS%!0f[Du U@Jj:Q4Fpb[1EcmSeQ&v5)%ٷZǾYvmv:o\Ȑq0yUmwpgf=(Q)MoQL^ V1FU+* la乓3\3oy" R -bg sDRNiQQh0w8H C<{ͲΜ+^җH-dXR>UCdj``"ҁYMv}j&P)A1LF!X=ybeÎ b`UlBOPQc( wS) H5,0R k)&( IT )EPF6!Iy^AG&1 Tm;q0ӗOPC qͭ$`r ta7.|p8+Lmiʟ%`3sD#m\ק7|T t%VEYkO $!H1*%y@dkT K;MJ%y(iaFU]\Ef "7͗\BJ.'}jA{0 X !%v{ƳbpBF%BlXD4ѦU|_ԩtX3ڵ +_}w9yǦ~Nn\Kv{w'Aleb883("ƊC &eShmJl9#D sxgacd%`~\`?+ MKW аř?}[פ"cuAwVE,MeIɰH)LJ!6vBau}G !YWe] ftA/O ՜/ql@0qMz?ŋMARL !f614ZgU8l2lUIِ "PI_"De73k`g,IIXU4 T[M' *!Aհaf u8F]XƖ氮_aɞtJu)uTJ*)c+L_T#l`ƮV%=B+MVbJ(ɜ,*= i@|alD眒̿/Z/=r*_߻wyՔ"`n>2Y}`jQÄIɌHd <1S<ŶJuCh g2tܠST~F9&<ѓa$>qv2hMD&tsحvbRQ8#DUq0d5pFO9Ipg$CIct):sXD`2MD(1 " N C ϧIcISɡ"1bxi`f&;Lvw" *pO)DEᬈ@T8_(aBJ!TY &&% l\v] +"D̦h|u^/8`D$Cltoݽrv%&!dC ɥ:o^YpCrU rnFΩOUh3k(,OzRUJAv55#zpv"|>r˙]Lq[:m$BD\)!JDygb3Xb0p})Ϫ.1Y2~4zrs 3&}v%UcqӯR ,;$vw7Wj:dDA$bbkȐʄÔ?|]KX:pz&¾dO_qU"ؘ`"5Ĩ!C ~KcW:6L$I"hNAt\p'*"!@s\uB |ЇZY!=V z> ƅ`c0mQB 1?oٳ a_p]Cz z}s%=7ծƘ}TkهP}\~ϲ.7u!C?FV"wHù@=K'`$9*{E=z?]!HIӰnM&٭Yqq.uOwM ",y7bb!~BDs;n:/TA4Dz8_yG}FRg  0V+OT Dym3wWR7s0.ċnvU=_8; 'բbC( iLvbַw_̪fQ{uYeXT$&wַjVH';N!Qfy򫗷J$ϐ] ";wcG*ih5MS 20ARʗ*%A37鐖 M91#t<2񑖀"E$:6֚> 81mZ퉧 QbD)%*)P[1AU bfCE]|ƳyŎJa_iNeZKI% W;,?-Rޖ:44x.+1΀&BAW Yٝ'Pf6泺%I۵m| Y^=ZQ (~co⴬j_8_z|ej/ɻneq΍ɷ4/5 U9+Y8LV&?*e;Iqh))g>l2eHyww~g=Ȳ)]Ŭ*cr! NݤYSi!dl2OA}DiB9XcIcԨݾ<=>m﷩uTy.Iy C03ưqTjQr*[eΐa\O*8|=;[U\~|ƌ}O)euj~O&[0Z-jqY}Bnvh+_~ M׀]^ܼiК_1־Zft1Xvb֯WfEQ8?-R[؛ 8lsPL&WS`zC"`('ɦ fwB`Dw _%=pEH(()f3+MykT!Q=M#1ƂJ nHcclw],m5/̨x=Wmo<,]{wS>>tO#$Μ|c*sꏾ\[zwQ$2,H5.v"pȼts!ˆi}|W^zKoWfc\]>6 C/1$"%pBx}is ~k՝kWV_ywB,Y"U@=eeԦ<7Gϟ/YMlSaPW蘴+O‡( F&Pq`f3/+άuWd#|jkM}B``iLO2-P(:\ fEof8c[jF7-Z:ӝof7'%Q ) Ayțņ /`EΣ!l+F`EIQ3h#ف-FKP,nݼ|T=k:My# ΩSg_[PZQ2IYg38U^U.jBȈ ʛ<&iZ$Y4f|':X4 ")Dݙ8\s7fl)'{iiE]u")"`ibB">2B: B}Ĉ(1=Jo}FM O^g/dvl\P#j,7 f7ǏUMmOZ)},M0 r^G젱 BR$θ͹a=)&Vgkcwô(+S/NOŸ鿞|pZ>I Zc<9ʮiYHD=0˺tyt} -ž__>.@}|*?O'>>Wt'S1NV'=5sW?v=ƍOnK3n$йR$ ^ (؊CO$`n #7ޒoaËK\ƪCÓ~ľDhe5)B&hYNlqe{K 42z},<͓H3FwF^ c^+W7H : U4&@Ps0:E%YD EFi:jjH1m۶m䏋rD1$h6C `ccbŅx&HuYW)j]l~'?xqorgIZ$[&@cF)>L#FAĉlܱ|긆H>In6M3mܡ\i(U$KLOPN瘍ZޢND\ꂍ?ߍ{Y__>#W?x7(K8CJYc2y+z'׮9Jw+cq(;{`5(ў1ϊ%#b;rg4o+]ܸ2/I<9]q!Ȉhh%Gٕ4=Jڨ]`dߜ$"c~ˣ{$Uύ'Bԇo'y8Qq ֏׳fM[^VzpY077uUM?VJAROe3v!o`F(J&%-s+զ\VXw dZe4 HWu&ibiVdjBg4FaO(s@|"ٜVVYӽR qpV]͇/մvdYJX[כ"Wa}ڜmiۖ=+Cc9b> 8rHݣc; HРnTMTV_||k~zfNHJjİ3H]VԒS8Fl Zw\7ykNr)Qfڪ)몞XM]f]'ӅaD3lvd=]!cR@5ƌ~f[xXi Z>'훷-_Og//Ͳ=nxkYjIEQJ4Vxt}ѕ\9sIhN#v=.%,ी!o{DwlevG.5}=9:;M."wC0(YIphyƙTkdXN"ei5*KOx=|jRFH pbp8b.iB(L69!8-ZM ɢ^5{ kX|G%2*E!Gxk4j4VmlR2+|Q~{+Y2ۮЂot,,h@YE̾oeҤNόQJfVI.wnϢFfFofe;m"E!Zt I 3+ιӳr"ۻ-$b".M,_[fn-G|w|oݚ}W~_'kۧty{jqF)՗Bb񈤕JD6]t /^FIhkvصm*+;6)) Svn>м([6~}\9E@{~P`sDvh' 1(1z: E9? cZ3&\bDY=BƧ `]sr L؂2"#ٷm6fNKuF.?*QH@N҉xiN@P!iTF@AEh6Eyc"RB0-0*T&ݶ]6PĚi5@PYmJ{_}*g=CɷgߚPknzzR6.ة{` 'E;~Hή?7nA+_~33={JZ_ֱh0{a/{w.I4yYNe[&/$/ONZ*O=бe= BU+# *۴hN/FԈ +øE]^_.Uߣc$a8AKԕY;۷6- m58# a5wr7N%A^GBNI`6ኢ@dG`D<//u*1fMU/rUuuuI6VE[CS ^:$BF@cc{–@yrS6m,A{ }ID)rEO0 |EP(āXRDpLAaO8B>fjMyQֳ6XY0p:NbV[p2-6[0!*Hȝo$ z;{arzxtkTY̦jS6mX>c/RP@MDMo[пU>ړ׺썳Ggvs{1f@$diFQGeYnekuP  1tA13:XzD=ʖk$WbZdgɼxT .a0(h4Wޝbph80v!( ^&T-Y| 7 0QEIPX|bm@Y|TN8iæE)V TUUk* 3c]\*9H7&2z1,@@H]!"6k|[O{yO!K>}+SR%nz|]%H|%!e"/?7??Xӿ0WW~?|'M׋(O4胆jN$U%W7ѝXHqL^O C,|]@c. &-bۥ (x@LucEdFAc <귵y6"aM۫U#馜n[8Wlb`3;hRj4I-fl?OGId@0l%mr]7!EmT4yWtju$O7/ Cw3a ߃Ø- Q)esM]7-ƤZ"P8s 5_|V{uoZ* >"{Zv@B[:|:;@rVu3NSPDڦ߻:5!-!xW0)X-ftl|sp-Uc龜:y ڽ8 M5&*(nTMa}#҇YB+o8 RȇrBV,|̺TCťQ㟃uU7Ѧ]4;X׸/QP"V.|`R\-U'BFG ΐ@mjbp/e ĎwݴE.؊ '$Em՛in>?y#iPD"̞=כVJ:Nk;^]Q ҂E`0zHDUUGpK/:I[b%^OQP0@}bw~wHEd&\^H= IDAT+~ kmuzɪZ8ޭ"vf68-6|J>xwsb%C;F$tum0d\7ÏE9a/$3Zw5 @"{]T$T7];qpFDfA<[rW40 {u/^,O߾]l5)VXQ)`xVO׳ْQڳ{/ƷM[""mF;_r][ ;O9Ç !$Zi{ZU]Y&H kG(qC]^˚C1tkUmrJ(DtX4Av0@KWƶj2 (y}93͢)O%:9Uh7)"I2x8zH 3!aqOo:n\~/]; dG_{d 4sNIQSW p||;?-MۡW~~W>+tG_|4;QFAM2k i%#,ӧr #+G;\gH2(`i wMw8!k`V=M&2~uW=%[ NⰇ/u[,h,;˔=;=AOק_>51cc d#'iR>~qMRo*՟{|VWpD.YM~blfܓoF֯NVպN$20{fMEk%QN="0P 7Tm۔:lr~I%i""#}i6':;:1 Q$Ȁ<{ jXޒv׉ACbapo.fe {~u=PBZ}u>?(D2~}xfϝܬ6Փg}ؿA‡$DibᜈjN SV/_rw _R~W_ՓG'ǯ?}z6{:;In>fzM1|QalHfH.<~wg݄mT;|>U?0!1/vΗ?Ѩs+ʣ ؁mN?D֧_:ouC5;ׇj,S{Ō?2'zjN62\ջȀՂF5- I%fwgb DN4zx^*Ԩ(}6Qk3!,#&=EbC.|ٕ,Ww>`pJ9eGY~%IA @֔$Vr|dV.>_+]ItsBA-JN~PO&gF>n!h~]  ^LBzL8XVvI{Cߩc6B{ZĆ}=XBABZ=\MߜΣfbkG(q[0:X<^NM'wl/ `2?X;nm@Flgdft4'y&(""ָ9)YjEs3ʋ J(PBMDBMմu[uS7M׸]B{<-RZI)bXPa YΦM]MBS^2{+wS6̞=x5B?!-CEXkZ=klj҃2.m״M74imunS`dУ OBS10wDi۪K0C0s0w3{E&IΞ>jwnx^+zf4.Ϋ.7mn #2'>Hn/ſWO<9?yBk&&=JQbA};l4}tqv|,Kn~$HmMlM m۫{wiW8P Hx0¬m;=;xCq1Ts|@JpgfnFquO'ͲLo':Kî#gG JEї~zoN>0HHDHkTj(@+ڗ PO:5OF1ΧoNUIamyQ6eC,CfYN弴&zǸ>h;Q*քJ!i.k{;zhs4OVYh;P,$]c̳9KY:ǂpp/-?<>i;;ƞ:""$T|BJm3{x[kg"ETopz(7UiħWbV67fיyQ]ͪQ{B@nOxafH ^[7Cׯ~t%_׿>=5VeCFw@l/f%(_|틟Oqs|NgjrY'AB2ht!Ʒ&$/B v}*>Imy= 8vuVsT" ;dܺ9n;LXw6mcRv.w;"j]}~bq|ggt@_DB5|E.E;x9oج=&L !f[F^ܟ/.̾=ʒE7s 4VT5nzٴuk;ܘQlY7Ф'fNFϏ1.C1|h un}g M2)ZʐfDd x*# ;Z٥*s:zqgi۶Y8(_ >X}3ˎ (_!;oDK߁2l']|ey VY6/7vтϩ`֋-q3*r+ӽ]$en+YxTGlQ2rBA#`tQ^D]GByXixvw6LDce3csכ\Uպ2% ĥJY[U4Z)E)JH#A.[r(!sM?} w& h p|+(| " (Rj}>;_VO9óM/H";4FY֣~CȠ.L;mۺ3R taК=wjWM sPYkө.g0q9ߤW Xeo (DWDVm ݲӹTo{޹GctE]bzJkŌj}Ӂq_/}-~]y(I{s5QGJkZ%o>YUk}G~䠸5E Uͽmh/{G,R$C8)Hiل,\%nq;5`p(=Kh>x-Q,zB?쀷d8 ihQ^ъ.N_8{rZ/Ⱥ"QDQ:Nlf2\{2Zj,'{ADrְnK0"oe1ؕ(`==gbk*;'Dĺ5e&1Ђv4*JS98tlDtxԖV4ܶ-7N|v=3vtT콸g':k]nqMW*θ9ʟˌ3I!T0(g P9Ra%zG,9T%D8pG\4)6jGHPI;pESWu%!WQY..+MQ)=|' K703{s(|?>O7/s(Odo0Zқj9 i??W3nAs=}zVy>Qɭ1vÿR,4/m}(G[8؆( SLAZbHn=*tԪ}Br % Ö1"BPȞCs2?p\߱#l/ EDhso}hgu]wͯؕmz*qixe=$MG Ӏ:B2Җ4J$?Fa<^TUIZzv`u1r: B=V4C&mkF ڨri ܲd$/t" O0] huyZVOʹ<{zzq6K:Ie<2W8lq5E_ǭHm q" V߉fD"®fTe,n , L@B;RKNWI&jUڱ@`YV2쎒_"I=hx!h]dHFڠ@~˺+a h5 u^Rmu,؜Ck6teTtښ PL\`PDVYP-W;_08iYesd@k ho_Ĉ/^lweWHvy @3&6쒗'9f 'bf!4`VH%1!v Ҕ-mזey8)f̌36mX{ҧFE@R ֫z~wz^ӧs撉KNiwvxZ,B`b\jQ=+ˋUbE9 )׳դA0]PMulåbTF9YtH@@["> {@H4 O m~=yχ].O/|v3Z녚DRM &)o[2ϼve~P}.BF,1E~X(5Nsh|XRnHD t=o^ۺ+RK=1[Tww?mo V}l}y>Hnlwwd]c=@&=QRRLgvuo}''`RƩqƐ-k CzQ[+*O糇ɇ&FIg/)qîSeڑb!35Auid~>{'Zi36Ye03`aAbޯ2X Qhj隺n&I&ŵem2JSM־niE yͺ>{xvq:Ez55$yBHD2Пz~v]Ryih>lD%><3yP (6Uy\Фƍ_v:gQ՛e9^$! *QUK&$z =+"$zU}i73nfW}c)2 = wop!4=Fty IDAT_)T?Ͼ>}<.P=o|{\8ó$ 2Nnʒ]j^dZX>`~nVpk\Ze/mDOſ}x|]~Gޓa=d ao$j7K@S8CsQS˧_}:M/taLa\b1ZjcBĈ8D y=(/F/\{wdBw jEHeA4  hbq!$fOщ ȩ z LqNK)rudw I-Ϥ(MLv-wPu܉(3k=ig糓'ڑ-He4枳Cp_fQ0^ͺJ(tw]k o;h"H`{.={u] N(``SgGv%fm5*M:Uh@!-o1 7xꃁ wa}'M▍7q/w_?-g B"BZM.8g$+~}|/ş7?kiwlr%(Fכt-{ﻲZk3\_wgϛՇak]H aDo)"SwXGU(;O@.In"_Z t!Nx 2)nX"1޸&A43"vLe :`/Sgy?HyW>)oΎ/+֜fҊUZhPg*9rtx. i ء/LQ:I "t Cc+} C[`ȊY@!Z?YͫYi)uC0J t2 ML,vޮŪV)jTܐ]D^W=rٵlt}QΣ!Uah6;_OWEՔ a4FYkVHQYPȽ !'[r7b[.9iS$?KyTDL&yUMf5'!wׅm:þQَ?߃t6LH zpW\2{e=sb{p|cZ&?p}q}"(c5@u"x~@p] /qyz#{"d֫18qlyZ.N*y9ٿ?9hevI[1ZuKkcOhew -qEmK9"rxH;$6f_j Jn&I[smHFTqUKR"a&L("DKրT>yeUUƉ+땟nfL:!e+d ݖ`xM| =JQCDH? EpVֽ=͐Ć<b=gfs{DRѱ܇쏭BHҁ-(NH@ʹΫjQ.܌+&D5p-H;o(}*x4 g/^V˓OVgkݺی+[%61ֹH 5PDa7c!>5 (Ĕ$!6oU[J#qn2+ԉYͲN9EB"Q0OӃl޴u2 Ϡ`u)b}k2r`!YR|/{/ܱ|qg޳pK>D(~\ Q$ד'?MCW_/_Ԩ'WN˥ʕbQHl1kmjA餼,ϖc7nW4IU;Qo"] Qd.3nvzsI(x|D.ݡ HÞsm^HvhBRKq'鑔Fl[ӷ/Ξ6PkED^f03 gsv=<pbdѽFnkaUe.P#^8FD8,ޞHѩƄPqQ6VvHFDV]uQU\WѭU$I0 Zu]G?|m2bkV25:*^j1 '`ϞS؉8n|a2aTA@C4XTBri"tA S$ ]p<BR%oA\HHFTU'`z Z叾G?.}ك3$I5t@S/^}pl!g#FT1HΜ skJ  wmw v&[}P40ȏȥ&;̲Qj\JB*7=g׳vn3 =1\ "B@*O7oO(5 qp n$@RQ|JlՋk1ekٶ*H`Mm3̶_t. C ЈbKkSU~DI!YSyEwDH?_",Pkݘox3=ڳ^>V0c/mfj*G(0_0_n}k^OmӴʱJJbf;:,~S FA ݓ~4Xs1"UbLeR @ t 2-aVڼ>VΤB]k˼`eq." \4G4vIJS%$e}a@55! *sZCH~T$BC&0 Je'_ciD.LJuwN&3LKC߻}~s|` J3*P_9"/~7_g?_SKҶVkM1+ӈz*Mƀ`ɠ;RBXR[̀W6^ D2egBU4R'ѾDq*=sNhqDL_w%=`V}d^Z>*IV"4LߙOO}v6`hlsRL  /^Yڼimid݂͓Sy-*zӰN?2@geR!5F%JݽswN?2jGj5 W@"@Ҷd&#b R80zg?p~.Z TcJBiz׏8og~+4hZo"{NuX3 %qy B:Cpԃf«+%ƺ͙QZpvV4 ~u8ʼnݝ-(=[TBWh頀 K`zˉIP =nVw4+ZdTCTR~H@P,~F/syEO $]+k /,eg|k2Ih5A$_rQˈ@j-:<7njMc${YT`\5ƴf5g\o˟ͮKF…jtXS8!y FMpfz2T.%Ӑ~cm|y?13 x9$x;_wO}c?[WiBZaE7˟/>/}guߚmGRY qmtiqVPo kW:aŇr5D59')ib>DidU\+{LʔNQGhJ(ߑ.=%Ic 5=0S1J)D,ǢEwt~:k&=j-$ޝlCn&X\d%jW=lALd@wnoq+dN;FԶ(H[+(¢R:(HfApaڡ=붏6yRJ(H}CמU6xQOAJrtE9!=ٜ 0^N}d_ؙqo)_ڮYRP9d d1Dh-C#I4B^Xc/:b4MnS&CT0 K"!8ͨlrrXJ2WC&uI!{oa1ӿ_gڧ<Oesړ%\wѝyg??-ۿUGw^!ͽF:˲/Q7EXBi '?xy5rn:gK# _t#IQR*L`<乘1 rtNȞ5mbjkI) 2Q+L=;%aw߽}a3MLk b  a֝V7NViKݧϺMk;[w ߑ'ET*\Ā߼&Hse1I^uDeRPYzћMP ׇgwl^5Ҵgr*lYŚfROYHd% fS+ )Bl=@$r)E /bHsy}ՑY;/j Ig4Pmߴ7;Oy![k3?ì zetދlIĽFKd11$JТ "Aw҅e~_W_5.{t^6B(l67\ݫeu kԄq,3V̚B^~>jjCM"LݐƜ (?K=̑A96"v]) U6tKɪ|WzuUOSkLɝ`é&LVR /Z88<==?["Z6޴FiVsgi)hb(,~e>(IQSb $3{_6tka []Y4>+ޝbE(P'BV)MI)J+ J)c!aǷݴF'|YdtzLVmf(0P q ,)s5ٜCq@?SZ'Aǿln.~64Z.eY,d= C N<`HRZ`v-{?/2 (a-D2-cjT51dQ4!ԡBT*x81*(DjȺ? ڞD)j e$䙡Ps%9='1+ $*Fcov_ -uR:59D5)zF~W1FE|nQcmkVwf6j4/~=}|e > 1U.`'XW@0?%N!ID{r:Wc-"+̊pmrDArmZ?]n[c5<㇄q0\XVSTVVxU<|($0M&PNۙ%/-Inϳ4>,q/qc4ʪvګ. WpHyy@(xIZ2:jt ^UH+\@ڱs}q/}D |}r| !Bc,x]`/Mؾ"Ur 8@(97wp_Z{fmkmcj67$[ L2t}Q`|!`e-!kS &yE*L+A VffJ} # (PBbTwm/{2D-LZ%{Mw&yY!,~Fi:k{xlloz%0*L!X#McbON>zz]%Dz ,L+imPD {;74([s?/BjQ0֔DMHv@8쇶kA(VR'J1Bɜۄ_˗ۇHD:}#FaZXCXeHE!OpJ▿@Zm;M:L~˽ӽ!TbG&hz% ,nt~>nVk FVLa~gKw+d*H'H_*>PNGgvWZimi.E1[$^9ih=~D|L/yyT[?zc6 )yc47sB[0is5On<*w9U/g"YAq3L+);ZuR|F\WI79h;Ӛx #<"F7m^ay >ljcPVp^mcZHlN\.:Jz9ߋ2wcxp_B 'U =!*"hj33 ohVh1Cw?jR_ԭ!$2E%+#O<1ڼb%y]I+9Ig\7z&k"ٓtQ5⅁q_ҧwef2T 5@ϱ:Sr*Y} IhM߶}=]\>xUFA'wO!GOlDE#Ύ8}ֲU^󝟞M/Mz IDAT7>}0G2J]6FutFk I٤䊽s/]N aݺ\Fk}MoLcb7hI<3Rge#= c et*%?и2a>I Њ`%x8;' &,s>3A' kmiyuM疉!DBf!"Ȃ)#xu_?y,7onpw~u5: {N7\p>\m0G Mֱ0+C_D$cLDTd#4G9{rν:W\^kQJ(6K)e$~^ݼ,nYl6o(U@6z52Vر%$~G"J9!ɗ"T(.cW2wj%YRtMMP\7A*4FQͣf&x~ݮ;,)"}6QbGKj:N^l  եVB G` McO-F^9!\ ><(րק9+6z^⿍* :HX=y ؾ;f4hTw_teZ!@0ǀ "XP$IDžCxm,E,Q>C" >ue zz+/Qm[8-p  I¨ɬKBEYr"5cy05mӴͦ?oT?ٻ/П;ѳ2ۡ4kB9DBD?{aaͫW?jwwv}~3i?I+Ll^1gglߖ0ka6e=3۾ڶgsyb5c @EL^U;STܘ A 9Q|x<6Wmwjec:BE?,ㄗJ۞}A; i5UQ⊝K~2rD nf3#{f ȞuLcHQ!Cr" 38@ED;C5,(Eͅ=`IBqS#*9뫨ްCmNﰲw/+0 IݩyAPOE Rhkݦnt :5w?|'43xŷwS& CՑ=vQHZGx1V }M B'-"D ,DDTFA/̗O2,=۲c?yTʥSe:d@t?8VozUiPӉ3HFaԥ$Cpʊ9uCj;QFAY}-*8".u.2ԮF""n۟v\??5<v;.8ECR'XI^/^1mӇ(NO^1 ٱ`zжcg}նm7; Ҷ3y~WseuMkjg'(!(,׋p>8!P`(M$՗8aP2V1 [)D'G&ERt84ENhI(/8sֱW`/V6G{eY/6ⵋG]a0G7;epnt0 4vcǛѻ`{+Dk˰FPJsJVE1)=UfKQ@fɔȐ65$$>~Trގ?q֝2b8) K ,nVl?i6ju8[4 SʧK[@u'5Sؗϖ<ѭ?NPTŗLR'/6k,:ھiw~۳o |c __vv8,N(j UR@膅45qczk:D8E̠‹ct%"l'! LcՐAX)THnд_%L4m=ynnݍ?~yq/.~༿POhtR1Z yqiV&K<'^J<VU4tLuĈ!ƍ8sfefn5Bʶqbf5کP+@Z$UJxYOʋbƶM7CSS\ ,Y\'n8 *8Zkz2^EI_UC_BN:!A\'>hkrg!)"lۘi7s$3L߽y497nsPƎLZ˼, [VsYto*+d($AFq .B,sA&Xtv{>@ރ8NL@6Zhlk@E$B\nz0F5*Jx :u߹Ň:"jsۘY-rZE}a=`/W<]=% )ENTO8o'ӌDlۮo7asχ~r7~E[|q3oIbBӿ9n`X2..q+ .9 , 0f2J ADc,rD7a/$a ~'"G~| ?~Gk"M1@jຟ:H8r\ã=ҽV$U g&AXe^pe!I IB<"\rփR * Vu:j0J  V'IR\k$ژf{qw{?[QB;ǯ~ߙӲLaY!$偙{st B;.̒Ěl 12@"O+*x8Nw]GY Ac&l-B*5+ ^Na0*K&- K'AћILӈ5EYӣk) ,k|$k2I:JzR_rrR2,L=/|S o+ZCWd0~nn8a7^}5}gm{o_hNRQD‘y0 HP$SGAi\HLS_Lj+ nNz&"|7 FeSCC6l(9/_ggPFB,S'e,ylnZkw僫է0v$1fAF]X)ǂfRWk)'櫕~ÕZm]gk="!Emkt釿{/_02 n; ^P@ELв vj\6R264楚c@^xx:,,BEQ=(* 04n"87  Z*ny߄%  0g48bj-5DRn\/!( %8/m"pr6aȧ1e )M,V/L/.u5r׶C@Kta+Eݦutܽ;ώF];2Zl{yus<¼7ޞ5zQQQ ;fqr -Ng N`` cfDHL107MP| z/_kZFG-4[ )GF!{.blۘh E.u+JʁѫVivEqs)2Lyr4+d s*NF\R'b6q=P5#iB(X6 z~?O~s?*v~_p?0O29=p&])"n >j8 >ˬR$$$!l&@f L̳jIBKpޜi{nV"@@Вrc`>0;vG?헮N{Ix|z8G&RZ|TZ!@"E$(Iy""<2$CM+B szEYAVh>p(2JLY[ІD-:䔜GFpZU_fkmqЃB p|6|u8P@|l=.aG?WwrJ5 4n)TX``EwQ}V9Z[dDoX5ӖO\Ø|Rsb eXJxN ɸ"b^`) "Lqթ=Y (yeilw}]zx7{?2q #3䒪y%uWZZjU/USx-*W!eRy2 ^X{fն"qSrG 6a<4y)d t3/nXxVh"6fF ߧoU [HX dw Jd!"\$+$Ҫ\B`Qsjε/a"|ԫNΌglf +vk7dw {mwVY9g.* |!1g8']hTV~C A+^LתRr|ADfO*ɯd-Q8ݻMklLTlWرqُ0ORa2%W1R,XL>f3kXJE_*TW5<`D]G{s1iݶ?ߝa6֫x j [|-nZѹf~vёa \57kǃ8?|7jm5jJVEJ?9 16Z FelT"$c1ZY%$8/9,^Mzsm4Dr χcěحF7q$ 7.xfOBd{fLsdX(,t5B*Vs4qs?XeqRb2&[!2 pa[٪! Q#D$F[kM}c{\]}j&0fɄ֚4Fsf֐J{De]3 A׶B(aF0&!]QʫqJ'|_`/)qsĸHJ}bDCEݘnZ+""Ԉ5$?E<** DsH%5J;ܿdU71Thɋ1,V1 6 g亾}մMsgF+@Zet.r*UKP VyyE7uiUVT F#!zJѠmnv'0g'ŋx!$"Y}<ɕwx\ M%CDS 2RՊBU|_ ="酓.(a DkMNHmXcwfɍmjT)($XKt꠳`%\ +! VPVJѫa+vٶN䄤C8""R\%JiivEGǏ>f?߅Sӯ=\OnpӴ̓_wCڒ]]2>\ЭzDq`bv`T?!*^b`2BYB˼iFij(XMʜS:5s1)H)4 #;w]ge^5Ƙ`9 ҈DѼ)$ԎːXfV@2$a|JɊ)kjQI4"'x맬`-o2,z hmۙoN/fKW"jΛ ؍uMщ˒- >I#- !H5kPGAL'֍h~?-k>G&|ҤEu/ (H.?}՝uӥK,Mb).Iɺ,20 7z ?t)$? $<Սa!\@uŧ5"BVɂ(|PQ66mc7]&>~.555|w޹~8qp9xx"`X|?)MS*j)&Fyts@dDZRB]ݻ B$SeXFsӜ5j DB)#q<"N۝!qry-" [7.~OE>Yfɰ@>'m/ &-W-'(ۓQIPtq*vJ BǕв#/Ƅn ǁj7]X{I^yUl0Rg9oVѻ(K=LTeQn|0۠$~Pau=㚧^WcqyPH`[ &,2.B +Q2p_44 %U> ^|Ճ@a?r`"" _;tx(j]!:t%JVY y̑FiF+co~{vatnSsOko}0q>0/gXE] TF+"Ů !| t@1\ uSH5q$z͙a{1" R]PJVaم1 11y=7G$@"EU xhe& QUNNDc;q-W{ RŤRUNб:a5Hw׳dZB% 0$!D VZpǭ/R5>:RH-s"MEQlb"'HK0gr|(i@ 89k÷tQQC!LjZ+zed_[(Grhr@Bݨfxs<LD7uݞG8Daz@q˧zOb h}yD>'=Artgb>=K^>d9t?ڮiΚn%ޱi^kdlRK!EJ/x)  * Ú yq%MYH4C/!w "R(&1mw.k> ڿeaZM!,5$=)ag BSROzzT,Nl\3hUʨmiF"F&р 1 ;4!pF^)uS+!xR3 |B`qbΔ45Y[l(DXjk2I"SAI=ژF5Jzlk Y/J {2SaFԝ:fOLaPL۫z# NȾ/9+DA"6rkW‘5h;1/u3 KV idg?׸MNX(tO'/7ip>@>tY>:MAbHS'K/,!ɪWk"UQrJXVOu }]Π"%Ak5m7]v8}5xc<9oð2N0Ņ8d's~Oawn5jc@e]XRɤ!z1-ɧ%V;.Eo=1VכRݑ JRJ,Mif?:BE@4]qaZEJRJp5 g#SF ($EͮYs0T4!!yIJ|b b} Z jP ' 뎚$;q\%_?H9 sjĖR=U&7|C-lfzǛA9Fw6tpxQ&’tY1>oRfV/CW|o`Ë%XO"gwήZv(C+ú^,pFO ʝȼLdaJ6]ا",-XJѺ]|f?tc—QDZ1kMӷnӟ'7?&|bnTVdq|#NEABJ UR6Zw]wtd.oo쇯zw@^?~_]n2nUrNM\`j-թJȨE1Hq {=c*\1 5Z )m0Y)s#@y 0RDk't1O&3"ժq" 3*+x9X.&8 b@r7Y Q6Ql(%-+s\v1T zcE8g,%%&.<2m&Fm!iF3tCZJ%ZڹɍO`B]FUfD$-0p`yfP,ا0>+0ĩKz]ˇIe] N?/vS?9zrC`;Tp5)rVWL =xw&l@Kq|oD|]qFx 0~iI "WȦ]W^;om8gW\>|9ٛKr [CuRog]wj:c{aiAypIlK VQlvr?HID[9Ayyfzm!a%./^feIC\`cS!8wTx>#Se0ckH Gڔ/M/;l7Kq܏J+~nui<6y0g "T](;/T44̓uZd)mEM6ra&3㴛Z ˺M`6ϕgQaI.vG(C?v[|s4KEV].Pq!h {LbWӣ: IB(eӬ7f}>{N%giGXg!_oӪXzxasǍl$U,֬`p8?W#S2 [AESgjX$97 )H! !\|}g7YkRB\d_ )` >o NUlewG XL+u +l9s(켐>>~ۏ7;{erB [;"2X6SgN\2qUd2f9L( $ۦY6GǷo~@OwWW=ih'cN&x!xdh'?팎k[ pڸ5!';;<C J۳=Yϔ:RJ|vyJJť;Es杢y .d@ ZFV'Y $JenQGz[J/5$T-4x`1bGbi;o]2kIʧW ,Gy ɅpÕ6z풖h.[ه>DQX!: ~RN5'x·!7d˝I/؛P]AMG@ntxpLLYl'BDE3z`aep>ɱd JT,R7ZIRB !ŬK>ܵ+J*wg<;$)nw F$m?-| @I\*2j>/u:"cV%>s퇅YUd9>R԰ U%׳ ܟΞW*BR:wx挛\< XJN.UOBz˚tU8Tb2c圈AS~ws_!ߋ!pC{L(0B"Cbxl1 }cTIػ1G-zdrR'Y)7"r elQ1'HF iWJv@?z &w1n4y} pEi; -!yWVa\LnŌXѝqnr%ThÕk>0C=`f/uf/:sdÖs-9<hk`m1[H!$MME4_?>rhLn4zsg3-0m'qM3$BjIqxsS#2"͕9}z<y.-1팰vC7ig3۪9Ƽ,{ 1Ssgks9X6bBqNG3NOl A+chFT$WR42'iN+ 8bwiBPDQRJ)QDlZgriK'Qy֦4"4Q IDATBjEx1*~zir!)PeՋd3,ܚQ/ABńW#*tQPҺoq ܜЙ) !&U-U⒬{/"yʆud>C @H]AB4Y;8 (RDVR4Rv $ aHIZ5G' jKT7܈P^%b 0rW xMDM4DRHҪmzsv0g?:[:??r_oa0ut`rTۍPBdJVA+&WqՎ9(ՋusfiXHʵZF 챞wi/Ew6df.ޘ5 NVI!HF7r@R+#%)"eABP# sP̽JHnj'#z] Ct)KZM@ X?W:e=Ңa)] |3_ЋUT1oe1RK}[`3^'gA5hv8"-u@T|JmAk /覛LwBTw 1 ew! 0=& Y|]Kǹub0 ] Z %A.ZP4S^E $jv>u?2ן_Zc 'GWO/iM?8c1"+.-YvtcfLI)ue Z#ǑbX2D@gg-JJ+R2Yf^]3NU)q2S?Yo5j6\!RQ-RH-RI)-{8":Fb&,[Db8g Q6# 58ܴ֩@Nb /iZn,bINXc*J \!%vZ~7#F#MNOfv #==[?} n%9曧__yYş ڭ uZ60Eoafޝ$QlʉH(R@Z^Yvםu8f-b"+y} IϹS jg{臅wN7/=&3#XZ7n%[FB}jty_[qvBh٬7zx:؃02>m֯lsiRI4[Wv3 E#2B!P[}ՑZsAT9_.y`vAl^iը4^ 5՞Q$ !BFF,!3赓߹/sosHMEd Xδ7vPvDrJ 5@K u$6'b.˃|[%Աo dƧ'A G(.Q1Jl4\f2vkH4"u U#:1WVJe'I|  e47D 9,ߚ(?XA4MfrotO iQ,nhuц*!EI}HN⁗ 3a g4sjneORdR*m6jmq7^y7|^Q \_|/'w|oL56eE]8o4i+J%&5r0 4TNS2!RKRK$頩{^‹x;C.g{g- @ JTifjgϖh $J6R5ҍ %I PHgoܺKw_}whլq͘ *SHM}sE:م%Xy҈__ ѯn1RPk:,P~!>P  ADL?=ڟnN!\e91߫:}L'V0!K|#(iKaCB-ca :_D on-ѳZއb{{e)X̶xCYsWHeG͵RN*䩗83uL D f>96Fq||_ pcxp}];~h䝍]|1e78iB'h"orBFdzRjJk٬&{3yh@:DFF 5Jix:k"2u׶`' IҖVRPGIZFmND)ZDҺJ !PB>U\Dq\sBum%\)`G攐;u{/BBs EDqPa{.?|gݶ=S?OҦM"J-VGmϷggr|GӋRQef";4 ,kz4텳>*N+SH/>b;Z9ˁIu.48 }? B(k˞4mf7ML1Rfs2UH):s xN^:jH jhm\>9{; a{>{h2?7&QQ^0Z)jDFeA@ ǧxT -bb]iq"κS]OQRq!t홐P#)ħIt#-jۮi;%[j:^zfs{G߳Z&BBJ!S+QE?Y6aQT[X4zm'}T`QYdQWKP~Q3͌ɼ 323+@%Κ)㩚r}"62'K{w.>}zO/ޡcbjB6IJo!.w~\@ |#܈Ks$)#o !o|L2I:sL(~ /~o~a)}*bGp1حEZjdsiXӈKVZ+K`"`QN,|9?>>ՌPVnR;`~/GӏcoqF3Zo-!bq0F' "WY}#κqh8rK*<pƩ6RRj%#mɡ9w/^Ңlj]zxI!)D,ŐI9CEJin:ݴnݞgwO6wۃKH$R)n|,r=0^ H=|嫭 @|4}OٓEQsdvdWQvzd!Ii|{ 4|w'fkJ=%ARշtg_tJ(oFy!:+*1D빌%+:1x`+oRTA~G(ޚ0PGxF&\H"ImGQJ*T,q*e Uhq)A2D&ycs:m:ɽEVfVQM;'okᓯNLoa;Lhs~\hN Q'Cb9!ǣ33q\l}ZeH謳)\K!b Lj=jK@D`ښ3 ]#W2c'a\Jgyka:BJ4R+մj4@ QK+҇{| /jM] Cl_Š뷸gy0q $H&}X~fZ83J^y+i( gPT։%4 "gd&}- x|qz!MeBg:SSJ"2фt͵KFB2?Ȃ??vvB0!ؐ 19~p12Fã4[]>U.ue-J fƕDD_ kѭvLwx~@qb|~w}~x6?'7{&aƍScNu}99ZƧp1AHE1T,.+T}d:XF -qn6΢yI){۩75jTP )K %iR!- AR)(4ͺmV]wn7Zn'߃kG/^ 5!_ĴN@ ޸aYV;&A' '&f0a0|]Ң2/sH)Q[g?99FMt)g@c#'`8<:m;jje|$H6RwZjF;^w @wԵ.@0iiVIjX%#bg¡DI6Cj \oQqX;` !q T q ,K!yŊ(W4 .u9{(V= X `(#EMU>99uvrҰ~n |WO~: nr!z1 T6 79Vc-b|Գ̵ @ AAfX:UXV -RsO** $ +#b`s7%2 بOD$+q#vZ+Ѽ|/u96},o钖pWvB"= So9:M$DiԆCh)jv0˻٬[`LO S6]lhw>4Գ8nGLLJ)|..Wn2thKVz݃DԅœW;[E b|ˈHB!]m6G'cw||{@<顽oa`=Q:~nr7[Jmba- o7[^`36^a'~Utg7mB&x=?23Meŕ-6VmGo cB*b⑟%Y(Wj(&DGC6Jwltw=?_[o3p~~=N)';M ^ (mUUsN@ ^|+px+o*a ~g~< tIMū [gbF%$I(תmzv֭n:ԧw~{Kp4 ç.{`eGl7b@$DOKŒfS V˸v[@EmńAi^u}Ҽy1k}T\ҫͪݴz1;ӜM==81TRoXZNτߍ89p)ߘ,10֚୷eW30&=u fƘ(V$%PPI쭷0aK(f۬Ws3 U 07_gif.(M/Hn7B%Z6DvZpv +0Nֳ`IjNnh;yhvx<=gcəE~ZQXRZFb9?X :U =&l̰HHS*( | Iz )R+ݶmRUբ?N<7FXu,#%RIjcr|N7\IÂr̉9:ޣ!I[>*R>bэqyf[&/]^øRtT`}T+ԑ!Lf¤Ҭ^gٻ ZabDA),L&"~u&l֚a2#= 5!I4QȞ鞽T/jh` $!.  GƸ{cד" J3U kF /2FVeܩi#(RV[ͅ㑎9} -Wpyw\>|4l49kw{v!`r .7Z6Jsd%`dQ^Ic꡷ގ4՜#-A3|^Mm ۝Wf~`i"!D\aQ)PFeՆBJ4M۵uhiUs]x0އ/>~7OGǬOE{ZهH"(>,fhOe}O Y^fs+D<oӓW,EHQ&Ӽ\.fӴWBH-_9jAI$ &JJǧt55 (lOZy$l~PSS|Q8r֡)=QO1u{"w_Ф#ru&*JFB\io: $%HLkuרi7nQǛɻo~|x0=/w;U'T#u8\ hb\9UZjϩbDc(ZBGg:¨" rtd%sinΜ~޶"@S'$̓2˘$k-M?q?*Q/ax֛A(q4J"jTRsyqyk )Eݒ9frG1-8qL={ ?IOn8L!pp! ?LH!~S?F5~7RXU30Y`_ytw,KyέMtZ5MuVcݏ_e=/ׇövL?LhGkRylEV"$fłsߊB/&'HdL>J~5U849N\O`qIRtR(JYSd:`Ŕ*I"jjMuJZ6[r~ow@-#/泋o3UB!8g1v޸hӚ׼ 2xWJ grI>Zac]#2 AgNL(bviܘ̙+ &vӂfzRf+nuBJ"\Z"WB79ΈHBngo $=ia ="ao,W"bQLf1 Q˿eB`f70f#IhJ9?\HHjΡFkmIJ٪uX K,,bLa/˙ TQ+(z}tOOއO7v핝3S?јa JfO~ڙ # dx<8)*[Yh&kG#:֑ZNPNH&r3Wp=8MZK$݋<&yRjZ6NnYm6FF(}^zwހw_x~_>k}O4wJ xs&ǵG+/e.x{U '"fУpgD4T3qkd~Ř RJBJ2aEfMCLZ^*jFѰhtOpp@O<̢!*V~EcPliw Ycq0Ex~0/F6LLN}!2"iƸt<YBc)יPTͅZVU^V'+=܇/>:l?Lѹe9ce[3np W2BSePyPde LRPIf-!z!u呔JE(PeZE;s,-lu֌NNV X;Td~Q$ DJwmZ6Gz#d#tsν›/ oyA&Z'?lp;օ>!"̞z(y$J{e j#-d<^jKB@m/ʩWρ|B[LfOdJh0Phqis=zc%6>.B(UP#qd"FK-crЭq$P?8'\09tLU0iH418j v&; ʺEӅjE'i7٭B AT\XzIQ"7Eu햸 sJD]?{,_QDlm۵G =w NW`/{WݮwS?a XdE5iRGLV^;d~+xvHfz)8J>+`GSRH9  /FH@ s9דs.PhVZ+!y<iQ]*흛:UX#}άɮ7V ﯆09v|F'xzne\Gw_泧O]]<շds&X7o\JbP`L.+Hfb]rfbYglv!*_<b^F~K:e;]3 gAe=WZeĂϩ|zќړ&;[O-ьM۬sLS# TvCї9r`[v|nhFǁ )d"SM ʍtl'%:P`4gH3hP1"sJ({n$ %UE zG'Ngp_0Li0M_WSH*' J3%ɂ# 8<P qkw r-o] :B 3$  0c&0Їq? &O]ǹ5fDJ'3SRU/zEȺ[R?j b,@9ELg'`@XyÏ}!:Ҏ!M)5  JPd]9&A77Л͠dT';#z39~4 Z`~y<&w9UYYOF\CfrKn|ӯ9Ʌ+j֫[ZoΖz%&)u\RPBHOmOG W_s/q1MW8-Y/MKB&O[Zoa#rK6ÀĔ[ѳNjЌ`h%s9}N # bWFRRZꋫ(֑iH_J:"fsGF!|7A |aݼ,aA&=zтRh[aIL]%U.ôRz˹]x~qſ~?LU&4DZ"Rh '+ś? }}8_Ӹ&,'`d0Wj'%9뱚GRD $GI.NR!"8D%7R(AVU)?BU=݊(B8_4GёyLƬ挅t4RJ(n;tƘ^}2ܽ_ }8U`px_~rާ-) -֍6X!ڧJW0f\W7j\ã+kW \idvn>|ך̭R- S=qTûVbZN=vw;>B,I֔#rR jy‡EHJ eZ1lBmœ8ƤuIQbzn3#kѿc:d?ޛjJ+0EB̓{wxO೙,6ȏ$>vU-9H<90.*ZmO`JIJ,][?; : `'_2N8نمDOsy$CD!ykBD~~2E#$L4KO|D~qnԑ:r ae%3Y{;4:"IBU% $)\˯w )|~E L= wK;CGj+VSJ"l2K<$`/r4ϖA^&S`n /qjQ z6ѽ~slO2R7x/?|oO=ϟ>7A@7~^ܲx8kCgB ޱf"j\b%~b`;Fh^؈ܯ ~V\MרꚑT5"G\5JOɂˬ{V1yM z u8  , \!ARHA?\t{'t']/1ى=S )Ϯ_b!{ y&P, IBwWߦ/E?{?yH2Mtxx^nj/BE5 M[GyQHXxoAGHB &??>9/tXx Gg.|8̇/K. [$?7ui=+}$E5!p,"L|f#IQ".)P0;O<RI5K˥$Y]9{aiz)N0Q95i= 0EiQFKinTIՙpO_ୗ@|So~Wާl4iKG1Uu9Gx>&Cm]$k&Ms#c[af4OIpĵp%jT]Yv|JVcx7HQ;׶-97_rF%ڨY #" )안"rvBsE0] =XOG.{\ D d(,e+b(ƇNQWb>N:r_޵U^Z{a8w IDATrפ$ɔXGej2לR)Rcv7tۍԆo|{= @| !~KuS[ܴyq\C6Lg]жI&j$^\ceH?`* V6`, ;?Dהo"h~;C"O'X>B(±96a1&gka5`w\t`-ct $-Չ[KWI#,LӒJKs!D (ԡ_B-1uH jwMnZaa RBay=؃#(uZSu3KkpskxtîBXDf܈ٿ8{>~o޿xdv>_%,.:)p79wP ;!z+cM4wn|4oeߐ),[O=ɝ[)k*nTP1;|/gVv J2Yr8lRB+i Л7ۮېP(ݟ? op 1˓o9;{Nd/9g.Z UUUr-Ȫњ] F^󪱮l Xr|rl!\MA˨swzcDnj7a)wy:YgQ`$*PrI/S H|x:Ǎёw((uX;f9Fc$E()1cݕo_ |uvm$:ZƤe@ "tm\2]̓(@[/Zdyhum\{iUR/k>M M y~pᗿ+p~䫯|\yq˒SG?{pLTzs.GW5یY~-{`Ct2)SBJ!]<^z2,.NϦl чfPzchf"/i5 `&b&k )TZtCmN HHl߄?A?t@<>חgc/r9ku޺\p>z3fC~PY%Q{AKF_0HU"\B .yWuzBkkjnE]wbM^@UXEx3 өjijU9Gގ aGxM9"ߔv~ `H``^X}GG4ݢ[6ZIIUT(}>C a ~rI&`#1 Iwduo2^" IBag-hPQF̣z %)<^Џ^ {Jʡrs}ֺ#1?=գ/R:,py2~Z;[9.8MQO^JaNLwG7b|9q䨴ʬl/i%#pgw2j'E-/|Fϼpt4^{A$ e y(JJy$` dKR jL%V9mk\]Jq%^[R$_"A;O>P"RN &v! ]iԪ9)`L iz2LSl7 5{;W'JdhqVV;Nl҇'8>;geL$ANJ%I ZT0;1 ֦0aPת^矾o߇)4y<}wEmE!NL!m!(T"s_-Ц#A%"4^u:U )i hjVf;u}sE4S:Qu {ȜcV}YTBP@Km|U4IF|~>¦z:IjѠg_BJ坟SBd4{z{)ҍ.p$cӿI_e>L}X&3Ӄ<~{OmJ/O`?,"C8֎<_K2yF׸l>  m4:UNOf8*5,򫦄 a*R)!E1h̊H0_x5B$Z %؊kz"oYV(8m"B!3~膍VNɮokoʷ߂WޜǿW3/ Y7/.n޺BF?úV{Ŋ24x883v'(8Grl yG֧)8a TMx-P^6x.2S~0/bV8ˆLf4-s>6+.ݘSGa/5!f!Dua'?p~r.Kt1%V!" %ԣ~eeQxj'<-Jb=]p3;%P'y `s9i3'J``wtPČm٪0=s,JsrY:b;r'.Y15*ba ;]Ma&C#aB5:2)EA%DJ +M3Cm60hKu[7߸[ 5|/pjvz AD]xWb+|Wq+$~!}GbBj)-aq-{PXZX~k²#+K&Wn91[f6B@K6d&@+a5e;# 2H( X/cVrk;rz2Og3rIFDB3I.fcЈczsuIL[ En][FΜm)}\=q>![k|&ƕ`mG>j,5=}ËaH3\]ڂBH܊yYjcd#0 f`RDB!hCvN;W;%ّ/}#*X51]9{<fF ˑG |/!IR+RF]l:L۞~^W |/ӳ7 c` 4Bts1D4UX e5n kp͉ƼK?"rM("~Tm0 j’8íFUk]5e 5:yZgsHdnf?203xNːI$Zq79Lz )4ZgiH$ ~Ii~WsEbM:I7-?CCP] !< >- UMg?[`} ېDm٫U)X[/E)jŌ+jN oa1͑P굶šjT"")]hdaq/AJ ?i'd_&[& TJN9ƳɎN )3ݳVX#߂ugt`n_|}Jot6C"b  b) D @~}AO]_-G69DV׻@![dǸ01!CY4i,lW따NbVS;c a)]()_U#7vt%rч$(" e9['cv J#D- y1Sr'EZ yeçrŲ˲,4@k\6)%U"a TZK'TK?la+Up'{{{]%[x|'O:M[87[;-vZu]h1 W@P^_ʤsQXU rkngwӡJ&37*ւ[BYd0/\dTh<4f))rj,ʬD"褺p˥6f6}k:yW4ppM)!oDRGĜ^n1&PĈyf $ M'w}(;蘭?G `T34yYi(SwMnVvxٚHw-qbd~~4b)hu&LΑs9\4} Ktw)(4!x-].s R"=Էw t{\,ଵZQWȚRίuG+Jkitfanv'RuRݫ^ћ/[wp+Ѡ7~'/w%@9;Y胷{1Dg " S.95BRMIJ^E$$8N# F@.x\tBHʋPnKדzOnis\.;} ;);QkzOQK+P!Cj{ѽ1}g7{);TRh~.t | |OWϺR8_l2jyN+Bv1ҦE[4 YWGѫ!źB)森*x=moW*Z^Wi-e c ^u3JG$1he6u]/Tt/}W3stv5A(RF^q K'5iCK (HJs*EuU+1Dɜ\_oD3/9>.}ċ@$؍x 5z{<껹Y_b'~ j?!a $nX6 _{e|2C^GI@155^tB)IiZ1yǧ`G^P|#6TF+VtoܘޘAtFxuTs]=87:!?dZEq&Tg16B*oêju@e@ ”ufDAQ*#n'ZF^yA؎ Gl5#W!axT(AU:e7'%!qf0rPKg7T'Q!}uk$Ý>zʤn97u*$gA%H"!q;Y ~q}RAjĄ8v8y JkUuyܶŠs58 ,PF $2r1/y~czagfo:! ۻ9F !E!g='RRuʆ r~YZFRh0Z>ή7'eΘ~6z7n0q 87|x!zpnIypb#G1O"$b6A>g^N5"#uF4E(ZJx2Tڜ&*F*y=v^+3 h8XLpA(PPnܘvpBfL EZ)*!TIvP$:q?rbB9lMZ{#* YHELr*B63Ctq|D{}m ~LLDRHY\X8>wah$Y+@faH닇~8aLegHO R0I ,L8 L2k|333Ez:QjIs/i503,Y%)K;ƫ)RS)$n=XˉBI:wv m:'ݭ_-P,_=WO/.a4sKYВg`3,kTd#gc91(IˡM JJ  -+x8k8ؤoh}F )S4xVNJXe-b H&;T e'F2D疔ez>" ڪ?زjb{׊WTaOntIXP/)S})HH" BP,)P|,,[9&1&r'2) Ybi9(Pխ,cyPŤ0s\pg !% CvPrI/q!ˣDBrd-w=8![t#=CDӷQD&_r/APYbbJْR3NCvfͰ޹wUx&I>??tA(:p[,Cc bNr22eC{:Hfm$sSyvP-564Q+^nO,o/xU )&~ t}D B&Q6^dF90_I-F(TRI%vt~ BI}b@2. AJUXc% .<ڥ;k,Ig:b %``E~<ಅhϽg֐Y[t.q >=.d|fTnd'M[]3 "p2'RQ)8}b Y'z'RR-XkJb$$E!x5ykKBT);d)wBBB(!RZIuCN^}ƽ{22`x W_' 57-~!-yRvo{:*2firðq,2ǟo?wAPxcB,6졐1r!"VIXU\ 񷃏G5iב:sȌ(m%l Uj=pw :bz2A+ 3,,󶟰z[Wkkhh X"#0jf78Lk':>>-Y !"(Q0씺ݕ3abBʍ.g>0$g5:|1q? ~j+#< NG~W;TO.]%9فcJ9EJ*8|>OOĎ+3`!˳}ispCz"[6e1 |,r0@D!d]vw~ le~Ǚ !dZ5J!R%Jdz>Th!H\("NѠ;v\q#iF"DR`fN5 )1Rn3Ͱ''n~;w@Ksx5|> 74(4Fg7/&RlEi'lkC^N:JI>kR&3-oh8G3֧14m9dUi2G|dn_%6[Q5nL6VkZbpMٔT-v>Hupѻc4Ƥsî±u$E];hv>0dDB!Dg6}K4)D2yevX3K I(b5$.ɂ^h6FU!'<2JI)&Q0p ku֯o5{6TZ)I# Xk],pćK<4c~tnoGؠy?wß ?ۨ~xzq,F$seW"{:-B7J$F$I%Z^J/apƧ|if z%;AJ}̘TG $B]lΘ^H#߼q߄'T3xOgP7I h9Ygs.yOv}sۨścȺQXsEx)><>#:Y9gq g~jSyâ僦KQ+ҪM-yM*Pz6\|Y,SЩNu!١FN1FiRHHFa^enr~ eI-XhB ! "3!z^آRJ)1f߼]7T$_zG{ W>Ox׵ tf<V%b&/J^# $!@dUk(Dw廿G^E*"Tu"W|Rh㼟I0-"`&+]9 `?"D`*o ; !!Iא<Ǫ$/'{T['Ý1Zhf0t3yrXr~yxv:!nb#(,Yq 6Xe^xiA\V3+Ӯ2uD pU>ZjdbZ+DpjO|ͪDp̖yG,u,T+$H֛7Qs S`H%oP Eu c 3q AjPbp!b҃x=i;Y;4$)K9nR93|N2TqqB!%{1T;'y7/JhDoB [e[^dW]ZЕI~ZOVb&FUu1EHr m͓[l)A!tL2-8ͷ ?Mqfg(c&ʣ.vqa#MI#GB*!$*B$DrMZ}qVr4K)Z!Wsm:m:on#7A)ܾ~'>nmK.%Y{s 1bEueTxٽE#:ZĨ}4-d#6x&q9 (cK2U:&MGQ±ѡg9Oȅ$d8jh 6U^=%q-e pP%I Jtbc@jyB)xg,o(}HXn/&?{ %ԝ>\6$BBX3Dj&$0pƟϾ/M/[2? 25QL1SistQ\H1c,v!;*Fc%3?Ok%eIB^_h$8"H2[QbU+I'\.!雧7wRKPTDTv16miy1-w?$hwc'I·9`ƉctOcTcH Vj۝H49S#B()UBtf[czcsnW᭻MTއ>?|uyph}GDu[w6})/J[6&IEު)T0 6D^k d&Rsʔ7b-)a} C.UtZ:p8嗂hb<ۯ4]kaJHR*VOt@ 1r~W( "-vis詓>2R %: q긋B c8V4 e<w@ f;\_K@77$HRDr "Rdzl /^Jr1'WmVugzu{NJ$ DQiB %'A!; 0\̑K;m4rjE) q{2-ܞ% Y: ?y\ jQHPJ""YĂ$&MbrcJ%%eZ}7owny%x%/x9/f:W+Kp;>JYpԘlԼX~=K)uX=R7ke}{蜞Mxv^P 4fbV lks+ b!ʮ:*lF@Z L6ey AVvt"' e䯼{"0V/voF(l^r@,3}Awmyݿ{׎v<;[I!](x+y~&4jT""Bl4Q!:1]Z./Nmvmi'ګhmu f۸MWOmtL<͓cd%uRZH1 (`hz~IXTw@t"$)HJBi13 %;!֍^u_z^M 8WQ[iԁ_\J1naUaHQ-k22DaI#OfJ5"U/XZcaBR5XOVkj|)(P\0T JԼf|\Zy=&{ "(%dh06θHqv_|dސŔHJsb g]5! ob@(Ph[j4#N_|Ond2ti44 !LrpdB )h0~ξ .c2bhu. 29ze2"ElHFi) P/G)R~nݽuFn4)I*)hՙΜ3J+Ur ! &08#Zfo&#0 $ZvZ"%w7z{ S| <sSS7"8{8c bMe+8,qTTXTx ! f}Hzx:PO2 4j9 \b%q69aC(ƄM5)c^iWo$з eՆ4R թyr$AHBMu8;H 2B9,Vj"!$IL4R(3Sfәj?"RɌޛ=I}~{cˬ 693difG =(RHbHa"Ȍ]gFyP@YЕ$l.8 ,e^Em6}5ieO}3x >z} z#S??ߨgz1jF-n8{1cLOPVCӴnQ0CzRzpM,*}kQf&팡OX "SY\꒎ഠ)и]Xs6!^-c,Qv%%{pJXnipij :?c#"3_viL)G+7;vB}nma15 \dyiz'ޝ 1>VJ-Θ6 tхIQD7r^jB‚GOng08Ct. JJ>;{m4Bʺ/eYK@|-1@BrozwdFku1mp f_Z{eE..2~>]N)}{<;;O#vr8o}1.LMs\}1RKF)kN*JZ ?lۜ% 9Asbvޡ 7uPީ:RM_)R*C ~Q8N͏5z9E:.k8j-;=LgʒDȕRKkH@{79wr<ƒG],$r"fwސR c r\AAdC8?Jp8gρ|18dy4{>&{ϖRsQ*/0yENo_)*iSB#zcfK˅-aӛix5NoF?y?zF\lMJ7 )k b#{YeJempB魩6he~U>}?#]A: / ?S7Sdw0Fsc"(KZEMԔ笒Ji0fBUVؒ %dGJ>ɂ$U.kfw.z yb7EǠ<*bv̒T:iFKp5MṞTGFA+G0 uBnf`@Fywo\`'h~7<ŠQ#,0 `T~ ?]A+%Ϟ=#cڊDHNP1<QKV-7Rڍ&0w !NѠtpO汹zlg;czi3 {mr^zO0L1! !*EMfGm}Y)˪[JӒJ'<&EBCtwOo?'O{ t?7?(}ʳs\p!̞Cz^[m\KN^ ŁQ;kq)vK{j]Ϻ"!sy\׾ ۲㛕7Hɹr9c!jMnUFFZmur4oIfLS> 5o@}HstjN3q{5>mf=|oݽ#L0h$Ml}x].h}tKwsL2ϳ+iFqP; CT"Kj֥﷗4%q;xf0[6ʘ*$>hW_\?;ޘQI"֗U #>%pi^]udaT{7.wZ.%ckG;L@jwl0qL ?Hiq@tbihKjZ\$/fݾp%?Y64űSt?lRLr/J@EJYA:[α Uy& iutݮԓqz3 Kg:TF@XpC/3SC*Jz~i52Hqx= ĉ&GИ`jF FYr)" _y∏Pݧ}ن*/~lXያk3(L ߶,&sIi1|710,lD 6bDңfZ(,ZI^^`cO>~`0O۟/?;}@]D!L;|1!5)!XZIZgzTImrme9/RVi#Zb%b [sKMoZ ഍< 'U؎tҭx5AqX jW"(e7fs~EK)NTD@]4qPVm IDATqz@H=͋^mUf_"PNo;~}ΓpxO>?>lo7O7WjJfN\겑:<܌ӛ) M>ybXd5봅 =5og5Oꀎ9P^xa jcx/f@?ӿ{~/}u~dg4ԞC R(qR''^^"NRReKDp89TΛ$S|Gf[=敜ͨvD!Rt!A`Σ  ϾEc_ϟҋ _8扥=BcM*Nta1UV+E P7Xjk5"9HI @%$.-p+ }6 ]lj%}Fj\.yXKAx[']&0et*ID,E`CJr[%loQo'}O@ }& }ʍO#  Rv!gd$}P`קՖTxNʝ4$Lôs;4 fr!8N}9004x/`wՖv[j}|#;6t"浪#c d4݌~nޅdARKCOtbD. q<fN #'b<xGn|:{տwt7YT|z8|6(_%V""7jHyCv!}5rNċei<Q4B0Ge< 7/O@O>}w<Hnlg8Rkq-P7= "I%KO *P&%WN`,m.r}}TjK+(K+ZAp`--Th)H"*"~lMtg'ѩda2)ٝŞЂM3L4Mw5 oL 7`B^mCoNoN5ERL|Udx:W7鯎QJ@b=L, 8M<%1EԽ&EqaǔLpIH3`1U'zDqH\õ ar"fKe]$Y$ BR6nT{GȐ}nfa?9c! b0lӮl*04oqY%٫S5ã^#Chg#ZRiWŐ]̍QϢR!G* ]YЫŶQ>t7΄.(TIHb"H f7It;Рi1DtI-Iu/ݽ;4@q}rY4k}U՛h 6V1R6FP0Sot3FW\&Z$vضțFO{[l'.y5Z"j{*ؽoc؂5dT]1!b °r a@޷+,bx 29/|P̻]\xv쥁E]͈UMS򗧟eNpKy~ \n25?mEI7EJo/c b Yտ_ ;ڊD8a! GBMgw=t-6Fmg?x k_ L5G xC=W(x|}>ڪ)ba"J/"U9rfw?aȐf@H+&\ @@ar,(AN?^OF@x׷lzv}սQ"BZL|Xi/(^W%Ҟ PoYHNi\AQJPCp!=ݣQPFplm0L5i)uc N{,H,n5K\q#]U5Vl=j*@=BսcCdβ:.H*)E)yFd_ZW(Y7ꊃ+L<fPG @\1>;hkLgQ*+- (ëqz5']>}$)EDYٞITa֨) &=V<녅6sG]-3N~s8<ǘTeA!J lE*Z{lQr`f)T|ˢd+*̓ T~3RP>֠f!$ѿ[cX(3F֎ȓLn|iNX)ONKwo?nRCb=dTRPs<@t쭹>0$ L>_߬,L?/woz^η7G?Qe'[R!6-"޺p*3.䈨NdBe90FOI:v:;/Zv>?hkդVA)-$ M$2a|391*H!D\I0-GB$(&2Z *6)9  a$7yN1^nwwJ30F;O0b1Ɣ.g3۶\I5ʬ"ވ=kIMB4g]0?ZVUKJ9o򵉖fUeqJLkA{v1|pCicLjJ~iaMFz KjD\TjMg4ɥ[oYAP,ܻ *’2phgvv?z`2УQ>?I0v):"\Yhŏ@aϽ״ #D2)a p~ ?C?9γ*^l/wӺĊxt;^}q iZ|ZȣN A`I짛qs8ageLl1HPϞ&YQBRi7P!!raAuI9r <ޏ<7fxlJYi EV%Z9mpYi2\4"(-9.1)I|:K% Õsq97C.g{,G,eYfpB)kvo91,_߄uj aZ{ 3(zyc.R~@hܜv~GDJ-VG6` hwY8*R)%D|=F t*0A7G û`1J+ cH"p̾aQAmB7?}s~wϊGvbo7F[(x'Ӎ"lj+E@@e|5L>H31vWaC ,PctI!Ta `fJ+>x<8>m6O~6Ddn,v @2crߋS| nͥE|,aD~gzp-L<:)4Ԁ{`6DlD$QKШG#RH.ի픽9}Q/XrJuGvgo}`04Q%.JE LǑkd*KOUw4'aQF]  @uf4_+{wbt9"WsʚF2ڹ=+~`T 3>A""LZtH4Nۗtݦ `T؀c]vIp3IDl`~SDzx7c8Z@1 ᲔKK\Ϥ ذEoaKkI:oR5ͺCqq5-/C7>W͓Bg KS}y⛗! .b[LZ`1Lg{eQ>)ïq|3nh0;y6ݶS$Nw4>$ԟҤW_}% HJŞՄbfI.`z~;QSQc6,F~sqiGdvl;Bf:~} 1gJ؍б5o^( iXIί8yvMWĖLNn~5l6Jc !K 9 I+t{?yf\tlUFXi:AOfrk;c;C}#s.tu؉DX҆."[Zd_( Z e^5.!nrJy,PUehu)YtCYo}0ض¥XYQgdxՇAK*_Kx(5ȒU]ΌdYHI3-^6l+dR\\,3ڛKƷnk: j>p-A)@EƲCߺLFh Yd@._ɗH:dYv%ܯ 5fTtn>$8_"lʥBrt|;8^kV `G΍7SI'  cMOw<͆M[$zVJ 340%r& Ĥ2 *Fuvz3Bt560ܞNona>52 BJw!gw÷g6Wu&*QMfaXIX$^f΍.x=hOnGvgJ+uL!Bt$("AMiDHx`d@Jw%einm. 7i/KW ޜv{D@"KDJwFoB! x5W[e[%Q̕yOk97ty>.a Ut n&=BQZz>T~_1bPGAF@!+:(*j j@ h zKl-RtgI>39QejS+jlU׹쑮:A zƚ/!?,O|"nCV>2BBl+2 )VQn *-3TT鼚#,bŸl 7&NxIUR6V\0;ifZ%>Oo:Y ሙG =+1L"w&;=tdz&KZeI.aMzooWcȊ>>|^ӚUmlH9/S2on>;tO*BDF7Z !>wϬ() `D}zAIX$pFB5'Za ()}N }ǵ2=ز #K,iY""8`˹XR١5ڳY%/ 'x*W&."O`F˅#G.ej^I¬SB)8Rϯ7Q*͒T:Y`p'I4^wsࠢJ yGNa!2*=73rνSxkGځmS{Yex  oد??~JY BNZQAGNg08k1Zk޻WOlgݺONntlxtFwsNPE|TIU>fjH@ p37;Y=h4"jw;9nry֒%"$R)MOQ2!u}޽JU! R50bE}$jfa`"T ?ּq ,%R J?X` @'ZFGJ&D%3&;tႍ't꾠zsG K+`QDڀSfj.*TD%=+c"i9k[wڈ6&ZDDb2xz |+s<;F?ߟn~tm>^\wtn WOwnN?ޞY6Ovk9#GJr9+[u;!RJ%E{z3No&7np!i=MUhBR@MoGw8>l_lf}g;f5JL IDATV)BD"鈪J24l5 W lݞe<}-EX9B p6i99w**;.J6K{d4.╌#%ھ"j&HGl{%i^5_L/aPRXNH-p9K iB`UqEkZV5`$ te`#`!Dn| ʳN % //pzs>JJ*Jj1fkƈV=/WϯwX4đMg_GO_mia/Nlzv=hk!l(6rp51*a|=ٍn\OnouMgNyydѶhQ@ҜjRܒZZ*MYfW2i:\\J$˔_n]InZp {ň[*n~i.75}7Iڎn6e :0JRuK\>$1 K|?m !{w k D9®B`|pYYBlޜK qMFm7JꍋsɚBjլ.6p/j gPaןέ_QC_\;RXEJP* zhʣ@DC|"_)-Pa{g/N|y~CM~ cߨ^P$AbnѴB6KXʓ@ڝ86:1~ǟ7Md>NL`zU(ڊKNWjrhF)bXEGD):g߿7.1G>~~ݮCOO>rw􂞇s`M Pi2XkBDZi&Acd]w_SuW_ك՝!"DZVBA9 B4Bǝ2}vyfSgYgVW$ G]6\})\b@3tb+U\evՆTZƗx} j{n3uBY]ڑYA-rpiPMyvg}u:7Z:GDvy;O"yZ(o }(7AxΌ}\xۃbk--XǦ\wYE[QA_\ƳQZ>yt8ArrEp™e;'=\_~).> ܔݣ<]q qK@V(E灹N[H\ sp'G;vH Kx0h]q>O {ByIdТ,#,#q跨CY2BыG"'7z~Xk2f'ng?MkRV$DRqo'!xţg{eTVDTC*C6WqǛqz7POqo1N4;0σ}۽vkH- jN|[,^',-rqG% "nͱ\wKp[(ϥ,TDTd4fĪG_H&iׅ."ljJr@kk~WZ29DίR 7wڎKbcr ]QkiiZ&\C H[RET d1jaaf@0&Pvoef*CV!i"N[/\kvw7#\Τݦ>ԂN%=ҋs8;N0ԠƈQ)ܽ;~{?2SjAEd>w W렍$}/X3ϩs8݌y-ϺiJE w|;Oyyg}zI蘅Yp+Xi\4`Y_cwRJj{LlsH12U،d"3gPhjEu\ա1 FIa~"jD,qy5بʋRS"#ҥaMDVƨjPΌْ 3I&pS$։0a^>94G5.tjTVԬlp|d5*:SxN·oY O$gB P<(xf!#LLJ fs\af0Vu= È9?uyVCo p|}7D y4NBu}+Df~z1}x{l{QI\:&-UC߮kN/37zsl^n7XJopyz;q7[yc5)lE17SEj+HOZeFa7i-ܵti0REĩ靜o+v)zQy~Cɬ^XsJ*t.xiY̎%"$kX. ;eX->DoQ5֓*$ r@n|Ń2œ BZ$(>xBxpe$202 KN= #G@8OZ+<DAipOuX\12yv7\oa~ z& qϣЈ Q$J'7iUW֓fJQh)"e-%c axq}!@I!gp5H$輋'E4dUJ!AAW2ֈha/4wW;gJd2?lt݅?Opz}?AІ,;9yiΣjJkDjM4Mᓝun'+T` *o1ތӫqx3L;se'^whkL"ngF\ؿ8vguw a6SK 8/ ʥuP\#T5#>%6=, ~1)qBV:ۈ؄p""8˂{Z'd!fDzy`eceC3dVIŷQ$6tƺ*M&dPOؼƾ^6\zvREZM+y@ky8C$ɛjŝ8F[ #FA)3Bz,458AOnί7xwbnQ(NΏϾvH3jxNRVLnNaAܭfA]gtڦR .rlk| [4{LE!zHזL +Vk.H4j@kӯ4~6-&:p\{3iԮh6ژK3^,lA(X"D )Cnt;N(5{E"K<(0|rFNވJVRHD)EhQuuitƂ_f" 12p@V4y1[V7a#?]}~FS!X+ԠӫiEd|yսFn߻<~pa )5wlt Sֽ2DDa a mΥZZS.;aFPf+eA j/ c6+XPUdi$k>@"Orb9./RX7HY%A/FO^ .b>r=X=ւ`E_Q=a)LP;ΒtUbMlb("$ouo8σ= F D&Ӷ^:PQpM*!?Q#aj4)R@>nQc 9@f{z Gyzɨt^TT+@(iUNuM!/D|B}dAH!8qV׻;c 髩N׽9v uʨbtܖ1@UׅR JdM1HG" YYezaC@! im{eނo__~p>&eQ"5w"Bw}zbMoQ{)6YiI. 47nScw}⚬NMG<a 8;J_+@YVVA`9@0[Q!;f˝ABb6 mbkM.:L,,ORβ O]Z\"b$Hٟ\iY|_dWY"8i~;RIֺZ;He]z`P{3 `%={<2 -bB ( -.cvUfgG>\B1:lAgpPWS;Vvj*N_yt 5p1 Hz1G\׍6ay{1PjGRW v+؜!T|9\ENddh{WhkC9`{yƫŲ_l|oN[>z zh#9HoWԯ.8rQTLgS ֛樑V$I6GH&>]/[u=bPQ31UMj.5P)`̘2 IY<^l?kA(JfLV~YNjta@E jd/QPe5`+z<X䥊_f#Ѡ9r2Dz0 ]hF;eaG Smxe"Vb[Yw}vDpb wOqj#99:9viz^f\1d:7! ?ǧlZ(/_N"@m$5ݲڂeɼPGf)T!ƚ;BI"i됰]ݺb/\xU904gi@[l}Za{noVҊe@DLjIR4m}f7/NO!Ž4n[4ݸ _XXև*Qd ?p$Y'Bb4V,tn~˝[М6͢kvSRUCuW}5/\9![-F[Ш7mds}FrynjbR>hRa✫y?By_IX}|] IDAT@D$ 3lբe9E\TԴ"BBvR6]>bjýϱ0vLvY^`2Ӈs0p_d ;4E%&wd43,9k`}&k4b0ܖ}NT]؟q1$0#^rbff$-O닦,N<8 q/(D3_ƕU1I M_Od<,ϡt@0/tӥE?zY6LgĤ`7ݹ:R+f\L闥- L[7[=iO:Ώ%=ѩ[NCwhHl56gMƳQ:]6O(Rsq[_"#J7כs_qJP4h}XWCFb<*Qoqw|| ߀}ovl11`h}~t2W@ZEXx:5N;-*4 hNfфŃW**dm<;^n&L4&.iRIꒈi͉нV.+Zi4 D2Lԭvњhi$Zd- s1wΑwCN{d >;OOSC-\ =2NL6D<#@CڠJCJz۾ ?: s6_h"!DF eX&fT*NcJѽ]O"4B mX& u@HH8JX^>{z㓛'g4?8Yٛ5֝_./lL#G9s69ʀbUyjӤ+)BUElvPr.[EN}?q'аY4͢Mix)K+.\98yoil%2)^]__onlؽ"]J l !ɏh mfo[5gy''Sh V*XsTD@6<$A1*i:#b692DM`,>o@UU5[{fX3@ ((!"Q Y.'csL*#-P5h8/)d# ϯKI"5#6D04"&[Kd.d2B,6q+!P4:Xڐ]yf4;],ϝptj sc"g5素>;π&:Ÿihr9 ݬP0 wOf^aТ~CX_iMX:[.w:!G>k˯.SuW}ՇW.d! wz7<0KLF.3m77ݲN$&*C(6C$*@-$C$ Db@#4ZjEY") j/v,$b|JX UȈ) ( -P@AF055EF&6QAFTQQȜ%7hHC(W)FLyB-f*hlȈ%!@PPW#1ɓ*Gzi1HG`7Q20ZbqU`b3˪S 1`m A]Vg4ykohAEẔoH@DN/w]a`:C _{GEGa:5icYiub6):%b$BC k ^5<_-;~~\} U9h'/V{g\]C臸.H&G8x%Y>w\_߬olV:^888Ȫ z΃Ku[#|8)E};.Y^XW![:q2L RUkush'%Ȫs~pMisig_2, P٬7-`@!pm+{Q1`l922.K IlB fX@IO1Dڔ6)5MWج[4{l eED,Ps5' (YH)&UuEL`pz)ri4l&"V]}0Sf yMڏ",B!f Yh:bŁ%dwiV!bviT Cuu8L-5)rۮl6{&0zfzF"ibuXfЮZ"ХNENa(ibqn@ګL-uUXM7M5  I)1t) jfd("Dۮ.g!Z&F V53%I:FޛUU9Nx4[$B(Z_Зh/ḭ,BpAd83#wZ>4V;l|x_U]ጉPL,h]۞67FP4bzGݢ8)d&F]>X>$M&&ۯf#qNL៖jv8A>05.Wє:Ibf҈&UC`}ER[q˕td33VD$%S3. `L6A6iZlWV. D2X61%ԌkIFdhɲ/1*^gP(TQ4PRfF̰0Z0&61H 4r#5Xϡ*fW|@ȑR+&@1u5tf@ȫuYZ11$ˆ(]$0*`D6]܋ jb#bvDȳR1Ie[U ugaΒpGmWm54iUE$jW%vb< <\q=xbCG܀DŽ;"Q>ҧA.Ig4@*~RcͪI3gˣ{tݜ_G˯}o7%HRc"@˿+|~0ڿz.N( e#Y:VRJT;:f'jT'L/JҘ0pzDc@31av"G YET?nn-x5٠(1\hN)C.%1q>%D2LRvP̖LMJr 0QH:5e.ZķTV&F3qxWE jHU^04Tc!VYDy1e 9f|hRdTIYnJ1p02W`u&JH:s>#)Zu IttHlj2Eq}X#c=sLv<{t,#ߛ3M.zL '59n9J!tiIۅW^Ɔ/d1'ngo۞Ĝз.G_yy7<6'UVVkvI#T16~=&d yΦf L(ik7NAdfC̋N.Rg3Al=Ĺ } E\Hy%!"@aY1if@eH5)2c1!#3yΈJP~h/nYYuB&zQ%r.08CU+bg@"" d`*Yf`@@9ڀ0%B646D%2 KC`мA?Z=|揜KD -r3q͚ll|=I;cG#&R1bѡhE*DfiljܠZޣtNݝt/kO!ǭag8$Ϻ=Qy8x~ MKyA |'nw:G4rmCN ǩ2[__mm6GN[̈́>!k=PS0R$յ*"n(j<'ˆ7NvBĊ3-j Q H" 9BY\a.y0(B&5!"__nLrհ2CY X[N&%q&{_bП6?p`hHH\%I!nŻ",*GO僙;/~'4W~ѿi"elF23f͍B oHeyKi HyfL 4i #fXSn~6jdh9 אi\fv4XɟH-ae2X9ִHlEC.J9J$0]>'̦u%L^hBɣ]?.';HO]zZɹ "h:_ooe%[@F .,[UGO=Oewo?A5 2=3B͵tݦVd#I0&TiéN? T<@DS!gsDLȰ,* jhd@yyPS(ƿا 0Q$#rКrC/ qQS%sg6Cq6|RC $H@!itt0fdD 0@D "mgES0 ,&&h>e8AbB(k-3O 9mźݴ6_nds1 x GտWj?SS  ud1Un]+HݲkPiaكLT4I%`Y ڔȑ0ujC`(2 Ibq23&25ͦT7+0-* &4)O3nMjF~0BVKbI~T~S+$f,풪RdO89ԙB*OIb87s4^DU$; Θ8! *DUDh ٵ8#M!5}9!$&H([ $MK/T̲`^eRрyu4(Z"5@TQq M)d Tzr[KM)Q@ tTME1Kf 0C &|9(a"dܴD0>$5-,!fbTSQѤm=?{lq._߿xp\]˓q^Asˇ58^ ?'FCN3 >m~z&FϦfWߜfOmGv5NHe;X|oY=}6jT73;E`~qnƜLs{2 *"45R,d Z#,P'rI~,AٽbzEɒIv)\$%: %$ >BP6q%.͚ESGX\1Aj,PR#FS.! N$Ty{ {1"!v*IBd $wd@ gSR,P i%_dDhWy4DS嚉I[imPf ) vNEÈL&̳f1BRj{rۦ@1tBrR aRc@0IŤK&m̿Oo,3|]^:1yuxEUҟg=zDcp^`T9}M s􏶖Ts;{+agY%?m} Q9~A0.l=L?9+};VcmmlD<@s>>anaNn(,2Nh6~R?W-Geʁ1 bE5OڻP|~{ߑ:K=OۯІ*rNjf.g\ءfM 0*AŸm/$Lw)DHnyz76dYn3@Jqwp[\= /A75K3k9K3ܺmW:s+N"*o_ҴU|1&;s:<{p5[岋|#O~ME|'q}+^uoӶ~Yhv_P[8s_eyky]sykyW{E;tBO?c#"9lR1ͬ]&r=|s]/.ngJ`I9_Sԃ9%hgsyW8#WeO*L<ٞ%=N}˳qc/٦fԆ64iejW;mٕ\_-bX?tS緒1»~]w|ɻEc?w~bhhf҉% P=Bw]J}3O_:\|ES C3V+U};ݵGyN9*OVիW&[|4se-(꿼8o;0l)!ˈp{;Coٳ[W\ϰקͷ,9ss.5/El%N'?xoܙvV~SM\1lPeiK $,9rqzG:ZDBZ-V̌./_|omo}۷5zȻqkG?WPh#et`֞V>zFW[CgE˭n> ~νq (LQL3V)ăhebf@Dپ {S˯b@B?<ٷ¹ &@& 1|c_,{C$b?p8п_O}S\3źDXfSSԚeSb10rx?u6w8N\<'}? MԤSH#I<٦7~==o7]p8пl~ YKU )tQDIFD*'qm'<_z~#p;?KLa/P-}%P4m}_| g?ngsMLJSMA.0"z$ĐHUU_-v]sT` Z7C?g:Kc4iǀg"r C'~׭R>#wp|kա}?#r ij_x~~8߿f@ZCŒTzy]}>-4p8+拟y߁*>yzڝw}ͧ:]KMà]Oo>?7 ݽadBC <vC~CMs4uogy oyC;;MIm:M"colp8B3??nѵv}] !Х˗Е|݇~RO~=99?qIxˏ -%>z/"T^랻_ϼp=ՇՓ)gsַۧo~kNV&V677_;6G_7gS_zŅ_1qz:?ܜ>Am8|ɋ_A?fgs.>z'~'4iߚ$cz'oz}w8NҨj1C=s'Hÿ6mri{z/1cΊ˙G_lp8B6 0_zp8;wZ= Zp8 @~  p8NpBw8p8; p8NpBw8'tp8; p8NpBw8'tp8; p8Np8'tp8; p8p8'tp8; p8p8'tp8;p8p8'tpBw8p8p8'tpBw8p8p8NpBw8p8p8NpBw8p8 p8NpBw8p8; p8NpBw8p8; p8NpBw8'tp8; p8Np8'tp8; p8Np8'tp8; p8p8'tp8; p8p8'tp8;p8p8opeUfIENDB`PKF-Pictures/10000000000000150000001C9244758A.pngPNG  IHDRzc sRGBgAMA a cHRMz&u0`:pQ< pHYs oytEXtSoftwarePaint.NET v3.36%aIDAT8O 0P:Y4$m _2 {wU|4;&a?q/1wb#xAzr?eIENDB`PKFQtjv-Pictures/10000000000000150000001C830DF90A.pngPNG  IHDRzc sRGBgAMA a cHRMz&u0`:pQ< pHYs oytEXtSoftwarePaint.NET v3.36%:IDAT8Oc"O H3X>4>'`I@!~j4O>J~[/ȔIENDB`PKF%c-Pictures/10000000000001F5000001F56A71D99E.pngPNG  IHDR`*F pHYs  tIME UA" IDATxwuUr-WH,RD)2ęF 5<%N2RX-v$E*Id'%$AH{xeV'y2Q =ڨ`1G`1c,ߍ1Xc|7cn1c,ߍ1Xc|7cn1c,ߍ1Xc|7cn1c1Xc|7cn1c1wc|7cn1c1wc1cn1c1wc1c,ߍ1c1wc1c,ߍ1Xc1wc1c,ߍ1Xc1wc1c,ߍ1Xc|7wc1c,ߍ1Xc|7cn1c,ߍ1Xc|7cn1c,ߍ1Xc|7cn1c1Xc|7cn1c1wc|7cn1c1wc1cn1c1wc1cn1c1wc1c,ߍ1c1wc1c,ߍ1Xc1wc1c,ߍ1Xc|7wc1c,ߍ1Xc|7cn1c,ߍ1|q_u?˗_zE o<}^{>;=WO>G^oF$4狙ƽ7eUi{b|z{?RӶyR9眈쎷:$IYOV/ُ;1wc]<'?xˢ#hmCfiԪR"!ͭ>t+{?Gwcr<~cOY:NT҄ӱ""ΪHΦ=[_y3cz\]ڻd.mp׿1~޳=DrLL@Y\9rxk)1FJ@T!VuHeh}ر#uxɹ,`8Q˯<̧׽uwػwc,=O/~鋜3#A3K U!1c|ZFŲv"B} W{cF y1Ш2۶I8r`wMz+\7XgT<(s.4K4jy#!n:mmJbD$FDfBիi-WJĪ^}`/<᏿`(~]UD;dRPQM rl6+1 z 1D\/xrҕ#ǎ4M3_zEgh[on85 k]p)q`ǻaoc=e܋_?w-MϿtч\~厷KXo#O‡>އ" JA CF,w$viT1ɸDLS^TM/*x1EY/+'kì(8s"̊r8:um, OӼ/M[cG<]64sOݝ]Vw}w]7?~', z1ƧyBx|X4O=ćm۞}//@@ QE R@@!re^$I !@CϫtvN {2D1OsHAU@]5W/_tRA_}gi*2[OÇ]J6Sg~}o~zh47z%JLXFO=Cǎ9z؉O؋XX'4Ï>* HD(*DD@ @HȈV$(*lQ9<ŦY/njH΅$ѥsIbf|>.qu0FE앛s_}+ˢ T bfفx:]\+5ʅ^y铧_{=TҕtjV`0쯍Fh&1սO>xTwo^~{g/JDQ DH!x/V8$c$&n51Ħyʪ Dd:h9,9' {%Zt:Ozeos}}}ͷR}3uGG?> ^z/^~uU/Z.ۦeUU;;;.Gh8=rKM !ژN'ɤm[67,SUA D@BؑsȑŮ%6FG@E#jH.RĪ^zxo%Gx\kkh}c}wIGw; Q "! D#~>8yS7U~s{p815\aPŷil6ͦ/^ꗽh8A!JTgמ}ǏW1'>x{}/ByVjTf,*cJ " YDm@QUɥ̚4sD옻MWtDN^7Fv.p(Ot^2ı@QIS/f ?0.e  ĈDn0nll&IT|}[l:M=m"Q$@ȌU 84I{@=pp[!y^B>8b"n9Fy^E(o|M61Ng|m>xA{ ['SQ>񾳏=(!8Q,a*@ ;CШD Bw(,*0 """!˓΄KI"2DĄJ>˲dDI,zQTD#f2 "-<UYfT eY-GWro~nx7˼j"(FѮ>@@ "J 1vO(P&.˲AEc1Ơmt`0Ȳ|>O&"GƢˢHiڶi}! 2OjDK^h,ߍyGz{aBpQǨ@1.ݠjHW @"cEҥVDG&Jt3'ǡ(Ӓ)Xb$t$1PC-i?o}UTE$(fPbynE5 J$*ԵJ^E+׮nlF.q_hmtС:_w TR% QQ0 "*1Zc&CjY-Ul!GaӶI]W 4%&Am{{/]!6YDEPIU@ kY]xɣY/6 zC u[*TEEU@b@M#fY*!Z,Uy'IHK(evnX,ze?q,QT#L}d皶"MӦI6O{E1(g}o5&;.֟~~>)B cT(#t g"BX@ BT@ ڵ j+]FDP]-3 *"c Oعz^=EF=$QE@_|]s/\윪3nl__,|+,͜KHEb ]^W#ǎ bZv$I@>Nm "2 +(Ĩ\62b1HUճ|G#1 29N8qi[T-8zdmm P[LŒ<Pe6Fm򑏾C'DR6K)QD&Cj vَL]PwTA]buH׋*I B붡yDθޓ(ĨiؽwY Ǐ wDQZN  !32qW`TEXEA pwL ( bDlHytn|mL9p䰺ҴrI32!Nv<)7K"$DEtgX-EU77FaQ1DMWݨ3b@"*p}H^gn>1Tt`ЍTP+g_~᥍i'i"Zd2uX-m,ˋ3c4e=ٟt[m׆hFU!mZ1Ͳ$f||,UT5m$zI 0#11 !ƨ+N$uau.eC`&G"*KvL w|TJTU]EEy zWל8FV^ rvY2']9Do}o*5 T5ʥ;;>ZjzYMTP@UKW| C 9$m뺞Yzt~v̻FOf $&Fuiy"b[CDF6GP*"+ }yD$@U-Es/pF{[?{J!Dut2"#5q 1F1&@&RUXETv,4Y,(h3.O}<%ACj{I3By^֋} 9Fv9unVc fQ4=t0{2/QV 5FN]eykOL<V @] nID. L}L8q<_T|ٶ+Ȋ,M{߆>$HXN&2_66יXB/Hm! M]x@m뫺AĶm\]UBt?Ͻ^{q>Dn/i\J]A%vUj4F *[Wf@Z5< @FFvݟgY& d 鴿1T5&% 5UҨ…4YL-TU )pmr'!Q9N\f˗/;vmk +3 "("Nӽt6Z['s:Byp7޲ S\(6wWq$u]Jsp1bEXT:&^ *mNbX=zx}}6JQbT%Ch4jϦĉk&FB@ǎ."Xc >s+jUDD1+(A;i׻5Hw@ Y5SN R BBXсcǍsn6ʲ7a$sȕo,NQ.1rD].%9 i/צ&ID:Lʣh}4R^WrQ] T8BDp8Z9v׶G9v8ϳ&ݐK PWWUE@?}%$D_|m&\ ӔbU7EXVb !Vu-^?/4R%j} Qᨩ DD_TWT{ !&ESG~_7&;S`n5t d̫hD`U ݤ @FD?n9`ā%Δ1vshi4MM"1,H"!"ff& u>[(gı$IZq@$̇<6C" `vD1t;~[:sJewя ebv;W/_]k5ZyYde}(H&dDzsg~3xk^} |,~Kg:{y/! r0 Pכ\.j4 >OO4I:1Fm EcIxbc۲í[~g{nEʲI\o88yƺW;>x'񁏼OATW A bVH@` DF$$"R$&' kDB $uQdzYOp rJ۳m}zb( d$h+7BD9bhf!sծ0r sPzlmCڷ!I1QQxms-/3&v 7ݰu!l5HHĬ{RO}g~P7կʯO>藾0~_?xSOoqLI@0~!5^vKGnPmEhX,\cfYuEei7^'-zE|o#@j|%Ğ> IDATncm7uwW؇?"*yg@&f D%`@nL9"@@Fa0"9`BF&, BPITbiFۗ&J/ M"ԕũs5&-U(G9uҁshd=,iY:&&$P(^rʋ~Dht9vĠW~TЅL~;7e6ǭ{oD(M$9w(@ (u p#HjDY "*3#p=n4O^?,WS\b0.v.we5U9mLt*ިofZ PDTM6@o^پlr>]LҜ4oB1((0a4-<4;#GGOH!*DQ&1H\mo~45qn;Gw'?s/? IJ aDͥ!t5=Q@U#** S@jZ%yB@Q B")HWijH4sY/,i/idB>';s P:B[5r7yeI2f+d>k:rK/tt6;|0K/;v,s@ *l![ï 2b4a8β} sgWGaU7o>^M23(Qmۦ/klLEBxR۶" .; t 2QC1Ȏ;|i"!o{l!+[os8Jve"DȤMxT=`L  ](1"iL1;d PVFE :s`=OWiG} RD)+B=\,rM^]5M^Ev^Y:U$frT\9\K._.z/rPnܺ;zэх_8afoP= Pվ3S?~F]jTH1sDo з/%y|oovo__OߞԧUÏpԱ !/A? KU3Α>/ vU>FiCńo:s+nT7"BH@5@TQImSKJ Dqkk}xO?:tԍ2[G$"htYs"( DR WG@D @#9"J@$HcwkVsլrSѼ*+:` ϲڢC$ uHͧv⳵71qMit0u7EdduQEb\Td&(;Pt7xe xʵi:۶ISGW@D೟g>YD|]o>ycGXQUGHӄ{@:ZmcZ8ls_~?#?@f*rgggccmbDeV,s6 1m^yt,t`R4\wtjY^_xXYo :E$J#*FՌ =BrQ]\58vXU4rҔHjA$"8r&qFF2S|Z E?A@%1je^UUJĦ 0W$IʎyV,e(lX.Gpmi|ŗt?3khmU1*j#3]}2 qǛ; b"bJUK mp ,sy*^YyO}~ ]Ra>!K"!Ng;ݽnDdƷA@@Mw gd" !c !rƮVWD&Qb>$8qK͗_߼ۺy|YLf+*dPD TAb(8 RYҝ t*G1XU#_4MwŨ$O@@QU 04:]diʜg|Q: YB&Ĉ1)ICfYsWGf~YnmnvS,:wZ=~/r̲\UDH!}pkCy|3_Wgгַޑ=x@R(O\۽7Sx衳79ELYa76~YlDx[,$H|95Y/ |+"Vp|jt=݈mf|:Z9[Ѩ(lNC[y?of16IgwѡCع+W-O glVdu '=߅HQD?~w{ޫx;sOg;?s?:2A/M64(s!v'J**VduOjf}Q5 (% uvD$* m>ܝqcO$6Jl1_VHA44̬zcn}Ug6&e t}owЩ+ 8V BH,L)3; H!cڝW:N$OPbq aDTWY7佭|SbWH岑Ii|7u im(F9v\ dܽG o{3!vGteViݝvIa@ҝw^uN]7;W%iei/;H?9{lT}ᅗ^""rQbbR@A덃D@J*ԵORʠ}H-A)i@Jf$y&5&AB)AD<9e/EmӖe7G$M9~Qj>Pp8X_@ ݥ8}:B[7n<9]*Jx>ߛtC @ym>ж!8M|Dl\.o_7QD%q݊\^{7lX; u]1Q$$a"rg-DԮqǓxEei.b&[[Y+TETq_xŽi.|K/g?{t]o~0-͟~Q>;fWZZI)&Ȅ$(( 9ETx&B &:#B룬:3DH2*2(AW'r8,]*Hnh["i؈DQAQP1v; `^,Ͳ4I!yh&yl:mzmv1Yw9G7%CyplU3AD(G_ l*\{ͯdblͮ5콿;95ުc۷oGMId F"YAh99%0KÈH$8ejh$H !ɑE$EI6w;֭霪:>TAF6_#}/=}'ǧop,4KqXԳ*PU|o?E"ݵ׿z=AD5xK 1 Ԭ5}iA$V(^ lK~x2Ţn5 :&I$J+<(d6 v("^:S~~o_EYo6;NVK}U2` 1#a4QgP϶ַýA[V:e0̉K|P}YVET18k R4 k׿ۿpr|xİ ^*>nUڕ+Z6"6:ͭG}cڱ֥Y~~io|Msdy}??>sKDʀ^%'@(! n62abe *1( *kS<[KU"żdcӴΡ ZCk3L l(!dV!P@t6|>˒EejW" jp&APDV-yi >@{{tpje4<|;[F!LfCHA(CQ&eY(D$J{{ }cm4UT)l*[nS9&w͵^kfur&TE*/)!"TDbDH1*"! AA<OG1l:γp4M' g󧏞gN2>Wi677.^b4}'fllnl~gM$It6UI\(3"JҤiI$HY L_0ëkExW4aõΆ/+|^KOhQz JJ !(BDF@" SPT k ڷ^24Lt8͈M(—޽78yyzt&Y]y~탯et}u}{gu&ɒI-*x$d~Vˋhj4*nmow}?_{oy}??XYpJJTl cP ^E-2u:r.T@|[{/gtr: yY-!6 ̦X%" "^Θg6Ftei9 cEQ [HbQ (^]ED*zH&'O>~x24Z$zzz24uMf4'|6ά3^v[$IM@*lҬ7!gO;sW ;kg_&$4-gNu^ջמ!!>WQ ATp$> -9'\(K\9|e{{{|QO=yxw8.`hiDx ._pq˹lm_3㙮s\DVhloUU%X#AE%x8(}X̏BjW>gwz?~x~?>js~H`1*:묁 4hD%ߍEb D@"4Btʪ1 (P؜Iê^ Zb mu 8O*HMD0A&)Ebk> "It4k!cc ;c*@1zdL /RZyR},M\F̧l>E׮ml7p:.EUVetGBcZ̧n2[DItcsd0'ڝ]tv~诽 B [/ * "`iQEU$U$cHE6Q: >J{Ɠl:;|vhD@a$N׹ՋWv^ (U&! ư/ǻIꆈI @.idDAA<;8DGDJ h/ /{{Q>ܿs?zlN'fsš57ݸzaj^A%Db:%р.g""@ȋp\Eaw.2ݍ͍͝ n\{n;gʪY&NB}\޿7}Wz֭y}:3"Aȫ00HeFJDq뇔P X+r9Ø~@e9$DH:nA3daHM "4dyQUIω-DbɈ{gef狲nLKW/ck HD5c?<=&F}kkYƀV٨՛Mt++=wkwON''eY_i!bR9eYLDUq !XΝL:,hO] Ȉƙ<ĥ3U)+_V8g9 "BU4Ve(5jY,,t:'o˯ܺw_ՏG/KX/9a8*"" +ʀKJ "*fL\GUUh +FBU5Wz]Iyak}s{it:VJ\2ulzvUd2'#caS\0,KZVU*X˨fIy_}PJ @>|GO>}l{g ՚-VNu ά}U%""{d}p4L'i v^\Xi뫫k;7 qI:Q $k4,e+60-UղK|6<9.e4?~thf gYj3kj\'@k1QI!,0 boĉx&a0URϞ`fLpkFmۻ|J >4jZ! y!x/RQEot.2HdTU5OE(gF$KfÇwVVWnFll$ưZcU5*!J%F[e7 *U݇A὇ssvWszĥd>3EXɱ5s|)iv}7x{YLV9"TAD! Te4e={lo>ٓhYʫ^ysl]z-!BFBHtU >h8.tXTu47Z=۹}zA@%K3Dt!uX<@G"cLyBD@ $ܻ=x=EYZoZgN.M+)]bׯ-J9~ x~W>^B* ECKû`D&T@;eˎH0!#2 &ćJ );d0'gFp-FB !P"6 10n_Nǧ~h5FQOlzo%fu++v]UHLP"et4=&SHp{޹7T6Wo޾8I(D^s |<4%Ŭh[WWDhE1OGnl\HTB~@%ÚG!áhx|rh6͍ h^QFC`f\j%ER*(h"P*C X1O/G?=PZhg H]݉j6,"`k7Q;U{^H{BUVaW 1gdv PBd x2#cP5(*x{bd3&7  10K!dyqz4OUY*925\k[5g5{"$PfRhP LlY@o1Dtt8=_ͬ9k]Yfֹ|6 NN|hVjךzi: ~? ǃcfi+AIR&2O5ƋFy:q΃ Noǻ[Npn4E|l53ǃkMoT}8/ ^7JҕK*X,;yiQ^]tl J$ E9/0绻'N3/w]vuuu4zhDhp.5l,CĄĄ (kT!Hxfg "['gˡ Y#cY3SЬB"}Pd\ 0P!DD!V|;/:ho˟x 4!c(.k+*-3{)B!ddB^J(˥XADPA$r5D@J#  aT3WM6W[H4"J26і_݆@0n8k%I=ibb;:,pDݬ6Vj6\%Is| ǃ{VjZZ#cYqn>99=G_l6V{+ɇRj|͋Ͻr݃punܼ Yn eZ>_ 73 ­ZVo˖WVWv.e*oLgtt|1v4 LOUUֲl8/ Ü?MzݮHL'ʋ/$IBL=HB0TCP$ DPF=wY&Κ^(bH"'K2KJ&I%D `R*Eǻ_k8/ꈊ$4.fQ@Lc;@1%cM֪A@** "HĪ$DH(K=@P {ZJD@`:OAzj23ZbFDBF|Fx8cdЫWQGN[)/rwA%n{emumsݪu{ͫxp48;iVnL$˲>yhmKЇi1?9.Efzjw夑E^EqƾqaXc/,Kڝ4/Yc/HaSJUCR@Ntnݺ|ҫIڹ3;d6L<ΎkkkkκK7/\h6UY$gYlƑMAZ Z+W$TH|h}#;ocY-eK;]&kĀĒ ^KP(hP8.pf)%!i~˯ׄs}/c@THEI8k8@ QQ0! HD"` ( *PX^)&B1ƺjRܟ쟌O5&, .J p,|כ+͕"|8-2ϋ|^Sf,M\e?8:^m;4K &`0E>f+Woxs2}//{;z0;4Ddۍʢgp0<Vu4,: {$@$fCWKC_3KJģp6NOO<-N{>7uT)Ilk6LakNlfDdmUE 6 Fė} DEPw{w> Ke YJk [Lj ڄ%$\|Ę?6^Eu~FZ,&x3ĨF' I.F*%ƚ'~F8D *N Ċ7TIUH*TbQfUDX: 2((XcK5!AD Ƒ!H *H+OH>x5ҴKRl2ﭵD˪ZUA  L:g}|Ou:['GGoݕJi9ƦW\shju/]^{A(} m^Zx|z|24rŝzgAc9,{dLiaL@AP-ʒ!|NZ$\][m4UU5z{g*$i* HG PD#y@ƑxU>b3s#c/t@** )!2Kj$TUϼB(f1ˬ!aZ:.HHRCNr@<@/۟Sp޿ݟ>x=hPTPCZ#Cf6FLʲĈDTKדXH) A?*s>(f9 rnIl-ZDdA F!{<;9=څu̒HDl2?=8wzJ]L^KBB$%2SZKjz&y1?:ZՕvm;;w>&ܾ}ŧ?.IRټr1'b~z|z?==9X NrL1EIU x0@ฬ͍,MK_6͝QU,ˬs jTKj R"11`(A@D-x "`Wޮ*{x}B2qΚ166_+&>DDEUcC+>^Bs/Nf .K5f I̕E!Ҁ+$2?  ~^yeP0@\LR}gV6fr Q@ ώZ"B 1j;,Ʈ TA T$@BPs_Bn6YKp.ꪈeQ·ӴfL@JM^ g6LkVP0@en^_[Ap28?zQ7=';?泟{+|?j)kmb؄JW߿4W\ r&(+"Wq /rU-xtl6nѸ|rj5[vk<QAZͲlD DJ<Jc"DX;!s8q:6WyQԔeBhN#:kUIa%d$5YU[YgEWBU 2 {xl?/M9߿ϫ~hRf&ֺxBbȂxs_+@P"%"@F%4hH#%1 9Sc{q E<3FER$x; i|\ iu6:.SEf*peQl,I9.RPX_[[}핗I$MSc !0JE (|YeQV\e$M<F[?|ׯ>5iש[_b\_e5~q+|1G8Mӭ 6E(}3&S@('NKDmnBjk %YI5EPd0?$*C(ssw޾ν;{*d ;Wiz8HeRH$q$ωYE. _a|0 Y;%Zi=1@ 4HtDs4,zU ]VrJl)ȏ|o}KPeRB5@֙ $:Ygd(`U4ą&="@O Y17Q֎Ԁ0^J`l2Ĕ7QTiTDh<^% *^aoo}?`4"- ( F XCT r2'v.loV6zgjYQZڽޥk,O6.l^} 0E("hdx8 ^9wͭN[hDVsIM@%Nb#83 ( *Ot}ν{_Gy:fJ1m?(f]-% P@A9@ )f(TS5SfS7i= 1"#@rĬ"H*Ǧ@T9N*J1kւ!Oq.'ѿ~~~>x4+c&Lgԥ|$q; B,cяMTD#V Xb ! p_wARI4-__\2 P>%<g^ IDATi& D%FDgdg ˑ/e ^[_ã77>_ $X""h@$cTe^QS_gs&A| RaC3,غxUQAA 2MG} pF˗.ZNh4uYYkDDZMD!< `-2"H_Q{ܻν_KD&azv%,q* !9H#/XvS ʊq 1ܣ`n $mԍl L*P2@*.t`D$b/Rǹs_O='lӸ̙Yc~7>us>ɟJ4@EY&ׂ(M8By`1#Il2",1'A(>1'2 !0(2!FZ .! @^[K, )X+D $\kՀc@qA mlݫkֹ/%D_|>׼|IPG5hC(a)3>|+kܽw4'iXg}H.Kx4~vwzx\j_X#3 2'gv:u:˗h86lhXcKD[2ш!<1  ٢| ~.'vftkZ h >exV^~ zXv *HAUㄜP澵\ݡƤ)$4"WHP!Bb"H#UaJΦ a6*Q_!9T2w(Arkd9M >M'Ԝ_o)$A<@0:LM ]t$Qx!$*gD{UQҘKd.UMI]D{@cؙwQLj.Z3yXi)yrD|ݸyC*ֲvjqEQ|SV_R"ګ/y/|15FtZ*?8:g>ݧO7777WW{>t׾[Z:gh< |1_Tr"(>oyDf#x"$d"%Z[jLCPhG C.٠W*!o6޽wSbf鋒 k.lGPUTB2.Q^c5X.6(hs٬Z:ٛ>cҞ, ZU Ԗ8F@DDE&f&|25Y$UTDLN&( U|z-%H^m+^g>_mwz'qY}k羠o囪x _{5_evXH&6wCa>^EUД,ݲeEhN@k,$#28@h&'9&bCڽZ~CZ l40ec> ttDUu4!D)gޘ92/=+ۦ[-L<u;O**boH|+W .>ȟ̜N̪*&^x(K|{Ǟ||%J"UU$h2"Fy<{Vs^DqY̎ ॗ^~ŗ_22$|dGLLD4"118JPD"hdL` Q$N$:Aic՝fw~g@X5383EB7M+i:}'$YCSD/&2M[Nʺ;f qhBm.,p?_ď}ؿ?<~1&W!B;Y6(lS=M1$&< DRS1!! !ӷBāT@!z, NӣLɐ']IGO-/p>g׽/?uO~p1cz饗|gW%Ye~NOz<>}/̮߸h &׮x7)Eg<+o;w|Be3%,m$"rώ1"292T0AEH1"N7<{W/K/ղ, 0YL/<}~~fִtȨfČflYTUқL5 В_8ńOƠO$a"@v0fhI;HQ~첽"ÅP1v}Vjç\h;M棶 H0a^8 g cjZUҝz\;Um؆؇۟6a}xޕw&I49OG$dDjb6Q+"Oò-I3swAJ\f~x`ϠZ*8 7wס sf6{d9$Iߛխ }#hv$F%tT1.nX,vf904,W'e#0 8x՗^}_u`eqgnAX$,CcGf鿣) gLalPC #7Ŕ<7um5D;G Au[u\Q_`b W|}dz1eW9U QLM!LjBbjV ёDcB Hs_bgo m?#ɏ>ϻ|K>K*mφm|Cj@*j'BTH$`dvI2NAäZ.fĦ6m:N%C%YnqEq^J e9沭[UϽcgѪ3ПeBh֙>fE9T4M}޽i<ﺎ]-lT}c<ǯ߾~xtL&"yO\ypBhf )̐9)ψE_Kx 3MG6g.e#$^4:ʣFlh RVҲ @x4'FF& D"9DBm[&4FB Р]̔ӑMfcHjրQVZFڗ"ƋT쌘ُa9*5"Qbeh` Q)']HlvfFشpk~XwU}]S-7,C&IDH4tCd`& I,'M.R`JHi 4 0!bGパ/3 0պj6j3j[m&=ׯ꫈.,+mכf6y,FN;&=B\MUz{ppdy1;Oγ>Ǿ;?y"9@eݣD71ZLH:" ?3+Y!xR+8EB!v="*(y$#tixi $1z1kj8pTPpfoـ$ԨȤ"Zy.>}e=YLt1]-&҅c .]u}4&UE&>hzMH3{SfͶЄ02Hj <{}0sT- ($1K)AU 7ۭ#眣mWr>_&lYw! {b¤%!1#vio=AdFI\Ԏ izpPP&r}#a /7qygVm¤gi $v'muRmg^|׮=x7ߺ{>zQl(/ڮڶ^o֡4{Ml֛o_S us,* lg{|Phb`6 5THDq_~_y嫯|+YIDhQἁC 18j64`F3$WxLӒ0a1DQD}#1 21r1.Cwn1̹]`⺭аvfݠPUo<b\-^|Ցt]+(@M>jheh1:9C_NV2+b!̘0{FTG -ň d$ Uh`h$n4 NRbJ@ 4!"4_~{a}x _nb+*f oۦ')RJ@F`1$K WT(`"r>**!8?fL:=.qzn<9,wDf.mxoc[/oȝ[wU q*VMUecvdg"o x2YovlΦla1clV 4!9{h4hLͨ06"*rBLo_ƌdb^θ:Y;E-k-r#5v 2PDbeI?)?IEEd2ׯq|gS] 햔)tqpr>gbAB03PmtݢP# lR2vyeFK%yh0+s.} 11 TbL}@4@ !mF JILo*>uFQuuL@IZR'2 D0$!ޑ4LAHCh+YAX2tZ (9@ģ۫I-!όf̼ ծWg/[Tߪ[|Sϴ}+|2}u:WI9q<.3޾~'u:Lժ|GTmooѴM3 fA}}4HTgqH1^| _zW_}_)EN)g:on,CA-IQ9 ̚`ͦev31EF|g(GQy  5|^}Cofn0ENcq @%Jl~#6I-sCN Hӽv'}w d*ˆv2̦9cHf@hd}T+޳!a0ceCj >&UЩ&BEdp78j7>MT5JA,lޘ<"1@T2A$Obц&s&[)a\0/`ePe!a K~bJ˅@dC*]V'MCS<{vl^W {E,gzɧ9wwF {;i_#5L.3_q}Et|sYM9D,}E(!m{쁙) Q$ |@xW}QNF}QBZbҷm`1Y0BEVھ^DJ/YaFH4Տ\߅[P1OD Q}|G>u$ # JZ zcj%"5N^ |m4OL5|^2oC-:,!"S ]cRhY 66"Hj1*CV>H 9ﺶQ Z2DgI響@(b3c?_33b13) jn2Yh3`@LnP."rD&K(DP&:^gCG/4p!f CƧ*|32ФǓ;BS^|+}կﭛmSdwZ~dwj:Y/d2~|՗-bd`0`IT]ZzЗ%i(xG] EC#}9.x33N&ȇʼ{`E(rӺ(EQusMl%D=إj8.n]mۦbED;}3z@HbZrVsYP|F)y+*!E|>HJ6PrhIT4CZ8eYhS$/U -"1Fڞ]C@#4RPU$F@v,Q f J!tm;=45(GrΟጽ,A1O"ٙEɀ H ƨj$A0[/Ifa4pN[LL@8ߟtRmk9$PhC6A"dLiONSK0i2D# `&!U+&./rrԬbTN $0U@0zC3CP0co3}9*,x< Uݮ)tg Ř #~g_KMx'8O>DT5u/G;BrGN3L HϔC$4f`@Udv$ȨXPi*f=b/pqq>ZQz=um:w뇿9 $`w޼s~xBׇ̀7u8L㻾mwue1gsG/v}nww;wncf}O~ ޮ7mleQ1=7Iq`HCÊ:#vm6\433 .f1rZ>Sjb!T5DInjȺã"Df䢙%Ԣ]]MVhoDPCܮS+e^Gh2DN.12 }O@"^DSM<"%YWmU.b\dyf`AD!^EcFDEƳf^We=0>=?xw\N@iıoN擱RN˨.ĜTj~On4U9ZA8 Ha4Ĕ o??<2RbEt>ڎ/s0vɽtڌ'X02ävO4|73 1D"jb5!%*i=s6*:nmMݷbg$4XY5? ǟs2oj*B/߻sg4*q{t|zYG/޹wlEQyvnmS'KDWg|o7ދ_yKn^Wb9)?CDQDMIdb>HP!p,e"Iq>\^X^cf8'$䲤HQh "hhd8 B]TU%Vba}|AD90,λ fy_h}eC6P@!6T'44ELsWl6'oǹ˲92*:mi9c[][bRNƓ m4,| *ԩ dZk.ȟ{v{Y /RV0shf_|K?gg߳l>+޳6 ږ2ynhHh*ISNv' L$TD*j&fPE%-=AI,]S͓n`er=L&SD}Ǹ|gO qwoqf}[?OmblL'UēF{^m4dxtxkHߗŗ¥+^}K/kŗn^!C4KSJl 5K1""(KNyH{ ]^XƢ@ZTR[-hL;F6HSvQeTȎ 0Q>*Ѩ@5`,fޑ@8`I0%+H&R)IDԾx ?7~gƻ~ر,J8*lB1!F<^DS3B CVGҸq )3U'9r2>Ďbzx^s3jug]O\O}|woϻ; QNn/fM۳g]8?3Q6/t2ߟEQ6P2Uۺզvv\M}¹>ko͍oD,9<{f"R Bj$ПJ?U'K{I$t1Sr fhTzDbD 1AD32]Yu]Cg]ՓtߎNj1;O lY]b%)2yl(3r0pDIf)"] 9g(ăa Kj }TK@ cCoUA9Gn<gy?ڨ>Jo= * !I ^MH`txh6gal25!?wϿ<ϿϚ#RC q@{L LLU"`Ja Δ<>@TbKBU)$:'=Dz 8 )]k`Ȍ#fr9~2L.\:Mx~gkb]lW%dOkQe|M;{?g¯Wܹ;Ξ=TDMU7d^zt|t|z뭶Ȼٛ|g@&%)43qĆp2LL!!"JzG&Q 1j+G O_Xn|NyY#fTB0J?~Ͻ?ϿF jC7 \fyϛ|<3_Fa Θ#GL葙7E晡T0P0CӐ& d@BS314V Cٽ7OnDd3=7gc=L'ӳc> N.}ٓ;;s|}\pkC4oL z;Y/W뺪F/7AU$jy}r}'|}hjB1HhtgR|峂\gs"eED*{~[W0 K=9tBH+I0S  6}/Tb[UU[MaJz S5M6M4rl ]2cSbo df9ݰ(Q?1&  l9 #1 @ c4jSYumc@M,5zZNuf96]89^:g.]Ll[o\vÀ/>z~6[mmfLd<ȹt}]|>V}05W_6{w:xuYWզƎ=y.sީSf17\>3TMS$BH@L6-wj}fn#;bBbb'#+'eo#FdAcB~Dd 0h C T̀Lqt@=&m޺C n+s@{#!U  X$pgv)5T Ie* B$U`g@]1]Y^,Wۓ&lgՌF ~Ouy5|/ޖr? y3ctFpC D<EC0!w`ti *`R. 4R&96hj>9\!b> cW2:$__ey{|B۶uKo_|9͏ޯs4nl^|n{}pq6mvxȹ :ƍ7nٓ\+Wݺqڭmmߺؕ,iU-_yǾ Jn}_|8^H9'ã[o:Yڛ||x,2Q 8@b[!.9dE9 Sy OhC`U[gnz"ŒNFD0;pCl**`_XJ 4ؔeѨ $ #Μϲ{̜GFN^-&6 r( RQn5V6Ҵ]CK0 &lOw`31cy 9S0A(b& )DQʌ'j~GIՄ&hNS77"!aF&ѐXkTTB&2]ޭ񱛸8+!dǟK_3ևvYp Ω0Xd8s֢-mg{# \2")T-#@DR2 :zD RQ$)|ew|mڀ;,'F y0/^~ MSNknMgsrz| K. 2"ܽ}"zܙ,ݺ~;1vU6uݷMvWUt~0gGy* ʂsOfH`f>j hbƀψI+`@8iXM`ij# q2YC#G"J!F۝~+h2y;LRF(ڏ?VJnX! ;vĄOB<`n"ф$` b$a&^,wg[mTdo@@??<_ZP84:'{%݋ Fu)0"S2,9\̆t5Bԟ :߱FE3nVW۹0s{QPk_mh˲lzMv.xrrr]q;}\lg/]h Ow瀰ZGΝQ/ =fYv>%H}O}PP ƾmo޼sөh3Gd""X[¬\y,pTu?KmCcQ|i4ugɹ|4ago.G &d M@Fb(!``Q'iL6A4AEC4`7UI[mC@`W9cM'g.EĈbbhX5 c6T$bDR꺙Z# Y<,BB66 Ȁ N)L,gU\iff ʓOaL ;/?{isw}ajETe^䀊 S??<yr 3h)(xs1޻l^}Z=wƞ| SbS ?hTJ(j JSR;` ҈(64* gLll͌gOa{?~!ug{4yg?k{][LSXF9II"f]؍^K6$Pq'W-]e7/v}<KNv;LJtck76vvvNNN=zݔ|6 ;7WUbW?xZt18|t]g?8<9\̗Z張5NL՗{ ܌D\@M9==UkGAhQL2\ޅώOO>O?+h}ij7kn墴-ޫvGRNƴvs eR R?LVbIޯ?"EÒ$9kx\[W'm]xăN9 ךՍyp~ҡd;9_^\DF7nA,_vm41=:>ypK4_VU5N㳛ׯ\v{mD]6ϊ䮪ן."1 kW"mimQWdYqZB"[8c &>9>yO\`/f h1}󷻮35r'a+Z2׆֙0B\KdŐ@nnv'V+!SջÓJ)mK-+:bj<*7h{.ZNlsngmT يuI",b&`Vr{/ݽBRɅDZ"6LLT]CLI OH1PSI ܂Vfv'ZiiٌAF$3s= /a~-O=<;~t|伽7nj~?w\_w;]y^A=Jհ҂7= H4%&2x@@DǦ3 IDAT8+{=ozqc$//rfid>)?<)^7pvmo}}ͦc"ݍ݃,JF[{G'gdٖbRNY.޵|ZGdl 2_,U %Gj,:oRΫ}10][ m jv i*3eYVT3D91^I$:2Í4$'\:mɂOdZpt.̔hɝuB9@JҺp:;=`N/qo _ZiS M{9 Vb UB#mffg0X\'sx]%L]W|= lNNg. FO^S?CIVug99C:HBfαtrs(^U'k*8)R,` Rphˣt]'i(7YRt~t\dxqp15 5D_zʼna3|߮Xsp4^^T۽a=zGk;Ft_^\kvup6U/VjJ9KIbNX_sҋ/|?[/^}^d0TM%`5#P9?>zte6{Ӻ%y03)Y)]YֹrŅ;Tȓ pNlFN`BHrUĒ9AFQ+{3X,k< V/.g׿A(:4.0BXF0SKr'%~ g:`7Y37qd—iG$(P/T RdzVP$[= 5m3;9>Xۣdtz貽tGjTOx}u&cI;N㵦,,U|r|9J!! DdN0%g c09A~yb./A'/.7am}=]ש /~7n03sz7oL?}'f 5kgtۻ.w$qF ,wLDER檳ν(Q1l3} _ݾwyMOw 9U凧+ Y;jX݈Dupb"/jD(gr8iD%XhǜAT(AFXU;`6$s"2ei3UN6tf6 3ybbag1qǍq\%J$nޣS@BN|NanD!%U( KDD,F,D,,(1vk歍w's xcӯ/?C?~Ͽ7RÕĄttoPP]եG'&XRnȩIc/H(&U24."B `rEkO IdmE+^۝Mj¼<]jg_oFsK"弳ټ.l~css}}ݧ[vrk}4ieϾs% <{5g&aN^ DDR%uSgO?_ Xirm:Y @BDNRL/=H^O]zȽ\bFu~tp*;b׆RڨnyW2o?]ήVm,d0AdŖ+gAR2եC94y^A{{?9`Ec]iʲεm'Ju ž͘E$Sl3)95s#X2G?(iq9'Ssw] ̬<=v_.#wf1/*3%8:wAf{Q2L LpV("ٙKp h&"lԆ'{?zOO*]TUݾŠs,77NNۿus{̴h>.y#" +"mJ N^||3/hQbBBTk6`0͉Eˇ/;?;J')ҕ$`m0d|#*#NU66wsg!d0Ue"&*p+.Y@<a Gz33Sv${`^vA9pgDqD gbIRr *3 zգˆ=3}{i(`ܻxc@^s2rcnlxh(->yeȐ%w>@F2yf0rE+vBrvpQjy>_]b Ns25/^Tۋvi3%mė khծ) "36C,\C~xs#"w01g$ͬ,`;S\ %,NI5\ۓN$$^mvo\1=_,駟i.\Jj۶m.g`n|[/p4 '3o]RL-"DҊlE"V )sR nPnw?g?{ޣW!# n ún|%AaFyy 077jD.,˶ẃ0Kʙ@ 8'gw A;v&`nS X cDX/qv8_,lYoSJp_;>:z@M0x<SooN])$!ǝw/<6Ϋw7}@B Lp"+^K`w6h _H6gf$_G^^II3U !s|ܭt:ݚ7Rp4$lpa. ASPt91'*:sȊd6Q/&L`f]93uBHg!J^[/080jDŊ$be9@fZ3SYdPsBb IՉRd-1Qϭ'+_581CH,l̋c$H&q+W+]) Lb3 B apQŅ h}o}9J){ӿo)9Vs]}=  3Μb"}ڤ#^YK1{!gc _7La Xx:}8;%Q"&wL$xQ&Q(fn.qkg`[ԕ"w'*Wd}2=wxrt7g?~k]RNGy7,UxN65dlX`a[{G_ÏͦF,vhgLϏ糓iQ3Rn ;}^JWOdk8Fķ7޸+*&|O?g>HAW7~W˲ڜN&)b>̉ƣqwGooNHVٛ)%ub}䃁 ԶRTbRSa_}]PIGɠ4"rKu9_~xxhqxڵ͍Ds6c.@gdxO =YvBWWER߫9ihaSPjjUJvf%N Il_88%JBR4p~*-DSٙULTIa&;:JD 7J@`=E9TIbU+P=dK" ıX,;E D䰶,Qp.7nmՓ/}=_Z)ZzX$$K4٬U4ɝ#?Qb%[($F+ 3݂(#w\U!A^l,燋E;+y [O&eqEieW`74GAU/ew]/_م O ǃ:W-'ӮuDԤ9e>;9ztpoGn[AnnVޕb\R:8Ex2ؼ?_|RhM=ͨ *l~xeUU1+K)*YT֞X۸T 9cSgΝE ʫ,!PQw *{Μ\U G(j#s#$sRADV/_,N"IHFfjМ2V@ fZVS~Z؛Eݡ! jYalŝz =I6SL,Q)W]TNj0X%8'01IELFEA$̑JpN D+jB%doJ긋?Goؼ_3_y'~_Nt2Ϛa5Xsv  RCgi9=$²a93a53XЯR`ƀ`x*tz3OӭIr|SO=uk4᤹x}_eqy?`0 ]x1ӣӝ흵d2nܽwxzoRDܚP[)_^^(I:<<N;-jGGeYJ(ξ} O|^]Q [iu5Djl{GBFl6hdLGۃJgG/<T8Xo4)7m/.^y>7k/_Z3\P0F8X1JY_2$ "!wV,0/ 3w*3,"eXdK )Z{33,2Kf'B.M@rZjԜ G"_p6v84@DN,#vHӊpyDc8X"9 `%@9 IDAT. rn D ,qPXV[\&h8?{?7+o{ܹ"ѽSr7U%DeD!z`wi/#e?^jL2FËN]ϭ5w*Զ][9R;'t4ܭ'ۃQ=}Qݿ׾k˺jr]AR}/~׿Et?gW2HTUUy|10_Mo/K1a^\.T[zX&wˮG^VW׾2YH<뺩8/+HԇO.n)$IYtĴp:u]5yTK$/5i];%PNCcgbh? 1XQ4W7 1[! C&eV iM[7Re9嘅{NK3G1EQU"ɹ<^INj&ڊ %q6@a63늺 s?s բ1H$UE!0.5!Q/u T qU5ꊙ8) l^$%13` :,hD4/unuo<ֿnV'㶒mAKUSy@.xy~ܛEz*G-0"ᔥJ$'&+^wN\eltƤ$m×Gdg05*FDDӽw?nUFQ3l/{ҿ_^7?WEigi9 ?G$fuSVRE=wΏ L#?o- ;nnfn[u+/qмۊ[1s(vbvgghc脫އ0&o߽*|MKoaI,@fݬ\-/cHs+Z;oJ%¢=|adc87q f45 /|7>?~mo{s]c\9O./N]EtUU/3YɝZ֣@)%Μr ,bGzU g''U^Of\\!1E&*&勳ˇwuvٚZ3ivjê@6x ڎ!9 9sr@fpSUe&IlkBf )buuNˏf>/? ^LSiFxUfɒC#$ 0SUw h)$r:)K޾w6U:$k-Ƅpܘ5>f} x|#Z/ N4ײtN=LBNnΜ(]1\:ୁ!.&ILNzʠ 35zH<,9 #G[^?,ޏ"#YTlWi(oKBd#ŕ7yLUY>mf= wgG38ql3~j*p[`qZ=/os.)M5 *Eҝ_OSO=Y Ms>=hꃃ㧟LalI;.|іކӵkٹI"7af:)-݅"V(Qӓӳ?Y4kƍp?1nX`(2F IB`Y1S'#\LA'}",HJIx3w[.l9 /z)7IlnW}z}0 C7Sd5O}U vv_/g?37n\Gz7w_9ӓӓӳ'Ĵ-Pm,UJ)I"-el*v`Dr1?_>7O ƃ*gg Ӈ/m[`[vM6ןؚ* fb3SJ1-y5$Jn0 IW:Uu9!u_)[B ,)u1n=n=V#$%gS pJ`2&$vmfxm 1 t `z>d]k}TՂj!7-̀ b *Eq(OYANœg,Ŭ-Piq%&\`UJ&+ŭՔ >ǀKꎺ~M潪/#w>ؼ?PMе_^LoUuGЛW V_!lڋ0SS; LX"ؽH}rg/BSJ{5MO LWup9NGW?5o5͐ڥ߽o7de/CN;eYrvݭDit:(DH1#BS|@$00q, NgGwhg86U*$(&N%|Qu]'a[uSsLj-)Sf[.m Bȱ)M ncrbba`& vHP(ZoͭεRhu#Dԃancb32)2)&qHشܴ$V^8X!"$MH22#p3wn%bED0=N ,EM %{*#h |!'+“s7}#ulG@"P&JD+NP|8@o7nj;"SFW_ԊiptI] n"'pwi$eRܻy3U3U&v}8ܬN+y[K]TW<zS2ywpmp4 q"ɜ 7m&I/|xS_?3?}|tp TfXW&'͍[\UU\].\{kL6qQwZ-_ٿ8*듵'P Y]*I¼gqJ33bkvS"BhQ3n"")UMεrY1LczLR5'8+j}q1:$"Eˢd=2WW,Wx4Qp'PZdHJRʪt@f{AAJd-Z,BTIpAE.L\MX:$$"AZ1Ww57@9~WQFUuL!PcKY8v|qI7ow/~~<=@R/OنAJBDh`7`\ABv%z,S gwky߽:3p8!%%ErJĨp/@PN ܢZA2_ ߒ$q$nq[AIl7I$E77q8s>{}Z.l?pYZDTW(cANfG]5>T3q~mg8:m]o>0GYA@+O~1hƒ/^S~o. j  sMfbAGww~>/vr&XoZYb'Y4BD΀E悓[Z- Bڸvam4\5 #BJڿzpEUNMmՎwmC5 JAɂ5ͨi ܛ@*qXM%7B)FbVb>Aw'$13֗1spEWhk[Maè/lGx ۣ  D`vgwFoō<Q&j$5ᔌnOu0S]Hd _LÛ{G7\[$| ?oqJ F^ƋΏؿgݯ_Oo=Ɛ!'iR6A9q:Y:Lt7j }Y+H%ԦS,r{7yW^`}8&H1qΙ}/}Ua{$Fí  1%"1hLYػ3sTͨ,m5ɍ?73dw# D"v H}s^K2z#"XLN.VF\0 X1P$R暴mawU2$c㛬 ` u&Dr!bJ>^'I & aQXȏDDw [ Eћvˏ@D{[p7 o{0VzTRkw8{L}fׇmP3MY~rO>W?KB`$B|z29;9۸F90%@Ї{+,"Ԯ&(]OFGwj8j;OѰi:sFL7޸?џ/ٯ>x+1}fTM}|LBC)Ajj:"jE,99L.9A{܌ <[mv Z]bI"m;Xqx<ʣq;,f@d0VmU]MD`3<{FQ9Vsf0\UMa`aػˉ"qj)G[ R#\5̼F|C*ח-,:or#ݝ,܍AQ=.DmnP^eC/w+ XR͜$@X=`|TS"o;j=8GbURhi08Q;b%BWDYҰ[oZg쑙$}$i拽Y`sskv_45~}#,)vv:曆Rt`HLxDQ̖N!̲&qiM'7?ٝߜ{%-Y߹$촜ܙ<ݾ:޹>lrf& 8`O\emYcALU[6̕At盷v_ Zn&& `6[ґRjM٬9%6=.3A2{] LYw0j r(;Wl }UPk/$9<ʫr9|zGr9Iw&XX$ ,pbR S"#ohQJGȊ2-)XP! )ь:rrEJF(1' gb]`0<'rHǠnsgJIGZaf5~UIqBp, !UEj+A(H,U`'7N3Q ΆhHHl$_>P̲S0g)Kؠ$:8;yuy, X H/M`"UD;Y(A.'}WѹG)gNN{эA$D~ߥOM;ZFd^nOA%SBYlҾԶ)O bue0l*K/8QŬ:x]h-TL5HXBn:s1ؼ4FQ3Ggkzxzp_tEKuP pyUV1Tn" ¥+''n9B@(2@2=!)8;1ZH*KNKky *|]\x1iD-L1V RrvRǂlldN!u8 bTNaaB)}1)%JJIHA)Hr:r&(\D( D]fpwy̸XuxdnrԷjLd&x b{.jzə]-rR#wۺeƠtt+1WM!iNgyG29$vO~a3dJ0}@pX88p ޅ7OL[7Tju{tضۏ2=)g[y\ X|k?X QpK_|w_{kb`N)A`t2s_mjrzqϿ|@%;@h)V2sJONJ)qn4[FkѰx\I(%IdrA_ٲb2k2lm۶M5bOp23ѢtRD|Ļef.JĠ.@D$_”&s<;/B>cj;iR,3]'G(lBVtQG{ቍκ9*%eD'6+ֳeNNJ)&HŌȳd% d7D#8cEL1PxlyqИ9 Wdl$Ѫ9kJxpz81m6/t~x>>jSf_xG?JAnIg?Ooz7տ{NNmr"'bޭ 'h_Ro} Kڣ' IDATP  DR7"gKb7NfG~G羶x{rf 4)[LJ}Wl ׅ 6w6An-I$f 'YQq7q8ͅā[ "-]S{`<~/cUCUUUcvOF9kL*qeuq vJ*rAVPMD*\MC+pJ [`jUKvs뫙Ȫ0,'qp-Jœs-HRQִ?Ko&͎zh8A=:9NL^z-?{"$sL泙PJXCso>מ:")AXX0o 7Sc&b<9;u..zl5IBo{ emk~niCߒ )2 Ff91U8J$% ~V4nw$)yUk2oDD!KO9 &Q CU|^f Y8"Lu#gcbRC%r!/Z%bCsj_LQ|9wsGĩX\(.f`F^@=[#89YwŖQV!NWcNB0y]nVsH3fMӜMi1ߝ6Fd~&ÐY#_?U*@ b\%IdZd8=t?m߽߯_SW!U<)XjlsW\y)h˜A͖h>] Knl6mb8p;q"$a3FԚaa^OO~ů}}{oJ-Z$e@Y )=gY2BMTq.[P13Oxz<g8[Nzwo-9>;.}o9^>^mۦ"!~a!R-L0Y-r3pkTSj}mڮ(-8\ (bwDkR3)&IBN™ə"$GI{Kb 'q6&HJDjpa)4yIRM a]#KVW7u~10P;W=]B?lj%3vˆڀظyU<:UՕ&,pxIz  ֶ `h$V1k @%#ټX_#,! nv=Uޯ0s\NL}Zjb u2t:\BD99 j Ijܱ-ALo^6Ir?;ݟMfuޝyZR)|}Dtm^ʃmbz֭\/޸g>Y[[/$ЃC 1앷^~/3'652;6XX/ǟI'{'Nf'Zy=mԀs ^aK"8c;>>IJ^{),qKP%c;AJ@\ xY`mەx0hYxͩ!*})VB_+ֹs43;1040*Ay3N" P"a(V$IBhQ6e "lUC!n tz_\T1qZ җ(`ƕy%8HNU46SG<)L1?gvl38- $5MbذgBS5cý7^xýL ~GSˉS{X7Pm^.f&UPĽK.C#,- wQP' t:k w_o_7tf{gQD,δmmΚ\$3?}ngg{{'IRҗ ؙW_yyQ( =V/:kܬZ;h2VZ8AӚ/LbѰV[?#1:ы9YO%bΐ_+;ioEz7j]xWG3nѥ!\XN=ATJ$D6m_^{aut A,W_ȬhuvF?##Ӑ$eϥtp%#+!,Ùjn ݭX8f)94lnwv j%kX^C^Io6IHm0;;ch;ƈl}mgmޝU?9%&}!h&3@z9/~{Xxݯ;Ux!9کZnR7ͧF%1Y]tp2";o;W]]ob>p6NGS3}4hؕ'{Ig+-߶Ҿ~36>$5KMtс"~^.ט1|o>/i f$=m;%Nc.f9399Χy p= 0C;#FyGi? B73dJ FҤH싾*!22b .I`8z 暲@ BK&B0JmVMo+Y U5nZVw*<1DО sc!v/D(N^>EjYR"A+)H]''p<7ng?D [3|?h}1&;<_K_Wo<jsb=Zd@YL}rpz?y m ;`ܛ H(妛Ӄ|$p6 AZFZW\\鲚 p!0BI"UBCsl~N %9%#K<3A0ž&%eK˓)49 Sfa&5&r*f%-  glP->yn~$&DF`C%b:E* lQ{ẞIxxrhQVZ| pνӃ#hz4k&%YX˗p}K:ccR*$2t99sbk9N^UY'zꓟN,SMg3xd!%7 ,^O}C8@,2'#8݆CvJ&w&+W~KB|:=;><>L釞konGg_?T ;* `)th|[_G[c3CFJ퀹(Nff9v>?M'Oj~~m(7 #G9pOIJ i47Uki1 \ L N@K1W[gX{2#%vE5,c8H͜5p5+eQK rP:&>#4S$|*0 l (U--PR@"PF*s/DbU( ([NJ.NĤTJ0=-UOMn(|=FLb*5JɤN~E-@bVܙYB^m 0rDS]RB,=4=p]94H;`M$#I /jpb'cTMjzZP Yjlp$`pդǟ?O~+x|_?+p`2kqN8XZ|ً`շ&Ez%f=>$㽳'^xgܟ3eS \lf}oc=~ndI''d2}/8;;]wz&kQii= dՂS5+}R#`!e$[\}E[M#TܴӒRmԩGM%!}R$M+f"’jJ\-3Sr+~@%0җS7PҸ XVpcI{-Je!B 6p_—iщxIp9"Mc]PspVTʐTrphs4օFqgZdz/j~PXyU-C",ĐӻNv.yL7Nd6kׯ޺y.۹p.?Ek "N'{i/^x?<nq&ŝļ'g)I 湗?o~ pw5y #a7uhދrΪJ<$/yr |_;I+IY_߿KsD;؝iٗj*gdNQrutRkzՑ[C!BA$gNjbsnlI7yk{?/~ˋ굇K ob1{wNmч;fCx^x6x5hR6i49=SpMXW^Bݑ-K!ֺ/f8 wYfduI|pȓ_4>9ޛgsS5F/y? ^08#rUg"JE@wAVUݠĨ֛P=%2w)T륮-27hul32} 8hQKFB&qeU5sqRbb0̬9@&ꦧ*erY(f\:^{}T=xͬa̭ݘр;!r!',) cf5+bO5 )TV ,qN9OSR>?NO#8]<<5 /prr'*ityspk(駞 a?"Y~U{[h{{Žr"n' /XHbFAJ!ٴ֤z\o)|z0tG?r+\|W__yWi),{҅fٗ𬛝-}G>r||jP7ꫯ^{{wG|>ICY85WWpkWk|GLnLY)s;y:;[M3Ӛl\Aɬ8RΠdϵ+}R{բ$s&95\'џeD9RNR:Jc[1o9^FV37W*wW3wM✒F+j)J4 swb=TEyJ\; b_~p]A 8G{$Lk{滠Ű{gJ244=9Ѿp?'IXp;g^|&6#3uDj\LU:7OoOjEip9f6R0ߘLojת[۶]p) }{3P9b%B9G ׈bH&O5k¡Em02 Ɏd>AZNLff !*֨-uC 8ՠ )Ii0VcaA:8@ үiE,$a\0XGJ1&kyK[ ({@=.)ifD:R>u2izo|<sUv(Dsatf΋nl6<Ǘ2lAYrit~Xt /nj Bݏbks9wqc4–*6g8о6LPBi8Ϧis Styu '$'D$3k)jn qL)(;EX"FdaD,c>,"᮪䒈|ɪH^]d3زAU)1sPׄ 5r&xpr/YX J9&/j<ENJقON,MN~qE)Ή(WP \V+HD!"NJsܽ-Gc_߿_vDP&-]zzvs \dBhN _ܝ5 "މNtRF3nunʵ~#?pڵӢEa;ѹ+PH,rk;׮\r#{{[ݦi]}XZr}g^s P1;_fwT!7#G1 D;/܇+ Yڸ89'IʙݜNZJ,2xn䜈$ aw\#{bb'̬ S71(~^NY{gV]Ib©ܝ$Bn+QcQY}MҚx x%q',%!rv^ ub8Y!*0s''n|vMFQw9%4U$)%hx2RGN;}箿7gO=Qc8NۃHm/a$&Dvqqr~P:2mXbrDP/Xxߝܞ+Mi{Gȇ~. vݼz.}nRX vcgJ!X0ķww]x$E23O>~'wҋ z[@I}KϿL\, ( %(`98 3gΓ;/NH;z4{Pp27Y'w&y7f?ytP`]QƗF aN"|sbLT@ %NfG;"*p0# )&0QIW;HD9a08Ac"6"17wqmF]9%-}Rbڻ'!B*NaG¤VŸL.7MU5J<"Qr R@#TrU {Cs 6[$U;ԭ;+W>DĤV '{ׯ-%>L jP)4-uZVm`S.5YL&ȁ\wbז55UZ/hXCH94mI*0˚o=92ՙj:~K74Lq4"a٬bOq4(LpKLd?͑"(UH]PZr5JcIiJ3k4H-HSJ)%IL]忋Գ @7` .EcGkG ڝŲ?'b$qsW`a'QUD'NJ{Dt5k^Ы/>|ݾzJ $O6ZjV2MѰUU%^opzrOn|fwgupUu~p߿/#J>nHs:Ź+s@ƨAJ3qyim76oG\Ւ T͎Wr\-XLDHrR%2Wek?e|'?fN)i[bWrcaffb4p7op n.9I#i} |JRzr:/XJ _yiX$W]ɩ2/_i"s奬J>"{vv뺿u2;۷ɽHRf&{u$fw57r홫e/lVM Q@dYplD2z]IҸјT|1]4F"֤30MGޖF GjL`a)E pzڵ$rkq'oEwauZڻDfܣVpBR*Fn" 0DIQk%bɼ9L8XDDRר;H$;ih:NMұ4½&qH }s B[&N̪bϊxHJ{H| oW $Żc"KP{͔[_jODᧇ'I.cBؚ?7i}wS+w!-=K֖ul.,Dѧ[S?M^lz,獛+}aιhŲQXNЮv+m^~կ}>Oz˗^B`✪f]0omoUP 5\ۿrO.}ݮڢE,V'.M|"n;EEmڪWN/¿Z1/S{ eM\oS#L 楋֑swմm3Ww߿vK|_ܳz`r>~c1lKq!5L@ܔA)^7̾h8 GUu{vUD@TKnnm1l^d><:8nONAo6QY*%L;ݽwÏV]KÝƐ lV->Ojt?qOnKY 6+(S63uwfN,LefD)mSa\em- 9ڢHĴpr[ A"DڢAWJ`274V%ݶ5zL "=>Y#S˙Nld|^x~k{>?.?{}`ؿߖFRH V-f%ds;@\̥ߚI?޽Ry4ت^(@%.ltx1K[viPToI:)|]q3 =iS ZV>U>Sr<TIs3A]:3D1"xؠZ|ztݟK_˯|?;;`ssj[+$^Խ_9W"Bv.ꌣo}onߺ} z[`0?:xxxg]Ѩ\Ջu;|w?03|ƕ~K[$ B$jΐKue0KBb3ӣ|2b%Гu)ROVlWG)13 ZiYѝŬx4W2aNʞ n\܊7@a*7*|mX$ +C]:U^ȕ<*w7"%K,;;K5S+]CƎ^6\"X]IezjȔ y{\Rf Ӓ]󨶥JhRڲX,oJv~>yͷ.\gU wNϟYf~>~4.]m=׊jl 8HX0}6LmӘCXRU7Oş&N6C@Fz$YRL7;^ޛ '?ᥪVOU_L)GtM*\; : ~1.mjUDHJ]^uBbd6te#}g*m;4p׾WG<.^5 ^=Š$$9Hrz/fwoa'!?^GW^U $kx0P1.K[ rǹGl1(VSMp3$QekHAPZ%b'&rc]2D]"8Hj$7P43Ybs8!IԶcԾNb$a3w,tjL+TEsŔ#4[pbxH33ݹ@fլVD~vpÇί^m8TQ9І"f5U^u},n {;~g  [6V0ږvbRRq(݊GYc-S܈~?Wrkx˃?ɽ$ޞ+b"Di٢/g[[Ƶ߻w7)K IDATw޸}ҔϿٍ(IZ6+8dIDX,_|9mJ{rzjW''wLӽK{ R{ZRO9ӳO;J?UXuU'Od̛8ܽrԝ)|^nm>R&("cƐK%*ޑ8o%jRDIUz"!ruS[sDHlA`7^?B)_wl1Z s =ԶL@pDЭL:=$HH88|"bEUL$*u3WF>/lf| d<˕h^hɷ?X֗f z TDuIOrhuR#^i,JOXJ#aO'&*R%Y`f ʈY@s zjֶD1ܝXD1j LPLԼu Y 6L$skM`+$v`*1U3`ssOFzI rx4 Hyv@%%&6eqzۨO&1eI;%3kچs:|tZ66(Q5ҺjL$u*yL5$'f\"y$ldlY:|~ͻGGNkxڰ?=w`fmmM0 /iIJX.ɬ,Zd@"|>[.W`X RT 1 13qm2qBo9=g㣣'n~v>1O?~'?=/~տUWRUxx٬,11wCJZ<č8(tDH$UɭɃ?6$ݻT5rU"&OQW_IؘdEċh粤bvDkN'%S242W%RnܕHZ'f,(m$ɢ48ZHմD˼үycJZV-'s]lU s+“D$f'%1VeTY89U09D t~|<:Y6=`n_Z֟ENr"ՕzYl<_kpԣAŚE.E/K/nݾ+օ0>vwY[BX(JwD[ʨ[tpxɃzvj_?RaLȔDR LtZJ`-Ƅf$F۽]8^!N _Yב\mH?""j~j6Aw-:^orUAcNkuRp$fw5$b䭶œ:h IKfɳB=!~2: `섃 #IQ0^:90`*8  q@wq$"Zp6 uMbY6hq\,eeU?U߹j\ǽڡ@g)ߺL{ed)E̝0s,I`h+ITn@>@)t<<]nV?P6f"ESb\elKSmVMMpVvww>_~Z{Rd1ǓҥH}<,,,0b z11K7 vwz|o? ؿIZ<`an"%]b7bX\#X["#7*{?顩hty ,rqZ-W)E iѭg[Xvs؁/Ba6 qb&S󊄉P\A05UW8Tq' ;LPu|%Rݣa'&lgJLFCq3&dTBb zFCH͘9qUjfdB΋E/$[׷ܭqӮ6K>}уٴIU67J\Yw6[SmbvwJowuy]WD lt|tn?|Dk7~zZٟg?qh׏M,bFjo|8[%JmSB!'LֶW__{qf>!t&IR\h<'ݽ]3+5$Z!L1ޅ 9裏?yjy|||ҥJrӴDD]l;;{,} rwu՞ J4R>cDBDx߹ݏM :JR1sUU"|iL֭$IJ'^t>~Zpxhb  ubDp&`gHb{Nȵ bL lXd"'֎Ӑ[m|GD:NrjHuu_|sGO>=h^\{]=֫50t2I%ea⍴sua8ܫ 3h0't$aws0(!)UU{LJ]9_:@w^o}>?eʙ?-0H/hwy֖e.sg*Ez+; JQE܄$ĒXv~WEz9g*WZbSN7^#^W7<@1a& $7F$7H.FDW+/Ώ*sLWI,T93m<7zfr*X$p!tގCp°rR&LDv+|:Y W76F!*z%yט08 ƌ.n/EF®CpLpg#cݍsŗj9LgM|>qpc([ues٬'1sbD7lyie0UU3cb[rV5c#!jCPD<,إw?9߾urrWt[t2:%CzIJdyLiBgj05\ C] %!y99\eVDPU  7&AŜR[s071N'SeIO0CGTwp,Cّ+51^:(XJqGWw|>>zkGFC^M Zj̬ |d9YSʣ-졷S-UP2Eo .Fj1SFj$I?JP9~،BPxΝoei});0WF@GpC hbZڦ:/9ϼҋ{mF-&㪻bL8pshn$TjZCϴ'Z?wmjp#5XI, B7Et,,ΒڢjtYx6W24'G'o훋_嫻u>;<{уr%hc4NjV[6n*IQ1KI* A0PT̝ fɻh2܅XX HHT#"G؝He ։wtLL%"2@$&nk?B4pb2@&9) ^0=|TgGӃKozC幟]]Dj>_dz\e6޹ ި-uUؒYBF4\ `pUqȘ1G'4XGyӝei}i~~~,rIb%:u7ZMOџk/677'H RQGf+>zTzLι*F3$ CN@T `,>ɕ+sGmonnF)gA.|fJ!!Zq]NDLF?FrꗿsiʢNb n^ dݟ68[6p#/V4;9IwǤ șL5@mDE!gp@)IJU-3:TI$&'(q0u%a! аf$&*H_`7iKѭALD"fDԫdvh{piwze!C̔lg3+6?\,yVrjX9T ɎV['de28+ BJ𼲹0gFb3$9SruCeН,30E^,,l㝽r[^/K&"A[ ⭹Z6']ۻ|iˣWLAI2e.! IR|ڵk`:Mή\2}p8#+Lbl`08EduE) qPI<;9Oi@p`Vf5oTh۷V-9T%@4\#Iyx"=&39;XS G'bp F0kludBg'xZS'1$8Z p)3E 5CGψitgMjqI0p%g祵2V-Ơ  ەI8 YMMͭHa(f,R$Ng'kV?xOOH7/HS^†N?o>s*$DnղLfʕ+׮JB2UUj{* #$Ikx:>>:ZHe'͔[+dM8Z&Aą] kYO6Ba b?|[_%gNÜS ZgJC&TڶnԽQ9$if1b"" ʡuIqG녙`Dby dPpLDf{,{{)݅999D&Nܥ;2u#;,64JLMe=h9wN(ZJZDl5rqjIvv WH!w/Yb). 6L&E Rgw(\,|q^5}jJzZ.;!ѿp8:359ӳbުۗ^~kݽݍ($3G}bd!\onnLdzIN嬋v/Z֍^˒*U# 2Qm8rMn sA{Us[67'p\U^ù̍́L'C'fq SGl!!4E{hJ3&s iAZ[a[6BUu}|QmZ_z[=mlVիvLfNnnr Rpef7fVmk<bNvvt÷?=83ؓ<-OښJDLQ=BPMNfYrg^|կnnmVR"\MY$I2.ͻޝNggApNvewkv:yoK꣖^~|zr-+W>o_={d!@813e3q'Xw1u=%Ia8/_lUgfLlr<|,"㞨Z;^' 5j5Q^r76u5wpu46!wA1s=q ݀X0T 2e\0ݙs33wVLl~@ 7C'nԄaf, zD?hyTܻmE_w!LjTX3I5YVRq'Q:98s~e't?8.?2Pt|ssҕkݭW'9yLӓl /ll cUNggGG9hc>z I)`N'~[7ɩ$b =B 1U-  y$]c,̔R"j_q =';Ԓo6ѧWب ,IXZmA{o_x, 8 SM+@oCMi@s"o}Gǧo\'a5Z -<8|{w?I+<~{a'H$]jm#uU?fRIч1MIlF)JN b}Ee1Qwkk"#u?;]׀:QټL_ܤ\ǡALc U ENJ7&c7 VYU:?ww/]$\Yʢ>8]P.N/xd./ҾZJZ?̟)j%.5]UPLqvu4js,1}_ٹ6M5)A[̏OorѨiH`~֕E${bsի7n{ypzt޴XU&[͵q~LoU֛og%WN@f8T)"AM&P ap$R+O׋~׮^& L !М_ŤkW$Ukwr5t0-pg"PM堷jE\|A5; ZwfR EYRCU F[:D)`Mr@}U$I.-CWkF8 @(JXTJC' ge~q|qX nz}*"^i-RE FBC[e}IҩBQ51E-Bb #I:zy~ P)txNvݕf,?},wYރ* hš=rV8Gko^s(A_\\ux<9 ޻}0T̔?Ϟ+oO~+W^~d:޺ IDAT7?ڛ97R)! HUT4j$ sVk>~?d;[[5 +llL_=`[@RDL96( LB)@RuCHu r#BB)xjj7u- R:4-1C߭2cJذ3P%f;J 2z[>CK tZ|Rkݵi/Q5U1@,TGh]7N:%fh6 (p{?4=!j}܋]uJŢ\khM-Tyr|֍snDԣR&YJX,>͛67m4A`w;= Re{_&ߛ_u㍷ǟ|ϟ~;w?bqΝG1Of9[/u@)(,d,,Tl._<{n\t]5 P(mLݭDF  0GP0T% %Nj[ɋ["XFx ,eĩP h5K`JI%KD0' aJ4 -iQen}eÏ_],b>ѩM%X493B5_fPk 6/0JW0PJT! 7=Y!fUDfgDzooLͲ -f]\ܽ%"U|!=0ŌTyѓ'O޻1H^vO/6w޹o>|?v_X؜N r2?]Mק"ZS*$QK _[\\/~}[fl)_vfMuAdɇ'_*u5]{˙\my5[BEA d5vTb@p EqxHY[,&1QJV=EuTÄ.B=hT.%dֿ GaX`Zق*0eU"b->bƦlr=](/%kP4!T&+ HI /hŁ,tdh@(}_+}ugF)A 8Z^LG %ڦ]_ou|֫׮^/x[[w6Sd)%3KPydMΏx'w[W{R X̝i7SJ;۷n޼8;88[;[ 5點q'{Y_;/zvDM+WvvdyK8—>5.(~rxLRqP#T%( ` 62 rO ֬fhfIxXJbB(I% w,%eѺTHҮ"!!:iFM%Mj,( :apj }u폨`c('$;%jQS>XT H:\8|HxTѧx ˣ꽿l< Ve+Y_Hj.[IlqqMGi*8eyv}=w{?=;==9===yW^2fRRM߁UPR-ϟ7_}kke5qw jcxcM)=}ʭ{Ç $Zv8><><>5ϟݽsowdۯ\\M#PL5YjhJ/!xCW^Og9ɤr̢bk3$W(0I!"xp2:a45@B<Nh%ثw-\ -džѫuI:Mc}(@a1C(Z3`):{9=+} MFkum֤e"`8H BkeEIT_G%0jp,!'}᥿~#B`cQ1< X,EIꔄ"$EW ;wכRJj\Va$J=J?wݢxgϞo+W䦩ľ3L.b.&zK;=}'r>:={tsg>ӳ+g'go\O޸{occo?vew&t}oу)Tݐ^W`11Y"SRQ-$db,@i)Yũ%WpJQ5^KZyB70i 4U"|W*A"T&@pZr@ZETh4_Β&ll؎\ߎ(R4Pi$^3T&4!Y,"I,^b*@!#iRޱ># S|îJW:_A!*b?;2W v{{+3JW+BXtNx:C>|[7_wѴx<ӇOx[o޼qEJ-ݽxދ@K}Jyogz+׮& 9o®/$ewwd4{'ݽwgg{mYD88X_O핛׋we4/'LM&?PYc` TKP/kt`)*TDl`vQ&*^D@ u #TUԄbC\( !,ϭ^6;d( b>NU1=%^oz!~/Nvqm3Y-Q./ Wx)hýc,Im2x<qwwW?TNKO.LmZdB*ejQǛ;7o8::~٣ڝ"BqwJ)] ]gY`/)xc:',Σ&R?k;WstԥxIHBbh@R2QK͏?;Wj/H5ASeHXtݳvw%b4 ZL輔*&tAVx$ T?)Si1^]( &XjcUBO}ONRES!DRMk_]ʔŋ/p)2&Ӊk{j8] #" B *)NY v)'?x|ei^о~JKQ]Ϥ%4"^ͺI Vo>\[o|֫"}e%"t)%'kygkͻo޼q=l>^O,[ALӔr><<~~ƍ5Xch;TPzij{lMSyG'~4"hՌ*epWQJ}^T6{??]֘ b/fLR}m 6x^$" ]"L$Y}a:lNY ճS û~!}CbIUQb0%[."$rhĵk;jnOHVL5"@S3N uIr)FM!L۴"lO}QU%u_{JWgE~A<"Tl6,Ǜphoc6yŋ׮^4tVӲ9K0^[ExlJ999]tkoǏ?7֧͑fu%2Er:>>{r,Y?Pj}JZb8D!mڜK9;?.<KSRMNRCP]8u}tr| U @/]߷j2 35S XmĻ}HB$j 5 mncZ8*BMN k* Gx}ѣOnmlElmlI%,lTnmp iq/^88<l-̒TT!Y9*.9N6Ӎ|>e%DGT @NG^-< CD#HjmW@7K$$.#KhPU*6+Uz )M6/f:i6h},ْ6,,}WӣjkKO>tD?+'O/:U:+}jBEҬbz;6 w~ճ3(/fk-3,!X'[+=^Y7B9%Zp ".N&7߸}|~iw]wwvv-劜z}(Tuжx>z_ӔsހjDJԘUL dׇ*(G|ˏãMfEEi9zI1/ەU?</ˁjU:!- NG uT5:ؗSΈaXEEͅT jb9YW Pl!i哔aٻ'` O.Pl!wW?q,T^N7&$ASU=+ۛl}}_~a]zYȜz)N}w&ɖ ^+UZn%K`L7;Ç=>][zdm4yȯ!dd7rZ}9>>Jj$RH袤" fvYT dN`-ݢms)EQ{5jR f@Uj|P_ BԖ]r:%Dr7zC@MֿLЫXҒUSh7?\3 sS6n]?Xl$e!Qv9j 5NP c2NxWEX{~8JC槊ϏfCQ<ںy/{\NJ_TU%+tKu U˚r^H8(,Ԁ89=9;;L&w_w_6QiYB_CמZR,[RJIi۶kWwFQJYSmM _0 mYV뢓*,Ynǟ>2`DX<% !(b7 (5Mr5 j(B0*nKGT$N"nXbIZ z7~b]o T!Rr RCxR#xzp/vp޽ Y?Xg?ٲy]a iԎ,nu=xw%,%QX]1 wSѾ]軞Sպ`(e/13 tfjg8'(fz@TAnb¡V_Y<4勽͍ M$1 `Qv)KZA0KW*=B/xiaMRȨ!ȐIMSQk .3ECDe@h'(M (02 tAB8L p(C(ٹ-gn# [4 T& %cwO|08֒_߯d}uV}/' вndck2K(T|_{7_xW]][[H۶PռPUL)j}W@Q5(j꯰diGբKy饛{.J_" _m %f<]V$J+lXQ˄:}Mv>n%6fhY4D <mNyk  dAO%gx|w\;OoUJufO\1I2T;.Oۣv(Xo|󭳳Ó"h4YA#|Xa8(AJCkOZU=tEbZ@Z[J6{(T5(Јefʢ!$ݽ/_N77hR1Hϻ !< #"'|zztVWUS6Ȳ@WT L`7 : $V#Hx*LDJP XUN*D4XA IiS }9==傧{gd:M^vek6>~*5^} 8b_B c?Z_W>Cw,ϻýӼ֌Z)T `Ic׽9k:܈յv=y? ˹)Hb{~,'RzKJXSSQ6m $ȶEqʯ 2JA!bk,7F!9QSHPMvPk4^$bW$`f`pHӃ4U$IЧ)6NJ]I4Jat%rbH$1 D^MB#cHw/mZku HH8x~tzp(';7v*!BK^2RT5 {qC~)r߿YPER?//N%d@ga7n]oƚ/=>][绻f<.]q/áz IDATy׻dT"MS]fkTR0Zp P(6 &^JAU!BQ<$Mݏ>&r;u'AxC!I?~>~xp T,N%BjPEUmb1j)@šG5-:dDP麾HzcTc$µhB]mTHMr-*+UO/I~w^v׿Ҍ_aVgmȰN,ao6Y2T$pfkfdmiɞ<}X|n)F-HE* Bd 69whʜS#"5ˆ(,eFl:KQUHr/&UYT(4mEpkB` p贈( $,y_\xD)eY:BYjjL#r2C"Q2$/ ņ8D %BByiYIQD$)FvݼٶYpcQњ}_r-W*vJ{?Py'3ik"r 6n\9;=Ϋ^v@~lv4DU-]/bf@.eekg|RjRyRjDѤ6i 1eUMU@:C4y)P5Yl/>~2Yvׂ\JD0'By8R 5Im>xq}d"՝"JbR,Mkkk㐺X$P Ш! _53VW9r )!2\Sh',몑P]:15+o,5HwXF8^}g/eI}߹|o~:+}VFTI@033~A8x6vm\+Ym{gJ/(^H#z IJIU1S1]*mKIMNy9 P@"=-e-cC1Q T׎"=xsUS᪦mt,^KH, />|R4^͆Ҏ}ĜqADْEf쳋yԎcw7K$C@YS$VX#l^fH1<4lbGg 9,^7u7xwG+S!wWjh le:BUD)"_[o R89&S(ϺczwIiN)eiVJl{RSI**@p"un(-G \]HH,5i:Ejl>ۘML5VDT53g53JG jhۦmlQnG9|Vk/z1ɚK)$SKYZHZӶ &Uf后 Yb@X2AUɄ*DzHU(RAb5xrp2}e]kZS`_>8;{_É}$'fB @gq7[t @.x4VU#HK f-s 88(3HeLFJ)˭{nD@ݝ%d&%Pba}?_,&)7u+D% Cy1y7Oo %BzoGM@Yk;I̢ѳ{"{j7saq@v#*Kɭ1TeUtS 9|lקY_=xї9"_d}uVYd9?/Nvی է@\sMrnR&Z#1?޽i=gd)%765q $l͢t^blIDRݨDJJTH]s%q;DK3S*KD="xZ·ǧ'g޾=Orr6wM&VuWKDŀ"N?>O躮^%,`3Ieʝ&Qgw/fr|s,Ej7L{]z3MqC7\F5N.FN2DQ*^{H8"lvB>d ௮&_eUJ_CD@slŨt}c:hsD-}ѧ{oV1]BTX*ҫ_ZK8 IeV. UۦQZҊRI5ɽp)YzRjaeߝN/Q8.U Z972՝IMTe{]jBRmIR{_''OI3?_LMbpzuquͥit=i y?;.}/;}ף/Iw2Wz΍YIE m*,:ԔKus**]׉HJ .rivLv?|wGGG wUy+e_>',AJ@im̤P!RLP[7o'V 1Ͼx7o޺r ); 6圲Ud%˝.zk APFGʗ*C1 TH"Y-c ^(d܌ڔ2,b*(SMfhc<ݸ{׶FkmoGL$&:gfZn&k$|q_u/>}W8"9մhVG%lr mjXePd3 ̿H /l~奪s (j~*WgTΏTަeXOGu'@Rciۯ^ks2Nk(Ug8,Y`ZW3Qt}GY"D'톙/ttn,i.KɲƮL`߇$: #ly;88Ba`84G?~1;77iS>?.G7lv>+$`0%q ltfkIY?ߝ?\W^0.>I3~LHP6ãA^D/J~ũ΋{^/]a'3@7y,lZ&mvRD5IDr"*"L,Gg~{Zw}}PU/|u;﮲WgT|Ot;Ffk,|{͚>{0]_qx4,,%M @%% :͚OQYW46U( B t DA&J%G>?qo1ndSD*IMD[ҟE|l B-N3(w]iI p $hTSfMc̮ww8]P#VK$K!;Vщ@?=Te#@bgUl+qTU2H'ڲdI%8r=V,H;m" pY@񒇇z_D'gqwjOKZd]0A7`NkVqxIإV+l֛7/Zi>AP¢?o~i#ϩo8Ck.Llz];ffg%hy /_\T?^7j 4MRW5ڎ%napUTR(țHlxq|EG!q|\[ f(KteYP,xĈiLP 1 V!NYΥccG=UkRIZ 7woqvjԦ&'D8acReKF +!N=`$c%ӀQX@473P Aj{?̆>gip}b۵Msr}_WԹ$4nfPbfzT,Wr`>^z8ojzBL=3;KU 0(ADR! tDWC!)s9Dub%DqzoAcnb.b0UM]|emu-ⱧܱsGdžQ&A|ެϿob\:0;6[|F.>2]"nk'wMA7"1&%` x9 fpp"}5֠κ~ U&R :9_>0vj4}滽. EN?^+{N;SPp '4x, UL-A9_.Ke^wuu?== %dDjً̛ *B 7O #ޫ8"!ڷ!Lx5qXojD".hQ7Í޹3;7;?:đa6wf)'D"A77K\2bR#K岙 lscQoMONMVf"E`Iْ $<:ɪR B$Nؔ[nI@'0 %9;`hn߹$XVHRZB" ٵ+׷[j}C?o߂+kk7۝n\U(lɱ2B)R"r6 XnML<+ D$F hHlB `D⵪0#QWb}s|l4h/]__kzN14h\O}~7녓'D &34ǧ'ʼnڑ#jlDb @L9'f>FĄȔlhfBtrZE2+++wtׯ]g~c\R(LNW+ͭA}m{lTMOM9ar2* 5, 66חW mfNDջT b(qbr753&A0jcԢ,cr1HBv$L -z$F'0B܌2`̴ںujEA_x09x^Q2`dLd̰@5z:ݽgGT$НWWV}鱱1e4RsF%BkTH`!0AԇB0"0)z8zY̊I HıDHȮ]b`ΝO=}{KKYڳkϾ{nܼ ġ#j53Kӄıc *,Ko߾}]oyFk\-족D) %HQSsQ4-UJ77ח7k3x 1b-*5x 0)eہe1ǝ6OiZcu(-0}'>粞pw,9&F XboFcvnvaߞ4qRщlO8Dk˛o~~8B{&]ّyGJ03>()q:7 X! B1Ej!z(`=ޗ SyA(> ŋKx}Ǐ8y⅓'^e=''wXB#Hޯk'0aG_Y][k׎R$$s!@]gN0793 ,,Lcg7 X5yٙbsǎ$bf{AJ"?ʚi'D% JRS!g Ss ,)I` #%E bWZ`z4}ޟCffKc3ЋT[̫. d1Q,# 4tp^G њC$`h0ʍkώ<~P,rдxT (YM!yB1 >aA_\82V/iZ"Seтs5Ū#3b{B%zC9O N1>[axbH%LF%bf ,ĈեF{Z#ht6\srr}8R"xT n٫ԪU\tVoaaj|2! C JFqn%fb&*; g I1s K7\tZ<&x$Ft(p s""f5@ ڝKAgϝF IDAT+^lbjX?h$)9LC(ź:.$Pt;=xmtڭ~luXb{K0t{ʲ} ޼yzf/N꣯q6\~ ?s 0F!{Hݻw%WN)L$KŁ d! oi 37o}tZ~cBLaKBp̱agA=3;jkme}r~9N[e,Y*E F0_؄-xF~~}kÁHGV .v}eBP' 9fAz[k͑QOiNNIEqQI\_i }U>SvQVc$B0X49ƗL9&h D* ( ܽs+=JL}6ѷ0ް 4hǛ٭Vc:;o9$a6w`2^>B:NH !aL{ݮBky0dBA s:Yҡ$tQŏ)] 1[kgoo6m?u_99'_yp`4"0&$j:Vطoa~WG8LՆ:D4-D` XPX$#Cz}8'Ŵhpl a79U;vDh7>fٹwciT+$`8lWL)1* AAeưEUH'=h`h-^\2Jcd8&f\wefFjt:B15ء#=v;xYJbp yMRNNSAfq0 ~#|Ϯڱs‚ĉc%#f |Ȣc$S' 4&iPbE;Izt޽cO>{EO!Az2nŋ7ͅ'RF0u4 f v˥ 7N H\՜e.BHR,|넠jY4Xoٍ wV[jz~!LNN Fdg:n7 $I8bʢDߎ\›\{ﮩ,xrD$LCnd^g66V^NU؎qY&̠d4f!bV q$,Scj5l4htylxС'ELxI 0hN%FFheZOFc.99gJ26 f)&Nwfa!)fPz:xN͐ 33R72_H ℅8^:f#f3ݿrb(J2k @Lizsϥpdm R:G 43c0G j&Lkl5v+W ,)':q4ŏFB_715V۷/ܯFۖ<7拯fK>oݯL}s; j%0Q` S "ıEԴєƔccf7zʕKWJ2j3Ba@G{nW_ԛK޼TZm4#SbBp q"™#08cMXll4%k, J5q1XFԚ띥 VFWo88'O2c99_QS^%˭JIuoۧvI233],Ȣw0c3 3cޠI͂fP̥$Ir{[v}l?B^hݹwJ*MihR%B!Ǚ R!A~?]U+D~䉃,. P,]zᝋfoknF#d`8W׼FCHH{dլ7KBOgvNT*C`ԉ!LAbL渹ڹ}F:0/G918y䋟KMsrr}o^l!°B泡>;wۻonv.Za"!K(B"."c0J`F`UU `FÝ.=Pw9;7 H Ĺg.|7[֠'1lŴIa?T\ѕS3(;N%':͍Bǫ\ISeoL 8fRكLN}Qhy>OٗrcNNQez[^cG:iu6SS{.h_Ȉ [G^KPoPB! S޸sn^#s;fU2l0ܙsw:~Ww=)iq(wätRR(i`^4k2)|, c`Afό^[_onoY=se=''^9Α0x"VN9T*g^fb"X :ډ112?4,As>K7n4M7c^ df bPCI%y;>ꇰy2Y0LzPrFΠY$ER G;fƵ2K\+ZlC4.b z͍=>,\LHMaj+b!ػwR)۷T9pHCPb x,j&A%sk767w܎JRTA3 si]vl._9 \թ+9)\MM%8Idnb$l&c Im&ф d`m`j @U[['tZbbscNΏlqAǏO?O=̱ZHݻ# Gf3 fA4e!z .uI!~ve,66>P(Q?P^wԙKZSiHBmjWmrgu0ƾP(b;iR8lu%ebQRBHNGQP3@FkyqËm3' x1? 3(c2޼t}Z,OݷPUVfRj%%CP.q"U3R(&`?|iʼn0ԃ8/-2 &$+W7{ 7Wﮨf: $x 571[VXZva[`!xafmobj6w3aO 30bK6ẉۛHۇ`;~Ky@NN?|e5x3~g,t'<~҅w{;w֪b8JK7%@bI1mvZ+k^έ{wWXW K ),\|sVkЪwr q"Zid:h׉XD-Dci`1 }Ȳ*x&7%`n-67ZYAma>srr}_{ 6!"NȺ:dxVzڷS.rT.vy2)LRxzwgߵ$u✰0n շ{P}W䌭8U*Rbu%djfclZ!d^̂W#3!bfe57pFD'Ssrޗo<|7k:ܻɣG4͂?sg\޽ w..r\*3B4 tcc;W}6L$Iv힟G# $j~}1囫phMDre &;&ar@$l@ ##sLDqbdf`dA3vkj-<N|zNN~~^{h[cL4z3S{v][1?{|뭷O7c= w/jRQXY 7[['kJ :VURN{n2<̲Sb$%7 FȈ!L R*HXFGs,Fs^\]ml }Ϝ=ߺk\ZMt6s=,^`Eqkk/\n]mjhlVPv"g9 R`y|lh5B[[ܯGrY3y0&A׏=S}c'B861vb`%R.o\qٳgeNNnnLLhjj|߁iܨ7mSg[o}2屹A39 f "6LqD`aFfd@`""jo]Znwz`'4K0991yNCMO}S!ް_.L2ͭ: j~<3ܻU$`]]\ZZ:ۗmm3;@RLeND+E1ULY^fjFfԥGP(Pѹsu ]S'NzNN~~z=Q뫝A3z\X PXAI#3w7n.[l81[Utr.Kf&1QoM[Ouva/{bc879_s[  0db@`1cba~`TٹZlLGѣfzNN_}0̘B_ٰOkf&"ejԳau$`nciR"qnv{՛kgߺ`@iRI8ARt)~ !! @wv{oq ͖atmAO|1''}}~G׏fP=ۼln>9FL ^̊Ę 10c'Ny-˷=i! V.UM(;09x#Se0rd6$ιfe56xe'|syzNNΏ_%Pā_RT%fͱX(^XHz~8 DN\>}[Pv=FOP+P*;+LΉ,b:Y>eرf*䈨[w}h~=V^4K c~7b8'Jcܺ~G{<q,<}􉧙8M\d` E̕kW?]]]k6 ].QQVwLD=b@xgݻWLF<~<1'''?|2 'L;pǎ^ DL$Dxw?[6H3{re ,;KQٻXd'0 m"VimtG;?<+'''?ɟ! zȱG6*OSD\i%A7eyssҕ{VĔelg4 "'d>j 5 ]^hmvF8V/')7 ⏿0>3G<L F0i cgfޔɟ,/w[=pi%IJtZJ5HNh:vd@Hf{.999ea&a1O =q<+'''9&:1ۮ sZOڝVPJdd a0n|e]ØIi=''燂՟'?IUeg ,|FDI7ot`N C5$IHTlfj.Xoov~1o/91&v,2d$$F@Pj׃!q`f lu][ꎦp ;y/p\srr_'??ݱC u$Υ)C0y ujGK4Zt[Fa9LNNN?]}ivrrr~Q|813@F] &5,-]^\7?jlf\vՋluq#>N.;%/~=L=?lc}&iz|[W4j@5XX*Z2V,|bi|b~gd.999?HޯFW3S&N˥wsk<ΪXmNLNϲ0Tՠ~cOo\Sk_/;."LPݻw+7OjunnT^^/.1`&b. "'''^k_B\PzB8qJ6Vk5#IZmnvgR#>k:766,+)3TTGϜ\>{9CbH ވfg皍(bd;,AÓU+RZgá@DJebr҉/cwFNNΏ:#<}G~g>s9)B149l2?< /^F! &5ݱcZktLTT&''/}Sk-rrr{k?\B$Nb@ KiT4M޷oacc7 DljAM\1;vT+4M?'?\=W_-$ 13!s,.;w$I@[DDئ"BPճ/M̒4m{SSjgԧ>{NNN?@Ν;?ן~_3H" &a@j![ZZ[[]ػb(rX3[^^I-UXf~ 999_m0rfCBD f0Yl䭛7buŤ066llِ!~ANNN?@~{a3a긐&H)V+OzIZعsFL>"'Դ?_!'*+^އ{&&\%5o}{sr`i_ZZtUYdc&>Z ~>wPVg*jY_[MLN;b1 B0Sw{W.] y r龻1sZ'ԧ>{NNN/GtBIJw_9 tJRVB4)dիfR-߹}g~~ "z<&=jK*rZ([7o4jqi{Jijwrc 9crǃGGnl_7:xONgx}on7Eat~k~>vo.*+?reeeee%C.bq…n]-..'cW닋~z~}@w}w}@w@w}@w}@w}@}@w}w}@w@w}@w}@w}w}@w@w}@w}@w}@}@w}w}@w@w}@w}@x0pu.lIENDB`PKF4-Pictures/10000000000000150000001C9F2EFF6A.pngPNG  IHDRzc sRGBgAMA a cHRMz&u0`:pQ< pHYs oytEXtSoftwarePaint.NET v3.36%8IDAT8Ocd@QvJ,?~~ pQDhaD2io]yQIENDB`PKF,-Pictures/100002010000003D0000007E29D669C1.pngPNG  IHDR=~+sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<DIDATx{]U}?Kn IH"0Bh $W4Rj;}[jkKqmTGF:8jg/! =7I$7_=k>U]/4@`б@Hh"2x>"2yM}> KˁBODb-ZF௨Ge5"m5n*6zyd4Dd e)\~)*?NC<Y .0]? LDbQlE4kX߇kP_*)O$\HH "rrT~0k׉;J:=Xl'xfyؑ1"r 6W$1vaiK 0': t?%In$kQD'D}–G'Ml^L"c-^*?R2S q6;ب%HSZRA~$Ra=L=zFgLDdV"rk%ϭnN ` <c;Ho#zW`Y3gtnuN4Ftf. L|BU$M&-#nS_e}5^D ]ی%gD~ <ūaR;;~sSGDd9P)05LQhtJt<WôӮ1ck={4r!\ă7Ezi UU瀓Xm` "2&"އ< qe74 ְ gUaڸK.N;umCMcKhyB!KDd{=oA+"vmzwy zO.#I2bGȚm328Tsl/I ܎[ &;3D;=X|Pg1m='W6Gh8TA"حS)KhG "7#x{ {}^HdvөG!b\I7p[ "VAUq),w`|Liyh-۰r?Bd|.?QY1oj 63i 6 -~ݗb\P!rZ)Kb#)]i+Z4p'ב5cS{8p籁G>q^%o~=Co`)ڗU~܁9'4:& k .,YmNT0M *?&gD +X/㻰Skk(]k~M\6mF's\I* .jwb[q1tzߏ-xe jLEY%"7a3<>5DHWF9ZF Ӟ5--.b:sA'"łq9kZ|w"olӝcʶ xTbfi%ėd{NƟUbQ?m=I}Boc[D?Wsjo'@&,X_DPu[~@~nדF`DA22!`E΀}n(jlO<`Rol&EizdہYBl,>o9 [ 'Q)fmRٱsVՏi{3A[Tl`ai,gUf@h{D)Өr-6Tch`h>mACWő^^6B[mAvvbt1@nʹ l4֚_biwSkcu[R1 ki*tۣE<}M6O7Co!1G[==!9{M鞭i?0pH0˙A`:P59D4俏}X3}|wnǔL"ôrŻLcfWL5l$TLt=3ݫLO+׫DBϙ.v=_K3]l 8z>eL3}خ2WWLt^LkdHGm2/RkIYwؚc8:-GyFa1J83:e2jW'thHPvReVLyzjNNorgZU礪ޒVtYz-ЫmЪx~i~}vR 499IENDB`PKFvT-Pictures/10000000000000160000001DCE8BCB6D.pngPNG  IHDR! sRGBgAMA a cHRMz&u0`:pQ< pHYs oytEXtSoftwarePaint.NET v3.36%XIDATHKc166f ={?}h"!jX?q3?\5O# tAn033V䐤qIENDB`PKFVۇKYKY-Pictures/10000000000001880000018778E97AA3.pngPNG  IHDR%wR pHYs  tIME / ϗl IDATx۳]u1k9 xŶī$*T'VNJl室:HctөT[mYr#ɲn$\HJ$@BZs1F>J.x"Y!8Ƙ;ffff~`fffYffffaifffYffffaiffffYfffffaiffffYfffffaiffffYffffaifffYffffaiffffYfffffaiffffYfffffaiffffYffffaifffYffffaiffffYfffffa)&̿33?|対~owo?S<{ޟ;;`f'KcgWiͿoYfff~|+_>nɝ@ D?`'?|C.?{K433Ñ/sw7WwvBI!g8je|O>|wSO>O>ēO<,L333=>~L &%ASNb]ÃAk S[ 0HW wuw;f 01 A`1A+ c!QbdNaf?~/_iff .]}?/ 0WߏADB "YXp35$DL"́E@쀷#W[-;,A$aOǟ|Fo7W_?ڟɟ;w  JNMj.,ܹMř `½$O sѪ$h~t7f+;~C2 =/lvɶ;1MX`i SqSAED@ "zc10,>#ݰ?_W?05k|_⋀|z?`w4 bB6G] ~O~OimU 30Dm:r0H}&P}ɟ{ Tϝ]9;HhSO=O=3O=3 O1]tڥW/7o>swjbp&jC p ?haSmjCnjj@Y=e[T< ?&Լ?o,L33?e\y9>yw LL 0h/4!bljL?׭7ZV@?0"[meLLO޷g_8[.|wi=z]yrfͭ[^~57^<0bNDޔc/.>r$:`~:CQY\5$BD0Y-@.aAW7w^❙-FW_S__pp m_߸{ދt:1q{_Q+uоj"QES(w{7&FMhN9 11'&&B;1L諺oo9~><ܺUB,L33?azt?_;/~ƍ7?L͆ԬH1v)aҩDiM߷ZƩuhސ!4GOXk101BJ1#&T{t6MFb%Sλ2o}?Yff~<˟_K/CZ^_}V0nWp;3#0u7 ):mMڟN'Ldp 3B 1djn1ck7̋rzw!v?z/7nif/?^r3A\L>~W~snҧ=xH p ꆮbҊմ81;mM٠?uX9vKUwW3pr(^2ݸ?))lE"My=3_z?|%ev&4bm={WdmBM6@, jfv5BCESJ!c TǜL4M%RR-ZFw&yrS@83 B āa  )X@n\ DNDԥH]tOo?ݸxղ^;0?{+]?3c\~e9ȉ`5Yɺn~_vm NɫksO]C }4,Ұ.5pU8 QepL Y͵Q Y鑐^{=b8a߻l86Խis'SdWpq[ yWY,xy7 "|?_x~|2]/|ʋWIlfTtUhA-v[ׯnƽՇADNLmCK&w)vbcK]dDX8H08Nf7NvڍiujfnLMLO sl楽I( (݉n fH}40I?:\,kH?~rq j~||&,L3xOO.}y7mn\ i ZY+jw^qrn]nWh%^#cJR߅>#U(!H nv8M"bmnB>(fc DȉܵM 0wZ%E`"ǐ$1 zpX<}L9\}=l;|V0ꋀO>{^ CMfĆUnܾsDL MޜS).Ů1% 13i܍w4iwTsSbqX)I @mUtoln!~"rX,}r2uZpwWo^YoPE<ӳ0{r[g?c|jp@3ԢkjZJ)h}gRusu0ڻ[gRi!].]J1401x%q4匽l`e?rj&CPةɉAC<8Í͍a]:~:Jjdr j*;<:sJL<,L33M.?w哟˗!p"a!ab+ujZNhjv{`OSJ/IB!t).H1(1F"^own7NӔ1)6JOQ鹹ý 1, Xݽ4!Z`B`pKtTuʲSxrSX.؅r̎>|!'_BS}EX[%DP֢9ɥmMrͥm8;ug|o߿ @@EՈ LLVt QA'UYBŢ#jH\X#;\Kb-}.W;AErQ{[}0ztӟ$a~دj.9Ejm#0=`RUsC:]i5ƔX p$Lo]7nN'wL} Oq:@1BF՜XBSN0Z "H n/xTcWjE B; I0CfZrӄy[ ]¥K43.3e! :wanfVӧ(ͭԢ[eBL^ s՚KqXK˞8LlnB% w-ww75ErsE&fs ϝhn&ip$Jt@sڈVg=8H P ]&J^=1&&)F'x7Xv.$>e7s4 Otqӟ]&!a)ska尩U3j8g@D*[l$ cբSc~psjZn =ǘ%_{zw՛j/).LDdǍصp[!_86 qL2 DnVhfW {N,-PMb$&)*!hlww Po՝ǘ<30Mգ/?+^EKh\]Q!Ru3V%9CGLI]ۊ{[ZtwfE\s.jVC"qHTk%;\];vT-?JK/[^y5U36&6U٪-P3/Y4HT%Pt&R8@.^̨V #C`i +m $p2BBH> ^2NӟHQ4M s&"Л= OHvS$\ "okD`r&TKq<$ !ĈaZ5Yfm i&sw+ZէvͺV\ʘ'KO=M!MTݪ\k%ƀDJZZz?'`P3hsBۻ&L,J mBND!BsXfNTDY3mnfpً_ryppB%2PEAH.YL(Yqkun׺[5RK_|ϓd-x oybcٵ@&7R,NEY[Ggo]s$vVFOצs7nsf@QUS)1p'n)2D)$;ME+M`U5=&3W'1!~d{{ga~>sXRL"]Ĉb\s#_/Yqqu٬usBdRjEVwa ,j5;g }n޸1;S YpN..y]ba jݺq+ @ݓw_[F󣽽[>%:JARUCߕ)Yb\MxH] %Hp@NA$TJp&ED0*Yij$ZT s3?zW.^rʕ$DsqD,DV/DDNf@mP-m7YZ7'77\j-hUTr!Lqb%7'vUUY{Wd-r£=} i>{r)k5p˰UV˳,C޹STjb}wdP '*%rgsDoOK#`}7gWV(P0D,/U (Q9H?wr|g}ruܵLiRilo~VK9ƾrW2 OWOK)G)A+8ۛ(9 ̏J>K(&3#u.BADޮX/:h\^sMMu>כqiWUuiC񎇋9}ݬ'o !HL,*8:`_\K%{6%D$y|/0g½xrw8ODZ<9X> } CρDX[ =ʻL%v.£S4 (`K.Z݃8xu1,5oZ,L3?l1z2@̧o:GOrpn `Fh) {GӓGD,mwiitr2=ޭe;n;NZQ.u|ྃw] ɜ($0U Ų;{Ɯ01Vk!V5 Zmn `6hU D, >lu^JI%`T3!2i>!f Y'%ƪh! GIs!8U Dgyڙjȍ\\Z(,<,L3?Uz·>/GA d^ZI/'Rjumzq6kW & !$Ե!& !*Ih?5tvMSb -UCEcZt9Owbs"q1Gbt ;XHJV[ fxM R^n0ZU0CbIR]JD,ags*S!wsj\jA <)\r%{n]٬7ǦVs!~`"d%qwcb NA= ,Gv-Z8&U'ùuA-I$C4v+\DWju}k#R; =ctuM2R6.[t}-VL~͙e[gd^kέJe1 } )>jAXbAbLrZVR8HH݈Xv"BDyHb6{pE绋: nh)Ju뻲Yf~⊣kDxڵS `K uѣpתX5o6yΛuwm-J2`"0:&0HMT"Xv[Τow}|䱷XSOu,NV8!}W[P%ݩV5cX;ӯwy?H &b ߼y[Cl^Iaэ9յd)J\,j5txHT^b "uff} L-`Ղ Cf:꜕Cئi}[> 0I5kh,L3/ztwOҵk? jjofZU˹NcmnW6M+K!< =;{3g1Hz!b 1ў^GMnjq&r-_*k#jӸ\pчdpXiv#!赦GG+'^JU2^3sjno<~>Cp3T fJ֜a뻁D]ӱ/B1Dw!v榵48wub%Zk)2nK@OGG NLثvkfe] IR>tP:WֲL@&xC|jȥGxNxsc]ZrLVM3R.unw1BVoMYD9kpPIāc bsdm H%O,΁Z67h?c *b]Uga ~ t;־Јīen[v۲ݜ\SFJDKA8pJ%b'0C=Ĕ- j˻6ӻہO7LMT$&}5d6gV=p` j/]1ŢO]W.hbnݭ'!3CRd=:MzMbjXF.Xkqح蓻 @ih--Яݐ$R$'h:TuUrsWSͥ f`H)s !OOì[f1':.On\ȸY6#L@fܙH"R\ @p2Iqo~뭏>Fe,A8 <c#.OӸݍۓ)]caX]%"%&[fUZL֖b2q܍*p*Kp4>1cRqiYseTE(ionW.ɇT%8EXTQ;3o!_ַ?o\ZTʸ[:%לۀ6K!.d]j"R'A8o i[dt fD a-}^b %ZJLA;0rَ۩X6;k-1owk)y4T\ ŸřM}MX^:H}yX,7;Eq[_u$HF 0 ,DBݙV}5PKuzd}PeA jga &k%8@y[3Np'gWH #p[qrxݘU8t̵v>Y3`CDbHNNjw~4曷rf K;s K9KyRlfbi7}1% yCR/ff'c&$tNܰxo#ur٧墒p Ĥl"rr'awwcUժ"JnoGwݛ P}!$5R)Df֪0?g)%6N]$Y a|?3؊Snsn>ucN7k#i?n= 0S  "R3UjN{ǐZԣ\zfʥ7eH j:nwfg˃xH׾ #b "sZHŅqڎ#K2xu'w$ǷNΞ;^ݛBL]7BDw㓳X 'sXL>[׮}[:y~kV5ԅpXnwK} `ANNN1~%:98@d{|[b ӴCNM*!TaJL54P屚#QO<[pdj[kɜ7 ̿?G_an?+&!b3T8۰nWU` .6$j"8+Eb־ݫ4#rwWuIӾbjcn,"ADQ) ARJ5ufW'26 olꀳ'ϕqk2Dbl Pvb@!appRHWjV&wΝ<#wrl k\~1jYuEv9aGr`>&;I1 &t+#U8JC(%R"=@n^ܨwQ-@9I\tR̬P);:ѣXXIVǝQ'0_G>ѯW_xiu5HYZ`jG~%Q˧h#(R3JdncݛuOJ`"av_k-Ԩ]Rs˃eO1HVմRjVUkɁ( Th`# FdfnRRr]]90MՂQBO8¸ -NP`P˃|hq|w" XlF шRaQa0s Ġ)qgi$Curw7kN!%:Z,x7@u]wR򣷽3p/U-B8);R*2Nœu~b'%Kz}\/I,9sjuzzJI$#REӋ$,CzHd$=zzz%nT xt++J hr6"~oL77oyDDxzQtDf/0[Sd )ԘB}Lge 0I?bO!)R ') e7f0sof_PL@xS6}ݞͳDjݕU/)daZMqXoJEX8T=pa'kf9H$*jad>a]fMkj$].600uI|nTcqH$f38I.=gzjt= $)%bMSھ:>D=1C 95lѫ2D YU_fs#q7R-7?χj=EH82@_~YdF'^JBŕƋΑs^n잺RN,at `B |qG,mLp~DAXC[ef՟;-]#=<x6ZamN S)9:9uC?~WX -]"10h&Hnn^>:\]/'?9R7D`{FxAFH a{0NM=%RJYJ|-'\jF0G.e ZSwǺ٧\Op0+Q ,T@H4Ij@:[m͌2j%&yXYDguuK,BY=`PXpKIדI01"BQwZfr)28:6ˠ1 /ran^.}W}̼5BakM}Q/#bD G̢Km=V]껒aA0m 'fB!r yN9'TF.~'TWӓZno~awD5nCj '0#Q!`pǮ/'' *FBl)gD٪ְڭO+ݼ 5 IPrI03L v46%ex[m }cYo&,T~Hp ̜ %rrבdN.H4 pvm?M㼻٩-(!,ڗw40@ān{x9{0B@>? Zڬmij$vfGY]jhS{ a о6a.%)sW\(+]Wn:&ܼT9JzzzggZ8%M-]p8DANN6㳧N\i56Zm[9@W|9A,I'NA05w gξ?;kҚ(E$rD$A׺? y71g^j ԥRioL7<7mw/DLJG}%0\RH82P m0d"rFns͍<`X(J Rvٶ2ciLG"[;!hǏ'+"X@յipspK$& "x46'j[rB\ڗO1qcF_@TVﺮ$SNRIA:fIbSMI鲜m:Sf2~B/aNON$&]!ZSx6$@V8@@")`)iKVDx38|ֹHY(.yX.j];䋋,]hUXyHW}Mƥmy˪ϥaedq~a M}iLZ88wg8"f!m$bcKqDsWpPmsmN̚$i;msOn/xkY "ӬY+Ρ꺑\R;ʔ繩pN p2?{9EzsYq Hy9/psT򰮇Cr mZ$ "wse$g] ywwBZ\"P7UNDdm o|HRÎv#oP`za"A2Ifv=!v7wUJGyEN1d\G?ޗ="%DI%DX48OGyS@vwwż?0/Xw"x?8T@h5P2^W@Xg[unUX47S`2 jw;ms)gaⰉ~nub˒pvc9Qx4Y”cf02TMkޕ߬Ȣ5 d~zkPraJBN%<(QM|(e0,L 8zP5T()u,9,Z6rʙs-ID K)y)Av=麎VL"t&~5t1DV:RI>'PFG]$ͣŢ4u`q&{.RLRD)G3 .] gkSkM}_[oAe !Bǃ;3#eDAaa>!\RfLa䈩XǛ]'N$bMD,8@Ҧu8zaiUӓ%歩SUkkњ%"/9tǜioo][Iz"z+o坧O>DQ?oIM7}~s{Qw=%qbӾc-;ٞv舚鰿zz 2ӳy=80$8iVzTRp9$L̥y o u n2Q qN00woUX$C8UBEQ*py^ af̑cTMK֯7Iel(%>q%$ SXΣJ.WGv??2'|AeM%O-ER/)MF"$ty~R8bjthnnfu<%=e_l$ OvwwL_߽|Yص~׿$?/>`ݸǏ>^>@o,$.V$ qdm6Ua6靵q^ܩEKo]Wƚ{faǍhjʝf*USBYӏ/zp"f1'b]V] UBUWmgNͼjnSF4M-Eu)Y16eYfsS0 "],ih !I1p|eI Xp&`.$_y\x @/ KWZnpl~tq^DBffm:g%7s5U|QՋϘg|qlmrI%<p}|ɏL|s՚={W{ڼ@6u]!@j<5`I!T+d]wXx4^>4" IDATڴ#bnoo$2Ym9"$jalGgY|-S&tYBR*hhnkiNsU!%97N2V_% þ.*`c2Y Et8^YGÈ%}XFHB H:*sfւAkoLWW^Sb`s~JD{&% ai88;<! Dq٬pg%"/ۖےl6yy_LxMG´Z _}80mi܏smSpHYv]`xP6ozo]^\&GUw]_=zxz7Mz}oڤ-?}>| p}[4ͥ$lVW׷@n7$uhS\sa!ˆICL8Y2s/V2I ɱYҭyO"DeL9(`8IRX\Q P_Hz+}{k͹YNh}IcuFD$GD"޷sߘ믮_J"]YQ*;B0=b<Jhp#pwUpuG >N {'h .%ora_]y|ꅈ`ui?Mn0@-B!BkkkmM[Wzw0?=ٞx򥇏.Dx5e`Ϟ='? # };O>5?{/#(<\o^CI89™”r:C!"ǵBLTzS|Zsw4s\:÷Mގݾ٧sN ''K$jf1Xp7:(0nz㫇_|`T#HH-ZSPZD63216E~ߘn;#$30_>pɞ:xP] ps7ztzs`%Dt\_/}r?vK#;wY_o ͕NSX[hwWk81s80enj*i׿f=LoW}ҕ,,x?9d5p>XK*%7o~[/Փ?ޜ/ ]Lr.n')ݍ!$pH {Hf0jkBr3$ ;B><׷yY]_J;JJ`7x@LZ ґ§iYPW>`{5 B˨28ǒqߘ/J'6gδu c>5 PoR89Tt=umD%hw3X(nDq0(t0jmujjfn-aեz#^湒S:,膲n;;&lW/ۏj8 @`TR$ 2Ϸv48Jd;ګ'ӛ뫏?w⮀]uhΜ%@wق8\P,A0 wΓC/D4u3怷NMI%*,Byތ0bܔ?}kCÜ%4DAaW6ZVMb|(z%p" &j^ǩ˙IrYSi>TtߘK?0u[BU`jIJ,lҥZ<9Em1Zn)ӒdUb| ,zIݣfcpFgg~5TBTN0ͳ&R&j۬Zx8,Rgjj.6{W./O@ѻwܫٛo?wC߻{i_?W0w_l%3k 5RښΩ//Ejirm 8/v:L,K"D.DD&Dfi2Z$NgĴ9n$97lL:aXr<)/,'D8gw6v)éAS>"È 9aj,#{{ğy_sasYI2eW S$lsm9!?xp׾/_^+ ^ t@u7 u];"'=?y/3G~dFAhUn}{3Nw87H#g)s nXש:%Q0ڒ %E)1/,@CPwvswa.}UǛs<gevYщE412{&g84ֺ> ר-1$rx/d˙+o}w?` j%'N^I/Io`dNUaMv|]~iQqZ䖠#iI%9fcw7ͺzPI}W,BZgSEXΩ+)LJ|]};~o>|t @7p$ RtwPJG'?<'']?Y +RO< H 0023Kp֫Mt] kj Br7׊ep{ pda".ܭVWWĹ/6Q?Sέ,A( $ 0qSrGڐ@Xt]Jy|t-/]<|ߘ?=#dokHuD±!4ڠ*۸;ܼx>苠ba9xBG3%Q[˿2CtL/ʼn#L>nOvgg|~ԹI"M8ɒytqܳP2LZ{VeN}"oFDG „ aABN56j`nOg?=ym>9C7H٥(%TKAX,zs%WRi)s.@SY&ފ1Mk_t_ycNġuuz9d] s'&~n?;GnG<.-p@`]HKܓEC"Ԍ^RvX-h~^lylm}pM)֫KӫNW@7?'L>;{G[헀@vzw7jNWWR`Vz/__'}xvqb8v[Wu /Ӄմ]L'Vk)C"ܯwpb"0^n b\%EܼO31V<[zI&0NZs`j躉Aen)B5nҝw,~4B&̜hvXm\%I#͜+$~Z}\JFD@Jىy { FOxw%d8Y%v)rB"aZHK8ZFj ,)" *I$%f!D+9jVr60< %Gj=}9l\]ץyѨu>w7\9nn>zK2}WJBWWO~GWWѿosoO>ΗZR);d3Yʅ<~%%6VU߯L#N^\r7>tcC͙=k„PKL盭bqɮQD!sI  gG}hQNRNu-i }FaOVg JCC~ٛ{͈HE HRbY ˎp$%dՠsmnẹl1>)\Dx"gb0/#w|x)%D,>=I"Ea jua?4DD5 woͰnNg[xp>?TcfΒI(+azqWa <_힉Xқo?n7? pXgے23\ݙX ϥK (LU\֫xr"ܕ<ʰYo')_~KwOYקW 3Iea$D"̌%?fvqY'O?'~^)Iy/.Φ@(]?;W"fA*PQxDfHP `iW-,XU;%j n깃 F8?ڼvJŠ V==$")(6D;iʀD2?|ؗoMaT ({1(sf IHQՏHBle_V.ibKִV 2ws_^e- X#bAő|r-@b3̶Y݃CE,֗~u)I <4̈hs^a脏@}&z |5^\H=9̼Y Bו.:@G+NEX?xr{;={a꬇j!,)mSIx,p~͇CZ ,Zռ7CR 5OVpW"gZS0\}. ,3_q\)]g\OJ!R.R=aNQx8spRDFa:Di~WC$ՙJ BYhnvߘYJbhaDD8v[JL 0L_r8Q'&k=l3l & cZBVvxxNd$E8aF%S釮IX8"EЕ͐Ro`8+΄?0ܘ!aYWn|~]^њNNU]` aۓZqcZB.jaS֥pfsw#qpD 's#ĵ9YqaSLL@J8=; q%Iџ5ү+TyEPPYb40 g"uw7/Z]-}oK+-y9FG2' m 5BL܃ͼi9gBaVkް- (|p%&&8E|K1Kb)YXE )%,f֬UljRa&Hﲤzx`ynnE%PW/?{P/^nֽ} \2/n%nڏ @`IDʹ-Q&t4KzTr'I"᪕,3y>Ђ)'s s]k=JHs{Ibʮ wCJ  "GNqu7Ble3݈V~L,iCCr%kWH#/],JZVp~F6|ya"9}p\1hZAn`@9`DJc4xw{q5M1.,dAEI7S D "`dS\[iQDZӖRYC*벛iku"aVzڬWpIЧ%rW/n}jIsmjSkkC=n_rOaXhmΥvu` 7#"yK3fh-.BFTS И0|/M)0 Aj!I(fB1HI..Im0]:a;e%قv|K612-֥ ZB(ü5^y^w#4gSwߘ XLY e] Q$#)̵m:7!j4ܯz8ٞO6}W.[Ṫ) )<:I)a{c0nkw0(ř|3!J8M,p=1 }$)e=B|}w'In#ZĴlB5\宔̝H)UЦ:5A_]Oޭɖ$[="2s_rn=3D@H释fJ$lAH$( `f\wCdf9UVU;=V3 :zs!5֑4g3R G|۷GI)eYZewGeJD]VS3>R,F Y&e~1O|r07-$͟o;r5ͽ驞h^z>y13R 0j"UM) fѓ}mYz?X@{m'PaL9' {3c@\y1yq^ƼM.$<{N "Ls*ɻB(C}z:S"Bڦu.IUۛO$z:_J*%$|A"i|(Yw ­u:0\KZ.&Hv#ŨpJY*Z$e{h b IDATR,l2L:,,e~A#2TP`$q g4vp8GlbOxυ ӟQa 2#~yzլ{8ߛ=ֻdQI 5'PbU& STRrl\ -<ϼ]"ICKБJaJYBE(--j6Q>^S.iqz-}$yy 5-$ *4\I hV7j}ݣQ>m׫^5g)%AumRhl4M:ǡdUM)n`-RvE%AwNDCWoZ맻wݞi^S*d"KN!R뛫ciWG4! AFVr}u3Y(4]]r^̈́Y)-IFn#bP6kId$~YS;To>=%)n>_Tt/7s)5#"ZsXS5"]ʟυi.V3{fG E? .$EDb@t xAJ01,;3.E'NS)KIUDdcuެZ&UMJՠ cDX*C@ETV`Dh_[VkK5"iH90Lz!Dhn9%v^s8 ׷W7ׇX܂dvy.ýEU8M%I.A3sweLD @7ə#B].| Ja@ĬCh774kDbw؝ϲz8C6\R?/U)KXI9Uy&3}.Lv] nBFhGPHR5 F7*S4%XSuY=P#s=z E4bcDةa nXD7UPI(Id*NRJII yᱚ2'M9'ݏZpzk$ !fN&pnp ں-`t9?-\y/^amMx_Rwq/V n)Xvчˊ0EJffpoߺxN2ݼ.4UoU_SZ{kj浮$?zsa|o{JoDSZacK"PP%jRR[㧏`H vABiH<@wP"{(9H{8X0Kj):4R4s*PH \rBo!D$t7@śB]6Ɯ2 n]oߝ.W7~t`.y,@`'͏WW_p9S-!ּWa([lm=ye X[zHGW %]! ujd$"y0 ~ u>?DZoB@HPW'u)(`kR-8>%|.L?7ߜq7TMX5j&M9Oc&&!uaS]fsM}{#P n-`DBDtՀ94\PjX3w4)U4gI%LTZrwpR,H50Y [_q7"P[5'ȲM˺}"fW\E8|h&wI wjf2vWa/UdfBkQy??\]~'0[Zw^ $IJZ͢=Bjk|TTv2uUsv~xpx"LTqzzZDnfmA*~y0}>pQE Z3RZ]MNSښ5e9V 8L Aly \)D"%Rrɥ 3OJkTu5%TЈv㶮s5Kf0y(ZKY.|aԔݚv<"x\ĭ 1}k#Yj(cFGB%=קD=!}!u܌ĭ~5 amі aW WǫVe= Bv{:.E׹v91 NZHƅ̬w:.K {^L42ԡC&DJh.v[.ּߊ C)"}.n"эHàC6պ_?gUUm]4oMY4qH\Z[ 3F ܇TZkAU@ohA+(R:viBEPw͖Jaj(BzZ|>˺ '@ xP> BB`PÍ0Β2?~",iܭf쐬2zŇ<2}y!(%a擻htTYsuLS*o0}>TDn^ܪSUHBhc @t*i,so<"ҷT8HD7N@Ip[;@"UҩޢuRPHe>?=]rΚR*UhKb=_\TfJ)6#">۲=P/q/e|ƒќ7)5݅4Ny6 pw4Ag Cx43i>/enEٹ'm8!wE|Dl&Ah"틇mSʗMDMt!|Wy,eZ MLe筢.47gE*YZ,*RTv5x(>_ݴyR#eJ8B 0=4:nA[͊M lx{P&jչ[Rsikr/O"Ds)%%M*-Un> eu>vof?Vrכ77ǫ`VgK|߻͢:R.k 2ϵw$ "9GJHuv#a$8PSTO"zJh``hsk2F7o޼.Z,-ˇO۲_b4-"nñczl3r4y+?$~o^iBB'`=v#{ B xfx]{Pc0Gtzg缷_V-yT͔8`HxM){I0a%i[~W-%$\Q]:Z#!A<ݿW?|?8nQa&ŃѣA7Fp' gfOM`k׶mz%@ 7B2jJ.p~x8]VE$EsiuQJ5Y^ZhauY>}vI3DD]eYjY..iO֒/_$qȗ"2??A`ie4[.~xҖC:5Wo<-K5$eBr6жh== 0a~?xs_{IaN 43ܚFTBTps<>> f-xoc{u{}x \RH$5giQ9;~ˋpŔB D}@VE ?ΗөO͵o=؛N G'Ic5]&!3h֬Cq|m].t[ tb Kbfa0fcFUpwz)KyDus0HLuRQWPuꠌ9)GDkŗSQibN2R~0֖=U}SgB$RBNu]f>;Zf0h9@>zlS^JMX\E"(0DˊHukьW&)%D\_t+0/?{O`:ћH"Q aN y?x3ۛM6}GY%Ba44m޿##dS>ךwglwDm͘N@dQaTl]s4EkukΏ狹J)""54]* nFY#8]#(lYaaMg%?.\jmckkT%/O(dm~?eN>d$φD]}]8C@* `5[M ihw:bCD8$ϒ@^)گ!AI)ZqpښB; ycsBˈӅk.x<>=>wdZ2) %u֋P];5~.Lxq& Me~-d?כ0a ӫ%KRngn!*یvۍ ""-6NEM080%JZt:?NNDE e4D9&0DR".tZ-")zWfKkf/gVq9LCH)bSjjAAãay^D4 Yi[Wۆ@cn$BEy!:8"`4G8$BdKl=|@R-Oዯo5)حJ-p޴uuoxgI ID4My$mfc wp)rIE߆@HB-DNEL歵ztw*hEs B QR451& Huy>ˊ@ʘqyNUIsҠIA( CJiDGA2!l]Oem^+գe~⋗"̊3s嬪97uaD3 fs] vC=R*^[I@0GFvR[\;ڍ[z S S4~|X  pwSUDëׯ7_ᮅqh2Bn,\T4Ҳ1υZi')T *H Q \$e`܄Ftueja(6g !p.A(W]J aVs5aR(Z1U|:=<>>< P!;ÔWs a(X-< )"zf:%,iA },6k0 ZUmODx)!Di˚!Jiɼ֜nŌTY˺Ԉ4nV-bPI>ec><=A<>#+ =a FI/8㫯0ÇOK64DRxG5xIBoMDuyᝌEiHi,xm۔Tu1}.Ltq|yjFB4Yfjm4pvY|6HvMwr6TJ(] $#$AغLNu(C6sz9=>><<$0i(Ut8Dtw,ܶVŚqh r&wW!hB486-"|yg5N0$dswa%204I)I_OOe".R=(>]DRx КAwpU@Ղ͟k=×-TDH \63 $&`,/^Ey/ޖ4y33 ԵRDD3KHΪX拗}x3LeuNAyYWDүVsaU!F(Ä*1hFxsG<>Zk)eF4HDcln#+VT;n:ϻqeȪB"&zz:]Zq@hmharJOwGQ-34{ι.goKAhˌC囯Ai]L LӡO~ۺ.ipFK^NiRP DkYI : zls!`݃-!]4J6b/nzԤb ^ߤ96vmלqRcwػcqAxom}$Cڐvt-=]uw{URC4VK2L\ z}DR2ܽ.,9 ٻyOz #`#w""ew8q]N!h-ZXc/ˇwOII$ɴʐݝAКMLzDgi\_zz|2]UktOI\:ϵ!Ze?7 8ݔC NuYt )у-|}-ZvWׇ4$&'eK<N]U2M0 EH0| $!*"]xNբ 1\.,0$=lZܠ^[R=ܡd:N85R+9\)IgO? ${ks~vu]]`rNY"Z\TAS"3b38(掋4>>R=!u*I_}\2mP+D#nzy/r;͝ q+֠ź_(7^[h-;^]/.z?~~C3()њN/~\s?CY8@W-ZF AǀUȺԶ.JH;zV<'shHNrX D|x9CI VķIPli^jMIUuv~J*֚їYڥ*t78<< Ǜ#|YQ Zۺ6fήHv:_2>Y$9AB4Jɩ$?,ؑmj5 2wZ_pEDNry* -rJDt;HI4ZEP@@M2fڵ.fN R_k.QKn&@C IDATմM7QZ-`"$zw@A_|_W bY_~}馌"jn9ik=~.L5xA=c"D PRNU'3eڪ"w@b+I !YK}PͻH$"q`g*-K59\|hJ9i?TU ("eS)`؆BֺFq˲=޽PJ!sݯ^LdPE zJGs'0 SWQvIu-/coφ1,LyÐTz܏eHRSTJ)yiK%9 !$Iϣ:=K|(pkͷZWJr÷<$*It:ҿKm]N:,?'Ѩ E☆rY q[E[ AAQ6 ݃mf1sO="( tDuCS4/UJJs_E!q.<4AFJF*,>Z~]gCW/PnHIw~|.L߱ҟvnh>xӅIqAњˌ˓ϒsDI!j@t $ZJtHC`E7"u;^˲~|ý5hNp六,-֬Ǩ0TH3 R< =;!A(l@! U밃j]*kDDVdY2BMlYKSVsJ7a L,'%Û2t^̃0LuJVK5u"Ji`l# "0۪M:Ø7P&w,sg6l:R%VÃoZm3ղfA|ۯڜ0:Qơ雟(O?\CHJ)E4# fandK0.IAl#IkZȺbU8 DB.S*E,ۛՕksTѐ`]׵qhmu {W" !JDĦLsy4"IkkdoOCDֵ֬O$xp;d﯏"e(jժ&"*DBl0E9 ō~,sP77}T2'6Łl7 :;v |PCs|0M`t -TRQRM+5IxS3۲vᖿ!#ͣ#:/ǣC5ͻG0_雟2ޗ we?T GѬeԞ&r+X *mSEP 'Gܶ`=~tjtv$".?$yY޾9A47a+Z̐a- /mADY2dtwyoUxuZZ09sފ73fn7*XHtr=T-%QMCIÐJK'U&[!Ȗ"\Ԓj%ŤCŻ Z{k;!Xۛo'9&\䜒F\3$34y'"QfͦDkm%|R%,R)A(A.ag |EM`tQ!CV$X~Zi$KCD ]kU5"ȠR^a|%tVݣԱq~\ey} /99y,u\~ _dC*%Q)M![a c w6i mQ1& ű.w0xBLxR6-nPM{a5"LR״fiܽb>^gFHʹ_,>Yu{HԔ˥-%HIm !! (a?ՠj)1lNnH ^Ē6R*u~\;M E.v]O'ilmQɂPI A&j9,K>]1Bw|:^?' oKaJb{BM5>7F1&+p(YFJ,s* @p<޾4!HڅfħiN*"C~;k8[e]OvӼymyJ,gS`emҺ.}wT$! j}#E`Z۴E(ݪ@ S*Rq,n au66,|nj0GuO^>d@b`N $4^h˸sZJLrb?Y(P JD!TxTuݗoU$ͰN3U<%DA<jMAmĨI|i"0IJ_Y뜒v4{w$Y\Χ =(z%uDQOGm~Cޚυ??'%xsv6W{sԲ|ґRo mfbM.`Y'v7)ܚ3tzb!Χ|Q5Q))mts84UMkZ7͋]JfXo+j&"0ug(0Lz0qFZ!*o9TIJj8y;E_2V"j 9I24j(% Efm뛷%,3L^aMP pD kI!Ҧ:4$N~^QJS91Et% •>xf#K݋c'^Kk"*YRJL$B 9C‘MR.ڙNhZTors{xy߽sg6Cs"Luєޞq0<2T&Y *ր,ܘ(pDeDq;2YI ;ro˵^/\@0ts;lh8Tv9߾D9fIRk:O<}7E&ldM S1[/}ܢ@ Ye,i0n\A91s<$b_أB{}^3q. p 'U:{$䒋!L5j*ڛ7]D-lQ Ds*б|]˯ϗ:ooRގ'DSkDϊo.%/ʙԀ"諯{iflfjLz9_XgƶP(/ij9=>.b oAU_Y@djңI&X>k9#,%2^ Z1<4O)%ѤBm5h.@2:Ӽ\%c$f)%t:6KRՖ3| LLA(}Gn@uv`VI-Ap&SKf9]"EFU`@;zr=_~|vhx# 5&5 qʺhPiS!%*P5A74-K]ljNQ!$b F&Kzs{/sa-(L3oos`KIA8!@ցM}6t I/M>O"Ot>=>TT4niK[rLu^i7e9V)@y,cmEDc^RHj'GJ/I;:ew2 L5 6wesdEe>6D\{ˈ`9RyV$1MJ:v$Bɗ"!4ahfOYvwx>=_k c2 4vҮ֙qJr\ìYǔsKbBM7sVgjN"H`BHCBKͰՕl*p"4Q4/TOKW_LU(5n]4mM~CJ?X/hgetS(h omH5Q+Dk]g^ o#DtP, a?\2p,KmѢyx:'$R5pu $J!mFb9J.YD[/V!@muE&AL53ǼoV$^/-DY|Λx|Aߝ{ EU` 2{n{ˋ` :kK#cxb۹8LUu\vMiˑeזwz}6[JnR^Klm\s6YnuIyM=U#ic۰/h!ˋ~&YB4DAh[6.Nr3 WVewӂ1za,"z~$"ZmjB3tg0:Oǜhsa*Py]'w7˄nVJtA2-Isx}8^-e1͂NBRU4@aDU|iln@eYR)e N@-wpGo3q5\iۥ"-}u^y LI jOW& >@Trֈb9!,ՒJl<7XNuƣPMmvǿ_uEHT\s#$% )T.32] sL+Q)E@EA\Rluhthӣ6I$!!>h1 ͗/w0qӈC732T5?u0tDRj`#cMN }}DV<O׹d *&nۛq3&fendR}xIx"ʲfۜ]w?|v%!A..@RYPEDѳJ]5&UUe8@,b6YxpimwlZ*9:Me;Oyr< (GfjjAPem)3 JiS0Q#]ʒE=Ucb3PS] l7xڂhLk(daЗCqo>qg'0̲Xq$F .@T  w A Z(fQ2ϵ.hfP!j)9>?>]Rdܔ͛bfhl:m|yg$UwےSw̤`WBAPP?EGYsvVirTL5|8lv[p/uZBZ[:sٌr_m#wT= )mЪ`ZJfjLU㯤K%sYZr\e4U|>?= @^>nn_\ZLi[BEBS)TA.dQ0'9߽sӪP;cNBACh@ק瀇w#ڒmvm(B@tB$D_◿תg !EQRiě9=B@k鱓eezG˙k_$ (%ɬ)$7wwf+Iܝ"enWmn)0nrX~At>`;B]B'"\ !Vw:2><|XFݏM4%((8/><,i21!m @3Eotb 0zLJAj6#МM eq<{b\]'oa9d{i!MVZ * s7oSr9R$R!0Q d|j,YpQS]n*EDaKm-=`څCɭNjfs\6r.}{~?kzXE5pZ&Փe$ laޯrw*":;UQ8_.ǧљt+֌K7ʦd8hv/ڣ7/ ѸFD'gD0!נ'z>ouϑĺo1'w"U&l"F͋~J&:ŽR?~xxYKVv*GGP ӱB!+َO_4S (:<_2MP a5ZKJ*Lq^^}jc8ݸzU5U ka]#vxB$D! wff-oI$l(›k~>"kNݛ:ۧBT:fqTeDZ6.K;6/K6?>,s3YlH*L LR]W)T~~t|YRpAipG8؂ x s+&lD%U:n.wq( dtG: Hes~ 9FD3(muWv l%Cv<[W/ sHCA, Xp˭"Pд<$ iUӍvlt~[tRʺ̞Ukk|߻@o%L8l,b*VhpFjVLKzznWӇK00*FCe$*"Sp4N%Y |.L`?Oݍ"hUt1ZuݍH% $D̠ iyx޿qJ ebۗf 4ATCwP+۝Z&)}lWPZ!^}P(J}U [E`~nL Z-w"BH5vy5b=+R-sQDDTU#A;(]|*o+Y!n (AG>~5LՆ! [)6;ʨ4d-8==nn/[KBRLI7[ϜKF'hcxe6PZ@p}zv:eэ `0ЦRkl-M9B6O_X9d 1|.L/dqBgk (<,G]J ,PJ#SVRC)'0!H"U7})+nf1_߾[KTrOb[,׫[`"PJWP \O-'fȐ{q` d&//fwOA$WʠFE()H*8ֻ4I3*j5@D, U-t;iz=zq+t{/eٌø)Vۤo};lV_mf%j S%R-7_QفbJhF` g/Fk.d?zsaAV03 n_`mK_5a4y{O@K!S65:Mg\Q|*6A+9je~W5L"FuV3SJNeTz00Դ(;A<}Vx"FԞ $ۜ:Mns87la;vo^!><=uA1PBӠA"mID~jI5ޠ"*B&MpY$*fV-fԤW\4i*֥1xPwW B)H&H1q4*Q,26$dS&R[JHV@=T4T45$յd o.s_υ~*[NZY&O#8QQ%<%Ӥ+4C>/:t0'|'ҝAЄ*-K=RΚjףu9]k iKy}|>iNRNeU H*DHv8dc`&w+%b9>FK i%hpKR,dѼ./=yiXnS7T~K ?PD~b*ooifixM,VP-%FR.4޷)DV72e5Lf݌)%Lڠ:e>??||~zpm(5 x9-sUMjFl''ܽ//f隸ThKcmR<‘L$Ϭb|^2.𸺌.e!I,JhWwқwhʒp? ~'?\~hW?`ww[YjIL]X[ XCW%yDz>=^d/!,OJmASg{h\||9i֔sm][S.>>[t6JL#Q-5W=(?^myA6 o&yС:K޲Wp[ld1x~R>m"51|?9$iυ?=tm5%)}&LRwXw鴥E24DȅuU#\_:}iSÉ70>}a=y-J.ý -rVCTsN"(4گU+UhJ\f0)4 Qv_Y.EE)IJYߛJ[䪏-26Q歂X )ݼzq{{cTF 2n9[Dni|t͹~kyq6XgK&n-/ =ۄ>hDcO-LK!]05AFUўrMHa(pk`:O*<ȏŰ_ZRL4%3 zrE5 9 꼘dW:l:ǏQ&$1{o*5KNJnñ_ƿ?ca~' TH)ʢ4B5w[!@̒"RyFFh)K)8lЕK8Qkma~;1-ެ@bZ GU(}\e8G9{3Թ-5(~M0pXA0~.)U;_/u]\w7˹-k0Pأ\=/T&'D@RPUP.HgF<0\!ab9%8ELZIdC=5(y_WC1V[S3:WP_wۜV>BS)R[Q]; Y_~0MTnR5}S^S+su\Rt YscMWSsWSDs~y'Oq ַVIfLʦAa*Xjv_s~/uTSIBЮ XC+mGAQtZ$DH" 96Rk a\*v/ӵ(o^/kDd ^+4%cͰjkNUºhUct%@B>f֗O 5liԛ{JpxHN@"&bChXuv("a9/tsLJ߱6֩B-w\.&RZ1*Mγ$[O`7luiyk~BK{+Lp/{Ǫυ7?zAaG(- 3AC]"7.S||L)5:CE3vwHYQ6B@ 2M׿tPt0-q, af;ՖFnX2X"j(Y  jX(f>eunŌ(Ž\dduUS[ip윿DJ˜*u`?ڮ?(^lwMYt:@53T(j7Z~tac$UeJ"%9Wrϓ}}a/}?NO^Jmd+e0. [vxF~yl&thAlX "b*LLAFx^]A)R%C{~ DKƼ'nANr11C]`FZR((j'{iT 3TJ"d^nov&Y뼟?_l6vw]ƌjfR5BzUK$H3 xQl"MP ﶚK:F-.eɞD!k [d5k-Ӵ6zq72usQLܧۻW?Yۜlb+QC+H0$Z#L-I!/~˟__xX=u.V T)lp)}a-Y{<:?K$3% aľY0|a9ZCE{DJH!۳Z X5f`#$3_]ՅxRU ZY2%M.f!2DzЙ)d5RU4%Yr$ * \O6GN Ñ9V0j6,!bn7]ڗ= ' +dZX'eTZQ > EVT%"j.W X 2"=9˅Ӡ dP)+VW1O)EL7մ!1ֻۛzOmp*mqx:igs1AB~G?N{l<0Q]۝;ޏr])3NNOZLGubH!آF4iL*(Sm$Òӳ~GfsAF o^x"5R̵&8)FVfXDZ"2cj$Sl$loU5jfL9[DLd?I^~A tj'ߪڪГ)]e=aCBP(EǓy_^ί/AADh8ʼ=9ޘjWgfD;<Ґ} #PUc0r&Bv@m&P99O1Z-VJ1a'slӜ40`8_z5))ґG',x]*6C)< F"VKɳlUm& IDAT.ky] u ^dMMz45U;ޒ&*͕ `@+epLV솣2V<r{} 摻˫ڧA%vWWOyo TTOBի˗/ddZsa~q] 'T/?X/L_gюO,n"hIR<EHFD~{KY֙bn5j8&X"s{ŋi (.@s!raΉ L0S-fccp1+!RfNPTaV8QOơ;3ӥ S\~f_{ Ig|(!sQw(Y Śt}w,C==Kj"cghSUN&* UidR ˆHE{fYKL2 i'$R'ȷwe5rϾJх︘Pv ]},RmVeNH:Qtb>mBD _ﶍfɖҦ$X Dڼ}^!jP1TF-d0alEduvjёDRM T1(>lR |EPbQC=4I@~P ["* Z"o/UTL+ZkZc'T3'"__|_ ],G={a₻Zb/A_2&g>_~V՟I9DJ*L`}ﭳnwjB:vWxE82 CdmZ&..o|%}%9 [ FD*v.)Ri ns۫k̤QUz}8;9>g_Z:Ha6$vuPtK.TPJ`F ]*Eq9=}9y^zmt_OP71, $R dMpbDl1LddtHv@]#(\E9ͦ]a5f4a8J1UIhG?xugGQ?!TI/WNfoCG;ߊ!ϢG'Ǡpi!̙$=xMDߢts3V!BbY9:62Ae%Wח7w$0 0;^AJ66W(;MxУB)*&JC622Dݻf 5qz_W^|Q85Bڠ_^\IT㓧I&æH]bFRV8ӓWº3fpu/=}W*NN B]ƱDtg^Q`AgX$`@0wbj"jҲu-jչ |M"9O7]իu(R (?'Op4}u=u؁&}?żY |cmh'OeKqkΙ-.w// "=m9jjk'^ cV0yL2\ŵ+ N.B%UP ?CyjtǽZf`Y%HExLOɼ9n֭] 7Z"%jMcNE&vwwN2b6 I7La72h1&7Gŧ./*y6h2GK%bחחo.#[Ry:L!]JGBߋԦ} NEߕS &~js~#͐&2Uε5  +ӟoAf?WlBhFTDt0)ONӇĺGi\j3uL-T&Aq62*:udK-n855AT0bM0s|sע}mQURԠd՞ӿo:FQ=|8ln?e2#TբuweF iՌ(m=9^EmL՘2)śښ8U9<сCHfj8`DR,CS eÁ>`mnWͭ C姟XϟXbb: 0Ȭ5ZO)173Xha..S DBUzd ,C)%Oe/&`k)fLB csddDvؚtQ'7EJEK!Ih*VIzL2Hf2ﮯon[eRf.<:D:=2lE~7/EOݸ}c퐷/"G};^X(-.[7}aۥ~{oN}pUVh5@" 80plCSv %rv4_W'h+vY֡> 닫i?SPƱkDA Cz$R8*Ld^AdP =^Wejm]rtLtt~gPRx;-\_\^湬>F_vd`f)XWf͊Gǭ% U\WjU2Wi S@f("if խM:1όfT1+E **S)D*!"ThÐsѻ] ZG\i)/~;yxgKp1[?}a:gu%L`ٺEэT~׹/C孢*(ϷGqnEE|,: lVn`Ǜtuބ(Q 2'=d7Ԍ iCFV!tAL`5z~M e6Y7\\d 7XJ2:B0"pq=gla%݇-"Tn- U{sf$% i螻Q tpԘBVL0 UAK92U3):Vlw.7w/~n堶 yktP J(:O;>r5PP/v/Ǔ'=2q U?x팞~`0!޶W8G(!mV[8aՒu> y~[ jɇLty^s粀3>Yt AIP53uEɜwѧ7܎W?|gL*6p<jePADfFd%VH&D'ӿܾݻ,A;΍gQu㒬c] w%P\z%ʞHzxNӌ0x?'gG'j a ק(|~[tF2K]]"T P `.h.Olb$FI]&48\mKsF0*b 5!t0`JM.q$DW[dY-48NBX_W`\њ79wbx_~?9"դP3sN\k  DƴWo5-%ZC'R8l}Xl̔nnno#W&% `N Padi ؖ,8J7.|Iuʘ3`S1>}r'"%}86Z:[ b; #D07UIC$F$5R>}> (VdoE(eLd" MKB-Ј֚QZдVMMoonnn :fEۋH2GD#c6~,.|TaZهV p悧g>}JDZhG 즸oZchh%4ݵFjsWUMa=JJvay*UL+ N4`!Q\D ȖٳE-belyOQ%[]ERP3T5ZB͛mHE XJ/eR"RTTh˟|yּc Hf"8-y=֑^TH ^(=/ -cVY? ]oC3jafE&3A-3SFDsٴDTðz{|2^D0iFV dhG-Zqf`&.RXЀ ST% qJE[\q|'gϞ8/sf۳H +ELҋ~}W k%HUh_fu#d5RƕP0HVSUZt@.Dl!&UzBTUw=EUBDf ᥀"xwy32(@Fy}q姿z-;nYP#}9$omr2cwϫ|̈LyHVS||P$da x% ~ַ{_! `A sf"ɔ:Ou7ջ[d 0 ""Xn]t5ep-Eڞ+2oﶯX D*n|zv\.PI2(-M])!yw L1 i1M["zPN֛uqum'ch )Pnon: )B::U:i/A@iѷ& Ƣ.VL3a"%bS0Y~Ճe T &EϜB !بݨB_ Zi)\Ǿ"EnB\)dO~e|x1 I܇L?[eCI} 1={dY32I1u+ɹM] E֫Up5A/bt3U u˗5UԇY$nnI&;7@ULfOgŢK΍-~ /<9WRB$vC"٘p1vxq%-Ir`ywLG 5YX=lbœ-n%3{*Z $RN3Q_Q uQgfwWחq./p)h2Ffݛ|}Ylp9RBމ8lޱF7'.\~CA _1o7o~>} .}7gt 8I "ѨUJEx6PTRM|tt$8VTv RaM8^JK֣?}v)RVyζ~ww?ך&=YLs" X]5NE軲W䩕F$YCQ.vsƩEQgg'D@^fq-,jB>x.(ed(ԡmL$Uu/^B&[ыoQTQGҺVH)j-M Y`']ILDw q5H!)4 Mp곟W/_8=Gm0Hؖߑ2<.o|p~oGΞ=olqHyΈ*hDͥf{": ͻc2~fIvEqIf IDATDfUVO-a2jRau:nwۻv3}}Z]K%*2皭e4R|~)QsegYN4!q_^N(U UBL0#XC(2gj0TTϐ mPE´[bL* &ZFA!T5%ʚT^T+ $-](2BH?xxx+~ zzZvo3XrR7?g?A? 5};)ٳ(<{n?B uEźktSU3#c?9= 9YMb(T8w\q5 C|5}%RVê5z}<9vnnFtAztU"nlu`IϿק,*EːRqtZ|\\,EG(AQHe;J!L7~">t #ZFqQ5f R"֭l(*ԐMTsCӌ4"4)-WwS#&S٣6._ŧ/+Eņ*’2t,'hڷv9l`ɺ qw bnX}U}rt`GM& CP^\i4MaX\|$C4b$#{?-H fF6wͤ U 5kB]* ȸ5ۨ^jbY3[C⍷W?u3&"y!#I޲Žxv+dE/w%ER;o~_߂g3{o~PNAf&sy/.4Sh"PUj^-!" "n~_WQ3q|P֭y$pZά@̚$!8j\Ommv?MbIW롛TrW@Eͫpӓڇ DCJHCR@Y{BV$[KP))"6NΟzRJq-`0kdj+(؈.gt׍FLg6̳%RuiL-*Vv#hxά2'Ij~D|gC`ɻ,E$wm|"h`Z 6n7;'&)6HZYuX~3NyhuD :8ؼCXLݧOOWZ5`)EpDIfWe\'dC-RalVTl Fd-Qg\*32*up8ul3BL`”3*;*Wo.7w"¤(T L$H>>Oyge}8ɯOej3y[.P} sw[CR{fo~JwΞg6IQE24n:3*1cڵ׼*cAiZ^-m_Z b D~7}z'ggO 54%*CL,)55qM"x2}^| S-k C{􃧛 _XٴMJxjSkS1V-  \l, QdH! *z<M^TS(n6 YG$[HFDys 411IUů~g[RUQ p)IOV GQ<ǃviEi["G_񇿭תҟ"4ϓΒt6X%b0^7Z8Ǔ'ff$EsXޛXveW~k}νo1#ITUbʬU', aV `CjH&"3cx{{}/"@ÆZV $9I{XBu]/ SRFUi'Y3_EDu[mEZIץWmFUC\R>"jBgTAJR73=<:8ݝ7`ΤmC몿 稩iN.WrpO^h`ĉ#DS@֔1h6kBz5$i2,e8=DI"h->LH)bԉj6}nx%uQ^9ϻ兎, ~YT'y!q˭J&`;,kõuFpx›>rNqmޖKM@Mn`F_x΃oܟ[az`!R6$ׁqݚ:}(ju8oQJNkb/" @Ϗ@Ιst[I TS H#33LGd`ħ4yg?&Z7杣֋y[N:WZ/.~(bfTiEhai0Sda[uy:t7sL&1,;6@POk-)gʹRc EwH1#dPi$:i6-yPgn=UV toٓkAi 9jⷔɍܼ:BA$Vnpo?ƮV p"Pz0 iDD-=>_ JPyiX*ViRj- M@!=?=9]w}u4M+fyŝ;PđN n.4 aC(iiJZe25! C?]mTsS]祬}ic+Ҽ*0F\,/?;\i[PGJ}ih&!#OdMYQ/CM)AX(VTd :hH8<",IR @"F?/C?1-@l2`|'ӧOGqͼ6PcKپ9qW hmvBMmںFѵgǏ|[}En9oV͔p`^ENHhEߝOb IZb`rgռ|yZM't瓣P;U9dxB7710]G.CYWFږc&b1|* 䩊0Q}գVM5bҭɹ6XueՉ $ t&7Q e:ѨHMDi[ ѠD AQP)]0SJQK2dmuݳOTݐDjGգo󟼿alTʪUfF۸M4n|)s{["esURFA"~+IW޿NvcZB-^h="0e2ֳYCL۔' [U0ͭZj՝-/V8"M7{FtaMҬ)PG a 1XəM#E**p>kj@^)jJf}+Ӷm$nbC,Ȫv*e9mRV>~,$ݽs75mu`l]ب@v\Fkf͢C$Z+HUpql苎-"sAx{'1n8$7̎sscvn8i ZmWy7Xp[G o|}[azl6աt5F2 ՐLQtXjX..5_.δ̛iK }p;9;_ך'-E=D;M*JjJ4(;j BBF `(UH* '!5SQ+դdR|9ܟ4άid54 Uu7IO] Ie]SII!"4T))g0 K #M r^ʔ[$ h5DTܝQ!"7D*z~|? y6N`$Ǵ9I)mtv3x $!Troo}w$ Ӎ=,;$J[-QqR=СԺ:t6Km4/"4)D<&8D=7Ӝ%' %M&{ӼM9fڢ0CTZ)V-( #Bba (*㇏=;C2TI&*=MvVu(hx¬kaP=9=?_^LbPR1f0 tNHd4 Pf$2ʊ3z9OĻjN =!I@wS\_&jӓ:# %D]JP!G6QAnWGmݷ^"nv@ k[m3 <÷o$ ӿ?vRz<1EU<VӬ{tqx^n_"55C+b=xV+}8Frzx8?<ڭO rj({xddOCW"Y-R[s8$IgX.a/_}KL <VyxxkHy2iHxӤIe$*&m{~ѝ-9Q"I1J84i(:_`@d4hӧ6ifAwJ[iEYKzM$"sג4S$lLQWw,_G0պ_j& `# r%C:qv9 ƞ_:x[޷}I?jqtW6CnTUAoJeHRmm3wC]a[jSH(BGE@ CWi7jUZOpGpZP)+($ڔXIQrmTry/>mۦ ^"#Z1NEnRb 2aLyVzf"uW^ ^](*I: Pb׭w 7ArV` SUrPyפHZm̵U%URNg'H?.j0PG鳫؆0ʝHlEy3Jr6 &ipcv3eܷP$wtM;?]/pFj5̆rqpmzD1\Le)v8=bF QaFRx$3x8ow¬Jz/Fĺ?L'-p ]?ފ%M*2iѽ|5Ő(ĊZg2Er\܎Jk0D$ټUt+ RAGDNZa."OUA؉boTJR TV Ҫ =LA|Gnˮ[Z?pc*Lߞ\n`6{-pI8GoۮVQB\m9dtĊJT^ļDZ}4.,]]]^Z4ͣid1vp/& *\JVk).p7$#!S+Dقc.Hi)Φ@K+C%%aJ|ͣz1Msʍ&t:tk`LnjO_׵B"2\=,tS^{U`b6_ZBL $-\})dg:0~eosn۱J/>?Ӹ*rx< ӶA6I3TZW- n64|V?0ΧБ擐Ƃ5QJ|"BBu R@̢Lq`Ū_L2yl6ɺ^J* ĴUABS]5)D)>V Ư>}t*NH'#%UeVw`wwDHFJ":3H@$a_ӧK g a#^RE"(L<'hDHjS")JkP 8#7J @%p]WOwX=5 RrSlӏOTlp־jb7F|vE2B7wy;߼m \zoZ],*LY]AqP`e-&/OS)*7C]lyܶ)yEi38Q i+M H7wnh;0+Rm;'??mfsqI~ǘ+9;s$OHjJvXxJ4a!qvzr,[8x'3PDD ԑR3V8|w<Բ4Rא$^p^0DX _\FIMIMx ?lfys۱ ,F(,g{c1F@&w{w8.V~[m])))+42F3JF֚bp鋕>feRM3f0ӗRE$ba򂌽lӔs @RDuƲRNB S&a0:$h6V$qrjY̏dJ="<9Dbpf.Ydˋ'i 2NGiI&YtدhjFR4K FxE8:p%CWP:gK!(∔>:n4st+LC #hRNDaet;nCExR"%vI OY'ݣgOFKuvZ|y.Mֶm|/'ˋ7~b6Ҍ9c&L5J OTJJry_=Lm#P_œ!2MgΪ"u6)E Z޺03Y]OI*cJ+DOj{1%9pFhB$60%(NQ/7+*#j(kuPԋx*N%Av?=yZ)M-F="o{v=Q|&Ac-&nw?p~xěf"Hs#y_!X~^ 5V8v^P /C99;[ My2E4c:ð{x\oʛx@b{w?.+BO9:'UwNN&0I65j*l:fgC_6{ 3.b2&Ӫ( xpޫ{4TK8ZdG.!KAӾ_CD<\D B21\G??^_ni߸oJ$Jn̸1c6m˶ܘy;|Vߟ:8FHUA-^&VT4앨czFˇ>yfeh6{,TZ-a"O=䃍gmU\ њWqg0f*}wo*ŭ0Wyk)#FIs{QpVMQ.[ۺBv;2)K&dC tQz(%эIBHzŨSL&G^8l6 %]sxxxxQPmTah$Cj-;Og!npZ*4U-g3a{ռm1o7kUz޽e ߇*,tCrx8,SH`[z'/ֵy]x~enۡa2o{%(quE?7s ?;C)\5EfUK0UewsIbҜ9 z29CYJ{)T6A0Tdl E( TD 7O$θ0z/V9C$(Ns#I %,7tq@.M2gM`ON_ <- "% 9{v>;~\qK0nFU"q__dk~փn$G_OL"ۨ Ua;ʀJ7,`U9N3Zw $"btH"+cZ;·iBpB-^$ff`x'lX;;IN)ȹ6F/!JJUKc^H4z߭ʔ|&#J%p"dJ(m_5hvI7޷ng۷(2?op5%NP &/^= P<$BB[7"#0>ztyr2I8@w꒢M*QcUc6UPrVL OTMP׆Օ2Nvpg&jVa]G YFϾ8~S)ƒ QCk *!G e<}a,܎mA"m u%KWtM<# ۋzJr鯾OEfDRfGQ#c!{JsMhd7ceJ Yqf; 촥Ӌ!")Lhx8)`hط9`1^uۈϒV1Y=`h=<8ٛ6$e%4,Tj y Vus1'A"R=o]#Fg@"BE)ݩh)kG:fR~u~~^Mۘ{L)#э_|O7uq4D:ꢸV-d#5ɭ$ ?{+ ֣a!R 9J!Sss>ǵLw(5+GWsn۱W8Jf Em}yѭ׋԰:̕hlQ֐d-51(R-VVkpC2\^\|>oN|]+h$TajTIyⵒq&(,ggsRd$zX5M?vUm+ܰnedlM(T?; t"v{{ۙzW#/7{3^ݝ.6 7s{G 'a (#Raȴڇժ  2* ]st]nc2-Pfi6\֬.E;.m|cToE!)_6'1.H#E$0R(ɇ?샟n<ٸ)yMہ47Z9R\9P..zU}}{p{+L[MvR"#0zVk-Ài&*RsEܒx8?t䉤&(#1I{~ݿsNټeo:;OG=>9]].KNhҳsnMp+FD;ܹg6m8@*JȾVymg ˳ .DҰ,泝RV^Gފ 01"DΔUHE"4* B&BU '..N]խՊ/%Ɯ*`%kc\o^+mˣm΃߹n k쟋0G½2F,ZГ/y`ӄ3??=_>;&Ӹ87Z(<|gqx{n[ͣ{rٝ6gMWl1Z)p$߸{e(lRIE<޺wf:e#7 R%eN燞zԠ R 'GT#GlAT- V2$DG/>/N{5^ϒK۳ƛ4ޔl%:dc"n[_Fo*V~gU/{{(kKjjwY2~@DC2i!2=?~|Ȋ JE-д ဎxxX~rrw~WG3ofR㓓?{2kV.nu_)V:!;E1L֋GXd\vϫ@8C8ĶHStH 5 p !H 5CPD:8;[]^!"@ʗOՍ[T%F q3Xռ}J>ͯ*DY{ZVB* X:ir:f?-C%2: Zi1^|ѶmJRdLPx8<"Ζ;wۜ|pO}gWKD3VuNJwpYmݝ G7D63ZЕ)ʋslI 8K/Ivvw|i`aDx-htlVьwvV=ܨRzzUfMveי>9yT(VqVeUՏ?8l*aw vۭEIdD)%&T $r;Où73AX"̼H 3pX{o@>^,[.]]w^u^O>+}ۍvjD%W! :Xh$#g't !,`͔z}nIE;'0MSSC2T35ӽNH3Ng a[mE,Ds)/Q1_x+W7׮4-IdX i9,q!8 3ȧz8J!E,F!S5 STNGd4v>5!疢Hѧw<dpI%!-%"{U/[ W_51}nK7H0 "c).e2!P10'pt]0V1@X֮\ &$S]~s{w6%O  ¥U=<;;ڍF5ˊN9lVqhvCW7ZkWkmJ+AP,[Lga>ntbCGH̙W+<̀PTiD󂋼<'K3 XT1 )KLDޙix> 2jy[wGn. xF{GGvK|_^[yj8Qi j,)#Q b:1#PO'\d`ZƔJ`VT}ŁP54:畎NN'AZiJVvI8UHe!k[7o۝R#0,d$2'+.10c23CTJ1XUG%3Ev&@`=Q ` א!?}n)KHyQ@v"/G7nwkbK3_X.zؘ)A )27Yb<zOf1L2H*Le/>y|0ηVm"V^L&ggA,di!(hL 2G(D`VjCq}mU #Hs%N̡l |2f;te4Ępd!2{"_ A)Y`VNCow]rG -sJJ=!3"٥9 *v| s ʋ_[y~F49[Y@P(Ր;?4$ 29 ~Ħ +eͪPDao6[WW جڬK^766k~;zCIj" i(B(\y +Yg4@ ѱ,ESƵb$"c 1)L,FPIC0;$˼"2ȑ}tbLi6,>z)oʖvsEх@MayVЏv5Khwvw_k)iMLg5m2`Tb" HE , g)F5h@""زQLh6?8H'CpToԫjh6dAj%M=D]uQ12A ȄaAS |?_-9˚㥧d_/6`pgwwXOL?-PhoHC02S\%p M&IC@,# P" F(E^$I#lhb&"'ǽ~n-cL|.!NF,Ni%AD@&F0jL4V4NĹ]4Pt@CMπ"! s*FR$M}V֪gǽS1l@2MF!xDH !">Z-?ZLT~_, (Vs~ZQמ51t᷿vwS LMs$UsU8.Ga4*]%5ZVzmJgg㳓|2u 'i bjLM`4ΛFݬ1Vk RkՉxifQ+5hj4y$)8Q_謷 A" ,Dp)^ .t>(+뤤5~:CfQ 1ba>'&F=9weнO*EP@z&>MjԻh<;;r%K}RFG:vf*@j!Ft:sLL`jyХ⧣i>[Hg׷ֆhDKVOdFA$y•a$j P(5J`hQ ;DIQ|瓣~4$Sde?w}jDheд*DeLػ|+lջtu?kiȑPE "8bLg3z%dzv:budhqTjzWI8YLLJG'Uk@T*(!*2"b:/ii>w÷d@بո6VҰU1M I$!*Đ"*AfTj\¢"p2O狅!SNO-rFZީj iU祝9%J#6+k?[G +saEUU]MM $N)iԕ0=A5#Z# t2d>{]v7*JZI%.\dZ촏Nx:*T2͐LY̜rМgSYL<{77(M U0ȝrVvU"qY#G.Squ(eܒwwZ'17D'jhjHHL4` Sy#)"0"QYve LE>;Fh18 }.. st7;k7?[ӯw +ۄbrLVjgAe1D4nZfS% $YJZmd{ X"4Dѫ_|ML \&WPd-. EH0qDs4‰ ad@d''g+7>ҝےv/~v۷;.]gY<2Rp8"[l|C=G{SDPò[5( GY% q"Q{a4><9v4%LL*wۛGG=D`O"#y((puT3W/1 J3TʮCQSrL@ C9 fj45hm@|4g3204@{-%xtq0XR5W_W~Y$_7P *!1'I%칿dptLF/nm4-xԽ9'ǽi?i%#@fiD@Ot?=ntuxE:99NB0CTp%F -]޸ɕeM( gӓGLP$FC0NSdY5 Hۭuk׆QU*i5uBA`yb?ITqQ{DclK'TFEf3y3lJ\<[4e*lDR @5PeB fH I=l6 flYk^$MBP\ܖA j~C@줋ҩ /3X"beF$j t~H 3SKqy&vjkvsJ;i&IR^kh* "/#0EUdo&E\%~;B(*YD&Ƭ\ \A F306!SPZ; 0C:~ÖDK)ûw6g2t[RU9\]|\2NJZcML7( e4TTV\g>2QP5vbK#i5t>߻Ãvmoz#<ާj÷jn3WOOO"VJٙbW ;ϋ"޼VzG%V4#1X`Պ8G( U!MQ1EP* 2, D=!H'ðX0" A>wopt\,@X,ֈωbp*7Nw^xa\ocoş|ic46 CS 搊|E[{E^ {#]uTLyG7e@A|: '`4]CjKRCP ask;ii h)A+հZjFbL-"!E#`gx1EA0xDاG}1giHCzX0P]> ٝgerj^oK)ivЀA"1)0s<:=;xX, JFXPU!!Y ?yW7lV_Ish6F1;{$٥.114o]]۔fe 쪆I1:; EE-XGU!Ls<+j(>û^diۥeR p)ǮBܝ_yy=%&ś/{.(j0rj0sVevRf**B04a@`f& '{'W;7ﴻ" )1ADƓG<@1BPC"bfRU_m1!AQѨܳOadfD * MTFZ&p`bj"rYCtqF+3m>:5 д| "_'^olJ1?> [Vm#l:f7pٯ<ӽg*}Y|b0A1 "@i 13W7.5BRz*'*2x#$&S(bl'?@?{o`ݒ2+4t x믬uk?d\zye D-.'ܨot.M#HX,f Zntvvl_Uןi=13@de~.Z0(|zDHLƾQ6Z<C$ER03 Ȅ̦ jLϦӳ"1Zg?|spp[s7,FRUWы)i51Knu U+@UDFV+FF=~LD33x2l Wwv_b1 s-vZ}xeǜ8g +ouyd"fr옰XEͭ< * b!2qݙ1"9fGE35CyCbs\G~I*?js+ tUUNrg^h\Wשkds^o[V $4p&",tj""b:ݻ"*###U+YZ٨׮\|>wÇC9RHfDLE(ײkW* sCق4H `+*A*Sf$5BH"A\O_B[Ԓj@(Oe2ECw_zbo:ȈDy(ãp\ll%>VCUSUBC#T l"Lɓ;won|2 Nn֬#pi'#$GEQ9~V֨`8U\sB` `,XT "Dh48><~7OM_bV&\0Xw_{_Zƚ&;֖""j0ɴ6fDཻBc WZg4SESE.b1NΕVȋ|ٮtjk}lL@ކo5mSYnQ41 Ԍ<`4S@fL"gb8E&&_ʄAi%(KJm kkb LY/W]`` ͌-+M Z):˥B1Gl4?yٽzkᩎ̑KҔl Pc0ʲ͛n"ip\;\e='# 35(0Lx÷ª~M}ex* U/+woZƚᾈ2Pƭ q1OOFCIN<J! .X@ 8dh{{QN`R؛h\o]!&2&MNBnbD* sDʈ(␋zE{ky-X)>Z:.wױ$kok\HU-Z@38 ?Q;88>=3ds5#v Ć,e *GV0hP|42q$ȲF}WW:ݫ?$,(ٺʍ"yV2դv1DUp0 Tѽ>{}XrLT*Ud];_|֔ƚhi,ca2?4feY󦳳O'y>!X!2H޻'nck;Uf$Aεk|H:`=":"%BP$BVdpŠ 3o >\%Xqkz᳽IBۻn&M|{olaj*'Obv;Uâ(8's'i A0eD)1:X a4^ݸAi f%)'E=i4hˀ^8(`DT0A#bb`2`0G'78>sڅvl9YItX : JYjV:-1qPS2`  ͖ j\* WmuVU+ D"C;~7+otx(D+!c* LT,`"G^Ȁ{_W[NL Bt^}YSkb; d#BS"LNNQRf-E@&@ᅴ/FlQ P؀A"XT WE@2fUhVBBP޾ovgӼ !قdAjvzpXd *ãoيV8`1ad#+ĄF`T!RhPUMڠ-*6[Q: iԪ͍v+< s!Nz1,GT dw|wF'2SM_|a-JZcMLOΟ9d:8fė{Ofgg2֘"H9Cpj`zq$kin4\yn!8*YY߾B-h1B(1z >C-Pg3"}KGDO!޽5o1d0V;I*bC>>:=Fb@bNU-),mj`LȎ]Fݵ7\ *蝅p:J$qU5qd2gƲ( {DBD"rf8 {G'@itxy6*#"+ݾ}絗:5Tk2{tW)ZZ38I tv:<~yTJq|{]@ jLP*V=m5F6Y,N>yyZvn^O6D$5$"bXύM82" "f)?{ηjuAlTٝ۷:50.W0c EݮZ!ăiX""-@0 r,_-CĖ!\իժmoa Ġy<}|0 ZmƵf,r){D"BLe#d@5Q?Ԕ޺:n//f%4}_yuXcML?v&(LOOZوj.DMw2UJ!R9 9rfeWBVoPl굍W+׮QfQ{In\ :c2lwejCiB|p+goٹYOSҪs ^KkQkb|FOFbuكJXsYPE*ՇH`YyfJLUoV3Qjjn4kmL̊bzrv|Aڨ7v["2+мi-+Qy'"h ?8*loK6V"|^XO~wgX Mq!;09y(7T$D0D$ DCX% O93S| ldjShj|<9㤚fVh@>qF7(FEc3 \(a7>zޥPq K/+k'o\5Z|1+&SY,ׯ!jZ"Qi^E5p`@Hՙ l 0+Y}VJYCW N^ ՚3BN)3$LDF`ӳ7XrZË7 5&_o5|:ż~V dV\7BQby[?yp "bNhٍ V='ZkjӪoo7i>p1_d֭N3'#s^Tq3D0ttxHn;xY@ fvg/5%O01}ﶺ|6XTmR1`氘ONY( n/֚uXTf_{QHUm)/p@&VY5(!=l||nn4EB&ʜDGp4>>1yw/NkO+y=%eb2yH _o(chhrǜD$1by?!ΦF)xx瞍?>XeF@ype )ԚZYh6ХbF!_ N>}uۭ+ZP "bⲻ |1:9Tm$o)~5%烘~_?SM,6Kq6}$:H"ҳ_xb 9eP U"ȏ|v604!bZjz֨ׯ]fTFg{gv}qm̄Lѥlj LĉÓz{T:_.4_ w__z5%爘g9]+Jr1Ηкu9zH1HzrNcx܊w8kYV5nV@YyoW7(C4|_߾:-I 5 )i5>o|72l}<>?9dz5k  bPG1 X|n| ?ɟ80sZ[nhij>&fӥJI%NT!ӳœû_j]Fy߯EIky%oU\*ja<9aTѴi?RZ>'J8Ȉ( DT%r쳽8^Yd̲Qolwׯi,򓇟EuJ᫩zEK8ѹ,'?y?,HK":W_yע5<ӯۙKh1O>~6Qb_E < @$E>Y E`_TR?R YԚZS܊!2+Axxҫmo6jE%j<"Q0 ˕ֿv_{饵`5~{fo?կ< ??:==fe?jݍf}V8g@pi!x9铿~J}7X$jhԷ6)J`ET7[Ε+rَ)"h2<1H%nk/EIk =[p5Jݺ̍V[s/~' ,:CZsi% lL={pÃ>rfD&>fۍ}98O?(i+8<~aiE3+/J5%O޷al\ᓃ[_|{P.|n^_ͧ:q:#2DbIڪl_F}!q>=,7ۍNǘߐϦLJGuћw;m)@0;kJZczm<_|?HG.M9M~K !E?_g`f!D Ƅα.ٿ>'bfin\Iwvz3;}M*YwQUϹlaBAQЄN-SqZ责vƖH voZ-ւ(dFPB@B0e7dwd_qNXEjؙ>dNvw=smd $`cɵ3a:$/_m]9s/>Ï<ȣ~/+@E*P6qg{rKoLCU)aC)HwjS @)%#Q/6>5HHt&$s5vz(u'8/EI_0~ժu50<Ogmwܾw UȲ$'rB?tD@IټnTYP"m hNsdMbY59x08eN]N2Q)>p35N⌧M]~y-7_05{oYl?]gm,|ܢi^3!Ib%hlwHN%wQ6WU9-Ι>ݡ)&cSNh4<ೌdڴBS8=}`z.{E[$IBY*)aOH#X}>)Q,IٙBٔsɴ&Cɉ|MQ-c3n<֭[3tzPq84EQ,Jx,2׭57/w(0D(ڌ{3f͘=̒ 8I|Iq?q@GjԏX# TM՜B(eĖ$qI Ȳ,4L$Z rIL+ F#ﷵG> /\PQ?ouHJׯ . b@Sm|JԻ+h0ʆ O?MINLNNڶֶpjQe5`-mÆW6n(Iy9%:u]TbFҰ9l!!s~muLē5554cOqws#}v}I1n}Hvfa<+=j,4D46CaƘ8VuםyYY9vO~N_ 6w@d2i0fYv,x5+mjڻweY!%#@|ݧEQDE!J^ok,9 0O߼y v1vcOqƉ@iS=N33;3mۖ;U'x?Zm{c˥r43L#;7+2{oG۾_Gbi߾VӴ?VĒD4&W_ݰdOsPsL----J&Js>9k}au-JK!dMMMmSJJo)i_߄0yŋo,۶mC7q#wUUUivQ%GL܈ a a a@a@a@a@a@&@&@&@& & & & & a a aV>YIENDB`PKFGP-Pictures/10000000000001010000005711DF58AD.pngPNG  IHDRW3sRGBgAMA a cHRMz&u0`:pQ<PLTE  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]___```aaabbbcccdddeeefffggghhhiiijjjkkklllnnnooopppqqqrrrtttuuuvvvwwwyyyzzz{{{|||}}}3f pHYs!!VytEXtSoftwarePaint.NET v3.36% IDATx^Ŝ{@M[9N:887qu bD/WI҄F"nWf([D tSQ媉yD*TzgG}~?:[^b/7 rNObCr nrE˧>xz˪thtBjz@mx6|KzƓ[JV&.zJdῒl `@w )QgLTfSpCGOd]/SQ]#w]$NG ޴Fb*3zrߨ U 0K͓qAnbG깉K+ ZA@}C^_Ibe"U݂N|\ mzx:a}mHnٳN8 |V{dҨqaEp&n )LaU $g"҈>x%}tg@TwC7_Ss4/;DMVgCcAgQzKA512b# J.>H+2#Ѯc>*J1䈋НXHcQ н{qFD߹EIx8p]duv +FR@Wq#T=.|y1?<&oJ D/A+$E+Ns_t#ۯv@Ao40Ymc!} WHq)݅PEryx]FF00{h@-)}ԓܛ2=)-.@u>dHC@gjem8F;XTSL?ޝD0:CLKbŔܹFVmIӀ ]-B 9f C=%@x\m(2:HsGF!l+8e0ዒMnSb[ް2;%FLsrg[vWB 8BOfDe6GFʎ*h+!|-yjZ <],4-jRO?6•4CeWC;r˻m> +\Mځ-CANn; E)y@B| bNTm/W:LєY cJX5\4@IsŰ('#@@G͂o42;^bBV'L8_6آ[JO*:B\~LR-NdVVO2H<~՞U2%lo-WB< ǔqC{MϒEB]{A[ m;ȭE [mkoOkߍV~xڷ('жՍd 5tr!BL(aƅjGWKJ!JCw%]GbR3.,BnXe\+Bj`ZٛWOD: 7~D W4S֑,'ι2}Lb1.Ձ kt9mY T Zi@2 >^gڤ>|)XK/ZWcl7P|NE#E*~aSrڠXz"f9+BGu* O̽ɐJx5yP?0o1|F$ߕ`s1^ys O)c1Ϻ~h)|tЕT'lXq oڌ^lcĺ?*{snRa泂Rk젝!5S=/xʿU+ ze;#3+p~jvjΏpUiI}rʳ熹zvi90XY͹KMߣ;;\O|Kn@L+@z%`-2?@0uDO琼rfLVL<@8CR3CZпC VXN=7c(GGԶ|־q^wΆ;ofD)`Rrɲ,Y4J)T98k|%Y2*=׿ex(OT2* NP#V @,.2Lc CE*-0#fu@׬Hy8Ca $u-@Ar,.nTAI!_R ̒HHy-ۇ$3m}xVvU{L/=gq^< sVׁCA~ qIc XƋ^\^zt}8eZ@&&Y &#T  HWrΪ>kU:噄@]^+,`s0_&h{&@ذLr4o>ho A%_Ɣt P1$[trPP|TZ>aBvTw>[qlDž% 0 B]8!r$_t՚<dfdHԐmt6YWWp6N0&P}邱\WpN)W_X@wY}hP ~9z .Rz}l/ZJi-M*A?5:2~|ʊr 3}}}BA.N\aZDeUB @z[' c֓|(=d~ &m2 Osce7wgrk'VnN_{ب̼ y8|(;h9FV+]-o;4k,'px1d~ӟx!IǚKfK]֝"~ 3FʚQT@oGsCMݒ{I%KjGE  ŒυB.3&YE7:,ol: pgH|FaA>V }USy틉t^`L87N8sΞ!1KMF^e+I׵:ΧeW=IENDB`PKF|L((-Pictures/1000000000000101000000564119C84D.pngPNG  IHDRV4sRGBgAMA a cHRMz&u0`:pQ<PLTE  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@BBBCCCDDDEEEGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYY[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrsssuuuvvvwwwyyyzzz{{{}}}~~~ pHYs!!VytEXtSoftwarePaint.NET v3.36% aIDATx^ŜC`5ETIk"(7BTv1PC.vMx1EC%d)RTnWd4Ea2̜9;y>}3sey g۲ϝ:Vy3S-nKj?/;v܏m}r]NT_"ba{mԷ}NvR0K6iSvNB߷S׶?ck"y{:Ly2oG*ϬDRv+,=Z}JGw%f@owǕ3GK rvH[&R54zG^NQP'O` 363b4IEAR P!I?CYP9bH dpwa[woSKN@Zْ[<15^6F3abCP5Ҹ7S0+$h&gy0;4 /z}Un GVSXG؍U`Lw0<"*|#rGY ʿAǍ/ E7׀ƖDzMpI}ST(On 82m#IW&28VzU-'OL F 2eo)Om3'Z˩{AK!2F~8g@N9ip>]jIOLj? CUvA^j whB+x b|F ~ƕY7EsCZnCe, ƕ~O۶M%ГͺC"qIQ0n,[^ބvy9 LDAY?kR[`!@Gm:6μDDZ ȝDI37xzҨʠI ȍP?US8ٔp, Ʃo9bt +ZFx76 b$_]ر4dKV/^0 |ᯚ]~A5 73yeƜ V\)eN ֵ&DhC 224 3#P@w}c ze^$N0 ~W0 (\^f,AkRk ]XE-0a# W5J0… 1-' ט@eަQP.X&-G-KSvu" (o@?شeA7(_O60k@<}p cV!UX]o[C7\fܕns]5r=eg6\[(b9<zƂZBGj`lz@րi˰'hAe@`,Db? 3zC[#&jrJLgmZ㋔GA j$`At45P6Y,q]ho*Y5ׄ^)Y]Q9`PjUfġ?D[TIWt6{bf.(i KGco2 Z"}3 P4umG WFn8, \ES@pHyJ½2.Go嗊Į&Z18˺e؏ HvQ0*h jɼR:gO@Ճ(e5ߤ‹=Մ0I5MU#dĪ1ԀfoDمe.L8q<;(rϷ)\Pwز@z!=59gW\@v4'y'??ף%hea~/ Ddh7) ;J#_QO |u)ZQG}$=rÓ,:T]sZW!2:t]kPS}(+~قN,LTwxdBE_{pl>soM,goʈ2ޓmsL _w,+ܧL~$lI e!2% BZF`0sNZ[$<8cysK:HpnoV,R:B.n'`OD&XvlHfjxM[ |3 ~p]Tgkua-)<_Q*_ńm@h52 b7g9bb{< U &XS&WDd3ܖp:9K"2Y&sO XѴNkY,2"{G7!lq "n*09JT5Dk1n(oMwEuLŔ'BDߏȊQv;^y&Uה^ŕD'^l6vνi_~6E5q1A[\m"~etpfәϢҘlg!511 CDdC0ojWYCG5 UeΠus^j D˽vlO[=?ζc iA4蓀0PPu_Or11, ~wWemҨ(<2cI4*N@uvO#]&&D̟zsz'0e^"򞸒-K˴y< %?hAypD=p4z[IPbTmlDè Qsν\6^ ~>vs^vi1ex܆ܻsa^qнU 9> S9s.C4 F" Y T5!W&FEC=d4|@НtO(Q} .<@PySv  ,vXftZq~62/g>f\ q9H{܆`"7tK=P%4N1 5G[)Kl*&Af MB!/kdWסּK=^@gd78θ4_袋ȶ䭐G9|rS2bmYL5~V6X"!K90 W:3a`Lwp|L%֚MmTo8AZ۰F,2krhH+ۏ.}րLFwV@1%b2X6dPPߋf@WpgR| ;Qى pH BDG !:mTtV%HvF>9Y pʓdf߳2@}4fjsHמk| j{ވ^oeJ{i5܋8݉Έˢ"y C4} OՁ{?*sQy5s׭/3"8s.ZQN@ } $OCg;"73 ܠθ| G}YPDח@C[V@V Ur`GmcL[d_kVh,",z/CxX{.8sns˘IxC/FI:Ȃ0]Z'bPf;%$%0SιnK}̐+w;PԼ ^rDC: E_cZuLtRsg-VB;^{|w!|i9L1Sd_#,|-R(# [9ݯ*NAec8D䣨C]=KiGv? 1vEE^N-  M֩^o&YBfX#s7_f#ZPf CPe3MGQd+/vνaxb 7n%G33Ty.\j @G<`W lOLx05^7M%V:P2zolGrWK7M)9 x%F9ۛ{@gP1$pRkl"EjMu'dk#,Ed~樗>ty,FH'Ag;sWvɛ ,*lPdWtJV Ns/EQlu}DZ23)AMvH`0?Pr_`70e:rM-#jIu1I 9]{Q}((CEБ!Ђ6 L>0,7mD]P; -[&\gJFo<ؑ%ttFA>;}tt zx%bwv-Q~N~R`^yMz&"Gu:|_|~$7Bg?RbFw,O_]ѭL!mAy.od+šĽ8*G"cݟGc(ЙwG!Ac}l9PLtx3ttcNmҰfw"+ߊn ߉4:Pw43үNg&,Fc-CH8RFks6Dž(=VegÕ=Dy'cI/UP5v |$j[>0B2:dXT|S >dchtm6yE Ϯ?[lV^h(3X& n5JC2 <5DZ t A"6ώDDk2{49Ws:*'4Qs}uUi=]F~vt܇ztR;HC;瞡>cH<vv*Y~y,kʼ':-)3$7C=d/BFg͗c?180(=m!{481%sGO-׾|]ɿ@NwI?O`/K2 9hІ A?3-*꟎ښv{x້:?CQ MC&Y_A.C~ d D~oD =ֱq_AWm3Iζ3LXFfVƴvBZאu 4@z0')&#kg@b<].[OJL|$;:<N*/T| 6X> h+ڈ~08k>}OѣlR*% +5ܹQHw\@\kXtJbc14_fQotjE\5I4~R:%7psjryDM2xgk{V7Ч>>*7zIavf 8*paª)W_5:i6`koك(?hLԹIF.0SWч i= KDS%xN@)J@6(~l]DΨ<\SK >DY@#&8ץ(~zi`YxFJak)~A"GA=nktJ8]ȗQ1^->&ZН:3wMҽA;wR*xث!7 2<<6c6FC "it'W7^;4Pj4 Ͷ:Դ9 :lvڧ򭨵:R9-9>' vnFq+K8?BkE&Cuáhir+YD]^HVڮ}4,ZoFls.ԛlevf2>cV^جR}zL1,/UwwjU>Ѹ!-oV`6[eih- ?&MԓP- m&/W5qr/pB a$٦8A|Y9,m7Qſ]5m8nE7Rݰݕ.{"%H=TS!Ov` Zg_G*{FLhvJtR['•]2#+S.|ƎhէQ (JӍtgS{И .<_D7W4uˆ.۝s?+Pfp n;='ہfpK<['ED#bp@"R *Uv,ǮP45Eok=J53xD̨P4&"uNCEZJfT x ~;+2Պ/Rk 1ƺ>оHx(W1Wzmg"27 VAec+4h3m-o`02J܊H=TxC; e2j)ܠu: .A"(~&""oޛED5oǟ+I?¯ f\bIENDB`PKF୏-Pictures/10000201000000620000007453687193.pngPNG  IHDRbt~*sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<xIDATxKn0FJ'E i)ɸn&@zysM,[l{zO R@@ H) }AJZL3kE@ H) R@@ H) R@@ Hiu3ۘY+3;1o-֕ƖrIw8/.KH:4`@c@xn1nt IgCP1Z# h=0^({! zaF@B `!Za &$UW9a!jx !c@@Hn$'~@ru$.֙Un0KBTB@Bd3;/Z 2ݵ=H8K{N: (A% t ʽ_-±F1dfI =W)>ؿnZn H) R@@ H) R@@ HiuԦ" R@@ HtIIENDB`PKFhgBB-Pictures/100000000000025300000253E12E18C2.pngPNG  IHDRSS# pHYs  tIME  _^ IDATx믭Yu7.su{}ΩNAUmYc..EЭ8Rd)D:6`0c*b)[vc[ӝ|M۾wK>wR6]z֘s<σI$I=C$I%I$I*_$I%I$I*_$I%I$I*_$I%I$I*_$I%I$I*_$I%I$I*_$Iʗ$I$|I$Iʗ$I$|I$Iʗ$I$|I$Iʗ$I$|I$Iʗ$I$|I$Iʗ$I$|I$Iʗ$I%I$I*_$I%I$I*_$I%I$I*_$I%I$I*_$I%I$I*_$I%I$I*_$I%I$|I$Iʗ$I$|I$Iʗ$I$|I$Iʗ$I$|I$Iʗ$I$|I$Iʗ$I$|I$Iʗ$I$|I$I*_$I%I$I*_$I%I$I*_$I%I$I*_$I%I$I*_$I%I$I*_$I%I$I*_$Iʗ$I$|ɿ_?_$I Cȟ~)>N$+t|24_w:UPww5g~JD?U$I*w0_?~O "fI$||>Ou ssգk׈h><Pp_0q)]ZM͟~ӛ؛QM$I{ܗ?|cۣZ~LZSsC׏#!Z0 DbB`zPg|$IR^YOKAޝ~+wzC8v]#DT qR{wL>I$|~>~W?strrwΝkMڬ8^ycݮ6G͆ BcC*֎;#y_='I}{x>vt|:l6j"y8;sq5Y#0"JLNw]M"@DDHH\͝WU^B$I*_Gz|zT\UUDTv4[2M0]*f"\Яzn`T w[~=oG$I*_-ӯ?O}ڍ\|\LZ]^ոZϭ(]7ChDD[K)v])JHfQi}̳$IT>qmDEE<_^#zuDQ#Rx\LDjf&*De\ >3USg~ܞ~]mJ$IKoJ]q*"MKwWsks] M ,A@$d20UUmAJu׮7@M ;"x#0>;.OM$B$IR?Dj*2͓MM4ժZCD&ơ;:rIK!"v#"U3HTHŵIlsd7KWn<0"f5gDb""B0Q,rabFBwy3#&IT=?k6ynMmk!jJC b.HHV;8B63":!"Q"mnMMꎀd߷9>CML ),i8yO|w~$IK Ǐxݶ T奃 󌄵v80_岸-DԮ/ bSQQ7+t]oDL""Һ/SV.D-~LHqoxͫWox<$IKG~oѵ7DMJ r߯7R473q!9*d&B5& aumn`VKR(6GDw '81"!6yiښۣ78vФIk m$LDtx;~G{$|ߣį|F67iMUUۼ.J0O" ]ׯ+Σ8rD fKgP!Z[kDDH |05 B}Ȅn@ELEE aX 7TD9 `KN]3obwD~ρ$IR /~?=ڮ5GkXR"D rLT")Yy{&d{| 1 2Ɵ~m0IR)E~/ѸZ=! >O~7Ffj n֭R:`^jBvG@-%&75UR\ENit.DJ\DA]p,ƀDn LP܈ݹ#O5d=Zk{ UsBrF@Viן>I ~]ߟ_͇dg046#b)uW LHH\)BHLıID$LW /frw.ʵTEpYk!*5~B mbQ GWj&fnDb95vכ7j^ɤZ\ 9RA{/LnJK$gW(_?zߋH31?l\~̋} 2.fPKf B!9jnx M*`xLL@H \*":39&Gz5" AI\mng vGhM]?aw-i\~T՗5Fޜ_޻gW~}|%Iʁ!>#j66}ٛzZKê2ct†.LDšDy5gvJZ炃}a=.ˢ&Dȵf{x+0MD Azٞ^?eByn7aߏq f¥Êk%"dr/}7|#ۿϱ$Ir{ܗ^??q̦n.JZpR«ohU嬈I) 5J]ש)dnMCN*l=D_<n@"R rFD\Dܢ)(v=d bT7k-Xbj5зvB[/kߵ PPm#jo~ӛ|%I%W6OfUk<ٽpX6\Rj-JWH`Ґؚhs33G5a!F@sPi"3` ;ĶKsnjnQTnN\J)*+!\4F;[D@DDtG@X"@aEA#8cT./8~Ee:L" J)fimn j?jjh"_ȟ$I^?|۟0AE~Fhu]_1|rS`K0uPuGq=G`@brfT"F"Zk殅 , c"1E5ŘVC~OcXDqܞq;nݹu (>J_b:ԬTsp@Bwi]7t]^\:&D5iyCϦ%I3__|]mLnj4CWሄ" @""FWD% 1<2ђW& ^]]W9``D`_:F!cS5eʅ _\^9;nRі]UEJW,HX RJAA/W~rK$g+\;>y@0ݾ*8Ra’}C"\tD2ƇGm!2++WHn,\8Z5Q!Z~1"aȞ!/@Z KY"#,F[sT0Vxe)Ue>L&mstLX p"+8@!zwVJ$əﯚ_ga546/`N4C_ (H&*uݒan`nṈ\ \vap;rXʅkٲWw 919f?2ih|W]R../ !v\eBD} 6䡥*F\>Ʀa5Ծ_"g>$Ir /}<6iڦz=&ЏÊcUh6ϭ5b "fȼ2p LMKL9C\J!"0t7RrjfjBHׄ⸴!4/DŽA"RKz]t{ܚ#ğ1 <ϓܖf:"R:@ RbL6 Cϼϼ}$I*_&/~7?ώn4:󼿸vSS%ոti3s7@B,jvQmLJYjAT݇˙9 @D\R #̳]my䅚{i(O'-pCТp\'{wU7P3p/ a:iZhEMڸ9:񩻩B:Edi)2b4qXC?g$|9OCtrM^..f-έn6a9H:(5 nlBFrؤ3R0մ'M<.Cj&-cC$\rJaT&* $jj"ܸB _SDU2SjVmnn:LM=CZpطi.0q{.ĪNN-b@Upioﻮ~.U}Go͟$IR9Hx[kښ·tuVml70ϹUBUbcN1u1@ʥV[U<@pu'DZr͈ .Becqe=UieԲ%%PU4"yf݌MW+߻73DgjMi{dfZ?1fz{rCv]gjFD}?PȀXkhEtIU{?qM$ߙ/_OoiKkִ6˼;~Mmgvzjwj։HTJ𛫊N\BVɗ L8EJ8Rh_'__hNI!{?|ᰟ61S=?ך79sj>*U j]eK>sWh33B0nKYaZk,M &t5 bAc%TM'`$T#b47eNFDqD*MEKj]߻{0 *"sYTja=Tu+ Ke_~ݻ뻾]X8tpzw0xMEx B"z7>7`n$IwyOk6MMEYvjSk9ﺎ8VQ0HKW:bE)"/&w88̗RADB@f=$JFb& ˮ6G2F DΙ,j`M4%OsG|xݎ]maKLčU3pA%[@\0nwy/*:c?a(^ɷ@K hfn."\=~$IO;䚫[kf_<_v]p84B].)yůUO!pA梾ԯǻ D3`2!+,,zBQN#`DL@hnPKY ڸ^w]ݾww@K>D 8*H֚̍`0%&B%%BB$`XDUPUj 0E9YYnVG'aT ":-mѹU{N^&I6=W~?~ƃGG'm޷yY6#w8̇[6]W (wnK3P"bbL=\tja$&"XWcS(?2_.a90[yasy0a7kE }6\vm^}oY.=Fsi%&WHO d$T{go⮖[6]ߵWأ HXf\D#1^gfm'557.xo壶|B}C|$?QkぇU[":|{taj*j1X B9#@X*nWx\ &I{)ž5 b *1c\)n3MDE"sٱe]̗ ւ6j@f@㓱/_^~&8,j<@2wZ-0n IbTUhf`5c䥭r:0Ĕf$\zulnT,v ծDto}XɵG''cpYLM*3` #Ww?_7>Ɵ$9}W_O|$TeFLѠ&];9:Zm6}*|:N7vb`6t&0t]_k!.ĄHfq0W/./ܾq5Z&  KiafG啈%GiqjR'e~PjJWjSu 7uuo}͹$| '?$vIk&*vgw7Gj1U1a׫ut:1jSṙ)!1~ YKU3D[X|W7^xB5bODUEȧ&@kF.ࠦ*Ҥøơ2߻s|4'^lUyi:c]Z+!-!QC\pfb*̪rCaVkܽa ,jpu.wC厕(FX;A6#^MDZk]'9"s㲔`>-:(g|[}-$|ߑ<‹nKZT„-t]3 a5Z쪈uI-8M$$F$F@&* ıEruT*:zĕC 3CT2]-_n9bYw50a5iyt8tۭ$a"&i,mr}Վ !N7r:T޾}o~aǾ(4![L \zaxL`-]Ns1 pi:R6G'7 R3#@-~=R4'I*w_W?ύ8<"Ddwqڼ^o ֫ 9,KXY_u al:9" EJ283m->g^zu/#ً!";6S[|q"E?\\NDn**"'6m-srK ծa$,0)r HDpoJ~CT8}1r&KDNJWBHIs.{aD-`Q!*㣮No>fđ_pg/0IR^@f;OӴ2nw1c4i8"UMMKq p-hO{w$nE)x|õ7cbJմ RUE>{Rh_ßrWۣcQi6Ga_oJ)-gjڰDn~{qnqU Hks3Zk0""*1'wj"sk3Z9e\<2Uu]]M֮v=D_a?īa˅ q껾;gW gC|%xjڨ޺0GA;ϝfSJLc-oK֪N(4eؚq/L\jN1 h3Q3ѨCi3p38]=:9?>tj\r%rcȏ~;x*_b$﯐_w|:!?Ǐ7ަyjۈX_l\:]\^aǎ@~7qz^$t215Ǧ3Z&\$CQ=ׁB0ϳ%^V-F QMz>s|<?O*QBf=zM,:9 \o6͚)"BXn_ܼ|yʓ׮>Bүl0%h:[S֦H,,HZ(<sAy&̈4O4ǽ@j7[#h?%axf\[/ $[&mMuf>MR6|$LjIc_0"Roo_n˰RRr`jJHĒ x~%2'q=" } DD$Ӷ,AH-{4MӄDQ{SPș_L53 $O!ӸZ xԲaX6#͠' ȈIJ)o{?t|DׂK%qpP33sxowR i"̬͈ 0t>a~쇳*%坛i6ys=gt 鐉r׏ٕT6\3aR#Ѻ}i;B|Ε??øm6iM0Kk-CC"fI D*(Xmwo<~",QXȈDa>43 , z ḟY3sW%`)RXI2_Ѡ 3$K4;y3:,ܑ8;*@a/qD/onnИk=lxleKfz|#Ȍ#Ign_RjE$52Q"G0'>ԲD K2<{Ew0a ns\3&R,|Xl` L0ԇvKx&r7:\I[Haj->} F6OmU 25!6#!|L?}VĜg{o 05k6<jynHRP"pMuy:^?|#%[.L8{5R_Fxtu-5,,MSAy`+3s(a`ABL]a/:=3]:{9IA$,y= eGJDT2szxX@r021x0L,l:>[g MdMR T^ Lb+.Xl, }K4`d$ Bt" ꡧqa=NL.QĜ(m]>|8 W_BaԷ PW7_[_O~\M䳟9_Rs>|~EUxy;X_\ZҽRR7OĽyúE!s;NK3{WVa)E<@偌f*""֥ˊM#wgB~ئyn0R2\YXz6&b1 HY5X/Pa5W8/~vwwDхKڵf2H)S {K'C҅2-_+/v-Zk]R*#V=q1={s_dtGjYHH,,"N[DZLp`2EDP7R{sfʨU_F.R:@ՠRRⲠ9Xy x(3.Q+S\_^qxAbHwF*GI{\?D̬M̃n IDAT89W>,Rӡͳ%Mm;e,Py]FUן<}Z`Ӭna^ Fĥ0 g[ )f>$.2antlӜSSDJ"V:kdT%5͔Hո^W۽x  ֦RK)5y@)Lvz mN^tD&/_)0 IE75HOl!2_t$ZeI=x˸yCB"^#6#F*}\ղeK7s{0y{ aAJL Z3u͚HDnaf͑iYo.ݵ@ tMFmN7ٹs>&_Ra=z"cñ ͳk-py:v(,LfA0ݵk BD&wp3 ܓ:<6M U80mK1FSKw{u5qZww/-< ivfZ;!H =- g%RT9YyDYJ7UCD)Li4 $ǩ;8&8҃A C@p4@Mf7,,i%I 3PBYU͸"@k˯jíYp] =mO}Bv>g~?ٿ97\|8y2tݹs \ӫΥ`vyMnŋ֦~Z!L-,)#DJvek!B1a: f 0R2]`b-bZZfp*a|i;\?7T0ոZ3Hl|"ycD:0cY#܉}$<^Q5I< G"lX|5m 8RJ)n57 7/jJxxgf@d?,R΁2 rLd*vHPuN"dk[^0SL-#",NM[ 50Cq\m6/<@pCk(0PAR 3Pw|9|SRsRd~7Ǐ`Fwai "u:v(c'1=H G@7cNRHwbuDvSuJD{tgi""e8*R}ݪj,8韮v.IE^ָ/b{7(R8# 2< ը=( D`CR07֟p !Fxy~T)+N$A,٠D J _sT2W7kd` 2GRYo)u?KqѨO#;F@{xlw~u.^?~B}ksooCo~ <~ዿ_?|+߷|RWa>`n^\^ܴjz·!6Z?w?~E 3gBs!IYJKsfr)ąkdpEinKV H,jVgfps$dX7PUϦy/NW:ϹLmS'Bv9Cޘͳq)̥f:7"Ury8-5_l}VkLL@y KC컲%_OnH~M7 Yz) R,tk"qHB䶤, g[0>ŬȼeO> ak0rFk+# VWH)<}6Y}V,9\{??c|M;K" mi:p|<q51W@8j!aoҀO TZ<|' p8Nҝv% ̴Y 3txZӍhny::8!R 3M0d΢[KDkĴRK…/ZOa)`H 3rv-2JL7!2I!mb8 8HZs_cno$f&!DH,Y7S! m6TfRK)0ˬ9=zn$CZ|s)n`J z/Kf>=E 0A2Fzf0}_|}z9"έ"qS8ƢR /=6zz+Zܵ7S "!7׹.,_w}'Mgv{?=??؎6Mq2Ӧ߻za`bUBb4M~}k>"B~i|HSEgV#kdDBwʌPa s 5oR<ÞKeZ7#><Si0(n..Wbsm_<.0"!!K93st@pZK97Ϟ u\*K94 Oc8f&()<'4T :Oo*ҲT,"0s2^#wAɴyEӞGMC$Ϊ9qU K)F!@Lۉ<l#t4m晘֛_yu jڍ(zplOf@8wY+?K|Εo'xs}m9O; ; _Be{kꖤ6xԔ)fXqroy ˫KYr@ |p}yN,aanֺP/3ї?{>{?{?/|'~'|ښ5M(K-سޡ>^DZ C-^<癤 8R*&(sf-< ]bRHD@ԅ"mpH=)9DVk {ghX!YH 21͠co}`F"vP33K.nj} K'b=<³ %pxbbv)Dyc~:g_xR 3QTԬPHy ks:8C"V&A {Лo~C=ǤϹ -ZkZSSW=ڔf2c q5LL$w=~{\: "3ܱ/U#4:aō}͕qOCKhz!6g(v͇at5"Q誘w~r*3mwGAB !Lm:YE24i;L?,Rk~v*uXZ+3yxӖʹܦ9q):=Qh Վfvh<_"7bBT)檙H`UP 1qO B3!1yxa3!gqndgHO>%Q"}kڬYs:O9GUt2b=!G˄='ڔ/0<~_~_^^m6<ܚiS]usA$GzsZ{2z@:5;ww/'1┝[ÚfOGTZ+ z9j{D.,,.E137UـH]":!4%n*CdZ_^nkay{b@ _rȵi}RX呿b !3uv+|x'!2'0OuXG&uG~JG:G{w 9p7=仦:[]BH ښŨIJV^D,y[f ̹鴇[w~J?|Ε_//ʹyS=춻0a5ϳ86lVMsvTu3'̝hy YJqӈpum-ڇ,z|) Y>s' a !Bv0 wi(|?_7WnMOmi)~}z>—~'Wa\<[liXpf`˫f0Onffnϟ?{0V!=`)jKܒJk R2 "bt\La@i:TLPSζ1"qڬj~Cd̩vyJQW|Pƣ Z/Xor~Uf:칚LL=Ǩ'㴭rpw.`=, :љI}dJ5$aߛK?- r$d"j3"HHO\ƕ21vRs^{rN?z{DX{CKPȉĝx빀^}ѝF!~n䈙7WכksfU p "@,7}gQ+bxj4wq5:Llڐde&lE"sWUigoڻ޸l2sgAJC@Αw K$X6sWN"λpgr<tq"|5`4sg:j͌=vDK$Eִ6MsC(Ě})ҹ77H\{63KApʗ~nDHMJ^y)"w5'$BPUl`L@ ,_9 1Ks4&670%"5cŒޛ@vOiN/3׷DSn L,R:8MR'nYsVyOԜS't"B<{>_FuDy2O ƈ7 IDATrq*C+U,]0$ yiFՃR*!󌄞J>lf ظ;" g&uFh}1xww+0&1C{H!)pqzt-}j܈Ug݋go_\]a|upV"b Sg-=O|LG9|'js1뻗7>[y}kH*\ 圩pax¡S>1+l 8Lwz%wD6yV)ُޏ*ꙻ31/o^<.:Rd1R#@e:סaZ4#<<3TUrIX(QD7sd dG%$$ T0#\M:zPX0Ǖ4y>(ή$3kG' ),o {J\8w{5* ,պ jעKI7(~~Jnn׾J&_;4S„Xm"֛W_{"LU[܅òrJNw}m#gp+|ӟR.ۻ/ZZkܦq4k!,C8"u&1Snm^O7a.fJLִ922 wSKanjԼi7 ua`Nk' `O'_ei:T4m=hU cq('VDˎr\:yNI(j)O]GJizqLU!oj r lsXsynzxV}!Hw7bmΑ2y_`Z,.]0=L05Iԭ;.+I؉ YnՖk7\%&&ŲoINoOIqՉH #R=6U~|y _nt8{SkK6M$RjN}N#L}[kpةǏ0 laj:KD"6c".ҵ-C-m YZPpgfkN̡'#`BӴ67)ԡE 9]iXeM~snR;)"Ny0yZb;έ"v0q-3r -65b B0вHt= .h""ζ97/H(!G'/(p{A̠LH훻/z5^ BCb1H؝t.xY \E\t"GYdΖZKy`&)\-?n"KZb`43W\ IR4A\/o?o_\\^^_zt1[S""4̂@o׽?|V/ʯjMMYQ+P~&H$5m|t۫0̘9TTҎi[@Z# l-vr 7$ZryM['1cLyA2$qW:]x`.}:P@܇ 1~OH,"&O ӿ0Nd:, bƧwB$!KLp N9Q̒B{ #WY]Bd!2d;+q͢p7$@ s@@#< Zk<UB9/H?1s)mWՃGRnjK${/?77=_gkT@l./]q8춥8LӬR:cB{!<|{n/<\Ð["]SYsɗ;zĘ}my{l\X ̥6Om՚K9R)IX #pua߾y(p9`)Syp#;" =!|ϟ?oچժTR)ĄYo.d.1=Ԛ["V$kt[:opLPyBvXۤKNNS~^wDAs j㡵z=/SYM*j,3Z=~a;4;H2~;>|v>>ۇ_ 6Mtk:OGd.MIaL*:32#0O[f6W ޒ]Ȅ2?Gn~'>cx>_cοf֮"vҕte<c0$J*NUR~TJ2 !crrCp)0 a!1J{nf9yc}r ~u\Hw3wR!s26MAq|FEi#ѫ9"{/`u>gGʞ B=O+'UE8!RL)璆2&$,Kf0OtNn3U_ta5:;Gs0yU%ǵ-t:s/ȹk}!u[Sʹȍ!̏cGn'mAQW'1\GL j=C80-&PՔK*j"Ȱ ZRg;QkXD+_/ (":yR)%bf_5qg!#`ɩMJHhdr 77$4Kyί,c"i`LR[%8y)Zkj dbqhӞ,K!m J8y hK\bh*BFfKRĿLޗ)ER?E43咈Zk11SBf4\txB7Ԁh.VwK"u-1/,RN[aNO0 ոMV*Ik[yӡjoo*wԟ3y)ݟx~&|?zDR[&"r< O\S> C_S zwZkV[{-oٝ0̿MdmS A8ف:s7產O!k* hƑ[-Lu:hm$4UQjܒW0Bδi;zk$ u;%l`www=DKΫq6 }H u`GnjS0؇J[x4>WiBd &@`ʉHMTTM'D$"*F `tNT}kUFB\9}I_bD5w{NK13e2Ϣ|_HTzҳ[>Q0sׅ H5Fri1QtHߣȺI ,NTnPM?a>v0S@E= rJ S;w77^G_<`wvGkVTu)Co\]+"W~ǾkǾr:GO|/{}}^{+#?sҸ t<4xXo>pb)0'0o-gfPlr.V$sfBPSG7اN,"iw_0Q|}${)G4OӸRmM{€Ff*X!D9#3]_]^>yc\Sk93`gx1*FAצ2Q2$D y0`6{(w@-eBJ=1f{h7I{T\Y=߻%|jo װW_U ;N]]\CK @Z7a 5DLQ63%f`X%$6A- L R_:9M~Һ;`sx=d,: iK31O!ciiwS-kWD40Ͼ" 0yR9ا hy)3·q+*Nrɫq^?}z{{#lbJXSD~WǾz:}w| /\22rxo/[9L'ԟ?aiViU:ۻzsɘՈDy$"JL.Pm:w;S]ovݙK,ȅ1u5bf\RΙ(|@jsm; %rhT!ik.S:Q\riR;f^ 8[WJlv٬6jd'Oq G #!#qJT4 CNKJ5{W@OZmj+T0sY G?g>9>w|w[t<&*usbnMs^ƒ7F \{mp|‹%^D{)%NLȧo P\(SL*&nC))]k8(Ng>" IEaWf(eӧy NZm|: sAZeF!7_3"J)y|RqaD ͈Ht}H&*ڤyٳwca>QDU7$U&<z7CGZ1)qq};Տ/eDP=A] ^4m01 {]Wk-aH)1 *3  IDATk_ip4[7 9'p4:j(>i ob5hHFTZOB]xf'1{8Fi10i*bi٘4O 8Eb2.PQfJ9{3O_ǿzvlGd:ͽ潍GL9מ3H|z?qΎt&t:ݰDHjlB=M`bBS lw󋋡Z[pU"j1VKI kJDpStD&@n\-8t:oq5th5t g@㍱ ȼmjHi:/>i"bwzZ4MRZ+2Cᅢ㻬%Ȼff- fJ9ߣ=ǾY r)!%.^dHoj"=h(jBya*޴Yb9yd嚈T|2,AHnқXXR)(&MNHi07!eW6HsJǂ q pJ)gGK?O}{?Ab^78 ̴yכ^i`Ȍr5rF `레 !ZөV'%Eeu2'!q"wHXSՃ3+ dIm"jX_VJjfL9Jm c)Jܻ(?̠Z8OC [%d*$$K!"%@o;8S~tS  )Vxy4p!Б?9VZȄ4P8 j̮T0e 90K}j&& `!v qcDDNlJ| )?.v2. *q+U|޶!{{"pf$dq*Ø8MukjhTB2EazK;2śĜ354WjRB&D22H9j[_ymojɓ;Ueit:O/hyB P@3 1uDCO 3Awh/>*a<A: D SV.JFs2!7KϺhsi!(CBf"iD %B"5hؽBYR-v%@-EuH$++Ip&o;{ [a |y񸸀\m~ 'n6͖ֆ%TX)cSLdZ|󍏫f7g;*!T}@˫'o<ڞovz3֢-ZOaf?6+=BX&ffY>fT1ч~/_A7_yj:=YZyjCZJ km@ :MtJ)\0&fbP@="Zus5< Hj\`NNlMV^FF|'U) armx7wwApNLP S.jax1rQ@e`D_$3SKy+@w@]<vy 2AO<,̢a"BPGkeY9Y;dZfRW|DtY]~#"; <.R*b C jD{4vSihjL*X TD:Pp䇘'NRJz(E=B2䜦q:cZs0͘R0@GO|#ޞ W̤G U7qޞ֛j2S5)_ɛ7x};[o\JB*JbI5$T;"?Oηx~ 6~sjTVs զI`8+dv0?u<"D R6ȁYt<2R)9\2 (D.~)S#"Buv|1m!25Ѧs*e(j-" Qp;=[Ur.9FJaL#SU"s[jxMPB0>=6֧i9ePi᮶ˀf"p/m6f />x DV&^V8sj&"%҃356z{wf HP+>ERX}FgZx#&Sl aueͧʮgF;|cM-N  Sw| ZCNRA$Eޑ210&A- -!)CB@,z83:^aWVC  $o1s!i\K#Ur'wFHJ >a:YP 5ֹMM\* !c"4iBD5ؠp<ϥ\ܟy_HD.i*v{uuzzK/8pjfw77ֳjq\MQr.)%SL܊]_\^^hn^x-Dz wZ_O'Ot/OOyIs_%gZi>nޝyEqج׮`84g *N))bb ɑuyIZR2JI)ľBR* IH{ۜs.ephsE(3 `bJ)a?qgiCfٞmvaGM91UQ*έ8/ʺ:$̉3g_m<}1r\J)|jBJ(*qo}z.9'Zb%rw8̄(fwn<i>MSkMɎ\"@kj xe8A e ƻDk5i ؅Ѳ{x3bE>lj_F=D{ )B8ǟmҴòGK_Z}Gؙ*Xj }yDU\[lqt)'c LDD%tm=;'֕9REi4| yv_|N4URJ)Փ< rW2 n V=`Wk_ëHkb ݖS:{_'_<|f8ͧy:|gR;'vwÇ_v Lhx:LD4r{5>W__o&=wDiw~ڢ.Qmεad҄RN:;]~ZX4}gc*grJSĦRQfAm:&c SJ1yE;n 316il{ NGSXV(R{Kf`8՘s˫-HHBvXGB,VuMf0)BjCg6ys^s )@n/tB;Sߐ=hR2UUYc)ET[Rj ` M|{UYW24MSW)CSddڢo"LTU4܀z}m]5K|bf%+jR\TK8h @cj)~(FGx/:2H[kM(3Ƿ{!PLQZ~c]'Sw۲oKbORc^1!1b"//!nFDLD~jhCa? | zHD(E$Gyr.WW۳].siR["A<GDV^K_x/!b lSkǭS*?>Hg~N? 17?{Jj Uε"J*@t|ȫo/0s SDDy+f) 6?G\r.C.CC)2ϓӜBSC0Ei>iÐ29ңd|HH7 Lj)͵ TAiV[! `n # xww{js.Ð(uG]#|}pq~q~giQ&UvhrNV?J0vm6"zsӧOo[Q<ҁ5…;Q$Bv+N|''Mj;: =}r=q  Q(^PJ Ą)2X. 2;F 8Q V':;|񻙺PmЎbLٔǞDVPORrMuBCӎ)̖& o:ceM$8 VFQ"3h_Z};ĹVlIZ)%fcTXvLD8ުT7qJQGoaČ4Oc  R!3sJ-=u#G)A \ LU_&1j2nj5C?ӟ[7w{A͙,&"-uy*ZoSN]vZfhs}3Qj2^bjNB@J Tzǔ89q3Zk%e"jgZ#5%2PO[&QzRʹ00-jAq<ᨂ6 BG=UUm &367U8{p]oכ!B)%2r:oo/on4L+mm^=8>XVfN9'a(9s)z[@=pPSNL"M\#HDD`vNDfܤ+pzɽh{+'h< x'U껫."eFUwYj*(x ]︯~3! H nnlF/O/bk v.=7IDOTXKDIx;p?A+U0'Ns7c5(?ݭwws#bJZX=""#W|y_ ,{q\:#Rι eZ\}X ->z4E*ĉW&חO/>ynټڧwɠĤMj;@M9|ӿN?׿^{g~~~Kz} ߹R՝ MyTm=ZksvJΉ<̑HBnA3fn5#OrӧA7?(1Z+d)'NG~nW)ebtB2qn&KƻӎUɏ@а0u:ZX>5T&>sW>"xʙ_?jJ0BUV0ȖUg绳3{꺏SJq8aVǏ;>ܬ)'/PnXxy}-M]рfȔ3+B6-:Tk,$6q3@q2U14&`|q#"d POwxQn:QkUE")z[bL-1Ϊt% p(t }$6 \-i+JSRCg5ӤBĐPCP4Rq|i D%w{nEŎQ'?At|: ?oֈno0z&.P&m8+$<OƉsɹ$;l=Ϥ=3hXA&Dj\9/DwfVh#I @Sj&?S|P'X='`V:Se]I[)%DxYRMy<ono]8a;6|qq~qqqq~1WvݡWW77777ǩξwؚ8QMT6œQc_ IDAT{ r㤡!G2EsK]giin-A*Q+"ǰd AT7ʈxϊHqMSqu~G,\mx/Y[Ժ@UE+l~ K7vj^MUj*}!1b ]i'J5&De O q wΔ;D "dѷ–:\rޞfJ90DNA% @@D FDiqlmkݦKԗaL:*t-qȋb. JBx_ŋ-o}Gk5y&U$y !q)Zo~#ȉ?_K_i7w}Dڸ@2_yj68y CJiD&  ѯۙ`yvh]nG 0ɣUIjD? ]g&DH1\" )JŎi!EBjq\՘rN! t_c6欲R)orbrP-z `4\_MSΙrU7b.U5v8ߝz<55Bp4_p зH#(h1 #'&e??_7!ҙ[wL }t]P@g.c~([ {0NˎEM@=fI"@=/>r4bx~*_.09 .=]T=]>t `lh^Q3ߋ>)뀔ǼGP# m! ,JFfTeIHp3:4h7NCk p'KW+?a10DgyJ)f]cJJ[ ߪOP𽳱1{;vv .i:oo_ş巬7ۗ_}{Vi-aLT!Nj֤6STC'^>?+/sFo֋/Eh85IQC)EE"K3nu{ϟ~@>4iBġ2;H6>"/@2bq""ġ";X o/2i&s&RbU\en/x 1-~a'J 93nklђDMD K h#!g+¢pq?)眇aq*Zӡz}ݞ r!ӓ7<~'xc AVxqvv]yjq%˫!@`@ 3P\rvۙ繶|ʼnY'Ъ3w 5T#mx@Gi(8:'f7i䰣%&f_N]]yab^*I !g2/Tz*hucb7!#TDCD 6@Y(y|:W(QLE&o E6E ڙ.fv<NǃSJ<iT=Wz}{3e\irIK|:MaJ)aډ_8SA1Q~`^ja4m*1Alt'׈!Tg6uj5?S_k_xoxn|}߯`ZIm*Z_xJ9ʁj>gz- SBYJؾ}9b=, 'Xk.r>_p_k -ڼEL$~1-Lb5Vz]%q5Y_BTR4^}84/Ԛ6D)ooOCaCʓGx K-1%vFʰ*/DHː&mɛ؜Kww:MuɅRZ ,DHIJcğ >t:~sF̿/}]o96Uv<j7ۭ"|`LDK7 c79iI?C̀cC '2#t%w_$E Z,THlq8Pf!!^] /@T{|(-4So>NpQ;ސ)="Lm}aF^ DC*H3QrItܿ9f/ʫM*sI ) xɯ^c(1/w<k7o{ոZouVP5z:MePjm8qr$d$@{ɢ.vSfb."D h3@Ttw|Kh|rF,~'u1$8B00Hk )o|vq52st9痪9ѳIf?{۶]zc9fͪUk}9-ۘH`" A (&1r$ $"!!!^_|BHDbH8Y{uy1 _puUs9f}ϐrw5| <;KN]e)%'qC \֫̆L)8M~RE DC@V+VZ l6כ'Ooov;y hc" rZ)wfj&Fk3J1Z<6;Im0iOЋqĨkq87WsvPk:'Lv=i" @},U4'!X,3󄇔CԏHs!J#jdg(xNSJ}6y^FN@)ELx:v*(AT(ϯhXD5,kUDj-s:)w9u}]UEz (Bj9u] 93KD׹ K)fgib x=M,z'~G_/}s0,ǃ'xi;NS)4(v9QGR.,BTh$lܶ/&1H 3FQCTׇ{ @ℍK@nhe3UѪ)rAĈj C޾9aIj)S)eJ)GZKv5u?Dx>ܗR)QRkNNdP6WfsZE[t~Soߚ]+}_=R&T VUjmg I geu)'&?(&F};q*t<jG'Ъ%!JHn_ B X3bm ٮXqoŜ,"nhl#^b9iMIQ}(.0W19xDŒ\X*}fD` f^QMѣ뿛c!\qW.ץԶ36B fq:d"!ܨ2Bk6geEESO_. Q5dM9"YW>P r_`ЙǠωNu\V21BjMX^} />˫buVUzeI`UR?x:~ۿr.S1AJ~I8/1!CLw mn5{"՛%"oD$Uq,RO4N"N\l̔zOs|G5 Cl`]*j>ϏXi&h ky! ޕը$@%70CE቎];ߩ~N7Y C7"pbWBDdsfæIE$:I-t)|xOk} tT CcD$dƁDH4^"TyO~D;&26nRs_*UEWg/>˫b)[JFZ5S12mm(c !B_#$#?ڨT:#}Ðܲ⃆#vN{{*W̮Jno?/~Mb"WeNEpd֔Ј̖P3OER4հB\"b]=pKi(F[f]Ĉi;h*!h'J.aK\-֗ܺ4#E`@|wpwO)D%IjA4F┘sf̈,ZE Lg_sOnn6WF⌄"c"wZZv]v VhцBY ð4DRmߡ>*uLU lIR@iQaNeLв-"gOUf S"^ DHO`|U<hR/ye4aim_~N[6 V3ʃ3m8TO4AL9[MˑB#SUj 3Va]\\0,~i&)├3 Hv0*)*02ky"!q 72 FRꏽcȉzUZ޿}^|Z]\W'L̜@P]Ȅj 33dE??>KW?}۷rs/IKqk9U}!hMF%`X s~HЅnQD#Do(\&Y-#6GJ0a$=b}$n臾ωSa0[[` " a)32飴g<@iOʜ0q)w9q2S*˼X rN%& A"r-"UZI+L9ekab>Qݙخ'0?v: ICPS: rX œ@synZq5BCom,t#ޙ7tD03edqA95!Bh;D|LAI)J0`W$jsQL\FMC-W!e 'wt*ǤjVEJ-z B~/g~=?go뺡NZKlt~$e(C? &G$Ba"RCfީ!7DP::XesIv}n"h jƀDj5~0U`p*5Wc,rl~r4{ܚ )QDUij-x'olӉf~zsLp'*i)eR&U+LS"-0gJ&h՚$AmInϟG?@fK8GbxvAoINp"Qa3~n qUg\›'#AcL $fEǃh)w?@p؝'`(uY7t{9@d̩`U~uq(;i]k 4285mHNM":܈f&U׼&53h0ÿcsGUTzm꺧}Hjj"*fRRhh]?Ώd-[7Sz}`-ꚈN*.$>뺰p>w96Qo3!>q]+QTaz7n(?zE&$E:SĠ?f@UBpD\RG\}v5|8 _s̈XeJOC&ψscF+Z d K,0RIA@ #n9}RN1^J j)(̜+Иt_~pČ& "~V^//V]וm۝YԢZC~PZV'7ϟ>}qjڤ]"*j`e*:M8@˻͓O\oV $|4ZJx:vqk \>FȎMIRΚ j3XRWF-4Pz gk8T V[ lԌZ\S;MUI aF@17KMT*=7w%u;01uؖM}.tðXr("Ȯư0eQR}< <9i:,v>=Ęaac£v4 P8ۑ~W4ԮƄu^@bo*͓rNLR&ZIBm Hk$Z2#QNO~~h<O|?C۾;aR3+Rp:%)w]HDDFF48k8hxY3n 9"+(Z%5@dtڗVmCSj0˝3%$Հ I "&:3l]wi=JмEE*D)qõWR8%`;nscgB$*B߽x￟'Nti"UT\֗+jGuC/O{߿;KfPDT Zo6WB1 rTj42Rs ff}?}j\,Rk~-yKx,SQSդ"4MS3J~`FdZ̦z5qT TC*BLjmsF4"F̮#VA fMٚO4cu9`gןK2/|*"JG-V 3;G%Jj`)QxJqa@44(pm~~_=uZsUr&)*JDR2"bL7Kzh"v3P<܃EU<Ͽi" _.]_N|2"B|$51rg&jBtI.R`|./b浫ى'On.kX_^~s{n=<(!&n>?hF5z|,HqO|Ks oeOhRJ '+?hۨY2"4/gìL-'æl,`%K0kr+Q& _Nuv94XHChsKDi< ̉B߁6`nn1T:]TKI)RŰX.~sN$R(Z!iaz33 ͻ JL>]n }Asn3JmkX@ԠځIcАH8ilv;FajevSŋWj& VD4I9e?#?s_|/}O}͛+5uyX.Nc-*2MAA ɸ]?w6Z嚅P FD!vs4Z'iITUL`yyh>RШA3"P0$&bѪfU$!*.V02w6P|]qqFbo#>n6/ ϢH! hNp9`(8ɦb۷2Z.v"fs^R޾{7.=yrt9sb\]hGzᡖ 2HWs~W`Zi뺋*%npb9wwTx/f\C${o# @x/=SE?RDĠi*S-XC#;(3rPbD"Z>֦2Z #HΛ My--@b bf1dRVrQh- ~?9kSo;~@JLRk"J4bu9I^}o@hu8Yj\>$OM=v ?[e u@)lh&M0-SU{'$&c& //xRjg?{~sySEosǯ_S oonlj W8hRXK >FWgYkLМQsG1 )9aq* +R~xoO8ULO ^iYwJ2tL`]_q\ƅBgC; !zԋ jqC5K-NM=#C fR=ЇɝApS@R!rRhxY>@iǝ 6lX PU_,W0 Ð(Sbrql^-CX_V2INژ֤,fZ%_DԆF7pf?a$R!'i ]E `df d" > @I*nݽyaX =Dlƈ#XJ٩BQͿ2M'~ӿo6ujb`ZUTJ8RN{dBй"7s!$D&R"Ddlk&.d@83EEDf\7/rZ{ZTì>01ĮE SF.~U{^u9whZa @+jVzl[.U=UZF:߫r׹=cŮꌭQlTR>{՛7כf\]]]&ND)q@[SJ[+3Kv?jj?|Ţ8%O.eݧ/_zV\_a\HQcbK:9甹!pZ-Wb/ijenCb<~K"8%Qlpm;&@J[s$QVM8JTL q} IDH|X-|: j>MIzj]|9o?#sޫZӮ~u(@Hh_3H]9iF53Eqߨ6"3޷%"iD$TUĤv}wn,/.ŰK}7=h cDgVN&mol3VE5 l'C  M dhZJ7#1p'Nv.?<%v FX 8+Kb&pz7ϾbX^ rETdf"~mW[[x$N n<~7yo//xaX] ]?ZqmbN^CSΝGvix]J<;޿y3s΍ <`VmQWo.b\Zka9}G̱j2%tAJ*aZ.|U]~'ϯWlIs;)⻇R*HZ*"]\.Cs97q:Ey8ͻyn=8v]ZWjA8RN)Ac!,qn,*;ٹ F= .#mAn~WՏbj@5}H(qzs`ŠHkmyjFۊ$evF+!ZGmEgP):?Rx<s fJ L. 4OgZ8NˋawX~9w+hm,Rjjcs*HD1"ﱼ.POgW[̥ΈH1[^D37 }[5_ȟ4 &Èaac ujD[yܧhTu[`2N8Ν&@#Gc߉2y$Uhr-XUu90:+ỏ>M oƕ؜63!Pg^4I,=戭G.ԍmhA/97uyMېDfX 5m< f3APBT4/e.N8N|ru<9jrY7iw_-V+bgE~Ϭ7]iT$@u4Y? ^13v}<L[-4}Cɯ`mJfH,l"y%)T29jDjhmҶs+VX]H jJ Dܨ~ G܏xF2"c6"&*Z xx԰XcbL ?b"fR1i=>XkgCQb"S)o//z\'̩뻋jRO_ŋxt>zgn t_jRZ{F3,Kv;%OAۛO./'9qkAb7t:b%J Zac?#fw "a)Eshڠ=,fQn[K(F(;]Go%ƭFy{Fhg"Wse28Z9Gm$JD+(8:[) #9!1S9BB:R79%>^L01v)'~||[1Ϊs Z.oRSb}T8#o4(6D O _ B$RF'@m5(u_S_+1sęq 3ߦyq6 9"1bF)e"۾{GWbX^7@g/?}j߿7~闾;sX-ŅC459qZ.K`&Ė042v9!؜[S]DĚN ]b2lvAw([:* ; !z<(L xfϐ!&N"{&04rK0`$E+Vk=wD$Uq`kT2 IDAT"ZA9U٘1pm^L_.>0HUKb"+j s]5c E-EorXt]#Og^O')5tZ~s>pD!r\~41S;bBR޾{WKը|}~ٳ'7׫{?ژ×_|pd^1EE`J tA.i2UA.wZ5@` e"s;\5wI!ۋF$T[B$O g+/ |2Čf5x**h"U=qh݅c>%5br7-y3|(;_c9%3OiDf$9w]L  B@Nbcmǯf}?Rs3iJ)i+љ e3bą(xi 3yցT_㙄'`Я4z? k>*YN 禹̀!V ࣪9e=ɷtۗ͆3!y/X]t}1W/]w#0,5nZk9>jsG>y NF4 %-q o;lpX#"27iSѽ?wIG`U8$FfL9V)s˔pTr;Wv`gZ \si2&Ka^Ԙz002sv.4i)Ej)Viu"J3,Df`]&*œJ-qųrN6YyHQD }y;_3\MRAۉٳOVa-o޾}n=F"d \zs]n>9'16Am 5G9'BCvdQ 1У|MZrpx!U$BHwJDB4r;DŽ:Po)f"PQ?\#䳟 sG81qJq0{+Zbq8*X34)swV^-ɗ/Zf,gCƫƶc=gh;f60aiB.Y5!f4|a5İBSsW3qh*=($ڙT0DXDB22;eO?Lt<ˋ.w QwoZ-Л{~~K&f8Z\(ka8:?^u᣸Gk][@dʹTR+9IpG08.Ef15Zjbѳ{|Ʒ7KY 4Ռr}̉x8w;kzkCR#a =3saϙ9?ϰovTIiD'łYO>45jOxrg&5 ˮ)u'O7WOnnŬ~ 8}ٳI!|CD"]ʻ'Bb@3 NCryqQTJA~#wUu}߁RNKy1,Rͤ燊%f\.U? }QLDN8M39gǩ $iΆUhv@~„|NQOo7K{}= IP 9q*HP8)zQSGv"$&?[3b(m)F5LL$EN4=x8%pD1]0Ҿs5\yS>N9w;Ku~Fpۜv!MCMPv4Br̕yR(n猯ε;PNbLbͷ|m l6P`䱢gD`dAqjC#xC  f>^M) aX,iv-䊰9\so}/q7//wb/ZJ'IMe<ө[ )&] (撪8%#v̟^ãˬ}].%U"bffbQ!3h|wt=Fܐ%fJ2 GR"IjX,2'08"igZ4RJ)ĔSWWw 'JN)p9场@nzJq|س:3(:&ikF3҅cQ0EŅ)'uy\]-A$z$/aѯ%ǼFx.R& .]Y_-%N)n< _w>OZ' RqI-}'D& 5ROzZ7@>Kw"eF߬T_!efU \ UU Ŵ8bF K򛨛Nb 1ךa)ۇx5\PSet3#Fr @ۇnGn'4Zg!/悜1)E\  Gj1` UHCeW ՜%*($2yNDzsSLbPYVTT޾}i ̺Wb\,ղ뺮NE?$N/aFepnqFR80U ET}@򗇾a燄 6mjjƍdw})e/E,[T$d%AĄ쭪B)MW@0K̙n7'DRN. 3 560CJAJhT HyvqE4$YJv4­8f0T(^+7Njt'M"jDqo7ݶmi\Y!P4M˼:pM*I9)+[ZE+)yn7Mؤu]ɳT~:YRXP^(]!h*3PTӛ1Cò69K7jy, A !W58 Pb1pfQB9! z5jI~|r171v}mzMytl4??ƞl̹ 3M)-b%%l}.p$tR> QUA 8K%U5H}HJT3 fa$9g&"` u"ʘ |obP4y<+A0P1PL`ibqr>]5%BTQ1a25?}Z<)|zCۦmFDH=0UM \e"]"wv)!Ab^rb]\YZYQg!ƌO}v4BdUE-wR֔ԦeUIrhlu}=ˆ1M>vmى1rsw':/?~iYK"ҶpoM!HELjߖyÄmyq~v 4ÐrnK+ƛT]=/$X LUsJ+:/8-)n 6#\T( ~&4~4B 7<=kSL݄鳷vˣJi؄yZIߨ4 sFR!ؠȏ}clb_3 ,r%c KY$V|Z,R0( L ɼLT7-3QS#6lCnbj̨yq7Mlç4Yƶ???iYP#q\uHn؛4ME[;5D1 7 3u|(H0䉖; Zs{e'jfbRBOJVVX#B lK|Rg7e)!"p(yEPTy`2_gwi })!DB e3~(Kbu621qBugϺ,I!0ϬțB$AqFTL ̐-ۊv@mr˒q8ϲg!dG& _~Rm7}]]uMbP@!U0I B"*2MۧLJIDE;aXc"\ 6]B0Biu]ic.4Nry;qfs@x_-8G洋<Ϥ}Ma׷4YOTːT$ ۬&=Y* }b*N (jq(kJ&,OU!_ 1㮝$&d ~w}Jis]/0|x<_]û}x<휟Y=/o_~a'$)}7Mm۶ͦڮ-ܰ$%p oLJODabrw|wwww]lyv\m7nf/clq6˲t%]^^OaXB ஘2벴mM\p-z+EAe\ue*d$%1#0KAZ [@ajmC8^i$BCrG@f;RM.o!Y DF@UEinl~U1x:Q&dbj[LnPvApM̀:Nl3ĬJϬRȢ 2?"*6DVC6!$Y.E$C%HpݹVG`zy@oQq8ZDL)iZq? {<Ħyq~b0en>_+?Ǻs#.Cky[0P1;3ɼ0D%kb,  V)(f느L8DB!ei'"٠PZ(>:Dϳ"yLaNSTSE j7"Qޖ2DRX6]"Q| 8pLA=g$#}zxu&t>oڮ|-xCc,fMr.8Np>/8Fw2 43_>><<=i֔b{vn,p3w3xqZ=H VSSKʺZJ1F*3IL xdqCPzCZýbnvv!w}xfT$p4 8Zf]l "VjWYwO*b\u;lvۮڶUI @`ȣ<ՕSJUvp|]ѾU@y-$M)b5k-KJ9|Cif>ZŒ-+9{FuTaR7y%貵_uËRJy3م{ٻ)au3X!2ڮC&4oo&1$(t~/AU|su!6 IDAT~|w* "%uw?7v}mrtc^\3%WbTDBĊ*}28CPQi*YaeLg4 $kfzrޅS $eP,R5U՗&[]}Ue]RB@MeIe7$=@_N6LaˍQ U]pf@3,˲"SItҷSipDy;/e>>>HJkJ^ûwww}of*W` 5=0LD0#^ ?Punwn+_4iM'$"9VE4"xX QC!+̲/OfJ%OTHM&E%.p@a KRd[S|&èV`(ZH^P"Rjڶ~]߶]׵8%912e>?|\7ֺkz_fYED]+ũV5F׊PU}x;+ `=w͌Q%>`!}1dZ ؜WZqZV9u7T\)D0am`^tfwF@RvyED y6Pĺ+umo/~Hv};5<q\Nm !@ ~K"L^!!s(/:)<Dy7 &؝jJHŖ{A0rTDe_/07b`m~M8iV ȟuY|8O:/!FIRe]ueP0\].˼cHĪJ(5́K C$L(R zqH!u@:P̋g#ʑ>4qےu9ft>a;wmt:ϯ8-w]2 A`HPC,)>0FuKᓇDURB"dWU9HRsKm2%o*;Bx˧ʙhʍ|E &/gM홉D1sUe]fif'=,ͦilb4شWs 6 ;C$$Lq'bk_ RlZw߫_ҒeqŚ$wx~o6 Ni r v<8OUh'IFwG(v`K\3L +t9?<>Nai"$y{޽obDOD"/\ćv:+HYapn}4-WZZhާ/_ִk j"b"BHj`mnC 1B #eY|8-mJaM+##sKdj}u]c`˒RZ=*:Hh˄$T i?H)%4w>"*e^ƜIMȞF2 Bs8G/ *ԵRg09vs"m6o1cv s}lĪ'-N%-U^xދsgױ=Ǯh UBPVT_GD e`b)("ݢzn(cьLMQ"w ` BRZ./&fk1Ǧ]՝e*}EBS&fǻG[okwSص1MT3sn{{yy~xv!Ͼ"4-|2&ooŒF(Qg^8Drrt˪tcJNS :"ڿ*W! sFk$1{್4)0㲮PR[0HkQ 1;`U)4e$d@4\.!Ʈkb%EꯋyY\n1 |&R#ӕZ!Aqi/!!s𛜣Q|xȥ#&2.g-JAAǔ畤QNDiɵj6-Ry]}TOCLHoij20,mDK)$߽nռ3$4\yQSbB@>!zSYrIli$YE xv S4O$*)6UÞI $M!A kAاً<<8W.dL-k q8|)0Ⱥk^~ÁAR~LuyCB̄8ՐyYaM\ p2z(Ky]$ZV zQm=&$\`MƚsR#bαeDRUf=$3RT3[ҽJǫ|֮л%`ȿ8&fڶB>~ĚȴHR:2..V#` 0P_?SL'I8Rrv > Ш8tϿm)5)93$Q"drKMEUA6f@ @ 5UV??4[ Gպ`U{907*.@vi6j4.WoYZ3s P.1pp66*SהYLCq!F"RTB$d[5]t(A%,.f$򶛜gyw(Yq֪в왈C0=gǪ7؇0KȈ( Ls,kpc]V»Rt~;=kqklb=gT䳏^2)O*2^r,+:ت\&HLUS31!36G/o*Hv%XѨ4 }o///&_IMl06ZB`9Y$bmӴmw_Gy ;g`]5˪)^__ !cRV07UX'˫HWyǏ>PSHA?  *EYynW'UjRdbya, &rSjɿp5$f$?20/TADf,3d(:iV겊VKN'Zqx[T՘"ĕiR AL4 l1eϑH$*d^#r^z6$#AN^p1|3V}aiyx|:w^Oiqrl6wmۦibhZ˛$Iꃘ~16P&z~=.)&o6"y^i|<=<;b ce!-KZ&1xw# .з`4EQ)ei )JVcUBddf1'&xW4rps zn5IV!% CA&<=5%l矇qYyYqqqexw<4Η(UѻqM_'TDVFbā2N]:Syaq 8zp-˜RZT/௉1#@1;7iYDT1SVZ#hTwPUIKJ+.b6]lڶi#2&hٹL:sQ*L>% - ?= $*Dn>n֖3HE MAAPr2{_5VAgf1fgp#wjRnB03e2]န|\|t׋In)V@TrpnleYoEX&EŴ&5q-+k[O?ܟ@QYS)Tpo5p33bLՔ_?~<ܿc_#DCvb @=nJ`b7a̠fI%fj *[5uP,)_i66MΧ574pLH~'Nfn_ D$f;!O2P !41:* J3W߈+kE "84~u^?+9wޢfR@0q2pn!kiCNԫ*=>=kZꃶfm\Er=1vyYV+?{~ yiiaL2/˺,mp?HQTn6]z!܌k7rgݻ0qia_@s*xjl*IjĬ@2\if"hBl3xeev=?D0"&Zݢ._u]~}~z9P>m\vA OFi%B=\^%Ʃ|G)HR- P02J=oSnn>,?C2sK"!Tyw5i IYi]L, ~L3XP#P"L (´L҅-?c>αtYo _".s ʚy~~7~?ܛ_3?~itQCVO QV &4 Iĵa `ҲqlJXYVCy^Bl<ʳb_3gy vUōyIgjF cL%U`9lHA f(,~4(iY緗}2Pϔ[?mۦyaՒFɍ8Y#ov1i^__^_):UhUOMTMx-3 Y43@5%unfgIwDb5ftzy}h?~G$O@&PnԒ B b_uUʔ²] ՒoXOg&6j"`&0GyiJ+{SU4C)K'C \Er\!vmmETiZا@RS,R-bOH-!Q ebuZwIiȊ.OWF5%/!7t. F75KR䓸 T|UN (`+"k+Wy_Qa,#?8g9tcR"g]muQY4Y\P7\7 Ԉy*N1Ʀ隶y/)eѽ_#aleDOKT SK 5[, $DoO??$~O°G!Ue#7.3Y~~~bgyC)%3u`f:^BlP jQQg@M%w}mma W !kT5mp&QZ/*,06MbvMLC` ̞]2]*V Oo8V˜3/X:ws[ rrH ]}T2bh8`]+dwL@MIfVAJxL3%I^:88i󻻻n۵-c(j__/ð˺aO00N8r1 //LU 򉛟\ßd|.'W>"1HLe j>AՒby棫$%sZ4eqw{0Жu"o& {Wz5J,7@5sޤ^XCZ@S$誼3R:N$/ ֯v~x u{ >/ {COR6 nf1jJ .smͶ/bӵz5B1TӺ-buP0/p-KS#"o?/|3_z$"SefUenD,]4AcL*!5}_c@gk!uiw8O ? nbl7Mc {68T` H84M˚cز=x@]>Gs!YTz,DIIB06m]߷!yMrv?K!@)PJusa djfVS}̩% >O]òY$%Qrl]ӆ] |K^scU&y_:nVdpj+!*_QF@lbl;bzY?d)x?LUCEeT٣5!T5fP"_V_k7^0rj]JVkV!G4??crM$%&NPU^fBXfgCYB 4NIR\g^md]g_\w8c&dCRZMTb4mv-H0դ$"wdgb״OkQ @Sn .>#Hv)o)C Y;4|[,P *縡rY@*sQV`q9b(D|*Rea/1->t@u}ߵCc1PLO/?tzqMkfnBU!gxߵ;C eM< 8뺮i=~uU:"Ӳi]?}z,SeY|A@")^T֍kG!BfaDĦiCȍ(~uQ8\v㝉"llQە%c+C :qnX.b:%]ފ%𳘋39.O-^W>ԡq$fָJ =}R:͠"* 3+ Qo6]QE, ^9F?>b,yaB;=*Kx3/{?s˿.ƽ\e"1DǵH_h~:k3EP Mi嫯K\pwρ>|#116C)"H$>5yR*jۭ,GD[^X}mFB8 2TG>F1Q"B-r@qpi euccwx"(d$-ȊrrCL$*05#)28QX@EE9EFzĿ67[7OHV!kkYMlf\-"JD.puZf39f_Qb^+Wfh..qTO:NnaM;$yW۴ʪ*r% v1: mM!%]JKP Mڮ%b၄t`nw;NIQx$(WbzFб{PAӦ}(ԓ 8UuN9wo'7G?/1i{iW9%7QWGĶF1qv0݊Cs"rz{{W]bʉ-  ,W\ѣA:i9e^.s-5|}FxHq\"#:HZy4 8Ә[Îl7 XGkJ3!+&VwCC%fԋ@V)YL5ֲ-:5fJ_Zs B"CI;-I!S%V3˜ HnK9ejcLzw g$V~[v㺮e!a""x~:_nr:׵8 9gjg ֲy]uYDy)يZzz\߯׫K9^89[|F8;]v E,&7ov.˭"'fDjO3d"XoI t^Ӑw3 uU-nhxLT062VcE3>'qhW\{*j-, Zl!/y} ȧEG.a"ry j"CTȴ6@6S&#$xXcn9 Vr~Uq~W7_Ok 7u?r`J>4h| C_Nt>~|}q-E@ݲ22ZkaH?}b*Uϟ}-k)_hs:_HՇa8RAhʐK^&3拏C ~圙5d#@}S8Q/vpQ%NpOϻAjRj2J$ 86U3+Uvtk)1j:x3VF. e%x1<(hjY58zZ?tI]]:5Zq8nzF5j,"JR "a@rCx:XR'0|ݘ08ӄ@hh՜O SF@jq¸z28] 6S BN1U76%#@LH@mdYU%lN&nMJAZ"b@duwr l&u# stx6!bG!G!In%S-u/o///Dl8_?Tn7ðyz5DJzUj-kN<0 9e 9Ŝ~~>].}vvӐUZa}y}:ay $X'FZȆhO~j RR5LHt^K4|䨽9Fr6gZ7!>"Z֜su8_^ǧ&ZD{-]iLJvWLJ#f^. rD\2'fΜw}o=EּD:_@ l{lž@u>N= bR,bPњMe;< )WHݛ9r[N-wVDN|a[FWߩ IwZԺO?{7})9w'՜ jHUWXkذqI/<)=A% }O/uT1UL RǟwnUzkTCoKZkE4rξ >?;GvUA +{nHCTjQۚI$s&,6(R5c/1&/ 8x" S:Z}26nz`Hy VBb5;-/1 iF"DDN-Tf)RFq]7Vؠ9¬|~}z~yyzyyލ";m4PK|<!L)uq vk!NyZHCm{w8 Cus/Ee^vDFu3J)RWa(c IDATØz [^>Rkuĝ XU7/z$qGN6(&Z[{!AgeeVo=(y,wx`{/~IP뿬ݥ "˲RjUux˳pb䚉%eΉ9Zmt[+0UV?ߟHWAZSǤhh:a6WmKswA2MD2,hڴо5tPC)<fp^k0Ќx"5M` PRJI)*R(ރ|y[OmF"@bUCw[o|n9za'FiX0>BE^y.CJMuEh@9~Y@7YU?_UR+5G;"̵-զn911Oo?`1Z"4MC/`Y T=r:_/g#y8 N:Viu5֪*rvicDPץsʜ8x˯F&A}^ }V 1>_HX ZtӼ2r D\n1_y+nY͢$yH D"`㰛yQUjA1NU]w9'h;lV\J Rs*2H̜~_>YQdKՒ-V..hITͫxxd~Wض$LM}O?>|0Sʉ5%/xuNrϧ* DFa*LZr&dd"r93T|阘Mv̀93b.L{PjU9sK쳂.)2u]f `Yz010䔘H0ulsɶ]蔁9o0N8iώFӈ Z2v22 84R"DZe]91sʉ9C᝗߮w1 ,EulSfyvHE]Xjqq]YK)őZ楘y@-uz/&LCa?%JuF\ T RJ9e{L)_Pf$Dֆm7P"ЦkljԦ=j{*#m+= =q*qil6hM뺪@!ETrw唜pU<[YJ^XT!o֝#&Ci*o_Cl 34W1{σ4, dbf)=AπhlI} E,Eo*r~{)~'9ڄ- cE̔R'<"Շ`ۯq`bpK~t`b 1]/6SRTpwHUՖC QFsJ)x+AVb*Jٓxe3<-]SmU%搪pNSUA7y5S8=&F߶8'RRT~2XrrSN\ɭis&ҷ_Pn+ɊHd#Tϟ?>χ!(mșoZ7>˲.ETԌw~G"rq'/P""/}v8\Hp $UDHIXq]v.k)5p7s2_FZUp0 i  (֎hnוv5rr%nouY-Ze-?@Rjv*0a`P< XkRe]K3)JN)eNك1 & 2pݓ`mӠ7D*颗v!s(vjM* vDo0YlwYi4LFp&]3JAZa(~NTz\ ,PµT3II#Fz#cRp?W?팶??nA`EbaCDfp]Ge!STS9|}G{yʬfz=/WU)q"dq0 ˹q= $Od5>t014>y{Cf6wNydsCېDy+NhH.tǠ@jڬFDLᆄ8=l{ZGKSJ]zd2 lLժh.7S1n201WfNMV-m(ީYF"F걩wkC[t"+~x"o>#Ј1Hzxp80|g2N_|ߍࢷpo FDϟOӲy.T߬e968D `\"-F)^oroa&QmXC$ff}Dy1ң{Ն&s6jzn7dFiio{Bb؀p}5*էL>FX+EJIE\Ih+tA,sBb BOI9Ef lPdd %&AmazMVz:i\0Έ0):}"9b $68cġMmDfZyފ0@CwFJJFȀo0aOHRe-eyOvra9QJS@)0ʼnۧo>}Z#ZL $n1eN9S~'&~:}~?/}' 9v-eut4Nn"BTx/"zk~bv@*.Ot|:/"auwT=% k)u]ZVB1!yf RJ,P%)X*v=3'"N%S&"2T'v &Qn[C0"jt@}zH -ػ8GYGBl1cTk>H PZ3!li`884'x^ ,np"xA`PJ%& 2F>,ڦ>EPw) ;~wO~KR "5$=L{Rm[6=rb2z4k ! @oI@L$v~{{{~ߤu< bpW8Ż0bF0yHHLS-~@Nlqf0ƛ&$hdf3f24Xp =hADDѼuXj)"Ք %TQq,TEJ>Fφu !t|ZK"Vׁƽ`c^ۡLtu"$8 "sj:EIhcI}{<غ<+?9aCͧuʺ |MN1ntir][t\.yM- GmJ/~'v8CΑs!QiG ?$i>ym-T9~|z>>=ONۮk+.pEA ժu]yx8Ȳ 11*6Q+P֔ lU:~rJyHrK.UITvQ(22E%p Hh͍. YCS,57JW=;ZmKvn lpט88E雟j)sb.'~NZROo-灈Nىd"#""**O=x#0l~+*4 Zd ?~ ,>!BB5)Ӟ+̄M!?֠eA7C .,SQhѦ͏ ӽ0i (06l Bz|W~v:~׋HAbb]: Jf"4;@6/m)5yz]ôqrWS!qT")ep@4_lt4pn.22Pb5U;24g_1LiHeEU@2n-2╉,ĦghhKF@Z1J"p^o>Q-B !@0 Ou/V:/;e1R2MayvM)yLn\׺,|>].r%H @J)~^xḔ{ $I<۱&ЦUG3&D^^>~|~}}~~=<=]O.b-z#@3r,OL) LHfʠV"ʳ苰 _}6D7" /_{{'? ?"GjOMh==ӣi#W9+UЋk׿vc 9Xs"2"|ٟ׏HLDD;~;<=i)Rj$XwTh"ˣrM)!8S4NуRQABƞ;@P zkDwײ-Ռ HM8h0P֪b՟f k1#SJip Zin,yh!Y j0Pͧ/j!VQuYn>UEq^B420j@MUxM?c"mϧrߗe]՜ $L8̳ ycAQ, d-PB奭Y8Np|I +gѫ[{eexv;tC'T}fc8s_?Q"$J4Ie]ZU#"H`) )2!ke!dvcàuP0)iK\ar9/CY~If*w51PK'@ sNt>/`UDzǧt<HX,˲z=:eNvoW DR U*=YԔ P!LLOPW)Tmv~78RNUj nH;>HrYוiq!B- VA˜hAP< }oeFblZ˺.u;LMX8D1wEb[d vl;vY>~ Z7ƽ36Sw0 ôhRNRf.Ac2ϟasrNط`ʞ0`RkʃA)(ZkC=HNO;~׿?%<,Sy"  6e&Uo%^<XEEr>8qdN)m2 GEs_v;1Gv,Sv=a_]]f$|^S98P֪9\hр;iT"qeíG_KZ+Hf/`ꂄ" f9&RΜk-t j )3bڴ[5A]E~T{iXEl0EU BdF-ZN _\xjh Ħ^Xϛ#Wŧ. -j4nb*д-BP[Pn{JMldcL88~x}=|:Cr%z}}}}}ލ4(]O IDATeYv_RMkJbbu%fd/1luU}T|!.uTUDO/ii*Բc`kYO0Zu]4|n,VLL9Ɛe D%p/$/ĔqR!2sX%圳3.ӟ&wl>hmM&| pI,Fώx-4#jq|P)}>щAU4"q|1rbNB0ô\UdFE-P$Tb*l؀?M9C̪=pfyBiГz<Ю3_Nu9%d{i7GeZo IvO|fp_.x|y;qJF*~C<< 9u-˲ #xq s7TQB/ _ζF p_J p{rW->V{w˧H>}00F E^E4=- ,Fx@#YӞZ_@naҤMˀWX 0y]wF>afG闟0RNiq>XPxT|χ *ց5,}F);`Rn˺wݮN38'rz\/oIU?~|}>>8cp]>fK)}"0`$s"̈`vJd)Qz@Y$g;$"7͂L@4s9;3@1q/˺v|R\oDD>YKl{Yy4N"R70cF~7q rZ+Re\ghN-F|׿g?'}fHfw#0d)}k۾Ԁ zҙm6PZTDAa;V.b^c䠩{`9DDjJ~^c"z5%BJ9!0v[%OSa pJNL9!f* шWaK񃤦R%}%AP}ďI "/beXQ%;8C„"W%xN x-e@Smi])!$ͫfaUg%3nc$lΒ`ZH@0YPbA"rotTSsJ""9~w\ODto6jUv}޻E|:=~"PFtD90 *9WNDIQLJX=r<Azekpi]9똧'S8\NmgZ!t.=kj7> u|~,x@nwlf 3Jx IDR3AqEFLM:&Fkm~&L:(xUCnՈ( zȟ95߱bb+|Zϗr Ncv_~]׵]O}5W2 'fr2YMI+C-ZsL|,FH`責L(E?iJe.eUk57 ;BPE!nDϐHj 9VӞ!jJ03B -* RBCUv_&D#0iեRe)ZkS{y`" A] |["t5v1*Xڏٯ֢zF׎r0N=mZKJA|/~rL"<3 $GFzR '24*9(=LT|oQF\(_VU2s EBwڊmkW"icjy]|ٸzD`4RV(2y{a0{c 7g *]Ηu]aGjq94Aukk*hbPJHl@F^6FNOG9ۢIXf*By]R6kl#y"tu#d!ľmO4C{g`BOWb+yeZKB33SN"[cKj%I ΍)rbk2usZ%N+m&U F:vor?>|y}z::i,ڣEZʺE,r^y.V.SGTlyxW)`h7vnC glj`躬Sm Xlfu-I5wyiIxՖ 8R@(T ,QF¸ a83"CBX~/^__^_^__nsw)~Q ndf8MfoۨsvܔV(j? vK"V=%.oΛ~դ,8vai9ͷX5>ĴYø?Z׻1[GJe6XhDjd=1JR *P֑lpO) P^.)gkUGl)}Z+7r*QBh;l}; _,?)qil??Ɓ33{#_^cs\z\<Ԉڴ$gBڼaL˼ d"<Ϸs5Tqoگ[nD]:afn~a|hܻw[KJL([lDO'XCF]c]3&v33fR#z7 ] x]a9GRFbZW=.kI `ńԶ 1F$ߙz[`"&Dpv1h@p sQ|6{ )-d(rw j}j5=`q[ɺQ5S_M fXO&Ìp_mlMP Z+d%CiVАA+❴j?r|t<pty3U\TU**V5UOU7f6#""Z]: u7F$DY)-[4|}0K㘇!> pGǯ4gP3uz8zD&hf$Nbg1 x5PxT u6_6 v(x 3kIIP05m.džVaMn ˳Op9=mDGThw"DH(m4>7e_2~|`9aB/H֒" ~.k}?x<|㇏^8 ,r"te[Iuma7t4c6e?/,ajf1|1Ȝ BdG0vޫjq.fkvU) LYDD )̈(ahCvN%ri{ \;/t{ H7k M r>s)DD~FF،Γ?>_Q؈找SyHD"~Bjp>VZ-ߍ(Fwo{?!)7X PqMXd4!R],?Ʈ/vtYH8g| Zk,6.@n1m'զKDD"u?#{)c1Y' 2bˆD5t QX߀qL&X%Kਗ਼!C5"1` >T;S`tF#^*&jp2BT`ĘE&5n.rY aSt0Ř7`<8nm7? uU] Aٱi 1I6";NpU?z`[ 3T5P\?Pܞ#hW#QHFLQml 3ljy3(Oӱ}pwAqY!8ÇR<~_(b6UYfNAHGyaL[kû^/ܶ%kN m|o߾}> 4^˅ξ0 v9$MWơ3m$fʥ{`]d^J 4"@~UH*( 0Mm԰}+e{/Kb=cF*~o"2Ƙ̻.a.-gm/u۶޻ޥb]u T&^FǴە1=0%b#{bӡodPֻ8: 0vn| Ȗ @DiT١a463p\d `~8ԟq4Vυuc?:T#\c!kɲCfZ$kS |^14޻?sÇVaݺh曫S0LqGS1`&*\|>xKʭֶvvi6yn"d\sY6r:ݿxy;Ïo yO̳v<'ȿR =s" 8haPMp@z"z8r <&/R8pC';AH{Î2T ì6LAzXCݷ KV!SDv8cDBBD}*1YDfLu+̠ڷp<"td_xKVo?ïˀjm_&20<${LNű KN|;VDs4;/ܹۘC@1zkk5vӖ#uC4mH@*@AO>a#FOm^x-@P{:\/Q#w{ IDATе~ե0z/99vpe框u_dT<~*!ꈘ'S->vFEFȧEyΟvR\W3xfN+C., ޳p1zW31q0Y[% 0UDL^1d[pq8Rn_aPU2oX+7/X2ʗ|>omBS\je^^F&2=縕{d H}4#Lp:-.˒?MTT-ao.Նd,zd,k]REWRD>hyP,F ]w^TS dF#b]e]o~eY|6޺hM=?>Oձ`@ѿ3'E.p/xpDإյJ߶rC|L oso? 9\<./6Y7K].pH8wNΖwc*n󧪜L2w%q0irdvW M0^~10ËWoPtA |HT 5>i,4 ) GuO0 (qF>FlwRj-X Oz vKXVL_*q}Y5⃥wk!pAfMhD`#">8"5Z-e( I3F$Itc,d瑍Y gf@\t}OUW:<ٵi%L_>@ QZCP*sAU4ㄯFTr;(0Q*ߒv^zw?r>kaի&b" |0]0s# c؍# 9TIK;f?6ܟA4?iwX?SBiec0:vC{-_5#o4L e]jJW61!zbg =|:ֻ=4QR1c,:R5"Ҷe]Zۈ+)D7}|_}|$Yp=D Y~÷e°4L|ZPXn77 '0HiF+ژ1"lUnYm7bFgnϏMz͛/첹K3"xRMԔFVRJ"dfB Lx9!ss<2^F sAdPk%rnIl \{$Br&uHSzTi"J6q&.Y obi@SS$DԔ6!ӎ4UT ă0R i$[d81^Og6ʕK`UWk*oB$&m[op<]^C)\J#=/6CFfJx0i]nvz<Gv/t>!_(Be #tS@] 3՘rߊhK1Rj-]&}Y."ca:>G($-$X )d2;7CeYjRV}qe@p:޳\Wϫ.OOӑ Ǒ凿oĉqLZu ]ќLs,`٘O7|O/1vD#~+VĆj;1FHRݪ0G* Fq¡sJ3S5:޻ˣy`%B`m3SEQ/^g~aBdߙJwڎWf3#Qjb/9XN縿vʖ1τs'{p!z^O T "RN"2fhB؅é;vfjKxƑ\*#B*H ?ʠhc>wJSмk/Q2iz0Y)> \F޻t6a, CNi?U-Q|j*JeYDRݨl?Z?$D{8&]zkLd˲rabnmN4Ӯj* ?NsL.]=_O'LOz'0`)v@AO T"N"Vl%i:GO9"TNi]zUs{/~MDáş|_ ﹱxv¢lVtZ^˲eYE| `XUx:]/X* |.L!v%zmGO`57l΄2#F-\+R׃U߲wwXGGUT3!8-sf7mjL;:CMSp$>=8t0~Ր9giwG`NHF[|~52FmgE[>vt>CB"߽{7fYV€2 a0.\Xk ;;,\ܯLNl@T], )qL'4v df\]b|4)|TG3FrgY\9$ǰ;9~ ΐr OK@;H[P5ѽ]8D~wzD=֝hfyY&\ŕ$ΐ9z5 $aKXN b&M|" ,ZUBp<5sjg`i\Sd γH5"a]G@1]g M"b=,Kv-zT>VWퟬܬfáE ."rq_B\vaQ3$el}kmk]/r/OT7zVfH ں. 1' זzbt15l~kԥEWfeYuRTMe=pj*f2?sOѶgݷKN۩BS,k]ײ.fV!! LփSo;LY3nVQ\M]̸=j0X撉vaq&=DEzW2α eS˲,҉+q!&q 8([QgRǒaRǏ{f". {r<; cь.UZ!U߆QaEu&$YOׅTTz2͸5o޾꫷?/B[Jһ P.{flw{bň4cƌ>/㍠tt:l"X &5 v*h 0ZǧǧֻJ)Ń 6beAP/+dumi79zX,ry>JEUn*ɯ XOz\y}qܯ4 !0`JQƹhD"ͽc5S[8_&tX7 !yf̃="j]eD.K)LN2 cXG4IG-:8 2͂:rAeYTS|UqRA@'0%E$ǧ'b3ɋ!i!Pb"bphl$ 0@-\65t)u}|}?~g"^mx]Zhv%}6O]v݄oN7`3+(Rҕ3M@B.u̡PyB, 31sa,i]:4T>x> %%.He'{saјtvD$;d V05PEmRM%Jdz9)*=;e`\Cgbb`cҾrHDY>OɄDt\8$r"91>T7G| V vaT 镇~TPڠz  ^! BBTz&R gy?oKĥp)"kXQ%ΨrLXӵ(eYR*#"C KD-xum<=WbfK/ lS1HdOLДK jVP2 ?[P_7Wo/2EHsiƒٛM-ɼ'{*e5LE7$"nszU<"{nO4x+n@xt5D j}evNn@LUûo_ϿZJuaFoLH@) o1H-=HGNRBR s ۀHD=Ska飚|݅6MU{Z76D IH֘*ԬK(_?dJV?Y6cjj\HXe.s`d Z%_?j +KvDNĥP)+8LOA 4>wQeYv~Y뺮uy~zUE>= PAHS/_'?6 S6`Ek4Ԙo$l0EtP(7ʜ߬@du…Ho[ sNG9>C xR6M-1?4.ZQח%H~9~{~xlݝ D27Q)%eo|hc^oq)u6PE|j'lhHD!-m0ݘN-~.aqIT>_ 9w#=E ^ m5pҗ:0"w`tS"wgBD (fzcEV3U5}{uǛ4i-ãl"T:CMɾ}557>pݤmsETRikчn0|I%.™2q fF&бv?8Mx9SjE  RwY+D+VXhXWPHQT I7'Y9ɰOR6CapEE.vx\3766#lG/G#"ŷ/w/unuktg^Y1fio~xuRLUD}ڦ ceT!QPa($-A`@EUQ%FriFD=Crf3P2 %Xߞ3 2oYuY?Cć"bku`.4<||,!,d4T3(Auxb"Ih$%j)SGCTn޾,4h&df[ 툛C=m`*; mrmFafGQQvWgJb>O. YZcQTFUFu3?D,(e70~ӘT޿/| .˂ĠdiU?XL-=baK&oX4Adm@)Yk|K^LHN֛d݆%>\ˇɆ}gf<;9auqf8z]O Y贲C)0Ð۩{aD7C7GhN~ phiXMk8 hxZ>p\DA͐kaޥR*KA))hC%mk]J]ʌ\ nK뛘jf.uo]證H߶5Y_:NuYʲ昵$˘]: 97D>q hceߣ 9r):Gc̮E%z۶mu]*ho2w`@wci#9{J;Xp.R|޿/w65흙7`"fe!*SݚqTՈsŬ{?<#Բ"b3]O7G9L${RưsD/aN=W{ӻl`7E a IDATFFc7jg=ezo'ydX ͣ柁w^˅k"I&7<ڸy^?̅8Ͽ&!CX}TF/ΪRJaQ '++30_MយQzB%b$PS$TKu1=S*+_'F sjP#F `I HSU @4Rk]%-k&fF0"t Az~Х?UաMDؤ^ IL9GRץ֚ ?T4TeR8W\X7Woߪj<\t@s"TܭGStm`7~o`htz'}wBzZf*`!'##Je7z\p?Ų2'v8Be+u?D[w*q :P&$9|&w̨9WH-Xn]T }{2Bk7G(J3XtxdYb VJYT^yݛtCPDŽ/^nۖv&" >dHyK̜jIU2Q1#[ 7Oow7=ӟTE&nhRPN|*_n7Kccݿ9 1@Fpz/Lr4A2+^Rmk۵qI*Qwie->|xOmќPOBq>#g?uue`D,kƗoV1qJpӛHl$4L,p)\#BtZ=o$Wv+8ERLT""cX4r+ L|4z.Ԥ2.2+@dS l>;7L#G'r{ڶrx8z8TCXtib.B 2Kk%x"r>_v\P՛w-BX#JNd,Kژy<@/;62gɮ;2 Okz^GJnӎmnmC".D҃i;px¸Q.~P DWfdM]"ꢭ5Rj]* (EPKe 3aY.DHc>[PSc $ZF\Cqg; ٲ,V To諿vĴٰQq 0vi;T/Ɵ2o~[kB(KC[^:bLfx0sTlC5"2n(nR#i-ߜ;Iǡ1}Rʫןy._/׶m"b*ČN'US"".:www Rbw$֧h8sHLl.ȋAQ-Nà1z_0W Rx7s}\@n4ۘs>8&߆Z e]+1olB%^Zy9[n.w7ՆOg^lL34&F1rֹÇm:>۩:K7߄'B |J#uMxa3cJ%bH|}HޭhfJHFvHt/xMՍ7vzu'ϓp؋!3SܷK-ŊJW1ka`76ڐ2cGP>~|ūǻJr-N 0*z^W#' / 8r9{h6 2 -1.Q:+JNY $Ð̱h1tU`k>LP$E{d}LF+ T̀ QҹDLv%|/UT@u=0LJr1+\g#)"vCh$`|>(\Otz~nMErM"'vzkfv<fpJк}4+8Qb@Xx?j" qb>ڞUd T=GqFTaS't,w,xijiv*76fNWSМL6x݀ɋ !1u]փ%."m]Wά0,aMGl[kϏ/^DCԳOe" r(ꕡ揥[onJ8_e]"B?g[q}۷o#$j·'M,&tpt _ AؗE|+?ɦĥ 941Qq*zDD3j"TwX<]O˺wZn턨H5D,==><|Pk=N_|Ow.p:.s~&r'.·a˱t#`@}<3C%ăoPD?Mjއ2 `G?Ь|ܔg&ͱph!`4掰[kͫZ*z& 3hb1v'Ç^ Ibcц)Gji)&ɓC:^Oe>Pk-eIH<rIlhRG͆z;}zcoũsN&cǻz8"Hw0 zm:6ACsDiywi.t&CkZ5.v{ibt] n=ݳzD|_qԄxƖ1 :|-21{IJuY]E ?<:Tv<uſOV0>4 nC;]d2sO03&l=o>$Ьmɜ}2l 9d]s6Ə{ xcϋva㵏ZZ~ZXr^<~wһ))g>#ݳe "'2c :xL.##a:8I(!!)LJ^~cRe]/H(A6ЍLF3Mg|×O6 ~W0s&Dm}šRnat݃1O$Ljy=Nww˺B޷rLmLmֶzmmCxw6C,@6 !RŠ0"#.,Jמp$u`PPMШ>*}^M֤7.Ly/GNCH%)3s 8IT\z7B\jo.ODXUP4{RzSy")b8čNU?I&qR>&o9*IPGp}۷?rۗɕdb3DhE}E ̻cwe>AUe졙ek`-sMÙA!ZrmUoD6Q*OY)*!.p@wJh4d \,xzxxxw_jORAg`4b'0Rs'4~ԈLz -axcpZN\h k@U%#w5Ke!#m~-ԫ\#-w1W3ۈu B52 anfҌ]znx({SqJ=f>iX貮΢:O}ۻYTA M41^,ְz`#WU@^h[ `n #K6"fy;rļGU4 #IME|.FJ?O(~0BvF 's~kfǛ*R>o}eY*Ԫ?.&…%2zgk9dCkG;j yXd,aᣇP:)C( n2SY :G$~?ϖ vyn#|pxnN ܕ(*5A|TD 4x88 ~}bH]r^XJ)! .`PzWo #EzLp:Rkv  o]ٖ !h+=3X!P=4CSugثlCl/k(B\z 5>q?Sb X)pݮ۶AxC),Jpu$en1k{1Lx'~eYeI^T4o.yK-a29G7D0MmGOwJج#Rp)˲L]y۾޾׃a/Ip;~4;VgT ;ze5FOn>2 1}ѠEHgubob K%>wpKp474O7>Ss̸tPfa8@gsϑ |F?`(SIUU}/Z*Tm1 !1q-\֚fESY #{~LJ9w:nC1͐B+6.%4 cFfX!K.yL}4R7E4CPmy\?)Ɍәꧠf0TeQO`Z!Rk)#SSsңƇP i^xʱ'NҶ M lvjk~;N98&4 8(\: =unCkE_f(QRJ݀Tpa&.m3&R 7N)HIRvZ |SxK@.~-fA9µiDu=hm1`c,g3]^0M1tPNG#8N.!#?zWo~P4b[to*-A:Q:2wi0k} 3@k3q^w[|jjt㿴 ;ƔxDfD jﭔ 4We)iO q-OUՍ/^#g_~¥1D$qa.Ƽ7\z8nY(I_X.dȅ ֏(ok=d͟3 z#0덨TDE.O߰&dZG\v^' IDATZiLA]^OǨr\鮏\.\JQ6|8rĬx)+vnenmDx:ݝNih$΀Edo/A˄ 3UTENo1s{7d:QRڤ'Sp'4]`-0`b3@eYF׶|>ZYO]OG #/_˶-q93\T[Eܔm,%?PR-(BW;C֐@3"܈9gStǀX*/23fDC gy~;GK umn 9Nx#bb^C)5c{_/~ߥ]0 W&Su7.ɶL` HGdvTs6<_O +ГR.uНapT)'M^B{q%QH'xJ]T. hfcwh>ʑɌ jjjwDoO?C J) M8,ͩ$X.piЕcD]Lg)*OEsԯ8Į'ŽB N =a0Sdv= !}OkW}{Q]֪ͬb+Ϧ뵔 U3nΦ|:l9CInZwŘ/lа:,LD-cWkAUJկ63oG8M J mC_QJ&\WNtqNҩ ]/inDK]eϤd(u{IQ*uss݇'/{,úislq(JIO6,D"K9eA~8֖8IA$Pgtv D7X+>u%"Ҡ=@D(1֮mkRz#hoDcLrA\ ` -0RkU3ZC@z8 <LA%aY+߮ZJ$َL=-D z٨BW·#>ʥ/|_~3.%̭54E{K&3K װi+}B֥.uIvP] MYňG W C<~G^1?tՌ٩KNvuE2(%9!4OE"CW[Pi 87.)k.LQdChJ7$KN.1q)K]UؿCph{.%Pq4mJ&r=u=EE2yfƼ!(O0}IJZBrM9w1K, DkB$tKIP5F%m"L:!QAtQ#I@oS]9هbf w:TK]JH|:"T*ܨ3z\wϞ>Ku<1#UE,w>T>X:aEzΪY `߈ݬ|a@ˑc]r^4;KƍLϹ)=oqҮtp,3[=%I"߉l1hfw2mNGIIcX_fl;O+`IZR-6&Ŧ 'L l`m CT?} bO#/s&Kb#IS3mn]etk =LcK -#|1vyO&"9 Ǥг%3A foAf ̮@u\OCK;u.Ppw=_|7XI F TPHcci}CW:Jmmg޿}q]pTSO ovN/>Pg 3P`uh= O Lhc̔ʘDGXD ]eT03B"TE.Rrb&m۶p'g($鄰PbLaE@d4^U,˒rsskD}hDS[kjȵz.Fp[rHs%ȭO CT1 |m̼54wA0V 'x(-jD*" SZO%c|Ul v\5SRaMD=mGAy±0è {m|3)^ţ ]%ę]QHi Wf]|.,uPT4±9'j]ٮS=U| $MUe-/~W5o})|kؽ]f4L6x䎚"ߤ9ɖ"(cn:"Vԇ jbU'qӞl?D@h#ẸņMTEVDŽ H+.5g˺j͓Vƌ>Y7݇wLXJ-H#vgPaLE "ݙ̈́=^Ԗud˪a?Ü"s_Njn΃B3lwrQ>>p"88Ņmfs*j}0*Zjeq So^ov3l:1s`d=Ng-/NBmۘ"S$ᄍD.5gv9޾y7?y\>TIc0xȞap@Qe`$|p3HUAY_-me (ljY}cd4qtȌ p&eAJh@bi2`"bnKC*;p.ԬeG#JX2w w2 @$x0Ea<"ZnnnۛpXuYzi :7ɷF̴,gO)kp|VDe8M2}fExl;1KTMLc];uI&v8fjf`/~w~__ВhoKDQ控Œ1)aS))u2Xl4dV)ҷkb\+GNw]FԘ> w{ 2>&3UUNL_Oc&Fܪ!чoۿ͓gϟ !a}yD\bQ4(i𡠇:8OD'z\ 9ފD(bS0:2߭"MѨbNbju331i;!U(X//ExD>Z 5_Pc]jYW Nv|:h$L6xM'/_NO7aKu8f͕ %cE^#op KYL͂.Qka7=I"gӞ% Khhf>2^TK!Q¨ S*bZ+mh;=6p|‴=̭WCGٜxi$rm3{꜈+z"Rhp&vXCpJw߾{ӧLէLKxEI>rC1WPu,H?fuc]:J]逪MDpԵ}H8WNh&-3U.x9Z.Q F𸅙^g;m\ҤC.k)|~^/ U:Nse+箊YjR/\ _LZjT|;?~!U"v7:혇I-&v^I`J*v%f,uғ!E%bmʹԂQ=D 2'p 5D=C*\K%dh5dۧ8 T?^v^hbgy4 J+W⡃aguچmLTO@*/ſ7&=q`}5+.L}_&T&Gl*u}}SXTGH1@(2pٿ>"fLbZ3ǭcDw,4&L8/ u䧀ȶ8 HGOܗ7SI͛gϟxik c)0XyYOŇn͚ )6m\B9'+QS4Ui-*_ u !PfU%ͺد^j@O5cƥ6A.m0-e;l׺iYֺ,r"1bu5 >qh1'b­|A5iO<[nhkp. /[7" !2 V#*10pI4IK+cxꫯp b0F6#9iw=PMFo`ΡM #;>׌EejN- !Qh^#nr'=im@-$8 m L<ط96 ö fN]Z!֛I+Çw_~r9oTI-2þ~|7Vc XP$^"ZkW*#Y{0BӢքƃEeܠv_(vx#SZ7Z+F+ x4?*hF/pB̭wϪH&Zrٖu=V.}nԏ(J!@ݩ cЮ[V5r:ema;.(Z`AuN|"8vDXu^e'* `- K%"ٮr]\P602m&@6Sf1Yz4CԼ͐o74pysZJ;Az9ma%e]c1cs1} X]bj$"%!;-O6q"º.n'~o_X)Dh&9dp5~Dh>zf=ncG>w3Ϫ)$hߑ}ΨBRGY0Z/CÛ.qك_ w~ՆVFP8kj(zGJkŤeԗ8P:Wqa ݇J]n>3Q!*.EcluEubVhDD}ѥ |}*so{=0̼t6S Co5ZIdr9G/"3i)߈8';q/if*r|z]ֵ0V5!4z<(:A>!Q-@.n;u]K)TJ\쒜r3S4q;v^čH4Hu5 s)> ݶ-WAH@Ɏa{ + R9VT{׍p=Pծy(ffjL|}zp7lOH͛R.ʼPzy50)?6tQGֺ̰!L+XDdU\6џAUׯ__}UOӥb–,ô0ӊ w:g͂̾Rv1ƨs"RJg; ʈHf%dDobD37.Mޔ0 }A=(o|씱δ]Zd!Br%'F'u1vDGX& /ꩉo޿}ɧ?xYV)6CAMn C׀(`)&5iL&mϸw3}kRbf.Է}RHUXYՈCI'1!tԳ[}_= ΁pˀC:C;"NIu;Ye ESc5labiB&5F@j?#(tu&gD`›ݑdٞ3OӽX͢ig$b3D{V33h鳻_|t=.mЧĈ,S{ߏTȔ:Shjn `*-6>nWj+ſ@tɶ'!+HfqM.#! D@/ dusʢIk]3I! v!X(y3M,#pAZͲ5?|8"c Ig{SuiN /'5%cy~:NՔk>ctxQ{afn1Stם?!\*H jd-prUNW3H{n l') o8+Mf@NZ> evZM n۵.{hS2 %CiVP 44Uw?7Qz-)oӺS޽Uox+r)*{Aĥf` B"{81O-UDxpo򬠙h1=W9it5P|h|EpAA `-3R!bnz>.sU&brB٭5דrr'Ikr<a9euArnyFt6U6#!& p?0avj.ǼzʥnUѦ*|*&׭˲e] PU A}%(D&ǚDD@0B4f!ϭ2HkU2H-tlh q3&Ʈ}kC߰Dp<7{*-!_m曺T"pLA?`K6"0驷rN@Yp)|=T 3?|jgmQI{`1 ~dI;G{}3rMW~o`Č-A8PfӶɐ(KDR\NADRkkAn uo4MvH.MYJ*y ~ms { ̂ËfZUҧ6鍥@2 U@Y`Z ?6R?qǩNFϹ]đ81~c3\!LK,̑I%Kyp9W'B\Jz؃M,Z[kNO=;ܔe˲rm QG&΂3.BB`=N63$1y1UZXaz^ΧZ,eYe)uK5':R!pr=ף[sۮ`\01QD lA 13+ WqȘ MA,eYvݤ (Qт*J.Le]sk4OTxvzq,F43z I(F@n(g~ׯk?uy?9\0bNo c]X#1Үw[gB@`+Q]UQӚDÔp`a˙ipaIzD-œ$P6n,YȓA.F'*lV^N3B(Vj55`6\m113Fyg9߽6DєNU~O^\niv_D|K$v`hlLܝs. 1&0pg}ڌ KtwGi.%> 3m/g)&B0h@R~o vs۶u]OxXooԥ vlk (z [o]8 ;(TVZ2/*R="CNeYJuY . 33w?5Bh_uup" `KK@3O"R:B=DHIO dz,J}oò˲DDD>M㱻fZnnnDhljw;47ppIƂ ` [{)RK\B1jT[|LRjL3D:GE?#9gSW/P` tsкM Ȏ:^ ڴ U= f{GC~B6#.xlffGzc[^q/o`ڸ]5Q;M.Jm\ 0( 2GÒ𜩻1c`rUbVQe{7^|*TAPPBA;&8[M[xDMۧusyNj)zMUv.3s)Xj˒ڛ";!2pձ'-̗0L 3DK범ei*PIEQC5Y&5"(a4[)ā`&7>Z٤ֵI24ofApn<.~08t)=ܵ.vߜΒ1Y,` '-:$.v$Sfң+3̠YJԿT_pt`"&]GtCOHX@qr݇޿{+@|+$ et̡aj&M"߼w#9 YHP''v\um[3~3S6 fM.2Fx\.l0@ raY,:->xi\j" Å P95}-Y+;ᖅ|zޒh5E("]/ZZja! hBg~ڍJVHf*f,kY*21 VcMՀo Ƀ \6;4YjщD~AENwwm1tI,yc$xGf*1~YDSjKߔC 0GbZLTE.ϝ 8>7N ݘJ¶OY@*0<(.v~-8R f-%#̔g*S1ڃtα|Cj=hvN#5:sg==sS"v\z HFJ"ssoEi]ݨ0=]/XUL߿{*o|uE\/7;TDZ?RZ7XyiHpIHD%К8?@*te @9%bbϧ\J)\8*`n "vzpwUs *bf]ȶԣ#ރ0dףԌw&1p|Fa#bզC!JR]\t'((ե,9uR{%o{& r.Z%ڿ)bQ:QPZlݏ SnDuEǬ%G`nEn3Ժm,~4:w?/ lV4+4Z܀tX@}p)DJ-ů35&\ 'iw9@dp޽{駥. JP1p9.U!xH1L'f,z|Pp)ǷO2'\@#QA{&dGzrTǽN .KL͔C1.H9?(55Yps\z<"Ѷ][D-Iڗcn BPez4.Џ2$f pD(LMe"[ctkMDv9K)hJKS O)șmi*,bVK->T羇Q46 YIs3̭)&R4]zsSдz@$r'${xs,">: '-q"ƺ[Ռ򔺆MzZka'=Q?jCCuka>anvqDDYO[Re>yyr"I~jZ ] AqL8?4~/z^m+PXilf᮳t@kȧ]1fS@ۙ',l@LPᅘus5A@|x߼x/&.aR "6/AU\1r!.i@%c#լ`~oX# EZ$H4c.oF{鑎$tvۘ<mizz. r9oúv=o۵992'} UrB )i0ŁR8aQpLRzd@Owt]c<̀?PZɸiҔk&r>g]Z?ф̳mTJma M %wo4=o(9w!x.|?PDA@C0qv3TB,@Troo<}zsI.KLą&¿8AK,dqDzS3&MV'%rKl|8"1D0t0RvlE)-kۆګ/mCiJKy4.xs{sd=,`u^,;hiCM&ZНcu*^݊bѲ'X vhl@n޽MZMmNViJ±ƬO41ZX݉kTbQ"77ݷf/~я~ac6åX9اy#±)Qv%&yWsހ! 5}>MiugPđv$w')@i1n&m죒?$#Ydzl\@w"+G2vc ;#`\NAZg\ $~viSUY spS[lmm6S\ tkFE^Z^&&%+_/g[aZ0͕exct ^ # x|r{9Z].m 3e}o;Mq =PpA ğ⊆NtRqShyV~ؕ~K]jEp8r)$&.Ju=OgEڻ<9*q횔gSۭ=):s"AP pBP tz8OU_~3L٘ɱo(})m۷œ"9|߻̓]讨??Uܛ.R 05-i*|3BR=~H[;&j$6)oopP0 ߥYYeSLC @*?*QfL.QBu Y 0B}xg/^~[ 15.T p4%:jcakV*ڶ cvI~7N$"|zO"⟂]n{gvcg6bLkZz=N'Oʲuzi3]j=,gea.]p܅{#қ3EA1umff)Ztr\)D@fv>%Jx,\߽-bsB]'vuD)o1[kEmvO:1K}@˾~u ~ mI"NJ6 NWh~v̓9 'Gvp]گjmĿNKAYlMu:]İ5'$ni* 'ʄ̓ecSf}Wmav^UUR(fMz2ʥ@e%!&\cƷf9j'.%Y|?]7Z@%maf#Y?2tISL:c@oh? df3K%nQrlr:#!-\ pł6w=3R֥pWiLrd7;26>S <^;Ä!h{$ՈG3C1J8ZZk&pku/01֖uV(NC2t4LgS(yDogoiJ˺7jteD^ΧMZ 'OnF@$")<2Zn@Aq9g0ŸM6&|zXsF_~~3]h1D9|2,|K )@3d e7_2%`bvT'`܉X(G7Ss,};7{^Ǹhm [0W٣qU0C5y-uִ&m۶]e㡔Zz<\ZL+TFV&#w)A~ 5^f>͠uZQ(vm 2LMon7Үݓzwd"D.wFt=y65y$,x H69!2VcW_ x aN9Ʈa5<7.COaK˸4F8EA6s#pfLag1|pɅBbnNa?tOFu@w`zm鋆4A?{>wߘْsmWc k;El'y4m# FeÇO=R> IDATR\vvc 8uFMh;$*̥> -udx@'zg~J;?<'Wy0RwFdv_wf5Tץ,]u^.m4fs m7H:j7(ܻO5*&GXsǑl_|25hDc1A3blKĥV5*M:ј$@Y5 /B  EZ]T 4-9xr>}|JS24V3lNK7}9s%#/cpaip H#p=Zk]]. ,NxWhZu4#jx ]M9 ikmYp}?X^Q@q\qJ{Jh"0sƇ&s|vb_X+jشwNw`R7RR'tqhFaC ixhƤho[{7|!YC;`MkxYEߕx5yz$&Ӿxy ]3`ҢdPw"&ԊS]XP#\tĈȕr`0Gd\cd雀ю&j ֚uOzX7˲.Z벝ϭ]񌌸$4 yi%cg\r/*Ra{%KZJy/C.$U / #s3c$Ͻls4#U3N7c@[1kk&(;x .z8dmOܤ'j"jΜv껷ouARWgL LLZU$d;=!RaZcʨͱ ڶfju)_'.1/lz`z.|6k6$fO@zSEl (593`cS &'w&fsf'!!}e%_,U8NL@qپme5paVzťRTi*MDeҚjad"LڔRjlhM-ᵔR޿Ϳwo~Wv\WцI&,֥16'}䬏qH1Fbtw1q1%HE@w_q۶&mԻ,a]uYVimMT2A89H| ɢYC@uyЭp ߖ>V>{}9dLqmdk6<=as2+X1;Hs 5ٚl*X5ň[8$> ctOQ]u=0Wq@ѸNxYktj11ZB7ZSefC( >O(x3(Sݠڶ+"W12?=OϋO^U5X=y}1CGbמڠiDzvp39Qc7%cgv'05^. Vaw),!Leɰ#j,XïQ.=f}D ֌ ;-I(obD:\S0ad֞xէP50K/8nI,\ʒO'M#Ry͇oO,,f +KE_>EzۮTڳ>'O^/y[50sg;F8Q5GYZjʅ ɂq:RBRX55uL b)Hk1d(6$ 0Pd*P*AO],!T淨6!'`9}<יoap_<}z)!6Uk[+.uOU*ᆙOBr8g1pbk765(LL DJ F~|#z\Cw1{ ϑ~hА?yﻸ?aAQ; 3MZ3ndn-C.Ñ^߽\FӺ̙ 8+vb/?"̦hFp\v3  8WzpIpR3F$jZ :&w#m".>Èu102#RK)U\J]`alNH. ޿ɧɧ? v"wZTLAT"t"e>zl25NXt{>R8D)DnsGB5*\Ĵ^ H)`&Ӌ9R N1`ё2@@*TIsQ_d* 3vQx.(XHz,\|Jzy8WogZ#{UZ|:mSVuj f{Üٖ bic;uU$F5((v53%ݜOu_/ۀ^Q| E"[)D- S#3kݕxqRdUpouLcc3AbֶLarp_T?Wˆt9onXN{7Rǡy x<]=z`>Ux>Ttۮ\ )$7c-FUF@J,zKeq̽ϹU@3/S"$si 8DDCt =3ZqLw4s$)vDb49"U*b#R׽ 遷R cdw|\1!SJp8,Sb=hHeR gc[蒯;[+m v5ּ:\>G+_K%_'^z~ſ+.p1 -6䌪_-~][ }"RyXUù =Q!h-< -̽AbK{X^ t0P'A cid3ؚ]+q4rot톓oQBH+.!…~J*" d1.$BfBRkVK?F"{Kh"?y_,۴JT!q"v@Ak-mRM8aI@!q99 -OP|+""=7XAEl/^<պáRRK4~ͣkŒ_n 0TGηAB=S}JrL P Fپ+A hvQ&.=BQDe]JXC(lR7AJPTmQ>z|{Rjᔻh۶xuДRYȭ6E. :_Pl<{GmQEmcNe*[__7>d-0}`, {)} s[s9BatΆA6;q0x20D &i~OaYkN'* DolbsN|{|>{sNN7fJkjEu'ǑnXH].rٮ>}p< _颛; u𔃇PpD)!Ei) 'Hk.pý;_VifcZ3bᴯ!AA d!!JjWq1IZN*4!5(pO*s2MégK@@LAjNw?]v9¼2S&M| ;4C>Z*|Òee[}9m==LxV"q?~^"o~ө,{;̣!ZuL eÌH93Um34@Ѣ2{&r+Z;RͺZ`p<͉<`0œHլik3. "{CP}Eý2pӷ=ʲSz }>kJ#m hR˖6.-vBMVCD WZJTo^xӫRJo~j<,~c"YҢZ@9|>NrJ.hv5+M?.PjEݺp8kNr:I Lm3~ 5K Jzp.k,**jNd9HkAfFo7pBBO *"%|M!u1v0j$D[C6ԄXsZ-V/ȵuQ[o S/tk Xab[rы!y|luT)GDX,=K_?>;MA㐽|6ۻIDt":~~ ؓTF*컈 ~_Jl$h9m(R7RltD7մeNHȜl# HyYK^ Dllfvۻ{bm%q1|I% q4-J|w{պJ)XC'qzT^{"OYa=4̪YJZ䡑O RZN򪀀12#r4%aQ"NltS$N[}hb29a A^܊49Z!y0jKHq @"RiJ܆吘s:>|&flz{}s<@9#a)I،vЌL~Nυ7 E@3cSK5*[qY1*R}7o{Σm`'z3)S3$8cЦ6X=qn垾bGȘ`=$S+Ǟ0Kw=8DOl'! dl5~׶wh7 Oƀxm) h}_kv-~q %שkDRSJs2T!f_{OVo ,/^>cbқ_4iʹ9qb"ܶ_r9NwZsNBwTCx+槙5TTp\֫VTKmbAWhڙ:S~dL0T%qIz`%ʒ8)]ULjA] bMJ"lZ+W(NE{(pL(CJs+5YBj1mU zm"_O ƴ,y]u@Cv ^k))gݶKcj|1B -Vs;#=y mԌbCÇݚiHZkղ֤AE2|j5Q~-+u&ig%&P™Z}\ }!AφʧM_5+o=I~=*I+64DΎoIRq>i\.vPm/G:2Ð]4&EH:@eA"Ӝ;DFT,܎Da^j1U]١DlǍ\ȳ'O_j)111BZwRt>L)qJ4 X'Q$UN*՟Um!1e9Բb6l=tp0;}˶#?{1@ o۰oé#RIɮBnKp.zrC$ T,a$hU5>&`EALh H0;O*%B!R'֪U,":QjEܛ,zje)-KxubBr9^7"‹/>QLkPF)shyuz39L%LB L ǃYE4gw~:>'=p" #c 5p4:Eft)wʷkS]p<5P|4Ni-cMϭ½5 :xkS=ܛ,C4iL{ 4աmE 櫺`8_s/=?95Χ]TTjm۶Te,mC?цliY4Sn/__^||ww;]MJ)ej̜8NwwwR=H9/9gD& b]o}fZk-鲝eE.˺ˁ8m&ڍd`&Iũs7P@.zr}@r͖7"L=Nga&ev@́=vVe+Qx*Rպ@KZj"HRj-u+V=&E8AY xjTQ'jZ~DTEQ9^=@*9_q}}bնڦ5]]?|![덝g6Ap0N¦`0!"Gݫ|Vci+z?NG{-t nm!4gQ$"ςhxcw\{I̴ELe\r+8m:rq )z+L #ŢL񟦗~a|CVZ5^[BL@>Ғ i',4͓W=[/={7,կgQ@ ](u]C+9|T ]h;XIǫhβm۶Y4(Xy鱷v77Wz F<x*1'{%aCjYħD(LHkSo\ 2CIBM䯚Z`"?0kōz"yR+ "Z8xb(RS&JUJ]ε"@"zɃTsbNH=:RU ɬ464S;`c7JIKus!?\og"j[/~aqǐU?,(o)x;d!v`mDDItT|z a4 Aq:DbۦJ-pah*|7} P G`+̑1-{niB$w)Uƿ@p o ֲmeYX !UPl UÂVmFR'}~G777۶!b~.c'8Y<\3es]RJR"V;ê`R1# 󽵤@c:*i3V T& : 6f2Y&3rV YTE|uRQ,-Kg(!ba-PkO럴Eu]BMKUU͡[:RK V@»5|Rī׌96/ Ɠ , iOJϪl7S&7[@]=\G]L XMH( [Ѳ:R ֺ"[e9ڊo۶]VaOifj- ÏF8L fBŖM_")Bj9!Mw:@2.HEj-8{ԟ(wPbMZ"Vd* -@gØTjSt"*Yz''Rb[;v55"eIiX)e=\1$rQ|[ǵVS>a]Ar┠-M#0MB +%wՊ$LaJSvD Syʷoo?{u]LüO#1 vmeS Ω0Pp5E}p/C6J.Csv.'fU#Yټ=^F H2} q?ǽпkXA t:4G貆]Cֆ{P?J'+$ctx(V|T0j"bbnmj)QÖbE Zi+I\_?zTI9WK {Fk?Q"&|:~'v:5l=ŹDR޽8CCTTͤm5%c!=/^*X;Gr74<қLEj-EB)Y:Zm?.XWD(%=釐t7ц7AVl"+yZH(3mUsYUn]`$ZKj-W=ܝ=;ޟ%>]]]풗#VXvDzJ5 |4KGՏLA"]"9-/Ggm~V7(4.D#^"+>lbs:3Ԑ9$MhUAjYPi.l JWC6ccǽ8A}bhQȚxE_O{'o 2raE#1ZfPB3e84ȹ0}Z9yEAAR=N?ۿ}oRJF8#d$&V ȨZ:n^ l3Rp,pضj*Ex7:C6\دH65][ΩAK;Zgo :bBv0ƉfbcmudpUT%zb U=@O Q H?x i&}q0!rNzXp8Xjik^_ΛsGQE?( {&m$"EfYѸ?"*Sx 黧bVb>GO|*ooW1#M-C` =ã=isA{ QM4@}G+=**[]'N0Gw`˶fGA9k=,'C &Ӂ A *|Mȇ MEI=pWt7>.bWm.`r.RX{[C㱿² mza=@ $Z0s5 50H~LPEǽZibkvJAXqTzn:%Gk N %iR SRlTA.=zD_i Dq z,[Ona5C>޾$RT41伬ww7)4n^r{A-5uZRS2k5]'Ҡ/1[izr9|::?DF_L( BB!?TfepkTMp/qR-Z3C%1I=$+4|^тСDb{ TD#Po+n5GxjެwQv?W&5뮉՝I [~dUn ^U =LĈZ'Cڅ݆DFP,*Ǐ?~Ɨ]TH P yKp<ú{WP ¸9m_s""pS5m_#"jgR@"]MdlhE`ERTia<5m %sa$8w:+&:"t8Ң/-ˢ"eطGĦI铫HSJ-Lڬ[4qYݴ-qx3a}9UZ岝Et]WBES!~E>zw& P̦ PaH]l0 `A&623"Lӽ)%SRN(4G&F\YGY蠋cadT-BLIk8"!PcC7_pZ.6 ?=Ĺo^`{4u0/ƍj󹔭ͬeۤV@f e/pRJ)e9/?_?}]\n{ԺZ/a=ʭ/aN't3o(ć86BZ*6 4(RK{ROyxg9 HboV0Rsi_?fjQ)ELFڸؠ4 1w(%b:NƜ핥TPG~p'+Zoo^ i{Z dgAG aPHgfK`?^'ب:T>={ލ60 gT*t&FٔiOwߗzKذA[Q3uwvtm:ô50k!dOa'i~nHn}XG*¬lW3Byc3P%=#R8mۊrMM O>xYVf&&Ė@PL1Ty)/l1FVG].3 ܇[EQ}ųO}hP4Ae\ڶ?z #_BՃ0fdA{i78{~0uZuws{.x .BUEFmhW8TNjh#q$&wj6 f{~GN4;s*|r)ۥx}u9Cr .MKDܿSN7/~7޼x^.Rv|zxeZ,뺬Z*Ei\eB&QxLƫMFȓV%j;=̜]+4VTTJ֪cϰ \A{~ j&{wA`btJlZtds V1ZEZM,OmsʙjZJUN* @HIqX2vYW!N)TK&d7SkEHUx7}_2\.𵁔"¿,F=wo͟*>6'ڗ:]u] ](m -yc a(8N=Zb Un9&-њ8 =,NDֽbgܩT'HhZ]+Rw;qjo$l0 a#;cv6zSZ֣eH #b0$˶%#JϳIl.uq\~b]y͏n ,/z{{mpXr>rl.0ŒDEP/K7…>CYډ*9[zF "c(04 %mA."Q@%0J2@h&mDPCRU"e%-M0QDsN\KZJלPYp^]=pw34$={Rb(ڑNDHnJ9"]E̕?#8ТIP[GLRAGƮRJAE̪/C_>>)hr߅%(.jlh?=5BS*Ͻd?}1dHJĀ]-Rh@q줭?EĠ!`'>\p8ޔ3 \C6cf*q QD =#EhE.v-F RĽ7pC1tW"C`myᜥ*@f<@RmWtn/Zv^<{=^g?yKo.1弬Z&G̸ }`JXV9H Lu ݇J~E"ZkliT\&Ҡ`A$QI!i. cDǬҠ_}k_v2FhuWStۚSb@|ww8b`=z칽"bOE>yḪ@ʉȨUj̔Sv= !(:UTO,nH]BM݇DD6]v gˁSQo~Q3տ~50Rcp6k$ŀ?U֊"3:f~=Qc8{@tWw8ش85֎vd0}m6z9)qk8a5vcceL 3.oN+=Lc=bb(I7l(9'#:fd)|@Բm[T5)jL}u tGkno/ Ǐ_+/_2/˒e9ORQ[8p5"q#Y=U %M=T= f5EzI;р|Mvֶ8lD)R6ж:|PBDU3D *R;x\i/=cZהszs}s\+{ㅰ*tu3XO?{00'℄ADfj)j{,@$iݶr!ī{ ?ϪR2{;eйe<8k"U份A5as[-]<lxV>vԝz(A930uRZLE?0_>1z>+']C8f%F~l_Dxpa྿-ܽ5bNL)Jr+TֲmlE3lwca]b s91_N˗)/SbN9[4m$Hu!eKuWm@D&9BΌ.5$FJDLykAӶ؞V@jR-[iޱVÍMmyRcTncMN *҂jB˘RbJ̗g8@.벬ٻ^9[pw"x:{wLRU]eeZ[o}_j{NA6:\+.#ۜv6%7QKhp rVF[BW@\;}՘q,]ZL׮T$vϙbP,+{,Y*90;Z}CgW\Tgΰ+ Z:IkIX¾v݋4~fZOf{h@8CTiyx9<5u<8J-1>lYaM&$:_N[:89KOg7 uL[{릃?rJ9sΔEL)j0Rr( ;+$ dlSq9ST IDATX .DeR!ЬԈZ-RALϲycu靘d@L˺,.|W/[KZs"BJliPΧɞ"F.ɓ\c[N J!*Ѓln)<BЋ%ez\o뛟ai%|^j`lh^X&` ZwN1 i\5sS5+(X701ѝ~8be'>ch_X'dFhzEص⦔xizS1^d,2CmO3]dMNRʩr9[Z- A>Ko+i;S"Re;>:>xp:.K޶s.ZI-2X!KYz3秳y9~Y]0 #j2SbJ͍{ <ӗ9go 9<Z"wTP"R9q)/iY3֍؝TC9MUks͎CZ*5w,9/sDjZuY3*8!0amV_VXɇ?[֌)'NLH6& Y_![镱Doq CG+gTGfv9Q^2g|}z{Uuw|bvR; :3i z+`ZOps݂ͷ1Vl)@jC85h`\I .f p7WL9`W6GIaasI 't{/BoOS(&n-TshbжIjVSėVCX؁pN'uYq]9g+u xlN*zURRm vܿ?cԯ/7Vd1"HNUb=AFfSay4(?)-)甲E fﮍ@Gs_-ln`XWy]˺֪mpVEJ)RVLiYqVµТTk5# #3u/y_Mmm~^moGaGPKa%>QV{\44'sVm lBD n‘ S7'9P a]h6ZJu-jxÆGBD6G{M>FGԏf6\EHZ{Y}`Ӝk< Ã2s^a Wᚚvfd*PeUf@wM-O9b@~᫪z>kTńv)fRK k|/B`d@E 0fQUq&R;ɋl@12z."m*nNW0xSd@DN{ӲH6`N #档ȴpp̸JrJ"HH ^Qr"^\.*úz)l h)֭\.Dr>K a'|faD*4Ŀ -{HvjwˈX]ݞu9 u*V{w)kc4H Uc\j*<f &a]-^}7nٙ Aŏ#~P #{O ]9ϪN4 vօ{Wh`L9p훰˲CS6&983Y/&EqGVk1:5e Qs3IETDjy?O.Mx )*U_dmKr0flɐZuJkdL;|B8%d%Jjq^DK]j-*QM˗@3dLZZqԈ/n UU˶H)S{tψEǵ5CtAļ{XEӮ$|.MCW{lR ybW<#-4|>B^2D}>yw%ҽ_>*;{(MRMT=0C6s'%zMfԳIIWׅj(Q\_@/vFG}C tVѣ7ir+Q݄'5[FΰG |n[NB=r4VRfDm.wf]c,Z'O_7}wss;m6 NX˷TΡ6Ȍ MҼ۵.5CEƴlA0KhUdI)h!38I5>jU qlP1L̙`E} aޔh)@K}qΙ/`xm浾TUVm(pX dH9d`Jb#nCZȖD(T8jcft%CSwW^QWqJ, YrĄs}9ps0$ };О}?HaY#+RkAD ݍ<})!/9w77O>ٶm7/ji2E fe }a֪tGB =n-Ւ,"7шy60Ii$[71 INhR'xXᷤN, _|qS==,˺9@9ebT0 Gו&`bd@ws:Lh*r\9/>4yb/?c?~|:Gv`J+*CՆivTG a]֏UNVQQ/( Ca9;0☦: KZE Q3'N/5~o&t*5xt,ȜN=qX.40GÏo Π z7Baqf ΋{RҙF-_eY(%Pq4B 0sʩDtⅈ<ʛ_Qr9CO >P~ o y,l |BΉVYz3~8Fj=xn![##X *Lk.gyr4M?I>9Hz5 ڔq I㟥~i:~Js 2cϒs0`]MǞGC9{0MR="[]ߝ06ڂD~2ֿVC<>tg7q99xa+s9'Nu=9_$5l%Ngf$bRn[ʉ͕E,*Ia1,gO=׿__/%,˂!ӕl l;AVO;h[C1mokgEt\Z-xnD%T;sETe3 ߶%6T &1ڴ˱I[5ւ0(BUXe]9UuSDf04,=. H2skєLl4l$H$$Z&4L3Z' @VRe=^5 6ԉƋ7q\c P$ B}{YpXHsTA,rxuTTɇ?)Ծhbsqdl^:0Z#l -~p>AHN$UvϜc_~E8:?yw⻏@Ժo؊[0wC  try柳E4. hWEMFa ;et2E\wiUyr0o=k#6ogR0}05R0p!U$X=SrA o,[Ab*о>0+lYl/<vOC:Mj)_Y q6Zibt^GhʰYY g'e4jU)ӭh 9US!,otkCӾ[LD?M*R l},F=$Q돉˿5N, lsEJ el= 6?ٌ=W^`KȮPi$JW`*B8a a;uӻ4òXkы*Ԧ`iv|zxQʉnҲZ Jn֫Gu+mSBfnúp0! hNHlv)͟MT3[LJYUj-[)6*35 JJ Ƀ|f ųm,k~?O_|3 gwJ**y$E'Qx+u@_: sN>?yFC026M/M?6C%t/{Fۀ(h1 .{-ר>Bܱ~%Vg c&Gsk2ll3of"V3O1Vmmݼxɓ?lRf/8O,"МjO<^o4􋤀Ekq%}{˲x))b؅(KdXm+[1a3yAngx:Uk0%N VP)/+s")_=|du7/Șr&c"Aϛ 4\/ ͌@`lsԲ3P\]6RNl\ rQn/ѱ'xS1L 9#';ql͆qs2 ]up;O/!ftLqߣFgDAw0Rp֔#tFg\\g-"OJ5NtYr%]?sq_8iID823(l0鍊,di\z˒r~tw9"hPxey^1ǴQm9 w%b*|h+bh xȺzVj4/J0Z ;Ѐ& AbJa8%6 Hܞ,墭i AvH3 '{"RA(vla.ia׻}ߋ&.f|to'&hrZMLu}k9b a9̷j| [S#ޮuuk˻Q1?LtyƘ@ .c|b`?ŎumE+Ґr՗[TD3gL'"@ɶLٓ~w|*ۥR"RK݌/Z#F jDNS漤9g5>Nspv5vFjg1|;X1%*n~0?2R6cm"Dlvp,yI9>F585opK> !%DC*i/xϵʪGETR8j hWvoRZН b HӯP}zYYXS ii $$1ҪqZf}pfhL k}mρBgEAjQ-qg£@F抃'ZtEI'K:'pEg2UT_vzP[H>R;;^~ Q$T$FID;?¶/#,_^gW)mrlr/usUA7aD-%Ĕ&f2IԊHO_a7]Ƙ7,XЯ@ OLd¼]rٮkucm@f"zJi]uY31!j 78 5=ZkQ %Sx T9qgн! j{ڻU fC ֣D,ETrˁ}!S2EjU1M); ~/\ Y>Pg4e狻36WDBq؃aRe-_)7pl7/\ZBPo0b®T_Cy[&{fwZ/=^ ''d$θ@a i9T)b}>z+_|0e%p"@  k'xeOǭ1:*A5!SaiUP7=4aGV$@=B#gbFd9۬W65eY>RPc%~Pk~r\ ֞={մE'+?§ߓ}o=d֒=dڪ#ing]|8z@7KXyVao¨=ē`)-1v'"BК n+}<ҰL7h>l$mΪq:`u56aK8˵:1?.`đW#|J#/ wj=gSy 8wԡ&߃ NPM |vE}vVȴYlDo" meϞ>ƛ_ʯWiK)׼攗4pe16^:4;քHTQ5׺#;ռ+zPBR@̯7<pY@&1n!t^ qN9IEHAH,Yj-#" xмdNN@Aqh8| (֔kisLSZh~-h=|}T>/*ߧUDŭh·Ue!!~o7JAvc, m2>yPP H.l; + JD~Tl\-vMYh7#?nOeǝPfwKwx,c1/ظ4@fɖ2b\"K;<# xlC-VU{!%׮:AsZ>}{V ܵ9FojKp7FlSF첀AUUԊ. xzWu2L^9M]1 *H\M~[[Y D\u0R 逸 1>GaC|Z1#A IDATDp"~RHq3R@ L!q ,yMWQȐr_mX!  %E 3+"uj R)aO rqBXdV0Z!3_<8qq)R#L*IAuR,ZsF)Šng)R<B$"#T /;wT_j}kup|Y(P@BcS-Jw{BNż3g7󽜏88Vb JBs4`ƞ'7{Yk"E?¨{](\Z0a33m3jSLҍٮL1f[9vw |1yn7Ww~𖰬OO%1 v!p 9ߑx&WuK36ioC-؜T=+-5mľZgb4۶N֫I\]]nۓӚ[eN1oWaݞ_8@" QDsSG0<!`{-*ވi^NNO1zY 㥻w/nzQ_űU?iODMX2L+[g&(h/k/ֺU>.b#]zfOhTϒ(K2=\qP崪R)1=G*#Z^zߍ  [/+(::\<{SpzjsaIl&[9piescɱ5Ӭפ|ξ1bX')0LmwED8ZEu5MsȦh0B)٭6soD/B%@xBJj,(%:VXS.͘eDtt>N{m{i7;nWWގܡ۔^{WO鸬Ild^}$ {f݋I$z] PyV͛ffNSda"B4xgb)MDt颒-(@qx|!0vn{ƍx^z}TCu;_?UL"- qcOy͞ D! |?y^O Zj w촫eA Q4=Q@rA7j珹&xI|97Xdl$4.*ۖjo !$ġfOis,`DH癅38 `Z8 c04t `1[{YGg(DCWl9 1"UZ)[+4M[\XbgԣS/6 0~W_~W jvA'0pf]Xb²ϕ`8HUGM^{^]U;;*U!U1\vZR#lBu<%yOyȷIksB[29x~f޻ Ref !IBS°*Cd\0PI5 4PZC/ZrLU䙟0 ~Bio)`cU2U,^X L c:d{> ,,,ʩcXoVp=slfoKm?źZLZXm-*VL~wP9@T ID%٧ƂCmdB]kpVUF9qbC13=ۛng'Z!"O=S,]l-;Qi)SŒr3~SbDRj mڅ&Sd>P# YRAcPʶ4"*ayN lVg;j&*}{o޸qո2i&0 |<3+y􃷾w$1PRi1 "PiDиX1L*"ʄdg6bM)1hD4 ٶ:y7m64Ĩ==-"M#_/9JPU%dFkEvm?9*RxJ$QmupN.|.M]C,㦨e/W>ؓ8xnhƗX8WG"Ziř?9s,Z@]&R,Ƹ;T$ qC( Y"vҢB߸er*ޓOB?U#czIUac HqY $ +/*u1G6$?X曊!HdO2T( 'O<č'Uan,( ̬`\)4vӄ)a\\\l7ɸ!F 09i̷pC *f-›\]5qRHU a\]m68Vq~7?|G<^?[kU.@Z<'L~,u5s+>y^ֳQq_5le mXzi1._ Yr(s))@9w1KnSbb1ZS965}.Wa`YBypɳP='Km$uxE$ >a!<09)`<[TDj?Ӥ{‰s=MU0'xɩ+^\h{kIHWLe6g{w\:H(K{q\ !S(ZKsJ⭟,#U@v.bG6aC4(6owrurbȂ0w|<K4(Iފ%8v;˽p5yC?حP#, qe_bR1ahB<. `aiuT V<n|;G߾{gв@1s"u)Bi t-[QP+yg+vǭ#Kq4ɼJj&0_P #;lF>1j"A{>ׯHdV5YQCάYLEPUDH +*LEVX׋Թ+e"a1V 2[CxXqEav '*f@ ! y,>e!-*?Aɪ> 9+8![vL8IJ01f;|?u H,{ V;霠OaeqvWN+gJNner6n|[.qDIK"S"\\LT% j^eU&!2< P q/q[w׋~.0"E`רqm| s2n59:0eCB *P=Kcb!"Pi~4}|\fW>lĂ*Uh|Sjȼ$6gXZTG;>7n|kװxλ'''jhYJ̜GYVR&KHi]Za!k)hW!ӜfKȹ =4M, s"$-1k_(40OfsnyJ3_WGs1<*޽8{p֋\%۔8e`g_3OeW]7lga]xiQ(cQ&vtOP ]?+!JP^7vY !bVПF :]CM^7Ni̼s Ս` %;wUscy*&{cEy,NS*ȩx=Tӽqj\CCwy2#`~e˩ޔjJ96!HbefI)%IlCA ȴ9By$p6U.0pqƵ9){XTmާdsP8  rq~1@(N '9+չY2EKU @γE(%X80`@8,=Vg_|9oR7*+N&|K͞[𦳌ʱ_eCb-ͷ&g{?O kc{ԴѾC y10ݦV?nQhy BϏ3ݺD.kayVIf ,c) 2Gkyk}kE"Cqq|ݷow-3y)*Y eKI,)Hs6SJפWvAHDzV1Qa5Bh ;'zvvNM0WNB4*(e<˭]YF>x͸ nRT*R|>3_:aNthN~fGic&pWk>(.j5Tuٮyk!EsV!07V;7Pw]q'7NN,l6hi|@48 톙AEja0ؖoZbdaq x} Z57K2h3\kdNr!848BvZYYoe*˂e:D̐odaPݣZ7Y5Q,#V`Q!BY %,$RRMMDh2pM.(-EҨ(<~`˜|A-~2&<#"lxU:lv$zg 18!"p9(+?FQ@;W ⪀0Oۍ]δU$ZÄ8('V"bN,Z+!*D ó7͞-UXA-*Db$ qB$G|`;ggoXHuK :̚ZÁXsuPv>tDƪ1,.ꀦx3vljWcku=`FE/~ֺZ:g ".93;/~nJ sQkᄈ!"Xs4MӔ9ވ9CE*<\pMf{0j% 1hmg{xcsu\]ͻ]Mp~-NFln/j=6:5)(yfNR60֫fȰdᐨD5t!!:?fBܚG O X& 7͏M2@! ('2@~O+x"U v[d{H!W9 ʲua{iì|+JM+%6k' $̅Qpxމ ko*:DG>qܷ *g5R~Чy.y[p]>_TOVJ⚯NMAu9UKEPZ㵔9hi2nϺ0E;޷s mIUX-r=G/t!N\NKwШD=s|ikcAD9ǵ*pkeÓ\fDaLG"Ta_Xp1w%/ ([bZ+k}3* c Ճ_GM;V)s[W],[@k}lPw$zE"mF oo7rԮ~}1bi+K cmH5.Zj#)oK.7_u=M鬳)A{[$C 1BJry^uiyq:Y\b!DTB*|bo-qZا)M@9㈈",]a `Ne>(Ņe<+2Ή&4}bGYLgڋ#?䃣c/򪪾꫕ǘ,ىUn"x}4=jF][!'9k b.Y 7%XrNjyY@]>iba3$po \ OXQ,/!ƺwu+BKl{?]^#yĔUd^7wzpލTO<0?O~#h&֔3zTrS-`Dxko8 O!kׯ //ݻc\'jc,@vbVYP*)scQ`N 3Ȓ$Ck!VR94nwn[Ni7ov_w܆HkauBl$8^Վ* j)z7qyei&JpM 8.V -h'Zh5ĘRO{Ӻp*t]qu.-ѷZTٙB‰ haA>k7B99(4o?c F(,)Kϣ6YinUUR>wo>׮P wRUSOZ'HB;?05kj=]HU 8fD@B4rKwC&L~_FuZKa#@Q6MVVYh]ۼM2 Hf,Ku|/_LHk#SUc7im'6*JL}Ud\`+&h֫cXDQT#\hy ڸ ,)J#d uld3yXSSjtU]S@Nԫ-+([+"׫2O!D@@3(A@&7;0taN,@H/?XקNzH11Zj=ր*H8q{qf!#3)1zuuua2Ue1$0'N"HTY)bTRvUJ#ŘW{8"6{H"DD%;+`JɀO$"[cDw~KŪuD;_9˯ZBc1iuQ>~ Zb+ -7eM-+]u[QyًBIA;h[T*'.]s 0aC$ l'ΐ[F/QgIrmbHػDUqC~ʞy:jrZX:dwH$ޢ9VQf~'q1R!dENNn99ŢR!p@ q@h%Hb%U!F<kc̳! S Fz4Mi% >4m甄ӗ>ٯ=&p9-(P?s0eRnӅj+/Y3PWQuB&뼩UbZ|_Ab  kj`c3=-ɪ6 NG׵%8geW7]ۀB>W ϺKؾ3EqDb3!a>.jmmozM.O]6XD}.@ g.(=0yX l:>4؊Cb31'MsiM4Ӕ8箺m"7)5.Ͼo{mIn648)4]^\\]^4q&Q؃:'!r (;T5{B-74"]BC͵T0fGHk ! ]! Zt|Q.޹mVt*bXB_w@Rd麵MG(֍+kю瀫,uȮO+سk PYv? Oxt+{н-| P˓o=xeC3/vÜ@i2j6 aOr38ش/xuyyw/vnwut޽{ˋnCH@y2Ϡbb,4 k z[p>f)=,ffYp.l0P,#VMsׯgD1Z c=+&uQG>ABKK:KVa|s^j1+^pB!o*eء*<ᤣV_lF9qJՋz~z4E"qI:)2)*Ex7ƢyR,U{ozoߢ㋶kS/H|Ry%[z&,86{w145 |qv? }g]lyRo/ry~w?s?IRJfHpqv~yq60 CmԈ1 D=@$*!DYc3 `9bEs2aȥ!Fc$in./U#O?C!Rh!:Ǐ)Hfr9Idà_6aѪhQ}9G8j1w=^(l,6I@ @K_N).vI5qeݮ7W mE{\ܨ+{a0 [@/,(-4^Yb| DToiVq-$8 =1C%dpS,~-bŪuY?syh)ep2e~`˷EʯHAJ@E$Ϛi'e鼳+ՒT5UGl*"ie5a9%<ɟc;?$~?xW^}y_݆Ybsv͛JA{Ug"ݰ_|f*w]da_4盺C5,˜{y:< jqxi6|(>^UU^ڃ7tzĽ,4>B[3y<ӥT$lKO XZ!ƈB"imqej^pB}m&^9GT H!dD Bg?bHi" (̹61UulCjK5DLaqʹXE3'$5pzU4|dBĖɒ 5=HKT6K2hD2y@DfqJqEFY#kgMb%M3i>Y\sl ~\d|w<-_l\< ~j3%ǖ7 5*fC*;iwwZ='ˑ=ӱf2_F]l.H҈_Ø R2kzu.(b#3]`ݰY.VSE;0Dby\^yiC)U.cKr1VQY2 ?|'ڼ@) IVPMZKiZmxI2{JLHHUgS=3@a4\#6!%N7o޴gQ'c+x5yB`3|P ^gY:٤z,KS pnJIC|±5{tWUT^O?¸qZ5uLϋ-?I6;NHFKJV5 B-6Q=[hOV(,!ȉqJZJޞDZ>@Z^TO߈C09wU`̔VJqj#˜4i&*HV쁦 =(xjyT59ڵk'׮ AQyx;oijU^[zE}J YP E +9S-Zwwi@yuRCm86[M<0-lX?kQp<Mr5T5A^sku^SفS H]\֚MZZlRCl)Z7 Ml$j65KHj֐xX(2&+>>`f /EltO#}(]_9OtYt<$tJ 3svSpDvQUC"9 AY=5E9'%Rca9VRbyzH,J!<*o0vn1D{9NW1h!G3kǞx;EbMթBa^J@^z`AK t.P^LJ6Yڴx Ju֖"RYT^Lç6E+ֈIsh Q/]z<{w[rE \S7kSef!"'A"9~օuIj<*A u M!4&KmQIqakA3 ~s'w<EIN^s]F/-- AٙEa]zL1P.T]PY80kB@5쮕^XmzBX t;v*Rk}W U7-l^ꂞ׼/t IDATn>Щ4P+p zd;g9mxΚ- M.Iv%oT I!v@I* {CG;\CPSĪfH: T3Zb_`i7 qUcHsE+8?w[3L:"@58]`kZV+5iٶ|}jҪ*#a7#?+ph0Ȝ{~P6?FJĮ8-ٖuNŦ^্{~[؅.P{f[.VQ#S1iU.; zQD,B]s)ձh2" ۷͈*F,p*`ťZ[isNJU9Bk&qb_A„j=_x`& 4aa hEf#:9֤ Dq TSaA.B Ua>S)͐y7)a\f!"7^"z*)PҹTdYoboLV]i4#Zea8>*/T;㐹y6*#3p VX=*نO03_9@5'gV ^6.K/{T: j5f*Bk)$NCU̅S̳e~Nj}B@ ngk#Ŭŷ5DzWop of98ڷ">w%vq3 ٭"T2,͚ :6ϩ 8νP-vYnVUڃc شbI߭B,vh]׬3F88紨Rx Zo> `AI/RomŶz} w6mhMxJR#b 696Tˈ gr \Y,'d|`[6U) ""$e,@9BXD<(3 " ZC6o+8| |[/Z7%iY ΐk_4Č\]SIOlEU`yfv9s9RD6*,O,'heu Ы\lNgiU@$w ఓ`;M!\80Kؙ)bY\fNiRVDTŜɈRXH$ E ƗaN)q8 q$}>k±"[;{!.B~:%-Kף'Ԑ-R8)"J!5T-Fq9[C$ˮ0GJΣͅf,T +FV;o:@l̰wu/޷ȡ_pCU vDg>!yN cC~6D2>iHъ(,cU4jyY)"B","9sD!qՄك8S:xB&Ħ6֪E,z]{["!|@|&CjpDWB$|ߚ~F-.5@ 0@<@Z(z B4MUl-@2isFα5j5B0|b.%X[{P빛L3[B=3,9D7?uXOqΝ۷nI?oD@fub.4ǡ>#o/G|ca 2@D:Tףz7F,-fܙ_ + ~ ̀_pRkYUgN8KڮBӉ>tA{T;#Qd͕Qc,*c Ky)`+%6ADPTD$%2E^X Eŷ_b'4XlYnS ! CC>kڇc8VӍ^߹}֋`9(^50|'_:%{Bʉ:>Yt)@RJ {5\`,}DЎcؒA=}ߨbAi1>hCN^+>p ?\&v,+5 ,-By6Zrsn7sJjJK*WfWJbIuEmvSJS@a1rJ;=x{Qp <]4- t孪 VRbλ[?~bQ'( Q0vTlD$jif@H^U$E0hy* ԩfVRPXiY<!A3ep/}Xpc;տ;Hʫ!ؾ:M{].^MT]s[~v(@Ȣ0#t?W#4Ғ4nzMXRJy"0 *@1r}E&fG啚ݧ=9W^O*usu>!đs2P`?k;_0qȹfυN5B&jp٥j d|r>뿾ϦN_+8?"?ilQq?[K`Sԁ{+gGUCa@xoVd -?٢#"PPHDlC2,"ɦiP SYs6u ; `Gr%hߢWMe_x+]H!43FD(7Ԩn sC>@)k֋v>с!*tuyC !H@7솏貋{su.ƽwn0q*F*bu[XH*^ #sUtO[ADu*sa^㸲%B0 >w<ǏRnbvT33Hl°CCjoc v^WY]y]BAXу>[L;A^+.|vLǙ5q>]s^kYeO}qA<ذY[eDžW@SO?# feiIn-vmNB$vc* ;g ꬷ./7+ ZNNNDaǔE*8~3C[?q<~Wz }W2*5:"WmunHh Zts:{U}X~K;c(B &mII LOw͜K^A @! =gUuϡ,R8~ĞsuժqT0YBsL"!ƉR S&+ AިҶHXэpڹHlclk ƺ:B.~E) ѷ;JݏR2M{MyGYHgu%gEDKgggipwیDf! aX}w 3ݽwߕ+,AXHF N]ȹ(UqQڋBsjneBB['=֥} ݡ.J+qWDԙw')GiOn+M2*?v5/Q tefU u7)Rk5^B)nF Sh("c[3- D<4jZ14E.Ƿ_{E?ӓ}w8(Cyr\֝ڑI5{p!(n$l7hK%L|_v̓,R_[sɾUk{[m:pt߈dcՒKu ^P5vӦ!~6Ut#Yn$sM]qsݘm5? C|w|;NbiRK1W7f-9MiRGնbMZ6N!ЬYmV5d"t޶ }B:jѶx2of{l6ܴMb lKL0Sz!FzkLqq: j̳m8XЦsJ{3z}y~~&"Ad#rfv# !D9||-ЃӓÃ}hZEzvIcyb%N/?~%ױsJq mwtӍOJ1)dD6f۪c73"͖.Ƕ7[C||wݝn3iEkTFv9DF,"!:ֈSGJ̺T) DĚ ]ਪeGfl ƕ_?TVod368e6kI}Vs5rԜ!Ʈ?`ZX(F%3G;h}b墥,RDVri$ԘEBxIsB뿳[1i5n\FI\˰hվb:=jʔ9H/ֻiFUWR6IMoԝ5ve;mfW[,@8d6{7̕k˭1^7|kfξ.˰$f7iL&}J9Y/ l&ۀu~-֤| hLWj!XqV˭uf>ztw)݄'{7oiҒ޽!GɅziz3xXȔEy;d_rhQ4iQۓ]mh|~mDjfP >KWJ ruw5I;B=HvQZ̶ulITCȾ)Bur\ŧD)5Y;661\-iØVW/1eyEc"ѾNnxլ?4D쭞ښ`,Ը$ʹUO3yxv c âJTbƭiyw718?Zjeiag~?xBPߝ۷OniT3M]wZuќ}ٙs|in\Y̡ffiaYv)V53}*'Ж&;$oڴ^dwF>ٽs6꺱n\Hwl3DBmI1CܔgR>{0 NvGeRƬToISBXbqR5=0 ggOArH+SȻaskZ39gPyX,aÐ\s pZnReeBȆ*ٖ^`N掄aۼXtL}ٯNSC᫋wu:Ǧ[ C;;\Y !`f1ɼ\H5dZڧq|"s|՘ʅaX҂{+kfk~^f;4W*yn"!'[Ad)_t;"4w2:RnVs"Wg76¶_/Z 8OjD$ATCX'*e mYp:)LcyEn^}X->:yrVNކ"v4j# dHkS"돬iWz;I,Zd笤y/)mc~]vEy -l1FS-;C9L,̃i]iHGF(Ƞ m㝝b4i)9Rwӿ[75#!\3JU6a۱bo@s3O(͟ӶaRFox1n#!Ut5W-fnU]FsTfO|B>c^fXrA33ս`5#Q0YTX8 iLw 'E]L3;;=]GfZ.W;;IN*nۯfC޻gf?Z2m]]K~]̐I[zzda°\.۳mCv3eP9$B4yzSv ˥0w]-ۿT)TR\ٸ @*蚧) ަ3 i%ZbYhVtJe M&+IȘZ@aV,YS7 mIGn~ I^l-tUTkOj E 1F*|EDX8YTR# +2MDU8Q1Ԉh6cT&QdRZXi 8xDĢd8S[ ðx? (wpz|tZmu6ҎcK}6;S.c42j>}CW]"lx>_[.&{sZ9uIYO%J hcBkцK"Nv3r*m'6#ը\wGXdl5aHߕ48ce8 шq\3b!bIbu(O>IoQ*e 2µ1mK 6I*/9Y0~)gKd{^Kjr^^ڼlj>+l[7,za#;hf^qqb \뒉 ՛B3!𣴬)цDBijX,%jT5(,ǧ%o[ ~VK7|ij'K0ь$va"&5 2hʔJZ՜ BfĒ, !I4NFݽb ,YT !_.~S|CD0dGoJ-Sf4%S#EH8u#P yUiW+V;;bd4U?l "z;uq4:!!0K -g.eUuFuD^NAP5NO[ Eh7]f[k΄\Z۬ugh֯Qk10Z,P#fR#*C΍28M8]^^ 'P-QYjtfwg-G̷#O\g(ܹw%!^ 4"۬uy>_%6c4XּڔPZ5Fa?F PQuz:4N g'R$H$3#aFV L!b3MSb\a\_^:9|T=2R:]9XJbmn2vu";>SNFG__Xw6SRW].$}F})K}qa1,˥iګH0 A"aV#g)WpͮW&DUgsEuNh^앫[8lʟO.~pj:'DiL%KqBX 0 l/ά%cתb+Iբ&C3P6̢].ͮKKFYo"j>l=⑗ͮKi>?W-MmB'hbB$ RFE=tC'j48fðbUc=.חo~x0 ~XW;0H{3n'te/es ꭆuW[R](Q i%='Ѷ5d42-r|D-zӺ'gȋ% s7mqtL;Y4FSqjaXjTMm˛/1gQ>=rC#1IDT?kZ@".̯kF|_F}Fs۝*Q_1m# &W>6Ř:E"F}bEԴ81qGA|4G_[~)+G,]\YoSo_7a%֗<٦;Q[Q wxWWgY-H0H]|٬>kϺ"uoglmWNGdwz3&l6Z,(e[=K.OUUia\0sHLB''=|{3ʞifk-Ssfd˵ k.6K "oybک*@[ejWm?#*l5?Q3ϋTTIc/5՜ ueŰ  Cb-u;X(点lΥNAnnVλ r#"V8O9n S&k$gIf2c)X.O4MsS8l'[aIri]KSε[xK(fR3r) "A:RnC>O ɳ1խu^Tȧsr~,&H`agFShmp1'f f> 0 leˡ fW*R sr&<bFD>CܵA^kj8jd%>HaNw\m3ʳ_wb9,Dbb1F3! qS3<3,K;3f>zss>O`@XLJ|ChQ>;L ZMkHuWk21uy!ۂ6+dj`6Fl6B t[d>fU?E5hI?IaRSi=3.LhaX. ,!ƩE>ş"( ׿\{kQ ޳Z^"g^7YX\Z#0%J_y'R;46emb>C-m g-ӭ@F]YIU}&:ϳKs~j"/W4Zf麽5݈ԇg,P׶BغnScT F8wW;Պ%ն?,'''y7d u۪EUyDjQK%ʢeTɪHϜ;\7K?/]kN-YTٯ}k5Mj"!{JS$.usBǏrmvͫF-/+BU@ndzYK4NZD$ɒR$ʦ3s?=g`K{4$xfBVXDÂ%d >%xk;zso+fEEl&.+'sj"$(튪+ %y2%[}6r*7%= UMG3iTNPҊ\e0>'sprލ1F3r6fXf*ri0Q iTNBk5҃WҲeUe& !=}IR"b)c .*![Ÿ 6_hT<|یmJKʃ,/TW:fg-0NkqV$d|'L0""=$tr THD)$Y%! !4˰ (|_VcaӬn$)M͜g-[RϋM5Vm_oL\. ' uɹDiv6˚4jfj19S]li< NOO {_v~Q'7oZ[TVY?bʠhRE՘۩_ukG4!45=F_'-M)Bj*HVਗ਼j7R,뫫_Cx" Q5v9 ԥQI!u9u"y\1[XZQ)JemDԗpY㚤ꖂB|?k̜|J11Tm'%ꋘ@uF55gbVm/t#DLZ(tG??Ԥܶ^ wJWV_!!P,f$Mػ<%V!4 ,(zcDo.P>ئGxY庯¤ {2+Qu)0ϧX6mL Sug"7Sxg9A,cc$*I!ӓ>'.;~1FӺWiР ֪a˖n݉`~{Iew|P(|fSUkE5P3BkM6ffʼng4J`L:,%c1շ^}/?P> }㽛7cQgKΙ?cׅ5 k^̐ݽKtOd+>2KiGGG8ރ?͛q%!y97i ~%݊6Zx`WBgCbyxWŃMvȽÏK5F72^j*im-f,alBuZo7?5u>FLMK`'ϪzCIfI qIJhLQFnE-.&wկ~=}Zz<1EA|sf-J0>~(r9ffNi&vչJ902Y rWn\̢ \\NGw=dKV ZgWeIN"2_ZMU-!j!>ʾD0xZ}xpɏo~amF5;cR!%AZTXLq,_ d8=yi'ލ1FcǭIrʃ65+N0Xp'SK-D֫/yP>뿓Gno\g\ie1Ɉ81z\{SP>bfNGF>ic]1όLm'a4w:^m(d5<ܽwÇo ;|P>@(|P>@(|P>@((|P>@(|P>@(|P>@((|P>@(|P>If IENDB`PKFG3v-Pictures/10000000000000FF0000005684F65827.pngPNG  IHDRV7ŰsRGBgAMA a cHRMz&u0`:pQ<PLTE  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~ ) pHYs!!VytEXtSoftwarePaint.NET v3.36% IDATx^͜{ TLy?dRd<6"M4UUv",' ~WwZ^ Rxmx1%zG6C-hwx Ewf'蔋}@_~^)Uw?CbaϺb4zF >>s ϺS69AGKH ͼzP PíYH~; yi;ogCQO/XH \5 ݃$ {o/d@ww?wS*!_?t g/K9o8--jq)>(~.@Kes{Psa>J:i9yZb .0cK/0 =Nܻ *`AYWg} Idۿ"k<}~wPqqwH>P F x{ct%pDm(ޒ 0qu;X?PHDg/w'௎2ڞi(wj0I$ri X9!3̄?q 3>:@'}8cG0Te{`S[d wǝ&%XؔRV;Sgٙd@i0Ji@L'Z JNJ](#tM?#)qgh>R/T_OD]3tDo Gp:s^9 T?g1(_bCtXSO=ױc?Q!-u!\#ggS0`IUFĢL)qEGM41vJDR󧰔C^$k _?>AZU2+ELz(@9>m~ O۷c4t1kT-X~pR?^.ok`" JӢER`҈Q&`]/L+z9xah +lY`3z+ h dSr1r=|#@Vi[?bKc/{󼅨ʟ9+;z+[BB ٕ33Ghץa.1]?y§GG@$"5d@@^Sy CsDzJL\k[>f \"Y\&o{ Iv՚G!CWɩ!j4 +~@$j9,"E 6O|^7:J1ɬ(Ff‡I/ŸJcUa{, R@+c_BG_z K/]+hEhnh?Zp"z3,3$CeNu:mFI2\r&Cx.Ӣٿ"]4_]+ +h)+^@&D%PmبKoRHJm){[_L=W,?Ĩ@ z]*1;z D;? ~ xqO"!?oBhjB!ZoTT(!e(~<{ Nq]V*SjH:)`,Tl({HI |ϊUZ$ TP(ӵoE;lR(ˍt|OEB " Hd#Q2*0>LJ=#q ٔ#!E?x7'h'yT6tQn\ބԞ|ٺ7N MM eFWl؎\=i#=70@sX-тjDnϗ2S%7Hxc4T V_k fQXr4|#p2dfJ`"?R #p3G`q { V$)L[x ˏO\ez^v]|KT@/K&9ۉ?^$G®Z7 "AwEX0i]̐ŏ{7 9pă\;{0 ,?"sm0`$΍t%`k$rHk5?0FuA֯P,kH [9*H&{h-uUX\b]ŭ+4=)|Kҍ^+RJ]]oV9#vC)Ɍ 5~rFOx!MT{[C(1yz'Xm+PY7Kߔ^nd݊ Oz~Ap7{#0 ?~VY{DZH{n't_?Vf'1݃$羼{.}k>oŞ2ch>?֕\pD$EPo獜x[O^J %;Z_XZAi Uw6;׋=B!d\7iUUbu\̍vkKN;\ё)EEfsuD7rGGQ[|W=ޛ+~4EL tvBV+jj$ss?ɿ"-Ė]IENDB`PKFnYY-Pictures/10000000000001F5000001F553F9B12C.pngPNG  IHDR`*F pHYs  tIME $r IDATxyeu޷}ι}Sݯg iQ,[d@ YE2)Jh2br4[DAҊUDI#+QC@7;ZcsSJ,K~U$nw߻[k} q7o888㸾;88888㸾;88888㸾;8888㸾;㸾;8888㸾;88888㸾;88888㸾;88888㸾;8888㸾;㸾;8888㸾;㸾;8888㸾;88888㸾;88888㸾;88888㸾;8888㸾;㸾;8888㸾;88888㸾;88888㸾;88888㸾;88888㸾;8888㸾;㸾;8888㸾;88888-p#O?GL돪p p~a3YHHHLɸQSDHWz'Ddc~Owq#?"! *$$jf{`sD3i>#"6DB}|[8ΟsI P&`\33S]޿l*,03`fHLLLH'odHt:N~;8(?'?ӿ_̲l&:ⓧ4_xf${w۝, !#R`B$D7FU0415%7[o?yc'NzܿGO|b DUE厷QnnKW sϞFLA7"/ZKH``&jf H)J%Ywq,<أ<! 0# 懷~be5֮\nlijzN!\ZY|ҕW~ѧkzS`"ȁpL}Gmo>DEU TM$9;\JOS vwyo.ZIR "1s([7L=etSH~x[ZL*PSY:b$3P34#/S ~K+xAoc<&dkdf']{w-:"ieVr`&`(ܸ 0hi.C jjbFj#D!44d.|a٧ֺp~73s siD@ DͤnɴLFL2|]nӻ;됟_G("jI9LKକwZő;c,cY "8υ5ib-fP7"ȘT DHQUEEzIN]U`DfFD/^xqʥ+׮^fH)f*YU.E9U1(4LD!@̌UcFā'O'yy ^e5P3],iU7^_$!PC,#`V51nf*əO 2@=tj`kI-R%t0̨ @ QDD "<Lc9pn fƤURi'^[[-Ɋ鴻VQ0TL2Qg(Cȋv1X@ ̄H'XEY,QRmNbRs q`f)2 X Q@3gB-5ݧVzS=p`\Fb DLĜzoJړ6Y%*BFDǏPO(]A 2E(P Z@iD$Nx\dyIBJ)@Zw BfiYMq2ryscڌQSoݻUET E  +!@gb|kĦ0GZ=(c{< Z `:@F(BLeD!$%JPQ˲L aK dDVbrՔ0PBETE jg']@UU#Nօ\efb/ʅ.M?zo9q|Zwsê*cY(ʐ(??o t}wxS~z,ֆ5 m)PM U-N'tf/=xc#M6Ed `~x{,vFu4 y(,$Vg:[/%]kRu~"!-XUEY@$U(bjHLĵSB]LuQ p~P s˦5ݦlj`""Bn)Tӹ($(lZ(yA@ "(jTH s$T#VUugg׾?z˲|_}G1BUU1VZw`NxS)w}wQs6Ol\i$(q:QOfk]G=?v~2:|P S(-hO_=R#kXTE{VUMKDl5"IEEbH82eT4U$#&/iɶ qsLD,*KaF"TȨFĢhBWQ&ZtNsOKˡ@MsRF*eɹ[zλyc>u:&%AWFn"PLD23)8-GW.^\HZt0w衃40Qj}f'qU@U+/`v{Ynn?7wphĤJ֘<81M!$+-kjLZ❞`" A:5bz)^ͫHPURVVUլk㭱Hvݥa1׿҄,sbYV@gYȇ-Ͽ3ӟPHi64)T%\zWS{[޳w S1Ji4"I #$c@P"S}(:FшjDbVʫ]Ej́쩺, V1Q`Z%0Y?Q`L)p͞ ԙh¦DžڍUgONJo(D+K)#Z? jAmמ}\`NgTۏKv!+Jb![ēƼ?~c=&iZ4"UX76\mlr+UU0shϡG滃N%bQguv>cB܍S J_A4RUU$ W.\|u}uSCǶ1t{] QUQ$T+0-7JvF!~~asEK\hVVh8{xynR+:;m^DubDuzTMI!@D $2C3h f: ! "@%g\M!2CQDEwn+уv LiiW4I(bf H l`3?`fBloGiT$&i Tj! A` uxk5ʳcvјTW$0ؔ?DBFDr2_[ݳgwgn8*J%Q(y@bML 7͂&1ǡ@F~#k DDE$Fd I;MZAR5=NOsShAz 7Rubj{۝vHʪWuCQo@1S{Tt<.p[N1Ft[:HeYUUxqՋqwv)`nn}_~!Ф"o}˛v.N> Etۛ?8'_p߿V"Q%6ET 2B:L*+)K\"dYEDRdi3c `&(,0E D b^I\ -&% ƞ#DUs]fJB@3ƻ!HƀUXY%ʙOV1>cr#LU[`axvHc)Q된zb0,A%D<χ Y9SWH$i[ֵ͍KWޠN+/= Y^ޗ+<{\!yWi(P#j>z~C:3_~T1HSz1 XL+eq}VvlY5&! ! 6QRj:F,/VYHsΈβV}[D4$kfxqO0tۯw\d& pӷc"{cq!uR;4TMgA$~tRVϞSӺD@u[U5osŅm;E*J%RƘYX՗eFfNn,0\Ph3! #uyF׮^vttB^Sim?|{/c?__[oZ[1"1g!1,dyZ!o^wħ?{=I^iUԬd1KvݍGԔ)"dydba=c b5NFssŅnJ 1pd4mZgLgD 4hi Rkf4d LEQ^z…>uy. 7Aps+Ҷ%BYUHdsE*,0gYfYڭv^dy@*ۭm_ƈPu:γիW_NDnhwznȂ!EQKW^ml.m[Z9_rn<ߎssEM!JLY,gq^|{>}g$F@@g,9)RUB̦"d1(˯]_Ow:V]ی4-˪,ѐBϊIdCɚiRB@`y(9gyw8ɲ&#&nbRA=^TJSVMMN1Rg@=:f}M(u2`#V1+^xڕ+kג7Yevl+lV1JeZVHd*E9yfҶg5n7V6_kڝ~7/!"B8y΁VWbZv;k& c*eʅWοD̼mo:{ǎͭd2Yr5ʩ!Uz-) 'H=&@D`(Znu(f65c|5!2 )(ަɜDbuSIzIv D%1VCiO>skoZ^_,.bw+m)dv;nkP?1@Ȳ"/6ǣ;EKW.]'[^)0 rXUF ! /13 :cG+P5CNYAa4U(siUѴT5̲Ԡ(-ӆB\P00M" Jf%NKbpnT73q0B͢%}fN)]̄@4k$Ԗ)5 1GQd3M`j34Y+'xy^O['cJ>F㍵huΞ;qRvڝU zsD 5qa b`AQڙ>;,3 tݿ Du$ TU\{\/]?ՙqFTKd$ Ve9)m˛['o=cReQТrfU* I܌0S,cUW_YFi:EB,Q D!0331b2X1PhyhY1k~G^.b&1FUBfS;5i]ڷtFVo#D2,("aU 'U|7b&mus Mt4icV^(X%2/(dLYD(Eр ӹ&Y^*A*[BT%fXUE?:q1wK3)=6ir TNer+/́93;rx[o?Qj,MDJ P0!1PVRb)*ʫ+4, y94W:xLԄ'Lgh9Ph LD9&(M:WIk(`O˦2ZuTa}$a @.DЌ3d,jj%VzɳU4̀W0Jj;v8qۭ{w: diNNɎο qmw`{>Y*9p`Q,K8Ƶ;wp/_J1߾cy20z+,Yc'~Cq}w~OO h q"մ_ss[ܾmXJtA3&Bda~j6fx} &d2}xYyrT#0 -{[n0O!`[ڭۖ;qݬX;꤁iͫ5U^M, Y%Ofc43"&J Rk)2 S|G4(Qb%"O=̬wfE;ߵ󃹹 * C(^o#ϳ kƠU\w~0?K/Fqb2NF^C3"B fϝ;w-KK~w8WQ0d!dr1t<`vW66S#/-V&eJ5IV^yj)1oh8[WnS6D7 `:N4"1geY`LfQjQЄջ@<ϲ(ځ( 2 D"QR& D3lvQZ)\&0KO)AxTm~J˹4MַdBYr+^~:"Tz_y]Ͻ1ܳ~ ZZZ(Aj5Ø,뉖r|YMꅕto{ׯ6zǛNw,0P (#?~8Q7W~T s"@H)lbYbU^|/yϛOwX1 C5A,m ̤^uﺥ@@3zjZs" l 1*sa3MӪ§84H=Qr#`m&E)EDD5hUMc&Evd^YݑIL*U4άwWgnij'dL^:i&dsk1VRc zNV\V@r6FiU@nd[Ii3`.0 I4[f7u q1[,K y=w|}GM&G"d$dBׯ_[й4kǖ?~ I;iSqUŧO=YUgH4O=o.ؘS?YZrjK=-PIֈD1J4o[EP;" kBwض0?ړVc}Nu@)eڗX$RQeZUPEibUWL5IȜ4U*5 !(S}鱥y Po*u3TcLwNtZv=/1jض}s4^OƲcN[v^J%# Zǐzm5K?o)()(6kaZ.틋[ѕKW9L'=X@ >H٧>r趥7KO>/O}C; 86SK%j}uט1tXUТJJ0oL(I4(HxKl 5i@D9ldڛ6+1 sH;+&d|ڴ)ϳDut_NzS&L9`1Vt2.mku:Yؓ Տ1})XDT .C) Mjs8b$0&j"Ҥ;BZ_]9ؙKǨD9zG\|Ur1Nrp8"0Ȩ~cT-E76!FrڴVW׸ڿϘ cfl?Oq|W~hkkٳ|w|ǟ>ة~c$& Ą9iYf2-irs/VU cGXVFiZNz4~j)gzY'K3ag""MLO͢U9Vض4d4.UYNLuZ}Vu{DJf8Z[sV @585{4 6QP@L6M[ PGcTRT4ϼqRvaW'v#U(@HB&&+U2H5͹9fo6ca~\EL=}^{;GuL IT40>&Van%hb *SUn4ܮM{|cdJBYU5ysV$J,|&@;qY8WJ` 3nߒ$Pl͓*Pyʪ,`"ZĎʊhĔt sO߿ gxt[WH8b;^S +$"ʗ7!>x,rB&bi'~W_~ƘΠ *Q0?$@TLۯ[{_B׶lL]?益<ͱ;H|ɽ_g`2z򱫓H5 S:p^"Js0&RPSNb9H:^lCY!0 w^}1@UQ *)FdWڃA9>>ΧbH!ZdyԄD>ԇ(*EIT"jĪ8mV TƔ5Ɣt>H1Qˬ /CEDaCh:Mri.|> À o{˦iNp63??wD ۹iblr:ZGĈg˪ʸ F"M"RR,DC9%K+2@T)3@zM Q KR"I UhUYBL(BB$⢰G{JUg}w23)h?jByvwh "if.JY]iOB6̘/(` B&`5* %*-%VKCn|~j"όZSszv\3<=Vw_#)UU d(?ta ( i2 EQCs`4<O&W_|vR}c924i$J~淼[.Ź8 S1=o?g}ڻrT!p_N11H8V/O_xX]6 \:cփAURv1fK!JJU[gS+1g'^^7eUcr IFހ|:O94oO>_s2ƋYUSj:You{zڶ1gO3*)%HDyoi)Ir%Iv^.Gq@#cNE"$hd(:$QTch%"O!  ^R~s~}&9$4;W#? 0d_RC g'wﭖklN&}jo2I($e1Fv>m[c3QDc\ Y(!A`!!fJVrߝ1HEa I0 #d‹ĐQATT! B`=k':YIEDHEU\.镣Nr\Y8BG)ސGDD"$6YUf>͖eQV9O? ȠEZgWiu?Ύ\>z\uX/|~ow$o7ֿYo7gyjrYoC $ *,g>ger" (2hX 㧞+\y;:;;_-VDDDݹ{lLh0FB+wѿ_yއ3$ۿ?"D5B4/ݺ}VO=cY! ֕1nxb%ƌg+ Ꮉ̆\i& kþn3[vAU ' !Ylu ( ƘBLч!/6) *M LHH rnƞ. ٴI% i3*w$8eoɘV{?ˍoZ!21{Ǟa{C6ƹ1SAbJ!t f~v67$(6''ޭT5kR<Ҡj߸F㏽ϿSz^Xm"`/z=,S @4!bKӇ'^%&E|>#)Hap7?'o/KbB›7tëWAEZgM1n7}_S^ .Ο?WW[}?9"&R$!,ކM6]r'9Ic:W) e")SHwRQYb<%(>D>QI(3LfJ$4˻>d$UAEэd%b"L;W+`6 @~=E߸ovdaDoX4M;V')ΖE>()Dt]Ld\sV!&7˕ԃǞy+!lWm]8g;fU!Ĕ(1J*M_xL)$4Z9>+P] d=GlY|Qнx<~Kckfs1XެV2M&CG֭/]k.߯}eD??xcW?c?}RcM>Hlcѷ$x}*\&^Ng֘LrX(Kg މ ذqcc0Eַ>u)xglaS8&96#3"C$ވ 1tgJv\)F]=X{1BNˎ|!V BI@5p||t,CrŰ"v\>/lB¹qU]>:!\'Nǯ_9cXTd~ BR5΍+WWGlYU~~\&r "2D9vVә$Ua5tV퍉!aͶ 4l4 6cQ$p>Wn޺7{.{_WU}7nG> ?sHTcR6 iڶ;>scz">g_HΙ GD̴@H) (AES3S:2SVaD>R!DX8 ҅v 3a0[k+얯RUzOPA5%ӆpzn!gf)"uf?t|tڱe>l 2Ճ(f\tMbUf%Pih0Ht'UU.כ+ý=[:ݪTS Y (f'󥃃|OtΧ/< a`CE*6xTaW|)e] f3O;wmobqjZ]9OWM{󎷽_h^z}?ʌ>ymtч@;?tZr_@LIwEa1hȨvv5}(,Zf|$cl~^/V+IZUiBu1"f{?qG鏜 *Ĕ™ &CD;?g832ki4&Cf Q5 v5_1ģ4CmΓ94)q@*gRW~DD2GW/e阧!_M1g4d UUu++r4dc@ID,0*[{à E45l"g޹uUm|Q8׮֣}!˯mabw^z_sΝ~9;_X~>6 އv۵M⵽78fBbH!<>)wwimcȊ &.wO0=U@M1fXmibd >)g2f0DUT2$ X@Kqn1ƠCۥ 7 ((9>*}]Ό'HU3FgA.0#lrQ+LJ{ɨjеAUl9H1I]v%CJdH:9|Ϲkb{$1 QA 3rQ 8M>|8-p_-W,'-WFѐse ƏI!`!bح;pt֭ |mVYk]HRv^Lg*(uk7_P2S~Ӽvksݹ7?[IB6=]nM)EuOOUclfl6zVQc1&bT acsB%hB1vA5eW8b+1@a,\zݵhJh)*澖 2˙[C;󏔳A&zIBk|lE\>:>TbqKM#1gq9SQl;EJc8mV[++iO<<|:11rj)q$G2G/}W.^=-Q|qh2rBL*>j1} jER H ٮGahNǃ= _oESNrP_x]g)c]@am s׉=zE}8_]~-)Ɣ|,}L>6OTf2l0}7*bDphي-]UUdLD]V/t&tzXk4e:s M;>:BkۮSIUK)Vg'bVUD6 G7_y%g-NKC~I ~] AYWkK!&U^ϗhh]l_ I]sb ijv 1kDt=_OϟyiA9v^z/ĨDm} cʪ"̴wR*$&Fy`ci#A^YW/?SwLTl< CHdz#n@H,كk X:kS&{n%!nbXmYΠ87 g|(~KE~qί/ƿ?qoo%` vmpT`cRdMR!%2 ~l}"c>bCEcLO~Ώ Ex#ln짵3d >-Km7o.&I H(,sԃwOĿ't;Cv} @N([I%IR"%Oh('q;g[Z.#ӕwܡ>. Q]]4 1}p>{?q#ʬ 1eѶVpmJ!ԓ~ +H42U]>s4[.><;ۮ>lĤ!1IFF*Tհ%mZLf:?=_JMΦzP9[:UE]9h4 ^plV!ィkg?c;-HDjYo\ÃkжbmcLDvA|L>B=hI"%'^HRR!%Fga鉱=9O&MS)A9XRvۅS0,m mE}﬒vx'V6/mSJ]w1B5H,HY:*>w0EfCu9łm_Ȁe"dcv1O?G"RwpY1y繨2q״]' d=]5U]㈙6M;63;cBL2(FnR% PMVLP{?veqrrϾl³t""1j"SJ{#UEM]`8?>!V|j` 96rzXcQԵ["&c2 #B~0߹\S >y>b+'MZm6mp0[>'o")$9<j?p/fl~xtPGIq1_/fg*LQpn\LY1b[lnY.WE]~ィ|G[̼޶Ql3f` IDATKCI FG3rycnUQl0ژ!t!ef&%FYk!;\}_FN?׻uRTLo k,5Bj AMlO]%dOS \lU$tABƸpZ"M)%@ }jFf H+%~?`b@H)%UEP${ tZ/0Fj|mB胲y'l9&x_V3'M*LHb\ojH@A4ƨ TV'|4>|2!MFr^o;wlY֬b&bD" mShXR|Ym;>8G]̛l@U+lQX˰^7/\a Km$dEA֦;T&cz>-o}E}8_??= `WVUÔ-AEBIzR?\QUXYfPI)cJ1:De"LWu.w8Gt"f..)ٌHrr6& )v) k]YƣrPoR aC{p$&$,UEQƚJj$B^b݋4119.Uwc%eXiFzzz޴-Ds׏'|NÕKwnd#-Wãâ*&o: X IQsB(ҙ(DWֹO'$UMnKȊD1UDє,BG{{ˇ+l>Y.I| 1^zI41wlZ;Bi{`Dgb1]Ha`*JWu!Z[آTi6@Ρa`bci}Ө꠰o}E}8_UЇUwnɼG*"`uPc#jCJt(* jr3MC:WccN^l5MQ$)dj1rc9E*FdBfbDI8rM}po82TxRmw]ٴͶi;#u`s+ʂq7G E J!w616쒼r+:cBn$4%C˗ q5!%% !`htz61ܿx~6{oFw_}lYc ZC"}핛W,q"&awɩ@)PAljOӇw̳hP$Ubr3i6VRڿth]$$ggt=.xPu]UYYR ̎]tƺMhϮbÔ o$:<8ػtǟ/"\ܺ}##v<ٹ UUPoR뻦k;y@ȻĢĭJEQu]@L TH)+KgI ծI Y&!`# 2W)2!+`JJ )wDL![p@Ƙu:CPE ѺEZU&]@FrJRLPȰ`KXΓ<߉,E7BZΦR2ë|Jm ?)!Lƛv>1,PͶ-ДE&0*S3HTGȔ2)gdHkSIS`dAi8޺7 ]>::9;l=h6=q|tZD%$>'Zftjm۞Ζb0uΕeQ b jZ<-FC.'pco712> Wz4*oF~q__\A/ѠΡ2sej4m?JEbEE{3f^1}}& F$`2_1cn5[dz" !Y"KdPRD& T _7 aO+᢮ɰ A}l.t!1$E y{c$UB4 *Q&$FTb9_B*{K{M@n*aJ}ē7N?lv8l> 2*ݠb29CА!"6LJ.3H$qvbՄS2,ȄdrӟWNOsv;qraa1yTȤPTn٢(ܠ,¹XgNOBLl |Ykl-柙sq^`oppXF(eNAqr~g7%>Ӈ~}{XJmXkCc2ISN]$Aec-Te=kAK1ID ab`j/r-T(c5)A 7KeGl .J7&V)q'9U16l7HZs7\!ŢHIR?O7u )Px0v>BX7gϷMs\qOlؓ{BN}׿l{A,2A /.\YΨs}aYDhؾ} DLy#Ae\էNf;jspҕW1|VTpH)>vږE1pXו8=?!a q\LQgl Br<<#kʲ];;;MS^vڕ+%;̧~r;_5ڵ+lWT`y}DnZVh =ܾ̳OFT6%QI "fTeJ˼ͷ;!xʦlڭ+zDON.KXV/><1祃$Y-:T%m-Wvikg+J 'h-]j 9̫(1[-([@QQ4MUR1URRMIِESƃӓ,D"8C=|̱9ۻzʳ>UeUx<4֠"ı dlav9gUARj31I)ED 7f "Q^!j6E@g3LǥQAl@y v I3N@1s3e$IvL$?.j(*̶(J2; !FsY5#!$@ "1(j*B(uY^j"ItXNէOѦͧ]m.ڦ-b4 Q}RRԜd-BJIAE H-*ȼEL (x7ҫW.!h4_6m@g/>󴠆j"NctƑ{'uSŠ*a=Tl>7$z;:k4QH<. @+ʽ^QW>_paˢ@|tx|x1_mg?~h0 (ɳCv'}!9s3U}w\1LIRc<{RuԞ+͉VJvں r#t#DI,311d{΂*A?|h~yV{9MBa&c hɒ/Wl(bH159bV b{$&l,˲$Ir޶mhLúWUBN/]9uYf r\߸2^KOӮmu~ t *$dDZ$4Mal6? A֪FDK JYL%7ꗏ._*ϧ3#"z"k.ukTS t:ʪ5ְ,W+,C:"-Kd6)u`զݶ\GWT! G`8$X[ԃry>K)]:EO|g$cmU5iPG"#y3kB>`)Ym!PC4 J9ccF}cJLm/ksΑw{b!\ͧ)Mn'/+8ue,eaBWWʣ{<ΩH~rRYUV"4U1й:*W=3̐}_@H1@bF@g6ei$6fH(C6lZpy|kAtyq}="ӳάb^Lu]vkiJeL 3;!4Ԝ; =_V}C y%ԑC=tⴘTEQW'>//_sQҗp6}u!#oKx9&#b72=Q̍qD n٣[87ΌDUd)$K#18>jT]o6f8[b)$Myh)H9yl@C:VMj)emHYfBLDm~ bNb$fXa/|1e%e^]v}of{H®𞘛yqrބ\Ô,Ǡ]\_ߨrav RYbg6#(0ͦz9L](p0h[gN! A6o1?NFlspu~~rq;H b" N'Nf rr|tx~qlܿChI5S5zcNшtCǐajv*KB4^ +դQ!wv>3teE"o" I qꆾah6ŕNh(" T#!4dRDP$k>9nWtY(hh)BԤ x@GLcg/]W77aZ"ݝcr<ʳBãݳ7v}0-fky$('ĎyzzCqb Ym{G싺4|2k,>E8|U!$tq~YUt/|oe>P/׸>/WEE!e")@h sl3J` 8Zc\e 1@q")0s.Z3 SJ,Q[*еm )Xoi(Xgi5)8)$$ӲռcҌqhL`k$&W_[G@cR/)iJ1ZJmZ@YLSc,d*JHR뺽NM q`w>۾aX.ήv=}{fsAre!̊S Rkք:d`˛g~D,o< #ll"͋C;,bֿf_kNFissۧ4ߣ'',4fS&R3'hd&jZj!+zЅͦ^Vԗ릩AX̴fMʕ(  Zdw~cXN`R4S@4@ 7fIYnVi\YRS//|y 5Ӌ'g)FMa!!EHΉNdoVJ. !ϱL+0H6*G8&ߤczʋ64 a4jT)%ECKT~d"!9IJ <06lAiĂ[9_M6ӨX*+ }߉bg^>6rg5tcѦ޹d2Yr{"̛nӇR}nίW>~:!a99VFvaLL1"\6a$6E%USQHT 2'4fKd7Wj~g~ow ۿSծ ϟ^sEٻ/#®aXM 5 "=$jaU",w RRkF2 !`NMœ[O(L{ɝQ 01uc[ R#|2$Rf6Jrx{;ˣP0q2W‹w- 8&dbqH(L,<1-keVoKY꺹_{HyDYڽC&2SR%Ul!N ]-W#zi}&"eg@Amrʩ9!)90PQ`-ٳ}cg? ]m[", I/noۺStH;~^MnWufdC&CHJUr7Db1!v*#\Y1Fh10Nڷ焔/,DFwtk@4ΪI^QM/ ɏt_^???WMm6u]{>::Gă=R%U4AJ9n8$"THTs;QLȜE3}w}O)iJ`Dh{ p>dS${bBB5s"0u4Mi6;'Ā94)4 $@B'u۲高w1;AL+v$ Hr[I5i6.c1Ë뫛%1!!dq>^ L^0m4)8P'"3L 8Cl" qJ {{۫G7b)mo8 fBN%{ mKr6,W&ާ sޚfAdV1<"brA1ɬ!L7/'OH$"Wy ( RPIX.. ylý^}zm[*0W}㋿SW,%HM{~vqzz7t:B˲ l5K!I$5a'ɒYVc @gz{yg}_M &>I ̒sg+ ĤBwd$@׷JWu_ ?w tb;)jg!0~[om_m? pBaj}AĹG~cx,l˰0k }!ejb枈XUVE92IA|^4`UUfӲ,( -`+7bDB 4EL :oYDMD})UY4>?]?._y6 ^w,Ťi YE!ά{ h!)8pS_ܗ_ev*}]拙*Dv]N㇆>Aߗj\[ÐRNNRLyOʲD.-$EHyO,fb\TXLTt6\.ctqH$jY,iLfLw7@#L&bрuL[o6m o~SŘF1#O5Mz0}_a! ~Ӕ`vo}Zך7~//owUmgw{Q ͌Y1,CZ4뺨Qr0h&HUYzD$x}Ƥn0" N&N8+)r1{;1@"RZu,|1ͫ0TsU5KjnȘ>ISm!Cm'\.«z]3 ! z78W盶=;)Ã= O9BJ:^G5QVUHIDC2hi9=o~}}iN[Jl4r㨏%(A!wyMi$) 3Rz<{L)`蛪TQ QX qRuDEUz ;{r1AP}߷t:c1"$D1sjJDIcFA4@缰KRԀr~vSaxY_?ec{t^yۇz ^&UY"tqp߷ry띓zb]oLJN\Q:^r"TC$S , I:2,dL+91%MV\=(lԍyos!B4`fi ]ps[vg C@X}?Pr9Q!Zd 2iH@r0caT.6u/ן"滾\)蕇K6M_ 8c{YQ*AgdwgqtonYx~󋫽]0;ab޽Z8Zӓ &%{ώbh c ]`JIYri[-&4M]N'~ B8  fyW7dQ&|Ìd `*0#ҋG]w7K?w~fi_޷WMgF0GX@rB.t/޿w CԦnbw}i립1Ӣ!뮯o4"l*d/+KYx_pY)&m)cj1SJ*B|10wtE,J|FUմf|6Msyyuzza2$"P59f)4nyswwJ#^.oV+P00:>:2/+3'Rj GEU0&)h}1@CC0 C $k{bvCC GR: 4@f="b&b&:9;)&/OO|WOr/3?P/_~z< J:mRcKݰT8ɺWuڮ֛m40SϏ1 vlwwG(B$)(&3̒ZJ: Pc ,YT5բ{wWƳ2b)fb}aZ΢(<ҝKRVF'N29/| $fqE !1Kе]QJ4V xZEQr6=}|6C@f ]}R*30yC-D9C `]4Rv'S*##!!" %+\&/|ͯ~#sO|㲷~13`B@̓a&v'}Gt{yyֻ#$oooRDNX(y[7Ð,,ėl;hno~(YB6k2f`6qBjDQ1%pI2_rT#8bD(8HEO7ų) '__Ja\|!e PCж A$4e LMcVnġnӷ5w[.{{NYPe-!X2+&sȫAJ9fN&|>u,c0wޔE2NGk/%yEC3ըA @);i=;يy8͌y3\ެ9%Ź?613" YNB؝Loj4mby}e{1􆀐1Ƕ}~!329LB9A5f)YLJɒ@ qsj8s as'&g ph9<3QT8DT8FB|~vu9}S߃N}G?M 1y$aW*PXS)YhzGYiJ,;$sRr\HE5 @2ah6m6:jU3oWm+ &̔v8:;:'$+&B\1rdjkBe>#fUUx|SSrIfa03&S v1#U)Q]Jfhf%J&bvQ@>bP5"g|>cۮ././7^ʊbQՓabdNYU9IrVtPM !@ jXm{ w0 IxCL܆?=zٿ1x|GZӬ$$Lj##;G'LDCԛCҠݜ]^lV/*;;ڕhDb{sB@!\9a!ݝAa;jJȄ^q!$35fdLC)DqH$\9֫jY~#_~7{_G}h6YQjS}??[,bTiR;eh1*JR2@bUM!!j_z@|ݺi۶¸mTrRG,BL).ʪ,Ka1ƔB9F*d$@AcR02TL)R2k̼-jams"F$BܾݖpjTs i1Ms~}||UU>\,o?{{3{{O'!>ulE^yt+4c ,o|`(h"wޝ?%~zLD߱ J)v,– jNX8',z{3{ a><9UW*EL-dBI8%ԇͦ>{qqqz٬IUPw0 %w`"YZ,+$2Mql_}>)]ׇqP:iEQD)jdhy5#'$ff\72 ?_e}~]_W~+fm6޹Y5)fSXME狒i(b28'(j 6ҥXF! :"YHW[o=߬뤚CnbGe)iҔbef{Ym=ZL ׄwsiwJPq#~{VFjǑ쟄6!TMv]5uI;NXXS"|1ͪ7tz4­nխ90@4E#1u77|6'ʗ Afw=x{kN(?9 @M5G@exdrssbhUjH,ɘ)3 bԡY Ť46YH>я_:?v?K_籤xz.~>A$ZBb_}mHS*<0 Q XےeٜM6@-@,ġ00N&U }֛ڶk~˷1Uͪtz0sݦ5(m8Z2!Rn_c4kҮydmfwO 0CRщDD"[20!zqUU^\ެ0r>ZڄG?fK1%DΜN4h "nNYEKo|q7L("bʪoFFTB+Z*d;b&A¶m<@}>9=?><7F, ፧_adpr]U9)}Qd2M oeLͺ]T>.IEa"bኾn42 >9<:3tɩK0yCDs#$Z;)X~3=/W?7^\DS2&7˫ͺk:CB\Yu]/7UYHLƄfoh)F%"6ێSsu bdȄQuh~>=h4B|UNvߕ,!DҦnN/tZg$@ur irJHaX9rG/]}=B8fjgTV;w:e+ʂʼn_\\\]^L&ܬJH1 IDAT4 1ytx믽5NFŠ`d1YJ <` 鬿]E@ٰ MxdU#[ur U-:~23&dvSTּΟ@Zw>FfIxIYUeE?hUU8/\@F"'Xo8 (*#a0b TYX ^]bὣ]bYf0 &@Ȃ!3]׫ul(vD&,"Ii^!(,Ç/7CJf$m/BMۉIdwo؇!mV~󽅛LȹgsMRAdIBM(D"4 no.,ެrtu!.B',t>9>?>cbtͦl35r@p9YƜ1O" ~YdZw,W_?7~gnٟ/4l>Mjbf}h  )mȳjSNQ㐒!*XF*dTyPe""dR$}1Ťk6m[>_,(MMFeba$% L-v;>h>%aԪ4mשBs9 ܥ"nΉs,|7_"$@dD9(=o5\QUO{q>Z2 (b}]כ*4zddd3%|~#v,C™$d*Ɉ0:̼~wrY#RJb6L 6z 9SWw^j쟯_;ۿf 8l˺fDhem _eY 8cބa0իzGw9D(ӏytpWfHdF ( 6Vzd (yxEFg8spdZ-۶o.է=N!== kPͥȄÌcvz,{ʝ4ő(srsH12DĔ~ -DhpkqųqWbYD1\T z').f?o.vv Xe{y@EQZ4˅_,2\Q]B"e埲3`SajDFՔt!F=9vu._?Ͽ<|?70` At/HHH3@[Xm5m]Ogʪt2JSLӇn-S"U|FRm_o6 @v?6 $U%T|5J޺L"㓣{GBQAE5M6'OOU4h P> FI9oac_]}/ 77AD.g5$1j9ЏD\aCX`fd&EP70l폒&tݐȰ JN )뙫 ٻ|;Ȗ ~?+6(^I~c"k舐 #8E!' 1 HY㜵8(ɑʫӳ~? 󎌉$&ePfrޚyI4tmo$F\\ںTQ50Pκ22d)*Q*ETd52xz~|rxx\-]~ɓvƦi=?M"ͮ],9ΰɎ|nH4l2N*7EG3( xגL ?z gū:7XF5+LX%ҘR̃P3,Y35AM>d 0awq<}9 ,s,'.//.:!Ui3 2k2SWtMe @I1Ĥ1%D"6H*ANdZtuyx6Wa)%MF @LPW11$)Ar!2 ;TTt*!j!3hnήnu:X-1#jTV%h2Ȗj@0թ{zHԜɈD" 2*Jw0j&b}?zq}7dOo~p.9PAi^4O̜e&P qUUճ1f4:_S>./ov}A(ii'ܳ]n6g7__ %g} (88k sʜlȶx}5׀hv<ܓ"АFo]l &4FRRФb (]OLʌ(z.;G߻\6V[?Iy 0 h77k!˾./ ĒѓpO ErqL)vc˚t$$ʠBUQD2Hs0o} WZ ?9wGE xiƫ1x__oOu|L(JV{(jQV%EՐaȔ@A!)he@RLY)u~睩 @pCߏ!lY05^&ALdD"4l0.@".KTI99@0*I8qvg vx0=w6FUa)2n0ד&ɅQoܧMdι(EFTNW2 ѨWWWϟɪj)OY]{g٠ψ-Yc)"I0cAU 5~I#ɚl~ޟUƘLmc:$Q0@d5/ )e2D̚b1b?Vu9_pw~lz#C?3_\oǟD $C nBTb8%ȍn4u׎׋پmC2C4#)Į/: aL9TƮ̋* *vD` iy?[_]&BT{pXD~?S/xϼ~'~WDDS6sgRU$Jl(\*e@$9Ń4Q󜵅Y5Ȇљ!٥(SU V5]iuI%$DI !4( )"ʅ SfAT˄I&v@ {WVw?Bq ]78rv;5O1.Dr/1FB7zG$IHLL]Q)a˪<@jGl2!]]\^^H!ė,Џ) ˲ &W8y# pl=Qroo*}9D9~a]͈nsjޘ֖N kC׶}jk)t`RQUrM RI!HQVhCI}*\?W//.MU "D 8hTFj9v ;G[qrq)(Xkz={@œ1P4q`!ڣHȌk 6kk Zb0_g}׿~G{ _??7{,&rN☵ Mh-!Q$)(`>=q gD 5@E$ f=_' $MIh0qBLJ7m͗UYj6_.`%&ғdWeʤeF4cEhVEa aTD`BXu,&UQ&#Q$4ġkMa\\R٬7|-GȰA&CC==i\櫙s9CdRJ!|[o*0E彏)]>@VD>uUWUQ )Xe9_Ο<9 >!e7Ƨ !UtD!8,BۆO+$D@U1w|#&VضMi1r1[̭(b=kU&"@bNIڶm H,˪ ]7\_/(bf~_>#cP%aw;! Κ J!0jVghFd9Y1JA51Qn12(>{1 U3q콱ǻmwssywt]E2n>?W%?sղ6l"N!IJ%dSx&B*fO4eY>q 3-+B$(zicR 2caCwuwCk c=2hҔâI* 1NkM"D y  R̴/UHX7D0"h(,#Ɣ! ɣf7_H6/rpx119ƛvm1l9w!uo]5;=;ԧ>9  :>|v1 gR-UU"zǖ$b>/ RHqlA9HQDCBF RU,)b_W5Nmms-]oڋ'_OU %9ͮ\oI*}Y(ۮ_ow7g7Wk u$~`-f;YoYV@8%eb_Wl2Z~Ftq|\-8j.3yO:JS 01D2FRŔiRQ[f6粟bjf0/rVBVc!3U* WH\S얹S(D5HB̊T)&KEs'F̽ԈHE˲C, 0 CmM}.X><òdA=sY0%Ȗ;ٚW*qo"DQФT ZG$ haRs֦Y5?Mwq~޶mlk*ݽ{Z-!qrr 3DDREtL (ӧ?}9Upc6\VEQW7`"VTY*oޠIa}ۤjX⽎τbU AT5Fe6&P?{=AfUlv!f~x )%Ӈ_eUGIaMv^`K_(wmuzvq~zYd#p7vL$J$n}yֱ+D\gfã!fNH$qE1 )E?I119g1@?"}!hfQɧ/>.>_/_YB4Vǘ$1*0AT,%@nٸǐ&LE$än'*AwLGު99Qi N~v+0 RǶhLzzvy~vILJu3@Tbx1`̄Lh8A8J /:HT8fC_hqxAD1m ? d߷WW7?!!^~585iLQH:fA5EcfYbJ];u(6еf kA en qWG9UM&Q%% EUEiQ: 1z={nﻛͮUGܻ秗լ6Ήj3sI)VU9}9!:y5xs;:F)J_EUUUe@N*4 7Oun6lSeRIRbh8ҰoUj2,,T!ŔDBJ1$d D|gk}UlG~ͧ࣢/P}GϠ IDATh/UC1(1BHrXwMo S <$fP6}J ʎnQ|$9 4d REf>ͤdiLZL ÃmnX;Fdjr.S -WT +k/n8/EQ؀q;r޷}9ED0]w||䬛 bĘ(&ݶכmWY=~٬jzc di̽0zWeI7:Q|p{SdÊ 2_+p.]RDQEEysTfVԌӋvpQ'UQY$L"R2[̌71%&zY(J_fe]z˳K-3 n3Zol]i jULYAȘ1vW0CRT2FC,)M$1ά"={>?{7%譡Vxh3_/[o$w??MS~CJcR`*ns@T1JHkE81D)Wi!6hւ4^ t}cNyBV' )%ERtLr{151ctUQ,gQ~ל;9bBըJCbjU FyvgVݾAɷT%uŔڦ}';]ofy7惇Oϯ, _UUˢug!4 TuҘ ) 2D[ާ*YTɝYCf1!E H2ecHpN)MsqTd"ŋd~G3_|?{~KUU~1)!_f8ٲN^Boؕ+kL!qS1 ?=SMR;'wONꪨfU! EMOFFrp?G_;{hEΨe;ĄqHI9c- 2$o0񠻸@cհDcMezAk-yRn(M3EUZ4\])1 -Wu۱oٰÄ}CkMYbQ¤!ɟ+1a(M*EP%@P0Ol&n("1@e(**Ǧ}w}8}NW>S퓽?/~K__+HRq!I`lCf߭o}w0ky[1, [zoc2HJIRTUR`UB$ ǹ{"dvDTШ4!eDcʢ1v=8>,W}8tcaghp9 ڑɶ**2&O$PdFLhddY[*D`Wŝwn3Tp^[>}1xn=?==>>!8k˲8v99{NU!7Z7/ Q䏾ڧH$)M=DF)!}C@Lp{1 Tf J6DL˲~ɱ1S$mhUyWx_xdp^_\k; 0&F:cayBk q.j?RdvWfo}XJL@,? 2/{mZ猩nv;{ISE.N/受j7kUC$nFqrZ:k6Ӌݾ"uBF l}>g "L&I(1$MQBCTxKxRuʊjLɹlr(>ul^d2XfkMQ9ݾUG"ϟ_8:nVuU9|8 q}d?srxq8pa)@86I%# hs)ye[bY 3ND/LJJCfuEOhs)ʪfU]UuYbYe&cYMiٌW f;z"@/Hi pXc?TU\yfj>$_1ASy̌@Ĝ3wOI%wi6"~G|o}~WQn>i\Plt wpy}٬qī&2%TQaKHָbjP0 :!1/t+t!&QMY WLd4l"qe/S8dY>B.*uA8 1L_FDj۸_og'G}7Mg{JwxuJaViobY»a?:jFl4ԉLdEQXcLa 1ŔB&J" "z;M 1- 완,*a]UEQb )ccO4Ya [(5h9+tkuJ)*)$Qj1%EHΰ Q֗lQu}iHn>;\֥3i0}fϤW: onɝ;0ۮl7OϚ1O/V`V~I{d3no㞳cM.1oé~$B4La `A$fTUoO9@Q!ڹzl (`Ϟ=}tucWU]gE|maN)B!Ķ ]ƘYC*R1+&n@-O m? @}!]."R} 3Y|a|}ql5_gSC84b IuJO*1vø5M c`YY;6EBSoB"(cc9)1cLq 1ď32 ,( 3gb-feoLl/K_J/ǯkv>VyήL #T2=G9>}AIRLmKZԼΝހ9yιzV+j}sy4lvൺflU@b|gUYW!cx"~߶~VXV2\ίo/0tIWU:ܙWn,>/G;fx\_(G~xpƔB8HBp B ![^ea-BCs_{aBޕRӇ<~tCUլvg˾,L@uF4ic\`Sy!0(2g 3Hv;P}<>~׌3~Џ֞4_tUq$%q߽w7oo` YVa9< g>/}_<1ĮvWr!1%6EY,*Ϟ=;wϖSpC6UYdƮ ijm|g[9#?"cR"{W>ŔbS1cX20bFJ*C&"A  ITrGJJ+Hqb@,xN:ka~ܮwr?>{k$TCI5HRIa2Í nA )%bL" D#:eݷj:@;B`TmC0&6c^4$, cu}}hu]_lbXeE;p`5gÄo7mڧ^C1²%HĐS"1#$*@L㓛~6uY#R-Hsɕ]?NGI|6;bBOjdL U$!77Ud- ْ6/+DGOjuC E1F |szv}q>ߵ=ޮ7pno5V'g'g˫_?? `I(lZG㋢,J1ȖU%HJa^} !KbckiasY"iH 1+1g}Qu)IYG2q e1bvU#b"cd"֩+ ~&sΠHag7 !>},2 lȅLT5L74_4HDuQ̊j:{ؖZSZ7~ʮ$E _\m^Zo֛zv-0/nV"*KƱug14Wכ^,fqHc57Gn]=| _5v7Qix{G,)"BpN.!B$IRJ)H!dǏeM7CJ@Uݴ}o٣mQtU ?[.բ\KƘ\QfG4 5Zȡ!2AR"I2Θ7?\Q 2_DrVK4ٓg0ަtڡN[_Pܵ~Ƿo[K̫r WqwIب1q vΖbE1t2"F&\mnί@!0sRͦ8: g2v*XRԔ-(cs9 #HJ)C$[gZObSu\.ZLQQMQc l0TAt֖Gf4U k@ u"IRI5- dc$" #T9+/.Ξ\Wsqlqȥېblkc`18\Xe]e!$~?c/s 21jv\w2\^; 1~lV}7ZDsUΙ%)Q@,wm[6Er\]Y/>}?Tz⒝4O?xryVU]e5K SU!6MŏctUA% \lUf˃ob?LI !8绦{[_?zBZ_y5_ͯc}߮w!Ҵ”00 bY$:(!u  PrޗEJO"1ľwi0F2Yä0 ɢ3&-!SER IBI uB])4lz"a"6Ėc,jDư:nڪQ'C3,doB_#ų0ak0ͫ EN7͗ΕH\YcSjq1*3㘚}ke~=ˮ59ߝnI HfyC<$)!Ĉ3vDҖD! $c;l KGC$Suwg{}~I%HBBu ^{%lDVM]"i14s5f\za"ޛC[c<>;\6mgfpy~yzvϞ=.9.lZY1jQn\#HǾLg-.$f>wrcaDWS8$Da?T- 8z1 vu]M&0v'uSy'xݸMi@VȌXn!TwP:m)lfuUqS0"vf{/ݮgw^㝛X"i!*8USfl 0:C4:޼/jيM4u-rU$\]Zұ )4g撦s14|v^nNNoOlاU5kΪf,ܯ7o~E}Q>?8qzfB dlP)LD;("b̹fլ"7T! lee!F2ɱ ޱ0H9arv/UiOr$$*F&z֘8 cץ.vjF+)pjޅXxf(Lf&,MUM%ΣvW q|ٳK!<ܟyǂ'cUYΟ=:ljBfY3 0?8vb;`H0զhijyi#fPAn3pzi2&^;xu/4 =- IDATrH"^o/eqqvyޝ= z{5Ϣ$D\Z 9gȦEc.NU+ǔm^ R5ڮ|!8&Ե(Xv58O7<Ʊk!eDV _kCJv AwM%Uyá/=;<3͈h*o~KſxǯO˿wDCiq4#7blj1Sz 8!MTP9331@A׏v@0N8' n@jɲ)Lt JEIhy#qBRiea 4D]hbb]]*Эtxٟ_\,ݦ1g}ڬޱfWU 7hfӥv)g ;/6Z-fvc}$T>[ZoqvKx4F۬FnSjo'$3H130)rݴm!_şv'L)KrƋiI5CfA$^jݏuU9L}p!m3 3ŮϞ~~rvt> $MyX.3;Ym,mG'/jRc*Bۤ HN\`WW?v2ɇ1  KL{ /K$/s0(gJA"={܍S^0ƎjrJ)M5_ a"Lt\ޅYdZ /^1n]_/76D΋R6r&G1!3;',9ik4M^egE&f*VL)RdbY%I99fq;m̚wO$v }hy跗iQr2#q]#}NyGUKʟϛ\F /+qszyA^C>~HqN@];tq iqРq~`^Ř=|3o: U,B sjѰmjkjvyߎqᓋn>P];&H9Gހ u\I]{4D1%Nvfq\_^ݽ_b>p*y{W8vCϋ;~___-8/ڟ?o0I^]73ԗhyr54'٠uYϧ*12S(];ѬBpSYH]!uUUSL)q׫zjK0iݩ42Gb_|)@9)48ƜAr`Jb'$o:񇩴l;k8ꪪB 4qۡѭ{%*VBԜ}ylMuFd"a$ddC 9m׭aB;utxSF9%p"Wk U Ҿo6 U& Gr5QX|`̚fuڬ6@t]_o;/JYh6".% z*?k X~xy~uy4*YӈcfS &<Ԏkrݮo$xCP y]Y>-ǧcC,efU~?xuq5ao'/%A~}W?/}'mQ_qc;!4!)3Lf{i( f) K,i 4sΪ&*5Mp98uh^b'YS?t]-19=#?/Me* O$TzybĜb1S^h@lp1SEݢXD?*"(Mx0 d'y"vWnV-"zIlnR)'r❳rA`'fĤC?v椩Ľ:Ǧ9nԜW^kl֧돽yVG]F8׎!h~+UZ.WubL8 0VmݶϞݻ8grДH4RzӮM* !&^n.W /2kfuٔB¬68P9$W> bss@x7l,$ʉw!!Ŷ+bۖQo///awڱ|'L[zNɼ7{)y&BlsҔ4g]>&fJIg8NԒ&%""a@LPh`R%Ϯq"يL¡ N9d4Un,hXvi:'Z9~Hт ,IeN&a9`vH7:fe>R 7r|FЉ7[ nh޹ʹ{eji̹9}g>x~UMȴT&B2bYڵ=ut/MIZh%uvnVMUpRwjZuS7uATB0$`f @ݴ')KHlJf0!0./n L$ΕNʼn{_?*{1vc74ԂxѶ?+w~dfŬ!b&*mSɇf&bhx~4߮K,LLLDNb3˚c~rN8q<"!j֩&9SH9qlj٬5ij NbT"д. XUs9Ɣ4Ɖ|(4]*h͊ 'X  Pס3fuuzpHf1*1Xi ǣ`"4?hyy{jΦA|AL{˯ 3;ljϿZ>~πah9\nbZU=97 #v]4uTv}zoOZjn:v նua )vݸO>\9梕aDZfuƜрrTfWـYy11&w8 Uq~rk]?f\>x/:(N3hj(>&xKomQo|]7LEvJE PJ8Ƥ;#?.BA1ibD3DW)y0trX69FqRU4 Jf?(KF"=#fӬ11q㘳*}x Jv%Uę`esqq٘RɲN\BUʉsy[r jW1N N8joz w‚7·B+YYljA89ǜ)4:BY BY.J)yLSN;=!fCGkq\U atSmӓ{' 3f5,v^9B/7w5)0%U9zZ,އi*YTˎDDZ1Sj}7U0jM%_ӯ "au} ͬU!gmt7-1Ws {Qyj3,9*cOUERHN1&@nv^s%@|oVi H?CCvVnѾ\m#~_r$&Jb9w4qrRf'\bM'^;Ӝ<8a5kY n0-ҝLU~N:n8 C@YLu#39ۮĖ +9~軶_.&QDؑ) "lL] αsD rJ98 8Li fHXVP5\E@9UrbuՈYFW486[WU왈ʅ|o:~p3!;/zĜPŜ8 31n׭=Z߻ۜz'xqyYq^DXجp(rʦ>HD)e@K1@rڤJfݻ;-3#4MQsBV{s̱:T58'rɒ8ZMe$5!BeBb“ܬvuqm9߾s;g^@c~B7۟ s{_E}>5ͦ6 DX4My˪0כz2γ0 嬹 )08quL9qo|d< -'%ͤs6&09vry2jv }盦Ht x&'@ 4S?Ӄ%4px}dR1vCj91_^* !1EL1ZV &UbzSWUi$Ν?{'p& `VuϾA7n8'G?˟ŒiX4G V(eYKf!ecʹRSJTZz, "%kA2q i)kWU1 (kNjfH41 102XNr)4J1TAvih`:3@KًoBZǔYmIȜ4O$bĤ1etNfYY|!:T) ~;.>pߏ81Y\]ˮjF1є0~Hfى]7m7t}K`y '''>sc̫Mwу'}ۻD1 y$@A-u}n;&f&ާa@B+T&Rr,Jwu=䘋/o jޘN#]Jm}[|E}Q=KbdA8Ԍ@f%2/ nK }fd3 xAC`)唒f=sSt<5r"P2B$EcSP<،7jmZ!VB%52" &4 q,WMjAdE}τ%hCFzDZr``t7TJ7L:=σsNc>凜PqyUnoo.BDXp7Pb@cbBH{f9#Gլ)eċݓӽgeBZM{qySB΋"N9!Qιʧ ƘL58(sR"[ۘr 33%{&H}.O¼?k)[!ܒ#z"2jݮ"MLJ'ڿs&2`b!BN"3$+w<^/ݶ~q%TN޺uϰGJkfT4S`h ")I!!&-@Kl%􎋇YsUJb\ʩmjn5q]UPTfVbT v։OH,޳Dc,Ri9] ,T0rq)gOɬ$ˍZ۵4Ai39 -lqoЮ!1AQ1 Bf͏P]R7*2bl;l9X/䃈'Ys֜X)3 ",\.s$'X5նmguz-%D:{zv)uܴӳg1PW5֡kdR뚦Mxf3*PUmۆL3L/hX_\=|gg8LyHA-Д|kS+|Z(sm1yQϿo FF1SnW)=}r T;{SBgv3@MqLtt0uktn膱!\ǣß&{_Ҭ~fJ@N\e5Ъv,bDo h؉01AxEj4Ym?xv{~49eWd24RdFs#F5ΐIc;lor3!86;o?tprt4b|{Z'lX 2DF0M){a5(g4D`|~3?q7n=|ryv YSԞY&f!i1fcI᜝#C4 $޲nVk~m˜nva7MB#c1n ⋶E}?WР11fEx<ýS?6Qsmg-txFdpj-f8kͧ>*}bӷ}O18qǷ}E a2 KW6I5e- eS.jKL$MYD Xn@EXu`αb]o~DzbԝV@ lnv`8 }1C˻) ,*ޭ#nԼӏtxt¼BrPUմ+馚,bLDf(Zi= U+PTk7U𡪂1<99_;!u)j IDATbGʕSlLkEB,pЬTV, dU/xv)S.o/'_IU Au#n c8P]hEѻˋOgiŹ),QoDSڥN;$>)/꯾??&ce2 ;LS"⸼ެħP8j֔r9nI35UDR֜I w)Ρ nA=zvmvӍ0c9cc?{/}Zp"?f޹cL KƶaPUjj;$&'E9mő܂s"s> [~\_-۶Cq<_ 1]|90W#p"7`9ь*wrnu׸ʗY*Lt+ ' LMm/Ήs\lɒm FRW}׵CjYMU- SV3lZrjcrXվkuە6|\_^, 2;aaッ\j^l6?R69g:;j>"ԌdĦZ|*cSqA>Rw_w(N]Nb77>~kE}x~wů1f#".EX䰩+)T.}o|r~l!$Qcm!9 )jj_f j4 #20D|~yQzgtۮon0i+|Kk]y̻"?z!~a@*`u휘2KRfK2%sʅL LSwzctDF%H$lZN؋+8+{G3hB94c&a}9kvG9gf% .M' 18fLËjV1C1kl>b̦6k!+!hɗ-31` dˈs@.1Y-<fγ0Hp+ )iN9n[s/ʫvo޷߾:;ŔE׾);q"(ߛ_Тs ` hx$+w )['[j\o'._2,bn 9U]^_n׹R;Y?E@sw%$VÜKbU3a}k-";ڛճa~y}v5s.R,URʥKSS&*FٔDD<3;!BCq9!Ĝ8CJZ \„4M@\Dڮ4Ĕ 懇{}6ISQ3A|1 c8\U5k$:kʺ5G9#w`NbJD Y}||rN1TN(0,Bbrfb1{wOgf0ƔKKkSN9bw?|j?1{ɲ"aʊRRr\jY {b>N7"nz'g{x'VMT*1)Q^S%miJ%JiP|8wru3Ӕnar! Gxs܋qyv`"V"8h>3BV'rx6}msNf: zL'G(8mcɅK"`@ *ĤYPׅ̀Ib,Ͼ^i}|9C.J5JM\'b"b昧|=5-!9*K|VK H@BNID*+;XއʋwĨ9w}\VmIk牐w ) c*9Dx0)X:H i TM4,礦1ǜ  ;!tC PDs"f'W~v0v=v=wtg]@hJj[s8maLfZԨs'#FljEw9;YCm۶#zc@Mo/XRR...>gqc?.vנvқ);~_n&_ ~5嗿/wW$]";8%yrȲ݈Ɇu;l[v9k4v9^&jxy>H_cMHTT5E0#4/-O/AċソZmnڡ+>_mfƅ+Y D(TDe5%j,}o1B`-s!₱B)y %/f]^-m'hjjNqP5ܔ LSLJfY- HHhL2K鍓b5)x&q2$":$Xv , >V^MwᑡfۙbrqImVqF"N)Bcޮ@Hu;+}NEsͶCιw)]׫jY.jc>o7^% n}D?Oel+nicS?1#Wf ~ a*=5,4'`: ,Ǿkveƫh כjgRn s,M2iOO7>%O 1fFTӽ~o 6'Z6Lm`BLRN&;_ཥ=U3 4|I{_UE1}߯f= _Q d):{̦ B,4 7LTK!1ͨ~ES)5eOdFf*br V|ShfB\,o[G΀TYSg oY`mcʗW׈x~y ]km?۶Sw "'']7>=?gmKiK!œG{gZ1K9nE7 aU5y< {jϝ/l"TU(o~sΦ{G_W`F@Զ6͢9Κ!&_Wو<6fk(Lep]KUl1pAp7q-nfY \Y*0;nV;d2L yΉI6m5kb!FΓwzl1%˺i۾cʠ@{14l}0j,$;q{78;0@c9fւv0*Cc.N64PA"BCA̦`@L :'؏Ov88uI圇]oWw5BBdsNؗRl ۶ U0DDމRg3؛71O>xXB4F'wz ySofs&7-89TKY@T<o~_1mѿx_LNX koVg{DHcfWr;tC}P@ 4"l~`?cn=fy8{jUի{z{!@d"$1  Avrn` qN6&;vABqp;{aW4M>@[իԫWuU9< i`JӸߏmquyF: fmĄαsf;, rV .zFvh]?cK;㙩hxiD)@dpwW#5AM}h&8瘎Ђ)YMBRdБ!IawԀSAع?|zv,U $rKE,iAQ埩 "w ]Ji92R9(|`﹭9zTլn~(A6 VUBcSWι|pYMY]jJi٪h:#i` _4@r¿?JG7ӡdBsr x{'@ r~hx/?w Ą$kP@L yH&4I~QAb15Mh4 : @秧s6Tn<:ԏ2XQw #; bhJN;@XY5["Hb !.α+TPKF4#PaQQKe)X`wgFQ-+q[7u<An~)mbpugOóOWTA(HؕI!t#TʧUcHE% 223;d,&xVa'$}fwgMUMCJpyf}?h!ScBfl-"2pN%.Oźڬ7P"vp1gM6ZbRIEP̎1+h)*ʐ5\ fhXd,Kk.RUU!XWyˑ1n=.VlUE!ĨeAKm dQ-s3BUR9(OCݝk_x@frW'MaPg?xrBG̏s {C9`;[<d5kضaV|,Wg痏c"7*y2o2r2;y0]ʻ?hȃI{GHڏ:%U]e@HøZ0raޚ+DUTDrǃgHfmS/fU}C @fG8ҾcryP4ˡK@?о19D3=mj&*e\B##UuUUL 9]_'ԶjSs\wP\;8 (Y՘ Vj`T^yrĮ Um,^wݷY,TEO tiq+cljbsN9<*L|tlG!0`:B!?aC@2BҔb0n>}fc?:f^^옜c".xqLRŊw~ߕ!znO=i408vHрITN٢_y,VWvdv&5"h#FwO3?sf¿?xА}>o}ǐA i1ktʖDav؍ڏ$R]*;PB Up!#]ݎ]?Nd2 nfmsZp2)04&0ps:jWm2j/_U9ߕ8=HuEwjc7wVcMdaͬY..NS?UcH JD-qԨ"#w kAl:~>k'lo7áD=(H `1jfH (aQST(mف!23`y ŋy\ov;&jͭcS!e0eY-USlCu'O/'Hyo9,3-Nϟi?~wwow  f ;D]=w? y/#ɄbX.ߟ,ټnjv"}_f U *TU#L0v ]{ Ggu}y23͊j mC<iggG}FC&K9isucÐJRAjey a a$G+h[8>x?|{O[&7/ޔTcOb*YnpSSDr*ַ$h jdrU.s;'S}8tn&F#Qc ZVͪ\v 9nlـREHb*j|' E6c!),Pyd\>Ȟ?y')قcH)]z"v2j \D&dI=!ͼf/p?V@=o@g { \ qZ@wdA߿c_EC\vu @n;ev^0tN941'1*RoGK.1TRx5c?1A?8gM5o+RְXӐIrFCiN|{XyE"Mc}}`1sD{4H ۔#n[`G{8?QQDIQYN/OS?}xy T&B*6R]dP؄K#ޕ!ݹ2{_lRsVu}qzq:Ǩ`aޤaq * O%✉G6c&\g蟘"sY' . h#38*t+,ǗwyQJrgqcf"z{;Y.v c꽳z5ofc !ƀޗZUtw|Iu~jlMGZawjNt@x+_;Oi gIS*ǧ~.^y;"#,IĎIS$2<嬦"d!Sp) iʪZ{|`Iss1=v3tqێE~pX-R>ƇWn_gF"Bt9Fܱ@ Dm+XNќAO_{_F"_ew-35^Oib&U{s.6uUUux\% k4-NNcvjOá58\mw]wӘS~A߿ws%jiD1ެS6cYT1f٬+@OI8a1.EG:nbADa}};t="NJ@mKͲ? ;=$I%M_5~-]1r)] >LS޹`.Extb1GqFMiO_n69MS,|zyzvѾ~ qB1Zű%q4dvu]r屮bʪ*#3|9#,UUXr֏C7Wo]?{! SSlD`߭٬&\|dHޱ# JJJTt(VUOV壳bR)gj!rnLgO>u=#11yJ<{ӡ_IQ;[U!lj}*1]ջH,U4{5Iq?oc54U U vBK)UmU,rmOqqrC78a1x$bd$$\5tC׏j@4k9 TNLs'6L4e#9,N &"*fm92V{4[Z$&B%Ȥ䝏ˋjM7W^!)OSJSJSo!CD.3qW3ˀR֌d8is'jEu) מJ4+28YZ@`18~&I=`71D8PP96 CI5n`n*5W>O?OE9j!ЏαN~D`4,"k!%}!K.&U3rH14WUmڦ9].-Cuց)qqIqaUS{-gTHôlI ʱBH͛٣DvS?>}: TDMsh=g>Fud3d'|ymK&FҌi"T!ӡ>G׿kz4es&)K3]|_&^ܾ5/|w"+HdcR) 8~L `59:E00`Ș9 <""!O46fW,"6U?M8nno~ "CtĕjlJuqY!"TDrU0P-Māj=Nbp壾C nߜ>zDd0looi90FUчЫ淯ߎX zYYzoFq{k%7+_?>;q7Uբ5lfR&C* \CMDs5LU4~ӘrJcf$J9rU!]]q,=Cmfuu~re4NݡTCTX8Hsћ `קچ M80ZJ9!YQghc tg6on~T‰7qӯ~v򦨞j9-)̕@͘bX䝏9D?lvu}>}1|\p) c )zk}ߙ0@QT2eP @9犟 ~XTs6DC"fONNn>kNݘ)3͖ڶ=_Y;5-x ~䪓r.(HP9o훷M'x DtQghj_zꃀ<ŏ/2umraF2~Yo5Pv9cGfh&e*@DLs %3ČMgY[Wmw]7c?NS,D.RQD1̚!|1f]]i )!8ٹY<96 "sMBH 8c!;jjZsl)yprEUST,GFK&aۜ!j1b7kg?{՛O_vdǔS6գG#fG14AXGHV8ٰ뉘m*`F!L6˜R?(v,TшBpqVqVYj39\]a}\DԴ8%Ԑ8j*wU?x33]nKPv;o0oyX!{7Uj`Ήa7l0YA Q !%2wW7yD?YJx4F_~ waG^.>_y}RT׿>U!))Эn. M%ϨdJ`(Ƕj*ɠWR T1)8 4Nؑ ̆\ձnT̬9jz]oǮW5Ė;v}=!90q"$&!s\ŀ)pr±!n{ yd&fQA(!1'55_p0,?o0qًzrʫG}mjn] ЀIJj̄G?7>|K LUٕ'a1"!n8\wrB2(e̎i3)zfdPQe52`BB&` @Y-""]Sa|g À_\Nr9Oÿ'v Uag3G BbvwLYu5<݋qs *K9P^-#}_zq?A眹0 |ȅ@hzЍjpZ,K")M̘90Lj0PU;rCqCYϗMC1< iWDdٖY1UP#RAɰ%m3ȤjD0x"# 3@b%ҩ$vUtҜXtjW?}^5+5BDŽo^9S.x 4#,V,O.f}*K_Y}dyZ5MS!3ڼ٪m& 1D6snn3R7j{݅q{;K1 ?_~}wG 0\t|+_~f5mj1e)~$"D3 LXpG@X>:g IO{fdz*u1(qw]id@r>8GQ 䂏uUW>`6v[%%B)nٽv1y ޣZR'yꝣPEWv,U! Ϛmi" èIXظ:󙥻mwgpsq/ 7z(LjscDŽ`)/c G* 4$RF%D<f+BVrNnFf7 S"P$vu21iZl5uӢwJdz n^f1S#CUMiQh"Vȡn_>B U]zJ@ov)G$0 #Uj*:B]4u_wWo 1x7llΆd 򗘜|7^Y?}_7/ތ>&qw/op}rV*~kGʇI1OǚU0ݼs;*=Fe(Ď_;;U<Хv-4#VUb́?9_΃ DDTUM\AΌ b 34f*+_Bf'ݡ d>Sv5 ɁeD8YSڶ/fvLvK:TDN0G/o! je4vC irn\_;mѱYE 8(C&3\_zżE,ILAtǸjڹwflnᙂa)@Dblq:*`U]g]}Cwa2vG'bLhZUwcU CFDADhRq'9Vub Sqa86_㋗UӮ'4sQhVQ<9qD%hٛ_rٳ_UvZ. ( ziR&Fѓ2#TR`NX`!FD~l0SS3 0V@2Mu}3o53O.N\_zo} ='ȥX6e"$R0.iR1D}?/˱n9Ԙ|mnH//9`Y:ŋT0RmvEK ޡD[}dp~! KK>컦m| ]T0ZvN&Y  }{u]hk} jN'Q tDY]!$nhE_"j+$e}!|zqG_99?u޼x͠93Y~ hN <c1`T;'ыId#@Sy_?F`bf[E3q!00s@̎wHuhv|" jrG,Od(e2"bG̼ -DJi*aČL/P1*YmWU{.fU]fsqqfYV|rbTK涼 $ !BgV4t$Wzl" "Z@۫qȄc(v#1@4nQO#32%ҹR0G?=|K?ÿ[3g2a=nXzрuv).z_GoDHaX MUR25" +?К\ҾȈZJ&$Fd2R#IDcX-3՛0t7W7ӘT4imn7is3qEH6=lֻ[d4Osz;u޹86psDɊˬ LcL֛vF3%Ø&T?'S y팄Ɏ"eO elO р@QT M- a!XV)1Q3kgn{X.f(j`rb`YkL DVx4`hfZpj$l~CyY>`VPeٸgfwjTT (}/b~RU 7B`+> Ɵy} /͗_9 } i\"1'?5y,EK.YI/*y .x稴-CB ޱLn^:\``*Yr6#g0NU[/OWbMsaf ߮7| M1:@23FDQClfyfFǒ3"!jٴՔSa=b%XVzTD(s IDAT ":`RS@`(RFofse"(.xUD 1$C #"BN*Y  Kݕ' DlhljYl `J{ocYva};DPYY$E`F bb?bn[$wЍF[Mb"))p^kùU/b'@&n"yci MU[YM5Df/WO,UߏcRaJrEqjLQKǒ}YGПBݿwR9ƺZ]hvDh*/ll#Kw #!! F;YvVuKX+ᲁ d` ,x867~R3a9q'o={a8wݱ˺*ˬ8N]ݐYm޵XZUN3$se FKT4MQU,kV8c?MCWa͋v=mJ1㲄fJ)'0d$yɘpnԆ)U]U3f "j|hjLH I Lwc{BbsY]l瘮c?0)Y0BfC3PCN#m6t>^mj)ŔOxllY?y+jc{<&M1*4K{hq6WbGYOv?4O}Xe. )cA lcQPaF &Wy\3vڄ@jiL.ϋs3%M";q H"6YU[OʺL"b"K[XXjHK9&{.8\@Ce h@(r@T!vcJlI1CtqQͼG3QM˗aD*jIPDTMcT,f4EQ8>($x)/q(aN1^]i ]l!pY*˝Ġkݩ#sn>s|.k_|;lD*˃wDJ%j]$1g$ E،`\1 M'O7upj["^n2wO~&\TyՔUSfyb"oӮ!Y̐c_BTs6OcL㡅aqNq̋ 4<0'3˫兰%K< D4t8֓'>biG&d_;Dh<,9,93C]HWD DɆ{ EA 1];MSVf8/ 75LyvLlG$R(D4d4NcYIJJ DQ )%](sطǶs!.fj`f$M ^Ձa84wͺiU{M2b{8GϞu M(;([ aמnvwyE]nmU ֑蹪5h !z(b=9n!f**_aP=J"'աNs8F"ǾzȭW8ӈg! yh!Tsiǣ!4#SJsI2&4qDd`jVd!Ӽ6KHcw8ʲcLyVN8ۦLU/=xxwxvy^˦)( !3XlWvq8NtlZouZ+x tiK&VyGI@q9]\^JY6>@MQo/H;(/} @M"K5,d__#}$3PUR%yCt3!F58/fndSIeS!/.fN]WϷϫSXȽ@FXbI?GjHZ 8Dg]fKN ƮWQS@Z38oF"Nw< Ɔ& tRE]V׎iΒ n*"!NSWOM͖$R`2{#] &L^g"D==uϐbψfbjH?%qX&Gz;}p*D=GFX,Kف wbި.O0,/ɟwKz8uͪ."q$oO7ea"?HT" d3v!!*Н>Ǘ[A'( &bdEmWqg2t3y@0HiN.3]xw bL)w7CUsC[A߿ ?3,u}E40B t]ܟ(, 3+ͱc$@T2*_nժdbcI,yl6ڟ4OSpWU L_r}E;UYXWmǯ>zo?]kF $QE t)xǡnC׏@Q:\x5jx}-Ps.4UQ0]?t9SȊLfMib64 @ՔA821B`D(K(Ibf@0Aw꽿KFDK X͖=&Y|Pj~tf%b/29q/{ݿ9R@HCi=ϧ+SUUf69n׫*weUL`e3iD!uETHlt'5`DJIEbL<uSV;@f"VS(r#œGO 3h:8ȱRͫ^z{yjdeޫ %$ILbqpڷ0,MT{ !{\6D7qXVyYV,JJFfXM0iwu+ʢެUS6EQ;`2粒$Sfy1"u;&Eo/$&3Fd [*d>4C@ɷ}>a_US.]( @@DDIK1?k3sƘA߿@7~:M! Y@0nk@ȈEYUU5`ƥujD%cEU:9uK632[(5a&)ϊEߵ>v4͈Ջ+ϝF4'YZ̈*[ Kg1ZO=x%vWwǗ>Cw;C;NLH,UL14 ;wa޵eSպL:d><".Mbb&bdYYzӡ?c3ج.ESs_W TlSS dnRDi,yvya(W y U'UQ͒3:U@HjL@x 0O`V \,i"GyR0:7`AW.V+L !,McFf~}?5ݻΗPS`3/LHЀla118()Ϩz}b?ƙu9ѐg\:Ss_T 2u}DB4UbpC D@hɸ)bw{7).YÊjlN!2&$KG m=SoocInwڜS"LI=SX5~>ˆS?Ȧ2Oy]쎻=VUjff438~xꏧ5BݔzpvMT}VLH EVy]at<9nU]햂EC^՘y$: ,ѱbq]j4Q^U&x ɥO.>z|Yb@Sh18L> s8"wYrea3X !z3WMD3LQeaLxJQChhoK8u`pd{y DAlcPPfnOxs5ۿXޝ_I+<+9]?:S&YѶ$kff>m$nhda)E *뛫7zS.նF0B14C䉢jDم; L~{\>'$D3&ZWLthwWWl;?Զ$s TLGR&J dEPx28t{s{E"kMn|!#¹;fD"i4$3Pj+PRntp:ͪTh Jt$Y3sYYshF3sYИ t$b*f꙲80#=~ʜ~/-:jXTM})U== B 4F4E,~޻'a"? _&xǔc2Ztnk;YZR’yB!*ʺΥa:vqG̜vwsC7,֍!F IDATދhR4Ȝ41(v1axVP.OYF}sIU0d`8dba"Զé꺹X+-)QL(j\3SQՅd:ǦFfns ,*ͤlE~- `sYMDŲ4@@Qzџ|$I@|*M⃸?gXofyF^rD{Emc{n{5)@pf3. 2y&Ӑn۷i"<Ņ(0{QUW9yT)B QPTEMВWfp\Rt~(ҷn8N K~ؼ t8է.\>zf&H]u`F`ITH M#<aW!MXsUR^8<_Q L- BVEYiJ*>BqNppcoC6O6|EĦ|ceɨ^ոu5Ss}q/eoNjs$ yg0{b"v&Bޤ},~rk#1NBlj7 ٬MC̪龽= 2;vN61%8{i<@0R^P/_}>l9_̐¹+x_ˈhg&@0%~tܵDPs O_A_)QVP"L$'?ʺjGxh4m+$R53%aQ y"iOáuScd` i0)x7clJr Dc<:_ru.blan8b7/׏=Y_n7U$")Ɣ"e:D<O~MXuL!`L)$3B H =-j˳|: tlWG4mo__nqʫ,+BӔvN/u8clPOy'xg yaj?qR6U}W%yݱϯ.8Zl)%"$p{ڬ>eU8%Ɣ$:pwWTud,q?fG5Q-jd\QE%%#b0@H#7^>p"`p{usxh"E(bs E~zq6OMMYn$ )bs~.2,D11sK =S(3&wcL)1y]6zUnB(.=Sr!yG I"HTSC"±7w4h{wo.rU10#$ƅ&9>x}?qywZVS @ETܒ`,cѺ.3F ?'j{UM1ҙyS5?9W .EU6+vL&EaO=bUScIjdY)-ѓhJD"$SevH jK͈0*hV,E^q,l&f`HR23В$2uVu7>fXUY?Z+1L&Wyȟ]O~GϞu PbO'BU㟩;um֥{=yiMIË}>lMzBSCg.مfDw7;r>8s<;Q VE5in>{[%u2&3O䐗 T$U)w禣9 s<,ؒ s&,3P?3ڜ&ǒ߷.7Y.Z#C_{62+zeF$TUU1n<0YUFqsE^Y Qhe dLDNSw2QR@< Ĕ@\2*ȀFl~ta} k؜f=T/޺x12":DǸPG!"03"n^mSw^/B_>>D>~L@@F߷8˩,/4ń㗷/zx۬j!c65bpHk]B8/ID- 1s930A`"MնԟxL\Yc hlh&ιqP4㰿C@nq٨bRv<,2rU0L"48Ta Q 4uĘPT C E2<]m6c?*~>1]yfnd .3::a e!Գj6N//vW_}ly^QU"Cƒ=(1!B LY1ADx F$X,_niahM;=X뫗}ay?D":9?cu]KTeޅ(DRBDC!wO[" EE`{i6t1Q^p4@nD, 0.w~O>wP?ve&2gz5qZ}#|T6!cBO۽4G`v/1g󲬪9sT!_*Kwܶm a}b98=Rz"hB6@Tmfz=/ȜK=tJuGޓ>{ !XS } cQ,ApbalGyY]V]TSzTSBJ_j3riAn>SwovuH$b{he|ryXϯ}'eԜ"/// ɌA DD (UV~^,/zx~hg7Csxg,ՠ˓u8-g>9y(D?F#fDU$jVębPUD*a{-yYfq:@H$hۛ|'dT4cv?'In(ňDSj"' )(î&$[Ͻ7SslM}1 !l}á=NWEcSMU͌ ML"w=Ff;fLbd1\85DC 9!ً'O];`z@wM=g'-|9pqĴVi x<8 rVf q&T9"b"SS35ܢ.JԘem;ØEQW弚E QgL1-s8 dQ7z8,VWz XţK0bV"sepȨQjw8O Ӿ=onߙ#$/.<gNOVLfLص.a<'_O?rLL`v{u|٧'{,q^oU s,(3Yz[2jQ)^ߤo|}˕ό1AwsgU3m-FJWU3Q dFF`DfeY _<ae`bI.5J{Z&uX]Q;Uz?k+banqI''eUuu5YL9DB4V\! JpUuXZеLؐ R'6Cyyv;9NrHĨHj PMD= ЏAeAt\@>_$m^ 1D 9̌D|Ǩ"DHHwflcDlDB̘Ƕۏ8h Yn"de^̋CיhVfCc`W/;:':UCnIDD&  Ve 㓟k[|7Dc2s|Es3;c(bXgueDt}dHhZqOlgbN !hRd$ ٬:;_m7]YYեg`JFEi%M88 */K )nx|D̾ȋ20c?.gx^.U]U':n)Tf1JsPS60`fΝ\/j웆gLD5%#B`p\e)yTa4%ALyw?xNjy*=G@ݾ71ġ;X8Ԩ ⨪ihHx&"245YL"HLjƄvh0=WO~򋳷.eތ3iy ;jvȂ?'}BqzwJ棯y (%8" ŋmb^%0_奔&׿%ˊlFHҒQ4]ݖuY&@ B 0E!1))##!$, 3ϊcIa]W٬"R9Qbf !J7 H, 2E}q(aq]_ﮮ|ZTU9<c Q&b6Mó%Bu!4dص6 3Eo!C3?dY6_, +0O1R̢!i/ߜ 11n״M|VEB(Ode%îoQ/G1Z%vRPRF<31EԾ0w O$жe94`j2t]QT6O~}~vrq3VTeh8juQE,Q/͟>tK_6y|3}f H%9֏HL`ERbcRK}/^ߤ~K$Z-V.bj%nDBCSU4T1DK?Li/ D g?C86CtC32+:+L8@Ȉ`h"EEQa R(c8 vuqrvRb>nh4HZMv 6 (;nCemˋKWgp lfqUI3I;tW1!9ư.Wjy61y'Γg2{tT* cTU*BS1$];p!0Qb>}9_ uM̀ Cne{g1;> c]s|VU,O֖/~Ou`w9ү& ކOTAғ O yt=Kac+/`{}?'8y5+Z4BŸ6QYeQ8.gul&j|?jh@DStT6j G>l]Y<,3(a7j*<˝E3#@.BTUEF vw廿۳ 1 UPUI(̫bpɯ>M~ߔr6]ߴyYj!)ʍ(5F, CN=aooxG_jBlf7ϟMS3y"5\s. DT*9jD ª @UiXJi]}TxZGe|y3 C?vDU4lw68n^\s11FA#\d',$v\4ˋm{ykDe. 'ÑAR$Ud@(d3 n5_pYc^! +RP E`&}Al6F_Y7ba:?='UU B&!"Tޙv,E#NA&9Jmn #*ŬV$fws_fӓjZה{""d47vk[77?M3)4c L}a Gp~M~VfYVEgQMрv틗Y3]ӎ!K# HHrP Ei~c{oFc0j캱rVNW iU5C&"<",#}1E'MUE^Ʊl/񣇏._jz}l|Ɔu]EJʼnL~; 1XFϖlݸ}rwuS.ɉ[LDIaP-iL,A{tsĘ2/*wZF@19б ¡'˺.YDd;pYe:Cb2"rꂚ$-j0 @`MZhQ, )Bawl.Ԣ80|ӟyA\,}Q@L@;,JgD1AM3OYQq8 C3gDhN5j #L۫١nctp8 0#cjo}!/ b Qamown6=X_fEƎ]8Kh;Mm7!a߼!Cn1z1g'+t.H  ءT6B{86]6D1SQc"! DeDFt0ƹ$f ~z>W$"0ns҂YT"ҍ* &,*G4fdQ$cfDP/Y ]?!Ĕe EE##Α3U(;Ma膺.OWjLg>͓4cz*= #7헊ȴij},,C"J(~dc|`2ͫ?i="WTD Jxa5+Ud vC5[U"EQRg)9Β& Bo-.1{8_,H}T11S JePH+`HeB r!cowEf|]Utw7_gO~ËEDUY8*Ĩ@8z }ah]ӝ<,8;vۧ?e9E2TMѱ"YMMG^0&겈R25wicS`˲\Mq5۟{Z,զƱZ,h$Qa 194P@Ph @)pnB9 EMsCK荈ݍԈ@#ơm à/Ζgz@00{>9/5yNP#Sޠ7ýtY?_of^_'?X3;fA AzzV훮(\ (@!<a“co/nO.VujDF_h7 "ijPz5d4`f(HQڄѻgjJɅbEWggfןˋG ,Ga1*cw;4R8fV2wZ=-/gndU."bA$C4ia HC3Qac"MjT(lQK3P@PMJgP1AY!ZD0=$gf$d 5UQ8( _` B'`@)h9Li71$x|8YUFSkwJdMmڝ1>XH-y vD$HVAbV񃈯j{q?ok_g8*2c v5M״U=CC:rڲZSeRddL`uD4!f/3W.np h.sb`2Bur C?l>Ov|ty}f1F$Tcn?}vr~zrZ1,n7Y4L11˲H>qq^.l݋j=/ϗj&nQ`"k`u$Md$BW}s~P41D@؅X) ѦE`B@N70D34FD4" Q) QDBIzBBu`4}wq]?<=[ y}[?}/tǦgރiͦ@bV;BS-K  :%Tk!璩 )q$UzQTlegfv) ƩwJ`Ǟ)CT[M }^3fFϳ"#_8 xz!MLX/]\v_}?8on7pH.gK_0LnhaVqRMr6.ͦll~j$ ]+ߊ!BnNi M(%! C>\]m֗@~j3)#HhÄ "d!B9Hc(M焷氯^NLa:9g&͑>~4}G|`G](<F0J]Ӎ,M%M ʼnc8%Ό.= !i,sI  T#k",!2:90a@$_ȌD7/^6^8Et)*@U RX|?yCF^];èEQzWsILSezASBgϳ(2/>P)P!295Ceb.la}\n7w{ ,v;&.>00Q0AT" :6JI5! c}^a򬜯A)-y*`Ta"MmҠ8_OO˧?1kh4UDl?@wx/UEg2>/6aq7)cj݋ySTQfTG8 ޅ8jQ{tH ( d2 ȂS@3K@ mͮ_\sDSSZH0SPcBta0= A~X]E UܣϽqCbHz@Ą-eEQOW|i(wb} 1 `hYE!Mۍv̂Kug@*kqQ,fj1˺"&"UcNTgqڮ@0p8݃Ͻ[danyrj~g#! +D$f&nT+Ȃ"jߏcYQBٵ8ƾEQ͗N8/:`01}Ȝ]˪+ğGSO&9MN㒑&XǴ9mx zd^]M)0~>W/*&_TՂKtމ;.YЋD閌Ď1(Q2H`k1b]a5}ڠb9Y ))"2!F2F tAZ27+d2p`2!!9"F61ܧ*!m0,N=<} 3RHH`e2tp }Ui$0ScU|ow歇\;#\YRRJ!9D"TK*MvA2a 媪p(}qir6/rDŽq žՐ0"N12Neum^ρ(Y ͌g@[dQ%!tڶ5ŪNV̥_{?w'}=ﶘ,djmSEp7M=5YKԚ^DMZꞞT_k_*K T@Z*+v("C?VEqZĠB)G#9b&h E`DIUTUYT8+7C벞EQUNfH3,ZJ&d4py`CѰnLz=8[,fܛa ǘe;aRx4IlQض\.@&nD"g2Rj3]{06vp/J򒙙dSAU JL)!FȈHB7ͬʽb?8Y"o;|{syϳڐv;D,jTKw8@LD @(J,:W|'נ4M0׎5n+C@tY(n_\|lgC3M)u!›4QK_^z9恨*?ƿ ;9}t^(*exGKeڊ1Umj4# A12S@#Sh0X0U!XKy1mGQyN!ԘˢbQfs[>.J}'T'v4F!Qb aļɔbL iȦqYr2t"2g(*ĩ;bf0lV M˿ m( G9LVDGQz=9l~w+N-sP}yUq%AAxh˲,sy=?[ r&42` 4@% u8;D|6,";Cw]狺YY" @L)99F1ݦz.D^^\]1ȦsƶGd1E1J8<~^8 [*0c꣪ihH >Z 1gDQy>6ALTLԺ;Ͷ׫z,ge犆h_/>_)uӯwXZjIV-WOC?6v?v~ึj:YZg?j'q0`'0.g< 8@̢ !q*{ tJ]T2sA2!z.VsZÐe5Tm76mǸٵ[U^dugGP9/LG&z;HK;bdU1Ӳ^W}n~(<~t*&|p:A$)HȈ󪶶۾?tYeesكZdfΑ!09q<*qy]Ϫ*8B~aڇJ5!'^R|'3v) 1$U1,NNqzlVol^ϛO~+[_xϳ9糠,TAA)ɨ *fh (`j@h8z@"s{& AP,C7Ō=[?7ì,DCgCHmZ纾,*ϫy*JQK oz DbQf|UvOO=|bVkb6Wpq6uM]34ˢ{erpzz$vČLE 0UDA4"f6PbrȤ؍& M-_f,^l>>UPY8$C%(iG 9HDOFBdB"& vl,)wAY B":]kv/o*YY5 IDATWFB?obӺ֝*\SSHv:e!?y+3rnV{B)ݚ*#2/~s0 y9}8]aYeu*(~*0"!)bHմJT#*)@c20 xh몚͙@co:rHE3ylYCVdȲf0J-ٙ"Z oA![-ONw٧Od}m/aX<|Pֳf[H(}&3ۦߵY]u1!*# # *J:CBKd2$" RJ8914X8v1ps[:l/l۬68p~~b"7틠0I7RtVt] >(@"Ҽ91ӣ{{nPV|(˲2{'.["{&wY0dl5NiaDBUnWWf䜚g u^g BDD7,ְ97朳ROl&ҙ~ %`0" 쉂`X=0 %բ&%lPjʌg{}oDT DFeVFfFF|wo}ko#ỏOޠWv-5nk& "\dBE ""FL@MgVpq@nZ#D# j:@W߿vv)z\Ζ :ilj9;ywƥXp>1,'KUܷ~w\7/b4@Fb0X~ AsH[L P d]G䘸im- x"diƽ[w,w)x9jqs`FA# (Ȣ6x@"Ѱ ^n2,T?,!!FѨLb$~"#bf>?>?jJkk ky_Ȗ:?** 6&D؂v~Ⴧ_1gmU QuL{?/+ꦬzXx8`벮0'g #u*/ 04I BS FV+2!s jjf>]-e>zoTme^ $:lS0*XBp.6ըQuqjj$l-F9hHڶrEcԲŶvPL@ȐI4H(gj>8+ 򙪘unM^mW8d.ԍiӥ?1/??\;O(tWs̃W~7?__>˿fuX.Z& FNϦo}G|xĔ +:@$hfS(n^8¶)/f6Joww  ЃQEE X5A&6:ɪn޿=w}33.dQ mt`E'wO`$4-;D[Iɻ=2C3$b9μsYU4kHtI j>Ͼeg7W Wg0UMfңqz}/ϗpDLdVE ?$H:ߪTl1TUU;!es\.V̓û\щQLLB !4UUU Av)07mQ @Ub <.'f.놑gU\wF2CbT@fb&F@@ жͪ:|U\̑X{\X T0IB]-eUPWUS woEĉƮ ϼVEUef&'*DA,(ֵI4)zdC$ѴU;E30dmfə wNE6x?|;^Ba#m I> W\u]EJIaI5|t7>/g?-$m'(k _ӭ貶X๧n]UmЁ3"1f3OΦyF'f,Pd3I L14e F;7'Sw~_tK:H5ڦnU,CDN{umVeն"X[N;fĊDd='01HmuJ%=iDfONʺhM;`vMei-g `JMmfy< UXZn>u0Km1'ãжet]`HqL3miw;3 !6 5@12QӳN9ǝN' 0 7_}oprNm θn1&KaŮnJ6 ʫ pO%3{G/o};??1M%_BJQUcBBC"f?޻sAy]y!"x1_s. MT D,1)+ 9!xCݼyw dHXHGZ@g.\ebX4Uɏʼٽ7u~]hmMvzv]"Xڶ4i3PJh"J IyowK {M`c9,cvkOr2DdZW LL"hlBKE^HT-э{TŨ!yg@HKU1))R!I,Q(QqSF3P@TUb(|:-'x0wGNe"/?xb; kh6\բ/mM )|5d;b?["҃^zٷӈi^~`җLUԘSX$sĻ<3aɜtY6Ms!g4 HEP-,b[Q .1d6]߽99ZQ`Dj``Ygy沼PUibO~ީs~g5wjZeh,τQBL\c&wsM"9 LS,yE"`|89st󩔐qR5.1"3[2 QUb"}6y昋n-Vlg8 @@@@D"MS!#w=3FY[B*RSVYhý8>˓EW!1*9hU/^7*; T"lTWbb>£ p).y/-oK_'5z^| 1Rbu6'H#yJQSCDC@d63# JPuG|wF" ( rjLBA$/D$y.ںVe;3£zXgQQCTj ymϊTK"pL R>Ϋr2g؃ UGH@ETR"..Wbzt謁jM3s w`Q%8 3c$MP@h 1iv~>Oi`4tgko~UPI&u2+Gʵ^;vگX%(J/[ D3G/ElOGdҗbQ ќ#GởxO}yIN]5'34=m``V Tb*ɹH"&!a0N?JjZe:I(n`Dm(SESVn]avvH w켘(h4I}JD(hǺ,CdBdKhm uY"p#HtǍQ n@.KH-D$ {}S Qi n1Ά{uU {}*Mj uQ:oJh͈ $D |0:*Dk&!'nM&*>%R 7>,t!2Sl A|=|p :W׾k.]m= _xǞmgu۴{6A799$L9".Z!,rλ##DNON/Rۻ{{`XZU $*Q!0"IƦd:_,dgGȳ1^5X{:Di3GbjZc]yYU!c D_=>Y{GÃ3y<_Oȥ}C2K*trNWz\7 ^QZB\r4pi:\I/T}>"vf~WK_aul@֍?}4(,P֕uid#2iZ, wv>l37vuYlC&i0! MUFDr:?}|\VN㋂GF"B`f@,d#d&r. Q"&̧hgwSNغP jhdC"DGloUʺj8c$xGGc3E轟_{7@DSkEBb`g!!:1TaUUǧ4u{ݑ!bgt~|~㫶IuƔh:Py[qݬICHW6.THM:?Ւ^~ٷO/~K_bRKH c_;c/7Ј4uӦ`#$5UpTnz5C gS`PnZjp3: Mh(jj*Sj؂*!il(c{W{g#FL;ȆI!2S3G"@[jY6Uj.s|sx  ǎ@gPC[媬r<,n>T4#bTPz(՝р@-`h&bh \wnHlfĖL >?~rˋ{/>w:0r^k)Ot)ׯHwYe̚٬iKV#1>EЬ/=z򣗷-YŴo( ?3P3w=(:Ef漩Hhw;38?9[oFy^AS5W˦iCjwvn;#jcTLuI`SΥZ.Cj,eYޘ< 33;b:vrJR T MpDA\ A@'fa9=9NVλɌozqq1cIE-Y@hg35W5q@Hh`'bruY=y''7Q*gӓ{>@UC4UVuUW1u T5U]V6P0Uhe\IѺn&ggyDAzY7/ݬD좡]:^-{tNY9@>~ n5Zo֘(("Z"j*OnnLicvM*i~m5'3F!JGQ)mkw>{Հ k\N`g4B5UY^] vGÝ1!["\p4SQs櫿d_P6 J5S.j( Rv\Mw~Ly\A<o{5UU% IDAT7_xNۺ^uWd>2QW|ܿs<7E4uVk 1yVwp_zUݸHT|rwGYڢDaUuTea( {"fYXMLQ`d1{O?xkw5 ^4IcRچjo?$yM#\"Q4i|G[vFz~?3;W-DŽH _x mfX$u$Cr!3$MA-kA4 b*cj˦=}p4ǻyQUN'N;N$TLPІ6Y-!F09|/rrO d nфnۏ"@L!75(1GEp,3Y'+Tt2~۷ ޑIlʵ""Rf̫9?6޺}\ުhBDI2C?w+'xӯ@* 9~="7ɐEt}Nku؛8X0U0 ֦-o|cӟBǯ[vr#I 5%۝;7ܿcHu,&yYrƽ^ƭ:J#ԈސJ4QBt vUgy7劉{-{3:0CHOh6-f5ИvN7̩Ahm[<\S7"DDLekhT4TU G` U{sg&sώRfض9^֯ʪZNq@.*0AT u@t>L.O| #{^{yЬ͢k7UGYk+x߼u?Z>xٷ8|++_/|cO1^yλkf]q|EDwo;d:zx0ڻyw}TmTMO) L c "mm!2BdwgD ں:vv;bBҤLY0LH,b&MPmS.ڪ"_F=d{!ưMyz3C IFDα?U&b2HcN..fO'w}d~OW>Hl/|+__|/[c6zB\ ̚=uwZweamE&v!xTh=*J 3hUZx"8=L~qQ@jD"DŽQT0 Zl'bوsHԶ-^;TEE:df40LzBd 4r(E0N};TYruq~x4 ImlX޹}g}.wAӷ_{'ͷMU2 ͒t=lGpjظ&>| z|Rٕ9+tʩ\@b5m`xQYu`<n^ zUUbct`c@Pq׋md89=?~r1~7u$!"KL53IfEBv:Yo`HYSƸV$w+%Hӈ؇lo=^ץtoX ڨH 68rw cAS*f1[Fn?sS3yo|7>Ll$QMI3Jnz\U)nk7}9 o[";3q2c43v9Z.˓3㾚r9[Q4Xt'lF$&dLP Z+vn*F`<\o'NHB1ph'@?(cѶ ̓^wg79Ťb$I B*FAvD׏`H(lmfmڦV^Ht29Oh#N̺ ٬;jbirz~qzrDO}ܾRw_>$|\0"V MT(a:5ePc^z7_Ϳwob;/.1V~JiI7#SDL'vFot)6 oO/Rv;_/?GsLm3A'6D7 m+7菆;;nsYu UC*,+,x7ʻ/ Y" f`M HR,nCim̆$@13H4M5hfb⪪fU.& ;nwS6?q9J`\O<>uw(!ֱj8}SOq"e/NNf v稪 1]V`HK抪|k6[wn0{3TEAru1wBhΐHDQ׍j`ZVQдl1e޽xg}C`?kp=rFjf.ʧ4 * . Zgξ˝G[o}ۯw7j{2sd:]_wǃ,/1T3BAu)X4A$U(իrY>::=-r Q2" FBMD53)!ڷ3s !DwAMBxrr69`V1rC`f*`&DЄxg*'ݺqxYTT@}uCຠ jf*x%ĄedUuf ^޲1O_ƿV'y!$vi[OATրz۷w'?~*Ņ$6|4M@@v1_:yWDY#&9PȀDTOj1~#J܅1qjCF#%aؤ0LTS3Ͻc"b&"3DjB 1@b! D y݈Lf2>z͛X_}#տLIaai70McJnxUR `+fOl3 pޫwNL^^d'{n3UZM^e??nuS>So( t6[8We+7r^3#9#hd1a#BIj۰^͛`A Ah&9D6"0#bXPdY̎}xν;#]<u221D"2pDк/f'8J oFCF2$@Boߜ=9Yۗ`d:MvR5UUST6nv-b /0T<|[dv׿.ԫr(2ƨDNxxGF{~Ƹ3Nf&>?ޏ>s ޙOgg~ld DM,w$'ێ5e5EER\Veԭ Դ1)'""TP13yFALUZZby t޳$r齏AI35lD&дA^Č voݸf2pz>7޾xrɌTo6=!J ͂=`/E1w)}[lO=~g߻qc_/_MeY4xux-*:]bt2K"?;֭üȵ ;>dw:BkXE8*h@1&}I[x<UTQ7x-׌\)bjQ"Clc ZEu|LO* YSE &ۦ"[5O~5~-1q-͵Ɍhf8MV/_j#>_|x;GჇѧ>oU5%>OD!M'nܹ(3dvqIطhO'O}cϴU4 zdV 5S ffjn 9FAb#шfL$HhhdhH D1"CTjFVqKVʬ*c ,ibY-ټ;GGq{B2OルwMr\\8SE$E[5oNz饗Ⱦl7onL~~;OeY~9/ "(Q魪2QA ??ߺ{EE!TTFFFdu u֍3 UQ  @F iE L Эu)æQ ,Dv}(9DmhgQD 6!m۬Eնqht;)3-q藮v5:TYbA1ײ nug?'o;)__OA}NARc):WW>d[;B:O~?~q}̙3H߈DR ޻ym#BB0BL"!QSWxg'7QDJD1PpH HDy"Ĥ.PC!BA(֌AC{zz]l2_MSA]f IE YÙYrW5"f#B@Zw~ٿW<|X;ry̨ xc3:h s\i+e&XbZ0-yDi9EQlH&SmCT;zucM<"‘g`޽>Yy]g_k[ot75 n$I$٢TLbkR5x25Kݍ<g2U?L9XmIȺڲ=uA-@s߽9 vObIIy~|*9vwgq:I82b!OLBFC$rIeĜx_,5:;ra6Ǿڨ e&UrueyV֯^3岜Nf>[??C ͩz7yIdm ekϣ3/K#+8p`۷utsl6,tY+.q}}k3K\vΟzs{8"mBo_RD]]ޡ!%6dJOȹf]|_wUN@,RYXJŹ(vH\I:"X5k9$BFİpd6/|jP|!:dd4ޫ/F/)|VkbqR֯䲆Dyy)5'_maKZ~柮T==Ͽe;؟zrsJyW;{VkqL&#ttV,AIupx;v̜ptJ9F.\tȆ[;)k=h2<EI# D dsa B)om6<TF( =Y!!QebjdTWmx7TZ7L2DjY2'T*Wj: wH ׮9yzʬj Uگ_  (=~ʫϛSo#V)Ue5T,2m I"joCVK奥Fahͺ(f1J3oy$7nT+8%;kM#ӢQ-|=S_5twv36իw!LI$Ripx`e*y;cHGug4+R&c7lٵgyF爘8}UJeUݸ~X+j1i`uD{+"DƊpt{±stmOꕜ ^E"!+rqeȭmLjmP&"N"rR:z;{:3 fD|^=IzSgCWo͝wt6qkwNO.nԁ ߫{o{xxZ9k_>_GQ\{)k+Nz9n s7oC$Һn9pV-WJb=ēVI$jCr3ҥYkBUcT5-Z'NIjkĤ)P[=) Y,')cBΨR\_(OD"(2bMZNJqZڵ|[HBPC*VNW5m%ftUfIҜ+ _zi7u`??xPWG>vxMZ}g6l9ֹ{>ju%-;-n~k_7Uiݤ+qm2,,EDvhtbr;\̇撖OMU8xkl$DbY}Ջde#BPpA(5&Lk)$1/U: ēni\%7 DIDAT3Bٺ`haiVWJq-V[6wvLFXޯŹ|f|fz R:Λ4wqs7 U{E}0=3O\nggQV+mަq/-ttu=vXH旂JdĤǗϝ1tӒje6o6][___CiĄ4hz܅$rYR==s[mg$ L.!52kXt>7c,RLX$$.86ZRjWZ2斗VR l#R&U=wjӜ=UoqWM|:<K`DtqL@@O>#ǍZ_owk 󶫳cM#gΝSȭΝ=TRcEI8tE'"Br-..?pWСM4}❤o:s[}\%"6꽣*5F9&Ycf\ĉJkz;8LT3z,,Z{֦L!8&CJoO9{4is9Cf 3UCl馕~ZG '?SO=D"vyRdݰadqaR\}=3g^^*fЖ_)?1ǎ#ǎň*e2Ќ:1V)ſ t'6gB"nOur;6bǖ{% A)QTy8BbLV'k &xXȋg*\ꋂd9Ho]EcR׮TūMJ 74miC#_o?7x≧PP6D!Xk)*=kGn^xw_#jkv(3=wߵR{Ͻ{Ï>rDH{o1m^[7nߺ!ZDީRY"\hp>T%1qC\TQ>'Q6ɵwںjڙsV+k7m1֦E8_:ÞPikIIWէvo۳  _Nzo{'ʕ>2D2|r]h-.'}#G/4OjUI~; ~#SSS.1SLkĈpzHݽDs7'k.P+FzWSvիuk@J/Kue XM6ͲHqaL!,0z칷ߜF#am>5 Rӂ"m5ًd@~}Qp\^{[E$kM𒸆RTjWt <~ɣe4 ]Ç?OLcTԈtrǍsbo۵mSzCԓ\GBXr.k=C'"V]{j.&cĈϏxm 7:uCI58/pkuttСCHvk}J_OIkxx!f֌5ʆ:<;s.x޽LMM}5l-?95?>kykZךZ\Ls];QsӿʶJRK{.,W^"c0ir$BVGļo-sZt;&JD!o.}$"ѱ{ŗJz-I\blo_K*y לg}7㦦&GG>G&LLNQ3Zֈ(L|ǎn_Bo֖rʺuCC/8mR&D6Xz3Ͳ0"bQjV (1s7VytAL@@pLyJ.c"kW_{[6_|Z ZFϿK"LN !!ݗoؤU wnLk r5+ף\֍l Q5JdL$ϼ}z2̤ZDo~y{A4333ytJLHT)T)LZ9=Mم;Yo=}{pG >r|Z` Li# kk=;Jevx5{9Wk(R1Al O,]SV1NZ# נ5{AHv?Sg e36c3ֆʕKk_n8RV҉g}H#G&&{ ^x#,!1>5m>@w{_CZae%.--գ|$44kZ գDޥ?O) %| ~d$+l6d#K+.qr#͍l𳟽v(ɻԫύ1bLIAX1oMcoN;q֪J%531D<666>ǧvX~i Z7n`DWU_x{jllLU_y;.ҎOܹw׮sKT J!ZC`l;B`6{<>>>22/|3/9N[ٵes'A{c??w~Nħ_;9w:"lE㉝օSֳS,w:4 ͿKXU[ngMߞ{^z};pS;55nԤ-;oݵ#8?s~d>LF@ p4@{ޏkJxॗ^㘈ҹQ&1l?J-n]bk%iż*ib#9=6:0б?%ITF]_z)_nk'1y=)Yk}q ?&&NM*1:xɞLwlg"[韽:J{ǚJ'NnߋX@ hllT>c@OW|; w; w@w@|@|; |; w; w@w@|@|;|; |; w@ w@w@|;|; |;|H_L#rIENDB`PKF-Pictures/100000000000021F0000021F83902772.pngPNG  IHDR_?) pHYs  tIME "[ IDATx]eu6Z|[UMR1/I$K$gflV$/FH @(Sny[bsk͏<} #GQ{oݻk9cbD;vW /;veǎ;vcǎ;vvٱcǎ;vvٱcǎ;رcǎ]vرcǎ]vرc.;vرcg;vرcg;vرˎ;veǎ;veǎ;vcǎ;vvٱcǎ;vvٱcǎ;رcǎ]vرcǎ]vرc.;vرcg;vرˎ;vرˎ;veǎ;vcǎ;vcǎ;vvٱcǎ;رcǎ;رcǎ~ vs;}7__;~3?D뗭Ї>e{o|볿ku۱ˎ;~>D?Bׯ_B@V@X}u]W7ps7{/掝]v9ūW/!/_</#"s"Ȥfuyܫͣ77Onn\u˺\:/BDg_7+cg;~嫗_bH @ 2{1| ^ֵk7533gRʣp{^<eazG(Rji<]>\ {F@ 1aw755>4<=t=nek0`__o~h.;vW^ÇϞ`jNB̈xw]/˺.c!GGx8RDs)RE!3 u75Cxz|:2}^֮@;1◿޻}}vcO ^??dREDA.9V_e]˺ͼ6L9" !)fDHH b&Df.„fnvs8>9ߜχujCm@/~eAwcǿx_}>ď^FBiTZ-pe?}rJ!!bDfP:,LBܒA@Df)#"< q)$jfaq<'77'盱} ]sUWcxC?_ޱˎx^HXJea0q)D#LPZRc93 !B"XڇvyrCƼ.]N".AD ,GȈ}c}{tĞcǿ.<NgV?R03_tL#M9N!bס:́ˑo8D ffb0BR6rBHn6lDZ"6="bvnǰpPe_/ 1ܻy:&0"&@qa?oc]v˗/Omx94M"E 7;Gs),̈y1 22"" #bazBb.n6ZW2᥵Z $LsRG!\e@J߽^뢦MjRSqukWˢCЮJLvٱˎx~?|`*֤)E "M ?.D[ a.DDQ<" >|x!0!>/uuվȦYVkZ2 fdYOBbbbD\ps@*#<"y`]uag.;v<^@/_z _|,afaai1ng4f+ 9賂ʻ REHHBoY%6ۊ&̇G U3@D$d&*R0R WNL _.,<"0[PH4H2kqu1z˲1T /oov%x嫗 YŅZk)wGC5oR6!B 3sC"|`&""\@B,'Rja6!`@  0H)R " ƀ pG~yGrwUSs5wǪSGDLRpYc{js{{.;~3xgψT[iZ<ԁ",4naa$@L(G9&safDBSa&b pB w} bK=2(( 0c("EH,H[!L-hCa?5eXJ!nf)1LULseǎ.A={ڟp<ӡZsAf!,""DOUL}q-BD#pFfͲm^X# 1¼U7p'"3'@<.9'DQ%"r*i3 !z-""Ӥf:F]t轫ڰR 3W/o˝]vgϟEӧO?|xݧ@"RjԒ."A:fفD*"2*RR{1\-LsL_ SxԚ},r2´uB:RnH|[eØԮj) ̕Ix[Y 0?00S "P>w ˎ?ٳgO=}aDJ+VDD cpێNb*EM:M-gA"F:#(]z"BDH0p:;gـ}l "q DR6$Te57fI =(g) },pATm㪜xd$My%^|KJ"Z9ΤCu0HZ=6IǓHz8<|6dߔ_D 030wf!4֫Glcb CDzˬ,G륯|>R$+fp ȕ`L"E KO VRu·]U-y7axDhDH$L%y-/wvvٱ O?O=ZkRSa.pjp<V@)Q~03Qڡny@p4 !1PbHc> TbKnSne("ájj&TFpUB&D`D`)p @\L猛[6"&FB7a&-UUPs Ԉ,Y&Ʀ 1tcu#..;vu O1ZMS#DF|'{WEXn[ѐZSȴ֪$\DX"U"="=[*4M1Rj-0Tus l5LQ݇ya[f#7R9,Ԇ鄉D2#=oAᖖf )BRj2A(RVԩF̿,DNmj@iL@2l#Sc:Lg,a]+f\@@G_=~[W 1;^FgsbnLZ.)])7W3f !Fx"3#2A`Z)K?/MHViNta@@R"$@"B,%뭜e1@$a#׽ 9kRe]FMXټŕph1;K@xիWW#YJm9SA8Q:6V$bkbd{:LL#HRZ+4RjRZ7w-p> c';LQ)hrdrJlv!YҲ[k-"LoC-{Au u\Ӌ~p@Ն{:r*'P^\!u"pwK=8t3`Ԑ7e^,5|Xh"Ę8z:>>ooNkS 7I\Ml갡ÖuYֵu^׾.koΧp8kkEx1X::.;~dۮGYjea̹6΄ǟISk&M7G룧j20C}]#`jSiV0޵1FHHHiB'D&R) *&])"LuÖ޻9"SMkчW7$`B ¥cSa{}_xȉQ*ÝVk<1bnC t};yGOޜNhfḁHm/-2ژe]׵1.%c.TJԩQ.M گu^.f׿n;ųω˗/_/_ Q-)s}0fBo DT.V3^IT8#rAAj nu]yYzZKHZp5c5N{ 0PPhLZ;Ke,C١IRAn˰C-P䫙Ցf!L؄h-£VjIuU7'VTk_{.DԄ䌆n̥5@/,XUUMU[t8<O2#3hk_U"Lm. Ӵ ӡHaa<#X2-\#ױ_uZ}k}m wv7_˗Lץp*R2j-Bs>!Ɇ3۶= ˖  ʚT5s3أzt8FD뺮<k0>Xy2Ez/1#v2K5g#/W\u \1(J_ί.;~2%^1}bRK-"EZKyx8HL^$OSk2ӡ>pS3 ZHf)E̽)"ڡF:"AƦA$Ĥ|~纗B497~*D`:1* X1mZ:ACet"s:?fe mzt>4ZK8SZ,}X9>9)RKdsfi#+ a7ûȎV "mc"*30U^)s`"9S[#caʝHPmh(-Ï) baNmd1Sg}i$b*%m:(Y*,{bf +Cuyj9A0;@/}Y CVG}]/ŝڻ_;7<2y /^ "RЦ6MϽz˼ѣ8MB|0ΧhxXmq7UU"!1`b^sC1:/ڗyYzcmps<pZst|EqpwK? 2 -Ӏ#R0@J0[4"- @C!!!m"e}vgI:4yZ #E,ǀzhyrcd$4假 `Mu!B崁Cf޳ta94Mi )3""a&!읩Y*Dz"\Ja@ B@ж!,@1sղgMy [_;kkswӧ):ߜC-UDac^u}h֞#ZMu}&`b7kvvWQx ׯjmx>77|w͏_:\k9On}ѓǧӡ*06RG9oG>,7pZ Өhy<(e_<_rm G0"B’8 pH@HXp+e=C3j|xLj(DBT Q*o$Gȵmy=:ac 5:e]u]һB!1 $hN0ntJUZgY͆yP'&4Gzϼ<ì?+'!0 0{L%E利f6@ ’o `j=5nPXJm7Cu‡ln-?HYUssCǍ1R __ag xYv^| Ц&6VXx}s_f.[ s+ئG77~"Zں9x#"f1;-a"nV <_2ϗ˲_y6p|r{x>r"33I-a6ؒ-A3٪uΈI U"mk=l;u]egr׻r]@J`\d"DpN0Z)M$GVURy˰e<-"&+cREJ)9gP}H5e]sI1R[Sjk0Gh{K@CeMWZ-Y -lcp w>TM@??>tSD/I|_xQD^}JXñMMӿ6&"sj.Ͻw۶zvm,Jyx:3o BK37eՖx s0G)/Cm^\:s_WW?O=~|s>Z:GPjb Vqf燍Y !ZjZTZ)PK9*pDs#u[#3zqkMjYXu]eNac}uǖHiRlE3U3CiW$j!_¦N@K4 a :( ӡ.N3Sf:Z.(pG!vҝx|}s_{W;cH9q:"64,b(T ,cFTD#x~}I.?K|!G77IJu/jف݆f!4GX.Zʡ֛x<֩0afy A>p>Imˆh= ?@̳H TCǛ>w_z3ny3<'!b׹w׮;3 `@O"7vhuB6EtU{d,I4Vї5[1tޗ:MRrrL$$"u#>\e^ Q;x"|]V!fA܉2th!>Ǜۛ㹵Zp!"o[n}U,E2*x줅Y;lC5wDD2xxM)L0es"L~?_O͏."|I9PJt:N)smd $ :,ctU0\1l]>jnMJͳHD%c }xۑD txtGFnTJmGyy>B¸u"!tȾ$ qfDzoBM-]mjS4th#5ZkMTm]Ԑ0k1ttͿ&D\Y;GǛVN a|'.;]̥HZッ]̀6P_7'wotU37Oy8HCH6TR(澘.|`JDvr].y5EZ`ws$B*<c@r!R"܈N^~^1"08&@Dr2ڒxf!p[Q fIBbJ)p,1{Ǹ󺮽wpeECi˲ 5,0))is.ƌ9$ec]ʻR  iPD \J-м1yQ>?/˽vy-r)20S \u])s;㱵VE:#jWd1_}~,~wr<"b  " :Mp@^!Mab&߷(\NO0!ҷ"[QqӢebCj-̵dF8`{Εµ1l]>:1x>!q}e].'SZcNq>TꪃJƅ=<>Z)H^{_(S)#u7olfm 1%sHuCa/IEuc(RHytsV)ñH%SGG"@m}Y-r%CxxZts>Ǜ糇rN+`B"Si%{9Cb0@'U} !sa-z]K 7:24"Y+!66(kRkY&&XGԊ"0ZNp85wp7:T} A$Ƃũ I$ML־u0W:EX2h-2@ 0k+ch_{i!~),M v[MH)@0QLͳnP70sBˁv`=GSjZX<֊[=O՜Z4kSug!epyvcLnӶҾѿ'?N_ضÚG2޶ mPnOTXpThREeY{$PPjjt~ts{sҨ߭RW0_NMtmJX"tMJtenTME@yiA1eH\م,]8iH-̥Y-\#HZ;"} b̅X< t``rd:20{Bٹ<]-pl ^zٷG^KAw~Ot:>E/|JfTxy>׻k.Ft=Zt=S[fAXʶ6'¦T3|F/c4 HD a:~z1ƩNNO?~/R4:k_zSkVZq}"v#*橈P>P|O~trM7o4n摏MjRT}>#4ܯ`b".Ti=a.q?Y7nS)zyږw}[U1Zks`Hb+&\@$"e|l0!R0|( EQl E56DK>AOZk1FU=/x(26ڣV_{F~HMZò\/TR"L9ChN w$)"Ax۶m}LUB`RJcN᩼C"E [?^usΈ(RLJZ*"%tLւ:+nv{,xǖRF7aNHzi"ǧ˺0cPB;Y=*";XVR "!Fp(,\Z93Kٜ jl=3ӟٗwk"Dl!֘}\.)Fdaa^ELucLUUue]^˲Va9M=a~yDAp+qQc`a\[ZZf11{x޻/:L1OD!V0pz eY(Kny1 F(*qdH:T=Z6t$>u_D@ ߆MRu]Zm ½8ѓc蘈L%OAulS=7D:ĮU=O۽c[vM@NCOI.G;lN]VlGh:TH1fjA |na("P31ܑ`FBRx :FDi dDB O># J%@(r(܄YW=oaQXHC.)y ?G?OK盱 oKׇ/% e$|קwO/~!9{v뻇%Fi9bL?s8M,.tB"DYcj)Z~1,..ZDic9Za^kY[i"Gcz]usi"4\ Er!"9ulw &NOMM0ϸ J"~>t+2z+c2K_t^/"#B)ei.K]Tp {)1ϙ[0Doph̭*ZZD&[jc&8ƼcD!lLD4K-2#1_-ٚ"^uY$"8sƲ`CnSu>pf>~ Ʌ8adi͜pI)qD8xOJ.c?mc! flх"R;Nnj5!."aHEZ.K[$0t>nFd["$lqP>1sHq0"dgyi8Nd%Dr~l}۶}u`K:tNګ |Nw;{cs&LJV>gJSa% "LqʒsgD_=aY./_}z-r$̌okXo5DTji)WG`dw< }e΁ReYjI0iX2m3;gb*K-K-k}>M`!,s9< p;mvU Zk8rA!UaF$'dg3G44Z KR9[ZI $bz^ "0,,."c۱MT 8%(oDFyvܜ ZEԥDZZҖryw})`8(#]jm9w>$&c-vN,fz \nv܏>}P|ӓ:sn3MHHL|B݄{;m#1VkDhf]_vۘx>{zλw+<}qj8R|>^{??9maw!a))b?_lx|u$rs 5jHRk%ʗCzg gs}L)sc>T Ku}^xxR} wF\khQ!7>ىQyo>[-ui#_.\ @gN=ADcaO`Y_Z&V=\ Pa RgYV֘I~?>GV)O0#94,@1y@vb#Nwqzy,(Հ\JTՁ9n:Y4p!VbD!$X !8s_4+" k-庬_|cC,`1?ft67J)HfIJ8d!P0s ֏O>ܷ{2Ͻ:nxp@bZ=~;zg&u?1wSo'd e ϩm+I] Xr)`Sp^X'CDDixa] IDAT" iڜ:pUT1>gcTwrsW_<ҖlGQ6g38tN bnR* <0S(apPi}qGsډ3 bd" *mI%JI< -:G:U Sh{x Av!wG$0ӈ\r"}ߎ8ct <96mڴ9-E)P8t UGXږKvy\/OahP$֧1f)&0X-<”K@6_mۏLBD˗|zYR2"`x9ϩGT:<\< F5fajs@DI>oǶvpOc1؎}?>Vb7}}nm#ܓK7ٿGy|3{CGX֋ ?#1Ɨ_~zZz^J"x#1t"\*ܳ9Ic D931 'N 톃CGs; _j{w>Rĥ0yزflN-Eֶ=\/w~o$EJ)n3?g ~B=GB_/ee懇l K- 眣G0 d]*"`0:=e,GmSb1s%B51İq۶_"~1UΡuz T* ๩GDץ-ւӦ[LF( Fdf}v1yy|> ZK)HA&lHތIM=  9Pem* >Sy$4J+-#zL솁 w6 AsbR w̆ b^z-K ϱ57(L r;[ !d*ouZ.DU}y;}۶naOKm>զl25Bfn'~#9HɽKKNV5aUեJRQ7Wϯ}N ʒ瞄1cwǧk^2N' ˯_c?y~0b,! ^ub B*"o = Aߪ| uPDJ)#"\j c1m~lc0"}~#" B%\)\,b1̍@+q.2"a}10A,RA-T5ܑdF~zN"u]jmYQpw"b` @ Hdn Rpa"OEH+1O*VR9{s؜=r Nظ,me]Kk;0Ι4O>ѻaV)1Hz>^.um^ <4c7p@#s|$R8fJ?GER >חmcLG"eZ^K]µR 11яޏ3 N?B_H\[1Y f>|̝TR0Ej00UeLc0$Fx.KW!pB4xkEHKeYI= #<`1GFm=%$Lā),/KkĘ"D8#;ԹqlclS@pmm}\k*DLή1\VJsHȀ (T?A`)LL" 2S},  @T3"q^4r& BjҖZ$JCƌF~?~>z3 @䀬YX@$ca!RdemK z'$ĄXYVڥ5A@~y֢NWT9]mOfC5c1L PVdYjK <3hn/^чfe?w_uumkkZ)4Ibr,DEmFʐM DJĜ0U5+Rx2'7۾}_}ޏȆ<ۅM5 @fCCÏ9uAa>0QnZ~>>N;-p5SUvE99_L{'{&Dx)g-Sm5 UwicLӶ^ ú|'m3a$tas;i@L4U=ZZ$$2s0tD%p~|?=!K>t.t5,o]6,Z,˲^,K[cCUtxf#  NjD,cs8A0ZmKk: <=<>RZro5f@a $2@Ԉ .`=|{Cȅ!W$L;#BYv/ۇv;L'X>-u~p}/cY @PKas1,lVY de$l:W>܏m1"yj7̜1vU.f~hk[WBΠ .@+__ϫw HED5ב#|?Hs>'3 .Z+Z[UJɖYV4+3)E TknfnVKmИzcmsj0Uq1֥Q %r}} 11y\k&LlNs:}x avvhlXN}" 5{cZe\.Ԕxno̡G4%c[N0)%3M 6.˜S,a!yǽw(G78{,D 0L"@Dݎ!Oۮ>C\>>,K%ckﮗ岶֘nmL$Ejm̜NIM4;$2m_f`Bf.,Ę*"H؋{$`G7_Gύb?@Ӊy+4;}+_:?Ϸdw?_ZmEJ~3ᅈm[>n۶2s+uu9ekh>"<+H@y'鴰{'%daq{W eYJ)rYV2KH_kC2:%EFxuFY0g|I͎So9TDC#k{թlٗH9/ݣֶ,ReYlyX9zvwc4w"* [ <5\j,0`<ܿHk UX[-".ʅ$3dYRD|Jݏͼ"Ehjj]GCq--[?FWnA0URRei38﷗mG70sP{\ևRYf?:\ZKM*"ΩaYeWbn 'D[UjD"BFDVRl}|}o~9UډdKM\OGxHn0av߶1ޏ_=>=]kif}@)AD?~yw7oF 0* "€_ukY"@KADscK"H氘S=X@qmGsƼ\k[K}w>^y9>=E33O }}"\%p)oyEKjʜF%SE"Td]겴Z&@pE?޾i mqriOz/pR_KVjNyK[Dl"*kۜ} wL(o"nzߏ{?> k,T& (DY&H,>ݶ}z8 l!w_T(Ti,R ÜSɵ.яgN@V+o ~Cwf)HhOo^ۖ(LUęj Z Kmmacjc9s3da)XQj zCD$o//?>o=._ /pԩ:OkIEm5㙵ՇLJsY,FHT"̜,F2o鉿Uc٧cc?6/eiDD s9cLr>V p;`-ƒujHBTp#bƌ%˼vm۷j:PZ-y@6L)B q9#7jg%Rx "XhaEcDɊHRD˻LJ3U=~O$Qy7sxe7YYcqǘ0#2f:~ ,HH ΡJpR\2 \)LX3"'L ZDd9& xՅ[2Iv?ޏCu=9J]IHSG~4$"buяPP.mYj,9f?Fߏ}o9__ ?>/q.f~EdU r\ 9x&$|D/ ;1IB'}$:"y&"mNX <"uwsr)EJ CǜNlc@C-'ؖOA-]&o "5`c0s1 2y8к.R!^{5Wty, #b@d4soq "4$җQZe>~ @`r1`23wsw"s$7c|mߎ0S} 7d&% h?yRxR FLv:T6LCoO_ VkBD)|S x۶mUDc i7&"| CRbKR)-Y0:9 5\Rqb,\k&&,!o*Lt (@1n=*U('yR bVe}&>D<__G-N# be˲VZM~ vSIJu\zabtK4B$фpXƘŒZh>Lޙ;ץ2G9 ^&ap Zk_><1Ð'=@v )ZRE zԡ6s=,狗e{||//ZfwO{蝙i=V i]9#3Rk+K[[8NΆ2[~fp@pJT1R)EJkNMĵRDK;g~N~"ۋTO; #ӉMjS*FN&PPx*&BG\g Gnv/mj3ˡJ]u]Zk$&Gut0/bZmrYk981wפGxyDB)L6 &~$P7i{c}L5`f´B @,,Z?Ǿ>lN77R ZrY.Kk $RrLts,֏M8cgL;{;Df"E 2 Xx㇏/۱1=o'#ǝ~R<_[zCБZ-E\8c9F:5CHHeY֧'wRq<>>Zc>~Kg߹bk$@ wf?g+^_KHSuU#&fbRؙJfFf:"(LX$ic1̋.3B(ZF&vp09>i%rFSk~B s3'_L1(f:bGN$\ f  HM@# IpDz!,]}& euq6JBȥr]D9@-YaOٌ 1z&b')M}tWf' !RoFW3¼Ҙ=<>apW#tDbBg*$]3>85Drk1CY$LJA  G_e]Zko}?~|nݧMe(GH$3/ZIIL*CU $m[5Oʃ<=kqa,<ֲ^.۶MCeYqyĿ8u]o'ğ}]~ oU^zq"S h'KjaHя1&b Hjk~`_MjL09sL؊x@Ӷ3bzy$pwU&f1!cHg2XOtxfN,gv74b""!5a1¦ IDAT #VVz桧&"ވ g}De/1Lɓt3uup3Vd:{9}>̂2XJy\=\"u("p)U!O3C; <* jsfPsBE&SܜWڭ?7_{#?nUdY;x\/KkDTXJ-mߏ}vcN33<_֋rQUG><12޷m 33 XOcMU_^o x~~A|;_ĵG !te,HW$'VDb.]4܏pwƘR^*uYx-P7u$09< ƷG -PR31c&,#Pg-s=|3 @<rA>e fj 3U Œp{ʥ5b{(Y 45s57 &!!1yD$$P"薎v@{ `Oę>fhPA=_4YLBdyVTWaJLo/y4U3!`!~XuYjD}RJd=@ 6c91z"jeb J77wyJ!4w A9NODkBq̾o?~޷>`+q"|_4)~o۱vP9ǧr"u߳M!>s"ޙiw$6&I{@)ev,˲qsʬ>$%^2ݔ&+E&h]Ŧ!JI!0l|Eͮ̈{5]I],P̈Ȉs1{t>m~!v@x~W{]n^Mݷmmk{N#U,e"Z$XJS !Fͤ:PϹU"ܲ'?h\5}uc{W3c42bE X"baNr p!އP<&$]f&&&@4n7rxY\u}o _EI2h6ua:z$@޷2Uđ0 /Z! N)kGDDMEo~ևkYf.y;>-Ӳ,fȔBd Qɞ%Zc14"Nq0%0C<4,UBZk-uf.ˆ?{˧fnEP2y*$:|ؾo}m۶۾#= ]/w햟].&^ۺ&O2Mnegsg"ՑkшH*3z>/}'Cx0w;|ߌ"TʗY snDm"tD'n@+\AJ%Dw@TK)ScUnևmc{ۺ;aN(T\* c'fI( PEzZʻɑ#%0pLLā\[O2:IJ2e}unHi{ǡUo뺭۾ .wݥ""4ƐZetL&]{G1FV Df1룷 `fs p"RAD6nLuwܯOO'?|;/Ku]~0qtR;NGx@1eD*LS-IҚH-BKLVH*Em#&чMsE k!ne ~rZN8M Bh0Ű9xLyyu&sy:*D1n۾7eƯ_j]M#jچIK)Ssvu7X 1 uHMD&kZ7ICGz:Z:Mu&6u"D&p7fL1{z\o5p[D[t>:ѡZhrEZXncFGy:DR (" -Iaa9U*MLmMّ Wƿ2@M )Ea޺zJ,-<}}pww4="^.wNUXW$b1'(†ƃU?jNDٿSt3J)WKDPV$r7"۶ 4UPCVso^fE2}?|7~>{)0/`N!i{8R^QDF1_!,["txs2eKR #2Da=W:QʑuܱE@twS" :{[ۖ/4τMRW)H6}4!D8a 70HYy}yX*nuF%*ʵ:ծFLSh[!ZB"An[۷>Zai*0_B T*OuN pyt-s"s,,LȈfD2J"BA[E{S4Fwbĵ.)O05SKw#| s=6F '$ `$7#uh1Bn۾my_ׯ,9"DdYeyZTnnfDcsVH,q(֞L,Hr%=]oOomjo|GzOOvsyRwADl|>ُR]@W>ϟρ!Cc"9j^Nu^H u"=d]nZm۶a%R9E r `B. t@L MnN4wӺnm"om{wNS){,H0\#/ #c xXET u T*{ba$@CT}( ܝOӼLĒX'ׂck7a:Mi"""}:re~u^gǘ ,+Z[ijZ20Ha[41K8Ȍ 2 [[#+OwRK-RϠa8:">a,t'N`\JDf@?)cOÉ0[֌;! ۺPu+~t}rw'wr^[k漢Rf";@ $@aqxi%:DtfD }]u[mu[=y$I}:RABo? =7E3_Eb\RHuL2w>R_~wwTHD<9Yu 0B<@2bpaI*3zm[mkjfXءHCG_7j)ۺnݮk)r]{Xkݻw^ݏQAD=k_O>яo/~ZM,̔cԏrۿgy4D8m#f3?|:"g1iEc48gBTv 'ᔀIm Bw]3tvb1sG$J|*u6Z:0z{w}t5j,߼kZVpi"PēUb  8Dއz  ؗy7wIjf8lan}ZZ#1-MS=U gPSa&Z]Gws11T TˋZ!SR1m۾RSR>}Mu1T#DNg"FJJг4}᪪έۦ-\ "^ജ߼D8T  QCRw}ُSwy~)/*œ R܃xĶo[>zNrf_{o>XJaov[hS2d #%rYǀRJIc.fș[ّ9 D'D"aZm>tmޮn[#$Z sq*R r70-"ǧ6Ѓ@$Y)US:3w#)8,GK-(H0u5 c) b.LVnNLG=t!ftK Y~4s~S}{H<,EChvT+J4r1*u#$i@p!e)>},?5sz۶޺E{EN!~_5Ӳdfjvݞ [V5uYقRz0M yYLf@4ՙYZ[۾オOL4I G`Gf&abe.33m[g}m۾CϿC7U=O Y3CDc쭉7~/K7}%&g%_'Lbsr[gnbs"|:)"B՞StT )s T"M f!Zj+wD`aAY-4imom۵1T!.j4Q\<$: [ځpV}]۶z”,'Yկn^E<֧Z}Wim7v[:+5vh^ pDJ}$JpHևmn۶6酈OsϮZS>n[23_'0sP@)߿8-pwy,"uvz~Yf3ӮD\ܧ1ƾm߾?T7{~"J18F",ނĤCs2ϓZHT2QTug.2#@3<cjVzDΠDi*Dh>4:cR3OߢGclև- USe.hj۾_oc0.a>\ w"FdDHF a ZɊIw:<f.dYeZ˲p HpPwбѳ7LЀL6\4m 5S۞\%΋֍z۶.=;- Z05fdoff͘t4͛,Ⱥn:񡈄vê1KJFXRdjGHLQV!fanOv]ovشRJF!2L̢Դ љG۾3.@Z+Dg:V=kkh("mo9\+c+UǗ_~/_|gx9!__#s"8Ly#2RQ7 8O_W 4u3[ r~AaaP䌐r?ƠN'ba:`pˍHD"E[|R]znz7m)c1;&CD#"ce(OWu7kdxDx S# ?3#}r#́ HVv5HPdy6[92 t 8u K%o@4hm#2 ,LJ]׭oY~7r7̌Ry6~V)9;8CxnYQ]}WDNL$l <.ÌH'*IĀ\@`("}x!db@9a붯}fsCiZ.w|aDT{WS`"%TƅGݦy @@ZyRC~m%pfIij @lrY͇"F-TkӷmoPb&,\pt4.b9ON6zc)n{KXskeL|6Diޓ,wZ]{޶mmֶ-[waSb3"xyVDpY7ȡDnDzcxz[3 `M̅QDx槧+Iߚdө2պL;c 0Go`N"JL榮pmkXy?#C#I}>P):zm_~|.q}5/E9)m2TDr3!o`J@8Sݻ2K.= :udQ!H̳QspCLn!wqʙ7s˥Ex ;KC I"H$413ԇWwo>y}[ Zku*4:b}Pq =y/ӴHmzR!pyGKEDJ60@MLr T5Oכqn4p&ᗾU00(2CD:09`’xi@8Nӹ,$~CLӌyJ ۻ}Em>qN4MR0>3"%<#i:jJZ!rm}Zmx}V @oH}۷ږ: ՛*М  @B`R,iI9/'Mc 5ZJffdG= "GٛW_{ qiOeǫuӲ\{ych) s)FnJDLfb,?~o_/?LS~=~ȍ"&D8h"dGfcdJ0 T+ y,+=b\s 3RG L?f5 k!nL[J 0HD  0GiA( kv8Er(Mk1aϯ@0c:F.TKJ-58=% nz0 xx>,{]5 ĵT.b0 ̋ѻ> !ZOR=φ~HT1h="3rwjJ)ի<,q[}ou33[Z񃧝YҏÜӔڔ"9!8rm>^n7Ձ<#"U>ep_ ;/_}GH]TQ ϥ"s2 &g|u?DAjFq:-o/Ż;.fzܝYͺ,C|>?>NGD<R1VD"Pطww>/gKum\~$O2D@#8av\xs?=B=2Ȉ6 Z h:ZLn*B- ᶜcpa8\0"JB `9;`jo{9s@Dԍ7$&T`auU]%= n44<<eO:T]1֚ T!M9}oԏ^GL̤tH_tsOD0h):} %:4Z0nn[o[;>]S e9/wz9_3 kWsgo߿뽫&?`)%y@bf5i|>R$I)!۶m}o?tR.Ax0HYHu燧"[]ݻwjJyZOĭw!|OLҋzfzxn !Se qn DOPPSf9t3Ŏwwyu wuב}sCX\1K¬Y\N <(aG&C3[Ki*3vDқy3֎qSX`* vB/ۿR]^I}{ 7 z{k@" b3$fr3&"0ǀ ~MC[S)c|.뺍~a 32adC;@P2 @p1TN9\@@If>ȇl-lS;68qM r$k FŽ-"ݻ1K`r41m@6Z P<GR$[B Es& b ]s14 S4vñ dbt m;'ͭc`wEbEtHۛ42Bw o[Cv]o5I[pWwc9fO2E޾}k<{1sQjuчEv^_\ls8!aW{I2UIvAX|{lGImt^5KcATug@@Tץ=O˲< ?/EϿ"橿K;3汮,s F"rRpimYpU!iba3H$M#9L6m9 Hۖ凧2mqa9xoNRj+c^*t9ΡjA,ky|\RK"ژIX8s~<1>)d@X&Қm;- fۦ\4zEfP "Y@zO=Vطm in_J|>V/ϗvpcYͧkqArDb&vn믿AiuObԖF:},~z0<8!a)y1'KCĺz [Zz^Zu&%xG>ΎIvۑP]d iMw}?D)u]RZҖ#q/#}5&wuZHsOwtRDvئKGDhy2ׄNO㙄Y<6uqy1}3ڶm;~Ӳ4m啈Fl9#aeYں.oTʛ󜣔j m.m}}}$(pι3:#Kرu!5R$ndZC/ˇ??G?,'b(LHlCUȳ775If@23Y Y_^^ "026 G^9H͛w7Rj);qAJdF# 榪6TGαÏ6/,f"d"Eenm߷_v,+[Z?60L+plGx~~y}rx~}=/|?V~ղO0of錸6 K޻0R XkZ3"*԰XjZkp 2.-U#c4XJf(# wY+7^j9LC!"wSmtrŀo"{틟OO>.^,fӉXt}|JǃnFJSɀq#D`20秧ow@pR{e/NK[E H&ŒФ"_n^32:up5-*<"SP S"AZܱKY 3s[XT/8e7VrOxtGjSe|K?EXjmmclml;#ܟIdN"̥Rm9uFZ׵vݮIdqvۯm۾o猈VafUwtA"悉@L DfjּV+-pZSkn:yL6֢eL g$# Df&$`f9A P #J8nb\I6 ?gkqfJ!`!Rl:m=XM`jXK%N IDATb* Zx+孚%Q4HH@p^T,<~?}qusO;3mvNܤlCB3f!@LkMD %e=mg1)#AniD4H-_׮fVINҖ[ d`qԒ{asz8Avhz@}g&2UuG@SϞbMU8j/~y-tAn6,[Jnas`fA١^,̆pA3s+\moX=zJ]0b}LÇ3.B"zx(Pae1>\57K,$,R}sf}12jaZBj6T#,A!"D74-4k% !5CYv|$~aDl@$,\Sq*9pɄq5J1֒2 Kq-a0U#9SM.Q I}TE|zyc[>|yZ~wo?tsxZ=ojkʝt`2/%P3!?{749q_? /e!fG0+ 9F$"7lE@ƒou½!1R_֚Nc .JTk!иe^,09nK63zo RR s-Pvve #MV4 rrY b8 F/@wcд "1's^os$q=n$3v>9-1PpsE9-7nfJZN31eMQ)\_v{w1RRYd9\}sbi5cD$ Qt?vX> Edt^o73󋪦Eh@4/M RJN*bVH)ԫ:Jr!#t(1F0&3֕Ot3)DbΘ qҊ16ٜCz .inwX"nn6fo>ܶ- 4¡e)c*0, JO K8ך}s]|=]M9gc@uiyTc1"ɺ,ݦ!0 r @C,-v\IvEhmε93pZքXT&>L,2U#!S'&B;g7Ai'3U58 ڈR {EcɌևL>9?s],C@KԔes z:?xsxDΟҀDKYj\Dt#}srsh[[Z"s#۶sIr?@\/yJY8W96"rT55"sF3t K3$)c"a9@Vv.e ʂ))nSYh)-"(asN1fwaAfӸݶ}u"bm[-u],9zR~C# ^//eYx>,lcNw9/>|*΄ݽ{[UM{tS`:xƹ7/~evl;yµrwkyђb wA߼nׁPXSa^Z{8?.1dG5yGQ7a0$]| e$$b-ġo.hBaۙ %3x RyK wR8`;ύ/G}O\{65@Z+R!<~<5SO t`sDx^9s(.?O~/>.կ?R[`Wsab !{D3`|J."D"s*37{:EP"\I؏$ Sk+m}=qL=*#C9rH1g&%'!q?`ĹKѣ)"f2"Lm>{n 7 `7o,uy]qv)jn@qT\.zzzy}y}y葵7DmiY&_jri c$z~x|P ܮ6}CtB" )Dr^q)""`o/_O"LDd}蜚e G=#'ہ YR |T6? s9 O+[+¦m¥D`36jXsy^8 97&Qq܃[+:lY@@$idpW0euYNm9-go߾{89pm^vn}U5NwxfI1sؤ|ZD>|`lp3D/崮YyJ`Ry{83gu񺞧1}}ܶ= P2,)T>^JZ^qPwD-ӃC|xpX"J)-7B{a}42 Sk9Nu%/|rrnɘɳc?cΙ>FBm tlA !ZB5Ŵ+b>Ȏ0xƏDGm 3 $W(,JST!ݻVRulDan>͘ (]j]|StWLpiV%EhN>O}}ﯯ/_'?- Z]_HYZ%NGax#QkTA0OCl: ^7 Lj.L1tJԤW!$G6|+q@vwf[q;Լ`J_{IJ鴴o3G@h J㶶zj|.,Ld{/߼>˰t:H"IDbNp$ݶWxOOO3[Okm-[{ e=5|b˙fnizm}SP?'(HZ.rY/0ZJmOOOdpq>uU3iG̀Ke f73LJbՂ=iu߶u],n:G="LIY~ U^*@ T(m˄H;s2@^fcP{h(Fctf ZI4F,☥}!woNq9z'u}vNˇWw^/N{zRt"("K Łc0U4XP33|e]p DИ*(,1D!e 8_SGyR.:$+ @f0x}eiEDT&emm-m]ۡ.f~^_^2Qr:?"[DbueM٘r^?<ӓCD߻zZ["eYZyTs.,Yt\mm}jHȦCú9J)ui 6!a&:dfVK>l]539 {F1|Ӳ"Z=e]!VPuط´m73%}g0"YYk#fX)BR  7tSE"eL ލLf)D5Cn*C%= Ӓ%PU_Ibd`lHynˆݑ#F?m}/9 9'*ssm뵶}ߐXM׺.K}^ 񨧋σ34[]L5ޟΧ?ُՏ>.uKbZ&IyC&C2Hh3y'*$wHAM[-l7uPzϘJNx+sEIC 4-SpQ ! !!{Ȅ^ȈJG-;.[jr^OKLhN ۸~vۮmۈ۷,wDBP0؜0{]/c!{!Px==V=e|9mF>P3RK/l}9%-}>sN${ Ut K>U\DtM'@wMFCK0Mݶ we$J&Km-k-uYbDEXg/L`.cpeY4BTZ+!'Z Ⱨ|vl wr, ZDXJqo!tN|@21S6S \;q1`GYr@jڷY40,R}ۘyv)l<|充 YD%JzE;`݃i&z;n[<ӿ?mj~_Z96Jax A@p L=D G<G2H:MP`QvaӦcĈh6#|lĀ @2@HifTRK9-.#aݺ^'sSձ1: NEgR #Zsi@Gvd{>=?<=]^^/O$,UUKY[I >uk؞_f}>(8,0|zR"K6Mt$bFYZJ dD"D-#QfPkM렰H0RI΀tؑ "NSkT 2S)%K)mSxvOM !b 3D6jJSgR*%8<;=3jsVۜ#<]HDAPJp fy38$<궓'RhD8W M<+K+¿ݐ(">n컚{^) ] 9=<5$: ӗbܑHfJ#"k+___uaf" P)1/~}Kb%,&yT:Hҝ}{{bnHcG7dHr[k#3jLf>+YHy]!b0#9ñ M3'rh: 9FCXja.qk^D 92[92W7C')0y]񕉦*;l֒δ}7 L=H6D/&^O'"̟*-9M-,2@d3\oGAxg;"m^DяaAaf$)RR sf3OHT>z}'?/~iuy_9u!W7( L E {UH##{x6h$KQMnZ-mZ$UFN@ҴZ(8EXƘ}eYemZ*Y,$3p={oER>ZC-vJR0D]\t639`D V𺮽SLX&azh2uVHkQ[`z+=Ƀ Kcs< #i̙1C`*D6B*&Tf¬Upw197_}O'V?piU059zejf^%u$#ݧtt;Ѱ \uz@j+̲V Q숀Zk-"G J[Rq8ڑcjs-6!-k+\1~γ>ƾoOOr^dIDz>e-0̾^$,R`X0#1Ͼ} sjD}T88O,r{~&?t\/d2QkKI[RL­V!jI=>~Wu]u"TEZZkuYZMm$(ۮ}ϯ%ǽH2K1ddZX,Aܗl#,C0 a)ٖ}<#J1O !rϵdLI$ eÛYV@"@F~Cb"IG &>r0"hI !\ < 1aއy vӺ}䁧E=!M3s RbsY*uE*!RkE&w,iNsB#/~wZ]".%woc ,RJ9̝̝XAO B͒t_ S~ߠ;]"`wKp]Y벞UVD)K[beYV[)Uw2\a]UEt>#ЛzFMe5\[UfRSnӃ./?9FіܲݧN>ŬZQ/~K3"jʻ7o>" $F]݄|Z#roZ :0i+0p@?3l逿A"ϸݮG_|WVP*ٷ:{B!"&2mncF*Y0QJуiJS[kvnܢd@T׃鞏~UWCji rb".RCjs?1HgDmYfƜ}Tꗿ櫯^$Q|Rץ>)5sĥ!jcm.i79כK J$E̶m,:g#뎅PA|^-o,:4qg!R3CB"*1'DږVJemKK<=<a@XvƁ}N$G"@ +"J=,DXJ )+?3k/@Ͳ8l|-+!VD$yf6B9% fya#q(;O{`!@fcdYr}vYL#ѷr}\5ƨKs-Ed.=+>#ǣ%&O?_/ٟ?e)T!Ds9<2㥵D{*yi Ikr~Ĥ'R[Y51p<<IQ]dF$/=z?[^]~7?¥cqp;o(z)pD؜^8 ا0:Q*{e"M)Yf=}ۥlm-n1H{u]}8BI.Tj>7_ߌ1R65ZK9k)mm4 }w8BDRZ{>]GsΉ >@T7p3m|ršXfn~g:3"²&E K+D(zo1,\ĭ Ą,-"lf\Kf{eЂTQ1s{8ӶoD'BrAq=RPB֖g)@ZG @)#UMlD@5rED>՘ .M䃓 Au!B1,p $hef7F2fD$)H0g'dKԲn< U YyjI 3Bx K9=Yz>nmY9ݶ8(ʷ޳ )4an&!;)Ҡo‘߶|Z]zK)R3m<|urlZ;ZOZ:z=iRDRJs^o7Cs}HZW &c%E!p._նm?ᚄw\x":; >6 jEO!kthTr?Eu۸|ۀp\X޶kɅݺ2So}\ €n3cDD҂!ATgD9Z()Fݒy$ qJӑ_|en4xyիTr)qkm6M>ܽSy0dOx/2 'JMHJ,%̈HSDu8LZmҥjJN, ǛS{|^PP*~Cgt>G NӔ0pĜК t`a:0@ [e{7muR sQEu^WS fJLOč hq\2cHa q #JTd2+ FsN3WHdž\a$pD* ]?"4 TK"RbfkU^(4)DZ `|DHeBn7%&g6ZVS҇,E0"4K0O9a"VaZ0-/'"j)i*ƉT{Nw>"22@nDL4x2֡A=a#G#2qiy83P:+Ȍ 3pb'333CVی4O8wT dN,M'sy6IQ1;kvd75В580N@cI`n!&4⋈U$pF[ۜ*]U=Dt Xj"1e4zc Ia!)\ܡR"jVڥyaIX j;!+JJSQ=7𙻹#h.xaz}xF(;S@ |>o7eo#RJ 2x@s .+?qVŠ`D0=Clnaw2;tz9sɥ{:UVxtzqc"^UYxtdps:Q@~2/)Vm[v5$ jܼ*\[c7mvs<q 3\);3giyǰ9D4VҥK3/2pbn]$vhf dHH|8UF?;=dK@D"#)AwSu'bV3Ԧ*n%֚#8!zk+"lD8Ԅ$cK}zdSU}&bɩ VU+(^s.'7b>OG;պQׂ:3NIMgHEbFE((O#Q1_E{o_WP_.szD]r J)e^10A(hdU@n~9?m[m"b}iDM_o48,w`X玘nNyY,DWseKɅsbF6.-rGts1IE BTd!G[A>D͉>͕p3 td@5a¡/5e" 1ٔT43E 2T"?&f!fR)%|Sq ?yJTPaEp1nc<G"rNHWQ7ﭙ)Q2yN3JS&mIo]jokmƻSRa{Dq8v^݌]c 3LTQ3|wUfᧈvjyF#BNS)Ej<=]//ooK,KD E \rKhq7xG9R_wp10zWO?_T wRJ)'`t\{_x釋>͛&})$CHj8ޏ84x {?o8|m7@X)a$.󜘧ei'$"m"f ]:3S)2Kmvڦ]UCua{sHv~:ǟzT崶SiJi:XD,Kf"6m]/4/%Pյk)%qqe 9Q,`!\c֏fЈߍM$Q՛X6dBApDT0"`1^#wHyUE q>bucbVBy=6=`LF"]DɈ#b25DK B3T48- ;6f!2=>m-SVf!TEMkCҺm! s\G܆zf )ȇF[Li9쫻.~o|o~9/-0d7Zk<`>x!zwi].q d2" ;^]fwwwgK_DL:M9cjMׯcQÇfZkKQ!2em۞HS19<#"yֺK@8fg?k ){dʲ,S)6HLRJN9AZU֊eդ`QRLAEb9%$ߣ@ƙkf&wf65c"ivJ =-jk1R4DJl6 0HXps)XMɉD8BLLZ5CQM .Bw6k015$ p0 ְE[JLLe. O7/y($:zYWMGsD0V tZ7qY[jmejM @zkU3\^]0¶2Mp54!ᔧt<~jz{{z|=:xJCIy.SI9xO(#;B6t2xy591K!8jI*@#ٟO_TW؉sNy@h(ÁfAzֶV]6Bb1(T/fG/~ˇGI"v:Vk"7E|;Os*Vm]k;Zyy`c"CPc*`%b$$U9r:.K+'v ,i^틛DiJiJ#1Dn4|UQi TR<t [NcQ(ƈԚƱg`S=djldΩH `bc`sN s.qd眧L!:v[ws4+uDdġ)8bHJkRLdJf)8OsL~mMEDV׭6yq:bZfd_HcM&OZ7u's}k;IeZkwNuV6V!nDkT7geld̼Rn"gu>_" ZRKJIۑTi=;Up IlPiK ث?#2#; 朥u7w'?W9b RJKyÂHoZ9f fcHHȾ?Y!g hDB BXpxZ/o:ϳg)Qwf@^jAHxˉ|ֺb!#7 0sJLɈխfyCws#0ђ\&$T*;{-7j5X1LkQQ1Z[K=DY*bF7f2`9j{Wh!x5}+sbFT^\΀@U4s"r2Ϧƽ C assf$dODr ]3d{o~7f>[W]o;4O"00G dɥ.b*}TD)sΜrxYˆ}k_*.91oۖ/|^<%+Q:"s(_Gttz[mqjN>J9y<#'"(SʜEGSo!9 8s GT*LFS11wtpO]z[yq\Ɣ>ss% h` Vm(1PSޟ "2;=]9sIOUuǑD]$.+ 33jJNC^3tё11V+12̩0Co=\ψx}Kf_[u#So"M2|0t<<__Ia>H~^*`)e~˛P IDATtqUfL)gFNlnL,*wjˢĄ@@:.t%HĪ LzW)pM*]' .s@O>HD3P w|`G\<FS3[M91I|s8F08ja_YjmyB ,3aE‹`xnskEx)dDLx9_E%1#R"̥䔖iYa1jr.q)~Ո:1a" Zm{۷׺;ÌGD1U/#g0Ga|u;>nvw攲\4~'Ͽ.eAyUF>31w@B6$ UDpw6 퐹i* >S ==?}qp3|9Y`Ap8Ϝ s ? BoܴaJ9}ĽwĞĮzQ#5wܜSXG{RJq0c 1 SDDyj!K$"{oZRD`1% hZL ЙrɰR&f┈A24mn9E܀Dk &]rIuԻԭi\&́iU89[08#¨du3PSJ38R\Jď_WK9Y7< TlrN) t,0uSbr /R/k Ԏ"ZobjUSv L%<{9V0rA?V @*Hn^x$<p&y'36VgX;NH7z57T0.7!IQE̜ 1& tzY,xx+_ڿ778~KMFdi6bM9.)[N)9{JSIPY(xpcƀT}}Q]>/_η-sEkѽZ3!'s`׽眷˙I"K"h,ri6:!eWq}q{p,r<>enjvXEqmHhCL/$0xSR;֮Mb1XJF&@7 aXJ)֔2P* nf &"zosFH[W7 0A2' f2PhQ֊)!| #Űwy||9GJ 1 Ș?jJ/I{n̸˼ dȍ1RPOTQ2)ڍ,3ŕ 9%Sa尀C R!+{ۥ =.6s-u.km" obSzzOEڴN &!wnU38y*Lt$b&p~R3D2Mlݾ8z?s0 |ne\3!Y\lUܢ\G@ڦAp|h ܑFC[v42Ǿᾂ|_TχJN`1U " ;m&Q!F%ZbbPԼsLDET,~i "'~k_}yGaKRҐÏֺ CH!' C,iץYX#s%>5 adjBP|LӳH}1"1[dqkR{R뭛yy^2QzVwx%0r){bœS)DXv~@N<nj )O=_njqU *>[7!^ں$KuILS8gj[O9@(twAz<9pMfk!"s)i {_Me;_|~S)rv.%!EDoR%3*`Sq;/o{gZu^0rbbBnpbFUPsDE\߮ Z8n]EJijkrNn>rh.%f4jZ%B&fZ[(ռLrER ;![koܽeYs֫޼|)DR޶|Y/~'tŪ]CFbꎰil&smuMX0Dp aN0-\tsS9e&.YEcP_J4O>>U5'y se*fv:$JD]9Ufʜyd$C{3Q4 wu r.s.􋪨ZPS"9U,$6D@[!9R&)џ"Rjo=_[[oImS!5PKD*;]1,ޥ[ *92߀v6`5Vn<{mz\.y';#Ck pW,L]`y^R˺=>]uڗlȜ*rJU^2EMùf8s1.ݡ5;&0Dn6/3OsYrmzR˕S; ʁ@am~X)-fN9nR*5NP3O;[4b*9ް"jm9[`KΦcg [3܏w}6a_2:ڊ7_o`8r,N2%Zg7 ɖou9NR}" n9j|1H!gdX{"sO"v9|$n":@SAT__~7$ڈYE/K)LnDbј 3\3;4靐S "dT6%Fimjs6,Ժȝ.<<*}̭uwt"e>Ayx.ilj)\zk9M//noe)%[25`4.= 8:$iJ)EH[q]lۺn[몦j&ERN[b~@&7YcDMx|ꝇCM@hSt.{ )U Z/mU=H=-ǜj"20D){oȝD{.ӡD3l8uzbff22OSkmx\/[ t˴ͣD߅%]]l9̴!" 1cD|՛7މz]z$`"_F k-Dkk5졺Hٶ:"#k\أƊ~ 3b s,);FqdW4!kzUq}R"h2RJ:1hF)TJLȞwU]bv]:.km6@n" ֭"몢Og sJ9:ss!Lpzq"VŲ,O38$?z`T}[k9z_+h"]ϏgibB.y||0 ?_Qr8,DԻt\T͝#Qp_ zH??tR\0kD"Afq&t ǛVpqjvV(e6u[-LL<=,v4/㺄H`v"W_>%`\t5 x8LbdLfsXD4t`ĭ%n`|1+lUB'RZ$x͜)Pr"&SQ&debHjD!u {mKɀLr.iyT$:"2'f ^n1JN&5i\ 3O/|\.O jݶw]k<73%L?SEǒZ{'@C̙yN)]=<2,b|QL3,pcRy`j\B@ķ/e-y*Sm3)MHpHo?\ᎁE{;e.n۲uSV[mMzBZD9"9Aڻ =C.yT8܃g~?׿/˿G_;"b,F@xٶ!LAG5=,s}(-ܧH DjJpHtdJM"Z+!2!Z['"5)m{oC5,N=;(nm۷w77C)Jνu8 Einb!~> R zݰM*ܣ%9^y1#>žDBZU}k휙9w730$9{ALcjRڲIETIo"Rel62}[Lce<>>~x^*tH29ݙE$ʢH Ĵ ~O|KQ 4"j`*b4/%FFiunR.t)5CD]Лrn/|]Dgpib1NhFCӗ:.ByS2BqUH4:&ӠgZ|Aw}|R8NobJW_ڬFD'дn@1%hYJ#CǔT( JPJA[[OKLCyK)+Z[y͝@iWQ1G *Pj vai+8C\:/ i)5/|SF4 "ԖeHͺ IDATyZ^M6R0 !u|qobw݇]4R8%e1ݶeb! uDCO9|C")vw+E ><Sp6ۈSꤗ&y.\=af؂̍S=)0s)Ug ИiYY]xvS UfwW;Jz fUMyKmp?t1e9z4yO|22NUEnt$ !n]DDdZ `a74kLam7 *4S31]\^D"/|{/+J 5CQ#.\u}7triFr@J\Vu DC@bښhNZ (E4(bkTk!:sɹ`\mw\ʼ,!FQ!-/o!l뤪w(Uph# s0="G}z:T(ō/`wSGx.38' FE]R%c\|>o?w~gh_zw;""f~8>uK^ ` }>cl|Lo*Хx*!Boq΃kTq_g6U{JL5||X3!#jUt`^)OG 0'SuYfBm"y؅!1S*"<?yr|4χeKbS_'1:..60 spx?_OO8MI̪h)eK a;l4!s@"OW5f O;eVT~o7q!E.2{dKNzř]Xk5u6,b][BDG!0~.ipH}roLbtbٸ+'Ġ:ρyM?)ןQ,xZp\.wkTRjKVEBIp;-8pJKf>/ U㍽,U'><  kb%|`b+ZrUj9K)O!_.?m*%@TӴ/<E ]f-D2D4Jmm?bi=M| aj<_+n 2l`Lm3_c1|`=Zp˜.2r7^\.!V%fɇN4.S 1!)HNo߿q5xCHjDDXUK^B;٩:e˳=\$ACSlࡂn5mh0gx-h붭5!fbbaqL1ޅR؅LR4-S^Jqxr"b~SAE>?<}~x7mZخDCeϗ4/<96 2S{ -`f)X`bnc.pe33!+,!l:z-\ 19к 9]ɥϗ>@_H4vu\a33 5sl1;"1TT⠓uɋ/H_nK0H#"ι0M^|ضr0|ɇ-kC;T's/T.^ٻcҿy\Q T2z.JK34_׾/OwUd٥pd;*3*(""o+""JggRm~; nFq ycvFSb"$8'RkLO[uuuvi%BT?ɋ/q ̘|3(w77{9v]Q@驔ew>*m?ˢsA"0<;9#;rN $9d w:E|K*EMX|쉸+w-BؘbwRƔ ļ<8sZD=$JUs. tÊ݇4~Oxo޻ѥ.(irxWb2s&% ,dNۋT,xʖk1bK)b:l6oԌcKs%k<4^B%3+E+$yK퉘jU?}9W)u&8xT6ztM(%;Ѷj)2ono9A`&b$b7.%ZL j)f!FS!0s͙2>yڥ@NUYrqyBc/ͥ4M[NHҵY9* $"yS|ւMAOι[do_.ï?FfC(&BR#FT`9TUSev-N#X.0{'!EhLc cR3F~FTgzsU  cCkAԪ>>|jNKDt6~͛d !ZyxXa@RZ=%ʫxuffDA4H!N-muz%Ц^ifu1bNeff~ۥ= ]N5:.8NsYJ.%Z!p%¯NfB3>5|t2/qӼ̳T!$4eQQJ 1RHum2m'bƠ @EjܑGsŲ/acBRK$@U9}#k.ŒRZ ̴`U"ɧ>^-[Xb;'&DQJRUfR jLNsԥύLU9?B xk#a ] =ES"jo6z\]r阥nܾ d6Vsnpy۳Vwq=Z>9|/:ykzTZ]7X# 9iNwo}[_.co7Hw}9REA."|󦪋sώ(p"v"DNf6uw9d?$YkLXhzk 3"f&dUo8xn70w[U;NKqQ|BfWre`fE7f`1v q WJ1ֹ)AhF.W,2rQ9/q,RĠ35*"R$LsH1xT 2#B-2^cɵ:bTUk]1q)Aj"090 b 1RqRR=)u!HLHb9 >l _q

    яϗK՝kz*.X`%/ܶ%g)xUE JX#AMܤkš[z\.%\H^,rxTwP5j"SXGf7Cs)aE6+ xD 1u}"3k'n&9)4ȥB󆢙"X"pw:L(0 3Ztua@%˒3"Η-窞F$- * !48g_}Tn)EU+ZjK^}V:0shT?.>ev|KMsy.௪,xWǜG(.S fK)R*X:r/烙p.vCOD X0_F9O6Xu}x10'1*Z,  rGZ`r灪(LkI]!xwSRX#™2|RcfhVEWJn1S *R 1yTeɂH"!V<fF$b1E,:1zB $ PJqC8` bwߧx&Tu5U5i 3wr^yBղDbiUuZ!h}$J1Qx)8ra%v^yVC;MDhφe[M،ԥm|zsD~a SCJ"GSAh=Fzono"$(lh~(~oo~y T-K32sVҪB!ZEjcz˧L( #z{V\r4_c!*Pkz'j&fLXk5ytk)RvcDDE&e^|fe:=2l l7ooǯ^u8$*L&jwC'"gd!kH 9Re55  !  J83:]k-jǜyZe)ZT <<*0l6:bD1T*ԒMEL (23)$Qqww8]`0` B 희[7bf f:ԚSy HFfdR@Dk~גbbAuN.$9ĐH"4 ɴ9`r-MD>oXS&L>{f}h31D&RW^=>>CRK`B:8u~4e&#Bw1-y)9ȋY՚Zվ赏 <#Uv!‡C 6[> ]<!QԔ ,Vo%(  瑲k! Y}ZU?yO^ $l?#@D%)1P"5 ڻqR|b<٦LtyC*"g*\VYf^/3z=f|6ҊHǨts5-u]w>7agwx?{v<Ĵ@mzg"dld ɝ:ܾ9gLU lR *8ro;&\Zχ%UD8yQb^#,r\A\f!8^S`Ѥl& UOUqx`T1U Jۛ.J5v8OK.L "REj$Z2u|~UhNOZXeViǎ)i͌{˥QUcz63DPZ1D07Zj ʌ}4bA0%DL9;o42ocJ1Slj9952ЬV! cTj.k>f˖Tf'5pV4oɟ闧_F!l>5].ٟL&!<>r u" z:_c 5T jP1s(U v{mxci51b;͈I>u̦ed",r6!yHK'}Ba3tw710UѐҋIcA1FrT5x+ B=;.St]NOݜa榔\k]lk1K)<+lILk_)žb1'5c/V6.m6< ],K'!z/A5^Ԇ!eRDO &|BP5"i?G}y5uRL!zRwDMF倌2Wh!-?}U%XJRFYJم(z#u"[gĤdtwqC"2= U$02Q'R9cDd4M'}0|M??d{~ȥļ3ȁ9iUrV SwVbHnuХzjNv>|ԧ`C1'Ջ.]ers@Yrɥ뒈r`Pm9bxB5-8M׼[Dkk5{CAVSǫT? y~xwOn.RI >Y1r~H1n,ˢ\J1K){jc1gbziߚA) 눜3 ULT80X@  ow7yK.]-DRc3PqYrvHj\yBBd46cyɁ8ƄHnȳFU1J,ﻡbIPDI"@RBf)K#MHH~zhAcqZ@3$PAh7艍Mty!g@0DUM4Mθ nR t>_>L7/'#]KEʢs5 89"&D(>P>lYE\bJLBR P!)qF\%_}o*< TӕìeɱՌJ-v@jh L"Zk-53+h)v0RdzNoI@CcfT.Ww|ED$X,"dX\s)J)!OڧTJ&&D LttiA!B$s6ǩhb4kNC?u\ CXX8{;@8yH"#:fRFk񜂚C!Yu]J)ujK-~ kh)9\#(EcrV믍!ES-cLn+34 w1VѥdTyY>=j kH1Q1Āfu4i(\ ~ _ױtnYu%4ļ,K^87Z5Epw_@n=GDQy:B_i*_~j~d͆[UdsHkH}f#fQرa#^Trhzx|F4Da;i(hf]+Y3mq5WE^S{+YEZZ?WquBRVO s{^Z|d 1J5g釯_QKbbmRC"v%{jn䷇eYi9/Kj6C c$\%tGR z\bsf]R@>q LkFB*RKȘ9T@!`>Oe!g&FLn0KUZ*1m^ۭ[j*zOSR\.W5qAg8a[A Sqn7 1cDQqvLȈ6#ЬI2Ugk rFJ[fV8 cKΩLr{ͽc̋UTGH5tuDz!.e"dn.jp~18NODJc`c4\L[V 3}fRE#vS)4Z)KSJ.~lRJiSv1"w}B) t]b\rysdZ.M̷ݛCL1+[1j <[@bBq !8 nɥ/g]o󒵑ukS`bF%["؊ 73H1Z<B *)6B!S]r.~ȔK.D8i䝀V5_ץۛO^,#Z7y!<>>/GX...uL)*@5U\6mE<3Sp?C4`Y`M,c`lDDk&Ғ[W闝(_o^Qw)FF bp8K-ϡN`lgFaL90whn*>oiEjR(^1ߤգۊ8F*St\ufu"H k|7>0bZ%Gnygtii/\ZOɅ`)f8$5L~~o'C=#EG{9 D[1"0L$%#bxbGQ!!Ԣ䥖peXS'CF!x8N׋W<Zowv*re^|%;pؗ&"b@jMfN]'#R!vjp<ŋ/tD%\K9UUU73@_VD]pVY%RFx}اGv+s)8sZC|:]~w(͞ SH$jɥ/TVu:+s7c@bRmzSMxvi͎Fk3F@x{Yw49 9xE[4 mt4 8ۼ񦚹 KL1W !ZcO} !SLKDm-RkRJbs) L1>֮u3TYw4r1v1Uy/&@DU{Ox>H*En88M욫k4RC |Z8wIb╃hԥd8um70/:PΗtReeEMq@X=FFhMY|f_HnM1Sqo>3B$1&3E$ Y͜ n>iMb.@ԥ8yY@:z4˥ZPrZȝH*Bopu:!f> l<<(aũdPvR9fN]W=Ğ,)Ë/|&3%{{!40:Dzo躼dĠx赈 TL߾}wן]r%g8N| *J˲ǟ|_%"$&E(M,h*FQD<#m7!u 2Uvsl 䀲e@H)%0Ȁ8,Yj(w;&ZK~B^QΙCh5\;qMlIBycJ&8NK|:@RO]wGQ,"L9gU1@$u4A"DX.xZy(_)j\Co]PDJz队Jr>_ׯ7&`C? ]crtr)9 d- 1l>2u]Xr& fR/o6;tGɠ,hMQRB49Vw:hX$ rYiuG.'h82b)R< Dcu⥛V.Sf^Ǽ%/~*E*"7[Pj^6S%fD𦈟֔WHTj!Įa0yʎ:)%DRn6]=q Xs(3yi, b>)b< { T@18wNDqY94\kEDesKNRjcȋ*99O'UJq"3hn81Ν#y0cbRqvŭhtqKx!]JC 輔Eٍ&/nn۾S9L1$D'c6簮JZHnmyWsε־ͮ Y(>6N$x !xLPq&vHy'&qTUN9Z1.B FH3쩵e{k~4q1TRrk> D?'RJ> k:4yEZJ96~isPɶ`;+l;rz4myH'8#0%jV#jk`?jw_;@x))K;/KɅkB`(+x_wȹ#[ UtOLt:_ͣ#׃IRqlR1O5JzST[42;{5!q7NbқoTgx*D1 i1|> FNh: )PJV5_,Ƙ!́c yι@![kwww֤V[uDi⺮֤~Nq` @N~dCH 8:}뚺NW{UL = 4w@ %,KV?\y v;u=޽<#ZZo=oOvy|.%#ښkO\q\/]ā9! 8&aa_lY"QST5'-S7Z>"Nb"e}8LJGij QgCE\k9$@`b'BmQ+iQh{Cͅ]Z,8g~:x:/ {<ƪ:=R߿W} q.{z:Xy)acV+K"'?3 Uh|:cηJLqbZrNYC<;B~, _w-gZi~PH%gWW0NyY _lF2n$TKkʉذ s}l%BqnƖMLŗL4|}%bRypsuu<B b4}Um@*PLro 裗/\Z1ukjjmFRLD|:b)m@aS\)p>ge!^ `0pK^T\hC0PV v3! X R lΫ:=uUubkB^j)Eb6RĪ&MM!ݟ[xe$|*΄cP*9ç{*"hYWOx]Ky 6 +Y`vi`f3XY؍40낈1D3CF4B2iNyքaMӨ*9櫫ü:ɍ'۷w˚4 Ž=/TRQad/B.09f-gnP*|O @;>>~}>? L80R-Z=i9!\s13R]/qy.u?H{غ%ЮQE-.tEOOyۘq* =IN-T4:Pkk_G_}?w%Oć xDϥZHGY7oa qtDr^DBD!s!.x8GgV$`yNCzŚ}ŚKa^eWWWkJ8%FĪSdP@#mڹsZW515EL%eUf!Fmueb(6k^  #͗OZ"t3 #p-Ŭ8F~/B{k5&Xج\:o@9 ښ1i 77|<=ɳZ 2qc˺TkI)@eHa^T7#!b *m^R6#?2&2QjKKܻO\:˭i$#ڬ$^{x]k5q I1 Ri!DS\U PL5ycҶބO̴.]lmIQݎmL1Z+utϧo|;2gC DdK-KεV9ȶ}(~y]ىMTEjK7M*Mps_Mol7oop؟N璋uvy`$5lr{{xxjDO?!K(wn!pS ]#r6cJD.M&6eVmRj@A|' n @` ;ǔbnqM(0">O@hW|9O)%KkCr wLF*wO?x:?w\184N!L#3{XaR4..pr7^u{A~%)߾}ǁ6?&˯_Qq\ҙR")rD5^)|ɥzUiY3&%Ԇ=Kk{ۻww4[Ġ%YYbz /082ȭTK|iyo: ϧ5mewE&,5=m3=Ƹp>akmb"Zk0&R1@a}OC"c"R q !Zea#|ڸvSL|F3 W9Ԥ>#Ү70IZKٺRI^M:r]MbL^8z?NZ## ySi7y44P"Fannf@A&b{!&Ա7``@Qrb&ܔ"5\J??Nib, iDSUT")pHyaWM#ީ'!R-GhJ0Ɛ׵!Dz6qwYU;Β.9hpJnw:ZJɢQ4cbg)ck- Cm›^ߍˁqPDlHx[u gXݎ}IDdNLN2홇eD|:7s$?׿K؍{JmU%T-NX7gWL jYkb 2;tc/߾"8b@RTi?0$Pjn 071GanDDcJFfS` &2"B_}?qUZ)z Mb̑AU9;Ui `͍MmzKkyfW/X@p`,JH8tPwi<ǫOt2?JsEdr @_dz"n./vUTX#:iTvSwdp!D>ݐߴ#|UI[sX!m+Z=2 ހTTTJ)!tWN8h/_|:{(ĔTs|^s^svvtzCp\˼ c*-Iؒv]LB7nC@!04߳TC~tLCU!)a2EH_E'v'g_2,^HV*]X8R)_o|E%ޥ5;G/F&kL|8'R)Rj52f0;ͳ6 lo?リ GM }躗8]/!d(PR@BRS VxIpSX%x| RK+.L1NØb<+lI|:sܾKSQ2s^3W|͛7Ǔ[\,Ux19\J.%W\J ޅ^ vaۨKeH)\=q߿+>N&GEn\(y9 O1$TA!#@)eP`FLN&f-LJo}[W+Uz7KQEjm!)MmWWCJJAt,RwRZuߵY[d7mևh=o޼yCjqҀ bB|<]Q=bKp!~S{O)ݽy{k9ޛv7ޖyQcLLH@48SH!FEx8)zuJU Z``aTKbB"?_|8Dd6fD3K1]\|"jw_ Юv{$fZZ- !n=P!~R"$E5aH9giY1EQ-8EH`=ھ7Ei5 [bto9ϕ.kѱfJ桅}Md+>OvZ4/pdq:2;>ʹ{4lǶۇfя>՚[+BH5&jQ15dv:w ƠbYi-.UqEcDD90aڏf Bi q k^J&J0+g70 C Vl֩aL/^iz/n)RnA}v̴ :10N*R! ~pdsf" v77jv{{RENH)iZnyVm3ܨ5bVj'0a_s>ΗFor_ȠH`;1_"tJ)҄:j׿gi!5v8$?6ѵ?5?#ZmW>pO0n<+Mj<#ڼMlMTl>x8Frjl?nMӺ~8&BTBLDU[/~ 2ň37&/^țE@@tfLeV瓿0&D!30DclL"uYFBĐ\)EZִ{ݼa UU@=m9"#hLc-":O)0b01ƌfUlnwԘQ ;dj_U+ Xlvid USvV=il41s@ Nfwq 1-jmkΎ6j)1p.J5)!l* )S= (k65!>>}t^v48Mgu$֫뿵x! 6i]&f%$Q'i7Q#>9354>V!uqU/گ_\Kfv{`ffv1fFj" )86iSaHd$,*k)C0e^r^&`h8we@3-ifҁ~t/v\1vp?Rs`on ˧ٮ/@ufa9撫 ^AjF" Mk.)%|km1 W]/s$ DCADBFBZDO%">~j+S]s!\]E1 j0 Aεl &h) q e>I T$0_sal!)u]৮+Bas! 8 &UL=l_j3ΦTja"1?FRRt[_i6BTG٥VD)=0;MZaǰY~J0z%j!EDs79:0 ,ޱ˛uwxԨׇZ[.FЇt؍/]ðG\[[DH b0!ܤ\TrnPEKjp#1q, C5dU>SPwVsrvd ؘ@7VkUE7$Snp:8~(]| AL1D>p@7͛c^j9\]i48N@OirEO208h{@L4 ~y!DAEIm g/؍c.6mdL<>C} 1nG7 ֏hJVZYqeԑbtʐ τ:6l !6i^ˮfTU>c"0+w?=oZ9N#p`1SVw3i.~u?6uqR<|Xjk ud9irWn'\lL}#v `ɟD֣6/\Fؚ0*e5iЁsH bjM`WbDǣkn0?|w{{>c!EVRDI-'ׂMD Y#1C8 z8S9B$FUWN#tn\_Ri_ߕ92DChETQ6[Υ @#D4V<8b) B RgN#Qdp3HCRAB(q1gdH\x$x7ST@DJCmp4DŰ=Z~6|~Մ 0 Ox:RJoZ<!! $''ژ7*v]}P[SQa7M!e1_*yboNM}x٧.{r. yI)Χű(i͠6tM[۴d[ h lu3wr`L"nHq=E.Qkm&f^LjG@VZ5`"k_W?oKTiRN֤j 0 ΑEk—oLu r.w{6ϓc\fKyBT{Avi7K*NdQÍe9ïD$`UTfG%'GLjCYE10(̵Q`y_4_A QQ}vC`34 ffL蠗#k)1?N5iMM#C::p/?'O,MUY%VRKuHeVYmPiO##Jy`oQ6k"qB bQѪBi&M5b6"P[3Q҅!Gd4$B)DobKB@ffion jkcLQݛhePkK12 ؚ9-WWWhU@W9D1 3y|o:DžCh!E"J) iH1Ni H8y{r>*<]pjs.rA10{{[[w\rm&1qh|F¦VZ#DXwe)pOT|4O w'YenCn갮EN%Ct8VQ5[?YHfL"Ư??W~ `#f'̆[/i1ERf 7oU4 NC[IM~b=/[wj\l)zU /jӮޥIj.H\7WUֻmQS-B]U'], fR2/nw^z-bB2Nn}yE ̤T~W9!V'"."MDC#35զ:a %/ܾxlaBfi8}ч7oUMrLK& "ݭF@ȄhM90nWU50,˚sQ v*+fTb D7Cٕ,몹56*R[4ׇCxZH(1Q.:M63Hѽ>f$Aoɗ{~%Uk84 `>#28N}Ǒ\ILL6/6U򫺔:MnJ_fZ!2/.|ţ0Qljj^w,N IDAT4ѝ@M407{,Q-9:h`W[{_9 1̞sK6D"Dj!k%#KnҜ 簫͖uV88{MDv DE<30KL[kJ5 `f*yHf, ?൩2Y;R꛻Ra]y]KmM$!HKɥ)'x^:&8BiH u)e*O4MY?Ub"ιQD-n 8?rHub# '\SyUU 5gy)%?7kv)5/5׏nCV [oo~O}6!E)ZK b"c4!dd`Ӽ\L5R#ao性=S{-~'u.(KG|^t}_sckgx+/O05!v䚨Ns1L!|~vs3/ːtf/^_%[>Wq UcƪLQ 8˒ 4\o[?c>ٿkgsTZ'_uY6e#w1%.OϯB y. mU[n@fXvn!Zv5]JYrNq) jWV!b:$=jhr0Zkްcw+_fŸWЛi]Tqh`DlEiALd`y]"h6SF0]t×އ%3%oW 44L7Xÿ10Ok|#'2Q)Opfbi, 6Kzs.2@^_7-  b fYrvhL!^1FD0_Mto+. ˃ڥ RTb o1R#ᰮ+`7"1\XIT'yLOsg??UCm08.l/zA̤:MD@ fs^""8jK]pߐmQCe>[ Nh-TB@$:O]0?m=$0gʴRw-e,wVȀa$z z6å RM(`pdϟ_y+,SLxwƔTMv<Ɣ>~,y>2o{:w J)j6 _EgHI/kġʪ/ُ(p:bݙi42ֈwO,ĘRh"k)Oo]3"ږ."5&["DFN!Bٸ^VZQ$||nUČB U'!Yr0Ֆ*80I;$"?w83(ỉO }Z1'[ٸW"(R}nɑ"Lifk^g՜t||,z"&1QN2by̪*(ݐ7Z J[scj,tP=M#h(PǭoRt'tN3Z|JjD%Fr5)VD˺!^V~oN4SI~_;F$g^kjADX=p ~}M@OqD6i9\,KqJ`H*؅P?MҐZq v\ ,}H@[u?%']HF>m!HPH\af]y Sbz~sK/r!4$=LtAUevJ{\燇G3wO֐[P=!zDܪ/ՈRrk&~n{"L)1Q?j@ ADfht88 %WYzl\Moh̙R*Ę:D$;gS@ĭV1{g>0QY6n3y/"24кDL̑) A)1Ei2CET[*7h9.Lw͚[}՚X)phҘ-@jF3qU"3ֆ) noN)Қ4 SΗskGaDfN`R5s&:ձ5,:NB->D$kq2o=XeDrf8)W5"1)@im (F?U@rW t:k^׌HUI1v$ka cDR1U>~!*4 "Eeų4jkȱպ,kq=f1a\JiPeͭ6@A0'2ctaAmS/aXAw.?s =滪gh0 vص,)+$rp)TkQD =5l~j8u\A]SJ/˺8B3Unn_yuWߛ/P+qMjLL) M40ZrA"3>>b81Ŭ"˚K^0b rЦv1 DkgA 0NܚgB+P λ5բa/g"\UlA5HImi| %ġՖ\<ƉStj-B!LTq(Bq~`00|񼜝wZ6fO|xq ^RQEҐ"3m(FFTMv/s(pG/kYEUԔi㨃5Ϟ=\fF5HL1XH1qWz !ʽ)e!r-M}M:aVsSKkCL`g\_t+D oQ#Kذ!aT5Ysout7К|~D`5ە8F!B2`S(g]u~|ČB9ID)}ڣM$bf!E & ԧ(Xj9\T4>]^p;=yxo܎<0xEDR1S &@.?cm.EDTK`iY0/deɅx8򦖶̳1)}7K^}Z>1KnjlҖeȅd}4jnAb  j.4rJ0&ȘƎXdւQ1lPjU`Hiǥdb`ZrY'#:LY|O1 -_|n< -k.uDB @d 1j޵'Lb DTk @Ե֦RJY$:S|>7i3ɳ{3B2 DbT#Gf{Da\UM$`mXF!*`"ev{S0x+=w>.MiqC*\vbZENε?|n*0~fiTUcZ[5) ) jy <.dm (,ҋH> ٱ3{sv%l=H[b)wwv]0x[barwAEb&}6'/HL+Iwz0V6;KOҞΗYM"9Wj2 ɝϟ?W_,0`@,81"q@u{!SR_EZJ8R䀄ØIqpmJYv8߾bY֟Z9kBŻw"ڼ􉜨iT 1aRmUDOI,eafư}3*- yᜆQcOvDzq *jå`u]=^ӵLC*RߛJ_H"br! )8óg7Z4f$Dj"g%BQOFZkl $|^w>}&MR) !Ƈǻ*0i9Ԛ%)(n-2a̅H8jj`8J)Pk dd r j3;|otZ`Vq/C`Vv0=ZiɞأQd 1TJͼqE#U_p㲬*ܴ=;Yݚ\j- 4I ]B}pm\r.RSa?]lx\>K7DUtC? pu wy?`eK7~f0"}Ը^>U"̜se^ac kn((5їϟ5i˼oNDDG |{"57jQUm`V"c`aJkRaJwO"c`km`p,@ޡSL沬3OD%Ȣc$pV\VYE f[ĀV[U0`" #OnidcL))D)9i⚥\9K=Lfc՚]fGKFZ'?_>{=4hcZ" nC"2 T}~Ely]ۭDrenf;[Zksg2Iq.#c벮jԁi{nnOk)M'?"UP ,HD&efO0z]P8%)9; UC$N45 P0_{C(1(~ǎ.Y]Mp=&]^OǣR]uy2l0eJJ-]ɏidu 2Hpgo< qeY}#'*E*ض]I QaykOMc◓2Kk[4jxk8~S/.R6#"S5|aSUR,xӎKMI m5Ԑ@%T QhWe}vzBk5FzAoLEMY q=V5ǭ'5^(ja"8T[kf!1qmJDRȌ r>.7FFp8tW[EYђ1Xm* ""@H@g,sEmky]m 7GT0ȥU TPCu0/3 <_~@A:IxO~HpNǏ I䬝'bj*]Wy`qE0 RʐZrJ+fM֜5B_>9*~}AAk K)[R˺i\ IDATT0};~2坺%K5f#)TrְPsR hA\M7Ad*f!l&fk)롔%hPKj3"0z{p3MW_Q=x.׏Tq4w,]D1RqoTJͥ*AGiֈHďm&fy{@7li(>Bҭc(7Nrz͗&BS>tB7(jϾrsTyz8N6B3S"<cC0] [xJz`Q#:/x$~vmC8o|y1X1<Ηb ˢ ,f*iks%B"$eYs0Vz8??aEZ)`^Z}%H3I"(ȚTZ0B1dB(G0L PK;/KpMVi2{`ZĘb8: $ B h#*(fV֚yN-? <}ef) "1Ww]*`@P\q1D#\5v7S"LCb e&Ly]J.wz̥}1a]f@$qC6 |$ "$f= k/0ɶh̴Զ +·aˢOfDD85o)'40#'4D#6:]U?:03-R"j`L)Ъmw )"u͹)\ ;YT͜jTJӱԞSDNgnT"f/KjFңˈՑ}M=؆D)SL՘Q?M*tw>RTH_~I7+H 7߾vC:U@̲COꐘb $0u֧N 6m&fi/\\4ZC\=u"MDNLŤ W<ԜcԢtP%4BHabJ5~'DDqP# IEB T}Yl(`1!ƒ}`$|*1&=e;לEdKZgcwʋZk"HXZK1hCc Llck3mPxp)|Z t8"b-սLa# #3Wx›@n{_s]>!yH#kKV*Ztn}Q"'n%Y)Qd"ϻj"?>Xu+809yEl)G'Z"CT\ꚳE"FZsnM@vKnV5['V>=x5M3xkle IP>1PQ#v um#āowoDPksV~T~U[սDK(4&x:˺,y-'31v@/w0^˽=vE≤= LTL=S :@jcbngL}yb#2oCιfPKER065j)Ћ%!2QaqN͐ejaa]WmΝC=%Z|^̢::DokJg:Lf߼w1 3)Xwǔc iWoΥ,)cLnDS6DH] x'a j-17/_͹W\'9]KkUu5BLb2R֊/wM92^\@^b6 1(ǟת;hwҮx%&z: M1FeYkrPe2ǂoW=np Qɜs [_׾>lOL"6>zr2+;Y`*B@`cv[@TpyvK̘#]5n@yfۓ#!ƐK N*:| 1,"#"X)c|[O)&@]][> Vs {UԽ&u챕Z:w~A`s+ $}IA͚ȳ4 K&yXk93DiH*V[&L D!FUcfC4Clf%Bs۪c<Nj:KEj5ƁaE˗ܚe2/.ݧV͑vO+ATj)%18eΫ TZ̥dߧ!Ҳ,Z=8g^yF1k[ʸg/z u iiUeYW$$@4,HdB'^o@ fa&ȑ3UT ,lG~84e j;FD@"cr'js>BP2 >k B`s&$A \k5??<=p)K: i>5%$岮xN,JQVOxpDێ+ {b`n|y.8}ǂuS\,>>34mӫE|2wRK)ZFgwiHx;y7WK99e t:1e]sk-jYOKFfКֺyYJ!gcS[~m%9Gu3]=rK11yݧyVz1kkUZj-Sju^H%TYR1i ɕ@JH<i 9nK )Mtxb<o?%/}]cbS,0o@cL:*&ŴLT#Sd2IND&RBRZmBݒnxL̄PEEdؐ0MumLiΑڧ潜S|ڇmxk]dg?vadA!HH+&`ҤcLsn2ߛ>DǾ)w0$ xQ+;{XB1ڻ!Sаd-鲏zo< Z~pl4_[VV[75M3>e"ZVR8Zm8^~9y͛j>~'Hr7"R 4~un=E`MjSJ_xѕ 'oщ$@Q1D#25 ڶ@kG ʞ&\Zk9v8B4 |SZJ!p3M,k yYSA$ 1p`7JZ;`V[Ys% q7es "\K.nU9 ߃I+pWo H8+BrRܠx$"L'ZJP1zx4MP|,q6D̵Y rܚyK W3&4@GKD8P G)SYfA-7fTyƔajZrѾ^.dp=+ev͑2SOweXhs4!"aJC@ԙ(LkΥu^00jhYDOQj|}NBDsӈO62OrxbF—_>>k kL9_Ię+/u[RdD\k%=]:frxSuKw깯ule1RKie,U/޼1ߚ:ZC{/BӦU8_ nGfN'%ݦ]5!`{+1]P07ZL7 HĆ@>G mHiJ}x|p  Drln=qR Xr5pPDy 5|XګgϝWR]^ xʊ_M 8bh6j":e@|eYZkL`^aZ5 C v&7 { R 00Zk`.oc4#1cĦ}qTpɥnSDUŗ~lhOS^jfC@A]u!FG,&EAwAygR˲\r΀B&AZHS]#Իo]OQ6;aG~A'Hg uXFQhnJo o紊!=lw D{d~'lfl}h* w$m^0ퟻ7=}u(#sj#yà*LØ88_}ż>-9##Z6jbx qjMq#K)){b "R /yɭUٖq$i.D%LqaG6Uc_Yok"i?7Ʃ Wt)@ EZ6O V޲ƶ4n]V7$gc Cǧ#M%Ƅ#&D!yɓw|8|4B#m7=$e_5> S7fcSk"yq>3%֚i.9C3IO^vʾK Xa]*bW ‡o?y'cHv1tR:MO,"eYW`fxN)hNP 9(ݱ)>bnvCqܻSΗ94 o>_fӡ!|k 7?z$C#55ӈ5`kVaT֜ChQ {ZS~[W$RS䷯^%;ϳ?{_@0*c@Hk0ݎ\4 b4S)&ۛ㝃 tlZ|q.1~$3@4UR!s."xqhhJDȷ,%DǑř BZvwiZ+h+%0$C/?\_j@h# ۱)E!01{τRLG?q覢 !'`18:F|L$oĔ qbI9Aw݇ܜap6$.n>= }k[\s@d*weG8I*1 a7R|i$ÓS!j{@eR+\5U Nmd/ac.Y!8g#޴lم|xct84uy5ZU`lec:JݧW_ɓUp]oR v(n~,}xvs3 5Տ79,KJ L^?;~</RZ C tczR͋a"dQAUC2+h11k|U5mQ4GKiȔ>||M`w 8e~^sO<DlДB':M@ʼn 3ÈL0H40i:1᳛eE,D T2a^=v ZKC2BU#61'/eYZJICF4`k(XJ`1[ff1yYUAT-y90\Z)z3R2Z˺~eFNI~I4Pq߽4H]'j5؜pU 1vf"9 Kr@!bM82`\E  1%g =<*N[ ړpw^9ZIDwF)0{˟4Q!4M4R.J"VYU[msES]7 DO(:24 } ?L2ڰh\Q["7` {2zlvQ+Q/<3V$t<iH ΗM Vk%Cj6Mx~$[\DP6%00_՝;@>_!|(%?/ D̚rj^r.TIr鑽6y*%0`kQyE p.cCOQazZ8N)%? 4h" ci !R=hMA!<<}KQ\ʒbY)33#5SDDؽO*HaǏHDƗ:HhzBcM2䰃@4x*G\/&ԺSg"!p T.aK0:+lӼ̝' Нwf1\3?r;~g.aaqYN,!3imn/,J-.˴>[9kELpth!nS~mYuv4/yYp88gbZ+ 1Һ#e)?ۿwzOZGб%ٍӅ K̮ڽ40B'i; ιY;ZJ]zMϥV@1:,004CS#R뻏L1j)#4T*m2x%i<_&?}83136QQ# HRm 60 q777dC[+Sׯ A,ygϞ>}ow #BSgqK=EuO*\Tp`b:wL"ͷ,&u7Mww!Ze>?|>ݪBDÐ59ZUČ5cȿ:> p8/K\ j*Ҝ*AEi1Fslͧ{ 6#e-&N6B ~ 1n(еǫ)(Ti5|JjqFBXjm\=ߨ;B$ qSݡA}r 0!vO)ipo֚Qv9꘰Ɩ\%R5QebUsj\R)dcG+1ڣ堧~I{@ð #NpE3#! \~uZ^*r -QTy9kBlNlߘ} }%W6:W7)l1[/jmD4/G 1N)|x?~7֗~ۥTmdDUT@߿Rzz{9J-08~}_T{ 1֊1>Ft{{<338o (utv]tSORJr q.\[##𡴺,y >ZTAS#"s1 iz|8y 1#eDyAݴ;)BiF+wJ eqc \AM0Z7jH4i 9\nBɥjZEՇxؓ8b`QY!0ܘYueY|GbMRZ30H3{vXWi)ڜ~t8 CL̬vsYE4jAkM V"`ҍ awNtEsE{Uvr>X3@e]paELiW7!Df:L:#1CuQZsD-k%p\&xz̕ٵ4 !YRjj݇񜦑Cp#Q0"*LDUw?˧) *R2"r }kc[aNw rzx2Ӳdi_|z?L D4Z箫4 VmH."MI ԎR )sy<T˃*X0XNf>3Zm9\rCu),#&@~r.i NEdCJI cILrŲ̏w޹.52<'wrtok8Evy>vb@o1*!)7Cg 7Ck82zs֗.eUڬ-h(+-rjj7F>-UAUI^ĝddTy=c1XJ 0Ug(nǔrΦ~sZ4DϫKF~ #OZ%/9~ެx'޽U=ce _c[,Ntg]:*>[g#n,UTÙh+u'cfT-(V?"O'Imċ]ҊR*.X0p.BM1"H@cf}DzMw^] _]Ԧ !nCk)LJsj)ۛS@v6x\Jp؍&Zx$O3ŵtFޝVZ+U7e1Jiv7_ݻwzлy.$)k M4<&Rs)e.90[Zuc/2aUkxIxxj!>+!QF@KD Gpxj)ge9nnTvjmŷ>C"ai !(ɥn1*rLqzCp3xLINO4j !"sksaYd=x5&7tDЕUe)ŵg]nмBTupDBoU&* =(y^\h٘<zRVWC4߾3_Jvg.9ك w:䒋1REZWlC_eW ly0PQ#xGOhiqm/Ws;D@l[SX^!2a+ZOqP7$rpįT #Vr̶ o%^W4>}o.v~Zv`Uqwt?~22-z9RնVF|a\VŪ&$VI^x;#tzNJCm_<B~x˧QtIV1*1N1= TT4h΀1b$N޺YWU4!a*seYaxr>s)y???#!i>CNy)e;"D"Հ36F)qbHHSH8{O!┒J#!RYD4@L5'OJ.!\V[k!QDE$L__>OseMϟ=EĜ 3n\[\֚KszQJqHbtuE9T{84h!6z2zjSfQ돝 ͱ|ۇ+.YAߤY D]q"4\X`$I֊nb%<Cy^<]Ps%!w_ǿn?1wONYޮer:CӐK0kڬϣqB5Lk]{7+0jwEo܊m۪Z?b>^}0jL"WO.~roDOkw9vUJ+ ?~_B|(^Eb\YN{m{%"B.(yG!v˒]j+Ki`M9 +UJ_æbr 8B[5 !&)t;*{&Ea4y}qQi?<_DBCDͫg?9?L9%Ͼbf%+ !_r^y^DNE RJޮUBPm>1&R*9;ʱ Vk@6h0sUiH!)@<0WY08ȵyQS`W4."J0kb@e"@J11r)byrw{|8Jaq00'^ymVrM 4OOӻW_ cKa!DWT͖\ܪ0"#Ik>l6w;7~[D\v̗.I 3f#BGTytu^=S)j? )`=\W 5Jkӭa+yPoooW/ϡ~J\ +YwePtGQLr<i&? >=SAjk/^<e)o?6F&bev. Zrb jkUc/ȫLsPʹK1XZw=@8850H)BNij_|_|HSL) j-OG>AL6Ҹ_J))3-""1q𤋮DynAjH(E=9i򋪂̾9Rk[ۆPT]đE$Đm泠o3;^e.H2u|D53TDJwA}Ӽ,魵qbG}J!Eؐޚt2 %ӈ1<cRQjΐ?) `5HGuJ^ZG0&7A`8DF&siJcK ߼~ij>ޗsRCU3sV~"MVM)/7VN7db )0 H)R' !2cs%i)zZr3NkȥbJTZs4E43IjCB!G1@C%1Z-Yp1/K.v9<{:|>&fK%c͛yɥ4U)wYd !-糨y)Z[NèMaMNs)h IDAT!PUJ}YSSb;imӷN?ҫ-7S:lZXhjS9زW`JzEޝbD9AIS{Vz\!]mu]2.Rտs/O3D-0*ҕ֘e1Հ#ku|Rzul~ؾYYu[Ӵ6G.lZ./L5ʗk* ڄ /x[]&d^-~uσtSo1BkM/kmu]cqVѷ ʴ 4SL=Lt<8U1NB-K!_?|~RqpOo1H(aL1Yf֘plPXRdvkIX 5)r|<nnۿ|_Jͥ6([U1@Wh륽6bq1Qc`BKmiw! SD9ya1y.Z p&$ W/~/~w~oϿLqJLܚ_1dAjmLRKif 4iڍ!n8 YQRh{_BhJs ?ۮU7Dc$.sW؆xRZUT0p)jdoa[\~u{2[5(12g^"# jm=_޴ j8gθtozQ9|b 08-E*R[L|u8ۃMf[Z^75TloӲ"Nn_/jY>`umia*is !y xQ6dY0m`& :{X^W&NL (""#ϑy虵1|^|f~v>#My춁@c&!qǽQH1@ Ф5sk3bW61[PS&fڤ )N n?Ƨ8D xsR[5bx:M5(FB,eA87[mZGN1kc:`?Nw74MO CVfHtw{Χx}?⋧O6xaw8bui21tni9G]_Tj3\PLiGV?{DMf`8Z e +"U5ĈD6&hIDڐDg1ڪŻAmݔ'׍(@MfZUN94:,UT^z4תzьmI眃G 蠓@MUM[=)D_F>8"s]4lH)S=%gQ%$0Nb-x+.|_ͻRH FL>c_-?.$NLI7KmF7;>[ m_ #IUPj~ 7Köں>C4R+Teޫ{nTozQ\ky||$a,AE9='Y]81ᰛ&3[r%REd/ݘ8bi-\r-KFi7ǔj4Aa>}rw̻i޽_|:aX5L(aFAc\GF65H)BDE;Y|昢O!Ͳ̌CH1Ŕt<.τgxP3#b6^[ݟbkjq/\ 5ߠ`Jfb 4VU4(ZA3TܴkRUDzq%umá7) (! RB?\M4hHVJM)a3}nW-5f:Y؉ 5'ևOrιH1ܖz&ٴJ zYÈ@ h ̈ >(n +&j fR H՜eE,d3p(EZ3%*"@`c}կ\kz|Wb:j7GnlTU_%{2mSwm{V義wW5[Vv08^Kp3F53՛C`VyΎcYclPmh=$r#\ [f!m;'3Pi̡c(([5st߀³8a嶤qN q4FКH !x{wR$\*2isՓwye%nK8P`#o#eGBL.26V 1V]p>~5 9u2 4VE-pmYx|჈K~x<\B>"0!Vӧ&̪SHKMZ )bHeDkO!,H0R/fM#N&2]$+GEfnZi!4#d CM;q; d]B1/9 U|>&Ą"t> CRߝj']vZKkaPfڛu5 ֥d/=EzӵCD)wunukmug)D߫J.ZW{Jz<Ky>z͕>k)D,̷W_ N&Zg435\ r s(/} Lm@:9@BY/O CoaU͛Yb̥ya|Yw>H5u@'q| .FUݾ u3)* RjdDM65߃֚˱.+g>w ];]pW>oe;b&dF|È78}9gQ)i8b!H vp/J1iT=OAbR$b"1ת4-GCJJcJM$|Cbb5;B TKI1k-Q{GTLEGkkMqYfHS/Ij@@HҴ%cFK]ՋW_y[3Utx(޵c: u&x5@sI)1 ar:L㰜g3?_ec.eV[c9ښ[K6U܃1ۺS.4gX\vP-5Nؐb vYSx7\@!0qk5/xT$21$Mvg(]6b3 u /؈I*DiFjbQEcOZ0ؼ,G7k N3JgiSݏ)0C!"Y +@'3-6iMZW%RɵyxEE1nm(>RG[1_ؚ +Q 8jխ뼀[:74p,kt b3}dB#Tϵ\,*^J5rG}Š*M ko C\ jW/:eUTPr!}~)a+DsCLՆϟn_E4k)i@BOzk; Hҥ[S7Tz~2JPAsSb?#MTTSZ=/uܙhjW"a:G+_'_!$dF"٫N;ȥi[C4U+gBxY|%'6Ck3V9&nˍ㪨.$A?vͷy9R_UE;q5ߋ"څTEn\GMKotga} W?Ο|_z2^{N+/jՠQ}43b90C- ֖yqiϞhfR2/3Hk>}Z9~) ݣ׀jT@O3MD bDJL j6 |j;R|wMq7z]fq!i {E*qXj!-y19#<Ӽ̭eYRL~ikUy!S$ڊ-lb2AT#ns^av{sӱjϞ>ɥ0SmvE$!0Dn`f''QdJݻ?^44 > %Zk"-rՅ!!7BϏl"J>|<0 FcԬH V0R c,D7nEU_Gqi*fCX `]'?ZT WW1pZWfj)znAi=/i _{ mW2B@!05s #z6Wwajp56202W+eض]oooJ˅Z~Zzko IebCUdfu[&Fެ[p[ܹ mJY'jrp+fyӪtcDU&,8WI/=eb:&1zX@.YLô#d j1X-P}s0ݤLDq?q &M@C"311G\hD>^33PEGj6pK6~BlLh*lZmyYm.(%&U9ٴ0HcH۰EujD!E'&UX0":ݜ9hkAiףN,"=}n 5awƈ(Šfڬ&zW 0,}8ȄRUw~A{.bVMUEsɢzB,ȥ#ĺrGsPOI?t^@k.e͑t_.DtU|lZCVH?+lE+ Xwwww޿SRrB-jedț1RUA!"QU+巛1VvZIWЋ{Z_mzK2KkҥBkxvU4/& 4T7 \5sku >REJO|,WU7kA"m5G4]T7baPL7m @d25:RM6PzG뇰V[l1_`]ORH[54Rz.kyĄTDmeY*0)iϮ*f 0u?2ٛ?@WMU IDATkv2S TDx5![=U"*^l_C.m ;pe8Dy~rbH-$f}xDH42?%䒋ZJkMo~O>}~xxib!ښhcS=>>8UÍ0s&~;P&bP[E^r)Uۛiy>3wC eGkmb|,mn7eNC1D`Ln<"Tk-V[kow䧡H#"5GBTUt%")0^Sah$ %$!4 Rr>ψBtZ "c {ok-!Wt) Nbd{M)yn ƻ0P2`n+#i23Hԕsg-R)DU9OϧxH!yvϥկ߽k_O)~cET% 0H+z:/1R-1 r1CwOOTjwZ,fC8Ud-e]-of)%dD)ג^sCGTiK^G"z~{:y}F"" 0s}!B)mfF &f{[VZ;&)^x6IBbj9UXFFXdt(]@!5C$E򇢊~f|hw|LDݐ7&f'3Ωm1)!V3.?ɟ90Ἓ򺊊9PUD#履la#, FBj:gw6z`ϲ)FovOp6EN͞IB3;pZw+PZXʒa2"Zsy::u鴛W/J"-ŀGo^7CqiN! M.XEkaNeYK\Z ḛLTͦETT# ZsYW@||j&fCK<S? jYre3UMͩC?ɪw˷O&؆(Țs!"huz-e#A<18pD W vzi.hZ@]ֵC4q^Lݲ=twED`;<.'=\k-r3U-f[h0n._Anpkc{n=e_e0}jly bFDBQD4x|xtZU#Pr 4#!U'3K"kVm!Ĕ0h#2!КWs`b9M UB$ ki'W_@ z<<iJQDO+Zi<"8"vS?W?)ġv\.MT5׿mhKq:|zwZr_|n0[E7ό#{ƱGȞZanUVEkL`V˧~{͹Rj]u>쭵Z#ciJ&r96d6(hEUUk".$\DoUWRΌ";"*# D{Y6v%.:덋F ձ>qOLU.u5݊>g)+ ~?=fZ1E.rGhY3M)9Wj5*$}C=͋S!;Lhޜ ?u]BAd}5(vdšw);y?u PY aӫyˢ$ƑYGv S@L3k1dem=m?>_}oO4M޽:V"MTs)ꢋR:Np>0ݎ-K{ ۞KCMK,B{|xK}mQ*-t2\umQe ɏN!"@v-ۥK)ڹDLj\ڶB1H-EDx* ud*ˆ뺺*Ĕˏt83./KΫH#.Ïy4:R4HI䑩"ݓ߉[%rƫg`;n{G޹3 iJ15@Fl~HT9M f[R\.1:LJ)*"7w6XM"c$\/ҥmyaܔ=^U}:X#2;ـ =ƶ1U@<ۖ/naÚB\[tbwh4U F&u{z?GBZbw%VW_}=uٝ.$#@M!4rwL9;ΑSnK}93טW/J)˙z~> jߢe~|w9Oѫ)~&44OdZZ5f$L#M4;J.BQ$zE/iҵ0׏/..C\_Hlf&Pd#B6R2O "*Sl>/\vBDŒdOYF(I4T 1SVh)yYj#\tۥi&$E4t[jrOvqCؔ+'iPDS`PEVWuE n36` b]6jnXj |s홸&MSDDJ).kf.!*.ā@&Z'm"VUuAvv`5ՍP&b4󊟱V=n>}$O!On3[t/]kuk:n0,%t{JJm6#W5{# m7@vM+`?[3[!M;n^k^ò,|o,kkBĴnC*F_6s!iJZ|/߼~jY_zf?m8KF Bʁ< ?)d7n#55$& wb |E85m U20]JC3/TyhT1yNsJuuח\Qt=EmD|z<N 0D b5)De_w?}zz'/_c@T8$@̡o [AB. 0ň/.Mш~ol&tv-lwɘ*111؈@G-\h:faBSLBPڤ؆/5׫N҅E.Z[d =#\aY5;$F.?l x-,4]M:WuB=qh8u]ٍ3efP2>o3AR᧦y#o:ݛon!TkDKOj{_=UZnhj!;_"t=ҿ/69rۂ Apr.uM̤KUq{Q#:la`V5h"l&:{a.E-=?=ÎӔrKΓ'|Gk_}aJRi·/W@k#W/G\kU?UZsAx\_Ӕ+# $1)TiIupnJ||)|捊V ( r@jU3JW_c"HC )i7OM_}<vČN%0p3kMȡsY)\՗F FyTr& ȏzjfAMKO_QaiSQ\F$I 1'L$j[z"b@. WΦLݕM ݁t$ftaB5p CnsrzDX5Ӣ_NfS5a$WlÑ#MoSL^qšsim&DRN5KGޛkP;-ԝc/ӶaW24kiTm:[C~fA.ι {z˽^#ᒇ#!!G|"0"Npm?V#ׄ=VUUk!(f464>F&[5!QJ*ZNo=^5:E[,t~>1QbZvi,%</[k 2E䲛@HZ˵AC0vHY!CQQe]W"30s0h>HBF2Sc bL`LB*MrLt]mL t7DAtoTEZ!r^1&hSj-kL)0wͻ)x< ۿz`fݾNxӔ !wX0Y/|TJ[իգKViJbFMZkGE7Ì1~JLh\U7l{7Ħ[QWY?(5ij#6! !Xa}# B 50;ajm龙GqݻySHEߊӟNBjt]\j11};^Uo"ܺ;޳6zݪbzMلBH)WuBtQ۷nݛ2qjښt4*vk=l?vQI$PhwrKݸT"ސplX#ij !R`W50\]P{آQ䉉Wz׳ltMsL|<hSs*JOOObO?vZ:CswbJ!q蛷o[Ӝ5g6BjpZrʇ^fZm\f2oIST `""K jiښ*TZiJ~7"|r|8$bF6dby>~ dE}"F&TԱKx:].^ hsIHbQDX˔rjKNRJYDK)3.Iu3u,|-ĴOZ!!žkJ3 I0t2_6>VѡJAVSt?~W5&&$JW>q1R{oJ{yx1f> C<]Ӥ^8[\sٽ7(:[ omvoQض+7hM`]W(sbgfٮn5Jr 1oajt:&<+lpHD+),Pk6~UݞmU]"Th{F~jݔ]g;ˇ[ęsSl`:B:`8uaoV1t_>>Zt59b6Oӷ?_;E`PV[bȻ)W/^Tkk(3 Bv5(K !p1ȥӔD'֋P H!1?]JQ@ucRVn1Քac!t )UDiJ)%5}ry}Zi=Z f͵)6~COsKrQ<ʺ|TiS,{i ֜23B58?= b@d߽}v/O)0S) 76=-I bj*֚({Æxyw!nH ZA@VZg41"\?@ o~yYb20 ]~DD> fM!: ι c:S4rՊO%; ޶ zkd ɿkChu{|z%4MvLJJm~%lj"nȟ3ݐHOiyk]6.[f!v53(ۍu~rFtҋ/u2?|}!0e~7V$ #BkIw!sq}9P{ nѧnӒ0zVMA{شaUP& L$*L3 "mcbf ZiLL@>ff5U+9g& I{d'G?.?G5x[2+ΉlΩݮޑ, }O6~逍 hȄ 9 ޥ`.31e 9 IDATUMܑ[%YF-6~J6"&wGEOpce`f! kp!mW䲮!LJJ69x@ %n# nʝ.T|ׯ 0s`ZF};9Zn2r&L*꾱ptF\ z!yj[,KnԜiJޓ!clĽYsө=׈("xY׷޽~RJS*9|ɯ4%l}z" |9WsLV/b)Y!3Q03<ܬBEJ!Lyyz; X[!\r)U=STͅm.%g)$냞w2=뺴f~7vwzC@D9)ei1䷦A zќ*JD%ժ䲬 'r ×է[CɹM颺wey!;vsm& n1;TO%lkB0-so%3Sc`"Tk%gtEݪH՘ 4b-ы! ,yTh8$Kn73+ԞM]~? !*L] xYRC9Vt )X^YݑDTnf ʤ]=F%~etVGdd]7>ݚь c Ԁ]\k@Zbr}=6? }E6)HU'Rw}\Oׯ޾yc^c]=Ԉ?l]V!-iz]vyz3M7]5­5Ie=@!xҤsn1g/.*kk.,`ĜR̵Z˗oވg}X ] 3j)?F\j]zy0ŐAUḿ^c^3tnCTs.?{n߸@:G֚͠D8]O3%R>;L;ZJQQlr.UtY_}WZ|x]9ƋByWVZYeT⇏/&!Li:jVIkҪ:5cb4yٝXi)6l?vi>;rY9t/?4e`!PJ3 hS<v쓨j.|{~~|RS5~jHD@j9{#m2k3f6ϓUek΀j*ph[# i4~woc`!g G/>Z/ZjioG}qnΗ`&و@*\02U /R6#B$k LUM{_,pعfЫ;]\M~Fa)bJazMq'nQg8D"xC||miU)h˅;=G:4_G~gP )zM]QrEa_Eʃ$Ɛx<|WN*KAa@ݔIDIT5RC5Ia-9LJn7IM U5AU1x+cL"MDh,Bl]9DsY.ݳʁ)C `!䔒je!9u!"\6ۼo[lpi]aSZL@C`\ѨVzHx=nrz~CFBStZ~b-CŶ LkPJ1#z%bwD_QaB i!6X͙2q&!05W7Lr#bA..crӶ߾Uz0 >AA@v]%90?ɝ RDUMc pKN:ݠͻAsN9n`%BSڒv| (l?6`^s\\ĝ;S6 2|:skZ;<\wy]y=>E_J23|]B`dSQk)_?=?>z8pj7id 6E cf˲朏G[!bhRsiUG`sü楁Z4*m=gbHR*z"- y7L/eY1G0MEI[i(/ɘ+!2F$DV`C04'^=>Zkt>ͻI|^Q"")Ζ{#'LTE"6Qj`ZjQAH^Ȅ07-"`bk[@aHDTeTRLf&\0pX%|j<!x%*]j?~ V?C[' Ū e-%t?k[ew d5DȇDzϭ^0v nslgܛ13tK@f_*ӹmH{q E { Gt0s 9[dSI`3i7lbҘ)K|=f"D#0b5]ik PVݞNKPQde2`؂;{)ڦOv{?rS|>\ ||y]^4O~ZUfjMcd TǬ滟~z<ɨF$XhH#&NLK_Mip8&m-iJ )@ӔqJ5eZ8Z !~/PU@5m}J1KC;3k՝0B1Z.4MhԌ1֤D]GL.w'>xYY=ZDIQL6DJс$]L@j)ΤZLBi K./!Ğ7&H0"9p&i:tz~[zBpnB 1u]e ۧ'=%]e% 3EdS=O|~7&C@ ,ra0f̑)8Z+){1jw;~o٥[tT4p^!V }z࿶b;I!M1TKI HkG@.xxxc?{vkfs&ء^5̿"DTbpﮗk]3!*:6wc }HPFm w)F "NM6 ibz50UQ~ma0-{= $ Y`4mٴ\E۶F??.?9֜c[HѶȏ-v\v݌=1H8Zl[BD/_>R=o-E rЛ/,_ۢ\&f"˲DH6yϡ߼ ܼ5lĭ*:MӢ+ީn(́~ lMh]nޡgT87!LS܌9[Εј60sV]c} M9q3ǎR̠I .kMvr8IԎ|[r\|Sz0;UVࡖ`SJ* *LҚ5!i#Ak4ӷ?|Z4b b IEׅӘV'kl@l*nD4qu  S2DEM9הC]pw痟zYn䬢e)1r- rLr.&r0NN8B(":VMD [:Ŷ/Mnl;M9tu'z3"!12R_pw;@8-@ 6. 8$ujaO1 h҆dZzXw"}-'7?CyRuH=zm[VYE?؅ uwm"xȾAڲZtlTp!"&/lh`Zy6X׉ ~7h!RhlZ:0`yz@km /qMlixx8vڀ3B'ҮA(IFn9}MJ9FuG<1I֜i@@yeYtSL1(%{fGě5U5}q8:OY@4RE3FjFV ┒g30^ !芾 * QŅDX !^>}FϦ5L5ZY >W˚w'W.,&Z>.1b0RJ^QbPs9_["M QkMEuB$bBD1!cPLkv y' T\gafS})tq+n!{*r<[R+3{:YWÏS1Ħv1ޣ绠l)ڤ'm&f "7w3.9Zt0sZsšX5Z)]T"wܺ~?7!v_Xm]]CpQQI!Իζ,P}d#\ `&UofJ@cTcpn^"אlzp,ХnZKS"rYĶ/pMιBi (@h۰ȵBTGT5Dr[RkMĠH%wݚf88rBRkeJ|:I [i͈D!yr]>>8&dF$ۏCCgȹxnS..Uz"y>w^4ücVI{ꪌ6a^ =67vJ]6 :wyܚO~ilh7b_mX(9);QꌸOfRk=ajp-n-r`8glB IDATף *vC iZUoX{+ݼCw[$#2s]O hCwfpwLF$9uwu;3#ZXY ]r`Cz]_ke9g0 1n>0/~aj`|8˖2$"S/BG4g?!1*jL m6Pr6JD96HjU~1r=y^ lRZ}#@!e0)XyV_1sbp~>3Һ%iN1="V8(~՗#"Ok-0TTZ9a:R+ l|1h9`eiqO5!Bgݑ@MBbOsi bj 9jB@d&R"3lFmY:>=H\1M勗UCL FLZ&U_T5 1X-58O1-ӓ\Te%-gr2s率ǍS:fQ{m2&C : 3NostԪgU`a!׊-c(YeR{T(353 -[ @̌m ]Hb"_)1pÇF cs?zg?5d(9% YJՏ$գ[^yqx7p=ueXWs7|2pG(RՀ>7dVĖ:1Qe =|y1K8v3`:yoJ*L)\* }Kf9o/`1Y]h8ռ&%êNObRr_^ӜDSe]6sH1$$UJ ):RpX|y/(A69HĴG| ; l`~8B7Biz ؍-_ Ro Oz ``-*U3ضo߼՛ӳh"º,j f[O˻{8-9pD-@T=HUk-ۺZ<(3t]*۶Z߼x{}v]]ٲJ^/˶1ƭGbOطzM2F&5]L {ak{=ڌ^j-bR&mV}.&7H RqTdr |o?]ovY)ZED *2<%[Z%9>FCۥ]v((hBc$ #<7˒ ]Q2S'"QnzYnv緮Nϴ,2CEz`E6D/j_I4iJmoVm7T$Pm|DUj |˟j"̦Su=yۚ뾯+ߣ]+6f q+f aHzJuM1NS1Zu; |TKIiϖu$"A!( !q4M ~W1Mf,]C~ B R )"͡3Ru~f/|>twZ9apg | 4UUʁkgVEԬ4g0cJȱ\Dop,!a\~/>jJFbn,n&ˆL]sFcE FOgTP7?=DۻHS&DZk0,"*!j4$n﷞ü:u=9Zb?FǞt>_ *"GK_-. jjf%W)].J 1ۈOU1R *F)%S9kz^ׯg#+nUDgwQjSa0" ËyYCʺeY\B)cdL%HmTCdR1D 12ZT=ήi?|t\LɤV1b[HSg=3@9DEmOvc|ӣRy"τtaEe 1R}`?3Ո|aj,mPGOPgp|o _J[EE0RJ 4:MaԤ\#=g ;]fr2ᵷ1UT < uE+r t! .HS5&PQ 𷾛!Nt`iם%T#x !D[U7uIvϏ^:ktenTԺ S|TD *]d[p98g#R)u`ݺ1\?zq&sBz[}cSbfdC9槧 pw~4p^/0O4l[뤳Y1d ===[ZouȄ(g'|~B'V|!rm˾nK3!bl*bfD 1PJ!݂*1;+B`1pCXmY頢rKi벬 2-EBx]6P޵1wD':m[RtΛ?FcP3[w67<7㣾x0Mɯw-Np88!@CX.4MD"!MDHiJ1& JV%P5g*C5LBy};<=Q ^׺-gks#V'TE$pPj7bqDHksi8u+eYּiN-VMw/uRmJ)VE)dx:^Dhru($}-gk”jV\$%ޓʹ@-ASݒbZzKRlL)eOmr,9V5hܗ^Hjn ~MK1jўk ;Owx˵)XҽuĬDJTH;˙H6 y#5sn > lH1o{s "?|~jAF 410É|.}0";=B3R[;w߲U-o *)d]=z_Ǐxm#so= {C淕J)`0"1D#ni3D!T%26kD UիffG`f1 0Ow|cDK1%URE5-i*uYxQm&D4Pv!>LsB^΀SJQM51UU<11(*Hdj" ֖cL-o^R咋!] j>N /,R+2yT VUORtQ\55 ^ViF@ʌ 3{=#[1!M1*2mTmNHVlb!5p*L\!"ywv⋟8`rWC{d7y|p Xr&`GĝzvEw&Z|)5QR'Ǫ%;z4"AOY]Ь6qkpB-e< -Ž֖"V+˦P1E !$S[u]־2PQ0,ev'1|@DfQ=f0-}'F(5J9LSAnה@TkMƝcD@M).] -͋eY$'EFj iJNǗ_~B)[bJ}p#Rah`u!,]d'J1pu]RB!9r]|>,x:N|.kYkJnOp7\V#M@&"(0ql3 cFKa&20;Z/WQ`j.ސ'iJSɹcSif 䫻͛^y!Mc~I(E$riԾso&4ÏM-(-$jTMF:L/U4o(^JAPn+MoypB4m˚l1k97Xdy!'n՟`mZ }?c4}4ea70jAri1!!6 !  #ZdreY/m۔&o,jm۬'F=fTM67&D̵t(G nib\8Z%ȵqsc\U!vMaw&ee1AT?R !ė/OO[.1%3^0q 9LNPbx IDATH )w,*91S`y RJ1@=ZR /~OKd?Tj|/OwLx:BRH6 Q$x8V1Ffdp!QZ\H(R0S"fr붖RRaB0ZjJ1+3VMcdbf6UEd kM: `iRe}ɛoc-JGb"&kn,O !VF{)$ZbqRJiB@ Ҭ`MSd}KG?wq؏\it3},-;v#!-7/þ4DۧW7ϏFοUWu *}|GF 8rdX4l* Ȟr\j YvQ& zMdmRxP-W U*ꢔNo,q6κhCp'kfuceH +*B x Pq&vvhHk!wjO0Zu**rY{ ̶u}zCUAR &uYbJ)!`A%N'U A71s[q>QѬlnq7t4f]]_VPMc;;tn][t5q Mu7iwk]'r6w߼^w_\cKB y $(|=fo5i71ߎ*l(o@].9q+44p=5dD*RdDnנZ+@(0Ӷav; -[ 72Q'_3 *$2.D@$f`)`.lIo˵ D3*Uh>߼ZLAUk.n_͋WI!֜tӺjR0F$&_i5CҤdiWwY9pADX)4K@Rb-1MT"8yc\+3S0R/rU޾YmY)Cڶm,TbE>PU<* "< $"gf0S@JSJ]83Q!Z|QaZtw=@mQx`)ɦm*H^1)2Q\%N)VZrZie23Zjuu1RKZ<Ɵ:hOS7'%zNGm:P<@R )&LNǀ@coX *L)r 401RB'R Ü&#Q mľDa3kc0ˆXһ8V;ݳ4up5m87}g33GM޾'BRle~?<3yI2NYqiSnuA8OtRJe6_}_ΗHT%UyYC!"*L@oJ*BLPOCȥ1!^ױJrzq3 { vqmҾTO@I D xA *QOzn1c[]ZO[|:.p\.6/^_k˲|<֒ϗ+>y q&&Z ~vÆn*gj[*3\D R}`&yS\+f658VS撋 GbL {f뚟pL_rq{>c붩|!"!TrnaD j$0 )x@DotT fj>;mdRci-a*dZ E 81LDRm!y 1JciR u3$RT5CtʖN?N._|-WEeL,CuFԮ5̍/[}f" E Bތ@Sȥ8Q:z-6:'oi14%7XhAJD?vfPO͎Q3SZ }YEΏjdO ԴLbrnonCմCRp`Vr9/ⲬRK.ׯgt7"s|/o^R,B`m"Y cFب]<*!fbdBࠠ0Pg{bPMU|.nO)^O.fFW_DL dĜt׫cxJ1ƴr]!b>} _K555!hĹuY]<i\]Ph)SN{90@Lk>&RJf7@fiKx3Q됤NM\E40@Zk]T??)UjOSƏő绅=eaQstmCeduNW.&=eBvhasUۥeY~8w .ڋݘR?K.+!ջ]2Mr)"my6͹ 3TQcH w#NWoF4ڽޡ} V3a`KcK^v߼JL3ALEJ6 -h`Jt>ܻLu rOMp=?J|!fsJM,2o[^ֵj$p}\CirͮZ?b,jpIS@4)nS3SAW5m[^w.",qc1Cfd{11Z|ݶmYSm;N1r%sL?addf늞]U8%;MͤVD.Kۼ2qSzK &QonPSDRQS5BQUAAs\Oi QLeYW /le1=84yCfjq,Z2w-@5䞝\KՍ...Q@#Ӊ!pcUu.5lX4{)ΏNAs0X7G#coK$I77P0 ap}65;iRc &KDHL!0ľx8R[u!Z2^gA}]oF#nc,F"{]edlS>[~H G6CF;MG eF9f)Fo t {܎9lPtFhJnoԞP$fA3nsR"s` 1*(LS.,@߼ǧb:N*Ul\iK)m~7Xyv;t̒[gM1t U qm3߁!6*ofJd- 1b3`YfO`ϩںmT}>"c|*b8ͻ9֖fD(msׁ٥"C'ZvIPtYqz>4$; 髍n$we!/>mp̒ t^Awx*tv:A iɾQkzSJ%ADu ́|w<[1h ~o޼xt:qLE*.A3jּT#  1NsB dWs?x @x:L`D 0sKM)4%$ɥdV)\JOD6+mE(ouY6O%iFRZhuY_|qwwZ HK=hVkЮ>Z|210PМ@@U5Y S:LPL&ci!-_׭dS 4ja3Unhm*JCh[SJeі|c.ۿ7-8n/= n9>9-"?4org; GPw3ƝXEw7͡I#1lPƀvG|꺞Zg -fc)yw$VV;<]5ի9/v\O'MB]f]PN iZuYf",iϹ.*H..@3f& n#{ϝ ! =96ҳ# {\t}<[v k@V(up\y.߾}{?Ͽ;?]!J)[~Qbæ o AcvDHfnYFbJm, !><ܗRϗ 1Wh)%@uފSCH1"Ku6(|)ۚ71*8R@퟿xs^וiZr&"K1O?}K^6һBP|'Cf6y Hv 35k <<֦#To"b)-o[ޖui tW lNT)N)qKY Df 9U*L4x*B3U5ik\d1??.W좦y%5T5"ujm??1F_ 0kOϭ٩T6w Y-{vy];l}Q,#XGfs.܊,E3*uAiJlj) yMkVjAi{9&x3|OL!t Pk%!54M,UͧF޶O)Rj!mmK!"bcar7Fl˘ˈL4M*-V}hL(ק-i4)+b[׻CZJ.0G K"c\j޶?/~˧/-.\/UpBSRJ1 ReefR1Z 3-6MɣOsULbUT Q]ŧ֔&BZn`LjZO#l˲\ D-#c5ڶ)!@BAڿ;$E9Ai! 3"\e>͊2"#з2=;k[-*˲xxebk}S-%QbKǼb0\kZU6lRrp*?~}6..~LTfvDh{5YֵAy]js.}|)e`m+Y%1*7V}H65XnjXO ֈ^WhW RK^]r."R>?sd~3_ϏҔ4m۶<B[Z44c @PԴx@MTFqoZTL6Uj݇GT|DD( "޹[4HHE$bO@d",՗޹uyLOZe'O+hr}F LU3a}%4q}f"B1E`0$Vk΁0%7ץ7E"'J?B v)%wJ۶l1 -KS;H!o'}@=N:\Vy#srkmY?~n+`A@6!sxx'gGfgl8a-1mC#d:iW@^Ww perZ%4͈~] ^m6M4m[^W;ׅcj:Oӑ&Q̗˲Kl^a~(MM݅F{ַփ*JLr63gHPb?,bPkwM  ^[6Cpk*B_KM,5 $"m P6x@rj܎Ⱥ:OՔt7RRϗ@.1pCo?/t’sx0t:MB|ca&"W6!R択G"j `f1_-` DKy˹4%E"@U TO%\4||n[6Cb4hy]bQUwww 's0BEWNAw^1JtknޛZ]}sU=+QR$c$ɫ),=ـYE9''KI1""(:Zs1F\k8CQ.q{9}̔DMF(Zԅ?HZET43v&6 qz!~ IDATM y]cI*҄&ϒZx4wpHTfǜKVՁ/_{o|;*XzZd;'K#=)G 8>~يNᱺ7P m\+  &RSJUT :Etu' 1#b3RTvk[p ۵"E]9Q3r":jfno`L1(YIr+~*(D? ̽)haЬa"3}@@u"#"Ra-E7 Nk/X{B0 Uv]"Xƪi*nIb[J]?(M5'N#ULdSݷi5Ju`;&I]G2U'K"3O-Mg#)hp!qծr)`X,nͶJ阑H@?L5Mub~[,s]dXts+Đs)6M'12[43c{ Xwr),j~G+aـՓ+Ui(T:V"BĮm=n^-WЗN8zT~ˋa߯Ϟ,H|5~3FF`BhQrg i"꺅,%xվv=LZGkun\Wޱpshcz Qe*3LJgtԲ(cri5RiWӱNƒӄ@ȣ9NZ/sse?`nǡqLcS>?[/IQd HH89^}TReq Y3XNoÅa-W22X΅8OU1 jZL%X۔3PԺcbԧR'7S;F`U}tژ:Nڦy7s^ls13!բ$gM 6=keH5,Ƙ՝vKN|*X%"G(NW-@QQU370!0Uއ'z|ZJQL.oo0]0.ODnn50ԯd^0596!j&TjF_Rߏ")O*2~/K?#?<>>vÖ K!Ps.M otBUX,MidSZum۴͛o>[1ųO\^/zfJq 8~گWo<Ųc3)%8B0a۟D+6Qk||kw*sgLTp麮m_N~ \w",pa>`r_fy߼K h?8^xؙgxB{WWvݜJeu+e}9}C9"dE@jYnqXهQfVv e 1ƈΒJξ_)RD}Xh§Wً3G4"3sp@F09gkۢrPD<1RJoF4)qLu?VC?ڦe"'szm˱2fa@rn814!vm3)6Q8&."}Yd\jK89Bh 3#~sVmqy@ 0sve<8¢p7>,a"F s9Ʀ[ @̦&x覿_7Ɵݽ//ۿ9Sww󗯎zj{;pCOEa*b"qYz>vUdDR)e/G~xH#|O ]׆ՠ"r$S3 TK̮͝=yΟs_N)s_| ]}u\?`'7_oDqM8d0H׵iVgC?PiLHtuqBLhJG@rrL%$T"pTVg_RL5 ,C` FZl{52<(4v]4Qڶ{8 EJc@j& cB.| ұ $Ӌ!Y]j5pacbdU[ujDbzD`O%pӴ `"Y\m(3чaWZkf>i[J  **>,A"~v_W+{k7t%non^<{fŐyVv s;c|.5]nIB-1 aJQSR._ch9؟7o0/^<{7矽rN΅M? k3 nnnnw]= ~~|z5kۧm}.eUh: CD]l|FC3T1y*fD\(Nws9Qe jD\93IQ82P}6T#ވEjVD?>pe\ʢ*{Ɲ2N#Ȅ7p]ofo6 sW$R `3Q-9_7)h{}ø*X)r~WxF07<TJiLK.cJP"yǔƳ%BUsȪ4c2Z&)I%02RĘgEmn`~~o:B4Ϳ?sяȿ׿<0Iburb^x-Y]QVv$W:ZSRnoooqϖ'W?ADϮ, 3 Mefp`bNr00xb 4ufo*73 E)rʾ20)MB1hf4E8Ru„ߜŠ8_ԱɄ}'9Tg2&&t<_w{Ҿ"gVbEb'S֜' ؇9saLIT]aZB|~Lv&2cM "DAf!0@R Ԡ٣]32@qL8C}U)cZ16Hf}zuqjH],8SEҘi8&*Y8׫áhټdjLרVDȄp6 *YN#y' nfm4!vf 1RI8cȫrT+lI\LqQ qT\D%T5RF_˿_7rۇ"rӧOB{LjOjb<2v2@OB8|ݩ@3,*nD 'o/.szǻ}\,W<\16}p2~}K\OؓBBt7/_xlz,O>4G/;e~|w߻_cXC?00"Ck{"9l`͢yxxd3^nњZNc.\z%B+Bl\æݢ=0}ocJp2?9K ! wix̴P'pw5}i[p>l:dǵ`OVU671 mS<.\RJsa^-*X׶"4ÈU>Bkc\jS@Uj@C`^ 躶mEXtjaRǹhJW;۷s' x ouBMy`&M`p(LU!0" 9FL0: "0QJi]]T8LD,gkD*ɢѭj%djeeP5ɤSvJ1|yLmrѺmz4M4]r Uq|uwي p`wpa$$4-"c۶],&*L,9ڔ]矿! ߏW sE lB@ &Rr61K@Ե]۶̡w@Jc)B@I̕1UeUZ!zٚ00iD $WJgC(x. "AN}E]k_?ESڶ9_zvg{o\^}RE L M[}<0Dx*묶>Ǻ%M嚲3дlpwK6 ^!맛ᄩ8W&T J=#fS@=>sߛjMӵݓ'WmcCwݼGI-MU&UQ)EEょq8\sy~6Bc`1bʣ qbPr]BU8Dsږ ': R)JRY 0PJ)TnJqQ MY_djR` SkDݡi`~<=!j|7իWʄmM-nJY|v{v8j<n#4/e037͚saTu?_'8ɫE 1Y.R)wUumr"0"m9,nwj } C0G3„pF):)͕ ZG\?f"F*fCd.!csj!S1!&{BlOfZs <̩Y\_@ԋPL{~'U(1nZ53nԻ׻GD7쵘zO0}?<>nӫϞ]A{W 7l5dޢ3n@'\wQ=~iSO.3"09FD?/\aDfQ$R"6DuRU8""bǸjv0o<C RJ.>Z̶i ιbj3N4f.M._Y*WZq5WvX>Q"MS4rE91z.zBn.i߻+_'$* |y~L"ہ'k٘rNi1)eWxZϖggg}) ŦnL\@q0>k_ZQɹpn80s䀈RorW8ν޺"S׍f6 cașjr)G$ f.\@DbV8'lN#Z00m UwjRX=e͟r8SHYMTQT\6!Dہ~@iɧ#^L眵no'S)静0qx{gC?󳳳/^\^?)Euku?a4:[TdsP_h'ʼz UT0Ѫ a_o7iLN{%+HjJQht=."P,3v)ޅaX.ϯ/.]M-˧ϟ޻:v~YoX08swZm/_T^VvK!iu`f(6]'"fP3y*Hu0KUsY) dUdvg(Jb*+S!|1@"\\nLq`<)碪9rѶRhѴ^8Drez*u2b]!)yChfdQg~*9k"Hd$(Q6-0D0"Rʛr` Avn1XJ jl,eLq`h]y\Jޠ|KSۿ\JDyv*"dzT\ql˫ Q02^)̷Hdbi@ă-yx:޻#û 4g}F|D&b\.>ύ/dîi܍} @)onn^[!?0@o[)ж&?㔳aifLiS\Ut!w{Z-#_|UJ87y^hJMdRS!&T55+^5!L8*Jlqכw9s$DZXy4(nkX)Ifv[trЏc Cmۼ֛~.gJМMI0@!"@X#m:b1n6;̄sNx"9ji[M4Sݾl D1]QĦb*pȥrE8Q`/BVIDXiE6S2^IQpiDԬq܋riP- `EM ڶI*XSm JEL͑mHSE[7zY3g @q"W.*Z}ȯ'<:۟z7MX,Teߋce&vm-O_يmVȧ#(DDt<'75fb4!"SŒ; 3y::ps{gf*R}X./beMY}7blnnnJ.vn_j2F~S)`@XdLā-ҫWwnх c\@O>C3K9Ov~q?ivoI4iN+PGLJGx\6,I)"0>?[ U #sQuV ϳrwjZlAJ@B)s!v 8<OaD=c3No ,256R:z;s4.06L]֋UxC =\8S#i<-]Fbj6%2U5W7DdfTOfǡH15FLC`H\SDij!YG 3S`l3ip@)ƀE@r!@TJS1si*솹U #(K&) K}iFj`F,^\(@W|`2#oBОVI$K)3R5*FN . Ŏfi `Nw*Œe鱆<Q&<0NQ *$5)t8 8=u\߂C? %㘗v+e|0@`#s`P~oR8O|dg+ E\124)LX(k)cPD"\!pv*E"mz:JQncۮ='8mXB^,;k*T[q[ZBE }7iARJIwE4:}G U_5ǒJHP"@/9H*ɦ8H =c,RQ!l\.k?S?[["S2:P~ N۶`rV)1h Tճg׷ww"eǒ˜E 8wSk|VD=Z:XQ͊ MX[el!x6y)'7ø\u_Ï>*&%` 1V+./0WW?&)EGDIQ vLc366a}!~wXv[/7q+E0sٷN)ʬ>QÐTbYƜEg!6Hcp_03Z'`ZϪDo{TQ VP *O&I3M{/ɚ~=& /`á1_]]&B }?jtr˦< 1m׵*(⋫'L8yh9;MmS4ssqwlRve և<?BDTDSiO<1ÜڶMEr)*LjtsM? T3D@'gOQPXtiD{mlbʙj5N y@.%]U9QdN0V"P#'"P(RJtڟC8T19r~H"a|!Ɯ3L>3(R JR\.W7__oӵ_8Gr1h}>c*]םqL!$UJ~j"yxx0ੱU!*N 3Jd gN,&2O oF$_Mw"f",Հ K/Sgis."O)C_fg/?u"SwEvkSkOz4޺.j Øܰ9S5:97W],% !&^6MW7imENxLD<#\s!%K%b HmwB8MNΪwBn~ m3Ħ,rϯz1eU2\VabӆR\d gf$pqp5.5Q1ҿ|e;ẫ9_yĩ35M<<8]]=vm?5䗈P, Eu4ņIJ^ވĨ{zjfFQ"lB ~L0Ymf}!~8{]UP8;CEK)?]|}_՚>{%\Ŧs)L@H)m4MFTE(fcΠR[WMlWM55] KdbQM93O i}Su&d$=MCIZ9_|7HM$Cknc!4 0>=c"EO>ח]^?o&l%0}!w);pq?E"~8O\5ɒJ^ 5Ð6lMUOM'< \nw~1|hb8;[6mRL۶yA,5Ej's4DMjX$#9[6 Nr@N``(Oaat-!'vrA]U᏾m#9xg%@1GsJ)"2j'LD]d3 h?&3!1n'NRR')=!d)#:>yu{-7gOAn7@5Wr5f$q]}C,RDmpϮ3!0ԐuIt.% B*NL-6U۴UbU ƺa)BG Cq(RiuEҔj_Wл;u0e'ZQy?xb)<jяzZ| d?DUI ueU)PJ ?>:sc#@\D`8x_OLR_w5wߝkA?&2:mЎ)N!D<=4MRrݤ..nblbMOR甥#s4OmEa8gT\^ȇ20V=rEuwe  H\J3Bl- eΙ9 O?3j!EDf:ڄNMoX*hJ\{8WO={FH|.ĶSbK2M 1<yJ>LsZtLGX<9j@3<;I> ǧ۳Von0pQJRfQ]bxrq91vLLgg0v+RբkEԒ&,it#mrF6p{ddf$.'"! 6Vj؄8f㟌KgP;zfww9TQMy&r) AqM1Xd-9T#UK`Ѭ'gMis11ϑT0meUDlC@"*m^ZEG !LD.?.G~ߜeM0?5DZyg(1m[3)pap&bѭ뫧O//c TTT+X$ |x=EWոeFE,dc!!nsQ`s 2?9gJKQ)޻W?qٳxyB`4ʹ6B3RN"33yK_O'-ApeV€8};3o6۳%j 1l35 @ʅXY#J.gP^̌!n›zi,EC fϒeyZ{:p5WW7C$KH R%j ɖ_LA Iֿ!JIr@ 0CUwMw3dGFxuoɽ}XQGV|f>[tG9\52ux{QЬI(1T!a. $ٔ8UkZcmuDiQU;q L B| wQkסV^ :F>f}^y5DlujCDH6J 躾nۦk5pӴ;gXzC3|lښ8{fU#JiBA7?sD Y@t6>:{ƕ,'х-7+gLId @L$NӧckI(PQӴMnm1e!{./~}.O>DL7n^GfҸ9ϑ$u"P*l|{XSYKb IDAT*Ăռ(pvdnECf,hI [ǒwP%:;=;>9.BnY)Ba7]{|z*ĔHW5֚f[3u֘7n8PH%AB)o\",Z4mL3b D$,DҶnE<\.5y7UۍED{IC}Xr8"ͧ9A{WmL(DchɈ3k疔-:cRsXEU {'>>DDŽFa, } 2[c1#5"OQsmP1Z¬ga "~O[E//|  QrhB 0| [!QR+!F^pkn~rw_!ᒅ7]R"cI_ѹ(PrwV%>}zvX#",s1z@}0ׯ߸uNڭL?OcZ-Ξ>6O" (%Q'/^o. _?WdGoF _zy"7m-4oVO$牍n<: ݻ\]]&y[gnZ5f2{,d ٛt`o8ԇLc a; 5A= ^iͦ^.֛T4O'Kk]ӶG &FdM4aa환9+ qWsۡ! B6%bkC!@cR35'jLdp-/'"s7޹io0eр'$J|qF S@ jV8kXX4SU9 feq@k6%(,֛QJ铟xw./?ūoNbgv=311/L[Ta:rPF1s۴I)@Jxsof\^vm;tѡƵkGWW;CtXs'O3 ]eC4{"uFݴWWkcqM2Sf]lkauֵ˲-iWK|[70_/ϮNOOEWօ?q|yGŗ>nRayk]ogO}z?(?_v7IRN))a<ܝŲB4r0\^a/sk{M 9/9cwO3&&~m:"bks cc;@wD#h`_5ӽK[9'$u+;˸w̔bLwUYz/ P#a눙(5Bb&'{j<<+297VbUjbXWE( (b۵b1_g[KfmJŔb"VfQԬ,P((!FN[Y!*/"Cu)q EKYчY}LUՠeH"عѦ1v4xeY2KNN(5ZCnnXZ"n[cspnCGXDGz޽.m׮ dQN fBhMdiDg4`&W+piɬ ֍އ>龍_zKgpǮNg3NG3O[e2)%ɵEi>!{Bp!lʪ}arCCo}뵣'xjn EFMlQJcSѧR=?_KS/hYxJ>⽻Jrf0dF `y1ނ%[Azh+%ְȈ# )˰ϵr8CT{NϲuLMvLB٥.f'Ё/Mćf7]\̚bg~xXی8 ۺѬ*"]˜&>$7upoZ͜LJDnEھ|0mVUQ J^o2t0+>7n\GkFojλƐ}UzǤCY>6MuDlwjlw[ǨeǦ)>/9? #^rۃPDa"6I0Xk03IM bfe (ꦅq#c g/?O}S736<m`=Lhv|16/c ;>}ͷ޶C?_6. YC-jăQu5F zkoa臦nxu|gVOϗwb??Σ`/u]ġ#ք.NDm;]wh!|/WhwΟ>/.ڮ=q|t|< 8;ժ(u{Ci<4Ӌ矻]Ba׎כMv/d(5,8zLi}=}}I])"cbSLEYyͣs_Zz-WUӋÃ9D֢58=mvZ %s껞R"h;+x]Ӟ>~r~~A4 b8<<88Z9 $7b7]Wf& g ԶݣOr11ladω2Gɦ[؍~ 3*Uwɛg>b`#y\8@8c=[hpGĔ4ll^eᬽy$]nEHRJM ;Z&س)C&YH@5͙04ŀ5CLlX۶Ґ"⭵,CQxu"`˛8GBDzXkUY8[‚KbVfJj͛@b"YTyB@7DH;(  9vؼt1Q %"owc +uwJ3+KHB)_Y^w{}}g?+#cVVY2j}ۣVؖ1Eqa¤sZS߳sj~YY@),%DA"үL}ߏ ŀ!i $ Κ#c./7GGb^ʔYgۺf(^| 7nߪn>%'ӳ7z]7n}C(*WVvlZムzH 5,}S"J,GWWzZe'ݻ3 QfBQof.v^6)2'cW/[}Wf6`fܤ0 ڝi_1;XF>ęs&D:M baMB6`3~4J4ӵ'KDfn5Bqc$kQ a(/Mqz{T-1ơ /L ,TA1L4aAk|C$BL.xw<u=haɸo$Jِ8NљT CS7 krע4\NczYfjdu0BYwkQ1[RBbvS51F@% @LE.w1&bgߵhxLDSUU @`qc'h⨺%K>__dyW6^#ͰQL3{NcE4 &B(caJ)Qb1FuaQ^)EcvDWON֛M5AS">h"|֝ۇUY=>{u޽2xkk ΁1XWU,Na?P"wHDo~;oMw3fuGOݬ,/I9X0Hݶo_ͣ1vn=ᮮ6_7TcMq8KE{_*Jn3M 0gI<q\0sL(G),y yTz>jAfT@@ɳMݢ)EYdz:Cܼa]7);wPvDkIW $`5"AD ft)bl)F<0t}v;'DLz#@J4Gb4E2L+LyZ_aE4eY6m^H6MӴ͡ROL`L,<+ Ղ1* A 7h. 5?Z^ED\ ֚נt`'Tn8kL>IU:<޵w__A3'ejKYFjG5(5z!ˀH@F)Ds:8wƘvj@6 Y4H1Y@#0D=X|z _^g3 dP?On߻wti'O<\v|qyY֖̊=:8΄୳"@ 3X 2XWOn`oݾĜhhWM56,[pzK)YH@ ჷ՘# ie?cvmuz TO"l]eYXcd""Rei*8vz7QQO=4,lvH@6dHU 9ф9ő=_6y[DBuU,;ܝ7O/'(b1mkjU뇢(@yܰP塏)R(ƫ`Fu(]D:`-9MצDc)8|r^qq0DcdG=Q[gRX"%"gQ3DH5,`z!P8K"ZU׃fZܘeX5'"9m41%A|? (@su?;cɟ|7z F=[&$T“ލ63sg3WzvofƸO6Fiյeς׶GO˥1aH7o]_.Ϫ,68_.ܾ}ﹻM7oj~bN%ATmQ[c- X8=~7?kfhbYWW۫i۶1% 8cMGUtW?çqveY_,|~p }/iIor95y7ϜEYUUe(x&7b⹓jI!k>EL8^c)?K_Ƀ*2Fbtv8 m;2% uYg f @eZ[EEw*@ĦiY 2ygo\VU99+EQ4*&2Z+nw-wn\m]]^ma1Z~>)$b?K[`Ѩ;bPWw}_3\Ńwޙ L(R,E:.mޠga@I%2%::c,jJ1ZAQ:FAk n[2V_\.uݣ#km?DF:c_w7? <Y"%gTibc$#3w̌XL}f "3Si &תgg3hyuX] # D኉N=8:_8?_,:w6M|߸,)LD]Q-AFb*ض}߼[|6m2vaPTãP!׮Sfl 'b@a,` (%JsvzwTYUUܑ5ۺ_8㤩N=!S`YwxZ,O]WoaISL~dԩ*2kPO=.g/%KEQW2YvE^3'Y~..&*"!ƭEY 7fz0vqyqr*8 )&u^cJTo*3?tGr'JmL+R B2|]1DiF75 g0Q0 C۶iG5c7rZ؎H{`kl8 Y19M$B@YuH̨SP `¸7jl)A4dܪ]L]S" /eEoۅ0̢71e'dczЉ£lcJNCPRR?&+2L4,58"{ Y2FQZsN0WWk|1;f_o~_/a6#"=3}jEHy"gc=Z,MkZӴ,6_nѪUB)!ڧwÇ(Jkd\WFbLC7qlֈ4eFI#Xsb8;boE5MGB)@C6Ju6hb<8\m=_. }aPhY54:cH)G%TDQUvJ2 3ir\əy6G(D4>MWvHɮPV /ӳˋ5ZBi jy1,3XL!3& IDAT.:[  !?>Q6XkD13K"R7'(ֺwEv]Lx)`'.OF] m7LJ7EaG(d~Ǭk>u_`7m75;<` !@$ S6(5RSX$&2h#Aɚ/m1Fͦ]vhvM^x=7rXCf5L+2MfȔܩ Ch!b!#ČS1o&y$d&Dc~41 ׶nV7oX.Ƙ>yx:(-:UYUo6H$iD >x@n M}u~?PX`uPeQ;_8owֹn'@n>{r>C]SͶ{4ssw"& 0ͶnkN2E)GCȉiژ뼟gkne\X^LSZɌ{|&۸7vJ#NbgsiQm 1:qlmXNG!D㜙ϪWXc!}<ʼ*0D:gr,wLm=>;Z ѻ%xZ,a֍oDƺ!|.RQnubkOi@L{ f Dλ>ާO϶mw٧\A bHc!Q~"]ϊY$3"5UA:7F 021)RHD깍Eԉu|-z19 ̺dŋ}aο?R=7m2&qt1 yϯb!,= E^ OK& jlwVRJjS QހhqLɮ5;lBcJD=;3w$<(Lp٬*|>yZ.q`Y7+ !5e 8Ŧdhi5CD7Ƣ/X0aH1F"ݬum՛mY;zupuUU5ٓU|p[Ecvl7N;δnۮzyYn޾gneY KJ*~:O=ȼEAJRbmB #ߜV6?ڑ;1٭.Aw9-viWճug~ Zb-Ջ/}Wk!)KnEVQ[k 1uCe0Ķﺾw.JO b=,K)9눨,Kkm^9 Ĕj<dAǏ O@R1,l21;ﭵZk!̃vz0ǘ̲u@Thv!cf@,lbD eG;ۆ f `VW#*W˿._>7ZRt!L,lei{[*PeHIEGfqB)*oݺo4 icLHÖi xٳ*g־<<\bzM0u]c zAD <<\8Е,5?cbJqV]C~,d3ͳgL1Ų(bPxA"`Y1 瘨v]6[R5﷍7˲yC/Cħw^3,n>~G+M-2]ÓpϪʀID GTޘ8uΚa%$6#"Gp7ɼ0j=Lt"ky/# gk}]!,9Rޘ!ئm eY77otݰlmP@7`XIxb8 Ct"webg]>Ե]2Ĩk04 1 @!0Lۙ%饲f80r֓zQpvU^mJU 11yE叧Eph 5 Demڠbr֎Nȅ1DUKXH&(\w.K4@dAc$MPzq<ӌ60bpTҢhɶMՌڈJGjK5Lּs:wzLM'ZLoo|_oOBL*>I0;T'h SuF@0 z獋Sw(|BUc*:}1U0tm6m״}׵bXoz۾:c5''G}קDID1 "(&A Ed~nljя~m|Qh̴&JL{ob1Ϙyu|o=)^]DA B޵j7qޅ6p4EUj%Z#sM$CyFB۴²:<@cD"7pap|tַ֣źnSJm;law("Q]qy˲:k 43:!̱1EY\\2јaλP@`O)'o 3+nak1VvN ]z=3""u{ۺ+~DcJXU6!0P 5]ek@J{q'8Ij ՏuyDDΘQj Mۨ?;W10L@)嵓5L˻v1j,,y&d1pqhe=QFMd=r.CgJ5J9GTF)@P=xxcNׯ -Z#XRw k19s$N'Oώ޽/aӴ`ulYm_ַۮHJ0/*Ya"Cu[ckګ߽},Bhޅ"9o>EQ"P9ު[-8WVI`*ef ۪Ёhaf.("ea1AOv1,bew連x^o!'k..)&cLQ8 *H&hdžvzp˽c#zCH}sԈP`Hs_\@LLb):P,(PXc.%3s67Dc,F]8HUU@bM| PG]&|kL!AX@WEVZZJÉQ*~1eY%J1}dѢVۮM QC$;Jp09GQYZ|y1B`(1{]ki>PB9LR,ԯ HO쯼W/h.QG [V)@qhdD؎3 #pZU yU U+km(T%yJ a+r5+|?Y~~~|c?i菿vfUcl/KoӐ%6b}z~Fuy+␄6 LɹޡeQVƹ=a&R7rZɠ (ʭFs&34J3h Bfaf{DJ 9qjbCk]`n0`[o>ڬnƲW[h_xWG aO3icȀp/P{wz<)meh, ܬ0U āX$ƦРNb|0Ĵ5M2_?O~_ӧDttrp|3kGӳW9U=e[C(gU(gԋI ]7.7`Q;l1?~mX@qn6]epx纖6W/* ҶiԶ`=5utCpo׿ۯ}PVô @Ykq祮Jr'u9~ZDHLY; Q7wk?2rYֿ_+,davlT MxL}PD>o|*z+,`sl;2@,#X ʥ͠ѱ0 z,ɮεWkG>'/ɟ|ཻ}ֽn/OONgiGϞ~˲h#5`!m|7\mPVb>v)%J; OUUx@+q6M/g#ݛ\mS?ΘV}F ~}L_(&1] !m=">Hl Jw1HI{ {A!ʻvaYkiLY֧"GyBa2Ruc쌑$ xޡ7@$Ygpp7kPm7`[4.YҔkb) mۥ ]E)"լ Eje|d<9ǎ #%g1 $n!1/A(_^1"RJD9V͡!Q*Y n9U܎)GtJ+H5IXXh"XyYyk5Ι1se*EA]೟+.K^NK@4=3oCD7wD\UX>={7w1A;;X3Y%ѿ9uNGN-+_Ї?a}{Bnݾ;G7Nܾ'~&Q7DCܷ]۴ țۮ.ejDMb9XT ƺz4뫫zkCPzg#օB3?>>9m7(~NL@H1)L53ւV$?6xgDq$3lPhk 3Q41ab0QBmƠsvHv޹|̅p^d'?#9JPg'e3V3d`edhϿ-mVfY4qHG1y,UvhiJ6Csc2""_Oڤ;yrC7K'HYU`y/o}k_kZ炱hІBhaݞ-K̬־}n}zznp"E$K ~ #tHh= a0 h3J"0) $ `fz/u֪CVG߅ 0gZUrLu}v)7]Ӯi*p7i'dorηחFv.']9'+|z>?ַNh -Xg/0we YTCJ-)ʨD$2k'nҽƒf{{sYo@edXI&`UcKVG3JTDZUlpTf>," q\f= 1lڦb3={z0i ",ٙ ER%y6 KLT&$(|3QĜreD0ZA XZ,E1XTV#b0^jS43kbnդ("kZ'հB{/UgQN pI@_=Yb&*ȶ87&]SLc"EƔlCq*,9?O_{"uRo^P-d+@EROJŐ/}'x' d}&_U34!ا)э̨lg R="#R|w~D^xwO⧚o *~o~JxܬCuhh>ig jevOdB'"xZnL9uIzF|ȶR">fr'fM1g*3 kg=̒U-FE08s"39}RR#!UD=WMdR*F’mB@ΦMUɡA2N )$\j%ifuAQ(/uB)[z5 vqpŕu4~;za)_ bp(I#@Ma)خIש12{Vմ"!g!r朹ʞ ;tjQ.5P-`Oiޜ29b$ BB iZvmv l `ۘ)ǜCK[̦FwARU@gID8'23Ղtr"0s*nXI:ZdoJ F,;>T$gz饗@W,cnV_KP zJPѶ 'Ǐ<>,{HHG~H ˘ a vv9XZ:pssss}sg}fӛӣg痗=n;>^=9T٬3apbRE$3l7j>a^F0l!]6|>ȜvM/ny) D)u5um̻ IDATClH2fgfWΊ$"n9\X Vym*B yrU\hI}KYZd}% Jd:9==ie΢Z̏oh 5ǼgpTƝVy\wU$f&٨DS샛OgSb* g OYoz {F r0szAi(6T5J޻"Xcvأ=b`j{$x_U^e,6{bB< i:6ۘ#5C!vMcAQS~ "E臁UD8ii!BKjuCu?'gb/{c2JE 5\͗;V,j~QkQʶ [gKpFA3Hͤ;qmh!C<HׄI䜇m'Ƥi4&O)֑#Ę<$NbZZiDsi{D#+20CMk5xG91D)hj5ÙsxcϞ]N\Ny\?#GozQ5JjI);U wbSdD%J=arG R])ӓ vP9j&ocr<>l]6^CY9Sjۮ`6V;c @$8Hp4(T\=)YY@c''WW7"/xڀ,SlC0gUgQ w/:JH3#Us# mD%圅5O.a_aj_^OMM?]`o Alojp8P.k[S D19Gفs4!4a< ys`df+ᭈr5ؙ@y;ecXݝwճgGg?ΏmC$ެ/M|3YofoW4]Φ|6ͦFv~n79@@|C9=_E"yxtzrp~~uumi+E"9Q 2WtA IFZe.HAA,$Aʢsb0KI#Qc&5vt:ų|cu]K"ѤXX a`SaWE֝F=:fT ML9r!snכN;w͇o1}$gjB9)yB ˙Y1nA@9VE~]M[z,l-l*}?x580a;l}ӴgՕ: 6=ghJ̅R{Έ0bBD;d@"9"O$}׶ضMݎqrډNdZCw _42i۪qjV-z.fr`rg5w <콏9.`6P޹G":,E@4d40 1B F$ <^~?Ǜulvo=K PhsJ)f999̙Nv)x{arqIm[ы@,!Yw߬G_zS\mt>qSـB۵ i3ˋgWz"@JyҵrHPo|n؋7dDknn'9ˎAZЊ+v ɡ[bLȥ˙sme9W>4PQj1QNbG3Gg,*9JN2A[,*ضjFٝ;0z|ޟ_,,7 C*hZnhHM*Sz#l6ѻX+eo-{POi)cv@b#: UDX b h9̞!"~g:Wyw*#7EudcBjI FQP$ ݻ*Q h:ږ3*q) !Ĝbʦ@Ua9WTN9[~8 HC>\DdmibN8<\V*QI"}ByλmM,3JA,xHgqKfbd /Gv#PEL5O__g)%Z5fbE+(U aME(jfMfFBBh뺾Y7 qT$")&(XY/]]뼳{x!d D>CVOfyiH%'9R7Y9 3ʓ7|񹳣W77o.rϿrYo6شtily7H8>|3믿n.[)'IN&T3],yѩo:5MP>9%C?j#8v@r)X,ꭊ{c!DTy i@9з"IvT@:ad6m˙}VfnW[ m0"œW`FHM KN2G{u.$-BC@=lH08t4 q,ٹm |#/?xɽv]m˛57lLO%CI]i<|͝N&33 CqW&G#fÎ{B~P~/1S (5-8lr-GH bIl#ǪUUWZUh~(Gd26=4s}"PİEF`Yo U1? E<'W2t@Î4FrO)0Kt (=!,W+@L:Dzp鹻'Rmw}9eBhhrN Rɺ%PwU5 rSr * TlegM97N㑢-޻{YD5qtY8gUdct$i_Esޱh?6̌۬7AY,;uU 糟܇O 揾/~)3 {0Tb_y_R\ž IWGstHfYUj!Sj.=oZtAD8ڐ[돁|Df~>}^Tt2 ޻AAכ-:BOP p6s޻&LkKg_:\6teQW;YFUXϖ05Ptr{0 oΏ~F;0 d2Ty)Ca{P>#= szBeUmœ2(0g9Msc 1~;i+ۡRoUuTãz]ƑST]YU ̒i>|{GbrsL)'sƩ[};q} 4}#6rQSc۞h `:pfACTal . FcS f_pw`k_e*B^e-`3%("&Cj kq0qs֑ |piHW PLZ*I97{ՎW,28J{-zhN9۫|6kf}'''i'y7'n:) a6fS$CcJ2?z˿|΃w_/'GO.=>ްnf5gO0]sn#qcRU$$udKE9uwn8DכR$A8U+/ 5VR++ÁaBDt0CYlUULfMB}„,LD=P}͇Yek>BH y5Dng1eYtڶcK?zv+BYi79:t*|ng9rfq@J09WmRʄ*Dhq9\%d - McV(N)r&MTE$۾Uvɩj=vKݏAqsrS^z%D|W< m0֐W4';-`G_ytoc o`ͮBIyje/m۶)EC\9RS+6yHy31ǜrl{7ַ񓋗_|x> <ο{ gOs̋ãٟ/onwzǎ[18|NiQU2Dj{tc .g޻̕~ )4,jNQ` B4ek1+¨fyo!ykQ#tzxvcm}u0_уIDڙ3")7X( 3iBD7tӣ-\n @aƅs!C)Hs05:C07MM97R\3.ӿaʗ j"KAaZ"wNrfڅ nipm7v[\eAG.ۦ 01V$>cMyGFyY❟ͦ}eMr)..r@4 0LgϮi6Wλl}?$`};vͻwONNOOonW>|]OM&5{~?vqq *7UTǒhLmҦ<cv[2!T_4W>?@&-gggrzrYU8. 抺c4ZXLg)R0 J,VQDD5XޜQ):;.0chJˬQHPEDRJ%χj7ŨPxHB)jt|еzO' ??6ͦI4߿k{_C }M 1'"m۔ʬKBNI!Z- fκ;b1\ʹmZQjכmf77mo svA&[a"Ơl:#R }!RfpxpYET7/ljm"~|vW{ gd״"] &f`Ue_ "y|| }ߗjiP#bjw] zBlλ.w9~=9`A!>g^x^6sdoÇ,tzelO<{;wON!_@ēLdwxZU^K B{!~#Ueζ嘉K%_~73ʿ_B4;8ΧLJٔS:\V,]*2)켍E/k:yR;$Zűs4+9G\1g[L@fvyX$x7bֲSFl#" րKjMgQ15! 7 .MDęcD(}QfD<>>b+ԑhng=NtjHN'3ǘ&ka"_~wy4 FD>dad֤H])i4e &HcxoL0l!$ʹ88sz DAbuDCØlag\gl|} _oڎ^o񆲛B9_Ls{23`.,U5l1㽥"cxe2V^r_Pf0FgM[sNEs1{gw?W~lGՊwC1^]^?z-&Ga&1]Hb//]>{v Ń_h&x*fcS شݛMk]\_y6VK|!TK^[$~O?x}uj=&ٽ䉈֪36J]_& {V "XKԦ"Drf,"=6J'"KʺV _ #Hfv10zhplr)rH1%Ĭ⃟L&DĪ}+)"2N#εMB$ef>&^ق 㽟N'ZX9|O 9'¯W ծkvvRˑHJ]vaH9O潀>q;l7 v`wا՝ʹ(IQUB]m??vyT0k~_ϖ]2 UA|ȑgM9&,TAX00;{&;Ua8Jم.2"^v2FG 3#s΍w9cb~{ݻs||tvvٝw^WfD||$@PsJs"f:91sRU 啙wNV7K"f1KoӧOmMȑ ,n3؇S0J#_|kd~v)"驈/g6OorΛdZdO1@(bAxG 02KmZg)Sy hlr0 0 fǠu4R m 5DD"x !߶j&4 mjT>bߘs[e + uì7H1m6ۜ#0z.,O|>sMrD4LN"Ջш,irM<z*bJ۾P D$:Z L's~W9UP ֺ[ox.G_گ{Hn IDAT7RZ{NU,n+]6qu,96.4)p{s;B2mqt薽e4,1A-2wCHɑZw !8rq>|.!uݻw٬7Y2RJi{Z߬fes9QmmbJhٔd 2linn׷u)Y#)yop^/o(PV"hOa{(Qw}i. ʬWp|rdzl6yv)˛ Ϗ}3Eڶ#BcUW{Jg ]!5:EGԄeo\Is ZFXc`X6 0DrC"6ۭ|hV*ghIwNQЇ]ƪ6Zc HTbNC.ڞ1,IV2ϼm/,{szyno'n:&SrnJi7mˠ#B-ߚo[q%SUzLDMĘ'q.Z;3uLZIDX=es^EX Ta~0slc23 Ao%!acJ0f3#Ӥ|ZFl"I,H8v 4Ueى&٘Mm0k⫦ v9Ec*Ÿ U%G GbPINga׵0\]^VsnCPXQ sփ{IEC^K蔄1~N7=*rJ 6QxcL>zoNQlۖ9 gn9)cy1[\`MRLO>+cA/䜨hYxrz2l~.6ڶiaߍC<:Z à0ew$Rb/B"9Ӛ]/S}b:nu²^i>#aX6rG+Eb%򃾻W菿/(} !HMUT9@Z>qE}qvm6lp~d" Vc1q-h"# onnJqCɑx i*CEL˴Zڶ Ĭ1#toMXc$r4n{i&TGIt7)̺ku4*E *&%+{B?D(<=f>~;uDwN"|ss-ooE,0ilvrrꃻ8v~T{q ,]tJ0&!rZ{Oh 0bꚥ5ʥj誱,3s-|Hf P&&:u0 MK>640X "[ j=A&͆d>r@uo1dS2?yrj:?FqfB mSw6+`:uc{BgXD9cʒl4@UOfD7靓ؓVJq1QD5|ߖa.ovwʱ[HvR9p!윳̎;t:]8*|蹴x c;N= ;r, M#$Pgp#Д4M3?™?JE¡@u-pGsdʒs4Q̭;,OqF6EAF!L瓜9Dɕ$yD l:Q=3+UP/ňs\h6햙S&rRLHTv8*EZ&ts@R캺"6a _dž=#gƹr'Xi)#{"+J)vdbYvQdYTaW\qe)"B9RJ2UתͱGŨ˨Q1649rŒT]%|%,liGONͦ}{wm4W!9gVg̪T ',ןrU rIv9OÜ2HӠ.ň;`:7 \܄ÇyG?%rZ¬¶691A("ECDf^" !t^ou+vmoY\$NC5jD&s6q(f-q#*'yu6`N&{C%ZJeX4'̆[3?UqcT#bbj9+y/f2R1.mHk#8 sE*P(Mf RP\2su\|Tk>ї]O 1nkCDX,,AιS复E9c ePcI #UgeNN$ud罋1y6뵈N`.f /z%D,1梗$JWsQFq̬AJTD TXiڴmVm5M̫?|Ss¡M%g>Rz9 ܹf9kDnvvu]3;aPeZ>`+:d;dd냥HOm0_p^kT:]`T-՜fL::r%#rZ_`q=똿 5R䲤%;fTm2Ctq>xL&]nV [8J#"~3rBс=lM]q8BevH['<ʼ ѕAu aHKpRfSʂyfɝw!4!tވH6`~px0χ~(+Y)91Bs ɬi0/])QXfon a9Rӄڦ3%2@4pH.q{5sdrgohoW,eg-s46XJD5PrgT5t2Y6)]_Fۮfg&ܦ7m!FCN.5zٲ xD1)d[qki8ih>rfKur޶R~X__w~SɽU]SD*198J@9guV&[Y ~>௮rig0!yrsn&=*Wӣ5DA',mUf.JxْYQM; l>+ 0 )'P ;o=vD\ɦkۮ{oa@ub#ya&7u!#$$ LI] 틼Ǽёvr*TI,7y/K?z/KãCBl.x??^ֲ m1UUaܸEM owuYQ!L!"Yr?ETˀ 1oCrΓIDS:G%1:M]׫ !λv2tO<DWP(]H^ 1&3)4c:mrSϟ9C"Z6;FR D]-Ϟ]Z.յh19#i@@R:hKB(|'-%I!@0#( Rsgp(#}~ FU~w~;p4`94)U`95"1'aT;攛m&4 bqpss;H9ϓMt(FɨIQv3KI9[QbUZ-Ő!x(39j73<)ͦEuDzQH93%r^C^x_4MM9\bɱ ƖCѼ9rA*‹+hW(!2r"f5Rq#˛[DTAĦmAm ?~:ĸ\.9K!)1(XP`̄ޗ**n4Tp,cdIٟ[DEY 7[}$QU$X,l6mf6ݻY^ l6 o6)%Gn uغ[2jѭJ)j6Ee_1Ɗ@H9Sr6ZyQ3!β`:}&bTp\i ) D佈ÝO3WSdXbC#UXV\K13ojÖETS-0~zj ^u{Q1vU*\":k[")Aat]9RceS=|U4E>wk=.vӹzɣD~;Vrd(9ȼ V  *t4ALe:Fl֦ ˞=KܿvxxcD}Try{yq)"qa g?SL&)ʞQY#jìsT9)؊\j.(ȥESe Aλq[е0 Նcö7@۶5Y-W1FmzB?a37 еm0t {gfƢDfzYA `>n`IqV&Tݹ\iwDD˺'.oLiSIۆ-BĔr;vP"Aq˕rv:14nQ+Z0}zHbF6^m<^mY=sF",=;zW:-u\mK2i4&"`fd،CU QFpu:WLA?ęU5g19ML̖b6r ;pkZ޿#rqEvK<GDU/w?@}.׫8._C 53֫8$PΝcfMrF^(02>D^2vOUHIb3K ;Q]mdM$"b `mmiAĎPG)}y\KX m7DǠffuGz4GjM3GCD$kY9ǛCs!&4'H!kScǘEm ;>JqH|*/ E5? kag"o}_GY HYSF3[u(jǯ*MlYFu< c5`GkT9uQӠTdTIFD/h=d=(dUctu]S7rB`,q*jA\K8ٙT; Satf0^Qa!B>%br\{8I)H1Xiƒ.Ο^<~vkFU/Tȵmu;䜵}h3 fPДٜ%3ǯ@DgihB,1$9ձ(6rb@[k\A$]I=6LέkVk,: ̸ReaWF/n末}jw?ރ/ru0̛wWsn6"{fq0_o%̰-H5l4*@YD9xoLfUiQ>Zk?__/;"QaxA &ޯ=1iw9J1Pj<ZfdĜ/%ILngOzAHE~rk֔K Tuˡ㑹P \yuNKg@Tb9>՜ZR끽f6-Y}^iVUZ@D'Z77kz)d򗴚=AwU3Sw 3{+Om!N]ӡ6NfeYZه@)KvW.˲,?Ǜ[ID_~ Coc!P+ڶ|t?4/ ج vnCUES"J9%k@wVU`Z?gvDk b8(2J]}+O0eΠ2MKOθr)UvkCm%QR&ӏJaw9qvQ@b0V[CV˺9Rrj-{""2M6U2SV_.%-86~cܴFRm6EgR> ]A,x9DG=?\s2<'huy};lDbɡ%xg?y^~u-m}iӌ]^^*8}?ze??>l"iUrG)gϟxd ^%FJ ͐&e^ץ:$̘y6c>`{N.4,fAA=yV`a$B.zBHϟ=?uqq~}}sVbZJ5@3HAPADu=;SPwԀVs]~`ѭ+'BC`$,_|BVѻ/` oms n\m52Q! gID֭6HQ9FQka7a)%ԥ3Zz3c VAHY>f8\ء Eu&v$@Z.}JR*mmYTZm<iw}R(;{ƗPz<y,GpGtfjQ5lB3.A7&}Bx4M@ױ IDAT㏟XʽE*hruu 70 ]_޻7.]uaǏ?J*aYcY$qZ 𲮵Tx+vJPhDKIH(T" UP_KU@BaMQ|JZ!kY /qSR.; @fanod;a^ua{,Ƙz Zy=8l񟖲~bл_v} _@?[YG>C67EZIlHчQNo|ږB/N{j'S٭MԲL MZ3Q6#<n@4\J5r|b_˲*¯2Nl6eKU$"O[ N$O6e"qAaكlF8$(%YbINmVSKA,:xqFCkq4]i__zWyc*k)4?{tuY:J"_҇DK}m,laZKT00 % `&PTs}Uލ424N psslQ8q*Mc3߻wy8;k19EP}!?;19yfAga^z!"^]]ZCDXJ)Օ*t]GD˺2 b DT„K;E7:<˺ZҌЄJO?W78:F:k2KE =Dn 7æ,zyNp8 C?MKc{1Cz8ReuI4gŕRU..ΙkG=>'u-q*K%rqӼ!B$N!@EU2"|!]J7Η~K)-*bCOP$J¢-^ 2͗. LT?BaҼթ,]ά|~BrLԀh|-89gME-"mmD1o^%RP1LYW6Pdlm @2XEV"H!@Nc \ꃊfOV?bۢv%4qn?=1Ivvp6ҘcmUC  'Y{jc2&_![ l; rNL*$BfA~^OE5c"aQQ6@K-i DZՖ@ ]Pj=vۿ۟67_]vvZLN HXjA./^{i-@;R (;4%,]E\91GC}46tcxIˑ4}X+kQ˺Z?'FDs­cJgWJHH(LDE 3;*SBeqiJznĦ>0"P zY^ 4}:C ݘ#ƨ!'`WKT4-Me0y1i.{FU]_rsskK5Ze9ZRK]ֵVw0q9gG/=Lp,kmVJKTײ.lhSFӖ!"֑Nr&2v%U=.C]"v]Cd[ ]O/SYpR6rLz,D˲~_]z|n6&&"Dy]ު m,On-Rb^._^\`f *QR 8P ﺾ눒-pv+l`sJsTQ\K&Y4Ybm*,jʔݫ#v%0Uء2Dt jQeU"K 7\ l+=ǵ8acYKDȮ71 7Q] 2ha&!nL_b,rEiLhtjinOr4BJ6ts_<ОU#0%\zDAYKe&l#-,7?~<%0zlV_ov^U8zdDd-kՀ}?0s):}#OtHҋ̧gAv,l5֝ciYR`Dž. [ՄŸٷ G4-k8J'G/=J92+* }-,&*kRKMDq~ߑSXkQtl?Z+>93U 0]jW#N60t!LrhQ\~Xalq8'oD")EfGqtc¬͈D8e"c{ ʂ 0:7WNB?{.'PquUDj-^[G2S}kuR&d 4%Vt?=>}z&n{o<%"ϳ*\fJ|yq,K/=jZوv~(qGeף: Z瀖s¿aU`zsJ-Rcw2"*OGnw |$">r\aY)v5|}:.]^K)SJ5d"X7p'/UP]k׵T{h,\j~1' 11F `L `c=6Y컚w1$i"I!,i[aeVr'xc؆ 8%Q:93* 6z$QoO:/0s\}ob^2/9sՆmKwp@3\\[E;}|SMuueW) svBr&6z)%(VN$؞dcyi?~̋F̲rΦݙ@ -¸Ne} yK|πWwk)k)U]J}nOB]S`w՟6G=iKl7Z.//S___i]J.KREN]:ZsEs)m:h'dbzD͛KROWJ- YD+W(*{na%-DرD!@TN>i+/[="%wE-D*42$#߷.Ծ$tںuk5\p4͞L50s@kGpI*˲9f6VPVޝUC`hl>!ީ},뒐[Hw˲_G^z`G7;K67ZgX,F,f5[Ӝ8ku뻮R֪ѬEvo8ɼ s7%nA^lomq0]i_(.+`XM <8mPu =gL]UX\ݡ'@Q%H=FF܀."&F[z+7Hu9o2RKR627"2eV =eYon2Rqi"R~?<;n;k)2 }XX݂kY=iu TaQQT]&/4)yarld-8O |=?yP|_L)y܀ggU%nsfێ OEe-/_~ȎI WyrU"`Yt5w.pO"+K1WnC<&H(¬96USS)»uj?j= [K~$KDnI96JNz\Ƒ&RZƣZSw֡ܪ]rs;y.E mZ[- N)Y(% 墰]qYʉsﺡrJl%lZ"{6?>g{2*t>>9i$3ZJmdTbi-Ïi Zeh`*j>Q 9: ]޽P`a/vqqZ bpeΙ;e]hl͵)MWB!\,D)s.wo {ァІ-I;2r`JqYDΜmhmv8#AHɚo'[@+,Փ)d3h&N"MNη:ˠ;}5Kcl+{Hx'â7sJ]NNH[&П !aStY+dYcveߦ$ލtk6$2Fbi;Ld{J)*.;j$2Prr{Я2i`#A jN;3ՐIN re6?N@[6}QjPД2ono~_W_י9-˒rZ}8;Eu2+scF@@b^s )|}{\jIggBgáKi'pU0_ЯXB>Ǿ̂7|dI #Z:e-x<0_}ɫ2Y[PEccl*ln~Dnj B*,Æ `4MvUрłPD[%pFhS^k[mЩM ML<8sj ёߦ,(W M#GⷙJ ;+ D6ǘi*ɴX&v*Z+DdYVcZʥVjB.;\cY8$[1遧vIj(-6M aypVj1su~3(v X^8"㈈CD{O=Zǡﲬ+'Jq#K9ra7jQ+RJ]SJ pk-MuOL?}mTaڙ@->rgK)7X]ZTpv 糟g?o@@o IDAT??L0侹oauv-))e@AҖpv`M2L 9h W&ZÀc,e-DGJRHBD%O6[xyl>Všc>.] ሖc&-jl .KYT6>hmS#,%ڢ#'u; fx\: eNMD] hj@흘MP-'JXkU9D@ॳy1Mm!6l88D?R^ϼZvj-}wJ˲"8 +e*[wA,j]12׵ɎY-erR^S.g7vHm|rJa(׶As\@Iya6) lxEdf+Fe" ?+lAa!"GSJVݣKi5_3l?Qqɵb ;Um+nJ]v؞Dk5)EK5Ͽt0^5.>oELZTf)ue]LEQ+ZakU+M|l0i'QSe>.lA;2 gk)kf4^b`eYU0&jF{HkeY0b!4bJ (ܿxs 'N:ӑ{c Ɲk#->ToHP ?`3z f]eoH6ӻnέ;wP,jl&:X M^j I `9nL8 *b.l[g mGBQ~E}3&%h[DpƊmiòu͞ 3)ˋy~ZܵSѾkM퉫 ';UǻMYZ?{Xlg$Pf$պ,J)S'Oh>v?Wہ\T0e@rH,‚ |"? q# ihAeYAEnoZbkŽʼ)q %LD# qDʦhoi^ 2H-0C5 4<|`P젆N2uY+y2wʹ{-oI^4c?7HD]ĝ A<;IDeY*+/]NliH9& ?z(wÇ=|W1,/jK:O8t]g"e^cdw(XYt{'m׀n1f2XO6 ">d8.P  h^mrR"fԽrSI-mQp7;xA *W8\EAY$Ru]e6Oы ID(M{t9ql`>͑#ҧ(3 7$ڧ8p[2g)vt"c0NM 5іٌbRծC'K_2EX,;w8/RH0asW?|7?]3~ -#Tթ-v#W4e^/./R/Ç}m,2ʜs΀z ={?З_~^{_{JYovVEG'u- b lhA > ȌxviĔS2N=Tي0*C昈G`m`-C@Ԕ:z; cS^Mwg܇ߨ[_٤N3)TYה@,A"Q25\]]oDWN.m &f 'LUnDF$rO 6|"XT6N $<"J),)ɺssHl$iM̖CaĤ*+iKʕ B3}>K]i}*іaEƁ)NV݋uƃ i^{O?}j|o'.˲.KTiJ |zD"ZV'bj2noZ nYĎ]1f8'id)F rkJ)7sN[!fԡ Zɖ'%LX4+04e)+kEmu[ HEv^M77rRt97Y< ze)ՎJV% QJ)[PDxZ4G*<)''v|޷FդM,ҥq=nk $Dv%'ϗy}~')~]^?uUH-(G8jĨ.Qy^r9]׽ʣ?|lSTC/ʩK:W0Wļ#]߭f4k$5Y ab@.9L=%i1$1%$u-Դ9[we;:;_`RS>JP^hu滽 ͦ#QJ~ŵrepUÃw9 cqZnn,bIYV [k 6ul#Vf?G$rz,9Z-fQZZKs5,8=f3{u]JvN\3Wc%|`{!En8OܠjS"W>rw?ojڄ"ٽ^mlFKG(+ Sf0M<}8ϯ=܆N4f_9؄\1Ʉ7 $L RpO^!iv5D䰌[sM\`oF nDhB S!ces|8Ԕ,".å4P!n~skzmm E=޾6g"=c9lm[f$ш}O v4Rüir+{$[M}}4 )y!wYTa$vDŹ9]rD "2iENxچhES}Ԁ.w}_wP4BN8Ky__׾甪i%"I|a*DPycJ?e2P-x;=efCwYrҲD bb5WD&,dD՗lД3J H+Җzݐ/.Etf ݲ }'Ԉ\X6mS2 #W!c%Ԃ7x)vʵjG0wʒe41@4lY\wUћۛcN$$  `k|$H ܲa)T~eVibzTjfV;sB"ItkK-=UP7 bP˕aBTSG jY/-twַr0=%&46k,,,Ϟ]㰖bRwi㾘>ΰYBD]*ʊQEe&BoX"e6:sY=#fb8`3;bq#9{t8; Cor;SǴATt۶`7aE46)%Rh0@06P6Q{ Ʀ } MaVE%QRPZ쩟OXI 㤨5{=>>ƃ_aL'e6Vy`d!`"6!jsBm}V Ҷ 7Vlblb<o""̔"$%iz ,kYkK#_/ U1DRRGbV9S$M]o| ";"dm݄C!"^R!$,-sU8›h"aNA.wx\ 2;ܔVtJ0H|"*l9~/Zb {{~H9A3~q](pJfiT  }oXQaeLlmoR<6Gq>trt&kr3 x8plW6"Go+Ʊ_s u,u!DBba@ v/:"Б 6fJ) Es)&EU1Ɛ6!Tf6p}B|֩9=8>;B"Hm12qﻮǩ/=@DlGJP+CT($CKpJfKei:]ۡ; j(,2WWNB"dx ]#bMn]oT~mZ5{0&Hu(,, W2&7"j45y8)SukH%nj o$"QA5$3{lcٵ@4xÁrJ)-JeCA{Rd,U[D[.%{Y[;nt!*2mR^y`wZ #d=ve.Rw8(Q%yYeݐ㢡V^eФwW8^K~˺"8 VN WAMV ?mAaׂ4jp l.ew`1!@c- ݆e-ٴ9.Rk Ȁw9[&GS${DK"L.e-0o5U^O;B@%-1m7 IDAT6;Tw eP6 cD‡ͦQ:MǞopLcF#Q}ߙCne"s!88%ن/Я|B$ζg"]יI;DRyc|+"CHij&&p1M"xj8zaso `'w]rkZSN3S\ʺzץ+&΢5t~߭iwߖow*""W[I`Ss5;eju݃s~YWSj6Ѿ} ՄLy* *!rD6f- U';n"ctq~^J)ϕJ˲=6cMD"l q&Hۭ ןF3o/DT-o)Q]]q<ƜqRt]'zl)` (Lr;F),Dsb9,V@a<Vf{۰68z 0oBiЃu-@AE[O`AT 5v+yAX;G$RZjɓ2[8Rl1Ƨ='78^_߈h4M3$1),VhgGkq8K,j3'^鶨)DHNdFB޶s(Wk)/M)sg84-̵VX7eYeµ]BWth;m-Y*zd\(U} /kzb``jj+aMZdb9lgsb w[ .~ ! wGX..1L4ϋ)yX:RQ5!e"Zef;,r}߃%x n#}[9[FbD9x\+QgY"N.hyh,79%^^VACEDz[== w3Muc҉ʺPz~~^Jez:*ۊoDд}clX`Ϸh{ qL1׷&"Pduݮv*!CR5TÛZIs6xgܢ6DpooGLS)!_Wd̢:2P}ȃD9崳H;(p𩪧ʚ8xF}}Z'bjRw{:rB(̰V]Ǿk/|q8,a+p,q93$?z `d8֫)/ җ;'yjˠStWUW7=~2%=n=ʹȋcc2z/'NO^7?n?sJjڭk oWsJAeÇ"ʄTJV ]& )ϹRy'\TKv:4M)v3%ud+6f;}͏^56ODtg\d4~]U^nNH5rcl9QqÔ9y9RLg"vLߗRl6V\y_lz%Dt1^_%԰𵹥\wQ2՜@K}3fɅ{oAbk&Á4 "`եќJhaHLXrĀXr:9t)|yy-E92Xf^&P Nyب._svwO K.X\w0m[Z "nWL\^^!R.*7 ^t?+9هu6.-g14nh("G7LfDG]\^ɍG(r^"1f1 R }]F>"eK8` D)L*UN$sƔؒe)`Vs ifܭ''׻˜RgC"3:Kv#[ɜYUiXjD\bM-͠;;bx/t]d{(o'SJc޲ĭ1.z҃N2*O9s 9jEt%J p^\\mK!_(9:e.i$=LB[~rq_<謺@YMz".{=_`F",O?}i#tU#Ua)ѾqR^== wp vɌ's$F:tK=Ed5#Vf @h9{]=9siK)a}hJ &9l`-,!W'lb :1"UjE;l0iJ/2QZf gZHPKO3.Tך̞ܿn 5ր 1R]FpjqϘݓK;me(,酱oݺEZO_SkT 7x vqf`բ*IcLy: $.YcRaPsލh] ldJ0%բ 5=XdV`w]Gײ, -7:~mF~rew ~'''Ӕnݺ#(幍U! ^n"g[ r"fRa-"Dn6n ?{z&޽{siTߜ6avL/hf]FW˒h4֝C^2lN4M<.ڜ#"Ζ5 QD'OI\_.X<=n]ظUK-a)f&,mbݎTxg=8Mv;=z cufɹXRݚ[C(mZu1PQ5@}z&v#5@;kΕ=Bb*%-`7.~o-ɕ*h˒WdٺiK=n#-Ot5x% ꓕ>;?Www3NQ]~gY59,fD?@jm%$&kbf1Dfr>4O :xULթx)O~S@%#boym&hUBUEUs]ck2檤Ե"tf󬶼/0`.%K! 3lC'7OO^z'1dNйE r!z4Rsu[4vmXaiT VMʉZim:|ߗDmlZDjh'7ΥTs$lpJt~~wUQTFuM?gF_BFRɍ"1/BX l.!RJ)KV,ck/U(z_΍n zbS/ضw*:dEށw./EW՜.)RM$\ʒd|0=DJ)qppvJ`! *)Qaٽ1uxeE{ª;f%4.ϸ mpHU3S}@.ӄXC?0sv.kr6KT ]EJ{|έ('6/?# ;/Z9Kd|b(!EbX\LKu8PT# 󴙘b"H(s$ %")e?O/u;W;UzV:m+6fMJwP7Ruj'\y.#5r)-ZGoE:{ _O-*WF9ia9x5ѰHZ稛ud?xT]O, gU@_0Z 3pH7]:2<<-fVWGEu0._G IDATy<|4w N̽Σ9C CfĜ3:B'KPjq$dGLy '剐d2lKLT^]䅘y3%~?D&_s QޚayiuY Yw~U khZMZa+K@ g*4_ͺ D#u~R蜳D^ K}: VeU|w,~yg왭g`]Hіr%pbm_lu&)/-ڹf)IӳW_}qeݻG_'G\viZE ˑDn=zfdBU!ih#<% 7^22 ;VAG:cd1+huE*8s9귝y,-vѝ&b? ,XT]!rÞvPW΃wAN`3.b`W @ў5J% 'Bֲ8 TXwzz FeT/ƛo߷$[mBJ\r1,kу*Dk:ralcgTr-mRvI/'vjQE͔ BNrYL 4)O3rKMb09`JrQMy1"sdz!l,0T.=K.".mV65%q:niZruy-EY[(ZySljiקZʢQz*'@8=(L,߄TAqkG5*ikYNOTuTQ]~`nZAիз $"RRD9 +ƅn1TYskT3Nnù P-BmWAZeAn[ 91W՚N']-fh7Kjgƹr0])ôUdk"C-- ~'e=ˬEq:RC&!u)M: bB]jFn}U틌:iaǝi/wڗ(wdT_n[ŽZ,bR@y&Ce_XiWHBÝK.the*f?}{`UۙD|#H] iIFx@D 1UAT{bVͶHLXwZr.u25BL!* A6 $"ɥO?yxI"!m]H(挢*jHؘؑ)R?s\g~:cyUk/?9-\)p$$&,NҚi.S=jBZߐ}u\al6E@if~Њv?#Oa gGYeY#i?p&X[!(гm-GQJɒ+3€֬Y\,V*Q略_^%- WQ?8_Sm=ʝ;c-ۅHmw)R0lK7 _^3"X֣)]%9GmF;Jӄ/^|_~b~xf9n4Y3*=)ӧ/K}z!bĘ1R}˲,٧^Q-Ԗ=DnG|/KFbOUe7үE3v=q|uu]"Wn%.Eu֛[W_K+w-(ciyS81Se1"ڈvK'6 6f= h̷VSJXqӳa90omR#BAyJb>]\<'n2Ӎ#;'ss.*N`6!)n6']\~򷿸#s۴߁}[t9g3o.yGK߲Fc&J'F"),@#W#' ,UK;oз(fT+c˲4S/':UMa jVi7lű "3&Vx̄ggf9Xێ?D0 jIcdQebffN"89x+",F89> BZf4%#*M <%DE4uEڀjrKq $)^-y1+Bts7 C~HOrGRd"%%g˓.GىtL)qJ͏YD1v;pgܬ:ࡻguDW"@3{20@+0o߾m  ttt|q EJ)ab@d‰3#y)5hYr1[';SSRmcLlY yYlLw'w @u-XATJ|;7.|1*1ۣU\ʒ3 mBTUE*i;cRq.ah5RQpbuggg~`T_(a)bǰptt]A.b~Gyۘ|iN*J-PdufbA\^g.m[ٝ}#6Gv1!BEJ@5U#l*48 v8^-Z:$OBPo8M)!uLUXy#u"SIDi.!^ytFu{p/9`fPɍ_~'?QENO9@-"& PY:33~_ O\}M6)b- `n3mT•n @3/"yY</dDt AXf3'bb z)Aee1۪k[t0Ʀm&{q>Ck]֩j6SbS}Q+W:z%w{͓_x__}%NH~GmGN;H.Wzo4H]:W įJDV]TԖJDi҈4@.E@)<]InLtFJi^~R!T|~Յ. :mS(Fa;_n&f[s-ؿL6Cn9<^ ]7Xu7?<5k\!\HR (?qr\eQ YSE 2SJv 9DdNjJz;_*U4?L`Yڭ.̿gLTWpp8 MQ U&ZU,諯~g|dl'o|֣ ~7i50*R Eeb&TOYJnYd Ҕ ²d&LSrm:P[|{RyqH)6 cD#O[mY4kJ6kK1'5"z屾ugйh6v;i4dŢe`T?иU9:neyv7zXP.@5⭟/>@+ {q*R ̭K1B`C[h7Rú!W}$oL@D^j?ܪMܷJ>rU@WG20?x~h\4qB|.=mP$E30+L@L)yܺyo>OyETUv@mgdr%u-`$R"7.p76 5aj. Qxp\x?f+_ JRB(1`ljuXi9==jQ]D㇯vs!Ҵ.>1*K &f v31smuKADN")%Fi{f*LSRRHYl1fLrYϼj[`0*@Y#rTq,\pM [G9Q#W(bMhCK׸hWNhXG>7Se`T/m}vbϋMGnF1GP1L&\T5MI)lyޤ=|O}~+7.hH%V%{o.r'OEPb5MWW +j=:竬"LBA+4|-*!wp&[#Z0=^ |U7uYeeY2J;s.71q/ 2J"BfF$)s]_?@yY=^G"Kbu.EZI#PN.RE -wMRz-X%U1-3/X-Qt{EBU(GV6On\<~˼) z;T[L?Лqɹ+-fjL3!]Xuqe]USrяbk>3[Um+o9d`T7޸чeO)XDRl7%b(C^9= ݻǭ}ti)̥^d!;Ky1Az[IDAT qsTXq} ߩZ\W{PUb퇜uf!pX 2{j H)2qkK=Fc]&eY"<<KEUQ1HIBU]fv#Pl N0ܹ} "ޯRأzHQ%6z;ڭ/-j|0c*!,7bQETu e|`;J&6}mk ؜ET %,hMr9j"YLb6)^kgW9@,a( |I vl]ejqV`l,SP( m(DVqD1ڢ u?5+z튎? [h_kQ]xiChSJ5|&+伄qԋ(1եJr yQAΆZ~``T>|xkBd{#1V,S\YJy?ZƢkQU1J.C800WkQHD*KWD%眣|Ju!yPx;C800W{"TE P:#b,DD&K)0'fK(wFEa0~-曪Uc"3'DDL*"E91Rd /2^);w .x뭷l)D9 [0)/%e!&]lVeo篾:dl裏>P{_ӳ z 200000kK000000. 200000. 200000. 2000000. 200000. 200000. 200000. 2000000. 200000. 200000. 2000000jI=IENDB`PKF\  -Pictures/10000000000000150000001CD38E5523.pngPNG  IHDRzc sRGBgAMA a cHRMz&u0`:pQ< pHYs oytEXtSoftwarePaint.NET v3.36%eIDAT8OK 0Ps=GHڦ䣎*nab!4$>+K=/Ű0W -vy^dž7=X<sw-o|?>t 醥[IENDB`PKF-Pictures/10000000000000150000001C9525A37E.pngPNG  IHDRzc sRGBgAMA a cHRMz&u0`:pQ< pHYs oytEXtSoftwarePaint.NET v3.36%qO W4 #ӫsUTߟ3u& H0L2 ͌YFR"É1ߝq|C|'00JHghkv^{kUʧGj05P` E}=* %EFٿ?-' ?+X ؄Lȍ.=,&9+'gO:::p8L&V.\``iiW?{V{b S~z_O2uV#GH45[lF]](Ǐ!" Qc0֮.wӦM񭔔1]Ǐ\d2u)++]>n߾]wD 3OU^GGE$jHd s@x:^ǏP(g4`ӧjb˗/._\__yf(77(..nhh;Uw`@EEINv;55B_SDڵk .ܻgQ$&;TUWOPo_^^Q$47lgU2ٜ .۷Lr)}bɒ%yNdk֬y EEEߗ.1xݻf.p&&fh *rr(/gz]x͛7jkkNnZVtב46ʆ (!,/_""/Ν;'"^rEUU >|8p٬Yv;x<|xyytm;w /1*[IENDB`PKF9``-Pictures/100000000000001B0000001B3D6B5810.pngPNG  IHDRcsRGBgAMA a cHRMz&u0`:pQ< pHYs oytEXtSoftwarePaint.NET v3.36%IDATHK;HA7j_h;'8,T9RGesXYh`XYhy(r DA}g.3-ǰ;vgfgl")K9*qxNXX_/鲶Q{(%UUB锫+mgfns6rpLrwrRKS }}jlYjj]]ͯ-yyˆ\ QY^^L?&& w(DjBqDo.q$-0 nB9+2JN͍I|uѫUYil =ʘ re$"zoџy3-P#{pЭ _kve%"Y "TΤ5=8]<>ƨ٩G4vvh,9>px͈S<( BWVt''|??|4PX&Nk). CgBK*Zb3H'@H BGFb@h0Xl8NLvua#'5Ғ^:b>-*2oC,/G] *י),(0doOVWejňu.z\r,TnJEfh}t訴wG#ӂckĖk<IENDB`PKF/JJ-Pictures/10000201000000200000001B624B8874.pngPNG  IHDR ǍsRGB pHYs oytIME{L IDATHǵ_HWIO3BH`K'T1h͘$H|jڇˊe3݄A|QțZ,ڕILlⰢ6DLb1gwK[wsDD&&$YrrO&<+W|5~C"YΈtw+]TD\哹l pVZTѨŢ"%PdEx_.vw$Wrwccʹy_+m'TXAe׼b;xFieC~Rc@~܁Pz>|CbF;.,3O !}o0Ve׾--* o݊[^^a\;w$K$O++[KSB˹.8u*'ر BϹ.@$"b6 "ol7"a]{ւփՂWaA?/L@ໞ(//d2&&zYXÑP=(L>_gO^E9cXYY)&'F6;*_ ( *KK6Ɣn`dd)22quMĞwyIYPӧ WgWXH̀q[zff`hhhnn& ƌ@!:$͆>Črr"JU>:dk_]`~~>nd4 00gG())!-- .fa867BEd-vEDdiiIs:V;,r b}X_i2334vuΩhBv6\ 09 -ff`h^|%N HUUJ@EEu"NHrr<*ۇ(RV&29)TPPͺ|cMMMX "]ӌ55j,/Co/<{6U]n.Xp"*b+0)xIENDB`PKF2-Pictures/10000201000000740000007B7C2D70D4.pngPNG  IHDRt{̤sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<kIDATx{Eu?@n$!\p ($Gn bPr)m D("` +P#1R$ $+dݝqfɻ~\ޙ[9&{3;g9VRJFcC \,^6?jT׋zJQDG}"7X,ZF2jj@R}SIW'~noVjJ@R〿f#Nwi6Z<5 J!9Ȼ7ZVP"`XC 5MlTa*8%F6ZֺR_FO7>ھP$dj:(*`v7<*vi(*#o<~~.?FVb <!e pGUi'F2}XkE<8W!F>0?.BalҧWj;:0 oqc`J5Ӥ8ěv*HFwkj@8^|)EmZLl3ǁ'3 P} kt1@ߊ=P[`F٬2_\2髏mJZV#a`H1ӬeC5|xsNodޙ $2u#a` @m1}yNP:G7MhOyɌYc Pāk>̱ߵoFQ [H =)O8*;P`Pg%sj_7!^&YF #1u]J {oG7~@||RI0yWXH)q2sY]#gN_ ,O'}ۙ^Gzo% y yu [CRi=fyjNԩ/َ`LQ3p1v0-#(פyk>^ t^Nv4rԈCB>)" ~5̓t:6G};k-7I`G֒دm9#龌a܋2ښ"Ӊr{0wP:/3]?4`vK[˳gc)矍֙[)UZ~`j`NFkfILš· h0ٵD gL dمm;0%dOdG,< P`8 #9&8|sh;F9=CV;ׅ:rqz{GsQox-I/C^Q=(Y23_ mNzGm%A`L9~58pF/ߛxCPWE3HYޘ7,ƣd=Sr}3GKnbs}2>ePrrO uaF'ɤ>V/Zoi{,nZj v+o!?ԯf' 7ZH&"qy}dy@.Zat L ticqӣR֐y1ϛLH¾> RE J`R5My q]dt׌mcs /ck(0/ynš19/ؗR7 Ǿ%d_9`B|38S0Qׯy:~"$ VY^nAFID֭%e% 8+{̓WJp }vbBz J`*uGHJa./~5#Hp E8(~";w z1"|ۻ)'cW`hB]'[d-G拾*dz0gֿ(ux u /RIHSJP^ג\c;X%=Ynzi:(Pb4q\~t?6L]?:@Xdo{BR__KZ LTC"fs暓nz-'`},W=0#탬٪3wu !@j/ s{e4;Ƚ}5~[)OWy.j%qWJ i+GU|A~ܺ"a>(QzRpyKߏP$lm'BPi@M!=|:GjT)h`vǝѱ?-C i$tFa?/ Xq&:1ŸSKA>ֆe>[ V-NXz`.@ +tF>)%N,)߳_hׇz`PH_)8˯S9q}fg$4DKxWɭyWWb*1|_L܂XHjIM h#HF#WxJ@3[RoPTT罷KD ae PRJE*}D8ħ*zk>ugi @8J)DS@KRD9p@QQ7}]FFO-/}g?ϝ;"H(Ec!&&*""&S *Ĥ&h%k) ]ocO<ě.xr e|8w%SUSwDwW3Cd&"&*9*ԢT J)nZUv 8-?.4]nh /|_1x%353UqUQDb"uscbDLuZR7ۚ8Dd"`ZDTUN]woǏ|sNWmOw4]n4y>j MDJQSGDfduu$dioH}?fCq4z'YLr.cq;x?G!Q15'@cZ&bQ4*",1J%LݧR "1#R`bI8B)eyxΧwOZ&GDd"1 0Q8124M8B`pWS !9H"!a0ȑMH֢Dj7oiv.7e|tyt7V~vvO-vw'CL1(K7 b\]EUB1P.YF,Ӕ Ԯ\$8t埽{7V7ozyxjޙ!039OIqwD Dw9lkDTDLͭ1bd)눈DT @ERT(p `{gG8_v㾫hr㻅?ZVHDlFÌqqb05U-yR)D́L 98nj"EEՔR**!SHDfTs.bJ1&susSw>$ irɷ.l>?sǝL8Z2@0 @$9RJ@L`LDLHn殹<VJ&)u]1"Է!q@2N!fLME['ӦˍƷ7{wO{wvw ԷQZ)Cw@`h&nEM.k~ ﺐ:͹c Dn!11z25zdDdw"?S*ˍƷ+_kτHS)T8p8``bd)z|tx|x013;58% Ę/ |;DW@*bJ)uvVw)y=NTDw "2Q)c>{㏽ڍnh|O{"bPzm!F 7G|Xܼ9]!ZL!ͪ"n Tq'|$zEl= l6!`6y\lk)E&4ss7-*~}oh|wO 9k11;{CL)I.yD#"dv*"j殮f*u}L O#>fH!"3#A0 !)"&fZvN1!" ZS)j#;`jMo)E39% r<cDp7wB1%q\;mV=YUUw':ڌ>O޺{ku3b&bLb ##,إo>uz^"VUd"Cdjnn=zS%t?>SXVq]C@"7W7RJK)jׄ#{R7tʸhծ*e'DR#RU?[7 }>^l"DK]LqfEKteQ̝j!"zqRծTpW!ak+0ols7ySO;d{y4]n4\O}Wc=qI)p@D3SRRץ^2SX8" a3{j"^+e3) "c@@G@d`Db'֍ Wy;_jfW/_vʵWé?W~}"@Hj9M#4Nqe]?k=O<>oiˍ7_'{$A)*RBɹl)3Q!&$s p""8N"1վf@ڌ "O7p#̛ڵZwzƍhn%g7iAGx{ d R9\2!b}d1WDL)Ő(fVʣ?c>أO1#s0<xT7Z"3_r;Θ9{JZy1{WAV5d6U5Ixoz1|E"Gx7# wo.GkozS~χ>|E@@U-]ӿokov_:ws.݌c"sj8.-)0oƫ]hEc1)ER@DiUUS)R)I.F7j0 jLCȨFP;Wn\|[ׯ{`oޙgbZL YKa^J90}?C;"!cJ)DL&IMks}6Tf^ׯ\ݿ~s3B@j^J21"b\΢bZ= >>4M~j #>?S!rۇs_:_>wbm+CjZ!Db>-L9.m)e+T !cNDUagp"" NӸjEbV*ā i=U_ltHOE kcWqj]I kG4Ns 548\|ӧvyP"Lyc9ZO:p-ZL$dD fڪ*v11-9f"v"ֵl@?Coio ^<ŋ.tKX)FB,9/Gfi\{H|{p3S73Ǣ%>ƐB \vQV-%CQ`&-R !8ց}oV͓x̅ENb@&"Dt9f:.WE%Pj(PJ!nӊX=)qe)"aJ]vn6f8ȵ""O<@P705l΁%\J.%Ra~!u[ okMRp /x ).#8ĤRiR7ooxs3:3ml3')l389.踛)xɣ2dn*1JED6)HO9~cCf0p>o­CXWFohvtpcS?l9qVM cJ)u5KVGjSGU QL1!2ei`m6NHaO9s7$YG7،䪦VTNAe H͛7]|=oo3"H)¿׿1_iup8h>Ssw"fZDBLRLD1Yg[/"&p`!pJA_~/F_={!nr"t1iʹRcPUqh2x012Rp5w(l]rbH530Slv73%#,XRr)1u8 0M8&0#htwqgds4ͦ:c]X5\R>vNN1<땉R`$6;Mms' 7^j* jw4.@k_׾o\|> ^U TTMTJakp1k2Uc)u̓[O<is}gNBT%%wǨbfb&S@a@ (Dp5ƕL~LBwKqVUd埘\dSM"{bwWU׫e.9rL]RrR땻/lf"Vǒ L!ս׀Ř(p`W(*x8}rȆDD!D"&s3 i#~Uͮ:^x2 wus7]u,^z7}Ã{ցRDaC &F$)Y캺bLD,e80Zw/ϷwJƟ.\|cz<CǑTLgw'ı nu]u˴v37TJ)y{SU\uMRrɦT{iu|=f[]MӴ\"D\kAtRJjٰptLࠨ+G"@ *B’D8cp\4DsRza)P 9GZ6iufp\k.IILljƿW[Ӕb8I!"2SWw<>٩w?_[5e>~B_*2L6!Q!HL^zM])yggoH2qvQu)e6]kXRDEsgciu\]Zs1g\rM#w]c Me1 H`޳5Sǫ:^.+㪉L6T nr4"b1u~|t n)-bd0 #&S o9«)6ݽnPjl&K)vHhj!BЮNIu, 1O8D5pz/~AeD֕Gt&X.~!/)%"Z!RB9DCmj1 iry^SJ}<;i@"Us@ӚjV cBLD7n9Bؚͫ7VW Y+*w]t i*{Sjdjdwc[K"ǹ`!bb2(4Nb?^q3mR6ҌU_Bšhp'3U3Ւhə9v]75hncK]b11#B@ie) bzKl>Wi呚upW~Ɵ}+_a:ҬRf12ֈU$T[m͇擸L@2zb)b׫rTK!ĜG)w]&]ft٭8jJL1vx3!t?}!FUP IDAT٬:i600wRTLD4/%.}t X;#Rx^11 u0zc#;(MEGlk9qLO~0}pׯܼzkKqfVc|S$5~'ܫSb&Zjn~`ffB"<^c8DB.󔧱 }?C+W.]tܷfqT]϶ѥ}nn3/9h浳LZNc-fDLHV'!śRZ̷gp<>>::::?'y9W뵩o/^yePms8AK"5.0S*N Sw٬b{HnU-k!Nn\R0}x[CmK$Q5qZbn6̈}9O3g/?szKdRJglK/_|1_OUM gS8W.}wJsƟN_}"/}9Nzx{0DMsu͇cq2WfU-mtO1[Gq<:<`!v]J)bN($pRJ P65p% 9C!gsbFSx1;;'_J =H/} ng(?^} B)u]ru| 1M)p$f3SU@`j=52v1:5ƵJAfP}*dcJ̱9%p[bMzraj\jŤ:%?~pfL_.r!j)D Sv|z.9f!`Rcd+n׮^r9F$gbjUd|ƗJ)i6Ęowv7]n|1/nh@XrlX|URofC7_fGp6ofIp&n ܰ@j9u+0@TDfb[UXi=c[wsǠ"mR1DLQ~"s t6^]u?{8<gj4W^ץ")=Oz;@bsK]!l֍9KJO@mmmԭr\jwpј)H n.κ#p,}]R?tCB9pӔ9)#3IUTS yW\vj7tۋb 6sF:_~U^yʹb7n_w_M]|3VLOZ62 ^5Z72X50Ov{:$ "nB` uwɠp#P2ZJ nV]#6q3,Oαk35ٮ'tV>RJ-vԩ1p"…?s"LԴN%px2 Mۧ]]DӨP 6."3#Vj nIO^{u婈r!K-fhfwoϊ;E[491׼]TE$L$o s4ζ^'Lՙ qW"ut%5AQKA۝Z>HkE) 6Q}\JrӚa0㣣ׯ "# [ Ó~"yBJ,&n)je\{^[WO!,9k)1GGW/rpb{X,b #n ݯ{Y.WOݸ~]Tj,CΙ /6PbQJ.S]/~Gsc|ůI-ˈps:x5 jtL9EЪY\;5 c$b R6ҶMz@ U6AdSZlfzӚg.={w7TLHLUii=E#"S)nu F:^-5Ĕ"׽*jRT1uB"&E#@Tݴ?w]'Vh)d\gqvSxy8Gͽ'Q0&B\ cdĚ+c`IC)ڥ]?Z9in#]ڟ|5 1V׽{YW._~m913J!7S`>::^l4HΩOLog.7c7(p> X;_ ߺpLi_0n}99MDm(:d0JWդp5G\rm{67@'3ptCppK)S򘧜Q7lͶiRiWƈ5:6ָ(4}k_C0 !!: LS㸽=q3߼yݷRtMZCQZ-,z{_KoN߰=k虤")ْ"2%YB\8 )ĉ;N% qbX2i"%j64XTDnvP5s<|;\>Ed2^4jw=m] Q~㷽?i]~z]7޾Mκ8-]NŎZIJ7*2F?L@} 8<B) Syb[G#`ΩH]]^Iv][eYzIh<y1FAٮآii: {*>V ܆h̸v\l7mzZ& luj4ϒT(.1QQ ARg۲mDwuNYR 0&U8gua\UR*fH lZ)mmj Ic$5Fm[-rg ڤ:I@Y~:>x iжw]^ I_zq6(6p7B*%$!(mbȳ,3H)A_śo{[H8u}{^E~+wfwx[M$dAhŦrGGYΆCdD6&n,B~d;><.Q mv,/4ϽuuSn^}#7QUFREwںlƍɑk[ 9spF 1i2xjͽ JmL$Y vn$ ٮmɕ4ۺچI@IIB@s]_'=|`]Li> hJK)9`vl6u6X׵1#3Hmj g>x/"45&HfnjRj)LpUCā:g"I{l6[\\($I}fE \Wyݮ*_~ Tժ+f8{9xpDH "ItMb<N&/sO~[~37޹B84[}ҡ"R~KE\>Da|Ѿ ̬VF4(@5qFpɍ|y&&I= Y..,q!xgs~2n6nKF)smy5m61Iӡb$"]`H smw^H붵]]׌"6ڤZk$﫺lG'tYwwιw0qV7/Nvŗ^3ISI.`D]89#5 ]ו{$ .H45i $"r0M22pZiw~klV$0q_(?kjH 6c lg۶.5AL,DWq=&ˑT:2? 1r!3bMy sιp>PrXCfGq&R߻񣓓+GWBpB4"bY'/'Cq`zjSyJiAԴ~RYRZDJ)۶B.8TTgs\2[ ѣyX /·4ːh2s47쌙?S? mAHqVվkm׵$(cLR(d FFS)ջ%~Yc 缵6޴ٮ닳c$| ?\uU^|Vҹ NNݻj6U[vvjvB4n>h5Ӻ&+J=@/9} ˌfYe8M m뼷mz(6|"Dژ/qТtSZi (p9֎'cRz Ci?6ƹ'"+H"o%J[=28{IbxXVxgi͗rU7h8i R)`AmeVi%DB* m[:1Ct{Hi$rĀ^_' |px8D"lvM=U9Qyc ۶#)TI$ ]tmgt8LN{;o"!~CHBxu39lnlr%5  {"dA0x ׎&I$..fggE[5ҵwjZ-s G%%E3?c8n͇cd4?g/|o>Ƕ(b'AXjS d4 "csmU{v]]kz1PZiebx!).kKOv`IfmS۶vQDiu> ~oN*TI H >MWCI!!!qjD~_fr> 'Igz^Yh4̲2:Ѳj,J:g4JPw]]R "V1[&IRE$Q>}TנI}>_!tM&I/E =s@!HHkM$ubTMSuuI<!{OB\BDsn9mV+gm1*,R"ZVah<:]y#ꊝSJ"QhÀGl^+(7&IaR܁cyݦB)ݵO~G fOw/).@ IDATg>u_~1Ƙ'qgv]׵Y \m6rڬ7DB*IBƵb`9*)0ذ-rʲRvX_8E> ;“q  Y`J\Ell6SBMv]v2G<ϗzfh|4Rbv҈h1A pp!Wkc YE!r>#Ay{qvߤq惲d8|g2Bpsv[LIL>0J R` hl 샓wz{v|>k$ʕB >#އ>Ov׋eu]+A2IjL"躶t~x> I٠zF1()c60"uں?.?=mz;p`ԁ6$Id2N<5Mӵ?7 v>Ca͂7g `å92!߶M+md8>zf$BX/բ[ݰ(^W'J)$ژ#R)]@)eT>Ļ%߽.0C:l@|]QɈ=Vrdd{MSY!RZ+7NGͶ4!B* rq59\!>`A]疫rBt2LYW*x4m6nѱQkcg(!w;W1}ZkX-vP<'xqx~q768)| "Ͻ~$b|ֺk[B|f0(cluy1I ALJ5`;ѩkÂ\.mۦEfC H38HmۦX,sHtPQcsk>?̀p8\ h) 0nDH&_xup:4#п{Bi<y1R@`c"wnMX ,J!$NɤmڳǏ?>+=q5rBI)-rDiI!ymf#8:>I*L1zJs>3USV)RI!ě߾ERʺiBbyJJ,Ƈg\ >" ߍ$Am-bADx:jZ˓ӓc H.|)C?x.0BrIb*!-`=7c!>sIR*''1v]ONԤD*Ɍ1ʮiL"-yR!wۍ"QLF:|9HTnwrrZ >JY#MI< a$$3{oٝU]D$aD)1-} ]powf0pzr:{Fv6w.7/_o{Wo'Q ,I4˳$IH pvlڶ;|G "dz||&|vbRff&AR($${ i )mCWVbqt|9F0@fbjтI>$qޑs_m}ֶMUC`! f5)e;p4̲gj ˯f  ) )7.PIBUU=ex4sDBʘ(=áX˂ 1$T]gnM"h:ϋ|4mw^) yA5}#أlr|<=>OO(عε:AA1TJK),WZ7UUW$˒,OT*cX6+!`8wl?R*}]Sa&F" 9]ɑ1*aP MUuRk=Bi 꺼<'ӣ$M޻i۶.sO~Ν׾'$(`FH$x2gIl@B~}/yvu$! ?z4I*qk#trfϞ!J$EtĈC3# $~L&値y͛7]:!Df|iq=Zo&ONf~qV`0`!/ Iyj&ۗ>H,O$˝sGWGӣ^E~@ uQRB) ڤH' Vel;9:IBJ-j%]$U=&v CBCe ߽=_̆Q1xxd&!0t>84b* خ] ODQ R`Q(Xv=sl>+w;DTJgy6M4B~tozxzB[ܡK8}qFD GGǣ(λl6l6]Y d8-i1f{^>y%`CH %88 H\+KYǣdc<}LcFƤn^$5F`8kmك$uMBzYWy>>T]Qa!Bmvm׃ ($P. .i.0x]@xz:ì:›.?=?~KRC 4MA1ǓԒ|n x7dY:OIl9k/J$^ _hbvT"}Ū$Ș,21IZʋaV eb]=홫BjuJIyGWSʪWUbdL C]<{[YJaIxh $$pp>PO1^pX!eVT IDv0 >w|\/fY )AJQhP8xʇpnMB' !:g}e`0଍9!2 HhŨL"!Tܻ#kWzR|Pm9罃Y$;oY'p4$B)6CB$5Z3JT)`B]1EE$ ؇sރ]۔rMNn<||<n"ۧo6x߼2y(&yfP&Ynl:w9T%)*cXd*U5;;]WucXbV2qYwGb(T접uf5OJu.ZKdmSwk"Lu$6Rh%H\27,R7^yAOH!{Eژv]U5AQQUW.+!I)WFJs#BRR Z R >8y{~jo-1s+D=y /KTmS/fuUiB*BJ%$sn5mWk56k$YGα|IC86azl׶m'$i$ILJ$#lfWIc`8R %ٯ߭ʪsޑޏ}+W`^/y1tF*-#ޝۛd4w΋"Idb|Py n*h@~KBՑ2ـ$I$R"*&r-F%"]i1( jX.mS{,(GyD(D5B@!Pā uoW׮_񜵾( .̬ir+:I!tMSnGxY/s8B[.7RϽh UUgt2^=ݮ7eUڮ#zOR3  !AJ@t)!ABADHhjSW|Nn}=š]L*MHBX^\ncbCLUZ;$s#M" yյ6U4 J)HoΛo:kCۭEG! \z(PIMRvݮ_rU*Z,WEQRIB [ۃ+ B!,89GAWhp@Q-fB,ˈBkƨ8O5:AGvZmhڋhh=x~s}.ω-M[:9Ri4B4M.lt!r\/ ''w;ϥBJDLDMUٮN.ڶ֒Ty?~lx<%wN$M3B\^&MYSZ80$ (0շm۶M]W_W_|}#!evݮx~׾O_n}]"Dh$F'i12zbDJW$!$Q&!&^<_ƧF? gzHdDiB&&QJ#!xu1[gQ}s.Mt'&l+jHJWɣ9ﺭu2 -4:M2@TR'Y웦֩$ɲ4I2c,<7?sD?1OO@D!gR~o!n<{=Ǐh4;c$gUJNqdh7MI2cTpYtCE~1!A1褗eYGgz$R!Jj) A)az>;mJJf$vnAƹ?QS7YH TMu>pAY$nE1G""D)n[ۿȧeg1w/y8 >(t](cm7 .8$b檬N]BV (gp]x~CB$Jn-|e~|:)!n[K_4a~"#)&Crq8D׵W.|% Ym6UYƣhr˪ThDD!B1й hf<;oH(^-5 tMwNu)KI1,V$v\4UMHh[/Wm %;oIvT$sPBL &M4u]&ɆI: ,k@OO}k_E%drtY{0 $uUV$"ࢺyP g`P 1KS!P0=x|Er J !0` XcW׫/{ >>L?>Oy֛/λ xCbD@4M=~e2Bx_~[g%&:rY]a;Bj IDAT -k,we"!$d$߸yc8'$WU/˪&ѴwzmJJiL*H\yAhR#HQLMy`Ru]XڦR4UW_}͋G˲g'o}ϿR\dl0(qR8I7ZW循, I1Ɠi=?BB . &..wDi "u =>LG:1 έW??`>__\iۦ{s_b+_䗖 !|08>>>rEIYUjn6vZbY7uӞjH轛 !~U{`Pp>(0P1uH}J~UDJ'.lP$i,U}~0w۶BE7oo.>8Ⱦd*%I4ٌf^mn0->U/௱Xf~YD4nݿsv-0A,f6fsdWqrru]wM{4]]YFE I; QI-v弮Rfsڍ}]& EQd{o;#sv^l+o"eA|XjCQ,UDAEtgDDGu1QZa4G$I4z$`g]VrD@%iߵmG#(u=/$IZW{)T+۶lַhQp4ˇCcD qO1@/CB }pD"2n*$M/7{=:^~}^!RPH%bZpW+GBHA;{kȥv~otMf6_XI(Ѫsv^nw@v^-$qiҨD"$ !@JF9U<=>M&Jkom7ՏmYoNOvj:~$iUiRZ|1,x~">m.!xoTJ6$!v4|>'")C^^1Tnk;SZ"v].F)yRZڦ G|){#=>|)'C+Owu&}43h*^P7b6_Vnts.p?x69_ B$IҴ~;Rfh\-j(4Iߢyy-DS)KzY8ُ|(Rlm6nkkx>;vBb0ZkR>x8mr1G׫Ug??:k۶r)u=s$(s` uUK!<ʒHH%9!t1" wDBa?pAHDDJK-wuVi=I(o]f/+Agxc,}P >gul!}>w^i~Z?K>&-dԈR"*j_vuhg@GW4SI {+t&^&K/\z+c $:X$|JmK7}Q|F;ڲ*'GG&=6n!NlBnۗ{|Xpib?DmgU&Z+mBLb$EoHI@RH)e4!Q(R)tv]f8/^d; ,bv>lfmkm:&Jr;7p[fAE;vN <3Nh>bmq ccZ,5)IGsbq ;e6?g !⾆12#M`P 7~ïri]GǗk=Ƨ۟󞝏H]v)q{VV7"Av>9b&M{B~O߬4-Fx*!t=A#I3.PD6xޛږeYs5?m"nnt7]ʝDIF c.+BOIϿE@H>3'q<>lGxaT|#Q*#R:7 3|!oeԩ#͟NTq^//~t6fGDl>|wJ^-Wxf!F&J+Q]E,C(TLuqpD>3aqx5M6<Ĝ^Qe⺮f!pl18 $}'"~r葋]B|K_Ϳڦ1$TukFj;ͷ rNFdL^\ 2gWURxERNW\ݮNUڮz##1oTN/G@p"!> K"ezYļ890짳EcG']׵m?Pf*ZEQ*deY{=Rٚ<<"Fqh,ȫTT; 93\1&֟ɫ1yk ܚ"{}[̉eHuK<"*"^x2f(E 4sttt|DKG]F"254$ԉd"C"JggUY֣Qa'k ff_ԧr:==˹jQY5_W\N5y4q#!Hvzl69r_;2ćE|,7в>*/Ɔ`x? k?eɌN֗'>i$Ƴł?x^cE`f!x7ez @+s겈cquU8*]wtr*G#"t]lwEYTV jRRu?{8Rh3O} ڦih42s>Ɩ󠑛h<'|=kso>[p|  71w`,{)ɼ*"ĴX֣hC5*ly~n_u`6Q]4 P/(lĈEEGQ, /y6uw@ףt2ˢ$"Vx?9E&;()O۷:9qr% 3|1=2UUzw*!h'QGo }qE:j Hg0ӖVˋʪ:{;DZ..//1pLF?c?&YM}-E)QT<^ǒ]HM'Jkx%v8;kA$%1 VK}d9,"99DF0CS*ȕB6 9D}Ȉ\("@YUmxB EQĺR4gΤ><:J}kvMmehX|_MFd2櫋et2Ƣ$ ȕQIUz]$}3\u&\okAU`C~ | {|Q29->cp>w-bc\.w&E`ݿ K` BQ2nUbS !um0l=_UqQD0@')d6'T@ӘZ!9r3~ 6(8 ̛k+k s]J}Α9bYu]rJ}c\\^n6ۢ. Dr?xprV-—8Ca2Wu}Zm۬.zyO0"XJ tuxƛ՜?O0/g}02o81{$S ~2!C4KPo`VQ0U=Ϟfݶ3s(a`.BX>xz`zye1qYԁ b& hI5Z]UUŴo?ղ=?_!U^JiUwmJv{]w{LHQ?ztUf /"0Up|KEd׫AN魷[Q>YQ}"B<4[(٬O^eQ"WyU1jHD]q b GVn`h4ՔyKνdU@T$ Dž\.We]{U&Eߵ~lvxx8>I9;7mvW[33cb nc>Ԫ~Xd`1DSQE+UQ?1~m}ͷ?_7@DM!9C R!MHd^9~k %][U=8P0lhT/G) .|88<:93zW€Apn}Lc89x͎NDeb(߰1_߉fB9ug!{.jy~<{2OėK}+W^mvfS@(,ا9ݚX0Qj˩r*GRp e>8h\?<O&j'vsyt)w|ώPw_>TA;i &bQ(H6GGRSfHOFڦ}1眻j8cQUEU6zivvm۴H{1X -އ.*nD@8hS9s`D2K覈: )J&HEє!B3lV'EB1Tq! 13wa"/yΝg{220 *9w]״;onUs/,ح!",2/Z5NT3aKƸ_dP$xSv)nvsXߧ,iBo*].'d6WE$"2r5Mݼ!9q0PX)aOnq}7(!{51>` IDAT8`|9Lʪw7 dvR7m:F2=fy^DkۛYAzH0'GmkOMJYRUVmӵŬb.t"kU"FDfz@Eٷ}}2l=3;ݗ/yǣ}0K`(1caDt}9-/Ίe8pL]qawy_rs_?wz~YUfQS0@Whcoʕl* 0S E9(`W5vf!ss/[Py(Z۴"cp's}`8/=|DZ7|1Zr΢\/Wɸ*L_z"b 1z{=#dfc 9*F3Kn@ ĄxT7M1ti>~w["D)vG!t%O"YͲAnD*lN5(0Ә{wם^cvSQž?`Y%g&*)B,%Kʩ뺣kAZET׵Rm6JM)eUEq@R6ͩ% gA(ŌoVbRD!zf\@@@ :?,c?rHlK8_hyqt2Fƶw Ͽxg_ 8|ާ`6gADPPtݬs(0O"J=F̈Jzݜ*'-W恡 ILH{s!)@c=,kLB 4U隦nt6v,}ETD$ "8ZbZe}өTխjfgX=tӭ{CR@EUga?9rڶm6zTu>$<)2;/47u۴qo?s0^r(|)5/||>.O?s녛7s䨁EFLIlnt}JiZw~['Lmkr~MGX CItpϸ-$XT]/#duƣf{rNk~]6fnnw}Q7o.rGǏGO+encI3!!]]ʢD3 L)]?6QB||~)E/ !ܧݏi:_HJ[Ͼ7η/yֳ<4,0Zu%GivQrYգ`Fa!;7~O^/Ͼ}c"A?ҽR@Dst6WDE,!d:-BDQDsL9"e8C7N),HNP)# 00 ao0jXak铪BFSP=mv/pӇ|9~6!1~e0 K"!ܴEQrJ `NOԧ`8Femn^d2>OBYA26ۄ{w5 Po'Zqma?5h?!bq/|zfDXEi2眚 E,:Fv}O*ty~qvvjHϾaN}zc,o}k4q0X!UM)e LC2!#0Q,b^!pbI!ɾT  AcI10f"@`b{n#d^~gNϗ*)>σi 8dG7܅gU=fR}ـj`L&,Qu/⪩".Ήgy S/Y IM)qE`}M[>̀Fz~yxXyF5P55ׯ_yY!@b۟9Ǐv)0Lf^0 zcqL\^L&hTw9eU6f}80L-.̡No.c9!}HMo_sD|D3y!{/ 3>H<`fݸLb@3QTsD UE$y9BU ,r;GH3PuJnvz4fnј3O?y35&.‹C^#$12Wo5@r2çE}wE "]uq9ˑʲFh2 !ttg\.F8dB :{|( >"JڔpxC/(C>Ys!ns^]dt}<͒hDcSMfMz5>gMF1Vg@x0F37FcQLJ0yXTjyba>{vqNĪ}M b(j2 ,Nxmu!t~Ri:>)4Tt(V *f骞$'_h&қ*"98ohp3e9_'i6uU/PՊ&4Zs/vsyrbqpzq1` %bEw !A ;rXĪbYyG?ƾ%$oDU¶KoOO̟?,c |\ Qp_#xUSUTpbqtXƮ唺M拃XT"B`F1KrMU~0A@PFw]ٙ}r'0SUEQ bu)u^nݙ3Rq0"VgGfb#̩(݊zȁ.!Er U,<Ϟ>99\,r2U79%y.mV琺Yrvrqp4ڵױHߎ̕7oy55ɞ@"$6єa<2! M{C,,7_ډz4/bQ8>rtȑ`*эgyxnQbau-Yb(?W^V905"45KHtވHN~SJͮ~­)ڍjiPl1X>OrO'(e{`WIW^v>2XS=}#3욓Ŭ*  h d GҜ:oT 0ئivB]hu}b]H1t~yy4M{ٴn[&X1xk΢"{K&Yn=s|:S|KQ9okDj*YڶmwްS>'ן̓3쇬wppzXa,uI.7Md8 "3Q 뻾٬%fP)J> 'vkvMnO] \us>}~ W"y q`ll1fx>x<CcS]H(5~:`E5IӮO%Web7-}JeF egi݉\?ytJDe2} /bH=A*qUݏ5sP>UY9\e?B(f'oG }gB a^sApd=C AHW!n*3rp!$ 9AK-p0 -Yr`(ʲ(^./NMm2&SǪeUyՊyV]T}\&cB=v}u~K˔z18F ȒE-)Km[C0`;Ih)r!"\V_zW~po|ꗾUrS40jd{^: ~%fR,0LGg5r 0kkd2W}Ri Ҟ-<:[9 ڮn7'7nĺ)PI@]h0=5Ti `UEXTz=MHMMn4+MٴW5?d6_/W]ۺ'cgd&뢘Opr5QgwchJDF2E`,u`Dv]7*ED1*Y9lvִ֛f4"It(bJVMYڜ߻X5hfw^~蘙(GX퍹Tu\>z}Jyq|w&/ bq|cm l%MV5PTU=Bg@ E4 A.%kКͣ/j9gC#pes!F0@G@$)vEU?)/̜^FYeY 9ԣh<"9gaÆ8K*ݬ'ә!(=<:^_^zcTs`005P* "}]tm׵f0W^lFhw|S 041ql٫_eF6&H<]-/nf7^`Y9 ͧz)$t?H65$ LV'eY(ߋE}CM{\ܥjTV%攻+zq|-r(FXsGd\(g'S[)F+}H9A߂D"ҁ8aÉS"fJU1f\^.ΐh4G@@DWĀL2MGnq9Oz{]-#VlHG􆕾iTh3JNM9D1k.,wRmxW~gɼ]M~UIJ}J ~N!*v\'?f}פCbQS3lw>ן>Ƈӷ[VmcQ7Ų\7P␙u?kt@`_{d@HfNMCh~30IKAƈ 5iԮM]qt{d:ڶwMJM-B|s|泡(@)ZDh:].#"'*5ubF +!R5MSJiڝO}ߙٍ[˲pfNĨ ($$O'?,ˊD=Qs!ھݎ8PM"tEt}s_$GF/N/c'I52)@Usվhղk[11Q]s{&r; /Ե]6~(s1xbx=Î\h--T)ί\CK?/8>.[a"`ua6EⲪbCe8 XXf{#_>`ꝧhg"s(cs\_8K3 \K: 0f&6Pd%2n7d2G~ Qu@|n֛FnnBT$a:_nX03  D/,R 7`0 8BDE߮r>> Y-p6JEzS@ߧix\dQ=::8OFԥnxK/ݺr("@rYEp4u}YX?lP$Ng,a0WuUǡ(N]iO'##8 b<_p O7j9O\b"G\Ql"dj9 ,h(8ubQ{> fHXxԵ٣'O2NFrTI1 BMcUE ۶mN꺦(+Aβ8< ES%) IDAT望]6;$, ^}&\F| pS}WtXo~S$:?7?:X"x{*9g$&ڷӃj4"&ɒ"K@!AQ10(`br+kbq<O`C#h5CɃ8` Å~/@Q9d<)rGg:Mяl7۳lכnf ]^-<CI<@"W\"7EE޿}칧nz!b6mr^.JEF0fѬ}]%$sFxT)3/|sN*\)'TѤ8@߻ZI4wMWVG /I9uڧȦSjvt~0T5x# C˳SBcEh6]eAbǺfA0tڜbr`|sQ-*JEc 'OF"JsJ>Z۷o]~C@vIMs@1ZT6r41Y꺃clw1V}w]۵fH GbMSId_m#ՍC?}Ųv-1": 5P_wcOſP0bO<"UUXxu ˳',t\S`F "h&DwHe bV1UBPh8H$"07 -Z9ݽ뻗ɦ2$#F  aC0$6.YF] l UU*25_s{-kUVeīʗ/~콚g&$9S*?/yde# '|v7EY53>7ú>]Y|C۷6hIw~8bƓ=CCLY#t 391 ԓ³guYrv}Jk&ew@^L)QAa0JYAAHrp;w~'}Ӥ9jBݶ];2|Y5|PUq6\_n''d 16 jIaI!n7[_UGǩ~N}߭׫4,gj1/b_1d237 OY=5OR֔U)acpWU[.wٴnꪪScLR pΜʪr!|NYNٲtZ./sfpPD˹/JOCYq_B2;[j.\XdiR owD$B Da0~$C>4~kky6|6sܙ>8DdLb^x`z9 U( $ "KʼLL ŨG㬈,WãîE@֙v88YHu,$O&m"ʢ;+GWK K׷J**2H"r' %z;j@  PlB PmwV'0J.;Ek> )lCY,/N$s 1ÔڗΙ׷W3|/Vnr@|;rHhmƣt*B"C'{_2vuktST&@!ƶko GA]J~g50"!ΦU32փ$`V[tΊ1ҠWK'bd(b@`Q @-r9ϧQ&B0*lSW >1}w?_M,ė%ZΦ{G A˘N/Ⱥ,011s, 8@$k\ Bj>4H1h^ FH$ +AFDN_-@C]2|?Ȗ&г%P{!R(,i. 3( xxO&c2J(OH`f*r4ƔTkU'Wx2kcRO]4}DcvZVnY9R(^$)A71c wS4;~xfe¸7-Wٻ|ct`l-~AC mU1b@ȒrYW$ih^FH*rQUsBJ뮻ΘlةVL9!,UUU¬4e9*W|_|ݳgBIdVaU]^]^ZE:98>vZ!U hh C }=3dSLD(;U.U$$G,@Q5jYUco^~Z G@$|6۬uSUeU>y5\ţz/1κ|w޹|Շ~>%NE}Y먶u `d^/bym#5`Bd1_Y Y `YB#hT( I,ղāC5K^ňX!)U?_xL/=̔0بytvRucESxR཯FS1)aIH C1!ԃg$wj21ސET' E+EOJg*A9XRDVd 3^__zk-'1;cnͫ'7fyxw]18 ټѺj7kPXB3KcȤju8kbE@P9QUyq^DI8 G(D$ `@(xX>ZgϞ=~oY$%IxF1rY?t6cC7o<ԭ˾l}V[ Ҁcz{xz^5sTS!ݦ&Z5޹lV7 c,؝18?saԑdz n0k Dk9ono.ONʲ.|5}ݷM݌QCdE.KB@ăǏqׇ|qt|g/ʓgo t $ h4)%uv\9a^hgkHA@M}º; tvW͞u"R Zu hAR"Cd.tpvL!)DjU ?>|1jFh\}6_>; sd8F Xol97ul:]V)+ g}xZpy] sBY}]߇ !Ye02n'c/q3R $$Yoj @MF()FN}L"PMLY[ź?.Ãw~+!݆>Ę0@U_g(W/>3 v>9=-ڐE;Df T#9cU5h~I7w)C5"[ecXG(jO5} CQswC*dN9g @׶l:}kdoh8tU)looX.._UUŜGUYK C !d8F|Q]qpZd|ˢ4kk.E8 eYטyOIe!X@]X8C@ă|?܏¹7??LuiۛЈH ]De]e]IcܮvcFccp#sS>!b} - $ۊvyR5r3f(1t !!֖e@p:KUŬ)#HEC D9TZps_̐ƾ, pq<!L}^/@;-"v47tz}1ġ AQXsbgsOUHcx[Zuѷ5BecH >O?>_|t0#-!"GCXS:0[COʲAC)&<גWF@F sH8NNMquPdS*6Ԍ'Ƙ\/p_஄kw8pwۦU 1cTœovuTUEd]%N7۵F콫;"]߅WČkӤv2^-QH{?)[f$2Zh۶(gr*CTMdVY}U UYѧwWd̳/|u1+_8sL }߽z"EUUYn0C0Y*}K!tH,o򄄓f6! 2f>g53cn6!޹Ek]@NS%8)P]gF?w85vZc x<D=u~`cD_f4.¹ڇkt>{m<{:션"K1%2_/P:3 ms7ծz瘴&ֹ{U@o6@I2DIX;x?=ֵa^̗wOew>Re,|w$1z4*RT")vo px||%) {Z8O 9j] ɷJ5UY봖^, `担/\3jdl/aǓ,Y+z Vyu6v,,2GXtSa1"nǣq3׫fi^u]5uH]߇j s3dx<]-f|ߛ  q;d֚$ Hߠ$몮vxuuӇp;oٵmBL!C#,!Dwcb/_+A}OǧhC懣L"b>7cR9a;,TX6 ڪb߅?:Vw܀%aX(h>LXl4dt1 GQ^Grj6pm!|)!$Ão>{g4tPr1_qB>mcJ?Y-L+hvGMɐs~Z>e- ]gO1(ҼHevO(9H˿K_=(ʺEۮn1_8kכ3, "IG!u?uIq80)/rL"!>zXg\帮7mzn'ھ;b x@Ab˗/LLn?!45qGC5" -v{tF]Wj[ c4cУuC\]V/y)h\EOJyp(i'ѵvJQ}6eab>7h(Df||QyVN5ZU1jB6D0"Ɛ%¤ JB#kI&B(˒0pK 5NӫB^c8٬WYӌ"DBz@_@jBg騾:GWn7 KbfQUQjlv>D eUQ st}׆0$2_-Gʪ*wB?R8$b )Rx:aI{4Mt?:y_C.%z1'i|Y\YkիW7Dv{rz拂]Eӧ7w]7;;8);h 7y5Hڲ({E3$:"v a œ(Ϟ$FCHyg?. vn7z6u4#$J5 1Do!{ssˆ0Nauޤ5UY qq~|tj|B@Nkf,z]YpbA1DZ{tzvwsc7؝^.ySX"dZs L Qt.t9F B=b?/#t.__ TkC{AYծ(BfZE9g=0ޓQdsɟVM%E"4ƚkY͇!U,5hp0E' EXLj#0 <0?q+Zۼ$jv\vm7y_h9b1)>_B۾[xCcZ%pX+ !!u6`?G:l]Sdޞ>Ю۶sn<UeAI14De ̼Z*J8Id,Ϧ<(].1(1Aח7ɟb~ !0b =>]d`>ב">f4+9F/NF)m6[#1n saDìIHz\8(,EQ.ff4^EXb6l8FD伏@DrjͽlY`Ydj2FR_uZ c @@NOONW%!' )ݶVS"u#:q:c"4u9 N@SUݧ>킠1"9Gg)`͐dQܟY*o(h7>tE]<:d}?CקpF$LeJb !nr\-]ƾ((w!^6 7G''["c\vYdZaye19@!4&_ yȉp @oCˢFD"z,/ʼn}`zS%8@tlmvA,f $8j{cC1:O1$LJo(T\N$#9\. ž,k4HmJP=~ h\Fgu5ޓVa0OAᢜ '=msUIR/DP˿k~3 ;"(cpdi!iQݴUnQdB|Ӿ09_}C:4r9LW;~YHQ bѨE D^Ġk4P=My6$_m׵m .*XR1iIXR{O~{XۭZ52:'"LIĞS;g?89>^fsDfok\/cE]e}aʎȉ@fاV|yqل)6RN$K,f嫮=}}c0E1HM2](@իtD!x^k.Hlj4shjNQY0h@R߶U 0jOLa5CiXG4ʧnI_wU] "0lMr$j ͡He }{{}5_,b*"3 Efӳ3c짟~ruuݶ[4UU/W Nq6[KMgze]-W= v`$ ֩{/nBׅmFCVoD~O?΁[ iB$$k-Y˪d.8 ZD)б'-Z&"k-ckm3!"+ C67~̿#u.w>ٯW*h\VFuB^-C"xo,N!KPjv}!jwY5 WqQD6F \f\u1zY/>pJQ).cLWڮ1 1l7ۺr  &YJM|t;ﮗ˾@09䉬0hfV]l/ʃx^VEzo\3ꪮGΗ9n!{LFiuPzc3Ld ΂azߔebZw՘Bخ#=  /+jkq9m_UiݹLШ\\^][RJ}6h4M'"4(љ@Gck)̛e$z!iQT1PwO|Uv7]nweDzS@}):6#K 9k|ֵ~7*KINxw]@h̉]ڬ??gt1oCy^sޟϦO(ղmی+c$'$ RVEq<om?1dQw-@{_|m_8Trº{qבZ"*^&K̜BС@k;^s^K$ւDvpw?"BfFc:^/c-xo\H1qT(Eϔ$q2p!ŠdO˶)0K9N uڋ9]NyNr;e:盦MONrR.^Bظh7ۢyޤ9 3'kij qY'kheB5WDL1Yc\AXgy C4֗u}p|6ߋ,  1fȮz6*?EX6o[-!)>YS)H]Wֹ[cf<6$Ŕ# hl]@u5'Ĝ17 c3!Nv0q :3dCsZ$-0Yf<g|Ba)*-cjSQ@|H"1z\.ӻ>Ei)asNu'o\ƓdZBV{_L1jB}d|~~~zv2Tʙ'00p* +C\YVuBh7jE|t7yzzλkͨOx':#R}kMI)FcrmS, X/ƀsEYi9E C%bb-gGIQC44"t~kMCd1ƨw3HFg;G K7>3^?CVdR"ciR,Hqb5"`8~Z|do/BPUUEYpھcThMm7/_ڶOvɇ^{YIHT5"No{BSD#DU^A?@ U%3<+1 Ř ˆ9uނYNOo9p/Er QUYU,Ӈ+d;F8wHbM9{xpxvvN6NON_|Ni:{w??m~\Cs޽6 eJ^t ]:Wf)&rzw;;Rbuxoa6""e{cƼ5ZZ5k%&I'FfsO?GXgPѨNĮzC!1

    $QPxa- b9F&WfC(Κ-8y'~p'ƯDy-GߣD u3k'i8.)g$dw^ַ_#x{{{{gmu/.. ,RҪ$9+hͬ -l>/m >W^|nⅦ2J (ԩFۮp0w_ܹ~jjt:}gɭ۟o]{xxqrtԕKI ?~7^|BwwIڃE>+@Shզru2"" @( %n^"h%w;C\1קu5TQItqVF?G:{J/U;tF׌<3S"U`)Du9HR?_H웕G_N W/j Ow]) YF`aEP`[ +S4 JnQ''ěo#΋Oh RMeg0/F+iS;۷<~sln5JI+V`t͠`O?}/\0=`v]@Ptǣí;_ܹv 7":;::th4a4^(F/h<"`]t:r+O_Z^{ Dp=)%Rvarvp|yO?4BMBF$ \5A0X uf!3C}VX?j>gY$PWlFua0Qg5:simPk JʬsK)bR}b|ÓݝAӘ"2ZYoc3o)i<aG8E"-XWÀjeIJ(7ýG_~pQ.;[\􃡈޺=bo AXZh !2G;yK7`0oW.1"ACgYRD1`go³/nK)fFxMr3 Gg:US*V]{ʕK޵̇YD8YѰ9<9sNYV5拀h@@R(*ݡf"`??< @REEIM)b*a9P *0 T !+fHHfG*ZsP5r@"N/N}."T̉.!nrzzrx&p،m4ABf,JТ2l+EDdk(YsfpWq8s+o޼8bsp"͏wEF+66ŝn_viZ DXߞJYWCӹuŚfppǗ.FM ᄫ+bŬ.-w?g~am8T Dd?0TPlr:OYWTZT<JYYHb:('TnRc65RuAJZxq="`EؗғEBe0jN$U)HI+B*FFaaIE*LBÓBIՠѡvHScՍK1.^]Zf9'$IRsc"fFs:}dzz)M@u`QEny1/p~Ԩ>мIj3T/lмFMtqSw_fp7{âfMJ"ts}z_|_ܹ{˯nm/-7|xL M:88O|協WX[Paڔ<"Q`];?<:d攛v >ЪtHƒã?hߣ$,VHVmYBW*䭃 @3.4U3rC(ow*sJ6vfgomXW$$*aBM.u!Iv26ua"A2 &jaT; 7*AĐ8۾{}>sd'izoy~5B3?iu U{D!YO&/5%3.N w~ʕG] YQDX f8n!l%J1+62 |޵h<~W_ݿnwa oruuysc5U e@BT-a IDATfYW9 )^:y4}$t& ՚{t8$ &z4yȼP "%$Z}nИ ɤ$M\u ;$tє#г 3{jHgeZx,gxuY6Ad>I*b}hbopTau!HςG(O'GG.5Tt|-I2+"¼8t/v:AOlo.co ~'QJ ) ,F j7a+KF76֞z/񫽝WRvB*EϞID+ֶ]וy۪0eM*pG_ nT |nMFD9?:::Y\?s׮_}A4)3sGfF0zfT}TjN$bv q;\\ǹi Hzp64p>ٍ &GE@Jjͽ;>|pzƪ<)K3C}0x] ~JаZ<"B"!$`A$ U6TȂBGUS"m6gN8:] n ((Bz( [ ViYYnp0J{͛[s^ k < Q槺^RfYl~?snݹ{O./yʙRHPEJ׶]m۹yK Pi QaӤPCȣS0v7л@bNQlnollJ3PiQJʗQ#i RTU͛AhF ^\B "D,jl$RTKu(AI|UZ `GU5^_CYY9%9 2o=)ûӜ.@40W-{Q;aYq9/U#9NhAST>U6;9|^zŭ{xismqyQTa:~XyQbޖKIUTE+) ElM#"f#1Rn "ҐfpCgUJk{l޹mA3l|>4ك5hLqFdTA}} L:-ܝ;ߙ&;*YDNѬyX%BI !JT&>#I* 3!5Kl ԛ& +Y:jt =0.4"rd6fGONjeyrJe4^TİSn<~'??5k@oy?>_ b_G[? #lz|?X0AK1m-W'0;fΠQJy5wPDU%zdVr]dQ 54^vp$/XxᙰJqH!b5S G;?\^/,ZW AjPF b:&)[K8<<ɭãC憔ʷIM, tH"\Ry@D+(dH>j2z)\T([x?b Vj' 37:vDs}C5dM(TI$WMRlZl^D)fkH 1F\Y9Mj/۫۟OD}#b ۳JbN63HЦMCbYT /.-.2tD3\޸w=wv7Iss2Bt"7g ЌZjuW"3i#^5V#5M:/^zaqiY̓SmLh3G!93̍@ HD@ӤHp$ A,Ráw^9\=YVވ%܆H ~C+gO3g>XAbi035'M*t|?t7fJ+z  Yqbx&Kd*1D̹ƒn ʑ9 YF<8=a9D C>|++;"sY("6{FeY-,v_X9u:5*eՃ$1HNĞݡy~plvڷss2U ,gPqfeܑbO" a}tYVuv;ml,-..v:%uӦMSLkk5e!h6 Q %aW1 Ox9LUrY@E5*$mXsMLxEXĦiܴny}ýq#Uj`J ݒÉc,ޝuSkj$ ֦!{SDdHŢٖ~ tɾ\ pCr0;w#;~SDSHKL`|p. "g,]r&$0y i Z\_\=C\'`p#F`M5$37{>~}. cN~9`f:!לH HU2t vZ+kk!;L&S !vBLIXRCB1"!%1S N+.M$b"G"R`g:Le,Ibt.̧j.q7@@YD&PUC!ۤs8q.[*ٔWզӺӦ"% 윀9\n&!! |6Dl 33$2AN<'!0`YE 64=J>GP>3,Z$iA`7W;gB4kL$7pU`wo?ˇ_9~o=/cN할Mē{71EI:BqN _&>5*򬼗|:M$Ả= ݵ䘽t:ɜ!"h,Lc#U;rEH8.~݈ dMhT B 8C"Asxf mRM9Ñ3Lٺ-bxN\~ڵ91'_?OomVE`&>:mӧ_җ_~U#R%B(Be_\֔@QH Y] 1׍PLd@D~-XĠ6?pOȁy<\5"L-}5B9=^;!#} ̈B&nv3&Ɍ@Un;9evMwT7(&1, H")U$ ,;Kj4Kbjc$R:+՜3S("H fm\]10YJ0Y 1;Ng\Yu|pxowO"+_|v\69dk?z( vGʙP9BԘ.^z׾#9${Vyj]&w`65UgMD&`*ْY؋c *d!";̎ %0"Rw$MUSms16fB`3HIYZ\Z\Z(C`BrYVEh&[Țf2fV#Mh & $¡BtZt,Ze|0&l4 2%0Ȑ5_?8r%OSvlp٭ 0Lo9`9#W(tV)vY\ Xh ݕ !n޼+WWi`%YUE¡""Bj)BD-%L1Frw5Us'"!w7謶y) %Y ~G+{ZX[]^^,Pƈc UtgnZYx"9ܻG|?<><[ݥ`,UGS^Y_:yVxUqjΝvZ讕YFu=GM4i܌$b+M%^膢˂Å8%Dfn3gM'Sb1ЬTM>S柢lwʧOmn{L BO>yvUs+Ě\cB%5CYӚjZ-) gڑ9O-fhA(rc];FՉ(&\. t]dgwwpoweYrQ>dܽI*5W^tÿ?bi']~|V/\.I[k ӆC$&wNx4cU.Ewdh`Zד&MIJERzmDEBQ44m(n,;I8hlԀ gOp8|:?vߞ易?JAOƓ3Q(*bN>B 33Eo`wnU nJ`.Db Df6`1iVL)w($mB!Dz='"vC(3 KRdFp NN}|ь{?~'y.J~!KoTj432JUs4!0nUV-'ÑM5m4xRG4(*KvF]ioHtШk$fg޶@gy易?z?14 z얭݌ýʫ_X;Izq4iV% {BgV-fTGɓ*UY5LK"H26M 4k+ܜ9kXL]rŁՑmQꃃމ+19VA 5WsM.AԱ3.%4qPxq?]Kq ^ Y &wZlr'r7Ma2;nRVΤSE|/{)%Ɠf:VĀw m,0Dzm/YZ-K8C䵿lLY3pys2Ag"L̡Sa) +x+o}q|Q3:{J,@nF\ -T&|TmFu5s8eվY)R2v[:e5(O,L)7|Fg4EQ~o2w?-+/UUD9MLQ>G-nG[)pJڭt2]:ݮ:|1Yt[fy~iŕ$"X}da͝|:[`diTݭnI]M[^qu&i.X)`{p΢Q!&uS3˵祒s_@BA~\aϽ[o⋗NUxrn{v"UWHv0xԦFUqF!͕g*h 'V:vࢀ{'ʯ&[R"Öy.=/%wOGG:}+l߂J`b~v$?7UFԘPv'?{|Y,E N9?fQ`1i "2UwCJujMJ@AmV}iܯ'6dh%69`'.5U.z{n~x9i(#XK,k_gos2K.`uƭy58%#?wkDbOnt:+W.,-;Ȓ;V(!q06MRmDDiWKL !38b,# SRk%RHM2 Xv 0iM.,1ÃCፏ>6ok ->0A~=x~;﬜>ǂKS.A$@A9uZP(LF ^!RxdH]g!w@]-0;rjKDR`i[eYjS=gprdj8xȟ~z{cc noWt|! 0W?_͏CQoԋWWNOMΦH=evÕճ1\yrw3yr8HTB`"1-85u݌r'p]~OUm7 䨞A]F#"&~շ ̓易?yB(X3H,YJu0xJ9guZ1i4&/]\; %rf5)oŝ@n\U8P' n^ż1w$Q!&%bYc&f!r08W;,){qwWqbv!1C恝DBRCpEL0:QSOG1sq, r&_`<ַoݟLF{{oɓk!'ܼ,ѤиNjm꽿qs¥<y p Np7Zjx:E0u3&I]Lϡ}Ɛ\jDMnhAokG 5865oP~o7>O cN?99HاoQ[>LvSNX]v:avlɥ`VȒyvͽj.=MkRQUY Led?zѣ{箼}E+KҲc˗O-xՕ7^Ooo<݌"VVV._8[ꉕEՌY$x4QQ?m%s) ąrÜ?p7&17gɎj"&M 'Uw S*B9fNY#LfL9/:;> 'PWt:h9d ~lL 0v@$OWNvΜ=w{nmn+gg,bg3 9gT51H M΁%84mvܸ݇ٹgoo|_Lu#c6_xųΜ]"EU o߹ݿ"0VN+n "P&g3Y 0 G>ڮ&Y*[-XX=e*$mr(̈j %1cc;O~kWkׯ]ssf.4krb'fPw0GUtWӧΞ>}Xk3XD 681c}x:&3 0lwuc0-L'lom~oW_>utUgϞQ*fw|lS)2Ƃ8cg8:xc< /wOMFBI,S6 #I8 @) 6'AQvʉ8G<5W0q}աx^0OJmbs1'LN3Ltm.pjQwoߺpSvN@"\77GR,ba0SfCTVr#7mR݈\޹;?mۯJji1(5)35Ϧ =lskrBi,:p Y˗ H ՔD ֦z2Xi:Y^(Jc.KI"4[U,r[P6z{{@4Dko= 1ǜ N4A86^|»]?fq-H` <CC#dp8<0(/HYP B>yrb Z;5L+@nÑ:Pv76L;<:O~rѳ`m&k_ju뺆QL! pswUϰQJ{ZOƭv;K+INDмx?2D;?A*%6 k9易iuə7y/,Byp^\XZ|3x:<&C , ְ p'2fiCXHbc*3 {}XvZU-0IdZR""~{GGij E &eas1=:IR$Y7c')hY*ss2q7 @&gҔnvspj;&4h9d_q, LȇI7w v( 37Onlҋ.PHBĔ7E31Kϴ,Jݐl @,rG6;;;{{oSfcNNUADn8V%UI<ӔO]DRM$$ yVw9;@NHGڌ[TU-j/Ou +u"Y:,f9zav^a?.kSsc9 vigXAB .rghrƭw7>lKcN;?5grrs2bG,N9+WN897K. DiD\U-\y `!r`wQF(0O0L)<}vg{1V'FsZ4v妙CM?{~X۫K'G!I@C果(sCd DhBz4U2ZXZ8sMX@P] n*jBws{;07@sWogU,G=uo.]yj}GN\Z^ W-cF"Rv1033irƓɴ &N1 akkg4mmn޾y] ӭVd000CUwn? 1ǜ ~Ep; RH;_W^^{yiq!]]ݒf[;{d;0rT7uMDfNFZ,;Ν{hx^{ "15yrw5X۷n'#wLC7jtԋHNh*&gٓMv}/h:Y=s~aT!Q3 Bl斈03qP>{q BsqDgҠ`s1'_Qw &v801{34Nxի_W~';/^8s攻{Nw7203q6%#K&BI] qRǏ^|]gϟ[N&G XtRie 1m5au:IX=Jd{iՕLD0H!{r`o`o}ύ9Dk9`9dkܫ'̖ȚAw8W旆a_;trb)̝geL2S`&fAR!BXgpx4:O&xs9UEY&yܿx4YrwWUmVN0"K l\^I kPSR3܌~?fճ/UQ A <9!gatw?9ۜU Qշ7ssi,@kvO<ŗ۝5\~YD$7ZJnZ! L"Tdp&i&[R7>kj+(/. 'A_O~?Lǽf:ѤNpX=qnem]鮝NX'Gz4F{ӱƖ5.5]]Z&FheK&$<4Y@mr/(}0DWz[|>1ǜ ~D qHAǃӲ+k.].n…vC.3gn`b 4/F, ir5 %fBE|=~tqaiY ,%w%G7z;ox5枚S[\;S(u"2eax?d8G4ɚP$Q89Ĕ\gGO}2 rlkooi> 1ǜ ~]~ s g.pSY;~4fKY k""PuSd1C!ߺj\?+Eegamwݿ>>:ܤ)59K(Z3Vμ0 "# Rj_ݥEX(8Lp܍ݍ{;DZOBp['1ǜ _ 'fzܗP,xҕōϝ?cMvIuNdYɁ%"y]usw}ۼ8̐)q,Tb5 ;Hr8PҦԤE"H[hljE8V,Jv/cj(8CJ$q}yozNoHQ4cJ~@3fvS%ksQ&ьOOzLowsɦ-[6mDyuzgrҥٙV:~ĥ 㤞}WUHU0PX5:戌lS*ynي'R[ l7q%iW#lϤBLŹ gǖ+Y] ^Q0xhMIs9+ٿ}ǎ6;?q^]3]. $qĤ4/-|ZM]j4O\pqMr.\eVqLXG=\.$Zeޫz23K@"&,tZ_ꋽl)e"Yi "lq^v%LMM85 iVɓSřկCa>"}-㺶v٨-HOxXnذaR!+ ŒOyֳJYK/+ O̾Rh5}ppRFce"2q\g_>r@my\K}s _lT+t;V زXk:E .&5픃V"I҄WJB,$1xՒUG];Gd _7ny=Y9 K+7n3#pÆo΅ĥ9sccc7lذnbX,%aQGqxrj$/̍A` e+̒-ՊzS'ϸdyalP>W_ƺTl`ĊʶR%FYr`ɖX}J꼦Z̓SA׫ 35G> 3=a]),Ze2708Ճ'N'V~onuk׭AWMR^V&Ν)[.RWWK( BOСCSMUOェb";%BIDAT kl[cذQ=wJD>U*nY Stg0N>CC/VzÄ7īTbi̖ŵb?pӛp s.NV^]S?}[Ç>|#Ƈ (|NLn9f,]74mxbUcck她 ^_U=7LTbKb&9%),JDX(UٖJbN-Og7;1p ޱk" 6MwJ1gύƍZ7^cŋsQ>_޶B!G -gN=Ç K_7FQiB.> 4m$j:U36ޥ.vAD>EX PHp&5(*%C†'/>z&_YZZ[0L]w=w'm^r'lo_z/,ƘNy߮^VuSY!v6wa> Wj1dLQ&첞Y?4"U;jgǖYk·>eax _lz]}0٨V o֭1j+$l"%ejgϽT֓fӷzh[,^0YYSlrcGyv-n̷Zu$bAJv I wfȓhyUYħ.k//a* S27yTu~_uU^0`d2,RT!mn6V7mڼ{MB>j 4_ƭ^o P\6m DF}bI$0iplޚ:gKԷaUUsŞ|_rT2nV8+5;2tzu^?ՅgWb7pL 02xŰ]gQ\X7w88 LhSrNU}%uڨggg<(*+DTY(%qlj6ys'Rԧ#6r/tZlbO6LfVfVrYWlcPgX6FKsO<"ý% ʁt}Z;v8OيNEȳRD 뮿!8qs,ƈ.W '={vM5;b#+ >&NY: a.5WRR'i@㧖f3aW^UBYU:67 [ng>O$^9Ug#1HeeM-cr65I*,A>cǚVuj~>e2Tw]$aT Y6{rxLN2LHU'c[Յ٫Άux%za?{c$HхgoXYO4;sYbJLA(bm ^Yg 8wCV(iΩ0̕UL3XX]KkȘ P;ةgf&C}SUUVyT2y^i#2/kdP1`lZkiΜD/x1y abVOޓJDa`1sKi9z Z=FA>QWQ/zmXٚBo*& $0D={UR]r98 ٩,uʆv՜]##b{([d K`-ۤqܳfa!!!2–٘N8U;ZL* 8|h(Ikyڥ.Mއ rbžB9O""$5M5Mv>0k7Xmi`H{*F9G'+VH ȗիԅ%^ 3&&6EىgZƅFOr9Uuޗ=S9dB+lH)M)4)! x'"mTO-/Lz kgu`׮5?>YY[ku =v˅FUFIHl'Q;5Դ$)M]SU%bS֔W_O^T*e\ꝰ0̆[3'jma^WH9r0?{2ݴa-7Ʊ"dž02D̵z{OΈ4IwޫagЄy5\1W"RL$*Jꈅ3\mazyaz%./ \޵sOp`DX~;nJLb AEF6A>b:CdDyfǿx܉j>ީjzmT^!_o\(yIbLD"BDy?Y_s#ĄA?E1Vom[sȐ䘌g!4NzҔ׾fs>,BYɆݫ>qdՊ(NbRU"/͞?Y_WԊ0q uX y۶j6'Yw!R&f,^}VtrǃJ=F=I*$=Td$|2BD,T27 'kKYɬ)AF8?^GUvmy^ITaVj SbkO)wFWD)GŁu6I"^rcDHũOg_1vܰvFh.:O'}vl)i3q$NI0s'jR-Ay0TƊg'ٙ,J ɫF'+〝 Y[?U$FDXIY'K/FxZ甌sK$NJF'u:U6*s3'땹}E9G502I_ >N VĐze6"Y2*2]}iɥi='J>%LԨMj}i=\Α]Fޱk'2?1z{xeア&8zGNWj=Ɔ*eV䔅 zߨNKj32|ZYع+k,eax {L}}/_f1,L9I%YC3]~!u;I{r.,Z_!_[^UsC#k̡oyyOFZ#YI=YkML>wSRԏK󓧏-d=f:F=w}wZ+b03)'C'&ƏN4M+/[۲0ݻqZӓUz]hQeլټSR?ubb{w.Y=?v:ӫKF\t\PYZ"·AZu]w>ē뮿~hhhzz[w3LϟysǗfYuRa^՗ᡉWvk8 Zo/tM &:sſyKcU3@Yz,S.q޾uk͝;H[cǟOƄ(=ҁolڲmj~javr^Kޞf׿JD$m~݄{׮?tߟ>&*rV߲i/t곁@̘twu[;vr̆A`DȫH«iةt 7nTUկV"QUy&/]AƍD\1߼}c3apgl=?-vuq40A`#LΥfuq&W9sY ΗfUɄ&nǬ?U,w{>iӍ^uM.Cahس0f4qo UF#ϯY3u-ș21i__*{/! apy\V 06m鋂hq~c:o6Z  Eڵk|!mY \IYKb*wu%>n k'>=fmwwk =ݽ}&0,BbT}V=rdhh0u1i0'FvLDb|yHU&خ|.y{>$ 9?uOYc[\XkRk|||xx[n=z(]iRsUUaPܖ͛?я>78O{l\T,JB1_ٺy5kNgؾ[oE\.%bĥJtG5%=cv;nў={ʞݻw~(Z}}_x۷?%"f B.IRffFYo6o3Q6\_,H_ǵG&z__~XR`i@?$i68>,LBU.{~>$k2 җѨ]$rB@ܪ~/,jRwvŋ3sbT{zIixh|Szթ!}ϟ8Ѩfϻ$I4 [=?qBb4M[q|ĉqb"ޞ3?k/o߁$xO>կ_U )|XHr]499jUV*K2ŅE}O&{˭fT*Ji8c5M]R)H}2;p^awODǎ9v[;,a`'"fU*]xT*߿L{'Fwzs'?DJT>sӟtm֊f9OI;i'I ߿rDbVl/O>+ ;y{o~ëk4Fh4ӿ7o~UsB̌k5 FFv;/p"3iq;w?ݻ/G3h0guӒؓ~4ԥNQtg?_Yln|?p2///++n$jٹ]y. ''JK.ݻw{5cV(1^fk_- ={liG8p@Zr^V!A].ABMs d'JrِW^jV$}^Nbd2u!Gˆip s 3cur\lT!d2--;CEExLLX,(Fy!S47?@8 a*p hGfE3"UTTu l[Slˁ y.gOW.6JHP27엝eñcbbb\nRIJ^Y,_[tuXh:YVZU5juL{e3C$CAJ$83"jS\&nٜ)EZEY)7^! }%k[,2S-i5r65cP҆u@{gzd6te86Aɲ@Jq~pbDg駯`Fad6[weepNFQ ӮP,BqXI,l B3/]F_`~?T8oܘP^qf?kp0/Q(+dV* z/y1c>t,gϞO.j]#Ec AI4}fQ3f. Z20pI^޷ ͞5>n㨰A6 ?bnV9wQ'C%!vsNhjpxi&HarxwfD>޺~zѣ:PѧOns`Er\rA>~j GT2 /aP#~tԥI ?N-:\??u)ch7[ c-ou]IS~ÇWWWZڪhB[TIzܹsvZXx/(,NC }/64`P76v dX=&Xp&lݢ$r\uF3 BWb,u CW{.ܮRr!N]󥂪|`mH$,F}U3Gc q4Zq*f?[j/ﳴTQe(o0f`TZz#蘢J Rtm5vUn$ݒ#i!Ics3v$ YkY8iOOly@2.ʰq#|eTJ LOZXXݞJ<(i%!tuuM B,AH⅛ +H,@䯧-~pA½en[ݛ%#ME[6̦fEEF㳩{ ҇\D⃞F4NOO."o_Vz$`}yH@6 S=SRb'ueŢ}02200cym߷}Ǻ Kvlp ]o> 6&oz0~ ,{yђ8[(| ֮Xl0Hw'NbV4#GqU_ (,%TR cJ+C8Xl(F5 L`h-\̌]N__]f:pEAg6!W:/+]0 !5? 2~߄Y'%aKsXWWr)`Q&Kr4BĉqQ<]QyW7/V(Uj A3i8Zmla&=KEF\囚p@W).n'2}Y.'?8t`EeMJ '80\j1O!}x\e{vm]hYuUc?9&虷>\WWCeG؎&B[PIAJAEB |.oor7l/ ,6¹ F@]&N0J;7PL+ &A[vQ$"q1Zt={ 0M0%a Xh )F=8]E"{0٘`di,kO+36nvii:돭ȚiJzOmֈ  X3v@)&MD'BZd28R=}x j-v&:2jߜ֐ 6Zǐ0+|dGg-zhmuPn(?ĊTdL%5aهb8Orޛ{Sm9r$(9)"F9Ξ%NC-eT l"a`OXuΝ־c'W]DC" /<*UA髶tts놆[|e˖Խ{L4) ˙d44Ī/EHYM-oJ` "67mw\yBQ '<~c͝im7o^iC MP/`'SFR[TO|Xn}\}~=$5EF-]-ȼ`3SM 3w8Dhڶ=+]zYYyʇ[Oi1Lkϻv<| kxzB"veIQ7-3e9It{bbm|fW#jqGՀ# 5':;=ؿuv444 S)7B8i0'7ߪZTD\n@텫n}'p6%2."̌eyݲUgXY2$3ḥUUⰹʎOܜ O^QQ#Դwq˗rˬ';--. l?Ycnn^~oO)Ah{0vf' Y]uH>~N]" 3# XhdD؀[s#ob;.؃T^Y2ajd:7n߿WUO322\"Ih/:m]%p)6գ4E RW(orPYt5W$%~NV^I NsloOX,޽1IgхV:O$!_Ǽ7Rر-^PX,^<߆/# ad%F"Q@RD,@EP"YBn?=gݟ]5u+͙9===={Osn,p{uR6u-@Etn! #97x ὿fJz& <`_*:}>T,PЕq`zʧιy `20Ra*8'巎5:ru\B>`~muRUC:B!L~"Y*ι@0T Yl/_G8dY*ιQ(#+sJr?휛U0uРιYQ$O`+[?y7+a4u΍^x]{yE?yJms)/Gʢǁ_[3%ι Sk _W06:#!CH]rΚP`p 0VaFf`ID}-xp ;l^^ksJѽ =fx̍Yż 8@=4JB 4qӏȻ'OTWݢv?ox&YձC7ÿZ@?98Eۻ#ekg?e=;O3'k.i%W[X? E)ahC lUҿ}bC>,2@C;7t``S6nP5u}4CWPqmy,0y,C{p6m (L Zis30vz@&G} m(rK[BF |4}S mQ#̲,n @ [qyio~ƶэse ͅ쌄guMm^ׇȣEѪy'<6'*=j8E/M梯e0+BE§G`&Eg[K4z1 gWxm}xĢgɥ6WgJI:G +c `h^m1<3[K(W(bYx8CeR˿sF$+@cgP/mh<Cfexwuԝs#Z`[$s' kdxN} |=w|kEc)<хf*|o<^`` _p^[b-ۊ~C/(❱ZB~$^M=Ϡ.14~W n} F0MsޗhK7 *lQwxBm TV!w*>ҞP/4wB+_ }xV`f~GQ7>[8 8+[ ۭ׍ q"X"T77[ 〮Q*'n"k0ZnمJNL 5Q=Q9BaP)ԒGgmh퍰dRyz(\JJw ]A}ޘ{'YnBW6KeJιKAyP0M{{b0m y6- UEh8ݯ4ݐHCnnm;ޯV)>+#iT|5~ p8//QtsKkj߅44B{6?^9wsnXJ[GB^_ Б@<qH*ιɑtR\=JuMCѬt(psnQ^Y o"$rHŒ"I3(}ʍh1os\i;L^RߡTsnxl z`z+ɗUy 8  хxH]3`]ũ] ;EGxQZƔ5th tiScAI6鑀 (0~+;8JO7KsT/lmD|LZYej%xsns"ȱ l{B;ɟ{g6 y{Cӎ[hsNeISPZ>uOO3 3~ A|S\0@n%gޣTt+WjFāox=^'N)ȉ (Ҙ\0?:u+$ #9$#E$~mwNE?\[ rl&@ch)rXR_OqG!_^!Ci/M9mi>ݛ|tpʋG9^pVI$ S 8 31G!Ɓeg&/[#,SQ-ARr%,܀:1Yʘ.{_6iH$A 0O*hѮ5{@nf@s(a>oނXHadHͼNߣLNɝZ+sX.b V2 #g'b03w¹J96 goSx_2kddCȔN LιnRj6bo0$F@v/ yjdiiSn"5te-;@=J+6@cL/!Ҳ00ӹm hLшY]r(n0P;-v\d wSF;-j:J>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~]} pHYs."."ݒtEXtSoftwarePaint.NET v3.36%cIDATx^]yN{],J"6T(*$*i-Ҧ~iE*Q$$QՒHl}{,w?>33<<,">y.:v9ǹoڼ>4i\W߫[O<3~ަ٭mn%`%`%`%P%gإgֽK'oɷ\_'nIJd7: kwĻnÇӺF݆Uµ JzkBg>%Xة)A1E_~Ln_YV OU^d<؎^ׇCPA/-~ӳ>_ k34Mo!ŲVESs{VVVV@;߿P˽:rė@,}gՈʘ#Aߌ uG,T|>_mѿ W5]+47}ZY^"`nڵ?;nN?_rӝ<ԄoH0Ǚo>8on'߾/4DnZ  ,&z8/Bf3rSL@H|ts.eubfɥUB(.!nE]=hc Eэzĝ\NtϳӖ`ztM7͸̶u21BlV/υ??{V7p/2dJ>' RFKqA`/sf`` }9`2gsOEtPͺ>)X;DJ`1HQcO*'cgFk-!_׶SZv }J )Щ2nT[AӰݿY0I }ҰBv 'OlUlO,:d=k[tmLVeB}bhrU'A[B Y7F%0W+޶Uw }a$R{dEj3D#K;S'R>s5P1 }2b,L`>̾>24 Y֔4]|S,xCIFCHE(GFG>_6A:$0{?t;hI(~@I̩MCM̱@.QlЧtBBj>JEShs7|KXUej0- }2b!&#~d'O5j!9;:SjOi>ne'd {2H[c",ɀuY϶CMD .@,)Ї41P?LumiO l7#~a5Sّ#c<^oS'0BR }H O\c=avX -J;/>OsX賩} 8>W!IB }H3z Tp)>$;Ojś] }"XʈdώjJR) ?k/H }J5!U2K\D|z3NayW('!!73S`]w2 x(Niڗ }J7!RUӝ }RVvĴJ`Lȭ*ϩY蓆U8 >Ô?@EL6\m~KV*ʗzʠh-Ij!'nv{yyZ蓆E7}~X9~p[/k)VBFa &O|KX"^KSb$pMjQY2} J QȩF>%>iP#3C`CNϚ=Bw^i˗EX#b ;8hQZ$[{ɑCoQB4sI&6&t@òy,XHSvpАY$[{ɑ4OZAZB43%* -]m(+d~d|< }%f>$k`Od;d$ BOMB4,.y :eVYVkmiCYڟuw=/Oz)[2o#+ȫOt('mh,Q[sRGv>ҳV4[eEmrY&8}X d>W&d-ɐ7}Uy -Ҳsnm`>W3d_X֮Ƣ2d%2 ܡz iҕՂ^)[碁 @ 4A^:TM,񒠵rkLcc%B OJ&eO Wp(g< .k R'R[Gz^ﴳ>xiþCWf+I }G8vX蓺NdtR;\*>^W_+ŭhGd8 [T|H-u^+"eZӰCR-ǡJ EmQ9qgݏnVv}1ԚJ >ؔ,K ~gx XSHS3c_'YMgۖ+TJmj*݇BT.X-PisEܬlt&kPEn:,Cb͚z%͞>f[:HCz{D.i3OaMq&mplӏ?xƀj:j?}FR>ԙ~cdSGBsb#PmnS&T~rrϺ_ΚOXPmYpi?VF7Ս~;XΜ?eR} DU]GS>{iٸ6T $0k5ZfͪѴ%M7ԦqGX;u˰ѰwLbvl{бM`޻ﲮ͸89uZtjԻ/UGI "/8lJz~s6:>P&ydS3jۤj{_/ZQ@-3nqp9T? }߈K\ޡ}isi\Asnyn>i) ar:yJs[_ JlO>ǶϞ ;B ŏmx |=SuB(F꼰o^w05xc'U(}v|r׉5}WEp{Ih@-a}A!ylO~ثeUrx3U}1a?R\JvR #> [#X[} ֞d2ST4iC1KmXh00XI^e.*A/u _#M?^c@WF%)s7X{ 6m/Mh2o!PEa>T#`5iM툑+C+ ? 9iE.X } !`#K d$CAs@Y u{WׇÌoP"݄ }WyMpis7>_mPgUmqBInyObi A:B6E-}bp]!ؠ pq+teq9!vEW!yF7b"'EUI}dYݦ@;H* `6ZvBbWt/#gJpa`}$,XdB|Ϋp>㔿lZj}auP~CO2cUjA+.obP Q8C]έr| O5j=~oq Qd͟|dӐCLTrف]"8BNgKV6cY>3'G6˵ǡ=Q'ȣ3ʍ˙f2mH }a{9FV:+ܠZݏ _]t& 89 %൜LB }^ b=%eDOznK Ktf!׶i)JV%zڍpݮZ1}^ӠSuj}hHRHPކW){SC%WgB?1x80i}(}$@7h؜HN }J0A~=]5-  }'R pibg̟xC'G }Fĺ| 4T#5]_i0q‰#1nZ"PXw 4bx/0EKZ"M9,YO+ɽMFżL<^89yQdB }0LԕE΋h/9k˹?tĠO"=)>t Y\B4] q\rސ5k[/8BAV¸b j] >*,5U}pb ;C5O`ivw6}RpC$(h+\B }.4Og===>+'n?t:bЧ7TI^gy?9VAyMPq7ql~Ǡ9p}>h"TI ڡtyˠ7>pDT|&;RZqȊ=)ށq^Lz1#[Pa4}fIǰ yAq0s9l;)V}*^ƭ0?OVj|"3E_>JzHM}0/酞w3ǔPzW'T^R h2 &+OU7 Klp?}$YR}ȳbn h[<o7AqZpsf.> nDI^VBV%s?!~wlGHCD痐Y }m>;k,)B{T>3E ^ OR6U$єzѝ9B t*SG  ފu֛vflA,gQL6[4`NOFe 4DZS]|O.**74}t4gO:I>N>2364 EdW }7Qg$nIk#snN|T>QI~jaBunO!%f3}&x 'O7GLG Bw"A_ۤ}Vϸw' }%(!k-ԟDU,Y$_ TrIXX~#O4B2[s{PQ1:{2GD`j(GBwYeJ9х>\6+ՠ,E}wusX6I>X.f+ +b l^?'@zJ*fLAG^ryErT𭆕c*!^4 G*D+0K܉3 ,܄ BF:eAԳVkV:Ie游j+ P6pgD|y!S}ơ:=hH>y >RFpcr0ǠSw*PWZ%(}Kv{Rc'm\B.B }Zt/.}C%qmD}RyȶAj`"ހj>tA,ѭø.9VPPqBl }N }< )B_sCa>F IQ C(~sS}rI6zXdDm "'X7A[#Vc3]\,kLUr79s6Gp1pĤ.!K T 6!+tlҠ7ㄭe:;9!Q>K"JΊ?rBCJD`Z$#Jwwǘ|[b}jv3hd(1zytPd0<"mIs l4~b_==]Rk* Yis〿~"S)LsL&މ@Ax7XHppV+r;XB']@hC,)!HäU'C]!n9&n87%M5iɝӖҤ46pL1ew> 'F ı^W92j6Gp!:}+}dXr9胥4}t }IJ$dsHN?0I8yLrTR~ϮZuw_Ujֈ FxQ͒ >;b*ZE2>Vd0jcU&~l:pH<&NP*M@d.4T^ծUB@^X$ϓ0t6׏{{01<ޔgy~x=_&9qnV/TDgl| derFA=@Z"va3a]Vf9U*OwdBeJ|τ8y2ŵsv MXkmeB\eI{Fd/ @qloNDPNߣ_" &Kv7}Z#Po䕛 mde(XrdЧ`f$Cry។p[7!7}* Lƞ6f@`>OdHCYh\7V]ذn7ANx:]޻*!0Y+ hWa@`=\R46cAQeG:b=Ζ+0l5s 8+oshk⨢[Ыࢡ@NTwFŧz2Yt2֭9_յ# Ն> c+Ȋ/_FC  ,9l+/Ҝ4 2L@ ޤ4"s>cyi>/'#;ہPDt[g}rc=T8أ^+04 }j2YlsEf)vr7&m/%1 6_ Jr~`{mC>b }J2o/:?WY,+_ `&@ A911? ]WЧO)ACoeR#X>E(wi@dj?YHC.Ps{VڿyWAsw<%-`VT~\K@@JՇ{_D1Zm k %)XdRA[&Y#Rej CꨠOWĝ/߯@*k-‚^՘g$aFlf@nO^֡j8 ?Ր$W4#9 .'K4wW Y#$+eq[=B)B7鵡XZ}J?d; wר, cMGE]4VkfdBbբ+RHuSn9t5Em[mR=UlmSÛ\W:Z|1.#(CuiH&z]8:řն_R![B }(>%)̬{[P$X!~BG} @|epH>=}A՛ @&J{Kxѻe3\j4 }WW&N9/%c̐ԘO8;q9XSITߺ 0}?xM>%`E^ߛ! eiBM@&nAMd ZY xB@SVG"6Y=u6\phX&.rPȟOSWHf8@V_Gwônp0X!"#ŝ`nH3V!Oשb/$>%`~_*C4RnTsXAFY4)ƅm e$gCSXZdA*ʀB֐q28!ѡ1?&ceͅ(84TjҮp# \RA]\ZG6(t:%yL@ה0nE} $f{P\>lʼn}vl Zv'C"+4JK S:R@#þ>=wA%91՞i!7zaM7zU,.bk }g H }J* ƎJLn|!Lйv @e &I)*jT #>@aba{ }Y(*5 ނM^fƌS|Ό:N}T]@q롑D2݅dAlHPoU/l 0 Mw2EJ[C%. }@>BF&oe/A }J&Y$3Ăx&)Fd,X Xxw8@xWȲB6<6hp‡ZXK}lb^l}Gu(Qz}+쐁Ї\ y "'1o*4kI3tlΘà Op[>BMoW?1ABigh8ogSz$g=>$#1FB?uB2v"4X !j${"ߓ+Ђp >pw20e5aߦleY&ubYb@N/zcTbB>RQ0ھ> rGp[‰RHCee4&URMbArO'P*Z_kP[&ml9P{# 9e/\(R xpoi()/-xR61Ty,3}wfJ6a kP#a(b8 YKBtOĀ #D }^'8\@YV5muSlbJ6d6}*02{14oGࣱs֜5"g!30CaNXSD>t 6X{Y0 &{˰L'ױfc׾)T/n΂MNF }X؂X͒L[AL%m xxQ|_Q@]ءŠ'܄P< (n 0!Xcc7H؉ PЇȝ,{0bw P,w=\ 3Y3@/6&?.]胩 ^0˗Â6a,zi0 rt>܆ @1ܥ)m5coO%”b_Ԅ>lZୱc*qt`PNt;];sX'2Պ71˴Ǹv5R$[pf(nׅ>L@E{ ʃ6 c>15|[WSP/'>` }AMDxa9*d0E4XHMtyxs[ Fe@ûbs{ZetW>]buB&ek Zg87><ԈSk }ٜ)&>I90Odс>l>;GQX}`ѲK(amX tRSG>̬AgV-#RI 0nl@Vp5uAy-ÅFĭ}Ƽ>kP z7hA6Ƴ><*> 7g& ҂>EO87S }8C$!\ ۽3YcvW>YJ%a'OSX}  ty[-mVe }yk@\LkAAdlhMD)>Cn vhN Z7dPSB/ ,ЧH,u]&{ ea!Ƹ0}ZV6b4nOb }:(a+A\lVnV` 逩YB:hY}|Ũ}|e^K]탗<䖪WK4}@7m-ûKx\< WX} tB0Gi `5|7wuҵltB$K3Y-8;`> hTVgsyM|}fCD 6WOFrZ' I'O})Gx1`jA2Ї.6R"9UzŖG c^.[ ᥪH$^PQnNFw-ۥ[}ryZЇ*C:fPCI߇ }6}yS6Ns6[-ӂVF\ X裮pڣA' }&wQ_8P^NB_5,:T\'e3YWF^Lb{3kL ڍ \ԟ>vftbB)@fh08 m#j`ݜm瑩ۦMbFOx|(n( }sŁGk>N곇[ ZhHXƇ- #裵S)mOMOsĤ2p%@֚0Y6ҷ^K],qɪlq HM)3P̠aO)>lRb)6vT?(F\ZOY}N^H샗±G}K~)>Os?F>U> R }~b)Vo19R2'̿`^g;\i>{tB }4ӼUmJ^|}.ӷpI)_SpH34Ӕ6s_9}tB }LBB^h6];!_{1{TwgC!Bh*9} \ZGx) ZNB },C㲷0,VR}_~mR}hjRX&B * }b³Mh}}4Ӵ4sB@Vn_-Hi>~PMRjT{>jԠBGc/M<, }^')nr^6`zPt6t7TH^(1 }eK`>"y^~ׅ>XڂXsVZCMX*TU-Q>X裵_k p]sqB'L:IKzZ"=- },Gk 4~D~Y\EGmA,NjRLJGAvz$Xcn]vX\$B'2SX0UE#B`iRXc>ȻҢׅ>AՒboڂXSxVЧHe>OE}(+8_vYQ|d}}m#A떀&Z} I׻ },'xx,o7C0-C1SrYQ|pYڂXKro4SP }VXHםc6aR6k}s]O6 }BX Yp ޤ2_'c>q([ɋ'օ>4kQ["kai۵& [th>jAe>o &EO`"ƻZ}jB Ǻ9+>6}DQt~dKEǀ\B }}>wQj$/XԱ%P欢͖xFX蓐J$9|}?ꔤt$@FH,Aek=^j+d>m𼩚Yc#"e(( xwD}.WAz\/@~V:Kq=,8/Q'gte N8Y#Ll9r^'4#dZɋ%ք>GckQ[ }A}5yQ"tkDZ>pm賊WՆ>Շ]xM2RYE$Z(9B<:4pBXS 'ֆ>WՆ>է4Y}@2 }NPǬGmS&B)U|!N>pQ[m賚ؓCnOi>*:]\'䵙!l.8 U[g>˹c!3zk],>-P#`?(Чp*FkHE=ц>`7`m> @!^BBG izIAGel곯J4] UE^]a7p15(vNWЧR&kC,hCp_@l#Lmmk%Ar:Z裦y*&ЇY.V }G7OmÛIjJLVRe9Ѱ},Q4 Q`>[㯤ka/ӄ:V0OU4_^OAYІ>ZSs>x)}.QBBm=TM2 }b(^׆>y}j,Ն>S,)U;vpGxЀ2,Q;- ԦIPDRXSO ]TD }6=G>`}}U)Qy}pH'P&*[裦kPMЧCj "DYжLVRe0ޚm>7'} e>&Y+u03-)Ч6VeA|hO>wBZ}T }RB5}.MC5$j=4N|>E]lO>( g> i" }T }QB5}KN z8]T,)ЧGyʂ6eO>#! 0y-'XIJmB>C qϟ6a^\[(nMx IG!O`y:CkFx ܖS6 dw]%r?måv8Z`S蔆K@G#U4+St>P6X3Y|I "@(em|]}l5(^贇Q ^#huOypeA|i>aX.,q p /J5GE|J4C*{ `mB }5JgH96 }TaЧ &^¥;eAj>Z}b$QXSOR}(j=Zrg9FgЧpވ }~BO{B }Qܜխ>i)xc;C/àO'MB¥uxц>k,S_BL>[wZS>KPgX_Givs vWsҽ eABOh>}exc Acp;y0ٞ94s!O nkFx.akg#y}U)YK8Y,OM<;|}&/׼>;yЧ[d`Fr8Ԟ9Ч/ƹI:@̨H'k>md$Ӡ&C@Æ>I>3!P{"s9&ρ}Ƹ|¥U [W]͙XFgsg8 -3Mlδ6-s?XB)Xl>%PlVܫ86秀o2?=9 sZЧ*;m0)`AK0 gdAy_@UKĬ>x%)N+AԂ>G}*cЂ>WAIi g5_Zg\Ч:6-su ϋ|o } }:',&n=|"|Gt_\7:fjAS+K\LQƆW'p"[$y 'ukAg#> hAR}^}`i> tsLzguG &E-Ԟpy1Z*s->cCkAơ^ſf/(S qv,V4[}7~iA#> hAS}^} OslI}L1)BP{R3sbkAici,s, gJbmﰵ#"\=]Έ(TjϠR 8q:ssVpt-厠}QC~ϭ)>Au}Ч{ҠC٠v"TC:!B P{R-sX A p%'`Bӂ>g>9!\ l)LJB 4"n/ }|>`yMЧ%G? >'@xA7=%$q^\t>x\ׇGZ 0h<${=!Y݄ut{-lW99BZ}\SP-Nӧ}Kkl=϶ >`g{Ҡ;SUotB>`\̱a> hA)d!6BzBw:.( \Xoc>j$ė7L ̊۵)>˱Ђ>9| >g &n-S^ TP ͒)rFi:>/CY}a?Z \#9$@pg(xB&jp6%Q| },>G`C([W@0S-}>@@k#̽lдB0>oC{KEO@&Ďw> G)X}S &,7؞pyOZJܖ][c'y@R <47Vl#ts }F;?!"H0lAю`;>icUp (ys^ܪX_@>w#͹"#^# CpN Es< >FZ>>OeV* /I /pE#Ebca^ "Z}24uHyՅ~ZXr42;Oe͋l qێNLmk :a,qI2l6F# (- a7#01rENfGݯ3f`,9PNAn}(!4S+’}4whgrV eD=`+ ׇX *_*9 y&x;HL”?d+R}&T)ՓkVV?o*zdPlՒ*({q،4'DNOVٍ0RY-Cv``l.c1;h 's~$b(\>4<N͙pDHm^͹/2L1_.w0"|{p-(TU9VxOJ ce"+вО`18KZJtw$uಔY&d~ɉ:Y/T 4P pDHu^{t1ECruZlQ6 Hh,0 9R\WR:>#2sr7siBnZ[!7 K.!=s92ux;@؏k{H BZs!Bc!:(+/~Yl 1ZȂQ%2_ޡT#>T qq xE>C>ں+1,i^U^܈7KG !riyHeO+鯺q:.FBF{ 'a<ZEм y>!1r}rDvhy8m :qiwhk9#Q!P43맼MG(:S j "B l(v!2OXK3&skFJ) 䊋[Y [Z}:|r2h am8~ԪN% d9usD)y-Q89W{*Cqg.@OuNOV0M9!{@ Od\ a[~558Vv]`_5(VTl4W I˥Պ;^"Erp'H؊3 |!swSmynbV"* u*UjM$R8t|4܄ECDE4riR8# -̋J3r@$are[" !#YaGva>X*< 0qsXX*r)akr>j"M9t.fu{de0YAV吙;x:W".y;G;c7VLeg%v4{I&d+@Q߸lN9H Cl~\(~!e?Q51!Ke*eO /HI!ozVE٥PϽ6. feFr(0mrjX%n";5C.||dZ.cAqAe28n3 .!we݋%ěL@Z#nb hf O(æN ʵs:u,+@cM !%Bf1_#<+)i;(E#97b=aބK@5>D$[[\(vJ7bgxЇ p7HQX}H21 ҝ8`fT3 h?Vpˆfd| uC)VM1h爑Oђ^胁PUU  ]b\ 4']^$<svmOHdNՇsӱs8,O聵><.F3ɾ— a,d8zU>ؙżyZ64[}aBrָWrŖu]3,b_DCpɽ{$U!;HT)E2#mbӋ L[MHiC>#il>6VJ'ֲ2M¡<|3a݋وd>:(ɿ%j"FJpdv$Y.ٙN4V վvS-{MV'' $fBT}Sk%&e|5p^r+$!߾~0=6 ێACЇBq#X&Vȇ9=zh\ -ޙ'I ,f$%‹)x*\0_-PX}vAyb~[Tו'ygD}dA/b*4C9uE[)~HBRFǧ!4"\0W=!L 1 : 1 &0dlnAxtٶhZ BCHGxed6F>krIH*g򡸽#>Pa(RhXbtC0K*)#)MRh(1K *եR]L')w"#ek$G!Scǡ Z>YZ,a_lxX[cӷAU+wL:i'1+[!= =>L-$gcTg"XRylGڇ K8qB>BI\z< ( GZq=b֚Nh3fe! ퟮ ӷQc* iVC"#N{O\`q2%HjD\X.XObZc?S2^tE]cHf>=&8˻MZvuE}V&ILaWp1X|"gОؚtTǜ3 @ʪܣ1q|P@i^Yn8QkmI{X^Hs k̮\ơENaL3Xv4~ 8X}(V("ҶcB_[vuE}(3 tX'µI;y-dTqO ֈVE7BxM 1h&y }򚆚h\rFrg2:#ƻӆvgK' 灸S倐B }|O.JA?H0YJHi>HZ"d Z.]NFHS{. ,BD б!5i:am/ 3@BЇ"iY(eӋ9+t@F;-Q:`"(m!Fj#!}?S@Rjd,GLTZ8Ї妰K{$H* \Rߝyg;z<<ȑď!<2~Ė@Z[\x 5{~f|$G+^hqJXCm ]$aLLAQ9rUtDenC }րм+zh! # ǡ_9A)".KvE儙P̮hy. F*ZF@L] *2@ɓ }XX*Cr#w9ƠꮮL=VAՄ4KzXЁT}HCiG,M\18@C<0%48j!ҿk剉'N6M6qIK><ʅP=A,2:j7ugCF~i rBơ}h!UL,z,44.!@>t%\d3F. L?_Fj}┳[3pB2bOl11*?hvFTm-D }, >Fːn>C7 @E,L'ҨL!dՇ F(X_uYh(lM3(ߝ.E>C?YLv<RUX&ђh1Q>,''`Wn.9C?3oEЇV/J gt{(vȚl<ʻϒq3lҧBkT&itv6hL͂'.GGXRXr e>Ї"]>.)xYx\">#n:+2y`ئBYl@E*k>Y .2uxUyAJ? A_6+8_\9>C-!Ї.IJ[9 n-zzgԙ(z3CwE|  DukGVȏ4w=yb̀>u]ޜPtX/ױ"ݙʊ.gYBCg#U ~iC`Y.)x9(>/^̀>tAL\&w*)FTGA07 XQdTѕTu|gD- ~_Mr>>.α-GXFnЇ.5~7GEeg0!Ї~[CP':/K|U7F}e=FCw G ^wh\v;xJeIkxj 1uT>7XíG$_N:s<>tdPC:0L[L>tY}/W\ˆ;<(W|CwtU*Yun⢭_OEf*t0"_d9J W<0IXՇҩf3`]mEf}E},H8K@I=77Mơ]-hH jJrF@9^ ~|j{Rd'Z,ݕ4`kP{`@a (=)O//x׎Yns{jY N,GU𬧛\ЇJ,3HehrAЇ.;̴Jk{+4T2i_!L>3o%:~#+HB.rQ)lx1Źb>/d~DHz"vOB }/%C&! YwO ;o0çJcT3 P3ʡ;Poo@2R-_6@ՇN3SCfF2Yg¥ 1PU͗ 0JSGl },p=$8p^"gK @J)d(Czc*g&n宫: 2/gtF }0KL]U2W7?TuB6_k"~{IB }C8olM"8>_Ӹ[U*C黺U-ձ0C@lo^ɺÓ ?]iLVfL>thYVcPM lGϳ8+,XYoGr_ZE^sQ0\{&0 },S#5ڢ >~[fpUP|~>4Q۸+JiyptxK iIJ$Jg&f`>QN=ٟ. xv?>mX=;~8ܗa&rTrϻmm>( B&~R(>]M?/BJ&w yh8Q+)A!ʨՊ('{;lf/+[XòX|VIx 6s4{ۈxrJ gC?zՐf? ,~AB }pC!`7qT\6Go]d }(u,r}1D`~KuC!!|*Ġ658ݜB }@0bմ|*ߑ8|=!}55^ m}T('}ˑЇҕ#![{Mw'C]j_P7GǝxQK,Cߏ =k7u)r;l}C񽇆J5vJP =|~ul&[c?[keߏhϋU ڳ IsHw7Ol@Urc~{5^gYu t!g s-yJ[ک\|c 嶩VT6ۀg 3 {n{PK5q71&nhj.<[W,WX_&&Ԡ_6|@bL/4 Ox*)?~_tD$xS+g__/ni6^H-Yl+c)eOTOw%mҗiyjt%>޻߅\uV 2SWUpX^ވ^iؤn%zjw:dl2@Z~eXW_z~hӃ:O'a>ԆR>k^z)O~ްU2h6_v~Û,|bϩ4fH71tks@B]a]?zʏ7!n tW_m{\~s603.Ż.yL: _z-:qCo.Fp ul;4F#; ,naIt f{;sL}pt{e!|挿U"68~kᄁ%qGMLҝ*Ʌ ^6qxAQB8H PUй/xW7ֵ:gk>ZE9^ۋѶke ՗k7tiIrNb4nxHȞ#Nv3hsGǬdd pxuMqghP*ܭ:kݪwκ;O(]7\k_0^R*u,k25m+6&)5vE!N:Ѐ* (Lew8Z߼Ubϫeq#Xj } fh_Τ۩ 5b<ٲ)ɍjnB pL E]|&Z(,$ux2b4{0h9K[RpgKbî|0EY!3.?r7`Aa(q-Z5mʾC!SfpbL$Iu 'ucܠ01iHkh2(m KDR cPp,hrw9c ~ j? Lr(vl" 7{D{lu:i]ke";qJ4p`e&b$0SH Me.Hw}c\]7ۿIq/r))=D_惪e)*Fњ+Q HqlI"xB{, X(.cI/ДI컬A&HԼXñw%b 6k( Es|VY!jM ؒtmwDӞň]@6)e}JShcc%LqSEa>yˁ颠TL >>%bT%Lؙܕl 0lVyRYx B5x`%NNo@(e> W",\SUN!1?CDWۥ  k\:WBكB~W$)4%>5ό׌ZoF=X]D6VՅ)X5y`ȃHpd#&tӍh$`0R]#`B $S;_E5jhX8@$4نRAmue"yrX 51[La(J>۱ms()GfJW8]?uw(Vboh&(-AMCeQvpp@sFf\ |ȰnvmdmX,3qx,/h?sln H䗣Lx [KZM!dz[VSdej1)cVr{Ԩ4~}LV[ٌ2ωianF-BR~Li TEq:KضǷcsͷs=m}7C(''^˖x֞1R| ZM&nx)~CnLc-Lv$[8k ͨ4Y&\`3J<SK"^Y "l`)-:#w&E6lş~W3*:\->Gww{]!0Abt0 5*mU}~B,TЩCDz*Em˵ 8. g2a{f&sR=lWqXH7q)s;t6MN9y30yYkhO%"✔Y+ QE%MGIP%Fa_@Al(g,H'ɠτP'BXaA^]%6/\Rߧ[NlhC8:.=A51M8ЂH֛z<&PVc.tCeKLyB4e–#!'/T ~]$e.!&IyH.A]7.Q~t>Ϸ5Б(41  xkOh$MF?9Q;y.o;:U҃:j?gfHNKY(mENpS>l6:LƣAr]hWZqe.tfmObrByufP8fG4OA|i|$QY_2$ՊbiV1ơ}!JFFAX}"H8 k%鮦Vjrʊ|~n/ha]q֣0|k|8!"Ou%@씚0])2Er>p6d՞sx^Wچ:hy^Xq:uk&r?b u u^'=yǧwҍYJ5*{&tm텷v +⎚=CgړC<q`cw`-ns[bor _Y>xѳë2u/z^;n 'fl<glMا~n7بvo-NYr6_}UqvQot/!(5^)U*FU0PsGG'#G.%8SOUcdݲj~S?Q5={2_ ،Omy$T v3D85E^, tz".swetnl1oݳp.1^^~&nq2-ćj:φ{*Lʒ5{(B]IÏ c9F{rjyO_k -,3k^p~($xz6槇Pj}(*CQLv] knA"rc ybg| /F| O λ]JC+VL`1ϿeOEW)Z [)jK`*_:. LǠ*|L$0pWQ8ZY}21ϛd|r:Fa3PِlGa "l gh" =ygVB:jI|a,Mq@=mY=L4 N39 -40D>zq;֑% (aXU0n -/T-6Xǒ aXtkԥu;,,0V,4Kp җ̇w¾Ͻ7xU8Gc/KcH5)_E'Hd$Edrf  ) 2~C)r{"`"p!s>s>s>s>sA>҆ÐkS?odg /2`bG>ӏ>ӏ>ӏ>ӏ>K?=TnVjٓ qP2},e(=@ @ $ ٪HHS| S]-I!|iOc4zmgO1>E7BOaOaC>S>SӦ0'U1EPK*1{vPKF manifest.rdf͓n0t?@uT-\4t+J^)l4E 3g(}c^f-,nvoǻ?_ҭ^|jM_ۍcW]ӭjWw~~9_O`lti=TAw}SOnQfS~sy.GXe_v_۪v]zuM,io7[ 6mu|zfgz7}{wu_7t%/DA _/H$! FA-bQ$"R4T00HL@s8'QAH :eRYMˆ`gKQ!p+ -hMI)g-0޾amHh98+QzG:ق9k(\! !rW8XTA0ͥx+#ϖM 5Zq@@YAtB#Z=)G!4jUPG@f2:{6{p2%I \=J  T .X4YxQ>RftLQ2;T9h ) $ Cgf5hkuH!RmsTZ$Z@SS8I^p.-==GYnt)7@=d3V4A@ADNB 1A:pkf)I=<BB9a 럶 O-x"6_כ9}hOǟCt6z[js:z3ݩ=OayjWÛOPKu-PKF^2 ''mimetypePKFɓH`<<MThumbnails/thumbnail.pngPKFXo!] layout-cachePKF{V Vcontent.xmlPKFW) 7settings.xmlPKFMod>7?meta.xmlPKFN'-FCPictures/10000000000001F5000001F51F450338.pngPKFkMcc-!8Pictures/10000000000001DC000001DC1D251760.pngPKF@ֵ-sPictures/100000000000025600000256E51A1ABA.pngPKF-*-*-6 Pictures/10000000000000640000006031270411.pngPKFC499-Ia Pictures/10000000000001AF000001AFD32F629D.pngPKF(``- Pictures/10000000000002360000023540E72DF7.pngPKFO/-x Pictures/10000000000001EC000001ECBC46FA65.pngPKFs-Pictures/100000000000020D0000020D02A68297.pngPKF$&-Pictures/10000000000001BF000001BFE8CE7056.pngPKFXʱʱ-Pictures/10000000000001EC000001ECA691C526.pngPKF.Kw>I>I-&Pictures/10000000000001B1000001B1A9D4F77B.pngPKF 5T;-Pictures/10000000000001EA000001EA24938CF1.pngPKF'@/u/u-Pictures/10000000000001C9000001C9925AADF8.pngPKF[=AA-9Pictures/1000000000000243000002432E6CCFEC.pngPKFWqq-<Pictures/10000000000001FA000001FA4BBA9147.pngPKFbZ-` Pictures/10000000000001DA000001DA5DDFE498.pngPKF;>>-=!Pictures/10000000000000640000005FD3739B8C.pngPKFևD-"Pictures/10000000000001ED000001EEF0A3F508.pngPKFUC-X$Pictures/10000000000001D4000001D425384674.pngPKFVbH55-%Pictures/10000000000001CE000001CE258E40A3.pngPKFq| | -(Pictures/100002010000005F0000008744F2504C.pngPKF22"-u!(Pictures/100000000000021300000213878E5583.pngPKF5.EE-ݯ*Pictures/10000201000000510000007AC37B12F9.pngPKF-m*Pictures/1000020100000071000000786EE9C279.pngPKF]$  -*Pictures/100002010000008300000077C838A317.pngPKF=k55-*Pictures/100002010000005100000075D6766D47.pngPKFG[-l*Pictures/100000000000006400000063E6C47F30.pngPKFK-6+Pictures/10000000000000150000001C667639A5.pngPKF=)((-+Pictures/10000000000001DA000001DAF8CE39EF.pngPKF=-,Pictures/10000000000000150000001CA2470136.pngPKFUNN-w,Pictures/10000000000001AD000001B2BB0107EB.pngPKF=--Pictures/10000000000001F0000001F1CBDA2D8E.pngPKF-/Pictures/10000000000000150000001C9244758A.pngPKFQtjv-U/Pictures/10000000000000150000001C830DF90A.pngPKF%c-/Pictures/10000000000001F5000001F56A71D99E.pngPKF4-ۤ1Pictures/10000000000000150000001C9F2EFF6A.pngPKF,-1Pictures/100002010000003D0000007E29D669C1.pngPKFvT-+1Pictures/10000000000000160000001DCE8BCB6D.pngPKFVۇKYKY-1Pictures/10000000000001880000018778E97AA3.pngPKFGP-3Pictures/10000000000001010000005711DF58AD.pngPKF|L((-K"3Pictures/1000000000000101000000564119C84D.pngPKF|M-13Pictures/10000201000000580000008C453DFDF9.pngPKF୏-L3Pictures/10000201000000620000007453687193.pngPKFhgBB-Q3Pictures/100000000000025300000253E12E18C2.pngPKFG3v-;6Pictures/10000000000000FF0000005684F65827.pngPKF<ww-eK6Pictures/100000000000001B0000001BC9A483DE.pngPKFnYY-'O6Pictures/10000000000001F5000001F553F9B12C.pngPKF-?8Pictures/100000000000021F0000021F83902772.pngPKF\  -.;Pictures/10000000000000150000001CD38E5523.pngPKF-;Pictures/10000000000000150000001C9525A37E.pngPKFJυ-;Pictures/10000000000000250000001BF86583B1.pngPKF9``-;Pictures/100000000000001B0000001B3D6B5810.pngPKF/JJ-;Pictures/10000201000000200000001B624B8874.pngPKF2-9;Pictures/10000201000000740000007B7C2D70D4.pngPKFqѱkk-r <Pictures/10000000000001DD000001DD260AE382.pngPKFK-nv>Pictures/100000000000001A0000001B0EC27C29.pngPKFR-y>Pictures/10000000000000FF0000005ABA029AD1.pngPKFq$@@->Pictures/100000000000001A0000001BCCF6F964.pngPKF{##-=>Pictures/100002010000004D0000008339E69CAE.pngPKFk,55->Pictures/100000000000001A0000001B5E8A504C.pngPKFK-+>Pictures/10000000000000250000001BA0A56BFA.pngPKFwؘlElE-8>Pictures/10000000000002510000025177A1B291.pngPKF5l-APictures/100000000000001A0000001B7F1F4B88.pngPKFgrW&W&- APictures/1000000000000206000002051568F9CF.pngPKF=AA-DPictures/100000000000001A0000001B7062D2DB.pngPKFf7--7DPictures/10000000000000150000001CE128101E.pngPKFL-DPictures/100000000000001A0000002AF9B89EEF.pngPKF\+\\-"DPictures/100000000000020100000201865E3C3F.pngPKFUR-_~FPictures/10000000000000150000001C13B02DDB.pngPKFspp-FPictures/10000000000000220000002ACAE8E28B.pngPKFnV-WFPictures/10000000000000220000002A6266D5E2.pngPKFkd-FPictures/100002010000006200000074A897AB16.pngPKF޵x00-FPictures/100000000000001A0000001B569B5E85.pngPKF -FPictures/10000201000000480000007D84290FD1.pngPKF'I -!FPictures/100002010000007100000071D57186CC.pngPKFJ?- FPictures/10000000000000150000001CCEA3BB54.pngPKF͜-JFPictures/100000000000002200000029ED446F78.pngPKFm! -vFPictures/10000201000000740000007463912C7F.pngPKF[q[ [ -FPictures/100002010000004800000078653F831B.pngPKFԍkk-MFPictures/10000201000000200000001BB26B2765.pngPKFF**-FPictures/1000000000000C740000026C1263BB6E.pngPKF2O-GPictures/1000000000000252000002525D5D8B7A.pngPKFs-IPictures/1000000000000204000002045247C071.pngPKFsW,-JPictures/100000000000004000000040AB357309.pngPKFw[oo-JPictures/100002010000007400000083AF471D4F.pngPKFQDff-JPictures/10000000000008FA000000C6557B5996.pngPKF*1{v HKstyles.xmlPKFh ZKmanifest.rdfPKF\KConfigurations2/popupmenu/PKF'N\KConfigurations2/accelerator/current.xmlPKF\KConfigurations2/floater/PKF\KConfigurations2/toolbar/PKF]KConfigurations2/statusbar/PKFI]KConfigurations2/images/Bitmaps/PKF]KConfigurations2/toolpanel/PKF]KConfigurations2/progressbar/PKF]KConfigurations2/menubar/PKFu-.^KMETA-INF/manifest.xmlPKhh<#WcKoolite-1.82/Doc/OoliteReadMe.odt000066400000000000000000003311441256642440500165030ustar00rootroot00000000000000PKF^2 ''mimetypeapplication/vnd.oasis.opendocument.textPKFConfigurations2/floater/PKF'Configurations2/accelerator/current.xmlPKPKFConfigurations2/images/Bitmaps/PKFConfigurations2/progressbar/PKFConfigurations2/menubar/PKFConfigurations2/popupmenu/PKFConfigurations2/statusbar/PKFConfigurations2/toolbar/PKFConfigurations2/toolpanel/PKFDThumbnails/thumbnail.pngPNG  IHDRzAYIDATx xEg7imxmRR8 8Ԃ /*pPлC< I@ P@} @>@ P@} ᩏ?uAn8q-[XOS۷oOnXIN2,Ν;7mmŰ $luLħEfiuAA ^zǫUj )o@ [}N*1E\?B^o۵/'!!!٦;+] >8.ؙ;A V"Ȓi_gcyyy_sH-(: I1bqgiGMH@,$EQQQq̙gPZ&H͆o f\(%ᩏwqд-LݭVQb!jxfOoԥtܹfuOUUsVvw^Oh~po߾gnXs{}@*l$1f=.._u֭ۿ$/?Tkf67^]qqqjpևD||p* /O2Epu=qdЙӟ4l0OH蔑M._ʭvB\!:::p`FÁh8qf(ZjĞ;Z=wnׂ|rss8[ًpW8Er-ՂX[5RR7oXuż-L͕uX|^z .ߺ *L\%ҘXVUmk8& yx㦏O7]vHQZfޱ/:# T u]h$V#kn^~שBMJCbJ=Igb=!6|nL䏉3 hy)+?#E\dɕɩ6(u[ĆD 54D/,*bR` }8 ߂k.xR)F>.]AXk(c]kXSSӪ{RUƋNc,q^%|]gu zOcxAODdÜ{~:mLL}[/_,SN>OvLF.e.BGظSxhµ*"MowxGFiTo}Ť~Yq--;dHG[}{ &:߷oCL 3oNI7d2u1^4ۥ'<פ~;x*|M#aIw}Ϟ=жNAGG:v{cƌI.w; •+_\tໝrr8hO~~UCGhӮ LZ' Wƌ7͓6g1}O o}+J޾ǚ+ Oy4R.#kpn1[->f?9llgjfr̢ToV)D$_˲۷o/ݚjM\e> +JN˼疭95FJRbE|Āg&7㍓DnzΫqK\rblѼZ.]k I-Kis&:G._ψ׎?κfAAwHKKoZZMI}dzgU\Q~}qo߾lԇ ɷ޼}}_f?/222j˗yH*.\8ǺD@}ӵkVڧLl6.P2X1孑E~ghFnN>ͺPl@}`22J-+6cZ7K ԇ<%%%*cǎ ԇxAIM@}4_t^Btҳ8A} @>f{7q9 3M%nAP.'9~@ P@} @>@ P@} } rxoNJ=p }sË@>@ ЇԂG.ؿ B#'%߇j8~*hhg8yz\#A@>S⵺B̤9;ُJz?"q14T`ɒ4S:W6]izzi#KsLwslg0U}Z+5 ~@JEs! P)ҼU7@3rSp;S)@ Pv<)h<5|4@dĽ#(Tؠ>`OwLJi @5qR.A P@} B J: A4"@>f E)@ P-E鑎xm# J! Hb^?0ʆ(UG- sS{σM,MKzI)A?`hUzfI)(T$ js).&}GGڹGS*l碚jz/4Q kS.YxGS>:H0? ɦ l{ʅgQ _g{xǁjxDݥl`أwT?-] hz|7>޸z&Χq#}|_@ -rP4)PF)ld#Vώ6FUH- t52ea(4ƳxX:E*$4A@}  /@vd'i=f>M |]3`@0ߺ:lHo8ؿ }wd-#zql35 Za.}|A.5C߻xA 6%Chz[DKA Po[*#݂ ƷoUG@ P@} @>@ PxA[ u>-R PT{ @>4eCdUpB2wxߥyA`@>M)a5DB"h8';_ݷ ;.<QdChNP=oH5Fޕu#z.j_*NP=oH5Fޕ^kY!'W_k(>T_k }! UA{} @} 1Ǜ" H4PH q栔Ug>(|(rTF } ! uo'FXFia/{}]p*EVrQrƏ>(~p"bz)49 eP@} @>@ P@} uljhIENDB`PKF content.xml}rr{SHi{D#ift5{v$@Q<)TT/~HURTOWB{-X HB{(`a]{u> َnʅҁLfׇOo??]]c'Kte_ KutT9qkLp?qܱsjen܏Էj;8uVGq?ƶ]+ώZPuY<Óbq4FՂe" &톞mPVdbP.mUۆdz6cFu9:O[ڱiOډj'@uKp,^KՄAܱ4[^&o޲`gPnTߡ֣G2;\[\S -5X4hW.B<{B2 ,R䯃Ngi?\_>ztn:jN c#^в0U wrvǷ~Ӟ,l өO:ɔ<\MEj Kb9ylWH@Ⰶ'9-/C{Qpu.l@|iϸ +oڭmd@X1y9y-F t|`Tj @T[^gj l -7r V{`umF{RuS[KG Z?s _??}jŒ'ꚺ`:wqFDCt,CuzD՟Y'6Ixp kބjMp3U d^biТ=bjl3ϣhoݡVS[K20\֞\B9!o  3v\6Xgzת[HuG/.Sss @y'Ss+cIU[￀h y9몞T)m~]dF:a_5Af 5b㤝6i,41:Y mRo˧tu(G$c۵\y=J%n{t@ fp+sӟi"Az8Bzq&I%P.Iˆ..lKxdln?oRecg-*0 x\IQ@y2] 5E%wD:I%;6m޳aFj*3BR5fKr0).˖ ,lVDp*/CچzVkJ1c5kg^/Ÿ$HOb5k瀩u V^@sio -o?zM޹pQюuK@YKڀҵ׬^fUZONzlw浬{o[ٲo[vNKN˕{쾧R|HJ橻;_'T AM֎wJ[L2 [O_c ƋNv)VjK.WYR%x;85|,>vp lnL07d5PR|i gM$L;KyvFWDӑ>]rAvv2wےgX/mvGUugMAJv"v,$_~GY1J`oZ<ʢK6]&Hܛ-2T}Mfcͺp;+F(3\\8m߯-8mJ"I\BN䏶y{09ھ,SG4BȰݾ[I"#EdYԍY=pz6oȴ}7JPn [5oGuU\ l}+xm+🉪6 W~_ϢgMhk,m'2&No Moj؉sT}qVNKY<ҁif>glMTzrmfV*l eH֣/9s3$s3+8kR}GY"۸J7Llos'z~ۈ J}jR/QITg;m2ꓒآvcd퍷y,E ǥB-ec0Lxe&mMa)R{ZJLKe'l%'1az7Աs~}y"$5 e{ jhfS{ۀZ Hp}!Ez L҈BN)܅+^x{{e_ABoҶvS;(I(.V2URP-7bf_%~I.toBeN)\TM=\Ty7BI_KE)w ZM `)\v'HLZhk8ߝ^;ͮY\Нv߾5oAY֚kj|Po8P3I5 MP>r갌'!s_I\a=nw4 _,ˎ!t:/Apc"8MW!tOo{i^j\s 7zvZ|9&3dؔ!!0gֽ9;hWXs$}Y%}VIќ); s7$i޿^iI9r$!M_Y$%_4agK34̗/R-ō"/9]P~D llc$ Jwc6"arQZP%`o qc&VC^rm^n AMi57%#$,s::| Uںj(_M]:LnŴ_cE9|х>@3U֝%]^: In%˅"1b C/hזUfdX]vfLUNaٺg}Xr.ykC)%~)PttGwT۝ږb"ݶ: 4 r([@7UBLت٬g3fn^:MCE= z\5T5/!v2>ֈ_pg; wqPQ)桊x{b`gUK|K99#5"8mT&C@!f_w1PSxچTjdtۙ&ozص 30W0j 4_1Xi_Th-\rM6-u [M\dշM$jr_djܐLlc6,"6.".kZM3O[fPj!S|m\6&.eI?66"6CWD"۸BEoMilOv8^kyvo"fs/'ݻe^ ۜ[f,6a;K2,ٽM=vxls.7"ވt ե5X{uBa{ۜa 8%PyXغ@7X,CMa4v`Kuc] 7x8~xغ). F [}D[E `9kt%^8?wbbKf=OU2eBw-sB; EA:LXi!TGw:[k& ;ܟpi*"c=mc>תܶbtW_AjT߻R.e+0wf?VSaS>S~=^p)nLS|;=S;S`1ӹ5) LqSFƖg+ : {'VP.LQmxCv`F*C=("Sb~8_xy|PIFB:x~3i=xS:lw#Btuc7Xahq:󝢛q#^M}9)Ph 6Sp-,Z\_u6k!vMN- Ļ?Eؑz 20O)N1? I2F6ai x g`L$<3K>x0 R@4Mh ÔBM`" W=+V8phq&#h2料 gHOr},LjQbHیL]LS@Lɫk3#F^AWA&r "9Zi>h baXv;s@$_ wXϲ"( ،& #7v1Al7j A`sP2>6琩΢=` 0`kj.vKw"G@T֥f3@X80$:|S,#'U7F󜣮PιwI8 x)nw XOI)Hr욂1T:ty҇3댁y0" @Mf-}S![" 4/_?:o+c飾.Dgؠ-uWx:a7,2.6X?wg0/67^#&S^2q! c|u=l!ëE@džoXOL2vx'q cWmWKYk>:VXV¼"@f^L7U-kfz1RV`f`a\U@7,V83)#6S uUUMY6q?U><t0GcjC^U= 2,rMj*iE;b͑n7#E#q!=x$V@i}W׶:GЬ:5oJ VV݂r:Y8;ںu9v8rLD#bn퍐uՀ:tw1:`-K"GA:& 艕WSaeC e`Js^<$pVqч[g4KB,XC VZMlёbl91Ksb,ٸm\#nͳ䈞RLnR NSm Ba2ɬ =A'$d/ #RO ߻,c]K%i;zA5t`2H,aX+`40Yw">eRIlv=Â9SȨ8??I|xvT%bG"qBnq`c!]48B@2 ;8`M-%uT Cf`yZ>ߤv=7M,רqe)<4>ןPW6LU!0& P>pR{ݨT-Oq8R5S (9}>Cf•Zh1gwx3À tCf]X@nx:ʆY ]q?:`b-ԃUGY _AşoNlc2ݣ/Wj،y dhIyaN q* <0NǪ5+GfT=*׎ZeQoo0]6*ل%+- .3y&HJY$o2X2^;F~,pCq$S1HV:Յ*,V`F)hr`5 ڌCU ٽc]i%Kqyf0ƲxZeȂ6 .jh_rXȌ?Ow8Pyib^9nl1<7Ԏzn.lo̿-Hv*O=:O4R G4yε $)])gic un(К&7OӦݓ;8bbZtRr[XSx;gJ\]{Dݺ!kKH^@'^".pRx׍pvA+6hvI$Y_5Qb>bƘ"v$`o: ?KG`|+)fm zM5%wl9sU]whb": eCU`ٳĨtؐ'Hm :* jiFsä4i4W:hmqq8eb$Kw'g{NK@Έd5{k1BAPN9JQȯ֠<3MW+Rjvz'Uqr;)ЇUe7 h[ l-HْI J27RjxxVۖɑU 0A-Ԉэ3Qu}t5~J&5͢,i;pxdj9ؔ J6[S09jT?PCS-$q-q{;/Ifof)~)E'D*uCAvyc<(QX| Zg7F`fRp+'($4%/ĵj]̻.M'QB GbΨءYCwB1C]CTȺq˕zN0O4j@&RfHAXEH!ݩN+rYEx;S$\<<gPaQVG i"郮u]:L9pYU*״ϓ2mi&)$-C6,0I'%CeeVxLr&Hs]X PN-Hu BuGu$ ZuJdO/ё mCI"UQ"a MF>sr+*tIuCF XsAGh -x f{֘m }A*4v "XbkRJn- +@~lQ~ !NaV+KͲϵs)#$&S6:sس'ԋDvqz1QDcBQ> E}yʯ?ysRӣ)GYiUį[ .8ŋłS NO'XQțY BaR+j4.j"dx:|oqjbVBr1C4!ʵE0K&1 fP>eX&cVTk8)MPŃy3=lө;iUbq>Fi R7M [Ǡ&(kЂ0b<\<OR˂#ehdҡ\KVD%дgvG^rC+{G|c 겖p!3 &r͈솠ioTw}9Z)F2ƀIðZ@KpuI6 QKbz> x..3g1a VJ] G+YS\W]k@#e{cǫՑ3́ObhIȒ7)X-ɐ`^Z8a嫤)@QШ<3JcmGS ǘ eP,㱑Ct鿣`,=2 :A6R̘>a?|,E' 2]n"HӥCx"O,_rNͲOMC憧ѵŬZ=nVQUwiqUcGdۤ:`'Zqkt 3IZ7G_Ts?w$ ,t'hVbcJLc5 |GɖMI%1Xdƴ4PER[-A,i. 7 Nly|Y^$K9:D1Q=te@<^U(] A FMAbA5P5 |X ,\M /kOi,hNJ"Ab7x4ߌ+"΂񀢫SFZo͓ [-BKl\ÂkEByV%dÉ >|cx.S±zC'ĉZ% PM |olN᜻y\UQVmpԮ$/cԈnxkiL~ofBY*)b\FAy75yd\kf7`%,6/j"\noSnȁSZ}x)Ϯ8CKRɥV(S L:3:mWjV]5(+u ^pZb BPZr:ɃtVN{T$ײyu(],YTF%;*i-0i3=bob$j>:Ia~'`#~yk\oznXسE٢ /:I$+R16BaA۲1oϜ&r2>u&I}Q6),;سc_A"FjTW녨rLfv`JGI]VvDi<ů㤛y:B ʃMZN߲hB27󐁅ARj7Jttj0-@)̉!uA8(^?/ӠNB@.Q@s!P)wEw..V+S >$O>prWbf f%氲6Lu\xSp}5-kpE$b]A=T=!f, KއҶ/zZG&75E1\U*^ӟo;Pr]U81Ή9H}9aBo jxX41)mq:|6ж1m̝xR!Qj{d2"G4l\UߙB,J5a:Cf=_e vKii~LXYDhW[ػ|@b :M?oô7T)Nf ?m;k͒Ay15#r 'gIaU"{%N>8Np%,x9~r¼l+8-,,;t:=?7`O=؎z."dGT:qޚr $_ޗqyb<#T6H&JH,oy5K/J-XX\)?"a<Ïwʐzw_?^])||1'Ip\,^܈ Uy6tozwI8 =Q + OPEf -mμci$Kt&k|OS#c/{(7k4.o[LhkMqx1z{m*mAJ2)62sP 6;wndG|HwOo⎋v;yδbnPn{ڶR6C嵍M}Ns|.y%ԎzbZ^J\ %ksT/D(Ra3'KqR:bYsNX~!w;,6ۡI,eluݵӡ:N!i9ޅwzpT5a_6DKBtH(>P^fBGnR㎥ :HI5]?!~UQX"r'Fs_`PꟇ>ɷuuMϋ!)$/$v^7~SdO7w;;Yj992cO[y|4#"y'9SK .F(Q79b5, G%kzA5tcvsaPO-!F͟X^`X8iUQikLBfR.4'1wdMf}C=Ë ʩ_CTbmG.ؗ|?SZ {'~nÐl yuP;CZCHw@P dߩ,LE)V/WJz|\)UIjARy{X7{̖I<;K/""18T9eFl y@&5,I,.\lZA4aQ:&4[Af7e6OMt(83u^:n(ՅŲ09部y^Wb< 'd$&Vyd5-Ma39xy'|k'I\8oW hC'G( 7q]J^؇4/BiJP{%Uĥ3u<ɗj]mVIjcdn[t,E XVhù,$n@]xjOKJ-(S]>X|Xj *"5X-/Ĝ9TSNA_0͍O\Z E`:T/'f{T+ ]u||;?y ;^DNm[5]E?O?^^]>tpsj)nSruQP\̻5Ó4b4mG,YxSlp݆/!~ia=7_BeSeh 6 ' 2,΁T)UR\-sijsIwvo93Pe(/+q~9e"=OA&$Q'X 8R_gX|US7076`6 X{:5G6']0+iTQͦBpϏPYܽ*ܲ\ q$-i#ztqZ''Gb0V}Uf`rvLݶ\9*PbUѹjUV-ӥ W2"c`J,5*Z\}ּAn`:8J%W&r! g g"ŇgaabFs'\> *[HQlr(*Ǖq\ [!+(HTVu}E@5M̲2z7HApOǝ*>#lTS@4Vc, C>%# ޥ"F,&sU}}-7IKPzjO˵x9Ni$!UQsy]\dx9UE*tTtX3T7ԉȧ[ʵ04(?R 1̗pmOyɃL%iJy>F`U&ztj*]bM?zu$s=7q. >a`EL~lGԓF4ف>G~ S/P+Uu੹NGe $3DB.t *(NLB KwK#T%$#rz<|=:y"K7y|3`)gty Y"`.s﫢2?Pce|'3xt(K*2q8{v=ո?zf}%lp@+(x)af?M=^{lS uiVqɷÇ=\'"pۧl.l^_n6FW_Wv,(H͈GH /~xDxxnC`N>  a_i,uQ<Hn5GWeZxiM1",MD« jfTr~=jX6mbrﳊ5HGKj_.[J÷ ~~y~q|Qyrx˃⾥ޜӛˏ_ni };%t(FrysvsNN=hϔOsn$/ʏ/[gWP7JJo\ԧx<8{L~`zW`]]⏋.`-?DFR9?> K;5NpB|{{Npn]yvNm.r0) ]5uI0yzpy{P sIQ PK GePKF layout-cachecd`d(c``p P'0a/p20x2@ a0#dPKŇ=6ZPKF+*((-Pictures/1000000000000200000002003DDD2196.pngPNG  IHDR{CsRGBIDATx^]UE޷oPD@Vl@ @L;~@a93sewq޹gfN3qppphzHlzCv#vppp 8ց@#MtݰpkAAAB&:n5 D!@x7lGppphpN#n 8884Q8D' AAA(h [M4щwvppp& GĻa;888ր@#MtݰpkAAAB&:n5 D!@x7lGppphpN#n 8884Q8D' AAA(h [M4щwvppp& GĻa;888ր@#MtݰpkAAAB&:n5 D!@x7lGppphpN#n 8884Q8D' AAA(h [M4щwvppp& GĻa;888ր@#MtݰpkAAAB&:n5 D!@x7lGppphpN#n 8884Q8D' AAA(h [M4щwvppp& GĻa;888ր@#MtݰpkAAAB&:n5 D!@x7lGppphpN#n 8884Q8D' AAA(BUUU =9s|ٳg>V K.{tv]9^y` 馛~m!4 1?裏2dHqqq ~B/?bs=נoX_އB!HG !kA+)SJLޠnXᅴ޷iyWxunq@1tP?yv~La @YYneee;Ouu}kAw @ڎ#8bĈz5jC{}v;G %#))[}֛6}]N:A*wppX'ڎVgN;}(oEbQaԜo}b+%=!54)s9o0˖- 9\8]m m@=>\}騩mGϚfm* #$CrC;?SW~g \ĭ3k֬"[#;fu+ʋ K6V iK.z$P?{/X+ !&-!Orы %a|M7 2wg$/6`MOH ٯOz|O[`z ` QFAcO?Ǯ}xz8%Cz>ި*MײeK ?_sEEy8QUɿ?T(!%w=?d pr-sppXO D3ʻߣ_Yq]$P"=NJ/9 }+pQ<\/$A2c]m|c~Wwߧ~a*tpp&O DoEcCa[!4 J@JG<2 L/t s]PY.cWw >Ugy_bŊ:̖{AAA^ 0o<۷oa \M}6 . m݆ B_w_M*{Gk~'vbRJT@G_L @,bRi,i3)&&y飯zۃ⤓N(СCu`\ fΜ w ~w<°r~4rS8:VК&IxЌ&B$1#=iQ1}ߊ”sI_4iᛞ^z b/%F00=m镩uaiS(2pL`qi30 ﷽OP* HZL D2dj{a$ * t ϭ*PXO̝9xoYeQFw[o~{:g[.FBYE&`e7loaw}p͍Y$qDL,IË̗$4@Q b+U'c&><4=Xz 'yzݔCvSRtW|ѼJoY}@AD)`"@x _ĄL L=H1Vr.[6mڀ_p \/pT.vz æUmPX{`N}vBey+Hlqq-RBMUzv+kj>F-[v\`> 98l3gqw55~d[*zb[-coDRKbe ס?0_gY$TA)Q ! f<c0͇WYTPkC^̮ԩS.N֛LfnbZS, ۸>Roa#W6+W*+_Ye<5?r *Z6Q+P;nEoBp\q5o޼up ?SwU:8|hluŀR^ۊܿ61Eu3c*6m&3/Β|sZ kZ1#d%pSӿ.L4Ж믃@\>}:̝;W:=-c׷ly(e(jQ ZcRhqFP:|xcq 2J,i5G(zSZyS~|n;?|ᇏ1bۯ_={Nz69(EGU$\3v56_x6xU@Ɲ6@ <}*`]Wky VCo<8=t]wݵ{3 '>sxO^۞X^gRPj%X?т5WOb &$%u.2Ĺ\1퍽wq1׿.4v*aJOEdhЍ88V@l jon%@( !eϾ3އ㏇0܄Õ 3f̀> z}G}iYQ?xhsQ|D~7 #T4⎕! ~m$eLaw{oYarC=֭ʫC: >Cݻwb<ѿ|yia/w+[  Jqr H`[`yU;~beP(AuMN!8ԯ8e/N$"q& :ry[KxCKS α}ǧIl<|td`yAQU@~?`:^cLu)X ѬvXt˰;f*\e7[VV[=l Ŏ9昵_u}﫿e6NU z>JixDuNƽ'g H.hX(%\ZiCz4/ %?GkV-栔b W&Π IN;W*UÒZ̈́! {/\J!g ȝԬ7nrni;4vU:8 W]u7|,:fn{Ni2IiU.6Qj*͍&II-AP%guQ㩀O8I*ʉ\b{b!#ZP#^,CWwM]hohر\rIKՕpp?~*,yԊb#oP3zB/+VIaHTBL" 6$vVI}&J&g=FF*lN}fɳL7vkANxso-+W^z饽{ombA?ѣǠA'*|4E< 4Y2`bT5y@ڋ#5' 97w ԗtSE(^P` 'QWRIk u/ MP$ JXqhx b 7mŽ#̬!V/Gs*Ct[ IN/B/V9$)}#UQ2Vi)abI F.bv",~:ݶkBz=MLao+*M,I8Seg.OͿ+^~#tAA/D@CBr69KF.9z ( ܩ,@ _Whmv!+ &J SN/TaWZNdt_qy^}s%WnەɤiL(^X ? Tf܄Up2-#(,I_onӦ;O,o|q̙[uvhve|A먳^2m~T-=+{J6V˾'t kхFՐ+`!hyQ*DB}F#z!S][]F Zb}M}vx,i.&|ڱ>&僊LWXk頢.ppVd4Ěq 9'@uA3c!rM05 {]uu941{_ۦ80;1NEn3f 5 dAe0G/Ȇz-ȫWa}z dap)Z qI>ӆ%vs=gq]z@%3哣xrhXpeDZ@_}y ڌy O:(?ImC[omCnw̿M cp!; 7f Gl#?]>UmE#:K?$7/<$˕RXUmj$L w(${_ _C6P/ްzuq 7 K|ɳ>;?K#RNh{hkH…R$FraStsj2`+!'tZ&W[gH8mdh6[u %zQw5y@p>hXKf AoEF/"8Q:p)‹X~Bg>9֗07.kk5sTB=jhV*0t @`M(Hk$ 1`]~M1"eLE 7 KrzTq<ճȪ:"ξ8=-liLCZ&јqdR5OF:S }ּ I4~˱-]0x@(pSjv!Ho.ZxЁ:}Qjd'z$=fTk)/wDP^B0J:g~ŜA'kСÆ kIlX3rܸqQ}KiT0v `d!{HEy!@Ϝ]H@Lҕ(G̿hpdEz/38ڵkנ3*wp!0g8z!xqX\DV^A.$p;4Z4fT! Z7€Vk>CV(m?,3a# T?ů|x#$E]S@>(8|gsR:<݀! < OL9j{Jh ,.AYg$J9=,򊛏)(K"^W^ =~  ,l2_۷8qSw06ވL XCa<`TQ +`v(P ^R3ll"P?nKF p'cܹsZrDÊ)S ><8xz7z-钞[A1oCI ;!2b00URkDeLK#b5wON߾}aj޼ZL{A`%KpO?';iA 7^#?hWFdS:RWO/&CR?g~_y`7|#Fwy6GdH7 /a ~C.`\-%H{blBQdOi .'X:ِw8L}`hdqc?7plӯpBUi 0BP޾f=)O5 AA_fhg&$&@D!djJCn;?*8;AA!zjLw_GzM)9رRŐOݓ JмG-Zا#^ a$إy } ^{[])..?~<|rssq=WRBk)SȘ@YW!xV*\.dBw/U\L F ͶY%,*q*Λ>B 5nH:uZ5 1c콟}׈݁M:hKH[^?RϏS^̮QY3^עarCD-}İg Z<,*푛8º$d|K(~Ӟ۶m[(x]~=o0FD<ڤ#o~&+iw{#%~9Y ޵ -lOH0y D|hm)z@ *q+W-<8?]׶@A{?//y_I,ʜ")f$~_*5iv׮,V-^gAs-*^_tGpx'tRih-Z0mڴ6▧lף&kQ "00』Vv!@hȄ{l%F` R12IVv4 %aʏ||YYoX(CZ醞~W@#@`Y/lfwÞe.+|pb7H|%/f^v+IzzmA!CַӢ; >}7ވ?#Ңvl@|#}<,AfrՠL.@˪0j˰Z)J*+FJ枇.Ӄ#E2,DD[5 X|9\\s'޷铷UII{(h~$AjFX^lgR@+x#|խStǿpBeqw3`oٻ~@\f L䰋]tЍ%EJ#d11+RA8tud1Se=.[|Jh8@&r X E8HРH|]_/qVp=Dq@ClNW@B)}={䔻ƾyب+\|XcU|ޟѿJcl*;htl҄h5kXaY L_ "6[xUOVoG30F)~TK@,dmP~T h(KG;R%)`rǟ}n pAA DP"%X‡򚋰.[RMp'+dzȤBNڪlCx8Ɵd?0j_߾cF>ɓ'Cر .<ݮ+OUy@bZF\' Q-JF]B3 6MTPrŢ(ڡϿΉխ[I&+ ꫯM؎ۜ\Z|QH:݋׿NkEj~iXÜH•3"=7l3$u 6 EX` [l;geSZ*V'ˋE +h6 GG l#@|SF؇9X\t23` ɀc^ ~;Viaaw{MU%f_{՗5>E2?u1i-ٯ/* DurfƟ7iBJZhw:=辁jM6i00g A#_ tjQx=+V'cm:~5ŭUL05c>;:T\B'r"(@~Ml0>YqSHF(S#z6Tb\.G?8XS[DOg*mFa}TeEg<ސ.ANaY/Dq gb?f-/C@G@ְl 60 4ʁRRNv_Hc/Q-eA{,xbkoB6g%fō[DvSvr#ۃOsm_ =}'I*{;[j|msׄ WVW[ՒbŢI"t0!o^70KJVO0C) $ˠ$KV&)-*, U׽݇01onO65?Kuu1 7I{)_%pQ@YzɅOlՔ@{qU=ႌ߶5̜)c XJ9䀬_I}{ qzC;R"qBDAːSA8/[熉-%N\ ^xjWu딙Tg6rNɗ_?]U^Pߊʪ2<N [,o̶lrT [a8O|@á}:XA {' 'Y[Z)CEF{ ƈ!̊˰m.K| ;riD@r꿣Ɲ6k֏>gff4f(lAA PTTGd4 Ե/{HEx1%^ 43[x[ 3nyj[>AQ I7ž•g??y% ξl`CDESz6_lK^Fo~_ Tm]QDUi ]9Z)&f#[UiEN`(}iWWM F KpA F7&p6= C ^bAa3Ziyk(+a&+$-lЊ~#vo߉gmho䖷wI"?h&#R>g5T@l KSr2*h'37" UlT%H BʨtrJ1@Xlj?PAdF"iq1#  /h֨:g,QFufwg ;(t@WiBqD$_hluنfWƐ#K(R6 ޟ,7))ԬE%u0pؠ%ޟ kqV6^V)NcH:`3fT{:3soVA*?u%2ˉ;.w"8!yXJq5@F_P0j*i/A ҇R"Ag#G'qIN(,fГ}#^_!3`XBVg8ڶ0x˕MԽ'!@zǹp r?Mg V7j؞\u e3Q0`\tF"l46Mf /3,lԒ\TdE.S[OoU=,ik kכW| YU8U:jr#Qeg7o T@IRG Ȟ#l x֢ZD${$ȱbbx . GEE <8J~viĉrH-:6mF}ϟCFF1/g-Df?ڇ?x5+U~Ա-}ddYDHPfFNn7H@;fX%@L p0+$aTշ_(NaO&i NQjHSr}p7 7F 5U@^w_ݺxڙ֬ɔ$+TQ&_KcX.xEi§[}U툧 H,$- /i^HLXH`LZh:w&V.#]:T2H">U] 6u"^{ uc}'|ү__5رÞtKKUxJ#^3X_4,\QW_+/F`"; Vm’3ygW^Vph֟P@/oJ!XIeH`W"$Zҫi7_HPN.(Lzs-;uA??Cذ W vG9]\Š~'+]`aE*s?GsOWZXvug[Z9(b[/4{zjg^4V6@VVqbr"b{ `$gD$*1c*k(i9|y㏍ 6prNgZ6p̘1kzܶyW ~G.<c{z,h-_ͨt[ԲyaK77::XAg#{" ΚY݋oCeѲV9JVFƲ 5‡ (_c/>@%.׹"Hq.6uTڳAZWk…ˬYpz rKXhhytywScmUV͔iy^l l`fyْ(%m X7l MCV%6 sFGPɸT4cBٙr41a\$GSMBB9Z185"nџci.bz7@* cQ@~I"8:(d^5g}w٦Mu: 8=x[T_1<%@nݺahέΝ{W'؋sA^:*|%lPDey=Um׮”_;1W$&jѬ 8yiBApٹyLt/1/aZ+.-v֓P s ŭX!0;0~/!XDY$aJPaDRJ b69H+>` 1b;Ю:H\ϗ,$''r)ȴۯeU{`N8O_ku2e* 1dxGw^pٔ,́*K2WkmJ}?x,j5֫>R.@ AiyٍJ W6*Köպޑhqt2e=u<}d҄냓&MqEP7U~'|[PVPy&'//pϯ?,lۈRh-z^?=mbs֌rapC{|}ʇ: FR.J9 &Gr (*+cuH}:H%J\'B#d{mdO2ŨG|B^HJ8 aizhw9u?q饗8|vf};.%0i-QlSS*Kg> X㯽d챻QZZzm3yJne>[:nV9T㒽ËYv5OuCK0m*c {<AUG(فET@}#EIXa1Z s6/CKUiH$EkujJmA cH A˄8E;#cĀ&2PfZo[t'"u[lQsWAV<mFlq+ 00bR$k C `oǍ8k *L>nH `{ K[{N%ɇm5"'] N #u;'ӐٹVJmT~#h { \?>ypS ;wֲ6:qvaQʖ``ʆY TkW0xT a uX||BZ1ۄyU@]k6V28,[Z8i8*ۄF 9)7wڵOgL SGzڹWkMhg:dp|%=~>k>k?,:+Wuf[0N^0=- GnQakyzE 1VM[)QʙX%#.cCz$hqT%d^vg@Ad;Զ <<3pb9r8;;FdaU(  -Uє5XeɟהԔ4L=|>jy cMkd9nSpPZ!@~ ٳ+pkUeʮMDZВ%K8>}HmZC~z1?6@j@:V9б:dX6|O\1.ODU=Oj} it!Ƶu"I'{xpN?[F}YTI`n C˃3P_EhzS_9օ!/of#}^1zw敍̻6ܑOv^לA@S2Ğ$؅7φ ?Sa3m/vJU"SfIA%{[ֱH6G#bJii*描seYj(\ְrr2nPJr>|ӉS&N=zXk֬9~7ځݢ&XIOtEJ(␴_7P;"vQfhx470Dwmdb/*g!'#͗Txaԡ0@;o޼ sZ?i/q deR̢OP?rgc)|@ѩWluOpIu#g:z|?# O<#Y`[M'̬1&tAL/ ^"A{mM~?z]zw"]lbMdԀv"^PT f"(GBj%x<0#:}jG|_H/)oujZ?^m>#NS>﫯:s)hĒ6 ,63؉Z*x귲Y|^wPT"U'q}ɾ- B(,&^Db"1HOZ2~ѷR>#! E0n\l{oUAc?+*M #I6We$v#;6FR>jAT j8گ‘#G+)w#.򡂒۳GOEb jjfgmx'<7|sUW 6} 6 2@ V @=GdYQߍ*Fژq:^o4ǣ?=jG7%<ӷx;cqN ,yx}Al7 {,Ůe@rᰰ(HPFvdt0'.]{͛1qiڴn\uJZ(&Z*˞U7=*mB K~2nyyڽ{wгfy| !>©㴓ϿuM~r UQB|F$u^?{>6H~Cp`<50M!x1F{ iQKdaZ,Ft${ oӠ믿o̺ΝL9Ne䌰4|k~q5H6I;ƀh];Ktr=[o{7 Q &7a6Hk;!GHҍ>Td#!blou!eayWamƌg%/:w_ 0ۃWtcR8Ybǰԭ@;g/?ct{o>2sAOǽ^{"f#IŰ|ի:2Z ։T-@Tt8 .AŋG4uձ.(JeYS9?U@c׸ lP 9ѡ6Y] n GApXj(8Nȓ*PF CH(qv(]nF|JxnJ%`dPLM')sT(;W9>ZCP!P)W/|{3K.h8HxF׬^3b_֣̳ (i)y[ )XɈl ƙP WkPׅ#6rl !cXc_YGٻ qf 8غXT0烷[h+m䑽Dc/K^ݗG3zݪϝ~Jn$?J#hB2V8O>x98q-[^iii 4ڍXaYNQU))i7YE&\)ݹ 2{Oh$Z2vz{sNf%n^7J FO)cE,ITDJ;Kd$jҭݧk g *>Gr;). I%J0,H*((O۠bq4 7&=x P[f-ti#O9һK2._Q rB2k4Zߣtb64 *?r]n+~.F/uW{1pW>sOg]ȉ6? c =G I4x}{%,\|XgE!'be2Q+CQ>4{V.4ݫT{fVO=I:ziD)ӕmyhQ%oUU~Yr̀>ȗ1rCɁnnݻ+#;j[͸ S YDx]Y{XP4LLqv P(O>#Qq@cOOג҇:ݶ?ތ[jmQ{vt_dbzڻ0;_x_ͱgᠨgݸ?Aq7!†_09~;P޻O({e,R 2([6RD5f{okuk5G{rK[I@(LN19KTjgSU,Q@R,EYlցQH*c쀓/6m Q矿bŊHJjR|qX2< Fxxڇs*.E\ŏmd3.  b5v`x@>!>n-G U~~N[og C7Tn*:E4_|7࿰=6߲+Փw^r~F0΋O("3u~wC9YJuY?-#mkĬ4 D-FJ坨E8X7*f4_e/K~{rHq k.f B!Y*(۔e>QY ,D舔f HmEPva]M0.VC;wnϞ=7 bs.>-H(n NXj&Gt?rHjXTQj׭v䏫lCZs缫w*ۮ>w Boշ~?-&b Џ?|ڡ b'r$RĻγHraF>A¶U0Jl ܑmn7Typ*/6}uArG<"l3nd/\{RIZ(B)T+h 9NƯw};5d@ %?" f AYAx H i )7|"u _ԩS8m4$ ʦ۶6Ĝ.Z')'IEHdt>sO$HFlP֘aأ*m1X{ߋQS]YgRʾ-U* 3(Zjrho|ޚ+r! Gk#xI5yCe7Î9s,/BO6ZWZ':f7UaA('Q2Rr)9K"-爫xzp?8wa_ZƼpj6< \T b, GC)  ;|?H{:t"lSx3G.JCT@͵YsC\F_+} -S?5ߌ>Bg:K.YWmkB|S'V CyT(_;-v=!Y9A57^D%)˧5]^ED_#4~T2VΜ25 87a&MT[ D6 dX\eGU$*RG,:VHQQ¡fG۶mmӧOGFϿ׳Դw~H]! NGV$1ΜB3"D̎lw5rd,y஛N6Nr|n+m(579@ԉANc|zJcjl.YOńoQ)Dri?[VZkv]t=Z8h{/2vĹؚB12pF&;hnǝvce:k|ޅƶfҷ1߆Cٱ])i{c+3\ i?&;sPw!̱o1Ĝs;H*P`'ϟ?jg53ƀq7Zlyd8弫tv`Aꇌ10zT2ƈx=(bռ X?c"uL<b'pB0QkF>^FL4Ij-v`Ts^fUaf߬ 6|.ijַj\Q )>h1^F)v1Wς95߀435M#ѧqқQPw=xbĬT3 kmGDҕ3 Px?eV|vdfrg/CB6i!ƽWwn:풃'p&B bF 14L,"^%L(ލ%_LL>{מ}磏>֭[@(R.XWo Tni-XB'3F8kZ&LPFj讫w\iv-ZVSD*$| _suQ M[C63kȮ?uEcT*CUbaU R'FmI{G>j""T '"f<vՇo> "Z F&HQ|+C9(s+Sx|iJŜ˵D6:3 Zgʉ1_Y}lΊ*P{T*EoEj¾k/Q{Wh39k0g^`'D?cv3< If闿C9ml+Rl8ekͬ~#~Y(2Os{{ Mڈnt@w?IZ]%Z[QdQ!*6 S!ie#}+WYeؼAFauv%?]}!!.s'﫳-ٜn~#|=5/QmD7*O{xRB#eT@ϙ3Agݮe{Q]* \凐=tC@ 9) /_[De[HtRK%-l3(]XCt 0IS#q{ʍqQ):06:{6ZA/s\ _g+ZSM^ #3\FP?T,r:OYΡ&3c,Xgz]wwr0дؙ&Ce֖t*9p(2  CR{p0` `w7Fy̩TU$eӖHgצ"(_W'auQW,-[g|OMC5Jğ?}k&#>{-^* \ ' Ў*ϏQtP)+. 7m舘 VҚΘ[ _gOפ8`_u>Pg̪V|o߿L?䟐rd_Rp.eX#_,9DOH%fp h'2n3phP c!P DK1oQ\0zel+z.}/Vނf] 0F8Ө]B뺼{N>>@CyPJwV6PJoMؼމ]./ra*g_+}4M I͟ ?6tͶ?Fb':W:|Nac0_pXr Bo:Q0o |*{_#/ּKK-3gDy2VA n1  G} OV+Q 6Y:3*D#^@ߢy2p'XE9$.Q}5D%*J;gE p*ROh?U#1#сvI7=ēlV'#*ڗ-#UHA @{ͫLQ3P:JȟPˏX8`yl|gyeI "Ld8+A)N%N.wA43fJs1{15{!,E/+is|.i\o:[UW!Kʪ_$9ˈrzSB_ J19e 9^R(K⨺)t`"ը&U^x|SsTDvsWvtESuCW5tmV# X@melQxxH.'N8 :|<{Q7q1T%IQOA^,$}1[<1iR3)^wfPBjkNgvM2ӽ| y,IDt(DhSeE3Lٌ#.W~}ُ?xm{cm9?~Ӷ{âtטeiL:RN'X1Fl`"YDV>mgFR¸1)Tfpr'Ru1*Wڅ-$nxl.6`Ud<*[:kl#kV3M97v޳/)/L$aC$$ŧX Ɩyn>叟B/]R9CjW~-O4"IZ csRGPp艨}D)W tZ-ݎ@lYQ0chH 45-\V#f#B ]TrphQ1z%2RBw\ʪ D{' ,9S9@B7F3NF, _ӊ(j[Tb ق/d{6N34dS^Ev>n8TLu}ɒ%v#x-ne4Rɉ@E<""(u3Jq% &pD0kBy^u~mc0%PnGCd|Dj3eu^խ(c/xg+5<tFv`GAXj #)!zn +F .dS%*1bԨD6{ #wlVPjǾv5UTT 2]≏EO݇;W^yVy>7f`IH }dV"aƟAN 1O"4̄\T|͡Q>} <ΪDvZ7e]/O+j$KcP9,6Ri6!d_n^fyR4cN%z[瑯+9(D,';}⃀)pc 0iՄ41#Y7QJH^':7j^_46/2Bi_63e̽8 sx]vdyJ$/-NO 5x$Bk. $2W|=08><9~i0aBB=8Egد Y觿\j%[G/!H6%ZuKe-_GN!8IЉ:2n?w]@`w .p=CrR&qmfd95*;Ն~]V2ȷycƔ}>1E!/l~t}{G#j3o: hZNXZZhŋ!=9ެ"}D,9\P濿~x䝨 %%%oD-0DZT8 !JvwmIJk !ljRv28O/80Ѱhh" a c!M"-[ Hq\&% 9uE. %@\`C9m8}ꃽNLx&:Dcnp&L^ullqFmQjg&0wjNV69ߤW]2ayuUT_C)l-e {mđ9I )+/裏m{RT[imY[fDܕWٿrY-!xYVšCK|~AiM~&}VY`jP\;S֘ s3ˀ^Ecb7d-Y`7E\L=tl(jƽe,v&щ%/tJ]#{.vqq,AGcs"2=cQXx(e `&{~z(t^> %qŎ3b74̞МdddԪK`?SPcqkU[FK;vcR[x+//r|`w57E n?KN20DYo/<jbM~R!&Q Vڟnҏ\rJ ;w .p=(y\k;hVT>w֛r"}3צq0#1*g{|-3⡮혐6󏼘= V=v׈R|]<-uta+TpCzY*\Lx*):Zczo UՎPDTCB㿴*m '!% 9yE\*g@G$CA('[S9C ĔHB 4'O>>5K>/2Y]W W˺40Y$rOW8g9uԔYcόw gvݽiļ~w^xtն:ߺNG>wCzg-L=q M>$C u{!ɾW>IJhg 1J]qe D]oQ 0bFa>5 )Uq~ jѣ>J2#|ծISUU+/}OֹQE]}aon<]rҤI}s80vR^޶hyDWIȠx0|71uAƹZӖi-W~+.?gazKb(>I j^D?đ=BD( X &;ft!^AAkHX͹ +F]vkpC;FЏٜpDP^|ќ~˗֡9y]89x]Nmա &"*H(^TÖZFbQQ|[_~e_%cj%˅ `mow !k$ļ9}C< a|Ą'9,:P-u]O^vʭϙ*,SD5gP/iŔLc:` 0W`S|=ꢞ6@m0Z5BrƭZcOL}ByE+[2sg;w9Z2m=MlaAPUʗ}j_fIlSɓ?sB{rq'8׺ f6f+mvD`$sd0xHLCU Uc4UIحJH$U$w"0)PO;Ό3`, y#~  `Bt)K.82؋ʷ|_EGωf5Ok@2GH]u "'4ç<>a!׼lL8Plg?%P:^SCρoNڊ*b!}J)L]4B)/Am0jz_"A[\Tk?iCJU(܅7 SHk!Comʀ6L@2MAQM_RV)4a#`=,\yȶE''|R_f…&-Z*)pr:aIue! ggcqyUI2+YzeF_k/2Ze*Є0賨sN8b9DDfEާOMybX .GS`0'x)nkǿ^{0;N(  Ea$I,4"QH"iR(8KSz ת 7* y55dʎb4>}y|`U=II LuBQo{ _dzj8q"zS 9  2u.!ø|Mz ]l)#`ֳR7ʟɓ'o#CҌz&N2I1IuM6"Id#צ~yѾը[dUXCZ8Q&䎕@_\N qw%4p*Kn;袋rmį; QK@7{U"3%-p \k}ANE- R#d978_EѢc+/ %wͯ"`}pW׼l|8PÜN6 G>)(jpRRwyvn(U2X{ e>E! aT^=VUFv\D`2`=>޺HD?.*W0IwqЂމ؊?{Lm)i>+7L*kE;,Z O]"4!$'nֻvL>?쳧~ =v/IğjMoBMו<6kQ=DtDIܧުqъ $ڽ̬6?@Pmmv&ț6n0U0&ćYK+, @W]fmnȤV3(_a#){'1!i|,>Z͌ 6j5c4nzz: TGk.Nȱ:pNC4.;-[nȑ*rV} DlE[xW5 %].dҘ\+8Qq>>{W^i4v90(&y@Z*NCpRf4+*X3o0Onn/-&?m'RPA%0Z -;z-o:aQ` /(UaGn21/ah{9?x4pwdg{:!ds͊ʅh!HD]"tFS ,fױy=N6FClJ"7Xv`%WЎQEKdB=si5ޖM.\W7`," Ȇf8e9kB;㗟M$< ̜9s}lyg_)KҡH*kHݬیY@ fh8 2mA3w}z t`Wu)g62Ò P2*ϼgY$גԠV^pG\;`۾ZON dfϞy1C{i9t+ˇߺq hi@xa⼢  ONйz`ߗhsGlczE Q 0i#СҼh{Lj JΝ;֛Ơ뙻t&-`% W}2&P2O(5[nQ|W#m-:x$J)i YD!ϾwyXw-բ$ :0 [;dˬC޵W>zG  ^/SaURC7.%.:v^a13oh x1}-4³5,T=w 9H=GG )Tcy :)c8SL1Xh>l?SUz?srS{,8y7|{I ۏN9%,f:6j^^>A*P*Ra{]`YlLC^? hP(ԡKq@Xdf1R([#̛0J1Pb RԾ1kDZ9VYuոZ -VџG7.r 'ڎ:N*SW9zOFT/?/4Wu@LiٙX;}j2p&Gklv3Aє-YBvs:W) T@-qQL9Tzy:edr,뇛lzY~t% jZ+΢8IY^$nzFfQ|)!J/ͦf֊]!c3qvl^^uAr0t}iʲu*X>J5E1[n0-H?SlYo ckL$+fJS S!!ݛ-{;U%cƍXs=B﵇UI}[|Dæm^6d~G &\Z&ٮWl$9P'ؓC$ apJ@$I;twˣY;&0PJW.7Oc$== whꕸlQA~{`m;O#:szq44 0>dɉƔϕ"Yq+z?8?@;Կ-j 4`U5X, SrC)^ nVܖ-z! Jf˵C)Q$R6PCbԊs96 /qmcر|gv=~oK,{ nPBU[`ȾݪM}YoQl5QGz/J`YE@9c V; zXj*=_`͝޽{`!C]-y,zLd?bUuFUE^TsP:hqĄ>39>&$ Q>vH$g]f? kJ̋Q)J}ns'uY.^m+q^y}wǮyK'70eĂF-ҪFЃh% g{8^g̘ѫW/d3{݆'kWJg#o~}A򫯾 wZMqucAYHvm;W[hZfT߂yյڡC$so۶-D@Dt?ݍ#k5a78+2|rUFV 1b\*]|eWAQle~mZ#Qiii}^@UZOvvv-r;2pn+Ƶ{ٕ @ݗwħZU7ZgI>mȬ*jlO#v5Uk@}\rIt/0. |H\pu:zѼ/ XTYQXYQPU_*YXUAKP^z0# K]o8 Kj9X* ~iF`6_]\u{MMGj7?|߾}fMS&nE6Xk_`t(Z[l})ѼUZN@M>2 mڬE_j|^^@yi ; 2hPvx Gr(ݠn~ѕ ᑙO7.:"LjD콲 b*7+iSːMX"G*7OR {+V>>ltM™~,#C|Q;zf9MY+ H9x]^Òq@y[i;uWߏX1vfn73e`&uQq5G… }vlk7`p,iFJhc[q*!.Ourgu$}]3H|jBo&*xY+ZߠDj(wn}"S!5]QDN8鰼ōF*R4y읯#,F! ]7 ]& }6xxWcޞ']vT0G?g^~Ht=⦅n5R.8]R27Te6d<P+@ /*Y^52Oi*M.#k@/=U!ujiE΁Gsy5xEdqHZ(z6 CO9]M7&" bԤ/o /o!@ʢZJ?y _|1hhN<{S(P`2x}SNOM8#-pS)w7ߖ`MSFnqS+lb^!zlMu >+_miv -k, (299zюJ?䇓`Ve{r `=Cnj\lRT$\:EJt}l!Ǫb†RWw-1Msmv}WĆ-$-ޠUX!Rj A< DsPrYe/|û<{ھ}zcMGb- 1c :4rýw8ԲVg=-&F$ ɿۭ0%wH=h֍i/\XYWWsuqvURMI4kI73*+o{9_Æ ?9ى 4o~(HJVZfִ<AQ 2TB$Jp~GHlp7oQ;tѪyIm2'w>1wjLҾHD5:k9BIWaOm~' Dϛc_|UΝ7g!k9NG a%Tսۥ?^Ïx_NNp~E>x$rG^oUZi;gƞsUTT@hW=DNr.o:"R]zyJZh^q1z5~Bb>9D^×FXYQdeywӢnF ;K2b^8EjdUa,fp}dGk/aKџ%&9N씚1fd훈:2sKgBtiR#qYhJW[it KJ/yW xe˖_oO>=i{vQ_rQ,stL2a{^߆qBgfov?E, χ*)))A~Jn&[lfmfd? ,*]읢^_32w2}.^]իCU@xm;v4g-.-_ZC~|@hSiw`^TIj AV7> {֛@ ְѥo xPυ&W0:eS. x ONR7(8'\m?Dw=qs8'a+DbX}~al-GCvgA0 \Ou}KVgR{e洀z:!?|b~QSdee)k׮GqD/ob/cZ< P!9-vH'ԏHiʽ[_}pB-P6m |GM2ykJB양ӻU_~gAJQ`ӹs /0<4 uѿWӏ*R2q#fvm̙>}sHn>[j9]3|IRzfis,] t/"uj` :+ǹ+[k c R?+|LZq9YZҬթy ǝuٳMic1 >G/);a/Jp߽֭*U 'l^AMI#)IWRx4Sԯ?~v>&N"(}l y^gB0$Dϵ#'y;OMzg{Һ&g a8Eߣ۝7|$:w +n45K!-Q%qP/oڤG0bS$ ;ν݅~G_r__WR\4 inyGFߡe| 1^0Qs3F,Džx_Ӏ36 @kC!,*-z|p Df>tޚ7ּy⯤n%!}G O._~ƣrK B5҇z-I+*/d~Ip]j'~^AkIIKͫ(]QQߢeo򢥥EK+ V-? 2g/89a6lhURʹ:KI7u@?.]f ` C=Ak+ңG ւszlWQ{ewUQPXEQq(YV-//ZTY|ՓZ/^ԅU%i--( 8y7E;U2ҭ5iW:PYk '_Y]Z-$bgy˔U!ƑXBN&qa.2#!1; 0yCGsd2?J $&$}XN8QJJn]2A6x7W6yP~ȅ;js($}`Rz]jJK ?S⋵yGfV;e_nێ)i ,vuÉn4+/a|3áLpZ-yo oe&%f%&ҋk#j) HUQ&íf'z *rX' /A-0D%:vulo;}jqO?f)$$) }I$UÅOr1s'^:5k٠I52~lP;ࠌN:n-:lkF3\K휞yv-Q-@ D&ѥ.]{dlz c_~>9֝4L3 PjSW/S:KnߕU iR*Ebpev)tR ~,wY~>%Q8^]"8 lz}{'x"husl5 `"(@?pN@y߯Hrbgנ\ $65蠃m޵W-ټ+q"VTLaDPKʕF~Tpu bپϝ;Lv`Uj,3/h~WWL"HMBU' qW}(`na\Eе[YV"a:baxۦ\z}(MZX@=#/r6'lˡ32_|][?PTw|<5\5bhyM(Ǐt.cNJLE믿>*߯_;"H>jIH0{VQ/E>Ͷ=U p?@LE?}_2z&R⣫G'-XP?u9A{{dSl>g?xmPG%of_| *#h6^9bOĬ7odpFծtʼґM3=/Sb[ת \2-k 'k W fkG%*xFmlܾ5eTh OyJܾ> *2&:v"B޺~1%.Yh6:ll")ot?31`aAoΟY*2gQW S5 x a*Y]TYټvu>bVا({/X_X"`?KC}ؓUPMۄK;Pϓi>,lӡ&_P w{BKmSĀ8*)`\ݞ-i8{{*x +lqeG ?=6O>"{Ӊ'Łżp+ԙTS[t?s9s_]YtaGRz"&m 91{#&{:je vj&6{wNmCjeXMCd5 %+&,.E6A@խh#~YOfߖš+68[y)'4w8>Sc$fWJa~*yRJD^C G}4O C.K_X;3wuaJ܄*IWYU+̫, Ӄ IvNkgF;yf  ܿbƊKҨ^WytCy3+Z $A0+Q fY.$pł0?&8 pNiF3E)b|HyWƮGJS ̂L@$$rSࢪ$J̊i2JFNQ Yu^ԛY\Zv0YI`Ibak߆ݽMvGy8=H|XBS9]CyyK s.$a9{;AcK%zGL_S֭o} L^o$ 4=%$܁[ֶ#X }j²ʢ5[ak*軺_v)ؠK LPwӫBI[<(Ms-`6ՃUX=}t{ߌ̀!.8vnaF}d_ L[5ceE?x_[717wOo/KbFoXe>_]o~WU<TX^Q@֐?"B_ͩ=v_>G $"@셋vV͕5xW_ȩNSvЛ((JΏz޽M:g9G:aRł)g%3ɷxR\HᄄScj{Bn:#fFѢ~Wtr!O VEl7t1N UڿmRR9, Gu */r_fLC}w{\inEcUalVŨ y- W8# a}W{wa5*QvUU9aqO,[+H`IJ,([J^*05 [[oז)lh*Gϯ9L:%!(ljJ+'S}Qx:+K2ĄJlAs23ؙW0X}_IAJt{?2rUFVkVO7 dהbuEU(kXܑeH1DiUJK&eo+g{^"ϏHph/@P&Q V]DlJvؐ 9 `~jBC%K澷: {\%.. `|g˔VW8A_|-xbu}Cf%jwr3l  Ǡ~)^sڦH!@jj*^_\P7SD bK1L^8y@VV(hf#R)ûAZ_̵!*HR V"(Lί?ғ-[l4sJ͟?[jHW` lcw @050l5} <[-b""zNȠ$WЮ[/Zog3Ϙg'?ȁ%W%*4>,v!2/ہɟG:5].#_~AJ{F-O5-Ҷ0+㏟}ٸ-Ŵ]w݅^)ٷX=q[eWE08SO91l,}veE~utl m~ %WL2hVtN$ Jyf1c{1v LR{E,&4e|d?hRBRVU*ل>LeݪO|۽|{F:F#ѧxҤI|9>f 3˸%i R\"{؁gs9&}f !`̊K) 偤 yKW\qXD΂zSTY̲vRO&d& >-80D ^>=3iB8yCO4MU~_q6S `:A'{ڇ2KaMh3@2τ}|A0iQYfM.v~QBD *С[m'4nq5ƒk&G/(*GSȭ,Fu(>d gT!+IIB$E9W&P_ d /4Az߼"V=*cgrDc^?n<#r8-OÍ7CFb~v9X*>1!wU`)ྙ|5_\Myjw}T'#=̚Oym[S z+GT(fbw̹F^ \uUfY{ 8RG Wv!gx#߼Y3䐝D\opcH7S^ i9ff3Z 'Ho$8K+$tI uJ[.x"í"y7ӥ?|z6K"/b0+7$RwK۪e#JoqsW: sOCL!,PONwvB7=%;mg?#HHc.vkn|M^zh_u${慕4U Ը jɜ`- tOAVԅg@-p U<~HqBf=ف^OӅ{)(Uꪚw)l2O "[  K [\PUo> ͯo.r4sMU52oMU."{sWfRWWҷ2ou|~Sџ@a1Kj?kSNÃ{٥@. C]wƴ{UWwMb\и (%UyU@mhX`)(C0P"tJQ0WQ|4;4#hdkojOT"R4fZ 00ؠ3V|zTNbͯʧNwriST%fɯ,‚JbbsP^#g:\@,@a,2gJe"Lד<{ f"qO 3( ZW_iԛZmɓ'nnٿּ{F5Jis_RNpn@pG. XJf)F=Sp~,!K5|}9n%]w/ ٹ}Vbj<>=b޹9 ժKсq tpb,yx 2iUW,4;zX`V Fv_-"oiun?J+*!$r NtDaCs'@$4>?4Eп@b;2eTjk:se PpC;t-(+-^ *K F_baQ| `vpRX4寮*r7@ٍ ` vJ(;KS/;RLJi'e  G}Z' $21_=K0-z1C(8:s_`X#@bYҹHqHfJ>Aj̓mE%S#j8]Ma`ʮGhRT~,Qf)?ߗ1r!O1*0\?|G= 8aZ2J˖-QTe=ͻ^8016‰0BU _kCB`H|1cs=7 xY"7n^ :z6/ȗL[Y_&lUZk;+12hK߳K?a^zc/FA>3ivѠ.}qRG'|rBj AF_s@cK¢B!a[B`HGD!f_Pu[PF }Cj ? 8t jxvB-{~d_@5oϳ;[slq:TEA4؇@mo|e? F3ze}p+B 0(]L*@imc޹yp'pꜚT@PsALn+Ċ U@gפz.=Hd< DWp0VM*_L.])21GN =@G2= 5u#u]ċ"T _ȘNڳ4L q31urS|a( `@aV2oEE^AeXaCC'<7KnmrR7G12d?u[rW mJ=ƊZ2/"A *yeK^=#$'gž*C \;X'p "^`$\}1̋s#}-@FeWqj>h)P[͋UL `l @c} [ &  k}N 0tq I~.5S^bK+AAK X"-ȳo6 ǕGiĊۭϔْT2VKr #uh]GqP(O 8#%\LZ5}qʨAx-?R|28Kq1,ֱkQKdv*2#}^T@YfrԞ`dX* &omi^:}û@Y 3yN1 O>ZS(>1;iG"H2(`̂E>Xr#FZN{] @= ?$:߬I"HEUfG ,0#IrϞ=80H z5<@8Kmٲe K/2e >q9Oʅ-D$dkœ.Guf"&b;zӲ2;gӧ8w~ R-h%M>yҷ0v#dMKVMhILпWLukߌք[6e*k>XSZUmz ζE% Dk+`Ƈv|C#O( @('Io}MQ4oc.QMbkۦ#?+W馛֞^C.(N%\d"fY @mis rC=x:HԃLq(Q}Ka5޲э`S6a,#J0rOWHVPPmcǎȉ y>Â*ghԝ< z/ԘFeyE L=fݶHn[Rw[!fiN(`] v p/[tg"W@)+D2˓SwFQNs98ZWthU;OKټǮ?WVUVTV w +K@Dqi^eiUL]_B.^3_(Td5Ҡ3_Ѭ5h8~+ 7ues+!#Q"CQ?~Ee+[?eWѷk-o +C7VD^^UI],Jϥϫ͊܊5 R"{8%L }!Ӥ櫊O*G$m@IZ_X}ze%cU_N kv٫o 8 {aAGT'DRO$5dѿ=Id'99axqn5VޟESm_O }_S )YK_AqU)F'Hވz= *3KO\. b rrA8L^ZE%=lD?嫜6us!1",_EC.x/?2_*:_>93ċ{k\Pg8D  is'/C/y5ZϛZC4C288_3Ok[HjnlMl\%31,^]N7֦ "d[Q[_.ZKF~q՟Sv}i0i: e4P?l€ YUzbhsIRlFWP]aL΢d*/l0VY+6  Ȉ ] jf^Z.(X,+G0:A A' t*@#$/a(`yvѹ7vFyIA>9Dž,`: _`eȮ[G~4~7' prܲlyeXY۝ж*Oj_VۥK0s$ ;SkC% 8z&4/Oӑ^1KmSŔ=kvMxrqBZ_![V4jlz)>yV}Jim"lͫV 2NՄ L t ,|uᓞ))٥/;Y&(I _.aajhۊ*K!!IA+'btHjnOWD ye V)E$@_"v*O`̴~Sm'܃qT9m4(.]+ɖG`R'9)XЋ*SC q'lJJCP Pڇ5{(HʅF^U)w,\Tr@,*[SN~_;L[嶄65r,8rr0|x|Î%7@oƺʺAjz$"WC}TL1htWe/msgSmT+ּ NV>!|KJOn?9ZvQ{)pPFex@8a]LNCakSC5. ys.<͈&GpEFJ2*V.eZ>UD Ō? f]),-o)5)J53--e&Fa2LҖ"=(4P]Ϟj!S}B7Ư4phGo1R~G}t"=>z^il"W\9#|G ^tAg,5Ǽq@=4A/lFx~)&q@\yϾbZ٤7}Bi-dX) +.\+TI7.KQ@GgMC_3~:5K[]y-SW7mX_Zַn_?oɌ)9´e ZҐ סU%Wu9nɷ}WhiC`q7R}SdPuΚ~XWmHA & 3`.8dxV2" T=SN7 _@_Ɖ%C}hԉJhQܥ{I!ũagkF,DNd9Q)H7TWؒn;=C1>YEvzM䣈gPFͻ]'/トhE wy=@vMOl}QЃ q2-3_p^:Sy Lxyz2&t:T=cӷM[ﯯ rR jysFb?#MFYPGUBVa;EDnGj@i\TuvEuzM00 }n#Ww.xôY=r3a`„3`a:V\SN~}vc a_5$Hכn?oM҆v/2׬Ѻ,: ~{f z6۷I6Am|q`}SAF\dY;ZuUjxacZ.:imǣĪMt߽i|}&#L0l%+]*!iN1Dod1hV\uöVS|v9/L)Sțl=ע78Qi M/rr m Zm; wf17?*[_7qr9Z (A!W$uP*A SrP{Xcz1}E&590L]ϡT>32ss͝^zOܠw}NjI9SN7 ,L!xod>~c4YwB\$?jMJYep*cY>7]g'$% `uַ+IfPX2tE+ZJ(@=QzIgsZT$޾kuqn\L gN,|#B->7E)2c7|EiV-%S")8VV}llzG֗?n"p ۜ,ԼS\HygBw V+/ VZQ(X*GK _A|03:ÝrQlҽdܜU ]l%XvM~{Cu^yV/IE:!B{x%S;fU&uˑX}`o䀄np;g(K͒z AS(]R-У0(ھybrUģujnp`t(䇧 nw_8ָ+ :} #cq+e6|`#sRݴ>v؇960UI3a{!3J̯Vh-E6l[?՞Fm-%!8Ӷ]յ# !Ƞ⫭vcO~⛨(XcXm/A[7S]Rb $q%dK/Q#DR8=$b:8ѰKB 3]t_2/3uT8^R?~n`Pzؖp .P:us3" ˸@$(F!%5'Bm)%HUO"T&'ZPqQ*`E⼔ 2n7ܲ c\۩\ˀlPÖ@&L!зo_ItWpyg |pNdCm {ĻPl{Drȷ !! !*QE"#} TﲀH_%JB22qy}| od{( h) dJ9 &Jv_x"[quV  &i2 >Gasx|wZ;~?BI-v~e&Ie+Tc{s%m}c?d[)kn=ſ~'12jO!{TZfITDF_ܩ7fF{24G. ͛:j^a1Vo<%VB+B\w O GAnHrO0RIMi>JPZNn%* \UX׿.z}ر5kvGeK8be9؝$QI.|'uk7 衇_j9 Ql=䓠0Yt}=۶3NE0E'qqK# Xs n ǧ0ǃJX҄$0T$KïLX2Ĺ&v0\bE8ؾ}{9oSI`Q"Lꫜ}e-+rXVK i&?" (b&WRM̞ r x2< -LdACKc=J\ [ )Cz$D uHT )D%ixy홭IN4zl0(LNca/CDtUz' ̊Gm%42PWǒU&=.ʕ+mڴ)/ l(2-[^jU:u됋ZG9FCdcC5Tj[zmT+9}EyZ.ȗXqKݫFP/YVje79iHKg-:v.RRGKQ7 +fODяD< R#%6' >tp_N+5#0qPR"h Թp'r^`I̿Zkp1#4/H+&%uXۋlܿFSd_*;bQ|M7}wf2qS?ߔS:ZzA-#a" #-zZ۩4v*lVh䥅lE>8c;[7~vv-ZJˣ^}Gi8pCԠ;@o#t-OXYVM;h9Rck?٦‰~YJ7rWE |(gؗZCɄo^?QTy#4NE?r} ~a+?Gtj~/qÆ 1I&rA@P^z}fe3uD>1T'ዑY80YjLw>bQĆǧ'œkػ}bky!U;/?13!IZ _4Rd8~ޔAb9~?&+-%Ǥǡpqi5eGZU@y2@C}2؋.jw +HGT@#<{GoXPǔxyrBKB*}3g;3h͞ m?y m$qV kbÀ^"WE1ڒ+Oru]3}r7BnXsPX8t '?o[3-cf$֛7ǐtm;"jB@C!i.r5mֶU_xLp(4H(F"! ]H$ZBsT Q2B܋h?,X۵&k7{(0 AmgD0o|Og\a- "$_x>- jG#oB@C* @,uO0eHe/ِptۿ;F?/52x *VS;D浱ǎظ+)[dz"{?8|U k.jѹң}~իw@QaQW'~ @?/ fڄ -}uCL1ie(ЅOď!R̻|ɖ{caAc͟?/Oovw8&LxGm=A[;}O/C{XCq[u A[YBA) -rk7[-FFGOchOF7nEn$Աhg۾Zlm͹,RbAoӾkd1Y7U+(zi1{ṙifkUk7(KBmjdUT32j_onvZFnI$^UH9oQ+a$s_KD2J_-HYɅ<=P}I}$I!<#-Fϻ+4}|?u>,?8DRmſ0ݙ#"#}6b.=5~K 䉊]5jvvzޒ\Xh}~u7o0 b.=]JTUOU ehĸMgw^nf-`}qcp!DDDw=ͱWvo?pHnHIIqP+UvdZnN8,DzRtdq{Ͼ""˾A bhSq+jô%vK/!b aн{wdE6m)SJ8eH#_z+j xWܟCo{m8skWh(v^ʽۿ6qCmY&q|'M:+T);uDb!Xp9_~٧Oe pJ6RebcyW}oY{7^$!URl4Z7q'^P%4)Ènv7o$EP][ #e}ߎin)^FO S! A )㋴iO:}ʓVo$ 23B`e#%⬵/=OuX"#ᤝ>LhLLLƍ!#PߍCnܺ~Ya.^+O\5t/`UZX#Q$o@(IDATOK|4duo# ;!q psرΝ;oOԀk清oWj9%tA:L!HjWKR&Yo2}0ZEF' <=[ja9i犓jmv J]1 -:/G#cE¤:U`[$I=]GqjI dXM)8TlԨ'|RZθpBclCrW-cB=~geJ˔ZĐQ}PI.X`lGpJdeҎAJ 3#b2~TCre, Ȏ\Do~!pbA{`1|PE7Mn:m#'srȗE ԃi HȄrOyLTF7"kP*ˆ0AR1dPR7룧|5Ϟ=,:@MI zG c%IIO~a(4ŕ5:?eiO1z2diLRv_ּ=m6gߙTiR_Kݍ\ /jGqKax=4Y;pκQD+U{7.& `V.OÆ  t:>#FђV,p!ֱxW}bU!ڴ(TFƠ6ɶ6!gMwgX///unm7b]ZHo|aO.cϴ ?xD"() Bb~u\ʦ ]9g?[.3f̨_~A@LEƌiIt{1ʭ6[¨a v%! }j9Z=uL4o>זzg s8JJR_%@ ,Żx:]][kI5T(„6GFmt_I殪Q+XSTʹԧq/J.[gϿ=D |o+\(b@D|\r#9{Ǎ/MJM#x(ĸGf*§d#b*4ڸL i1=1G'T])lБ) ݀tAT)OA*'iK\Q.(@zdۇCjX *7eˆҩ J B*% 'hTffeMq폖Avyܠ+pO?eYCg.”4 }Y[D9 Z7\&K-*ȵ1mrhMac`N \GE?|-~lVOzoJ~R%=tŻ.Lt);slgο3#2Ԝ"jܨk ꭂ钤  ӫ#}tw\\8B00U1Y 𘇰b֭`S)}`mo?;_J*Սw{AoMyۨIL8 K^"֢ j\Zԩb!}E2[0{Ѷm[|Eo =,B?NGL4 ҃: 8\ +4 ȕ6-rBR+j}cF|FVb#\HרpȢ^Ifjm\2Ra#I箎YM@Bm~;IKuF5#r ʥ 1wƸ&DSLK4 !oݲN<2ن;o*t Sʔ)3nܸk׶je?$u y.x9yh#'ن13򋋎+ PZBnۼDuU6 #Ud@7$mt&@I l?[*IdGB|E~<23NdF4l 1n5xYӂܿiӦO?1Qa@(`F{ MZ~hƢDhb~*rm(t}elmu \/D%KptYZ#PO,Ƞl#R;PtGnr* :2!+!m܎ag r zꩧSbzC Osc`ػ/" gؤ :^+t ?e /+l4䂖pozرc@@!ƫ﨧νfxo9xƢW6-8+T?_@O>p-܌\hs%T"<*.D75 7߰B择u GLڿ?D<6~^ 6t#ST(OQ('.)*Wy\,~ tW /+xG ;GLkV\aO)"poTAr]~ʘB*fngfV] 8Oj׮_sNՆ?,Bkf ج%.6 G}}9?/Ͻ]R-;06_y|ҷo yx"pC@cG?hٲ%/]ruGt1`˧Df g6:\0""rβ$EK@tg Law3la>!0=_ܟ8c(Rn^Hsk65랑l({ysV&)T, fZ4|OͦD)d0m]v&C=ͅCT6M;$x}mUpA_xnmk22p6!@Ʈ,2a &L O?^cŽ', Z`}v:{ȤЛ<_|MvX]A^ ʕ+\2s^*4YuWP*{PϧT9gעL O,IgbŗMsۆ[B߆!X.F+s8p`k6{ 9F.dڳgOz~GR0AU@{9MzU&*KuǠM?y Xr=Ȗ.:ux #PR()$RAҠ[%FϩYjݶ12qOZ֪#l%[.l_nQR r;@#*,yW_}uӦMv킘(/{aJ @g-+WEz;rtI\rzڬsss[s9DN4jժE!0B@~OLEH}ƍ=UR}iYMƣMzː!C~a96Z+x%t&M$&&#\Xo-7k'}V/H VQ 9~ވ{*zJUi"A}4o޼`}1D@0澂@ZZ̙3!"B0mD,}C'̙繂G7A "\'$kڪGB,8x(Γ nx(>W_TСCJ !6a= {8bFF}L/%9@ ]%zG7u]pBݱcGBBB ;̽8:`r2-݊K~&@,e=z;M1$^;c ̝Ŝ6maOeFCW$= 0~`*o3f 3f̀]E]1@@0P>|{2|5iu[|RRǏOOIϷ /;~[I/7hos#!ڶmuVƷ84V0``,$ #}YY5jW=1 R.+V039d$bǛok z!ٯnO|Bk@H=Mww@#P 妃)S{hjF/໾G ~l @h cJrXfW\aʻ3r_h~p콸* t> BA,q@7%Kzq@ tpVC_n:5=m{$`yB}!IX@?("Gwy6!C 8s(,yXcOt%<`p-CQAG!$w((Q8!qN:`K?Tn*xZ *T@BOc 9206)DcnC@A 0/M@.z߽y ^=^aE4J7P<@4` zK^w?xkFD`P"0r#D`[Y/}{gWpa0p:9q `a ,%g=V`(駟^|š65ӧO76]R2#>} #=I 3W)–PO;_@&B޽{!34[;}v_<~y}@)-x/(,&n -l;ս@im߾}]3" @z_|7;mWuT@bal߹y C<~UC?ؼysH 1Frw_ G2@),S0M)偾뾢lҢEP3N8@~7>UL\#Ώk|<l¾+B~ oB8@Ħ|mX&s!pi`#<جY'NhRa.QA勌q}4 Gܑyy wGg!!:4ĈHa E Ç753~#pp" *qȾ=6Eޯ b$&&}v-:4n=s4@l74?~a0EB@`BڴiӔk?aeLB[Wڻ{g}Ų2*Uξ^,[IZzQP'>>"ԯwT(]pF 0q|I]z:cؖ-6NO^Ჹ{|s:uuwz`B Ow@J~A?묳 -СCis'@.aNo|g8UDsJNnDZ. E^{wݱctڠA]3|= !0a%fGiժ(ڲY$1w =¬Xvm۶UXQ?<|0 [.ă t \,B=\j 2v2٧?."bA2I*5'<5kV 3A@hp^KXC4}>yyC飌 GQl0BkǮ/-x\vY3Szsˌ@0`/`}%KܿNƗvx01WBDqy ]P~ϡ Tgҥ` ]u֙ 4;/G$w!!?T|+g&~ f prsPllP0 ŗ[2lºӲe+K #͗HsO\i~S}>a Wf\P]7͚<.( Xfصks r= ޓG #} _Aa, uS,4qnj@) @&C5},3}.V.?nJl`[mww2{*ڊ]Oۥln۽:{7XlrOL3<RO&lFuץ&.E)/n0,y+Igr=Æa2cm/KvMܛK-a>rRlt`Ĵ~"xyoGzkHqܚ\2܊FlОu12kUJ Vؾx56ОQ?Q{g0m~ST ,\My-ehg [(IѪD]S%8 G ߿gdeq+Ȕ4O=Z o'"&V]75:@[ʔ%e_)yJ7"(GżCZ1eLa]p /x5,F 3=N.Afjw2xw7 twHmԱS}(cX=-R(Q4/R$̊|=߇UOURBIU[*v^"uPN$UZ"Pt)jY/A`_1Ȥiz̓!Vn#C=ٲAfevLB씱_OLol8~|̈́>h%gLfjh$ԥ VT׳Q#P VYSFP$pN/VjeP>n dQGqXQ 6QC9m!8J<(&]6~(Y͠Fl)l~ Q8t g"*:mF"*=,"XIjjM"ϣ]aRW>/EU(d? E[#3)M,p?hOP]OG4xXqߌ\ p>8=w+;0JEsD  (G0FjX?@c(s̅`pdgl tYfrHNZGF(+ ^ԃRF\H9ZnIJ6y &d r66e>ZE2MC tmgGVMۋIW ̩j jsuX]ݒ4cYɆ϶VDkIfeNl7jbf=*>3TVtnJmkԈ?CKJVQd @U^+"m sPf+Y 7\  2<ttU*5Ԅ*dv %#=xcFeW Z\Vjwο{6HqO 45>Y"]͖*[ϒ.ؔahGu^,9|l%M7̉\vځ]٘-o$6G^] 8%!9WsTt^y&/r`Cv)uuN%B"!B"D!B"D!B")xW ](K' ܨs񒔩PFPGn''U$7W],PǺa g[wZo콗A{!6&g7x5$h+Ҿ?±J;C;K NH t@^S-ra遄ْB@˭!;D,P5&!tjk䐷uy;LL0# X n4$$ۇELVdq!`@]ŠNm2|Y,B{,jQ!ۦ^gyh[; t )!`Rnbf`hb!Y CӾx%zXc87Ljߓ;t/ E(h ,yYޡb|HK:*+TP-Vl8r#ݽq˴YVYӟ?[/χ<‡<;OG÷G'ợG? <uѓGO>=tӋoo?>`|矍//>_|97l?WL_X?/g_>oχ ? ? 4e|㋏_|Wb5)UluH(xZM˳ &$zh0B'? 4b!BChl"'J7l ;[RPgClpě3zm0RR=\<%%u-%8i8E ,arK[HUoyS nq&-| e~Y-c[RL3TC:3xhzя悐^VU6r8!6 !k,i--}cjt964-Vy툸W@-ܹHoϯOoW.YTp2jFwo7ub<ĵ)HPnoϊj bGKhmS޳0ޘ AawdVtZeX^*];6M5}WܩMe6? Iy< > ?nlؓn"s4䝬9xAg1}vbL7-4gvE:(&|!}n6MtN\9aZ;fZd{P%$Q#kwv[nj 8VY8B@( 8t*pڠ WƋf85dL=WӓB`plRf~)**LI[_ugQݹ d2ӏܞP7TI?WQv6l8h]N;m1Fq0rPڗ֥&uZ`۵Y?4|A76SU];e f6vCӁ]_#Mk.lZV~-QQޜs20Lc2'eV`kZSܽFiGG[`eխ(B{2s3lqM#ÌVtH9m#[/ړ^ եDwwDPvI 7b)XX\fpY>{=6OAhD3+Q}{TGQPTLP)& A*%c7SzZr dQט(#R.;FYǰ(lY!Z&.;e%J&91זn@L\ |]É;Dčd–n7CD EݬX+Wҭ`< ؑ@蛷xѠl"D,98a&MWI5}W&;3ϊTMJRnikEPł)5&6ڒKV78CaV4kqzO4|"gy'퀿4i:%; llpZtY!:iR@Z3CZP:7pϟ3yW༱'Gz̐GHo,%ɑyVHw[~9T-ɑ>3#rZJH Ó#=zfHӴb(<8$=~f0U8o,%ɑv; ^ׯ4uѤVq~6E R˫e ZҦ3Gn%?O(znۢ/2]nzc5)[] U'xVI:fK6 MHCPWp< BYl< ^#PxpXA9à+iJы`a xnZiJ4G Kb-GH{ŕ^yKQ% %;:sدaʾiԻxSV[ioV[ioV[ii7s쭽76+MJ{X< Vڛf` ؾoe`e ,Xo V_+|3X27C+̷2|+̷2̿< Vf`e RNFȯ``;nzkǠa"frwdlP`f 0\0nZ|8iH5J4) HʎeE8i@h2g~Rzbt%8 #H c `ѸiBl! _f $>}0RvE5kjw[aEWm6iөb|l?s+a !0Qe{<>+{z^8>[̡f(. ^јmڳu{{ EmWߙ̣KU>hi?PK PKF manifest.rdf͓n0 barknikk2009-08-23T11:58:002015-05-15T20:12:27.482015-05-07T11:28:34.1196P3DT16H12M49SOpenOffice/4.0.1$Win32 OpenOffice.org_project/401m5$Build-9714Oolite created by Giles Williams, Jens Ayton and contributors © 2003-2012 GNU General Public Licence ver 2.0 Creative Commons License: - BY - NC -SA 3.0 Oolite website: www.oolite.org Oolite Wiki is part of the Elite Wiki at: wiki.alioth.net /index.php/oolite Oolite BBS: aegidian.org/bb/ Oolite is inspired by the Elite computer game series, originally created by Ian Bell and David BrabenQuick Start Guide to the computer game OoliteOoliteReadMeComputer GameSpace GameOoliteEliteGame-play Guides.Konstantinos SykasKonstantinos SykasPKFMETA-INF/manifest.xmlMn09NT($F]w1Cb?G5%jI5gr}*9Қ,$`-c>j <"s_ʂgr˽|"5Ҋ`??.@FVURA]wk(%O 4;-!"߮RiPF$*Trٛ:&sD`Z$CPKF)MPKF^2 ''mimetypePKFMConfigurations2/floater/PKF'Configurations2/accelerator/current.xmlPKFConfigurations2/images/Bitmaps/PKFConfigurations2/progressbar/PKFQConfigurations2/menubar/PKFConfigurations2/popupmenu/PKFConfigurations2/statusbar/PKFConfigurations2/toolbar/PKF-Configurations2/toolpanel/PKFDeThumbnails/thumbnail.pngPKF Ge -content.xmlPKFŇ=6Z ]layout-cachePKF+*((-]Pictures/1000000000000200000002003DDD2196.pngPKFxU /_ settings.xmlPKF  styles.xmlPKFh umanifest.rdfPKF meta.xmlPKFF)MMETA-INF/manifest.xmlPKIoolite-1.82/Doc/PORTING.TXT000066400000000000000000000102551256642440500152020ustar00rootroot00000000000000Porting Oolite ============== Oolite is portable to any platform that supports SDL and GNUstep. It is known to run on Linux, FreeBSD 5 and 6 and SGI IRIX. (The OS X version is the 'canonical' version - Oolite appeared on Mac OS X first and was later ported). It also runs under Windows. Oolite doesn't care about the endian-ness of the architecture - so far, it is known to have run on PowerPC, Intel/amd x86, amd x86_64 (and presumably Intel's emt64 when it's available) and 64-bit MIPS. Oolite uses the BSD strl* string functions. These aren't included in GNU's libc, so make sure that src/BSDCompat files are included in your build if you are using a libc that doesn't have the strl* functions. Making binary packages for your platform ======================================== There is a tarball installer system. To generate a tarball installer for your platform, run 'tools/mktarballs'. The result is deposited in TarballPackages off the root of this repository. There should be a directory 'deps/OPSYS-CPU-deps', where OPSYS is the OS reported by 'uname' with no flags, and CPU is the result of 'uname -p' (for i686 etc. this is converted to x86). If you are making a new dependency bundle for your platform, deps/OPSYS-CPU-deps should contain the following: In the root: install A shell script that installs Oolite on the user's computer. oolite.src A shell script fragment that is used to make the shell script 'oolite' that runs the game. oolite-update.src A shell script fragment that can rsync updates. OoliteReadMe.pdf Brief players guide. OoliteRS.pdf Players Reference Sheet. README.TXT Readme for your platform. Subdirectories: oolite-deps GNUstep A minimalist set of GNUstep run time files lib Shared libraries that support the game If your platform does not yet have this dependencies directory, you can model yours on the Linux-deps directory. Most things will be the same. Issues you may encounter when building Oolite on a new platform =============================================================== Symptom: Altitude bar drawn right across the screen, time under the scanner showing stupid value (it should be something like NNNNNNN:NN:NN:NN), probably no rotating Cobra showing on startup (and no view out of the window when you launch your ship) Cause: Bad maths. floor(), part of the standard C math library, is returning funny values or your int type is not at least 32 bits wide. Make sure #include is done in all applicable files; for Linux this was put in oolite-linux.h which is included by every file. Check that floor() returns a sensible value by writing a short program that does: int result; double thing=180058016009.741669; result=floor(thing / 86400.0); printf("Result is %d and thing is %f\n", result, thing); Result should equal 2084004. Try it including math.h and not including math.h and note any differences. If it gives the right result with math.h but the wrong result without, then you've not included math.h In the case that your int type is only 16 bits, this will probably work: #define int long and put it in your equivalent of oolite-linux.h. Additional info: see the manpage for floor(). TODO: Include an assertion on startup that causes the game to exit immediately with an error message describing the problem if floor() doesn't return the correct value. I'll only bother if this problem keeps cropping up. An error message that can be reported is much better than a vague description of these symptoms by some guy who just wants to play the game. -------------------------------------------------------------------------- Symptom: Floating point exceptions Cause: Some rhs of / and % expressions are turning out to be zero (I assume this isn't the case on OS X). In some instances, the simple fix of doing an if(rhs_of_expression)... before the div or mod operation is appropriate. It's probably best to look for the root cause of why the rhs is zero in the first place to check that it's not harmless and you're not going to hide a new problem or hide the root cause by doing this test. The location of the exception is easily found by doing 'make debug=yes' and doing 'debugapp oolite.debug' and then looking at the line of code it crashes in. oolite-1.82/Doc/README_LINUX.TXT000066400000000000000000000101011256642440500157620ustar00rootroot00000000000000Oolite-Linux ============ This repository contains the files required to build Oolite for Linux, GNUstep and OpenGL. It should be easily portable to FreeBSD. 0. Pre-requisites - Objective-C. On Fedora Core, 'up2date -i gcc-objc' installs. - SDL Development libraries. (Currently used only for sound). Most distros have this pre-installed or as an easy-to-install package. Also, SDL_Mixer and SDL_Image are required (they are standard SDL libraries, but most Linux distros don't install them by default) - GNUstep Development libraries. I advise you build GNUstep from source since some prepackaged versions don't have a new enough NSOpenGLView. It builds easily from source so don't panic. Tip: Get the GNUstep Startup Version. Everything you need in one package. Make sure you do: PATH=$PATH:. before running make when you build GNUstep Startup, because it depends on running a shell script in the current directory. Hardware OpenGL support is a must. Oolite linux was tested on the following machines - a 2GHz P4 with a GeForce 4ti and an old Compaq 733MHz P3 with ATi Radeon Mobility. It ran fine on both machines. I have heard reports of bad things happening with ATi graphics cards, but only off one person (textures didn't display), and another person with a Matrox graphics card had problems with the text. Building ======== Type: make If this fails and you're certain you have GNUstep's development stuff installed, make sure you have this in your .bashrc or equivalent: . /usr/GNUstep/System/Library/Makefiles/GNUstep.sh GNUstep tells you this if you build it but you won't have been told if you've installed your distro's GNUstep binaries :-) Running ======= Type: openapp oolite or openapp oolite.debug if you built with 'make debug=yes'. Troubleshooting =============== I suggest you go through some of the GNUstep tutorials and make sure these build and run successfully to ensure your build and runtime environment is sane. Also, there's a little (and rather nasty) environment tester on ftp.alioth.net/oolite/gl-gnustep.tar.gz. If you're having problems I recommend you get this running first; it's relatively simple and should expose any problems your installation has without possible Oolite problems clouding your view. Is it borked for you? ===================== If you find it's borked, please post a message on the oolite-linux forum (see http://aegidian.org/bb). Please provide a backtrace if you have one, screenshots, and describe weirdness with sound. Also provide log messages from the console. Better still, if you have a fix, send us the patch! Modifications from OSX Oolite ============================= Makefiles: GNUmakefile and GNUmakefile.postamble. The former controls compilation and linking, and the latter copies data files (PNG images, plists, dat files) into oolite.app/Contents/Resources. PlayerEntity_Additions.m, PlayerEntity_contracts.m, ShipEntity_AI.m - These just #include "PlayerEntity (contracts).m" etc. because spaces and brackets really suck in the Makefile and shell. #ifdefs - All over the code you'll see #ifdef GNUSTEP ... #else .... #endif If you grep for these, you can see where work has to be done. I've usually put a TODO: comment line in these (many of them are not filled in). I've not #ifdef'd out any methods - I've left at least a stub if there's nothing to put in there . Addition of Comparison.m/h from ObjectiveLib (see http://objectivelib.sourceforge.net). ObjectiveLib is an LGPL'd set of libraries to add functionality to GNUstep. Comparison.h/m implements a category of NSObject that adds isEqualTo:, isGreaterThan:, isGreaterThanOrEqualTo:, isLessThan:, isLessThanOrEqualTo:, isNotEqualTo: methods. It looked like a relatively simple category, so rather than creating a dependency on the whole of ObjectiveLib, I decided to just add these two files. Sound uses SDL instead of the AppKit's sound (the sound daemon crashes). Graphics use SDL instead of NSOpenGLView. Notes for the terminally insane =============================== See PORTING.TXT - it's useful to read this if you're tinkering on Linux and not porting. It may save you grief. oolite-1.82/Doc/Version-bump.txt000066400000000000000000000014021256642440500166000ustar00rootroot00000000000000Prior to release: - Minor & Major versions: installers/autopackage/default.x86.apspec installers/autopackage/default.x86_64.apspec src/Cocoa/Info-Oolite.plist src/Cocoa/oolite-version.xcconfig Resources/InfoPlist.strings (twice. note: UTF-16) DebugOXP/Debug.oxp/manifest.plist - Major versions: Doc/OoliteReadMe.doc Doc/OoliteReadMe.pdf http://wiki.alioth.net/index.php/Oolite_Instruction_Manual#Playing_the_game http://wiki.alioth.net/index.php/Pilot%27s_Reference_Manual DebugOXP/Resources/oolite-debug-console.js On release: http://www.oolite.org/download (current version six times, previous version for Windows updater once) Topic of #oolite latest news at http://www.oolite.org/ update changelog link on freshmeat.net project page Submit A Release, freshmeat oolite-1.82/Doc/contributors.txt000066400000000000000000000030201256642440500167450ustar00rootroot00000000000000Contributors ============ This list is in alphabetical order, and almost certainly incomplete. The Man: Giles Williams (aegidian) Kevin Anthoney (kanthoney) Jens Ayton (Ahruman) Nikos Barkas (another_commander) Andrey Belov (timer) Nicolas Boulenguez CaptSolo Eugene Chernyakov (seventh) colinh Commander McLane Paul Cooper (PhantorGorth) Dermot Costello Chris Crowther (hikari) Blake Deakins (Cmd. Cheyd) Griff Jelmer Graafstra James Hobson (Cmdr James) Influence D Kaks Olli Krienke (Svengali) Lazygun Dave MacLachlan (Jester) Steve Murphy (Capt. Murphy) Chris Morris (cim) Norbert Nagy (Norby) Neil (ZygoUgo) Nic No Sleep Nigel Jake Oliver (Pleb) Vladimir A. Pavlov (pv4) Dennis Pedersen (Frame) David Pradier (Day) Erich Ritz (drumz) Jay Roper (Halfhand) Darren Salt (dsalt/_ds_) Adam Sampson Chip Richards (caracal) rion Rob C. Skilton (KZ9999) Alex Smith Dylan Smith (winston) stardotstar Konstantinos Sykas (Getafix) David Taylor (dajt) Tichy Eric Walch Michael Werle (Micha) Gary Wong (Y A J) Alexander Yancharuk Library credits: ---------------- GNUstep: various and numerous. libpng: Glenn Randers-Pehrson, Andreas Dilger, Guy Eric Schalnat and contributors. SDL - Simple DirectMedia Layer: Sam Lantinga and contributors. SpiderMonkey: Brendan Eich et al, Mozilla Foundation. LibOgg/LibVorbis: Xiph.Org Foundation. MiniZip: Gilles Vollant, Mathias Svensson and others Mac OS X-specific libraries: ---------------------------- RBSplitView: Rainer Brockerhoff Sparkle: Andy Matuschak and contributors VirtualRingBuffer: Kurt Revis oolite-1.82/Doc/plist verifier design.txt000066400000000000000000000014151256642440500203770ustar00rootroot00000000000000String filters: lowerCase upperCase capitalized truncFront:# truncBack:# subStringTo:$ (No effect if specified string is not a substring) subStringFrom:$ (No effect if specified string is not a substring) subStringToInclusive:$ (No effect if specified string is not a substring) subStringFromInclusive:$ (No effect if specified string is not a substring) Can be chained using array. Strings: filters applied first. requiredPrefix (may be array) requiredSuffix (may be array) requiredSubString (may be array) minLength maxLength Numerical types: minimum maximum Enumerations: filters applied before test. values Dictionary: minCount maxCount valueType schema allowOthers (default:YES) requiredKeys Array: minCount maxCount valueType oneOf: options delegatedType: baseType keyoolite-1.82/Doxyfile000066400000000000000000003036421256642440500144650ustar00rootroot00000000000000# Doxyfile 1.8.6 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. # # All text after a single hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all text # before the first occurrence of this tag. Doxygen uses libiconv (or the iconv # built into libc) for the transcoding. See http://www.gnu.org/software/libiconv # for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded by # double-quotes, unless you are using Doxywizard) that should identify the # project for which the documentation is generated. This name is used in the # title of most generated pages and in a few other places. # The default value is: My Project. PROJECT_NAME = "Oolite" # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. PROJECT_NUMBER = # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = # With the PROJECT_LOGO tag one can specify an logo or icon that is included in # the documentation. The maximum height of the logo should not exceed 55 pixels # and the maximum width should not exceed 200 pixels. Doxygen will copy the logo # to the output directory. PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = "./doxygen/" # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and # will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes # performance problems for the file system. # The default value is: NO. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, # Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), # Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, # Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, # Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, # Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, # Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. # The default value is: YES. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator that is # used to form the text in various listings. Each string in this list, if found # as the leading text of the brief description, will be stripped from the text # and the result, after processing the whole list, is used as the annotated # text. Otherwise, the brief description is used as-is. If left blank, the # following values are used ($name is automatically replaced with the name of # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. # The default value is: NO. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. FULL_PATH_NAMES = YES # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which # header file to include in order to use a class. If left blank only the name of # the header file containing the class definition is used. Otherwise one should # specify the list of include paths that are normally passed to the compiler # using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the # first line (until the first dot) of a Javadoc-style comment as the brief # description. If set to NO, the Javadoc-style will behave just like regular Qt- # style comments (thus requiring an explicit @brief command for a brief # description.) # The default value is: NO. JAVADOC_AUTOBRIEF = NO # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus # requiring an explicit \brief command for a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this # tag to YES if you prefer the old behavior instead. # # Note that setting this tag to YES also means that rational rose comments are # not recognized any more. # The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a # new page for each member. If set to NO, the documentation of a member will be # part of the file/class/namespace that contains it. # The default value is: NO. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen # uses this value to replace tabs by spaces in code fragments. # Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that act as commands in # the documentation. An alias has the form: # name=value # For example adding # "sideeffect=@par Side Effects:\n" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". You can put \n's in the value part of an alias to insert # newlines. ALIASES = # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding "class=itcl::class" # will allow you to use the command class in the itcl::class meaning. TCL_SUBST = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all # members will be omitted, etc. # The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or # Python sources only. Doxygen will then generate output that is more tailored # for that language. For instance, namespaces will be presented as packages, # qualified scopes will look different, etc. # The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources. Doxygen will then generate output that is tailored for Fortran. # The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for VHDL. # The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, Javascript, # C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make # doxygen treat .inc files as Fortran files (default is PHP), and .f files as C # (default is Fortran), use: inc=Fortran f=C. # # Note For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See http://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by by putting a % sign in front of the word # or globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. AUTOLINK_SUPPORT = YES # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this # tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); # versus func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. # The default value is: NO. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen # will parse them like normal C++ but will assume all classes use public instead # of private inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make # doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. # The default value is: YES. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent # subgrouping. Alternatively, this can be done per class using the # \nosubgrouping command. # The default value is: YES. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions # are shown inside the group in which they are included (e.g. using \ingroup) # instead of on a separate page (for HTML and Man pages) or section (for LaTeX # and RTF). # # Note that this feature does not work in combination with # SEPARATE_MEMBER_PAGES. # The default value is: NO. INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions # with only public data fields or simple typedef fields will be shown inline in # the documentation of the scope in which they are defined (i.e. file, # namespace, or group documentation), provided this scope is documented. If set # to NO, structs, classes, and unions are shown on a separate page (for HTML and # Man pages) or section (for LaTeX and RTF). # The default value is: NO. INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or # enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically be # useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. # The default value is: NO. TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the # code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small # doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 # symbols. At the end of a run doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. # Note: This will also disable the warnings about undocumented members that are # normally produced when WARNINGS is set to YES. # The default value is: NO. EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = YES # If the EXTRACT_PACKAGE tag is set to YES all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = YES # If the EXTRACT_STATIC tag is set to YES all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined # locally in source files will be included in the documentation. If set to NO # only classes defined in header files are included. Does not have any effect # for Java sources. # The default value is: YES. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local methods, # which are defined in the implementation section but not in the interface are # included in the documentation. If set to NO only methods in the interface are # included. # The default value is: NO. EXTRACT_LOCAL_METHODS = YES # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base name of # the file that contains the anonymous namespace. By default anonymous namespace # are hidden. # The default value is: NO. EXTRACT_ANON_NSPACES = YES # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO these classes will be included in the various overviews. This option has # no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend # (class|struct|union) declarations. If set to NO these declarations will be # included in the documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any # documentation blocks found inside the body of a function. If set to NO these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation that is typed after a # \internal command is included. If the tag is set to NO then the documentation # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. INTERNAL_DOCS = YES # If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file # names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. # The default value is: system dependent. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. SHOW_INCLUDE_FILES = YES # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each # grouped member an include statement to the documentation, telling the reader # which file to include in order to use the member. # The default value is: NO. SHOW_GROUPED_MEMB_INC = NO # If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the # documentation for inline members. # The default value is: YES. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. # Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief # member documentation. # Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting # detailed member documentation. # The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by # fully-qualified names, including namespaces. If set to NO, the class list will # be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the alphabetical # list. # The default value is: NO. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a # simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the # todo list. This list is created by putting \todo commands in the # documentation. # The default value is: YES. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the # test list. This list is created by putting \test commands in the # documentation. # The default value is: YES. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the # documentation. If the initializer consists of more lines than specified here # it will be hidden. Use a value of 0 to hide initializers completely. The # appearance of the value of individual variables and macros / defines can be # controlled using \showinitializer or \hideinitializer command in the # documentation regardless of this setting. # Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at # the bottom of the documentation of classes and structs. If set to YES the list # will mention the files that were used to generate the documentation. # The default value is: YES. SHOW_USED_FILES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. This # will remove the Files entry from the Quick Index and from the Folder Tree View # (if specified). # The default value is: YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces # page. This will remove the Namespaces entry from the Quick Index and from the # Folder Tree View (if specified). # The default value is: YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml # will be used as the name of the layout file. # # Note that if you run doxygen from a directory containing a file called # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool # to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. Do not use file names with spaces, bibtex cannot handle them. See # also \cite for info how to create references. CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to # standard output by doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. # The default value is: YES. WARNINGS = YES # If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = YES # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some parameters # in a documented function, or documenting parameters that don't exist or using # markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO doxygen will only warn about wrong or incomplete parameter # documentation, but not about the absence of documentation. # The default value is: NO. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard # error (stderr). WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with # spaces. # Note: If this tag is empty the current directory is searched. INPUT = ./src # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: http://www.gnu.org/software/libiconv) for the list of # possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank the # following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, # *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, # *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, # *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, # *.qsf, *.as and *.js. FILE_PATTERNS = # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # # Note that relative paths are relative to the directory from which doxygen is # run. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. # The default value is: NO. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands # irrespective of the value of the RECURSIVE tag. # The default value is: NO. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain images that are to be included in the documentation (see the # \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # # # # where is the value of the INPUT_FILTER tag, and is the # name of an input file. Doxygen will then use the output that the filter # program writes to standard output. If FILTER_PATTERNS is specified, this tag # will be ignored. # # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: pattern=filter # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER ) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) and # it is also possible to disable source filtering for a specific pattern using # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will be # generated. Documented entities will be cross-referenced with these sources. # # Note: To get rid of all source code in the generated output, make sure that # also VERBATIM_HEADERS is set to NO. # The default value is: NO. SOURCE_BROWSER = YES # Setting the INLINE_SOURCES tag to YES will include the body of functions, # classes and enums directly into the documentation. # The default value is: NO. INLINE_SOURCES = YES # Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented # function all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = YES # If the REFERENCES_RELATION tag is set to YES then for each documented function # all documented entities called/used by that function will be listed. # The default value is: NO. REFERENCES_RELATION = YES # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set # to YES, then the hyperlinks from functions in REFERENCES_RELATION and # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will # link to the documentation. # The default value is: YES. REFERENCES_LINK_SOURCE = NO # If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the # source code will show a tooltip with additional information such as prototype, # brief description and links to the definition and documentation. Since this # will make the HTML file larger and loading of large files a bit slower, you # can opt to disable this feature. # The default value is: YES. # This tag requires that the tag SOURCE_BROWSER is set to YES. SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see http://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global # - Enable SOURCE_BROWSER and USE_HTAGS in the config file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # # The result: instead of the source browser generated by doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. # The default value is: YES. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all # compounds will be generated. Enable this if the project contains a lot of # classes, structs, unions or interfaces. # The default value is: YES. ALPHABETICAL_INDEX = YES # The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in # which the alphabetical index list will be split. # Minimum value: 1, maximum value: 20, default value: 5. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of # it. # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). # The default value is: .html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for # each generated HTML page. If the tag is left blank doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets # that doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" # for information on how to generate the default header that doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the # default header when upgrading to a newer version of doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of # the HTML output. If left blank doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style # sheet that doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- # defined cascading style sheet that is included after the standard style sheets # created by doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefor more robust against future updates. # Doxygen will copy the style sheet file to the output directory. For an example # see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that the # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the stylesheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see # http://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use grayscales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 # gradually make the output lighter, whereas values above 100 make the output # darker. The value divided by 100 is the actual gamma applied, so 80 represents # a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not # change the gamma. # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting this # to NO can help when comparing the output of multiple runs. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = YES # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to # such a level that at most the specified number of entries are visible (unless # a fully collapsed tree already exceeds this amount). So setting the number of # entries 1 will produce a full collapsed tree by default. 0 is a special value # representing an infinite number of entries and will result in a full expanded # tree by default. # Minimum value: 0, maximum value: 9999, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development # environment (see: http://developer.apple.com/tools/xcode/), introduced with # OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a # Makefile in the HTML output directory. Running make will produce the docset in # that directory and running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO # This tag determines the name of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # The default value is: Doxygen generated docs. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.doxygen.Project # The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. # The default value is: org.doxygen.Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. # The default value is: Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop # (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on # Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for # words in the documentation. The HTML workshop also contains a viewer for # compressed HTML files. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO # The CHM_FILE tag can be used to specify the file name of the resulting .chm # file. You can add a path in front of the file if the result should not be # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler ( hhc.exe). If non-empty # doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated ( # YES) or that it should be included in the master .chm file ( NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO # The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated ( # YES) or a normal table of contents ( NO) in the .chm file. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members to # the table of contents of the HTML help documentation and to the tree view. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help # (.qch) of the generated HTML documentation. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify # the file name of the resulting .qch file. The path specified is relative to # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace # (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual # Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- # folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location of Qt's # qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the # generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To # install this plugin and make it available under the help contents menu in # Eclipse, the contents of the directory containing the HTML and XML files needs # to be copied into the plugins directory of eclipse. The name of the directory # within the plugins directory should be the same as the ECLIPSE_DOC_ID value. # After copying Eclipse needs to be restarted before the help appears. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO # A unique identifier for the Eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have this # name. Each documentation set should have its own identifier. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project # If you want full control over the layout of the generated HTML pages it might # be necessary to disable the index and replace it with your own. The # DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top # of each HTML page. A value of NO enables the index and the value YES disables # it. Since the tabs in the index contain the same information as the navigation # tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag # value is set to YES, a side panel will be generated containing a tree-like # index structure (just like the one that is generated for HTML Help). For this # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can # further fine-tune the look of the index. As an example, the default style # sheet generated by doxygen has an example that shows how to put an image at # the root of the tree instead of the PROJECT_NAME. Since the tree basically has # the same information as the tab index, you could consider setting # DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. # Minimum value: 0, maximum value: 20, default value: 4. # This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 4 # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. # This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 # When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are not # supported properly for IE 6.0, but are supported on all modern browsers. # # Note that when changing this option you need to delete any form_*.png files in # the HTML output directory before the changes have effect. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # http://www.mathjax.org) which uses client side Javascript for the rendering # instead of using prerendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: # http://docs.mathjax.org/en/latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML # output directory using the MATHJAX_RELPATH option. The destination directory # should contain the MathJax.js script. For instance, if the mathjax directory # is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from http://www.mathjax.org before deployment. # The default value is: http://cdn.mathjax.org/mathjax/latest. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site # (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. # For large projects the javascript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically # , /#\i7đ|u Xu G"ݏo-\@D$F !hN}VMXJ?m^ nr&@4jӷq .@XV4_nG%l4|ؼYڬq0 *fQrh\@f|q| $&7oLɖ\6HjHO&bܭh,6Hb[K3VF7Øs>`PEo!t^٭?IS !Vܮ/+;r_*̇i0eDT[ \QC&=W-4˓ѡ?E~Q+[AqVV_3-\Go]0#Dii}/r Jfx6>Ep1# v3A`"!;bD=+~ņ<>gTu荳nd*NlCD}&o(ecx0P;"%r&O b`80#ޕyRV |ÿFq*VJ\삟8#A+"o|ΚRj:ħ(bs,7Ĵv$,(ϒiE=~r~  ک1(DeYn1cƮC]ҴMumAK_O3ǖY}Gi?@$59h!0' 7sqF0+ל-BH:J9ܫoCk8;F;Tǥc(>׭V&cg+MOuM{ L\9(Rf>h͟oYt/G"K"@},Ep)s]0/opL6* UB@\syGmcl82$PaLLRsp1-r%:Vfn ߻lQ,o좂r3F }xF]kPC?[! (˖<6ցxHʗT/n-# rMLW0]JJiIhVSk\x^Vռ6a8WCe"{(kзB zJ>m.P}XcV.F|T, ޞXp"|5]FAWlA ޟQb~g<''1Zc;4DS|J ;b^QF-Z!$6WQ7 b)ʋ⋩[UKL2{{P04$ &Ǎi4IP7׈WVEE Rt)4)՜ټƀqd6Jnf̸!yrAt0w ?& 8fq& 4{\܏ Xu2c ʴRtkKbwy|T2)cҿA-)ri`ņ8VtX V?ߍcBQSqd"GZdŀ mr蟆ϸH L*j1y/<ॕ3]ΌSvouX[]qI| N)qUiOzj؎Vیd8!zxD(/S'>8Gǰ;0.gJĜԘ0r{wԷ_.䉷R B% qrQŕK]xf~@ǥMZSK ;BIa vb:Fxzx]I`*)n(Ϳ _Rh+qiM  Hu7oa8 6P "` bʹ$>A@r 1QB-(;J ѽpj&$n={3]3x$oD-ocQ[EJΐPScL FМ^[fiҩheu1> po:t$SHvQ~л+ K*unu_M*'0o ^b&qFu!x5Cqg5gεBb\N4vG^Wrrb$UӐ'3Ȉ.?Qz=cX2FwzEsR0XR nY⭷q?_#; m<؀bzO.&dv=%M>&\_H95]5|jϮ2WV}wڏ4i'(o%d~(8j~l Ҋ@&oo#ooo, V;Ƶbg:n! /f(B3 v$AdK鎴oLƢl?)|!\Rdܺ7Ȯ0cfH p-LY4J'xbZg3ܹ~Ǧ.4[.6tp,3 'no^'v <ӄ0p䃤>n]xzQJ3ܔ ,[u|7|7όAroo,A< lJ9^^^^^^^^^^^^|<-<7_0dqU<a!.+; s}XbooߺD0]c0rBuTDpȈ9'V^FJo$bf3LrR4j=@!;xT#p=LE8zT@AU=5 E=NA:ٚZx?nġ#"wUQ{Lcvw:;_)^ӟBD]~UcFd+3_װ`^ }H{_ gʝ$ z*WeCqg># 0 ek8@ $Ga $8) yQ;g2_.P=,) A4W`9(o yB!mⓋ27!Tj.̨GYХ蛖lr֣|/pw<8¸jΠH9Hkә[dC4'kq.G3>LP=@Ru" CBq/F;菡 fsscbN:5p +6I FOZhhplߗST.r:(,G`mHυ:Ƚ } `(ȃޘOpuP 2"N`A{08k;mjcBuA[P >:TGL=W@hBܝJ\P&Lx'ڸ, vƒ; ۉwIǶ/[6|LEF!`A~I i3c\vw9QE.J<)eujl!sZq/NXA].$*N-E|f%'y 0XyjJ ߑ38`"xeRlH&-us.7DViq LG18ne_S I+%d[8IiVӥUSw*4$50\ CW#Ra:nzW^&[X1-G..1as5jcxٌ0GbY,J ş[ ?x7|7hFyr .~XG ҘqNm +Gdw.$ޞRGf'wV>AAk170wmXrV܎;w 2t޳hlv1v,,0-ض <ԞyMl]q3+wW [S<ㅯoj"Bs杒q8jn`S%"4v w*4 9mF CGR@e0QYɗ|hߡ]Fk{1zXۆ 4k c\īl ʿhfjB(vFlpp cӒ UT_"ըV 7, YԖ_ i'd%#v: ҭuɃ9H< e%;ˋo&w(Gj'Ұ(gaK-h3o @ c !!em*2n]ۣu徺_2rHl'^OJ rF%YIDkLw @`H9K ع`ˊ]'1y!_m\9k)7S: z&l2˃%_A-bB,ObPx̉bK*F" Ib*;UkNnlH9Vz{Lh ,xjMZΧ&u$֋GCƧ68%)- j@k#q M< $}?ƕƪ8?G'1{Yi7]"6#χF>R$8ۡW8ҽtQہ%Gr"rqP`vgA6.LJS [/܋16v\8kuƢqT3ankHi$gWRx#ϜZ"}X]b~xt:;R%ئ.KnCnUJr(BOwSz 1)rU>NR=kNJTMNW$~9[gώ, l/,}^y L->Yޅ9"^WBﭺ Xyqnx+9?5C鯕%R/. 6ѵ|QO'="}QkO#.6ZSz^zoA j|91'JܴwS Ԓˑ.(+.CN2!P[6hhfFN۹fjތλ>t@ZMZOⳐ:][9;q%>]˧2hT0Z!G`s 2)B{Oÿ/zZ Nt7_規$[K ES a({Q3Wcc>-sk1}̜,@ѵd)zmP)X*W1l]0LœA[#prT]psߥ!jhr+gE9`#v)*/H0OP@~gOIBEWu3D1fa;ꊋx*'GVIs@,Io[Qõ"f55NIHu0G9mzcr엓"`Gcqƻ0P#@{,Um)&xfB1 c fY}Z ӑ盵'R:aBdz)(HN22xxcwdT웺9vvM<fmNki7Iҵ#”U`M%'R:?,#l;?qUu׉87YʔBfPAvO^xYgSۯohI^e"T 49GMc4i|9-& -ZήSxxdxq|gw5֧4F?-c)yK^KVί"F]+89eg{|V]"V>5i8x6 o"ϋ: = zxbp9TκeG8 (Zp>zcJTZqU*z1VЄş/|bO&RZ[1:$%BT'ФSlkb]8m"%q\!5RK"6o!$./ Q8 !V 4LSX0XT^̷&L=3K*-8iY'Nx80(y!8>W`CR9kPM6g'Q:pU@[/upv+K(H̺Jz >%v.ul'SZ(˸b29Ea8 pd{wo$bpzu#çm\:ORýx,g0D8*H|V7RּΠk `-FMԗ~l1ڵ<) xv@%5_ۚ pp-ͰY4jd,IwFu[oPz„g^[M: XWSKRZva6kъ㈧RJ>m4C1Ndg}} ܲ^t'S7۔RG9XTyάIzĥLA+ٌ|g;}ެ$8R6Ĝ'=MۺN'䬸UoQcK+aq.w?mrM-bTH^qX@q`E9G$ԩ2FA,/ǒ?cSezuyfUZT'R(3LA1FMfo+Z[:z`J<5r rN " Mp=`w1`L Y9EZ }@!>Q~1džFj6S !Mkh "yx±TW G) .@ŠU/ײQ#8(ҋ>! 0"SOBUkG Fw"Iݐ'vy'-#-5=JT?zuI45]=ɱs1$5V(3QBV ¢ꈰfgQ&%VMT]E;O*"wnlSXXx˘gM8 $(fCX~K Zcː(L6lV]DZb0J-)kު^IL:G¹xL;cpoZ׵؅FsJz)4L6UR#KPHQT3}P0K\oˠD8h&WZvWw E18SxQI-|sEP/z,5Ȼg+fzNZ;bڞ ߍonKITLiT.9@iUzdYq+#{9:/x [9 ->Va"Bk, #VϋE-ZcQ⏕ĀK֞JHشyv,jW&/;,}F{?Ѹ1I^d+EPE=&h/i*|G81>er˭00uS ̭W{*_eUoaV< 3Zo!`%G%Y@/(@)FM"y `)',d!f(axT=a3\ Ï !ga5W\kBx)%DݯtD95f/i(wܯ'u,ouޖ YU ZP}CJSG{)΄v$~h0@ V|7|7%&L0PX`X; AAfq'*V[iI+U*$p(%IIǘ@([ /)yK^R򗔼/m°Sbo0K^R򗔼/)yKx-EK^R򗔼/)yK^N2)mkv$fOF:yܨwǯ78q[.g@1 $%l3 S1/G$Sd SG}ٔebF3K1NqhZfޭiv8k@ di0@$C * I @5 5&$l;(8eT$Js߂l2T 'hnt $$Է0P0o*55^ fTk@ < {I-04~@? '[f`??S#EE<ĤS/*IN=x9ܝƣsjjz|숮dΌeV bDR$.&ӕi.cL@02 'XEd^^f]*@;$y# T۞F:Z7^Wp^uD@ ,)5 "܎ݍڛ6/:ȤWy[$ Ҏ)I'95%#L=VgAU˜(xih+z%MXцBo$uy8K]LT lq]&7OҪϫ hHlwH7a_9˙TK-;8=#^J={ Ͱ&!OUӣ,j '[>ATˑrZ`'bQ ^pJem#tŁck74Quјn̼4.ۇg`/3_iTlsȓj7E@^d`؎mX@Gf~} ; Jeu7~lXm„fj O.4[p!?MYVOɜ5p0r6 ڳ1Hf>ZqY /R1,:1,W{8=:rnĺYWgvd nv_Yvf?ECDŽGx&)u {U@;K-uI&_xv-Ahs#5od"}K 7U+**ϟ!0^H7n_2asV*X.5dsg-a4W> ˷ 1wTp8N0ғ bd bϙEk6V!amj҂LE3\ĎAimaC,aާԩY@&*uo|l "1"?ijk쀴N7x:2bN9½º7Yݳq`:jMLƨL&m~dǕP7ւ-sg:pk7JM80:iP7)6]YuRۿWZZZ07G9E ̎&3 ya e tB J^8yŬ‹}d>?֢D̝Qe 2ޖw15aW;MNnu^Ȟ5x˻ :<4QOP]]4Sb:QxR_4fi/hh:\C [)d~x'bI]l B"SL&ŤSc<[d0`4UuM7^dFl pe i>ߏBbd܁3%V ;0c-vLhB0"1D3QbtdK΋~O \ѿ5\5/gRo]@!Fe^,M8\l9'h=i?{3>%0}n+kfqGm|tf0KD:duC[=p ]bvWnT㍮v|^;+VY)}:`oQ9-|Ztb qraHˇqx i*(Mj6sntKח郾0om~G!MK*֓9R"ˎw=wXiBKl cn2r_,Ź"(%W._htfjpc'n"UIiA<ȓ ko9`&h:9++41g+=~Pl 9ttx wPnH~YŐNTBڧָOguv&1w-9&14 pU<"`zj늻8 Fn\&H d>è"F {zW:'VQ+l)g_9L&b+Gy[^@z6adLru Οh AXM5qm?8Ҟ`iGȭ*ҙjXԦRoG%tēr0Yf!;*|dTHZ'2 (';V1Xx\m%B1p˦|@*O7J+ 2Q8^ w3{+_Selݰy:zlp5Ok["SO\YͿ[,$͙,N/޻2GOMQT0^za:b AIJ`Yqkx^'3-G95LS|2C2EO"]pMUmZyA0O2+lCV6nx u' 2Io<™.;g\$eU:f]rU||d+l#0',Z=IdE7ZjR]x K7:ۮm˄0 |c>j%N1f3o* X1f[D- ĩB mD.(54 "X oJByG]I,)~<ᘒ#NNXcV(Vgk%dҦs= 0" XNɻeV?+O@Yq߽H5\2[ӺfԾgeYlFiqBc$?9߮ up#ӘQyyhVZW1t$x:E$k<'[`.+ݳ=)~XvפH/ڬӠ^$5ھl 6gU5]@wX1身T:PM6^X66Y;M_0ARm cU&(_, \k+@;99 ^ {l\h򎎔}1+LofRԢ@94 [?PXiÌqt\qn8}g+>\C nȤn`#mAXvj$D 3B}QӮr:aBmUju_Z 򌕻2Χ?woH4 ])';yѾ 6'3uUIa9X[wNp5re\rkpYC.<{aS&߅}}1[D0q-'Ww֭ 5 ^> _1]3B*H?68bvF|X`UGg#`aG$T ڪSNU^l֫[:ؒ`\E| t+%8VNk^;oڀg|g٬OO^/ S)E&׹[=Sݡv2m}BڶJJ<Ցl.Qm^ Gd`\9_hJ-O%MuT<@* (ЯwOOd% a{IeX- +&3bml='l E2Mb 8Ţ{$+z/7Ea *.c])TtDU ZjU=Vr)[``" 0ZHDsxBFd y $'vjOܡ~<)q[wO~lUKꏮ@`ξrYmTп)3xW7U6ET5_O +2W5Kq J\Q1ϒ;{43U!* Bb%(N“%қb|ڄƶ28 *9X} .vM6$pi7?;\~ izQX+uRdҧ24b(M 1JjAi?)[yG-{ pQ◆TeozG.{l3$Q~ %yB+x f;~0R~~6Jo|Eʇ89ժ'뢘WLG>kM0 =K\%@:#ftHKD4q B5BPa!!*Mz|ᄄ5%@tl2TJ=%@%:#ftHKD4q B5BPa!!*Mz|ᄄ5%@tl2TJ=%@%^[1dQ*ӓZ %$YbEy!YOlj:KE6~J[.!FRk9w'8_PG8.Hn]YӳJ:psx wU#n_ I|7)GK,~Ş4.!8bAb3K|ǻEL5[l~`j_9^SchrN{Ó!tT>0ur<ȔXE9$VQN}z*{QXmto0?!}*PN4oQ7|+:n.Ј>4@"UU}`tmgBG7x{<>L(\ `HTm ̮ NNg*R[&tm|\dzۗE,z m~R&YXS.0*Iϊ~\.`l==n0q֥1\9rgoKE+H"R'>bu ?"[]^ZGx eۙtPቖK`uff-|yt[PҗՅ5\VQ t]0izG!rd"5{}. j":p! }.*†m:qȀw/}-8A416mIZ6Xr rSQɸW9 $(_;!W=&a+-y1;޻3Dw(Dz׉09@\4}.(IWߑ=R!"HChL3@˃5h-ϾQ%pF2 AUp ،.zl!};GlqΦ#" kPR^Ykirt4v}Q9P{l}_k~)C=}B|ح @mooI. t'kBW%cHWaEA2ySF#Cϩ{Aod l,S%E1UrXvlQ,cmtf-"$xU+ϒQ8U{sVx7еGtD/?{KgX 6LzD +uR%jdd09Z>E]eREƺL -ڨOU*񇨪LРM?`/3ʒ 9YcRTߔDѸGy] 9.I!߄'t2NTywW3LkC^z~ Ps^Cd d#\,ḵ#d]^_{Sr ^k;]3% G0>Qo=;Ѧf:闋Sf7MFc1FLb:7Gs%DKEÁh6YeOX؏?U>x}G WdD/L{u {B}U !GݶW5۴HՅk9'b-s}]Ȁ1?0"GL߶sdaqܽIcuQZsz\WV5Rݭ(^wD < R.Agfm8>4-|@@WP#(N>ZCRN77W虩:@ihbN`CIyݯ&7Hr"{2^'踕~\x&șOztewk鳁E~b"::F=ِL6񇁣0bY4E-(2Hy[e]ƝmN- hesi_o(ⴧ`;8򦖌Ki.hPm  t}!/_^I"`-s @ O"}swCAHMdVR?7ӔUAl'?ȻR΅dy_rA,%n/Ucޫ9D0s%o!+oM73AcmWvô%\<)[a'G2wXѨqR6ʸ%Rxq޺M|3z ©#eU"^'uKΞ\^'i>CXDJŽbʈeo'omeڟ,\/Rxmrbn AS0>mշ<: (|e`6fAK5@_sv?GWd 9[В3~ty`!Fm.&Aչ/(vFyrK^OrY)'`:U*:Z3p\9l;VIt8v$o0YRv 0iwp-u[ rpCrU?m0u@n/(dDgQm}{Xr8T7.V}MXW]&w-D^n3"hȞkZ|Dna7NŁMD';8 >7.k%7$tjzB=6-86m $^"5*}p9Pؘp_/:>+Ȧa^pcd=Awg B߱v=&qXV , `HY=[b>JPٌg[ ˤ3|(҂ ϞfA@"gUdl:>C 3+ h+սt. 2)pMx4Q\h ZGIR:0*^ӦD؅>h^`_| 2oG.L5'0wneJ),Bv[4A;,W7nb?+(5?4-nX%mTrubʶ®<~=rtN]Exej"RP(N'V_Z ;7x{`ʖ`5mJfNr0a kQ +±M>1NPX:*B yu \5*m_Y{ 55C(*I+:-j<y[4r3#!,ȸZHTYK^եrB^gF1fg5Sqh$LACɩPdĊ+".64_%ě#[I^unR٬n!ʴ_86~Hpc@EqK(PiLhF=cb=(uX6TaOAt9;ro#ܧ1QDڊL|(b/ s~$en:4:,g2OթPc07#mdV^vӐ6Byl8v+[{F>Ut~2s5M&C*(Lna.jcΦ>QmuU3J6fɦ 8yO$<юG B4rZdDp>eyCo$N]0pd-i puf9UF῟ #Ѓ:xV?OBR8OVB2jԻ6`Aѳ]Wg n>\M>Dɛa3AUsuVPJ d8+ƴYQ9/ r\V,ߞW(kTDt8 a|+H ʹ\ v޷ ;Dӹ ۚ+7Ah3~[ɈtFLJ1c~uI @cc6ݹ3QMXNa,#6P!rL8Tዜ0[u*֝0̊p>\3P}`O1Vo$xjtڟ v+30A! :3lRu_ɜnI1Mn4ʮfIm _c1_Ӈw|oc[\5$7-ztޚ5ހqR㑨Ahz.˨"rXSVu- ^Aýk E@O[R\M#( f6WTC %\TJ9sVh!T>^8Mr8젰־A&e_ D{Ri ay}g*-QSpf?ά2x$Wu/W| )P Ĩ?)OcߠӀ&HFr:` u_+c<(4ikv 6^s4f/*VJ5:9Au_Ap%q̎4$kN‘;#`e[I-]0آ^JrLv,Z2g/+:%**'&\πF/9nghxӘ6Ur%3xuN)!P)+rGa;$ ŢBp}|o|{Seuc 0 F@PnMiT=qWpA+% Jo(jm9&4aSs}8q[;٪jp i|}8Q" ih t.硭A?H<Zg|ޒ* c8]J=~_Yxg6skgK+qSKrWNrH|W^61~Mݭ&б /+>h1W9j꽪k;IhJʐVkߣliꤤDz2 *Q@'Qׇᒦҗ3bNdz}Eku%K9P}؇<`vWy<8LĄ5-Lib$c5x @.&hH@ 5%yAGY:@XXx+jfp5]`( ^PB0SkU/Enm&S62)hb9˧[zI .7@\aP‰a=`pFEFќE!k4]7J]jٸ#cS)?B?2_5Lޮme3q:KG=Pq/p V—t*\+%4+Yʡӗ3%P~uö%lq닪2&‹f/c濾^t@.0ͨX rT*VO:raZ΃6?rښoWiP2 XHCd |#Ϟco$Boh3>[޽}] 쀄뙊}uΕc<UZSC˵I 8-+N/E|jgi: 7E[luiGkvBhqDS*v azCF(pzƎ(,Q(ԇ+薮ADIj zsh xh>ӄ 5vպ j<@rS)8I8<Axr!#QPd)ft5ټL)ILIUXξZCgd)8P-,#rN8,~kRS qA+YZFD.uF g6dLi_Ց͓,qn+iY~<%/!RJ{~9± o &#'c?FȬ0DPMx]}?r@_3Z ^e[JՉ6͆ʝvZ2(dsSIN*. +>o:1yEL8BEdnR7T~O ;l3ċɠ; @l)U'Q'k&7Y͛m 6ޛS:{wbעM`ORoDA^tR|Wq@T&WCE?xjP:)ܱ ry6faBoS> Z^ keFf rKrs&Ur7һKj.G[O;ɵ7/ctg#\ q}J›l]Ǧ7|I %> 狟+Ksn!$˷b]?Y=-S>-3Gr"c<@٪VZa*R̸lw+^מn-L**~03^BwSWM ҡ]n~VW@?Z.EjRj(,h2S$ T\HUCΎӥmsЅdXp8w]e2wP6{,+;Zn\(^<)DB\G'V80ǒYqp(־$cؒfW֑JTN/IsoZ|Dg?ā8 ~p2'm4|cZtnۇpC[ F0OE-5Šl5yIyYicG-%O_̌۩k+DZ.L%k! `ӲRN4R%afEćԡ;uSyT>-#Mdoڏ1*LHhc E%kV@S_;Ô0B(Q  i*PܵŢ?e[RQ| sg`MmzifixZt?32Y?9'3ci⼏SWcuw(,ONm6=-=iMMZOՈĝU/} ȴ?Ѧ^d/ݖHvS>±[6꣍b "|F |=3p$ړZ>aF|ˀqKZ1PnQWpj4r5=܏#-v]B{b]gl)j4&RUÍ~Q@ھ2۵ʽ]䟚$392Vf$X1'^;Ny&9 ^$PfR?CDY:LA՞ !;-^p *ĂW=$՛M]0ըwVd?ZYmnzKaLHMWЋ|$P@ CSXĄhIgB)ZNCz˘Z:C1'du(j*2LD)\ SqM貒~+ 6i{\$-W=~G3aNa?]br@3LʮI8OssIHalotkj kO"W!MuV3^ ԓWV(>|- n:&J'`|:DZiOy/c}(qb:qe1jIVΘY͑ ߬"sj8*$0lsqD@1$]SǫÆ~gPF U6eeo:k}W%+lQM'_)/ VGL^d<@V4EZoqh>SaUw-LeTVVEםF»Cc0e+7q 4DP5GI.6-ySx`}4g>?i?e$dN+=>+G\g!5 Ec4mo{,Oӫ"ͯZ7A;mAnYسAOĶ[VxaOO0{,$*qr6˼-Խb5͑KG y3)c?W[a{Qe5c(LMxڤ<]8+ؓl@ qs 1n;N}a  DuCQ{V8'*wZ[z1};[UL"oޟ^@cocU'L d/u ċKG2Qn5C{`fcc!h C%+`,S$N0Ɲ8>psugwbκfP;;px IYq_-4-chS.ʶt!͊d_* Ғ9W҅pBFXt꘻!P@PT\@ǰkHb[c(vzi8`}ʒZ :3ւ5H(7 1/ÏGɘwn,J/7C>SYFC[LDa'DSͨ9Z{PVΑ6Za2;xyikSJ/mgUmNqK0Ee>sqA';<g4cu4è JWwY{P<[uޖ[2A= imyYmHP|O 4uL DՅ #c–/C=R Ď"İ%?^Px0 j x驃~Ӕl'c=*eݼ5cdhvH`UMfd$+5ے%lnyjxi,T\:t<&9W:mPi*k; "L(øuԃ|Knx 0?:R{N)8I]j"'; |'YGiRĩE[daMبv\G  nZ l3Nc<~դ􅬎h$7{HԂ#9a#9A Ng<]oagіZm+%Mr~%CAv~K,, Da3@_ "*E5Ϳ5T qR| 0m1(o-6}y#BZ D&\a^>ȁUcuxs2\{&էS9.0P[pDMd()3-/-NJyO?`y'_'z4l3- ;^YM@Yp[i@H:TUT;uZ 'ai6؝*bT=,yjXĸ7(S!Éf6wL!yg[K궖[ UҮ1% etz[7 #;C~8 wTq0}U! W;[jjKqtݶa6s;%C;CXN!\Ml*pk=:I4afnJj`#>TzO?|Pp7&˲XɴWzFHWiL}"Nwdd誤f/aXmJse:%s\'3yþҮZ~!+7K\|˒ti-;@/qMiVd$X[z Obc%5QL:R8;"ο_? MC͑)Kdmgrf65C|^Ug''ɟ[:12u_I4n2)U[gg ;&nxuym#xi:kmqNv.8⾕HBgEi]om]H_&xddq2ZV r;?pK~nv^{JvkN6vP޵56Xݮ Q^)0`Gh'TIڒ}r O{^ \ 4H YMZB1֤U_HC芢 o%1 iX|nk>!׮nPԸZRL_4 0ޕpKzz{gaZ^4rzib-iJlԎ;=-:/L+od%SF:q%3/AL.$=+hNRWL<+ю5!}d()LS3{L%(Y[`b]P3!@rmjzLrܠuA 6ݼICr NTr#dWoBXLj0<|O|Vo{ci jY6(=-S:jٶ1A:20VrMf uD-F氚W(;{OZw(RaF |eg>":"22IK,,a/q S$zqNk͇3ȉNJ!"ё?yđ~>[m3nuǐ%kgU62]x Χ SKLu#IAQˣce?PE@@^X¸TD,: $ ]UJ{vr|8MEhXU5@&'I++g-!@6gm<ɝ|_~@-(+@^ӤftxUCT'N[tBZn t:*i;leҲ3TY:=| y_t4$ TP&p׉fu7ݱ śk2r@As)chw\,}!fE.$ye a0}/ D=x؎2C`XʤwUd5m\dmnϖ Бdi}b|}K"0ߢO'\ЖI5ߪh֕`anm`*iRԘ/08.y9pwʲőIh`!Uf2+Tz=Sū< jc: wKKJy>?\P᎛(47P)#\] TSFAR+|TCjOIb~L Rm<Hǥ6 vˁH)q5`if>s[$Ys&RW27F3 HBnѯ%7 9gi*'wlct@rU.k#E,ϕ$M]ԡ4i{"zj9tcQ^Qߊ騵==67!qI)#ށ?,",+kmw>Jmjq cJAsS/5vV|Rm|oi/?+] ґS a،L ~L빣.SžvL\0_P#MxdžŢx>w=P^)һxOw#r F!b@BT\?]]<y0[dMZ4v_=JGwL _wM PLtW6K6զP];ޤ.cBwhp"ɗY@’Uy)! ]ΡJULT"=ƨ@C[7r":NP$'?y{Ρ>. x7¤$'GH䑌 c S3.qS,ZյՍV:SǨqKqbD<. Opn@9 >@[9b^ 5]zFu[%$dΪs?ʻgҕ!4lZ@(`)ư"! gvM1g5lvf=uZ B$/TA<Tcby9/aИv%~]eI,ƔI0R- | R(-2 dmINz| EYvnZzuCMDS2"nfeOu-R.Ec( 96w iЇ\H݆!.a1k;@6@δd`W:Mua$F+xML ^4H?[QVL]LUvPږ~@cN¼:t5] ;P ZpMk/ĥF WUtW|lCà C#큂8\]7 zq+!ЛU#56*. QKSkTT?vvO!56@9Z+e(U^Ʋb; @_ՇM#'/6o!ޅ|,neo;2H9᫹K9D+Z.nm!7_ٴu_S,L8e 7h'݁f& Yui p"k7pΑUep~0P$n fH]`b~:֨ lwGERH9'o-aL!Uh2g :"zVGQ67, "]#ajIͽ m녣=A^ )P S$s6et JAU݅?m$w EƒXX,IA-ѭ ass浤f  R. CH2Y1VD"sfќekWx.;>nKf6b5EO5nwTd S=?EM))5M[Ĩh!^Pr6}># WQ12fkٗP8hdJ;&4-Vld"۩_:Mj/>6jZqɟA(w,6C2f]YMO|wԉgZ֚Rl#@Z,s׬5#VP(h/ʿQ;>AIoR AmWx4_* V 4fM)r}O,KǤcD&Zɇf53[R۱`3ư/GTWl/{ lgO?6Fm`$P>\ezjBE0)PބhJ0/vTƘpA*c K^86!nʟKGtv<2yKN(3N,s 6S Z[\Aoj@HF" b{ѷ'a9u}ԴqRMYŸ: opRN~Jlkˁx5xh$Qw #M*DN3I^@f'^["qa Irk8I˜7-.BuO=! yKcP]Cx+sf7 ~.UENYMa}]9єB', 1y)O`{i x>6Erī>UwEe;s1s@`N9:|..wey"NJ ]n'o0}>0 e&,%^) d.)e!Ʒ~Dҟz]b tvĐ?wQ, )T'ȦSWM*hsϧt2 D Gc[9.Dzϥ` A,X2|*Ÿ/7 2CC'w`%vB1/ zc4 sppBt|dO_RcTOsIg-T.=9U(VvhZt-g8 lK$^(]J*1(7Z0@1?Єk6MFh: HXJmfDX1pyl&rzۗR%ѫg_e[ 4;YY5Ik *(IօQpy>zu"avQo#_fPjՌ!*W.(ZbȾ&,F"rԮ;ʳʼn* Α6b S{P v(/%t$U)O%xy` }HO .:Ҙ01o -h;'?Nsa"4 #ЭLF:=?XA^8!=RLz@!Oй3]Gl"F]& .-]z,Hn:A>>0Pr~6aYbc]CP~UwHjl?%M|(?ƥǀqڞ?CQK3o ~UƄ&EvQJ Nd'nf3ɥT!xh~xⴒ$ uǹ1L|P'NZ 'rw}zV8 1hY=T3ڨ$2^ crE>L-;?Ѩ^&a/0qb^'qL&ԋ* SBS_]}lP|$1^Uh 1+7FNL&tP`)N6pj\E۠SjnSu2%+9s}#X;IG6:kkN?;ugC‹Ut4LY(U'V$K؟ TĜ\ r E㞛3lP HW-VcvP7'"~u&8\ȁ>H818b TL4_ Tʴ.S\ISĆT2)Z n1,kh!Vv,8WЬʘ {Ю* pxvRwQ@}Ƚ2Dۖڷ;W~x_4=u2s-DގH`k5?gCHYmKFV<Ҙ)# @7df0!G;R#1Og5y4NVLG;7YWhޓ"FKde+%=q(/&ĻoVFE dE7&Lc@@ wxSj8q{?"˟:3R}3i~%d#\C!5oDޫJrq+_"ղaG4]N\P9{T4Gq1jyy#xnQ{3v_A$:i. q}D〓Enp-{ Lm.[%d4|c*΃S^N~? $UeT:_@Ƚ &Ri'+UI^擲~)'݊g !y 803&s 6r-H$L?BBܓUIdˬk+Il[P,~_8oimer-%ew]?(xp@S;Op:՝z?,kh>.%rWX&Xg!N'G)s!co7vz)n}ftjfԳ$y*xTÙFʕol-+d֭xlz7c5-F|fVOKݦJ\/֬S}Q4DEQʰ* ^ؔxRx'^"$ANgIdRb)tb&9<>3 Qh~ilȂ/oC6*4jMD۫&ni@_n.] mzcˊcQvFE]wFwiYR{̏H__Rom+ dMZʣNiJXB`Y+?-(C\d,o;.F5e+_!1bFp<ġF)B E#Ee$7# nwb}[{D0-io3TN޸؁b#vsGW o_8:nm~,JuEG+uU-(S1VFLoSآ[SLuc,<,W&i(FQmlD!6Kٷ~!frw.^k(*cqޢTy.[_F=SۜxPU|3尝5,ZjT0)\YhƀGa6x7Y$:6};S'{b}VI ?-.a{CE4M;eV/ I,n4P2e=c8pe I/`cOƭ,0~ّ8I֧pbѺPJo3VUw JZk4_-BJ18Ci8{F.\/7}U_t4iOW?jE_QğڍIZOQh?Q9?QXt+`&BPE*M[XWlkk1z?f2 ۠(?z_ "N,TR 5V@)Gۧ=ՕMn*P/YS]~֪tB*zCV=mQfѷ &;4L!]ngxg'):T;i?*pu=^-K1KFo෗j~ e@cQ;ї)*Khp1|Ν QQm^a&iZQKgK:Ror4SEŠ|OF¬EZ2*5UNX>=1O, L@Q9het/1҆r&ϝ ~J8Uњq2fPBm֩sԿ ĪU-ID??b|A.pK3.z@0Eh`MLmɣvZT1O, L@Q9het/1҆r1. .ЉR1@a!/Z1O㻏'~UnU@0;`)(M xHK+:|$|BG Fi|3EPkײC|C[ve瓄1Rn GAlG@.ЉꛀP*!-4ya$x S>r{æɍ ?|]5Ez<6 R=XVu !BHGXhˆO栀jɀeOu1J5E UaMM ~ E@a!a@ _a!!ܡA]H/Pẘ"M[VT9ѼZ(>pϙ^7?.bcn@ 禙X,)@і}9/]jx$dɥru%Iva.z}D`?'& S"KpfXHBl\_im6*~Zߌd<֌ߜAqcsQ ;ǗQ1u|z*p_dUTMI~$ҐyNv nSPk M`2d5 4)ڳCRQhia>7ι'[Ƴ,1{. M@Z2b:#50 a!,PBA.;2:#50 a!,PBA.;2^u |EYCqJ&AXxh N8brDPWC XTU9۷b/! 2.\xT1I8!vT?*Qe#}=)_]"4HajX—o$|P <[T%i3*C 풓rg E)SQp 綱+x>ȥ5['P% Z,wXxl"%WA\#c~ eb&T{yw_h=uړ_V4~ J7g}ߴGemhZ,[u @{?@s$ P?qߴoxt7R *`Tކޑkf@wVJ=zH(_s_6SoS?O`5xޅ/n'Ԥ>o/@//'}`Rp3mL wJp~w{Cx o֯zl=z(d0Lo۱[hOY}݌`hʼ#qgo YbG 9qsq "/\CTC)Wj5H?y%`HK/O" /$UKLeTco0^+o0C[O:P_؀)? /mSH`l_E% Q o=}Djy2m_ G'g^j-3{V'6K晸Ļ.5폲= 4G0%K$kjᷭﮀlB&.ޡ6SF  g@AV y2cL^}"~$qk/p$0,4P#7י~]D(0ݝm23ݏ: m?{Z^EO pڝ{$ǢX%x]R|lVY}??Duө I8>R'N1~B}ii+:VʼIE#IW1ɚ7' ֈhJ]A'gP3O![2w;Q-A6X 96i;\ 9@!>-ƕö?z \O>Nu xv˦KkWu/1~(Kp4 NaV-̝MY~-b HSn &rwxauDY8+[^1Lpڮ)4m|/}6lOΒ$&RrC3Aq$6EyэR-hrɧ\ nbHS|>?\50NQ;!&YhOd.+]xax&I~^ػ.N3|!yTP g/0r|Juf:+KEov; rS~Ebl")Q܆V+:ݿ񕓽(xQIQSkAP VA_ϥ`H5WW>Һ(>*k涔d>Br*bQeש޲W;V6/ɸD_'HT A;졇6G?1Jv/MHHcabU,G[9f:*lVIu$hÛC7sW &i)ħd5dV^ A]9ND!@bJ{jOL^Ĕ=IJ.OJ{93GtnƮU: boq9y"~+ GS^C2rFKt !~Ou> }(hJ#>LÜ]`&f9 4EUSyiaNi SҜzOSkyx2ݧh!Q|V@;4v7P``2K=pqўLw!7 QZ54LH,&Ϟ*t$a:f6\s^.Mt[_#^4פ:b>B2MB⦂~SC< s@uϏ*HK0dfZk)VJ\5ROdjjXl]PY=^-7Ihdp w`1sE-/y(0;Gez^YYRW@4o Qy[vw\R,"mx$qH#GK(CtA(/yyv!=O\be^*@0F}hn_H? C^OIg`88uY7DA#[bov Bn^:Y߱֊ڦr,ڟBP "(z - @\pPTt:۸4v)DK-$5FXEN;uʫȶZ9-N_2!J0 [SbKV0 u's%u0m5n=_;"J7p|gjd\*JF`$ 4mk2ijJ<n:dxYM\uVg= /~ [_T,Űjs.h5<% @{-OJX{J6Td#v.Y(6l ]]nϢI _^pX#v*\pDAC5:RhaI0`ntԓmgk( cUL5D L΅Tc ysw_XSORՉC5u7<ڔYo3Ӕ:҉:˨[HG , :R#/o&AXYtLFcؤ-Mc|H5fi>#=oܾN/-m­G?1Jvk[5-:ڂxzO[TA\N=+)b ^8@apVyJ uY@5TDl"+ki /35er5+EׄE]vVtJ_Q 8SUHrRVe3 Q[k}O3EC'v +0|& B[  "1ge:9xRg@sU TjwVl}̈-8>"8%BbeYnw*@28UrbF;h~"Gd 5CY-2.)OYx< ~C];C ž._$k8`kl3 !"&Іc@KX ? ;BhvIgt"vz /5:~%NC݊%EoyP?m3bn(K,a:5̥[k6NeH Is*"5Q{4(-TTX[4C,3K^ʎP(Wt/ 5_mUvH!4 Ucgd&N|@[$` râ*ǐ7VȚ5~]q4TE*3b )Q0Ös"=9exX;bPa[ * vpO&Yͤt)qQ=,sˌTdk=6QT?WX*#WӚ "DmbSHt9fjd)tkQ{ -?*6j=L_/;/8na2&la4՟Y۬i]2DOZh λ L r3 ˃85xq 88RiOA БfvLeψ m1$5~OB^% s=<Br~2@pyN;ͿM]o6R o" >Q9p@2U7 ]̜ٕ`R#Ou>zrAaEX&ț)Ie%`AdKqq!q _y\}Ů-9a^*&W =0|*k SxBP Gl˖: y U%-q1gdJULH%f`c>;mթķptpg<4a9(Ն,Q[d,ٹ fH>Y~:f<%')9ڙEt[ơ/@oTQ-]@kt ڤbݞwуogv H7 A%H !, t #Gwl$*trOg[]&>C(t˧5Aߙ-:&Ϩ]O;в'_>,xoK咑ͪ=/"M(Ųw{)sbo d&(٪?P8a 7uo `9 q~-b5:oF?7okmbU8,Mw Y)\9gtZ2_0:-#Y{]s!|6J.Ra')׫*m[)J/#fz,+'7xOeh/ O |ׯWo"SԝA> ,/h`h]h%a9ɯ^}MSi^MZnJH%1X_h: q,+D̨B ^H>ucsѢ^TD+\-G?1Jv/V0|$sO.(`DϠkUKS$:1`!X"unr]P.q$krlxnuNt%ㅧړ~7Mu@q;JLOQHY{o6D<вZq<|&|ԜRFURΔLa z(Oy3IsdLx-P D'b=r??tk3^tB ]}zҚnmy5Y% ;ZoP`mV8Lڀ!;Df5C҅p-/&e.W]:מq}zwPW"YԖ'3ڐ PƨҸ$8ƽjdX0!({N J+FF^wD8zL`vI>EقӒ&OfP굂vAh[jYw]%zF\Yk::? ;3pZb)CSӊdŢIF[wa4*=XWw3C0Xj?5k>ֽwAȎ_ zN8f[. ʭ2,>@s`ܷDP9t YYq>w5D ^& ̉STg&OwbѪhl1Љe;Ёˤ](DoS_ym0M%T)P9K`xT8 shX* ɄbX$,l"cM$2n|3j 0;?w~EHc?a cFO’?[E._+mr5 P’}q,暡lyRp Fh `tAZE:U z(5)߁< T`$ZF8nA0MR`&aDva2v/ 0)J4T[J:yG6z5KB w]hRl 7Vf^7WDg 53ٜ;Oͬ)#/U;v{,Cty?.ӟjxHzODc`}i9_9OgrM,_z!O⥋ZiII!_T{ AF2וBrF:rW,`3;~`$W?vn*=Hq'yx9d3 {-1Mr˶SY($& &4f36(|?}I0 zgwYB/12dtsA%UvP[Bq^I}yO\*2'v9s:m`ý.@+0Cg)QoS Hڀ>5x.w‡Tm1G2]; /UՀAë>/~| .v&$)24K3 As/ O{)gYHASVaø["Q_`gjA3bbԈ&]T΀9`Os2LY0 dib`  'EnTt~ ݠ:;-ZCJKX4^mfm ([gQbuk1K>Z&UG"~*qbDfJyd33$^g:^ⲣ h2i_ `cj *fCXƕ kBL|U&¡-~ T vvȬm]v̋SZ7Y /~bא ,S /foK+qƯ܇>* ͗ITG;$IA4v\CR_4Y{#8N ʱ@dɥ>}a.0:Yu0@V7<.͵#(86OQKFI0p!$7ޔ"6ֈkY xkSKu9W.h yϜyd99$ӖK|^& R[h=.1B1֝X2?\ (.ܨ;[4O(n;ݽlQԠ7"d91<LЎ$p|O`Y׺-S**s|pSi@Vʔ@/h=h +/Ot}~KEW(I1HFP6h~4Uu/HJDr|3J+>ë>/ξA}N9uuz(՝cHv[,bš̓LQD3V)\?QēB8/@`*_ݝ~F€v:j:'Jt`r:-wl٧#7-z-væH4|vuC9zdF!% \sB<ڃKZӑs ~ls46\R(wESh;EK26S$aQq좏E.]{Ep+B]z}+<+䫿+CW"",xJ\$Г?8 R1'la!"s2p L"wSuI<},G?1Jv/ #S Ca \?T;MФu0tjKunv%q=_=xfKaEZv{Sgf-l#1z[ ӔPJ#TghH=2Rh?pkV?rţ.8"1<'GUi;8VCDh%f[O9_\vY)&P:31jl>?8aL86mvc(_?&djxFe\KCJm ^}iyꍀrv?hmNaNs-J_V&̫%r 99@(.E!V7 f:%P6lʛU]$0Wkzqorا?s%LS/C+=bxͱ0R^^[ F>YJq4~jA> e$󝛅mMJv4vH3@O7{Ġ:;p`JvsvXO{( ZLWw%!c C[ B@$W=ѶK|e/14MJ!j]Nksnp ԼN5][ZiRN GQm)\@qh;b&諨ɇ&+[>;3E*?;.9}Y@[=W2JSգmD|LQSvlQb ';Pܱ$zntKS '2W1OwZDO֖/9C?O  GК &\˜ ">'\FP,; (lPq*{%ޖhPf+0a {Sd lc5p˅搅|"<Xj🯿L:g)*7 <,)v_Ϋֺ7Ul#=]{䰜'"H@<)e[9 nuk!@x!7$iakxU('.ozW]#aQ(:d=;Q^6Sg @mQ7Ʋ#ڡ@i]r/ƀ ?6pzRoެG>k&vO>+|kx=wDLCb[ġ#X?h!r2ku6Y%rԳkiVP3]i~Cnޫ{y~A\s{$_𯱓amI :ieDTgdtB6V==FƙzP5teb(~KUD@O4=0!=Q ;O8$M9y\8'],)\h>MI9 ;ўSѷ|GCwFeAg1*/8:^;W T<"HQӋc>SD) R;IL< gxiR$qnFMaXfGBC &w0\9l̂Ľ0?F-5sb})j{8`vl>doԂNS2s1GfuH%th@{G謄5 ԣKo|Y7&<#.+#vHTwˤӡNx@,&4,Cҙ_*5Q$/&ԀRx/'ܠ1ˎtn3w=٪:& : gayAA5~^48>U*w͔]90sf^CIe+oV´'%MN:CM⊮Q rUzPp.:V18KXDcsDH|G  alL?̲~wv8hG Fo*& l.Zݖ)[V3JP B,SA]J/' 5DVN]}_VCWwƎkpP6{~ e$g'm+f-5)6fA(A: y] NK:٥ϣOb9Y, Pq[رÔMW;_Dc~UuRMshw}{KN^&ЙWr7mikH}'[FҡGuۡe6 z7> 5 ;ux߻gɗ*`擢z \Miv%TҴΉSr@(5l,ވ9qrʭ%.Dpa0fWճ/]/4FhȔ"_IV8RgN64aJWOv 9 8ajC2<`ݘj#|o@SW:ʟcC|ȠM7{K DjtYqTLo`?,xI=(b!P ҾI1^ӆ1d>.rՅ)CgȂ+"LP$pDe:dgRxG]~=[6: |?gECd(EepK++C, 燪ZE b>dHN95T'27DV6&L0G 8 3>ufdz$8tvoR$UGH#]Қj:ýrN{qB:ۅIq'K |⟍ρ)g"~F VnkSIO>x;irc)Rѱf12[B_hP %xhUTEߵ $:Z oY*òi뮓vhƁ&P0,@ՇtI#4nT{).F /bԟ*@n@-סf0oh5 %1XyPr?B>v#>wҺZ-+c)OnVg];&k1sï_cBbq3zG1(@'e@\bɖW ~^})-j^aIc0㋐/I3 cLrs;xnxWE>!r}ptK2$ؑHLBFoscR*")U$Aq0k-?en*l26y7Ci|Hd1!W:1q=*c jSy) Ϳ42v[QG?5fa>s#d IFs/j[Hqj_P.`lڙ*2,Z&nR6Z:{3Oz3;T-g9Y)"hCH4_)hvX$R^Ŗ$,FυK qp1<:VFjIV/.MsyS_=o(UZ nwHW~ٲ{/lUo_0QǁoZKPJPe XQՍg} ݒ7|i38Q1 KE?@"0`&Ǚ C4/z$QF׫8{՚1'`2TOPHD O<},G?1Jv/ P=I`Z-edǩO "h\/$rfF彼m m2HF|;.iK>-ZJ\n9!yYIҫYqYTRSD975O㙶UG0p:קvVSͲg- 2seAH.M_P'Qrt/񇔈6 -#~K?(K`[!QG℮r&=ry?6=CTJZRDҀ:CČʙB& 1S$JGgcwMnewЊ BzS.Azު?-Ƣr]`c*ǏWFBDkSG:M9"ҿr= u8#` 3j$&#eOcBΦ؁ CM RZkp'57haZ9^^xFⱌ&%z&mE: OL2giypdf<?@F`tAάf͋* mc24%z~$j@);:;@ ,p O*~h1qKNߛXr*5Y; ~{r F.h%R;S/?)V)%&[mj̇q@ORG+V8a5[Aa[sa!(gA @PuoMv<},G?1Jv/1-<},u2uC+QPqi^c4K?AbRͅ?0Pv}r%`~dCV݇zwO؀|1ω 89I2Kuy]")P.!|@˵{$GOY27P̳m"b5%U ,5JgrF;7QTqXll[e|=$@iF=9,kԋ:Htp0˺ )9ޱdt9ߵ=g z^}Wp6 j>Ru)S`/,`/ܹyHcՄ?V Xo`1ѫy9Fq+?X;sh#gZ9w2t\9CHG(QlňT#rxەW,bwd4Bui@~+*E9ns4٫MpQ#,Z[PZ8t ELOz% l0R :NHAOջcKlx~r;ad*lS'{E(} A4VX#M|iե27%4~ ?Q"g te8NP ݭ;tA8ݭ;tA8Z)G2NB$(e1x%p hڬoRx/*NThL{γC Ǎ7)Z~suƆnN:#N!@x?7Vv5HŽ;Ng?5ԬV9ޤ-KDB Q9}_ԇIiE޾\y8-brK2#07ɭ\0ce7R*;~#+sa53uxu6$MB4l}%1ˆ`1p{n/I3UrvtٸX_{,Úpt"6'^Dړ %"Ui{w[9O)>oڮ #9Y&G3r?u"?p!j9 y,ࡢ&# Dncmm%? 0XYi'0G'GY"+A'ۡkmٻd׀WrEҼ~8a؟9Qhot!> ^p/O_|~mG_m@ 0b_IiҨJ3%]Zk)V!?( 7B h{mL* 8J77ޔTZ-y41c#:b;x g*G60I$<ᠳ"* LMB >GW] Z"[,_\4y:Si)K b^P&TU ̇ DGYCkui=9dIh1_q9HǶFG0`^H$!'//c+&x _i޿<ЬbW}H ޟVVurLE*f9Ҥ]A'QvFD*coqvj=(0wֿyv9f~ BWsہw>&3EF!>bg sT=X[ gٗZ~݄#Bq@JP^+uP*e;FKъFFgFE@YW4Sb)ao֖̕ ۷>&! a|H+&x7J:+)A*E6vb~hlvq_|߅T^GL *l$@{'ѶG&*2r U>EH 8eN:UvOj#u3Kۇa/,ZCn“Oli ڐBwP/6Dw&n"֎GTBϙ|uζ,Ԭf~qJ\G yl:*ޗ|e̖J^_B-@.,S*g_g8ൠ-^6bm^LVEZ4GXQO'^]KdA 64CS5|!2)-ŗw7JP]㜮m5@u!Ժٙ-xXO철KQbːdB"WcgtvGf%"_ےy+JGذC-SØiR(C<H?e4dLӂ~Taғ_cO9) 9!ɚ 5zH(T b :M,e` 5 9W\ M Ct` u'0!-V!Qۤ]bz&A9l4O*">aK4f$/^4. ,]c4R%w8vi&IN،')pee]%/[x[ ̷)#Jm{!WoD!DxKFY0ժ%\H~7 m ԡZoqW8ܬ EŜrlo&la!k~o{U%$񬋸:S;9KK2ڌ Zp], >ʗn>K{iu] M:6ŔrKf֩Y*ӾRLHX+:Ө&@qc%==#H?vY({]]Qw;.ř*+`F%༰Y ĽbmL鳣p]dȡa&7̮dGE MW^7hBl`o4e{c7b ]]im~9IuAx1FZ4#RFEʡ\W%,~ykf4t`o (4hJ pbWtئgtk\%'@WWB=IA *6VipI[_iRn vޏhzRq%p 0G` k` XWD@+C+]Hm~ ɺp IT`)[p`-nl-qffe]od%(G-K?s]uBguB(,CG TujշɌtE@(7ͤ(%;8,U,W5rO;/w,Z! g|ph[S1h _.v8rqSB bi2Ay䒮7539(Mk=ZWm}g!E¸aawnfMpD+D7ȣw׈v{pvKo͒q T,u5Xh&)@9T8$Ǥ0Y]KY`G܌{mo^ŷMeh4' t&oLTH2 錤̒ Red!&sUnJ;t|kTn-`g|Jn#E']8[xzAw肆0 7-(3w2h6 R[rv,?F q#;+[9Ѯ6|RVIՀ0EPӒb]m,lUp:j7?F!Q<aՖc"?f/z m 'j*&޹7"ԎlHl,\!<K[U UfP/{aQS`ȹ*܋եT#qؒ fNU]EnrQHq(tMT ٹ#) <@Ɇ)nO4t 8>yjL45MJ&u! G!Oס?N0m!;ا9h ")[2 S]$^[:ڸA~e[`+Rzd?[W(sG ZPU@}%5FKbfK|<5tF|U\Ġp~_Ui=@-*_!XH_s{.*%-p -zc]x*?ٗq +5\Ȏ!)Iu:|ǩ'AiaMO-$!J蒊Z{2ђ! +d=ƪOl& Ψ;F@I+4"(![ArPQc3Y*&\ȹJ&7XVC<=" '- XY%SI%W/#&:cg8)髞i*)$4 ioLt)[62(aKGruBy* g4ZpKMJQ8E($J>n#JhqċFv&8Βz8YP+ez(DX~V2h$J:Z*g )9aP`Y,_Emdʇ%j`kM0&&G@Ղ%"ҍ+-OecHVs)%j:zwJ_0iZ L0H:}4x5*A<&씅9&))FiÂ҈W_?ŗH* ;nl@l I5 nbɁQ͔% s8= f"/R8.)='/a*?g  ^bƻ1bpN=n'J _ ZhI+c6i60G(e/)S l&'PTVrMF(Id6f"?~S rM1+~M0Ty4Tzb y\HgKEo>ogM1ZE?@\ Rxgf~sǴ=l2pFa}-3Cr+m];h#S<@odݳ)̇vMwdx ֍,(,<+kAGIEgKu`4pc"T+?qu2v[$k]a_58;4V~H(׺Tc/zxKA+QlWt_GgۈKQt"()6QlT=3#PГ*H4Xuy=vOWi"/_mjGm bhnoy$Ct8/;jXBoxZ՗S8wM|XfNa; ҩaDoŔݙpFTHȠ]U|4@ĜnMz5*(yCEA44 7;=I P\]y5,I7'LvSk?x#R3DܒIGTFH36`7ң>ԞN@Z1tGC<6:,Yqإ8 ]gLd΄\ݪPȑ N@Ϸ7Jy DpIECzVj{^+ÿ,D]_OXێe7Շ=i{y5"; lՕV 'b*ge']ΈK,򫌢WnuLfQNI$H}zVGUG)&I B3'>}*}#Jog4yW5@^`ɳN#9Oࠃi~,CyKI LAja27"-#H Q!-)[r6vSm{| F4DSzc U&Cð`fk9. _sD;sz_PN(0oG:}5a3ca+'[갤 T}1H<+$ HR2\g&Aj{r\A??o2T=I`VJb&=UHnȐ}2`dV?%g>^y )@'#@8}*?ؙ`V$'O _f]ɕmm͗1Vևk@qQRW,4,܍g>LaW2Pˣ52Oˏ+ftuy(t:9~CjƇ4ei/` 4AtX3ĝd hm>0t+~.<4 A[+\H* F{{+m !b N"#g$'g{Rqnj_B Kmw ȟFo2``Z\cs, + ԝzEʚ!CCzLuD$ nO\x-50懟cMK1c-TΖ7ݳBT.3IJy2v3 Jᖷt !R 3ɜ4n;#h1߫ mᓀhE W/5YwXܬL> )8BnI$T-7SmmmHqEΩmmm%e$I$dKAiJpH lvޒs$B%f5nKWZM͵zŠ":r;xi`%zڗ%18bQB~bׇ̭dѳŃivM൦DFy3-:0_ T?H810%7NcI'lg]88?-FEcyǦ-̗:vRH JO0+EoE& 0a&%ܟsp@XʘEL^ᦷ1h6Tֳ3HBlq!GftQ}҅̕$J6ePJԑl&dE.IXmZK [5/G<Χ_H,)}>< Z[a|yrDZ6'-e5}Du0MsZ2u!$. Dͩmf?jPN3;1,ys#,>$Sk{b&h 9P%FīZNdFlp&gX%Jsk&2 0xcu?)Vu,"F[i9*7C5R_;j%=6vژ/Aa[.]diNf~h5 DŬf6^7ZP)ġZ U@S)t% Do?I) w/ݣo[hFZ,at?V  Yms6ĉ ki>,l0ܚ̺KJ+-%65P]3 V. 6֕a7 D LjN[. &zΪAd|mq$|HHm&w"1coq⢐Cߋh1Q))l"֡-əƫ*'Ev7%CdBcjg|p3X=8|\a!bOiy6i  qy2\~"c O/, y .=KsLg > $~4.+`ֆQ~WeѰ?h<=c{D~b?_ 3N:tg]G-4_.Ѓr-' iڙ_M;@GBz25r0K 9;?T x\0J@Ř%WTFs+o$v/`wMN}C w7mK$X'OZF TGNg;+q/?w,D26+by:rP "swh~xJ3&0WI;XNӶ%=+\}F ֌ϼdIz|[C猼ЌRP! ꫑AŘVe1ڈ3Cu%UF zuqC؅fr!"{0(] o5!9Jw>AMlKn4m/HCzoFCju7?o,ʐsm: osPcvDfN,yjSQL'Ig11Y פ|YJS"m+ Ct^sXbӸ6k1^v74  Q)|amdj]SliKnQPU P0ܒ#|n`cnZUO JSvd=JK,G"eZ& ɝ7Y_pdAWV`NCO,`0 i8(ix9U'1fǢVe{>'@Ö?cH`OVQOX7.h<5[54^Ց} <"v ?;Byx/ :aF]K\CP_ܼA!*ufY}7%ST(:s4b 4!{o;#dFါ ǼQ]W5lu2bӑؒu~)=wZ@zWN36G^ [q?zف=`S#"ϝ2Wd8aP4ͦB^ر5B 1W1wfBTo{Ps0g- _9AkZ5n" [` V OG0?g f:=MqOa3kG8xx]C( X7 xuQ-x덽ڞv^ꓰF]FTۊ$;(> -|Wwh8IGu:pS5 6 CêS 0 S@ U\[wG`0YN&bȠBɿ5Mi{5J6H8EO\5'MP3ꞹ6](˯uhQ$7U:{FpJS7MQLrGjƂA`s?t׵ (=VEVVJ.tT/U΀uoдI.))U3;#l(rq._+^Bar(sY(q;Cc܌K[gWVX25ņ[};GOdZORV%Ê(mא\cʯ$_ZH&!50"תljE{vGK%,i i4S @>΋]V/f(JX(._l+܊ 8o7 qgz ='ز9y݀O>d(BCޙ!,:JH*[)UfK:4인O4 j=,t#9,J5>ȼ!Q6)8JG!H кoNAJh4f|q1L$己'NMj k0:\u7,-jlQf_\(]Xwr!>;żpJ3F%qbL!I G?&.̶:v؈N0L@(Jxڇ'yK(.F dکzG=cᴲ$? }[KYاXKZ4M`.Miz"QPbnh{oܽ00jvQv0*$f!t/ p&a*fF7^[H.QBxf1դT-c_D̬yQ"V `/&5zR_-iw( uB lښ '[t FZ_璾{O5smfA]Z糊C! 4p&'%ck C_ȱOÈ6 Gϩ醦W/ 5:lXYwРN;@&Y>;[o@lxދUL`Ak8 4s|ka2,NU7 VMG15!s^  z[1p%pb6&2(iV b%oDPfK_K^9w29w29w29w2^2vHJC!FA IjRGwg D扥Dxʻ2+ zrX,WiPݕY $yo&2*G:Z<Lj:0:D< w GAqDŠ%0) C`6=17lvEAF 䄞8)y'љ~;[.$ыdy;g c+/ޔw5X2l 3#7 !=Jl8<>RM Ǐ(pg )6{2p/{۰9z'Uf#K>Jm* 8*OqOkC?8x^Bh, p`rYPåodM &YgMSSF r 7+ T= ]E\!gui^aS! Mp.ZWm f?3iLT /T`=S1l%4TNV.Lr?fz5n,CPKn&sh͕f-Z4ci)Ȕ@]QZki 'NKjqW|afW3WqfgT(GDV_1 3k3Ϗ=j? I1W." )L (%SuBZYDIXǮdd.tK=ox!2 e&Ey@kHJ0gAH/ld ^12R-0+|29|\5AzWՏ9ZSjmYA<7&ۧPj(6+]lS.Aokzft+rj굚+Z5i$AXrб 07㘲UPdlǪ97OiaxNG- U|\s^N-!bVP lM3`zvvN@2T밊fNbM!7!|\v_@HcU>ksN"KЪvåpMN1;i{zqۀ3SJͷȯ )5^'fs-H_i~ }Z42ףI@BsUUQ j;򬽎Kںn̕Mʹ&l֤@̛L׎>TsWV/fŗq8 [#5(?xqXe;!G¬nbOY\92!~Z oυIPg.7p 3 :'O-eEEFx4Ä`릮NPrù=[]gq֪RNN\®lxM1XL@JyCm)󀕠w0B%ǝp xbOÚlѫm ,hX3 zl jTܑmjf7s$Mflj%E$/>Eby^I%xJdOr4k^$SjC3C z8?qHXx޽p(IyOO|D Oc/ R@GĴ#骙߆AaOlfPó/ w5)n,.DݕTx&W|}>p΢Wܪf![PN"'$ a޴Qo`o>vډNC& HYΛYaV5ҹ|䂌D͝BR +|yzs $0Og1̩CM#$ae&Mh8V3&G)9pDZ⽻%tzFz!tWHכbc5xJ8%lS438GMjfKX8\q? 5ZsW2> o%*Kg-Vx`4wBO\iĆPv T!,0 dkX:!SM دAIjUi&,׼Ρfk )rw(V3ơ-۠;5VA }+\Jfb3ye3奯0J( 'DI?OÑB)L!*Dwzk><=G|2r\4Ä%˄tk!s4l=ԹKU-) Nu<0//y4[ =" F8}lyY 'E4`h_ܞ IO$Е=:{ ۽G9UؠaCw ̓^)h4}X%YM@gm~7A&O'TQV"ً1í3Gzgp+)OF:\=t/R/k ]ѣߏG#}j# mhFD^I>Ua7Z% h'륪`Tb sV)1( )>cƁ_Vy5֞R8IًaZq;:߫EyD PD$+IJ;G2-!:<ɳBR6fF^0eA>h҇řھS}4Z< 2]z6褲h5"6j k>T5PF0QD*]\K %†/nw07B#q~uqd; '~"V)B@_ \>Q !w;, F-U]6F.7ưdA$2Wk}մ..lDߞJ2v"9 &?}>bY=, =,k\!0%zpʳ !=G!l|* /ޤ =CR/oin0KqQIaHFA#d1'_;:릒{0Κ82 rB2iTBrC,4 P6h7P¶'e]2 q";VRȠL@w:F* 9n] OyV+!u3p % Gl0*^$U Pb|K{$V >+NRTs][?H DdH!tí0Y*YR CeyRZT2Ni}5䏤SKS&AIT0V)ѯBc會i/@%bMSBY`ƠS@5`Z asYA?BsʑX ekOvi;b;50[dwM//QdžN{1)Cf(տxsNOReF0ns7 @<]meZу+y):"iܠFL8[v!nƿH0IȮGhb|0$jc;k(d\*}¬DwQ~´޼4N ' =+sNVzZ2^BDϟ ݃Bv.yY > +bZa*7ή; |JӲ* 2P#ܼORQ\lQLZEyx_(kEB@e tH8!D aSi˨ -ӹT?A!xNs6Qſ.FLbEU Q"Lgl!=ǭeyz/\-!.~N6Ʀ9LxvIr$\cfh~S*@]췡v?Kmքp /9K..8TRx= *bJy]`O"!]?0rB|0+(G0E 1S!;EA득} $j~k @l':COC\d w^QMءJgsdl6ťcŎ PTYHQl+3>`wr%nYĆo}된Wq,XY#I\ MR sچlw$=*Nӵ86ww;JPp} p L 3 h55}:ނM?{۹we^7uBsq*}E7&,xc4fs``hD`O6i֔iWw$9r9e@Vqhkq/e{]C_.,yZ qyZS^w) ޜ`+xZiex^I'(*ps ?A42MUZT"HѠBNP&I?ҀYXPi+:Lu9tB[ϡrx<ϳRmΘ].vU.Ԑ; O -Hk(c0*!2S2@b<} G n@gk /9489t'?IZfRwRqVi2$I<F-KNts'8mE@8I)}jm USVm=ώ  lpA?I4|K[e$Y<-x oLcfDQw4* kpt9j^B4'&Zoj8n`x|X+Zհ/J πh`ԫ(lJR_C.k>.jL$b474bWKؓ5*%Œdxrc-Yr|N9oa1(O]؋͗j{$Z0@KLLA崗M:w@yK좭2|FpW;"oG͛՘I] ..7(̚WuӱFOyɔWu= e yn8UPc_vZKr Tͯ02ԼQG\JDӎ`Iv0e^q4MM|dw™6uOJ]WȈmr,$\NVJOuu kA $eϴM#%<9m_vkHc)KJqL)0.Ոtw!j~])-wkwxO9=V慘϶ ؂@,'UKpi8ᡳ =.3$[>0Oz^4p/)Q|V΂MBGӎ.RkC`4h$U^ftg<qQOK%핆k|>@ДGGRaqo\W5FlC~8 gi.9QQΠz$IY´ZZ";#ǫZ QK6 C^_rbRY2" qJRx%3(q!ݭ%T;ܽYtQNHx&ɗS@P(0ت^눗b~=蘖596S~(R%D ?`>o# O2cK!JsҒI5}R]:g+ تxhf9;K`\ 8t/ GMxSEc3+j_~=:_s8 ov.GN>)_upG!C{uY(e{XUK!v1 o#' Ͱq][(M25D#" j\tc,.\O;NJ$^8uO &nk[A꯲ê H2-^RUp[89u@*T. ;.X~d!Ͳ@ϬJYω<+77׽eCV%)˖A ,[up.ڢfpgR;*wx%".] pKO!yH|)':kJe/AΜ:v^yq;nUemıTI*X\[C$,!TߤJ p0UxgBX*>(; uK,7 `OChiևSJ%dPo+o8e9y~pC6OIanSA+ZsGh% 7 kRŅ:zF< #ƝV:̂RmK6?IM"9T,EukY D_EL͖v1'ghE|^#C"BF (ju̡ 7xsV&S.+pMJ`3Ÿj}Q:߈Ц!cBK>]"D=S1|9r"AH4 W*7>+rFH8OXp/] F E~.wTf&f-}Ab]kIqTţ^c-P89BSԏz|x8 }R".rPԘBAAi(ntOHq2y^8_3|=r,7K>>^0fSSI(otN*ݐ =i"BOHɥ-:YfZyrw3W~j飬n$ .ƔY@H' A)q`5@|Z,S@ )]a }˅}1qTGv Z 2' kQC/o,Ā2 mCUy 2I~ѯZu2Y!"3Nxm^y>*T(r"Z]Nn!n18$A(/8>LK; pv|R9ٟ{_r .f[ڣcv6Ef̫5`|k6O[N蜓H$kt?`}x6S>Q5]!$"Ղ6a&-.@TH4+&$WM\v7s١1ݢp>p ~&W ~Eu; *p#Ѻ:pHD!AcЋگ| ٝ;:d8'[v*zl+[Z8b*VE <ڿnA3nIbXEu]@3p|Dyw?f]gP`(Wt]B>!Fy";SC DU0Do>/`#~)mf>p9?fl%jǜU,oZ^&8V{1~`A93,up=iOr*|ԅ|dǜîes ;kG,z4;G ,S7 >'sj\p=-''05q]ho(ιWX ^&D@|~aWe|V:}c奣@m͎\S|yTRd,BtRR@[/2/A7+m bյZb*o:ԧzZk$,=GJ/\6I=DiBn`)ϔQY疄jz@) ?GJbԉN0V_2 &.Vg(almH$7JDa|`Ԙ&YOpd9U~-h.\]Œ]4PCϾ%azϻɼzf;_<\2!wG߲.ixԏĨ)Dvr5SOBN]h>ĭԖWEⓁU6`γ<^Ixdj:cu[|:`= -"cDR#D>F!q7@v@8iXU p\^4k_DۧÑ)4U1:y*67%pVG\+yg_t66=݇~]i묨K*ϩ}g? Pzzݪd_ns<޲pY,¦9g!4˞B ӻCF4fCAkj0izK>hE%v:xTOтn,,=*6L`pP;6do)Y83=~<0t^hfA%"$0$ Q8gC# 䇡tiq'I} ;6lG-)< èԙF 4`gm`\|G }]ؾrybOѴǼx)y9}N^-5E6YdPG n&g BՎO_庙~ݸμH7g)g]jj(Oy'ɔv`z sny}b;#8@ncR9"1ڭ9~I59lPMI=%o>-AYPT! x^TgO'ݽ|SD, ־,p$!;>~g [yW?(g^Z FsĤ%SYy>}hjcAPPc4y"=ҘUG?*dVfjxt,ܩV\vbm+yZnD",!7_)P5A6эS@$,gUM'sh䞲8RW/~S 6_L}o !w7ˈrU|Zfy|5{ mvP;&33_ +6K`a71*Ctiy}HQq}1izQHṬ,6 6+L {w'-lC!ڽx )YS/c7ߤD+sÉ["xj'nHeTj|JW 'צ̂UV&rEh)`|55 ,;+ l|ۡH;EOs+;9)BGpϗ̝29.3b@$ x8w UEyelԄz((傤uUt"L$vȫ]7k0 6@YHZ}4즯Bwv i'JK5'GiśMKnM],z.0cϤslG'Bt;}pE (Tc%};N}׆zH =r taA::<|fIXApl-R_H]zuNv[(|-OQ9 sӍ$Ȝy9dz,;X8֕+1.qb@wY-fd3Pt ŋhlS\rΟ#`1ۙƺrHJE"_.Ŗ%ӳ͜\?Z*6!D6Ƚ\enrcDt/pOpIF5k]aܰArEը8Dn,UU~ @.jZ pfW$%e&>Q?z6Kt EA (@vsXpO)ə0#tk>؁ДQ.]+kz| kGs5xϯx r!7rIG {ڥ- AI6{QTs҇9P(%X.haZ .j«QrbKZ[ED~͔ gG@tvӾu\=GqDN^Y?seh%pܭcSC`a{>ڪtQTnriP2>]͖Ly]#-<J+TA]U<$%^ E1^xj";R"›%LG$[1EaKtl`Wf_1ŋqү$+̵&X%}}OStؑz;BْY^?,k!qZQI!DR+~S^kTBsQ:U|4 KwYyx3)߁pJ YmsXN &OBTbK6:m$/@9\I2_n&=tb)9@`zj ߝlL A@E82V8 2LxC 1+lBf~JJ~U/t}:.`g\D~ƾ%)\뎁pbm$ۜ0F aސ#׍X_K_b]G0N 5CU'mA&N d M'JL͏.\(ĕ?㐚l;u͒ב?WO *KO s,ZXQ,dlDG.cȳ`Xs5nsij֝,qD;Mmc%CUb=Y1PnڠˤKS }8UI3EI?FEOr\<o|OiJs%he_ṰRHZ͑ݪ2F A$c~__]$ǽ`*<$M8Cކ?l;8}MA'i`~1)őh+:S12iC *i@k)l׏i 7~! ;{jܑ8tg`E*VWרQѤA Iql/5\yS"7o:U ˮXk9; B*DW. H$ϻths&{%G/,Mekzl78&OB=k.: !"T3#ȧ4PCgt ңr)gi,BvAKMN,w#8oxMGnXY}pmT-)AlOHg7]G(ńqvj.AAcK|43[@}iD4ЯETţ"E?R* ƌVPr vg*ngFs䝂%=}3-ىM6GcT<)}PHH, W9U,UgTQG3=˺+a3\<h}]&_=tA֟zܨNg(3ߏ[YeFN&<*|t@2:6yY_"heG'*^tZuũWȱϡ0ܗ&Fn;M'm4f'P=d2u7Y2D=LgtDg"dA}T UY<\vgIO6{-K8,:NYs++r<RͿ+lBZ y$"H,Ebtb,P~[ý>ZCB|NEEIy{̹04!Q8 #qoW&s6|x|} y?Nɛ7:̶b駅;\,Aj0|C)f6+[ 5az+B6B I,Թ8IZ=ptX`-N5?0&gyL?8 $q#V~[50d%^o:Wtg=nl __5lԡgڇ,OڽD"2GȊ5&V(E@&ƒ;d(̿]Y5,z$PT;OA7#IFY}ًANwDQhui@";e~ +d+e ȞOIZ>H/`\{}w.@*3O;bFl ^U⛃8$bZI(j;"1glpemZJU km19vd3RMp3nБY9=1`,K ׁj"yU%*# ;) 􍣼{J :8҆5#^{;'fɿ{= SvX)Q\wzjK5 w#N~/[ 9RzeD7'?ˣ!\>C2Y~U=n)CK{A9GBS>rw+Q>p t0g0S*2oSB>}$b˿?߿si6xb蠀F!3t3=߈\ZZ[b "|XL`B"2@4.!Q/Q,V*Al%Oia$aTo}Ø#\"4W mGKpfg!J.QV Sb9h%kx~t$NvMu~@(sOVO/[t3L<_Y[)Ew"b4oA}{f+=F/韔Y<ф4i~$ L#$bqC;ȟ×%i@WP. \kǯHNuTt |h ΥI!Be!ml1U #n?Qp ޽tOE1k(S:6`ٙ?lkHb!,D9c?O,P +g&94ioZOaa6Cp&GYAt?6N@*I&Idk"za%0JM >`J *72<0fDI75 #\fbZo3:/ctH;m{wr8t;Ze0UB=~к6fO^R U֏Bf^yFl_xm՞EuF*'Y` p ʯu] ~ Hb&γsRǁ?.067g1V}g|7l^37'B`7;1+,f4ui s咟ƒ&㊡D"ۈ7lے9)P;cݩ @WnJ+`}7ei8v7Xl^Bzyh1ȶ^œ[V+^lA΋(DWj^GOZa.1 QQ$Σv*1-?Ko0g@g*d^DMx#b?ɹ6F=DnI;(804Y20s N˝]9vlEnw=nez@o*aeNVm) j˵3QvG8yĸ⤍s囦#=͹6.銤>(Ta'!<9y2էhW9g剝Ed0`{[_ɾOf"p͹N"S^Z.[]L&dȿws):ڏ5kV!v uY04o4l" 'VҋfjmddL9k&/<"Y6Sma?cIn~Qirи%'xA{]4]>rA>Bj-!OgIA0oZ l̥cuG&6v}2[;][Z\5g0"%Ruj\InYqL&ؿefg-zܛB뺟Kи3"s缕pb1X͗0 hc{;Ԓb?wנ?tv++T#T+,i=Fn&{ t[rJysw_6p1[B{klт4?ޜ8*e%AMFf%X)PH|h5]H+yҘ(?<~9ܑ.i>魴Q %= 6ܒH .@nU*zp[Muj* ?o%O(!ʍf޺ lГUsb5%x^](5.F10i6`lĖFzp_9,>ډxpVȫ;iύ lۀHO`V Ջ` X?aRLjcBn"QB1Հ8b$),}^#A$ dBKS=40(&ڞx'kA{e,Ƚ=Y !r m H֠mN$`!ɍ@Dc~Y%JJH@^id^t4}@Tnlq<,DŽ ?wg;[K׷[]k}m  +H⤣H%~( sxN'Cwx8u=#5ovDZΈ~>vb$*VW5&?PTM٠A^AS "ܿc5"ZVSfkwɬ zX72_ųsa=~&7F<io=0qt 3ӫkGe-"Bc_hJ<'C0};z/'kԜ]μ _SN@^f"ոJL zb^}*)! Ӎ3;!LOAIk%WOT1zυk+NQ0#l=h6=|bȀ %S'xjN{X6dzmK'$H*ğS0)ܠےE Lf*6;_̙S8*0*ӛ!H} m-KeW.MHBm3^4L} %=i΃k~lq-Au 3muj3f7 @zڮAʞ#DUSpfRodhNE,@nYNĵ6 F~5u"7OBrI$BK-oP-*{fJt#'n{_&aI<ߊt-:c5q& .T)MTv6ఌEAmJ3K.QN: EOvE1#omyFVɠQ Hr_B")p, ty(.m#&F̖ʱDL2:mͷ|ɖ^XT]_gǁBxMuTX=fƂ޿`Cnrءe TZZ%PCiQLΫ?"wKZr.㯷)= WGegZF O8MlEWW.=H>dh*kkxΜ1mx6F9pbEOH- 1/8Cȫc*("@t8js4(?*H38\38`)ũ]ޥmR21es,If#7LW#*TGSʋMAA\e-bb셂LC ) m$!ou[rW`r?u ˅_, DRM2)mx:'_ ZUTg ؼnYCbِ 2T) d \D΂@lL$x3#pEȗ*4t5K~<яt^:L}(@#!cfi2wg`Oq~b iOjZFU{՜;X`Jw@dL4)ezP7ą,!&uE]c4,A'Za$nG;fU߲+}<@fZe|u7\"5 `+Gk8&:kz鯈]O#e6HƵVS#TlOuÆ`qmHar/3f` ܋:k$fPKIĭRZU*,GZ4:vX9a]\XPՒ7¬^|p_ B10rtGA_0d;K,@!ZQm0ӲNkHNatlhOyr4×Q-2(Igᔙ'ElkS F̨g4@9'r흅0Yrhn8}D;=Krr W`<s͏sv.,1bRBZȩ]ClW5"h&pc! {4)LAg#t*+! -_N< lo6siEiNa{0\1kCN#ٷ<2g 0Bh L+&=?4+τ^_]ݭu{IrR"3Z$KB !Oà q5_TD6֑ !{j3%pۖn̅-׷WS+E*># ]K,իQ6[ڢ~:g6s/ Ūu:K+Vy]գL(si);rY v H}z#$o4KJ fAtZ4Qq-Ekw ^`m<`›nx t̤O׺m~2(?(^zpR%ntU| @6$l/Wd]+>_$-|W c=${hk8T{=vzf1" % 3[>rgzBc¡+@7~єIdNJP Úyrv3eAz\Eq$\ A;tA#rdz7RSRvX}uO:CVpb/璴0@j!Sf:?BVt󼌽<,1l"Ay-"!s#k$ :@Ď5=蟋@9`ARrJƅx)4&,\Ց6 HpMD0ysm>/QDeɸzh#1An/ >.U;^߰dc *2}n~pwߞ;afV;К#@ݠ7y.f&!L;fl4z botb%pKR{BB1@xľ"bNTfFC)퍞 QWvOO!gD۷AlFdNc8/o6-c}).4EɱdMfB0E;uaNHHuΊ߳v`wR.o? +)Zq#\ d.4zv7 j6o[g@y(]@?~4i'I~pA? 1=B4.EbEw{`.^^qo bϰAғS9TDEk'E=z&(04vH3 ,*rf-d]J!b_ 8'`DoVֶIhjg1x4sZc*:x+w4PPp1=`9@Cx]]~|YD!vD9x:'o P]].r2'1n:oEnO$foh~|ViCEyW/3ak UӄpKq&[# $8k'bOW$~T#9:1vq~ ݗ gQi8Dn^H"Iq4eFWaŔ o;W/A(}IY%&ukOҪ(rt0S_`s Z{aRS,dNyĆƧ:䭝JRS"$7v!6$,9ɲ}EzG9m.5S^}2zAP*%{R1̦2(*b= +kZ6D29,l%a6dY];sJIb.-C0klHxObfѷO{Zl\넅]⼄ B ׿B]Nș(: nJȜLZBű0a]nҔ7ȰD;i,kIba(5/2?cc]j$qڀm[+|R s=2Bm֖ꙸA9^ohȱėp. ɑPfx" !Ň%/dT,Lا.1(cr/!sRR:jt.@\(@8=V0W ʻynXR~4[FaQvrmFensAaDWvowڨ_y07X#>9 _íOa u G *x.&ivk{8vt:2% kї_$[eW"I&S=˒ҪFY0ž+\ј򨆢 V v#uG:-TBcOy?94m,j'{kx,~ D Z .oh#U :,re_#oK̪Z-%p] [n6 ә| ʺLe;ChB3(i0*iEQJ8,NFEgZUO@YJ |=:O1%Cr{A1LTUQ^cifL/*MZ-,܇h&eJ1A=_@yj Q@ (F϶e[t5PyU* K%/t`h`O郶 JEX6xH.7EpAc_۰mnrJڎWK))ncvMw dQ+!`2",3'9^20St͆ Wr CO(PM u.Tˢ!9|(gST>P_fBI胘ƬhOٓcH9MF T ChU">UAz'N d}to޾ X=m{\v+5W7q~5dE5bp{20= {]oFCE>}'O!Rj%ڷTk(\Pə|8H"22XuU?^1'X[vt$MW@ep[tR+!2@fE4\LXQIVߧa/2F~u駼?’b b-f)(9荦cMPMoN5l5.*j@ȍZPwKp|>-g[l Tھ+WN %~2p$[$xBa[`+7œlO }:=d0w/Hđ3x~ܙΕkJUSفŖѫN;n國|&W>fԿ;;(rA~Q,L6~YUx&XEp;dݓ) v lED 0> p~|~,]cIs2:$qC@SsEi$T̵V|)vbh><3Ƌڳd^-d/RS 8GcK$]CL-JnbΨ6OPuYr8\`MgQc)kY7?ya}#VUUW"J8ec)v6Rwu6"qUŷ6bH iDna/*]\y3^]_XJ) 7@%/k 87sU%y#ym^2 ZV0 \#0U,Ux|dˏ;ϼ/Vp8ed]s1>;g#@ dHKJ ĺ1Yn &8I{ڗbmRj>tGXK/@gkN;vPf/ i$[gɌ\ .f8 1 !#!qbiIPv "7\n7+ 6ttjz#+ p̾\ٔ\zD&[u@!˃\e"A}Ũs\њ "Y6Dhcpj61mJć @! AOYy> on4ZV!k7)u#n^s7ƈPڟ-Rę6wƇ `(/ܩspqg1#?YIl0wƝV.Pv7-j]x(\c:HW:d&8q:S#yY C0MFlq[u'cwĎ<ʆF x3і2F0G;'1*><]S ap'k$}v AUO/5/x),},}'("68TscV>bRx ڝc?aZޫ{q{AWQHřVl>y:c`4A@髶 OpGl籔MqK:QxĤ)<cs7>|DNC"9b[q;SlXe-kuQ:ݽJ#z2=[HFr mDZݢ挩!.]J0n {>brM;QbB j? n"bj $!<=>mvN; lhս]_y5gOj8PTb,i.@>Je@p? h -%<モWgqyX\Iêo/76X )O ja`RzoFx_w'ؗu<~` fPTkw`C}E$Ti?zHhi[n 8ho)}6oN(aY=JLs%h|^hmCY ;̹7{*J7+{U _0eN:PETx^9oa8_Nv]DN2R95%GaD2M^\&Jk%ecRQ)J< 5mG2zpAM.c=H93!o˰<7)-G`4@9eR:Քch&W(;? $UJ1dcbЉ{2t"0jWu[ ?UǩTSp@H-X:I> gfmb^Jw!ISV1#zB`gt\T]ԌN~ӎk(K]$fLÔ/5L)c}͉nK u!@x͍Zi MDB'̢YIΨh<bT\%;zULOoVp!ō9(|b\ì;Q '&9x,!(ÑSBw꽹 ,p{ 㼃'B3D(=[D£OH\ ƛԄ=zLtRvi]u|BC`AĶTe͟"~Y8CnZ3[#u=^T_Sfdt\j`j;}_t7KXq(sG@^ncT.LjOR+)C2ϩϒU1*d2 ?j>v&?BBr*4{EA 5ɯ`=lgx)\xX |nOeָۗȤtdQ`͖h)sy:F9@ep1ʚcM۠kx-Qҟ9ʤua^)5u8тrs|? a8sq\}g܊α*adJ?C(H4K9 +F +77ݔ#ܝ VP3Dg]_ݏNKM;E >Q-/62XN3&bQjΟsg!ܽ oS:n[=*A 5|pZj(] "m*ZtL׫@uY^7DF`$C`̝ZmrӡV*vԕh*B;FI ]Fu-N2-8BYb~w(? %8ł'[@x)O{0 *>\iŀ$'4`IG`oa[ݎg;җ[b&Y\@f<$𪚈-Լm.7nJ'`#F)`ߎ#JqԃZ=qpY{f} (yUnЃgP$#ffEcxʐT$A{‡~0J'ek0 ;Ԫ 7^L*;}?ͦ:dD",^|Q`*$&;8pj[CMc]Z!Bk $ ~:7D a Zj"~zU 1'+W rL1I2A$ީj=rC~T,H\(va3Mhv$b*8  ![7"s;?#>㾺Z<3Ud[w,;,`h^'ԅö-1`9oa~` d9_sb@K.ъ<<$kfSDĊ:Wc0&MW̼GE#ӟQSr^Uwl4pܺbaQ#}zRd>@W^W_:ݍwgT&N)$vֺZǞgkɻGgH8"ϛxo-D 3 A,JwfdDGI`r]ˆ}}&agC?QÃH?O蜿+U7꿋nV,Iy O7}t(36ۜТC j`FmJY}Ý-Ni#E_KF˃7VF?frBɀ2T+XI)xWS؋wҺ4k2 E ";Y 'کb5zuVjA]F6Ͳ<n1~Sr4esq@n`pAx#.GsxS{L\3#K햔_1f6^4PC X|aRuZʲoG}jVPbfyr n{?V"۰DbXgCLP~T8 ZxS9O;M rVcyňn6bQ4Z4ԫq&? [e焍搃t &\-/R(CǮpnt(: fOrQ8v!o}(?Bsb٬eP8PX 2'-hSYGaf3⣊t>poh3Xo[ %/c;Aי%U9L|=eQ4x@a̜zd` fA]bQYsؙ/? ym2E6B@mA¥rk$a}3V :, ]Yt(S/ӆMU8 a#'2E<>++f~3r;zolq(S (y V{1˚ ,D?(ղDw\<~V=,АO:NܸlI ɉ-9 n.R[Ё)_j9\DɘvʾUP,rGDmqG &Q.S\fA)`Qc?N[Uӎ-ү:S^VG, l1V@I.Rk^qp;j[L2Z6}6Sj *,Z{"M7ql .zCS-*yxu|j"z}HWGh7g|1ic^{bbX#6ԏH-5)hMHf,-Ybݙ\}+'kFOrf(p؜u}!ms3zۢG;3CNS)>P0A$̎a0 ô7d1$keC쫜3oƝtU0aoVV¿FtY>y*}/ZC"?ȫX]H|wp|KxGڮa+r CJ8{37B s̙y%$D`$В]] ,jf=!M0HwOX գםcܠ#_hM(PZq!:Dmg(9y+-g"l*NrIKzn HuĖlҤ&)e$T楮h@2KTL,5Xh*befќOy}X$K/ֶP3xRp,T%Si2wQ9{1!uG9PsPEMGyDN.QN>9@\'ﱭ#D%iIpȍh=n@BjT !/OҐ#[t^E>cB4HeY`xN S^NJh1'qE$k8RHBƚ:46oŗMȭ> /LAq!!0ҾI9JgxTN*(V0>cC@aK`Di։qDV>v RL~7IOÊs'#CxAp`(x@KhKь:Wd۵6EJȀ涏DJX}h%_UJn%&RY@y/X֬ `7=ʴx%=9?JBōy7Z O|pUwePq~J!#;] |QG06ylF:zOh1A.j1\b8:Ma\4tBfsCά)úP^|h@/UӓT1xǎWn&cxvDNzft$c"٨IyZf=hCuá 7ryfù,y3(֝Ho'f ;,Sݖbb" ޏ9nd=×%vtx]^䎬ȼ_a6}8SfNϗ*S8OE􉿸 cBᆅ'oU4%X;M5B{n®SL(J]7s  y)XXOzL/fԇ}VO:$z?2zn&Ch 2kPSiíbq6vHTeWqBA+-~f>OT- v]y[ ުm)8c1|tZ(ǕSA5aR̔ I#ȿDu7k Tqjl&5\!HNjT>ivbi $[Bu B&fC Jl2ιL LNmdVA\ZL`wb0Npf{*{nFGbZ^K cs `23YoVqK{4ˮF`RWnIiJ^/#!M5}gU &34+]Y"[΍vDy3v(t{kMxQE^BX SII]߸ע\}Ywq>^]S!:)3/.f-!T5 ~}nSPnZ97(1lPTE 9H4BΦop|4\#6-p6v/q&UӘ{%򠸸QXm.7=_7qȹMeR<'.30k]=rXR@'> 'Q抏rFK_$!DqZ:2L&D!|*Z@:ㄎ/lz^HVS{ )x:V5XLEblYP͎[tZ=ݪԇnrHZn_Ɩ;^KR R9lė- 0Ց'=GO9p ո"I X"f~2u mt._OkOUm~isdgV!@5ّ5C#ˈwp̊wb ?.#A1lv%}򠏊A=Dc9cD"ı&zEN$Qцٿ*YG7xg_4qo"[Px"6 +1YٮAo ߖ~ wklܘW8v>+wfeDkh\Ȅ=GU/w} 7B -RwD¡ ,>-Q纫ІѰbۏo9\iʮ9D+( F];Я-*(δ]*{e]k!Yz%&]QnôvJ TU! q'GJxW&󄥽wǟ֡KyGIL2C$z^{\)kw8^\D1] -M:?܄_eaCvu\1>L1""hP{ ]%Mh &!۸X_ě"(hF#Z>=BXH WKA71W˂:)W7m Q2Lk:]cT#+PNZm"zW^t43TJ>Rڏ(˽fw*-͉p@K!y2bWԩ h SQ0}#ͪSυp.(]=kzQ xcմ!vUŪ#=4A-:yrcKAhxKkMn~}#}J<JfP.}}=m]| iӪJR%BLWS! K($ Y~8 d06Y|uoҒeegϿH'*'od.[a28?/FnAkdd Y p򛬢L WÚ줻9!s"2rmYL[5PseT3m2-CvUId(d':,3k;5M42ܙ]ђ-4/wuila:iS2=fn!>hoMV^ K;sujd)FRTWQt-{ղK37,D̥#I{A!pgJM`)=7"a\ ].Ʈ""Zo.%^ XyF2!Gj:Qv{^BKN{9tIMả, >PCG8 7qaƪZ)?v}bJZ9έ[ooK:bؤTdxfm2,&K9 DvmO ߛv"A{ZIay?f F3eoWӫ9ףwKM߫У_=VJ'!T%?[TB}ه3fC/Q{lxu01z aSw3"~X{.Q['&,3?\UVx1}'$(jӻ:~!;X!bws J 7H g3vcGje(r[7SxMGhl &^WɭęcfߞJu*pX^Nh\om[Rh_׆Hm4oH7vYFW0CpYX^6R2?%+6 ] ,Q #^j&w&pK91[-:LW jO}g#pA99jΖ=2KdDׁnN7҉IX >7'N+Pg*@U_eŔT?ry g@UGz"AOa8TV &f/O% yLIdJ"&X?" b'qbTsh%[i7b m)<.LJOy EQ*<{X㴌MmiN$nB|X[O$RS&yo*w}qI&NluيS\d~h7KX-2s>$Mspvϣ^>H0.?o߄/u(ɮdڍǗX ]QP]e88BHh 'MJN.&t :Q.}+ + |?vaL[G^k#m?Eԕ4tA)Q<JCAa+ʁ/!۔ٯlI$i3D>QNjGr7JY揕36 L2:Kl5dp~S9H8ȫAOXZ[ ,d㗃.:@}&igHiԣ5ys{/(HK[=+]H"Ԫ x rZ6nN_ o.gިNOe3Cj֝:W0kEP>nH8" `/e]Dp%oS#kpS*2D1 0{,0F4HvA{V.i|:^Ѽ/ŕu/4jجFXxߐ*ME؊i ћ8_~k.0r!$=@:zpeĕn{WbDʡUatGzE}~ޯS7>oRbߴ+P`I᥄ץ:PC.8di" jIǡ@Y<bx216|P.tC" <>W ӝŋ2^ZВ@*Wְ+vL:A`Z2~:rp+ݜ$F?/ɋ*cc2XL~fzz$ը\&4ŅA`| YАiRSŊ*9r-O nğRk93ҫ#KcµQѬ0tv#R2{eMbh&S"=F\s}~ap 5h HҦVm= Z|HL:7OF`e9tўt[2bԀ:T& >ۓւ^Fa{2))V[%0XH)x|xw}Dj3BпZΪp8^FHx/gq{}E-Bxdg,XAܪe 7eeЖ^IpZ@u պβ434z檯+8*Ĺ`[OY;x ۄ%ut/* 6`zVKqe`&srLm?!0Ak/2m͛IP, aSbyFv4DyWV苴?\܄8 >BΜ~b f]e;DZ^ۆњK!wZR hG06)L&;s;-癥]( S_=.Q%aSqI],Vu&9rIJ\ ;:.%E*ǏaxqAQ%Շ }~'s^ j+dG4x 0@nt\%U]@r$6Ma66mE(.K[+9@Xφp} ُ?=g _ 3$c\y.' bҒxE"&\.TDG争@u{RZ݌w,ge b^oT{Am'e( $.j{QbU<엉:q9~YsTV>Cyoj Ѿj!3;Qt̩7,$0asUl䆣zSFa+\5lYPHb;jNen1w.ikNk7 !_'l = \K 3- J;J ~&5\q|Bfne@fiQN;y[3a.@HkvIaf@萻׾M+Ic.zJ ă[4y񾀅"fOx{$X\[ pX1s4 ~u~5s 1tA=} "#?<(Ypؚn~0ebo=QP`&fFFP8|Oз3,)8oY7`*(޳T F_}iT'@/`-A(D}ȣKܴgChK s&mg!rI*jDEX؊_Hk'Oԏ$I"#\({j蠃 Oq fW pz (T3 ` gHPRc?1?NDS<0lC3BqO@jJ@i^ ug ZbEyv3m$G@-!ZWML -M,MaRCQa~0AuH yHŐr-VpĂ59ěkL#qR0hi y[K'Ijtn4\[~N6E4a\ہ@>EZfBDq)oDQ. ey7ɴWĊ2ƺԩ{m!rf%ENGW¼odlLD1OV%0%.'EɅYc_K ֶN`)չzw|(Rmi> 뀾"K՞یO|,Z `3]Ms ]464qBx,TʝdD0Ji֞\@s'h-7nX&{' =ՃāY%zL03"g]̝QstԹ W %AizW3Q|̝,HFv`,MXJ<&Z +FD/r9M$n_\D9=u恔6'8{G2aj_"l|U$_i˼ Wo(N!|mEO>Vl5n15цFfj)g|_-d:x-0ta4/d{+"+G\ yoO`WXӎ^2g.ҶcIB%a @^dm7\t3UPU F3n%Kt@(1_T73lKԢ͢=$tY)f>(A5D`BcweLj] /3eƹ37o+$sM&m^.6:_clxg+!n 3O2EdiuTND(('3`Ĵ&)Ol&d!+M&*Ĺ/m\aur"/]QVpuľY$onWmD $VFSp(!kG5ixʿ:XEAz_H:-z:fOV$ N3NY:"2XXvPB(q xBڙr~Hb]>@sE3s0'@n=Go{n7"Ha[+{jEf7x-fC"tKfo\]XO4l~I\~ d.Ε*O"k(ks!&ZɏHʬ>sg*I c:ZW><ieɑ-eGe6\m6Dz]5|ii[~O&CJL C/H`#M(7zwJ5΍"8[8SN] Dd v2/]c-6^f?KמZ8``"\haAwh9+r}Fm璵҅ԭ>z2Iլ ~"YG!gt}іsª&w★>P%=Y-h٫+/6[1QW5aDp))TL_h Z`1m9 2%bX k@zHlI. #GRzB#p:YBNKe;(E_!Q3hJ \ʌ)#YR=[eJCPn=ҥT EKLǛzbz>Rk\-]{dpWfM ۨ,r x4kN[{OXz ˻:x `70y81P8Px$*TS#(=a^{*ž4V:[hmC=pUќI^#-#*S@1n_ePrģa_܆;礽 [ ۼ_߼طԛH4޽9d%K18!\TڅA(8&"h0`Vjy*s4 Z@qIU0/y &Eh|+jc豨E.jϚZU1Q ]E?-H[ʍ_ $9iR=h5j|E{HRZ;x&%9녬6 hw5ݏ<+H%TU56 ` IW4c0,?@DL6`ܢ"PpkGGXL U+9y5h4j_&yڳ'&+ܐg 𸗱w݇mdx*e*0pf3bl!S˦Edm}YR:q% S3u0I}-x J'@=FSљ#g)ypC-z}Պ)eɡ>m?^gRG0/;vI[z :P#Hmwv?jM.9sTz8s8M@u K]L{aQ"~Op`Tآi\֍Q 0MYfO267eilwp6&gc!ӪTx^y3`t' (g{ސWdӺ+j`d@6k(~e5+ dxy_֯ajm`:?5s2sв0X"8]7N ;µLz  VJpxjm[؂ANRݲ.vGA4N(qI6!6{Y6:><Al4Z K}x5ahAݒC"4(맢cNsblUV I8m#Wq;>E\ZB.bmbگ.f_*p;L2+]]UMn_h/a?}H|~]Jd1(sWr N$S87d x [&phFߔIM| }ҫǞ']g0f6:KɶN6_]~Y1L$@ۿleH[\ Ԙl~b`̙i6\fDh,hjp]w7=hUn1ݽIM.y Uڢ{\d;^i>?i v?D\=|E Sޚ]Q{ɞvQv}_jF'0m){n+"G{u?S7KaJ j-r}&5'0 _OToUuZ $ve~\ɀ9/BW wB xd,L$"ٌ+1q<[7Ш_.{Va?ďBwV2Є6ʜ.cOѼmAIdeuK޲cx&֜GgaOKH/0IϿۃc$!Lej6ޫ?g5; _ӋWysSF2. өT,:֨M'xvۊnUXC-NSYz$΋w# =sF(YtfTk@d_ҖOQWu$}Jcrr J%42qlR>}\.Ok_l,:?ApejbEܪ+aS]} 17O/д8]\19O( B\%S] fG4ƚ7_ jxǬJnq} qہ_N@nHnNŻ~NF=MWՏw XPzZXoIpK,gQ7t;LFtj\ۭ3S(Snl@-I F cL~BP-wN D}\:>Fz la{X=FBY[矟ݧY (zV0T5N.o4O^l+瑻dw.ְXԧ*w2@=at.߯ h+Q+’60Fa2|5YdTWg2o+/} 5syAL?PW U5\Y=[hPC83LeGtVkPd z07T4VOÓ&S1&ml!A.R5\zjg4-vtCۈӁ4ˀcb Y,J0K*gB0oP0CV;jpPܗ(rħZ`-RuʳDurO^H}*VyЋ.bȒ8[ֿqB2&Q>JċbvAFqGѲID;l_F: 9mm)cL m1pb]_IvzoTPd>F2.l fM14y_}ڣ1zOP EAxJ݇RPxV m":]Aܱ7*b2mw10Zٹ@c F^nL 0cp0N[7;#6q"@z:t}b0d-wq;&:lNJ VG,JŠ|"D/7Z˷/=)ppΐ׷*-,T ȃMˇY '%:#q̂ /0<]U2+r|\%" q1\#+_ zĒKRlw/b 0*7D%ؗk1iZ@pݬ]TA nF5Lg$zsOw\TE ^IX%D@usv60Ta<|b'F;IAWqD` g-0I[TeHoԡ~9ha6?h_6aO\e4_vדpԝ~t7wI#7{G4hg?ԒmmeV?@ !,_Բ㓯,ϕ`[K 6IdJN^%fiM d LH?x)DKedXQ gW0Q]1\-%{_a?(lTJBN^pxgNck` }?QB\WpŇ3I2OD 5L\#l4Zj9ђ A]\R"e!֦VsEjIGPT.sA !Sq!M1(e{:Eqn1?daWY򒂎;XIO*//1_,~NQ1YZ (qz…,Ni/v_1xs?T R|"ZS2Y EȷP}JvZ8Fu |_l+hJ.@ce'M,$ݱo4HQqK-w>9^[J⤛Lju,uŕWXb-ֳ:Rv4P0fFe|3{8*FVeyNnY0m#+n ,:fSz;D>dhu7tg<ᣈ#ڹF)́x,Dc VRg%`a4-LVֻ熣zz}5rwܹ, K!JvZ8Fu |_lǹz^f<}d_ya|='En*-J1b\"MM^'qbo4< G"?ag/0m@Q)~}40;j߹vCh]סR:x, qALgyj:֧,;&/]]Fy͇*oQ{hړ&JvZ8Fu |_l1sߴB_ַI\O2JvZ8Fu |_l1sߴB_ַI\O2JvZ8Fu |_l1sߴB_ַI\O2JvZ8Fu |_l1sߴB_ַI\O2RgJvz i(a{Kɂuy*QM՞6m3޸/4kj:Ѓ`[;d97t+$~3d !?1SzzTsG1ϊe-e% $CnT4-h2]  [qGթFT08 mI"vƉᇎϕ )EIۯ'CcI_g\!AdLnd rͼEHDA'$m V/vh4Bf@>T "i͵!C_I;fѕ}~7U#NB_mCpNB_mCp%t dt{&rBj)ð+Fhw EI(q,4K? >ƂA 8:R!+M_O68El|Irg/v-Ik3m8ꤢ;T΋tlEK#`ğgl[~Q ]a䋇>QYlx-ҟqzWn5ڃ \:WEaM&J\0rM%T +"jzgf W ArY:|x2ce Or;($nv)1Ҫż`]ht<X0 xgEp',Mû %]ry/P<mLM#kb񡅆J\No L{,"mq Sɠ9xW|6E<ЋN J]Q>ZP)pwJLwT0 2OQH@7*vhTr)d9s߀60xkP7r~&D 1D]GeڳXu4*t10Q` TuHZ!< MH@a s$P/ȱY |`'?Zhcp\]̗I䐣š`em4-Dg-}o/{h< GM`gC?bqcCzezo oI?Yhap]()* &7 ]ex*ЌEUQ甐, ƕ[$@_298_{=bzkfrJ~ZLW*g.J8}R\ U"htN"_BHgN5aN /zFmm'wCǡsDBHtzmWz\" ο\%v'% P D=\ LLqy|*A맷7zM fu%BʓiRo$3}(C/IAQ+-;uO^x"YQ+sߋ eB[,o?w"@D$7XUxGxcW6@,W*cq<CoI][ic13_ jP ftypjp2 jp2  jp2hihdrcolrappl mntrRGB XYZ   acspAPPLappl-appl descodscmxlcprt8wtptrXYZ0gXYZDbXYZXrTRClchad|,bTRClgTRCldescGeneric RGB ProfileGeneric RGB Profilemluc skSK(xhrHR(caES$ptBR&ukUA*frFU(Vaeobecn RGB profilGeneri ki RGB profilPerfil RGB genricPerfil RGB Genrico030;L=89 ?@>D09; RGBProfil gnrique RVBu( RGB r_icϏProfilo RGB genericoGenerisk RGB-profil| RGB \ |Obecn RGB profil RGB Allgemeines RGB-Profilltalnos RGB profilfn RGB cϏeNN, RGB 000000Profil RGB generic  RGBPerfil RGB genricoAlgemeen RGB-profielB#D%L RGB 1H'DGenel RGB ProfiliYleinen RGB-profiiliUniwersalny profil RGB1I89 ?@>D8;L RGBEDA *91JA RGB 'D9'EGeneric RGB ProfileGenerel RGB-beskrivelsetextCopyright 2007 Apple Inc., all rights reserved.XYZ RXYZ tM=XYZ Zus4XYZ (6curvsf32 B&l"cdefres resdoojp2cOQ2R \ PXX`XX`XX`XXXPPXdKakadu-v5.2.1 V_8?Irs[3$; ݕOڡ l;>PĘt29 )!f8.d<<[L8D08zcZ/.JEټ~W _Wt8yLX)c, /ʲV6Bh|rfO y NҴvuOGl_}?pͷoQםshA9K3)8i%} `bW kSL{`YlyTG-ؽH [j]n/5Ôv'T@׈y9K`nv(+&#}UoWڵWږe ''9"Nq`75v.e_EX:ZLT!;F2i y}O$ݵ8FI+h;/28p M ;v )@E  U}O]M&TK{wO@n,.71E@"^ mz+5^ I~c ?Tk"~& 9iƖ(ث <Ip~]gJ*=>U~+Ia"?.okNOGA=?XOρGqib`f)rrȨBw lb[`3˹lRO(KI)O q1'ieiNB|eI_!{|ƔC'#A'Ywl~G:+=\<_`@9MeEw `Hth?B_*4G Y|$+o CFu삭G%DuOEfermx<gҮpo 8Z@ڵEOgP<*^ M^feq1۔2gRx,~#S q-C[FC?θM]**@MTMFX/ 75oGpDJNIf0w"cOf""D: أU߸8  zA꾪5.?UnLϟ1:[!cVT3 0Wk(dўd|]ѭ)|y4drux'e_jvD Ҁza iP3[py Hb89_ǫ}@?A"A\I",R3e$+{cNcfM^L*s,82ko~I)z33^ϝp /̱Ys$B"Kتa%0؊gkn>z ٚcv2JgMQ("?C/ Sjtfm!G">M J྇$E0Rqvu JT|Y] Dg?RDqXےI$?m43~vq%@Lf"& l5k"VkpɎD$I濊0.k^zb#x*@b*J-Fk2q 1R*(ȣM!NΩA:7wo" # w`o^gBfsxUOdW0)R}< {{gaH61V78#aسqRE/ծS3A[&-e2-q1zoHUŠtf}^bGAN]BsZdZlA->&z^ގA27!p}lB(3q/9?# Vt۲y%hXeԜ>^P0rqFCy4O:\oۄps!D105@ O9ި?<,HJ/,Z<æyDRT?s႑>oh⨛c>95~ܻOWz4| J.(͒+;5JzeA-7Rt/hy%ڷr.̖F(&UVX^"JQ$Jߋh#)Vip Oo}eb.Jl9euzCFx_KѸyim~2R,.Ƃ;  P3.c) B-8|C] m'Zn ~ ̧{Qtpb{iTPtӆFYBv喀fx‡ύ>^YF/g^nNZ0 ZwZ 6 h~]Jz%#P&6 /YTϓ \N³ۄ,>@:Nҗ֑6Ejr`D pXɉ!fFnrKY@WyݾhjH lj0gd>" 7Y9W2b}e"6JsggІRdZp9LneuXgU ?deoN{bռecOY/8I0שHr[)R&,mKF`{:R2En *$cgse+IOgMjT$Рb tVF>P5_mSYYq`p21Ew`oQ  n R-4?CǣTn8pᨙyo}4+oFC*ZOZ~}X=Yeww&QPPRb? ñqEskwkwو2m(G@WVvaoK}3S 6VP2'pE#1px 3-EuOu#4{)y/8:35mEzZk:u7K{PIE >ehO޹h-zTd>VkBrRJR#|W_Qq¸xͷKu{ď\u;H6nףy.RoM2خ\~Tk3m0<կ)nʛ8%Cv{ɨM@nd, C<)!-ڲ?G7ֹM? ?| #syDc?u Z)A&J(=j^$%(_+FKW(ì(|Vr##C2jPp ꣡v~GMY'@䮡+8'nw_[_?Y>H1~ۤ:Elg3&@jZXNovJ?Xt#HdkV4ic55ei!2M1:%jjuG3K8u j |Ub]?uO]\u_Z{QL@W6Vsl2-hd(Dc^ڊ#&PkSpֳTjʿ'ʷ1$m:qD\ CAܒI$I$I$Hm9(oS/kl.7IC&Jn.r1vn@*ߙA+mu7HB?@{N&Q$:{DjΔz>F֭3a|%-_gE_qݍvҲKjt",bflp,;4ZY.2XcےI$I$I$I$Cݦ$v# ) }{Kyq*Nn>Y?zh-KOT8=9*&+Rf4yEPvӖ&gI=[t>w`tP`( T 3g84e;Y ̑j%WeP=QةiM,}AB]5H܂v&$HxS&&iJݧXo#8 sq &zMqCr"й׬oAM67 6'` 8vݧg` )D97poqJ`Ƹ?`?FL|Ny%jZsAuV\~Eg8^v2}S@5p 6}X:౽~ lp7 8z:sr;mq`\>* 8+×c9M([O{Dcqwġ\˞}>G:ŭw%~F%>>,̴>pK9&?B\ b0fM[=2lM %ᛥն1 EayQ)ͥ(~p/zAmȧ$S %1t~b̪`˓͸MKЊ*/! t!ǥF]PΟX&Afuw,7R'Ψql2@c:3hJ4$᳒z$7I f$%Q$89Z<2a:KM.{:XLO(ڧ(5noPZGQØ,.bH Z80p^N9lQ$kUw W\3ꠓrcUĆ_BSgH䊑$DdlJ>JP7KxyvL8n9!ɔܤX*elMUIB,۔>Z-_O/Z =ܣV>$<Vƿ*~Jg:&:D^1񞉜ÖeywV%ZE$׳l0 N @3֯ oT`V2Yv}o+ fhf*y+]x ;i zmM-S>M. ̭6oMSe2p()Ɩ1@|Ϊ4(3ǹb~Z I_ŕ#Ƙ|>Rg_]-P|J^MHgz,dm{V³؇@D]EK4ޔ&${u_8Bu4nѕz|ܦ9[i_ÈBAT.Pft%b# RI|NX0Ք._;7#Waf@Q9,CFdrLLq_2>D52IR,O2fOi"=>vR'D ?W)GL3{1 &Fi&z9dr$sׅU0(B+C04N|ozC2FawW SԒC1zO[t`UC=|ĆĞL.'[4kO7FCԔAګpYS},&C߫ M/PW}|Y \d:AJLU d_y;bͧs|(";;U-U&O*_9PcvH'dN[QNA3(/<(Scsjl[soLٟ#AmWs/ (ь﫫hدځY4Ca>u,MQKznr7H5nח@UpO?kH)1G$f&?HȠxZpvp@Z r4/f֌{^,+vdb2_5=1H7G8"=7^yB:Nvgkч*DG\}K˥:HeVLi-nYИ5b~ɱuvz-b<:2}{ƐħPDܭj-AR?YX ?HO2O K(W2>l4s< y ,g2Uv q5qG׍ğOTRt7yi= :?< 6DϠЈ"A`mjC`W M2Fؔ꒳T^ɤFgAy4 x z֫*.95,o!z󍇾ڶ]V)2wPmN"c~O׳y"\G) 8$#  }Iq]{̓SwqzY>OWm  g/ٟ{Ӱe\gK Z,v{RɃ‚n v8.øA Royx pR׊.784!f;1܍ 1Faޚv*+3T<;cjd ~3 ~cQ)W@wب2gs~,n /W F_ܙ6q&rm|7>. 1+s>Za܁x|);G菉p[|7|7>BCaN4h/I8_]h\w`A[3x?gz[Sbxg-.􏔕?Zx0y_-= &Q!I)',\@sZ(./^@jw^p#:EApz(,$"&F 4?::˖ K)5A)f3WslXyyQ#t!S#d-auc0NsȪ-J)!Lln!R,?^ ;~ ЛS]aJMCDO1It0AC}=Xp} E v~LJp+}!||F \J;N*!~5?*O%l{PvԚ«~Ytw߬ݝj+ E-+܆I~ٽ97}rVA~n=S)@eiFJ'¼OŒ4nٷ<1P૯j*6 $<]KgAc昜ݻo%Ę=YgS~ Vu .|6[)fj""2=iyŇC ݁z=TԹdڙSvRņ*|0$JW#(ѽc'W#-<ToܛU }}~e~$]b.Xc=^}Ow =/SQ.@כau "FTV0>h ;n:ゅj%IbǂCk 1Hjyuhbڅ&uE$qV΁!^ /F*`7pF,]i 4,l͚v yJZ W9uZZMt?j7:M_3Kbk 07pu,#dO:\r#t\QF $),..gॻ.^i8QR7=2=|Kxa,5w`;㐂;Ϟ:%EO 2{ :&ٙ;*mrjHDxyybcLůDXuPtJ#3Ԉ)ka$ ;%olJɱIgm#9Co 2:vOCNl.[pRBf>_·7[| zn}! 8lJ5xbsaPӴy9"+aEqNJ[vLI6}Ȩ:ǮWf29IL2z敩_0V\we+A eU)^N @>4BlUϾ3Z ꋶF!!kke6cX,Z,Sp|! cX?ˁ`%9bt_NS}vx֟e_+(a]*DMM:@OoV<j)cM&Uֹ̪@gPq;d<;Ji2b`{wiGD(|gdxj V3LV} %dmZq|4l^bIXP_rRl;Pakd}E ͕}j9~FN%̑r7]vc.Yϼyƚ O*[O5/nIj夆QEuFplؕ(3x̓᠗p5zzH-rikNMqnq+E˲⢱(-.}, cHACwq4;jo@sG_nNz Ѹ>œL 09Oo_-]n^wVu6e; ~2omGAOȂPoZS< =h<+ʋHš{ic?PiY;6Jw-E@L)&3#&] gė'y}b֕VQͤyr+t-PIS/~ p{Li#|ٮPG3zl$ʹTrAS1}u/N! V 0*RgTA2xTN3j:u9|²X5#28K6e&p* ?kMN[16#P,bsEkje 'Jw̾'hkf/,V5|;n|x*xgQSF3JM4-^JqLett/)C0Td6Tw?d@D v*VDž|]6.+)b:iF_!f6Kct]`=6`Y`!Aۙ8DIerD?z !3C1ffs7ܜ;6DE(R3z%T&N1Q+L,pxĞ^Cp}1z 7-{q}&{.0f1ONps,$_ftt(!ٔ ݍ=%F JȾF 3z;i0g+,/\wT#4ߜ3/H$QVOSPP&|9Mp,֭n1ˈ q{[(N=pH8gF Ė垴2?&;g$[7r!X0Q`nqh-5P+:-.Q:H@,d$~2ZÌW!©4Txuv3'cd0$XyG 04=l2A|;;Mc'A,X5qylKXg:*v8ٍ=F/(h3Sbclۢ?$@L3հ,{'0O!Գ.X`| | S$330 ,v׌*x.;@tw 5툉EZKT234MF %m[,QaT W@H9obo y]#gxۊ™,gXO{c}1UsGv. F6ya]+)8G8U:Dʲ0 ^:ng a^qxadk*[7bA[loc=kޘyz`CwAD:1)z޺y/"B$g5!cbKSqj@':du)Ӥk/mD/ SnW ~ЄQ1Q1k HJU*Qf>M-DK]CLzQ=x<.eEB!o:O$2ɛw\&W cXvaU[tPf`WFmǫ+chhr-~[jVAR X;~Ql&˩)#\${*Qgk!EZ3KٹP dHDH%!2N$Zxyx](NTK(R $H&WƲDi Ef̢cԘ/0 FAFZ/3AT=H駙f+Hena@N&}$ lr͜nduѩ+Rg g, leKVW!n+@!S d˻>f,;ΫT Ɣ`a|U8 ƍ=~Y1̼] L13^+ MpC4oBY`!L;_)J@}NBP}Pљo.`5i`*E|2"B-Ɂ(wռfi*؝IGgDI8ș<}ߢs?΋lv-WwU[x8G;g1T}Ӡ>"KZL”] o3<`#0Ϥu%*H zTe t3+ ;"I{0<Ks(<-䫄|c.Q:Çc, \"/v[D0 ^D D_v#Z fP/+N#' H*@Cdթyn`$!L7̯DT{ܡl\!Km7DBn_zOOͯÅEE\w;vYv:Gt3aXc 4L~JJpy׋jB$)yS`bXc@ H?XX+= i߼S~M-Z_4&vѽ: OkY«zJw#E .Ooޏk[=6dr8&QQ~rȶ_Jǒ t}ꠉ˾enVvoĴ7ͫt7<(B/G#BI^^.J*G#,+' g`*el>UcWkV+Á5*,hH3??=[wWFNx S 5zLF:"C*Rr@|?7^Qcn*yu e Jhy 08OCr}Gåo'Ѩw䛒Km{|N a _0F/RCFhuSr1w7& 7IDТq {:nȉlǬ5um)./dx|Ӊe+Y~:DVF@f;igUsnF#޴2TW{P%kpL?6IMR4kS8óoЅ/!.o(b@j}U(t: ʋnQ8XtS9GbxQp;ƃPyk q4Q5x6C 2%|DzN˵;2.e~yx7h b`U](@:bQɵ^ xY]:"Ӓ9"AI0ߜ 4K֪Nц~0oR_9Ep.@M,EECyks@tP[MﲬT]-TA%PCIn{8sW[ſ`] !b /Ae xI3iɒ^֮"pN2YM FmFcKHs%oCfP|46 o+pJD:ﶅ!rOE𗗄xev\q/ ':e+jM}&RV$ &>~&ClN L u0ȅ!F8AQ=oDIbi: ) yH&= LB: U Uٻ0z ëST3e$vTas i4v~@FThF oWW"5bmFU*10Ic ؔ\K1z%LVݲ&J T'Xo`Rwa=ELe=-ABAK| vTq퍻t3=dLVd4\U1eP7A5|(/$ K\p}|TDB䥅0^pݏ Gq]Wn5˵I$gy1* fU_v6 ‡;:4K9).)\ƒZ`pG?2T8"5UѡNg^&^ 㬩5dG ҟ*W$[v&M_ӚRMBZ] ]@*DfoV`̋7 $;> h7[c}ۓҋWYF}ZzVj\MBkQUd\="#~lXwF^1(f !%62.Mj1LS%G#IlGr8n-_nI*$ջ1l{v 7pr{q瓾pikA!jVdc0~?X?H#NEMt[߭ ;u ht%MwJBPL^nmzsdhϕ7/t<:D#\UL>*3˼t'D4 m( hfո1$aM?ēm52[DD>xJ##RtKgm2Ycg@E!y^y/&\rsjm{H lnlI7@_cϛg=ٹh 1ɤ;u )h5P`S3?&>IA UJ[Fl;zy8<#4GV!.o,lwU.6=pA}F!c_?xF%RRmFps#mh]|Xw Nazߝ!;c [RnXZESH_T I&#DHכFd ю$l彌é3Uy5f.a-~tI#ߠ5d!-l 5]lB4k2:(axE<;fY\|vn*H jIm>`=T%>G;@f; v(rSJ$V$~)#Yy0-0H28CYf+7 jM- N Z|C4&1B{R ]j 9Tߜɽ> p['otQQl=.W>Y Ȟ$`&3*gZ"mjoj@l0;b; L#5 }Y=&qDp9əT}v͋;I`@?NLT}%OL(玱7j#jWyiju$aƏmF2bwP3.0YLZ{ #"Е's /扦QJ?_ u [|x`QT >%57 f_MkG+JT܇i}vZbt@akl@:ͬ .b-/և@y aK<.YQ?v| `v-UGj2jO;S{L%'*@Z1f{ rrw7*ON_Ǚ l ?u^ C}tn~wK5z$Nd٧{;?PZnbT4 :A A2jMZ$Xy]նwr+_!ԸaÛwDyo4A@¬z]uT!zfZ n Ǖ^`jC XX6ګKd<Hv`AB$zhYJՙT1| o )M~PD Ȋ?X6, Ӳ0sh{yBGr `TL^ ;`9!|Z/짫lϖ3R((xhDb*e}~[X0弹tF \cy&LA_f Ȁ56ϥ\xi~$`-‡f֙޺/9.g(Pfkrg%#_oE^ 3('685HF  ^r#BߓvNnhc2`tw砿&Sd%PM*_5B bj#0黭̈ۑo@sxIל+)":'.`1E@Q-H'C355O׶ g2lCTo"T5Nѹ_A!r!Ɉ4NPXlWyTbATK鏤Ih2"j j:J͡cq ^81ƕ検z]m -@ ;\vHXII4MP2,y' ӧ,y;27Df8T,c ս -0 rKw̪_έCN`- x{/#HSaǽ3ϐ!`N@Fu~~~/I[~o&i7 ؼdx@`p!@Ő-a{ yMv/e(l[gY*Mr͕9Ey%@/]F B|..u2PBb_2$rJLoNypÄ_8Q()xқrHW= g|DՊK-P2rYqF67SKvneaN?non][2e! Ea0< |"UW ʙ7L|RdW!)FAfHE~_7|7{i;prB8 A\04t ׌Lx? $I$I$I$' xBJN׊r힄f0fz\@sAZ(ӘFP"$ٟ u~ i,*  @INjG% 39՝/#/䳮Ou>?ʸ+؈&'%MOE%m$I4>>#;P>r$xRW 3(\{ { E(z~l/M8 H#R[7yK5VJ/AH3DmI̼3 ]NС |[Bu} :{d(<C/wuckuwbQr 5 ˸n'O'ǂ-\:W2 Œ*9'$!ݵg]K7fˈ ic09 jP ftypjp2 jp2 jp2hihdrcolrappl mntrRGB XYZ   acspAPPLappl-appl descodscmxlcprt8wtptrXYZ0gXYZDbXYZXrTRClchad|,bTRClgTRCldescGeneric RGB ProfileGeneric RGB Profilemluc skSK(xhrHR(caES$ptBR&ukUA*frFU(Vaeobecn RGB profilGeneri ki RGB profilPerfil RGB genricPerfil RGB Genrico030;L=89 ?@>D09; RGBProfil gnrique RVBu( RGB r_icϏProfilo RGB genericoGenerisk RGB-profil| RGB \ |Obecn RGB profil RGB Allgemeines RGB-Profilltalnos RGB profilfn RGB cϏeNN, RGB 000000Profil RGB generic  RGBPerfil RGB genricoAlgemeen RGB-profielB#D%L RGB 1H'DGenel RGB ProfiliYleinen RGB-profiiliUniwersalny profil RGB1I89 ?@>D8;L RGBEDA *91JA RGB 'D9'EGeneric RGB ProfileGenerel RGB-beskrivelsetextCopyright 2007 Apple Inc., all rights reserved.XYZ RXYZ tM=XYZ Zus4XYZ (6curvsf32 B&l"cdefjp2cOQ2R \ PXX`XX`XX`XXXPPXdKakadu-v5.2.1 ܈nOM2)0 ҮpIh-|Jk,Y3 zExI8еʗrdz[y#(Q|o i8kktҘ6c$G0'w Z2 uVs(BOʾ;)B.ΪKw䘝 ȝ6"a_ mٻ` 81('AEZE9so"v1D=+m܅9 E`A}Lhjv p( ,l^ȥ\&Nmp{]wj@˝l䦝GY e$½`Z FQ/0!M"~*y=v DpCT5ނXed \]Êf5ωHEr 0~ 7Nc$:\4; 6-űgW·\U&p#N5G'E:hxpg&`vqbK2b 8,U/hOiH8G+')NYU%hk+l-ĝŖC >fj!P%T /M5 󛆞">p "c )'^AYnTPv|25_`cPiK6>>pXҋޯó733e3\S vۜ 6!$AIM+lcVޡ0]%αy|m$Cg׆wq $Gdb:{ztM!xX.rN4ʁcLm7б>,ŭK,ܪxw1}6 T~ur[m3l<~z'7}MnC˙?D<¯&/]O[, *t…9پ9C!6y|fO"u-1F(z.GaP11ȇKX옸H;.q0݄o? :|YUl\ANj*(ow+ܧ/%u0,N1"ZʵW-%Rxȑ*lA٠Ÿ<elsu?θpL({=*ߩU' G5ʈAt7#/EbWn 5%g,IO3jjs+"c.Fm8+++ENLpT q1r?@st:z]76UXA꾪%JW*bk'qp·$jT;kWyBG,T f44)[p)><*ؽ@c X +re L$= dq^2g 'ܾ!+2ds{z `τCTľL"ôE:(A뇀{!I|8>P<#tee?ˍ+-/ ֔Ц ]A2aW ]P@@3U ?,"Z}OiiM%3qAE*nSlrOaXy\PIt442:<@%ӏT %3F>塻ϬJW.) @q:4ۊvegWoOamҠW+qmR˸(T $cɨAl?fˊD/_(b/87M 񪃟s/qMsV/s8XHs D :hɎ'AwId]rhb,m4d0'wxGtN9XLXk~5,S -4pKi㦹qPITl2%f Q6sA5l]I^/4)KLN8^dMܒI#yQI-9Ng+KAܩY'eUPpNڳsh6Hs]h}C{B@X7r' O)öT A$Ij_h{p9} A:đ@YiWK{6 IgnI$H 2 9G 4 DP:Őx:"5YQ3:ӘIυ={^a6_u#!q W$I/>H'lwv V7>y/=V= EKC r0]ׁ/.nIw[Y\ˊ?nSL`{Ĩ) F{4[lۍo\`_%cid #Vxy U]>.a7pB_26D vԱB%'6YƜ$ l%}:6LW:҆l8|YqMwkB^ 2va]J*Y--kWNb.Õ K14'x Z1QEdJ'Ҕ9,.ȦJ$1sC^25G-8c| qy߆؏!=J6h9Nyv+51s- 3CE?YG u+/3<(?Šύ6nĩЫ'E(߇=['ی~풑;g v>KN:a=2ѩ.!g&u;gtu_~ W d) 8,[ΖC|\{(Dd6EhHmʈ|fw*yzFf?)-- b؝;[(s<1CN7WjR~B_դq"G=1[p|Xuv5F)Cx|aԎcǤ/lV^^d R8SKL ׳ ֋eOi;Ʌa~d^@ٓ,'^x6*B{kUCY@7a:ugVĪ xp%R54}oٔ?iK$\?¶qw /vZ{&>@7)WTa5+W KGp/Ba/41$oojaR _&l^=w>YBFO?Mؿc} x:[OQs WhOT"-=WfCٳ]%;FJ+ q>e ^ `ya7ɑ;f6CpŖ="իB~@ QB^888w`oQ  |,oȵY ۥ)[`u<\M uD~M~~:A[8f#>|9a6C^BNms:Hb8,|Cq/ʓ=TVIʇ 0X;9hf?'bzORcajΧ|yq24dʒ!5qM'>`];l;8Fܽ#LJN35%uOo%Us]Sؼ\QS4[+MYF{[xКȦRlԿV&S_lOX`dA@ FvҗW8ͶCd!#;DJ|d8$^Mg9_kD7 vpp9°}-WlZfhXt4tr1, 8R.w̨'JDaV߀(K-G{;ua.2ޤ4~‰Bz"Bn¥.(Yvɑ6j-G%: T 992D }\R.abd菦3 ]x UWJj XG.{Kųbs]$N @Gxd!ppq%4UZ7q|zl--7yAGoxnD`Y݉8PŴ\F9%Ad0]deYܻ4;2I$ B܅26IJ͋N}3è7T񅧨vZv\9f7Y18OWb4}z1լ\(gmڡu}rַ>hiKNczƩ$Yj̟=pjkGQ*y*|Vbn*-q2ss1GWRTui<)j ii+b3@D8BBBMdsXUe{jpn9*Weevֵ(z-_i;8.`[7x2HQbzǨ2_$(&"`W5~\?啅}?J&e:4إɹt1{ q`#>zu×D@]}ER#BÐrUM$0+gf$`d!wťp:G vhmѕKDwfK!Һ6|!nҒV'SS:ր^* BVmPm96g(g:Pq6$W<|1lNً=4~i/Lȑ]C~ &-o DrL{l}9J@]6.!5n:>Ћ ?'+E,Yngߒ ̺HinBaYDZ;gl?`?i@ Qe%*ݢʉu{6ف4ʹrkܨZ(|T}ߣ Ī'EE5Q;D:r R9% yI/NueIÔp%Z))<P Y@;ɱ vLcv7mVfAq2T,zad,]ϥA_,cb$Dȁ9+EHdO>*<2}pɪB1~"ۿI$ﱫzG"ȋ0SLȮxkmxmbK_Mt1/\K*Oۆ aRFgF`fО {w$/#߇x= {,ܺ.*s^ߧ|/ٕG 1\YV<~J,jU'q<|󋴝G-d3CFE=sQ_'.`d8~= -G4b_|]23/E~R;`ي[Rol&!g7ѶЖUd3kDle?3:QNQ)&,zSf;ߩ˃ة=ZjEdv89vr mV~saŭ Wff`ӷz_- Cpn:nɌ{uTG/HFͤtZv#3;0K ;J J.Bhŝz)NS =QMu·m]oVlS"g=SeJ!Zl2z qV]]Ll4jj!yg',<γ>D=|Fwj-/rM抟!,PɃ "6^跉%1`v'Y!5ܻǧ'̪*PfPUQrLjg>ٖet-J.,:XxN2Sxf5:"tbH^툱Bӿۋ#߅8'e25'r\Z>PyoD6_#'-Ԃ_]9] RrN !I0535rI,H=<#hg)JLVMW%}I; Ii?^x(}.PG&C1)3é= alԼ%eXTEE ~d1 !z{Ú/W"o)2M_w>9F){N' ˆe<;A$$ȣ3 #٠{ZLSݨlDU䉿d8-MlyAr5y"e '89i_Pek xL&έ8Mr<]MQ`,YsyFFiayySQ_Z%OnN i4iwy 4h}.T#+`_HȝOXF'l\M͖ #B,FJWé~]{Jc6Uqp+a5>枉=\ؼ(,PRO5g12.CR3LЍ8hQ5;R״iWcYdiI@F-'}Є lR'Fzhë?P7j htWӪcfZBČP\wZ0᫑k? g`les4G+mh4q -CuҤ_F״wNRCZDQ56LDOJwh+iMS^`@8$Zn>*S%"rfӼ I;d $ /O;)9'2yF,=yEP"c}-Mptm+% y-}; vi:4LdmdCr|qr;2/!Hٿi-ݫ Y$ȡIKgkM 8{~{cL0vQDÝO \RVz]h( F2e)3 d1;,K{G 6ti{94)v;$SZM8⸨(mӰP.33{EGgؕ9?tSo ;9~0GAES,ߣ\0U>DopXʜN$[Tgts{q4o`k 5Ρbq! S5˧!wC7?4 MI 6H^ҒH~!'su(WPιct%قqac~zs^G츢~لw&f)rG0]If^բ4DHݺA ^A}WU _#U'Uz/<$d=0[/a\B `36">}pFN01nY`[M]jqE/ЈeytV璥x/jFF:p𺽭A. '.]?*큫y=GЅ\?ٖZE@M3{ZxА~Kj`}0DŇ4nAdו*~ 4A0+2\ӏW5Byь2yMcn4 XY˄&ya˱"~EsB+~_,QHS<"W ᒠ4A}^o^- 4HHT^vg'phI"m򉟟=JKCzQg0vEGgxq9(3tm;77;}_l|6ka`W7>7h`\ b'USmEZƘS+-?K4c71!_Bs,ѣ$|HK^;<Ϊmx .9Mg04&lx(EP @uhe[Aђ;`:jAdA&>nNwFC~Ǹkd-ױ0%J3M2 z]r4=BUEmpz+}`@\k״׬xDygj]M.:nރ޸J~DKʼFSm>JMt!%h׹ִWT&yGDx=xZS<tI6Nx0( 7r8W$vFW/Ιi=纙C[Fɳ]v&dX ݮ-~_ȼ:*-(n~ÃVWFXJ0@TO]V,zz4˸Ydk^jI lٟ</ZlVvyS-繗2 ryan3|?ٕMrS׷.2񂐭 S.e$<)+-&eLʕƟY•aÐYV~ˏ?B^!` Vꈹ /~: mL2 v&I ֽtɩComz7Ρۚ>!xIIWj2Ŏ*Z"X8uXF9Lo/XoRSg$awf؄0!\m3 ao]zTE;3Q[QRm] ]*7H{d $ezCN?܆/BxĘ4EsuI7ۤA<>=qs=5FPKG։k#t5>YXDk,/wOyR/xJ>F(mmĀF>{Giwb{pyK2bF#Aw;bj-Q2 w ]';e4,e͎{C`a6 B9P݀ 3%SxɴvFYԅPapn#]pư_KE\V ,n]4s NUE& *}Y$]K0bT= پc>9#hǡVTq8+<Rr Oǎfs`kHҵkDFs<{Lɣn8$4Cm^ اԇ\ubNNRHRy`Ag<kRNoLq7S{HWF4 ҈2_K oLCslaϢ-b"Pi,ˇi#ٴ*&iIPAd$ hl|!E)Mʕ;]ܶQaB*8rb ]fHr c"_YiUB Jv.YQ 'PL鯼br ^ ~zl5eql \/)yK!=1B1tY]q)X#dTC:hJ]v \&DǐCQ%v"ҟ(=Ji ]Pt0sq>R9EDb9Mlwyէj\~R;߁ʎd=Uy^CK[+"nϘ7WuKtr/0K^P=#f6yzP(HbYsT #jrRbQo/8 XvOJ+ ]8_KPoMH]1Ȝ ~{cI}j_~ ,f}te|8QK=Gr J< JJ Ss%\f `*@;5_S*f _ m'q⿼EM~~B9{X:Ubvbw]݉c/2-n$:t\ QlLGkEؗ <>xŌ)4 Z*3$^('̆xYszP(_)%3QbOY_]%Ë]`P+ qe:L $B'TMc8B`P:OqDp5Děw8%y>X+d݊ _$]:HPJ)fWß5m]NџкZ%dya_v%Q`| ұvkyFT Rί7}LbU>ž2VJ]b Çx啭c?Woh`=חLu#FF_ ^nb.WQ]Pp32vaQ3"FOZ9뵐Z&N3zFόtfmzS9 ?]AЗnep+E"0CNa"1_HA+B  XL,-U=$ B!M_H G{b2!ҋ}z&]\ߡ||o?!#fH=6l&9Jq NjZ;+h-&5p! XW,8Yˋj7'((3Vf4>pqOi~<+ Tt$nB(mGlm ^,JL.iYr2$̣֥|X  =ו+Slȴ ~ٜ3cV o,-]{x@ n!>,P޴&UgSr[m*:3OKtl8/dAq#tWF_'R )_ӳU2mERl(ꏠ람Pw|C}/z{ID.k0"+:z%4mE6~T .OXYn?^ Ϣ{E 22<3]9L[汈km $_JG Nz]{Bp\Jr)/3MCrw  %jY}dǡs_mG/ ^~K4|n$Nm)ŕ w\$4Oj}$7e%~|;wq:%Q?eCץzRݯU@E{'zΗ/vDu6(d&YSGT\m(qD>n?!ùs&XZkm8 ;tbJg &nJ*l9?B^T3 b3~[`'Tɧf iHŢ%aB-4nv(|k&x0o(MQ s.!RXQk %I2$=!Q9ȫwN_MDO _]ZyM0ac` Fo4cط&o1E[IQY;M(}x 2K$ %lIVٯLX{lZirǕ j:a쌤 DP:D\1HV|9Oz#Tum8GՃ\J-UpzvulT9h(aCqebr~gULSFHt`NW_oY\|%}ޢ>$1*n2fFߦ#-Sdi'ze+mCʿ-'Qi*taڠH05g(SKf U@ lu=FJ(IO9˗]sm:&u"Ʊ>2po 4EϬ)X$}׃7ݴ-DԄf*ʝIn\J/cR,F+8ˣD0_agp2$=LaNRI28Z]i!dTw ԺB o96+FkW-?K_\?."_R(^P J`o(,JԠ㕕mz=Bn)t?f|ޅgcȆ:`A,2B#bRں#ǩCB5aʪX_zș)9szZNYꑢcp22X6}qC7@N6q9}WVDeۼ:B\n54mf9m ĸzxfZ6'~_EmeT&o*sq% 0L!Yzeeg_8\$$$+0K.D,ΌVS+h:SUireyv|!A 1(vmՠK'sܿ,+et1ٯc+$c^= V\T7G*fWֳTI6_A? C6F{N`_jۤ= ^>Smp:g}w`negMQbVo ӱV=C_oT/Mu!5~2img&@A*to%^}yRx6qBΚ4ǯV(.~zag͍Cg*P3/O125*/D"AѳF"t@qm+1սG'#[c u9"@h{fz/|F5V$R$ٹ^55ə|:cLm$+7u pp%jgfO\ HŽ#}T}W?7@?mhhtoyHisec|Y:& 0t'p ~@w`5k69| duIxXnGʷ,gWPJ%E[:iP${qY.ٮ[S=(SчtEn&]Y;}GFͮ&i9_ac8i >s9hfs-A~ cia]F*T+Z2!j5pL-YQD="왶 @U̽ow b7B3L; { ` B~ HzC7jC aa'.NK!>χi5 6XK8Xotbs0V;q+ 9}a#YЕLRheS xU5|jhbB8wZ+t'1֜g_oJCß5+6D=u'\[2:tQzD_یx F[13 ] <=L_5USl {so]Rhȿ9mRKr=J)^l3ڜ[bS󶶉CF@*M_7ِ9rLXcF`4 0pp;22 9mtF)UgZqz;1~d0(*^uBe+oO;n(A!)9?35tFaȴh+kFk(QS^r'iwl=7kR0YoW+f֐dsV%^]ӄ'߁HH ;y ZU]%]{}ljh޲zzP!5бVMu"*:Fq=_vBw/ƨYN"ɟG"ٔC iRzpZsp%L50R~r_Yqȓڿ L)s~. 02Gn_:jQ7hU~6|Ƽ&' VX^b*0@ABꀮWgf3ެ.(dץ:ѮrQZбI 7TQHCVN+\TinUob^qyfIyjdsH7tY}D0H֫IK'ϠyA%nэ8 \GwR)+'\Ω߫^jU-<<>SЀfn_x 1jUȔ g?QF9yWBBWq A| n<1HE4cG2~͜g|tOÚKF=jD0+LUҘ0,MN&&j1]ni;tGRl]u<o:fmu`{&s nI@T㧝^I6c !s3""堩`x@InaT jV0ma{N'חBD_wxpdOpb76^0&}:ltʱ5 -;040U}xFŜ/@ݵ%\0'[i i8gxձn8IaӾ\ /Վ'-8 %e.< esc}m{~6˗`zu B85Tt2=[ۚhdhZ23e&9';5pQpd9O͵Ж[tX@McU[_NI +"G_xohlTl;Ni˛D2VC:ۋkD)%4Ձ~TI)5Uyt(4 ;+})}d]{-gGx0ozrqT:ݫ#=bQ$,ic?֠&tOM\q{6{ fyz"4 e; ܭD4p =DIYEAȣtdn`ӗDYP`=PNP>UQjsn*bjD:w0Ci}XѠy^@v nhmmEbX@_,{]w0 ;e 4l'" +Q Q x~Ez z>wn!̇o%hK,t;f~['<1Fv]Q(Uzyz?!2d'N0k.`,2Ma*ӢYqe!R>Gq>Ԡw"ݑ8F[7u3P gЌ40`>1f"(ۓǂemJ2k+;D6| _R ~*v("ܕCTU!)ZיKtrD\?,j*sgDY^EO$\>>$O"cQ"JE}|$?ES8HY?Ŏڲ0RZ׹VgK:0[3}śEUQ\n4q? Q07g;/gEC 92Rz=x-X`W] M Aerȁ E5܉q >g,/|{N5ʷ:3abzѐ*4-eJ RTͮ(֖Wh`I$L{F)QGs*C^}f9jfͱPդb@J6,6FjMSfl)HOh:>=|w8}0çUyH+?~Dz&;]F?^O]0|T2l\|\U3ɺ)}Ӛw-N;fbi+\0(8Z+%-t& rdg2=8i$25+f+ 30Z9j]#y[]iY^1yjKT7yBNʼnDJFm+J1F KJَ*bl-tv"<=w5c2}{NC9n'œYL&Mo' /,\/,jlNw? I[Qp "c*i62(H*1av̈́nYkEQH*/$V&>c_oc)m Dv`HJ\üj6 70$Az%H1 I2&30/T6d888{;pzĠ~0""A%F,`q] c̛[Bq. Wf uQ޻үXJ]c0ۖh gpp` ;yȮNq:2l+ᗴUܾ|FDۺCArZtP(V" W)2w 4{^JȿHz35DsTKnt%rXk~-+D{fAM{{:B$kX3lY,)DDp`QǪjP{η]!8ÝT@65+e gaAf7_wt,lۻ׫`Hr0Z%-Ik<^*5uqP߃fq6'@Q(F zoRZ}hK5=g8F9XF4~c:JsUʟ3pwo濦1|❑E̞HRIYDM԰/'-Eѝ4I.k=ct2uܻ#TjPeްzBr}USoj9|Y}%TVvy56E(=d3XO\+ kdjZPm}R?j}A”P@o%Twu4ӆ9@a!+:0;-`+noooCMXEU˼~T (AS|[ )a^YȞ* %N^:aFP(xև5*_1mMLDY``sQbU+nps|_ܫa0K΍qxTF% n9U3]@Wr ~{GV{8㑦ސl6n][PU}Mv0\gOИboogI'V}_L,l`荗X/7i}87HIh/oobPX>ځ.* =R+nooErBuH8WH_B_b}Ϗ<{d xB ?X8iGB9wk0ٹE%wś%@@mf~7{Z}¦dہ1Ƿ#;D@#|Ca *ȭ2?ZQ_[A $1x9os-/$M $f}x C/d/Poۀa { mⓖoGw;"YХ蛘כ6KڿZcj;\sH)=v0f apDl~dL\ffr@wA2SI-YSP/ Ԗ+n~BT=mI̝eƯi?Q4QiOs%jpy ;ɳ+9ߠo}83 ՘x1R!Ӟ`xU-{iW C?Iѯu} +Ӿ-?M[k}o8﷽~zbXXo" @S|\-"W6z  g'///////////`.*oo\]&oDTy.7|77mF)ڳh,wA!! =7 Io=W>(26ѵ6!T,9!m`XqfDbWѮh0Huti"XYq伷RL֪EXn0ćI7U<.'?OT|crVL=hdD9v ^# +JфY'IMX]9E.z ǥk% b _(>c}nС#ڐ H#DoKڴkvP Zg HٺԬR`LzL;_26܅IWu MAMp( J5'c&?nH =-qdsa3V,5;[v%(H/,l#={Yƣ~)$7,Q6]5>YϷs,L Д̏!vذ=-Gle..Yl& ;A U粌22 8\I%Dq}_DkfRa[h9>;C4*lQ~Ԑ7=?'Ó]Dvb=z\/ږzFZdy{Xj /smCZ rFt$=J hbw7v-E+r# >VnjYy\8llT ]yhjj7D@>'^5)te];Si}lyX)j߲ huiru< 5}}z8>)GЍ=wF_$xXP<8ύ/[%P8<CqFzZ&3஖*,,.x5+y ]Ƶ0Н2~/(s1ǐ1N~u?n/mV;; 흌,ˢLHEm)1h$N P95p lG^.;TAMB [ ݾeh-w/@ЏQ%6m͊]& n4䥘n!Ƚw+Qh `B-noِ`nGFi@m'V-n.G$VxƋ q/^ޕ=S g7;}|V*!+D:Ya51U-:r:O:56  ?B. bTėp9gT0VK9}AMt@Ú{4BPTB*)o 3jilD$`rkm P62Ӳ2li!=lo]E|tTBvS_ /.V7۵*6Oi})4L+anM*=NO6P`DxhXÍ{L?(diL"PٞMv>kt%adEF+6$*3/ =mEwEg47uvN ̲bJm.p&^Jx"GTng QCz"魫aDfGZ f޴#DU8AKSUyRHf̎ŨPu.D`k6*ﳛo9.XvȪAhg~`n87ӋKXFubQ`zk+(kES/]L1{ $0w[r4B A%)qr[kn_\Y lRG<*Q?ۜlQpl,$ش}5@E=W5%=K0Qzpy' zM)J{Cj*O6RB#_z4pEt_P">v:@s º(G\5R/6+klY2D3p>ƩV ~Je'΀SEkaupҁb")?U?2hb?࢟l#z`.?̇ tVx^Ar4gZ܉`L(h)N) z>>c("pTD@4 M@jGħwV/ŻLjNA7. z+|t&-=ocTsxIja2tw:ęV)oo6+=mgSey #@H6ѐL1Jbe9^ X+7c&~Zj hnO)ӮwbÎ$0 fΏ2R=H?ay#l 4B5/qyQJ^Ej)1R1F+._GdxN 07/Dׄ{EBS*I`e[L˽DW @Dmb sz+迕^&~SNT Օ`B j{Іj]IgB/("<5><hMC=S{wgM-N#CLHl/~܆K) Sa};ڇgyeoҖ~`.6擥T9a7^r> VP PpSMYhc`IV\3:M[Lͽ11ÖI 1c dPd ˼Ah}EĶ!qwWkW<1vz @f+΢l> . FN?+{op^N9{K2F8Z2mvn,%Lo|D(d$!)3v r_ ޓT֘9@r^Y" " t7?A?H}2Tg9\`.Et![u|7> jyC>r[\"km>^v(SE - ݈d4&s+9;y.\4VY(D%keBPj|N!4]ϔfcq19I =Pǧ⨍I^䥂|k:`׭*&Rpeű-Ϩ%y6Ȟ]s j\2&K@΋5pP, +nl>]ףylu )}%c'hr؈ z7J^S)'gj <hong b< UA駘D5uYYedF-Q4naϖwojrulqqLA~tcj%hD1bcA-4bEe|e&mfb_.G;^R <'|{ y Q Noq ?c0mL = '8kӆM{`&r*b^jfR#nTa m(ӫ|J{O]|xbN~fT>@g ܲmrq$U ^5=):]D{)f\7A~>\C èoҦHP,*FV')y3vbP18,pf+`^8I*+dplhWV|e)JSEГ0뽍ъxGř:'auF :(`4zOFEQ(W$We;b[ [s%nuZL pاw!Cɣ0pĶZ7cg Wٓ=& GHgcjirS99&.ĵ+b!yM8\ Ќ9l&cAA?!nqiLRσ=/ˏ~ЬĆPj yNMzr"F<-%'vP *D^p=#=E SȲ#B<*F > b#{UJYP[K-a}MyD;Dy[C^ks9w1åתw # !`U0w1OEcnH`hOjx#nvn)ZctPWw+Ac @,pOhZra@dA؃~bdȲς>Wh&.kv>Ra$G/+g\P0ߪᅝ,ѿ Te(׾àCqPJ)(GjB !B%+}M]@,Pbu&s$jcfUcQ0a$$4 ,rRhKà6W0(3>_уUKƞܰy !\w6|A ͓9$Ru:!xj V'EL |r?}R]dOZkX".?`;+ߞǟѳxgM, ~lxOp!f͎?86 mht2F9%"L@#ʥ,OwHj&1ti::LuX1q"zw~1]wXM"5@fR}Ȳw,Ir* X'q+L|\gbY`Pq|C cA$#ӝƚ>PcȩY^?;~{J &+)'_wIO3ۭ%*~}| [lGwӀ-z#WQrlybtqV= |<@J0j8 ҏRJPѽ  |J:1 ٛK*[nEp1hY2-şAgFrJi(tUp=:}>bZ$gxicXqjȏX'1 TB+YTbg.azq(޳czGaPoooR7|7|7|8zE|7|7;|6`? ۯ4[u~ZQ0+,TgƠ VML<3?>$%ۛpƯp $%) IgH_L P@n ÄN\|K@T߅s_9]MzWX8` hpo␣VO6@N7xx> P!z9W/g\abv9==7TxgfFtT_!`($m ]!TMX:)3-ߚgA-Dܮh1Fm[.O/q=Wqwqw] + XTڟJ/ v074'>v QUGVș`צ%fx2ԕY|m-+h+Wlڸ_3 ,\rdWdF ʾְqL3[UW0.A s(hk0E҆29C)!|JX)#&!A'زhm;`ʬ^U8CY CD4I–YѽZcl--rU*0=ʵ皯]B> `BI0hY0XC*2,̨4y ϊ2;+X3vxwښa]dFa,PVa?:A Q(JWDD%dpq(C4?A*w͜rK $OX+sq)dDkdLYfuw}<ω-~Q Ozt&,᳨LJcKt'AU"X !wOxsCQ@n Lz۩#g+H9qr~e:{` 7;DP1@X*oW`l:>S]CHOEYܐ:4޿hdS{V؇/e`BmbsdE~Rr8 3S|#L=3'w-2diDU|Q]iĖY3U4YuAiH^*OEskҲO2$θMLMy* ixjgњⴇC%S sa{Nya?g3J҆n&})TGdV…Qӭ{`"T[r]t9d 5g+%u6p$3_!\9%괋V&]OaV`| 7;g$ ^|o@>̡XxSaW;R@1bj9/snJG) 11GQ ĒIAgwG#<>]VYQ?\9Pk{7J{4[|t,!zmeA#2W6Jy=Wq\%XQbD:@} sڱ0Za{c"5gfe.UGiGb_j>3|ԭ0y+2+p梭&'bp{KƗ耴bSB|D]A,p+л$4g9wG  h'J Ш땐(yPmf8q5H]8]' qF:unzڴ; RfcmH!) Ðfcd m3Tkpp[[A١h&:ujj=?I,+~XNTipG|8EX}9D 1ޯ"V(w\J*q=&2yPa#C;K|D?6Vc&׺ ZN6s'*/XM0h\͛N(^}H л4}c=^;)%ZɼW*݄߫䎻>t-Gz+nTؿfis~Be/3jIYKh<;?I kQf`,yMxi7˧BvTN _,7ڡR D?qw˜@ȻZX-JTc?.hctΝIppXF/v:(AЃG36N[ |P/‘r)DGaڇBf7VYC*5B-\kYl P?ckT*VNff#9|c ~?wC䠬c-3)?@y8S|s@)#MGyE6J,kމŊpK wfu-^]*Ä -ϖYA]TE%HQGq}+{R>T-4AgSZ茒y݁ ۠ hػ!m{VȮW }͝r᯲ナcdx*4,sUeUAt#ՊM*P~_C֤Zhcg!zc4Jt#])z3gF>d4(EwR3, ߊ"6|w+0Bĸ D8'yicO̖ E  -zq04v ]~]@1hbgʬmIHOf*D<(\KA3~^4rU)]+e_1km SQ}C^ھi;MQռ]O?'mD?%ȸ{Y)jn_J\ƪPGyP;+,H-*V*Fv5Lrt_g;辛J%]_pD ;h & 嶜37~beϙLD g%x~%Gɣ޳ECh rW?E@wǵ6jn{dhOt7P{ȅP`CaWa^£vj`d$xzXS,PL CVGSBYjgIX_.>C G`.s`e)R q `ď5G")y Xy&h|,ULbjq;D:{~|_%W3]4->B3CMhlsOd +J_ j$q G[C}$]?Y tSKgE"dwEpaJ^?`:3^]s?輶vhbTR#珞ZО6N=M[$z-m3`tLIʐGHxL@IG1:i걗?L\hٽ'Zj~SZ.*kr (*vK(scwmX} ʡ)س̪S&QƢ]'nƆMOy"@GUCzӸdyaj/'Z2 Vi"%Nn17h< {`\w z{kz],;"o0w7!gk@*yB%×NUc7$ԓcHv UnONrX&+:2 28r&.ŋu+a1h\J#wT- sx%0&jla~(<}e3A绎q~y }EЇG2`m^ϡE]Pc‡9JT*C8$tG 6 Gˤc%_芓X4*'U]1m-m&%Apϖ5pJ]jߍ:&c#_ $,Nq'8P$?hd՞ tAF%}$s5/!H>Q3dC+f^|,oGv ;- ӦƧyJ{]B`.48ȱ+^e9G#Xyx)]Y> wBbzު=9ֳia0btޞ.')6<֤c!ߘfts5a{r=}Ctc:G,SUvOQ9`ϱKTHh RptO$@ې ^^rKR q6Xt`q,iKwߞ3B'=L3/UQ.Yy"3]eY1?3Sp5@iɤW^E^tUdqS7/&DPn3VchꯢKЃ+TKjۆbX`r}SC c+[ndwWRF7ȍڠi;Er]DPJ& X^{lH,q?k)kW>n$ bs="D?['ÂNGkI_0l}PՉ d=̑d_L=5\Bx^z8Ι!:9zΕr'r7x8h܏kxJ4sbNk4j 0ז` ^CnGTY)\Ws<$25ms>l|!N\laQn˭D:-u}:&U[>~ c68@TR\h8l TM l :#ftHK GEģ  BOqc  BNWc  BF:#ftHK GEģ  BOqc  BNWc  BF^dXg@i'(wzoD05oB b^}Ӕ]:Ԡ64BI7?*HYMICJy!:ZϞ_DhMЀn\KBfnErpPiӯ>"'K#X4eFG\h7W:`__ȼ¿٫>E Zx\~μfX _i `Ø޹\6*M/Sa[gJuY,꺯$Ѳ_-l+Hӟ0uTcx; v!#_p@IK< /EV"m[j;’S9ۊ2ZTq0sW$DSΒdԄH\U}-s V)3r.8vng|_QzSe%QJ4EE%sn=,'|Xݘ PYqloqlzlTˇooٛz*{0 C|W=:@ 9!l-*$q'Yt\:gISgk1g!!@l(lFo _`7B9H^V(a`:qsoهxjj2rҸ *>iޚܭHk8R'f\D_dowK^ c[1D>{" ă~^EJl@EV {o&|0XX WpI;S[(oY@E@^s$oJ{'ܔX6-B^7Dew;xդ{y֗-PˊfQ9Sm al(27R4 j) >8j#2Qʖo~Xx|f>r_DyXPVdf]inZp񎓠%_q+eOQ_v}@~ʀnY(͜}s9K捸AjSC_-͠nbE3aӬ cߍ"%VzҺl=9E' kCUgϰFW%&0E~ n^]UeW M0*+|% 1nj7_x&/ E3Jj,nZ3SI=rx9*U#u6SeimW$K?P{~=<(l,pY"ۃ~:$ Uy*gQ;oe4-Ć C:f@ozիUZK0{qR Gv?1OEv$_RKz3nv?@,-у9Wߨ\Ehy#n}7Xg1h|:YlI'(JSLپg"]L=:jΈ[dv`hnL>@nYnٳ+,_Z-@z<4Շb' r(mD` HB&3 ҥ?,(1zvu-_iU1-)Dƞ3B2j0'F]l˸Ff@A1 P0 'S{žb֔wO\ B_*9haRi:Ά.X@пrMUٞN$d~Wqy7tXq5 P<] 5a ֭yKgs-Wg7h,Hz'Įj"P\p|6d[|0*85o~"ʈL@: )N9rp֛B2Scʭ$UdNKyy@m=t9>pG5'^5>9wN!BqcQo y[{LD*c8p[X[uyj_XjyTfq%Q+Wȏ{0Z|v/~+ MR, k&1)?IomFAn<^(.J’b WOVXT, Lr$Ӿtyqlq0劕a@7e4vHNh㈜rr5iS!"iz^2Jk9DN]baI2z Q|"ScqDuQ'V!yo)5ing&[~̎9mfWZ1Bg+ O3m2Y|L@`**>QSSet|BFr&'>9ny_Yɓ6KӪw7U8b'{VCqh`6Jhy \(C:m:D'3 p1KILUSoE@XBqX*AsPgxlJS Ys~~-bC-s"vʉ7gP 4cK+ ²ϯЈרŠo4oIR:Ky9rXӀŤ`vͯ*Fw)vI !#C:c.Uąī'S7F*yB$q½|7+GqPD)T"|C h(]a.Swd<sKܶǞp=+5.ESWwnxгɶ.OM*.FwF8HiΙh6.€tpE~XY `fjط4? ʢ0$Vmt;i]}yȆtqm~iH|'x~ehX8g3_ ]fS P=G}1?=#YEkX?a"xC%xQ8Aλ1-Cb^:S(>^U*V_xVb.]"37faq_i j!݁$tIbA^w#WxwBPT#c %mהCHA+-C5 Q}m+𝙬L[tzGtQ4F"B61rΛ_)Z 087{m c/u\g kR O,H}xxT14}Nq6; ՜s>VX8qm|wԤ"zEwB\^(Nx'48G0i.Y[|J/5Z$ʰGmZd$8R@rX2`z> .w=l֗ V̀Y6l FBN~Wp塬!YCk\Gn(eFh1kīKn/pO'-ޱc~~5!?館 \"Mn5n7CqĆalk>v(zGA3 3wu 0K& @I!U~WY<80R)Lk6 Ү\; Gj5Q~aC\!B(vKޕ?{zp% k ^"QT_ ˸61۽"h#Ԍ LuĺG^);[- \BoĦ%]2UI ҹNN|oqB_[ýFLb"c.!ǦT4G37NPD&%& LgGp+e2JX)Qۘ@tcȴP M`bfkkqLNB (d\Tt8u7"qo򘇃»\#M9< lC @Uq1q19JΒ9J*ø|jhd  BW$& =_lĚb;VGU s 'pE윺K洒?&n[Xٌ]4v'-)ұy>9!W~B /6Afik TUTPKT3,*PmYuK|aSjjwlF)?Fs%=w#I9@&7P$C$` rHf` qO YqS͒I@057B:Q'+ȩ Re8cZEp({-F=kFQHhїӯbfُ8M}2Hw=,ÞI& yHY r@cs(Ks54#{]P~ C/͜IfwA$K07*{D_) K)OOo1Kȯ_,Owp&)Cj mObϸ>C})eǛ`UFT.J԰hh3OۛftoUiPY[P6ŨƀP=JKtGjSm=r.juϊ z8 (؃2+@عЄ![h@Q'p]rWc,4p\F^RT~(hs*y@g= lm->8Ge!N/8Q +zYTKU:z>B!M|e!.Hkhɺ.ݭ@5 UV0T?0IMB PJl_A4nʠ #4bgk/Ы XTx/1t.IZܷ4A#;_(.QwINt@`VxWtr+K t?}R8AڍC0(7@žddmnsרO clr?{ )#{?,JN,sN~)YVz.>nj]BH~+6%x--:H,| (Ht\?%BY#Ӹ|Q;R^Pr0,W ̺u:~mtf~Z/Eͮ?u;y?c. ױc>w8I@R~6c{Q44B|jRkRV=]Tͯغ } 0շK/pe0!s>Vw-ݝܼ;;*@W_1AA[-[o&DFA.aKlk.n4p3Ft/ﻪ Vy9Y`ŷZ[ nPWYum6ڪ,X?6VG @}AwEBgYB񥗆 +])u\id B,JKӨH% {蟫3mdEr:޺{> 0 weK"dLPSM:i窇=28j6icUpcϼ=)B8Ԩ[f;gXDPuJȜn@ 1(Ame!-h,ҎELgM)B%&4DNgvAT#Y~]'Rfݤ0m,+f_3BF|,XːAhnTIoD=|qyumIuh3IM?ZJi2rpfMgǧÅ7+*ya W[> o {_7jH5ЈA:02J WH[aqv3y-<#abןOm)k^S:߀ '^,F2%x.Ip3ԀD+pSN^뿶%~ϊ+#%4#HuބyDwI}Vч&➫Gݎ$y|&КTFc?6]Qs fTqI:(.<|ׯmx531 e`ChN^_7h0RFZ`U6]ێ@5LjiMWf!PxwR&m“O$':9͍}ypd0"do۷^ءc䟀\>QՊ h3`}xEsO -}hRhnZ" rT[i$9/Tvv1Kun|6n֙-b}ab FwAyf4Bs0`Je 8osC9EMBe1q5 =Ᶎ&&GeZX6n!2K]0ӼKw 6=Xΰ׃p \0`& _.#"yY^qdwI BgL(6h(h4ؑ7ƒ:.Xg@ 09jâU&夹[5KW`JgeO=VcWy:;p*>AmmŐrIAU \F .f EB0D~E2z4^2XmU#kt* SuF'Ia"9J, S&( _aMa8WW̸J S)uq!QQf~l(GJphtMlgc?˻p3/ML o©ˬ 譑::` Bh2N̾uphq*]i x55"M3zE**8iNrI(1SN$4ˇ` $P3e!_ ݈0Hc0hTM4Ã*f\)Nd0!ZX%;u[JfSؤN a,.<&uI^AW/l2_V]v@,J';2q9&8Wy'`~A~Ԛ8k]4U!SdMvLD!2MW +yRqcg*Ov+7Ӄ׆{C:[yYZ\7R\2*^4U``L&X/xkc+12h ɮ Cwz@ ~R0Gvד ==4fs?>ZKj+[k$qvDH عԢ6luZ;jp]) @U֥ɔWR# f&0 k\t㽵:Pqf~Zdt5 ye/vbڒF~Nߨq⍪.6O펑L5y:r0QZN(M{e 4kh!ݣ{+N.;sy_-qțIXm* /JIJ5YMsl_(o=Z¯Ӟz8i 9!ykkM)R>p;$J!V|d5A K퓎r aYSx7p*>A*#cwI~ux9R&H𻥬HpaD0Lw,qK@PIoįpa\8%%f SaJ2-VɌ.EUݯ0߫0.< DŽI\h6ή}3hpS3MfQ j˳Z3>$J'FĤNuC:&x:FvyR3!붛e9Ey[º$}8DF2%sfh:$sJ BֲP=8s˭8[bXJ)"#5 5~@PfN3gR4  ]p$cs˾ =l朷ɺ#@e: '$i"uUQWf+Q?1@[h5{6Cly\Luh{YF_v~9Njք/pֶnMtB|Qf=Ve-{0'ۃl^/ C 'œ 'Nm0>>AC<@S'2q{r+Ó '{L *ЊM / ?t(Jw}if9h{ZΊpM; OTpR:s M%Ψs`M,&"iQFA,i/s-TcdR*I 6/c].ҋD08YHo h;)I8D:ytO&U:ӧKJDHPLma%j.fm0:}]&/.0%jZ;j(PC%9>dU#ʝbIUǿV/~1_~w ϐ]?x~2%ǧ3mZOMF&7+>//Vs2C$u嶥"$ȃ,[GHҾ*E>]N!+k"|SvۢM0^t?, YuJZH> ٬8lT UFo#dK-eǮjnrH(o7\;3)ߡ^}8>O\ɔQz'9=s?(wrQ̳VRizل -gCM>υ8hQ#jǼT_\h,.lEwm?z -6޷w24p?K&vZV]j&JS>giBcQE!r\#/̉LQuu_ y֬]nKT0F7^'+4t{݆[D),*s= @Sۄ;Gz03+T~bPZ2㓕#݂>NCB S8iD !1f+0(2 }Ӷd`ODl&CB`ۣ pI+=J9 |j>Ý)F5ċ|?ѩ&+Rpw~BȂ^]@$=B~8sy6e .8sI[~P {6N_MZ 0>br^Cqݍҥ\ͲцA$~N2C W7&HоH˜E(Ԍ)}M`Ɍ(nR`{ Ԧ%o^GHT{$ohw̮Ԛ& ~}/1]W$)$/|̴$'JQDbуAK-1*0#V6GƇP[EuAoJ=ͫ@a] IC@*jw^.%' ob&f}(.N1;QX(+Sp!A/ҧ4Uߢ,1 Dh b4E(-R%eoVu ցR{Τ'C !?ZAX֐؟օAB% ]Cc_lU YIB28]܀OxΪƭ(Jq#__.=T nMP /A38{,"\;(694zU1$VmoCxIiq(H2^\=oBfFZcHr2)Q<R2lKUjHk/w LH7k#VI[ă=vcYR)߲烮 ,ڗc{RGAxGX{l]q|aHU[;b*Sf0CUmQ ]\HB')󹐠-ꍑ*>#^u/?IipNDRkvo]? <7^!HQRgv'lŞQ*Aԉ8#*K_°3olbCoDXO) O~x5F ܢ`Ƣz 'c{ېFC$JbKϕǩK'x PL>Yk^5ɪED[4FB)/j=+zx=xF05AQh~SVNcf{1[J ׁ~/ 5 ,'%ދ^S+?'ibmƆx^(7(Tԋoue.|+Uyz opF<%uFG_=O<uОըA]$[J`.EN,+&v gn4zjïyPMډF4gnJFe|5S`'Z丛DYJa0 ȝ}?s+#T. *SRܫ&õOcګ> K G7 I$v產RR-C4I\I' c`8{W aiWwc><%4s,M*c\1#_MN+#vGd.֜F)ѲTwxͺ;twlzیutIjK.Cfx9杈H8m>չiX?y|8sEab^RώlOʯ߼;PZYu } ZD/FOONٻdc3|if7#ϯIVJ.wK%f!,ξ_<*jϙl%uփ 7T揭lI]8Ȭ56kob㎩zjs8qӳ)C"W՚\R4pO^,dJY2nb]MIPiew2ql`ldz(_[pKݮI4N[崽%1l[fܾ"i%=U[ HS|!EDK9P]VB|i%;+|)_l;<O5 _bVAM? ?rc1YWbcr (Zk]  ﭦ҉j4]{Mjzj NC3BMwV(PC TI%]ҥ"qJ.fd^13o[̮ &lfKL[-Ji:sU]03)܎7Hi׺ yVPoN* i@{YH AM2b&+~p3ǖ,%r$K I}D"1㿯U@k/i*B]JJ F t9|{-"`ۅ: q#_*WdIa.LKk17##%DYJڞ<~M.|b2Ê=|!V%|Nq^+tӠK#᪡:~N 31G#]WR!ƽBv`~?3I46 JuG8[q\? VKǽ<&IjXd,yX~ӯuwKiXoߒsj1Nm0/: sG-9-ZlVNeRKG87pG$% 4n=#y6-W%IwW*W;T[ k[{֙ыIڂPM[x}` BX TmYxq*$~ ;[w+5q"C#asWL77ZBPA.р$v0@y =)Ld&f|e#s=v^mq?e:B`/͠n;\}ϲ+0ڷ̵X~}=xXIH etXIS . 8 @9pO]V/{NylARhs9 nb 钤Y!u<*w ź2Ŗ…sh @ʿ: oG./HT9|ȰB{,Y e;Ai6 uGe`$^w|?w'hpεg )rzjy1X`vKh>fp'U !RG'*0 sའֳY]?~SM]wlf=/n6/jeJњruij֗r60[F83/Zs鶠k@%f8Gq5Fx,^) [(ntqZ!a4}y#_JDLJ::=7m;jzI',QWtrn6,,HYӪcbfY躵טX}mg/wb=9 𸌏<7(cqek.GHN EGL(o -gmWrUjI`ktxtXNxӠۀE(nY7H`$PF/+6]TLTL_ Ў`k8޸C'~*,v#voҷoP,5A-?IFɳedhVPޭk5y~h| 6(>ɍPyʺh`4< W#bmq߅6'qRpa/KloЁC WIu0dMx1 tȢQ3 P0 EO_ *yd!C42qn\yVA/M6pr#&qo^aK1CܩMD>jڻcq)H>*x $3 qH*a"vfQ7„U$}n :f2xq3iD7 Dm ɴqɕRA— 5T D{%3`+m:~}fjjK7LGs Mkk~@Щc1p"+ #_,#h_Xn3,رә0(W!?(wm.}R!u"y.*<0ƽ $,Mݥأ%cc11NN䞱3 {5)8j Yq [GA6 %0R#QbYPh2hyVSm ZB a{CrD;›BѪBc&w`li`9G%ݜgꃿ4al6 amJ#Qe1O,gpehEňٮ1. BBLbHK0I-nooo1O)aKq$d!JZL& YHK&3gBێEU=d4IDE1R mnUXH:h%-q9-X [j^JL'a`? ۯo9NEa6qV{cެR;+6z. ~ $$"19X0R uTEL+o՜kf&BmI36#|mڡ|\/1eIq9`.q8Zkdޣàp:?B0%E6Qa"ݡi2<ڸKغ;2RKSs(妰duh3C[1>8c}ie2 rG7J[zaV+KF8cT <1He 6Q[^n d D~'} \55~ ?+rB\,@~0b*M`1及s;`B٩l00?eX2vݰE~Ç:#.@ $%HJ$:#.@ $%HJ$^uղH "-<ʝmsgH0b+"aK5r:`=[ RY$4JKʗ1aO5/o3ט`[TX?u@8Ed月v +&<;dpl4 Gy <&.)6B<^MyOQVaeobecn RGB profilGeneri ki RGB profilPerfil RGB genricPerfil RGB Genrico030;L=89 ?@>D09; RGBProfil gnrique RVBu( RGB r_icϏProfilo RGB genericoGenerisk RGB-profil| RGB \ |Obecn RGB profil RGB Allgemeines RGB-Profilltalnos RGB profilfn RGB cϏeNN, RGB 000000Profil RGB generic  RGBPerfil RGB genricoAlgemeen RGB-profielB#D%L RGB 1H'DGenel RGB ProfiliYleinen RGB-profiiliUniwersalny profil RGB1I89 ?@>D8;L RGBEDA *91JA RGB 'D9'EGeneric RGB ProfileGenerel RGB-beskrivelsetextCopyright 2007 Apple Inc., all rights reserved.XYZ RXYZ tM=XYZ Zus4XYZ (6curvsf32 B&l"cdefres resdoojp2cOQ2@@@@R \ PXX`XX`XX`XXXPPXdKakadu-v5.2.1 0 R zj > Ԫ ܽ[\CJ| >@p8P pX .*B DN 3   vP K )>^6\L;x2rKмuy8f8k".x4hAwlYa$F#B@R f@n:z UN de`%ԩM'Jhl? =ˇfzoj<;ڗ#C^qEKG0?8h:.=X ͩ\Igwp_zo)c 󮼳(?L w?sjۮj1>2>o1~4Hq}|^.81D P}G>beSDmhs%jIjG, uyO\?iB$習qjzٓqG*,\w< Rao`H5hJT[b7jg(2Y#Zv,L-HXxd@ :Nـ^Z9% C F$fǏ4 q[0O>39v 'ڪI!HS|!5XX O~cuAʩA$$<Mw:yHсniZ\-;&(EKUZd,v"ؽ9T&fQOGDS2CY18}顠Z kҼV@~NTH#=Q r}xE5I(O  A x]Ox!ȫv#\/$VE7v N0/,,ijNDH>@H:uDAS\ԂH8IMφ2WL+~V+Ql`y }pa6˵<9,s- *kFkq05* ;틕'ĺՆB0f@_w&'EtAoK^ř!Dԛo̱EUHoًD*qV 8 2;S9:?M|Ay M(KNW]:(8p96Ihm2=;Oܑɪb=R- T-4m ֋TWy('zFQ3ۿoZ뢔I's+_vypgeE_.珑FÁN xB;m.Ⱥ8 |x C2^#,p7hYt"vW{MWoG3/}[bŒ|6]#72g;KIG)<q]ѯ#dIk }߬P{ BCP29K<'`ɉ~Oل@׹n7mj 3Nxu(L uS2Ĺ@Qz:^)EOȵq&V<"IzSFbTS  {lUXI6Dii'\w2 (^+UNO{x͔aXvl}E>/8]f%x;*,O.1 M|y,eG Ρ |rӏkYDpezAWPh}"}rfp0I';$QuiKSBuNl7Q\Y[#?lK*,&.]DzG8mA/drY}4rJ\eu{nY.ߌd!ɟrз{ĥ`F^?4jY33 62_zMZuK&eaF5z)ѕD|{ZjzWͻŠхua|Gّ-dS>|FGrޢ![f0h]o4"6ma˜QAtd0=<|unɩOţԕr7+9(2]獿=m*c(zRޫ@RAoQij53;x\>!,RƤM*Ɍ]m\=fwUHi0Wjql#}F Gj_G"yK[8+UJ(7Ū394:iJ܁{ЄEO+g[}mK #xXGEEH3K h45 ̱m-bK?QRì x"Zttqߊpi¶N))PF!@e>߿ Gn]?2Q$i6u NȊ3&|\@#BriсmJZGW^o?d`N$*,D9h]%Bo?vD !Y91ij9Lǫ$I$s=6%X2F@[˵Sl $I 6s8C(LvIs@eSo\녟d3+ic07+ jP ftypjp2 jp2 jp2hihdrcolrappl mntrRGB XYZ   acspAPPLappl-appl descodscmxlcprt8wtptrXYZ0gXYZDbXYZXrTRClchad|,bTRClgTRCldescGeneric RGB ProfileGeneric RGB Profilemluc skSK(xhrHR(caES$ptBR&ukUA*frFU(Vaeobecn RGB profilGeneri ki RGB profilPerfil RGB genricPerfil RGB Genrico030;L=89 ?@>D09; RGBProfil gnrique RVBu( RGB r_icϏProfilo RGB genericoGenerisk RGB-profil| RGB \ |Obecn RGB profil RGB Allgemeines RGB-Profilltalnos RGB profilfn RGB cϏeNN, RGB 000000Profil RGB generic  RGBPerfil RGB genricoAlgemeen RGB-profielB#D%L RGB 1H'DGenel RGB ProfiliYleinen RGB-profiiliUniwersalny profil RGB1I89 ?@>D8;L RGBEDA *91JA RGB 'D9'EGeneric RGB ProfileGenerel RGB-beskrivelsetextCopyright 2007 Apple Inc., all rights reserved.XYZ RXYZ tM=XYZ Zus4XYZ (6curvsf32 B&l"cdefjp2cOQ2R \ PXX`XX`XX`XXXPPXdKakadu-v5.2.1 "LE2R^PI59l*c1+Ba f" HLlA:֧g8t'FD_?%jY/zEgǎ3 4Z[ٞU !*HPb/$< qN9 a/lP@%Xar.VC1|v|Nhl? =R l <ݲx p$-l3IfR+ ƭf?%n"l\d,\HVÛ%V ~wh R pUz'KSjU:콆C^"/AM+8 aVF*|3pƺ `ft\[ s`0+s)(YD^O{7yN;LtP3;m 9l}h> &&xh4$'|O3coM"nPږ 5Ac *# A |@U5TA -$1q1OǙν5Xyhm1M ];VM~c??|H_I)ya9_ 3gLv [=<z_y$ Tkʠ ,+IKhHE+QDē4VGLGAڳK1zF`=+7WM,R~)~rZqT_q4I2VBZPUI$ e RK^4]lS56Cs5V X3CL)z\eNItb; ·9yǫ{!*Nj"c )6Dd9bi V~"o(IޔFhg~^׿kds%O#AyB+%Iòe!6RCUe #"^Q2YضiUQ{"jd%DRPKeU TDº5Uʿ(">)? <̪+nF~-~i MSXg^ÝWŐRP,E+S]ٵƑZI %*/EY ȑPujw9ynO A0dQȞR*hM69p NAn]Y/w1WÝfqoI9`u4׋/B^cbx%9xzk(]v9byJs@y=XSR&58hy'QO0 ^2b沅KV#:''^F*<}Qt (m*hl[}]3wᚢ/펲CA"͚toG!\Qa7Ē >{RYc0fiƲ@| UƎC? fvТ %(RzYpc?Sm5g}7=i708gfn`W2@<hMh<"cA2iK Lu#j[9ɘo{h6^XG_ΓڻAd v_-w^n/Qp2_ؘ4*XSy"FVd W$!8d֋ ̧,E1@TODjk/q~RD1ozM!6SfI< pg h1FBV|Qx iCsbl)s`8W18eIV0cI ~)gDd)f9ϻ}VU7Tj;׾oH -Z=Wm_zHU>1]A8\ͯ6ΐ5~[NjGXu^?9qK\*x < xĦ WD ~B4^Bju/jZ)UO~|QC IsreDඥPE=]Zϳ1N{7mq= b3eKx;gjH⨼C/TDCEm;KͅK@Ng3t8xLf6՗O'j Ke Ok;ˁ8|MD%k?]oe7Gk[`ےϞۿ2^>ػ1VGw U8^#Mx̨~uIg ]崀63GW%a:o ̂ՓEF=/.p(Olq)ǵ9/o6jOZEQڅM8;zSªzbĬ ,1e =% Jqef3OgyK5*ȰQxa` GU{$ Kݷl bIaʰG.禀'sbylp ʘe"5`+DV6h܎Αʷb.Ę3/ѣpg7~ke5pzLH꣇X!8 v쎝O fV%wx=Vhڒ"CJ\an7w9yM[*Co'l[vJCXB`dK[>,M?K?2"Aq?]I.qpLX&t>dxs#/4FE#:ã~⻩cd>"Y|_`RQɟ]gTִs%2L8)2>^חR+Fs-[oJT2!lO- `USkް#ܧ9l:0z|g!G&Q|#{(QW+ -QpVi74zRq->͔H?R8=TڟLTo7,JC9!|&mmZ  )kWY3姼;X<ׅ,,"q-,]/ \ d̎+|+4+S!v%h:/шm~ gc} )ԩj!֞HAhB}`ʺC/&uJ @HcO}{D$8w K8nS=HT~*ʺ~BBo zehM?,i|􏿝lLGWkP[%KRZ?$m5ꂎz=2RW/ܴRI#l@dݎ>,Q[ ܒI!xb U@e ۽ 2mժd~`榈 Bffe)F uyFMl3@q*H ʑwǭ Bed\>}6v$?šb@:UA0@B'#)/9 UY0lTXg"8R:{aɨK'<9:Fܡ9&I`8JxRiohwT5#d i+h!0T8CZe/&p7#[wWKt׻C~5%ZYup3if:&׍gӷM|2nۡqX[PDzicX(Pt@I6;UnS^uXZa7<5Ɗ#f^25#r*.IUqUٞ6җ nwm͡ ǹ< &[,񨌒Q8a,s0t)Jn6֋ۑ^niL0OV&!`f4̸al hU\,tTBkP/Di;|R}gr=w>ģAS`0ꮫC`' aQ@-4.Z~2Ficpś5̿$2ViKr` K )s$`OGV j f/礡|%/yӂp.42$GX-`$s&Hguht.8!̂\Ȭ\DE4JKiZfsHVVqF'mG(,E8bZE+j겓=w ?&9L3G&*Ց)ϱŻL,ʿ$/uIny!¯z&鞪Vm;B$(vr݈*z7s2;N5v#t$٢cZa̲|_8 }:gcc0t MүGp%rQ=Q&1ۼ)OT>H6AG~?$5Fi[3LQ܎?ebܽ'>vC$!V4(:'0`!9TWFY .+A0z\౱LPBl-ܣ}=t 䙀pgY؊{Ѣ-;'"_ ].q8ztr_[YgWpb>Z4scC '3Y s/̤;}\gt1n$ U,U'ǂL_4\ܝ9BW%9ggKvj,A1f\jV=fBw7]3\%*9lQ Aԏ +ӽ$0&S >'Hw}w/@;Li!$S7~]3*E#fo[!"Oh $B:nBF/d -dxf;f!7ӅW>ل $ȦB!p?&Pyzyo?)ǯ*Z{L2mje@ю8T]{Lb?ĊFhhKD Jh*tMuh:՛?{mxA=q٣:cJeuJՂ lJWˌ|Vګ=|I\WqQsI9?Mt3_U׀yȺSB<Da $6I^F{a iY9q%PC1@bNtgcikʔmD_EOv_uUh YhwHhݩx=jY[Bn@ϓC~DkOaɮ7_v6Gy˄[dUM χ,48T˝Um}z@(urK跉%6~S۹p]qw#e >6 ࠧzVi`f}p3q򸛊D鐁@Yel (űoK1sJߏKVİSh<:>9h;ĚH+T9檓xq)`rV WP|~U4FSC̅ss :Tf>m<^Yjm0Q .u_RT2#,wSyLZgAݴp L8C8*Y7EryWu=HL2x lξ o=3=σСluŢ7mDP‚݌Rz$PMS=_maWCKtO=tew]6Q`\^ SK(/3} l1L\u4 mt3PW *1- M i4KCHNV61fuBBD9#}VItMK8hqɮ+^h"U s@Gu}+*)0F@qp~v j%0P~hb?9kf6 T TWWךt yq&5O‡{@CoSZ>NlFAqpLK.0vɉ\ƿ5 RaGM3͗y"KfOGr Te R%j Ztn[ E3Y"MB#3rwvi||_NƌHZ\TR: Yr\J]lu!k6vb%2|V4MU!cxdHqۦVQoGKTLnf{RCS2$ 7!Qw4i>ns}lQr.RٮC2>vC"6Y{KaE$Q$g౦#Df%YZKMȍJ#7(N)7v&tfBQL#k-5iof5PXGhJŗ=2J4"k ?N V巡#Lϓu~c/vwU2~oҺM_.ogQ%%26~-bjwQg!0e0|N9=qj;A}wcx=_j7AJW5A)nA>>jnd^n0]mxv:K`xmv1I9O_SOWzh.wBa+n{@RsyAye@Q2_*1-l;ixhD#ƥo.TBN4\e=1+<͝ H]3h_; %ɘ21jqAP}X}qA- (tle$E]>i̵EŽ̝  S4i\ ?:!4$` ,"z>Ju['#!) Zj#'Ii_ ZF\/g]2Y5Q⡄b] Vaeobecn RGB profilGeneri ki RGB profilPerfil RGB genricPerfil RGB Genrico030;L=89 ?@>D09; RGBProfil gnrique RVBu( RGB r_icϏProfilo RGB genericoGenerisk RGB-profil| RGB \ |Obecn RGB profil RGB Allgemeines RGB-Profilltalnos RGB profilfn RGB cϏeNN, RGB 000000Profil RGB generic  RGBPerfil RGB genricoAlgemeen RGB-profielB#D%L RGB 1H'DGenel RGB ProfiliYleinen RGB-profiiliUniwersalny profil RGB1I89 ?@>D8;L RGBEDA *91JA RGB 'D9'EGeneric RGB ProfileGenerel RGB-beskrivelsetextCopyright 2007 Apple Inc., all rights reserved.XYZ RXYZ tM=XYZ Zus4XYZ (6curvsf32 B&l"cdefres resdoojp2cOQ2 R \ PXX`XX`XX`XXXPPXdKakadu-v5.2.1 I:|;`0>`>@0>] 0 n n&Z>>@ 4|\ ;pAbOجP n #l 'BDj-kyLT+_Ͼr| S->J N(!Y2)@IF\CN +M]QtallA<1a38-ǶC (3Q^Jht?#{rMyYE}%S zb=lFM3*@J?3,BTIڎ̟C'Ľ?bT8 oCWء~ ȅ.esE3H~-Ta_= ܼmvwW7%4A#U*YHN{j7sߡS.+,o zNPB6t ,Y" ?cXPkmH<$ ^_N`w6Ϫ C2Ӱ>S==s:0ǝ$@`1RZfGi_؝F/$&tÅ ђznT/]= CF!ͻE 8ucJABO뙓j!:MXhTZ58OVkwU‡(M >3ie _p> |P5aHK9[Hzz&I_mc~3%/!d 'Eoi>T~ GRcR[GQZ2ߝ?$1#MA.L숺\XCw ?z"5) sIL1bz- B=48`y_W 0OO< 2@%d԰zUS}Q{ŷ.4l<]V/@{ C "LnEWdx?j}xDi!bE|"z:OڤCVI[DT ceb+(yL>dӇ#]酧uѻAGyƋwgS$rusF/b?"~k0hPīV:0`g YT' |֠6=  -Sf,3" f *i@z^g"RIzrJ ky}rF}\ be -T'@'o Mϖe$ myFhoj?L\Z|l*:%;|*˝y|(ILz `#J]w״>| -~JYc~Wqt&*S@ʩHZUЌ"cų 5z dwzdz_q>} T"7KXyPGN{iQ(DmWH+X_ XEdkʜƢLtP&qAImXzQ8Z/Q 5"bŪq@~!Z`|@,嘬T*agK5ylBM)T%]tɣ?Yw? NO σnlLYSicp4<vvz풤ݙ d~e yjYYit ]ff^ [Yvvz튡ݖ \] qp Ī vvzݜ ̙Úͮ || s8mk+8888888889999::;;==>>??@@AACCDDEEEE6OXdknnkdXO6ic14 jP ftypjp2 jp2  jp2hihdrcolrappl mntrRGB XYZ   acspAPPLappl-appl descodscmxlcprt8wtptrXYZ0gXYZDbXYZXrTRClchad|,bTRClgTRCldescGeneric RGB ProfileGeneric RGB Profilemluc skSK(xhrHR(caES$ptBR&ukUA*frFU(Vaeobecn RGB profilGeneri ki RGB profilPerfil RGB genricPerfil RGB Genrico030;L=89 ?@>D09; RGBProfil gnrique RVBu( RGB r_icϏProfilo RGB genericoGenerisk RGB-profil| RGB \ |Obecn RGB profil RGB Allgemeines RGB-Profilltalnos RGB profilfn RGB cϏeNN, RGB 000000Profil RGB generic  RGBPerfil RGB genricoAlgemeen RGB-profielB#D%L RGB 1H'DGenel RGB ProfiliYleinen RGB-profiiliUniwersalny profil RGB1I89 ?@>D8;L RGBEDA *91JA RGB 'D9'EGeneric RGB ProfileGenerel RGB-beskrivelsetextCopyright 2007 Apple Inc., all rights reserved.XYZ RXYZ tM=XYZ Zus4XYZ (6curvsf32 B&l"cdefres resdoojp2cOQ2R \ PXX`XX`XX`XXXPPXdKakadu-v5.2.1 nOM2)0 ҮpIZV .}45iwMCV /43z*^x+8)Xx%;RGLGA=4xYʌ̅7*↜72c[OU{RDC)|siPQ@nC,HHޝPRCa O#:{ 諄o}y WlbM$z \C:OA@QTx:W"c )'^AYnTްW+Wqt~{ڿ!>sϰ%׵rprv]Q7j9-2U)2`.{O3_,PGU;8U\ΌO6k Zuw4Ew lr nz~Z<]~8Jx&$2e$S!-+e+v @ڠG\\7~5=M %/zhf~ Λ}{&V! '1>;wx0>#0`W_s :Cb2*\画Y!+/n"x}x,;ó.#'թF#j#1R͔jFӫw@[i,L!T>+#q$y:@oBWɌ|vobBW$00iБ1bCK{ 7f2t8mB\= nl' .VoʆhR7Lsڀ=e<^FԤqXm$Hx.AG'\ik_FWbɝ%5|!g^&q,-ܒI%tk9y}ui, U@Ld.*^B]1ER;8PrI$y9>Lڌ{-X%ʞJw. \V٫s׵]jfBLUXr"\i!z%ܒI$]xi9&ޑnwv L+\p*,Ϭ)1b Lb6A#>گ,T1eqksBOk+4r>2_f2YCHO 7j(:ZIW]shsqg wuoxm5 xC:R>C9ZGT'8Sl!?fpFZA`g >e摗}"Kۯ2WjzbwKsX-_H T?s`!Vu{ǺnZGMF\>ũW=A*?07( vcJdUիpKyF/IcŽTl0fr8@X# ΉOGJtw +ǮV9Lߛ~7j}fc|^Qhk9"\A8X0+5ڿaۈ4tA i6]]3U \ ViKK *.vȸ%vG$=>sy} ұ8@7)WTa5+W KGp.X0)@-x1Ł)} ]dK*uG'5Oz2 g{Q]\'[i7s WhOT"-=WfCٳ[QN'q0Ux*H`)9,Cf^ʿߡ#X rm-J O* w`oQ  |,oȵY mҔٻՑUAOK .GaѾ~~轘ǂ!:5\lRr @زS`01"+ l.wmBpE'ݙru(]W\;L¡yfƋG Q8T_u)a@X*>j94&/;ON:^,WYuK2#GCdk.ՙr}|REi-ј q ҉s -Olp3I+UŎ2Vߩސn> 7hf0JCb2)qMGah"VuoOܘf=qH]a5e"ow|O *g`SzqTlD a8FVKĈ,jl-$D`f:ކ`o%@ ɱoҝU]8svj)SQ{o}p_ɚ}BZO|9Nw/'z)zq]Nh"ky"z.w߷,'ZT.5hEE_d(7P?r|TN&t%ٸU\S.r&_1.( "Hv8ƗԸ6` ݨ_.^"i-ٗ2 {fCoTl6m=ѐQdR-a_U_g; eD\xKop=P[%e^gRQb4UዠL(KWbvI5v<;mc*K5UĨC,Y0(: ^5h6жZj曁6jtk>C!Z}[x WKiq2􊢭p  t.݆dǂ_W7t[~o$[ujjD!q վ{j*гtw6j1?XǨ{2ktJ7aZx&6?Ꟃ]c22'hPJF~   Ek`MVJA8? jφe)ncv|ICcsR8hzKJ"w:)IX= vxcAD>Dy9Ѡ쐷uJ{ae-sdaռKwnX8<4?` <a]^1 Yv&*GȨNT[ڋ6Aq=Ewg3tM19-iQÌADI8ė8X.6OG_ăܓ+VThL% O=iWȭ"}^ٺ1=xq>H^?%@EQG1gy$ 71U}pP#~.Ĵ:>hydCGI pCH)11.yWMZ)\`V*J\6U9IT7K>Rc u$He:Al W -=(3ruDJ[[ 6-Sab/ʖkݜ-XD(!aJ07sݚ>"Ar#tw4G6Mc5@?]`Q q7[N'QnQW+r9uB>O{gm 2)R.1=1_Z^`i3k Q1/BJ8岺 NJO\wK {Hd8\Q@"P1u3\0Nff.kvyx0ė:.4RH(n"2B.*eGcte&E4T4}(31( sEN%{cAE])H?dfK;݇ (i8*wpa it;bYIz+_U"P2GMf˭@Rʓc'yX̕n}¾e8vNFpv<Ӓ,t.^8 ˔QF@  ۨh:eQQeA|$7oFQCؚiu~##"#2Z|GZK3dHT+ cdR̸*,䏫כ7+ ~EjrY]_R|Q%_7xvvRpJe9g0i~7D{>wt!9VK8: 4lbh j^?N[ asD֟({qQ:]*rF fdǹoBjGAa`0{P;OA.z{3F.d6[?V8fW&6SLH!vJIU1F秘 A#9[V- vZ$[%^MƁ!oTuk a;80rb+].aZS3DlG(-axP1s'0a/23 aB bpaftwؕ5skWR[q;-,ǛHDZ,|~ 0 KT:{ո J,k|y )*6X4/BS\v4^ydžy]X95Cj]=cCs4f)2Ń.#HҫG"wc /֛[lH2 ,BA/r s_! WnOkѱCF+J{B9 Uω>&h-yx;! QccԾ;l#KuߥNkTeo MEI) Z} $کz ܔе+iU&Q6oALbRU he9]_JxwK:N7`< GS;S* A=m 3"UbOZ吋Syq0rY%Nʤ LڹSd1StBg#`x{׳LW䪐鐃?_)m2O1Їec'. Mr q>ˤZjQ$n  ;>MvK8!#ucm71ӓ)\"U*)Kz%VJ<7cٚ1z[*^&GD1W*{.Mkoruv +)q)nj'ٺ~=&$etgቩ݀+J|/QQ ~_+ouO V5}3C:-Ye)k ?KZΰ0wC>Һp4a tltn߉p Mı%BݩQwRFڝ)˃4_Z_\Jaxn$",44\#E AZv)UlKW |U_zR 8J.\67-#gR~lfG?jT(|*&]Vfqkln`ȤSW5dV[GX0oka ' #BEږL?m+0ӕut\VQkhp .q#xE-J(#YKėXH=b}^GR˧@qµKvis/8<5CSqD4 e/152Ll׳=x*Ñlԍ GAh &i^ϮI%8m}ﴸ /RvBݩ.^d9ӑo&C3T^!Jǻ0n2OQSyZ\w폔d[U j'h<.=_#=wOfU t=FoȊ7ĂњR6eK-&qZ¨ uq7l;hMc*W]eys\XCgӇ ad__'ul >%1MI+e0WaYvuܸںAkG p ܼL%xA ~JѸѧw-l_g0~po 6̽!]5f겨|;nܐ&3 ""cBb973$ǥZ݋&l.Dk]0iJy 0(pi>ZUu~o PaʊmϤ,b`؂*x;c:ϩ)ez_qףX I%bR`"۩x-k-MxVzP2+#EZ\}COJ ;>bҗZ\TVh_z'BJ|t w&iLbY>p,@;O' Nɺ'$w|:cF =ese1nmMB[xe $E5dywtRiC V\r^yF07˲|OmšNA"z3W&)"Ք,RѼ&鯒Eq2RI1lab \;%Z6_ଲP+H}S[@Wp}Oήm̟/y-m/:WIZVF ߰GWx2zOc?S8AzOķ2 AT;q2} %nF8O3Is126u)Pdw~YzTdlm>fr7UˌN2 ݤXEЕR(xА~ SkO vD y,Œ5nؐVNYAT;H ǠHfŜw>tc \`(;HyJa q-[y=a 9t1W0R,JvD*}>M@{YtqUBuMjg*"_'"sbvR6C2^I`׾HBk.DO%jrz%e"!VBo$mx}Ӣ==ar$WR10lG77AD}jMD Y6)0f N~i匜aupo5z0ԔÍR(\fLo֕v[,;=N">z@=~03vMsz Y"q75_C!g$%փֶo. ˲<=g&RJHw0a^yփ.^IOa3V{2 A,W$A& 8toWQ`",Υon[;]]/FZf<7 LEa;pʣ5HJKG0KXXD }n"eq=Ybe0e 1wcC?pR`q^k@3UfU3N0X͈/xHEeI]-V|b%UF+#W[,%R (TƅSBJ#ʳ[ZAՒsScDsR#Q6Rkì3>muP3 'JEwJ}vI})XCBQc\^M04>Lc. Ԟ˵H)ZoB$AW12%uʬ)߱)cS@o=3vrZR>{ub6v;vHLx(Iat!Fn+3Iz1%$A6tn$CbeQ4.牭Ys=b=!_t|lۗuK[IOk1z­x8W "XZ.Ơb9@'Q 5%2+> #ܾ;PMu*kѝk^;㼞’r}v^u6qP#hS#(8L5?Aa?zP-(!|&xe#_=@07=+u_le*F Z$ u5V$2d#Dr/<#n"$nZ{<袦mv9*䕻&cA;it@jE.Hj{)F4Ah1=s8(u]|qYLh=]Q4xԯ| }IwNl<\ZD5h0@xC+\{:HJ2Mk:|s`|H:X{/s+oczTFe \,[PWEK?c+zXϞppi0~N=㒨2 WIFiMFͮS#@H"qoTa-C_,F|+[[:t?HУ*xQD:{[-|&M«fl[ Y%Pt6V9En]H`\uϕqC_oPwU\p{%YtO<vA5v;.6 }v1Lg|eCׅ)yK^R^"uqz;6 e@J藶Jb6BWwʵuQj6K^R(=%gJIyՓ9y܊0O_K/a)x[+dPr|`yn=R򗔼1> 2Q4uE'ʯztp7ދjWLț7W~V-zS5L%I*@gIzpKxĚƟ`$2.7kd9$FOt |/pˢlۼdCޜFI˛H"t '#ш3hZV6S8^7ljBfwT&'ahҫW+.8]x*\wcȆu"(('=maWOTwi_XZ-4&ʧtnn$xA%`v4ZũGYӞuŁQfFTmV{Zap  !{FpFҺk4F{mOCaYA+3%;B<(@4Us~B&!+r3hoqgˈ&[S%?e|$:`K37gɋiP+ޠ{ ֨tV}ʴ<@Vt7Fzl_xaΌi oYwHG ^SF[,VjP\| h5 v!c@V=ln-KR+j}x5x (\؁wq'RۼI6U_a6VQKORvZ n\Qܠ/ +$Z41_HR1::ƕ8%?;R6L-L#q3YK4G$D1%/ᨮRhl\lf$WѵIt::֮_[UϣKJ?uDB1 KYS4AAqܺw;"E] _Ӟh@KQs@tf&ܣvE 'rTV}O LaVO$mNN= _=y^meT5.ܻr2ÞU4.W10TjWa16VELT$ڡ3ԑH-$YZ!K{z)%L8m; !f9+0lWnCiNﬗȌޚyx [i0;.БyϭLkAÍ0)> YwjQ`Tph_~DQ ^z~%ɱy\p%5iFIzgaߴ)H= HHf֠oM*Qa5lPmW7Xs3CW-UD-a%* $d9ŔI_v%H Ikaz7?-G xw vS"CV5^A't9R:fHf],z&NTI"jp{# ʡJ6TGFS8hJsI,|M"Ěmd+HUjJ ְ~7j^n…l R&#:z7 `?#AJ/?jq=zFT)cJzo_k=eYv "| jgjGn4KΒLܕ0J|ݤpW4$D'p51?`49,-,0:wi&}*}cϡ#N;7@kNʫj:w-po4PL< d~Ȩmp/:<3? ]"QZ۠Jd+)/EӂiŶOH#a>@M]4JH<<\!$c5v`{^lv"A> g跦=Ѡ7W!yInY$wVIT{mٯIq^E?6~Ղaެt0i[;kxKu]F/>2WWcGShp#q{"?GVѸ_/7?N*O]v 6;]2rFO`!'N;tvc?tb3]0KS()0f,LV\ؚae=6mFlkpNJ7psR}0Ta : 'ZBX7yH@ʟuz%!g̨֦CupGl[74GPB^, DRp/̲"@勱*$1.ɉh!qEqE䈟~Hαq踎a8+HdU2`򭈪zeIrfv卷,@^-cw7-8/DB%<_zbJƺA D\4,9N7S4UxOYgr)O䰛`9X]}WӑpKqay r7`=}"ryu\^Ղ44P "傦~ny, "QBpG>\]v:jA)ϸ,Ds՛ y4}#`{/$-}{JX 7<_\j 0/NBaخq[Of2$Sx FG)D 䝺7@J5[ȽEЖ6"kU%=qrCY3[󘛚QU;oI Ñ Smm¯T-nݫGԹcqO$$Npި2Mz_ڧ8Oo`dA2f@lƮ/l{x?h%#Ո`Q >Xs-E͒PJe\* NkOqЯB9T`9op)UܲЀ4f>:m{f]Yχ8P: 1Qxx376֚=*L'(Tʽ=Y#.]]sO5 ؤv:{lfmYR;%~^y|r~e>XL?+iY7=΂Dk㨱^~U)8<^>9yP5_ n KMXn@""LbrnGք%lK>U])}>J֗^{$h 62=n*1L2d4jQhr %M)pi k Pm GT!,eV)r@m$V?+_?]>()eyS!Ҕ^$- !*DVI S'B€|༆ Λ`ZXh5JsyJܪ-1N}g{U+KKt&4nTcЗ `׃kɧ2 QE|;RW76CEq=.43:<kؚK[`ïQBb]OhS Q%:܉P:R{CҠѻ~r=JW¦%Lg[6[rCɼ^N4=3//iXӒBNrVsCq1``t=3 .լOG. '[Q ETiJlWq@7 X"6OȂ^-^Xїwceil (^VfVJ?3fW\ɍ^.{6|13]Qs:cr i0 #XMFz,H}Z}ZooZW]K꽯>ڧN (v%9ʺصAzas}cm4zU{д X>;6YGlzOVz%V,hkdYIDL&J3{]!Y)(k 5r9ֳR1ey*B/v+p4dLz}"8Bs>lmf~pnS aWݦh*QT PI߲Ʊ,W?ռSFZz\DO)"#ikel2ы1kIhH7SQ⥭KI2=h ;>BQj)f #k,Qǐ%BsVŘYla:l zV_xZ+ьY[lMڜpIt>UJ PG͹IU*YN RjC6z+hTeVϡ`E/qJ`~;=̒qyέ:ݳ3"/kgA e@~ PVyV] <3K5޽{;Mq<P\ecľnp^ 5b%t;+.f%F!BHeBm a 3>׬Zr"GXl/?U*ׁ, V+jY XQe6inPm!ԝ%n`qzZNY:^Q挥@Z)>`"zI"se!eH%BE?JA+c܌Ũ\`̄e>BR9a>0q5ɞ26vV7 zވ.]a@nd&jg~؞(hy8}VS<`Z.ʣovM D°-} r trD68*#H!U $dٺSh,#lv=> 6T ]S} K3#-^G[tDh>8qt Et\]='HZYx+UB8{ol݁I̵~1+kUtǶC$ӮQe|VUEQ=\MLsoMJzeu{yFTZÜL MЄ=긩`J#4Xs)t3u{P2~.HO]iHiWA7SCI13j{FS  #WWD之KZs4!(kzDs.}MS)\ >"#4Z0kaځP1ڻI5_MRp${W]"2;6s̺Tٟ 1N&7';pWeZ SlU'I\r'Q{٥_7?.>\iT4m0HFPaOFOa<1˴\<ɓ^G7L~; M/8du-.]m5n\/F}'7:M Gb^H1ShgXT.Fm l08a.$v]'M_3I|חNG(`H䕗U+2!%f=i`b|3ɉwH}V[_F?mmtGNՠwՠD?m4j< Vo΢t>w[prahz}]8ZJDXWʚpT7\Bu2^X( jOOI%znup&)6P3yvbsH;$R{EhNP>PptŠzq=s4I(lA6p56âqFmr6bVxZEwYwxr,Rb T2S{Lb 0liD,?{v|D/xL@9C<ް㼆tmhTc`S/]ųY !:HD>]n8/z-(hwr.\ *QaAB B[lᒜPCRWi) L&(sfܫtnJ.Jӥ*rDh;5}--)Qqr ƑHN ک wRݿJE7SrvuAL QqnxawNq>M^ L2įQ@c^r&.g|'9Ro"No(aqerEgvˈvz|2Kx{M0ֳ*:(aExG'l#`"$udtnt y$[opK䥘&pZm2rLFftY[[EkQq* $U6V js(P)9W" if#4W¶XBU{_S1Qi̛Rˤ!4qQ |e_8grt]FA+%4s0BSg.{778K]:Hx@D!0XM:a(ˇa ;V8B7#5KR^d-J*ԛcn ʖ|YN}'x=KсZÇKc\_+9HIWOiW`ސK rN\K;J;[. Ww*0\Q^G`kW}\'sbd1ՕƁ{hO%8pgY%B_ mA_b 2o9۲د3 bfFn""UQ)ƒm*췲T .]^rx!3Pa-Zj{v`Ҙ uK@IxgygJ闈mѴD3/aD `'e(lE:^M4EaEsnQVxӟ.dٟGxz@aqk*=x̓'cC-(pD7mu`0]Otu>y'|s)8W"A-p_;>,ye֪jL#IT9iB2lmxm?׋4|R r){/ %.1yxZ$MpU\hcWd'\AY(oYԙ-<N]W הjMc_BiXזizQ[Am{vi)`%>b2˷7,H2iZvuJeETAD쑄m҂DexAڠOM_\ֹ?ۀNSjdXR!3/` ]0 j^cHrZ5[oe` [т fn!FA 􅎕+xHEWFtqdf Wr7ݎ|&B}:?G ^'f 1\u9;ws Plx AT5уW҈j U}R(f ]N~A?=nkVƳ am֣X($rc)pZfVJۢחk'<-F2%!{=. E&t}c)-R^ď>zh?֮^U!* JgA=a+{j0{)ng VW6\)Q5zL%"Ac `L|O.*A )4Ј!Sʒ,uim@wgu`^}"pJʁJ$]+) t#fRH&UcVKm^  /jumA{ե3c~-f z[=Q[ׄ_}f,;į^{q>xrEB;^PmR?j?}A‘P@o%Twu4ӆ9@a!+:0;-`+noooCMXEU˼~T (AS|[ )a^YȞ* %N^:aFPH'5jGRAaU{=$m0~[.+y pr5d,eE(U+>?UQ*سe5?gf$5Y:Nbџ՜6~N[$k4tK&_c)u|WMn][PU}Mv0\gOИboogI'V}_L,l`荗X/7i}87HIh/oobPX>ځ.* =R+nooErBuH8WH_B_b}Ϗ<{d xB*x39+Uh;cM/Z湤ӍA[74 5gKSPȚcgۓLx koOCڞUҤ2H 9O0NЉK@W3/<\SwqGN'>#;D@#|Ca *ȭ2?ZQ_[A $1x9os-/$M $f}x C/d/Poۀa { mⓖoGw;"YХ蛘כ6KڿdīȂĠ ^^1W L!࣏!BA@,A q_`$R&{@01cDd=@g7,mP Û&ǏzVh3^0.VO&1aXG[a9zE9ߠo}83 ՘x1R!Ӟ`xS-+?v4үj`Iѯu}+;A/ M'ZW}_o﷽^{bXXo" @S|\-"W6z  g'///////////`.*oo\]&oDTy.7|77mF)ڳh,w)GЍ=wF_$x`i `=?+ 2d  (qI.Z]ngmYxd47{w?ۊO*|!j^kAw,\KCޘ(I,#q)g7?2-P;6;R Cպ5poh3`dK|%ʻ(;nѫ(j_јȣ Dބ덃dGiGߵu& D7}V|q4 (ʹ~įcRt= ҕ7Na!"RG |huYFzfK%;1}~#IG9b !eoofq?#-w; 9OH`^'.:b 3*pMr"PQsrIB@4 PQHV(S 7kKU+37=GJr">-,)d[ hw',?mP{^2Wwf`Żr W)Bh!HQdQA8/:q&tU!` @ߡ+ݿlƦ=Z6K3mY!H+3U4Xnz0jwF΋WlL!K(WoIjGK@;m#KӦt$1ޡWӭ=Nf)8np/vTL.G{):#"ͨ߫E$@Y& >ib͖)b,ffg oΓXiazCGDf!mh(P>1YW^"K6O8xT4/'L(Jv>4}tT/3YfA(]U9;xĖ.+j DՌl-{b 2F-:mSJiI*?r/Y=IQ?ۢ+?X]ۦ P NNs~|WH@O[Q!T bz6֟Ũc1%d,h:WcVD`8@AvLd->`BO&<%Ӏ%l͂9@bLak4#4kY_KK L&Jx+_Qv?,GMXʜ% ??ge&Yxw/UPzJApVӍMqG4?]-*Puj 0kF6ԣ^Z6rGvnq?in7,Y9G'TӰD}߼ӕG|4 SE$R ; 2hxp]&O m2{"^QA7ɕB˒_4hyLvtT49~lC)ʫL|0"Gw!~KS竘ͫ'S7"6ML:nAr ƻD0<^~;cyOg=-5+>e @:YQJmZ).~ ^0sĶ%xjQ5СTlH_-0Pa9!Ӌx+HLuQ8Q}{],J=DV Պ]`jaUȁU^P螄xuQ{gX9P|C%ļ,gDq0k}}Zzb$80v=Ɣ%),.781nت+,A B wW:_i'a5urBh[< X:n+Uvij,~hԪHEf vL$4{v 8:<{ 3دcĺ@nf|e/̻ȭD}D,WQGv=[ fAL'! i9ΓNRIYAe\ȬxGလEu 1ݳajj89A,d׵ =F\ 'n:m _ 38 `*;3('*ml4r<▌U @{kt9KYDZhfx '9^w /!G ~~1%`hށ-QZ?kEi OdžL^7P8_Oݜ[,gkv r_ ޓO&"+X%QTH"UD(oĶ '`JPkҼ& źժT'[u|7|u[ \5CS&:m|wrp1mI%{rKl?F!Hu*@kq5I[1MEX@xݪ=Ĉq !$"=bZ;)nLf:Ţa@ƀn4 n)͕2:@]epUNK WJm2޹%(q2gR=e4tX!y+L$Yj5pP, +nl>]]ohZK6ZZyu? ߚd;C9\'l %nu:D(A_Lqeb#B[7t%z*8=Itue\$;lcgK#q#M]} Yr"2L=w}sw$\$Ʋ#ؕK+GD0_V|$f83w{Pizu_Kp@ ZI"4|c9IbP#k}DEO}!=2o?o2 DX*ЈhSUݞ**PŦp8ňbIRz)'guGu^aNp3PY(}użZdb*#ғpY! f|v'|ljjЛg]ά &1C$Ju \ R/J0T*ZǑR|=pAt sن!vg2򅐇qL@wt&~O9S΂6)k8@/McהۧFo1]3IM'Gf V-)}[k␛n-ɥ!ЬĆ υj$%ۛpƯp $%) IgH_L P@n ÄN\|K@T߅s_9]MzWX8` hpo␣VO6@N7xx> fH1L缃SIydͳG 6s:Wz}{,;'ĕoPu/U"BRje<3!> ;[[G# '3?:2 l-E_B@gd_Ug4kӻDP/(pLܝ 9ŒV3\ĝ6}/e-YE I0>@1 YiE |nYs9hv7噸$8ef]cqʬSyŞƵSUM ܯ #vxwښa]dFa,PVa?:A Q(JWDD%db;Ɣ]DBkn$Ӎ2C[xtW]Cq6HyWxndo 9H kd_HD؎6+G<=RTY`wDhfQSkJrfh  |gZO)deJUc@3NK.~i1}D觉 `Jk$X+@{ 1zdxIpJaaՅ 73ZV* CTښ#;?r9^dlj.Vo9:OR`dtvq%eמ* iF-`'{]1i{W֡9XL9v|E3-&|[XA1kqfJZ 9@!0,ֶ8U/qzZk9ׇ^IroȠ(i2T oՃ"f~\-tqM?f|S߰iEW\;әߐ@P!Yɼ/܁/]f=5lv w}þԈh5yE Gq2@hjj^ŃUGj`|F z;ypyYUdqw]*8~4Xo&ߞGb{gȲxՉo3)v+{c ^ݔ!!c7q$8nH= #Ql|8黛m*ubv:/iDT''|2,'`)APWVcuMd}ZdK]KaAf;Ce'ע(X@"[>( xqPtT~K3m0\mEPy i` 6Dj̈?.wU53Pѳ0,Iv5>~Ң7 m/VQa&6d$|O-gS&I ؝EX a)_uPr3Og7yK䢩wHg[qrZm>~1F ؘ'O{Ef̙3i8`39&I.zz!w#}NoKʯJ0I)Kl Af:i>w32pyL#-c.g A-=0iy} ?O*TXOEmAq0'BpVؙOQ\/eTWŗ=-i釗=\R#< d(j @kTۅQo26> 3cp%zgsrw^P_#a],7W7~@8eӟ8}gpIYEшz[ԣڐ5ɔ+a];|? D~tYMNG}|=]Ƞrt H?a*:Äf;eGhwս#!1}b. C`՞2Y뻵XBgU9$vb2 JfVIZpLвW K|!io3_"gI!Qy\vMuGbh?נ<kd>G7P{ȅP`Camlx}ܒA __,UE`:9u.+[Ҍ(piYɓ546̪1Mr>tq wke{ؐS3,C7bo-W6Ս_٨'!SRk=ǡk>CxI=RC[B['h@CdFߘ~#$q}qIvwϷ)YyArk r[#vR̩dvlʵ:kW]LpT^pX_PET\baJr.HcSrM訬/7I,xmZh|}mȈɄָ &eIK`W:.T), _T>hDskʲ}s0//,:e%v#u[1Mg ? U;,٦|:4atrg+UVEpXz]vp3@!nf^ V~\:Z29&Dћ 1 [*8aؤ cP'z3bJO"7"juBISVV?q/; ChhxۺZ:lK.4l\q`%qIY޼e[(zu[-L>wء$F#XUБ(b'E,۔3 ۝+X" o#'Zi"=+aC]vuZ?yfq'D L%S TpA~)& '5FF=b5 nX1LA+:;cm |hq^m~L鄰'%NVX ymU|M#3S+#Lwt67{)(0x,'*ƳiBҵ:d.j4a,.@e:KWzxƧ1hjSjFP cFAfJ& _51ep3 dIuɜU#ziRnEѾp43DFw0IEn*"]hqVX3ɻ8VXΊXV[,M}.fc!F\[N@[;iCT#\L-2*mcӥBKV~r -8ȃm#*,. \Ep̐k?6 LjFߵ>l|!N\laQn˭D:-u}:&U[>~ c68@TR\h8l TM l :#ftHK GEģ  BOqc  BNWc  BF:#ftHK GEģ  BOqc  BNWc  BF^dXg@i'(wzoD05oB b^}Ӕ]:Ԡ64BI7?*HYMICJy!:ZϞ_DhMЀn\KBfn710׿N{W̕ײzmpXѲ^fs4%6K֝9RH:8? CMCr ?Zg!SJOq#nje"ԢQgHU\ɒ=usl=^aܻ;]>OQnt)qYm"wH hj:%9rߌuun\ު.X6jW0I V6k{]ćx%N3NQ<:eBHe@?m$uRdQ vЕh| Q\!gsm9RQ|-۪;6 s dp Tp;g] Ē=^D{iAEiI6{\\g!ѱup/!X 39*$zNsJd-%Fk\NUsg2,g/QdRKZՀӠ-x'!BIk`ak.<X}P4 b@ Jצeߐ̺"!{_F9}^>$PBbE $#?avw@Usr?/UyW@@-%u(eH3"W<оrFx˃G/6I?4 j) >8j#2Qʖo~Xx|fV_pN,be1,[Aw@ Hf$CBd~[0TamG-#6V#5 ^g@ٜrVW\nXtu^FsawIEQhǗ6b($5dP~y/gû(cw6 `%^CZ%ͮJ˜Vyx5PzF_$dMY7{uw);ҤM-Ɨ$\n 10}{$.|j+h띁0Mo^+ǎ M$4Q@ tQUʛOW])FB ` i /mi˺ x{SAtOa1QvAؓO!U#V*tZ%b՗w6°ȊrQ#Qo+Ic٦!.!@Wk`!- +>r=ǜaH(Gw[8‰xxe8h}+2 <5)X_܂W5{|l[iv{>|@FB#Gsԣa Eh{@hOaxEy>‹,BG2TXhc& 8> eJDHZ| [d&`4:6}=X$ۄƊ{^pv΃oE JL /b7f1 v}%AIaHKV6K}&AcPBP˄$ r7Ԙ뤲Op.EѝLWsJ_4J!7GS(kUX#LAJv4Q`8//skI_hR9{A0rlE~:pe 7jY|A>v1>h+N&f+0[ܾ4-~[3b+ ]5CQɄ.Q~J>%=YLAOI"o%.K(T։ɜUG-L -eW2r=NxM&I`bݜop+z:&,V|8t\o՘u[Wmȵ<[*cRAK [X:$fv)ܙ[:!$mq:o.eXj4abv+uXeI} Db$aV86֞ӃVG+Q0Ww a an:~~XY `e*D @ _#gӈ̓Qf#X'̎juw%CH% _,Ǚ:k S:?ĩ'S5?xULb03 `oI%E@I[9oFutMÁܓUS6n6гWR40 ?cq"'<>"K7[[ ֘_1貪U90\3iL2# en?vv:?#>'}|i l(,+R<e%w녜[<A;54;tiνyTsS:`&ZܕtiLree߽Qq'fa@7d`O8N( w =Kǫk#CruRuBПGXIՏAԮAd7'FĄ>g`炍_يǃfJV?*oP9Ձ&N& QSdp-";q}'ؓ |!O?B ^8:]~3MZ%h4a ;~5rymNn*y U"^d n"oK⦼Iok-eL.X =HOHYCz?HX`S>;r4rn1j:RusY`z 瓵zZkG8㖥geOپړɹ98۷qʌxEFo : c1 jmL}{q;#W"[ODvB72mZ21&&!YBhjD1yFa&vQ;~:J^hXT ?ٹ֒C^ҮZ}[U~= .6˃. (Ef`řlա-e:JwM#y2gDIh ٽ-`AჷBҒ™ؓ6@WZO@O`ZP Y!Q, ]6e{;ɉ[n7j*ZG_.޴YU\d#/ e>7QΡITQ1N0yp8xPFW~>)3 x?kNL ?#1deռT rр@._&P F}ua|1tk ܑSKEqBQJ ٟ>tƊAcqAg%'9xOЅ}b-ў>S2Ɍ:DPV'] \":׽:v-CL}n;3Bo@, 2[_s|~g*KCR-X0t /%CFCճQ"yWJI d[(~DD`6.P&JK׳zNE01SWNQlj(R}l2 2&붞q 6ܥ{۔h(RP/5 s[A/ݒ^D_8ۋjT"Qgʹ\y`M.@A k [_P$K \R~wlQ<0뻊RD}\d!:Ȟ_\^.fT4*1%>X1QRxus UY3Rv$IZdI-Sz/4ZJ .کĨTm\@@GeRY@$o9'1w;tro;zI؁b:OqeW?e"y pb ҊUj!r?bjp 9wuؿ 8`ͬ+ R܆@ȥy=VX!hŚ!J{S(Ѩn;~1鞩g&vMz].a&ter_rjmЀGRZ˲#/pR=CN90%~wpSi,&ݹiPo2KQ蒞.CKC^:Ԙ+QlYWb;.0ZV(gݠMGioɰa6{l+j:0 @k$JÝπƧ`\ɀ~\>/S^}q͕:z`yws^#%Ds%J-(I]R\QL::@]_e?I+v4:η}2 >WઔyGGW.ԾH0 S}c-V \ƛ$tlJeލ UÒQb)5xs4Ky]4AQtHJ#˃6/gG[IgW뮭SuU;Puӯ͝@cC/ʓF^wLFi|+wSWqKoM{A7H~3].$*rv|A8.,͡XV*gԗW$wTի1?J岇XVPpN3 ->.mGI G[̂’7s`6m"uaz5ê|gF:̆X&7pzzlFRcɭ&*xRG"bH3rUgdT>WX&a_rP%f:$Dǫy\ IDSK&AJ10!M͞rֺk Qӎ7X#g# H`!K,6v \%tBC/5j5񒖨i/a" J̄,A%JDepV2_] B?jW!&m#Dx.4f̣I$O!F3q kG,dd{0:'upr :aKONZ>B}|bHE48g7z#m˂Dw,kAײs6^=H@yƣ 8IY<8򈷁,fRglܳ.q.gz{{i _(}}*sK ,xU$4Enyiicn|o@c@Q"0o3@uK5jrymvN  ! Mc .Bn(r|dYkLa{A"aμms4ی6@B*Ov D3^5o)tgP=qspe<Ώ<[XZ{Vi:MqX>0꘰]=lDtiU6R=qި>gt/ bc#t4CZ'Pp?ڒ]#1H7~ldCȘNm.=rzl' fz5ڑb*\~\M۰zt͐hJʅA xr#C'h:;cF^5akݰ(\TeՅ%BC~Ҽ4 kX4ډaľ 7M+G 0Ry)G8IJ%KͶvs0'΀؝LgkU^]O2|Zm[ j~Z-dSa4j=:0Ie&~+' T#c5rvDCk$nj ! sZ/#ɖTd"D*ݠerLa15rfaS)0@=1R_Ko-/*_Y=+T7h@[@úx7{YL=cD.bE(!Ob鮤T?,wCx0a#fU9OUFDB!okMb y=@^/u ,/ =bUת ~*׌S o;˞{Q}@W 8#BMM<92ld!؍c16[Y4%(y L0UvEΤl4cΌ 0ENRAu [! &zuQBO?\W'{P(RW0d̈́XۧS"$!aτ5r/긚.p_Spy~W`Ymv2M{l]%;yҟ@Ч1KZsGٽbΌԈkT $U71c?oyMvK?~̴!Zq0VnChρ01շbЏه\0R* V_ؗBrig9kе/|go}}e*7C _!G;g^U 9P&y@/a uBYo&t `#؃nTC8/uFL:=5&,CES:!X|(?(^ ?N!; ]5B 5T;})V%TrR?soxRvُ],G0vߴO]Fxfs;V{;]-A)[;Hq^H]lUˤB .1EܠeCdn f.!2G!Ǭ 8i~R$F< o &$w 嵗I >;_(f.MCYk(e7<" >ug3q`Q԰jjÎ "_*0i{+HMmb'xsn%YQ1(E`-:Ak)gq2_okvf$1wU~ICK-St\Dn#P:3ɰ[@jw[p$٭}O&1A]P gK"N\ ?-|뾝Z'[~YS2-,4eH.o8 F~-:d➈REY)n!@pSV$'X=hτ#fIg]m?2cZZȖ̄戮Àܪ A?X`oM1qx3 eUҠ\<P>ׁMz໊BuiزBөw_鈘ţXqe^~!R'7@Cl`W1Bh %!-(Yݧv&J͗DT }\[srݡ6)llMi רD__$4<pCP?=:ŇgeSIe.'>E W0 n)Fu$2U3pLXGHbn몡p3n yWER r:혝7UuDі|;ߥ)A(˭0Ȣ&mϦ%Z-J!S~p&1_JG^'8Hz裁%jz5s~}7̭xg̲(_B4҂7هsW~2&44Ͳ(X7};0. s֑ -4@ ֓Pbze*^q ӓ__^?|Bʊ:: 2>A[_Q* b,e;h`:=E- @3vxk/͚=x+ zO@j~5HՈ`=YDvfzW';EbcC_{Yg6OTMb?'i m7;GNk \'Όx .S|ФCqOq"ЯQh.G'e##ь3 {KQG˸x 2ޯLS P7`]@{ou^Zz5Hõy%GP7Uռ$wϬXUIAѐk L>L\oE@.xiq uܲ ހ "TU&W$@gќU贝`> vAIvվ>Tc~QTd]q:ȫC>ԋύZeQ2)joQ9$Tcob%pCg.;%nYԐk&.rMyfRds &Æ{㽹ج:DI|&lmtൄBJ9#5M喜US_/o-^ 'tZsa?R&ȲЩqȗH9jkxK[۹'hVe6 @V$ؒ)2 f.@&ִ%!J .3^rxS)# R⤠CTgX+;vĨR۝|y z>w`d k// ٟ&٠HXq.jqZO89wy# w}YoEx79(xby5üGǛ ]^?+ՐWx0 Ӟo}ЈGg.j"P9+wv{" |C$rm0 ~3/}8EN'|]׊ʤ+cf,ډ[aܣǒ$m*MWGaI+RB^SH~FEXz[U̇gSPH.%{U` auZZyHw] W g`1fA&mʢZs_Y/Tg7:[D>%9RAcTY26)yI!)#&PPKan_G|S:h$tݕ~uФpOOjj~6sUH_iӣn@=VE^R4.!jڠ/d(-\rT,*~=[˷,l] %²zOh-aw~~ĬXvkP Y֝V i̸0F *~ 认LO_% 0P"nu?#C#BydnA5xy{>MNv J{![w-@Ff fn7C_ &V@ILDݍ>q"bQwΡA*^"JC"1%d^ޗm83&.UGş e^F>yM)L5‡F!y:ՠÚl*p< _Y,ow~w0%[⽠~ f$읔#"19B#M3OGw? I/Ï)+'2 .|¸ ]-kS)~Z=6\no=CqmMmat_'-33jU !ʛM$4.a[m@W#†oH/T`~/TQƺ#7IW@?NS!џ8,pzucX4Q{lf:VF+Sv޼\x-D @RRW!"5|%4Ɍ; +] ]-qW$D4$y׳W:9'mȍr>BVpl_?ˇ/@='荷_,,݀yE6!SG Z`n04>;ldv^F|cƓ ü<]Je<{ )Lp"[ 3:&VV[oQW< {(-1 $hzbW[<7\s]}Pw0wqpڪy\kg8wOhDlЙm")?xW$[7vf蜝"/)Чt9L{-~(0_ȑ:J>|%s`j$+#3qY]U֑ Qo+RH|x]F;bs;6@ӫzPTe' >0 YYzr&jOlBIl{g[i ޴ɀovU+n(0٨1m޶:"=T wԁ_յ ZF,.ڒ>IJ&&›v `XzyuNnPC MB\Hͥ1~4]|n/X`UnE< -CO;qdmRIF7.QiJHECHF"a;1⻸cbA E*>jTR,b}}KIX͋my֑n$5;= %D-hjYa@Pى͌5K9U-pj(hI\xfQgz4^\)b,^2.>䖨~ѣxϪ\kkzX7<=4 %7X} ^FV':X k_c9:Zx)A8 xxZjY sK!US5a+>̽gMkԧ?oVI;>юQw,3㞣 g "$҅߈,'F]nվGaVA[ _,c8sRyiFkI,@Ւdꇩ)EN7cb~:ZRn!rzuEy_ډE XSOt'KY;KO")5/k=- Q 6QfmZROQ;] bH鳯BƋ H/JBf8RB~ f0L+Y!?($aF~;Ug.h!Ve{`z-RrOC$>yW^¡`JW&gZT`AZJ3Ԇ]Nɳ [x$Rt9^*z{̹Y,%CfcF ُү/Q [_M41iQ -*-p`xy@2kw[.V5<۫BUNV“_{$k`*8!FV=y"R LK+qy*8_'0wTV=Aє֐(j`g{:J$,A6)w)f!f"֋ pd#ٚű1i)+c3b[%BJ`Cjw`Ȗ?{&*0 `صL CK{R1f>zNgxn >_ CY޻T[x*/E >i]Ivx P lvU֞6[1GqJ:{P sʩrS& 4΂~U-ʙ}ѫ[D9`bRUe^$ҷF5#._.֑Df bpCNӦPuۉ+."Vg{Ocf}Qՠ.%@0뀂C/= K''%Xcw%$cT9$ݝ{i3QٓבTd<=DnbsUHTou2شAH|V>=E}݅TjHHr>&`nhbqJ3T)悲ǒMp#$(IRlPOa7/-Jbś/[#BRVu} YzV7᠟G\/D 4dߞIdvUONUi. jѤ ,R;v82ո46lR&"ZPGSLt1Y;AA^d %n]3{ =<.pR3U2 -+BzX(ƞsᄟbAoOuz*uߖ.o1w =^\J=Mc<e! |&< FCdW? 04c3HGאQlI=X|r>CJ1c) .ۧgpZ&{+VfLg"}VJi yAj#Q7#RiFݡk~ 31}Hd=H9').7,7J.XBn1ߑ~Hn޹mHչs_ыb!+CZVVl6+_+d%k4/%pI9Nbo7Xs揯 gj5:hϠٞsJ;_/ dcMOiX#hPpДi[R_\J %ӝS?9݂24vL&4/J $H& ߇{!N i{6SZ5V`w(cfecˬ1\tv}d™cZf^BO]~ETZ*P)'Vddro**ޜwS8 k& w$DmlWw R㝾n] ˗}gfW7V~|Fbnz2-UK(CA+F^\^VĹ fxAdťls=a>Ex ra|3F񼼙L ];ѠNx{%c-#J͠(lשW1sWBI$:>bdԃzBCo\ $R2`Ƣ0{b\Em~xKBB_2a'nZ\hmlw6jY|(g$L~ESObgSKkE; xsBW5$Co(5̸VYoPB>BwJ W2X"Ft4_|@Q>L bM 4i b{Q̻U2/0lyYNWe^4uҧD90Fls@7j_ HUn[m0<܄=G:\Yb!(jZ%Z{ ; hš R 5dC¡cЪ _ٌ'Pny2>1KC " طx(-wbi3߶y7obQ.S5>彳D2hܦC$9M A X/)Z(^lT5UW: 7aNl8p,7mn6nVxl$X -͜"x3UL6_#L#"}9縵CYB0`sl8@|6 DVJ!$"`D4. Z"E mΩ~.ԟ?n >Ͼ(F߽B LzAc]P\RNzFLAj3{KRz'꒨zSٷyL(e: jdIXξYTY!cfBy^]n2!l~؋iHy$VMEF0O/#";hA9ψ>zZsLB w>o%BnSܖUmJpɒZjgP4Bv WzB}"D ME8P%2.,0Q+F&qlͨ-ydDı1? })V+7=RGq =!Z.-?We߯y>`D o|.,ȵe u.t n6  `UDRR`:C mYDLe 3{NLʓ|H2)x'%Љ}Iq*va;FksPG82Mc 竁Th~f%^T EPtZ!*W>x[/ïxF1<ʆLC a_? ? oS}KŸa`P+QZ {TlgnJL<%^~$٨BeͫFtESP*e4.I56::I5!-8fTioynlVZǀ@{2LX]Eq0gX@Кݮk!=Y}xjzsQbʏ< R#q4 3=ztJh =JNΞʹbq^L ʟ .Ay# t.܌zꗏFе}*$d&*cjޡz~j%GɓK}<]}?`k1>'pU=,YimNZVD6BF4~hvln) b9ۈsw<%pWgz;?vp;ӿE.֬PT`W3Y+5RNSrGي ;7IwS>V=L xOK*C3758́w"BVZ}Mt#C[~-:AJj;.Sɮ 9:~HL2.he,-m\<_.RGrg?ԚdU؁$#+.Ҵީ*Τ(00t_E8 SCS{$Q˜`UJ/"09)as?´ش+Su.΋3XĔ[sW$YB0 8kqbXer}<ӳ=J@`5HW9^8zYW I458ׂ0d)a!ArS&ϱ)z6& LoבoDu e-;ak|N?W_yp{=bz 'Rm0{U{մ+W|pKmlGjQo\ @yEIUzxgIt{7ftG:ˤރQݜV2'GLslFBgߢizB%)!D(8NGтmګ2SPŲ쉆+\VY vV{F,ËO\ y>7:Xb\cߴD BXM M șԬe-d:#-WedL_>@ܛ݁7zbVC2k'e` Z7ײYCinZbrЯp^sZp71Jv39<~Hd,-n!)&US|9u (oH4T[e$G`Ō\_KMLU'-PW% Ļ18c}ie2 rGip/ S:fyդMErhi9'Wj~\ ֱ D.M ݠ)~'} \55k0:<ѣ_ ]f&zUPҌH(M`1及s;`B٩l00?eX2vݰE~Ç:#.@ $%HJ$:#.@ $%HJ$^uղH "-<ʝmsgH0b+"aK5r:`=[ RY$4JKʗ1aO5/o*Bg&uJx6ĻP]'xp>E`ݻvu"uė6VqpRAj$EE*VG]b&@H1鵍Z@"0,6ϭJ_X_ҾJKva`0 E"_|$kly(q.7a^l*u#3,] U/IbIBw@(.qRF׮xXO lY#&C5B` Zz-%}/dxI)d5**k}_chзiba IL 37)4.l}j, 0^ߴb7%!iԵo!WOOހTE>5**ɠhиiba IL 37)4.l}j, 0^ߴb7%!iԵo!WOOހTE>5**ɖhнiba IL 37)4s8mk  /wt, 3^Æf; "u"bT[MKB?;65+,!w_^ @E &ڞ/?il32  HyxC %dַ]&/J\ۀ йoY?4;G | *\ͮo:G϶<Y=F.3"%  ~  ޑqmZmVehfZCb]u\kK3zge_\<$a^et-|us֩fXQ  @=+.o} Re :Q %آ<d* .  HyxC %dַ]&/J\ۀ йoY?4;G | *\ͮo:G϶<Y=F.3"%  m  ޒqmډmV柒pZC⮠v^fK3ᓹ{<$}-|us֩fXQ  @=+.o} Re :Q %آ<d* .  HyxC %dַ]&/J\ۀ йoY?4;G | *\ͮo:G϶<Y=F.3"%    ުm֫mV扡ZCK3ᗜƯ<$唧-|ܐus֭fXQ  @=+.o} Re :Q %آ<d* .l8mk   A|y: f[ *DV~绂`S95۫I  )`u8 H8[;J0:'.!&!   {z  km  [_ GL +( 7)  'zk ]UCA -/ hy J_/G sΟ2 3it32oM=b\[\\]\^]=lՀ Էn 4ŀƀk Rݪр |Ԁ׀؃3ڀ܀55߀Î6 d紸Ι۷j 3ݜgljpM )%fa{﷬&¡Ʃec!V:|קĪ] # Zӻ%طa #R$պL( )~  Ϳv)kο ȷ3##[!Ңj3.H+͡e$,8oӶ&±ś\&aڹ/ʹj"Tڷ -Ұ.;޸׼,էh%5ԕž:&Ŧ ˳7Ҽ +   ܀(WӁ'@ށ݀1;݀ނހ0t؀ۀ܀&?(ׁف׀ >ϮԀՂ*פE ֕҇ԃ ˜. וЀρр5ŗv֟̀́ŝ^ Z⧶ڻ 7ɴƀ3ղ)  .z+n wH$.8n怰~΀ )9昔}݀߅+{ ڀۀ܀(jڀgtuـց sib~x \a_n zZ[`` ppncYc` #abwiZZdv $B&?`a[\b $ d`[`[ ]Vgb[daօ$/ j]Rdf_piY .LlqUZnQchZ !}uYa]V[ˣ?fp /opklcpakLOW}dz~lejad {/Q`li]Wa}hj;LNSOVeruj03/# u"#Tm!2F9/6i4<~ 1[/w8Q (9 '% tZG F!+"  \݃o :ڀV; }ր*\4  L jؾ=>)=b\[\\]\^]=lՀ Էn 4ŀƀk Rݪр |Ԁ׀؃3ڀ܀55߀Î6 d紸Ι۷j 3ݜgljpM )%fa{﷬&¡Ʃec!V:|קĪ] # Zӻ%طa #R$պL( )~  Ϳv)kο ȷ3##[!Ңj3.H+͡e$,8oӶ&±ś\&aڹ/ʹj"Tڷ -Ұ.;޸׼,էh%5ԕž:&Ŧ ˳7Ҽ +   ܀(WӁ'@ށ݀1;݀ނހ0t؀ۀ܀&?(ׁف׀ >ϮԀՂ*פE ֕҇ԃ ˜. וЀρр5ŗv֟̀́ŝ^ Z⧶ڻ 7ɴƀ3ղ)  .x+n fHnm$.8nr}f΀ )9ltq݀߅+ton ڀۀ܀(]lfڀddgdـց ukYhr dh[b zjga\ ppuij` #ab瀛|qqw $B&?篖ys $ 蒩n sopօ$/ zxa .hzb !}~̣X /o疩Ơwisvdzzg\_SO {/Q͎er|[ZYZSV]alsuzxwnj03Rrχukc_^_cfgfvl&L/2opZLt|qh_[Z\`jrk ?)!{a~hۣsvxnd^Z`bVSg  :0 ߢ߇댛tlgmX *1q苗ЊoqppQ '1łrⴍ򍏫x (k砐ӵ_w :_貊Yރ\ %OE ~&/x|ڞUn`*q)2jlN $:v[bS`)ߟ8p_eӆwt$ 8[7H'j/# u"#Tm!2F9/6i4<~ 1[/w8Q (9 '% tZG F!+"  \݃o :ڀV; }ր*\4  L jؾ=>)=b\[\\]\^]=lՀ Էn 4ŀƀk Rݪр |Ԁ׀؃3ڀ܀55߀Î6 d紸Ι۷j 3ݜgljpM )%fa{﷬&¡Ʃec!V:|קĪ] # Zӻ%طa #R$պL( )~  Ϳv)kο ȷ3##[!Ңj3.H+͡e$,8oӶ&±ś\&aڹ/ʹj"Tڷ -Ұ.;޸׼,էh%5ԕž:&Ŧ ˳7Ҽ +   ܀(WӁ'@ށ݀1;݀ނހ0t؀ۀ܀&?(ׁف׀ >ϮԀՂ*פE ֕҇ԃ ˜. וЀρр5ŗv֟̀́ŝ^ Z⧶ڻ 7ɴƀ3ղ)  .}+n 哘H$.8n΀ )9݀߅+ ڀۀ܀(ܡҴڀـց  ˨ z pp #ab $B&?ݛ $  pv։$/ y폚ٮ .U !}叛gỵ} /oz|yxdzvv~ {$Qtdimw}j%3HbmƟϽ}&L2m`y~vWM| | ?!rPeWwǣs̠q  : nj  *1wqqogtӁ '1ŗwsm}z (k碰󦦊i^ :_財alxX %OZ嫵z\juֺ ~&/糦ۡߚI]vb*q)BcI $:͂xHfkL)ߟ8zӈyt$ 8[c'j<ݬrGj"24 vQ<&aBe!6Lu }`V E/>/# u"#Tm!2F9/6i4<~ 1[/w8Q (9 '% tZG F!+"  \݃o :ڀV; }ր*\4  L jؾ=>)t8mk@4OUTTTUO2 Iکr51n KLd_FJޛF 3ԋ: %py/  $9UnȿövN+ 6uҟ_ (~<"#ZAf84 (pՂ6 `}1My1=B(Q"8)&}_L;o$X K ? -  !  s  Z   E  9  +  m  ]  N  {E  d6  R(  D$  5'  /%  !,"!  #%!"  %#w##  %%~b$#   &&gV%$   ''WN&$  !()LG)&  "'+@=,&  !(/23.'   !(//..'    '////'   !'/...'!   '/.nx-.&"   '-/fn,-&    &,/Vf,,%  %*/EW,+$  #).>I+)#  !',7B)&!  $,-:($  #*)2'"   ''x'%  $#\#   IH!  .C   63!  )*!   "el&"   FY"!   5C"!  !({4#  !"W*"   "?m'"   !,R!"  #'g?""  "!L0"   !7)!  !)m#"   #cR   C@  03  $v'  Ue  8N  '7  ^*  A|  )X  qC  S1  3"  #p  dZ  A=  %%  t   Nk  0J  4  d#  ?b sFQ*ic08o jP ftypjp2 jp2 Ojp2hihdrcolr"cdefjp2cOQ2R \ PXX`XX`XX`XXXPPXdKakadu-v5.2.1 n */axˠegnFf|g/R 5QvXbk?ؘӊv7y0i;%F>ZXd S;JE:i ߉)yA,=YWmh<"E:qWF7Cgۚ;:#Gr)1j+HaC-#'y툱*ΝW?(@sڀ@[&ۅ)OxS;5#.ӣ1OЎ56@'Ψ(>%ɻ~~=z` =~B`8>AqV H+⠮OOx"FQW|fo#-p@cC|=-:=PGg9r"!ߧ՞-wqLc?$MVGH^ObKQ%R^M_7xA֧AFB AD-5PdF9@Es&EPDt { 4A&cK[.¢܂yM~f@9d-`EmFu) Tw`+(cuoiޒàK+ܑ zf_75pS ?3nR黇Q g"N8IX=k)KН,3vDk {?B3Zr954bP ʇXzRt9bOˇcd /o)KJ@KbU3@4V)59fI5?Nŗ}JCe3Poĺ\IJ:T }{2m"w:%$=OhK-+( >Hm5Xԣ sfhZĄC X Do:]l-$_VBtS=.Vf'Q"%IÇ %:C8NTNGlZ;tlҼjnL\3:Mӱk = s({ B Mi_7^#S} QL[Ϻܹ!5)wS:ņ=x=#ʫ,?uʔ~|7 @^+s\Mece8Sh*3k2Ⱥ.&)~ɯ6|nBX\&{]_ubȸYp]qQV.e0_?7#,dzNht)y5ܲQuy [U=O,@6_:򋠤ɨC-j;Tbi9H$>Y1P'bvOwV9qfƳP tcFk&"v[%ed$,>OfȥL0kXnL"gWMp9gga E1ܥ48i05@秄,}Ѭ'@9zԤ6(/DlH]zCm7i=_]+zb _!<28Ce1ãCIX C*:ݑj⋰sjB=H-OLN)bN߃a w.7p8Xu j$OqOg[G6}ht;r/ìܗJ‰|]XB6L~@x+4sɩrc>^:ñnRu_{$9LX+O!}{ĭ uT]*~M K6iL8N##'CeTQٰ?N}ިj }9<b kF5lGCԵ HUO3֧z#PVs jtv l<2ަv\s=iJaEشfXk% )D ' N۳ QbC8"nnө %G|Pm֩Mw)[mSL #=5_RM 4L\ԥ.>4N?N\ƺ5 M;K7_Y$rOᐙ]|}E;&჆=n61:Vw@ͻ'TW#iآ5^a1n?!fՔYS/%0Y!VF_.6xZsp1@,>01#AYYwB`><|΃6ċ-wf#;BWzEdRy)#D1&yo׿P 9I.s@4]Ǔ9\`P/#%[(Ӊ>4a#+}2dCK5CռG?Nl:y=܄g<ν997"+e~4')_gÚ;IsnrpD ɠP(iG{NjJ0fEZo'L'v"CN:0+ 4U%($~z廛rssC &`4ʰ R GY5o-IQ޷/M7ŗ*LA((fPp%fHSN`2}{kyVYܡ}6o̠$Q aSTw3F zAPONWx1PMՕ>̳Wa_<ǦO,CO[~>d\7v5x8(RUʐH OQ D*v M <$3؟*nF 4t*cd쯤(yصdߜm~VDky] ؃V#_" 9l2*DՇ$9 -/P8Wc$[sh;"GCEMDbvh %_WV4s[/z/1,)Mܵ\6rk~:*l17a:C!5ݩn$nq鶽eU3Q1ؾCsg4B&֏-z1IPcTb)r\hm` IlHK#'Kh]M cuCuGOՠ+/ (܍ 3]y&L̗1){K1Z-CXYBPđg1V\ !Ff0 EAO0 Ҳ~6%Mx?fB xغ BEsӷu)fڃνϨƶ}/Ҷi"hbns2}S(5]l갌١.|ֳMxJ_Nm=rUr*0DgMkzם;?/vQۇO `Llα%iRM$ 2®qfr.ɷ;yH=E?Bᡑ^p/`JnmL.$'I@X*`NCYu+B)mQ$A]^Q&SF9J:nx9$&BtKD6 / 8O}4#^ IqE=`yxptф)$E1$y`aP4eЊRD~t6* qqwQ9#/:?H[P1JS^VZW0a<]k&WR&OAm8C8'%Yv᧯AX#8TW͟'Zni2#5ޞofvJkzz//(BYmR㦀6d8YRjY?%#*'19h81g_qcȉe%DS e?ż9rciw1OyTc7\[ӯP-CLHl?WLN̑FN ڹ-c~i?xb$J2iMDC_GNe6unK ?4m)O+TUDnք&\.%V+Jް-uO`BX\R^A 󯋊z#LŔe+zJ-MEE*tP$/ŎL\]y,#u'2jqPo%|`A%WK' `Q~罡 A~ UCxz cM/Zx < H>Upq-[`~;RRF4P:C}3|hC>RhBt!ڗo;2ѓHH!#BsNbB܀g %^qVHx̸6؋_~׹Fbp+/}A]w(Xo0V|v15z) 5z`x7 *k,e> `ny<ٟ+bF"B'^xӊس|_>6K2H-3]uAE#d6> ]|\P7V RFYzC_dAԫ[CJ~J /+@/}bi.MJ' )R)M%@thy;1]#"pGcd 3g _Jm3 McyLKC|ӒIu| gؚGH;F^6 YaxpwLf,2k}dw-I9% 1t˹~6D's5P0Mk9{cE : 3-C6ο8'%=(bʠL`NEbRKL.J咻Y$qz~uS64jK}.`hdeeBBFd"1eHo>j}\WKWHl@ƍOqs" XdKc4P)Ns,"Ssk_fIΡ j֚_fAzBB6(te0/r<8rԐ{A ʜx:{jC(@67durvuDcZmOT&P$q8upcr8h'gH#UbHuR o`aLx?oyÚi#L_>0ٶQyл;33RaX]څ3*Bm1ؚ-%7)_[Mk0G?2P׊ Ԕf h/5vP*UYz1]޶+/VzG)YE61 .Q74v7I"eZo cPۢT f6QS4= ZP Vk:ic޷/ϔVE"`=?mkUB;P^ƓɭkUE&Œ;tZY[񽻕ހk=$fJA4d&,XYޥ"n-Hڏ12znƁ ae/*ZFn.-0N:ˌ5J`/CQT bAIVs 4iJN3WF3PXB\jl-!н5.ܷSvMB@l Ɵ_ 9f՞] u#F ^{?; dU8L ʵ//< Ng3wnԟ$Ylg"+Xq.Sc~S ۟r 5I.يt FYV9CUMTCѴtW7 .W2U9ԟEHoz0|{.%J%dEO}eU-ʕ^L~yrڶɮw%_FcB2/<%Rɮ{ZjDXć2#pgMH |48H }V+%2FL]{Ə=)$ bw0tY3A=hԒ .9C R5:bмj2E0p-]ͻ"7cnE^:[4XX!~jtx6Z{N`%/K|W#7@qW%W'LVgg'8r5Ӧ_[Mkf$',ՙ] UAq y/~ 9W*loJoJys4_;Dchvpݓ36N=WB֠ܯ{F*¨8c+7S[}lm˅[\$J0(1uVkdŤʓ?,᠐mGnXq@FYp43xHņɹܽ2 -{b!%GaErDj%9絼]z!l/=K\88Dy^raB/~o dmiO"]T]K2ِl`6ϲ^{Ƙ>ZgiI4}Jx3 29SLY*"ĚxZvвO  ^F8;x<*!we5x9,< 7†1^:M/Vr4a!8q - %| =P ~Yd߹yъXD|= svŕhpv^yZ9 ä Ѐ#Q+dۅ)]k协ڧq[gVu4dзe-9Qji#?QQD2=HMp5yp 9K`KlK#:18,+\LrtvΩ<F,3VL5jt\}88ݷDd1|lizh9{LK FOA +D"m5OPmP].4yKTVtA"(I[^BYF|.ZY/D꧘qcՀG͑|q>%DmJ| /+U]SF sKn폪I?SqY2Pk1P޷[8a]h頊G{NPfJ=qƴ4>̛,nP(Hgߜys4x&Is-G'7'#le_d"Ww6nɛ.aHy]xq cIf5q#DZWU P/fep v5"q,=@_Yv|sQ'+S.2kDXZ@X4kv3 Sa\?ύgՒCXul?(VJxyd;;(ZÚ.㨴-:ԤԴ8}Ld1$+|z| 47EGu^V[woAp$_x>AT댷ml4=B{K~ Ss/v9CO,D5S[8 Ǔ&@Y6.&׶6 )1lK}IÌ*F_^OyID~'nԖfd~6#aU^QovCI".X|O<9{bV @<׿sRU-"*+>K;Zu1bE|K1ی΋Ymj Y߭QkZz LB7…RyR]u.FϦ$l.]jՉ}955uOh ĈM_M7'r+lS=i ^ed=yXbS?V5Z(S.O§3U<+6V]SB##/Z]Fæ/>CEE~'R6~l7)Xe;+P(J.%«1#cͳszqSZ 55* 8VF #5Řl]@?ÀV[s1g5sdX~yP%MBږO4 hP!7xwp3pH`Ӯ ]o~4ōj?FLxxn8,N}e^m N1 Au罹^eCm2#B 9VTe|%])]ŨAAl <׺F8xnk" HGBcyY"v&Rf^S#`Y6o_@NrZ e,p&UTmgr G7.o;gȝThe5e+CЦaBmB:0 .}e Qhf*<#z#E7v5G n7#5E3Dt<<%^v/.'v&0agh9~ʾ) #F%GhQo m\Y.ɁveLR!ï!\~n$tMe\|jԏgP4[ cێ޹slX%VᄗVZ,Q0 6\sʌv}. 5Ǒf"Q)]8(?O+Bq#kKd4_.yj4˖C* `5\DŽ,W>`x]L/Ri̚3^W/]D7w Ȧߵ}"BǦҞJ2v4cbvF6y$\qs(V2b졭B{ٯOgRoA  OkOf J9b.ᝨG޴\Q22"Ր\b!zqH ![@AQD);e$%ʣSC ~ktS;VDOʻ l(UY>hAv1EnT)`ZW~DzhGd1͡;0ڠm@>m{6H䡋4z |^ۃ (U`/n'mΧ<a_ȟf f x.)vY/y}Z1W;fgDj\žvޠ@ϻ}v\Q&i-  *!F;Fx6`*~x܎iU8D$DrN~bU > Ժ +6ALw-4eYd&:7қpu_Tsm|nD" 6 b+Cj[ӳ{-U"lgFA+.ʹұZlHJo(+)UJ_ |x幏H+h»Gi/qKt$InND|}ll~BqK. 5 ,R zϓϽii eHqȉW.^qؤdL=s]j 9QbOr;- ̚χO&)OVg1BDO(Bo?:0^a)>-ANHtKvdӨ6Ӫ`Fn!{'h'{RĸZR0ЇL6nj2GΟuw I?py{+_eCzԁ߶nnlscI'hB>1oҚ&4gm9n4B,PUzp?S%ԤНwp¡dz6῀b_CQpMjeET>HY0be=u525vvo%7l׀'ąpdTʯB я{5,P]afQMiUժrLz?$A~UQkR;#\?%^ҤyH.|s`Z w#H,ӥ3q 4`o-ct,\]z9e`9,nO/<y_h=az1y\~]X1N.(%.GR| )J(|ٯw :}V#ʥ|]yQ9>WzLmƘrmHCڵJaVpëba vN+}E]ySu(,+P3e2o3 Ha!ص~ <#a!|R=S'\. A6/[a(ZJ܌=50c6&!~M]"W?)>oJSI8\;Q8oB  [2 f16fs܋8n\9ֹ/CǶ#|C&0n=spr]YBlp(Uv J;& sgoi1CpK4䍜ɂzɛP|kw݃ӎfzTl>TyIөq®/ėŨ@ ֌4I'(wpn')U\:0Eub:3."ZJn;CTHX)=(N#q*,n}zÂR$|%wѷK8=nG\Ĭ,a.vlѡ+vp@;cZ`-w>A> O#Lzjx8|`Ն! ֠}`%W)ߴ"|  n".OM}iˏJ[~!Paci zo37j;ͧG=ƓWQdTcP['d; C˹|儥DT҆`X Aߒƞ2BE h=J-{]fVLNꧪB?ձ52Od˗y]0A@/J$jM .Y^B/D{8?Y^ UL+ axaTl𨪼 %oe3[lAhpO+Ň+:o gͧ_TA[?بu(X]OKGmZ84_Og7'FFnJ>ݙZ9-U}9;$pߠPiqׁz%l:(?޻k'mi}"k)㠳QY&뽚;J/jFvxV`!P^ErexQ/ζrrwk;(J"JgE󔙔9u8"Չoc4IdV3 l2f5ǝ%]v6sSb\oŏ5rn Yy΃:Vu'#Cx`|# A>U٧|'kATv +Feu} J<ٵ1|B< ξQbf'Zb[цa%3>Ѧw/Oo"S3зFvY+ЇM9"34n.'C܉ ddhqd1TFNDO>orbx &:B,?L["ijk?KtKp,&FbkZ(3R0Ԃ+#UcRb}+i6l5jՐ ܖ41HCVGj{tIz)E}9vV C>0L⡜h=u\4[#me/s7{%CiQr^`Ռc qRG~qlv%0,::+X <6)O!L;=[(|m>᧖Vn[GiXV;oq\)<ƪZH.\Hiii1azQl )ЫNDNv.s`kqֽƴh×ƐߛgşJ6FMxB ep8njԡlTIٯpKbRF3`uVih8W}Vaѷެ>ءPHW6i@65d(Q?8Q)xov oN=y:t;l"Y8Z冋/pfc˗HzA.f{!ƟcvW9/YN,I?zSO⩠;%bHLm׃@XIg:s;o}4WXԣe'StB6Q(nHH h K\@ɁXiYiؑX n?. E܏GHG_rJD3WBks<ʏ!c˝:[ iYLDah$_e$69ӾʑULUS_'ŇiyLw0{Xl+P B|H Ǻt:>e΃ ǵRENy,{!3V ok$52[ Oyc O O! G~,8zoG &_K'r_ XڸÞ;(Ww 'E:!ՁZ\ڥ1N'ucT']tSSb1QOpMa5ִZdtN;=ݥ55 (~s>9R2Za3{$DМǫwG-qvA]B_{2QNp(u `~jy |ol0~CCc?SbrVy;S\xvb">{@/2ed\g@3a@)SܖPW}M07;|%S2VIƲQйt=˭NADُ 7F2R >^l`0S|In^x[agX :-zaCQ7fH"}b"wVP7:H7$رčYl]rhI!F왪&>^a/,y .,j_SSw-HK,ٞcc sC`Ɲ,\{*&m&/ ʰ`?ܾwcC!zAv&|c. $6!_VMo c [>O=Rɘep]$)v'+)@˶OޫI|%p|(I=Ȥ|s'ȧ}.~` %j[!.wsgk|͌:l3# O 3+Ӹ2뽜bg.pJ" p`9 ',גo'F.>LrwI:A[k6".Ѷ7VW rYJ"v"$}m=Z#kKWJ!^Ccujykxm L~ R+ Im2w c2ٞˮ nlۻ3ͩDwza( YҴ`zx7jC ؔ~2YRh",!`X=mC s ?$քLJq"p|3?TeUA3Wg1UP 9TLDM0nPXe&lNS0Pa;1wGmMe^>Uц@0=j"꣓rkl[HJҭ{qǞɶ>I]XBs$=0Kk2 t5rO.e%cy>p51Sn/YOG0\;B-Vވ\/.툹}(sqk&If?yP?2Liܣ$cۃpL_xb?8&qorfZ .i鲗aZ 1LS}ZXs광_ZmPr=SDcOH= !SzMI׃m^j"?>fܬm{ae5nQymHcȷt=q6rm@0-| ן]ѭɆfڬԔ"}M<, ZEHJ);?!T:TFƆy3@~5oaR.8 va]h]EC E%s<9IH5:@DN7^s.57NLcm愲W2v{"rxZ=Qucb=˔d\Dps3qI u`1wjYlq~}_;0zNzlr~ iEO \L"ZKO\j:XږcNDx_:Ql]*Rܬ5OqS(? 8GCup=I&4Tئ ط-*=gMp 8EiB'K4p 7KB?:-\-0=A+WmNjNV>@f?6̐7‰04̪Sn!-)]J)}l[cpuR)jK/ؑJs3 2GQd8{Ɯn_w=֫(v;&! I=L>_TzA[g< W*PN2]\:Yd +ȯtߪhKٯ;=_R9jY;Z3gz u~ Ǥ"6EFQ UK/daLSq1mmYnsƊpy{/5-$QWz0%{tU:Z(\M .{&k|_;NfT7Ⱦ(\o>R>[oovTЁqpy3)6sNGǧ(EBtU|!=ފZq_ýzC"񆸽L%kRr{߮oZkD"ĥKY7og-)&lj >5Eۛ3ce[eH#ͧ!AE;ꆬ7X6Eh^ylgf"D#'k w>iDY^z Xh%dVBe);#>%3YPTu ]vĖAC_syF&=Z!9$ xY$+HT (>?X ߸S>*"Y;G2J@@X"9\YnIakSۥmϧ<^H?j}gMY}Xal9{(;o %\B^S(TANZrhΜ7HOt[u@Ǫp2/ F8نv#;WPl U3#d"7 IîX)B!J"3PuآkGx GݔUHꙹ@O:VN 9ڸd[3TC 4(y-VcGזQE-z2/~wTo ؁o3}jӪ`qYzM3ƱK?Ty_flp>Nd^0Fr*Pc4kKQt?4SZ P<n"b RT=0灍oj Sn1yKntlL3GYڷҌ+wÉW]w&| 7/c]*Xy]SqxTD*.6|p$W7㠄< V x2b*܈ykmʘ4AgcbXܝ5<%VB"9͐oo%jJbkW# zZdEŨ)`2_ XkͰhM`wFwlk&~NPnIh qlCbXچ_T-@jGNïmяy:LC~N_7o7o֞')ht{lcU>u+.Ø ] VqoY1w捨~>3 Kʢ޳w.Ki⪛|R&;M7Eyv{5Ѣeʇc7Юwi7^{\q{iS긹 ALaSR%5O,\`v25۴n[bNrd%1Sh80~0PGo|E?C4eBL[Gԛ\rBTn3oML/PW߽E k[]-g.?J;CR*nH|T)NZo,>慼O׃̎Gjk0y"պ$@iTWb8vJFg¤A2={yV#.@4e-&'|%;p+5aGq^'WH1rl{/sI HK %+LDKHNg .f;0@9@hܦپVdSL;~Icz 8Zc9,T2U? G 8I˕ߝ LJOV񆣻oO4J݌h}-i@75,r\8⽳Ao`g)먫gg v///6@Is]U(#Y _>S=c'b3}&("_121{0f%S>Y;~ aHwWZ$wbW, 0?x!+wR[ T}eRꣲ{ڝDpf4MVx%gČs3$9U@ۡ ?UCS vl+ 1|0̵<3"mZŔu8aiS0~@Dɳ("E!ɕ*oIZA `"_(nT=FߓMoYò}.7L8-cP Ko eR8aИ B波D'm^K]*=ni #~8E|_LW ka,ɢ fvDj71J/B12T)fG\vcqN=Ԇ2U/y!]OQzA6J+?? r+.)i7/[ TZ}ePiR[eLL$iH(2Fgsyq fEVKNEtA |\zNŔw}ost"$iBT9Ѫ?)!bG30e~];ˮPx \“7MHvc]S3C~k'y0gv O!9@Eؤ^;ᙴ% ԯILȲ;y!o.4nì^\h1 ϣ1JgPCݜ*P# NYlGf/R"2ZKW@O7SWјa䘵Z0uQV`,[*C2Aa;mP5j}0=/2H >N^bOc ާy/ޏW.AKsjyjvN!&O3 ߓT-:^Vc5YtiL=&.6RHIRE4&W]UNߛ>?bz2ß=Ay=Y ? T,7~n`kX*Iՠ[QhS9: 촪A| d1ca^bշDe NO`YK4sem){*RӄKdR,G'dwx-'W! jc/ڮ:f 01CBrfH/_-n?1O Cאa\HԤ3XbWt%|hݹǾwbrk\fc3cרPT ֵEvF@LWfZ2b*:}Y<&[}aɇL@^. @(]!Íu@|K#q V,^0YZ $|_FlۣL/ tP m"uCG'~X1``gbvz|n"Ac6 ^C[r?<xK:t.K]ƔMbʯ/UaQ:ؽxVDK *馩!jB_u%}ẏpvYD7`v^ZyjSa^gV륞YT>xO?ï"Y$>?Li,0@ѸbWgG9MfCpܑ+_&+JE2+/6zqιne+9&LGi_+R-at2L^É^cG${rL|Kr|i#'$f@'1Biɼz`GqeBr?w`z,ș8r`zuZf:B@'[lC/ExnHrV{_3x PCŠ@rh'vݗiB3l@*yPZ<%ft1Av87 ZR`>@(?н!Ti:'kU 4 ?J΋^{BЩBBtIsы5i|[bM *0}m4Psi+*>d1G`Io"c.ܞxl[(L ,Fj ٜgU=lphhtˏbHwi 9?&%@04b&m@: 1cƮCbn$۰oDe<=e&J'ShpPn|(d_jVQgrT)dJ$%{!.V1A-m:X)A^XY 02SwrzM_U3/4NTFH/@{#!VtIZM3ff8N5 J{_0f \%h }uFZǏvzj!jH zI:zH ?yM7нg+ۿthB ^}dU Q>Ow%첣^1c fd4L" Oڱ\xV9yO6LijGZ _`ƠI.XUBj )! ḋJ{oy$4ι9#"P LJZKmZp}ӵ>KAQhot3NNY #s}{YL^L(<ცqZ>F?rsP9X9?:o$:F(14my!N?$”T~<բ)c:qH*XrCOUܕL^%䇙 ENcPt$/<^VnCY> F4zȇ}Œ? L$끫1n**S/րH̾#w -xV+JrC-nEm43XIR|Ȍ%9T#UŨH=wkxcf le"jm|9N;A$-f'`8~vZZ); ~f$KDOe 8.3(5\EL2)Sj;Ⱥr+C1Tc>,B͛FͥjkKpl{O<:pJg-PaO|-‬M2X^\Su1W8:Ho2[I\Іۭ#KP zߎ心lM]kgDf6%tRCm0`☎J\W'Ehm-6]`HȴGv)&Ƶ1VvhKZ2j?#zgJ05FU;([M7Ӛ >6,PYA^]p- ^UW+> ֌SbR>6d5c2B\VKiPy/Uډ>Nd' Qi'*)N:3)TT50;G] h|Fop:U=K&*ZO-khɪ[%@ےɳ?勞!x̃j}p0]ZOM)M8<̀ȓ Uȯޕtt2;ûu"m~ָSin˲Vs%TI8n?)Bz;Fſ<2m wֿC3R Ȃr* gj%xV&Wzڮ^`sA*/6Gհru&|v\.R b<+s̋)${7Kt7B-q񜰐ʕw`X%  KC'?A<*ݗ. fLn=.x@ ,4j_G ~\A,۵4+Pm~1; \-b~wk=3d%ݕN`8]F4{8b*th.-_nHIGzNAJh=9UӃOjBU&&!rW;cQa)EW)ޛ(pLA vheȽ*!_{t[L:b,-;$Bw C<3nfulĒ$CD5̶#͸obW5 L̫" K-n",_ axidZK^'WNa:D?L>QY4)8˶)&3͘!)3 .QET0)bN{d4R)57M;KvnR'o1[mLƇrGܔ#Zuܜ8#gK^o!' TħLg@zW,cf n~*q;P`(ޙp/E a7&]ʂ9!Cm&I"rĎ_-wv%WqN$".c^ !~|(F7]冏/;? 6jԿ3ů,@p\x,RR+JѹbW`ZR G)G2SC&z0ic09 jP ftypjp2 jp2 Ojp2hihdrcolr"cdefjp2cOQ2R \ PXX`XX`XX`XXXPPXdKakadu-v5.2.1 $΅r2Lܪj%j:POTqЌo&W9 Il0XC?4t7ѦV@@#̲Nb?AD׭;q=S!5 s}h;dsgxZԳOu{!3s?ը^g IXKH68֑TXwb3S DnӜ O~P.ϊÔV0#Exv3ƉE?]BMkqY4yyP2Lv؞2C K_:Jl:v71ۤF `uk: x#)[xAɣ+V)VZQTMAUFkBwV9qfƳP tJΥX/0hhɵJ~k0Ce-:NVpjy9EyNSs(X}Y1'PB܌.,@8$2YU6%B rN#*ῠ^Ya/>>F<)u{ z$_^y<wZF]<'Q)iP3ٿւLݑ߀8KZ-c4J\贅zvi#%&3OŨ@=rm(,j>CH.)|垆s76#0i?y}x+{ 7D24@yz??tkUNQ(ZSE y =$ְg(TLsW ֛R5ɔ_ ? Fi:p{`MRr1 c%->5HachjlD9^Iό=_.Ҭ>}]E>4Bpqg/VŨ fا*5 O m{/8O^?%" 0W[\vN~HVKd8=1U~BbaKj~}y-_ u231]|5a1<<ƾ0ޞ5"}9^;ܥXudxFeۉ׮-?fݮ3U2܌ ܕ;m~a!ѳ4+gSqG7!ٲq96*gp8MLPҐkXqlE+4q叧PU#b}T` ~ȸz'F4oPrj;^=^E;H]" _[([*pP`>Ec3 Xi(߹$c&\[Sntk6 _fⱯw'(bdvfǔv=Bf(D? 34>#PI 6y dsNfY%3VFfԆź=" 2Jq/͑͝a g 5⸻0En{lNXxIQ #f"M|[NWmQ&x RC`L=#ݑ3ZkC3s M?+9(T ؞^LxpϧΠ6wM͐ -C-F]|j4EqzƊc2*=&ZaN=n TYǭח C [S4m(nquŚ!tj3îxmsp;Ίk\PqVV^nTʺ сC/p^3 㖓>BؾJ`g…*ӵ1" E.$# TK=T|_Vzt'{绨_st*2D#DU+c3#Th5[w<Ŗp=lf=J['X nK+mIը"7^<3@—BT@i+a]fvs79Nc8e'Y8 ){h.t;G\ L=")_VG 񽍚Oړ010)ōw<ۍ`͏]+NNpFrx ˊq] UϖPo?'پ*Z؈9=q8rSX&˯Z&+b!( V0ƿz :\ZynR\j2fz k 7E*w;BS QÉk28qÆ!kO21D]MradG-v(@[گڶj#תּ䯤\eYGEGVp\a( v5 \o=\N@CB 0 C},dEfL 'L9hϨS_}U#5,4g$,$NXi']9_ FyF?lUΎ\ro&|нڿ(eB,,a^ G)MkD_ɡ)i[ǁDZ.daoҏ]0Q¹=~w[yaJ ^8j6Ⱥ{i3ᢑ^P4v-_3$^O-UX<~?eQ|̩_°Iڕ_tVN! S 2=!CZ`dZ1=8CC۶5#c(oҊ7Ǫ?Rs٪LȽKV(.?4wu0Cs aBtx'ckjqxkT;L -wӜT4\@P(B?dί못Rxn]Tr $H6:2B@ghtt5|R&D3[ץLTAƠ)/t5!րg3/l:D *{7il }a0CV Y;fH]=ʬ1|i~'CRMT8j&JAI꺈)1'<6ֶCserrȾnSUwtZozg=U1 6Ov2Y`:2 y ~˾lY+"9%[&ƛڦ3yt %QjtK)*{|oIo1p~(x4^kR4yg+4vO3ڟS)_ߥ(;F: 7#r v)+zC tQ|ܴKQ]i,>*ȏuN7 x%TOyS5U4kIh InX4ܵćr&:KnnFX ,zgIg2q =DwlJt@;1CX>!Im~~@Y.@[g}T2.J*挊 8d=F R#= Z'"PE,{כV`WzR^ZQ2LWI(cmn%If Y-O"l;Ni=}x`\=лA( 1"`W [µAU]Dz,xkj2\ΧþYnceKKNb/npױB3'szhtY,&Su=t(۴5dO []ȑZ_k󀂯#U7QߖIhbz?">λElAZN$Y5r$Ҍc`6?ȘLr?>ZM-`h !!w7ݕ2[*9/IE{f1M뾘i,a _Su: -L|k?A >@u )vNٕІ͛*O]'}5ȸ Ն=h~%ʱIJj`𫋘Q4⦿΃,^#+߬)mbY]5ABI lh֊ 6o"6% pC&d oc_W5y|"8 6dQz!P%ߔw S:\ko1LRD1\Q'Ѫd$-Dܓ=I. *zQ17^zgI}`F9|jYsg_v_;tgt<=RJK F=SF|tPO_PI{zҶ 6*B%xa^_ʹ Ը{eD4z.(!*kpV+1[^['b5qKŦS^#x,7as%LΫ;d.Ɖ9 v*tQPYQ)s{I頠q[0kfVCB|~H+Zw`X]eH2*HO`A|c\=G:V^`- 0~8Pe_OB)Ow>j(ݥs^pDblRe4ʮ.du#[WS ;גC蹏~:+r ~`jUI3PƑqɴٱxsFgpg|~2E%B`WnVyAb-L.KxcT6ϹK1ܹV}IGBǕ6GE]˶}ЋiPXɎ&'zTqj+!5%Б:ƪ/ +Y^4MA5Ac%(s1Lz >>~ߤqXϵ,Zt 2z{FR_ʨ< ^LщQJ8dL| 7D~$JMĮO8;.2U#PT+5=iU=m N(1S'A V 2f:ʼqZD$\jyĥcmڲŚH2%'! \8#ajduɨ@C@u's%u}j>ў&qoWhkaQLF"{>;sBȒW[y8v60:HF6= /Iv,zT##'1eOh/ېR_aX" >no2ȟ0C+r\iXP#_emE~jK F0 *b|W@D+Ld=` !bVm]ַsXrB6KMuWmi6̈́ ' VH\kCI>X AkP8݂ŭ$t5m v djU6vVlwZX',6o%o,My8 Xx}&.V_rC UX5uV&YW赌$x-C70#I~-P]>xmUQE)eianOo_x%kZ0IR )hy;v;K"SAI d˶(l@#KD[F hzQ% %//91NNcrsrJh\E7% e7"(sfCk!g]i ̴BrLׁ} :;` y/r׀vN@EYѭi8x#wsD2)8%FΞNǻ`o.L*26!T&D BW~4'B5&q 9 ];b[CA*j()p`Lwbn,Ok /b|?B0:sSǪ2xgK ,v}UHM o#E8d?E>mw=f*=~19/ͨYlvlAё]߭E@?R[: U4Dy܁%nˬ ?о.'arjJڥ`~ 3R)6$* .F۳3O&wfP֊{thbg:Ť_Y85|Nl%:KbԮc󻥛~J'u=oRodX<- 5gjvE7P c)NQjG]NrxKh Olfz@\s,RZ]r<ܾ=Th?LWxfV:/[] eԢ`7҉l4d ZRfqg-psC+~K[Fj-#{-3}bl:| Ӎ6bտ,' dYRdHi7,gԬ BX +|:}KgNYpG;Çlxz3h-ѕuvG_ XQ]\)Q:6K61.[jn'R8i.BxleV>*o`U Yt 9;ruK\Dֶw%أ7Qy9H0R)\i,G3Ky]D1˜ J(C?,zp*!!8h ŋAʵӚwR( u{6dMHvK20х6wj)Uwc!C/Mux7W75U_J4'T8x4PIK,G8e`m.y2]ǜ~aH8S+ߘ Aƹ|RAPjz`8_+f.kʝܫfd2F*z7YQmх{QʫJoZy "V_Fg8-~9LR tdUSrB8M.Q*~r0 UI:TNCnCa\`rk׏(-re$#RZ_Ior}wJKTAWȅQl\,i״3ނ:tR0 GI`XZ&"mDk=r7wR60dl,@<ڕ=Z w&ʩ`bm}([RJMuP6t}>S z'z:*t/j" *]+-)ytmi'aznަł5+j7B-|إjқ,@?Ȧ8YГPSc}~jcim-KK~Ss|ގt~aPaBj(Ú{N>WuC'{@%Vr iӳ{Pk>.>*LJ# M2l1#u=m-c6u!wFd>p~uj3<)V޶ͦCl(})WN/bE4C9a`*ʨ̰E.NVXY{ǫL'FK ̪)jdEHu z"(+o414aZw3}%w9blwZ0F^d{\ǐszLO-jŚZC<݊2@LQw~ Cs!Qv?ȱ%Ts3bE/x['*F`Z%x!7 wkSL)CCUä5: q16kv>dv-U \qMUB_Et'A2f ￰3{`SgtRx}!85җA`2()kutR݈f1l X!"(K5l0p,N4ߥo2yn:&+d}3*"psO^cˁGA]'%p>$9OJڢ8He8B.$?4zeǹ{ƻ,vxBrECE181d; 5w ,Ђ%_⫫+Ǥk"dZ},bw$4S[|r.IZ9 $uUM$/ +k+.FpӢOeo*t}U%~NuvCJP.;@аxj*9d{X]3pɾgc+7``IPfXZ`u0[6[}3MGxeLʂ!aޖ P,Aq~Gpˊ$){jm5؉L-#[NQw{1pJ ieۘ ũ/]AJ?l[oLXծl:=zÒO/=VuzXp`GkZy.9o7{*$Q0Y⴮8H_Je ,QdAVo=m@ 6 Om#KCvk$g4%G<(D^F sjBJ($ 2чVUX@8A*R%SYB|M5xWY+ǒa`އ͕~Z%2F L]2i|ƑoOqD5wF*/> 7I:o v-QRѿwէ eUGZaa#@Sޤ'w-FE;1T',mnjwp Xƾ~vWDq͸`j*t8GӋ{@JwU¸>V)WLgh(U7|@#bU/ ~gJ(?Ô*pƑ޴Xy 28)*SFΘX)W.^%GKBW癇@jUћ({t<_˥Ccm9>3Hmܘ=c^Q>ef qrsZ@,jLy7G/DwHFGZtg,qEu8>I B(H(IFIJŴ/ ,PI(bx:asi;wz(mYJYs7]1VUZ kp-!_5e!Dk=8̈́4k1iW1ǪF"1rwӌ鑳lQWBN6j9Z5Ko ]*Ru :v3]g/O4`uGUS!ѻ:vGnixf]mG |]RV?)=o!_8eWm´ng8Z1k_nކBM\VJ[<|B&aJ.cwo;îoó%~=v}>ޭ?54TkAϻc)p@XC-I J[L蹑;r0'<1|ޝuYB7Rg;aم{6% 6 p\JטkyX3_ߋ//P2y LɮK>/%O6E+SGMUm^L=+M!d9:]>QZÈp.Qp<~t6.Y…m{)-ޝQR0KR"R"He1+GF CK$oR:uFO<.(v>vnb1VDckOwbsnL Dgmc=|3W-m4۸0>HrP8nR N290!b{ ͱ}٬D+J] 'V M@:/,e.v _|-y ~-Hn I؝P5J0y{MG$\@;!0GH"cakjciS}t#lzpl8]<P bB/PfF -m"lEtzrAs@`EwDE 1U]C]FZRj <e É_*$$1`V -; sA)65KyQ <&! ^>19#2oYE,p55Y#KP yeG5Eu\VP@pepy2KpLd C09-0QD$t֪xk䭚$ݱȿ7gu!9čth1,Frvkga8\Q)l3DR渨"uBQ>&BE‡<,tZ+|C|܆-M#Akǯ\`PaN+NNmUMx`Afr֮ݎFmwXio+`ـ\`!kˇ>4n#{̙֥: 6r+ G`A Ԇ!/: }-;KAQX҃Ml^ٌ JǕpvDI!'^Y ?=a9 Vb?DAI$üEt0[չ:|Z4p (JTPZb;ʆH   5zd v=8Aٓ e+i[n'4+"xÎ/OhLz:ԁ|_&`k4ls| euG0,e^c jB)ba#P#TSGS^gSX^xdSyWPXZpa&;L`+'_!;ne,OM0Xy-ڹucPnLRO)C x 2lgQex,4EFrW1j"G}YxݾAΜ- z WFi'4P]ɍ-3"-xJ[l|h#g;=7ZV 3 +2ZU[} -nn _ UQ]]ATva'׈ZpGA`#PPP`H9TcsÿsI-y{ab=Zbrˤ}ݽݥ7 4R>V^bpދ A dg`å~sL "MEf#% :`!_{o\.*cg<t<v EYRC/,h443RAA IHu* =]do믇!X 'pn}WZY%y0pkT#/H:0ZdaҽXXįW*g?]yPOk) 5Tfcb1@$t @A1D ɺ;3#M2_\8kTjb胠CE(tcN2y 7NQvU-gҝ2HU9ع-[,G/$:~t1gN`' JATLHr sO mϳs<Ɵ | VM- b$VaSPߢu010"fnl8 ;PNUGhqr?/xdsCes,;T6)R|SU*94~=Ѯ-pe=hYO>J|x3)F&VoЁ ؞ՒmޚoW뉶R(Cbjwv?T7G8z JxE#ϗ",^:CZg{Jq,τ.`L=` _tu8x5#" 5tV/}L:#!Kl* $nNOlߑj4i&u%@ &Ղ9.$Hjӏ^i-Q\x *ƍQ݅^|^Dwu !0~Tn+g{jaˎD-w3p (H:9VS- sRvs{[[ Vh=8=ȭ'/1'@3ߤjztĔfֳ-c*|ɠ3Lf&CJ~JR~RÖ6$L:U?|ұ!ScubzGWCƨ`k9M)'. VH蚻y^ڇ-p"\Bc+=9x3nxd O55\ HvF  P6XJ3Ѵg\2>QȸWT@\]V ΁{ւb mP OQe+N//tdOҎX!A:9 ȥuh))y~ԭY^.l){Q=L=dmÅS$K7SxӘxUavA#wӽ3 H;n@,@6TL΁`87+vIBi{٩,b`m꿠X%lBtc}fS.$Դ_p~w\=Z%y>dP5r.m[cH7Mxnz?SU1γ5i_VoJ>o=9HJc_R䮹yI.ٟX&lipm GYe.I|EfEKDxur]j(FCY\57%O슠qk{ô㈦rRp|COmu3;t("1dqă==u|\; g> T}z,Q`)\mE<vH)>4'-Ԧkpz(VLoixXceSj,޽pݬTNx+Hfۦ})3o~J+*:؈!vtL՜ia>O{@l4I&g5."k1Wzdժ-MCT) 2 jMI!0g7AQL,LEyN-3ѳP4{*f_nH$ǛY)9+&OiUӾ+C \#Qt 5=7Y>ހn>Qsv*k?IIGe;=$-[iۘ]rDA5Jp [^шPKdC]~̩<#QJ5T ?؀'́xX͖pNRV}HRx4sv)Ǚqn}ℐ%5}>jq|( ;D @3듄Àd;=iv=AN\FyޓR91 ӗD|wIM5pFD{"Id9 CJN*]ZWJ|&+촬D!j*2YԬ.PC'–TXBCEL|& o5HĔ&G䓵0&D+W㐘 9zm J#XzIpұkS!;]\P ׿jK<OSo4 M5V.H~"I gxHeТ 7.'Ρ'q^-eEӐ T&-ꢊ)igh]bMc2uox!^d"`T <ߪ&ev7 DE9ېK}"8HY;'s ڠ7gx.t4~-SkQi -G`gXg^jrUg*Y]h=a Mhp8cêWn@pU %MMwTr! 9gt4,}ap{xǤLz;%r6wjk~ŏ#ސL0Lֈ 0KFZ[Z9А%m̢PS@޿"Ρqg[fe6V aוe3v:m+?tHvc~eO8U>G"Ѣut# p7*Pƶ O :~+ WF{(]g[B[柮4"MO1g8,“ ע;%(R#' :z;6cLBM$Q4RF`YS׻"0EqGopKqrtr4`@y3~@Yg_ '#5˵:6|fފyT ;ڪgpnA--iq]4cpYXSsP";O+17T|=p=ElN;g7pN*#X )moK%\Pu!:lq1VfZk2WQ$lAp0rhc%)Sӊ3bx7ML]//(~ q1aGk@0`ZL u` nc>?,3jޛl>=^lB[m`ԏ$a얻*tdr<EmUăr- ݂Oy;EӿeɤE9:1i_^JK,faqwM/(ϲ ]箋25;JbB;r=Gj)dngT:JwΚRedFٵ+:V XT<@Frўw!OD ۦ5e7,?f V`q/hvw+}4lռc@%3UqǤxM}-$!%BۜIS;~:-=܉'XL jCb, Q &oT[c=9m4Q Pb`K OXcۅ<TiLMWѼ-ưp lO"K*G[ṻ*lqEΊ s *q*rAIoHln۴z8u gX';Xv(>|9>s"ٽh0K9N$U9$yd“z׵C4 <˷`;H}wn2`4_iR7S8sLGNcGr|[v>m[E!G@IJn1tٹC*'%r;ˆYZKd7{8ՍP@dSt㫨YH&q+|4k}~Hk( F 2?;^P_;`Οف0hطR-栺8ߘt/# TB,ɉ}w Yi%H_M'MY~ .E>FȅJi+N5ZM<M Gam!Zng1'HDŎbP#$19%Aٝ9F7GmU@0ƫoa_X s@LfaL8 Z܈~3IVYBc *7mlZΐ*PWt:| g{I:؂N6X Bԏk6oyJHQ0͵?͢ 8ꨔ.8krc8+X!` K~UWX!C7_ΫV{lv%@fîqS?hN+Fj_" l0fp >>DNVPOf-9]W1Ő$.N9P&IЩҺ[-hԛ2Iƹp42W\8z.:mye,pNdmL[/b/eRg=|Ȋ;Ga,;b|F"iPz*G2lZ~5;[[sޅ^:LJGZ1<[N#k0 )|~>ډ0TB%G1f/ daJ)sԍs»Rs'|p5C5JLc~2{}ڪ&C!Q gR$oS tjK}c9o7C^ 1uLHO)G²d/k;Ayvv"*: )QV)DX tHQ~aGQn"PWyաbȝ:K|eVtCM|q.au^pQ/R6N5?#HaPP[uyZ8ySJbDܐ/X'gCǏ›nS^k>Yr*og-D-KVU櫶xcA9kξFlv-D{vsz~w Iy?N?+89h(8;s@MPG-.sԃ}pИ ?Dk9.aҫϞmGeM2R s[XS7fybPu+}ēATo+rb损X꟨/hJ`j3<ɱ9%EA!Jr=OM7)+W{&Ǡ!  G,MiBo Y'%Y8l{{=7NtG9AoyH U7$t @r]p&!f ])ϡN9*zْhBNW9LdX )ƦKth+FßR L_h'rܑau8tC6:o  ^Q#{!prY'ao]pz}w~. n&<ԛ6MUCea۩y |osG^YwST ajTu/z˙5 / J ESL7ZNV\텁ca:sVs5⩋s˜+kH|߷K~_Lĵp1hRgKg݀ߢ!JJVN CYv@H_]T׈>7Cw-4xMt~ |QѤ2"έa/I:8Q]8z-ŁE*KDC.i#[oO’&1r]t`g9κgb1o;ް{ybTІVԮ ƝdCqFz~H{u#C?F7n|6w^"$5 : |j}>m4X`mpui$mP=11_u٬oߍ-cR++ݬv̈=|'(>;{pAC㒪>`I$ a9СǶSߛg,cYbW>-Ĺmۨ0]Rt!v~h)L 8Йt+5'_2%3¨yaXF0Flf#4!1EL' {,~3-HMߓ\djFz JGʩI괹X2f+X.Bl}VDK؟Uuû}45+Kz?}52'+3+8q{В)C798L P ˻$-'Ꚃe lΖ/\쎢k7uhR:s$4aR ٫Q2:;t40'粥*LO61Հ80N5<,K 3@oW'٥[sy3GރCFQ7 qiǔJCV{cH gf!3^JDGSeRFM< ~/H@4vKxdpFbg@0HC7َOzj&x3}OS/׫:Ԗ:@>k NNd/`/܁rPzOr*wbŁu-'=.CH~7nƬ3lZ$~"E9OWc^Nʳ,RdUY jH?ӎJBpLAA؀ jd+m=l@8h&-5tbp)P?xٟ%B aL }xZ㣐hᬝZlxl*;0r)-<(I@Ui%Msw|x'ԊhuŬ;q#g@S85ۇH״LVU*b2FAo3̈k.HrEyBXq$̾ț2E#kj% ^*H?rU[rJ}ABY WS$?9%|tQ7ЌIP AvD;"Dj$M@HrN?)x&!R9lq~$P1JqQv-S8t?E%}F9yaʹoϓ`SLq)smau/˼zKvNҲog1[~YZ9 W.G. zʗ(PL\ynȁ Cb(`@-nHs!)5D2E"˔o#U'b=H)2c6EIDZMC:A]>hภqRfc>/`gǀaf{mr 0rfgvsziTTBGo?\Lf{t%[(4vޏf]mfŋ{f ̕9D- 3[br9($SHH\ԖF.@`/ +f{^hvŚX4æ=Ckw|M^t=B<ϣSL#/[mmQ,ϗB&G|uwb!+o} Q $ `ƏG_$M ԛmp䥖ȍ,so@HoJ, rs'l41f V0cnr>/Qc|S,QZ۶54F;/6i@No @ZQz,ic5K6i-LGnYG,{4:j5R>a>˭1cΒJI$ZGCvk[nSp! -5&Jܣ,Z%iR:{Vg|i7Tt(mI$\NR8VPUJ)ckbp`f)v}k u8_q =0ҝR 1׿1^1r9rtSmȅǷ#䐘j<)1o~.+sU fR88Ynf.Õ%67>h㪯HgF޷xk*b}>~dW3rh4d!4Et*\n=RKsUE'\"'/:PqY׏ ]6>ĤYd5j,uȲ4gCmwPB`ZV*~_*#-i#Xx}jt)Ugh~^C5@M`VA$ oqZTqlc8foSBl52\lzlӎI`O5w: zQ,kaF-.=l-{nqıhPJGG'!;aFvUp sF x*uᰆ c 2~`Z=\a]x^ƶ7ymX)/]8gTU%hnx߽RXhoI=%\SA[ *_b6Vo~8:Ҡ? fq}Y]~ܾ:D (ulDG7,_H:. {vÍZ<kgxQFHXu%4*M)Ոqխ´;έm=z߆ű݉ 7=g>dm2x'ޱ2BjLbNqeK>ܯ7BITEEc xoҤs@|}̎j?MעF3ȀyVdgsNLl LIbᴬ,dbHn'1i֎/oE/vZ# /X7BO|Bo0 TkkrTUs /Fv5R_WpN&F$m<Õ AuO572rU{|Xic$yb=pƯ$m wиԛ&+n);4y ɕ}KE"W,5+S u?t0ʑS"Wkb}A^OsVmw ImZ"sFË ~-m= U)K/آ-'HYEE,6Ǜ.;Uc(.]K6%RJ6]~Ͷpr:K ǚr`^0iBOEgdqkxZ^XrΎ /FM:0lz5TDЊ/%BϢ@J7lu"1 Ԭk|8"cȠ9MH- _ 0~8̪7n>Aw3b}QޠgߴVWN n-bO;0 **MC8䄬E"R}L~ `O ~ƆMDžބ_~p35t昖唿Fj=4 2`8M*\2{\iv\:fgi2+Yy Ħ(*ba<0p"m68l$,HIwsj_CeM>82 4ߋz$/}AYkTT &@ _DJh1+U>)_m sũKc8R\Sm_O9J#ukc|cXS#`,m%5["w~. .#퇟ˎbs5簉M?@h#eѐh-Ăh.'M 'RP-`?>[a$e!~`, EnBk K}pXK؅fB Ƃsߏw5;mnl#'G#ѻ >.ϱ>||yqBGA*/6:z3qOw⭔p橓S%*8]h$UPXuG-Y\!y e*xtr~ Y83n"8,8235$lT ];;# x{- xP$,~p!U rsU7xs_*:+s5?\ɡq(V5rƘv{]44M`TDIJD[bҿ^֡5FKc<k KNB~i ޽ړInoyR'@^Ua 'x" W "LN 8IǺ O547b-%JJ AMXX6 t pk*E RAʸ,$鎂C =Ẃ!xja8""/4+8 c?D;'7Sb#NVدIp7m9rAGr 'y3Cr2#B{]h"4eo*"ԏ$^~0Yf]hݶWF;\Rf gmHƉskiVH/*<7',Fl_6f3ͼ Q[a$klfDV1 (P zoս|a@iKQP$ [YD[%zyL Z;fIqPK] IA!gRbB}?;5CVz7w=ȥ^)R`TU6:*K׹W<~s?ðj}]í?ϫ[[uc:? >OîxdOf7z;q[d/~oD'կzU'׏uY=;hgt}e<ҧJHp)N 7V/ǩhsO6ooWM_i#,*x=0?ߓhɘTիQRmgHvXbUs政 zSp4hH׎SC (mXF7$}2GnLn,'SՌuӑ$;AB.tg( Mt;Cn,YqV$B26kvׂj#00yaqHLX/̄rDn腓0*jrȉzlI;ViP6&g,\%4mHF9q_VRxd=h:fktCbۡ% i99!T*^M!vdGQ{?;FJi'g K)Č)SգOCh½>Hw(a1<*!J[Zj! 4Vv|J+rG2Qm]PCGgXN)]@ASSW#7N*4(y) iu0dA)Pmfz\:AmApoт&F (rW5n< ްI哈8'((dz]q*w4,>/C͓ӿ>/bWW<@0gI8Ab[`/pCŘV0$jqݻn)pT6˴t-3*%_L{oJ|~`@#i ==!w;: 1&N}ۥ̻#ZK7#frS2's'xoUVEgW ?Y@Ƚ <޼P]o{iMH?:Kz@2U_IC4֌oeic>+[C %3R:5br\Sq,#tTn"]MӕA >} 0LN\ySe'>}Q@|;R LLeU)m;k)@JB^ 隅r{cSZ55=!|^۩Y4g`5[ X [e*S6oc։DTou&5z3T+N%GP;F 8K 6|ƘSO]kqGM_mu%c#U݌\31 ۩Ƨpf"ױО/ :0cфB[e̦^:bp c==a\ёx+F:N\PgZNێ7\"1qs4zS %e<VZp޿ r- `e*jNzo_Y#p+r͉-=)LZ?@ˮlu(3&X{}EzD[mWO˨Y$L:P5_g )B+?]4]-M1yëU?*Ҽy~(+ fdK~.LߐvQka++x?_k fOT^0}<# Uo]3M} ,'d*w)rp9q2he9|\.闥{zd͖˼o\m<;po 듳0bȡ3羲|o?S 2TCaYfCm+Zt} cWVUV-FxuujMWk#YE G?٠ A-=RS6|"A)Ń{.A9noa@Ќ^E0G":\9Hg>wWr^!GҔ !,[w{?lb7b^oa8*ygmY`uYggx\gpݡi8;KsxL&/p'B7}!$X }M{ڔ^8գfvBBhi|Gk{2jiw< .~qݪp omhəW6MKVbEֈANwLlg5FRҋ/T>P<*6]FVM"0쒚#(਴ .܏:f69AiС:=eA-QZ'l˵tT$Rlvꋤ᪕w*2J%'R<'Wf ^P:H|<}uró|L=Za(<#`Tsr2˷=JV}| 絘U 8e!OukNXѻ$wNdd;'_ [%_l}G%@"6/`Pˏ3{_V1MG90hO0o@2C4N -kGV{?=XP!Ѻ?C-Q-(B-ȝuҽ>[X0]of@/V6Qo x7/A|,ȍP.E/VUɶOcZy{c='YT~]Ӄ0zܿBbmEv~L-v8) ¾8Pӓ.AShG i'+Lz̈,P8vV# OcEMXkGGD)Kpփ|ݘMu~ʗT) 6`م0m~)B׵oP`RI6Rg Cljp~H;Q{"e)6#Oչo:&.ZqͷU8{K&HL qZ,ef?z%MZ1R/z3~Iap+A~p*el59}{ ّQo. $̔B+SPY/_iZ\kw&JNRo6T>ԉ' bpoW[G)ZxLE#W} qGH_QTXy|ԅC JnF8ڲq'h*Gu6HMaēSlD5 h ev 4X |ϸJ_܃.: wBaAe -Q_v0*"1|0'iB,]{w*WPˁ96(p-y4_4Ԕ?<N<pЃy܄墶8;XU`W|YIdw%3GFI124fsNEc= xLU9C 4r΢P脝(\VsDҊ3n(=yܒOF pHNM_SŖ5h/bGt+3Y^c=+bƙغwÈ4⺶"[cHX [2O)(L.Xy8`\v{ڻA]5SkFE4e@/r"LD: n0`%y c<>x< u kI=Es\)ǹ@Cw/`٤%"YC- ! 3;*eúKw?ٸG HV7Fz;yڭ٪->.G)9:.{JBBviHT^^Zw~P6Pyʡ5ڊ#yֱ5x1.̀YquO\_#* Z jN&FՕmqQ%~O1<}vT͓/K2ƙ?H &@;9=b}u]5.74˷okeǝ.N>Qe!Udwx3u^4pKG6ZaIJANSڍd2( [)eRF߳,Ag0)pO3?Ȱ^Q1 bGW,WimK~ďaa?օQ`NtmdR +Q< IB&;o85_"[şL5$g>ouiOU̽JyjOAQ;_=;&0[8~Y#"ЖTW,fhvulrxQӵ?7 ]D g9 ?a+Ö-i]FF/~%X1&g;Lf[M'-`vL'O^i?O$#Sʛ;sr3abK.:lyDS͓5 ">v%ܓ?M9jAHd ;/Zܗzۏ'Dq`o+%1Bvz-2QoR2z2Op[;je-ȫ;>VP JZW8'٘? CdBZ6/cv2X bc 0 36J.$?JM&D:kr[Wxpn$e`5NNH8:mEW#uAWwCg[pv #^y. c K݃;*AfG+Ti[dh ԃP5Z{ gqEP^rERI s:sOnT$[_:ZJQ\0O٠ʿ:DG#ǫ(\<D?ćǩכk#yϞ$Te`(80PwEK)`^ZEQ6`4{unyS:x5aBTgHmvG-Z]kFŎh}Y+mk["|I1T0yq$vjv@(N7uYmddNwWi5ؤl& ~<`yaZS@.~hZָ7h JLm FS7%$[>"JIHEG\j}'!dД~>T8+_Li?.\FNB~_7Ae`]`? ۯZ$[㖛auG8ٻgj8 ˓{ʂYyT@M# BSW562 gv2ZVމ<\!G.u B[pӹrq9A 19{笪J0E;F TQ-M@ӏq?n1۱KѪq>QE(LjlDc:pY uC+WϲaQQ q tXE&~jt?ZZ캒?@L׏CA |dUb=7S<ҐR.u8!he# pA+X.K CNfH% hA `zps-Y;ˣh|#7=+<:[O܄6.(+̱U/6i?HJZz5Gm > >ѕ}Ѽ"of&(J78unͭPv2hZc&_pqrn$1iวpg<ҙ$}ҠxMK" BpsTQ'c8P;8ؤƨwU sUPR`$& ?اo{il jH01gr*yUPU#<Ά2n:Gkv GD6G@w?U4^-h&RJ=k@OoLA,W.SEbme/Z>1?5U{mDRFr89>:kKk0t%jMy'{"Dݕ ֤d?v|McS=/dvbƘTA׫[jHFh+8eD YYAZ8}gkv8!w g8&44Ƚ, ̀=Y7['dܷOUQ-Y!^GEIcsF1cJi/Wl\ugܲ n'Gw5l3dh؜`:# 9Gw%*r ~´vEE;!u{xXI|*mDMm.im5ɏsdEEӄOڴ\IeLy*h{J5|%2JeMR ʚA)5H ´JeOTbygȁ S*}zkSwU6#Q ϖsUFQMzV2 a.Ȝy7FzWAŽhG0'5O`8qMd%65]sW?(;Ď_&mM?}o좑:"t˝k?ܳ(9mlho6qJbm_&nQq1 ƗN"A2y)[Å!5VIp7y,:(QTHJ23/QmX hao8iq0E"#F$sËePκ>*6J'iI'- 9A]~r?a):OO݄גC;ނW)p}NF0$EydhbÓhitDU#ob{jKh±XnT7QO7Nx'gk0-תFMk^1r/˵x< 3-3@(bFZx0_QA뛆;bl娬?Rt;UXG׳·_/֘@ȝnc{r5H7lkS5ƆF}H7Y؊T KptqK4%b Fq^\FM!nYKCT\%U]C7fد}O;}; 6Fa ^[\b1wh=MgRZ4mtހi ur^jG-Q;?ϝiX1meUYʏ/f7Z1!)/v0*pI/sBs0D y0VB$`A D]=w_T{#d0Pꔆ/?vn<DXfLfҮÍ&;]r nh_ ym.\JXBCj;_UFj.0hm=CI[v4_1j] #Řݱ4sf(ȁ}D¥w MyKζ2]va7K k#THg0 m>sP]@8lb aLEKu-[L@n쉧.?ݗM,&2 j&$@_R6Ў]j`L",,*>ϕI:R!β;6b1cb(7 dS"kjN#]GwVHCM1.fKb7 g|j0u_ tŃK7vt,|C@M_7;6EN] _7FD|PQ}idt.{t3`㠐ZNe9P%=%q|S}:m[Up|f͛F)=a0T@zw=y!$|tcqpyB"{Wt5>P2le-] ~\K≨ lq],7y%ByNU7Fki 4`ώפKqGcsļ`CȭE&y/E ?jGm% 1Ƀ#_JZ,=Z R؀*x/#3B^0; Yf#5[{v0VqE$H?t F6V\-׶Nxg q0v_ZN{tFCSdP"HZ4B{T5ɌWFG # (v#D}ʼ1[_߰K1ILk6 %;7JoXS &g۳IhҩEБш:Vp{=UN:`e,遃$E+P5񆝶;B gdiCSDż?r}inhtHI~nH^Pm ɤrrvVy?;kĸ%\ JSG}${e? ƾ/A=: [@"(8=AˮR"eqt8b4h%!>h|/\?2KvTэ"ڠ{;DP W*7m}aED]> A~5Nn/=}[oH0 dTGϮoi됽ƒbInX$=)}aZ2kD-IƝBix|k*iP MAt&yݫ63:qή $_s6UZe5.JC:ӓ%"U4U)Zl&Vb(F- Qs'w6" cu5z#gXݷ׼K"$@ΌϭMݽ_rجa1n։oUd4ҬqQ6\m<+ѰgwY]eE"cE fgv78?)3!68ڕ-r]&9,RBXڙbC$(n~x#⨱E[S>GPqOM r&}-PJqcx ǿi=Fd=)rg6ַ5\#cdڗ]~ &rJv6'sI^K;ǘ;#]BhʆtyciGp0!f'H\av|^P-&v  9=7ù:֟\H y2襹 ҅Fe='!סy_KMм5fX$rz.i`P2(\l |O\EKBt%5zoy g ׉<Hz$(Tu+#-ݽm[9MWչGͽ ]2xE{>vo֬.`+yF> Cɵ@WXRxNc m$@9Z,w3"kiznV $؛ i,VI԰f$.Gc{2.ܜG*l^[VaeGk䫕j r*컆2Dgj(_s֋g`>U-:&A6m=f9X+Z%"?]MCNOHkND lN=\2cr^4d{Y0 QLY5Uk`XnנQ3P8!3Sd; W%LĪb|`lx͒Ճ:Z@LCgB`5pqN۶(CYT*{'bq=I_xBulҩ|ڪe yؾu`+l@+hI+i#sn b WwhCkp6k[ZN:p 1sohf,*_&AG4%5^ Y?69@· b/Ŗdhy-aI"*S};AɕN A, \3xݎ3iXcWr7zyT[WãPNvFhfm*BPfylz/p04-AKcXt2svMc2g Kc{桐ِ씘Eb+LpA>*05zXb܀W:ސD=؅+7#qKj4@К79Tp8yj+e ALp *_xhB>5͹0| -۔ٝtbB5"߇%OLѶdA;2ng5Z`. 7Q{SIN-h[-W dIq@GY(ӯS ֗g5/+fOLmO l|? i V)Tɠ@9CU♩^F_t Z!ej  ?Th^;,(Py}{w#3 AOMrÏlលq #Wk2HF QtٶϧouKDOO3,__J : *Ɇu㨯 0|R2'lJ?/dx&6'Mu`c=_QULrzRv o_ ߗvEM xս8j5},>jP?ʲnVӇJ Gzʢ`6q.Ɓ9!sGw"eSj r^|RYC_wԢϰڡ.}j^laݑ@aCn<}reBA\De\,oV {-hxv5F)g(Xn:t\}Iflȵ̄  nKv":|zj8eyIHGwxiicwQ"}9b7 4ZZ"nŋ[nU<#Xv҆Iwc|[ ^A #ia:aZEACIչH4hݝ7)%j, 6r ?ǝA>ϒЛ% t w 90ܭiOXJb_ Ce2};b87k<~^C|E% G8)fBZ~oB۪zCW#7Sg1HWyrpz5x$)U:n^[UYȊD\DfUc> 3<=nV"*qX6V"|Ztl5՟@!Ѷ|{Zƒ|!c A !9MU#E2Z XG(p?};RI,|8}{ҖR lY@ e<&c[_L諦ӶhvƁ$Ż|B)E!|Q4.z8QKzj zb^Sy%ߨԍ}yH! 8}d uJn9-OPO*_պ!l,\ ]qC7}h7:Iw!7;\MBWt^"geKtqr;IC* ]h޿rvQ=Hَ7-֐yI.7ۼ@ WH%;1μX$f !tBr(x5J%AT0Gn ].sH1ǎC~(#k݆vK6ބ@ֵ NRpm<:8j ԭͨtx/+5{ePta8B>/C 0\& t@FTs1*[n 8Lͼ[zB7B&s48[  cJ( V6A5lb4W#gIV@U+"DyFYT92UykHa`5)t4q_a&xLWr-a$ʴ(r*d9 5;ҶG+a!ߡJNI-^.EҎ!a)3wmx2dq@s,7a#ȉz" Q|88B_:' Αg@j3WjH*Kf3^̭$ {x- En7isT9[TfGTԁ:%gv 琸ar/H+G? 0qUWXqalIbꊚIajE8cw.L^5պIo(>d5NNƜuesG *ՙ{G .lG4/vSvz,W T [b,[F!c^cQP<_ME>43ro>_ݎe+1~b! cqUt{~+201@νu<ιJTSmZ]qYL &{^ ^$XqE_n.< f H*<>_2-Q ^ep7lu;lf Q"?#aK~'oria0#Y8c(vEؖ YoJ'! ~JeeϪURBH9,>ߣnKKr+V+1ծy]DW "p@-zE%+{P6= ,<X_ D']x@[T:rg-kÈOwu3Fbeg <틄S"U$)I_IK9 TM>̭sZ=>Wե(V7T LȨ!5 @jc{ʞaU/}\lK٬Lf)Kzaݑo8cԈ$IPAـg xpݪX\|xIޱyw;"OmW4?4SnqB^5\؏+0N^Ctgf*TAZMi-ֶ)G -+挬m/;.xK;gזhmgnvRmƺA+EN9 Q̷@O*M!{ n[sqBQ;k؏Tj#B<Nn*׀puKgQ &l,bKIEM!$Bxݴqdȃz9ꯒElN Ua:I F@8u< o/-Ëwt!,y-;"K0zN = YFjV | 8}D ڒT[_m!rU\d<hiGwWcDYzd @ :c_`TǁxC J[`6Ba@Y_m0~ 7B"pUO5e{|YԱ~>D'Bj26 KI[9+2إ׈LtF22&zSem/ndl&P첑9lr4{"HLX${(M1/-KO26;kzK*vD5i{ Kuct"syW7"@ 1[D{txu7%ۢg>2-"]YY3}j&WZfaΆOM3FcyH`R Q*`gb19Y+5Uε#v~AZ<`+fldœBf+7DC4hmW0jL2r"O\X,)T^@r~.p`3֩Ɵ{qA~k'iG[f(Iݦ1?đ(f\iQ*8.uJ8 [! 5PVj M(C^Xmv=TaDc>^wfl5#/O ų,Tj_4 I!|P`u1ACskot|:aYzQ=!U3\B* ?zE@|I *qڕW d/ VѴ8"oYT0M/)4 NGO%T:lk Jb|w)SdjE`O4̰1i97 ~5Q ΂T62)D/ErS*g^-} 8XzDG:IS罣m^gΝ|ůxT$ 9;mN(I0`E7xѧ )F0-Ysn4xp Yc W#d1MmJ*1?5Yܱ7$O8P8w"qojȪIN)]0=| [2S$M3 BAťn ]#D{[^(e .9f[өW:ZCpvzff;͜KϞW ZDLK7ԔLBܩ6&Zq)D*0c`}C$B+#bf+=}:sA/PE" dA̜+wO70iYL՚RA*@p_q_1*?v8MwT? R11BeSxr OD&(!5Rz,yESf-)`b^VLӛmg?Nb981MYz,tLsF:a}XOGaq؈`5SA Q:b=v]26O.{qJdG'亄cܶ hobT|ḎG.;_gˍ삪l˙7"ߖuΫ!fhѴZuT* T}DbxYq=afDa]xڧ!HIG Bi#}`7;,4~""zq һb.a@WNv\y"ųI ekvk,k2"αz21OI w@\TNp"Y^l31*S~uK涂$wdcS.sơCB^~ő1:-7&V q"ˉb3bG$-]PZږUȦ—ɽ$Z[J ˸{#_dY c+}dr%2N}Xb`zӢA# hlf_6Xgjg#Po#&հ['hZ{'G_x2fz5:aE#yJWHh g˚>#䦠C9hJE?%m%&UG5:2<2)8 =NԎ( pGOyq*jWk:~@V>obH8?bn`gM4bm6~[ yLg*#g+U$pQ6doE*S$M<+TEܓXpgz?uѽ xk(y6/B n['^ڠ5πb-am tYrwc~ 2QP^%/h]:dJJ<@6ۡ9z+̃ _\ʓWrLdۼ<8"O[W(/5)ǮA 8(62-Vf߼D@N@7})*Ȼ2B/OQ̓oX\a +"Ţ ]oMtp ڔ{i)Q>,J 5)TbcĒZ%v Z pR|h'lDNɮr~ќ/t,#C'3_"Dc/Yl 0`?]|A_EnxeP'P,(݉)jڑ`,C@llzTo<#dr %u|e`5 KɣY ?)Bgfuc_{ъJSՊ\_=ʗtP(U&U܇%V]9a La6,M3Ǻ+_σVx}S[R_ߵ)?w4웓{ W1%4;>++$pl&KrV ys-(L(_st %+lt~ؿ>DhT_r*9bLZz0(w! uDG̚'+5;p`` m|@;D@by4A,Z|y3jx?/|n02DtߞwA,9EgȖ2`%E 'ڰ+ZqSl;XV_IDCœKtx74l8-I61}Y*vwL{/ws`,5*{\fӎӣlV3\1fwFJkyg\Da @"fOyD\}_KJx@4ghĸm'(G-2mNQ7_oLKԈni^,! Ai*>֣|Sj%7L1g_J)zGSeP<''P O#Uqξe&p"ʙLlZ}Q`l=ecG-irVǘ3|YȊrFt)QKZMqCt;`bHV5UezB0;ԛ{U'tpaۈwm݋X'_DZ#qŢO#bpT~V.*8Mc:WtE Y;%Dn>Ki +|HR1D)j@1g#'YDsZHKn Rkc#?7 dM-C>Y],Yg>F+mo':tU$0 z/sb"2zw eiáĤ#7Vgp>!Q&A&5h\Oj㿁6.M_?wl0|F($|Q|]wJ(zXjfH^r1Ujfv(J "1˅#K)Bbw9!|3N*l ĉJ@BAM#ӻ ֍NP"Qu}b:l[55LjQ !^FWrl4Hi+Xe/T+[ZH'qQk@ gG3!ٳ=Ȃix JpĻON_19`NM/i6dQNJwG)e0;jf)kfΊ\&m$+ݯ%zP:eIҞ0{:ǩ9= F"ڜ/9Q0L~ŝ樀ubu"(Roq>0=5.,ݑto\vwJ TQ$wJGl)xeAiQ aX!!Y X%Bֽ7 -yr \~(1'Q@_JQ`w=r|䁖nn.3MpflpppPV^C<QGǺ3',TUֺn=ղ]]I&C. O-_x*r%V*Ik}HbEldw 3Al!uF=X4po/St<]2\rYvÙއJۖ5*_m̑oSAfI\#@_c֤:1Bp>.syp<-tْVkˆ<$Kj,lV(t˫bk't>ϸ T>@dj>WݟV< !:^|\>rIfxmɕp+f+ ʯ>\!QF 35mb3/D8@g&0[VD&^;K/ I]gB+1N_sE6#ܪ%*ss蛪Sr!+ϴF~dEkυ*[Uaҙ[ioUKv֘OϢ} q:חX0q_QY _ l;'^ DŽj> _#+wDQZ%mJ߮3,)݌ ^?|mdPТڝgRn Siw0}g1.Tq56@tre?_]H%f]0J M8^n.^q\_qI<ܓ׶7c׸^X2#Ζ[8H䋅`ML qȿ[LAixL00:Z@j!Ԧ6S' 2 jg“qAytQlzjvszU$- ?Vi/}v߱ŕs{E:G6̙)S\ԏĂdLY^wg[V93LįfuzptG `Ǥq,zGJu H`V-ߟd;GC( .6#'LG{\_Np4 M`VZG9jS=orA F/ӓ|XrO0b^7ƅ bT[X+_Hqf~";rf$p~5annobIO(UU16DeC![(+@ndUfo3kR݆#F>kMݍ,-̀.Ć|yʼ'bu$4@8ź!lf*g^SU 25/BGi[ SjsgaJW\` ʼnv9'u؛5)Zˣkֶ΀Hs"䔄`CfB5+6X2!G^Nl %_j9 H5ڜ)pA7X@W"%fCw1'>/@uB(X)0Rpů.Qlcyr1 WkVvr $- 'Q/-W@dù'8S~RԗA1A^?h(ʕ$<) S8A67CUa`saX1Ā]dE"OX?u KgRU0MA-ƃwD/RɊ2<alzĠ5R:b:qԬnJΒH58p U@[ @73eRBvcr]@!GeX̕!~#AD]0 H~Q\{: #q\8fV3 OD7 68' H@ M8Ycc` r_ p,؋XQe1"/NZcQ&R4#+5+[T遇W=}EB2D0czz~HoGt!͠%"u^t:F`%MRLꤧ4*;U|kz2fSG9_4aI^%b)0+E`"0h 7XoPhuiW-'N:5Ocgf)fͣ 8fȷn I-iTt3[7";/PdgYΜ2cXI y$:QpxC=Xh"'̺jM>J7y!UK)[ʖAEД|(U=eE2fKGC~)fb>w±?I;g>䷆_oB}Fm zM dmߞEقlLϸ)j@[g$֟`P:jSC,!6㐿qjsF|\4ZUaj ;᝖NL7و+{ G$Vm0 yJg`,խj v"߅s]%6xVS6:ѝs1Fɞ5n6bHП{싗6)- 4\9YH~5^4K7Znw;ЮBVZ Wjx "PKrS|'q=?6I̓\Z+_ M4œ,6RlNtAs53g ;PZ(Rf\Yfm%,}b)$f?z晏<.Q%B_D1 l59>dMr; yOV)nUWm+A`B(M Qm#3ȍ(Lf2ݩ̞/hRk kg~`b C|l 4s Fͣ _I;tSP\7w%Samal!:3N0[q1Jꔅ?`E($vRw٠ЀE V 8oYr#NXiq,OSaim=ӇmN4\l҂=9EDHLBRԴvH &lB%5%G: h >rXO~¿:>}Jh|s;v0$ UO &nC[oݱpF7׻\Ow)>fXwﰈ7G-%)u$$KO~%e1"\.x<6c2 dLUyEpOwXxYÖZx.M<uTFkX(^.Ŧ\sŏЕ7(F-_n0(FϦb3U֒Xbyezi@}u+Y4aQŤTm57O$It«HoR<*2@wmVbhKP8Osc*C]{]94(;n0#؇QJ1ӱ5^0FuKȘנ'A_|WukQuy&ls&t1jO>~ GZ%8 [2b{:tvaBN{ɂԛM\.ԟmN|بɻN+hA>|3ii nJyw%'yX<_*DVPnn(0gݞzF;[ˋħoO>9rF-cFj dFr'OzJ«d=G_ihA~'aORϺgIN@qT4DQ7=\wl%Qu6 zUs65ORqˠ9ZhrHwb~ Vɒ~"f*F±<} FFt̶Mg7TG,̾5EQmįW3gp_]lrL0џ@/#3"[Oʛ穯¨ike8hנMդI;6[i >6`'Ǫ$HmZ~z(5H-X8H&_툯 +{A  s]d1@PY]wL1,(9 ׽^e"4w䅱M&K6R)@T$ Ks3ѧCU48oeFbn @D޳]3"jx\2< R%Go=85ϧIrX1s[4YiW[ OzK,/Z%y\^N) gCHjx,jKDa-bc&=h>ʫL7Ы'AঐjҬf$38;g!jk+ ATJghjDž3q-8i @`0⧘2u.Nc fy qIh9 A.y'Ef(*Rɐ\R!34G c"W|,c-m,1ы魐>cv%y״z_>_y:’ᆳJCgFk ?4$Kk.=;?  k[g{CLvr$*lQx-`]5] {>@sm&Oߦ -4ޣBs),  tY=/В$䙚ᒌ*w0BſMA琏[ J.`Ȃg]_rL1MPyi6=Ta`YF- }X 6ƶuNsInM<g鲠XDnՋXAP5LP=}˜݊\[o,&f,iN9O [gAszeuI-ⴕ;ukVtWAZľYh{ӄv ;eXOR7<;jWd亙.M[Wr,KodIjJ=R}僻94%Qůp̖r >=LFc!/܂F.vT@q8O )Vg FܾwUH2冠_}|3#ߐo[+`kd?#8{E`n~o:;\K*u_MhS|1C-R ;܊{̏nsx+_jʝ4X"u$(GRe#5~(FzFÉi>*ZT g!j{(0`sD@%h`/N;1QպݮiY'y¬ MHqZrv1p?nDW u?7pgc9۷.|WbumCio[R8;gn Yoծߨs~կ]'լg?mp{>x|=ݜf`]OۯvR.5fCmQԘ)5TH".q2R ) ]Ißy۩ 8ʳvb|hҢVÁb-@HXƌ?`}` @̅ceKo>l>2 Yh%v&o+4;~@z+3b>16݃.Z7ۛKw_)0 1?vLg;p#S l;G9H06S[Iш<'5n1pq$z B(~9lEQ'².ly\5;ƿf: f,"^+ ;ƺX$2uN77Z/Q*Ce AJ>^O7b`߯W%mL0/v;e2D|U^5tpA$^(_|c1GmNg ־:\&`#ٙ%U44P|_r"Dl{UK3GU'a~5;4:f*A8vCIdɗPI{XKGfwX-:lU3mgov;GurO<_?kFݵ{aPx:.8F0C?mi~7,*(>s7va~Db.?dV8A0~ &7o ru$|!"> m)f<*il=ܗtKX۠ 6(M3GQ׽%+$0߿͘b-U;M,Kkṽiǎ^c); )އ7@!!Rii~p4ܛ%,G>~͎!.{ .)Q6"'=UMʨ·U~;1;TCA瀢&Xy*4(%zgiV$;r`%@<>H By$ݱl-2 1 _]ZHC0C$AL4kR>M!~2bQcOėڿ\EX N EK;ڧ\K > ٯgO!% igbeXڕ-H%5m+Tdړ ! 8$%>,u7\,dw":,j2{,Gg((?+? =0 B4C&(og0pN=t+M*BXZw @”}iO}R+'kSVj'V6^RBE VLGYr&hط.aYr ]%7>wob[x=Ej`uۨ,Q8v:`\^g\| yh3g$V#;(#cȍ~c3qdڿ+:s>sdE{Q>Q%u * %FR-D._R6r]Dcy1z!iDڂ*qfR,ՊO˲}YL^d~V5 \:Q* Yxvj㱧5*2GS*-)#rBN$G=G"u%MQ3`VDW&ZX|Vg3 m$$ē߉*C8=9"Q 2-(ä>щ/Ss]E1jޟC{c)hTE TA·/n5_˭&?-.?lpɓBnllƨsH@PO n&)CǗ/j֭S PbuPN,\rgh+6G`eӤ$ީgK:ˆt'ј~)kY ?rp"9,S[?j7n-a.m)NzV tWW#h8yg3# ŖHrNT2\\*ഏ`taɻ 5H<^;k[u?Ĭ:*C#e(N*gl*M=PN!*/dԃ=ffj{vJ4 EYAHBlT O/JHPCLя~]j{-kj%aOu(l$t W:X UO8}c:Q wS8SE.i;2hxy\x[!W/Ow]p+#CA"6c#z@9{`Jp>(/0W  |8+p!QXNpY׉ [d뽁?Pi|(C앾 ~ZϒbRL/)˚~RiiRUW\ p}Hw;뽢Z˳ijMnp1=(gc$O]qY"kf)dwD5mX3pӫ ܥ8W+E)-8}2kLOh)D͆뗽4]!g}/?Uq-{ J[22aLE 嗭o+Kf;,eӨ VBfZKyȖ'~Sew=$"S/ 6ץL~?(jH%{aF ŋ](΄ՑzFSavbm/}yUNU`۟v8Qj\T=QlᚾHHAAJW1J_Gj»C1t&}JRI!Y0v(Cdg&h#ok.쨄mbKp[5{[v:M[#`e[&]yfYwY3zSlJ%BCl7 םP_#G%`6"f"a-K$>Sw_vfݳ]sn>$M7qsᗗA8J ,Ӟȯ v8|kR=tngLI}h LQD[#$(23L+=zRΦr}o( ȏt8s;Eot ,=(؊{#S&ZdlrP(y h0[*REIfX aE3і ^2 cgZTXzFc}Ig?Bj8l0;%p-6}o R,>2W:dM1bvZat@F`<">O'01!.yZD8J&p fZ@,I TE0{l9s_1lf*dfQJGsŒ[9f ^7 Q;Z3L<Μ&lY޾+0$](3ׇyJ'}n6ϧĭ2Y_h+l5kkUnn[!ڇ)?x?3niB4'OCz~oc,9/PsӕɰRq4V"bsF7?S-*b +[Tk2bNiL[6ګ%{-d`8MeGVM U(v|?+{I.0BO c1lܷA&5LzD{m+! *Nws%v:GAEJs :j*@ C4 hXnƿadh_7j"Q5W;+ JX`օl!Akl%-lHLȼF0K9@\:D sG2Zų=T1bSϘ ˩>(!BOMyp7,wGm9Zash`|C(m5z8\t[TO#?䖁hz$W塡S)+Ϋ?p)~1m!'W%~֏0|1W˭%ύ#45'k>T):.Z]+yeۈX|v |Dke1"dft-1kkUrś  ƈZ6Q|iX>uGtCK; v8Q)[NKz#Ja!;zù䩀L#H)Pz& * j?33Åmd0o~<qնc a`56֦a37L͑R@ފ[AG<6.;J^œ(,Gogfnq$hڦ ^{9)Xj/+$Z"3\> +B$vKd-$uK?V;7_QCFnkóڽՉe Q2losuvE,fLٗ$elRLMҖ@gǦhT_l#r{'XDܨz͆gZC=/$=*<ʜWz;k7B o57'B!ŤP* :CK%T BPʞ終fjG;xЈQńJ ZKPϽcXuIOJ3a2&h[DmٳKJ͐xeSXFr#M]Y*\GhL gw_p)UzZ@mypUz.QN^q1(.e +)wepesGp;Pd($̄ pG:6/0+sT"c֞2DF|\S{:KZprH[^RUZ. -^1czU̠6Zh]e"ʐ0ۦ3 1"6 r%~!qIfM-]vEΘ8Sqsռ5q,'Q I),[]siEc+ұ]aщ*~+" bHv^4}եGM%iFɛ޸ so$vf[􌶨WV+jbbMUi x9"Vl< IM$M&Mʒv%-D'x0˲H#M-3)a3Pw'7` o=8p@ȗh@L=-ZMդss7R;YN"))%]dIBʎo]f;`x]Dh͹QK[u}e:Qy`YJ Ag%L2}1Y[,0l?ȴȿ6ܔDbc##DDrќ. {֔eD&WBc "D;Ґ+VQomq/ w67']Jmu|oRx|+"ˬ:J1 Dʁ0$؜{ ]&ןե;D[ `C YS&`_vus :M|IXl*Q&zN<-Bk/WB28'm.oY,Oes-ηs#`~QtK$5V;f3 .2Ğy1&{` n-wLeN(~H՘}xL{N("@+V4NB/W6Tc{z`=K׏O4՛icnV Boolite-1.82/Resources/oolite-icon.icns000066400000000000000000021412751256642440500200340ustar00rootroot00000000000000icns½ic08ʰ jP ftypjp2 jp2 jp2hihdrcolrappl mntrRGB XYZ   acspAPPLappl-appl descodscmxlcprt8wtptrXYZ0gXYZDbXYZXrTRClchad|,bTRClgTRCldescGeneric RGB ProfileGeneric RGB Profilemluc skSK(xhrHR(caES$ptBR&ukUA*frFU(Vaeobecn RGB profilGeneri ki RGB profilPerfil RGB genricPerfil RGB Genrico030;L=89 ?@>D09; RGBProfil gnrique RVBu( RGB r_icϏProfilo RGB genericoGenerisk RGB-profil| RGB \ |Obecn RGB profil RGB Allgemeines RGB-Profilltalnos RGB profilfn RGB cϏeNN, RGB 000000Profil RGB generic  RGBPerfil RGB genricoAlgemeen RGB-profielB#D%L RGB 1H'DGenel RGB ProfiliYleinen RGB-profiiliUniwersalny profil RGB1I89 ?@>D8;L RGBEDA *91JA RGB 'D9'EGeneric RGB ProfileGenerel RGB-beskrivelsetextCopyright 2007 Apple Inc., all rights reserved.XYZ RXYZ tM=XYZ Zus4XYZ (6curvsf32 B&l"cdefjp2cOQ2R \ PXX`XX`XX`XXXPPXdKakadu-v5.2.1  79ۿL/xղ2{co*`܁ҵW侦w5M VXE_;(ZStϔO9Nc-HxSS [~LuoO:NREhieX5D!cUH[˲D*Xk8d$qe?=0 ar٩ yD$>]erꋀtD4=Ky"d7ѲJ.!5q'ի1w~h Q'ZvYx8(lE˖'vTq)3X1R:6=1@)[KKR,"F_PbO+QZE[]nx/DjMhD{oOݷgϑ25jM;4-J-Z* >bgQ [7w. 6L>В'vªؐlT)W 57n @/;Q&j,{l{`Hƶ9#Ñ>,ZHOMDn`F`'QRZ5񁜾r"J!DHMzֽuVKQ3:'Ϸf½aR9Td)j4DsNKK#CZx0 w;׉դ.c 0~ܪ:u? /䡾NBpDj,#H:C Wp7 K$"|~ &rv͒c,l"A㕶6]ӷU3<*ѫ ø9Ejju\00QƥήX?U2>_ [Ɉ/Z .ޠ`οh^ԣt\H2A*t#TP`8CxX-Gh:yC0,Bםe3=^t!!cH4@6?Iws c/ܜR*п,r,lcy%r9&H.$"nγ8^\R o٭rn峮01u\=.%O(em~oԌ\. eb*vvA{^:jwï 06wm\5_6 #I Sr1̗&Gv0E۬?O*HTbu$,LCĵ4@Je:P֌G2y\)$(u9FI%)!6:Knkb ҷa3x*hSu|ΓFLl y<,5j6b,PJHgZII~#fR}/?Dހ%xüpTk ӆ=oaFy4Mcޯo>?;0C#Z#ty܊(ERZH/WFwIҳ*U<+w;{CfY9]z,D՝<ͦU.h4ݫPʷN&S;/wW?gYv .sy{evif*0'CWB!?D`@Snt<{Jkӷ9= ǻ% +!EneY(E[8S Qc|G]o&WY\9`e_c5_.*2 20ϓޥFΏHbZ«^aQ<غ> Ǹ լ*\GTW\cko5X%H$fk༎Sr]r~-ſۅlVyI SԧVuͰ2nYߢlF2x5[xfw?nF2Ԃn>۾d;Q %(Y'#œ 9[Fо dy$MJ&E"T^czעŚYuM\4$~nD}ȄKSWxy9m|i~b@D;BH_Z.`@e0$NY DU%n%ޫΩ N 2^Ҙ!o} /0w0mlx&^+ꔺOP Qϥ?nnSIsŗFYKLQ >y[%˦iy;ĪԨ~%&KwVpz bO:8rICoJ2;f}_&H s:$etOWV992XCǫr_g?<H o( &ȭpg8\|yX7TƜ +߅>V.yϱ3"9y㑩)LɯhRI,}}팊,)\Imi5B0sS/ET78iNB{-K74@~Dk4&jLndHGؖ5n 9O}Z`b^HZtzL%%qQ򭦔mt W.WNZ,heB!Ǻ6~{_nP=ux=Ny KbF沤z6PpnҍXPP@d ] <<>qg,T>HKBb=85QF1< a#~CBcɧFAI,o4-L"*t@87j@oWe0Z?\?8#CB[Yzo!^4C^8v'甪5)<8gA>?d)dG$ 1do:@~dM!9ӓ*Dwe/a=0w5< dO%0eAu(FO =;7rvn%&!,h#b!>,) SMuľQPڏ=^=ݼ&檷 0ᨦ'oǍL2s-dHlOhٜ`icfc Wx`(Vߛ%ÿ#MXA@F 6I!@1iUMB+~Hu0P%~<)r \ nFpN(rԛCK1S|dƖ8'3GXtA8s;>_A8L6hc`p)C-N̡-tTp> I~NM]VFKm3EtndZCl2C:)&fS.~OP6?ڥSJ;I$1 V"z$r|^`"8ҏI`m!)do׼PihH:ֱq H8A׬)"U|Jw['ʸw9 _"#jPJB]i@weZ=+q A z.F7ӼKHP% 3#A+=!Xg?`E_lo,+LXpC۰ȥsSq(9#~zm$Ot?Y~#y5VUNK3z #YAx_S>rA`Ҙ}qۦ 'spk$BW|Щ$N"ѵfV].JR&Z!ۏS g%ʺK[S@j%͟m --)k? ^a6A 7MR(?! DofҍS.O5#t(jUНO-E&6#E204JCK9y?SEۦK Nǧ̐Ymm/jp%,7Z2~-SWMM(۫Wr;6*F"rn1tvx7\f֌e\x.ծ߄J g&.o+oC.k^ImILsD P)}aR"`1eˡzEcݷi1ڻe/B9BY/4)o.AeZ]b>+e&6ިSՉ uT'B#ɞ[,o t)~'yLES)hrK+l j I JkL j)H5"}ͣ*T 9>!.mGhP{ s]A8ٹE3w sU?{5\űMCF.mUq \.yS]5 <̃nTE 0^*J9 nl"PU)J"[VH1vदQ\t0Lq1{U( ^'kR;XD20`21de4MGP/Vo=Ge]$2p*нaI'Ѕ-#ar[rK_#{ Z2C00"n/?n-<ؚ=Pi<:C ]g㗘!cEϐi4? %RR׶q=g]f B^ZgXدMN63I[ܢG4 @v>MbxY2Snjh$3]N]#[^2KdU*&*GW|9u듡eaeG?wc)ÈXtbg\VG،" o1qp7tno2o9PQlQs /;‹Hw مl?,U(fϭZ- }2~e!Prn VC lG'=zH(!u7?#2b]^m{l~BB({_6e8+y \;rѥl@2( DsRϨ#O/1ds,iXAX~rL5dU.Mg?e GR5:Ȣ{xFi Bcs M5OBhl 2:MNw,+b3 p4vi> HEn5jde:×2Yɧ᪤P7eS(EYK1uv̩L4=e5lfD<#P gE66I~#{A4XZNie]q2?H݀zz~F^:ڔɊ,k&?$s8A5t, k'Ȏ>* % 3|UKE< O}VbAysx%_tw.w5lc8S=rV9 bR-G 0]!<h:6jUЪgBo!Yy%(KG* #heD31it? 4E"Ij^Sү"<;Dl42]L8 ՠ X ?3%ܻ8qP=fs>[gw dw}yn{&4̾HIܝ3ha({VEw6s |KQљ(6j:J +T,/qQ<1mU'v?&4F]7g%qmQ[YzV3+ǚO9h=|\o+& K*6 _aYز3t.t )tWNL'l+OP ]8ww]r&%_0p4x: 6v3'ZfwqN,r2:km2p6鿡l$XɷV%Ge/~^>dd41 LWp1Gu#+wȍfKwfJVewSmiZHZ' zIHpjnȃ懽]NcP7iVpl3jm< J|EMx:l$_ *= $FTF~e<  B᳝?7ܴr6 DRY{:BS>$]W;1gP,~ >|K ~- #oܮBT'vtl:=T_n4: ]M̲CDǶ{Bԉ(enf6QĨETd,{"4]e \O랗ظ#/ a0fUn*S4 E?܌T|ʸud Rh,_KNZJkȺ';>P2sH~~h ؒB|܅&V9y)_RnG˹|ߗBf c^Kҹ8<覉LůPWXbX@qxAxǂZ[tOzt?7HJp4ۭq10vks(=Pa8'oaE:3jïn|C'rdF]㋮ئSoK7oj|ۄ̒B>Kl?(d`?Kqavrpa99jA<؈q{$ kYY}H]@?pD_wE|au]?{tJ0 oK|Әm7Q P֔ž@o]2FGPhwft&ŕ 0mah@gߍx4XQOqxO-Rf3,P,Gl [>^h7+1uBHb%EdbdkΫ.^;n)g3t[N:+%{y`[оmVgMkBXqB\ :<7Oz+d9{oNׯ3 -\LJF0Y쵫f@^y~Mxݏd\<2sttkrpGA*,9* ba.̃sxd$hSNZKP ᬥQ@-x+#<:&/HLiLHGO 贞OʩE,^(9/J {Lc7 7Vͫ%j֛}gUH 6" y6!45ڬL\#`rɅa/%slll\s/{Iŋ:#01Es5cZNsCVzrpYSy tʞ֕c672 .[j,@_Jf3բx"tع"U>ʇ3ؒ7 5ށ]Ӗٹ゚JNi`Ъ}vI尧A7%T@XTbM*"A 2?>V8RH [a:=K'41g*0V5gOn;am{ҴCV9tvaWqO>d]-5ssn{Mh-܆i/_߶"*Pr*ۉO݊$Hr(2tS[3$AK :j/b&>lְgQզ寞vz*#״RHUp?Ӥ O+Ő_ߩ;ob-M.BQsxgs]GnR^8-)iGi$ƻO9qfg(4//QKܸPUudA ݻ5XB=lZa<q <:ǕHsBz0d>O%[lǍ {0^4s}$'p"!1{F.q[OtgT 6&GX(( KIDƷ-Hn"~dak^\pcBƳ?i(չ L/z;uT 1l/G(M |J[$F?1M͂NǪ`Lj!aAMCAglςe]b-HrM= 󊺩tOp^6y(U?5*3lj O%JjĐ= @N:FRt &7V4vz\!!v}|! 7$j>>aS-xqW"Y2ǁ!XWݿue0C~/爅e;y 3@TNu}`=W0t#xs!ˆ}Uu^ZMRM%V) ,@g>#_'%5EO%cq0i A27|4P>>qL J|ifVM~d>ۂ$]/A*BQ2=/\%TEfK|p*`uu NF'~LZ@0*B?H{` \S6P}]`Dg0gqJ?]j kLMpځ;)Ԟ$Vi_M;ޣM.#Vpꔐ״Fw?OI} S꟫t>oY/L ԅK1or)'FG doFExjc\8mp$n2]JnV +fh+ж&yEQ㈻0<7n|?#Ud ّVj1iEdb\Z*Da*TjhGϊ Ô{O6.a)FZ]qvMȡ8l)RFJW^h*qc7  d+AF#Yq7Wt*ߥk0{@ld|\EguD nv7Oиq (ꑖu<+tfU&]e6K90߂Geۂ$nV !gq1[SX Ac׎C}&D40<)~ @} Bz& deyI8\F廓YZ(uNRB]@=D펍Ž|<ֳj1{d׸'#hKkz|JFC胂@6P#n*Ji)\Yyy)Jr"v,;3O@;X g2_jB=ХH9s@sX&oYPθxQKUigK:Qc%e+Ay0D^5?x447 O1zOe]3/M\FH1Y'Ho.kn稽ݷ q|<&Y̼.g1P$鐻d׮{GǓ^Gbt2vV c%IB°ėɐb xJQeQ*iMojk]" r2В^RqZ>WV^.},z/*лOƓPLW~)|'^޹ $j"m)dR+N>~'M8r  ^x5: 4vOTZb(s_㌨}([ *iƵەдEVJTmH3'7_y異XVǡ$FX>=V0PmTƳ{+E{d#u~1FJNjhͿL7k™pw<8q(u*t#떶Z9 X;'}X.tm` gPŐehjـ7[40;5l[|Zdv@)<^Q3 a~Ԧ2%X%/_Q\gvN$+c"y59N Jþ%Σ\¢zJ j~ОUşȔFcW!Ҥp*sys `_*u3ԾљڊfUSm#T`at_4XRlm_g:"k F~hF5܄:zI=`/$>lz`DcB+e Qoyn~|b!{ter‹R<Ï]f@yGSk2 GYtՓ߻gmȓ+hHN/Lv WF!Zt |p3SZ՝ڨs& Yf8d'de*VhC,6O|6+P4zG gŋkeJMCej+@N a /k 2ikE8kfq63Lodp{&/>v)-$1PhI]sRUe|?{ȃT9Z:f.pд bΠHBm 9L"G?osz[fBhV=\xV٦:'~G;"nχu 5k-DS$JoŬsu9hyp0IW43Bvq4.zkJ)F36)sW0PI koéc" QpAe4I=%N@52:VQD-FhCi#C3qY~KЭX[Ay^xྯm݊̽6N$F Th ADB{yy -Pj}||h_A{1.*Hjkz{n-v8t;S @J6 pUFрh7XDvf Bmy{2?bzRwdd!w G G9q\#? <)@CS4}ufʸ%UWZ<>;y[AW}hjV|uT8R J|+5]FZ8L}WV+27b's(H]RltH]`,~qT$K 5$m̼;۲Z)xT>ُWGr}B%̸kxG*ENUZl/prd&4I(-􊔈l1QpJiȳPsV%3nDJ2)%5Du!eR*Jixe5؅|bz`'w#Zk*{^7`vd=RG璋4 +lcPG;΢Z4ʬDɊf[lʣ+:3B‰9C3fU.Gd!y1M5eQde MCTc,MIVekt`%q#oV7@I% &]a @A EeLK2uyJꙋxi2yڳnYODHɒ-Ov ΝpOuS?SΓ|[hv{{,)nRum*Mgmg,_'tRz4 y-vc5xbe؂@h}t }\2?ϯ3g /D1_B?apyGq*f%y=)Mȟo(`#T czs=pZ#+EBc :Z _xj_A 浭D}CWlw/=5['IK%ѕё$Q.(>q`lN~QvV=sj e'n^#Ag1֟o^Z_3 \ai n]̍e}DMcX˩prTQ!L[BƿMkȒ^7C ̲ -d /Kљs'Y+8|?4Fal͵|zfYt%1}| ~sxzCpLv[5~s00Fy̜{)cuwiěxM,B^g9lhk-^ےӧ"l߭Xx='b9Re{v rS*WɱJIx l]B[AUJ}N_jV q#*e5π,wn\*vkI%BE%l|bΒ&0q<+a6؋*t4bXjYU͹aTtgd!{@vHb+D۬Fun_^L2g~ݾ"rN5G'-O%G#|f ||?ۈGjU(fo#4۶ҁDCm,iJ(OF]RCrhCrFL3hւl5$hO&abn>' s Tr\ʢk36埩2ݷn_çt9w #J\w[DnMF4Oa] Ue h[A{ʘ-:a\CY'[+؁QJ#< C8F~H 7?I>:.!G|$A:*bIr73XߋҚ=X 8LeY$U(Q+ >2ن[_{GE]/gℱ)7߮C %1L 79V  6!sf]B]bH*4k0f.n|Rq]!H~f=TGc)Tf<${r@[19ÍY]胙Hlb^AysZg6{m|6y@ 璜wB*B[{l~NY[~k+{s"u'Wt.&Lr08_@ݸpۦ M7#Eղa)bn9 F#?+e /شjJOQE2_Psm*8Kac⿴iB & m'ID{0"89/EN,ܷ1XKd;)i}) j4=5fVh3OFvրWm8ړA1{8483%ވM[@8H Z˄J#xy`3)[)N!lagQ^7O:^*H=1gL_L 3Y 5^ ݾֳKa0Lܧ v6*Bq(haĆV'հY#97+XġeYĴaYGeKB6 ֡2FS. /V.$Y>YB_j ;#S?It(nB4*b[ϐn8.kIt+ҴgbuD[FVn'_vtECHĥ6>/aÊ;{s[#x?V2y(􏝡811kg+񋳏4)< |;=7Cvz߾z`hr:UJm-1HЅ:%%l,t']nڔ XM& 6б逘8ȥa2 ,¥J .[ku؟0R[(UCm͌Ρ7߶Rn?h cB~aE \6? ȑ=(u-vb tHÆN׽ŬF@KɄuKپ:45@&੹ 7stWWU6$H>J=3JkUc U?n Z+T2hGE@wUsHS{XxI 6u&D&Ap\ǫY8 NR]U\Rf xfW*46ʎ^vBz xٓz&&h@!`$3k[o~.c^&Z pG#C`@O+![#~M-(p҉i{+_#wmP!0"'Fo&ԳA)םD.-)̿RicU tiY8|&q곋A!0g=ak羜Ƃ,ZLN@8 <,QysJ3ahT|h,= J!ɎO o&GY<,~.GxY w M95HCR|}B㥛StX`%1:Z J?HYAU>'AiA=y,4~":r*"qa Y8Iy5?"3Rh?cU1)0-klxzJ"/,.[ӧ&yDp''q.Iԉ\~]QꝇXNERs(Dmm& )i4ڼAQqwwN|$h 5(dG˖wnwDSưmyE kkblf3LJ4bKVGV%^G5%?*4yadxA!?fÒMPxm6M``S* eY%)W֮[#a//Е]kZZMpȘ9]`Ϳل%eӪ|ȝx2Q[éO@̒1*|+(Rz=ɥ G_Cd_9r2p._z)m1POSiuzהD1e7f}Jj))v%=(<@EF`/Yy4m>D.VD憯&9g=>cT0~Y 3>R:ݛcнZZW[6yWYH-B~)S9CR!wǔ? JNס^ʢˇ\v+od|*a@}ܕ_=.fU?a}K0O!JEq̹I$tqX nFG@14T>@M$]nM.hF,2&T s̏`,!W8ݴ%tX}8= a(;٘ ^Giǰ>o*D6*U{!m(sLG)L?̯&ůȅ-F<9/asqb(qT/8&"T[(-?gcNr\Sxyz/Y,Ȱ)5Λ!p"k!0f[NnߖVJE= vX]:Esz{%` hat9?uH%'=,YX('\}YC0 ֤N&>sސP'at㾣~[7tG:(e olJoĦd>'P8RйulBXX:6K 9 m") 'lS#@4'zq߉v !Z>T uŒUr#p\/оɝy#rEmtQ dUGvD6U|$ƌ>)/,lzFl{4q/aá$ =< ;ggAV9Yooevea K>ps 1z3 /6Ep) kh\2֪^Epld-Ur8F_ mBi}&Kp`'>~E*/LPwY|i '_<Hm#,~:(\yXF΁]C;i ѡp%!Z*ˮ_{C73b"Q[_E2>{60}W] Z;*1TB newH*gٷuK!P{ȯUQZ=s éU=ߺ fy}+Pia\X#֨Rm.3ԿG*cĠi2D*UloO(wԾeHVn3dYk$UlmHV t,JrUx.ٿѰGy@$v_~f \FEZ.m;3Y9̹)Oԍ@%HJA(ƲT~oQVj-S%+iOhhR 1ӎ6]ep0-^*%*Js㳂⻺d^v_ɭT˙1lJϽ6֥LhA߮f6S٩&L(g@MQ35?7YlHIa,!a߷rf5o r "_&#yEh',N)$'uN5>L/E+epDF4*^(/y䩯q-p3HV4'y U}b)(BY[Ey,w;Cf{REaS@Ec!MS^1^ץhTۻ.q`߸O`}c|W}X9g&]qN2D]7䦊qav3Ǽ)n2f-)ՃO8 rq͕%a؟5<//z~ڒd֍Zo &j0;{G~p6rS z;V\#HOeNik;|JdMI‚9uDtr|xAJGbG*|!9;9NuұdcwMŵ>B63*na*+;v sYtSj[&lA(NSo>@eRs\Yۨ0˜]-#g?VUʡ%+ @Y/N"bq$EAb=7$+~17fp>zkA6_&z\Z sŚG #< GUL3lu/\q8: <#K94% 0?.WpI} ~7o[Dpy "4X;s{b{Q #֤T~I)sCWޜ_ A-xNPUZ"-~Q'U o3}%/15L_@BCb,h( HZDӛR ·])Ν|1li2͕meJr$YS:&Jr< ƙ/(n^T9s  | ::j>b17tOE&$E̩Bbf=Ll7otSݥ| ɑ)i"Tc9)TC]m:&R@|뇱mV :J[h`i#HVa2:>2 Q dQ0qA86I&{;vh4,u]PWE"}A.Osinib1/KŦ$CjQG#BRѮ,3^rK3j 1o{8; lyӟF+κ5dj_'U硫yLUCd:R@υ`}?(K6P^BRPgUV{- Db"3iVuf<'?5~sX)#[yUKp4/3 DQ=})% d>zKudY| H B񂋸XwΜ&C%ᨹwr8ΠrYiR-)ۏ+">ԖzG}"6^@!e2\3EBؼ_ֶ"2%*AB.$|10xV-{m4e/g.Ұ;[*e6`iv@FCvPz !oܠbXV.c&iOǑ_? [Z%r [KXQv :HnsZTٮA6I9tw.٠r> f8U150s<)وcKѩ$t'O\P)uti#f^GSXns 51TP^uA-Ƴ- T_̥xx]'N# hO; ҃;c 8ȓ7e^#gRhSWb F̤&&3EA?k$I |YVF%RF'}n?W v3sȧ4t@R*1_4R>3Bb-$=4E(bL^,X egND)"ŋQDqwCOuؐQ!2(^aqlQ}*2|D+S]N Tri88fg8о!-L1CEE\_卺R`nO*dˏ9R1x>e ?vj«=R%Le.}6A[؎ P+ @??s2aKBj0!J6R]z>{ ^p^YJ7"06*$/6J ̪>c6KW1=Y0G͞N&([p̄+LJ,V1=MbPHG)XπHm-#͐4En^IT^f)M:vVUG#Ģ,<_Ufݚ<;R[`mB>;]|ӧ<b$0KZLҪ]Vx`ܵb/4Ya$-xhg] sNT a,S4̳=~n0UnV+h/8He<R3-{CcQCwZcf|0TE9h)G(}VPuv8S&y8 8JL;#.$>V%b!6r!mS2u15L59<woFuTɤD= ºաÇJXexcfl^rs(<( 9+D'/D3JPllq -L#ۘIb(U_YorGW̜TK˙ 3#1C=PvY n:T8'Lɸ$@qފ{O->Qtяј/FiP*<2^PQamS{a;at!awyd;~0I ~U#BvR:7 #G&jfF}7 'UdV'd; BuKD.r$\kcgv;GCW߈3]RL.U2߭ƶғf/-Ux_ݔpD 4Zh|},iB|})3W$~[Bzr3FĊZXo|!?Vb? W^s晲^ B [qz}DiE:51aoi 5].Rd͑zNЭV|\e b=-rTOiWٝF@6RA.99\wXTG+Q/ 㺊ܯ=?B gd7upܰ4z?9D]>+'c*3-+^ Iۜ$'o$)Bsj30齧Ith.I(CN.E!m^0tpiM}%.ə QO|WxT(Ηy.Qkͱyh(Y(gw4UxJQ4}򭡄~.*:s#. t"&0˷ie(+ :S@5cdN ]3 nrGL>`Z/.pokFl./{lad.U+Y%j <ƽL7tm_$ ~>mx !SUh?zJܘ~wLR>k_|7AAkyzX$ԇTHG18$lE4nthڴ,YqkU :$| uH&EXl1Wm4w2YA[8's57ҟRduQ٬*{gaX[&N5JST~p/͢#e‰[H$ =iG)66$LXNGk)(DT$i"h54!!;sHjd(-j}Wf|BYImk66"#҅ik %㍩3l lL]nƨQU~Sᔁ-U6B7J0}bX@OSo. Պ(X^])9$t$7щW_ !&"Ĭ`2k*qjes Sn4}C*ˋ#ڐ[kEZWg5b:9*><SGb58;:kƥ4Gi]~\/@j.5եӂRw<!.{a೒xUgZI$׈矙⸍V5ZfLapUV:#IV61Tđ(9E7r2O:O_m2$פz9]EӋoxdz%"TJTzb(!h$?/%E@9O~Ss%w!),k% `ڤk@\`/'⸿:KB rj/in|8LkB6<;,"-';SAk^5$m))Hb[>kg]E/uxt̵hJ~_Tmjݵ2 /Y{_]p1nD j:҂E_!V_C!,z }R]s*dS^-"<|WP̃$ zi?D"+tsgByVV>$ I^X 8+tpȩĊn=D/~ L^̟h;A ՘$jVNL#z/? oPie|X7,k)oʆ<a-"#!Ծi$h,mU:ȷ[h)-[d*+vΚRN}>#p;^=sK9ؿ Y"TY^0_Ѡ^,-C#E}sikVǬPqcʑ񫷩UJ7t'[J >QFғì  j.pv&"A$+mh\+oKsJ2unH\@e<ä!Y~v6nj=# KqѢhѮ2RaYr,|1 łv0Ub;4\Xx{خtOHg Uvw5{Jÿ;|*^ʂ3^J}smd]ԀO$o>'1{ g^I֤3bzFӌ2*1V̰v ' [Y1R.Ěy)*̞ka/Gy5];m(G-sϛۆ#TSUݧ:r}V`U2),SQ{ z 9D$4<0IDY &$W/7aԂ\'n;JlhM+#c*)jaɀ8\sShF`2ّ`-Vf&Kfn-aԫ* ľd^~vҼlHDணCqcږmcLinJΪ9觱>؝cӐrlBYuƊD^%ыւ,:G4;s!u;?ғZdWo"5`W21*+uE?˛+f(*K`qO:*WՃ'컻ӵR7D\dIk"}i d"apx]/ݾ >s)גD¾Ys6&Ӯ ͠6ݦ.A¬d0 xX(Hyp/u$nr}#?(^ RI^H/a_Y(5-x E}PDg]z:}^ssl{/Cetsmі@LqtRȷoJVCFI`,px%dcL6`_}Ґ7fexգ>f>u~SHeZ#8#হkIy7odIgo85~&KX16XCɰz,Ѽ_͂3O"$]be X&B쀖k7< r@Sp`Pj E\QmbOnq )b؅i$;¦؃_V/ee(LZp^.s>钤7"h@1DLP^sdku) opk$,}żr7=GY1M@'jПF,4RXFם0$ӓ= ͫ{]^4hH3FoͺIXaJ'%u[؋4q5@3̛-}dD,ki+%>'G `>]c[(Vvu*ǡng8;*$ UhVJAw|YRb&luiË-2~w=Bѓu\Re 28]rCTnOT/4Ln!;VY=g{ Ϲm/M7ChT2B;Ȣ[L8K>_{[!"EC}uQ.2ɻ8?7({,؄DetW_0=(#/C+(gxaTG(,]g!k"SUQ35}[ßiOc|d~KGwއ%c*b;CG~ORbSVo-4ԈWˋ@V_65xRMY z,Ȟ@޴md,o;^FH= :3' #Di}H@@?UF84%^SNJ(X*IA;K4dLjMjA}aٱl'$iP}[>t~Ї_%S:^eY1ng]($S"o`Ur8(\nY,s3u{Xa=R)y+i~`- F?C-J9yC…YV$*AX@PL0i?\JeMmx]pH(}ȜǙL}DaT Cg] mKv?5Ɓ_N)Z} cFh64m$Yն$ϭΫ>uA ^Tzl•E|p .pW>YIdW)nFG+{1k,$DZOS5 2)(kq'b=iVg:a*M*>֨0#&tuس-φUSB\jZY%:uMכ掱]Ly@u$-Q\INS˼uF[Ti"R[u[D4\} .|F x>I;dNeOv6om.($\@OZqj?q4Y*!eqs)U(IƦ,:TI k6Męm*1%C3z.2*t  3Py o8@JV$OLԄo/#R;lM "'1s:W=8Š9E&U| hSK>!H>Ukq:M!J".IY6sn{3Ahn 2+E I8\Y s<4W@0g'ydV2IJY fZ\ݓm6[@wa#ӖwWJy e+)GTۊ,M/,$*qr?"'=U&w(vۊĕ phZOOC|Mx8[p@ 4<a+U>INUp8(tK<oI ]Zv.3aҝ\<' Bp_CE,Aӝ"ܦ(O|O P)!aua& &S;8 )f^7BKC?gp(7ڪRgC1Pp=;r}wsMoI̸1* MPܜv<kb"5>6hf.juE@b{p;y>~/%֐m0pRbEt.T2+"ksbejCo9D ='>T[N8Jg&$JHJG f޿ HiB$L\Mlgrw~"$ MPX[?a7NZ שˌ0>!ݬzf/ &. ܬ7{FVaV%S8N[9Vp+]O`ʵtzi~ǦJdo)aI2䕸TQC$LWcM㹪ea*KjmeCbXH4KNT:+HfJQH-'6wd[BP<ȁ@q:Wh6:.pP-t n0TL| S襄3t!@Yi}Nlŗh´\[%B:6ęrQc=0g!¥p[+bՑ^n PO+M7~G'̭|d؆+fYXY@h:k1rƭ?ٹ<^ڍivp|⪥0#:Md(pYhSZ{uh |]M\6*X0itZM ɤW/t%ÀɁA< jvVJS)/OgHD# (m,3 »K_y t=:㆕w 2rLU ͸tq7emu1@տ `$9%*j5x5yDh $eGbh#{?MXOl6քH(Q#vn !@w]| 侧Q/ iyF7 D"M9rr!#05 ~6ٵ֔+J9h|*c5Jֻ 4QgiW+47=t8["g&N g$[<]xT~x [Y[ D+0)1Y/Βpk6i==Uot<8XO /Xj:#?:[q22J̓N0}?"ƪh`@>2^}~"Lx0:y$#Cyr9nPҩhxLzATEhݹ7LݾnF߈*`CIOB;1,e^8$4~7s`AI>>hc8)$.T!#=/UB7 :9C~C}g}^|pEuOSR{rA ~n(Il"6PEiިm(rS$~5$| oMC۬"k~U>9+/Z5J## ~-% 4W.F BE+N}ˎŭBeI236! m.Y_76b :g*9…t+Ֆ-ɫD[jl}7=bJ5LOQ v[WuhB]$7, eԹ%Xv}N߹lzwPJɛԡCκ;x 8YR0TH ɇM.AJ1XJ *;x":HnU-rFM{}dc3YDFPn(''d1n,q`f+qئAƆ!'-%b""7; 0DzTQ-]l鰊"gRp8j &~$ 24Bۆ2-X%CוesN (T͚OI*ȆY`~ YWwr*K?$L{. ?Pfi$_*`SJicղ工f'{HR 0.7V..=[t)]\ԕ.lS:`we+>4T㫅1⽟̫IP'*B LتlQfK#oWi, =eʌsE.)Q7^JtQ_yNfBRj\dzw&m%K@ΰ%5,6p,D|Ɔ2|a!̙oG+!JC;Wv ;Ȇ|ߟd# m2I= ~pҽV_ɀLrK9V}G" $J^AFd q=h3V4*FIϙ/F!~fuS+$aEKfRFi)kRYRԈk'5js$[@8uHǞkJ~ Xs`>9l[&fL]>>m-DY=%g^uFĨ7²I,IgZ:Pq<[`f^CĽk|8B(X'O!r`6[ݡ\ ]y‰i;^80LQ;n(SEBXQA h5zC<#GtlFHvv+PE45z KXOFG@S5-_(E~Dz:cE7ERU,xE ȔiLY. 8+EO<͓B[Μ;P+ZV-2C@#P 0DVZst_՛l&6|)[lqui-d%55_幄BʁM=^VeaT%x G.g@YINsZ`=0NuP$o="qQWHZFou9ep ѹk{v6Ϋ??Bf;G0 ^Ws!x]ϟ[I_\7(3r62u~wuյU-)[p=yPѲ| z15K_%CB l4;HrdT@q? gfvfGz Ksu9V6@ӈ g$_+1VTe#bTgWk +n|.uv~AK-[ PK"Pn˓Ɓ0 4erR}t X#0.x]h/:4H⁁uvD*qzIi/iF}mJhܙB<0xKШS؁P:38#C+3`@0L?^T4M2_$`]$Avr!Pd@W&lmF/㰡\NTjFPӖ"NI~l^TwMJ:GONɬ@_M\F iȀEmi+bak9rbxeF Ǐ0Vem`~U։lԟˠ%P.V6k$ᥨPfbծcx+]"Jbi[[bNô83n4(Qos<ǰLܷCKýhO64-9'7NAߣSKeaǗHKMRKקޖno} $/|;*zWn0ÚhPK+|N#fWXŧP9|$K& z e);eflBȨLŖ =S:nGR-4>7o^$iV >֖2Cfp`-&şhYԝD(5_89+| 9UvO z1o?m^\n^aMlưRN `-Y|E:FiɉQ'2jϸWginRƐetf *_JaaTɅX1Ä t q`ͱX;uPde3TĊ:VMUBxkY91颿Uyh);6etQ䥰!!Ga@6,Gmk q1}~(2>r+%>Z:h (|ޠ:DOO(S 8̉qXTuqL Tq\"#-g|~dhR7(RPϖ@r>]J5$7yגck267[!\Cw4&szXm)mqCrLqͦCQ#HJm}{S4>M_ce|DlYOq2+ˬOяH;Y+Cn޺7PAI aYY|"wt? dV$\xU9F!3]ѲhjƠ\ouQY, |%?R^՝/dnZ'p2 j]L,Slm-6iɧ ,;O!sܗ CI\Th4A.[*pEZQ *dtN": Dn'U^`d:0pR=@(@؋z*kJf?`yP B-cBݑ7SjL?)L\n/nD~;mK"{p |WN*^bJ(kS`O. 08v @RlD]JݝbHz i<_d ?\'UU-mgG-x9&0(L&46*h2T*NRH`>0`,S߹-̆s[ȳќseWFK[K^^㔛He5+(&ҡ-'cb&syLnД I>G4rY__=ZՍۆn }4Z>΅*tWjN*@լin>r΢Rl- v9ē%Uz?ƹςu!QĞx- `n",ַ!],[O3qc4H @˒8)i0cl4CEԷ0ru^m@[Ͱ"w 4!UI;> Mk/ dDnFI̛bQ\{n| k\H+~mcly|1j >qĐ1QV2c4j w(*!y87'ɐ$X j Eͩp&(śܒ1 Bh}Z~[5uGd"y&Q %\Л}2Q$#off5(xү+@õ+['K3vl&KJ؎(b8 "`=iDrU؂( H:"սh5_OeB&)Y[6K"m5uUY$O*s+&$>f\o,5bNDP,94=}PRɽs*|zAefSؾz `%,oR-[G 6M6=.S4(N9^Lv@2o/ЅlH >w~29(#n(hV2Q[rh‑|pp x[Rj@ ̱ P8Xpfm]ADY)b E1bTտJP%4w.2k}JaƆQ9ͭxɒmhSBû+/xY"9_ȍ]p3hUnޯcbjo ”U60}3Rvc >(4ul3O#1/XӋBo/Ak@%{.w`dR@ܔU1QL"ܟ吻rPȁ[6 W*v<& __h/`uzgod++%i7H_1k)Xe܄G}s Evw6@iUZ2'g.b 66|7`q I2\ULD+ j_<'!bxښF*+UGBee!얲e(%*yDs{N*EՊ>9bI%}tƼҕiۡ- q%&`iV3(Ek1V}2bΠM.jNd{v$ v@P$ ;^U] V5oHq6 k %'to,ANV>FJyC®2:hd-GM hEu9fUV 囃rr5ŊQ̨w;m{&\a@L5e_ts}5Q}ypL00 c> dShߑxPܕ;xλ3aFf2 I7C.X~f鈄;}.+s-j5\۰$HEA-iR q\(Z4˄=Oyk]-_:O }!\;A-کY`ǖjg m!7D v!;3:-k M0O]o:E6r8:3q'&Ȁ%b(wJ#EOǾ2RՎe ;"#P2qT _ jpV3m2Ox€"̪$KADm])E'·ݍ~Hb {$PQgl^'2V;ُ x}!`r2DF3~eo)l:hx u=Y>>MQ3h]u_ GL7C(wMO1[K NV  *dsV?AbLJ\xjV?”?oN0$Q0h\r>|&bp8c( kud>?!.6A]#luls^0E=mVTTkи1C@Y?wp2+< >kk{gp]轊{e OVpCڨX0VMw;hdEn;_Ecx;46/Ymyw,D[_- N|hBېc2#Ue]PfM= 47;f@.xa\y?JN(?2C8ʨvh1Kѻc~- fh'c8ԗ[a@:_QXQFߖl m,h~bu6o&|l2$*jo#qYVyIlHZY/r;}|YGNe"(eWKqCڡZ'HיA Oj)J(sS6\򌿫GH򔉸X휴:~ Mi[KSXt+bNlG:*!Sj灓cs>Ołgl,6JryT5Q%q~% -P)54`\c<"nRHg,8e~qܪG4ʕɧ? Bi ջͷq颃_5kHx+Wal3lw:KtJ7#@7cr鲪 L@xP'!qv~k[{$8\v֠g/LjT m˼||$AdG#~q>t\vZsH-FϨZɹT4vM |J>dC}N7ԨdLY+C<M^smy.-y}-BeqSTK{jG,#2} dž *ɑQB}ՊPʮ 6UxyUaM()u4~F_j73,Ċtހ~p%Bm>;?7X3}J$sV[45SJ:$]bk^ 7%c6\ɛ9 rIQ󩅆J)FYs\%Šídfdލt3t+]AZ\3o;CGPLwGF=뒹[tQ/8TE?tګ4 ɖL\oϰx$KE{}Z}ѻ YA6krX,0/C}(p ѢY[$*=(x-sѡ}jkhJ7C_\gQ=b ] S3VAmuvb?X 6guVae5>#="8 *oJO]&C089>xY zl2H:թ҇=9sKx}leXGG2 YJ0$ݪ3{{AI2'͏* dT!-(.ãDZr%&"a% #21UP'M)H"Jma?>I`[ˍ4WǓq73=]~Qy=p2/|U:C1q- F 7L \.eqqE(/Y!5p{{ J @h$6u5lI6/$W# <ybos[:$kDZ.&U=dH%ҼeM /q଱޽ۖQpsMl-!DCZ7{Orŵc]F Hsld$ m۹]pƒG~f\ J^C&W!+%L"*Ǜuek˳Hm#K 14_U HuiFWU+;0LQOF5|jVUFSN /fm+XWUEK^sfML.q"8+Y$ݛG!% ŋM1f|<O ~69ʨ:֔/TCqCw)pBJ+d[ﭬUHC{7M!|#^Z،܁6<ᆰ'9#R$ʋx';6xC܂.'ܿa340+`E{8>`݆߮~h|]6##˗aUĈNp$,|%ɫvC,/-bƮL$聂H~&eh 1]fI|דcV`X2C??\[`lI$׮偰rdź˛\mP9AKנ߲^(~-EAk6AyK_~/xrQ ecnöʍ2)i6+"Z).ӟ{G!80j8<)[^yvrUBb'[w~js,S9KzM*G7F0:"" ޴U\S!­Jf+g+L\D\.qɔ엦n@]Jfeӿiv;?s _H;ɹc=ي$_O() 4V鿶p!Wdl*w.Dy XBCD4nmВc&~=,p_/|2dlsĶ3C*: R#)т Vq"F;zy'Cal!m?8pD(eɳqL{3/4O qA9<{> <3ekm@$Ma+%ۣ\XZרՒ޽+=IDF!w!A+>pRAơ ;:?ZxC"j(V8Uz {d 8U}66yVs@}Oq"cZunh e.<k7w!߫rG$ExK']'XbȠ@`}C qVZQ^BI5,Xl#S=XT(b|Ѝ̶m`]@Xl̮Guf  #]B2X_|}RH∏~ROuⱫ|A?\UNVl\3v[|' Y9{ |2w?nHXt,a$N^Ʈy=F;hOþ.\Y2xQ0LP~Z_)AĠS=? 1?Vwl&9<;6/eI_܄hf96d5n[Z ‚`cP7 )a;]2URB?uDydVA?IEem/~y0/_r fG~WmYU/0E5ks޾S"E>qI4Lß2P]do;jd&BpP$&s? B SAdш{΁[F@0Ht =4O*JZɡ"B+o"ۮa- LV W{7ѯhҦH5e'Hc A#\lAKx5SMWYrCOg?K"‹I1_‘$D< 1`K|GdRduNim6}hw8#fb'}0AF]Wkx߯$l[&D&6zb>\F(D_#)ͨ ~EEeHi7ǁunwͺG9\ӓbqgESX"9Ѵbeܖ+O*YJ6lgk^PWtv"nifߒ3t7wERw2-5Y,,Ö/.I~v+1ʱn:h=uII.RymVaeobecn RGB profilGeneri ki RGB profilPerfil RGB genricPerfil RGB Genrico030;L=89 ?@>D09; RGBProfil gnrique RVBu( RGB r_icϏProfilo RGB genericoGenerisk RGB-profil| RGB \ |Obecn RGB profil RGB Allgemeines RGB-Profilltalnos RGB profilfn RGB cϏeNN, RGB 000000Profil RGB generic  RGBPerfil RGB genricoAlgemeen RGB-profielB#D%L RGB 1H'DGenel RGB ProfiliYleinen RGB-profiiliUniwersalny profil RGB1I89 ?@>D8;L RGBEDA *91JA RGB 'D9'EGeneric RGB ProfileGenerel RGB-beskrivelsetextCopyright 2007 Apple Inc., all rights reserved.XYZ RXYZ tM=XYZ Zus4XYZ (6curvsf32 B&l"cdefres resdoojp2cOQ2R \ PXX`XX`XX`XXXPPXdKakadu-v5.2.1 \)óPRd1LDu LRfsI*׿lИ([9fANshgY0ExO&TpY2Տjdv!#nBJQXwDc(mJd5SN{g:D@$-\H^P49 Gx8 Oihr,BS2SbXQ {fHIl$kdAXY:'=NH q\(:,)`#9ϝ[j)\WH~H\E@˙[,bEfwzN~7Ig*]@M[O|jedSn긘֖U~`)X2qE^FRV?cvȕkzPÄr^8<" qC8ªUs+<, q?PCJA}9W QE4Z3$8DL+`ݕH?Ad"y4wkY1ui>Viݩ#n>K*b[Ljm8|f\F:ɟE`԰lE ^<0*p=zx%kGqw-."<5<Zΰ o4fX)P*xpǘ>~>"f * M#SW#<_^q !cTI?Cn=ة$H j3ND\!z z9X6>/y6ND+C4rrPCIr#²GPHlӴD\'Ͱc̫iI#D R7Q2Sjt">l!I$ֱ6fdU${iT8TC6*$o3-7jc1_Rb$y>H1v\ym!{掄2,qV襁X=OsP4UkEm^P\sGi?$7xٙ֏$=~ j)nnJ767'^.֮CX2ljYO@α~zQfӳtV6Wjƞ UבS/1{B |XYd dl`a6RVwbyv`(2zH{d^Og0b°vXD'32H CW?cj41R#҇HX'm0ܸ@ ajA:f)*@ QL)r^B~k)7d.@JIViT6Elb1@[ݑJ\g*S߿o 5WѢå0Pv?,,c I%le|>{tC`&tV:PpUҺBw>n@"ڂXN%bG3lj}-:'_5`Aiyg$T՝z?Q08|kEJA;X d G|rL\o!Z *>8!c+:̵\"jM&lF!F$:))EtX{inۜb4n&w70a(ӷ T\wq:i9x$uh[{URHQ^o;4>:Me@>Xfωr;^}oB8ͩinx;+Ds#Q}lcrLd{%xԘe٭Z,R=zTƖ:F8)0Vf8vv * ӑLBUpGKL<wJ."d]LPb-nikȎûec) ]5%2w2"y2LE&iԀ~d RZOgԀֺJ@&O>a7Ř1 uDxZc9*EZhy7?![R,S]!hZ9ZI/&J(}9LA#eE0mXy^:]ķ|tF#n'E?j-*!׎~f } V2,!Wfk&5;,6'U '_-x⳽?yc%MȞzӇomXlX%TD/ߓ"Gk*oRs4;G]VP]T7|4 se1qho'5SzCn[p0h6w<'^' =~K=-75(*4|ЍOgs揨IՏId~W)GY !zbBM)_}=nwyzvEN6EhA*o'܏Lgb5REpiu{eO5QxXp5K!'K[JEs|sUE]F>B<* *i{ WSV* j0U^l)Eq%׬P<+;KO!׿7猑~$&)40QtyCCfm}40=R=3Hu.j G@o1cZlD[x`=FmꟹqmFtOҁRcXx9pLjHe)L5=6HN,DD. 8-}]sw$>> JAe>NGϩy`cku͐L(-dC9u_%x> `8' 2Ÿ r/p)f7r$}K=N\?0qD#}Fo$pur퍎C '!} $(w=΂/j,Xu X SJlw*::qDxt9֐9w 6w} ݝa(OlI֙WƱI!U_  (-PkrV0 9L<Mwf[QhҸ}aۧ 's]u-Lq{E pTN A *~e4г؍*7e 0zI|e6z }zWkod%>Jv*Ptx6 Li޵QxQG N{LY'lǗ BRE)Do!Cr\ou⺷УKWr, 4ZgM IaNINoin3UKC0j!p9F0גomCZ" JQX~֗dH u>]kKAܔnf02y}"Mخ؋ m,:ÒFtj@N>%>8NO(2MmA o^dACEPgge8 ߜ[3m1c|>vqUc;cJr'lrSɸ7ig*19Q, *]Ͽ!y_e-Ϭmh,+EJDQkhj"~z/z'%sN%oYiZs ݶLPZ-<!'G#~Iu9/.5HB 8vX&岪5UDO2f펀 e^c-S Z57~nZZ{%W3L{Y<.|ބ¾|(J 6v4txiE!o0̔e-M"$۸(F:ۭV5j͕?QLA~;ʡPϖ&< i>Zv :Ql˹UuȮ 5Vد]?JDW6.G`Z-U(:0Eb*ֲ0,kAVU~DYVQx[O/gV=j/-, 㧞₌DJ܁^u*S m2o?W|@igl[? aGcسeZB{sca&ߙMGnRX!vv*"FpݻcL'᧱i? 5$FshfGFdWX*^<̆P`<)mCVŴ 9ضo%N0C| ?P(==q 1:آ&?yu,S`i`=ee2Mb0'iԂ rNUlfђOjޕQ-A#:RZ5i1Y:m@Ȅ Qt"UK߳ī{mmc5uwH_ w-&⨹@VJFEѪA;6Y͍e<ʙd~ ݠY0_s >+k,at=RMMCdnPb8sa[P;_8=~n]0ROUAvcc?t=kvVX0`Z*Xa9ha75=|wqDָ)íoƶ4^,'@uMDdŚ`D4&@Gʔ7GiIq1 YC 3@ot<, Y7s_U-c[CGFm53z5-<0"[bR;\g,_KRAsjyL^T[/0~-g؉KI*&M|TewVzߨb*O`gڔ~¸LI|LpMO8H!BMOX=C|60F$~S*[0싴,t4wV!`K5jR *a|)ruO&1$ ζ](EӍ[a i w-BK[2괆l޼2٥ӱ-#q'dT]#MQ%gvґ?6P152k, )DӪS[fx KS^<%OoIHC~Q3Ź*?FrF= Nf (xBބ&R7*mV6P0,pM%!98(Lߣ8[_#]_o|rXۚ{iDlsJjeKMj%=HC[^Z}헀E%#*i`dO[9 Xtor*Ѣnͥŧ{7k<:Uiơlys,m׌vc)gJ[ krꗈϭP4UJNeERuc28Eݎy^xcqgӴJvzjxOcϪيpe(QPs,-_|]RJ5"u&}7 @zxq fc!'߂a>꼀N=.f\1BE;jm~S;I?)]ѠF3~i6] OQv`ђIҲ. C̠~aªw%Wj,փ6#EwHJfc-Dͬk,'xYC ؖ%|@]*rUZ=70bl_ؿ6KhD*"[iX3 .5SeE~eXe?<+ttCoJwpM*' h yw<PB|=G /s$8U^@Lq> Hݕ{Al"D9K{6O.0U|$-7pAsdetjh)~bxUd}HϙY.~piu2ttӁ?A b0`PIV\6#S[uaaS92 +2"{0e<vgC ZQ: u߰YT^_JYHs$ 5:5Yg5PXF0_7S&s-GBm h{b,d *`0іng?_Ojd(RL, 8G>TYEn:CO8{by.8ל?,1K;u7}P݋v+ۼ fw,.}B7׃ M==^XH϶>%䱍,Bg& IxZ >EmGzt^,ʊk-v<='8\gU7& @r5mӵ; uG}͐neH-jg7S޷%8' Ufi )mY<1&p')IOQZhpu!/}JB>,l%#<89])^_R+LK;H+NJ3=|;-~@t gjj^7)w5C$p輵 gۮ3G5W?N͊r$n|W4w6q 6"k0 ,&~qgjXBiy {OA?>$AWJ"-^Ҧr(cInAʩ/(4hoӣCś?H7`ѼR rD>G1<]: ]^-"O=u/gsCHʞ2\:\k"B Jmƥ-Ԟfi+^aBbta*ھ@_XlWL g!h1vZټ0Cd.j?h\I#DtyP~ȳTCTg5FbˈG 4cyMhj?I\HMUB3I{.D sOhVOW$9u̶\qi}~*IsDI-6k3;q"i7U)L4}G_@9ۯAιNKy` .ObP__(,J*{ϛlGco8mbXq!@52U,m@b}W_FkJf Z\R@0Rg{ʃUwi:Q 6~1SIE y{FyXҥg&J;@BMh p(d^qg[Sn[D_n{4sO'_pM(B,+⼓)W 2d#HeWm[ҐUlX%.e[4O1_GگLk8E d $4A* 0˲}!Y 01et;#~4(C2?*RN$&jE-3 "S$9ihŝv sD W]sz"?UdSHB:IoAי#%> і܅)b6y`j/XS(tUŮ_ ?^u^y%T ZԒ}q@ݒɳ#ڑg<-Xjl5hIQ pM\I1Z1+4J3MaXx9ڵNZH!xy$ehaSY_KB,N)3[M| 'qz]rU'epx.+wc?ɚ?mOQ"@0*adHƩYEcAru=aaMlϐsboE9&u0i|-lϯI-%6>De7(iD*wd.Z%HU Zz|8>/pU= i `K/Z1a g>?~ ;K!Mkfgi\gUN"FOƎ?B&Ep׉9PgE8y, 2)&n`]3%{s%@-LaI¥R)w,Р%^V<6` }nuw:X]]\DWσ&<[>py;qon ՝d}T>o]@ZG {<֪T9_)-~\a#(1y#CU ?۷ :w"7Pٸת4U)Фjlmۄ y[ Ws0]v?/԰0qɗc[?csڛ͟I'8.jB96ɧ0mj % Ґ406D>윔x%zw_@a5Wep$}6k! f Pd#lוi.G%[͚g  hȕ}()x9;H>Xtm8d]t.*,׼tf-AZDԅ ^Goۛ M+jMUs!9vƥ¡tLARixB?,tTڸd{<-Œ%!F.f#R/Y;sѿ $nZ1NvdHLʨٻF\Lͺ)#Zb$f:^py$b3H6!gkѾܯOCƯ?J)]6U !fd'L_|[?[- +5C_ u= R?)wϻT# b9^̆7& g:';@$Wrc v@L0Cjd=k2W#RX:p/pkPN9*ӨZQ=g[sNNaWX )idjlT~QbicWTn\C?~j4zbȗ|~ G\$av"L ry՜vaZ|s.a(+<gC2n]:HpP`T]yk BZ2sRžT 46| C̷!{ ^xf;&Տsx+/o.T'T)i?!-Ģ} [2-4+V[/TeTJ/ݴGTwCe4$}d to Ӆ)+򦗪DaЭ&TC9j2q X5fpܧ1sw`m#GO7e.:/wM/> L&3tGd4PctQ6b|]<#G*7h( u7DQqN*RY m#!;59HbQ3-,0NVJ^v> _Hh襍[jK|8,t.dRSkcB<+[V[6_n;gwo\k,eZaHO}ɾ/*7%/tDn+n"m!9rwho-C^,CyNL-Df hD`+XJɻ9NwdԱ 9=242ŝw Ynu\Ax6/a̔&XW mv,I-pN2W# B4 㢿qӑL  )fjI6@&Yc _u5y]A0 AWӴ 7oiQjZUS2'c?+$TG2 ~/9_]qq.[}o=ru/˩~E 6Mw1JKO`h;_y-k&]ӠD}5E` S$Q&o^,v %4Ƭa?TGh€2~#x9[Raֲl+GiKqTW;f30aM{XD_Ƃ$;rtEۢ!!a--1But !#܂?9'` ՏB6*$YE'~7T\W% L@,,֣Zu wKyﱨ᭶'0b1$Lw@R9,f sjtၬ1&&;v+B1(>K縙MJ^^)m4o_;ޑsr$ [VWcз>]o//!^2ȡ",4lɢq}`ٟssDtۅ:+e1ބX+U|F 4{7ZڃP DywoY)xy0Ê8Q rkGrSA*I >kI9YƻVn/, +3ߞwsu HI7 )k6`-af܀1Iʶs{pn2,ӨC_TįgY4DV" Y$a=f[&x5Ӆ^s8*em%Ri6HTlRT OnÌz07JD XL5HėzZ Rs({ܺ}u=>?#HfÚ7zO %KOA mP$Ht C#;?KQ+toux=&IH7u/n*y3 "X#T$Qb&z4ttBQg tWhO>_Hfz0{!OTf1rАNdFوxooP^{ V \4"!+0mryBg4c@ eI)<̂`_t&Or}[ ]u9 ü̖nYTrȾK~=)%:>Dw)) Iܣ[`<)CH/y+*X {9ziwҪY{&i$6S+%&yl JIWQ(nm.!/V4,(nT4NW J<|"tLE{!s:iFh^{l[{^w$4t Z?avt={YG'u/e*嘐: 2 V+.P:PD;n/ 2O6b~ \r&5Ha 4~FRcE4X \E{Kja%%Jp4{@m4e "^qFŀ16Rj3Uv91g5M\EM~a7`\JIi;thjcghwFqln׍ƴI :a+Y x-H,RY*m>7?<&VWF XH  vXp%K-T1vOm,:kL^p<]ߤf[Jm36^P/]&{.{ j8)ln騩mXM,F|4q!ǜz "64C/|;܆=e&R7b1ܞ-D8$Qŀ@{{ -;vO2#+)FD$TۉT+ 0Y2}Dp=t:W=/z);q4h6LiF"?dx&s_;WOQW S&h 5,ڮ`b{gHLS/~nPE5ypqbԄ +{#śGSF[gT<;*\{gҜv|/Km_sS -ӜG@Qum"vl*uc\%N;E^]mPESI5Ck,~Dދ,&6;o\Ӿy u2Vfu|g_Ǟ.q!A`LH`佑|p[潐%[ԲuJH&kFn,0j T!k3Qgd"M4 "Ɲ^Lr&Ieo7ݑ+ࠤ*܉JM! m-nC84yA-|N(eKJ>ysdpD%G40M:w2 kaQkxnZOlBR4 q1.֮O&kPȘx]QxFrZm99c0їm ƲCm;5} .y,.]bwO?IQKWLDa;rhOQ35.,sNA%zԴ̂q/e8{Jk Q7JsA/$!^fxUvZО~ 4%$ Zh҅3X'P/J37^̱)5Bu}U`tK2exч#ڴ6Rk31(֊wQb`e2wZ1+TݠjĖ"?' j. :bK&1ǾY[ðny=DXO=ΚrmЬ&nӣ{<,xxi> */8=;pZ# qՆD'A=@eGׄν!&A-} V<|y\ zhv5:^P89pRPCH6~U^z5FU-9턠cwaK}&qcɺe'83sK7l\ `}# (V'1Zʡu[8׏>H< v2'%eyd5z%ˋBm?( ~޳G)=p Xۛ5 ꄓMʀ_Wαz^R$aNmpAѩ91CZ}пƧ̵-^9pdRHUA)sآsgpLq E]u5dTn6*flȽ7Ʃ}:fm}:Y%_œXYּ MxutL(F/ 5T+? nT›)uQ&0;c(Φ1}<_pqp*Z$]lG% KO'ȩ+4W #YkPV׃PKuun!Zu Z>h[yVn"n g% l{4#^d25 08z uE*P?IS<. |' ɔ5 89k5:"'4BzP֫E0 ?w$SWX(4.aSgMd7 9Hܤ=ӱET}3i4TR'ήgEՏWL_nzu .z*sc߸?$-sX8 [ PXn LB~\D\ɺrv06mPs+ѻ;g8#96_ U\RrK;4EfjwbVS@Ip dؿ2_wGlBF=;[Kd -Y\ (zgWi_ZUi; $+Θj=\ZɅ:)Մjt_N4Xl>Ex u>أmSR?Ō%H?R>:mBX@  :cjӡ1k_wZbI^sRlsʔʟ%jjWOihqF xN]/2,s[P ֆRȒTZtIOߔ|^|w;]QDQi0i%KV\vdtvH'Fnlk=uQ30Mja'c6OpW[Q^yЬ(sQO~r2v4@FhӲCVpFt.R¼m6>="Ҹ>Tmߕw-w /\|ۭc[J=:JF~8-[|yf Y8i(t/3{ǑGAZ\5V|]d~^$$-+KXƘDWc7.:ߚd ӐetSԿ4YK> &kRDz u-Ƽ.{xXJCfz:.(2`os1Xpe5W@1:ӆR8᳽E3kRu &A=[2(}cs *5[ŦV$˼2^} +Im iДwizmp $ʻT 9Ҵy~Sȧ JmfHdq\׿6=ʒž9}CF-VMb{HVߺc#f~є%8UncX}>c;1| hFҋ몠г;̇3›V3};6!5?9^5 pghv$X(r$ )z\(-D@bV=^-wphY9]ɛ8Y{q$ NCc,Uѥ"m2A JmmHZc։"koJrG,Ʈ|5 4N:cI2I_ዘG SR 'Qe^_0N2c_eCEdxπXd׼vtX2+-7{wb98Mhdul] $g>9'?ȓIE.ny|UAH_OE4p71-E4w $R\d%fcE7ZAa` [QŎ. 1H0kDSmyrNGPjl%22 UbK\ ҭyu?PQc;$OÁC>5d/ǕV|}TA;H =7GQHbd9>ɑ̱u"ʽdbjCzhD@u:O"F6ȉI6_Ē&6W45l눮O/}d\FVIJ;tzYW)HV֢$',煁B^64w4޷:'xc[;v(j}Ĭ8xKA{o$/d/daW:/gvI ?~"ٸy/c1mρ k1 8돥%>W}园 rXf7ܨLnOPbr P\0"zIۭ A4AD4ZۄQ 4Ia>A(SWMiX&u$c &y˘4CsU1g56.d8%PFS"dVJRoqhr$lJ5EOHP*Gx;0;]@8+ƢX̦3I[NVZ+6{R$cgNPDePU V9އ{dYEv:u3R ]ђ'CW+I=VlI`M*=+=(/( B|ou]3`MƫaPk\ukuSW-L:@H K|P[snr}og'杕Ӿm?'`/Jdv*24jUp 'b32lS~u)tո')6x)QkzTo:+ Aj`'{DK #"\$Q ՛}FC96ji=0}] K|, xo*W> x-KlU@`,q`5vol78=x<|1 (.mvj r\Ӆ+?d-8gݘ3 K |!a P0MP0,ttnVè@k\}kp0{WrmRʷ̎/1I<1tNx\Zr.TȮ>|#쇚72vmV<~f7䅡przhil%ςj.&r[i0FhƗ {T)װRМ0]^6cFui špQ$ R.3 TnWCŔQ:HsaQ %4s7C>e X n 2 йs._e¦6* Jή["\?eҚAhkyBOmn,tU$O@$/w}nɕ]AXX?mm曈q>sIK<g~JhMT f8h5£(%1F[Ϙo 85á iX$' hD)UtEORbXDej_CCCs1*c5A0$H2v &}Ϣ'ٽkFsjPJKG. u2=yIzjk){G1>lTZ"߆(Rh`楯4'QXX,ް-u,1`l.eꇌ0)BVyl4/sp㈚@kT F|R."1GiIrh$ቘiO"}Lt A#l6A_ g -T5OP&DQ+jM{G A@hѷ!t^~uJ?EDf> ɿC< *.p.4Oy ڟ>h2Rl[FtL@j^LaYsd˻ī4g94}~OtNq{5nY^J]-˂fXhfۢt(ބB P3p4:8Y.ͷzV-YJL1p 1Ty ژFdHN@ic[wlTБj*!J̬m˟$ȫpn!ta(vrMڰ~+kέZO\-b*]_=Qg+Y|)Ek,qȷJж:I{dSs ˲b.Lytb^R!R2\ shtB"A~}5"XH5ZGInAwu/\<$-Gxd¤qm t.Yυ|N0 @M,,tzߡH.kӈ̹wZ,FkOߒ,i: PѕnRTuQ{:PSԤSvcO"8HWzgPo?dPSRz=e"ܕ@gvur3Ҥ~y7Y mdL+);<ӐJ0^H]rѤ\XnfF5v)8=̩wrT4FQsGo:; HQ.l"#Tk~"q"d>09F$ @vgX:Uܴ2*/ _X,-] L9U@dc^|9Ufpu2]7TZJ1Xˏnp9zEL)RҒPKw+]{=1k=l6=d{-;H 4xXyG^YI>) KK\, X*L^eir Қ̚SmjVڏQj/[h";&P>#hԤkCǨkGM\1S FlP^|/f $j8?ӵ5m&*T;֊Z.cbVrFOhvf'@(JtRlG L87SF> <V%7^ Oy阘vd s"I_F&?`JJ q'c(dQ蝄@Yk-@U /pz5"Ff[&١eLHQƯq)ySMO4T$^;fաĒ"NkF͓2\__" VlwդX/UR`蕅LcDB7m}WJ>*v.h_eVs|&xpvp)Me:'.Óȥ:B|p (9ɐ,[١ڥ)ztOu ;ΉyFBǷias墂T>ZN/y7&> W˗$ |x1rf~g P0-&npN͋4qq7k.v^11LWt4r؉";ȼ-1[$%+0KUx/) [ԩ,Ѧo#glZZUef`2{;ezN+J Cz72)ɐˬXƃE%,4mSn@\wZ|a^o 6Y"XY4yjiAӪiPxkH;Y"/Rɓ\>EZD!=÷46X0~kE>x07SOsPB>%r7ȣG^9 88 ԨhQFFGci4w>kҎȉ$pƒ9l;2p 8,`G~(v/Vш%aSCyIGp} _Wд Յ3:7 6q{? 5ASWb;@^_i f;hgy#׌1MpfΞ;)td͉ g;eְ&*pd]-az2 G|4`AX_<]Xm~op$?DoP>R!eEi#~8L!0T/­?A 5{ P8(ky;>}9 p!zD| Q[06G?:@i#^Neepy7dMĀHg0=DhLźRF_ P澸`S%7"A_7|\b&=+_#Q5允 Ȓk/aet㕀s:E=Gh]ᦿ/F?eC61 ά+fL4 yޭ(#[4kn/ ?g.:>xC `│K$}p^' 5̭<$"卥!Gq@삁ʻ񣅉}MT!(>+JzFg <,.lĻ*sm3뗋n X\IlU\I&lO?ŚSÝ?,WL-szLkÍ9 5K+]kGoYP/?s-qbA/dיJnB%W#,:ܕmcxZC. Ce/eŒ\ƶ7^exovBו~xWi!ʿ6pH\MI/7",7͸s#ë{boT^şZ&5GkB*t2nՅ `Gn8(jضvΐ.kt HO+UQ- ՟1h@nR'* ('BRͳ@sAp7z>Ã8f&9r=څA+^#$ T1V <,[~;Nqjw=qZonyCIC:caC׵(ܸLb".6+C6B`0.2x*,E ,| !Me!"E{"إ!+1)I9e{lg!73V%VL)BWb޸]N)aÒ'c;d;-hZZ֬Y4TK]>۱!$lR8bo3U-6VԵඇ}몟%d1k-J`v/7u Ø4{nVG׫ebqa4%~h~kwu9cnyPyvU)A֥i)Uz4*v% Q~#é, Zr7].A2" 錬9~f`> l0]sE{X(9wBL{MǑI?9W~VϦF^Og1A%;ET ]͉Y= mtê_V>P@L8+uA~jќNeʵ v/l(OS%{>l|S=)ڲ_C'g/xٖ$86{h2gy pa@C&81_*eb:fX hjyMͻNio>۔F(\5+׻@hfPbnx_)2L,~/4!SY>%R\dL,`zb+uWH?|Ec:synnJtט2lwCBEWGEt8ڳ/ot4j4/r3z;KLdOl6Ԕ(ڰ!M^D7ޏFNaNQ#Ti]R煭+Q.UZ`8׭ 8FkUz}^GTL5=s)e?4mk/'y5$l8$g;!JcSI 4z-|`F/Ii1ٚ&<~=cktWFG{3 {ꖋG%* ,JG |BNϹ/;H=<NJ@PSL@j4kl 2@"o5plTH!X.tNRM&aieHǩءU*"ed44kwCrG9ŪiL ַRh-} haZZhM֢( vJ%{ĜMbT&iV=/u5׺m+1>WW! _J(R?MݖЦR$ miإS04 .RFY=W AǞtnPH##fM~LFoG݉bmRIKmqE{VA~, EC.+_R 뱒/6 y6Ot~GL=Jtı ^̌t@t7aB2|!ܒ_Y\U_|Pь\P JH+<(u_QVh^ۜ+g?STcR]@d[YV7g 4)rRNK.v2-(S];᡾aCft㕾_i9,hSՏ<[)Fir3$j5-K]!2+D%E[Obw7 2.^*lT\< a]X2XR3rs0ȇ1eٽF_ #W\GO F;?'=ugMf驫$Z~"?e3+0 .5Y.־~G>6 !A]ֹtZbH -3"}CɗâHn' JFʂc :NI+ Ov0~z\.(;p 1XC "STTzYiAPqܻRl0H(Eȥ:\._K|IH!:FLvu_n31=KF,(S R )ZgR;]SQo l[]MErcz8I^gqѮMx\~Ds@R֎b.jgt|NGq csYwf\#vG IzJ% g+rYy1 ~@Įn2U!k61Jhm۾4hy/}XWp}> 7bdtDpKҀ{Y\VmyoK)]IcȍYS``&m3}:/4 vg#BgsT{LBO̜t:6:EYhRi]98EOe,&o6pj%B^DduM(k+Njcf`r@2(Ns"|(faO]N(%jV.FUw2AnƔ'CV/Ҳv_(!p"/Ex(+R;C/udZ%g3~NI 2xՌDI,o078"]-fyɷD7qG> g- vQhRY4A Iwgv|RAj~^s2?fgo}PW!]K-1p8͕b9ȸXNQ`c Cͩ7Yf5؍HN4q>F(gQ.8=1'[ {`TW"CRtX}3ٳ %rݙ~,cE_ A,R,w*I 3![NO pTyq@:#R78yJ2'4j˴AԳ=!SzJԨLgd|B1X?1Th (C0$|棕O`[)*1cbu+ ,DP/%T%`4Ϩ= {=̵Y0  *ى"vXX=# D|82ȍƵw mfLZNV < !=FmCJGA K#{Lq ʁ{b N:r/X"I>qdϑVYl@9Cq#mR9y6E=Hk*x߭6nSn`Ĥ!S=(ՑQ򌤆n$:b ꆷQSĠ3*M-? YFݎ#\h17Ee<%#m/:5!} ˣZ<އsܡi:bѰȟi RwN8HQI|v ER_;fJTn&/=]mbzb k7_*,;k<ڕ.Ѭ,8*KE9"DF)F (0PB(^C_ӟz3Ÿ0p9_gw.a|a!ʐ0 2+ϦHa$ n;.x@:a`<ֳW WHCYˇ(\,XYé0hAifA>ڀHBԈjH1#(SVk%t:̼M!Js$m[mK7+p`s P1?*Dt?jgF:H*w-C|wX{=uo #=7@Wхv8.xDҳ$͟d{LBz=iYx9d&T+.'.56'Ⱦ^Dc401f Ϸ SMX9g:1J# Zk,uWL `4)wcR(cLcS @ْX+~ VAUgl ܈J|044X0-=(ޤ,ΠڑM9[pK=鄡ۛv71:~ |mX^1:NX2balk(2-Jm?бXKwTSaFBm4Bq8by.`kLq1n]}_9U|i:^uQ;Q2 ,a~PZZD촉omv *AT'YrGn78/ ¢5[ن&Mp؞-/28T6/RLxLJ NBbxƣa>c þʟJϿ*z ˍw/e6(?M,ҟGISrnuJPls9Gv\ʒGH;&kK=WGl]2:ec7ʰfŽ8%(d~T Z`ٓด}GXk_"H##p XSF GeKmQfPzA[[(ufim {ݦ;F3. ?xUm+MUa?ʬ,x=`t'?%t2Ֆⵞ4 1kŞD%Jg/64Oyoc~`4tye[hsؼT9~]'SS^ƿ` Ru# EeYΐ/=ue8bIRA/(w8YlN0< Ijkv/G]Qv(RkV#D>* HM%%8gIp(&!͒wxB!|9/ ND \b?#;K0ʆ"ybbbKܦ`/Rq+nj:rP/NjWUvҸZն1U8z%Q#lk.)^%rsDC:Rc.GZ֑@SN,eO|]S#^D^ϿRU w?')'LNm lHho` v3U=ЎX LSej*UD0"WR$ J %],U@XCOU?!:['{{0u$B8{NZ' Vǧc,`BsՆ%)Unq^]V-g4a{VʼnPs\B窚ەgme!ץ3Y<ɩP-P} ՁdFH;~2Tݴ?kbsGjUȖ|Pthv ï q nfWjB>sn^I[Dmm'`آƷ~ 9ިAϐP8-nHl3¸X)dHܹAeW}L5`+өjԃ WE}6TRN|d|*37y+iqM *-$Jxwߍt>7trW *+~T  CH[+[ u"QIN4TZsjXEgMc)dSQuƳ[(aC BsO|W.`qzK ΀ƒg:wci:%w|N7 nLh_>gX-xz Ws7">}V*0ND$(_Cs&`yϴۨ Cs~:`h_S*<)OWsqץ˂H(< "td e*2j܉i4cm~"zziYOeþ#m:z_|iϩ{~r %s o+LJQd󈆄tҞ_UAzS5}/՞f-'0DWs Z3)#eZ/& ~.q"F|L2uʧJ EpRşkRRjXyip- F"?bv[ )7PmB--Z x!͊0r|%}"@fQ@c):8^ogS3&gEp ¶,)UoP.:tU=6W1#Cl+$tbJ #g)E\*ȰZ af =FMMFn=$Փ_vC3q3A@V;jx|]%K K`MR;,3&AU@`1lH?\H(<\(!YRӝ$^$ "oi9Dɼ *v#,{MmGj"}2V7߷BDBM9%K˞ pǔ%gqżNrȱlg+9Y:=NŒFxW҈2B#dPM Ll@`J:B|fs(гqPXD0fgS~\PLƞ~jȄ⫃V_&k u[|H%*Cb󺸵vKssS.q0f+I$Kb1/@`uq.oe2)ήxUgR,̺x[Ghb|w:i{F Up__?;#!(҈<.ŧ@ðBwp +s\ݾJ;#.SiLC =R?qpIk` aA.8{H"OOh1&ɵwy2,OQqo"wfl\W ]=4B`$r?-Nl=)&1Me|}J 2`ql#yW˦^'WK ӈ^R uzn (j2M^b U~qr7H"yЎAP ʠA AELZ>s_W5B44tKE@d >/̀6p{EG 6((vQ )o/qO8ezl361.v來PgOW$Ɓv0?ۇ9)_xHYjuE "\}X9թ 3DshT7;.;IwkW4~=YmCO. f4f%?6!f&E< hN$`}~vKt1s|-_f<Ǟ2ݽO^ͱak|+LXs%rs}-Ipϑ̵T H4lejl0ŕJKB|nɣ1XΈ[ݶeB1%ݬ&KyÉ Awh{7aon֩DIV<`tXav,`S `w>@ ņ&sRJO2^MbK%;i3C9E &JOe Pd/ ’YsXVvB! s6^FO3:OasB75ciufL2/71lp._弳{a`b++P:$vLN̯xHA>o|\)މ6' IQCHA#ٳMYtޑrY j;M`+LmG<7zˀCԂGpɓCH]Dfa@>-µJvPh uS^ Ur&,D>"뼸6`&t4ߒJhAHdH$>NŨUsjT֊t,qWᓉ@I'vG,.2蓒2HL=d(%z.CU?cG3yK9TXWM,u (H7,~Ozݹ7:dDB20Onkm@7ARZkVb@lm a-RB,EU43lĊ4w\#; Ixְ֩T~5_-g37l=,)y۳FO7X]~|}w4x]_} `o2-{HSfp{ *F.<;xdr#՜6Yɳ!&l{dRFG?5%Cp̼e!ڍ_p;ѐc q.гȎjcp{XQ BdqE2 C 4g ܃!U;#rdY)bF ܋5 t>hFB|CVs$E] _'gӀ$]5@ZC9 8g47!٬pSL%⊾6x7In9-Sub^>݌v#<_ K?CΜ>DZ! Qh~S xD LOQB!%g VA[%|f4`9.gܓ4Ƹ-t O/v160DT9`3tU pG9r"dkek +:a$I;bz;77[-#'{tC TDNmJAt]W/ioIdm%]9ů90`D 2 N8 펹@V6t $)OGx/t̍8?{_v5\9 Ň[| ݣ$-S-~9YIND] #S@J>`9j4+k 餔 ͥKB%O?nHO-X :󷹼.淰l2{0ĉ9\gm%Y/ҒlR 9w]Q)i JyQ@' ['0ij*40n90ZP!]Bu|R7|~1O2Kkc=p\WgЅ`M 'Xma S$g9ms`gq BL5pE~x^ ;0ShvT!khGLMs?)[1'}QJǛ}>,PҼ/˭¬gx"7zt9\.E8L {kT-Jksqx!dn'O { (Q9vڥ!ۣ[2ѹoZKq!t6`#?Ɇ.j)N|6 T; zTb'Zj)<ށ"/Ojs-|zo*ҼCC 1Sr7$ȟmk歍g|K9x۴p{<ΎGJb+1^k5tҚ\ƞ߲89{ݒiI}*[+#ڞ0nqj T NJxRX)6fh1<#t #r$0 .!ijd{x;HB=b4}SJ '`OubͦM{&o!5EWxc$قEjVj#pWOUu!@ySҞ~ uԄ+T}ЭlcӀ22YUyc: mx\ 1~uM=PA^ۦ=rA(BF]w_S6Ѱ (X+^;٢:pÓe;=}T6J3~ݕԾސńxI"S Ӽ`Poޮ,dYAZh2"%(95Zp2&&KvS =go_ОMJyd 8yJt>"&Kcz-o֎D;Z]Փ/+bgV4~R\y ѫ?_(G;`|''} 覀oVa\=uB[\z_?Tbb 8$t ?n_ӕgoSk'> }S Z'_|OۇCh*?A[WO9V<=s9)O,.rP#gސ"^߂ Êp܎>3*o$V'K,NgN'R}=DD-#>AEIS7/̟Y8!Pf$ʜN§gG<`-T\&@40ItY;S|oP*Th#iGdV‚7yz(Tn`Xq3J©'cӫxVoZ)z~G"StXl@6R!_)2`-x|MlAc9 rI OIy(N {&^+G(8:eOFcL95Lzc=; L@Odu\,rpLڕK]{u7"sc~s<طnQ.αk|6mSpN tQW2 rGԊO8(mGfn5գdFZ>$!/:\&}MIB{*!GNEx`]G̔'z+klU]݃ 7[Tqge4^9di1Z\mO14ʯF2a0kI74igyӎWn}Y*VXe' wg(0'7'-=$L-}'ڻBr8~ h9)G w:g@wncy)aH`=G.z+ \ ty@g8eˎln9ZGR&$1̾ݱz5pne=>7fuHc>@^nTXzAS+Wg5,}xqE\>XD 8d1EԼ3ŞbIc]T|IEiWBS6~Zr*"2pY.3H½Q[F-! אܓj|hSb}g3#ثwPUCәQeǚݡ&H, 3[^sΊs e?o&( 1@a_lI׷@&oր;V|$G9GbUD4Z\2ɟzv|?kqyQbAkO$ep SaVr>+̰𐅨-%"*8,yQ[@][$Q&']sJm=f[s\;OUP瘇d{a$E-dTkxʗ%|S7nXw(-8%+2jNOVŞxyrR$Y/Xz6↻JEZpZU"5yiq Qf9N&j/E2W⁲6Vp9}N/ 9e3! =gKH^MoTBO~^$Ъ*}2(Q* J@7{x,8n+B"ep J8M8buz:cZ*,6Vf$ vbZ#JJζvaf_AM'3vߋ$J>Ć^:P_Y|L[$[1_^AY E$i#=["28A<'R41jS80!1 7& p)z׳'_rASH6Ϸq>3邗>ޭY̙dy˽6B&XPpѦ:,JU:vU40kBY B i!}!AnQ/tp Q0ݜ>c0@ L;Vr[Fn,XJJo4e! AKOaLzYփy- e DF2Z|aBՊ@/&ǑFaFmT-UYD8Ou]~PIZ2AĴtgW-PGy~z5Rh+q.Q q;I<9K&{d1WeBt)\z E[sQ()%6AN nl^k[ĝO<2yy w(#z1K g-s9Vo&{ !@$…, | lÒ!r wl?2J6sM`fʉ">ӀG չz̘(~F,Ro;_^ᚂst/#9-Fz,CLv} s+ P,O~=цp7!j+7עiݟ5r FpՁ+-Xdjw!%@nf_#7%{ ؎؎~^TÃU)oX ¢%=Km`BuׇbNT@"qy_ux 'IMO4.cv\*B(1]3b Q Luz]ᮠ3r< 3^:SK4 tCFlD1="$C H3*9 ԫčA j |3 B`gFbN'Y(uDM xN6BbRTMݸ#0 ƨٙñ/0T-GH Dj,6OaU'l5{Ԏ³,ĩ-Hin(l R$OVtFnT8;NE]!RYH+vYrPS_\_A`eiw*w2tch? m/_I贐‚9{C~'oԍa0 UC$nj˰+M$v$=GA|lW:&N)ZNT.HYcA*qF룥jo1Q@q- xÂuZGzocAxEI[mGג,zi`tH%pi&4[(,+>gЗly~7q!%"K.bKhzI63zHϯᰭKr'@v1ZۙlnVkGN^c*)9e\AHᝉ䫶_r )G ٴ<<-^P(,&L,%(&KK'*0!Gڔ*q" 2xvs6}ÐjsJΟ}tQsEǑüJ ci1D6齝}һ,OI/94[ռ}!cHtэ5i(#Y.*DEvli?"PʼnݞT3ӡ+s9@7QCaPy=GfGQj0N2O\й!K C\~*aW:U/7vo>Xٹ'ueY68C;,U̼|\Q'O{ wN{C>֭Rhءx1, wo!N w"/[]RĈZ*/Ojh_u+yRNSIWqb8.m68R!yVUˉvIbG [b§P(a6F%;LꥸP[ԧø:ӚSjNxRVIDJ8mbBKP8,dJs.gI''RXXK(>yij rޮ3'ރO=-Js9R>aQr("TK[O\LOzNJ}?/Z^)k侔3GM7}D{C{& ŀ"@H{ (;Tdy`@ojS:ҒGuiҟBmZds\;;wG#mcR~puc^OhΐctrLfKAUEOU"Oҳt@Q6ƪ>  s2-V#eF'wjF}+LlpIk {:Ԣ!JfS񙈩IYdqϤzdW_S]8:i\o=چ am6f 2^a* 9 K Tㆤ'AN hD 02v~߷(T;A"O`%g %"ҨmDhrugNMl0I:?-a֦ V*:bEL-6QꈄԊ+"E/VVH.ZIߑ1)cv.Qt;Tn#LjRJ1"$E|[vg1;nOSفru=cȝ7e5]ہ.P,ct8KdzfeoU؋}JArL$3_ )|fI̓uzkhx,a/G8<Y/yx s4Ef8-1:@ a><dC/ VB"蹙lT,t/((E"*Qq' E 3H.1k zsCx8(|c(HBv`^8Bv[/ D}{%=y_@ShklqNTikHy`İP2|: 4 6LL7QE~%wam_2R ( i-˞3苅ˑ ZloϊѯΜLNeT86+8PʬZF .ϧ3B[*p3 I`J`sY02lE G;1}U "F٘g [#+0g2 #<KH=UG:ownk<6 7amb(g[ /'mN쇞[tJBʓi^[%i[C鸞 ;y! /yeeWwGA4lNjݣfQVst65s[tKحz^cǹ!9Gkw:LiUNtQ:Uվ| ;VWUZ`rl'֜Ot][%0Рc4-el{:HvLt |Hi?}?÷_c=zGCi:1!8nöpizã?#I?å)@v5=*?W}j-2ZdJ9X d B JoY Pq'1m m)Iȷ>~.>\0dص/^Xd*Fwy¦dZ<0FZ 17a*alf}~K5BPځT b0\9:О\p?Q$Ǔ 鷤\8/լ`Tl%z4ї[NeL  enRgJ\jZ :ZFsa#PRZeEYi:^^ J<AC pom.oH~tNTV&Zt!"n v(xx,@?/9Fwf׽]Q3]f] 9 PaL-ӱ/wLHSHQ?Un%g+OCqiGGHUIE4d[d:pe.FOU [sAy?`!sSn-\k@v[7 O׆.wy4܍5'M3:N!050ϟ-87nVSIHd?^lzTBM |\A,xT5<%V{Je{1\ DiZ'W}'bX%aJXʚGfZYN&#ctFU` ,R4۠Us1(pk[2EATWl1'yZe{$h4MSA4'b13V@J{ Pm#&FZRc<eenﶞ5BѬ=Ll~>2Viµ wyxZ+Z{0 \2SS!GB܈'SGb  &=HRmy)O{fqPMllA2 ߕ5ǐT'.AMGq'QQ vwۗ[kv6iNLR]_>#:<5Bh qd6}]!JjܨRGW+kt&{/B, ifܫs{{hA~pWE0kt{}c Yj& NFݫY jHYqkp+<z17fSI#kG3@y>P!mCrjJ fa7ͣ [3X=|`қߋm(ju9w%}4<wAXV3ŃDy4H /v-aP\%Là %2Ov1=٘s)?Nا>-prރ'ops%(y$SFwK${1׺>w-4 |4ҫ(b8)o 0|lQS\+&*u9"eܟ&Ȍ{X/e\f)}|5j̈mxêDCIH'-GKXb\4nଛpgc^,"ǭLO F7u EVZ2c],~$xuv|Eh(>R4]?S8s*Bƴ DӕFN+|q8ԟcPJb7%&i4Y;#abQpΛS%gF1P|6K]s9sO/h StrvU g*IXhK!F&PPҞjA.~vpCGu;mFW 7Q,zɲ;dVx{u mGm)&Lm0<mb7Biz-m뵷j"!-Ge8RNyjܠ/]9 ťQu(sW"aK3e\Wx3>^N~)ҼzFI[bb#(/#4U;WE h c"` zFur2˴rψkDL7Nȡ2p4 1ID&elMF@\Mh t;]KnXUbps8jҪ#Uov W%.3IJ9rբ7cAK3 !817@EtdU\ (=4IUR*=A6og]=A' .uI½-=Gqι˅R{cXR7$'azR<%`Q{]J %?[l` Bjw6k)aMf=&tA]cs25#wܥRX7U;{RǁCQ m`[ C?2VUүcEr$AWѨ8"fΘFY >㼠 7?>G9xӉj駾{ϾHp߱Jw4?尥 -PiT&%NNGn!0cJǷ#[VSםVtebvp/T>@$M򻻭qwYK;>36O8;R駴h}hY3וca4Z-Ozl#] /PAq{j)NTv3s}BcƉF}2V}gJ#[1W٥sk6ǣbNID 1QPᘮw9z&~XxwMz^)`nRbk+2(1~AIhԻ h$GYc5OU"0|{ȧD)j= 윜OT~:x)R+5jţqdl1Mf:bW]S;{ѻ*%[’d[˅tXھ.:{m/nOfҵ'@Vֲ wE@}ޛ\. 3B3%K? V t&Hh[%pb6mJpksωl1qp317bgF@*! d|;a &;#!l۱mv΂e s{qp'sWӷߝ2NuL?. Mƈx%˦v1q5ΰ}oxU3S_Hk2 [`Ted𮥱r'E/i SZ2fjz9_604QlD #WyL&usI`2.\х`DJ{?0q X!K&q$Fuu1.>.nSMXwa `چԒᛂԷ!bvwi mifأR`錄;c!u N7۸c_^g&iG|g7$ st_Hd @6p`xӦ?acs%qt^{0mְ ?.WaJ}H*܊gܱ.^2z[ucA9'ӓCJk?U`(ǁuxtȦҪVZyfow*+U9>X9OlXY. 9B6 RmUSЇdRSL1 HeAx|P~㝭$JFe%}kEԵe>QAS3[PSE̥q@EEk"P}Ŷ=84Sk!`Ǻm.eفXh|/wh#Dp)`րLv>EDgggciׅKi0a>B^f"$޴s D"w Q8jVNu7LsleG*Q\cM˚jdgx$F j$?RR״G3 1PP@ ܆nH4x1Pӏ= xxQZlZK(}w\Őr6ZA* )S Ill(8j_"KS3NL77jvƓ`K-&NjzXnhF{uENP5^E"i@G յk_I 6.8B Keh}oE{s& @ѐ`,Vb=;6GjR5v:ʦ7s6E ?xFpUS!4=RNʼn7鶝,\Fe^B xq#>io~~)WUH gͳ=LR<\-]-םqW4\@ȍb5s(9 E#.3@ cG&pmG\ǻ,-G51|tN5N:?z gfMZ)JUIqUDm6'5~ИU 4 {w{=P^Wo-6LNl۠5gqKPG襧NOGK`Cc!2?j8¬e 2K+U؍h?~uR&ಶ~N[:(qͶû\ח@na7F*oOKFz$F,a\u)Y`&g߉/& Z^Q彨*B?3CApYv/L S1 3OgT,!OoPOl(>WP=8툎_\H߰D8h;8[A4,7`Gbjha9z]$LNq$Mۖ+I~|`cc)48Z%/v nO7BElrAExM2p3_:^O! Y ~۠5Tľ{ЖoK ֍RHUe̸@8yY S=q faA:-ܦum>n ZY.=뇅>-wI_\B -(CcKR$0BL)qc`qآdF2* B"Goo֎(PW:r%Qt?3gf(db%7c`K ,7.5!3ʌ men1KK6B'Dw褵|rԉ٦B}K8ɥ .rKz;LGGڃwY%*S:k$->Vԟn8~oOA$›X04V`/0hdi; L['D+_; 34.uOK\DU]Uz-P' +`=b@y[Ix(/$w(LDfvOK~uJ|KQw0D~1_Nz ~aN~3ý nv͘:P >u '7K|:/ J~«]3hTTp^yt*E5Z3xvMU"csvA_U8ݎ>մ4HԸf%_ nWo. @h(3ui1kr}d;'Oޒ0-bZ?5 ? (G})W]g$bh;I3Pߛ؉Be]z㍱XkvW܎"KZ$ 9!JF 0Ɨ `-xp9RX7/S" ],8>f'gM8m@#I_C 1Su5MOR(}&ĸ e# Eo隰n3o_LzL>W@[ xmחU SpFU#U*({+b @lGCrrp 0ȹf/~(JOc$$n9٠HS?O$x C1.b2wS#2uN_>ܵ_`ᣠ~=a_ݜex@.~P FƵ9V%\)/u7R0Q@8MEX58jw:ZX<M}MY /d1n6OIkmM>cb.I^zľadk{ .'[t:o>Z|"TȘU:܀psdg=′-Ʀ̫8 Ksߗ~~~+$/"&d3vօ;B4^VzvU1m"J&^*i\ѳBSN%'FHMD̐*e Q"r8 RUKOm-v!'; QWOp|hZt:| |^oUNW(:f/ɔɲb>r(^л Ve:ǷObmF F0# ~@jg3_".S ]o7kq߱pF[ww؁K}=D,'#$3;{u&VJfk;-~M KZe@JTC"?TLz1 l'4{ܲaxAs.NJd+'^EW~2ANY60ר?͡}ۻ;OR3Qci>_Q)-<"48QKd>^x 61t]+`#SrHt릟Nq ?@{1|6|ds$zGIehb/G13g4}!:T s)0p29۱cHs.NA i_{iM0TV*5!i9OK$r.ɀ1dʔ=rLN(싿M)r&RKe`}A7sAN\!>vjėmG͈tW3@JFҚYY5u!?jz)1`jUZv.I>kAYh`Wg|٬|OU UFK𳅭˛T.1ch0?1h\cN+])ycFu^6Xp)):awAʕG3P$FaL#ymـa :i %Sc!2yM5T `d' |tx92 yݚ{=I@^/)oMP0q$<=bӻ Օ"CD[UߢUD9;BfmJaײ%^`KW*bk88Џq qNܼC')cZuOvJ "*LҹImRkY g٧(iP2Qb5Tx#.hp^I Zڂ9 >I7߬?Dkjw@ɂY47y,ni sbvO>kWM-KWۨD8]? ~A|U W7&nNBHt QhB(A=kc􍖊Rxcon%40e|W_wYf<%-P3`؂́u/X)1-򔼠~+0TZ6Tm- `IeˇF"!{π5=ncrG3j.SaSE(+zzF.%js" <y@u=$qXњϜ|~^ma r|WYj6ySH-E\mDv8 [g%k J$5F=pD<{|+̧fq8a#A% ouZ tč $vwp=İp7EQ\K+qtA`u[ ǝЛFu"Ͽ1Lj^ o$Uq%,%+d~(@?šDG_A_m~CXˢN@1,܋=)ymTj̏UPPy9l!$A."Ww^&jkA0Hop' 0B@X VS Ii.8 =ݔq@D^8( rĬyp"kOU ֵxNYI=t>3xvCXr2gByS*Av]2Vi-[lk>%o XY#¿hln:$XZ!.kTevQsܔOx՜cu8FJz6 I@J?bd1D_SVF55rK. [$o5XIȣ4$ GT,=әO.YΏdĭdY[ȼjzcϤ#" PFVcTAĸѧ*9TK+o:[TU:m.[.e' F%>۩CQ.um-,3YvO/bU#gL`a*@8IWWkσ|&mͫFy7!^^$V;$%|8-yeuw|2 )lfr6VIT yaڛG]]KUon8r5c~TkEF WڌjH du KԦT%Ϩp<43Un]Et@G4^9ppTj^`6\dJ]a0&2A#' x18ڡ82jzA@+a]?:V씚 dM[ZT5cx#@ 0/1YL4#h}^b=ѯTuQ#3@6%j߮n!0>iL1U=(IzA5`BR)E\KE[|q}=^6@|@ MRՃ_k,zBw"Y%~˦$nyy%tAuJUs:έea<럇i X´om4z8w[xϫ| #>mxA'q3OU-M\>鏭.w rIg:{v lJWBǨݵ-٤ kgnv+O}'dtypxzG2j U\?}^MFtv$Nym#Bcw Y桽@t W~72@~T#$BKFHD5@ ~"0C=*!v4nl]R#8bg%ՌZ޽UIiAHd8\xvcZO>@@on/iL̀/=PӲQhU/%WrAcbpH,5T$:tpW"oc$9r߶'µC#ֶ;fa֨iK )WUgW%p(e% ԳQ y#iyB[y -4!zSW !?ܚYʋ Pi s*]20"hҰvc=ȂIiAxک}4|y]a9FڡkD1߃{h/?}$/\=UvGuAI y) #qH@.NKяٍp0"{"z48 TGplHѝmzSl)A_*O̧^ӑKh=GJnɇXEE99/S033lVm"HTxEe4mBp–?ݶsghanũ/#=V'4S 1=Bd)|ZC֜*"Pt$Y8و 곛"~alr{17j.NϽdsqڎ"Zͫ\{mZEZګٽjU{SfN"sn?p$j+j{#B} ~¼yW8t?-]H׌I25GIRw2ĉgœu:g/*;xx@ؒjD 7I % QGxrX@JMŮIO( m%O},t OTS`O. bSC~f')G N鍕(SkKT;5Be$Uʸ]! 1 9bm6eY)Lp wvXb rxXg*M%+6v h|T=Hd&UQi@f,=†&d1jx%Zغ3kr .o~dS{CXi\ԶoB )R|L&5y,44SDCbm LG8wZ&㋝`dNf<DRDf xcnA`1"f"ncBǒ}d[X"(+Zp'ӟN! ~I]{)e)k:mCl4@ 9\]Ǿ*5A"dъ_ u۪:Ϊt'T I@nÂ՘Y h@mF]ә ֚|b}c2aU?=LY.&H6T.6H4zѐn'w4ye|sǫ͵߉X[{`F%?4 c\G"K&f*Ou^f374|~ b_^5Z"Du'zlssC ji_I@~x}|<(lVz~ԅe!LiV0]uzZ ~}/wKp,[>C O- 1CeD{hQir \í4J]d~'bU)|J{=*5N?H+Faru% :w_U "+`NbWb3_l#M S9e:7&p"0Qb)Ar_:*ĤX7pdY (HI*'Om!}xZ"*l= ͶLay(SIuH| k.s?RwXwU1] f13VAb%EYvQ_HL|2b x"״>85̑&] 3qqO>XhNM?TrԺ[ø*0bu7>OaZvlf%|g+W/dQROC]g88.ߞBۘ)ph [u% >Em++є>Hǥ.>)P- \;Qg`WfHKַi|}am{ ϋVs75+)V9 C']Ugl!WENYa5\DڦT% Mo/BwgMyb0td ז[i?5²o]F!3|Hk[S, YmjΦEEmI=s>XJ'+ #uP1xO `!US$O)ͳrQG\eyRktSumU(%'C+>^dц`Fʹ6k?r3 'zbޟpul kUd:gIn}o:owݪ!.]Sf.Ng4 Rn$QR,2<zsl1#9 >k|xJGw.wcy݂׹< 0g,+x!dYsVva3JEYw5~rD|L8vg![|Lb 7j*iv3Ms2b~,*-y\pf)s4uYVeS;qx@6* 3}hGoH">'[ mwH5-IWpE$COe )×8⩡FHBe%i[rվ@9}p?" y mTF_KzfQu.)5Ôq=x  !x1T,72'ej͗[Gry*1*W{VؒMpN"MΪ$?ޭgSr8YΐLN=WTb;0 ٶ#`JQёNba Y}h'(I)~,$I;̢ HK)C]Yj7sp19\h,Ϡ?IdWhM [k{(^y-7u>eOB-B60U $ NNnD Jih@IeC$ b shG+z"e% W%s&氾sG+ ~OڦF a fx*k;{($v΋r>`f3-#RKpNm."5uº)1x R#t6L'X `.œo( 7nK?~ے&ZɱSU c]C֊2`I*Ӽul.4מViC3I}ըu`2zăANf _M~BuJK4ѭ1֯b}Q2HciXW p蚂cv:_FQhIHJ=*26J.sNf|vs Yd6 9?t ٥{]AScXe!;VƿWT63 WwjC=bȟ"UJ/\ue=p| 8@w aqSCHګҽsKZPt}zwX F݁d7Ј˕GX_*9]hNlD;}4# מh.{}OFjNi:l `b 98}dhk:F3[ˡR1i!mW[Pl";P؅0 "4,zیiF/M:;j[ dzFޭ.?/"Yޗ?X JwT5fg~tI5z9̕hyԙSbU fPLlgTz䱌p({M2T'2@"SdiLCC_oxbtԑHī85<wZ1]Kƿ [R-Ī86>Dѡܢ"S 5;-HgA4Rm^aU@iЇn)^k˜0/ՠm\z lyoVcYTY@*/u]$ϓ'fhѤ5 LG[@mKLJ-rP lw7V)*ܶjAPaȒ>Lp LJAtL;{YBIOВ@aYwoy w$I%V F ]\芼'3 N}w]$ui&n%tD9vdp,`Jp)[`Zu P*ZG*d_8>沙3\E"%IoBe멟U|/{`~)sok"OuHF3zާxBA4H5YpC P&jTamm VWpϮ4e"x;$vi.?+ e]<Y#LAǶ2m1 ͮMv̢07/< ޺'`=w/kLIc]"c"m_ˬ*8@e]KxѲlAnۊ=A{|__Pka:"wKyYU{õ k26TJ9n}@B:ә''SHss/A׳nA~Eb.)L?lR2^ ˒cK;jlyAt%ךzʤa/Kݧ,7l͝oOs=Ŧ:4`1}<> G0ŭ볽3DQGMFyG`sD1t~eR=wB\ k0Ο7h(Oz*CϒN =ݶVJ4hW20 S~L' fb6,/Uvy5Ka%[z|/J`Y> %S?dȂ^$;W]3Ӏ۸QǧkG;+ȏ>v0ՆKIH 97DN|!pmY sm`B$ ~ MސYC4)Y'L&o5RJ # Vj?ZIauVH t*ҏ.QnB tR,SWWUutm|8PXL ~XG Ւ1ڿkr"pNlXN6>aD20l6P!tB4^) ,_äX>܆gK'VD^Nj :]&8.t4Tۄ1?5hrl.KK%T>#i* אv~*]}9 .,1L65i%x]8yvx yf]huu5YuVn^RPtG=>RIe\zA5L ү}դX>G RZRdV%t-a!fzY\QileMxz@M*78/{) |j@Y S{Դ$ba` /T.ݵK evq&1ӊ?Bth]C\bk U-5f- syzD&Q 6cb7$|f ʮ)X`^J1^ cj[91-͞ĺH\so{.=o> *9> m&ljŦ!kmA$Z(%I_6h>(?h$ QLW:cH:%4K w1]nO;A0;Rw0">2A*& ns9虏v)v(9_ ӕ+V?t˲e2/䌁v7$3Qfue嚒pmޜyL8UWڽ*Ѫ I/* 0vN5_G@S=uErP4K"843s٬_O9K E7A`ӯnD/} ɪ(nĠRoq4e#Ά;O0gFRhMbeY guk8uh``DUߧhw6#Tfy̤%SZSF`*)[xBJqK%|룪t|MVNwՋFc9G-y~㉲i)#zS}iN=ЀD5K5Tcj.I6(EU`\?W:G*蕟CZG\Q |a{fˉ"ՒwتUx[J" LB@NA3 .̿z 0R=4$tkYV1##k~m| _=wЖ.t=';XTy6m?S_ *>mS,U>vzT*b3aTCV%8'\@?hTW@9Ak#ŏp^h?j*'~8| :KWa^,4nNgnmEPMU6Y j>&!1vһűA12]- fe5kTdrv :J<NN8%úbM 5uWy]8fJՌL"P\b.xC5_&u̧)ѠXe$KvF: }lǔ}y0w=o/aĒWi${&'u;ýGu6d|A"ֻu;Ru1s0%B$dp?6Lk'XQu#\qrq+5P&b6raaPLWjH1щY / x'6%,;m'8]HGP/%(䍂DOwkb_UO(*NlJR% 9vZf%?t&pU6J~.fWJ75 +S|(%15J;HD$HTs&$.*nRP.{ K2kl;X>JwQ[LOTĺؼL4 3P;S"`gv}eHyB $7 SJj9%m2ݵVZ2 $T6 uՌ`,!iDm&;1M8k3RQ+ [+tT?X 0fODhYA=>fӧJ\D$hѝpS[zq)a9.W P %-l콓+uCfͥ+/Y-i%VQnSS) z?؇0p=Ij 27V`ۿJiy`KUz:lljRvLNftw=+QeJ+iԒ?|o?]vڟRYٽWCZ)F/hj\eh5ai>S,rj&2BD0DPB8cwOxNuU@3Ko?h%V1=ù\=&pr`C>cZg6tcMguBDF的a%.mF8@ٶ׮?"uɌcz_lnm 2iQp;;InAC D.ʡ/EZ@ {Ǜc̷_KCT"\#spZHPX|E(jJ[3lQoqjdvgaPwT^ ep \:_|_itduSҺ5'*MízzvQ$y 3wN9=4m! rgqE&0Ok\6a1 u-v;(^ |\|9qs[<=•O]9K xO{) W\Fc` T5Zh0~1'1t͛ExYJLCWc8&E ) x>\zoΎ >uP/FwHY= FA]c`T8M(/Y@›k(/8x&%=N$ l D*)ߡiP 7b G2tN|Z9&GZH $ ͼ_Z4ɫ5$<ѱW3E.C8= }"{ߓY;clTv_e>&y@mf's-"l@i9.FAffW "dJK(JRbA-UfPc\"~+ƭ܍L+^Z#gt%Gۭ]+| ,4lР"t4;*3$E[A[Ȑ@ =EH"R$O T7M~aY&X4"^YMOBDP蓋ʖ0\3 ցW6*cgykc9=E۞跧0M$J8/jVѦ*;agN /㨎XH!ˉHՖ9%Duv$\oF m^pl4:2H ulT˫Rΰ:4&, "m[clwpJGbOۢ CJDRI"_&%5S.2iðT:̲/eZY a̽X`p qJ#P̪P_0(5W,3C.Z|H"#_*g}}#+Pa(pcx G[(Md>~r}vm'IIgakJ[ m$:@DUC|CeK̸fhC1`tNO*Q) {O]"n_y;Tq$&vMbQmtf8Y=u;shHN{P)llpmPABaddwz{5LcptrAh j*b?7~@1FG7B)qv6H6t (= J݈>a\H|+&b9C5!jpg >2n02>8`c ~t<šu|k|ؙnP Eٺ>;^hCg!-`)j 0ןIn1>c\ w5?e^"4iUo^Ǽr SȚ砕/LfK_{0S╼X9^N9Q@\,¥e"Ĵ<Y|yP $zEN O*n3!tV"({|EȀi/Joeϳ̪)MWv؉/s]MWآfu&DUz?rV[kq?Xyr2.ܾ@vaW OYr]EϺ+=ߍzܭ&l8M5 pVIH@7wFВ]mI/KӰ+_5 < r*[aE܍i?ULp@YVfСo P5.qGBoİ=U%ic4`8>29ݒT6 8"MFC'*Y&,1Ml2?Av;XG;=Fvm+nМ}9,kGq/1Jk@mHu@ |ڀb^.4ꀙ8frNX`>vzMȘ#/L;E3[k{?V)[XBs!U Gz-˴zRqT$, ƪn* 緝*pr\pPlY? ^[˺54(RM`{VH<L26Ks ?k)9?Mq?"[1Fbب4 kΤ M\_ 7ۏ,(:s܃HC>zs_waΉ+յ-.F'4g,M|YY;=i~͞gA׌;R+cx!,dl}(./Fz~_TH-MuK/ND;x-(bHexZ [&˿3SyAbQ`՚c7$D;WP [™P8glkzmː6 38!Ŋʗ벽}Π]"KkG5SP~n/W $sT;RPYLbrGz,^.,$dV-qUǨR'KJdIP m~f**[O(3VK_J]śzt׳~ߚB p`<޸bc͑eOx4K^"dr| _ ]O}NQ&* D+D{}ҢRb DJkLۜPԅdjEC[@7gO*֕ Ż`.cKO&ڀӤ̧y>{Ksp(Ĭ7_hz Z lYl^׻ ;g:ZkX^R&>F_ QϻNbFqqm= ~V$2ɀl;SJk5!u뽍Ҍ=?U:izfK}ɢw:"`J-hm~9_XeHVGЕʶ:P&A\u؝%<16@; *4 QOYwuɕr3Y1r ˇC`Pf!9ѕE N{/hC\j-"ȻjC IcpȧSfJ8]U/baMﴂsjo傩<n=zS=VCK}!23uG:nRǮ2= bU!Qd8J̎Lڧ߄SKKgdGbcV s 2ˎ, o} 1]E@^-cC*N:jmb-ASëm@5j5DmgBu0KwB@TvuRq?w].)>sR{fI2Z^^miB6ƅGx7e7 lD \h Θug/oH\I㺣VV9hdQȑg,P`55"x3Nc3v}]u`&I(Ƨ]/)Vv'D ŚFLI3}I0 b*R @L2^61/4V)>O&?&es%Œ"|tzEoWG UT 8y/Q3XLYFlZy.$^(a渉)ufpV3?e Oi b~V%\c? '(6"_,NjA'CMF0K HeF^)a0w)5FUDYiw_c% aP+'DG,"3[~,M$d??o1]G SCO59ryI }IwR5JO7Ŏ֋gkN2*{Y7d)K Tm{@ c{,uEH gԑ;)h;7ѻZi; gK|whRt#ބ|2V(jU#nI \;&ηvC:U;:d86yԐr-6{ %z^[vA)kc4`@Fg.r{h@|іY1$d|pnc_ sn֫6t'\(]a"e#2*P<4 ?zӛ/itL#--xvJ-@!"_䜡*S{z5E쓔 b_+2[k\ 3U\_-pNpxy{FE4:+1AaN`?ksJQ~h¯4*jUdG lǤCԑta3:ʗ#:&y:H{+eHa&=RCqWoU2g'$݄bd1H":[ _Lq.jO0Mr4>XW_[4MlkI}Vx҃ hYK[I'$ШQdRJ ՘GJgf1eP\Z|Apd0qQ"?rk$Rr?L*,¸U"rع8a./~ C%5r-&GYY.$ 2LhSQ!W=>G[Ɩ/g7qe9PD%ld=3u0`mOEp#}B*;D}o q4zfHp^D${jaļ@Gccs9>_vC Ti;s`A\{]B"eX` [9j iPM";&d˲8{FN&Zt)bBSuzU M+Tw*ܜ}̪I vG#Sr R*Od;kKxϭţѼDB`a ޝpϽ.(`KG [\i?*Ur zAu擫Ĺ[]}N2S B{r_~6}viߏT'݅Ȩ}7HDžHF BfSCN(˱v^7˰8@*=7|{>| z3f#X'rBAjh\m OoU览4eys>샖ӟk~N]-ӉSL Pzyf0L̹b͏ g\g,ڇ;vz3JR(/oݯHΊGcHM[DO0zP#G P2zGیL.^nGV }}nձs)HcR,ta."2fHaZc}`<̡|cbև2PkDMB$ .y7'Q~B ׁ2r ,2aJ )S&ULC ձ Ts8H:rL$EV` ֽ7`fَQ8&^GEJ8|2! :C?9ZkoB~D7敺7S. ogZP<Rfmx#2(K41|Z^eUWӍ`Xа7VQM\f}U1rK˷%[w,(h8%$cV1;":}+LD#ыmC9g\ٶ Qjh챠eJ5pr6.Ay6H)TAl%s5C4ms=!Yܚ<1lN8*˭q`@.`7̱,,#"sxJjM xGt;\6D:\΅ }G$i['.uBkYu*V";Oc2]@O%mi!5DhړoZ9gLZv|P@p\];t9#|Pgm(^6'k@t蟟 V' N]CI].̧&7$n7!- *of$q S閊{6x䳄 e o^#{9QiLʁ]x5S-kGV䳜[pDo4)U?FT0yϪ#_))sB3!'M+ɂD/&Ϻ?x2}P2q\ЦW#`Ѣ3&Yz٩>z!K3 3;FnKM,4Q-' Nmmس%)T-$Rz8۫f#Dl{=>a?Jء__1.[eS4>rtyw0b=Ā"mn &h5 X5N k$#XM;APn1W Oq~Rkiq 9% (i@)I:b5``d^)LR&f?EG`%PĨƐOA3N) ŋ`/S(562&vgU-΋,3ѝ뼦{cU< !rfnȋM2RGך7X o|Z冶=<QLz}4o~)wfpjY'":hYiWfY AL+L-L~j3}%-Sqh̻3ªLl(tO)e,[ҟT -KOݸ;ģ!RIN% -%JsPO^iWio$O+[}''O1p b|+G9Бq`{^(\4=_=$ ZXF H _>%"T3=v?t<4ii]]LcɵM+R¿-*%1?nqQ|)c$w*y1s! zgT \U?v:bbd \ OO/l_-NU~ODU(?vVC=[s~b!Yy*u~9ygqV$lM&~}!OAKfghjT\[o}Ne\?yibSsuD4:*ӫ] -7 jZ9:?HGe>s Zk~ӎٹjûyL6ą~J{ ]pU#[itdPNnLy3 (fY/,9š\ ] 2hO^~} Aɴ 2\JW?rN*: o#`H*"0%,a`WM <-sSqYy3<}Մ>b<ʆ*NEAKBCUcǂћeՌcQ#_b~$r(X?=O I N`pWEn&&\'(n:tp4Q,?1%RQ}B – f$$`:ɓ_ 6~5S5ܾ8XH+n1S`!REa*2|}'s?}b WgRvm-X[ SK<ɷxa) cm슇tsYbӐ,L>&cJ LAfGq ;겊uܡ؃N.=-ܨ|J9ql2&jƝϋT gV1 _?:8 34vL&+R'dGQU2 gT90 *grz1>}|XwmZrlxOO}&D޲S D>p)vfyܨ3ǭ hylYf4|8z?&KrA;{{Kuo;*1\ߤZDSwZw6ॕ(X̦'$dxBR+]a.ʶ$v2Lk/`G+#8H.xJ~yRf!z&CPov` JAhs,ŻY!Τe (y5={m&GG5E#'954hi9bPtm~W~ .:%1m52j ӟ#o reh#ܛCt Rӈ|/HkQ#c_"OǵAyU8۰ybQtZI0_V9.Z2~RNJGٳӞ%2]w`:Iv*<XA`&&]Rᢀegŕh iK cw(FqqKUܧޅz ՟ȯݔن;yE]xT6\#d5߀[n}1Z[S =O;09xYNj 烬 Uk_*N[I7f(ՌmFBAl&vfpN <1(;^eyϷ|X!:l,!v<6mjv3w6:0k =$a^q\c_9XB]Z^BGE&+gY/j# iCfP s]4oHx!j~\殝F OcZc3D|b*'W\jqtј$.@,*a.}^i#-wAWcYQJ< s 'AN=qrY̘i$rx M_ywEY_/$~J"|G6SұLjY$ٱP>MDuq YiI҆J,4R]N[R`@7eoBY[2gK3%lt tX`Ԉ xߞߟyeoͩJҚtk?M /5W_KWԬLR.8Pr~u.Ҍa9ւ Fc" y6bh'~/J$N k i[1kh3O w_ d-ӂK',j ZpK,lWv ZSN+d0οo(Ჹlر3o+$2[ݩn%osNܰX+-9zÃG^BZ>YI_9h 栂>=7%ypt~KNirAcy>*iј3R@fu}{a{z"{gJ!R+3mTv^XZ *.W]2.<T#Am1bOQ2a$?gjs,F))RVfq$-\U*e?ro_3twB E=SZ:M@l7<$8e?}}j)t":Җ0ަ KeĺUЕHЇ5vDJ˞2OC sXxq6Gf{ U\",o u 0ZqA|$;hgpӚX9O+63$ `yk-`1_CgKG^XDQT?5›4p Bڰ~#t枔!X`П߯lBw2"|02~x/WyCKOD3ili" sL lw.]ҎcS[=%;-`)s;i7}䳄y]J)PE њ!+q/w݇m;I(՞0Sw!G+ڽlVg13Ƨů >H'`U̎KrUNn& VC\kT3! #'Us'Ejk,d>.0(ȧ;9y/I-j3J4:.uw/Hҁ,: _dBu? NxdLF=z) X;E'8K 8O0`,1jcppb$+[h Z1\! 5l:Ih.'9{AFbܜR.4B_3v=1ЏOPtY)pp@E ~Ƞꀁ8PAw3NHUPH|ՏR2`ޡ!e(coBd!M1{ʬ}Hh:jgoCxLM#c>y T|jn' 6) wsߝA̐*XA,'g"Td|klD2Ba_:A.Qbܙ/RJ9x=^B{\-ƴvG6NP.^Ꟙجڰ*{2?Ȇ-%qZ2,Q,'zH=Nv[s`jGцT~FXlGC[ovͩ:lzQibSO[b$fs3-}|e'ONk sJ?MXHDD١-]GuLǃpS->r~w;">K9Epf@'|gl}4i шXؑeWnk}aFbq`M;*F:jc3{e?֑@ڋh57"PD=RY{qo K!*a`A03o3/Y7@/ñq~Bql]v9Jt6u2]k0QWtxN&j%haхs ҆7B_Q7"BBFA_{PE_O1 !C1Hq^២W^+Ɵ=)P~"'pΪ"H]:\۷4z_)׵(ݏVyˡFB? wj|G&teS+F;ǗJͰ>PP=޶-p>jd{e a2Kg j"[c56"'Zju>s.㎉߫7GLoE(/ &`<&ʮf](`uι"? 0kMEum"djCΧtANjvtdzDfakApE5_T!2~ JŴ&pd!%-h:J&+JyD=Q\#•;PI~(-F59cu>I]NѺ$X$D\#B;_4\Z@w+dnIKMO8Ҝ۽WS o] "k]뜜-XeRu-LЩsW`&#"S|Qi၆gd˺,{V튻 heϼ& [ kv?|%7>=SrzL'YwDɻZ{h+\6%#сe uj} C|cLs^Kp:%'u:*Q+yp00 I)"0FLSSDu6m+zvbI|9~+v'2@}@oNh~ =LѽIr̬ &2\H39ϸܨ- -b@ar0vP| 6xH_:bmվw-lDlvyÇ, vS(Ҩ /CjY]\Dok{();0E7?S6 )J{5OuNR޿ٴۘ?tUW_xJw.v؊uBI2{p.=b QN!p} >8QI:P*Jz/ j?SpdWDYz_[z+֔1@AbCv Jo ojX{oޣlqTx`WQ̂ktӫWi $dqdRF?+k eSLmuvG|7/'G5Jo§/=q?E0,CYZT|@HA52V\:21JG^X/j3i$0 ZKZ,g-CtSaJ=HVTKew :*I"Pl Bx+"Xw bW bl@TN3K']'$QpG@ChPot) I: &VkwStd+Qu)'ЦR\]&8]uGK'lL)G@:=`jQ -^N"5MbA& Apܤ\Dmhi"q` sFdqs'@̿ 6s,sS/b܂,jN%/uw9,2v$GB\uVɈ)Xm%VWqYEAPh Gy,Wo>h?lo5 ^%5OgzMր,'0og?!6{9LA4 ioƍ裶{Ԅ &7,N})~֍/V*ik:ы[P<=ݝMϥ}^ZɥӰgfgR Fn*l- ?Y7/&h¼&,HgȠHK()J"+"498Y<q3Lv[]ԇj4=A!Z{wEfQRt8Ƃ&s q1# p^I3ޱFJRm]+҃;oHj 'xKʁq ^ܞp Abۢ.| e0B;vfQU%;ww1^qO utTZ" 9z]%)LԂQz?n|23|q-+=N4?yb.dw{BLxƢق,v07x0,I0oOѳw+2cBVP&3ǑGF 8P^gkl@srf,/IkmMˏCuϛAXx0=yhdK/l L Qִϯ:2ҫ,Mq|8s:pcQ',5u Q$a˭]dn{ɂWY@%X}pyD<&\jlAڮJ|~H`^ hr. 9J%ն&R]@}G~a0{/R_55'Bª]wE)WB,NNAb2چ:2l;Q1>g;8[W R; Z45u6S.>% U6nՙk P1,vj-?`a##q C.SgQ*''exPLӛpV8y=ܑ͟CHޯw,*~J%+}ٳmz&1jzzN/6faco GPJC;N%͆'2u,6c,'2r><ֵ_GUru%H,Cժ}+D{4r(r/z CPd;#C?_JykxFu9̻%Xa hc}m[hj7=IQ|zn^1fU/ V&)M (,iW'${/ᕵes_DCiUKuSy8pʍR\u`_֦UG(=O홲&![KAs@&@5Q"~b 1W>VG_UNI,εǢ~޸S7 PÎ/l&ŭ͢/޵Ҍ:E[z"0!% cX"S_+^ jbbK3| ."h7JYwL}(cر=0-9J3Kh;GRN3y٥,?kt}%~wdž`|l4q.$Ψ=otKU73SɿiW !z+x3O07:>8 3+0MtcB BɑDIPߍR(*Jg-JDkQNvoUN~ЫHb4,Hm˜lfm? ۪ҫ _RS eФן D@6EmR-A?~Jqdj#3G2)CX-@T/SoGi;H#l %٨Xj\Yv1`"F:s͐q4Re~6<-<% I8p!5mJEz19Bj8 9yǙҦz,0NW ,u˚ x Q]8Rjփ]7dl\N8,1@eT!6'j;t\e"?Bp hzy|FF'~^HSr-Х32=utu1Х{E*Ԥ鵀qkQU3I&UaVcS0hSNM(Љgjf5.^=KE{ݣ\!ZȂ#Ld(0M-W՛,}$U3/F$ ) r5Ǚ.0H;%M1\/~j72z8EY+DWY /s AP:= -(uͣJN*Lo)3L ~8_HL`Q =jLŚ$@F<N}l'[ש.>DN*N z^v(BЗV΃"z]܇*ކqFc_}QDﯗ>?v̌Bº.MBߘv17EOZvX% Y2cޡQ J|<ݔ&dSv7`zB<)(0(L8dQVGkl ,jr?i[d9/@˟BJCj_;ͫȌp3gZu $,aUi (#93K^,y+*eCg\&bZ e7 }4 w,JYC{^InL3|Ҩ+Sc:$ mylDq\!.3t&[0};G<Kp ;U& ",pn1z(YO0d#EhҜ'b v?1 \7Hiss1.X* eR |98vǏ;#} UWא7.[Wj )"jQzUr)#yn "۳&j,:I_ 7?lF2'm"Ro%1c_|;=w~V=JeS4 QfLE̕܉A@TR=Ao"ȶ3G }#b<࠮R<蘕4uh#CҺ9BEHծk-J>4ߥhؔ.Y,- SO0̩O-cjXٶp5xGGઆRns;*8u$=U.sCQȦ ߚ=^]l4/{7'H~d>4qZ*ed=s5'lnmBgͱX<>@ltjh7_pPefjuSWgH׍QSuրáGNhj,/hJm_$3rCrڃd!&AFUpAXq/N'BnvMWz%&F[vZ-,D%OkvB_vߊPS0tu,=`|e?tD6$Vb$=6L֙.PM(Q~џ1{KUu*L*M#7ON\AJg)BMBrTqDbAkvHYS.bE @:i"+hJ%_h_ 6HW0TUSVzVd˜h02 RKueB:eG>gKPV[{Ue( lA\NW ET\.j19;UfM;sCĨ*a )gL xȫڲ袊ӰJHc+?!#zZH ~ mXNI*Nx4Mp{eQՖH-)\ب]AHoOK`Td[8L?J<**\r,uL֤~ Գ|݉,cN}u?|Y6 og.;H`=Hf&Sa)RI]>Me{JS iY %9*a|,eJaGg޸eff?.!$F =0h252lZVVvʘ'[oOY:dg/μЏ/x|uKL+)YӁnN}]tr'©SaƏ<{P/n6,WSI@w@|3+Y9.5 g̊ģ2a}] C[Z<'h~;D% L9LfMn{e ~tQ1cFt>LtXj vx 1 d^A6+p冋 dKb֙ߘbN2 uU F6XG`)Ųq=?Y ^ҸcDG|,ww #VfLS@Br*̋[vӸ(1ʛQ%:n{Rnp&6Pr\R"8~YxJ⎀5`^+퐑 /?Fć7XC _犂:6,IH)\LmUVK=|$焢2*̪R^2xw縲O$VBq'xk ! ^@XY‘;I90lDH3 \`CTePa妞@,=M2_/~LX=yI^8Sd 0;$p=Q2 ڏC>-jc1bW>S+$ǀCYڬ:P̛9 X3G3Cpj;ӛVYA$:59>Fn&љ6 Gcs[6`̿y9PT~5`pj{wDdpc/)z4w*C({red1zOߔer^ִ[h6-]c[2|PGu[XBnq#H_VpqfOBk ԣ`?VR3mKq/(&ēuݣ>6e <R9O-wV5b>@fu(k5ñtOb&vBT-.CUD?M٣t/q7L^|3 8 Z9v9T@Ef.Āsԇ22#I %q!_3U1(-8;;9D&zʴ{ܸ ~EO]І<6?^d~$7GeEeR*b;b0%y|W{ԝg" xE'^un&R͍Œ0AA֤]OH@"$H~xsyVP;{BסEfP-¦JС-"#+ѾJ}:V]p+ ݊Kmoĝ{j(2|kï˟e:*@'NxkC6@(@R.viFQfoSp7QY@H7&ikjZiD#̆uhϋ)P[ex/ˈJ_xNhtZ"[ *z D=VzN b FA_`+$zs$$ŀL̈a} w壃z,$n);N[v#P|%& ˊ8MA0Y}x߆õAXH&7MS3B4HƔiI=#)5Bwu jr-)ۉvN*ektc6 x|2k>mE*ĞFlQ249IF]n_ U>,o5>lu @Fh1rw[\}1mQ2墱MHgW:e|ז q!%^#CH#Mf2Gzb15*'&A(U?J-H25wi+m~&p!gxs%[crzA54ۋҋ &E=WJ4M5,ƛeob-jy6?1 jy._|b*Ʃw֊" q`pXNfh;iJ߱j]꬧wFUEݱAj4SG&p=]e} _ H҃=ؚqTBSyDzR (:ux֎59wFՌ@ru` [ $ҀUF:ۮFPJ~m@LQPÑO1wrZ4z FY[3LCBgrsė}έɸc<K\HQՐw/7w8bk0?yWB#V}]""LBfCzVƅuLZC,ʦ6d+ nk洄Rw70ia&9%w<`y` TF>X@㎮3uWoY>L%rO{5kJd9@ˢ=qq1: Һ~DRAH\*~>qocHlPM٭ģl#q-{^P2X=וTpR4aڧP`e]e /ުWAG 9 ę% ]Eߩ9!P-/ԮeXDܸYi0gs&R 9T߂- e{&/OC""S2 TNз`yy;tLoM Ձ%0Q8%ZV<O6mmDZ ڇ~yT s LTI5F 7з~07:iR h\Swo.ײ-i,%n;ZIwiwv\Wn lIʘU]oiLCȨb,t}._/C SO׎{ p?/Qԣ$7MzE\閁oU==t c&{2ex0ZSB >L_AZxo6ݮAv BPVip4{fwLbSkt_uwvkc{ 27Zc`?@$yHV& D_퐇xPUψZ3YaVp3C{(@vT68)iA38-mTdJ:(߯+2i$9Am@,~}P P%A~rض,%_|5ˏcB+m9 B:uw>pYnʍTFrj.J]F6/ޘpjE[f/xr(1gTOh.3cJ%=Jsl+}l3 ncZ9Kc'r+u`A zuٸb咒dHV~b A$dou8lj[#@)'J> DZ^O_QС7eTƲLD+ WGSnd.{iW!_~y\|DI?uh9E<1I (p4,OCWtNnX z^lƀȶ h cD[ɢFhg$gxcZK=iy<"!YR:݁61㒹 %l$^,n c!&.(taN،iDvm WEmU.,0M\!STQp"ah,BRSKcnʹT晐@> Gw% Ųuy_$HR%O ,MoޣzDF N_؊{MF&Ę9 Qq hAM*g w0p8EqD/r{+u |EE2]g-^vxƒ M_іO.'Zr>]Ex ᠚G)=*l_#5##b bkS"%@^2S7l,}~vxL}u ؅6="2vbJc*sp6|mخN Z!YTxk9ߟp!Ƭ VVleH}܊l˛:Ri{*TW| b,|Ds ZeVј[E 5̜* v?" ;sa|H򳠕ཨvJ~cjHo0INGiv)\)#~6O6`b"+v]. _ ym}oIl( ]E瞻[~2> e\P٬HQ/OzX 1Vʭ﹙O˖n&@Lҳ v3(Y%zk\t-̝EƹD _T]ma/zdv׊zR4Q&SXX~3Tx)K@ EX Vߜ[[ӠB0/=?ç4נF{KhA`Ix͑?lW;@h] ($Bp)3]]k(M߯Ә`'ZTi" mX}\)1n()'l".^]J"4쬂3B_nN:@ q AȦDPdu[2oFRdla#T`'*_b5r e\ 3wAZ59 Ydr<]f,x ~J?H/"nV%C@%@N})OY߾AlKg( 5+] ΂*Hg"z #6h}A/cF|Vn< vև+:-7Po]CmEJMaqsڋn3v qrѣN*htӲWY֠}oIst#O޸U#Z=S:3ߚ>@ŹgUE>M]0z4Fog$O"'Obv(鹪@v?c l FC͜eadr{+Bse`)vr,GI$_Vw?zK{0sL#6-{}M4{tAǢ]lu(7Ka"Cpڋ uP ߐ&r{Cbb/yaP!W37uIr4clz4A5C%A+QDpVhyQqc^0;d%6C3=/'-܆Ab`)WP2?i>Ӥh4곡W&j6agƲMFhb!Ԧ9ɔ~لH$My5BPQ?#-zw*9졪WV(#1~e}&e}LdYKyJǝ:}j ~|71|i5}H&RԯQEGt,z#Ƈ 139^CԎ2WH` |06&chәC/b79V^ 2V| wJ5j<,oF?@gm.0o,T nFhӭ3/cY&Y@ ;up7{I;p꽝q}ΝV­1ˀ8P{6$6L+1g>\)); x5_]AS!Jp#1 \x($mH!Ú6i\U:MK#c}bp)+]r B$7eMpc Esϖ^\qrB0 sZ4A4SD'SR;S]˭V7l` *Mri Ei'\UjXd7\D-::%y'tylg?2ԕOF)?#\L(7h|0HML;rL&a1 WDwnh.__7Q&!#rt&uX\#(m&oG RPo~1ޔ=c^.Lׄ`C f>[2u,$/b, jQDpDCVrYUv!3 - Dt/z c2iU l59GG"~oձ G0HR, PE]P2څۉx.linљ9yܷ@nѨRQnW~-[ZXL׀ X <,f&A,͉SlZyp]rX_Rj8f>ZWRt6Iǫܘb=,faԜ]&?6dɡ!4(?(iVaNP JE9wBr1\'\# 1&dR=Qik|s* lnP: zv[`Q}үwu6qB|x))Ocޅ﫳_t9kCК^C586L _/^%Ɓ!RX42\ ;[J< m5j3:@ N}kn#HUX""XZ9Hhl_[?,D_W[=r#, W&xTlKd}IҀfPn&F !j',vYŬg%`bRzQk Ca ðe?\"v)ˊzM<@`X'3kY*; ^mSc7dcomjY.q+ W!-ݨgi"!%&'Wh&Wg >;^IU}%#$}e"c&>L3fڰg声k%]•jdy)@O$(p"g3^\vTg#B=JԴHU[h@P_,l{.6t;-qՆ0Zշl KY©{M!Yَ(.ICݮjS/#KpMɗ{&ƅ3x*[6y]1sR#}h8Em{#-&Ќ Pq%-–L1bSݙ]!q)4C qXˠ$qte7I~?y5:N$&Dh=|Ƨq|qO!/*$JO[cQf}h⏘1΀<>C6[juOtPDSy'.d:-xc&?%M6|y{>g6rιH|K󒍴a3*eDɂY k"%0Gvia8\wVu0@Y ilJ0MQL?˧ԭ;5NM@ݯ<ڊk [#,!vH=6WW<;ls``& zcuԌO^ N_?4hݗVE\7?䪥6tued?OY|Z OHW)JϯV fupI ﲙ rۧ)kJWon Ku5AiVǘu1&l|by3Ϲt!vK/#H?YqT8&2#TfS= jǝ5AiG*}Rq] `ftJm25bڒRkj#:ΚCFty?g٧y5$HKR-MoS^>xh J?: (X!X3=Nr1萳usÖ=gjhR$v)~E;B8ٲM\÷eGØ,&$᝾*m8]2g`.^ H1䷣߾:1Mk><MMN[dJ+a^U7Uz9%&4vx!7ӂ":ww2Gͬ4\&fJ W V}v% D 0}GLLyI6:H7Νs jy)^\Jla_/5.c? RU$d1aW( !F`+ Xdy0m l`.C>܆`Fǔl);(C %{8:fѱ4(것O@XK"577vOb.RSSrW"FT@MMDTPDq7_ߑ-_'ckZ}eq:dHs_530pS\TcjqU?HN5wM(^|F&6I=zT'@{Y7fr?O<&5jv6b )~i頀:[%¡/ڬ-GGPP3ysc<_N([B mƹzq;z? a۶Ff pxY@Z˜ydQyp5J.z30{$E]~Gg{3vx! &_)/:f&[йb<@![ CYL=Kof-FdU$T=} `I)-y&(;r4,LOB(DA*Ce&RQ*KrտHHYĒQdzt3 3JWAؒ4qOo5rЋi1DfRNZOO|VQBxѢon4hBZpMQr3%fnYz_.C Xdp(kP^H(KRjJcˆn֙,p\8ٯ,J^^H^D"5/ oł}]vB G8Wݖ-$J)uf2Ѹ!k"$9`]zr:ew39:?j^15%k #jWf/)g=\vBR( vOTVevb-֞1\@N8I̕Q {@ roşɇXrUJ9^jۡ}IQC b9 m^_7 jgqɂ'3WCVLBeDO΅dԓ6D1B=[@ yYRlFma5,p)ԓi?b|)#?2(f"r:JZx3+OORjJqTD\bvvs^ǭ(Tv~a;11M'8`~,A@EAaYwCsx+ڙ1m3]B{)Dx8jԶ-Ll3Q|Ρ{6PkQsFpipD&2$E:Ll|'E΍WdS0g3lik}!K,iHϤGi8NYH\P" OD%IAAH"faK`;0=3F' k*<ϋUk/˽+wd٣IoD\."TG :Cwsc d+ 64Z:2?VDf$=^|".[@_c5"$ `@pQ1~Za h:^T@2@s dOCPRGMqaZRWҰЮ\ wz~^@嶆ȝ|f=tڋ ŕEg4$S*JS)0X{IaiBOJzY}fh5d!H=CR>JM嘻}ۜQzaG[6 B9)wChS9(<ug]ګ |?טu{:dmd4t԰PzWV<,Ck35J9~۵_32W665ZoUO/kmpt_?{ p ΛICJO'_/`yC鉥HWni)Rfv; |+K>Znm<|kR Ob!J#jz^_uNC97 %wkR6JBPA!–D\S+Uڈ^{ɻ njG=6A V)Q?!WJd!-uB\خE֌y5f>vD%UO9_&כ k5ngr(Kj%/p&jz*R, qsd&6l{EY ELl@-pd>&b4cIѩZjaM{[i$JfK(:ϥ]v3J1a$NqYࡁxkgCckk;ÉcTIR!ZNoճn Ymv>c,URLjغ[b@=͂xl^4o,$|cp+y[>Cxp"T^МzMezd5d ѩw)8NP3.~uL]YtH|W'0 Xm8.,ĘZr! R!GcGQRf-TUyF:[bt!WHtQ:{ddnSDC+M8 IW4.FViד>KPFV3& JbٷlGFgս =+P[LT LChڊ M'fy׉qp]^1 UHsfTq/䝊Y!ĐN)S]X ^J5˒.d_3-/pj VEbxnecBBBm$EE# nt!*Ъ 0}u=ƝzRDC?Į}5>%ЁA tRF#T_wꗯF ۙrtTva&MB4ZNa CMjvBueOb^9X:`/~sWY9Ku2%qzmhyU( S[:Uq,mz-cd0}Ĥ(&}*OIQu|Jp%r:H(ޟ7uw.'ݛއ#ȕj][cB)5azڃ.0nfoFG csKwH$^س/5bn ^gɰnt {:ށ߫Zu=Rm1_5BuL0iWgcv)u<ð.6i,7gmݭP?^k|Nv?gTk4G${ݴy$ntY_e/9APTE) PMB5Cd0gpd9t93^nڽ^Xi~U6l][O؈[]!Gil!IsW| gx[I%7+.Yq`KZ5d/AmM"D( Bۑ~STZwN ivC)}I!bfLOloު?~%>]zmj|Fs:?=56'@ af. bb^G_?Za@9utw:=ܥF=Eq/{Q\pոnhA= ,j?ָ XrP+وã,~1S _ ;va4P-\|}z?|UBK|xh[=z3l( ow2W< 4h/<qcX hU{JnŰJ)3nkdkm_B)Kn]e!uݾN"blZW0 M#iwk 1se&jM\ ݁S%R@z'4Tv·&2KucA${)6/L"$ҤŠPͺ326}wۢ`|~QޓcTU>tFFPȬoI$ﱯ71"2dy&nJEWiND ? jtM\XbEf2sp+s}LU@ʱawD< `vjkXkU 4qv 84SL8阀NsR_8$ T@Cf6=DIeza֪)tU%D`#5")x1ʩXDcr>Z ޶µybO|0})ܐ`n/C4Ƿ Gd;œE)^@*c}1qg{Ut4=*r.Wam4 gc6cة &awσdKy0:H6;x/H4춚qC( li>ALi[M蜻i5|5 C/hqW;XrkAO?'A+!AV(idO|{mD'd.I\*3Ov(*[X<&,z@_DVo˜DN<(@}5}Vpl{تɣN{{c4`sAWZƿαB:gH$vDFb^E*i^lB9t:`fnU{Rb7tMofTeL;ʅ9 f =Cˍ%_1 \thAn,ﰰ@ٛݪ >{jwc]ZN4€\a*̀ǼݟDq4uW\WϦs/VѣB=*|''hΣuG]~6FoM841+6]}ǰ{Ku/lw}_7X$N<$+ _`R_a?^ gwHmB׻Gw>v͖jr[#&|0%8 ;NAw*wkc֙XF?ՅCV(cuLHv[9B T.w*=ៈ)G$|,"P=DtaMRAR@R[WJ27 ^?ř:kÞ͡ %je ߪ=\1S*#M̱rݪ&}M!m*!M 'Ҧ1/"1 QibC눪X6!BheӼ3X憳0^d^,2m6^Poz}yoFX0D فޙ}%3;I{_0fl\`w QFemPXlp #df?JZ8S %"Πq1.2/ki!(K=_j'l Y`vYbXژNɗ2rARqZc: K(sZ)N&܄ YCS7Hg] wg&[YAe%sOw)6< i#][!pʈ1xC4M=fU~])@R\nP=d,F i>ސZY[1aJ|/򇫍r_9uvL;\^RH^&#h;?ʈsbBM.Ntԛ5quQgІ iZDբY\<#{spm T_2)A,Bz#[IjK#ǥ}2hVMT֝=pyj0䟟8E͂CV{o ]~_Wv0agI'Hqzc˽I@}nE4%r;lW)XeJ9S_nAJ nk^\Mxّ4nB`Y .Z^8dŌJX(.F,M.du"iw_*:}(⮷&|ɋf#C~ΎB+(r6R$2q$H[UTxE?`}X DXAg!OʱwWNWs+vܧص:&#D#TrGiYoweڊ; _ڲKmdqŒc)H9XƁ~sDV\#5h[qօ}0UݬwLj'ç;Z4~ƒ:,@ֈX02w~(;y2 S A\~' r m%5#g1B@sf*.D>8|aHBY{%Z5K$/dޔ r e'80KN^nA'fɤ)9+4 > P۸JBz~~JBI'[ 4[HH|>L/ :-,b!KN#!^ FmOlB=r`%;J,DL:٭UXf)>m׭-&=ޑ;W)!qg~H: 4+- s\.4yy\Kn')fFwР!XFvCr1m)ޱ9/WBu6Cl&*NO,r…yG"^x{H"ҏ[?dj{jX'gˆcȦzMzYM!щe\dFdRIƪJ! i~\Br'+T5l`ѝ:wnM2β &n;r?z/$w8_&ĽOdKv.f_*FոikmEZI2\ޏM#K?' *dȍQRf-El)}X_bpm2+ZӜXjCL71)f_ W}ۦ:0@OYW.2&XX nLtr b߆䒦MG7C!kC :QG5r`u>{2ZۜiUEI/`;v)"2I|C(ϟxa(I#] @7}lN2+WEiJL枖``d \}ο%d:/jO$.i4 b9 :!N1/vyi3;M֞$)/S߯ O}%/^l@D"mP]{0ny!΢%Tc6Oa}4mM NfRTUPa@ 1[Q*AfAʡ-;H==0Iry#igyJ{D v Ry#زvo}k0!g%/yAmD ϏƩEqZ5`!=+[tFn"sH^q`JoEZU_ w 8n 4僈=Xʣ(Ԗܮ=ߧu:% (BABE>tA-6 twV̉бH"ܗ!Q 6 4K 2b`(V?x]Hra(@¹qAƑT/[L_dg1:עo6bD;;8Wp>QHʃX&8WGV1: ThRe-̃7` X9(2lf+s^oy3.EUT}U]`(HM})5R~Gr #}a9]&IGeUE۾Lf'*I8q Xaa_A0@\jIK%'o f>.+)biܓWVؓƭZs@]ɹ fQ<,EC1x4?Z΄썦7|kQbȌܟA18cpZNoj+:>&Vrctsؤu4&X)蘿O3b:^wX\{ڰ(*QmDRa;ANץ^ytmùr 5e93tNvGOoD=_VR=3>!I/w{ǛZ*CCx7#|#zb IHPh*(MN{$d6/hJj3%»!9_#/܆q!ƒњEmOl=~70q{V |r\8Z<&NR/>ݓv aAx}jLnQMJv҂Č׏5qSv.k"慪W7`dskkЍFBZUk݁Ov&RbO" ?XEgsAkӁ^pUpmAcúgh^1؃']S?-{q֊cT*7 Z/52r(ڹ'R+^|뙃87\ Ou5KN'+m?յD֓e/l4gz_6Er^?ө~~ӧլR.B?:N(tR(6dQʞ޼+uc,S=ǕxDjC;G$ݯt8t}78)\If-i-UKxށ= {AEU ]!R'u{ ߅ ?gwse:ȿDz ],:[.?s\O¼Lȝ:vr}Qm厮I劒r|ȃHcQrv,va${AfH/]`LcH8g>׸ hص@>Jx7w\F2( Dd;Rn|\ś ,<=4؆xqU ,)d$ō)r?B۹X+p1@u3R[ ]16Hu_t>MTI a _x~*TyEe)'+"?⮥EfB[Xm;yAF8_ \r_RƸ%FPbmm+Ues-6q~av 3N"믬P1,9 }!&Ks3#k+ks_q1p3q㹁o W-uvdA[;_MCmG8妸^bpJq~cKB':]j6hm(]YR-ؼLx7yy8\,^Mf 7HWٝc 5 !%,,n0Q;a) ^)ǔ:FtZfew xZ *XLRt F*RbՆ$?xql/Tՙ[,mȪOo0e6=WqG{<݉.[ 禗p.X]b<LiZ09I(Z]stDߊiVk{.vrXj{Ad*6 Z.qJ2"~Pſ!L J%39m_t] :cjS"s~-%Vx]"sKo t3ũwᵳHIg XP~L'c!XfUZD:@/bƷR|b4PUg=BP50KʆZ+;#iNЧidp[2A-pٱȫE3*K_!YXiW<#(R`yE\yM_l0K M &AojhR- +/|Y~f_aBU޼= u` ܁2[ #Jap'5qP0sZq83Sy{fWT܇[nڵ-u/N@X cI;9YLIéi +(ЯTK$a,{׈2*rn]3]EYxfG `8 XYA]ϱYt8\X#{khЋ5`?9փ!K+nkGxmBDմ/n&x +|ḵl'a_y=3indCv4߃lęue }A^-З yR6C6p>OR_}D;/' /h6|5 [ROAZ u*m]=迒3$*tFZĨT;jtl-d</͢y ϛc?tJ sGB%O4,6~:w5^d6PX] S8a|YUyS8 gzВ1QG- 1J'Ί/C'N` @a/% ,u)Pz4Xd fE Ņ&-Nr]0*J:G;_'lF3ӫ`]T uM7d0JprAmJLk y}T .($ez 0 FKgLZor,Piʃ];w؅IjeeVvsȠQҌ\Rp\5βSÆ3!G pHĀn9ηh*yJo,Ah"bɣOB@X*yBQoxiTFh!)sdVOre;$3*^q)= $?NT v(d+BK%3-U~mݷLJ]uV,sb-[~ 8|4ⷳ~UhJu/[Cөz06dh2iZ(? ι'~RIaY>Yn0{*a?;) 8u>( ;3Y~q}ud|=-᪫WRXWH:|HH֔ ,Y8VmKLe^f~Rh|'~]un_z|ގ7ۥ|'pn ݊L߶)nEnOu%OiᲿgwta n򋇂1k`oV36"Ħ#%$Be|\26%|oi:+Y)Zb% V8 ]!-IM؍;hXB yUGrSXC MmF.YE{[֣>rmN"@[qM} P>0idce] QAѭyߜl1b!mCPLd>O6j슃¥ILd^h-0Fi2G#F-=$_1iҕ\$F'jrun!/(ڱDqP.'C%~+N|I-&B8:I^bD<94br= 桖6 ё~cɪ#%a4gvY @It*%tg,\ #*Os!O%w 8Jsk[>ca6 j| AB;jPd`ЍO?q$,w@r26G!?L%,9`=;8M}R͐jr}j!!,Lt.fd_ @ؽ8YPZ ;yŲ~V]{|x.95~;xT)a^YzYyI͒u!Ls{K[A'>xlu>eT:<4sjtj'It>Q9-D\o$eRiئ;m.oq,ZsT)ۿ98bq(Apv@"}@1@}oiǔ<,Lu}ߤ-9n,Žo~¶*6YM!w򍑨Ͻ|ņ`$貒Rڳ=GXo檌`9( M͟UB?ÀMVr)UTp@0JV#R @k 2! 26ՓH"ּ׺: yxD듂C^y*w}Wo>ьa>␃}.Ϻp 3CSwtI٫2*8)M dIT 5s}%S$ <N=5aJZUiX0؅\~}X&?7Q'WS2sDb{K,3ו>~Z\sYU Yt nt߻+wIZ)_.} % b?y2C[R6nd5ehFt|y\;DЮS u?7GTAM34!c T6jmqFy÷Se}OV촆كE"@^&pbi7Z0-"ʙ@-'Q4A"sUcCGDEdkq^?k{_|l@! |CΛ9~Bmp5yDʓ8#sJz0Cɷ7|F}#ma7#wu,Q{x( y{ѕ89YrG0ؖo?f.RƐɛL[ \CvRZƻ֓?:o_*oX/L5ԋҨib.@Ut92Ej.|jbт]a~u (,8$9]dLfxTؽ]%脙Elś?>P .kحwh/N  ;-.s7$$w١8wB>-h[ -gp2n%SlI/>rf3\ك mTB Y05>&5rctej 8ꕉ>++IukF2¸,R6{k#k"K Sb4~14wnTHo7ϟn 9Dat%̆´*3od=w!gL8xLmvk8FûGX8{|4#q؋eO.1a=r0E:[bO"}vG[ wi[ݷTm]YX刦_,SGW*m t]L? |9ͤfi5>Hf  ЉT-%cu*0 $*__*]#FpE*;ZJ.b8mxm%FU W'S? f>0;KUA^ 7n|,V#.&2}ײ5W,_\V*vY?EͿ?FLBs 1SwS1=81 Rmֹ IPmNAsD+XOs Znb$ 901n^b. nauoxc|oJO2Ctͥbp2G"aRKB/kt5ZyǤmS`jE7VD<1\+(5$ VR`q3HG u4zҢjq,h 0d Che;|42} *Mo.r Ex ̗f:ǴB}Sv \ʼ1(kk˯g՛D)+`APD ӽ Tm nC$3;.++RsVISz# MhDH %{V+4ge 6`X_z .k_?琌frT EQhy S[q4K>< ۘ (H(}4rNx/HMq@+ًaE;*.ybkmFaISq݇t#)M8@G઄[-A®Α~_'d*Qc&h"SlqfwrD {,G-r8m+35dћ(BaDRVM^i?ƭy$#),¿}78pSgC֫&O ée\+_QWQ,s/pcVB:MDTv/"PEǿ?|3Y X#˃1c)"kp^bie4U4b[&|9޾^~iB*a=:Dl(bLtM93p>7k6W5a1q;+R @.؏9[ND|d0\BhRSeeD{VZ!"r\M(r. d%zĽzRgx aY`^M=| XlVDȇy} VkcxvkiB,+-B¢'ї/q>kMAjWѪak6~gX2a4>=$o(xp8UPC g='9&ZL>J 2Zb˥d#,DM){IHi3* z{".;N3؝o ZdŽ/P%@"ة'ЈBq$"+)*^;]3gL5BTp'BQd.駼L9Yrwta.}VnCVTZŵmHbZS9-HeWTlnb JJNnG6^v?0Wޙ2Cչ 5̼AN Qa+7kn捨dV¬OG0.asc|tiN\)Z1wǠGMAMV_y{RWz5Ta\Ee| q-aȵ8&WP۟F:w1*Qګ aHT;?-+}>r4/<,t2F˰|Unu+)e%J0\.f`M}c}~Ψh-yނfr#Qt4f܊\D^W4>$ydJXW:qI0"ZY9@2X\} i;+8eQXS ]%G3Tvy=k䍭gz0rsb,v _#9%I.Q^P  ' dο:/l+PQ'eOά[i|r[Vvz4-§8}Vsü&=,1]Jloxe(4 /V+DJlݵ1ۗH<+bc s(PDq$̪m@$ߖXGAH|/+ Yk C@bY"c!W5s\nUJ$)o}p'sDOc V hnQQ'1 / [  (Y֫탨G9huj9 pDo$cea%C=澩E B8^vLe,vR3^tLxk=n*0AK^2 saǦ%,\wzL%o0,?#S&AnֽਠnaU6F28=ωc [7rO\# )ypSnV@ Q݂iGE5PNqO=WC e^LT=)mh|c Գ:(<'̊x(=Oe'uL)zG4oLă/;eD>đ|ݭ]8ɶ%/W/G!*1$7e_6:Η se2E#_)(ˍh%(gu6b#irmAr4M&x<+ 2[SP"E+w1:7HW\:rϲt{ m$ P-Vcz~?KV$9ńks.hh&o'@$_^%"0n:dP._'پr|aubۅqORoM"/l ՜?WݵMbž Z&6'-$ߟ8ʍ7; jeT_^OePӝkihi p7i 4Ued8hxn(, x'E(ɲck O=|I#b-*B\ðRjk`AVL@] /~xJ JBB '] 9(Y޸Y0# 1BY濅] uFnԟU qSW=7G RMZLA1y!\u+7 8ׄ d\e°m4E=8 t^oN",4ǞBeۏDUWAe!@wcM)Bf bfJf /E1  ,-QX%[UG)Zn"8$>vv\%׹y7Tx .93,9]layތPW@#Xp iKdiXC%x̅=7fEBX$eF1TH"r{7rafiSO||1ҁ_N̠Nje6 jK?c5s\39|^ `gXR:Fr#-8Zuj T?VCs9_C-"@1XY5Ayl]@9$ޒХ2qb"Cܭo^uw6\g.IS6脎G_#M[U:< BVbV8ʲ+=6]bXn75/O=&15Nkïga?cÞ-R"҇Fa4E_v>ʼnZ!2LҰ %J:#Œ-P%0mMy}ܻS)]~GP"FXT|+_,ƍ#1Ġ/zsJ rOvZq5%(-R`qW=اV:}Ox]L^weu_A\?ނfx%n`̈́H5=/2+8sy^[072ʜmԄyμPTU Lf?F/0 q]7ьhVָcpda]GkֈNW0S Ξө 2x0j.t{'簘 "~?.??aKk- FY73lG[va%,*ͷ&T5Q";I .+iu!0dH6L P+*OsOf,].tWYE/+%;LP}d8ܲ@E08^B6SҀe%R@-TBGYO>#SFQQI u!=,^>Ƙ 2T鰈 5`l"TB2RW؟p"pJ_\ q%XxZrQ+AsDZSczWLzej|\\)EfPe"-^*Gr!=ƒmluxJ <lK]Y(̊$=R21fܤW~2[sj_zPbc̩/SyiwL 9q؆{ h?]E=Gp K_ETyH\֍ dϾ)Uփ[|yǩ`a_t`U^0/*[J P`N 3FWTmݩ߉,n#}3t]#zJZ)ѿM4} 'W{<.ugM2MZU8ri$˴j'5f(=_pDJ1DBdd{ ǨI7:ЉzDGDFѥMÞ AEa:4A9$K-͞$@inp=?uq$i|m<,S'vMW;i$)9F=$ƙ"LR.Dp$Dp;C"e4ҩeԡw:gV`05@ǩi\~֮#ihQ\߻= x.D]X/${wsO:fauV4|o>zc̻ iHIH!yoq*Kڎ˚tNƮ *.QRթP |`SKN/ؓغTr2 U[|:w qvO: kXo6_RZ GhTmCU~`VRdM>%tRKjJEwFm6a$w!/ 'P7\H)Ќ`sLdžPB}d/T{5~, Z+֢؀>w BAXdqGiQT1 z ;1۽hhěP[h$Tp"d&N H&$Y,s$a(UZYZ]|)(2cX $W="Mh!Re-gzebH$Wr"jp=K5Zb)VJ2IA9PA 7[huqBQL jkZ^ -ZHZg^n+1bF3VLHq؀JI[B8#ys&4bZߗ'J ]̀M*|C{$˦#2b?e@F~?t}0yXeM'lURӝ_Ie "S8#ieiuO/_ؿ:ӿoYoz: ûW#*vo}m}cõc4o_üއ/?]†o?7u;Pó~HoW=7_;zZw: W}?OPhRB/w^ۧDøwGv?^t:ë/:,ݯF{;tcC9u+}?oٯGCU z}C(Z: _3gJ22X@ b!:FLאlS0Hn\2%Z|ciZE)[#&e-mP%<ߒy<>Pk"B&,5y.zF03X|8yWN{-/2V-;0tnzqa Y>Y*@&fW1Z5 pqtazIT-Y`Rlz ҘM/(6E8[:/]vcn-]cOW<2:^.r*N(2a+r}?4,}֗з-qe>N!y} X3^;YVKx,k0vyߍf͈Ζkl-bBD Bn>k K&q"?I^.Ǟh)@o.cܦ>6Pǫn8}rMH3ߐeR_cb-<}@ Q{[>BH4GX]%yALЇ:Cs3M7/m'ȂB6nW@kEݼNyUƋzd Q.KF(4wDΊ3KȞ_ϛє}c *asBw9-NwLҌ'@JG w_._ 2.U륰y.6{xS+]ʛ]lyA벨42Ag@RW,|옎 $dJC6=6õPExo-QJ@}Si>Ym{S؜ I*bTÄ>ڸT_o}'߹h![Y-4Uî2Fe^ҽ:zx |&57Y Ak= V|O7MWq ޶LɣHSrU?bRb&-RzAYϬdóކO }5vX0 %+jW#h,1;Qg/N֛.ִku"`DiRp' R:vN#5Sy6}NK"D ASFVݖaDb$U0 0h6DM#ePɁ,χ\u+G {ICImˡ?#ڪ[ =ihd6TGJcywc^qMpI~AH4((`JvHf&l#H8zZ1d*ȍU*72xOE}+,.:&y)o"6ֿBEgC=;;^ۧJ}JMSƗP م Dl;ȭ\BnM:Vs VTŸex5[쭰>SB9F&Òu!Ι\ {_4.B.Io.{]za0?.uŹd\YD|8 >} o lڈ3qX{LLg!A7V*P 22!r:SAf域Z0>,` g#*,(ԡ ?a,+P;> )2nTKs4hK[\bGtB-~rدsdW3B[ŽD\.ЖuR.aZѯi[C>dZgאU m!S" uW\@ {Y2'θkF -)I [CfjUoN'`') 03%S ޗUݯukՏja,]^Pb)dSUS7pY6|&b+1jSqhs_{tpϏMYЎ}FRF*a&B-{?D<0mU57C՟teJ:W'"Z#͛]BNۖeUs;f]!:  43%ad_iez9&LC*D]*4HJ…坼caLaH4ʕ,v(rM]nA$nA%+7"$Thwۼ*(ib)F5t?-@tl4=zx"3lbт$2$&FLU]'3w*M>dYBi)eCaGyr^P)G~ Ǡ 8xwg_Ex p^dv2$4%[zc^sri N.@B#(ʌh3)ơL9-£[I;V׭Q4e #͢0pCI`i|PjqMt t]&RPadq _ӂklH轢w$yF[2º:~7k*~e_FN<| 7=켽$20y.x f,=c1"58l,:ʄY- ӛdCt~tʦ$e~LKaRpG788g 8fj|+Q|d7ɅKDž5AB&IaPiJKs& [ˑ{&1D_2E"+ˡLfav0(*BS7N"NȽ *I[CΘ+N|LblN,9$||AN9gbe?h-@&r[Nӻ>|,#\6C`G`EO 1΢6Y塏n³(,.[ 9QSJ8*F>P;R8 '-u>s8a 1>mq8tYɻ53cwVcMsӾ5Կ$(Ea7bQdL.!@+Ѽ!S5=6vu?gi8,m*FWfXJ&fQ~H ǻ'VH|qaRпYuF0Uy p#~3C`dLIP^;jŁ|6epgZ$;K ŋ_=:nLy{YiPzә[yk'Q{g[;Ic&.aaLbJJ~K첹'N+$&E 0Dv+$rMA.R \53ͥk@(}\ j蜷r5 ࠍ@{ D ؊RCv Q&;]AS}1=aM\anv XUg]FʺKNqʁ]=,|fNq*5j鎳ٍɊZuP3^aܑh)tRl͒%4vpMvvIpc}Lߦ;.S1ʸUfOS=GvSHA ߶!H&fT Z,]>~Y"<7J{"5/ JhP6B:0 M9<ūQcDn~P@$jil@2=_/`zZYeD[I퇘HXr4X'z<.P2Ӗys"<0o-0-k #ǚJ'IhMyY]BcBu!Ĭu Vජ#3]me@n0vr `:1Nh g9sy8ZkuD`:ugΘ<3U$g}NjNL6..ӈ% !#dh%n%G$}Cg$`D%6;N̻bTg-i?%lhD 21\c ^0 'ӔTRW.\G%ju/LNR̀W_Vf՟4C 6yFC,>RZSt0(J>7# Q'@6%kG`];7Y2{md*mZBhK64}A(Bγ4>M[p^F",#jK]E=Dӱ$N\qkqH,. 2nA|8=bSU,o#T츝O?1E=7g@%1fHk#_ܨYkوcݙ_a-9< !B6Р3_M wX rH:eWM?MqK1[ VD)P'g(c:c X_{9WER}٨ӯ(@]Vp;aZaӨbmu۠LIO)߬3O4n %ȆY> PV ۣ+3~vćA\K5}L"d r?f/(w:FOҖtYIlx!mV-CZw+]sh\$g‘ ЮVW4p s9=&6? 2U^e#<@dvjlDA7vDXt_M%US`Hݠ窸sE/)GA RBdzeaTo Gp<[i(#c2^z}'f.4Jj¨3>->!tDsfNf?d= #eW6O2%8~!_'Ũ&C׷f17/'[ O&XFMLAAO,^R"xy{+7Fv,*(;OyFV;{+@ϻ)j^RAeCSmPLF:O!OF髪%ڤx^bxȺei_0@Rvq<~c.886m &,gc \]V _Cl :e $K޲9ƄtY{uR{w;HDњ}fDᭀ~.{,n2 [rmҙ٥V)b lJLSߨuF:;@3/#}o!uXҊ?z<r{"hq TmO3![F0?\stv@\mÆyʈ:v M Re<ٙogW; Ei+I o5@U+d7]B\5 @:6ۘ=9fi@@đ<{o{`8te= fCqvR|c\85^f;y2d. 6}_yLs͵@~f}'L_J [ÍܪVē겓qtGxX^td|ZxxTCM^l`Z,+X"$jLܺ iҊ"e(KBT&<%rSD懴!?j[Qa`[\lx1˖dAzi jO!;ӕ9%5],pSp ##}j :WT7W\vkcׯ3OpX̎/셸{e1 gyqN=HG0t5;EWk&U4x,Bc{ <ӕȿNq?z_.W/\f^f4ʾ$+c"fAgB,{S)́\'Zݵ|e -4`#k50gT&zV&𑨶n,(VuH5A"<@c`@O5, O)5Ԛk6"F@pNS!{)iA "RvBg$ ZT-VJbEI[T(0}P?$>_%hʮFohtZu {FQ^nc#Ƣ'xHIfPG;EFP.#᜝xNq1KDf|X9*Tu$\nv (Ug,HZ]띣6m@| ӃKX6i#ݪ?b,ԓtV.ZFZvf&fTT @cQikɭ8{&eRQE u=c0??yEupɳv1\?'k)]fl$'RCM#}_Ʉx3Xx&PsȤOvJ?wy2 *yZ8ry#i6A)$D} d?oj'e owU kђQeD{`P34T^ cJ1Ԟ0ЩSب]J˷B y{plg4 O]wFHΥ.(%ttfsxrG`4ÎJ]5Uːց&|h@G`4(XqZP lNS xT>ElįJ?,ŲO;X-_!yԫ%t 趮Tm)p{9n ?{<1i@z"P1W|T/X+2B 5M_T6L 6R%9U-٪C|/pSo^bo+%IwD!0B e4'wAPddZ68?LbkCg([yb ۯX",B{)3mp0dMů"Gb8SDf8yvbn@kgŀ< Ո aBJ0͜yJ10 9 bSw72 .ņΥB/q{1Li=J?9A_K =!IB%cl?"?pu9ql3é|Z`<"CA/2qHvroa1șɰ;(zpsЉ 0 $HZ D6>f( )}dRzz*ERYʒQ  )_V%ۨ]tTNHGuΨ\ǫE#UƔ.p˴Jxg)<%j=wBO b^vS3qf%*v%o= ` Qct7\߲ͨcĦ>*ʙRM{,)nҖ㈙CPRIؑiaڊ_K .tfyk\^@~7|$!Z@D]lQ*4ަL!/9x?wG4d6ؿlTC<-`#J(4َF#p=@P$eik~,_ys双 hP6@Qkbۀ[1sTrǥ}*wW(M(;& AP4yO|@ )$HnPQ1~@ӧq]*<aF}3ϒôr>]-eY\=\U $h<:Bct_6>7">8{ 5iԌ+at? V \vyѡD#'ޤqQc$sA_at1 rS'-O{ˍ 60x;yEMzrQE#b.G7߾@(L*[^AѶ!_%:v5{G9vղ;YAf۾)jO-*vv~+]i׼K Zboj!3nV+x RH()<&Z+u2\yXp4'@ueCI5/Y̳ "T9l.dBwzWްB"evﭻ"b"B+fs-LvH?J?w ;^OpȲ!uSDd"cB@7it h8\^-G?,xxZUR[4[(]9pc#3;l=k_dr;L!E.挓DoI'N#,L. VW(Hoѥ=J FZ/?qSI+VD 連&g,Fgnh͋|k;a/`qyi ).v<#ܙE   وk}\͛k^r|s*sfOgV]|_؋mnhe8m):H/0bܤ =[x쾒ND  N36y?ϴtlޗL32c5V N3x1?(بy4anI?cDuRʫ.b92(MAf7Z \gA AHm^)G-̢e78v1ݥП|SyBFr5)Ƣ-?ݩhیcjsYY`۱I80C#se #Du<.$ :ο6`rR%xw)՗INY$gCQ*64NFP W]X$;ܱQҽq$t&KR_o*Pu>&LEwg&Y`%BdNYu m?0ÃLҀkF\fMq ͥ`ɺjF=pV"c&7RPtK*d۬;J#:ӡHf1!F4 BΞHMz/%).@p﷽iHEjg@T Mz R҆M,-0Q/DCD)|/dښr5QWlB7/EKD qg^A?|A¬Π,ratl<٠rȩfFp,OxsT8Y;h&p+&\ZzHmP{ ʇ|(G b}a}/-0ͣg) f-ʑK>:Գ?{^o&XUQ#tѓ2E29y1ʩʠw׷=a P7u%g9h.X"A 19 CsLM{M8 lͥY˵ njŝ'""~:mQޒ-$2s:'qXKG[95v攚H }*'jd^^x*N*ߥ=zeѨp>;8vWLVP(σly:2-^k˹₆A{xMv" Kӌg56֖Ӟo`"2(F5Un^Dkt"dPwxÊu>BLޫX\lc ЀyNdYߧ ق= tl5#G2rTu?/7SFsɴr"ÐM2YLs l1 E6.Zic)l 4b2˜2J:R{-f[d[6jjr,o)Â<Vl67%+N #c^]y"qf^D@5Kr'n2˯@\:_ˈuw|u+RM9 FdQ#d neErY{Svn/YǸuԄiI?a/97cal=ACeoSn+dl4#|E{ҍqn8fWPbAɮ`^`|O}}ݞk{ ׶g>5d 7ޞҭ.g/  RJkFֿ{Ec<\%iuVL `-:شۈ~c2 ^a2mYmUiU2I)ר'h!_:(k# _y" [XA pG?]H+ `O=_JK%=L7m9c<׌jUk2V#_X>&s`uuH S:ĵq-K tC1eegTh +z ʱNAUF7@PgR;'Z Cm;>懎qAQ-kӈ;mY2S 6]_d a uFqq. mCϾZe7=D9W tTup1nt#/Dg ~Á~g)lZ`\7ޏ{.XՅy.oI)egS'$#_t0A/{ SK1BL%Gr-m.li:>&Ŝ+N ~Vw[;x%f-̂UdmWTxWjd1% Yk}_hgd\jPlC)DDe8Մ:םvm/_[Zjak5C]qNQ>B{rYgO ے 5DPA~]kJjŔJM5stC>1 5ק]8Wt_u7@%kzZ\U9íP8ynyJvva'tԢbHY/b\)cIm x8=@ %iM&)bKa>F *]> x-n'^ QWP@7*cAb/r.~Xʼn}ͣfdx,0ar{g2K'OMd] EeDX9x:8_e7C25rKZhH eVmS 2}\A gG89|'U?lRq86%]h F\ +}{GcwOhk8IgR\=5>(y@KRd/4tj&+V+:qhc~ji`0\Vo02;(#f[IJ)x"$wc$Fƞ\c7iMwP9o|.HJ9Xh$ gvEMύY)\\?d*n1Qe2['d~fQ sƟ)̡=h + ?v{7&]:X1t)&mM?:F7MxFEPS`: G^N Ú_},}ڿ`'yRg"c1Uߙ VRRPg;JH*! (sB|xJȒ~as.$k Y'J!'v!;m  >1}׵u3TŤNf{43f3E$*L1d8`V1}IS):7x_}O,g:/ P*j2bu%5רxpbVh] )9$M8b$0sR|z  p3q_?믮hįHy#m `h U'S@887tumٗ:|QTZ){ >V䋍0peEvDhu!~iN fc e)8l +q ܟu($N&ص6-bdiFuo BՐV;gJ#|֓ [r,%ٶjvHHnщ?v J~ O\3('h7@ΫqfJʒs3b׈- o^>}`$ڽf S35nLI Y$ɸy+ߎ&Z8^@\/]$SDaR]s[Z-H>ڸ3@hL8ݘ>8a<ǹ08rM^@$ ̌KQ^*lPtsgl*a,=D")&emKVҚ ܀TY[g*J8\]֢;鵸l84{)\k&׶qrHU&J<mM?+hfR_-OXu4O_piF7(g*{ې.1@m3D?JwHfNjG $ pIˬc mo_,6.Ex,:t s3b1Z0hPO 6TTtOJ+"k,9N7g+%ܫqZ$[,.:Ps G:򥝺*[ܞ}^ҌL[#bJ2oN0x{T<Ő{9 wJQ%vMDʣ՘)$ 9ǎBᙺO&,sӾk>N3AWMmpm.;ebLn֔UT/䗗pO#oT)?&E eEh5OcNPmD%qD8Yif*#Qd ǎ^bUwUzPYzY-nR=͙ weB9(c7RRvumicX{n6 E^\7u;6g|dFZ.2+WgB 8-y~eSsfSxASLg3E94=uaGLJl>`eF4 e'^K55d1s&mHڿ9Ҕ@TLT {#B\b3[סe뺔 @fnk~d;Մ-4 v+k7pkrOja~YTf7xF&0 ]vcTgڭǮM_/\ {:[bzjsØ~FąNL[H(,=R!7zs-W7y*c%[bCfXҼӪ sйb]˃\vu>pAj&GIg%b2X׼{.B( A;Վ~Sn?G,T:]gR)CủxtKfIa}0;*43&(zc myoƘ[B24P|chơx\YoF7 8hNZ.ƩQFey_7|9\i:z6 _:ߪk Z'^lk `^<+~:,cM#E_l['F/{|$S5Sp9UWّr[yMI9Z+?fFG 90WRƉ5W lXҮ[ƴJ"9iES_ L\7 F2/u2}zX a*>~"vu;(qÿ`)f)Ǘ8m@o)QݕY֚p$F%krlI,4v9!*Q+J5Hg+36A nɐZr4x0?T L`/@%n3xƯN=(G-0u^:zxGH&ۻXЩzΓ_`Meԣ!חa.z9>G"#)` .sE0|GЌ# ^2vgc]mϘ]뚌MKMUUt$6`b (^DjNLowg&)PZH6, Y6*IpVs8Dc Q d4t -V^ JӕA9O=ynZ7ԋؔ\%qOgh߳a pNe;}"N:Y̏,ou2ըXTJ${6>?NgG6f8J@킭'Fdd"P53Amm7pp{}bQP<,x-A eeA8R| V@ X;șV]% -3mqKū^[yҿߎ|rAv԰2;Db&Ac)zC{@Q^mh;}+ZK&A\$e>g&6r`d@?9u39/KHZM@.^K59TNƼ4kc9T 3Z:C\tۆ"VLȁ܌ 4J^PFV&2 E(r¦|1oo)+O]AIQ96h| h̳DM0Mi罔%vLm|P>)Up 9%\#}92gAF me,r\EVAB + 9Be@8Hrs։ ʩ% $6vsJQLv15AM*PFHwʹWk'aw?ޯ>@&f~צp\'~<8AL:v\Jm=JAde 5ofPane0xQRLӌΆVv8.ldːumP3Cu7攠i \6q )xbHSRyŘ>Bt=𞴿s9i%bi45n`\xӣ5/e ty!22ZJ \k(Hg{v`p5{?RxZ^nsp-iCks3h@e،#[Ԭex$s!KifT|HkbxͤN Aհ-h F@l? 6ƥVW97%nw澰@H#CB@׌bh m/ wf RSRdFzKۧ~RC)b=rŧwޞӉXPft~(g/yfE8e!W_ac5 w ?x5Ш$/`͈^ɦ^k#9K0I*koѨhSD+X0F5e['YeM`6j'XjDҧ]S5G@#)2Co0P_rz" ooUͩhL~V,6@W$z;}u\(<߇Ԥac^Rt^ɘ;cH w-wۆ#3F*ᓻ%cC犒u$)\;p'羮7#B<"K;\eaoڛ.u~ ͩڍ 2Ծ{loHT;`]b'N{|8! ql9wե&'j._n0Z5n{jKFݭ B] :30R-ntd,IX{p_B&k+om >J׀#(@IXpjhɓ QDFl;|Ѯ8'60,XY}s1̮S'Oz7޺a`٬pê5'Uv+cif\ݣ)F1*Cr|>{aм̐ĭL5k4|ْPYW(j^ިU1óP]ORCxZ2&%YuQl.O yȿ$p(pd?iK3Wr=)pL}ųߜW΀C aUTd skC Od_ c !qG'ʸ xO<>ZG9M`5zWǢOn4 bW(8nxϳ03Xzw|*&m //=vҷ,VP~R |55@TCp-'V€3;LV=ia{tZ#l ;k\]]e2 0.NU/fHDoR=QWxK+Zl#3EYVb<6W/xf?xqD[Jh!G\S¢X`.j+ `K(2em3WUV^]WG|h28DmIy4G $ƱԬ%.qO8eK3ȍ1P5,_S B dU7^2rIެWј:>3W5ן<ĦڦUV;3SRN;mٞ"V)oQoI\pr V awŀDw]7{Nr:VG-rq=E6SQ[;guD&QZ1U#3_Ȫ򉛐 *{%7^'$U%*YW6}/,FY3gt:R^Zg-LBjAѾu{XIUǸ nMF&Aϱ}PK[!l3IMZ -ʍ`]L+'qy`EFl|+.ECws1>M$)El 1 Q82qOOXZ t3ag0Te3&LgqXQP~y"zбT"I0mN;[gZ#c-";1ob+2#JXH}yDE8QH ʠbC"j߾2«tJDԃWwO ZU],QpB؍CE#mDJ6Hf⚆.bImw}C;b)8$~)s- ba͎fl2AHPE {Nv|KV")`QHRY/Mkq EDtzYf&AO3%xDGVֲfCT}4Oj/kRElkWgGN\]'Ahs¾DxJIZN^: ƞf<Q%fh_9YvȓQ ;w!c5";Dǭ S&NIojdܛx\Yej%c+tD?Cfy~Wn Vz {ԗOutDyF&Χ~-6)R'ձj;pʺ8sA[aJQb#CQ^e%, Zoo"D"'NéM8Kq\NMAЮtɥ xAef.n?γ|Jm~oL̔fZ/0dzR]S-q^ƻEQ@'%p,򵰭61&#b]Z䠐UgkpnӆPiYš^+7"*hW):i\ &G`ԅ$R|8auzN3SBlJPüʩ:7k2ѭ9՛D;7ygG1w cCMԥC Q+lz3{T)8[77s zپOs*s`~)00^ q9h3 > e*=-oJHMW 7?LfۗQTf>Aby8ѡNŠI Z7T31HQ/#ooHD%_g\+8F_?!K(6oX|!tW}tUG 02u 8ֻwQ{exbBIE,?TFpwt\ ;vx\.ۀ-,];#E Xz %Vp鋷n#3 |3LDɰUi7Mk̽ns\eFir1@8ڿ=,$x" "NuXA4}Q0q9;N+顮ѿ( %cePGWfmܹ 9 =5t4բbrZq z+ėHNJe6&D澑38 #XALl[!1ZCqDN&| r4 -jrNjaN2:(BJl?ɫƥJ7د5мD"z_0h2Qd.OjCk?^Fv$烲tL R+ K =et}c& ||ոJ~ ,q^BXQ1GR4pdv,).&Jz.pL-kcj25YAykqA|AWih_HBmTl.&Y{p=H@=TG76%%ª'@F=f׌NӉ0AćV٬K bdS;Xe+gFnKq6Aqsvk}x]觋cAY`r _Am|0vtU~wEov#A݅D* w %9l7}%3@8f]:]ىs &}R&VRfःF+\ש府k nGB.tts;08,b JM4-XGǽRh:'u/Y@i`߽CHte pKp,&QӚ2K?G [ӑȡwk.{i,f+q'!9FZ q^`+nUUThӸgS*%IMH1X%">OOc@m 3yB4]LPTUϣ02ګB(xn !uXRXQ\оF>%?e$۫".2U:9dIKߖ[6ϐ%nvwAy1?-mJoBgIqoÞkly=O ſ,#PH bAeCH¿tRdzνxU5$yhΪ}`Ź$r g);쪡EܝΔJE7t%L7m5Ǯ`-n〣 d!2EFՇ-vf[0I+[BV Eoq$2Kyȋfkׯ=!FяOZiUMP׶]\Rw\6#40 併rL.'HRS&bLCUb qiP$U0gA!`2I  B,M.\Lv8輝l:FIo=j|Úux7zb5!H ^A w%݊`ܪԎIr9‘ Zpb<ƄE!`m1-QTT ǧڅ~ _(>i9ل KϧhA ,rf0 om%'ӇaybIQh=*Gɑi.a vY++9444yҠC딑VuN wkc;? Lɞ2p67VJ1(9eѥ 8sq"?w'Ŗ;bĐIx -rD.ooZ@ =C:\r[xA9²Q/䞞o+OKA,sCRiۺu4̖gah BP륵DΦl*!kwC+9`J5]V%۟HwWG4N9E6S#r/YA8b%I$ݕs@Q!1'}]n?^ѭTX<S#C!T!aK WʏY^~F1;ūnsN_.I5qnp!9էPdDX9էyWJv%F@R[XYPC H$1P6N+6[&z5$=P]2V |i`5A(H wC ~^7|HK.+]xnmw Wp"s8iF+lPXNErT*m)2跩<Ê_XlR)RkȲQ58*?M^&2^Bݡ]N"{h5(]Q  L!M0tpACFpdAgZ&j׽+" 4է&~KJlxnw4jX_@fX?,[&rtz n fy T6#d" d VT7O1g\SHV *F70a1ik`&(/@5kl 謍2n2Il %Xx<1e^ڙGY^DmJ7^O \m45 eorN\A昒Gleȭ!}7ܦ|0 >+:K'sV8ML>]^#-TC"O덛gW$ٔfU3񈊫?N5uzt" ݫ#B@l}y^J`ooZyR4g`dy'>s_(@rvySZ![v-l_l\#9lkm@}ņ`Xէ7iqrEɠQ*0 R}4]RAN=1AD=ٳ eg|eqV~oN{+cu !2d?7yhSm|5n,"ɬ>P=>Ir ^Q&iGXA嚦g3w-%$mNEzL݇܉͜cJE ތe; vBMm6lX\ rU @FT[XF9(j0\LmAsJ#i-g,"[EhŽ3s6Oc$x,ئ>6A% t[W`Gdk4u*K+*IJ:?+ToQ9WR@c⺋~?r$y:2(A*Cl$]q `q <(ӪI "_KĐۀ%ձt#fYU.痒[+ vȯ af~/,Rc7]ߠ돍c}oOr\ni4zk*Uц41<,T,IB'_'8_1ɔ?S$'GGlu|ґ.X>M/1&",xͬO@%pJ F_G)zY8l̻'FhD)OH2!m"`lBc2J# m]^ 9) WiU2mgH2ߴG:joX>G   *1_ﰩkkvG#\j) Hwu?0Lm ⱄhzn$Z[gkOaꪩ 6^_4x42H.U(V!n`P2MTtA{mfA$|X.c>H߸o41~\՟* qBóf jn %AOKTrh?aq咭XܻVRONC?fF/+ayd4 2Xt+ab̝{mٵ=g%ԯ1*˿"۱RuRL%}$mۍC_gAouŇ!>h'+;:s(1uݰJH u\jBf3r|AD9 ;NM'6%IAWa/r\ϝZbv ^0h Sچ,ԩ~ʱ=stݻ/uYj>kjn*벍C ;۪Cȥ$l)-T!30x~Iic:*Y0DŃ%6GM6Pj|`QdK1QAܽPǚrxfjށWRӢgRR}CB• 2Dr׾̨FKbۣt?1˴Hw$J:&QʻM v[Dblb&"~쩇185r \2~iu]/s$KSE 3dWlh<+%a7Y4Md_Ƌǿ*d9i.~Ҁ'4#֥NCY:3Uc3991-)(GUh9~XKeH]W n(iWJ:#7PߧCb+@kW+)uűmnFm=`a`1jNҊo+w1egœ=3ABQaEuA;YA( p:ܵxE*~%DX".` -!^9*<d$[Qv 1 *CQ`ZjO`9"I1???y)!r]P)!kwUc=-T#f>/66NIט3SZ4W Le0 5 %cƦY:2~_N{TGE|j\R[јUZbó&BQ}Ap˿䒓Z9b pIP#7xl+Jy:*ZCRWu,d_N_6yEi6I bs)|NX&$ܬV!2\|ҍT xyyN.W]k44!z78B>xrJǔ>Akk?A~ls\W]f72p:݅ZXb+ ;Eu]?cȒʒ_s+O\Sa8j{E W;&wp2I~*ѤM6SWo )vd R>Hs`@'6vE"-~ cg wX17ה3-;\Nn"%UO!5Y. `=yZeݼ:Yy&-xN7xE_WEp[~!ۖإMO1#.f\]=oJ>dɱ%gZ=4aJ$PN<Q{nB^s:u(I xkmK#% Ye} g~XBV4Aeg^ݏ&bERP.aِ[a ϳ!MގB=ʱs q\(i`+(0+> AU+Bs/^v55fo;*}d@YۄklVk,< %[1 4G&N #2t'fy85S8j{Z *tykLuU #yR"jv+H j޸iJ&Q!(L[F;! j\np45|5TB}9q::LJS]@Pښzl[ߵw`&8(<\J'qLHBȝ>߮]9{O+6?'(UIDz3p<_9 8H*=KLR7{ٕxqWD _S j>v J^mtҷGl/Rm$؛-Ft@o^h3y2Yӧ]V3S j= 4ėEV0fUJHp䬹0I ꚪwyn-'ؽJ"I*IZL=|2dDfY.1$hA,a N& /(<5ɡS%K0FSG~#wlVZ{0O88 |R ,'krsPMH#BAlD'ڛxsxh:!`TfAwo~-ɴ ڽ.xNBEh< QwҾeŘEs˃;i&/yI#$Ly$Xlz~J "]+2 t2wL$.,%7Cc+mr߁r 3j~LA"3!fpL>fઅ˃D-+ 4H`<42TS ܱIň&PA DOr#oiIp jp *Z=x-g>HT+iS1{stkZ "3oFrڵ z!ZN95KIF`uOJm..u)p78\Aܧ KqÞ: FW%5Y;aȠ>L^g^*!{' lTv8~.>9` \ ՃJ{V_MXJNܪ%xWl`5G SbĿQ*aO0Q ެv2CkA 9.`+dSNf%Ud1Lعl~ur#]/\Fq};[pmbQ~jc_X}PAxOzC^QkBvEk*1TB< ~@i|n P IFv+ƺ]45Once%S!F56-}WT&Y3(x|m+$pgB&EH6~/(ҋ!\ /ÏsNRxCw=!r%jQw%΋ݎbx|Z1xcfFܱ,nfkҨ6 5aFR߫ apS&-d*!U&،:xQ$k& = P$`0j_V$grEDZ.Ta={14:CnO6R"dGQyٻkT!B]kODJWK懐@?;8S?:d|jqLbN 6̙xDW3k{'%W(^,EVB)51[53j&LWcuV$FVwudo%pv|nͶ*:SEI;HA9NKd;rC+?vtPs( bxeĸJC0?p[*p.ZJi. vI!sE/6:!wQV{>~֞){o !:cA+f%'vx{g OpIȸ!pR)ٚw JN63d͎QKECD x#_VJڏ< &yaѸ`@ʷ@a>ޫw+ )yد#ȕ&Igc+S 'gɦ 3LϞnXDJdza rG]7C3@^1% ߯xЭ[/gI]{.=Z_toנ&cfmiL"KF6g3CN[$NK[|RL(TmFj)j Z<5Hr͊% n(`=k^! ;q|؈6fGɄ.$g}Q ԥCH# 祸ڃu:1ZT8Rݓ.PӧA?n3+<)\ ƘGXTEN*},ډ̀`z@)ƒ]Am<{Ro96dVDO/ژ f5pź>'_FW z{^$JfLy눧c7W&UQL:lH$}BF_:/G).5AH|Sz{ə.6uc`w6+T.6qXi*4趧_04dR @9wuUȘ@TgB'3H^:ϖV3Ku e.S6Ѯ!HJ]H#z_Xؼm1': +%x9sݯ0"$*w,Q(Z[8hc-Y2Ìέ\+SYoS!/m0ڟ j?d11M7/wxUVf,Z;Dسy/\E"na{}U@AYtca넛@g*EgwJ2OߦïMs\mfd4CAh|Gc۽="`Ouѐ&ovV?'@KqϛCijl0Ql}Wa8ָOdH8] +pK+um|iD΃~FTUd^?9r*`]R;=;%'~84ѡ&cvq,V0S?(f-hWaN_-g]}Z&9AxȌ/BFJtkmH_ZKMk?3X,{>w0a.0!$MW摈!X{4*܄j@ȫ0@wV_İqӢ+#>S9c2Kdܩ YU'mA+t9TXS10 a BHX'@bU҉ihlGD;m)a7Q]|u0aG.#9*c- a_)D o e; }0 Cp."Evy9("o\nxe*5G|@#AaӍuݥ 1@Xqm` //EHR &އ+8!'c-9I_lQbC,Ba T݆Km͵QƢjz285H msE(*}GA2/ʼn[Х =}E{h](}~kڼs5*>ڐeq|+]8H%x=WTT ־xRV Ji'bEؔl2W/נH'z,R]0jun:ӗŠ osfw978BJt;4 \n>ḾՕW*{ t‹bq58{F[^QqE+0l[%8v9!K$ͩ 85.ATk@.7X}7(?z n58A½yOd-7"SzwX /]֊u -l)Gy:EQ(T=$!cL?] 'H;&7j-AW'^s3I#㒦x}D؏u#\fl{%Tۨ[l|MϖUK5ځPl|8ԕRSCNnT(.yZwoD 5e! .Apٛ!O/O+pQ;ܔ v q9o|֊R?ӤsB Kk-}Rz9+."iTjhʶEɰL#`nîTL3`C7GBy"y,?6UhX˃otgK27.sXSdB8 '7HP V/,IFV~JSiҲ[vJAd4!Mmmfq*j˻WWhZ,pIL0;ы'AZF(H6|G>{fMlO[a+р)ɹ^t4hiG]KWkiH i-i= ZYeBNX=lH/ ]O|F:hXxGcat)}q9卭J®̈́T|CC{ $I$ p `zelUQ^a7  3%Dl6Bv|ћ$cHq8nǹ&9b@:PY5Y|1`x񓙋SCEy;ۢ6cT fK!ɽ 3k<;?n~9 LA(&8 iq [~:l.96$\v-/Ce`fN[?8~zpfe:&i VZiиB&*M;B6W@Zћ "փQy=aL;oi3J΁ %&}g_ň:^y wƢY )2~G0*370,+nDV V`yl ?TM*~IA NQ5QF1I!(W}br]_MkR 3 f4&j%f( ͍/_q03m@I1T SQך)9zȲ PQZ,5BK0]pdDH-;a35C^d`,ƝbHҪu+5A\ xGD`/j\ꁆ( "]!ot(No詉SԷ 攱$4%w" B-Ī$:OokyJO5P5$#C3"a#* /lCQvGV G618hв^ۏܭgΝs{?ܮ[Pǧqiufb QTQ^2f>Q޿H9U`&g @*L}7j^*N_9c rj38,=ĈM_Z[|u&J)5 ;7b}v[ĆݏFqތVdx:g"DzXNXC%4z`9ߐ%[;?N%zw(nOmDIOC0tYH]ߖ Z{G|r ޽@7]#tFaӛR:ؕʦigDEDT@l| C6;I0cok%nC ի9|EW%y jiOMKKRضu'}u_7oL<GIum$z/9Ԣz0bڤuDzk 4%A>5%Îjw}9b_=p`!]8ö Ǟy$)܃4w(r9~1 ClcP^pX 8>ZHnCw IEzܿHpEs8a\NDžAP0l7l\$ך'J])EEkw-sT>{}* O}v(X{H(0:yt^D8y1Zʸҝ5O黷`cO^&\KHG)%!h -2 "Ħ֌0D f04Jkh d5IƍVt_c*d`cAO1UŲ( 9OB4'!D;BmLHXO;r'e_XQI*7󬂠}ޤ-kHR#igZ|"݂gB8`J-*u-V0/ 28vV +S?"Ibe/ev{_wεtj} tY)|{  ȟ6mܺ{~3NC6ڒnCdaM(;ÈG# Fj@*3s=N6 ,@&NݬäeNvJfI=q3A~ kUu]`c=6#yu>F(f,qc&3-nr"S5!SR+'b".Mz䕡&M`L>宸G)D5szYD ȓQde1)+#9;%/垽!K/(麆t>FBI!/G^j ÊU-8e-tQ:TI$I$I$HL% uנ#"MZ>=إ㯋f465.lm#/\/f/UfBV=}rYY2}}I6 P׹@/m*g3إ BG [~%X3ʮ?az*FF:w-HJ!|ߛ{1+J/Zei*6 ,]I0ӫ|"q q*/`C3>Sxsެurj+ENh{W*i{^vvޗmruPeh { , ,pFd"VZŔ!N L;I'8ܵޏD1"n T,tYxQT/5q~y)U~ݍe|RH!Ah""jRL园Ur74hfޜ#( zex'QMFϹTb&1 rh!ZЭ0,b,^9wvЏ ?v;GgI=vXf2_5"ZODMݏ6Oܐ7/m^`ߥ$t LoK&4%s\@f`mU!V5bɗzFk/ tK` qs'6!" 70|o=5/ClQ=1?e.Z ,Yy\oMVj,UrtxmvNZ55^}QHt/Ɂxswѩc7^fހߨ!S,#=i77  bANLǩ1NjnkCsSC+ إ NJ`1+mY:vVXmgyď-2/tM *+fS[I-WLަ'=Z5Ä>Eg8lStwp opBmdͷ45q5BnRnݘU3b!#3Ƨ΢zԡ ehd<1Zvh-OE⌁?/ nj2IP!q3ن_#,!NC^?ԟ"!Vy\&'ꗔF}N ň>MgyrP{(5> ۳I#铹%XL?f`2BڌGS~pZ `47aK0LBY(ԅPV [++9ѱȎPr)B30Ny~ٴNP%3%vz ;X!В'5˸2Bd >> I բ'$bxFQYrΪi=8MB.sZQ&O;m}eJ*Gs#g+(. Kwr̕o?ޝg{SGJj֧-XBvT{[;uڵA+ :ylL):gت,^U⡂2Y3%9*!@Y] 76K?kzS5_1Mׁӕl*Vb;ܗinëJYZl@Jȩ7InT9.V0;ZRuٲEa9KUK'sy |#&p FˇPUO) ;/kBF^Nox 5,ɥh* F`;[JH<$Cm>e>;VGXeX 3gh_UM1 s4"f!c5ic:ue6ү3I'50XmhyjQXppa@{ŰN =G w&Ҝr!s|0粏kQvʟxOda ؄}T=r![).B'(=0Nyŧ}:RaQ_~yUjK_'=graK{%J J* >f*fV2dUFJjf Ţ5W8l(CES"L]$xɲ6;qs<|M1sO6K2Zqx9BO4BFW؄&P٦ŽlR*g[ބ$+R]!ȩbCzu۷K=xNx :jMw^̆&Ֆc | 2г 1Uƪ21*4&Mk*pX6U< Nc']:d=H߬beҁ']/ʏ%֥ sE9xK1Қ A%Z Y?xlR:9:Z395IL"M!wm!#W?k@- }eq~|hX|~t"9{*k@ݨI<%h 8 YCIMk"0u~c_y{Pd6T((uO6A4G ŮvٌY § GJ{bs)  ͇[6J 8牥2)6E3.PD,#oŤH }Ig߿I6D\T6 w:#f/C&c%VI.?! 5\vpm544 , #G禧߁E^14#. x!աS*L=w^^K c{.W\{Zp RK4-h (6QM+8!!@ } w>%SmpCxe/S-HJ~4j,aa 6=S_( {M0#PlY`@ZI%\BqCuTSġgUلvX-nPJϗ,qRk1 !Y\N^.? x fXm ]Ǫ+{:Cu|wJC; &QrA#z;ٕ/Q++sί\É@3\%ز{I6ȴS55L~5',,QsfebmpD+Li48 " +QtQ k̉,'&Oj ` 66DL [#~0YLZфZEңMQ2|8_KWct<&ATvŘ?9ru2/KTvwE\^as*!ݣԐ#a4UGaş%ܝaɚrD}$S~%"]FƳegZ7N3x6(.}@nd? 4T|`!2$q9<,RN8 L!v nAb>Exm!3jױDXn89nf#lqCRE BȧTm56Ux \I%{=Eĕї4j'Rxv2i;P jq N?Ez 0鿭~}CDGuv~;oxW7bleJ3%|ߚH ;W(]2©*ކa|T"O z|4XUjJh};HFf |;pUk08BH_zi򢵂Z= 3P9FDŽ_RJW\ot@GvWn%Ep;)pKF΁$Ns.o167໋0);hZ|\L.*1ol,L.vHHT_cZBˋ+i۬Y$A%6%w/;Uk۸HSJt~Eܯ P@gTΒX'6(4ab^t\M0~<0\}tt~<-ZPUusL|"ԋd*&Zb&×Kcehnۂ*˜=CoXpR'V>Rw02rQ9rcjQ`vM8@ӵc_kt+c]/4va]p{5tK xE˔~8W#zi"{j]Y262vŗ7K`cbV񋚔iU@GUBg_17} p]nil@^T&Z-7*~ YQA(9w#FB8^п3+vិTPW]f040V*,ܯri6SsX5_ W,oQ@zN}m:ڪLjl_ɧ1WbJ 漝p04ZzI)E=F;xs7@{ & 0E|2Μ'|V`H!%?I - O:θ}Z655&%3t5OT@E2u V47ݜxB‚hImtL7=SyD -daUpk*=G}8}h ϸG[3$v'_G En jMǀHҐ ‡7#kIK:2=Uv4/ۚ.M#aه}9| T^ e@y/N"(tنr:B=Hk?4~9nd7vSRv'-b˼ D{mP@[ ^Vᯈ\uq}3xЭ_ǁ'?ʀt 4^b4+%6R9:8$.&6PǼ C[+~&#-z78tK'E#m\eX';E4 I䔘9jɵkQ&LfփϔR)܉9=Rԅ έ d]Ãw6Ҩɘ4Ώgǖ(Q.E)BΖY\;13m:r:f8 _Z۰~M n5,vd#};uK*MH+mluV(.At=RZͻ?VfRКjMI+tٍ3esWx&:,/A?E:x: Z'ض4⎣T07:n W9+!+7o 0(?VE:qz-\4m05>5TCy(KWNB ,FXzhEx/ Dm_ B[m;gO&܃6؉?CN$ص׆0WN`S.%v/tQU 'O^Hwڟ6rd"(IW/H =c(svт)R&ٮ\ ?E?3hSJ8f^ѨqyM&PUŤݡJu$?{ɔ 3[_&1#y'3Cg>\rQq}PJ )F2F `?.T Jw }оY)] Z#Ff1Ȕpr`.PPe =йzFo`iPtxφH\$ ["1%|d_gbei}ma61%O,vB9J>]8Xɾ8h4jk!SM</,kEdN\%*_%cy'#pcgݏ<\x=GUNl'g_\]p`qEH18kfFW!dbaћvLzevhsEN!.9s 4L_AuMFˇ6^i׃fG(' U+X7ucp2T`~us,)3g~I#|d9g͙;JVW\R p *MI~܄Dra&ptJk7<8b5U/΅0M(`9{$lRDgcɳSwC M )=ҺF H'oy oe.4+w7pzWY)'I^3-*\=ٺz$V9#%u("̀=FJÈ#Kpِ)2' bLn|ǡ*וQ'0X&h8T7^U`YN'_0XB}OB:#FK.*ˎ' >G@߄:|s8-Jhٲ4gÀ=@b&Kqqzڟ]?$GݥBaƜ/ dAjU3$gG\'haKu1f6G _]\g_GQP^=GeypmK"Q="w΂nH1{qJWuEc~5xЏqwB?\Xd#к`(>WWĢmH@Aم[ #5 kB[vhU ;,nZ>Sk[ 4t>؏`FA2wKKQȷRIŅ{^yOӇwMfUVoi$ns3HkRJE:{S"v9|:wO;R(Oc>C6fgmUr7]ALZ0cr>WPM3mOOO2yh}XpVYoS勫LM!0e_ɴ:6EPfKl2D恸@F]~yjYn(ȒϡXd9#7 Z.%=Mt֗(~]o*(kS-ʉ[K_q2B]6=὾^8[u dς1nmhzbv OL?lO]42?7q`5^Y_ȴHQъHb VP%G6U: U#Eg W֏8AƆ$QN ]!KuwwRyҟd/(mhp,$ xCbwܚƵ`lnܮig)Arm!}\  l܊0oϮNq{B+&LSfih絫{EgJb][YxfV<}F_d #3#w"څfxS0@2ѓy~8d/KfeDHҵLiWa9fETt硆t,B]1V80Rvd<|k!O)KwdTƲ?VW䁏_ SVKSaW]xg LCtG|?v'雌dՉgCbd¿Fh# g_CF JK-|9>V'X, /VT-CK+\) ?uvL!2$ې|.4X8Pk^'R*.\$~G:m#uB]QҎݟީ7i\=|eP $Eyl]jH*|lۢG)? mne?' 7S#Xw"&nt_0}LL5cC p|QbLt=r*Y9c1Af^ gW CkغHeTٳARFD{r~SeNry|+B1X`t|'][[) [~Kf$e)ẅ́ :y8MߛC5 (w 0nOY5{Czc|XU M"}hgdlGiaO۾RD6D@32sF FJ|/9N`cP\܈3߼#m^"jR~1k\],}C} Bd / ^~l}5Hm<< >R;Cn|:|bc:Xl y>o *yJ4Vyܺt%S8S>1D\)aPqٱk^=lݐF3e{p*{&%T-Uo9(ʻtTjJG\fs<fa@LP_2/CސاT!u3QhN+Xu].y"߁#"oޜn.0.hX T\[x$af[?GQ]Ef0[6yZۅ:ZzV|WVYʻJ4+|%B4tO&]8BuUSnjдY$ )m&WW6"`Ҧ#UHE߄S @mM:JDԒtq {&,1ͮBPЬWʦE]V5z6y|YLgsg3*TAҏN4'Dqkd6NH\W$8/!)Lz\ kȺ5x (h -D *ů %ѐ 5OT]gWŮ!E׬B(X>hv-sl+ʏ*CpqMQfצ`_I\5/q T`:O3gnlE'jњRfKDa-F ?c)vch`yP(fHPc=Hjs%)a ic9A?9V"雿_-sϭzvc[0ߙ\G h}-dlXPCC"ީ 1 8BI+L+ M?ӑ@*oqPUj?RXa9YA)7wp9~S~pl4](C  9`5!PD/ fFsAm$$Q2τ-q: vn7p?pwcD_v]n9=czµpoOSX$u߱%^rX5GeT䛯S38͎4(CZBVO+~{V1HWpFQ.S nS` y;bF1߁= /ښh^@Նgv\h7|>uD=陃<i8bϸ|c{ +QAMQD>{tUzWsPÍsK|j!D)GAGy#kW w&j|0esR&fhdi&~rFTfC gYBu1%Ռ9:-~HdzUз#k*MAv-zgreCseH_?@oO~i2ը[@^j^YNt2x搁2F'jt%*]jy|/8et~Qlx֥'X=bO9&G6L}HEF齰~LJe1/Ɲž]j>" ᶠDj"!g_{ܫl51dV T|zg{j-Ue8 ֦e;#rT2Ҙ_>_v,[- BfaDx/|6Ky+>8}PopGG#o,1C6ei4)p[?{ALRveei3'[so2bugǃeyVU~cʄ ~oߘ,lE}?f+/w|D[\\/.F$ѰE_ F \Ľ~ 0<4$†5^t#@RUg)$)tW_o.)Y+r=:1 nQyk?a"͚kؖ 嵣t OSQtY&iPkm4lG.q`yƭ Ԙknna-(Du?BQ?J_`2厛;)jSm@t.1J+x$xrSqs&O :v+xSW_<О4b,"z0=滫 K R"d lW6qۇbMz5 9,^حenrQy IO ܚ781Eǀ̉SgŞBmu_[8$;* MG+&Swd'{Gwj7@j)N v\O&;O0CȹLp"~AM3 )z ]B<5|!W& ?b'PSv:"W>^0dO-q&$w'>`/"fOλI[HUك:l]NPFI- JGZ;] iϓד`Zd/;o ȑM@笐;@rJXAmoޏ00l5v:3fi'uL$q߷?+-$\}&{v?Z~.W-M8`~A -4`}x-!܅W 2dji0VLb Og:f=&h0J4W=$1$yhL͎I:cw{|̣)J|t'h!'Pht.JugAݐu^=?aҬ+W<ϭ9zecM@jj[*Wc'\FfuRNid|$\IIN$v,,'AũLDb2Ez wm-D;ʸ^q2H@Kbe4)c.,w.E 2X#MwXKPb`t/ (b _LmiEm56`ziɥќUs0zYތw'a`^M0ەOا,/=7joK9jb0=@Ez='5 ǎfqjRD˛%۴G|qbs\q93G)t1ȇ|sٛ +,}cO Qaθ Ȯa!sF!7 /PwPKtd\ԺPa= T?XI6x@둁:cʊ/ZSf8  ~>#"{fW]xDL6O#c8ke`bben,D&B;vԅ=Lj0x#,W"1B4I cpnS$ Ŕ|lyd;߭1ښ1)GUp}|g 8GuCc_A9몉}Qi?&ne]@1l}nqg>zl) N"jXLh1H\ y(CUDFz9eIaJ0xTXW)3mmpKM2 0O\chvB{,0 6žH׫/A2l75Ip8WWh Ę@%qCv`q9uƒu^NA=nDXbGV{):7! U}j\i ͏vWPUبI>=@@Pqʿ  ޲Ye+i 9\hDPAv*RjM ɱ]VĉiM#$bĝQw/ȫ1;rBA]]Mo#5HRfPZ-ӮK:YОԀ5fP&Dam4"nI3ٍz%@j"@ i^먀HKďV0iȱ31h 4H'P,І* ] C"NkF[JWq2Puו@9 Kz#B-] !u$FO"{#<<-(Ϙ:paی i %]nxj3.\<RNT/ 5ڪLҜ DsI94qjN+xwhǍ]Jdb9?GPyn='YoE[0p 6[-׌OBR;WIJ_ѠOu^(KyU#D>bҪ:1 A#J6TMV'C՗cAoWf۞6ڤ՚};褕dRvE4WQX#`g8@: >NvI!^7A8>fH(DlAD7!T9MgU'XUD抍gBhH}CTF{ S̨9YФ#y"kCbޙ3{+UP̩n,h{ )ww0L&M,c/6K^yx^ r]껋ajL0b֧TaANrtiYNC8?9MoZX热77da|T-]05#& ,Y3ۨ7Y?r P~Lɪ?!$_)%pCo骃=;l{nJs c.$%p%8]~J'oT[eNr/8cO0 bi:$| !ftTVCC{뭂R4t|jnOeqI'_[]$6_M5m,U}(:(LhZ*Fmf|Li. XQT:` ϵllsx]@ 䦡ȹdf~$L+|3]kp iDČn Fl.2u`‰U ѩCBU !q8:j=(}_[9_|]C/°(|IP-o"ٛ,~CŖ Mg; o!y-\yIIv6qB$Th_LT5f-(EȊz] oG]m5<9 _ Da !߼ xPL]gV7>pOul5K(D,y[3f>j nRY;v% 5|?-iĀ877 t]vip9%  C* Π" dkv6'uUJiu @l^T0jrCKNMFqsvTNC㝍B8?AMu3Edf+R ;aV s`-k^_!+ǝ7Z46bKf݉K遆6y7ugK(և< 90=V t'iNBRYc=R @_5iYg+AGxI,Q*2mF7XiJ=XX8re q+Ӓ襶kQ4T /{s..8G]$΋Fs<z]5 p* *+@Ht ōЪa`4OZ˘w)HRIʾ*b[m۝P@xo0[;*Cy 7ҧtz[P>vYC~O=3${@HW6tm$iJ~iߵ 0-l8g"m :?n73>ßp~X wr4ް R ݟta2DMt.撓 ekJf eEs8D*՚&_e^d48A'%`=GK;P4nAdX@y+i#gz+?9ߩ]\N޿X={3v-KX8V^@u|v^_+b>[jl?'k6_ŅYS QMIg.>i؎N><&oH9PȺ:_OT oaI45w".֍v._ 9_~}g+۷;<*qeE93Sfb똚릒"H>)Zp%d% \{{g I \pB\ 7s;%]$Ŗ3uJhrE=؎ vI+ q,l\nڼkG*W.YB4 ,?Na4kH6^OLպĩ\̮[wgR]yq'"hxwOg`s7.3^ 3!I~oW^NZ]w9h,G(86_Va8(5#YQD/*xRƲ8 sOO7xK3 @F2 :{JH^N—12VЛ^"\4xtz]d5",N =}h=)@`nV(p{?`RD-9ɛ0`9^X#YGCIoXj@2Vmyݥ>3{MS(<ixuecdz#~I(1Z*?_;J + M{6{ [f%<,Je F27RSz 7l >u{>eu"6bo}M{+|\+O'1$? ^)X>}zԽaɼ%#q(q]~4iq{XbA{DFI ۥ/5Z~v\/ <1L`:xdl{3Wa4иxxZ)p@d2/yqQR8EË7֘7 sKG@żW,фϘqHO-i>*09 W=l:[X5I+s1Y)tn0`"96YW9 $b&wnҢ8GL;] ߽G?3AXQKޗmΈDŽ^/@_Ͽx:\J'b=LI!M;@]TkqVJ_!%RTo{V zb-N-csNf9] x!;}S)Xɧo1({T^zusQfe|JC.CstFE~dy7 qT.PDŽz^F@y'[^3>^и/mbW HM:1Pc5I ܰzg`Hsn3S72+HŋQn9ZA'-PUԑ2H?^ v +dF3F}=L(ʶݱ?F-qܭL:ݡJ9e!@W [贵vBLѵhW0dSmZ-[QpʧQ1j`Hj' z 웄r;!V "iw[M7Cδ xOl. K8Bܐ-⤘)|_BݜT~dJAli)Ulși11rT , 1(Ź_<ٳ% Y] u.TXZVG IF#r88/okV~=S`WjmmB|ǹIVp&0Tʸh3J1r'd+/0`݌e q(f{\wu^PeaL)WT}_5qC+A|kɮVm8 ֛#0X55ٴs nʴ|5 :~QnJ&3`T[MFfT.@&a\7q.lb `19 b Aٜ=H#Okұ z>o`Mkxʓ#yPd@덍B|ywWrF? \%|ީ&C@%? ;:04Tf]Kc(6@ӭӁ{ljůvI`Vdm<:>yTNTc%nbjVLxu1!ИAOp(SqPA%lHJ?Q:cmX^y6-"%$F=j_BgF-vfI(&+XUe,G -VJ=c6*d9SlѤo.Gk{uuTMӎн' GboP!wl#NڼzLɚАp;B0UZ`' [/27K`1m xݳ/߾o3%]ܰ/PZ`MP-M?t:+5&/D{Uؔl©^[iW6B2J/O,G,"|Z{I>\x1ʥ~0 :/' y@WW,>S(6p]))Vup7H,i.JnӃw̃^_OэN &8<*} ?P|]zy~d*6\KtQl-$`aX웭' e*p{ .C7)߭=nFci+qS{0뱽9ntu @Z=K!+=4P+]{zDF|]U5cS^i{؄H#+hxȀM1 (+1iQk,RҎ +F"7(27o6h֠B= {LX~!RqG>z,્g]Xj/V=XtL3SXnZn)NݚZїT28w8 ]K'5=餼?D^c68P1IHㅹ[~R[<|5ԘM.k=\4!U@I C2Hc#7U qBDMIׄ5P;9 oK3Ϋ'mDz[JDZixr\'P y&_]ǖ#{pH5uez}Y[PuaJs~`ę_{jˬ9ȅ US?A, XY"T!ǒPA8ՊkߊVi4Jŀ)ldaHƖXCzS+s3! z^ǕTǴVO eoIiħH rYK7@Bm2OfNQ&V?rE^bDGRҹ.tq m4eUyz#iA4[ m7) Jv~Qu@wT}giNZygJw&Ǿ+YxwZY&qA?9M>51(t[ m_;0BA"0AɂEc:Bn܋c!د"ŇY}U[*(ĹϨtG oJC0 Rle3d(*퀫O"^bzPsʖdqҕoZp[-˹!ǑMc}V/V|X2ە#? IfhPor>AEe,^@#{ExɆgB% Z<{ u&r{raX'O5kZ繃P1:)SZ!dccR2}I^Ih @2knDS"VNr' /ϟ,w1'[;Vx 蛞=x*XOEUJ/֫ȉB| Ay'(I1LDP{m/%ygMV[zvF`u3ƤˆPS;[b,td6cbz|&Vʑclb6-WY-qm!g2 Vmr$kl!C$pegHHQ*,M=TsX11l#CNvMsuWw 5 Sэ$@=fTݏ ? ݇IuNl@B1w2ISCYgJu>j?v"Z0l=a?:8P;>t<÷WH j\2_I>:* AT&LwXm=sl Ro09=@r{G7FHzj9X_pSi)Ri-?"MyL=-X$7'v-S8Ek0L4d9> N";ңD"ҰB ~Դ-2s4Z&l!(S4ZFyoYc.|l<.Wi=M=85E??>D4P;pBW15"hyҽ p:*@]xD Ϧ"/|OFuk}i{A^JB)^A Od.+fDEV]7 p]9L'uLlY\MD1iT8-';`MFG.{fDyj,#4pO- )1}1r߽0x5KJڣ7Bۯ8Q'OR Ę߇ ~Դ_rRZ )0qw;,R߲krSt\_lj@ER;'e6IbyR0/(*N.g-{4ր7s4T @+QCp( SRHkձB #Px /$!@"lsD_FS>w0δ'q$S{x?w:["FbI\=^m9?; <-y4̓ te.80bu"Řie%O|͊ jN"6걦Й<080b4IZfWM( 6nOn/XAzqQ`ūbF"@WTd 2уU J{L(oc27% ԇ`+AqVKNU[c T6??Jv ^5L埔Cz<\ֶ+̀ ,FnOW$%eFDb{{ =Oes>`di^jP||n@Ugr  (4C0nUp}+TǶ ^wÙY"(@h@5&9 ؑ b_"'Zp^Smc "5 jT~31E8/KYNVP.uPpZ+oQqDͱ0‰vYak(6L( ~~PZV+2+Aʢd4nQuuD|/v߬7;qWۅ M լۿ ,repF<z"CůVߤ.p^F[.aL/JNdHfy>#hgZx.Alf\Cݬ}ZÚu5cGVXS Z{*[L?89+/lsFԦȾF | nSvCK Ly>h!E?t='g>yoUtuXt9RnK0,<.-|:ZͩIWT\ O^|㥰d#߶>knɠ%zH4Ot"\Ehɓ+>J4H5*#1R;&_PoGkO̢䁠 5hb6mRPMRfE| S'=8nQY.jw (Эcv &85/mS2nitpjlek^̣Ŵ?%79Oօ=;ZPÖRhtaAhj33P{ KEӇr㊼y(6oO|ߴ f1"@A7qcd*O,96Qj, ~b Pp[|!iŷ:+ -2InP·FU-)P-uΙܿ|ewU%Z»|<@< Rp!岉DJR{7f Cy>Ћ8:MPeA73ڋŢ&qUj$sjW\EZJC"pFSYSRPyׯx<=7\p(ϯgW(LteY|-xՖwf~Or:2!.05fȑ1W{t0IveIKM*sJ:[P@XU@qffqZMEIm KpDwkq>1= k~-& _cIu:U˸d|?8io 23M5HwPHRoFg#}4/=&5P@UȞ޻?q,&Hb)ۉv!N܅k0v*n|W̶f$pBM&7:0|#̝ K C#e*d!Ьl{>ɬ~fC  f~~wVo 6tu%|Mh"d!L[%xc~%;8<"/0,Ů>VYIv$jHs&p~>!7.qr>H|)y\kH+fCA ZI͔ ]3([k8 oХ(?ċ:J2݊p 42eH/yK%k.vʥ16·\QJՕ RﻖBW쒩z0.%Mϟޭإ)vcWX63c*AMܧ#b2ijqck:!{lM`2; q :-8>lp~`}G; _I4@Z˰w d]m$ h `*eGF4Ƥe{Ɨ/NOGKwttW^d?=dZm5t8'\B~Cv+?6[uV!$W~^"QG;P7{kKG@/0=݌o*p)18-b}dYnQ(9`\˂*!wHs`Qp O{Ԩ+C*s٥ zPgkڢ;Τ&e)2U[ZJ ČR:kf\kRx^"߻H4|Ln.Yv>]?vmg ;t O 7ĶT:dSj3Yvb[Ld.yVĦ,}N+f#Ga-H:lT`d&Yp+ՊMv^uR>%d ,O.>)kq2`2[OyFyi# X]uz{VRJfۡvXjSzl[?(ܛt6A(3RZ#㓴5q/`!xVXmS>m˭-kim M߭Kf]Q{ڲH.(yheo~0w=0qB8&R`_Y~,KkL*?۩Nto}$"Ҹت^/U#B f`:L׆=ǹx 2iO44M%6ˀ*@FPZ@;W_n׈yD#DpC+TKf<6ܵ5+H@> 4q1m2D3<$Ҽ0\QOꓰ>Y *=i:7yۈ4gYq@.:ˣk/ Su`߹v#ߌ~'J~ 1J`0 TK.,2֪2H Q`\( ZApzeݝqB9';FX.xWI*YY}O}byjsOrJ.gUWqfŦA!.@+/IĝCqM٣;**JSk' iU+l>dn&<Ţ-Fo+<xi^| /s+w<_Ja$yǦuK7&N4jc6xry}[=8.u~ b.G'.x knΥ}^oLџl?+=Ѳ Kfـ_F|6<1 Bo(K5 5ncCS O|UT S:йknLkٝ6Qݒ[6"O#:wsːKs7}x.!@"C"v2T?w|5C|vɩ٤e^ G.8i!7=PT'k\k?*zU[QWDo9w< eYQ)q|_Fr?76/B"S+>PU_@&6^e!lSJ;k}j,\ _(bu'|gC E퀰?W{p^bs&Y$iweWRlnvOaȗ܂Aֆx.?i7bܒ€jJd8q(v;V V]hqs=), n+fHk?.N3`g<1ē ˾7nT^RO.!̌e{Lu+F~Vnx1l7@\H\f%UfC99ܢmb\ʅ,>Xa[%x("D B YL4ÿ1lM!LX:W|=*XF&㛮4 .8Ujܢ?>o_d.lJhdl14\oD0>gx'p UF3)] \-½>XZ)Oj]gi*ez5Υ\1BΊ, s^ ةtؾ1_8x]m6Į]yڛPj8*O= >AzB?8WC'ev?c&?#ޮ K!XP_`v<(ೂ0xn}w0,djގ4 )#(9=bQ1L7I-V 4xSA7V&orLuZՙ{)^$ 4fKXi#U.NqgZb'y80 j{>d ,n[?O.D%д'v^p/‘r~oZv(fQ' QNVBI~ >啞ޙ LሁOĠ0Ny,>B#)քw2KK[1ϵw?cٹʎܰ7MsSJ|"XY=)-z]D4Zgr ۚ%|خL= /X챬mhsfJZZ7?z eea؂ͷSV&A}]fSzbA,<K_EvXGfCf.y%AJF q*bMwgAg%bB3rx␪WR}"pI[.^P\`2l*4ejuV~kĩ1Sbu RhNS|?Ԍ\BzTnJ]\h؇tfZ)1Ig<4˟E" ]YXawr;ְ// dzgW'GL *[edQXEI'Mj:UU8/o({i|K #l8T {? i +d#9~a~9oXXBEb+O/IT˗/rIAZM:kj<jxq;Cz6ՠG ? q]*oZwaӽew5f꠺9 mݔލ8i/-͑ 䡴 "= }|7*gO0YY!9ʫpaEh %swȋJ%`DGOz@EmZPlu0*ȗS|>s^wc(މQQlT-bE(~$UcV)Y1~SL-mcW8ޫov,scWD&gOpW9 t[ l\AVI v#NS$xOLGJ=-zŹy} sU%Mp̥8D䃢r q-/A,E{Dop|vk;[ ttgB LSqI;. ]f-(V3չ#+&H c,99Ϗ."WI~ %/svKх5LJ<rn.c0<0_)y{([1bx̂MV!&c58nWBf;\4Bs-9MF[Op94S@:;h;5*jÇ]b\hΛ5|:g~tH冄x^L*/YVjVL#Fѝ L8TH BI/{Y@H  ^ ;ĠaF`>mcI*j{rwd{%)0#]#0+UlQ7k<쯁P!n!d y߿s=n%R7k~( D+Dp>_Z1mNV8)<{wˮĖ":1kڶ+p2M< %kt+ue!7ok%фј~i˨ ~ %$kP#<1MsEP0d& MɴZb&OdN_lXvBz2x&&QvD1~IDL"+IKM~I /M>|+Ed=^w#u:9Ƃv Eڳg5{GnC2E< wFTgTx2| -z%!e4 {e/kR|Ҭz;Em nǺ8| j\^9#YHBNP<&=7#[zɄ!U;*116i cڰˑ٬ 19y1&/nr+9>3"Pi0ߢD9$Hkw*TCzQ(#{oȁ@vܽdםD+\ Paʦ7͛E1qX[cd"6:؇+d?|5WjhBer!W L t /_ct`6C.lDx8ޣk纮 c ]sg&w;)|ӹ~sVa,A)c13`5j#Ԝf\Jn|2P2%nFH*tZãYב)&E'3hU"GN(^\]xGxY%)L|D ÷.w kpLСL{{fZ~w*mW <o_A{uտjg4"0lj#P@眓vU12|WȻHb[frxL0`%̳ gTig}h ewqudޔ2lYU2S:JLW7dHIo_[߫EjW{R?gޞ~|u3~V6~~ǿO~N|=}]~_B?աqHs+L?եoo~YouY߫zeU] OI|ٯT=~Z?W{]>N~WyWqVU qH?ե_*Z7z2UQ~e>[|\~7]~վ~w'@oL&hL賹1ZH'lgNӳv¸s#xp=_ {^aL)( VYnd%a ӝxesl4઴v.D \.hʠwUBOC(Y|* x+X9f)֘^Nu4'OD$eU2Ա4 ^Ap%Nn-l%!ۂ+8IFޑÏV#Ž1pX9P#td>5˨5:z,Kne2xEGmh$Q2V)CD_#t{yPcj(4=3 tY|1 P("Bܒept_„JT ZM1njJ% Qh % o`QӃoL`~π4nPaaS&bɠ ?ZUChWI ¿F\ҐI^ps6#TQ2q5"24G' B?YK:7lGՂf٤Z=U^{(!{/DTyOlu! oם9e6kޑYs^HzGbݼ]XyE7*_}:+Λ.^ u]kKӼ%L?I{-4h7N-x֝L. 㻲lۇ]F0"g3GV*@SXw! dF[s֪+y~m-̟%ǦxEvafl<%a4 )'q`͉3OЎ3P)#.Nyƅ4np7!"jlb0Tq +ua&yDll4MEeӵ>R` VJ4i%_zqIj|\3*o >zpBKUQqAj Extr<PI̤vBQE.g CE,M2;Kb5*ygz 7Jf1y)6h~!;C8xޭ%YJHX5,u^081=xb*,CsY _[b{@o H H/ H*:u~S}# 㙏زkȊ:t7ƱAS Ãf@,cMjs. h*(B$@·ge$_ H}SBV2fHumH!!19vS#)}FY ^?Q6 |2W{?15InuI?IZ DliX|izֈgRz2hX"nJVuB9QL)yÎl w!ߤ3 *>ZۈM3>uM$N2l[ ˈ!o6$k^^i^7bY`JED"΅z:Z@A2O 4AN ח=.p߆]X,U@[5y6lO?3kF^rQ+i y{C ,9Չ+ eނZ=)YǛ_afҋޙ;?dy2&hz Y<{ NBՉz {fP*.CӒv$Z, /iqv" S$7՟p ` cQ:L@{煋g{~m)h)lAɾؗZ.KgjݩŀjA'.eכsWho ,dgjn&-ꖥv8&LVKi$>_бޚS8/t:qRtlӅF|^"H#߳RZkؑ1H)1ʗFdW$JJwM:Z_8>af'hlGqRuO!$aXg+R&{cUsoAJ}mki4qwf9ǧhƽqõ/2uniICԯѧzy0%R+BJ|Ђ.#fNT>9[D< }a{J{xqј..!Q5[~岑7,i&҇*<(:Cy#߬GGF!c2FiZW  8ICHHV{9LHᦏu!q]RueC$}ʗ T.-3g7f9 Yj;xjUاMitz֏a0_l/ql ХDxJoˢ$?X&6WRܘJ& 0`p^Lfm4Hݏ6CH4LwK&S#DЦާdclS\~ޮ'ūnH.E*vH8~*҈[ שJ^B\r\D?1y"rU}?,,Ă8U5p&pԸ0tMVJѮ˰͒ӀmcM1t\.i"!N~+KA\@[*RC5RM}G\d ft d1K\4e&Ww  BQ*g~4R)2Ƈ3'R/c#7R 7  Qz¾ r@10Qh.p jokEUb\"gϽ dhd#4·mQbG<]/[p*m_zp]&"*€h[^ޮ~m꞊_{IE0>/y\T$6(}g]!*c>JK) ^,b? KrLv,(n"F\»5Xi2!Hpi%G(%ZJǛ .zW]-"gDiSrۜk1t^hK1m;S L%R~(f .+@9ߏtTulBR&VhX!ڸQ%Lz-#++X2'84xe/*tM l;-YW9腤1_Z}[@P+5oW'`,£ޅϷHݧ:YnE[^[=dq, aYo)x0 Nfy|.eq@FBEL62.uR"ig&YNS}&4PJC,Fi!(gk jn#24a*F.4Y|%g}% KWZ⇣jW{l&ϫq:.Hqno=2~e\ghǗTla XoU{b̓WL˱E|E-+\dӕ]46=$efǦ*mzz^B/`ˠdn: 5pV_X#EbbbAx!fDyJ7:xx Rkpb#;?fn_ü ǕxmMZh&7 cPhgR* zUT [}ӸWD8)jKlfqTU %iGd0 .'nO@=˧GUn{Qܶۉ )0b`nF ҩyLUK1u$>,]~UBz egM>2 9pY0.Bۥ9cg8;qr^ˆӜPvDlZׄB*CcK&:9|Z}QLsGsP|1󀇳o5#qB]dŭsV*{TsVB(|&o8tĈ^iYrmi/ٺi³5N4Ow@mD0fq|Ekөx&;9$+|eq6 G 9MCr]Q*{Nr"ϨyXНKa+Ҷ#~>gT/^)<76ʣ)pҡ = |IϋXOo ӈ҉%jQM0- %c0>BB~pR04nuV a{oF:I%ɘ T4~{\:S1+vh'#5uRG텭}Aijf_ljʀK{{RI k2+DL6S\jf}CepW^ Dm?k0ϒCvW} sH1&2C9-J~v2X T ekݔFwAnhJ䴾anDAILj C`AG)[@h4OhA^*J+3="V"XK49={ g,p28?KC[wKߝ3&sEHIXC3GI5%ԙ iCMǨH񄃝!ѭBWžvJfe"&@k3!L82$ڷv\N^軭R.??K^mGH&wK.GM݋toٽRm8i9~ϑ8sϝ™6Z ΎSez8m XJjG꫞p҆9.]tmah(Ꮀ5H!*V z]G/_!{}.;қ wxH#,}? "w#ǪxPȮ$ - CWv+(m᩿VtkL"F(?b &|zӔR0i NjْBk&w-Ɓ2Pq>, Lpo63j62`>&,%uYEh$mO$ug&B1]t}.OMTܲTWqR6s+Ui F~Bbnkk캡_˨O1~fH$/a-݋D/(㓻|?R1a>Hi4]8;G:ydzpx7˦i(݋+d])qQi/vcso\;ZXcXD|uO'bpR&);˲%Sɤ+L-l%Qgi#YAbW ;څfbAf`9ō,YMn|)U99ڝț?@95-r P'I1~~%lr@4E%䦴2S7rqjs1īvsܟ AkBoUrS,DM'zWꄉΞZpV72B2I)1`dd @OcKЅYt (|v=OB]6tAcW-"B rJ=k/Mp<呢|XA7\vwng1mbkzG\]9SȉT<W}\bo~xu^W2>4:!j瀉Q*: -qA5-8F:W&,i*VVT3o$q# Ox"x}o|2š}ycFWrp2:$6p?R8ءE }P2/Rf oFrt[Q'm[UGED;:0,۱#?cV6qDN׫3Gv .C[p5i8>[1YL *T{keqח*Ʃ ǙPVS8eCSG%|!][h2NA5^C4 H;4#֚z&YVd UQDP};&S_r¤`!b5C9_ D}&sm9spkN>;;uMGIwh΂]r=]?ri2t!?94mQ)0S;++((cXh//'@>ogqz0s*Qh;tL4֗50}Wa~\y;=/rAFt9Dk\7H<әS3Oa(s^GCYF3v(+/a@D`_pn0 -^UWFrksߩD1$W@#GK yd]dڈNY,ԸY)y'Ek`lWswc>x+N-MRkS/6v[m (o_Y6pƨ}[`v kZhϡHJ9p/`(߫T,z:=1i,ls2Ag4y EJ7,=9vM3+{h&[߳O#}:>但BVzQ1Tش~z bJV{S: Z`YSf}&g$fτ 7*8nSëH`A:%˻%묖;!1Rgc=Ġ @2ŔbeAu,>Ei}v,E}@ =98kr#RZ:> d0lM+!DH$bGB $u=yom?c[}c^*s'ViD]T:!4g(.wDG5P}zS0mC琁MMAƚ2:"&}{mEnFMZ](2pජ@A1xWwbvkksmRD2!TG+gd]멘&;\ep*MF鲘zwᢩdZy<Dl7Ͷ^ez,mqS|CJ[ӑ4A)2&ǥA'mhհƔ>à*IEyW47Fis6rny]|I*L7 PI0{UCyڗ"-go,@ V|k{_y i$ _L@^ZCCg2Qs _#;~<=@>GCn"ͨEV|^FN՞dd#FLݑvMɄnèŵ*!lO#<7#;-`/:?v)([zݟcT݃ml.C`[ T2I#xZn^KVN40`ưH)m?(FH* լ"S` E;oY`3[yA*p@WXExFg /;8#˹ӫ)5|vvTh~И}fܷo1> Sr#28۰).]ͳnK /@dW"徝Sa\32}HBk2E c 9W"W7GhT#G;65„ҁnQx/&vT qA, }\Aɠa {>MVv:[ ֬nq<_'Sʆmn7Fׅ*nh~+K?d!$|2Iнa-& 1>GE~ᜉ8PmN*`Air$ٜ:PM'̶eэz/s6ZP $ME,-91fj|1l 11U]w1E[`ے`-՝.Je*yUY& H2^ɌW+mj](OӁ(n5nm 2 ]R1 i{om=pTʼnk~}an׸\7TY m=8;6 ,<űd414'6[}*\]ZBky%Y?/gH_ 5QIW]X; CTTOnBW+ܜa/%6ĥơ ɥL~??uTK14R؊$s/NdZRMÀ LQ@  UnX0/dpW81qVTk[;TTWdNC:!WF拈AD댇5! %?D_ۡTL W$!t:Hl~cېOv*<ө M(} R%iO Jq|N|:dF R]u.hjwinqF_ ?NˊCJh:jn#~)7 .݀gzOܷVf_&mm|]I7Sl(suf? 4\I'')! u52AxKˠpC 8|此d%1IQ8 O5΃M6&^#0RHJż%)hװwTB9&ek$4qߜlbb5I c^D7J+GP.R"j;[bz%\v #\w!|p9h֐Our"bSmX9b,w)J NnѢ/ruXqӌAqYʃ':ԏרQҧQ2gӷW5֮cW-51<`Tnv1޸/7dZ6賙 @H;%Z}1cOj2u1=G~*snX୊QH賠jn$7p?m6)cMX)IՄ;#b2m-AFhڜڪ~ZԺ[J'fW9v6|ث 6|"zlw H&0 Ϛa,0)bz zx`&kFrj]ghIbbunŜuT@RYVd Ñ0 u0yi[sF|-ڮ' 7#[y4KyT]k7)E҆E'~71SZR }QI 1'<~#XƁ[Bqg?A"bq4.N0P0gFu4? n^3t:W+.|drF&(!np?Hmxw nt* ;`t8 d?6Bf|6Jw_r?5VQJ4 xWmA3@lA߮k$Zҭ`Gc8yzxۤ"@!M.6YZ~rCS]u@ /n\.1ѿ] cD.1Ϋ he N tY5'd=6U5t??oCa`t٢2C\8*xܪ߈9ZX{SB]wc [)!Z-Nl5 cr*R !B9q[W{n-Av#ٺ{a+5`"\(/rۡ4.s'kXm(g( V՝U&~/$)\.|2bѢTJ}:G(Ӝޓ\ɰY ۦ @7<<  /_pĊ3AX NfҺ]0V PVƓD *PTL )U1鯸\}_ubjuטXG @Ъ\a#悪=ƲnW5ľJ@oVӏߥmvp"(,$ gɨ-N[Tb;[0+9=00)=<݋#ƒг[.+N4U]( -*c(b) {>8YWC73c~]3CJm0Rn/@QhOē\dkJ!NU}CL ? aL7m&,kYI?Ap"]gKK5 O)fc?kԓ'kT>MH`s<flQI,fҕmEn[w1+nMȎE ٘-kAַ`i6H3"8b4C]c̫4:%2'"0ٜZiUܺaKX-7뙪LW*K}Q>:j/R;C2sf1EX A7OqHkSSgDjUlBy1^BQ 77|/k)լW" =X'/ 4V7 L^9}n]*Yzw-w3ٸoJBgD-؃xLjc,_'X$}:wj؉A!t~ꑸY*m|"f:C/wSF8KLt}\#DERMpu!Cn]?Ռ>߳VAh>O7̄Gdr mE6CT_HPN|0 $:8t)-e;LZXh[*o/ҷ&s)I gҠa:kOot6偨 !w\=(c^ekbq5pŲxJ EGmqDJ,Oi oC@5ZEȸ[M ,;L~l(xNnYn+%]+HӒxLpvŠRMA,N9~yS~9}!ðV^AC0*>iRTCJm{K ~t/_RI2o:Ek=\zA̓dcTň`drZo`k=w׭Y?ܽBSLJ U2䷺DG ̻ >Zq)Ncpm?D[ZLlpjzL!ZQ %j0&UiT Vr=Պ1u kv `M0CRrXEӁoP5c}TA jG|\%2+YD'Ԛ34[y5^, l*ZA etp0xpY{cª S&Ri݉x[ְM|$=M7$O}=/1ʰy4J@D8P%UYp<0pST5]mfI&eh:T3LR#v$__!U\Rꯠ+m55U]:3u-#bѼƲ#*̦s-dZ*ÞŝTcPQQ*n%%v5D-{KUpϣB z*SfB~ڦ2T'Toclu4*lyaPfL;]Ojؼ#ݜS1XhCiXЮGsM˩B,iGs^{+}:5'K4"?LF\J#B0~#Fys_5`zi1.l%?X]OaV2P{>C}SrJ;`fo5:IvpN,=S *5`R-"bת(ݑ1@2v^V t,ylR;ET2Zp>8oFľ-Q^`4jUUJAgSm$T='(6rpLc#Cq'#MœNg:,* 9z-wxO5Q)隇aM'ħڧ:6 Xp 0d%fZbEtyX7@;Hr%L!xtKf.eɝ%Ҥ@ڔ}a34 *Nm7\ _CXJ=IGѼ{)iDaTԵUiTfSK^߅7kCax{K@ _RإK#FN`NۗđLE6SPGaZm_HOY BtpȖ=ȇ\Sa+ŝM[ Ga-{<~C":fh%1j8Py숵% *>L̓byEL:o"lj5h3<סvV=fC{]&ND7h-y¹5ƾl:x _1qcYmW^o-sФ%BDA!JUĢ5w7w+gn|Yz-00 gѣ9&4b{qU1wi%+8wꝙ+$'NF{G`fFi͚Ys;Tg,b\z5?" N\BInDi $c'ɧ8u>F1)rg}tF< OA+ap* ($ދ }\ꐍ/bXFƝ'yHheWI|kXn$|q|{X, Dmf3}wCJgs&ۛJ ;"amK5DEX~hef>W l.Gvz>6xK*++)زd3~0)fv,y\3 硫=&x,ܩɅm]Рw_!P44}>6m' fX:BFpWRχd~#Yv 棜eyj?DMkK_Wn._e-ّq#аAG3 љG,(`qu)6 TYq(-R@%S6uoHzFj!ȳ{"(x\3kȐd3@&^^J>w`(if]ݚz.E3 /\ 7x3φ>WzN^)z'25&>h 0SIɫ;nacUnS`^[h<]6rXSFLh#yiApVr?2{b|+UW3 %kuQk ?!;adpI7E!/sR;Nɪp]O?[5p6՛ ͽ~<& u_s#͋3~q .14e F"ٜ4?t?{pm؁?7>jIExkۇk7\,V`0==sXa?K@K{'~+X+Bi,y1 gKBadl}? F!8i1" RElv-E#FvXm1D<u B3ØSt3 E7z{Z6)Q%v.kKpTjo0#-/2Ή ecT}?uG$A}'z7-U 8•ѓ3=Aj;~ $EJsF_^Iv:,j5*:Փ͈S8ZޔTwMF⠘0fk%ۯDR [:AMZs&ck[Ur;.ޭ}\W<\7]4r%5lpF^JJ;Mgֽx 4bEx|RZяjUd$p+ES2ԝoRh7E8LtY9s!5M ߚ$#40^"НJd*<dZP?KJfXN# .^'6JUcH%6)|/\A1eϳay?s ;RW0-ٺ['}K0Q%ɸK4wb)dtg>kCI61kXؽ,O 3 nUㄒ5gI:T_U5\7_}B)@Dk`}v8IBП2jR=o9'/A} HvĖMc6IDaY<(Hȗh~rR+PWm H%Vrh,6]EJ(* `6hjr׎LAm p0.݂I xlǘ>X 5̐-?f%72 `y;C@q=e?##znֽ"ÑaRi`mh t6D9VGRv# ,}5`c DĨtxlg~YDzrCp8|d7Ֆl]ZsjqYǔ]T"cS22͠(c7%\qʐhc +f~wng WyUQ&g8ݣءfSj?=/9 R{/Š?Qn ,'s#T3#0l*qHzvG2Rv'DװCWOڧYɈJmDǯT3?MM+ȳXY'1f$0|.c.)#׷>wիfgf}zf:ȕ O$C>ggo=( 㘾+i;JFx8bôW_t% %PuK_0iEc]REr-t,Rw i?p 0Zpusѡ{ud?&䨖+9*e\79=S1IH,65:-?Nqg/ݩ R1@dJ'H5J.3NWb77FݰG`K2#I@1Ky=6J/c51OĂN;~TsQDn๾"xAi } F98%xvT2qa?^ሷy)Ny۱7OQ#7SCX MZHQkɛZtY}*0U !rL3Q$R:, \1,[ևli-_ıh`>f=ޒ招9*4/M$ơ4R'PG!SWbt/UM@7`G+#ѕ,xPJ/5tVrn\J/zVeSThu_RR=^g/V9ܟFS݋$(a#P=B h x>V֧TJd"?YCJ[Ki\,\yBY\~wm;e 70I_EM#t.MG xZ{A&~xUtIO}ϕb{11t2fmߵ 9?X1 )Gdx}RPi &A))Qa4mO:k1$4\޵z.Jaǚ3<â(KH"< y+@|@"$ F{56IB|L-#6zyCTߜZ=Q`w-?>-P濲;5cŧy|Q)3(Yy'Xe#W;fW7;64ECQa2|])5P?ژbZ)t6I= p<=<=P|6J(^uT4CiR@2ENN"eŻy'^*<~b3.ߊ{Z>[dA"ac;!6vJn6՝a>e knJUïԳ5vYh]P;o^dKنG!4rUωw(A1ݏMi/24e.Apߙ'OkYIykS;~#mi+괺48aª ?<[*R ;  ؐqe$k´'iH(CK7"O 1Nb*R@bC RՐU\WVrBSL2Z0K\Sz t9xa1Rm?#o1:N3%ތR x:7>=!e~~PayIuj9AAP]qg16O5iq4A6פ|X.aoG%_%6426&W}!Z+?'8 p}0Ҹq̵pn>p2!"uӱI] 6qڹ6yP1B$`01,Uܠ+Y)Y*_̆`]$u!qq%ULIT׏}ՁN7*\8BJ$-0jGuoϗ ]Jh@oXoV4msXX/F- ([<Pg1 e*lk׷8'7PP.J?/peQնo殠 } tO0D=eB*rשӳ7[Tjh154pa~¿igWg{Hk1dt7?` YD ;l5*kCwxyPgo/xq*ڧ_YS./WEn7 LgѣZP[R]_.rh &\fjjQsTzi6ԒEſ≀7fzt?Lx|y@Qdu4F o]9[tٮFG|^I+W ҖY we7* cb2fJ77ݩ֞(Ȫ =9cz'_c7ζ^rO"`FP үࣻm5ժHj 87P "}A hTP*\ds|`e/QI +:`X|kQ.5|fN+DT|}nB÷{FLOZƗ?'zn\fn%}& z Ԝ tS]cW__Q}j;k6x<_(Xt];Xj(R.Q 7Iu>Q)oHӺ_XdzZԷ. <M{)@Z+^~Ŧ* bwglWmŪ0YU.mh\ѓj5|-v,kPWI(ئSx̓d3+uysA6fdeo [!bVkd oW?Y8$C,vjUə7(01V"<] j|'4h#98:im ƻ?ž!֖8 HRLdB۔bXcc>׏0(@JEvB#չɺ!|Nm;3t=CY;+\U>vP"h?t7V2ï!kif׿` XZP xn'x_Eq wϚ`ޖo6b!hWh']V]$vpZ {Z4T(pY8;jѡBB{w^OKR:}儐R&x;lϘ{B!,X;]cp9 L͑H_G.N*¬dS&^vn)1 g ҧf1vñywo1* p*aAkkFĦ0=/j8"5~^vV"uq@T/mqD_y&ŗ)X|,P1I}Vj:Ncep7Tg~A?XJrؔI'Qf'eԣrJf#7䠴9dqݱiP")҉Us^N%HiW1fIצVD?+_|E]\-MC,Y@.5)kAq&_Ճ3?UQ!>;${-2%×i@L#Gğr=Bad٭\wXw p;пboe4̼kn c"I!ga #Ym779ek\EW8J(*of;7Q0GK/w&$Q^k=O1n۷k8cЙ#u%b82OoQ"t_{Z`XSn{b`[MF铓,E_U)Bm9L2fujhu': /GS;+gDwk6<) 8p-zxpȤuˇ_tjH-w3Ψ\ړKh+^%|6C05(WR]f3K(PrLzNyJTw:ݹijtz @4d&(0Hn#m>P: yW у};m;"޺-L*ǖ 0,JDhW1^+c ;?k&YRFUyᖇumK:.EuTT?$y{\诡 /mGoҗ͕kd]T'jt9g!ĥMkPpzΠգ3 FZTWIǸ"69+66QCRQH }^{٭T(>|UINNCgFU\;hP1n>&֊{Ʃ*|HI>y+NYEF)= &M;9=5k`ykE.X֟]~2yu^$v$1$d1lJh1"sQ>`h-R:˯ZaC/ QhIA5|+3,.!uYe2UǼ9.N{5oG"^\wk7RX7qgX+f@W lyi |{8`xHkRXsrɬwjq{gDY8>06LuwFz` @䉺ET}m#WVG1گ@"\$^cnW&uv@wzx}t&YM\~@ ?ս%Zw^ */>h1_:Ed!}0 &)qzD6ǧ&YyܟoA&^AOUtQ<-bKU(RaMz/6:"mj5$zۙdrKiwa Uw,fia(x%=  mCEq[O7Wm  LD@6t#Jii7iӂ[YjfIa!~\ \JMA]q:(K#B&Oe,Zm-@`ǩ:+ը9K-TJ]bn07 d+`פ PS}:JؽRFg]Ýl9 ˈOZh)8u?, Yh2] CGԜ;;Gpː@ƸyFQxhK{`V+ E\DDwk#, ,/*LZ.jѽ`9ϵ)ijT`W[l+59F:giW/`L蛡S.=*<`UF؈.m'_~#W6 ި@a3yW1!.U&:qn\$tTY@vBƨs3gpKm#--}!+ 7~X֐; sUR17g1O&Nal)J~xM킽r`@ (J{G:T~NqD> *-A8,%kngU֓ PiJN{3LUWJ%%Oyb^C_I=\&R DΡ i!XN+ )^sJ/uDFi\ FO޶ofBJ[z{WA=c_m'j(@i*fT2(..Ģj2]Βӆb Fj*/ƈ"0 !=08OT+-vf|[.hͻ#/V.{o7'uYfP0 ?<8Zj·?)4HBQ HOg.ʲPG;pyreJ:G(}>'EGIS` Rmi+3jf> yzAa1ax=^"w!@]!#otV LSq1I^*jt+ZD]LjLC6FvyIFĪ< Sn;-ouX);(Ո즋z. 3fKR?Tsǻ@#k-8"n=WH̽D&^Z?vrKݠ,[tx:}8( Qؕlr?Hfg3`@hq=UF޶7 (mqǗ24&ث}UpD%&Z?vCLHmi)'TjiΓ05rUﻸ_W.SL`<GJ-Y#+!,"\3F%}ɭWϭn08dwÐl9F |r.i&BwqLʦZ_⺌k4R=QXj7H Q!* t'?AYfEwk:+3A[5xүpPW&(pRv81-=ү]j;X4^m&z̻pP9{ &o5 0 Yp|@` ]6w3{s|Ka ToCwzH$ Yy&1V\ꍖ4׭g?L&XcxKPˎw: -zmϚ9W;qSj[Øֵa`'I2qCR%;z_$o4V2= WE=2VQcdŜ<3R=2RK%@{L/:yug&1ݩF״KȘ}Nz%XOr`ZltV ~>IofLB!Gs٤  1>Zs8Bfp,|o6Of}w@RCLyN.K3-x38ku֕yl643\+7}XIݥfk*QR`QbufmWR|$B$7taFe!p6]D \>fMi i-\c,\?,֐P:9]h!AuR@ ;X߳|}Ia%6ߐHkĖT2{K73 {EL!bECJ vް^a&6J[cۇ'N7+:n胔n{MQ]ɻ/ӂz-;q\f Ёv9=MuiGm;Q`3^R;  c$׈~\w"""\\cwʵ)#)`~Q(SAi"àeIK66N1_h5h?>s8 V_;- #FzC}Je'"oҵ0GkjY OAU3ω.-|ه] !Q XL+vV$m; `ew.jq!@)&uP˓Q> b;$3M̷Ql-ua߻oUih!r.d D$Jb.GHT:}m ^1eOh1wuqٔ&ƥƑmHqKwc pAdOG2ObIǸioη4hwB{1B9SutLR^"1afBk\yBxAdz!3$+}rпP;XtzA ڟ9TI3.)`8dcXp,AV/k_#6LK5==Pز6ﴼ'疗,f݂:Ȝ*+bt7^_:ʯo(<`b^ʂj=3k"D?5Ob&^ƶ@Y`kh}5֝`rf\?7lu3xLQՙu?hAQ(?am`0)Cەrz@*f|{U1 ,vʌJvnߚD(0W-L>N+Bsm1l[' 1vZxfe`a]l<6GirV;p9Yk&{eYeZtؗo߭FGo0eٽڒ \Eem %߶N OŒ[bw1)m6/>& ɭqZ噏m0'n\\gmc ~jOdbKmrפq3U֏;وyvf$D#qJ=+ ;7 d.Л4W-qptY{fuIodf g7fv3lM6'(Oo ,5xqJފ'o 0ԫDpA&6c B˺1)3 [tkσ)-,q} T$俪!`ӇA\L~1h {!]!r|;ǫ%no'_{}ECi3G`Y7?9P~<[QYsw9bԣ(0&l³}Gqʯ 41I3\^CU d:r1 dIgR3𯽲م%DY` 3PL0[BTaISܷ%H5)e(9亜w޸hLe-Lq}d9U&IRF~ zŰ_:=B<@ w_ @Jl:Q>!}BVTS0{;`'tZL&T߳.Z"^. ݆Q :`;M/NG9G1eJm}O˘Q4*A3?y>rӧ=:.ngy/N;DXwWI'}iάd)}8d(ԫ$^n؄LEdr#|Շ=@ɂyfƏ26:~Q03F1TݖD9OBϳwX7?Ոx?KyZ[:wdYQd .Dgaм,f/ז;sڀhk,5Bpe:~=Tl~P*C/m*JT1yV0m$$SRyՐXMX}vxp$dӗ q֩} ' Uyʑb0I|%ObNDvUl n=^0Q Ojyl\5F=.9Ċ h[ 8}*-͊PQMNR /Yk$zOf  ru5"֘k 3#BBء\NrZQI78d\c)NQ2Q~oaUP Etm֚BYɐlDqC=Ns=%m)-{k# ,Lg*,|ƖNJ84~+YE n0ZIs2ah~"NL0RvĮiY|}G#5T$wt4(NL9Tn;Ta@䛹X;ipb2pp^a(_lKԕMnLzNv5v 8B5na02T*lM9Vz4׺R&I+`iq4$Ovqع#S,*o(Ȍm=~Ռ2t/&Li2:(H*!sXb\YY\JK B(JB1o6n ֦`n/$% k`tڇL͝mZ mۖj;u$ULB7Cܽ8 ƈ fxoY /l@ `ɹz{ hX U>qO+y](sH& y@?r}ԟt1&TP%Xi%1<mu<*?22hh?oS)My^`xMq-Ճ7jMU oNp~:%|'Hm<Zp7P8#}eU-ri!%T ŕ-sl$fj:KP J{QY "AOx 52%(=E=1u]&b eLbR Oz|?To Z.j)G,)"J`Ror/P--0}( R#]'췥}4ᔏ@PеT#A 7^'8n>%36haf?ȆIpGj}Su:Iן5meI>ju:o>M \ `IX*:_t[POQ,iėڜi9EzBf4^Fؐi |ϵ3cֽκ0qP-?$n,/N2Y@,Y`_ ^Cؿe- ?I15#c({՝$s}oaBĽB,(Ok^V Szjv+b~%ρ %Hh6,B@TNiޣɋ%)?lc÷'kJ@O}c.o>t Da2!XE:ғG8k!Lyƪ/|ns.2r?.u=YLg406HlevM@ym5*k;] R\V%z#BvQ9WrؚԶ$T˸ w=ސ>jD[t k=px8}E+Smݨ0I CVx/se&V[Gbl-jy,k&tHE$_uGwyb,rK!- ~V hOTmt4UI|x+_k܆eJ꺍&i9 oȅ˾6rb~jޏY?[)E/EZ?4EӺY%ڀ>hLHܜ L@jl+R6OzOlblp3r&׌[>9ޘ|`t9H%)1,&ꗑڌLu:_7"^Pw]\Iu"W畵˲D$ 7xRH#=^_ ]=4h*-h)I0"ZCHu?`B(`I1m!)~ЍH̶Ѭ:̓,_/,g;A`*^![GHym f=/'ⶵ D#qFKOA /{Z:ی:^r@S ވ"0kfB%L $typI;u]E^[+whjk'j߆dԼ%( ggUA2H$v/d4ďK(z TmUȷ1)෾E=;J1#Ҁ 3'dY%sNgKPD5V/|Gz(Aa҆M.iCb%םO}CWY`bD5cǝϬaħ/R^j?HC+05 ׸s]M\(Dp!>4U"91mŒ̡ma)1&g&t+f Z61 JD5ڈꃾx|]4w/7yR#R?P83j ;pZwϕH#ZҬF{!Fk\RJIz%|&d5>{f\R+ ܗПDPnlۄixly7T NDwwA]d9E@vˢLQIx$煋e'4Q{BRI(q.vB;]}W0?PGˮNR Xm9AV`z3v>(%!/}j7,4gݫĎuZ0zlcvrO?͂1d=6I"Vn^סN^p?}!tYN] 4Hܻ`NUŒȴ Βn-Tu^ InYYbyY. fWِBbc[ZɈ]6K *ooRtZCapo'Y+'9-Nk}ҋl#4mCq0+̧a[+K]Lp] ^XZ. P*h@g r,El& ƴҮF#-Co05*Ҏ> )6]Fiԏkms,Ae'p=^fyF2{@"\+ކEMev)4m#,rZʆ%\X6α̗",Z0Y ЀvF!)F^WIV:.L|:;-.\D+SЄC4 \@ 1A*C;HBYO1# \1vD^JKyV)bXOTBJfYp}"apΪ&yB5q_Zg-r{gӉ .T1`)G(XEW&4'y]jT'K!|YQ? [ ̞l(v} ,X'T`*}h̰:vNywgHiSBbN7(8 UXLL:߁y }aYq~m-Vw*k*t2 V35Ka.ӕݚl8;bԝY@ sFӒ 5k7 '}5ME(C7>9Ԯ:w LshV߱F寸DrL~#TS]2lTcNf"'fPa7A)&3E)2eˏ.JH+ 8X(^φC~"s )Bn|v.&R!%KCTfs ktEzm)a;~bUSbfscjM+IokҰPnuvd=^$yk 4,^lo;4gX3^mT*/t2^O=V'_ P52v6^?@zi93O>Hq;\L8- IK&@Plp{9M2E dҦ#bm /ALjC}!36skDh+qt@Z&ai?rv.UD m#/jp6GTeܙR! NQ5% 9ʲ%`?Zc)$"ClD5' ؾȇ$ n]P!l8JŎT}x?߸zbxV jDI{g><{3k oܔ 5 qˇm1] ߷矞9<mC?Dy7%{Cq49SƤiFʞUPZ6|1fzqv@Q1NEJ#. F|r /(R=Œd!rkdVxWdgRxJDFTdji2F J(j`ld[}XQSFETV79t> Ŝ&"~^~mhvR4;0Zh(l^WʷO #^Ɋi=_!drvT=ܺ42u| [R*7$s!AFv>`=T}%]>fO4{Udz|8Zx3-+2-!17MoA>8m : >(yQuߜSr*x0:]L ?A_(a8)(ѺpwI7ϙ2qwzwVBefIpv|HCog3&|Q͙]aEjUWFZUtAwX+:!%|S8"U\`/P9Q +E60'~KcyJ^lƢ:l iBK}VAE;RҩE15+^ԩL:h*kG'T؟8v:ŗUB 2 "a^<3}ծ$}O5U'p׮wjHoT2?ux<ـ]ၱA0ޕ l'XbNv^h|PO6BO,꿞d^KN3"^J+;X?PybMcGWKhx <0"pe┇,};k6v%ta~֣\x3_ {,$5cR8.f*-\"QI_J42G5"s UcUWs CwMk Twz 6(Gnmdߔg}8sԙ TqG2s ٍQHY49^/blm,ɫ)2/yY8;2(y\:-fć]C븀A'&J3Qq~F˺Ǵ ʍIa U&jƘ`QX>8=XdvO\hC_uApC8<@'IZuzA-{4w*&>Dn~PBjP9y ͆Ѿ~w *"]nSH8"y`8O3,G/fce TTBm>,r霍.9vtgv`/P 4 ]B^ 2@||MP aYG0sQ1.)mH+UVpC^)!;BA0ڪZl 0#kH ե@9qTgxpj޾3ܴgɀ[Whxj+Xܫ0Qm2^Elβ&;rK7̟` OQme ।G2nQ_.POy3wdl.cT<ФCM :"YmƎ?װK˺lkqPIŎPdac) #fWm mСD⿥n*ۻq pUÓf}bZ53@Y` PVrI{RuALmv+5gt.àY%m4`6$Q;(>$&s){UTK_z _lnf{X'\i ;yW2U?/HLB-HL;r&]/v fk<CXIE0ƀ'o@JU&. ܷQhJ-xWL.i)tJJXr~M" ?(#"Z7$;dNE54!\ ½ݤ]KK0)v)zAyھ= 'ӧ4Kv/Q  "{=ۯl+aJԈ739`jp]GcД!}E;8=4з<ԥ͘o^te\2j!sgDž1qu_M6}_md>D?23y 6exE"4(iPJ˧dqSIe  b=/'ϑH{̶B6NcyT jn {X2B諒 rڸpMYqQp,Jgfi|;DQ; L_JMjQmOz(oDPG&v* @շc.8 eL;U'֖jpT lTqQ"s0rvVZiquO{J/oc#`7%zܲosh: 8LNGǠ%1İfݏlߢN6&D21}! АVZ ZB.ui:z1{RGMp,]:{@X/-R 54OCF0o+whAE*x)Jzchi5^L vz+RV?! \d ,О8HX^Te9cUQ5b:Dqg+f0&"Ԫ zGT:r1 K/\܈o $€<z*Lj8\w1}m!@f$5m38]r{"{ Y`3^ <3k{K2_fep5XXBh#%/Fp f{S}WpȊ6dMK2kO F9r-J]TB>X[wX>ݽ0kϵ|"j'mSS-C~&BAj˒I g}g%Zg?.an8R `bf"K.ĵRL'!^{RUڦ ab, a(̞֦&vHJԡnlXlZI%xT5TDJKo}-sNܑ٬,"um<őg0x#r?g ޿#@ ]^;vq2B_5Iy$4*Am' ρ/f{x͈ZFX;n^@r-G567,~ 3kI\toIUI_}l 58f O<0bU )sDZy{=XRGUXXi:U;$m-_/Re1qUFRXą B _#3>aՃГYҖls'Ϻ4م/[.ng \}%t;'t Ȏ9~~U5/ +*Viʴ>?&q,,\0v$_ӝCk(*ˉesAķMWX}l?s~k~b+P7e:&>!):tm:e87G*v)j3NgY†5# hኞ(ֵcDо3,8(qa x:8(…"p:Qc@ois'5ź,0o?Uo"Z- -0z, +((f |o H,}@Yc7mߡHVQr [Ոwb4ּbIX[#wP d~sf[ğ:ᙊ]K+D@ep/*GY$'\Bm~'}(2۷8"Tj~1y yv4F]m3'Ml7 Gv`FЖ$ 7ri9Ӗ@u fk:=xFRupBLd{ʜ62 nyQsKjOqh|a[{ɸW`MY0\zP@3ʘ%@Eœ('\U"dm Ɩ~HeΓ y :K`1 ?aǿjKFƜ+ (| 9Oqp؞}~@7 bH@ CET^8PDǿ|,Ф]SF|4wq4 j/+F hծB|$xňV~7;ɾ9$: #|bkՑףxČ^zAJr&=8~8,1vS|(ɽfJza&';k@ {{nCDX{ j5X/)D$lS ˑ̴R.*߰q5~-:>)ixs 2=L#U!zr8l 7Q|FfĮX.@ ) =`S:Rdy|&6[W~$?Qs2Ӷc8@yb^EKÕf0ljmls=ĴV' T֡"SN]&z'*BJ='tzHg`& @Ҿgտ=t'Z#x력y3y1o@d2&'Zk#գmMYpssc +w߀ =+ @GM2S@Unn_?U(Geϊi[r!3OQpvYk YyV8n^|p6gbjR;]"]Exylj.kPQ^pd^ʟc/?sV-N.)7ѿ5BNЗ<{%U=P|/zY킲c\h&;zQ NGtы>q:UzD@.> K(4IWzMԠL_ufH8z:8GlX t?`7kg9*|Vjl].|P=y]'[JZys'E6A7An>+F]z,үx{%C%RsCP NapEO9l)6)Q66DMѝ&ƿz`ӵˁmWJdU8ZQ;?ps6\IZ?'#ˠ,fՕS=2#`s&:&ۗ,z95s]1 5u$XMV⻢H C +8#ig!)gbmZfT#EaލX/]LgaylZɆTf^SS. >|! ,ItޤvU`<]DnmAB)#|gQt(RF`mh[:?6Ҡ|~QS`~Wœ_܇::o f^ ryÿ\>>y2d?/ Bw: t`MȰq iiQegԱf2埊T bB.E V d,t;xm10"b ]sm݆6G6̡{Hp#N4yq%*3gQ=B*^jiK6.>v6 P }bo"8N_D 'eD01 Rp3b=X\,5V1AM(LΎE*'NZb ğɾp,/z.8{+9P#R띇0TWó3 JcS[f_򘎚i"'NM!k$T V@so9T:wv !*#y##\Ez~/[ȀePTbDd|`J[oRGɤ#xX4QzV g )<5".cp8p3e,v,Ay( r-hc,W wT32&hTjYc~WKچ kSs2> _820DL/%4znW;D4G #G|"B婭 hhk}/#o)\:;e9HB2Ύg}W tQMm>(S'8oNaUA0c5[e.PEʮv4zr8 kf2H#k]NL ,XC)5O rGk{R'r| [ Hi8?I_}Hpbk+13'3N/yل@;a{|C~eKʽ/;d2:buJ]/h *+O r)3Kz-u(k\(,xI|y]7&lA,ˌ鵊eSI$󍝁vGJJHŘ U=m ad3j올V5$l*;J{N eu%7-Plz#d3ؿ5/ێ묟_MS5"DKG,5 ¾M,!QD,ezaWD=2_Uɴ!cSq}(o2{Тk>ҺcqXZ!+4͂}eWeZJ^Pƾ OFP車'7_5 zp@a 2IiF,ep^GvH# JQ0cI:sTqtmњ"-$=vuq{o:~\0LWݝ:@Pg>|CW˿ ZՁQ=nfm;(l@u_3C皇qY#Lns~,lژ?<6Q@Cݢv_.y-_h̀h503pQl2L'MSŽFLI>EOxK;*N  zf'Jl ORfwˈ0`h$^Pul$94` ):<7EZMfN/p/h'w\uZ? Ihr%3(‘}le::,lл, B;m%EaK.ea:3j(˺cwL|z=A#x0.FՀ,&bndi/>{\P¢UABiFiҕpx;nL\rɮO)ư˵1qݩyPd_?go1p5mݐm:V`x_-}nXQ9䣏gK~*&ܩ Y0nF1~=*XeE|YToQ9w&xȭyu,ײg H9e/'C7ˏZ\cn|=[>OfUnm~zuё>}}]_Wm3ɭ15R+>ɬ'=ޙh>||=&~Ww?WEͿV[}os;l`[H􊾭w~Mkɳg6>MK>O>Nϓ@s>O<4o]+_ոM++js+x~!]V ~M_8'>h*=rЏQ6#8(HGz{V,&uy: IآC[>:Y#Ă'Ht}aHG=s_5~w%"fZ|Xv=X +QCm,'L*6Dr7VAcbWbz Յ@s<[༇zg_dgd֧pr@PQ}Qݶ) o续Ŋ5NyJ5+CfŖT|R;FDݷ `¡.9dW{@xb ג?[s;h87 0쒶=駔?;q9{-Iqh.j$?yY1H3z+Kylh"ԝ۶x`w;iv?ﱺr9,=[hRXVs 1J`Q޼鍠-#`k2K^Š] X%Z0V[DgꛥgI{!ٓe*!U'WGkEG'cKU:\X5`р@>wz,b1 Mi#2h"(th֤󩖒-.Ap~RE'xQA˦Yk UFSC{dsRqu6ΫnRV*^1[ A"PɌ>S^宽|4SzX?3 773':oO]_Ӥ5k2mޤ a(P|4Xfahfty v6^5%a1" vAxv@d&N8:/OWx.`R9K: 3Ax9 ZOP\_zFtI`#ʱЫ0)283iMLc*dR{IR]e~xb(v$`ޡ+3\|$S (x΅"| j(r Hx-ksQ=7enh?gFqe_.?}RJi|d%!8v8d<ېqs̷2g@`u^fMXB.4xiո|)2%[y(N$.B3E >OeN'7J$y F\x#gǢGA3U@dM;E*Su^$?Ll}~f JY~m)ȒK}]7Fg}8Gtz:5ߥ=nm>U&|k8/} l^=dʀwbtJe7&Lߊ-Tdfhц ɘqqjAiIV \9N?AknQ!b'X.OD?{Y\4ueEI,X/e+8ِdj7֝Ae,9YIAARG: h M{7D3< Zg:F@I= )ib{z rVJO~FJwZ.QHtofDI^w?7(n36Ĕ}X#4\'n U/F }δ ,iHq3=+*' llWwN5pM "x:V-6hUDB0qߙYQ~c:P:fN{{e`R*~pK+-a ÍҺbLA(軠y*"w1o2 W% 6R0 j  ^m%[F$dTlW >_ [qOsزgTs쫃G8EڥM-Y HAFRSoL lˋ tXݰ%fb99C ?yK] E@'?[6^mUfՒ뻧@@TXZ[zΓTNMX_f$BN&7 #"Bl?M'v;M\h6ge4̽6 F'\uCVh0}@e E'M~X3}*AA'?ҔBslSm4g]&l ftevPYi+FHPlR(բUSI잼"۴_y(`z(y: I H-Sx8Z?0TMMD67sjQ8x㉺}t{U fgM~y$GJ367b 64\ #hߡ׉Q{7Tw(Ji]ax3b\lH 6i NjtĕiĽA$N;t9pTm8)JXF!K84Td.6[7^ @-e%-NL y~:h_FOkE )orΉK$sH┱ }@:TB@ [@-U)Co#[ģ)1 y3qk9PEj} kY) ǃٶ?ԫ#~3bjVƅ<0;Wj*W _ln}<,GI$d$vz&a'}] Nu`JgρvA|ƛZCs n'"闺!,|Vi\J :_pm C_`$pYA.1|]57CY+ڧm4͖a,Zf_J 8;v;/n Nru8m%Q6C,e!z|~/ڕK*~H;o¤]ᴟF AȹvGB͇c9oVUx_х*@4IôMs= yMi\/Y~)/fyo*5$* 'neKTNhz˧_ݱ"򀋼~>!̲D Sl0k:֔x7KR{ :<2hHOnAE1qYgFh:RBgJd8~}Ο7_`aTQ CJr u3bgiAV:aLmlDB`@+T~Zf1aB2ŋ"U3)$̴!# '$҄;ob $dd gDMqKO^3H$=?BBKھ卧MմQhp:Sݮ`qkK4!0H2r6N !pa H̟}to[*iFm'61?. |B<(-8 SNjוFC<#Đp f%]|KB F[EwoB]2C x7 "=_B Y}4^^~=Gdkl5S)b*GZ=TC"m@(y 5)7q5 O-> @RlJ+äP޽GJ;SF%Ltr@lqa+zZb=T0WV\`&'],&yW=YnL.w:ØQIlO\Z'/$旊AgXVY&zvd׹eG\fY`&h^©EIChM nn܏;&wsxl31ۛBͦ)[bx ]rT CBF7d# v8rϼd.@-hw#NG'w ?ps`Fmztrᡏ${AnPz=/Mf#u>RaU-=ŤBM˸iônl'HwV[Z.:C(R$[G%ͶS'^vSQ'Y&Ͻ${k -^ %h6q3CpOcu45^~ UpRetg(A ;ᩯ*=o&`#R 0?f [~/\8D h vyXjGY޷SDEvyMX+SrioҎ}A&'5[tN/VȑRZ*%u=cc,A&@t`ֱ"~‹<}r Os2X,,kP'ɲsU;ꤋ}uߢUWT%Kc+/{sgw5q"-]H 0φ hSM9Jʲπ.S~9 }u@}~P#*ePPFf째}8d4&9t,{JrC^2rn z._qHz/ (qsƂ'<_4ߕbMIb*%;i g'P^@hw Pt!SATbZ@Mӷ+ުkalmkyqJA:س&;)(ݸ[-iFPR:B'VC/X LUb{<L6ʁ({Lb8OSCm5Yqa^N龒u֮ %bQ4 لpV-?1krWSMe/)y)" ɵ7#x$`5פ// \D>G0q~7,USuĂ jǢ+\t oUW?A]ѱ<_CuA zT˻ 3(XS`˹B_fxj6m e023\gZ E%6+^S3oEz Y33zѵD.6abԌ{d'|Wg/k5PX؎ -]dÕr%8؊e??8!bwE2`|H\#=%yAFX{:~Bl) L AgE~ 9kDID*&g?ǡA/3ԍ'wjwTGRYa)4dNWuOPg6:x#GG> ҈c8z9Cڌt̾&."_o&nas4&Lz|R))#P!$ǝ\r]cA˺No0Vn" Z`Ģd=? )֣OFnՆx/ ſnQ,2.%w'\z8C:!; nQ -ro?ӈ \OQ{E*&Hn=H;SMPXigigR c-!O 8r' 2FTPhZ\e}a)De!1״1c t|γ¶ҾRǡ_tª]^y8 ^671-&b&`FOַ0{{[ҼaDb^Bȶ Qs޾A)Ń^BqH90'6+6vcFMGm͈%l1)T6ҫIc0@ݣ[rj&8%h6z-#~hJǠ%GP* [ `C7 ʐNTcP^|N];Gh\?!4&K';",>B'@(v-֙XDg#`Lb1x.;"^8ԕXo;%LPwh,K$=G*]w 0ry1OCh3eUAƠ7D;Ƌ35R?K˅dxN>Dp HQ%̿&$fKՈ)G;2ylFAAgEUs$}_EW$p>zĕj MĊbqzArK ^~{mƁ- XFd->=d4lٶ2-Sb1DE=tQg)]/ejR:y/嵝z*G Ffkw*#tžAs@ƒۋ_Do +wWȨ { ?z<|",VQfsL1T6@V&/_u5~˿\68>6!oPΙpf-%;h#4ƢiP(\ߎJ-0rCZ.n4JF>jZ3T*<+YkVƻՉsדէjSV"SOK9 +su5Y8n=^ W09ָ*R+]\d̈́ o gJ9s+Cҟ&˒ Dr5+HVNNvS\7gXt6=G|1|~5$g`Fb$ubxF2O~|xPSUfV973c\󛔦niݢTI@1QfWʇgi,G ~ˊ/|cl֖sb0Yg 81j3G=ڵ|X+\1dXtR6;v\ÛMiO_]Pݲ4 Sn D!\; } ib6 NS)8aW\fς+O,JZ=,2Y `Nh!#hκ!bۿ'ym꬏pO/ E8 "aU }.Mk$>ϵAn nRSU.9?y{HNtI+ .pm|L99w OH˰ʃaP֛Ӱ =1}m]~4Ǽ,a׺'"Q Z71//cWk@h֎a>xgKW2,RslHhu@@[l:NS lMҾpVyu]N}\.xu驲JpU:xp^eJ6L !fHA/ج˞B%=r=H{ʴ>/ܩd]F1JAbTȘ]xl?(WR zh=/[ mnYi3F7/ɷͲǴES6SU#+Mq0LUL8VH X> zlYe`ySzH==/s[1R5\?qH};&@S]Dupz "uswIeFHuΘ4"b d89ǣfMG< R0җF=> JNɮ;ظN]fT'*>yhW>%+X-e4\7(&&wA$9]Cτ~!ȹrP L]#nxl%N3{bGteվǏ%r-&!HV3ċswjg~&!Z%6P o\vv9 8uvu:xAy'|`l%i< [NQ'eYr8'/nRX`kRt@%9􉋚!2z:YjΉ<㞉D6 /zaH*fxބYebnbqc(gAłF9Zp<$_RJ_wcr^Au*A膀 6^~-# go?Q/# (g M="|F" O1^(gFO\Vlz&TAY&![ T\E QRG3ht4q#˻CfLmK>BOJOj\3OnMk`v@w2Hm4sŞdE5/U ЌhT[Z}4 ""jVm5+N#þ5㭄cZM6I!G؃;)c1zέ\e;7 40dnRf̆|c@M>+'e>52QP8шmKVv>(Ä,5x7fXYubo Ng\.T(显sN*bkT_1(Keܦh*NUDXhNJ#0S@Rƻ0hMO&ў9&rthTa,s3AE䄡G?E-7fV>K=u01y#Ɗ+n?&$2@KaG:\ئ0;\PZRچ:4k ǻOc>  @gyYPn隇hJ"'^t L.yz$S]$І -^7dĉ@TZksrgbHsr? c [,˻ &|6q&h6CF{d68 /\ 4Gm QV=0x!zn^0kω7(sJ?ĸ-ߖ80rό=<lQ6iȓВrt_tp-+C,Vy F1_U}Qϗex uE4*sDL ЅJ[HAȗ;}c۩ʜ"UQG`2A w &]i'Ův2"OV'U#:y>e~<6pq4 h*h|E[0]>]P z9JLg&GB|5Pb+D)Pd c6ދ|޵M3 S٪W2OS'LUe8'co2o\bCJ 0>DaHn~nJ(<Ϸ w>h9KT8Oi[BK^ӰֶqGz?Abe"Z.A驡a/M|:׬iq<$ۮ,?HVƔ xHVc6BX"Oidҳ{Cxb^0Ĺ,,k-0 Z+OyЉN))R*_oU-"F#r̸|tt%(h=JJv$,@ݐxFTˮPv%:\6 t<PW$H$ʏ%6f!tnqn׏UG?4v~a/uQғ 9(wy.+̫e:bz ,'9k`3HWYISIDU׼!uڂݚ# {z}a͚ndK>e3BQ*D k}^x+Ve{/q|'@BCH .4kkt(Mf\6Jkl4e-7hO\zȺ~ؘRX2S|/pF jTu\!Xh6눋sR9jjUq'Y{^֒8x+]_jp-=<%[M2_a|9̨'G\ӽ7kYURB֣ϛ fy5)1ϻ.(2b)ꡘ.*( '2Ȱj͟XHutPs=JxcPKCi'n~_ F ]ؔclԚʎ3I2p#nd+e' ¼? 4mZB_|w)Z@@}t[ECej2zxEnI%v2=H3^zy]M I|C7Vo!ỹ<~4"Izyo%Ee lEC2rf#I!?sCDIl:ϕa5!H 2(>U7°e5Fi$ӣ3S;47@u*v>i{@}$ ![-D#q_rtޱ(~ƵѶS~>^@ Kf- FZ:Pp9$dZA,`΄_KْCҲA@R*n/J uƎ(H-VvόWs¶y/cCY Dk-\$Z kKbS>WmfKy7E0-+A7҅ ;g%21]ciw C/ol=+=P˴ހw%$ʳ 5C &)5mP^\Kd?X`"eurumu^_(Nnᄍ[ҏ u5JY<] 07 @V>~8f e0 +[e,:aߝG>\ C!?^ū(xu #զA9Z(Fr8uʾuQ}hv/{E&!p7ލQsrƼΆ{]Z=sT2=M.=$](F3[7$u4B^6tA#C s`Ŏ6Iu~hڸKfm'~6ۡ˷>ېBBn QuQ0m9  ;d; U>gMiQQ6!@z= BKS?&tqeRsΑadښYU*^%Ӻ+6GJ$MoQmVQ&j̹aŧ8T]3TyFL2TwZB?ƀe5bt#eїAED/&res=O$aOeyY7rεqn2l)eN`}]_ʇBJG>(D;S/cA(SLLH:qpɎwJ\؛?A22~/`~auX)%ŐXlX묅wJ%h}_+y EZi}3| a# -4*읔v V5lІ,1aX1:n]

    BX4[ p ڠÊH2fqoC^w!/ ==1ec7Bj6jSObh,KK"}]7si6:]5" \ο]u`MP3UiĤbjɀD >9 R]0xcD"/NO94ɝ%dF}OY$⮓vh%rDHuB>V0Z?5 7 =vRi @VؕekoU"q)F@`\Sdrhb uN'`XSsPGbM#Oiuלp˄d&DlpdVw?7uKnXhQ0 Ji~QK^rZwɺ$kBHx\3%C%(?,k] po,j`R!&?&) Jƀ]wHߠ=cWy``ef3,4@2:]5vJңb*\3Ia-zO/9\mmxr˅0q;sUuXY!JurSgxmHzKZ۵Zzl$dG#+4D[Em&!g*yOIp}62aA3o?o)K]գEݬs&! $#Jn/vp/3T7b!g3fd3bƊOM%3@1.CYd" dh(%KoAne^?h!᠚TѽBDi5va_>pN]M KGS>MU82C\^> c#Xga\]`e#DěRЛګ:M ,~UkR 5"ETP,\]:੉{҄iW@u _mƊiAj. k_1tDϮR籄|@-WO36s c勇"}diN}=;-:EYƠ&S ՗:AduDN ,D6"dz|7d៯RZ[Kץƅ/\M6xV90bf`hf0mW2ٯ^⢍>i{YnxSޑ@f97i,B&S$(J˨ṈoKx_1͠,9Ƅ' zO3-4c &" u?La3ORVn=1Rͺj|BiBc#1-$#X^]{킛*ҼSXil kRLbL{$Jְ@N'hDzl(jR/EYl9='L-"H*Ėdh~}J~h8Ar\XsS|~A-qQq3d^U5GvSd,cTaIxY*,yН QEs+|@ٙtfӗ>]0_P&`>L/C7e<?2zjrSƝ|o\5'n@kʻqe>^s5_&Y,% hg ~MQ:Z5+"ZmV3QsNsP$cznfJAQX+2Fxx>X}\)Z"MWͻ Ajsp=.@!҄s/E,7YhDɊ;bDE0߲Ǐ"lA0>D3EAc{y~wVqҸں1ܻd["!y![O2щp0r W5 LWw:S0G)R_f +)2%OC2j&D^;rjVٛ&og %z74JU;ziGb &ؾMն{OAIC6kUa"7S 0"ad~>eſf}쿌j uynfe&3Ul<-^\!? L\LF">_x^6~VޘgWӦ-r iu?/;m3ׂD;.ZymEnMĴeKƝ l}NnVy:*I:=wB,Uu=!r(&'n5KGQ})0e6BFVM0E6?Wl;\gdZH!ߎ,`gFB# 7;YHt BAt(?e쎟 L!U!e$P.)\yg,f]֨{v`M~oNb{F_(?rB zAg`Ɔ@ز>igjk}^)3|.y^F6\H0դP/-݋/s6_<էmp# ^s/kTv,@l&~3r4,"^( *)_~ӳL Qj/tQU5+E~1Vl[ϸP]lqgѽJ5ei$&uIfwߍ6mמ "|$J$bu,JVrTȸThj* :ϊ+BHHLiI[G-;3S\*b I!o0PTb}Ţ= GH֖r} z`43Hk`?q٘@GuPA^PZ O].o6FB; w1mܯƄwêoeN4ew>_bE1z(pgT1La3-55y`+ŞCKr-l3aĞQ<%  Q_c  !( E)pړR T\kk!D< 2#Q´\H(k$%E;8h-$q1^MMv- 3Xyc+[w]ǥ+?/r{^J ފ+?60o \1%,b[PX}7<_ ,F]Ҳ#l $r4ԽүOR eG#] $x0ôỦx$8V]/vޥ5BluA\)AE}퀉Vȥ{)u4,d+eK9 uEx`N"oz^YLF,l!ã;6ւKwr밓8[.Tzs\p&+  a݊UU[]{T 9QӅ5M(ȱU7Bpߝ.nFRd[]>9U&kD촡MHbbl/nQQ*r?6 Y^jzEI՗]9dJ~G&: G.;ȯY9}Յ93WzT ?1"gWPw^KUץbǦz3ʅ?TdL^}Zi0Z _5i i~Z_H -J2HI 8LV5Ƹܺ|$cyp l@Iq뚒 6_t6[QMptp:Qڨ8RICVd8b0 Q.qTK&y-uʭ"a^Юm:s?b}.#&o5*TBy_)j 9M˯b3}l@0 7]>as?FKx7.c1e ˇ)DFuGanF 6F$ jSԹ6 NH6?t.*ߌ0WU`e 6LJwքq^eBQTvLj{UjKBxmI.cV7oڗH_^^qmZü /sܞ]DbJy:؂7:Uy0-&pZy3;{.ǥ!11G{t >'ðdD{|jk"0yMSa ՊAźъTnnR*Ky< 礂.T35.hNԔ*X?;Y,dnP#Ȓa[ʟ>*7AZ#բ~R% W[tSߓagY/]SbXx;èmɕUuɦ 1u楅tXl&ul0N&#+Py)~Q{ !f(q#a[H"RvC n :ozڸ9]l=8 o~}dRCPj;{;m !0C0 NfO6ݧ(N|F>bJZ 7|e&QpB&ѻf%:"m fDAqkϋar ӤfS}x܉ȋ+0H,`h$ם3-cKřWЇ5 %ZT!hkcSY`x^hf+7hɂ 5xĭ>MNIWҷ*6yd~dLt@7?[6g̦C2vhY(QIY۽Ւnz@/ uƣ&Xr|*-K&DgqSD`ȓ=aB wcS0󶮠Me1{{s*zͷ^,_d7ҕ|OJpұ.Chu`xyԅ7v;Gj2]n Mo=ΛO 3X@Q]4C+ioP+x[儵@0=^j+&sP97ήч|=_ڮms<~ʖSazYg?~c/U}*?$tƢbk->YTz_A a(J/濑08xVZWDKWT>0g1KLh ƶzY^y7]Wo4 F>fއ7a^_95UK),b _@|uȝl17#龷ҕP Y[V)gZi VԓoF0f82UG؃~PM((F 3 d 6>U$#,ϖXu3 MoD6hT) 2BXvBJ h{kM/K_] 0vtxŏ:%|\IgOò߾z4 XknEh8 bϤTEαZQj,$ &E<:*KAScItߙR{IJK43US,zZ~&w snAsZ,? T*T<:jK?a^ ~n*x_ ,ŤuM.<.W񸓑Vڃrl֙>o fWCZ%apsʘ#C(|~mr1fRb|B?Q%w5#*R:+$իrFͲ Fr=& ~3Clm*Z _jJ|6& /tEI$oxidS5 Z:L>iΜ&̢:NMa*ۇ,mxu1 噬7 Nk?J 3֮/ݒiBT6Uxp(Ѷ 36ޗ5zʎ4+Oˇғppc!ɊQ#~P_ƫ2ll(ΐu5Mvo]@HTJ@Xq'i}.ΏcW 4IdiF躮t Qx7xw$ e}~]lkvxI|WUVb`E9ZL[y-)(`W}6m8hôҿ㝎+fY[Yr>~0Q:X}us1~z4 )[U׳߷kP}]؁}͙ P6( lM]gR< 0ebCi Py`툣XJY0t3(o*y5mAx^oU7yD=@ lmy^B%fŐWh3O :;" 5>:S8~q4pTvN#4'0#!R: >lZˏ?!3TeП@4vCW%MŪTz@n@gMo&utO`=':yUnٵ:8Wa96* H85o<ņZ KCǩ̬#}8Z7/v~Daf 8lwaO/lvdd-  ΧuI);BQHCfu޺qpw֢CR`|% :Xz;NƥEm䊏 8J:m"@HTP_[v mf=9yGv0.Z'.Dԕ2xgAl{i] PJ`+uTM^9DA*ڝ) cnqףtԔ㻲\~vaW}PvR W Q: xYd ]놳U1I|A;olF.)? 1CiY6lߖRVc~#U'1&BRr;᮫-QaDeWrwe<9x7SX&&e5  oA4 f ZRWF }>gcw0&dN;ݎ("]k6 -7Y$G9F}GH%8>4ӻy$ٷjGpdQP9j HɞhJڈ QaD&ѶIhIDnA!1]`?moцSLUl* t0dTg+ g&o=pSc](ZWO(m(*{C\C6ss& }/P`aG҉1nɦ3)ǵ!sfbW]sdmojE5YI[eTIm_?/(}Cxmͯ1J{3J} J 2{<VUԸmJk܄ec=8KΞ<$-fg`.Ș~cjEOZ&_ofBcC m"4 MB0g:<18"''#]xڦ9ߚ>i֪0}#a^WثC Z(+vu_ 7*8qM&kD”ą#{g~WOE,sݲC!nn{~{E&aS"¨0M!cېɛ@'5Db;&-ajX*G9Li.{$[!iaŰ4\|Y HIA{:[ӺPMP+//֥I߀-7azXmT^Bs_,ƷtpH`^Þk&@ezTx脑p4:?4Gל$8A'$ &}(I%Q-,UlvW}Er ٘Cf l('b>_<1 /)dYFa c5T˦"ξשiicl>CT>\( st*ooV{?f3D$B.)7bcѨu>೸@yX^󁔽Zq,Ξw3Fc}Җq_r-'()nvAG'Ej_@ʼpӠU|1M Jjwv6M:⺌k4j;jAyHohr6KĦ._IN{k bL>48GRH|6TD#y^D(1璝Uma1PUKc#1aF֞qon0ïU;Yu@y*޼I1\ݵgݽZ>ր #4~~8 )yff~؀~&y$i|ʂ4Qcˏ\b3W!%4~vONڢU1ZƧm˫Fذh)gй1PmF`}-[3pq@PCr5)l C_0=QGfWwZFb3y.@0ޭVp>d$V/]{4W6Kqb-i_(ZLb g5vkWnb(;jN9Jxڥl( e| t&tQC5mC {;<Oj'~E\R>j9}wSe!xT*>3s x2Isp#fXbf3 ->s銧/YuUN_IO`,;^Vxrɹ/?_ I3lu~SN9Ȍh_.X`hU+qz@ RC#ĖׁXÒHfqf~LqP4z)Jpy+G:~IW/=eU?h޽qRW*; 2^"q0%[l bN{м.tKm@3B95!?Vmh6,)DYyRњ&:5#@p]s쎗 9f'?>$V 缔.< 'K˻)0;dtÏ\e3 n)蜕e\݃DAMj +c0$<X3nLXz7nuZ:@kÅkOmdVt:\`WHàtaELv*> ج &qW̯ m,~8@kfNN!/E̴!-.|I}t ݀ZUnO;)BA; f|ľbh0#ۗ#Ls;DH4< 8Bw+2 ڵMś{5ߙ)%^W< `4wOĪp aAkGmJtFTR3+z>UNS] H%Ye'X6 >*$9eލ#o⽜4hh\*T]Q>7V')Yo,Y}]72FG4T&Ό}|*~L:*x뉼{F㖘F6fXV?G{19q2r{m&aE ]ht#"Y>K|ºm!`IA6y`҉\lŸoY4jLFUD q]XjA-# > nA`AJu53A CˆQ  C!z$QVQm&fe>BOC΀iD).} ^@r] i3 |xwT1zmh(Y-5w?^lBk\-t'И{c}%t$W:=JI!}|b+n!pBfC}&!nATc(Vv|tbC:ȄEQ3>[l))!L@|>]b^z{oMeD'Amy|A`,u8( HEI0ІHUK"`'3`K Bx1<*Wln&Itŷ?/ cE1a q9\Hh䋳$l z$y(v<Ǩı5[rΓ+V )kAA{ \"QZK>K)Kn/SYQ2gV*BD5T 'N_`qz!r1DF5-zzk#+*VN'<*u<0=5>|b_uGE;mD,)G qv:T5U(Q/um=/j1:ٵЀ=)8nض$\„g-Dq_ ֖J;2m݆NUnXtn oępy곕bX_Swmİ-'s LG91aIRBAED F: jZG2w :} `d%uBPnV4) 6Ա.1jR|}uY^whN 3hS;(*:Uf):]0=\u"; ZċsՏEw4l`]T+ TMa5e7X aFZ̰1+0/cR.#n9P1w.ퟍhKOIL+^D͘aǜ3.hH J.DJ(h#FuHaLc%;mj~cdPVvRG/g?<)k'B҉ =Mq)bCh4}r)(nM6ӣ螸N ac|@Vx|?T_BZ\P_qc ~ɕ4vɱv^ /ܮ/H(u~8+E4Ai*DWȸa\EBM4j' i k]DR=z㑧X"+۸ȍ޻h>6*=~bvfDžD4 ~AN gh!H:ЫF*{HJG8[H2&F>g?,]t%AԼY g:'x{oO@-.)/}:AV2Gl8TBƠ̊,HR+e樸s{s@V7:z҄1QI9B Ue_ݏKL=E)̚{ 5Z,߀bL3ZW`I2@ģA͑:.ڹRga z#Gv Z~*QnjRvJ<\! Q 9 WN*tsJT( *N7,TlA=5`5ajl!hZZ@HJ5?-#kX)D@ߎ<ǣʁH)1nLDHi}S?"9*^.=loY[&#NZBVZ=S".&&`+𵻺(WX n&TY-oVxXIFhRt}nD' D'懨;z!70+@]Cm[ :3[!k:RPrji?,%43P>̴༄6 ! K6 j{`I ?J8RHK%erc^>j̅Bxڜ[:cO*5|kD vw1zP9Mz6u|қ~CTP Uv->pVb4N8BWFuˢ)qm-gb8B1`xf/?7S)ٲMx|t]|>{}uULB7<оojGٝpy`irURmcFWZˆFDl U2MT2{2;}gş/K#Ϩ3 ݕTDFCd (vv}`F%Rq@m)nhr/oSM}$ j(/R7l=TE.>y}/ A~2i?x0i_IaO^ކ{D,:R4tFy̏Q|KI‹,O[POC'O hR_Y),>BVH5(.Rk{JI xo+94uS҆qQ}l_ͱ}ڎ@dk-f̠i0{c*^rۂ/==Y|S1 Vk(40eBs*qk--C*!ɴVv@\Aժ%q+XV;c;C؅SrF~V[^VbtV6h7I*4#CC`]PZ.ޟ+$Qd3")s>Wy >6zAhɛNFdODd$wrm eN:I=~-FG:ˀ?v03{@ jFOqy^I1EB] 8Ae4tITG o:1)$o˜#0Yhk2}kjOEqZOǷ#/Sؓxߊ]zp._<¬禃\#DrѤ03gzl޲ESx?w\?%821 JRS7t m`0B,0YЪRb y?-pzJgx}ry?P-5cª`|!9f,PxHS`4ه2"4uŋY}@B+.r+8l<.ѠC!J{Px0X~`ƣ\5y&윎߯J G=0q/zKQ:>G#B[3}$VM" ?t"(8[FUcW%vƝd'b 0azސLЄB-! /N]/r) -0Fw  m'pc%m:tEH9Qfw̠$tY[Up3;2ryԌJʺRw*u {,_&pscI?` 9(f$Lթ(!Zw៧]Ng/2rOĆXIűlS9ô~4cZvnttlK%rF]l!eOqxϠ;1H񯎑1=dYH){䙿G+}ٴ}_ƣrS G=m%;FaՊ:+#=m}Ϫhge#lES9]&9e*]4Se F3ϐy&%?vhUMǧYMc|(4OE kgjtp. &Eta @2m*[ֹ2?XfbR8 JZ ?#FTx+( ԷlOD* \ɖ< 4.55: FzǼT,0m2zާ0mD#Ԯ-G ?Ae=q _AtDN課9*D mYF=19ԽO.eZLXWUrY}8|`yڗ1e,hxg 1|]63 h4rȳ~5.>s}siGxÔ +(d,!tG7JP^$6n4T@ޘ@]d ō=p)j}7ʾ=wQ-b?&Xs7>90hQW Sń[!8 0F\t )i37׌a}Y=i4SXʼnl_'@s[$GvO˽®Agv}˜j7Ur(m`°adYᚚVD%^? U /mSԌKFyWTq"nI&{Xf򹪯(e5 5*lViwzs>׏S_JyL W M#pّỎh% k rM)d4B݂ahŪ_`ʪ#Yx@<T0ˣM(~hi1zjۉL40I m(i\ ?CԓZ452Nw`Mvέ/A\㓸 ȸ2yA2y9D9a-rX9tzX&Ox+K:MjdwRe)>]qD?>(ķݴvTƑY9PBcm/ 2.OyӎHrajb$YWtCҰNCuV@KT,K"~7ļ٠fPM\HcOM0zo(>2}2W0Q/oƒcn1 PUֳVUxO\U ԣC4a}IC1 풸XJ\ [=Nټ!ZDK\kcX)Rd >@i 1B^ɤ*AI8ZScVQ1mt@0W)t/CQVc*5h%-ߍ[>gm^87ɴFSr9+)'[ #^j=+DEft$(&lgΜTܶ7;‰_Wf}`ݑqx{ [ n7)`O|k5Klf_NVF`}Vg@`xØ,q]7kbWt7u KUa\xm*01(h@m }ŵt|y=c!"r fl8d.ު=Gܼk0EQ:e{%.oӏTQK0w&R1TOfvLgޥ YIKF ք7ٮ >nW@b90yFky~7쉐?knbk|wz#9,P6TKkGkǮb*JØăվ}9e5:!C GC ml<CfvsE5ɐ5ԉ;wcoR''<-"7%LO1љ%oV ~l e[ GEVȨ=rb׃eNj"Ӥbq0'RaaW$~q\;xy JHPbүb6nӈBT,YV ~7múnN[0s$QIW 9`$ R(䔮3]1<졑0>}>m35)Nb! @&D O4[ .٬>=dHwM< B凚ٻk_DqGrgqYDF%JeAbh31GOvYl\i\Ҥ|H̺aצW7?"Y\d:^S]/U՚w3b*vU\$DžCUjڢ`kyQ ?->@N"'?ui[0q?e @xm \%R+c3A )1"Y kl=mo9'[niU?JAx׵~!~*c55J1it~*? 2Tˌղmq]T@p}zT"˨g}W̃f(Ucf<XkUxk?evo+9 {,WUn0;f_}}lacg1Sa>)/lYq]&?5Mc %es5+& ֦#챪eˍfj#)԰ʫlbs䭵V$Ax8_P{1X}h6r&|קOM`7c3 ==uQٍ4ˤQ;ޣ.~p|}91 FSjf"m]|+ rK 9G*(J(SԃU,dH#sLi;MitZ^? GC;0:e`s< 6XsVlϛ)n[;8WoS璺K7%];x  Gg%-Nϲb J7~ j^ wK&i6w3zhPwN68*kdD3^$P{]؜yɬ$Ĭ3CӔbn2e  ~.C1>M5i#$} ms8M}GGӭĺb/05PVbCg%-ee QPZz0hZ#B8 f~gu*K@]WN+1}&~83iNg~3 .|L}RH#ulFG*7\w1򚣯 xs+0 EFp2ED4`͖2ef]SD4q_*V2ҏRZ .3 -"~gdl4j߈moVMz xTIVU ÜfSed/9]%]Y)O9}&LWG9;(!Ezkpl?{*׹/)/:c"ugXd..fud1zUJE KƖ D_!Dzb>QfKш}u 7>b)8dXM6TrSg=z8ca\;ӻ R3n%w=# SCnD4 EǺ&KgSܓ^li {B1ji J Jnq+fhWʃ@*Ud\y)G%% \Ȓ䎲h@ry2H%\}zs˴g>.E]OڢAUVmh|ʸo`=KÛ}Dn!yvE5glkD߬8"k\n=js\{* 붪S2ڒrvfA䋣|frn Az"3@[t?JQ^^^Hܷͥq )_r򋭴7nժԀsjR#Cz"tc'ITw=+ QkPW"n w 4e'`sW/߰q),~8خ6=>21,y; ʮ80"HуߨX:̕\qߧ-!s& úXV Yn9 փfqlv 4K=nXJlRJ 5 0)B"XP]7!e۷JG)>ʪ,7s=LdOmuæܻ&]BP#caS' g/|`(z{ж j9e[og ; ֶ 05Xe-{:131h|-Ư6)X5*tSg +@4]'>';z{gV aDMd ikgh:욗hd}*6`e(θ{3,|B|2?{l'+1nCoO< gabX8jv{!wyi$oV4 y-)1t*^lOr0ۤSd!0Aݸm'e𾰠5u߃[i0V3RlJïеJ({/x3W;jJ!8H9ydI -q[^u?r}QaR+9ݫ[^H XFNn_X XL.d *+.p\8|45YrYb0޸D7w'Qk( =#|q!<,dmjF,@6xEC1!o t>)?qՆ%%" I5=+ك5aM_`iIIX, fE`GzuȫZڧ<\Z ba=Q13YUJG.aAMF2ʑcH){7:0_Ҍ*4IoL *r$e),I"24J @cgԈLWVF;|Ƨ5As1}C nN^={g9!wSt>R`36*/|N,|L]k0z7:)KU*.߬_safmnQ&^ .T J3_a1Jس =E FĄN36߹zv5S }hԮ鯞\,0h.]g)co$x,),$tTAhŭI2bVS4̾$V ocGF<^> M^gx:'_K<}=~ }sc-[<#v&sP9WRW Ig ]̒n#N[|rJ3KKxqVvI%Ò Gf@e y?';!uؔsz]Ah҈DkHEϤw` ;5x\V;p?!W>v$p7ǤP`zwKř$@^1X& IW%BL PD)/-2 ^זHrWm2HŰ9`3Ap #&^Vi*'&ŴU^oWFAAϓ#6Cw˾J_Xh\^"39QP/Ṕd˙/@/|":*L̓dͣh^'39HtCꀣ"EMkmeFk [fjnX?Gֆ6|`*Pvy[c]J*zp/H> 5f.z3 W%cIܶ{ dHz̖)֢1>]gHWgHipX%ITψ? ^5A/+$RLEO `;AEׁH6a3 k$ MT3wQ_q 9-YO4D>ՋfRYNeьM:]neZYIד؀EM'>ΣpWN{V w\> ,hh W)鉛dO HQ?i@T늲g8I_AmػV h55dQaueGKV~({2ΌGPTZKIfWo"ΡUJ@ <.ߠEC<.٭xk&Xu#P(,Q 4]^h (Vy ;b2M%3!A]חEMP1RQ\;e鄄I?/"pD~Yک:c.nifե~ޛ{YVhPV(4 gYcL䚨ל_*J􆆯?1hlKbod o|ޤ:EqT\"/X߽QyЍNM@U{g`.Q"\8&?x?Zdwټy#1]Jnyp˔့_>N|]WAT9/BF%3<ǰAMʗ.]!/!yrF2 J'`~)&\HDI_T*\ljuGRZᶉȼ[O3X(K:v+D麋^j4uzfÞލشJ:ہ]xF~?<U7r ;@{›kDŐnVX--V]TH`-_ꢋf+j\G7?4~N-w^rjѱ"KԡR &O#2ݙE *+WߖQUg Y|qUOIy A3=V׾c=ʇeMuz2JM3d訰 Gѳ4{9$ ]g#sÕkum[5eU.$f&S`nUn $ v %x8bKĤ,O0Н Lq+&OO@tW|>'^\Vܟ B'so@X>k7 !`ջp}2;]>inam]0`'I-uW{Hb{$,?j@s㶹PE~D{_A\U:޵<d77 Tg1UmNtZۄ g-8eihzYf 'a1^X  SI$[fZ.DWjeV'rBqjK6,ġ6xY 2sin8+mlA?1H"g4SrI 'aCm3`"f}FB{j̟ǂ uV}fz曩 b\odV yFK'KmY^XXUS%qf=yM,z7=dTO>cI#VBSJx<=avq ȷ9v;{. OUژjDvUV';mVbIUNPb~dV"ɵ|9(bNS 7@VdE c R+z.+rԈtX rdf&"1OނBz6=W'\Vχ[W {aW9Hܳ+#Jem+<-rՑb'GbtG7N2(_,AkM@'m:s:DXpg*1VGcORZe2 -/{Ppb[h)  B (ΰ ex6&xARlg w6gwoxx92=Q,7=3%Vթ& "Eض%)usEDs&/A+)%gzPGr_><@ ofZrt&q ',m9tZTY|#RE&ם KfͼN'ᰐԜ Cd+o _wsWR\: a/K)̳`$'eQQ;q{|O*%É>'uN:}ObBn]F9%NyAb86P&:_% m^5[ W+V{#+dB{M$:vBR75!HT ?ɍW= j[мЯ o6n=͚>ݡAlb4B/b]ɬM r8QX+avw]ыP{q̼S!xtȹ*S؞ao^bQmՔh?ݟ S22ZnfcV;g^ Lb#-[_P 1<0: ^kR aѽURGPt&5S>#%>pQ^,1Hח@OKLPVsK qԖwpcSq-0ޛv m H636є7).m_m}MY\kn(ا#Wg~n=Т.V֐ҷu`Nb Mgz?fcX"^%vn~46* ŞD8dN_?\KKH6dw/px닗=>C; 0XeYVvR:z#jFS:Rn4%iߚ7FBTLʽCiF.cɛ$J,@Y7+#^!#}`;_='"[ g^6#Zi83xpF<ҮH4w||ص܆ 8.5/AQ'O^][ű{}!40U ip4(ʨœlHdBF_*7$9)J.IxJzyQn,H*&Y1U$:=NV_MKZAIbL 66k@0(3 >8N/]DTa*ސmOYSOYr%G˖bC Zr ~[#B]) beibJ `,P%kLfUkcm]V]769S!;qkG*]V߽b<c9cH_Mڑʅɓ^N[{M4 fA_,4 K(#,)=w2Eju+' %Eo`o_/~JX O\6= .ڔ6{?C4g鷈(L7SxJyV6xOДdE&Ʉg/G:*VsA@h5u!F3*!շ :Ll rbBzJKBp<" wZ._SzBoj0q}|Ϧ/E\BZ-#[LZ!R }Q7a y <ǎnq!{!wѕԹ"RIz";VfW"cȹJ[v\F=m %v5{䵩Q"vK y+'\+4٤$"d#:3¹s+pae 41VLFK\X*9+8mE.%$s?EAxY{mO00σqJexU9A@f|arsG.AuEm&eA c HWp^6iT29*?9ZW9UӔ).%,eFM`#1}0$1(/yi/BHm=*qHC  hѤ_WQcl5?  =?zN]̙vCB'0) [7bLfZzX5 q\޿1 s`&b`[Ghm'] jiɿSV?*JNUDL!c>L wK3(;43Xa%oOr|]w<-xL:X" ϓz"GGcLϮ3ZuVt"`K C,Hv!ajB(hEenWJ@{1*wt/')#aA-/AnzV/{$*}sǯB"zVē2zFS[ofkk෪- fg@ٳ]c:t-ՉIC6ЯCǵu"GoXAwӒjElhS㒉Ow7F?Xy@1s+ sݩ^j#F2$ A]@"?چIPIDDi5x3_QggS9-gc{{eNٷեtϥ`37p~:BANk>hM)bs1o}"JYio&Pړ7xPVxHLf'$%gZp|ɓ3/ƙBF1^8N+%+s_zz(Ý^G.+K<\/k ۗUD i{]VOcXO*ۇk)"댪Zp8t[RCf{AQ4ЀN mayZ@d| 6df5nn; f ݂ڠ 3]Rqgk:P,`[ 3/;EvI~;kS}p}r.v [gq_a)j#"O^ǷDٚ +ˆLda?fDWEt%E 6e6ngr|K'ojD,̨&\ҧ#$>:; .*F|P*SB бOE0mk.>&Jl!A7&VY·4,r $2m%6hD~bߍy* .2{?킥ݠ…`^`E>EMa ons5Z'G<8卬R.ETPURn^N#yK|\r#kl3`ފB1z KS:̊p8͒@.dSmv7h=@cK*4'':R>yZ]HBa:wRU)*&\isy zE0#Geg< !jí_dMSd7/ Fr"c$h=Qٞ>ۭ^}\eFp!A/W*HDXnH7f̤TA :\Oٔ%"q.W"1  Pg4E#MϜyb73l+ tu{&v p,Z_7ߓYUjlql|o阇l/bN l= v tI@S܇ #,|u=$r]r"@gK*VGr` ` %|爃$\ӧ}\SYP~~snŌ У؛Y"~:~ҁ3m搨 ]lL AT0,{vEz/h=BXSj͋a3^ʵgFf_ ŀ꼯8>)wM_ 7pYjL I\2dq8-"WZItn/2gLd 5)*YKg 섿俷 $QɎK (-5}ˠY?n06d w%tZy{Ӥcq~C6p+Zv̶M̜|0ox3̲ByBO,Px7+>r",/tc,=@)֋Q#}5XFi>669*{F~9|%oZi]0M} w3өz,j^ǔ7T'%-Yj"0U!+ng@MVC; :߫<>GoT,2d|4X-qPW|fk\/3.ui)֛`%.e,=*:yy͟ݏn^:9wH/<.$<+N3\f !1СPE(l:?|Cil#!qƒ$T+o'dž8J٣ z wx%) zh>0nyVGrjx!' IE^w樟X$Ez<`+0=+ǵs.» effG}bz(2*S)ҁ!d*F|56 7*UM ^El!itd vZ-;0[@S\Kk*Ċ]7edidU6o3߈3$S퀧P:{b )/D5ʻYfk50S7zc9?:DSk8V?:(\vYRB؀Ǜ.ld*]}E VPԔ )xRyRgў?2 sM lNLx(zQL?7IuKWEq6'2yhjlO82=GL ֞ {~*nM|mP MjM.c{an,y+7N'Ɏi`bZz2*&U]b^HgI= ǮAb(('kk ;&F9؆@(L\vӤ&u=5;w754.T~FZV+lβEi2{ůC(0m7Joi;w~ȇʘADY7 ,YK"-}w z'{|Q`jZ)wfͧ:a򏠡*3>9_kجj8ӞDAGLub?KyݗX?;*H3~跁$i~ϠQ. ̆-jя+-^mmVT9 P7_T&dtwb]peez1b9O`ER;hâ-؈3WT.mܻDRذ 7:;_m`@@r5`ڻAkm+] ~yzkR֒XC,'K(o5yCr~ni4Ǘ9~&Ѱk_n\}M"_p級Iݾ0!`rupe0]guRV<-|9ǻVY v@t "x9L`W~q?AxQ2GQWњ7-P(P0;'^b_PM>Us>Sg[뻧N3Kk1j EyOّCDa 7-esmPF'Gb^א JmVtL"VݐǼ&8Gbl-bM|{w Ƹ*BwE%)uwѣӂGWa(h0ѧ!%Z ]YD8lh#*\ռ-r70,O34f|V6Ka6d_Dٯp_iN77Ѐ?A6/M-kV ȆpQ@\?CHl h[=qܿOC:ՀU,re$ ;9ףU>r؞ |^71:f_J[ִH4M:-eء$&aP]"g'I튚+vMMU9kI(Ї!h|}ѳy\o3wzH}.6oh4$KvMr-_GL[-l*f=(с$Xdram~D6Koro`oQ͓h[Kq5L`7v :|"ȍSwZ# ag}8YSc?c*Ԅj7,4=Өh 8Pmhp~]~tOpuϡS%uNOp6zHr@)m<*gS-& r ,yZ"KZ>V.ӓ!Mu9[Ln"Кnipk]#Kqv,9g>}Jna y=Uk"ҧ0)q}f(|4/B2+`1;(bB Dv c'XlB9 "DbD[hHXX*f[Pƛqpj=ky~OÉ9s[{;@lɈ&B,و]|ƶC^٣T BA*,{MN?-}sixd9eD?| g@2_Onv1̓]OB [g-m$qKqI;dRj8rb 쌮=pFyG˫ːIHZ$zqo1l1 ]%G :`wU€&#mҧ3(>8 RV #ⵚrkx4$=j7K_a%3 `yLyk뼛t U8MPv/a qvMh=4ːcL۫g,'=uZ׸QRhOJF*p\UJPx4jwb B9 +n*ӔdҞ|G9ͯx}k{UWz4_V4髳kIJˋ)¶yqztF,@%q64Pr7wXbk Yd'@ӈT-[=w>+d)g`c`m?S߭6 Ꮫw>=nI2buckmU} #,uX{TdfdȺRbTJ'TX"7d8Q+ x:eEOMO؍.__QvGhlU헋"*ΥMi:\(3   K6v1e P"8|U_1WgOE=#톷:-8Anjdq" (L` !׆)bYSfX@khs1Dey[ǟ"Y +A2cZoU!NaNΥ ؕg0Mh‘K^RV_xfl(9-_R5w`U'AOwx=" Ոq.! 9_Tg ]z`i 7J"5ČByGbeETw=6tsG}cO-}SBu[ao~@x, ^ ]V/]?׷u OS̒Y9S9\͈ne_C2f?͹ :FoѦ+cbw6GUv|$3}uX&)1=|h4"1 P!(7st]8ְ]S{y?hڃי*:#ѮͨD>Y40Ji#`Dl:^ۦ_핽QrD I7H;K;9Ncߛm3vޯ(Q[Ĺ1]XVr 3BLҐ3?Й4^hR?fVe(k+>9_2o^j-Qi ӃTp]' ̽3tj7;wO&;NHdT# Y/o!CHn](A< Ak5Yp vnE=J.g#5B!7f+umXAh9Ɍp:zE`ɢY2 V1s<)C="/rTjo`,c;.Mk-:w\ϙۈ*ajvӛLsf/WYcd5=CR Cj8M0tJӔx.v\K>ϗ _sw]a3;Y yɧfSBAsȭVopMCusZc>o!q`a2NjD8-feQ'ov,ܸJ|҆jƒś%9|i<z[\;WvE{ddz;]AH}t-kpn2 }kL*h=6!ol$ Kdbp $oJNaHvYq{r,A69,;xk[zNy2wx~زpngdna@kB55=m 0\ y{rpR}0=AUw `}PCm|^DDNTjG|Ý[qhI~-g N帔5N!{zr&VT:QAWSP&QzZ1ϿvT]5kڹfiO71)_2m:8O)1l4m+VxA;ЏW hkW߼[|¹1-$ neö"`Vu4D;]mD5w:fAw{޶dݢ$de, avA ~, FCx2C Tm4C{Alj5Te6c`AK(׆,UgEAX꣺w~J`9t~Ć⦐mnh`Hn,>zwFg:"S&Zg,m)6M3ut2/D[Wם[?UOPrtZKOae[w%2/,\ݙ (pccol.Ytg;KH T%#蒏e&{ R(l8pM*:U|5 ¡ࢵykG/i~R(ɉ5Bz]kJ ҮK'y ١A9{a YLLAluH}b;)99#^8ě")nBWn3Q9hҩ-89O+ ^ '&Kp/^.){&t>V ^ĝSGn[+YlI(-#AufJ\lfݺP{"C~/2$Ssx0 aKAoI*EFW@=]. ufrSZ!/^c؈ G6:rAiIspɹcaC'ԡIhlo{K?3*5L@R09f8.\sg#`!.Qv`>t8vy6%''|9!viֽXR¸݂d,p-Bۣ,ĥ3 bm#|^qZA,dTrGWVMK%1%dd`(xP:yݿ@(A wQX"YFUr=_ Kqyf @,س|&x[T 2vsXt)\ (1+ɁOqcwf1 zrQ2-`qP#,Mc8]QWB1"tbXkXĸwهHyUZZRb$RWS)pO=7g:rto1>BFOX95GЕ?RM *݊r;8;>Ѻդti)>CAI};k '9s̝V=]0g 7kj"،W [Z=:P|ڔ_ZZ (zژ) L|gC "yܦJA`PZukq|FR=Nz@ UUO`J$V5:e^4Ig=w?9?,3&!NR8w>E\э\Nceʼn!"QSS<ͺ+HWF(ɉ[( Vp+ED ES9PB|2 Ʃp4HF݄ hw10zSI  nfw8/ AB [,e|g58ABuS"'cbEr9Ϙ\wqS/[vKHok/_Mudaf3/"I@ۤ7z# 79jW=Z!mRSi`b1X؝3۱0K}5 <Y~HUDEcSrtPwh7,ѿU .7x?t&ЎN'O aFxEMH筨Ǵ`C`-vy vH)ɨ\ }{LҽMix9b| !y=k{16Uҥ)'k5)B)7Ľ`U\ijqof "L!4vs zA70Qimfӏ갰rag%Cm_V#Ĵ?? 7.Hq .QƀRN@B.7B{51kP 3U\1RX36U%,Ղw=L’ LV-dbC "/'蛷*{G[.Uxͨ,mW)ߝxoMŽ0>xPqH ǼCCz~XjD: R(&Q0sS@UH!3h`Rz.Keqܹ N`r/ N/0]5QQdp'g @w|住l !w*RbQr[G.`-q8 }CQa6\iI{!kkyoɩ;E s;# /풳mSeJ쾬md۶tB^b~3'@݅8Ry^8SP@E{ #۠#mnZPA =Z0>OC!Ć)#7,ɿf˼.e?j'\ s"QFb[9l_ebO6A2#'(F,`Km"@ATܤ^W vLhX30b+uNH/6GHV,sf]2}Tm%Zbq_b:YL ^B9TfY-N>2kDѣ%tdG;t+`;W 0xPv swIjQLv\^1hy=@ߞO֛ Dx`h]]uĒD^7)'̍z@/ѱRotGa$z |B⺓"XTI?A]bRy^v WNN{voȩg@mzVVƓ㫯aDӳJR?Cï'cu"\{[ eb\ax 5:dT]jRκfEWZ|W Ro/eBmEW5lw[#pYj Sa;拁|rQg?2"{bvë5J;HOVHyD _K(Su"c&yaiZ`P,oiMZ t4d/γƝ{ A&\+v'5 \_|@NM,T=t_"'^[b 3J\C쁍$@/`žN&j PD8 r 5(# FO[kɪvAp5 S &ͤ3!:%{Pỉrg 64v )ǮqxԝvQ7q\% $EiGՀA]/TB u \NJi78D1WCkJ-F0B5X7Bh^FՌ8󛡒HLC>%2d,Cϸ\FDfӛ-L/T*PMͭUzʰ"降hqr+8Y@ܢ홅 ݣI6VSc[mIIЌ~ŚW$K,q:%2)02 W !РBP( h[ȿqW)|;5N\EXpl g #r"1.swȁf{[GDа죉0͗ XHb@\RX?BƔioTo(qu8xg{Zus 25l30^8l tSVHD[5*K L(fiK)`W'DHȤ\X]gic:d'mm= z {!tɵ\Gw-W44r%5I4f"ie)u4OpўE޻?>Gս{џW2:–|71~.*j:ezXm-vEK+7wYV-F%N1{32 c]Wlzz>9T|O:2OxMu)GCI6w?IQ0sD:S9B]DGvX SiP}wn=M1eBԾZͥWE$|r:eEbQ%/3mDwno3+| 7ň`]pH]'G\l%?s@C#1s_U/:I1H FMB-{pCT]\n 1$;v)3lhDKS3ZB|FmoΩV =1EN2K]>6KIM%QX)YU3aTgHn/'denuD9 EY FIG+J8K4X8kUӭ>[Hox?^܂fV#Q;}ն?g~wNX* Xm=ٓ7&Xa͆UwF ,2|H ?$A˳XP7N@6axO }ob0'z 'ܔJ$mI?5QMJa$bWFH2 SNOEZFX/f1Wo[=VH:`!wܶv߷ikZuHVРPaI}eF/oR83&B<$źi,J҆Ue$=HZ4fyF>%ˤ.+=Q {$ɎSUߦafemk;bq ;_jaףzIdV)@>kDXDEM$ixߐK9հgL>^`# Ƒ8`-;4:{"XҊ lG+5D9M"/ ;{י#.6Պm$Ed[ȵFhN#arqoS7g;K_/ :S6[7j&ndO + 8t]X#G}2# (2~jt^oAtg!<>Yqv¢U3tԍFUY꟥Zj^/j_Νу)E Y[)bhԎR'7Zˁ(X* z7.n'$y?y:'mSTZ{/vbFO]=0?3mXv#^,g %O riV˳S=eh01UJBkRI 5,%@_ta,?ffLgud{`gUw%_J[H4܅q7aWV!Z8‰O-^L2կ֊w8YE-8՝͏=էJJ5k>a,< `2ހ_qkY? UbqoaTkjpMܰPn 8q)0m(IO2_fa_{Ia6Q):fB §ŃX\ueCۡv|a4\Ose7MS30G&96-7q3xڠGJN)+6,`3$vLzmGp=bĂQ2v}ϰ,=f( 8pwTgy]|?#Ʊ04SqM(w l$~^<#dz&Wy=#|sۜ",&{jK^PIpR^yVoO$$!֢;L;]J4!gOc3?(ޙhV>Gp@2E/" B>Zk<]4dfi16K\ǀݤqOx_="PͶ[ؖ.bX-J\t\Dy9c#ԱY3 3]BW&t"HB78O0WvVٟF QEm0Lq4K]?}hu2}'@bn 3浼tRdK%)N@qaa{pيt5UZwi'NC9).iº+_{ 5%/zae&L v*b#?q]A(UDXco@gU0R5.Q*d\I ꁣ_.}.BO0uS1sӖtNsҞ7@}/ 䉜[ÍLVJvS$d< zTփ_23} <_>XQ3 _Vv<`4uS "tn @;bg/\mؗ>aoOieUA2{RWW[f޷r0ɛeYZ 186׬aih꠩tp9鳁`:F~*O hK*Q})[pƎ @ĉ5d }L");_\}V:Ot-Zi#IF Gezb^@35/ DnT TXpkLZY*.< Qr"*ʯ1 &=8CK6"KR1]Z5txCgeW5_(D+1{9td}.~*8>9}=/~ F]s1(R /%F"r_ x|Hn3*!N4?I\CVkUBlr2J혪9W`}fpބ"[!ʃ~O I/bVK e%cca5aJ *#l0KB\N>6G'16t: Ճ,Iy#٪WS3qLJIbř g8H'2NO M5t-}tYhOlV '$$} 18&0>ht<`H?.F!4ˑYjxQ͂@REk:|Bjɸڐ!Arɷ?b(s2M0@_A_ic09 jP ftypjp2 jp2 jp2hihdrcolrappl mntrRGB XYZ   acspAPPLappl-appl descodscmxlcprt8wtptrXYZ0gXYZDbXYZXrTRClchad|,bTRClgTRCldescGeneric RGB ProfileGeneric RGB Profilemluc skSK(xhrHR(caES$ptBR&ukUA*frFU(Vaeobecn RGB profilGeneri ki RGB profilPerfil RGB genricPerfil RGB Genrico030;L=89 ?@>D09; RGBProfil gnrique RVBu( RGB r_icϏProfilo RGB genericoGenerisk RGB-profil| RGB \ |Obecn RGB profil RGB Allgemeines RGB-Profilltalnos RGB profilfn RGB cϏeNN, RGB 000000Profil RGB generic  RGBPerfil RGB genricoAlgemeen RGB-profielB#D%L RGB 1H'DGenel RGB ProfiliYleinen RGB-profiiliUniwersalny profil RGB1I89 ?@>D8;L RGBEDA *91JA RGB 'D9'EGeneric RGB ProfileGenerel RGB-beskrivelsetextCopyright 2007 Apple Inc., all rights reserved.XYZ RXYZ tM=XYZ Zus4XYZ (6curvsf32 B&l"cdefjp2cOQ2R \ PXX`XX`XX`XXXPPXdKakadu-v5.2.1  E[s~hx3<{25˃wP͈i SuQIC)Oilh׿V$z45g07xBʚ:fjO(o:֒yvG~dG7qqO/:?{J[7WaG9GP[|o 㬋?8 8TE!~NE4ybIJoXa_A Kow3sfE_}Ǒc#q"-v"nQ"}~&y'X?^5y[I. P-z0cBo*Uf  r~?<m|/w% P30*Eu;8I|JxFVEFA5G,弟|kŚvr,y/:Nh2o'PAN`[Nݻl: [U`8Ŀu;HDL7ov)!.a CHTKzbj1yS (Z8X2(@5"F0 (0H\`i8(bO ͡f3xJȗqCY/Y#suIla^λhBo{6h|)~6V@+@Adẏqc/D']0`r- }A@lzk嶅[{+^!LB#ʧwc $(ʚ|D셝~ =B kwLV9nL@_ʱ;/#YثѱE;A4ދO(em~oֈ-{dfl5#H`[cuyyB(Ηl|*09I^V/Up;_zH,c+֊ MR-ؽR ꋣË~.ezr"zJ{pG/#?)]I!x$@fX%h[&N ӎa"S0ŭ`D\,dBGCyc⫏E:W!JFNOǼiJH@&na`ENX%D6]=FfQl&}lXCfwW%{z.AnYIT0<X)}iZs1̗U(WEUL`n15oߪ\Y@tS%^"("J)6~9F6b!&W*%TSKng?DܚT͕38+<v @#=D`x)E7"=!#HNeP|L$sߣŇz{?<˾&Í>SޢZ 7/{CgW<+Ap]'r@;莯- 6և $}lz9^-R.}~-?ؐ&d:\YߍlT:7!?BAt!G`,@{ICk0)' AEnS"l#\6'yk!O ƪ-mvȿ8q7p 5?G߉o $TLdn Β”IbZ¥P '&EԳ-[1 \u-:`,[ZhBCuH$TA?oB:P}O/Y&5ú2fe=n { R߁ݡx)7H r_[e[dM::zxztxE{#2XMWbvd9vt?$6{:d@%лnC6OSƀFD cʷQgYoiPIkI/ϡ՞!6Gc@xHuKSW𬴗'O]7Nta} x|ewpG;v\>!R3IV&t vôƆX]4ARư8g8n͆)Ĺn-pCjM$1ӂohzƥOQPYϥ?n)ؤK"1GCiZI@A9%JRr<@xB حbPYUE;άWnd\X!ӫ\oa}jGV{ooH`Wx.GV 0݆f]^x?v{͋pQR|PgF8 Ilc:X/> Jm0 _W۫Z0fgVqfQ u!loI.Y-mP+W#LR__=d "LgNR! o7Q\ʧPڵ/ؼJA|c+q*&Y&qe)*|8ua كp ;m]Gx*Y=TjZ>b~1sKU";UN؋jźm,4`CGzJeA`S=y#1<jFUR⿼ku1dM$HL$PMr(5~ٚjhT͙ịb-p+Pg @t-YK}yS q鑫=6Yex\t,' 4ľ@Kvr8os9MEpϋTG' y)8ѥ}뜖"È x2\w:V]yA~}!mTcr(Mo=,C,^6VxbZJ QAaB6p]1x4rQkE UnKZ-6&[t>ttB#KzeSD . KE 4{MIÒgz;NNo'+'TQ}]qZG>ժ(NT we SN #fQ֦EWqIHHG<ݩ徧I>^ɂl#h/& 'C3=֍\ ~BZxpK_;REI(k3.ط$QGO}Tr}v$yb¬ r413 _Wa-#$. |{U\t#@YNf4ɉfVo9sF!U\rGГG qrJxe TdӜڍUm7t8vfdZI:йAŭY^agHvFf+o o~ N'Nwl";;x;#噴C?v3H|aWEoNZc=׸\4-լG`V5m2Iz7#V B;B&>햢}ɭQ7@vР'LwPsNr8M1([%N[8рOMxWR- :m]|roVrh[l dw&ж az'f֓=@W)btpXaR3?) dMy=ZV9Nƞ=êeNqp|9U vdD= *l9gp/>3$nŏ*%GZwI>$ }<}9ѺHdpQ4_`sGug(Ϡ4Ҹ}gۦ 's]sphGa} /b ٱ{S|4{<2|ؿt`鶊3&qR[rxiY0+'oa 0&5-5,]MٓCZ$؈X4ho99nl&V@(I{}ZXmw3m.Zo+j8q3kꗅ10ubNf=.5B$͙5]Gܕ<~p,'ph@WJ|[JE[a5NJTԏ,3~FR\ˠ~(=?O}9 :(Z0[r 4 !M*+4z3@ɨ qi<$ ;7m IU{8*&XK0`-`1v>o|)w^Eʢ?[`ϙ;V>8t3K˺@h^vegT'LW"eȓW:yaNuԱdJ1 "$3[ēLeښY"qȮp)6bfyl2.itg HHrOb-3 gC<H (ڞXk~m$'ad?V8HЇ>$R fOb^݋%12Er7܂?\a{L.ᡑi,? +"" LS{;UʘYw (}WˉYvS n$`PN\QQ%~k. sVm P@8ǭ41ho!Q&N*R~ҽ5 !܏d&t]Dg=03QH|sF[XgT+u ΋ѬX`h~UMJK 3\o#Ww!Fs>DErkh^Ii:@ϬBH j 9L]j2 ɹ6y Y+h6ڇB<6m;ɜ, QT4l_vO(7݂śJ,/uS}dLr%k=Xv`EE3G^!}!J?uH+ V6MF8N#7S4LpLwiFqb^o(qm|b V"Vr@*DOĚ f+]H&䏟}xPצ('ۮ6j 9=⢷c;  T)B/8Ґx6!yX{q9Oș9Ո @BCI9X|E'߹yaiO& C|!n5~3G;TY/ \5fYl0?YD{ƕP>akYF `ă -*mM~:q-G\9ʭdj\dUHnR*$N].etFu4bN}T=y)2scL15H/Y9+]-\᧑id? 8mEqWC֕Ru/By1I$=8o\g PEu=jhX=/+Mz_%q%4>.RFXn*$ ďu{Cm]Ñ)NJAxRI=ytjSk)!*Y.EܫWSL( (]޲(SIpe͙#jm)qǭH#V[i9`@祮c1]#8NU㬢 c=P2v;+ gxI;!U[; aɢ>;?xCH`H 9u=Ys A-#(q00nf"Ff\mVݍ>J&k[)3ub).qȉ?~o*b<٥%;rTk&ہHI"_ϔJ);;FlbD7t9Jp9@S³s]v~H'`> |h4\Nq8 YFo{1m/0MP&3H9WKë>̲_+泋v*>ƛ".~~Q]Hd3Z< ^1,zPZD<䖖͵Q D<,%ʢ4Is-U*s>Ӫ@eqK1N;xh݊eIk=TTșjE;IYAR WY>hPξS L<'ebA]Es/X T1.4n5[ bƯPxNC/$K+aZLTC%#'+)@JjYJ #՗Fd:G0 J`BVŞР/kq9o 5K滞U1j@+R}J(aҚ|I>(s{ؖk ic ?MNf复폻`9-#1j (L*}̼_S@ږ@S+v-1~!TOQՈ,9QA,3LKJf2cg/Ampx(}",گ:U4ZS '">^U?;`2痁>%kU< 8m?U$r0gM%Z uw,To%5*:}E%Ǚ4Պ&HPg߲\%2h gS~Rԙ[vVc=8ڣ +^H fJHfUȌ2_R/!Qce0#wF{tu 䱟AĔM@ oo#Cr+qYa߼,ܶWawF .Smb"5RjŽB;Q0m#$66}Scd/SN}aE&^Zd=[69JO&] 3Zit㜈&PmODRC**T6xoj fv|g:a PXhr ߹V~vH+)Њ,cq계%wyNBY rQ!#6|N׆[kyD ZiURhؽ%AK(8~HMSé*0RVq 8qwΝ߬~<]?r7Ռ;E S}o '@k'R(p:ؠkTyO"Ic{r "ŕW࢛ѸIy %ΚYO\*nuQ>(=ne=ļbMO6f$j0H:R' AVٮJ/%_ G,.p/$vc{\n= "; j;عXZ4Ψ֭ BNӰGc>}[xͣLp>Ac\ˇ ,ud ,3R~1w5n4N INQ 5tܟ3~icYVX.;͋Sws۹H:\FcDVL2JTZOݿRbOfEٚbo#1uK_*4};dSH _DhkY9gJjX5lHH^ 5&\8!$ GijSƘ'FWFnKw\מh/zVuA7|[}%jNA c݇;}}8O|C%gϺb%==>Afd2xiG+3O dOf`XGv.lup~WGcyzMϰ=~FDmip,@=̤l.[OI K>ieB>}q$SaEL9[-9GbwN+g$c`sRP& Exr_&?*L&ᬟǔz_Wi28*kaS–x,Ɓ_~ݵȣh ESi:?la l;tc-ڀ uR &zmE1UDԁ *FG@ɷM鴡MNvlĵ: 'gW g`b:S}=.-`a.<^kFȍs5ƺm즻z9ď{;M ⠲%),<`P(s_ت<3_H|DW}lDt_l~\=-HeKR 1ZBhm g`ɱ=Nմ(@/spV6F7Vs ӅvJl`kR.[NДqO= [LfUs}ZÅMcoNqO!%%'J1 ASK)ׇxCGS 6?'ug89Z]3ȢyEߎ&_xXL-ON糖nr=g%IE"`yT4f!qNO^SА2:^V`( 0vG;28+x_܈.aHDʷN9]ɍ<+00ZMR6w|3*FP?ҠPWeoTf;{+7?x!]BopxOgH?:WZx6oBȇ/>Q`3|*G}rlj0r^PDyG&a3'X7`G .$PG"?ee7MN"L9#7tnk%zM 4q=T snQ0V flBcD-[^ބeK~ &t-i S3ʑFG`=pO0yy}0*Vus>誈$z+_$N{ O ]"PS--Jĩh$vRI*H|s.CO9S7d66>Y5єm^M-ʔS[D BJyJ5( }UY_wu}NZ{ +k̫gU];2pRyD@4ɞhr&/;bsxd( Wqе;΃ l~hւEXrxy4}8t ŗFSoZO"=raԓq*e͌Ő kr&z@_B}f^c0DxkHs)Ϥj:">SCO<_Kpކ*3cDc|8B0 m(U `^7Neaiv8E!yMlnKsvgfctoXU(JcUW O!֭Ll$s,^3)S[/.(84.0R:.ܚ[3,S`S8N {Q/*^H?):i1}^Gewjp>!tQ%lP}PCWM;\(ek1X:3f>?n.iQxh)]3',{'H}O'rxzF+tv(CO:^0(3Pߚ@]BGDh =)9U#!bX2=ES6ả[d ܱI"_p̰tC )zDS;-qCI 1#ӲHv>^1njiįk\;/]LkmyigPɁokxoTTYFu@}v[3@q'Zf 7 eOŮ{`ϥ KD9\Ig6|.7q}9)bz_YI?ENp1N> QjNzwHu.SLP)h :6qy1uZ/ RPg?*z]axZ˨6LG((*Jyf:O'sayyE+3z˜${gpn,a:D=Ad G' */ILbCK?k5 5q)909ef9-g18ݺ~ZJ;@n_}}|oFgJj$,Rm1=Hn,rT+1uN@/Ϊ}(-X]mdhwd'Wo:Ww 6"f.\8k(.DV]<^?Ϡ{S@ a eV@j:Kc@'j#ĠNΗju4U`kWkVo ?&ېVF h Lf[JA},5ّ<נy).[&HuS JYWdvlp_sjWzҵKaT&\TeXO T.)"Id9M? |t)K{n؈?,xj!G,Bv kq@],Dg҃B@ $c RIJF4灝 3M*a7ŒsQZ㪣D`wæ2.֭528&sݽ|]C`/AqfI_ rT>\z#+ F1\oU~r[[u޼^%ꎘ+I6x__Mea6eEˏd?2+Nl) ym +X`Rw]R C7\Ċ-b4//L򕧯*_~]\ TV-Az)GM&IR6q-<4: L#dp#X*-\}J N>|Qw-hGf1kZq˝NFN̬ļk4—xY4̰*x'h2,ǗGpv18R vitqCvb7E+a5-Aك]E^vSZ+cxOMvY4k<⟬7#C?N!ѻ)rt~7N(^wjVaMX-! /n?tj\Ɛ'U&=2SF[Z֩QƟl'Z +ϼB̧L|~,L' aX khO:%>|ԗA:9lSחCXBEH crAubYWvoEj<Oo ȄJv6mL m -7c')ɃBP+&0$Nh%(8q^ZDē$)K2!\pzK !m+gMS zˎcE8⍌RAB5"+\g?pnJ;Þ$qvUA%B7pW['r⛀GqRtTϣq:ƚ(xvs'r߻VNy m[8 n_3Jhm:`5́D.-c.ZtlƔ|ٗ$d" g6Z=ԮZiS@Jf# $խ#oo"xQ24#ܐDy7ۘ4hRJ2Fdȃ zwhRf$UXb{S͞j|2zkJڶ?h$v =M|YG[6u3_>5 5}B8_<ру;V[ 1 {)iwi'!Vb)׌9v^%9lkDM}ͤsYw@G0Ӗuo=3` ZY罫B{HE`f <1 s-x0x>R[rt-}~]ah#b4w ̭"F? $ TcoDzZqՈ Z)s-s* D#]=S)r>xb=嗙(t0a=mz3Ia1 UrXG{E(6Tұs`@KDc&L2ԢI DuM 4psQ@Q:}H{g~.]CN Q>V3S~Ty 1`ْ%bȏ7Mo2clȚ ]_TwQS[ռ+K~v#t_hxK TBf<'Bn*"%L4+S_e;u~ANQy-:>}k8Î&y|̥^QYf|UΦlAdru%BOy)*#IB*6en@õ#)9YXgOٰN4ávXim^;a*"H 5n2,==! H]OG-S5W8}r`P[s"8J`Hl!yf⨘7P#}gdZwhv~\t o[jnNjbdJ'I7*r fR#]']*Mm4o|Ř54ReruV-CCjMoiݼ'efc W2N=ƵWL XVB!!< uJ)7_Z}{%3௣V_xܴk*mwW5z. E{ ҦV]qor IW&HNx 9Hf Op*"t_åh* U͋t>vP~=#Ky.~K41XbdA3YrCb\%46&t~̥~zVE=1=h??zH Z\Rxu;8D,mis 8n-00?x k#UcK&Fv`꧿xS+6Aۥ82nmz@D0<3^O'xzb%&_A#?>ۀ.#%|%MfAob"♩)Uid* , cѤ@eORт3tuY/˔9d4Ӑ=0HL9#Hf4@NԦgd.yL~eXƞTԃ?^J NVL5E; [ff)ƸwM_lXW[(g2 0 Lߊ<KFmzƐ# J3aKV^>/*knͪUvvQd/h[!S,4?@h k]ȕI#'Q^.Ne0wY6Ѩ 9*_FZhdK]U0l'(!]~VM9g5n$~S`P pAs]}Ķze(<X EY8q e$ ЮPbbY\< ڪYQJK{hVn} @/7#yWgY}M@*яp"zBJ *jf@,J)״͕{dYuu`:oJO\;Zs}n 0ZLїbW=Ψ`(ԈE6a}^*)Nqء(@d[!2|Ο1цd "(lbKNYKqI"J_Ny um9'PEuV(`SD^i%Qb]΂ #HD.Ҋ'.kMK*vVb ^eL1 M.ԳSi>B!7XW-J (% wSN2KE$zhг)q nֲ|@;0468#e/ƣR"j(wË6(AZ^3I,/ⶢ2P4+Ťw VTr.@P{A* yosx+T˛: ɟAY**@?}bWqA:S[/'!lCQ"+*H'U̩й2ߵm4ȝ`紼 b":R3s;%츕Ol?23*n.ܤX,yX%im'p |oAtuuLY1H[ i |ITU1j$}/TuMkuB DY)Ǧ'8uNI}B' \-$0@.C~ OS@^3J$'- >.Lk@&ajl V} ˬg.~sN>-!vJVM)͌脁#~ 'Ѥ -O7L 9'3H7Rvl, ꥬ~Lul 2R&V+JCڀ2~In1bDXo/po TBOtskaT{! IFR鶿Tke|u< h"%Ҙ6G r,L摅 E"!]~ @ʜlm;WUR+YZ*ir M7F !?j3`A=f AOUr3Ov~rD!%Tg+λMܱI: |a,we{*uq-Wnv" ķ T|0l(@OևwV4ZI.*-֋n:^ 'Zmp8Jjw@Kd\V>˻#mgn;zO9:\6E݇v7O-4݉Y=JRe5FxK霾Q+]?Rw`/y *ZY<@-ޥ_k K5P3N/u<`.IU>AdeZѹd\GNh nyVm>ӿWҴZcdk| &TjP"ޝjD'si땻qRbjD6)" X4?\OCN&J^hOY10͑jLĝY@lEG/ax8q!~iIzIbL}!*g Zs9P0b<]9˗"n{JTO|~*0|HR[bpぉR rGX ټFCq6`NĮk~eGmQT3NΥ.nHgD8t$ V r$@C͝X"*K:EHyL+v Jc(>\ w! Ϲfg{=ur<Ld=Cj,b9:8h89:Sugǻ1qyUa%8tyq&J>Q@nrh+vl)ʉShCYP)5@bmԎzB8+HRTȓA/3$zc? --܅ Nޕ@ͶmٮI8@8$pz~ӌ!sv=W9w3Vװ5͎*OUazy _Ŵ1>r=5Be?%Y!f& :Q⌤즆%'97R&~cKߙd[)l%wOm&g tv!Y$3Pu4]I29hO_ XuZ0LI7tD$FQVhj<<'z.S44dT_ _eS#YdXvI֧-%8 XʒLR( U6;^$I7֪SwTs?6G7_Yatx5+ x6GEAuxa4i+3 [` eZ2IU[=JW>g^h|w wlhF8؛71N½ɾ@!9%tǒw@\(FK_]GWM0CVIR{%Q\g8 aR+DsyP2@1=ehjgYTMoEڮE>1wlD˶Ig$CՌ:!у!9C1^-Aԇ:j ֓Ř0s7ujuR#V%ӚUHb|eMK'pz|`O+\ 1h2:Qz .r*p!!!\=\ ?iB`/eƽ\ 4J̀L?A)6,gr%v+3W1:hF\T.7/%.9T?iuPj]E)-ehPeFɚnZU?f@]YtwBoJ#BWڥ֯5U't O:Ĝ = !~h8'h(Bߏjv`|lk Hw SeLnW)-M8uXS˫aaEK{|'TD* o\ZU?؅ˢ-2|UWLzp7WqJ n$ :'Rox%.uAhdYuT8I7>VRp;X{62xN`.T8sA<%qcI#f=Ejy U3xo<Pp@D)g kUᑼ3Ԇ[]TՊ*ƥύ~_e!ےRB (L+:.& C߂&2"K YLi4ss@* oATWF6߶(=6Qzr·l~ &c?\6/*,sXX—} fxfzë!/s616GhGqKd"](RUtlVaz{QD/PP`KJbd^ پֹwR6Mԗz:FU%Nh qyUpW;#'V%[9m¶ TtH#>çGS!@ cY9@exWrukIt63REw(xbmaohIOS!aYbhinD̔U EpĘZ ΓxP <rk5Lr mb:XkЂ8"HknXҞlBUGڬE^|??c:YM fm9SxjI\ 4ҩG1'9*_E߭`r[ԸACV6(ӵR@[oHX旰*^0qdB,u?Tx]j[L I\4(zWzS"xVK_RHuu0Az0zwՎ ЃAV;2Y7iEf)&x/Mml͊h(NHsCR#'-c޼|ä[w9\]K;ڰ)bB9p!m֎UpJ0u;@j@q.}wA/-sT^䝄( πRЕԶ7tE^$O>t+GfjM^آa9<*~>ęITǯ歵3֓@N`'isKDHoFl*#Nш*%Q.J!*ٕ|Y{7 C6^Xrw_¸ۨ#j~9o΅=/gY IƍvGVZ]m)^[|ڎYh*=:w SV:t#ۋ,;_e4ؾ9n8fYF^q!Z5R Y.FJdž!y,[#z܊*0d3+⯹D6z,;1?")r`wO! (q -Kޛ`*DŽrAF/Uz׸{6_Yo2.òt<ą?@D:3vu1'lM*%o*w3c DaA >Ӂdz4'x}*dLzl*)a=a!YHģ2 %F6=vq{5 ~z3 1p2 u1k;_U\"oúi<<}3sqj `rLOR!=/s|u|̵ϼ3-R33& iYšԗ/o7N(Bv;ʀKjD=\`Ɩ~&cx%Dy1VÕa^釞pB ڪzMר:ޒ7ٓ~S/ KD! zegPy6޴N{z.or bXL j!5 >rFG 2tpϓ(f6,Ma+m]=gx%7\lњ+ƻϜv/HmPn%X)/^ntio&w+ɸC(#fXqEvEOPɓJ<J0 d̃2\ԋ9rL{*ǒ q}#pK e cm/xt՟N vﲲN~!ڙrMۻ;T~&PR8ޏS$bKoÓ ^-sic^ c.'.K%;_žm pqb1n<:N~<35GaM-vrPdAaf bߚ߼YRKЛۆ,RR?/K`k -hRfjK3)y[@ C,3zme%_Q?.]3L՜ -bv;'0@i ?=:g/~o`WG:T:fUO{ꬖgdU]58^CPxփ])P@ʀ^ōOU  K$zoQjUsN8B(86W$@ y( ђ4~VU>8RJfU1_PxNQ\y-)MӼ|?D 1(F)e"t3Cmk}Q5=HrRO2(4rbC?&Mha#4Fѩ?bRfM 4"=dlb;t݊wN۴U8φTƃsmAZg􉡺TNd*-x{2f3J' L/*V2>B X]71WV!TuӼ]YЍ qw;.H#(d~>ص:jZb,5-  +k83]@1#L4I&ơxSKrVrDqqzI(!?PjrnvfiCe֚J8.R3O/"+{Hh6ʼ(n3&MVj9{EUXkdfǝϢg9R皊@W Je|uo^eAnޜ u|bHoV#V>!NwP#VՕ٫}3mTf)?5v.hDبߕ{V2:EJ$Z5v+ #_y0!'ļ>3f%s <98]Džp&cGsW9q; +pLUVj E"uR| _$OФ;^w{aV' ITDﶸV;@x-(~b~T=WNlu#wݱCL;uRn| 3>kTk,#0LБJrNyBap!m+DϠ9-9(}/K\ [mr9J/(X(Cح^ kt#-,eL*?Dd$R/o ȉL 9W-ݳq %oH\)DQ&wz:ש]1to)CFEFEU7ףT[\ FZL 0DH'5g9s .塆}FRc! 7FcqNł}VFNF&ŸQo_^vrmac1vs-o2;u[-( ΍#w?E ψ`c+9+4pX|if5,5N[ϰ9C=/jn눳zD#Ř$yR!u\Z=ŏ+''}nI7g`h~{NRв5tr|5uh1kᖳukyP=wR OQф~]ᚩG MG=Ǧ@.NC<]B;5E+򃮪7>.| kש=&8Q?Rx@౶\1JrW8r)% c3) `YKSi5j`ق@z'8׌5 |9|Hc5sr"0Q]cǔ~Ë>b1|^!'aF78H /iႊ^ X ؼ]ѾIʛL_unr0ʭO`pWx>O"k-k}e+C%9Izt-{H-EDdiuX{KY8Bg:flCX1ÿg7Q™FeeuM٣k+| OK*Ia|TO'mab*L$j̇ l*Jf cF6YEJ-\ $@2M߈ Bȉ.Dj;, eŒɈφyU5ܮMʵ9/au歃M-rg2YB21/فB֭Mqj0õHQ}5IIW hTrIx=%{YxVl_ [Q dwG@;D15 d @%$xH d 2my,FV-0g]j }L* t5t骏&CKfew}=9BɧQ jJ;E'u_\L10S$wbFoBM*J.a5ϧeq̢hhc˓K' -S롤'r%Lg9]J WWAœݖqm _B`` W!UιzBu$04LEh带P-1q_>d /kvIr0W%ks [uIee%F{^WJ]\br޹5Wĥ5;7ֳ ^DDi6Gd:NXbƕ%ڢƼRT.b^hlDdHXXVxm;k9$vN3Sn"n0dÙS%wSudJ M }M( w?L N bמ|]?9FiN%=]anm$4ќG(' 5+()ZS(e|dza6,9s6tU8ljvϟF tQ#͆ݙ ؟`@L^*A˜NIgA 9܀dv?UGn݁Bxjo"O5ӽQu_:20wgڂ_;:Ab$SYu7ǃE:<mt^ }xZ>4,-,iG'TH3*BnR'&ZO{t#Pzu>e|4y S:e_/cH΄D*S]D'#/~ث%ՙYORRGv#̶ZNٵN9, oѠ~ bǒ$uqlRGt.VF8v?HiT!XTwJ].콎-wE1C s;M莿%IL.!b2ԊKt[|aݺz=p)o%a[lSһ/Hׂ8l4s֊cyj6&K&w3$ *jzO?{ g5p#LSvžmOD\DŽx0UR#N)/Hr}]hS[i}s1qz`_KIacSu5zQ7qzCkCxc'xgW_fKZ:3OΣ#/ ͎Kً|Wϫo'8?9YD ޅ!NIYKr *ƣ{Fgj惴18S39b>PYսܪYȦd5ΚC&<:AXNG0YG v D Qr3^Z2Ͻ:ǚ:n(fsܯvj wAM-t6¸RfL1ve1SɆ[~^lN bCAbDߩcny4VQcؚɬnmǚ3 ;qC|r:p'wʚ#KET/؛zKh;)W[ F΀Ζ*D[*.ҕ}F?CMUx<|Jf [:㏈-uF `4Cv͎])15@ю2punU0M%_;'+< eRرhZt E6^"n"$6¾ܛ=V"*1h^מA~cL8uwy{O',So4wQudCμ)M2ѻ&> x&FyB9}AA(dbQh@E ,tɱwqVq&^No!"ڤ6 QM}LO"/.je+՜#&EywEnM2yƿܛэ@3\_F,;ɪEf1yCWom(E q){K|wk-]@Q ƸiqMxeCS›mm-HΒTt5REӔƞb `εmۦ]?X T<ٶ|^T5*bʒ Ufx*-%44 =8ɧȋ$G-W$#H4yQ;GEy\*ͅl۲v7l_@t| \0P&tAN^3:oʣJ2bKa#?4;ڸ.ϓ(_x`ĵRZ#7 ;%"&犤{nG o:ƅ]Tk`(f%1<a%,a1/X ʾU%0 Cef$ƒg؈lO@MGTFQ.0u E}uGs\TL\;m a`5E[UMT5#Y_tbI􄾳 )WBkArAˋ,%0w6u UB(ELq_Ogkh`tBioa i Xd|oPLͭk a4Bˎ\GE遚yO_Ԫ 4d!t ҎON>*ȕz\Ye;+X|c`۝9URjB,f'R8Y)[{DEqQu21 ճǏje&LC=sH{*|x& ר((=v/oL9d8'mߗ;z-xĿCF;^ۢ8F0 .qTemR@%KQaghxj |ZyB1D3n18ù&& 2ѳM F_xZ˛H'0PdǼ5ofQa@WY\͌fT y %E$rvMճ~X|U,p JK J T QJXQ]Bţ]Kɰ#V)eG>{< \D*UaЗDueFL"?C|n; @pC!(K_~;@cH*Q)0fIa4[6Ё`Epo,J^P@^¬O,;!Ƙ j^aujNtn*۹a,5,`ָSjsnVWzw듷ft:~m:(Tsdd[&fO}<>8 =**U./::s Fi)}[ *}:^X,JQtŨw_TFv:+g;a,ݬvE kC'0td(Vm e[ĜƯ5lky9Z( q5'}[syȌ9iyC;>'Hb>}?,d>-3sV\0gN'pm>+ M0 L`ZDXKGf"tUW@X(@l/TAcmLVg0A_ +b;5R ^Kfh\b Ɩ;Jc/b90[y% z4.;~/-b<JlLO0)2 vBx-RFݚO`ƌx1OV`+ߌsՕx@Gԥ"%u{U]wwk\i-dNܭxTbz/{ҕ-\CCqtz0Y 7@+oTi;+y8 sgO"te AGhx#"]8jð:U(|=SR $Xn'QmPA]/ vyPnRIo6|z:YMg9CK$/k&C?V7.8|i x&gx[))ڼy_I4@d 36 bx!dS[է:ͬՒ$!Hv`td(}fF)xx/ދ.lTJfx}TwH. > =xu+ TqZݴh]Y$Za_}㾞[a g##d$łfI70CއīA#櫄h7HAr!5π]Ӄtquc ae77 'uy~m1S!v+ķ[:,+Y54)gԵל7^^b)\ImԾ)߯`_7~9!06-h<\WkW$.er={L}bTp|MDQqOY)A&meU&[>z'X\ eҊu۸ӆЫLpso&]/mMPʡgA bn )d+CMEy|t-"'j$uKzem!! nMnG]9XjWOr\=^uEc i1m=0ԩ %ގo`o $oyRW\ʶK@tlp@.LX2}6ꋩ BNGi mE MZfN- SD ]5M qJ7(+n Gm'׭J@_O}O~ zyiD\w,Y8wP8=xTi܅zmȓn/U+,yV5Rc.tNE}r([ |w7޿۵_WHW''{CM%FAFr/Lط2i鴁 #K,lV>[ :q ɷ xSʩ0>3(эTI!ñɦUb9kAI28PK̝,Zp/Hpf<*krJgZ֪^-E()" :`|'qĴ7zhwd}Vzܾ!3 \fִX:fZ9Ƅl7/4ڕ=_!{ 'w|gB1eBIC>aL+eEe8uTyzwz2?Re!iT'kfI}U: .I+-5kFi:_r߲o]NIaafGgAW6*i!OTKzl,/Uh:LSQ,9ÌiUFd器K.ՋG'/6Ys̨۞$>ad`ӗ3}.swcN33Ț07j1"WX +WhG#iDxaN;Wa{iF<>֧R,Z7Ryh^ɉ҃|7%:8,{Nȱc;[{JW] a lɣu7͔+GkCJXտ"Nh vW\Ce#s았zTkR T,vߨ=K'DxX.xk k mU{Vɡƒ y R_CqܓiVD430= yHgqH!Pa9y27eD^CZ"gHD:H}Jz -_!"TJXj! d܊4M>V5a38̹} )iaj6yd9f#3kK[3| x'ctv):9P{O-Wݚ3~rVIܧbV VW5s +}U?A+~iש6QK8o'o0+R!sb'kGI+:OFб;UQZ +(WQ=8u@W>< 'Ew J:fc!M/Qd/A9ckL:a(yG4=J}Jݏ<;Hb%L(Q?xcv9KN)[^Fo !K.F:+ą)앮ánC(vGZFwz`Ere K+ ; )#m﬘Ci3*\n (% PǬOCR348ʜ{ڥ:Z x(ٴ4D C>gˬb1x9 ;"̗#Z*s~p'ڱ%5 I*nnep6VHc TWiS*WGc1w[DjH$JU|k1EfJ({ItE LiP^NBk, _2H").%HZc>ӈ\k^=uUi,"vazVD(YB{QO?*90S}'@8(yudR8N+:u+\V.zx>b!TdŔͻGެAUZGeA wm#+6'. 7=~'Qb |`ԣ8^jadEA/+_ҏ8V\\Zrtin: qץ,HӔ{+] 5Cc`#jGC& J~ )R^H7)2PSz.R-Ѽ)}jf1ܶÎHjoO㫤5f#Bߔkp{ӣzSѦik^cann߾n~睅h!?fu%Rbѽ?{sRٍnYcHv>A{~2t%iGgb w˜kYa]9 ּ0P|G пJK 򏄊ZX;B^7VA0z4Ay^@S 4W9 =m,Frt>IoJVk$Tu^3Ԝ[WRkKR\٣/U€ΪZ>s싮@0]1|M Kl*ѡB#]ﱆN\ѴlX莻dKHƆFKUG:װ`Jr>(&ΐzwΣQ4]fUTfP*[CWnQ>%ұU< _np"[y: ߞvg O+>t1W9Qk Z8[ ) ^t<ٝ w$d+vfhOtBTe96, (MZV~ )![~d<4|AH`ٯ\YD[8=6pqؚ!*ﴺ;*ݧڼ ᭭'Ac왤|O}T.sszu (XJҹy}\غzuX+Pߏ&w KMn]T5ޣ;FyJȫh9ҏl}I:o1wb}=Pj 3alkasľGۭҐ0Pk!'irWmh6{*g4U&¥K?.PD@b20צ r7(o7y=e d<ܗszaGUJM5,BC"۠\Vن2f@}0e9@(r:\ =5bl[6.^N:#CGj4GVU)직yzJ$ÓJէO0(=$\Yvʻo\ qi$葪\cVlS[$%M}D P>8u;U4׃qI2k;]Q)]YݙHk)&_R Q>_0["F87t`ґqs"a!KT8S9NBq" RtG"CdRDT =("@]~ED1>dL}oCFG\P9]Ʊ∖N1kɫ4jQTli#>6"X߰pO\?ts45g@Ty_ ET~-}H6*KvCNNZRlMxhQ.{?N :~IA-V[Vx-|Ltf%N^AGb WQ!v!h ΋s|oNhU|!~ibF_dnPu'[҇7[.|EbP'rH#i?FYB9.z})}J2} 7/@h]-/Y`V9ֲ풓lG ,tOT/@&`0By:ާ“m0 xqFa/!L]g?ߚTYN=IhHSo¡N(ZR>mZ/ŵeJh[tFSP&Ev|MurU|D1`UGP(#x*OӐ$@#Ҟ6 Ѿ/ wH?Zs f7zj9 +| ]o?vu |9=MJg\M:^@ޛW65pxӉoA=;UdʛuIQ nPB?):g^(w3<%K4c9Q #w֮)b.j3H Kg%zjIZIj󮸋t$mD`m ͘q34v')b5>tȟ\H]XD}moD$ADEH&\ؠ)WGD!()w^剾PHIϻ!O׮c#\ :v" 봋t_o9bp NgX%?2RES$}[M}[Mz0%*ދ~PDmz?dxE#Q> (T&+j}zzJ.kz>7?~#LGt(lܾs(Ke>SV@agZ-:pkx7kis:ypg6=8:"iw)'3GѴ1rWXOXDpU"&,ow{8yXg/zC `>\Q>NR cƟHΨLZi`,ozkFg,y@H[$xW4)Ts5u5Gyk–Oj1=O& ?0"|v8%2m'\ kAue̴YJfRTp$a-)y˭̢>Еz4׭I#{=w,)9M %)xLY^p~ߨF4Q4x}IGAЧAdE詡ÛLp*on*&F|βI;J7r C1^kvf!lS3mx C%@Ȍ (WC7a#ԑtDAMj*n iVܿ ba遍ǔb t|w\˼zx!21"=K=NeEݺN 3BW=8XC!Q`~[ђML\xb-BݟZB6uN%h*'$4ܳn#A$zRmc,hގMe܍G]L15dfFF\$NpeMtC-A`9R dpSK}7p)-[~OvOgBhyhb$$dv.x"u׿sU?*~Ό'BɈPI}[ЃIW%>*O~$vhb|gV*Zm hjs9F۰I,}G*C;^]S[. ȵS އʗ#X0qO+j]meTFSj?}Fdѿk[$2TG]Ό-2YDr񂵻0 MPPO] ]@ )r>F1¸+YOXkI|Mk*&;Tx$Nq}9 FLp<Pu`i>`&n$ґgWrN&_k>R?1 7Bq~˕rQ0ȾaFOv 骤cnY||8Ɵб7%mOn _|>zvV5 o'MQc\.RHH|FnuJ =6FI/J.ZĤ|)K*_8ws+>%6G=Q,iḕyLPlJC%k0eAH]rb)͑Ą$Z .:K/ActFZ,*\4d}hl F[=e$|O , fnHpI7n`kJ;΄#q6&YA8d,&F^&VΆKQSK#3F,B[cO %so;5Yt_pZ6#iU eHS +Q1h:jx{벊l5"}ۯТz⑷>@YKtDxYqGS@ '<='\QUx(Pއ1鼪dMD(eXުx+աa YP#d@'oWte9=D|JCi!`%nk-.5ܮu_3<_u UE ~ʾsN&7U\¾.mo+sJ|_S^Qޒ|AI/ɴ-ZM8.`⛖~K68TJ(@22 sh@~bUGwdRdfY'A3bTpy5kn&}_|(;*Eq)ЉQk\S#; ƙa>6Q0hӍqf'|fH\T!oz!rH_¤UbLX'FŒ\!ȹbf|)/)F8&2"Qdt)tLeuɫ1@.|%o;ā۾לnZGaSmB];gŸeUzN:D-gE:tz%aಓsmI2+z$B2[v[:ӡGz,c4OzfflW75B%cBI{=Dd kT$ϴė+M:^=xNCԾ_Z4_v_bK(ɥnXtqSq1b%o~jJwDǥ|EKsKby7N-&"dFV=x|Tg3x ЮZN} 1-#mDN#X1CɆP63Q[KPꄭօd?'-%fT+HוC#ۓuqv|Y_$GL8[Ƣ-#zUȦ|)WGuMڱW>XUf*ReeǘFHp:&^hNj21?6 ˓.TZP;sl`VpڝVթ[1ZMOaGș҃iMľ7A_i7a H٩1Aqڑ3*bQQ]&7Dz0һnVldTEpDa눻z6W8[ĀJjGD>C_v`"g/W~ g96tu`\/DoyG1` Y;(Zp4.,x-/H: JݪK[TuMft!ŴVXN= p'tJ7{>&me+’HUX|5P|=A(5s3A."1um2$$Y᠓}EUD/^H&̆8ߋF4ڣ:MLR 7go]W4Em !0oRر1=/Aȝ;*Xw1ICF=4P,%֥}I~Q"hK ;n*f6+B`}~:']qW Xcupi}O4. c+|;Jj9C~.jQebбnʤGI=繴~)}8ݬYCdƤ[@~wUBr<  T0 +9x꒟_Mرҥ 7um#3 ;Hu &gc5YC՚ \@&՘ PY+ 22U03l0T*FV⤄.ٴ`vh^uL|@=vQ&'80fR`Sp7yC{B uvN1qtvYl>!܆uDtn`V'˧*ePsMbؽvMz|y* KIE0L\g$[\uI5ӜA`mMv!0|:4X[$)D&8oEC޳cεi0U8!f)0e !UCrԽWEn9k,&]~Cq-IRէQ2<=[H\Lg>,pl ̰pK]=ef^Km}!{ it~8k?hn훫cq)2!5m߷D/#dO;S'aT^0 " /*wFډ+2De҄ M_b ŒWtU/;R\JaWΒ z&ȶni 1lwfޕ cq5,-Rvv{v?м?n݆ th B1a .%zm"%ȨFOjl#Cl RSL?L$J]ä~ʿ]H͛}.d_z|<"X 勱3)gP~G5'}x〲Az^ TkL0}myAwl-F VrW՝P贐}bNX>#SMӊeAäۂY6;5/kوȹ^lbIՙ>=WRсjE^@ՂʣkYo œQ^;m6r;&r> UW~|!OI.hra0\7Sfz<~-0X\M{q2ia)5uEhTwsj U(ZDÈipZ,r!RsyoW7e: x3\znMg:T?1ػ IYcyj.%p^P<"I . 2qO|Ο`㣮x{;z1ʬpZ4M8XiOjH/`Yn{)R!|,-F+d\z?|;~j]aLoĐ2(B q;Si|k2GoY& #-O$,MON"-N5#[blv\G2CT$ tq6'p %pxS%D'wp+R<$0MJkțrυ긩u=Fm2=2d[d"=9<] YmI.} b[N/1G:e,ŠaA\8g~VbibXGx6JI~R~.U>*HvJQuj\5O[W8Uo 0 b(ѥnVlS؜SCr&bolOMCKXbv?`9."g.0aNG}?|~WձWDʼnk?p(`ն\yv* ?p*U^4M =ôF@-7$ẹûLfX.2ܒ^\0Cx#+!^4G]&پ w`"h?; BbM=p|qg{ƍI0!<0<(:P^^$Iӿ(g6>' M6&Eo[nj Wbn_3bAP}}WG^&JJwٝ8 Z喨]B()mz]d1rD)/** !" pe@Y sz,DGs̍_3u@7j ɤ糝#\\^pP`.= i<(M}2`6l Z~X@s&RŦȣKӯ{7^ c~Wh1=Bb Ou# Ҡ BXÝE5B(sŎ?#V?${eπƄ^=^'eF˞s4vWrDlc:5o~7|CV6 mZZIk@s>}Cu w@X8)cTVݘpTVL ֎7\V_KWXb%KPCemWt׆xHC9'<ƪ ٗ^{&IJ? F$LL-wݼdtu4ۮ?lߚ^Y=މ!X4 |~F(o᧒T[$7K"R 3@@߮fZA $ y:wpggbM`H줝KC뻸OQ2d (^[A9O41vug>B%uQz5 ] 3׈M@V|&KW G1]d%V &3{Fyۼlӥ]BUyw^W6(~LA,_i*tl)K3rgHI( %RXt& 4D1-n%*N>u@\#*IG173e}A&Pt)DȻ?Z%H=)caqI4Ms Dd/H$.H/Tt!j ŵr]9JRJE :r|::$7Wȝ D;֞βk }5χs)0Gשt lb"Հ] L*4ĸ_WTgG섢!LeʮAaF*ޕ"و| _$K/Kjj! 0ֈV+sp+Q]=lfbtEZWO)v, L3sN.&gg.MID At,,lv#$^ ۾THk<: CcMCquE 1ߡِ!N8d99Gl,C +"@1b-q v*X"ZZ^cagMI~=)/}eHo ʉOg쓀b ;V0c?# GiS +8{yBF)J!v@%۱f1  Gd Vq:aӰIL@l*O͕TwCEy#$8٦K%䓊ۀP\E5u(+GnuxZ'fVd0:m'ȳ&,ei"wkEm2Q}G)  * NG"<H(fxy}fDbr;p@z85xU")̉gЎQ֠~j7u\4eA˭IKIbd7}'3!w2ℳK~0@!,Cv.y;{Ţ\\nW$/Z,?[Z)Gρ͉.HׁGuD+tV^$mlv&]( K{*MD$ +8S_d;rwҳҰ-Qgh[as8$&4h ;m8G:G끃!RK{h@#ڲcנ3; tP'鈽 $l&uj.VאG.ٲ9n|fM@1Q`Թhy X~=2ԋlyhc8jeAF Z6mcFJQhwJ꣌%և@u1mZ4[#T\j?e;z2t-/ڶ6y_sG(.f7+$?.DJ =]ʊH6Qf)5bѮTyT#憋Eh%_kpÊ" 毂%I erwc^$}wO,")( 2A}4$k f :H?^ЧzCqܷ_t|?zCp[zxyqost;a;Cd=9CcjO_V;@Bb:YS9%V\?'w:%Ixa8{aꙆp׾ 5'% e4j٩Z6Yi C9L*D=ŏbc:2h\Ђ sF3ҥ1lrU8|j`p5 .+Pc1 Q~O u 4XXD6#^- $ >laU8) smi Y5x䡇c$ D+1iMګ&?)d Rq'{Zb,:oDr2t *ߪK=iԘܤe@tc"珫` 1V ʤJ#{k|@pƊTx`ż>X% SQ=VEw$ռ'Xo*}9ةTz<FNGtL9xBl0.{}`zp>QR["ߒ7 kN<*z}24ۘQ ?3hѬ%2dBbKi3Bn(e-LaVE*y0s@n s\9g%}~1ҝ|Q57Zwש%:恔Q,G /v>O0)}{O$EshII4X~)ُ-Z61!vg=/@Ns3‰LR1=(D94S纪ZAdap( G.F1]9 #ef 9t} l,!o(CLÚ$eȻrh#N@QI\DmJ5 Q2i!Dizt g|H +fpZ.JYacwǟI tBxw9C`QM`RAylAIJM~ 淡Mn[j;:Z|ww^{XW!SYHY7W1CFRKE:p.,=peLj. Hݹܫ@+]G>}O 0 h'cC OM/<Ʒ]Y${%FW:h?) ]gG~%@~]$jC5nDsDg!^ =AI#bn(VG:Y6?Oilφ ëq!Q3e5 herG \??.3jw< U=Qs{0Eť!zJY˜ȶ5\GVg"IqSvjxGWiQ|(s#]aht9|~A&2XOP3a>^J taWz0Xx µx.m%(ኃ.%f.UHʣ_Cdn\)L ãkc0>C,5Bj %znθXq 0y%"ӟW=.,h) y7W۟ yg@w+9U%p'~So)s8pBϳooIt^FG y< 4 }cvaOWV ;]uP2b14S$zN*Lk{=s,)[Vff:f9'S:i'*jZ*]Z䀤(߭K& EjVh?3W1gʢ2SuecRzUȍ@h(lyF ayys= V_DT.&bocDV^u%$ŢJxmv"L s!wE?z+*/0CBo`<5`쵧JpntX׊?sDB b36/`^*0뛧 eq)Ե*&Qne oN^PM ;<}pGP)&2h @`)툡ٓ|Hf $j) .L ن I~ 4 qB&.?@} )"1|4~ǀeϔE]k.E]B\9>Qp`I=VW Mt?%h&3vf|3##9`tZ]b )y rU2L.wxW^}kjlDVYB><[|'Cꧮ{ۭWAJl,s_D>-EXH[B7@{Z\:T,VjAU"Z6,)MU2Q̛fz\w>p]Gp!ĩ0*˾ wFxu:i3 7eݬ,0]ɝR4}mKc)6 F8&$c-1h:H]Rk4 ן(q] 5}jV05tI|_ŕ}^P>jSaɈb XK4݀P1?aDB?t_voBu9kO*^+s,V n<14K:^zd]ؒ%ajZΰ.0%,dm[II73HblHu2#xED&r,dRAk7jg<~%d';ލЦx1:8LJ0q=SRG-?pOn!{h|v&3Df1 !p-e>|D!\[2R,N!!j3pXeF\hfA\đ|8r-sE#nN:DF_YeSͺHˆIfI *pq̗ mC%lz$w*܂C([o|n&A 4ʌeYZ'+3>/kzC aFu~c ”瓋M;I::.~vD!2c}>S^F1μjFŪtdW? >uNCP՗ӣIO`WιT2ҏMzU>jKI:B]m2%Frտ[hٵ~/l޿nXf}P[@{o>gr 9Z*ezJzVW~|(vj((6J˕O s.O#2 +NHT`$uҭqdY0x-Y&b.'6\4ȨOQ[]0ßUvC2=%-֧.$ueQHΛY<\7aTzw~z^'Zv.$cdRr\}Xy:uR,aQ9z:-ĜlЄx>\1v!gya.qM_6(-'qe-,Θ\UrZxUϞ 4KnSj&Mԙ͵G15يV:s2u೛=v$™x) ˴wA@3. lR_pBR'4ԙ^և<ۛ=^2gq"}w)o('X+}`!U0Y,J!jφZpwHi6KW9ZdE>トe;w ;hC 3jsss d6zXC53PFF4 Hȟtxfqie XynM0CFJ) ©f*OqA2F+C;(̾mi==ZqtIqg1*\궜)s!lɚG 46i˕5}vf낮i(ޠ%ff%_L}I6[]+EXFd «DZm_؃^gs k0yaԓ^CXy*$/W̢ʍhzD@It[%Tn_z48H{N%i{ -3QK26|?_  C2MhuKkܠQ܈cY/Auu{H2ĢkL1Xt eE1nV> QR=AIp=D*չ\=F>҆Dnb. ҈ Vp.s[o)L:y<dztH܉bRګSLNT PiSn֒,!JFsf4FoM3Oe}aUoZKR!ox}UM>d(W6Eߵx"$<|;*kyu &T\yԊǕaES^=71KqԪA \iԣ0|sƦh0g4)W>pƤ MZ`guLs5T+E6&9:lNCI^ek FG2ɲfU'6r~ lMa6#v02QƂ4beI*8SEiQaIFoBoSf/&X._Õ;sQYU1ˠxSލnVQEvPh84Bgc !ԭ\t}$Y2W-Kt  Am$ ww,)tf6GB"NTyٗH=q@ϐ˺I7hof7$08aǖ"Yޢft+gDe 9]/ɯu(ES!=_rẽL@'g٩\ 58T㬵'En#va]ҌrNX x`,s *zXT|+pc}Цs LHLeSI줘!uEb uVL evf>FTg+M16&Qs0.WiE۝MlG5ˮu1q,.8}& :ջi'DI_c)SDM7R|2L@ -3ZǗ% Mu3td!^MڗCF[x2Uq<6SOzWL*!ŕkIr-A?ؙm2F3#C,PMפR՝UjihڂDC@,LONJP\))?Ȋ'!'DO-KRْY t!dƟC}QwGTݟ̃AHçsKoa3_ %&8hdU_0:p?uHOZ;DeS/YP! ňiPw_ 8 :PVr2~wI~x|dHW ZTHl(vCdn[DN#c+P[^K8#ږܧ :|#h+k#Z=v}1=&,;dZ*[>LL{;w%-]^=>jF;&+M#:nf;,jK$$hpm mYpi:.zP6(tV9i$gkfe|Hڛr{ KXm rέC\7j1 tQe sݮp.hfom_=M"_Vpd[?з bt7GQi! D'[5tXC4t=v8;&Xb4kg(OE~,cEK׽+YFiMMG]J0QO>ꠔ5k"6@yO#͵#F1>Sk dUTҫ~@=,LZE\i(>M4d6 4Kf'3`L`"v'8|/{kNAso1 b L7´fzU;8tWP, +yv;XXn,Y+$+oTBȫR{!+b)I$hcڱ>Q+zwa#z}\C,xPa|OD+@('gKfsK78Wdg-Hy/K/aFI&w1i'af5a 0"/oifok;a(N6$* ȊWX<0bhU뢡TjB] @G4^i^Њ Ǧw!ȏnўӏ/RYWW^ǪLff(QE0CO}pGu -j)uD]uECMY-;eVl|a;H+aaqMw 6AgJV34cP367{:':*-CP+*;Tݸ5."p$I]4ө_wg_- v@ƾQgM0?j.gEΌFgϯ_!( *p j!-_oLaFx'QdC6#az}tВb#9OvfQ_*/i3͇P;H ɸ|oG,Vbr?nn#^vNfk_con|{P077e-RBOFm4^[Ԅ9H<z Է1`&( VWWoЩ~ɷ2W˯ ѠYK~x/sP`*Hw3b*~0Q @^*^BA)pO] 1D+u9QpЋ58@gNv{в? gG p~H4A>/XHZc51ų $c?⁛~ϭW m/<--6 ;A ٧ys/D 6} 2ۻ/ut {0l`r)}g`{xWEQQxO>*{!7mƭ>@ÿ":J\@Ã'7bbݞ 9י6m EˑB5t0& ^R~ad e#UR婡Nd'">^~ tk; uC5y Ʉ{~2. ,i{&9˷@H椋5h_<9 \aL0[1'vw/ \U:"ƥEe/F!V`9*V##AT9aj|T#+TM͊?0蚟,o1$6Bx‹ҥ"vŌ)8z@J/̃cR5bDCewq7mth Fn''PUN=FkU , y1s2Όs =!Fl--_dV^"$̛F󥵑Ք?G޶09PGbnse~j?Yl3˻VM[ϋ콧#wxV؆C ea[g-"n꯴*Y[GDz>Pt&0Bvtx-M&W+P)H)fPؕ3BP]~gvOX'}:ݓ =>`qeege5N &m<ϛW49^b!nWt! )4,enӥ\nXgA0J*]9SAKQ~j2-3|? 9XFnt1c8yŅ28s*Ye`טb[ťlsd}vGJSNjc`F:^YH=;-|GI2p{棶=q TaԨ;~.s$V3nr3"xj3Lh?.n(- k0 AА; $'{8gLS4!\59/!_thj ZqꌽƁ>tOWZb'T)xSU kYT(~Ÿ,w\wNͪީ0#PE8(}3KEysU$(7Q^W$X]M5ם0FuNF̿N Y_!i%ZɄzB\.O?9qP$)ˢ"\;s_jsE [y`BrL<1.j}/#""BKJn>z=s#@B–ǰOUGbXF ߠqg= X@VB=8 eI;:puqr5~q=D"_d(lČd)_b F= AAaqf! ֐p3(H2ކS]X|VI~xt81\WӾR]|9P²cy}%:12.Gۙ6 K*Hf0T"/,K!BZCP\C[\NgA5ѱ ʍG0eD{evGeDAva >?dwԾC5̨c;=|j6=ðbTBΓM6es JRK㸊W̥0 U8%~uxH0D趞GF+ş5dgXQQYT$k%BJ{10swǃ|o3erH!5eͨPZ>NJef|v.c=p ؗjc hF6C_FoPjoh_<kHܵ2ԆsBvvOc%),._C0,VE-| geabD:kZswQ1TL aQmGdsOf d M'p`uGxRf/ Tӈ^yu xmz)=8lCzp8=ʏ ) =5v6x ^+AQ! (Sl @ŃBL#Bj337+nyIYMIYѧWb|X`$B#"x1ui> *CbnP1R TȊiA಻,w~$4[MH%}"ʚoQHx蘇2S;}zҼ@mIi** N8gco4Yd]Y%VlMy&0-Kl9mߏxٍy#$ t+f(P]v u3ڧjFD WCtx|#c'0(ͬ=lnޝ&xp>`0 L$&K40)m]Z/ouvp.~tVuPOekW{nص_mWa-FC8-,%BPvDZqB<: Yg{'u;J'LH) |1*̀Ԗa%`tP(:3DX`Ywa $CfT&)WQˌ1PLmH!h._P6=[z 􂫖3E3TU(& _V 3du Ex9a}^}Wf {OSqSufN{sϴCŁEQc(T(z]D; QmSlx8m'u5?BY+@r>-EnɁ뢈y-.c)/`ȟN\= XF$ m/cpB%#l0Fmt2*x 9Θ' {,Q{ڹA*Y+9X۷$k- DRo’AIqr AaoWM,tߌnaX0ImaJe%zA7u]ɡbj#i]^ɏ'f[?PŖHd.P%{v!< T5` ^MW^Th؈5<zR[ =L= @m0(9΂|)%jOj.F'Ќ:dziϐZ+ILV@c*t&9xH8Si_PQ@·8u"G;0 ]'r*pҿȫcAM{s-G $5ǜGIC*dޞУ",E D1BmoTsV= A]r9`NI=ww>(JE]Щ}cpU|H>3^|-zpxt$*%9迚isYn>-QGp1kXG|rtqLʛlMy8UƢ%50L&ah]~p ro^96f^utWa.YEnқm4@/}Im0IG\{`yCp=3v6Zf_l]qfK1@k)@RT]KK̖!MHq5H˺-@A`nV+M=1IclGOd̃3ղ"';V -^R$!:*UE6ݝFw n8wFp .ҿ-]#mS7*3Ļն/T\{D8SEb&7:?$tOMpߐ[uBNus;k榙Db 25Dwig|~RA ̞QKGdoxx~3򕌠OoHq9jE)q dQh0`ºo!UDeq/^!ZDܱrKPaG` N)G`= %jFIuy}q#?g87Cʕ{X8Kٱ.GWPB*FP*_Byu}et'L"c>&pJtۢ+y7'Fm#8]we =3ମ;Jeq$`l=CK;kg=/~JWmtj#oa9t|FPJ:8< (ތ,>]GHYVPo(mKg[Bduc8oeR<2P$ vu* E[8 L;s(Kڳy mMd?}bYaNChIeT2Ngmde kt/Ͷ^2)rO8*5ʝܖk[]&yC귏p\@"|MSSLB7MFµ3P@yМB/BpK9bAS%o~"|z7 :(#S d*E054Q_W5HvK BLq'>#LPNHg9s9s9pn5ޗѢ3%){2H9΋o4*N+3X^B|>Fi'*f$x@nB*^A?+*s EMGbbBDa+av̀*`S]ImYyWqhuy,ArD9* ~1#4eL[\侤 Wa,`Eϥ K͸z|"Lvi`o-E7TP@Sn:i.H|:Q錧N%Mx&rL 0S835>O #vS'wp~*-HdZ!:$>\ಁUJ!{1񲙐$٘GWʪZP8o,;: P'v2ݍbæRmѳzKwʧi"rp_XԳfHz ~bQ2 $;94^cXN\&o:V`MsR[>F c'٫ɹ v( уI$AcK 45h.tYUB}"aJ4=_xqTB/2hղ:xCVLItCqbN y!td/π,)Rn(/.__ǎ6UH^yC3B_K/~$.E!ѮmNyj9{NxZ͂"ڛI9p(:*í>ZҩiF[f<ׄ QCL_U+ >أɏYHyTo(WG,'݅ 31R40 ),rdl\2>'X<@IH/ٖf\3HE j%e-$mF!! j~xs7dB6# ۪B0W"(7E֡S<)FSsEb.~&T8Ut4MYyCEA4Y{ŏx{=[sAZ[ZxW4R$k }m3@*cͣeiێWX)uv _Epǩ5|T՝[f8|~EB3['M(u&[:tf{ͅupe.Ov@_C* T?e(2:7)]fH!0ߴk"ۃLGTN8@k kU֟,ZP-`PwRnN#͜~- kL{Bj&=Hل*nHu/FSa+BBBS}kf"\2KK=[.6**7q/ 7TFRx~AnLOA=eg:^q|ore@-IC>?&(6ÙPѝq*.M3$:40FӘdw~rM v:vR(w!SM.sm3UUUUUUYҡ\'-nZJs\8(,?U72 A@[[B|Pj_*fHu2~ CĩIlb% hLgd%Fk+|d@+hNaⲣX/'Bز c'U=MͰNlc9amynI&ߞ5w#X)Tr8;=Lwx p78Rxg2:Ηg rTs_ZZW95H̛iKW.x;^= m@4LaA1sR $?@yWR/rʨPF(lh^`\X ۜ=/eD2by)"@QZ/9i$*'!n(r c@A9N'\UQu/͗@EP$LcFW,]wm A^6=ysT ,y^_@z7.jc~fFRF? މJQ PPW*,R\Z ѬRkd=;ڸ?m?R{7.Ɗ-?Jj}*P1xFؔxd#&*֜%y6#fK>*\321=aC'J*'O[י*~*h ٛ16I%.fFմ_Kaыdm?݅&nށWuZsmr!^GCʫZy3Ie 9 SҀK.oRja%͵zw 63j^` SqUM:MQpN/Ű./e*Vc^i^_|'0ۯ+UxvX! ?*@op稰CMIQ;<]-QcΪ9^dߛn2m!Bz~r䔨0Ǧ"t[-aR[̕G18(iB^o|Md6+eMĭmϻ8Y⸨Ճjg vP^PcRЀQ*1MAJpAY Z(@z~^!$qn52)ArkM0\)L}t}P3S_1P[",VKK}?Ra)ٍl㣮vͿ *]yĝ7>p6S-?E9b5? $ԚGhBw&bBM44~w/C3a v]WŮdxDy*}t7vA[z=\FF)YXQCU o-Ƽ˿1y´\AȓRf9i+jT#xzbsOjla[.\J[XY[D)`;yK_^ $ kvFoS-< JETjb49pW-G<xE|c#0҉!Q)6D`ȟ3@C{e*7MP`OJw!j Ҕ6?=)B<"D#"ksXϒPaU6g{; rkmc  w}gߡ /ÜܾxՎ\8rYK?W1 bW r'0OeA`h<9\Gkcwm9X?|.UV\28Q㠼c8{MA\X>t8o1.]o89&Žfhנ.AtZXČڥ#&&uoHoN."1 $V ٳ<ʖDHj#7iqMIj dﺼcUȁ9|(A[˥qYI(+(ISPkCFp]^eoS۸s$·MG;FX TO摄2_j&8yK˽S}XAp3n)!]U0S<􎥓jakEP}Ml<2FŐ ͮ:#֜>RG G!u gO=Y* _Ss~d> L 顙QvxX7k=^wey۱m/)4vQ졛(uK!@[ ?;~\.N'hjaC.,.;u^ 8U3@~XHĺ]am FށsŬ gGvI]dZSb?x*m6ֳ%\䖋VIk1Tݛ"[HeV{{3m \?'`{VF3(%,-WYH+P"ԴRӸp :lpSfNʮ&O1g_˨:޼K 24ylyU ,G 2l%1cz9;2aҩ96~uV~F9]x??:\l!ÎAD+dBb?K腾zb@]t^JYQi?Dfx?D؆En$Kr~qtZB;~orS%2TKy1-U_vJai;$y:%.p*; ,n3nCOw`Dg\ig^Ib*4m!ES.˾k,K\_X3Y:7CF߄ v/wLǯ|P[eeSK3/thuEmeg=(Dh ks9S[w(}ףc9[00]J[,fft~*lSpqnKA~4G~teOJ_jGOSy|\%:e- K`.DA#"L@w4A|U$l80ig'K ZEl+i nd=oV몗*l-@/ֻ8 )pQ qG>LtSjOB6%a ?wA@f_eggZ Aώ7V@)Zmζ֌1b!͛Wv' @;H㉤L=H8E$hr#;/OVJgݘPSUK x)fܳ}{EvUw'>Xɥ Mf`n7M8NeXzpz15.ĨY  /\y }+N#vE}Ո)`|E+IM0_ଞ0/LUOռF]A,d>< Hl菰ܑ׼2vR q92Jm!8@Vj8J,pӝ1Kϼa=(vGEdpC apw(Ydg~ W ڶH4+ t8'"3ٮ.z3Ő,fC%cJgiD3 _cijH!B)/5>=*D<NgwqmPpTMlW ҹ3j=DU<:3,\iW?qp=Q(> Y(2R~ν/eov0ې 9uRZBK 5k>Ϛh|J6UU]@vZg NJ${קNI$9-fߑ9c\;3HUF h7owXV5薭Ds-gVτ@Z@ ר*Ϗa8>n|bRD`B_4Etݚ|:<^'t;BƒBoQ`a]ɬj>ୂ5Ϛv[GyW8,D)?=Lvа+v}t= =yD `qT&}AsEl;n'mz@9䯝HE.rТ9]3`8)7oH -2(N 3e8fN齒6E:"})y6߿:kz#r'K #(@bqNdXs(3[2Әo}z+ZkqPa@R8'e(vHi%˖GG"e5ѲwTlhz0-p2&V#_ccULp.uM^<$ts*}\8ժOx&)Z|q7CO&2ʛ.$Rb`R=?7T'pQeFv])NZn9~F-J )u[~~@XOg Ͱ=erqv B"-2\S少ajiޖ#mi 64tK=×}bVݐcbt(fU(X!g%꙾c`dQ•l4"]OjfF|pU$̳?e< ^R4ӞD|+S Q˂xmw-x?55/psNa_{e2xamܘ' ~tY^b$Ts3en-e4tTvrRs8NIx$owS.4,ڈMsRDOZ5d6 JF+HP=$p: E1/\QQhVSiE~V#D.!1vvr21*Cb>i=[K׍Fu\~;Y'P  tvNKmb{*SڍkD1d͟O3 @X"]][}%~x#+۠@zںHHlzW[,\yP~q=Z/qO k%i_#%# G쏉NfJ;ڍwKbt y*P|D&Vƹ9ź<"PVw4 / + TSt9;.;\XЙ-|Z8xڲ(]%kZ$  I BhG͕C -_bз#p&%|6)!`ޑΐ+@`fJTlZ'kQ_Boȁk._SfdPFN^h u;?=%w8}$P獒9n#mV{ "6,[T@Tt2$tܒepd5%7|omΈCrYQ3vU:AҺ]W- I u @Rvz =UWzI# ="OZF=@?%s-]@3D=h{lwLIa͏yɟr(U5rf.a^3ndr|g̛!Q47nS Ng{&dnbU`c ^_yH9~PJ"~?rG7'*e`ØhG -6J7u/xp.D E$5@Ke[Q; r l]¬P o“IMHδ7aTnbb+ Jj0t#'p+fZaC]qXll;䞄hO:*Sj,uj䚫G7bs'~ͽ/P!DLr6h;% 1~ɢ"&A[ϖ(dSۏ#B4EsZbKy#vA5ioY[ U/xa.*.n_x(٤g> qmiDy+i"mZ=nYvzsT2Gߧdf2}r(Lg à:kIUξfH.+Scn9 2"UDoqee!{^2;cf.[rs*YAO$ ʤ"!" ጼi͊6VEIG"b dUlZi;vh hr65,?um) b^FصK|58(/ѕ-)ڟ[$UnzB% W"8Yc "ouڈ2%lA:I8R3o5 ;N9rPqm֫TBǾ PN"'-y1 c3HOس1^a`zp#Ϯy*޾ YDbiM~̕0f HHt=[S|9ԑdL;vIBvBƉΑܔ}6Tx=?+SNm1?.sA+kTfּ3Yý"]ϩt_). Ȧ#=/jl׉h@'5:"o o'5ŻɃz*I4!8z`Lk0 z'B zL@] !/WaLj 9ET@qAIF=mĜZB}x{ njW >=tX^X$i#!>^+u5lCѝY[28/:+} Ә+4=,n8V{C%;'岃Ӭ0I=ۇ{ccɜ-_W.1%BI{ҫq iwlS2i%3!y~1UK(=.VR5E67>z\gtޡ$/A[n/5q~'j=Yض%©Jşv gbv(Eť^ẏ-g|qs1F4$o@$-F :[liurs6jkY xR~e+stBઍX-JoHmnZ*|\|x1dɩRcj*|>bжesg>mcP L_ W|\VM6]vqWlV{QCECOXqf %Ζ~k{8F:X8xj&60Rycd,[b3U Vґxݵ#!7y5ی.?6-- o Sg4;IW@}$m HvԮyJ&j.Hr7g ^23P&9Qz+Ћ) ^ #w7=ƔOyt7n|, =! P~&Ez㋠.XC2ۙm]fa̻0Q{<FwdO?[p2WճքVL,α @h~:3(9|ڙ=в.:>9|9a$IB31jؿu>/z"0D%S[m$+_.+}"F%܁{>Rz&wɱ3žE1i\d Qa`A6Fج-ݢЄ̈́ 6dը8< iEZ3a^VB,;j \=j8n"Ǿv /RTdI9˩ZjR|N^~TZ-s=rQR7,t zЪ2]j+ou#mi WgC4;"nҵǣ;ȥa>riԷb&{cNW]mFo٠"i\ ǢlM4owD8w?|ˣA:0Lަ2{>ņVLWH&'tigu6>RU@JTB.}}RHaVJx1ZZK(35i".݊  yZzQSώZAemA!GbU'/wwU̩MQ<ޤ9+).\kN a B.DX& y.9"ʇȡKL[;/4 #S#U2_&U<xeQ5a xxqЌ*A6ø3j&w*Kc9_"z&tuh:vTSkśq@d/ƨ[ËKv1YP5uT3ȹӱi=cXw]kusxFyªԊVhaH6, QCm& }M$6@2"rHWeqYʖLV%-E9Dָ %a}ykP-@h" H(8afPkKCЏ j;? Mi2b, (e<¥^ټaShP NƳQ#XX"k9p2b>K>ׯD-Jq+'!P/NJ(I}z()"ї[O:-blsC{ubq _P!rR5~gW[l@Ӝ9& f9@rɉ_-Tyh"P7JJaCrZm%uۭ;0|v&q6@Ewy)J7L= >] MQ>Y2 ErqHF"v,=5R*O`da:HN>bgU?plc鹏7_%CKvM5lf>>1W-a^FD[$ Y3m{LAJ˰GU.O%mPVRB3#87G&"$C޿U]ǣd$1>9nYbr[x[KH31SIukaPzAx`i̎F}$\ TZyube_.PKqB69/p^<|:,m( >g": m*JTqR0鞓燥:8W)HM! Ȕ\A{C!wy\uR :[w16)\APɌbC'E+)1yQg9#S_a°GGuMQy_#<23^3?d#%Vïs0j8cPV[1Q]PQIJ%[f>ދ`. \,űr83z^< @ F5orp/x` $fcXH,[4s^D^@7EUY4g"ā,r)XŁ?_$\cwqG %NzgT 4 %_z9?&aDrUAL%WLvUV 'Mv Cb)⩸:_m?0z4m2n 7-+-N+S@ruqUS*:a2HPZD^j3d}ǨFe+C-G!*U7 A[9om{2A^`}-F}DD&6Z$zJXWVxĐbC3J''MAKv$W{&X^#5ֲmXN鳔9) ]o ;"_,b=) 2-d7:PcO'nUFK{y(n]}b.ܿ MtpmAgv$&a\LAX0ia`C X)Zm$ېL 5$lh Ф\c=kWH_.ƚ^l9 ,礸֓RA{5>SgLsu6Z%`GI]^ 93V$%,ѧټnE9\|]U+X[V(qx78%ei?nYԨ <ْ*}^m8!hEFEĨ1UpdG-!,^ %'$z|l#GP/b]fpI&dԁೃܯV)FG U'G^i0| `u04>OڰE:'F ER O1K=+(6l} 7vȓRP}91 X$[P\g}JHg;ˡC>O7زepe(%dff>+-`8 , l﯈ ̳m *`6Tu۞dאr<OGJSW2J ςNBT˴k~5V9@A0UЯV$0Z<&M]CXͽeݬߨ^;Ug|RF~fըQrLJڝ1eLIqV傮qBoPCDl(2І ܰ% #io.6jlم4W&KJd:rc|OhX(~n?BW^{D )Y#(10St>#2QoAv$庮sde8]{CV", r{^@_[՞7SsbvQ*A~fʔoRɻ"jW0|~d}ĸ "d%謩~z/O) ΝDYͲbXƌ r5@>S9hb=jaҭ8׸DiĽ*&H}' E .ͥ^#%6 j jEu$Z4h#O;=xti=.*^aItdc~}vǬ>o1`*~u8C.lK)Hq1t tnδYLK2x&72gJR3P̨ERø++wf@qMp,CFO`R/t S /" }ʊG`Z?{"Q&*'H~ML npO8Rmo:tH[ >&Nl36ݧ1>/hh {t*PtD@T4eXٍheBت*_Z1̾ s_ߚsr~ `׹) |0*&͑+#ka[Kɼ% X9u.M/ُ *[) ¥;9߻nkܬ8OGl=JP:Vʪ9!6yE{Q^`s+8~o!#\J?scm-b\5r@Jz^_^_zOi!NrmɟJ }D3iHݏ*uBIBTgo|z+GvKVNy:U]WrL}vK.ГJ%ybR>רU}n (R[ЋA<29 6''9977QuF U=Yj0m\xj7|}"kJ'1?y W]f0>@u"EEf5/"U}.GE ЕKƪwFg27c>nߩIМ&B'Qz^ ʒ!M9P&Sꥁruib,\Gz.?.Gm4Z ӆR m "D΍YN)Uy{tZ[=w;¯MߩV?Wt?w?WUT꣜d/갿V~?WVUf] 簿SGh<}^QB4_4"}c?3;PN]-82ϊЃGN:N+ݢj>CaŤ|7āqg!ET $%n|ԁ_o2;=>կEBFj:rykEXUW69 ܽ[8oTw<CǤ+AZTOG2zeȠLq~t*-'`'[DNYKI^êTyT(*]Jщݛ,TWr=)!TLFqYխ%BE90YA,-cJ$&;(tشU2 YJ:G JU"_39@7YZ2i"(i ->gbބ{\dMj@ٖ180(?K/M7nʦT\1/k5$\|TvӛW_;P*BO`GKMtdRGo.&ّiqw<*R) 4?di)$Q)R%OH5M%Yr]Lc% Ze!*-f|c0y䠰(-pe;{ 5֫C3\xBN@P0wqhх2ŭ*i_]?U2짣4 Qj22^ ǝ}b#!E[lh6`H )yf b{5q@ka0̼7"A ߁/m`5brg4 g Ձ_ȪJ2{d}S9%*v}o XT<yMZ&A#v%ݤA'T[LՃTLkϹvHyHz-GèƋ}jꞖSl޺aTæ!9'1Tnn<dyynܫ?]z9_50䡾DQa }5K\.G` o,{ORD+c6ߟV4lfkIg_[~H{E({ 1O{G|3r,\e8S]LN;N2ͮL(HPk:tL&(fH;NC @Cr̻odn*EC/x^3ODS}c!_رWR(\R/fFAM-nգ}j7AwR Rh6d˙v*"3$NMv;+zdi3X'  J=Q1z'l-|vUo $dn*}.2O:ZŵP/jJO'|f /\H 5 Ō7 ZBKF+`bI VP`g'=;W*'fe~ lF7F>2Cn:Z&x-L9cL/?r`nΥϺ1 <}4]LTx8lԶ !osm*-[ RҬl~YlYF,AsHdhm*IfWgABW{g2<3rrkd)dz:5+C{w a w0K68:z07s(ytZmil-FNz4D kF& d|6/=ičǗ*ܚGVct֩!s>~S& w&x6?U >̟H;pcOmYYt#?ND:_vCʮS#t4;qk @\[: ~ֺ(yW{FksFBؕ ,,0V [1 X*|A6hD9W# !k^f"4>p(1ٖ=qTyBNmf\GI:,U"BJ,_;t}'dyZ "VBgsɥOF|<>Z) Uw:H$X"If+:}сkb˽^΀ѩ@ojn ?ؘzQ`D,ɗ+:|Mid 6i1kлcľbn=B$Lorݪܸ nEb{p=HɌtBѼ3{m6(m 3Ip3Fq~e6e Br}Z\8bnEg6p ."\n*,)Al hX\b~G=gTC8.`P4;ٯ?gm X4R U3f2EV%}h7wf 2.B3˪6@u\ Z*sX~P|s"l jm49#PE36Em6JS/Ei=eVCUb MX֌Q&'ֲJV[)?5T7ݛIJ |P`b6b$bpvZX}ГaohϘ cRqs qݪdϓnn]b3shz|$sݮN?r#ۅJLo-fY<'"m7HP>hE! zǎ-NEWB}Wd5ߡ¤[Z MτI RN3wx.^ ۂ_䖰sS9u ^>wfy|GF2#5.\c9xO٦uX/u})7K#^k#+1|+N?b1byO ":ym ],yb+\akpBBQOJ4!7J0WQvz(&52-j"pN8mL>O[UQ]{1rjJ!us찷s?4WtJ8@2_6! j߲gS+g=̮nwօ0/AHBc:|]TLdẏUVou%$7mvξ%v=E]1sc!ӕ%v,eRu[ 7h]&W((}^_A~|Iy{hgdY^Y9M#ᒭMs`D[uTi2%ٯ+f]a:|Y,u$1$KQERJ\ߒYUhq' _Vj(~NCEuN\53#.]t)ԑ- W/KWQD2i ڰRqqk8]:W}EFԘ= :5=zX֙LEФ7[J$)*wG[uϔ_S p$U-G<;.9ݓc 35mC}F,pO;RQV5Ibm }wE;^f1֔W3> _wA?zɲM|ibVQc7pu8HZ%p]TG#TB:-A],w*2A0zoan!aQVh<:İ}OC('tڪ<۔:* s0YH\%`"Z, xbqzGح,1(75T"]ndog@՚}h'xUG|\/;Q1jPCX04+>#/I-G<ۍtf6EBMYureYn.C2븅g kH$6T_ X\䛛|  hlߤj@d>GPʹKnC3G'{@dH<"i2؅߳ĵLWWba4WXCӻN\="yKM_;v򠋢qrwns]@W]H(١^ygaD[AЃGb]K ?r'ygpa,8ù.ꂘ;>) #} v&D$+@Vtu^1 QcW" @|*7Jeon(FâyUVky9} ?m @Z IR܄()P(xNOFHaSQCBK+9Ig.#@ծsuw[2.XEmS@CcF71bIQSZfؗ(m&8ŒG^:gV);kfg+h4.~g )}fj5~*HH+%#sGYaַ@2jcG YE-rvW)Έ|h;54\.B2.vahE=H٠+:U$工r^;2w]vi¡m 6 WTy.[/ޞ>.y[n;꺨||O8`@eNv;Kj,ZR!ggAlR1I57Me sZ-Q"^n`/]њnLNѺ]ƲZfe֙eN6|k: G[F"7'Jdi^npP)dDW5w =֩çp\2%=%CB# kt!Wπ d+8'\i?|'ڠp~퀍YSݖg=sٯ ^kH{̡}Z52!fIiaQ$mkᓧޘ}6ES \UbG}zXknNVRZL-/cc^gs詸Sܽ` W@Ay'9@L>JnP^U}5fWn"Yxz,[Ln$[񣏷Нb M*ruDlŔ_LF} #C;ȢdSZ7ыCin]DOƛ $OIϥtq1W :"Hog)ѿ\GMlIz1HЅCVپI%?"V-=H%r1ik!qM LSD?#\L6Ns~ 9Kk;97g(1sHs3K5wU+4TϹ7J=I#(>\DPc^w%8ZL̨_m K 92`9Ists3oHDBʼn*_9¼Y_h[ES̙݆g`6ݻ4RWmqScŽhkz0\ S E8a7n{UC?yKm +V=¸k&?`zõ*Qn8 oF za02a]oC#3-ŸaE3 nyà7IU;FTfN 8EJ%敓LEdSnKSUσχ4s,f}#AR&4ŕӧbp9("mQ&V)N֢VkOY1dE)Tc2؞|"4S{suGңg%ݺd9&~;`N(M;Ljncs>2: $APA: va+2jC=Qu-JL_0$ =Z5~dz{wR] v_VvmЧFkH:h/Bᮌv ekvYi'/9+7X8P2kxxJ&`.<ݻqo$"et ?2yu3P~Cl,5Q*3 1!o_P0됼!g uK5kseLLXYupd;{}лn/znz8ف>T (8nX.u -~0|g֯p_KctmT&Y.-;ri,dÕ^&ʊQ)ٔ"rspZr1(]RKaLxހm8K6%9݁ 5pC f&s8~8,櫁Q P1k_k],̈yA Vp݅ҋoۥ7AhY\m#Lg1(tHa 5ǢUs"A%fVR숸fS̔Ym@ `lq?yt0P0E6Y !Ab3[1+pUH3_|@&>g_2Ԋx##P}BO<ӿ*ɶA_lvoQ 't`~ef9wnT,/n̠?̪:j.wӉ(tW|z ^2ӃΔ"zY%v{̷lARDf'O #Q㘣)R}fJ2ʺQȠ`+K9ز|QbPazYIގIOȞjuJ?<Ob`5k%}=Ju6J>NZp e}Zia*~F,Prtv-iX -9px{T8./-Z_ժÈaf#P9|GNP3ku7jk+3$ C`Bɰn/n63(ea!vEp*K&KޓKkI|/).pNѓcv)(orMhۯ6 `* |[ \˗8\~7L]srV"]IhUkLo+ltU<-/6%D6>\XrٻH&ž\UmQR= v"jwr l<)l%Ȝ|j+.DnN%@d!tJD'eI$M(*97i"5puBNR)ARqD~UaO fp2}(e~CSȖ@>]h 73\BRGgwowO]Go¶6 `w0l-#K..kfk̓ y,m-ԅ9}-#ﰢ K#MN!@w/9 vA^[aAGmX>IˆfXПer5Ù I"8ړrv2*H m/+3zEvm w 3X<9-Uq9b_FRI,:C91cP"=O۪a%z' 0xT$'fj^UE6/|zV Q`Ej͟}kI1V.%&e<3mO0Ѩe6h0*6S 9tK\m qBBpnyK0X\aI5uLI6|!O|tO\\l5; dN㥿%lŋZV{nti7`V}g`\U '/蜹_⣐/[f8碟ApJڃE9Xa\{$Dow-h3/|+ Ip@wu>ٯuW1M#2_*F8Qb^FW2Y`PUjm4mvKn^78wB&, P SU3xOU葂RRkw5!0Ýwi9gOjQLGϞSaVؾ1Td#S7+ĉDx<8pg)&i 0ҌQFOޓnfplN/6_V\(0ޏ`7ʼn3îz|;KGF1u;Hg7Ӱ0Ȉ&0T!zx[ ) 䚕Ny(  OlTSlBXl8p IWAz5=&nLQpN{jhlM\r"o-ءfw&(*%h ,^[)d7|,w'<m1M%ǕAZ ?|LwݷG1kITF̓yV+_maߝ:27*jԘ^ j٫BrĜu쉛,jBYU\h}*^pQےLj&>Y"%dnx^ht 7Zt,Ff쓩Iv&Rg' IhEsj>U=Q3Dk/T[I@ 5H5]?țٺHL*:928\dItH// ldFX-)0r#% G4!5= 5'.NHZWD3` %f`>:p/>ɪ bt,AZ]}-ZQH-/FPhCʔ=Yh q0,g{$ZiScelzh+v5k`@ < CM7È_=>Q՞* ,)`EnC]IPRP|؃'u3 tT d|.L9g#Oa@p?@èC @ڟmRtwM 0aAF\T]]\T 67~ia$z>peۿe3+ILiAŎGw3prz+am1Z,3 )5~^XtiFC&7)E̺`.Zbn1ihR۱ h(P 4?59[/!Ec3x?WS:Pw)S`RgH:߀& 0Oxᄢ 92hf<L6!ZnPcW`m1[v SD a?])ڶn0mxXMҁd۾ThG)o 1[l6[ܘ2t}4.wV 攲T.Y['1Fh(,2<ƶSM?T?İkiFINq;\MS?NkuΚ&n~;Q{,2(y ![%,NAA\ոeOI8$($_q\$?yl]9t}+]iRA7:liLvαvMCtP>_[> @CկЪ#lKBS5+F$b"0]&Eij fQxD_YGP+f<ӱGM{>E]+xFyrACz&zD 㻒_#rX^ :S^aQ͸g皷-a#GJqu+|&4cLx4̮:,+2/kEC00SE->}D8[[V2N#PQrWBB6ŀyd\ڧO lgz^7ym󶿾$BjL554؉Py!֤>1]{[):f8ХMM O]aCe4ߔuP*VBXyL'&dLq@a ih<y9E #8r~>mςĴ;@0|iW6OTfȿ3k=fJOX]Зj.S3Qq}q ef7s_~mxvC"GU] b #k(nLBeRԮ|lVe$*7,vd%ZK)ɉs5aVLP/pmnnSFR3-| 0[/dʚLOݿ1Be@QTgVC;v7Jv!#OT k5%b`$ÛcSח)zT..[Z@V\vz'xtFA 8BTdF{`50*5~`P8u)dwatE/xLM GBuq_1IP@Ɍ  4o{5/? >7̝EƹxS!gJ372+Ԍxb>Z In혢"ctX 7jNW!;I%%O!Fh6!Օ"qz.*dtB \ɓ1f/0v*,EyV߰"16\\vfI3?y?~T5TWe%!€ox?ȞUi2$M7wPK "t$"tuEadEw9}f!IOZAyֿPͤkLQdDX@f&%LM"su,o)~o-τeʪ[J`X$/еY>0!QB,i:VJ,zz=.0Y5m]?-B:6=T1eA6u_iJrM[<8樳C ͑yge1IՉd8Y֭%ןf>Ar 4)qO6N/AyRBjyS)SA.\^ u䰀066Yx).k#oEn̎qI'$h'r W=pń=9 L}3VYhe4A3QxxO[%YPU< &%le[ |q/m0YRu|P ?i)]|:LɆ,dr=;9&5>ςf]l/}y7P4ɺ r%I2nE͠b\ `c\rIɰ,D;YJȫ:'OܛǂC'ͺ#U9|Cf;8YL(2yfv9n@LvEvrb;kԮ~#Cmqf/koӥbֲZRir߿TO( =iIq>%|Ij1|&/e"\śí_Gw*eQ87:ÍkYlnWҾRdv"v#\WR+53<-ZJT(2M !a /ɿ, {_ޗ):%!68rZ3%1Jx7ڽH2;N| A6** wqmޔ0tρXkQIe3gtg`{M| B<3Tމ@ӆMl}kR0uPsm&_販.2&9=~V# NJl6U*TO/lBd{,+ޝ?kC -UEr*=vIQy@|{[=-kA~X4/m ,s Kr:>jD3nV@hh #Po!dչX9d-fE@(FіUk+]RX# 7ÈHn|Mn}M,Ih*j%LW *#%袗!!b-x3`ʋkmw5:!JER"Z%znHr>n+`=5& abW[Hw[tO]=4Y%-~F#Uvq{){DJ1&vg6c {*!ɈC+14 }WxVGErz ٕeK^{jDj{zZ>_jCnֿᏤ(‡z-!_ M1xd7㡤kʠ̙WCs ﴵ;m4ZkCOahO^;Zi\UN0l,ƛO5k]c~qO74ߖXTͽ)*Js#!tp-{oc%3نA@HLDFNCcEO\7jqL s{RG}4s]Kw㐎w$wQ;V1kf*F!koZ" "S\aeYZ:ԮmhR@*F;g?WVzKw#'$uaEi@x5bA꽇 FEv_6`)հ P媉a K76F1lK]W 3;|ux!'j_hSo@ alٔvń[/*H gQ[O] |{'wJ҆~ɀwODL&4O)#䲼]ٶDÂO²v^qWIla}ӸwoMR λ‡hrRkfX̤((pPZz;CQʀv-^~541f gql˱Gm6`^ N } P\:`N{S]lD9c)GaLAvhE;Sk^j[se>"p92ϱ=5ۮ+Z|E>} AKr4¥5-xا:-NEȞwNd@\vqNj$\dMP؆fuO&JO LC+Lw s'o8t WW7$@hXݨlj"1AQSGtL}dJl…b^pSJ?]w:<&IId +z49#?̓mV(ӺXB'~ dph8Q\CqDrX;c(UQH+JEDq@cdR5 r>%㽵Zr,G{D)ȺY>VSx\nډNhi,?BbdrnFX1p9M݇R]_>U;gkt|嗢qGB}Uw]I6zm@>[֟s?WX_պm^6E%;!R#!М#}w8!ںHT?䗸|:QO}}*`^"~q:8 ?J*&Z^@jE$µeV3-%JZ$Ao4sr&qB%ҴWO)\غ4^)I\G4 jԪ+( NO,^!g"v>_@|qӯ˥ 7Ϩ'$]*f##)6v;A㧪~խgɻl!!539!/ sqo1i"" QeQ*aAu"i4ZADs#X0E'3):3Tbu)*3V]6Y%7"N~KiZC!*1)`D~Rm3o"̫eNRT9l&gc!`ٷV;;'sT֛QiWaɄou _nm0Bw V]OfI^eXZVgEcB bYn+O X3|m@#aZS,|p_8?pצ f~1_Myi)xuq?IqR}'d"|Y; 5x!e׳?\f^lZ06f),j+bI|98X+8iB7 =h?D)\K_`y|yNdE,71ڴM3^ OA'_|-^mfQJUջ6i8fGئ],up/]kxcKGX7&]}㯚X~k@oٰEcF۶zbpLҲZe䦏J-+kǔCW$#qw.-<^3=-p!DSoφ~$փ aٕ+6\*~"NJh4|s`׭5g _)d-Vڒ][Ңr!'wX PҎL캩wXϪz2RIKNp?Kȿ]Sq-;Uݘ@0T9 *RgN7겄]h5SrG&*L`к kMB29b}zCʪ(Y>ՄY>?4&W2 !T|f/-/|2&jʱU< ap]V hkrY %PXo%wCry#s'm?FIB_jRBB+)SSc[m~֜P@: e~6rNugZJ>d]6s(K0+^#y,^KIi6&6*% Oy: O A] GYOS GӒxqIS-oZy]!J@!Io*³',4ᑁ_I`!j? yزId%چ(YxrX>3,5SCyÆ<3 0 :ɇhoђy}^˦z ~J ^ T+[8s)5UHe GZ;(v&74uIԆDjw{c v ;^ ɲ(9"da-1x4C%9{,u7\-\rπ[<UZNOϠ c.HLU^t7;BWGB..(Rox`ur/jV­DKZR ^rmbl>FH+ЗH?AN1mrл9_77dMHy)ɸU$FZ}B>sw|S)knE56:$SCMT-2oLľ!XGP;$ Ç Wg*r5pR@^.Kb+wᥧg/|wv8q4ph| HOVU9޾!VsY(>.h^l k!3 ]&f֯K;+s௖A9jgA1b?쮏5 4a vu"ȚW6ޛu0{yc}$(t-!uA>U wS^'?*rO3+*-wlxq7ڕjtKܸ^RhAڽIZL3[KX? h *H[v$MdCR!+x!}NdP yUoѼUJ$2GIFªxY@Аb:h3\mUQOt 5PLM N7ba'rUKyT4PZo1B-@9o]+~4^XnL/А2<]Nۼn yЀMAxM8M^'֢]\Z Kx=/%?OBM"F0^ԗ*˗<0'*ٞң]`SÈV qWۛ/OfkzFgw<}ߦpc>TFg_Џc7HȶBFkR0E)3uO&FuJ] V?ŞP4k]iGK'm.dL^7aO9`x^ ^uT7$a&ѱb% =41W:/E+ ?&E4brS`l ZSkWo6-νN(F¼ lgȦ|9*}4P3r~-0S`n:_#rrfTbdbMbu6dn)IJzE?J^wZVL -c "S:aMݛCX4yӯ0 ^YE𕤿1oI}yB ;r8 ~ZIe>i &[|\ȨA8ވwMϝ#> ("%[RWBɾ :uݹA}[yu#<>@1Os>:ۿ(]NCū+j{6Be∟׳Gs ;xu>A* 1fj#n:ژ]m# aR(Ė, ?Y l  ,xkIz 0%#u Mҹx~-Q˿Avd}mؤ:Kf-cqۨ ܂QzE~<(Zt(HQnue/~XrЁQ]h܃U1ѿ*@j]Ցt bG?+iKSoiUx=J <&ĭ$}5{ (18<|hgI@N APSz${ǧ IgP2иSvF;t{-4x'?qԼһ)yD_]rg䮾6 = jB&Fshֆa" ݯVLy !Jp/zQ&?Iy{x{,7[Ԣ4 łO>Yc ~K7R 28&ghkQ21K,Bx-v9߸?.F8KZۜ&Gc&gqTL NVcV6FZT -Ͷ٤D~qt+ z-隺Kd>9mb>,#%|Ue/-$vP Hw,8 RAW USUf35 ?a`F!"gEst9 )`$Y.(¹DuBwgL/ Xo,LQ@pjX2UR2C6Q3T"Ɔ4aZ B5ꨖ^F pRW*YQF7W49I8f8K\c"mNȳƈU@&}-k~k13BK[uU~M\D攪2B\!77gXUa9xMvENGeL tZ:Ym86@mS4 @ qf~v6WsrL^wE`He_n,653N)62N$gZ$FR(U٫'b˲&12&<|e=}~7p5ݼ"D%:S!_XÞ4mIߔ\udG{`*^JTPKs-Ы 0-홽(q$ l(yԅe',m:X&bvŲ%5rO B+ Gw~GWs5CZX\|dnbyAd/ZXIݦ}#w@@۶wdwm_?Uƥa1Cf)TМN̖vdBH&4Mm棐_=jZEiy\p]H&n]N Ҏ;G7PGW r n] FnOYQd}gt=Pp@:r\4W?FNċՅ(#:yv:]By-X) mg.XB f?* fɪ!A#Y1T \┾k!,2Ε0g7,-CUc`kAI[GNG/WL"I4ۦ2 ]*w@=9C:oiZAw rSŒ0Yxtot* LjY7k ?K~ mߌ27tcq0/?GFZ|ՀLJT:: %0[NIk2ZItR #ӂL Hg!ۢObpMa'؋9?V`F ]|؇#~厈 aw Ӏed&mIUDZSE/gXYO` pֆT)c2(T䍯gtB3EԿme<Buk5g#^>( ^E2jN[Z^(s޹02D͟oG}5(Ji*q*P`R,7DvilYW VNnw^3,O3HCnz\I/}`]gn\G0n 81U1`$]RM>Hr&1=0?n9$EWp᜼7ϯMCgs|4q4z0늨ܵkpfZR_%MB9{ZOs , uSkLKeτ*$w?[ߔK@R r~Fռ\bV6/`M `ʩ2ܿM0c(tJ>=@h 껵H-yD&刄A]Fdu*ἣxj0\lccqI,o_}rpBȥYf˶& q^u/ P|Q9 f9^ūN\h.xb /< t9%vS&BJL&zK^~Q5@tqe~U[=Ym,(P4ǁ:+g,zlH(vlJ.] ر|xaUfp/X՟b_ܝ J>}4G̥ƨ >5N%SY6$Z hL={1g bIٞtnk1;NUw-󺿀"5_tY )Ov'ZOm6,I͘ǕFNL׼PImV%+7+dp;D>}ַ߭rZ+ OهU\g[= eb hWt,KZgZĺΥ\e[+Y*1 ~Y8XzlEǸhY)GiEPR~#H;~*9r]AaZҋk ,0B )Mܛ0 J&Fn .APv3o/e6^_F2a-UYJ >`S] G\|s9bf`H "o D9Z. yB|zvU:": Ag8FEHS~ېKZ#0NoAT˸ %gqU_6 U5#'-znc:7v`# M׬n,iBUy~tta]g1Z!0K-yntަ_38bͫ ^ՑnE- RlĽǟx_ZpCѹզ"_^=_B&kBT6Xs~e4A?SIp N:IJã+Ǖ Ú`N o9JQ&DySդMݷ6pp4'c藶o eGm˟Ȧ2E9@J8 }b2jS2\mAh˩L+TI!rc{> 9Vw f1V{?82-@Ӷ*,^ȤL<?d"Bظt0)9FH (תه\'ϑk^si1a2e,jt6K04Ԣ&cRxt8\nn?A 0v/765Y L"!#h _2TD沘x)+|/MQ~XE4}.;adoZluz9LU{8TVN|۽-)(RYju :a*6Tۿdg@e-Xw=-?c/o}?nc s~%L_䍾nE<3opT h#b@++2IA : -kcCrz# |g^ E1>̌pO+u/G[<K_ekзP2~ݯW-xxea/0:zؾ yΪOoGɪr_ͫ͆)UYktTh-Xt6#ȪArpCҪюi'=?a#$r- RbkZ`?,Rt[E5;s0S;z g'lJ?Ƈf(sS5:mʶ8.D >ICZ&Ԋ›x0H{0N kA&Zc`n##odSJ50l-bM[tu;Q%t\(Bzg;8)l]Fm)p;&:TYuh)^җ3aq| g$\ɤ[ Y/UA^w9wU=^k$- yGGNRsyΈͪ$ Mm&/Yީm{R!U1LiF,YvE@qvjukqN'g_JC? <>/p{,+y(ITQ X;șt#Hwh&ߒ$ uA@}G̹1dhxTgzB9 =[ue /YMW(V."x NѲ-byaBgoڷ)DEx ;Wv\G?Jc>-'g3lV"+@P5(.5`qQ;%!tUSrx e`Id<7Il$~)&#S%:Tߗ̈́h#|HvNgzuZ"斕6q#[͈BX3bJZ1ݟ%>k}$\9/Gh(,muGmB-W|nfN_|RLJ?2#%ޥεdgSӠ4Zg7 =&}h jRiY{rv1GJT?ep$M0D˺Hm/$a6_;wgK}I̴&=4rTVpR rח/G6ɍC4*a%jDh_\T=E04flDK79X!d2AޒюjM^0$1S ƨHIE3_0D`s`a|x5tX *ϗXY ibTBu:N9TkˬKý14;.f.܇ 0a"ƫ#MɉaV>`6}B2W'$4MtYllAH?Ӽv%c;K?X7"Y }G1O]޼Lvwf(=z1`+Ap_e#DѴcZ9 RC^džHcBj*9^h|mQ7'Ȥ6?؍@HFI&ۊ+ܙ(F*P"` 芟&[ԯ렉UJu7^AӚިx؍>Bb鱧`֫""&`B-L6賽6l.'·ޘvQ<ϫ/ 7}d] 5#F]B:+@<.˔:P{2bcRAuå\5Ù[}Zn)JuADZXPbSyЖq(wBg;$1cPi̅ͱ.h{D.~pcp# Q g@ɱ^k#&z-+/܅-R~PވJid 5N rNgz|aI7/:0@2ߔ^YTu #݃|:1 $<}re#Tw g7bOݡ%)_Sa3Pa%y7]nS2``CmEqTuybYmPuN7H</؊.(R0bԮ4:8ҿ}ȁG.Z2_Pqlꢤۅ:C!ۛ&znx/cS}2"ZhiNe_Kbmh,6tS^=2Ee<#R ɧB]v,Ex Pt}ii u:v6Aoi`#uuA^͖1:Ϸh5ߚԥii`˞/!?|Jc.wU .nū"eCTnس(x9õ\n#|`"D)H+c'e',K#i+rǽĂ3Z c v&_hx_'H7MKϲ @(Q"}hbj<9˄H-s8%4!TžfOfӥu#;]1 tBi; ׈t |) nL9;J4lw|D1 ^Sb~m&"zupC&хQ?8.ٯ\}f5-戅R *#v q'H@>+ A?_b01n)>_&/" u|`N}.hM5YhEa vyQtƒo|/A*Hbl`w7JAY 7} #Fi1Gګ#nKCvH }*91B(NCEX^ic"nNl$AfЭ3lf4`HdMB?dF.ѩ8Ku٣AKrx{\\ ^h!4IӸzn3M:/W¤uwUo_ή^qϽEsU?DE,q II V 7M4n!vq^Eѝ\nr dSّۍIGVR  /΅Sy)T&n #CS̻n'HSzYZss5ѭ~@DC֏Pc( U3BmJOt|6 KynL=Uf_vrcZ'1*w>;Y&.=^ 'Us9l>r}Hֆsƅg` pdr1 sioߠӭm& $Kep#*Q )}hEJ{KUB`͒3LGT#ڲHl5 ݔeEK.?݆*QW.`;V8&8sC'tP&> yb.2";Vhʌ};DH9-F=UK;#7(7E ʞS%Gt$# ocr pqEʅ5fU`o,}zdFC?S*<#!)~5k?NX! rxN*}P1ƫdQ:x/<A ݪE*'\jpxK֫8JnX|rXR2AkpBlYj 66])zfW8<ؖ+ӟ[tn6V _\ֲq'NC61' 2:LM6A 9Ex1H , G+t-0Va<i*)6ܧ㟑;@qs 53g{w,S 7.¿X3p3S0'nMu"e"t3?ȏ`hlvd5?DeV(EЉE` O(ġ]b'1Mr7B' ӗ~}Κ}PԷ΅R8!K̂{Z[ڏo:]RM5YYN)pXLAi<'m•u T" _򩆽.94n)i>m1Mk8=*zZ1@:p ,`ꑗ2tӵ$z^?mߙfLpL+wnb%J.R~kyhCL`*D (uE6ȗN\R&ֆYl\poū*[lTYWn9\mo( z\MylvFr65ipJ}>|oa 3Fd46c#q\nBbrV7~u!*Xe;!jNQgZs;'Mj>q5ښƤ{en36bI7s+[|Ipd\ | Q锊kSŢzϓֶ^/*gx@N/BeخL+".% emc \ `hFKEw0mֵAj}CNu 8PTEf zl .>J J0U sev%ݎ7']m( $ym lq3k>ڣ؜kc.y^Outmhl@)r#ZY;mk[6e_r.\QU&5`kOϓ8(KQ ~AFWȨS 6C_OG3MQuU%83{ +O#Z;{DEQ2nhl&5aU\fl;c2Wqi!WW*!&<ڜkqN?4A%4߇YzڭeW%7ZL:쾃yEK?~s=ip'^HH|lfHJ0d?8 9ϘGPu(9_aN1$=v,8qtqd {#|:(S :dz`qkjF}{ [ő/~->7 7oVRPՋxsXTCd5l`&yS/䵼WL7-Mf0fuLd6ԋG!|24Ӿ1-.Ij4GwnlrsAVF:rBқS>{\a T!]OvlM*b^i8HYO|]NV~v CT"8*Hq[aiUʑ^d` Dp%[CzT {""4կȸ$9;HE.$[zP[>}7Drx =d@}ox鋜G8fA+p:ք3~ײdmo $i6L_=}XXuw)a+ۉ Qk"ejPp,YzHCI*7 !X7ud s<]tmdsҾ )J3w[[(H45=R D/% &kU^B9 Ё bȍ4oY+Kx*>saI'mN'j~XsBܓe`R-JB `~lR.} ov,/)PuK8R}rK$Ox\7L:Etˬt]I 5BXPG#3e8#G70V|i4&&t V*D^g׹0p|뒧hDPq5@ EQiaiYdP2& [7yδ;h9*躸ӭ Q [N9<**)>F҈j\ܳYN=YaZ IL80tW{pID]Ǭyo'81m8~~Q5ۡ(":Q+0.T=S 9x3xˤ;IuI2.9b5X;Y8^ "$"$0VT6~UEx)tq!}0:Xt9TbTF391&a-8p୩dT7(XІ>Lf[ !<k޳Vղ^s'- a&_(S$w8aL:$%loj̡zʟ!X}jA͍ΞCֱPnC*ɖ0V#E LOFQ|R@&;ZJbc^Elj&2Wr.ܔ6A pM|jx-P$ œ0[ӝHR(y'-dO^tzm1oiHw;@mf 5"@}V\efuvrL֢]]Ȍ3k?W$zu>X5 gr텿M؀6m1B{VA[r7aPxpnZAe{1Ku-1~,uj",X+zG]GYd"~05)9 /p:2 ØolV5s_>ukϷ)%xv$&^Q5Ͳpa&9>V~g)=Jʹp 8@;kI&v=$DbrA|X˩!d3߱iz"ҵC2w.(8u .=Y\꾠/~ $w՘~ ĄP-Mz_j$nmK L`DŽcԸ5poegY˯,=@Sy3G=LS 9_LBI٨rNR,6̮8EgYt;7|D]ҾyC+=,6 0_X R:ȯ.=ѯG[;)`nLȷtTٍ(`4}?k̬*Wtsf8_7*+SڝE*'7oBp2Clnc@*vl9nr K:TRܱ,;jAɎx9C104\8mf@iw0k Y4qPu{#DՍ { CC<XQHQG4<+pXHu5~F@Ȩ>rDƏZ?ACh#Eqdj/?e7^ᤚ茭  b`q)_Ղ#g <إ˚FjP<qθ} /Jвx jIlBF^F?!"|M(?٤IrZ@V7aF&8,3]Ze1w׈0k -͑ N%_.42^\w_O:?W1Kۺ0tĭHaơr0qM/F"*.$Ӎr-k#y<59?t,CQ40RN^W[j{1+Uwb˶3fl(ǎDPg?$}e˶bڞ.p i c#;,& {lftOȽi3Y@byI`N>30 3 lhphà@G'n|.gLJܤE-Ŋy_.$L$TYyja gHҜzh|8]L%b~%żyJЯP[N7uN nx 63b:Uln oC NlXg_x(_ l7 ,İZ9k~RLfrzs,GgG)$k[&Xw$@P,0Ui,W%"&z6] ,6FՃQ˦!YY0}§=j +˕.;+(퍛"<}F5w^Az6iwF ֨ :HʇASIG qst$3{+Dttr=|SzYs2شn%#ظfţ$5lT RLTWߵ(VE૶8Y }Nfv+tvu&#v&s!( $ :E}XsFN;T7JB-ݙpp8jo5 nm|iz!fi2<T!P~_2 >G3s*daw6|`A),W36s""'aSΉ滦8մPA. LUᵄ ^-k9:NR%T8ufO4=MRESLģ(cSqTۯ%눅҂?GGP@F)t߻PTDolrIJscOroV:>r;lǹ]Wt3TՌ4-!Hrƌآ Jں7Xi8jBԱ~ r}@],?ʁDp$Jڞ\@M'YBRˏ }ڄOBz߀W !1ky[냼ީO ]oxlR9[+iA;|(w "4'Ϫ%D aA-si|~IJ4 zqdмLA}-6U@()^}r>w}7G]I4&& rP$i]`D!֭8;3߯RcOAȸ濅ő dMtOvH5XIsa:2) l4Xxa+}pHX('|0#k3/I컳!L콫dg(M gw_-ӵRx?UAm2^уM/>!-]e=,MMg{ǽ||{S ϖt?EɕBWBTu@k(eh0`Q;@|4c%ڙϟ[PZXurM /ZS{"2=={Og |v .wvՑΧTdE|R>\ R fZa[3SP+Kwǵ.A%oh0T¦??r(7'AYYtm9]QJ!0~n%2(2yMl׾(*uxXN.I}(_NvT9ͧ0:fm&dT=:9co; q}C,SZg&+ϠힸUF1W\;7MÏݚZQ󢰭=9^(M;ۮ#J=K^)pcQOnLV&ྺJhV,W0ÓyV=w1<};BuC4B4ȘCF+Kr#fx%qj)hO[jWU]=㙔7} b #ffYeHQ[8Oʱ"~Yz v/㖂Opwo?l"PC3~O'T2rkwj:z=p>RTuYqBUqB{hW1aq &tipM|#߁"#P?Y_vKℽizcؒ *92#mU.d-'^j뢌xYB?6Aǎ߇9jdh6 Ό /;s6xxTPS{EMN[{杺NrJֶH^SGxae09 /뚲S}Kڗ}m[L| /Kc ϓ'YZHEQN۴KiT) @4RXQx:vPpFu881p#Aw?Z 72?ld& i 1xoK0j9x%5jCy^: ˯4U4 Pz)'(1i=3ߑ4UI+cءӚT$b5*4 WdhxA6^3t!QR ufVEc}'A ČH`VuJ {$}"~Q_s ! (7+'wJ(þ7 ?@8R'@܈"DЬˈno'$?r X:휎d])A]4Eg09tCGV&ЛU:Iew Y}ovKhZE>j:WJտb`ZW :Ӈ8fo/F U`৩),x5kaʯx9XؽS 怿6v-j2~ ]5);8pyo>rv)!(Marr <hLL'Z*c) +8s]({((X*wz`9赃XYKa2$M+Uy`-|;x *m|]J/Khx )_ Աf;5"1Lco8k;m1 $t־/~y0$_CE}bg֚_vkeeXK3˂qeTe:MͷmC10S_*r20U)(o{kN dkh{QG+=G/8]L%y{=iLm6*%ۨ} MSA~E )SH=ϼ;`68SNϼm`)?_fyII[ث25.n2qBVUPM&[ibt.T? Xh8uR6gץкLC?5ic12"u jP ftypjp2 jp2  jp2hihdr@@colrappl mntrRGB XYZ   acspAPPLappl-appl descodscmxlcprt8wtptrXYZ0gXYZDbXYZXrTRClchad|,bTRClgTRCldescGeneric RGB ProfileGeneric RGB Profilemluc skSK(xhrHR(caES$ptBR&ukUA*frFU(Vaeobecn RGB profilGeneri ki RGB profilPerfil RGB genricPerfil RGB Genrico030;L=89 ?@>D09; RGBProfil gnrique RVBu( RGB r_icϏProfilo RGB genericoGenerisk RGB-profil| RGB \ |Obecn RGB profil RGB Allgemeines RGB-Profilltalnos RGB profilfn RGB cϏeNN, RGB 000000Profil RGB generic  RGBPerfil RGB genricoAlgemeen RGB-profielB#D%L RGB 1H'DGenel RGB ProfiliYleinen RGB-profiiliUniwersalny profil RGB1I89 ?@>D8;L RGBEDA *91JA RGB 'D9'EGeneric RGB ProfileGenerel RGB-beskrivelsetextCopyright 2007 Apple Inc., all rights reserved.XYZ RXYZ tM=XYZ Zus4XYZ (6curvsf32 B&l"cdefres resdoojp2cOQ2@@@@R \ PXX`XX`XX`XXXPPXdKakadu-v5.2.1  Z])zS( N{ljr&S WJspC#{mBC >Y Vc?0` z(zl L2N?1`bшxm{xN:-zMzuN$N pIeȮ {7&r#LUOp0x7] 9 +4LE9|r_ AF͙$vrnR(b # 3nJMaߡLMᣍ<E14οR?8~#G/*A|#bC9uM+rX 鞻N~HKq~"~g8gn*EmXA FiNHTԷjbe!tRcz̓05)ݔ-ۮ|RQ>n_];QsQ$g'r̅ ,5ybߖYYмuNUĥȅ#x[hKA_'16hB{2 'eJ7]fbxw?$tBky\ " :{@FʴZ_~al$3(Yce<ʬ|X]N薉RlnTaA9T  ꘑjzڷtu, O? 1tjݭfJiF'T->fffQ!zR< q_]^\ıHpYYHՇ<"(,wLHtYjےuw5g|n`ةh1>$s2/LxI .LN+Ů8ڒwu6e!d] Y=a¢{p'&yXJMˠFI֑"R[|dQtfŐ Fd>pF,95h+,{yN,t䓲3{I/[t|z<66x;0*0G"HNIz'VK\%ʖۆ@/Լk[sCʍĴ5UL (,ye^;:A.~$ޥ4=ʆKr4{IU1WuLe|>R>DpڟSyV4ej2i+!c:v(A;۶_msFhNTko2rZZv9ٽ ntA,i+lEKX@p]f&}%֓LeQDIQ)d lJz_JXS?[ﭙw*/@i$:1]*[,o`X_3pXI}k?_ _i<֕sf./T[4;'m$WړjaeQTDt-$f/mH^Le$m $vz>S#y듅v<^Dtn _K&'[?>~ܤw)ތy8W 9B:*.C)jbۏj$iwbawyχD@mM,~E24`×z H;-kq >LΡګp +z,';Z/,]9 UlNe-[(DRN)*D SV@PU k;&k*H+|T#AV!^B̪VƙqcF{Cg9 ژ-KܩAG:S|X`W'Zi8xzBnj0Zmc:v胭7 "w zU?! 'Ϡ^+g(z,/ :-سFp$ )z` l=ɾ<>x̦pxŮk N. <q"h `oS@'+ES8hʫ@s1DGFFbX (8 @y[)Z/nn Z9\&bQCr oDZԇ3{3,&¯ n1SszXF\.@ľ ]\Q1S~ʵ~xzvNaq$ၝ]ˮqw3 QW8>Z`lA w`n0^ؕZo1V*c]X U }~"5ܽ?T F!Jʒ(/w?JZ|ךETD,gk[?5 Î$0>K*`Wo4>0 JB|@[VWKP&ǩ/ 7|yn5w@C 8;O|Z`" Z.C=;M+O1W+KUZy ? Q-0eUNL֗n1=UЫ/d?l,bJ1hɒWѺFs{-:=V1]OW wKŜ J >2IܜM'~TTb;|*͒;߈@&H@ gj*'DϧoNUS*ܶ0-k@dOo*}a]HܟQ. ;gJǂSUPI@gWkn5 [PBoFUZ5x5RN?QX OGwXoڨʺ?GbȢv[;'0/oVITor14\;N!MDWNJ\qp٬+FmU//<2ՠsz? \f _*:5b궋\\?;?o/++K Knڙ-Fف@{l Jt\̓C'(B\$zf$yvfym.Bl{D\8}`nJ5Qv֑|U*U{AJ=Љ_wjIfm=Κ1x鿵e>~!X SdVQMMPb9bm[dB =-af:[MBS~$yk.vEgD(#CN!_ruK%}*,M:{R.OvNO|:D=s$$ ~R 2<]Q7_M )1PIsV/d4d) Op(tZ=o~%i0sS$nKꡫ\,֛ 2W ,[#"Bַ3ȴE"g,J(PY$_)Xx |wwB[w}2g<|hȟ|!eO$+Ϋi[;2lvԙ=ڍUP$q7KJ ׈g' FLa,A_.PVpI9sOY5JN+`0<{F=荐)D;ڋ;[̭ GT.NEG?&86ωÿy-CFt#vEzє}6IirCFmE "~:4Ѐ֡Q|CE 8OpuX!x!ٽ?AtjiQ*Zo1d#m <\xbpF$@)9c%bS8RrƒhE6i-CjL@.kV&3 3~usk:;xHOZ@AJ|t;X? \F!yBt6b 5s F2uYĬƟ[%N{2BpGT$@»~?=xDGB^ xG۠o[ B-tf[ bb9cnM5E UЍBrZYݼzٙ|7~+m%ׯ <PWVSqiOM< C<Ixh;ϴdթ'hښ,/lkaG e vQݵs_4xJ^HfTx"R"sr8iwf%D_. 92Z]XM:q7衫,U0qT~-ғ =ЧDX `DRF:Ŕ概,[upt,̦'hbW\"B]ǿFRXw澡?so;\aN}H?(bH1eiIo>g/h̏E'~XWG @J3;=n3JOCVڐDҹ{}C;W秏ٽ/M9 X 7d݁µGQxU]gCSxBb D!}['I0ˤ$l(ڔc0 xF[i/9?ڐ!һ KiNSm`ADްi],t+Ը6FMQ r (*>IF~92*{ auV6OwU:_BjRe̮_yX*PW+b,fjeב &g9rl2_o%{1ev!H07pBo.Q>Q PGyM'k+q $==ZS n6ZMڴ0HWZ* Mi AEC]Ă\/Jݪ3Q_K58B,uu>q'ƅU{No􈺷,KJ;N *UEv9h&=}? [ %j]c:`_(s83rplcFm[M4SplH7cvP'b$71̗B#aM*XH_ҨțWFrη k2.c8H=#CBNiKrdyP0ͩ{EMVaeobecn RGB profilGeneri ki RGB profilPerfil RGB genricPerfil RGB Genrico030;L=89 ?@>D09; RGBProfil gnrique RVBu( RGB r_icϏProfilo RGB genericoGenerisk RGB-profil| RGB \ |Obecn RGB profil RGB Allgemeines RGB-Profilltalnos RGB profilfn RGB cϏeNN, RGB 000000Profil RGB generic  RGBPerfil RGB genricoAlgemeen RGB-profielB#D%L RGB 1H'DGenel RGB ProfiliYleinen RGB-profiiliUniwersalny profil RGB1I89 ?@>D8;L RGBEDA *91JA RGB 'D9'EGeneric RGB ProfileGenerel RGB-beskrivelsetextCopyright 2007 Apple Inc., all rights reserved.XYZ RXYZ tM=XYZ Zus4XYZ (6curvsf32 B&l"cdefjp2cOQ2R \ PXX`XX`XX`XXXPPXdKakadu-v5.2.1 DL,v$ Pu!U .dP(R;G|iD9UZsSLcFncmW! %H#7fq5ScR?8~#*_૎(;cG|M=>w(.&vFCet1r:f:kdBDs~h Q'^Cr. o$ө䖩C󡝕%U_=O mR:6ڏ):kOb/tRj"') +Uc JDvN]dp` TW#`>̊YSᄇdjYqHm( ']7ˀhB>RڪAVxtO`}{včfO⟝\d?#3Lo.4Pácc&"L=ULؒ`Kc(;"'T:clC&)+ZH8рnqg^&3qmk/ ӕaeeh7)}3 #CZuHgL9"GjrA %RkY,(Ty?\TbGÒ]@WcQ$|x7Yxri3߹¬ +VbN>Īy"To0WI`Hlj_PlJfT Q"3ڲ+CmNă@=jhq:iYڨݱHE4Q1o<9bV'kA$-A[jm3^νhlh ;q1y#C}h.܌kA]b; Zcsy3Pmnkсj:)EܞU|'"OǏ<7=nBy'u@H*-I3j B\ȹksѩkm`c/0ėT؈ rV)"O6#r59ʞmw Gym8`sM:U2O ʜiZ|sEaGKfHQEt,v r~D< ;unmoF, ޵&-,wǢŸ̯wfv+3_i}r0Gij%fBzt 9(͕ʷdSۯ4 ?Oa*Fasi&1_7~(P{bzL^__? dCτV-y0:Fm*d%AK CX(2p3Wy4'%CJb0q$6V}`*\GTbAX՞m0lhEk <C9^$S*^I!_Q MRTZKt1̗&I ]_][Ù9 baGT*W|C:q k!X2I`~0gZSu]RM7If``ԁXRC4xߥ⌴bcÂmzޛSNbmXEb yuHb(/9`P/03UH{ޚG.sFysw ʨWB478×0Ko$WuEQ7m+0۠U#%iKѵbYdLQw=ZjWwʎ 9s$-Ǡ86ҙhpqZz*'7FƑ$Y W:x~[eP6LD5žQ1JͶ>16JQ%P`ghV0%?,ߚjNԑ/{d'9h)1 G ƦGu{6]rB?wh}YG pַ;bDuL oKAkuHY5 ̺ԪDds>Yyq|Ava qDvrBHtrw ͤ'O7"" yQ* #eӒ qc}Η/ο'LՂ25nLZyoKre@D-9Ĺk(64sUB ^TfbzAg7l Ԓn~Ggn-/UcZZ5b@8(̺%[ay5|d-1y3?|\dYDDyMk[wcq%BO.B()gkd>+tj\}DXTjkfDK۳[vv&"IRe2|Y>AZ8˧PfgYָ$tP%Y`Ε NU@JBJ7÷菛aÔc2/Pnj<=ruӌ/6 uo6dG] */R@42D D`ti~ِbRA{V+`.1 ֐d G,VL)-*XRQ4jno+){$`U;;jHFwL4k?ޞU\RbݢUV6;r|37W,6i Fv7Qz8"WD}Q5[_3{bl!#a HqlX,1 ϙgZ (Ѓ1w&uJ(jڣM*Q;i}g>.*^P:sAx"TC(?'7^K~M$ߙ:Lty&S'Z̄_ G9"EԻ>.T>D΍ذoRZ Gş* !G'{ ,ebhBھV#tv@Џ+[pF5`D=v'z uY1gz[ >W-b(8>&}NiMVRI!uwZ|<ɉ%/n;Ȼ0^8xW/PƩSg>t#{m6;(F_-<5Q2-3lXWa5qZ8U-{m`TjфB- 8*7FO0*X=$C~-B=a">. haVQ>(hZy6^]U.IpZ?cB^1`7"]#<A`C0e`St /}CךV:@8+Q ˬ7n~E=~Sfx;!`d9T3;8&Jܞ_\<(x8#m~cS߬PN{ T^Ư~ R?>g̾QX~CJ@+~F!I3i0 M v7o{Oq4tNX<rM#MCfq2L=;iJ dQ6ST B~f_HX62!b0'_KͯݵZ%#29G_>]/#)}EhxLYx\F^5ĽQ^h36XA0h3!I8+8J|6ֽeT(х^YG>e=.?K _uĊ[K"s0tWKBnAA}K:Ԙɱ͢b̳f]^`ךig}8A]+*쌰SHc$oGY˔NvN_zD\ O]PG6qb0n#PmBE7BV9bk$}M+@!F% w(bmC[Ęu-eo1>̛p)Sf.,4D{ F".Td(&\紦Sص& (8oN=5J1'xe}c.=r 0>_D7xyܙ2ިyZhOy-Ll9J֐k+v5š;OM7/x{o ? o;6q~ KC[uAjVJ9ő6/2l=h+$x٫YMރhdc+uJ'gdw0>_Ǡd<]IcU$KIiĢ.!Fn䇑OCNH~<0m/{r֍[u'lЮʐ͆U7gBZfq~lm-ɹ P6Nth:N+_[?Os1 f٭I9*V{wLѧ0i /֨|mul{ŚA<&ѯ^ީ584i,S\fLhDcy o,*f/f1.j@t} 4𫇼+>(8|zM|S!It[_׃6'#msJT'G3/&֏ &C!:B#UCتJYklaW ah9Y VUeץ1=!Xh8[đxVVz{;{#士[V ϢO ^"$8Q^5A3WLoR?XmsJ;s껔lGg&UdVf/j)~e{w %R!Nn>ڟPxMP-j&s-uwsox7ټ34eH9*P0?CYA%QdWi;.ԡ2;ug,!Ŧg9⤏&tD Us R|1}_xD\8ː. ߃C•i%Ud^pT;^]7Qt1-P3 %lBM0= 5mpHM=ȜSk yf#&BwKڧ 널Q T6 : )3FkP#e#HٱW?ݯO+7J/mbaCԈoF ybTMQ /e9QL ҅꣍Hs$7^~qĦLJ7X8p뫾7%띅M;INQ|#[rc_]0Xn\Nx*:W5gK"b[c2mVq mqsm>|d%'Ǘ(g-+Nksd5rAJ̿5~Iz 3(Wr#2! U;0ЫF򀷙1+RN P3 /{Rjuu$Pz %>d1.Nh)^RҮ{IbxXLfU:u'v r2:).8RopyAQ͊ , O rQu$~Z{ea 7h |4Siw9:J9[S׬m8] i<3;h1#Ɲ%Uk)x6Zfh D.\PUIkf}\~þoNŷݗqחнsr8}O/M3%7OWsۈt)Igo3 Tnu , !Ph)rDtH5k'`b zeaDhF:\sݏ\,׊ U ^+tu`9~V'ԃdH&OvX¦k*o/RVĬ842ᦎw,TRlDsܗmXW:΍{l_"41kK\ZqGq,i')ƼXz6qlc&BfB4)Ax U:T|G~ڬ9Vtv:pF#5$#[|aԒo\9"h-Bz!wV"H?&~6׷jPlŞ˵ RL@? R*d5w2Mn\}ٕ-672NeבDvrW*z;H| 2s f+&)VQOZ}@0Sg8VrpiY+HF uw$P ՀJw P߱&&Zݍ$}O9&K>:B |0H%FabʊkdcSЗz8|P2:)/'*?h¼?\OE [$ZH|w6I6NQ'rQgM䠂Y4㬈phfj!/*23fDg\n'mlR&29axR5Jlhg{YkNR0J>jSsבM9d6ݴCۨV @QTo'N~b]2g^)ЖLdquʻF[܍4/`ea p4iů"!?ܨ;`;E;ŇՌ]wxqs?2oi>[= o}7*^^EDW!@5A155W.|#7nD /r'e/K%~>7sʌ&J\xVMQ\N ػe,0ae03!K2/VԤ>qnK5XouVCPC9~OJmU ΪQXxe7k4 3`Y>#xҪemR^)7!OWBjCrRyTq=1qR^d=:'hޣ9~ o(_{< rOd=R-:vkHxQ/?ʳ=7jAh)e$*nr@{Vyvn,G"Ytxh߱, j~QDGɣYضfoR f-kp@@ڛ*a黗x !F'.a+1R h1,![6!حaL`$|}D01_@5-UZۈ=H\ qm,o7/ρ[/U UYwgFh1Dkrʠ=pWXa?#PTcJúK e]д[(=7MF~7^l9mp~ڼO_T'xlsxXxE^Th4S6y#`y悳bȹYݱJOu*8*U _IW_᧺!NJH?U tr63ݔc ÛcR(h5kӸr-E iB% ZO0t5즦{sΗ(kdާDŽџ紐|⭌]t 3FLfU6$h/n"PgٟF@WIF\T9KȜwQVe)W=ufZtgPP; LlxZ[ P-A%\F.NP| gR w$gҟ8EEN҄Su%cB_°X.=&e!CysGZ_3` (Sdl[lNs29hPb<,,gI*& }6+I{3w\|:[ϘMcS0-iGOU]pŒ7?J.XͳFx@5u>XgbyFSm%A-Sl0:X lYS׻2D>`dZ~ׂ}+TXo\q}["JAIē#kleDe٦Uovѩ4yRpz>|}AxS|2,-R2']t<1$tٞDr1It>X8z6*Km= *d gQ6O60WhKzF!|n!>oP#:l]J}ٸbOãqy. %EkFe]Bm[hUs3e~3L|NF$n%@oAVl;y&3ә=ݖ"A'`Bhz [\vAeΦMB{{Px9DVf0c ⤹Ca=92XB*D=aPxK'tV F<,Y>1V:nK\4G0'pZdkϿB\$4(, P:x$FhS?aب&&$D:DA=̟)SޝApXF94 ͖JĴ.Cw*@FCuoE?]y\".&}# <Hu#oƹkL\E!5֌{.3k].[y~$%h5/Pĸ&Ǜu@ֺkdf/tm_>S: >Jo(5p3= X{ͯ}dEes)bĸ[r$4 бш^ Y:Y·Xi 8nRJҽ"BTA2׏Bр_r=GF nS#C_c>ڥ.yw*/]ZwԳgM`ɹKE=Iܭ\LYEOADIAN)TJPD6t.퓷1"z"TJxw8 kJӹG.i; .'(@B1C׷0oN> zn wΟ@ݗ{IAPh_s‹2]Q6`N(ccX#/X75u/0v >UvmRƗ$Ldv+{ґrbf3tm;\B_cT]Ɠk0›HB X®4$CX.𚉬q2yeGSC4T?m 6XΦ6߯#V.u0n+aS1R~Y&SެNG  +(7Xg_~*j246mET+ףBJx 7V@neB~$m i{[֔>_4DF@,Օ LmZ^I!>(8$wGNa֤Ѽ}C/0^&4ܴS,ƨ.=Ր3 \ iIh$#yoEؙi+=grMU"bQp,MyNX!u2 ƦnlÊ"û˔\(x:/_cPkpD|?' ފ#.6a"ʦ}%>xC6G1oE}bB3˪z픝~a')F3-V ;pdq2;I(YH.:n-ՙKeIsBSP@|&%9Q]W{m}h/mBl6r!͆g`9dTH@=HT /?t9>6d3uG7XQ7JvCvc[ܱ*u*4I]" v=Fڂ2 `縃`'!W] `zlej#,4Ɨ]7A 'toJb`|Mx.{|ƴ|<:9x}8!csSc7Z ݝdvqL!r.>˄l rB-g/ZXpmH\{"Jw?htt\ @. ʔō|P6I?Ia唀CWGE|8ŕT٦=iK L>P4 ܋.>d1CA[tC;|Fd$q b4u0TfʭԚߣM؉W}.|7Q&,ff0Lv)$%T\Ѯ،h@|!Xe9NI8{Z 6RrwOTq ҽ/溰s;v ze\w*Bi2/ػN95X΋>>G}Ӱ 5'l-2L'6HW*à eiFf#lbpJOѾu]*~JEa( tnF=%T*/ŧXq8]ʂ HtXncvicp5Ҟ g f C C qp F43F|puRRup|En^\[he#$eh\\^n5g[YX]iA $mm$ Aj]XY[f7hZ g^1nn2^gZhne\ aY^^Ya\enPfgflYooYmfgfP Fhst~wB:TV9AvstrcF!)yS:eF!W[aVjrhhpq\]ZU1}uprmssnqpu}3TzvlkuzU 0=KooK=0/[Z2VXSS]_'' W V yv8 7vy wlwZYwlw ;remp--nmeq<k[_mLLm_[kFo][Zge#$eg[[]o:tlgfkyI &{}' Hykfgjt:~}{9:{}}~~|}~)x·x) *e¬_Zy|S\e+2ϭuyyT`u3A[ض:Fv΂\Atoomxk͉_v,vvӃr#EɭG!sķt">R__R>6om;_aWWUV## ( ' Y X  aEDa%%_2! 3Xq 46 qX"KL"!"-ؒ-)dŵaZU`e*2cgfut}H|Rc348s#,qJR:_4QsDDcBHZ@VZ}Uz:OS&nxnvzvl!E˱I"~հ# HcsscIEM33l8mk76ZZoJJnnm?::?<= n1)*0n?mm? ;̙< o̙n43!"8:=>LL35EFfgLOz}]]&&ic11 jP ftypjp2 jp2  jp2hihdr colrappl mntrRGB XYZ   acspAPPLappl-appl descodscmxlcprt8wtptrXYZ0gXYZDbXYZXrTRClchad|,bTRClgTRCldescGeneric RGB ProfileGeneric RGB Profilemluc skSK(xhrHR(caES$ptBR&ukUA*frFU(Vaeobecn RGB profilGeneri ki RGB profilPerfil RGB genricPerfil RGB Genrico030;L=89 ?@>D09; RGBProfil gnrique RVBu( RGB r_icϏProfilo RGB genericoGenerisk RGB-profil| RGB \ |Obecn RGB profil RGB Allgemeines RGB-Profilltalnos RGB profilfn RGB cϏeNN, RGB 000000Profil RGB generic  RGBPerfil RGB genricoAlgemeen RGB-profielB#D%L RGB 1H'DGenel RGB ProfiliYleinen RGB-profiiliUniwersalny profil RGB1I89 ?@>D8;L RGBEDA *91JA RGB 'D9'EGeneric RGB ProfileGenerel RGB-beskrivelsetextCopyright 2007 Apple Inc., all rights reserved.XYZ RXYZ tM=XYZ Zus4XYZ (6curvsf32 B&l"cdefres resdoojp2cOQ2 R \ PXX`XX`XX`XXXPPXdKakadu-v5.2.1 : @|@ >`>@P8 AA &% kM UxjC"l9 BC69 #' V2?0` g K%?@#Ѷgq2\Ɛ&K) d5i V> `hH.8#DU N2] A, (ъ.>L #f+mM|i#KpбNp#&U6u1N za)QVyR?8~#G9<9@bCzKv.K)4QA~dC88~hSD@Zw\PtR@sw_.|)+8; -=df9 J3S.QkF3pBjH>Gt0 [AtY(+ߣꐜ 7@U^u UȧV%Vr&d1(,DO:&F kVnElpTZ]2e%:|X8 d{1wu o?AKJmSDSTDP 9OJ9mV7pPDWUZ ;=a]BnvÅ`U|Sw@qp)uv.utbWHUdewuH5Kj*o nYu%/[[7PKB<+aFvtgد&AaulDna`6=I<  A*Qby[W7E~YO6lxp}?|UҊk 9e7^ԶR>ak]= }1N\]gKU^XN 6,uo~Gw/s@:2Τny8 wG o d,ڐ Jճ>g> H6KShfԸ:'d K?$WvA)} `?AHU1ə-FX;;[ Ǫl"U81|zBe"tcǵhnZߖ-!y`(I7A/=I[Oԣ_R\.w{:MF= f2;_g Cz ʹc~A{䊙a:U# ;,֭ +\bzapn=TU72n}yHY`~xppIV}*"?0gƻepT$z Xf$Z˓]^iF)JB}^_ 3g@tK;*t̍Eq{8ǂe-$y>2qtVrS0۔9"2t2w}vp]ʫ&KE9E<$6ebf)@pxX(e3_'.c=p0BkS1m᭧Si䙃_vC]Ds$ ֲ(>jWn{4]k |{NgJzmtMMf^߳=ƀ\upܓwicp4B$  b 1 [ EtoJ_gk?-2 :jgai\p;Q[8q\g/HwyRW\PywNR|kmqqnkzW `uppootb"^b)in-HL9=  M 1I :}a^|>dlqB07 ?pkeGboB/dut}qm~+Ԭ2Ӓ15IO49,% 1}b*$kiIS ^]T/k|yvip!x{&8=hrLRs8mk2*(0qibq*! +TeQ^18/3C[ &*oolite-1.82/Schemata/000077500000000000000000000000001256642440500144745ustar00rootroot00000000000000oolite-1.82/Schemata/README.txt000066400000000000000000000003141256642440500161700ustar00rootroot00000000000000Schemata ======= This folder contains property list schemata for the various plists used in Oolite. For more information, see src/Core/OXPVerifier/OOPListSchemaVerifier.h and tools/plistSchemaVerifier. oolite-1.82/Schemata/demoshipsSchema.plist000066400000000000000000000001701256642440500206630ustar00rootroot00000000000000/* Schema for demoships.plist. */ { type = array; valueType = { type = delegatedType; baseType = string; }; } oolite-1.82/Schemata/hudSchema.plist000066400000000000000000000053531256642440500174600ustar00rootroot00000000000000/* Schema for hud.plist. */ { type = dictionary; schema = { "dials" = { type = array; valueType = { type = dictionary; schema = { // Not all values apply to all types of dial. "alpha" = $floatZeroToTwo; "equipment_required" = $equipmentKey; "selector" = $oneParamSelector; "x" = integer; "y" = integer; "width" = positiveFloat; "height" = positiveFloat; "rgb_color" = $simpleRGBColor; "color" = $colourSpecifier; // Alternative to rgb_color as of 1.70 "draw_surround" = boolean; "labelled" = boolean; "spacing" = positiveInteger; "n_bars" = positiveInteger; // These are for use with the resetGuis: selector. "message_gui" = $guiParameters; "comm_log_gui" = $commGuiParameters; }; allowOthers = NO; requiredKeys = ( "selector" ); }; }; legends = { type = array; valueType = { type = dictionary; schema = { "text" = string; "x" = integer; "y" = integer; "width" = positiveFloat; "height" = positiveFloat; "image" = $imageFileName; }; allowOthers = NO; }; allowOthers = NO; }; }; allowOthers = NO; $definitions = { $equipmentKey = { type = string; requiredPrefix = "EQ_"; }; $oneParamSelector = { type = delegatedType; key = oneParamSelector; baseType = { type = string; requiredSuffix = ":"; }; }; $floatZeroToOne = { type = positiveFloat; maximum = 1; }; $floatZeroToTwo = { // Alpha can reasonably be two for aegis, since it's scaled by 0.5. // Why the inconsistency? Kaks 20101029 type = positiveFloat; maximum = 2; }; $simpleRGBColor = { type = array; valueType = $floatZeroToOne; minCount = 3; maxCount = 3; }; $colorSpecifier = { type = delegatedType; baseType = { type = oneOf; options = ( array, dictionary, string ); }; key = colorSpecifier; }; $imageFileName = { type = delegatedType; baseType = string; key = imageFileName; }; $guiParameters = { type = dictionary; schema = { "x" = integer; "y" = integer; "width" = positiveFloat; "height" = positiveFloat; "row_height" = positiveInteger; "alpha" = $floatZeroToOne; "background_rgba" = string; "title" = string; }; allowOthers = NO; }; $commGuiParameters = { type = dictionary; schema = { "automatic" = boolean; "permanent" = boolean; "x" = integer; "y" = integer; "width" = positiveFloat; "height" = positiveFloat; "row_height" = positiveInteger; "alpha" = $floatZeroToOne; "background_rgba" = string; "title" = string; }; allowOthers = NO; }; }; } oolite-1.82/Schemata/plistschema.plist000066400000000000000000000167761256642440500201060ustar00rootroot00000000000000/* plistschema.plist Schema for plist schemas. Oooh, meta. Note: for clarity, quoted strings are used only for strings that will appear in the file being checked. This may cause problems under GNUstep. */ { type = $rootTypeSpecifier; $definitions = { $typeSpecifier = { type = oneOf; options = ( $simpleTypeSpecifier, $macroInvocation, $stringTypeSpecifier, $arrayTypeSpecifier, $dictionaryTypeSpecifier, $numberTypeSpecifier, $positiveNumberTypeSpecifier, $parameterLessTypeSpecifier, $enumerationTypeSpecifier, $oneOfTypeSpecifier, $delegatedTypeSpecifier ); }; $rootTypeSpecifier = { type = oneOf; options = ( $simpleTypeSpecifier, $rootMacroInvocation, $stringTypeSpecifier, $numberTypeSpecifier, $positiveNumberTypeSpecifier, $parameterLessTypeSpecifier, $enumerationTypeSpecifier, $rootArrayTypeSpecifier, $rootDictionaryTypeSpecifier, $rootOneOfTypeSpecifier, $rootDelegatedTypeSpecifier ); }; $simpleTypeSpecifier = { // Simple types that can be referred to by name with no parameters type = enumeration; values = ( "string", "array", "dictionary", "integer", "positiveInteger", "float", "positiveFloat", "boolean", "fuzzyBoolean", "vector", "quaternion" // Not oneOf, enumeration or delegatedType ); }; $macroInvocation = { type = oneOf; options = ( $macroInvocationString, { type = dictionary; schema = { "type" = $macroInvocation; }; allowOthers = NO; requiredKeys = ( "type" ); } ); }; $macroInvocationString = { type = string; minLength = 2; requiredPrefix = "$"; }; $stringTypeSpecifier = { type = dictionary; schema = { "type" = { type = enumeration; values = ( "string" ); }; "filter" = $filterSpecifier; "requiredPrefix" = $stringOrStringArray; "requiredSuffix" = $stringOrStringArray; "requiredSubString" = $stringOrStringArray; "minLength" = positiveInteger; "maxLength" = positiveInteger; }; allowOthers = NO; requiredKeys = ( "type" ); }; $arrayTypeSpecifier = { type = dictionary; schema = { "type" = { type = enumeration; values = ( "array" ); }; "minCount" = positiveInteger; "maxCount" = positiveInteger; "valueType" = $typeSpecifier; }; allowOthers = NO; requiredKeys = ( "type" ); }; $dictionaryTypeSpecifier = { type = dictionary; schema = { "type" = { type = enumeration; values = ( "dictionary" ); }; "minCount" = positiveInteger; "maxCount" = positiveInteger; "valueType" = $typeSpecifier; "schema" = { type = dictionary; valueType = $typeSpecifier; }; "allowOthers" = boolean; "requiredKeys" = { type = array; valueType = string; }; }; allowOthers = NO; requiredKeys = ( "type" ); }; $numberTypeSpecifier = { type = dictionary; schema = { "type" = { type = enumeration; values = ( "integer", "float" ); }; "minimum" = integer; "maximum" = integer; }; allowOthers = NO; requiredKeys = ( "type" ); }; $positiveNumberTypeSpecifier = { type = dictionary; schema = { "type" = { type = enumeration; values = ( "positiveInteger", "positiveFloat" ); }; "minimum" = positiveInteger; "maximum" = positiveInteger; }; allowOthers = NO; requiredKeys = ( "type" ); }; $parameterLessTypeSpecifier = { type = dictionary; schema = { "type" = { type = enumeration; values = ( "boolean", "fuzzyBoolean", "vector", "quaternion" ); }; }; allowOthers = NO; requiredKeys = ( "type" ); }; $enumerationTypeSpecifier = { type = dictionary; schema = { "type" = { type = enumeration; values = ( "enumeration" ); }; "values" = { type = array; valueType = string; minCount = 1; }; "filter" = $filterSpecifier; }; allowOthers = NO; requiredKeys = ( "type", "values" ); }; $oneOfTypeSpecifier = { type = dictionary; schema = { "type" = { type = enumeration; values = ( "oneOf" ); }; "options" = { type = array; valueType = $typeSpecifier; minCount = 1; }; }; allowOthers = NO; requiredKeys = ( "type", "options" ); }; $delegatedTypeSpecifier = { type = dictionary; schema = { "type" = { type = enumeration; values = ( "delegatedType" ); }; "baseType" = $typeSpecifier; "key" = string; }; allowOthers = NO; requiredKeys = ( "type", "key" ); }; // "Root" variants of types which can have typeSpecifier arguments. These are the only types for which a $definitions entry is meaningful. $rootArrayTypeSpecifier = { type = dictionary; schema = { "type" = { type = enumeration; values = ( "array" ); }; "$definitions" = $definitionsDictionary; "minCount" = positiveInteger; "maxCount" = positiveInteger; "valueType" = $typeSpecifier; }; allowOthers = NO; requiredKeys = ( "type" ); }; $rootDictionaryTypeSpecifier = { type = dictionary; schema = { "type" = { type = enumeration; values = ( "dictionary") ; }; "$definitions" = $definitionsDictionary; "minCount" = positiveInteger; "maxCount" = positiveInteger; "valueType" = $typeSpecifier; "schema" = { type = dictionary; valueType = $typeSpecifier; }; "allowOthers" = boolean; "requiredKeys" = { type = array; valueType = string; }; }; allowOthers = NO; requiredKeys = ( "type" ); }; $rootOneOfTypeSpecifier = { type = dictionary; schema = { "type" = { type = enumeration; values = ( "oneOf" ); }; "$definitions" = $definitionsDictionary; "options" = { type = array; valueType = $typeSpecifier; }; }; allowOthers = NO; requiredKeys = ( "type", "options" ); }; $rootDelegatedTypeSpecifier = { type = dictionary; schema = { "type" = { type = enumeration; values = ( "delegatedType") ; }; "$definitions" = $definitionsDictionary; "baseType" = $typeSpecifier; "key" = string; }; allowOthers = NO; requiredKeys = ( "type", "key" ); }; $rootMacroInvocation = { type = oneOf; options = ( $macroInvocationString, { type = dictionary; schema = { "type" = $macroInvocation; "$definitions" = $definitionsDictionary; }; allowOthers = NO; requiredKeys = ( "type" ); } ); }; $stringOrStringArray = { type = oneOf; options = ( string, { type = array; valueType = string; } ); }; $filterSpecifier = { type = enumeration; filter = subStringToInclusive::; values = ( "lowerCase", "upperCase", "capitalized", "truncFront:", "truncBack:", "subStringTo:", "subStringFrom:", "subStringToInclusive:", "subStringFromInclusive:" ); }; $definitionsDictionary = { // NOTE: there is currently no way to apply string requirements (like requiredPrefix = "$") to dictionary keys. type = dictionary; valueType = $typeSpecifier; }; }; } oolite-1.82/Schemata/shipdataEntrySchema.plist000066400000000000000000000272221256642440500215160ustar00rootroot00000000000000/* This is a schema for a single entry in a shipdata.plist file (not the file as a whole). */ { type = "dictionary"; schema = { like_ship = "$shipRole"; is_template = "boolean"; is_external_dependency = "boolean"; max_flight_speed = "positiveFloat"; max_flight_roll = "positiveFloat"; max_flight_pitch = "positiveFloat"; max_flight_yaw = "positiveFloat"; thrust = "positiveFloat"; accuracy = "float"; max_energy = "positiveFloat"; energy_recharge_rate = "positiveFloat"; forward_weapon_type = "string"; aft_weapon_type = "string"; port_weapon_type = "string"; starboard_weapon_type = "string"; weapon_facings = "positiveInteger"; weapon_energy = "positiveFloat"; weapon_range = "positiveFloat"; fire_rate = { type = "positiveFloat"; minimum = "0.25"; }; scanner_range = "positiveFloat"; fuel_charge_rate = "positiveFloat"; missiles = "positiveInteger"; has_ecm = "fuzzyBoolean"; has_scoop = "fuzzyBoolean"; has_escape_pod = "positiveInteger"; has_energy_bomb = "fuzzyBoolean"; has_fuel_injection = "fuzzyBoolean"; has_cloaking_device = "fuzzyBoolean"; has_military_jammer = "fuzzyBoolean"; has_military_scanner_filter = "fuzzyBoolean"; fragment_chance = "fuzzyBoolean"; has_shield_booster = "fuzzyBoolean"; has_shield_enhancer = "fuzzyBoolean"; fuel = "positiveInteger"; bounty = "positiveInteger"; ai_type = "$aiFileName"; max_cargo = "positiveInteger"; likely_cargo = "positiveInteger"; extra_cargo = "positiveInteger"; cargo_carried = "$cargoCarried"; cargo_type = "$cargoType"; model = "$modelName"; materials = "$materialDict"; shaders = "$materialDict"; smooth = "boolean"; density = "positiveFloat"; name = "string"; display_name = "string"; roles = "$roles"; exhaust = { type = "array"; valueType = "$exhaustSpecifier"; }; is_hulk = "boolean"; subentities = { type = "array"; valueType = "$subEntitySpecifier"; }; frangible = "boolean"; scanner_display_color1 = "$colorSpecifier"; scanner_display_color2 = "$colorSpecifier"; laser_color = "$colorSpecifier"; scan_class = "$scanClass"; scanClass = "$scanClass"; launch_actions = "$scriptActions"; script_actions = "$scriptActions"; death_actions = "$scriptActions"; setup_actions = "$scriptActions"; escorts = { valueType = "positiveInteger"; maximum = 16; }; beacon = "string"; rotational_velocity = "quaternion"; track_contacts = "boolean"; weapon_position_forward = "vector"; weapon_position_aft = "vector"; weapon_position_port = "vector"; weapon_position_starboard = "vector"; weapon_offset_x = "float"; // NOTE: ignored after 1.65 scoop_position = "vector"; heat_insulation = "positiveFloat"; pilot = "$characterKey"; unpiloted = "fuzzyBoolean"; escort_role = "$shipRole"; escort-role = "$shipRole"; escort_ship = "$shipKey"; escort-ship = "$shipKey"; escorts = "positiveInteger"; missile_launch_position = "vector"; missile_load_time = "positiveFloat"; missile_role = "$shipRole"; escape_pod_role = "$shipRole"; // new, improved (1.77) escape_pod_model = "$shipRole"; // old, deprecated aft_eject_position = "vector"; auto_ai = "boolean"; script = "$scriptFileName"; conditions = "$scriptConditionList"; script_info = "dictionary"; is_submunition = "boolean"; is_carrier = "boolean"; isCarrier = "boolean"; no_boulders = "fuzzyBoolean"; hyperspace_motor = "boolean"; hyperspace_motor_spin_time = "positiveFloat"; throw_sparks = "boolean"; max_missiles = "positiveInteger"; cloak_passive = "boolean"; cloak_automatic = "boolean"; debris_role = "$shipRole"; has_scoop_message = "boolean"; rotating = "boolean"; station_roll = "float"; defense_ship = "$shipKey"; defense_ship_role = "$shipRole"; has_npc_traffic = "fuzzyBoolean"; has_patrol_ships = "fuzzyBoolean"; has_shipyard = "$hasShipyard"; hasShipyard = "$hasShipyard"; port_radius = "positiveFloat"; port_dimensions = "$portDimensions"; equivalent_tech_level = "integer"; max_scavengers = "positiveInteger"; max_defense_ships = "positiveInteger"; max_police = "positiveInteger"; equipment_price_factor = "positiveFloat"; requires_docking_clearance = "boolean"; tunnel_corners = "positiveInteger"; tunnel_start_angle = "float"; tunnel_aspect_ratio = "positiveFloat"; market = "string"; interstellar_undocking = "boolean"; allows_auto_docking = "boolean"; allows_fast_docking = "boolean"; counts_as_kill = "boolean"; extra_equipment = "$extraEquipmentDictionary"; hud = "$hudFileName"; view_position_forward = "vector"; view_position_aft = "vector"; view_position_port = "vector"; view_position_starboard = "vector"; custom_views = { type = "array"; valueType = "$customViewSpec"; }; }; $definitions = { // "Special" types referred to above. $materialDict = { type = "dictionary"; schema = { ambient = "$colorSpecifier"; // Deprecated but permitted alias for ambient_color ambient_color = "$colorSpecifier"; diffuse = "$colorSpecifier"; // Deprecated but permitted alias for diffuse_color diffuse_color = "$colorSpecifier"; diffuse_map = "$textureSpecifier"; emission = "$colorSpecifier"; // Deprecated but permitted alias for emission_color emission_color = "$colorSpecifier"; emission_map = "$textureSpecifier"; emission_modulate_color = "$colorSpecifier"; specular = "$colorSpecifier"; specular_color = "$colorSpecifier"; // Deprecated but permitted alias for emission_color specular_map = "$textureSpecifier"; specular_modulate_color = "$colorSpecifier"; shininess = { type = "positiveInteger"; maximum = "128"; }; fragment_shader = "$shaderFileName"; textures = { type = "array"; valueType = "$textureSpecifier"; }; uniforms = { type = "dictionary"; valueType = { type = "oneOf"; options = ( "string", { type = "dictionary"; schema = { asMatrix = "boolean"; binding = "string"; clamped = "boolean"; normalized = "boolean"; type = { type = "enumeration"; values = ( "binding", "float", "real", "int", "integer", "texture" ); }; value = "float"; }; } ); }; }; vertex_shader = "$shaderFileName"; }; }; $textureSpecifier = { type = "oneOf"; options = ( "string", { type = "dictionary"; schema = { anisotropy = "positiveFloat"; mag_filter = { type = "enumeration"; values = ( "nearest", "linear" ); }; min_filter = { type = "enumeration"; values = ( "nearest", "linear", "mipmap", "default" ); }; name = "string"; no_shrink = "boolean"; repeat_s = "boolean"; repeat_t = "boolean"; texture_LOD_bias = "float"; }; } ); }; $scanClass = { type = "enumeration"; values = ( "CLASS_NOT_SET", "CLASS_NO_DRAW", "CLASS_BUOY", "CLASS_CARGO", "CLASS_MILITARY", "CLASS_MISSILE", "CLASS_MINE", "CLASS_POLICE", "CLASS_ROCK", "CLASS_STATION", "CLASS_THARGOID" ); }; $cargoType = { type = "enumeration"; values = ( "CARGO_NOT_CARGO", "CARGO_SLAVES", "CARGO_ALLOY", "CARGO_MINERALS", "CARGO_THARGOID", "CARGO_RANDOM", "CARGO_CARRIED", "CARGO_SCRIPTED_ITEM", "CARGO_CHARACTER" ); }; $customViewSpec = { type = "dictionary"; schema = { "view_description" = "string"; "view_position" = "vector"; "view_orientation" = "quaternion"; "weapon_facing" = { type = "enumeration"; filter = "lowerCase"; values = ( "forward", "aft", "port", "starboard" ); }; }; }; // Types handled in code. $modelName = { type = "delegatedType"; baseType = { type = "string"; filter = "lowerCase"; requiredSuffix = ".dat"; }; key = "modelName"; }; $colorSpecifier = { type = "delegatedType"; baseType = { type = "oneOf"; options = ( "array", "dictionary", "string" ); }; key = "colorSpecifier"; }; $textureFileName = { type = "delegatedType"; baseType = { type = "string"; filter = "lowerCase"; requiredSuffix = ".png"; }; key = "textureFileName"; }; $hasShipyard = { type = "oneOf"; options = ( "$scriptCondition", "boolean" ); }; $aiFileName = { type = "oneOf"; options = ( "$aiFileNamePlist", "$aiFileNameJs" ); key = "aiFileName"; }; $aiFileNamePlist = { type = "delegatedType"; baseType = { type = "string"; filter = "lowerCase"; requiredSuffix = ".plist"; }; }; $aiFileNameJs = { type = "delegatedType"; baseType = { type = "string"; filter = "lowerCase"; requiredSuffix = ".js"; }; }; $shaderFileName = { type = "delegatedType"; baseType = { type = "string"; filter = "lowerCase"; requiredSuffix = ( ".vertex", ".vert", ".fragment", ".frag" ); }; key = "shaderFileName"; }; $exhaustSpecifier = { type = "delegatedType"; baseType = "string"; key = "exhaustSpecifier"; }; $subEntitySpecifier = { type = "oneOf"; options = ( { type = "delegatedType"; baseType = "string"; key = "subEntitySpecifier"; }, { type = "dictionary"; requiredKeys = ( "type", "subentity_key" ); schema = { "type" = { type = "enumeration"; values = ( "standard", "ball_turret" ); }; subentity_key = "$shipKey"; position = "vector"; orientation = "quaternion"; is_dock = "boolean"; }; }, { type = "dictionary"; requiredKeys = ( "type" ); schema = { "type" = { type = "enumeration"; values = ( "flasher" ); }; color = "$colorSpecifier"; colors = { type = array; valueType = "$colorSpecifier"; }; position = "vector"; orientation = "quaternion"; size = "positiveFloat"; frequency = "float"; phase = "float"; initially_on = "boolean"; }; } ); }; $scriptActions = { type = "delegatedType"; baseType = "array"; key = "scriptActions"; }; $characterKey = { type = "delegatedType"; baseType = "string"; key = "characterKey"; }; $shipRole = { type = "delegatedType"; baseType = "string"; key = "shipRole"; }; $shipKey = { type = "delegatedType"; baseType = "string"; key = "shipKey"; }; $roles = { type = "delegatedType"; baseType = "string"; key = "roles"; }; $scriptCondition = { type = "delegatedType"; baseType = "string"; key = "scriptCondition"; }; $scriptConditionList = { type = "delegatedType"; baseType = "array"; key = "scriptConditions"; }; $portDimensions = { type = "delegatedType"; baseType = "string"; key = "portDimensions"; }; $extraEquipmentDictionary = { type = "delegatedType"; baseType = "dictionary"; key = "extraEquipmentDictionary"; }; $hudFileName = { type = "delegatedType"; baseType = "string"; key = "hudFileName"; }; $cargoCarried = { type = "delegatedType"; baseType = "string"; key = "cargoCarried"; }; $scriptFileName = { type = "delegatedType"; baseType = "string"; key = "$scriptFileName"; }; }; } oolite-1.82/Schemata/shipyardSchema.plist000066400000000000000000000024051256642440500205160ustar00rootroot00000000000000/* Schema for shipyard.plist. */ { type = dictionary; valueType = { type = dictionary; schema = { "chance" = float; "optional_equipment" = $equipmentKeyArray; "price" = positiveInteger; "standard_equipment" = { type = dictionary; schema = { "forward_weapon_type" = $weaponType; "extras" = $equipmentKeyArray; "missiles" = positiveInteger; }; allowOthers = NO; }; techLevel = { type = positiveInteger; maximum = 99; // Normally 0..13, but 99 is special. }; weaponFacings = { type = positiveInteger; maximum = 15; }; "conditions" = $scriptConditions; "max_cargo" = positiveInteger; }; }; $definitions = { $equipmentKey = { type = string; requiredPrefix = "EQ_"; }; $equipmentKeyArray = { type = array; valueType = $equipmentKey; }; $weaponType = { type = enumeration; values = ( "EQ_WEAPON_NONE", // "EQ_WEAPON_PLASMA_CANNON", // Currently not supported in code. "EQ_WEAPON_PULSE_LASER", "EQ_WEAPON_BEAM_LASER", "EQ_WEAPON_MINING_LASER", "EQ_WEAPON_MILITARY_LASER", "EQ_WEAPON_THARGOID_LASER" ); }; $scriptConditions = { type = delegatedType; baseType = array; key = scriptConditions; }; }; } oolite-1.82/config.make000066400000000000000000000014671256642440500150630ustar00rootroot00000000000000# # This file contains makefile configuration options for Oolite builds # # This file is sourced by both GNUmakefile and Makefile # # Any options can be overridden on the command-line: # $ make debug=yes DOCKING_CLEARANCE=no # $ make -f Makefile LIBJS_OPT=yes # VERBOSE = yes CP = cp # Setting the build parameters independently. We need everything set as below for the full test release configuration. BUILD_WITH_DEBUG_FUNCTIONALITY = yes NO_SHADERS = no ESPEAK = yes OO_CHECK_GL_HEAVY = no OO_EXCLUDE_DEBUG_SUPPORT = no OO_OXP_VERIFIER_ENABLED = yes OO_LOCALIZATION_TOOLS = yes DEBUG_GRAPHVIZ = yes OO_JAVASCRIPT_TRACE = yes OO_FOV_INFLIGHT_CONTROL_ENABLED = no oolite-1.82/deps/000077500000000000000000000000001256642440500137025ustar00rootroot00000000000000oolite-1.82/deps/URLs/000077500000000000000000000000001256642440500145275ustar00rootroot00000000000000oolite-1.82/deps/URLs/README000066400000000000000000000005631256642440500154130ustar00rootroot00000000000000These files contain download URLs for various dependencies. They are used by the Mac OS X build system to download dependencies automatically (see deps/Cocoa-deps/scripts). The first line must be an URL which can be downloaded by curl and expanded by tar. Any additional lines will be ignored. Currently, the second line is the original URL for files which are mirrored. oolite-1.82/deps/URLs/libpng.url000066400000000000000000000002771256642440500165340ustar00rootroot00000000000000http://oolite.org/dependencies/mac/libpng-1.5.13.tar.bz2 http://jens.ayton.se/oolite/deps/libpng-1.5.13.tar.bz2 ftp://anonymous@ftp.simplesystems.org/pub/libpng/png/src/libpng-1.5.13.tar.bz2 oolite-1.82/deps/URLs/sparkle.url000066400000000000000000000001621256642440500167130ustar00rootroot00000000000000http://oolite.org/dependencies/mac/Sparkle%201.5b6.zip http://sparkle.andymatuschak.org/files/Sparkle%201.5b6.zip oolite-1.82/installers/000077500000000000000000000000001256642440500151275ustar00rootroot00000000000000oolite-1.82/installers/FreeDesktop/000077500000000000000000000000001256642440500173425ustar00rootroot00000000000000oolite-1.82/installers/FreeDesktop/oolite-icon.png000066400000000000000000000053631256642440500223000ustar00rootroot00000000000000PNG  IHDR00W IDAThY TGv%u5hP`! 5p830!*$D9F10ɺ"^Qp4Ƹ$1!]]UuuuO^9=SHЇF]P===.!%hsm {RkF{7aTzOGG77e9%2!v9_xSWCO%?}+Evْ5|B-e r2}eaux>,Rݦi2`_~@6PTd2G----%; m <?L~Uˑ\$(K#hܒym1}-]S#!>ҝ&Z-^{NńJTin# Yxa ؿإ{E~xYIz !`zEx5'w Wކ6l!Cޥo050=$jtPԁdi" ݥRcfLZV(hRCF'7$+*)j=8i( q6?@291.#kK񺋿NO|} ߆FDFB6rLa;B?a1mHnWtut2:'܄\ҩ>+pqxLWWnbP{1 #pm 5">S>bĿyb٬ ˝\=G}NpIfى5J(3HZ쇺$\Kz24 %tO@\޷|DDJ3B$\`m(rbKd+Σmm`I5T[uyHR>A>l&i%d(3G٢&{!E0}u4,tDxm6vʅsmL ,R! 9M/4b\ q06WneXXV#T`а>1 9+.͝2B.p7~ VMs8O' &TuBtĥwZH GRpYA-ttu9r$MRt~yb?sUr @i_{X i |o#e !/Qѧ6 3zfV+0v`ـ!f559KNxk4BqZhxi&ۘ#-+b͈~6_̥:jlXS(VĝѧCVI=X$<3):ּ'k;D "|ħ&xW_DV2!X;*?9?s2UB-Ř)ӹZmڮ.Pu{NH=JH.s Nq}eqKb-n?LOvΊupRă#VE29R `9&eȑǘ._}nҿ@*|[>ycJ*;̊ nj@ Bx0O [?/Ҽ,8'b,ݖ!NT67_%"[*iiiyt˞4uN` NʱT,|^šOvg!Eļt$7N`XGxbJ2>+(>څAK y󇬭3$ޘ)[U9: Q z͆ѮUpigvSʎ"y1KxT@rX.`2rM1k& "͐u! L Cz]Ɯ^4wf**d.KPAuv;fy𹛗i ͊^?/> ES1&ۑ tbWaoŘbEpy'nQ>XZ&"<^*VIy$K!= s*_MZm(}]nBHj. sE^<߆g3V9Ínó4#S36U"`ʒ!{ K(Yk}!e0~V[nIeZd:lHbL;60f{}Ⱥ*xo]o]Ro`5ϱŸ1a,`Rg? %m ?mv2OjnZԄW;v I,AR[ǰK@$e?'^[dɞ gvbդ. M:=Z'~ٛ"OBڕ{ps/7R <|#w&wk)s3.#&u;D{CR)!vxg_;):tdSCcW=&FFF'HF MgtY#+($ +gHy=qG!E>$#<u-m6aYmn=Vrn!{=,"GUFXGcAՄ3iOxbUV4l쿖޴[9 o{cV3g)yp|}nr'ds ^ꩯߝ ,_ * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef NEED_STRLCPY #include #include #include "bsd_string.h" /* DJS for Oolite on Linux/win32 */ /* * Copy src to string dst of size siz. At most siz-1 characters * will be copied. Always NUL terminates (unless siz == 0). * Returns strlen(src); if retval >= siz, truncation occurred. */ size_t strlcpy(char *dst, const char *src, size_t siz) { char *d = dst; const char *s = src; size_t n = siz; /* Copy as many bytes as will fit */ if (n != 0 && --n != 0) { do { if ((*d++ = *s++) == 0) break; } while (--n != 0); } /* Not enough room in dst, add NUL and traverse rest of src */ if (n == 0) { if (siz != 0) *d = '\0'; /* NUL-terminate dst */ while (*s++) ; } return(s - src - 1); /* count does not include NUL */ } #endif oolite-1.82/src/Cocoa/000077500000000000000000000000001256642440500145625ustar00rootroot00000000000000oolite-1.82/src/Cocoa/Comparison.h000066400000000000000000000000641256642440500170450ustar00rootroot00000000000000// Empty for OS X, compatibility stuff for GNUstep. oolite-1.82/src/Cocoa/Info-Oolite.plist000066400000000000000000000112671256642440500177720ustar00rootroot00000000000000 CFBundleDevelopmentRegion English CFBundleDocumentTypes CFBundleTypeExtensions oolite-save CFBundleTypeIconFile oolite-document CFBundleTypeName Oolite Saved Game CFBundleTypeRole Editor LSHandlerRank Owner LSItemContentTypes org.aegidian.oolite.save CFBundleTypeExtensions oxp CFBundleTypeIconFile oolite-expansion-document CFBundleTypeName Oolite Expansion Pack CFBundleTypeRole Viewer LSHandlerRank Alternate LSItemContentTypes org.aegidian.oolite.oxp LSTypeIsPackage CFBundleTypeExtensions oxz CFBundleTypeIconFile oolite-expansion-document CFBundleTypeName Oolite Expansion Pack CFBundleTypeRole Viewer LSHandlerRank Owner LSItemContentTypes org.aegidian.oolite.oxp.compressed LSTypeIsPackage 0 CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIconFile oolite-icon CFBundleIdentifier org.aegidian.oolite CFBundleInfoDictionaryVersion 6.0 CFBundleName Oolite CFBundlePackageType APPL CFBundleSignature Ool8 CFBundleVersion 1.82 LSApplicationCategoryType public.app-category.games LSMinimumSystemVersionByArchitecture x86_64 10.6.0 NSDockTilePlugIn Oolite.docktileplugin NSMainNibFile Oolite NSPrincipalClass OoliteApp SUPublicDSAKeyFile SparkleVerificationKey.pem UTExportedTypeDeclarations UTTypeConformsTo public.text com.apple.property-list UTTypeDescription Oolite Saved Game UTTypeIconFile oolite-document UTTypeIdentifier org.aegidian.oolite.save UTTypeTagSpecification public.filename-extension oolite-save UTTypeConformsTo public.item UTTypeDescription Oolite Expansion Pack UTTypeIconFile oolite-expansion-document UTTypeIdentifier org.aegidian.oolite.expansion UTTypeReferenceURL http://wiki.alioth.net/index.php/OXP_howto UTTypeTagSpecification UTTypeConformsTo org.aegidian.oolite.expansion com.apple.package UTTypeDescription Oolite Expansion Pack UTTypeIdentifier org.aegidian.oolite.oxp UTTypeTagSpecification public.filename-extension oxp UTTypeConformsTo org.aegidian.oolite.expansion com.pkware.zip-archive UTTypeDescription Oolite Expansion Pack UTTypeIdentifier org.aegidian.oolite.oxp.compressed UTTypeReferenceURL UTTypeTagSpecification public.filename-extension oxz oolite-1.82/src/Cocoa/JAPersistentFileReference.h000066400000000000000000000037421256642440500217330ustar00rootroot00000000000000/* JAPersistentFileReference.h Store file references in a property list format. For file URLs, uses bookmark data (when available) and aliases to track files even if they're moved or renamed. Copyright (C) 2010-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import enum { kJAPersistentFileReferenceWithoutUI = 0x00000001UL, // Avoid user interaction. kJAPersistentFileReferenceWithoutMounting = 0x00000002UL, // Avoid mounting volumes. kJAPersistentFileReferenceReturnReferenceURL = 0x00000004UL // Return a file reference URL if possible. }; typedef uint32_t JAPersistentFileReferenceResolveFlags; NSDictionary *JAPersistentFileReferenceFromURL(NSURL *url); NSURL *JAURLFromPersistentFileReference(NSDictionary *fileRef, JAPersistentFileReferenceResolveFlags flags, BOOL *isStale); NSDictionary *JAPersistentFileReferenceFromPath(NSString *path); NSString *JAPathFromPersistentFileReference(NSDictionary *fileRef, JAPersistentFileReferenceResolveFlags flags, BOOL *isStale); oolite-1.82/src/Cocoa/JAPersistentFileReference.m000066400000000000000000000152361256642440500217410ustar00rootroot00000000000000/* JAPersistentFileReference.m Copyright (C) 2010-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "JAPersistentFileReference.h" #import #define kURLKey @"url" #define kAliasKey @"alias" #define kBookmarkKey @"bookmark" #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 #define BookmarkDataSupported() (YES) #else #if __ppc__ || __ppc64__ // Bookmark data is only available in Snow Leopard and later, which excludes PPC systems. #define BookmarkDataSupported() (NO) #else #define BookmarkDataSupported() ([NSURL instancesRespondToSelector:@selector(bookmarkDataWithOptions:includingResourceValuesForKeys:relativeToURL:error:)]) #endif #if MAC_OS_X_VERSION_10_6 > MAC_OS_X_VERSION_MAX_ALLOWED @interface NSURL (SnowLeopardMethods) - (NSData *)bookmarkDataWithOptions:(unsigned long)options includingResourceValuesForKeys:(NSArray *)keys relativeToURL:(NSURL *)relativeURL error:(NSError **)error; - (NSURL *)fileReferenceURL; - (NSURL *)filePathURL; - (BOOL)isFileReferenceURL; + (id)URLByResolvingBookmarkData:(NSData *)bookmarkData options:(unsigned long)options relativeToURL:(NSURL *)relativeURL bookmarkDataIsStale:(BOOL *)isStale error:(NSError **)error; @end enum { NSURLBookmarkResolutionWithoutUI = ( 1UL << 8 ), NSURLBookmarkResolutionWithoutMounting = ( 1UL << 9 ), }; #endif #endif NSDictionary *JAPersistentFileReferenceFromURL(NSURL *url) { if (url == nil) return nil; NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:3]; [result setObject:[url absoluteString] forKey:kURLKey]; if ([url isFileURL]) { FSRef fsRef; if (CFURLGetFSRef((CFURLRef)[url absoluteURL], &fsRef)) { AliasHandle alias = NULL; if (FSNewAlias(NULL, &fsRef, &alias) == noErr) { NSData *aliasData = [NSData dataWithBytes:*alias length:GetAliasSize(alias)]; if (aliasData != NULL) { [result setObject:aliasData forKey:kAliasKey]; } DisposeHandle((Handle)alias); } } } if (BookmarkDataSupported()) { NSURL *refURL = [url fileReferenceURL]; if (refURL != nil) { NSData *bookmarkData = [refURL bookmarkDataWithOptions:0 includingResourceValuesForKeys:nil relativeToURL:nil error:NULL]; if (bookmarkData != nil) { [result setObject:bookmarkData forKey:kBookmarkKey]; } } } return result; } static inline unsigned long BookmarkOptionsFromFlags(JAPersistentFileReferenceResolveFlags flags) { unsigned long result = 0; if (flags & kJAPersistentFileReferenceWithoutUI) result |= NSURLBookmarkResolutionWithoutUI; if (flags & NSURLBookmarkResolutionWithoutMounting) result |= NSURLBookmarkResolutionWithoutMounting; return result; } static inline unsigned long AliasMountFlagsFromFlags(JAPersistentFileReferenceResolveFlags flags) { unsigned long result = 0; if (flags & kJAPersistentFileReferenceWithoutUI) result |= kResolveAliasFileNoUI; return result; } NSURL *JAURLFromPersistentFileReference(NSDictionary *fileRef, JAPersistentFileReferenceResolveFlags flags, BOOL *isStale) { NSURL *result = nil; BOOL stale = NO, staleIfFile = NO; // Try bookmark. if (BookmarkDataSupported()) { NSData *bookmarkData = [fileRef objectForKey:kBookmarkKey]; if ([bookmarkData isKindOfClass:[NSData class]]) { result = [NSURL URLByResolvingBookmarkData:bookmarkData options:BookmarkOptionsFromFlags(flags) relativeToURL:nil bookmarkDataIsStale:&stale error:NULL]; } else staleIfFile = YES; } // Try alias. if (result == nil) { NSData *aliasData = [fileRef objectForKey:kAliasKey]; if ([aliasData isKindOfClass:[NSData class]]) { size_t size = [aliasData length]; AliasHandle alias = (AliasHandle)NewHandle(size); if (alias != NULL) { memcpy(*alias, [aliasData bytes], size); FSRef fsRef; Boolean carbonStale; if (FSResolveAliasWithMountFlags(NULL, alias, &fsRef, &carbonStale, AliasMountFlagsFromFlags(flags)) == noErr) { stale = carbonStale; result = (NSURL *)CFURLCreateFromFSRef(kCFAllocatorDefault, &fsRef); #if 1050 <= MAC_OS_X_VERSION_MAX_ALLOWED CFMakeCollectable((CFURLRef)result); #endif [result autorelease]; } DisposeHandle((Handle)alias); } } else staleIfFile = YES; } // Try URL. if (result == nil) { NSString *urlString = [fileRef objectForKey:kURLKey]; if ([urlString isKindOfClass:[NSString class]]) { result = [NSURL URLWithString:urlString relativeToURL:nil]; if ([result isFileURL] && ![[NSFileManager defaultManager] fileExistsAtPath:[result path]]) { result = nil; } } } // If we got nothing, it's definitely stale. if (result == nil) { stale = YES; } else { if ([result isFileURL] && staleIfFile) stale = YES; // Convert to/from file reference URL as appropriate. if (BookmarkDataSupported()) { if (flags & kJAPersistentFileReferenceReturnReferenceURL) { if (![result isFileReferenceURL] && [result isFileURL]) { NSURL *refURL = [result fileReferenceURL]; if (refURL != nil) result = refURL; } } else { if ([result isFileReferenceURL]) { NSURL *pathURL = [result filePathURL]; if (pathURL != nil) result = pathURL; } } } } if (isStale != NULL) *isStale = stale; return result; } NSDictionary *JAPersistentFileReferenceFromPath(NSString *path) { return JAPersistentFileReferenceFromURL([NSURL fileURLWithPath:path]); } NSString *JAPathFromPersistentFileReference(NSDictionary *fileRef, JAPersistentFileReferenceResolveFlags flags, BOOL *isStale) { NSURL *url = JAURLFromPersistentFileReference(fileRef, flags & ~kJAPersistentFileReferenceReturnReferenceURL, isStale); if ([url isFileURL]) return [url path]; return nil; } oolite-1.82/src/Cocoa/MyOpenGLView.h000066400000000000000000000131261256642440500172230ustar00rootroot00000000000000/* MyOpenGLView.h Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #import "OOOpenGL.h" #import "OOMouseInteractionMode.h" #import "OOOpenGLMatrixManager.h" #define MAX_CLEAR_DEPTH 10000000000.0 // 10 000 000 km. #define INTERMEDIATE_CLEAR_DEPTH 100.0 // 100 m. #define MIN_FOV_DEG 30.0f #define MAX_FOV_DEG 80.0f #define MIN_FOV (tan((MIN_FOV_DEG / 2) * M_PI / 180.0f)) #define MAX_FOV (tan((MAX_FOV_DEG / 2) * M_PI / 180.0f)) #define NUM_KEYS 320 #define MOUSE_DOUBLE_CLICK_INTERVAL 0.40 #define OOMOUSEWHEEL_EVENTS_DELAY_INTERVAL 0.05 @class Entity, GameController; enum GameViewKeys { gvFunctionKey1 = 241, gvFunctionKey2, gvFunctionKey3, gvFunctionKey4, gvFunctionKey5, gvFunctionKey6, gvFunctionKey7, gvFunctionKey8, gvFunctionKey9, gvFunctionKey10, gvFunctionKey11, gvArrowKeyRight, gvArrowKeyLeft, gvArrowKeyDown, gvArrowKeyUp, gvMouseLeftButton = 301, gvMouseDoubleClick, gvHomeKey, gvEndKey, gvInsertKey, gvDeleteKey, gvPageUpKey, gvPageDownKey, gvNumberKey0 = 48, gvNumberKey1, gvNumberKey2, gvNumberKey3, gvNumberKey4, gvNumberKey5, gvNumberKey6, gvNumberKey7, gvNumberKey8, gvNumberKey9, gvNumberPadKey0 = 310, gvNumberPadKey1, gvNumberPadKey2, gvNumberPadKey3, gvNumberPadKey4, gvNumberPadKey5, gvNumberPadKey6, gvNumberPadKey7, gvNumberPadKey8, gvNumberPadKey9 //319 }; enum MouseWheelStatus { gvMouseWheelDown = -1, gvMouseWheelNeutral, gvMouseWheelUp }; enum StringInput { gvStringInputNo = 0, gvStringInputAlpha = 1, gvStringInputLoadSave = 2, gvStringInputAll = 3 }; extern int debug; @interface MyOpenGLView: NSOpenGLView { @private GameController *gameController; BOOL keys[NUM_KEYS]; BOOL supressKeys; // DJS BOOL opt, ctrl, command, shift; BOOL allowingStringInput; BOOL isAlphabetKeyDown; BOOL commandQ; BOOL commandF; BOOL f12; int keycodetrans[255]; BOOL m_glContextInitialized; NSTimeInterval timeIntervalAtLastClick; BOOL doubleClick; NSMutableString *typedString; NSPoint virtualJoystickPosition; NSSize viewSize; GLfloat display_z; GLfloat x_offset, y_offset; float _fov; int _virtualScreen; NSData *_pixelFormatAttributes; OOOpenGLMatrixManager *matrixManager; } - (void) setStringInput: (enum StringInput) value; - (void) allowStringInput: (BOOL) value; - (enum StringInput) allowingStringInput; - (NSString *) typedString; - (void) resetTypedString; - (void) setTypedString:(NSString*) value; - (NSSize) viewSize; - (GLfloat) display_z; - (GLfloat) x_offset; - (GLfloat) y_offset; - (GameController *) gameController; - (void) setGameController:(GameController *) controller; - (void) noteMouseInteractionModeChangedFrom:(OOMouseInteractionMode)oldMode to:(OOMouseInteractionMode)newMode; - (void) initialiseGLWithSize:(NSSize) v_size; - (NSData *)pixelFormatAttributes; - (void) drawRect:(NSRect)rect; - (void) updateScreen; - (BOOL) snapShot:(NSString *)filename; - (void)mouseDown:(NSEvent *)theEvent; - (void)mouseUp:(NSEvent *)theEvent; - (void) setVirtualJoystick:(double) vmx :(double) vmy; - (NSPoint) virtualJoystickPosition; - (void) clearKeys; - (void) clearMouse; - (void) clearKey: (int)theKey; - (BOOL) isAlphabetKeyDown; - (void) supressKeysUntilKeyUp; // DJS - (BOOL) isDown: (int) key; - (BOOL) isOptDown; - (BOOL) isCtrlDown; - (BOOL) isCommandDown; - (BOOL) isShiftDown; - (int) numKeys; - (int) mouseWheelState; // Command-key combinations need special handling. - (BOOL) isCommandQDown; - (BOOL) isCommandFDown; - (void) clearCommandF; // Check current state of shift key rather than relying on last event. + (BOOL)pollShiftKey; - (OOOpenGLMatrixManager *) getOpenGLMatrixManager; #ifndef NDEBUG // General image-dumping methods. - (void) dumpRGBAToFileNamed:(NSString *)name bytes:(uint8_t *)bytes width:(NSUInteger)width height:(NSUInteger)height rowBytes:(NSUInteger)rowBytes; - (void) dumpRGBToFileNamed:(NSString *)name bytes:(uint8_t *)bytes width:(NSUInteger)width height:(NSUInteger)height rowBytes:(NSUInteger)rowBytes; - (void) dumpGrayToFileNamed:(NSString *)name bytes:(uint8_t *)bytes width:(NSUInteger)width height:(NSUInteger)height rowBytes:(NSUInteger)rowBytes; - (void) dumpGrayAlphaToFileNamed:(NSString *)name bytes:(uint8_t *)bytes width:(NSUInteger)width height:(NSUInteger)height rowBytes:(NSUInteger)rowBytes; // Split alpha into separate file. - (void) dumpRGBAToRGBFileNamed:(NSString *)rgbName andGrayFileNamed:(NSString *)grayName bytes:(uint8_t *)bytes width:(NSUInteger)width height:(NSUInteger)height rowBytes:(NSUInteger)rowBytes; #endif // no-ops to allow gamma value to be easily saved/restored - (void) setGammaValue: (float) value; - (float) gammaValue; - (void) setFov:(float)value fromFraction:(BOOL)fromFraction; - (float) fov:(BOOL)inFraction; @end oolite-1.82/src/Cocoa/MyOpenGLView.m000066400000000000000000000704121256642440500172310ustar00rootroot00000000000000/* MyOpenGLView.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "MyOpenGLView.h" #import "GameController.h" #import "Universe.h" #import "Entity.h" #import "OOPlanetEntity.h" #import "ResourceManager.h" #import "GuiDisplayGen.h" #import #import "NSFileManagerOOExtensions.h" #import "OOGraphicsResetManager.h" #import "PlayerEntity.h" #ifndef NDEBUG #import #endif static NSString * kOOLogKeyCodeOutOfRange = @"input.keyMapping.codeOutOfRange"; static NSString * kOOLogKeyUp = @"input.keyMapping.keyPress.keyUp"; static NSString * kOOLogKeyDown = @"input.keyMapping.keyPress.keyDown"; static void GetDesiredCursorState(OOMouseInteractionMode mode, BOOL *outHidden, BOOL *outObscured); static void ApplyCursorState(OOMouseInteractionMode mode); static void UnapplyCursorState(OOMouseInteractionMode mode); @interface MyOpenGLView(Internal) - (int) translateKeyCode:(int)input; - (void) recenterVirtualJoystick; @end #if !OOLITE_MAC_OS_X_10_7 @interface NSView (Lion) - (BOOL) wantsBestResolutionOpenGLSurface; - (void) setWantsBestResolutionOpenGLSurface:(BOOL)flag; - (NSPoint) convertPointToBacking:(NSPoint)aPoint; - (NSPoint) convertPointFromBacking:(NSPoint)aPoint; - (NSSize) convertSizeToBacking:(NSSize)aSize; - (NSSize) convertSizeFromBacking:(NSSize)aSize; - (NSRect) convertRectToBacking:(NSRect)aRect; - (NSRect) convertRectFromBacking:(NSRect)aRect; @end #endif @implementation MyOpenGLView - (id) initWithFrame:(NSRect)frameRect { #ifndef NDEBUG if (NSZombieEnabled) { OOLog(@"debug.zombieEnabled", @"*** ZOMBIES WILL EAT YOUR BRAIN ***"); } #endif matrixManager = [[OOOpenGLMatrixManager alloc] init]; // Pixel Format Attributes for the View-based (non-FullScreen) NSOpenGLContext NSOpenGLPixelFormatAttribute attrs[] = { // Specify that we want a windowed OpenGL context. // Must be first or we'll hit an assert in the legacy fullscreen controller. NSOpenGLPFAWindow, // We may be on a multi-display system (and each screen may be driven by a different renderer), so we need to specify which screen we want to take over. // For this demo, we'll specify the main screen. NSOpenGLPFAScreenMask, CGDisplayIDToOpenGLDisplayMask(kCGDirectMainDisplay), // Specifying "NoRecovery" gives us a context that cannot fall back to the software renderer. // This makes the View-based context a compatible with the fullscreen context, enabling us to use the "shareContext" // feature to share textures, display lists, and other OpenGL objects between the two. NSOpenGLPFANoRecovery, // Attributes Common to FullScreen and non-FullScreen NSOpenGLPFACompliant, NSOpenGLPFAColorSize, 32, NSOpenGLPFADepthSize, 32, NSOpenGLPFADoubleBuffer, NSOpenGLPFAAccelerated, #if FSAA // Need a preference or other sane way to activate this NSOpenGLPFAMultisample, NSOpenGLPFASampleBuffers, 1, NSOpenGLPFASamples,4, #endif 0 }; // Create our non-FullScreen pixel format. NSOpenGLPixelFormat *pixelFormat = [[[NSOpenGLPixelFormat alloc] initWithAttributes:attrs] autorelease]; if ((self = [super initWithFrame:frameRect pixelFormat:pixelFormat])) { if ([self respondsToSelector:@selector(setAcceptsTouchEvents:)]) { [self setAcceptsTouchEvents:YES]; } if ([self respondsToSelector:@selector(setWantsBestResolutionOpenGLSurface:)]) { // Enable high resolution on Retina displays. [self setWantsBestResolutionOpenGLSurface:YES]; } _pixelFormatAttributes = [[NSData alloc] initWithBytes:attrs length:sizeof attrs]; virtualJoystickPosition = NSMakePoint(0.0,0.0); typedString = [[NSMutableString alloc] initWithString:@""]; allowingStringInput = gvStringInputNo; isAlphabetKeyDown = NO; timeIntervalAtLastClick = [NSDate timeIntervalSinceReferenceDate]; _virtualScreen = [[self openGLContext] currentVirtualScreen]; } return self; } - (void) dealloc { DESTROY(typedString); DESTROY(_pixelFormatAttributes); DESTROY(matrixManager); [super dealloc]; } - (void) setStringInput:(enum StringInput)value { allowingStringInput = value; } - (void) allowStringInput:(BOOL)value { if (value) { allowingStringInput = gvStringInputAlpha; } else { allowingStringInput = gvStringInputNo; } } - (enum StringInput) allowingStringInput { return allowingStringInput; } - (NSString *) typedString { return typedString; } - (void) resetTypedString { [typedString setString:@""]; } - (void) setTypedString:(NSString *)value { [typedString setString:value]; } - (NSSize) viewSize { return viewSize; } - (GLfloat) display_z { return display_z; } - (GLfloat) x_offset { return x_offset; } - (GLfloat) y_offset { return y_offset; } - (GameController *) gameController { return gameController; } - (void) setGameController:(GameController *) controller { gameController = controller; } - (void) noteMouseInteractionModeChangedFrom:(OOMouseInteractionMode)oldMode to:(OOMouseInteractionMode)newMode { UnapplyCursorState(oldMode); ApplyCursorState(newMode); } - (void) updateScreen { if ([[self window] isVisible]) { [self drawRect:NSMakeRect(0, 0, viewSize.width, viewSize.height)]; } } - (void) drawRect:(NSRect)rect { if ((viewSize.width != [self frame].size.width)||(viewSize.height != [self frame].size.height)) // resized { m_glContextInitialized = NO; viewSize = [self frame].size; } if (!m_glContextInitialized) [self initialiseGLWithSize:viewSize]; // do all the drawing! if (UNIVERSE != nil) [UNIVERSE drawUniverse]; else { // not set up yet, draw a black screen OOGL(glClearColor(0.0, 0.0, 0.0, 0.0)); OOGL(glClear(GL_COLOR_BUFFER_BIT)); } [[self openGLContext] flushBuffer]; } - (void) noteMovedToBadDisplay { NSRunInformationalAlertPanel(DESC(@"oolite-mac-bad-display"), @"%@", nil, nil, nil, DESC(@"oolite-mac-bad-display-2")); } - (void) update { NSOpenGLContext *context = [self openGLContext]; [context update]; int virtualScreen = [context currentVirtualScreen]; if (virtualScreen != _virtualScreen) { @try { [[OOGraphicsResetManager sharedManager] resetGraphicsState]; _virtualScreen = virtualScreen; } @catch (NSException *exception) { /* Graphics reset failed, most likely because of OpenGL context incompatibility. Reset to previous "virtual screen" (i.e., renderer). OS X's OpenGL implementation will take care of copying */ [context setCurrentVirtualScreen:_virtualScreen]; [[OOGraphicsResetManager sharedManager] resetGraphicsState]; // If this throws, we're screwed. if ([[self gameController] inFullScreenMode]) { [[self gameController] pauseFullScreenModeToPerform:@selector(noteMovedToBadDisplay) onTarget:self]; } else { [self noteMovedToBadDisplay]; } } } } - (void) initialiseGLWithSize:(NSSize)v_size { viewSize = v_size; if (viewSize.width/viewSize.height > 4.0/3.0) { display_z = 480.0 * viewSize.width/viewSize.height; x_offset = 240.0 * viewSize.width/viewSize.height; y_offset = 240.0; } else { display_z = 640.0; x_offset = 320.0; y_offset = 320.0 * viewSize.height/viewSize.width; } if ([self respondsToSelector:@selector(convertSizeToBacking:)]) { // High resolution mode support. v_size = [self convertSizeToBacking:v_size]; } [self openGLContext]; // Force lazy setup if needed. [[self gameController] setUpBasicOpenGLStateWithSize:v_size]; [[self openGLContext] flushBuffer]; m_glContextInitialized = YES; } - (NSData *)pixelFormatAttributes { return _pixelFormatAttributes; } #ifdef MAC_OS_X_VERSION_10_7 // If building against 10.7 SDK, where relevant symbols are defined... - (void) viewDidMoveToWindow { /* Subscribe to NSWindowDidChangeBackingPropertiesNotification on systems which support it (10.7 and later). This notification fires when the scale factor or colour space of the window's backing store changes. We use it to track scale factor changes. */ if (&NSWindowDidChangeBackingPropertiesNotification != NULL && [self.window respondsToSelector:@selector(backingScaleFactor)]) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backingPropertiesChanged:) name:NSWindowDidChangeBackingPropertiesNotification object:self.window]; // Also, ensure the initial state makes sense. [self backingPropertiesChanged:nil]; } } - (void) backingPropertiesChanged:(NSNotification *)notification { GLSetDisplayScaleFactor(self.window.backingScaleFactor); } #endif - (BOOL) snapShot:(NSString *)filename { BOOL snapShotOK = YES; NSSize v_size = viewSize; if ([self respondsToSelector:@selector(convertSizeToBacking:)]) { // High resolution mode support. v_size = [self convertSizeToBacking:v_size]; } int w = v_size.width; int h = v_size.height; if (w & 3) w = w + 4 - (w & 3); long nPixels = w * h + 1; unsigned char *red = (unsigned char *)malloc(nPixels); unsigned char *green = (unsigned char *)malloc(nPixels); unsigned char *blue = (unsigned char *)malloc(nPixels); // backup the previous directory NSString *originalDirectory = [[NSFileManager defaultManager] currentDirectoryPath]; // use the snapshots directory NSString *snapshotsDirectory = [[[GameController sharedController] snapshotsURLCreatingIfNeeded:YES] path]; if (![[NSFileManager defaultManager] changeCurrentDirectoryPath:snapshotsDirectory]) { NSBeep(); OOLog(@"savedSnapshot.defaultPath.chdir.failed", @"Could not navigate to %@", snapshotsDirectory); snapShotOK = NO; goto FAIL; } BOOL withFilename = (filename != nil); static unsigned imageNo = 0; unsigned tmpImageNo = 0; NSString *pathToPic = nil; NSString *baseName = @"oolite"; if (withFilename) { baseName = filename; pathToPic = [filename stringByAppendingString:@".png"]; } else { tmpImageNo = imageNo; } if (withFilename && [[NSFileManager defaultManager] fileExistsAtPath:pathToPic]) { OOLog(@"screenshot.filenameExists", @"Snapshot \"%@.png\" already exists - adding numerical sequence.", pathToPic); pathToPic = nil; } if (pathToPic == nil) { do { tmpImageNo++; pathToPic = [NSString stringWithFormat:@"%@-%03d.png", baseName, tmpImageNo]; } while ([[NSFileManager defaultManager] fileExistsAtPath:pathToPic]); } if (!withFilename) { imageNo = tmpImageNo; } OOLog(@"screenshot", @"Saved screen shot \"%@\" (%u x %u pixels).", pathToPic, w, h); NSBitmapImageRep* bitmapRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL // --> let the class allocate it pixelsWide: w pixelsHigh: h bitsPerSample: 8 // each component is 8 bits (1 byte) samplesPerPixel: 3 // number of components (R, G, B) hasAlpha: NO // no transparency isPlanar: NO // data integrated into single plane colorSpaceName: NSDeviceRGBColorSpace bytesPerRow: 3*w // can no longer let the class figure it out bitsPerPixel: 24 // can no longer let the class figure it out ]; unsigned char *pixels = [bitmapRep bitmapData]; OOGL(glReadPixels(0,0, w,h, GL_RED, GL_UNSIGNED_BYTE, red)); OOGL(glReadPixels(0,0, w,h, GL_GREEN, GL_UNSIGNED_BYTE, green)); OOGL(glReadPixels(0,0, w,h, GL_BLUE, GL_UNSIGNED_BYTE, blue)); int x,y; for (y = 0; y < h; y++) { long index = (h - y - 1)*w; for (x = 0; x < w; x++) // set bitmap pixels { *pixels++ = red[index]; *pixels++ = green[index]; *pixels++ = blue[index++]; } } [[bitmapRep representationUsingType:NSPNGFileType properties:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSImageInterlaced, NULL]] writeToFile:pathToPic atomically:YES]; // save PNG representation of image // free allocated objects and memory [bitmapRep release]; FAIL: free(red); free(green); free(blue); // return to the previous directory [[NSFileManager defaultManager] changeCurrentDirectoryPath:originalDirectory]; return snapShotOK; } - (BOOL) acceptsFirstResponder { return YES; } - (void) keyUp:(NSEvent *)theEvent { NSString *stringValue = nil; int key; int keycode; stringValue = [theEvent charactersIgnoringModifiers]; /* Bug: exception when releasing accent key. Analysis: Dead keys (accents and similar) return an empty string. Fix: reject zero-length strings. This is the Wrong Thing - we should really be using KeyTranslate()/UCKeyTranslate() to find out what the string would be if you pressed the key and then space. -- Ahruman 20070714 */ if ([stringValue length] < 1) return; supressKeys = NO; keycode = [theEvent keyCode] & 255; key = keycodetrans[keycode]; // retrieve the character we got for pressing the hardware at key location 'keycode' OOLog(kOOLogKeyUp, @"Key up: stringValue = \"%@\", keyCode = %d, key = %u", stringValue, keycode, key); // Special handling of command keys used in full-screen mode. if ([theEvent modifierFlags] & NSCommandKeyMask) { switch (key) { case 'q': commandQ = NO; break; case 'f': commandF = NO; break; } // Pass through to allow clearing of normal key as well. } /* HACK: treat f12 as alias to cmd-F for compatibility with helpful forum advice etc. */ if (key == NSF12FunctionKey) { commandF = NO; f12 = NO; return; }; isAlphabetKeyDown = NO; if ((key >= 0)&&(key < [self numKeys])&&(keys[key])) { keys[key] = NO; } else { if (key > [self numKeys]) OOLog(kOOLogKeyCodeOutOfRange, @"Translated key: %d out of range", key); } } - (void) keyDown:(NSEvent *)theEvent { NSString *stringValue = nil; int key; int keycode; stringValue = [theEvent charactersIgnoringModifiers]; /* Bug: exception when pressing accent key. Analysis: Dead keys (accents and similar) return an empty string. Fix: reject zero-length strings. This is the Wrong Thing - we should really be using KeyTranslate()/UCKeyTranslate() to find out what the string would be if you pressed the key and then space. -- Ahruman 20070714 */ if ([stringValue length] < 1) return; key = [stringValue characterAtIndex:0]; keycode = [theEvent keyCode] & 255; key = [self translateKeyCode:key]; OOLog(kOOLogKeyDown, @"Key down: stringValue = \"%@\", keyCode = %d, key = %u", stringValue, keycode, key); // Special handling of command keys used in full-screen mode. if ([theEvent modifierFlags] & NSCommandKeyMask) { switch (key) { case 'q': commandQ = YES; break; case 'f': commandF = YES; break; } return; } keycodetrans[keycode] = key; // record the chracter we got for pressing the hardware at key location 'keycode' /* HACK: treat f12 as alias to cmd-F for compatibility with helpful forum advice etc. */ if (key == NSF12FunctionKey) { if (!f12) { f12 = YES; [gameController performSelector:@selector(toggleFullScreenAction:) withObject:nil afterDelay:0.0]; } return; }; if ((key >= 0)&&(key < [self numKeys])&&(!keys[key])) { keys[key] = YES; if (allowingStringInput) { if ((key == gvDeleteKey) && [typedString length] > 0) { // delete [typedString deleteCharactersInRange:NSMakeRange([typedString length] - 1, 1)]; } isAlphabetKeyDown = NO; // limited input for planet find screen if (allowingStringInput == gvStringInputAlpha) { if (isalpha(key)) { isAlphabetKeyDown = YES; // convert to lower case [typedString appendFormat:@"%c", tolower(key)]; } } // full input for load-save screen or 'all' input if (allowingStringInput >= gvStringInputLoadSave) { // except '/' for loadsave if (isprint(key) && key != '/') { isAlphabetKeyDown = YES; [typedString appendFormat:@"%c", key]; } else if (key == '/' && allowingStringInput == gvStringInputAll) { isAlphabetKeyDown = YES; [typedString appendFormat:@"%c", key]; } } } } else { if (key > [self numKeys]) { OOLog(kOOLogKeyCodeOutOfRange, @"Translated key: %d out of range", key); } } } /* Capture shift, ctrl, opt and command press & release */ - (void)flagsChanged:(NSEvent *)theEvent { NSUInteger flags = [theEvent modifierFlags]; opt = (flags & NSAlternateKeyMask) ? YES : NO; ctrl = (flags & NSControlKeyMask) ? YES : NO; command = (flags & NSCommandKeyMask) ? YES : NO; shift = ( flags & NSShiftKeyMask ) ? YES : NO; } - (void)mouseDown:(NSEvent *)theEvent { if (doubleClick) { doubleClick = NO; keys[gvMouseDoubleClick] = NO; } keys[gvMouseLeftButton] = YES; // 'a' down } - (void)mouseUp:(NSEvent *)theEvent { NSTimeInterval timeBetweenClicks = [NSDate timeIntervalSinceReferenceDate] - timeIntervalAtLastClick; timeIntervalAtLastClick += timeBetweenClicks; if (!doubleClick) { doubleClick = (timeBetweenClicks < MOUSE_DOUBLE_CLICK_INTERVAL); // One fifth of a second keys[gvMouseDoubleClick] = doubleClick; } keys[gvMouseLeftButton] = NO; // 'a' up } - (void)mouseMoved:(NSEvent *)theEvent { double mx = [theEvent locationInWindow].x - viewSize.width/2.0; double my = [theEvent locationInWindow].y - viewSize.height/2.0; if (display_z > 640.0) { mx /= viewSize.width * MAIN_GUI_PIXEL_WIDTH / display_z; my /= viewSize.height; } else { mx /= MAIN_GUI_PIXEL_WIDTH * viewSize.width / 640.0; my /= MAIN_GUI_PIXEL_HEIGHT * viewSize.width / 640.0; } [self setVirtualJoystick:mx :-my]; } - (void) mouseDragged:(NSEvent *)theEvent { [self mouseMoved:theEvent]; } - (void) otherMouseDragged:(NSEvent *)theEvent { [self mouseMoved:theEvent]; } - (void) rightMouseDown:(NSEvent *)theEvent { [self recenterVirtualJoystick]; } - (void) rightMouseUp:(NSEvent *)theEvent { [self recenterVirtualJoystick]; } - (void) touchesEndedWithEvent:(NSEvent *)theEvent { [self recenterVirtualJoystick]; } - (void) recenterVirtualJoystick { if ([PLAYER guiScreen] == GUI_SCREEN_MAIN) { [[GameController sharedController] recenterVirtualJoystick]; } } ///////////////////////////////////////////////////////////// /* Turn the Cocoa ArrowKeys into our arrow key constants. */ - (int) translateKeyCode: (int) input { int key = input; switch ( input ) { case NSUpArrowFunctionKey: key = gvArrowKeyUp; break; case NSDownArrowFunctionKey: key = gvArrowKeyDown; break; case NSLeftArrowFunctionKey: key = gvArrowKeyLeft; break; case NSRightArrowFunctionKey: key = gvArrowKeyRight; break; case NSF1FunctionKey: key = gvFunctionKey1; break; case NSF2FunctionKey: key = gvFunctionKey2; break; case NSF3FunctionKey: key = gvFunctionKey3; break; case NSF4FunctionKey: key = gvFunctionKey4; break; case NSF5FunctionKey: key = gvFunctionKey5; break; case NSF6FunctionKey: key = gvFunctionKey6; break; case NSF7FunctionKey: key = gvFunctionKey7; break; case NSF8FunctionKey: key = gvFunctionKey8; break; case NSF9FunctionKey: key = gvFunctionKey9; break; case NSF10FunctionKey: key = gvFunctionKey10; break; case NSF11FunctionKey: key = gvFunctionKey11; break; case NSHomeFunctionKey: key = gvHomeKey; break; case NSDeleteCharacter: key = gvDeleteKey; break; case NSInsertFunctionKey: key = gvInsertKey; break; case NSEndFunctionKey: key = gvEndKey; break; case NSPageUpFunctionKey: key = gvPageUpKey; break; case NSPageDownFunctionKey: key = gvPageDownKey; break; default: break; } return key; } - (void) setVirtualJoystick:(double) vmx :(double) vmy { virtualJoystickPosition.x = vmx; virtualJoystickPosition.y = vmy; } - (NSPoint) virtualJoystickPosition { return virtualJoystickPosition; } ///////////////////////////////////////////////////////////// - (void) clearKeys { int i; for (i = 0; i < [self numKeys]; i++) keys[i] = NO; } - (void) clearMouse { keys[gvMouseDoubleClick] = NO; keys[gvMouseLeftButton] = NO; doubleClick = NO; } - (void) clearKey: (int)theKey { if (theKey >= 0 && theKey < [self numKeys]) { keys[theKey] = NO; } } - (BOOL) isAlphabetKeyDown { return isAlphabetKeyDown = NO; } // DJS: When entering submenus in the gui, it is not helpful if the // key down that brought you into the submenu is still registered // as down when we're in. This makes isDown return NO until a key up // event has been received from SDL. - (void) supressKeysUntilKeyUp { if (keys[gvMouseDoubleClick] == NO) { supressKeys = YES; [self clearKeys]; } else { [self clearMouse]; } } - (BOOL) isDown: (int) key { if( supressKeys ) return NO; if ( key < 0 ) return NO; if ( key >= [self numKeys] ) return NO; return keys[key]; } - (BOOL) isOptDown { return opt; } - (BOOL) isCtrlDown { return ctrl; } - (BOOL) isCommandDown { return command; } - (BOOL) isShiftDown { return shift; } - (int) numKeys { return NUM_KEYS; } - (int) mouseWheelState { // FIXME: Mousewheel in-game implementaiton for Macs needed return gvMouseWheelNeutral; } - (BOOL) isCommandQDown { return commandQ; } - (BOOL) isCommandFDown { return commandF; } - (void) clearCommandF { commandF = NO; } + (BOOL)pollShiftKey { #define KEYMAP_GET(m, index) ((((uint8_t*)(m))[(index) >> 3] & (1L << ((index) & 7))) ? 1 : 0) KeyMap map; GetKeys(map); return KEYMAP_GET(map, 56) || KEYMAP_GET(map, 60); // Left shift or right shift -- although 60 shouldn't occur. } - (OOOpenGLMatrixManager *) getOpenGLMatrixManager { return matrixManager; } #ifndef NDEBUG // General image-dumping method. - (void) dumpRGBAToFileNamed:(NSString *)name bytes:(uint8_t *)bytes width:(NSUInteger)width height:(NSUInteger)height rowBytes:(NSUInteger)rowBytes { if (name == nil || bytes == NULL || width == 0 || height == 0 || rowBytes < width * 4) return; NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&bytes pixelsWide:width pixelsHigh:height bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO colorSpaceName:NSCalibratedRGBColorSpace bytesPerRow:rowBytes bitsPerPixel:32]; if (bitmap != nil) { [bitmap autorelease]; NSString *filepath = [[[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:name] stringByAppendingPathExtension:@"png"]; [[bitmap representationUsingType:NSPNGFileType properties:nil] writeToFile:filepath atomically:YES]; } } - (void) dumpRGBToFileNamed:(NSString *)name bytes:(uint8_t *)bytes width:(NSUInteger)width height:(NSUInteger)height rowBytes:(NSUInteger)rowBytes { if (name == nil || bytes == NULL || width == 0 || height == 0 || rowBytes < width * 3) return; NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&bytes pixelsWide:width pixelsHigh:height bitsPerSample:8 samplesPerPixel:3 hasAlpha:NO isPlanar:NO colorSpaceName:NSCalibratedRGBColorSpace bytesPerRow:rowBytes bitsPerPixel:24]; if (bitmap != nil) { [bitmap autorelease]; NSString *filepath = [[[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:name] stringByAppendingPathExtension:@"png"]; [[bitmap representationUsingType:NSPNGFileType properties:nil] writeToFile:filepath atomically:YES]; } } - (void) dumpGrayToFileNamed:(NSString *)name bytes:(uint8_t *)bytes width:(NSUInteger)width height:(NSUInteger)height rowBytes:(NSUInteger)rowBytes { if (name == nil || bytes == NULL || width == 0 || height == 0 || rowBytes < width) return; NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&bytes pixelsWide:width pixelsHigh:height bitsPerSample:8 samplesPerPixel:1 hasAlpha:NO isPlanar:NO colorSpaceName:NSCalibratedWhiteColorSpace bytesPerRow:rowBytes bitsPerPixel:8]; if (bitmap != nil) { [bitmap autorelease]; NSString *filepath = [[[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:name] stringByAppendingPathExtension:@"png"]; [[bitmap representationUsingType:NSPNGFileType properties:nil] writeToFile:filepath atomically:YES]; } } - (void) dumpGrayAlphaToFileNamed:(NSString *)name bytes:(uint8_t *)bytes width:(NSUInteger)width height:(NSUInteger)height rowBytes:(NSUInteger)rowBytes { if (name == nil || bytes == NULL || width == 0 || height == 0 || rowBytes < width * 2) return; NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&bytes pixelsWide:width pixelsHigh:height bitsPerSample:8 samplesPerPixel:2 hasAlpha:YES isPlanar:NO colorSpaceName:NSCalibratedWhiteColorSpace bytesPerRow:rowBytes bitsPerPixel:16]; if (bitmap != nil) { [bitmap autorelease]; NSString *filepath = [[[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:name] stringByAppendingPathExtension:@"png"]; [[bitmap representationUsingType:NSPNGFileType properties:nil] writeToFile:filepath atomically:YES]; } } - (void) dumpRGBAToRGBFileNamed:(NSString *)rgbName andGrayFileNamed:(NSString *)grayName bytes:(uint8_t *)bytes width:(NSUInteger)width height:(NSUInteger)height rowBytes:(NSUInteger)rowBytes { if ((rgbName == nil && grayName == nil) || bytes == NULL || width == 0 || height == 0 || rowBytes < width * 4) return; uint8_t *rgbBytes, *rgbPx, *grayBytes, *grayPx, *srcPx; NSUInteger x, y; BOOL trivalAlpha = YES; rgbPx = rgbBytes = malloc(width * height * 3); if (rgbBytes == NULL) return; grayPx = grayBytes = malloc(width * height); if (grayBytes == NULL) { free(rgbBytes); return; } for (y = 0; y < height; y++) { srcPx = bytes + rowBytes * y; for (x = 0; x < width; x++) { *rgbPx++ = *srcPx++; *rgbPx++ = *srcPx++; *rgbPx++ = *srcPx++; trivalAlpha = trivalAlpha && ((*srcPx == 0xFF) || (*srcPx == 0x00)); // Look for any "interesting" pixels in alpha. *grayPx++ = *srcPx++; } } [self dumpRGBToFileNamed:rgbName bytes:rgbBytes width:width height:height rowBytes:width * 3]; free(rgbBytes); if (!trivalAlpha) { [self dumpGrayToFileNamed:grayName bytes:grayBytes width:width height:height rowBytes:width]; } free(grayBytes); } #endif static void GetDesiredCursorState(OOMouseInteractionMode mode, BOOL *outHidden, BOOL *outObscured) { NSCParameterAssert(outHidden != NULL && outObscured != NULL); *outHidden = (mode == MOUSE_MODE_FLIGHT_WITH_MOUSE_CONTROL); *outObscured = (mode == MOUSE_MODE_FLIGHT_NO_MOUSE_CONTROL); } static void ApplyCursorState(OOMouseInteractionMode mode) { BOOL hidden, obscured; GetDesiredCursorState(mode, &hidden, &obscured); if (hidden) [NSCursor hide]; if (obscured) [NSCursor setHiddenUntilMouseMoves:YES]; } static void UnapplyCursorState(OOMouseInteractionMode mode) { BOOL hidden, obscured; GetDesiredCursorState(mode, &hidden, &obscured); if (hidden) [NSCursor unhide]; if (obscured) [NSCursor setHiddenUntilMouseMoves:NO]; } - (void) setGammaValue: (float) value { // no-op } - (float) gammaValue { return 1.0; } - (void) setFov:(float)value fromFraction:(BOOL)fromFraction { _fov = fromFraction ? value : tan((value / 2) * M_PI / 180); } - (float) fov:(BOOL)inFraction { return inFraction ? _fov : 2 * atan(_fov) * 180 / M_PI; } @end oolite-1.82/src/Cocoa/OOFullScreenWindow.h000066400000000000000000000025761256642440500204350ustar00rootroot00000000000000/* OOFullScreenWindow.h Trivial NSWindow subclass which allows canBecomeKeyWindow and canBecomeMainWindow to be set. Copyright (C) 2012-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" #if OOLITE_64_BIT // Not used in 32-bit builds. @interface OOFullScreenWindow: NSWindow @property (readwrite) BOOL canBecomeKeyWindow; @property (readwrite) BOOL canBecomeMainWindow; @end #endif oolite-1.82/src/Cocoa/OOFullScreenWindow.m000066400000000000000000000032301256642440500204260ustar00rootroot00000000000000/* OOFullScreenWindow.m Copyright (C) 2012-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOFullScreenWindow.h" #if OOLITE_64_BIT @implementation OOFullScreenWindow @synthesize canBecomeKeyWindow = _canBecomeKeyWindow; @synthesize canBecomeMainWindow = _canBecomeMainWindow; - (id) initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag { self = [super initWithContentRect:contentRect styleMask:aStyle backing:bufferingType defer:flag]; if (self != nil) { _canBecomeKeyWindow = [super canBecomeKeyWindow]; _canBecomeMainWindow = [super canBecomeMainWindow]; } return self; } @end #endif oolite-1.82/src/Cocoa/OOMacJoystickManager.h000066400000000000000000000021431256642440500207040ustar00rootroot00000000000000/* OOMacJoystickManager.h By Alex Smith and Jens Ayton Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #import "OOJoystickManager.h" #define STICK_GAMMA 2.0 enum { kJoystickGammaTableSize = 256 }; @interface OOMacJoystickManager: OOJoystickManager { @private IOHIDManagerRef hidManager; CFMutableArrayRef devices; int gammaTable[kJoystickGammaTableSize]; } @end oolite-1.82/src/Cocoa/OOMacJoystickManager.m000066400000000000000000000263541256642440500207230ustar00rootroot00000000000000/* OOMacJoystickManager.m By Alex Smith and Jens Ayton Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOMacJoystickManager.h" #import "OOLogging.h" #import "OOCollectionExtractors.h" @interface OOMacJoystickManager () - (void) handleInputEvent:(IOHIDValueRef)value; - (void) handleJoystickAttach:(IOHIDDeviceRef)device; - (void) handleDeviceRemoval:(IOHIDDeviceRef)device; @end static void HandleDeviceMatchingCallback(void * inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef inIOHIDDeviceRef); static void HandleInputValueCallback(void * inContext, IOReturn inResult, void* inSender, IOHIDValueRef inIOHIDValueRef); static void HandleDeviceRemovalCallback(void * inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef inIOHIDDeviceRef); @implementation OOMacJoystickManager - (id) init { if ((self = [super init])) { // Initialise gamma table int i; for (i = 0; i< kJoystickGammaTableSize; i++) { double x = ((double) i - 128.0) / 128.0; double sign = x>=0 ? 1.0 : -1.0; double y = sign * floor(32767.0 * pow (fabs(x), STICK_GAMMA)); gammaTable[i] = (int) y; } /* SLOW_CODE The call to IOHIDManagerSetDeviceMatching() has a significiant presence in warm startup profiles; sometimes it's even makes -[OOMacJoystickManager init] the single heaviest Oolite symbol within -[GameController applicationDidFinishLaunching]. Setup should be made asynchronous. -- Ahruman 2012-09-14 */ hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); IOHIDManagerSetDeviceMatching(hidManager, NULL); IOHIDManagerRegisterDeviceMatchingCallback(hidManager, HandleDeviceMatchingCallback, self); IOHIDManagerRegisterDeviceRemovalCallback(hidManager, HandleDeviceRemovalCallback, self); IOHIDManagerScheduleWithRunLoop(hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); IOReturn iores = IOHIDManagerOpen(hidManager, kIOHIDOptionsTypeNone); if (iores != kIOReturnSuccess) { OOLog(@"joystick.error.init", @"Cannot open HID manager; joystick support will not function."); } devices = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); } return self; } - (void) dealloc { if (hidManager != NULL) CFRelease(hidManager); if (devices != NULL) CFRelease(devices); [super dealloc]; } - (NSUInteger) joystickCount { return CFArrayGetCount(devices); } - (void) handleDeviceAttach:(IOHIDDeviceRef)device { NSArray *usagePairs = (NSArray *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDDeviceUsagePairsKey)); for (NSDictionary *pair in usagePairs) { if ([pair oo_unsignedIntForKey:@kIOHIDDeviceUsagePageKey] == kHIDPage_GenericDesktop) { unsigned usage = [pair oo_unsignedIntForKey:@kIOHIDDeviceUsageKey]; if (usage == kHIDUsage_GD_Joystick || usage == kHIDUsage_GD_GamePad) { [self handleJoystickAttach:device]; return; } } } if (OOLogWillDisplayMessagesInClass(@"joystick.reject")) { NSString *product = (NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); unsigned usagePage = [(NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDPrimaryUsagePageKey)) unsignedIntValue]; unsigned usage = [(NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDPrimaryUsageKey)) unsignedIntValue]; OOLog(@"joystick.reject", @"Ignoring HID device: %@ (primary usage %u:%u)", product, usagePage, usage); } } - (void) handleJoystickAttach:(IOHIDDeviceRef)device { OOLog(@"joystick.connect", @"Joystick connected: %@", IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey))); CFArrayAppendValue(devices, device); IOHIDDeviceRegisterInputValueCallback(device, HandleInputValueCallback, self); if (OOLogWillDisplayMessagesInClass(@"joystick.connect.element")) { // Print out elements of new device CFArrayRef elementList = IOHIDDeviceCopyMatchingElements(device, NULL, 0L); if (elementList != NULL) { OOLogIndent(); CFIndex idx, count = CFArrayGetCount(elementList); for (idx = 0; idx < count; idx++) { IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elementList, idx); IOHIDElementType elementType = IOHIDElementGetType(element); if (elementType > kIOHIDElementTypeInput_ScanCodes) { continue; } IOHIDElementCookie elementCookie = IOHIDElementGetCookie(element); uint32_t usagePage = IOHIDElementGetUsagePage(element); uint32_t usage = IOHIDElementGetUsage(element); uint32_t min = (uint32_t)IOHIDElementGetPhysicalMin(element); uint32_t max = (uint32_t)IOHIDElementGetPhysicalMax(element); NSString *name = (NSString *)IOHIDElementGetProperty(element, CFSTR(kIOHIDElementNameKey)) ?: @"unnamed"; OOLog(@"joystick.connect.element", @"%@ - usage %d:%d, cookie %d, range %d-%d", name, usagePage, usage, (int) elementCookie, min, max); } OOLogOutdent(); CFRelease(elementList); } } } - (void) handleDeviceRemoval:(IOHIDDeviceRef)device { OOLog(@"joystick.remove", @"Joystick removed: %@", IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey))); CFIndex index = CFArrayGetFirstIndexOfValue(devices, CFRangeMake(0, CFArrayGetCount(devices)), device); if (index != kCFNotFound) CFArrayRemoveValueAtIndex(devices, index); } static int AxisIndex(uint32_t page, uint32_t usage) { /* Map axis-like HID usages to SDL-like axis indices. These are all the commonly used joystick axes according to Microsoft's DirectInput documentation (hey, you've got to get your info somewhere). GD_Slider, GD_Dial, DG_Wheel and Sim_Throttle are actually distinct; unlike the others, they're uncentered. (By implication, IOHIDElementHasPreferredState() should be false.) This should, in particular, be considered when mapping to the throttle: centered axes should provide relative input (for gamepads), while uncentered ones should provide absolute input. Since that festering pool of pus, SDL, can't make this distinction, OOJoystickManager can't either (yet). -- Ahruman 2011-01-04 */ switch (page) { case kHIDPage_GenericDesktop: switch (usage) { case kHIDUsage_GD_X: return 0; case kHIDUsage_GD_Y: return 1; case kHIDUsage_GD_Z: return 2; case kHIDUsage_GD_Rx: return 3; case kHIDUsage_GD_Ry: return 4; case kHIDUsage_GD_Rz: return 5; case kHIDUsage_GD_Slider: return 6; case kHIDUsage_GD_Dial: return 7; case kHIDUsage_GD_Wheel: return 8; } break; case kHIDPage_Simulation: switch (usage) { case kHIDUsage_Sim_Throttle: return 9; } } // Negative numbers indicate non-axis. return -1; } static uint8_t MapHatValue(CFIndex value, CFIndex max) { /* A hat switch has four or eight values, indicating directions. 0 is straight up/forwards, and subsequent values increase clockwise. Out-of-range values are nulls, indicating no direction is pressed. */ if (0 <= value && value <= max) { if (max == 3) { const uint8_t valueMap4[4] = { JOYHAT_UP, JOYHAT_RIGHT, JOYHAT_DOWN, JOYHAT_LEFT }; return valueMap4[value]; } else if (max == 7) { const uint8_t valueMap8[8] = { JOYHAT_UP, JOYHAT_RIGHTUP, JOYHAT_RIGHT, JOYHAT_RIGHTDOWN, JOYHAT_DOWN, JOYHAT_LEFTDOWN, JOYHAT_LEFT, JOYHAT_LEFTUP }; return valueMap8[value]; } } return JOYHAT_CENTERED; } - (void) handleInputEvent:(IOHIDValueRef)value { IOHIDElementRef element = IOHIDValueGetElement(value); uint32_t usagePage = IOHIDElementGetUsagePage(element); uint32_t usage = IOHIDElementGetUsage(element); int buttonNum = 0; int axisNum = 0; axisNum = AxisIndex(usagePage, usage); if (axisNum >= 0) { JoyAxisEvent evt; evt.type = JOYAXISMOTION; evt.which = 0; evt.axis = axisNum; CFIndex intValue = IOHIDValueGetIntegerValue(value); CFIndex min = IOHIDElementGetLogicalMin(element); CFIndex max = IOHIDElementGetLogicalMax(element); float axisValue = (float)(intValue - min) / (float)(max + 1 - min); // Note: this is designed so gammaIndex == intValue if min == 0 and max == kJoystickGammaTableSize - 1 (the common case). unsigned gammaIndex = floor(axisValue * kJoystickGammaTableSize); /* CRASH: r4435, Mac OS X 10.6.6, x86_64 -[OOMacJoystickManager handleInputEvent:] + 260 http://aegidian.org/bb/viewtopic.php?f=3&t=9382 The instruction is: movl (%r15,%rbx,4), %r12d which corresponds to the load gammaTable[gammaIndex]. Check added to find out-of-range values. -- Ahruman 2011-03-07 */ if (EXPECT_NOT(gammaIndex > kJoystickGammaTableSize)) { OOLogERR(@"joystick.gamma.overflow", @"Joystick gamma table overflow - gammaIndex is %u of %u. Raw value: %i in [%i..%i]; axisValue: %g; unrounded gammaIndex: %g. Ignoring event. This is an internal error, please report it.", gammaIndex, kJoystickGammaTableSize, (int)intValue, (int)min, (int)max, axisValue, axisValue * kJoystickGammaTableSize ); return; } // End bug check evt.value = gammaTable[gammaIndex]; [self decodeAxisEvent:&evt]; } else if (usagePage == kHIDPage_GenericDesktop && usage == kHIDUsage_GD_Hatswitch) { CFIndex max = IOHIDElementGetLogicalMax(element); CFIndex min = IOHIDElementGetLogicalMin(element); JoyHatEvent evt = { .type = JOYHAT_MOTION, .which = 0, .hat = 0, // The abuse of usage values for identifying elements means we can't distinguish between hats. .value = MapHatValue(IOHIDValueGetIntegerValue(value) - min, max - min) }; [self decodeHatEvent:&evt]; } else if (usagePage == kHIDPage_Button) { // Button Event buttonNum = usage; JoyButtonEvent evt; BOOL buttonState = (IOHIDValueGetIntegerValue(value) != 0); evt.type = buttonState ? JOYBUTTONDOWN : JOYBUTTONUP; evt.which = 0; evt.button = buttonNum; evt.state = buttonState ? 1 : 0; [self decodeButtonEvent:&evt]; } } - (NSString *) nameOfJoystick:(NSUInteger)stickNumber { IOHIDDeviceRef device = (IOHIDDeviceRef)CFArrayGetValueAtIndex(devices, stickNumber); return (NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); } - (int16_t) getAxisWithStick:(NSUInteger) stickNum axis:(NSUInteger) axisNum { return 0; } @end //Thunking to Objective-C static void HandleDeviceMatchingCallback(void * inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef inIOHIDDeviceRef) { [(OOMacJoystickManager *)inContext handleDeviceAttach:inIOHIDDeviceRef]; } static void HandleInputValueCallback(void * inContext, IOReturn inResult, void* inSender, IOHIDValueRef inIOHIDValueRef) { [(OOMacJoystickManager *)inContext handleInputEvent:inIOHIDValueRef]; } static void HandleDeviceRemovalCallback(void * inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef inIOHIDDeviceRef) { [(OOMacJoystickManager *)inContext handleDeviceRemoval:inIOHIDDeviceRef]; } oolite-1.82/src/Cocoa/OOMacSnowLeopardFullScreenController.h000066400000000000000000000020051256642440500240730ustar00rootroot00000000000000/* OOMacSnowLeopardFullScreenController.h Full-screen controller used in 64-bit Mac builds under Mac OS X 10.6. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOFullScreenController.h" #if OOLITE_MAC_OS_X && OOLITE_64_BIT @interface OOMacSnowLeopardFullScreenController: OOFullScreenController @end #endif oolite-1.82/src/Cocoa/OOMacSnowLeopardFullScreenController.m000066400000000000000000000200651256642440500241060ustar00rootroot00000000000000/* OOMacSnowLeopardFullScreenController.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOMacSnowLeopardFullScreenController.h" #if OOLITE_MAC_OS_X && OOLITE_64_BIT #import // For SetSystemUIMode() #import "MyOpenGLView.h" #import "OOLogging.h" #import "OOFullScreenWindow.h" #import "OOCollectionExtractors.h" #define SUPRESS_BLANKING_WINDOWS ( 1 && OO_DEBUG) @interface OOMacSnowLeopardFullScreenController () @property (nonatomic, retain) NSWindow *fullScreenWindow; // Window on main gameplay screen which contains our OpenGL view in full screen mode. @property (nonatomic, retain) NSArray *blankingWindows; // Plain black windows covering other screens if relevant. @property (nonatomic, retain) NSWindow *standardWindow; // The main game window, stashed away for use when existing full screen mode. @property (nonatomic, retain) NSScreen *gameScreen; - (void) beginFullScreenMode; - (void) endFullScreenMode; - (void) screenParametersChanged:(NSNotification *)notification; - (void) setUpBlankingWindowsForScreensOtherThan:(NSScreen *)gameScreen; - (void) removeBlankingWindows; @end @implementation OOMacSnowLeopardFullScreenController @synthesize fullScreenMode = _fullScreenMode; // Future note: needs to be explicit because property declaration is inherited. @synthesize fullScreenWindow = _fullScreenWindow; @synthesize blankingWindows = _blankingWindows; @synthesize standardWindow = _standardWindow; @synthesize gameScreen = _gameScreen; - (void) dealloc { self.fullScreenMode = NO; DESTROY(_fullScreenWindow); DESTROY(_blankingWindows); DESTROY(_standardWindow); DESTROY(_gameScreen); [super dealloc]; } - (void) setFullScreenMode:(BOOL)value { if (!value && self.fullScreenMode) { [self endFullScreenMode]; } else if (value && !self.fullScreenMode) { [self beginFullScreenMode]; } } - (NSArray *) displayModes { NSSize size = self.fullScreenWindow.frame.size; NSDictionary *fakeMode = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithUnsignedInt:size.width], kOODisplayWidth, [NSNumber numberWithUnsignedInt:size.height], kOODisplayHeight, nil]; return [NSArray arrayWithObject:fakeMode]; } - (NSUInteger) indexOfCurrentDisplayMode { return 0; } - (BOOL) setDisplayWidth:(NSUInteger)width height:(NSUInteger)height refreshRate:(NSUInteger)refresh { return NO; } - (NSDictionary *) findDisplayModeForWidth:(NSUInteger)width height:(NSUInteger)height refreshRate:(NSUInteger)refresh { NSDictionary *fakeMode = [self.displayModes objectAtIndex:0]; if (width == [fakeMode oo_unsignedIntegerForKey:kOODisplayWidth] && height == [fakeMode oo_unsignedIntegerForKey:kOODisplayHeight]) { return fakeMode; } return nil; } - (void) noteMouseInteractionModeChangedFrom:(OOMouseInteractionMode)oldMode to:(OOMouseInteractionMode)newMode { NSAssert(self.fullScreenMode, @"%s called in wrong state.", __FUNCTION__); [self.gameView noteMouseInteractionModeChangedFrom:oldMode to:newMode]; } #pragma mark - Actual full screen mode handling - (void) beginFullScreenMode { NSAssert(!self.fullScreenMode, @"%s called in wrong state.", __FUNCTION__); // Stash the windowed-mode window so we can restore to it later. self.standardWindow = self.gameView.window; /* Set up a full-screen window. Based on OpenGL Programming Guide for Mac [developer.apple.com], dated 2012-07-23, "Drawing to the Full Screen". */ self.gameScreen = self.gameView.window.screen; NSRect frame = self.gameScreen.frame; OOFullScreenWindow *window = [[OOFullScreenWindow alloc] initWithContentRect:frame styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; if (window == nil) return; self.fullScreenWindow = [window autorelease]; [window setOpaque:YES]; [window setMovable:YES]; window.canBecomeKeyWindow = YES; window.canBecomeMainWindow = YES; window.acceptsMouseMovedEvents = YES; #if !OO_DEBUG /* Leaving a full-screen window visible in the background is anti-social, but convenient in debug builds. */ window.hidesOnDeactivate = YES; #endif // TODO: handle screen reconfiguration. [self.standardWindow orderOut:nil]; window.contentView = self.gameView; SetSystemUIMode(kUIModeAllSuppressed, kUIOptionDisableMenuBarTransparency); [self setUpBlankingWindowsForScreensOtherThan:self.gameScreen]; [window makeKeyAndOrderFront:self]; _fullScreenMode = YES; // Subscribe to reconfiguration notifications. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(screenParametersChanged:) name:NSApplicationDidChangeScreenParametersNotification object:NSApp]; } - (void) endFullScreenMode { NSAssert(self.fullScreenMode, @"%s called in wrong state.", __FUNCTION__); [self.fullScreenWindow orderOut:nil]; [self removeBlankingWindows]; SetSystemUIMode(kUIModeNormal, 0); self.standardWindow.contentView = self.gameView; [self.standardWindow makeKeyAndOrderFront:nil]; self.standardWindow = nil; self.fullScreenWindow = nil; self.gameScreen = nil; _fullScreenMode = NO; // Unsubscribe from notifications. [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationDidChangeScreenParametersNotification object:NSApp]; } - (void) screenParametersChanged:(NSNotification *)notification { NSAssert(self.fullScreenMode, @"%s called in wrong state.", __FUNCTION__); [self.fullScreenWindow setFrame:self.gameScreen.frame display:YES]; [self setUpBlankingWindowsForScreensOtherThan:self.gameScreen]; } - (void) setUpBlankingWindowsForScreensOtherThan:(NSScreen *)gameScreen { #if SUPRESS_BLANKING_WINDOWS // Skip blanking windows while debugging. return; #endif /* On muliple-screen systems, fill all screens except the game screen with an all-black window. This behaviour has its critics, but it is consistent with both traditional Oolite behaviour and Mac OS X 10.7 and later standard behaviour. */ // Remove any existing blanking windows. [self removeBlankingWindows]; NSArray *screens = [NSScreen screens]; if (screens.count <= 1) { // No blanking windows needed on single-screen systems. return; } NSMutableArray *windows = [NSMutableArray arrayWithCapacity:screens.count - 1]; for (NSScreen *screen in screens) { if ([screen isEqual:gameScreen]) continue; NSRect frame = screen.frame; OOFullScreenWindow *window = [[OOFullScreenWindow alloc] initWithContentRect:frame styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; [window setOpaque:YES]; [window setMovable:YES]; window.collectionBehavior = NSWindowCollectionBehaviorTransient | NSWindowCollectionBehaviorIgnoresCycle; window.canBecomeKeyWindow = NO; window.canBecomeMainWindow = NO; window.hidesOnDeactivate = YES; window.backgroundColor = [NSColor blackColor]; [windows addObject:window]; [window orderFront:nil]; [window release]; } self.blankingWindows = windows; } - (void) removeBlankingWindows { for (NSWindow *window in self.blankingWindows) { [window orderOut:nil]; } self.blankingWindows = nil; } - (void) checkWindowVisible:(NSTimer *)timer { NSWindow *window = timer.userInfo; OOLog(@"temp.fullScreen", @"Window %@ is %@ on screen %@", window, window.isVisible ? @"visible" : @"INVISIBLE", window.screen); } @end #endif oolite-1.82/src/Cocoa/OOMacSystemStandardFullScreenController.h000066400000000000000000000031021256642440500246020ustar00rootroot00000000000000/* OOMacSystemStandardFullScreenController.h Full-screen controller used in 64-bit Mac builds under Mac OS X 10.7 (on systems with a single display, as determined at application startup) and always under Mac OS X 10.8 or later. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOFullScreenController.h" /* OOMacSystemStandardFullScreenController requires the Mac OS X 10.7 SDK. */ #if OOLITE_MAC_OS_X #if OOLITE_64_BIT && defined(MAC_OS_X_VERSION_10_7) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 #define OO_MAC_SUPPORT_SYSTEM_STANDARD_FULL_SCREEN 1 #endif #endif #ifndef OO_MAC_SUPPORT_SYSTEM_STANDARD_FULL_SCREEN #define OO_MAC_SUPPORT_SYSTEM_STANDARD_FULL_SCREEN 0 #endif #if OO_MAC_SUPPORT_SYSTEM_STANDARD_FULL_SCREEN @interface OOMacSystemStandardFullScreenController: OOFullScreenController + (BOOL) shouldUseSystemStandardFullScreenController; @end #endif oolite-1.82/src/Cocoa/OOMacSystemStandardFullScreenController.m000066400000000000000000000065521256642440500246230ustar00rootroot00000000000000/* OOMacSystemStandardFullScreenController.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOMacSystemStandardFullScreenController.h" #if OO_MAC_SUPPORT_SYSTEM_STANDARD_FULL_SCREEN #import "MyOpenGLView.h" #import "OOLogging.h" #import "OOPrimaryWindow.h" #import "OOCollectionExtractors.h" #ifndef NSAppKitVersionNumber10_7 #define NSAppKitVersionNumber10_7 1138 #endif @implementation OOMacSystemStandardFullScreenController + (BOOL) shouldUseSystemStandardFullScreenController { if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_6) { // Never on 10.6 or earlier; the necessary API doesn't exist. return NO; } // If safe to use, allow override for debugging. NSString *override = [[NSUserDefaults standardUserDefaults] stringForKey:@"full-screen-mode-override"]; if (override != nil) { if ([override isEqualToString:@"lion"]) return YES; if ([override isEqualToString:@"snow-leopard"]) return NO; } if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_7) { // Always use on 10.8 or later. return YES; } return NSScreen.screens.count == 1; // Use if there's a single screen on 10.7. } - (BOOL) inFullScreenMode { return ([NSApp presentationOptions] & NSApplicationPresentationFullScreen) != 0; } - (void) setFullScreenMode:(BOOL)value { if (!value == self.fullScreenMode) { OOPrimaryWindow *window = (OOPrimaryWindow *)self.gameView.window; NSAssert([window isKindOfClass:OOPrimaryWindow.class], @"Incorrect UI setup; main game window should be OOPrimaryWindow."); [window makeKeyAndOrderFront:nil]; [window standardToggleFullScreen:nil]; } } - (NSArray *) displayModes { NSSize size = self.gameView.window.frame.size; NSDictionary *fakeMode = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithUnsignedInt:size.width], kOODisplayWidth, [NSNumber numberWithUnsignedInt:size.height], kOODisplayHeight, nil]; return [NSArray arrayWithObject:fakeMode]; } - (NSUInteger) indexOfCurrentDisplayMode { return 0; } - (BOOL) setDisplayWidth:(NSUInteger)width height:(NSUInteger)height refreshRate:(NSUInteger)refresh { return NO; } - (NSDictionary *) findDisplayModeForWidth:(NSUInteger)width height:(NSUInteger)height refreshRate:(NSUInteger)refresh { NSDictionary *fakeMode = [self.displayModes objectAtIndex:0]; if (width == [fakeMode oo_unsignedIntegerForKey:kOODisplayWidth] && height == [fakeMode oo_unsignedIntegerForKey:kOODisplayHeight]) { return fakeMode; } return nil; } - (void) noteMouseInteractionModeChangedFrom:(OOMouseInteractionMode)oldMode to:(OOMouseInteractionMode)newMode { [self.gameView noteMouseInteractionModeChangedFrom:oldMode to:newMode]; } @end #endif oolite-1.82/src/Cocoa/OOPDFView.h000066400000000000000000000023021256642440500164320ustar00rootroot00000000000000/* OOPDFView.h PDFView subclass which renders images using high-quality interpolation. Copyright (C) 2011-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import @interface OOPDFView: PDFView @end oolite-1.82/src/Cocoa/OOPDFView.m000066400000000000000000000026471256642440500164530ustar00rootroot00000000000000/* OOPDFView.m Copyright (C) 2011-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOPDFView.h" @implementation OOPDFView - (void) drawPage:(PDFPage *)page { // Force PDF view to render scaled images nicely (just like Preview). [NSGraphicsContext saveGraphicsState]; [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh]; [super drawPage:page]; [NSGraphicsContext restoreGraphicsState]; } @end oolite-1.82/src/Cocoa/OOPrimaryWindow.h000066400000000000000000000034151256642440500200070ustar00rootroot00000000000000/* OOPrimaryWindow.h Trivial NSWindow subclass which lets us intercept toggleFullScreen: in order to interpose our custom full screen handling when needed. Copyright (C) 2012-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" @protocol OOPrimaryWindowDelegate; @interface OOPrimaryWindow: NSWindow #if !__OBJC2__ { @private id _fullScreenDelegate; } #endif @property (nonatomic, assign) IBOutlet id fullScreenDelegate; // Call through to standard toggleFullScreen: implementation. - (void) standardToggleFullScreen:(id)sender; @end @protocol OOPrimaryWindowDelegate @optional // Sent in response to toggleFullScreen:. - (void) toggleFullScreenCalledForWindow:(OOPrimaryWindow *)window withSender:(id)sender; @end oolite-1.82/src/Cocoa/OOPrimaryWindow.m000066400000000000000000000032201256642440500200060ustar00rootroot00000000000000/* OOPrimaryWindow.m Copyright (C) 2012-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOPrimaryWindow.h" #if !OOLITE_MAC_OS_X_10_7 @interface NSWindow (Lion) - (void) toggleFullScreen:(id)sender; @end #endif @implementation OOPrimaryWindow @synthesize fullScreenDelegate = _fullScreenDelegate; - (void) standardToggleFullScreen:(id)sender { [super toggleFullScreen:sender]; } - (void) toggleFullScreen:(id)sender { id delegate = self.fullScreenDelegate; if ([delegate respondsToSelector:@selector(toggleFullScreenCalledForWindow:withSender:)]) { [delegate toggleFullScreenCalledForWindow:self withSender:sender]; } } @end oolite-1.82/src/Cocoa/Oolite.xib000066400000000000000000002234711256642440500165320ustar00rootroot00000000000000 1060 12E55 3084 1187.39 626.00 3084 2053 NSBox NSCustomObject NSCustomView NSImageCell NSImageView NSMenu NSMenuItem NSTextField NSTextFieldCell NSUserDefaultsController NSView NSWindowTemplate PDFView com.apple.InterfaceBuilder.CocoaPlugin com.apple.pdfkit.ibplugin PluginDependencyRecalculationVersion OoliteApp FirstResponder NSApplication MainMenu Oolite 1048576 2147483647 NSImage NSMenuCheckmark NSImage NSMenuMixedState submenuAction: Oolite About Oolite 2147483647 Check for Updates… 2147483647 YES YES 1048576 2147483647 Oolite Screen Shots 1048576 2147483647 Expansion Packs 1048576 2147483647 Show Previous Log 1048576 2147483647 YES Logs Folder 524288 2147483647 YES YES 1048576 2147483647 Hide Oolite h 1048576 2147483647 Hide Others H 1048576 2147483647 Show All 1048576 2147483647 YES YES 1048576 2147483647 Quit Oolite q 1048576 2147483647 _NSAppleMenu Edit 1048576 2147483647 submenuAction: Edit Undo z 1048576 2147483647 Redo Z 1048576 2147483647 YES YES 1048576 2147483647 Cut x 1048576 2147483647 Copy c 1048576 2147483647 Paste v 1048576 2147483647 Paste and Match Style V 1572864 2147483647 Delete 1048576 2147483647 Select All a 1048576 2147483647 Window 1048576 2147483647 submenuAction: Window Minimize m 1048576 2147483647 Zoom 1048576 2147483647 Close w 1048576 2147483647 Enter Full Screen f 1310720 2147483647 YES YES 1048576 2147483647 Bring All to Front 1048576 2147483647 _NSWindowsMenu Help 1048576 2147483647 submenuAction: Help Oolite Help ? 1048576 2147483647 _NSMainMenu 9 2 {{44, 222}, {640, 480}} 1886912512 Oolite OOPrimaryWindow View {640, 480} 256 18 274 298 301 Apple PDF pasteboard type Apple PICT pasteboard type Apple PNG pasteboard type NSFilenamesPboardType NeXT Encapsulated PostScript v1.2 pasteboard type NeXT TIFF v4.0 pasteboard type {{128, 41}, {384, 384}} YES 134217728 33554432 NSImage splash 0 1 0 NO NO YES 298 {{17, 8}, {606, 17}} YES 67108864 138412032 loading… LucidaGrande 13 1044 YES 6 System controlColor 3 MC42NjY2NjY2NjY3AA 1 MC44MTU2MDgzMjI2IDAuODIwMTg5MTc4IDEAA NO {{0, 30}, {640, 424}} NSView {640, 480} {640, 480} {0, 0} 67108864 0 LucidaGrande 11 3100 6 System textBackgroundColor 3 MQA 3 MCAwLjgwMDAwMDAxMTkAA 0 4 0 NO 1 MCAwIDAAA {640, 480} {{0, 0}, {1366, 746}} {640, 502} {10000000000000, 10000000000000} oolite-main-window 128 YES GameController 15 2 {{26, 348}, {640, 480}} 1886912512 Help NSWindow View {640, 400} 256 18 NSFilenamesPboardType {640, 480} YES 1 NO 1.0212417840957642 YES {640, 480} {{0, 0}, {1366, 746}} {640, 422} {10000000000000, 10000000000000} oolitehelp 256 YES 3 2 {{50, 220}, {640, 480}} 1886912512 Panel NSWindow View {213, 107} 256 274 {640, 480} MyOpenGLView NSOpenGLView {640, 480} {{0, 0}, {1366, 746}} {213, 129} {10000000000000, 10000000000000} YES YES Show 2147483647 submenuAction: Show Screen Shots 2147483647 Expansion Packs 2147483647 Previous Log 2147483647 YES Logs Folder 524288 2147483647 SUUpdater terminate: 139 hideOtherApplications: 146 hide: 152 unhideAllApplications: 153 delegate 207 orderFrontStandardAboutPanel: 273 _gameController 361 _gameWindow 362 pasteAsPlainText: 328 paste: 329 delete: 332 cut: 338 redo: 339 selectAll: 341 undo: 342 copy: 343 performZoom: 351 performMiniaturize: 352 arrangeInFront: 353 performClose: 358 delegate 241 initialFirstResponder 296 fullScreenDelegate 439 gameWindow 213 gameView 262 splashView 264 splashProgressTextField 271 showLogAction: 399 showAddOnsAction: 400 showSnapshotsAction: 409 dockMenu 423 showSnapshotsAction: 424 showAddOnsAction: 425 showLogAction: 426 showLogFolderAction: 427 showLogFolderAction: 429 helpView 437 toggleFullScreenAction: 438 makeKeyAndOrderFront: 228 gameController 265 checkForUpdates: 433 0 -2 File's Owner -1 First Responder -3 Application 29 MainMenu 56 57 58 134 136 144 145 149 150 381 382 383 384 103 106 111 299 300 301 302 303 305 313 319 320 325 326 345 346 347 348 349 350 354 356 198 Main Window 199 205 GameController 225 Help Window 224 256 non-displayed Window 257 260 385 Shared User Defaults Controller 416 Dock menu 417 418 419 420 421 422 428 431 432 435 360 261 412 270 413 436 com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{274, 644}, {640, 480}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{358, 638}, {640, 480}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin OOPDFView com.apple.pdfkit.ibplugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin 439 GameController NSObject id id id id id showAddOnsAction: id showLogAction: id showLogFolderAction: id showSnapshotsAction: id toggleFullScreenAction: id NSMenu MyOpenGLView NSWindow PDFView NSTextField NSView dockMenu NSMenu gameView MyOpenGLView gameWindow NSWindow helpView PDFView splashProgressTextField NSTextField splashView NSView IBProjectSource ./Classes/GameController.h MyOpenGLView NSOpenGLView IBProjectSource ./Classes/MyOpenGLView.h OOPDFView PDFView IBProjectSource ./Classes/OOPDFView.h OOPrimaryWindow NSWindow fullScreenDelegate id fullScreenDelegate fullScreenDelegate id IBProjectSource ./Classes/OOPrimaryWindow.h OoliteApp NSApplication GameController NSWindow _gameController GameController _gameWindow NSWindow IBProjectSource ./Classes/OoliteApp.h SUUpdater NSObject checkForUpdates: id checkForUpdates: checkForUpdates: id delegate id delegate delegate id IBProjectSource ./Classes/SUUpdater.h 0 IBCocoaFramework com.apple.InterfaceBuilder.CocoaPlugin.macosx com.apple.InterfaceBuilder.CocoaPlugin.macosx YES 3 {11, 11} {10, 3} {384, 384} oolite-1.82/src/Cocoa/OoliteApp.h000066400000000000000000000024611256642440500166320ustar00rootroot00000000000000/* OoliteApp.h This is a subclass of NSApplication for Oolite. It gets around problems with the system intercepting certain events (NSKeyDown and NSKeyUp) before MyOpenGLView gets to see them, it does this by sending those events to MyOpenGLView regardless of any other processing NSApplication will do with them. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" @class GameController; @interface OoliteApp: NSApplication { @private IBOutlet NSWindow *_gameWindow; IBOutlet GameController *_gameController; NSString *_exitContext; } - (void) setExitContext:(NSString *)exitContext; @end oolite-1.82/src/Cocoa/OoliteApp.m000066400000000000000000000032331256642440500166350ustar00rootroot00000000000000/* OoliteApp.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OoliteApp.h" #import "GameController.h" #import "MyOpenGLView.h" @implementation OoliteApp - (void)sendEvent:(NSEvent *)theEvent { NSEventType etype = [theEvent type]; MyOpenGLView* gameView = [_gameController gameView]; if ([NSApp keyWindow] == _gameWindow) { // Ensure key events are handled at least once when game window is key switch (etype) { case NSKeyDown: [gameView keyDown:theEvent]; break; case NSKeyUp: [gameView keyUp:theEvent]; break; default: break; } } [super sendEvent:theEvent]; // perform the default event behaviour } - (void) setExitContext:(NSString *)exitContext { [_exitContext release]; _exitContext = [exitContext copy]; } - (void) terminate:(id)sender { if (_exitContext == nil) [self setExitContext:@"Cocoa terminate event"]; OOLog(@"exit.context", @"Exiting: %@.", _exitContext); [super terminate:sender]; } @end oolite-1.82/src/Cocoa/exports-debug-64.exp000066400000000000000000000034201256642440500203160ustar00rootroot00000000000000# Symbols used by Debug.OXP .objc_class_name_PlayerEntity .objc_class_name_OOCacheManager .objc_class_name_OOGraphicsResetManager .objc_class_name_OOTexture .objc_class_name_ResourceManager .objc_class_name_OOJavaScriptEngine .objc_class_name_OOColor .objc_class_name_OOWeakRefObject .objc_class_name_OOScript .objc_class_name_OOJSScript .objc_class_name_OODebugMonitor .objc_class_name_ShipEntity .objc_class_name_OOEntityWithDrawable .objc_class_name_OOOpenGLExtensionManager _OBJC_CLASS_$_ShipEntity _OBJC_CLASS_$_Entity _OBJC_CLASS_$_AI _OBJC_CLASS_$_OOShipGroup _OOLogWithFunctionFileAndLine _OOLogWithPrefix _OOLogInsertMarker _OOLogSetDisplayMessagesInClass _OOLogSetShowFileAndLine _OOLogSetShowFunction _OOLogSetShowTime _OOLogSetShowMessageClass _OOLogShowFileAndLine _OOLogShowFunction _OOLogShowTime _OOLogShowMessageClass _OOLogWillDisplayMessagesInClass _OOLogHandlerGetLogPath _OOLogIndent _OOLogOutdent _gDebugFlags _gSharedUniverse _OOJSBasicPrivateObjectConverter _OOJSNativeObjectFromJSObject _OOJSRegisterObjectConverter _JS_ConvertStub _JS_EnumerateStub _JS_GetPrivate _JS_SetPrivate _JS_InitClass _JS_NewObject _JS_PropertyStub _JS_ResolveStub _OOJSReportBadPropertySelector _OOJSReportError _JS_InternString _OOJSNativeObjectFromJSValue _JS_ValueToNumber _JS_SetProperty _OOJSReportWarning _OOBooleanFromObject _OOStringFromScanClass _OOStringFromEntityStatus _QuaternionDescription _VectorDescription _OOStringFromBehaviour _OOStringFromShaderSetting # _OOSoundRegisterDebugMonitor # Not implemented in OpenAL builds _OOJSProfileEnter _OOJSProfileExit _OOJSUnreachable _OOStringFromGraphicsDetail # Debug value formatter support _JSValueToStrDbg _JSObjectToStrDbg _JSStringToStrDbg _JSValueTypeDbg _JSValueToStrSafeDbg _JSObjectToStrSafeDbg _JSStringToStrSafeDbg _JSIDToStrSafeDbg oolite-1.82/src/Cocoa/exports-release.exp000066400000000000000000000000001256642440500204100ustar00rootroot00000000000000oolite-1.82/src/Cocoa/main.m000066400000000000000000000003461256642440500156670ustar00rootroot00000000000000#import "OOCocoa.h" #import "OOLoggingExtended.h" #import "OODebugFlags.h" #ifndef NDEBUG NSUInteger gDebugFlags = 0; #endif int main(int argc, const char *argv[]) { OOLoggingInit(); return NSApplicationMain(argc, argv); } oolite-1.82/src/Cocoa/oolite-nonshared.xcconfig000066400000000000000000000002641256642440500215600ustar00rootroot00000000000000#include "oolite-options.xcconfig" EXPORTED_SYMBOLS_FILE = src/Cocoa/exports-debug-32.exp EXPORTED_SYMBOLS_FILE[arch=x86_64] = src/Cocoa/exports-debug-64.exp OO_MATHS_OPTS = -O3 oolite-1.82/src/Cocoa/oolite-options.xcconfig000066400000000000000000000007611256642440500212740ustar00rootroot00000000000000#include "oolite-version.xcconfig" #include "oolite-targets.xcconfig" #include "oolite-snapshot.xcconfig" // OOLITE_MAC_OS_X is used within Oolite to detect the platform it's being built for. // OBJC_OLD_DISPATCH_PROTOTYPES causes the Objective-C runtime headers (in the 10.8 // SDK or later) to declare objc_msgSend(), etc. and IMP as void(*)(void), so they // need to be cast to the appropriate function pointer type. OOLITE_OPTION_MACROS = OOLITE_MAC_OS_X=1 OBJC_OLD_DISPATCH_PROTOTYPES=0 oolite-1.82/src/Cocoa/oolite-snapshot.xcconfig000066400000000000000000000005711256642440500214370ustar00rootroot00000000000000// Hacktackular: Universe.m and OOLogHeader.m have $SNAPSHOT_MACROS in their // "Additional Compiler Flags" setting. The Mac nightly script overrides this // file to set up the SNAPSHOT_BUILD and OOLITE_SNAPSHOT_VERSION macros. // OO_UNUSED_MACRO is, as the name suggests, unused; putting an empty string // here leads to build problems. SNAPSHOT_MACROS = "-DOO_UNUSED_MACRO" oolite-1.82/src/Cocoa/oolite-targets.xcconfig000066400000000000000000000003221256642440500212430ustar00rootroot00000000000000#include "oolite-warnings.xcconfig" ARCHS = x86_64 SDKROOT = macosx MACOSX_DEPLOYMENT_TARGET = 10.6 GCC_VERSION = com.apple.compilers.llvm.clang.1_0 CLANG_ENABLE_MODULES = YES oolite-1.82/src/Cocoa/oolite-version.xcconfig000066400000000000000000000000241256642440500212560ustar00rootroot00000000000000OOLITE_VERSION=1.82 oolite-1.82/src/Cocoa/oolite-warnings.xcconfig000066400000000000000000000063131256642440500214300ustar00rootroot00000000000000// The vast majority of format warnings in 32-bit will be using %l{diux} for // NS[U]Integer, which is perfectly safe. It's possible suppressing this will // miss some real bugs, but not too likely, and casting everywhere is too ugly. OO_ARCH_WARNING_FLAGS = OO_ARCH_WARNING_FLAGS[arch=i386] = -Wno-format // Start off with all possible warnings, and make a stable subset errors. OO_LOTS_OF_WARNINGS = -Weverything -Werror=all // Trim off stylistic warnings from -Wall or -Wextra that we don't want. OO_WARNING_EXCEPTIONS = -Wno-unused-parameter -Wno-missing-field-initializers // Disable stuff from -Weverything that we don't want. // -Wpedantic: Even I can't be bothered to conform to -Wpedantic. Yet. // -Wgnu: We use GNU extensions freely. // -Wundef: Warns about legal and normal use of the preprocessor. // -Wobjc-interface-ivars, Wdirect-ivar-access, -Wno-receiver-forward-class: // Newfangled Objective-C style stuff we can't adopt while being // compatible with old versions of GNUstep and GCC. // -Wpadded: Points out normal and exepected compiler behaviour. // -Wfloat-equal: Would be a good warning if it ignored comparison to 0. // -Wswitch-enum: Extra-pedantic version of -Wswitch which complains about // missed enum cases even if there's a default case. // -Wcast-align: rarely a real problem. // -Wunused-exception-parameter: Who cares? // -Wmissing-noreturn: Bad analysis for a micro-optimization. // -Wunreachable-code: Lies. // -Wused-but-marked-unused: This is compatible with the semantics of unused. // -Wformat-nonliteral: Good warning, but we currently use DESC for format // strings rather a lot. // -Wconditional-uninitialized: Too many false positives, use static analyizer // instead. // -Wbad-function-cast: Warns about explicit casts of integers to other integer // types. That's what explict casts are for. // -Wassign-enum: Correctly warns about Cocoa's abuse of enum types for bit // masks, which we can't do anything sensible about. // -Wvla: We use variable-length arrays with reckless abandon. OO_EXTRA_WARNING_EXCEPTIONS = -Wno-pedantic -Wno-gnu -Wno-undef -Wno-objc-interface-ivars -Wno-direct-ivar-access -Wno-receiver-forward-class -Wno-padded -Wno-float-equal -Wno-switch-enum -Wno-cast-align -Wno-unused-exception-parameter -Wno-missing-noreturn -Wno-unreachable-code -Wno-used-but-marked-unused -Wno-format-nonliteral -Wno-conditional-uninitialized -Wno-bad-function-cast -Wno-assign-enum -Wno-vla -Wno-auto-import $(OO_VERSION_SPECIFIC_WARNING_EXCEPTIONS_$(XCODE_VERSION_MINOR)) // New warnings in Xcode 6.3: // -Rmodule-build: Very chatty remarks about the Clang module system that we // don't care about // -Wcstring-format-directive Warning when using %s in NSString formatting, // unfortunately this is triggered by NSParameterAssert OO_VERSION_SPECIFIC_WARNING_EXCEPTIONS_0630 = -Rno-module-build -Wno-cstring-format-directive // Things we don't want to break the build when they're introduced by devs on other platforms. OO_WARNING_NO_ERROR = -Wno-error=deprecated-declarations -Wno-error=semicolon-before-method-body OO_WARNING_FLAGS = $OO_LOTS_OF_WARNINGS $OO_WARNING_EXCEPTIONS $OO_EXTRA_WARNING_EXCEPTIONS $OO_WARNING_NO_ERROR $OO_ARCH_WARNING_FLAGS oolite-1.82/src/Cocoa/release-exports-64.exp000066400000000000000000000003621256642440500206520ustar00rootroot00000000000000# Symbols used by Oolite Leopard support.bundle _OOLogWithFunctionFileAndLine _OOLogWillDisplayMessagesInClass _OOLogIndent _OOLogOutdent .objc_class_name_OOJoystickManager _OBJC_CLASS_$_OOJoystickManager _OBJC_METACLASS_$_OOJoystickManager oolite-1.82/src/Core/000077500000000000000000000000001256642440500144265ustar00rootroot00000000000000oolite-1.82/src/Core/AI.h000066400000000000000000000053431256642440500150750ustar00rootroot00000000000000/* AI.h Core NPC behaviour/artificial intelligence class. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #import "OOWeakReference.h" #import "OOTypes.h" #define AI_THINK_INTERVAL 0.125 @class ShipEntity; @interface AI: OOWeakRefObject { @private id _owner; // OOWeakReference to the ShipEntity this is the AI for NSString *ownerDesc; // describes the object this is the AI for NSDictionary *stateMachine; NSString *stateMachineName; NSString *currentState; NSMutableSet *pendingMessages; NSMutableArray *aiStack; OOTimeAbsolute nextThinkTime; OOTimeDelta thinkTimeInterval; NSString *jsScript; } + (AI *) currentlyRunningAI; + (NSString *) currentlyRunningAIDescription; - (NSString *) name; - (NSString *) associatedJS; - (NSString *) state; - (void) setStateMachine:(NSString *)smName withJSScript:(NSString *)script; - (void) setState:(NSString *)stateName; - (void) setStateMachine:(NSString *)smName afterDelay:(NSTimeInterval)delay; - (void) setState:(NSString *)stateName afterDelay:(NSTimeInterval)delay; - (id) initWithStateMachine:(NSString *) smName andState:(NSString *) stateName; - (ShipEntity *)owner; - (void) setOwner:(ShipEntity *)ship; - (void) preserveCurrentStateMachine; - (void) restorePreviousStateMachine; - (BOOL) hasSuspendedStateMachines; - (void) exitStateMachineWithMessage:(NSString *)message; - (NSUInteger) stackDepth; // Immediately handle a message. This is the core dispatcher. DebugContext is a textual hint for diagnostics. - (void) reactToMessage:(NSString *) message context:(NSString *)debugContext; - (void) takeAction:(NSString *) action; - (void) think; - (void) message:(NSString *) ms; - (void) dropMessage:(NSString *) ms; - (NSSet *) pendingMessages; - (void) debugDumpPendingMessages; - (void) setNextThinkTime:(OOTimeAbsolute) ntt; - (OOTimeAbsolute) nextThinkTime; - (void) setThinkTimeInterval:(OOTimeDelta) tti; - (OOTimeDelta) thinkTimeInterval; - (void) clearStack; - (void) clearAllData; - (void)dumpState; @end oolite-1.82/src/Core/AI.m000066400000000000000000000630141256642440500151010ustar00rootroot00000000000000/* AI.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "AI.h" #import "ResourceManager.h" #import "OOStringParsing.h" #import "OOWeakReference.h" #import "OOCacheManager.h" #import "OOCollectionExtractors.h" #import "OOPListParsing.h" #import "ShipEntity.h" #import "ShipEntityAI.h" enum { kRecursionLimiter = 32, // reactToMethod: recursion kStackLimiter = 32 // setAITo: stack overflow }; typedef struct { AI *ai; SEL selector; id parameter; } OOAIDeferredCallTrampolineInfo; static AI *sCurrentlyRunningAI = nil; @interface AI (OOPrivate) // Wrapper for performSelector:withObject:afterDelay: to catch/fix bugs. - (void) performDeferredCall:(SEL)selector withObject:(id)object afterDelay:(NSTimeInterval)delay; + (void) deferredCallTrampolineWithInfo:(NSValue *)info; - (void) refreshOwnerDesc; // Set state machine and state without side effects. - (void) directSetStateMachine:(NSDictionary *)newSM name:(NSString *)name; - (void) directSetState:(NSString *)state; // Loading/whitelisting - (NSDictionary *) loadStateMachine:(NSString *)smName jsName:(NSString *)script; - (NSDictionary *) cleanHandlers:(NSDictionary *)handlers forState:(NSString *)stateKey stateMachine:(NSString *)smName; - (NSArray *) cleanActions:(NSArray *)actions forHandler:(NSString *)handlerKey state:(NSString *)stateKey stateMachine:(NSString *)smName; @end #if DEBUG_GRAPHVIZ extern void GenerateGraphVizForAIStateMachine(NSDictionary *stateMachine, NSString *name); #endif @interface OOPreservedAIStateMachine: NSObject { @private NSDictionary *_stateMachine; NSString *_name; NSString *_state; NSMutableSet *_pendingMessages; NSString *_jsScript; } - (id) initWithStateMachine:(NSDictionary *)stateMachine name:(NSString *)name state:(NSString *)state pendingMessages:(NSSet *)pendingMessages jsScript:(NSString *)script; - (NSDictionary *) stateMachine; - (NSString *) name; - (NSString *) state; - (NSSet *) pendingMessages; - (NSString *) jsScript; @end @implementation AI + (AI *) currentlyRunningAI { return sCurrentlyRunningAI; } + (NSString *) currentlyRunningAIDescription { if (sCurrentlyRunningAI != nil) { return [NSString stringWithFormat:@"%@ in state %@", [sCurrentlyRunningAI name], [sCurrentlyRunningAI state]]; } else { return @""; } } - (id) init { if ((self = [super init])) { nextThinkTime = INFINITY; // don't think for a while thinkTimeInterval = AI_THINK_INTERVAL; stateMachineName = @""; // no initial brain } return self; } - (id) initWithStateMachine:(NSString *)smName andState:(NSString *)stateName { if ((self = [self init])) { if (smName != nil) [self setStateMachine:smName withJSScript:@"oolite-nullAI.js"]; if (stateName != nil) currentState = [stateName retain]; } return self; } - (void) dealloc { if (sCurrentlyRunningAI == self) { sCurrentlyRunningAI = nil; } DESTROY(_owner); DESTROY(ownerDesc); DESTROY(aiStack); DESTROY(stateMachine); DESTROY(stateMachineName); DESTROY(currentState); DESTROY(pendingMessages); DESTROY(jsScript); [super dealloc]; } - (NSString *) descriptionComponents { return [NSString stringWithFormat:@"\"%@\" in state: \"%@\" for %@", stateMachineName, currentState, ownerDesc]; } - (NSString *) shortDescriptionComponents { return [NSString stringWithFormat:@"%@:%@ / %@", stateMachineName, currentState, [stateMachine objectForKey:@"jsScript"]]; } - (ShipEntity *)owner { ShipEntity *owner = [_owner weakRefUnderlyingObject]; if (owner == nil) { [_owner release]; _owner = nil; } return owner; } - (void) setOwner:(ShipEntity *)ship { [_owner release]; _owner = [ship weakRetain]; [self refreshOwnerDesc]; } - (void) reportStackOverflow { if (OOLogWillDisplayMessagesInClass(@"ai.error.stackOverflow")) { BOOL stackDump = OOLogWillDisplayMessagesInClass(@"ai.error.stackOverflow.dump"); NSString *trailer = stackDump ? @" -- stack:" : @"."; OOLogERR(@"ai.error.stackOverflow", @"AI stack overflow for %@ in %@: %@%@\n", [_owner shortDescription], stateMachineName, currentState, trailer); if (stackDump) { OOLogIndent(); NSUInteger count = [aiStack count]; while (count--) { OOPreservedAIStateMachine *preservedMachine = [aiStack objectAtIndex:count]; OOLog(@"ai.error.stackOverflow.dump", @"%3lu: %@: %@", count, [preservedMachine name], [preservedMachine state]); } OOLogOutdent(); } } } - (void) preserveCurrentStateMachine { if (stateMachine == nil) return; if (aiStack == nil) { aiStack = [[NSMutableArray alloc] init]; } if ([aiStack count] >= kStackLimiter) { [self reportStackOverflow]; [NSException raise:@"OoliteException" format:@"AI stack overflow for %@", _owner]; } OOPreservedAIStateMachine *preservedMachine = [[OOPreservedAIStateMachine alloc] initWithStateMachine:stateMachine name:stateMachineName state:currentState pendingMessages:pendingMessages jsScript:[stateMachine objectForKey:@"jsScript"]]; #ifndef NDEBUG if ([[self owner] reportAIMessages]) OOLog(@"ai.stack.push", @"Pushing state machine for %@", self); #endif [aiStack addObject:preservedMachine]; // PUSH [preservedMachine release]; } - (void) restorePreviousStateMachine { if ([aiStack count] == 0) return; OOPreservedAIStateMachine *preservedMachine = [aiStack lastObject]; #ifndef NDEBUG if ([[self owner] reportAIMessages]) OOLog(@"ai.stack.pop", @"Popping previous state machine for %@", self); #endif [self directSetStateMachine:[preservedMachine stateMachine] name:[preservedMachine name]]; [self directSetState:[preservedMachine state]]; // restore JS script [[self owner] setAIScript:[preservedMachine jsScript]]; [pendingMessages release]; pendingMessages = [[preservedMachine pendingMessages] mutableCopy]; // restore a MUTABLE set [aiStack removeLastObject]; // POP } - (BOOL) hasSuspendedStateMachines { return [aiStack count] != 0; } - (void) exitStateMachineWithMessage:(NSString *)message { if ([aiStack count] != 0) { [self restorePreviousStateMachine]; if (message == nil) message = @"RESTARTED"; [self reactToMessage:message context:@"suspended AI restart"]; } } - (void) setStateMachine:(NSString *)smName withJSScript:(NSString *)script { NSDictionary *newSM = [self loadStateMachine:smName jsName:script]; if (newSM) { [self preserveCurrentStateMachine]; [self directSetStateMachine:newSM name:smName]; [self directSetState:@"GLOBAL"]; nextThinkTime = 0.0; // think at next tick /* CRASH in objc_msgSend, apparently on [self reactToMessage:@"ENTER"] (1.69, OS X/x86). Analysis: self corrupted. We're being called by __NSFireDelayedPerform, which doesn't go through -[NSObject performSelector:withObject:], suggesting it's using IMP caching. An invalid self is therefore possible. Attempted fix: new delayed dispatch with trampoline, see -[AI setStateMachine:afterDelay:]. -- Ahruman, 20070706 */ [self reactToMessage:@"ENTER" context:@"changing AI"]; // refresh name [self refreshOwnerDesc]; } } - (void) setState:(NSString *) stateName { if ([stateMachine objectForKey:stateName]) { /* CRASH in objc_msgSend, apparently on [self reactToMessage:@"EXIT"] (1.69, OS X/x86). Analysis: self corrupted. We're being called by __NSFireDelayedPerform, which doesn't go through -[NSObject performSelector:withObject:], suggesting it's using IMP caching. An invalid self is therefore possible. Attempted fix: new delayed dispatch with trampoline, see -[AI setState:afterDelay:]. -- Ahruman, 20070706 */ [self reactToMessage:@"EXIT" context:@"changing state"]; [self directSetState:stateName]; [self reactToMessage:@"ENTER" context:@"changing state"]; } } - (void) setStateMachine:(NSString *)smName afterDelay:(NSTimeInterval)delay { [self performDeferredCall:@selector(setStateMachine:) withObject:smName afterDelay:delay]; } - (void) setState:(NSString *)stateName afterDelay:(NSTimeInterval)delay { [self performDeferredCall:@selector(setState:) withObject:stateName afterDelay:delay]; } - (NSString *) name { return [[stateMachineName retain] autorelease]; } - (NSString *) associatedJS { return [stateMachine objectForKey:@"jsScript"]; } - (NSString *) state { return [[currentState retain] autorelease]; } - (NSUInteger) stackDepth { return [aiStack count]; } #ifndef NDEBUG typedef struct AIStackElement AIStackElement; struct AIStackElement { AIStackElement *back; ShipEntity *owner; NSString *aiName; NSString *state; NSString *message; NSString *context; }; static AIStackElement *sStack = NULL; #endif - (void) reactToMessage:(NSString *) message context:(NSString *)debugContext { unsigned i; NSArray *actions = nil; NSDictionary *messagesForState = nil; ShipEntity *owner = [self owner]; static unsigned recursionLimiter = 0; AI *previousRunning = sCurrentlyRunningAI; /* CRASH in _freedHandler when called via -setState: __NSFireDelayedPerform (1.69, OS X/x86). Analysis: owner invalid. Fix: make owner an OOWeakReference. -- Ahruman, 20070706 */ if (message == nil || owner == nil || [owner universalID] == NO_TARGET) return; #ifndef NDEBUG // Push debug stack frame. if (debugContext == nil) debugContext = @"unspecified"; AIStackElement stackElement = { .back = sStack, .owner = owner, .aiName = [[stateMachineName retain] autorelease], .state = [[currentState retain] autorelease], .message = message, .context = debugContext }; sStack = &stackElement; #endif /* CRASH when calling reactToMessage: FOO in state FOO causes infinite recursion. (NB: there are other ways of triggering this.) FIX: recursion limiter. Alternative is to explicitly catch this case in takeAction:, but that could potentially miss indirect recursion via scripts. */ if (recursionLimiter > kRecursionLimiter) { OOLogERR(@"ai.error.recursion", @"AI dispatch: hit stack depth limit in AI %@, state %@ handling message %@ in context \"%@\", aborting.", stateMachineName, currentState, message, debugContext); #ifndef NDEBUG AIStackElement *stack = sStack; unsigned depth = 0; while (stack != NULL) { OOLog(@"ai.error.recursion.stackTrace", @"%4u %@ - %@:%@.%@ (%@)", depth++, [stack->owner shortDescription], stack->aiName, stack->state, stack->message, stack->context); stack = stack->back; } // unwind. if (sStack != NULL) sStack = sStack->back; #endif return; } messagesForState = [stateMachine objectForKey:currentState]; if (messagesForState == nil) return; #ifndef NDEBUG if (currentState != nil && ![message isEqual:@"UPDATE"] && [owner reportAIMessages]) { OOLog(@"ai.message.receive", @"AI %@ for %@ in state '%@' receives message '%@'. Context: %@, stack depth: %u", stateMachineName, ownerDesc, currentState, message, debugContext, recursionLimiter); } #endif actions = [[[messagesForState objectForKey:message] copy] autorelease]; sCurrentlyRunningAI = self; if ([actions count] > 0) { ++recursionLimiter; @try { for (i = 0; i < [actions count]; i++) { [self takeAction:[actions objectAtIndex:i]]; } } @catch (NSException *exception) { OOLog(kOOLogException, @"Squashing exception %@:%@ in AI handler %@:%@.%@", [exception name], [exception reason], stateMachineName, currentState, message); } --recursionLimiter; } else { if (currentState != nil) { if ([owner respondsToSelector:@selector(interpretAIMessage:)]) { [owner performSelector:@selector(interpretAIMessage:) withObject:message]; } } } sCurrentlyRunningAI = previousRunning; #ifndef NDEBUG // Unwind stack. if (sStack != NULL) sStack = sStack->back; #endif } - (void) takeAction:(NSString *)action { ShipEntity *owner = [self owner]; #ifndef NDEBUG BOOL report = [owner reportAIMessages]; if (report) { OOLog(@"ai.takeAction", @"%@ to take action %@", ownerDesc, action); OOLogIndent(); } #endif NSArray *tokens = ScanTokensFromString(action); NSUInteger tokenCount = [tokens count]; if (tokenCount != 0) { NSString *selectorStr = [tokens objectAtIndex:0]; if (owner != nil) { NSString *dataString = nil; if (tokenCount == 2) { dataString = [tokens objectAtIndex:1]; } else if ([tokens count] > 1) { dataString = [[tokens subarrayWithRange:NSMakeRange(1, tokenCount - 1)] componentsJoinedByString:@" "]; } SEL selector = NSSelectorFromString(selectorStr); if ([owner respondsToSelector:selector]) { if (dataString != nil) [owner performSelector:selector withObject:dataString]; else [owner performSelector:selector]; } else { OOLogERR(@"ai.takeAction.badSelector", @"in AI %@ in state %@: %@ does not respond to %@", stateMachineName, currentState, ownerDesc, selectorStr); } } else { OOLog(@"ai.takeAction.orphaned", @"***** AI %@, trying to perform %@, is orphaned (no owner)", stateMachineName, selectorStr); } } else { #ifndef NDEBUG if (report) OOLog(@"ai.takeAction.noAction", @"DEBUG: - no action '%@'", action); #endif } #ifndef NDEBUG if (report) { OOLogOutdent(); } #endif } - (void) think { NSArray *ms_list = nil; unsigned i; if ([[self owner] universalID] == NO_TARGET || stateMachine == nil) return; // don't think until launched [self reactToMessage:@"UPDATE" context:@"periodic update"]; if ([pendingMessages count] > 0) { ms_list = [pendingMessages allObjects]; [pendingMessages removeAllObjects]; } if (ms_list != nil) { for (i = 0; i < [ms_list count]; i++) { [self reactToMessage:[ms_list objectAtIndex:i] context:@"handling deferred message"]; } } } - (void) message:(NSString *)ms { if ([[self owner] universalID] == NO_TARGET) return; // don't think until launched if (EXPECT_NOT([pendingMessages count] > 32)) { // Generate the error, but don't crash Oolite! Fixes bug #18055 - Pending message overflow for thargoids, -> crash ! OOLogERR(@"ai.message.failed.overflow", @"AI message \"%@\" received by '%@' AI while pending messages stack full; message discarded. Pending messages:\n%@", ms, ownerDesc, pendingMessages); } else { if (pendingMessages == nil) { pendingMessages = [[NSMutableSet alloc] init]; } [pendingMessages addObject:ms]; } } - (void) dropMessage:(NSString *)ms { [pendingMessages removeObject:ms]; } - (NSSet *) pendingMessages { if (pendingMessages != nil) { return [[pendingMessages copy] autorelease]; } else { return [NSSet set]; } } - (void) debugDumpPendingMessages { NSArray *sortedMessages = nil; NSString *displayMessages = nil; if ([pendingMessages count] > 0) { sortedMessages = [[pendingMessages allObjects] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; displayMessages = [sortedMessages componentsJoinedByString:@", "]; } else { displayMessages = @"none"; } OOLog(@"ai.debug.pendingMessages", @"Pending messages for AI %@: %@", [self descriptionComponents], displayMessages); } - (void) setNextThinkTime:(OOTimeAbsolute) ntt { nextThinkTime = ntt; } - (OOTimeAbsolute) nextThinkTime { if (!stateMachine) return INFINITY; return nextThinkTime; } - (void) setThinkTimeInterval:(OOTimeDelta) tti { thinkTimeInterval = tti; } - (OOTimeDelta) thinkTimeInterval { return thinkTimeInterval; } - (void) clearStack { [aiStack removeAllObjects]; } - (void) clearAllData { [aiStack removeAllObjects]; [pendingMessages removeAllObjects]; nextThinkTime += 36000.0; // should dealloc in under ten hours! } - (void)dumpState { OOLog(@"dumpState.ai", @"State machine name: %@", stateMachineName); OOLog(@"dumpState.ai", @"Current state: %@", currentState); OOLog(@"dumpState.ai", @"Next think time: %g", nextThinkTime); OOLog(@"dumpState.ai", @"Next think interval: %g", thinkTimeInterval); } @end /* This is an attempt to fix the bugs referred to above regarding calls from __NSFireDelayedPerform with a corrupt self. I'm not certain whether this will fix the issue or merely cause a less weird crash in +deferredCallTrampolineWithInfo:. -- Ahruman 20070706 */ @implementation AI (OOPrivate) - (void)performDeferredCall:(SEL)selector withObject:(id)object afterDelay:(NSTimeInterval)delay { OOAIDeferredCallTrampolineInfo infoStruct; NSValue *info = nil; if (selector != NULL) { infoStruct.ai = [self retain]; infoStruct.selector = selector; infoStruct.parameter = object; info = [[NSValue alloc] initWithBytes:&infoStruct objCType:@encode(OOAIDeferredCallTrampolineInfo)]; [[AI class] performSelector:@selector(deferredCallTrampolineWithInfo:) withObject:info afterDelay:delay]; [info release]; } } + (void)deferredCallTrampolineWithInfo:(NSValue *)info { OOAIDeferredCallTrampolineInfo infoStruct; if (info != nil) { assert(strcmp([info objCType], @encode(OOAIDeferredCallTrampolineInfo)) == 0); [info getValue:&infoStruct]; [infoStruct.ai performSelector:infoStruct.selector withObject:infoStruct.parameter]; [infoStruct.ai release]; [infoStruct.parameter release]; } } - (void)refreshOwnerDesc { ShipEntity *owner = [self owner]; [ownerDesc release]; if ([owner isPlayer]) { ownerDesc = @"player autopilot"; } else if (owner != nil) { ownerDesc = [[NSString alloc] initWithFormat:@"%@ %d", [owner name], [owner universalID]]; } else { ownerDesc = @"no owner"; } } - (void) directSetStateMachine:(NSDictionary *)newSM name:(NSString *)name { if (stateMachine != newSM) { [stateMachine release]; stateMachine = [newSM copy]; } if (stateMachineName != name) { [stateMachineName release]; stateMachineName = [name copy]; } } - (void) directSetState:(NSString *)state { if (currentState != state) { [currentState release]; currentState = [state copy]; } } - (NSDictionary *) loadStateMachine:(NSString *)smName jsName:(NSString *)script { NSDictionary *newSM = nil; NSMutableDictionary *cleanSM = nil; OOCacheManager *cacheMgr = [OOCacheManager sharedCache]; NSEnumerator *stateEnum = nil; NSString *stateKey = nil; NSDictionary *stateHandlers = nil; NSAutoreleasePool *pool = nil; if (![smName isEqualToString:@"nullAI.plist"]) { // don't cache nullAI since they're different depending on associated JS AI newSM = [cacheMgr objectForKey:smName inCache:@"AIs"]; if (newSM != nil && ![newSM isKindOfClass:[NSDictionary class]]) return nil; // catches use of @"nil" to indicate no AI found. } if (newSM == nil) { pool = [[NSAutoreleasePool alloc] init]; OOLog(@"ai.load", @"Loading and sanitizing AI \"%@\"", smName); OOLogPushIndent(); OOLogIndentIf(@"ai.load"); @try { // Load state machine and validate against whitelist. NSString *aiPath = [ResourceManager pathForFileNamed:smName inFolder:@"AIs"]; if (aiPath != nil) { newSM = OODictionaryFromFile(aiPath); } if (newSM == nil) { [cacheMgr setObject:@"nil" forKey:smName inCache:@"AIs"]; NSString *fromString = @""; if ([self state] != nil) { fromString = [NSString stringWithFormat:@" from %@:%@", [self name], [self state]]; } OOLog(@"ai.load.failed.unknownAI", @"Can't switch AI for %@%@ to \"%@\" - could not load file.", [[self owner] shortDescription], fromString, smName); return nil; } cleanSM = [NSMutableDictionary dictionaryWithCapacity:[newSM count]]; for (stateEnum = [newSM keyEnumerator]; (stateKey = [stateEnum nextObject]); ) { stateHandlers = [newSM objectForKey:stateKey]; if (![stateHandlers isKindOfClass:[NSDictionary class]]) { OOLogWARN(@"ai.invalidFormat.state", @"State \"%@\" in AI \"%@\" is not a dictionary, ignoring.", stateKey, smName); continue; } stateHandlers = [self cleanHandlers:stateHandlers forState:stateKey stateMachine:smName]; [cleanSM setObject:stateHandlers forKey:stateKey]; } [cleanSM setObject:script forKey:@"jsScript"]; // Make immutable. newSM = [[cleanSM copy] autorelease]; #if DEBUG_GRAPHVIZ if ([[NSUserDefaults standardUserDefaults] boolForKey:@"generate-ai-graphviz"]) { GenerateGraphVizForAIStateMachine(newSM, smName); } #endif // Cache. [cacheMgr setObject:newSM forKey:smName inCache:@"AIs"]; } @finally { OOLogPopIndent(); } [newSM retain]; [pool release]; [newSM autorelease]; } return newSM; } - (NSDictionary *) cleanHandlers:(NSDictionary *)handlers forState:(NSString *)stateKey stateMachine:(NSString *)smName { NSEnumerator *handlerEnum = nil; NSString *handlerKey = nil; NSArray *handlerActions = nil; NSMutableDictionary *result = nil; result = [NSMutableDictionary dictionaryWithCapacity:[handlers count]]; for (handlerEnum = [handlers keyEnumerator]; (handlerKey = [handlerEnum nextObject]); ) { handlerActions = [handlers objectForKey:handlerKey]; if (![handlerActions isKindOfClass:[NSArray class]]) { OOLogWARN(@"ai.invalidFormat.handler", @"Handler \"%@\" for state \"%@\" in AI \"%@\" is not an array, ignoring.", handlerKey, stateKey, smName); continue; } handlerActions = [self cleanActions:handlerActions forHandler:handlerKey state:stateKey stateMachine:smName]; [result setObject:handlerActions forKey:handlerKey]; } // Return immutable copy. return [[result copy] autorelease]; } - (NSArray *) cleanActions:(NSArray *)actions forHandler:(NSString *)handlerKey state:(NSString *)stateKey stateMachine:(NSString *)smName { NSEnumerator *actionEnum = nil; NSString *action = nil; NSRange spaceRange; NSString *selector = nil; id aliasedSelector = nil; NSMutableArray *result = nil; static NSSet *whitelist = nil; static NSDictionary *aliases = nil; NSArray *whitelistArray1 = nil; NSArray *whitelistArray2 = nil; if (whitelist == nil) { whitelistArray1 = [[ResourceManager whitelistDictionary] oo_arrayForKey:@"ai_methods"]; if (whitelistArray1 == nil) whitelistArray1 = [NSArray array]; whitelistArray2 = [[ResourceManager whitelistDictionary] oo_arrayForKey:@"ai_and_action_methods"]; if (whitelistArray2 != nil) whitelistArray1 = [whitelistArray1 arrayByAddingObjectsFromArray:whitelistArray2]; whitelist = [[NSSet alloc] initWithArray:whitelistArray1]; aliases = [[[ResourceManager whitelistDictionary] oo_dictionaryForKey:@"ai_method_aliases"] retain]; } result = [NSMutableArray arrayWithCapacity:[actions count]]; for (actionEnum = [actions objectEnumerator]; (action = [actionEnum nextObject]); ) { if (![action isKindOfClass:[NSString class]]) { OOLogWARN(@"ai.invalidFormat.action", @"An action in handler \"%@\" for state \"%@\" in AI \"%@\" is not a string, ignoring.", handlerKey, stateKey, smName); continue; } // Trim spaces from beginning and end. action = [action stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; // Cut off parameters. spaceRange = [action rangeOfString:@" "]; if (spaceRange.location == NSNotFound) selector = action; else selector = [action substringToIndex:spaceRange.location]; // Look in alias table. aliasedSelector = [aliases objectForKey:selector]; if (aliasedSelector != nil) { if ([aliasedSelector isKindOfClass:[NSString class]]) { // Change selector and action to use real method name. selector = aliasedSelector; if (spaceRange.location == NSNotFound) action = aliasedSelector; else action = [aliasedSelector stringByAppendingString:[action substringFromIndex:spaceRange.location]]; } else if ([aliasedSelector isKindOfClass:[NSArray class]] && [aliasedSelector count] != 0) { // Alias is complete expression, pretokenized in anticipation of a tokenized future. action = [aliasedSelector componentsJoinedByString:@" "]; selector = [[aliasedSelector objectAtIndex:0] description]; } } // Check for selector in whitelist. if (![whitelist containsObject:selector]) { OOLog(@"ai.unpermittedMethod", @"Handler \"%@\" for state \"%@\" in AI \"%@\" uses \"%@\", which is not a permitted AI method.", handlerKey, stateKey, smName, selector); continue; } [result addObject:action]; } // Return immutable copy. return [[result copy] autorelease]; } @end @implementation OOPreservedAIStateMachine - (id) initWithStateMachine:(NSDictionary *)stateMachine name:(NSString *)name state:(NSString *)state pendingMessages:(NSSet *)pendingMessages jsScript:(NSString *)script { if ((self = [super init])) { _stateMachine = [stateMachine copy]; _name = [name copy]; _state = [state copy]; _pendingMessages = [pendingMessages copy]; _jsScript = [script copy]; } return self; } - (void) dealloc { [_stateMachine autorelease]; [_name autorelease]; [_state autorelease]; [_pendingMessages autorelease]; [_jsScript autorelease]; [super dealloc]; } - (NSDictionary *) stateMachine { return _stateMachine; } - (NSString *) name { return _name; } - (NSString *) state { return _state; } - (NSSet *) pendingMessages { return _pendingMessages; } - (NSString *) jsScript { return _jsScript; } @end oolite-1.82/src/Core/AIGraphViz.m000066400000000000000000000265701256642440500165620ustar00rootroot00000000000000/* AIGraphViz.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #if DEBUG_GRAPHVIZ #import "OOStringParsing.h" #import "ResourceManager.h" #import "OOCollectionExtractors.h" // Generate and track unique identifiers for state-handler pairs. static NSString *HandlerToken(NSString *state, NSString *handler, NSMutableDictionary *handlerKeys, NSMutableSet *uniqueSet); static void HandleOneCommand(NSMutableString *graphViz, NSString *stateKey, NSString *handlerKey, NSMutableDictionary *handlerKeys, NSArray *handlerCommands, NSUInteger commandIter, NSUInteger commandCount, NSMutableSet *specialNodes, NSMutableSet *uniqueSet, BOOL *haveSetOrSwichAI); static void AddSimpleSpecialNodeLink(NSMutableString *graphViz, NSString *handlerToken, NSString *name, NSString *shape, NSString *color, NSMutableSet *specialNodes); static void AddExitAINode(NSMutableString *graphViz, NSString *handlerToken, NSString *message, NSMutableSet *specialNodes); static void AddChangeAINode(NSMutableString *graphViz, NSString *handlerToken, NSString *method, NSArray *components, NSArray *handlerCommands, NSUInteger commandIter, NSUInteger commandCount, NSMutableSet *specialNodes); void GenerateGraphVizForAIStateMachine(NSDictionary *stateMachine, NSString *smName) { NSMutableSet *uniqueSet = [NSMutableSet set]; NSMutableDictionary *handlerKeys = [NSMutableDictionary dictionary]; NSMutableString *graphViz = [NSMutableString stringWithFormat: @"digraph ai_flow\n{\n" "\tgraph [charset=\"UTF-8\", label=\"%@ transition diagram\", labelloc=t, labeljust=l rankdir=LR compound=true nodesep=0.1 ranksep=2.5 fontname=Helvetica]\n" "\tedge [arrowhead=normal]\n" "\tnode [shape=box height=0.2 width=3.5 fontname=Helvetica color=\"#808080\"]\n\t\n" "\tspecial_start [shape=ellipse color=\"#0000C0\" label=\"Start\"]\n\tspecial_start -> %@ [lhead=\"cluster_GLOBAL\" color=\"#0000A0\"]\n", EscapedGraphVizString(smName), HandlerToken(@"GLOBAL", @"ENTER", handlerKeys, uniqueSet)]; NSEnumerator *stateKeyEnum = [stateMachine keyEnumerator]; NSString *stateKey = nil; NSMutableSet *specialNodes = [NSMutableSet set]; while ((stateKey = [stateKeyEnum nextObject])) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [graphViz appendFormat:@"\t\n\tsubgraph cluster_%@\n\t{\n\t\tlabel=\"%@\"\n", stateKey, EscapedGraphVizString(stateKey)]; NSDictionary *state = [stateMachine oo_dictionaryForKey:stateKey]; NSEnumerator *handlerKeyEnum = [state keyEnumerator]; NSString *handlerKey = nil; while ((handlerKey = [handlerKeyEnum nextObject])) { [graphViz appendFormat:@"\t\t%@ [label=\"%@\"]\n", HandlerToken(stateKey, handlerKey, handlerKeys, uniqueSet), EscapedGraphVizString(handlerKey)]; } // Ensure there is an ENTER handler for arrows to point at. if ([state objectForKey:@"ENTER"] == nil) { [graphViz appendFormat:@"\t\t%@ [label=\"ENTER (implicit)\"] // No ENTER handler in file, but it's still the target of any incoming transitions.\n", HandlerToken(stateKey, @"ENTER", handlerKeys, uniqueSet)]; } [graphViz appendString:@"\t}\n"]; // Go through each handler looking for interesting methods. handlerKeyEnum = [state keyEnumerator]; while ((handlerKey = [handlerKeyEnum nextObject])) { NSArray *handlerCommands = [state oo_arrayForKey:handlerKey]; NSUInteger commandIter, commandCount = [handlerCommands count]; BOOL haveSetOrSwichAI = NO; for (commandIter = 0; commandIter < commandCount; commandIter++) { HandleOneCommand(graphViz, stateKey, handlerKey, handlerKeys, handlerCommands, commandIter, commandCount, specialNodes, uniqueSet, &haveSetOrSwichAI); } } [pool release]; } if ([specialNodes count] != 0) { [graphViz appendString:@"\t\n"]; NSEnumerator *specialEnum = [specialNodes objectEnumerator]; NSString *special = nil; while ((special = [specialEnum nextObject])) { [graphViz appendString:special]; } } [graphViz appendString:@"}\n"]; [ResourceManager writeDiagnosticString:graphViz toFileNamed:[NSString stringWithFormat:@"AI Dumps/%@.dot", smName]]; } static NSString *HandlerToken(NSString *state, NSString *handler, NSMutableDictionary *handlerKeys, NSMutableSet *uniqueSet) { NSString *result = [[handlerKeys oo_dictionaryForKey:state] oo_stringForKey:handler]; if (result == nil) { result = [NSString stringWithFormat:@"%@_h_%@", state, handler]; result = GraphVizTokenString(result, uniqueSet); NSMutableDictionary *stateDict = [handlerKeys objectForKey:state]; if (stateDict == nil) { stateDict = [NSMutableDictionary dictionary]; [handlerKeys setObject:stateDict forKey:state]; } [stateDict setObject:result forKey:handler]; } return result; } static void HandleOneCommand(NSMutableString *graphViz, NSString *stateKey, NSString *handlerKey, NSMutableDictionary *handlerKeys, NSArray *handlerCommands, NSUInteger commandIter, NSUInteger commandCount, NSMutableSet *specialNodes, NSMutableSet *uniqueSet, BOOL *haveSetOrSwichAI) { NSString *command = [handlerCommands oo_stringAtIndex:commandIter]; if (EXPECT_NOT(command == nil)) return; NSArray *components = ScanTokensFromString(command); NSString *method = [components objectAtIndex:0]; NSString *handlerToken = HandlerToken(stateKey, handlerKey, handlerKeys, uniqueSet); if (!*haveSetOrSwichAI && [method isEqualToString:@"setStateTo:"]) { if ([components count] > 1) { NSString *targetState = [components objectAtIndex:1]; NSString *targetLabel = HandlerToken(targetState, @"ENTER", handlerKeys, uniqueSet); BOOL constraint = YES; if ([targetState isEqualToString:stateKey]) constraint = NO; else if ([targetState isEqualToString:@"GLOBAL"]) constraint = NO; [graphViz appendFormat:@"\t%@ -> %@ [lhead=cluster_%@%@]\n", handlerToken, targetLabel, targetState, constraint ? @"" : @" constraint=false"]; } else { [specialNodes addObject:@"\tspecial_brokenSetStateTo [label=\"Broken setStateTo: command!\\n(No target state specified.)\" color=\"#C00000\" shape=diamond]\n"]; [graphViz appendFormat:@"\t%@ -> special_brokenSetStateTo [color=\"#C00000\"]\n", handlerToken]; } } else if ([method isEqualToString:@"becomeExplosion"]) { AddSimpleSpecialNodeLink(graphViz, handlerToken, @"becomeExplosion", @"diamond", @"804000", specialNodes); } else if ([method isEqualToString:@"becomeEnergyBlast"]) { AddSimpleSpecialNodeLink(graphViz, handlerToken, @"becomeEnergyBlast", @"diamond", @"804000", specialNodes); } else if ([method isEqualToString:@"landOnPlanet"]) { AddSimpleSpecialNodeLink(graphViz, handlerToken, @"landOnPlanet", @"diamond", @"008040", specialNodes); } else if ([method isEqualToString:@"performHyperSpaceExit"]) { AddSimpleSpecialNodeLink(graphViz, handlerToken, @"performHyperSpaceExit", @"box", @"008080", specialNodes); } else if ([method isEqualToString:@"performHyperSpaceExitWithoutReplacing"]) { AddSimpleSpecialNodeLink(graphViz, handlerToken, @"performHyperSpaceExitWithoutReplacing", @"box", @"008080", specialNodes); } else if ([method isEqualToString:@"enterTargetWormhole"]) { AddSimpleSpecialNodeLink(graphViz, handlerToken, @"enterTargetWormhole", @"box", @"008080", specialNodes); } else if ([method isEqualToString:@"becomeUncontrolledThargon"]) { AddSimpleSpecialNodeLink(graphViz, handlerToken, @"becomeUncontrolledThargon", @"ellipse", @"804000", specialNodes); } else if ([method isEqualToString:@"exitAIWithMessage:"]) { NSString *message = ([components count] > 1) ? [components objectAtIndex:1] : nil; AddExitAINode(graphViz, handlerToken, message, specialNodes); } else if ([method isEqualToString:@"setAITo:"] || [method isEqualToString:@"switchAITo:"]) { *haveSetOrSwichAI = YES; AddChangeAINode(graphViz, handlerToken, method, components, handlerCommands, commandIter, commandCount, specialNodes); } } static void AddSimpleSpecialNodeLink(NSMutableString *graphViz, NSString *handlerToken, NSString *name, NSString *shape, NSString *color, NSMutableSet *specialNodes) { NSString *identifier = GraphVizTokenString([@"special_" stringByAppendingString:name], nil); NSString *declaration = [NSString stringWithFormat:@"\t%@ [label=\"%@\" color=\"#%@\" shape=%@]\n", identifier, EscapedGraphVizString(name), color, shape]; [specialNodes addObject:declaration]; [graphViz appendFormat:@"\t%@ -> %@ [color=\"#%@\"]\n", handlerToken, identifier, color]; } static void AddExitAINode(NSMutableString *graphViz, NSString *handlerToken, NSString *message, NSMutableSet *specialNodes) { NSString *token = nil; NSString *label = nil; if ([message isEqualToString:@"RESTARTED"] || [message length] == 0) { token = @"exitAI"; label = @"exitAI"; } else { token = GraphVizTokenString([@"exitAI_" stringByAppendingString:message], nil); label = EscapedGraphVizString([@"exitAIWithMessage:\n" stringByAppendingString:message]); } [specialNodes addObject:[NSString stringWithFormat:@"\t%@ [label=\"%@\" color=\"#0000A0\" shape=ellipse]\n", token, label]]; [graphViz appendFormat:@"\t%@ -> %@ [color=\"#0000C0\"]\n", handlerToken, token]; } static void AddChangeAINode(NSMutableString *graphViz, NSString *handlerToken, NSString *method, NSArray *components, NSArray *handlerCommands, NSUInteger commandIter, NSUInteger commandCount, NSMutableSet *specialNodes) { NSString *methodTag = [method substringToIndex:[method length] - 3]; // delete "To:". if ([components count] > 1) { NSString *targetAI = [components objectAtIndex:1]; NSString *token = [NSString stringWithFormat:@"%@_%@", methodTag, targetAI]; NSString *label = [NSString stringWithFormat:@"%@\n%@", method, targetAI]; // Look through remaining commands for a setStateTo:, which applies to the new AI. NSString *targetState = nil; NSUInteger j = commandIter; for (; j < commandCount; j++) { NSString *command = [handlerCommands oo_stringAtIndex:j]; if ([command hasPrefix:@"setStateTo:"]) { NSArray *components = ScanTokensFromString(command); if ([components count] > 1) targetState = [components objectAtIndex:1]; } } if (targetState != nil) { token = [NSString stringWithFormat:@"%@_%@", token, targetState]; label = [NSString stringWithFormat:@"%@ (%@)", label, targetState]; } token = GraphVizTokenString(token, nil); label = EscapedGraphVizString(label); [specialNodes addObject:[NSString stringWithFormat:@"\t%@ [label=\"%@\" color=\"#408000\" shape=ellipse]\n", token, label]]; [graphViz appendFormat:@"\t%@ -> %@ [color=\"#408000\"]\n", handlerToken, token]; } else { [specialNodes addObject:[NSString stringWithFormat:@"\tspecial_broken_%@ [label=\"Broken %@ command!\\n(No target AI specified.)\" color=\"#C00000\" shape=diamond]\n", methodTag, method]]; [graphViz appendFormat:@"\t%@ -> tspecial_broken_%@ [color=\"#C00000\"]\n", handlerToken, methodTag]; } } #endif oolite-1.82/src/Core/CollisionRegion.h000066400000000000000000000050541256642440500177020ustar00rootroot00000000000000/* CollisionRegion.h Collision regions are used to group entities which may potentially collide, to reduce the number of collision checks required. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #import "OOMaths.h" #define COLLISION_REGION_BORDER_RADIUS 32000.0f #define COLLISION_MAX_ENTITIES 128 #define MINIMUM_SHADOWING_ENTITY_RADIUS 75.0 @class Entity, OOSunEntity; @interface CollisionRegion: NSObject { @private BOOL isUniverse; // if YES location is origin and radius is 0.0f int crid; // identifier HPVector location; // center of the region GLfloat radius; // inner radius of the region GLfloat border_radius; // additiønal, border radius of the region (typically 32km or some value > the scanner range) unsigned checks_this_tick; unsigned checks_within_range; NSMutableArray *subregions; BOOL isPlayerInRegion; Entity **entity_array; // entities within the region unsigned n_entities; // number of entities unsigned max_entities; // so storage can be expanded CollisionRegion *parentRegion; } - (id) initAsUniverse; - (id) initAtLocation:(HPVector) locn withRadius:(GLfloat) rad withinRegion:(CollisionRegion*) otherRegion; - (void) clearSubregions; - (void) addSubregionAtPosition:(HPVector) pos withRadius:(GLfloat) rad; // collision checking - (void) clearEntityList; - (void) addEntity:(Entity *)ent; - (BOOL) checkEntity:(Entity *)ent; - (void) findCollisions; - (void) findShadowedEntities; // Description for FPS HUD - (NSString *) collisionDescription; - (NSString *) debugOut; @end /* Given a region centred at e1pos with a radius of e1rad, the depth * of shadowing cast by e2 from the_sun is recorded in outValue, with * >1 = no shadow, <1 = shadow */ BOOL shadowAtPointOcclusionToValue(HPVector e1pos, GLfloat e1rad, Entity *e2, OOSunEntity *the_sun, float *outValue); oolite-1.82/src/Core/CollisionRegion.m000066400000000000000000000427301256642440500177110ustar00rootroot00000000000000/* CollisionRegion.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "CollisionRegion.h" #import "OOMaths.h" #import "Universe.h" #import "Entity.h" #import "ShipEntity.h" #import "OOSunEntity.h" #import "OOPlanetEntity.h" #import "StationEntity.h" #import "PlayerEntity.h" #import "OODebugFlags.h" static BOOL positionIsWithinRegion(HPVector position, CollisionRegion *region); static BOOL sphereIsWithinRegion(HPVector position, GLfloat rad, CollisionRegion *region); static BOOL positionIsWithinBorders(HPVector position, CollisionRegion *region); @implementation CollisionRegion // basic alloc/ dealloc routines // static int crid_counter = 1; - (id) init // Designated initializer. { if ((self = [super init])) { max_entities = COLLISION_MAX_ENTITIES; entity_array = (Entity **)malloc(max_entities * sizeof(Entity *)); if (entity_array == NULL) { [self release]; return nil; } crid = crid_counter++; } return self; } - (id) initAsUniverse { if ((self = [self init])) { isUniverse = YES; } return self; } - (id) initAtLocation:(HPVector)locn withRadius:(GLfloat)rad withinRegion:(CollisionRegion *)otherRegion { if ((self = [self init])) { location = locn; radius = rad; border_radius = COLLISION_REGION_BORDER_RADIUS; parentRegion = otherRegion; } return self; } - (void) dealloc { free(entity_array); DESTROY(subregions); [super dealloc]; } - (NSString *) description { return [NSString stringWithFormat:@"<%@ %p>{ID: %d, %lu subregions, %u ents}", [self class], self, crid, [subregions count], n_entities]; } - (void) clearSubregions { [subregions makeObjectsPerformSelector:@selector(clearSubregions)]; [subregions removeAllObjects]; } - (void) addSubregionAtPosition:(HPVector)pos withRadius:(GLfloat)rad { // check if this can be fitted within any of the subregions // CollisionRegion *sub = nil; foreach (sub, subregions) { if (sphereIsWithinRegion(pos, rad, sub)) { // if it fits, put it in! [sub addSubregionAtPosition:pos withRadius:rad]; return; } if (positionIsWithinRegion(pos, sub)) { // crosses the border of this region already - leave it out return; } } // no subregion fit - move on... // sub = [[CollisionRegion alloc] initAtLocation:pos withRadius:rad withinRegion:self]; if (subregions == nil) subregions = [[NSMutableArray alloc] initWithCapacity:32]; [subregions addObject:sub]; [sub release]; } // update routines to check if a position is within the radius or within its borders // static BOOL positionIsWithinRegion(HPVector position, CollisionRegion *region) { if (region == nil) return NO; if (region->isUniverse) return YES; HPVector loc = region->location; GLfloat r1 = region->radius; if ((position.x < loc.x - r1)||(position.x > loc.x + r1)|| (position.y < loc.y - r1)||(position.y > loc.y + r1)|| (position.z < loc.z - r1)||(position.z > loc.z + r1)) { return NO; } return YES; } static BOOL sphereIsWithinRegion(HPVector position, GLfloat rad, CollisionRegion *region) { if (region == nil) return NO; if (region->isUniverse) return YES; HPVector loc = region->location; GLfloat r1 = region->radius; if ((position.x - rad < loc.x - r1)||(position.x + rad > loc.x + r1)|| (position.y - rad < loc.y - r1)||(position.y + rad > loc.y + r1)|| (position.z - rad < loc.z - r1)||(position.z + rad > loc.z + r1)) { return NO; } return YES; } static BOOL positionIsWithinBorders(HPVector position, CollisionRegion *region) { if (region == nil) return NO; if (region->isUniverse) return YES; HPVector loc = region->location; GLfloat r1 = region->radius + region->border_radius; if ((position.x < loc.x - r1)||(position.x > loc.x + r1)|| (position.y < loc.y - r1)||(position.y > loc.y + r1)|| (position.z < loc.z - r1)||(position.z > loc.z + r1)) { return NO; } return YES; } // collision checking // - (void) clearEntityList { [subregions makeObjectsPerformSelector:@selector(clearEntityList)]; n_entities = 0; isPlayerInRegion = NO; } - (void) addEntity:(Entity *)ent { // expand if necessary // if (n_entities == max_entities) { max_entities = 1 + max_entities * 2; Entity **new_store = (Entity **)realloc(entity_array, max_entities * sizeof(Entity *)); if (new_store == NULL) { [NSException raise:NSMallocException format:@"Not enough memory to grow collision region member list."]; } entity_array = new_store; } if ([ent isPlayer]) isPlayerInRegion = YES; entity_array[n_entities++] = ent; } - (BOOL) checkEntity:(Entity *)ent { HPVector position = ent->position; // check subregions CollisionRegion *sub = nil; foreach (sub, subregions) { if (positionIsWithinBorders(position, sub) && [sub checkEntity:ent]) { return YES; } } if (!positionIsWithinBorders(position, self)) { return NO; } [self addEntity:ent]; [ent setCollisionRegion:self]; return YES; } - (void) findCollisions { // test for collisions in each subregion [subregions makeObjectsPerformSelector:@selector(findCollisions)]; // reject trivial cases if (n_entities < 2) return; // // According to Shark, when this was in Universe this was where Oolite spent most time! // Entity *e1, *e2; HPVector p1; double dist2, r1, r2, r0, min_dist2; unsigned i; Entity *entities_to_test[n_entities]; // only check unfiltered entities unsigned n_entities_to_test = 0; for (i = 0; i < n_entities; i++) { e1 = entity_array[i]; if (e1->collisionTestFilter != 3) { entities_to_test[n_entities_to_test++] = e1; } } #ifndef NDEBUG if (gDebugFlags & DEBUG_COLLISIONS) { OOLog(@"collisionRegion.debug", @"DEBUG in collision region %@ testing %d out of %d entities", self, n_entities_to_test, n_entities); } #endif if (n_entities_to_test < 2) return; // clear collision variables // for (i = 0; i < n_entities_to_test; i++) { e1 = entities_to_test[i]; if (e1->hasCollided) { [[e1 collisionArray] removeAllObjects]; e1->hasCollided = NO; } if (e1->isShip) { [(ShipEntity*)e1 setProximityAlert:nil]; } e1->collider = nil; } checks_this_tick = 0; checks_within_range = 0; // test each entity in this region against the entities in its collision chain // for (i = 0; i < n_entities_to_test; i++) { e1 = entities_to_test[i]; p1 = e1->position; r1 = e1->collision_radius; // check against the first in the collision chain e2 = e1->collision_chain; while (e2 != nil) { checks_this_tick++; if (e1->isShip && e2->isShip && [(ShipEntity *)e1 collisionExceptedFor:(ShipEntity *)e2]) { // nothing happens } else { r2 = e2->collision_radius; r0 = r1 + r2; dist2 = HPdistance2(e2->position, p1); min_dist2 = r0 * r0; if (dist2 < PROXIMITY_WARN_DISTANCE2 * min_dist2) { #ifndef NDEBUG if (gDebugFlags & DEBUG_COLLISIONS) { OOLog(@"collisionRegion.debug", @"DEBUG Testing collision between %@ (%@) and %@ (%@)", e1, (e1->collisionTestFilter==3)?@"YES":@"NO", e2, (e2->collisionTestFilter==3)?@"YES":@"NO"); } #endif checks_within_range++; if (e1->isShip && e2->isShip) { if ((dist2 < PROXIMITY_WARN_DISTANCE2 * r2 * r2) || (dist2 < PROXIMITY_WARN_DISTANCE2 * r1 * r1)) { [(ShipEntity*)e1 setProximityAlert:(ShipEntity*)e2]; [(ShipEntity*)e2 setProximityAlert:(ShipEntity*)e1]; } if (dist2 >= min_dist2) { if (e1->isStation) { StationEntity* se1 = (StationEntity *)e1; [se1 shipIsInDockingCorridor:(ShipEntity *)e2]; } else if (e2->isStation) { StationEntity* se2 = (StationEntity *)e2; [se2 shipIsInDockingCorridor:(ShipEntity *)e1]; } } } if (dist2 < min_dist2) { BOOL collision = NO; if (e1->isStation) { StationEntity* se1 = (StationEntity *)e1; if ([se1 shipIsInDockingCorridor:(ShipEntity *)e2]) { collision = NO; } else { collision = [e1 checkCloseCollisionWith:e2]; } } else if (e2->isStation) { StationEntity* se2 = (StationEntity *)e2; if ([se2 shipIsInDockingCorridor:(ShipEntity *)e1]) { collision = NO; } else { collision = [e2 checkCloseCollisionWith:e1]; } } else { collision = [e1 checkCloseCollisionWith:e2]; } if (collision) { // now we have no need to check the e2-e1 collision if (e1->collider) { [[e1 collisionArray] addObject:e1->collider]; } else { [[e1 collisionArray] addObject:e2]; } e1->hasCollided = YES; if (e2->collider) { [[e2 collisionArray] addObject:e2->collider]; } else { [[e2 collisionArray] addObject:e1]; } e2->hasCollided = YES; } } } } // check the next in the collision chain e2 = e2->collision_chain; } } #ifndef NDEBUG if (gDebugFlags & DEBUG_COLLISIONS) { OOLog(@"collisionRegion.debug",@"Collision test checks %d, within range %d, for %d entities",checks_this_tick,checks_within_range,n_entities_to_test); } #endif } // an outValue of 1 means it's just being occluded. static BOOL entityByEntityOcclusionToValue(Entity *e1, Entity *e2, OOSunEntity *the_sun, float *outValue) { if (EXPECT_NOT(e1 == e2)) { // you can't shade self return NO; } return shadowAtPointOcclusionToValue(e1->position,e1->collision_radius,e2,the_sun,outValue); } // an outValue of 1 means it's just being occluded. BOOL shadowAtPointOcclusionToValue(HPVector e1pos, GLfloat e1rad, Entity *e2, OOSunEntity *the_sun, float *outValue) { *outValue = 1.5f; // initial 'fully lit' value GLfloat cr_e2; if ([e2 isShip]) { cr_e2 = e2->collision_radius * 0.90f; // 10% smaller shadow for ships } else { cr_e2 = e2->collision_radius; } if (cr_e2 < e1rad) { // smaller can't shade bigger return NO; } // tested in construction of e2 list // if (e2->isSunlit == NO) // return NO; // things already /in/ shade can't shade things more. // // check projected sizes of discs GLfloat d2_sun = HPdistance2(e1pos, the_sun->position); GLfloat d2_e2sun = HPdistance2(e2->position, the_sun->position); GLfloat d2_e2 = HPdistance2( e1pos, e2->position); if (d2_e2sun > d2_sun) { // you are nearer the sun than the potential occluder, so it // probably can't shade you if (d2_e2 < cr_e2 * cr_e2 && [e2 isShip]) { // exception: if within the collision radius of the other // object, might still be shadowed by it. GLfloat bbx = 0.0f, bby = 0.0f, bbz = 0.0f; BoundingBox bb = [(ShipEntity*)e2 totalBoundingBox]; bounding_box_get_dimensions(bb,&bbx,&bby,&bbz); float minbb = bbx; if (bby < minbb) { minbb = bby; } if (bbz < minbb) { minbb = bbz; } minbb -= e1rad; // subtract object's size /* closer to the object than the shortest axis. This check * branch is basically for docking at a rock hermit facing * away from the sun, but it checks the shortest bounding * box size rather than the collision radius to avoid * getting weird shadowing effects around large planar * entities like the OXP Torus Station. * * Well... more weird shadowing effects than there already * are, anyway. * * There are more accurate ways to check "sphere inside * bounding box" but this seems accurate enough and is * simpler. * * - CIM */ if (d2_e2 < minbb * minbb) { *outValue = 0.1; return YES; } } return NO; } GLfloat cr_sun = the_sun->collision_radius; GLfloat cr2_sun_scaled = cr_sun * cr_sun * d2_e2 / d2_sun; if (cr_e2 * cr_e2 < cr2_sun_scaled) { // if solar disc projected to the distance of e2 > collision radius it can't be shaded by e2 return NO; } // check angles subtended by sun and occluder // double theta_sun = asin( cr_sun / sqrt(d2_sun)); // 1/2 angle subtended by sun // double theta_e2 = asin( cr_e2 / sqrt(d2_e2)); // 1/2 angle subtended by e2 // find the difference between the angles subtended by occluder and sun float d2_e = sqrt(d2_e2); float theta_diff; if (d2_e < cr_e2) { // then we're "inside" the object. Calculate as if we were on // the edge of it to avoid taking asin(x>1) theta_diff = asin(1) - asin(cr_sun / sqrt(d2_sun)); } else { theta_diff = asin(cr_e2 / d2_e) - asin(cr_sun / sqrt(d2_sun)); } HPVector p_sun = the_sun->position; HPVector p_e2 = e2->position; HPVector p_e1 = e1pos; Vector v_sun = HPVectorToVector(HPvector_subtract(p_sun, p_e1)); v_sun = vector_normal_or_zbasis(v_sun); Vector v_e2 = HPVectorToVector(HPvector_subtract(p_e2, p_e1)); v_e2 = vector_normal_or_xbasis(v_e2); float phi = acos(dot_product(v_sun, v_e2)); // angle between sun and e2 from e1's viewpoint *outValue = (phi / theta_diff); // 1 means just occluded, < 1 means in shadow if (phi > theta_diff) { // sun is not occluded return NO; } // all tests done e1 is in shade! return YES; } static inline BOOL testEntityOccludedByEntity(Entity *e1, Entity *e2, OOSunEntity *the_sun) { float tmp; // we're not interested in the amount of occlusion just now. return entityByEntityOcclusionToValue(e1, e2, the_sun, &tmp); } - (void) findShadowedEntities { // reject trivial cases if (n_entities < 2) return; // // Copy/pasting the collision code to detect occlusion! // unsigned i, j; if ([UNIVERSE reducedDetail]) return; // don't do this in reduced detail mode OOSunEntity* the_sun = [UNIVERSE sun]; if (the_sun == nil) { return; // sun is required } unsigned ent_count = UNIVERSE->n_entities; Entity **uni_entities = UNIVERSE->sortedEntities; // grab the public sorted list Entity *planets[ent_count]; unsigned n_planets = 0; Entity *ships[ent_count]; unsigned n_ships = 0; for (i = 0; i < ent_count; i++) { if (uni_entities[i]->isSunlit) { // get a list of planet entities because they can shade across regions if ([uni_entities[i] isPlanet]) { // don't bother retaining - nothing will happen to them! planets[n_planets++] = uni_entities[i]; } // and a list of shipentities large enough that they might cast a noticeable shadow // if we can't see it, it can't be shadowing anything important else if ([uni_entities[i] isShip] && [uni_entities[i] isVisible] && uni_entities[i]->collision_radius >= MINIMUM_SHADOWING_ENTITY_RADIUS) { ships[n_ships++] = uni_entities[i]; // don't bother retaining - nothing will happen to them! } } } // test for shadows in each subregion [subregions makeObjectsPerformSelector:@selector(findShadowedEntities)]; // test each entity in this region against the others for (i = 0; i < n_entities; i++) { Entity *e1 = entity_array[i]; if (![e1 isVisible]) { continue; // don't check shading of objects we can't see } BOOL occluder_moved = NO; if ([e1 status] == STATUS_COCKPIT_DISPLAY) { e1->isSunlit = YES; e1->shadingEntityID = NO_TARGET; continue; // don't check shading in demo mode } Entity *occluder = nil; if (e1->isSunlit == NO) { occluder = [UNIVERSE entityForUniversalID:e1->shadingEntityID]; if (occluder != nil) { occluder_moved = occluder->hasMoved; } } if (([e1 isShip] ||[e1 isPlanet]) && (e1->hasMoved || occluder_moved)) { e1->isSunlit = YES; // sunlit by default e1->shadingEntityID = NO_TARGET; // // check demo mode here.. if ([e1 isPlayer] && ([(PlayerEntity*)e1 showDemoShips])) { continue; // don't check shading in demo mode } // test last occluder (most likely case) if (occluder) { if (testEntityOccludedByEntity(e1, occluder, the_sun)) { e1->isSunlit = NO; e1->shadingEntityID = [occluder universalID]; } } if (!e1->isSunlit) { // no point in continuing tests continue; } // test planets for (j = 0; j < n_planets; j++) { float occlusionNumber; if (entityByEntityOcclusionToValue(e1, planets[j], the_sun, &occlusionNumber)) { e1->isSunlit = NO; e1->shadingEntityID = [planets[j] universalID]; break; } if ([e1 isPlayer]) { [(PlayerEntity *)e1 setOcclusionLevel:occlusionNumber]; } } if (!e1->isSunlit) { // no point in continuing tests continue; } // test local entities for (j = 0; j < n_ships; j++) { if (testEntityOccludedByEntity(e1, ships[j], the_sun)) { e1->isSunlit = NO; e1->shadingEntityID = [ships[j] universalID]; break; } } } } } - (NSString *) collisionDescription { return [NSString stringWithFormat:@"p%u - c%u", checks_this_tick, checks_within_range]; } - (NSString *) debugOut { NSMutableString *result = [[NSMutableString alloc] initWithFormat:@"%d:", n_entities]; CollisionRegion *sub = nil; foreach (sub, subregions) { [result appendString:[sub debugOut]]; } return [result autorelease]; } @end oolite-1.82/src/Core/Debug/000077500000000000000000000000001256642440500154545ustar00rootroot00000000000000oolite-1.82/src/Core/Debug/OODebugFlags.h000066400000000000000000000013331256642440500200660ustar00rootroot00000000000000#include enum OODebugFlags { DEBUG_LINKED_LISTS = 0x00000001, // UNUSED = 0x00000002, DEBUG_COLLISIONS = 0x00000004, DEBUG_DOCKING = 0x00000008, DEBUG_OCTREE_LOGGING = 0x00000010, // UNUSED = 0x00000020, DEBUG_BOUNDING_BOXES = 0x00000040, DEBUG_OCTREE_DRAW = 0x00000080, DEBUG_DRAW_NORMALS = 0x00000100, DEBUG_NO_DUST = 0x00000200, DEBUG_NO_SHADER_FALLBACK = 0x00000400, DEBUG_SHADER_VALIDATION = 0x00000800, // Flag for temporary use, always last in list. DEBUG_MISC = 0x10000000 }; #define DEBUG_ALL 0xffffffff #ifndef NDEBUG extern NSUInteger gDebugFlags; extern uint32_t gLiveEntityCount; extern size_t gTotalEntityMemory; #else #define gDebugFlags (0) #endif oolite-1.82/src/Core/Debug/OODebugMonitor.h000066400000000000000000000074251256642440500204710ustar00rootroot00000000000000/* OODebugMonitor.h Debugging services object for Oolite. The debug controller implements Oolite's part of debugging support. It can connect to one debugger object, which conforms to the OODebuggerInterface formal protocol. This can either be (part of) a debugger loaded into Oolite itself (as in the Mac Debug OXP), or provide communications with an external debugger (for instance, over Distributed Objects or TCP/IP). Oolite debug support Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" #import "OOWeakReference.h" #import "OODebuggerInterface.h" @class OOJSScript; @protocol OODebugMonitorInterface // Note: disconnectDebugger:message: will cause a disconnectDebugMonitor:message: message to be sent to the debugger. The debugger should not send disconnectDebugger:message: in response to disconnectDebugMonitor:message:. - (void)disconnectDebugger:(in id)debugger message:(in NSString *)message; // *** JavaScript console support. // Perform a JS command as though entered at the console, including echoing. - (oneway void)performJSConsoleCommand:(in NSString *)command; - (id)configurationValueForKey:(in NSString *)key; - (void)setConfigurationValue:(in id)value forKey:(in NSString *)key; - (NSString *)sourceCodeForFile:(in NSString *)filePath line:(in unsigned)line; @end @interface OODebugMonitor: OOWeakRefObject { @private id _debugger; // JavaScript console support. OOJSScript *_script; struct JSObject *_jsSelf; NSDictionary *_configFromOXPs; // Settings from debugConfig.plist NSMutableDictionary *_configOverrides; // Settings from preferences, modifiable through JS. // Caches NSMutableDictionary *_fgColors, *_bgColors, *_sourceFiles; // TCP options BOOL _TCPIgnoresDroppedPackets; BOOL _usingPlugInController; } + (OODebugMonitor *) sharedDebugMonitor; - (BOOL)setDebugger:(id)debugger; // *** JavaScript console support. - (void)appendJSConsoleLine:(id)string colorKey:(NSString *)colorKey emphasisRange:(NSRange)emphasisRange; - (void)appendJSConsoleLine:(id)string colorKey:(NSString *)colorKey; - (void)clearJSConsole; - (void)showJSConsole; - (id)configurationValueForKey:(NSString *)key class:(Class)class defaultValue:(id)value; - (long long)configurationIntValueForKey:(NSString *)key defaultValue:(long long)value; - (NSArray *)configurationKeys; - (BOOL) debuggerConnected; - (void) dumpMemoryStatistics; - (size_t) dumpJSMemoryStatistics; - (void) setTCPIgnoresDroppedPackets:(BOOL)flag; - (BOOL) TCPIgnoresDroppedPackets; - (void) setUsingPlugInController:(BOOL)flag; - (BOOL) usingPlugInController; #if OOLITE_GNUSTEP - (void) applicationWillTerminate; #endif @end oolite-1.82/src/Core/Debug/OODebugMonitor.m000066400000000000000000000636671256642440500205100ustar00rootroot00000000000000/* OODebugMonitor.m Oolite debug support Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef OO_EXCLUDE_DEBUG_SUPPORT #import "OODebugMonitor.h" #import "OOCollectionExtractors.h" #import "OOLoggingExtended.h" #import "ResourceManager.h" #import "NSStringOOExtensions.h" #import "OOJSConsole.h" #import "OOJSScript.h" #import "OOJSEngineTimeManagement.h" #import "OOJSSpecialFunctions.h" #import "NSObjectOOExtensions.h" #import "OOTexture.h" #import "OOConcreteTexture.h" #import "OODrawable.h" static OODebugMonitor *sSingleton = nil; @interface OODebugMonitor (Private) - (void) setUpDebugConsoleScript; - (void) javaScriptEngineWillReset:(NSNotification *)notification; - (void)disconnectDebuggerWithMessage:(NSString *)message; - (NSDictionary *)mergedConfiguration; /* Convert a configuration dictionary to a standard form. In particular, convert all colour specifiers to RGBA arrays with values in [0, 1], and converts "show-console" values to booleans. */ - (NSMutableDictionary *)normalizeConfigDictionary:(NSDictionary *)dictionary; - (id)normalizeConfigValue:(id)value forKey:(NSString *)key; - (NSArray *)loadSourceFile:(NSString *)filePath; @end @implementation OODebugMonitor #if OOLITE_GNUSTEP NSString *NSApplicationWillTerminateNotification = @"ApplicationWillTerminate"; #endif - (id)init { NSUserDefaults *defaults = nil; NSMutableDictionary *config = nil; self = [super init]; if (self != nil) { config = [[[ResourceManager dictionaryFromFilesNamed:@"debugConfig.plist" inFolder:@"Config" andMerge:YES] mutableCopy] autorelease]; _configFromOXPs = [[self normalizeConfigDictionary:config] copy]; defaults = [NSUserDefaults standardUserDefaults]; config = [self normalizeConfigDictionary:[defaults dictionaryForKey:@"debug-settings-override"]]; if (config == nil) config = [NSMutableDictionary dictionary]; _configOverrides = [config retain]; _TCPIgnoresDroppedPackets = NO; OOJavaScriptEngine *jsEng = [OOJavaScriptEngine sharedEngine]; #if OOJSENGINE_MONITOR_SUPPORT [jsEng setMonitor:self]; #endif [self setUpDebugConsoleScript]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:NSApplicationWillTerminateNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(javaScriptEngineWillReset:) name:kOOJavaScriptEngineWillResetNotification object:jsEng]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setUpDebugConsoleScript) name:kOOJavaScriptEngineDidResetNotification object:jsEng]; } return self; } - (void)dealloc { [self disconnectDebuggerWithMessage:@"Debug controller object destroyed while debugging in progress."]; [_configFromOXPs release]; [_configOverrides release]; [_fgColors release]; [_bgColors release]; [_sourceFiles release]; if (_jsSelf != NULL) { [[OOJavaScriptEngine sharedEngine] removeGCObjectRoot:&_jsSelf]; } [super dealloc]; } + (OODebugMonitor *) sharedDebugMonitor { // NOTE: assumes single-threaded access. The debug monitor is not, on the whole, thread safe. if (sSingleton == nil) { sSingleton = [[self alloc] init]; } return sSingleton; } - (BOOL)setDebugger:(id)newDebugger { NSString *error = nil; if (newDebugger != _debugger) { // Disconnect existing debugger, if any. if (newDebugger != nil) { [self disconnectDebuggerWithMessage:@"New debugger set."]; } else { [self disconnectDebuggerWithMessage:@"Debugger disconnected programatically."]; } // If a new debugger was specified, try to connect it. if (newDebugger != nil) { @try { if ([newDebugger connectDebugMonitor:self errorMessage:&error]) { [newDebugger debugMonitor:self noteConfiguration:[self mergedConfiguration]]; _debugger = [newDebugger retain]; } else { OOLog(@"debugMonitor.setDebugger.failed", @"Could not connect to debugger %@, because an error occurred: %@", newDebugger, error); } } @catch (NSException *exception) { OOLog(@"debugMonitor.setDebugger.failed", @"Could not connect to debugger %@, because an exception occurred: %@ -- %@", newDebugger, [exception name], [exception reason]); } } } return _debugger == newDebugger; } - (oneway void)performJSConsoleCommand:(in NSString *)command { JSContext *context = OOJSAcquireContext(); jsval commandVal = OOJSValueFromNativeObject(context, command); OOJSStartTimeLimiterWithTimeLimit(kOOJSLongTimeLimit); [_script callMethod:OOJSID("consolePerformJSCommand") inContext:context withArguments:&commandVal count:1 result:NULL]; OOJSStopTimeLimiter(); OOJSRelinquishContext(context); } - (void)appendJSConsoleLine:(id)string colorKey:(NSString *)colorKey emphasisRange:(NSRange)emphasisRange { if (string == nil) return; OOJSPauseTimeLimiter(); @try { [_debugger debugMonitor:self jsConsoleOutput:string colorKey:colorKey emphasisRange:emphasisRange]; } @catch (NSException *exception) { OOLog(@"debugMonitor.debuggerConnection.exception", @"Exception while attempting to send JavaScript console text to debugger: %@ -- %@", [exception name], [exception reason]); } OOJSResumeTimeLimiter(); } - (void)appendJSConsoleLine:(id)string colorKey:(NSString *)colorKey { [self appendJSConsoleLine:string colorKey:colorKey emphasisRange:NSMakeRange(0, 0)]; } - (void)clearJSConsole { OOJSPauseTimeLimiter(); @try { [_debugger debugMonitorClearConsole:self]; } @catch (NSException *exception) { OOLog(@"debugMonitor.debuggerConnection.exception", @"Exception while attempting to clear JavaScript console: %@ -- %@", [exception name], [exception reason]); } OOJSResumeTimeLimiter(); } - (void)showJSConsole { OOJSPauseTimeLimiter(); @try { [_debugger debugMonitorShowConsole:self]; } @catch (NSException *exception) { OOLog(@"debugMonitor.debuggerConnection.exception", @"Exception while attempting to show JavaScript console: %@ -- %@", [exception name], [exception reason]); } OOJSResumeTimeLimiter(); } - (id)configurationValueForKey:(in NSString *)key { return [self configurationValueForKey:key class:Nil defaultValue:nil]; } - (id)configurationValueForKey:(NSString *)key class:(Class)class defaultValue:(id)value { id result = nil; if (class == Nil) class = [NSObject class]; result = [_configOverrides objectForKey:key]; if (![result isKindOfClass:class] && result != [NSNull null]) result = [_configFromOXPs objectForKey:key]; if (![result isKindOfClass:class] && result != [NSNull null]) result = [[value retain] autorelease]; if (result == [NSNull null]) result = nil; return result; } - (long long)configurationIntValueForKey:(NSString *)key defaultValue:(long long)value { long long result; id object = nil; object = [self configurationValueForKey:key]; if ([object respondsToSelector:@selector(longLongValue)]) result = [object longLongValue]; else if ([object respondsToSelector:@selector(intValue)]) result = [object intValue]; else result = value; return result; } - (void)setConfigurationValue:(in id)value forKey:(in NSString *)key { if (key == nil) return; value = [self normalizeConfigValue:value forKey:key]; if (value == nil) { [_configOverrides removeObjectForKey:key]; } else { if (_configOverrides == nil) _configOverrides = [[NSMutableDictionary alloc] init]; [_configOverrides setObject:value forKey:key]; } // Send changed value to debugger if (value == nil) { // Setting a nil value removes an override, and may reveal an underlying OXP-defined value value = [self configurationValueForKey:key]; } @try { [_debugger debugMonitor:self noteChangedConfigrationValue:value forKey:key]; } @catch (NSException *exception) { OOLog(@"debugMonitor.debuggerConnection.exception", @"Exception while attempting to send configuration update to debugger: %@ -- %@", [exception name], [exception reason]); } } - (NSArray *)configurationKeys { NSMutableSet *result = nil; result = [NSMutableSet setWithCapacity:[_configFromOXPs count] + [_configOverrides count]]; [result addObjectsFromArray:[_configFromOXPs allKeys]]; [result addObjectsFromArray:[_configOverrides allKeys]]; return [[result allObjects] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; } - (BOOL) debuggerConnected { return _debugger != nil; } - (void) writeMemStat:(NSString *)format, ... { va_list args; va_start(args, format); NSString *message = [[NSString alloc] initWithFormat:format arguments:args]; va_end(args); OOLog(@"debug.memStats", @"%@", message); [self appendJSConsoleLine:message colorKey:@"command-result"]; [message release]; } static NSString *SizeString(size_t size) { enum { kThreshold = 2 // 2 KiB, 2 MiB etc. }; unsigned magnitude = 0; NSString *suffix = @""; if (size < kThreshold << 10) { return [NSString stringWithFormat:@"%zu bytes", size]; } if (size < kThreshold << 20) { magnitude = 1; suffix = @"KiB"; } else if (size < ((size_t)kThreshold << 30)) { magnitude = 2; suffix = @"MiB"; } else { magnitude = 3; suffix = @"GiB"; } float unit = 1 << (magnitude * 10); float sizef = (float)size / unit; sizef = round(sizef * 100.0f) / 100.f; return [NSString stringWithFormat:@"%.2f %@", sizef, suffix]; } typedef struct { NSMutableSet *entityTextures; NSMutableSet *visibleEntityTextures; NSMutableSet *seenEntities; unsigned seenCount; size_t totalEntityObjSize; size_t totalDrawableSize; } EntityDumpState; - (void) dumpEntity:(id)entity withState:(EntityDumpState *)state parentVisible:(BOOL)parentVisible { if ([state->seenEntities containsObject:entity] || entity == nil) return; [state->seenEntities addObject:entity]; state->seenCount++; size_t entitySize = [entity oo_objectSize]; size_t drawableSize = 0; if ([entity isKindOfClass:[OOEntityWithDrawable class]]) { OODrawable *drawable = [entity drawable]; drawableSize = [drawable totalSize]; } BOOL visible = parentVisible && [entity isVisible]; NSSet *textures = [entity allTextures]; if (textures != nil) { [state->entityTextures unionSet:textures]; if (visible) [state->visibleEntityTextures unionSet:textures]; } NSString *extra = @""; if (visible) { extra = [extra stringByAppendingString:@", visible"]; } if (drawableSize != 0) { extra = [extra stringByAppendingFormat:@", drawable: %@", SizeString(drawableSize)]; } [self writeMemStat:@"%@: %@%@", [entity shortDescription], SizeString(entitySize), extra]; state->totalEntityObjSize += entitySize; state->totalDrawableSize += drawableSize; OOLogIndent(); if ([entity isShip]) { NSEnumerator *subEnum = nil; id subentity = nil; for (subEnum = [entity subEntityEnumerator]; (subentity = [subEnum nextObject]); ) { [self dumpEntity:subentity withState:state parentVisible:visible]; } if ([entity isPlayer]) { NSUInteger i, count = [entity dialMaxMissiles]; for (i = 0; i < count; i++) { subentity = [entity missileForPylon:i]; if (subentity != nil) [self dumpEntity:subentity withState:state parentVisible:NO]; } } } if ([entity isPlanet]) { #if NEW_PLANETS // FIXME: dump atmosphere texture. #else PlanetEntity *atmosphere = [entity atmosphere]; if (atmosphere != nil) { [self dumpEntity:atmosphere withState:state parentVisible:visible]; } #endif } if ([entity isWormhole]) { NSEnumerator *shipEnum = nil; NSDictionary *shipInfo = nil; for (shipEnum = [[entity shipsInTransit] objectEnumerator]; (shipInfo = [shipEnum nextObject]); ) { ShipEntity *ship = [shipInfo objectForKey:@"ship"]; [self dumpEntity:ship withState:state parentVisible:NO]; } } OOLogOutdent(); } - (void) dumpMemoryStatistics { OOLog(@"debug.memStats", @"Memory statistics:"); OOLogIndent(); // Get texture retain counts before the entity dumper starts messing with them. NSSet *allTextures = [OOTexture allTextures]; NSMutableDictionary *textureRefCounts = [NSMutableDictionary dictionaryWithCapacity:[allTextures count]]; OOTexture *tex = nil; NSEnumerator *texEnum = nil; for (texEnum = [allTextures objectEnumerator]; (tex = [texEnum nextObject]); ) { // We subtract one because allTextures retains the textures. [textureRefCounts setObject:[NSNumber numberWithUnsignedInteger:[tex retainCount] - 1] forKey:[NSValue valueWithNonretainedObject:tex]]; } size_t totalSize = 0; [self writeMemStat:@"Entitites:"]; OOLogIndent(); NSArray *entities = [UNIVERSE entityList]; EntityDumpState entityDumpState = { .entityTextures = [NSMutableSet set], .visibleEntityTextures = [NSMutableSet set], .seenEntities = [NSMutableSet set] }; id entity = nil; NSEnumerator *entityEnum = nil; for (entityEnum = [entities objectEnumerator]; (entity = [entityEnum nextObject]); ) { [self dumpEntity:entity withState:&entityDumpState parentVisible:YES]; } for (entityEnum = [[PLAYER scannedWormholes] objectEnumerator]; (entity = [entityEnum nextObject]); ) { [self dumpEntity:entity withState:&entityDumpState parentVisible:YES]; } OOLogOutdent(); [self writeMemStat:@"Total entity size (excluding %u entities not accounted for): %@ (%@ entity objects, %@ drawables)", gLiveEntityCount - entityDumpState.seenCount, SizeString(entityDumpState.totalEntityObjSize + entityDumpState.totalDrawableSize), SizeString(entityDumpState.totalEntityObjSize), SizeString(entityDumpState.totalDrawableSize)]; totalSize += entityDumpState.totalEntityObjSize + entityDumpState.totalDrawableSize; /* Sort textures so that textures in the "recent cache" come first by age, followed by others. */ NSMutableArray *textures = [[[OOTexture cachedTexturesByAge] mutableCopy] autorelease]; for (texEnum = [allTextures objectEnumerator]; (tex = [texEnum nextObject]); ) { if ([textures indexOfObject:tex] == NSNotFound) { [textures addObject:tex]; } } size_t totalTextureObjSize = 0; size_t totalTextureDataSize = 0; size_t visibleTextureDataSize = 0; [self writeMemStat:@"Textures:"]; OOLogIndent(); for (texEnum = [textures objectEnumerator]; (tex = [texEnum nextObject]); ) { size_t objSize = [tex oo_objectSize]; size_t dataSize = [tex dataSize]; #if OOTEXTURE_RELOADABLE NSString *byteCountSuffix = @""; #else NSString *byteCountSuffix = @" (* 2)"; #endif NSString *usage = @""; if ([entityDumpState.visibleEntityTextures containsObject:tex]) { visibleTextureDataSize += dataSize; // NOT doubled if !OOTEXTURE_RELOADABLE, because we're interested in what the GPU sees. usage = @", visible"; } else if ([entityDumpState.entityTextures containsObject:tex]) { usage = @", active"; } unsigned refCount = [textureRefCounts oo_unsignedIntForKey:[NSValue valueWithNonretainedObject:tex]]; [self writeMemStat:@"%@: [%u refs%@] %@%@", [tex name], refCount, usage, SizeString(objSize + dataSize), byteCountSuffix]; totalTextureDataSize += dataSize; totalTextureObjSize += objSize; } totalSize += totalTextureObjSize + totalTextureDataSize; OOLogOutdent(); #if !OOTEXTURE_RELOADABLE totalTextureDataSize *= 2; #endif [self writeMemStat:@"Total texture size: %@ (%@ object overhead, %@ data, %@ visible texture data)", SizeString(totalTextureObjSize + totalTextureDataSize), SizeString(totalTextureObjSize), SizeString(totalTextureDataSize), SizeString(visibleTextureDataSize)]; totalSize += [self dumpJSMemoryStatistics]; [self writeMemStat:@"Total: %@", SizeString(totalSize)]; OOLogOutdent(); } - (size_t) dumpJSMemoryStatistics { JSContext *context = OOJSAcquireContext(); JSRuntime *runtime = JS_GetRuntime(context); size_t jsSize = JS_GetGCParameter(runtime, JSGC_BYTES); size_t jsMax = JS_GetGCParameter(runtime, JSGC_MAX_BYTES); uint32_t jsGCCount = JS_GetGCParameter(runtime, JSGC_NUMBER); OOJSRelinquishContext(context); [self writeMemStat:@"JavaScript heap: %@ (limit %@, %u collections to date)", SizeString(jsSize), SizeString(jsMax), jsGCCount]; return jsSize; } - (void) setTCPIgnoresDroppedPackets:(BOOL)flag { if (_TCPIgnoresDroppedPackets != flag) { OOLog(@"debugMonitor.TCPSettings", @"The TCP console will %@ TCP packets.", (flag ? @"try to stay connected, ignoring dropped" : @"disconnect if an error affects")); } _TCPIgnoresDroppedPackets = flag; } - (BOOL) TCPIgnoresDroppedPackets { return _TCPIgnoresDroppedPackets; } - (void) setUsingPlugInController:(BOOL)flag { _usingPlugInController = flag; } - (BOOL) usingPlugInController { return _usingPlugInController; } - (NSString *)sourceCodeForFile:(in NSString *)filePath line:(in unsigned)line { id linesForFile = nil; linesForFile = [_sourceFiles objectForKey:filePath]; if (linesForFile == nil) { linesForFile = [self loadSourceFile:filePath]; if (linesForFile == nil) linesForFile = [NSArray arrayWithObject:[NSString stringWithFormat:@"", filePath]]; if (_sourceFiles == nil) _sourceFiles = [[NSMutableDictionary alloc] init]; [_sourceFiles setObject:linesForFile forKey:filePath]; } if ([linesForFile count] < line || line == 0) return @""; return [linesForFile objectAtIndex:line - 1]; } - (void)disconnectDebugger:(in id)debugger message:(in NSString *)message { if (debugger == nil) return; if (debugger == _debugger) { [self disconnectDebuggerWithMessage:message]; } else { OOLog(@"debugMonitor.disconnect.ignored", @"Attempt to disconnect debugger %@, which is not current debugger; ignoring.", debugger); } } #if OOLITE_GNUSTEP - (void) applicationWillTerminate { [[NSNotificationCenter defaultCenter] postNotificationName:NSApplicationWillTerminateNotification object:nil]; } #endif - (void)applicationWillTerminate:(NSNotification *)notification { if (_configOverrides != nil) { [[NSUserDefaults standardUserDefaults] setObject:_configOverrides forKey:@"debug-settings-override"]; } [self disconnectDebuggerWithMessage:@"Oolite is terminating."]; } @end @implementation OODebugMonitor (Private) - (void) setUpDebugConsoleScript { JSContext *context = OOJSAcquireContext(); /* The path to the console script is saved in this here static variable so that we can reload it when resetting into strict mode. -- Ahruman 2011-02-06 */ static NSString *path = nil; if (path == nil) { path = [[ResourceManager pathForFileNamed:@"oolite-debug-console.js" inFolder:@"Scripts"] retain]; } if (path != nil) { NSDictionary *jsProps = [NSDictionary dictionaryWithObjectsAndKeys: self, @"console", JSSpecialFunctionsObjectWrapper(context), @"special", nil]; _script = [[OOJSScript scriptWithPath:path properties:jsProps] retain]; } // If no script, just make console visible globally as debugConsole. if (_script == nil) { JSObject *global = [[OOJavaScriptEngine sharedEngine] globalObject]; JS_DefineProperty(context, global, "debugConsole", [self oo_jsValueInContext:context], NULL, NULL, JSPROP_ENUMERATE); } OOJSRelinquishContext(context); } - (void) javaScriptEngineWillReset:(NSNotification *)notification { DESTROY(_script); _jsSelf = NULL; OOJSConsoleDestroy(); } - (void)disconnectDebuggerWithMessage:(NSString *)message { @try { [_debugger disconnectDebugMonitor:self message:message]; } @catch (NSException *exception) { OOLog(@"debugMonitor.debuggerConnection.exception", @"Exception while attempting to disconnect debugger: %@ -- %@", [exception name], [exception reason]); } id debugger = _debugger; _debugger = nil; [debugger release]; } - (NSDictionary *)mergedConfiguration { NSMutableDictionary *result = nil; result = [NSMutableDictionary dictionary]; if (_configFromOXPs != nil) [result addEntriesFromDictionary:_configFromOXPs]; if (_configOverrides != nil) [result addEntriesFromDictionary:_configOverrides]; return result; } - (NSArray *)loadSourceFile:(NSString *)filePath { NSString *contents = nil; NSArray *lines = nil; if (filePath == nil) return nil; contents = [NSString stringWithContentsOfUnicodeFile:filePath]; if (contents == nil) return nil; /* Extract lines from file. FIXME: this works with CRLF and LF, but not CR. */ lines = [contents componentsSeparatedByString:@"\n"]; return lines; } - (NSMutableDictionary *)normalizeConfigDictionary:(NSDictionary *)dictionary { NSMutableDictionary *result = nil; NSEnumerator *keyEnum = nil; NSString *key = nil; id value = nil; result = [NSMutableDictionary dictionaryWithCapacity:[dictionary count]]; for (keyEnum = [dictionary keyEnumerator]; (key = [keyEnum nextObject]); ) { value = [dictionary objectForKey:key]; value = [self normalizeConfigValue:value forKey:key]; if (key != nil && value != nil) [result setObject:value forKey:key]; } return result; } - (id)normalizeConfigValue:(id)value forKey:(NSString *)key { OOColor *color = nil; BOOL boolValue; if (value != nil) { if ([key hasSuffix:@"-color"] || [key hasSuffix:@"-colour"]) { color = [OOColor colorWithDescription:value]; value = [color normalizedArray]; } else if ([key hasPrefix:@"show-console"]) { boolValue = OOBooleanFromObject(value, NO); value = [NSNumber numberWithBool:boolValue]; } } return value; } - (oneway void)jsEngine:(in byref OOJavaScriptEngine *)engine context:(in JSContext *)context error:(in JSErrorReport *)errorReport stackSkip:(in unsigned)stackSkip showingLocation:(in BOOL)showLocation withMessage:(in NSString *)message { NSString *colorKey = nil; NSString *prefix = nil; NSString *filePath = nil; NSString *sourceLine = nil; NSString *scriptLine = nil; NSMutableString *formattedMessage = nil; NSRange emphasisRange; NSString *showKey = nil; if (_debugger == nil) return; if (errorReport->flags & JSREPORT_WARNING) { colorKey = @"warning"; prefix = @"Warning"; } else if (errorReport->flags & JSREPORT_EXCEPTION) { colorKey = @"exception"; prefix = @"Exception"; } else { colorKey = @"error"; prefix = @"Error"; } if (errorReport->flags & JSREPORT_STRICT) { prefix = [prefix stringByAppendingString:@" (strict mode)"]; } // Prefix and subsequent colon should be bold: emphasisRange = NSMakeRange(0, [prefix length] + 1); formattedMessage = [NSMutableString stringWithFormat:@"%@: %@", prefix, message]; // Note that the "active script" isn't necessarily the one causing the // error, since one script can call another's methods. // avoid windows DEP exceptions! OOJSScript *thisScript = [[OOJSScript currentlyRunningScript] weakRetain]; scriptLine = [[thisScript weakRefUnderlyingObject] displayName]; [thisScript release]; if (scriptLine != nil) { [formattedMessage appendFormat:@"\n Active script: %@", scriptLine]; } if (showLocation && stackSkip == 0) { // Append file name and line if (errorReport->filename != NULL) filePath = [NSString stringWithUTF8String:errorReport->filename]; if ([filePath length] != 0) { [formattedMessage appendFormat:@"\n %@, line %u", [filePath lastPathComponent], errorReport->lineno]; // Append source code sourceLine = [self sourceCodeForFile:filePath line:errorReport->lineno]; if (sourceLine != nil) { [formattedMessage appendFormat:@":\n %@", sourceLine]; } } } [self appendJSConsoleLine:formattedMessage colorKey:colorKey emphasisRange:emphasisRange]; if (errorReport->flags & JSREPORT_WARNING) showKey = @"show-console-on-warning"; else showKey = @"show-console-on-error"; // if not a warning, it's a proper error. if (OOBooleanFromObject([self configurationValueForKey:showKey], NO)) { [self showJSConsole]; } } - (oneway void)jsEngine:(in byref OOJavaScriptEngine *)engine context:(in JSContext *)context logMessage:(in NSString *)message ofClass:(in NSString *)messageClass { [self appendJSConsoleLine:message colorKey:@"log"]; if (OOBooleanFromObject([self configurationValueForKey:@"show-console-on-log"], NO)) { [self showJSConsole]; } } - (jsval)oo_jsValueInContext:(JSContext *)context { if (_jsSelf == NULL) { _jsSelf = DebugMonitorToJSConsole(context, self); if (_jsSelf != NULL) { if (!OOJSAddGCObjectRoot(context, &_jsSelf, "debug console")) { _jsSelf = NULL; } } } if (_jsSelf != NULL) return OBJECT_TO_JSVAL(_jsSelf); else return JSVAL_NULL; } @end @implementation OODebugMonitor (Singleton) /* Canonical singleton boilerplate. See Cocoa Fundamentals Guide: Creating a Singleton Instance. See also +sharedDebugMonitor above. NOTE: assumes single-threaded access. */ + (id)allocWithZone:(NSZone *)inZone { if (sSingleton == nil) { sSingleton = [super allocWithZone:inZone]; return sSingleton; } return nil; } - (id)copyWithZone:(NSZone *)inZone { return self; } - (id)retain { return self; } - (NSUInteger)retainCount { return UINT_MAX; } - (void)release {} - (id)autorelease { return self; } @end #endif /* OO_EXCLUDE_DEBUG_SUPPORT */ oolite-1.82/src/Core/Debug/OODebugStandards.h000066400000000000000000000027701256642440500207630ustar00rootroot00000000000000/* OODebugStandards.h OXP strictness warnings for errors and deprecated content Copyright (C) 2014 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" // Warn/exit if deprecated functionality used void OOStandardsDeprecated(NSString *message); // Warn/exit if an OXP error is detected void OOStandardsError(NSString *message); // Return true if in standard enforcing mode // Always false in release builds // This will *not* exit in "exit on error" mode BOOL OOEnforceStandards(); void OOSetStandardsForOXPVerifierMode(); oolite-1.82/src/Core/Debug/OODebugStandards.m000066400000000000000000000061471256642440500207720ustar00rootroot00000000000000/* OODebugStandards.m OXP strictness warnings for errors and deprecated content Copyright (C) 2014 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OODebugStandards.h" #import "OOLogging.h" #import "OOCollectionExtractors.h" #import "GameController.h" #ifdef NDEBUG // in release mode, stubs void OOStandardsDeprecated(NSString *message) {} void OOStandardsError(NSString *message) {} BOOL OOEnforceStandards() { return NO; } void OOSetStandardsForOXPVerifierMode() {} #else void OOStandardsSetup(); void OOStandardsInternal(NSString *type, NSString *message); static BOOL sSetup = NO; typedef enum { // do nothing (equivalent to release build) STANDARDS_ENFORCEMENT_OFF = 0, // warn in log but otherwise do nothing STANDARDS_ENFORCEMENT_WARN, // warn in log, block use of deprecated or error items STANDARDS_ENFORCEMENT_ENFORCE, // note in log, then exit if deprecated or error condition occurs STANDARDS_ENFORCEMENT_QUIT } OOStandardsEnforcement; static OOStandardsEnforcement sEnforcement = STANDARDS_ENFORCEMENT_WARN; void OOStandardsSetup() { if (sSetup) { return; } NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; int s = [prefs oo_intForKey:@"enforce-oxp-standards" defaultValue:STANDARDS_ENFORCEMENT_WARN]; if (s < STANDARDS_ENFORCEMENT_OFF) { s = STANDARDS_ENFORCEMENT_OFF; } else if (s > STANDARDS_ENFORCEMENT_QUIT) { s = STANDARDS_ENFORCEMENT_QUIT; } sEnforcement = s; } void OOStandardsInternal(NSString *type, NSString *message) { OOStandardsSetup(); if (sEnforcement == STANDARDS_ENFORCEMENT_OFF) { return; } OOLog(type, @"%@", message); if (sEnforcement == STANDARDS_ENFORCEMENT_QUIT) { [[GameController sharedController] exitAppWithContext:type]; // exit } } void OOStandardsDeprecated(NSString *message) { OOStandardsInternal(@"oxp-standards.deprecated",message); } void OOStandardsError(NSString *message) { OOStandardsInternal(@"oxp-standards.error",message); } BOOL OOEnforceStandards() { OOStandardsSetup(); return sEnforcement >= STANDARDS_ENFORCEMENT_ENFORCE; } void OOSetStandardsForOXPVerifierMode() { sEnforcement = STANDARDS_ENFORCEMENT_WARN; sSetup = YES; } #endif oolite-1.82/src/Core/Debug/OODebugSupport.h000066400000000000000000000057211256642440500205130ustar00rootroot00000000000000/* OODebugSupport.h Set up debug support. Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef OO_EXCLUDE_DEBUG_SUPPORT void OOInitDebugSupport(void); #else #define OOInitDebugSupport() do {} while (0) #endif #if OOLITE_MAC_OS_X #import /** * Set a point flag in Instruments. * * Signal flags are hidden by default. To show them, select "Manage Flags..." * from the Window menu, and select "Signal Flags" from the "Displayed Flags" * dropdown menu. * * @param string An NSString identifying the context of the flag. This will be * displayed as "from oolite [point]". */ #define OOProfilerPointMarker(string) \ OODTSendSignalFlag(string, DT_POINT_SIGNAL, NO) /** * Set a start flag in Instruments. * * A start flag should be balanced with a matching end flag. * * @param string An NSString identifying the context of the flag. This will be * displayed as "from oolite [point]". The start flag * is matched with the following end flag with the same string. */ #define OOProfilerStartMarker(string) \ OODTSendSignalFlag(string, DT_START_SIGNAL, NO) /** * Set an end flag in Instruments. * * An end flag should be balanced with a matching start flag. * * @param string An NSString identifying the context of the flag. This will be * displayed as "from oolite [point]". The end flag * is matched with the previous start flag with the same string. */ #define OOProfilerEndMarker(string) \ OODTSendSignalFlag(string, DT_END_SIGNAL, NO) #define OODTSendSignalFlag(string, signal, includeBacktrace) \ do { const char *stringC = [[@"oolite " stringByAppendingString:string] UTF8String]; DTSendSignalFlag(stringC, signal, includeBacktrace); } while (0) #else #define OOProfilerPointMarker(string) \ do {} while (0) #define OOProfilerStartMarker(string) \ do {} while (0) #define OOProfilerEndMarker(string) \ do {} while (0) #endif oolite-1.82/src/Core/Debug/OODebugSupport.m000066400000000000000000000104561256642440500205210ustar00rootroot00000000000000/* OODebugSupport.m Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef OO_EXCLUDE_DEBUG_SUPPORT #import "OODebugSupport.h" #import "ResourceManager.h" #import "OOCollectionExtractors.h" #import "OODebugMonitor.h" #import "OODebugTCPConsoleClient.h" #import "GameController.h" #import "OOJavaScriptEngine.h" #if OOLITE_MAC_OS_X static id LoadDebugPlugIn(void); #else #define LoadDebugPlugIn() nil #endif static id sDebugPlugInController; @interface NSObject (OODebugPlugInController) - (id) setUpDebugger; @end void OOInitDebugSupport(void) { NSString *debugOXPPath = nil; NSDictionary *debugSettings = nil; NSString *consoleHost = nil; unsigned short consolePort = 0; id debugger = nil; BOOL activateDebugConsole = NO; // Load debug settings. debugSettings = [ResourceManager dictionaryFromFilesNamed:@"debugConfig.plist" inFolder:@"Config" mergeMode:MERGE_BASIC cache:NO]; // Check that the debug OXP is installed. If not, we don't enable debug support. debugOXPPath = [ResourceManager pathForFileNamed:@"DebugOXPLocatorBeacon.magic" inFolder:@"nil"]; if (debugOXPPath != nil) { // Load plug-in debugging code on platforms where this is supported. sDebugPlugInController = [LoadDebugPlugIn() retain]; consoleHost = [debugSettings oo_stringForKey:@"console-host"]; consolePort = [debugSettings oo_unsignedShortForKey:@"console-port"]; // If consoleHost is nil, and the debug plug-in can set up a debugger, use that. if (consoleHost == nil && [sDebugPlugInController respondsToSelector:@selector(setUpDebugger)]) { debugger = [sDebugPlugInController setUpDebugger]; [[OODebugMonitor sharedDebugMonitor] setUsingPlugInController:YES]; } // Otherwise, use TCP debugger connection. if (debugger == nil) { debugger = [[OODebugTCPConsoleClient alloc] initWithAddress:consoleHost port:consolePort]; [debugger autorelease]; [[OODebugMonitor sharedDebugMonitor] setUsingPlugInController:NO]; } activateDebugConsole = (debugger != nil); } if (!activateDebugConsole) { activateDebugConsole = [debugSettings oo_boolForKey:@"always-load-debug-console"]; } if (activateDebugConsole) { // Set up monitor and register debugger, if any. [[OODebugMonitor sharedDebugMonitor] setDebugger:debugger]; [[OOJavaScriptEngine sharedEngine] enableDebuggerStatement]; } } #if OOLITE_MAC_OS_X static id LoadDebugPlugIn() { OO_DEBUG_PUSH_PROGRESS(@"Loading debug plug-in"); id debugController = nil; NSURL *plugInURL = NSBundle.mainBundle.builtInPlugInsURL; plugInURL = [plugInURL URLByAppendingPathComponent:@"Debug.bundle"]; NSBundle *debugBundle = [NSBundle bundleWithURL:plugInURL]; if ([debugBundle load]) { Class principalClass = debugBundle.principalClass; if (principalClass != Nil) { // Instantiate principal class of debug bundle, and let it do whatever it wants. debugController = [[principalClass new] autorelease]; } else { OOLog(@"debugSupport.load.failed", @"Failed to find principal class of debug bundle."); } } else { OOLog(@"debugSupport.load.failed", @"Failed to load debug OXP plug-in from %@.", plugInURL.path); } OO_DEBUG_POP_PROGRESS(); return debugController; } #endif #endif /* OO_EXCLUDE_DEBUG_SUPPORT */ oolite-1.82/src/Core/Debug/OODebugTCPConsoleClient.h000066400000000000000000000041421256642440500221430ustar00rootroot00000000000000/* OODebugTCPConsoleClient.h Oolite Debug Support Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" #import "OODebuggerInterface.h" @class OODebugMonitor; typedef enum { kOOTCPClientNotConnected, kOOTCPClientStartedConnectionStage1, kOOTCPClientStartedConnectionStage2, kOOTCPClientConnected, kOOTCPClientConnectionRefused, kOOTCPClientDisconnected } OOTCPClientConnectionStatus; @interface OODebugTCPConsoleClient: NSObject { @private NSHost *_host; NSOutputStream *_outStream; NSInputStream *_inStream; OOTCPClientConnectionStatus _status; OODebugMonitor *_monitor; struct OOTCPStreamDecoder *_decoder; } - (id) initWithAddress:(NSString *)address // Pass nil for localhost port:(uint16_t)port; // Pass 0 for default port @end #if OOLITE_MAC_OS_X /* Declare conformance to NSStreamDelegate, which is a formal protocol starting in the Mac OS X 10.6 SDK. At the time of writing, it's still an informal protocol in GNUstep trunk. -- Ahruman 2012-01-07 */ @interface OODebugTCPConsoleClient (NSStreamDelegate) @end #endif oolite-1.82/src/Core/Debug/OODebugTCPConsoleClient.m000066400000000000000000000463371256642440500221640ustar00rootroot00000000000000/* OODebugTCPConsoleClient.m Oolite Debug Support Copyright (C) 2009-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef OO_EXCLUDE_DEBUG_SUPPORT #import "OODebugTCPConsoleClient.h" #import "OODebugTCPConsoleProtocol.h" #import "OODebugMonitor.h" #import "OOFunctionAttributes.h" #import "OOLogging.h" #include #import "NSDictionaryOOExtensions.h" #if OOLITE_WINDOWS #include #else #include // For htonl #endif #import "OOCollectionExtractors.h" #import "OOTCPStreamDecoder.h" #ifdef OO_LOG_DEBUG_PROTOCOL_PACKETS static void LogSendPacket(NSDictionary *packet); #else #define LogSendPacket(packet) do {} while (0) #endif static void DecoderPacket(void *cbInfo, OOALStringRef packetType, OOALDictionaryRef packet); static void DecoderError(void *cbInfo, OOALStringRef errorDesc); OOINLINE BOOL StatusIsSendable(OOTCPClientConnectionStatus status) { return status == kOOTCPClientStartedConnectionStage1 || status == kOOTCPClientStartedConnectionStage2 || status == kOOTCPClientConnected; } @interface OODebugTCPConsoleClient (OOPrivate) - (void) closeConnection; - (BOOL) sendBytes:(const void *)bytes count:(size_t)count; - (void) sendDictionary:(NSDictionary *)dictionary; - (void) sendPacket:(NSString *)packetType withParameters:(NSDictionary *)parameters; - (void) sendPacket:(NSString *)packetType withValue:(id)value forParameter:(NSString *)paramKey; - (void) readData; - (void) dispatchPacket:(NSDictionary *)packet ofType:(NSString *)packetType; - (void) handleApproveConnectionPacket:(NSDictionary *)packet; - (void) handleRejectConnectionPacket:(NSDictionary *)packet; - (void) handleCloseConnectionPacket:(NSDictionary *)packet; - (void) handleNoteConfigurationChangePacket:(NSDictionary *)packet; - (void) handlePerformCommandPacket:(NSDictionary *)packet; - (void) handleRequestConfigurationValuePacket:(NSDictionary *)packet; - (void) handlePingPacket:(NSDictionary *)packet; - (void) handlePongPacket:(NSDictionary *)packet; - (void) disconnectFromServerWithMessage:(NSString *)message; - (void) breakConnectionWithMessage:(NSString *)message; - (void) breakConnectionWithBadStream:(NSStream *)stream; @end @implementation OODebugTCPConsoleClient - (id) init { return [self initWithAddress:nil port:0]; } - (id) initWithAddress:(NSString *)address port:(uint16_t)port { BOOL OK = NO; NSDictionary *parameters = nil; if (address == nil) address = @"127.0.0.1"; if (port == 0) port = kOOTCPConsolePort; self = [super init]; if (self != nil) { _host = [NSHost hostWithName:address]; if (_host != nil) { [_host retain]; [NSStream getStreamsToHost:_host port:port inputStream:&_inStream outputStream:&_outStream]; } if (_inStream != nil && _outStream != nil) { [_inStream retain]; [_outStream retain]; [_inStream setDelegate:self]; [_outStream setDelegate:self]; [_inStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [_outStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [_inStream open]; [_outStream open]; // Need to wait for the streams to reach open status before we can send packets // TODO: Might be neater to use the handleEvent callback to flag this.. - Micha 20090425 NSRunLoop * myRunLoop = [NSRunLoop currentRunLoop]; NSDate * timeOut = [NSDate dateWithTimeIntervalSinceNow:3]; // Wait up to 3 seconds while( _host != nil && ([_inStream streamStatus] < 2 || [_outStream streamStatus] < 2) && [myRunLoop runMode:NSDefaultRunLoopMode beforeDate:timeOut] ) ; // Wait _decoder = OOTCPStreamDecoderCreate(DecoderPacket, DecoderError, NULL, self); } if (_decoder != NULL) { OK = YES; _status = kOOTCPClientStartedConnectionStage1; // Attempt to connect parameters = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithUnsignedInt:kOOTCPProtocolVersion_1_1_0], kOOTCPProtocolVersion, [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"], kOOTCPOoliteVersion, nil]; [self sendPacket:kOOTCPPacket_RequestConnection withParameters:parameters]; if (_status == kOOTCPClientStartedConnectionStage1) _status = kOOTCPClientStartedConnectionStage2; else OK = NO; // Connection failed. } if (!OK) { OOLog(@"debugTCP.connect.failed", @"Failed to connect to debug console at address %@:%i.", address, port); [self release]; self = nil; } } return self; } - (void) dealloc { if (StatusIsSendable(_status)) { [self disconnectFromServerWithMessage:@"TCP console bridge unexpectedly released while active."]; } if (_monitor) { [_monitor disconnectDebugger:self message:@"TCP console bridge unexpectedly released while active."]; } [self closeConnection]; OOTCPStreamDecoderDestroy(_decoder); _decoder = NULL; [super dealloc]; } - (BOOL)connectDebugMonitor:(in OODebugMonitor *)debugMonitor errorMessage:(out NSString **)message { if (_status == kOOTCPClientConnectionRefused) { if (message != NULL) *message = @"Connection refused."; return NO; } if (_status == kOOTCPClientDisconnected) { if (message != NULL) *message = @"Cannot reconnect after disconnecting."; return NO; } _monitor = debugMonitor; return YES; } - (void)disconnectDebugMonitor:(in OODebugMonitor *)debugMonitor message:(in NSString *)message { [self disconnectFromServerWithMessage:message]; _monitor = nil; } - (oneway void)debugMonitor:(in OODebugMonitor *)debugMonitor jsConsoleOutput:(in NSString *)output colorKey:(in NSString *)colorKey emphasisRange:(in NSRange)emphasisRange { NSMutableDictionary *parameters = nil; NSArray *range = nil; parameters = [NSMutableDictionary dictionaryWithCapacity:3]; [parameters setObject:output forKey:kOOTCPMessage]; [parameters setObject:colorKey ? colorKey : (NSString *)@"general" forKey:kOOTCPColorKey]; if (emphasisRange.length != 0) { range = [NSArray arrayWithObjects: [NSNumber numberWithUnsignedInteger:emphasisRange.location], [NSNumber numberWithUnsignedInteger:emphasisRange.length], nil]; [parameters setObject:range forKey:kOOTCPEmphasisRanges]; } [self sendPacket:kOOTCPPacket_ConsoleOutput withParameters:parameters]; } - (oneway void)debugMonitorClearConsole:(in OODebugMonitor *)debugMonitor { [self sendPacket:kOOTCPPacket_ClearConsole withParameters:nil]; } - (oneway void)debugMonitorShowConsole:(in OODebugMonitor *)debugMonitor { [self sendPacket:kOOTCPPacket_ShowConsole withParameters:nil]; } - (oneway void)debugMonitor:(in OODebugMonitor *)debugMonitor noteConfiguration:(in NSDictionary *)configuration { [self sendPacket:kOOTCPPacket_NoteConfiguration withValue:configuration forParameter:kOOTCPConfiguration]; } - (oneway void)debugMonitor:(in OODebugMonitor *)debugMonitor noteChangedConfigrationValue:(in id)newValue forKey:(in NSString *)key { if (newValue != nil) { [self sendPacket:kOOTCPPacket_NoteConfiguration withValue:[NSDictionary dictionaryWithObject:newValue forKey:key] forParameter:kOOTCPConfiguration]; } else { [self sendPacket:kOOTCPPacket_NoteConfiguration withValue:[NSArray arrayWithObject:key] forParameter:kOOTCPRemovedConfigurationKeys]; } } - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode { if (_status > kOOTCPClientConnected) return; if (stream == _inStream && eventCode == NSStreamEventHasBytesAvailable) { [self readData]; } else if (eventCode == NSStreamEventErrorOccurred) { [self breakConnectionWithBadStream:stream]; } else if (eventCode == NSStreamEventErrorOccurred) { [self breakConnectionWithMessage:[NSString stringWithFormat: @"Console closed the connection."]]; } } @end @implementation OODebugTCPConsoleClient (OOPrivate) - (void) closeConnection { [_inStream close]; [_inStream setDelegate:nil]; [_inStream release]; _inStream = nil; [_outStream close]; [_outStream setDelegate:nil]; [_outStream release]; _outStream = nil; [_host release]; _host = nil; } - (BOOL) sendBytes:(const void *)bytes count:(size_t)count { if (bytes == NULL || count == 0) return YES; if (!StatusIsSendable(_status) || _outStream == nil) return NO; do { NSInteger written = [_outStream write:bytes maxLength:count]; if (written < 1) return NO; count -= written; bytes += written; } while (count > 0); return YES; } - (void) sendDictionary:(NSDictionary *)dictionary { NSData *data = nil; NSString *errorDesc = NULL; size_t count; const uint8_t *bytes = NULL; uint32_t header; bool sentOK = YES; if (dictionary == nil || !StatusIsSendable(_status)) return; data = [NSPropertyListSerialization dataFromPropertyList:dictionary format:NSPropertyListXMLFormat_v1_0 errorDescription:&errorDesc]; if (data == nil) { OOLog(@"debugTCP.conversionFailure", @"Could not convert dictionary to data for transmission to debug console: %@", errorDesc != NULL ? errorDesc : (NSString *)@"unknown error."); #if OOLITE_RELEASE_PLIST_ERROR_STRINGS [errorDesc autorelease]; #endif return; } LogSendPacket(dictionary); count = [data length]; if (count == 0) return; header = htonl(count); bytes = [data bytes]; /* In testing, all bad stream errors were caused by the python console rejecting headers. Made the protocol a bit more fault tolerant. -- Kaks 2012.03.24 */ if (![self sendBytes:&header count:sizeof header]) { OOLog(@"debugTCP.send.warning", @"Error sending packet header, retrying."); // wait 8 milliseconds, resend the header [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:.008]]; if (![self sendBytes:&header count:sizeof header]) { //OOLog(@"debugTCP.send.warning", @"Error sending packet header, retrying one more time."); // wait 16 milliseconds, try to resend the header one last time! [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:.016]]; if (![self sendBytes:&header count:sizeof header]) { sentOK = NO; } } } if(sentOK && ![self sendBytes:bytes count:count]) { OOLog(@"debugTCP.send.warning", @"Error sending packet body, retrying."); // wait 8 milliseconds, try again. [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:.008]]; if(![self sendBytes:bytes count:count]) { sentOK = NO; } } if (!sentOK) { OOLog(@"debugTCP.send.error", @"The following packet could not be sent: %@", dictionary); if(![[OODebugMonitor sharedDebugMonitor] TCPIgnoresDroppedPackets]) { [self breakConnectionWithBadStream:_outStream]; } } } - (void) sendPacket:(NSString *)packetType withParameters:(NSDictionary *)parameters { NSDictionary *dict = nil; if (packetType == nil) return; if (parameters != nil) { dict = [parameters dictionaryByAddingObject:packetType forKey:kOOTCPPacketType]; } else { dict = [NSDictionary dictionaryWithObjectsAndKeys:packetType, kOOTCPPacketType, nil]; } [self sendDictionary:dict]; } - (void) sendPacket:(NSString *)packetType withValue:(id)value forParameter:(NSString *)paramKey { if (packetType == nil) return; if (paramKey == nil) value = nil; [self sendDictionary:[NSDictionary dictionaryWithObjectsAndKeys: packetType, kOOTCPPacketType, value, paramKey, nil]]; } - (void) readData { enum { kBufferSize = 16 << 10 }; uint8_t buffer[kBufferSize]; NSInteger length; NSData *data; length = [_inStream read:buffer maxLength:kBufferSize]; while (length > 0) { data = [NSData dataWithBytesNoCopy:buffer length:length freeWhenDone:NO]; OOTCPStreamDecoderReceiveData(_decoder, data); length = [_inStream read:buffer maxLength:kBufferSize]; } } - (void) dispatchPacket:(NSDictionary *)packet ofType:(NSString *)packetType { if (packet == nil || packetType == nil) return; #define PACKET_CASE(x) else if ([packetType isEqualToString:kOOTCPPacket_##x]) { [self handle##x##Packet:packet]; } if (0) {} PACKET_CASE(ApproveConnection) PACKET_CASE(RejectConnection) PACKET_CASE(CloseConnection) PACKET_CASE(NoteConfigurationChange) PACKET_CASE(PerformCommand) PACKET_CASE(RequestConfigurationValue) PACKET_CASE(Ping) PACKET_CASE(Pong) else { OOLog(@"debugTCP.protocolError.unknownPacketType", @"Unhandled packet type %@.", packetType); } } - (void) handleApproveConnectionPacket:(NSDictionary *)packet { NSMutableString *connectedMessage = nil; NSString *consoleIdentity = nil; NSString *hostName = nil; if (_status == kOOTCPClientStartedConnectionStage2) { _status = kOOTCPClientConnected; // Build "Connected..." message with two optional parts, console identity and host name. connectedMessage = [NSMutableString stringWithString:@"Connected to debug console"]; consoleIdentity = [packet oo_stringForKey:kOOTCPConsoleIdentity]; if (consoleIdentity != nil) [connectedMessage appendFormat:@" \"%@\"", consoleIdentity]; hostName = [_host name]; if ([hostName length] != 0 && ![hostName isEqual:@"localhost"] && ![hostName isEqual:@"127.0.0.1"] && ![hostName isEqual:@"::1"]) { [connectedMessage appendFormat:@" at %@", hostName]; } OOLog(@"debugTCP.connected", @"%@.", connectedMessage); } else { OOLog(@"debugTCP.protocolError.outOfOrder", @"Got %@ packet from debug console in wrong context.", kOOTCPPacket_ApproveConnection); } } - (void) handleRejectConnectionPacket:(NSDictionary *)packet { NSString *message = nil; if (_status == kOOTCPClientStartedConnectionStage2) { _status = kOOTCPClientConnectionRefused; } else { OOLog(@"debugTCP.protocolError.outOfOrder", @"Got %@ packet from debug console in wrong context.", kOOTCPPacket_RejectConnection); } message = [packet oo_stringForKey:kOOTCPMessage]; if (message == nil) message = @"Console refused connection."; [self breakConnectionWithMessage:message]; } - (void) handleCloseConnectionPacket:(NSDictionary *)packet { NSString *message = nil; if (!StatusIsSendable(_status)) { OOLog(@"debugTCP.protocolError.outOfOrder", @"Got %@ packet from debug console in wrong context.", kOOTCPPacket_CloseConnection); } message = [packet oo_stringForKey:kOOTCPMessage]; if (message == nil) message = @"Console closed connection."; [self breakConnectionWithMessage:message]; } - (void) handleNoteConfigurationChangePacket:(NSDictionary *)packet { NSDictionary *configuration = nil; NSArray *removed = nil; NSEnumerator *keyEnum = nil; NSString *key = nil; id value = nil; if (_monitor == nil) return; configuration = [packet oo_dictionaryForKey:kOOTCPConfiguration]; if (configuration != nil) { for (keyEnum = [configuration keyEnumerator]; (key = [keyEnum nextObject]); ) { value = [configuration objectForKey:key]; [_monitor setConfigurationValue:value forKey:key]; } } removed = [configuration oo_arrayForKey:kOOTCPRemovedConfigurationKeys]; for (keyEnum = [removed objectEnumerator]; (key = [keyEnum nextObject]); ) { [_monitor setConfigurationValue:nil forKey:key]; } } - (void) handlePerformCommandPacket:(NSDictionary *)packet { NSString *message = nil; message = [packet oo_stringForKey:kOOTCPMessage]; if (message != nil) [_monitor performJSConsoleCommand:message]; } - (void) handleRequestConfigurationValuePacket:(NSDictionary *)packet { NSString *key = nil; id value = nil; key = [packet oo_stringForKey:kOOTCPConfigurationKey]; if (key != nil) { value = [_monitor configurationValueForKey:key]; [self debugMonitor:_monitor noteChangedConfigrationValue:value forKey:key]; } } - (void) handlePingPacket:(NSDictionary *)packet { id message = nil; message = [packet objectForKey:kOOTCPMessage]; [self sendPacket:kOOTCPPacket_Pong withValue:message forParameter:kOOTCPMessage]; } - (void) handlePongPacket:(NSDictionary *)packet { // Do nothing; we don't currently send pings. } - (void) disconnectFromServerWithMessage:(NSString *)message { if (StatusIsSendable(_status)) { [self sendPacket:kOOTCPPacket_CloseConnection withValue:message forParameter:kOOTCPMessage]; } [self closeConnection]; _status = kOOTCPClientDisconnected; } - (void) breakConnectionWithMessage:(NSString *)message { [self closeConnection]; if (_status != kOOTCPClientConnectionRefused) _status = kOOTCPClientDisconnected; if ([message length] > 0) { OOLog(@"debugTCP.disconnect", @"No connection to debug console: \"%@\"", message); } else { OOLog(@"debugTCP.disconnect", @"Debug console not connected."); } #if 0 // Disconnecting causes crashiness for reasons I don't understand, and isn't very important anyway. [_monitor disconnectDebugger:self message:message]; _monitor = nil; #endif } - (void) breakConnectionWithBadStream:(NSStream *)stream { NSString *errorDesc = nil; NSError *error = nil; error = [stream streamError]; errorDesc = [error localizedDescription]; if (errorDesc == nil) errorDesc = [error description]; if (errorDesc == nil) errorDesc = @"bad stream."; [self breakConnectionWithMessage:[NSString stringWithFormat: @"Connection to debug console failed: '%@' (outStream status: %li, inStream status: %li).", errorDesc, [_outStream streamStatus], [_inStream streamStatus]]]; } @end static void DecoderPacket(void *cbInfo, OOALStringRef packetType, OOALDictionaryRef packet) { [(OODebugTCPConsoleClient *)cbInfo dispatchPacket:packet ofType:packetType]; } static void DecoderError(void *cbInfo, OOALStringRef errorDesc) { [(OODebugTCPConsoleClient *)cbInfo breakConnectionWithMessage:errorDesc]; } #ifdef OO_LOG_DEBUG_PROTOCOL_PACKETS void LogOOTCPStreamDecoderPacket(NSDictionary *packet) { NSData *data = nil; NSString *xml = nil; data = [NSPropertyListSerialization dataFromPropertyList:packet format:NSPropertyListXMLFormat_v1_0 errorDescription:NULL]; xml = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; OOLog(@"debugTCP.receive", @"Received packet:\n%@", xml); } static void LogSendPacket(NSDictionary *packet) { NSData *data = nil; NSString *xml = nil; data = [NSPropertyListSerialization dataFromPropertyList:packet format:NSPropertyListXMLFormat_v1_0 errorDescription:NULL]; xml = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; OOLog(@"debugTCP.send", @"Sent packet:\n%@", xml); } #endif #endif /* OO_EXCLUDE_DEBUG_SUPPORT */ oolite-1.82/src/Core/Debug/OODebugTCPConsoleProtocol.h000066400000000000000000000243131256642440500225300ustar00rootroot00000000000000/* OODebugTCPConsoleProtocol.h Definitions used in Oolite remote debug console protocol. Oolite Debug Support Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef OOALSTR #ifdef __OBJC__ #import #define OOALSTR(x) @""x #else // C #include #define OOALSTR(x) CFSTR(x) #endif #endif enum { kOOTCPConsolePort = 0x002173 /* = 8563 */ }; /* PROTOCOL OVERVIEW The basic unit of the protocol is property lists. Property lists originate with OpenStep and are thus defined in Cocoa and GNUstep, but they're also implemented in CoreFoundation (Mac OS X) and CFLite (cross-platform), with a C interface. The protocol uses a TCP stream in each direction. Each stream consists of a series of property lists in the Apple XML encoding. Each property list is framed in the simplest possible way: it is preceeded by an unsigned 32-bit length, in network-endian order. Each length + property list combination is referred to as a packet. Every packet's property list must have a dictionary as its root element. The dictionary must contain a kOOTCPConsolePacketType key, whose value determines the meaning of the rest of the dictionary. */ /* *** Packet types *** */ /* kOOTCPPacket_RequestConnection client --> server Message sent from client (Oolite) to server (console) to request a cosole connection. Required values: kOOTCPProtocolVersion kOOTCPOoliteVersion Expected responses: kOOTCPPacket_ApproveConnection OR kOOTCPPacket_RejectConnection */ #define kOOTCPPacket_RequestConnection OOALSTR("Request Connection") /* kOOTCPPacket_ApproveConnection client <-- server Message sent in response to kOOTCPPacket_RequestConnection if connection is established successfully. Optional values: kOOTCPConsoleIdentity */ #define kOOTCPPacket_ApproveConnection OOALSTR("Approve Connection") /* kOOTCPPacket_RejectConnection client <-- server Message sent in response to kOOTCPPacket_RequestConnection if connection is not established successfully. After this message is sent, the connection is closed with no further traffic. Optional values: kOOTCPMessage Expected responses: None permitted. */ #define kOOTCPPacket_RejectConnection OOALSTR("Reject Connection") /* kOOTCPPacket_CloseConnection client <-> server Message sent by either party to cleanly close connection. Optional values: kOOTCPMessage Expected responses: None permitted. */ #define kOOTCPPacket_CloseConnection OOALSTR("Close Connection") /* kOOTCPPacket_ConsoleOutput client --> server Message sent by Oolite to print text to console. Required values: kOOTCPMessage kOOTCPColorKey Optional values: kOOTCPEmphasisRanges */ #define kOOTCPPacket_ConsoleOutput OOALSTR("Console Output") /* kOOTCPPacket_ClearConsole client --> server Message sent by Oolite to clear console output. */ #define kOOTCPPacket_ClearConsole OOALSTR("Clear Console") /* kOOTCPPacket_ShowConsole client --> server Message sent by Oolite to request that the cosole makes itself visible and active. */ #define kOOTCPPacket_ShowConsole OOALSTR("Show Console") /* kOOTCPPacket_NoteConfiguration client --> server Message sent by Oolite to appraise the console of the contents of the configuration dictionary. Sent once after the initial handshake. Required values: kOOTCPConfiguration */ #define kOOTCPPacket_NoteConfiguration OOALSTR("Note Configuration") /* kOOTCPPacket_NoteConfigurationChange client <-> server Message sent by Oolite when the contents of the configuration dictionary change, or by console to change the configuration dictionary (in which case a confirmation will be returned in form of one or more kOOTCPPacket_NoteConfigurationChange messages). For this message, kOOTCPConfiguration is a delta -- keys not contained in it are not to be removed, they are simply unchanged. Deletions are handled with kOOTCPRemovedConfigurationKeys. This key is also sent in response to a kOOTCPPacket_RequestConfigurationValue message, even if no configuration value has changed. Required values (at least one of): kOOTCPConfiguration kOOTCPRemovedConfigurationKeys */ #define kOOTCPPacket_NoteConfigurationChange OOALSTR("Note Configuration Change") /* kOOTCPPacket_PerformCommand client <-- server Message sent by console to issue a command. Required values: kOOTCPMessage */ #define kOOTCPPacket_PerformCommand OOALSTR("Perform Command") /* kOOTCPPacket_RequestConfigurationValue client <-- server Message sent by console to request a configuration value. This will result in a kOOTCPPacket_NoteConfigurationChange message being sent. If the value is nil, the response will contain a kOOTCPRemovedConfigurationKeys value. Required values: kOOTCPConfigurationKey Expected response: kOOTCPPacket_NoteConfigurationChange */ #define kOOTCPPacket_RequestConfigurationValue OOALSTR("Request Configuration Value") /* kOOTCPPacket_Ping client <-> server Must be responded to with a kOOTCPPacket_Pong message containing the same kOOTCPMessage, if any. Optional values: kOOTCPMessage Expected response: kOOTCPPacket_Pong */ #define kOOTCPPacket_Ping OOALSTR("Ping") /* kOOTCPPacket_Pong client <-> server Must be sent in response to kOOTCPPacket_Ping. If the kOOTCPPacket_Ping packet had a kOOTCPMessage, the same kOOTCPMessage value must be attached to the kOOTCPPacket_Pong. Optional values: kOOTCPMessage (required if included in ping) */ #define kOOTCPPacket_Pong OOALSTR("Pong") /* *** Value keys *** */ /* kOOTCPPacketType String indicating packet type. See above under See constants below under *** Packet types ***. */ #define kOOTCPPacketType OOALSTR("packet type") /* kOOTCPProtocolVersion Number indicating version of debug console TCP protocol. Sent with kOOTCPPacket_RequestConnection. See constants below under *** Version constants ***. */ #define kOOTCPProtocolVersion OOALSTR("protocol version") /* kOOTCPOoliteVersion String indicating the version of Oolite, for example "1.70" or "1.71.1 b2". Consists of two or more integers separated by .s, optionally followed by a space and additional information in unspecified format. Sent with kOOTCPPacket_RequestConnection. */ #define kOOTCPOoliteVersion OOALSTR("Oolite version") /* kOOTCPMessage Textual message sent with various packet types. No specified format. */ #define kOOTCPMessage OOALSTR("message") /* kOOTCPConsoleIdentity String identifying console software. No specified format. */ #define kOOTCPConsoleIdentity OOALSTR("console identity") /* kOOTCPColorKey String identifying colour/formatting to be used. The configuration dictionary contains keys of the form foo-foreground-color and foo-background-color to be used. If no colour is specified for the specified key, the key "general" should be tried. The colour values are specified as arrays of three numbers in the range 0-1, specifying RGB colours. For example, if the configuration key contains: { general-background-color = (1,1,1); general-foreground-color = (0,0,0); foo-background-color = (1,0,0); } the colour key "foo" maps to the background colour (1,0,0) and the foreground color (0,0,0). Sent with kOOTCPPacket_ConsoleOutput. */ #define kOOTCPColorKey OOALSTR("color key") /* kOOTCPEmphasisRanges An array containing an even number of integers. Each pair of integers specifies a range (in the form offset, length) which should be emphasized. Sent with kOOTCPPacket_ConsoleOutput. */ #define kOOTCPEmphasisRanges OOALSTR("emphasis ranges") /* kOOTCPConfiguration A dictionary of key/value pairs to add/set in the configuration dictionary. Sent with kOOTCPPacket_NoteConfiguration and kOOTCPPacket_NoteConfiguration. */ #define kOOTCPConfiguration OOALSTR("configuration") /* kOOTCPConfiguration An array of keys to remove from the configuration dictionary. Sent with kOOTCPPacket_NoteConfiguration. */ #define kOOTCPRemovedConfigurationKeys OOALSTR("removed configuration keys") /* kOOTCPConfigurationKey A string specifying the configuration key for which a value is requested. Sent with kOOTCPPacket_RequestConfigurationValue. */ #define kOOTCPConfigurationKey OOALSTR("configuration key") /* *** Version constants *** */ /* Version constants have three components: format, major and minor. The format version will change if the framing mechanism is changed, that is, if we switch from the property-list basted protocol in use. The major version will be changed to indicate compatibility-breaking changes. The minor version will be changed when new non-critical packets are added. */ #define OOTCP_ENCODE_VERSION(f, mj, mi) \ ((((f) << 16) & kOOTCPProtocolVersionFormatMask) | \ (((f) << 8) & kOOTCPProtocolVersionMajorMask) | \ ((mi) & kOOTCPProtocolVersionMinorMask)) #define OOTCP_VERSION_FORMAT(v) (((v) & kOOTCPProtocolVersionFormatMask) >> 16) #define OOTCP_VERSION_MAJOR(v) (((v) & kOOTCPProtocolVersionMajorMask) >> 8) #define OOTCP_VERSION_MINOR(v) ((v) & kOOTCPProtocolVersionMinorMask) enum { kOOTCPProtocolVersionFormatMask = 0x00FF0000, kOOTCPProtocolVersionMajorMask = 0x0000FF00, kOOTCPProtocolVersionMinorMask = 0x000000FF, kOOTCPProtocolVersionPListFormat = 1, // 1:1.0, first version. kOOTCPProtocolVersion_1_1_0 = OOTCP_ENCODE_VERSION(kOOTCPProtocolVersionPListFormat, 1, 0) }; oolite-1.82/src/Core/Debug/OODebuggerInterface.h000066400000000000000000000050471256642440500214360ustar00rootroot00000000000000/* OODebuggerInterface.h Protocols for communication between OODebugMonitor and OODebuggerInterface. Oolite Debug Support Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ @class OODebugMonitor; // Interface for debugger. @protocol OODebuggerInterface // Sent to establish connection. - (BOOL)connectDebugMonitor:(in OODebugMonitor *)debugMonitor errorMessage:(out NSString **)message; // Sent to close connection. - (void)disconnectDebugMonitor:(in OODebugMonitor *)debugMonitor message:(in NSString *)message; // Sent to print to the JavaScript console. // colorKey is intended to be used to look up a foreground/background colour pair // in the configuration. EmphasisRange is to specify a bold section of text. - (oneway void)debugMonitor:(in OODebugMonitor *)debugMonitor jsConsoleOutput:(in NSString *)output colorKey:(in NSString *)colorKey emphasisRange:(in NSRange)emphasisRange; // Sent to clear the JavaScript console. - (oneway void)debugMonitorClearConsole:(in OODebugMonitor *)debugMonitor; // Sent to show the console, for instance in response to a warning or error message. - (oneway void)debugMonitorShowConsole:(in OODebugMonitor *)debugMonitor; // Sent once when the debugger is connected. - (oneway void)debugMonitor:(in OODebugMonitor *)debugMonitor noteConfiguration:(in NSDictionary *)configuration; // Sent when configuration changes. newValue may be nil. - (oneway void)debugMonitor:(in OODebugMonitor *)debugMonitor noteChangedConfigrationValue:(in id)newValue forKey:(in NSString *)key; @end oolite-1.82/src/Core/Debug/OOJSConsole.h000066400000000000000000000025111256642440500177210ustar00rootroot00000000000000/* OOJSConsole.h JavaScript object representing the JavaScript console. Oolite Debug Support Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import #include @class OODebugMonitor; JSObject *DebugMonitorToJSConsole(JSContext *context, OODebugMonitor *monitor); void OOJSConsoleDestroy(void); oolite-1.82/src/Core/Debug/OOJSConsole.m000066400000000000000000001001031256642440500177220ustar00rootroot00000000000000/* OOJSConsole.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef OO_EXCLUDE_DEBUG_SUPPORT #import "OOJSConsole.h" #import "OODebugMonitor.h" #include #import "OOJSEngineTimeManagement.h" #import "OOJSScript.h" #import "OOJSVector.h" #import "OOJSEntity.h" #import "OOJSCall.h" #import "OOLoggingExtended.h" #import "OOConstToString.h" #import "OOOpenGLExtensionManager.h" #import "OODebugFlags.h" #import "OODebugMonitor.h" #import "OOProfilingStopwatch.h" #import "ResourceManager.h" @interface Entity (OODebugInspector) // Method added by inspector in Debug OXP under OS X only. - (void) inspect; @end NSString *OOPlatformDescription(void); static JSObject *sConsolePrototype = NULL; static JSObject *sConsoleSettingsPrototype = NULL; static JSBool ConsoleGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool ConsoleSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); static void ConsoleFinalize(JSContext *context, JSObject *this); // Methods static JSBool ConsoleConsoleMessage(JSContext *context, uintN argc, jsval *vp); static JSBool ConsoleClearConsole(JSContext *context, uintN argc, jsval *vp); static JSBool ConsoleScriptStack(JSContext *context, uintN argc, jsval *vp); static JSBool ConsoleInspectEntity(JSContext *context, uintN argc, jsval *vp); #if OO_DEBUG static JSBool ConsoleCallObjCMethod(JSContext *context, uintN argc, jsval *vp); static JSBool ConsoleSetUpCallObjC(JSContext *context, uintN argc, jsval *vp); #endif static JSBool ConsoleIsExecutableJavaScript(JSContext *context, uintN argc, jsval *vp); static JSBool ConsoleDisplayMessagesInClass(JSContext *context, uintN argc, jsval *vp); static JSBool ConsoleSetDisplayMessagesInClass(JSContext *context, uintN argc, jsval *vp); static JSBool ConsoleWriteLogMarker(JSContext *context, uintN argc, jsval *vp); static JSBool ConsoleWriteMemoryStats(JSContext *context, uintN argc, jsval *vp); static JSBool ConsoleWriteJSMemoryStats(JSContext *context, uintN argc, jsval *vp); static JSBool ConsoleGarbageCollect(JSContext *context, uintN argc, jsval *vp); #if DEBUG static JSBool ConsoleDumpNamedRoots(JSContext *context, uintN argc, jsval *vp); static JSBool ConsoleDumpHeap(JSContext *context, uintN argc, jsval *vp); #endif #if OOJS_PROFILE static JSBool ConsoleProfile(JSContext *context, uintN argc, jsval *vp); static JSBool ConsoleGetProfile(JSContext *context, uintN argc, jsval *vp); static JSBool ConsoleTrace(JSContext *context, uintN argc, jsval *vp); #endif static JSBool ConsoleSettingsDeleteProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool ConsoleSettingsGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool ConsoleSettingsSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); #if OOJS_PROFILE static JSBool PerformProfiling(JSContext *context, NSString *nominalFunction, uintN argc, jsval *argv, jsval *rval, BOOL trace, OOTimeProfile **profile); #endif static JSClass sConsoleClass = { "Console", JSCLASS_HAS_PRIVATE, JS_PropertyStub, // addProperty JS_PropertyStub, // delProperty ConsoleGetProperty, // getProperty ConsoleSetProperty, // setProperty JS_EnumerateStub, // enumerate JS_ResolveStub, // resolve JS_ConvertStub, // convert ConsoleFinalize, // finalize JSCLASS_NO_OPTIONAL_MEMBERS }; enum { // Property IDs kConsole_debugFlags, // debug flags, integer, read/write kConsole_detailLevel, // graphics detail level, symbolic string, read/write kConsole_maximumDetailLevel, // maximum graphics detail level, symbolic string, read-only kConsole_displayFPS, // display FPS (and related info), boolean, read/write kConsole_platformDescription, // Information about system we're running on in unspecified format, string, read-only kConsole_ignoreDroppedPackets, // boolean (default false), read/write kConsole_pedanticMode, // JS pedantic mode (JS_STRICT flag, not the same as "use strict"), boolean (default true), read/write kConsole_showErrorLocations, // Show error/warning source locations, boolean (default true), read/write kConsole_dumpStackForErrors, // Write stack dump when reporting error/exception, boolean (default false), read/write kConsole_dumpStackForWarnings, // Write stack dump when reporting warning, boolean (default false), read/write kConsole_glVendorString, // OpenGL GL_VENDOR string, string, read-only kConsole_glRendererString, // OpenGL GL_RENDERER string, string, read-only kConsole_glFixedFunctionTextureUnitCount, // GL_MAX_TEXTURE_UNITS_ARB, integer, read-only kConsole_glFragmentShaderTextureUnitCount, // GL_MAX_TEXTURE_IMAGE_UNITS_ARB, integer, read-only // Symbolic constants for debug flags: kConsole_DEBUG_LINKED_LISTS, kConsole_DEBUG_COLLISIONS, kConsole_DEBUG_DOCKING, kConsole_DEBUG_OCTREE_LOGGING, kConsole_DEBUG_BOUNDING_BOXES, kConsole_DEBUG_OCTREE_DRAW, kConsole_DEBUG_DRAW_NORMALS, kConsole_DEBUG_NO_DUST, kConsole_DEBUG_NO_SHADER_FALLBACK, kConsole_DEBUG_SHADER_VALIDATION, kConsole_DEBUG_MISC }; static JSPropertySpec sConsoleProperties[] = { // JS name ID flags { "debugFlags", kConsole_debugFlags, OOJS_PROP_READWRITE_CB }, { "detailLevel", kConsole_detailLevel, OOJS_PROP_READWRITE_CB }, { "maximumDetailLevel", kConsole_maximumDetailLevel, OOJS_PROP_READONLY_CB }, { "displayFPS", kConsole_displayFPS, OOJS_PROP_READWRITE_CB }, { "platformDescription", kConsole_platformDescription, OOJS_PROP_READONLY_CB }, { "pedanticMode", kConsole_pedanticMode, OOJS_PROP_READWRITE_CB }, { "ignoreDroppedPackets", kConsole_ignoreDroppedPackets, OOJS_PROP_READWRITE_CB }, { "__showErrorLocations", kConsole_showErrorLocations, OOJS_PROP_HIDDEN_READWRITE_CB }, { "__dumpStackForErrors", kConsole_dumpStackForErrors, OOJS_PROP_HIDDEN_READWRITE_CB }, { "__dumpStackForWarnings", kConsole_dumpStackForWarnings, OOJS_PROP_HIDDEN_READWRITE_CB }, { "glVendorString", kConsole_glVendorString, OOJS_PROP_READONLY_CB }, { "glRendererString", kConsole_glRendererString, OOJS_PROP_READONLY_CB }, { "glFixedFunctionTextureUnitCount", kConsole_glFixedFunctionTextureUnitCount, OOJS_PROP_READONLY_CB }, { "glFragmentShaderTextureUnitCount", kConsole_glFragmentShaderTextureUnitCount, OOJS_PROP_READONLY_CB }, #define DEBUG_FLAG_DECL(x) { #x, kConsole_##x, OOJS_PROP_READONLY_CB } DEBUG_FLAG_DECL(DEBUG_LINKED_LISTS), DEBUG_FLAG_DECL(DEBUG_COLLISIONS), DEBUG_FLAG_DECL(DEBUG_DOCKING), DEBUG_FLAG_DECL(DEBUG_OCTREE_LOGGING), DEBUG_FLAG_DECL(DEBUG_BOUNDING_BOXES), DEBUG_FLAG_DECL(DEBUG_OCTREE_DRAW), DEBUG_FLAG_DECL(DEBUG_DRAW_NORMALS), DEBUG_FLAG_DECL(DEBUG_NO_DUST), DEBUG_FLAG_DECL(DEBUG_NO_SHADER_FALLBACK), DEBUG_FLAG_DECL(DEBUG_SHADER_VALIDATION), DEBUG_FLAG_DECL(DEBUG_MISC), #undef DEBUG_FLAG_DECL { 0 } }; static JSFunctionSpec sConsoleMethods[] = { // JS name Function min args { "consoleMessage", ConsoleConsoleMessage, 2 }, { "clearConsole", ConsoleClearConsole, 0 }, { "scriptStack", ConsoleScriptStack, 0 }, { "inspectEntity", ConsoleInspectEntity, 1 }, #if OO_DEBUG { "__setUpCallObjC", ConsoleSetUpCallObjC, 1 }, #endif { "isExecutableJavaScript", ConsoleIsExecutableJavaScript, 2 }, { "displayMessagesInClass", ConsoleDisplayMessagesInClass, 1 }, { "setDisplayMessagesInClass", ConsoleSetDisplayMessagesInClass, 2 }, { "writeLogMarker", ConsoleWriteLogMarker, 0 }, { "writeMemoryStats", ConsoleWriteMemoryStats, 0 }, { "writeJSMemoryStats", ConsoleWriteJSMemoryStats, 0 }, { "garbageCollect", ConsoleGarbageCollect, 0 }, #if DEBUG { "dumpNamedRoots", ConsoleDumpNamedRoots, 0 }, { "dumpHeap", ConsoleDumpHeap, 0 }, #endif #if OOJS_PROFILE { "profile", ConsoleProfile, 1 }, { "getProfile", ConsoleGetProfile, 1 }, { "trace", ConsoleTrace, 1 }, #endif { 0 } }; static JSClass sConsoleSettingsClass = { "ConsoleSettings", JSCLASS_HAS_PRIVATE, JS_PropertyStub, // addProperty ConsoleSettingsDeleteProperty, // delProperty ConsoleSettingsGetProperty, // getProperty ConsoleSettingsSetProperty, // setProperty JS_EnumerateStub, // enumerate. FIXME: this should work. JS_ResolveStub, // resolve JS_ConvertStub, // convert ConsoleFinalize, // finalize (same as Console) JSCLASS_NO_OPTIONAL_MEMBERS }; static void InitOOJSConsole(JSContext *context, JSObject *global) { sConsolePrototype = JS_InitClass(context, global, NULL, &sConsoleClass, OOJSUnconstructableConstruct, 0, sConsoleProperties, sConsoleMethods, NULL, NULL); OOJSRegisterObjectConverter(&sConsoleClass, OOJSBasicPrivateObjectConverter); sConsoleSettingsPrototype = JS_InitClass(context, global, NULL, &sConsoleSettingsClass, OOJSUnconstructableConstruct, 0, NULL, NULL, NULL, NULL); OOJSRegisterObjectConverter(&sConsoleSettingsClass, OOJSBasicPrivateObjectConverter); } void OOJSConsoleDestroy(void) { sConsolePrototype = NULL; } JSObject *DebugMonitorToJSConsole(JSContext *context, OODebugMonitor *monitor) { OOJS_PROFILE_ENTER OOJavaScriptEngine *engine = nil; JSObject *object = NULL; JSObject *settingsObject = NULL; jsval value; NSCAssert(JS_EnterLocalRootScope(context), @"Failed to create JS GC root scope"); engine = [OOJavaScriptEngine sharedEngine]; if (sConsolePrototype == NULL) { InitOOJSConsole(context, [engine globalObject]); } // Create Console object object = JS_NewObject(context, &sConsoleClass, sConsolePrototype, NULL); if (object != NULL) { if (!JS_SetPrivate(context, object, [monitor weakRetain])) object = NULL; } if (object != NULL) { // Create ConsoleSettings object settingsObject = JS_NewObject(context, &sConsoleSettingsClass, sConsoleSettingsPrototype, NULL); if (settingsObject != NULL) { if (!JS_SetPrivate(context, settingsObject, [monitor weakRetain])) settingsObject = NULL; } if (settingsObject != NULL) { value = OBJECT_TO_JSVAL(settingsObject); if (!JS_SetProperty(context, object, "settings", &value)) { settingsObject = NULL; } } if (settingsObject == NULL) object = NULL; } JS_LeaveLocalRootScope(context); return object; // Analyzer: object leaked. (x2) [Expected, objects are retained by JS object.] OOJS_PROFILE_EXIT } static JSBool ConsoleGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) switch (JSID_TO_INT(propID)) { #ifndef NDEBUG case kConsole_debugFlags: *value = INT_TO_JSVAL((uint32_t)gDebugFlags); break; #endif case kConsole_detailLevel: *value = [OOStringFromGraphicsDetail([UNIVERSE detailLevel]) oo_jsValueInContext:context]; break; case kConsole_maximumDetailLevel: *value = [OOStringFromGraphicsDetail([[OOOpenGLExtensionManager sharedManager] maximumDetailLevel]) oo_jsValueInContext:context]; break; case kConsole_displayFPS: *value = OOJSValueFromBOOL([UNIVERSE displayFPS]); break; case kConsole_platformDescription: *value = OOJSValueFromNativeObject(context, OOPlatformDescription()); break; case kConsole_pedanticMode: { uint32_t options = JS_GetOptions(context); *value = OOJSValueFromBOOL(options & JSOPTION_STRICT); } break; case kConsole_ignoreDroppedPackets: *value = OOJSValueFromBOOL([[OODebugMonitor sharedDebugMonitor] TCPIgnoresDroppedPackets]); break; case kConsole_showErrorLocations: *value = OOJSValueFromBOOL([[OOJavaScriptEngine sharedEngine] showErrorLocations]); break; case kConsole_dumpStackForErrors: *value = OOJSValueFromBOOL([[OOJavaScriptEngine sharedEngine] dumpStackForErrors]); break; case kConsole_dumpStackForWarnings: *value = OOJSValueFromBOOL([[OOJavaScriptEngine sharedEngine] dumpStackForWarnings]); break; case kConsole_glVendorString: *value = OOJSValueFromNativeObject(context, [[OOOpenGLExtensionManager sharedManager] vendorString]); break; case kConsole_glRendererString: *value = OOJSValueFromNativeObject(context, [[OOOpenGLExtensionManager sharedManager] rendererString]); break; case kConsole_glFixedFunctionTextureUnitCount: *value = INT_TO_JSVAL([[OOOpenGLExtensionManager sharedManager] textureUnitCount]); break; case kConsole_glFragmentShaderTextureUnitCount: *value = INT_TO_JSVAL([[OOOpenGLExtensionManager sharedManager] textureImageUnitCount]); break; #define DEBUG_FLAG_CASE(x) case kConsole_##x: *value = INT_TO_JSVAL(x); break; DEBUG_FLAG_CASE(DEBUG_LINKED_LISTS); DEBUG_FLAG_CASE(DEBUG_COLLISIONS); DEBUG_FLAG_CASE(DEBUG_DOCKING); DEBUG_FLAG_CASE(DEBUG_OCTREE_LOGGING); DEBUG_FLAG_CASE(DEBUG_BOUNDING_BOXES); DEBUG_FLAG_CASE(DEBUG_OCTREE_DRAW); DEBUG_FLAG_CASE(DEBUG_DRAW_NORMALS); DEBUG_FLAG_CASE(DEBUG_NO_DUST); DEBUG_FLAG_CASE(DEBUG_NO_SHADER_FALLBACK); DEBUG_FLAG_CASE(DEBUG_SHADER_VALIDATION); DEBUG_FLAG_CASE(DEBUG_MISC); #undef DEBUG_FLAG_CASE default: OOJSReportBadPropertySelector(context, this, propID, sConsoleProperties); return NO; } return YES; OOJS_NATIVE_EXIT } static JSBool ConsoleSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) int32 iValue; JSBool bValue = NO; NSString *sValue; switch (JSID_TO_INT(propID)) { #ifndef NDEBUG case kConsole_debugFlags: if (JS_ValueToInt32(context, *value, &iValue)) { gDebugFlags = iValue; } break; #endif case kConsole_detailLevel: sValue = OOStringFromJSValue(context, *value); OOJS_BEGIN_FULL_NATIVE(context) [UNIVERSE setDetailLevel:OOGraphicsDetailFromString(sValue)]; OOJS_END_FULL_NATIVE break; case kConsole_displayFPS: if (JS_ValueToBoolean(context, *value, &bValue)) { [UNIVERSE setDisplayFPS:bValue]; } break; case kConsole_pedanticMode: if (JS_ValueToBoolean(context, *value, &bValue)) { uint32_t options = JS_GetOptions(context); if (bValue) options |= JSOPTION_STRICT; else options &= ~JSOPTION_STRICT; JS_SetOptions(context, options); } break; case kConsole_ignoreDroppedPackets: if (JS_ValueToBoolean(context, *value, &bValue)) { [[OODebugMonitor sharedDebugMonitor] setTCPIgnoresDroppedPackets:bValue]; } break; case kConsole_showErrorLocations: if (JS_ValueToBoolean(context, *value, &bValue)) { [[OOJavaScriptEngine sharedEngine] setShowErrorLocations:bValue]; } break; case kConsole_dumpStackForErrors: if (JS_ValueToBoolean(context, *value, &bValue)) { [[OOJavaScriptEngine sharedEngine] setDumpStackForErrors:bValue]; } break; case kConsole_dumpStackForWarnings: if (JS_ValueToBoolean(context, *value, &bValue)) { [[OOJavaScriptEngine sharedEngine] setDumpStackForWarnings:bValue]; } break; default: OOJSReportBadPropertySelector(context, this, propID, sConsoleProperties); return NO; } return YES; OOJS_NATIVE_EXIT } static BOOL DoWeDefineAllDebugFlags(enum OODebugFlags flags) GCC_ATTR((unused)); static BOOL DoWeDefineAllDebugFlags(enum OODebugFlags flags) { /* This function doesn't do anything, but will generate a warning (Enumeration value 'DEBUG_FOO' not handled in switch) if a debug flag is added without updating it. The point is that if you get such a warning, you should first add a JS symbolic constant for the flag, then add it to the switch to supress the warning. NOTE: don't add a default: to this switch, or I will have to hurt you. -- Ahruman 2010-04-11 */ switch (flags) { case DEBUG_LINKED_LISTS: case DEBUG_COLLISIONS: case DEBUG_DOCKING: case DEBUG_OCTREE_LOGGING: case DEBUG_BOUNDING_BOXES: case DEBUG_OCTREE_DRAW: case DEBUG_DRAW_NORMALS: case DEBUG_NO_DUST: case DEBUG_NO_SHADER_FALLBACK: case DEBUG_SHADER_VALIDATION: case DEBUG_MISC: return YES; } return NO; } static void ConsoleFinalize(JSContext *context, JSObject *this) { OOJS_PROFILE_ENTER [(id)JS_GetPrivate(context, this) release]; JS_SetPrivate(context, this, nil); OOJS_PROFILE_EXIT_VOID } static JSBool ConsoleSettingsDeleteProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { OOJS_NATIVE_ENTER(context) NSString *key = nil; id monitor = nil; if (!JSID_IS_STRING(propID)) return NO; key = OOStringFromJSString(context, JSID_TO_STRING(propID)); monitor = OOJSNativeObjectFromJSObject(context, this); if (![monitor isKindOfClass:[OODebugMonitor class]]) { OOJSReportError(context, @"Expected OODebugMonitor, got %@ in %s. %@", [monitor class], __PRETTY_FUNCTION__, @"This is an internal error, please report it."); return NO; } [monitor setConfigurationValue:nil forKey:key]; *value = JSVAL_TRUE; return YES; OOJS_NATIVE_EXIT } static JSBool ConsoleSettingsGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_STRING(propID)) return YES; OOJS_NATIVE_ENTER(context) NSString *key = nil; id settingValue = nil; id monitor = nil; key = OOStringFromJSString(context, JSID_TO_STRING(propID)); monitor = OOJSNativeObjectFromJSObject(context, this); if (![monitor isKindOfClass:[OODebugMonitor class]]) { OOJSReportError(context, @"Expected OODebugMonitor, got %@ in %s. %@", [monitor class], __PRETTY_FUNCTION__, @"This is an internal error, please report it."); return NO; } settingValue = [monitor configurationValueForKey:key]; if (settingValue != NULL) *value = [settingValue oo_jsValueInContext:context]; else *value = JSVAL_VOID; return YES; OOJS_NATIVE_EXIT } static JSBool ConsoleSettingsSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { if (!JSID_IS_STRING(propID)) return YES; OOJS_NATIVE_ENTER(context) NSString *key = nil; id settingValue = nil; id monitor = nil; key = OOStringFromJSString(context, JSID_TO_STRING(propID)); monitor = OOJSNativeObjectFromJSObject(context, this); if (![monitor isKindOfClass:[OODebugMonitor class]]) { OOJSReportError(context, @"Expected OODebugMonitor, got %@ in %s. %@", [monitor class], __PRETTY_FUNCTION__, @"This is an internal error, please report it."); return NO; } // Not OOJS_BEGIN_FULL_NATIVE() - we use JSAPI while paused. OOJSPauseTimeLimiter(); if (JSVAL_IS_NULL(*value) || JSVAL_IS_VOID(*value)) { [monitor setConfigurationValue:nil forKey:key]; } else { settingValue = OOJSNativeObjectFromJSValue(context, *value); if (settingValue != nil) { [monitor setConfigurationValue:settingValue forKey:key]; } else { OOJSReportWarning(context, @"debugConsole.settings: could not convert %@ to native object.", OOStringFromJSValue(context, *value)); } } OOJSResumeTimeLimiter(); return YES; OOJS_NATIVE_EXIT } // *** Methods *** // function consoleMessage(colorCode : String, message : String [, emphasisStart : Number, emphasisLength : Number]) : void static JSBool ConsoleConsoleMessage(JSContext *context, uintN argc, jsval *vp) { NSRange emphasisRange = {0, 0}; OOJS_NATIVE_ENTER(context) id monitor = nil; NSString *colorKey = nil, *message = nil; jsdouble location, length; // Not OOJS_BEGIN_FULL_NATIVE() - we use JSAPI while paused. OOJSPauseTimeLimiter(); monitor = OOJSNativeObjectOfClassFromJSObject(context, OOJS_THIS, [OODebugMonitor class]); if (monitor == nil) { OOJSReportError(context, @"Expected OODebugMonitor, got %@ in %s. %@", [monitor class], __PRETTY_FUNCTION__, @"This is an internal error, please report it."); OOJSResumeTimeLimiter(); return NO; } if (argc > 0) colorKey = OOStringFromJSValue(context,OOJS_ARGV[0]); if (argc > 1) message = OOStringFromJSValue(context,OOJS_ARGV[1]); if (argc > 3) { // Attempt to get two numbers, specifying an emphasis range. if (JS_ValueToNumber(context, OOJS_ARGV[2], &location) && JS_ValueToNumber(context, OOJS_ARGV[3], &length)) { emphasisRange = (NSRange){location, length}; } } if (message == nil) { if (colorKey == nil) { OOJSReportWarning(context, @"Console.consoleMessage() called with no parameters."); } else { message = colorKey; colorKey = @"command-result"; } } if (message != nil) { [monitor appendJSConsoleLine:message colorKey:colorKey emphasisRange:emphasisRange]; } OOJSResumeTimeLimiter(); OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // function clearConsole() : void static JSBool ConsoleClearConsole(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) id monitor = nil; monitor = OOJSNativeObjectFromJSObject(context, OOJS_THIS); if (![monitor isKindOfClass:[OODebugMonitor class]]) { OOJSReportError(context, @"Expected OODebugMonitor, got %@ in %s. %@", [monitor class], __PRETTY_FUNCTION__, @"This is an internal error, please report it."); return NO; } [monitor clearJSConsole]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // function scriptStack() : Array static JSBool ConsoleScriptStack(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOJS_RETURN_OBJECT([OOJSScript scriptStack]); OOJS_NATIVE_EXIT } // function inspectEntity(entity : Entity) : void static JSBool ConsoleInspectEntity(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) Entity *entity = nil; if (JSValueToEntity(context, OOJS_ARGV[0], &entity)) { OOJS_BEGIN_FULL_NATIVE(context) if ([entity respondsToSelector:@selector(inspect)]) { [entity inspect]; } OOJS_END_FULL_NATIVE } OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } #if OO_DEBUG // function callObjC(selector : String [, ...]) : Object static JSBool ConsoleCallObjCMethod(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) id object = nil; jsval result; BOOL OK; object = OOJSNativeObjectFromJSObject(context, OOJS_THIS); if (object == nil) { OOJSReportError(context, @"Attempt to call __callObjCMethod() for non-Objective-C object %@.", OOStringFromJSValueEvenIfNull(context, JS_THIS(context, vp))); return NO; } OOJSPauseTimeLimiter(); result = JSVAL_VOID; OK = OOJSCallObjCObjectMethod(context, object, [object oo_jsClassName], argc, OOJS_ARGV, &result); OOJSResumeTimeLimiter(); OOJS_SET_RVAL(result); return OK; OOJS_NATIVE_EXIT } // function __setUpCallObjC(object) -- object is expected to be Object.prototye. static JSBool ConsoleSetUpCallObjC(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) if (EXPECT_NOT(!JSVAL_IS_OBJECT(OOJS_ARGV[0]))) { OOJSReportBadArguments(context, @"Console", @"__setUpCallObjC", argc, OOJS_ARGV, nil, @"Object.prototype"); return NO; } JSObject *obj = JSVAL_TO_OBJECT(OOJS_ARGV[0]); JS_DefineFunction(context, obj, "callObjC", ConsoleCallObjCMethod, 1, OOJS_METHOD_READONLY); OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } #endif // function isExecutableJavaScript(this : Object, string : String) : Boolean static JSBool ConsoleIsExecutableJavaScript(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) BOOL result = NO; JSObject *target = NULL; if (argc < 2 || !JS_ValueToObject(context, OOJS_ARGV[0], &target) || !JSVAL_IS_STRING(OOJS_ARGV[1])) { OOJS_RETURN_BOOL(NO); // Fail silently } // Not OOJS_BEGIN_FULL_NATIVE() - we use JSAPI while paused. OOJSPauseTimeLimiter(); // FIXME: this must be possible using just JSAPI functions. NSString *string = OOStringFromJSValue(context, OOJS_ARGV[1]); NSData *stringData = [string dataUsingEncoding:NSUTF8StringEncoding]; result = JS_BufferIsCompilableUnit(context, target, [stringData bytes], [stringData length]); OOJSResumeTimeLimiter(); OOJS_RETURN_BOOL(result); OOJS_NATIVE_EXIT } // function displayMessagesInClass(class : String) : Boolean static JSBool ConsoleDisplayMessagesInClass(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *messageClass = nil; messageClass = OOStringFromJSValue(context, OOJS_ARGV[0]); OOJS_RETURN_BOOL(messageClass != nil && OOLogWillDisplayMessagesInClass(messageClass)); OOJS_NATIVE_EXIT } // function setDisplayMessagesInClass(class : String, flag : Boolean) : void static JSBool ConsoleSetDisplayMessagesInClass(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *messageClass = nil; JSBool flag; messageClass = OOStringFromJSValue(context, OOJS_ARGV[0]); if (messageClass != nil && JS_ValueToBoolean(context, OOJS_ARGV[1], &flag)) { OOLogSetDisplayMessagesInClass(messageClass, flag); } OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // function writeLogMarker() : void static JSBool ConsoleWriteLogMarker(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOLogInsertMarker(); OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // function writeMemoryStats() : void static JSBool ConsoleWriteMemoryStats(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOJS_BEGIN_FULL_NATIVE(context) [[OODebugMonitor sharedDebugMonitor] dumpMemoryStatistics]; OOJS_END_FULL_NATIVE OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // function writeJSMemoryStats() : void static JSBool ConsoleWriteJSMemoryStats(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOJS_BEGIN_FULL_NATIVE(context) [[OODebugMonitor sharedDebugMonitor] dumpJSMemoryStatistics]; OOJS_END_FULL_NATIVE OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // function garbageCollect() : string static JSBool ConsoleGarbageCollect(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) uint32_t bytesBefore = JS_GetGCParameter(JS_GetRuntime(context), JSGC_BYTES); JS_GC(context); uint32_t bytesAfter = JS_GetGCParameter(JS_GetRuntime(context), JSGC_BYTES); OOJS_RETURN_OBJECT(([NSString stringWithFormat:@"Bytes before: %u Bytes after: %u", bytesBefore, bytesAfter])); OOJS_NATIVE_EXIT } #if DEBUG typedef struct { JSContext *context; FILE *file; } DumpCallbackData; static void DumpCallback(const char *name, void *rp, JSGCRootType type, void *datap) { assert(type == JS_GC_ROOT_VALUE_PTR || type == JS_GC_ROOT_GCTHING_PTR); DumpCallbackData *data = datap; const char *typeString = "unknown type"; jsval value; switch (type) { case JS_GC_ROOT_VALUE_PTR: typeString = "value"; value = *(jsval *)rp; break; case JS_GC_ROOT_GCTHING_PTR: typeString = "gc-thing"; value = OBJECT_TO_JSVAL(*(JSObject **)rp); } fprintf(data->file, "%s @ %p (%s): %s\n", name, rp, typeString, [OOJSDescribeValue(data->context, value, NO) UTF8String]); } static JSBool ConsoleDumpNamedRoots(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; BOOL OK = NO; NSString *path = [[ResourceManager diagnosticFileLocation] stringByAppendingPathComponent:@"js-roots.txt"]; FILE *file = fopen([path UTF8String], "w"); if (file != NULL) { DumpCallbackData data = { .context = context, .file = file }; JS_DumpNamedRoots(JS_GetRuntime(context), DumpCallback, &data); fclose(file); OK = YES; } [pool release]; OOJS_RETURN_BOOL(OK); OOJS_NATIVE_EXIT } static JSBool ConsoleDumpHeap(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) BOOL OK = NO; NSString *path = [[ResourceManager diagnosticFileLocation] stringByAppendingPathComponent:@"js-heaps.txt"]; FILE *file = fopen([path UTF8String], "w"); if (file != NULL) { OK = JS_DumpHeap(context, file, NULL, 0, NULL, SIZE_MAX, NULL); fclose(file); } OOJS_RETURN_BOOL(OK); OOJS_NATIVE_EXIT } #endif #if OOJS_PROFILE // function profile(func : function [, Object this = debugConsole.script]) : String static JSBool ConsoleProfile(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) if (EXPECT_NOT(OOJSIsProfiling())) { OOJSReportError(context, @"Profiling functions may not be called while already profiling."); return NO; } NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; OOTimeProfile *profile = nil; JSBool result = PerformProfiling(context, @"profile", argc, OOJS_ARGV, NULL, NO, &profile); if (result) { OOJS_SET_RVAL(OOJSValueFromNativeObject(context, [profile description])); } [pool release]; return result; OOJS_NATIVE_EXIT } // function getProfile(func : function [, Object this = debugConsole.script]) : Object { totalTime : Number, jsTime : Number, extensionTime : Number } static JSBool ConsoleGetProfile(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) if (EXPECT_NOT(OOJSIsProfiling())) { OOJSReportError(context, @"Profiling functions may not be called while already profiling."); return NO; } NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; OOTimeProfile *profile = nil; JSBool result = PerformProfiling(context, @"getProfile", argc, OOJS_ARGV, NULL, NO, &profile); if (result) { OOJS_SET_RVAL(OOJSValueFromNativeObject(context, profile)); } [pool release]; return result; OOJS_NATIVE_EXIT } // function trace(func : function [, Object this = debugConsole.script]) : [return type of func] static JSBool ConsoleTrace(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) if (EXPECT_NOT(OOJSIsProfiling())) { OOJSReportError(context, @"Profiling functions may not be called while already profiling."); return NO; } NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; jsval rval; JSBool result = PerformProfiling(context, @"trace", argc, OOJS_ARGV, &rval, YES, NULL); if (result) { OOJS_SET_RVAL(rval); } [pool release]; return result; OOJS_NATIVE_EXIT } static JSBool PerformProfiling(JSContext *context, NSString *nominalFunction, uintN argc, jsval *argv, jsval *outRval, BOOL trace, OOTimeProfile **outProfile) { // Get function. jsval function = argv[0]; if (!OOJSValueIsFunction(context, function)) { OOJSReportBadArguments(context, @"Console", nominalFunction, 1, argv, nil, @"function"); return NO; } // Get "this" object. jsval this; if (argc > 1) this = argv[1]; else { jsval debugConsole = OOJSValueFromNativeObject(context, [OODebugMonitor sharedDebugMonitor]); assert(JSVAL_IS_OBJECT(debugConsole) && !JSVAL_IS_NULL(debugConsole)); JS_GetProperty(context, JSVAL_TO_OBJECT(debugConsole), "script", &this); } JSObject *thisObj; if (!JS_ValueToObject(context, this, &thisObj)) thisObj = NULL; jsval ignored; if (outRval == NULL) outRval = &ignored; // Fiddle with time limiter. // We want to save the current limit, reset the limiter, and set the time limit to a long time. #define LONG_TIME (1e7) // A long time - 115.7 days - but, crucially, finite. OOTimeDelta originalLimit = OOJSGetTimeLimiterLimit(); OOJSSetTimeLimiterLimit(LONG_TIME); OOJSResetTimeLimiter(); OOJSBeginProfiling(trace); // Call the function. BOOL result = JS_CallFunctionValue(context, thisObj, function, 0, NULL, outRval); // Get results. OOTimeProfile *profile = OOJSEndProfiling(); if (outProfile != NULL) *outProfile = profile; // Restore original timer state. OOJSSetTimeLimiterLimit(originalLimit); OOJSResetTimeLimiter(); JS_ReportPendingException(context); return result; } #endif // OOJS_PROFILE #endif /* OO_EXCLUDE_DEBUG_SUPPORT */ oolite-1.82/src/Core/Debug/OOTCPStreamDecoder.c000066400000000000000000000146501256642440500211540ustar00rootroot00000000000000/* OOTCPStreamDecoder.c Copyright (C) 2007 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef OO_EXCLUDE_DEBUG_SUPPORT #include "OOTCPStreamDecoder.h" #include #include #include #include "OODebugTCPConsoleProtocol.h" #ifdef OO_LOG_DEBUG_PROTOCOL_PACKETS extern void LogOOTCPStreamDecoderPacket(OOALDictionaryRef packet); #else #define LogOOTCPStreamDecoderPacket(packet) do {} while (0) #endif struct OOTCPStreamDecoder { uint8_t header[4]; uint32_t headerSpaceUsed; OOALMutableDataRef nextPacketData; uint32_t nextSize; OOTCPStreamDecoderPacketCallback Packet; OOTCPStreamDecoderErrorCallback Error; OOTCPStreamDecoderFinalizeCallback Finalize; void *cbInfo; }; static void Error(OOTCPStreamDecoderRef decoder, OOALStringRef format, ...); static void PacketReady(OOTCPStreamDecoderRef decoder); OOTCPStreamDecoderRef OOTCPStreamDecoderCreate(OOTCPStreamDecoderPacketCallback packetCB, OOTCPStreamDecoderErrorCallback errorCB, OOTCPStreamDecoderFinalizeCallback finalizeCB, void *cbInfo) { OOTCPStreamDecoderRef decoder = NULL; if (packetCB == NULL) return NULL; decoder = malloc(sizeof *decoder); if (decoder == NULL) return NULL; decoder->headerSpaceUsed = 0; decoder->nextPacketData = NULL; decoder->nextSize = 0; decoder->Packet = packetCB; decoder->Error = errorCB; decoder->Finalize = finalizeCB; decoder->cbInfo = cbInfo; return decoder; } void OOTCPStreamDecoderDestroy(OOTCPStreamDecoderRef decoder) { if (decoder == NULL) return; if (decoder->Finalize != NULL) { decoder->Finalize(decoder->cbInfo); } if (decoder->nextPacketData != NULL) { OOALRelease(decoder->nextPacketData); decoder->nextPacketData = NULL; } free(decoder); } void OOTCPStreamDecoderReceiveData(OOTCPStreamDecoderRef decoder, OOALDataRef data) { if (decoder == NULL || data == NULL) return; OOTCPStreamDecoderReceiveBytes(decoder, OOALDataGetBytePtr(data), OOALDataGetLength(data)); } void OOTCPStreamDecoderReceiveBytes(OOTCPStreamDecoderRef decoder, const void *inBytes, size_t length) { const unsigned char *bytes = NULL; size_t remaining; size_t bytesToAdd; OOALAutoreleasePoolRef pool = NULL; if (decoder == NULL) return; bytes = inBytes; remaining = length; if (bytes == NULL && remaining != 0) { Error(decoder, OOALSTR("Invalid data -- NULL bytes but %u byte count."), remaining); return; } while (remaining != 0) { if (decoder->nextPacketData != NULL) { // More data expected bytesToAdd = remaining; if (decoder->nextSize < bytesToAdd) bytesToAdd = decoder->nextSize; OOALMutableDataAppendBytes(decoder->nextPacketData, bytes, bytesToAdd); remaining -= bytesToAdd; decoder->nextSize -= bytesToAdd; bytes += bytesToAdd; if (decoder->nextSize == 0) { // Packet is ready. pool = OOALCreateAutoreleasePool(); PacketReady(decoder); OOALDestroyAutoreleasePool(pool); pool = NULL; OOALRelease(decoder->nextPacketData); decoder->nextPacketData = NULL; } } else if (decoder->headerSpaceUsed < 4) { // Read bytes for packet header remaining--; decoder->header[decoder->headerSpaceUsed++] = *bytes++; } else if (decoder->headerSpaceUsed == 4) { // We've read a header, start on a packet. decoder->nextSize = (decoder->header[0] << 24) | (decoder->header[1] << 16) | (decoder->header[2] << 8) | (decoder->header[3] << 0); decoder->headerSpaceUsed = 0; if (decoder->nextSize != 0) { decoder->nextPacketData = OOALDataCreateMutable(decoder->nextSize); } } else { Error(decoder, OOALSTR("OOTCPStreamDecoder internal error: reached unreachable state. nextSize = %lu, bufferUsed = %lu, nextPacketData = %@."), (unsigned long)decoder->nextSize, (unsigned long)decoder->headerSpaceUsed, decoder->nextPacketData); } } } static void PacketReady(OOTCPStreamDecoderRef decoder) { OOALDictionaryRef packet = NULL; OOALStringRef errorString = NULL; OOALStringRef packetType = NULL; packet = OOALPropertyListFromData(decoder->nextPacketData, &errorString); // Ensure that it's a property list. if (packet == NULL) { Error(decoder, OOALSTR("Protocol error: packet is not property list (property list error: %@)."), errorString); OOALRelease(errorString); return; } // Ensure that it's a dictionary. if (!OOALIsDictionary(packet)) { Error(decoder, OOALSTR("Protocol error: packet is a %@, not a dictionary."), OOTypeDescription(packet)); return; } LogOOTCPStreamDecoderPacket(packet); // Get packet type (and ensure that there is one). packetType = OOALDictionaryGetValue(packet, kOOTCPPacketType); if (packetType == NULL) { Error(decoder, OOALSTR("Protocol error: packet contains no packet type.")); return; } if (!OOALIsString(packetType)) { Error(decoder, OOALSTR("Protocol error: packet type is a %@, not a string."), OOTypeDescription(packetType)); return; } decoder->Packet(decoder->cbInfo, packetType, packet); } static void Error(OOTCPStreamDecoderRef decoder, OOALStringRef format, ...) { va_list args; OOALStringRef string = NULL; if (decoder == NULL || decoder->Error == NULL || format == NULL) return; va_start(args, format); string = OOALStringCreateWithFormatAndArguments(format, args); va_end(args); if (string != NULL) { decoder->Error(decoder->cbInfo, string); OOALRelease(string); } } #endif /* OO_EXCLUDE_DEBUG_SUPPORT */ oolite-1.82/src/Core/Debug/OOTCPStreamDecoder.h000066400000000000000000000041371256642440500211600ustar00rootroot00000000000000/* OOTCPStreamDecoder.h Psuedo-object to take blobs of data, create Oolite TCP debug console protocol packets. Copyright (C) 2007-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef INCLUDED_OOTCPStreamDecoder_h #define INCLUDED_OOTCPStreamDecoder_h #include "OOTCPStreamDecoderAbstractionLayer.h" typedef struct OOTCPStreamDecoder *OOTCPStreamDecoderRef; typedef void (*OOTCPStreamDecoderPacketCallback)(void *cbInfo, OOALStringRef packetType, OOALDictionaryRef packet); typedef void (*OOTCPStreamDecoderErrorCallback)(void *cbInfo, OOALStringRef errorDesc); typedef void (*OOTCPStreamDecoderFinalizeCallback)(void *cbInfo); OOTCPStreamDecoderRef OOTCPStreamDecoderCreate(OOTCPStreamDecoderPacketCallback packetCB, OOTCPStreamDecoderErrorCallback errorCB, OOTCPStreamDecoderFinalizeCallback finalizeCB, void *cbInfo); void OOTCPStreamDecoderDestroy(OOTCPStreamDecoderRef decoder); void OOTCPStreamDecoderReceiveData(OOTCPStreamDecoderRef decoder, OOALDataRef data); void OOTCPStreamDecoderReceiveBytes(OOTCPStreamDecoderRef decoder, const void *bytes, size_t length); #endif /* INCLUDED_OOTCPStreamDecoder_h */ oolite-1.82/src/Core/Debug/OOTCPStreamDecoderAbstractionLayer.h000066400000000000000000000073301256642440500243450ustar00rootroot00000000000000/* OOTCPStreamDecoderAbstractionLayer.h Abstraction layer to allow OOTCPStreamDecoder to work with CoreFoundation/ CF-Lite, Cocoa Foundation or GNUstep Foundation. */ #ifndef INCLUDED_OOTCPStreamDecoderAbstractionLayer_h #define INCLUDED_OOTCPStreamDecoderAbstractionLayer_h #ifndef OOTCPSTREAM_USE_COREFOUNDATION #define OOTCPSTREAM_USE_COREFOUNDATION 0 #endif #if OOTCPSTREAM_USE_COREFOUNDATION #include #import "JAAutoreleasePool.h" #define OOALRelease(object) CFRelease(object) #define OOTypeDescription(object) JAAutorelease(CFCopyTypeIDDescription(CFGetTypeID(object))) typedef CFStringRef OOALStringRef; #define OOALIsString(object) (CFGetTypeID(object) == CFStringGetTypeID()) #define OOALSTR(str) CFSTR(str) #define OOALStringCreateWithFormatAndArguments(format, args) CFStringCreateWithFormatAndArguments(kCFAllocatorDefault, NULL, format, args) typedef CFDictionaryRef OOALDictionaryRef; #define OOALIsDictionary(object) (CFGetTypeID(object) == CFDictionaryGetTypeID()) #define OOALDictionaryGetValue(dictionary, key) CFDictionaryGetValue(dictionary, key) typedef CFDataRef OOALDataRef; typedef CFMutableDataRef OOALMutableDataRef; #define OOALIsData(object) (CFGetTypeID(object) == CFDataGetTypeID()) #define OOALDataCreateMutable(capacity) CFDataCreateMutable(kCFAllocatorDefault, capacity) #define OOALMutableDataAppendBytes(data, bytes, length) CFDataAppendBytes(data, bytes, length) #define OOALDataGetBytePtr(data) CFDataGetBytePtr(data) #define OOALDataGetLength(data) CFDataGetLength(data) typedef JAAutoreleasePoolRef OOALAutoreleasePoolRef; #define OOALCreateAutoreleasePool() JACreateAutoreleasePool() #define OOALDestroyAutoreleasePool(pool) JADestroyAutoreleasePool(pool) #define OOALPropertyListFromData(data, errStr) JAAutorelease(CFPropertyListCreateFromXMLData(kCFAllocatorDefault, data, kCFPropertyListImmutable, errStr)) #else /* !OOTCPSTREAM_USE_COREFOUNDATION */ #include #include #include #if __OBJC__ #import typedef id OOALObjectRef; typedef NSString *OOALStringRef; typedef NSData *OOALDataRef; typedef NSMutableData *OOALMutableDataRef; typedef NSDictionary *OOALDictionaryRef; typedef NSAutoreleasePool *OOALAutoreleasePoolRef; #define OOALSTR(x) @""x #else typedef const void *OOALObjectRef; typedef const struct NSString *OOALStringRef; typedef const struct NSData *OOALDataRef; typedef struct NSData *OOALMutableDataRef; typedef const struct NSDictionary *OOALDictionaryRef; typedef const struct NSAutoreleasePool *OOALAutoreleasePoolRef; OOALStringRef OOALGetConstantString(const char *string); // Should only be used with string literals! #define OOALSTR(string) OOALGetConstantString("" string "") #endif void OOALRelease(OOALObjectRef object); OOALStringRef OOTypeDescription(OOALObjectRef object); bool OOALIsString(OOALObjectRef object); OOALStringRef OOALStringCreateWithFormatAndArguments(OOALStringRef format, va_list args); bool OOALIsDictionary(OOALObjectRef object); OOALObjectRef OOALDictionaryGetValue(OOALDictionaryRef dictionary, OOALObjectRef key); bool OOALIsData(OOALObjectRef object); OOALMutableDataRef OOALDataCreateMutable(size_t capacity); void OOALMutableDataAppendBytes(OOALMutableDataRef data, const void *bytes, size_t length); const void *OOALDataGetBytePtr(OOALDataRef data); size_t OOALDataGetLength(OOALDataRef data); OOALAutoreleasePoolRef OOALCreateAutoreleasePool(void); #define OOALDestroyAutoreleasePool(pool) OOALRelease(pool) OOALObjectRef OOALPropertyListFromData(OOALMutableDataRef data, OOALStringRef *errStr); #endif /* OOTCPSTREAM_USE_COREFOUNDATION */ #endif /* INCLUDED_OOTCPStreamDecoderAbstractionLayer_h */ oolite-1.82/src/Core/Debug/OOTCPStreamDecoderAbstractionLayer.m000066400000000000000000000052241256642440500243520ustar00rootroot00000000000000/* OOTCPStreamDecoderAbstractionLayer.h Abstraction layer to allow OOTCPStreamDecoder to work with CoreFoundation/ CF-Lite, Cocoa Foundation or GNUstep Foundation. Foundation implementation. */ #ifndef OO_EXCLUDE_DEBUG_SUPPORT #import "OOTCPStreamDecoderAbstractionLayer.h" #import "OOCocoa.h" // Simulate literal CF/NS strings. Each literal string that is used becomes a single object. Since it uses pointers as keys, it should only be used with literals. OOALStringRef OOALGetConstantString(const char *string) { static NSMutableDictionary *sStrings = nil; NSValue *key = nil; NSString *value = nil; if (sStrings == nil) { sStrings = [[NSMutableDictionary alloc] init]; } key = [NSValue valueWithPointer:string]; value = [sStrings objectForKey:key]; if (value == nil) { // Note: non-ASCII strings are not permitted, but we don't bother to detect them. value = [NSString stringWithUTF8String:string]; if (value != nil) [sStrings setObject:value forKey:key]; } return value; } void OOALRelease(OOALObjectRef object) { [object release]; } OOALStringRef OOTypeDescription(OOALObjectRef object) { return [[object class] description]; } bool OOALIsString(OOALObjectRef object) { return [object isKindOfClass:[NSString class]]; } OOALStringRef OOALStringCreateWithFormatAndArguments(OOALStringRef format, va_list args) { return [[NSString alloc] initWithFormat:format arguments:args]; } bool OOALIsDictionary(OOALObjectRef object) { return [object isKindOfClass:[NSDictionary class]]; } OOALObjectRef OOALDictionaryGetValue(OOALDictionaryRef dictionary, OOALObjectRef key) { return [dictionary objectForKey:key]; } bool OOALIsData(OOALObjectRef object) { return [object isKindOfClass:[NSData class]]; } OOALMutableDataRef OOALDataCreateMutable(size_t capacity) { return [[NSMutableData alloc] initWithCapacity:capacity]; } void OOALMutableDataAppendBytes(OOALMutableDataRef data, const void *bytes, size_t length) { [data appendBytes:bytes length:length]; } const void *OOALDataGetBytePtr(OOALDataRef data) { return [data bytes]; } size_t OOALDataGetLength(OOALDataRef data) { return [data length]; } OOALAutoreleasePoolRef OOALCreateAutoreleasePool(void) { return [[NSAutoreleasePool alloc] init]; } OOALObjectRef OOALPropertyListFromData(OOALMutableDataRef data, OOALStringRef *errStr) { id result = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:NULL errorDescription:errStr]; [result retain]; #if !OOLITE_RELEASE_PLIST_ERROR_STRINGS [*errStr retain]; #endif return result; } #endif /* OO_EXCLUDE_DEBUG_SUPPORT */ oolite-1.82/src/Core/Entities/000077500000000000000000000000001256642440500162125ustar00rootroot00000000000000oolite-1.82/src/Core/Entities/DockEntity.h000066400000000000000000000052451256642440500204460ustar00rootroot00000000000000/* DockEntity.h ShipEntity subclass representing a dock. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "ShipEntity.h" #import "StationEntity.h" // For MAX_DOCKING_STAGES @interface DockEntity: ShipEntity { @private NSMutableDictionary *shipsOnApproach; NSMutableArray *launchQueue; double last_launch_time; // double approach_spacing; // not needed now holding pattern changed ShipEntity *id_lock[MAX_DOCKING_STAGES]; // OOWeakReferences to a ShipEntity Vector port_dimensions; double port_corridor; // corridor length inside station. BOOL no_docking_while_launching; BOOL allow_launching; BOOL allow_docking; BOOL disallowed_docking_collides; BOOL virtual_dock; } - (void) clear; // Docking - (BOOL) allowsDocking; - (void) setAllowsDocking:(BOOL)allow; - (BOOL) disallowedDockingCollides; - (void) setDisallowedDockingCollides:(BOOL)ddc; - (NSUInteger) countOfShipsInDockingQueue; - (NSDictionary *) dockingInstructionsForShip:(ShipEntity *)ship; - (NSString *) canAcceptShipForDocking:(ShipEntity *)ship; - (BOOL) shipIsInDockingCorridor:(ShipEntity *)ship; - (BOOL) shipIsInDockingQueue:(ShipEntity *)ship; - (void) abortDockingForShip:(ShipEntity *)ship; - (void) abortAllDockings; - (BOOL) dockingCorridorIsEmpty; - (void) clearDockingCorridor; - (void) autoDockShipsOnApproach; - (NSUInteger) pruneAndCountShipsOnApproach; - (void) noteDockingForShip:(ShipEntity *)ship; // Launching - (BOOL) allowsLaunching; - (void) setAllowsLaunching:(BOOL)allow; - (NSUInteger) countOfShipsInLaunchQueue; - (NSUInteger) countOfShipsInLaunchQueueWithPrimaryRole:(NSString *)role; - (BOOL) allowsLaunchingOf:(ShipEntity *)ship; - (void) launchShip:(ShipEntity *)ship; - (void) addShipToLaunchQueue:(ShipEntity *)ship withPriority:(BOOL)priority; // Geometry - (void) setDimensionsAndCorridor:(BOOL)docking :(BOOL)ddc :(BOOL)launching; - (Vector) portUpVectorForShipsBoundingBox:(BoundingBox)bb; - (BOOL) isOffCentre; - (void) setVirtual; @end oolite-1.82/src/Core/Entities/DockEntity.m000066400000000000000000001145751256642440500204620ustar00rootroot00000000000000/* DockEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "DockEntity.h" #import "StationEntity.h" #import "ShipEntityAI.h" #import "OOCollectionExtractors.h" #import "OOStringParsing.h" #import "OOStringExpander.h" #import "Universe.h" #import "HeadUpDisplay.h" #import "PlayerEntityLegacyScriptEngine.h" #import "OOLegacyScriptWhitelist.h" #import "OOPlanetEntity.h" #import "OOShipGroup.h" #import "OOQuiriumCascadeEntity.h" #import "AI.h" #import "OOCharacter.h" #import "OOJSScript.h" #import "OODebugGLDrawing.h" #import "OODebugFlags.h" @interface DockEntity (OOPrivate) - (void) clearIdLocks:(ShipEntity *)ship; - (void) clearAllIdLocks; - (void) autoDockShipsInQueue:(NSMutableDictionary *)queue; - (void) addShipToShipsOnApproach:(ShipEntity *)ship; - (void) pullInShipIfPermitted:(ShipEntity *)ship; @end @implementation DockEntity - (NSUInteger) pruneAndCountShipsOnApproach { // Remove dead entities. // Enumerate over allKeys explicitly because we mutate the dictionary. NSNumber *idObj = nil; foreach (idObj, [shipsOnApproach allKeys]) { ShipEntity *ship = [UNIVERSE entityForUniversalID:[idObj unsignedIntValue]]; if (ship == nil) { [shipsOnApproach removeObjectForKey:idObj]; } } if ([shipsOnApproach count] == 0) { if (last_launch_time < [UNIVERSE getTime]) { last_launch_time = [UNIVERSE getTime]; } } return [shipsOnApproach count]; } - (void) abortAllDockings { double playerExtraTime = 0; no_docking_while_launching = YES; NSNumber *idObj = nil; foreach (idObj, [shipsOnApproach allKeys]) { ShipEntity *ship = [UNIVERSE entityForUniversalID:[idObj unsignedIntValue]]; if ([ship isShip]) { [ship sendAIMessage:@"DOCKING_ABORTED"]; [ship doScriptEvent:OOJSID("stationWithdrewDockingClearance")]; } } [shipsOnApproach removeAllObjects]; PlayerEntity *player = PLAYER; StationEntity *station = (StationEntity*)[self parentEntity]; BOOL isDockingStation = (station == [player getTargetDockStation]) && ([station playerReservedDock] == self); if (isDockingStation && [player status] == STATUS_IN_FLIGHT && [player getDockingClearanceStatus] >= DOCKING_CLEARANCE_STATUS_REQUESTED) { if (HPmagnitude2(HPvector_subtract([player position], [self absolutePositionForSubentity])) > 2250000) // within 1500m of the dock { [station sendExpandedMessage:@"[station-docking-clearance-abort-cancelled]" toShip:player]; [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE]; [player doScriptEvent:OOJSID("stationWithdrewDockingClearance")]; } else { playerExtraTime = 10; // when very close to the port, give the player a few seconds to react on the abort message. int seconds = round(playerExtraTime); [station sendExpandedMessage:OOExpandKey(@"station-docking-clearance-abort-cancelled-in-time", seconds) toShip:player]; [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_TIMING_OUT]; } } // mark docking queue flight pattern as clear [self clearAllIdLocks]; last_launch_time = [UNIVERSE getTime] + playerExtraTime; } - (void) abortAllLaunches { no_docking_while_launching = NO; [launchQueue removeAllObjects]; } - (void) autoDockShipsInQueue:(NSMutableDictionary *)queue { NSNumber *idObj = nil; foreach (idObj, [queue allKeys]) { ShipEntity *ship = [UNIVERSE entityForUniversalID:[idObj unsignedIntValue]]; if ([ship isShip]) { [self pullInShipIfPermitted:ship]; } } [queue removeAllObjects]; } - (void) autoDockShipsOnApproach { [self autoDockShipsInQueue:shipsOnApproach]; } - (BOOL) allowsDocking { return allow_docking; } - (void) setAllowsDocking:(BOOL)allowed { if (!allowed && allow_docking) { [self abortAllDockings]; } allow_docking = allowed; } - (BOOL) disallowedDockingCollides { return disallowed_docking_collides; } - (BOOL) allowsLaunching { return allow_launching; } - (void) setAllowsLaunching:(BOOL)allowed { if (!allowed && allow_launching) { [self abortAllLaunches]; } allow_launching = allowed; } - (void) setDisallowedDockingCollides:(BOOL)ddc { disallowed_docking_collides = ddc; } - (void) setVirtual { virtual_dock = YES; } - (NSString*) canAcceptShipForDocking:(ShipEntity *) ship { // First test permanent rejection reasons if (!allow_docking) { return @"DOCK_CLOSED"; // could be temp or perm reject } BoundingBox bb = [ship totalBoundingBox]; if ((port_dimensions.x < (bb.max.x - bb.min.x) || port_dimensions.y < (bb.max.y - bb.min.y)) && (port_dimensions.y < (bb.max.x - bb.min.x) || port_dimensions.x < (bb.max.y - bb.min.y))) { return @"TOO_BIG_TO_DOCK"; } // callback to allow more complex filtering on accept/reject JSContext *context = OOJSAcquireContext(); jsval rval = JSVAL_VOID; jsval args[] = { OOJSValueFromNativeObject(context, ship) }; JSBool accept = YES; BOOL OK = [[self script] callMethod:OOJSID("acceptDockingRequestFrom") inContext:context withArguments:args count:1 result:&rval]; if (OK) OK = JS_ValueToBoolean(context, rval, &accept); if (!OK) accept = YES; // default to permreject OOJSRelinquishContext(context); if (!accept) { return @"TOO_BIG_TO_DOCK"; } // Second test temporary rejection reasons if (no_docking_while_launching) { return @"TRY_AGAIN_LATER"; } // if there are pending launches, temporarily don't accept docking requests if (allow_launching && [launchQueue count]) { return @"TRY_AGAIN_LATER"; } return @"DOCKING_POSSIBLE"; } - (BOOL) isOffCentre { if (fabs(position.x) + fabs(position.y) > 5.0f) { return YES; } Vector dir = vector_forward_from_quaternion(orientation); if (fabs(dir.x) + fabs(dir.y) > 0.1f) { return YES; } return NO; } - (NSDictionary *) dockingInstructionsForShip:(ShipEntity *)ship { if (ship == nil) return nil; OOUniversalID ship_id = [ship universalID]; NSNumber *shipID = [NSNumber numberWithUnsignedShort:ship_id]; StationEntity *station = (StationEntity *)[self parentEntity]; HPVector launchVector = HPvector_forward_from_quaternion(quaternion_multiply(orientation, [station orientation])); HPVector temp = (fabs(launchVector.x) < 0.8)? make_HPvector(1,0,0) : make_HPvector(0,1,0); temp = HPcross_product(launchVector, temp); // 90 deg to launchVector & temp HPVector vi = HPcross_product(launchVector, temp); HPVector vj = HPcross_product(launchVector, vi); HPVector vk = launchVector; // check if this is a new ship on approach // if (![shipsOnApproach objectForKey:shipID]) { HPVector delta = HPvector_subtract([ship position], [self absolutePositionForSubentity]); float ship_distance = HPmagnitude(delta); if (ship_distance > SCANNER_MAX_RANGE) { // too far away - don't claim a docking slot by not putting on approachlist for now. return OOMakeDockingInstructions(station, [self absolutePositionForSubentity], [ship maxFlightSpeed], 10000, @"APPROACH", nil, NO, -1); } [self addShipToShipsOnApproach: ship]; if (ship_distance < 1000.0 + [station collisionRadius] + ship->collision_radius) // too close - back off return OOMakeDockingInstructions(station, [self absolutePositionForSubentity], [ship maxFlightSpeed], 5000, @"BACK_OFF", nil, NO, -1); float dot = HPdot_product(launchVector, delta); if (dot < 0) // approaching from the wrong side of the station - construct a vector to the side of the station. { HPVector approachVector = HPcross_product(HPvector_normal(delta), launchVector); approachVector = HPcross_product(launchVector, approachVector); // vector, 90 degr rotated from launchVector towards target. return OOMakeDockingInstructions(station, OOHPVectorTowards([self absolutePositionForSubentity], approachVector, [station collisionRadius] + 5000) , [ship maxFlightSpeed], 1000, @"APPROACH", nil, NO, -1); } if (ship_distance > 12500.0) { // long way off - approach more closely return OOMakeDockingInstructions(station, [self absolutePositionForSubentity], [ship maxFlightSpeed], 10000, @"APPROACH", nil, NO, -1); } } if (![shipsOnApproach objectForKey:shipID]) { // some error has occurred - log it, and send the try-again message OOLogERR(@"station.issueDockingInstructions.failed", @"couldn't addShipToShipsOnApproach:%@ in %@, retrying later -- shipsOnApproach:\n%@", ship, self, shipsOnApproach); return OOMakeDockingInstructions(station, [ship position], 200, 100, @"TRY_AGAIN_LATER", nil, NO, -1); } // shipsOnApproach now has an entry for the ship. // NSMutableArray* coordinatesStack = [shipsOnApproach objectForKey:shipID]; if ([coordinatesStack count] == 0) { OOLogERR(@"station.issueDockingInstructions.failed", @" -- coordinatesStack = %@", coordinatesStack); return OOMakeDockingInstructions(station, [ship position], 0, 100, @"HOLD_POSITION", nil, NO, -1); } // get the docking information from the instructions NSMutableDictionary *nextCoords = (NSMutableDictionary *)[coordinatesStack objectAtIndex:0]; int docking_stage = [nextCoords oo_intForKey:@"docking_stage"]; float speedAdvised = [nextCoords oo_floatForKey:@"speed"]; float rangeAdvised = [nextCoords oo_floatForKey:@"range"]; // calculate world coordinates from relative coordinates HPVector rel_coords; rel_coords.x = [nextCoords oo_doubleForKey:@"rx"]; rel_coords.y = [nextCoords oo_doubleForKey:@"ry"]; rel_coords.z = [nextCoords oo_doubleForKey:@"rz"]; HPVector coords = [self absolutePositionForSubentity]; coords.x += rel_coords.x * vi.x + rel_coords.y * vj.x + rel_coords.z * vk.x; coords.y += rel_coords.x * vi.y + rel_coords.y * vj.y + rel_coords.z * vk.y; coords.z += rel_coords.x * vi.z + rel_coords.y * vj.z + rel_coords.z * vk.z; // check if the ship is at the control point double max_allowed_range = 2.0 * rangeAdvised + ship->collision_radius; // maximum distance permitted from control point - twice advised range HPVector delta = HPvector_subtract(ship->position, coords); if (HPmagnitude2(delta) > max_allowed_range * max_allowed_range) // too far from the coordinates - do not remove them from the stack! { if ((docking_stage == 1) &&(HPmagnitude2(delta) < 1000000.0)) // 1km*1km speedAdvised *= 0.5; // half speed return OOMakeDockingInstructions(station, coords, speedAdvised, rangeAdvised, @"APPROACH_COORDINATES", nil, NO, docking_stage); } // else, reached the current coordinates okay.. // get the NEXT coordinates nextCoords = (NSMutableDictionary *)[coordinatesStack oo_dictionaryAtIndex:1]; if (nextCoords == nil) { return nil; } docking_stage = [nextCoords oo_intForKey:@"docking_stage"]; speedAdvised = [nextCoords oo_floatForKey:@"speed"]; rangeAdvised = [nextCoords oo_floatForKey:@"range"]; BOOL match_rotation = [nextCoords oo_boolForKey:@"match_rotation"]; NSString *comms_message = [nextCoords oo_stringForKey:@"comms_message"]; if (comms_message) { [station sendExpandedMessage:comms_message toShip:ship]; } // calculate world coordinates from relative coordinates rel_coords.x = [nextCoords oo_floatForKey:@"rx"]; rel_coords.y = [nextCoords oo_floatForKey:@"ry"]; rel_coords.z = [nextCoords oo_floatForKey:@"rz"]; coords = [self absolutePositionForSubentity]; coords.x += rel_coords.x * vi.x + rel_coords.y * vj.x + rel_coords.z * vk.x; coords.y += rel_coords.x * vi.y + rel_coords.y * vj.y + rel_coords.z * vk.y; coords.z += rel_coords.x * vi.z + rel_coords.y * vj.z + rel_coords.z * vk.z; if([id_lock[docking_stage] weakRefUnderlyingObject] == nil && [id_lock[docking_stage + 1] weakRefUnderlyingObject] == nil && [id_lock[docking_stage + 2] weakRefUnderlyingObject] == nil) // check three stages ahead { // approach is clear - move to next position // // clear any previously owned docking stages [self clearIdLocks:ship]; if (docking_stage > 1) // don't claim first docking stage { [id_lock[docking_stage] release]; id_lock[docking_stage] = [ship weakRetain]; // otherwise - claim this docking stage } //remove the previous stage from the stack [coordinatesStack removeObjectAtIndex:0]; return OOMakeDockingInstructions(station, coords, speedAdvised, rangeAdvised, @"APPROACH_COORDINATES", nil, match_rotation, docking_stage); } // else, approach isn't clear - hold position.. // [[ship getAI] message:@"HOLD_POSITION"]; if (![nextCoords objectForKey:@"hold_message_given"]) { // COMM-CHATTER [UNIVERSE clearPreviousMessage]; [self sendExpandedMessage: @"[station-hold-position]" toShip: ship]; [nextCoords setObject:@"YES" forKey:@"hold_message_given"]; } return OOMakeDockingInstructions(station, ship->position, 0, 100, @"HOLD_POSITION", nil, NO, -1); } - (void) addShipToShipsOnApproach:(ShipEntity *) ship { int corridor_distance[] = { -1, 1, 3, 5, 7, 9, 11, 12, 12}; int corridor_offset[] = { 0, 0, 0, 0, 0, 0, 1, 3, 12}; /* Eric's improvements to the docking flight code seem to have * made it safer to go quite a bit faster here. With the increased * numbers of ships which might need to dock at the main station, * faster docking will help avoid massive queues. Previous speed * was mostly 48 - CIM: 27/8/2013*/ int corridor_speed[] = { 96, 96, 128, 128, 96, 128, 128, 256, 512}; // how fast to approach the next point int corridor_range[] = { 24, 12, 6, 4, 4, 6, 15, 38, 96}; // how close you have to get to the target point int corridor_rotate[] = { 1, 1, 1, 1, 0, 0, 0, 0, 0}; // whether to match the station rotation int corridor_count = 9; int corridor_final_approach = 3; NSNumber *shipID = [NSNumber numberWithUnsignedShort:[ship universalID]]; StationEntity *station = (StationEntity *)[self parentEntity]; HPVector launchVector = HPvector_forward_from_quaternion(quaternion_multiply(orientation, [station orientation])); HPVector temp = (fabs(launchVector.x) < 0.8)? make_HPvector(1,0,0) : make_HPvector(0,1,0); temp = HPcross_product(launchVector, temp); // 90 deg to launchVector & temp HPVector rightVector = HPcross_product(launchVector, temp); HPVector upVector = HPcross_product(launchVector, rightVector); // will select a direction for offset based on the entity personality (was ship ID) int offset_id = [ship entityPersonalityInt] & 0xf; // 16 point compass float c = cos(offset_id * M_PI * ONE_EIGHTH); float s = sin(offset_id * M_PI * ONE_EIGHTH); // test if this points at the ship HPVector point1 = [self absolutePositionForSubentity]; point1.x += launchVector.x * corridor_offset[corridor_count - 1]; point1.y += launchVector.x * corridor_offset[corridor_count - 1]; point1.z += launchVector.x * corridor_offset[corridor_count - 1]; HPVector alt1 = point1; point1.x += c * upVector.x * corridor_offset[corridor_count - 1] + s * rightVector.x * corridor_offset[corridor_count - 1]; point1.y += c * upVector.y * corridor_offset[corridor_count - 1] + s * rightVector.y * corridor_offset[corridor_count - 1]; point1.z += c * upVector.z * corridor_offset[corridor_count - 1] + s * rightVector.z * corridor_offset[corridor_count - 1]; alt1.x -= c * upVector.x * corridor_offset[corridor_count - 1] + s * rightVector.x * corridor_offset[corridor_count - 1]; alt1.y -= c * upVector.y * corridor_offset[corridor_count - 1] + s * rightVector.y * corridor_offset[corridor_count - 1]; alt1.z -= c * upVector.z * corridor_offset[corridor_count - 1] + s * rightVector.z * corridor_offset[corridor_count - 1]; if (HPdistance2(alt1, ship->position) < HPdistance2(point1, ship->position)) { s = -s; c = -c; // turn 180 degrees } // NSMutableArray *coordinatesStack = [NSMutableArray arrayWithCapacity: MAX_DOCKING_STAGES]; float port_depth = port_dimensions.z; // 250m deep standard port. int i; for (i = corridor_count - 1; i >= 0; i--) { NSMutableDictionary *nextCoords = [NSMutableDictionary dictionaryWithCapacity:3]; int offset = corridor_offset[i]; float corridor_length = port_depth * corridor_distance[i]; float rx = s * port_depth * offset; float ry = c * port_depth * offset; float rz = corridor_length; // if there are many ships on approach, randomise coordinates a bit if ((i == corridor_count - 1) && [self countOfShipsInDockingQueue]) { /* This used to try to just space the ships further out * along the 16 approach lanes - this had various problems * with putting ship coordinates on top of each other * and/or spacing them out all the way back to the * witchpoint. Instead, use a few more bits of * entityPersonalityInt to shuffle the holding coordinates * a bit more. It still doesn't guarantee two ships won't * want the same space, but it makes it considerably more * unlikely - I dropped 100 docking ships into the aegis * at once, and they all got allocated positions far * enough from the others to avoid collisions or near * misses - CIM: 27 May 2014 */ int offset_id2 = ([ship entityPersonalityInt] & 0xf0)>>4; // 16 point compass int offset_id3 = ([ship entityPersonalityInt] & 0xf00)>>8; // 16 point step position float c2 = cos(offset_id2 * M_PI * ONE_EIGHTH); float s2 = sin(offset_id2 * M_PI * ONE_EIGHTH); float ssize = MAX(port_depth,1500.0); rx += c2 * ssize; ry += s2 * ssize; rz += ssize * ((float)offset_id3 / 4.0); // OOLog(@"docking.debug",@"Adjusted coordinates by %f x %f x %f",c2 * ssize,s2 * ssize,ssize * ((float)offset_id3 / 4.0)); } // add the lenght inside the station to the corridor, except for the final position, inside the dock. if (corridor_distance[i] > 0) corridor_length += port_corridor; [nextCoords oo_setInteger:corridor_count - i forKey:@"docking_stage"]; [nextCoords oo_setFloat:rx forKey:@"rx"]; [nextCoords oo_setFloat:ry forKey:@"ry"]; [nextCoords oo_setFloat:rz forKey:@"rz"]; [nextCoords oo_setFloat:corridor_speed[i] forKey:@"speed"]; [nextCoords oo_setFloat:corridor_range[i] forKey:@"range"]; if (corridor_rotate[i]) { [nextCoords setObject:@"YES" forKey:@"match_rotation"]; } if (i == corridor_final_approach) { if (station == [UNIVERSE station]) { [nextCoords setObject:@"[station-begin-final-aproach]" forKey:@"comms_message"]; } else { [nextCoords setObject:@"[docking-begin-final-aproach]" forKey:@"comms_message"]; } } [coordinatesStack addObject:nextCoords]; } [shipsOnApproach setObject:coordinatesStack forKey:shipID]; // COMM-CHATTER if (station == [UNIVERSE station]) { [station sendExpandedMessage:@"[station-welcome]" toShip:ship]; } else { [station sendExpandedMessage:@"[docking-welcome]" toShip:ship]; } } - (void) noteDockingForShip:(ShipEntity *) ship { // safe to do this for now, as it just clears the ship from the docking queue [self abortDockingForShip:ship]; // avoid clashes with outgoing ships last_launch_time = [UNIVERSE getTime]; } - (void) abortDockingForShip:(ShipEntity *)ship { OOUniversalID ship_id = [ship universalID]; NSNumber *shipID = [NSNumber numberWithUnsignedShort:ship_id]; if ([shipsOnApproach objectForKey:shipID]) { [shipsOnApproach removeObjectForKey:shipID]; } if ([ship isPlayer]) { PlayerEntity* player = PLAYER; if ([player status] == STATUS_IN_FLIGHT && [player getDockingClearanceStatus] >= DOCKING_CLEARANCE_STATUS_REQUESTED) { if (HPmagnitude2(HPvector_subtract([player position], [self absolutePositionForSubentity])) > 2250000) // within 1500m of the dock { [[self parentEntity] sendExpandedMessage:@"[station-docking-clearance-abort-cancelled]" toShip:player]; [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE]; } else { int seconds = 10; // when very close to the port, give the player a few seconds to react on the abort message. [[self parentEntity] sendExpandedMessage:OOExpandKey(@"station-docking-clearance-abort-cancelled-in-time", seconds) toShip:player]; [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_TIMING_OUT]; } } } // clear any previously owned docking stages [self clearIdLocks:ship]; } - (Vector) portUpVectorForShipsBoundingBox:(BoundingBox)bb { BOOL twist = ((port_dimensions.x < port_dimensions.y) ^ (bb.max.x - bb.min.x < bb.max.y - bb.min.y)); if (!twist) { return vector_up_from_quaternion(quaternion_multiply(orientation, [[self parentEntity] orientation])); } else { return vector_right_from_quaternion(quaternion_multiply(orientation, [[self parentEntity] orientation])); } } - (BOOL) shipIsInDockingQueue:(ShipEntity *)ship { if (![ship isShip]) return NO; if ([ship isPlayer] && [ship status] == STATUS_DEAD) return NO; OOUniversalID ship_id = [ship universalID]; NSNumber *shipID = [NSNumber numberWithUnsignedShort:ship_id]; if ([shipsOnApproach objectForKey:shipID]) { return YES; } // player docking manually if ([ship isPlayer] && [[self owner] playerReservedDock] == self) { return YES; } return NO; } - (NSUInteger) countOfShipsInDockingQueue { return [shipsOnApproach count]; } - (NSUInteger) countOfShipsInLaunchQueue { return [launchQueue count]; } - (BOOL) shipIsInDockingCorridor:(ShipEntity *)ship { if (![ship isShip]) return NO; if ([ship isPlayer] && [ship status] == STATUS_DEAD) return NO; BOOL allow_docking_thisship = allow_docking || !disallowed_docking_collides; // ships can physically dock here, and this routine is mainly for // collision detection, but will never be directed here by traffic // control, if allow_docking is false but d_d_c is also false StationEntity *station = (StationEntity *)[self parentEntity]; if ([station status] == STATUS_DEAD) { return NO; } Quaternion q0 = quaternion_multiply(orientation, [station orientation]); Vector vi = vector_right_from_quaternion(q0); Vector vj = vector_up_from_quaternion(q0); Vector vk = vector_forward_from_quaternion(q0); HPVector port_pos = [self absolutePositionForSubentity]; BoundingBox shipbb = [ship boundingBox]; BoundingBox arbb = [ship findBoundingBoxRelativeToPosition: port_pos InVectors: vi : vj : vk]; // port dimensions.. GLfloat ww = port_dimensions.x; GLfloat hh = port_dimensions.y; GLfloat dd = port_dimensions.z; BOOL rotatedPort = (ww >= hh) ? NO : YES; BOOL rotatedShip = ((shipbb.max.x - shipbb.min.x) >= (shipbb.max.y - shipbb.min.y)) ? NO : YES; BOOL rotationsMatch = (rotatedShip == rotatedPort); if (rotationsMatch) { // the ship and port are both bigger in x than y while (shipbb.max.x - shipbb.min.x > ww * 0.90) ww *= 1.25; while (shipbb.max.y - shipbb.min.y > hh * 0.90) hh *= 1.25; } else { // the ship and port have different x/y biggerness while (shipbb.max.y - shipbb.min.y > ww * 0.90) ww *= 1.25; while (shipbb.max.x - shipbb.min.x > hh * 0.90) hh *= 1.25; } ww *= 0.5; hh *= 0.5; #ifndef NDEBUG if ([ship isPlayer] && (gDebugFlags & DEBUG_DOCKING)) { BOOL inLane; float range; unsigned laneFlags = 0; if (arbb.max.x < ww) laneFlags |= 1; if (arbb.min.x > -ww) laneFlags |= 2; if (arbb.max.y < hh) laneFlags |= 4; if (arbb.min.y > -hh) laneFlags |= 8; inLane = laneFlags == 0xF; range = 0.90 * arbb.max.z + 0.10 * arbb.min.z; OOLog(@"docking.debug", @"Normalised port dimensions are %g x %g x %g. Player bounding box is at %@-%@ -- %s (%X), range: %g", ww * 2.0, hh * 2.0, dd, VectorDescription(arbb.min), VectorDescription(arbb.max), inLane ? "in lane" : "out of lane", laneFlags, range); } #endif if ((arbb.max.x < ww * 3.0)&&(arbb.min.x > -ww * 3.0)&&(arbb.max.y < hh * 3.0)&&(arbb.min.y > -hh * 3.0)) { if ([station requiresDockingClearance] && [ship isPlayer] && [ship status] != STATUS_LAUNCHING && [ship status] != STATUS_AUTOPILOT_ENGAGED && [PLAYER getDockingClearanceStatus] < DOCKING_CLEARANCE_STATUS_GRANTED) { if ((0.90 * arbb.max.z + 0.10 * arbb.min.z < 3000) && (dot_product(vk,[ship forwardVector]) < -0.9)) { // player is in docking corridor and facing dock // and within 3km [UNIVERSE addMessage:DESC(@"oolite-station-docking-requires-clearance") forCount:3]; } } } if (arbb.max.z < -dd) { return NO; } if ((arbb.max.x < ww)&&(arbb.min.x > -ww)&&(arbb.max.y < hh)&&(arbb.min.y > -hh)) { if ([ship status] != STATUS_LAUNCHING && !allow_docking_thisship) { // launch-only dock: will collide! if (arbb.min.z < dd) { [ship takeScrapeDamage: 5 * [UNIVERSE getTimeDelta]*[ship flightSpeed] from:station]; // and bounce HPVector rel = HPvector_subtract([ship position],port_pos); rel = HPvector_multiply_scalar(HPvector_normal(rel),[ship flightSpeed]*0.4); [ship adjustVelocity:HPVectorToVector(rel)]; } if (arbb.max.z < 0.0) { // give some warning before exploding... return NO; } } // in lane if (0.90 * arbb.max.z + 0.10 * arbb.min.z < 0.0) // we're 90% in docking position! { [self pullInShipIfPermitted:ship]; } return YES; } if ([ship status] == STATUS_LAUNCHING) { return YES; } // if close enough (within 50%) correct and add damage // GLfloat safety = 1.0+(50.0/100.0); if ((arbb.min.x > -safety * ww)&&(arbb.max.x < safety * ww)&&(arbb.min.y > -safety * hh)&&(arbb.max.y < safety * hh)) { if (arbb.min.z < 0.0) // got our nose inside { if ((arbb.min.x < -ww && arbb.max.x > ww) || (arbb.min.y < -hh && arbb.max.y > hh)) { /* No matter how much safety margin there is, if the * ship is going off opposite edges of the dock at * once, that's a fatal collision */ return NO; } GLfloat correction_factor = -arbb.min.z / (arbb.max.z - arbb.min.z); // proportion of ship inside // damage the ship according to velocity - don't send collision messages to AIs to avoid problems. [ship takeScrapeDamage: 5 * [UNIVERSE getTimeDelta]*[ship flightSpeed] from:station]; [station doScriptEvent:OOJSID("shipCollided") withArgument:ship]; // no COLLISION message to station AI, carriers would move away! [ship doScriptEvent:OOJSID("shipCollided") withArgument:station]; // no COLLISION message to ship AI, dockingAI.plist would abort. Vector delta; delta.x = 0.5f * (arbb.max.x + arbb.min.x) * correction_factor; delta.y = 0.5f * (arbb.max.y + arbb.min.y) * correction_factor; if (arbb.max.x < ww && arbb.min.x > -ww) { // x is okay - no need to correct delta.x = 0.0f; } if (arbb.max.y > hh && arbb.min.y > -hh) { // y is okay - no need to correct delta.y = 0.0f; } // adjust the ship back to the center of the port HPVector pos = [ship position]; pos.x -= delta.y * vj.x + delta.x * vi.x; pos.y -= delta.y * vj.y + delta.x * vi.y; pos.z -= delta.y * vj.z + delta.x * vi.z; [ship setPosition:pos]; } // if far enough in - dock if (0.90f * arbb.max.z + 0.10f * arbb.min.z < 0.0f) { [self pullInShipIfPermitted:ship]; } return YES; // okay NOW we're in the docking corridor! } return NO; } - (void) pullInShipIfPermitted:(ShipEntity *)ship { // allow_docking: docking permitted and expected // disallowed_docking_collides: unauthorised docking does not result in explosion if (allow_docking || !disallowed_docking_collides) { [ship enterDock:(StationEntity*)[self parentEntity]]; } } - (void) addShipToLaunchQueue:(ShipEntity *)ship withPriority:(BOOL)priority { [self pruneAndCountShipsOnApproach]; if (ship == nil) return; if (launchQueue == nil) { launchQueue = [[NSMutableArray alloc] init]; // retained } [ship setStatus:STATUS_DOCKED]; if (priority) { [launchQueue insertObject:ship atIndex:0]; } else { [launchQueue addObject:ship]; } } - (void) launchShip:(ShipEntity *) ship { if (![ship isShip]) return; BoundingBox bb = [ship boundingBox]; StationEntity *station = (StationEntity *)[self parentEntity]; HPVector launchPos = [self absolutePositionForSubentity]; Vector launchVel = [station velocity]; double launchSpeed = 0.5 * [ship maxFlightSpeed]; if ([station maxFlightSpeed] > 0 && [station flightSpeed] > 0) // is self a carrier in flight. { launchSpeed = 0.5 * [ship maxFlightSpeed] * (1.0 + [station flightSpeed]/[station maxFlightSpeed]); } Quaternion q1 = [station orientation]; q1 = quaternion_multiply(orientation, q1); Vector launchVector = vector_forward_from_quaternion(q1); // launch orientation if ((port_dimensions.x < port_dimensions.y) ^ (bb.max.x - bb.min.x < bb.max.y - bb.min.y)) { quaternion_rotate_about_axis(&q1, launchVector, M_PI*0.5); // to account for the slot being at 90 degrees to vertical } [ship setNormalOrientation:q1]; // launch position [ship setPosition:launchPos]; if([ship pendingEscortCount] > 0) [ship setPendingEscortCount:0]; // Make sure no extra escorts are added after launch. (e.g. for miners etc.) if ([ship hasEscorts]) no_docking_while_launching = YES; // launch speed launchVel = vector_add(launchVel, vector_multiply_scalar(launchVector, launchSpeed)); launchSpeed = magnitude(launchVel); [ship setSpeed:launchSpeed]; [ship setVelocity:launchVel]; // launch roll/pitch [ship setRoll:[station flightRoll]]; [ship setPitch:0.0]; [ship setYaw:0.0]; [UNIVERSE addEntity:ship]; [ship setStatus: STATUS_LAUNCHING]; [ship setDesiredSpeed:launchSpeed]; // must be set after initialising the AI to correct any speed set by AI last_launch_time = [UNIVERSE getTime]; double delay = (port_corridor + 2 * port_dimensions.z)/launchSpeed; // pause until 2 portlengths outside of the station. [ship setLaunchDelay:delay]; [[ship getAI] setNextThinkTime:last_launch_time + delay]; // pause while launching [ship resetExhaustPlumes]; // resets stuff for tracking/exhausts [ship doScriptEvent:OOJSID("shipWillLaunchFromStation") withArgument:station]; [station doScriptEvent:OOJSID("stationLaunchedShip") withArgument:ship andReactToAIMessage: @"STATION_LAUNCHED_SHIP"]; } - (NSUInteger) countOfShipsInLaunchQueueWithPrimaryRole:(NSString *)role { NSUInteger count = 0; ShipEntity *ship = nil; foreach (ship, launchQueue) { if ([ship hasPrimaryRole:role]) count++; } return count; } - (BOOL) allowsLaunchingOf:(ShipEntity *) ship { if (![ship isShip]) return NO; BoundingBox bb = [ship totalBoundingBox]; if ((port_dimensions.x < (bb.max.x - bb.min.x) || port_dimensions.y < (bb.max.y - bb.min.y)) && (port_dimensions.y < (bb.max.x - bb.min.x) || port_dimensions.x < (bb.max.y - bb.min.y)) && ![ship isPlayer]) { return NO; } // callback to allow more complex filtering on accept/reject JSContext *context = OOJSAcquireContext(); jsval rval = JSVAL_VOID; jsval args[] = { OOJSValueFromNativeObject(context, ship) }; JSBool accept = YES; BOOL OK = [[self script] callMethod:OOJSID("acceptLaunchingRequestFrom") inContext:context withArguments:args count:1 result:&rval]; if (OK) OK = JS_ValueToBoolean(context, rval, &accept); if (!OK) accept = YES; // default to permreject OOJSRelinquishContext(context); if (!accept) { return NO; } return YES; } - (void) clear { [launchQueue removeAllObjects]; [shipsOnApproach removeAllObjects]; } - (BOOL) dockingCorridorIsEmpty { double unitime = [UNIVERSE getTime]; if (unitime < last_launch_time + STATION_DELAY_BETWEEN_LAUNCHES) { // leave sufficient pause between launches return NO; } StationEntity *station = (StationEntity *)[self parentEntity]; if ([station playerReservedDock] == self) { // player probably will not appreciate a ship launch right now return NO; } // check against all ships BOOL isEmpty = YES; int ent_count = UNIVERSE->n_entities; Entity **uni_entities = UNIVERSE->sortedEntities; // grab the public sorted list Entity *my_entities[ent_count]; int i; int ship_count = 0; for (i = 0; i < ent_count; i++) { //on red alert, launch even if the player is trying block the corridor. Ignore cargopods or other small debris. if ([uni_entities[i] isShip] && ([station alertLevel] < STATION_ALERT_LEVEL_RED || ![uni_entities[i] isPlayer]) && [uni_entities[i] mass] > 1000) { my_entities[ship_count++] = [uni_entities[i] retain]; // retained } } for (i = 0; (i < ship_count)&&(isEmpty); i++) { ShipEntity* ship = (ShipEntity*)my_entities[i]; double d2 = HPdistance2([station position], [ship position]); if ((ship != station) && (d2 < 25000000)&&([ship status] != STATUS_DOCKED)) // within 5km { HPVector ppos = [self absolutePositionForSubentity]; d2 = HPdistance2(ppos, ship->position); if (d2 < 4000000) // within 2km of the port entrance { Quaternion q1 = [station orientation]; q1 = quaternion_multiply([self orientation], q1); // HPVector v_out = HPvector_forward_from_quaternion(q1); HPVector r_pos = make_HPvector(ship->position.x - ppos.x, ship->position.y - ppos.y, ship->position.z - ppos.z); if (r_pos.x||r_pos.y||r_pos.z) r_pos = HPvector_normal(r_pos); else r_pos.z = 1.0; // double vdp = HPdot_product(v_out, r_pos); //== cos of the angle between r_pos and v_out // if (vdp > 0.86) { isEmpty = NO; last_launch_time = unitime - STATION_DELAY_BETWEEN_LAUNCHES + STATION_LAUNCH_RETRY_INTERVAL; } } } } for (i = 0; i < ship_count; i++) { [my_entities[i] release]; //released } return isEmpty; } - (void) clearDockingCorridor { // check against all ships StationEntity *station = (StationEntity *)[self parentEntity]; BOOL isClear = YES; int ent_count = UNIVERSE->n_entities; Entity **uni_entities = UNIVERSE->sortedEntities; // grab the public sorted list Entity *my_entities[ent_count]; int i; int ship_count = 0; for (i = 0; i < ent_count; i++) { if (uni_entities[i]->isShip) { my_entities[ship_count++] = [uni_entities[i] retain]; // retained } } for (i = 0; i < ship_count; i++) { ShipEntity *ship = (ShipEntity*)my_entities[i]; double d2 = HPdistance2([station position], [ship position]); if ((ship != station)&&(d2 < 25000000)&&([ship status] != STATUS_DOCKED)) // within 5km { HPVector ppos = [self absolutePositionForSubentity]; float time_out = -15.00; // 15 secs do { isClear = YES; d2 = HPdistance2(ppos, ship->position); if (d2 < 4000000) // within 2km of the port entrance { Quaternion q1 = [station orientation]; q1 = quaternion_multiply([self orientation], q1); // Vector v_out = vector_forward_from_quaternion(q1); Vector r_pos = make_vector(ship->position.x - ppos.x, ship->position.y - ppos.y, ship->position.z - ppos.z); if (r_pos.x||r_pos.y||r_pos.z) r_pos = vector_normal(r_pos); else r_pos.z = 1.0; // double vdp = dot_product(v_out, r_pos); //== cos of the angle between r_pos and v_out // if (vdp > 0.86) { isClear = NO; // okay it's in the way .. give it a wee nudge (0.25s) [ship update: 0.25]; time_out += 0.25; } if (time_out > 0) { HPVector v1 = HPvector_forward_from_quaternion(orientation); HPVector spos = ship->position; spos.x += 3000.0 * v1.x; spos.y += 3000.0 * v1.y; spos.z += 3000.0 * v1.z; [ship setPosition:spos]; // move 3km out of the way } } } while (!isClear); } } for (i = 0; i < ship_count; i++) { [my_entities[i] release]; //released } } - (void)setDimensionsAndCorridor:(BOOL)docking :(BOOL)ddc :(BOOL)launching { StationEntity *station = (StationEntity*)[self parentEntity]; if (virtual_dock) { port_dimensions = [station virtualPortDimensions]; } else { BoundingBox bb = [self boundingBox]; port_dimensions = make_vector(bb.max.x - bb.min.x, bb.max.y - bb.min.y, bb.max.z - bb.min.z); } HPVector vk = HPvector_forward_from_quaternion(orientation); BoundingBox stbb = [station boundingBox]; HPVector start = position; while ((start.x > stbb.min.x)&&(start.x < stbb.max.x) && (start.y > stbb.min.y)&&(start.y < stbb.max.y) && (start.z > stbb.min.z)&&(start.z < stbb.max.z) ) { start = HPvector_add(start, HPvector_multiply_scalar(vk, port_dimensions.z)); } port_corridor = start.z - position.z; allow_docking = docking; disallowed_docking_collides = ddc; allow_launching = launching; } //////////////////////////////////////////////// from superclass - (BOOL) isDock { return YES; } - (id)initWithKey:(NSString *)key definition:(NSDictionary *)dict { OOJS_PROFILE_ENTER self = [super initWithKey:key definition:dict]; if (self != nil) { shipsOnApproach = [[NSMutableDictionary alloc] init]; launchQueue = [[NSMutableArray alloc] init]; allow_docking = YES; disallowed_docking_collides = NO; allow_launching = YES; virtual_dock = NO; } return self; OOJS_PROFILE_EXIT } - (void) dealloc { DESTROY(shipsOnApproach); DESTROY(launchQueue); [self clearIdLocks:nil]; [super dealloc]; } - (void) clearIdLocks:(ShipEntity *)ship { int i; for (i = 1; i < MAX_DOCKING_STAGES; i++) { if (ship == nil || ship == [id_lock[i] weakRefUnderlyingObject]) { DESTROY(id_lock[i]); } } } - (void) clearAllIdLocks { int i; for (i = 1; i < MAX_DOCKING_STAGES; i++) { DESTROY(id_lock[i]); } } - (BOOL) setUpShipFromDictionary:(NSDictionary *) dict { OOJS_PROFILE_ENTER isShip = YES; isStation = NO; if (![super setUpShipFromDictionary:dict]) return NO; return YES; OOJS_PROFILE_EXIT } - (void) update:(OOTimeDelta) delta_t { [super update:delta_t]; if (([launchQueue count] > 0)&&([shipsOnApproach count] == 0)&&[self dockingCorridorIsEmpty]) { ShipEntity *se=(ShipEntity *)[launchQueue objectAtIndex:0]; // check to make sure ship has not been destroyed in queue by script if ([se status] == STATUS_DOCKED) { [self launchShip:se]; } [launchQueue removeObjectAtIndex:0]; } if (([launchQueue count] == 0) && no_docking_while_launching) { no_docking_while_launching = NO; // launching complete } } // avoid possibility of shooting the virtual dock damaging the station - (void) noteTakingDamage:(double)amount from:(Entity *)entity type:(OOShipDamageType)type { if (virtual_dock) // can't be damaged { return; } [super noteTakingDamage:amount from:entity type:type]; } - (void) takeEnergyDamage:(double)amount from:(Entity *)ent becauseOf:(Entity *)other { if (virtual_dock) // can't be damaged { return; } [super takeEnergyDamage:amount from:ent becauseOf:other]; } // virtual docks are invisible - (void) drawImmediate:(bool)immediate translucent:(bool)translucent { if (virtual_dock) // not drawn { return; } [super drawImmediate:immediate translucent:translucent]; } @end oolite-1.82/src/Core/Entities/DustEntity.h000066400000000000000000000026661256642440500205110ustar00rootroot00000000000000/* DustEntity.h Entity representing a number of dust particles. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "Entity.h" #import "OOOpenGLExtensionManager.h" #import "OOTexture.h" #define DUST_SCALE 2000 #define DUST_N_PARTICLES 600 @class OOColor, OOShaderProgram, OOShaderUniform; @interface DustEntity: Entity { @private OOColor *dust_color; Vector vertices[DUST_N_PARTICLES * 2]; GLushort indices[DUST_N_PARTICLES * 2]; GLfloat color_fv[4]; OOTexture *texture; bool hasPointSprites; #if OO_SHADERS GLfloat warpinessAttr[DUST_N_PARTICLES * 2]; OOShaderProgram *shader; NSArray *uniforms; uint8_t shaderMode; #endif } - (void) setDustColor:(OOColor *) color; - (OOColor *) dustColor; @end oolite-1.82/src/Core/Entities/DustEntity.m000066400000000000000000000255561256642440500205210ustar00rootroot00000000000000/* DustEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "DustEntity.h" #import "OOMaths.h" #import "Universe.h" #import "MyOpenGLView.h" #import "OOGraphicsResetManager.h" #import "OODebugFlags.h" #import "OOMacroOpenGL.h" #if OO_SHADERS #import "OOMaterial.h" // For kTangentAttributeIndex #import "OOShaderProgram.h" #import "OOShaderUniform.h" #endif #import "PlayerEntity.h" #define FAR_PLANE (DUST_SCALE * 0.50f) #define NEAR_PLANE (DUST_SCALE * 0.25f) // Declare protocol conformance @interface DustEntity (Private) - (void) checkShaderMode; @end #if OO_SHADERS enum { kShaderModeOff, kShaderModeOn, kShaderModeUnknown }; #endif @implementation DustEntity - (id) init { int vi; // this should be unnecessary // ranrot_srand((uint32_t)[[NSDate date] timeIntervalSince1970]); // seed randomiser by time self = [super init]; for (vi = 0; vi < DUST_N_PARTICLES; vi++) { vertices[vi].x = (ranrot_rand() % DUST_SCALE) - DUST_SCALE / 2; vertices[vi].y = (ranrot_rand() % DUST_SCALE) - DUST_SCALE / 2; vertices[vi].z = (ranrot_rand() % DUST_SCALE) - DUST_SCALE / 2; // Set up element index array for warp mode. indices[vi * 2] = vi; indices[vi * 2 + 1] = vi + DUST_N_PARTICLES; #if OO_SHADERS vertices[vi + DUST_N_PARTICLES] = vertices[vi]; warpinessAttr[vi] = 0.0f; warpinessAttr[vi + DUST_N_PARTICLES] = 1.0f; #endif } #if OO_SHADERS shaderMode = kShaderModeUnknown; #endif dust_color = [[OOColor colorWithRed:0.5 green:1.0 blue:1.0 alpha:1.0] retain]; [self setStatus:STATUS_ACTIVE]; hasPointSprites = [[OOOpenGLExtensionManager sharedManager] haveExtension:@"GL_ARB_point_sprite"]; if (hasPointSprites) { texture = [[OOTexture textureWithName:@"oolite-particle-dust.png" inFolder:@"Textures" options:kOOTextureMinFilterMipMap | kOOTextureMagFilterLinear | kOOTextureAlphaMask anisotropy:kOOTextureDefaultAnisotropy / 2.0 lodBias:0.0] retain]; } collision_radius = DUST_SCALE; // for draw pass calculations [[OOGraphicsResetManager sharedManager] registerClient:self]; return self; } - (void) dealloc { DESTROY(dust_color); [[OOGraphicsResetManager sharedManager] unregisterClient:self]; DESTROY(texture); #if OO_SHADERS DESTROY(shader); DESTROY(uniforms); #endif [super dealloc]; } - (void) setDustColor:(OOColor *) color { if (dust_color) [dust_color release]; dust_color = [color retain]; [dust_color getRed:&color_fv[0] green:&color_fv[1] blue:&color_fv[2] alpha:&color_fv[3]]; } - (OOColor *) dustColor { return dust_color; } - (BOOL) canCollide { return NO; } - (void) updateCameraRelativePosition { HPVector c_pos = [PLAYER viewpointPosition]; cameraRelativePosition = make_vector((OOScalar)-fmod(c_pos.x,DUST_SCALE),(OOScalar)-fmod(c_pos.y,DUST_SCALE),(OOScalar)-fmod(c_pos.z,DUST_SCALE)); } - (void) update:(OOTimeDelta) delta_t { // [self setPosition:position]; zero_distance = 0.0; #if OO_SHADERS if (EXPECT_NOT(shaderMode == kShaderModeUnknown)) [self checkShaderMode]; // Shader takes care of repositioning. if (shaderMode == kShaderModeOn) return; #endif Vector offset = vector_flip(cameraRelativePosition); GLfloat half_scale = DUST_SCALE * 0.50; int vi; for (vi = 0; vi < DUST_N_PARTICLES; vi++) { while (vertices[vi].x - offset.x < -half_scale) vertices[vi].x += DUST_SCALE; while (vertices[vi].x - offset.x > half_scale) vertices[vi].x -= DUST_SCALE; while (vertices[vi].y - offset.y < -half_scale) vertices[vi].y += DUST_SCALE; while (vertices[vi].y - offset.y > half_scale) vertices[vi].y -= DUST_SCALE; while (vertices[vi].z - offset.z < -half_scale) vertices[vi].z += DUST_SCALE; while (vertices[vi].z - offset.z > half_scale) vertices[vi].z -= DUST_SCALE; } } #if OO_SHADERS - (OOShaderProgram *) shader { if (shader == nil) { NSString *prefix = [NSString stringWithFormat: @"#define OODUST_SCALE_MAX (float(%g))\n" "#define OODUST_SCALE_FACTOR (float(%g))\n" "#define OODUST_SIZE (float(%g))\n", FAR_PLANE / NEAR_PLANE, 1.0f / (FAR_PLANE - NEAR_PLANE), (float)DUST_SCALE]; // Reuse tangent attribute ID for "warpiness", as we don't need a tangent. NSDictionary *attributes = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kTangentAttributeIndex] forKey:@"aWarpiness"]; shader = [[OOShaderProgram shaderProgramWithVertexShaderName:@"oolite-dust.vertex" fragmentShaderName:@"oolite-dust.fragment" prefix:prefix attributeBindings:attributes] retain]; DESTROY(uniforms); OOShaderUniform *uWarp = [[OOShaderUniform alloc] initWithName:@"uWarp" shaderProgram:shader boundToObject:self property:@selector(warpVector) convertOptions:0]; OOShaderUniform *uOffsetPlayerPosition = [[OOShaderUniform alloc] initWithName:@"uOffsetPlayerPosition" shaderProgram:shader boundToObject:self property:@selector(offsetPlayerPosition) convertOptions:0]; uniforms = [[NSArray alloc] initWithObjects:uWarp, uOffsetPlayerPosition, nil]; [uWarp release]; [uOffsetPlayerPosition release]; } return shader; } - (Vector) offsetPlayerPosition { // used as shader uniform, so needs to be low precision HPVector c_pos = [PLAYER viewpointPosition]; Vector offset = make_vector((OOScalar)fmod(c_pos.x,DUST_SCALE),(OOScalar)fmod(c_pos.y,DUST_SCALE),(OOScalar)fmod(c_pos.z,DUST_SCALE)); return vector_subtract(offset, make_vector(DUST_SCALE * 0.5f, DUST_SCALE * 0.5f, DUST_SCALE * 0.5f)); } - (void) checkShaderMode { shaderMode = kShaderModeOff; if ([UNIVERSE detailLevel] >= DETAIL_LEVEL_SHADERS) { if ([[OOOpenGLExtensionManager sharedManager] useDustShader]) { shaderMode = kShaderModeOn; } } } #endif - (Vector) warpVector { return vector_multiply_scalar([PLAYER velocity], 1.0f / HYPERSPEED_FACTOR); } - (void) drawImmediate:(bool)immediate translucent:(bool)translucent { if ([UNIVERSE breakPatternHide] || !translucent) return; // DON'T DRAW PlayerEntity* player = PLAYER; assert(player != nil); #ifndef NDEBUG if (gDebugFlags & DEBUG_NO_DUST) return; #endif #if OO_SHADERS if (EXPECT_NOT(shaderMode == kShaderModeUnknown)) [self checkShaderMode]; BOOL useShader = (shaderMode == kShaderModeOn); #endif GLfloat *fogcolor = [UNIVERSE skyClearColor]; float idealDustSize = [[UNIVERSE gameView] viewSize].width / 800.0f; BOOL warp_stars = [player atHyperspeed]; float dustIntensity; OO_ENTER_OPENGL(); OOSetOpenGLState(OPENGL_STATE_OPAQUE); OOGL(glDisableClientState(GL_NORMAL_ARRAY)); if (!warp_stars) { // Draw points. float dustPointSize = ceil(idealDustSize); if (dustPointSize < 1.0f) dustPointSize = 1.0f; OOGL(GLScaledPointSize(dustPointSize)); dustIntensity = OOClamp_0_1_f(idealDustSize / dustPointSize); } else { // Draw lines. float idealLineSize = idealDustSize * 0.5f; float dustLineSize = ceil(idealLineSize); if (dustLineSize < 1.0f) dustLineSize = 1.0f; GLScaledLineWidth(dustLineSize); dustIntensity = OOClamp_0_1_f(idealLineSize / dustLineSize); } if (fogcolor[3] > 0.0) { // fade out dust when entering atmosphere (issue #100) dustIntensity = OOClamp_0_1_f(dustIntensity-(fogcolor[3]*3.0)); } if (dustIntensity > 0.0) { float *color = NULL; if (player->isSunlit) color = color_fv; else color = UNIVERSE->stars_ambient; OOGL(glColor4f(color[0], color[1], color[2], dustIntensity)); #if OO_SHADERS if (useShader) { [[self shader] apply]; [uniforms makeObjectsPerformSelector:@selector(apply)]; } else #endif { OOGL(glEnable(GL_FOG)); OOGL(glFogi(GL_FOG_MODE, GL_LINEAR)); OOGL(glFogfv(GL_FOG_COLOR, fogcolor)); OOGL(glHint(GL_FOG_HINT, GL_NICEST)); OOGL(glFogf(GL_FOG_START, NEAR_PLANE)); OOGL(glFogf(GL_FOG_END, FAR_PLANE)); } OOGL(glEnable(GL_BLEND)); OOGL(glDepthMask(GL_FALSE)); if (warp_stars) { OOGL(glDisable(GL_TEXTURE_2D)); #if OO_SHADERS if (useShader) { OOGL(glEnableVertexAttribArrayARB(kTangentAttributeIndex)); OOGL(glVertexAttribPointerARB(kTangentAttributeIndex, 1, GL_FLOAT, GL_FALSE, 0, warpinessAttr)); } else #endif { Vector warpVector = [self warpVector]; unsigned vi; for (vi = 0; vi < DUST_N_PARTICLES; vi++) { vertices[vi + DUST_N_PARTICLES] = vector_subtract(vertices[vi], warpVector); } } OOGL(glVertexPointer(3, GL_FLOAT, 0, vertices)); OOGL(glDrawElements(GL_LINES, DUST_N_PARTICLES * 2, GL_UNSIGNED_SHORT, indices)); #if OO_SHADERS if (useShader) { OOGL(glDisableVertexAttribArrayARB(kTangentAttributeIndex)); } #endif OOGL(glEnable(GL_TEXTURE_2D)); } else { if (hasPointSprites) { #if OO_SHADERS if (!useShader) #endif { OOGL(glBlendFunc(GL_SRC_ALPHA, GL_ONE)); } OOGL(glEnable(GL_POINT_SPRITE_ARB)); [texture apply]; OOGL(glVertexPointer(3, GL_FLOAT, 0, vertices)); OOGL(glDrawArrays(GL_POINTS, 0, DUST_N_PARTICLES)); OOGL(glDisable(GL_POINT_SPRITE_ARB)); } else { OOGL(glDisable(GL_TEXTURE_2D)); OOGL(glVertexPointer(3, GL_FLOAT, 0, vertices)); OOGL(glDrawArrays(GL_POINTS, 0, DUST_N_PARTICLES)); OOGL(glEnable(GL_TEXTURE_2D)); } } // reapply normal conditions #if OO_SHADERS if (useShader) { [OOShaderProgram applyNone]; } else #endif { OOGL(glDisable(GL_FOG)); OOGL(glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA)); } OOGL(glDisable(GL_BLEND)); OOGL(glDepthMask(GL_TRUE)); } OOGL(glEnableClientState(GL_NORMAL_ARRAY)); OOVerifyOpenGLState(); OOCheckOpenGLErrors(@"DustEntity after drawing %@", self); } - (void) resetGraphicsState { #if OO_SHADERS DESTROY(shader); DESTROY(uniforms); shaderMode = kShaderModeUnknown; /* Duplicate vertex data. This is only required if we're switching from non-shader mode to a shader mode, but let's KISS. */ memcpy(vertices + DUST_N_PARTICLES, vertices, sizeof *vertices * DUST_N_PARTICLES); #endif } #ifndef NDEBUG - (NSString *) descriptionForObjDump { // Don't include range and visibility flag as they're irrelevant. return [self descriptionForObjDumpBasic]; } #endif @end oolite-1.82/src/Core/Entities/Entity.h000066400000000000000000000202641256642440500176430ustar00rootroot00000000000000/* Entity.h Base class for entities, i.e. drawable world objects. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #import "OOMaths.h" #import "OOCacheManager.h" #import "OOTypes.h" #import "OOWeakReference.h" @class Universe, CollisionRegion, ShipEntity, OOVisualEffectEntity; #ifndef NDEBUG extern uint32_t gLiveEntityCount; extern size_t gTotalEntityMemory; #endif #define NO_DRAW_DISTANCE_FACTOR 1024.0 #define ABSOLUTE_NO_DRAW_DISTANCE2 (2500.0 * 2500.0 * NO_DRAW_DISTANCE_FACTOR * NO_DRAW_DISTANCE_FACTOR) // ie. the furthest away thing we can draw is at 1280km (a 2.5km wide object would disappear at that range) #define SCANNER_MAX_RANGE 25600.0 #define SCANNER_MAX_RANGE2 655360000.0 #define CLOSE_COLLISION_CHECK_MAX_RANGE2 1000000000.0 #define ENTRY(label, value) label = value, typedef enum OOEntityStatus { #include "OOEntityStatus.tbl" } OOEntityStatus; #ifndef OO_SCANCLASS_TYPE #define OO_SCANCLASS_TYPE typedef enum OOScanClass OOScanClass; #endif enum OOScanClass { #include "OOScanClass.tbl" }; #undef ENTRY @interface Entity: OOWeakRefObject { // the base object for ships/stations/anything actually ////////////////////////////////////////////////////// // // @public variables: // // we forego encapsulation for some variables in order to // lose the overheads of Obj-C accessor methods... // @public OOUniversalID universalID; // used to reference the entity unsigned isShip: 1, isStation: 1, isPlayer: 1, isWormhole: 1, isSubEntity: 1, hasMoved: 1, hasRotated: 1, hasCollided: 1, isSunlit: 1, collisionTestFilter: 2, throw_sparks: 1, isImmuneToBreakPatternHide: 1, isExplicitlyNotMainStation: 1, isVisualEffect: 1; OOScanClass scanClass; GLfloat zero_distance; GLfloat cam_zero_distance; GLfloat no_draw_distance; // 10 km initially GLfloat collision_radius; HPVector position; // use high-precision vectors for global position Vector cameraRelativePosition; Quaternion orientation; int zero_index; // Linked lists of entites, sorted by position on each (world) axis Entity *x_previous, *x_next; Entity *y_previous, *y_next; Entity *z_previous, *z_next; Entity *collision_chain; OOUniversalID shadingEntityID; Entity *collider; CollisionRegion *collisionRegion; // initially nil - then maintained @protected HPVector lastPosition; Quaternion lastOrientation; GLfloat distanceTravelled; // set to zero initially OOMatrix rotMatrix; Vector velocity; GLfloat energy; GLfloat maxEnergy; BoundingBox boundingBox; GLfloat mass; NSMutableArray *collidingEntities; OOTimeAbsolute spawnTime; struct JSObject *_jsSelf; @private NSUInteger _sessionID; OOWeakReference *_owner; OOEntityStatus _status; } // The session in which the entity was created. - (NSUInteger) sessionID; - (BOOL) isShip; - (BOOL) isDock; - (BOOL) isStation; - (BOOL) isSubEntity; - (BOOL) isPlayer; - (BOOL) isPlanet; - (BOOL) isSun; - (BOOL) isStellarObject; - (BOOL) isSky; - (BOOL) isWormhole; - (BOOL) isEffect; - (BOOL) isVisualEffect; - (BOOL) isWaypoint; - (BOOL) validForAddToUniverse; - (void) addToLinkedLists; - (void) removeFromLinkedLists; - (void) updateLinkedLists; - (void) wasAddedToUniverse; - (void) wasRemovedFromUniverse; - (void) warnAboutHostiles; - (CollisionRegion *) collisionRegion; - (void) setCollisionRegion:(CollisionRegion*)region; - (void) setUniversalID:(OOUniversalID)uid; - (OOUniversalID) universalID; - (BOOL) throwingSparks; - (void) setThrowSparks:(BOOL)value; - (void) throwSparks; - (void) setOwner:(Entity *)ent; - (id) owner; - (ShipEntity *) parentEntity; // owner if self is subentity of owner, otherwise nil. - (ShipEntity *) rootShipEntity; // like parentEntity, but recursive. - (void) setPosition:(HPVector)posn; - (void) setPositionX:(OOHPScalar)x y:(OOHPScalar)y z:(OOHPScalar)z; - (HPVector) position; - (Vector) cameraRelativePosition; - (GLfloat) cameraRangeFront; - (GLfloat) cameraRangeBack; - (void) updateCameraRelativePosition; // gets a low-position relative vector - (Vector) vectorTo:(Entity *)entity; - (HPVector) absolutePositionForSubentity; - (HPVector) absolutePositionForSubentityOffset:(HPVector) offset; - (double) zeroDistance; - (double) camZeroDistance; - (NSComparisonResult) compareZeroDistance:(Entity *)otherEntity; - (BoundingBox) boundingBox; - (GLfloat) mass; - (Quaternion) orientation; - (void) setOrientation:(Quaternion) quat; - (Quaternion) normalOrientation; // Historical wart: orientation.w is reversed for player; -normalOrientation corrects this. - (void) setNormalOrientation:(Quaternion) quat; - (void) orientationChanged; - (void) setVelocity:(Vector)vel; - (Vector) velocity; - (double) speed; - (GLfloat) distanceTravelled; - (void) setDistanceTravelled:(GLfloat)value; - (void) setStatus:(OOEntityStatus)stat; - (OOEntityStatus) status; - (void) setScanClass:(OOScanClass)sClass; - (OOScanClass) scanClass; - (void) setEnergy:(GLfloat)amount; - (GLfloat) energy; - (void) setMaxEnergy:(GLfloat)amount; - (GLfloat) maxEnergy; - (void) applyRoll:(GLfloat)roll andClimb:(GLfloat)climb; - (void) applyRoll:(GLfloat)roll climb:(GLfloat) climb andYaw:(GLfloat)yaw; - (void) moveForward:(double)amount; - (OOMatrix) rotationMatrix; - (OOMatrix) drawRotationMatrix; - (OOMatrix) transformationMatrix; - (OOMatrix) drawTransformationMatrix; - (BOOL) canCollide; - (GLfloat) collisionRadius; - (GLfloat) frustumRadius; - (void) setCollisionRadius:(GLfloat)amount; - (NSMutableArray *)collisionArray; - (void) update:(OOTimeDelta)delta_t; - (void) applyVelocity:(OOTimeDelta)delta_t; - (BOOL) checkCloseCollisionWith:(Entity *)other; - (void) takeEnergyDamage:(double)amount from:(Entity *)ent becauseOf:(Entity *)other; - (void) dumpState; // General "describe situtation verbosely in log" command. - (void) dumpSelfState; // Subclasses should override this, not -dumpState, and call throught to super first. // Subclass repsonsibilities - (double) findCollisionRadius; - (void) drawImmediate:(bool)immediate translucent:(bool)translucent; - (BOOL) isVisible; - (BOOL) isInSpace; - (BOOL) isImmuneToBreakPatternHide; // For shader bindings. - (GLfloat) universalTime; - (GLfloat) spawnTime; - (GLfloat) timeElapsedSinceSpawn; #ifndef NDEBUG - (NSString *) descriptionForObjDumpBasic; - (NSString *) descriptionForObjDump; - (NSSet *) allTextures; #endif @end @protocol OOHUDBeaconIcon; // Methods that must be supported by entities with beacons, regardless of type. @protocol OOBeaconEntity - (NSComparisonResult) compareBeaconCodeWith:(Entity *) other; - (NSString *) beaconCode; - (void) setBeaconCode:(NSString *)bcode; - (NSString *) beaconLabel; - (void) setBeaconLabel:(NSString *)blabel; - (BOOL) isBeacon; - (id ) beaconDrawable; - (Entity *) prevBeacon; - (Entity *) nextBeacon; - (void) setPrevBeacon:(Entity *)beaconShip; - (void) setNextBeacon:(Entity *)beaconShip; - (BOOL) isJammingScanning; @end enum { // Values used for unknown strings. kOOEntityStatusDefault = STATUS_INACTIVE, kOOScanClassDefault = CLASS_NOT_SET }; NSString *OOStringFromEntityStatus(OOEntityStatus status) CONST_FUNC; OOEntityStatus OOEntityStatusFromString(NSString *string) PURE_FUNC; NSString *OOStringFromScanClass(OOScanClass scanClass) CONST_FUNC; OOScanClass OOScanClassFromString(NSString *string) PURE_FUNC; oolite-1.82/src/Core/Entities/Entity.m000066400000000000000000000537571256642440500176650ustar00rootroot00000000000000/* Entity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "Entity.h" #import "EntityOOJavaScriptExtensions.h" #import "PlayerEntity.h" #import "OOPlanetEntity.h" #import "OOMaths.h" #import "Universe.h" #import "GameController.h" #import "ResourceManager.h" #import "OOConstToString.h" #import "CollisionRegion.h" #import "NSScannerOOExtensions.h" #import "OODebugFlags.h" #import "NSObjectOOExtensions.h" #ifndef NDEBUG uint32_t gLiveEntityCount = 0; size_t gTotalEntityMemory = 0; #endif #ifndef NDEBUG static NSString * const kOOLogEntityAddToList = @"entity.linkedList.add"; static NSString * const kOOLogEntityAddToListError = @"entity.linkedList.add.error"; static NSString * const kOOLogEntityRemoveFromList = @"entity.linkedList.remove"; static NSString * const kOOLogEntityRemoveFromListError = @"entity.linkedList.remove.error"; static NSString * const kOOLogEntityUpdateError = @"entity.linkedList.update.error"; #endif static NSString * const kOOLogEntityVerificationError = @"entity.linkedList.verify.error"; @interface Entity (OOPrivate) - (BOOL) checkLinkedLists; @end @implementation Entity - (id) init { self = [super init]; if (EXPECT_NOT(self == nil)) return nil; _sessionID = [UNIVERSE sessionID]; orientation = kIdentityQuaternion; rotMatrix = kIdentityMatrix; position = kZeroHPVector; no_draw_distance = 100000.0; // 10 km collidingEntities = [[NSMutableArray alloc] init]; scanClass = CLASS_NOT_SET; [self setStatus:STATUS_COCKPIT_DISPLAY]; spawnTime = [UNIVERSE getTime]; isSunlit = YES; #ifndef NDEBUG gLiveEntityCount++; gTotalEntityMemory += [self oo_objectSize]; #endif return self; } - (void) dealloc { [UNIVERSE ensureEntityReallyRemoved:self]; DESTROY(collidingEntities); DESTROY(collisionRegion); [self deleteJSSelf]; [self setOwner:nil]; #ifndef NDEBUG gLiveEntityCount--; gTotalEntityMemory -= [self oo_objectSize]; #endif [super dealloc]; } - (NSString *)descriptionComponents { return [NSString stringWithFormat:@"position: %@ scanClass: %@ status: %@", HPVectorDescription([self position]), OOStringFromScanClass([self scanClass]), OOStringFromEntityStatus([self status])]; } - (NSUInteger) sessionID { return _sessionID; } - (BOOL)isShip { return isShip; } - (BOOL)isDock { return NO; } - (BOOL)isStation { return isStation; } - (BOOL)isSubEntity { return isSubEntity; } - (BOOL)isPlayer { return isPlayer; } - (BOOL)isPlanet { return NO; } - (BOOL)isSun { return NO; } - (BOOL) isStellarObject { return [self isPlanet] || [self isSun]; } - (BOOL)isSky { return NO; } - (BOOL)isWormhole { return isWormhole; } - (BOOL) isEffect { return NO; } - (BOOL) isVisualEffect { return NO; } - (BOOL) isWaypoint { return NO; } - (BOOL) validForAddToUniverse { NSUInteger mySessionID = [self sessionID]; NSUInteger currentSessionID = [UNIVERSE sessionID]; if (EXPECT_NOT(mySessionID != currentSessionID)) { OOLogERR(@"entity.invalidSession", @"Entity %@ from session %lu cannot be added to universe in session %lu. This is an internal error, please report it.", [self shortDescription], mySessionID, currentSessionID); return NO; } return YES; } - (void) addToLinkedLists { #ifndef NDEBUG if (gDebugFlags & DEBUG_LINKED_LISTS) OOLog(kOOLogEntityAddToList, @"DEBUG adding entity %@ to linked lists", self); #endif // // insert at the start if (UNIVERSE) { x_previous = nil; x_next = UNIVERSE->x_list_start; // move UP the list while ((x_next)&&(x_next->position.x - x_next->collision_radius < position.x - collision_radius)) { x_previous = x_next; x_next = x_next->x_next; } if (x_next) x_next->x_previous = self; if (x_previous) x_previous->x_next = self; else UNIVERSE->x_list_start = self; y_previous = nil; y_next = UNIVERSE->y_list_start; // move UP the list while ((y_next)&&(y_next->position.y - y_next->collision_radius < position.y - collision_radius)) { y_previous = y_next; y_next = y_next->y_next; } if (y_next) y_next->y_previous = self; if (y_previous) y_previous->y_next = self; else UNIVERSE->y_list_start = self; z_previous = nil; z_next = UNIVERSE->z_list_start; // move UP the list while ((z_next)&&(z_next->position.z - z_next->collision_radius < position.z - collision_radius)) { z_previous = z_next; z_next = z_next->z_next; } if (z_next) z_next->z_previous = self; if (z_previous) z_previous->z_next = self; else UNIVERSE->z_list_start = self; } #ifndef NDEBUG if (gDebugFlags & DEBUG_LINKED_LISTS) { if (![self checkLinkedLists]) { OOLog(kOOLogEntityAddToListError, @"DEBUG LINKED LISTS - problem encountered while adding %@ to linked lists", self); [UNIVERSE debugDumpEntities]; } } #endif } - (void) removeFromLinkedLists { #ifndef NDEBUG if (gDebugFlags & DEBUG_LINKED_LISTS) OOLog(kOOLogEntityRemoveFromList, @"DEBUG removing entity %@ from linked lists", self); #endif if ((x_next == nil)&&(x_previous == nil)) // removed already! return; // make sure the starting point is still correct if (UNIVERSE) { if ((UNIVERSE->x_list_start == self)&&(x_next)) UNIVERSE->x_list_start = x_next; if ((UNIVERSE->y_list_start == self)&&(y_next)) UNIVERSE->y_list_start = y_next; if ((UNIVERSE->z_list_start == self)&&(z_next)) UNIVERSE->z_list_start = z_next; } // if (x_previous) x_previous->x_next = x_next; if (x_next) x_next->x_previous = x_previous; // if (y_previous) y_previous->y_next = y_next; if (y_next) y_next->y_previous = y_previous; // if (z_previous) z_previous->z_next = z_next; if (z_next) z_next->z_previous = z_previous; // x_previous = nil; x_next = nil; y_previous = nil; y_next = nil; z_previous = nil; z_next = nil; #ifndef NDEBUG if (gDebugFlags & DEBUG_LINKED_LISTS) { if (![self checkLinkedLists]) { OOLog(kOOLogEntityRemoveFromListError, @"DEBUG LINKED LISTS - problem encountered while removing %@ from linked lists", self); [UNIVERSE debugDumpEntities]; } } #endif } - (BOOL) checkLinkedLists { // DEBUG check for loops if (UNIVERSE->n_entities > 0) { int n; Entity *check, *last; // last = nil; // n = UNIVERSE->n_entities; check = UNIVERSE->x_list_start; while ((n--)&&(check)) { last = check; check = check->x_next; } if ((check)||(n > 0)) { OOLog(kOOLogEntityVerificationError, @"Broken x_next %@ list (%d) ***", UNIVERSE->x_list_start, n); return NO; } // n = UNIVERSE->n_entities; check = last; while ((n--)&&(check)) check = check->x_previous; if ((check)||(n > 0)) { OOLog(kOOLogEntityVerificationError, @"Broken x_previous %@ list (%d) ***", UNIVERSE->x_list_start, n); return NO; } // n = UNIVERSE->n_entities; check = UNIVERSE->y_list_start; while ((n--)&&(check)) { last = check; check = check->y_next; } if ((check)||(n > 0)) { OOLog(kOOLogEntityVerificationError, @"Broken y_next %@ list (%d) ***", UNIVERSE->y_list_start, n); return NO; } // n = UNIVERSE->n_entities; check = last; while ((n--)&&(check)) check = check->y_previous; if ((check)||(n > 0)) { OOLog(kOOLogEntityVerificationError, @"Broken y_previous %@ list (%d) ***", UNIVERSE->y_list_start, n); return NO; } // n = UNIVERSE->n_entities; check = UNIVERSE->z_list_start; while ((n--)&&(check)) { last = check; check = check->z_next; } if ((check)||(n > 0)) { OOLog(kOOLogEntityVerificationError, @"Broken z_next %@ list (%d) ***", UNIVERSE->z_list_start, n); return NO; } // n = UNIVERSE->n_entities; check = last; while ((n--)&&(check)) check = check->z_previous; if ((check)||(n > 0)) { OOLog(kOOLogEntityVerificationError, @"Broken z_previous %@ list (%d) ***", UNIVERSE->z_list_start, n); return NO; } } return YES; } - (void) updateLinkedLists { if (!UNIVERSE) return; // not in the UNIVERSE - don't do this! if ((x_next == nil)&&(x_previous == nil)) return; // not in the lists - don't do this! #ifndef NDEBUG if (gDebugFlags & DEBUG_LINKED_LISTS) { if (![self checkLinkedLists]) { OOLog(kOOLogEntityVerificationError, @"DEBUG LINKED LISTS problem encountered before updating linked lists for %@", self); [UNIVERSE debugDumpEntities]; } } #endif // update position in linked list for position.x // take self out of list.. if (x_previous) x_previous->x_next = x_next; if (x_next) x_next->x_previous = x_previous; // sink DOWN the list while ((x_previous)&&(x_previous->position.x - x_previous->collision_radius > position.x - collision_radius)) { x_next = x_previous; x_previous = x_previous->x_previous; } // bubble UP the list while ((x_next)&&(x_next->position.x - x_next->collision_radius < position.x - collision_radius)) { x_previous = x_next; x_next = x_next->x_next; } if (x_next) // insert self into the list before x_next.. x_next->x_previous = self; if (x_previous) // insert self into the list after x_previous.. x_previous->x_next = self; if ((x_previous == nil)&&(UNIVERSE)) // if we're the first then tell the UNIVERSE! UNIVERSE->x_list_start = self; // update position in linked list for position.y // take self out of list.. if (y_previous) y_previous->y_next = y_next; if (y_next) y_next->y_previous = y_previous; // sink DOWN the list while ((y_previous)&&(y_previous->position.y - y_previous->collision_radius > position.y - collision_radius)) { y_next = y_previous; y_previous = y_previous->y_previous; } // bubble UP the list while ((y_next)&&(y_next->position.y - y_next->collision_radius < position.y - collision_radius)) { y_previous = y_next; y_next = y_next->y_next; } if (y_next) // insert self into the list before y_next.. y_next->y_previous = self; if (y_previous) // insert self into the list after y_previous.. y_previous->y_next = self; if ((y_previous == nil)&&(UNIVERSE)) // if we're the first then tell the UNIVERSE! UNIVERSE->y_list_start = self; // update position in linked list for position.z // take self out of list.. if (z_previous) z_previous->z_next = z_next; if (z_next) z_next->z_previous = z_previous; // sink DOWN the list while ((z_previous)&&(z_previous->position.z - z_previous->collision_radius > position.z - collision_radius)) { z_next = z_previous; z_previous = z_previous->z_previous; } // bubble UP the list while ((z_next)&&(z_next->position.z - z_next->collision_radius < position.z - collision_radius)) { z_previous = z_next; z_next = z_next->z_next; } if (z_next) // insert self into the list before z_next.. z_next->z_previous = self; if (z_previous) // insert self into the list after z_previous.. z_previous->z_next = self; if ((z_previous == nil)&&(UNIVERSE)) // if we're the first then tell the UNIVERSE! UNIVERSE->z_list_start = self; // done #ifndef NDEBUG if (gDebugFlags & DEBUG_LINKED_LISTS) { if (![self checkLinkedLists]) { OOLog(kOOLogEntityUpdateError, @"DEBUG LINKED LISTS problem encountered after updating linked lists for %@", self); [UNIVERSE debugDumpEntities]; } } #endif } - (void) wasAddedToUniverse { // Do nothing } - (void) wasRemovedFromUniverse { // Do nothing } - (void) warnAboutHostiles { // do nothing for now, this can be expanded in sub classes OOLog(@"general.error.subclassResponsibility.Entity-warnAboutHostiles", @"***** Entity does nothing in warnAboutHostiles"); } - (CollisionRegion*) collisionRegion { return collisionRegion; } - (void) setCollisionRegion: (CollisionRegion*) region { if (collisionRegion) [collisionRegion release]; collisionRegion = [region retain]; } - (void) setUniversalID:(OOUniversalID)uid { universalID = uid; } - (OOUniversalID) universalID { return universalID; } - (BOOL) throwingSparks { return throw_sparks; } - (void) setThrowSparks:(BOOL) value { throw_sparks = value; } - (void) throwSparks { // do nothing for now } - (void) setOwner:(Entity *)ent { [_owner release]; _owner = [ent weakRetain]; } - (id) owner { return [_owner weakRefUnderlyingObject]; } - (ShipEntity *)parentEntity { id owner = [self owner]; if ([owner isShipWithSubEntityShip:self]) return owner; return nil; } - (id) superShaderBindingTarget { return [self parentEntity]; } - (ShipEntity *) rootShipEntity { ShipEntity *parent = [self parentEntity]; if (parent != nil) return [parent rootShipEntity]; if ([self isShip]) return (ShipEntity *)self; return nil; } - (HPVector) position { return position; } - (Vector) cameraRelativePosition { return cameraRelativePosition; } - (GLfloat) cameraRangeFront { return magnitude(cameraRelativePosition) - [self frustumRadius]; } - (GLfloat) cameraRangeBack { return magnitude(cameraRelativePosition) + [self frustumRadius]; } // Exposed to uniform bindings. // so needs to remain at OpenGL precision levels - (Vector) relativePosition { return HPVectorToVector(HPvector_subtract([self position], [PLAYER position])); } - (Vector) vectorTo:(Entity *)entity { return HPVectorToVector(HPvector_subtract([entity position], [self position])); } - (void) setPosition:(HPVector) posn { position = posn; [self updateCameraRelativePosition]; } - (void) setPositionX:(OOHPScalar)x y:(OOHPScalar)y z:(OOHPScalar)z { position.x = x; position.y = y; position.z = z; [self updateCameraRelativePosition]; } - (void) updateCameraRelativePosition { cameraRelativePosition = HPVectorToVector(HPvector_subtract([self absolutePositionForSubentity],[PLAYER viewpointPosition])); } - (HPVector) absolutePositionForSubentity { return [self absolutePositionForSubentityOffset:kZeroHPVector]; } - (HPVector) absolutePositionForSubentityOffset:(HPVector) offset { HPVector abspos = HPvector_add(position, OOHPVectorMultiplyMatrix(offset, rotMatrix)); Entity *last = nil; Entity *father = [self parentEntity]; while (father != nil && father != last) { abspos = HPvector_add(OOHPVectorMultiplyMatrix(abspos, [father drawRotationMatrix]), [father position]); last = father; if (![last isSubEntity]) break; father = [father owner]; } return abspos; } - (double) zeroDistance { return zero_distance; } - (double) camZeroDistance { return cam_zero_distance; } - (NSComparisonResult) compareZeroDistance:(Entity *)otherEntity { if ((otherEntity)&&(zero_distance > otherEntity->zero_distance)) return NSOrderedAscending; else return NSOrderedDescending; } - (BoundingBox) boundingBox { return boundingBox; } - (GLfloat) mass { return mass; } - (void) setOrientation:(Quaternion) quat { orientation = quat; [self orientationChanged]; } - (Quaternion) orientation { return orientation; } - (Quaternion) normalOrientation { return [self orientation]; } - (void) setNormalOrientation:(Quaternion) quat { [self setOrientation:quat]; } - (void) orientationChanged { quaternion_normalize(&orientation); rotMatrix = OOMatrixForQuaternionRotation(orientation); } - (void) setVelocity:(Vector) vel { velocity = vel; } - (Vector) velocity { return velocity; } - (double) speed { return magnitude([self velocity]); } - (GLfloat) distanceTravelled { return distanceTravelled; } - (void) setDistanceTravelled: (GLfloat) value { distanceTravelled = value; } - (void) setStatus:(OOEntityStatus) stat { _status = stat; } - (OOEntityStatus) status { return _status; } - (void) setScanClass:(OOScanClass)sClass { scanClass = sClass; } - (OOScanClass) scanClass { return scanClass; } - (void) setEnergy:(GLfloat) amount { energy = amount; } - (GLfloat) energy { return energy; } - (void) setMaxEnergy:(GLfloat)amount { maxEnergy = amount; } - (GLfloat) maxEnergy { return maxEnergy; } - (void) applyRoll:(GLfloat) roll andClimb:(GLfloat) climb { if ((roll == 0.0)&&(climb == 0.0)&&(!hasRotated)) return; if (roll) quaternion_rotate_about_z(&orientation, -roll); if (climb) quaternion_rotate_about_x(&orientation, -climb); [self orientationChanged]; } - (void) applyRoll:(GLfloat) roll climb:(GLfloat) climb andYaw:(GLfloat) yaw { if ((roll == 0.0)&&(climb == 0.0)&&(yaw == 0.0)&&(!hasRotated)) return; if (roll) quaternion_rotate_about_z(&orientation, -roll); if (climb) quaternion_rotate_about_x(&orientation, -climb); if (yaw) quaternion_rotate_about_y(&orientation, -yaw); [self orientationChanged]; } - (void) moveForward:(double)amount { HPVector forward = HPvector_multiply_scalar(HPvector_forward_from_quaternion(orientation), amount); position = HPvector_add(position, forward); distanceTravelled += amount; } - (OOMatrix) rotationMatrix { return rotMatrix; } - (OOMatrix) drawRotationMatrix { return rotMatrix; } - (OOMatrix) transformationMatrix { OOMatrix result = rotMatrix; return OOMatrixHPTranslate(result, position); } - (OOMatrix) drawTransformationMatrix { OOMatrix result = rotMatrix; return OOMatrixHPTranslate(result, position); } - (BOOL) canCollide { return YES; } - (GLfloat) collisionRadius { return collision_radius; } - (GLfloat) frustumRadius { return collision_radius; } - (void) setCollisionRadius:(GLfloat) amount { collision_radius = amount; } - (NSMutableArray *) collisionArray { return collidingEntities; } - (void) update:(OOTimeDelta)delta_t { if (_status != STATUS_COCKPIT_DISPLAY) { if ([self isSubEntity]) { zero_distance = [[self owner] zeroDistance]; cam_zero_distance = [[self owner] camZeroDistance]; [self updateCameraRelativePosition]; } else { zero_distance = HPdistance2(PLAYER->position, position); cam_zero_distance = HPdistance2([PLAYER viewpointPosition], position); [self updateCameraRelativePosition]; } } else { zero_distance = HPmagnitude2(position); cam_zero_distance = zero_distance; cameraRelativePosition = HPVectorToVector(position); } if ([self status] != STATUS_COCKPIT_DISPLAY) { [self applyVelocity:delta_t]; } hasMoved = !HPvector_equal(position, lastPosition); hasRotated = !quaternion_equal(orientation, lastOrientation); lastPosition = position; lastOrientation = orientation; } - (void) applyVelocity:(OOTimeDelta)delta_t { position = HPvector_add(position, HPvector_multiply_scalar(vectorToHPVector(velocity), delta_t)); } - (BOOL) checkCloseCollisionWith:(Entity *)other { return other != nil; } - (double)findCollisionRadius { OOLogGenericSubclassResponsibility(); return 0; } - (void) drawImmediate:(bool)immediate translucent:(bool)translucent { OOLogGenericSubclassResponsibility(); } - (void) takeEnergyDamage:(double) amount from:(Entity *) ent becauseOf:(Entity *) other { } - (void)dumpState { if (OOLogWillDisplayMessagesInClass(@"dumpState")) { OOLog(@"dumpState", @"State for %@:", self); OOLogPushIndent(); OOLogIndent(); @try { [self dumpSelfState]; } @catch (id exception) {} OOLogPopIndent(); } } - (void)dumpSelfState { NSMutableArray *flags = nil; NSString *flagsString = nil; id owner = [self owner]; if (owner == self) owner = @"self"; else if (owner == nil) owner = @"none"; OOLog(@"dumpState.entity", @"Universal ID: %u", universalID); OOLog(@"dumpState.entity", @"Scan class: %@", OOStringFromScanClass(scanClass)); OOLog(@"dumpState.entity", @"Status: %@", OOStringFromEntityStatus([self status])); OOLog(@"dumpState.entity", @"Position: %@", HPVectorDescription(position)); OOLog(@"dumpState.entity", @"Orientation: %@", QuaternionDescription(orientation)); OOLog(@"dumpState.entity", @"Distance travelled: %g", distanceTravelled); OOLog(@"dumpState.entity", @"Energy: %g of %g", energy, maxEnergy); OOLog(@"dumpState.entity", @"Mass: %g", mass); OOLog(@"dumpState.entity", @"Owner: %@", owner); flags = [NSMutableArray array]; #define ADD_FLAG_IF_SET(x) if (x) { [flags addObject:@#x]; } ADD_FLAG_IF_SET(isShip); ADD_FLAG_IF_SET(isStation); ADD_FLAG_IF_SET(isPlayer); ADD_FLAG_IF_SET(isWormhole); ADD_FLAG_IF_SET(isSubEntity); ADD_FLAG_IF_SET(hasMoved); ADD_FLAG_IF_SET(hasRotated); ADD_FLAG_IF_SET(isSunlit); ADD_FLAG_IF_SET(throw_sparks); flagsString = [flags count] ? [flags componentsJoinedByString:@", "] : (NSString *)@"none"; OOLog(@"dumpState.entity", @"Flags: %@", flagsString); OOLog(@"dumpState.entity", @"Collision Test Filter: %u", collisionTestFilter); } - (void)subEntityReallyDied:(ShipEntity *)sub { OOLog(@"entity.bug", @"%s called for non-ship entity %p by %p", __PRETTY_FUNCTION__, self, sub); } // For shader bindings. - (GLfloat)universalTime { return [UNIVERSE getTime]; } - (GLfloat)spawnTime { return spawnTime; } - (GLfloat)timeElapsedSinceSpawn { return [UNIVERSE getTime] - spawnTime; } #ifndef NDEBUG - (NSString *) descriptionForObjDumpBasic { NSString *result = [self descriptionComponents]; if (result != nil) result = [NSString stringWithFormat:@"%@ %@", NSStringFromClass([self class]), result]; else result = [self description]; return result; } - (NSString *) descriptionForObjDump { NSString *result = [self descriptionForObjDumpBasic]; result = [result stringByAppendingFormat:@" range: %g (visible: %@)", HPdistance([self position], [PLAYER position]), [self isVisible] ? @"yes" : @"no"]; return result; } - (NSSet *) allTextures { return nil; } #endif - (BOOL) isVisible { return cam_zero_distance <= ABSOLUTE_NO_DRAW_DISTANCE2; } - (BOOL) isInSpace { switch ([self status]) { case STATUS_IN_FLIGHT: case STATUS_DOCKING: case STATUS_LAUNCHING: case STATUS_AUTOPILOT_ENGAGED: case STATUS_WITCHSPACE_COUNTDOWN: case STATUS_BEING_SCOOPED: case STATUS_EFFECT: case STATUS_ACTIVE: return YES; default: return NO; } } - (BOOL) isImmuneToBreakPatternHide { return isImmuneToBreakPatternHide; } @end oolite-1.82/src/Core/Entities/EntityShaderBindings.m000066400000000000000000000032641256642440500224560ustar00rootroot00000000000000/* EntityShaderBindings.m Extra methods exposed for shader bindings. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "Entity.h" #import "PlayerEntityScriptMethods.h" #import "PlayerEntityLegacyScriptEngine.h" @implementation Entity (ShaderBindings) // Clock time. - (GLfloat) clock { return [PLAYER clockTime]; } // System "flavour" numbers. - (unsigned) pseudoFixedD100 { return [PLAYER systemPseudoRandom100]; } - (unsigned) pseudoFixedD256 { return [PLAYER systemPseudoRandom256]; } // System attributes. - (unsigned) systemGovernment { return [[PLAYER systemGovernment_number] unsignedIntValue]; } - (unsigned) systemEconomy { return [[PLAYER systemEconomy_number] unsignedIntValue]; } - (unsigned) systemTechLevel { return [[PLAYER systemTechLevel_number] unsignedIntValue]; } - (unsigned) systemPopulation { return [[PLAYER systemPopulation_number] unsignedIntValue]; } - (unsigned) systemProductivity { return [[PLAYER systemProductivity_number] unsignedIntValue]; } @end oolite-1.82/src/Core/Entities/OOBreakPatternEntity.h000066400000000000000000000031271256642440500224030ustar00rootroot00000000000000/* OOBreakPatternEntity.h Entity implementing tunnel effect for hyperspace and stations. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "Entity.h" @class OOColor; enum { kOOBreakPatternMaxSides = 128, kOOBreakPatternMaxVertices = (kOOBreakPatternMaxSides + 1) * 2 }; #define BREAK_PATTERN_RING_SPACING 50.0 #define BREAK_PATTERN_RING_SPEED 200.0 @interface OOBreakPatternEntity: Entity { @private Vector _vertexPosition[kOOBreakPatternMaxVertices]; GLfloat _vertexColor[kOOBreakPatternMaxVertices][4]; NSUInteger _vertexCount; double _lifetime; } + (instancetype) breakPatternWithPolygonSides:(NSUInteger)sides startAngle:(float)startAngleDegrees aspectRatio:(float)aspectRatio; - (void) setInnerColor:(OOColor *)color1 outerColor:(OOColor *)color2; - (void) setLifetime:(double)lifetime; @end @interface Entity (OOBreakPatternEntity) - (BOOL) isBreakPattern; @end oolite-1.82/src/Core/Entities/OOBreakPatternEntity.m000066400000000000000000000106141256642440500224070ustar00rootroot00000000000000/* OOBreakPatternEntity.m Entity implementing tunnel effect for hyperspace and stations. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOBreakPatternEntity.h" #import "OOColor.h" #import "Universe.h" #import "OOMacroOpenGL.h" @interface OOBreakPatternEntity (Private) - (void) setInnerColorComponents:(GLfloat[4])color1 outerColorComponents:(GLfloat[4])color2; @end @implementation OOBreakPatternEntity - (id) initWithPolygonSides:(NSUInteger)sides startAngle:(float)startAngleDegrees aspectRatio:(float)aspectRatio { sides = MIN(MAX((NSUInteger)3, sides), (NSUInteger)kOOBreakPatternMaxSides); if ((self = [super init])) { _vertexCount = (sides + 1) * 2; float angle = startAngleDegrees * M_PI / 180.0f; float deltaAngle = M_PI * 2.0f / sides; float xAspect = fmin(1.0f, aspectRatio); float yAspect = fmin(1.0f, 1.0f / aspectRatio); NSUInteger vi = 0; for (NSUInteger i = 0; i < sides; i++) { float s = sin(angle) * xAspect; float c = cos(angle) * yAspect; _vertexPosition[vi++] = (Vector) { s * 50, c * 50, -40 }; _vertexPosition[vi++] = (Vector) { s * 40, c * 40, 0 }; angle += deltaAngle; } _vertexPosition[vi++] = _vertexPosition[0]; _vertexPosition[vi++] = _vertexPosition[1]; [self setInnerColorComponents:(GLfloat[]){ 1.0f, 0.0f, 0.0f, 0.5f } outerColorComponents:(GLfloat[]){ 0.0f, 0.0f, 1.0f, 0.25f }]; [self setStatus:STATUS_EFFECT]; [self setScanClass:CLASS_NO_DRAW]; isImmuneToBreakPatternHide = YES; } return self; } + (instancetype) breakPatternWithPolygonSides:(NSUInteger)sides startAngle:(float)startAngleDegrees aspectRatio:(float)aspectRatio { return [[[self alloc] initWithPolygonSides:sides startAngle:startAngleDegrees aspectRatio:aspectRatio] autorelease]; } - (void) setInnerColor:(OOColor *)color1 outerColor:(OOColor *)color2 { GLfloat inner[4], outer[4]; [color1 getRed:&inner[0] green:&inner[1] blue:&inner[2] alpha:&inner[3]]; [color2 getRed:&outer[0] green:&outer[1] blue:&outer[2] alpha:&outer[3]]; [self setInnerColorComponents:inner outerColorComponents:outer]; } - (void) setInnerColorComponents:(GLfloat[4])color1 outerColorComponents:(GLfloat[4])color2 { GLfloat *colors[2] = { color1, color2 }; for (NSUInteger i = 0; i < _vertexCount; i++) { GLfloat *color = colors[i & 1]; memcpy(&_vertexColor[i], color, sizeof (GLfloat) * 4); } } - (void) setLifetime:(double)lifetime { _lifetime = lifetime; } - (void) update:(OOTimeDelta) delta_t { [super update:delta_t]; _lifetime -= BREAK_PATTERN_RING_SPEED * delta_t; if (_lifetime < 0.0) { [UNIVERSE removeEntity:self]; } } - (void) drawImmediate:(bool)immediate translucent:(bool)translucent { // check if has been hidden. if (!isImmuneToBreakPatternHide) return; if (translucent || immediate) { OO_ENTER_OPENGL(); OOSetOpenGLState(OPENGL_STATE_OPAQUE); OOGL(glDisable(GL_LIGHTING)); OOGL(glDisable(GL_TEXTURE_2D)); OOGL(glEnable(GL_BLEND)); OOGL(glDepthMask(GL_FALSE)); OOGL(glDisableClientState(GL_NORMAL_ARRAY)); OOGL(glVertexPointer(3, GL_FLOAT, 0, _vertexPosition)); OOGL(glEnableClientState(GL_COLOR_ARRAY)); OOGL(glColorPointer(4, GL_FLOAT, 0, _vertexColor)); OOGL(glDrawArrays(GL_TRIANGLE_STRIP, 0, _vertexCount)); OOGL(glEnable(GL_LIGHTING)); OOGL(glEnable(GL_TEXTURE_2D)); OOGL(glDisable(GL_BLEND)); OOGL(glDepthMask(GL_TRUE)); OOGL(glEnableClientState(GL_NORMAL_ARRAY)); OOGL(glDisableClientState(GL_COLOR_ARRAY)); OOVerifyOpenGLState(); OOCheckOpenGLErrors(@"OOBreakPatternEntity after drawing %@", self); } } - (BOOL) canCollide { return NO; } - (BOOL) isBreakPattern { return YES; } @end @implementation Entity (OOBreakPatternEntity) - (BOOL) isBreakPattern { return NO; } @end oolite-1.82/src/Core/Entities/OOECMBlastEntity.h000066400000000000000000000021371256642440500214130ustar00rootroot00000000000000/* OOECMBlastEntity.h Invisible entity which radiates ECM blast energy. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "Entity.h" @class ShipEntity; @interface OOECMBlastEntity: Entity { @private OOTimeDelta _nextBlast; uint_fast8_t _blastsRemaining; OOWeakReference *_ship; } - (id) initFromShip:(ShipEntity *)ship; @end @interface Entity (OOECMBlastEntity) - (BOOL) isECMBlast; @end oolite-1.82/src/Core/Entities/OOECMBlastEntity.m000066400000000000000000000061651256642440500214250ustar00rootroot00000000000000/* OOECMBlastEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOECMBlastEntity.h" #import "Universe.h" #import "ShipEntity.h" #import "OOEntityFilterPredicate.h" #import "OOJavaScriptEngine.h" // NOTE: these values are documented for scripting, be careful about changing them. #define ECM_EFFECT_DURATION 2.0 #define ECM_PULSE_COUNT 4 #define ECM_PULSE_INTERVAL (ECM_EFFECT_DURATION / (double)ECM_PULSE_COUNT) #define ECM_DEBUG_DRAW 0 #if ECM_DEBUG_DRAW #import "OODebugGLDrawing.h" #endif @implementation OOECMBlastEntity - (id) initFromShip:(ShipEntity *)ship { if (ship == nil) { DESTROY(self); } else if ((self = [super init])) { _blastsRemaining = ECM_PULSE_COUNT; _nextBlast = ECM_PULSE_INTERVAL; _ship = [ship weakRetain]; [self setPosition:[ship position]]; [self setStatus:STATUS_EFFECT]; [self setScanClass:CLASS_NO_DRAW]; } return self; } - (void) update:(OOTimeDelta)delta_t { _nextBlast -= delta_t; ShipEntity *ship = [_ship weakRefUnderlyingObject]; BOOL validShip = (ship != nil) && ([ship status] != STATUS_DEAD); if (_nextBlast <= 0.0 && validShip) { // Do ECM stuff. double radius = OOClamp_0_1_d((double)(ECM_PULSE_COUNT - _blastsRemaining + 1) * 1.0 / (double)ECM_PULSE_COUNT); radius *= SCANNER_MAX_RANGE; _blastsRemaining--; NSArray *targets = [UNIVERSE findEntitiesMatchingPredicate:IsShipPredicate parameter:NULL inRange:radius ofEntity:self]; NSUInteger i, count = [targets count]; if (count > 0) { JSContext *context = OOJSAcquireContext(); jsval ecmPulsesRemaining = INT_TO_JSVAL(_blastsRemaining); jsval whomVal = OOJSValueFromNativeObject(context, ship); for (i = 0; i < count; i++) { ShipEntity *target = [targets objectAtIndex:i]; ShipScriptEvent(context, target, "shipHitByECM", ecmPulsesRemaining, whomVal); [target reactToAIMessage:@"ECM" context:nil]; [target noticeECM]; } OOJSRelinquishContext(context); } _nextBlast += ECM_PULSE_INTERVAL; } if (_blastsRemaining == 0 || !validShip) [UNIVERSE removeEntity:self]; } - (void) drawImmediate:(bool)immediate translucent:(bool)translucent { #if ECM_DEBUG_DRAW && OO_DEBUG OODebugDrawPoint(kZeroVector, [OOColor cyanColor]); #endif // Else do nothing, we're invisible! } - (BOOL) isECMBlast { return YES; } @end @implementation Entity (OOECMBlastEntity) - (BOOL) isECMBlast { return NO; } @end oolite-1.82/src/Core/Entities/OOEntityWithDrawable.h000066400000000000000000000024521256642440500223760ustar00rootroot00000000000000/* OOEntityWithDrawable.h Abstract intermediate class for entities which use an OODrawable to render. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "Entity.h" @class OODrawable; // Methods that must be supported by subentities, regardless of type. @protocol OOSubEntity - (void) rescaleBy:(GLfloat)factor; // Separate drawing path for subentities of ships. - (void) drawSubEntityImmediate:(bool)immediate translucent:(bool)translucent; @end @interface OOEntityWithDrawable: Entity { @private OODrawable *drawable; } - (OODrawable *)drawable; - (void)setDrawable:(OODrawable *)drawable; @end oolite-1.82/src/Core/Entities/OOEntityWithDrawable.m000066400000000000000000000062211256642440500224010ustar00rootroot00000000000000/* OOEntityWithDrawable.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOEntityWithDrawable.h" #import "OODrawable.h" #import "Universe.h" #import "ShipEntity.h" #import "OOVisualEffectEntity.h" @implementation OOEntityWithDrawable - (void)dealloc { [drawable release]; drawable = nil; [super dealloc]; } - (OODrawable *)drawable { return drawable; } - (void)setDrawable:(OODrawable *)inDrawable { if (inDrawable != drawable) { [drawable autorelease]; drawable = [inDrawable retain]; [drawable setBindingTarget:self]; collision_radius = [drawable collisionRadius]; no_draw_distance = [drawable maxDrawDistance]; boundingBox = [drawable boundingBox]; } } - (double)findCollisionRadius { return [drawable collisionRadius]; } - (void) drawImmediate:(bool)immediate translucent:(bool)translucent { if (no_draw_distance < cam_zero_distance) { // Don't draw. return; } if (no_draw_distance != INFINITY && ![self isImmuneToBreakPatternHide]) { // (always draw sky, always draw break patterns) if (![self isSubEntity]) { GLfloat clipradius = collision_radius; if ([self isShip]) { ShipEntity* shipself = (ShipEntity*)self; clipradius = [shipself frustumRadius]; } else if ([self isVisualEffect]) { OOVisualEffectEntity* veself = (OOVisualEffectEntity*)self; clipradius = [veself frustumRadius]; } // don't bother with frustum culling within/near collision radius, as // potential for problems with floating point inaccuracy causing // unwanted disappearance maybe fix // http://aegidian.org/bb/viewtopic.php?f=3&t=13619 - CIM if (cam_zero_distance > (clipradius+1000)*(clipradius+1000)) { if (![UNIVERSE viewFrustumIntersectsSphereAt:cameraRelativePosition withRadius:clipradius]) { return; } } } else // is subentity { // don't bother with frustum culling within 1km, as above - CIM if (cam_zero_distance > (collision_radius+1000)*(collision_radius+1000)) { // check correct sub-entity position if (![UNIVERSE viewFrustumIntersectsSphereAt:cameraRelativePosition withRadius:[self collisionRadius]]) { return; } } } } if ([UNIVERSE wireframeGraphics]) OOGLWireframeModeOn(); if (translucent) [drawable renderTranslucentParts]; else [drawable renderOpaqueParts]; if ([UNIVERSE wireframeGraphics]) OOGLWireframeModeOff(); } #ifndef NDEBUG - (NSSet *) allTextures { return [[self drawable] allTextures]; } #endif @end oolite-1.82/src/Core/Entities/OOExhaustPlumeEntity.h000066400000000000000000000033601256642440500224440ustar00rootroot00000000000000/* OOExhaustPlumeEntity.h Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "ShipEntity.h" #import "OOTexture.h" typedef struct { double timeframe; // universal time for this frame HPVector position; Quaternion orientation; Vector k; // direction vectors } Frame; enum { kExhaustFrameCount = 16 }; @interface OOExhaustPlumeEntity: Entity { @private Vector _exhaustScale; OOHPScalar _vertices[34 * 3]; GLfloat _glVertices[34 * 3]; GLfloat _exhaustBaseColors[34 * 4]; Frame _track[kExhaustFrameCount]; OOTimeAbsolute _trackTime; uint8_t _nextFrame; } + (id) exhaustForShip:(ShipEntity *)ship withDefinition:(NSArray *)definition andScale:(float)scale; - (id) initForShip:(ShipEntity *)ship withDefinition:(NSArray *)definition andScale:(float)scale; - (void) resetPlume; - (Vector) scale; - (void) setScale:(Vector)scale; - (OOTexture *) texture; + (void) setUpTexture; + (OOTexture *) plumeTexture; + (void) resetGraphicsState; @end @interface Entity (OOExhaustPlume) - (BOOL)isExhaust; @end oolite-1.82/src/Core/Entities/OOExhaustPlumeEntity.m000066400000000000000000000507061256642440500224570ustar00rootroot00000000000000/* OOExhaustPlumeEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOExhaustPlumeEntity.h" #import "OOCollectionExtractors.h" #import "ShipEntity.h" #import "Universe.h" #import "OOMacroOpenGL.h" #import "PlayerEntity.h" #import "MyOpenGLView.h" #import "OOTexture.h" #import "OOGraphicsResetManager.h" #define kOverallAlpha 1.0f #define kTimeStep 0.05f #define kFadeLevel1 0.4f #define kFadeLevel2 0.2f #define kFadeLevel3 0.02f #define kScaleLevel1 1.0f #define kScaleLevel2 0.8f #define kScaleLevel3 0.6f static OOTexture *sPlumeTexture = nil; @interface OOExhaustPlumeEntity (Private) - (void) saveToLastFrame; - (Frame) frameAtTime:(double) t_frame fromFrame:(Frame) frame_zero; // t_frame is relative to now ie. -0.5 = half a second ago. @end @implementation OOExhaustPlumeEntity + (id) exhaustForShip:(ShipEntity *)ship withDefinition:(NSArray *)definition andScale:(float)scale { return [[[self alloc] initForShip:ship withDefinition:definition andScale:(float)scale] autorelease]; } - (id) initForShip:(ShipEntity *)ship withDefinition:(NSArray *)definition andScale:(float)scaleFactor { if ([definition count] == 0) { [self release]; return nil; } if ((self = [super init])) { [self setOwner:ship]; HPVector pos = { [definition oo_floatAtIndex:0]*scaleFactor, [definition oo_floatAtIndex:1]*scaleFactor, [definition oo_floatAtIndex:2]*scaleFactor }; [self setPosition:pos]; // scale.z is special and *not* multiplied by scaleFactor Vector scale = { [definition oo_floatAtIndex:3]*scaleFactor, [definition oo_floatAtIndex:4]*scaleFactor, [definition oo_floatAtIndex:5] }; [self setScale:scale]; } return self; } - (Vector) scale { return _exhaustScale; } - (void) setScale:(Vector)scale { _exhaustScale = scale; if (scale.z < 0.5 || scale.z > 2.0) { _exhaustScale.z = 1.0; } } - (BOOL)isExhaust { return YES; } - (double)findCollisionRadius { return collision_radius; } - (void) update:(OOTimeDelta) delta_t { // Profiling: this function and subfunctions are expensive - CIM // don't draw if there's no ship, or if we're just jumping out of witchspace/docked at a station! ShipEntity *ship = [self owner]; // also don't draw if the ship isn't visible if (EXPECT_NOT(ship == nil || ![ship isVisible] || ([ship isPlayer] && [ship suppressFlightNotifications]))) return; OOTimeAbsolute now = [UNIVERSE getTime]; if ([UNIVERSE getTime] > _trackTime + kTimeStep) { [self saveToLastFrame]; _trackTime = now; } //GLfloat ex_emissive[4] = {0.7f, 0.9, 1.0f, 0.9f * kOverallAlpha}; // pale blue - old definition collision_radius = 0; GLfloat length; Vector vertex; GLfloat ex_emissive[4]; [[ship exhaustEmissiveColor] getRed:&ex_emissive[0] green:&ex_emissive[1] blue:&ex_emissive[2] alpha:&ex_emissive[3]]; const GLfloat s1[8] = { 0.0, M_SQRT1_2, 1.0, M_SQRT1_2, 0.0, -M_SQRT1_2, -1.0, -M_SQRT1_2}; const GLfloat c1[8] = { 1.0, M_SQRT1_2, 0.0, -M_SQRT1_2, -1.0, -M_SQRT1_2, 0.0, M_SQRT1_2}; Quaternion shipQrotation = [ship normalOrientation]; Frame zero = { .timeframe = [UNIVERSE getTime], .orientation = shipQrotation, .k = [ship forwardVector] }; int dam = [ship damage]; GLfloat speed = [ship speedFactor]; // don't draw if not moving. if (EXPECT_NOT(speed <= 0.001f)) return; GLfloat hyper_fade = 8.0f / (8.0f + speed * speed * speed); GLfloat flare_factor = fmaxf(speed,1.0) * ex_emissive[3] * hyper_fade; GLfloat red_factor = speed * ex_emissive[0] * (ranrot_rand() % 11) * 0.1; // random fluctuations GLfloat green_factor = speed * ex_emissive[1] * hyper_fade; if (speed > 1.0f) // afterburner! { red_factor = 1.5; } if ((int)(ranrot_rand() % 50) < dam - 50) // flicker the damaged engines red_factor = 0.0; if ((int)(ranrot_rand() % 40) < dam - 60) green_factor = 0.0; if ((int)(ranrot_rand() % 25) < dam - 75) flare_factor = 0.0; HPVector currentPos = ship->position; Vector vfwd = [ship forwardVector]; GLfloat spd = 0.5 * [ship flightSpeed]; vfwd = vector_multiply_scalar(vfwd, spd); Vector master_i = [ship rightVector]; Vector vi,vj,vk; vi = master_i; vj = [ship upVector]; vk = [ship forwardVector]; zero.position = make_HPvector(currentPos.x + vi.x * position.x + vj.x * position.y + vk.x * position.z, currentPos.y + vi.y * position.x + vj.y * position.y + vk.y * position.z, currentPos.z + vi.z * position.x + vj.z * position.y + vk.z * position.z); GLfloat speedScale = fminf(1.0,speed*5.0); GLfloat exhaust_factor = _exhaustScale.z; GLfloat i01 = -0.00 * hyper_fade; GLfloat i03 = -0.12 * exhaust_factor; GLfloat i06 = -0.25 * exhaust_factor; GLfloat i08 = -0.32 * exhaust_factor; GLfloat i10 = -0.40 * exhaust_factor; GLfloat q01 = i01/i10; // factor for trail GLfloat q03 = i03/i10; GLfloat q06 = i06/i10; GLfloat q08 = i08/i10; GLfloat r01 = 1.0 - q01; // factor for jet GLfloat r03 = 1.0 - q03; GLfloat r06 = 1.0 - q06; GLfloat r08 = 1.0 - q08; Frame f01 = [self frameAtTime: i01 fromFrame: zero]; Vector b01 = make_vector(r01 * i01 * vfwd.x, r01 * i01 * vfwd.y, r01 * i01 * vfwd.z); Frame f03 = [self frameAtTime: i03 fromFrame: zero]; Vector b03 = make_vector(r03 * i03 * vfwd.x, r03 * i03 * vfwd.y, r03 * i03 * vfwd.z); Frame f06 = [self frameAtTime: i06 fromFrame: zero]; Vector b06 = make_vector(r06 * i06 * vfwd.x, r06 * i06 * vfwd.y, r06 * i06 * vfwd.z); Frame f08 = [self frameAtTime: i08 fromFrame: zero]; Vector b08 = make_vector(r08 * i08 * vfwd.x, r08 * i08 * vfwd.y, r08 * i08 * vfwd.z); Frame f10 = [self frameAtTime: i10 fromFrame: zero]; int ci = 0; int iv = 0; int i; float r1; // f01.position = vector_subtract(zero.position, vk); // 1m out from zero f01.position = zero.position; ex_emissive[3] = flare_factor * kOverallAlpha; // fade alpha towards rear of exhaust ex_emissive[1] = green_factor; // diminish green part towards rear of exhaust ex_emissive[0] = red_factor; // diminish red part towards rear of exhaust vertex = vector_add(HPVectorToVector(f01.position), b01); collision_radius = magnitude(vector_subtract(vertex, HPVectorToVector(currentPos))); _vertices[iv++] = vertex.x;// + zero.k.x * flare_factor * 4.0; _vertices[iv++] = vertex.y;// + zero.k.y * flare_factor * 4.0; _vertices[iv++] = vertex.z;// + zero.k.z * flare_factor * 4.0; _exhaustBaseColors[ci++] = ex_emissive[0]; _exhaustBaseColors[ci++] = ex_emissive[1]; _exhaustBaseColors[ci++] = ex_emissive[2]; _exhaustBaseColors[ci++] = ex_emissive[3]; Vector k1 = f01.k; Vector j1 = cross_product(master_i, k1); Vector i1 = vector_multiply_scalar(cross_product(j1, k1), _exhaustScale.x * speedScale); j1 = vector_multiply_scalar(j1, _exhaustScale.y * speedScale); for (i = 0; i < 8; i++) { vertex = vector_add(HPVectorToVector(f01.position), vector_add(b01, vector_add(vector_multiply_scalar(i1,s1[i]), vector_multiply_scalar(j1,c1[i])))); length = magnitude(vector_subtract(vertex, HPVectorToVector(currentPos))); if (length > collision_radius) { collision_radius = length; } _vertices[iv++] = vertex.x; _vertices[iv++] = vertex.y; _vertices[iv++] = vertex.z; _exhaustBaseColors[ci++] = ex_emissive[0]; _exhaustBaseColors[ci++] = ex_emissive[1]; _exhaustBaseColors[ci++] = ex_emissive[2]; _exhaustBaseColors[ci++] = ex_emissive[3]; } ex_emissive[3] = kFadeLevel1 * flare_factor * kOverallAlpha; // fade alpha towards rear of exhaust ex_emissive[1] = kFadeLevel1 * green_factor; // diminish green part towards rear of exhaust ex_emissive[0] = kFadeLevel1 * red_factor; // diminish red part towards rear of exhaust k1 = f03.k; i1 = vector_multiply_scalar(cross_product(j1, k1), _exhaustScale.x * kScaleLevel1 * speedScale); j1 = vector_multiply_scalar(cross_product(master_i, k1), _exhaustScale.y * kScaleLevel1 * speedScale); for (i = 0; i < 8; i++) { r1 = randf(); vertex = vector_add(HPVectorToVector(f03.position), vector_add(b03, vector_add(vector_multiply_scalar(i1,s1[i]), vector_add(vector_multiply_scalar(j1,c1[i]), vector_multiply_scalar(k1,r1))))); length = magnitude(vector_subtract(vertex, HPVectorToVector(currentPos))); if (length > collision_radius) { collision_radius = length; } _vertices[iv++] = vertex.x; _vertices[iv++] = vertex.y; _vertices[iv++] = vertex.z; _exhaustBaseColors[ci++] = ex_emissive[0]; _exhaustBaseColors[ci++] = ex_emissive[1]; _exhaustBaseColors[ci++] = ex_emissive[2]; _exhaustBaseColors[ci++] = ex_emissive[3]; } ex_emissive[3] = kFadeLevel2 * flare_factor * kOverallAlpha; // fade alpha towards rear of exhaust ex_emissive[1] = kFadeLevel2 * green_factor; // diminish green part towards rear of exhaust ex_emissive[0] = kFadeLevel2 * red_factor; // diminish red part towards rear of exhaust k1 = f06.k; i1 = vector_multiply_scalar(cross_product(j1, k1), 0.8f * _exhaustScale.x * kScaleLevel2 * speedScale); j1 = vector_multiply_scalar(cross_product(master_i, k1), 0.8f * _exhaustScale.y * kScaleLevel2 * speedScale); for (i = 0; i < 8; i++) { r1 = randf(); vertex = vector_add(HPVectorToVector(f06.position), vector_add(b06, vector_add(vector_multiply_scalar(i1,s1[i]), vector_add(vector_multiply_scalar(j1,c1[i]), vector_multiply_scalar(k1,r1))))); length = magnitude(vector_subtract(vertex, HPVectorToVector(currentPos))); if (length > collision_radius) { collision_radius = length; } _vertices[iv++] = vertex.x; _vertices[iv++] = vertex.y; _vertices[iv++] = vertex.z; _exhaustBaseColors[ci++] = ex_emissive[0]; _exhaustBaseColors[ci++] = ex_emissive[1]; _exhaustBaseColors[ci++] = ex_emissive[2]; _exhaustBaseColors[ci++] = ex_emissive[3]; } ex_emissive[3] = kFadeLevel3 * flare_factor * kOverallAlpha; // fade alpha towards rear of exhaust ex_emissive[1] = kFadeLevel3 * green_factor; // diminish green part towards rear of exhaust ex_emissive[0] = kFadeLevel3 * red_factor; // diminish red part towards rear of exhaust k1 = f08.k; i1 = vector_multiply_scalar(cross_product(j1, k1), 0.5f * _exhaustScale.x * kScaleLevel3 * speedScale); j1 = vector_multiply_scalar(cross_product(master_i, k1), 0.5f * _exhaustScale.y * kScaleLevel3 * speedScale); for (i = 0; i < 8; i++) { r1 = randf(); vertex = vector_add(HPVectorToVector(f08.position), vector_add(b08, vector_add(vector_multiply_scalar(i1,s1[i]), vector_add(vector_multiply_scalar(j1,c1[i]), vector_multiply_scalar(k1,r1))))); length = magnitude(vector_subtract(vertex, HPVectorToVector(currentPos))); if (length > collision_radius) { collision_radius = length; } _vertices[iv++] = vertex.x; _vertices[iv++] = vertex.y; _vertices[iv++] = vertex.z; _exhaustBaseColors[ci++] = ex_emissive[0]; _exhaustBaseColors[ci++] = ex_emissive[1]; _exhaustBaseColors[ci++] = ex_emissive[2]; _exhaustBaseColors[ci++] = ex_emissive[3]; } ex_emissive[3] = 0.0; // fade alpha towards rear of exhaust ex_emissive[1] = 0.0; // diminish green part towards rear of exhaust ex_emissive[0] = 0.0; // diminish red part towards rear of exhaust length = magnitude(vector_subtract(vertex, HPVectorToVector(currentPos))); if (length > collision_radius) { collision_radius = length; } _vertices[iv++] = f10.position.x; _vertices[iv++] = f10.position.y; _vertices[iv++] = f10.position.z; _exhaustBaseColors[ci++] = ex_emissive[0]; _exhaustBaseColors[ci++] = ex_emissive[1]; _exhaustBaseColors[ci++] = ex_emissive[2]; _exhaustBaseColors[ci++] = ex_emissive[3]; (void)iv; (void)ci; // Suppress Clang static analyzer warnings. } static GLuint tfan1[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 1 }; // initial fan 0..9 // normal polys static GLuint tstr1[9] = { 1, 5, 9, 13, 17, 21, 25, 29, 33 }; static GLuint tstr2[9] = { 2, 6, 10, 14, 18, 22, 26, 30, 33 }; static GLuint tstr3[9] = { 3, 7, 11, 15, 19, 23, 27, 31, 33 }; static GLuint tstr4[9] = { 4, 8, 12, 16, 20, 24, 28, 32, 33 }; // aft-view special polys static GLuint afttstr1[4] = { 1, 5, 25, 29 }; static GLuint afttstr2[4] = { 2, 6, 26, 30 }; static GLuint afttstr3[4] = { 3, 7, 27, 31 }; static GLuint afttstr4[4] = { 4, 8, 28, 32 }; static GLfloat pA[6] = { 0.01, 0.0, 2.0, 4.0, 6.0, 10.0 }; // phase adjustments - (void) drawSubEntityImmediate:(bool)immediate translucent:(bool)translucent { if (!translucent) return; ShipEntity *ship = [self owner]; if ([ship speedFactor] <= 0.001f) return; // don't draw if not moving according to 'update' calculation OO_ENTER_OPENGL(); OOSetOpenGLState(OPENGL_STATE_ADDITIVE_BLENDING); OOGLPopModelView(); OOGLPushModelView(); // GLTranslateOOVector(vector_flip([self cameraRelativePosition])); HPVector cam = [PLAYER viewpointPosition]; for (unsigned n=0;n<34*3;n++) { switch (n%3) { case 0: // x coordinates _glVertices[n] = (GLfloat)(_vertices[n] - cam.x); break; case 1: // y coordinates _glVertices[n] = (GLfloat)(_vertices[n] - cam.y); break; case 2: // z coordinates _glVertices[n] = (GLfloat)(_vertices[n] - cam.z); break; } } OOGL(glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT)); OOGL(glDisable(GL_LIGHTING)); OOGL(glEnable(GL_BLEND)); OOGL(glDepthMask(GL_FALSE)); OOGL(glEnableClientState(GL_TEXTURE_COORD_ARRAY)); OOGL(glEnable(GL_TEXTURE_2D)); [[self texture] apply]; // OOGL(glDisable(GL_CULL_FACE)); // face culling OOGL(glShadeModel(GL_SMOOTH)); OOGL(glEnableClientState(GL_COLOR_ARRAY)); OOGL(glVertexPointer(3, GL_FLOAT, 0, _glVertices)); OOGL(glColorPointer(4, GL_FLOAT, 0, _exhaustBaseColors)); double intpart, dphase = 1.0-modf((double)[UNIVERSE getTime]*2.5,&intpart); GLfloat phase = (GLfloat)dphase; GLfloat texCoords[68] = { 0.5, phase+pA[0], 0.1, phase+pA[1], 0.1, phase+pA[1], 0.1, phase+pA[1], 0.1, phase+pA[1], 0.9, phase+pA[1], 0.9, phase+pA[1], 0.9, phase+pA[1], 0.9, phase+pA[1], 0.1, phase+pA[2], 0.1, phase+pA[2], 0.1, phase+pA[2], 0.1, phase+pA[2], 0.9, phase+pA[2], 0.9, phase+pA[2], 0.9, phase+pA[2], 0.9, phase+pA[2], 0.1, phase+pA[3], 0.1, phase+pA[3], 0.1, phase+pA[3], 0.1, phase+pA[3], 0.9, phase+pA[3], 0.9, phase+pA[3], 0.9, phase+pA[3], 0.9, phase+pA[3], 0.1, phase+pA[4], 0.1, phase+pA[4], 0.1, phase+pA[4], 0.1, phase+pA[4], 0.9, phase+pA[4], 0.9, phase+pA[4], 0.9, phase+pA[4], 0.9, phase+pA[4], 0.5, phase+pA[5], }; OOGL(glTexCoordPointer(2, GL_FLOAT, 0, texCoords)); // reduced detail for internal view to avoid rendering artefacts if ([[self owner] isPlayer] && [UNIVERSE viewDirection] != VIEW_CUSTOM) { OOGL(glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_INT, afttstr1)); OOGL(glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_INT, afttstr2)); OOGL(glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_INT, afttstr3)); OOGL(glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_INT, afttstr4)); } else { OOGL(glDrawElements(GL_TRIANGLE_STRIP, 9, GL_UNSIGNED_INT, tstr1)); OOGL(glDrawElements(GL_TRIANGLE_STRIP, 9, GL_UNSIGNED_INT, tstr2)); OOGL(glDrawElements(GL_TRIANGLE_STRIP, 9, GL_UNSIGNED_INT, tstr3)); OOGL(glDrawElements(GL_TRIANGLE_STRIP, 9, GL_UNSIGNED_INT, tstr4)); } /* Need a different texture and color array for this segment */ GLfloat fanTextures[18] = { 0.5, 0.0+phase, 0.2, 0.0+phase, 0.2, 0.1+phase, 0.2, 0.2+phase, 0.2, 0.3+phase, 0.2, 0.4+phase, 0.2, 0.3+phase, 0.2, 0.2+phase, 0.2, 0.1+phase }; OOGL(glTexCoordPointer(2, GL_FLOAT, 0, fanTextures)); GLfloat fanColors[36]; GLfloat fr = _exhaustBaseColors[0], fg = _exhaustBaseColors[1], fb = _exhaustBaseColors[2]; unsigned i = 0; fanColors[i++] = fr; fanColors[i++] = fg; fanColors[i++] = fb; fanColors[i++] = 1.0; for (;i<36;) { fanColors[i++] = fr; fanColors[i++] = fg; fanColors[i++] = fb; fanColors[i++] = 0.5; } OOGL(glColorPointer(4, GL_FLOAT, 0, fanColors)); OOGL(glDrawElements(GL_TRIANGLE_FAN, 10, GL_UNSIGNED_INT, tfan1)); OOGL(glDisableClientState(GL_TEXTURE_COORD_ARRAY)); OOGL(glDisable(GL_TEXTURE_2D)); OOGL(glDisableClientState(GL_COLOR_ARRAY)); OOGL(glPopAttrib()); OOVerifyOpenGLState(); } #define PREV(n) ((n + kExhaustFrameCount - 1) % kExhaustFrameCount) #define NEXT(n) ((n + 1) % kExhaustFrameCount) - (void) saveToLastFrame { ShipEntity *ship = [self owner]; // Absolute position of self // normally this would use the transformation matrix, but that // introduces inaccuracies // so just use the rotation matrix, then translate using HPVectors HPVector framePos = OOHPVectorMultiplyMatrix([self position], [ship drawRotationMatrix]); framePos = HPvector_add(framePos,[ship position]); Frame frame = { [UNIVERSE getTime], framePos, [ship normalOrientation], [ship upVector] }; _track[_nextFrame] = frame; _nextFrame = (_nextFrame + 1) % kExhaustFrameCount; } - (Frame) frameAtTime:(double)t_frame fromFrame:(Frame) frame_zero // t_frame is relative to now ie. -0.5 = half a second ago. { if (t_frame >= 0.0) return frame_zero; Frame frame_one; int t1 = PREV(_nextFrame); double moment_in_time = frame_zero.timeframe + t_frame; double period, f0; if (moment_in_time > _trackTime) // between the last saved frame and now { frame_one = _track[t1]; // last saved moment period = (moment_in_time - t_frame) - _trackTime; f0 = 1.0 + t_frame/period; } else if (moment_in_time < _track[_nextFrame].timeframe) // more than kExhaustFrameCount frames back { return _track[_nextFrame]; } else { while (moment_in_time < _track[t1].timeframe) { t1 = PREV(t1); } int t0 = NEXT(t1); frame_zero = _track[t0]; frame_one = _track[t1]; period = frame_zero.timeframe - frame_one.timeframe; f0 = (moment_in_time - _track[t1].timeframe)/period; } // interpolate double f1 = 1.0 - f0; HPVector posn; posn.x = f0 * frame_zero.position.x + f1 * frame_one.position.x; posn.y = f0 * frame_zero.position.y + f1 * frame_one.position.y; posn.z = f0 * frame_zero.position.z + f1 * frame_one.position.z; Quaternion qrot; qrot.w = f0 * frame_zero.orientation.w + f1 * frame_one.orientation.w; qrot.x = f0 * frame_zero.orientation.x + f1 * frame_one.orientation.x; qrot.y = f0 * frame_zero.orientation.y + f1 * frame_one.orientation.y; qrot.z = f0 * frame_zero.orientation.z + f1 * frame_one.orientation.z; Frame result; result.position = posn; result.orientation = qrot; result.timeframe = moment_in_time; result.k = vector_forward_from_quaternion(qrot); return result; } - (void) resetPlume { /*ShipEntity *ship = [self owner]; // Absolute position of self Vector framePos = OOVectorMultiplyMatrix([self position], [ship drawTransformationMatrix]); Frame frame = { [UNIVERSE getTime], framePos, [ship normalOrientation], [ship upVector] }; _track[_nextFrame] = frame; _nextFrame = (_nextFrame + 1) % kExhaustFrameCount;*/ _nextFrame = 0; HPVector framePos = OOHPVectorMultiplyMatrix([self position], [[self owner] drawTransformationMatrix]); uint8_t i; for (i = 0; i < kExhaustFrameCount; i++) { _track[i].timeframe = 0.0; _track[i].position = framePos; _track[i].orientation = kIdentityQuaternion; _track[i].k = kZeroVector; } } - (void) rescaleBy:(GLfloat)factor { _exhaustScale = vector_multiply_scalar(_exhaustScale, factor); } - (OOTexture *) texture { return [OOExhaustPlumeEntity plumeTexture]; } + (void) setUpTexture { if (sPlumeTexture == nil) { sPlumeTexture = [[OOTexture textureWithName:@"oolite-exhaust-blur.png" inFolder:@"Textures" options:kOOTextureMinFilterMipMap | kOOTextureMagFilterLinear | kOOTextureAlphaMask | kOOTextureRepeatT | kOOTextureRepeatS anisotropy:kOOTextureDefaultAnisotropy / 2.0 lodBias:0.0] retain]; [[OOGraphicsResetManager sharedManager] registerClient:(id)[OOExhaustPlumeEntity class]]; } } + (OOTexture *) plumeTexture { if (sPlumeTexture == nil) [self setUpTexture]; return sPlumeTexture; } + (void) resetGraphicsState { [sPlumeTexture release]; sPlumeTexture = nil; } @end @implementation Entity (OOExhaustPlume) - (BOOL)isExhaust { return NO; } @end oolite-1.82/src/Core/Entities/OOExplosionCloudEntity.h000066400000000000000000000023441256642440500227700ustar00rootroot00000000000000/* OOExplosionCloudEntity.h Cloud effect during explosions Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOParticleSystem.h" @interface OOExplosionCloudEntity: OOParticleSystem { @private float _growthRate; float _cloudDuration; float _alpha; OOTexture *_texture; NSDictionary *_settings; } + (instancetype) explosionCloudFromEntity:(Entity *)entity withSettings:(NSDictionary *)settings; + (instancetype) explosionCloudFromEntity:(Entity *)entity withSize:(float) size andSettings:(NSDictionary *)settings; @end oolite-1.82/src/Core/Entities/OOExplosionCloudEntity.m000066400000000000000000000165551256642440500230060ustar00rootroot00000000000000/* OOExplosionCloudEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOExplosionCloudEntity.h" #import "Universe.h" #import "PlayerEntity.h" #import "OOColor.h" #import "OOTexture.h" #import "OOGraphicsResetManager.h" #import "OOCollectionExtractors.h" #define kExplosionCloudDuration 0.9f #define kGrowthRateFactor 1.5f #define kExplosionCloudAlpha 0.85f #define kExplosionDefaultSize 2.5f // keys for plist file static NSString * const kExplosionAlpha = @"alpha"; static NSString * const kExplosionColors = @"color_order"; static NSString * const kExplosionCount = @"count"; static NSString * const kExplosionDuration = @"duration"; static NSString * const kExplosionGrowth = @"growth_rate"; static NSString * const kExplosionSize = @"size"; static NSString * const kExplosionSpread = @"spread"; static NSString * const kExplosionTexture = @"texture"; @interface OOExplosionCloudEntity (OOPrivate) - (id) initExplosionCloudWithEntity:(Entity *)entity size:(float)size andSettings:(NSDictionary *)settings; @end @implementation OOExplosionCloudEntity - (id) initExplosionCloudWithEntity:(Entity *)entity size:(float)size andSettings:(NSDictionary *)settings { unsigned i; unsigned maxCount = [UNIVERSE detailLevel] <= DETAIL_LEVEL_SHADERS ? 10 : 25; HPVector pos = [entity position]; Vector vel = [entity velocity]; if (settings == nil) { _settings = [[NSDictionary dictionary] retain]; } else { _settings = [settings retain]; } unsigned count = [_settings oo_unsignedIntegerForKey:kExplosionCount defaultValue:25]; if (count > maxCount) { count = maxCount; } if (size == 0.0) { size = [entity collisionRadius]*[_settings oo_floatForKey:kExplosionSize defaultValue:kExplosionDefaultSize]; } _growthRate = [_settings oo_floatForKey:kExplosionGrowth defaultValue:kGrowthRateFactor] * size; _alpha = [_settings oo_floatForKey:kExplosionAlpha defaultValue:kExplosionCloudAlpha]; _cloudDuration = [_settings oo_floatForKey:kExplosionDuration defaultValue:kExplosionCloudDuration]; GLfloat spread = [_settings oo_floatForKey:kExplosionSpread defaultValue:1.0]; NSString *textureFile = [_settings oo_stringForKey:kExplosionTexture defaultValue:@"oolite-particle-cloud2.png"]; _texture = [[OOTexture textureWithName:textureFile inFolder:@"Textures" options:kOOTextureMinFilterMipMap | kOOTextureMagFilterLinear | kOOTextureAlphaMask anisotropy:kOOTextureDefaultAnisotropy lodBias:0.0] retain]; if (_texture == nil) { [self release]; return nil; } GLfloat baseColor[4] = {1.0,1.0,1.0,1.0}; if (magnitude2(vel) > 1000000) { // slow down rapidly translating explosions vel = vector_multiply_scalar(vector_normal(vel),1000); } if ((self = [super initWithPosition:pos velocity:vel count:count minSpeed:size*0.8*spread maxSpeed:size*1.2*spread duration:_cloudDuration baseColor:baseColor])) { NSString *color_order = [_settings oo_stringForKey:kExplosionColors defaultValue:@"rgb"]; for (i=0;i c1) { c2 = c1; } if (c3 > c2) { c3 = c2; } if ([color_order isEqualToString:@"rgb"]) { _particleColor[i][0] = c1; _particleColor[i][1] = c2; _particleColor[i][2] = c3; } else if ([color_order isEqualToString:@"rbg"]) { _particleColor[i][0] = c1; _particleColor[i][1] = c3; _particleColor[i][2] = c2; } else if ([color_order isEqualToString:@"grb"]) { _particleColor[i][0] = c2; _particleColor[i][1] = c1; _particleColor[i][2] = c3; } else if ([color_order isEqualToString:@"gbr"]) { _particleColor[i][0] = c3; _particleColor[i][1] = c1; _particleColor[i][2] = c2; } else if ([color_order isEqualToString:@"brg"]) { _particleColor[i][0] = c2; _particleColor[i][1] = c3; _particleColor[i][2] = c1; } else if ([color_order isEqualToString:@"bgr"]) { _particleColor[i][0] = c3; _particleColor[i][1] = c2; _particleColor[i][2] = c1; } } _particleColor[i][3] = _alpha; } } return self; } - (void) dealloc { DESTROY(_texture); DESTROY(_settings); [super dealloc]; } + (instancetype) explosionCloudFromEntity:(Entity *)entity withSettings:(NSDictionary *)settings { return [[[self alloc] initExplosionCloudWithEntity:entity size:0 andSettings:settings] autorelease]; } + (instancetype) explosionCloudFromEntity:(Entity *)entity withSize:(float)size andSettings:(NSDictionary *)settings { return [[[self alloc] initExplosionCloudWithEntity:entity size:size andSettings:settings] autorelease]; } - (void) update:(OOTimeDelta)delta_t { [super update:delta_t]; // Fade out. GLfloat fadeRate = _count / 25.0; unsigned i, count = _count; GLfloat (*particleColor)[4] = _particleColor; float newAlpha = _alpha * (1-(_timePassed / _cloudDuration)); NSString *color_order = [_settings oo_stringForKey:kExplosionColors defaultValue:@"rgb"]; NSUInteger primary = 0, secondary = 1, tertiary = 2; if ([color_order isEqualToString:@"rgb"]) { primary = 0; secondary = 1; tertiary = 2; } else if ([color_order isEqualToString:@"rbg"]) { primary = 0; secondary = 2; tertiary = 1; } else if ([color_order isEqualToString:@"grb"]) { primary = 1; secondary = 0; tertiary = 2; } else if ([color_order isEqualToString:@"gbr"]) { primary = 1; secondary = 2; tertiary = 0; } else if ([color_order isEqualToString:@"brg"]) { primary = 2; secondary = 0; tertiary = 1; } else if ([color_order isEqualToString:@"bgr"]) { primary = 2; secondary = 1; tertiary = 0; } for (i=0;i 0.0) // fade blue (white to yellow) { particleColor[i][tertiary] -= delta_t*0.5*fadeRate; if (particleColor[i][tertiary] < 0.0) { particleColor[i][tertiary] = 0.0f; } } else if (particleColor[i][secondary] > 0.0) // fade green (yellow to red) { particleColor[i][secondary] -= delta_t*fadeRate; if (particleColor[i][secondary] < 0.0) { particleColor[i][secondary] = 0.0f; } } else if (particleColor[i][primary] > 0.0) // fade red (red to black) { particleColor[i][primary] -= delta_t*2.0*fadeRate; if (particleColor[i][primary] < 0.0) { particleColor[i][primary] = 0.0f; } } } } } - (OOTexture *) texture { return _texture; } @end oolite-1.82/src/Core/Entities/OOFlashEffectEntity.h000066400000000000000000000023021256642440500221650ustar00rootroot00000000000000/* OOFlashEffectEntity.h Flashes during explosions and laser hits - not to be confused with flashers. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOLightParticleEntity.h" @interface OOFlashEffectEntity: OOLightParticleEntity { @private float _duration; float _growthRate; float _alpha; } + (instancetype) explosionFlashFromEntity:(Entity *)entity; + (instancetype) laserFlashWithPosition:(HPVector)position velocity:(Vector)vel color:(OOColor *)color; + (void) setUpTexture; @end oolite-1.82/src/Core/Entities/OOFlashEffectEntity.m000066400000000000000000000077521256642440500222100ustar00rootroot00000000000000/* OOFlashEffectEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOFlashEffectEntity.h" #import "Universe.h" #import "PlayerEntity.h" #import "OOColor.h" #import "OOTexture.h" #import "OOGraphicsResetManager.h" #define kLaserFlashDuration 0.3f #define kExplosionFlashDuration 0.4f #define kGrowthRateFactor 150.0f // if average flashSize is 80 then this is 12000 #define kMinExplosionGrowth 600.0f #define kLaserFlashInitialSize 1.0f #define kExplosionFlashAlpha 0.5f static OOTexture *sFlashTexture = nil; @interface OOFlashEffectEntity (Private) // Designated initializer. - (id) initWithPosition:(HPVector)pos size:(float)size color:(OOColor *)color duration:(float)duration; + (void) resetGraphicsState; - (void) performUpdate:(OOTimeDelta)delta_t; @end @implementation OOFlashEffectEntity - (id) initExplosionFlashWithPosition:(HPVector)pos velocity:(Vector)vel size:(float)size { if ((self = [self initWithPosition:pos size:size color:[OOColor whiteColor] duration:kExplosionFlashDuration])) { _growthRate = fmax(_growthRate, kMinExplosionGrowth); _alpha = kExplosionFlashAlpha; [self setVelocity:vel]; } return self; } - (id) initLaserFlashWithPosition:(HPVector)pos velocity:(Vector)vel color:(OOColor *)color { if ((self = [self initWithPosition:pos size:kLaserFlashInitialSize color:color duration:kLaserFlashDuration])) { [self setVelocity:vel]; _alpha = 1.0f; } return self; } + (instancetype) explosionFlashFromEntity:(Entity *)entity { return [[[self alloc] initExplosionFlashWithPosition:[entity position] velocity:[entity velocity] size:[entity collisionRadius]] autorelease]; } + (instancetype) laserFlashWithPosition:(HPVector)pos velocity:(Vector)vel color:(OOColor *)color { return [[[self alloc] initLaserFlashWithPosition:pos velocity:vel color:color] autorelease]; } - (id) initWithPosition:(HPVector)pos size:(float)size color:(OOColor *)color duration:(float)duration { if ((self = [super initWithDiameter:size])) { [self setPosition:pos]; _duration = duration; _growthRate = kGrowthRateFactor * size; [self setColor:color alpha:1.0f]; assert([self collisionRadius] == 0 && [self energy] == 0 && magnitude([self velocity]) == 0); } return self; } - (void) update:(OOTimeDelta)delta_t { [super update:delta_t]; float tf = _duration * 0.667; float tf1 = _duration - tf; // Scale up. _diameter += delta_t * _growthRate; // Fade in and out. OOTimeDelta lifeTime = [self timeElapsedSinceSpawn]; _colorComponents[3] = _alpha * ((lifeTime < tf) ? (lifeTime / tf) : (_duration - lifeTime) / tf1); // Disappear as necessary. if (lifeTime > _duration) [UNIVERSE removeEntity:self]; } - (OOTexture *) texture { if (sFlashTexture == nil) [OOFlashEffectEntity setUpTexture]; return sFlashTexture; } + (void) setUpTexture { if (sFlashTexture == nil) { sFlashTexture = [[OOTexture textureWithName:@"oolite-particle-flash.png" inFolder:@"Textures" options:kOOTextureMinFilterMipMap | kOOTextureMagFilterLinear | kOOTextureAlphaMask anisotropy:kOOTextureDefaultAnisotropy lodBias:0.0] retain]; [[OOGraphicsResetManager sharedManager] registerClient:(id)[OOFlashEffectEntity class]]; } } + (void) resetGraphicsState { [sFlashTexture release]; sFlashTexture = nil; } @end oolite-1.82/src/Core/Entities/OOFlasherEntity.h000066400000000000000000000031651256642440500214070ustar00rootroot00000000000000/* OOFlasherEntity.h Flashing light attached to ships. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOLightParticleEntity.h" #import "ShipEntity.h" @interface OOFlasherEntity: OOLightParticleEntity { @private float _frequency; float _phase; float _wave; float _brightfraction; NSArray *_colors; NSUInteger _activeColor; OOTimeDelta _time; BOOL _active; BOOL _justSwitched; } + (instancetype) flasherWithDictionary:(NSDictionary *)dictionary; - (id) initWithDictionary:(NSDictionary *)dictionary; - (BOOL) isActive; - (void) setActive:(BOOL)active; - (OOColor *) color; // setColor is defined by superclass - (float) frequency; - (void) setFrequency:(float)frequency; - (float) phase; - (void) setPhase:(float)phase; - (float) fraction; - (void) setFraction:(float)fraction; @end @interface Entity (OOFlasherEntityExtensions) - (BOOL) isFlasher; @end oolite-1.82/src/Core/Entities/OOFlasherEntity.m000066400000000000000000000112211256642440500214040ustar00rootroot00000000000000/* OOFlasherEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOFlasherEntity.h" #import "Universe.h" #import "PlayerEntity.h" #import "OOColor.h" #import "OOCollectionExtractors.h" #import "NSDictionaryOOExtensions.h" @interface OOFlasherEntity (Internal) - (void) setUpColors:(NSArray *)colorSpecifiers; - (void) getCurrentColorComponents; @end @implementation OOFlasherEntity + (instancetype) flasherWithDictionary:(NSDictionary *)dictionary { return [[[OOFlasherEntity alloc] initWithDictionary:dictionary] autorelease]; } - (id) initWithDictionary:(NSDictionary *)dictionary { float size = [dictionary oo_floatForKey:@"size" defaultValue:1.0f]; if ((self = [super initWithDiameter:size])) { _frequency = [dictionary oo_floatForKey:@"frequency" defaultValue:1.0f] * 2.0f; _phase = [dictionary oo_floatForKey:@"phase" defaultValue:0.0f]; _brightfraction = [dictionary oo_floatForKey:@"bright_fraction" defaultValue:0.5f]; [self setUpColors:[dictionary oo_arrayForKey:@"colors"]]; [self getCurrentColorComponents]; [self setActive:[dictionary oo_boolForKey:@"initially_on" defaultValue:YES]]; } return self; } - (void) dealloc { [_colors release]; [super dealloc]; } - (void) setUpColors:(NSArray *)colorSpecifiers { NSMutableArray *colors = [NSMutableArray arrayWithCapacity:[colorSpecifiers count]]; id specifier = nil; NSEnumerator *specEnum = [colorSpecifiers objectEnumerator]; while ((specifier = [specEnum nextObject])) { [colors addObject:[OOColor colorWithDescription:specifier saturationFactor:0.75f]]; } _colors = [colors copy]; } - (void) getCurrentColorComponents { [self setColor:[_colors objectAtIndex:_activeColor] alpha:_colorComponents[3]]; } - (BOOL) isActive { return _active; } - (void) setActive:(BOOL)active { _active = !!active; } - (OOColor *) color { return [OOColor colorWithRed:_colorComponents[0] green:_colorComponents[1] blue:_colorComponents[2] alpha:_colorComponents[3]]; } - (float) frequency { return _frequency; } - (void) setFrequency:(float)frequency { _frequency = frequency; } - (float) phase { return _phase; } - (void) setPhase:(float)phase { _phase = phase; } - (float) fraction { return _brightfraction; } - (void) setFraction:(float)fraction { _brightfraction = fraction; } - (void) update:(OOTimeDelta) delta_t { [super update:delta_t]; _time += delta_t; if (_frequency != 0) { float wave = sin(_frequency * M_PI * (_time + _phase)); NSUInteger count = [_colors count]; if (count > 1 && wave < 0) { if (!_justSwitched && wave > _wave) // don't test for wave >= _wave - could give wrong results with very low frequencies { _justSwitched = YES; ++_activeColor; _activeColor %= count; //_activeColor = ++_activeColor % count; is potentially undefined operation [self setColor:[_colors objectAtIndex:_activeColor]]; } } else if (_justSwitched) { _justSwitched = NO; } float threshold = cos(_brightfraction * M_PI); float brightness = _brightfraction; if (wave > threshold) { brightness = _brightfraction + (((1-_brightfraction)/(1-threshold))*(wave-threshold)); } else if (wave < threshold) { brightness = _brightfraction + ((_brightfraction/(threshold+1))*(wave-threshold)); } _colorComponents[3] = brightness; _wave = wave; } else { _colorComponents[3] = 1.0; } } - (void) drawImmediate:(bool)immediate translucent:(bool)translucent { if (_active) { [super drawImmediate:immediate translucent:translucent]; } } - (void) drawSubEntityImmediate:(bool)immediate translucent:(bool)translucent { if (_active) { [super drawSubEntityImmediate:immediate translucent:translucent]; } } - (BOOL) isFlasher { return YES; } - (double)findCollisionRadius { return [self diameter] / 2.0; } - (void) rescaleBy:(GLfloat)factor { [self setDiameter:[self diameter] * factor]; } @end @implementation Entity (OOFlasherEntityExtensions) - (BOOL) isFlasher { return NO; } @end oolite-1.82/src/Core/Entities/OOLaserShotEntity.h000066400000000000000000000026301256642440500217230ustar00rootroot00000000000000/* OOLaserShotEntity.h Entity subclass implementing GIANT SPACE LAZORS. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "ShipEntity.h" #import "OOTypes.h" #import "OOTexture.h" #import "OOMaths.h" @class OOColor; @interface OOLaserShotEntity: Entity { @private GLfloat _color[4]; OOTimeDelta _lifetime; GLfloat _range; Vector _offset; Quaternion _relOrientation; } + (instancetype) laserFromShip:(ShipEntity *)ship direction:(OOWeaponFacing)direction offset:(Vector)offset; - (void) setColor:(OOColor *)color; - (void) setRange:(GLfloat)range; - (OOTexture *) texture1; - (OOTexture *) texture2; + (void) setUpTexture; + (OOTexture *) innerTexture; + (OOTexture *) outerTexture; @end oolite-1.82/src/Core/Entities/OOLaserShotEntity.m000066400000000000000000000172111256642440500217310ustar00rootroot00000000000000/* OOLaserShotEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOLaserShotEntity.h" #import "Universe.h" #import "ShipEntity.h" #import "OOMacroOpenGL.h" #import "OOTexture.h" #import "OOGraphicsResetManager.h" #import "MyOpenGLView.h" #define kLaserDuration (0.09) // seconds // Default colour #define kLaserRed (1.0f) #define kLaserGreen (0.0f) #define kLaserBlue (0.0f) // Constant alpha #define kLaserAlpha (0.45f) #define kLaserCoreWidth (0.4f) #define kLaserFlareWidth (1.8f) #define kLaserHalfWidth (3.6f) static OOTexture *sShotTexture = nil; static OOTexture *sShotTexture2 = nil; @implementation OOLaserShotEntity - (instancetype) initLaserFromShip:(ShipEntity *)srcEntity direction:(OOWeaponFacing)direction offset:(Vector)offset { if (!(self = [super init])) return nil; ShipEntity *ship = [srcEntity rootShipEntity]; Vector middle = OOBoundingBoxCenter([srcEntity boundingBox]); NSCParameterAssert([srcEntity isShip] && [ship isShip]); [self setStatus:STATUS_EFFECT]; if (ship == srcEntity) { // main laser offset [self setPosition:HPvector_add([ship position], vectorToHPVector(OOVectorMultiplyMatrix(offset, [ship drawRotationMatrix])))]; } else { // subentity laser [self setPosition:[srcEntity absolutePositionForSubentityOffset:vectorToHPVector(middle)]]; } Quaternion q = kIdentityQuaternion; Vector q_up = vector_up_from_quaternion(q); Quaternion q0 = [ship normalOrientation]; velocity = vector_multiply_scalar(vector_forward_from_quaternion(q0), [ship flightSpeed]); switch (direction) { case WEAPON_FACING_NONE: case WEAPON_FACING_FORWARD: break; case WEAPON_FACING_AFT: quaternion_rotate_about_axis(&q, q_up, M_PI); break; case WEAPON_FACING_PORT: quaternion_rotate_about_axis(&q, q_up, M_PI/2.0); break; case WEAPON_FACING_STARBOARD: quaternion_rotate_about_axis(&q, q_up, -M_PI/2.0); break; } [self setOrientation:quaternion_multiply(q,q0)]; [self setOwner:ship]; [self setRange:[srcEntity weaponRange]]; _lifetime = kLaserDuration; _color[0] = kLaserRed/3.0; _color[1] = kLaserGreen/3.0; _color[2] = kLaserBlue/3.0; _color[3] = kLaserAlpha; _offset = (ship == srcEntity) ? offset : middle; _relOrientation = q; return self; } + (instancetype) laserFromShip:(ShipEntity *)ship direction:(OOWeaponFacing)direction offset:(Vector)offset { return [[[self alloc] initLaserFromShip:ship direction:direction offset:offset] autorelease]; } - (void) dealloc { [self setColor:nil]; [super dealloc]; } - (NSString *) descriptionComponents { return [NSString stringWithFormat:@"ttl: %.3fs - %@ orientation %@", _lifetime, [super descriptionComponents], QuaternionDescription([self orientation])]; } - (void) setColor:(OOColor *)color { _color[0] = [color redComponent]/3.0; _color[1] = [color greenComponent]/3.0; _color[2] = [color blueComponent]/3.0; // Ignore alpha; _color[3] is constant. } - (void) setRange:(GLfloat)range { _range = range; [self setCollisionRadius:range]; } - (void) update:(OOTimeDelta)delta_t { [super update:delta_t]; _lifetime -= delta_t; ShipEntity *ship = [self owner]; if ([ship isPlayer]) { /* Reposition this shot accurately. This overrides integration over velocity in -[Entity update:], which is considered sufficient for NPC ships. */ [self setPosition:HPvector_add([ship position], vectorToHPVector(OOVectorMultiplyMatrix(_offset, [ship drawRotationMatrix])))]; [self setOrientation:quaternion_multiply(_relOrientation, [ship normalOrientation])]; } if (_lifetime < 0) { [UNIVERSE removeEntity:self]; } } static const GLfloat kLaserVertices[] = { 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, }; - (void) drawImmediate:(bool)immediate translucent:(bool)translucent { if (!translucent || [UNIVERSE breakPatternHide]) return; OO_ENTER_OPENGL(); OOSetOpenGLState(OPENGL_STATE_ADDITIVE_BLENDING); /* FIXME: spread damage across the lifetime of the shot, hurting whatever is hit in a given frame. -- Ahruman 2011-01-31 */ OOGL(glEnableClientState(GL_TEXTURE_COORD_ARRAY)); OOGL(glEnable(GL_TEXTURE_2D)); OOGLPushModelView(); OOGLScaleModelView(make_vector(kLaserHalfWidth, kLaserHalfWidth, _range)); [[self texture1] apply]; GLfloat s = sin([UNIVERSE getTime]); GLfloat phase = s*(_range/200.0); GLfloat phase2 = (1.0+s)*(_range/200.0); GLfloat phase3 = -s*(_range/500.0); GLfloat phase4 = -(1.0+s)*(_range/500.0); GLfloat laserTexCoords[] = { 0.0f, phase, 0.0f, phase2, 1.0f, phase2, 1.0f, phase, 0.0f, phase, 0.0f, phase2, 1.0f, phase2, 1.0f, phase }; GLfloat laserTexCoords2[] = { 0.0f, phase3, 0.0f, phase4, 1.0f, phase4, 1.0f, phase3, 0.0f, phase3, 0.0f, phase4, 1.0f, phase4, 1.0f, phase3 }; OOGL(glColor4fv(_color)); glVertexPointer(3, GL_FLOAT, 0, kLaserVertices); glTexCoordPointer(2, GL_FLOAT, 0, laserTexCoords2); glDrawArrays(GL_QUADS, 0, 8); OOGLScaleModelView(make_vector(kLaserCoreWidth / kLaserHalfWidth, kLaserCoreWidth / kLaserHalfWidth, 1.0)); OOGL(glColor4f(1.0,1.0,1.0,0.9)); glDrawArrays(GL_QUADS, 0, 8); [[self texture2] apply]; OOGLScaleModelView(make_vector(kLaserFlareWidth / kLaserCoreWidth, kLaserFlareWidth / kLaserCoreWidth, 1.0)); OOGL(glColor4f(_color[0],_color[1],_color[2],0.9)); glTexCoordPointer(2, GL_FLOAT, 0, laserTexCoords); glDrawArrays(GL_QUADS, 0, 8); OOGLPopModelView(); OOGL(glDisableClientState(GL_TEXTURE_COORD_ARRAY)); OOGL(glDisable(GL_TEXTURE_2D)); OOVerifyOpenGLState(); OOCheckOpenGLErrors(@"OOLaserShotEntity after drawing %@", self); } - (BOOL) isEffect { return YES; } - (BOOL) canCollide { return NO; } - (OOTexture *) texture1 { return [OOLaserShotEntity outerTexture]; } - (OOTexture *) texture2 { return [OOLaserShotEntity innerTexture]; } + (void) setUpTexture { if (sShotTexture == nil) { sShotTexture = [[OOTexture textureWithName:@"oolite-laser-blur.png" inFolder:@"Textures" options:kOOTextureMinFilterMipMap | kOOTextureMagFilterLinear | kOOTextureAlphaMask | kOOTextureRepeatT anisotropy:kOOTextureDefaultAnisotropy / 2.0 lodBias:0.0] retain]; [[OOGraphicsResetManager sharedManager] registerClient:(id)[OOLaserShotEntity class]]; sShotTexture2 = [[OOTexture textureWithName:@"oolite-laser-blur2.png" inFolder:@"Textures" options:kOOTextureMinFilterMipMap | kOOTextureMagFilterLinear | kOOTextureAlphaMask | kOOTextureRepeatT anisotropy:kOOTextureDefaultAnisotropy / 2.0 lodBias:0.0] retain]; } } + (OOTexture *) innerTexture { if (sShotTexture2 == nil) [self setUpTexture]; return sShotTexture2; } + (OOTexture *) outerTexture { if (sShotTexture == nil) [self setUpTexture]; return sShotTexture; } + (void) resetGraphicsState { [sShotTexture release]; sShotTexture = nil; [sShotTexture2 release]; sShotTexture2 = nil; } @end oolite-1.82/src/Core/Entities/OOLightParticleEntity.h000066400000000000000000000032131256642440500225500ustar00rootroot00000000000000/* OOLightParticleEntity.h Simple particle-type effect entity. Draws a billboard with additive blending. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "Entity.h" @class OOTexture, OOColor; @interface OOLightParticleEntity: Entity { @protected GLfloat _colorComponents[4]; float _diameter; } - (id) initWithDiameter:(float)diameter; - (float) diameter; - (void) setDiameter:(float)diameter; - (void) setColor:(OOColor *)color; - (void) setColor:(OOColor *)color alpha:(GLfloat)alpha; /* For subclasses that don't want the default blur texture. NOTE: such subclasses must deal with the OOGraphicsResetManager. Also, OOLightParticleEntity assumes the texture is twice as big as the nominal size of the particle (with a black border for anti-aliasing purposes). */ - (OOTexture *) texture; + (void) setUpTexture; + (OOTexture *) defaultParticleTexture; - (void) drawSubEntityImmediate:(bool)immediate translucent:(bool)translucent; @end oolite-1.82/src/Core/Entities/OOLightParticleEntity.m000066400000000000000000000207471256642440500225700ustar00rootroot00000000000000/* OOLightParticleEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOLightParticleEntity.h" #import "Universe.h" #import "PlayerEntity.h" #import "OOTexture.h" #import "OOColor.h" #import "OOCollectionExtractors.h" #import "OOFunctionAttributes.h" #import "OOMacroOpenGL.h" #import "OOGraphicsResetManager.h" #import "MyOpenGLView.h" #define PARTICLE_DISTANCE_SCALE_LOW 12.0 #define PARTICLE_DISTANCE_SCALE_HIGH 36.0 static OOTexture *sBlobTexture = nil; @interface OOLightParticleEntity (Private) + (void) resetGraphicsState; @end @implementation OOLightParticleEntity - (id) initWithDiameter:(float)diameter { if ((self = [super init])) { _diameter = diameter; no_draw_distance = pow(diameter / 2.0, M_SQRT2) * NO_DRAW_DISTANCE_FACTOR * NO_DRAW_DISTANCE_FACTOR; no_draw_distance *= [UNIVERSE reducedDetail] ? PARTICLE_DISTANCE_SCALE_LOW : PARTICLE_DISTANCE_SCALE_HIGH; _colorComponents[0] = 1.0f; _colorComponents[1] = 1.0f; _colorComponents[2] = 1.0f; _colorComponents[3] = 1.0f; [self setScanClass:CLASS_NO_DRAW]; [self setStatus:STATUS_EFFECT]; } return self; } - (float) diameter { return _diameter; } - (void) setDiameter:(float)diameter { _diameter = diameter; } - (void) setColor:(OOColor *)color { [color getRed:&_colorComponents[0] green:&_colorComponents[1] blue:&_colorComponents[2] alpha:&_colorComponents[3]]; } - (void) setColor:(OOColor *)color alpha:(GLfloat)alpha { [self setColor:color]; _colorComponents[3] = alpha; } - (void) drawSubEntityImmediate:(bool)immediate translucent:(bool)translucent { if (!translucent) return; /* TODO: someone will inevitably build a ship so big that individual zero_distances are necessary for flashers, if they haven't already. -- Ahruman 2009-09-20 */ cam_zero_distance = [[self owner] camZeroDistance]; if (no_draw_distance <= cam_zero_distance) return; Entity *father = [self owner]; Entity *last = nil; HPVector abspos = position; while (father != nil && father != last && father != NO_TARGET) { OOMatrix rM = [father drawRotationMatrix]; abspos = HPvector_add(OOHPVectorMultiplyMatrix(abspos, rM), [father position]); last = father; if (![father isSubEntity]) break; father = [father owner]; } OOMatrix temp_matrix = OOGLPopModelView(); OOGLPushModelView(); /* Flashers are drawn using the absolute view matrix */ OOGLLoadModelView([UNIVERSE viewMatrix]); /* ...modified by the aggregate translation calculated above */ OOGLTranslateModelView(HPVectorToVector(HPvector_subtract(abspos,[PLAYER viewpointPosition]))); // move to camera-relative position [self drawImmediate:immediate translucent:translucent]; OOGLLoadModelView(temp_matrix); } - (void) drawImmediate:(bool)immediate translucent:(bool)translucent { if (!translucent) return; if ([UNIVERSE breakPatternHide] && ![self isImmuneToBreakPatternHide]) { Entity *father = [self owner]; while (father != nil && father != NO_TARGET) { if (![father isSubEntity]) break; father = [father owner]; } if (![father isImmuneToBreakPatternHide]) { return; } } if (no_draw_distance <= cam_zero_distance) return; OO_ENTER_OPENGL(); OOSetOpenGLState(OPENGL_STATE_ADDITIVE_BLENDING); OOGL(glPushAttrib(GL_COLOR_BUFFER_BIT | GL_ENABLE_BIT)); OOGL(glEnable(GL_BLEND)); OOGL(glBlendFunc(GL_SRC_ALPHA, GL_ONE)); OOGL(glEnable(GL_TEXTURE_2D)); OOGL(glDepthMask(GL_FALSE)); GLfloat distanceAttenuation = cam_zero_distance / no_draw_distance; distanceAttenuation = 1.0 - distanceAttenuation; GLfloat components[4] = { _colorComponents[0], _colorComponents[1], _colorComponents[2], _colorComponents[3] * distanceAttenuation }; OOGL(glColor4fv(components)); OOGL(glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, components)); OOGL(glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND)); OOViewID viewDir = [UNIVERSE viewDirection]; if (viewDir != VIEW_GUI_DISPLAY) OOGLMultModelView([PLAYER drawRotationMatrix]); [[self texture] apply]; /* NOTE: nominal diameter is actual radius, because of the black border in the texture. However, the offset along the view axis is not affected by the border and needs to be scaled. -- Ahruman 2009-12-20 */ float viewOffset = _diameter * 0.5f; OOGLBEGIN(GL_QUADS); switch (viewDir) { case VIEW_FORWARD: case VIEW_GUI_DISPLAY: glTexCoord2f(0.0, 1.0); glVertex3f(-_diameter, -_diameter, -viewOffset); glTexCoord2f(1.0, 1.0); glVertex3f(_diameter, -_diameter, -viewOffset); glTexCoord2f(1.0, 0.0); glVertex3f(_diameter, _diameter, -viewOffset); glTexCoord2f(0.0, 0.0); glVertex3f(-_diameter, _diameter, -viewOffset); break; case VIEW_AFT: glTexCoord2f(0.0, 1.0); glVertex3f(_diameter, -_diameter, viewOffset); glTexCoord2f(1.0, 1.0); glVertex3f(-_diameter, -_diameter, viewOffset); glTexCoord2f(1.0, 0.0); glVertex3f(-_diameter, _diameter, viewOffset); glTexCoord2f(0.0, 0.0); glVertex3f(_diameter, _diameter, viewOffset); break; case VIEW_STARBOARD: glTexCoord2f(0.0, 1.0); glVertex3f(-viewOffset, -_diameter, _diameter); glTexCoord2f(1.0, 1.0); glVertex3f(-viewOffset, -_diameter, -_diameter); glTexCoord2f(1.0, 0.0); glVertex3f(-viewOffset, _diameter, -_diameter); glTexCoord2f(0.0, 0.0); glVertex3f(-viewOffset, _diameter, _diameter); break; case VIEW_PORT: glTexCoord2f(0.0, 1.0); glVertex3f(viewOffset, -_diameter, -_diameter); glTexCoord2f(1.0, 1.0); glVertex3f(viewOffset, -_diameter, _diameter); glTexCoord2f(1.0, 0.0); glVertex3f(viewOffset, _diameter, _diameter); glTexCoord2f(0.0, 0.0); glVertex3f(viewOffset, _diameter, -_diameter); break; case VIEW_CUSTOM: { PlayerEntity *player = PLAYER; Vector vi = [player customViewRightVector]; vi.x *= _diameter; vi.y *= _diameter; vi.z *= _diameter; Vector vj = [player customViewUpVector]; vj.x *= _diameter; vj.y *= _diameter; vj.z *= _diameter; Vector vk = [player customViewForwardVector]; vk.x *= viewOffset; vk.y *= viewOffset; vk.z *= viewOffset; glTexCoord2f(0.0, 1.0); glVertex3f(-vi.x -vj.x -vk.x, -vi.y -vj.y -vk.y, -vi.z -vj.z -vk.z); glTexCoord2f(1.0, 1.0); glVertex3f(+vi.x -vj.x -vk.x, +vi.y -vj.y -vk.y, +vi.z -vj.z -vk.z); glTexCoord2f(1.0, 0.0); glVertex3f(+vi.x +vj.x -vk.x, +vi.y +vj.y -vk.y, +vi.z +vj.z -vk.z); glTexCoord2f(0.0, 0.0); glVertex3f(-vi.x +vj.x -vk.x, -vi.y +vj.y -vk.y, -vi.z +vj.z -vk.z); } break; default: glTexCoord2f(0.0, 1.0); glVertex3f(-_diameter, -_diameter, -_diameter); glTexCoord2f(1.0, 1.0); glVertex3f(_diameter, -_diameter, -_diameter); glTexCoord2f(1.0, 0.0); glVertex3f(_diameter, _diameter, -_diameter); glTexCoord2f(0.0, 0.0); glVertex3f(-_diameter, _diameter, -_diameter); break; } OOGLEND(); OOGL(glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE)); OOGL(glPopAttrib()); OOVerifyOpenGLState(); } - (OOTexture *) texture { return [OOLightParticleEntity defaultParticleTexture]; } + (void) setUpTexture { if (sBlobTexture == nil) { sBlobTexture = [[OOTexture textureWithName:@"oolite-particle-blur.png" inFolder:@"Textures" options:kOOTextureMinFilterMipMap | kOOTextureMagFilterLinear | kOOTextureAlphaMask anisotropy:kOOTextureDefaultAnisotropy / 2.0 lodBias:0.0] retain]; [[OOGraphicsResetManager sharedManager] registerClient:(id)[OOLightParticleEntity class]]; } } + (OOTexture *) defaultParticleTexture { if (sBlobTexture == nil) [self setUpTexture]; return sBlobTexture; } + (void) resetGraphicsState { [sBlobTexture release]; sBlobTexture = nil; } - (BOOL) isEffect { return YES; } - (BOOL) canCollide { return NO; } #ifndef NDEBUG - (NSSet *) allTextures { return [NSSet setWithObject:[self texture]]; } #endif @end oolite-1.82/src/Core/Entities/OOParticleSystem.h000066400000000000000000000037001256642440500215710ustar00rootroot00000000000000/* OOParticleSystem.h Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "Entity.h" #import "OOTypes.h" #import "OOMaths.h" @class OOTexture, OOColor; enum { kFragmentBurstMaxParticles = 64, kBigFragmentBurstMaxParticles = 16 }; @interface OOParticleSystem: Entity { @protected Vector _particlePosition[kFragmentBurstMaxParticles]; Vector _particleVelocity[kFragmentBurstMaxParticles]; GLfloat _particleColor[kFragmentBurstMaxParticles][4]; GLfloat _particleSize[kFragmentBurstMaxParticles]; unsigned _count; unsigned _particleType; OOTimeDelta _timePassed, _duration; double _maxSpeed; } /* Initialize particle effect with particles flying out randomly. Initiali _particleSize[] is equal to speed. */ - (id) initWithPosition:(HPVector)position velocity:(Vector)velocity count:(unsigned)count minSpeed:(float)minSpeed maxSpeed:(float)maxSpeed duration:(OOTimeDelta)duration baseColor:(GLfloat[4])baseColor; - (OOTexture *) texture; @end @interface OOSmallFragmentBurstEntity: OOParticleSystem + (id) fragmentBurstFromEntity:(Entity *)entity; @end @interface OOBigFragmentBurstEntity: OOParticleSystem { @private GLfloat _baseSize; } + (id) fragmentBurstFromEntity:(Entity *)entity; @end oolite-1.82/src/Core/Entities/OOParticleSystem.m000066400000000000000000000230471256642440500216040ustar00rootroot00000000000000/* OOParticleSystem.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOParticleSystem.h" #import "Universe.h" #import "OOTexture.h" #import "PlayerEntity.h" #import "OOLightParticleEntity.h" #import "OOMacroOpenGL.h" #import "MyOpenGLView.h" // Testing toy: cause particle systems to stop after half a second. #define FREEZE_PARTICLES 0 @implementation OOParticleSystem - (id) init { [self release]; return nil; } /* Initialize shared aspects of the fragburst entities. Also stashes generated particle speeds in _particleSize[] array. */ - (id) initWithPosition:(HPVector)pos velocity:(Vector)vel count:(unsigned)count minSpeed:(float)minSpeed maxSpeed:(float)maxSpeed duration:(OOTimeDelta)duration baseColor:(GLfloat[4])baseColor { NSParameterAssert(count <= kFragmentBurstMaxParticles); if ((self = [super init])) { _count = count; [self setPosition:pos]; velocity = vel; _duration = duration; _maxSpeed = maxSpeed; for (unsigned i = 0; i < count; i++) { GLfloat speed = minSpeed + 0.5f * (randf()+randf()) * (maxSpeed - minSpeed); // speed tends toward middle of range _particleVelocity[i] = vector_multiply_scalar(OORandomUnitVector(), speed); Vector color = make_vector(baseColor[0] * 0.1f * (9.5f + randf()), baseColor[1] * 0.1f * (9.5f + randf()), baseColor[2] * 0.1f * (9.5f + randf())); color = vector_normal(color); _particleColor[i][0] = color.x; _particleColor[i][1] = color.y; _particleColor[i][2] = color.z; _particleColor[i][3] = baseColor[3]; _particleSize[i] = speed; } [self setStatus:STATUS_EFFECT]; scanClass = CLASS_NO_DRAW; } return self; } - (NSString *) descriptionComponents { return [NSString stringWithFormat:@"ttl: %.3fs", _duration - _timePassed]; } - (BOOL) canCollide { return NO; } - (BOOL) checkCloseCollisionWith:(Entity *)other { if (other == [self owner]) return NO; return ![other isEffect]; } - (void) update:(OOTimeDelta) delta_t { [super update:delta_t]; _timePassed += delta_t; collision_radius += delta_t * _maxSpeed; unsigned i, count = _count; Vector *particlePosition = _particlePosition; Vector *particleVelocity = _particleVelocity; for (i = 0; i < count; i++) { particlePosition[i] = vector_add(particlePosition[i], vector_multiply_scalar(particleVelocity[i], delta_t)); } // disappear eventually. if (_timePassed > _duration) [UNIVERSE removeEntity:self]; } #define DrawQuadForView(x, y, z, sz) \ do { \ glTexCoord2f(0.0, 1.0); glVertex3f(x-sz, y-sz, z); \ glTexCoord2f(1.0, 1.0); glVertex3f(x+sz, y-sz, z); \ glTexCoord2f(1.0, 0.0); glVertex3f(x+sz, y+sz, z); \ glTexCoord2f(0.0, 0.0); glVertex3f(x-sz, y+sz, z); \ } while (0) - (void) drawImmediate:(bool)immediate translucent:(bool)translucent { if (!translucent || [UNIVERSE breakPatternHide]) return; OO_ENTER_OPENGL(); OOSetOpenGLState(OPENGL_STATE_ADDITIVE_BLENDING); OOGL(glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT)); OOGL(glEnable(GL_TEXTURE_2D)); OOGL(glEnable(GL_BLEND)); OOGL(glBlendFunc(GL_SRC_ALPHA, GL_ONE)); [[self texture] apply]; HPVector viewPosition = [PLAYER viewpointPosition]; HPVector selfPosition = [self position]; unsigned i, count = _count; Vector *particlePosition = _particlePosition; GLfloat (*particleColor)[4] = _particleColor; GLfloat *particleSize = _particleSize; if ([UNIVERSE reducedDetail]) { // Quick rendering - particle cloud is effectively a 2D billboard. OOGLPushModelView(); OOGLMultModelView(OOMatrixForBillboard(selfPosition, viewPosition)); OOGLBEGIN(GL_QUADS); for (i = 0; i < count; i++) { glColor4fv(particleColor[i]); DrawQuadForView(particlePosition[i].x, particlePosition[i].y, particlePosition[i].z, particleSize[i]); } OOGLEND(); OOGLPopModelView(); } else { float distanceThreshold = collision_radius * 2.0f; // Distance between player and middle of effect where we start to transition to "non-fast rendering." float thresholdSq = distanceThreshold * distanceThreshold; float distanceSq = cam_zero_distance; if (distanceSq > thresholdSq) { /* Semi-quick rendering - particle positions are volumetric, but orientation is shared. This can cause noticeable distortion if the player is close to the centre of the cloud. */ OOMatrix bbMatrix = OOMatrixForBillboard(selfPosition, viewPosition); for (i = 0; i < count; i++) { OOGLPushModelView(); OOGLTranslateModelView(particlePosition[i]); OOGLMultModelView(bbMatrix); glColor4fv(particleColor[i]); OOGLBEGIN(GL_QUADS); DrawQuadForView(0, 0, 0, particleSize[i]); OOGLEND(); OOGLPopModelView(); } } else { /* Non-fast rendering - each particle is billboarded individually. The "individuality" factor interpolates between this behavior and "semi-quick" to avoid jumping at the boundary. */ float individuality = 3.0f * (1.0f - distanceSq / thresholdSq); individuality = OOClamp_0_1_f(individuality); for (i = 0; i < count; i++) { OOGLPushModelView(); OOGLTranslateModelView(particlePosition[i]); OOGLMultModelView(OOMatrixForBillboard(HPvector_add(selfPosition, vectorToHPVector(vector_multiply_scalar(particlePosition[i], individuality))), viewPosition)); glColor4fv(particleColor[i]); OOGLBEGIN(GL_QUADS); DrawQuadForView(0, 0, 0, particleSize[i]); OOGLEND(); OOGLPopModelView(); } } } OOGL(glPopAttrib()); OOVerifyOpenGLState(); OOCheckOpenGLErrors(@"OOParticleSystem after drawing %@", self); } - (BOOL) isEffect { return YES; } - (OOTexture *) texture { return [OOLightParticleEntity defaultParticleTexture]; } #ifndef NDEBUG - (NSSet *) allTextures { return [NSSet setWithObject:[OOLightParticleEntity defaultParticleTexture]]; } #endif @end @implementation OOSmallFragmentBurstEntity: OOParticleSystem - (id) initFragmentBurstFrom:(HPVector)fragPosition velocity:(Vector)fragVelocity size:(GLfloat)size { enum { kMinSpeed = 100, kMaxSpeed = 400 }; unsigned count = 0.4f * size; count = MIN(count | 12, (unsigned)kFragmentBurstMaxParticles); // Select base colour // yellow/orange (0.12) through yellow (0.1667) to yellow/slightly green (0.20) OOColor *hsvColor = [OOColor colorWithHue:0.12f + 0.08f * randf() saturation:1.0f brightness:1.0f alpha:1.0f]; GLfloat baseColor[4]; [hsvColor getRed:&baseColor[0] green:&baseColor[1] blue:&baseColor[2] alpha:&baseColor[3]]; if ((self = [super initWithPosition:fragPosition velocity:fragVelocity count:count minSpeed:kMinSpeed maxSpeed:kMaxSpeed duration:1.5 baseColor:baseColor])) { for (unsigned i = 0; i < count; i++) { // Note: initWithPosition:... stashes speeds in _particleSize[]. _particleSize[i] = 32.0f * kMinSpeed / _particleSize[i]; } } return self; } + (id) fragmentBurstFromEntity:(Entity *)entity { return [[[self alloc] initFragmentBurstFrom:[entity position] velocity:[entity velocity] size:[entity collisionRadius]] autorelease]; } - (void) update:(OOTimeDelta) delta_t { #if FREEZE_PARTICLES if (_timePassed + delta_t > 0.5) delta_t = 0.5 - _timePassed; #endif [super update:delta_t]; unsigned i, count = _count; GLfloat (*particleColor)[4] = _particleColor; GLfloat timePassed = _timePassed; for (i = 0; i < count; i++) { GLfloat du = 0.5f + (1.0f/32.0f) * (32 - i); particleColor[i][3] = OOClamp_0_1_f(1.0f - timePassed / du); } } @end @implementation OOBigFragmentBurstEntity: OOParticleSystem - (id) initFragmentBurstFrom:(HPVector)fragPosition velocity:(Vector)fragVelocity size:(GLfloat)size { float minSpeed = 1.0f + size * 0.5f; float maxSpeed = minSpeed * 4.0f; unsigned count = 0.2f * size; count = MIN(count | 3, (unsigned)kBigFragmentBurstMaxParticles); GLfloat baseColor[4] = { 1.0f, 1.0f, 0.5f, 1.0f }; size *= 2.0f; // Account for margins in particle texture. if ((self = [super initWithPosition:fragPosition velocity:fragVelocity count:count minSpeed:minSpeed maxSpeed:maxSpeed duration:1.0 baseColor:baseColor])) { _baseSize = size; for (unsigned i = 0; i < count; i++) { _particleSize[i] = size; } } return self; } + (id) fragmentBurstFromEntity:(Entity *)entity { return [[[self alloc] initFragmentBurstFrom:[entity position] velocity:vector_multiply_scalar([entity velocity], 0.85) size:[entity collisionRadius]] autorelease]; } - (void) update:(double)delta_t { #if FREEZE_PARTICLES if (_timePassed + delta_t > 0.5) delta_t = 0.5 - _timePassed; #endif [super update:delta_t]; unsigned i, count = _count; GLfloat (*particleColor)[4] = _particleColor; GLfloat *particleSize = _particleSize; GLfloat timePassed = _timePassed; GLfloat duration = _duration; GLfloat size = (1.0f + timePassed) * _baseSize; GLfloat di = 1.0f / (count - 1); for (i = 0; i < count; i++) { GLfloat du = duration * (0.5 + di * i); particleColor[i][3] = OOClamp_0_1_f(1.0f - timePassed / du); particleSize[i] = size; } } @end oolite-1.82/src/Core/Entities/OOPlanetEntity.h000066400000000000000000000043141256642440500212430ustar00rootroot00000000000000/* OOPlanetEntity.h Entity subclass representing a planet. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOStellarBody.h" #if !NEW_PLANETS #import "PlanetEntity.h" #else #import "Entity.h" #import "OOColor.h" @class OOPlanetDrawable, ShipEntity, OOMaterial; @interface OOPlanetEntity: Entity { @private OOPlanetDrawable *_planetDrawable; OOPlanetDrawable *_atmosphereDrawable; BOOL _miniature; OOColor *_airColor; double _mesopause2; Vector _rotationAxis; float _rotationalVelocity; Quaternion _atmosphereOrientation; float _atmosphereRotationalVelocity; unsigned _shuttlesOnGround; OOTimeDelta _lastLaunchTime; OOTimeDelta _shuttleLaunchInterval; NSDictionary *_materialParameters; NSString *_textureName; NSString *_name; } - (id) initAsMainPlanetForSystem:(OOSystemID)s; - (id) initFromDictionary:(NSDictionary *)dict withAtmosphere:(BOOL)atmosphere andSeed:(Random_Seed)seed forSystem:(OOSystemID)systemID; - (instancetype) miniatureVersion; - (double) rotationalVelocity; - (void) setRotationalVelocity:(double) v; - (BOOL) planetHasStation; - (void) launchShuttle; - (void) welcomeShuttle:(ShipEntity *)shuttle; - (BOOL) hasAtmosphere; // FIXME: need material model. - (NSString *) textureFileName; - (void) setTextureFileName:(NSString *)textureName; - (BOOL) setUpPlanetFromTexture:(NSString *)fileName; - (OOMaterial *) material; - (OOMaterial *) atmosphereMaterial; - (BOOL) isFinishedLoading; @end #endif // NEW_PLANETS oolite-1.82/src/Core/Entities/OOPlanetEntity.m000066400000000000000000000646011256642440500212550ustar00rootroot00000000000000/* OOPlanetEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOPlanetEntity.h" #if NEW_PLANETS #define NEW_ATMOSPHERE 1 #import "OOPlanetDrawable.h" #import "AI.h" #import "Universe.h" #import "ShipEntity.h" #import "PlayerEntity.h" #import "ShipEntityAI.h" #import "OOCharacter.h" #import "OOMaths.h" #import "ResourceManager.h" #import "OOStringParsing.h" #import "OOCollectionExtractors.h" #import "OOSystemDescriptionManager.h" #import "OOPlanetTextureGenerator.h" #import "OOStandaloneAtmosphereGenerator.h" #import "OOSingleTextureMaterial.h" #import "OOShaderMaterial.h" #import "OOEntityFilterPredicate.h" #import "OOGraphicsResetManager.h" #import "OOStringExpander.h" #import "OOOpenGLMatrixManager.h" @interface OOPlanetEntity (Private) - (void) setUpLandParametersWithSourceInfo:(NSDictionary *)sourceInfo targetInfo:(NSMutableDictionary *)targetInfo; - (void) setUpAtmosphereParametersWithSourceInfo:(NSDictionary *)sourceInfo targetInfo:(NSMutableDictionary *)targetInfo; - (void) setUpColorParametersWithSourceInfo:(NSDictionary *)sourceInfo targetInfo:(NSMutableDictionary *)targetInfo isAtmosphere:(BOOL)isAtmosphere; - (void) setUpTypeParametersWithSourceInfo:(NSDictionary *)sourceInfo targetInfo:(NSMutableDictionary *)targetInfo; @end @implementation OOPlanetEntity // this is exclusively called to initialise the main planet. - (id) initAsMainPlanetForSystem:(OOSystemID)s { NSMutableDictionary *planetInfo = [[UNIVERSE generateSystemData:s] mutableCopy]; [planetInfo autorelease]; [planetInfo oo_setBool:YES forKey:@"mainForLocalSystem"]; if (s != [PLAYER systemID]) { [planetInfo oo_setBool:YES forKey:@"isMiniature"]; } return [self initFromDictionary:planetInfo withAtmosphere:[planetInfo oo_boolForKey:@"has_atmosphere" defaultValue:YES] andSeed:[[UNIVERSE systemManager] getRandomSeedForSystem:s inGalaxy:[PLAYER galaxyNumber]] forSystem:s]; } static const double kMesosphere = 10.0 * ATMOSPHERE_DEPTH; // atmosphere effect starts at 10x the height of the clouds - (id) initFromDictionary:(NSDictionary *)dict withAtmosphere:(BOOL)atmosphere andSeed:(Random_Seed)seed forSystem:(OOSystemID)systemID { if (dict == nil) dict = [NSDictionary dictionary]; RANROTSeed savedRanrotSeed = RANROTGetFullSeed(); self = [self init]; if (self == nil) return nil; scanClass = CLASS_NO_DRAW; // Load random seed override. NSString *seedStr = [dict oo_stringForKey:@"seed"]; if (seedStr != nil) { Random_Seed overrideSeed = RandomSeedFromString(seedStr); if (!is_nil_seed(overrideSeed)) seed = overrideSeed; else OOLogERR(@"planet.fromDict", @"could not interpret \"%@\" as planet seed, using default.", seedStr); } // Generate various planet info. seed_for_planet_description(seed); NSMutableDictionary *planetInfo = [[UNIVERSE generateSystemData:systemID] mutableCopy]; [planetInfo autorelease]; [self setUpTypeParametersWithSourceInfo:dict targetInfo:planetInfo]; _name = nil; [self setName:OOExpand([dict oo_stringForKey:KEY_PLANETNAME defaultValue:[planetInfo oo_stringForKey:KEY_PLANETNAME defaultValue:@"%H"]])]; int radius_km = [dict oo_intForKey:KEY_RADIUS defaultValue:[planetInfo oo_intForKey:KEY_RADIUS]]; collision_radius = radius_km * 10.0; // Scale down by a factor of 100 OOTechLevelID techLevel = [dict oo_intForKey:KEY_TECHLEVEL defaultValue:[planetInfo oo_intForKey:KEY_TECHLEVEL]]; if (techLevel > 14) techLevel = 14; _shuttlesOnGround = 1 + techLevel / 2; _shuttleLaunchInterval = 3600.0 / (double)_shuttlesOnGround; // All are launched in one hour. _lastLaunchTime = [UNIVERSE getTime] + 30.0 - _shuttleLaunchInterval; // launch 30s after player enters universe. // make delay > 0 to allow scripts adding a station nearby. int percent_land = [planetInfo oo_intForKey:@"percent_land" defaultValue:24 + (gen_rnd_number() % 48)]; [planetInfo setObject:[NSNumber numberWithFloat:0.01 * percent_land] forKey:@"land_fraction"]; int percent_ice = [planetInfo oo_intForKey:@"percent_ice" defaultValue:5]; [planetInfo setObject:[NSNumber numberWithFloat:0.01 * percent_ice] forKey:@"polar_fraction"]; RNG_Seed savedRndSeed = currentRandomSeed(); _planetDrawable = [[OOPlanetDrawable alloc] init]; // Load material parameters, including atmosphere. RANROTSeed planetNoiseSeed = RANROTGetFullSeed(); [planetInfo setObject:[NSValue valueWithBytes:&planetNoiseSeed objCType:@encode(RANROTSeed)] forKey:@"noise_map_seed"]; [self setUpLandParametersWithSourceInfo:dict targetInfo:planetInfo]; _airColor = nil; // default to no air #if NEW_ATMOSPHERE if (atmosphere) { _atmosphereDrawable = [[OOPlanetDrawable atmosphereWithRadius:collision_radius + ATMOSPHERE_DEPTH] retain]; // convert the atmosphere settings to generic 'material parameters' percent_land = 100 - [dict oo_intForKey:@"percent_cloud" defaultValue:100 - (3 + (gen_rnd_number() & 31)+(gen_rnd_number() & 31))]; [planetInfo setObject:[NSNumber numberWithFloat:0.01 * percent_land] forKey:@"cloud_fraction"]; [self setUpAtmosphereParametersWithSourceInfo:dict targetInfo:planetInfo]; // planetInfo now contains a valid air_color _airColor = [planetInfo objectForKey:@"air_color"]; // OOLog (@"planet.debug",@" translated air colour:%@ cloud colour:%@ polar cloud color:%@", [_airColor rgbaDescription],[(OOColor *)[planetInfo objectForKey:@"cloud_color"] rgbaDescription],[(OOColor *)[planetInfo objectForKey:@"polar_cloud_color"] rgbaDescription]); _materialParameters = [planetInfo dictionaryWithValuesForKeys:[NSArray arrayWithObjects:@"cloud_fraction", @"air_color", @"cloud_color", @"polar_cloud_color", @"cloud_alpha", @"land_fraction", @"land_color", @"sea_color", @"polar_land_color", @"polar_sea_color", @"noise_map_seed", @"economy", @"polar_fraction", @"isMiniature", nil]]; } else #else // NEW_ATMOSPHERE is 0? still differentiate between normal planets and moons. if (atmosphere) { _atmosphereDrawable = [[OOPlanetDrawable atmosphereWithRadius:collision_radius + ATMOSPHERE_DEPTH] retain]; _airColor = [[OOColor colorWithRed:0.8f green:0.8f blue:0.9f alpha:1.0f] retain]; } if (YES) // create _materialParameters when NEW_ATMOSPHERE is set to 0 #endif { _materialParameters = [planetInfo dictionaryWithValuesForKeys:[NSArray arrayWithObjects:@"land_fraction", @"land_color", @"sea_color", @"polar_land_color", @"polar_sea_color", @"noise_map_seed", @"economy", @"polar_fraction", @"isMiniature", nil]]; } [_materialParameters retain]; _mesopause2 = (atmosphere) ? (kMesosphere + collision_radius) * (kMesosphere + collision_radius) : 0.0; _textureName = [[dict oo_stringForKey:@"texture"] retain]; [self setUpPlanetFromTexture:_textureName]; [_planetDrawable setRadius:collision_radius]; // Orientation should be handled by the code that calls this planetEntity. Starting with a default value anyway. orientation = (Quaternion){ M_SQRT1_2, M_SQRT1_2, 0, 0 }; _atmosphereOrientation = kIdentityQuaternion; _rotationAxis = vector_up_from_quaternion(orientation); // set speed of rotation. if ([dict objectForKey:@"rotational_velocity"]) { _rotationalVelocity = [dict oo_floatForKey:@"rotational_velocity" defaultValue:0.01f * randf()]; // 0.0 .. 0.01 avr 0.005 } else { _rotationalVelocity = [planetInfo oo_floatForKey:@"rotation_speed" defaultValue:0.005 * randf()]; // 0.0 .. 0.005 avr 0.0025 _rotationalVelocity *= [planetInfo oo_floatForKey:@"rotation_speed_factor" defaultValue:1.0f]; } _atmosphereRotationalVelocity = [dict oo_floatForKey:@"atmosphere_rotational_velocity" defaultValue:0.01f * randf()]; // set energy energy = collision_radius * 1000.0; setRandomSeed(savedRndSeed); RANROTSetFullSeed(savedRanrotSeed); // rotate planet based on current time, needs to be done here - backported from PlanetEntity. int deltaT = floor(fmod([PLAYER clockTimeAdjusted], 86400)); quaternion_rotate_about_axis(&orientation, _rotationAxis, _rotationalVelocity * deltaT); quaternion_rotate_about_axis(&_atmosphereOrientation, kBasisYVector, _atmosphereRotationalVelocity * deltaT); #ifdef OO_DUMP_PLANETINFO #define CPROP(PROP) OOLog(@"planetinfo.record",@#PROP " = %@;",[(OOColor *)[planetInfo objectForKey:@#PROP] descriptionComponents]); #define FPROP(PROP) OOLog(@"planetinfo.record",@#PROP " = %f;",[planetInfo oo_floatForKey:@"" #PROP]); CPROP(air_color); FPROP(cloud_alpha); CPROP(cloud_color); FPROP(cloud_fraction); CPROP(land_color); FPROP(land_fraction); CPROP(polar_cloud_color); CPROP(polar_land_color); CPROP(polar_sea_color); CPROP(sea_color); OOLog(@"planetinfo.record",@"rotation_speed = %f",_rotationalVelocity); #endif [self setStatus:STATUS_ACTIVE]; [[OOGraphicsResetManager sharedManager] registerClient:self]; return self; } static Vector RandomHSBColor(void) { return (Vector) { gen_rnd_number() / 256.0, gen_rnd_number() / 256.0, 0.5 + gen_rnd_number() / 512.0 }; } static Vector LighterHSBColor(Vector c) { return (Vector) { c.x, c.y * 0.25f, 1.0f - (c.z * 0.1f) }; } static Vector HSBColorWithColor(OOColor *color) { OOHSBAComponents c = [color hsbaComponents]; return (Vector){ c.h/360, c.s, c.b }; } static OOColor *ColorWithHSBColor(Vector c) { return [OOColor colorWithHue:c.x saturation:c.y brightness:c.z alpha:1.0]; } - (void) setUpTypeParametersWithSourceInfo:(NSDictionary *)sourceInfo targetInfo:(NSMutableDictionary *)targetInfo { [targetInfo oo_setBool:[sourceInfo oo_boolForKey:@"mainForLocalSystem"] forKey:@"mainForLocalSystem"]; [targetInfo oo_setBool:[sourceInfo oo_boolForKey:@"isMiniature"] forKey:@"isMiniature"]; } - (void) setUpLandParametersWithSourceInfo:(NSDictionary *)sourceInfo targetInfo:(NSMutableDictionary *)targetInfo { [self setUpColorParametersWithSourceInfo:sourceInfo targetInfo:targetInfo isAtmosphere:NO]; } - (void) setUpAtmosphereParametersWithSourceInfo:(NSDictionary *)sourceInfo targetInfo:(NSMutableDictionary *)targetInfo { [self setUpColorParametersWithSourceInfo:sourceInfo targetInfo:targetInfo isAtmosphere:YES]; } - (void) setUpColorParametersWithSourceInfo:(NSDictionary *)sourceInfo targetInfo:(NSMutableDictionary *)targetInfo isAtmosphere:(BOOL)isAtmosphere { // Stir the PRNG fourteen times for backwards compatibility. unsigned i; for (i = 0; i < 14; i++) { gen_rnd_number(); } Vector landHSB, seaHSB, landPolarHSB, seaPolarHSB; OOColor *color; landHSB = RandomHSBColor(); if (!isAtmosphere) { do { seaHSB = RandomHSBColor(); } while (dot_product(landHSB, seaHSB) > .80); // make sure land and sea colors differ significantly // saturation bias - avoids really grey oceans if (seaHSB.y < 0.22f) seaHSB.y = seaHSB.y * 0.3f + 0.2f; // brightness bias - avoids really bright landmasses if (landHSB.z > 0.66f) landHSB.z = 0.66f; // planetinfo.plist overrides color = [OOColor colorWithDescription:[sourceInfo objectForKey:@"land_color"]]; if (color != nil) landHSB = HSBColorWithColor(color); else ScanVectorFromString([sourceInfo oo_stringForKey:@"land_hsb_color"], &landHSB); color = [OOColor colorWithDescription:[sourceInfo objectForKey:@"sea_color"]]; if (color != nil) seaHSB = HSBColorWithColor(color); else ScanVectorFromString([sourceInfo oo_stringForKey:@"sea_hsb_color"], &seaHSB); // polar areas are brighter but have less colour (closer to white) color = [OOColor colorWithDescription:[sourceInfo objectForKey:@"polar_land_color"]]; if (color != nil) { landPolarHSB = HSBColorWithColor(color); } else { landPolarHSB = LighterHSBColor(landHSB); } color = [OOColor colorWithDescription:[sourceInfo objectForKey:@"polar_sea_color"]]; if (color != nil) { seaPolarHSB = HSBColorWithColor(color); } else { seaPolarHSB = LighterHSBColor(seaHSB); } [targetInfo setObject:ColorWithHSBColor(landHSB) forKey:@"land_color"]; [targetInfo setObject:ColorWithHSBColor(seaHSB) forKey:@"sea_color"]; [targetInfo setObject:ColorWithHSBColor(landPolarHSB) forKey:@"polar_land_color"]; [targetInfo setObject:ColorWithHSBColor(seaPolarHSB) forKey:@"polar_sea_color"]; } else { landHSB = RandomHSBColor(); // NB: randomcolor is called twice to make the cloud colour similar to the old one. // add a cloud_color tinge to sky blue({0.66, 0.3, 1}). seaHSB = vector_add(landHSB,((Vector){1.333, 0.6, 2})); // 1 part cloud, 2 parts sky blue scale_vector(&seaHSB, 0.333); float cloudAlpha = OOClamp_0_1_f([sourceInfo oo_floatForKey:@"cloud_alpha" defaultValue:1.0f]); [targetInfo setObject:[NSNumber numberWithFloat:cloudAlpha] forKey:@"cloud_alpha"]; // planetinfo overrides color = [OOColor colorWithDescription:[sourceInfo objectForKey:@"atmosphere_color"]]; if (color != nil) seaHSB = HSBColorWithColor(color); color = [OOColor colorWithDescription:[sourceInfo objectForKey:@"cloud_color"]]; if (color != nil) landHSB = HSBColorWithColor(color); // polar areas: brighter, less saturation landPolarHSB = vector_add(landHSB,LighterHSBColor(landHSB)); scale_vector(&landPolarHSB, 0.5); color = [OOColor colorWithDescription:[sourceInfo objectForKey:@"polar_cloud_color"]]; if (color != nil) landPolarHSB = HSBColorWithColor(color); [targetInfo setObject:ColorWithHSBColor(seaHSB) forKey:@"air_color"]; [targetInfo setObject:ColorWithHSBColor(landHSB) forKey:@"cloud_color"]; [targetInfo setObject:ColorWithHSBColor(landPolarHSB) forKey:@"polar_cloud_color"]; } } - (id) initAsMiniatureVersionOfPlanet:(OOPlanetEntity *)planet { // Nasty, nasty. I'd really prefer to have a separate entity class for this. if (planet == nil) { [self release]; return nil; } self = [self init]; if (self == nil) return nil; scanClass = CLASS_NO_DRAW; [self setStatus:STATUS_COCKPIT_DISPLAY]; collision_radius = planet->collision_radius * PLANET_MINIATURE_FACTOR; orientation = planet->orientation; _rotationAxis = planet->_rotationAxis; _rotationalVelocity = 0.04; _miniature = YES; _planetDrawable = [planet->_planetDrawable copy]; [_planetDrawable setRadius:collision_radius]; // FIXME: in old planet code, atmosphere (if textured) is set to 0.6 alpha. _atmosphereDrawable = [planet->_atmosphereDrawable copy]; [_atmosphereDrawable setRadius:collision_radius + ATMOSPHERE_DEPTH * PLANET_MINIATURE_FACTOR * 2.0]; //not to scale: invisible otherwise [_planetDrawable setLevelOfDetail:0.8f]; [_atmosphereDrawable setLevelOfDetail:0.8f]; return self; } - (void) dealloc { DESTROY(_name); DESTROY(_planetDrawable); DESTROY(_atmosphereDrawable); //DESTROY(_airColor); // this CTDs on loading savegames.. :( DESTROY(_materialParameters); DESTROY(_textureName); [[OOGraphicsResetManager sharedManager] unregisterClient:self]; [super dealloc]; } - (NSString*) descriptionComponents { return [NSString stringWithFormat:@"position: %@ radius: %g m", HPVectorDescription([self position]), [self radius]]; } - (void) setOrientation:(Quaternion) quat { [super setOrientation: quat]; _rotationAxis = vector_up_from_quaternion(quat); } - (double) radius { return collision_radius; } - (OOStellarBodyType) planetType { if (_miniature) return STELLAR_TYPE_MINIATURE; if (_atmosphereDrawable != nil) return STELLAR_TYPE_NORMAL_PLANET; return STELLAR_TYPE_MOON; } - (instancetype) miniatureVersion { return [[[[self class] alloc] initAsMiniatureVersionOfPlanet:self] autorelease]; } - (void) update:(OOTimeDelta) delta_t { [super update:delta_t]; if (EXPECT(!_miniature)) { if (EXPECT_NOT(_atmosphereDrawable && cam_zero_distance < _mesopause2)) { NSAssert(_airColor != nil, @"Expected a non-nil air colour for normal planet. Exiting."); double alt = (sqrt(cam_zero_distance) - collision_radius) / kMesosphere; if (EXPECT_NOT(alt > 0 && alt <= 1.0)) // ensure aleph is clamped between 0 and 1 { double aleph = 1.0 - alt; double aleph2 = aleph * aleph; // night sky, reddish flash on entering the atmosphere, low light pollution otherwhise OOColor *mixColor = [OOColor colorWithRed:(EXPECT_NOT(alt > 0.98) ? 30.0f : 0.1f) green:0.1f blue:0.1f alpha:aleph]; // occlusion rate: .9 is 18 degrees after the terminus, where twilight ends. // 1 is the terminus, 1.033 is 6 degrees before the terminus, where the sky begins to redden double rate = ([PLAYER occlusionLevel] - 0.97)/0.06; // from 0.97 to 1.03 if (EXPECT(rate <= 1.0 && rate > 0.0)) { mixColor = [mixColor blendedColorWithFraction:rate ofColor:_airColor]; // TODO: properly calculated pink sky - needs to depend on sun's angular size, // and its angular height on the horizon. /* rate -= 0.7; if (rate >= 0.0) // pink sky! { rate = 0.5 - (fabs(rate - 0.15) / 0.3); // at most a 50% blend! mixColor = [mixColor blendedColorWithFraction:rate ofColor:[OOColor colorWithRed:0.6 green:0.1 blue:0.0 alpha:aleph]]; } */ } else { if (PLAYER->isSunlit && _airColor != nil) mixColor = _airColor; } [UNIVERSE setSkyColorRed:[mixColor redComponent] * aleph2 green:[mixColor greenComponent] * aleph2 blue:[mixColor blueComponent] * aleph alpha:aleph]; [_atmosphereDrawable setRadius:collision_radius + (ATMOSPHERE_DEPTH * alt)]; } } else if (EXPECT_NOT([_atmosphereDrawable radius] < collision_radius + ATMOSPHERE_DEPTH)) { [_atmosphereDrawable setRadius:collision_radius + ATMOSPHERE_DEPTH]; } double time = [UNIVERSE getTime]; if (_shuttlesOnGround > 0 && time > _lastLaunchTime + _shuttleLaunchInterval) [self launchShuttle]; } quaternion_rotate_about_axis(&orientation, _rotationAxis, _rotationalVelocity * delta_t); // atmosphere orientation is relative to the orientation of the planet quaternion_rotate_about_axis(&_atmosphereOrientation, kBasisYVector, _atmosphereRotationalVelocity * delta_t); [self orientationChanged]; // FIXME: update atmosphere rotation } - (BOOL) isFinishedLoading { OOMaterial *material = [self material]; if (material != nil && ![material isFinishedLoading]) return NO; material = [self atmosphereMaterial]; if (material != nil && ![material isFinishedLoading]) return NO; return YES; } - (void) drawImmediate:(bool)immediate translucent:(bool)translucent { if ([UNIVERSE breakPatternHide]) return; // DON'T DRAW if (_miniature && ![self isFinishedLoading]) return; // For responsiveness, don't block to draw as miniature. if (![UNIVERSE viewFrustumIntersectsSphereAt:cameraRelativePosition withRadius:([self radius] + ATMOSPHERE_DEPTH)]) { // Don't draw return; } if ([UNIVERSE wireframeGraphics]) OOGLWireframeModeOn(); if (!_miniature) { [_planetDrawable calculateLevelOfDetailForViewDistance:cam_zero_distance]; [_atmosphereDrawable setLevelOfDetail:[_planetDrawable levelOfDetail]]; } // 500km squared // if (magnitude2(cameraRelativePosition) > 250000000000.0) /* Temporarily for 1.82 make this branch unconditional. There's an * odd change in appearance when crossing this boundary, which can * be quite noticeable. There don't appear to be close-range * problems with doing it this way all the time, though it's not * ideal. - CIM */ { /* at this distance the atmosphere is too close to the planet * for a 24-bit depth buffer to reliably distinguish the two, * so cheat and draw the atmosphere on the opaque pass: it's * far enough away that painter's algorithm should do fine */ [_planetDrawable renderOpaqueParts]; if (_atmosphereDrawable != nil) { OOGLPushModelView(); OOGLMultModelView(OOMatrixForQuaternionRotation(_atmosphereOrientation)); [_atmosphereDrawable renderTranslucentPartsOnOpaquePass]; OOGLPopModelView(); } } #if OOLITE_HAVE_FIXED_THE_ABOVE_DESCRIBED_BUG_WHICH_WE_HAVENT else { /* At close range we can do this properly and draw the * atmosphere on the transparent pass */ if (translucent) { if (_atmosphereDrawable != nil) { OOGLPushModelView(); OOGLMultModelView(OOMatrixForQuaternionRotation(_atmosphereOrientation)); [_atmosphereDrawable renderTranslucentParts]; OOGLPopModelView(); } } else { [_planetDrawable renderOpaqueParts]; } } #endif if ([UNIVERSE wireframeGraphics]) OOGLWireframeModeOff(); } - (BOOL) checkCloseCollisionWith:(Entity *)other { if (!other) return NO; if (other->isShip) { ShipEntity *ship = (ShipEntity *)other; if ([ship behaviour] == BEHAVIOUR_LAND_ON_PLANET) { return NO; } } return YES; } - (BOOL) planetHasStation { // find the nearest station... ShipEntity *station = nil; station = [UNIVERSE nearestShipMatchingPredicate:IsStationPredicate parameter:nil relativeToEntity:self]; if (station && HPdistance([station position], position) < 4 * collision_radius) // there is a station in range. { return YES; } return NO; } - (void) launchShuttle { if (_shuttlesOnGround == 0) { return; } if ([PLAYER status] == STATUS_START_GAME) { // don't launch if game not started return; } if (self != [UNIVERSE planet] && ![self planetHasStation]) { // don't launch shuttles when no station is nearby. _shuttlesOnGround = 0; return; } Quaternion q1; quaternion_set_random(&q1); float start_distance = collision_radius + 125.0f; HPVector launch_pos = HPvector_add(position, vectorToHPVector(vector_multiply_scalar(vector_forward_from_quaternion(q1), start_distance))); ShipEntity *shuttle_ship = [UNIVERSE newShipWithRole:@"shuttle"]; // retain count = 1 if (shuttle_ship) { if ([[shuttle_ship crew] count] == 0) { [shuttle_ship setSingleCrewWithRole:@"trader"]; } [shuttle_ship setPosition:launch_pos]; [shuttle_ship setOrientation:q1]; [shuttle_ship setScanClass: CLASS_NEUTRAL]; [shuttle_ship setCargoFlag:CARGO_FLAG_FULL_PLENTIFUL]; [shuttle_ship switchAITo:@"oolite-shuttleAI.js"]; [UNIVERSE addEntity:shuttle_ship]; // STATUS_IN_FLIGHT, AI state GLOBAL _shuttlesOnGround--; _lastLaunchTime = [UNIVERSE getTime]; [shuttle_ship release]; } } - (void) welcomeShuttle:(ShipEntity *)shuttle { _shuttlesOnGround++; } - (BOOL) isPlanet { return YES; } - (BOOL) isVisible { return YES; } - (double) rotationalVelocity { return _rotationalVelocity; } - (void) setRotationalVelocity:(double) v { if ([self hasAtmosphere]) { // FIXME: change atmosphere rotation speed proportionally } _rotationalVelocity = v; } - (BOOL) hasAtmosphere { return _atmosphereDrawable != nil; } // FIXME: need material model. - (NSString *) textureFileName { return [_planetDrawable textureName]; } - (void)resetGraphicsState { // reset the texture if graphics mode changes [self setUpPlanetFromTexture:_textureName]; } - (void) setTextureFileName:(NSString *)textureName { BOOL isMoon = _atmosphereDrawable == nil; OOTexture *diffuseMap = nil; OOTexture *normalMap = nil; NSDictionary *macros = nil; NSDictionary *materialDefaults = [ResourceManager materialDefaults]; #if OO_SHADERS OOGraphicsDetail detailLevel = [UNIVERSE detailLevel]; BOOL shadersOn = detailLevel >= DETAIL_LEVEL_EXTRAS; #else const BOOL shadersOn = NO; #endif if (textureName != nil) { NSDictionary *spec = [NSDictionary dictionaryWithObjectsAndKeys:textureName, @"name", @"yes", @"repeat_s", @"linear", @"min_filter", @"yes", @"cube_map", nil]; diffuseMap = [OOTexture textureWithConfiguration:spec]; if (diffuseMap == nil) return; // OOTexture will have logged a file-not-found warning. if (shadersOn) { [diffuseMap ensureFinishedLoading]; // only know if it is a cube map if it's loaded if ([diffuseMap isCubeMap]) { macros = [materialDefaults oo_dictionaryForKey:isMoon ? @"moon-customized-cubemap-macros" : @"planet-customized-cubemap-macros"]; } else { macros = [materialDefaults oo_dictionaryForKey:isMoon ? @"moon-customized-macros" : @"planet-customized-macros"]; } } else textureName = @"dynamic"; } else { [OOPlanetTextureGenerator generatePlanetTexture:&diffuseMap secondaryTexture:(detailLevel >= DETAIL_LEVEL_EXTRAS) ? &normalMap : NULL withInfo:_materialParameters]; if (shadersOn) { macros = [materialDefaults oo_dictionaryForKey:isMoon ? @"moon-dynamic-macros" : @"planet-dynamic-macros"]; } textureName = @"dynamic"; } /* Generate atmosphere texture */ if (!isMoon) { OOLog(@"texture.planet.generate",@"Preparing atmosphere for planet %@",self); /* Generate a standalone atmosphere texture */ OOTexture *atmosphere = nil; [OOStandaloneAtmosphereGenerator generateAtmosphereTexture:&atmosphere withInfo:_materialParameters]; OOLog(@"texture.planet.generate",@"Planet %@ has atmosphere %@",self,atmosphere); OOSingleTextureMaterial *dynamicMaterial = [[OOSingleTextureMaterial alloc] initWithName:@"dynamic" texture:atmosphere configuration:nil]; [_atmosphereDrawable setMaterial:dynamicMaterial]; [dynamicMaterial release]; } OOMaterial *material = nil; #if OO_SHADERS if (shadersOn) { NSMutableDictionary *config = [[[materialDefaults oo_dictionaryForKey:@"planet-material"] mutableCopy] autorelease]; [config setObject:[NSArray arrayWithObjects:diffuseMap, normalMap, nil] forKey:@"_oo_texture_objects"]; material = [OOShaderMaterial shaderMaterialWithName:textureName configuration:config macros:macros bindingTarget:self]; } #endif if (material == nil) { material = [[OOSingleTextureMaterial alloc] initWithName:textureName texture:diffuseMap configuration:nil]; [material autorelease]; } [_planetDrawable setMaterial:material]; } - (BOOL) setUpPlanetFromTexture:(NSString *)textureName { [self setTextureFileName:textureName]; return YES; } - (OOMaterial *) material { return [_planetDrawable material]; } - (OOMaterial *) atmosphereMaterial { return [_atmosphereDrawable material]; } - (NSString *) name { return _name; } - (void) setName:(NSString *)name { [_name release]; _name = [name retain]; } @end #endif // NEW_PLANETS oolite-1.82/src/Core/Entities/OOPlasmaBurstEntity.h000066400000000000000000000016311256642440500222540ustar00rootroot00000000000000/* OOPlasmaBurstEntity.h Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOLightParticleEntity.h" @interface OOPlasmaBurstEntity: OOLightParticleEntity - (id) initWithPosition:(HPVector)position; @end oolite-1.82/src/Core/Entities/OOPlasmaBurstEntity.m000066400000000000000000000031161256642440500222610ustar00rootroot00000000000000/* OOPlasmaBurstEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOPlasmaBurstEntity.h" #import "Universe.h" #define kPlasmaBurstInitialSize 64.0f #define kPlasmaBurstGrowthRate 64.0f #define kPlasmaBurstDuration 2.0f @implementation OOPlasmaBurstEntity - (id) initWithPosition:(HPVector)inPosition { if ((self = [super initWithDiameter:kPlasmaBurstInitialSize])) { [self setPosition:inPosition]; [self setCollisionRadius:2.0]; [self setColor:[OOColor redColor]]; } return self; } - (void) update:(double)delta_t { [super update:delta_t]; OOTimeDelta lifeTime = [self timeElapsedSinceSpawn]; float attenuation = OOClamp_0_1_f(1.0f - lifeTime / kPlasmaBurstDuration); _diameter = kPlasmaBurstInitialSize + lifeTime * kPlasmaBurstGrowthRate; _colorComponents[3] = attenuation; if (lifeTime > kPlasmaBurstDuration) [UNIVERSE removeEntity:self]; } @end oolite-1.82/src/Core/Entities/OOPlasmaShotEntity.h000066400000000000000000000020741256642440500220740ustar00rootroot00000000000000/* OOPlasmaShotEntity.h Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOLightParticleEntity.h" @interface OOPlasmaShotEntity: OOLightParticleEntity { @private OOTimeDelta _duration; } - (id) initWithPosition:(HPVector)position velocity:(Vector)velocity energy:(float)energy duration:(OOTimeDelta)duration color:(OOColor *)color; @end oolite-1.82/src/Core/Entities/OOPlasmaShotEntity.m000066400000000000000000000054111256642440500220770ustar00rootroot00000000000000/* OOPlasmaShotEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOPlasmaShotEntity.h" #import "Universe.h" #import "PlayerEntity.h" #import "OOColor.h" #import "OOPlasmaBurstEntity.h" #define kPlasmaShotSize 12.0f #define kPlasmaShotActivationDelay 0.05f /* If nonzero, plasma shots fade with distance. Bits of this were in the old ParticleEntity code, but I think it was disabled on purpose. -- Ahruman 2009-09-25 */ #define PLASMA_ATTENUATION 0 @implementation OOPlasmaShotEntity - (id) initWithPosition:(HPVector)inPosition velocity:(Vector)inVelocity energy:(float)inEnergy duration:(OOTimeDelta)duration color:(OOColor *)color { if ((self = [super initWithDiameter:kPlasmaShotSize])) { [self setPosition:inPosition]; [self setVelocity:inVelocity]; [self setCollisionRadius:2.0]; [self setColor:color alpha:1.0]; _colorComponents[3] = 1.0f; [self setEnergy:inEnergy]; _duration = duration; } return self; } - (BOOL) canCollide { return [UNIVERSE getTime] > [self spawnTime] + kPlasmaShotActivationDelay; } - (BOOL) checkCloseCollisionWith:(Entity *)other { return ([other rootShipEntity] != [self owner]) && ![other isEffect]; } - (void) update:(double)delta_t { [super update:delta_t]; OOTimeDelta lifeTime = [self timeElapsedSinceSpawn]; #if PLASMA_ATTENUATION float attenuation = OOClamp_0_1_f(1.0f - lifeTime / _duration); #else const float attenuation = 1.0f; #endif NSUInteger i, count = [collidingEntities count]; for (i = 0; i < count; i++) { Entity *e = (Entity *)[collidingEntities objectAtIndex:i]; if ([e rootShipEntity] != [self owner]) { [e takeEnergyDamage:[self energy] * attenuation from:self becauseOf:[self owner]]; [UNIVERSE removeEntity:self]; // Spawn a plasma burst. OOPlasmaBurstEntity *burst = [[OOPlasmaBurstEntity alloc] initWithPosition:[self position]]; [UNIVERSE addEntity:burst]; [burst release]; } } #if PLASMA_ATTENUATION _colorComponents[3] = attenuation; #endif if (lifeTime > _duration) [UNIVERSE removeEntity:self]; } @end oolite-1.82/src/Core/Entities/OOQuiriumCascadeEntity.h000066400000000000000000000021221256642440500227120ustar00rootroot00000000000000/* OOQuiriumCascadeEntity.h Droppings of a Q-mine, or one of its victims. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "Entity.h" @interface OOQuiriumCascadeEntity: Entity { @private OOTimeDelta _timePassed; GLfloat _color[4]; } + (instancetype) quiriumCascadeFromShip:(ShipEntity *)ship; @end @interface Entity (OOQuiriumCascadeExtensions) - (BOOL) isCascadeWeapon; @end oolite-1.82/src/Core/Entities/OOQuiriumCascadeEntity.m000066400000000000000000000072531256642440500227310ustar00rootroot00000000000000/* OOQuiriumCascadeEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOQuiriumCascadeEntity.h" #import "PlayerEntity.h" #import "Universe.h" #import "OOMacroOpenGL.h" #define kQuiriumCascadeDuration (20.0) // seconds #define kQuiriumCollisionDelay (0.05) // seconds before we start colliding with and damaging things. @implementation OOQuiriumCascadeEntity - (id) initQuiriumCascadeFromShip:(ShipEntity *)ship { if (ship == nil) { [self release]; return nil; } if ((self = [super init])) { [self setPosition:[ship position]]; [self setStatus:STATUS_EFFECT]; scanClass = CLASS_MINE; [self setOwner:[ship owner]]; // Red and green channels are animated. _color[2] = 1.0f; _color[3] = 0.5f; } return self; } + (instancetype) quiriumCascadeFromShip:(ShipEntity *)ship { return [[[self alloc] initQuiriumCascadeFromShip:ship] autorelease]; } - (NSString *) descriptionComponents { return [NSString stringWithFormat:@"%f seconds passed of %f", _timePassed, kQuiriumCascadeDuration]; } - (void) update:(OOTimeDelta) delta_t { [super update:delta_t]; _timePassed += delta_t; rotMatrix = OOMatrixForBillboard(position, [PLAYER position]); GLfloat tf = _timePassed / kQuiriumCascadeDuration; GLfloat stf = tf * tf; GLfloat expansionSpeed = 0.0; if (_timePassed > 0) // Avoid divide by 0 { expansionSpeed = fmin(240.0f + 10.0f / (tf * tf), 1000.0f); } collision_radius += delta_t * expansionSpeed; // expand energy = delta_t * (100000 - 90000 * tf); // adjusted to take into account delta_t _color[3] = OOClamp_0_1_f(0.5f * ((0.025f / tf) + 1.0f - stf)); _color[0] = _color[1] = fmin(1.0f - 5.0f * tf, 1.0f); if (_color[0] < 0.0f) { _color[0] = 0.25f * tf * randf(); _color[1] = 0.0f; } // manage collisions Entity *owner = [self owner]; Entity *e = nil; foreach (e, collidingEntities) { [e takeEnergyDamage:energy from:self becauseOf:owner]; } // expire after ttl if (_timePassed > kQuiriumCascadeDuration) { [UNIVERSE removeEntity:self]; } } - (void) drawImmediate:(bool)immediate translucent:(bool)translucent { if (!translucent || [UNIVERSE breakPatternHide]) return; OO_ENTER_OPENGL(); OOSetOpenGLState(OPENGL_STATE_ADDITIVE_BLENDING); OOGL(glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT)); OOGL(glDisable(GL_CULL_FACE)); OOGL(glDisable(GL_TEXTURE_2D)); OOGL(glEnable(GL_BLEND)); OOGL(glBlendFunc(GL_SRC_ALPHA, GL_ONE)); OOGL(glColor4fv(_color)); OOGLBEGIN(GL_TRIANGLE_FAN); GLDrawBallBillboard(collision_radius, 4, sqrt(cam_zero_distance)); OOGLEND(); OOGL(glPopAttrib()); OOVerifyOpenGLState(); OOCheckOpenGLErrors(@"OOQuiriumCascadeEntity after drawing %@", self); } - (BOOL) isEffect { return YES; } - (BOOL) isCascadeWeapon { return YES; } - (BOOL) canCollide { return _timePassed > kQuiriumCollisionDelay; } - (BOOL) checkCloseCollisionWith:(Entity *)other { return YES; } @end @implementation Entity (OOQuiriumCascadeExtensions) - (BOOL) isCascadeWeapon { return NO; } @end oolite-1.82/src/Core/Entities/OORingEffectEntity.h000066400000000000000000000022571256642440500220400ustar00rootroot00000000000000/* OORingEffectEntity.h Entity subclass for expanding-ring effect, used for hyperspace entry and for some large explosions. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "Entity.h" @interface OORingEffectEntity: Entity { @private GLfloat _timePassed; GLfloat _innerRadius, _outerRadius, _innerGrowthRate, _outerGrowthRate; } + (instancetype) ringFromEntity:(Entity *)sourceEntity; + (instancetype) shrinkingRingFromEntity:(Entity *)sourceEntity; @end oolite-1.82/src/Core/Entities/OORingEffectEntity.m000066400000000000000000000103571256642440500220450ustar00rootroot00000000000000/* OORingEffectEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OORingEffectEntity.h" #import "Universe.h" #import "OOMacroOpenGL.h" #define kRingDuration (2.0f) // seconds #define kRingAttack (0.4f) // fade-up time // Dimensions and growth rates per second in terms of base size. #define kInnerRingInitialSizeFactor (0.5f) #define kOuterRingInitialSizeFactor (1.25f * kInnerRingInitialSizeFactor) #define kInnerRingGrowthRateFactor (1.1f * kInnerRingInitialSizeFactor) #define kOuterRingGrowthRateFactor (1.25f * kInnerRingInitialSizeFactor) // These factors produce a ring that shrinks to nothing, then expands to the size of a "normal" ring. #define kShrinkingRingInnerGrowthFactor (-2.5) #define kShrinkingRingOuterGrowthFactor (-2.0) enum { kCircleSegments = 65 }; static NSPoint sCircleVerts[kCircleSegments]; // holds vector coordinates for a unit circle @implementation OORingEffectEntity + (void)initialize { unsigned i; for (i = 0; i < kCircleSegments; i++) { sCircleVerts[i].x = sin(i * 2 * M_PI / (kCircleSegments - 1)); sCircleVerts[i].y = cos(i * 2 * M_PI / (kCircleSegments - 1)); } } - (id) initRingFromEntity:(Entity *)sourceEntity { if (sourceEntity == nil) { [self release]; return nil; } if ((self = [super init])) { GLfloat baseSize = [sourceEntity collisionRadius]; _innerRadius = baseSize * kInnerRingInitialSizeFactor; _outerRadius = baseSize * kOuterRingInitialSizeFactor; _innerGrowthRate = baseSize * kInnerRingGrowthRateFactor; _outerGrowthRate = baseSize * kOuterRingGrowthRateFactor; [self setPosition:[sourceEntity position]]; [self setOrientation:[sourceEntity orientation]]; [self setVelocity:[sourceEntity velocity]]; [self setStatus:STATUS_EFFECT]; [self setScanClass:CLASS_NO_DRAW]; [self setOwner:sourceEntity]; } return self; } + (instancetype) ringFromEntity:(Entity *)sourceEntity { return [[[self alloc] initRingFromEntity:sourceEntity] autorelease]; } + (instancetype) shrinkingRingFromEntity:(Entity *)sourceEntity { OORingEffectEntity *result = [self ringFromEntity:sourceEntity]; if (result != nil) { result->_innerGrowthRate *= kShrinkingRingInnerGrowthFactor; result->_outerGrowthRate *= kShrinkingRingOuterGrowthFactor; } return result; } - (NSString *) descriptionComponents { return [NSString stringWithFormat:@"%f seconds passed of %f", _timePassed, kRingDuration]; } - (void) update:(OOTimeDelta) delta_t { [super update:delta_t]; _timePassed += delta_t; _innerRadius += delta_t * _innerGrowthRate; _outerRadius += delta_t * _outerGrowthRate; if (_timePassed > kRingDuration) { [UNIVERSE removeEntity:self]; } } - (void) drawImmediate:(bool)immediate translucent:(bool)translucent { if (!translucent || [UNIVERSE breakPatternHide]) return; OO_ENTER_OPENGL(); OOSetOpenGLState(OPENGL_STATE_ADDITIVE_BLENDING); GLfloat alpha = OOClamp_0_1_f((kRingDuration - _timePassed) / kRingAttack); GLfloat ex_em_hi[4] = {0.6, 0.8, 1.0, alpha}; // pale blue GLfloat ex_em_lo[4] = {0.2, 0.0, 1.0, 0.0}; // purplish-blue-black OOGLBEGIN(GL_TRIANGLE_STRIP); for (unsigned i = 0; i < kCircleSegments; i++) { glColor4fv(ex_em_lo); glVertex3f(_innerRadius * sCircleVerts[i].x, _innerRadius * sCircleVerts[i].y, 0.0f); glColor4fv(ex_em_hi); glVertex3f(_outerRadius * sCircleVerts[i].x, _outerRadius * sCircleVerts[i].y, 0.0f); } OOGLEND(); OOVerifyOpenGLState(); OOCheckOpenGLErrors(@"OOQuiriumCascadeEntity after drawing %@", self); } - (BOOL) isEffect { return YES; } - (BOOL) canCollide { return NO; } @end oolite-1.82/src/Core/Entities/OOSparkEntity.h000066400000000000000000000021251256642440500210760ustar00rootroot00000000000000/* OOSparkEntity.h Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOLightParticleEntity.h" @interface OOSparkEntity: OOLightParticleEntity { @private GLfloat _baseRGBA[4]; GLfloat _duration, _timeRemaining; } - (id) initWithPosition:(HPVector)position velocity:(Vector)velocity duration:(OOTimeDelta)duration size:(float)size color:(OOColor *)color; @end oolite-1.82/src/Core/Entities/OOSparkEntity.m000066400000000000000000000037641256642440500211150ustar00rootroot00000000000000/* OOSparkEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOSparkEntity.h" #import "Universe.h" #import "PlayerEntity.h" #import "OOColor.h" @interface OOSparkEntity (Private) - (void) performUpdate:(OOTimeDelta)delta_t; @end @implementation OOSparkEntity - (id) initWithPosition:(HPVector)pos velocity:(Vector)vel duration:(OOTimeDelta)duration size:(float)size color:(OOColor *)color { if ((self = [super initWithDiameter:size])) { [self setPosition:pos]; [self setVelocity:vel]; _duration = _timeRemaining = duration; [self setCollisionRadius:2.0]; [color getRed:&_baseRGBA[0] green:&_baseRGBA[1] blue:&_baseRGBA[2] alpha:&_baseRGBA[3]]; [self performUpdate:0]; // Handle colour mixing and such. } return self; } - (void) update:(OOTimeDelta)delta_t { [super update:delta_t]; [self performUpdate:delta_t]; } - (void) performUpdate:(OOTimeDelta)delta_t { _timeRemaining -= delta_t; float mix = OOClamp_0_1_f(_timeRemaining / _duration); // Fade towards red while fading out. _colorComponents[0] = mix * _baseRGBA[0] + (1.0f - mix); _colorComponents[1] = mix * _baseRGBA[1]; _colorComponents[2] = mix * _baseRGBA[2]; _colorComponents[3] = mix * _baseRGBA[3]; // Disappear when gone. if (mix == 0) [UNIVERSE removeEntity:self]; } @end oolite-1.82/src/Core/Entities/OOStellarBody.h000066400000000000000000000030501256642440500210430ustar00rootroot00000000000000/* OOStellarBody.h Protocol shared by suns and planets (which used to be the same class). Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #import "OOWeakReference.h" #import "OOTypes.h" #import "OOMaths.h" #ifndef NEW_PLANETS // not for 1.77/8 #define NEW_PLANETS 1 #endif typedef enum { STELLAR_TYPE_SUN, STELLAR_TYPE_NORMAL_PLANET, // Terrestrial planet with atmosphere and oceans #if !NEW_PLANETS STELLAR_TYPE_ATMOSPHERE, #endif STELLAR_TYPE_MOON, // Rocky/airless planet STELLAR_TYPE_MINIATURE // Display proxy for a "normal" planet } OOStellarBodyType; #define ATMOSPHERE_DEPTH 500.0 #define PLANET_MINIATURE_FACTOR 0.00185 #define MAX_SUBDIVIDE 6 @protocol OOStellarBody - (double) radius; - (OOStellarBodyType) planetType; - (NSString *) name; - (void) setName:(NSString *)name; @end oolite-1.82/src/Core/Entities/OOSunEntity.h000066400000000000000000000050621256642440500205660ustar00rootroot00000000000000/* OOSunEntity.h Entity subclass representing a sun. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOPlanetEntity.h" #import "Entity.h" #import "legacy_random.h" #import "OOColor.h" #define SUN_CORONA_SAMPLES 729 // Samples at half-degree intervals, with a bit of overlap. #define MAX_CORONAFLARE 600000.0 // nova flare #ifndef SUN_DIRECT_VISION_GLARE #define SUN_DIRECT_VISION_GLARE 1 #endif #if SUN_DIRECT_VISION_GLARE #define SUN_GLARE_MULT_FACTOR 3.0 #define SUN_GLARE_ADD_FACTOR (M_PI/16.0) #define SUN_GLARE_CORONA_FACTOR 0.5 #else #define SUN_GLARE_CORONA_FACTOR 1.5 #endif @class ShipEntity; @interface OOSunEntity: Entity { @private GLfloat sun_diffuse[4]; GLfloat sun_specular[4]; GLfloat discColor[4]; GLfloat outerCoronaColor[4]; GLfloat cor16k, lim16k; double corona_speed_factor; // multiply delta_t by this before adding it to corona_stage double corona_stage; // 0.0 -> 1.0 GLfloat rvalue[SUN_CORONA_SAMPLES]; // stores random values for adjusting colors in the corona float corona_blending; GLuint sunTriangles[3240*3]; GLfloat sunVertices[1801*3]; GLfloat sunColors[1801*4]; OOTimeDelta _novaCountdown; OOTimeDelta _novaExpansionTimer; float _novaExpansionRate; NSString *_name; } - (id) initSunWithColor:(OOColor*)sun_color andDictionary:(NSDictionary*) dict; - (BOOL) setSunColor:(OOColor*)sun_color; - (BOOL) changeSunProperty:(NSString *)key withDictionary:(NSDictionary*) dict; - (OOStellarBodyType) planetType; - (void) getDiffuseComponents:(GLfloat[4])components; - (void) getSpecularComponents:(GLfloat[4])components; - (void) setRadius:(GLfloat) rad andCorona:(GLfloat)corona; - (BOOL) willGoNova; - (BOOL) goneNova; - (void) setGoingNova:(BOOL) yesno inTime:(double)interval; - (void) drawStarGlare; - (void) drawDirectVisionSunGlare; @end oolite-1.82/src/Core/Entities/OOSunEntity.m000066400000000000000000000500221256642440500205670ustar00rootroot00000000000000/* OOSunEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOSunEntity.h" #import "OOOpenGLExtensionManager.h" #import "OOMacroOpenGL.h" #import "Universe.h" #import "AI.h" #import "MyOpenGLView.h" #import "ShipEntityAI.h" #import "OOColor.h" #import "OOCharacter.h" #import "OOStringParsing.h" #import "PlayerEntity.h" #import "OOCollectionExtractors.h" #import "OODebugFlags.h" #import "OOStringExpander.h" @interface OOSunEntity (Private) - (void) calculateGLArrays:(GLfloat)inner_radius width:(GLfloat)width zDistance:(GLfloat)z_distance; - (void) drawOpaqueParts; - (void) drawTranslucentParts; @end @implementation OOSunEntity #ifndef NDEBUG - (id) init { assert(0); return nil; } #endif - (BOOL) setSunColor:(OOColor*)sun_color { if (sun_color == nil) return NO; OO_ENTER_OPENGL(); float hue, sat, bri, alf; OOColor *color = nil; [sun_color getHue:&hue saturation:&sat brightness:&bri alpha:&alf]; hue /= 360; /* // FIXME: do away with hue_drift altogether? // The following two lines are functionally identical to 1.73: randf();randf(); // avoid ranrot dirft! float hue_drift = 0.0f; */ // anything more than a minimal hue drift will wipe out the original colour. float hue_drift = 0.038f * fabs(randf() - randf()); // set the lighting color for the sun GLfloat r,g,b,a; [sun_color getRed:&r green:&g blue:&b alpha:&a]; GLfloat sun_ambient[] = { 0.0, 0.0, 0.0, 1.0}; // real ambient light inside gl_LightModel.ambient sun_diffuse[0] = 0.5 * (1.0 + r); // paler sun_diffuse[1] = 0.5 * (1.0 + g); // paler sun_diffuse[2] = 0.5 * (1.0 + b); // paler sun_diffuse[3] = 1.0; sun_specular[0] = r; sun_specular[1] = g; sun_specular[2] = b; sun_specular[3] = 1.0; OOGL(glLightfv(GL_LIGHT1, GL_AMBIENT, sun_ambient)); OOGL(glLightfv(GL_LIGHT1, GL_DIFFUSE, sun_diffuse)); OOGL(glLightfv(GL_LIGHT1, GL_SPECULAR, sun_specular)); // main disc less saturation more brightness color = [OOColor colorWithHue:hue saturation:sat * 0.333f brightness:1.0f alpha:1.0f]; [color getRed:&discColor[0] green:&discColor[1] blue:&discColor[2] alpha:&discColor[3]]; /* Two inner corona layers with low alpha and saturation are additively blended with main corona. This produces something vaguely like a bloom effect. */ hue += hue_drift * 3; // saturation = 1 would shift white to red color = [OOColor colorWithHue:hue saturation:OOClamp_0_1_f(sat*1.0f) brightness:bri * 0.75f alpha:0.45f]; [color getRed:&outerCoronaColor[0] green:&outerCoronaColor[1] blue:&outerCoronaColor[2] alpha:&outerCoronaColor[3]]; return YES; } - (id) initSunWithColor:(OOColor *)sun_color andDictionary:(NSDictionary *) dict { int i; self = [super init]; collision_radius = 100000.0; // 100km across scanClass = CLASS_NO_DRAW; [self setSunColor:sun_color]; [self setName:OOExpand([dict oo_stringForKey:KEY_SUNNAME defaultValue:@"[oolite-default-star-name]"])]; corona_blending=OOClamp_0_1_f([dict oo_floatForKey:@"corona_hues" defaultValue:1.0f]); corona_speed_factor=[dict oo_floatForKey:@"corona_shimmer" defaultValue:-1.0]; if(corona_speed_factor<0) { // from .22222 to 2 corona_speed_factor = 1.0 / (0.5 + 2.0 * (randf() + randf())); } else { //on average: 0 = .25 , 1 = 2.25 - the same sun should give the same random component corona_speed_factor=OOClamp_0_1_f(corona_speed_factor) * 2.0 + randf() * randf(); } #ifdef OO_DUMP_PLANETINFO OOLog(@"planetinfo.record",@"corona_shimmer = %f",corona_speed_factor); #endif corona_stage = 0.0; for (i = 0; i < SUN_CORONA_SAMPLES; i++) rvalue[i] = randf(); // set up the radius properties [self changeSunProperty:@"sun_radius" withDictionary:dict]; unsigned k = 0; for (unsigned i=0 ; i < 360 ; i++) { unsigned j = (i+1)%360; // disc sunTriangles[k++] = 0; sunTriangles[k++] = 1+i; sunTriangles[k++] = 1+j; } for (unsigned i=0 ; i < 360 ; i++) { unsigned j = (i+1)%360; // ring 1 sunTriangles[k++] = 1+i; sunTriangles[k++] = 1+j; sunTriangles[k++] = 361+i; sunTriangles[k++] = 1+j; sunTriangles[k++] = 361+i; sunTriangles[k++] = 361+j; // ring 2 sunTriangles[k++] = 361+i; sunTriangles[k++] = 361+j; sunTriangles[k++] = 721+i; sunTriangles[k++] = 361+j; sunTriangles[k++] = 721+i; sunTriangles[k++] = 721+j; // ring 3 sunTriangles[k++] = 721+i; sunTriangles[k++] = 721+j; sunTriangles[k++] = 1081+i; sunTriangles[k++] = 721+j; sunTriangles[k++] = 1081+i; sunTriangles[k++] = 1081+j; // ring 4 sunTriangles[k++] = 1081+i; sunTriangles[k++] = 1081+j; sunTriangles[k++] = 1441+i; sunTriangles[k++] = 1081+j; sunTriangles[k++] = 1441+i; sunTriangles[k++] = 1441+j; } return self; } - (void) dealloc { DESTROY(_name); [super dealloc]; } - (NSString*) descriptionComponents { NSString *result = [NSString stringWithFormat:@"ID: %u position: %@ radius: %.3fkm", [self universalID], HPVectorDescription([self position]), 0.001 * [self radius]]; if ([self goneNova]) { result = [result stringByAppendingString:@" (gone nova)"]; } else if ([self willGoNova]) { result = [result stringByAppendingString:@" (will go nova)"]; } return result; } - (BOOL) canCollide { return YES; } #ifndef NDEBUG - (BOOL) checkCloseCollisionWith:(Entity *)other { if (gDebugFlags & DEBUG_COLLISIONS) { OOLog(@"sun.collide", @"SUN Collision!"); } return [super checkCloseCollisionWith:other]; } #endif - (void) update:(OOTimeDelta) delta_t { [super update:delta_t]; PlayerEntity *player = PLAYER; assert(player != nil); rotMatrix = OOMatrixForBillboard(position, [player viewpointPosition]); if (throw_sparks && _novaExpansionRate > 0) // going NOVA! { if (_novaCountdown >= 0.0) // countdown { _novaCountdown -= delta_t; if (corona_speed_factor < 5.0) { corona_speed_factor += 0.75 * delta_t; } } else { if (_novaExpansionTimer <= 60.0) // expand for a minute { double sky_bri = 1.0 - 1.5 * _novaExpansionTimer; if (sky_bri < 0) { [UNIVERSE setSkyColorRed:0.0f // back to black green:0.0f blue:0.0f alpha:0.0f]; } else { [UNIVERSE setSkyColorRed:sky_bri // whiteout green:sky_bri blue:sky_bri alpha:1.0f]; } if (sky_bri == 1.0) { // This sun has now gone nova! [UNIVERSE setSystemDataKey:@"sun_gone_nova" value:[NSNumber numberWithBool:YES] fromManifest:@"org.oolite.oolite"]; [UNIVERSE setSystemDataKey:@"corona_flare" value:[NSNumber numberWithFloat:0.3] fromManifest:@"org.oolite.oolite"]; [UNIVERSE setSystemDataKey:@"corona_hues" value:[NSNumber numberWithFloat:0.05] fromManifest:@"org.oolite.oolite"]; // Novas are stored under the core manifest if the // player was there at the time. Default layer 2 // is fine. OOLog(@"sun.nova.start", @"DEBUG: NOVA original radius %.1f", collision_radius); } discColor[0] = 1.0; discColor[1] = 1.0; discColor[2] = 1.0; _novaExpansionTimer += delta_t; [UNIVERSE setSystemDataKey:@"sun_radius" value:[NSNumber numberWithFloat:collision_radius + delta_t * _novaExpansionRate] fromManifest:@"org.oolite.oolite"]; } else { OOLog(@"sun.nova.end", @"DEBUG: NOVA final radius %.1f", collision_radius); // reset at the new size _novaCountdown = 0.0; _novaExpansionTimer = 0.0; _novaExpansionRate = 0.0f; throw_sparks = YES; // keep throw_sparks at YES to indicate the higher temperature } } } // update corona if (![UNIVERSE reducedDetail]) { corona_stage += corona_speed_factor * delta_t; while (corona_stage > 1.0) { int i; corona_stage -= 1.0; for (i = 0; i < 360; i++) { rvalue[i] = rvalue[360 + i]; rvalue[360 + i] = randf(); } } } } - (void) drawImmediate:(bool)immediate translucent:(bool)translucent { if (![UNIVERSE breakPatternHide]) { if (translucent) { // nothing... } else { [self drawOpaqueParts]; /* Despite the side effects, we have to draw the translucent * parts on the opaque pass. Planets, at long range, aren't * depth-buffered. So if the translucent parts are drawn on the * translucent pass, they appear in front of planets they are * actually behind. Telabe in G3 is a good one to test with if * you have any clever ideas. * * - CIM 8/7/2013 */ [self drawTranslucentParts]; } } } - (void) updateCameraRelativePosition { HPVector cr_temp = HPvector_subtract([self absolutePositionForSubentity],[PLAYER viewpointPosition]); /* Special calculation as suns viewed over ~1E9 - and the bigger * ones are still just about visible at this range - get floating * point errors messing up the display */ if (EXPECT_NOT(HPmagnitude2(cr_temp) > 1E18)) { cr_temp = HPvector_multiply_scalar(cr_temp,1E9/HPmagnitude(cr_temp)); } cameraRelativePosition = HPVectorToVector(cr_temp); } - (void) drawOpaqueParts { float sqrt_zero_distance = sqrt(cam_zero_distance); float effective_radius = collision_radius; float effective_cor16k = cor16k; /* At very long ranges the floating point inaccuracies make a * complete mess of the calculations, so if the sun is more than * 1E9 away, draw it closer but smaller. Painter's algorithm * should stop oddities with planets transiting it */ float large_distance_compensator = sqrt_zero_distance / 1000000000.0; //1E9 if (large_distance_compensator > 1.0) { sqrt_zero_distance /= large_distance_compensator; effective_radius /= large_distance_compensator; effective_cor16k /= large_distance_compensator; } OO_ENTER_OPENGL(); OOSetOpenGLState(OPENGL_STATE_ADDITIVE_BLENDING); if ([UNIVERSE reducedDetail]) { int subdivideLevel = 2; // 4 is probably the maximum! float drawFactor = [[UNIVERSE gameView] viewSize].width / 100.0; float drawRatio2 = drawFactor * effective_radius / sqrt_zero_distance; // equivalent to size on screen in pixels if (cam_zero_distance > 0.0) { subdivideLevel = 2 + floor(drawRatio2); if (subdivideLevel > 4) subdivideLevel = 4; } /* The depth test gets disabled in parts of this and instead we rely on the painters algorithm instead. The depth buffer isn't granular enough to cope with huge objects at vast distances. */ BOOL ignoreDepthBuffer = cam_zero_distance > effective_radius * effective_radius * 25; int steps = 2 * (MAX_SUBDIVIDE - subdivideLevel); // Close enough not to draw flat? if (ignoreDepthBuffer) OOGL(glDisable(GL_DEPTH_TEST)); OOGL(glColor3fv(discColor)); // FIXME: use vertex arrays OOGL(glDisable(GL_BLEND)); OOGLBEGIN(GL_TRIANGLE_FAN); GLDrawBallBillboard(effective_radius, steps, sqrt_zero_distance); OOGLEND(); OOGL(glEnable(GL_BLEND)); if (ignoreDepthBuffer) OOGL(glEnable(GL_DEPTH_TEST)); } else { [self calculateGLArrays:effective_radius width:effective_cor16k zDistance:sqrt_zero_distance]; OOGL(glDisable(GL_BLEND)); OOGL(glVertexPointer(3, GL_FLOAT, 0, sunVertices)); OOGL(glEnableClientState(GL_COLOR_ARRAY)); OOGL(glColorPointer(4, GL_FLOAT, 0, sunColors)); OOGL(glDrawElements(GL_TRIANGLES, 3*360, GL_UNSIGNED_INT, sunTriangles)); OOGL(glDisableClientState(GL_COLOR_ARRAY)); OOGL(glEnable(GL_BLEND)); } OOVerifyOpenGLState(); OOCheckOpenGLErrors(@"SunEntity after drawing %@", self); } - (void) drawTranslucentParts { if ([UNIVERSE reducedDetail]) { return; } OO_ENTER_OPENGL(); OOSetOpenGLState(OPENGL_STATE_ADDITIVE_BLENDING); OOGL(glVertexPointer(3, GL_FLOAT, 0, sunVertices)); OOGL(glEnableClientState(GL_COLOR_ARRAY)); OOGL(glColorPointer(4, GL_FLOAT, 0, sunColors)); OOGL(glDrawElements(GL_TRIANGLES, 24*360, GL_UNSIGNED_INT, sunTriangles+(3*360))); OOGL(glDisableClientState(GL_COLOR_ARRAY)); } - (void) calculateGLArrays:(GLfloat)inner_radius width:(GLfloat)width zDistance:(GLfloat)z_distance { // if (EXPECT_NOT(inner_radius >= z_distance)) return; // inside the sphere GLfloat activity[8] = {0.84, 0.74, 0.64, 0.54, 0.3 , 0.4 , 0.7 , 0.8}; GLfloat si, ci; GLfloat rv0, rv1, rv2, c0, c1, c2; GLfloat pt0, pt1; unsigned short i, j, k; GLfloat theta = 0.0f, delta; delta = M_PI / 180.0f; // Convert step from degrees to radians pt0=(1.0 - corona_stage) * corona_blending; pt1=corona_stage * corona_blending; sunVertices[0] = 0.0; sunVertices[1] = 0.0; sunVertices[2] = 0.0; k = 3; for (j = 0 ; j <= 4 ; j++) { GLfloat r = inner_radius; switch (j) { case 4: r += width; break; case 3: r += width/1.5; break; case 2: r += width/3.0; break; case 1: r += width/15.0; break; } theta = 0.0; for (i = 0 ; i < 360 ; i++) { GLfloat rm = 1.0; if (j >= 1 && j < 4) { rm = 1.0 + ((0.04/j)*(pt0 * (rvalue[i]+rvalue[i+1]+rvalue[i+2]) + pt1 * (rvalue[i+360]+rvalue[i+361]+rvalue[i+362])))/3; } GLfloat z = r * r * rm * rm / z_distance; si = sin(theta); ci = cos(theta); theta += delta; sunVertices[k++] = si * r * rm; sunVertices[k++] = ci * r * rm; sunVertices[k++] = -z; } } GLfloat blackColor[4] = {0.0,0.0,0.0,0.0}; GLfloat *color = blackColor; GLfloat alpha = 0.0; k=0; sunColors[k++] = discColor[0]; sunColors[k++] = discColor[1]; sunColors[k++] = discColor[2]; sunColors[k++] = discColor[3]; for (j = 0 ; j <= 4 ; j++) { switch (j) { case 4: color = blackColor; alpha = 0.0; break; case 3: color = outerCoronaColor; alpha = 0.1; break; case 2: color = outerCoronaColor; alpha = 0.6; break; case 1: color = discColor; alpha = 0.95; break; case 0: color = discColor; alpha = 1.0; break; } for (i = 0 ; i < 360 ; i++) { if (j == 0) { sunColors[k++] = color[0]; sunColors[k++] = color[1]; sunColors[k++] = color[2]; sunColors[k++] = 1.0; } else { rv0 = pt0 * rvalue[i] + pt1 * rvalue[i + 360]; rv1 = pt0 * rvalue[i + 1] + pt1 * rvalue[i + 361]; rv2 = pt0 * rvalue[i + 2] + pt1 * rvalue[i + 362]; c0 = color[0] * (activity[j-1] + rv0*activity[j+3]); c1 = color[1] * (activity[j-1] + rv1*activity[j+3]); c2 = color[2] * (activity[j-1] + rv2*activity[j+3]); if (c1 > c2 && c1 > c0) { c1 = fmaxf(c0,c2); } sunColors[k++] = c0; sunColors[k++] = c1; sunColors[k++] = c2; sunColors[k++] = alpha; } } } } - (void) drawDirectVisionSunGlare { #if SUN_DIRECT_VISION_GLARE OO_ENTER_OPENGL(); OOSetOpenGLState(OPENGL_STATE_OVERLAY); GLfloat sunGlareAngularSize = atan([self radius]/HPdistance([PLAYER viewpointPosition], [self position])) * SUN_GLARE_MULT_FACTOR + (SUN_GLARE_ADD_FACTOR); GLfloat directVisionSunGlare = [PLAYER lookingAtSunWithThresholdAngleCos:cos(sunGlareAngularSize)]; if (directVisionSunGlare) { NSSize siz = [[UNIVERSE gui] size]; MyOpenGLView *gameView = [UNIVERSE gameView]; GLfloat aspectRatio = ([gameView viewSize].width / [gameView viewSize].height); GLfloat z = [gameView display_z] / (aspectRatio > 4.0/3.0 ? aspectRatio : 1.0 / aspectRatio); GLfloat atmosphericReductionFactor = 1.0f - [PLAYER insideAtmosphereFraction]; // 182: square of ratio of radius to sun-witchpoint distance // in default Lave GLfloat distanceReductionFactor = OOClamp_0_1_f(([self radius] * [self radius] * 182.0) / HPdistance2([PLAYER position], [self position])); GLfloat sunGlareFilterMultiplierLocal = [PLAYER sunGlareFilter]; GLfloat directVisionSunGlareColor[4] = {discColor[0], discColor[1], discColor[2], directVisionSunGlare * atmosphericReductionFactor * distanceReductionFactor * (1.0f - sunGlareFilterMultiplierLocal) * 0.85f}; OOGL(glColor4fv(directVisionSunGlareColor)); OOGLBEGIN(GL_QUADS); glVertex3f(siz.width, siz.height, z); glVertex3f(siz.width, -siz.height, z); glVertex3f(-siz.width, -siz.height, z); glVertex3f(-siz.width, siz.height, z); OOGLEND(); } #endif } - (void) drawStarGlare { OO_ENTER_OPENGL(); OOSetOpenGLState(OPENGL_STATE_OVERLAY); float sqrt_zero_distance = sqrt(cam_zero_distance); double alt = sqrt_zero_distance - collision_radius; if (EXPECT_NOT(alt < 0)) { return; } double corona = cor16k/SUN_GLARE_CORONA_FACTOR; if (corona > alt) { double alpha = 1-(alt/corona); GLfloat glareColor[4] = {discColor[0], discColor[1], discColor[2], alpha}; NSSize siz = [[UNIVERSE gui] size]; MyOpenGLView *gameView = [UNIVERSE gameView]; GLfloat aspectRatio = ([gameView viewSize].width / [gameView viewSize].height); GLfloat z = [gameView display_z] / (aspectRatio > 4.0/3.0 ? aspectRatio : 1.0 / aspectRatio); OOGL(glColor4fv(glareColor)); OOGLBEGIN(GL_QUADS); glVertex3f(siz.width, siz.height, z); glVertex3f(siz.width, -siz.height, z); glVertex3f(-siz.width, -siz.height, z); glVertex3f(-siz.width, siz.height, z); OOGLEND(); } } - (BOOL) changeSunProperty:(NSString *)key withDictionary:(NSDictionary*) dict { id object = [dict objectForKey:key]; static GLfloat oldRadius = 0.0; if ([key isEqualToString:@"sun_radius"]) { oldRadius = [object doubleValue]; // clamp corona_flare in case planetinfo.plist / savegame contains the wrong value [self setRadius:oldRadius andCorona:[dict oo_floatForKey:@"corona_flare" defaultValue:0.0f]]; } else if ([key isEqualToString:KEY_SUNNAME]) { [self setName:[dict oo_stringForKey:KEY_SUNNAME]]; } else if ([key isEqualToString:@"corona_flare"]) { [self setRadius:collision_radius andCorona:[object floatValue]]; } else if ([key isEqualToString:@"corona_shimmer"]) { corona_speed_factor=OOClamp_0_1_f([object floatValue]) * 2.0 + randf() * randf(); } else if ([key isEqualToString:@"corona_hues"]) { corona_blending=OOClamp_0_1_f([object floatValue]); } else if ([key isEqualToString:@"sun_gone_nova"]) { if ([dict oo_boolForKey:key]) { [self setGoingNova:YES inTime:0]; } else { [self setGoingNova:NO inTime:0]; // oldRadius is always the radius we had before going nova... [self setRadius: oldRadius andCorona:[dict oo_floatForKey:@"corona_flare" defaultValue:0.0f]]; } } else { OOLogWARN(@"script.warning", @"Change to property '%@' not applied, will apply only after leaving this system.",key); return NO; } return YES; } - (OOStellarBodyType) planetType { return STELLAR_TYPE_SUN; } - (void) getDiffuseComponents:(GLfloat[4])components { NSParameterAssert(components != NULL); memcpy(components, sun_diffuse, sizeof sun_diffuse); } - (void) getSpecularComponents:(GLfloat[4])components { NSParameterAssert(components != NULL); memcpy(components, sun_specular, sizeof sun_specular); } - (double) radius { return collision_radius; } - (void) setRadius:(GLfloat) rad andCorona:(GLfloat)corona { collision_radius = rad; if (corona < 0.01f) { corona = 0.01f; } cor16k = rad * 8 * corona; GLfloat corouter = rad * (1+(8*corona)); lim16k = corouter * corouter * NO_DRAW_DISTANCE_FACTOR*NO_DRAW_DISTANCE_FACTOR; } - (void) setPosition:(HPVector) posn { [super setPosition: posn]; [UNIVERSE setMainLightPosition: HPVectorToVector(posn)]; } - (BOOL) willGoNova { return throw_sparks; } - (BOOL) goneNova { return throw_sparks && _novaCountdown <= 0; } - (void) setGoingNova:(BOOL) yesno inTime:(double)interval { throw_sparks = yesno; if (throw_sparks) { _novaCountdown = fmax(interval, 0.0); OOLog(@"script.debug.setSunNovaIn", @"NOVA activated! time until Nova : %.1f s", _novaCountdown); } _novaExpansionTimer = 0; _novaExpansionRate = 10000; } - (BOOL) isSun { return YES; } - (BOOL) isVisible { return YES; } - (NSString *) name { return _name; } - (void) setName:(NSString *)name { [_name release]; _name = [name retain]; } @end oolite-1.82/src/Core/Entities/OOVisualEffectEntity.h000066400000000000000000000077061256642440500224100ustar00rootroot00000000000000/* OOVisualEffectEntity.h Entity subclass representing a visual effect with a custom mesh Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOEntityWithDrawable.h" #import "OOPlanetEntity.h" #import "OOJSPropID.h" #import "HeadUpDisplay.h" #import "OOWeakReference.h" @class OOColor, OOMesh, OOScript, OOJSScript; @interface OOVisualEffectEntity: OOEntityWithDrawable { @private NSMutableArray *subEntities; NSDictionary *effectinfoDictionary; GLfloat _profileRadius; // for frustum culling OOColor *scanner_display_color1; OOColor *scanner_display_color2; GLfloat _hullHeatLevel; GLfloat _shaderFloat1; GLfloat _shaderFloat2; int _shaderInt1; int _shaderInt2; Vector _shaderVector1; Vector _shaderVector2; Vector _v_forward; Vector _v_up; Vector _v_right; OOJSScript *script; NSDictionary *scriptInfo; NSString *_effectKey; BOOL _haveExecutedSpawnAction; // beacons NSString *_beaconCode; NSString *_beaconLabel; OOWeakReference *_prevBeacon; OOWeakReference *_nextBeacon; id _beaconDrawable; // scaling GLfloat scaleX; GLfloat scaleY; GLfloat scaleZ; } - (id)initWithKey:(NSString *)key definition:(NSDictionary *) dict; - (BOOL) setUpVisualEffectFromDictionary:(NSDictionary *) effectDict; - (OOMesh *)mesh; - (void)setMesh:(OOMesh *)mesh; - (NSString *)effectKey; - (GLfloat)frustumRadius; - (void) clearSubEntities; - (BOOL) setUpSubEntities; - (void) removeSubEntity:(Entity *)sub; - (void) setNoDrawDistance; - (NSArray *)subEntities; - (NSUInteger) subEntityCount; - (NSEnumerator *) visualEffectSubEntityEnumerator; - (BOOL) hasSubEntity:(Entity *)sub; - (NSEnumerator *)subEntityEnumerator; - (NSEnumerator *)effectSubEntityEnumerator; - (NSEnumerator *)flasherEnumerator; - (void) orientationChanged; - (Vector) forwardVector; - (Vector) rightVector; - (Vector) upVector; - (OOColor *)scannerDisplayColor1; - (OOColor *)scannerDisplayColor2; - (void)setScannerDisplayColor1:(OOColor *)color; - (void)setScannerDisplayColor2:(OOColor *)color; - (GLfloat *) scannerDisplayColorForShip:(BOOL)flash :(OOColor *)scannerDisplayColor1 :(OOColor *)scannerDisplayColor2; - (void) setScript:(NSString *)script_name; - (OOJSScript *)script; - (NSDictionary *)scriptInfo; - (void) doScriptEvent:(jsid)message; - (void) remove; - (GLfloat) scaleMax; // used for calculating frustum cull size - (GLfloat) scaleX; - (void) setScaleX:(GLfloat)factor; - (GLfloat) scaleY; - (void) setScaleY:(GLfloat)factor; - (GLfloat) scaleZ; - (void) setScaleZ:(GLfloat)factor; // convenience for shaders - (GLfloat)hullHeatLevel; - (void)setHullHeatLevel:(GLfloat)value; // shader properties - (GLfloat) shaderFloat1; - (void)setShaderFloat1:(GLfloat)value; - (GLfloat) shaderFloat2; - (void)setShaderFloat2:(GLfloat)value; - (int) shaderInt1; - (void)setShaderInt1:(int)value; - (int) shaderInt2; - (void)setShaderInt2:(int)value; - (Vector) shaderVector1; - (void)setShaderVector1:(Vector)value; - (Vector) shaderVector2; - (void)setShaderVector2:(Vector)value; - (BOOL) isBreakPattern; - (void) setIsBreakPattern:(BOOL)bp; - (NSDictionary *)effectInfoDictionary; @end oolite-1.82/src/Core/Entities/OOVisualEffectEntity.m000066400000000000000000000450131256642440500224060ustar00rootroot00000000000000/* OOVisualEffectEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the impllied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOVisualEffectEntity.h" #import "OOMaths.h" #import "Universe.h" #import "OOShaderMaterial.h" #import "OOOpenGLExtensionManager.h" #import "ResourceManager.h" #import "OOStringParsing.h" #import "OOStringExpander.h" #import "OOCollectionExtractors.h" #import "OOConstToString.h" #import "OOConstToJSString.h" #import "OOMesh.h" #import "OOColor.h" #import "OOPolygonSprite.h" #import "OOFlasherEntity.h" #import "OODebugGLDrawing.h" #import "OODebugFlags.h" #import "OOJSScript.h" #import "OOFilteringEnumerator.h" #import "MyOpenGLView.h" @interface OOVisualEffectEntity (Private) - (void) drawSubEntityImmediate:(bool)immediate translucent:(bool)translucent; - (void) addSubEntity:(Entity *) subent; - (BOOL) setUpOneSubentity:(NSDictionary *) subentDict; - (BOOL) setUpOneFlasher:(NSDictionary *) subentDict; - (BOOL) setUpOneStandardSubentity:(NSDictionary *)subentDict; @end @implementation OOVisualEffectEntity - (id) init { return [self initWithKey:@"" definition:nil]; } - (id)initWithKey:(NSString *)key definition:(NSDictionary *)dict { OOJS_PROFILE_ENTER NSParameterAssert(dict != nil); self = [super init]; if (self == nil) return nil; _effectKey = [key retain]; if (![self setUpVisualEffectFromDictionary:dict]) { [self release]; self = nil; } _haveExecutedSpawnAction = NO; return self; OOJS_PROFILE_EXIT } - (BOOL) setUpVisualEffectFromDictionary:(NSDictionary *) effectDict { OOJS_PROFILE_ENTER effectinfoDictionary = [effectDict copy]; if (effectinfoDictionary == nil) effectinfoDictionary = [[NSDictionary alloc] init]; orientation = kIdentityQuaternion; rotMatrix = kIdentityMatrix; collision_radius = 0.0; NSString *modelName = [effectDict oo_stringForKey:@"model"]; if (modelName != nil) { OOMesh *mesh = [OOMesh meshWithName:modelName cacheKey:_effectKey materialDictionary:[effectDict oo_dictionaryForKey:@"materials"] shadersDictionary:[effectDict oo_dictionaryForKey:@"shaders"] smooth:[effectDict oo_boolForKey:@"smooth" defaultValue:NO] shaderMacros:OODefaultShipShaderMacros() shaderBindingTarget:self]; if (mesh == nil) return NO; [self setMesh:mesh]; } isImmuneToBreakPatternHide = [effectDict oo_boolForKey:@"is_break_pattern"]; scaleX = 1.0; scaleY = 1.0; scaleZ = 1.0; [self clearSubEntities]; [self setUpSubEntities]; [self setScannerDisplayColor1:nil]; [self setScannerDisplayColor2:nil]; scanClass = CLASS_VISUAL_EFFECT; [self setStatus:STATUS_EFFECT]; _hullHeatLevel = 60.0 / 256.0; _shaderFloat1 = 0.0; _shaderFloat2 = 0.0; _shaderInt1 = 0; _shaderInt2 = 0; _shaderVector1 = kZeroVector; _shaderVector2 = kZeroVector; [self setBeaconCode:[effectDict oo_stringForKey:@"beacon"]]; [self setBeaconLabel:[effectDict oo_stringForKey:@"beacon_label" defaultValue:[self beaconCode]]]; scriptInfo = [[effectDict oo_dictionaryForKey:@"script_info" defaultValue:nil] retain]; [self setScript:[effectDict oo_stringForKey:@"script"]]; return YES; OOJS_PROFILE_EXIT } - (void) dealloc { [self clearSubEntities]; DESTROY(_effectKey); DESTROY(effectinfoDictionary); DESTROY(scanner_display_color1); DESTROY(scanner_display_color2); DESTROY(scriptInfo); DESTROY(script); DESTROY(_beaconCode); DESTROY(_beaconLabel); DESTROY(_beaconDrawable); [super dealloc]; } - (BOOL) isEffect { return YES; } - (BOOL) isVisualEffect { return YES; } - (BOOL) canCollide { return NO; } - (OOMesh *)mesh { return (OOMesh *)[self drawable]; } - (void)setMesh:(OOMesh *)mesh { if (mesh != [self mesh]) { [self setDrawable:mesh]; } } - (NSString *)effectKey { return _effectKey; } - (GLfloat)frustumRadius { return [self scaleMax] * _profileRadius; } - (void) clearSubEntities { [subEntities makeObjectsPerformSelector:@selector(setOwner:) withObject:nil]; // Ensure backlinks are broken [subEntities release]; subEntities = nil; // reset size & mass! if ([self mesh]) { collision_radius = [self findCollisionRadius]; } else { collision_radius = 0.0; } _profileRadius = collision_radius; } - (BOOL)setUpSubEntities { unsigned int i; _profileRadius = collision_radius; NSArray *subs = [effectinfoDictionary oo_arrayForKey:@"subentities"]; for (i = 0; i < [subs count]; i++) { [self setUpOneSubentity:[subs oo_dictionaryAtIndex:i]]; } [self setNoDrawDistance]; return YES; } - (void) removeSubEntity:(Entity *)sub { [sub setOwner:nil]; [subEntities removeObject:sub]; } - (void) setNoDrawDistance { GLfloat r = _profileRadius * [self scaleMax]; no_draw_distance = r * r * NO_DRAW_DISTANCE_FACTOR * NO_DRAW_DISTANCE_FACTOR * 2.0; } - (BOOL) setUpOneSubentity:(NSDictionary *) subentDict { NSString *type = [subentDict oo_stringForKey:@"type"]; if ([type isEqualToString:@"flasher"]) { return [self setUpOneFlasher:subentDict]; } else { return [self setUpOneStandardSubentity:subentDict]; } } - (BOOL) setUpOneFlasher:(NSDictionary *) subentDict { OOFlasherEntity *flasher = [OOFlasherEntity flasherWithDictionary:subentDict]; [flasher setPosition:[subentDict oo_hpvectorForKey:@"position"]]; [self addSubEntity:flasher]; return YES; } - (BOOL) setUpOneStandardSubentity:(NSDictionary *)subentDict { OOVisualEffectEntity *subentity = nil; NSString *subentKey = nil; HPVector subPosition; Quaternion subOrientation; subentKey = [subentDict oo_stringForKey:@"subentity_key"]; if (subentKey == nil) { OOLog(@"setup.visualeffect.badEntry.subentities",@"Failed to set up entity - no subentKey in %@",subentDict); return NO; } subentity = [UNIVERSE newVisualEffectWithName:subentKey]; if (subentity == nil) { OOLog(@"setup.visualeffect.badEntry.subentities",@"Failed to set up entity %@",subentKey); return NO; } subPosition = [subentDict oo_hpvectorForKey:@"position"]; subOrientation = [subentDict oo_quaternionForKey:@"orientation"]; [subentity setPosition:subPosition]; [subentity setOrientation:subOrientation]; [self addSubEntity:subentity]; [subentity release]; return YES; } - (void) addSubEntity:(Entity *)sub { if (sub == nil) return; if (subEntities == nil) subEntities = [[NSMutableArray alloc] init]; sub->isSubEntity = YES; // Order matters - need consistent state in setOwner:. -- Ahruman 2008-04-20 [subEntities addObject:sub]; [sub setOwner:self]; double distance = HPmagnitude([sub position]) + [sub findCollisionRadius]; if (distance > _profileRadius) { _profileRadius = distance; } } - (NSArray *)subEntities { return [[subEntities copy] autorelease]; } - (NSUInteger) subEntityCount { return [subEntities count]; } - (NSEnumerator *) visualEffectSubEntityEnumerator { return [[self subEntities] objectEnumeratorFilteredWithSelector:@selector(isVisualEffect)]; } - (BOOL) hasSubEntity:(Entity *)sub { return [subEntities containsObject:sub]; } - (NSEnumerator *)subEntityEnumerator { return [[self subEntities] objectEnumerator]; } - (NSEnumerator *)effectSubEntityEnumerator { return [[self subEntities] objectEnumeratorFilteredWithSelector:@selector(isVisualEffect)]; } - (NSEnumerator *)flasherEnumerator { return [[self subEntities] objectEnumeratorFilteredWithSelector:@selector(isFlasher)]; } - (void) drawSubEntityImmediate:(bool)immediate translucent:(bool)translucent { if (cam_zero_distance > no_draw_distance) // this test provides an opportunity to do simple LoD culling { return; // TOO FAR AWAY } OOGLPushModelView(); // HPVect: camera position OOGLTranslateModelView(HPVectorToVector(position)); OOGLMultModelView(rotMatrix); [self drawImmediate:immediate translucent:translucent]; OOGLPopModelView(); } - (void) rescaleBy:(GLfloat)factor { if ([self mesh] != nil) { [self setMesh:[[self mesh] meshRescaledBy:factor]]; } // rescale subentities Entity *se = nil; foreach (se, [self subEntities]) { [se setPosition:HPvector_multiply_scalar([se position], factor)]; [se rescaleBy:factor]; } collision_radius *= factor; _profileRadius *= factor; } - (GLfloat) scaleMax { GLfloat scale = 1.0; if (scaleX > scaleY) { if (scaleX > scaleZ) { scale *= scaleX; } else { scale *= scaleZ; } } else if (scaleY > scaleZ) { scale *= scaleY; } else { scale *= scaleZ; } return scale; } - (GLfloat) scaleX { return scaleX; } - (void) setScaleX:(GLfloat)factor { // rescale subentities Entity *se = nil; GLfloat flasher_factor = pow(factor/scaleX,1.0/3.0); foreach (se, [self subEntities]) { HPVector move = [se position]; move.x *= factor/scaleX; [se setPosition:move]; if ([se isVisualEffect]) { [(OOVisualEffectEntity*)se setScaleX:factor]; } else { [se rescaleBy:flasher_factor]; } } scaleX = factor; [self setNoDrawDistance]; } - (GLfloat) scaleY { return scaleY; } - (void) setScaleY:(GLfloat)factor { // rescale subentities Entity *se = nil; GLfloat flasher_factor = pow(factor/scaleY,1.0/3.0); foreach (se, [self subEntities]) { HPVector move = [se position]; move.y *= factor/scaleY; [se setPosition:move]; if ([se isVisualEffect]) { [(OOVisualEffectEntity*)se setScaleY:factor]; } else { [se rescaleBy:flasher_factor]; } } scaleY = factor; [self setNoDrawDistance]; } - (GLfloat) scaleZ { return scaleZ; } - (void) setScaleZ:(GLfloat)factor { // rescale subentities Entity *se = nil; GLfloat flasher_factor = pow(factor/scaleZ,1.0/3.0); foreach (se, [self subEntities]) { HPVector move = [se position]; move.z *= factor/scaleZ; [se setPosition:move]; if ([se isVisualEffect]) { [(OOVisualEffectEntity*)se setScaleZ:factor]; } else { [se rescaleBy:flasher_factor]; } } scaleZ = factor; [self setNoDrawDistance]; } - (GLfloat) collisionRadius { return [self scaleMax] * collision_radius; } - (void) orientationChanged { [super orientationChanged]; _v_forward = vector_forward_from_quaternion(orientation); _v_up = vector_up_from_quaternion(orientation); _v_right = vector_right_from_quaternion(orientation); } // exposed to shaders - (Vector) forwardVector { return _v_forward; } // exposed to shaders - (Vector) upVector { return _v_up; } // exposed to shaders - (Vector) rightVector { return _v_right; } - (OOColor *)scannerDisplayColor1 { return [[scanner_display_color1 retain] autorelease]; } - (OOColor *)scannerDisplayColor2 { return [[scanner_display_color2 retain] autorelease]; } - (void)setScannerDisplayColor1:(OOColor *)color { DESTROY(scanner_display_color1); if (color == nil) color = [OOColor colorWithDescription:[effectinfoDictionary objectForKey:@"scanner_display_color1"]]; scanner_display_color1 = [color retain]; } - (void)setScannerDisplayColor2:(OOColor *)color { DESTROY(scanner_display_color2); if (color == nil) color = [OOColor colorWithDescription:[effectinfoDictionary objectForKey:@"scanner_display_color2"]]; scanner_display_color2 = [color retain]; } static GLfloat default_color[4] = { 0.0, 0.0, 0.0, 0.0}; static GLfloat scripted_color[4] = { 0.0, 0.0, 0.0, 0.0}; - (GLfloat *) scannerDisplayColorForShip:(BOOL)flash :(OOColor *)scannerDisplayColor1 :(OOColor *)scannerDisplayColor2 { if (scannerDisplayColor1 || scannerDisplayColor2) { if (scannerDisplayColor1 && !scannerDisplayColor2) { [scannerDisplayColor1 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]]; } if (!scannerDisplayColor1 && scannerDisplayColor2) { [scannerDisplayColor2 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]]; } if (scannerDisplayColor1 && scannerDisplayColor2) { if (flash) [scannerDisplayColor1 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]]; else [scannerDisplayColor2 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]]; } return scripted_color; } return default_color; // transparent black if not specified } - (void) drawImmediate:(bool)immediate translucent:(bool)translucent { if (no_draw_distance < cam_zero_distance) { return; // too far away to draw } OOGLPushModelView(); OOGLScaleModelView(make_vector(scaleX,scaleY,scaleZ)); if ([self mesh] != nil) { [super drawImmediate:immediate translucent:translucent]; } OOGLPopModelView(); // Draw subentities. if (!immediate) // TODO: is this relevant any longer? { Entity *subEntity = nil; foreach (subEntity, [self subEntities]) { [subEntity drawSubEntityImmediate:immediate translucent:translucent]; } } } - (void) update:(OOTimeDelta)delta_t { [super update:delta_t]; if (!_haveExecutedSpawnAction) { [self doScriptEvent:OOJSID("effectSpawned")]; _haveExecutedSpawnAction = YES; } Entity *se = nil; foreach (se, [self subEntities]) { [se update:delta_t]; } } - (BOOL) isBreakPattern { return isImmuneToBreakPatternHide; } - (void) setIsBreakPattern:(BOOL)bp { isImmuneToBreakPatternHide = bp; } - (NSDictionary *)effectInfoDictionary { return effectinfoDictionary; } /* scripting */ - (void) setScript:(NSString *)script_name { NSMutableDictionary *properties = nil; properties = [NSMutableDictionary dictionary]; [properties setObject:self forKey:@"visualEffect"]; [script autorelease]; script = [OOScript jsScriptFromFileNamed:script_name properties:properties]; // does not support legacy scripting if (script == nil) { script = [OOScript jsScriptFromFileNamed:@"oolite-default-effect-script.js" properties:properties]; } [script retain]; } - (OOJSScript *)script { return script; } - (NSDictionary *)scriptInfo { return (scriptInfo != nil) ? scriptInfo : (NSDictionary *)[NSDictionary dictionary]; } // unlikely to need events with arguments - (void) doScriptEvent:(jsid)message { JSContext *context = OOJSAcquireContext(); [script callMethod:message inContext:context withArguments:NULL count:0 result:NULL]; OOJSRelinquishContext(context); } - (void) remove { [self doScriptEvent:OOJSID("effectRemoved")]; [UNIVERSE removeEntity:(Entity*)self]; } /* beacons */ - (NSComparisonResult) compareBeaconCodeWith:(Entity *) other { return [[self beaconCode] compare:[other beaconCode] options: NSCaseInsensitiveSearch]; } - (NSString *) beaconCode { return _beaconCode; } - (void) setBeaconCode:(NSString *)bcode { if ([bcode length] == 0) bcode = nil; if (_beaconCode != bcode) { [_beaconCode release]; _beaconCode = [bcode copy]; DESTROY(_beaconDrawable); } // if not blanking code and label is currently blank, default label to code if (bcode != nil && (_beaconLabel == nil || [_beaconLabel length] == 0)) { [self setBeaconLabel:bcode]; } } - (NSString *) beaconLabel { return _beaconLabel; } - (void) setBeaconLabel:(NSString *)blabel { if ([blabel length] == 0) blabel = nil; if (_beaconLabel != blabel) { [_beaconLabel release]; _beaconLabel = [OOExpand(blabel) retain]; } } - (BOOL) isBeacon { return [self beaconCode] != nil; } - (id ) beaconDrawable { if (_beaconDrawable == nil) { NSString *beaconCode = [self beaconCode]; NSUInteger length = [beaconCode length]; if (length > 1) { NSArray *iconData = [[UNIVERSE descriptions] oo_arrayForKey:beaconCode]; if (iconData != nil) _beaconDrawable = [[OOPolygonSprite alloc] initWithDataArray:iconData outlineWidth:0.5 name:beaconCode]; } if (_beaconDrawable == nil) { if (length > 0) _beaconDrawable = [[beaconCode substringToIndex:1] retain]; else _beaconDrawable = @""; } } return _beaconDrawable; } - (Entity *) prevBeacon { return [_prevBeacon weakRefUnderlyingObject]; } - (Entity *) nextBeacon { return [_nextBeacon weakRefUnderlyingObject]; } - (void) setPrevBeacon:(Entity *)beaconShip { if (beaconShip != [self prevBeacon]) { [_prevBeacon release]; _prevBeacon = [beaconShip weakRetain]; } } - (void) setNextBeacon:(Entity *)beaconShip { if (beaconShip != [self nextBeacon]) { [_nextBeacon release]; _nextBeacon = [beaconShip weakRetain]; } } - (BOOL) isJammingScanning { return NO; } /* Shader bindable uniforms */ // no automatic change of this, but simplifies use of default shader - (GLfloat)hullHeatLevel { return _hullHeatLevel; } - (void)setHullHeatLevel:(GLfloat)value { _hullHeatLevel = OOClamp_0_1_f(value); } - (GLfloat) shaderFloat1 { return _shaderFloat1; } - (void)setShaderFloat1:(GLfloat)value { _shaderFloat1 = value; } - (GLfloat) shaderFloat2 { return _shaderFloat2; } - (void)setShaderFloat2:(GLfloat)value { _shaderFloat2 = value; } - (int) shaderInt1 { return _shaderInt1; } - (void)setShaderInt1:(int)value { _shaderInt1 = value; } - (int) shaderInt2 { return _shaderInt2; } - (void)setShaderInt2:(int)value { _shaderInt2 = value; } - (Vector) shaderVector1 { return _shaderVector1; } - (void)setShaderVector1:(Vector)value { _shaderVector1 = value; } - (Vector) shaderVector2 { return _shaderVector2; } - (void)setShaderVector2:(Vector)value { _shaderVector2 = value; } @end @implementation OOVisualEffectEntity (SubEntityRelationship) // a slightly misnamed test now things other than ships can have subents - (BOOL) isShipWithSubEntityShip:(Entity *)other { assert ([self isVisualEffect]); if (![other isVisualEffect]) return NO; if (![other isSubEntity]) return NO; if ([other owner] != self) return NO; #ifndef NDEBUG // Sanity check; this should always be true. if (![self hasSubEntity:(OOVisualEffectEntity *)other]) { OOLogERR(@"visualeffect.subentity.sanityCheck.failed", @"%@ thinks it's a subentity of %@, but the supposed parent does not agree. %@", [other shortDescription], [self shortDescription], @"This is an internal error, please report it."); [other setOwner:nil]; return NO; } #endif return YES; } @end oolite-1.82/src/Core/Entities/OOWaypointEntity.h000066400000000000000000000023731256642440500216350ustar00rootroot00000000000000/* OOWaypoint.h A waypoint for the HUD Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "Entity.h" @interface OOWaypointEntity: Entity { @private OOScalar _size; NSString *_beaconCode; NSString *_beaconLabel; OOWeakReference *_prevBeacon; OOWeakReference *_nextBeacon; id _beaconDrawable; BOOL oriented; } + (instancetype) waypointWithDictionary:(NSDictionary *)info; - (id) initWithDictionary:(NSDictionary *)info; - (BOOL) oriented; - (OOScalar) size; - (void) setSize:(OOScalar)newSize; @end oolite-1.82/src/Core/Entities/OOWaypointEntity.m000066400000000000000000000142071256642440500216410ustar00rootroot00000000000000/* OOWaypointEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOWaypointEntity.h" #import "Entity.h" #import "OOCollectionExtractors.h" #import "OOStringExpander.h" #import "Universe.h" #import "PlayerEntity.h" #import "OOPolygonSprite.h" #import "OOOpenGL.h" #import "OOMacroOpenGL.h" #define OOWAYPOINT_KEY_POSITION @"position" #define OOWAYPOINT_KEY_ORIENTATION @"orientation" #define OOWAYPOINT_KEY_SIZE @"size" #define OOWAYPOINT_KEY_CODE @"beaconCode" #define OOWAYPOINT_KEY_LABEL @"beaconLabel" @implementation OOWaypointEntity + (instancetype) waypointWithDictionary:(NSDictionary *)info { return [[[OOWaypointEntity alloc] initWithDictionary:info] autorelease]; } - (id) initWithDictionary:(NSDictionary *)info { self = [super init]; if (EXPECT_NOT(self == nil)) return nil; oriented = YES; position = [info oo_hpvectorForKey:OOWAYPOINT_KEY_POSITION]; Quaternion q = [info oo_quaternionForKey:OOWAYPOINT_KEY_ORIENTATION]; [self setOrientation:q]; [self setSize:[info oo_nonNegativeFloatForKey:OOWAYPOINT_KEY_SIZE defaultValue:1000.0]]; [self setBeaconCode:[info oo_stringForKey:OOWAYPOINT_KEY_CODE defaultValue:@"W"]]; [self setBeaconLabel:[info oo_stringForKey:OOWAYPOINT_KEY_LABEL defaultValue:@"Waypoint"]]; [self setStatus:STATUS_EFFECT]; [self setScanClass:CLASS_NO_DRAW]; return self; } - (void) dealloc { DESTROY(_beaconCode); DESTROY(_beaconLabel); DESTROY(_prevBeacon); DESTROY(_nextBeacon); DESTROY(_beaconDrawable); [super dealloc]; } // override - (void) setOrientation:(Quaternion)q { if (quaternion_equal(q,kZeroQuaternion)) { q = kIdentityQuaternion; oriented = NO; } else { oriented = YES; } [super setOrientation:q]; } - (BOOL) oriented { return oriented; } - (OOScalar) size { return _size; } - (void) setSize:(OOScalar)newSize { if (newSize > 0) { _size = newSize; no_draw_distance = newSize * newSize * NO_DRAW_DISTANCE_FACTOR * NO_DRAW_DISTANCE_FACTOR * 2; } } - (BOOL) isEffect { return YES; } - (BOOL) isWaypoint { return YES; } - (void) drawImmediate:(bool)immediate translucent:(bool)translucent { if (!translucent || no_draw_distance < cam_zero_distance) { return; } if (![PLAYER hasEquipmentItemProviding:@"EQ_ADVANCED_COMPASS"]) { return; } int8_t i,j,k; GLfloat a = 0.75; if ([PLAYER compassTarget] != self) { a *= 0.25; } if (cam_zero_distance > _size * _size) { // dim out as gets further away; 2-D HUD display more // important at long range a -= 0.004*(sqrt(cam_zero_distance) / _size); } if (a < 0.01) { return; } GLfloat s0 = _size; GLfloat s1 = _size * 0.75; OO_ENTER_OPENGL(); OOSetOpenGLState(OPENGL_STATE_TRANSLUCENT_PASS); OOGL(glEnable(GL_BLEND)); GLScaledLineWidth(1.0); OOGL(glColor4f(0.0, 0.0, 1.0, a)); OOGLBEGIN(GL_LINES); for (i = -1; i <= 1; i+=2) { for (j = -1; j <= 1; j+=2) { for (k = -1; k <= 1; k+=2) { glVertex3f(i*s0,j*s0,k*s1); glVertex3f(i*s0,j*s1,k*s0); glVertex3f(i*s0,j*s1,k*s0); glVertex3f(i*s1,j*s0,k*s0); glVertex3f(i*s1,j*s0,k*s0); glVertex3f(i*s0,j*s0,k*s1); } } } if (oriented) { while (s1 > 20.0) { glVertex3f(-20.0,0,-s1-20.0); glVertex3f(0,0,-s1); glVertex3f(20.0,0,-s1-20.0); glVertex3f(0,0,-s1); glVertex3f(-20.0,0,s1-20.0); glVertex3f(0,0,s1); glVertex3f(20.0,0,s1-20.0); glVertex3f(0,0,s1); s1 *= 0.5; } } OOGLEND(); OOGL(glDisable(GL_BLEND)); OOVerifyOpenGLState(); } /* beacons */ - (NSComparisonResult) compareBeaconCodeWith:(Entity *) other { return [[self beaconCode] compare:[other beaconCode] options: NSCaseInsensitiveSearch]; } - (NSString *) beaconCode { return _beaconCode; } - (void) setBeaconCode:(NSString *)bcode { if ([bcode length] == 0) bcode = nil; if (_beaconCode != bcode) { [_beaconCode release]; _beaconCode = [bcode copy]; DESTROY(_beaconDrawable); } // if not blanking code and label is currently blank, default label to code if (bcode != nil && (_beaconLabel == nil || [_beaconLabel length] == 0)) { [self setBeaconLabel:bcode]; } } - (NSString *) beaconLabel { return _beaconLabel; } - (void) setBeaconLabel:(NSString *)blabel { if ([blabel length] == 0) blabel = nil; if (_beaconLabel != blabel) { [_beaconLabel release]; _beaconLabel = [OOExpand(blabel) retain]; } } - (BOOL) isBeacon { return [self beaconCode] != nil; } - (id ) beaconDrawable { if (_beaconDrawable == nil) { NSString *beaconCode = [self beaconCode]; NSUInteger length = [beaconCode length]; if (length > 1) { NSArray *iconData = [[UNIVERSE descriptions] oo_arrayForKey:beaconCode]; if (iconData != nil) _beaconDrawable = [[OOPolygonSprite alloc] initWithDataArray:iconData outlineWidth:0.5 name:beaconCode]; } if (_beaconDrawable == nil) { if (length > 0) _beaconDrawable = [[beaconCode substringToIndex:1] retain]; else _beaconDrawable = @""; } } return _beaconDrawable; } - (Entity *) prevBeacon { return [_prevBeacon weakRefUnderlyingObject]; } - (Entity *) nextBeacon { return [_nextBeacon weakRefUnderlyingObject]; } - (void) setPrevBeacon:(Entity *)beaconShip { if (beaconShip != [self prevBeacon]) { [_prevBeacon release]; _prevBeacon = [beaconShip weakRetain]; } } - (void) setNextBeacon:(Entity *)beaconShip { if (beaconShip != [self nextBeacon]) { [_nextBeacon release]; _nextBeacon = [beaconShip weakRetain]; } } - (BOOL) isJammingScanning { return NO; } @end oolite-1.82/src/Core/Entities/PlanetEntity.h000066400000000000000000000064171256642440500210130ustar00rootroot00000000000000/* PlanetEntity.h Entity subclass representing a planet or an atmosphere. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOStellarBody.h" #if !NEW_PLANETS #import "Entity.h" #import "legacy_random.h" #import "OOColor.h" @class OOTexture; #define MAX_TRI_INDICES 3*(20+80+320+1280+5120+20480) #define MAX_PLANET_VERTICES 10400 typedef struct { Vector vertex_array[MAX_PLANET_VERTICES + 2]; GLfloat color_array[4*MAX_PLANET_VERTICES]; GLfloat uv_array[2*MAX_PLANET_VERTICES]; Vector normal_array[MAX_PLANET_VERTICES]; GLuint index_array[MAX_TRI_INDICES]; } VertexData; #define PlanetEntity OOPlanetEntity @interface PlanetEntity: Entity { @private OOStellarBodyType planet_type; uint8_t lastSubdivideLevel; BOOL useTexturedModel; BOOL isTextureImage; // is the texture explicitly specified (as opposed to synthesized)? NSString *_textureFileName; OOTexture *_texture; int planet_seed; double polar_color_factor; double rotational_velocity; GLfloat amb_land[4]; GLfloat amb_polar_land[4]; GLfloat amb_sea[4]; GLfloat amb_polar_sea[4]; PlanetEntity *atmosphere; // secondary sphere used to show atmospheric details PlanetEntity *root_planet; // link back to owning planet (not retained) int shuttles_on_ground; // starting number of shuttles double last_launch_time; // space launches out by about 15 minutes double shuttle_launch_interval; // space launches out by about 15 minutes double sqrt_zero_distance; GLuint displayListNames[MAX_SUBDIVIDE]; GLuint vertexCount; VertexData vertexdata; Vector rotationAxis; } - (id) initFromDictionary:(NSDictionary*)dict withAtmosphere:(BOOL)atmo andSeed:(Random_Seed)p_seed; - (void) miniaturize; - (BOOL) setUpPlanetFromTexture:(NSString *)fileName; - (int) planet_seed; - (BOOL) isTextured; - (BOOL) isExplicitlyTextured; // Specified texture, not synthesized. - (OOTexture *) texture; - (NSString *) textureFileName; - (double) polar_color_factor; - (GLfloat *) amb_land; - (GLfloat *) amb_polar_land; - (GLfloat *) amb_sea; - (GLfloat *) amb_polar_sea; - (OOStellarBodyType) planetType; - (void) setPlanetType:(OOStellarBodyType) pt; - (double) radius; // metres - (void) setRadius:(GLfloat) rad; - (double) rotationalVelocity; - (void) setRotationalVelocity:(double) v; - (BOOL) hasAtmosphere; - (void) launchShuttle; - (void) welcomeShuttle:(ShipEntity *) shuttle; - (void) drawUnconditionally; #ifndef NDEBUG - (PlanetEntity *) atmosphere; #endif @end #endif // !NEW_PLANETS oolite-1.82/src/Core/Entities/PlanetEntity.m000066400000000000000000001377221256642440500210240ustar00rootroot00000000000000/* PlanetEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "PlanetEntity.h" #if !NEW_PLANETS #import "OOOpenGLExtensionManager.h" #import "Universe.h" #import "AI.h" #import "TextureStore.h" #import "OOTexture.h" #import "OOTextureInternal.h" // For GL_TEXTURE_CUBE_MAP -- need to clean this up. #import "OOPixMapTextureLoader.h" #import "MyOpenGLView.h" #import "ShipEntityAI.h" #import "OOColor.h" #import "OOCharacter.h" #import "OOStringParsing.h" #import "PlayerEntity.h" #import "OOCollectionExtractors.h" #import "OODebugFlags.h" #import "OOGraphicsResetManager.h" #if !OOLITE_MAC_OS_X #define NSIntegerMapKeyCallBacks NSIntMapKeyCallBacks #define NSIntegerMapValueCallBacks NSIntMapValueCallBacks #endif // straight C static Vector base_vertex_array[MAX_PLANET_VERTICES]; static int base_terrain_array[MAX_PLANET_VERTICES]; static unsigned next_free_vertex; static NSMapTable *sEdgeToVertex; static int n_triangles[MAX_SUBDIVIDE]; static int triangle_start[MAX_SUBDIVIDE]; static GLuint vertex_index_array[3*(20+80+320+1280+5120+20480)]; static GLfloat texture_uv_array[MAX_PLANET_VERTICES * 2]; @interface PlanetEntity (OOPrivate) - (double) sqrtZeroDistance; - (void) drawModelWithVertexArraysAndSubdivision: (int) subdivide; - (void) initialiseBaseVertexArray; - (void) initialiseBaseTerrainArray:(int) percent_land; - (void) paintVertex:(unsigned) vi :(int) seed; - (void) scaleVertices; - (id) initAsAtmosphereForPlanet:(PlanetEntity *)planet dictionary:(NSDictionary *)dict; - (void) setTextureColorForPlanet:(BOOL)isMain inSystem:(BOOL)isLocal; - (id) initMiniatureFromPlanet:(PlanetEntity*) planet withAlpha:(float) alpha; - (void) loadTexture:(NSDictionary *)configuration; - (OOTexture *) planetTextureWithInfo:(NSDictionary *)info; - (OOTexture *) cloudTextureWithCloudColor:(OOColor *)cloudColor cloudImpress:(GLfloat)cloud_impress cloudBias:(GLfloat)cloud_bias; - (void) deleteDisplayLists; - (void) setUseTexturedModel:(BOOL)flag; @end static unsigned baseVertexIndexForEdge(GLushort va, GLushort vb, BOOL textured); typedef struct { unsigned v; float s; float t; } BaseFace; static const Vector kUntexturedVertices[] = { { +0.000000, -0.850664, -0.525710 }, { +0.000000, -0.850664, +0.525710 }, { +0.000000, +0.850664, +0.525710 }, { +0.000000, +0.850664, -0.525710 }, { -0.525710, +0.000000, -0.850664 }, { +0.525710, +0.000000, -0.850664 }, { +0.525710, +0.000000, +0.850664 }, { -0.525710, +0.000000, +0.850664 }, { -0.850664, -0.525710, +0.000000 }, { -0.850664, +0.525710, +0.000000 }, { +0.850664, +0.525710, +0.000000 }, { +0.850664, -0.525710, +0.000000 } }; static const BaseFace kUntexturedFaces[][3] = { { { 9, +0.600000, +0.666667 }, { 7, +0.800000, +0.666667 }, { 8, +0.800000, +0.333333 } }, { { 8, +0.800000, +0.333333 }, { 4, +0.600000, +0.333333 }, { 9, +0.600000, +0.666667 } }, { { 3, +0.400000, +0.666667 }, { 9, +0.600000, +0.666667 }, { 4, +0.600000, +0.333333 } }, { { 8, +0.800000, +0.333333 }, { 0, +0.500000, +0.000000 }, { 4, +0.600000, +0.333333 } }, { { 7, +0.800000, +0.666667 }, { 1, +1.000000, +0.333333 }, { 8, +0.800000, +0.333333 } }, { { 2, +0.500000, +1.000000 }, { 7, +0.800000, +0.666667 }, { 9, +0.600000, +0.666667 } }, { { 2, +0.500000, +1.000000 }, { 9, +0.600000, +0.666667 }, { 3, +0.400000, +0.666667 } }, { { 1, +1.000000, +0.333333 }, { 0, +0.500000, +0.000000 }, { 8, +0.800000, +0.333333 } }, { { 4, +0.600000, +0.333333 }, { 0, +0.500000, +0.000000 }, { 5, +0.400000, +0.333333 } }, { { 5, +0.400000, +0.333333 }, { 3, +0.400000, +0.666667 }, { 4, +0.600000, +0.333333 } }, { { 6, +1.000000, +0.666667 }, { 1, +1.000000, +0.333333 }, { 7, +0.800000, +0.666667 } }, { { 7, +0.800000, +0.666667 }, { 2, +0.500000, +1.000000 }, { 6, +1.000000, +0.666667 } }, { { 1, +0.000000, +0.333333 }, { 11, +0.200000, +0.333333 }, { 0, +0.500000, +0.000000 } }, { { 3, +0.400000, +0.666667 }, { 10, +0.200000, +0.666667 }, { 2, +0.500000, +1.000000 } }, { { 0, +0.500000, +0.000000 }, { 11, +0.200000, +0.333333 }, { 5, +0.400000, +0.333333 } }, { { 5, +0.400000, +0.333333 }, { 10, +0.200000, +0.666667 }, { 3, +0.400000, +0.666667 } }, { { 1, +0.000000, +0.333333 }, { 6, +0.000000, +0.666667 }, { 11, +0.200000, +0.333333 } }, { { 6, +0.000000, +0.666667 }, { 2, +0.500000, +1.000000 }, { 10, +0.200000, +0.666667 } }, { { 5, +0.400000, +0.333333 }, { 11, +0.200000, +0.333333 }, { 10, +0.200000, +0.666667 } }, { { 11, +0.200000, +0.333333 }, { 6, +0.000000, +0.666667 }, { 10, +0.200000, +0.666667 } } }; static const Vector kTexturedVertices[] = { { +0.525731, +0.000000, +0.850651 }, { -0.525731, +0.000000, +0.850651 }, { +0.525731, +0.000000, -0.850651 }, { -0.525731, +0.000000, -0.850651 }, { +0.000000, +0.850651, +0.525731 }, { +0.000000, +0.850651, -0.525731 }, { +0.000000, -0.850651, +0.525731 }, { +0.000000, -0.850651, -0.525731 }, { -0.850651, +0.525731, +0.000000 }, { +0.850651, +0.525731, +0.000000 }, { -0.850651, -0.525731, +0.000000 }, { +0.850651, -0.525731, +0.000000 }, { +0.000000, +0.850651, -0.525731 }, { +0.525731, +0.000000, -0.850651 } }; static const BaseFace kTexturedFaces[][3] = { { { 1, +0.400000, +0.666667 }, { 0, +0.600000, +0.666667 }, { 6, +0.500000, +0.333333 } }, { { 3, +0.100000, +0.333333 }, { 2, -0.100000, +0.333333 }, { 5, +0.000000, +0.666667 } }, { { 4, +0.500000, +1.000000 }, { 0, +0.600000, +0.666667 }, { 1, +0.400000, +0.666667 } }, { { 4, +0.500000, +1.000000 }, { 1, +0.400000, +0.666667 }, { 8, +0.200000, +0.666667 } }, { { 12, +1.000000, +0.666667 }, { 13, +0.900000, +0.333333 }, { 9, +0.800000, +0.666667 } }, { { 5, +0.000000, +0.666667 }, { 4, +0.500000, +1.000000 }, { 8, +0.200000, +0.666667 } }, { { 6, +0.500000, +0.333333 }, { 0, +0.600000, +0.666667 }, { 11, +0.700000, +0.333333 } }, { { 7, +0.500000, +0.000000 }, { 2, -0.100000, +0.333333 }, { 3, +0.100000, +0.333333 } }, { { 7, +0.500000, +0.000000 }, { 3, +0.100000, +0.333333 }, { 10, +0.300000, +0.333333 } }, { { 7, +0.500000, +0.000000 }, { 6, +0.500000, +0.333333 }, { 11, +0.700000, +0.333333 } }, { { 8, +0.200000, +0.666667 }, { 1, +0.400000, +0.666667 }, { 10, +0.300000, +0.333333 } }, { { 8, +0.200000, +0.666667 }, { 3, +0.100000, +0.333333 }, { 5, +0.000000, +0.666667 } }, { { 9, +0.800000, +0.666667 }, { 0, +0.600000, +0.666667 }, { 4, +0.500000, +1.000000 } }, { { 9, +0.800000, +0.666667 }, { 13, +0.900000, +0.333333 }, { 11, +0.700000, +0.333333 } }, { { 9, +0.800000, +0.666667 }, { 4, +0.500000, +1.000000 }, { 12, +1.000000, +0.666667 } }, { { 10, +0.300000, +0.333333 }, { 1, +0.400000, +0.666667 }, { 6, +0.500000, +0.333333 } }, { { 10, +0.300000, +0.333333 }, { 3, +0.100000, +0.333333 }, { 8, +0.200000, +0.666667 } }, { { 10, +0.300000, +0.333333 }, { 6, +0.500000, +0.333333 }, { 7, +0.500000, +0.000000 } }, { { 11, +0.700000, +0.333333 }, { 0, +0.600000, +0.666667 }, { 9, +0.800000, +0.666667 } }, { { 11, +0.700000, +0.333333 }, { 13, +0.900000, +0.333333 }, { 7, +0.500000, +0.000000 } } }; @implementation PlanetEntity - (id) init { [self release]; [NSException raise:NSInternalInconsistencyException format:@"%s, believed dead, called.", __PRETTY_FUNCTION__]; return nil; } - (id) initAsAtmosphereForPlanet:(PlanetEntity *)planet dictionary:(NSDictionary *)dict { BOOL procGen = [UNIVERSE doProcedurallyTexturedPlanets]; if (dict == nil) dict = [NSDictionary dictionary]; self = [super init]; int percent_land = 100 - [dict oo_intForKey:@"percent_cloud" defaultValue:100 - (3 + (gen_rnd_number() & 31)+(gen_rnd_number() & 31))]; polar_color_factor = 1.0; #define CLEAR_SKY_ALPHA 0.05 #define CLOUD_ALPHA 0.50 #define POLAR_CLEAR_SKY_ALPHA 0.34 #define POLAR_CLOUD_ALPHA 0.75 amb_land[0] = gen_rnd_number() / 256.0; amb_land[1] = gen_rnd_number() / 256.0; amb_land[2] = gen_rnd_number() / 256.0; amb_land[3] = CLEAR_SKY_ALPHA; // blue sky, zero clouds amb_sea[0] = 0.5 + gen_rnd_number() / 512.0; amb_sea[1] = 0.5 + gen_rnd_number() / 512.0; amb_sea[2] = 0.5 + gen_rnd_number() / 512.0; amb_sea[3] = CLOUD_ALPHA; // 50% opaque clouds amb_polar_land[0] = gen_rnd_number() / 256.0; amb_polar_land[1] = gen_rnd_number() / 256.0; amb_polar_land[2] = gen_rnd_number() / 256.0; amb_polar_land[3] = POLAR_CLEAR_SKY_ALPHA; // 34% gray clouds amb_polar_sea[0] = 0.9 + gen_rnd_number() / 2560.0; amb_polar_sea[1] = 0.9 + gen_rnd_number() / 2560.0; amb_polar_sea[2] = 0.9 + gen_rnd_number() / 2560.0; amb_polar_sea[3] = POLAR_CLOUD_ALPHA; // 75% clouds // Colour overrides from dictionary OOColor *clearSkyColor = nil; OOColor *cloudColor = nil; OOColor *polarClearSkyColor = nil; OOColor *polarCloudColor = nil; float cloudAlpha; clearSkyColor = [OOColor colorWithDescription:[dict objectForKey:@"atmosphere_color"]]; cloudColor = [OOColor colorWithDescription:[dict objectForKey:@"cloud_color"]]; polarClearSkyColor = [OOColor colorWithDescription:[dict objectForKey:@"polar_atmosphere_color"]]; polarCloudColor = [OOColor colorWithDescription:[dict objectForKey:@"polar_cloud_color"]]; cloudAlpha = OOClamp_0_1_f([dict oo_floatForKey:@"cloud_alpha" defaultValue:1.0]); if (clearSkyColor != nil) { [clearSkyColor getRed:&amb_land[0] green:&amb_land[1] blue:&amb_land[2] alpha:&amb_land[3]]; } if (cloudColor != nil) { [cloudColor getRed:&amb_sea[0] green:&amb_sea[1] blue:&amb_sea[2] alpha:&amb_sea[3]]; } if (polarClearSkyColor != nil) { [polarClearSkyColor getRed:&amb_polar_land[0] green:&amb_polar_land[1] blue:&amb_polar_land[2] alpha:&amb_polar_land[3]]; } else if (clearSkyColor != nil) { memmove(amb_polar_land, amb_land, sizeof amb_polar_land); amb_polar_land[3] = OOClamp_0_1_f(amb_polar_land[3] * (POLAR_CLEAR_SKY_ALPHA / CLEAR_SKY_ALPHA)); } if (polarCloudColor != nil) { [polarCloudColor getRed:&amb_polar_sea[0] green:&amb_polar_sea[1] blue:&amb_polar_sea[2] alpha:&amb_polar_sea[3]]; } else if (cloudColor != nil) { memmove(amb_polar_sea, amb_sea, sizeof amb_polar_sea); amb_polar_sea[3] *= (POLAR_CLOUD_ALPHA / CLOUD_ALPHA); } //amb_land[3] is already 0.05 amb_sea[3] *= cloudAlpha; amb_polar_land[3] *= cloudAlpha; amb_polar_sea[3] *= cloudAlpha; amb_sea[3] = OOClamp_0_1_f(amb_sea[3]); amb_polar_sea[3] = OOClamp_0_1_f(amb_polar_sea[3]); atmosphere = nil; if (procGen) { RANROTSeed ranrotSavedSeed = RANROTGetFullSeed(); RNG_Seed saved_seed = currentRandomSeed(); cloudColor = [OOColor colorWithRed: amb_sea[0] green: amb_sea[1] blue: amb_sea[2] alpha: amb_sea[3]]; float cloud_bias = -0.01 * (float)percent_land; float cloud_impress = 1.0 - cloud_bias; _texture = [self cloudTextureWithCloudColor:cloudColor cloudImpress:cloud_impress cloudBias:cloud_bias]; [_texture retain]; isTextureImage = NO; setRandomSeed(saved_seed); RANROTSetFullSeed(ranrotSavedSeed); } if (!planet) { OOLogERR(@"planet.atmosphere.init.noPlanet", @"planet entity initAsAtmosphereForPlanet: no planet found."); return self; } [self setOwner: planet]; position = [planet position]; orientation = [planet orientation]; if (planet->planet_type == STELLAR_TYPE_NORMAL_PLANET) collision_radius = planet->collision_radius + ATMOSPHERE_DEPTH; // atmosphere is 500m deep only if (planet->planet_type == STELLAR_TYPE_MINIATURE) collision_radius = planet->collision_radius + ATMOSPHERE_DEPTH * PLANET_MINIATURE_FACTOR*2.0; //not to scale: invisible otherwise shuttles_on_ground = 0; last_launch_time = 0.0; shuttle_launch_interval = 3600.0; scanClass = CLASS_NO_DRAW; // orientation.w = M_SQRT1_2; // is already planet->orientation // orientation.x = M_SQRT1_2; // orientation.y = 0.0; // orientation.z = 0.0; planet_type = STELLAR_TYPE_ATMOSPHERE; planet_seed = ranrot_rand(); // random set-up for vertex colours [self setUseTexturedModel:YES]; [self initialiseBaseVertexArray]; [self initialiseBaseTerrainArray:percent_land]; unsigned i; for (i = 0; i < next_free_vertex; i++) { [self paintVertex:i :planet_seed]; } [self scaleVertices]; // set speed of rotation rotational_velocity = [dict oo_floatForKey:@"atmosphere_rotational_velocity" defaultValue:[planet rotationalVelocity]*(0.9+(randf()*0.2))]; // 90-110% of planet rotation speed root_planet = planet; rotationAxis = vector_up_from_quaternion(orientation); [[OOGraphicsResetManager sharedManager] registerClient:self]; return self; } - (void) miniaturize { planet_type = STELLAR_TYPE_MINIATURE; shuttles_on_ground = 0; last_launch_time = 0.0; shuttle_launch_interval = 3600.0; [self setStatus:STATUS_COCKPIT_DISPLAY]; collision_radius = [self collisionRadius] * PLANET_MINIATURE_FACTOR; // teeny tiny [self scaleVertices]; if (atmosphere != nil) { atmosphere->collision_radius = collision_radius + ATMOSPHERE_DEPTH * PLANET_MINIATURE_FACTOR*2.0; //not to scale: invisible otherwise [atmosphere scaleVertices]; } rotational_velocity = 0.04; rotationAxis = kBasisYVector; } - (id) initFromDictionary:(NSDictionary*)dict withAtmosphere:(BOOL)atmo andSeed:(Random_Seed)p_seed { BOOL procGen = [UNIVERSE doProcedurallyTexturedPlanets]; if (dict == nil) dict = [NSDictionary dictionary]; RANROTSeed ranrotSavedSeed = RANROTGetFullSeed(); self = [super init]; planet_type = atmo ? STELLAR_TYPE_NORMAL_PLANET : STELLAR_TYPE_MOON; if (atmo) planet_seed = p_seed.a * 13 + p_seed.c * 11 + p_seed.e * 7; // pseudo-random set-up for vertex colours else planet_seed = p_seed.a * 7 + p_seed.c * 11 + p_seed.e * 13; // pseudo-random set-up for vertex colours OOTexture *texture = [dict oo_objectOfClass:[OOTexture class] forKey:@"_oo_textureObject"]; if (texture != nil) { _texture = [texture retain]; isTextureImage = [dict oo_boolForKey:@"_oo_isExplicitlyTextured"]; } else { NSDictionary *textureSpec = [dict oo_textureSpecifierForKey:@"texture" defaultName:nil]; if (textureSpec != nil) { [self loadTexture:textureSpec]; } // CIM: nothing actually uses textureSpec after the assignment below, so skip it /* if (textureSpec == nil && !procGen && !atmo) { // Moons use metal.png by default. textureSpec = OOTextureSpecFromObject(@"metal.png", nil); } */ NSString *seedStr = [dict oo_stringForKey:@"seed"]; if (seedStr != nil) { Random_Seed seed = RandomSeedFromString(seedStr); if (!is_nil_seed(seed)) { p_seed = seed; } else { OOLogERR(@"planet.fromDict", @"could not interpret \"%@\" as planet seed, using default.", seedStr); } } } seed_for_planet_description(p_seed); NSMutableDictionary *planetInfo = [NSMutableDictionary dictionaryWithDictionary:[UNIVERSE generateSystemData:p_seed]]; int radius_km = [dict oo_intForKey:KEY_RADIUS defaultValue:[planetInfo oo_intForKey:KEY_RADIUS]]; int techlevel = [dict oo_intForKey:KEY_TECHLEVEL defaultValue:[planetInfo oo_intForKey:KEY_TECHLEVEL]]; shuttles_on_ground = 1 + floor(techlevel * 0.5); last_launch_time = 0.0; shuttle_launch_interval = 3600.0 / shuttles_on_ground; // all are launched in an hour last_launch_time = [UNIVERSE getTime] + 30.0 - shuttle_launch_interval; // debug - launch 30s after player enters universe collision_radius = radius_km * 10.0; // scale down by a factor of 100 ! scanClass = CLASS_NO_DRAW; orientation.w = M_SQRT1_2; orientation.x = M_SQRT1_2; orientation.y = 0.0; orientation.z = 0.0; [self setUseTexturedModel:(procGen || _texture != nil)]; int percent_land = [planetInfo oo_intForKey:@"percent_land" defaultValue:24 + (gen_rnd_number() % 48)]; //if (isTextured) percent_land = atmo ? 0 :100; // moon/planet override // save the current random number generator seed RNG_Seed saved_seed = currentRandomSeed(); // For historical reasons, stir the PRNG vertexCount times. unsigned i; for (i = 0; i < vertexCount; i++) gen_rnd_number(); [planetInfo setObject:[NSNumber numberWithFloat:0.01 * percent_land] forKey:@"land_fraction"]; polar_color_factor = [dict oo_doubleForKey:@"polar_color_factor" defaultValue:0.5f]; Vector land_hsb, sea_hsb, land_polar_hsb, sea_polar_hsb; if (isTextureImage) { // standard overlay colours. land_hsb.x = 0.0; land_hsb.y = 0.0; land_hsb.z = 1.0; // non-saturated fully bright (white) sea_hsb.x = 0.0; sea_hsb.y = 1.0; sea_hsb.z = 1.0; // fully-saturated fully bright (red) // override the mainPlanet texture colour... [self setTextureColorForPlanet:!![dict objectForKey:@"mainForLocalSystem"] inSystem:[dict oo_boolForKey:@"mainForLocalSystem" defaultValue:NO]]; } else { // random land & sea colours. land_hsb.x = gen_rnd_number() / 256.0; land_hsb.y = gen_rnd_number() / 256.0; land_hsb.z = 0.5 + gen_rnd_number() / 512.0; sea_hsb.x = gen_rnd_number() / 256.0; sea_hsb.y = gen_rnd_number() / 256.0; sea_hsb.z = 0.5 + gen_rnd_number() / 512.0; while (dot_product(land_hsb,sea_hsb) > .80) // make sure land and sea colors differ significantly { sea_hsb.x = gen_rnd_number() / 256.0; sea_hsb.y = gen_rnd_number() / 256.0; sea_hsb.z = 0.5 + gen_rnd_number() / 512.0; } // assign land_hsb and sea_hsb overrides from planetinfo.plist if they're there. ScanVectorFromString([dict objectForKey:@"land_hsb_color"], &land_hsb); ScanVectorFromString([dict objectForKey:@"sea_hsb_color"], &sea_hsb); // polar areas are brighter but have less color (closer to white) land_polar_hsb.x = land_hsb.x; land_polar_hsb.y = (land_hsb.y / 4.0); land_polar_hsb.z = 1.0 - (land_hsb.z / 10.0); sea_polar_hsb.x = sea_hsb.x; sea_polar_hsb.y = (sea_hsb.y / 4.0); sea_polar_hsb.z = 1.0 - (sea_hsb.z / 10.0); OOColor *amb_land_color = [OOColor colorWithHue:land_hsb.x saturation:land_hsb.y brightness:land_hsb.z alpha:1.0]; OOColor *amb_sea_color = [OOColor colorWithHue:sea_hsb.x saturation:sea_hsb.y brightness:sea_hsb.z alpha:1.0]; OOColor *amb_polar_land_color = [OOColor colorWithHue:land_polar_hsb.x saturation:land_polar_hsb.y brightness:land_polar_hsb.z alpha:1.0]; OOColor *amb_polar_sea_color = [OOColor colorWithHue:sea_polar_hsb.x saturation:sea_polar_hsb.y brightness:sea_polar_hsb.z alpha:1.0]; amb_land[0] = [amb_land_color redComponent]; amb_land[1] = [amb_land_color blueComponent]; amb_land[2] = [amb_land_color greenComponent]; amb_land[3] = 1.0; amb_sea[0] = [amb_sea_color redComponent]; amb_sea[1] = [amb_sea_color blueComponent]; amb_sea[2] = [amb_sea_color greenComponent]; amb_sea[3] = 1.0; amb_polar_land[0] = [amb_polar_land_color redComponent]; amb_polar_land[1] = [amb_polar_land_color blueComponent]; amb_polar_land[2] = [amb_polar_land_color greenComponent]; amb_polar_land[3] = 1.0; amb_polar_sea[0] = [amb_polar_sea_color redComponent]; amb_polar_sea[1] = [amb_polar_sea_color blueComponent]; amb_polar_sea[2] = [amb_polar_sea_color greenComponent]; amb_polar_sea[3] = 1.0; [planetInfo setObject:amb_land_color forKey:@"land_color"]; [planetInfo setObject:amb_sea_color forKey:@"sea_color"]; [planetInfo setObject:amb_polar_land_color forKey:@"polar_land_color"]; [planetInfo setObject:amb_polar_sea_color forKey:@"polar_sea_color"]; } if (procGen && _texture == nil) { _texture = [self planetTextureWithInfo:planetInfo]; isTextureImage = NO; [_texture retain]; } [self initialiseBaseVertexArray]; [self initialiseBaseTerrainArray:percent_land]; for (i = 0; i < next_free_vertex; i++) { [self paintVertex:i :planet_seed]; } [self scaleVertices]; // set speed of rotation if ([dict objectForKey:@"rotational_velocity"]) { rotational_velocity = [dict oo_floatForKey:@"rotational_velocity" defaultValue:0.01f * randf()]; // 0.0 .. 0.01 avr 0.005 } else { rotational_velocity = [planetInfo oo_floatForKey:@"rotation_speed" defaultValue:0.002 * (0.5+0.5*randf())]; // 0.001 .. 0.002 avr 0.0015 rotational_velocity *= [planetInfo oo_floatForKey:@"rotation_speed_factor" defaultValue:1.0f]; } // do atmosphere NSDictionary *atmoDict = dict; if (_texture != nil) atmoDict = [NSDictionary dictionaryWithObjectsAndKeys:@"0", @"percent_cloud", [NSNumber numberWithFloat:[planetInfo oo_floatForKey:@"cloud_alpha" defaultValue:1.0]], @"cloud_alpha", nil]; if (atmo) atmosphere = [[PlanetEntity alloc] initAsAtmosphereForPlanet:self dictionary:atmoDict]; setRandomSeed(saved_seed); RANROTSetFullSeed(ranrotSavedSeed); // set energy energy = collision_radius * 1000.0; root_planet = self; rotationAxis = vector_up_from_quaternion(orientation); /* MKW - rotate planet based on current time. * - do it here so that we catch all planets (OXP-added and otherwise! */ int deltaT = floor(fmod([PLAYER clockTimeAdjusted], 86400)); quaternion_rotate_about_axis(&orientation, rotationAxis, rotational_velocity * deltaT); [self setStatus:STATUS_ACTIVE]; [[OOGraphicsResetManager sharedManager] registerClient:self]; return self; } - (void) dealloc { [self deleteDisplayLists]; DESTROY(atmosphere); DESTROY(_texture); DESTROY(_textureFileName); [[OOGraphicsResetManager sharedManager] unregisterClient:self]; [super dealloc]; } - (NSString*) descriptionComponents { NSString *typeString; switch (planet_type) { case STELLAR_TYPE_MINIATURE: typeString = @"STELLAR_TYPE_MINIATURE"; break; case STELLAR_TYPE_NORMAL_PLANET: typeString = @"STELLAR_TYPE_NORMAL_PLANET"; break; case STELLAR_TYPE_ATMOSPHERE: typeString = @"STELLAR_TYPE_ATMOSPHERE"; break; case STELLAR_TYPE_MOON: typeString = @"STELLAR_TYPE_MOON"; break; default: typeString = @"UNKNOWN"; } return [NSString stringWithFormat:@"ID: %u position: %@ type: %@ radius: %.3fkm", [self universalID], HPVectorDescription([self position]), typeString, 0.001 * [self radius]]; } - (BOOL) canCollide { switch (planet_type) { case STELLAR_TYPE_MINIATURE: case STELLAR_TYPE_ATMOSPHERE: return NO; break; case STELLAR_TYPE_MOON: case STELLAR_TYPE_NORMAL_PLANET: case STELLAR_TYPE_SUN: return YES; break; } return YES; } - (BOOL) checkCloseCollisionWith:(Entity *)other { #ifndef NDEBUG if (gDebugFlags & DEBUG_COLLISIONS) OOLog(@"planet.collide", @"PLANET Collision!"); #endif if (!other) return NO; if (other->isShip) { ShipEntity *ship = (ShipEntity *)other; if ([ship behaviour] == BEHAVIOUR_LAND_ON_PLANET) { return NO; } #ifndef NDEBUG if ([ship reportAIMessages]) { HPVector p1 = ship->position; OOLog(@"planet.collide.shipHit", @"DEBUG: %@ %d collided with planet at (%.1f,%.1f,%.1f)",[ship name], [ship universalID], p1.x,p1.y,p1.z); } #endif } return YES; } - (void) update:(OOTimeDelta) delta_t { [super update:delta_t]; sqrt_zero_distance = sqrt(cam_zero_distance); switch (planet_type) { case STELLAR_TYPE_NORMAL_PLANET: // we have atmosphere in any case. { double alt = sqrt_zero_distance - collision_radius; double atmo = 10.0 * (atmosphere->collision_radius - collision_radius); // effect starts at 10x the height of the clouds if ((alt > 0)&&(alt <= atmo)) { double aleph = (atmo - alt) / atmo; if (aleph < 0.0) aleph = 0.0; if (aleph > 1.0) aleph = 1.0; [UNIVERSE setSkyColorRed:0.8 * aleph * aleph green:0.8 * aleph * aleph blue:0.9 * aleph alpha:aleph]; } else if (alt > 0 && alt <= atmo * 1.5) { /* Without this, if you leave the atmosphere at very low * frame rates, you might still have skyalpha > 0 when you * go sun-skimming. Since skyalpha > 0 simulates atmospheric * friction and continually pumps heat into the ship, this * is rapidly fatal - CIM */ [UNIVERSE setSkyColorRed:0.0f green:0.0f blue:0.0f alpha:0.0f]; } } case STELLAR_TYPE_MOON: { double ugt = [UNIVERSE getTime]; if ((shuttles_on_ground > 0)&&(ugt > last_launch_time + shuttle_launch_interval)) { [self launchShuttle]; shuttles_on_ground--; last_launch_time = ugt; } } case STELLAR_TYPE_MINIATURE: // normal planetary rotation if (atmosphere) [atmosphere update:delta_t]; quaternion_rotate_about_axis(&orientation, rotationAxis, rotational_velocity * delta_t); [self orientationChanged]; break; case STELLAR_TYPE_ATMOSPHERE: { // atmospheric rotation quaternion_rotate_about_axis(&orientation, rotationAxis, rotational_velocity * delta_t); [self orientationChanged]; } break; case STELLAR_TYPE_SUN: break; } } - (void) setPosition:(HPVector)posn { position = posn; [atmosphere setPosition:posn]; } - (void) setOrientation:(Quaternion)inOrientation { rotationAxis = vector_up_from_quaternion(inOrientation); [super setOrientation:inOrientation]; if (atmosphere) [atmosphere setOrientation:inOrientation]; } - (void) setUseTexturedModel:(BOOL)flag { if (flag) { useTexturedModel = YES; vertexCount = sizeof kTexturedVertices / sizeof *kTexturedVertices; } else { useTexturedModel = NO; vertexCount = sizeof kUntexturedVertices / sizeof *kTexturedVertices; } } // TODO: some translucent stuff is drawn in the opaque pass, which is Naughty. - (void) drawImmediate:(bool)immediate translucent:(bool)translucent { if ([UNIVERSE breakPatternHide] || translucent || immediate) return; // DON'T DRAW GLfloat radWithAtmosphere = [self radius] + ATMOSPHERE_DEPTH; if (radWithAtmosphere * radWithAtmosphere * 1.1 < cam_zero_distance) { // for some reason this check doesn't work when extremely close to // the planet and with the horizon near the side of the frame (FP // inaccuracy?) if (![UNIVERSE viewFrustumIntersectsSphereAt:cameraRelativePosition withRadius:radWithAtmosphere]) { // Don't draw return; } } [self drawUnconditionally]; } - (void) drawUnconditionally { uint8_t subdivideLevel = 2; // 4 is probably the maximum! double drawFactor = [[UNIVERSE gameView] viewSize].width / 100.0; double drawRatio2 = drawFactor * collision_radius / sqrt_zero_distance; // equivalent to size on screen in pixels if (cam_zero_distance > 0.0) { subdivideLevel = 2 + floor(drawRatio2); if (subdivideLevel > 4) subdivideLevel = 4; } if (planet_type == STELLAR_TYPE_MINIATURE) { subdivideLevel = [UNIVERSE reducedDetail]? 3 : 4; // max detail or less } lastSubdivideLevel = subdivideLevel; // record OOSetOpenGLState(OPENGL_STATE_OPAQUE); OOGL(glPushAttrib(GL_ENABLE_BIT)); // OOGL(glEnable(GL_LIGHTING)); // OOGL(glEnable(GL_LIGHT1)); GLfloat specular[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; OOGL(glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular)); OOGL(glMateriali(GL_FRONT_AND_BACK, GL_SHININESS, 0)); /* The depth test gets disabled in parts of this and instead we rely on the painters algorithm instead. The depth buffer isn't granular enough to cope with huge objects at vast distances. */ BOOL ignoreDepthBuffer = (planet_type == STELLAR_TYPE_ATMOSPHERE); if (cam_zero_distance > collision_radius * collision_radius * 25) // is 'far away' { ignoreDepthBuffer |= YES; } [_texture ensureFinishedLoading]; switch (planet_type) { case STELLAR_TYPE_ATMOSPHERE: if (root_planet) { subdivideLevel = root_planet->lastSubdivideLevel; // copy it from the planet (stops jerky LOD and such) } OOGLMultModelVew(rotMatrix); // rotate the clouds! OOGL(glEnable(GL_BLEND)); // OOGL(glDisable(GL_LIGHTING)); // Fall through. case STELLAR_TYPE_MOON: case STELLAR_TYPE_NORMAL_PLANET: case STELLAR_TYPE_MINIATURE: //if ((gDebugFlags & DEBUG_WIREFRAME_GRAPHICS) if ([UNIVERSE wireframeGraphics]) { // Drop the detail level a bit, it still looks OK in wireframe and does not penalize // the system that much. subdivideLevel = 2; OOGLWireframeModeOn(); } { GLfloat mat1[] = { 1.0, 1.0, 1.0, 1.0 }; // opaque white if (_texture != nil) { if ([_texture isCubeMap]) { #if OO_TEXTURE_CUBE_MAP OOGL(glDisable(GL_TEXTURE_2D)); OOGL(glEnable(GL_TEXTURE_CUBE_MAP)); #endif } else { // OOGL(glEnable(GL_TEXTURE_2D)); } OOGL(glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, mat1)); [_texture apply]; } else { OOGL(glDisable(GL_TEXTURE_2D)); [OOTexture applyNone]; } OOGL(glShadeModel(GL_SMOOTH)); // far enough away to draw flat ? if (ignoreDepthBuffer) { OOGL(glDisable(GL_DEPTH_TEST)); } OOGL(glColor4fv(mat1)); OOGL(glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mat1)); OOGL(glEnableClientState(GL_COLOR_ARRAY)); OOGL(glColorPointer(4, GL_FLOAT, 0, vertexdata.color_array)); // OOGL(glEnableClientState(GL_VERTEX_ARRAY)); OOGL(glVertexPointer(3, GL_FLOAT, 0, vertexdata.vertex_array)); // OOGL(glEnableClientState(GL_NORMAL_ARRAY)); OOGL(glNormalPointer(GL_FLOAT, 0, vertexdata.normal_array)); if (_texture != nil) { OOGL(glEnableClientState(GL_TEXTURE_COORD_ARRAY)); if ([_texture isCubeMap]) { OOGL(glTexCoordPointer(3, GL_FLOAT, 0, vertexdata.vertex_array)); } else { OOGL(glTexCoordPointer(2, GL_FLOAT, 0, vertexdata.uv_array)); } } else { OOGL(glDisableClientState(GL_TEXTURE_COORD_ARRAY)); } if (displayListNames[subdivideLevel] != 0) { OOGL(glCallList(displayListNames[subdivideLevel])); } else { OOGL(displayListNames[subdivideLevel] = glGenLists(1)); if (displayListNames[subdivideLevel] != 0) // sanity check { OOGL(glNewList(displayListNames[subdivideLevel], GL_COMPILE_AND_EXECUTE)); OOGL(glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)); OOGL(glEnable(GL_COLOR_MATERIAL)); [self drawModelWithVertexArraysAndSubdivision:subdivideLevel]; OOGL(glDisable(GL_COLOR_MATERIAL)); OOGL(glEndList()); } } #if OO_TEXTURE_CUBE_MAP if ([_texture isCubeMap]) { OOGL(glDisable(GL_TEXTURE_CUBE_MAP)); OOGL(glEnable(GL_TEXTURE_2D)); } #endif OOGL(glDisableClientState(GL_COLOR_ARRAY)); OOGL(glDisableClientState(GL_TEXTURE_COORD_ARRAY)); OOGL(glEnable(GL_DEPTH_TEST)); } //if ((gDebugFlags & DEBUG_WIREFRAME_GRAPHICS) if ([UNIVERSE wireframeGraphics]) { OOGLWireframeModeOff(); } // OOGL(glDisableClientState(GL_VERTEX_ARRAY)); // OOGL(glDisableClientState(GL_NORMAL_ARRAY)); break; case STELLAR_TYPE_SUN: break; } if (ignoreDepthBuffer) { OOGL(glEnable(GL_DEPTH_TEST)); } OOGL(glEnable(GL_TEXTURE_2D)); // OOGL(glEnable(GL_LIGHTING)); OOGL(glDisable(GL_BLEND)); OOGL(glPopAttrib()); OOVerifyOpenGLState(); OOCheckOpenGLErrors(@"PlanetEntity after drawing %@", self); if (atmosphere) { OOGLPopModelView(); // get old draw matrix back OOGLPushModelView(); // and store it again OOGLTranslateModelView(cameraRelativePosition); // centre on the planet // rotate // OOGLMultModelView([atmosphere rotationMatrix]); // draw atmosphere entity [atmosphere drawImmediate:false translucent:false]; } } #ifndef NDEBUG - (PlanetEntity *) atmosphere { return atmosphere; } #endif - (int) planet_seed { return planet_seed; } - (BOOL) isTextured { return _texture != nil; } - (NSString *) textureFileName { return _textureFileName; } - (void) setTextureColorForPlanet:(BOOL)isMain inSystem:(BOOL)isLocal { Vector land_hsb, land_polar_hsb; land_hsb.x = 0.0; land_hsb.y = 0.0; land_hsb.z = 1.0; // white // the colour override should only apply to main planets if (isMain) { if (isLocal) ScanVectorFromString([[UNIVERSE currentSystemData] objectForKey:@"texture_hsb_color"], &land_hsb); else ScanVectorFromString([[UNIVERSE generateSystemData:[PLAYER target_system_seed]] objectForKey:@"texture_hsb_color"], &land_hsb); } land_polar_hsb.x = land_hsb.x; land_polar_hsb.y = (land_hsb.y / 5.0); land_polar_hsb.z = 1.0 - (land_hsb.z / 10.0); amb_sea[0] = amb_land[0] = [[OOColor colorWithHue:land_hsb.x saturation:land_hsb.y brightness:land_hsb.z alpha:1.0] redComponent]; amb_sea[1] = amb_land[1] = [[OOColor colorWithHue:land_hsb.x saturation:land_hsb.y brightness:land_hsb.z alpha:1.0] blueComponent]; amb_sea[2] = amb_land[2] = [[OOColor colorWithHue:land_hsb.x saturation:land_hsb.y brightness:land_hsb.z alpha:1.0] greenComponent]; amb_sea[3] = amb_land[3] = 1.0; amb_polar_sea[0] =amb_polar_land[0] = [[OOColor colorWithHue:land_polar_hsb.x saturation:land_polar_hsb.y brightness:land_polar_hsb.z alpha:1.0] redComponent]; amb_polar_sea[1] =amb_polar_land[1] = [[OOColor colorWithHue:land_polar_hsb.x saturation:land_polar_hsb.y brightness:land_polar_hsb.z alpha:1.0] blueComponent]; amb_polar_sea[2] = amb_polar_land[2] = [[OOColor colorWithHue:land_polar_hsb.x saturation:land_polar_hsb.y brightness:land_polar_hsb.z alpha:1.0] greenComponent]; amb_polar_sea[3] =amb_polar_land[3] = 1.0; } - (BOOL) setUpPlanetFromTexture:(NSString *)fileName { if (fileName == nil) return NO; [self loadTexture:OOTextureSpecFromObject(fileName, nil)]; [self deleteDisplayLists]; unsigned i; [self setUseTexturedModel:YES]; // recolour main planet according to "texture_hsb_color" // this function is only called for local systems! [self setTextureColorForPlanet:([UNIVERSE planet] == self) inSystem:YES]; [self initialiseBaseVertexArray]; [self initialiseBaseTerrainArray:100]; for (i = 0; i < next_free_vertex; i++) { [self paintVertex:i :planet_seed]; } [self scaleVertices]; GLfloat oldCloudAlpha = 0.0; if (atmosphere) { oldCloudAlpha = [atmosphere amb_sea][3] / CLOUD_ALPHA; } NSDictionary *atmo_dictionary = [NSDictionary dictionaryWithObjectsAndKeys:@"0", @"percent_cloud", [NSNumber numberWithFloat:oldCloudAlpha], @"cloud_alpha", nil]; [atmosphere autorelease]; atmosphere = [self hasAtmosphere] ? [[PlanetEntity alloc] initAsAtmosphereForPlanet:self dictionary:atmo_dictionary] : nil; //rotationAxis = vector_up_from_quaternion(orientation); return [self isTextured]; } - (double) polar_color_factor { return polar_color_factor; } - (GLfloat *) amb_land { return amb_land; } - (GLfloat *) amb_polar_land { return amb_polar_land; } - (GLfloat *) amb_sea { return amb_sea; } - (GLfloat *) amb_polar_sea { return amb_polar_sea; } - (OOStellarBodyType) planetType { return planet_type; } - (void) setPlanetType:(OOStellarBodyType) pt { planet_type = pt; } - (double) radius { return collision_radius; } - (void) setRadius:(GLfloat) rad { collision_radius = rad; } - (double) rotationalVelocity { return rotational_velocity; } - (void) setRotationalVelocity:(double) v { if (atmosphere) // change rotation speed proportionally { [atmosphere setRotationalVelocity:[atmosphere rotationalVelocity] * v / [self rotationalVelocity]]; } rotational_velocity = v; } - (double) sqrtZeroDistance { return sqrt_zero_distance; } - (BOOL) hasAtmosphere { return atmosphere != nil; } - (void) drawModelWithVertexArraysAndSubdivision: (int) subdivide { OOGL(glDrawElements(GL_TRIANGLES, 3 * n_triangles[subdivide], GL_UNSIGNED_INT, &vertexdata.index_array[triangle_start[subdivide]])); } - (void) launchShuttle { ShipEntity *shuttle_ship; Quaternion q1; HPVector launch_pos = position; double start_distance = collision_radius + 125.0; quaternion_set_random(&q1); Vector vf = vector_forward_from_quaternion(q1); launch_pos.x += start_distance * vf.x; launch_pos.y += start_distance * vf.y; launch_pos.z += start_distance * vf.z; shuttle_ship = [UNIVERSE newShipWithRole:@"shuttle"]; // retain count = 1 if (shuttle_ship) { if (![shuttle_ship crew]) { [shuttle_ship setSingleCrewWithRole:@"trader"]; } [shuttle_ship setPosition:launch_pos]; [shuttle_ship setOrientation:q1]; [shuttle_ship setScanClass: CLASS_NEUTRAL]; [shuttle_ship setCargoFlag:CARGO_FLAG_FULL_PLENTIFUL]; [shuttle_ship switchAITo:@"oolite-shuttleAI.js"]; [UNIVERSE addEntity:shuttle_ship]; [shuttle_ship release]; } } - (void) welcomeShuttle:(ShipEntity *) shuttle { shuttles_on_ground++; } - (void) initialiseBaseVertexArray { BOOL isTextured = [self isTextured]; static BOOL lastOneWasTextured; if (lastOneWasTextured != isTextured) { if (sEdgeToVertex != NULL) { NSFreeMapTable(sEdgeToVertex); sEdgeToVertex = NULL; } lastOneWasTextured = isTextured; } if (sEdgeToVertex == NULL) { sEdgeToVertex = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSIntegerMapValueCallBacks, 7680); // make a new one next_free_vertex = 0; const Vector *vertices = NULL; const BaseFace (*faces)[3] = NULL; GLuint faceCount = 0; if (useTexturedModel) { vertices = kTexturedVertices; faces = kTexturedFaces; faceCount = sizeof kTexturedFaces / sizeof *kTexturedFaces; } else { vertices = kUntexturedVertices; faces = kUntexturedFaces; faceCount = sizeof kUntexturedFaces / sizeof *kUntexturedFaces; } // set first 12 or 14 vertices GLuint vi; for (vi = 0; vi < vertexCount; vi++) { base_vertex_array[next_free_vertex++] = vertices[vi]; } // set first 20 triangles triangle_start[0] = 0; n_triangles[0] = faceCount; GLuint fi; for (fi = 0; fi < faceCount; fi++) { unsigned j; for (j = 0; j < 3; j++) { vertex_index_array[fi * 3 + j] = faces[fi][j].v; texture_uv_array[faces[fi][j].v * 2 + 0] = faces[fi][j].s; texture_uv_array[faces[fi][j].v * 2 + 1] = faces[fi][j].t; } } // for the next levels of subdivision simply build up from the level below!... unsigned sublevel; for (sublevel = 0; sublevel < MAX_SUBDIVIDE - 1; sublevel++) { int newlevel = sublevel + 1; triangle_start[newlevel] = triangle_start[sublevel] + n_triangles[sublevel] * 3; n_triangles[newlevel] = n_triangles[sublevel] * 4; int tri; for (tri = 0; tri < n_triangles[sublevel]; tri++) { // get the six vertices for this group of four triangles int v0 = vertex_index_array[triangle_start[sublevel] + tri * 3 + 0]; int v1 = vertex_index_array[triangle_start[sublevel] + tri * 3 + 1]; int v2 = vertex_index_array[triangle_start[sublevel] + tri * 3 + 2]; int v01 = baseVertexIndexForEdge(v0, v1, isTextured); // sets it up if required int v12 = baseVertexIndexForEdge(v1, v2, isTextured); // .. int v20 = baseVertexIndexForEdge(v2, v0, isTextured); // .. // v0 v01 v20 vertex_index_array[triangle_start[newlevel] + tri * 12 + 0] = v0; vertex_index_array[triangle_start[newlevel] + tri * 12 + 1] = v01; vertex_index_array[triangle_start[newlevel] + tri * 12 + 2] = v20; // v01 v1 v12 vertex_index_array[triangle_start[newlevel] + tri * 12 + 3] = v01; vertex_index_array[triangle_start[newlevel] + tri * 12 + 4] = v1; vertex_index_array[triangle_start[newlevel] + tri * 12 + 5] = v12; // v20 v12 v2 vertex_index_array[triangle_start[newlevel] + tri * 12 + 6] = v20; vertex_index_array[triangle_start[newlevel] + tri * 12 + 7] = v12; vertex_index_array[triangle_start[newlevel] + tri * 12 + 8] = v2; // v01 v12 v20 vertex_index_array[triangle_start[newlevel] + tri * 12 + 9] = v01; vertex_index_array[triangle_start[newlevel] + tri * 12 +10] = v12; vertex_index_array[triangle_start[newlevel] + tri * 12 +11] = v20; } } } // all done - copy the indices to the instance unsigned i; for (i = 0; i < MAX_TRI_INDICES; i++) { vertexdata.index_array[i] = vertex_index_array[i]; } } static unsigned baseVertexIndexForEdge(GLushort va, GLushort vb, BOOL textured) { if (va < vb) { GLushort temp = va; va = vb; vb = temp; } void *key = (void *)(((uintptr_t)va << 16) | vb); uintptr_t num = (uintptr_t)NSMapGet(sEdgeToVertex, key); if (num != 0) { // Overall cache hit rate is just over 83 %. return (unsigned)num - 1; } else { unsigned vindex = next_free_vertex++; NSCAssert(vindex < sizeof base_vertex_array / sizeof *base_vertex_array, @"Vertex array overflow in planet setup."); // calculate position of new vertex Vector pos = vector_add(base_vertex_array[va], base_vertex_array[vb]); pos = vector_normal(pos); // guaranteed non-zero base_vertex_array[vindex] = pos; if (textured) { //calculate new texture coordinates NSPoint uva = (NSPoint){ texture_uv_array[va * 2], texture_uv_array[va * 2 + 1] }; NSPoint uvb = (NSPoint){ texture_uv_array[vb * 2], texture_uv_array[vb * 2 + 1] }; // if either of these is the polar vertex treat it specially to help with polar distortion: if (uva.y == 0.0 || uva.y == 1.0) uva.x = uvb.x; if (uvb.y == 0.0 || uvb.y == 1.0) uvb.x = uva.x; texture_uv_array[vindex * 2] = 0.5 * (uva.x + uvb.x); texture_uv_array[vindex * 2 + 1] = 0.5 * (uva.y + uvb.y); } // add new edge to the look-up num = vindex + 1; NSMapInsertKnownAbsent(sEdgeToVertex, key, (void *)num); return vindex; } } - (void) initialiseBaseTerrainArray:(int) percent_land { RANROTSeed saved_seed = RANROTGetFullSeed(); // set first 12 or 14 vertices if (percent_land >= 0) { GLuint vi; for (vi = 0; vi < vertexCount; vi++) { if (gen_rnd_number() < 256 * percent_land / 100) base_terrain_array[vi] = 0; // land else base_terrain_array[vi] = 100; // sea } } // for the next levels of subdivision simply build up from the level below!... BOOL isTextured = [self isTextured]; int sublevel; for (sublevel = 0; sublevel < MAX_SUBDIVIDE - 1; sublevel++) { int tri; for (tri = 0; tri < n_triangles[sublevel]; tri++) { // get the six vertices for this group of four triangles int v0 = vertex_index_array[triangle_start[sublevel] + tri * 3 + 0]; int v1 = vertex_index_array[triangle_start[sublevel] + tri * 3 + 1]; int v2 = vertex_index_array[triangle_start[sublevel] + tri * 3 + 2]; int v01 = baseVertexIndexForEdge(v0, v1, isTextured); // sets it up if required int v12 = baseVertexIndexForEdge(v1, v2, isTextured); // .. int v20 = baseVertexIndexForEdge(v2, v0, isTextured); // .. // v01 if (base_terrain_array[v0] == base_terrain_array[v1]) base_terrain_array[v01] = base_terrain_array[v0]; else { uint32_t s1 = 0xffff0000 * base_vertex_array[v01].x; uint32_t s2 = 0x00ffff00 * base_vertex_array[v01].y; uint32_t s3 = 0x0000ffff * base_vertex_array[v01].z; ranrot_srand(s1+s2+s3); base_terrain_array[v01] = (ranrot_rand() & 4) *25; } // v12 if (base_terrain_array[v1] == base_terrain_array[v2]) base_terrain_array[v12] = base_terrain_array[v1]; else { uint32_t s1 = 0xffff0000 * base_vertex_array[v12].x; uint32_t s2 = 0x00ffff00 * base_vertex_array[v12].y; uint32_t s3 = 0x0000ffff * base_vertex_array[v12].z; ranrot_srand(s1+s2+s3); base_terrain_array[v12] = (ranrot_rand() & 4) *25; } // v20 if (base_terrain_array[v2] == base_terrain_array[v0]) base_terrain_array[v20] = base_terrain_array[v2]; else { uint32_t s1 = 0xffff0000 * base_vertex_array[v20].x; uint32_t s2 = 0x00ffff00 * base_vertex_array[v20].y; uint32_t s3 = 0x0000ffff * base_vertex_array[v20].z; ranrot_srand(s1+s2+s3); base_terrain_array[v20] = (ranrot_rand() & 4) *25; } } } RANROTSetFullSeed(saved_seed); } - (void) paintVertex:(unsigned) vi :(int) seed { RANROTSeed saved_seed = RANROTGetFullSeed(); BOOL isTextured = _texture != nil; GLfloat paint_land[4] = { 0.2, 0.9, 0.0, 1.0}; GLfloat paint_sea[4] = { 0.0, 0.2, 0.9, 1.0}; GLfloat paint_color[4]; Vector v = base_vertex_array[vi]; int r = isTextured ? 0 : base_terrain_array[vi]; // use land color (0) for textured planets int i; double pole_blend = v.z * v.z * polar_color_factor; if (pole_blend < 0.0) pole_blend = 0.0; if (pole_blend > 1.0) pole_blend = 1.0; paint_land[0] = (1.0 - pole_blend)*amb_land[0] + pole_blend*amb_polar_land[0]; paint_land[1] = (1.0 - pole_blend)*amb_land[1] + pole_blend*amb_polar_land[1]; paint_land[2] = (1.0 - pole_blend)*amb_land[2] + pole_blend*amb_polar_land[2]; paint_sea[0] = (1.0 - pole_blend)*amb_sea[0] + pole_blend*amb_polar_sea[0]; paint_sea[1] = (1.0 - pole_blend)*amb_sea[1] + pole_blend*amb_polar_sea[1]; paint_sea[2] = (1.0 - pole_blend)*amb_sea[2] + pole_blend*amb_polar_sea[2]; if (planet_type == STELLAR_TYPE_ATMOSPHERE) // do alphas { paint_land[3] = (1.0 - pole_blend)*amb_land[3] + pole_blend*amb_polar_land[3]; paint_sea[3] = (1.0 - pole_blend)*amb_sea[3] + pole_blend*amb_polar_sea[3]; } ranrot_srand((uint32_t)(seed+v.x*1000+v.y*100+v.z*10)); for (i = 0; i < 3; i++) { double cv = (ranrot_rand() % 100)*0.01; // 0..1 ***** DON'T CHANGE THIS LINE, '% 100' MAY NOT BE EFFICIENT BUT THE PATTERNING IS GOOD. paint_land[i] += (cv - 0.5)*0.1; paint_sea[i] += (cv - 0.5)*0.1; } for (i = 0; i < 4; i++) { if (planet_type == STELLAR_TYPE_ATMOSPHERE && isTextured) paint_color[i] = 1.0; else paint_color[i] = (r * paint_sea[i])*0.01 + ((100 - r) * paint_land[i])*0.01; // finally initialise the color array entry vertexdata.color_array[vi*4 + i] = paint_color[i]; } RANROTSetFullSeed(saved_seed); } - (void) scaleVertices { NSUInteger vi; for (vi = 0; vi < next_free_vertex; vi++) { Vector v = base_vertex_array[vi]; vertexdata.normal_array[vi] = v; vertexdata.vertex_array[vi] = make_vector(v.x * collision_radius, v.y * collision_radius, v.z * collision_radius); vertexdata.uv_array[vi * 2] = texture_uv_array[vi * 2]; vertexdata.uv_array[vi * 2 + 1] = texture_uv_array[vi * 2 + 1]; } } - (void) deleteDisplayLists { unsigned i; for (i = 0; i < MAX_SUBDIVIDE; i++) { if (displayListNames[i] != 0) { glDeleteLists(displayListNames[i], 1); displayListNames[i] = 0; } } } - (void)resetGraphicsState { [self deleteDisplayLists]; } - (BOOL) isExplicitlyTextured { return isTextureImage; } - (OOTexture *) texture { return _texture; } - (void) loadTexture:(NSDictionary *)configuration { [_texture release]; _texture = [OOTexture textureWithConfiguration:configuration extraOptions:kOOTextureAllowCubeMap | kOOTextureRepeatS]; [_texture retain]; [_textureFileName release]; if (_texture != nil) { _textureFileName = [[configuration oo_stringForKey:@"name"] copy]; isTextureImage = YES; } else { _textureFileName = nil; isTextureImage = NO; } } /* Ideally, these would use OOPlanetTextureGenerator. However, it isn't designed to use separate invocations for diffuse and cloud generation, while old-style PlanetEntity calls these from different places for different objects. -- Ahruman 2010-06-04 */ - (OOTexture *) planetTextureWithInfo:(NSDictionary *)info { unsigned char *data; GLuint width, height; fillRanNoiseBuffer(); if (![TextureStore getPlanetTextureNameFor:info intoData:&data width:&width height:&height]) { return nil; } OOPixMap pm = OOMakePixMap(data, width, height, kOOPixMapRGBA, 0, 0); OOTextureGenerator *loader = [[OOPixMapTextureLoader alloc] initWithPixMap:pm textureOptions:kOOTextureDefaultOptions | kOOTextureRepeatS freeWhenDone:YES]; [loader autorelease]; return [OOTexture textureWithGenerator:loader]; } - (OOTexture *) cloudTextureWithCloudColor:(OOColor *)cloudColor cloudImpress:(GLfloat)cloud_impress cloudBias:(GLfloat)cloud_bias { unsigned char *data; GLuint width, height; fillRanNoiseBuffer(); if (![TextureStore getCloudTextureNameFor:cloudColor :cloud_impress :cloud_bias intoData:&data width:&width height:&height]) { return nil; } OOPixMap pm = OOMakePixMap(data, width, height, kOOPixMapRGBA, 0, 0); OOTextureGenerator *loader = [[OOPixMapTextureLoader alloc] initWithPixMap:pm textureOptions:kOOTextureDefaultOptions | kOOTextureRepeatS freeWhenDone:YES]; [loader autorelease]; return [OOTexture textureWithGenerator:loader]; } #ifndef NDEBUG - (NSSet *) allTextures { if (_texture != nil) return [NSSet setWithObject:_texture]; else return nil; } #endif - (BOOL)isPlanet { return YES; } - (BOOL) isVisible { return YES; } @end #endif // !NEW_PLANETS oolite-1.82/src/Core/Entities/PlayerEntity.h000066400000000000000000001037451256642440500210260ustar00rootroot00000000000000/* PlayerEntity.h Entity subclass nominally representing the player's ship, but also implementing much of the interaction, menu system etc. Breaking it up into ten or so different classes is a perennial to-do item. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #import "WormholeEntity.h" #import "ShipEntity.h" #import "GuiDisplayGen.h" #import "OOTypes.h" #import "OOJSPropID.h" #import "OOCommodityMarket.h" @class GuiDisplayGen, OOTrumble, MyOpenGLView, HeadUpDisplay, ShipEntity; @class OOSound, OOSoundSource, OOSoundReferencePoint; @class OOJoystickManager, OOTexture, OOLaserShotEntity; @class StickProfileScreen; #define ALLOW_CUSTOM_VIEWS_WHILE_PAUSED 1 #define SCRIPT_TIMER_INTERVAL 10.0 #ifndef OO_VARIABLE_TORUS_SPEED #define OO_VARIABLE_TORUS_SPEED 1 #endif #define GUI_ROW_INIT(GUI) /*int n_rows = [(GUI) rows]*/ #define GUI_FIRST_ROW(GROUP) ((GUI_DEFAULT_ROWS - GUI_ROW_##GROUP##OPTIONS_END_OF_LIST) / 2) // reposition menu #define GUI_ROW(GROUP,ITEM) (GUI_FIRST_ROW(GROUP) - 4 + GUI_ROW_##GROUP##OPTIONS_##ITEM) #define ENTRY(label, value) label, typedef enum { #include "OOGUIScreenID.tbl" } OOGUIScreenID; #define GALACTIC_HYPERSPACE_ENTRY(label, value) GALACTIC_HYPERSPACE_##label = value, typedef enum { #include "OOGalacticHyperspaceBehaviour.tbl" GALACTIC_HYPERSPACE_MAX = GALACTIC_HYPERSPACE_BEHAVIOUR_FIXED_COORDINATES } OOGalacticHyperspaceBehaviour; #undef ENTRY #undef GALACTIC_HYPERSPACE_ENTRY enum { // Values used for unknown strings. kOOGUIScreenIDDefault = GUI_SCREEN_MAIN, kOOGalacticHyperspaceBehaviourDefault = GALACTIC_HYPERSPACE_BEHAVIOUR_UNKNOWN }; typedef enum { OOPRIMEDEQUIP_ACTIVATED, OOPRIMEDEQUIP_MODE } OOPrimedEquipmentMode; typedef enum { OOSPEECHSETTINGS_OFF = 0, OOSPEECHSETTINGS_COMMS = 1, OOSPEECHSETTINGS_ALL = 2 } OOSpeechSettings; typedef enum { OOLRC_MODE_NORMAL = 0, OOLRC_MODE_ECONOMY = 1, OOLRC_MODE_GOVERNMENT = 2, OOLRC_MODE_TECHLEVEL = 3 } OOLongRangeChartMode; // When fully zoomed in, chart shows area of galaxy that's 64x64 galaxy units. #define CHART_WIDTH_AT_MAX_ZOOM 64.0 #define CHART_HEIGHT_AT_MAX_ZOOM 64.0 // Galaxy width / width of chart area at max zoom #define CHART_MAX_ZOOM (256.0/CHART_WIDTH_AT_MAX_ZOOM) //start scrolling when cursor is this number of units away from centre #define CHART_SCROLL_AT_X 25.0 #define CHART_SCROLL_AT_Y 31.0 #define CHART_CLIP_BORDER 10.0 #define CHART_SCREEN_VERTICAL_CENTRE (10*MAIN_GUI_ROW_HEIGHT) #define CHART_ZOOM_SPEED_FACTOR 1.05 #define CHART_ZOOM_SHOW_LABELS 2.0 // OO_RESOLUTION_OPTION: true if full screen resolution can be changed. #if OOLITE_MAC_OS_X && OOLITE_64_BIT #define OO_RESOLUTION_OPTION 0 #else #define OO_RESOLUTION_OPTION 1 #endif enum { GUI_ROW_OPTIONS_QUICKSAVE, GUI_ROW_OPTIONS_SAVE, GUI_ROW_OPTIONS_LOAD, GUI_ROW_OPTIONS_SPACER1, GUI_ROW_OPTIONS_GAMEOPTIONS, GUI_ROW_OPTIONS_SPACER2, GUI_ROW_OPTIONS_BEGIN_NEW, #if OOLITE_SDL GUI_ROW_OPTIONS_SPACER3, GUI_ROW_OPTIONS_QUIT, #endif GUI_ROW_OPTIONS_END_OF_LIST, STATUS_EQUIPMENT_FIRST_ROW = 10, STATUS_EQUIPMENT_MAX_ROWS = 8, GUI_ROW_EQUIPMENT_START = 3, GUI_MAX_ROWS_EQUIPMENT = 12, GUI_ROW_EQUIPMENT_DETAIL = GUI_ROW_EQUIPMENT_START + GUI_MAX_ROWS_EQUIPMENT + 1, GUI_ROW_EQUIPMENT_CASH = 1, GUI_ROW_MARKET_KEY = 1, GUI_ROW_MARKET_START = 2, GUI_ROW_MARKET_SCROLLUP = 4, GUI_ROW_MARKET_SCROLLDOWN = 16, GUI_ROW_MARKET_LAST = 18, GUI_ROW_MARKET_END = 19, GUI_ROW_MARKET_CASH = 20, GUI_ROW_INTERFACES_HEADING = 1, GUI_ROW_INTERFACES_START = 3, GUI_MAX_ROWS_INTERFACES = 12, GUI_ROW_INTERFACES_DETAIL = GUI_ROW_INTERFACES_START + GUI_MAX_ROWS_INTERFACES + 1, GUI_ROW_NO_INTERFACES = 3, GUI_ROW_SCENARIOS_START = 3, GUI_MAX_ROWS_SCENARIOS = 12, GUI_ROW_SCENARIOS_DETAIL = GUI_ROW_SCENARIOS_START + GUI_MAX_ROWS_SCENARIOS + 2, GUI_ROW_CHART_SYSTEM = 19, GUI_ROW_PLANET_FINDER = 20 }; #if GUI_FIRST_ROW() < 0 # error Too many items in OPTIONS list! #endif enum { GUI_ROW_GAMEOPTIONS_AUTOSAVE, GUI_ROW_GAMEOPTIONS_DOCKINGCLEARANCE, GUI_ROW_GAMEOPTIONS_SPACER1, GUI_ROW_GAMEOPTIONS_VOLUME, #if OOLITE_SPEECH_SYNTH GUI_ROW_GAMEOPTIONS_SPEECH, #if !OOLITE_MAC_OS_X // FIXME: should have voice option for OS X GUI_ROW_GAMEOPTIONS_SPEECH_LANGUAGE, GUI_ROW_GAMEOPTIONS_SPEECH_GENDER, #endif #endif GUI_ROW_GAMEOPTIONS_MUSIC, #if OO_RESOLUTION_OPTION GUI_ROW_GAMEOPTIONS_SPACER2, GUI_ROW_GAMEOPTIONS_DISPLAY, #endif GUI_ROW_GAMEOPTIONS_DISPLAYSTYLE, GUI_ROW_GAMEOPTIONS_DETAIL, GUI_ROW_GAMEOPTIONS_WIREFRAMEGRAPHICS, #if !NEW_PLANETS GUI_ROW_GAMEOPTIONS_PROCEDURALLYTEXTUREDPLANETS, #endif GUI_ROW_GAMEOPTIONS_SHADEREFFECTS, #if OOLITE_SDL GUI_ROW_GAMEOPTIONS_GAMMA, #endif GUI_ROW_GAMEOPTIONS_FOV, GUI_ROW_GAMEOPTIONS_SPACER_STICKMAPPER, GUI_ROW_GAMEOPTIONS_STICKMAPPER, GUI_ROW_GAMEOPTIONS_KEYMAPPER, GUI_ROW_GAMEOPTIONS_SPACER3, GUI_ROW_GAMEOPTIONS_BACK, GUI_ROW_GAMEOPTIONS_END_OF_LIST }; #if GUI_FIRST_ROW() < 0 # error Too many items in GAMEOPTIONS list! #endif typedef enum { // Exposed to shaders. SCOOP_STATUS_NOT_INSTALLED = 0, SCOOP_STATUS_FULL_HOLD, SCOOP_STATUS_OKAY, SCOOP_STATUS_ACTIVE } OOFuelScoopStatus; enum { ALERT_FLAG_DOCKED = 0x010, ALERT_FLAG_MASS_LOCK = 0x020, ALERT_FLAG_YELLOW_LIMIT = 0x03f, ALERT_FLAG_TEMP = 0x040, ALERT_FLAG_ALT = 0x080, ALERT_FLAG_ENERGY = 0x100, ALERT_FLAG_HOSTILES = 0x200 }; typedef uint16_t OOAlertFlags; typedef enum { // Exposed to shaders. MISSILE_STATUS_SAFE, MISSILE_STATUS_ARMED, MISSILE_STATUS_TARGET_LOCKED } OOMissileStatus; typedef enum { PLAYER_FLEEING_UNLIKELY = -1, PLAYER_FLEEING_NONE = 0, PLAYER_FLEEING_MAYBE = 1, PLAYER_FLEEING_CARGO = 2, PLAYER_FLEEING_LIKELY = 3 } OOPlayerFleeingStatus; typedef enum { MARKET_FILTER_MODE_OFF = 0, MARKET_FILTER_MODE_TRADE = 1, MARKET_FILTER_MODE_HOLD = 2, MARKET_FILTER_MODE_STOCK = 3, MARKET_FILTER_MODE_LEGAL = 4, MARKET_FILTER_MODE_RESTRICTED = 5, // import or export MARKET_FILTER_MODE_MAX = 5 // always equal to highest real mode } OOMarketFilterMode; typedef enum { MARKET_SORTER_MODE_OFF = 0, MARKET_SORTER_MODE_ALPHA = 1, MARKET_SORTER_MODE_PRICE = 2, MARKET_SORTER_MODE_STOCK = 3, MARKET_SORTER_MODE_HOLD = 4, MARKET_SORTER_MODE_UNIT = 5, MARKET_SORTER_MODE_MAX = 5 // always equal to highest real mode } OOMarketSorterMode; #define ECM_ENERGY_DRAIN_FACTOR 20.0f #define ECM_DURATION 2.5f #define ROLL_DAMPING_FACTOR 1.0f #define PITCH_DAMPING_FACTOR 1.0f #define YAW_DAMPING_FACTOR 1.0f #define PLAYER_MAX_WEAPON_TEMP 256.0f #ifdef OO_DUMP_PLANETINFO // debugging #define PLAYER_MAX_FUEL 7000 #else #define PLAYER_MAX_FUEL 70 #endif #define PLAYER_MAX_MISSILES 16 #define PLAYER_STARTING_MAX_MISSILES 4 #define PLAYER_STARTING_MISSILES 3 #define PLAYER_DIAL_MAX_ALTITUDE 40000.0 #define PLAYER_SUPER_ALTITUDE2 10000000000.0 #define PLAYER_MAX_TRUMBLES 24 #define PLAYER_TARGET_MEMORY_SIZE 16 #if OO_VARIABLE_TORUS_SPEED #define HYPERSPEED_FACTOR [PLAYER hyperspeedFactor] #define MIN_HYPERSPEED_FACTOR 32.0 #define MAX_HYPERSPEED_FACTOR 1024.0 #else #define HYPERSPEED_FACTOR 32.0 #endif #define PLAYER_SHIP_DESC @"cobra3-player" #define ESCAPE_SEQUENCE_TIME 10.0 #define FORWARD_FACING_STRING DESC(@"forward-facing-string") #define AFT_FACING_STRING DESC(@"aft-facing-string") #define PORT_FACING_STRING DESC(@"port-facing-string") #define STARBOARD_FACING_STRING DESC(@"starboard-facing-string") #define KEY_REPEAT_INTERVAL 0.20 #define PLAYER_SHIP_CLOCK_START (2084004 * 86400.0) // adding or removing a player ship subentity increases or decreases the ship's trade-in factor respectively by this amount #define PLAYER_SHIP_SUBENTITY_TRADE_IN_VALUE 3 #define CONTRACTS_GOOD_KEY @"contracts_fulfilled" #define CONTRACTS_BAD_KEY @"contracts_expired" #define CONTRACTS_UNKNOWN_KEY @"contracts_unknown" #define PASSAGE_GOOD_KEY @"passage_fulfilled" #define PASSAGE_BAD_KEY @"passage_expired" #define PASSAGE_UNKNOWN_KEY @"passage_unknown" #define PARCEL_GOOD_KEY @"parcels_fulfilled" #define PARCEL_BAD_KEY @"parcels_expired" #define PARCEL_UNKNOWN_KEY @"parcels_unknown" #define SCANNER_ZOOM_RATE_UP 2.0 #define SCANNER_ZOOM_RATE_DOWN -8.0 #define SCANNER_ECM_FUZZINESS 1.25 #define PLAYER_INTERNAL_DAMAGE_FACTOR 31 #define PLAYER_DOCKING_AI_NAME @"oolite-player-AI.plist" #define MANIFEST_SCREEN_ROW_BACK 1 #define MANIFEST_SCREEN_ROW_NEXT ([[PLAYER hud] isHidden]?27:20) #define MISSION_DEST_LEGACY @"__oolite_legacy_destinations" @interface PlayerEntity: ShipEntity { @private OOSystemID system_id; OOSystemID target_system_id; float occlusion_dial; OOSystemID found_system_id; int ship_trade_in_factor; NSDictionary *worldScripts; NSDictionary *worldScriptsRequiringTickle; NSMutableDictionary *commodityScripts; NSMutableDictionary *mission_variables; NSMutableDictionary *localVariables; NSString *_missionTitle; NSInteger /*OOGUIRow*/ missionTextRow; NSString *missionChoice; BOOL _missionWithCallback; BOOL _missionAllowInterrupt; BOOL _missionTextEntry; OOGUIScreenID _missionExitScreen; NSString *specialCargo; NSMutableArray *commLog; NSMutableArray *eqScripts; NSDictionary *_missionOverlayDescriptor; NSDictionary *_missionBackgroundDescriptor; OOGUIBackgroundSpecial _missionBackgroundSpecial; NSDictionary *_equipScreenBackgroundDescriptor; NSString *_missionScreenID; BOOL found_equipment; NSMutableDictionary *reputation; unsigned max_passengers; NSMutableArray *passengers; NSMutableDictionary *passenger_record; NSMutableArray *parcels; NSMutableDictionary *parcel_record; NSMutableArray *contracts; NSMutableDictionary *contract_record; NSMutableDictionary *shipyard_record; NSMutableDictionary *missionDestinations; NSMutableArray *roleWeights; // temporary flags for role actions taking multiple steps, cleared on jump NSMutableDictionary *roleWeightFlags; NSMutableArray *roleSystemList; // list of recently visited sysids double script_time; double script_time_check; double script_time_interval; NSString *lastTextKey; double ship_clock; double ship_clock_adjust; double fps_check_time; int fps_counter; double last_fps_check_time; NSString *planetSearchString; OOMatrix playerRotMatrix; BOOL showingLongRangeChart; // For OO-GUI based save screen NSString *commanderNameString; NSMutableArray *cdrDetailArray; int currentPage; BOOL pollControls; // ...end save screen NSInteger marketOffset; OOCommodityType marketSelectedCommodity; OOMarketFilterMode marketFilterMode; OOMarketSorterMode marketSorterMode; OOWeakReference *_dockedStation; /* Used by the DOCKING_CLEARANCE code to implement docking at non-main * stations. Could possibly overload use of 'dockedStation' instead * but that needs futher investigation to ensure it doesn't break anything. */ StationEntity *targetDockStation; HeadUpDisplay *hud; NSMutableDictionary *multiFunctionDisplayText; NSMutableArray *multiFunctionDisplaySettings; NSUInteger activeMFD; NSMutableDictionary *customDialSettings; GLfloat roll_delta, pitch_delta, yaw_delta; GLfloat launchRoll; GLfloat forward_shield, aft_shield; GLfloat max_forward_shield, max_aft_shield, forward_shield_recharge_rate, aft_shield_recharge_rate; OOTimeDelta forward_shot_time, aft_shot_time, port_shot_time, starboard_shot_time; OOWeaponFacing chosen_weapon_facing; // for purchasing weapons double ecm_start_time; double last_ecm_time; OOGUIScreenID gui_screen; OOAlertFlags alertFlags; OOAlertCondition alertCondition; OOAlertCondition lastScriptAlertCondition; OOPlayerFleeingStatus fleeing_status; OOMissileStatus missile_status; NSUInteger activeMissile; NSUInteger primedEquipment; NSString *_fastEquipmentA; NSString *_fastEquipmentB; OOCargoQuantity current_cargo; NSPoint cursor_coordinates; NSPoint chart_cursor_coordinates; NSPoint chart_focus_coordinates; NSPoint chart_centre_coordinates; // where we want the chart centre to be - used for smooth transitions NSPoint target_chart_centre; // Chart zoom is 1.0 when fully zoomed in and increases as we zoom out. The reason I've done it that way round // is because we might want to implement bigger galaxies one day, and thus may need to zoom out indefinitely. OOScalar chart_zoom; OOScalar target_chart_zoom; OOScalar saved_chart_zoom; OORouteType ANA_mode; OOTimeDelta witchspaceCountdown; // player commander data NSString *_commanderName; NSString *_lastsaveName; NSPoint galaxy_coordinates; OOCreditsQuantity credits; OOGalaxyID galaxy_number; OOCommodityMarket *shipCommodityData; ShipEntity *missile_entity[PLAYER_MAX_MISSILES]; // holds the actual missile entities or equivalents OOUniversalID _dockTarget; // used by the escape pod code int legalStatus; // legalStatus both is and isn't an OOCreditsQuantity, because of quantum. int market_rnd; unsigned ship_kills; OOCompassMode compassMode; OOWeakReference *compassTarget; GLfloat fuel_leak_rate; #if OO_VARIABLE_TORUS_SPEED GLfloat hyperspeedFactor; #endif // keys! NSDictionary *keyconfig_settings; OOKeyCode key_roll_left; OOKeyCode key_roll_right; OOKeyCode key_pitch_forward; OOKeyCode key_pitch_back; OOKeyCode key_yaw_left; OOKeyCode key_yaw_right; OOKeyCode key_view_forward; // && undock OOKeyCode key_view_aft; // && options menu OOKeyCode key_view_port; // && equipment screen OOKeyCode key_view_starboard; // && interfaces screen OOKeyCode key_gui_screen_status; OOKeyCode key_gui_chart_screens; OOKeyCode key_gui_system_data; OOKeyCode key_gui_market; OOKeyCode key_gui_arrow_left; OOKeyCode key_gui_arrow_right; OOKeyCode key_gui_arrow_up; OOKeyCode key_gui_arrow_down; OOKeyCode key_increase_speed; OOKeyCode key_decrease_speed; OOKeyCode key_inject_fuel; OOKeyCode key_fire_lasers; OOKeyCode key_launch_missile; OOKeyCode key_next_missile; OOKeyCode key_ecm; OOKeyCode key_prime_equipment; OOKeyCode key_activate_equipment; OOKeyCode key_mode_equipment; OOKeyCode key_fastactivate_equipment_a; OOKeyCode key_fastactivate_equipment_b; OOKeyCode key_target_missile; OOKeyCode key_untarget_missile; OOKeyCode key_target_incoming_missile; OOKeyCode key_ident_system; OOKeyCode key_scanner_zoom; OOKeyCode key_scanner_unzoom; OOKeyCode key_launch_escapepod; OOKeyCode key_galactic_hyperspace; OOKeyCode key_hyperspace; OOKeyCode key_jumpdrive; OOKeyCode key_dump_cargo; OOKeyCode key_rotate_cargo; OOKeyCode key_autopilot; OOKeyCode key_autodock; OOKeyCode key_snapshot; OOKeyCode key_docking_music; OOKeyCode key_advanced_nav_array; OOKeyCode key_map_home; OOKeyCode key_map_info; OOKeyCode key_pausebutton; OOKeyCode key_show_fps; OOKeyCode key_mouse_control; OOKeyCode key_hud_toggle; OOKeyCode key_comms_log; OOKeyCode key_prev_compass_mode; OOKeyCode key_next_compass_mode; OOKeyCode key_chart_highlight; OOKeyCode key_market_filter_cycle; OOKeyCode key_market_sorter_cycle; OOKeyCode key_next_target; OOKeyCode key_previous_target; OOKeyCode key_custom_view; OOKeyCode key_docking_clearance_request; #ifndef NDEBUG OOKeyCode key_dump_target_state; #endif OOKeyCode key_weapons_online_toggle; OOKeyCode key_cycle_mfd; OOKeyCode key_switch_mfd; OOKeyCode key_oxzmanager_setfilter; OOKeyCode key_oxzmanager_showinfo; OOKeyCode key_oxzmanager_extract; OOKeyCode key_inc_field_of_view; OOKeyCode key_dec_field_of_view; // save-file NSString *save_path; NSString *scenarioKey; // position of viewports Vector forwardViewOffset, aftViewOffset, portViewOffset, starboardViewOffset; Vector _sysInfoLight; // trumbles NSUInteger trumbleCount; OOTrumble *trumble[PLAYER_MAX_TRUMBLES]; float _trumbleAppetiteAccumulator; // smart zoom GLfloat scanner_zoom_rate; // target memory // TODO: this should use weakrefs NSMutableArray *target_memory; int target_memory_index; // custom view points Quaternion customViewQuaternion; OOMatrix customViewMatrix; Vector customViewOffset, customViewForwardVector, customViewUpVector, customViewRightVector; NSString *customViewDescription; // docking reports NSMutableString *dockingReport; // Woo, flags. unsigned suppressTargetLost: 1, // smart target lst reports scoopsActive: 1, // smart fuelscoops scoopOverride: 1, //scripted to just be on, ignoring normal rules game_over: 1, finished: 1, bomb_detonated: 1, autopilot_engaged: 1, afterburner_engaged: 1, afterburnerSoundLooping: 1, hyperspeed_engaged: 1, travelling_at_hyperspeed: 1, hyperspeed_locked: 1, ident_engaged: 1, galactic_witchjump: 1, ecm_in_operation: 1, show_info_flag: 1, showDemoShips: 1, rolling, pitching, yawing: 1, using_mining_laser: 1, mouse_control_on: 1, keyboardRollOverride: 1, // Handle keyboard roll... keyboardPitchOverride: 1, // ...and pitch override separately - (fix for BUG #17490) keyboardYawOverride: 1, waitingForStickCallback: 1, weapons_online: 1, launchingMissile: 1, replacingMissile: 1; #if OOLITE_ESPEAK unsigned int voice_no; BOOL voice_gender_m; #endif OOSpeechSettings isSpeechOn; // For PlayerEntity (StickMapper) int selFunctionIdx; NSArray *stickFunctions; OOGalacticHyperspaceBehaviour galacticHyperspaceBehaviour; NSPoint galacticHyperspaceFixedCoords; OOLongRangeChartMode longRangeChartMode; NSArray *_customViews; NSUInteger _customViewIndex; OODockingClearanceStatus dockingClearanceStatus; NSMutableArray *scannedWormholes; WormholeEntity *wormhole; ShipEntity *demoShip; // Used while docked to maintain demo ship rotation. OOLaserShotEntity *lastShot; // used to correctly position laser shots on first frame of firing StickProfileScreen *stickProfileScreen; double maxFieldOfView; double fieldOfView; #if OO_FOV_INFLIGHT_CONTROL_ENABLED double fov_delta; #endif } + (PlayerEntity *) sharedPlayer; - (void) deferredInit; - (BOOL) setUpAndConfirmOK:(BOOL)stopOnError; - (BOOL) setUpAndConfirmOK:(BOOL)stopOnError saveGame:(BOOL)loadingGame; - (void) completeSetUp; - (void) completeSetUpAndSetTarget:(BOOL)setTarget; - (void) startUpComplete; - (NSString *) commanderName; - (void) setCommanderName:(NSString *)value; - (NSString *) lastsaveName; - (void) setLastsaveName:(NSString *)value; - (BOOL) isDocked; - (void) warnAboutHostiles; - (void) unloadCargoPods; - (void) loadCargoPods; - (void) unloadAllCargoPodsForType:(OOCommodityType)type toManifest:(OOCommodityMarket *) manifest; - (void) unloadCargoPodsForType:(OOCommodityType)type amount:(OOCargoQuantity) quantity; - (void) loadCargoPodsForType:(OOCommodityType)type fromManifest:(OOCommodityMarket *) manifest; - (void) loadCargoPodsForType:(OOCommodityType)type amount:(OOCargoQuantity) quantity; - (OOCommodityMarket *) shipCommodityData; - (OOCreditsQuantity) deciCredits; - (int) random_factor; - (void) setRandom_factor:(int)rf; - (OOGalaxyID) galaxyNumber; - (NSPoint) galaxy_coordinates; - (void) setGalaxyCoordinates:(NSPoint)newPosition; - (NSPoint) cursor_coordinates; - (NSPoint) chart_centre_coordinates; - (OOScalar) chart_zoom; - (NSPoint) adjusted_chart_centre; - (OORouteType) ANAMode; - (OOSystemID) systemID; - (void) setSystemID:(OOSystemID) sid; - (OOSystemID) targetSystemID; - (void) setTargetSystemID:(OOSystemID) sid; - (OOSystemID) nextHopTargetSystemID; - (NSDictionary *) commanderDataDictionary; - (BOOL)setCommanderDataFromDictionary:(NSDictionary *) dict; - (void) doBookkeeping:(double) delta_t; - (BOOL) isValidTarget:(Entity*)target; - (BOOL) massLocked; - (BOOL) atHyperspeed; - (float) occlusionLevel; - (void) setOcclusionLevel:(float)level; - (void) setDockedAtMainStation; - (StationEntity *) dockedStation; // Dumb setter; callers are responsible for sanity. - (void) setDockedStation:(StationEntity *)station; - (BOOL) engageAutopilotToStation:(StationEntity *)stationForDocking; - (void) disengageAutopilot; - (void) resetAutopilotAI; - (void) setTargetDockStationTo:(StationEntity *) value; - (StationEntity *) getTargetDockStation; - (HeadUpDisplay *) hud; - (BOOL) switchHudTo:(NSString *)hudFileName; - (void) resetHud; - (float) dialCustomFloat:(NSString *)dialKey; - (NSString *) dialCustomString:(NSString *)dialKey; - (OOColor *) dialCustomColor:(NSString *)dialKey; - (void) setDialCustom:(id)value forKey:(NSString *)key; - (NSArray *) multiFunctionDisplayList; - (NSString *) multiFunctionText:(NSUInteger) index; - (void) setMultiFunctionText:(NSString *)text forKey:(NSString *)key; - (BOOL) setMultiFunctionDisplay:(NSUInteger) index toKey:(NSString *)key; - (void) cycleMultiFunctionDisplay:(NSUInteger) index; - (void) selectNextMultiFunctionDisplay; - (NSUInteger) activeMFD; - (void) setShowDemoShips:(BOOL) value; - (BOOL) showDemoShips; - (GLfloat) forwardShieldLevel; - (GLfloat) aftShieldLevel; - (GLfloat) baseMass; - (void) setForwardShieldLevel:(GLfloat)level; - (void) setAftShieldLevel:(GLfloat)level; - (float) forwardShieldRechargeRate; - (float) aftShieldRechargeRate; - (void) setMaxForwardShieldLevel:(float)new; - (void) setMaxAftShieldLevel:(float)new; - (void) setForwardShieldRechargeRate:(float)new; - (void) setAftShieldRechargeRate:(float)new; // return keyconfig.plist settings for scripting - (NSDictionary *) keyConfig; - (BOOL) isMouseControlOn; - (GLfloat) dialRoll; - (GLfloat) dialPitch; - (GLfloat) dialYaw; - (GLfloat) dialSpeed; - (GLfloat) dialHyperSpeed; - (void) currentWeaponStats; - (GLfloat) dialForwardShield; - (GLfloat) dialAftShield; - (GLfloat) dialEnergy; - (GLfloat) dialMaxEnergy; - (GLfloat) dialFuel; - (GLfloat) dialHyperRange; - (GLfloat) dialAltitude; - (unsigned) countMissiles; - (OOMissileStatus) dialMissileStatus; - (OOFuelScoopStatus) dialFuelScoopStatus; - (float) fuelLeakRate; - (void) setFuelLeakRate:(float)value; #if OO_VARIABLE_TORUS_SPEED - (GLfloat) hyperspeedFactor; #endif - (BOOL) injectorsEngaged; - (BOOL) hyperspeedEngaged; - (double) clockTime; // Note that this is not an OOTimeAbsolute - (double) clockTimeAdjusted; // Note that this is not an OOTimeAbsolute - (BOOL) clockAdjusting; - (void) addToAdjustTime:(double) seconds ; - (NSString *) dial_clock; - (NSString *) dial_clock_adjusted; - (NSString *) dial_fpsinfo; - (NSString *) dial_objinfo; - (NSMutableArray *) commLog; - (Entity *) compassTarget; - (void) setCompassTarget:(Entity *)value; - (void) validateCompassTarget; - (NSString *) compassTargetLabel; - (OOCompassMode) compassMode; - (void) setCompassMode:(OOCompassMode)value; - (void) setPrevCompassMode; - (void) setNextCompassMode; - (NSUInteger) activeMissile; - (void) setActiveMissile:(NSUInteger)value; - (NSUInteger) dialMaxMissiles; - (BOOL) dialIdentEngaged; - (void) setDialIdentEngaged:(BOOL)newValue; - (NSString *) specialCargo; - (NSString *) dialTargetName; - (ShipEntity *) missileForPylon:(NSUInteger)value; - (void) safeAllMissiles; - (void) selectNextMissile; - (void) tidyMissilePylons; - (BOOL) removeFromPylon:(NSUInteger) pylon; - (BOOL) assignToActivePylon:(NSString *)identifierKey; - (void) clearAlertFlags; - (int) alertFlags; - (void) setAlertFlag:(int)flag to:(BOOL)value; - (OOAlertCondition) alertCondition; - (OOPlayerFleeingStatus) fleeingStatus; - (BOOL) mountMissile:(ShipEntity *)missile; - (BOOL) mountMissileWithRole:(NSString *)role; - (OOEnergyUnitType) installedEnergyUnitType; - (OOEnergyUnitType) energyUnitType; - (ShipEntity *) launchMine:(ShipEntity *)mine; - (BOOL) activateCloakingDevice; - (void) deactivateCloakingDevice; - (double) scannerFuzziness; - (BOOL) weaponsOnline; - (void) setWeaponsOnline:(BOOL)newValue; - (BOOL) fireMainWeapon; - (OOWeaponType) weaponForFacing:(OOWeaponFacing)facing; - (OOWeaponType) currentWeapon; - (Vector) currentLaserOffset; - (void) rotateCargo; - (BOOL) hasSufficientFuelForJump; - (BOOL) witchJumpChecklist:(BOOL)isGalacticJump; - (void) enterGalacticWitchspace; - (void) setJumpType:(BOOL)isGalacticJump; - (BOOL) takeInternalDamage; - (BOOL) endScenario:(NSString *)key; - (NSMutableArray *) roleWeights; - (void) addRoleForAggression:(ShipEntity *)victim; - (void) addRoleForMining; - (void) addRoleToPlayer:(NSString *)role; - (void) addRoleToPlayer:(NSString *)role inSlot:(NSUInteger)slot; - (void) clearRoleFromPlayer:(BOOL)includingLongRange; - (void) clearRolesFromPlayer:(float)chance; - (NSUInteger) maxPlayerRoles; - (void) updateSystemMemory; - (void) loseTargetStatus; - (void) docked; - (void) setGuiToStatusScreen; - (NSArray *) equipmentList; // Each entry is an array with a string followed by a boolean indicating availability (NO = damaged). - (NSString *) primedEquipmentName:(NSInteger)offset; - (NSUInteger) primedEquipmentCount; - (void) activatePrimableEquipment:(NSUInteger)index withMode:(OOPrimedEquipmentMode)mode; - (NSString *) fastEquipmentA; - (NSString *) fastEquipmentB; - (void) setFastEquipmentA:(NSString *)eqKey; - (void) setFastEquipmentB:(NSString *)eqKey; - (NSArray *) cargoList; //- (NSArray *) cargoListForScripting; // now in ShipEntity - (unsigned) legalStatusOfCargoList; - (void) setGuiToSystemDataScreen; - (NSDictionary *) markedDestinations; - (void) setGuiToLongRangeChartScreen; - (void) setGuiToShortRangeChartScreen; - (void) setGuiToChartScreenFrom: (OOGUIScreenID) oldScreen; - (void) setGuiToLoadSaveScreen; - (void) setGuiToGameOptionsScreen; - (OOWeaponFacingSet) availableFacings; - (void) setGuiToEquipShipScreen:(int)skip selectingFacingFor:(NSString *)eqKeyForSelectFacing; - (void) setGuiToEquipShipScreen:(int)skip; - (void) setGuiToInterfacesScreen:(int)skip; - (void) showInformationForSelectedInterface; - (void) activateSelectedInterface; - (void) highlightEquipShipScreenKey:(NSString *)key; - (void) showInformationForSelectedUpgrade; - (void) showInformationForSelectedUpgradeWithFormatString:(NSString *)extraString; - (BOOL) setWeaponMount:(OOWeaponFacing)chosen_weapon_facing toWeapon:(NSString *)eqKey; - (BOOL) changePassengerBerths:(int) addRemove; - (OOCargoQuantity) cargoQuantityForType:(OOCommodityType)type; - (OOCargoQuantity) setCargoQuantityForType:(OOCommodityType)type amount:(OOCargoQuantity)amount; - (void) calculateCurrentCargo; - (void) setGuiToMarketScreen; - (void) setGuiToMarketInfoScreen; - (NSArray *) applyMarketFilter:(NSArray *)goods onMarket:(OOCommodityMarket *)market; - (NSArray *) applyMarketSorter:(NSArray *)goods onMarket:(OOCommodityMarket *)market; - (OOCommodityMarket *) localMarket; - (void) setupStartScreenGui; - (void) setGuiToIntroFirstGo:(BOOL)justCobra; - (void) setGuiToKeySettingsScreen; - (void) setGuiToOXZManager; - (void) noteGUIWillChangeTo:(OOGUIScreenID)toScreen; - (void) noteGUIDidChangeFrom:(OOGUIScreenID)fromScreen to:(OOGUIScreenID)toScreen; - (void) noteViewDidChangeFrom:(OOViewID)fromView toView:(OOViewID)toView; - (OOGUIScreenID) guiScreen; - (void) buySelectedItem; - (BOOL) tryBuyingCommodity:(OOCommodityType)type all:(BOOL)all; - (BOOL) trySellingCommodity:(OOCommodityType)type all:(BOOL)all; - (OOSpeechSettings) isSpeechOn; - (void) addEquipmentFromCollection:(id)equipment; // equipment may be an array, a set, a dictionary whose values are all YES, or a string. - (void) getFined; - (void) adjustTradeInFactorBy:(int)value; - (int) tradeInFactor; - (double) renovationCosts; - (double) renovationFactor; - (void) setDefaultViewOffsets; - (void) setDefaultCustomViews; - (Vector) weaponViewOffset; - (void) setUpTrumbles; - (void) addTrumble:(OOTrumble *)papaTrumble; - (void) removeTrumble:(OOTrumble *)deadTrumble; - (OOTrumble **) trumbleArray; - (NSUInteger) trumbleCount; // loading and saving trumbleCount - (id) trumbleValue; - (void) setTrumbleValueFrom:(NSObject *)trumbleValue; - (float) trumbleAppetiteAccumulator; - (void) setTrumbleAppetiteAccumulator:(float)value; - (void) mungChecksumWithNSString:(NSString *)str; - (NSString *)screenModeStringForWidth:(unsigned)inWidth height:(unsigned)inHeight refreshRate:(float)inRate; - (void) suppressTargetLost; - (void) setScoopsActive; - (void) clearTargetMemory; - (NSMutableArray *) targetMemory; - (BOOL) moveTargetMemoryBy:(int)delta; - (void) printIdentLockedOnForMissile:(BOOL)missile; - (void) applyYaw:(GLfloat) yaw; /* GILES custom viewpoints */ // custom view points - (Quaternion)customViewQuaternion; - (OOMatrix)customViewMatrix; - (Vector)customViewOffset; - (Vector)customViewForwardVector; - (Vector)customViewUpVector; - (Vector)customViewRightVector; - (NSString *)customViewDescription; - (void)resetCustomView; - (void)setCustomViewDataFromDictionary:(NSDictionary*) viewDict withScaling:(BOOL)withScaling; - (HPVector) viewpointPosition; - (HPVector) breakPatternPosition; - (Vector) viewpointOffset; - (Vector) viewpointOffsetAft; - (Vector) viewpointOffsetForward; - (Vector) viewpointOffsetPort; - (Vector) viewpointOffsetStarboard; - (NSDictionary *) missionOverlayDescriptor; - (NSDictionary *) missionOverlayDescriptorOrDefault; - (void) setMissionOverlayDescriptor:(NSDictionary *)descriptor; - (NSDictionary *) missionBackgroundDescriptor; - (NSDictionary *) missionBackgroundDescriptorOrDefault; - (void) setMissionBackgroundDescriptor:(NSDictionary *)descriptor; - (OOGUIBackgroundSpecial) missionBackgroundSpecial; - (void) setMissionBackgroundSpecial:(NSString *)special; - (void) setMissionExitScreen:(OOGUIScreenID)screen; - (OOGUIScreenID) missionExitScreen; // Nasty hack to keep background textures around while on equip screens. - (NSDictionary *) equipScreenBackgroundDescriptor; - (void) setEquipScreenBackgroundDescriptor:(NSDictionary *)descriptor; - (BOOL) scriptsLoaded; - (NSArray *) worldScriptNames; - (NSDictionary *) worldScriptsByName; - (OOScript *) commodityScriptNamed:(NSString *)script; // *** World script events. // In general, script events should be sent through doScriptEvent:..., which // will forward to the world scripts. - (BOOL) doWorldEventUntilMissionScreen:(jsid)message; - (void) doWorldScriptEvent:(jsid)message inContext:(JSContext *)context withArguments:(jsval *)argv count:(uintN)argc timeLimit:(OOTimeDelta)limit; - (BOOL)showInfoFlag; - (void) setGalacticHyperspaceBehaviour:(OOGalacticHyperspaceBehaviour) galacticHyperspaceBehaviour; - (OOGalacticHyperspaceBehaviour) galacticHyperspaceBehaviour; - (void) setGalacticHyperspaceFixedCoords:(NSPoint)point; - (void) setGalacticHyperspaceFixedCoordsX:(unsigned char)x y:(unsigned char)y; - (NSPoint) galacticHyperspaceFixedCoords; - (OOLongRangeChartMode) longRangeChartMode; - (void) setLongRangeChartMode:(OOLongRangeChartMode) mode; - (BOOL) scoopOverride; - (void) setScoopOverride:(BOOL)newValue; - (void) setDockTarget:(ShipEntity *)entity; - (BOOL) clearedToDock; - (void) setDockingClearanceStatus:(OODockingClearanceStatus) newValue; - (OODockingClearanceStatus) getDockingClearanceStatus; - (void) penaltyForUnauthorizedDocking; - (NSArray *) scannedWormholes; - (WormholeEntity *) wormhole; - (void) setWormhole:(WormholeEntity *)newWormhole; - (void) addScannedWormhole:(WormholeEntity*)wormhole; - (void) initialiseMissionDestinations:(NSDictionary *)destinations andLegacy:(NSArray *)legacy; - (NSString *)markerKey:(NSDictionary*)marker; - (void) addMissionDestinationMarker:(NSDictionary *)marker; - (BOOL) removeMissionDestinationMarker:(NSDictionary *)marker; - (NSMutableDictionary*) getMissionDestinations; - (void) setLastShot:(OOLaserShotEntity *)shot; - (void) showShipModelWithKey:(NSString *)shipKey shipData:(NSDictionary *)shipData personality:(uint16_t)personality factorX:(GLfloat)factorX factorY:(GLfloat)factorY factorZ:(GLfloat)factorZ inContext:(NSString *)context; - (void) doGuiScreenResizeUpdates; /* Fractional expression of amount of entry inside a planet's atmosphere. 0.0f is out of atmosphere, 1.0f is fully in and is normally associated with the point of ship destruct due to altitude. */ - (GLfloat) insideAtmosphereFraction; @end /* Use PLAYER to refer to the shared player object in cases where it is assumed to exist (i.e., except during early initialization). */ OOINLINE PlayerEntity *OOGetPlayer(void) INLINE_CONST_FUNC; OOINLINE PlayerEntity *OOGetPlayer(void) { extern PlayerEntity *gOOPlayer; #if OO_DEBUG NSCAssert(gOOPlayer != nil, @"PLAYER used when [PlayerEntity sharedPlayer] has not been called."); #endif return gOOPlayer; } #define PLAYER OOGetPlayer() #define KILOGRAMS_PER_POD 1000 #define MAX_KILOGRAMS_IN_SAFE ((KILOGRAMS_PER_POD / 2) - 1) #define GRAMS_PER_POD (KILOGRAMS_PER_POD * 1000) #define MAX_GRAMS_IN_SAFE ((GRAMS_PER_POD / 2) - 1) NSString *OODisplayRatingStringFromKillCount(unsigned kills); NSString *KillCountToRatingAndKillString(unsigned kills); NSString *OODisplayStringFromLegalStatus(int legalStatus); NSString *OOStringFromGUIScreenID(OOGUIScreenID screen) CONST_FUNC; OOGUIScreenID OOGUIScreenIDFromString(NSString *string) PURE_FUNC; OOGalacticHyperspaceBehaviour OOGalacticHyperspaceBehaviourFromString(NSString *string) PURE_FUNC; NSString *OOStringFromGalacticHyperspaceBehaviour(OOGalacticHyperspaceBehaviour behaviour) CONST_FUNC; oolite-1.82/src/Core/Entities/PlayerEntity.m000066400000000000000000012712741256642440500210370ustar00rootroot00000000000000/* PlayerEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #import "PlayerEntity.h" #import "PlayerEntityLegacyScriptEngine.h" #import "PlayerEntityContracts.h" #import "PlayerEntityControls.h" #import "PlayerEntitySound.h" #import "PlayerEntityScriptMethods.h" #import "StationEntity.h" #import "OOSunEntity.h" #import "OOPlanetEntity.h" #import "WormholeEntity.h" #import "ProxyPlayerEntity.h" #import "OOQuiriumCascadeEntity.h" #import "OOLaserShotEntity.h" #import "OOMesh.h" #import "OOMaths.h" #import "GameController.h" #import "ResourceManager.h" #import "Universe.h" #import "AI.h" #import "ShipEntityAI.h" #import "MyOpenGLView.h" #import "OOTrumble.h" #import "PlayerEntityLoadSave.h" #import "OOSound.h" #import "OOColor.h" #import "Octree.h" #import "OOCacheManager.h" #import "OOOXZManager.h" #import "OOStringExpander.h" #import "OOStringParsing.h" #import "OOPListParsing.h" #import "OOCollectionExtractors.h" #import "OOConstToString.h" #import "OOTexture.h" #import "OORoleSet.h" #import "HeadUpDisplay.h" #import "OOOpenGLExtensionManager.h" #import "OOMusicController.h" #import "OOEntityFilterPredicate.h" #import "OOShipRegistry.h" #import "OOEquipmentType.h" #import "NSFileManagerOOExtensions.h" #import "OOFullScreenController.h" #import "OODebugSupport.h" #import "CollisionRegion.h" #import "OOJSScript.h" #import "OOScriptTimer.h" #import "OOJSEngineTimeManagement.h" #import "OOJSInterfaceDefinition.h" #import "OOConstToJSString.h" #import "OOJoystickManager.h" #import "PlayerEntityStickMapper.h" #import "PlayerEntityStickProfile.h" #import "OOSystemDescriptionManager.h" #define PLAYER_DEFAULT_NAME @"Jameson" enum { // If comm log is kCommLogTrimThreshold or more lines long, it will be cut to kCommLogTrimSize. kCommLogTrimThreshold = 15U, kCommLogTrimSize = 10U }; static NSString * const kOOLogBuyMountedOK = @"equip.buy.mounted"; static NSString * const kOOLogBuyMountedFailed = @"equip.buy.mounted.failed"; static float const kDeadResetTime = 30.0f; PlayerEntity *gOOPlayer = nil; static GLfloat sBaseMass = 0.0; NSComparisonResult marketSorterByName(id a, id b, void *market); NSComparisonResult marketSorterByPrice(id a, id b, void *market); NSComparisonResult marketSorterByQuantity(id a, id b, void *market); NSComparisonResult marketSorterByMassUnit(id a, id b, void *market); @interface PlayerEntity (OOPrivate) - (void) setExtraEquipmentFromFlags; - (void) doTradeIn:(OOCreditsQuantity)tradeInValue forPriceFactor:(double)priceFactor; // Subs of update: - (void) updateMovementFlags; - (void) updateAlertCondition; - (void) updateFuelScoops:(OOTimeDelta)delta_t; - (void) updateClocks:(OOTimeDelta)delta_t; - (void) checkScriptsIfAppropriate; - (void) updateTrumbles:(OOTimeDelta)delta_t; - (void) performAutopilotUpdates:(OOTimeDelta)delta_t; - (void) performInFlightUpdates:(OOTimeDelta)delta_t; - (void) performWitchspaceCountdownUpdates:(OOTimeDelta)delta_t; - (void) performWitchspaceExitUpdates:(OOTimeDelta)delta_t; - (void) performLaunchingUpdates:(OOTimeDelta)delta_t; - (void) performDockingUpdates:(OOTimeDelta)delta_t; - (void) performDeadUpdates:(OOTimeDelta)delta_t; - (void) updateTargeting; - (void) showGameOver; - (void) updateWormholes; - (void) updateAlertConditionForNearbyEntities; - (BOOL) checkEntityForMassLock:(Entity *)ent withScanClass:(int)scanClass; // Shopping - (void) showMarketScreenHeaders; - (void) showMarketScreenDataLine:(OOGUIRow)row forGood:(OOCommodityType)good inMarket:(OOCommodityMarket *)localMarket holdQuantity:(OOCargoQuantity)quantity; - (void) showMarketCashAndLoadLine; - (OOCreditsQuantity) adjustPriceByScriptForEqKey:(NSString *)eqKey withCurrent:(OOCreditsQuantity)price; - (BOOL) tryBuyingItem:(NSString *)eqKey; // Cargo & passenger contracts - (NSArray*) contractsListForScriptingFromArray:(NSArray *)contractsArray forCargo:(BOOL)forCargo; - (void) prepareMarkedDestination:(NSMutableDictionary *)markers :(NSDictionary *)marker; - (void) witchStart; - (void) witchJumpTo:(OOSystemID)sTo misjump:(BOOL)misjump; - (void) witchEnd; // Jump distance/cost calculations for selected target. - (double) hyperspaceJumpDistance; - (OOFuelQuantity) fuelRequiredForJump; - (void) noteCompassLostTarget; @end @interface ShipEntity (Hax) - (id) initBypassForPlayer; @end @implementation PlayerEntity + (PlayerEntity *) sharedPlayer { if (EXPECT_NOT(gOOPlayer == nil)) { gOOPlayer = [[PlayerEntity alloc] init]; } return gOOPlayer; } - (void) setName:(NSString *)inName { // Block super method; player ship can't be renamed. } - (GLfloat) baseMass { if (sBaseMass <= 0.0) { // First call with initialised mass (in [UNIVERSE setUpInitialUniverse]) is always to the cobra 3, even when starting with a savegame. if ([self mass] > 0.0) // bootstrap the base mass. { OOLog(@"fuelPrices", @"Setting Cobra3 base mass to: %.2f ", [self mass]); sBaseMass = [self mass]; } else { // This happened on startup when [UNIVERSE setUpSpace] was called before player init, inside [UNIVERSE setUpInitialUniverse]. OOLog(@"fuelPrices", @"Player ship not initialised properly yet, using precalculated base mass."); return 185580.0; } } return sBaseMass; } - (void) unloadAllCargoPodsForType:(OOCommodityType)type toManifest:(OOCommodityMarket *) manifest { NSInteger i, cargoCount = [cargo count]; if (cargoCount == 0) return; // step through the cargo pods adding in the quantities for (i = cargoCount - 1; i >= 0 ; i--) { ShipEntity *cargoItem = [cargo objectAtIndex:i]; NSString * commodityType = [cargoItem commodityType]; if (commodityType == nil || [commodityType isEqualToString:type]) { if ([commodityType isEqualToString:type]) { // transfer [manifest addQuantity:[cargoItem commodityAmount] forGood:type]; } else // undefined { OOLog(@"player.badCargoPod", @"Cargo pod %@ has bad commodity type, rejecting.", cargoItem); continue; } [cargo removeObjectAtIndex:i]; } } } - (void) unloadCargoPodsForType:(OOCommodityType)type amount:(OOCargoQuantity)quantity { NSInteger i, n_cargo = [cargo count]; if (n_cargo == 0) return; ShipEntity *cargoItem = nil; OOCommodityType co_type; OOCargoQuantity amount; OOCargoQuantity cargoToGo = quantity; // step through the cargo pods removing pods or quantities for (i = n_cargo - 1; (i >= 0 && cargoToGo > 0) ; i--) { cargoItem = [cargo objectAtIndex:i]; co_type = [cargoItem commodityType]; if (co_type == nil || [co_type isEqualToString:type]) { if ([co_type isEqualToString:type]) { amount = [cargoItem commodityAmount]; if (amount <= cargoToGo) { [cargo removeObjectAtIndex:i]; cargoToGo -= amount; } else { // we only need to remove a part of the cargo to meet our target [cargoItem setCommodity:co_type andAmount:(amount - cargoToGo)]; cargoToGo = 0; } } else // undefined { OOLog(@"player.badCargoPod", @"Cargo pod %@ has bad commodity type (COMMODITY_UNDEFINED), rejecting.", cargoItem); continue; } } } // now check if we are ready. When not, proceed with quantities in the manifest. if (cargoToGo > 0) { [shipCommodityData removeQuantity:cargoToGo forGood:type]; } } - (void) unloadCargoPods { NSAssert([self isDocked], @"Cannot unload cargo pods unless docked."); /* loads commodities from the cargo pods onto the ship's manifest */ NSString *good = nil; foreach (good, [shipCommodityData goods]) { [self unloadAllCargoPodsForType:good toManifest:shipCommodityData]; } #ifndef NDEBUG if ([cargo count] > 0) { OOLog(@"player.unloadCargo",@"Cargo remains in pods after unloading - %@",cargo); } #endif [self calculateCurrentCargo]; // work out the correct value for current_cargo } // TODO: better feedback on the log as to why failing to create player cargo pods causes a CTD? - (void) createCargoPodWithType:(OOCommodityType)type andAmount:(OOCargoQuantity)amount { ShipEntity *container = [UNIVERSE newShipWithRole:@"1t-cargopod"]; if (container) { [container setScanClass: CLASS_CARGO]; [container setStatus:STATUS_IN_HOLD]; [container setCommodity:type andAmount:amount]; [cargo addObject:container]; [container release]; } else { OOLogERR(@"player.loadCargoPods.noContainer", @"couldn't create a container in [PlayerEntity loadCargoPods]"); // throw an exception here... [NSException raise:OOLITE_EXCEPTION_FATAL format:@"[PlayerEntity loadCargoPods] failed to create a container for cargo with role 'cargopod'"]; } } - (void) loadCargoPodsForType:(OOCommodityType)type fromManifest:(OOCommodityMarket *) manifest { // load commodities from the ships manifest into individual cargo pods unsigned j; OOCargoQuantity quantity = [manifest quantityForGood:type]; OOMassUnit units = [manifest massUnitForGood:type]; if (quantity > 0) { if (units == UNITS_TONS) { // easy case for (j = 0; j < quantity; j++) { [self createCargoPodWithType:type andAmount:1]; // or CTD if unsuccesful (!) } [manifest setQuantity:0 forGood:type]; } else { OOCargoQuantity podsRequiredForQuantity, amountToLoadInCargopod, tmpQuantity; // reserve up to 1/2 ton of each commodity for the safe if (units == UNITS_KILOGRAMS) { if (quantity <= MAX_KILOGRAMS_IN_SAFE) { tmpQuantity = quantity; quantity = 0; } else { tmpQuantity = MAX_KILOGRAMS_IN_SAFE; quantity -= tmpQuantity; } amountToLoadInCargopod = KILOGRAMS_PER_POD; } else { if (quantity <= MAX_GRAMS_IN_SAFE) { tmpQuantity = quantity; quantity = 0; } else { tmpQuantity = MAX_GRAMS_IN_SAFE; quantity -= tmpQuantity; } amountToLoadInCargopod = GRAMS_PER_POD; } if (quantity > 0) { podsRequiredForQuantity = 1 + (quantity/amountToLoadInCargopod); // put each ton or part-ton beyond that in a separate container for (j = 0; j < podsRequiredForQuantity; j++) { if (amountToLoadInCargopod > quantity) { // last pod gets the dregs. :) amountToLoadInCargopod = quantity; } [self createCargoPodWithType:type andAmount:amountToLoadInCargopod]; // or CTD if unsuccesful (!) quantity -= amountToLoadInCargopod; } // adjust manifest for this commodity [manifest setQuantity:tmpQuantity forGood:type]; } } } } - (void) loadCargoPodsForType:(OOCommodityType)type amount:(OOCargoQuantity)quantity { OOMassUnit unit = [shipCommodityData massUnitForGood:type]; while (quantity) { if (unit != UNITS_TONS) { int amount_per_container = (unit == UNITS_KILOGRAMS)? KILOGRAMS_PER_POD : GRAMS_PER_POD; while (quantity > 0) { int smaller_quantity = 1 + ((quantity - 1) % amount_per_container); if ([cargo count] < [self maxAvailableCargoSpace]) { ShipEntity* container = [UNIVERSE newShipWithRole:@"1t-cargopod"]; if (container) { // the cargopod ship is just being set up. If ejected, will call UNIVERSE addEntity [container setStatus:STATUS_IN_HOLD]; [container setScanClass: CLASS_CARGO]; [container setCommodity:type andAmount:smaller_quantity]; [cargo addObject:container]; [container release]; } } else { // try to squeeze any surplus, up to half a ton, in the manifest. int amount = [shipCommodityData quantityForGood:type] + smaller_quantity; if (amount > MAX_GRAMS_IN_SAFE && unit == UNITS_GRAMS) amount = MAX_GRAMS_IN_SAFE; else if (amount > MAX_KILOGRAMS_IN_SAFE && unit == UNITS_KILOGRAMS) amount = MAX_KILOGRAMS_IN_SAFE; [shipCommodityData setQuantity:amount forGood:type]; } quantity -= smaller_quantity; } } else { // put each ton in a separate container while (quantity) { if ([cargo count] < [self maxAvailableCargoSpace]) { ShipEntity* container = [UNIVERSE newShipWithRole:@"1t-cargopod"]; if (container) { // the cargopod ship is just being set up. If ejected, will call UNIVERSE addEntity [container setScanClass: CLASS_CARGO]; [container setStatus:STATUS_IN_HOLD]; [container setCommodity:type andAmount:1]; [cargo addObject:container]; [container release]; } } quantity--; } } } } - (void) loadCargoPods { /* loads commodities from the ships manifest into individual cargo pods */ NSString *good = nil; foreach (good, [shipCommodityData goods]) { [self loadCargoPodsForType:good fromManifest:shipCommodityData]; } [self calculateCurrentCargo]; // work out the correct value for current_cargo cargo_dump_time = 0; } - (OOCommodityMarket *) shipCommodityData { return shipCommodityData; } - (OOCreditsQuantity) deciCredits { return credits; } - (int) random_factor { return market_rnd; } - (void) setRandom_factor:(int)rf { market_rnd = rf; } - (OOGalaxyID) galaxyNumber { return galaxy_number; } - (NSPoint) galaxy_coordinates { return galaxy_coordinates; } - (void) setGalaxyCoordinates:(NSPoint)newPosition { galaxy_coordinates.x = newPosition.x; galaxy_coordinates.y = newPosition.y; } - (NSPoint) cursor_coordinates { return cursor_coordinates; } - (NSPoint) chart_centre_coordinates { return chart_centre_coordinates; } - (OOScalar) chart_zoom { return chart_zoom; } - (NSPoint) adjusted_chart_centre { NSPoint acc; // adjusted chart centre double scroll_pos; // cursor coordinate at which we'd want to scoll chart in the direction we're currently considering double ecc; // chart centre coordinate we'd want if the cursor was on the edge of the galaxy in the current direction // When fully zoomed in we want to centre chart on chart_centre_coordinates. When zoomed out we want the chart centred on // (128.0, 128.0) so the galaxy fits the screen width. For intermediate zoom we interpolate. acc.x = chart_centre_coordinates.x + (128.0 - chart_centre_coordinates.x) * (chart_zoom - 1.0) / (CHART_MAX_ZOOM - 1.0); acc.y = chart_centre_coordinates.y + (128.0 - chart_centre_coordinates.y) * (chart_zoom - 1.0) / (CHART_MAX_ZOOM - 1.0); // If the cursor is out of the centre non-scrolling part of the screen adjust the chart centre. If the cursor is just at scroll_pos // we want to return the chart centre as it is, but if it's at the edge of the galaxy we want the centre positioned so the cursor is // at the edge of the screen if (chart_focus_coordinates.x - acc.x <= -CHART_SCROLL_AT_X*chart_zoom) { scroll_pos = acc.x - CHART_SCROLL_AT_X*chart_zoom; ecc = CHART_WIDTH_AT_MAX_ZOOM*chart_zoom / 2.0; if (scroll_pos <= 0) { acc.x = ecc; } else { acc.x = ((scroll_pos-chart_focus_coordinates.x)*ecc + chart_focus_coordinates.x*acc.x)/scroll_pos; } } else if (chart_focus_coordinates.x - acc.x >= CHART_SCROLL_AT_X*chart_zoom) { scroll_pos = acc.x + CHART_SCROLL_AT_X*chart_zoom; ecc = 256.0 - CHART_WIDTH_AT_MAX_ZOOM*chart_zoom / 2.0; if (scroll_pos >= 256.0) { acc.x = ecc; } else { acc.x = ((chart_focus_coordinates.x-scroll_pos)*ecc + (256.0 - chart_focus_coordinates.x)*acc.x)/(256.0 - scroll_pos); } } if (chart_focus_coordinates.y - acc.y <= -CHART_SCROLL_AT_Y*chart_zoom) { scroll_pos = acc.y - CHART_SCROLL_AT_Y*chart_zoom; ecc = CHART_HEIGHT_AT_MAX_ZOOM*chart_zoom / 2.0; if (scroll_pos <= 0) { acc.y = ecc; } else { acc.y = ((scroll_pos-chart_focus_coordinates.y)*ecc + chart_focus_coordinates.y*acc.y)/scroll_pos; } } else if (chart_focus_coordinates.y - acc.y >= CHART_SCROLL_AT_Y*chart_zoom) { scroll_pos = acc.y + CHART_SCROLL_AT_Y*chart_zoom; ecc = 256.0 - CHART_HEIGHT_AT_MAX_ZOOM*chart_zoom / 2.0; if (scroll_pos >= 256.0) { acc.y = ecc; } else { acc.y = ((chart_focus_coordinates.y-scroll_pos)*ecc + (256.0 - chart_focus_coordinates.y)*acc.y)/(256.0 - scroll_pos); } } return acc; } - (OORouteType) ANAMode { return ANA_mode; } - (OOSystemID) systemID { return system_id; } - (void) setSystemID:(OOSystemID) sid { system_id = sid; galaxy_coordinates = PointFromString([[UNIVERSE systemManager] getProperty:@"coordinates" forSystem:sid inGalaxy:galaxy_number]); chart_centre_coordinates = galaxy_coordinates; target_chart_centre = chart_centre_coordinates; } - (OOSystemID) targetSystemID { return target_system_id; } - (void) setTargetSystemID:(OOSystemID) sid { target_system_id = sid; cursor_coordinates = PointFromString([[UNIVERSE systemManager] getProperty:@"coordinates" forSystemKey:[UNIVERSE keyForPlanetOverridesForSystem:sid inGalaxy:galaxy_number]]); } // just return target system id if no valid next hop - (OOSystemID) nextHopTargetSystemID { // not available if no ANA if (![self hasEquipmentItemProviding:@"EQ_ADVANCED_NAVIGATIONAL_ARRAY"]) { return target_system_id; } // not available if ANA is turned off if (ANA_mode == OPTIMIZED_BY_NONE) { return target_system_id; } // easy case if (system_id == target_system_id) { return system_id; // no need to calculate } NSDictionary *routeInfo = nil; routeInfo = [UNIVERSE routeFromSystem:system_id toSystem:target_system_id optimizedBy:ANA_mode]; // no route to destination if (routeInfo == nil) { return target_system_id; } return [[routeInfo oo_arrayForKey:@"route"] oo_intAtIndex:1]; } - (WormholeEntity *) wormhole { return wormhole; } - (void) setWormhole:(WormholeEntity*)newWormhole { [wormhole release]; if (newWormhole != nil) { wormhole = [newWormhole retain]; } else { wormhole = nil; } } - (NSDictionary *) commanderDataDictionary { NSAssert([self isDocked], @"Cannot create commander data dictionary unless docked."); int i; NSMutableDictionary *result = [NSMutableDictionary dictionary]; [result setObject:[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"] forKey:@"written_by_version"]; NSString *gal_id = [NSString stringWithFormat:@"%u", galaxy_number]; NSString *sys_id = [NSString stringWithFormat:@"%d", system_id]; NSString *tgt_id = [NSString stringWithFormat:@"%d", target_system_id]; // Variable requiredCargoSpace not suitable for Oolite as it currently stands: it retroactively changes a savegame cargo space. //unsigned passenger_space = [[OOEquipmentType equipmentTypeWithIdentifier:@"EQ_PASSENGER_BERTH"] requiredCargoSpace]; //if (passenger_space == 0) passenger_space = PASSENGER_BERTH_SPACE; [result setObject:gal_id forKey:@"galaxy_id"]; [result setObject:sys_id forKey:@"system_id"]; [result setObject:tgt_id forKey:@"target_id"]; [result setObject:[NSNumber numberWithFloat:saved_chart_zoom] forKey:@"chart_zoom"]; [result setObject:[NSNumber numberWithInt:ANA_mode] forKey:@"chart_ana_mode"]; [result setObject:[NSNumber numberWithInt:longRangeChartMode] forKey:@"chart_colour_mode"]; if (found_system_id >= 0) { NSString *found_id = [NSString stringWithFormat:@"%d", found_system_id]; [result setObject:found_id forKey:@"found_system_id"]; } // Write the name of the current system. Useful for looking up saved game information and for overlapping systems. [result setObject:[UNIVERSE getSystemName:[self currentSystemID]] forKey:@"current_system_name"]; [result setObject:[self commanderName] forKey:@"player_name"]; [result setObject:[self lastsaveName] forKey:@"player_save_name"]; [result setObject:[self shipUniqueName] forKey:@"ship_unique_name"]; [result setObject:[self shipClassName] forKey:@"ship_class_name"]; /* BUG: GNUstep truncates integer values to 32 bits when loading XML plists. Workaround: store credits as a double. 53 bits of precision ought to be good enough for anybody. Besides, we display credits with double precision anyway. -- Ahruman 2011-02-15 */ [result oo_setFloat:credits forKey:@"credits"]; [result oo_setUnsignedInteger:fuel forKey:@"fuel"]; [result oo_setInteger:galaxy_number forKey:@"galaxy_number"]; [result oo_setBool:[self weaponsOnline] forKey:@"weapons_online"]; if (forward_weapon_type != nil) { [result setObject:[forward_weapon_type identifier] forKey:@"forward_weapon"]; } if (aft_weapon_type != nil) { [result setObject:[aft_weapon_type identifier] forKey:@"aft_weapon"]; } if (port_weapon_type != nil) { [result setObject:[port_weapon_type identifier] forKey:@"port_weapon"]; } if (starboard_weapon_type != nil) { [result setObject:[starboard_weapon_type identifier] forKey:@"starboard_weapon"]; } [result setObject:[self serializeShipSubEntities] forKey:@"subentities_status"]; if (hud != nil && [hud nonlinearScanner]) { [result oo_setFloat: [hud scannerZoom] forKey:@"ship_scanner_zoom"]; } [result oo_setInteger:max_cargo + PASSENGER_BERTH_SPACE * max_passengers forKey:@"max_cargo"]; [result setObject:[shipCommodityData savePlayerAmounts] forKey:@"shipCommodityData"]; NSMutableArray *missileRoles = [NSMutableArray arrayWithCapacity:max_missiles]; for (i = 0; i < (int)max_missiles; i++) { if (missile_entity[i]) { [missileRoles addObject:[missile_entity[i] primaryRole]]; } else { [missileRoles addObject:@"NONE"]; } } [result setObject:missileRoles forKey:@"missile_roles"]; [result oo_setInteger:missiles forKey:@"missiles"]; [result oo_setInteger:legalStatus forKey:@"legal_status"]; [result oo_setInteger:market_rnd forKey:@"market_rnd"]; [result oo_setInteger:ship_kills forKey:@"ship_kills"]; // ship depreciation [result oo_setInteger:ship_trade_in_factor forKey:@"ship_trade_in_factor"]; // mission variables if (mission_variables != nil) { [result setObject:[NSDictionary dictionaryWithDictionary:mission_variables] forKey:@"mission_variables"]; } // communications log NSArray *log = [self commLog]; if (log != nil) [result setObject:log forKey:@"comm_log"]; [result oo_setUnsignedInteger:entity_personality forKey:@"entity_personality"]; // extra equipment flags NSMutableDictionary *equipment = [NSMutableDictionary dictionary]; NSEnumerator *eqEnum = nil; NSString *eqDesc = nil; for (eqEnum = [self equipmentEnumerator]; (eqDesc = [eqEnum nextObject]); ) { [equipment oo_setInteger:[self countEquipmentItem:eqDesc] forKey:eqDesc]; } if ([equipment count] != 0) { [result setObject:equipment forKey:@"extra_equipment"]; } if (primedEquipment < [eqScripts count]) [result setObject:[[eqScripts oo_arrayAtIndex:primedEquipment] oo_stringAtIndex:0] forKey:@"primed_equipment"]; [result setObject:[self fastEquipmentA] forKey:@"primed_equipment_a"]; [result setObject:[self fastEquipmentB] forKey:@"primed_equipment_b"]; // roles [result setObject:roleWeights forKey:@"role_weights"]; // role information [result setObject:roleWeightFlags forKey:@"role_weight_flags"]; // role information [result setObject:roleSystemList forKey:@"role_system_memory"]; // reputation [result setObject:reputation forKey:@"reputation"]; // initialise parcel reputations in dictionary if not set int pGood = [reputation oo_intForKey:PARCEL_GOOD_KEY]; int pBad = [reputation oo_intForKey:PARCEL_BAD_KEY]; int pUnknown = [reputation oo_intForKey:PARCEL_UNKNOWN_KEY]; if (pGood+pBad+pUnknown != MAX_CONTRACT_REP) { [reputation oo_setInteger:0 forKey:PARCEL_GOOD_KEY]; [reputation oo_setInteger:0 forKey:PARCEL_BAD_KEY]; [reputation oo_setInteger:MAX_CONTRACT_REP forKey:PARCEL_UNKNOWN_KEY]; } // passengers [result oo_setInteger:max_passengers forKey:@"max_passengers"]; [result setObject:passengers forKey:@"passengers"]; [result setObject:passenger_record forKey:@"passenger_record"]; // parcels [result setObject:parcels forKey:@"parcels"]; [result setObject:parcel_record forKey:@"parcel_record"]; //specialCargo if (specialCargo) [result setObject:specialCargo forKey:@"special_cargo"]; // contracts [result setObject:contracts forKey:@"contracts"]; [result setObject:contract_record forKey:@"contract_record"]; [result setObject:missionDestinations forKey:@"mission_destinations"]; //shipyard [result setObject:shipyard_record forKey:@"shipyard_record"]; //ship's clock [result setObject:[NSNumber numberWithDouble:ship_clock] forKey:@"ship_clock"]; //speech [result setObject:[NSNumber numberWithInt:isSpeechOn] forKey:@"speech_on"]; #if OOLITE_ESPEAK [result setObject:[UNIVERSE voiceName:voice_no] forKey:@"speech_voice"]; [result setObject:[NSNumber numberWithBool:voice_gender_m] forKey:@"speech_gender"]; #endif // docking clearance [result setObject:[NSNumber numberWithBool:[UNIVERSE dockingClearanceProtocolActive]] forKey:@"docking_clearance_protocol"]; //base ship description [result setObject:[self shipDataKey] forKey:@"ship_desc"]; [result setObject:[[self shipInfoDictionary] oo_stringForKey:KEY_NAME] forKey:@"ship_name"]; //custom view no. [result oo_setUnsignedInteger:_customViewIndex forKey:@"custom_view_index"]; //local market for main station if ([[UNIVERSE station] localMarket]) [result setObject:[[[UNIVERSE station] localMarket] saveStationAmounts] forKey:@"localMarket"]; // Scenario restriction on OXZs [result setObject:[UNIVERSE useAddOns] forKey:@"scenario_restriction"]; [result setObject:[[UNIVERSE systemManager] exportScriptedChanges] forKey:@"scripted_planetinfo_overrides"]; // trumble information [result setObject:[self trumbleValue] forKey:@"trumbles"]; // wormhole information NSMutableArray *wormholeDicts = [NSMutableArray arrayWithCapacity:[scannedWormholes count]]; NSEnumerator *wormholes = [scannedWormholes objectEnumerator]; WormholeEntity *wh = nil; foreach(wh, wormholes) { [wormholeDicts addObject:[wh getDict]]; } [result setObject:wormholeDicts forKey:@"wormholes"]; // docked station StationEntity *dockedStation = [self dockedStation]; [result setObject:[dockedStation primaryRole] forKey:@"docked_station_role"]; HPVector dpos = [dockedStation position]; [result setObject:ArrayFromHPVector(dpos) forKey:@"docked_station_position"]; [result setObject:[UNIVERSE getStationMarkets] forKey:@"station_markets"]; // scenario information if (scenarioKey != nil) { [result setObject:scenarioKey forKey:@"scenario"]; } // create checksum clear_checksum(); // TODO: should checksum checks be removed? // munge_checksum(galaxy_seed.a); munge_checksum(galaxy_seed.b); munge_checksum(galaxy_seed.c); // munge_checksum(galaxy_seed.d); munge_checksum(galaxy_seed.e); munge_checksum(galaxy_seed.f); munge_checksum(galaxy_coordinates.x); munge_checksum(galaxy_coordinates.y); munge_checksum(credits); munge_checksum(fuel); munge_checksum(max_cargo); munge_checksum(missiles); munge_checksum(legalStatus); munge_checksum(market_rnd); munge_checksum(ship_kills); if (mission_variables != nil) { munge_checksum([[mission_variables description] length]); } if (equipment != nil) { munge_checksum([[equipment description] length]); } int final_checksum = munge_checksum([[self shipDataKey] length]); //set checksum [result oo_setInteger:final_checksum forKey:@"checksum"]; return result; } - (BOOL)setCommanderDataFromDictionary:(NSDictionary *) dict { // multi-function displays // must be reset before ship setup [multiFunctionDisplayText release]; multiFunctionDisplayText = [[NSMutableDictionary alloc] init]; [multiFunctionDisplaySettings release]; multiFunctionDisplaySettings = [[NSMutableArray alloc] init]; [customDialSettings release]; customDialSettings = [[NSMutableDictionary alloc] init]; [[UNIVERSE gameView] resetTypedString]; // Required keys if ([dict oo_stringForKey:@"ship_desc"] == nil) return NO; // galaxy_seed is used is 1.80 or earlier if ([dict oo_stringForKey:@"galaxy_seed"] == nil && [dict oo_stringForKey:@"galaxy_id"] == nil) return NO; // galaxy_coordinates is used is 1.80 or earlier if ([dict oo_stringForKey:@"galaxy_coordinates"] == nil && [dict oo_stringForKey:@"system_id"] == nil) return NO; NSString *scenarioRestrict = [dict oo_stringForKey:@"scenario_restriction" defaultValue:nil]; if (scenarioRestrict == nil) { // older save game - use the 'strict' key instead BOOL strict = [dict oo_boolForKey:@"strict" defaultValue:NO]; if (strict) { scenarioRestrict = SCENARIO_OXP_DEFINITION_NONE; } else { scenarioRestrict = SCENARIO_OXP_DEFINITION_ALL; } } if (![UNIVERSE setUseAddOns:scenarioRestrict fromSaveGame:YES]) { return NO; } //base ship description [self setShipDataKey:[dict oo_stringForKey:@"ship_desc"]]; NSDictionary *shipDict = [[OOShipRegistry sharedRegistry] shipInfoForKey:[self shipDataKey]]; if (shipDict == nil) return NO; if (![self setUpShipFromDictionary:shipDict]) return NO; OOLog(@"fuelPrices", @"Got \"%@\", fuel charge rate: %.2f", [self shipDataKey],[self fuelChargeRate]); // ship depreciation ship_trade_in_factor = [dict oo_intForKey:@"ship_trade_in_factor" defaultValue:95]; // newer savegames use galaxy_id if ([dict oo_stringForKey:@"galaxy_id"] != nil) { galaxy_number = [dict oo_unsignedIntegerForKey:@"galaxy_id"]; if (galaxy_number >= OO_GALAXIES_AVAILABLE) { return NO; } [UNIVERSE setGalaxyTo:galaxy_number andReinit:YES]; system_id = [dict oo_intForKey:@"system_id"]; if (system_id < 0 || system_id >= OO_SYSTEMS_PER_GALAXY) { return NO; } [UNIVERSE setSystemTo:system_id]; NSArray *coord_vals = ScanTokensFromString([[UNIVERSE systemManager] getProperty:@"coordinates" forSystem:system_id inGalaxy:galaxy_number]); galaxy_coordinates.x = [coord_vals oo_unsignedCharAtIndex:0]; galaxy_coordinates.y = [coord_vals oo_unsignedCharAtIndex:1]; chart_centre_coordinates = galaxy_coordinates; target_chart_centre = chart_centre_coordinates; cursor_coordinates = galaxy_coordinates; chart_zoom = [dict oo_floatForKey:@"chart_zoom" defaultValue:1.0]; target_chart_zoom = chart_zoom; saved_chart_zoom = chart_zoom; ANA_mode = [dict oo_intForKey:@"chart_ana_mode" defaultValue:OPTIMIZED_BY_NONE]; longRangeChartMode = [dict oo_intForKey:@"chart_colour_mode" defaultValue:OOLRC_MODE_NORMAL]; target_system_id = [dict oo_intForKey:@"target_id" defaultValue:system_id]; coord_vals = ScanTokensFromString([[UNIVERSE systemManager] getProperty:@"coordinates" forSystem:target_system_id inGalaxy:galaxy_number]); cursor_coordinates.x = [coord_vals oo_unsignedCharAtIndex:0]; cursor_coordinates.y = [coord_vals oo_unsignedCharAtIndex:1]; chart_cursor_coordinates = cursor_coordinates; chart_focus_coordinates = cursor_coordinates; found_system_id = [dict oo_intForKey:@"found_system_id" defaultValue:-1]; } else // compatibility for loading 1.80 savegames { galaxy_number = [dict oo_unsignedIntegerForKey:@"galaxy_number"]; [UNIVERSE setGalaxyTo: galaxy_number andReinit:YES]; NSArray *coord_vals = ScanTokensFromString([dict oo_stringForKey:@"galaxy_coordinates"]); galaxy_coordinates.x = [coord_vals oo_unsignedCharAtIndex:0]; galaxy_coordinates.y = [coord_vals oo_unsignedCharAtIndex:1]; chart_centre_coordinates = galaxy_coordinates; target_chart_centre = chart_centre_coordinates; cursor_coordinates = galaxy_coordinates; chart_zoom = 1.0; target_chart_zoom = 1.0; saved_chart_zoom = 1.0; ANA_mode = OPTIMIZED_BY_NONE; NSString *keyStringValue = [dict oo_stringForKey:@"target_coordinates"]; if (keyStringValue != nil) { coord_vals = ScanTokensFromString(keyStringValue); cursor_coordinates.x = [coord_vals oo_unsignedCharAtIndex:0]; cursor_coordinates.y = [coord_vals oo_unsignedCharAtIndex:1]; } chart_cursor_coordinates = cursor_coordinates; chart_focus_coordinates = cursor_coordinates; // calculate system ID, target ID if ([dict objectForKey:@"current_system_name"]) { system_id = [UNIVERSE findSystemFromName:[dict oo_stringForKey:@"current_system_name"]]; } else { // really old save games don't have system name saved // use coordinates instead - unreliable in zero-distance pairs. system_id = [UNIVERSE findSystemAtCoords:galaxy_coordinates withGalaxy:galaxy_number]; } // and current_system_name and target_system_name // were introduced at different times, too if ([dict objectForKey:@"target_system_name"]) { target_system_id = [UNIVERSE findSystemFromName:[dict oo_stringForKey:@"target_system_name"]]; } else { target_system_id = [UNIVERSE findSystemAtCoords:cursor_coordinates withGalaxy:galaxy_number]; } found_system_id = -1; } NSString *cname = [dict oo_stringForKey:@"player_name" defaultValue:PLAYER_DEFAULT_NAME]; [self setCommanderName:cname]; [self setLastsaveName:[dict oo_stringForKey:@"player_save_name" defaultValue:cname]]; [self setShipUniqueName:[dict oo_stringForKey:@"ship_unique_name" defaultValue:@""]]; [self setShipClassName:[dict oo_stringForKey:@"ship_class_name" defaultValue:[shipDict oo_stringForKey:@"name"]]]; [shipCommodityData loadPlayerAmounts:[dict oo_arrayForKey:@"shipCommodityData"]]; // extra equipment flags [self removeAllEquipment]; NSMutableDictionary *equipment = [NSMutableDictionary dictionaryWithDictionary:[dict oo_dictionaryForKey:@"extra_equipment"]]; // Equipment flags (deprecated in favour of equipment dictionary, keep for compatibility) if ([dict oo_boolForKey:@"has_docking_computer"]) [equipment oo_setInteger:1 forKey:@"EQ_DOCK_COMP"]; if ([dict oo_boolForKey:@"has_galactic_hyperdrive"]) [equipment oo_setInteger:1 forKey:@"EQ_GAL_DRIVE"]; if ([dict oo_boolForKey:@"has_escape_pod"]) [equipment oo_setInteger:1 forKey:@"EQ_ESCAPE_POD"]; if ([dict oo_boolForKey:@"has_ecm"]) [equipment oo_setInteger:1 forKey:@"EQ_ECM"]; if ([dict oo_boolForKey:@"has_scoop"]) [equipment oo_setInteger:1 forKey:@"EQ_FUEL_SCOOPS"]; if ([dict oo_boolForKey:@"has_energy_bomb"]) [equipment oo_setInteger:1 forKey:@"EQ_ENERGY_BOMB"]; if ([dict oo_boolForKey:@"has_fuel_injection"]) [equipment oo_setInteger:1 forKey:@"EQ_FUEL_INJECTION"]; // Legacy energy unit type -> energy unit equipment item if ([dict oo_boolForKey:@"has_energy_unit"] && [self installedEnergyUnitType] == ENERGY_UNIT_NONE) { OOEnergyUnitType eType = [dict oo_intForKey:@"energy_unit" defaultValue:ENERGY_UNIT_NORMAL]; switch (eType) { // look for NEU first! case OLD_ENERGY_UNIT_NAVAL: [equipment oo_setInteger:1 forKey:@"EQ_NAVAL_ENERGY_UNIT"]; break; case OLD_ENERGY_UNIT_NORMAL: [equipment oo_setInteger:1 forKey:@"EQ_ENERGY_UNIT"]; break; default: break; } } /* Energy bombs are no longer supported without OXPs. As compensation, we'll award either a Q-mine or some cash. We can't determine what to award until we've handled missiles later on, though. */ BOOL energyBombCompensation = NO; if ([equipment oo_boolForKey:@"EQ_ENERGY_BOMB"] && [OOEquipmentType equipmentTypeWithIdentifier:@"EQ_ENERGY_BOMB"] == nil) { energyBombCompensation = YES; [equipment removeObjectForKey:@"EQ_ENERGY_BOMB"]; } eqScripts = [[NSMutableArray alloc] init]; [self addEquipmentFromCollection:equipment]; primedEquipment = [self eqScriptIndexForKey:[dict oo_stringForKey:@"primed_equipment"]]; // if key not found primedEquipment is set to primed-none [self setFastEquipmentA:[dict oo_stringForKey:@"primed_equipment_a" defaultValue:@"EQ_CLOAKING_DEVICE"]]; [self setFastEquipmentB:[dict oo_stringForKey:@"primed_equipment_b" defaultValue:@"EQ_ENERGY_BOMB"]]; // even though there isn't one, for compatibility. if ([self hasEquipmentItemProviding:@"EQ_ADVANCED_COMPASS"]) compassMode = COMPASS_MODE_PLANET; else compassMode = COMPASS_MODE_BASIC; DESTROY(compassTarget); // speech isSpeechOn = [dict oo_intForKey:@"speech_on"]; #if OOLITE_ESPEAK voice_gender_m = [dict oo_boolForKey:@"speech_gender" defaultValue:YES]; voice_no = [UNIVERSE setVoice:[UNIVERSE voiceNumber:[dict oo_stringForKey:@"speech_voice" defaultValue:nil]] withGenderM:voice_gender_m]; #endif // reputation [reputation release]; reputation = [[dict oo_dictionaryForKey:@"reputation"] mutableCopy]; if (reputation == nil) reputation = [[NSMutableDictionary alloc] init]; [self normaliseReputation]; // passengers and contracts [parcels release]; [parcel_record release]; [passengers release]; [passenger_record release]; [contracts release]; [contract_record release]; max_passengers = [dict oo_intForKey:@"max_passengers" defaultValue:0]; passengers = [[dict oo_arrayForKey:@"passengers"] mutableCopy]; passenger_record = [[dict oo_dictionaryForKey:@"passenger_record"] mutableCopy]; /* Note: contracts from older savegames will have ints in the commodity. * Need to fix this up */ contracts = [[dict oo_arrayForKey:@"contracts"] mutableCopy]; NSMutableDictionary *contractInfo = nil; // iterate downwards; lets us remove invalid ones as we go for (NSInteger i = (NSInteger)[contracts count] - 1; i >= 0; i--) { contractInfo = [[[contracts oo_dictionaryAtIndex:i] mutableCopy] autorelease]; // if the trade good ID is an int if ([[contractInfo objectForKey:CARGO_KEY_TYPE] isKindOfClass:[NSNumber class]]) { // look it up, and replace with a string NSUInteger legacy_type = [contractInfo oo_unsignedIntegerForKey:CARGO_KEY_TYPE]; [contractInfo setObject:[OOCommodities legacyCommodityType:legacy_type] forKey:CARGO_KEY_TYPE]; [contracts replaceObjectAtIndex:i withObject:[[contractInfo copy] autorelease]]; } else { OOCommodityType new_type = [contractInfo oo_stringForKey:CARGO_KEY_TYPE]; // check that that the type still exists if (![[UNIVERSE commodities] goodDefined:new_type]) { OOLog(@"setCommanderDataFromDictionary.warning.contract",@"Cargo contract to deliver %@ could not be loaded from the saved game, as the commodity is no longer defined",new_type); [contracts removeObjectAtIndex:i]; } } } contract_record = [[dict oo_dictionaryForKey:@"contract_record"] mutableCopy]; parcels = [[dict oo_arrayForKey:@"parcels"] mutableCopy]; parcel_record = [[dict oo_dictionaryForKey:@"parcel_record"] mutableCopy]; if (passengers == nil) passengers = [[NSMutableArray alloc] init]; if (passenger_record == nil) passenger_record = [[NSMutableDictionary alloc] init]; if (contracts == nil) contracts = [[NSMutableArray alloc] init]; if (contract_record == nil) contract_record = [[NSMutableDictionary alloc] init]; if (parcels == nil) parcels = [[NSMutableArray alloc] init]; if (parcel_record == nil) parcel_record = [[NSMutableDictionary alloc] init]; //specialCargo [specialCargo release]; specialCargo = [[dict oo_stringForKey:@"special_cargo"] copy]; // mission destinations NSArray *legacyDestinations = [dict oo_arrayForKey:@"missionDestinations"]; NSDictionary *newDestinations = [dict oo_dictionaryForKey:@"mission_destinations"]; [self initialiseMissionDestinations:newDestinations andLegacy:legacyDestinations]; // shipyard DESTROY(shipyard_record); shipyard_record = [[dict oo_dictionaryForKey:@"shipyard_record"] mutableCopy]; if (shipyard_record == nil) shipyard_record = [[NSMutableDictionary alloc] init]; // Normalize cargo capacity unsigned original_hold_size = [UNIVERSE maxCargoForShip:[self shipDataKey]]; // Not Suitable For Oolite //unsigned passenger_space = [[OOEquipmentType equipmentTypeWithIdentifier:@"EQ_PASSENGER_BERTH"] requiredCargoSpace]; //if (passenger_space == 0) passenger_space = PASSENGER_BERTH_SPACE; max_cargo = [dict oo_unsignedIntForKey:@"max_cargo" defaultValue:max_cargo]; if (max_cargo > original_hold_size) [self addEquipmentItem:@"EQ_CARGO_BAY" inContext:@"loading"]; max_cargo = original_hold_size + ([self hasExpandedCargoBay] ? extra_cargo : 0); if (max_cargo < max_passengers * PASSENGER_BERTH_SPACE) { // Something went wrong. Possibly the save file was hacked to contain more passenger cabins than the available cargo space would allow - Nikos 20110731 unsigned originalMaxPassengers = max_passengers; max_passengers = (unsigned)(max_cargo / PASSENGER_BERTH_SPACE); OOLogWARN(@"setCommanderDataFromDictionary.inconsistency.max_passengers", @"player ship %@ had max_passengers set to a value requiring more cargo space than currently available (%u). Setting max_passengers to maximum possible value (%u).", [self name], originalMaxPassengers, max_passengers); } max_cargo -= max_passengers * PASSENGER_BERTH_SPACE; // Do we have extra passengers? if (passengers && ([passengers count] > max_passengers)) { OOLogWARN(@"setCommanderDataFromDictionary.inconsistency.passengers", @"player ship %@ had more passengers (%lu) than passenger berths (%u). Removing extra passengers.", [self name], [passengers count], max_passengers); for (NSInteger i = (NSInteger)[passengers count] - 1; i >= max_passengers; i--) { [passenger_record removeObjectForKey:[[passengers oo_dictionaryAtIndex:i] oo_stringForKey:PASSENGER_KEY_NAME]]; [passengers removeObjectAtIndex:i]; } } // too much cargo? NSInteger excessCargo = (NSInteger)[self cargoQuantityOnBoard] - (NSInteger)[self maxAvailableCargoSpace]; if (excessCargo > 0) { OOLogWARN(@"setCommanderDataFromDictionary.inconsistency.cargo", @"player ship %@ had more cargo (%i) than it can hold (%u). Removing extra cargo.", [self name], [self cargoQuantityOnBoard], [self maxAvailableCargoSpace]); OOCommodityType type; OOMassUnit units; OOCargoQuantity oldAmount, toRemove; OOCargoQuantity remainingExcess = (OOCargoQuantity)excessCargo; // manifest always contains entries for all 17 commodities, even if their quantity is 0. foreach (type, [shipCommodityData goods]) { units = [shipCommodityData massUnitForGood:type]; oldAmount = [shipCommodityData quantityForGood:type]; BOOL roundedTon = (units != UNITS_TONS) && ((units == UNITS_KILOGRAMS && oldAmount > MAX_KILOGRAMS_IN_SAFE) || (units == UNITS_GRAMS && oldAmount > MAX_GRAMS_IN_SAFE)); if (roundedTon || (units == UNITS_TONS && oldAmount > 0)) { // let's remove stuff OOCargoQuantity partAmount = oldAmount; toRemove = 0; while (remainingExcess > 0 && partAmount > 0) { if (EXPECT_NOT(roundedTon && ((units == UNITS_KILOGRAMS && partAmount > MAX_KILOGRAMS_IN_SAFE) || (units == UNITS_GRAMS && partAmount > MAX_GRAMS_IN_SAFE)))) { toRemove += (units == UNITS_KILOGRAMS) ? (partAmount > (KILOGRAMS_PER_POD + MAX_KILOGRAMS_IN_SAFE) ? KILOGRAMS_PER_POD : partAmount - MAX_KILOGRAMS_IN_SAFE) : (partAmount > (GRAMS_PER_POD + MAX_GRAMS_IN_SAFE) ? GRAMS_PER_POD : partAmount - MAX_GRAMS_IN_SAFE); partAmount = oldAmount - toRemove; remainingExcess--; } else if (!roundedTon) { toRemove++; partAmount--; remainingExcess--; } else { partAmount = 0; } } [shipCommodityData removeQuantity:toRemove forGood:type]; } } } credits = OODeciCreditsFromObject([dict objectForKey:@"credits"]); fuel = [dict oo_unsignedIntForKey:@"fuel" defaultValue:fuel]; galaxy_number = [dict oo_intForKey:@"galaxy_number"]; // NSDictionary *shipyard_info = [[OOShipRegistry sharedRegistry] shipyardInfoForKey:[self shipDataKey]]; OOWeaponFacingSet available_facings = [shipyard_info oo_unsignedIntForKey:KEY_WEAPON_FACINGS defaultValue:[self weaponFacings]]; if (available_facings & WEAPON_FACING_FORWARD) forward_weapon_type = OOWeaponTypeFromEquipmentIdentifierLegacy([dict oo_stringForKey:@"forward_weapon"]); else forward_weapon_type = OOWeaponTypeFromEquipmentIdentifierSloppy(@"EQ_WEAPON_NONE"); if (available_facings & WEAPON_FACING_AFT) aft_weapon_type = OOWeaponTypeFromEquipmentIdentifierLegacy([dict oo_stringForKey:@"aft_weapon"]); else aft_weapon_type = OOWeaponTypeFromEquipmentIdentifierSloppy(@"EQ_WEAPON_NONE"); if (available_facings & WEAPON_FACING_PORT) port_weapon_type = OOWeaponTypeFromEquipmentIdentifierLegacy([dict oo_stringForKey:@"port_weapon"]); else port_weapon_type = OOWeaponTypeFromEquipmentIdentifierSloppy(@"EQ_WEAPON_NONE"); if (available_facings & WEAPON_FACING_STARBOARD) starboard_weapon_type = OOWeaponTypeFromEquipmentIdentifierLegacy([dict oo_stringForKey:@"starboard_weapon"]); else starboard_weapon_type = OOWeaponTypeFromEquipmentIdentifierSloppy(@"EQ_WEAPON_NONE"); [self setWeaponDataFromType:forward_weapon_type]; if (hud != nil && [hud nonlinearScanner]) { [hud setScannerZoom: [dict oo_floatForKey:@"ship_scanner_zoom" defaultValue: 1.0]]; } weapons_online = [dict oo_boolForKey:@"weapons_online" defaultValue:YES]; legalStatus = [dict oo_intForKey:@"legal_status"]; market_rnd = [dict oo_intForKey:@"market_rnd"]; ship_kills = [dict oo_intForKey:@"ship_kills"]; ship_clock = [dict oo_doubleForKey:@"ship_clock" defaultValue:PLAYER_SHIP_CLOCK_START]; fps_check_time = ship_clock; // role weights [roleWeights release]; roleWeights = [[dict oo_arrayForKey:@"role_weights"] mutableCopy]; NSUInteger rc = [self maxPlayerRoles]; if (roleWeights == nil) { roleWeights = [[NSMutableArray alloc] initWithCapacity:rc]; while (rc-- > 0) { [roleWeights addObject:@"player-unknown"]; } } else { if ([roleWeights count] > rc) { [roleWeights removeObjectsInRange:(NSRange) {rc,[roleWeights count]-rc}]; } } roleWeightFlags = [[dict oo_dictionaryForKey:@"role_weight_flags"] mutableCopy]; if (roleWeightFlags == nil) { roleWeightFlags = [[NSMutableDictionary alloc] init]; } roleSystemList = [[dict oo_arrayForKey:@"role_system_memory"] mutableCopy]; if (roleSystemList == nil) { roleSystemList = [[NSMutableArray alloc] initWithCapacity:32]; } // mission_variables [mission_variables release]; mission_variables = [[dict oo_dictionaryForKey:@"mission_variables"] mutableCopy]; if (mission_variables == nil) mission_variables = [[NSMutableDictionary alloc] init]; // persistant UNIVERSE info NSDictionary *planetInfoOverrides = [dict oo_dictionaryForKey:@"scripted_planetinfo_overrides"]; if (planetInfoOverrides != nil) { [[UNIVERSE systemManager] importScriptedChanges:planetInfoOverrides]; } else { // no scripted overrides? What about 1.80-style local overrides? planetInfoOverrides = [dict oo_dictionaryForKey:@"local_planetinfo_overrides"]; if (planetInfoOverrides != nil) { [[UNIVERSE systemManager] importLegacyScriptedChanges:planetInfoOverrides]; } } // communications log [commLog release]; commLog = [[NSMutableArray alloc] initWithCapacity:kCommLogTrimThreshold]; NSArray *savedCommLog = [dict oo_arrayForKey:@"comm_log"]; NSUInteger commCount = [savedCommLog count]; for (NSUInteger i = 0; i < commCount; i++) { [UNIVERSE addCommsMessage:[savedCommLog objectAtIndex:i] forCount:0 andShowComms:NO logOnly:YES]; } /* entity_personality for scripts and shaders. If undefined, we fall back to old behaviour of using a random value each time game is loaded (set up in -setUp). Saving of entity_personality was added in 1.74. -- Ahruman 2009-09-13 */ entity_personality = [dict oo_unsignedShortForKey:@"entity_personality" defaultValue:entity_personality]; // set up missiles [self setActiveMissile:0]; for (NSUInteger i = 0; i < PLAYER_MAX_MISSILES; i++) { [missile_entity[i] release]; missile_entity[i] = nil; } NSArray *missileRoles = [dict oo_arrayForKey:@"missile_roles"]; if (missileRoles != nil) { for (NSUInteger roleIndex = 0, missileCount = 0; roleIndex < [missileRoles count] && missileCount < max_missiles; roleIndex++) { NSString *missile_desc = [missileRoles oo_stringAtIndex:roleIndex]; if (missile_desc != nil && ![missile_desc isEqualToString:@"NONE"]) { ShipEntity *amiss = [UNIVERSE newShipWithRole:missile_desc]; if (amiss) { missile_list[missileCount] = [OOEquipmentType equipmentTypeWithIdentifier:missile_desc]; missile_entity[missileCount] = amiss; // retain count = 1 missileCount++; } else { OOLogWARN(@"load.failed.missileNotFound", @"couldn't find missile with role '%@' in [PlayerEntity setCommanderDataFromDictionary:], missile entry discarded.", missile_desc); } } missiles = missileCount; } } else // no missile_roles { for (NSUInteger i = 0; i < missiles; i++) { missile_list[i] = [OOEquipmentType equipmentTypeWithIdentifier:@"EQ_MISSILE"]; missile_entity[i] = [UNIVERSE newShipWithRole:@"EQ_MISSILE"]; // retain count = 1 - should be okay as long as we keep a missile with this role // in the base package. } } if (energyBombCompensation) { /* Compensate energy bomb with either a QC mine or the cost of an energy bomb (900 credits). This must be done after missiles are set up. */ if ([self mountMissileWithRole:@"EQ_QC_MINE"]) { OOLog(@"load.upgrade.replacedEnergyBomb", @"Replaced legacy energy bomb with Quirium cascade mine."); } else { credits += 9000; OOLog(@"load.upgrade.replacedEnergyBomb", @"Compensated legacy energy bomb with 900 credits."); } } [self setActiveMissile:0]; [self setHeatInsulation:1.0]; max_forward_shield = BASELINE_SHIELD_LEVEL; max_aft_shield = BASELINE_SHIELD_LEVEL; forward_shield_recharge_rate = 2.0; aft_shield_recharge_rate = 2.0; forward_shield = [self maxForwardShieldLevel]; aft_shield = [self maxAftShieldLevel]; // used to get current_system and target_system here, // but stores the ID in the save file instead // restore subentities status [self deserializeShipSubEntitiesFrom:[dict oo_stringForKey:@"subentities_status"]]; // wormholes NSArray * whArray; whArray = [dict objectForKey:@"wormholes"]; NSEnumerator * whDicts = [whArray objectEnumerator]; NSDictionary * whCurrDict; [scannedWormholes release]; scannedWormholes = [[NSMutableArray alloc] initWithCapacity:[whArray count]]; while ((whCurrDict = [whDicts nextObject]) != nil) { WormholeEntity * wh = [[WormholeEntity alloc] initWithDict:whCurrDict]; [scannedWormholes addObject:wh]; /* TODO - add to Universe if the wormhole hasn't expired yet; but in this case * we need to save/load position and mass as well, which we currently * don't if (equal_seeds([wh origin], system_seed)) { [UNIVERSE addEntity:wh]; } */ } // custom view no. if (_customViews != nil) _customViewIndex = [dict oo_unsignedIntForKey:@"custom_view_index"] % [_customViews count]; // docking clearance protocol [UNIVERSE setDockingClearanceProtocolActive:[dict oo_boolForKey:@"docking_clearance_protocol" defaultValue:NO]]; // trumble information [self setUpTrumbles]; [self setTrumbleValueFrom:[dict objectForKey:@"trumbles"]]; // if it doesn't exist we'll check user-defaults return YES; } ///////////////////////////////////////////////////////// /* Nasty initialization mechanism: PlayerEntity is alloced and inited on demand by +sharedPlayer. This initialization doesn't actually set anything up -- apart from the assertion, it's like doing a bare alloc. -deferredInit does the work that -init "should" be doing. It assumes that -[ShipEntity initWithKey: definition:] will not return an object other than self. This is necessary because we need a pointer to the PlayerEntity early in startup, when ship data hasn't been loaded yet. In particular, we need a pointer to the player to set up the JavaScript environment, we need the JavaScript environment to set up OpenGL, and we need OpenGL set up to load ships. */ - (id) init { NSAssert(gOOPlayer == nil, @"Expected only one PlayerEntity to exist at a time."); return [super initBypassForPlayer]; } - (void) deferredInit { NSAssert(gOOPlayer == self, @"Expected only one PlayerEntity to exist at a time."); NSAssert([super initWithKey:PLAYER_SHIP_DESC definition:[NSDictionary dictionary]] == self, @"PlayerEntity requires -[ShipEntity initWithKey:definition:] to return unmodified self."); maxFieldOfView = MAX_FOV; #if OO_FOV_INFLIGHT_CONTROL_ENABLED fov_delta = 2.0; // multiply by 2 each second #endif compassMode = COMPASS_MODE_BASIC; afterburnerSoundLooping = NO; isPlayer = YES; [self setStatus:STATUS_START_GAME]; int i; for (i = 0; i < PLAYER_MAX_MISSILES; i++) { missile_entity[i] = nil; } [self setUpAndConfirmOK:NO]; save_path = nil; scoopsActive = NO; target_memory_index = 0; DESTROY(dockingReport); dockingReport = [[NSMutableString alloc] init]; [hud resetGuis:[NSDictionary dictionaryWithObjectsAndKeys:[NSDictionary dictionaryWithObjectsAndKeys:nil], @"message_gui", [NSDictionary dictionaryWithObjectsAndKeys:nil], @"comm_log_gui", nil]]; [self initControls]; } - (BOOL) setUpAndConfirmOK:(BOOL)stopOnError { return [self setUpAndConfirmOK:stopOnError saveGame:NO]; } - (BOOL) setUpAndConfirmOK:(BOOL)stopOnError saveGame:(BOOL)saveGame { fieldOfView = [[UNIVERSE gameView] fov:YES]; unsigned i; showDemoShips = NO; show_info_flag = NO; DESTROY(marketSelectedCommodity); // Reset JavaScript. [OOScriptTimer noteGameReset]; [OOScriptTimer updateTimers]; GameController *gc = [[UNIVERSE gameView] gameController]; if (![gc inFullScreenMode] && stopOnError) [gc stopAnimationTimer]; // start of critical section if (EXPECT_NOT(![[OOJavaScriptEngine sharedEngine] reset] && stopOnError)) // always (try to) reset the engine, then find out if we need to stop. { /* Occasionally there's a racing condition between timers being deleted and the js engine needing to be reset: the engine reset stops the timers from being deleted, and undeleted timers don't allow the engine to reset itself properly. If the engine can't reset, let's give ourselves an extra 20ms to allow the timers to delete themselves. We'll piggyback performDeadUpdates: when STATUS_DEAD, the engine waits until kDeadResetTime then restarts Oolite via [UNIVERSE updateGameOver] The variable shot_time is used to keep track of how long ago we were shot. If we're loading a savegame the code will try a new JS reset immediately after failing this reset... */ // set up STATUS_DEAD [self setDockedStation:nil]; // needed for STATUS_DEAD [self setStatus:STATUS_DEAD]; OOLog(@"script.javascript.init.error", @"Scheduling new JavaScript reset."); shot_time = kDeadResetTime - 0.02f; // schedule reinit 20 milliseconds from now. if (![gc inFullScreenMode]) [gc startAnimationTimer]; // keep the game ticking over. return NO; } // end of critical section if (![gc inFullScreenMode] && stopOnError) [gc startAnimationTimer]; // Load locale script before any regular scripts. [OOJSScript jsScriptFromFileNamed:@"oolite-locale-functions.js" properties:nil]; [[GameController sharedController] logProgress:DESC(@"loading-scripts")]; [UNIVERSE setBlockJSPlayerShipProps:NO]; // full access to player.ship properties! DESTROY(worldScripts); DESTROY(worldScriptsRequiringTickle); DESTROY(commodityScripts); #if OOLITE_WINDOWS if (saveGame) { [UNIVERSE preloadSounds]; [self setUpSound]; worldScripts = [[ResourceManager loadScripts] retain]; [UNIVERSE loadConditionScripts]; commodityScripts = [[NSMutableDictionary alloc] init]; } #else /* on OSes that allow safe deletion of open files, can use sounds * on the OXZ screen and other start screens */ [UNIVERSE preloadSounds]; [self setUpSound]; if (saveGame) { worldScripts = [[ResourceManager loadScripts] retain]; [UNIVERSE loadConditionScripts]; commodityScripts = [[NSMutableDictionary alloc] init]; } #endif [[GameController sharedController] logProgress:OOExpandKeyRandomized(@"loading-miscellany")]; // if there is cargo remaining from previously (e.g. a game restart), remove it if ([self cargoList] != nil) { [self removeAllCargo:YES]; // force removal of cargo } [self setShipDataKey:PLAYER_SHIP_DESC]; ship_trade_in_factor = 95; // reset HUD & default commlog behaviour [UNIVERSE setAutoCommLog:YES]; [UNIVERSE setPermanentCommLog:NO]; [multiFunctionDisplayText release]; multiFunctionDisplayText = [[NSMutableDictionary alloc] init]; [multiFunctionDisplaySettings release]; multiFunctionDisplaySettings = [[NSMutableArray alloc] init]; [customDialSettings release]; customDialSettings = [[NSMutableDictionary alloc] init]; [self switchHudTo:@"hud.plist"]; scanner_zoom_rate = 0.0f; longRangeChartMode = OOLRC_MODE_NORMAL; [mission_variables release]; mission_variables = [[NSMutableDictionary alloc] init]; [localVariables release]; localVariables = [[NSMutableDictionary alloc] init]; [self setScriptTarget:nil]; [self resetMissionChoice]; [[UNIVERSE gameView] resetTypedString]; found_system_id = -1; [reputation release]; reputation = [[NSMutableDictionary alloc] initWithCapacity:6]; [reputation oo_setInteger:0 forKey:CONTRACTS_GOOD_KEY]; [reputation oo_setInteger:0 forKey:CONTRACTS_BAD_KEY]; [reputation oo_setInteger:MAX_CONTRACT_REP forKey:CONTRACTS_UNKNOWN_KEY]; [reputation oo_setInteger:0 forKey:PASSAGE_GOOD_KEY]; [reputation oo_setInteger:0 forKey:PASSAGE_BAD_KEY]; [reputation oo_setInteger:MAX_CONTRACT_REP forKey:PASSAGE_UNKNOWN_KEY]; [reputation oo_setInteger:0 forKey:PARCEL_GOOD_KEY]; [reputation oo_setInteger:0 forKey:PARCEL_BAD_KEY]; [reputation oo_setInteger:MAX_CONTRACT_REP forKey:PARCEL_UNKNOWN_KEY]; DESTROY(roleWeights); roleWeights = [[NSMutableArray alloc] initWithCapacity:8]; for (i = 0 ; i < 8 ; i++) { [roleWeights addObject:@"player-unknown"]; } DESTROY(roleWeightFlags); roleWeightFlags = [[NSMutableDictionary alloc] init]; DESTROY(roleSystemList); roleSystemList = [[NSMutableArray alloc] initWithCapacity:32]; energy = 256; weapon_temp = 0.0f; forward_weapon_temp = 0.0f; aft_weapon_temp = 0.0f; port_weapon_temp = 0.0f; starboard_weapon_temp = 0.0f; lastShot = nil; forward_shot_time = INITIAL_SHOT_TIME; aft_shot_time = INITIAL_SHOT_TIME; port_shot_time = INITIAL_SHOT_TIME; starboard_shot_time = INITIAL_SHOT_TIME; ship_temperature = 60.0f; alertFlags = 0; hyperspeed_engaged = NO; autopilot_engaged = NO; velocity = kZeroVector; flightRoll = 0.0f; flightPitch = 0.0f; flightYaw = 0.0f; max_passengers = 0; [passengers release]; passengers = [[NSMutableArray alloc] init]; [passenger_record release]; passenger_record = [[NSMutableDictionary alloc] init]; [contracts release]; contracts = [[NSMutableArray alloc] init]; [contract_record release]; contract_record = [[NSMutableDictionary alloc] init]; [parcels release]; parcels = [[NSMutableArray alloc] init]; [parcel_record release]; parcel_record = [[NSMutableDictionary alloc] init]; [missionDestinations release]; missionDestinations = [[NSMutableDictionary alloc] init]; [shipyard_record release]; shipyard_record = [[NSMutableDictionary alloc] init]; [target_memory release]; target_memory = [[NSMutableArray alloc] initWithCapacity:PLAYER_TARGET_MEMORY_SIZE]; [self clearTargetMemory]; // also does first-time initialisation [self setMissionOverlayDescriptor:nil]; [self setMissionBackgroundDescriptor:nil]; [self setMissionBackgroundSpecial:nil]; [self setEquipScreenBackgroundDescriptor:nil]; marketOffset = 0; DESTROY(marketSelectedCommodity); script_time = 0.0; script_time_check = SCRIPT_TIMER_INTERVAL; script_time_interval = SCRIPT_TIMER_INTERVAL; NSCalendarDate *nowDate = [NSCalendarDate calendarDate]; ship_clock = PLAYER_SHIP_CLOCK_START; ship_clock += [nowDate hourOfDay] * 3600.0; ship_clock += [nowDate minuteOfHour] * 60.0; ship_clock += [nowDate secondOfMinute]; fps_check_time = ship_clock; ship_clock_adjust = 0.0; isSpeechOn = OOSPEECHSETTINGS_OFF; #if OOLITE_ESPEAK voice_gender_m = YES; voice_no = [UNIVERSE setVoice:-1 withGenderM:voice_gender_m]; #endif [_customViews release]; _customViews = nil; _customViewIndex = 0; mouse_control_on = NO; // player commander data // Most of this is probably also set more than once [self setCommanderName:PLAYER_DEFAULT_NAME]; [self setLastsaveName:PLAYER_DEFAULT_NAME]; galaxy_coordinates = NSMakePoint(0x14,0xAD); // 20,173 credits = 1000; fuel = PLAYER_MAX_FUEL; fuel_accumulator = 0.0f; fuel_leak_rate = 0.0f; galaxy_number = 0; // will load real weapon data later forward_weapon_type = nil; aft_weapon_type = nil; port_weapon_type = nil; starboard_weapon_type = nil; scannerRange = (float)SCANNER_MAX_RANGE; weapons_online = YES; ecm_in_operation = NO; last_ecm_time = [UNIVERSE getTime]; compassMode = COMPASS_MODE_BASIC; ident_engaged = NO; max_cargo = 20; // will be reset later marketFilterMode = MARKET_FILTER_MODE_OFF; DESTROY(shipCommodityData); shipCommodityData = [[[UNIVERSE commodities] generateManifestForPlayer] retain]; // set up missiles missiles = PLAYER_STARTING_MISSILES; max_missiles = PLAYER_STARTING_MAX_MISSILES; [eqScripts release]; eqScripts = [[NSMutableArray alloc] init]; primedEquipment = 0; [self setFastEquipmentA:@"EQ_CLOAKING_DEVICE"]; [self setFastEquipmentB:@"EQ_ENERGY_BOMB"]; // for compatibility purposes [self setActiveMissile:0]; for (i = 0; i < missiles; i++) { [missile_entity[i] release]; missile_entity[i] = nil; } [self safeAllMissiles]; [self clearSubEntities]; legalStatus = 0; market_rnd = 0; ship_kills = 0; chart_centre_coordinates = galaxy_coordinates; target_chart_centre = chart_centre_coordinates; cursor_coordinates = galaxy_coordinates; chart_cursor_coordinates = cursor_coordinates; chart_focus_coordinates = cursor_coordinates; chart_zoom = 1.0; target_chart_zoom = 1.0; saved_chart_zoom = 1.0; ANA_mode = OPTIMIZED_BY_NONE; scripted_misjump = NO; _scriptedMisjumpRange = 0.5; scoopOverride = NO; max_forward_shield = BASELINE_SHIELD_LEVEL; max_aft_shield = BASELINE_SHIELD_LEVEL; forward_shield_recharge_rate = 2.0; aft_shield_recharge_rate = 2.0; forward_shield = [self maxForwardShieldLevel]; aft_shield = [self maxAftShieldLevel]; scanClass = CLASS_PLAYER; [UNIVERSE clearGUIs]; dockingClearanceStatus = DOCKING_CLEARANCE_STATUS_GRANTED; targetDockStation = nil; [self setDockedStation:[UNIVERSE station]]; [commLog release]; commLog = nil; [specialCargo release]; specialCargo = nil; // views forwardViewOffset = kZeroVector; aftViewOffset = kZeroVector; portViewOffset = kZeroVector; starboardViewOffset = kZeroVector; customViewOffset = kZeroVector; currentWeaponFacing = WEAPON_FACING_FORWARD; [self currentWeaponStats]; [save_path autorelease]; save_path = nil; [scannedWormholes release]; scannedWormholes = [[NSMutableArray alloc] init]; [self setUpTrumbles]; suppressTargetLost = NO; scoopsActive = NO; [dockingReport release]; dockingReport = [[NSMutableString alloc] init]; [shipAI release]; shipAI = [[AI alloc] initWithStateMachine:PLAYER_DOCKING_AI_NAME andState:@"GLOBAL"]; [self resetAutopilotAI]; lastScriptAlertCondition = [self alertCondition]; entity_personality = ranrot_rand() & 0x7FFF; [self setSystemID:[UNIVERSE findSystemAtCoords:[self galaxy_coordinates] withGalaxy:galaxy_number]]; [UNIVERSE setGalaxyTo:galaxy_number]; [UNIVERSE setSystemTo:system_id]; [self setGalacticHyperspaceBehaviourTo:[[UNIVERSE globalSettings] oo_stringForKey:@"galactic_hyperspace_behaviour" defaultValue:@"BEHAVIOUR_STANDARD"]]; [self setGalacticHyperspaceFixedCoordsTo:[[UNIVERSE globalSettings] oo_stringForKey:@"galactic_hyperspace_fixed_coords" defaultValue:@"96 96"]]; cloaking_device_active = NO; demoShip = nil; [[OOMusicController sharedController] justStop]; [stickProfileScreen release]; stickProfileScreen = [[StickProfileScreen alloc] init]; return YES; } - (void) completeSetUp { [self completeSetUpAndSetTarget:YES]; } - (void) completeSetUpAndSetTarget:(BOOL)setTarget { [OOSoundSource stopAll]; [self setDockedStation:[UNIVERSE station]]; [self setLastAegisLock:[UNIVERSE planet]]; // If loading from a savegame don't reset the targetted system. // Always loading from a savegame nowadays, so... - CIM // if (setTarget) target_system_seed = [UNIVERSE findSystemAtCoords:cursor_coordinates withGalaxySeed:galaxy_seed]; JSContext *context = OOJSAcquireContext(); [self doWorldScriptEvent:OOJSID("startUp") inContext:context withArguments:NULL count:0 timeLimit:kOOJSLongTimeLimit]; OOJSRelinquishContext(context); } - (void) startUpComplete { JSContext *context = OOJSAcquireContext(); [self doWorldScriptEvent:OOJSID("startUpComplete") inContext:context withArguments:NULL count:0 timeLimit:kOOJSLongTimeLimit]; OOJSRelinquishContext(context); } - (BOOL) setUpShipFromDictionary:(NSDictionary *)shipDict { DESTROY(compassTarget); [UNIVERSE setBlockJSPlayerShipProps:NO]; // full access to player.ship properties! if (![super setUpFromDictionary:shipDict]) return NO; DESTROY(cargo); cargo = [[NSMutableArray alloc] initWithCapacity:max_cargo]; // Player-only settings. // // set control factors.. roll_delta = 2.0f * max_flight_roll; pitch_delta = 2.0f * max_flight_pitch; yaw_delta = 2.0f * max_flight_yaw; energy = maxEnergy; //if (forward_weapon_type == WEAPON_NONE) [self setWeaponDataFromType:forward_weapon_type]; scannerRange = (float)SCANNER_MAX_RANGE; [roleSet release]; roleSet = nil; [self setPrimaryRole:@"player"]; [self removeAllEquipment]; [self addEquipmentFromCollection:[shipDict objectForKey:@"extra_equipment"]]; [self resetHud]; [hud setHidden:NO]; // set up missiles // sanity check the number of missiles... if (max_missiles > PLAYER_MAX_MISSILES) max_missiles = PLAYER_MAX_MISSILES; if (missiles > max_missiles) missiles = max_missiles; // end sanity check unsigned i; for (i = 0; i < PLAYER_MAX_MISSILES; i++) { [missile_entity[i] release]; missile_entity[i] = nil; } for (i = 0; i < missiles; i++) { missile_list[i] = [OOEquipmentType equipmentTypeWithIdentifier:@"EQ_MISSILE"]; missile_entity[i] = [UNIVERSE newShipWithRole:@"EQ_MISSILE"]; // retain count = 1 } DESTROY(_primaryTarget); [self safeAllMissiles]; [self setActiveMissile:0]; // set view offsets [self setDefaultViewOffsets]; if (EXPECT(_scaleFactor == 1.0f)) { forwardViewOffset = [shipDict oo_vectorForKey:@"view_position_forward" defaultValue:forwardViewOffset]; aftViewOffset = [shipDict oo_vectorForKey:@"view_position_aft" defaultValue:aftViewOffset]; portViewOffset = [shipDict oo_vectorForKey:@"view_position_port" defaultValue:portViewOffset]; starboardViewOffset = [shipDict oo_vectorForKey:@"view_position_starboard" defaultValue:starboardViewOffset]; } else { forwardViewOffset = vector_multiply_scalar([shipDict oo_vectorForKey:@"view_position_forward" defaultValue:forwardViewOffset],_scaleFactor); aftViewOffset = vector_multiply_scalar([shipDict oo_vectorForKey:@"view_position_aft" defaultValue:aftViewOffset],_scaleFactor); portViewOffset = vector_multiply_scalar([shipDict oo_vectorForKey:@"view_position_port" defaultValue:portViewOffset],_scaleFactor); starboardViewOffset = vector_multiply_scalar([shipDict oo_vectorForKey:@"view_position_starboard" defaultValue:starboardViewOffset],_scaleFactor); } [self setDefaultCustomViews]; NSArray *customViews = [shipDict oo_arrayForKey:@"custom_views"]; if (customViews != nil) { [_customViews release]; _customViews = [customViews retain]; _customViewIndex = 0; } // Load js script [script autorelease]; NSDictionary *scriptProperties = [NSDictionary dictionaryWithObject:self forKey:@"ship"]; script = [OOScript jsScriptFromFileNamed:[shipDict oo_stringForKey:@"script"] properties:scriptProperties]; if (script == nil) { // Do not switch to using a default value above; we want to use the default script if loading fails. script = [OOScript jsScriptFromFileNamed:@"oolite-default-player-script.js" properties:scriptProperties]; } [script retain]; return YES; } - (void) dealloc { DESTROY(compassTarget); DESTROY(hud); DESTROY(multiFunctionDisplayText); DESTROY(multiFunctionDisplaySettings); DESTROY(customDialSettings); DESTROY(commLog); DESTROY(keyconfig_settings); DESTROY(target_memory); DESTROY(_fastEquipmentA); DESTROY(_fastEquipmentB); DESTROY(eqScripts); DESTROY(worldScripts); DESTROY(worldScriptsRequiringTickle); DESTROY(commodityScripts); DESTROY(mission_variables); DESTROY(localVariables); DESTROY(lastTextKey); DESTROY(marketSelectedCommodity); DESTROY(reputation); DESTROY(roleWeights); DESTROY(roleWeightFlags); DESTROY(roleSystemList); DESTROY(passengers); DESTROY(passenger_record); DESTROY(contracts); DESTROY(contract_record); DESTROY(parcels); DESTROY(parcel_record); DESTROY(missionDestinations); DESTROY(shipyard_record); DESTROY(_missionOverlayDescriptor); DESTROY(_missionBackgroundDescriptor); DESTROY(_equipScreenBackgroundDescriptor); DESTROY(_commanderName); DESTROY(_lastsaveName); DESTROY(shipCommodityData); DESTROY(specialCargo); DESTROY(save_path); DESTROY(scenarioKey); DESTROY(_customViews); DESTROY(dockingReport); [self destroySound]; DESTROY(scannedWormholes); DESTROY(wormhole); int i; for (i = 0; i < PLAYER_MAX_MISSILES; i++) DESTROY(missile_entity[i]); for (i = 0; i < PLAYER_MAX_TRUMBLES; i++) DESTROY(trumble[i]); [super dealloc]; } - (NSUInteger) sessionID { // The player ship always belongs to the current session. return [UNIVERSE sessionID]; } - (void) warnAboutHostiles { [self playHostileWarning]; } - (BOOL) canCollide { switch ([self status]) { case STATUS_START_GAME: case STATUS_DOCKING: case STATUS_DOCKED: case STATUS_DEAD: case STATUS_ESCAPE_SEQUENCE: return NO; default: return YES; } } - (NSComparisonResult) compareZeroDistance:(Entity *)otherEntity { return NSOrderedDescending; // always the most near } - (BOOL) validForAddToUniverse { return YES; } - (GLfloat) lookingAtSunWithThresholdAngleCos:(GLfloat) thresholdAngleCos { OOSunEntity *sun = [UNIVERSE sun]; GLfloat measuredCos = 999.0f, measuredCosAbs; GLfloat sunBrightness = 0.0f; Vector relativePosition, unitRelativePosition; if (EXPECT_NOT(!sun)) return 0.0f; // check if camera position is shadowed OOViewID vdir = [UNIVERSE viewDirection]; unsigned i; unsigned ent_count = UNIVERSE->n_entities; Entity **uni_entities = UNIVERSE->sortedEntities; // grab the public sorted list for (i = 0; i < ent_count; i++) { if (uni_entities[i]->isSunlit) { if ([uni_entities[i] isPlanet] || ([uni_entities[i] isShip] && [uni_entities[i] isVisible])) { // the player ship can't shadow internal views if (EXPECT(vdir > VIEW_STARBOARD || ![uni_entities[i] isPlayer])) { float shadow = 1.5f; shadowAtPointOcclusionToValue([self viewpointPosition],1.0f,uni_entities[i],sun,&shadow); /* BUG: if the shadowing entity is not spherical, this gives over-shadowing. True elsewhere as well, but not so obvious there. */ if (shadow < 1) { return 0.0f; } } } } } relativePosition = HPVectorToVector(HPvector_subtract([self viewpointPosition], [sun position])); unitRelativePosition = vector_normal_or_zbasis(relativePosition); switch (vdir) { case VIEW_FORWARD: measuredCos = -dot_product(unitRelativePosition, v_forward); break; case VIEW_AFT: measuredCos = +dot_product(unitRelativePosition, v_forward); break; case VIEW_PORT: measuredCos = +dot_product(unitRelativePosition, v_right); break; case VIEW_STARBOARD: measuredCos = -dot_product(unitRelativePosition, v_right); break; case VIEW_CUSTOM: { Vector relativeView = [self customViewForwardVector]; Vector absoluteView = quaternion_rotate_vector(quaternion_conjugate([self orientation]),relativeView); measuredCos = -dot_product(unitRelativePosition, absoluteView); } break; default: break; } measuredCosAbs = fabs(measuredCos); /* Bugfix: 1.1f - floating point errors can mean the dot product of two normalised vectors can be very slightly more than 1, which can cause extreme flickering of the glare at certain ranges to the sun. The real test is just that it's not still 999 - CIM */ if (thresholdAngleCos <= measuredCosAbs && measuredCosAbs <= 1.1f) // angle from viewpoint to sun <= desired threshold { sunBrightness = (measuredCos - thresholdAngleCos) / (1.0f - thresholdAngleCos); // OOLog(@"glare.debug",@"raw brightness = %f",sunBrightness); if (sunBrightness < 0.0f) sunBrightness = 0.0f; else if (sunBrightness > 1.0f) sunBrightness = 1.0f; } // OOLog(@"glare.debug",@"cos=%f, threshold = %f, brightness = %f",measuredCosAbs,thresholdAngleCos,sunBrightness); return sunBrightness * sunBrightness * sunBrightness; } - (GLfloat) insideAtmosphereFraction { GLfloat insideAtmoFrac = 0.0f; if ([UNIVERSE airResistanceFactor] > 0.01) // player is inside planetary atmosphere { insideAtmoFrac = 1.0f - ([self dialAltitude] * (GLfloat)PLAYER_DIAL_MAX_ALTITUDE / (10.0f * (GLfloat)ATMOSPHERE_DEPTH)); } return insideAtmoFrac; } #ifndef NDEBUG #define STAGE_TRACKING_BEGIN { \ NSString * volatile updateStage = @"initialisation"; \ @try { #define STAGE_TRACKING_END } \ @catch (NSException *exception) \ { \ OOLog(kOOLogException, @"***** Exception during [%@] in %s : %@ : %@ *****", updateStage, __PRETTY_FUNCTION__, [exception name], [exception reason]); \ @throw exception; \ } \ } #define UPDATE_STAGE(x) do { updateStage = (x); } while (0) #else #define STAGE_TRACKING_BEGIN { #define STAGE_TRACKING_END } #define UPDATE_STAGE(x) do { (void) (x); } while (0); #endif - (void) update:(OOTimeDelta)delta_t { STAGE_TRACKING_BEGIN UPDATE_STAGE(@"updateMovementFlags"); [self updateMovementFlags]; UPDATE_STAGE(@"updateAlertCondition"); [self updateAlertCondition]; UPDATE_STAGE(@"updateFuelScoops:"); [self updateFuelScoops:delta_t]; UPDATE_STAGE(@"updateClocks:"); [self updateClocks:delta_t]; // scripting UPDATE_STAGE(@"updateTimers"); [OOScriptTimer updateTimers]; UPDATE_STAGE(@"checkScriptsIfAppropriate"); [self checkScriptsIfAppropriate]; // deal with collisions UPDATE_STAGE(@"manageCollisions"); [self manageCollisions]; UPDATE_STAGE(@"pollControls:"); [self pollControls:delta_t]; UPDATE_STAGE(@"updateTrumbles:"); [self updateTrumbles:delta_t]; OOEntityStatus status = [self status]; /* Validate that if the status is STATUS_START_GAME we're on one * of the few GUI screens which that makes sense for */ if (EXPECT_NOT(status == STATUS_START_GAME && gui_screen != GUI_SCREEN_INTRO1 && gui_screen != GUI_SCREEN_SHIPLIBRARY && gui_screen != GUI_SCREEN_NEWGAME && gui_screen != GUI_SCREEN_OXZMANAGER && gui_screen != GUI_SCREEN_LOAD && gui_screen != GUI_SCREEN_KEYBOARD)) { // and if not, do a restart of the GUI UPDATE_STAGE(@"setGuiToIntroFirstGo:"); [self setGuiToIntroFirstGo:YES]; //set up demo mode } if (status == STATUS_AUTOPILOT_ENGAGED || status == STATUS_ESCAPE_SEQUENCE) { UPDATE_STAGE(@"performAutopilotUpdates:"); [self performAutopilotUpdates:delta_t]; } else if (![self isDocked]) { UPDATE_STAGE(@"performInFlightUpdates:"); [self performInFlightUpdates:delta_t]; } /* NOTE: status-contingent updates are not a switch since they can cascade when status changes. */ if (status == STATUS_IN_FLIGHT) { UPDATE_STAGE(@"doBookkeeping:"); [self doBookkeeping:delta_t]; } if (status == STATUS_WITCHSPACE_COUNTDOWN) { UPDATE_STAGE(@"performWitchspaceCountdownUpdates:"); [self performWitchspaceCountdownUpdates:delta_t]; } if (status == STATUS_EXITING_WITCHSPACE) { UPDATE_STAGE(@"performWitchspaceExitUpdates:"); [self performWitchspaceExitUpdates:delta_t]; } if (status == STATUS_LAUNCHING) { UPDATE_STAGE(@"performLaunchingUpdates:"); [self performLaunchingUpdates:delta_t]; } if (status == STATUS_DOCKING) { UPDATE_STAGE(@"performDockingUpdates:"); [self performDockingUpdates:delta_t]; } if (status == STATUS_DEAD) { UPDATE_STAGE(@"performDeadUpdates:"); [self performDeadUpdates:delta_t]; } UPDATE_STAGE(@"updateWormholes"); [self updateWormholes]; STAGE_TRACKING_END } - (void) doBookkeeping:(double) delta_t { STAGE_TRACKING_BEGIN double speed_delta = SHIP_THRUST_FACTOR * thrust; OOSunEntity *sun = [UNIVERSE sun]; double external_temp = 0; GLfloat air_friction = 0.0f; air_friction = 0.5f * [UNIVERSE airResistanceFactor]; if (air_friction < 0.005f) // aRF < 0.01 { // stops mysteriously overheating and exploding in the middle of empty space air_friction = 0; } UPDATE_STAGE(@"updating weapon temperatures and shot times"); // cool all weapons. float coolAmount = WEAPON_COOLING_FACTOR * delta_t; forward_weapon_temp = fdim(forward_weapon_temp, coolAmount); aft_weapon_temp = fdim(aft_weapon_temp, coolAmount); port_weapon_temp = fdim(port_weapon_temp, coolAmount); starboard_weapon_temp = fdim(starboard_weapon_temp, coolAmount); // update shot times. forward_shot_time += delta_t; aft_shot_time += delta_t; port_shot_time += delta_t; starboard_shot_time += delta_t; // copy new temp & shot time to main temp & shot time switch (currentWeaponFacing) { case WEAPON_FACING_FORWARD: weapon_temp = forward_weapon_temp; shot_time = forward_shot_time; break; case WEAPON_FACING_AFT: weapon_temp = aft_weapon_temp; shot_time = aft_shot_time; break; case WEAPON_FACING_PORT: weapon_temp = port_weapon_temp; shot_time = port_shot_time; break; case WEAPON_FACING_STARBOARD: weapon_temp = starboard_weapon_temp; shot_time = starboard_shot_time; break; case WEAPON_FACING_NONE: break; } // cloaking device if ([self hasCloakingDevice] && cloaking_device_active) { UPDATE_STAGE(@"updating cloaking device"); energy -= (float)delta_t * CLOAKING_DEVICE_ENERGY_RATE; if (energy < CLOAKING_DEVICE_MIN_ENERGY) [self deactivateCloakingDevice]; } // military_jammer if ([self hasMilitaryJammer]) { UPDATE_STAGE(@"updating military jammer"); if (military_jammer_active) { energy -= (float)delta_t * MILITARY_JAMMER_ENERGY_RATE; if (energy < MILITARY_JAMMER_MIN_ENERGY) military_jammer_active = NO; } else { if (energy > 1.5 * MILITARY_JAMMER_MIN_ENERGY) military_jammer_active = YES; } } // ecm if (ecm_in_operation) { UPDATE_STAGE(@"updating ECM"); if (energy > 0.0) energy -= (float)(ECM_ENERGY_DRAIN_FACTOR * delta_t); // drain energy because of the ECM else { ecm_in_operation = NO; [UNIVERSE addMessage:DESC(@"ecm-out-of-juice") forCount:3.0]; } if ([UNIVERSE getTime] > ecm_start_time + ECM_DURATION) { ecm_in_operation = NO; } } // Energy Banks and Shields /* Shield-charging behaviour, as per Eric's proposal: 1. If shields are less than a threshold, recharge with all available energy 2. If energy banks are below threshold, recharge with generated energy 3. Charge shields with any surplus energy */ UPDATE_STAGE(@"updating energy and shield charges"); // 1. (Over)charge energy banks (will get normalised later) energy += [self energyRechargeRate] * delta_t; // 2. Calculate shield recharge rates float fwdMax = [self maxForwardShieldLevel]; float aftMax = [self maxAftShieldLevel]; float shieldRechargeFwd = [self forwardShieldRechargeRate] * delta_t; float shieldRechargeAft = [self aftShieldRechargeRate] * delta_t; float rechargeFwd = MIN(shieldRechargeFwd, fwdMax - forward_shield); float rechargeAft = MIN(shieldRechargeAft, aftMax - aft_shield); // Note: we've simplified this a little, so if either shield is below // the critical threshold, we allocate all energy. Ideally we // would only allocate the full recharge to the critical shield, // but doing so would add another few levels of if-then below. float energyForShields = energy; if( (forward_shield > fwdMax * 0.25) && (aft_shield > aftMax * 0.25) ) { // TODO: Can this be cached anywhere sensibly (without adding another member variable)? float minEnergyBankLevel = [[UNIVERSE globalSettings] oo_floatForKey:@"shield_charge_energybank_threshold" defaultValue:0.25]; energyForShields = MAX(0.0, energy -0.1 - (maxEnergy * minEnergyBankLevel)); // NB: The - 0.1 ensures the energy value does not 'bounce' across the critical energy message and causes spurious energy-low warnings } if( forward_shield < aft_shield ) { rechargeFwd = MIN(rechargeFwd, energyForShields); rechargeAft = MIN(rechargeAft, energyForShields - rechargeFwd); } else { rechargeAft = MIN(rechargeAft, energyForShields); rechargeFwd = MIN(rechargeFwd, energyForShields - rechargeAft); } // 3. Recharge shields, drain banks, and clamp values forward_shield += rechargeFwd; aft_shield += rechargeAft; energy -= rechargeFwd + rechargeAft; forward_shield = OOClamp_0_max_f(forward_shield, fwdMax); aft_shield = OOClamp_0_max_f(aft_shield, aftMax); energy = OOClamp_0_max_f(energy, maxEnergy); if (sun) { UPDATE_STAGE(@"updating sun effects"); // set the ambient temperature here double sun_zd = sun->zero_distance; // square of distance double sun_cr = sun->collision_radius; double alt1 = sun_cr * sun_cr / sun_zd; external_temp = SUN_TEMPERATURE * alt1; if ([sun goneNova]) external_temp *= 100; // fuel scooping during the nova mission very unlikely if ([sun willGoNova]) external_temp *= 3; // do Revised sun-skimming check here... if ([self hasFuelScoop] && alt1 > 0.75 && [self fuel] < [self fuelCapacity]) { fuel_accumulator += (float)(delta_t * flightSpeed * 0.010 / [self fuelChargeRate]); // are we fast enough to collect any fuel? scoopsActive = YES && flightSpeed > 0.1f; while (fuel_accumulator > 1.0f) { [self setFuel:[self fuel] + 1]; fuel_accumulator -= 1.0f; [self doScriptEvent:OOJSID("shipScoopedFuel")]; } [UNIVERSE displayCountdownMessage:DESC(@"fuel-scoop-active") forCount:1.0]; } } //Bug #11692 CmdrJames added Status entering witchspace OOEntityStatus status = [self status]; if ((status != STATUS_ESCAPE_SEQUENCE) && (status != STATUS_ENTERING_WITCHSPACE)) { UPDATE_STAGE(@"updating cabin temperature"); // work on the cabin temperature float heatInsulation = [self heatInsulation]; // Optimisation, suggested by EricW float deltaInsulation = delta_t/heatInsulation; float heatThreshold = heatInsulation * 100.0f; ship_temperature += (float)( flightSpeed * air_friction * deltaInsulation); // wind_speed if (external_temp > heatThreshold && external_temp > ship_temperature) ship_temperature += (float)((external_temp - ship_temperature) * SHIP_INSULATION_FACTOR * deltaInsulation); else { if (ship_temperature > SHIP_MIN_CABIN_TEMP) ship_temperature += (float)((external_temp - heatThreshold - ship_temperature) * SHIP_COOLING_FACTOR * deltaInsulation); } if (ship_temperature > SHIP_MAX_CABIN_TEMP) [self takeHeatDamage: delta_t * ship_temperature]; } if ((status == STATUS_ESCAPE_SEQUENCE)&&(shot_time > ESCAPE_SEQUENCE_TIME)) { UPDATE_STAGE(@"resetting after escape"); ShipEntity *doppelganger = (ShipEntity*)[self foundTarget]; // reset legal status again! Could have changed if a previously launched missile hit a clean NPC while in the escape pod. [self setBounty:0 withReason:kOOLegalStatusReasonEscapePod]; bounty = 0; thrust = max_thrust; // re-enable inertialess drives // no access to all player.ship properties while inside the escape pod, // we're not supposed to be inside our ship anymore! [self doScriptEvent:OOJSID("escapePodSequenceOver")]; // allow oxps to override the escape pod target /** * This code branch doesn't seem to be used any more - see ~line 6000 * Should we remove it? - CIM */ if (EXPECT_NOT(target_system_id != system_id)) // overridden: we're going to a nearby system! { system_id = target_system_id; [UNIVERSE setSystemTo:system_id]; galaxy_coordinates = PointFromString([[UNIVERSE systemManager] getProperty:@"coordinates" forSystem:system_id inGalaxy:galaxy_number]); [UNIVERSE setUpSpace]; // run initial system population [UNIVERSE populateNormalSpace]; [self setDockTarget:[UNIVERSE station]]; // send world script events to let oxps know we're in a new system. // all player.ship properties are still disabled at this stage. [UNIVERSE setWitchspaceBreakPattern:YES]; [self doScriptEvent:OOJSID("shipWillExitWitchspace")]; [self doScriptEvent:OOJSID("shipExitedWitchspace")]; [[UNIVERSE planet] update: 2.34375 * market_rnd]; // from 0..10 minutes [[UNIVERSE station] update: 2.34375 * market_rnd]; // from 0..10 minutes } Entity *dockTargetEntity = [UNIVERSE entityForUniversalID:_dockTarget]; // main station in the original system, unless overridden. if ([dockTargetEntity isStation]) // fails if _dockTarget is NO_TARGET { [doppelganger becomeExplosion]; // blow up the doppelganger // restore player ship ShipEntity *player_ship = [UNIVERSE newShipWithName:[self shipDataKey]]; // retained if (player_ship) { // FIXME: this should use OOShipType, which should exist. -- Ahruman [self setMesh:[player_ship mesh]]; [player_ship release]; // we only wanted it for its polygons! } [UNIVERSE setViewDirection:VIEW_FORWARD]; [UNIVERSE setBlockJSPlayerShipProps:NO]; // re-enable player.ship! [self enterDock:(StationEntity *)dockTargetEntity]; } else // no dock target? dock target is not a station? game over! { [self setStatus:STATUS_DEAD]; //[self playGameOver]; // no death explosion sounds for player pods // no shipDied events for player pods, either [UNIVERSE displayMessage:DESC(@"gameoverscreen-escape-pod") forCount:kDeadResetTime]; [UNIVERSE displayMessage:@"" forCount:kDeadResetTime]; [self showGameOver]; } } // MOVED THE FOLLOWING FROM PLAYERENTITY POLLFLIGHTCONTROLS: travelling_at_hyperspeed = (flightSpeed > maxFlightSpeed); if (hyperspeed_engaged) { UPDATE_STAGE(@"updating hyperspeed"); // increase speed up to maximum hyperspeed if (flightSpeed < maxFlightSpeed * HYPERSPEED_FACTOR) flightSpeed += (float)(speed_delta * delta_t * HYPERSPEED_FACTOR); if (flightSpeed > maxFlightSpeed * HYPERSPEED_FACTOR) flightSpeed = (float)(maxFlightSpeed * HYPERSPEED_FACTOR); // check for mass lock hyperspeed_locked = [self massLocked]; // check for mass lock & external temperature? //hyperspeed_locked = flightSpeed * air_friction > 40.0f+(ship_temperature - external_temp ) * SHIP_COOLING_FACTOR || [self massLocked]; if (hyperspeed_locked) { [self playJumpMassLocked]; [UNIVERSE addMessage:DESC(@"jump-mass-locked") forCount:4.5]; hyperspeed_engaged = NO; } } else { if (afterburner_engaged) { UPDATE_STAGE(@"updating afterburner"); float abFactor = [self afterburnerFactor]; float maxInjectionSpeed = maxFlightSpeed * abFactor; if (flightSpeed > maxInjectionSpeed) { // decellerate to maxInjectionSpeed but slower than without afterburner. flightSpeed -= (float)(speed_delta * delta_t * abFactor); } else { if (flightSpeed < maxInjectionSpeed) flightSpeed += (float)(speed_delta * delta_t * abFactor); if (flightSpeed > maxInjectionSpeed) flightSpeed = maxInjectionSpeed; } fuel_accumulator -= (float)(delta_t * afterburner_rate); while ((fuel_accumulator < 0)&&(fuel > 0)) { fuel_accumulator += 1.0f; if (--fuel <= MIN_FUEL) afterburner_engaged = NO; } } else { UPDATE_STAGE(@"slowing from hyperspeed"); // slow back down... if (travelling_at_hyperspeed) { // decrease speed to maximum normal speed float deceleration = (speed_delta * delta_t * HYPERSPEED_FACTOR); if (alertFlags & ALERT_FLAG_MASS_LOCK) { // decelerate much quicker in masslocks // this does also apply to injector deceleration // but it's not very noticeable deceleration *= 3; } flightSpeed -= deceleration; if (flightSpeed < maxFlightSpeed) flightSpeed = maxFlightSpeed; } } } // fuel leakage if ((fuel_leak_rate > 0.0)&&(fuel > 0)) { UPDATE_STAGE(@"updating fuel leakage"); fuel_accumulator -= (float)(fuel_leak_rate * delta_t); while ((fuel_accumulator < 0)&&(fuel > 0)) { fuel_accumulator += 1.0f; fuel--; } if (fuel == 0) fuel_leak_rate = 0; } // smart_zoom UPDATE_STAGE(@"updating scanner zoom"); if (scanner_zoom_rate) { double z = [hud scannerZoom]; double z1 = z + scanner_zoom_rate * delta_t; if (scanner_zoom_rate > 0.0) { if (floor(z1) > floor(z)) { z1 = floor(z1); scanner_zoom_rate = 0.0f; } } else { if (z1 < 1.0) { z1 = 1.0; scanner_zoom_rate = 0.0f; } } [hud setScannerZoom:z1]; } [[UNIVERSE gameView] setFov:fieldOfView fromFraction:YES]; // scanner sanity check - lose any targets further than maximum scanner range ShipEntity *primeTarget = [self primaryTarget]; if (primeTarget && HPdistance2([primeTarget position], [self position]) > SCANNER_MAX_RANGE2 && !autopilot_engaged) { [UNIVERSE addMessage:DESC(@"target-lost") forCount:3.0]; [self removeTarget:primeTarget]; } // compass sanity check and update target for changed mode [self validateCompassTarget]; // update subentities UPDATE_STAGE(@"updating subentities"); totalBoundingBox = boundingBox; // reset totalBoundingBox ShipEntity *se = nil; foreach (se, [self subEntities]) { [se update:delta_t]; if ([se isShip]) { BoundingBox sebb = [se findSubentityBoundingBox]; bounding_box_add_vector(&totalBoundingBox, sebb.max); bounding_box_add_vector(&totalBoundingBox, sebb.min); } } // and one thing which isn't a subentity. Fixes bug with // mispositioned laser beams particularly noticeable on side view. if (lastShot != nil) { [lastShot update:0.0]; lastShot = nil; } STAGE_TRACKING_END } - (void) updateMovementFlags { hasMoved = !HPvector_equal(position, lastPosition); hasRotated = !quaternion_equal(orientation, lastOrientation); lastPosition = position; lastOrientation = orientation; } - (void) updateAlertConditionForNearbyEntities { if (![self isInSpace] || [self status] == STATUS_DOCKING) { [self clearAlertFlags]; // not needed while docked return; } int i, ent_count = UNIVERSE->n_entities; Entity **uni_entities = UNIVERSE->sortedEntities; // grab the public sorted list Entity *my_entities[ent_count]; Entity *scannedEntity = nil; for (i = 0; i < ent_count; i++) { my_entities[i] = [uni_entities[i] retain]; // retained } BOOL massLocked = NO; BOOL foundHostiles = NO; #if OO_VARIABLE_TORUS_SPEED BOOL needHyperspeedNearest = YES; double hsnDistance = 0; #endif for (i = 0; i < ent_count; i++) // scanner lollypops { scannedEntity = my_entities[i]; #if OO_VARIABLE_TORUS_SPEED if (EXPECT_NOT(needHyperspeedNearest)) { // not visual effects, waypoints, ships, etc. if (scannedEntity != self && [scannedEntity canCollide]) { hsnDistance = sqrt(scannedEntity->zero_distance)-[scannedEntity collisionRadius]; needHyperspeedNearest = NO; } } else if ([scannedEntity isStellarObject]) { // planets, stars might be closest surface even if not // closest centre. That could be true of others, but the // error is negligible there. double thisHSN = sqrt(scannedEntity->zero_distance)-[scannedEntity collisionRadius]; if (thisHSN < hsnDistance) { hsnDistance = thisHSN; } } #endif if (scannedEntity->zero_distance < SCANNER_MAX_RANGE2 || !scannedEntity->isShip) { int theirClass = [scannedEntity scanClass]; massLocked |= [self checkEntityForMassLock:scannedEntity withScanClass:theirClass]; // we just need one masslocker.. if (theirClass != CLASS_NO_DRAW) { if (theirClass == CLASS_THARGOID || [scannedEntity isCascadeWeapon]) { foundHostiles = YES; } else if ([scannedEntity isShip]) { ShipEntity *ship = (ShipEntity *)scannedEntity; foundHostiles |= (([ship hasHostileTarget])&&([ship primaryTarget] == self)); } } } } #if OO_VARIABLE_TORUS_SPEED if (EXPECT_NOT(needHyperspeedNearest)) { // this case should only occur in an otherwise empty // interstellar space - unlikely but possible hyperspeedFactor = MIN_HYPERSPEED_FACTOR; } else { // once nearest object is >4x scanner range // start increasing torus speed double factor = hsnDistance/(4*SCANNER_MAX_RANGE); if (factor < 1.0) { hyperspeedFactor = MIN_HYPERSPEED_FACTOR; } else { hyperspeedFactor = MIN_HYPERSPEED_FACTOR * sqrt(factor); if (hyperspeedFactor > MAX_HYPERSPEED_FACTOR) { // caps out at ~10^8m from nearest object // which takes ~10 minutes of flying hyperspeedFactor = MAX_HYPERSPEED_FACTOR; } } } #endif [self setAlertFlag:ALERT_FLAG_MASS_LOCK to:massLocked]; [self setAlertFlag:ALERT_FLAG_HOSTILES to:foundHostiles]; for (i = 0; i < ent_count; i++) { [my_entities[i] release]; // released } BOOL energyCritical = NO; if (energy < 64 && energy < maxEnergy * 0.8) { energyCritical = YES; } [self setAlertFlag:ALERT_FLAG_ENERGY to:energyCritical]; [self setAlertFlag:ALERT_FLAG_TEMP to:([self hullHeatLevel] > .90)]; [self setAlertFlag:ALERT_FLAG_ALT to:([self dialAltitude] < .10)]; } - (void) setMaxFlightPitch:(GLfloat)new { max_flight_pitch = new; pitch_delta = 2.0 * new; } - (void) setMaxFlightRoll:(GLfloat)new { max_flight_roll = new; roll_delta = 2.0 * new; } - (void) setMaxFlightYaw:(GLfloat)new { max_flight_yaw = new; yaw_delta = 2.0 * new; } - (BOOL) checkEntityForMassLock:(Entity *)ent withScanClass:(int)theirClass { BOOL massLocked = NO; if (EXPECT_NOT([ent isStellarObject])) { Entity *stellar = (Entity *)ent; if (EXPECT([stellar planetType] != STELLAR_TYPE_MINIATURE)) { double dist = stellar->zero_distance; double rad = stellar->collision_radius; double factor = ([stellar isSun]) ? 2.0 : 4.0; // plus ensure mass lock when 25 km or less from the surface of small stellar bodies // dist is a square distance so it needs to be compared to (rad+25000) * (rad+25000)! if (dist < rad*rad*factor || dist < rad*rad + 50000*rad + 625000000 ) { massLocked = YES; } } } else if (theirClass != CLASS_NO_DRAW) { // cloaked ships do not mass lock! if (EXPECT_NOT ([ent isShip] && [(ShipEntity *)ent isCloaked])) { theirClass = CLASS_NO_DRAW; } } if (!massLocked && ent->zero_distance <= SCANNER_MAX_RANGE2) { switch (theirClass) { case CLASS_NO_DRAW: case CLASS_PLAYER: case CLASS_BUOY: case CLASS_ROCK: case CLASS_CARGO: case CLASS_MINE: case CLASS_VISUAL_EFFECT: break; case CLASS_THARGOID: case CLASS_MISSILE: case CLASS_STATION: case CLASS_POLICE: case CLASS_MILITARY: case CLASS_WORMHOLE: default: massLocked = YES; break; } } return massLocked; } - (void) updateAlertCondition { [self updateAlertConditionForNearbyEntities]; /* TODO: update alert condition once per frame. Tried this before, but there turned out to be complications. See mailing list archive. -- Ahruman 20070802 */ OOAlertCondition cond = [self alertCondition]; OOTimeAbsolute t = [UNIVERSE getTime]; if (cond != lastScriptAlertCondition) { ShipScriptEventNoCx(self, "alertConditionChanged", INT_TO_JSVAL(cond), INT_TO_JSVAL(lastScriptAlertCondition)); lastScriptAlertCondition = cond; } /* Update heuristic assessment of whether player is fleeing */ if (cond == ALERT_CONDITION_DOCKED || cond == ALERT_CONDITION_GREEN || (cond == ALERT_CONDITION_YELLOW && energy == maxEnergy)) { fleeing_status = PLAYER_FLEEING_NONE; } else if (fleeing_status == PLAYER_FLEEING_UNLIKELY && (energy > maxEnergy*0.6 || cond != ALERT_CONDITION_RED)) { fleeing_status = PLAYER_FLEEING_NONE; } else if ((fleeing_status == PLAYER_FLEEING_MAYBE || fleeing_status == PLAYER_FLEEING_UNLIKELY) && cargo_dump_time > last_shot_time) { fleeing_status = PLAYER_FLEEING_CARGO; } else if (fleeing_status == PLAYER_FLEEING_MAYBE && last_shot_time + 10 > t) { fleeing_status = PLAYER_FLEEING_NONE; } else if (fleeing_status == PLAYER_FLEEING_LIKELY && last_shot_time + 10 > t) { fleeing_status = PLAYER_FLEEING_UNLIKELY; } else if (fleeing_status == PLAYER_FLEEING_NONE && cond == ALERT_CONDITION_RED && last_shot_time + 10 < t && flightSpeed > 0.75*maxFlightSpeed) { fleeing_status = PLAYER_FLEEING_MAYBE; } else if ((fleeing_status == PLAYER_FLEEING_MAYBE || fleeing_status == PLAYER_FLEEING_CARGO) && cond == ALERT_CONDITION_RED && last_shot_time + 10 < t && flightSpeed > 0.75*maxFlightSpeed && energy < maxEnergy * 0.5 && (forward_shield < [self maxForwardShieldLevel]*0.25 || aft_shield < [self maxAftShieldLevel]*0.25)) { fleeing_status = PLAYER_FLEEING_LIKELY; } } - (void) updateFuelScoops:(OOTimeDelta)delta_t { if (scoopsActive) { [self updateFuelScoopSoundWithInterval:delta_t]; if (![self scoopOverride]) { scoopsActive = NO; [self updateFuelScoopSoundWithInterval:delta_t]; } } } - (void) updateClocks:(OOTimeDelta)delta_t { // shot time updates are still needed here for STATUS_DEAD! shot_time += delta_t; script_time += delta_t; unsigned prev_day = floor(ship_clock / 86400); ship_clock += delta_t; if (ship_clock_adjust > 0.0) // adjust for coming out of warp (add LY * LY hrs) { double fine_adjust = delta_t * 7200.0; if (ship_clock_adjust > 86400) // more than a day fine_adjust = delta_t * 115200.0; // 16 times faster if (ship_clock_adjust > 0) { if (fine_adjust > ship_clock_adjust) fine_adjust = ship_clock_adjust; ship_clock += fine_adjust; ship_clock_adjust -= fine_adjust; } else { if (fine_adjust < ship_clock_adjust) fine_adjust = ship_clock_adjust; ship_clock -= fine_adjust; ship_clock_adjust += fine_adjust; } } else ship_clock_adjust = 0.0; unsigned now_day = floor(ship_clock / 86400.0); while (prev_day < now_day) { prev_day++; [self doScriptEvent:OOJSID("dayChanged") withArgument:[NSNumber numberWithUnsignedInt:prev_day]]; // not impossible that at ultra-low frame rates two of these will // happen in a single update. } //fps if (ship_clock > fps_check_time) { if (![self clockAdjusting]) { fps_counter = (int)([UNIVERSE timeAccelerationFactor] * floor([UNIVERSE framesDoneThisUpdate] / (fps_check_time - last_fps_check_time))); last_fps_check_time = fps_check_time; fps_check_time = ship_clock + MINIMUM_GAME_TICK; } else { // Good approximation for when the clock is adjusting and proper fps calculation // cannot be performed. fps_counter = (int)([UNIVERSE timeAccelerationFactor] * floor(1.0 / delta_t)); fps_check_time = ship_clock + MINIMUM_GAME_TICK; } [UNIVERSE resetFramesDoneThisUpdate]; // Reset frame counter } } - (void) checkScriptsIfAppropriate { if (script_time <= script_time_check) return; if ([self status] != STATUS_IN_FLIGHT) { switch (gui_screen) { // Screens where no world script tickles are performed case GUI_SCREEN_MAIN: case GUI_SCREEN_INTRO1: case GUI_SCREEN_SHIPLIBRARY: case GUI_SCREEN_KEYBOARD: case GUI_SCREEN_NEWGAME: case GUI_SCREEN_OXZMANAGER: case GUI_SCREEN_MARKET: case GUI_SCREEN_MARKETINFO: case GUI_SCREEN_OPTIONS: case GUI_SCREEN_GAMEOPTIONS: case GUI_SCREEN_LOAD: case GUI_SCREEN_SAVE: case GUI_SCREEN_SAVE_OVERWRITE: case GUI_SCREEN_STICKMAPPER: case GUI_SCREEN_STICKPROFILE: case GUI_SCREEN_MISSION: case GUI_SCREEN_REPORT: return; // Screens from which it's safe to jump to the mission screen // case GUI_SCREEN_CONTRACTS: case GUI_SCREEN_EQUIP_SHIP: case GUI_SCREEN_INTERFACES: case GUI_SCREEN_MANIFEST: case GUI_SCREEN_SHIPYARD: case GUI_SCREEN_LONG_RANGE_CHART: case GUI_SCREEN_SHORT_RANGE_CHART: case GUI_SCREEN_STATUS: case GUI_SCREEN_SYSTEM_DATA: // Test passed, we can run scripts. Nothing to do here. break; } } // Test either passed or never ran, run scripts. [self checkScript]; script_time_check += script_time_interval; } - (void) updateTrumbles:(OOTimeDelta)delta_t { OOTrumble **trumbles = [self trumbleArray]; NSUInteger i; for (i = [self trumbleCount] ; i > 0; i--) { OOTrumble* trum = trumbles[i - 1]; [trum updateTrumble:delta_t]; } } - (void) performAutopilotUpdates:(OOTimeDelta)delta_t { [self processBehaviour:delta_t]; [self applyVelocity:delta_t]; [self doBookkeeping:delta_t]; } - (BOOL) engageAutopilotToStation:(StationEntity *)stationForDocking { if (stationForDocking == nil) return NO; if ([self isDocked]) return NO; if (autopilot_engaged && [self targetStation] == stationForDocking) { return YES; } [self setTargetStation:stationForDocking]; DESTROY(_primaryTarget); autopilot_engaged = YES; ident_engaged = NO; [self safeAllMissiles]; velocity = kZeroVector; [self setStatus:STATUS_AUTOPILOT_ENGAGED]; [self resetAutopilotAI]; [shipAI setState:@"BEGIN_DOCKING"]; // reboot the AI [self playAutopilotOn]; [self doScriptEvent:OOJSID("playerStartedAutoPilot") withArgument:stationForDocking]; [self setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_GRANTED]; [[OOMusicController sharedController] playDockingMusic]; if (afterburner_engaged) { afterburner_engaged = NO; if (afterburnerSoundLooping) [self stopAfterburnerSound]; } return YES; } - (void) disengageAutopilot { if (autopilot_engaged) { [self abortDocking]; // let the station know that you are no longer on approach behaviour = BEHAVIOUR_IDLE; frustration = 0.0; autopilot_engaged = NO; DESTROY(_primaryTarget); [self setTargetStation:nil]; [self setStatus:STATUS_IN_FLIGHT]; [self playAutopilotOff]; [self setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE]; [[OOMusicController sharedController] stopDockingMusic]; [self doScriptEvent:OOJSID("playerCancelledAutoPilot")]; [self resetAutopilotAI]; } } - (void) resetAutopilotAI { AI *myAI = [self getAI]; // JSAI: will need changing if oolite-dockingAI.js written if (![[myAI name] isEqualToString:PLAYER_DOCKING_AI_NAME]) { [self setAITo:PLAYER_DOCKING_AI_NAME ]; } [myAI clearAllData]; [myAI setState:@"GLOBAL"]; [myAI setNextThinkTime:[UNIVERSE getTime] + 2]; [myAI setOwner:self]; } #define VELOCITY_CLEANUP_MIN 2000.0f // Minimum speed for "power braking". #define VELOCITY_CLEANUP_FULL 5000.0f // Speed at which full "power braking" factor is used. #define VELOCITY_CLEANUP_RATE 0.001f // Factor for full "power braking". #if OO_VARIABLE_TORUS_SPEED - (GLfloat) hyperspeedFactor { return hyperspeedFactor; } #endif - (BOOL) injectorsEngaged { return afterburner_engaged; } - (BOOL) hyperspeedEngaged { return hyperspeed_engaged; } - (void) performInFlightUpdates:(OOTimeDelta)delta_t { STAGE_TRACKING_BEGIN // do flight routines //// velocity stuff UPDATE_STAGE(@"applying newtonian drift"); assert(VELOCITY_CLEANUP_FULL > VELOCITY_CLEANUP_MIN); [self applyVelocity:delta_t]; GLfloat thrust_factor = 1.0; if (flightSpeed > maxFlightSpeed) { if (afterburner_engaged) { thrust_factor = [self afterburnerFactor]; } else { thrust_factor = HYPERSPEED_FACTOR; } } GLfloat velmag = magnitude(velocity); GLfloat velmag2 = velmag - (float)delta_t * thrust * thrust_factor; if (velmag > 0) { UPDATE_STAGE(@"applying power braking"); if (velmag > VELOCITY_CLEANUP_MIN) { GLfloat rate; // Fix up extremely ridiculous speeds that can happen in collisions or explosions if (velmag > VELOCITY_CLEANUP_FULL) rate = VELOCITY_CLEANUP_RATE; else rate = (velmag - VELOCITY_CLEANUP_MIN) / (VELOCITY_CLEANUP_FULL - VELOCITY_CLEANUP_MIN) * VELOCITY_CLEANUP_RATE; velmag2 -= velmag * rate; } if (velmag2 < 0.0f) velocity = kZeroVector; else velocity = vector_multiply_scalar(velocity, velmag2 / velmag); } UPDATE_STAGE(@"updating joystick"); [self applyRoll:(float)delta_t*flightRoll andClimb:(float)delta_t*flightPitch]; if (flightYaw != 0.0) { [self applyYaw:(float)delta_t*flightYaw]; } UPDATE_STAGE(@"applying para-newtonian thrust"); [self moveForward:delta_t*flightSpeed]; UPDATE_STAGE(@"updating targeting"); [self updateTargeting]; STAGE_TRACKING_END } - (void) performWitchspaceCountdownUpdates:(OOTimeDelta)delta_t { STAGE_TRACKING_BEGIN UPDATE_STAGE(@"doing bookkeeping"); [self doBookkeeping:delta_t]; UPDATE_STAGE(@"updating countdown timer"); witchspaceCountdown = fdim(witchspaceCountdown, delta_t); // damaged gal drive? abort! /* TODO: this check should possibly be hasEquipmentItemProviding:, * but if it was we'd need to know which item was actually doing * the providing so it could be removed. */ if (EXPECT_NOT(galactic_witchjump && ![self hasEquipmentItem:@"EQ_GAL_DRIVE"])) { galactic_witchjump = NO; [self setStatus:STATUS_IN_FLIGHT]; [self playHyperspaceAborted]; ShipScriptEventNoCx(self, "playerJumpFailed", OOJSSTR("malfunction")); return; } int seconds = round(witchspaceCountdown); if (galactic_witchjump) { [UNIVERSE displayCountdownMessage:OOExpandKey(@"witch-galactic-in-x-seconds", seconds) forCount:1.0]; } else { NSString *destination = [UNIVERSE getSystemName:[self nextHopTargetSystemID]]; [UNIVERSE displayCountdownMessage:OOExpandKey(@"witch-to-x-in-y-seconds", seconds, destination) forCount:1.0]; } if (witchspaceCountdown == 0.0) { UPDATE_STAGE(@"preloading planet textures"); if (!galactic_witchjump) { /* Note: planet texture preloading is done twice for hyperspace jumps: once when starting the countdown and once at the beginning of the jump. The reason is that the preloading may have been skipped the first time because of rate limiting (see notes at -preloadPlanetTexturesForSystem:). There is no significant overhead from doing it twice thanks to the texture cache. -- Ahruman 2009-12-19 */ [UNIVERSE preloadPlanetTexturesForSystem:target_system_id]; } else { // FIXME: preload target system for galactic jump? } UPDATE_STAGE(@"JUMP!"); if (galactic_witchjump) [self enterGalacticWitchspace]; else [self enterWitchspace]; galactic_witchjump = NO; } STAGE_TRACKING_END } - (void) performWitchspaceExitUpdates:(OOTimeDelta)delta_t { if ([UNIVERSE breakPatternOver]) { [self resetExhaustPlumes]; // time to check the script! [self checkScript]; // next check in 10s [self resetScriptTimer]; // reset the in-system timer // announce arrival if ([UNIVERSE planet]) { [UNIVERSE addMessage:[NSString stringWithFormat:@" %@. ",[UNIVERSE getSystemName:system_id]] forCount:3.0]; // and reset the compass if ([self hasEquipmentItemProviding:@"EQ_ADVANCED_COMPASS"]) compassMode = COMPASS_MODE_PLANET; else compassMode = COMPASS_MODE_BASIC; } else { if ([UNIVERSE inInterstellarSpace]) [UNIVERSE addMessage:DESC(@"witch-engine-malfunction") forCount:3.0]; // if sun gone nova, print nothing } [self setStatus:STATUS_IN_FLIGHT]; // If we are exiting witchspace after a scripted misjump. then make sure it gets reset now. // Scripted misjump situations should have a lifespan of one jump only, to keep things // simple - Nikos 20090728 if ([self scriptedMisjump]) [self setScriptedMisjump:NO]; // similarly reset the misjump range to the traditional 0.5 [self setScriptedMisjumpRange:0.5]; [self doScriptEvent:OOJSID("shipExitedWitchspace")]; [self doBookkeeping:delta_t]; // arrival frame updates suppressAegisMessages=NO; } } - (void) performLaunchingUpdates:(OOTimeDelta)delta_t { if (![UNIVERSE breakPatternHide]) { flightRoll = launchRoll; // synchronise player's & launching station's spins. [self doBookkeeping:delta_t]; // don't show ghost exhaust plumes from previous docking! } if ([UNIVERSE breakPatternOver]) { // time to check the legacy scripts! [self checkScript]; // next check in 10s [self setStatus:STATUS_IN_FLIGHT]; [self setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE]; StationEntity *stationLaunchedFrom = [UNIVERSE nearestEntityMatchingPredicate:IsStationPredicate parameter:NULL relativeToEntity:self]; [self doScriptEvent:OOJSID("shipLaunchedFromStation") withArgument:stationLaunchedFrom]; } } - (void) performDockingUpdates:(OOTimeDelta)delta_t { if ([UNIVERSE breakPatternOver]) { [self docked]; // bookkeeping for docking } } - (void) performDeadUpdates:(OOTimeDelta)delta_t { if ([self shotTime] > kDeadResetTime) { BOOL was_mouse_control_on = mouse_control_on; [UNIVERSE handleGameOver]; // we restart the UNIVERSE mouse_control_on = was_mouse_control_on; } } // Target is valid if it's within Scanner range, AND // Target is a ship AND is not cloaked or jamming, OR // Target is a wormhole AND player has the Wormhole Scanner - (BOOL)isValidTarget:(Entity*)target { // Just in case we got called with a bad target. if (!target) return NO; // If target is beyond scanner range, it's lost if(target->zero_distance > SCANNER_MAX_RANGE2) return NO; // If target is a ship, check whether it's cloaked or is actively jamming our scanner if ([target isShip]) { ShipEntity *targetShip = (ShipEntity*)target; if ([targetShip isCloaked] || // checks for cloaked ships ([targetShip isJammingScanning] && ![self hasMilitaryScannerFilter])) // checks for activated jammer { return NO; } OOEntityStatus tstatus = [targetShip status]; if (tstatus == STATUS_ENTERING_WITCHSPACE || tstatus == STATUS_IN_HOLD || tstatus == STATUS_DOCKED) { // checks for ships entering wormholes, docking, or been scooped return NO; } return YES; } // If target is an unexpired wormhole and the player has bought the Wormhole Scanner and we're in ID mode if ([target isWormhole] && [target scanClass] != CLASS_NO_DRAW && [self hasEquipmentItemProviding:@"EQ_WORMHOLE_SCANNER"] && ident_engaged) { return YES; } // Target is neither a wormhole nor a ship return NO; } - (void) showGameOver { [hud resetGuis:[NSDictionary dictionaryWithObject:[NSDictionary dictionary] forKey:@"message_gui"]]; NSString *scoreMS = [NSString stringWithFormat:OOExpandKey(@"gameoverscreen-score-@"), KillCountToRatingAndKillString(ship_kills)]; [UNIVERSE displayMessage:OOExpandKey(@"gameoverscreen-game-over") forCount:kDeadResetTime]; [UNIVERSE displayMessage:@"" forCount:kDeadResetTime]; [UNIVERSE displayMessage:scoreMS forCount:kDeadResetTime]; [UNIVERSE displayMessage:@"" forCount:kDeadResetTime]; [UNIVERSE displayMessage:OOExpandKey(@"gameoverscreen-press-space") forCount:kDeadResetTime]; [UNIVERSE displayMessage:@" " forCount:kDeadResetTime]; [UNIVERSE displayMessage:@"" forCount:kDeadResetTime]; [self resetShotTime]; } - (void) showShipModelWithKey:(NSString *)shipKey shipData:(NSDictionary *)shipData personality:(uint16_t)personality factorX:(GLfloat)factorX factorY:(GLfloat)factorY factorZ:(GLfloat)factorZ inContext:(NSString *)context { if (shipKey == nil) return; if (shipData == nil) shipData = [[OOShipRegistry sharedRegistry] shipInfoForKey:shipKey]; if (shipData == nil) return; Quaternion q2 = { (GLfloat)M_SQRT1_2, (GLfloat)M_SQRT1_2, (GLfloat)0.0f, (GLfloat)0.0f }; // MKW - retrieve last demo ships' orientation and release it if( demoShip != nil ) { q2 = [demoShip orientation]; [demoShip release]; } ShipEntity *ship = [[ProxyPlayerEntity alloc] initWithKey:shipKey definition:shipData]; if (personality != ENTITY_PERSONALITY_INVALID) [ship setEntityPersonalityInt:personality]; [ship wasAddedToUniverse]; if (context) OOLog(@"script.debug.note.showShipModel", @"::::: showShipModel:'%@' in context: %@.", [ship name], context); GLfloat cr = [ship collisionRadius]; [ship setOrientation: q2]; [ship setPositionX:factorX * cr y:factorY * cr z:factorZ * cr]; [ship setScanClass: CLASS_NO_DRAW]; [ship setRoll: M_PI/10.0]; [ship setPitch: M_PI/25.0]; if([ship pendingEscortCount] > 0) [ship setPendingEscortCount:0]; [ship setAITo: @"nullAI.plist"]; id subEntStatus = [shipData objectForKey:@"subentities_status"]; // show missing subentities if there's a subentities_status key if (subEntStatus != nil) [ship deserializeShipSubEntitiesFrom:(NSString *)subEntStatus]; [UNIVERSE addEntity: ship]; // MKW - save demo ship for its rotation demoShip = [ship retain]; [ship setStatus: STATUS_COCKPIT_DISPLAY]; [ship release]; } // Game options and status screens (for now) may require an immediate window redraw after // said window has been resized. This method must be called after such resize events, including // toggle to/from full screen - Nikos 20140129 - (void) doGuiScreenResizeUpdates { switch ([self guiScreen]) { case GUI_SCREEN_GAMEOPTIONS: //refresh play windowed / full screen [self setGuiToGameOptionsScreen]; break; case GUI_SCREEN_STATUS: // status screen must be redone in order to possibly // refresh displayed model's draw position [self setGuiToStatusScreen]; break; default: break; } } // Check for lost targeting - both on the ships' main target as well as each // missile. // If we're actively scanning and we don't have a current target, then check // to see if we've locked onto a new target. // Finally, if we have a target and it's a wormhole, check whether we have more // information - (void) updateTargeting { STAGE_TRACKING_BEGIN // check for lost ident target and ensure the ident system is actually scanning UPDATE_STAGE(@"checking ident target"); if (ident_engaged && [self primaryTarget] != nil) { if (![self isValidTarget:[self primaryTarget]]) { if (!suppressTargetLost) { [UNIVERSE addMessage:DESC(@"target-lost") forCount:3.0]; [self playTargetLost]; [self noteLostTarget]; } else { suppressTargetLost = NO; } DESTROY(_primaryTarget); } } // check each unlaunched missile's target still exists and is in-range UPDATE_STAGE(@"checking missile targets"); if (missile_status != MISSILE_STATUS_SAFE) { unsigned i; for (i = 0; i < max_missiles; i++) { if ([missile_entity[i] primaryTarget] != nil && ![self isValidTarget:[missile_entity[i] primaryTarget]]) { [UNIVERSE addMessage:DESC(@"target-lost") forCount:3.0]; [self playTargetLost]; [missile_entity[i] removeTarget:nil]; if (i == activeMissile) { [self noteLostTarget]; DESTROY(_primaryTarget); missile_status = MISSILE_STATUS_ARMED; } } else if (i == activeMissile && [missile_entity[i] primaryTarget] == nil) { missile_status = MISSILE_STATUS_ARMED; } } } // if we don't have a primary target, and we're scanning, then check for a new // target to lock on to UPDATE_STAGE(@"looking for new target"); if ([self primaryTarget] == nil && (ident_engaged || missile_status != MISSILE_STATUS_SAFE) && ([self status] == STATUS_IN_FLIGHT || [self status] == STATUS_WITCHSPACE_COUNTDOWN)) { Entity *target = [UNIVERSE firstEntityTargetedByPlayer]; if ([self isValidTarget:target]) { [self addTarget:target]; } } // If our primary target is a wormhole, check to see if we have additional // information UPDATE_STAGE(@"checking for additional wormhole information"); if ([[self primaryTarget] isWormhole]) { WormholeEntity *wh = [self primaryTarget]; switch ([wh scanInfo]) { case WH_SCANINFO_NONE: OOLog(kOOLogInconsistentState, @"Internal Error - WH_SCANINFO_NONE reached in [PlayerEntity updateTargeting:]"); [self dumpState]; [wh dumpState]; // Workaround a reported hit of the assert here. We really // should work out how/why this could happen though and fix // the underlying cause. // - MKW 2011.03.11 //assert([wh scanInfo] != WH_SCANINFO_NONE); [wh setScannedAt:[self clockTimeAdjusted]]; break; case WH_SCANINFO_SCANNED: if ([self clockTimeAdjusted] > [wh scanTime] + 2) { [wh setScanInfo:WH_SCANINFO_COLLAPSE_TIME]; //[UNIVERSE addCommsMessage:[NSString stringWithFormat:DESC(@"wormhole-collapse-time-computed"), // [UNIVERSE getSystemName:[wh destination]]] forCount:5.0]; } break; case WH_SCANINFO_COLLAPSE_TIME: if([self clockTimeAdjusted] > [wh scanTime] + 4) { [wh setScanInfo:WH_SCANINFO_ARRIVAL_TIME]; [UNIVERSE addCommsMessage:[NSString stringWithFormat:DESC(@"wormhole-arrival-time-computed-@"), ClockToString([wh estimatedArrivalTime], NO)] forCount:5.0]; } break; case WH_SCANINFO_ARRIVAL_TIME: if ([self clockTimeAdjusted] > [wh scanTime] + 7) { [wh setScanInfo:WH_SCANINFO_DESTINATION]; [UNIVERSE addCommsMessage:[NSString stringWithFormat:DESC(@"wormhole-destination-computed-@"), [UNIVERSE getSystemName:[wh destination]]] forCount:5.0]; } break; case WH_SCANINFO_DESTINATION: if ([self clockTimeAdjusted] > [wh scanTime] + 10) { [wh setScanInfo:WH_SCANINFO_SHIP]; // TODO: Extract last ship from wormhole and display its name } break; case WH_SCANINFO_SHIP: break; } } STAGE_TRACKING_END } - (void) orientationChanged { quaternion_normalize(&orientation); rotMatrix = OOMatrixForQuaternionRotation(orientation); OOMatrixGetBasisVectors(rotMatrix, &v_right, &v_up, &v_forward); orientation.w = -orientation.w; playerRotMatrix = OOMatrixForQuaternionRotation(orientation); // this is the rotation similar to ordinary ships orientation.w = -orientation.w; } - (void) applyAttitudeChanges:(double) delta_t { [self applyRoll:flightRoll*delta_t andClimb:flightPitch*delta_t]; [self applyYaw:flightYaw*delta_t]; } - (void) applyRoll:(GLfloat) roll1 andClimb:(GLfloat) climb1 { if (roll1 == 0.0 && climb1 == 0.0 && hasRotated == NO) return; if (roll1) quaternion_rotate_about_z(&orientation, -roll1); if (climb1) quaternion_rotate_about_x(&orientation, -climb1); /* Bugginess may put us in a state where the orientation quat is all zeros, at which point it’s impossible to move. */ if (EXPECT_NOT(quaternion_equal(orientation, kZeroQuaternion))) { if (!quaternion_equal(lastOrientation, kZeroQuaternion)) { orientation = lastOrientation; } else { orientation = kIdentityQuaternion; } } [self orientationChanged]; } /* * This method should not be necessary, but when I replaced the above with applyRoll:andClimb:andYaw, the * ship went crazy. Perhaps applyRoll:andClimb is called from one of the subclasses and that was messing * things up. */ - (void) applyYaw:(GLfloat) yaw { quaternion_rotate_about_y(&orientation, -yaw); [self orientationChanged]; } - (OOMatrix) drawRotationMatrix // override to provide the 'correct' drawing matrix { return playerRotMatrix; } - (OOMatrix) drawTransformationMatrix { OOMatrix result = playerRotMatrix; // HPVect: modify to use camera-relative positioning return OOMatrixTranslate(result, HPVectorToVector(position)); } - (Quaternion) normalOrientation { return make_quaternion(-orientation.w, orientation.x, orientation.y, orientation.z); } - (void) setNormalOrientation:(Quaternion) quat { [self setOrientation:make_quaternion(-quat.w, quat.x, quat.y, quat.z)]; } - (void) moveForward:(double) amount { distanceTravelled += (float)amount; [self setPosition:HPvector_add(position, vectorToHPVector(vector_multiply_scalar(v_forward, (float)amount)))]; } - (HPVector) breakPatternPosition { return HPvector_add(position,vectorToHPVector(quaternion_rotate_vector(quaternion_conjugate(orientation),forwardViewOffset))); } - (Vector) viewpointOffset { // if ([UNIVERSE breakPatternHide]) // return kZeroVector; // center view for break pattern // now done by positioning break pattern correctly switch ([UNIVERSE viewDirection]) { case VIEW_FORWARD: return forwardViewOffset; case VIEW_AFT: return aftViewOffset; case VIEW_PORT: return portViewOffset; case VIEW_STARBOARD: return starboardViewOffset; /* GILES custom viewpoints */ case VIEW_CUSTOM: return customViewOffset; /* -- */ default: break; } return kZeroVector; } - (Vector) viewpointOffsetAft { return aftViewOffset; } - (Vector) viewpointOffsetForward { return forwardViewOffset; } - (Vector) viewpointOffsetPort { return portViewOffset; } - (Vector) viewpointOffsetStarboard { return starboardViewOffset; } /* TODO post 1.78: profiling suggests this gets called often enough * that it's worth caching the result per-frame - CIM */ - (HPVector) viewpointPosition { HPVector viewpoint = position; if (showDemoShips) { viewpoint = kZeroHPVector; } Vector offset = [self viewpointOffset]; // FIXME: this ought to be done with matrix or quaternion functions. OOMatrix r = rotMatrix; viewpoint.x += offset.x * r.m[0][0]; viewpoint.y += offset.x * r.m[1][0]; viewpoint.z += offset.x * r.m[2][0]; viewpoint.x += offset.y * r.m[0][1]; viewpoint.y += offset.y * r.m[1][1]; viewpoint.z += offset.y * r.m[2][1]; viewpoint.x += offset.z * r.m[0][2]; viewpoint.y += offset.z * r.m[1][2]; viewpoint.z += offset.z * r.m[2][2]; return viewpoint; } - (void) drawImmediate:(bool)immediate translucent:(bool)translucent { switch ([self status]) { case STATUS_DEAD: case STATUS_COCKPIT_DISPLAY: case STATUS_DOCKED: case STATUS_START_GAME: return; default: if ([UNIVERSE breakPatternHide]) return; } [super drawImmediate:immediate translucent:translucent]; } - (BOOL) massLocked { return ((alertFlags & ALERT_FLAG_MASS_LOCK) != 0); } - (BOOL) atHyperspeed { return travelling_at_hyperspeed; } - (float) occlusionLevel { return occlusion_dial; } - (void) setOcclusionLevel:(float)level { occlusion_dial = level; } - (void) setDockedAtMainStation { [self setDockedStation:[UNIVERSE station]]; if (_dockedStation != nil) [self setStatus:STATUS_DOCKED]; } - (StationEntity *) dockedStation { return [_dockedStation weakRefUnderlyingObject]; } - (void) setDockedStation:(StationEntity *)station { [_dockedStation release]; _dockedStation = [station weakRetain]; } - (void) setTargetDockStationTo:(StationEntity *) value { targetDockStation = value; } - (StationEntity *) getTargetDockStation { return targetDockStation; } - (HeadUpDisplay *) hud { return hud; } - (void) resetHud { // set up defauld HUD for the ship NSDictionary *shipDict = [[OOShipRegistry sharedRegistry] shipInfoForKey:[self shipDataKey]]; NSString *hud_desc = [shipDict oo_stringForKey:@"hud" defaultValue:@"hud.plist"]; if (![self switchHudTo:hud_desc]) [self switchHudTo:@"hud.plist"]; // ensure we have a HUD to fall back to } - (BOOL) switchHudTo:(NSString *)hudFileName { NSDictionary *hudDict = nil; BOOL wasHidden = NO; BOOL wasCompassActive = YES; double scannerZoom = 1.0; NSUInteger i; if (!hudFileName) return NO; // is the HUD in the process of being rendered? If yes, set it to defer state and abort the switching now if (hud != nil && [hud isUpdating]) { [hud setDeferredHudName:hudFileName]; return NO; } hudDict = [ResourceManager dictionaryFromFilesNamed:hudFileName inFolder:@"Config" andMerge:YES]; // hud defined, but buggy? if (hudDict == nil) { OOLog(@"PlayerEntity.switchHudTo.failed", @"HUD dictionary file %@ to switch to not found or invalid.", hudFileName); return NO; } if (hud != nil) { // remember these values wasHidden = [hud isHidden]; wasCompassActive = [hud isCompassActive]; scannerZoom = [hud scannerZoom]; } // buggy oxp could override hud.plist with a non-dictionary. if (hudDict != nil) { [hud setHidden:YES]; // hide the hud while rebuilding it. DESTROY(hud); hud = [[HeadUpDisplay alloc] initWithDictionary:hudDict inFile:hudFileName]; [hud resetGuis:hudDict]; // reset zoom & hidden to what they were before the swich [hud setScannerZoom:scannerZoom]; [hud setCompassActive:wasCompassActive]; [hud setHidden:wasHidden]; activeMFD = 0; NSArray *savedMFDs = [NSArray arrayWithArray:multiFunctionDisplaySettings]; [multiFunctionDisplaySettings removeAllObjects]; for (i = 0; i < [hud mfdCount] ; i++) { if ([savedMFDs count] > i) { [multiFunctionDisplaySettings addObject:[savedMFDs objectAtIndex:i]]; } else { [multiFunctionDisplaySettings addObject:[NSNull null]]; } } } return YES; } - (float) dialCustomFloat:(NSString *)dialKey { return [customDialSettings oo_floatForKey:dialKey defaultValue:0.0]; } - (NSString *) dialCustomString:(NSString *)dialKey { return [customDialSettings oo_stringForKey:dialKey defaultValue:@""]; } - (OOColor *) dialCustomColor:(NSString *)dialKey { return [OOColor colorWithDescription:[customDialSettings objectForKey:dialKey]]; } - (void) setDialCustom:(id)value forKey:(NSString *)dialKey { [customDialSettings setObject:value forKey:dialKey]; } - (void) setShowDemoShips:(BOOL)value { showDemoShips = value; } - (BOOL) showDemoShips { return showDemoShips; } - (float) maxForwardShieldLevel { return max_forward_shield; } - (float) maxAftShieldLevel { return max_aft_shield; } - (float) forwardShieldRechargeRate { return forward_shield_recharge_rate; } - (float) aftShieldRechargeRate { return aft_shield_recharge_rate; } - (void) setMaxForwardShieldLevel:(float)new { max_forward_shield = new; } - (void) setMaxAftShieldLevel:(float)new { max_aft_shield = new; } - (void) setForwardShieldRechargeRate:(float)new { forward_shield_recharge_rate = new; } - (void) setAftShieldRechargeRate:(float)new { aft_shield_recharge_rate = new; } - (GLfloat) forwardShieldLevel { return forward_shield; } - (GLfloat) aftShieldLevel { return aft_shield; } - (void) setForwardShieldLevel:(GLfloat)level { forward_shield = OOClamp_0_max_f(level, [self maxForwardShieldLevel]); } - (void) setAftShieldLevel:(GLfloat)level { aft_shield = OOClamp_0_max_f(level, [self maxAftShieldLevel]); } - (NSDictionary *) keyConfig { return keyconfig_settings; } - (BOOL) isMouseControlOn { return mouse_control_on; } - (GLfloat) dialRoll { GLfloat result = flightRoll / max_flight_roll; if ((result < 1.0f)&&(result > -1.0f)) return result; if (result > 0.0f) return 1.0f; return -1.0f; } - (GLfloat) dialPitch { GLfloat result = flightPitch / max_flight_pitch; if ((result < 1.0f)&&(result > -1.0f)) return result; if (result > 0.0f) return 1.0f; return -1.0f; } - (GLfloat) dialYaw { GLfloat result = -flightYaw / max_flight_yaw; if ((result < 1.0f)&&(result > -1.0f)) return result; if (result > 0.0f) return 1.0f; return -1.0f; } - (GLfloat) dialSpeed { GLfloat result = flightSpeed / maxFlightSpeed; return OOClamp_0_1_f(result); } - (GLfloat) dialHyperSpeed { return flightSpeed / maxFlightSpeed; } - (GLfloat) dialForwardShield { if (EXPECT_NOT([self maxForwardShieldLevel] <= 0)) { return 0.0; } GLfloat result = forward_shield / [self maxForwardShieldLevel]; return OOClamp_0_1_f(result); } - (GLfloat) dialAftShield { if (EXPECT_NOT([self maxAftShieldLevel] <= 0)) { return 0.0; } GLfloat result = aft_shield / [self maxAftShieldLevel]; return OOClamp_0_1_f(result); } - (GLfloat) dialEnergy { GLfloat result = energy / maxEnergy; return OOClamp_0_1_f(result); } - (GLfloat) dialMaxEnergy { return maxEnergy; } - (GLfloat) dialFuel { if (fuel <= 0.0f) return 0.0f; if (fuel > [self fuelCapacity]) return 1.0f; return (GLfloat)fuel / (GLfloat)[self fuelCapacity]; } - (GLfloat) dialHyperRange { if (target_system_id == system_id && ![UNIVERSE inInterstellarSpace]) return 0.0f; return [self fuelRequiredForJump] / (GLfloat)PLAYER_MAX_FUEL; } - (GLfloat) laserHeatLevel { GLfloat result = (GLfloat)weapon_temp / (GLfloat)PLAYER_MAX_WEAPON_TEMP; return OOClamp_0_1_f(result); } - (GLfloat)laserHeatLevelAft { GLfloat result = aft_weapon_temp / (GLfloat)PLAYER_MAX_WEAPON_TEMP; return OOClamp_0_1_f(result); } - (GLfloat)laserHeatLevelForward { GLfloat result = forward_weapon_temp / (GLfloat)PLAYER_MAX_WEAPON_TEMP; // no need to check subents here return OOClamp_0_1_f(result); } - (GLfloat)laserHeatLevelPort { GLfloat result = port_weapon_temp / PLAYER_MAX_WEAPON_TEMP; return OOClamp_0_1_f(result); } - (GLfloat)laserHeatLevelStarboard { GLfloat result = starboard_weapon_temp / PLAYER_MAX_WEAPON_TEMP; return OOClamp_0_1_f(result); } - (GLfloat) dialAltitude { if ([self isDocked]) return 0.0f; // find nearest planet type entity... assert(UNIVERSE != nil); Entity *nearestPlanet = [self findNearestStellarBody]; if (nearestPlanet == nil) return 1.0f; GLfloat zd = nearestPlanet->zero_distance; GLfloat cr = nearestPlanet->collision_radius; GLfloat alt = sqrt(zd) - cr; return OOClamp_0_1_f(alt / (GLfloat)PLAYER_DIAL_MAX_ALTITUDE); } - (double) clockTime { return ship_clock; } - (double) clockTimeAdjusted { return ship_clock + ship_clock_adjust; } - (BOOL) clockAdjusting { return ship_clock_adjust > 0; } - (void) addToAdjustTime:(double)seconds { ship_clock_adjust += seconds; } - (NSString *) dial_clock { return ClockToString(ship_clock, ship_clock_adjust > 0); } - (NSString *) dial_clock_adjusted { return ClockToString(ship_clock + ship_clock_adjust, NO); } - (NSString *) dial_fpsinfo { unsigned fpsVal = fps_counter; return [NSString stringWithFormat:@"FPS: %3d", fpsVal]; } - (NSString *) dial_objinfo { NSString *result = [NSString stringWithFormat:@"Entities: %3ld", [UNIVERSE entityCount]]; #ifndef NDEBUG result = [NSString stringWithFormat:@"%@ (%d, %zu KiB, avg %lu bytes)", result, gLiveEntityCount, gTotalEntityMemory >> 10, gTotalEntityMemory / gLiveEntityCount]; #endif return result; } - (unsigned) countMissiles { unsigned n_missiles = 0; unsigned i; for (i = 0; i < max_missiles; i++) { if (missile_entity[i]) n_missiles++; } return n_missiles; } - (OOMissileStatus) dialMissileStatus { if ([self weaponsOnline]) { return missile_status; } else { // Invariant/safety interlock: weapons offline implies missiles safe. -- Ahruman 2012-07-21 if (missile_status != MISSILE_STATUS_SAFE) { OOLogERR(@"player.missilesUnsafe", @"Missile state is not SAFE when weapons are offline. This is a bug, please report it."); [self safeAllMissiles]; } return MISSILE_STATUS_SAFE; } } - (BOOL) canScoop:(ShipEntity *)other { if (specialCargo) return NO; return [super canScoop:other]; } - (OOFuelScoopStatus) dialFuelScoopStatus { if ([self hasScoop]) { if (scoopsActive) return SCOOP_STATUS_ACTIVE; if ([cargo count] >= [self maxAvailableCargoSpace] || specialCargo) return SCOOP_STATUS_FULL_HOLD; return SCOOP_STATUS_OKAY; } else { return SCOOP_STATUS_NOT_INSTALLED; } } - (float) fuelLeakRate { return fuel_leak_rate; } - (void) setFuelLeakRate:(float)value { fuel_leak_rate = fmax(value, 0.0f); } - (NSMutableArray *) commLog { assert(kCommLogTrimSize < kCommLogTrimThreshold); if (commLog != nil) { NSUInteger count = [commLog count]; if (count >= kCommLogTrimThreshold) { [commLog removeObjectsInRange:NSMakeRange(0, count - kCommLogTrimSize)]; } } else { commLog = [[NSMutableArray alloc] init]; } return commLog; } - (NSMutableArray *) roleWeights { return roleWeights; } - (void) addRoleForAggression:(ShipEntity *)victim { if ([victim isExplicitlyUnpiloted] || [victim isHulk] || [victim hasHostileTarget] || [[victim primaryAggressor] isPlayer]) { return; } NSString *role = nil; if ([[victim primaryRole] isEqualToString:@"escape-capsule"]) { role = @"assassin-player"; } else if ([victim bounty] > 0) { role = @"hunter"; } else if ([victim isPirateVictim]) { role = @"pirate"; } else if ([UNIVERSE role:[self primaryRole] isInCategory:@"oolite-hunter"] || [victim scanClass] == CLASS_POLICE) { role = @"pirate-interceptor"; } if (role == nil) { return; } NSUInteger times = [roleWeightFlags oo_intForKey:role defaultValue:0]; times++; [roleWeightFlags setObject:[NSNumber numberWithInt:times] forKey:role]; if ((times & (times-1)) == 0) // is power of 2 { [self addRoleToPlayer:role]; } } - (void) addRoleForMining { NSString *role = @"miner"; NSUInteger times = [roleWeightFlags oo_intForKey:role defaultValue:0]; times++; [roleWeightFlags setObject:[NSNumber numberWithInt:times] forKey:role]; if ((times & (times-1)) == 0) // is power of 2 { [self addRoleToPlayer:role]; } } - (void) addRoleToPlayer:(NSString *)role { NSUInteger slot = Ranrot() & ([self maxPlayerRoles]-1); [self addRoleToPlayer:role inSlot:slot]; } - (void) addRoleToPlayer:(NSString *)role inSlot:(NSUInteger)slot { if (slot >= [self maxPlayerRoles]) { slot = [self maxPlayerRoles]-1; } if (slot >= [roleWeights count]) { [roleWeights addObject:role]; } else { [roleWeights replaceObjectAtIndex:slot withObject:role]; } } - (void) clearRoleFromPlayer:(BOOL)includingLongRange { NSUInteger slot = Ranrot() % [roleWeights count]; if (!includingLongRange) { NSString *role = [roleWeights objectAtIndex:slot]; // long range roles cleared at 1/2 normal rate if ([role hasSuffix:@"+"] && randf() > 0.5) { return; } } [roleWeights replaceObjectAtIndex:slot withObject:@"player-unknown"]; } - (void) clearRolesFromPlayer:(float)chance { NSUInteger i, count=[roleWeights count]; for (i = 0; i < count; i++) { if (randf() < chance) { [roleWeights replaceObjectAtIndex:i withObject:@"player-unknown"]; } } } - (NSUInteger) maxPlayerRoles { if (ship_kills >= 6400) { return 32; } else if (ship_kills >= 128) { return 16; } else { return 8; } } - (void) updateSystemMemory { OOSystemID sys = [self currentSystemID]; if (sys < 0) { return; } NSUInteger memory = 4; if (ship_kills >= 6400) { memory = 32; } else if (ship_kills >= 256) { memory = 16; } else if (ship_kills >= 64) { memory = 8; } if ([roleSystemList count] >= memory) { [roleSystemList removeObjectAtIndex:0]; } [roleSystemList addObject:[NSNumber numberWithInt:sys]]; } - (Entity *) compassTarget { Entity *result = [compassTarget weakRefUnderlyingObject]; if (result == nil) { DESTROY(compassTarget); return nil; } return result; } - (void) setCompassTarget:(Entity *)value { [compassTarget release]; compassTarget = [value weakRetain]; } - (void) validateCompassTarget { OOSunEntity *the_sun = [UNIVERSE sun]; OOPlanetEntity *the_planet = [UNIVERSE planet]; StationEntity *the_station = [UNIVERSE station]; Entity *the_target = [self primaryTarget]; Entity *beacon = [self nextBeacon]; if ([self isInSpace] && the_sun && the_planet // be in a system && ![the_sun goneNova]) // and the system has not been novabombed { Entity *new_target = nil; OOAegisStatus aegis = [self checkForAegis]; switch ([self compassMode]) { case COMPASS_MODE_INACTIVE: break; case COMPASS_MODE_BASIC: if ((aegis == AEGIS_CLOSE_TO_MAIN_PLANET || aegis == AEGIS_IN_DOCKING_RANGE) && the_station) { new_target = the_station; } else { new_target = the_planet; } break; case COMPASS_MODE_PLANET: new_target = the_planet; break; case COMPASS_MODE_STATION: new_target = the_station; break; case COMPASS_MODE_SUN: new_target = the_sun; break; case COMPASS_MODE_TARGET: new_target = the_target; break; case COMPASS_MODE_BEACONS: new_target = beacon; break; } if (new_target == nil || [new_target status] < STATUS_ACTIVE || [new_target status] == STATUS_IN_HOLD) { [self setCompassMode:COMPASS_MODE_PLANET]; new_target = the_planet; } if (EXPECT_NOT(new_target != [self compassTarget])) { [self setCompassTarget:new_target]; [self doScriptEvent:OOJSID("compassTargetChanged") withArguments:[NSArray arrayWithObjects:new_target, OOStringFromCompassMode([self compassMode]), nil]]; } } } - (NSString *) compassTargetLabel { switch (compassMode) { case COMPASS_MODE_INACTIVE: return @""; case COMPASS_MODE_BASIC: return @""; case COMPASS_MODE_BEACONS: { Entity *target = [self compassTarget]; if (target) { return [(Entity *)target beaconLabel]; } return @""; } case COMPASS_MODE_PLANET: return [[UNIVERSE planet] name]; case COMPASS_MODE_SUN: return [[UNIVERSE sun] name]; case COMPASS_MODE_STATION: return [[UNIVERSE station] displayName]; case COMPASS_MODE_TARGET: return DESC(@"oolite-beacon-label-target"); } return @""; } - (OOCompassMode) compassMode { return compassMode; } - (void) setCompassMode:(OOCompassMode) value { compassMode = value; } - (void) setPrevCompassMode { OOAegisStatus aegis = AEGIS_NONE; Entity *beacon = nil; switch (compassMode) { case COMPASS_MODE_INACTIVE: case COMPASS_MODE_BASIC: case COMPASS_MODE_PLANET: beacon = [UNIVERSE lastBeacon]; while (beacon != nil && [beacon isJammingScanning]) { beacon = [beacon prevBeacon]; } [self setNextBeacon:beacon]; if (beacon != nil) { [self setCompassMode:COMPASS_MODE_BEACONS]; break; } // else fall through to switch to target mode. case COMPASS_MODE_BEACONS: beacon = [self nextBeacon]; do { beacon = [beacon prevBeacon]; } while (beacon != nil && [beacon isJammingScanning]); [self setNextBeacon:beacon]; if (beacon == nil) { if ([self primaryTarget]) { [self setCompassMode:COMPASS_MODE_TARGET]; } else { [self setCompassMode:COMPASS_MODE_SUN]; } break; } break; case COMPASS_MODE_TARGET: [self setCompassMode:COMPASS_MODE_SUN]; break; case COMPASS_MODE_SUN: aegis = [self checkForAegis]; if (aegis == AEGIS_CLOSE_TO_MAIN_PLANET || aegis == AEGIS_IN_DOCKING_RANGE) { [self setCompassMode:COMPASS_MODE_STATION]; } else { [self setCompassMode:COMPASS_MODE_PLANET]; } break; case COMPASS_MODE_STATION: [self setCompassMode:COMPASS_MODE_PLANET]; break; } } - (void) setNextCompassMode { OOAegisStatus aegis = AEGIS_NONE; Entity *beacon = nil; switch (compassMode) { case COMPASS_MODE_INACTIVE: case COMPASS_MODE_BASIC: case COMPASS_MODE_PLANET: aegis = [self checkForAegis]; if ([UNIVERSE station] && (aegis == AEGIS_CLOSE_TO_MAIN_PLANET || aegis == AEGIS_IN_DOCKING_RANGE)) { [self setCompassMode:COMPASS_MODE_STATION]; } else { [self setCompassMode:COMPASS_MODE_SUN]; } break; case COMPASS_MODE_STATION: [self setCompassMode:COMPASS_MODE_SUN]; break; case COMPASS_MODE_SUN: if ([self primaryTarget]) { [self setCompassMode:COMPASS_MODE_TARGET]; break; } // else fall through to switch to beacon mode. case COMPASS_MODE_TARGET: beacon = [UNIVERSE firstBeacon]; while (beacon != nil && [beacon isJammingScanning]) { beacon = [beacon nextBeacon]; } [self setNextBeacon:beacon]; if (beacon != nil) [self setCompassMode:COMPASS_MODE_BEACONS]; else [self setCompassMode:COMPASS_MODE_PLANET]; break; case COMPASS_MODE_BEACONS: beacon = [self nextBeacon]; do { beacon = [beacon nextBeacon]; } while (beacon != nil && [beacon isJammingScanning]); [self setNextBeacon:beacon]; if (beacon == nil) { [self setCompassMode:COMPASS_MODE_PLANET]; } break; } } - (NSUInteger) activeMissile { return activeMissile; } - (void) setActiveMissile:(NSUInteger)value { activeMissile = value; } - (NSUInteger) dialMaxMissiles { return max_missiles; } - (BOOL) dialIdentEngaged { return ident_engaged; } - (void) setDialIdentEngaged:(BOOL)newValue { ident_engaged = !!newValue; } - (NSString *) specialCargo { return specialCargo; } - (NSString *) dialTargetName { Entity *target_entity = [self primaryTarget]; NSString *result = nil; if (target_entity == nil) { result = DESC(@"no-target-string"); } if ([target_entity respondsToSelector:@selector(identFromShip:)]) { result = [(ShipEntity*)target_entity identFromShip:self]; } if (result == nil) result = DESC(@"unknown-target"); return result; } - (NSArray *) multiFunctionDisplayList { return multiFunctionDisplaySettings; } - (NSString *) multiFunctionText:(NSUInteger)i { NSString *key = [multiFunctionDisplaySettings oo_stringAtIndex:i defaultValue:nil]; if (key == nil) { return nil; } NSString *text = [multiFunctionDisplayText oo_stringForKey:key defaultValue:nil]; return text; } - (void) setMultiFunctionText:(NSString *)text forKey:(NSString *)key { if (text != nil) { [multiFunctionDisplayText setObject:text forKey:key]; } else if (key != nil) { [multiFunctionDisplayText removeObjectForKey:key]; // and blank any MFDs currently using it NSUInteger index; while ((index = [multiFunctionDisplaySettings indexOfObject:key]) != NSNotFound) { [multiFunctionDisplaySettings replaceObjectAtIndex:index withObject:[NSNull null]]; } } } - (BOOL) setMultiFunctionDisplay:(NSUInteger)index toKey:(NSString *)key { if (index >= [hud mfdCount]) { // is first inactive display index = [multiFunctionDisplaySettings indexOfObject:[NSNull null]]; if (index == NSNotFound) { return NO; } } if (index < [hud mfdCount]) { if (key == nil) { [multiFunctionDisplaySettings replaceObjectAtIndex:index withObject:[NSNull null]]; } else { [multiFunctionDisplaySettings replaceObjectAtIndex:index withObject:key]; } return YES; } else { return NO; } } - (void) cycleMultiFunctionDisplay:(NSUInteger) index { NSArray *keys = [multiFunctionDisplayText allKeys]; if ([keys count] == 0) { [self setMultiFunctionDisplay:index toKey:nil]; return; } id current = [multiFunctionDisplaySettings objectAtIndex:index]; if (current == [NSNull null]) { [self setMultiFunctionDisplay:index toKey:[keys objectAtIndex:0]]; } else { NSUInteger cIndex = [keys indexOfObject:current]; if (cIndex == NSNotFound || cIndex + 1 >= [keys count]) { [self setMultiFunctionDisplay:index toKey:nil]; } else { [self setMultiFunctionDisplay:index toKey:[keys objectAtIndex:(cIndex+1)]]; } } } - (void) selectNextMultiFunctionDisplay { activeMFD = (activeMFD + 1) % [[self hud] mfdCount]; NSUInteger mfdID = activeMFD + 1; [UNIVERSE addMessage:OOExpandKey(@"mfd-N-selected", mfdID) forCount:3.0 ]; } - (NSUInteger) activeMFD { return activeMFD; } - (ShipEntity *) missileForPylon:(NSUInteger)value { if (value < max_missiles) return missile_entity[value]; return nil; } - (void) safeAllMissiles { // sets all missile targets to NO_TARGET unsigned i; for (i = 0; i < max_missiles; i++) { if (missile_entity[i] && [missile_entity[i] primaryTarget] != nil) [missile_entity[i] removeTarget:nil]; } missile_status = MISSILE_STATUS_SAFE; } - (void) tidyMissilePylons { // Make sure there's no gaps between missiles, synchronise missile_entity & missile_list. int i, pylon = 0; OOLog(@"missile.tidying.debug",@"Tidying fitted %d of possible %d missiles",missiles,PLAYER_MAX_MISSILES); for(i = 0; i < PLAYER_MAX_MISSILES; i++) { OOLog(@"missile.tidying.debug",@"%d %@ %@",i,missile_entity[i],missile_list[i]); if(missile_entity[i] != nil) { missile_entity[pylon] = missile_entity[i]; missile_list[pylon] = [OOEquipmentType equipmentTypeWithIdentifier:[missile_entity[i] primaryRole]]; pylon++; } } // Now clean up the remainder of the pylons. for(i = pylon; i < PLAYER_MAX_MISSILES; i++) { missile_entity[i] = nil; // not strictly needed, but helps clear things up missile_list[i] = nil; } } - (void) selectNextMissile { if (![self weaponsOnline]) return; unsigned i; for (i = 1; i < max_missiles; i++) { int next_missile = (activeMissile + i) % max_missiles; if (missile_entity[next_missile]) { // If we don't have the multi-targeting module installed, clear the active missiles' target if( ![self hasEquipmentItemProviding:@"EQ_MULTI_TARGET"] && [missile_entity[activeMissile] isMissile] ) { [missile_entity[activeMissile] removeTarget:nil]; } // Set next missile to active [self setActiveMissile:next_missile]; if (missile_status != MISSILE_STATUS_SAFE) { missile_status = MISSILE_STATUS_ARMED; // If the newly active pylon contains a missile then work out its target, if any if( [missile_entity[activeMissile] isMissile] ) { if( [self hasEquipmentItemProviding:@"EQ_MULTI_TARGET"] && ([missile_entity[next_missile] primaryTarget] != nil)) { // copy the missile's target [self addTarget:[missile_entity[next_missile] primaryTarget]]; missile_status = MISSILE_STATUS_TARGET_LOCKED; } else if ([self primaryTarget] != nil) { // never inherit target if we have EQ_MULTI_TARGET installed! [ Bug #16221 : Targeting enhancement regression ] /* CIM: seems okay to do this when launching a * missile to stop multi-target being a bit * irritating in a fight - 20/8/2014 */ if([self hasEquipmentItemProviding:@"EQ_MULTI_TARGET"] && !launchingMissile) { [self noteLostTarget]; DESTROY(_primaryTarget); } else { [missile_entity[activeMissile] addTarget:[self primaryTarget]]; missile_status = MISSILE_STATUS_TARGET_LOCKED; } } } } return; } } } - (void) clearAlertFlags { alertFlags = 0; } - (int) alertFlags { return alertFlags; } - (void) setAlertFlag:(int)flag to:(BOOL)value { if (value) { alertFlags |= flag; } else { int comp = ~flag; alertFlags &= comp; } } // used by Javascript and the distinction is important for NPCs - (OOAlertCondition) realAlertCondition { return [self alertCondition]; } - (OOAlertCondition) alertCondition { OOAlertCondition old_alert_condition = alertCondition; alertCondition = ALERT_CONDITION_GREEN; [self setAlertFlag:ALERT_FLAG_DOCKED to:[self status] == STATUS_DOCKED]; if (alertFlags & ALERT_FLAG_DOCKED) { alertCondition = ALERT_CONDITION_DOCKED; } else { if (alertFlags != 0) { alertCondition = ALERT_CONDITION_YELLOW; } if (alertFlags > ALERT_FLAG_YELLOW_LIMIT) { alertCondition = ALERT_CONDITION_RED; } } if ((alertCondition == ALERT_CONDITION_RED)&&(old_alert_condition < ALERT_CONDITION_RED)) { [self playAlertConditionRed]; } return alertCondition; } - (OOPlayerFleeingStatus) fleeingStatus { return fleeing_status; } ///////////////////////////////////////////////////////////////////// - (void) interpretAIMessage:(NSString *)ms { if ([ms isEqual:@"HOLD_FULL"]) { [self playHoldFull]; [UNIVERSE addMessage:DESC(@"hold-full") forCount:4.5]; } if ([ms isEqual:@"INCOMING_MISSILE"]) { if ([self primaryAggressor] != nil) { [self playIncomingMissile:HPVectorToVector([[self primaryAggressor] position])]; } else { [self playIncomingMissile:kZeroVector]; } [UNIVERSE addMessage:DESC(@"incoming-missile") forCount:4.5]; } if ([ms isEqual:@"ENERGY_LOW"]) { [UNIVERSE addMessage:DESC(@"energy-low") forCount:6.0]; } if ([ms isEqual:@"ECM"] && ![self isDocked]) [self playHitByECMSound]; if ([ms isEqual:@"DOCKING_REFUSED"] && [self status] == STATUS_AUTOPILOT_ENGAGED) { [self playDockingDenied]; [UNIVERSE addMessage:DESC(@"autopilot-denied") forCount:4.5]; autopilot_engaged = NO; [self resetAutopilotAI]; DESTROY(_primaryTarget); [self setStatus:STATUS_IN_FLIGHT]; [[OOMusicController sharedController] stopDockingMusic]; [self doScriptEvent:OOJSID("playerDockingRefused")]; } // aegis messages to advanced compass so in planet mode it behaves like the old compass if (compassMode != COMPASS_MODE_BASIC) { if ([ms isEqual:@"AEGIS_CLOSE_TO_MAIN_PLANET"]&&(compassMode == COMPASS_MODE_PLANET)) { [self playAegisCloseToPlanet]; [self setCompassMode:COMPASS_MODE_STATION]; } if ([ms isEqual:@"AEGIS_IN_DOCKING_RANGE"]&&(compassMode == COMPASS_MODE_PLANET)) { [self playAegisCloseToStation]; [self setCompassMode:COMPASS_MODE_STATION]; } if ([ms isEqual:@"AEGIS_NONE"]&&(compassMode == COMPASS_MODE_STATION)) { [self setCompassMode:COMPASS_MODE_PLANET]; } } } - (BOOL) mountMissile:(ShipEntity *)missile { if (missile == nil) return NO; unsigned i; for (i = 0; i < max_missiles; i++) { if (missile_entity[i] == nil) { missile_entity[i] = [missile retain]; missile_list[missiles] = [OOEquipmentType equipmentTypeWithIdentifier:[missile primaryRole]]; missiles++; if (missiles == 1) [self setActiveMissile:0]; // auto select the first purchased missile return YES; } } return NO; } - (BOOL) mountMissileWithRole:(NSString *)role { if ([self missileCount] >= [self missileCapacity]) return NO; return [self mountMissile:[[UNIVERSE newShipWithRole:role] autorelease]]; } - (ShipEntity *) fireMissile { ShipEntity *missile = missile_entity[activeMissile]; // retain count is 1 NSString *identifier = [missile primaryRole]; ShipEntity *firedMissile = nil; if (missile == nil) return nil; if (![self weaponsOnline]) return nil; // check if we were cloaked before firing the missile - can't use // cloaking_device_active directly because fireMissilewithIdentifier: andTarget: // will reset it in case passive cloak is set - Nikos 20130313 BOOL cloakedPriorToFiring = cloaking_device_active; launchingMissile = YES; replacingMissile = NO; if ([missile isMine] && (missile_status != MISSILE_STATUS_SAFE)) { firedMissile = [self launchMine:missile]; if (!replacingMissile) [self removeFromPylon:activeMissile]; if (firedMissile != nil) [self playMineLaunched:[self missileLaunchPosition]]; } else { if (missile_status != MISSILE_STATUS_TARGET_LOCKED) return nil; // release this before creating it anew in fireMissileWithIdentifier firedMissile = [self fireMissileWithIdentifier:identifier andTarget:[missile primaryTarget]]; if (firedMissile != nil) { if (!replacingMissile) [self removeFromPylon:activeMissile]; [self playMissileLaunched:[self missileLaunchPosition]]; } } if (cloakedPriorToFiring && cloakPassive) { // fireMissilewithIdentifier: andTarget: has already taken care of deactivating // the cloak in the case of missiles by the time we get here, but explicitly // calling deactivateCloakingDevice is needed in order to be covered fully with mines too [self deactivateCloakingDevice]; } replacingMissile = NO; launchingMissile = NO; return firedMissile; } - (ShipEntity *) launchMine:(ShipEntity*) mine { if (!mine) return nil; if (![self weaponsOnline]) return nil; [mine setOwner: self]; [mine setBehaviour: BEHAVIOUR_IDLE]; [self dumpItem: mine]; // includes UNIVERSE addEntity: CLASS_CARGO, STATUS_IN_FLIGHT, AI state GLOBAL ( the last one starts the timer !) [mine setScanClass: CLASS_MINE]; float mine_speed = 500.0f; Vector mvel = vector_subtract([mine velocity], vector_multiply_scalar(v_forward, mine_speed)); [mine setVelocity: mvel]; [self doScriptEvent:OOJSID("shipReleasedEquipment") withArgument:mine]; return mine; } - (BOOL) assignToActivePylon:(NSString *)equipmentKey { if (!launchingMissile) return NO; OOEquipmentType *eqType = nil; if ([equipmentKey hasSuffix:@"_DAMAGED"]) { return NO; } else { eqType = [OOEquipmentType equipmentTypeWithIdentifier:equipmentKey]; } // missiles with techlevel above 99 (kOOVariableTechLevel) are never available to the player if (![eqType isMissileOrMine] || [eqType effectiveTechLevel] > kOOVariableTechLevel) { return NO; } ShipEntity *amiss = [UNIVERSE newShipWithRole:equipmentKey]; if (!amiss) return NO; // replace the missile now. [missile_entity[activeMissile] release]; missile_entity[activeMissile] = amiss; missile_list[activeMissile] = eqType; // make sure the new missile is properly activated. if (activeMissile > 0) activeMissile--; else activeMissile = max_missiles - 1; [self selectNextMissile]; replacingMissile = YES; return YES; } - (BOOL) activateCloakingDevice { if (![self hasCloakingDevice]) return NO; if ([super activateCloakingDevice]) { [UNIVERSE addMessage:DESC(@"cloak-on") forCount:2]; [self playCloakingDeviceOn]; return YES; } else { [UNIVERSE addMessage:DESC(@"cloak-low-juice") forCount:3]; [self playCloakingDeviceInsufficientEnergy]; return NO; } } - (void) deactivateCloakingDevice { if (![self hasCloakingDevice]) return; [super deactivateCloakingDevice]; [UNIVERSE addMessage:DESC(@"cloak-off") forCount:2]; [self playCloakingDeviceOff]; } /* Scanner fuzziness is entirely cosmetic - it doesn't affect the * player's actual target locks */ - (double) scannerFuzziness { double fuzz = 0.0; /* Fuzziness from ECM bursts */ double since = [UNIVERSE getTime] - last_ecm_time; if (since < SCANNER_ECM_FUZZINESS) { fuzz += (SCANNER_ECM_FUZZINESS - since) * (SCANNER_ECM_FUZZINESS - since) * 500.0; } /* Other causes could go here */ return fuzz; } - (void) noticeECM { last_ecm_time = [UNIVERSE getTime]; } - (BOOL) fireECM { if ([super fireECM]) { ecm_in_operation = YES; ecm_start_time = [UNIVERSE getTime]; return YES; } else { return NO; } } - (OOEnergyUnitType) installedEnergyUnitType { if ([self hasEquipmentItem:@"EQ_NAVAL_ENERGY_UNIT"]) return ENERGY_UNIT_NAVAL; if ([self hasEquipmentItem:@"EQ_ENERGY_UNIT"]) return ENERGY_UNIT_NORMAL; return ENERGY_UNIT_NONE; } - (OOEnergyUnitType) energyUnitType { if ([self hasEquipmentItem:@"EQ_NAVAL_ENERGY_UNIT"]) return ENERGY_UNIT_NAVAL; if ([self hasEquipmentItem:@"EQ_ENERGY_UNIT"]) return ENERGY_UNIT_NORMAL; if ([self hasEquipmentItem:@"EQ_NAVAL_ENERGY_UNIT_DAMAGED"]) return ENERGY_UNIT_NAVAL_DAMAGED; if ([self hasEquipmentItem:@"EQ_ENERGY_UNIT_DAMAGED"]) return ENERGY_UNIT_NORMAL_DAMAGED; return ENERGY_UNIT_NONE; } - (void) currentWeaponStats { OOWeaponType currentWeapon = [self currentWeapon]; // Did find & correct a minor mismatch between player and NPC weapon stats. This is the resulting code - Kaks 20101027 // Basic stats: weapon_damage & weaponRange (weapon_recharge_rate is not used by the player) [self setWeaponDataFromType:currentWeapon]; } - (BOOL) weaponsOnline { return weapons_online; } - (void) setWeaponsOnline:(BOOL)newValue { weapons_online = !!newValue; if (!weapons_online) [self safeAllMissiles]; } - (Vector) currentLaserOffset { return [self laserPortOffset:currentWeaponFacing]; } - (BOOL) fireMainWeapon { OOWeaponType weapon_to_be_fired = [self currentWeapon]; if (![self weaponsOnline]) { return NO; } if (weapon_temp / PLAYER_MAX_WEAPON_TEMP >= WEAPON_COOLING_CUTOUT) { [self playWeaponOverheated:[self currentLaserOffset]]; [UNIVERSE addMessage:DESC(@"weapon-overheat") forCount:3.0]; return NO; } if (isWeaponNone(weapon_to_be_fired)) { return NO; } [self currentWeaponStats]; if (energy <= weapon_energy_use) { [UNIVERSE addMessage:DESC(@"weapon-out-of-juice") forCount:3.0]; return NO; } using_mining_laser = [weapon_to_be_fired isMiningLaser]; energy -= weapon_energy_use; switch (currentWeaponFacing) { case WEAPON_FACING_FORWARD: forward_weapon_temp += weapon_shot_temperature; forward_shot_time = 0.0; break; case WEAPON_FACING_AFT: aft_weapon_temp += weapon_shot_temperature; aft_shot_time = 0.0; break; case WEAPON_FACING_PORT: port_weapon_temp += weapon_shot_temperature; port_shot_time = 0.0; break; case WEAPON_FACING_STARBOARD: starboard_weapon_temp += weapon_shot_temperature; starboard_shot_time = 0.0; break; case WEAPON_FACING_NONE: break; } BOOL weaponFired = NO; if (!isWeaponNone(weapon_to_be_fired)) { if (![weapon_to_be_fired isTurretLaser]) { [self fireLaserShotInDirection:currentWeaponFacing]; weaponFired = YES; } else { // nothing: compatible with previous versions } } if (weaponFired && cloaking_device_active && cloakPassive) { [self deactivateCloakingDevice]; } return weaponFired; } - (OOWeaponType) weaponForFacing:(OOWeaponFacing)facing { switch (facing) { case WEAPON_FACING_FORWARD: return forward_weapon_type; case WEAPON_FACING_AFT: return aft_weapon_type; case WEAPON_FACING_PORT: return port_weapon_type; case WEAPON_FACING_STARBOARD: return starboard_weapon_type; case WEAPON_FACING_NONE: break; } return nil; } - (OOWeaponType) currentWeapon { return [self weaponForFacing:currentWeaponFacing]; } // override ShipEntity definition to ensure that // if shields are still up, always hit the main entity and take the damage // on the shields - (GLfloat) doesHitLine:(HPVector)v0 :(HPVector)v1 :(ShipEntity **)hitEntity { if (hitEntity) hitEntity[0] = (ShipEntity*)nil; Vector u0 = HPVectorToVector(HPvector_between(position, v0)); // relative to origin of model / octree Vector u1 = HPVectorToVector(HPvector_between(position, v1)); Vector w0 = make_vector(dot_product(u0, v_right), dot_product(u0, v_up), dot_product(u0, v_forward)); // in ijk vectors Vector w1 = make_vector(dot_product(u1, v_right), dot_product(u1, v_up), dot_product(u1, v_forward)); GLfloat hit_distance = [octree isHitByLine:w0 :w1]; if (hit_distance) { if (hitEntity) hitEntity[0] = self; } bool shields = false; if ((w0.z >= 0 && forward_shield > 1) || (w0.z <= 0 && aft_shield > 1)) { shields = true; } NSEnumerator *subEnum = nil; ShipEntity *se = nil; for (subEnum = [self shipSubEntityEnumerator]; (se = [subEnum nextObject]); ) { HPVector p0 = [se absolutePositionForSubentity]; Triangle ijk = [se absoluteIJKForSubentity]; u0 = HPVectorToVector(HPvector_between(p0, v0)); u1 = HPVectorToVector(HPvector_between(p0, v1)); w0 = resolveVectorInIJK(u0, ijk); w1 = resolveVectorInIJK(u1, ijk); GLfloat hitSub = [se->octree isHitByLine:w0 :w1]; if (hitSub && (hit_distance == 0 || hit_distance > hitSub)) { hit_distance = hitSub; if (hitEntity && !shields) { *hitEntity = se; } } } return hit_distance; } - (void) takeEnergyDamage:(double)amount from:(Entity *)ent becauseOf:(Entity *)other { HPVector rel_pos; double d_forward, d_right, d_up; BOOL internal_damage = NO; // base chance OOLog(@"player.ship.damage", @"Player took damage from %@ becauseOf %@", ent, other); if ([self status] == STATUS_DEAD) return; if (amount == 0.0) return; BOOL cascadeWeapon = [ent isCascadeWeapon]; BOOL cascading = NO; if (cascadeWeapon) { cascading = [self cascadeIfAppropriateWithDamageAmount:amount cascadeOwner:[ent owner]]; } // make sure ent (& its position) is the attacking _ship_/missile ! if (ent && [ent isSubEntity]) ent = [ent owner]; [[ent retain] autorelease]; [[other retain] autorelease]; rel_pos = (ent != nil) ? [ent position] : kZeroHPVector; rel_pos = HPvector_subtract(rel_pos, position); [self doScriptEvent:OOJSID("shipBeingAttacked") withArgument:ent]; if ([ent isShip]) [(ShipEntity *)ent doScriptEvent:OOJSID("shipAttackedOther") withArgument:self]; d_forward = dot_product(HPVectorToVector(rel_pos), v_forward); d_right = dot_product(HPVectorToVector(rel_pos), v_right); d_up = dot_product(HPVectorToVector(rel_pos), v_up); Vector relative = make_vector(d_right,d_up,d_forward); [self playShieldHit:relative]; // firing on an innocent ship is an offence if ([other isShip]) { [self broadcastHitByLaserFrom:(ShipEntity*) other]; } if (d_forward >= 0) { forward_shield -= amount; if (forward_shield < 0.0) { amount = -forward_shield; forward_shield = 0.0f; } else { amount = 0.0; } } else { aft_shield -= amount; if (aft_shield < 0.0) { amount = -aft_shield; aft_shield = 0.0f; } else { amount = 0.0; } } OOShipDamageType damageType = cascadeWeapon ? kOODamageTypeCascadeWeapon : kOODamageTypeEnergy; if (amount > 0.0) { energy -= amount; [self playDirectHit:relative]; if (ship_temperature < SHIP_MAX_CABIN_TEMP) { /* Heat increase from energy impacts will never directly cause * overheating - too easy for missile hits to cause an uncredited * death by overheating against NPCs, so same rules for player */ ship_temperature += amount * SHIP_ENERGY_DAMAGE_TO_HEAT_FACTOR / [self heatInsulation]; if (ship_temperature > SHIP_MAX_CABIN_TEMP) { ship_temperature = SHIP_MAX_CABIN_TEMP; } } } [self noteTakingDamage:amount from:other type:damageType]; if (cascading) energy = 0.0; // explicitly set energy to zero when cascading, in case an oxp raised the energy in noteTakingDamage. if (energy <= 0.0) //use normal ship temperature calculations for heat damage { if ([other isShip]) { [(ShipEntity *)other noteTargetDestroyed:self]; } [self getDestroyedBy:other damageType:damageType]; } else { while (amount > 0.0) { internal_damage = ((ranrot_rand() & PLAYER_INTERNAL_DAMAGE_FACTOR) < amount); // base chance of damage to systems if (internal_damage) { [self takeInternalDamage]; } amount -= (PLAYER_INTERNAL_DAMAGE_FACTOR + 1); } } } - (void) takeScrapeDamage:(double) amount from:(Entity *) ent { HPVector rel_pos; double d_forward, d_right, d_up; BOOL internal_damage = NO; // base chance if ([self status] == STATUS_DEAD) return; if (amount < 0) { OOLog(@"player.ship.damage", @"Player took negative scrape damage %.3f so we made it positive", amount); amount = -amount; } OOLog(@"player.ship.damage", @"Player took %.3f scrape damage from %@", amount, ent); [[ent retain] autorelease]; rel_pos = ent ? [ent position] : kZeroHPVector; rel_pos = HPvector_subtract(rel_pos, position); // rel_pos is now small d_forward = dot_product(HPVectorToVector(rel_pos), v_forward); d_right = dot_product(HPVectorToVector(rel_pos), v_right); d_up = dot_product(HPVectorToVector(rel_pos), v_up); Vector relative = make_vector(d_right,d_up,d_forward); [self playScrapeDamage:relative]; if (d_forward >= 0) { forward_shield -= amount; if (forward_shield < 0.0) { amount = -forward_shield; forward_shield = 0.0f; } else { amount = 0.0; } } else { aft_shield -= amount; if (aft_shield < 0.0) { amount = -aft_shield; aft_shield = 0.0f; } else { amount = 0.0; } } [super takeScrapeDamage:amount from:ent]; while (amount > 0.0) { internal_damage = ((ranrot_rand() & PLAYER_INTERNAL_DAMAGE_FACTOR) < amount); // base chance of damage to systems if (internal_damage) { [self takeInternalDamage]; } amount -= (PLAYER_INTERNAL_DAMAGE_FACTOR + 1); } } - (void) takeHeatDamage:(double) amount { if ([self status] == STATUS_DEAD || amount < 0) return; // hit the shields first! float fwd_amount = (float)(0.5 * amount); float aft_amount = (float)(0.5 * amount); forward_shield -= fwd_amount; if (forward_shield < 0.0) { fwd_amount = -forward_shield; forward_shield = 0.0f; } else { fwd_amount = 0.0f; } aft_shield -= aft_amount; if (aft_shield < 0.0) { aft_amount = -aft_shield; aft_shield = 0.0f; } else { aft_amount = 0.0f; } double residual_amount = fwd_amount + aft_amount; [super takeHeatDamage:residual_amount]; } - (ProxyPlayerEntity *) createDoppelganger { ProxyPlayerEntity *result = (ProxyPlayerEntity *)[[UNIVERSE newShipWithName:[self shipDataKey] usePlayerProxy:YES] autorelease]; if (result != nil) { [result setPosition:[self position]]; [result setScanClass:CLASS_NEUTRAL]; [result setOrientation:[self normalOrientation]]; [result setVelocity:[self velocity]]; [result setSpeed:[self flightSpeed]]; [result setDesiredSpeed:[self flightSpeed]]; [result setRoll:flightRoll]; [result setBehaviour:BEHAVIOUR_IDLE]; [result switchAITo:@"nullAI.plist"]; // fly straight on [result setTemperature:[self temperature]]; [result copyValuesFromPlayer:self]; } return result; } - (ShipEntity *) launchEscapeCapsule { ShipEntity *doppelganger = nil; ShipEntity *escapePod = nil; if ([UNIVERSE displayGUI]) [self switchToMainView]; // Clear the F7 screen! [UNIVERSE setViewDirection:VIEW_FORWARD]; if ([self status] == STATUS_DEAD) return NO; /* While inside the escape pod, we need to block access to all player.ship properties, since we're not supposed to be inside our ship anymore! -- Kaks 20101114 */ [UNIVERSE setBlockJSPlayerShipProps:YES]; // no player.ship properties while inside the pod! ship_clock_adjust += 43200 + 5400 * (ranrot_rand() & 127); // add up to 8 days until rescue! dockingClearanceStatus = DOCKING_CLEARANCE_STATUS_NOT_REQUIRED; flightSpeed = fmin(flightSpeed, maxFlightSpeed); doppelganger = [self createDoppelganger]; if (doppelganger) { [doppelganger setVelocity:vector_multiply_scalar(v_forward, flightSpeed)]; [doppelganger setSpeed:0.0]; [doppelganger setDesiredSpeed:0.0]; [doppelganger setRoll:0.2 * (randf() - 0.5)]; [doppelganger setOwner:self]; [doppelganger setThrust:0]; // drifts [UNIVERSE addEntity:doppelganger]; } [self setFoundTarget:doppelganger]; // must do this before setting status [self setStatus:STATUS_ESCAPE_SEQUENCE]; // now set up the escape sequence. // must do this before next step or uses BBox of pod, not old ship! float sheight = (float)(boundingBox.max.y - boundingBox.min.y); position = HPvector_subtract(position, vectorToHPVector(vector_multiply_scalar(v_up, sheight))); float sdepth = (float)(boundingBox.max.z - boundingBox.min.z); position = HPvector_subtract(position, vectorToHPVector(vector_multiply_scalar(v_forward, sdepth/2.0))); // set up you escapePod = [UNIVERSE newShipWithName:@"escape-capsule"]; // retained if (escapePod != nil) { // FIXME: this should use OOShipType, which should exist. -- Ahruman [self setMesh:[escapePod mesh]]; } /* These used to be non-zero, but BEHAVIOUR_IDLE levels off flight * anyway, and inertial velocity is used instead of inertialess * thrust - CIM */ flightSpeed = 0.0f; flightPitch = 0.0f; flightRoll = 0.0f; flightYaw = 0.0f; // and turn off inertialess drive thrust = 0.0f; /* Add an impulse upwards and backwards to the escape pod. This avoids flying straight through the doppelganger in interstellar space or when facing the main station/escape target, and generally looks cool. -- Ahruman 2011-04-02 */ Vector launchVector = vector_add([doppelganger velocity], vector_add(vector_multiply_scalar(v_up, 15.0f), vector_multiply_scalar(v_forward, -90.0f))); [self setVelocity:launchVector]; // if multiple items providing escape pod, remove the first one [self removeEquipmentItem:[self equipmentItemProviding:@"EQ_ESCAPE_POD"]]; // set up the standard location where the escape pod will dock. target_system_id = system_id; // we're staying in this system [self setDockTarget:[UNIVERSE station]]; // we're docking at the main station, if there is one [self doScriptEvent:OOJSID("shipLaunchedEscapePod") withArgument:escapePod]; // no player.ship properties should be available to script // reset legal status [self setBounty:0 withReason:kOOLegalStatusReasonEscapePod]; bounty = 0; // new ship, so lose some memory of player actions if (ship_kills >= 6400) { [self clearRolesFromPlayer:0.1]; } else if (ship_kills >= 2560) { [self clearRolesFromPlayer:0.25]; } else { [self clearRolesFromPlayer:0.5]; } // reset trumbles if (trumbleCount != 0) trumbleCount = 1; // remove cargo [cargo removeAllObjects]; energy = 25; [UNIVERSE addMessage:DESC(@"escape-sequence") forCount:4.5]; [self resetShotTime]; // need to zero out all facings shot_times too, otherwise we may end up // with a broken escape pod sequence - Nikos 20100909 forward_shot_time = 0.0; aft_shot_time = 0.0; port_shot_time = 0.0; starboard_shot_time = 0.0; [escapePod release]; return doppelganger; } - (OOCommodityType) dumpCargo { if (flightSpeed > 4.0 * maxFlightSpeed) { [UNIVERSE addMessage:OOExpandKey(@"hold-locked") forCount:3.0]; return nil; } OOCommodityType result = [super dumpCargo]; if (result != nil) { NSString *commodity = [UNIVERSE displayNameForCommodity:result]; [UNIVERSE addMessage:OOExpandKey(@"commodity-ejected", commodity) forCount:3.0 forceDisplay:YES]; [self playCargoJettisioned]; } return result; } - (void) rotateCargo { NSInteger i, n_cargo = [cargo count]; if (n_cargo == 0) return; ShipEntity *pod = (ShipEntity *)[[cargo objectAtIndex:0] retain]; OOCommodityType current_contents = [pod commodityType]; OOCommodityType contents; NSInteger rotates = 0; do { [cargo removeObjectAtIndex:0]; // take it from the eject position [cargo addObject:pod]; // move it to the last position [pod release]; pod = (ShipEntity*)[[cargo objectAtIndex:0] retain]; contents = [pod commodityType]; rotates++; } while ([contents isEqualToString:current_contents]&&(rotates < n_cargo)); [pod release]; NSString *commodity = [UNIVERSE displayNameForCommodity:contents]; [UNIVERSE addMessage:OOExpandKey(@"ready-to-eject-commodity", commodity) forCount:3.0]; // now scan through the remaining 1..(n_cargo - rotates) places moving similar cargo to the last place // this means the cargo gets to be sorted as it is rotated through for (i = 1; i < (n_cargo - rotates); i++) { pod = [cargo objectAtIndex:i]; if ([[pod commodityType] isEqualToString:current_contents]) { [pod retain]; [cargo removeObjectAtIndex:i--]; [cargo addObject:pod]; [pod release]; rotates++; } } } - (void) setBounty:(OOCreditsQuantity) amount { [self setBounty:amount withReason:kOOLegalStatusReasonUnknown]; } - (void) setBounty:(OOCreditsQuantity)amount withReason:(OOLegalStatusReason)reason { NSString *nReason = OOStringFromLegalStatusReason(reason); [self setBounty:amount withReasonAsString:nReason]; } - (void) setBounty:(OOCreditsQuantity)amount withReasonAsString:(NSString *)reason { JSContext *context = OOJSAcquireContext(); jsval amountVal = JSVAL_VOID; int amountVal2 = (int)amount-(int)legalStatus; JS_NewNumberValue(context, amountVal2, &amountVal); legalStatus = (int)amount; // can't set the new bounty until the size of the change is known jsval reasonVal = OOJSValueFromNativeObject(context,reason); ShipScriptEvent(context, self, "shipBountyChanged", amountVal, reasonVal); OOJSRelinquishContext(context); } - (OOCreditsQuantity) bounty // overrides returning 'bounty' { return legalStatus; } - (int) legalStatus { return legalStatus; } - (void) markAsOffender:(int)offence_value { [self markAsOffender:offence_value withReason:kOOLegalStatusReasonUnknown]; } - (void) markAsOffender:(int)offence_value withReason:(OOLegalStatusReason)reason { if (![self isCloaked]) { JSContext *context = OOJSAcquireContext(); jsval amountVal = JSVAL_VOID; int amountVal2 = (legalStatus | offence_value) - legalStatus; JS_NewNumberValue(context, amountVal2, &amountVal); legalStatus |= offence_value; // can't set the new bounty until the size of the change is known jsval reasonVal = OOJSValueFromLegalStatusReason(context, reason); ShipScriptEvent(context, self, "shipBountyChanged", amountVal, reasonVal); OOJSRelinquishContext(context); } } - (void) collectBountyFor:(ShipEntity *)other { if (other == nil || [other isSubEntity]) return; if (other == [UNIVERSE station]) { // there is no way the player can destroy the main station // and so the explosion will be cancelled, so there shouldn't // be a kill award return; } if ([self isCloaked]) { // no-one knows about it; no award return; } OOCreditsQuantity score = 10 * [other bounty]; OOScanClass killClass = [other scanClass]; // **tgape** change (+line) BOOL killAward = [other countsAsKill]; if ([other isPolice]) // oops, we shot a copper! { [self markAsOffender:64 withReason:kOOLegalStatusReasonAttackedPolice]; } BOOL killIsCargo = ((killClass == CLASS_CARGO) && ([other commodityAmount] > 0) && ![other isHulk]); if ((killIsCargo) || (killClass == CLASS_BUOY) || (killClass == CLASS_ROCK)) { // EMMSTRAN: no killaward (but full bounty) for tharglets? if (![other hasRole:@"tharglet"]) // okay, we'll count tharglets as proper kills { score /= 10; // reduce bounty awarded killAward = NO; // don't award a kill } } credits += score; if (score > 9) { NSString *bonusMessage = OOExpandKey(@"bounty-awarded", score, credits); [UNIVERSE addDelayedMessage:bonusMessage forCount:6 afterDelay:0.15]; } if (killAward) { ship_kills++; if ((ship_kills % 256) == 0) { // congratulations method needs to be delayed a fraction of a second [UNIVERSE addDelayedMessage:DESC(@"right-on-commander") forCount:4 afterDelay:0.2]; } } } - (BOOL) takeInternalDamage { unsigned n_cargo = [self maxAvailableCargoSpace]; unsigned n_mass = [self mass] / 10000; unsigned n_considered = (n_cargo + n_mass) * ship_trade_in_factor / 100; // a lower value of n_considered means more vulnerable to damage. unsigned damage_to = n_considered ? (ranrot_rand() % n_considered) : 0; // n_considered can be 0 for small ships. BOOL result = NO; // cargo damage if (damage_to < [cargo count]) { ShipEntity* pod = (ShipEntity*)[cargo objectAtIndex:damage_to]; NSString* cargo_desc = [UNIVERSE displayNameForCommodity:[pod commodityType]]; if (!cargo_desc) return NO; [UNIVERSE clearPreviousMessage]; [UNIVERSE addMessage:[NSString stringWithFormat:DESC(@"@-destroyed"), cargo_desc] forCount:4.5]; [cargo removeObject:pod]; return YES; } else { damage_to = n_considered - (damage_to + 1); // reverse the die-roll } // equipment damage NSEnumerator *eqEnum = [self equipmentEnumerator]; OOEquipmentType *eqType = nil; NSString *system_key; unsigned damageableCounter = 0; GLfloat damageableOdds = 0.0; while ((system_key = [eqEnum nextObject]) != nil) { eqType = [OOEquipmentType equipmentTypeWithIdentifier:system_key]; if ([eqType canBeDamaged]) { damageableCounter++; damageableOdds += [eqType damageProbability]; } } if (damage_to < damageableCounter) { GLfloat target = randf() * damageableOdds; GLfloat accumulator = 0.0; eqEnum = [self equipmentEnumerator]; while ((system_key = [eqEnum nextObject]) != nil) { eqType = [OOEquipmentType equipmentTypeWithIdentifier:system_key]; accumulator += [eqType damageProbability]; if (accumulator > target) { [system_key retain]; break; } } if (system_key == nil) { [system_key release]; return NO; } NSString *system_name = [eqType name]; if (![eqType canBeDamaged] || system_name == nil) { [system_key release]; return NO; } // set the following so removeEquipment works on the right entity [self setScriptTarget:self]; [UNIVERSE clearPreviousMessage]; [self removeEquipmentItem:system_key]; NSString *damagedKey = [NSString stringWithFormat:@"%@_DAMAGED", system_key]; [self addEquipmentItem:damagedKey withValidation: NO inContext:@"damage"]; // for possible future repair. [self doScriptEvent:OOJSID("equipmentDamaged") withArgument:system_key]; if (![self hasEquipmentItem:system_name] && [self hasEquipmentItem:damagedKey]) { /* Display "foo damaged" message only if no script has repaired or removed the equipment item. (If a script does either of those and wants a message, it can write it itself.) */ [UNIVERSE addMessage:[NSString stringWithFormat:DESC(@"@-damaged"), system_name] forCount:4.5]; } /* There used to be a check for docking computers here, but * that didn't cover other ways they might fail in flight, so * it has been moved to the removeEquipment method. */ [system_key release]; return YES; } //cosmetic damage if (((damage_to & 7) == 7)&&(ship_trade_in_factor > 75)) { ship_trade_in_factor--; result = YES; } return result; } - (void) getDestroyedBy:(Entity *)whom damageType:(OOShipDamageType)type { if ([self isDocked]) return; // Can't die while docked. (Doing so would cause breakage elsewhere.) OOLog(@"player.ship.damage", @"Player destroyed by %@ due to %@", whom, OOStringFromShipDamageType(type)); if (![[UNIVERSE gameController] playerFileToLoad]) { [[UNIVERSE gameController] setPlayerFileToLoad: save_path]; // make sure we load the correct game } energy = 0.0f; afterburner_engaged = NO; [self disengageAutopilot]; [UNIVERSE setDisplayText:NO]; [UNIVERSE setViewDirection:VIEW_AFT]; // Let scripts know the player died. [self noteKilledBy:whom damageType:type]; // called before exploding, consistant with npc ships. [self becomeLargeExplosion:4.0]; // also sets STATUS_DEAD [self moveForward:100.0]; flightSpeed = 160.0f; velocity = kZeroVector; flightRoll = 0.0; flightPitch = 0.0; flightYaw = 0.0; [[UNIVERSE messageGUI] clear]; // No messages for the dead. [self suppressTargetLost]; // No target lost messages when dead. [self playGameOver]; [UNIVERSE setBlockJSPlayerShipProps:YES]; // Treat JS player as stale entity. [self removeAllEquipment]; // No scooping / equipment damage when dead. [self loseTargetStatus]; [self showGameOver]; } - (void) loseTargetStatus { if (!UNIVERSE) return; int ent_count = UNIVERSE->n_entities; Entity** uni_entities = UNIVERSE->sortedEntities; // grab the public sorted list Entity* my_entities[ent_count]; int i; for (i = 0; i < ent_count; i++) my_entities[i] = [uni_entities[i] retain]; // retained for (i = 0; i < ent_count ; i++) { Entity* thing = my_entities[i]; if (thing->isShip) { ShipEntity* ship = (ShipEntity *)thing; if (self == [ship primaryTarget]) { [ship noteLostTarget]; } } } for (i = 0; i < ent_count; i++) { [my_entities[i] release]; // released } } - (BOOL) endScenario:(NSString *)key { if (scenarioKey != nil && [key isEqualToString:scenarioKey]) { [self setStatus:STATUS_RESTART_GAME]; return YES; } return NO; } - (void) enterDock:(StationEntity *)station { NSParameterAssert(station != nil); if ([self status] == STATUS_DEAD) return; OOProfilerStartMarker(@"dock"); [self setStatus:STATUS_DOCKING]; [self setDockedStation:station]; [self doScriptEvent:OOJSID("shipWillDockWithStation") withArgument:station]; if (![hud nonlinearScanner]) { [hud setScannerZoom: 1.0]; } ident_engaged = NO; afterburner_engaged = NO; autopilot_engaged = NO; [self resetAutopilotAI]; cloaking_device_active = NO; hyperspeed_engaged = NO; hyperspeed_locked = NO; [self safeAllMissiles]; DESTROY(_primaryTarget); // must happen before showing break_pattern to supress active reticule. [self clearTargetMemory]; scanner_zoom_rate = 0.0f; [UNIVERSE setDisplayText:NO]; [[UNIVERSE gameController] setMouseInteractionModeForFlight]; if ([self status] == STATUS_LAUNCHING) return; // a JS script has aborted the docking. [self setOrientation: kIdentityQuaternion]; // reset orientation to dock [UNIVERSE setUpBreakPattern:[self breakPatternPosition] orientation:orientation forDocking:YES]; [self playDockWithStation]; [station noteDockedShip:self]; [[UNIVERSE gameView] clearKeys]; // try to stop key bounces } - (void) docked { OOProfilerPointMarker(@"-docked called"); StationEntity *dockedStation = [self dockedStation]; if (dockedStation == nil) { [self setStatus:STATUS_IN_FLIGHT]; return; } [self setStatus:STATUS_DOCKED]; [UNIVERSE enterGUIViewModeWithMouseInteraction:NO]; [self loseTargetStatus]; [self setPosition:[dockedStation position]]; [self setOrientation:kIdentityQuaternion]; // reset orientation to dock flightRoll = 0.0f; flightPitch = 0.0f; flightYaw = 0.0f; flightSpeed = 0.0f; hyperspeed_engaged = NO; hyperspeed_locked = NO; forward_shield = [self maxForwardShieldLevel]; aft_shield = [self maxAftShieldLevel]; energy = maxEnergy; weapon_temp = 0.0f; ship_temperature = 60.0f; [self setAlertFlag:ALERT_FLAG_DOCKED to:YES]; if ([dockedStation localMarket] == nil) { [dockedStation initialiseLocalMarket]; } NSString *escapepodReport = [self processEscapePods]; [self addMessageToReport:escapepodReport]; [self unloadCargoPods]; // fill up the on-ship commodities before... // check import status of station // escape pods must be cleared before this happens if ([dockedStation marketMonitored]) { OOCreditsQuantity oldbounty = [self bounty]; [self markAsOffender:[dockedStation legalStatusOfManifest:shipCommodityData export:NO] withReason:kOOLegalStatusReasonIllegalImports]; if ([self bounty] > oldbounty) { [self addRoleToPlayer:@"trader-smuggler"]; } } // check contracts NSString *passengerAndCargoReport = [self checkPassengerContracts]; // Is also processing cargo and parcel contracts. [self addMessageToReport:passengerAndCargoReport]; [UNIVERSE setDisplayText:YES]; [[OOMusicController sharedController] stopDockingMusic]; [[OOMusicController sharedController] playDockedMusic]; // Did we fail to observe traffic control regulations? However, due to the state of emergency, // apply no unauthorized docking penalties if a nova is ongoing. if ([dockedStation requiresDockingClearance] && ![self clearedToDock] && ![[UNIVERSE sun] willGoNova]) { [self penaltyForUnauthorizedDocking]; } // apply any pending fines. (No need to check gui_screen as fines is no longer an on-screen message). if (dockedStation == [UNIVERSE station]) { // TODO: A proper system to allow some OXP stations to have a // galcop presence for fines. - CIM 18/11/2012 if (being_fined && ![[UNIVERSE sun] willGoNova] && ![dockedStation suppressArrivalReports]) [self getFined]; } // it's time to check the script - can trigger legacy missions if (gui_screen != GUI_SCREEN_MISSION) [self checkScript]; // a scripted pilot could have created a mission screen. OOJSStartTimeLimiterWithTimeLimit(kOOJSLongTimeLimit); [self doScriptEvent:OOJSID("shipDockedWithStation") withArgument:dockedStation]; OOJSStopTimeLimiter(); if ([self status] == STATUS_LAUNCHING) return; // if we've not switched to the mission screen yet then proceed normally.. if (gui_screen != GUI_SCREEN_MISSION) { [self setGuiToStatusScreen]; } [[OOCacheManager sharedCache] flush]; [[OOJavaScriptEngine sharedEngine] garbageCollectionOpportunity:YES]; // When a mission screen is started, any on-screen message is removed immediately. [self doWorldEventUntilMissionScreen:OOJSID("missionScreenOpportunity")]; // also displays docking reports first. OOProfilerEndMarker(@"dock"); } - (void) leaveDock:(StationEntity *)station { if (station == nil) return; NSParameterAssert(station == [self dockedStation]); OOProfilerStartMarker(@"undock"); // ensure we've not left keyboard entry on [[UNIVERSE gameView] allowStringInput: NO]; if (gui_screen == GUI_SCREEN_MISSION) { [[UNIVERSE gui] clearBackground]; if (_missionWithCallback) { [self doMissionCallback]; } // notify older scripts, but do not trigger missionScreenOpportunity. [self doWorldEventUntilMissionScreen:OOJSID("missionScreenEnded")]; } if ([station marketMonitored]) { // 'leaving with those guns were you sir?' OOCreditsQuantity oldbounty = [self bounty]; [self markAsOffender:[station legalStatusOfManifest:shipCommodityData export:YES] withReason:kOOLegalStatusReasonIllegalExports]; if ([self bounty] > oldbounty) { [self addRoleToPlayer:@"trader-smuggler"]; } } OOGUIScreenID oldScreen = gui_screen; gui_screen = GUI_SCREEN_MAIN; [self noteGUIDidChangeFrom:oldScreen to:gui_screen]; if (![hud nonlinearScanner]) { [hud setScannerZoom: 1.0]; } [self loadCargoPods]; // do not do anything that calls JS handlers between now and calling // [station launchShip] below, or the cargo returned by JS may be off // CIM - 3.2.2012 // clear the way [station autoDockShipsOnApproach]; [station clearDockingCorridor]; // [self setAlertFlag:ALERT_FLAG_DOCKED to:NO]; [self clearAlertFlags]; [self setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE]; scanner_zoom_rate = 0.0f; currentWeaponFacing = WEAPON_FACING_FORWARD; [self currentWeaponStats]; forward_weapon_temp = 0.0f; aft_weapon_temp = 0.0f; port_weapon_temp = 0.0f; starboard_weapon_temp = 0.0f; forward_shield = [self maxForwardShieldLevel]; aft_shield = [self maxAftShieldLevel]; [self clearTargetMemory]; [self setShowDemoShips:NO]; [UNIVERSE setDisplayText:NO]; [[UNIVERSE gameController] setMouseInteractionModeForFlight]; [[UNIVERSE gameView] clearKeys]; // try to stop keybounces [[OOMusicController sharedController] stop]; [UNIVERSE forceWitchspaceEntries]; ship_clock_adjust += 600.0; // 10 minutes to leave dock velocity = kZeroVector; // just in case [station launchShip:self]; launchRoll = -flightRoll; // save the station's spin. (inverted for player) flightRoll = 0; // don't spin when showing the break pattern. [UNIVERSE setUpBreakPattern:[self breakPatternPosition] orientation:orientation forDocking:YES]; [self setDockedStation:nil]; suppressAegisMessages = YES; [self checkForAegis]; suppressAegisMessages = NO; ident_engaged = NO; [UNIVERSE removeDemoShips]; // MKW - ensure GUI Screen ship is removed [demoShip release]; demoShip = nil; OOProfilerEndMarker(@"undock"); [self playLaunchFromStation]; } - (void) witchStart { OOProfilerStartMarker(@"witchspace"); // chances of entering witchspace with autopilot on are very low, but as Berlios bug #18307 has shown us, entirely possible // so in such cases we need to ensure that at least the docking music stops playing if (autopilot_engaged) [self disengageAutopilot]; if (![hud nonlinearScanner]) { [hud setScannerZoom: 1.0]; } [self safeAllMissiles]; [UNIVERSE setViewDirection:VIEW_FORWARD]; currentWeaponFacing = WEAPON_FACING_FORWARD; [self currentWeaponStats]; [self transitionToAegisNone]; suppressAegisMessages=YES; hyperspeed_engaged = NO; if ([self primaryTarget] != nil) { [self noteLostTarget]; // losing target? Fire lost target event! DESTROY(_primaryTarget); } scanner_zoom_rate = 0.0f; [UNIVERSE setDisplayText:NO]; if ( ![self wormhole] && !galactic_witchjump) // galactic hyperspace does not generate a wormhole { OOLog(kOOLogInconsistentState, @"Internal Error : Player entering witchspace with no wormhole."); } [UNIVERSE allShipsDoScriptEvent:OOJSID("playerWillEnterWitchspace") andReactToAIMessage:@"PLAYER WITCHSPACE"]; // set the new market seed now! // reseeding the RNG should be completely unnecessary here // ranrot_srand((uint32_t)[[NSDate date] timeIntervalSince1970]); // seed randomiser by time market_rnd = ranrot_rand() & 255; // random factor for market values is reset } - (void) witchEnd { [UNIVERSE setSystemTo:system_id]; galaxy_coordinates = [[UNIVERSE systemManager] getCoordinatesForSystem:system_id inGalaxy:galaxy_number]; [UNIVERSE setUpUniverseFromWitchspace]; [[UNIVERSE planet] update: 2.34375 * market_rnd]; // from 0..10 minutes [[UNIVERSE station] update: 2.34375 * market_rnd]; // from 0..10 minutes chart_centre_coordinates = galaxy_coordinates; target_chart_centre = chart_centre_coordinates; OOProfilerEndMarker(@"witchspace"); } - (BOOL) witchJumpChecklist:(BOOL)isGalacticJump { // Perform this check only when doing the actual jump if ([self status] == STATUS_WITCHSPACE_COUNTDOWN) { // check nearby masses //UPDATE_STAGE(@"checking for mass blockage"); ShipEntity* blocker = [UNIVERSE entityForUniversalID:[self checkShipsInVicinityForWitchJumpExit]]; if (blocker) { [UNIVERSE clearPreviousMessage]; NSString *blockerName = [blocker name]; [UNIVERSE addMessage:OOExpandKey(@"witch-blocked", blockerName) forCount:4.5]; [self playWitchjumpBlocked]; [self setStatus:STATUS_IN_FLIGHT]; ShipScriptEventNoCx(self, "playerJumpFailed", OOJSSTR("blocked")); return NO; } } // For galactic hyperspace jumps we skip the remaining checks if (isGalacticJump) { return YES; } // Check we're not jumping into the current system if (![UNIVERSE inInterstellarSpace] && system_id == target_system_id) { //dont allow player to hyperspace to current location. //Note interstellar space will have a system_seed place we came from [UNIVERSE clearPreviousMessage]; [UNIVERSE addMessage:OOExpandKey(@"witch-no-target") forCount: 4.5]; if ([self status] == STATUS_WITCHSPACE_COUNTDOWN) { [self playWitchjumpInsufficientFuel]; [self setStatus:STATUS_IN_FLIGHT]; ShipScriptEventNoCx(self, "playerJumpFailed", OOJSSTR("no target")); } else [self playHyperspaceNoTarget]; return NO; } // check max distance permitted if ([self hyperspaceJumpDistance] > [self maxHyperspaceDistance]) { [UNIVERSE clearPreviousMessage]; [UNIVERSE addMessage:DESC(@"witch-too-far") forCount: 4.5]; if ([self status] == STATUS_WITCHSPACE_COUNTDOWN) { [self playWitchjumpDistanceTooGreat]; [self setStatus:STATUS_IN_FLIGHT]; ShipScriptEventNoCx(self, "playerJumpFailed", OOJSSTR("too far")); } else [self playHyperspaceDistanceTooGreat]; return NO; } // check fuel level if (![self hasSufficientFuelForJump]) { [UNIVERSE clearPreviousMessage]; [UNIVERSE addMessage:DESC(@"witch-no-fuel") forCount: 4.5]; if ([self status] == STATUS_WITCHSPACE_COUNTDOWN) { [self playWitchjumpInsufficientFuel]; [self setStatus:STATUS_IN_FLIGHT]; ShipScriptEventNoCx(self, "playerJumpFailed", OOJSSTR("insufficient fuel")); } else [self playHyperspaceNoFuel]; return NO; } // All checks passed return YES; } - (void) setJumpType:(BOOL)isGalacticJump { if (isGalacticJump) { galactic_witchjump = YES; } else { galactic_witchjump = NO; } } - (double) hyperspaceJumpDistance { NSPoint targetCoordinates = PointFromString([[UNIVERSE systemManager] getProperty:@"coordinates" forSystem:[self nextHopTargetSystemID] inGalaxy:galaxy_number]); return distanceBetweenPlanetPositions(targetCoordinates.x,targetCoordinates.y,galaxy_coordinates.x,galaxy_coordinates.y); } - (OOFuelQuantity) fuelRequiredForJump { return 10.0 * MAX(0.1, [self hyperspaceJumpDistance]); } - (BOOL) hasSufficientFuelForJump { return fuel >= [self fuelRequiredForJump]; } - (void) noteCompassLostTarget { if ([[self hud] isCompassActive]) { // "the compass, it says we're lost!" :) JSContext *context = OOJSAcquireContext(); jsval jsmode = OOJSValueFromCompassMode(context, [self compassMode]); ShipScriptEvent(context, self, "compassTargetChanged", JSVAL_VOID, jsmode); OOJSRelinquishContext(context); [[self hud] setCompassActive:NO]; // ensure a target change when returning to normal space. } } - (void) enterGalacticWitchspace { if (![self witchJumpChecklist:true]) return; OOGalaxyID destGalaxy = galaxy_number + 1; if (EXPECT_NOT(destGalaxy >= OO_GALAXIES_AVAILABLE)) { destGalaxy = 0; } [self setStatus:STATUS_ENTERING_WITCHSPACE]; ShipScriptEventNoCx(self, "shipWillEnterWitchspace", OOJSSTR("galactic jump"), INT_TO_JSVAL(destGalaxy)); [self noteCompassLostTarget]; [self witchStart]; [UNIVERSE removeAllEntitiesExceptPlayer]; // remove any contracts and parcels for the old galaxy if (contracts) [contracts removeAllObjects]; if (parcels) [parcels removeAllObjects]; // remove any mission destinations for the old galaxy if (missionDestinations) [missionDestinations removeAllObjects]; // expire passenger contracts for the old galaxy if (passengers) { unsigned i; for (i = 0; i < [passengers count]; i++) { // set the expected arrival time to now, so they storm off the ship at the first port NSMutableDictionary* passenger_info = [NSMutableDictionary dictionaryWithDictionary:[passengers oo_dictionaryAtIndex:i]]; [passenger_info setObject:[NSNumber numberWithDouble:ship_clock] forKey:CONTRACT_KEY_ARRIVAL_TIME]; [passengers replaceObjectAtIndex:i withObject:passenger_info]; } } // clear a lot of memory of player actions if (ship_kills >= 6400) { [self clearRolesFromPlayer:0.25]; } else if (ship_kills >= 2560) { [self clearRolesFromPlayer:0.5]; } else { [self clearRolesFromPlayer:0.9]; } [roleWeightFlags removeAllObjects]; [roleSystemList removeAllObjects]; // may be more than one item providing this [self removeEquipmentItem:[self equipmentItemProviding:@"EQ_GAL_DRIVE"]]; galaxy_number = destGalaxy; [UNIVERSE setGalaxyTo:galaxy_number]; // Choose the galactic hyperspace behaviour. Refers to where we may actually end up after an intergalactic jump. // The default behaviour is that the player cannot arrive on unreachable or isolated systems. The options // in planetinfo.plist, galactic_hyperspace_behaviour key can be used to allow arrival even at unreachable systems, // or at fixed coordinates on the galactic chart. The key galactic_hyperspace_fixed_coords in planetinfo.plist is // used in the fixed coordinates case and specifies the exact coordinates for the intergalactic jump. switch (galacticHyperspaceBehaviour) { case GALACTIC_HYPERSPACE_BEHAVIOUR_FIXED_COORDINATES: system_id = [UNIVERSE findSystemAtCoords:galacticHyperspaceFixedCoords withGalaxy:galaxy_number]; break; case GALACTIC_HYPERSPACE_BEHAVIOUR_ALL_SYSTEMS_REACHABLE: system_id = [UNIVERSE findSystemAtCoords:galaxy_coordinates withGalaxy:galaxy_number]; break; case GALACTIC_HYPERSPACE_BEHAVIOUR_STANDARD: default: // instead find a system connected to system 0 near the current coordinates... system_id = [UNIVERSE findConnectedSystemAtCoords:galaxy_coordinates withGalaxy:galaxy_number]; break; } target_system_id = system_id; [self setBounty:0 withReason:kOOLegalStatusReasonNewGalaxy]; // let's make a fresh start! cursor_coordinates = PointFromString([[UNIVERSE systemManager] getProperty:@"coordinates" forSystem:system_id inGalaxy:galaxy_number]); [self witchEnd]; // sets coordinates [self doScriptEvent:OOJSID("playerEnteredNewGalaxy") withArgument:[NSNumber numberWithUnsignedInt:galaxy_number]]; } // now with added misjump goodness! // If the wormhole generator misjumped, the player's ship misjumps too. Kaks 20110211 - (void) enterWormhole:(WormholeEntity *) w_hole { if ([self status] == STATUS_ENTERING_WITCHSPACE || [self status] == STATUS_EXITING_WITCHSPACE) { return; // has already entered a different wormhole } BOOL misjump = [self scriptedMisjump] || [w_hole withMisjump] || flightPitch == max_flight_pitch || randf() > 0.995; wormhole = [w_hole retain]; [self addScannedWormhole:wormhole]; [self setStatus:STATUS_ENTERING_WITCHSPACE]; ShipScriptEventNoCx(self, "shipWillEnterWitchspace", OOJSSTR("wormhole"), INT_TO_JSVAL([w_hole destination])); if ([self scriptedMisjump]) { misjump = YES; // a script could just have changed this to true; } #ifdef OO_DUMP_PLANETINFO misjump = NO; #endif if (misjump && [self scriptedMisjumpRange] != 0.5) { [w_hole setMisjumpWithRange:[self scriptedMisjumpRange]]; // overrides wormholes, if player also had non-default scriptedMisjumpRange } [self witchJumpTo:[w_hole destination] misjump:misjump]; } - (void) enterWitchspace { if (![self witchJumpChecklist:false]) return; OOSystemID jumpTarget = [self nextHopTargetSystemID]; // perform any check here for forced witchspace encounters unsigned malfunc_chance = 253; if (ship_trade_in_factor < 80) { malfunc_chance -= (1 + ranrot_rand() % (81-ship_trade_in_factor)) / 2; // increase chance of misjump in worn-out craft } else if (ship_trade_in_factor >= 100) { malfunc_chance = 256; // force no misjumps on first jump } #ifdef OO_DUMP_PLANETINFO BOOL misjump = NO; // debugging #else BOOL malfunc = ((ranrot_rand() & 0xff) > malfunc_chance); // 75% of the time a malfunction means a misjump BOOL misjump = [self scriptedMisjump] || (flightPitch == max_flight_pitch) || (malfunc && (randf() > 0.75)); if (malfunc && !misjump) { // some malfunctions will start fuel leaks, some will result in no witchjump at all. if ([self takeInternalDamage]) // Depending on ship type and loaded cargo, this will be true for 20 - 50% of the time. { [self playWitchjumpFailure]; [self setStatus:STATUS_IN_FLIGHT]; ShipScriptEventNoCx(self, "playerJumpFailed", OOJSSTR("malfunction")); return; } else { [self setFuelLeak:[NSString stringWithFormat:@"%f", (randf() + randf()) * 5.0]]; } } #endif // From this point forward we are -definitely- witchjumping // burn the full fuel amount to create the wormhole fuel -= [self fuelRequiredForJump]; // Create the players' wormhole wormhole = [[WormholeEntity alloc] initWormholeTo:jumpTarget fromShip:self]; [UNIVERSE addEntity:wormhole]; // Add new wormhole to Universe to let other ships target it. Required for ships following the player. [self addScannedWormhole:wormhole]; [self setStatus:STATUS_ENTERING_WITCHSPACE]; ShipScriptEventNoCx(self, "shipWillEnterWitchspace", OOJSSTR("standard jump"), INT_TO_JSVAL(jumpTarget)); [self updateSystemMemory]; NSUInteger legality = [self legalStatusOfCargoList]; OOCargoQuantity maxSpace = [self maxAvailableCargoSpace]; OOCargoQuantity availSpace = [self availableCargoSpace]; if ([roleWeightFlags objectForKey:@"bought-legal"]) { if (maxSpace != availSpace) { [self addRoleToPlayer:@"trader"]; if (maxSpace - availSpace > 20 || availSpace == 0) { if (legality == 0) { [self addRoleToPlayer:@"trader"]; } } } } if ([roleWeightFlags objectForKey:@"bought-illegal"]) { if (maxSpace != availSpace && legality > 0) { [self addRoleToPlayer:@"trader-smuggler"]; if (maxSpace - availSpace > 20 || availSpace == 0) { if (legality >= 20 || legality >= maxSpace) { [self addRoleToPlayer:@"trader-smuggler"]; } } } } [roleWeightFlags removeAllObjects]; [self noteCompassLostTarget]; if ([self scriptedMisjump]) { misjump = YES; // a script could just have changed this to true; } if (misjump) { [wormhole setMisjumpWithRange:[self scriptedMisjumpRange]]; } [self witchJumpTo:jumpTarget misjump:misjump]; } - (void) witchJumpTo:(OOSystemID)sTo misjump:(BOOL)misjump { [self witchStart]; //wear and tear on all jumps (inc misjumps, failures, and wormholes) if (2 * market_rnd < ship_trade_in_factor) { // every eight jumps or so drop the price down towards 75% [self adjustTradeInFactorBy:-(1 + (market_rnd & 3))]; } // set clock after "playerWillEnterWitchspace" and before removeAllEntitiesExceptPlayer, to allow escorts time to follow their mother. NSPoint destCoords = PointFromString([[UNIVERSE systemManager] getProperty:@"coordinates" forSystem:sTo inGalaxy:galaxy_number]); double distance = distanceBetweenPlanetPositions(destCoords.x,destCoords.y,galaxy_coordinates.x,galaxy_coordinates.y); [UNIVERSE removeAllEntitiesExceptPlayer]; if (!misjump) { ship_clock_adjust += distance * distance * 3600.0; [self setSystemID:sTo]; [self setBounty:(legalStatus/2) withReason:kOOLegalStatusReasonNewSystem]; // 'another day, another system' [self witchEnd]; if (market_rnd < 8) [self erodeReputation]; // every 32 systems or so, drop back towards 'unknown' } else { // Misjump: move halfway there! // misjumps do not change legal status. if (randf() < 0.1) [self erodeReputation]; // once every 10 misjumps - should be much rarer than successful jumps! [wormhole setMisjump]; // just in case, but this has usually been set already // and now the wormhole has travel time and coordinates calculated // so rather than duplicate the calculation we'll just ask it... NSPoint dest = [wormhole destinationCoordinates]; galaxy_coordinates.x = dest.x; galaxy_coordinates.y = dest.y; ship_clock_adjust += [wormhole travelTime]; [self playWitchjumpMisjump]; [UNIVERSE setUpUniverseFromMisjump]; } } - (void) leaveWitchspace { float d1 = (float)(SCANNER_MAX_RANGE*((Ranrot() & 255)/256.0 - 0.5)); HPVector pos = [UNIVERSE getWitchspaceExitPosition]; // no need to reset the PRNG Quaternion q1; HPVector whpos, exitpos; GLfloat min_d1 = [UNIVERSE safeWitchspaceExitDistance]; quaternion_set_random(&q1); if (abs((int)d1) < min_d1) { d1 += ((d1 > 0.0)? min_d1: -min_d1); // not too close to the buoy. } HPVector v1 = HPvector_forward_from_quaternion(q1); exitpos = HPvector_add(pos, HPvector_multiply_scalar(v1, d1)); // randomise exit position position = exitpos; [self setOrientation:[UNIVERSE getWitchspaceExitRotation]]; // While setting the wormhole position to the player position looks very nice for ships following the player, // the more common case of the player following other ships, the player tends to // ram the back of the ships, or even jump on top of is when the ship jumped without initial speed, which is messy. // To avoid this problem, a small wormhole displacement is added. if (wormhole) // will be nil for galactic jump { if ([[wormhole shipsInTransit] count] > 0) { // player is not allone in his wormhole, synchronise player and wormhole position. double wh_arrival_time = ([PLAYER clockTimeAdjusted] - [wormhole arrivalTime]); if (wh_arrival_time > 0) { // Player is following other ship whpos = HPvector_add(exitpos, vectorToHPVector(vector_multiply_scalar([self forwardVector], 1000.0f))); [wormhole setContainsPlayer:YES]; } else { // Player is the leadship whpos = HPvector_add(exitpos, vectorToHPVector(vector_multiply_scalar([self forwardVector], -500.0f))); // so it won't contain the player by the time they exit [wormhole setExitSpeed:maxFlightSpeed*WORMHOLE_LEADER_SPEED_FACTOR]; } HPVector distance = HPvector_subtract(whpos, pos); if (HPmagnitude2(distance) < min_d1*min_d1 ) // within safety distance from the buoy? { // the wormhole is to close to the buoy. Move both player and wormhole away from it in the x-y plane. distance.z = 0; distance = HPvector_multiply_scalar(HPvector_normal(distance), min_d1); whpos = HPvector_add(whpos, distance); position = HPvector_add(position, distance); } [wormhole setExitPosition: whpos]; } else { // no-one else in the wormhole [wormhole setExitSpeed:maxFlightSpeed*WORMHOLE_LEADER_SPEED_FACTOR]; } } /* there's going to be a slight pause at this stage anyway; * there's also going to be a lot of stale ship scripts. Force a * garbage collection while we have chance. - CIM */ [[OOJavaScriptEngine sharedEngine] garbageCollectionOpportunity:YES]; flightSpeed = wormhole ? [wormhole exitSpeed] : fmin(maxFlightSpeed,50.0f); [wormhole release]; // OK even if nil wormhole = nil; flightRoll = 0.0f; flightPitch = 0.0f; flightYaw = 0.0f; velocity = kZeroVector; [self setStatus:STATUS_EXITING_WITCHSPACE]; gui_screen = GUI_SCREEN_MAIN; being_fined = NO; // until you're scanned by a copper! [self clearTargetMemory]; [self setShowDemoShips:NO]; [[UNIVERSE gameController] setMouseInteractionModeForFlight]; [UNIVERSE setDisplayText:NO]; [UNIVERSE setWitchspaceBreakPattern:YES]; [self playExitWitchspace]; if ([self currentSystemID] >= 0) { if (![roleSystemList containsObject:[NSNumber numberWithInt:[self currentSystemID]]]) { // going somewhere new? [self clearRoleFromPlayer:NO]; } } [self doScriptEvent:OOJSID("shipWillExitWitchspace")]; [UNIVERSE setUpBreakPattern:[self breakPatternPosition] orientation:orientation forDocking:NO]; } /////////////////////////////////// - (void) setGuiToStatusScreen { NSString *systemName = nil; NSString *targetSystemName = nil; NSString *text = nil; GuiDisplayGen *gui = [UNIVERSE gui]; OOGUIScreenID oldScreen = gui_screen; if (oldScreen != GUI_SCREEN_STATUS) { [self noteGUIWillChangeTo:GUI_SCREEN_STATUS]; } gui_screen = GUI_SCREEN_STATUS; BOOL guiChanged = (oldScreen != gui_screen); [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:NO]; // Both system_seed & target_system_seed are != nil at all times when this function is called. systemName = [UNIVERSE inInterstellarSpace] ? DESC(@"interstellar-space") : [UNIVERSE getSystemName:system_id]; if ([self isDocked] && [self dockedStation] != [UNIVERSE station]) { systemName = [NSString stringWithFormat:@"%@ : %@", systemName, [[self dockedStation] displayName]]; } targetSystemName = [UNIVERSE getSystemName:target_system_id]; OOSystemID nextHop = [self nextHopTargetSystemID]; if (nextHop != target_system_id) { NSString *nextHopSystemName = [UNIVERSE getSystemName:nextHop]; targetSystemName = OOExpandKey(@"status-hyperspace-system-multi", targetSystemName, nextHopSystemName); } // GUI stuff { NSString *shipName = [self displayName]; NSString *legal_desc = nil, *rating_desc = nil, *alert_desc = nil, *fuel_desc = nil, *credits_desc = nil; OOGUIRow i; OOGUITabSettings tab_stops; tab_stops[0] = 20; tab_stops[1] = 160; tab_stops[2] = 290; [gui overrideTabs:tab_stops from:kGuiStatusTabs length:3]; [gui setTabStops:tab_stops]; NSString *lightYearsDesc = DESC(@"status-light-years-desc"); legal_desc = OODisplayStringFromLegalStatus(legalStatus); rating_desc = KillCountToRatingAndKillString(ship_kills); alert_desc = OODisplayStringFromAlertCondition([self alertCondition]); fuel_desc = [NSString stringWithFormat:@"%.1f %@", fuel/10.0, lightYearsDesc]; credits_desc = OOCredits(credits); [gui clearAndKeepBackground:!guiChanged]; text = DESC(@"status-commander-@"); [gui setTitle:[NSString stringWithFormat:text, [self commanderName]]]; [gui setText:shipName forRow:0 align:GUI_ALIGN_CENTER]; [gui setArray:[NSArray arrayWithObjects:DESC(@"status-present-system"), systemName, nil] forRow:1]; if ([self hasHyperspaceMotor]) [gui setArray:[NSArray arrayWithObjects:DESC(@"status-hyperspace-system"), targetSystemName, nil] forRow:2]; [gui setArray:[NSArray arrayWithObjects:DESC(@"status-condition"), alert_desc, nil] forRow:3]; [gui setArray:[NSArray arrayWithObjects:DESC(@"status-fuel"), fuel_desc, nil] forRow:4]; [gui setArray:[NSArray arrayWithObjects:DESC(@"status-cash"), credits_desc, nil] forRow:5]; [gui setArray:[NSArray arrayWithObjects:DESC(@"status-legal-status"), legal_desc, nil] forRow:6]; [gui setArray:[NSArray arrayWithObjects:DESC(@"status-rating"), rating_desc, nil] forRow:7]; [gui setColor:[gui colorFromSetting:kGuiStatusShipnameColor defaultValue:nil] forRow:0]; for (i = 1 ; i <= 7 ; ++i) { // nil default = fall back to global default colour [gui setColor:[gui colorFromSetting:kGuiStatusDataColor defaultValue:nil] forRow:i]; } [gui setText:DESC(@"status-equipment") forRow:9]; [gui setColor:[gui colorFromSetting:kGuiStatusEquipmentHeadingColor defaultValue:nil] forRow:9]; [gui setShowTextCursor:NO]; } /* ends */ if (lastTextKey) { [lastTextKey release]; lastTextKey = nil; } [[UNIVERSE gameView] clearMouse]; // Contributed by Pleb - show ship model if the appropriate user default key has been set - Nikos 20140127 if (EXPECT_NOT([[NSUserDefaults standardUserDefaults] boolForKey:@"show-ship-model-in-status-screen"])) { [UNIVERSE removeDemoShips]; [self showShipModelWithKey:[self shipDataKey] shipData:nil personality:[self entityPersonalityInt] factorX:2.5 factorY:1.7 factorZ:8.0 inContext:@"GUI_SCREEN_STATUS"]; [self setShowDemoShips:YES]; } else { [self setShowDemoShips:NO]; } [UNIVERSE enterGUIViewModeWithMouseInteraction:NO]; if (guiChanged) { NSDictionary *fgDescriptor = nil, *bgDescriptor = nil; if ([self status] == STATUS_DOCKED) { fgDescriptor = [UNIVERSE screenTextureDescriptorForKey:@"docked_overlay"]; bgDescriptor = [UNIVERSE screenTextureDescriptorForKey:@"status_docked"]; } else { fgDescriptor = [UNIVERSE screenTextureDescriptorForKey:@"overlay"]; if (alertCondition == ALERT_CONDITION_RED) bgDescriptor = [UNIVERSE screenTextureDescriptorForKey:@"status_red_alert"]; else bgDescriptor = [UNIVERSE screenTextureDescriptorForKey:@"status_in_flight"]; } [gui setForegroundTextureDescriptor:fgDescriptor]; if (bgDescriptor == nil) bgDescriptor = [UNIVERSE screenTextureDescriptorForKey:@"status"]; [gui setBackgroundTextureDescriptor:bgDescriptor]; [gui setStatusPage:0]; [self noteGUIDidChangeFrom:oldScreen to:gui_screen]; } } - (NSArray *) equipmentList { GuiDisplayGen *gui = [UNIVERSE gui]; NSMutableArray *quip1 = [NSMutableArray array]; // damaged NSMutableArray *quip2 = [NSMutableArray array]; // working NSEnumerator *eqTypeEnum = nil; OOEquipmentType *eqType = nil; NSString *desc = nil; NSString *alldesc = nil; BOOL prioritiseDamaged = [[gui userSettings] oo_boolForKey:kGuiStatusPrioritiseDamaged defaultValue:YES]; for (eqTypeEnum = [OOEquipmentType reverseEquipmentEnumerator]; (eqType = [eqTypeEnum nextObject]); ) { if ([eqType isVisible]) { if ([eqType canCarryMultiple] && ![eqType isMissileOrMine]) { NSString *damagedIdentifier = [[eqType identifier] stringByAppendingString:@"_DAMAGED"]; NSUInteger count = 0, okcount = 0; okcount = [self countEquipmentItem:[eqType identifier]]; count = okcount + [self countEquipmentItem:damagedIdentifier]; if (count == 0) { // do nothing } // all items okay else if (count == okcount) { // only one installed display normally if (count == 1) { [quip2 addObject:[NSArray arrayWithObjects:[eqType name], [NSNumber numberWithBool:YES], nil]]; } // display plural form else { NSString *equipmentName = [eqType name]; alldesc = OOExpandKey(@"equipment-plural", count, equipmentName); [quip2 addObject:[NSArray arrayWithObjects:alldesc, [NSNumber numberWithBool:YES], nil]]; } } // all broken, only one installed else if (count == 1 && okcount == 0) { desc = [NSString stringWithFormat:DESC(@"equipment-@-not-available"), [eqType name]]; if (prioritiseDamaged) { [quip1 addObject:[NSArray arrayWithObjects:desc, [NSNumber numberWithBool:NO], nil]]; } else { [quip2 addObject:[NSArray arrayWithObjects:desc, [NSNumber numberWithBool:NO], nil]]; } } // some broken, multiple installed else { NSString *equipmentName = [eqType name]; alldesc = OOExpandKey(@"equipment-plural-some-na", okcount, count, equipmentName); if (prioritiseDamaged) { [quip1 addObject:[NSArray arrayWithObjects:alldesc, [NSNumber numberWithBool:NO], nil]]; } else { [quip2 addObject:[NSArray arrayWithObjects:alldesc, [NSNumber numberWithBool:NO], nil]]; } } } else if ([self hasEquipmentItem:[eqType identifier]]) { [quip2 addObject:[NSArray arrayWithObjects:[eqType name], [NSNumber numberWithBool:YES], nil]]; } else { // Check for damaged version if ([self hasEquipmentItem:[[eqType identifier] stringByAppendingString:@"_DAMAGED"]]) { desc = [NSString stringWithFormat:DESC(@"equipment-@-not-available"), [eqType name]]; if (prioritiseDamaged) { [quip1 addObject:[NSArray arrayWithObjects:desc, [NSNumber numberWithBool:NO], nil]]; } else { // just add in to the normal array [quip2 addObject:[NSArray arrayWithObjects:desc, [NSNumber numberWithBool:NO], nil]]; } } } } } if (max_passengers > 0) { desc = [NSString stringWithFormat:DESC_PLURAL(@"equipment-pass-berth-@", max_passengers), max_passengers]; [quip2 addObject:[NSArray arrayWithObjects:desc, [NSNumber numberWithBool:YES], nil]]; } if (!isWeaponNone(forward_weapon_type)) { desc = [NSString stringWithFormat:DESC(@"equipment-fwd-weapon-@"),[forward_weapon_type name]]; [quip2 addObject:[NSArray arrayWithObjects:desc, [NSNumber numberWithBool:YES], nil]]; } if (!isWeaponNone(aft_weapon_type)) { desc = [NSString stringWithFormat:DESC(@"equipment-aft-weapon-@"),[aft_weapon_type name]]; [quip2 addObject:[NSArray arrayWithObjects:desc, [NSNumber numberWithBool:YES], nil]]; } if (!isWeaponNone(port_weapon_type)) { desc = [NSString stringWithFormat:DESC(@"equipment-port-weapon-@"),[port_weapon_type name]]; [quip2 addObject:[NSArray arrayWithObjects:desc, [NSNumber numberWithBool:YES], nil]]; } if (!isWeaponNone(starboard_weapon_type)) { desc = [NSString stringWithFormat:DESC(@"equipment-stb-weapon-@"),[starboard_weapon_type name]]; [quip2 addObject:[NSArray arrayWithObjects:desc, [NSNumber numberWithBool:YES], nil]]; } // list damaged first, then working [quip1 addObjectsFromArray:quip2]; return quip1; } - (NSUInteger) primedEquipmentCount { return [eqScripts count]; } - (NSString *) primedEquipmentName:(NSInteger)offset { NSUInteger c = [self primedEquipmentCount]; NSUInteger idx = (primedEquipment+(c+1)+offset)%(c+1); if (idx == c) { return DESC(@"equipment-primed-none-hud-label"); } else { return [[OOEquipmentType equipmentTypeWithIdentifier:[[eqScripts oo_arrayAtIndex:idx] oo_stringAtIndex:0]] name]; } } - (void) activatePrimableEquipment:(NSUInteger)index withMode:(OOPrimedEquipmentMode)mode { // index == [eqScripts count] means we don't want to activate any equipment. if(index < [eqScripts count]) { OOJSScript *eqScript = [[eqScripts oo_arrayAtIndex:index] objectAtIndex:1]; JSContext *context = OOJSAcquireContext(); NSAssert1(mode <= OOPRIMEDEQUIP_MODE, @"Primable equipment mode %i out of range", (int)mode); switch (mode) { case OOPRIMEDEQUIP_MODE: [eqScript callMethod:OOJSID("mode") inContext:context withArguments:NULL count:0 result:NULL]; break; case OOPRIMEDEQUIP_ACTIVATED: [eqScript callMethod:OOJSID("activated") inContext:context withArguments:NULL count:0 result:NULL]; break; } OOJSRelinquishContext(context); } } - (NSString *) fastEquipmentA { return _fastEquipmentA; } - (NSString *) fastEquipmentB { return _fastEquipmentB; } - (void) setFastEquipmentA:(NSString *)eqKey { [_fastEquipmentA release]; _fastEquipmentA = [eqKey copy]; } - (void) setFastEquipmentB:(NSString *)eqKey { [_fastEquipmentB release]; _fastEquipmentB = [eqKey copy]; } - (OOEquipmentType *) weaponTypeForFacing:(OOWeaponFacing)facing strict:(BOOL)strict { OOWeaponType weaponType = nil; switch (facing) { case WEAPON_FACING_FORWARD: weaponType = forward_weapon_type; break; case WEAPON_FACING_AFT: weaponType = aft_weapon_type; break; case WEAPON_FACING_PORT: weaponType = port_weapon_type; break; case WEAPON_FACING_STARBOARD: weaponType = starboard_weapon_type; break; case WEAPON_FACING_NONE: break; } return weaponType; } - (NSArray *) missilesList { [self tidyMissilePylons]; // just in case. return [super missilesList]; } - (NSArray *) cargoList { NSMutableArray *manifest = [NSMutableArray array]; NSArray *list = [self cargoListForScripting]; NSEnumerator *cargoEnum = nil; NSDictionary *commodity; if (specialCargo) [manifest addObject:specialCargo]; for (cargoEnum = [list objectEnumerator]; (commodity = [cargoEnum nextObject]); ) { NSInteger quantity = [commodity oo_integerForKey:@"quantity"]; NSString *units = [commodity oo_stringForKey:@"unit"]; NSString *commodityName = [commodity oo_stringForKey:@"displayName"]; NSInteger containers = [commodity oo_intForKey:@"containers"]; BOOL extended = ![units isEqualToString:DESC(@"cargo-tons-symbol")] && containers > 0; if (extended) { [manifest addObject:OOExpandKey(@"manifest-cargo-quantity-extended", quantity, units, commodityName, containers)]; } else { [manifest addObject:OOExpandKey(@"manifest-cargo-quantity", quantity, units, commodityName)]; } } return manifest; } - (NSArray *) cargoListForScripting { NSMutableArray *list = [NSMutableArray array]; NSUInteger i, j, commodityCount = [shipCommodityData count]; OOCargoQuantity quantityInHold[commodityCount]; OOCargoQuantity containersInHold[commodityCount]; NSArray *goods = [shipCommodityData goods]; // following changed to work whether docked or not for (i = 0; i < commodityCount; i++) { quantityInHold[i] = [shipCommodityData quantityForGood:[goods oo_stringAtIndex:i]]; containersInHold[i] = 0; } for (i = 0; i < [cargo count]; i++) { ShipEntity *container = [cargo objectAtIndex:i]; j = [goods indexOfObject:[container commodityType]]; quantityInHold[j] += [container commodityAmount]; ++containersInHold[j]; } for (i = 0; i < commodityCount; i++) { if (quantityInHold[i] > 0) { NSMutableDictionary *commodity = [NSMutableDictionary dictionaryWithCapacity:4]; NSString *symName = [goods oo_stringAtIndex:i]; // commodity, quantity - keep consistency between .manifest and .contracts [commodity setObject:symName forKey:@"commodity"]; [commodity setObject:[NSNumber numberWithUnsignedInt:quantityInHold[i]] forKey:@"quantity"]; [commodity setObject:[NSNumber numberWithUnsignedInt:containersInHold[i]] forKey:@"containers"]; [commodity setObject:[shipCommodityData nameForGood:symName] forKey:@"displayName"]; [commodity setObject:DisplayStringForMassUnitForCommodity(symName) forKey:@"unit"]; [list addObject:commodity]; } } return [[list copy] autorelease]; // return an immutable copy } // determines general export legality, not tied to a station - (unsigned) legalStatusOfCargoList { NSString *good = nil; OOCargoQuantity amount; unsigned penalty = 0; foreach (good, [shipCommodityData goods]) { amount = [shipCommodityData quantityForGood:good]; penalty += [shipCommodityData exportLegalityForGood:good] * amount; } return penalty; } - (NSArray*) contractsListForScriptingFromArray:(NSArray *) contracts_array forCargo:(BOOL)forCargo { NSMutableArray *result = [NSMutableArray array]; NSUInteger i; for (i = 0; i < [contracts_array count]; i++) { NSMutableDictionary *contract = [NSMutableDictionary dictionaryWithCapacity:10]; NSDictionary *dict = [contracts_array oo_dictionaryAtIndex:i]; if (forCargo) { // commodity, quantity - keep consistency between .manifest and .contracts [contract setObject:[dict oo_stringForKey:CARGO_KEY_TYPE] forKey:@"commodity"]; [contract setObject:[NSNumber numberWithUnsignedInt:[dict oo_intForKey:CARGO_KEY_AMOUNT]] forKey:@"quantity"]; [contract setObject:[dict oo_stringForKey:CARGO_KEY_DESCRIPTION] forKey:@"description"]; } else { [contract setObject:[dict oo_stringForKey:PASSENGER_KEY_NAME] forKey:PASSENGER_KEY_NAME]; [contract setObject:[NSNumber numberWithUnsignedInt:[dict oo_unsignedIntForKey:CONTRACT_KEY_RISK]] forKey:CONTRACT_KEY_RISK]; } OOSystemID planet = [dict oo_intForKey:CONTRACT_KEY_DESTINATION]; NSString *planetName = [UNIVERSE getSystemName:planet]; [contract setObject:[NSNumber numberWithUnsignedInt:planet] forKey:CONTRACT_KEY_DESTINATION]; [contract setObject:planetName forKey:@"destinationName"]; planet = [dict oo_intForKey:CONTRACT_KEY_START]; planetName = [UNIVERSE getSystemName: planet]; [contract setObject:[NSNumber numberWithUnsignedInt:planet] forKey:CONTRACT_KEY_START]; [contract setObject:planetName forKey:@"startName"]; int dest_eta = [dict oo_doubleForKey:CONTRACT_KEY_ARRIVAL_TIME] - ship_clock; [contract setObject:[NSNumber numberWithInt:dest_eta] forKey:@"eta"]; [contract setObject:[UNIVERSE shortTimeDescription:dest_eta] forKey:@"etaDescription"]; [contract setObject:[NSNumber numberWithInt:[dict oo_intForKey:CONTRACT_KEY_PREMIUM]] forKey:CONTRACT_KEY_PREMIUM]; [contract setObject:[NSNumber numberWithInt:[dict oo_intForKey:CONTRACT_KEY_FEE]] forKey:CONTRACT_KEY_FEE]; [result addObject:contract]; } return [[result copy] autorelease]; // return an immutable copy } - (NSArray *) passengerListForScripting { return [self contractsListForScriptingFromArray:passengers forCargo:NO]; } - (NSArray *) parcelListForScripting { return [self contractsListForScriptingFromArray:parcels forCargo:NO]; } - (NSArray *) contractListForScripting { return [self contractsListForScriptingFromArray:contracts forCargo:YES]; } - (void) setGuiToSystemDataScreen { NSDictionary *targetSystemData; NSString *targetSystemName; targetSystemData = [[UNIVERSE generateSystemData:target_system_id] retain]; // retained targetSystemName = [targetSystemData oo_stringForKey:KEY_NAME]; BOOL sunGoneNova = ([targetSystemData oo_boolForKey:@"sun_gone_nova"]); OOGUIScreenID oldScreen = gui_screen; GuiDisplayGen *gui = [UNIVERSE gui]; gui_screen = GUI_SCREEN_SYSTEM_DATA; BOOL guiChanged = (oldScreen != gui_screen); Random_Seed targetSystemRandomSeed = [[UNIVERSE systemManager] getRandomSeedForSystem:target_system_id inGalaxy:[self galaxyNumber]]; [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:NO]; // GUI stuff { OOGUITabSettings tab_stops; tab_stops[0] = 0; tab_stops[1] = 96; tab_stops[2] = 144; [gui overrideTabs:tab_stops from:kGuiSystemdataTabs length:3]; [gui setTabStops:tab_stops]; NSUInteger techLevel = [targetSystemData oo_intForKey:KEY_TECHLEVEL] + 1; int population = [targetSystemData oo_intForKey:KEY_POPULATION]; int productivity = [targetSystemData oo_intForKey:KEY_PRODUCTIVITY]; int radius = [targetSystemData oo_intForKey:KEY_RADIUS]; NSString *government_desc = [targetSystemData oo_stringForKey:KEY_GOVERNMENT_DESC defaultValue:OODisplayStringFromGovernmentID([targetSystemData oo_intForKey:KEY_GOVERNMENT])]; NSString *economy_desc = [targetSystemData oo_stringForKey:KEY_ECONOMY_DESC defaultValue:OODisplayStringFromEconomyID([targetSystemData oo_intForKey:KEY_ECONOMY])]; NSString *inhabitants = [targetSystemData oo_stringForKey:KEY_INHABITANTS]; NSString *system_desc = [targetSystemData oo_stringForKey:KEY_DESCRIPTION]; NSString *populationDesc = [targetSystemData oo_stringForKey:KEY_POPULATION_DESC defaultValue:OOExpandKeyWithSeed(kNilRandomSeed, @"sysdata-pop-value", population, inhabitants)]; if (sunGoneNova) { population = 0; productivity = 0; radius = 0; techLevel = 0; government_desc = OOExpandKeyWithSeed(targetSystemRandomSeed, @"nova-system-government"); economy_desc = OOExpandKeyWithSeed(targetSystemRandomSeed, @"nova-system-economy"); inhabitants = OOExpandKeyWithSeed(targetSystemRandomSeed, @"nova-system-inhabitants"); system_desc = OOExpandKeyWithSeed(targetSystemRandomSeed, @"nova-system-description"); populationDesc = OOExpandKeyWithSeed(targetSystemRandomSeed, @"sysdata-pop-value", population, inhabitants); } [gui clearAndKeepBackground:!guiChanged]; [UNIVERSE removeDemoShips]; { NSString *system = targetSystemName; [gui setTitle:OOExpandKeyWithSeed(targetSystemRandomSeed, @"sysdata-data-on-system", system)]; } NSArray *populationDescLines = [populationDesc componentsSeparatedByString:@"\n"]; NSString *populationDesc1 = [populationDescLines objectAtIndex:0]; NSString *populationDesc2 = [populationDescLines lastObject]; [gui setArray:[NSArray arrayWithObjects: OOExpandKeyWithSeed(targetSystemRandomSeed, @"sysdata-eco"), economy_desc, nil] forRow:1]; [gui setArray:[NSArray arrayWithObjects: OOExpandKeyWithSeed(targetSystemRandomSeed, @"sysdata-govt"), government_desc, nil] forRow:3]; [gui setArray:[NSArray arrayWithObjects: OOExpandKeyWithSeed(targetSystemRandomSeed, @"sysdata-tl"), OOExpandKeyWithSeed(targetSystemRandomSeed, @"sysdata-tl-value", techLevel), nil] forRow:5]; [gui setArray:[NSArray arrayWithObjects: OOExpandKeyWithSeed(targetSystemRandomSeed, @"sysdata-pop"), populationDesc1, nil] forRow:7]; [gui setArray:[NSArray arrayWithObjects:@"", populationDesc2, nil] forRow:8]; [gui setArray:[NSArray arrayWithObjects: OOExpandKeyWithSeed(targetSystemRandomSeed, @"sysdata-prod"), @"", OOExpandKeyWithSeed(targetSystemRandomSeed, @"sysdata-prod-value", productivity), nil] forRow:10]; [gui setArray:[NSArray arrayWithObjects: OOExpandKeyWithSeed(targetSystemRandomSeed, @"sysdata-radius"), @"", OOExpandKeyWithSeed(targetSystemRandomSeed, @"sysdata-radius-value", radius), nil] forRow:12]; OOGUIRow i = [gui addLongText:system_desc startingAtRow:15 align:GUI_ALIGN_LEFT]; missionTextRow = i; for (i-- ; i > 14 ; --i) { [gui setColor:[gui colorFromSetting:kGuiSystemdataDescriptionColor defaultValue:[OOColor greenColor]] forRow:i]; } for (i = 1 ; i <= 12 ; ++i) { // nil default = fall back to global default colour [gui setColor:[gui colorFromSetting:kGuiSystemdataFactsColor defaultValue:nil] forRow:i]; } [gui setShowTextCursor:NO]; } /* ends */ [lastTextKey release]; lastTextKey = nil; [[UNIVERSE gameView] clearMouse]; [targetSystemData release]; [self setShowDemoShips:NO]; [UNIVERSE enterGUIViewModeWithMouseInteraction:NO]; // if the system has gone nova, there's no planet to display if (!sunGoneNova) { // The next code is generating the miniature planets. // When normal planets are displayed, the PRNG is reset. This happens not with procedural planet display. RANROTSeed ranrotSavedSeed = RANROTGetFullSeed(); RNG_Seed saved_seed = currentRandomSeed(); if (target_system_id == system_id) { [self setBackgroundFromDescriptionsKey:@"gui-scene-show-local-planet"]; } else { [self setBackgroundFromDescriptionsKey:@"gui-scene-show-planet"]; } setRandomSeed(saved_seed); RANROTSetFullSeed(ranrotSavedSeed); } if (guiChanged) { [gui setForegroundTextureKey:[self status] == STATUS_DOCKED ? @"docked_overlay" : @"overlay"]; [gui setBackgroundTextureKey:sunGoneNova ? @"system_data_nova" : @"system_data"]; [self noteGUIDidChangeFrom:oldScreen to:gui_screen]; [self checkScript]; // Still needed by some OXPs? } } - (void) prepareMarkedDestination:(NSMutableDictionary *)markers :(NSDictionary *)marker { NSNumber *key = [NSNumber numberWithInt:[marker oo_intForKey:@"system"]]; NSMutableArray *list = [markers objectForKey:key]; if (list == nil) { list = [NSMutableArray arrayWithObject:marker]; } else { [list addObject:marker]; } [markers setObject:list forKey:key]; } - (NSDictionary *) markedDestinations { // get a list of systems marked as contract destinations NSMutableDictionary *destinations = [NSMutableDictionary dictionaryWithCapacity:256]; unsigned i; OOSystemID sysid; NSDictionary *marker; for (i = 0; i < [passengers count]; i++) { sysid = [[passengers oo_dictionaryAtIndex:i] oo_unsignedCharForKey:CONTRACT_KEY_DESTINATION]; marker = [self passengerContractMarker:sysid]; [self prepareMarkedDestination:destinations:marker]; } for (i = 0; i < [parcels count]; i++) { sysid = [[parcels oo_dictionaryAtIndex:i] oo_unsignedCharForKey:CONTRACT_KEY_DESTINATION]; marker = [self parcelContractMarker:sysid]; [self prepareMarkedDestination:destinations:marker]; } for (i = 0; i < [contracts count]; i++) { sysid = [[contracts oo_dictionaryAtIndex:i] oo_unsignedCharForKey:CONTRACT_KEY_DESTINATION]; marker = [self cargoContractMarker:sysid]; [self prepareMarkedDestination:destinations:marker]; } NSEnumerator *keyEnum = nil; NSString *key = nil; for (keyEnum = [missionDestinations keyEnumerator]; (key = [keyEnum nextObject]); ) { marker = [missionDestinations objectForKey:key]; [self prepareMarkedDestination:destinations:marker]; } return destinations; } - (void) setGuiToLongRangeChartScreen { OOGUIScreenID oldScreen = gui_screen; GuiDisplayGen *gui = [UNIVERSE gui]; [gui clearAndKeepBackground:NO]; [gui setBackgroundTextureKey:@"short_range_chart"]; gui_screen = GUI_SCREEN_LONG_RANGE_CHART; target_chart_zoom = CHART_MAX_ZOOM; [self setGuiToChartScreenFrom: oldScreen]; } - (void) setGuiToShortRangeChartScreen { OOGUIScreenID oldScreen = gui_screen; GuiDisplayGen *gui = [UNIVERSE gui]; [gui clearAndKeepBackground:NO]; [gui setBackgroundTextureKey:@"short_range_chart"]; gui_screen = GUI_SCREEN_SHORT_RANGE_CHART; [self setGuiToChartScreenFrom: oldScreen]; } - (void) setGuiToChartScreenFrom: (OOGUIScreenID) oldScreen { GuiDisplayGen *gui = [UNIVERSE gui]; BOOL guiChanged = (oldScreen != gui_screen); [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:YES]; target_system_id = [UNIVERSE findSystemAtCoords:cursor_coordinates withGalaxy:galaxy_number]; [UNIVERSE preloadPlanetTexturesForSystem:target_system_id]; // GUI stuff { //[gui clearAndKeepBackground:!guiChanged]; [gui setStarChartTitle]; // refresh the short range chart cache, in case we've just loaded a save game with different local overrides, etc. [gui refreshStarChart]; //[gui setText:targetSystemName forRow:19]; // distance-f & est-travel-time-f are identical between short & long range charts in standard Oolite, however can be alterered separately via OXPs //[gui setText:OOExpandKey(@"short-range-chart-distance", distance) forRow:20]; //NSString *travelTimeRow = @""; //if ([self hasHyperspaceMotor] && distance > 0.0 && distance * 10.0 <= fuel) //{ // double time = estimatedTravelTime; // travelTimeRow = OOExpandKey(@"short-range-chart-est-travel-time", time); //} //[gui setText:travelTimeRow forRow:21]; if (gui_screen == GUI_SCREEN_LONG_RANGE_CHART) { NSString *displaySearchString = planetSearchString ? [planetSearchString capitalizedString] : (NSString *)@""; [gui setText:[NSString stringWithFormat:DESC(@"long-range-chart-find-planet-@"), displaySearchString] forRow:GUI_ROW_PLANET_FINDER]; [gui setColor:[OOColor cyanColor] forRow:GUI_ROW_PLANET_FINDER]; [gui setShowTextCursor:YES]; [gui setCurrentRow:GUI_ROW_PLANET_FINDER]; } else { [gui setShowTextCursor:NO]; } } /* ends */ [self setShowDemoShips:NO]; [UNIVERSE enterGUIViewModeWithMouseInteraction:YES]; if (guiChanged) { [gui setForegroundTextureKey:[self status] == STATUS_DOCKED ? @"docked_overlay" : @"overlay"]; [gui setBackgroundTextureKey:@"short_range_chart"]; if (found_system_id >= 0) { [UNIVERSE findSystemCoordinatesWithPrefix:[[UNIVERSE getSystemName:found_system_id] lowercaseString] exactMatch:YES]; } [self noteGUIDidChangeFrom:oldScreen to:gui_screen]; } } static NSString *SliderString(NSInteger amountIn20ths) { NSString *filledSlider = [@"|||||||||||||||||||||||||" substringToIndex:amountIn20ths]; NSString *emptySlider = [@"........................." substringToIndex:20 - amountIn20ths]; return [NSString stringWithFormat:@"%@%@", filledSlider, emptySlider]; } - (void) setGuiToGameOptionsScreen { MyOpenGLView *gameView = [UNIVERSE gameView]; [[UNIVERSE gameView] clearMouse]; [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:YES]; // GUI stuff { GuiDisplayGen* gui = [UNIVERSE gui]; GUI_ROW_INIT(gui); int first_sel_row = GUI_FIRST_ROW(GAME)-4; // repositioned menu [gui clear]; [gui setTitle:[NSString stringWithFormat:DESC(@"status-commander-@"), [self commanderName]]]; // Same title as status screen. #if OO_RESOLUTION_OPTION GameController *controller = [UNIVERSE gameController]; NSUInteger displayModeIndex = [controller indexOfCurrentDisplayMode]; if (displayModeIndex == NSNotFound) { OOLogWARN(@"display.currentMode.notFound", @"couldn't find current fullscreen setting, switching to default."); displayModeIndex = 0; } NSArray *modeList = [controller displayModes]; NSDictionary *mode = nil; if ([modeList count]) { mode = [modeList objectAtIndex:displayModeIndex]; } if (mode == nil) return; // Got a better idea? unsigned modeWidth = [mode oo_unsignedIntForKey:kOODisplayWidth]; unsigned modeHeight = [mode oo_unsignedIntForKey:kOODisplayHeight]; float modeRefresh = [mode oo_floatForKey:kOODisplayRefreshRate]; BOOL runningOnPrimaryDisplayDevice = [gameView isRunningOnPrimaryDisplayDevice]; #if OOLITE_WINDOWS if (!runningOnPrimaryDisplayDevice) { MONITORINFOEX mInfo = [gameView currentMonitorInfo]; modeWidth = mInfo.rcMonitor.right - mInfo.rcMonitor.left; modeHeight = mInfo.rcMonitor.bottom - mInfo.rcMonitor.top; } #endif NSString *displayModeString = [self screenModeStringForWidth:modeWidth height:modeHeight refreshRate:modeRefresh]; [gui setText:displayModeString forRow:GUI_ROW(GAME,DISPLAY) align:GUI_ALIGN_CENTER]; if (runningOnPrimaryDisplayDevice) { [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,DISPLAY)]; } else { [gui setColor:[OOColor grayColor] forRow:GUI_ROW(GAME,DISPLAY)]; } #endif // OO_RESOLUTIOM_OPTION if ([UNIVERSE autoSave]) [gui setText:DESC(@"gameoptions-autosave-yes") forRow:GUI_ROW(GAME,AUTOSAVE) align:GUI_ALIGN_CENTER]; else [gui setText:DESC(@"gameoptions-autosave-no") forRow:GUI_ROW(GAME,AUTOSAVE) align:GUI_ALIGN_CENTER]; [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,AUTOSAVE)]; // volume control if ([OOSound respondsToSelector:@selector(masterVolume)]) { double volume = 100.0 * [OOSound masterVolume]; int vol = (volume / 5.0 + 0.5); // avoid rounding errors NSString* soundVolumeWordDesc = DESC(@"gameoptions-sound-volume"); if (vol > 0) [gui setText:[NSString stringWithFormat:@"%@%@ ", soundVolumeWordDesc, SliderString(vol)] forRow:GUI_ROW(GAME,VOLUME) align:GUI_ALIGN_CENTER]; else [gui setText:DESC(@"gameoptions-sound-volume-mute") forRow:GUI_ROW(GAME,VOLUME) align:GUI_ALIGN_CENTER]; [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,VOLUME)]; } else { [gui setText:DESC(@"gameoptions-volume-external-only") forRow:GUI_ROW(GAME,VOLUME) align:GUI_ALIGN_CENTER]; [gui setColor:[OOColor grayColor] forRow:GUI_ROW(GAME,VOLUME)]; } #if OOLITE_SDL // gamma control float gamma = [gameView gammaValue]; int gamma5 = (gamma * 5); NSString* gammaWordDesc = DESC(@"gameoptions-gamma-value"); [gui setText:[NSString stringWithFormat:@"%@%@ (%.1f) ", gammaWordDesc, SliderString(gamma5), gamma] forRow:GUI_ROW(GAME,GAMMA) align:GUI_ALIGN_CENTER]; [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,GAMMA)]; #endif // field of view control float fov = [gameView fov:NO]; int fovTicks = (int)((fov - MIN_FOV_DEG) * 20 / (MAX_FOV_DEG - MIN_FOV_DEG)); NSString* fovWordDesc = DESC(@"gameoptions-fov-value"); [gui setText:[NSString stringWithFormat:@"%@%@ (%d%c) ", fovWordDesc, SliderString(fovTicks), (int)fov, 176 /*176 is the degrees symbol Unicode code point*/] forRow:GUI_ROW(GAME,FOV) align:GUI_ALIGN_CENTER]; [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,FOV)]; #if OOLITE_SPEECH_SYNTH // Speech control switch (isSpeechOn) { case OOSPEECHSETTINGS_OFF: [gui setText:DESC(@"gameoptions-spoken-messages-no") forRow:GUI_ROW(GAME,SPEECH) align:GUI_ALIGN_CENTER]; break; case OOSPEECHSETTINGS_COMMS: [gui setText:DESC(@"gameoptions-spoken-messages-comms") forRow:GUI_ROW(GAME,SPEECH) align:GUI_ALIGN_CENTER]; break; case OOSPEECHSETTINGS_ALL: [gui setText:DESC(@"gameoptions-spoken-messages-yes") forRow:GUI_ROW(GAME,SPEECH) align:GUI_ALIGN_CENTER]; break; } [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,SPEECH)]; #if OOLITE_ESPEAK { NSString *voiceName = [UNIVERSE voiceName:voice_no]; NSString *message = OOExpandKey(@"gameoptions-voice-name", voiceName); [gui setText:message forRow:GUI_ROW(GAME,SPEECH_LANGUAGE) align:GUI_ALIGN_CENTER]; [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,SPEECH_LANGUAGE)]; message = [NSString stringWithFormat:DESC(voice_gender_m ? @"gameoptions-voice-M" : @"gameoptions-voice-F")]; [gui setText:message forRow:GUI_ROW(GAME,SPEECH_GENDER) align:GUI_ALIGN_CENTER]; [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,SPEECH_GENDER)]; } #endif #endif #if !OOLITE_MAC_OS_X // window/fullscreen if([gameView inFullScreenMode]) { [gui setText:DESC(@"gameoptions-play-in-window") forRow:GUI_ROW(GAME,DISPLAYSTYLE) align:GUI_ALIGN_CENTER]; } else { [gui setText:DESC(@"gameoptions-play-in-fullscreen") forRow:GUI_ROW(GAME,DISPLAYSTYLE) align:GUI_ALIGN_CENTER]; } [gui setKey: GUI_KEY_OK forRow: GUI_ROW(GAME,DISPLAYSTYLE)]; #endif [gui setText:DESC(@"gameoptions-joystick-configuration") forRow: GUI_ROW(GAME,STICKMAPPER) align: GUI_ALIGN_CENTER]; if ([[OOJoystickManager sharedStickHandler] joystickCount]) { [gui setKey: GUI_KEY_OK forRow: GUI_ROW(GAME,STICKMAPPER)]; } else { [gui setColor:[OOColor grayColor] forRow:GUI_ROW(GAME,STICKMAPPER)]; } [gui setText:DESC(@"gameoptions-keyboard-configuration") forRow: GUI_ROW(GAME,KEYMAPPER) align: GUI_ALIGN_CENTER]; [gui setKey: GUI_KEY_OK forRow: GUI_ROW(GAME,KEYMAPPER)]; NSString *musicMode = [UNIVERSE descriptionForArrayKey:@"music-mode" index:[[OOMusicController sharedController] mode]]; NSString *message = OOExpandKey(@"gameoptions-music-mode", musicMode); [gui setText:message forRow:GUI_ROW(GAME,MUSIC) align:GUI_ALIGN_CENTER]; [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,MUSIC)]; if ([UNIVERSE wireframeGraphics]) [gui setText:DESC(@"gameoptions-wireframe-graphics-yes") forRow:GUI_ROW(GAME,WIREFRAMEGRAPHICS) align:GUI_ALIGN_CENTER]; else [gui setText:DESC(@"gameoptions-wireframe-graphics-no") forRow:GUI_ROW(GAME,WIREFRAMEGRAPHICS) align:GUI_ALIGN_CENTER]; [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,WIREFRAMEGRAPHICS)]; #if !NEW_PLANETS if ([UNIVERSE doProcedurallyTexturedPlanets]) [gui setText:DESC(@"gameoptions-procedurally-textured-planets-yes") forRow:GUI_ROW(GAME,PROCEDURALLYTEXTUREDPLANETS) align:GUI_ALIGN_CENTER]; else [gui setText:DESC(@"gameoptions-procedurally-textured-planets-no") forRow:GUI_ROW(GAME,PROCEDURALLYTEXTUREDPLANETS) align:GUI_ALIGN_CENTER]; [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,PROCEDURALLYTEXTUREDPLANETS)]; #endif OOGraphicsDetail detailLevel = [UNIVERSE detailLevel]; NSString *shaderEffectsOptionsString = OOExpand(@"gameoptions-detaillevel-[detailLevel]", detailLevel); [gui setText:OOExpandKey(shaderEffectsOptionsString) forRow:GUI_ROW(GAME,SHADEREFFECTS) align:GUI_ALIGN_CENTER]; [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,SHADEREFFECTS)]; if ([UNIVERSE dockingClearanceProtocolActive]) { [gui setText:DESC(@"gameoptions-docking-clearance-yes") forRow:GUI_ROW(GAME,DOCKINGCLEARANCE) align:GUI_ALIGN_CENTER]; } else { [gui setText:DESC(@"gameoptions-docking-clearance-no") forRow:GUI_ROW(GAME,DOCKINGCLEARANCE) align:GUI_ALIGN_CENTER]; } [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,DOCKINGCLEARANCE)]; // Back menu option [gui setText:DESC(@"gui-back") forRow:GUI_ROW(GAME,BACK) align:GUI_ALIGN_CENTER]; [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,BACK)]; [gui setSelectableRange:NSMakeRange(first_sel_row, GUI_ROW_GAMEOPTIONS_END_OF_LIST)]; [gui setSelectedRow: first_sel_row]; [gui setShowTextCursor:NO]; [gui setForegroundTextureKey:[self status] == STATUS_DOCKED ? @"docked_overlay" : @"paused_overlay"]; [gui setBackgroundTextureKey:@"settings"]; } /* ends */ [self setShowDemoShips:NO]; gui_screen = GUI_SCREEN_GAMEOPTIONS; [self setShowDemoShips:NO]; [UNIVERSE enterGUIViewModeWithMouseInteraction:YES]; } - (void) setGuiToLoadSaveScreen { BOOL canLoadOrSave = NO; MyOpenGLView *gameView = [UNIVERSE gameView]; OOGUIScreenID oldScreen = gui_screen; [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:YES]; if ([self status] == STATUS_DOCKED) { if ([self dockedStation] == nil) [self setDockedAtMainStation]; canLoadOrSave = (([self dockedStation] == [UNIVERSE station] || [[self dockedStation] allowsSaving]) && !([[UNIVERSE sun] goneNova] || [[UNIVERSE sun] willGoNova])); } BOOL canQuickSave = (canLoadOrSave && ([[gameView gameController] playerFileToLoad] != nil)); // GUI stuff { GuiDisplayGen* gui = [UNIVERSE gui]; GUI_ROW_INIT(gui); int first_sel_row = (canLoadOrSave)? GUI_ROW(,SAVE) : GUI_ROW(,GAMEOPTIONS); if (canQuickSave) first_sel_row = GUI_ROW(,QUICKSAVE); [gui clear]; [gui setTitle:[NSString stringWithFormat:DESC(@"status-commander-@"), [self commanderName]]]; //Same title as status screen. [gui setText:DESC(@"options-quick-save") forRow:GUI_ROW(,QUICKSAVE) align:GUI_ALIGN_CENTER]; if (canQuickSave) [gui setKey:GUI_KEY_OK forRow:GUI_ROW(,QUICKSAVE)]; else [gui setColor:[OOColor grayColor] forRow:GUI_ROW(,QUICKSAVE)]; [gui setText:DESC(@"options-save-commander") forRow:GUI_ROW(,SAVE) align:GUI_ALIGN_CENTER]; [gui setText:DESC(@"options-load-commander") forRow:GUI_ROW(,LOAD) align:GUI_ALIGN_CENTER]; if (canLoadOrSave) { [gui setKey:GUI_KEY_OK forRow:GUI_ROW(,SAVE)]; [gui setKey:GUI_KEY_OK forRow:GUI_ROW(,LOAD)]; } else { [gui setColor:[OOColor grayColor] forRow:GUI_ROW(,SAVE)]; [gui setColor:[OOColor grayColor] forRow:GUI_ROW(,LOAD)]; } [gui setText:DESC(@"options-return-to-menu") forRow:GUI_ROW(,BEGIN_NEW) align:GUI_ALIGN_CENTER]; [gui setKey:GUI_KEY_OK forRow:GUI_ROW(,BEGIN_NEW)]; [gui setText:DESC(@"options-game-options") forRow:GUI_ROW(,GAMEOPTIONS) align:GUI_ALIGN_CENTER]; [gui setKey:GUI_KEY_OK forRow:GUI_ROW(,GAMEOPTIONS)]; #if OOLITE_SDL // GNUstep needs a quit option at present (no Cmd-Q) but // doesn't need speech. // quit menu option [gui setText:DESC(@"options-exit-game") forRow:GUI_ROW(,QUIT) align:GUI_ALIGN_CENTER]; [gui setKey:GUI_KEY_OK forRow:GUI_ROW(,QUIT)]; #endif [gui setSelectableRange:NSMakeRange(first_sel_row, GUI_ROW_OPTIONS_END_OF_LIST)]; if ([[UNIVERSE gameController] isGamePaused] || (!canLoadOrSave && [self status] == STATUS_DOCKED)) { [gui setSelectedRow: GUI_ROW(,GAMEOPTIONS)]; } else { [gui setSelectedRow: first_sel_row]; } [gui setShowTextCursor:NO]; if ([gui setForegroundTextureKey:[self status] == STATUS_DOCKED ? @"docked_overlay" : @"paused_overlay"] && [UNIVERSE pauseMessageVisible]) [[UNIVERSE messageGUI] clear]; // Graphically, this screen is analogous to the various settings screens [gui setBackgroundTextureKey:@"settings"]; } /* ends */ [[UNIVERSE gameView] clearMouse]; [self setShowDemoShips:NO]; gui_screen = GUI_SCREEN_OPTIONS; [self setShowDemoShips:NO]; [UNIVERSE enterGUIViewModeWithMouseInteraction:YES]; [self noteGUIDidChangeFrom:oldScreen to:gui_screen]; } static NSString *last_outfitting_key=nil; - (void) highlightEquipShipScreenKey:(NSString *)key { int i=0; OOGUIRow row; NSString *otherKey = @""; GuiDisplayGen *gui = [UNIVERSE gui]; [last_outfitting_key release]; last_outfitting_key = [key copy]; [self setGuiToEquipShipScreen:-1]; key = last_outfitting_key; // TODO: redo the equipShipScreen in a way that isn't broken. this whole method 'works' // based on the way setGuiToEquipShipScreen 'worked' on 20090913 - Kaks // setGuiToEquipShipScreen doesn't take a page number, it takes an offset from the beginning // of the dictionary, the first line will show the key at that offset... // try the last page first - 10 pages max. while (otherKey) { [self setGuiToEquipShipScreen:i]; for (row = GUI_ROW_EQUIPMENT_START;row<=GUI_MAX_ROWS_EQUIPMENT+2;row++) { otherKey = [gui keyForRow:row]; if (!otherKey) { [self setGuiToEquipShipScreen:0]; return; } if ([otherKey isEqualToString:key]) { [gui setSelectedRow:row]; [self showInformationForSelectedUpgrade]; return; } } if ([otherKey hasPrefix:@"More:"]) { i = [[otherKey componentsSeparatedByString:@":"] oo_intAtIndex:1]; } else { [self setGuiToEquipShipScreen:0]; return; } } } - (OOWeaponFacingSet) availableFacings { OOShipRegistry *registry = [OOShipRegistry sharedRegistry]; NSDictionary *shipyardInfo = [registry shipyardInfoForKey:[self shipDataKey]]; unsigned available_facings = [shipyardInfo oo_unsignedIntForKey:KEY_WEAPON_FACINGS defaultValue:[self weaponFacings]]; // use defaults explicitly return available_facings & VALID_WEAPON_FACINGS; } - (void) setGuiToEquipShipScreen:(int)skipParam selectingFacingFor:(NSString *)eqKeyForSelectFacing { [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:YES]; missiles = [self countMissiles]; OOEntityStatus searchStatus; // use STATUS_TEST, STATUS_DEAD & STATUS_ACTIVE NSString *showKey = nil; unsigned skip; if (skipParam < 0) { skip = 0; searchStatus = STATUS_TEST; } else { skip = skipParam; searchStatus = STATUS_ACTIVE; } // don't show a "Back" item if we're only skipping one item - just show the item if (skip == 1) skip = 0; double priceFactor = 1.0; OOTechLevelID techlevel = [[UNIVERSE currentSystemData] oo_intForKey:KEY_TECHLEVEL]; StationEntity *dockedStation = [self dockedStation]; if (dockedStation) { priceFactor = [dockedStation equipmentPriceFactor]; if ([dockedStation equivalentTechLevel] != NSNotFound) techlevel = [dockedStation equivalentTechLevel]; } // build an array of all equipment - and take away that which has been bought (or is not permitted) NSMutableArray *equipmentAllowed = [NSMutableArray array]; // find options that agree with this ship OOShipRegistry *registry = [OOShipRegistry sharedRegistry]; NSDictionary *shipyardInfo = [registry shipyardInfoForKey:[self shipDataKey]]; NSMutableSet *options = [NSMutableSet setWithArray:[shipyardInfo oo_arrayForKey:KEY_OPTIONAL_EQUIPMENT]]; // add standard items too! [options addObjectsFromArray:[[shipyardInfo oo_dictionaryForKey:KEY_STANDARD_EQUIPMENT] oo_arrayForKey:KEY_EQUIPMENT_EXTRAS]]; unsigned i = 0; NSEnumerator *eqEnum = nil; OOEquipmentType *eqType = nil; unsigned available_facings = [shipyardInfo oo_unsignedIntForKey:KEY_WEAPON_FACINGS defaultValue:[self weaponFacings]]; // use defaults explicitly if (eqKeyForSelectFacing != nil) // Weapons purchase subscreen. { skip = 1; // show the back button // The 3 lines below are needed by the present GUI. TODO:create a sane GUI. Kaks - 20090915 & 201005 [equipmentAllowed addObject:eqKeyForSelectFacing]; [equipmentAllowed addObject:eqKeyForSelectFacing]; [equipmentAllowed addObject:eqKeyForSelectFacing]; } else for (eqEnum = [OOEquipmentType equipmentEnumerator]; (eqType = [eqEnum nextObject]); i++) { NSString *eqKey = [eqType identifier]; OOTechLevelID minTechLevel = [eqType effectiveTechLevel]; // set initial availability to NO BOOL isOK = NO; // check special availability if ([eqType isAvailableToAll]) [options addObject:eqKey]; // if you have a damaged system you can get it repaired at a tech level one less than that required to buy it if (minTechLevel != 0 && [self hasEquipmentItem:[eqType damagedIdentifier]]) minTechLevel--; // reduce the minimum techlevel occasionally as a bonus.. if (techlevel < minTechLevel && techlevel + 3 > minTechLevel) { unsigned day = i * 13 + (unsigned)floor([UNIVERSE getTime] / 86400.0); unsigned char dayRnd = (day & 0xff) ^ (unsigned char)system_id; OOTechLevelID originalMinTechLevel = minTechLevel; while (minTechLevel > 0 && minTechLevel > originalMinTechLevel - 3 && !(dayRnd & 7)) // bargain tech days every 1/8 days { dayRnd = dayRnd >> 2; minTechLevel--; // occasional bonus items according to TL } } // check initial availability against options AND standard extras if ([options containsObject:eqKey]) { isOK = YES; [options removeObject:eqKey]; } if (isOK) { if (techlevel < minTechLevel) isOK = NO; if (![self canAddEquipment:eqKey inContext:@"purchase"]) isOK = NO; if (available_facings == 0 && [eqType isPrimaryWeapon]) isOK = NO; if (isOK) [equipmentAllowed addObject:eqKey]; } if (searchStatus == STATUS_DEAD && isOK) { showKey = eqKey; searchStatus = STATUS_ACTIVE; } if (searchStatus == STATUS_TEST) { if (isOK) showKey = eqKey; if ([eqKey isEqualToString:last_outfitting_key]) searchStatus = isOK ? STATUS_ACTIVE : STATUS_DEAD; } } if (searchStatus != STATUS_TEST && showKey != nil) { [last_outfitting_key release]; last_outfitting_key = [showKey copy]; } // GUI stuff { GuiDisplayGen *gui = [UNIVERSE gui]; OOGUIRow start_row = GUI_ROW_EQUIPMENT_START; OOGUIRow row = start_row; unsigned facing_count = 0; BOOL displayRow = YES; BOOL weaponMounted = NO; BOOL guiChanged = (gui_screen != GUI_SCREEN_EQUIP_SHIP); [gui clearAndKeepBackground:!guiChanged]; [gui setTitle:DESC(@"equip-title")]; [gui setColor:[gui colorFromSetting:kGuiEquipmentCashColor defaultValue:nil] forRow: GUI_ROW_EQUIPMENT_CASH]; [gui setText:OOExpandKey(@"equip-cash-value", credits) forRow:GUI_ROW_EQUIPMENT_CASH]; OOGUITabSettings tab_stops; tab_stops[0] = 0; tab_stops[1] = -360; tab_stops[2] = -480; [gui overrideTabs:tab_stops from:kGuiEquipmentTabs length:3]; [gui setTabStops:tab_stops]; unsigned n_rows = GUI_MAX_ROWS_EQUIPMENT; NSUInteger count = [equipmentAllowed count]; if (count > 0) { if (skip > 0) // lose the first row to Back <-- { unsigned previous; if (count <= n_rows || skip < n_rows) previous = 0; // single page else { previous = skip - (n_rows - 2); // multi-page. if (previous < 2) previous = 0; // if only one previous item, just show it } if (eqKeyForSelectFacing != nil) { previous = 0; // keep weapon selected if we go back. [gui setKey:[NSString stringWithFormat:@"More:%d:%@", previous, eqKeyForSelectFacing] forRow:row]; } else { [gui setKey:[NSString stringWithFormat:@"More:%d", previous] forRow:row]; } [gui setColor:[gui colorFromSetting:kGuiEquipmentScrollColor defaultValue:[OOColor greenColor]] forRow:row]; [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-back"), @"", @" <-- ", nil] forRow:row]; row++; } for (i = skip; i < count && (row - start_row < (OOGUIRow)n_rows); i++) { NSString *eqKey = [equipmentAllowed oo_stringAtIndex:i]; OOEquipmentType *eqInfo = [OOEquipmentType equipmentTypeWithIdentifier:eqKey]; OOCreditsQuantity pricePerUnit = [eqInfo price]; NSString *desc = [NSString stringWithFormat:@" %@ ", [eqInfo name]]; NSString *eq_key_damaged = [eqInfo damagedIdentifier]; double price; [gui setColor:[gui colorFromSetting:kGuiEquipmentOptionColor defaultValue:nil] forRow:row]; if ([eqKey isEqual:@"EQ_FUEL"]) { price = (PLAYER_MAX_FUEL - fuel) * pricePerUnit * [self fuelChargeRate]; } else if ([eqKey isEqualToString:@"EQ_RENOVATION"]) { price = [self renovationCosts]; [gui setColor:[gui colorFromSetting:kGuiEquipmentRepairColor defaultValue:[OOColor orangeColor]] forRow:row]; } else { price = pricePerUnit; } price = [self adjustPriceByScriptForEqKey:eqKey withCurrent:price]; price *= priceFactor; // increased prices at some stations NSUInteger installTime = [eqInfo installTime]; if (installTime == 0) { installTime = 600 + price; } // is this item damaged? if ([self hasEquipmentItem:eq_key_damaged]) { desc = [NSString stringWithFormat:DESC(@"equip-repair-@"), desc]; price /= 2.0; installTime = [eqInfo repairTime]; if (installTime == 0) { installTime = 600 + price; } [gui setColor:[gui colorFromSetting:kGuiEquipmentRepairColor defaultValue:[OOColor orangeColor]] forRow:row]; } NSString *priceString = [NSString stringWithFormat:@" %@ ", OOCredits(price)]; NSString *timeString = [UNIVERSE shortTimeDescription:installTime]; if ([eqKeyForSelectFacing isEqualToString:eqKey]) { // Weapons purchase subscreen. while (facing_count < 5) { switch (facing_count) { case 0: break; case 1: displayRow = available_facings & WEAPON_FACING_FORWARD; desc = FORWARD_FACING_STRING; weaponMounted = !isWeaponNone(forward_weapon_type); break; case 2: displayRow = available_facings & WEAPON_FACING_AFT; desc = AFT_FACING_STRING; weaponMounted = !isWeaponNone(aft_weapon_type); break; case 3: displayRow = available_facings & WEAPON_FACING_PORT; desc = PORT_FACING_STRING; weaponMounted = !isWeaponNone(port_weapon_type); break; case 4: displayRow = available_facings & WEAPON_FACING_STARBOARD; desc = STARBOARD_FACING_STRING; weaponMounted = !isWeaponNone(starboard_weapon_type); break; } if(weaponMounted) { [gui setColor:[gui colorFromSetting:kGuiEquipmentLaserFittedColor defaultValue:[OOColor colorWithRed:0.0f green:0.6f blue:0.0f alpha:1.0f]] forRow:row]; } else { [gui setColor:[gui colorFromSetting:kGuiEquipmentLaserColor defaultValue:[OOColor greenColor]] forRow:row]; } if (displayRow) // Always true for the first pass. The first pass is used to display the name of the weapon being purchased. { [gui setKey:eqKey forRow:row]; [gui setArray:[NSArray arrayWithObjects:desc, (facing_count > 0 ? priceString : (NSString *)@""), timeString, nil] forRow:row]; row++; } facing_count++; } } else { // Normal equipment list. [gui setKey:eqKey forRow:row]; [gui setArray:[NSArray arrayWithObjects:desc, priceString, timeString, nil] forRow:row]; row++; } } if (i < count) { // just overwrite the last item :-) [gui setColor:[gui colorFromSetting:kGuiEquipmentScrollColor defaultValue:[OOColor greenColor]] forRow:row-1]; [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-more"), @"", @" --> ", nil] forRow:row - 1]; [gui setKey:[NSString stringWithFormat:@"More:%d", i - 1] forRow:row - 1]; } [gui setSelectableRange:NSMakeRange(start_row,row - start_row)]; if ([gui selectedRow] != start_row) [gui setSelectedRow:start_row]; if (eqKeyForSelectFacing != nil) { [gui setSelectedRow:start_row + 1]; [self showInformationForSelectedUpgradeWithFormatString:DESC(@"@-select-where-to-install")]; } else { [self showInformationForSelectedUpgrade]; } } else { [gui setText:DESC(@"equip-no-equipment-available-for-purchase") forRow:GUI_ROW_NO_SHIPS align:GUI_ALIGN_CENTER]; [gui setColor:[gui colorFromSetting:kGuiEquipmentUnavailableColor defaultValue:[OOColor greenColor]] forRow:GUI_ROW_NO_SHIPS]; [gui setSelectableRange:NSMakeRange(0,0)]; [gui setNoSelectedRow]; [self showInformationForSelectedUpgrade]; } [gui setShowTextCursor:NO]; // TODO: split the mount_weapon sub-screen into a separate screen, and use it for pylon mounted wepons as well? if (guiChanged) { [gui setForegroundTextureKey:@"docked_overlay"]; NSDictionary *background = [UNIVERSE screenTextureDescriptorForKey:@"equip_ship"]; [self setEquipScreenBackgroundDescriptor:background]; [gui setBackgroundTextureDescriptor:background]; } else if (eqKeyForSelectFacing != nil) // weapon purchase { NSDictionary *bgDescriptor = [UNIVERSE screenTextureDescriptorForKey:@"mount_weapon"]; if (bgDescriptor != nil) [gui setBackgroundTextureDescriptor:bgDescriptor]; } else // Returning from a weapon purchase. (Also called, redundantly, when paging) { [gui setBackgroundTextureDescriptor:[self equipScreenBackgroundDescriptor]]; } } /* ends */ chosen_weapon_facing = WEAPON_FACING_NONE; [self setShowDemoShips:NO]; gui_screen = GUI_SCREEN_EQUIP_SHIP; [self setShowDemoShips:NO]; [UNIVERSE enterGUIViewModeWithMouseInteraction:YES]; } - (void) setGuiToEquipShipScreen:(int)skip { [self setGuiToEquipShipScreen:skip selectingFacingFor:nil]; } - (void) showInformationForSelectedUpgrade { [self showInformationForSelectedUpgradeWithFormatString:nil]; } - (void) showInformationForSelectedUpgradeWithFormatString:(NSString *)formatString { GuiDisplayGen* gui = [UNIVERSE gui]; NSString* eqKey = [gui selectedRowKey]; int i; OOColor *descColor = [gui colorFromSetting:kGuiEquipmentDescriptionColor defaultValue:[OOColor greenColor]]; for (i = GUI_ROW_EQUIPMENT_DETAIL; i < GUI_MAX_ROWS; i++) { [gui setText:@"" forRow:i]; [gui setColor:descColor forRow:i]; } if (eqKey) { if (![eqKey hasPrefix:@"More:"]) { NSString* desc = [[OOEquipmentType equipmentTypeWithIdentifier:eqKey] descriptiveText]; NSString* eq_key_damaged = [NSString stringWithFormat:@"%@_DAMAGED", eqKey]; int weight = [[OOEquipmentType equipmentTypeWithIdentifier:eqKey] requiredCargoSpace]; if ([self hasEquipmentItem:eq_key_damaged]) { desc = [NSString stringWithFormat:DESC(@"upgradeinfo-@-price-is-for-repairing"), desc]; } else { if([eqKey hasSuffix:@"ENERGY_UNIT"] && ([self hasEquipmentItem:@"EQ_ENERGY_UNIT_DAMAGED"] || [self hasEquipmentItem:@"EQ_ENERGY_UNIT"] || [self hasEquipmentItem:@"EQ_NAVAL_ENERGY_UNIT_DAMAGED"])) desc = [NSString stringWithFormat:DESC(@"@-will-replace-other-energy"), desc]; if (weight > 0) desc = [NSString stringWithFormat:DESC(@"upgradeinfo-@-weight-d-of-equipment"), desc, weight]; } if (formatString) desc = [NSString stringWithFormat:formatString, desc]; [gui addLongText:desc startingAtRow:GUI_ROW_EQUIPMENT_DETAIL align:GUI_ALIGN_LEFT]; } } } - (void) setGuiToInterfacesScreen:(int)skip { [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:YES]; if (gui_screen != GUI_SCREEN_INTERFACES) { [self noteGUIWillChangeTo:GUI_SCREEN_INTERFACES]; } // build an array of available interfaces NSDictionary *interfaces = [[self dockedStation] localInterfaces]; NSArray *interfaceKeys = [interfaces keysSortedByValueUsingSelector:@selector(interfaceCompare:)]; // sorts by category, then title int i; // GUI stuff { GuiDisplayGen *gui = [UNIVERSE gui]; OOGUIRow start_row = GUI_ROW_INTERFACES_START; OOGUIRow row = start_row; BOOL guiChanged = (gui_screen != GUI_SCREEN_INTERFACES); [gui clearAndKeepBackground:!guiChanged]; [gui setTitle:DESC(@"interfaces-title")]; OOGUITabSettings tab_stops; tab_stops[0] = 0; tab_stops[1] = -480; [gui overrideTabs:tab_stops from:kGuiInterfaceTabs length:2]; [gui setTabStops:tab_stops]; unsigned n_rows = GUI_MAX_ROWS_INTERFACES; NSUInteger count = [interfaceKeys count]; if (count > 0) { if (skip > 0) // lose the first row to Back <-- { unsigned previous; if (count <= n_rows || skip < (NSInteger)n_rows) { previous = 0; // single page } else { previous = skip - (n_rows - 2); // multi-page. if (previous < 2) { previous = 0; // if only one previous item, just show it } } [gui setKey:[NSString stringWithFormat:@"More:%d", previous] forRow:row]; [gui setColor:[gui colorFromSetting:kGuiInterfaceScrollColor defaultValue:[OOColor greenColor]] forRow:row]; [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-back"), @" <-- ", nil] forRow:row]; row++; } for (i = skip; i < (NSInteger)count && (row - start_row < (OOGUIRow)n_rows); i++) { NSString *interfaceKey = [interfaceKeys objectAtIndex:i]; OOJSInterfaceDefinition *definition = [interfaces objectForKey:interfaceKey]; [gui setColor:[gui colorFromSetting:kGuiInterfaceEntryColor defaultValue:nil] forRow:row]; [gui setKey:interfaceKey forRow:row]; [gui setArray:[NSArray arrayWithObjects:[definition title],[definition category], nil] forRow:row]; row++; } if (i < (NSInteger)count) { // just overwrite the last item :-) [gui setColor:[gui colorFromSetting:kGuiInterfaceScrollColor defaultValue:[OOColor greenColor]] forRow:row - 1]; [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-more"), @" --> ", nil] forRow:row - 1]; [gui setKey:[NSString stringWithFormat:@"More:%d", i - 1] forRow:row - 1]; } [gui setSelectableRange:NSMakeRange(start_row,row - start_row)]; if ([gui selectedRow] != start_row) { [gui setSelectedRow:start_row]; } [self showInformationForSelectedInterface]; } else { [gui setText:DESC(@"interfaces-no-interfaces-available-for-use") forRow:GUI_ROW_NO_INTERFACES align:GUI_ALIGN_LEFT]; [gui setColor:[gui colorFromSetting:kGuiInterfaceNoneColor defaultValue:[OOColor greenColor]] forRow:GUI_ROW_NO_INTERFACES]; [gui setSelectableRange:NSMakeRange(0,0)]; [gui setNoSelectedRow]; } [gui setShowTextCursor:NO]; NSString *desc = [NSString stringWithFormat:DESC(@"interfaces-for-ship-@-and-station-@"), [self displayName], [[self dockedStation] displayName]]; [gui setColor:[gui colorFromSetting:kGuiInterfaceHeadingColor defaultValue:nil] forRow:GUI_ROW_INTERFACES_HEADING]; [gui setText:desc forRow:GUI_ROW_INTERFACES_HEADING]; if (guiChanged) { [gui setForegroundTextureKey:@"docked_overlay"]; NSDictionary *background = [UNIVERSE screenTextureDescriptorForKey:@"interfaces"]; [gui setBackgroundTextureDescriptor:background]; } } /* ends */ [self setShowDemoShips:NO]; OOGUIScreenID oldScreen = gui_screen; gui_screen = GUI_SCREEN_INTERFACES; [self noteGUIDidChangeFrom:oldScreen to:gui_screen]; [self setShowDemoShips:NO]; [UNIVERSE enterGUIViewModeWithMouseInteraction:YES]; } - (void) showInformationForSelectedInterface { GuiDisplayGen* gui = [UNIVERSE gui]; NSString* interfaceKey = [gui selectedRowKey]; int i; for (i = GUI_ROW_EQUIPMENT_DETAIL; i < GUI_MAX_ROWS; i++) { [gui setText:@"" forRow:i]; [gui setColor:[gui colorFromSetting:kGuiInterfaceDescriptionColor defaultValue:[OOColor greenColor]] forRow:i]; } if (interfaceKey && ![interfaceKey hasPrefix:@"More:"]) { NSDictionary *interfaces = [[self dockedStation] localInterfaces]; OOJSInterfaceDefinition *definition = [interfaces objectForKey:interfaceKey]; if (definition) { [gui addLongText:[definition summary] startingAtRow:GUI_ROW_INTERFACES_DETAIL align:GUI_ALIGN_LEFT]; } } } - (void) activateSelectedInterface { GuiDisplayGen* gui = [UNIVERSE gui]; NSString* key = [gui selectedRowKey]; if ([key hasPrefix:@"More:"]) { int from_item = [[key componentsSeparatedByString:@":"] oo_intAtIndex:1]; [self setGuiToInterfacesScreen:from_item]; if ([gui selectedRow] < 0) [gui setSelectedRow:GUI_ROW_INTERFACES_START]; if (from_item == 0) [gui setSelectedRow:GUI_ROW_INTERFACES_START + GUI_MAX_ROWS_INTERFACES - 1]; [self showInformationForSelectedInterface]; return; } NSDictionary *interfaces = [[self dockedStation] localInterfaces]; OOJSInterfaceDefinition *definition = [interfaces objectForKey:key]; if (definition) { [[UNIVERSE gameView] clearKeys]; [definition runCallback:key]; } else { OOLog(@"interface.missingCallback", @"Unable to find callback definition for key %@", key); } } - (void) setupStartScreenGui { GuiDisplayGen *gui = [UNIVERSE gui]; NSString *text = nil; [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:YES]; [gui clear]; [gui setTitle:@"Oolite"]; text = DESC(@"game-copyright"); [gui setText:text forRow:15 align:GUI_ALIGN_CENTER]; [gui setColor:[OOColor whiteColor] forRow:15]; text = DESC(@"theme-music-credit"); [gui setText:text forRow:17 align:GUI_ALIGN_CENTER]; [gui setColor:[OOColor grayColor] forRow:17]; int row = 22; text = DESC(@"oolite-start-option-1"); [gui setText:text forRow:row align:GUI_ALIGN_CENTER]; [gui setColor:[OOColor yellowColor] forRow:row]; [gui setKey:[NSString stringWithFormat:@"Start:%d", row] forRow:row]; ++row; text = DESC(@"oolite-start-option-2"); [gui setText:text forRow:row align:GUI_ALIGN_CENTER]; [gui setColor:[OOColor yellowColor] forRow:row]; [gui setKey:[NSString stringWithFormat:@"Start:%d", row] forRow:row]; ++row; text = DESC(@"oolite-start-option-3"); [gui setText:text forRow:row align:GUI_ALIGN_CENTER]; [gui setColor:[OOColor yellowColor] forRow:row]; [gui setKey:[NSString stringWithFormat:@"Start:%d", row] forRow:row]; ++row; text = DESC(@"oolite-start-option-4"); [gui setText:text forRow:row align:GUI_ALIGN_CENTER]; [gui setColor:[OOColor yellowColor] forRow:row]; [gui setKey:[NSString stringWithFormat:@"Start:%d", row] forRow:row]; ++row; text = DESC(@"oolite-start-option-5"); [gui setText:text forRow:row align:GUI_ALIGN_CENTER]; [gui setColor:[OOColor yellowColor] forRow:row]; [gui setKey:[NSString stringWithFormat:@"Start:%d", row] forRow:row]; ++row; text = DESC(@"oolite-start-option-6"); [gui setText:text forRow:row align:GUI_ALIGN_CENTER]; [gui setColor:[OOColor yellowColor] forRow:row]; [gui setKey:[NSString stringWithFormat:@"Start:%d", row] forRow:row]; [gui setSelectableRange:NSMakeRange(22,6)]; [gui setSelectedRow:22]; [gui setBackgroundTextureKey:@"intro"]; } - (void) setGuiToIntroFirstGo:(BOOL)justCobra { NSString *text = nil; GuiDisplayGen *gui = [UNIVERSE gui]; OOGUIRow msgLine = 2; [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:NO]; [[UNIVERSE gameView] clearMouse]; [[UNIVERSE gameView] clearKeys]; if (justCobra) { [UNIVERSE removeDemoShips]; [[OOCacheManager sharedCache] flush]; // At first startup, a lot of stuff is cached } if (justCobra) { [self setupStartScreenGui]; // check for error messages from Resource Manager //[ResourceManager paths]; done in Universe already NSString *errors = [ResourceManager errors]; if (errors != nil) { OOGUIRow ms_start = msgLine; OOGUIRow i = msgLine = [gui addLongText:errors startingAtRow:ms_start align:GUI_ALIGN_LEFT]; for (i-- ; i >= ms_start ; i--) [gui setColor:[OOColor redColor] forRow:i]; msgLine++; } // check for messages from OXPs NSArray *OXPsWithMessages = [ResourceManager OXPsWithMessagesFound]; if ([OXPsWithMessages count] > 0) { NSString *messageToDisplay = @""; // Show which OXPs were found with messages, but don't spam the screen if more than // a certain number of them exist if ([OXPsWithMessages count] < 5) { NSString *messageSourceList = [OXPsWithMessages componentsJoinedByString:@", "]; messageToDisplay = OOExpandKey(@"oxp-containing-messages-list", messageSourceList); } else { messageToDisplay = OOExpandKey(@"oxp-containing-messages-found"); } OOGUIRow ms_start = msgLine; OOGUIRow i = msgLine = [gui addLongText:messageToDisplay startingAtRow:ms_start align:GUI_ALIGN_LEFT]; for (i--; i >= ms_start; i--) { [gui setColor:[OOColor orangeColor] forRow:i]; } msgLine++; } // check for messages from the command line NSArray* arguments = [[NSProcessInfo processInfo] arguments]; unsigned i; for (i = 0; i < [arguments count]; i++) { if (([[arguments objectAtIndex:i] isEqual:@"-message"])&&(i < [arguments count] - 1)) { OOGUIRow ms_start = msgLine; NSString* message = [arguments oo_stringAtIndex:i + 1]; OOGUIRow i = msgLine = [gui addLongText:message startingAtRow:ms_start align:GUI_ALIGN_CENTER]; for (i-- ; i >= ms_start; i--) { [gui setColor:[OOColor magentaColor] forRow:i]; } } if ([[arguments objectAtIndex:i] isEqual:@"-showversion"]) { OOGUIRow ms_start = msgLine; NSString *version = [NSString stringWithFormat:@"Version %@", [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]]; OOGUIRow i = msgLine = [gui addLongText:version startingAtRow:ms_start align:GUI_ALIGN_CENTER]; for (i-- ; i >= ms_start; i--) { [gui setColor:[OOColor magentaColor] forRow:i]; } } } } else { [gui clear]; text = DESC(@"oolite-ship-library-title"); [gui setTitle:text]; text = DESC(@"oolite-ship-library-exit"); [gui setText:text forRow:27 align:GUI_ALIGN_CENTER]; [gui setColor:[OOColor yellowColor] forRow:27]; } [gui setShowTextCursor:NO]; [UNIVERSE setupIntroFirstGo: justCobra]; if (gui != nil) { gui_screen = justCobra ? GUI_SCREEN_INTRO1 : GUI_SCREEN_SHIPLIBRARY; } if ([self status] == STATUS_START_GAME) { [[OOMusicController sharedController] playThemeMusic]; } [self setShowDemoShips:YES]; if (justCobra) { [gui setBackgroundTextureKey:@"intro"]; } else { [gui setBackgroundTextureKey:@"shiplibrary"]; } [UNIVERSE enterGUIViewModeWithMouseInteraction:YES]; } - (void) setGuiToKeySettingsScreen { GuiDisplayGen *gui = [UNIVERSE gui]; NSUInteger i,j,ct; [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:NO]; [[UNIVERSE gameView] clearMouse]; [UNIVERSE removeDemoShips]; [gui setTitle:DESC(@"oolite-keysetting-screen")]; gui_screen = GUI_SCREEN_KEYBOARD; OOGUITabSettings tab_stops; tab_stops[0] = 0; tab_stops[1] = 115; tab_stops[2] = 170; tab_stops[3] = 285; tab_stops[4] = 340; tab_stops[5] = 455; [gui setTabStops:tab_stops]; [gui setSelectableRange:NSMakeRange(0,0)]; [gui setNoSelectedRow]; NSArray *keys = [NSArray arrayWithObjects: @"key_roll_left",@"key_pitch_forward",@"key_yaw_left", @"key_roll_right",@"key_pitch_back",@"key_yaw_right", @"key_increase_speed",@"key_decrease_speed",@"key_inject_fuel", @"key_mouse_control",@"",@"", @"key_view_forward",@"key_gui_screen_status",@"key_gui_arrow_left", // @"key_view_aft",@"key_gui_chart_screens",@"key_gui_arrow_right", @"key_view_port",@"key_gui_system_data",@"key_gui_arrow_up", @"key_view_starboard",@"key_gui_market",@"key_gui_arrow_down", @"key_custom_view",@"key_map_info",@"key_map_home", @"key_snapshot",@"key_advanced_nav_array",@"key_chart_highlight", // @"",@"",@"", @"key_fire_lasers",@"key_launch_missile",@"key_ecm", @"key_ident_system",@"key_target_missile",@"key_untarget_missile", @"key_weapons_online_toggle",@"key_next_missile",@"key_target_incoming_missile", @"key_next_target",@"key_previous_target",@"key_launch_escapepod", // @"",@"",@"", @"key_jumpdrive",@"key_hyperspace",@"key_galactic_hyperspace", @"key_autopilot",@"key_autodock",@"key_docking_clearance_request", @"key_docking_music",@"",@"", @"key_scanner_zoom",@"key_dump_cargo",@"key_prev_compass_mode", // @"key_scanner_unzoom",@"key_rotate_cargo",@"key_next_compass_mode", @"key_comms_log",@"key_cycle_mfd",@"key_switch_mfd", @"",@"",@"", @"key_prime_equipment",@"key_activate_equipment",@"key_mode_equipment", @"key_fastactivate_equipment_a",@"key_fastactivate_equipment_b",@"", // #if OO_FOV_INFLIGHT_CONTROL_ENABLED @"key_inc_field_of_view",@"key_dec_field_of_view",@"", #else @"",@"",@"", #endif @"key_pausebutton",@"key_show_fps",@"key_hud_toggle", nil]; OOGUIRow row = 0; ct = [keys count]; for (i=0; i credits) ? (old_credits - credits) : 0.0; [UNIVERSE forceWitchspaceEntries]; if (adjust == 0) { ship_clock_adjust += time_adjust + 600.0; } else { ship_clock_adjust += (double)adjust; } [self doScriptEvent:OOJSID("playerBoughtEquipment") withArgument:key]; if (gui_screen == GUI_SCREEN_EQUIP_SHIP) //if we haven't changed gui screen inside playerBoughtEquipment { // show any change due to playerBoughtEquipment [self setGuiToEquipShipScreen:0]; // then try to go back where we were [self highlightEquipShipScreenKey:key]; } if ([UNIVERSE autoSave]) [UNIVERSE setAutoSaveNow:YES]; } } else { [self playCantBuyCommodity]; } } - (OOCreditsQuantity) adjustPriceByScriptForEqKey:(NSString *)eqKey withCurrent:(OOCreditsQuantity)price { NSString *condition_script = [[OOEquipmentType equipmentTypeWithIdentifier:eqKey] conditionScript]; if (condition_script != nil) { OOJSScript *condScript = [UNIVERSE getConditionScript:condition_script]; if (condScript != nil) // should always be non-nil, but just in case { JSContext *JScontext = OOJSAcquireContext(); BOOL OK; jsval result; int32 newPrice; jsval args[] = { OOJSValueFromNativeObject(JScontext, eqKey) , JSVAL_NULL }; OK = JS_NewNumberValue(JScontext, price, &args[1]); if (OK) { OK = [condScript callMethod:OOJSID("updateEquipmentPrice") inContext:JScontext withArguments:args count:sizeof args / sizeof *args result:&result]; } if (OK) { OK = JS_ValueToInt32(JScontext, result, &newPrice); if (OK && newPrice >= 0) { price = (OOCreditsQuantity)newPrice; } } OOJSRelinquishContext(JScontext); } } return price; } - (BOOL) tryBuyingItem:(NSString *)eqKey { // note this doesn't check the availability by tech-level OOEquipmentType *eqType = [OOEquipmentType equipmentTypeWithIdentifier:eqKey]; OOCreditsQuantity pricePerUnit = [eqType price]; NSString *eqKeyDamaged = [eqType damagedIdentifier]; double price = pricePerUnit; double priceFactor = 1.0; OOCreditsQuantity tradeIn = 0; BOOL isRepair = NO; // repairs cost 50% if ([self hasEquipmentItem:eqKeyDamaged]) { price /= 2.0; isRepair = YES; } if ([eqKey isEqualToString:@"EQ_RENOVATION"]) { price = [self renovationCosts]; } price = [self adjustPriceByScriptForEqKey:eqKey withCurrent:price]; StationEntity *dockedStation = [self dockedStation]; if (dockedStation) { priceFactor = [dockedStation equipmentPriceFactor]; } price *= priceFactor; // increased prices at some stations if (price > credits) { return NO; } if ([eqType isPrimaryWeapon]) { if (chosen_weapon_facing == WEAPON_FACING_NONE) { [self setGuiToEquipShipScreen:0 selectingFacingFor:eqKey]; // reset return YES; } OOWeaponType chosen_weapon = OOWeaponTypeFromEquipmentIdentifierStrict(eqKey); OOWeaponType current_weapon = nil; switch (chosen_weapon_facing) { case WEAPON_FACING_FORWARD: current_weapon = forward_weapon_type; forward_weapon_type = chosen_weapon; break; case WEAPON_FACING_AFT: current_weapon = aft_weapon_type; aft_weapon_type = chosen_weapon; break; case WEAPON_FACING_PORT: current_weapon = port_weapon_type; port_weapon_type = chosen_weapon; break; case WEAPON_FACING_STARBOARD: current_weapon = starboard_weapon_type; starboard_weapon_type = chosen_weapon; break; case WEAPON_FACING_NONE: break; } credits -= price; // Refund current_weapon if (current_weapon != nil) { tradeIn = [UNIVERSE getEquipmentPriceForKey:OOEquipmentIdentifierFromWeaponType(current_weapon)]; } [self doTradeIn:tradeIn forPriceFactor:priceFactor]; // If equipped, remove damaged weapon after repairs. -- But there's no way we should get a damaged weapon. Ever. [self removeEquipmentItem:eqKeyDamaged]; return YES; } if ([eqType isMissileOrMine] && missiles >= max_missiles) { OOLog(@"equip.buy.mounted.failed.full", @"rejecting missile because already full"); return NO; } // NSFO! //unsigned passenger_space = [[OOEquipmentType equipmentTypeWithIdentifier:@"EQ_PASSENGER_BERTH"] requiredCargoSpace]; //if (passenger_space == 0) passenger_space = PASSENGER_BERTH_SPACE; if ([eqKey isEqualToString:@"EQ_PASSENGER_BERTH"] && [self availableCargoSpace] < PASSENGER_BERTH_SPACE) { return NO; } if ([eqKey isEqualToString:@"EQ_FUEL"]) { #if MASS_DEPENDENT_FUEL_PRICES OOCreditsQuantity creditsForRefuel = ([self fuelCapacity] - [self fuel]) * pricePerUnit * [self fuelChargeRate]; #else OOCreditsQuantity creditsForRefuel = ([self fuelCapacity] - [self fuel]) * pricePerUnit; #endif if (credits >= creditsForRefuel) // Ensure we don't overflow { credits -= creditsForRefuel; fuel = [self fuelCapacity]; return YES; } else { return NO; } } // check energy unit replacement if ([eqKey hasSuffix:@"ENERGY_UNIT"] && [self energyUnitType] != ENERGY_UNIT_NONE) { switch ([self energyUnitType]) { case ENERGY_UNIT_NAVAL : [self removeEquipmentItem:@"EQ_NAVAL_ENERGY_UNIT"]; tradeIn = [UNIVERSE getEquipmentPriceForKey:@"EQ_NAVAL_ENERGY_UNIT"] / 2; // 50 % refund break; case ENERGY_UNIT_NAVAL_DAMAGED : [self removeEquipmentItem:@"EQ_NAVAL_ENERGY_UNIT_DAMAGED"]; tradeIn = [UNIVERSE getEquipmentPriceForKey:@"EQ_NAVAL_ENERGY_UNIT"] / 4; // half of the working one break; case ENERGY_UNIT_NORMAL : [self removeEquipmentItem:@"EQ_ENERGY_UNIT"]; tradeIn = [UNIVERSE getEquipmentPriceForKey:@"EQ_ENERGY_UNIT"] * 3 / 4; // 75 % refund break; case ENERGY_UNIT_NORMAL_DAMAGED : [self removeEquipmentItem:@"EQ_ENERGY_UNIT_DAMAGED"]; tradeIn = [UNIVERSE getEquipmentPriceForKey:@"EQ_ENERGY_UNIT"] * 3 / 8; // half of the working one break; default: break; } [self doTradeIn:tradeIn forPriceFactor:priceFactor]; } // maintain ship if ([eqKey isEqualToString:@"EQ_RENOVATION"]) { OOTechLevelID techLevel = NSNotFound; if (dockedStation != nil) techLevel = [dockedStation equivalentTechLevel]; if (techLevel == NSNotFound) techLevel = [[UNIVERSE currentSystemData] oo_unsignedIntForKey:KEY_TECHLEVEL]; credits -= price; ship_trade_in_factor += 5 + techLevel; // you get better value at high-tech repair bases if (ship_trade_in_factor > 100) ship_trade_in_factor = 100; [self clearSubEntities]; [self setUpSubEntities]; return YES; } if ([eqKey hasSuffix:@"MISSILE"] || [eqKey hasSuffix:@"MINE"]) { ShipEntity* weapon = [[UNIVERSE newShipWithRole:eqKey] autorelease]; if (weapon) OOLog(kOOLogBuyMountedOK, @"Got ship for mounted weapon role %@", eqKey); else OOLog(kOOLogBuyMountedFailed, @"Could not find ship for mounted weapon role %@", eqKey); BOOL mounted_okay = [self mountMissile:weapon]; if (mounted_okay) { credits -= price; [self safeAllMissiles]; [self tidyMissilePylons]; [self setActiveMissile:0]; } return mounted_okay; } if ([eqKey isEqualToString:@"EQ_PASSENGER_BERTH"]) { [self changePassengerBerths:+1]; credits -= price; return YES; } if ([eqKey isEqualToString:@"EQ_PASSENGER_BERTH_REMOVAL"]) { [self changePassengerBerths:-1]; credits -= price; return YES; } if ([eqKey isEqualToString:@"EQ_MISSILE_REMOVAL"]) { credits -= price; tradeIn += [self removeMissiles]; [self doTradeIn:tradeIn forPriceFactor:priceFactor]; return YES; } if ([self canAddEquipment:eqKey inContext:@"purchase"]) { credits -= price; [self addEquipmentItem:eqKey withValidation:NO inContext:@"purchase"]; // no need to validate twice. if (isRepair) { [self doScriptEvent:OOJSID("equipmentRepaired") withArgument:eqKey]; } return YES; } return NO; } - (BOOL) setWeaponMount:(OOWeaponFacing)facing toWeapon:(NSString *)eqKey { NSDictionary *shipyardInfo = [[OOShipRegistry sharedRegistry] shipyardInfoForKey:[self shipDataKey]]; unsigned available_facings = [shipyardInfo oo_unsignedIntForKey:KEY_WEAPON_FACINGS defaultValue:[self weaponFacings]]; // use defaults explicitly // facing exists? if (!(available_facings & facing)) { return NO; } // weapon allowed (or NONE)? if (![eqKey isEqualToString:@"EQ_WEAPON_NONE"]) { if (![self canAddEquipment:eqKey inContext:@"purchase"]) { return NO; } } // sets WEAPON_NONE if not recognised OOWeaponType chosen_weapon = OOWeaponTypeFromEquipmentIdentifierStrict(eqKey); switch (facing) { case WEAPON_FACING_FORWARD: forward_weapon_type = chosen_weapon; break; case WEAPON_FACING_AFT: aft_weapon_type = chosen_weapon; break; case WEAPON_FACING_PORT: port_weapon_type = chosen_weapon; break; case WEAPON_FACING_STARBOARD: starboard_weapon_type = chosen_weapon; break; case WEAPON_FACING_NONE: break; } return YES; } - (BOOL) changePassengerBerths:(int) addRemove { if (addRemove == 0) return NO; addRemove = (addRemove > 0) ? 1 : -1; // change only by one berth at a time! // NSFO! //unsigned passenger_space = [[OOEquipmentType equipmentTypeWithIdentifier:@"EQ_PASSENGER_BERTH"] requiredCargoSpace]; //if (passenger_space == 0) passenger_space = PASSENGER_BERTH_SPACE; if ((max_passengers < 1 && addRemove == -1) || ([self maxAvailableCargoSpace] - current_cargo < PASSENGER_BERTH_SPACE && addRemove == 1)) return NO; max_passengers += addRemove; max_cargo -= PASSENGER_BERTH_SPACE * addRemove; return YES; } - (OOCreditsQuantity) removeMissiles { [self safeAllMissiles]; OOCreditsQuantity tradeIn = 0; unsigned i; for (i = 0; i < missiles; i++) { NSString *weapon_key = [missile_list[i] identifier]; if (weapon_key != nil) tradeIn += (int)[UNIVERSE getEquipmentPriceForKey:weapon_key]; } for (i = 0; i < max_missiles; i++) { [missile_entity[i] release]; missile_entity[i] = nil; } missiles = 0; return tradeIn; } - (void) doTradeIn:(OOCreditsQuantity)tradeInValue forPriceFactor:(double)priceFactor { if (tradeInValue != 0) { if (priceFactor < 1.0f) tradeInValue *= priceFactor; credits += tradeInValue; } } - (OOCargoQuantity) cargoQuantityForType:(OOCommodityType)type { OOCargoQuantity amount = [shipCommodityData quantityForGood:type]; if ([self status] != STATUS_DOCKED) { NSInteger i; OOCommodityType co_type; ShipEntity *cargoItem = nil; for (i = [cargo count] - 1; i >= 0 ; i--) { cargoItem = [cargo objectAtIndex:i]; co_type = [cargoItem commodityType]; if ([co_type isEqualToString:type]) { amount += [cargoItem commodityAmount]; } } } return amount; } - (OOCargoQuantity) setCargoQuantityForType:(OOCommodityType)type amount:(OOCargoQuantity)amount { OOMassUnit unit = [shipCommodityData massUnitForGood:type]; if([self specialCargo] && unit == UNITS_TONS) return 0; // don't do anything if we've got a special cargo... OOCargoQuantity oldAmount = [self cargoQuantityForType:type]; OOCargoQuantity available = [self availableCargoSpace]; BOOL inPods = ([self status] != STATUS_DOCKED); // check it against the max amount. if (unit == UNITS_TONS && (available + oldAmount) < amount) { amount = available + oldAmount; } // if we have 1499 kg the ship registers only 1 ton, so it's possible to exceed the max cargo: // eg: with maxAvailableCargoSpace 2 & gold 1499kg, you can still add 1 ton alloy. else if (unit == UNITS_KILOGRAMS && amount > oldAmount) { // Allow up to 0.5 ton of kg (& g) goods above the cargo capacity but respect existing quantities. OOCargoQuantity safeAmount = available * KILOGRAMS_PER_POD + MAX_KILOGRAMS_IN_SAFE; if (safeAmount < amount) amount = (safeAmount < oldAmount) ? oldAmount : safeAmount; } else if (unit == UNITS_GRAMS && amount > oldAmount) { OOCargoQuantity safeAmount = available * GRAMS_PER_POD + MAX_GRAMS_IN_SAFE; if (safeAmount < amount) amount = (safeAmount < oldAmount) ? oldAmount : safeAmount; } if (inPods) { if (amount > oldAmount) // increase { [self loadCargoPodsForType:type amount:(amount - oldAmount)]; } else { [self unloadCargoPodsForType:type amount:(oldAmount - amount)]; } } else { [shipCommodityData setQuantity:amount forGood:type]; } [self calculateCurrentCargo]; return [shipCommodityData quantityForGood:type]; } - (void) calculateCurrentCargo { current_cargo = [self cargoQuantityOnBoard]; } - (OOCargoQuantity) cargoQuantityOnBoard { if ([self specialCargo] != nil) { return [self maxAvailableCargoSpace]; } /* The cargo array is nil when the player ship is docked, due to action in unloadCargopods. For this reason, we must use a slightly more complex method to determine the quantity of cargo carried in this case - Nikos 20090830 Optimised this method, to compensate for increased usage - Kaks 20091002 */ OOCargoQuantity cargoQtyOnBoard = 0; NSString *good = nil; foreach (good, [shipCommodityData goods]) { OOCargoQuantity quantity = [shipCommodityData quantityForGood:good]; OOMassUnit commodityUnits = [shipCommodityData massUnitForGood:good]; if (commodityUnits != UNITS_TONS) { // calculate the number of pods that would be used // we're using integer math, so 99/100 = 0 , 100/100 = 1, etc... assert(KILOGRAMS_PER_POD > MAX_KILOGRAMS_IN_SAFE && GRAMS_PER_POD > MAX_GRAMS_IN_SAFE); // otherwise we're in trouble! if (commodityUnits == UNITS_KILOGRAMS) quantity = ((KILOGRAMS_PER_POD - MAX_KILOGRAMS_IN_SAFE - 1) + quantity) / KILOGRAMS_PER_POD; else quantity = ((GRAMS_PER_POD - MAX_GRAMS_IN_SAFE - 1) + quantity) / GRAMS_PER_POD; } cargoQtyOnBoard += quantity; } cargoQtyOnBoard += [[self cargo] count]; return cargoQtyOnBoard; } - (OOCommodityMarket *) localMarket { StationEntity *station = [self dockedStation]; if (station == nil) { if ([[self primaryTarget] isStation] && [(StationEntity *)[self primaryTarget] marketBroadcast]) { station = [self primaryTarget]; } else { station = [UNIVERSE station]; } if (station == nil) { // interstellar space or similar return nil; } } OOCommodityMarket *localMarket = [station localMarket]; if (localMarket == nil) { localMarket = [station initialiseLocalMarket]; } return localMarket; } - (NSArray *) applyMarketFilter:(NSArray *)goods onMarket:(OOCommodityMarket *)market { if (marketFilterMode == MARKET_FILTER_MODE_OFF) { return goods; } NSMutableArray *filteredGoods = [NSMutableArray arrayWithCapacity:[goods count]]; OOCommodityType good = nil; foreach (good, goods) { switch (marketFilterMode) { case MARKET_FILTER_MODE_OFF: // never reached, but keeps compiler happy [filteredGoods addObject:good]; break; case MARKET_FILTER_MODE_TRADE: if ([market quantityForGood:good] > 0 || [self cargoQuantityForType:good] > 0) { [filteredGoods addObject:good]; } break; case MARKET_FILTER_MODE_HOLD: if ([self cargoQuantityForType:good] > 0) { [filteredGoods addObject:good]; } break; case MARKET_FILTER_MODE_STOCK: if ([market quantityForGood:good] > 0) { [filteredGoods addObject:good]; } break; case MARKET_FILTER_MODE_LEGAL: if ([market exportLegalityForGood:good] == 0 && [market importLegalityForGood:good] == 0) { [filteredGoods addObject:good]; } break; case MARKET_FILTER_MODE_RESTRICTED: if ([market exportLegalityForGood:good] > 0 || [market importLegalityForGood:good] > 0) { [filteredGoods addObject:good]; } break; } } return [[filteredGoods copy] autorelease]; } - (NSArray *) applyMarketSorter:(NSArray *)goods onMarket:(OOCommodityMarket *)market { switch (marketSorterMode) { case MARKET_SORTER_MODE_ALPHA: return [goods sortedArrayUsingFunction:marketSorterByName context:market]; case MARKET_SORTER_MODE_PRICE: return [goods sortedArrayUsingFunction:marketSorterByPrice context:market]; case MARKET_SORTER_MODE_STOCK: return [goods sortedArrayUsingFunction:marketSorterByQuantity context:market]; case MARKET_SORTER_MODE_HOLD: return [goods sortedArrayUsingFunction:marketSorterByQuantity context:shipCommodityData]; case MARKET_SORTER_MODE_UNIT: return [goods sortedArrayUsingFunction:marketSorterByMassUnit context:market]; case MARKET_SORTER_MODE_OFF: // keep default sort order break; } return goods; } - (void) showMarketScreenHeaders { GuiDisplayGen *gui = [UNIVERSE gui]; OOGUITabSettings tab_stops; tab_stops[0] = 0; tab_stops[1] = 137; tab_stops[2] = 187; tab_stops[3] = 267; tab_stops[4] = 321; tab_stops[5] = 431; [gui overrideTabs:tab_stops from:kGuiMarketTabs length:6]; [gui setTabStops:tab_stops]; [gui setColor:[gui colorFromSetting:kGuiMarketHeadingColor defaultValue:[OOColor greenColor]] forRow:GUI_ROW_MARKET_KEY]; [gui setArray:[NSArray arrayWithObjects: DESC(@"commodity-column-title"), OOPadStringToEms(DESC(@"price-column-title"),3.5), OOPadStringToEms(DESC(@"for-sale-column-title"),3.75), OOPadStringToEms(DESC(@"in-hold-column-title"),5.75), DESC(@"oolite-legality-column-title"), DESC(@"oolite-extras-column-title"), nil] forRow:GUI_ROW_MARKET_KEY]; [gui setArray:[NSArray arrayWithObjects: DESC(@"commodity-column-title"), DESC(@"oolite-extras-column-title"), OOPadStringToEms(DESC(@"price-column-title"),3.5), OOPadStringToEms(DESC(@"for-sale-column-title"),3.75), OOPadStringToEms(DESC(@"in-hold-column-title"),5.75), DESC(@"oolite-legality-column-title"), nil] forRow:GUI_ROW_MARKET_KEY]; } - (void) showMarketScreenDataLine:(OOGUIRow)row forGood:(OOCommodityType)good inMarket:(OOCommodityMarket *)localMarket holdQuantity:(OOCargoQuantity)quantity { GuiDisplayGen *gui = [UNIVERSE gui]; NSString* desc = [NSString stringWithFormat:@" %@ ", [shipCommodityData nameForGood:good]]; OOCargoQuantity available_units = [localMarket quantityForGood:good]; OOCargoQuantity units_in_hold = quantity; OOCreditsQuantity pricePerUnit = [localMarket priceForGood:good]; OOMassUnit unit = [shipCommodityData massUnitForGood:good]; NSString *available = OOPadStringToEms(((available_units > 0) ? (NSString *)[NSString stringWithFormat:@"%d",available_units] : DESC(@"commodity-quantity-none")), 2.5); NSUInteger priceDecimal = pricePerUnit % 10; NSString *price = [NSString stringWithFormat:@" %@.%lu ",OOPadStringToEms([NSString stringWithFormat:@"%lu",(unsigned long)(pricePerUnit/10)],2.5),priceDecimal]; // this works with up to 9999 tons of gemstones. Any more than that, they deserve the formatting they get! :) NSString *owned = OOPadStringToEms((units_in_hold > 0) ? (NSString *)[NSString stringWithFormat:@"%d",units_in_hold] : DESC(@"commodity-quantity-none"), 4.5); NSString *units = DisplayStringForMassUnit(unit); NSString *units_available = [NSString stringWithFormat:@" %@ %@ ",available, units]; NSString *units_owned = [NSString stringWithFormat:@" %@ %@ ",owned, units]; NSUInteger import_legality = [localMarket importLegalityForGood:good]; NSUInteger export_legality = [localMarket exportLegalityForGood:good]; NSString *legaldesc = nil; if (import_legality == 0) { if (export_legality == 0) { legaldesc = DESC(@"oolite-legality-clear"); } else { legaldesc = DESC(@"oolite-legality-import"); } } else { if (export_legality == 0) { legaldesc = DESC(@"oolite-legality-export"); } else { legaldesc = DESC(@"oolite-legality-neither"); } } legaldesc = [NSString stringWithFormat:@" %@ ",legaldesc]; NSString *extradesc = [shipCommodityData shortCommentForGood:good]; [gui setKey:good forRow:row]; [gui setColor:[gui colorFromSetting:kGuiMarketCommodityColor defaultValue:nil] forRow:row]; [gui setArray:[NSArray arrayWithObjects: desc, extradesc, price, units_available, units_owned, legaldesc, nil] forRow:row++]; } - (NSString *)marketScreenTitle { StationEntity *dockedStation = [self dockedStation]; /* Override normal behaviour if station broadcasts market */ if (dockedStation == nil) { if ([[self primaryTarget] isStation] && [(StationEntity *)[self primaryTarget] marketBroadcast]) { dockedStation = [self primaryTarget]; } } NSString *system = nil; if ([UNIVERSE sun] != nil) system = [UNIVERSE getSystemName:system_id]; if (dockedStation == nil || dockedStation == [UNIVERSE station]) { if ([UNIVERSE sun] != nil) { return OOExpandKey(@"system-commodity-market", system); } else { // Witchspace return OOExpandKey(@"commodity-market"); } } else { NSString *station = [dockedStation displayName]; return OOExpandKey(@"station-commodity-market", system, station); } } - (void) setGuiToMarketScreen { OOCommodityMarket *localMarket = [self localMarket]; GuiDisplayGen *gui = [UNIVERSE gui]; OOGUIScreenID oldScreen = gui_screen; gui_screen = GUI_SCREEN_MARKET; BOOL guiChanged = (oldScreen != gui_screen); [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:YES]; // fix problems with economies in witchspace if (localMarket == nil) { localMarket = [[UNIVERSE commodities] generateBlankMarket]; } // following changed to work whether docked or not NSArray *goods = [self applyMarketSorter:[self applyMarketFilter:[localMarket goods] onMarket:localMarket] onMarket:localMarket]; NSInteger maxOffset = 0; if ([goods count] > (GUI_ROW_MARKET_END-GUI_ROW_MARKET_START)) { maxOffset = [goods count]-(GUI_ROW_MARKET_END-GUI_ROW_MARKET_START); } NSUInteger commodityCount = [shipCommodityData count]; OOCargoQuantity quantityInHold[commodityCount]; for (NSUInteger i = 0; i < commodityCount; i++) { quantityInHold[i] = [shipCommodityData quantityForGood:[goods oo_stringAtIndex:i]]; } for (NSUInteger i = 0; i < [cargo count]; i++) { ShipEntity *container = [cargo objectAtIndex:i]; NSUInteger goodsIndex = [goods indexOfObject:[container commodityType]]; // can happen with filters if (goodsIndex != NSNotFound) { quantityInHold[goodsIndex] += [container commodityAmount]; } } if (marketSelectedCommodity != nil && ([marketSelectedCommodity isEqualToString:@"<<<"] || [marketSelectedCommodity isEqualToString:@">>>"])) { // nothing? } else { if (marketSelectedCommodity == nil || [goods indexOfObject:marketSelectedCommodity] == NSNotFound) { DESTROY(marketSelectedCommodity); if ([goods count] > 0) { marketSelectedCommodity = [[goods oo_stringAtIndex:0] retain]; } } if (maxOffset > 0) { NSInteger goodsIndex = [goods indexOfObject:marketSelectedCommodity]; // validate marketOffset when returning from infoscreen if (goodsIndex <= marketOffset) { // is off top of list, move list upwards if (goodsIndex == 0) { marketOffset = 0; } else { marketOffset = goodsIndex-1; } } else if (goodsIndex > marketOffset+(GUI_ROW_MARKET_END-GUI_ROW_MARKET_START)-2) { // is off bottom of list, move list downwards marketOffset = 2+goodsIndex-(GUI_ROW_MARKET_END-GUI_ROW_MARKET_START); if (marketOffset > maxOffset) { marketOffset = maxOffset; } } } } // GUI stuff { OOGUIRow start_row = GUI_ROW_MARKET_START; OOGUIRow row = start_row; OOGUIRow active_row = [gui selectedRow]; [gui clearAndKeepBackground:!guiChanged]; StationEntity *dockedStation = [self dockedStation]; if (dockedStation == nil && [[self primaryTarget] isStation] && [(StationEntity *)[self primaryTarget] marketBroadcast]) { dockedStation = [self primaryTarget]; } [gui setTitle:[self marketScreenTitle]]; [self showMarketScreenHeaders]; if (marketOffset > maxOffset) { marketOffset = 0; } else if (marketOffset < 0) { marketOffset = maxOffset; } if ([goods count] > 0) { OOCommodityType good = nil; NSInteger i = 0; foreach (good, goods) { if (i < marketOffset) { ++i; continue; } [self showMarketScreenDataLine:row forGood:good inMarket:localMarket holdQuantity:quantityInHold[i++]]; if ([good isEqualToString:marketSelectedCommodity]) { active_row = row; } ++row; if (row >= GUI_ROW_MARKET_END) { break; } } if (marketOffset < maxOffset) { if ([marketSelectedCommodity isEqualToString:@">>>"]) { active_row = GUI_ROW_MARKET_LAST; } [gui setKey:@">>>" forRow:GUI_ROW_MARKET_LAST]; [gui setColor:[gui colorFromSetting:kGuiMarketScrollColor defaultValue:[OOColor greenColor]] forRow:GUI_ROW_MARKET_LAST]; [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-more"), @"", @"", @"", @" --> ", nil] forRow:GUI_ROW_MARKET_LAST]; } if (marketOffset > 0) { if ([marketSelectedCommodity isEqualToString:@"<<<"]) { active_row = GUI_ROW_MARKET_START; } [gui setKey:@"<<<" forRow:GUI_ROW_MARKET_START]; [gui setColor:[gui colorFromSetting:kGuiMarketScrollColor defaultValue:[OOColor greenColor]] forRow:GUI_ROW_MARKET_START]; [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-back"), @"", @"", @"", @" <-- ", nil] forRow:GUI_ROW_MARKET_START]; } } else { // filter is excluding everything [gui setColor:[gui colorFromSetting:kGuiMarketFilteredAllColor defaultValue:[OOColor yellowColor]] forRow:GUI_ROW_MARKET_START]; [gui setText:DESC(@"oolite-market-filtered-all") forRow:GUI_ROW_MARKET_START]; active_row = -1; } // actually count the containers and valuables (may be > max_cargo) current_cargo = [self cargoQuantityOnBoard]; if (current_cargo > [self maxAvailableCargoSpace]) current_cargo = [self maxAvailableCargoSpace]; // filter sort info { NSString *filterMode = OOExpandKey(OOExpand(@"oolite-market-filter-[marketFilterMode]", marketFilterMode)); NSString *filterText = OOExpandKey(@"oolite-market-filter-line", filterMode); NSString *sortMode = OOExpandKey(OOExpand(@"oolite-market-sorter-[marketSorterMode]", marketSorterMode)); NSString *sorterText = OOExpandKey(@"oolite-market-sorter-line", sortMode); [gui setArray:[NSArray arrayWithObjects:filterText, @"", sorterText, nil] forRow:GUI_ROW_MARKET_END]; } [gui setColor:[gui colorFromSetting:kGuiMarketFilterInfoColor defaultValue:[OOColor greenColor]] forRow:GUI_ROW_MARKET_END]; [self showMarketCashAndLoadLine]; [gui setSelectableRange:NSMakeRange(start_row,row - start_row)]; [gui setSelectedRow:active_row]; [gui setShowTextCursor:NO]; } [[UNIVERSE gameView] clearMouse]; [self setShowDemoShips:NO]; [UNIVERSE enterGUIViewModeWithMouseInteraction:YES]; if (guiChanged) { [gui setForegroundTextureKey:[self status] == STATUS_DOCKED ? @"docked_overlay" : @"overlay"]; [gui setBackgroundTextureKey:@"market"]; [self noteGUIDidChangeFrom:oldScreen to:gui_screen]; } } - (void) setGuiToMarketInfoScreen { OOCommodityMarket *localMarket = [self localMarket]; GuiDisplayGen *gui = [UNIVERSE gui]; OOGUIScreenID oldScreen = gui_screen; gui_screen = GUI_SCREEN_MARKETINFO; BOOL guiChanged = (oldScreen != gui_screen); [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:YES]; // fix problems with economies in witchspace if (localMarket == nil) { localMarket = [[UNIVERSE commodities] generateBlankMarket]; } // following changed to work whether docked or not NSArray *goods = [self applyMarketSorter:[self applyMarketFilter:[localMarket goods] onMarket:localMarket] onMarket:localMarket]; NSUInteger i, j, commodityCount = [shipCommodityData count]; OOCargoQuantity quantityInHold[commodityCount]; for (i = 0; i < commodityCount; i++) { quantityInHold[i] = [shipCommodityData quantityForGood:[goods oo_stringAtIndex:i]]; } for (i = 0; i < [cargo count]; i++) { ShipEntity *container = [cargo objectAtIndex:i]; j = [goods indexOfObject:[container commodityType]]; quantityInHold[j] += [container commodityAmount]; } // GUI stuff { if (EXPECT_NOT(marketSelectedCommodity == nil)) { j = NSNotFound; } else { j = [goods indexOfObject:marketSelectedCommodity]; } if (j == NSNotFound) { DESTROY(marketSelectedCommodity); [self setGuiToMarketScreen]; return; } [gui clearAndKeepBackground:!guiChanged]; [gui setTitle:[NSString stringWithFormat:DESC(@"oolite-commodity-information-@"), [shipCommodityData nameForGood:marketSelectedCommodity]]]; [self showMarketScreenHeaders]; [self showMarketScreenDataLine:GUI_ROW_MARKET_START forGood:marketSelectedCommodity inMarket:localMarket holdQuantity:quantityInHold[j]]; OOCargoQuantity contracted = [self contractedVolumeForGood:marketSelectedCommodity]; if (contracted > 0) { OOMassUnit unit = [shipCommodityData massUnitForGood:marketSelectedCommodity]; [gui setColor:[gui colorFromSetting:kGuiMarketContractedColor defaultValue:nil] forRow:GUI_ROW_MARKET_START+1]; [gui setText:[NSString stringWithFormat:DESC(@"oolite-commodity-contracted-d-@"), contracted, DisplayStringForMassUnit(unit)] forRow:GUI_ROW_MARKET_START+1]; } NSString *info = [shipCommodityData commentForGood:marketSelectedCommodity]; OOGUIRow i = 0; if (info == nil || [info length] == 0) { i = [gui addLongText:DESC(@"oolite-commodity-no-comment") startingAtRow:GUI_ROW_MARKET_START+2 align:GUI_ALIGN_LEFT]; } else { i = [gui addLongText:info startingAtRow:GUI_ROW_MARKET_START+2 align:GUI_ALIGN_LEFT]; } for (i-- ; i > GUI_ROW_MARKET_START+2 ; --i) { [gui setColor:[gui colorFromSetting:kGuiMarketDescriptionColor defaultValue:nil] forRow:i]; } [self showMarketCashAndLoadLine]; } [[UNIVERSE gameView] clearMouse]; [self setShowDemoShips:NO]; [UNIVERSE enterGUIViewModeWithMouseInteraction:YES]; if (guiChanged) { [gui setForegroundTextureKey:[self status] == STATUS_DOCKED ? @"docked_overlay" : @"overlay"]; [gui setBackgroundTextureKey:@"marketinfo"]; [self noteGUIDidChangeFrom:oldScreen to:gui_screen]; } } - (void) showMarketCashAndLoadLine { GuiDisplayGen *gui = [UNIVERSE gui]; OOCargoQuantity currentCargo = current_cargo; OOCargoQuantity cargoCapacity = [self maxAvailableCargoSpace]; [gui setText:OOExpandKey(@"market-cash-and-load", credits, currentCargo, cargoCapacity) forRow:GUI_ROW_MARKET_CASH]; [gui setColor:[gui colorFromSetting:kGuiMarketCashColor defaultValue:[OOColor yellowColor]] forRow:GUI_ROW_MARKET_CASH]; } - (OOGUIScreenID) guiScreen { return gui_screen; } - (BOOL) tryBuyingCommodity:(OOCommodityType)index all:(BOOL)all { if ([index isEqualToString:@"<<<"] || [index isEqualToString:@">>>"]) { ++marketOffset; return NO; } if (![self isDocked]) return NO; // can't buy if not docked. OOCommodityMarket *localMarket = [self localMarket]; OOCreditsQuantity pricePerUnit = [localMarket priceForGood:index]; OOMassUnit unit = [localMarket massUnitForGood:index]; if (specialCargo != nil && unit == UNITS_TONS) { return NO; // can't buy tons of stuff when carrying a specialCargo } int manifest_quantity = [shipCommodityData quantityForGood:index]; int market_quantity = [localMarket quantityForGood:index]; int purchase = 1; if (all) { // if cargo contracts, put a break point on the contract volume int contracted = [self contractedVolumeForGood:index]; if (manifest_quantity >= contracted) { purchase = [localMarket capacityForGood:index]; } else { purchase = contracted-manifest_quantity; } } if (purchase > market_quantity) { purchase = market_quantity; // limit to what's available } if (purchase * pricePerUnit > credits) { purchase = floor (credits / pricePerUnit); // limit to what's affordable } // TODO - fix brokenness here... if (unit == UNITS_TONS && purchase + current_cargo > [self maxAvailableCargoSpace]) { purchase = [self availableCargoSpace]; // limit to available cargo space } else { if (current_cargo == [self maxAvailableCargoSpace]) { // other cases are fine so long as buying is limited to <1000kg / <1000000g // but if this case is true, we need to see if there is more space in // the manifest (safe) or an already-accounted-for pod if (unit == UNITS_KILOGRAMS) { if (manifest_quantity % KILOGRAMS_PER_POD <= MAX_KILOGRAMS_IN_SAFE && (manifest_quantity + purchase) % KILOGRAMS_PER_POD > MAX_KILOGRAMS_IN_SAFE) { // going from < n500 to >= n500 would increase pods needed by 1 purchase = MAX_KILOGRAMS_IN_SAFE - manifest_quantity; // max possible } } else // UNITS_GRAMS { if (manifest_quantity % GRAMS_PER_POD <= MAX_GRAMS_IN_SAFE && (manifest_quantity + purchase) % GRAMS_PER_POD > MAX_GRAMS_IN_SAFE) { // going from < n500000 to >= n500000 would increase pods needed by 1 purchase = MAX_GRAMS_IN_SAFE - manifest_quantity; // max possible } } } } if (purchase <= 0) { return NO; // stop if that results in nothing to be bought } [localMarket removeQuantity:purchase forGood:index]; [shipCommodityData addQuantity:purchase forGood:index]; credits -= pricePerUnit * purchase; [self calculateCurrentCargo]; if ([UNIVERSE autoSave]) [UNIVERSE setAutoSaveNow:YES]; [self doScriptEvent:OOJSID("playerBoughtCargo") withArguments:[NSArray arrayWithObjects:index, [NSNumber numberWithInt:purchase], [NSNumber numberWithUnsignedLongLong:pricePerUnit], nil]]; if ([localMarket exportLegalityForGood:index] > 0) { [roleWeightFlags setObject:[NSNumber numberWithInt:1] forKey:@"bought-illegal"]; } else { [roleWeightFlags setObject:[NSNumber numberWithInt:1] forKey:@"bought-legal"]; } return YES; } - (BOOL) trySellingCommodity:(OOCommodityType)index all:(BOOL)all { if ([index isEqualToString:@"<<<"] || [index isEqualToString:@">>>"]) { --marketOffset; return NO; } if (![self isDocked]) return NO; // can't sell if not docked. OOCommodityMarket *localMarket = [self localMarket]; int available_units = [shipCommodityData quantityForGood:index]; int pricePerUnit = [localMarket priceForGood:index]; if (available_units == 0) return NO; int market_quantity = [localMarket quantityForGood:index]; int capacity = [localMarket capacityForGood:index]; int sell = 1; if (all) { // if cargo contracts, put a break point on the contract volume int contracted = [self contractedVolumeForGood:index]; if (available_units <= contracted) { sell = capacity; } else { sell = available_units-contracted; } } if (sell > available_units) sell = available_units; // limit to what's in the hold if (sell + market_quantity > capacity) sell = capacity - market_quantity; // avoid flooding the market if (sell <= 0) return NO; // stop if that results in nothing to be sold [localMarket addQuantity:sell forGood:index]; [shipCommodityData removeQuantity:sell forGood:index]; credits += pricePerUnit * sell; [self calculateCurrentCargo]; if ([UNIVERSE autoSave]) [UNIVERSE setAutoSaveNow:YES]; [self doScriptEvent:OOJSID("playerSoldCargo") withArguments:[NSArray arrayWithObjects:index, [NSNumber numberWithInt:sell], [NSNumber numberWithInt: pricePerUnit], nil]]; return YES; } - (BOOL) isMining { return using_mining_laser; } - (OOSpeechSettings) isSpeechOn { return isSpeechOn; } - (BOOL) canAddEquipment:(NSString *)equipmentKey inContext:(NSString *)context { if ([equipmentKey isEqualToString:@"EQ_RENOVATION"] && !(ship_trade_in_factor < 85 || [[[self shipSubEntityEnumerator] allObjects] count] < [self maxShipSubEntities])) return NO; if (![super canAddEquipment:equipmentKey inContext:context]) return NO; NSArray *conditions = [[OOEquipmentType equipmentTypeWithIdentifier:equipmentKey] conditions]; if (conditions != nil && ![self scriptTestConditions:conditions]) return NO; return YES; } - (BOOL) addEquipmentItem:(NSString *)equipmentKey inContext:(NSString *)context { return [self addEquipmentItem:equipmentKey withValidation:YES inContext:context]; } - (BOOL) addEquipmentItem:(NSString *)equipmentKey withValidation:(BOOL)validateAddition inContext:(NSString *)context { // deal with trumbles.. if ([equipmentKey isEqualToString:@"EQ_TRUMBLE"]) { /* Bug fix: must return here if eqKey == @"EQ_TRUMBLE", even if trumbleCount >= 1. Otherwise, the player becomes immune to trumbles. See comment in -setCommanderDataFromDictionary: for more details. -- Ahruman 2008-12-04 */ // the old trumbles will kill the new one if there are enough of them. if ((trumbleCount < PLAYER_MAX_TRUMBLES / 6) || (trumbleCount < PLAYER_MAX_TRUMBLES / 3 && ranrot_rand() % 2 > 0)) { [self addTrumble:trumble[ranrot_rand() % PLAYER_MAX_TRUMBLES]]; // randomise its looks. return YES; } return NO; } BOOL OK = [super addEquipmentItem:equipmentKey withValidation:validateAddition inContext:context]; if (OK) { if ([self hasEquipmentItemProviding:@"EQ_ADVANCED_COMPASS"] && [self compassMode] == COMPASS_MODE_BASIC) { [self setCompassMode:COMPASS_MODE_PLANET]; } [self addEqScriptForKey:equipmentKey]; } return OK; } - (void) removeEquipmentItem:(NSString *)equipmentKey { [self removeEqScriptForKey:equipmentKey]; if(![self hasEquipmentItemProviding:@"EQ_ADVANCED_COMPASS"] && [self compassMode] != COMPASS_MODE_BASIC) { [self setCompassMode:COMPASS_MODE_BASIC]; } [super removeEquipmentItem:equipmentKey]; } - (void) addEquipmentFromCollection:(id)equipment { NSDictionary *dict = nil; NSEnumerator *eqEnum = nil; NSString *eqDesc = nil; NSUInteger i, count; // Pass 1: Load the entire collection. if ([equipment isKindOfClass:[NSDictionary class]]) { dict = equipment; eqEnum = [equipment keyEnumerator]; } else if ([equipment isKindOfClass:[NSArray class]] || [equipment isKindOfClass:[NSSet class]]) { eqEnum = [equipment objectEnumerator]; } else if ([equipment isKindOfClass:[NSString class]]) { eqEnum = [[NSArray arrayWithObject:equipment] objectEnumerator]; } else { return; } while ((eqDesc = [eqEnum nextObject])) { /* Bug workaround: extra_equipment should never contain EQ_TRUMBLE, which is basically a magic flag passed to awardEquipment: to infect the player. However, prior to Oolite 1.70.1, if the player had a trumble infection and awardEquipment:EQ_TRUMBLE was called, an EQ_TRUMBLE would be added to the equipment list. Subsequent calls to awardEquipment:EQ_TRUMBLE would exit early because there was an EQ_TRUMBLE in the equipment list. as a result, it would no longer be possible to infect the player after the current infection ended. The bug is fixed in 1.70.1. The following line is to fix old saved games which had been "corrupted" by the bug. -- Ahruman 2007-12-04 */ if ([eqDesc isEqualToString:@"EQ_TRUMBLE"]) continue; // Traditional form is a dictionary of booleans; we only accept those where the value is true. if (dict != nil && ![dict oo_boolForKey:eqDesc]) continue; // We need to add the entire collection without validation first and then remove the items that are // not compliant (like items that do not satisfy the requiresEquipment criterion). This is to avoid // unintentionally excluding valid equipment, just because the required equipment existed but had // not been yet added to the equipment list at the time of the canAddEquipment validation check. // Nikos, 20080817. count = [dict oo_unsignedIntegerForKey:eqDesc]; for (i=0;i 0; } return NO; } - (BOOL) hasPrimaryWeapon:(OOWeaponType)weaponType { if ([[forward_weapon_type identifier] isEqualToString:[weaponType identifier]] || [[aft_weapon_type identifier] isEqualToString:[weaponType identifier]] || [[port_weapon_type identifier] isEqualToString:[weaponType identifier]] || [[starboard_weapon_type identifier] isEqualToString:[weaponType identifier]]) { return YES; } return [super hasPrimaryWeapon:weaponType]; } - (BOOL) removeExternalStore:(OOEquipmentType *)eqType { NSString *identifier = [eqType identifier]; // Look for matching missile. unsigned i; for (i = 0; i < max_missiles; i++) { if ([[self missileForPylon:i] hasPrimaryRole:identifier]) { [self removeFromPylon:i]; // Just remove one at a time. return YES; } } return NO; } - (BOOL) removeFromPylon:(NSUInteger)pylon { if (pylon >= max_missiles) return NO; if (missile_entity[pylon] != nil) { NSString *identifier = [missile_entity[pylon] primaryRole]; [super removeExternalStore:[OOEquipmentType equipmentTypeWithIdentifier:identifier]]; // Remove the missile (must wait until we've finished with its identifier string!) [missile_entity[pylon] release]; missile_entity[pylon] = nil; [self tidyMissilePylons]; // This should be the currently selected missile, deselect it. if (pylon <= activeMissile) { if (activeMissile == missiles && missiles > 0) activeMissile--; if (activeMissile > 0) activeMissile--; else activeMissile = max_missiles - 1; [self selectNextMissile]; } return YES; } return NO; } - (NSUInteger) parcelCount { return [parcels count]; } - (NSUInteger) passengerCount { return [passengers count]; } - (NSUInteger) passengerCapacity { return max_passengers; } - (BOOL) hasHostileTarget { ShipEntity *playersTarget = [self primaryTarget]; return ([playersTarget isShip] && [playersTarget hasHostileTarget] && [playersTarget primaryTarget] == self); } - (void) receiveCommsMessage:(NSString *) message_text from:(ShipEntity *) other { if ([self status] == STATUS_DEAD || [self status] == STATUS_DOCKED) { // only when in flight return; } [UNIVERSE addCommsMessage:[NSString stringWithFormat:@"%@:\n %@", [other displayName], message_text] forCount:4.5]; [super receiveCommsMessage:message_text from:other]; } - (void) getFined { if (legalStatus == 0) return; // nothing to pay for OOGovernmentID local_gov = [[UNIVERSE currentSystemData] oo_intForKey:KEY_GOVERNMENT]; if ([UNIVERSE inInterstellarSpace]) local_gov = 1; // equivalent to Feudal. I'm assuming any station in interstellar space is military. -- Ahruman 2008-05-29 OOCreditsQuantity fine = 500 + ((local_gov < 2 || local_gov > 5) ? 500 : 0); fine *= legalStatus; if (fine > credits) { int payback = (int)(legalStatus * credits / fine); [self setBounty:(legalStatus-payback) withReason:kOOLegalStatusReasonPaidFine]; credits = 0; } else { [self setBounty:0 withReason:kOOLegalStatusReasonPaidFine]; credits -= fine; } // one of the fined-@-credits strings includes expansion tokens NSString *fined_message = [NSString stringWithFormat:OOExpandKey(@"fined-@-credits"), OOCredits(fine)]; [self addMessageToReport:fined_message]; [UNIVERSE forceWitchspaceEntries]; ship_clock_adjust += 24 * 3600; // take up a day } - (void) adjustTradeInFactorBy:(int)value { ship_trade_in_factor += value; if (ship_trade_in_factor < 75) ship_trade_in_factor = 75; if (ship_trade_in_factor > 100) ship_trade_in_factor = 100; } - (int) tradeInFactor { return ship_trade_in_factor; } - (double) renovationCosts { // 5% of value of ships wear + correction for missing subentities. OOCreditsQuantity shipValue = [UNIVERSE tradeInValueForCommanderDictionary:[self commanderDataDictionary]]; double costs = 0.005 * (100 - ship_trade_in_factor) * shipValue; costs += 0.01 * shipValue * [self missingSubEntitiesAdjustment]; costs *= [self renovationFactor]; return cunningFee(costs, 0.05); } - (double) renovationFactor { OOShipRegistry *registry = [OOShipRegistry sharedRegistry]; NSDictionary *shipyardInfo = [registry shipyardInfoForKey:[self shipDataKey]]; return [shipyardInfo oo_doubleForKey:KEY_RENOVATION_MULTIPLIER defaultValue:1.0]; } - (void) setDefaultViewOffsets { float halfLength = 0.5f * (boundingBox.max.z - boundingBox.min.z); float halfWidth = 0.5f * (boundingBox.max.x - boundingBox.min.x); forwardViewOffset = make_vector(0.0f, 0.0f, boundingBox.max.z - halfLength); aftViewOffset = make_vector(0.0f, 0.0f, boundingBox.min.z + halfLength); portViewOffset = make_vector(boundingBox.min.x + halfWidth, 0.0f, 0.0f); starboardViewOffset = make_vector(boundingBox.max.x - halfWidth, 0.0f, 0.0f); customViewOffset = kZeroVector; } - (void) setDefaultCustomViews { NSArray *customViews = [[[OOShipRegistry sharedRegistry] shipInfoForKey:PLAYER_SHIP_DESC] oo_arrayForKey:@"custom_views"]; [_customViews release]; _customViews = nil; _customViewIndex = 0; if (customViews != nil) { _customViews = [customViews retain]; } } - (Vector) weaponViewOffset { switch (currentWeaponFacing) { case WEAPON_FACING_FORWARD: return forwardViewOffset; case WEAPON_FACING_AFT: return aftViewOffset; case WEAPON_FACING_PORT: return portViewOffset; case WEAPON_FACING_STARBOARD: return starboardViewOffset; case WEAPON_FACING_NONE: // N.b.: this case should never happen. return customViewOffset; } return kZeroVector; } - (void) setUpTrumbles { NSMutableString *trumbleDigrams = [NSMutableString stringWithCapacity:256]; unichar xchar = (unichar)0; unichar digramchars[2]; while ([trumbleDigrams length] < PLAYER_MAX_TRUMBLES + 2) { NSString *commanderName = [self commanderName]; if ([commanderName length] > 0) { [trumbleDigrams appendFormat:@"%@%@", commanderName, [[self mesh] modelName]]; } else { [trumbleDigrams appendString:@"Some Random Text!"]; } } int i; for (i = 0; i < PLAYER_MAX_TRUMBLES; i++) { digramchars[0] = ([trumbleDigrams characterAtIndex:i] & 0x007f) | 0x0020; digramchars[1] = (([trumbleDigrams characterAtIndex:i + 1] ^ xchar) & 0x007f) | 0x0020; xchar = digramchars[0]; NSString *digramstring = [NSString stringWithCharacters:digramchars length:2]; [trumble[i] release]; trumble[i] = [[OOTrumble alloc] initForPlayer:self digram:digramstring]; } trumbleCount = 0; [self setTrumbleAppetiteAccumulator:0.0f]; } - (void) addTrumble:(OOTrumble *)papaTrumble { if (trumbleCount >= PLAYER_MAX_TRUMBLES) { return; } OOTrumble *trumblePup = trumble[trumbleCount]; [trumblePup spawnFrom:papaTrumble]; trumbleCount++; } - (void) removeTrumble:(OOTrumble *)deadTrumble { if (trumbleCount <= 0) { return; } NSUInteger trumble_index = NSNotFound; NSUInteger i; for (i = 0; (trumble_index == NSNotFound)&&(i < trumbleCount); i++) { if (trumble[i] == deadTrumble) trumble_index = i; } if (trumble_index == NSNotFound) { OOLog(@"trumble.zombie", @"DEBUG can't get rid of inactive trumble %@", deadTrumble); return; } trumbleCount--; // reduce number of trumbles trumble[trumble_index] = trumble[trumbleCount]; // swap with the current last trumble trumble[trumbleCount] = deadTrumble; // swap with the current last trumble } - (OOTrumble**) trumbleArray { return trumble; } - (NSUInteger) trumbleCount { return trumbleCount; } - (id)trumbleValue { NSString *namekey = [NSString stringWithFormat:@"%@-humbletrash", [self commanderName]]; int trumbleHash; clear_checksum(); [self mungChecksumWithNSString:[self commanderName]]; munge_checksum(credits); munge_checksum(ship_kills); trumbleHash = munge_checksum(trumbleCount); [[NSUserDefaults standardUserDefaults] setInteger:trumbleHash forKey:namekey]; int i; NSMutableArray *trumbleArray = [NSMutableArray arrayWithCapacity:PLAYER_MAX_TRUMBLES]; for (i = 0; i < PLAYER_MAX_TRUMBLES; i++) { [trumbleArray addObject:[trumble[i] dictionary]]; } return [NSArray arrayWithObjects:[NSNumber numberWithUnsignedInteger:trumbleCount], [NSNumber numberWithInt:trumbleHash], trumbleArray, nil]; } - (void) setTrumbleValueFrom:(NSObject*) trumbleValue { BOOL info_failed = NO; int trumbleHash; int putativeHash = 0; int putativeNTrumbles = 0; NSArray *putativeTrumbleArray = nil; int i; NSString *namekey = [NSString stringWithFormat:@"%@-humbletrash", [self commanderName]]; [self setUpTrumbles]; if (trumbleValue) { BOOL possible_cheat = NO; if (![trumbleValue isKindOfClass:[NSArray class]]) info_failed = YES; else { NSArray* values = (NSArray*) trumbleValue; if ([values count] >= 1) putativeNTrumbles = [values oo_intAtIndex:0]; if ([values count] >= 2) putativeHash = [values oo_intAtIndex:1]; if ([values count] >= 3) putativeTrumbleArray = [values oo_arrayAtIndex:2]; } // calculate a hash for the putative values clear_checksum(); [self mungChecksumWithNSString:[self commanderName]]; munge_checksum(credits); munge_checksum(ship_kills); trumbleHash = munge_checksum(putativeNTrumbles); if (putativeHash != trumbleHash) info_failed = YES; if (info_failed) { OOLog(@"cheat.tentative", @"POSSIBLE CHEAT DETECTED"); possible_cheat = YES; } for (i = 1; (info_failed)&&(i < PLAYER_MAX_TRUMBLES); i++) { // try to determine trumbleCount from the key in the saved game clear_checksum(); [self mungChecksumWithNSString:[self commanderName]]; munge_checksum(credits); munge_checksum(ship_kills); trumbleHash = munge_checksum(i); if (putativeHash == trumbleHash) { info_failed = NO; putativeNTrumbles = i; } } if (possible_cheat && !info_failed) OOLog(@"cheat.verified", @"CHEAT DEFEATED - that's not the way to get rid of trumbles!"); } if (info_failed && [[NSUserDefaults standardUserDefaults] objectForKey:namekey]) { // try to determine trumbleCount from the key in user defaults putativeHash = (int)[[NSUserDefaults standardUserDefaults] integerForKey:namekey]; for (i = 1; (info_failed)&&(i < PLAYER_MAX_TRUMBLES); i++) { clear_checksum(); [self mungChecksumWithNSString:[self commanderName]]; munge_checksum(credits); munge_checksum(ship_kills); trumbleHash = munge_checksum(i); if (putativeHash == trumbleHash) { info_failed = NO; putativeNTrumbles = i; } } if (!info_failed) OOLog(@"cheat.verified", @"CHEAT DEFEATED - that's not the way to get rid of trumbles!"); } // at this stage we've done the best we can to stop cheaters trumbleCount = putativeNTrumbles; if ((putativeTrumbleArray != nil) && ([putativeTrumbleArray count] == PLAYER_MAX_TRUMBLES)) { for (i = 0; i < PLAYER_MAX_TRUMBLES; i++) [trumble[i] setFromDictionary:[putativeTrumbleArray oo_dictionaryAtIndex:i]]; } clear_checksum(); [self mungChecksumWithNSString:[self commanderName]]; munge_checksum(credits); munge_checksum(ship_kills); trumbleHash = munge_checksum(trumbleCount); [[NSUserDefaults standardUserDefaults] setInteger:trumbleHash forKey:namekey]; } - (float) trumbleAppetiteAccumulator { return _trumbleAppetiteAccumulator; } - (void) setTrumbleAppetiteAccumulator:(float)value { _trumbleAppetiteAccumulator = value; } - (void) mungChecksumWithNSString:(NSString *)str { if (str == nil) return; NSUInteger i, length = [str length]; for (i = 0; i < length; i++) { munge_checksum([str characterAtIndex:i]); } } - (NSString *) screenModeStringForWidth:(unsigned)width height:(unsigned)height refreshRate:(float)refreshRate { if (0.0f != refreshRate) { return OOExpandKey(@"gameoptions-fullscreen-with-refresh-rate", width, height, refreshRate); } else { return OOExpandKey(@"gameoptions-fullscreen", width, height); } } - (void) suppressTargetLost { suppressTargetLost = YES; } - (void) setScoopsActive { scoopsActive = YES; } // override shipentity to stop foundTarget being changed during escape sequence - (void) setFoundTarget:(Entity *) targetEntity { /* Rare, but can happen, e.g. if a Q-mine goes off nearby during * the sequence */ if ([self status] == STATUS_ESCAPE_SEQUENCE) { return; } [_foundTarget release]; _foundTarget = [targetEntity weakRetain]; } // override shipentity addTarget to implement target_memory - (void) addTarget:(Entity *) targetEntity { if ([self status] != STATUS_IN_FLIGHT && [self status] != STATUS_WITCHSPACE_COUNTDOWN) return; if (targetEntity == self) return; [super addTarget:targetEntity]; if ([targetEntity isWormhole]) { assert ([self hasEquipmentItemProviding:@"EQ_WORMHOLE_SCANNER"]); [self addScannedWormhole:(WormholeEntity*)targetEntity]; } // wormholes don't go in target memory else if ([self hasEquipmentItemProviding:@"EQ_TARGET_MEMORY"] && targetEntity != nil) { OOWeakReference *targetRef = [targetEntity weakSelf]; NSUInteger i = [target_memory indexOfObject:targetRef]; // if already in target memory, preserve that and just change the index if (i != NSNotFound) { target_memory_index = i; } else { i = [target_memory indexOfObject:[NSNull null]]; // find and use a blank space in memory if (i != NSNotFound) { [target_memory replaceObjectAtIndex:i withObject:targetRef]; target_memory_index = i; } else { // use the next memory space target_memory_index = (target_memory_index + 1) % PLAYER_TARGET_MEMORY_SIZE; [target_memory replaceObjectAtIndex:target_memory_index withObject:targetRef]; } } } if (ident_engaged) { [self playIdentLockedOn]; [self printIdentLockedOnForMissile:NO]; } else if ([targetEntity isShip] && [self weaponsOnline]) // Only let missiles target-lock onto ships { if ([missile_entity[activeMissile] isMissile]) { missile_status = MISSILE_STATUS_TARGET_LOCKED; [missile_entity[activeMissile] addTarget:targetEntity]; [self playMissileLockedOn]; [self printIdentLockedOnForMissile:YES]; } else // It's a mine or something { missile_status = MISSILE_STATUS_ARMED; [self playIdentLockedOn]; [self printIdentLockedOnForMissile:NO]; } } } - (void) clearTargetMemory { int i = 0; int j = [target_memory count]; for (i = 0; i < PLAYER_TARGET_MEMORY_SIZE; i++) { if (j > i) { [target_memory replaceObjectAtIndex:i withObject:[NSNull null]]; } else { [target_memory addObject:[NSNull null]]; } } target_memory_index = 0; } - (NSMutableArray *) targetMemory { return target_memory; } - (BOOL) moveTargetMemoryBy:(int)delta { unsigned i = 0; while (i++ < PLAYER_TARGET_MEMORY_SIZE) // limit loops { target_memory_index += delta; while (target_memory_index < 0) target_memory_index += PLAYER_TARGET_MEMORY_SIZE; while (target_memory_index >= PLAYER_TARGET_MEMORY_SIZE) target_memory_index -= PLAYER_TARGET_MEMORY_SIZE; id targ_id = [target_memory objectAtIndex:target_memory_index]; if ([targ_id isProxy]) { ShipEntity *potential_target = [(OOWeakReference *)targ_id weakRefUnderlyingObject]; if ((potential_target)&&(potential_target->isShip)&&([potential_target isInSpace])) { if (potential_target->zero_distance < SCANNER_MAX_RANGE2 && (![potential_target isCloaked])) { [super addTarget:potential_target]; if (missile_status != MISSILE_STATUS_SAFE) { if( [missile_entity[activeMissile] isMissile]) { [missile_entity[activeMissile] addTarget:potential_target]; missile_status = MISSILE_STATUS_TARGET_LOCKED; [self printIdentLockedOnForMissile:YES]; } else { missile_status = MISSILE_STATUS_ARMED; [self playIdentLockedOn]; [self printIdentLockedOnForMissile:NO]; } } else { ident_engaged = YES; [self printIdentLockedOnForMissile:NO]; } [self playTargetSwitched]; return YES; } } else { [target_memory replaceObjectAtIndex:target_memory_index withObject:[NSNull null]]; } } } [self playNoTargetInMemory]; return NO; } - (void) printIdentLockedOnForMissile:(BOOL)missile { if ([self primaryTarget] == nil) return; NSString *fmt = missile ? @"missile-locked-onto-target" : @"ident-locked-onto-target"; NSString *target = [[self primaryTarget] identFromShip:self]; [UNIVERSE addMessage:OOExpandKey(fmt, target) forCount:4.5]; } - (Quaternion) customViewQuaternion { return customViewQuaternion; } - (OOMatrix) customViewMatrix { return customViewMatrix; } - (Vector) customViewOffset { return customViewOffset; } - (Vector) customViewForwardVector { return customViewForwardVector; } - (Vector) customViewUpVector { return customViewUpVector; } - (Vector) customViewRightVector { return customViewRightVector; } - (NSString *) customViewDescription { return customViewDescription; } - (void) resetCustomView { [self setCustomViewDataFromDictionary:[_customViews oo_dictionaryAtIndex:_customViewIndex] withScaling:NO]; } - (void) setCustomViewDataFromDictionary:(NSDictionary *)viewDict withScaling:(BOOL)withScaling { customViewMatrix = kIdentityMatrix; customViewOffset = kZeroVector; if (viewDict == nil) return; customViewQuaternion = [viewDict oo_quaternionForKey:@"view_orientation"]; customViewRightVector = vector_right_from_quaternion(customViewQuaternion); customViewUpVector = vector_up_from_quaternion(customViewQuaternion); customViewForwardVector = vector_forward_from_quaternion(customViewQuaternion); Quaternion q1 = customViewQuaternion; q1.w = -q1.w; customViewMatrix = OOMatrixForQuaternionRotation(q1); // easier to do the multiplication at this point than at load time if (withScaling) { customViewOffset = vector_multiply_scalar([viewDict oo_vectorForKey:@"view_position"],_scaleFactor); } else { // but don't do this when the custom view is set through JS customViewOffset = [viewDict oo_vectorForKey:@"view_position"]; } customViewDescription = [viewDict oo_stringForKey:@"view_description"]; NSString *facing = [[viewDict oo_stringForKey:@"weapon_facing"] lowercaseString]; if ([facing isEqual:@"aft"]) { currentWeaponFacing = WEAPON_FACING_AFT; } else if ([facing isEqual:@"port"]) { currentWeaponFacing = WEAPON_FACING_PORT; } else if ([facing isEqual:@"starboard"]) { currentWeaponFacing = WEAPON_FACING_STARBOARD; } else if ([facing isEqual:@"forward"]) { currentWeaponFacing = WEAPON_FACING_FORWARD; } // if the weapon facing is unset / unknown, // don't change current weapon facing! } - (BOOL) showInfoFlag { return show_info_flag; } - (NSDictionary *) missionOverlayDescriptor { return _missionOverlayDescriptor; } - (NSDictionary *) missionOverlayDescriptorOrDefault { NSDictionary *result = [self missionOverlayDescriptor]; if (result == nil) { if ([[self missionTitle] length] == 0) { result = [UNIVERSE screenTextureDescriptorForKey:@"mission_overlay_no_title"]; } else { result = [UNIVERSE screenTextureDescriptorForKey:@"mission_overlay_with_title"]; } } return result; } - (void) setMissionOverlayDescriptor:(NSDictionary *)descriptor { if (descriptor != _missionOverlayDescriptor) { [_missionOverlayDescriptor autorelease]; _missionOverlayDescriptor = [descriptor copy]; } } - (NSDictionary *) missionBackgroundDescriptor { return _missionBackgroundDescriptor; } - (NSDictionary *) missionBackgroundDescriptorOrDefault { NSDictionary *result = [self missionBackgroundDescriptor]; if (result == nil) { result = [UNIVERSE screenTextureDescriptorForKey:@"mission"]; } return result; } - (void) setMissionBackgroundDescriptor:(NSDictionary *)descriptor { if (descriptor != _missionBackgroundDescriptor) { [_missionBackgroundDescriptor autorelease]; _missionBackgroundDescriptor = [descriptor copy]; } } - (OOGUIBackgroundSpecial) missionBackgroundSpecial { return _missionBackgroundSpecial; } - (void) setMissionBackgroundSpecial:(NSString *)special { if (special == nil) { _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_NONE; } else if ([special isEqualToString:@"SHORT_RANGE_CHART"]) { _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_SHORT; } else if ([special isEqualToString:@"LONG_RANGE_CHART"]) { _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_LONG; } else if ([special isEqualToString:@"LONG_RANGE_CHART_SHORTEST"]) { if ([self hasEquipmentItemProviding:@"EQ_ADVANCED_NAVIGATIONAL_ARRAY"]) { _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_LONG_ANA_SHORTEST; } else { _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_LONG; } } else if ([special isEqualToString:@"LONG_RANGE_CHART_QUICKEST"]) { if ([self hasEquipmentItemProviding:@"EQ_ADVANCED_NAVIGATIONAL_ARRAY"]) { _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_LONG_ANA_QUICKEST; } else { _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_LONG; } } else { _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_NONE; } } - (void) setMissionExitScreen:(OOGUIScreenID)screen { _missionExitScreen = screen; } - (OOGUIScreenID) missionExitScreen { return _missionExitScreen; } - (NSDictionary *) equipScreenBackgroundDescriptor { return _equipScreenBackgroundDescriptor; } - (void) setEquipScreenBackgroundDescriptor:(NSDictionary *)descriptor { if (descriptor != _equipScreenBackgroundDescriptor) { [_equipScreenBackgroundDescriptor autorelease]; _equipScreenBackgroundDescriptor = [descriptor copy]; } } - (BOOL) scriptsLoaded { return worldScripts != nil && [worldScripts count] > 0; } - (NSArray *) worldScriptNames { return [worldScripts allKeys]; } - (NSDictionary *) worldScriptsByName { return [[worldScripts copy] autorelease]; } - (OOScript *) commodityScriptNamed:(NSString *)scriptName { if (scriptName == nil) { return nil; } OOScript *cscript = nil; if ((cscript = [commodityScripts objectForKey:scriptName])) { return cscript; } cscript = [OOScript jsScriptFromFileNamed:scriptName properties:nil]; if (cscript != nil) { // storing it in here retains it [commodityScripts setObject:cscript forKey:scriptName]; } else { OOLog(@"script.commodityScript.load",@"Could not load script %@",scriptName); } return cscript; } - (void) doScriptEvent:(jsid)message inContext:(JSContext *)context withArguments:(jsval *)argv count:(uintN)argc { [super doScriptEvent:message inContext:context withArguments:argv count:argc]; [self doWorldScriptEvent:message inContext:context withArguments:argv count:argc timeLimit:0.0]; } - (BOOL) doWorldEventUntilMissionScreen:(jsid)message { NSEnumerator *scriptEnum = [worldScripts objectEnumerator]; OOScript *theScript; // Check for the presence of report messages first. if (gui_screen != GUI_SCREEN_MISSION && [dockingReport length] > 0 && [self isDocked] && ![[self dockedStation] suppressArrivalReports]) { [self setGuiToDockingReportScreen]; // go here instead! [[UNIVERSE messageGUI] clear]; return YES; } JSContext *context = OOJSAcquireContext(); while ((theScript = [scriptEnum nextObject]) && gui_screen != GUI_SCREEN_MISSION && [self isDocked]) { [theScript callMethod:message inContext:context withArguments:NULL count:0 result:NULL]; } OOJSRelinquishContext(context); if (gui_screen == GUI_SCREEN_MISSION) { // remove any comms/console messages from the screen! [[UNIVERSE messageGUI] clear]; return YES; } return NO; } - (void) doWorldScriptEvent:(jsid)message inContext:(JSContext *)context withArguments:(jsval *)argv count:(uintN)argc timeLimit:(OOTimeDelta)limit { NSParameterAssert(context != NULL && JS_IsInRequest(context)); NSEnumerator *scriptEnum = nil; OOScript *theScript = nil; for (scriptEnum = [worldScripts objectEnumerator]; (theScript = [scriptEnum nextObject]); ) { OOJSStartTimeLimiterWithTimeLimit(limit); [theScript callMethod:message inContext:context withArguments:argv count:argc result:NULL]; OOJSStopTimeLimiter(); } } - (void) setGalacticHyperspaceBehaviour:(OOGalacticHyperspaceBehaviour)inBehaviour { if (GALACTIC_HYPERSPACE_BEHAVIOUR_UNKNOWN < inBehaviour && inBehaviour <= GALACTIC_HYPERSPACE_MAX) { galacticHyperspaceBehaviour = inBehaviour; } } - (OOGalacticHyperspaceBehaviour) galacticHyperspaceBehaviour { return galacticHyperspaceBehaviour; } - (void) setGalacticHyperspaceFixedCoords:(NSPoint)point { return [self setGalacticHyperspaceFixedCoordsX:OOClamp_0_max_f(round(point.x), 255.0f) y:OOClamp_0_max_f(round(point.y), 255.0f)]; } - (void) setGalacticHyperspaceFixedCoordsX:(unsigned char)x y:(unsigned char)y { galacticHyperspaceFixedCoords.x = x; galacticHyperspaceFixedCoords.y = y; } - (NSPoint) galacticHyperspaceFixedCoords { return galacticHyperspaceFixedCoords; } - (OOLongRangeChartMode) longRangeChartMode { return longRangeChartMode; } - (void) setLongRangeChartMode:(OOLongRangeChartMode) mode { longRangeChartMode = mode; } - (BOOL) scoopOverride { return scoopOverride; } - (void) setScoopOverride:(BOOL)newValue { scoopOverride = !!newValue; [self setScoopsActive]; } #if MASS_DEPENDENT_FUEL_PRICES - (GLfloat) fuelChargeRate { GLfloat rate = 1.0; // Standard charge rate. rate = [super fuelChargeRate]; // Experimental: the state of repair affects the fuel charge rate - more fuel needed for jumps, etc... if (EXPECT(ship_trade_in_factor <= 90 && ship_trade_in_factor >= 75)) { rate *= 2.0 - (ship_trade_in_factor / 100); // between 1.1x and 1.25x //OOLog(@"fuelPrices", @"\"%@\" - repair status: %d%%, adjusted rate to:%.2f)", [self shipDataKey], ship_trade_in_factor, rate); } return rate; } #endif - (void) setDockTarget:(ShipEntity *)entity { if ([entity isStation]) _dockTarget = [entity universalID]; else _dockTarget = NO_TARGET; //_dockTarget = [entity isStation] ? [entity universalID]: NO_TARGET; } - (NSString *) commanderName { return _commanderName; } - (NSString *) lastsaveName { return _lastsaveName; } - (void) setCommanderName:(NSString *)value { NSParameterAssert(value != nil); [_commanderName autorelease]; _commanderName = [value copy]; } - (void) setLastsaveName:(NSString *)value { NSParameterAssert(value != nil); [_lastsaveName autorelease]; _lastsaveName = [value copy]; } - (BOOL) isDocked { BOOL isDockedStatus = NO; switch ([self status]) { case STATUS_DOCKED: case STATUS_DOCKING: case STATUS_START_GAME: isDockedStatus = YES; break; // special case - can be either docked or not, so avoid safety check below case STATUS_RESTART_GAME: return NO; case STATUS_EFFECT: case STATUS_ACTIVE: case STATUS_COCKPIT_DISPLAY: case STATUS_TEST: case STATUS_INACTIVE: case STATUS_DEAD: case STATUS_IN_FLIGHT: case STATUS_AUTOPILOT_ENGAGED: case STATUS_LAUNCHING: case STATUS_WITCHSPACE_COUNTDOWN: case STATUS_ENTERING_WITCHSPACE: case STATUS_EXITING_WITCHSPACE: case STATUS_ESCAPE_SEQUENCE: case STATUS_IN_HOLD: case STATUS_BEING_SCOOPED: case STATUS_HANDLING_ERROR: break; //no default, so that we get notified by the compiler if something is missing } #ifndef NDEBUG // Sanity check if (isDockedStatus) { if ([self dockedStation] == nil) { //there are a number of possible current statuses, not just STATUS_DOCKED OOLogERR(kOOLogInconsistentState, @"status is %@, but dockedStation is nil; treating as not docked. %@", OOStringFromEntityStatus([self status]), @"This is an internal error, please report it."); [self setStatus:STATUS_IN_FLIGHT]; isDockedStatus = NO; } } else { if ([self dockedStation] != nil && [self status] != STATUS_LAUNCHING) { OOLogERR(kOOLogInconsistentState, @"status is %@, but dockedStation is not nil; treating as docked. %@", OOStringFromEntityStatus([self status]), @"This is an internal error, please report it."); [self setStatus:STATUS_DOCKED]; isDockedStatus = YES; } } #endif return isDockedStatus; } - (BOOL)clearedToDock { return dockingClearanceStatus > DOCKING_CLEARANCE_STATUS_REQUESTED || dockingClearanceStatus == DOCKING_CLEARANCE_STATUS_NOT_REQUIRED; } - (void)setDockingClearanceStatus:(OODockingClearanceStatus)newValue { dockingClearanceStatus = newValue; if (dockingClearanceStatus == DOCKING_CLEARANCE_STATUS_NONE) { targetDockStation = nil; } else if (dockingClearanceStatus == DOCKING_CLEARANCE_STATUS_REQUESTED || dockingClearanceStatus == DOCKING_CLEARANCE_STATUS_NOT_REQUIRED) { if ([[self primaryTarget] isStation]) { targetDockStation = [self primaryTarget]; } else { OOLog(@"player.badDockingTarget", @"Attempt to dock at %@.", [self primaryTarget]); targetDockStation = nil; dockingClearanceStatus = DOCKING_CLEARANCE_STATUS_NONE; } } } - (OODockingClearanceStatus)getDockingClearanceStatus { return dockingClearanceStatus; } - (void)penaltyForUnauthorizedDocking { OOCreditsQuantity amountToPay = 0; OOCreditsQuantity calculatedFine = credits * 0.05; OOCreditsQuantity maximumFine = 50000ULL; if ([self clearedToDock]) return; amountToPay = MIN(maximumFine, calculatedFine); credits -= amountToPay; [self addMessageToReport:[NSString stringWithFormat:DESC(@"station-docking-clearance-fined-@-cr"), OOCredits(amountToPay)]]; } // // Wormhole Scanner support functions // - (void)addScannedWormhole:(WormholeEntity*)whole { assert(scannedWormholes != nil); assert(whole != nil); // Only add if we don't have it already! NSEnumerator *wormholes = [scannedWormholes objectEnumerator]; WormholeEntity *wh = nil; while ((wh = [wormholes nextObject])) { if (wh == whole) return; } [whole setScannedAt:[self clockTimeAdjusted]]; [scannedWormholes addObject:whole]; } // Checks through our array of wormholes for any which have expired // If it is in the current system, spawn ships // Else remove it - (void)updateWormholes { assert(scannedWormholes != nil); if ([scannedWormholes count] == 0) return; double now = [self clockTimeAdjusted]; NSMutableArray * savedWormholes = [[NSMutableArray alloc] initWithCapacity:[scannedWormholes count]]; NSEnumerator * wormholes = [scannedWormholes objectEnumerator]; WormholeEntity *wh; while ((wh = (WormholeEntity*)[wormholes nextObject])) { // TODO: Start drawing wormhole exit a few seconds before the first // ship is disgorged. if ([wh arrivalTime] > now) { [savedWormholes addObject:wh]; } else if (NSEqualPoints(galaxy_coordinates, [wh destinationCoordinates])) { [wh disgorgeShips]; if ([[wh shipsInTransit] count] > 0) { [savedWormholes addObject:wh]; } } // Else wormhole has expired in another system, let it expire } [scannedWormholes release]; scannedWormholes = savedWormholes; } - (NSArray *) scannedWormholes { return [NSArray arrayWithArray:scannedWormholes]; } - (void) initialiseMissionDestinations:(NSDictionary *)destinations andLegacy:(NSArray *)legacy { NSEnumerator *keyEnum = nil; NSString *key = nil; id value = nil; /* same need to make inner objects mutable as in localPlanetInfoOverrides */ [missionDestinations release]; missionDestinations = [[NSMutableDictionary alloc] init]; for (keyEnum = [destinations keyEnumerator]; (key = [keyEnum nextObject]); ) { value = [destinations objectForKey:key]; if (value != nil) { if ([value isKindOfClass:[NSDictionary class]]) { value = [value mutableCopy]; [missionDestinations setObject:value forKey:key]; [value release]; } } } if (legacy != nil) { OOSystemID dest; NSNumber *legacyMarker; for (keyEnum = [legacy objectEnumerator]; (legacyMarker = [keyEnum nextObject]); ) { dest = [legacyMarker intValue]; [self addMissionDestinationMarker:[self defaultMarker:dest]]; } } } - (NSString *)markerKey:(NSDictionary *)marker { return [NSString stringWithFormat:@"%d-%@",[marker oo_intForKey:@"system"], [marker oo_stringForKey:@"name"]]; } - (void) addMissionDestinationMarker:(NSDictionary *)marker { NSDictionary *validated = [self validatedMarker:marker]; if (validated == nil) { return; } [missionDestinations setObject:validated forKey:[self markerKey:validated]]; } - (BOOL) removeMissionDestinationMarker:(NSDictionary *)marker { NSDictionary *validated = [self validatedMarker:marker]; if (validated == nil) { return NO; } BOOL result = NO; if ([missionDestinations objectForKey:[self markerKey:validated]] != nil) { result = YES; } [missionDestinations removeObjectForKey:[self markerKey:validated]]; return result; } - (NSMutableDictionary*) getMissionDestinations { return missionDestinations; } - (void) setLastShot:(OOLaserShotEntity *)shot { lastShot = shot; /* No need to retain it, since the universe will be for as long as we * reference it. This is needed because the laser shot update routine * is used to correct its position relative to the ship, but isn't * called by Universe on the frame the shot is added (because updates * never are) */ } #ifndef NDEBUG - (void)dumpSelfState { NSMutableArray *flags = nil; NSString *flagsString = nil; [super dumpSelfState]; OOLog(@"dumpState.playerEntity", @"Script time: %g", script_time); OOLog(@"dumpState.playerEntity", @"Script time check: %g", script_time_check); OOLog(@"dumpState.playerEntity", @"Script time interval: %g", script_time_interval); OOLog(@"dumpState.playerEntity", @"Roll/pitch/yaw delta: %g, %g, %g", roll_delta, pitch_delta, yaw_delta); OOLog(@"dumpState.playerEntity", @"Shield: %g fore, %g aft", forward_shield, aft_shield); OOLog(@"dumpState.playerEntity", @"Alert level: %u, flags: %#x", alertFlags, alertCondition); OOLog(@"dumpState.playerEntity", @"Missile status: %i", missile_status); OOLog(@"dumpState.playerEntity", @"Energy unit: %@", EnergyUnitTypeToString([self installedEnergyUnitType])); OOLog(@"dumpState.playerEntity", @"Fuel leak rate: %g", fuel_leak_rate); OOLog(@"dumpState.playerEntity", @"Trumble count: %lu", trumbleCount); flags = [NSMutableArray array]; #define ADD_FLAG_IF_SET(x) if (x) { [flags addObject:@#x]; } ADD_FLAG_IF_SET(found_equipment); ADD_FLAG_IF_SET(pollControls); ADD_FLAG_IF_SET(suppressTargetLost); ADD_FLAG_IF_SET(scoopsActive); ADD_FLAG_IF_SET(game_over); ADD_FLAG_IF_SET(finished); ADD_FLAG_IF_SET(bomb_detonated); ADD_FLAG_IF_SET(autopilot_engaged); ADD_FLAG_IF_SET(afterburner_engaged); ADD_FLAG_IF_SET(afterburnerSoundLooping); ADD_FLAG_IF_SET(hyperspeed_engaged); ADD_FLAG_IF_SET(travelling_at_hyperspeed); ADD_FLAG_IF_SET(hyperspeed_locked); ADD_FLAG_IF_SET(ident_engaged); ADD_FLAG_IF_SET(galactic_witchjump); ADD_FLAG_IF_SET(ecm_in_operation); ADD_FLAG_IF_SET(show_info_flag); ADD_FLAG_IF_SET(showDemoShips); ADD_FLAG_IF_SET(rolling); ADD_FLAG_IF_SET(pitching); ADD_FLAG_IF_SET(yawing); ADD_FLAG_IF_SET(using_mining_laser); ADD_FLAG_IF_SET(mouse_control_on); // ADD_FLAG_IF_SET(isSpeechOn); ADD_FLAG_IF_SET(keyboardRollOverride); // Handle keyboard roll... ADD_FLAG_IF_SET(keyboardPitchOverride); // ...and pitch override separately - (fix for BUG #17490) ADD_FLAG_IF_SET(keyboardYawOverride); ADD_FLAG_IF_SET(waitingForStickCallback); flagsString = [flags count] ? [flags componentsJoinedByString:@", "] : (NSString *)@"none"; OOLog(@"dumpState.playerEntity", @"Flags: %@", flagsString); } /* This method exists purely to suppress Clang static analyzer warnings that these ivars are unused (but may be used by categories, which they are). FIXME: there must be a feature macro we can use to avoid actually building this into the app, but I can't find it in docs. Mind you, we could suppress some of this by using civilized accessors. */ - (BOOL) suppressClangStuff { return missionChoice && commanderNameString && cdrDetailArray && currentPage && key_roll_left && key_roll_right && key_pitch_forward && key_pitch_back && key_yaw_left && key_yaw_right && key_view_forward && key_view_aft && key_view_port && key_view_starboard && key_gui_screen_status && key_gui_chart_screens && key_gui_system_data && key_gui_market && key_gui_arrow_left && key_gui_arrow_right && key_gui_arrow_up && key_gui_arrow_down && key_increase_speed && key_decrease_speed && key_inc_field_of_view && key_dec_field_of_view && key_inject_fuel && key_fire_lasers && key_launch_missile && key_next_missile && key_ecm && key_prime_equipment && key_activate_equipment && key_mode_equipment && key_fastactivate_equipment_a && key_fastactivate_equipment_b && key_target_missile && key_untarget_missile && key_target_incoming_missile && key_ident_system && key_scanner_zoom && key_scanner_unzoom && key_launch_escapepod && key_galactic_hyperspace && key_hyperspace && key_jumpdrive && key_dump_cargo && key_rotate_cargo && key_autopilot && key_autodock && key_snapshot && key_docking_music && key_advanced_nav_array && key_map_home && key_map_info && key_pausebutton && key_show_fps && key_mouse_control && key_hud_toggle && key_comms_log && key_prev_compass_mode && key_next_compass_mode && key_chart_highlight && key_next_target && key_previous_target && key_custom_view && key_cycle_mfd && key_switch_mfd && key_docking_clearance_request && key_dump_target_state && key_weapons_online_toggle && _sysInfoLight.x && selFunctionIdx && stickFunctions && showingLongRangeChart && _missionAllowInterrupt && _missionScreenID && _missionTitle && _missionTextEntry; } #endif @end NSComparisonResult marketSorterByName(id a, id b, void *context) { OOCommodityMarket *market = (OOCommodityMarket *)context; return [[market nameForGood:(OOCommodityType)a] compare:[market nameForGood:(OOCommodityType)b]]; } NSComparisonResult marketSorterByPrice(id a, id b, void *context) { OOCommodityMarket *market = (OOCommodityMarket *)context; int result = (int)[market priceForGood:(OOCommodityType)a] - (int)[market priceForGood:(OOCommodityType)b]; if (result < 0) { return NSOrderedAscending; } else if (result > 0) { return NSOrderedDescending; } else { return NSOrderedSame; } } NSComparisonResult marketSorterByQuantity(id a, id b, void *context) { OOCommodityMarket *market = (OOCommodityMarket *)context; int result = (int)[market quantityForGood:(OOCommodityType)a] - (int)[market quantityForGood:(OOCommodityType)b]; if (result < 0) { return NSOrderedAscending; } else if (result > 0) { return NSOrderedDescending; } else { return NSOrderedSame; } } NSComparisonResult marketSorterByMassUnit(id a, id b, void *context) { OOCommodityMarket *market = (OOCommodityMarket *)context; int result = (int)[market massUnitForGood:(OOCommodityType)a] - (int)[market massUnitForGood:(OOCommodityType)b]; if (result < 0) { return NSOrderedAscending; } else if (result > 0) { return NSOrderedDescending; } else { return NSOrderedSame; } } oolite-1.82/src/Core/Entities/PlayerEntityContracts.h000066400000000000000000000111211256642440500226710ustar00rootroot00000000000000/* PlayerEntityContracts.h Methods relating to passenger and cargo contract handling. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "PlayerEntity.h" #import "PlayerEntityLegacyScriptEngine.h" #import "GuiDisplayGen.h" #define PASSENGER_KEY_NAME @"name" #define CARGO_KEY_ID @"id" #define CARGO_KEY_TYPE @"co_type" #define CARGO_KEY_AMOUNT @"co_amount" #define CARGO_KEY_DESCRIPTION @"cargo_description" #define CONTRACT_KEY_START @"start" #define CONTRACT_KEY_DESTINATION @"destination" #define CONTRACT_KEY_DESTINATION_NAME @"destination_name" #define CONTRACT_KEY_LONG_DESCRIPTION @"long_description" #define CONTRACT_KEY_DEPARTURE_TIME @"departure_time" #define CONTRACT_KEY_ARRIVAL_TIME @"arrival_time" #define CONTRACT_KEY_FEE @"fee" #define CONTRACT_KEY_PREMIUM @"premium" #define CONTRACT_KEY_RISK @"risk" #define MAX_CONTRACT_REP 70 #define GUI_ROW_PASSENGERS_LABELS 1 #define GUI_ROW_PASSENGERS_START 2 #define GUI_ROW_CARGO_LABELS 8 #define GUI_ROW_CARGO_START 9 #define GUI_ROW_CONTRACT_INFO_START 15 #define GUI_ROW_SHIPYARD_LABELS 1 #define GUI_ROW_SHIPYARD_START 2 #define GUI_ROW_SHIPYARD_INFO_START 15 #define GUI_ROW_NO_SHIPS 10 #define MAX_ROWS_SHIPS_FOR_SALE 12 @interface PlayerEntity (Contracts) - (NSString *) processEscapePods; // removes pods from cargo bay and treats categories of characters carried - (NSString *) checkPassengerContracts; // returns messages from any passengers whose status have changed - (NSDictionary *) reputation; - (int) passengerReputation; - (void) increasePassengerReputation:(unsigned)amount; - (void) decreasePassengerReputation:(unsigned)amount; - (int) parcelReputation; - (void) increaseParcelReputation:(unsigned)amount; - (void) decreaseParcelReputation:(unsigned)amount; - (int) contractReputation; - (void) increaseContractReputation:(unsigned)amount; - (void) decreaseContractReputation:(unsigned)amount; - (OOCargoQuantity) contractedVolumeForGood:(OOCommodityType) good; - (void) erodeReputation; - (void) normaliseReputation; - (void) addMessageToReport:(NSString*) report; // - (void) setGuiToContractsScreen; //- (BOOL) pickFromGuiContractsScreen; //- (void) highlightSystemFromGuiContractsScreen; - (BOOL) addPassenger:(NSString*)Name start:(unsigned)start destination:(unsigned)destination eta:(double)eta fee:(double)fee advance:(double)advance risk:(unsigned)risk; // for js scripting - (BOOL) removePassenger:(NSString*)Name; // for js scripting - (BOOL) addParcel:(NSString*)Name start:(unsigned)start destination:(unsigned)destination eta:(double)eta fee:(double)fee premium:(double)premium risk:(unsigned)risk; // for js scripting - (BOOL) removeParcel:(NSString*)Name; // for js scripting - (BOOL) awardContract:(unsigned)qty commodity:(NSString*)commodity start:(unsigned)start destination:(unsigned)destination eta:(double)eta fee:(double)fee premium:(double)premium; // for js scripting. - (BOOL) removeContract:(NSString*)commodity destination:(unsigned)destination; // for js scripting - (NSArray *) passengerList; - (NSArray *) parcelList; - (NSArray *) contractList; - (void) setGuiToManifestScreen; - (void) setManifestScreenRow:(id)object inColor:(OOColor*)color forRow:(OOGUIRow)row ofRows:(OOGUIRow)max_rows andOffset:(OOGUIRow)offset inMultipage:(BOOL)multi; - (void) setGuiToDockingReportScreen; // ---------------------------------------------------------------------- // - (void) setGuiToShipyardScreen:(NSUInteger)skip; - (void) showShipyardModel:(NSString *)shipKey shipData:(NSDictionary *)shipDict personality:(uint16_t)personality; - (void) showShipyardInfoForSelection; - (NSInteger) missingSubEntitiesAdjustment; - (void) showTradeInInformationFooter; - (OOCreditsQuantity) priceForShipKey:(NSString *)key; - (BOOL) buySelectedShip; - (BOOL) buyNamedShip:(NSString *)shipName; - (void) newShipCommonSetup:(NSString *)shipKey yardInfo:(NSDictionary *)ship_info baseInfo:(NSDictionary *)ship_base_dict; @end oolite-1.82/src/Core/Entities/PlayerEntityContracts.m000066400000000000000000001743231256642440500227140ustar00rootroot00000000000000/* PlayerEntityContracts.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "PlayerEntity.h" #import "PlayerEntityLegacyScriptEngine.h" #import "PlayerEntityContracts.h" #import "PlayerEntityControls.h" #import "ProxyPlayerEntity.h" #import "HeadUpDisplay.h" #import "ShipEntityAI.h" #import "Universe.h" #import "AI.h" #import "OOColor.h" #import "OOCharacter.h" #import "StationEntity.h" #import "GuiDisplayGen.h" #import "OOStringExpander.h" #import "OOStringParsing.h" #import "OOCollectionExtractors.h" #import "OOConstToString.h" #import "MyOpenGLView.h" #import "NSStringOOExtensions.h" #import "OOShipRegistry.h" #import "OOEquipmentType.h" #import "OOTexture.h" #import "OOJavaScriptEngine.h" static unsigned RepForRisk(unsigned risk); @interface PlayerEntity (ContractsPrivate) - (OOCreditsQuantity) tradeInValue; - (NSArray*) contractsListFromArray:(NSArray *) contracts_array forCargo:(BOOL) forCargo forParcels:(BOOL)forParcels; @end @implementation PlayerEntity (Contracts) - (NSString *) processEscapePods // removes pods from cargo bay and treats categories of characters carried { unsigned i; BOOL added_entry = NO; // to prevent empty lines for slaves and the rare empty report. NSMutableString *result = [NSMutableString string]; NSMutableArray *rescuees = [NSMutableArray array]; OOGovernmentID government = [[[UNIVERSE currentSystemData] objectForKey:KEY_GOVERNMENT] intValue]; if ([UNIVERSE inInterstellarSpace]) government = 1; // equivalent to Feudal. I'm assuming any station in interstellar space is military. -- Ahruman 2008-05-29 // step through the cargo removing crew from any escape pods // No enumerator because we're mutating the array -- Ahruman for (i = 0; i < [cargo count]; i++) { ShipEntity *cargoItem = [cargo objectAtIndex:i]; NSArray *podCrew = [cargoItem crew]; if (podCrew != nil) { // Has crew -> is escape pod. [rescuees addObjectsFromArray:podCrew]; [cargoItem setCrew:nil]; [cargo removeObjectAtIndex:i]; i--; } } // step through the rescuees awarding insurance or bounty or adding to slaves for (i = 0; i < [rescuees count]; i++) { OOCharacter *rescuee = [rescuees objectAtIndex:i]; if ([rescuee script]) { [rescuee doScriptEvent:OOJSID("unloadCharacter")]; } else if ([rescuee legacyScript]) { [self runUnsanitizedScriptActions:[rescuee legacyScript] allowingAIMethods:YES withContextName:[NSString stringWithFormat:@"", [rescuee name]] forTarget:nil]; } else if ([rescuee insuranceCredits] && [rescuee legalStatus]) { float reward = (5.0 + government) * [rescuee legalStatus]; float insurance = 10 * [rescuee insuranceCredits]; if (government > (Ranrot() & 7) || reward >= insurance) { // claim bounty for capture, ignore insurance [result appendFormat:DESC(@"capture-reward-for-@@-@-credits-@-alt"), [rescuee name], [rescuee shortDescription], OOStringFromDeciCredits(reward, YES, NO), OOStringFromDeciCredits(insurance, YES, NO)]; [self doScriptEvent:OOJSID("playerRescuedEscapePod") withArguments:[NSArray arrayWithObjects:[NSNumber numberWithUnsignedInteger:reward],@"bounty",[rescuee infoForScripting],nil]]; } else { // claim insurance reward with reduction of bounty [result appendFormat:DESC(@"rescue-reward-for-@@-@-credits-@-alt"), [rescuee name], [rescuee shortDescription], OOStringFromDeciCredits(insurance - reward, YES, NO), OOStringFromDeciCredits(reward, YES, NO)]; reward = insurance - reward; [self doScriptEvent:OOJSID("playerRescuedEscapePod") withArguments:[NSArray arrayWithObjects:[NSNumber numberWithUnsignedInteger:reward],@"insurance",[rescuee infoForScripting],nil]]; } credits += reward; added_entry = YES; } else if ([rescuee insuranceCredits]) { // claim insurance reward [result appendFormat:DESC(@"rescue-reward-for-@@-@-credits"), [rescuee name], [rescuee shortDescription], OOStringFromDeciCredits([rescuee insuranceCredits] * 10, YES, NO)]; credits += 10 * [rescuee insuranceCredits]; [self doScriptEvent:OOJSID("playerRescuedEscapePod") withArguments:[NSArray arrayWithObjects:[NSNumber numberWithUnsignedInteger:(10 * [rescuee insuranceCredits])],@"insurance",[rescuee infoForScripting],nil]]; added_entry = YES; } else if ([rescuee legalStatus]) { // claim bounty for capture float reward = (5.0 + government) * [rescuee legalStatus]; [result appendFormat:DESC(@"capture-reward-for-@@-@-credits"), [rescuee name], [rescuee shortDescription], OOStringFromDeciCredits(reward, YES, NO)]; credits += reward; [self doScriptEvent:OOJSID("playerRescuedEscapePod") withArguments:[NSArray arrayWithObjects:[NSNumber numberWithUnsignedInteger:reward],@"bounty",[rescuee infoForScripting],nil]]; added_entry = YES; } else { // sell as slave - increase no. of slaves in manifest [shipCommodityData addQuantity:1 forGood:@"slaves"]; [self doScriptEvent:OOJSID("playerRescuedEscapePod") withArguments:[NSArray arrayWithObjects:[NSNumber numberWithUnsignedInteger:0],@"slave",[rescuee infoForScripting],nil]]; } if ((i < [rescuees count] - 1) && added_entry) [result appendString:@"\n"]; added_entry = NO; } [self calculateCurrentCargo]; return result; } - (NSString *) checkPassengerContracts // returns messages from any passengers whose status have changed { if ([self dockedStation] != [UNIVERSE station]) // only drop off passengers or fulfil contracts at main station return nil; // check escape pods... // TODO NSMutableString *result = [NSMutableString string]; unsigned i; // check passenger contracts for (i = 0; i < [passengers count]; i++) { NSDictionary* passenger_info = [[passengers oo_dictionaryAtIndex:i] retain]; NSString* passenger_name = [passenger_info oo_stringForKey:PASSENGER_KEY_NAME]; int dest = [passenger_info oo_intForKey:CONTRACT_KEY_DESTINATION]; // the system name can change via script NSString* passenger_dest_name = [UNIVERSE getSystemName: dest]; int dest_eta = [passenger_info oo_doubleForKey:CONTRACT_KEY_ARRIVAL_TIME] - ship_clock; if (system_id == dest) { // we've arrived in system! if (dest_eta > 0) { // and in good time long long fee = [passenger_info oo_longLongForKey:CONTRACT_KEY_FEE]; while ((randf() < 0.75)&&(dest_eta > 3600)) // delivered with more than an hour to spare and a decent customer? { fee *= 110; // tip + 10% fee /= 100; dest_eta *= 0.5; } credits += 10 * fee; [result appendFormatLine:DESC(@"passenger-delivered-okay-@-@-@"), passenger_name, OOIntCredits(fee), passenger_dest_name]; if ([passenger_info oo_unsignedIntForKey:CONTRACT_KEY_RISK defaultValue:0] > 0) { [self addRoleToPlayer:@"trader-courier+"]; } [self increasePassengerReputation:RepForRisk([passenger_info oo_unsignedIntForKey:CONTRACT_KEY_RISK defaultValue:0])]; [passengers removeObjectAtIndex:i--]; [self doScriptEvent:OOJSID("playerCompletedContract") withArguments:[NSArray arrayWithObjects:@"passenger",@"success",[NSNumber numberWithUnsignedInteger:(10*fee)],passenger_info,nil]]; } else { // but we're late! long long fee = [passenger_info oo_longLongForKey:CONTRACT_KEY_FEE] / 2; // halve fare while (randf() < 0.5) // maybe halve fare a few times! fee /= 2; credits += 10 * fee; [result appendFormatLine:DESC(@"passenger-delivered-late-@-@-@"), passenger_name, OOIntCredits(fee), passenger_dest_name]; if ([passenger_info oo_unsignedIntForKey:CONTRACT_KEY_RISK defaultValue:0] > 0) { [self addRoleToPlayer:@"trader-courier+"]; } [passengers removeObjectAtIndex:i--]; [self doScriptEvent:OOJSID("playerCompletedContract") withArguments:[NSArray arrayWithObjects:@"passenger",@"late",[NSNumber numberWithUnsignedInteger:10*fee],passenger_info,nil]]; } } else { if (dest_eta < 0) { // we've run out of time! [result appendFormatLine:DESC(@"passenger-failed-@"), passenger_name]; [self decreasePassengerReputation:RepForRisk([passenger_info oo_unsignedIntForKey:CONTRACT_KEY_RISK defaultValue:0])]; [passengers removeObjectAtIndex:i--]; [self doScriptEvent:OOJSID("playerCompletedContract") withArguments:[NSArray arrayWithObjects:@"passenger",@"failed",[NSNumber numberWithUnsignedInteger:0],passenger_info,nil]]; } } [passenger_info release]; } // check parcel contracts for (i = 0; i < [parcels count]; i++) { NSDictionary* parcel_info = [[parcels oo_dictionaryAtIndex:i] retain]; NSString* parcel_name = [parcel_info oo_stringForKey:PASSENGER_KEY_NAME]; int dest = [parcel_info oo_intForKey:CONTRACT_KEY_DESTINATION]; int dest_eta = [parcel_info oo_doubleForKey:CONTRACT_KEY_ARRIVAL_TIME] - ship_clock; if (system_id == dest) { // we've arrived in system! if (dest_eta > 0) { // and in good time long long fee = [parcel_info oo_longLongForKey:CONTRACT_KEY_FEE]; while ((randf() < 0.75)&&(dest_eta > 86400)) // delivered with more than a day to spare and a decent customer? { // lower tips than passengers fee *= 110; // tip + 10% fee /= 100; dest_eta *= 0.5; } credits += 10 * fee; [result appendFormatLine:DESC(@"parcel-delivered-okay-@-@"), parcel_name, OOIntCredits(fee)]; [self increaseParcelReputation:RepForRisk([parcel_info oo_unsignedIntForKey:CONTRACT_KEY_RISK defaultValue:0])]; [parcels removeObjectAtIndex:i--]; if ([parcel_info oo_unsignedIntForKey:CONTRACT_KEY_RISK defaultValue:0] > 0) { [self addRoleToPlayer:@"trader-courier+"]; } [self doScriptEvent:OOJSID("playerCompletedContract") withArguments:[NSArray arrayWithObjects:@"parcel",@"success",[NSNumber numberWithUnsignedInteger:10*fee],parcel_info,nil]]; } else { // but we're late! long long fee = [parcel_info oo_longLongForKey:CONTRACT_KEY_FEE] / 2; // halve fare while (randf() < 0.5) // maybe halve fare a few times! fee /= 2; credits += 10 * fee; [result appendFormatLine:DESC(@"parcel-delivered-late-@-@"), parcel_name, OOIntCredits(fee)]; if ([parcel_info oo_unsignedIntForKey:CONTRACT_KEY_RISK defaultValue:0] > 0) { [self addRoleToPlayer:@"trader-courier+"]; } [parcels removeObjectAtIndex:i--]; [self doScriptEvent:OOJSID("playerCompletedContract") withArguments:[NSArray arrayWithObjects:@"parcel",@"late",[NSNumber numberWithUnsignedInteger:10*fee],parcel_info,nil]]; } } else { if (dest_eta < 0) { // we've run out of time! [result appendFormatLine:DESC(@"parcel-failed-@"), parcel_name]; [self decreaseParcelReputation:RepForRisk([parcel_info oo_unsignedIntForKey:CONTRACT_KEY_RISK defaultValue:0])]; [parcels removeObjectAtIndex:i--]; [self doScriptEvent:OOJSID("playerCompletedContract") withArguments:[NSArray arrayWithObjects:@"parcel",@"failed",[NSNumber numberWithUnsignedInteger:0],parcel_info,nil]]; } } [parcel_info release]; } // check cargo contracts for (i = 0; i < [contracts count]; i++) { NSDictionary* contract_info = [[contracts oo_dictionaryAtIndex:i] retain]; NSString* contract_cargo_desc = [contract_info oo_stringForKey:CARGO_KEY_DESCRIPTION]; int dest = [contract_info oo_intForKey:CONTRACT_KEY_DESTINATION]; int dest_eta = [contract_info oo_doubleForKey:CONTRACT_KEY_ARRIVAL_TIME] - ship_clock; if (system_id == dest) { // no longer needed // int premium = 10 * [contract_info oo_floatForKey:CONTRACT_KEY_PREMIUM]; int fee = 10 * [contract_info oo_floatForKey:CONTRACT_KEY_FEE]; OOCommodityType contract_cargo_type = [contract_info oo_stringForKey:CARGO_KEY_TYPE]; int contract_amount = [contract_info oo_intForKey:CARGO_KEY_AMOUNT]; int quantity_on_hand = [shipCommodityData quantityForGood:contract_cargo_type]; // we've arrived in system! if (dest_eta > 0) { // and in good time if (quantity_on_hand >= contract_amount) { // with the goods too! // remove the goods... [shipCommodityData removeQuantity:contract_amount forGood:contract_cargo_type]; // pay the premium and fee // credits += fee + premium; // not any more: all contracts initially awarded by JS, so fee // is now all that needs to be paid - CIM if ([shipCommodityData exportLegalityForGood:contract_cargo_type] > 0) { [self addRoleToPlayer:@"trader-smuggler"]; } else { [self addRoleToPlayer:@"trader"]; } credits += fee; [result appendFormatLine:DESC(@"cargo-delivered-okay-@-@"), contract_cargo_desc, OOCredits(fee)]; [contracts removeObjectAtIndex:i--]; // repute++ // +10 as cargo contracts don't have risk modifiers [self increaseContractReputation:10]; [self doScriptEvent:OOJSID("playerCompletedContract") withArguments:[NSArray arrayWithObjects:@"cargo",@"success",[NSNumber numberWithUnsignedInteger:fee],contract_info,nil]]; } else { // see if the amount of goods delivered is acceptable float percent_delivered = 100.0 * (float)quantity_on_hand/(float)contract_amount; float acceptable_ratio = 100.0 - 10.0 * system_id / 256.0; // down to 90% if (percent_delivered >= acceptable_ratio) { // remove the goods... [shipCommodityData setQuantity:0 forGood:contract_cargo_type]; // pay the fee int shortfall = 100 - percent_delivered; int payment = percent_delivered * (fee) / 100.0; credits += payment; if ([shipCommodityData exportLegalityForGood:contract_cargo_type] > 0) { [self addRoleToPlayer:@"trader-smuggler"]; } else { [self addRoleToPlayer:@"trader"]; } [result appendFormatLine:DESC(@"cargo-delivered-short-@-@-d"), contract_cargo_desc, OOCredits(payment), shortfall]; [contracts removeObjectAtIndex:i--]; // repute unchanged [self doScriptEvent:OOJSID("playerCompletedContract") withArguments:[NSArray arrayWithObjects:@"cargo",@"short",[NSNumber numberWithUnsignedInteger:payment],contract_info,nil]]; } else { [result appendFormatLine:DESC(@"cargo-refused-short-%@"), contract_cargo_desc]; // The player has still time to buy the missing goods elsewhere and fulfil the contract. } } } else { // but we're late! [result appendFormatLine:DESC(@"cargo-delivered-late-@"), contract_cargo_desc]; [contracts removeObjectAtIndex:i--]; // repute-- [self decreaseContractReputation:10]; [self doScriptEvent:OOJSID("playerCompletedContract") withArguments:[NSArray arrayWithObjects:@"cargo",@"late",[NSNumber numberWithUnsignedInteger:0],contract_info,nil]]; } } else { if (dest_eta < 0) { // we've run out of time! [result appendFormatLine:DESC(@"cargo-failed-@"), contract_cargo_desc]; [contracts removeObjectAtIndex:i--]; // repute-- [self decreaseContractReputation:10]; [self doScriptEvent:OOJSID("playerCompletedContract") withArguments:[NSArray arrayWithObjects:@"cargo",@"failed",[NSNumber numberWithUnsignedInteger:0],contract_info,nil]]; } } [contract_info release]; } // check passenger_record for expired contracts NSArray* names = [passenger_record allKeys]; for (i = 0; i < [names count]; i++) { double dest_eta = [passenger_record oo_doubleForKey:[names objectAtIndex:i]] - ship_clock; if (dest_eta < 0) { // check they're not STILL on board BOOL on_board = NO; unsigned j; for (j = 0; j < [passengers count]; j++) { NSDictionary* passenger_info = [passengers oo_dictionaryAtIndex:j]; if ([[passenger_info objectForKey:PASSENGER_KEY_NAME] isEqual:[names objectAtIndex:i]]) on_board = YES; } if (!on_board) { [passenger_record removeObjectForKey:[names objectAtIndex:i]]; } } } // check contract_record for expired contracts NSArray* ids = [contract_record allKeys]; for (i = 0; i < [ids count]; i++) { double dest_eta = [(NSNumber*)[contract_record objectForKey:[ids objectAtIndex:i]] doubleValue] - ship_clock; if (dest_eta < 0) { [contract_record removeObjectForKey:[ids objectAtIndex:i]]; } } // check parcel_record for expired deliveries ids = [parcel_record allKeys]; for (i = 0; i < [ids count]; i++) { double dest_eta = [(NSNumber*)[parcel_record objectForKey:[ids objectAtIndex:i]] doubleValue] - ship_clock; if (dest_eta < 0) { [parcel_record removeObjectForKey:[ids objectAtIndex:i]]; } } if ([result length] == 0) { result = nil; } else { // Should have a trailing \n [result deleteCharacterAtIndex:[result length] - 1]; } return result; } - (OOCargoQuantity) contractedVolumeForGood:(OOCommodityType) good { OOCargoQuantity total = 0; for (unsigned i = 0; i < [contracts count]; i++) { NSDictionary* contract_info = [contracts oo_dictionaryAtIndex:i]; OOCommodityType contract_cargo_type = [contract_info oo_stringForKey:CARGO_KEY_TYPE]; if ([good isEqualToString:contract_cargo_type]) { total += [contract_info oo_unsignedIntegerForKey:CARGO_KEY_AMOUNT]; } } return total; } - (void) addMessageToReport:(NSString*) report { if ([report length] != 0) { if ([dockingReport length] == 0) [dockingReport appendString:report]; else [dockingReport appendFormat:@"\n\n%@", report]; } } - (NSDictionary*) reputation { return reputation; } - (int) passengerReputation { int good = [reputation oo_intForKey:PASSAGE_GOOD_KEY]; int bad = [reputation oo_intForKey:PASSAGE_BAD_KEY]; int unknown = [reputation oo_intForKey:PASSAGE_UNKNOWN_KEY]; if (unknown > 0) unknown = MAX_CONTRACT_REP - (((2*unknown)+(market_rnd % unknown))/3); else unknown = MAX_CONTRACT_REP; return (good + unknown - 3 * bad) / 2; // return a number from -MAX_CONTRACT_REP to +MAX_CONTRACT_REP } - (void) increasePassengerReputation:(unsigned)amount { int good = [reputation oo_intForKey:PASSAGE_GOOD_KEY]; int bad = [reputation oo_intForKey:PASSAGE_BAD_KEY]; int unknown = [reputation oo_intForKey:PASSAGE_UNKNOWN_KEY]; for (unsigned i=0;i 0) { // shift a bean from bad to unknown bad--; if (unknown < MAX_CONTRACT_REP) unknown++; } else { // shift a bean from unknown to good if (unknown > 0) unknown--; if (good < MAX_CONTRACT_REP) good++; } } [reputation oo_setInteger:good forKey:PASSAGE_GOOD_KEY]; [reputation oo_setInteger:bad forKey:PASSAGE_BAD_KEY]; [reputation oo_setInteger:unknown forKey:PASSAGE_UNKNOWN_KEY]; } - (void) decreasePassengerReputation:(unsigned)amount { int good = [reputation oo_intForKey:PASSAGE_GOOD_KEY]; int bad = [reputation oo_intForKey:PASSAGE_BAD_KEY]; int unknown = [reputation oo_intForKey:PASSAGE_UNKNOWN_KEY]; for (unsigned i=0;i 0) { // shift a bean from good to bad good--; if (bad < MAX_CONTRACT_REP) bad++; } else { // shift a bean from unknown to bad if (unknown > 0) unknown--; if (bad < MAX_CONTRACT_REP) bad++; } } [reputation oo_setInteger:good forKey:PASSAGE_GOOD_KEY]; [reputation oo_setInteger:bad forKey:PASSAGE_BAD_KEY]; [reputation oo_setInteger:unknown forKey:PASSAGE_UNKNOWN_KEY]; } - (int) parcelReputation { int good = [reputation oo_intForKey:PARCEL_GOOD_KEY]; int bad = [reputation oo_intForKey:PARCEL_BAD_KEY]; int unknown = [reputation oo_intForKey:PARCEL_UNKNOWN_KEY]; if (unknown > 0) unknown = MAX_CONTRACT_REP - (((2*unknown)+(market_rnd % unknown))/3); else unknown = MAX_CONTRACT_REP; return (good + unknown - 3 * bad) / 2; // return a number from -MAX_CONTRACT_REP to +MAX_CONTRACT_REP } - (void) increaseParcelReputation:(unsigned)amount { int good = [reputation oo_intForKey:PARCEL_GOOD_KEY]; int bad = [reputation oo_intForKey:PARCEL_BAD_KEY]; int unknown = [reputation oo_intForKey:PARCEL_UNKNOWN_KEY]; for (unsigned i=0;i 0) { // shift a bean from bad to unknown bad--; if (unknown < MAX_CONTRACT_REP) unknown++; } else { // shift a bean from unknown to good if (unknown > 0) unknown--; if (good < MAX_CONTRACT_REP) good++; } } [reputation oo_setInteger:good forKey:PARCEL_GOOD_KEY]; [reputation oo_setInteger:bad forKey:PARCEL_BAD_KEY]; [reputation oo_setInteger:unknown forKey:PARCEL_UNKNOWN_KEY]; } - (void) decreaseParcelReputation:(unsigned)amount { int good = [reputation oo_intForKey:PARCEL_GOOD_KEY]; int bad = [reputation oo_intForKey:PARCEL_BAD_KEY]; int unknown = [reputation oo_intForKey:PARCEL_UNKNOWN_KEY]; for (unsigned i=0;i 0) { // shift a bean from good to bad good--; if (bad < MAX_CONTRACT_REP) bad++; } else { // shift a bean from unknown to bad if (unknown > 0) unknown--; if (bad < MAX_CONTRACT_REP) bad++; } } [reputation oo_setInteger:good forKey:PARCEL_GOOD_KEY]; [reputation oo_setInteger:bad forKey:PARCEL_BAD_KEY]; [reputation oo_setInteger:unknown forKey:PARCEL_UNKNOWN_KEY]; } - (int) contractReputation { int good = [reputation oo_intForKey:CONTRACTS_GOOD_KEY]; int bad = [reputation oo_intForKey:CONTRACTS_BAD_KEY]; int unknown = [reputation oo_intForKey:CONTRACTS_UNKNOWN_KEY]; if (unknown > 0) unknown = MAX_CONTRACT_REP - (((2*unknown)+(market_rnd % unknown))/3); else unknown = MAX_CONTRACT_REP; return (good + unknown - 3 * bad) / 2; // return a number from -MAX_CONTRACT_REP to +MAX_CONTRACT_REP } - (void) increaseContractReputation:(unsigned)amount { int good = [reputation oo_intForKey:CONTRACTS_GOOD_KEY]; int bad = [reputation oo_intForKey:CONTRACTS_BAD_KEY]; int unknown = [reputation oo_intForKey:CONTRACTS_UNKNOWN_KEY]; for (unsigned i=0;i 0) { // shift a bean from bad to unknown bad--; if (unknown < MAX_CONTRACT_REP) unknown++; } else { // shift a bean from unknown to good if (unknown > 0) unknown--; if (good < MAX_CONTRACT_REP) good++; } } [reputation oo_setInteger:good forKey:CONTRACTS_GOOD_KEY]; [reputation oo_setInteger:bad forKey:CONTRACTS_BAD_KEY]; [reputation oo_setInteger:unknown forKey:CONTRACTS_UNKNOWN_KEY]; } - (void) decreaseContractReputation:(unsigned)amount { int good = [reputation oo_intForKey:CONTRACTS_GOOD_KEY]; int bad = [reputation oo_intForKey:CONTRACTS_BAD_KEY]; int unknown = [reputation oo_intForKey:CONTRACTS_UNKNOWN_KEY]; for (unsigned i=0;i 0) { // shift a bean from good to bad good--; if (bad < MAX_CONTRACT_REP) bad++; } else { // shift a bean from unknown to bad if (unknown > 0) unknown--; if (bad < MAX_CONTRACT_REP) bad++; } } [reputation oo_setInteger:good forKey:CONTRACTS_GOOD_KEY]; [reputation oo_setInteger:bad forKey:CONTRACTS_BAD_KEY]; [reputation oo_setInteger:unknown forKey:CONTRACTS_UNKNOWN_KEY]; } - (void) erodeReputation { int c_good = [reputation oo_intForKey:CONTRACTS_GOOD_KEY]; int c_bad = [reputation oo_intForKey:CONTRACTS_BAD_KEY]; int c_unknown = [reputation oo_intForKey:CONTRACTS_UNKNOWN_KEY]; int p_good = [reputation oo_intForKey:PASSAGE_GOOD_KEY]; int p_bad = [reputation oo_intForKey:PASSAGE_BAD_KEY]; int p_unknown = [reputation oo_intForKey:PASSAGE_UNKNOWN_KEY]; int pl_good = [reputation oo_intForKey:PARCEL_GOOD_KEY]; int pl_bad = [reputation oo_intForKey:PARCEL_BAD_KEY]; int pl_unknown = [reputation oo_intForKey:PARCEL_UNKNOWN_KEY]; if (c_unknown < MAX_CONTRACT_REP) { if (c_bad > 0) c_bad--; else { if (c_good > 0) c_good--; } c_unknown++; } if (p_unknown < MAX_CONTRACT_REP) { if (p_bad > 0) p_bad--; else { if (p_good > 0) p_good--; } p_unknown++; } if (pl_unknown < MAX_CONTRACT_REP) { if (pl_bad > 0) pl_bad--; else { if (pl_good > 0) pl_good--; } pl_unknown++; } [reputation setObject:[NSNumber numberWithInt:c_good] forKey:CONTRACTS_GOOD_KEY]; [reputation setObject:[NSNumber numberWithInt:c_bad] forKey:CONTRACTS_BAD_KEY]; [reputation setObject:[NSNumber numberWithInt:c_unknown] forKey:CONTRACTS_UNKNOWN_KEY]; [reputation setObject:[NSNumber numberWithInt:p_good] forKey:PASSAGE_GOOD_KEY]; [reputation setObject:[NSNumber numberWithInt:p_bad] forKey:PASSAGE_BAD_KEY]; [reputation setObject:[NSNumber numberWithInt:p_unknown] forKey:PASSAGE_UNKNOWN_KEY]; [reputation setObject:[NSNumber numberWithInt:pl_good] forKey:PARCEL_GOOD_KEY]; [reputation setObject:[NSNumber numberWithInt:pl_bad] forKey:PARCEL_BAD_KEY]; [reputation setObject:[NSNumber numberWithInt:pl_unknown] forKey:PARCEL_UNKNOWN_KEY]; } /* Update reputation levels in case of change in MAX_CONTRACT_REP */ - (void) normaliseReputation { int c_good = [reputation oo_intForKey:CONTRACTS_GOOD_KEY]; int c_bad = [reputation oo_intForKey:CONTRACTS_BAD_KEY]; int c_unknown = [reputation oo_intForKey:CONTRACTS_UNKNOWN_KEY]; int p_good = [reputation oo_intForKey:PASSAGE_GOOD_KEY]; int p_bad = [reputation oo_intForKey:PASSAGE_BAD_KEY]; int p_unknown = [reputation oo_intForKey:PASSAGE_UNKNOWN_KEY]; int pl_good = [reputation oo_intForKey:PARCEL_GOOD_KEY]; int pl_bad = [reputation oo_intForKey:PARCEL_BAD_KEY]; int pl_unknown = [reputation oo_intForKey:PARCEL_UNKNOWN_KEY]; int c = c_good + c_bad + c_unknown; if (c == 0) { c_unknown = MAX_CONTRACT_REP; } else if (c != MAX_CONTRACT_REP) { c_good = c_good * MAX_CONTRACT_REP / c; c_bad = c_bad * MAX_CONTRACT_REP / c; c_unknown = MAX_CONTRACT_REP - c_good - c_bad; } int p = p_good + p_bad + p_unknown; if (p == 0) { p_unknown = MAX_CONTRACT_REP; } else if (p != MAX_CONTRACT_REP) { p_good = p_good * MAX_CONTRACT_REP / p; p_bad = p_bad * MAX_CONTRACT_REP / p; p_unknown = MAX_CONTRACT_REP - p_good - p_bad; } int pl = pl_good + pl_bad + pl_unknown; if (pl == 0) { pl_unknown = MAX_CONTRACT_REP; } else if (pl != MAX_CONTRACT_REP) { pl_good = pl_good * MAX_CONTRACT_REP / pl; pl_bad = pl_bad * MAX_CONTRACT_REP / pl; pl_unknown = MAX_CONTRACT_REP - pl_good - pl_bad; } [reputation setObject:[NSNumber numberWithInt:c_good] forKey:CONTRACTS_GOOD_KEY]; [reputation setObject:[NSNumber numberWithInt:c_bad] forKey:CONTRACTS_BAD_KEY]; [reputation setObject:[NSNumber numberWithInt:c_unknown] forKey:CONTRACTS_UNKNOWN_KEY]; [reputation setObject:[NSNumber numberWithInt:p_good] forKey:PASSAGE_GOOD_KEY]; [reputation setObject:[NSNumber numberWithInt:p_bad] forKey:PASSAGE_BAD_KEY]; [reputation setObject:[NSNumber numberWithInt:p_unknown] forKey:PASSAGE_UNKNOWN_KEY]; [reputation setObject:[NSNumber numberWithInt:pl_good] forKey:PARCEL_GOOD_KEY]; [reputation setObject:[NSNumber numberWithInt:pl_bad] forKey:PARCEL_BAD_KEY]; [reputation setObject:[NSNumber numberWithInt:pl_unknown] forKey:PARCEL_UNKNOWN_KEY]; } - (BOOL) addPassenger:(NSString*)Name start:(unsigned)start destination:(unsigned)Destination eta:(double)eta fee:(double)fee advance:(double)advance risk:(unsigned)risk { NSDictionary* passenger_info = [NSDictionary dictionaryWithObjectsAndKeys: Name, PASSENGER_KEY_NAME, [NSNumber numberWithInt:start], CONTRACT_KEY_START, [NSNumber numberWithInt:Destination], CONTRACT_KEY_DESTINATION, [NSNumber numberWithDouble:[PLAYER clockTime]], CONTRACT_KEY_DEPARTURE_TIME, [NSNumber numberWithDouble:eta], CONTRACT_KEY_ARRIVAL_TIME, [NSNumber numberWithDouble:fee], CONTRACT_KEY_FEE, [NSNumber numberWithDouble:advance], CONTRACT_KEY_PREMIUM, [NSNumber numberWithUnsignedInt:risk], CONTRACT_KEY_RISK, NULL]; // extra checks, just in case. if ([passengers count] >= max_passengers || [passenger_record objectForKey:Name] != nil) return NO; if (risk > 1) { [self addRoleToPlayer:@"trader-courier+"]; } [passengers addObject:passenger_info]; [passenger_record setObject:[NSNumber numberWithDouble:eta] forKey:Name]; [self doScriptEvent:OOJSID("playerEnteredContract") withArguments:[NSArray arrayWithObjects:@"passenger",passenger_info,nil]]; return YES; } - (BOOL) removePassenger:(NSString*)Name // removes the first passenger that answers to Name, returns NO if none found { // extra check, just in case. if ([passengers count] == 0) return NO; unsigned i; for (i = 0; i < [passengers count]; i++) { NSString *this_name = [[passengers oo_dictionaryAtIndex:i] oo_stringForKey:PASSENGER_KEY_NAME]; if ([Name isEqualToString:this_name]) { [passengers removeObjectAtIndex:i]; [passenger_record removeObjectForKey:Name]; return YES; } } return NO; } - (BOOL) addParcel:(NSString*)Name start:(unsigned)start destination:(unsigned)Destination eta:(double)eta fee:(double)fee premium:(double)premium risk:(unsigned)risk { NSDictionary* parcel_info = [NSDictionary dictionaryWithObjectsAndKeys: Name, PASSENGER_KEY_NAME, [NSNumber numberWithInt:start], CONTRACT_KEY_START, [NSNumber numberWithInt:Destination], CONTRACT_KEY_DESTINATION, [NSNumber numberWithDouble:[PLAYER clockTime]], CONTRACT_KEY_DEPARTURE_TIME, [NSNumber numberWithDouble:eta], CONTRACT_KEY_ARRIVAL_TIME, [NSNumber numberWithDouble:fee], CONTRACT_KEY_FEE, [NSNumber numberWithDouble:premium], CONTRACT_KEY_PREMIUM, [NSNumber numberWithUnsignedInt:risk], CONTRACT_KEY_RISK, NULL]; // extra checks, just in case. if ([parcel_record objectForKey:Name] != nil) return NO; if (risk > 1) { [self addRoleToPlayer:@"trader-courier+"]; } [parcels addObject:parcel_info]; [parcel_record setObject:[NSNumber numberWithDouble:eta] forKey:Name]; [self doScriptEvent:OOJSID("playerEnteredContract") withArguments:[NSArray arrayWithObjects:@"parcel",parcel_info,nil]]; return YES; } - (BOOL) removeParcel:(NSString*)Name // removes the first parcel that answers to Name, returns NO if none found { // extra check, just in case. if ([parcels count] == 0) return NO; unsigned i; for (i = 0; i < [parcels count]; i++) { NSString *this_name = [[parcels oo_dictionaryAtIndex:i] oo_stringForKey:PASSENGER_KEY_NAME]; if ([Name isEqualToString:this_name]) { [parcels removeObjectAtIndex:i]; [parcel_record removeObjectForKey:Name]; return YES; } } return NO; } - (BOOL) awardContract:(unsigned)qty commodity:(OOCommodityType)type start:(unsigned)start destination:(unsigned)Destination eta:(double)eta fee:(double)fee premium:(double)premium { unsigned sr1 = Ranrot()&0x111111; int sr2 = Ranrot()&0x111111; NSString *cargo_ID =[NSString stringWithFormat:@"%06x-%06x", sr1, sr2]; if (![[UNIVERSE commodities] goodDefined:type]) return NO; if (qty < 1) return NO; // avoid duplicate cargo_IDs while ([contract_record objectForKey:cargo_ID] != nil) { sr2++; cargo_ID =[NSString stringWithFormat:@"%06x-%06x", sr1, sr2]; } NSDictionary* cargo_info = [NSDictionary dictionaryWithObjectsAndKeys: cargo_ID, CARGO_KEY_ID, type, CARGO_KEY_TYPE, [NSNumber numberWithInt:qty], CARGO_KEY_AMOUNT, [UNIVERSE describeCommodity:type amount:qty], CARGO_KEY_DESCRIPTION, [NSNumber numberWithInt:start], CONTRACT_KEY_START, [NSNumber numberWithInt:Destination], CONTRACT_KEY_DESTINATION, [NSNumber numberWithDouble:[PLAYER clockTime]], CONTRACT_KEY_DEPARTURE_TIME, [NSNumber numberWithDouble:eta], CONTRACT_KEY_ARRIVAL_TIME, [NSNumber numberWithDouble:fee], CONTRACT_KEY_FEE, [NSNumber numberWithDouble:premium], CONTRACT_KEY_PREMIUM, NULL]; // check available space OOCargoQuantity cargoSpaceRequired = qty; OOMassUnit contractCargoUnits = [shipCommodityData massUnitForGood:type]; if (contractCargoUnits == UNITS_KILOGRAMS) cargoSpaceRequired /= 1000; if (contractCargoUnits == UNITS_GRAMS) cargoSpaceRequired /= 1000000; if (cargoSpaceRequired > [self availableCargoSpace]) return NO; [shipCommodityData addQuantity:qty forGood:type]; current_cargo = [self cargoQuantityOnBoard]; if ([shipCommodityData exportLegalityForGood:type] > 0) { [self addRoleToPlayer:@"trader-smuggler"]; [roleWeightFlags setObject:[NSNumber numberWithInt:1] forKey:@"bought-illegal"]; } else { [self addRoleToPlayer:@"trader"]; [roleWeightFlags setObject:[NSNumber numberWithInt:1] forKey:@"bought-legal"]; } [contracts addObject:cargo_info]; [contract_record setObject:[NSNumber numberWithDouble:eta] forKey:cargo_ID]; [self doScriptEvent:OOJSID("playerEnteredContract") withArguments:[NSArray arrayWithObjects:@"cargo",cargo_info,nil]]; return YES; } - (BOOL) removeContract:(OOCommodityType)type destination:(unsigned)dest // removes the first match found, returns NO if none found { if ([contracts count] == 0 || dest > 255) return NO; if (![[UNIVERSE commodities] goodDefined:type]) return NO; unsigned i; for (i = 0; i < [contracts count]; i++) { NSDictionary *contractInfo = [contracts oo_dictionaryAtIndex:i]; unsigned cargoDest = [contractInfo oo_intForKey:CONTRACT_KEY_DESTINATION]; OOCommodityType cargoType = [contractInfo oo_stringForKey:CARGO_KEY_TYPE]; if ([cargoType isEqualToString:type] && cargoDest == dest) { [contract_record removeObjectForKey:[contractInfo oo_stringForKey:CARGO_KEY_ID]]; [contracts removeObjectAtIndex:i]; return YES; } } return NO; } - (NSArray*) passengerList { return [self contractsListFromArray:passengers forCargo:NO forParcels:NO]; } - (NSArray*) parcelList { return [self contractsListFromArray:parcels forCargo:NO forParcels:YES]; } - (NSArray*) contractList { return [self contractsListFromArray:contracts forCargo:YES forParcels:NO]; } - (NSArray*) contractsListFromArray:(NSArray *) contracts_array forCargo:(BOOL) forCargo forParcels:(BOOL)forParcels { // check contracts NSMutableArray *result = [NSMutableArray arrayWithCapacity:5]; NSString *formatString = (forCargo||forParcels) ? @"oolite-manifest-item-delivery" : @"oolite-manifest-person-travelling"; unsigned i; for (i = 0; i < [contracts_array count]; i++) { NSDictionary* contract_info = (NSDictionary *)[contracts_array objectAtIndex:i]; NSString* label = [contract_info oo_stringForKey:forCargo ? CARGO_KEY_DESCRIPTION : PASSENGER_KEY_NAME]; // the system name can change via script. The following PASSENGER_KEYs are identical to the corresponding CONTRACT_KEYs NSString* destination = [UNIVERSE getSystemName: [contract_info oo_intForKey:CONTRACT_KEY_DESTINATION]]; int dest_eta = [contract_info oo_doubleForKey:CONTRACT_KEY_ARRIVAL_TIME] - ship_clock; NSString *deadline = [UNIVERSE shortTimeDescription:dest_eta]; OOCreditsQuantity fee = [contract_info oo_intForKey:CONTRACT_KEY_FEE]; [result addObject:OOExpandKey(formatString, label, destination, deadline, fee)]; } return result; } // only use within setGuiToManifestScreen #define SET_MANIFEST_ROW(obj,color,row) ([self setManifestScreenRow:obj inColor:color forRow:row ofRows:max_rows andOffset:page_offset inMultipage:multi_page]) - (void) setGuiToManifestScreen { OOGUIScreenID oldScreen = gui_screen; GuiDisplayGen *gui = [UNIVERSE gui]; gui_screen = GUI_SCREEN_MANIFEST; BOOL guiChanged = (oldScreen != gui_screen); if (guiChanged) { [gui setStatusPage:0]; // need to do this earlier than the rest } // GUI stuff { NSInteger current, max; OOColor *subheadColor = [gui colorFromSetting:kGuiManifestSubheadColor defaultValue:[OOColor greenColor]]; OOColor *entryColor = [gui colorFromSetting:kGuiManifestEntryColor defaultValue:nil]; OOColor *scrollColor = [gui colorFromSetting:kGuiManifestScrollColor defaultValue:[OOColor greenColor]]; OOColor *noScrollColor = [gui colorFromSetting:kGuiManifestNoScrollColor defaultValue:[OOColor darkGrayColor]]; NSArray* cargoManifest = [self cargoList]; NSArray* missionsManifest = [self missionsList]; NSUInteger i = 0; NSUInteger max_rows = 20; NSUInteger manifestCount = [cargoManifest count]; NSUInteger cargoRowCount = (manifestCount + 1)/2; OOGUIRow cargoRow = 2; OOGUIRow missionsRow = 2; OOGUIRow nextPageRow = MANIFEST_SCREEN_ROW_NEXT; // show extra lines if no HUD is displayed. if ([[self hud] isHidden] || [[self hud] allowBigGui]) { max_rows += 7; nextPageRow += 7; } NSUInteger mmRows = 0; id mmEntry = nil; foreach (mmEntry, missionsManifest) { if ([mmEntry isKindOfClass:[NSString class]]) { ++mmRows; } else if ([mmEntry isKindOfClass:[NSArray class]]) { mmRows += [(NSArray *)mmEntry count]; } } NSInteger page_offset = 0; BOOL multi_page = NO; // NSUInteger total_rows = cargoRowCount + MAX(1U,[passengerManifest count]) + MAX(1U,[contractManifest count]) + mmRows + MAX(1U,[parcelManifest count]) + 5; NSUInteger total_rows = cargoRowCount + mmRows + 5; if (total_rows > max_rows) { max_rows -= 2; page_offset = ([gui statusPage]-1) * max_rows; if (page_offset < 0 || (NSUInteger)page_offset >= total_rows) { [gui setStatusPage:0]; page_offset = 0; } multi_page = YES; } OOGUITabSettings tab_stops; tab_stops[0] = 0; tab_stops[1] = 256; [gui overrideTabs:tab_stops from:kGuiManifestTabs length:3]; [gui setTabStops:tab_stops]; // Cargo Manifest current_cargo = [self cargoQuantityOnBoard]; [gui clearAndKeepBackground:!guiChanged]; [gui setTitle:DESC(@"manifest-title")]; current = current_cargo; max = [self maxAvailableCargoSpace]; NSString *cargoString = OOExpandKey(@"oolite-manifest-cargo", current, max); current = [[self passengerList] count]; max = max_passengers; NSString *cabinString = OOExpandKey(@"oolite-manifest-cabins", current, max); NSArray *manifestHeader = [NSArray arrayWithObjects:cargoString,cabinString,nil]; SET_MANIFEST_ROW( manifestHeader , entryColor, cargoRow - 1); if (manifestCount > 0) { for (i = 0; i < cargoRowCount; i++) { NSMutableArray* row_info = [NSMutableArray arrayWithCapacity:3]; // i is always smaller than manifest_count, no need to test. [row_info addObject:[cargoManifest objectAtIndex:i]]; if (i + cargoRowCount < manifestCount) { [row_info addObject:[cargoManifest objectAtIndex:i + cargoRowCount]]; } else { [row_info addObject:@""]; } SET_MANIFEST_ROW( (NSArray *)row_info, subheadColor, cargoRow + i); } } else { SET_MANIFEST_ROW( (DESC(@"manifest-none")), subheadColor, cargoRow); cargoRowCount=1; } missionsRow = cargoRow + cargoRowCount + 1; // Missions Manifest manifestCount = [missionsManifest count]; if (manifestCount > 0) { if ([[missionsManifest objectAtIndex:0] isKindOfClass:[NSString class]]) { // then there's at least one without its own heading // to go under the generic 'missions' heading SET_MANIFEST_ROW( (DESC(@"manifest-missions")) , entryColor, missionsRow - 1); } else { missionsRow--; } NSUInteger mmRow = 0; for (i = 0; i < manifestCount; i++) { NSString *mmItem = nil; mmEntry = [missionsManifest objectAtIndex:i]; if ([mmEntry isKindOfClass:[NSString class]]) { mmItem = [NSString stringWithFormat:@"\t%@",(NSString *)mmEntry]; SET_MANIFEST_ROW( (mmItem) , subheadColor, missionsRow + mmRow); ++mmRow; } else if ([mmEntry isKindOfClass:[NSArray class]]) { BOOL isHeading = YES; foreach (mmItem, mmEntry) { if (isHeading) { SET_MANIFEST_ROW( ((NSString *)mmItem) , entryColor , missionsRow + mmRow); } else { mmItem = [NSString stringWithFormat:@"\t%@",(NSString *)mmItem]; SET_MANIFEST_ROW( ((NSString *)mmItem) , subheadColor , missionsRow + mmRow); } isHeading = NO; ++mmRow; } } } } if (multi_page) { OOGUIRow r_start = MANIFEST_SCREEN_ROW_BACK; OOGUIRow r_end = nextPageRow; if (page_offset > 0) { [gui setColor:scrollColor forRow:MANIFEST_SCREEN_ROW_BACK]; [gui setKey:GUI_KEY_OK forRow:MANIFEST_SCREEN_ROW_BACK]; } else { [gui setColor:noScrollColor forRow:MANIFEST_SCREEN_ROW_BACK]; r_start = nextPageRow; } [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-back"), @" <-- ",nil] forRow:MANIFEST_SCREEN_ROW_BACK]; if (total_rows > max_rows + page_offset) { [gui setColor:scrollColor forRow:nextPageRow]; [gui setKey:GUI_KEY_OK forRow:nextPageRow]; } else { [gui setColor:noScrollColor forRow:nextPageRow]; r_end = MANIFEST_SCREEN_ROW_BACK; } [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-more"), @" --> ",nil] forRow:nextPageRow]; [gui setSelectableRange:NSMakeRange(r_start,r_end+1-r_start)]; [gui setSelectedRow:r_start]; } [gui setShowTextCursor:NO]; } /* ends */ if (lastTextKey) { [lastTextKey release]; lastTextKey = nil; } [self setShowDemoShips:NO]; [UNIVERSE enterGUIViewModeWithMouseInteraction:NO]; if (guiChanged) { [gui setForegroundTextureKey:[self status] == STATUS_DOCKED ? @"docked_overlay" : @"overlay"]; [gui setBackgroundTextureKey:@"manifest"]; [self noteGUIDidChangeFrom:oldScreen to:gui_screen]; } } - (void) setManifestScreenRow:(id)object inColor:(OOColor*)color forRow:(OOGUIRow)row ofRows:(OOGUIRow)max_rows andOffset:(OOGUIRow)offset inMultipage:(BOOL)multi { OOGUIRow disp_row = row - offset; if (disp_row < 1 || disp_row > max_rows) return; if (multi) disp_row++; GuiDisplayGen *gui = [UNIVERSE gui]; if ([object isKindOfClass:[NSString class]]) { [gui setText:(NSString*)object forRow:disp_row]; } else if ([object isKindOfClass:[NSArray class]]) { [gui setArray:(NSArray*)object forRow:disp_row]; } [gui setColor:color forRow:disp_row]; } - (void) setGuiToDockingReportScreen { GuiDisplayGen *gui = [UNIVERSE gui]; OOGUIScreenID oldScreen = gui_screen; gui_screen = GUI_SCREEN_REPORT; BOOL guiChanged = (oldScreen != gui_screen); OOGUIRow i, text_row = 1; [dockingReport setString:[dockingReport stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]]; // GUI stuff { [gui clearAndKeepBackground:!guiChanged]; [gui setTitle:OOExpandKey(@"arrival-report-title")]; for (i=1;i<=18;i++) { [gui setColor:[gui colorFromSetting:kGuiDockingReportColor defaultValue:nil] forRow:21]; } // dockingReport might be a multi-line message while (([dockingReport length] > 0)&&(text_row < 18)) { if ([dockingReport rangeOfString:@"\n"].location != NSNotFound) { while (([dockingReport rangeOfString:@"\n"].location != NSNotFound)&&(text_row < 18)) { NSUInteger line_break = [dockingReport rangeOfString:@"\n"].location; NSString* line = [dockingReport substringToIndex:line_break]; [dockingReport deleteCharactersInRange: NSMakeRange( 0, line_break + 1)]; text_row = [gui addLongText:line startingAtRow:text_row align:GUI_ALIGN_LEFT]; } [dockingReport setString:[dockingReport stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]]; } else { text_row = [gui addLongText:[NSString stringWithString:dockingReport] startingAtRow:text_row align:GUI_ALIGN_LEFT]; [dockingReport setString:@""]; } } [gui setText:[NSString stringWithFormat:DESC_PLURAL(@"contracts-cash-@-load-d-of-d-passengers-d-of-d-berths", max_passengers), OOCredits(credits), current_cargo, [self maxAvailableCargoSpace], [passengers count], max_passengers] forRow: GUI_ROW_MARKET_CASH]; [gui setColor:[gui colorFromSetting:kGuiDockingSummaryColor defaultValue:nil] forRow:GUI_ROW_MARKET_CASH]; [gui setText:DESC(@"press-space-commander") forRow:21 align:GUI_ALIGN_CENTER]; [gui setColor:[gui colorFromSetting:kGuiDockingContinueColor defaultValue:nil] forRow:21]; [gui setShowTextCursor:NO]; } /* ends */ if (lastTextKey) { [lastTextKey release]; lastTextKey = nil; } [self setShowDemoShips:NO]; [UNIVERSE enterGUIViewModeWithMouseInteraction:NO]; if (guiChanged) { [gui setForegroundTextureKey:@"docked_overlay"]; // has to be docked! NSDictionary *bgDescriptor = [UNIVERSE screenTextureDescriptorForKey:@"report"]; if (bgDescriptor == nil) bgDescriptor = [UNIVERSE screenTextureDescriptorForKey:@"status_docked"]; if (bgDescriptor == nil) bgDescriptor = [UNIVERSE screenTextureDescriptorForKey:@"status"]; [gui setBackgroundTextureDescriptor:bgDescriptor]; [self noteGUIDidChangeFrom:oldScreen to:gui_screen]; } } // ---------------------------------------------------------------------- static NSMutableDictionary *currentShipyard = nil; - (OOCreditsQuantity) priceForShipKey:(NSString *)key { NSDictionary *shipInfo = [currentShipyard oo_dictionaryForKey:key]; return [shipInfo oo_unsignedLongLongForKey:SHIPYARD_KEY_PRICE]; } - (void) setGuiToShipyardScreen:(NSUInteger)skip { OOGUIScreenID oldScreen = gui_screen; GuiDisplayGen *gui = [UNIVERSE gui]; gui_screen = GUI_SCREEN_SHIPYARD; BOOL guiChanged = (oldScreen != gui_screen); unsigned i; // set up initial market if there is none OOTechLevelID stationTechLevel; StationEntity *station = [self dockedStation]; if (station != nil) { stationTechLevel = [station equivalentTechLevel]; } else { station = [UNIVERSE station]; stationTechLevel = NSNotFound; } if ([station localShipyard] == nil) { [station setLocalShipyard:[UNIVERSE shipsForSaleForSystem:system_id withTL:stationTechLevel atTime:ship_clock]]; } NSMutableArray *shipyard = [station localShipyard]; // remove ships that the player has already bought for (i = 0; i < [shipyard count]; i++) { NSString *shipID = [[shipyard oo_dictionaryAtIndex:i] oo_stringForKey:SHIPYARD_KEY_ID]; if ([shipyard_record objectForKey:shipID]) { [shipyard removeObjectAtIndex:i--]; } } [currentShipyard release]; currentShipyard = [[NSMutableDictionary alloc] initWithCapacity:[shipyard count]]; for (i = 0; i < [shipyard count]; i++) { [currentShipyard setObject:[shipyard objectAtIndex:i] forKey:[[shipyard oo_dictionaryAtIndex:i] oo_stringForKey:SHIPYARD_KEY_ID]]; } NSUInteger shipCount = [shipyard count]; //error check if (skip >= shipCount) skip = shipCount - 1; if (skip < 2) skip = 0; // GUI stuff { [gui clearAndKeepBackground:!guiChanged]; NSString *system = [UNIVERSE getSystemName:system_id]; [gui setTitle:OOExpandKey(@"shipyard-title", system)]; OOGUITabSettings tab_stops; tab_stops[0] = 0; tab_stops[1] = -258; tab_stops[2] = 270; tab_stops[3] = 370; tab_stops[4] = 450; [gui overrideTabs:tab_stops from:kGuiShipyardTabs length:5]; [gui setTabStops:tab_stops]; int rowCount = MAX_ROWS_SHIPS_FOR_SALE; int startRow = GUI_ROW_SHIPYARD_START; NSInteger previous = 0; if (shipCount <= MAX_ROWS_SHIPS_FOR_SALE) skip = 0; else { if (skip > 0) { rowCount -= 1; startRow += 1; previous = skip - MAX_ROWS_SHIPS_FOR_SALE + 2; if (previous < 2) previous = 0; } if (skip + rowCount < shipCount) rowCount -= 1; } if (shipCount > 0) { [gui setColor:[gui colorFromSetting:kGuiShipyardHeadingColor defaultValue:[OOColor greenColor]] forRow:GUI_ROW_SHIPYARD_LABELS]; [gui setArray:[NSArray arrayWithObjects:DESC(@"shipyard-shiptype"), DESC(@"shipyard-price-label"), DESC(@"shipyard-cargo-label"), DESC(@"shipyard-speed-label"), nil] forRow:GUI_ROW_SHIPYARD_LABELS]; if (skip > 0) { [gui setColor:[gui colorFromSetting:kGuiShipyardScrollColor defaultValue:[OOColor greenColor]] forRow:GUI_ROW_SHIPYARD_START]; [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-back"), @" <-- ", nil] forRow:GUI_ROW_SHIPYARD_START]; [gui setKey:[NSString stringWithFormat:@"More:%ld", previous] forRow:GUI_ROW_SHIPYARD_START]; } for (i = 0; i < (shipCount - skip) && (int)i < rowCount; i++) { NSDictionary* ship_info = [shipyard oo_dictionaryAtIndex:i + skip]; OOCreditsQuantity ship_price = [ship_info oo_unsignedLongLongForKey:SHIPYARD_KEY_PRICE]; [gui setColor:[gui colorFromSetting:kGuiShipyardEntryColor defaultValue:nil] forRow:startRow + i]; [gui setArray:[NSArray arrayWithObjects: [NSString stringWithFormat:@" %@ ",[[ship_info oo_dictionaryForKey:SHIPYARD_KEY_SHIP] oo_stringForKey:@"display_name" defaultValue:[[ship_info oo_dictionaryForKey:SHIPYARD_KEY_SHIP] oo_stringForKey:KEY_NAME]]], OOIntCredits(ship_price), nil] forRow:startRow + i]; [gui setKey:(NSString*)[ship_info objectForKey:SHIPYARD_KEY_ID] forRow:startRow + i]; } if (i < shipCount - skip) { [gui setColor:[gui colorFromSetting:kGuiShipyardScrollColor defaultValue:[OOColor greenColor]] forRow:startRow + i]; [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-more"), @" --> ", nil] forRow:startRow + i]; [gui setKey:[NSString stringWithFormat:@"More:%ld", rowCount + skip] forRow:startRow + i]; i++; } [gui setSelectableRange:NSMakeRange( GUI_ROW_SHIPYARD_START, i + startRow - GUI_ROW_SHIPYARD_START)]; [self showShipyardInfoForSelection]; } else { [gui setText:DESC(@"shipyard-no-ships-available-for-purchase") forRow:GUI_ROW_NO_SHIPS align:GUI_ALIGN_CENTER]; [gui setColor:[gui colorFromSetting:kGuiShipyardNoshipColor defaultValue:[OOColor greenColor]] forRow:GUI_ROW_NO_SHIPS]; [gui setNoSelectedRow]; } [self showTradeInInformationFooter]; [gui setShowTextCursor:NO]; } // the following are necessary... [self setShowDemoShips:(shipCount > 0)]; [UNIVERSE enterGUIViewModeWithMouseInteraction:YES]; if (guiChanged) { [gui setForegroundTextureKey:@"docked_overlay"]; [gui setBackgroundTextureKey:@"shipyard"]; } } - (void) showShipyardInfoForSelection { NSUInteger i; GuiDisplayGen *gui = [UNIVERSE gui]; OOGUIRow sel_row = [gui selectedRow]; if (sel_row <= 0) return; NSMutableArray *row_info = [NSMutableArray arrayWithArray:(NSArray*)[gui objectForRow:GUI_ROW_SHIPYARD_LABELS]]; while ([row_info count] < 4) { [row_info addObject:@""]; } NSString *key = [gui keyForRow:sel_row]; NSDictionary *info = [currentShipyard oo_dictionaryForKey:key]; // clean up the display ready for the newly-selected ship (if there is one) [row_info replaceObjectAtIndex:2 withObject:@""]; [row_info replaceObjectAtIndex:3 withObject:@""]; for (i = GUI_ROW_SHIPYARD_INFO_START; i < GUI_ROW_MARKET_CASH - 1; i++) { [gui setText:@"" forRow:i]; [gui setColor:[gui colorFromSetting:kGuiShipyardDescriptionColor defaultValue:[OOColor greenColor]] forRow:i]; } [UNIVERSE removeDemoShips]; if (info) { // the key is a particular ship - show the details NSString *salesPitch = [info oo_stringForKey:KEY_SHORT_DESCRIPTION]; NSDictionary *shipDict = [info oo_dictionaryForKey:SHIPYARD_KEY_SHIP]; int cargoRating = [shipDict oo_intForKey:@"max_cargo"]; int cargo_extra; cargo_extra = [shipDict oo_intForKey:@"extra_cargo" defaultValue:15]; float speedRating = 0.001 * [shipDict oo_intForKey:@"max_flight_speed"]; NSArray *shipExtras = [info oo_arrayForKey:KEY_EQUIPMENT_EXTRAS]; for (i = 0; i < [shipExtras count]; i++) { if ([[shipExtras oo_stringAtIndex:i] isEqualToString:@"EQ_CARGO_BAY"]) { cargoRating += cargo_extra; } else if ([[shipExtras oo_stringAtIndex:i] isEqualToString:@"EQ_PASSENGER_BERTH"]) { cargoRating -= PASSENGER_BERTH_SPACE; } } [row_info replaceObjectAtIndex:2 withObject:OOExpandKey(@"shipyard-cargo-value", cargoRating)]; [row_info replaceObjectAtIndex:3 withObject:OOExpandKey(@"shipyard-speed-value", speedRating)]; // Show footer first. It'll be overwritten by the sales_pitch if that text is longer than usual. [self showTradeInInformationFooter]; i = [gui addLongText:salesPitch startingAtRow:GUI_ROW_SHIPYARD_INFO_START align:GUI_ALIGN_LEFT]; if (i - 1 >= GUI_ROW_MARKET_CASH - 1) { [gui setColor:[gui colorFromSetting:kGuiShipyardDescriptionColor defaultValue:[OOColor greenColor]] forRow:i - 1]; [gui setColor:[gui colorFromSetting:kGuiShipyardDescriptionColor defaultValue:[OOColor greenColor]] forRow:GUI_ROW_MARKET_CASH - 1]; } // now display the ship [self showShipyardModel:[info oo_stringForKey:SHIPYARD_KEY_SHIPDATA_KEY] shipData:shipDict personality:[info oo_unsignedShortForKey:SHIPYARD_KEY_PERSONALITY]]; } else { // the key is a particular model of ship which we must expand... // build an array from the entries for that model in the currentShipyard TODO // } [gui setArray:[NSArray arrayWithArray:row_info] forRow:GUI_ROW_SHIPYARD_LABELS]; } - (void) showTradeInInformationFooter { GuiDisplayGen *gui = [UNIVERSE gui]; OOCreditsQuantity tradeIn = [self tradeInValue]; OOCreditsQuantity total = tradeIn + credits; NSString *shipType = [self displayName]; [gui setColor:[gui colorFromSetting:kGuiShipyardTradeinColor defaultValue:nil] forRow:GUI_ROW_MARKET_CASH - 1]; [gui setColor:[gui colorFromSetting:kGuiShipyardTradeinColor defaultValue:nil] forRow:GUI_ROW_MARKET_CASH]; [gui setText:OOExpandKey(@"shipyard-trade-in-value", shipType, tradeIn) forRow: GUI_ROW_MARKET_CASH - 1]; [gui setText:OOExpandKey(@"shipyard-total-available-with-trade-in", shipType, total, credits, tradeIn) forRow: GUI_ROW_MARKET_CASH]; } - (void) showShipyardModel:(NSString *)shipKey shipData:(NSDictionary *)shipData personality:(uint16_t)personality { if (shipKey == nil || [self dockedStation] == nil) return; [self showShipModelWithKey:shipKey shipData:shipData personality:personality factorX:1.2 factorY:0.8 factorZ:6.4 inContext:@"shipyard"]; } - (NSInteger) missingSubEntitiesAdjustment { // each missing subentity depreciates the ship by 5%, up to a maximum of 35% depreciation. NSUInteger percent = 5 * ([self maxShipSubEntities] - [[[self shipSubEntityEnumerator] allObjects] count]); return (percent > 35 ? 35 : percent); } - (OOCreditsQuantity) tradeInValue { // returns down to ship_trade_in_factor% of the full credit value of your ship /* FIXME: the trade-in value can be more than the sale value, and ship_trade_in_factor starts at 100%, so it can be profitable to sit and buy the same ship over and over again. This bug predates Oolite 1.65. Partial fix: make effective trade-in value 75% * ship_trade_in_factor% of the "raw" trade-in value. This still allows profitability! A better solution would be to unify the price calculation for trade-in and for-sale ships. -- Ahruman 20070707, fix applied 20070708 */ unsigned long long value = [UNIVERSE tradeInValueForCommanderDictionary:[self commanderDataDictionary]]; value -= value * 0.006 * [self missingSubEntitiesAdjustment]; // TODO: 0.006 might need rethinking. value = cunningFee(((value * 75 * ship_trade_in_factor) + 5000) / 10000, 0.005); // Multiply by two percentages, divide by 100*100. The +5000 is to get normal rounding. return value * 10; } - (BOOL) buySelectedShip { GuiDisplayGen *gui = [UNIVERSE gui]; OOGUIRow selectedRow = [gui selectedRow]; if (selectedRow <= 0) return NO; NSString *key = [gui keyForRow:selectedRow]; if ([key hasPrefix:@"More:"]) { NSInteger fromShip = [[key componentsSeparatedByString:@":"] oo_integerAtIndex:1]; if (fromShip < 0) fromShip = 0; [self setGuiToShipyardScreen:fromShip]; if ([[UNIVERSE gui] selectedRow] < 0) { [[UNIVERSE gui] setSelectedRow:GUI_ROW_SHIPYARD_START]; } if (fromShip == 0) { [[UNIVERSE gui] setSelectedRow:GUI_ROW_SHIPYARD_START + MAX_ROWS_SHIPS_FOR_SALE - 1]; // next bit or the first ship on the list gets wrongly previewed [UNIVERSE removeDemoShips]; } return YES; } // first check you can afford it! NSDictionary *shipInfo = [currentShipyard oo_dictionaryForKey:key]; OOCreditsQuantity price = [shipInfo oo_unsignedLongLongForKey:SHIPYARD_KEY_PRICE]; OOCreditsQuantity tradeIn = [self tradeInValue]; if (credits + tradeIn < price * 10) return NO; // you can't afford it! // sell all the commodities carried NSString *good = nil; foreach (good, [shipCommodityData goods]) { [self trySellingCommodity:good all:YES]; } // We tried to sell everything. If there are still items present in our inventory, it // means that the market got saturated (quantity in station > 127 t) before we could sell // it all. Everything that could not be sold will be lost. -- Nikos 20083012 // pay over the mazoolah credits -= 10 * price - tradeIn; NSDictionary *shipDict = [shipInfo oo_dictionaryForKey:SHIPYARD_KEY_SHIP]; [self newShipCommonSetup:[shipInfo oo_stringForKey:SHIPYARD_KEY_SHIPDATA_KEY] yardInfo:shipInfo baseInfo:shipDict]; // this ship has a clean record legalStatus = 0; NSArray *extras = [shipInfo oo_arrayForKey:KEY_EQUIPMENT_EXTRAS]; for (NSUInteger i = 0; i < [extras count]; i++) { NSString *eq_key = [extras oo_stringAtIndex:i]; if ([eq_key isEqualToString:@"EQ_PASSENGER_BERTH"]) { max_passengers++; max_cargo -= PASSENGER_BERTH_SPACE; } else { [self addEquipmentItem:eq_key withValidation:YES inContext:@"newShip"]; } } // add bought ship to shipyard_record [shipyard_record setObject:[self shipDataKey] forKey:[shipInfo objectForKey:SHIPYARD_KEY_ID]]; // remove the ship from the localShipyard [[[self dockedStation] localShipyard] removeObjectAtIndex:selectedRow - GUI_ROW_SHIPYARD_START]; // perform the transformation NSDictionary* cmdr_dict = [self commanderDataDictionary]; // gather up all the info if (![self setCommanderDataFromDictionary:cmdr_dict]) return NO; [self setStatus:STATUS_DOCKED]; [self setEntityPersonalityInt:[shipInfo oo_unsignedShortForKey:SHIPYARD_KEY_PERSONALITY]]; // adjust the clock forward by an hour ship_clock_adjust += 3600.0; // finally we can get full hock if we sell it back ship_trade_in_factor = 100; if ([UNIVERSE autoSave]) [UNIVERSE setAutoSaveNow:YES]; return YES; } - (BOOL) buyNamedShip:(NSString *)shipKey { NSDictionary *ship_info = [[OOShipRegistry sharedRegistry] shipyardInfoForKey:shipKey]; NSDictionary *ship_base_dict = [[OOShipRegistry sharedRegistry] shipInfoForKey:shipKey]; if (ship_info == nil || ship_base_dict == nil) { return NO; } [self newShipCommonSetup:shipKey yardInfo:ship_info baseInfo:ship_base_dict]; // perform the transformation NSDictionary* cmdr_dict = [self commanderDataDictionary]; // gather up all the info if (![self setCommanderDataFromDictionary:cmdr_dict]) return NO; // refill from ship_info NSArray* extras = [NSMutableArray arrayWithArray:[[ship_info oo_dictionaryForKey:KEY_STANDARD_EQUIPMENT] oo_arrayForKey:KEY_EQUIPMENT_EXTRAS]]; for (unsigned i = 0; i < [extras count]; i++) { NSString* eq_key = [extras oo_stringAtIndex:i]; if ([eq_key isEqualToString:@"EQ_PASSENGER_BERTH"]) { max_passengers++; max_cargo -= PASSENGER_BERTH_SPACE; } else { [self addEquipmentItem:eq_key withValidation:YES inContext:@"newShip"]; } } [self setEntityPersonalityInt:[ship_info oo_unsignedShortForKey:SHIPYARD_KEY_PERSONALITY]]; return YES; } - (void) newShipCommonSetup:(NSString *)shipKey yardInfo:(NSDictionary *)ship_info baseInfo:(NSDictionary *)ship_base_dict { if (current_cargo) { // Zero out our manifest. [shipCommodityData removeAllGoods]; current_cargo = 0; } // drop all passengers [passengers removeAllObjects]; [passenger_record removeAllObjects]; // parcels stay the same; easy to transfer between ships // contracts stay the same, so if you default - tough! // okay we need to switch the model used, lots of the stats, and add all the extras [self clearSubEntities]; [self setShipDataKey:shipKey]; NSDictionary *shipDict = ship_base_dict; // get a full tank for free [self setFuel:[self fuelCapacity]]; // get forward_weapon aft_weapon port_weapon starboard_weapon from ship_info int base_facings = [shipDict oo_unsignedIntForKey:KEY_WEAPON_FACINGS defaultValue:15]; int available_facings = [ship_info oo_unsignedIntForKey:KEY_WEAPON_FACINGS defaultValue:base_facings]; // not retained - weapon types are references to the objects in OOEquipmentType's cache if (available_facings & WEAPON_FACING_AFT) aft_weapon_type = OOWeaponTypeFromEquipmentIdentifierSloppy([shipDict oo_stringForKey:@"aft_weapon_type"]); else aft_weapon_type = OOWeaponTypeFromEquipmentIdentifierSloppy(@"EQ_WEAPON_NONE"); if (available_facings & WEAPON_FACING_PORT) port_weapon_type = OOWeaponTypeFromEquipmentIdentifierSloppy([shipDict oo_stringForKey:@"port_weapon_type"]); else port_weapon_type = OOWeaponTypeFromEquipmentIdentifierSloppy(@"EQ_WEAPON_NONE"); if (available_facings & WEAPON_FACING_STARBOARD) starboard_weapon_type = OOWeaponTypeFromEquipmentIdentifierSloppy([shipDict oo_stringForKey:@"starboard_weapon_type"]); else starboard_weapon_type = OOWeaponTypeFromEquipmentIdentifierSloppy(@"EQ_WEAPON_NONE"); if (available_facings & WEAPON_FACING_FORWARD) forward_weapon_type = OOWeaponTypeFromEquipmentIdentifierSloppy([shipDict oo_stringForKey:@"forward_weapon_type"]); else forward_weapon_type = OOWeaponTypeFromEquipmentIdentifierSloppy(@"EQ_WEAPON_NONE"); // new ships start with weapons online weapons_online = 1; // get basic max_cargo max_cargo = [UNIVERSE maxCargoForShip:[self shipDataKey]]; // ensure all missiles are tidied up and start at pylon 0 [self tidyMissilePylons]; // get missiles from ship_info missiles = [shipDict oo_unsignedIntForKey:@"missiles"]; // reset max_passengers max_passengers = 0; // reset and refill extra_equipment then set flags from it // keep track of portable equipment.. NSMutableSet *portable_equipment = [NSMutableSet set]; NSEnumerator *eqEnum = nil; NSString *eq_desc = nil; OOEquipmentType *item = nil; for (eqEnum = [self equipmentEnumerator]; (eq_desc = [eqEnum nextObject]);) { item = [OOEquipmentType equipmentTypeWithIdentifier:eq_desc]; if ([item isPortableBetweenShips]) [portable_equipment addObject:eq_desc]; } // remove ALL [self removeAllEquipment]; // restore portable equipment for (eqEnum = [portable_equipment objectEnumerator]; (eq_desc = [eqEnum nextObject]); ) { [self addEquipmentItem:eq_desc withValidation:NO inContext:@"portable"]; } // set up subentities from scratch; new ship could carry more or fewer than the old one [self setUpSubEntities]; // clear old ship names [self setShipClassName:[shipDict oo_stringForKey:@"name"]]; [self setShipUniqueName:@""]; // new ship, so lose some memory of actions // new ship, so lose some memory of player actions if (ship_kills >= 6400) { [self clearRolesFromPlayer:0.1]; } else if (ship_kills >= 2560) { [self clearRolesFromPlayer:0.25]; } else { [self clearRolesFromPlayer:0.5]; } } @end static unsigned RepForRisk(unsigned risk) { switch (risk) { case 0: return 1; case 1: return 2; case 2: default: return 4; } } oolite-1.82/src/Core/Entities/PlayerEntityControls.h000066400000000000000000000023671256642440500225500ustar00rootroot00000000000000/* PlayerEntityControls.h Input management methods. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "PlayerEntity.h" @interface PlayerEntity (Controls) - (void) initControls; - (void) pollControls:(double)delta_t; - (BOOL) handleGUIUpDownArrowKeys; - (void) clearPlanetSearchString; - (void) targetNewSystem:(int) direction; - (void) switchToMainView; - (void) noteSwitchToView:(OOViewID)toView fromView:(OOViewID)fromView; - (void) beginWitchspaceCountdown:(int)spin_time; - (void) beginWitchspaceCountdown; - (void) cancelWitchspaceCountdown; @end oolite-1.82/src/Core/Entities/PlayerEntityControls.m000066400000000000000000004173651256642440500225650ustar00rootroot00000000000000/* PlayerEntityControls.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "PlayerEntityControls.h" #import "PlayerEntityContracts.h" #import "PlayerEntityLegacyScriptEngine.h" #import "PlayerEntityScriptMethods.h" #import "PlayerEntitySound.h" #import "PlayerEntityLoadSave.h" #import "PlayerEntityStickMapper.h" #import "PlayerEntityStickProfile.h" #import "ShipEntityAI.h" #import "StationEntity.h" #import "Universe.h" #import "OOSunEntity.h" #import "OOPlanetEntity.h" #import "GameController.h" #import "AI.h" #import "MyOpenGLView.h" #import "OOSound.h" #import "OOStringParsing.h" #import "OOCollectionExtractors.h" #import "OOOXZManager.h" #import "OOStringExpander.h" #import "ResourceManager.h" #import "HeadUpDisplay.h" #import "OOConstToString.h" #import "OOConstToJSString.h" #import "OOLoggingExtended.h" #import "OOMusicController.h" #import "OOTexture.h" #import "OODebugFlags.h" #import "OOStringExpander.h" #import "OOSystemDescriptionManager.h" #import "OOJoystickManager.h" #import "OOJSScript.h" #import "OOEquipmentType.h" #import "OODebugSupport.h" #import "OODebugMonitor.h" static BOOL jump_pressed; static BOOL hyperspace_pressed; static BOOL galhyperspace_pressed; static BOOL pause_pressed; static BOOL prev_compass_mode_pressed; static BOOL next_compass_mode_pressed; static BOOL next_target_pressed; static BOOL previous_target_pressed; static BOOL prime_equipment_pressed; static BOOL activate_equipment_pressed; static BOOL mode_equipment_pressed; static BOOL fastactivate_a_pressed; static BOOL fastactivate_b_pressed; static BOOL next_missile_pressed; static BOOL fire_missile_pressed; static BOOL target_missile_pressed; static BOOL target_incoming_missile_pressed; static BOOL ident_pressed; static BOOL safety_pressed; static BOOL rotateCargo_pressed; static BOOL autopilot_key_pressed; static BOOL fast_autopilot_key_pressed; static BOOL docking_clearance_request_key_pressed; #ifndef NDEBUG static BOOL dump_target_state_pressed; static BOOL dump_entity_list_pressed; #endif static BOOL taking_snapshot; static BOOL hide_hud_pressed; static BOOL f_key_pressed; static BOOL m_key_pressed; static BOOL pling_pressed; static BOOL cursor_moving; static BOOL disc_operation_in_progress; #if OO_RESOLUTION_OPTION static BOOL switching_resolution; #endif #if OOLITE_SPEECH_SYNTH static BOOL speech_settings_pressed; #if OOLITE_ESPEAK static BOOL speechVoiceSelectKeyPressed; static BOOL speechGenderSelectKeyPressed; #endif #endif static BOOL wait_for_key_up; static BOOL upDownKeyPressed; static BOOL leftRightKeyPressed; static BOOL musicModeKeyPressed; static BOOL volumeControlPressed; #if OOLITE_SDL static BOOL gammaControlPressed; #endif static BOOL fovControlPressed; static BOOL shaderSelectKeyPressed; static BOOL selectPressed; static BOOL queryPressed; static BOOL spacePressed; static BOOL chartInfoPressed; static BOOL switching_chart_screens; static BOOL switching_status_screens; //static BOOL switching_market_screens; static BOOL switching_equipship_screens; static BOOL zoom_pressed; static BOOL customView_pressed; static BOOL weaponsOnlineToggle_pressed; static BOOL escapePodKey_pressed; static BOOL cycleMFD_pressed; static BOOL switchMFD_pressed; static BOOL mouse_left_down; static BOOL oxz_manager_pressed; static NSPoint mouse_click_position; static NSPoint centre_at_mouse_click; static NSUInteger searchStringLength; static double timeLastKeyPress; //static OOGUIRow oldSelection; static int saved_view_direction; static double saved_script_time; static int saved_gui_screen; static OOWeaponFacing saved_weapon_facing; static int pressedArrow = 0; static BOOL mouse_x_axis_map_to_yaw = NO; static NSTimeInterval time_last_frame; @interface PlayerEntity (OOControlsPrivate) - (void) pollFlightControls:(double) delta_t; - (void) pollFlightArrowKeyControls:(double) delta_t; - (void) pollGuiArrowKeyControls:(double) delta_t; - (void) handleGameOptionsScreenKeys; - (void) pollApplicationControls; - (void) pollCustomViewControls; - (void) pollViewControls; - (void) pollGuiScreenControls; - (void) pollGuiScreenControlsWithFKeyAlias:(BOOL)fKeyAlias; - (void) pollMarketScreenControls; - (void) handleUndockControl; - (void) pollGameOverControls:(double) delta_t; - (void) pollAutopilotControls:(double) delta_t; - (void) pollDockedControls:(double) delta_t; - (void) pollDemoControls:(double) delta_t; - (void) pollMissionInterruptControls; - (void) handleMissionCallback; - (void) setGuiToMissionEndScreen; - (void) switchToThisView:(OOViewID)viewDirection; - (void) switchToThisView:(OOViewID)viewDirection andProcessWeaponFacing:(BOOL)processWeaponFacing; - (void) switchToThisView:(OOViewID)viewDirection fromView:(OOViewID)oldViewDirection andProcessWeaponFacing:(BOOL)processWeaponFacing justNotify:(BOOL)justNotify; - (void) handleAutopilotOn:(BOOL)fastDocking; // Handlers for individual controls - (void) handleButtonIdent; - (void) handleButtonTargetMissile; @end @implementation PlayerEntity (Controls) - (void) initControls { NSMutableDictionary *kdic = [NSMutableDictionary dictionaryWithDictionary:[ResourceManager dictionaryFromFilesNamed:@"keyconfig.plist" inFolder:@"Config" mergeMode:MERGE_BASIC cache:NO]]; // pre-process kdic - replace any strings with an integer representing the ASCII value of the first character unsigned i; NSArray *keys = nil; id key = nil, value = nil; int iValue; unsigned char keychar; NSString *keystring = nil; #if OOLITE_WINDOWS // override windows keyboard autoselect [[UNIVERSE gameView] setKeyboardTo:[kdic oo_stringForKey:@"windows_keymap" defaultValue:@"auto"]]; #endif keys = [kdic allKeys]; for (i = 0; i < [keys count]; i++) { key = [keys objectAtIndex:i]; value = [kdic objectForKey: key]; iValue = [value intValue]; // for '0' '1' '2' '3' '4' '5' '6' '7' '8' '9' - we want to interpret those as strings - not numbers // alphabetical characters and symbols will return an intValue of 0. if ([value isKindOfClass:[NSString class]] && (iValue < 10)) { keystring = value; if ([keystring length] == 1 || (iValue == 0 && [keystring length] != 0)) { keychar = [keystring characterAtIndex: 0] & 0x00ff; // uses lower byte of unichar } else if (iValue <= 0xFF) keychar = iValue; else continue; [kdic setObject:[NSNumber numberWithUnsignedShort:keychar] forKey:key]; } } // set default keys. #define LOAD_KEY_SETTING(name, default) name = [kdic oo_unsignedShortForKey:@#name defaultValue:default]; [kdic setObject:[NSNumber numberWithUnsignedShort:name] forKey:@#name]; #define LOAD_KEY_SETTING_ALIAS(name, oldname, default) name = [kdic oo_unsignedShortForKey:@#name defaultValue:[kdic oo_unsignedShortForKey:@#oldname defaultValue:default]]; [kdic setObject:[NSNumber numberWithUnsignedShort:name] forKey:@#name] LOAD_KEY_SETTING(key_roll_left, gvArrowKeyLeft ); LOAD_KEY_SETTING(key_roll_right, gvArrowKeyRight ); LOAD_KEY_SETTING(key_pitch_forward, gvArrowKeyUp ); LOAD_KEY_SETTING(key_pitch_back, gvArrowKeyDown ); LOAD_KEY_SETTING(key_yaw_left, ',' ); LOAD_KEY_SETTING(key_yaw_right, '.' ); LOAD_KEY_SETTING(key_view_forward, '1' ); LOAD_KEY_SETTING(key_view_aft, '2' ); LOAD_KEY_SETTING(key_view_port, '3' ); LOAD_KEY_SETTING(key_view_starboard, '4' ); LOAD_KEY_SETTING(key_gui_screen_status, '5' ); LOAD_KEY_SETTING(key_gui_chart_screens, '6' ); LOAD_KEY_SETTING(key_gui_system_data, '7' ); LOAD_KEY_SETTING(key_gui_market, '8' ); LOAD_KEY_SETTING(key_gui_arrow_left, gvArrowKeyLeft ); LOAD_KEY_SETTING(key_gui_arrow_right, gvArrowKeyRight ); LOAD_KEY_SETTING(key_gui_arrow_up, gvArrowKeyUp ); LOAD_KEY_SETTING(key_gui_arrow_down, gvArrowKeyDown ); LOAD_KEY_SETTING(key_increase_speed, 'w' ); LOAD_KEY_SETTING(key_decrease_speed, 's' ); LOAD_KEY_SETTING(key_inject_fuel, 'i' ); LOAD_KEY_SETTING(key_fire_lasers, 'a' ); LOAD_KEY_SETTING(key_weapons_online_toggle, '_' ); LOAD_KEY_SETTING(key_launch_missile, 'm' ); LOAD_KEY_SETTING(key_next_missile, 'y' ); LOAD_KEY_SETTING(key_ecm, 'e' ); LOAD_KEY_SETTING(key_prime_equipment, 'N' ); LOAD_KEY_SETTING(key_activate_equipment, 'n' ); LOAD_KEY_SETTING(key_mode_equipment, 'b' ); LOAD_KEY_SETTING_ALIAS(key_fastactivate_equipment_a, key_cloaking_device, '0' ); LOAD_KEY_SETTING_ALIAS(key_fastactivate_equipment_b, key_energy_bomb, '\t' ); LOAD_KEY_SETTING(key_target_missile, 't' ); LOAD_KEY_SETTING(key_untarget_missile, 'u' ); LOAD_KEY_SETTING(key_target_incoming_missile, 'T' ); LOAD_KEY_SETTING(key_ident_system, 'r' ); LOAD_KEY_SETTING(key_scanner_zoom, 'z' ); LOAD_KEY_SETTING(key_scanner_unzoom, 'Z' ); LOAD_KEY_SETTING(key_launch_escapepod, 27 /* esc */ ); LOAD_KEY_SETTING(key_galactic_hyperspace, 'g' ); LOAD_KEY_SETTING(key_hyperspace, 'h' ); LOAD_KEY_SETTING(key_jumpdrive, 'j' ); LOAD_KEY_SETTING(key_dump_cargo, 'd' ); LOAD_KEY_SETTING(key_rotate_cargo, 'R' ); LOAD_KEY_SETTING(key_autopilot, 'c' ); LOAD_KEY_SETTING(key_autodock, 'C' ); LOAD_KEY_SETTING(key_docking_clearance_request, 'L' ); LOAD_KEY_SETTING(key_snapshot, '*' ); LOAD_KEY_SETTING(key_docking_music, 's' ); LOAD_KEY_SETTING(key_advanced_nav_array, '^' ); LOAD_KEY_SETTING(key_map_home, gvHomeKey ); LOAD_KEY_SETTING(key_map_info, 'i' ); LOAD_KEY_SETTING(key_pausebutton, 'p' ); LOAD_KEY_SETTING(key_show_fps, 'F' ); LOAD_KEY_SETTING(key_mouse_control, 'M' ); LOAD_KEY_SETTING(key_hud_toggle, 'o' ); LOAD_KEY_SETTING(key_comms_log, '`' ); LOAD_KEY_SETTING(key_prev_compass_mode, '|' ); LOAD_KEY_SETTING(key_next_compass_mode, '\\' ); LOAD_KEY_SETTING_ALIAS(key_chart_highlight, key_contract_info, '\?' ); LOAD_KEY_SETTING(key_market_filter_cycle, '?' ); LOAD_KEY_SETTING(key_market_sorter_cycle, '/' ); LOAD_KEY_SETTING(key_cycle_mfd, ';' ); LOAD_KEY_SETTING(key_switch_mfd, ':' ); LOAD_KEY_SETTING(key_next_target, '+' ); LOAD_KEY_SETTING(key_previous_target, '-' ); LOAD_KEY_SETTING(key_custom_view, 'v' ); LOAD_KEY_SETTING(key_oxzmanager_setfilter, 'f' ); LOAD_KEY_SETTING(key_oxzmanager_showinfo, 'i' ); LOAD_KEY_SETTING(key_oxzmanager_extract, 'x' ); LOAD_KEY_SETTING(key_inc_field_of_view, 'l' ); LOAD_KEY_SETTING(key_dec_field_of_view, 'k' ); #ifndef NDEBUG LOAD_KEY_SETTING(key_dump_target_state, 'H' ); #endif if (key_yaw_left == key_roll_left && key_yaw_left == ',') key_yaw_left = 0; if (key_yaw_right == key_roll_right && key_yaw_right == '.') key_yaw_right = 0; // other keys are SET and cannot be varied [keyconfig_settings release]; keyconfig_settings = [[NSDictionary alloc] initWithDictionary:kdic]; // Enable polling pollControls=YES; } - (void) pollControls:(double)delta_t { MyOpenGLView *gameView = [UNIVERSE gameView]; NSString *exceptionContext = @"setup"; @try { if (gameView) { // poll the gameView keyboard things exceptionContext = @"pollApplicationControls"; [self pollApplicationControls]; // quit command-f etc. switch ([self status]) { case STATUS_WITCHSPACE_COUNTDOWN: case STATUS_IN_FLIGHT: exceptionContext = @"pollFlightControls"; [self pollFlightControls:delta_t]; break; case STATUS_DEAD: exceptionContext = @"pollGameOverControls"; [self pollGameOverControls:delta_t]; break; case STATUS_AUTOPILOT_ENGAGED: exceptionContext = @"pollAutopilotControls"; [self pollAutopilotControls:delta_t]; break; case STATUS_DOCKED: exceptionContext = @"pollDockedControls"; [self pollDockedControls:delta_t]; break; case STATUS_START_GAME: exceptionContext = @"pollDemoControls"; [self pollDemoControls:delta_t]; break; default: // don't poll extra controls at any other times. break; } } } @catch (NSException *exception) { OOLog(kOOLogException, @"***** Exception checking controls [%@]: %@ : %@", exceptionContext, [exception name], [exception reason]); } } // DJS + aegidian: Moved from the big switch/case block in pollGuiArrowKeyControls - (BOOL) handleGUIUpDownArrowKeys { MyOpenGLView *gameView = [UNIVERSE gameView]; GuiDisplayGen *gui = [UNIVERSE gui]; BOOL result = NO; BOOL arrow_up = [gameView isDown:key_gui_arrow_up]; BOOL arrow_down = [gameView isDown:key_gui_arrow_down]; BOOL mouse_click = [gameView isDown:gvMouseLeftButton]; BOOL mouse_dbl_click = [gameView isDown:gvMouseDoubleClick]; if (arrow_down) { if ((!upDownKeyPressed) || (script_time > timeLastKeyPress + KEY_REPEAT_INTERVAL)) { if ([gui setNextRow: +1]) { result = YES; } else { if ([gui setFirstSelectableRow]) result = YES; } if (result && [gui selectableRange].length > 1) [self playMenuNavigationDown]; else [self playMenuNavigationNot]; timeLastKeyPress = script_time; } } if (arrow_up) { if ((!upDownKeyPressed) || (script_time > timeLastKeyPress + KEY_REPEAT_INTERVAL)) { if ([gui setNextRow: -1]) { result = YES; } else { if ([gui setLastSelectableRow]) result = YES; } if (result && [gui selectableRange].length > 1) [self playMenuNavigationUp]; else [self playMenuNavigationNot]; timeLastKeyPress = script_time; } } if (mouse_click) { if (!upDownKeyPressed) { int click_row = 0; if (UNIVERSE) click_row = UNIVERSE->cursor_row; if ([gui setSelectedRow:click_row]) { result = YES; } } } if (mouse_dbl_click) { int click_row = 0; if (UNIVERSE) click_row = UNIVERSE->cursor_row; if ([gui setSelectedRow:click_row]) { result = YES; } else { // if double-clicked on an unselectable row, clear the // state so it doesn't activate whatever was last // selected [gameView clearMouse]; } } upDownKeyPressed = (arrow_up || arrow_down || mouse_click); return result; } - (void) targetNewSystem:(int) direction whileTyping:(BOOL) whileTyping { target_system_id = [[UNIVERSE gui] targetNextFoundSystem:direction]; cursor_coordinates = [[UNIVERSE systemManager] getCoordinatesForSystem:target_system_id inGalaxy:galaxy_number]; found_system_id = target_system_id; if (!whileTyping) { [self clearPlanetSearchString]; } cursor_moving = YES; } - (void) clearPlanetSearchString { [[UNIVERSE gameView] resetTypedString]; if (planetSearchString) [planetSearchString release]; planetSearchString = nil; } - (void) targetNewSystem:(int) direction { [self targetNewSystem:direction whileTyping:NO]; } - (void) switchToMainView { OOGUIScreenID oldScreen = gui_screen; gui_screen = GUI_SCREEN_MAIN; if (showDemoShips) { [self setShowDemoShips: NO]; [UNIVERSE removeDemoShips]; } [(MyOpenGLView *)[UNIVERSE gameView] allowStringInput:NO]; [UNIVERSE enterGUIViewModeWithMouseInteraction:NO]; [self noteGUIDidChangeFrom:oldScreen to:gui_screen]; } - (void) noteSwitchToView:(OOViewID)toView fromView:(OOViewID)fromView { [self switchToThisView:toView fromView:fromView andProcessWeaponFacing:NO justNotify:YES]; // no extra processing needed! } -(void) beginWitchspaceCountdown:(int)spin_time { if ([self hasHyperspaceMotor]) { if (spin_time == 0) { witchspaceCountdown = hyperspaceMotorSpinTime; } else { #ifndef OO_DUMP_PLANETINFO if (spin_time < 5) { witchspaceCountdown = 5; } else #endif { witchspaceCountdown = spin_time; } } [self setStatus:STATUS_WITCHSPACE_COUNTDOWN]; [self playStandardHyperspace]; // say it! [UNIVERSE clearPreviousMessage]; int seconds = round(witchspaceCountdown); NSString *destination = [UNIVERSE getSystemName:[self nextHopTargetSystemID]]; [UNIVERSE displayCountdownMessage:OOExpandKey(@"witch-to-x-in-y-seconds", seconds, destination) forCount:1.0]; [self doScriptEvent:OOJSID("playerStartedJumpCountdown") withArguments:[NSArray arrayWithObjects:@"standard", [NSNumber numberWithFloat:witchspaceCountdown], nil]]; [UNIVERSE preloadPlanetTexturesForSystem:target_system_id]; } } -(void) beginWitchspaceCountdown { if ([self hasHyperspaceMotor]) { [self beginWitchspaceCountdown:hyperspaceMotorSpinTime]; } } -(void) cancelWitchspaceCountdown { if ([self status] == STATUS_WITCHSPACE_COUNTDOWN) { [self setStatus:STATUS_IN_FLIGHT]; [self playHyperspaceAborted]; } // say it! [UNIVERSE clearPreviousMessage]; [self doScriptEvent:OOJSID("playerCancelledJumpCountdown")]; } @end @implementation PlayerEntity (OOControlsPrivate) - (void) pollApplicationControls { if (!pollControls) return; NSString *exceptionContext = @"setup"; // does fullscreen / quit / snapshot MyOpenGLView *gameView = [UNIVERSE gameView]; GameController *gameController = [UNIVERSE gameController]; BOOL onTextEntryScreen = (gui_screen == GUI_SCREEN_LONG_RANGE_CHART) || (gui_screen == GUI_SCREEN_MISSION) || (gui_screen == GUI_SCREEN_SAVE) || (gui_screen == GUI_SCREEN_OXZMANAGER); @try { // command-key controls #if !OOLITE_MAC_OS_X || !OOLITE_64_BIT // On 64-bit Macs, these are handled by normal menu shortcuts. if ([gameController inFullScreenMode]) { exceptionContext = @"command key controls"; if ([gameView isCommandFDown]) { [gameView clearCommandF]; [gameController exitFullScreenMode]; if (mouse_control_on) { [UNIVERSE addMessage:DESC(@"mouse-off") forCount:3.0]; mouse_control_on = NO; } } if ([gameView isCommandQDown]) { [gameController pauseFullScreenModeToPerform:@selector(exitAppCommandQ) onTarget:gameController]; } } #endif #if OOLITE_WINDOWS if ( !onTextEntryScreen && ([gameView isDown:'Q']) ) { exceptionContext = @"windows - Q"; [gameController exitAppWithContext:@"Q pressed [Windows]"]; exit(0); // Force it } #endif // handle pressing Q or [esc] in error-handling mode if ([self status] == STATUS_HANDLING_ERROR) { exceptionContext = @"error handling mode"; if ([gameView isDown:113]||[gameView isDown:81]||[gameView isDown:27]) // 'q' | 'Q' | esc { [gameController exitAppWithContext:@"Q or escape pressed in error handling mode"]; } } if ([gameController isGamePaused]) { // What's the status? switch ([self status]) { case STATUS_WITCHSPACE_COUNTDOWN: case STATUS_IN_FLIGHT: case STATUS_AUTOPILOT_ENGAGED: case STATUS_DOCKED: // Pause is handled inside their pollControls, no need to unpause. break; default: { // In all other cases we can't handle pause. Unpause immediately. script_time = saved_script_time; [gameView allowStringInput:NO]; if ([UNIVERSE pauseMessageVisible]) { [UNIVERSE clearPreviousMessage]; // remove the 'paused' message. } [gameController setGamePaused:NO]; } break; } } // snapshot const BOOL *joyButtonState = [[OOJoystickManager sharedStickHandler] getAllButtonStates]; if ([gameView isDown:key_snapshot] || joyButtonState[BUTTON_SNAPSHOT]) // '*' key { exceptionContext = @"snapshot"; if (!taking_snapshot) { taking_snapshot = YES; [gameView snapShot:nil]; // nil filename so that the program auto-names the snapshot } } else { taking_snapshot = NO; } // FPS display if (!onTextEntryScreen && [gameView isDown:key_show_fps]) // 'F' key { exceptionContext = @"toggle FPS"; if (!f_key_pressed) [UNIVERSE setDisplayFPS:![UNIVERSE displayFPS]]; f_key_pressed = YES; } else { f_key_pressed = NO; } // Mouse control BOOL allowMouseControl; #if OO_DEBUG allowMouseControl = YES; #else allowMouseControl = [gameController inFullScreenMode] || [[NSUserDefaults standardUserDefaults] boolForKey:@"mouse-control-in-windowed-mode"]; #endif if (allowMouseControl) { exceptionContext = @"mouse control"; if (!onTextEntryScreen && [gameView isDown:key_mouse_control]) // 'M' key { if (!m_key_pressed) { mouse_control_on = !mouse_control_on; if (mouse_control_on) { [UNIVERSE addMessage:DESC(@"mouse-on") forCount:3.0]; /* Ensure the keyboard pitch override (intended to lock out the joystick if the player runs to the keyboard) is reset */ #if OOLITE_GNUSTEP [gameView resetMouse]; if ([[NSUserDefaults standardUserDefaults] boolForKey:@"grab-mouse-on-mouse-control"]) { [gameView grabMouseInsideGameWindow:YES]; } #endif mouse_x_axis_map_to_yaw = [gameView isCtrlDown]; keyboardRollOverride = mouse_x_axis_map_to_yaw; // Getafix: set keyboardRollOverride to TRUE only if yaw is mapped to mouse x-axis keyboardPitchOverride = NO; keyboardYawOverride = !keyboardRollOverride; } else { [UNIVERSE addMessage:DESC(@"mouse-off") forCount:3.0]; #if OOLITE_GNUSTEP [gameView grabMouseInsideGameWindow:NO]; #endif } } if (OOMouseInteractionModeIsFlightMode([gameController mouseInteractionMode])) { [gameController setMouseInteractionModeForFlight]; } m_key_pressed = YES; } else { m_key_pressed = NO; } } else { if (mouse_control_on) { mouse_control_on = NO; [UNIVERSE addMessage:DESC(@"mouse-off") forCount:3.0]; #if OOLITE_GNUSTEP [gameView grabMouseInsideGameWindow:NO]; #endif if (OOMouseInteractionModeIsFlightMode([gameController mouseInteractionMode])) { [gameController setMouseInteractionModeForFlight]; } } } // HUD toggle if ([gameView isDown:key_hud_toggle] && [gameController isGamePaused]) // 'o' key while paused { exceptionContext = @"toggle HUD"; if (!hide_hud_pressed) { HeadUpDisplay *theHUD = [self hud]; [theHUD setHidden:![theHUD isHidden]]; } hide_hud_pressed = YES; } else { hide_hud_pressed = NO; } } @catch (NSException *exception) { OOLog(kOOLogException, @"***** Exception in pollApplicationControls [%@]: %@ : %@", exceptionContext, [exception name], [exception reason]); } } - (void) pollFlightControls:(double)delta_t { MyOpenGLView *gameView = [UNIVERSE gameView]; OOJoystickManager *stickHandler = [OOJoystickManager sharedStickHandler]; NSString *exceptionContext = @"setup"; @try { exceptionContext = @"joystick handling"; const BOOL *joyButtonState = [[OOJoystickManager sharedStickHandler] getAllButtonStates]; BOOL paused = [[UNIVERSE gameController] isGamePaused]; double speed_delta = SHIP_THRUST_FACTOR * thrust; if (!paused && gui_screen == GUI_SCREEN_MISSION) { exceptionContext = @"mission screen"; OOViewID view = VIEW_NONE; NSPoint virtualView = NSZeroPoint; double view_threshold = 0.5; if ([stickHandler joystickCount]) { virtualView = [stickHandler viewAxis]; if (virtualView.y == STICK_AXISUNASSIGNED) virtualView.y = 0.0; if (virtualView.x == STICK_AXISUNASSIGNED) virtualView.x = 0.0; if (fabs(virtualView.y) >= fabs(virtualView.x)) virtualView.x = 0.0; // forward/aft takes precedence else virtualView.y = 0.0; } if (([gameView isDown:gvFunctionKey1] || [gameView isDown:key_view_forward]) || (virtualView.y < -view_threshold) || joyButtonState[BUTTON_VIEWFORWARD]) { view = VIEW_FORWARD; } if (([gameView isDown:gvFunctionKey2])||([gameView isDown:key_view_aft])||(virtualView.y > view_threshold)||joyButtonState[BUTTON_VIEWAFT]) { view = VIEW_AFT; } if (([gameView isDown:gvFunctionKey3])||([gameView isDown:key_view_port])||(virtualView.x < -view_threshold)||joyButtonState[BUTTON_VIEWPORT]) { view = VIEW_PORT; } if (([gameView isDown:gvFunctionKey4])||([gameView isDown:key_view_starboard])||(virtualView.x > view_threshold)||joyButtonState[BUTTON_VIEWSTARBOARD]) { view = VIEW_STARBOARD; } if (view == VIEW_NONE) { // still in mission screen, process the input. [self pollDemoControls: delta_t]; } else { [[UNIVERSE gui] clearBackground]; [self switchToThisView:view]; if (_missionWithCallback) { [self doMissionCallback]; } // notify older scripts, but do not trigger missionScreenOpportunity. [self doWorldEventUntilMissionScreen:OOJSID("missionScreenEnded")]; } } else if (!paused) { exceptionContext = @"arrow keys"; // arrow keys if ([UNIVERSE displayGUI]) [self pollGuiArrowKeyControls:delta_t]; else [self pollFlightArrowKeyControls:delta_t]; // view keys [self pollViewControls]; if (OOMouseInteractionModeIsFlightMode([[UNIVERSE gameController] mouseInteractionMode])) { exceptionContext = @"afterburner"; if ((joyButtonState[BUTTON_FUELINJECT] || [gameView isDown:key_inject_fuel]) && [self hasFuelInjection] && !hyperspeed_engaged) { if (fuel > 0 && !afterburner_engaged) { [UNIVERSE addMessage:DESC(@"fuel-inject-on") forCount:1.5]; afterburner_engaged = YES; [self startAfterburnerSound]; } else { if (fuel <= 0.0) [UNIVERSE addMessage:DESC(@"fuel-out") forCount:1.5]; } afterburner_engaged = (fuel > 0); } else afterburner_engaged = NO; if ((!afterburner_engaged)&&(afterburnerSoundLooping)) [self stopAfterburnerSound]; exceptionContext = @"thrust"; // DJS: Thrust can be an axis or a button. Axis takes precidence. double reqSpeed=[stickHandler getAxisState: AXIS_THRUST]; // Updated DJS original code to fix BUG #17482 - (Getafix 2010/09/13) if (([gameView isDown:key_increase_speed] || joyButtonState[BUTTON_INCTHRUST])&&(flightSpeed < maxFlightSpeed)&&(!afterburner_engaged)) { flightSpeed += speed_delta * delta_t; } // ** tgape ** - decrease obviously means no hyperspeed if (([gameView isDown:key_decrease_speed] || joyButtonState[BUTTON_DECTHRUST])&&(!afterburner_engaged)) { flightSpeed -= speed_delta * delta_t; // ** tgape ** - decrease obviously means no hyperspeed hyperspeed_engaged = NO; } NSDictionary *functionForThrustAxis = [[stickHandler axisFunctions] oo_dictionaryForKey:[[NSNumber numberWithInt:AXIS_THRUST] stringValue]]; if([stickHandler joystickCount] != 0 && functionForThrustAxis != nil) { if (flightSpeed < maxFlightSpeed * reqSpeed) { flightSpeed += speed_delta * delta_t; } if (flightSpeed > maxFlightSpeed * reqSpeed) { flightSpeed -= speed_delta * delta_t; } } // DJS: end joystick thrust axis (Getafix - End code update for fixing BUG #17482) if (!afterburner_engaged && ![self atHyperspeed] && !hyperspeed_engaged) { flightSpeed = OOClamp_0_max_f(flightSpeed, maxFlightSpeed); } exceptionContext = @"hyperspeed"; // hyperspeed controls if ([gameView isDown:key_jumpdrive] || joyButtonState[BUTTON_HYPERSPEED]) // 'j' { if (!jump_pressed) { if (!hyperspeed_engaged) { hyperspeed_locked = [self massLocked]; hyperspeed_engaged = !hyperspeed_locked; if (hyperspeed_locked) { [self playJumpMassLocked]; [UNIVERSE addMessage:DESC(@"jump-mass-locked") forCount:1.5]; } } else { hyperspeed_engaged = NO; } } jump_pressed = YES; } else { jump_pressed = NO; } exceptionContext = @"shoot"; // shoot 'a' if ((([gameView isDown:key_fire_lasers])||((mouse_control_on)&&([gameView isDown:gvMouseLeftButton]))||joyButtonState[BUTTON_FIRE])&&(shot_time > weapon_recharge_rate)) { if ([self fireMainWeapon]) { [self playLaserHit:([self shipHitByLaser] != nil) offset:[self currentLaserOffset]]; } } exceptionContext = @"weapons online toggle"; // weapons online / offline toggle '_' if (([gameView isDown:key_weapons_online_toggle] || joyButtonState[BUTTON_WEAPONSONLINETOGGLE])) { if (!weaponsOnlineToggle_pressed) { NSString* weaponsOnlineToggleMsg; [self setWeaponsOnline:![self weaponsOnline]]; weaponsOnlineToggleMsg = [self weaponsOnline] ? DESC(@"weapons-systems-online") : DESC(@"weapons-systems-offline"); if ([self weaponsOnline]) { [self playWeaponsOnline]; } else { [self playWeaponsOffline]; } [UNIVERSE addMessage:weaponsOnlineToggleMsg forCount:2.0]; weaponsOnlineToggle_pressed = YES; } } else weaponsOnlineToggle_pressed = NO; exceptionContext = @"missile fire"; // shoot 'm' // launch missile if ([gameView isDown:key_launch_missile] || joyButtonState[BUTTON_LAUNCHMISSILE]) { // launch here if (!fire_missile_pressed) { [self fireMissile]; fire_missile_pressed = YES; } } else fire_missile_pressed = NO; exceptionContext = @"next missile"; // shoot 'y' // next missile if ([gameView isDown:key_next_missile] || joyButtonState[BUTTON_CYCLEMISSILE]) { if (!ident_engaged && !next_missile_pressed && [self weaponsOnline]) { [self playNextMissileSelected]; [self selectNextMissile]; } next_missile_pressed = YES; } else next_missile_pressed = NO; exceptionContext = @"next target"; // '+' // next target if ([gameView isDown:key_next_target] || joyButtonState[BUTTON_NEXTTARGET]) { if ((!next_target_pressed)&&([self hasEquipmentItemProviding:@"EQ_TARGET_MEMORY"])) { [self moveTargetMemoryBy:+1]; } next_target_pressed = YES; } else next_target_pressed = NO; exceptionContext = @"previous target"; // '-' // previous target if ([gameView isDown:key_previous_target] || joyButtonState[BUTTON_PREVTARGET]) { if ((!previous_target_pressed)&&([self hasEquipmentItemProviding:@"EQ_TARGET_MEMORY"])) { [self moveTargetMemoryBy:-1]; } previous_target_pressed = YES; } else previous_target_pressed = NO; exceptionContext = @"ident R"; // shoot 'r' // switch on ident system if ([gameView isDown:key_ident_system] || joyButtonState[BUTTON_ID]) { // ident 'on' here if (!ident_pressed) { [self handleButtonIdent]; } ident_pressed = YES; } else ident_pressed = NO; exceptionContext = @"prime equipment"; // prime equipment 'N' - selects equipment to use with keypress if ([gameView isDown:key_prime_equipment] || joyButtonState[BUTTON_PRIMEEQUIPMENT]) { if (!prime_equipment_pressed) { // cycle through all the relevant equipment. NSUInteger c = [eqScripts count]; // if Ctrl is held down at the same time as the prime equipment key, // cycle relevant equipment in reverse if (![gameView isCtrlDown]) { primedEquipment++; if (primedEquipment > c) primedEquipment = 0; } else { if (primedEquipment > 0) primedEquipment--; else primedEquipment = c; } if (primedEquipment == c) { if (c > 0) { [self playNextEquipmentSelected]; [UNIVERSE addMessage:DESC(@"equipment-primed-none") forCount:2.0]; } else [UNIVERSE addMessage:DESC(@"equipment-primed-none-available") forCount:2.0]; } else { [self playNextEquipmentSelected]; NSString *equipmentName = [[OOEquipmentType equipmentTypeWithIdentifier:[[eqScripts oo_arrayAtIndex:primedEquipment] oo_stringAtIndex:0]] name]; [UNIVERSE addMessage:OOExpandKey(@"equipment-primed", equipmentName) forCount:2.0]; } } prime_equipment_pressed = YES; } else prime_equipment_pressed = NO; exceptionContext = @"activate equipment"; // activate equipment 'n' - runs the activated() function inside the equipment's script. if ([gameView isDown:key_activate_equipment] || joyButtonState[BUTTON_ACTIVATEEQUIPMENT]) { if (!activate_equipment_pressed) { [self activatePrimableEquipment:primedEquipment withMode:OOPRIMEDEQUIP_ACTIVATED]; } activate_equipment_pressed = YES; } else activate_equipment_pressed = NO; exceptionContext = @"mode equipment"; // mode equipment 'b' - runs the mode() function inside the equipment's script. if ([gameView isDown:key_mode_equipment] || joyButtonState[BUTTON_MODEEQUIPMENT]) { if (!mode_equipment_pressed) { [self activatePrimableEquipment:primedEquipment withMode:OOPRIMEDEQUIP_MODE]; } mode_equipment_pressed = YES; } else mode_equipment_pressed = NO; exceptionContext = @"fast equipment A"; if ([gameView isDown:key_fastactivate_equipment_a] || joyButtonState[BUTTON_CLOAK]) { if (!fastactivate_a_pressed) { [self activatePrimableEquipment:[self eqScriptIndexForKey:[self fastEquipmentA]] withMode:OOPRIMEDEQUIP_ACTIVATED]; } fastactivate_a_pressed = YES; } else fastactivate_a_pressed = NO; exceptionContext = @"fast equipment B"; if ([gameView isDown:key_fastactivate_equipment_b] || joyButtonState[BUTTON_ENERGYBOMB]) { if (!fastactivate_b_pressed) { [self activatePrimableEquipment:[self eqScriptIndexForKey:[self fastEquipmentB]] withMode:OOPRIMEDEQUIP_ACTIVATED]; } fastactivate_b_pressed = YES; } else fastactivate_b_pressed = NO; exceptionContext = @"incoming missile T"; // target nearest incoming missile 'T' - useful for quickly giving a missile target to turrets if ([gameView isDown:key_target_incoming_missile] || joyButtonState[BUTTON_TARGETINCOMINGMISSILE]) { if (!target_incoming_missile_pressed) { [self targetNearestIncomingMissile]; } target_incoming_missile_pressed = YES; } else target_incoming_missile_pressed = NO; exceptionContext = @"missile T"; // shoot 't' // switch on missile targeting if (([gameView isDown:key_target_missile] || joyButtonState[BUTTON_ARMMISSILE])&&(missile_entity[activeMissile])) { // targeting 'on' here if (!target_missile_pressed) { [self handleButtonTargetMissile]; } target_missile_pressed = YES; } else target_missile_pressed = NO; exceptionContext = @"missile U"; // shoot 'u' // disarm missile targeting if ([gameView isDown:key_untarget_missile] || joyButtonState[BUTTON_UNARM]) { if (!safety_pressed) { //targeting off in both cases! if ([self primaryTarget] != nil) [self noteLostTarget]; DESTROY(_primaryTarget); [self safeAllMissiles]; if (!ident_engaged && [self weaponsOnline]) { [UNIVERSE addMessage:DESC(@"missile-safe") forCount:2.0]; [self playMissileSafe]; } else { [UNIVERSE addMessage:DESC(@"ident-off") forCount:2.0]; [self playIdentOff]; } ident_engaged = NO; } safety_pressed = YES; } else safety_pressed = NO; exceptionContext = @"ECM"; // shoot 'e' // ECM if (([gameView isDown:key_ecm] || joyButtonState[BUTTON_ECM]) && [self hasECM]) { if (!ecm_in_operation) { if ([self fireECM]) { [self playFiredECMSound]; [UNIVERSE addMessage:DESC(@"ecm-on") forCount:3.0]; } } } exceptionContext = @"escape pod"; // shoot 'escape' // Escape pod launch - NOTE: Allowed at all times, but requires double press within a specific time interval. // Double press not available in strict mode or when the "escape-pod-activation-immediate" override is in the // user defaults file. if (([gameView isDown:key_launch_escapepod] || joyButtonState[BUTTON_ESCAPE]) && [self hasEscapePod]) { BOOL goodToLaunch = [[NSUserDefaults standardUserDefaults] boolForKey:@"escape-pod-activation-immediate"]; static OOTimeDelta escapePodKeyResetTime; if (!goodToLaunch) { if (!escapePodKey_pressed) { escapePodKey_pressed = YES; // first keypress will unregister in KEY_REPEAT_INTERVAL seconds escapePodKeyResetTime = [NSDate timeIntervalSinceReferenceDate] + KEY_REPEAT_INTERVAL; [gameView clearKey:key_launch_escapepod]; if ([stickHandler joystickCount]) { [stickHandler clearStickButtonState:BUTTON_ESCAPE]; } } else { OOTimeDelta timeNow = [NSDate timeIntervalSinceReferenceDate]; escapePodKey_pressed = NO; if (timeNow < escapePodKeyResetTime) goodToLaunch = YES; } } if (goodToLaunch) { [self launchEscapeCapsule]; } } exceptionContext = @"dump cargo"; // shoot 'd' // Dump Cargo if (([gameView isDown:key_dump_cargo] || joyButtonState[BUTTON_JETTISON]) && [cargo count] > 0) { [self dumpCargo]; } exceptionContext = @"rotate cargo"; // shoot 'R' // Rotate Cargo if ([gameView isDown:key_rotate_cargo]) { if ((!rotateCargo_pressed)&&([cargo count] > 0)) [self rotateCargo]; rotateCargo_pressed = YES; } else rotateCargo_pressed = NO; exceptionContext = @"autopilot C"; // autopilot 'c' if ([gameView isDown:key_autopilot] || joyButtonState[BUTTON_DOCKCPU]) // look for the 'c' key { if ([self hasDockingComputer] && (!autopilot_key_pressed)) { [self handleAutopilotOn:false]; } autopilot_key_pressed = YES; } else autopilot_key_pressed = NO; exceptionContext = @"autopilot shift-C"; // autopilot 'C' - fast-autopilot if ([gameView isDown:key_autodock] || joyButtonState[BUTTON_DOCKCPUFAST]) // look for the 'C' key { if ([self hasDockingComputer] && (!fast_autopilot_key_pressed)) { [self handleAutopilotOn:true]; } fast_autopilot_key_pressed = YES; } else { fast_autopilot_key_pressed = NO; } exceptionContext = @"docking clearance request"; if ([gameView isDown:key_docking_clearance_request]) { if (!docking_clearance_request_key_pressed) { Entity *primeTarget = [self primaryTarget]; if (primeTarget != nil && [primeTarget isStation] && [primeTarget isKindOfClass:[StationEntity class]]) { NSString *stationDockingClearanceStatus = [(StationEntity*)primeTarget acceptDockingClearanceRequestFrom:self]; if (stationDockingClearanceStatus != nil) { [self doScriptEvent:OOJSID("playerRequestedDockingClearance") withArgument:stationDockingClearanceStatus]; } } } docking_clearance_request_key_pressed = YES; } else { docking_clearance_request_key_pressed = NO; } exceptionContext = @"hyperspace"; // hyperspace 'h' if ( ([gameView isDown:key_hyperspace] || joyButtonState[BUTTON_HYPERDRIVE]) && [self hasHyperspaceMotor] ) // look for the 'h' key { if (!hyperspace_pressed) { if ([self status] == STATUS_WITCHSPACE_COUNTDOWN) { [self cancelWitchspaceCountdown]; if (galactic_witchjump) { galactic_witchjump = NO; [UNIVERSE addMessage:DESC(@"witch-user-galactic-abort") forCount:3.0]; } else { [UNIVERSE addMessage:DESC(@"witch-user-abort") forCount:3.0]; } } else if ([self witchJumpChecklist:false]) { [self beginWitchspaceCountdown:hyperspaceMotorSpinTime]; } } hyperspace_pressed = YES; } else hyperspace_pressed = NO; exceptionContext = @"galactic hyperspace"; // Galactic hyperspace 'g' if (([gameView isDown:key_galactic_hyperspace] || joyButtonState[BUTTON_GALACTICDRIVE]) && ([self hasEquipmentItemProviding:@"EQ_GAL_DRIVE"]))// look for the 'g' key { if (!galhyperspace_pressed) { if ([self status] == STATUS_WITCHSPACE_COUNTDOWN) { [self cancelWitchspaceCountdown]; if (galactic_witchjump) { galactic_witchjump = NO; [UNIVERSE addMessage:DESC(@"witch-user-galactic-abort") forCount:3.0]; } else { [UNIVERSE addMessage:DESC(@"witch-user-abort") forCount:3.0]; } } else { galactic_witchjump = YES; // even if we don't have a witchspace motor, we can still do a default galactic jump (!) if(EXPECT([self hasHyperspaceMotor])) witchspaceCountdown = hyperspaceMotorSpinTime; else witchspaceCountdown = DEFAULT_HYPERSPACE_SPIN_TIME; [self setStatus:STATUS_WITCHSPACE_COUNTDOWN]; [self playGalacticHyperspace]; // say it! [UNIVERSE addMessage:[NSString stringWithFormat:DESC(@"witch-galactic-in-f-seconds"), witchspaceCountdown] forCount:1.0]; // FIXME: how to preload target system for hyperspace jump? [self doScriptEvent:OOJSID("playerStartedJumpCountdown") withArguments:[NSArray arrayWithObjects:@"galactic", [NSNumber numberWithFloat:witchspaceCountdown], nil]]; } } galhyperspace_pressed = YES; } else galhyperspace_pressed = NO; } #if OO_FOV_INFLIGHT_CONTROL_ENABLED // Field of view controls if (![UNIVERSE displayGUI]) { if (([gameView isDown:key_inc_field_of_view] || joyButtonState[BUTTON_INC_FIELD_OF_VIEW]) && (fieldOfView < MAX_FOV)) { fieldOfView *= pow(fov_delta, delta_t); if (fieldOfView > MAX_FOV) fieldOfView = MAX_FOV; } if (([gameView isDown:key_dec_field_of_view] || joyButtonState[BUTTON_DEC_FIELD_OF_VIEW]) && (fieldOfView > MIN_FOV)) { fieldOfView /= pow(fov_delta, delta_t); if (fieldOfView < MIN_FOV) fieldOfView = MIN_FOV; } NSDictionary *functionForFovAxis = [[stickHandler axisFunctions] oo_dictionaryForKey:[[NSNumber numberWithInt:AXIS_FIELD_OF_VIEW] stringValue]]; if ([stickHandler joystickCount] != 0 && functionForFovAxis != nil) { // TODO think reqFov through double reqFov = [stickHandler getAxisState: AXIS_FIELD_OF_VIEW]; if (fieldOfView < maxFieldOfView * reqFov) { fieldOfView *= pow(fov_delta, delta_t); if (fieldOfView > MAX_FOV) fieldOfView = MAX_FOV; } if (fieldOfView > maxFieldOfView * reqFov) { fieldOfView /= pow(fov_delta, delta_t); if (fieldOfView < MIN_FOV) fieldOfView = MIN_FOV; } } } #endif #ifndef NDEBUG exceptionContext = @"dump target state"; if ([gameView isDown:key_dump_target_state]) { if (!dump_target_state_pressed) { dump_target_state_pressed = YES; id target = [self primaryTarget]; if (target == nil) target = self; [target dumpState]; } } else dump_target_state_pressed = NO; #endif // text displays exceptionContext = @"pollGuiScreenControls"; [self pollGuiScreenControls]; } else { // game is paused // check options menu request exceptionContext = @"options menu"; if (([gameView isDown:gvFunctionKey2] || [gameView isDown:key_view_aft]) && (gui_screen != GUI_SCREEN_OPTIONS)) { [gameView clearKeys]; [self setGuiToLoadSaveScreen]; } #if (ALLOW_CUSTOM_VIEWS_WHILE_PAUSED) [self pollCustomViewControls]; // allow custom views during pause #endif if (gui_screen == GUI_SCREEN_OPTIONS || gui_screen == GUI_SCREEN_GAMEOPTIONS || gui_screen == GUI_SCREEN_STICKMAPPER || gui_screen == GUI_SCREEN_STICKPROFILE || gui_screen == GUI_SCREEN_KEYBOARD) { if ([UNIVERSE pauseMessageVisible]) [[UNIVERSE messageGUI] leaveLastLine]; else [[UNIVERSE messageGUI] clear]; NSTimeInterval time_this_frame = [NSDate timeIntervalSinceReferenceDate]; OOTimeDelta time_delta; if (![[GameController sharedController] isGamePaused]) { time_delta = time_this_frame - time_last_frame; time_last_frame = time_this_frame; time_delta = OOClamp_0_max_d(time_delta, MINIMUM_GAME_TICK); } else { time_delta = 0.0; } script_time += time_delta; [self pollGuiArrowKeyControls:time_delta]; } exceptionContext = @"debug keys"; #ifndef NDEBUG // look for debugging keys if ([gameView isDown:gvNumberKey0])// look for the '0' key { if (!dump_entity_list_pressed) { [UNIVERSE debugDumpEntities]; gDebugFlags = 0; [UNIVERSE addMessage:@"Entity List dumped. Debugging OFF" forCount:3]; } dump_entity_list_pressed = YES; } else dump_entity_list_pressed = NO; // look for debugging keys if ([gameView isDown:'d'])// look for the 'd' key { gDebugFlags = DEBUG_ALL; [UNIVERSE addMessage:@"Full debug ON" forCount:3]; } if ([gameView isDown:'b'])// look for the 'b' key { gDebugFlags |= DEBUG_COLLISIONS; [UNIVERSE addMessage:@"Collision debug ON" forCount:3]; } if ([gameView isDown:'c'] && ![[OODebugMonitor sharedDebugMonitor] usingPlugInController]) // look for the 'c' key { // This code is executed only if we're not using the integrated plugin controller if (!autopilot_key_pressed) { if (![[OODebugMonitor sharedDebugMonitor] debuggerConnected]) { OOInitDebugSupport(); if ([[OODebugMonitor sharedDebugMonitor] debuggerConnected]) [UNIVERSE addMessage:@"Connected to debug console." forCount:3]; } else { [[OODebugMonitor sharedDebugMonitor] setDebugger:nil]; [UNIVERSE addMessage:@"Disconnected from debug console." forCount:3]; } } autopilot_key_pressed = YES; } else autopilot_key_pressed = NO; if ([gameView isDown:'x'])// look for the 'x' key { gDebugFlags |= DEBUG_BOUNDING_BOXES; [UNIVERSE addMessage:@"Bounding box debug ON" forCount:3]; } if ([gameView isDown:'s'])// look for the 's' key { OOLogSetDisplayMessagesInClass(@"$shaderDebugOn", YES); [UNIVERSE addMessage:@"Shader debug ON" forCount:3]; } if (([gameView isDown:key_gui_arrow_left] || [gameView isDown:key_gui_arrow_right]) && gui_screen != GUI_SCREEN_GAMEOPTIONS && [UNIVERSE displayFPS]) { if (!leftRightKeyPressed) { float newTimeAccelerationFactor = [gameView isDown:key_gui_arrow_left] ? fmax([UNIVERSE timeAccelerationFactor] / 2.0f, TIME_ACCELERATION_FACTOR_MIN) : fmin([UNIVERSE timeAccelerationFactor] * 2.0f, TIME_ACCELERATION_FACTOR_MAX); [UNIVERSE setTimeAccelerationFactor:newTimeAccelerationFactor]; } leftRightKeyPressed = YES; } else leftRightKeyPressed = NO; if ([gameView isDown:'n'])// look for the 'n' key { gDebugFlags = 0; [UNIVERSE addMessage:@"All debug flags OFF" forCount:3]; OOLogSetDisplayMessagesInClass(@"$shaderDebugOn", NO); } #endif } exceptionContext = @"pause"; // Pause game 'p' if ([gameView isDown:key_pausebutton] && gui_screen != GUI_SCREEN_LONG_RANGE_CHART && gui_screen != GUI_SCREEN_MISSION)// look for the 'p' key { if (!pause_pressed) { if (paused) { script_time = saved_script_time; // Reset to correct GUI screen, if we are unpausing from one. // Don't set gui_screen here, use setGuis - they also switch backgrounds. // No gui switching events will be triggered while still paused. switch (saved_gui_screen) { case GUI_SCREEN_STATUS: [self setGuiToStatusScreen]; break; case GUI_SCREEN_LONG_RANGE_CHART: [self setGuiToLongRangeChartScreen]; break; case GUI_SCREEN_SHORT_RANGE_CHART: [self setGuiToShortRangeChartScreen]; break; case GUI_SCREEN_MANIFEST: [self setGuiToManifestScreen]; break; case GUI_SCREEN_MARKET: [self setGuiToMarketScreen]; break; case GUI_SCREEN_MARKETINFO: [self setGuiToMarketInfoScreen]; break; case GUI_SCREEN_SYSTEM_DATA: // Do not reset planet rotation if we are already in the system info screen! if (gui_screen != GUI_SCREEN_SYSTEM_DATA) [self setGuiToSystemDataScreen]; break; default: gui_screen = saved_gui_screen; // make sure we're back to the right screen break; } [gameView allowStringInput:NO]; [UNIVERSE clearPreviousMessage]; [UNIVERSE setViewDirection:saved_view_direction]; currentWeaponFacing = saved_weapon_facing; // make sure the light comes from the right direction after resuming from pause! if (saved_gui_screen == GUI_SCREEN_SYSTEM_DATA) [UNIVERSE setMainLightPosition:_sysInfoLight]; [[UNIVERSE gui] setForegroundTextureKey:@"overlay"]; [[UNIVERSE gameController] setGamePaused:NO]; } else { saved_view_direction = [UNIVERSE viewDirection]; saved_script_time = script_time; saved_gui_screen = gui_screen; saved_weapon_facing = currentWeaponFacing; [UNIVERSE pauseGame]; // pause handler } } pause_pressed = YES; } else { pause_pressed = NO; } } @catch (NSException *exception) { OOLog(kOOLogException, @"***** Exception in pollFlightControls [%@]: %@ : %@", exceptionContext, [exception name], [exception reason]); } } - (void) pollGuiArrowKeyControls:(double) delta_t { MyOpenGLView *gameView = [UNIVERSE gameView]; BOOL moving = NO; BOOL dragging = NO; double cursor_speed = ([gameView isCtrlDown] ? 20.0 : 10.0)* chart_zoom; GameController *controller = [UNIVERSE gameController]; GuiDisplayGen *gui = [UNIVERSE gui]; GUI_ROW_INIT(gui); // deal with string inputs as necessary if (gui_screen == GUI_SCREEN_LONG_RANGE_CHART) { [gameView setStringInput: gvStringInputAlpha]; } else if (gui_screen == GUI_SCREEN_SAVE) { [gameView setStringInput: gvStringInputLoadSave]; } else if (gui_screen == GUI_SCREEN_MISSION && _missionTextEntry) { [gameView setStringInput: gvStringInputAll]; } #if 0 // at the moment this function is never called for GUI_SCREEN_OXZMANAGER // but putting this here in case we do later else if (gui_screen == GUI_SCREEN_OXZMANAGER && [[OOOXZManager sharedManager] isAcceptingTextInput]) { [gameView setStringInput: gvStringInputAll]; } #endif else { [gameView allowStringInput: NO]; // If we have entered this screen with the injectors key pressed, make sure // that injectors switch off when we release it - Nikos. if (afterburner_engaged && ![gameView isDown:key_inject_fuel]) { afterburner_engaged = NO; } } switch (gui_screen) { case GUI_SCREEN_LONG_RANGE_CHART: if ([self status] != STATUS_WITCHSPACE_COUNTDOWN) { if ([[gameView typedString] length] > 0) { planetSearchString = [[[gameView typedString] lowercaseString] retain]; NSPoint search_coords = [UNIVERSE findSystemCoordinatesWithPrefix:planetSearchString]; if ((search_coords.x >= 0.0)&&(search_coords.y >= 0.0)) { // always reset the found system index at the beginning of a new search if ([planetSearchString length] == 1) [[UNIVERSE gui] targetNextFoundSystem:0]; // Always select the right one out of 2 overlapping systems. [self targetNewSystem:0 whileTyping:YES]; } else { found_system_id = -1; [self clearPlanetSearchString]; } } else { if ([gameView isDown:gvDeleteKey]) // did we just delete the string ? { found_system_id = -1; [UNIVERSE findSystemCoordinatesWithPrefix:@""]; } if (planetSearchString) [planetSearchString release]; planetSearchString = nil; } moving |= (searchStringLength != [[gameView typedString] length]); searchStringLength = [[gameView typedString] length]; } case GUI_SCREEN_SHORT_RANGE_CHART: if ([gameView isDown:key_chart_highlight]) // '?' toggle chart colours { if (!queryPressed) { OOLongRangeChartMode mode = [self longRangeChartMode]; if (mode != OOLRC_MODE_TECHLEVEL) { [self setLongRangeChartMode:mode+1]; } else { [self setLongRangeChartMode:OOLRC_MODE_NORMAL]; } } queryPressed = YES; } else { queryPressed = NO; } if ([gameView isDown:key_map_info] && chart_zoom <= CHART_ZOOM_SHOW_LABELS) { if (!chartInfoPressed) { show_info_flag = !show_info_flag; chartInfoPressed = YES; } } else { chartInfoPressed = NO; } if ([self status] != STATUS_WITCHSPACE_COUNTDOWN) { if ([self hasEquipmentItemProviding:@"EQ_ADVANCED_NAVIGATIONAL_ARRAY"]) { if ([gameView isDown:key_advanced_nav_array]) // '^' key { if (!pling_pressed) { if ([gameView isCtrlDown]) { switch (ANA_mode) { case OPTIMIZED_BY_NONE: ANA_mode = OPTIMIZED_BY_TIME; break; case OPTIMIZED_BY_TIME: ANA_mode = OPTIMIZED_BY_JUMPS; break; default: ANA_mode = OPTIMIZED_BY_NONE; break; } } else { switch (ANA_mode) { case OPTIMIZED_BY_NONE: ANA_mode = OPTIMIZED_BY_JUMPS; break; case OPTIMIZED_BY_JUMPS:ANA_mode = OPTIMIZED_BY_TIME; break; default: ANA_mode = OPTIMIZED_BY_NONE; break; } } } pling_pressed = YES; } else { pling_pressed = NO; } } else { ANA_mode = OPTIMIZED_BY_NONE; } if ([gameView isDown:gvMouseDoubleClick]) { [gameView clearMouse]; mouse_left_down = NO; [self noteGUIWillChangeTo:GUI_SCREEN_SYSTEM_DATA]; showingLongRangeChart = (gui_screen == GUI_SCREEN_LONG_RANGE_CHART); [self setGuiToSystemDataScreen]; break; } if ([gameView isDown:gvMouseLeftButton]) { NSPoint maus = [gameView virtualJoystickPosition]; double vadjust = MAIN_GUI_PIXEL_HEIGHT/2.0 - CHART_SCREEN_VERTICAL_CENTRE; double hscale = MAIN_GUI_PIXEL_WIDTH / (64.0 * chart_zoom); double vscale = MAIN_GUI_PIXEL_HEIGHT / (128.0 * chart_zoom); if (mouse_left_down == NO) { NSPoint centre = [self adjusted_chart_centre]; centre_at_mouse_click = chart_centre_coordinates; mouse_click_position = maus; chart_focus_coordinates.x = OOClamp_0_max_f(centre.x + (maus.x * MAIN_GUI_PIXEL_WIDTH) / hscale, 256.0); chart_focus_coordinates.y = OOClamp_0_max_f(centre.y + (maus.y * MAIN_GUI_PIXEL_HEIGHT + vadjust) / vscale, 256.0); } if (fabs(maus.x - mouse_click_position.x)*MAIN_GUI_PIXEL_WIDTH > 2 || fabs(maus.y - mouse_click_position.y)*MAIN_GUI_PIXEL_HEIGHT > 2) { target_chart_centre.x = OOClamp_0_max_f(centre_at_mouse_click.x - (maus.x - mouse_click_position.x)*MAIN_GUI_PIXEL_WIDTH/hscale, 256.0); target_chart_centre.y = OOClamp_0_max_f(centre_at_mouse_click.y - (maus.y - mouse_click_position.y)*MAIN_GUI_PIXEL_HEIGHT/vscale, 256.0); dragging = YES; } if (gui_screen == GUI_SCREEN_LONG_RANGE_CHART) [gameView resetTypedString]; mouse_left_down = YES; } else if (mouse_left_down == YES) { NSPoint maus = [gameView virtualJoystickPosition]; if (fabs(maus.x - mouse_click_position.x)*MAIN_GUI_PIXEL_WIDTH <= 2 && fabs(maus.y - mouse_click_position.y)*MAIN_GUI_PIXEL_HEIGHT <= 2) { cursor_coordinates = chart_focus_coordinates; moving = YES; } else { dragging = YES; } mouse_left_down = NO; } if ([gameView isDown:key_map_home]) { [gameView resetTypedString]; cursor_coordinates = galaxy_coordinates; chart_focus_coordinates = cursor_coordinates; target_chart_centre = galaxy_coordinates; found_system_id = -1; [UNIVERSE findSystemCoordinatesWithPrefix:@""]; moving = YES; } if ([gameView isDown:gvPageDownKey] || [gameView mouseWheelState] == gvMouseWheelDown) { target_chart_zoom *= CHART_ZOOM_SPEED_FACTOR; if (target_chart_zoom > CHART_MAX_ZOOM) target_chart_zoom = CHART_MAX_ZOOM; saved_chart_zoom = target_chart_zoom; moving = YES; } if ([gameView isDown:gvPageUpKey] || [gameView mouseWheelState] == gvMouseWheelUp) { if (gui_screen == GUI_SCREEN_LONG_RANGE_CHART) { gui_screen = GUI_SCREEN_SHORT_RANGE_CHART; target_chart_zoom = CHART_MAX_ZOOM; [gui clearAndKeepBackground: YES]; } target_chart_zoom /= CHART_ZOOM_SPEED_FACTOR; if (target_chart_zoom < 1.0) target_chart_zoom = 1.0; saved_chart_zoom = target_chart_zoom; moving = YES; //target_chart_centre = cursor_coordinates; chart_focus_coordinates = target_chart_centre; } BOOL nextSystem = [gameView isShiftDown]; if ([gameView isDown:key_gui_arrow_left]) { if (nextSystem && pressedArrow != key_gui_arrow_left) { [self targetNewSystem:-1]; pressedArrow = key_gui_arrow_left; } else if (!nextSystem) { [gameView resetTypedString]; cursor_coordinates.x -= cursor_speed*delta_t; if (cursor_coordinates.x < 0.0) cursor_coordinates.x = 0.0; moving = YES; } chart_focus_coordinates = cursor_coordinates; } else pressedArrow = pressedArrow == key_gui_arrow_left ? 0 : pressedArrow; if ([gameView isDown:key_gui_arrow_right]) { if (nextSystem && pressedArrow != key_gui_arrow_right) { [self targetNewSystem:+1]; pressedArrow = key_gui_arrow_right; } else if (!nextSystem) { [gameView resetTypedString]; cursor_coordinates.x += cursor_speed*delta_t; if (cursor_coordinates.x > 256.0) cursor_coordinates.x = 256.0; moving = YES; } chart_focus_coordinates = cursor_coordinates; } else pressedArrow = pressedArrow == key_gui_arrow_right ? 0 : pressedArrow; if ([gameView isDown:key_gui_arrow_down]) { if (nextSystem && pressedArrow != key_gui_arrow_down) { [self targetNewSystem:+1]; pressedArrow = key_gui_arrow_down; } else if (!nextSystem) { [gameView resetTypedString]; cursor_coordinates.y += cursor_speed*delta_t*2.0; if (cursor_coordinates.y > 256.0) cursor_coordinates.y = 256.0; moving = YES; } chart_focus_coordinates = cursor_coordinates; } else pressedArrow = pressedArrow == key_gui_arrow_down ? 0 : pressedArrow; if ([gameView isDown:key_gui_arrow_up]) { if (nextSystem && pressedArrow != key_gui_arrow_up) { [self targetNewSystem:-1]; pressedArrow = key_gui_arrow_up; } else if (!nextSystem) { [gameView resetTypedString]; cursor_coordinates.y -= cursor_speed*delta_t*2.0; if (cursor_coordinates.y < 0.0) cursor_coordinates.y = 0.0; moving = YES; } chart_focus_coordinates = cursor_coordinates; } else pressedArrow = pressedArrow == key_gui_arrow_up ? 0 : pressedArrow; if ((cursor_moving)&&(!moving)) { if (found_system_id == -1) { target_system_id = [UNIVERSE findSystemAtCoords:cursor_coordinates withGalaxy:galaxy_number]; } else { // if found with a search string, don't recalculate! Required for overlapping systems, like Divees & Tezabi in galaxy 5 NSPoint fpos = [[UNIVERSE systemManager] getCoordinatesForSystem:found_system_id inGalaxy:galaxy_number]; if (fpos.x != cursor_coordinates.x && fpos.y != cursor_coordinates.y) { target_system_id = [UNIVERSE findSystemAtCoords:cursor_coordinates withGalaxy:galaxy_number]; } } cursor_coordinates = [[UNIVERSE systemManager] getCoordinatesForSystem:target_system_id inGalaxy:galaxy_number]; } if (chart_focus_coordinates.x - target_chart_centre.x <= -CHART_SCROLL_AT_X*chart_zoom) { target_chart_centre.x = chart_focus_coordinates.x + CHART_SCROLL_AT_X*chart_zoom; } else if (chart_focus_coordinates.x - target_chart_centre.x >= CHART_SCROLL_AT_X*chart_zoom) { target_chart_centre.x = chart_focus_coordinates.x - CHART_SCROLL_AT_X*chart_zoom; } if (chart_focus_coordinates.y - target_chart_centre.y <= -CHART_SCROLL_AT_Y*chart_zoom) { target_chart_centre.y = chart_focus_coordinates.y + CHART_SCROLL_AT_Y*chart_zoom; } else if (chart_focus_coordinates.y - target_chart_centre.y >= CHART_SCROLL_AT_Y*chart_zoom) { target_chart_centre.y = chart_focus_coordinates.y - CHART_SCROLL_AT_Y*chart_zoom; } chart_centre_coordinates.x = (3.0*chart_centre_coordinates.x + target_chart_centre.x)/4.0; chart_centre_coordinates.y = (3.0*chart_centre_coordinates.y + target_chart_centre.y)/4.0; chart_zoom = (3.0*chart_zoom + target_chart_zoom)/4.0; chart_cursor_coordinates.x = (3.0*chart_cursor_coordinates.x + cursor_coordinates.x)/4.0; chart_cursor_coordinates.y = (3.0*chart_cursor_coordinates.y + cursor_coordinates.y)/4.0; if (cursor_moving || dragging) [self setGuiToChartScreenFrom: gui_screen]; // update graphics cursor_moving = moving; } case GUI_SCREEN_SYSTEM_DATA: break; #if OO_USE_CUSTOM_LOAD_SAVE // DJS: Farm off load/save screen options to LoadSave.m case GUI_SCREEN_LOAD: { NSString *commanderFile = [self commanderSelector]; if(commanderFile) { // also release the demo ship here (see showShipyardModel and noteGUIDidChangeFrom) [demoShip release]; demoShip = nil; [self loadPlayerFromFile:commanderFile asNew:NO]; } break; } case GUI_SCREEN_SAVE: [self pollGuiScreenControlsWithFKeyAlias:NO]; /* Only F1 works for launch on this screen, not '1' or * whatever it has been bound to */ if ([gameView isDown:gvFunctionKey1]) [self handleUndockControl]; if (gui_screen == GUI_SCREEN_SAVE) { [self saveCommanderInputHandler]; } else pollControls = YES; break; case GUI_SCREEN_SAVE_OVERWRITE: [self overwriteCommanderInputHandler]; break; #endif case GUI_SCREEN_STICKMAPPER: [self stickMapperInputHandler: gui view: gameView]; leftRightKeyPressed = [gameView isDown:key_gui_arrow_right] || [gameView isDown:key_gui_arrow_left]; if (leftRightKeyPressed) { NSString *key = [gui keyForRow: [gui selectedRow]]; if ([gameView isDown:key_gui_arrow_right]) { key = [gui keyForRow:GUI_ROW_FUNCEND]; } if ([gameView isDown:key_gui_arrow_left]) { key = [gui keyForRow:GUI_ROW_FUNCSTART]; } int from_function = 0; NSArray *keyComponents = [key componentsSeparatedByString:@":"]; if ([keyComponents count] > 1) { from_function = [keyComponents oo_intAtIndex:1]; if (from_function < 0) from_function = 0; [self setGuiToStickMapperScreen:from_function resetCurrentRow: YES]; if ([[UNIVERSE gui] selectedRow] < GUI_ROW_FUNCSTART) { [[UNIVERSE gui] setSelectedRow: GUI_ROW_FUNCSTART]; } if (from_function == 0) { [[UNIVERSE gui] setSelectedRow: GUI_ROW_FUNCSTART + MAX_ROWS_FUNCTIONS - 1]; } } } break; case GUI_SCREEN_STICKPROFILE: [self stickProfileInputHandler: gui view: gameView]; break; case GUI_SCREEN_GAMEOPTIONS: [self handleGameOptionsScreenKeys]; break; case GUI_SCREEN_KEYBOARD: if ([gameView isDown:' ']) { [self setGuiToGameOptionsScreen]; } break; case GUI_SCREEN_SHIPLIBRARY: if ([gameView isDown:' ']) // '' { // viewed in game, return to interfaces as that's where it's accessed from [self setGuiToInterfacesScreen:0]; } if ([gameView isDown:key_gui_arrow_up]) // '<--' { if (!upDownKeyPressed) [UNIVERSE selectIntro2Previous]; } if ([gameView isDown:key_gui_arrow_down]) // '-->' { if (!upDownKeyPressed) [UNIVERSE selectIntro2Next]; } upDownKeyPressed = (([gameView isDown:key_gui_arrow_up])||([gameView isDown:key_gui_arrow_down])); if ([gameView isDown:key_gui_arrow_left]) // '<--' { if (!leftRightKeyPressed) [UNIVERSE selectIntro2PreviousCategory]; } if ([gameView isDown:key_gui_arrow_right]) // '-->' { if (!leftRightKeyPressed) [UNIVERSE selectIntro2NextCategory]; } leftRightKeyPressed = (([gameView isDown:key_gui_arrow_left])||([gameView isDown:key_gui_arrow_right])); break; case GUI_SCREEN_OPTIONS: [self handleGUIUpDownArrowKeys]; OOGUIRow guiSelectedRow = [gui selectedRow]; BOOL selectKeyPress = ([gameView isDown:13]||[gameView isDown:gvMouseDoubleClick]); if (selectKeyPress) // 'enter' { if ((guiSelectedRow == GUI_ROW(,QUICKSAVE))&&(!disc_operation_in_progress)) { @try { disc_operation_in_progress = YES; [self quicksavePlayer]; } @catch (NSException *exception) { OOLog(kOOLogException, @"\n\n***** Handling exception: %@ : %@ *****\n\n",[exception name], [exception reason]); if ([[exception name] isEqual:@"GameNotSavedException"]) // try saving game instead { OOLog(kOOLogException, @"\n\n***** Trying a normal save instead *****\n\n"); if ([controller inFullScreenMode]) [controller pauseFullScreenModeToPerform:@selector(savePlayer) onTarget:self]; else [self savePlayer]; } else { @throw exception; } } } if ((guiSelectedRow == GUI_ROW(,SAVE))&&(!disc_operation_in_progress)) { disc_operation_in_progress = YES; [self savePlayer]; } if ((guiSelectedRow == GUI_ROW(,LOAD))&&(!disc_operation_in_progress)) { disc_operation_in_progress = YES; if (![self loadPlayer]) { disc_operation_in_progress = NO; [self setGuiToStatusScreen]; } } if ((guiSelectedRow == GUI_ROW(,BEGIN_NEW))&&(!disc_operation_in_progress)) { disc_operation_in_progress = YES; [UNIVERSE setUseAddOns:SCENARIO_OXP_DEFINITION_ALL fromSaveGame:NO forceReinit:YES]; // calls reinitAndShowDemo } if ([gameView isDown:gvMouseDoubleClick]) [gameView clearMouse]; } else { disc_operation_in_progress = NO; } #if OOLITE_SDL // quit only appears in GNUstep as users aren't // used to Cmd-Q equivs. Same goes for window // vs fullscreen. if ((guiSelectedRow == GUI_ROW(,QUIT)) && selectKeyPress) { [[UNIVERSE gameController] exitAppWithContext:@"Exit Game selected on options screen"]; } #endif if ((guiSelectedRow == GUI_ROW(,GAMEOPTIONS)) && selectKeyPress) { [gameView clearKeys]; [self setGuiToGameOptionsScreen]; } break; case GUI_SCREEN_EQUIP_SHIP: if ([self handleGUIUpDownArrowKeys]) { NSString *itemText = [gui selectedRowText]; OOWeaponType weaponType = nil; if ([itemText isEqual:FORWARD_FACING_STRING]) weaponType = forward_weapon_type; if ([itemText isEqual:AFT_FACING_STRING]) weaponType = aft_weapon_type; if ([itemText isEqual:PORT_FACING_STRING]) weaponType = port_weapon_type; if ([itemText isEqual:STARBOARD_FACING_STRING]) weaponType = starboard_weapon_type; if (weaponType != nil) { BOOL sameAs = OOWeaponTypeFromEquipmentIdentifierSloppy([gui selectedRowKey]) == weaponType; // override showInformation _completely_ with itemText if ([[weaponType identifier] isEqualToString:@"EQ_WEAPON_NONE"]) itemText = DESC(@"no-weapon-enter-to-install"); else { NSString *weaponName = [[OOEquipmentType equipmentTypeWithIdentifier:OOEquipmentIdentifierFromWeaponType(weaponType)] name]; if (sameAs) itemText = [NSString stringWithFormat:DESC(@"weapon-installed-@"), weaponName]; else itemText = [NSString stringWithFormat:DESC(@"weapon-@-enter-to-replace"), weaponName]; } [self showInformationForSelectedUpgradeWithFormatString:itemText]; } else [self showInformationForSelectedUpgrade]; } if ([gameView isDown:key_gui_arrow_left]) { if ((!leftRightKeyPressed)||(script_time > timeLastKeyPress + KEY_REPEAT_INTERVAL)) { if ([[gui keyForRow:GUI_ROW_EQUIPMENT_START] hasPrefix:@"More:"]) { [self playMenuPagePrevious]; [gui setSelectedRow:GUI_ROW_EQUIPMENT_START]; [self buySelectedItem]; } timeLastKeyPress = script_time; } } if ([gameView isDown:key_gui_arrow_right]) { if ((!leftRightKeyPressed)||(script_time > timeLastKeyPress + KEY_REPEAT_INTERVAL)) { if ([[gui keyForRow:GUI_ROW_EQUIPMENT_START + GUI_MAX_ROWS_EQUIPMENT - 1] hasPrefix:@"More:"]) { [self playMenuPageNext]; [gui setSelectedRow:GUI_ROW_EQUIPMENT_START + GUI_MAX_ROWS_EQUIPMENT - 1]; [self buySelectedItem]; } timeLastKeyPress = script_time; } } leftRightKeyPressed = [gameView isDown:key_gui_arrow_right]|[gameView isDown:key_gui_arrow_left]; if ([gameView isDown:13] || [gameView isDown:gvMouseDoubleClick]) // 'enter' { if ([gameView isDown:gvMouseDoubleClick]) { selectPressed = NO; [gameView clearMouse]; } if ((!selectPressed)&&([gui selectedRow] > -1)) { [self buySelectedItem]; selectPressed = YES; } } else { selectPressed = NO; } break; case GUI_SCREEN_INTERFACES: if ([self handleGUIUpDownArrowKeys]) { [self showInformationForSelectedInterface]; } if ([gameView isDown:key_gui_arrow_left]) { if ((!leftRightKeyPressed)||(script_time > timeLastKeyPress + KEY_REPEAT_INTERVAL)) { if ([[gui keyForRow:GUI_ROW_INTERFACES_START] hasPrefix:@"More:"]) { [self playMenuPagePrevious]; [gui setSelectedRow:GUI_ROW_INTERFACES_START]; [self activateSelectedInterface]; } timeLastKeyPress = script_time; } } if ([gameView isDown:key_gui_arrow_right]) { if ((!leftRightKeyPressed)||(script_time > timeLastKeyPress + KEY_REPEAT_INTERVAL)) { if ([[gui keyForRow:GUI_ROW_INTERFACES_START + GUI_MAX_ROWS_INTERFACES - 1] hasPrefix:@"More:"]) { [self playMenuPageNext]; [gui setSelectedRow:GUI_ROW_INTERFACES_START + GUI_MAX_ROWS_INTERFACES - 1]; [self activateSelectedInterface]; } timeLastKeyPress = script_time; } } leftRightKeyPressed = [gameView isDown:key_gui_arrow_right]|[gameView isDown:key_gui_arrow_left]; if ([gameView isDown:13] || [gameView isDown:gvMouseDoubleClick]) // 'enter' { if ([gameView isDown:gvMouseDoubleClick]) { selectPressed = NO; [gameView clearMouse]; } if ((!selectPressed)&&([gui selectedRow] > -1)) { [self activateSelectedInterface]; selectPressed = YES; } } else { selectPressed = NO; } break; case GUI_SCREEN_MARKETINFO: [self pollMarketScreenControls]; break; case GUI_SCREEN_MARKET: [self pollMarketScreenControls]; if ([gameView isDown:key_market_filter_cycle] || [gameView isDown:key_market_sorter_cycle]) { if (!queryPressed) { queryPressed = YES; if ([gameView isDown:key_market_filter_cycle]) { if (marketFilterMode >= MARKET_FILTER_MODE_MAX) { marketFilterMode = MARKET_FILTER_MODE_OFF; } else { marketFilterMode++; } } else { if (marketSorterMode >= MARKET_SORTER_MODE_MAX) { marketSorterMode = MARKET_SORTER_MODE_OFF; } else { marketSorterMode++; } } [self playChangedOption]; [self setGuiToMarketScreen]; } } else { queryPressed = NO; } break; case GUI_SCREEN_REPORT: if ([gameView isDown:32]) // spacebar { if (!spacePressed) { BOOL reportEnded = ([dockingReport length] == 0); [self playDismissedReportScreen]; if(reportEnded) { [self setGuiToStatusScreen]; [self doScriptEvent:OOJSID("reportScreenEnded")]; // last report given. Screen is now free for missionscreens. [self doWorldEventUntilMissionScreen:OOJSID("missionScreenOpportunity")]; } else { [self setGuiToDockingReportScreen]; } } spacePressed = YES; } else spacePressed = NO; break; case GUI_SCREEN_STATUS: [self handleGUIUpDownArrowKeys]; if ([gameView isDown:key_gui_arrow_left]) { if ((!leftRightKeyPressed)||(script_time > timeLastKeyPress + KEY_REPEAT_INTERVAL)) { if ([[gui keyForRow:STATUS_EQUIPMENT_FIRST_ROW] isEqual:GUI_KEY_OK]) { [gui setSelectedRow:STATUS_EQUIPMENT_FIRST_ROW]; [self playMenuPagePrevious]; [gui setStatusPage:-1]; [self setGuiToStatusScreen]; } timeLastKeyPress = script_time; } } if ([gameView isDown:key_gui_arrow_right]) { if ((!leftRightKeyPressed)||(script_time > timeLastKeyPress + KEY_REPEAT_INTERVAL)) { if ([[gui keyForRow:STATUS_EQUIPMENT_FIRST_ROW + STATUS_EQUIPMENT_MAX_ROWS] isEqual:GUI_KEY_OK]) { [gui setSelectedRow:STATUS_EQUIPMENT_FIRST_ROW + STATUS_EQUIPMENT_MAX_ROWS]; [self playMenuPageNext]; [gui setStatusPage:+1]; [self setGuiToStatusScreen]; } timeLastKeyPress = script_time; } } leftRightKeyPressed = [gameView isDown:key_gui_arrow_right]|[gameView isDown:key_gui_arrow_left]; if ([gameView isDown:13] || [gameView isDown:gvMouseDoubleClick]) // 'enter' { if ([gameView isDown:gvMouseDoubleClick]) { selectPressed = NO; [gameView clearMouse]; } if ((!selectPressed)&&([gui selectedRow] > -1)) { [gui setStatusPage:([gui selectedRow] == STATUS_EQUIPMENT_FIRST_ROW ? -1 : +1)]; [self setGuiToStatusScreen]; selectPressed = YES; } } else { selectPressed = NO; } break; case GUI_SCREEN_MANIFEST: [self handleGUIUpDownArrowKeys]; if ([gameView isDown:key_gui_arrow_left]) { if ((!leftRightKeyPressed)||(script_time > timeLastKeyPress + KEY_REPEAT_INTERVAL)) { if ([[gui keyForRow:MANIFEST_SCREEN_ROW_BACK] isEqual:GUI_KEY_OK]) { [gui setSelectedRow:MANIFEST_SCREEN_ROW_BACK]; [self playMenuPagePrevious]; [gui setStatusPage:-1]; [self setGuiToManifestScreen]; } timeLastKeyPress = script_time; } } if ([gameView isDown:key_gui_arrow_right]) { OOGUIRow nextRow = MANIFEST_SCREEN_ROW_NEXT; if ([[self hud] isHidden] || [[self hud] allowBigGui]) { nextRow += 7; } if ((!leftRightKeyPressed)||(script_time > timeLastKeyPress + KEY_REPEAT_INTERVAL)) { if ([[gui keyForRow:nextRow] isEqual:GUI_KEY_OK]) { [gui setSelectedRow:nextRow]; [self playMenuPageNext]; [gui setStatusPage:+1]; [self setGuiToManifestScreen]; } timeLastKeyPress = script_time; } } leftRightKeyPressed = [gameView isDown:key_gui_arrow_right]|[gameView isDown:key_gui_arrow_left]; if ([gameView isDown:13] || [gameView isDown:gvMouseDoubleClick]) // 'enter' { if ([gameView isDown:gvMouseDoubleClick]) { selectPressed = NO; [gameView clearMouse]; } if ((!selectPressed)&&([gui selectedRow] > -1)) { [gui setStatusPage:([gui selectedRow] == MANIFEST_SCREEN_ROW_BACK ? -1 : +1)]; [self setGuiToManifestScreen]; selectPressed = YES; } } else { selectPressed = NO; } break; case GUI_SCREEN_SHIPYARD: if ([self handleGUIUpDownArrowKeys]) { [self showShipyardInfoForSelection]; } if ([gameView isDown:key_gui_arrow_left]) { if ((!leftRightKeyPressed)||(script_time > timeLastKeyPress + KEY_REPEAT_INTERVAL)) { if ([[gui keyForRow:GUI_ROW_SHIPYARD_START] hasPrefix:@"More:"]) { [self playMenuPagePrevious]; [gui setSelectedRow:GUI_ROW_SHIPYARD_START]; [self buySelectedShip]; } timeLastKeyPress = script_time; } } if ([gameView isDown:key_gui_arrow_right]) { if ((!leftRightKeyPressed)||(script_time > timeLastKeyPress + KEY_REPEAT_INTERVAL)) { if ([[gui keyForRow:GUI_ROW_SHIPYARD_START + MAX_ROWS_SHIPS_FOR_SALE - 1] hasPrefix:@"More:"]) { [self playMenuPageNext]; [gui setSelectedRow:GUI_ROW_SHIPYARD_START + MAX_ROWS_SHIPS_FOR_SALE - 1]; [self buySelectedShip]; } timeLastKeyPress = script_time; } } leftRightKeyPressed = [gameView isDown:key_gui_arrow_right]|[gameView isDown:key_gui_arrow_left]; if ([gameView isDown:13]) // 'enter' NOT double-click { if (!selectPressed) { // try to buy the ship! NSString *key = [gui keyForRow:[gui selectedRow]]; OOCreditsQuantity shipprice = 0; if (![key hasPrefix:@"More:"]) { shipprice = [self priceForShipKey:key]; } OOCreditsQuantity money = credits; if ([self buySelectedShip]) { if (money != credits) // money == credits means we skipped to another page, don't do anything { [UNIVERSE removeDemoShips]; [self setGuiToStatusScreen]; [self playBuyShip]; [self doScriptEvent:OOJSID("playerBoughtNewShip") withArgument:self andArgument:[NSNumber numberWithInt:shipprice]]; // some equipment.oxp might want to know everything has changed. } } else { [self playCantBuyShip]; } } selectPressed = YES; } else { selectPressed = NO; } if ([gameView isDown:gvMouseDoubleClick]) { if (([gui selectedRow] == GUI_ROW_SHIPYARD_START + MAX_ROWS_SHIPS_FOR_SALE - 1) && [[gui keyForRow:GUI_ROW_SHIPYARD_START + MAX_ROWS_SHIPS_FOR_SALE - 1] hasPrefix:@"More:"]) { [self playMenuPageNext]; [gui setSelectedRow:GUI_ROW_SHIPYARD_START + MAX_ROWS_SHIPS_FOR_SALE - 1]; [self buySelectedShip]; } else if (([gui selectedRow] == GUI_ROW_SHIPYARD_START) && [[gui keyForRow:GUI_ROW_SHIPYARD_START] hasPrefix:@"More:"]) { [self playMenuPagePrevious]; [gui setSelectedRow:GUI_ROW_SHIPYARD_START]; [self buySelectedShip]; } [gameView clearMouse]; } break; default: break; } // damp any rotations we entered with if (flightRoll > 0.0) { if (flightRoll > delta_t) [self decrease_flight_roll:delta_t]; else flightRoll = 0.0; } if (flightRoll < 0.0) { if (flightRoll < -delta_t) [self increase_flight_roll:delta_t]; else flightRoll = 0.0; } if (flightPitch > 0.0) { if (flightPitch > delta_t) [self decrease_flight_pitch:delta_t]; else flightPitch = 0.0; } if (flightPitch < 0.0) { if (flightPitch < -delta_t) [self increase_flight_pitch:delta_t]; else flightPitch = 0.0; } if (flightYaw > 0.0) { if (flightYaw > delta_t) [self decrease_flight_yaw:delta_t]; else flightYaw = 0.0; } if (flightYaw < 0.0) { if (flightYaw < -delta_t) [self increase_flight_yaw:delta_t]; else flightYaw = 0.0; } } - (void) pollMarketScreenControls { MyOpenGLView *gameView = [UNIVERSE gameView]; GuiDisplayGen *gui = [UNIVERSE gui]; if (gui_screen == GUI_SCREEN_MARKET) { [self handleGUIUpDownArrowKeys]; DESTROY(marketSelectedCommodity); marketSelectedCommodity = [[gui selectedRowKey] retain]; } else { // handle up and down slightly differently BOOL arrow_up = [gameView isDown:key_gui_arrow_up]; BOOL arrow_down = [gameView isDown:key_gui_arrow_down]; if (arrow_up || arrow_down) { if ((!upDownKeyPressed) || (script_time > timeLastKeyPress + KEY_REPEAT_INTERVAL)) { OOCommodityMarket *localMarket = [self localMarket]; NSArray *goods = [self applyMarketSorter:[self applyMarketFilter:[localMarket goods] onMarket:localMarket] onMarket:localMarket]; if ([goods count] > 0) { NSInteger goodsIndex = [goods indexOfObject:marketSelectedCommodity]; if (arrow_down) { ++goodsIndex; } else { --goodsIndex; } if (goodsIndex < 0) { goodsIndex = [goods count]-1; } else if (goodsIndex >= (NSInteger)[goods count]) { goodsIndex = 0; } DESTROY(marketSelectedCommodity); marketSelectedCommodity = [[goods oo_stringAtIndex:goodsIndex] retain]; [self setGuiToMarketInfoScreen]; } } upDownKeyPressed = YES; timeLastKeyPress = script_time; } else { upDownKeyPressed = NO; } } BOOL isdocked = [self isDocked]; if (([gameView isDown:key_gui_arrow_right])||([gameView isDown:key_gui_arrow_left])||([gameView isDown:13]||[gameView isDown:gvMouseDoubleClick])) { if ([gameView isDown:key_gui_arrow_right]) // --> { if (!wait_for_key_up) { if (isdocked && [self tryBuyingCommodity:marketSelectedCommodity all:[gameView isShiftDown]]) { [self playBuyCommodity]; if (gui_screen == GUI_SCREEN_MARKET) { [self setGuiToMarketScreen]; } else { [self setGuiToMarketInfoScreen]; } } else { if ([[gui selectedRowKey] isEqualToString:@">>>"]) { [self playMenuNavigationDown]; [self setGuiToMarketScreen]; } else if ([[gui selectedRowKey] isEqualToString:@"<<<"]) { [self playMenuNavigationUp]; [self setGuiToMarketScreen]; } else { [self playCantBuyCommodity]; } } wait_for_key_up = YES; } } if ([gameView isDown:key_gui_arrow_left]) // <-- { if (!wait_for_key_up) { if (isdocked && [self trySellingCommodity:marketSelectedCommodity all:[gameView isShiftDown]]) { [self playSellCommodity]; if (gui_screen == GUI_SCREEN_MARKET) { [self setGuiToMarketScreen]; } else { [self setGuiToMarketInfoScreen]; } } else { if ([[gui selectedRowKey] isEqualToString:@">>>"]) { [self playMenuNavigationDown]; [self setGuiToMarketScreen]; } else if ([[gui selectedRowKey] isEqualToString:@"<<<"]) { [self playMenuNavigationUp]; [self setGuiToMarketScreen]; } else { [self playCantSellCommodity]; } } wait_for_key_up = YES; } } if ((gui_screen == GUI_SCREEN_MARKET && [gameView isDown:gvMouseDoubleClick]) || [gameView isDown:13]) // 'enter' { if ([gameView isDown:gvMouseDoubleClick]) { wait_for_key_up = NO; [gameView clearMouse]; } if (!wait_for_key_up) { OOCommodityType item = marketSelectedCommodity; OOCargoQuantity yours = [shipCommodityData quantityForGood:item]; if ([item isEqualToString:@">>>"]) { [self tryBuyingCommodity:item all:YES]; [self setGuiToMarketScreen]; } else if ([item isEqualToString:@"<<<"]) { [self trySellingCommodity:item all:YES]; [self setGuiToMarketScreen]; } else if (isdocked && [gameView isShiftDown] && [self tryBuyingCommodity:item all:YES]) // buy as much as possible (with Shift) { [self playBuyCommodity]; if (gui_screen == GUI_SCREEN_MARKET) { [self setGuiToMarketScreen]; } else { [self setGuiToMarketInfoScreen]; } } else if (isdocked && (yours > 0) && [self trySellingCommodity:item all:YES]) // sell all you can { [self playSellCommodity]; if (gui_screen == GUI_SCREEN_MARKET) { [self setGuiToMarketScreen]; } else { [self setGuiToMarketInfoScreen]; } } else if (isdocked && [self tryBuyingCommodity:item all:YES]) // buy as much as possible { [self playBuyCommodity]; if (gui_screen == GUI_SCREEN_MARKET) { [self setGuiToMarketScreen]; } else { [self setGuiToMarketInfoScreen]; } } else if (isdocked) { [self playCantBuyCommodity]; } wait_for_key_up = YES; } } } else { wait_for_key_up = NO; } } - (void) handleGameOptionsScreenKeys { MyOpenGLView *gameView = [UNIVERSE gameView]; GuiDisplayGen *gui = [UNIVERSE gui]; GUI_ROW_INIT(gui); [self handleGUIUpDownArrowKeys]; OOGUIRow guiSelectedRow = [gui selectedRow]; BOOL selectKeyPress = ([gameView isDown:13]||[gameView isDown:gvMouseDoubleClick]); if ([gameView isDown:gvMouseDoubleClick]) [gameView clearMouse]; if ((guiSelectedRow == GUI_ROW(GAME,STICKMAPPER)) && selectKeyPress) { selFunctionIdx = 0; [self setGuiToStickMapperScreen: 0 resetCurrentRow: YES]; } if ((guiSelectedRow == GUI_ROW(GAME,KEYMAPPER)) && selectKeyPress) { selFunctionIdx = 0; [self setGuiToKeySettingsScreen]; } #if OO_RESOLUTION_OPTION if (!switching_resolution && guiSelectedRow == GUI_ROW(GAME,DISPLAY) && ([gameView isDown:key_gui_arrow_right] || [gameView isDown:key_gui_arrow_left])) { GameController *controller = [UNIVERSE gameController]; int direction = ([gameView isDown:key_gui_arrow_right]) ? 1 : -1; NSInteger displayModeIndex = [controller indexOfCurrentDisplayMode]; NSArray *modes = [controller displayModes]; if (displayModeIndex == (NSInteger)NSNotFound) { OOLogWARN(@"graphics.mode.notFound", @"couldn't find current fullscreen setting, switching to default."); displayModeIndex = 0; } else { displayModeIndex = displayModeIndex + direction; int count = [modes count]; if (displayModeIndex < 0) displayModeIndex = count - 1; if (displayModeIndex >= count) displayModeIndex = 0; } NSDictionary *mode = [modes objectAtIndex:displayModeIndex]; int modeWidth = [mode oo_intForKey:kOODisplayWidth]; int modeHeight = [mode oo_intForKey:kOODisplayHeight]; int modeRefresh = [mode oo_intForKey:kOODisplayRefreshRate]; [controller setDisplayWidth:modeWidth Height:modeHeight Refresh:modeRefresh]; NSString *displayModeString = [self screenModeStringForWidth:modeWidth height:modeHeight refreshRate:modeRefresh]; [self playChangedOption]; [gui setText:displayModeString forRow:GUI_ROW(GAME,DISPLAY) align:GUI_ALIGN_CENTER]; switching_resolution = YES; #if OOLITE_SDL /* TODO: The gameView for the SDL game currently holds and sets the actual screen resolution (controller just stores it). This probably ought to change. */ [gameView setScreenSize: displayModeIndex]; // changes fullscreen mode immediately #endif } if (switching_resolution && ![gameView isDown:key_gui_arrow_right] && ![gameView isDown:key_gui_arrow_left] && !selectKeyPress) { switching_resolution = NO; } #endif // OO_RESOLUTION_OPTION #if OOLITE_SPEECH_SYNTH if ((guiSelectedRow == GUI_ROW(GAME,SPEECH))&&(([gameView isDown:key_gui_arrow_right])||([gameView isDown:gvArrowKeyLeft]))) { if (!speech_settings_pressed) { if ([gameView isDown:key_gui_arrow_right] && isSpeechOn < OOSPEECHSETTINGS_ALL) { ++isSpeechOn; [self playChangedOption]; speech_settings_pressed = YES; } else if ([gameView isDown:key_gui_arrow_left] && isSpeechOn > OOSPEECHSETTINGS_OFF) { speech_settings_pressed = YES; --isSpeechOn; [self playChangedOption]; } if (speech_settings_pressed) { NSString *message = nil; switch (isSpeechOn) { case OOSPEECHSETTINGS_OFF: message = DESC(@"gameoptions-spoken-messages-no"); break; case OOSPEECHSETTINGS_COMMS: message = DESC(@"gameoptions-spoken-messages-comms"); break; case OOSPEECHSETTINGS_ALL: message = DESC(@"gameoptions-spoken-messages-yes"); break; } [gui setText:message forRow:GUI_ROW(GAME,SPEECH) align:GUI_ALIGN_CENTER]; if (isSpeechOn == OOSPEECHSETTINGS_ALL) { [UNIVERSE stopSpeaking]; [UNIVERSE startSpeakingString:message]; } } } } else { speech_settings_pressed = NO; } #if OOLITE_ESPEAK if (guiSelectedRow == GUI_ROW(GAME,SPEECH_LANGUAGE)) { if ([gameView isDown:key_gui_arrow_right] || [gameView isDown:key_gui_arrow_left]) { if (!speechVoiceSelectKeyPressed || script_time > timeLastKeyPress + KEY_REPEAT_INTERVAL) { [self playChangedOption]; if ([gameView isDown:key_gui_arrow_right]) voice_no = [UNIVERSE nextVoice: voice_no]; else voice_no = [UNIVERSE prevVoice: voice_no]; [UNIVERSE setVoice: voice_no withGenderM:voice_gender_m]; NSString *voiceName = [UNIVERSE voiceName:voice_no]; NSString *message = OOExpandKey(@"gameoptions-voice-name", voiceName); [gui setText:message forRow:GUI_ROW(GAME,SPEECH_LANGUAGE) align:GUI_ALIGN_CENTER]; if (isSpeechOn == OOSPEECHSETTINGS_ALL) { [UNIVERSE stopSpeaking]; [UNIVERSE startSpeakingString:[UNIVERSE voiceName: voice_no]]; } timeLastKeyPress = script_time; } speechVoiceSelectKeyPressed = YES; } else speechVoiceSelectKeyPressed = NO; } if (guiSelectedRow == GUI_ROW(GAME,SPEECH_GENDER)) { if ([gameView isDown:key_gui_arrow_right] || [gameView isDown:key_gui_arrow_left]) { if (!speechGenderSelectKeyPressed) { [self playChangedOption]; BOOL m = [gameView isDown:key_gui_arrow_right]; if (m != voice_gender_m) { voice_gender_m = m; [UNIVERSE setVoice:voice_no withGenderM:voice_gender_m]; NSString *message = [NSString stringWithFormat:DESC(voice_gender_m ? @"gameoptions-voice-M" : @"gameoptions-voice-F")]; [gui setText:message forRow:GUI_ROW(GAME,SPEECH_GENDER) align:GUI_ALIGN_CENTER]; if (isSpeechOn == OOSPEECHSETTINGS_ALL) { [UNIVERSE stopSpeaking]; [UNIVERSE startSpeakingString:[UNIVERSE voiceName: voice_no]]; } } } speechGenderSelectKeyPressed = YES; } else speechGenderSelectKeyPressed = NO; } #endif #endif if ((guiSelectedRow == GUI_ROW(GAME,MUSIC))&&(([gameView isDown:key_gui_arrow_right])||([gameView isDown:gvArrowKeyLeft]))) { if (!musicModeKeyPressed) { OOMusicController *musicController = [OOMusicController sharedController]; int initialMode = [musicController mode]; int mode = initialMode; if ([gameView isDown:key_gui_arrow_right]) mode++; if ([gameView isDown:key_gui_arrow_left]) mode--; [musicController setMode:MAX(mode, 0)]; if ((int)[musicController mode] != initialMode) { [self playChangedOption]; NSString *musicMode = [UNIVERSE descriptionForArrayKey:@"music-mode" index:[[OOMusicController sharedController] mode]]; NSString *message = OOExpandKey(@"gameoptions-music-mode", musicMode); [gui setText:message forRow:GUI_ROW(GAME,MUSIC) align:GUI_ALIGN_CENTER]; } } musicModeKeyPressed = YES; } else musicModeKeyPressed = NO; if ((guiSelectedRow == GUI_ROW(GAME,AUTOSAVE))&&(([gameView isDown:key_gui_arrow_right])||([gameView isDown:key_gui_arrow_left]))) { if ([gameView isDown:key_gui_arrow_right] != [UNIVERSE autoSave]) [self playChangedOption]; [UNIVERSE setAutoSave:[gameView isDown:key_gui_arrow_right]]; if ([UNIVERSE autoSave]) { // if just enabled, we want to autosave immediately [UNIVERSE setAutoSaveNow:YES]; [gui setText:DESC(@"gameoptions-autosave-yes") forRow:GUI_ROW(GAME,AUTOSAVE) align:GUI_ALIGN_CENTER]; } else { [UNIVERSE setAutoSaveNow:NO]; [gui setText:DESC(@"gameoptions-autosave-no") forRow:GUI_ROW(GAME,AUTOSAVE) align:GUI_ALIGN_CENTER]; } } if ((guiSelectedRow == GUI_ROW(GAME,VOLUME)) &&(([gameView isDown:key_gui_arrow_right])||([gameView isDown:key_gui_arrow_left])) &&[OOSound respondsToSelector:@selector(masterVolume)]) { if ((!volumeControlPressed)||(script_time > timeLastKeyPress + KEY_REPEAT_INTERVAL)) { BOOL rightKeyDown = [gameView isDown:key_gui_arrow_right]; BOOL leftKeyDown = [gameView isDown:key_gui_arrow_left]; double volume = 100.0 * [OOSound masterVolume]; int vol = (volume / 5.0 + 0.5); if (rightKeyDown) vol++; if (leftKeyDown) vol--; vol = (int)OOClampInteger(vol, 0, 20); [OOSound setMasterVolume: 0.05 * vol]; [self playChangedOption]; #if OOLITE_ESPEAK espeak_SetParameter(espeakVOLUME, vol * 5, 0); #endif if (vol > 0) { NSString* soundVolumeWordDesc = DESC(@"gameoptions-sound-volume"); NSString* v1_string = @"|||||||||||||||||||||||||"; NSString* v0_string = @"........................."; v1_string = [v1_string substringToIndex:vol]; v0_string = [v0_string substringToIndex:20 - vol]; [gui setText:[NSString stringWithFormat:@"%@%@%@ ", soundVolumeWordDesc, v1_string, v0_string] forRow:GUI_ROW(GAME,VOLUME) align:GUI_ALIGN_CENTER]; } else [gui setText:DESC(@"gameoptions-sound-volume-mute") forRow:GUI_ROW(GAME,VOLUME) align:GUI_ALIGN_CENTER]; timeLastKeyPress = script_time; } volumeControlPressed = YES; } else volumeControlPressed = NO; #if OOLITE_SDL if ((guiSelectedRow == GUI_ROW(GAME,GAMMA)) &&(([gameView isDown:key_gui_arrow_right])||([gameView isDown:key_gui_arrow_left]))) { if (!gammaControlPressed) { BOOL rightKeyDown = [gameView isDown:key_gui_arrow_right]; BOOL leftKeyDown = [gameView isDown:key_gui_arrow_left]; float gamma = [gameView gammaValue]; gamma += (((rightKeyDown && (gamma < 4.0f)) ? 0.2f : 0.0f) - ((leftKeyDown && (gamma > 0.2f)) ? 0.2f : 0.0f)); if (gamma > 3.95f) gamma = 4.0f; if (gamma < 0.25f) gamma = 0.2f; [gameView setGammaValue:gamma]; int gamma5 = gamma * 5; // avoid rounding errors NSString* gammaWordDesc = DESC(@"gameoptions-gamma-value"); NSString* v1_string = @"|||||||||||||||||||||||||"; NSString* v0_string = @"........................."; v1_string = [v1_string substringToIndex:gamma5]; v0_string = [v0_string substringToIndex:20 - gamma5]; [gui setText:[NSString stringWithFormat:@"%@%@%@ (%.1f) ", gammaWordDesc, v1_string, v0_string, gamma] forRow:GUI_ROW(GAME,GAMMA) align:GUI_ALIGN_CENTER]; } gammaControlPressed = YES; } else gammaControlPressed = NO; #endif if ((guiSelectedRow == GUI_ROW(GAME,FOV)) &&(([gameView isDown:key_gui_arrow_right])||([gameView isDown:key_gui_arrow_left]))) { if (!fovControlPressed ||(script_time > timeLastKeyPress + KEY_REPEAT_INTERVAL)) { BOOL rightKeyDown = [gameView isDown:key_gui_arrow_right]; BOOL leftKeyDown = [gameView isDown:key_gui_arrow_left]; float fov = [gameView fov:NO]; float fovStep = (MAX_FOV_DEG - MIN_FOV_DEG) / 20.0f; fov += (((rightKeyDown && (fov < MAX_FOV_DEG)) ? fovStep : 0.0f) - ((leftKeyDown && (fov > MIN_FOV_DEG)) ? fovStep : 0.0f)); if (fov > MAX_FOV_DEG) fov = MAX_FOV_DEG; if (fov < MIN_FOV_DEG) fov = MIN_FOV_DEG; [gameView setFov:fov fromFraction:NO]; fieldOfView = [gameView fov:YES]; int fovTicks = (int)((fov - MIN_FOV_DEG) / fovStep); NSString* fovWordDesc = DESC(@"gameoptions-fov-value"); NSString* v1_string = @"|||||||||||||||||||||||||"; NSString* v0_string = @"........................."; v1_string = [v1_string substringToIndex:fovTicks]; v0_string = [v0_string substringToIndex:20 - fovTicks]; [gui setText:[NSString stringWithFormat:@"%@%@%@ (%d%c) ", fovWordDesc, v1_string, v0_string, (int)fov, 176 /*176 is the degrees symbol ASCII code*/] forRow:GUI_ROW(GAME,FOV) align:GUI_ALIGN_CENTER]; [[NSUserDefaults standardUserDefaults] setFloat:[gameView fov:NO] forKey:@"fov-value"]; timeLastKeyPress = script_time; } fovControlPressed = YES; } else fovControlPressed = NO; if ((guiSelectedRow == GUI_ROW(GAME,WIREFRAMEGRAPHICS))&&(([gameView isDown:key_gui_arrow_right])||([gameView isDown:key_gui_arrow_left]))) { if ([gameView isDown:key_gui_arrow_right] != [UNIVERSE wireframeGraphics]) [self playChangedOption]; [UNIVERSE setWireframeGraphics:[gameView isDown:key_gui_arrow_right]]; if ([UNIVERSE wireframeGraphics]) [gui setText:DESC(@"gameoptions-wireframe-graphics-yes") forRow:GUI_ROW(GAME,WIREFRAMEGRAPHICS) align:GUI_ALIGN_CENTER]; else [gui setText:DESC(@"gameoptions-wireframe-graphics-no") forRow:GUI_ROW(GAME,WIREFRAMEGRAPHICS) align:GUI_ALIGN_CENTER]; } #if !NEW_PLANETS if ((guiSelectedRow == GUI_ROW(GAME,PROCEDURALLYTEXTUREDPLANETS))&&(([gameView isDown:key_gui_arrow_right])||([gameView isDown:key_gui_arrow_left]))) { if ([gameView isDown:key_gui_arrow_right] != [UNIVERSE doProcedurallyTexturedPlanets]) { [UNIVERSE setDoProcedurallyTexturedPlanets:[gameView isDown:key_gui_arrow_right]]; [self playChangedOption]; if ([UNIVERSE planet]) { [UNIVERSE setUpPlanet]; } } if ([UNIVERSE doProcedurallyTexturedPlanets]) [gui setText:DESC(@"gameoptions-procedurally-textured-planets-yes") forRow:GUI_ROW(GAME,PROCEDURALLYTEXTUREDPLANETS) align:GUI_ALIGN_CENTER]; else [gui setText:DESC(@"gameoptions-procedurally-textured-planets-no") forRow:GUI_ROW(GAME,PROCEDURALLYTEXTUREDPLANETS) align:GUI_ALIGN_CENTER]; } #endif if (guiSelectedRow == GUI_ROW(GAME,SHADEREFFECTS) && ([gameView isDown:key_gui_arrow_right] || [gameView isDown:key_gui_arrow_left])) { if (!shaderSelectKeyPressed || (script_time > timeLastKeyPress + KEY_REPEAT_INTERVAL)) { int direction = ([gameView isDown:key_gui_arrow_right]) ? 1 : -1; /* (Getafix - 2015/05/07) Fix bug coincidentally resulting in Graphics Detail value cycling when left arrow is pressed. OOGraphicsDetail is an enum type and as such it will never go negative. The following code adjusts "direction" to avoid illegal detailLevel values. Perhaps a more elegant solution could be set in place, restructuring in Universe.m the logic behing setDetailLevelDirectly and setDetailLevel, not forgetting to consider Graphic Detail assigned from various sources (i.e. menu, user prefs file, javascript, etc.). This is postponed in order not to risk the recently announced plans for v1.82 release. Generally we should decide whether the menu values should cycle or not and apply it for all menu entries. */ if ((([UNIVERSE detailLevel] == DETAIL_LEVEL_MINIMUM) && (direction == -1)) || (([UNIVERSE detailLevel] == DETAIL_LEVEL_MAXIMUM) && (direction == 1))) direction = 0; OOGraphicsDetail detailLevel = [UNIVERSE detailLevel] + direction; [UNIVERSE setDetailLevel:detailLevel]; detailLevel = [UNIVERSE detailLevel]; NSString *shaderEffectsOptionsString = OOExpand(@"gameoptions-detaillevel-[detailLevel]", detailLevel); [gui setText:OOExpandKey(shaderEffectsOptionsString) forRow:GUI_ROW(GAME,SHADEREFFECTS) align:GUI_ALIGN_CENTER]; [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,SHADEREFFECTS)]; timeLastKeyPress = script_time; } shaderSelectKeyPressed = YES; } else shaderSelectKeyPressed = NO; #if OOLITE_SDL if ((guiSelectedRow == GUI_ROW(GAME,DISPLAYSTYLE)) && selectKeyPress) { [gameView toggleScreenMode]; // redraw GUI [self setGuiToGameOptionsScreen]; } #endif if ((guiSelectedRow == GUI_ROW(GAME,DOCKINGCLEARANCE))&&(([gameView isDown:key_gui_arrow_right])||([gameView isDown:key_gui_arrow_left]))) { if ([gameView isDown:key_gui_arrow_right] != [UNIVERSE dockingClearanceProtocolActive]) [self playChangedOption]; [UNIVERSE setDockingClearanceProtocolActive:[gameView isDown:key_gui_arrow_right]]; if ([UNIVERSE dockingClearanceProtocolActive]) [gui setText:DESC(@"gameoptions-docking-clearance-yes") forRow:GUI_ROW(GAME,DOCKINGCLEARANCE) align:GUI_ALIGN_CENTER]; else [gui setText:DESC(@"gameoptions-docking-clearance-no") forRow:GUI_ROW(GAME,DOCKINGCLEARANCE) align:GUI_ALIGN_CENTER]; } if ((guiSelectedRow == GUI_ROW(GAME,BACK)) && selectKeyPress) { [gameView clearKeys]; [self setGuiToLoadSaveScreen]; } } - (void) pollCustomViewControls { if ([[UNIVERSE gameView] isDown:key_custom_view]) { if (!customView_pressed && [_customViews count] != 0 && gui_screen != GUI_SCREEN_LONG_RANGE_CHART) { if ([UNIVERSE viewDirection] == VIEW_CUSTOM) // already in custom view mode { // rotate the custom views _customViewIndex = (_customViewIndex + 1) % [_customViews count]; } [self setCustomViewDataFromDictionary:[_customViews oo_dictionaryAtIndex:_customViewIndex] withScaling:YES]; [self switchToThisView:VIEW_CUSTOM andProcessWeaponFacing:NO]; // weapon facing must not change, we just want an external view } customView_pressed = YES; } else customView_pressed = NO; } - (void) pollViewControls { if(!pollControls) return; MyOpenGLView *gameView = [UNIVERSE gameView]; OOJoystickManager *stickHandler = [OOJoystickManager sharedStickHandler]; NSPoint virtualView = NSZeroPoint; double view_threshold = 0.5; if ([stickHandler joystickCount]) { virtualView = [stickHandler viewAxis]; if (virtualView.y == STICK_AXISUNASSIGNED) virtualView.y = 0.0; if (virtualView.x == STICK_AXISUNASSIGNED) virtualView.x = 0.0; if (fabs(virtualView.y) >= fabs(virtualView.x)) virtualView.x = 0.0; // forward/aft takes precedence else virtualView.y = 0.0; } const BOOL *joyButtonState = [stickHandler getAllButtonStates]; // view keys if (([gameView isDown:gvFunctionKey1] || [gameView isDown:key_view_forward]) || (virtualView.y < -view_threshold)||joyButtonState[BUTTON_VIEWFORWARD] || ((([gameView isDown:key_hyperspace] && gui_screen != GUI_SCREEN_LONG_RANGE_CHART) || joyButtonState[BUTTON_HYPERDRIVE]) && [UNIVERSE displayGUI])) { [self switchToThisView:VIEW_FORWARD]; } if (([gameView isDown:gvFunctionKey2] || [gameView isDown:key_view_aft])||(virtualView.y > view_threshold)||joyButtonState[BUTTON_VIEWAFT]) { [self switchToThisView:VIEW_AFT]; } if (([gameView isDown:gvFunctionKey3] || [gameView isDown:key_view_port])||(virtualView.x < -view_threshold)||joyButtonState[BUTTON_VIEWPORT]) { [self switchToThisView:VIEW_PORT]; } if (([gameView isDown:gvFunctionKey4] || [gameView isDown:key_view_starboard])||(virtualView.x > view_threshold)||joyButtonState[BUTTON_VIEWSTARBOARD]) { [self switchToThisView:VIEW_STARBOARD]; } [self pollCustomViewControls]; // Zoom scanner 'z' if (([gameView isDown:key_scanner_zoom] && ([gameView allowingStringInput] == gvStringInputNo)) || joyButtonState[BUTTON_SCANNERZOOM]) // look for the 'z' key { if (!scanner_zoom_rate) { if ([hud scannerZoom] < 5.0) { if (([hud scannerZoom] > 1.0)||(!zoom_pressed)) scanner_zoom_rate = SCANNER_ZOOM_RATE_UP; } else { if (!zoom_pressed) // must release and re-press zoom to zoom back down.. scanner_zoom_rate = SCANNER_ZOOM_RATE_DOWN; } } zoom_pressed = YES; } else zoom_pressed = NO; // Unzoom scanner 'Z' if (([gameView isDown:key_scanner_unzoom] && ([gameView allowingStringInput] == gvStringInputNo)) || joyButtonState[BUTTON_SCANNERUNZOOM]) // look for the 'Z' key { if ((!scanner_zoom_rate)&&([hud scannerZoom] > 1.0)) scanner_zoom_rate = SCANNER_ZOOM_RATE_DOWN; } if (EXPECT([[self hud] isCompassActive])) // only switch compass modes if there is a compass { // Compass mode '|' if ([gameView isDown:key_prev_compass_mode]) // look for the '|' key { if ((!prev_compass_mode_pressed)&&(compassMode != COMPASS_MODE_BASIC)) [self setPrevCompassMode]; prev_compass_mode_pressed = YES; } else { prev_compass_mode_pressed = NO; } // Compass mode '\' if ([gameView isDown:key_next_compass_mode]) // look for the '\' key { if ((!next_compass_mode_pressed)&&(compassMode != COMPASS_MODE_BASIC)) [self setNextCompassMode]; next_compass_mode_pressed = YES; } else { next_compass_mode_pressed = NO; } } // ';' // Cycle active MFD if ([gameView isDown:key_cycle_mfd]) { if (!cycleMFD_pressed) { [self cycleMultiFunctionDisplay:activeMFD]; } cycleMFD_pressed = YES; } else { cycleMFD_pressed = NO; } // ':' // Select next MFD if ([gameView isDown:key_switch_mfd]) { if ([[self hud] mfdCount] > 1) { if (!switchMFD_pressed) { [self selectNextMultiFunctionDisplay]; } } switchMFD_pressed = YES; } else { switchMFD_pressed = NO; } // show comms log '`' if ([gameView isDown:key_comms_log]) { [UNIVERSE showCommsLog: 1.5]; [hud refreshLastTransmitter]; } } - (void) pollFlightArrowKeyControls:(double)delta_t { MyOpenGLView *gameView = [UNIVERSE gameView]; OOJoystickManager *stickHandler = [OOJoystickManager sharedStickHandler]; NSUInteger numSticks = [stickHandler joystickCount]; NSPoint virtualStick = NSZeroPoint; double reqYaw = 0.0; /* DJS: Handle inputs on the joy roll/pitch axis. Mouse control on takes precidence over joysticks. We have to assume the player has a reason for switching mouse control on if they have a joystick - let them do it. */ if (mouse_control_on) { virtualStick=[gameView virtualJoystickPosition]; double sensitivity = 2.0; virtualStick.x *= sensitivity; virtualStick.y *= sensitivity; reqYaw = virtualStick.x; } else if (numSticks > 0) { virtualStick = [stickHandler rollPitchAxis]; // handle roll separately (fix for BUG #17490) if(virtualStick.x == STICK_AXISUNASSIGNED) { // Not assigned - set to zero. virtualStick.x=0; } else if(virtualStick.x != 0) { // cancel keyboard override, stick has been waggled keyboardRollOverride=NO; } // handle pitch separately (fix for BUG #17490) if(virtualStick.y == STICK_AXISUNASSIGNED) { // Not assigned - set to zero. virtualStick.y=0; } else if(virtualStick.y != 0) { // cancel keyboard override, stick has been waggled keyboardPitchOverride=NO; } // handle yaw separately from pitch/roll reqYaw = [stickHandler getAxisState: AXIS_YAW]; if(reqYaw == STICK_AXISUNASSIGNED) { // Not assigned or deadzoned - set to zero. reqYaw=0; } else if(reqYaw != 0) { // cancel keyboard override, stick has been waggled keyboardYawOverride=NO; } } double roll_dampner = ROLL_DAMPING_FACTOR * delta_t; double pitch_dampner = PITCH_DAMPING_FACTOR * delta_t; double yaw_dampner = YAW_DAMPING_FACTOR * delta_t; BOOL isCtrlDown = [gameView isCtrlDown]; double flightArrowKeyPrecisionFactor = [[NSUserDefaults standardUserDefaults] oo_doubleForKey:@"flight-arrow-key-precision-factor" defaultValue:0.5]; if (flightArrowKeyPrecisionFactor < 0.05) flightArrowKeyPrecisionFactor = 0.05; if (flightArrowKeyPrecisionFactor > 1.0) flightArrowKeyPrecisionFactor = 1.0; rolling = NO; // if we have yaw on the mouse x-axis, then allow using the keyboard roll keys if (!mouse_control_on || (mouse_control_on && mouse_x_axis_map_to_yaw)) { if ([gameView isDown:key_roll_left] && [gameView isDown:key_roll_right]) { keyboardRollOverride = YES; flightRoll = 0.0; } else if ([gameView isDown:key_roll_left]) { keyboardRollOverride=YES; if (flightRoll > 0.0) flightRoll = 0.0; [self decrease_flight_roll:isCtrlDown ? flightArrowKeyPrecisionFactor*roll_dampner*roll_delta : delta_t*roll_delta]; rolling = YES; } else if ([gameView isDown:key_roll_right]) { keyboardRollOverride=YES; if (flightRoll < 0.0) flightRoll = 0.0; [self increase_flight_roll:isCtrlDown ? flightArrowKeyPrecisionFactor*roll_dampner*roll_delta : delta_t*roll_delta]; rolling = YES; } } if(((mouse_control_on && !mouse_x_axis_map_to_yaw) || numSticks) && !keyboardRollOverride) { stick_roll = max_flight_roll * virtualStick.x; if (flightRoll < stick_roll) { [self increase_flight_roll:delta_t*roll_delta]; if (flightRoll > stick_roll) flightRoll = stick_roll; } if (flightRoll > stick_roll) { [self decrease_flight_roll:delta_t*roll_delta]; if (flightRoll < stick_roll) flightRoll = stick_roll; } rolling = (fabs(virtualStick.x) > 0.0); } if (!rolling) { if (flightRoll > 0.0) { if (flightRoll > roll_dampner) [self decrease_flight_roll:roll_dampner]; else flightRoll = 0.0; } if (flightRoll < 0.0) { if (flightRoll < -roll_dampner) [self increase_flight_roll:roll_dampner]; else flightRoll = 0.0; } } pitching = NO; // we don't care about pitch keyboard overrides when mouse control is on, only when using joystick if (!mouse_control_on) { if ([gameView isDown:key_pitch_back] && [gameView isDown:key_pitch_forward]) { keyboardPitchOverride=YES; flightPitch = 0.0; } else if ([gameView isDown:key_pitch_back]) { keyboardPitchOverride=YES; if (flightPitch < 0.0) flightPitch = 0.0; [self increase_flight_pitch:isCtrlDown ? flightArrowKeyPrecisionFactor*pitch_dampner*pitch_delta : delta_t*pitch_delta]; pitching = YES; } else if ([gameView isDown:key_pitch_forward]) { keyboardPitchOverride=YES; if (flightPitch > 0.0) flightPitch = 0.0; [self decrease_flight_pitch:isCtrlDown ? flightArrowKeyPrecisionFactor*pitch_dampner*pitch_delta : delta_t*pitch_delta]; pitching = YES; } } if(mouse_control_on || (numSticks && !keyboardPitchOverride)) { stick_pitch = max_flight_pitch * virtualStick.y; if (flightPitch < stick_pitch) { [self increase_flight_pitch:delta_t*pitch_delta]; if (flightPitch > stick_pitch) flightPitch = stick_pitch; } if (flightPitch > stick_pitch) { [self decrease_flight_pitch:delta_t*pitch_delta]; if (flightPitch < stick_pitch) flightPitch = stick_pitch; } pitching = (fabs(virtualStick.y) > 0.0); } if (!pitching) { if (flightPitch > 0.0) { if (flightPitch > pitch_dampner) [self decrease_flight_pitch:pitch_dampner]; else flightPitch = 0.0; } if (flightPitch < 0.0) { if (flightPitch < -pitch_dampner) [self increase_flight_pitch:pitch_dampner]; else flightPitch = 0.0; } } yawing = NO; // if we have roll on the mouse x-axis, then allow using the keyboard yaw keys if (!mouse_control_on || (mouse_control_on && !mouse_x_axis_map_to_yaw)) { if ([gameView isDown:key_yaw_left] && [gameView isDown:key_yaw_right]) { keyboardYawOverride=YES; flightYaw = 0.0; } else if ([gameView isDown:key_yaw_left]) { keyboardYawOverride=YES; if (flightYaw < 0.0) flightYaw = 0.0; [self increase_flight_yaw:isCtrlDown ? flightArrowKeyPrecisionFactor*yaw_dampner*yaw_delta : delta_t*yaw_delta]; yawing = YES; } else if ([gameView isDown:key_yaw_right]) { keyboardYawOverride=YES; if (flightYaw > 0.0) flightYaw = 0.0; [self decrease_flight_yaw:isCtrlDown ? flightArrowKeyPrecisionFactor*yaw_dampner*yaw_delta : delta_t*yaw_delta]; yawing = YES; } } if(((mouse_control_on && mouse_x_axis_map_to_yaw) || numSticks) && !keyboardYawOverride) { // I think yaw is handled backwards in the code, // which is why the negative sign is here. stick_yaw = max_flight_yaw * (-reqYaw); if (flightYaw < stick_yaw) { [self increase_flight_yaw:delta_t*yaw_delta]; if (flightYaw > stick_yaw) flightYaw = stick_yaw; } if (flightYaw > stick_yaw) { [self decrease_flight_yaw:delta_t*yaw_delta]; if (flightYaw < stick_yaw) flightYaw = stick_yaw; } yawing = (fabs(reqYaw) > 0.0); } if (!yawing) { if (flightYaw > 0.0) { if (flightYaw > yaw_dampner) [self decrease_flight_yaw:yaw_dampner]; else flightYaw = 0.0; } if (flightYaw < 0.0) { if (flightYaw < -yaw_dampner) [self increase_flight_yaw:yaw_dampner]; else flightYaw = 0.0; } } } - (void) pollGuiScreenControls { [self pollGuiScreenControlsWithFKeyAlias:YES]; } - (void) pollGuiScreenControlsWithFKeyAlias:(BOOL)fKeyAlias { if(!pollControls && fKeyAlias) // Still OK to run, if we don't use number keys. return; GuiDisplayGen *gui = [UNIVERSE gui]; MyOpenGLView *gameView = [UNIVERSE gameView]; BOOL docked_okay = ([self status] == STATUS_DOCKED); // text displays if ([gameView isDown:gvFunctionKey5] || (fKeyAlias && [gameView isDown:key_gui_screen_status])) { if (!switching_status_screens) { switching_status_screens = YES; if (gui_screen == GUI_SCREEN_STATUS) { [self noteGUIWillChangeTo:GUI_SCREEN_MANIFEST]; [self setGuiToManifestScreen]; } else [self setGuiToStatusScreen]; [self checkScript]; } } else { switching_status_screens = NO; } if (([gameView isDown:gvFunctionKey6])||(fKeyAlias && [gameView isDown:key_gui_chart_screens])) { mouse_left_down = NO; [gameView clearMouse]; if (!switching_chart_screens) { switching_chart_screens = YES; // handles http://aegidian.org/bb/viewtopic.php?p=233189#p233189 if (EXPECT_NOT([self status] == STATUS_WITCHSPACE_COUNTDOWN && gui_screen == GUI_SCREEN_SHORT_RANGE_CHART)) { // don't switch to LRC if countdown in progress switching_chart_screens = NO; } else if (gui_screen == GUI_SCREEN_SHORT_RANGE_CHART || (gui_screen == GUI_SCREEN_SYSTEM_DATA && showingLongRangeChart)) { if (target_chart_zoom != CHART_MAX_ZOOM) { saved_chart_zoom = target_chart_zoom; } target_chart_zoom = CHART_MAX_ZOOM; [self setGuiToLongRangeChartScreen]; } else { if (target_chart_zoom == CHART_MAX_ZOOM) { target_chart_zoom = saved_chart_zoom; } //target_chart_centre = cursor_coordinates = [[UNIVERSE systemManager] getCoordinatesForSystem:target_system_id inGalaxy:galaxy_number]; [self setGuiToShortRangeChartScreen]; } } } else { switching_chart_screens = NO; } if (([gameView isDown:gvFunctionKey7])||(fKeyAlias &&[gameView isDown:key_gui_system_data])) { if (gui_screen != GUI_SCREEN_SYSTEM_DATA) { showingLongRangeChart = (gui_screen == GUI_SCREEN_LONG_RANGE_CHART); [self noteGUIWillChangeTo:GUI_SCREEN_SYSTEM_DATA]; [self setGuiToSystemDataScreen]; } } if (([gameView isDown:gvFunctionKey8])||(fKeyAlias && [gameView isDown:key_gui_market])) { if (gui_screen != GUI_SCREEN_MARKET) { [gameView clearKeys]; [self noteGUIWillChangeTo:GUI_SCREEN_MARKET]; [self setGuiToMarketScreen]; } else { [gameView clearKeys]; [self noteGUIWillChangeTo:GUI_SCREEN_MARKETINFO]; [self setGuiToMarketInfoScreen]; } } if (docked_okay) { if ((([gameView isDown:gvFunctionKey2])||(fKeyAlias && [gameView isDown:key_view_aft]))&&(gui_screen != GUI_SCREEN_OPTIONS)) { [gameView clearKeys]; [self setGuiToLoadSaveScreen]; } if (([gameView isDown:gvFunctionKey3])||(fKeyAlias && [gameView isDown:key_view_port])) { if (!switching_equipship_screens) { if ([self dockedStation] == nil) [self setDockedAtMainStation]; OOGUIScreenID oldScreen = gui_screen; if ((gui_screen == GUI_SCREEN_EQUIP_SHIP) && [[self dockedStation] hasShipyard]) { [gameView clearKeys]; [self noteGUIWillChangeTo:GUI_SCREEN_SHIPYARD]; [self setGuiToShipyardScreen:0]; [gui setSelectedRow:GUI_ROW_SHIPYARD_START]; [self showShipyardInfoForSelection]; } else { [gameView clearKeys]; [self noteGUIWillChangeTo:GUI_SCREEN_EQUIP_SHIP]; [self setGuiToEquipShipScreen:0]; [gui setSelectedRow:GUI_ROW_EQUIPMENT_START]; } [self noteGUIDidChangeFrom:oldScreen to:gui_screen]; } switching_equipship_screens = YES; } else { switching_equipship_screens = NO; } if (([gameView isDown:gvFunctionKey4])||(fKeyAlias && [gameView isDown:key_view_starboard])) { [self setGuiToInterfacesScreen:0]; [gui setSelectedRow:GUI_ROW_INTERFACES_START]; } } } - (void) pollGameOverControls:(double)delta_t { MyOpenGLView *gameView = [UNIVERSE gameView]; if ([gameView isDown:32]) // look for the spacebar { if (!spacePressed) { [UNIVERSE displayMessage:@"" forCount:1.0]; shot_time = INITIAL_SHOT_TIME; // forces immediate restart } spacePressed = YES; } else spacePressed = NO; } static BOOL toggling_music; static BOOL playing_music; static BOOL autopilot_pause; - (void) pollAutopilotControls:(double)delta_t { // controls polled while the autopilot is active MyOpenGLView *gameView = [UNIVERSE gameView]; if (![[UNIVERSE gameController] isGamePaused]) { // view keys [self pollViewControls]; // text displays [self pollGuiScreenControls]; if ([UNIVERSE displayGUI]) [self pollGuiArrowKeyControls:delta_t]; const BOOL *joyButtonState = [[OOJoystickManager sharedStickHandler] getAllButtonStates]; if ([gameView isDown:key_autopilot] || joyButtonState[BUTTON_DOCKCPU] || [gameView isDown:key_autodock] || joyButtonState[BUTTON_DOCKCPUFAST]) // look for the 'c' and 'C' key { if ([self hasDockingComputer] && !autopilot_key_pressed && !fast_autopilot_key_pressed) { [self disengageAutopilot]; [UNIVERSE addMessage:DESC(@"autopilot-off") forCount:4.5]; } autopilot_key_pressed = YES; if ([gameView isDown:key_autodock] || joyButtonState[BUTTON_DOCKCPUFAST]) { fast_autopilot_key_pressed = YES; } } else { autopilot_key_pressed = NO; fast_autopilot_key_pressed = NO; } if (([gameView isDown:key_docking_music])) // look for the 's' key { if (!toggling_music) { [[OOMusicController sharedController] toggleDockingMusic]; } toggling_music = YES; } else { toggling_music = NO; } // look for the pause game, 'p' key if ([gameView isDown:key_pausebutton] && gui_screen != GUI_SCREEN_SHORT_RANGE_CHART && gui_screen != GUI_SCREEN_MISSION) { if (!autopilot_pause) { playing_music = [[OOMusicController sharedController] isPlaying]; if (playing_music) [[OOMusicController sharedController] toggleDockingMusic]; // normal flight controls can handle the rest. pause_pressed = NO; // pause button flag must be NO for pollflightControls to react! [self pollFlightControls:delta_t]; } autopilot_pause = YES; } else { autopilot_pause = NO; } } else { // paused if ([gameView isDown:key_pausebutton]) { if (!autopilot_pause) { if (playing_music) [[OOMusicController sharedController] toggleDockingMusic]; } autopilot_pause = YES; } else { autopilot_pause = NO; } // let the normal flight controls handle paused commands. [self pollFlightControls:delta_t]; } } - (void) pollDockedControls:(double)delta_t { MyOpenGLView *gameView = [UNIVERSE gameView]; GameController *gameController = [UNIVERSE gameController]; NSString *exceptionContext = @"setup"; @try { // Pause game, 'p' key exceptionContext = @"pause key"; if ([gameView isDown:key_pausebutton] && (gui_screen != GUI_SCREEN_LONG_RANGE_CHART && gui_screen != GUI_SCREEN_MISSION && gui_screen != GUI_SCREEN_REPORT && gui_screen != GUI_SCREEN_SAVE) ) { if (!pause_pressed) { if ([gameController isGamePaused]) { script_time = saved_script_time; [gameView allowStringInput:NO]; if ([UNIVERSE pauseMessageVisible]) { [UNIVERSE clearPreviousMessage]; // remove the 'paused' message. } [[UNIVERSE gui] setForegroundTextureKey:@"docked_overlay"]; [gameController setGamePaused:NO]; } else { saved_script_time = script_time; [[UNIVERSE messageGUI] clear]; [UNIVERSE pauseGame]; // 'paused' handler } } pause_pressed = YES; } else { pause_pressed = NO; } if ([gameController isGamePaused]) return; if(pollControls) { exceptionContext = @"undock"; if ([gameView isDown:gvFunctionKey1] || [gameView isDown:key_view_forward]) // look for the f1 key { if (EXPECT(gui_screen != GUI_SCREEN_MISSION || _missionAllowInterrupt)) { [self handleUndockControl]; } } } // text displays // mission screens exceptionContext = @"GUI keys"; if (gui_screen == GUI_SCREEN_MISSION) { [self pollDemoControls: delta_t]; // don't switch away from mission screens } else { if (gui_screen != GUI_SCREEN_REPORT)[self pollGuiScreenControls]; // don't switch away from report screens } [self pollGuiArrowKeyControls:delta_t]; } @catch (NSException *exception) { OOLog(kOOLogException, @"***** Exception in pollDockedControls [%@]: %@ : %@", exceptionContext, [exception name], [exception reason]); } } - (void) handleUndockControl { // FIXME: should this not be in leaveDock:? (Note: leaveDock: is also called from script method launchFromStation and -[StationEntity becomeExplosion]) -- Ahruman 20080308 [UNIVERSE setUpUniverseFromStation]; // player pre-launch if ([self dockedStation] == nil) [self setDockedAtMainStation]; StationEntity *dockedStation = [self dockedStation]; if (dockedStation == [UNIVERSE station] && [UNIVERSE autoSaveNow] && !([[UNIVERSE sun] goneNova] || [[UNIVERSE sun] willGoNova])) { [self autosavePlayer]; } [self launchFromStation]; } - (void) pollDemoControls:(double)delta_t { MyOpenGLView *gameView = [UNIVERSE gameView]; GuiDisplayGen *gui = [UNIVERSE gui]; NSUInteger end_row = 21; OOOXZManager *oxzmanager = [OOOXZManager sharedManager]; switch (gui_screen) { case GUI_SCREEN_INTRO1: [self handleGUIUpDownArrowKeys]; int row_zero = 21; if (!selectPressed) { if (!disc_operation_in_progress) { if (([gameView isDown:gvMouseDoubleClick] || [gameView isDown:13]) && [gui selectedRow] == 2+row_zero) { disc_operation_in_progress = YES; [UNIVERSE removeDemoShips]; [gui clearBackground]; if (![self loadPlayer]) { [self setGuiToIntroFirstGo:YES]; } break; } } if (([gameView isDown:gvMouseDoubleClick] || [gameView isDown:13]) && [gui selectedRow] == 1+row_zero) { missionTextRow = 0; [self setGuiToScenarioScreen:0]; } else if (([gameView isDown:gvMouseDoubleClick] || [gameView isDown:13]) && [gui selectedRow] == 3+row_zero) { [self setGuiToIntroFirstGo:NO]; } else if (([gameView isDown:gvMouseDoubleClick] || [gameView isDown:13]) && [gui selectedRow] == 4+row_zero) { [self setGuiToKeySettingsScreen]; } else if (([gameView isDown:gvMouseDoubleClick] || [gameView isDown:13]) && [gui selectedRow] == 5+row_zero) { [self setGuiToOXZManager]; } else if (([gameView isDown:gvMouseDoubleClick] || [gameView isDown:13]) && [gui selectedRow] == 6+row_zero) { [[UNIVERSE gameController] exitAppWithContext:@"Exit Game selected on start screen"]; } else { disc_operation_in_progress = NO; } } selectPressed = [gameView isDown:13]; if ([gameView isDown:gvMouseDoubleClick]) { [gameView clearMouse]; } break; case GUI_SCREEN_KEYBOARD: if ([gameView isDown:' ']) // '' { [self setGuiToIntroFirstGo:YES]; } break; case GUI_SCREEN_SHIPLIBRARY: if ([gameView isDown:' ']) // '' { // viewed from start screen, return to it [self setGuiToIntroFirstGo:YES]; } if ([gameView isDown:key_gui_arrow_up]) // '<--' { if (!upDownKeyPressed) [UNIVERSE selectIntro2Previous]; } if ([gameView isDown:key_gui_arrow_down]) // '-->' { if (!upDownKeyPressed) [UNIVERSE selectIntro2Next]; } upDownKeyPressed = (([gameView isDown:key_gui_arrow_up])||([gameView isDown:key_gui_arrow_down])); if ([gameView isDown:key_gui_arrow_left]) // '<--' { if (!leftRightKeyPressed) [UNIVERSE selectIntro2PreviousCategory]; } if ([gameView isDown:key_gui_arrow_right]) // '-->' { if (!leftRightKeyPressed) [UNIVERSE selectIntro2NextCategory]; } leftRightKeyPressed = (([gameView isDown:key_gui_arrow_left])||([gameView isDown:key_gui_arrow_right])); break; case GUI_SCREEN_NEWGAME: if ([self handleGUIUpDownArrowKeys]) { [self showScenarioDetails]; } if (!selectPressed) { if ([gameView isDown:13] || [gameView isDown:gvMouseDoubleClick]) // enter { if (![self startScenario]) { [UNIVERSE removeDemoShips]; [self setGuiToIntroFirstGo:YES]; } } } selectPressed = [gameView isDown:13]; if ([gameView isDown:gvMouseDoubleClick] || [gameView isDown:gvMouseLeftButton]) { [gameView clearMouse]; } break; case GUI_SCREEN_OXZMANAGER: // release locks on music on this screen [[OOMusicController sharedController] stopThemeMusic]; if (EXPECT(![oxzmanager isRestarting])) { if ([oxzmanager isAcceptingGUIInput]) { if ([oxzmanager isAcceptingTextInput]) { [gameView setStringInput: gvStringInputAll]; [oxzmanager refreshTextInput:[gameView typedString]]; } else { [gameView allowStringInput: NO]; } if ([self handleGUIUpDownArrowKeys]) { // only has an effect on install/remove selection screens [oxzmanager showOptionsUpdate]; } if ([gameView isDown:key_gui_arrow_left]) { if ((!leftRightKeyPressed)) { [oxzmanager processOptionsPrev]; } } else if ([gameView isDown:key_gui_arrow_right]) { if ((!leftRightKeyPressed)) { [oxzmanager processOptionsNext]; } } leftRightKeyPressed = [gameView isDown:key_gui_arrow_right]|[gameView isDown:key_gui_arrow_left]; if (!selectPressed) { if ([gameView isDown:13] || [gameView isDown:gvMouseDoubleClick]) // enter { if ([oxzmanager isAcceptingTextInput]) { [oxzmanager processTextInput:[gameView typedString]]; } else { [oxzmanager processSelection]; } } } selectPressed = [gameView isDown:13]; if ([gameView isDown:gvMouseDoubleClick] || [gameView isDown:gvMouseLeftButton]) { [gameView clearMouse]; } } // endif isAcceptingGUIInput if ([gameView isDown:key_oxzmanager_setfilter] || [gameView isDown:key_oxzmanager_showinfo] || [gameView isDown:key_oxzmanager_extract]) { if (!oxz_manager_pressed) { oxz_manager_pressed = YES; if ([gameView isDown:key_oxzmanager_setfilter]) { [oxzmanager processFilterKey]; } else if ([gameView isDown:key_oxzmanager_showinfo]) { [oxzmanager processShowInfoKey]; } else if ([gameView isDown:key_oxzmanager_extract]) { [oxzmanager processExtractKey]; } } } else { oxz_manager_pressed = NO; } } break; case GUI_SCREEN_MISSION: if ([[self hud] allowBigGui]) { end_row = 27; } if (_missionTextEntry) { [self refreshMissionScreenTextEntry]; if ([gameView isDown:13] || [gameView isDown:gvMouseDoubleClick]) // '' or double click { [self setMissionChoice:[gameView typedString]]; [[OOMusicController sharedController] stopMissionMusic]; [self playDismissedMissionScreen]; [self handleMissionCallback]; [self checkScript]; selectPressed = YES; } else { selectPressed = NO; [self pollMissionInterruptControls]; } } else if ([[gui keyForRow:end_row] isEqual:@"spacebar"]) { if ([gameView isDown:32]) // '' { if (!spacePressed) { [[OOMusicController sharedController] stopMissionMusic]; [self handleMissionCallback]; } spacePressed = YES; } else { spacePressed = NO; [self pollMissionInterruptControls]; } } else { [self handleGUIUpDownArrowKeys]; if ([gameView isDown:13] || [gameView isDown:gvMouseDoubleClick]) // '' or double click { if ([gameView isDown:gvMouseDoubleClick]) { selectPressed = NO; [gameView clearMouse]; } if (!selectPressed) { [self setMissionChoice:[gui selectedRowKey]]; [[OOMusicController sharedController] stopMissionMusic]; [self playDismissedMissionScreen]; [self handleMissionCallback]; [self checkScript]; } selectPressed = YES; } else { selectPressed = NO; [self pollMissionInterruptControls]; } } break; #if OO_USE_CUSTOM_LOAD_SAVE // DJS: Farm off load/save screen options to LoadSave.m case GUI_SCREEN_LOAD: { NSString *commanderFile = [self commanderSelector]; if(commanderFile) { // also release the demo ship here (see showShipyardModel and noteGUIDidChangeFrom) [demoShip release]; demoShip = nil; [self loadPlayerFromFile:commanderFile asNew:NO]; } break; } #endif default: break; } } - (void) pollMissionInterruptControls { if (_missionAllowInterrupt) { [self pollGuiScreenControls]; if (gui_screen != GUI_SCREEN_MISSION) { if (gui_screen != GUI_SCREEN_SYSTEM_DATA) { [UNIVERSE removeDemoShips]; } [self endMissionScreenAndNoteOpportunity]; } } } - (void) handleMissionCallback { [UNIVERSE removeDemoShips]; [[UNIVERSE gui] clearBackground]; [self setGuiToMissionEndScreen]; // need this to find out if we call a new mission screen inside callback. if ([self status] != STATUS_DOCKED) [self switchToThisView:VIEW_FORWARD]; if (_missionWithCallback) { [self doMissionCallback]; } if ([self status] != STATUS_DOCKED) // did we launch inside callback? / are we in flight? { // TODO: This is no longer doing anything because of an 'isDocked' check inside the function. ***** Probably remove it for 1.76 [self doWorldEventUntilMissionScreen:OOJSID("missionScreenEnded")]; // no opportunity events. } else { if (gui_screen != GUI_SCREEN_MISSION) // did we call a new mission screen inside callback? { // note that this might not be the same end screen as last time... [self setGuiToMissionEndScreen]; // if not, update status screen with callback changes, if any. [self endMissionScreenAndNoteOpportunity]; // missionScreenEnded, plus opportunity events. } } } - (void) setGuiToMissionEndScreen { MyOpenGLView *gameView = [UNIVERSE gameView]; [gameView clearKeys]; if ([self status] != STATUS_DOCKED) { // this setting is only applied when not docked [self setGuiToStatusScreen]; return; } switch (_missionExitScreen) { case GUI_SCREEN_MANIFEST: [self noteGUIWillChangeTo:GUI_SCREEN_MANIFEST]; [self setGuiToManifestScreen]; break; case GUI_SCREEN_EQUIP_SHIP: [self noteGUIWillChangeTo:GUI_SCREEN_EQUIP_SHIP]; [self setGuiToEquipShipScreen:0]; break; case GUI_SCREEN_SHIPYARD: if ([[self dockedStation] hasShipyard]) { [self noteGUIWillChangeTo:GUI_SCREEN_SHIPYARD]; [self setGuiToShipyardScreen:0]; [[UNIVERSE gui] setSelectedRow:GUI_ROW_SHIPYARD_START]; [self showShipyardInfoForSelection]; } else { // that doesn't work here [self setGuiToStatusScreen]; } break; case GUI_SCREEN_LONG_RANGE_CHART: [self setGuiToLongRangeChartScreen]; break; case GUI_SCREEN_SHORT_RANGE_CHART: [self setGuiToShortRangeChartScreen]; break; case GUI_SCREEN_SYSTEM_DATA: [self noteGUIWillChangeTo:GUI_SCREEN_SYSTEM_DATA]; [self setGuiToSystemDataScreen]; break; case GUI_SCREEN_MARKET: [self noteGUIWillChangeTo:GUI_SCREEN_MARKET]; [self setGuiToMarketScreen]; break; case GUI_SCREEN_MARKETINFO: [self noteGUIWillChangeTo:GUI_SCREEN_MARKETINFO]; [self setGuiToMarketInfoScreen]; break; case GUI_SCREEN_INTERFACES: [self setGuiToInterfacesScreen:0]; break; case GUI_SCREEN_STATUS: default: // invalid screen specifications [self setGuiToStatusScreen]; } } - (void) switchToThisView:(OOViewID)viewDirection { [self switchToThisView:viewDirection andProcessWeaponFacing:YES]; } - (void) switchToThisView:(OOViewID)viewDirection andProcessWeaponFacing:(BOOL)processWeaponFacing { [self switchToThisView:viewDirection fromView:[UNIVERSE viewDirection] andProcessWeaponFacing:processWeaponFacing justNotify:NO]; } - (void) switchToThisView:(OOViewID)viewDirection fromView:(OOViewID)oldViewDirection andProcessWeaponFacing:(BOOL)processWeaponFacing justNotify:(BOOL)justNotify { if (!justNotify) { if ([UNIVERSE displayGUI]) [self switchToMainView]; [UNIVERSE setViewDirection:viewDirection]; } if (processWeaponFacing) { OOWeaponFacing facing = WEAPON_FACING_NONE; switch (viewDirection) { case VIEW_FORWARD: facing = WEAPON_FACING_FORWARD; break; case VIEW_AFT: facing = WEAPON_FACING_AFT; break; case VIEW_PORT: facing = WEAPON_FACING_PORT; break; case VIEW_STARBOARD: facing = WEAPON_FACING_STARBOARD; break; default: break; } if (facing != WEAPON_FACING_NONE) { currentWeaponFacing = facing; [self currentWeaponStats]; } else { OOLogERR(kOOLogParameterError, @"%s called with processWeaponFacing=YES for non-main view %i.", __FUNCTION__, viewDirection); } } if ((oldViewDirection != viewDirection || viewDirection == VIEW_CUSTOM) && ![[UNIVERSE gameController] isGamePaused]) { JSContext *context = OOJSAcquireContext(); ShipScriptEvent(context, self, "viewDirectionChanged", OOJSValueFromViewID(context, viewDirection), OOJSValueFromViewID(context, oldViewDirection)); OOJSRelinquishContext(context); } } // Called on c or Shift-C - (void) handleAutopilotOn:(BOOL)fastDocking { NSString *message = nil; // Check alert condition - on red alert, abort // -- but only for fast docking if (fastDocking && ([self alertCondition] == ALERT_CONDITION_RED)) { [self playAutopilotCannotDockWithTarget]; message = OOExpandKey(@"autopilot-red-alert"); goto abort; } Entity *target = [self primaryTarget]; // If target isn't dockable, check for nearby stations if (![target isStation]) { Universe *uni = UNIVERSE; Entity **entities = uni->sortedEntities; // grab the public sorted list int nStations = 0; unsigned i; for (i = 0; i < uni->n_entities && nStations < 2; i++) { if (entities[i]->isStation && [entities[i] isKindOfClass:[StationEntity class]] && entities[i]->zero_distance <= SCANNER_MAX_RANGE2) { nStations++; target = entities[i]; } } // If inside the Aegis, dock with the main station. // If we found one target, dock with it. // If outside the Aegis and we found multiple targets, abort. if ([self withinStationAegis] && legalStatus <= 50) { target = [UNIVERSE station]; } else if (nStations != 1) { if (nStations == 0) { [self playAutopilotOutOfRange]; message = OOExpandKey(@"autopilot-out-of-range"); } else { [self playAutopilotCannotDockWithTarget]; message = OOExpandKey(@"autopilot-multiple-targets"); } goto abort; } } // We found a dockable, check whether we can dock with it // NSAssert([target isKindOfClass:[StationEntity class]], @"Expected entity with isStation flag set to be a station."); // no need for asserts. Tested enough already. StationEntity *ts = (StationEntity *)target; NSString *stationName = [ts displayName]; // If station is not transmitting docking instructions, we cannot use autopilot. if (![ts allowsAutoDocking]) { [self playAutopilotCannotDockWithTarget]; message = OOExpandKey(@"autopilot-station-does-not-allow-autodocking", stationName); } // Deny if station is hostile or player is a fugitive trying to dock at the main station. else if ((legalStatus > 50 && ts == [UNIVERSE station]) || [ts isHostileTo:self]) { [self playAutopilotCannotDockWithTarget]; message = OOExpandKey((ts == [UNIVERSE station]) ? @"autopilot-denied" : @"autopilot-target-docking-instructions-denied", stationName); } // If we're fast-docking, perform the docking logic else if (fastDocking && [ts allowsFastDocking]) { if (legalStatus > 0) { // there's a slight chance you'll be fined for your past offences when autodocking int fine_chance = ranrot_rand() & 0x03ff; // 0..1023 int government = 1 + [[UNIVERSE currentSystemData] oo_intForKey:KEY_GOVERNMENT]; // 1..8 if ([UNIVERSE inInterstellarSpace]) government = 2; // equivalent to Feudal. I'm assuming any station in interstellar space is military. -- Ahruman 2008-05-29 fine_chance /= government; if (fine_chance < legalStatus) { [self markForFines]; } } [self setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_GRANTED]; [UNIVERSE forceWitchspaceEntries]; ship_clock_adjust += 1200.0; // 20 minutes penalty to enter dock ident_engaged = NO; [self safeAllMissiles]; [UNIVERSE setViewDirection:VIEW_FORWARD]; [self enterDock:ts]; } else { // Standard docking - engage autopilot [self engageAutopilotToStation:ts]; message = OOExpandKey(@"autopilot-on"); } abort: // Clean-up code if (message != nil) [UNIVERSE addMessage:message forCount:4.5]; return; } - (void) handleButtonIdent { // Clear current target if we're already in Ident mode if (ident_engaged) [self noteLostTarget]; [self safeAllMissiles]; ident_engaged = YES; if ([self primaryTarget] == nil) { [self playIdentOn]; [UNIVERSE addMessage:OOExpandKey(@"ident-on") forCount:2.0]; } else { [self playIdentLockedOn]; [self printIdentLockedOnForMissile:NO]; } } - (void) handleButtonTargetMissile { if (![self weaponsOnline]) { [self handleButtonIdent]; return; } // Clear current target if we're already in Missile Targeting mode if (missile_status != MISSILE_STATUS_SAFE) { [self noteLostTarget]; } // Arm missile and check for missile lock missile_status = MISSILE_STATUS_ARMED; if ([missile_entity[activeMissile] isMissile]) { if ([[self primaryTarget] isShip]) { missile_status = MISSILE_STATUS_TARGET_LOCKED; [missile_entity[activeMissile] addTarget:[self primaryTarget]]; [self printIdentLockedOnForMissile:YES]; [self playMissileLockedOn]; } else { // if it's nil, that means it was lost earlier if ([self primaryTarget] != nil) { [self noteLostTarget]; } [missile_entity[activeMissile] noteLostTarget]; NSString *weaponName = [missile_entity[activeMissile] name]; [UNIVERSE addMessage:OOExpandKey(@"missile-armed", weaponName) forCount:2.0]; [self playMissileArmed]; } } else if ([missile_entity[activeMissile] isMine]) { NSString *weaponName = [missile_entity[activeMissile] name]; [UNIVERSE addMessage:OOExpandKey(@"mine-armed", weaponName) forCount:2.0]; [self playMineArmed]; } ident_engaged = NO; } @end oolite-1.82/src/Core/Entities/PlayerEntityLegacyScriptEngine.h000066400000000000000000000223331256642440500244570ustar00rootroot00000000000000/* PlayerEntityLegacyScriptEngine.h Various utility methods used for scripting. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "PlayerEntity.h" @class OOScript; typedef enum { COMPARISON_EQUAL, COMPARISON_NOTEQUAL, COMPARISON_LESSTHAN, COMPARISON_GREATERTHAN, COMPARISON_ONEOF, COMPARISON_UNDEFINED } OOComparisonType; typedef enum { OP_STRING, OP_NUMBER, OP_BOOL, OP_MISSION_VAR, OP_LOCAL_VAR, OP_FALSE, OP_INVALID // Must be last. } OOOperationType; @interface PlayerEntity (Scripting) - (void) checkScript; - (void) setScriptTarget:(ShipEntity *)ship; - (ShipEntity*) scriptTarget; - (void) runScriptActions:(NSArray *)sanitizedActions withContextName:(NSString *)contextName forTarget:(ShipEntity *)target; - (void) runUnsanitizedScriptActions:(NSArray *)unsanitizedActions allowingAIMethods:(BOOL)allowAIMethods withContextName:(NSString *)contextName forTarget:(ShipEntity *)target; // Test (sanitized) legacy script conditions array. - (BOOL) scriptTestConditions:(NSArray *)array; - (NSDictionary*) missionVariables; - (NSString *)missionVariableForKey:(NSString *)key; - (void)setMissionVariable:(NSString *)value forKey:(NSString *)key; - (NSMutableDictionary *)localVariablesForMission:(NSString *)missionKey; - (NSString *)localVariableForKey:(NSString *)variableName andMission:(NSString *)missionKey; - (void)setLocalVariable:(NSString *)value forKey:(NSString *)variableName andMission:(NSString *)missionKey; /*-----------------------------------------------------*/ - (NSString *) mission_string; - (NSString *) status_string; - (NSString *) gui_screen_string; - (NSNumber *) galaxy_number; - (NSNumber *) planet_number; - (NSNumber *) score_number; - (NSNumber *) credits_number; - (NSNumber *) scriptTimer_number; - (NSNumber *) shipsFound_number; - (NSNumber *) d100_number; - (NSNumber *) pseudoFixedD100_number; - (NSNumber *) d256_number; - (NSNumber *) pseudoFixedD256_number; - (NSNumber *) clock_number; // returns the game time in seconds - (NSNumber *) clock_secs_number; // returns the game time in seconds - (NSNumber *) clock_mins_number; // returns the game time in minutes - (NSNumber *) clock_hours_number; // returns the game time in hours - (NSNumber *) clock_days_number; // returns the game time in days - (NSNumber *) fuelLevel_number; // returns the fuel level in LY - (NSString *) dockedAtMainStation_bool; - (NSString *) foundEquipment_bool; - (NSString *) sunWillGoNova_bool; // returns whether the sun is going to go nova - (NSString *) sunGoneNova_bool; // returns whether the sun has gone nova - (NSString *) missionChoice_string; // returns nil or the key for the chosen option - (NSNumber *) dockedTechLevel_number; - (NSString *) dockedStationName_string; // returns 'NONE' if the player isn't docked, [station name] if it is, 'UNKNOWN' otherwise - (NSNumber *) systemGovernment_number; - (NSString *) systemGovernment_string; - (NSNumber *) systemEconomy_number; - (NSString *) systemEconomy_string; - (NSNumber *) systemTechLevel_number; - (NSNumber *) systemPopulation_number; - (NSNumber *) systemProductivity_number; - (NSString *) commanderName_string; - (NSString *) commanderRank_string; - (NSString *) commanderShip_string; - (NSString *) commanderShipDisplayName_string; - (NSString *) commanderLegalStatus_string; - (NSNumber *) commanderLegalStatus_number; /*-----------------------------------------------------*/ - (NSArray *) missionsList; - (void) setMissionDescription:(NSString *)textKey; - (void) clearMissionDescription; - (void) setMissionInstructions:(NSString *)text forMission:(NSString *)key; - (void) setMissionInstructionsList:(NSArray *)list forMission:(NSString *)key; - (void) setMissionDescription:(NSString *)textKey forMission:(NSString *)key; - (void) clearMissionDescriptionForMission:(NSString *)key; - (void) commsMessage:(NSString *)valueString; - (void) commsMessageByUnpiloted:(NSString *)valueString; // Enabled 02-May-2008 - Nikos. Same as commsMessage, but // can be used by scripts to have unpiloted ships sending // commsMessages, if we want to. - (void) consoleMessage3s:(NSString *)valueString; - (void) consoleMessage6s:(NSString *)valueString; - (void) setLegalStatus:(NSString *)valueString; - (void) awardCredits:(NSString *)valueString; - (void) awardShipKills:(NSString *)valueString; - (void) awardEquipment:(NSString *)equipString; //eg. EQ_NAVAL_ENERGY_UNIT - (void) removeEquipment:(NSString *)equipString; //eg. EQ_NAVAL_ENERGY_UNIT - (void) setPlanetinfo:(NSString *)key_valueString; // uses key=value format - (void) setSpecificPlanetInfo:(NSString *)key_valueString; // uses galaxy#=planet#=key=value - (void) awardCargo:(NSString *)amount_typeString; - (void) removeAllCargo; - (void) removeAllCargo:(BOOL)forceRemoval; - (void) useSpecialCargo:(NSString *)descriptionString; - (void) testForEquipment:(NSString *)equipString; //eg. EQ_NAVAL_ENERGY_UNIT - (void) awardFuel:(NSString *)valueString; // add to fuel up to 7.0 LY - (void) messageShipAIs:(NSString *)roles_message; - (void) ejectItem:(NSString *)item_key; - (void) addShips:(NSString *)roles_number; - (void) addSystemShips:(NSString *)roles_number_position; - (void) addShipsAt:(NSString *)roles_number_system_x_y_z; - (void) addShipsAtPrecisely:(NSString *)roles_number_system_x_y_z; - (void) addShipsWithinRadius:(NSString *)roles_number_system_x_y_z_r; - (void) spawnShip:(NSString *)ship_key; - (void) set:(NSString *)missionvariable_value; - (void) reset:(NSString *)missionvariable; /* set:missionvariable_value add:missionvariable_value subtract:missionvariable_value the value may be a string constant or one of the above calls ending in _bool, _number, or _string egs. set: mission_my_mission_status MISSION_START set: mission_my_mission_value 12.345 set: mission_my_mission_clock clock_number add: mission_my_mission_clock 86400 subtract: mission_my_mission_clock d100_number */ - (void) increment:(NSString *)missionVariableString; - (void) decrement:(NSString *)missionVariableString; - (void) add:(NSString *)missionVariableString_value; - (void) subtract:(NSString *)missionVariableString_value; - (void) checkForShips: (NSString *)roleString; - (void) resetScriptTimer; - (void) addMissionText: (NSString *)textKey; - (void) addLiteralMissionText: (NSString *)text; - (void) setMissionChoiceByTextEntry:(BOOL)enable; - (void) setMissionChoices:(NSString *)choicesKey; // choicesKey is a key for a dictionary of // choices/choice phrases in missiontext.plist and also.. - (void) setMissionChoicesDictionary:(NSDictionary *)choicesDict; - (void) resetMissionChoice; // resets MissionChoice to nil - (void) clearMissionScreen; - (void) addMissionDestination:(NSString *)destinations; // mark a system on the star charts - (void) removeMissionDestination:(NSString *)destinations; // stop a system being marked on star charts - (void) showShipModel:(NSString *)shipKey; - (void) setMissionMusic:(NSString *)value; - (NSString *)missionTitle; - (void) setMissionTitle:(NSString *)value; - (void) setFuelLeak: (NSString *)value; - (NSNumber *)fuelLeakRate_number; - (void) setSunNovaIn: (NSString *)time_value; - (void) launchFromStation; - (void) blowUpStation; - (void) sendAllShipsAway; - (OOPlanetEntity *) addPlanet: (NSString *)planetKey; - (OOPlanetEntity *) addMoon: (NSString *)moonKey; - (void) debugOn; - (void) debugOff; - (void) debugMessage:(NSString *)args; - (NSString*) replaceVariablesInString:(NSString*) args; - (void) playSound:(NSString *) soundName; - (BOOL) addEqScriptForKey:(NSString *)eq_key; - (void) removeEqScriptForKey:(NSString *)eq_key; - (NSUInteger) eqScriptIndexForKey:(NSString *)eq_key; - (void) targetNearestHostile; - (void) targetNearestIncomingMissile; - (void) setGalacticHyperspaceBehaviourTo:(NSString *) galacticHyperspaceBehaviourString; - (void) setGalacticHyperspaceFixedCoordsTo:(NSString *) galacticHyperspaceFixedCoordsString; /*-----------------------------------------------------*/ - (void) clearMissionScreenID; - (void) setMissionScreenID:(NSString *)msid; - (NSString *) missionScreenID; - (void) setGuiToMissionScreen; - (void) refreshMissionScreenTextEntry; - (void) setGuiToMissionScreenWithCallback:(BOOL) callback; - (void) doMissionCallback; - (void) endMissionScreenAndNoteOpportunity; - (void) setBackgroundFromDescriptionsKey:(NSString*) d_key; - (void) addScene:(NSArray *) items atOffset:(Vector) off; - (BOOL) processSceneDictionary:(NSDictionary *) couplet atOffset:(Vector) off; - (BOOL) processSceneString:(NSString*) item atOffset:(Vector) off; @end NSString *OOComparisonTypeToString(OOComparisonType type) CONST_FUNC; oolite-1.82/src/Core/Entities/PlayerEntityLegacyScriptEngine.m000066400000000000000000002533261256642440500244740ustar00rootroot00000000000000/* PlayerEntityLegacyScriptEngine.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "PlayerEntityLegacyScriptEngine.h" #import "PlayerEntityScriptMethods.h" #import "PlayerEntitySound.h" #import "PlayerEntityContracts.h" #import "GuiDisplayGen.h" #import "Universe.h" #import "ResourceManager.h" #import "AI.h" #import "ShipEntityAI.h" #import "ShipEntityScriptMethods.h" #import "OOScript.h" #import "OOMusicController.h" #import "OOColor.h" #import "OOStringParsing.h" #import "OOStringExpander.h" #import "OOConstToString.h" #import "OOTexture.h" #import "OOCollectionExtractors.h" #import "OOLoggingExtended.h" #import "OOSound.h" #import "OOSunEntity.h" #import "OOPlanetEntity.h" #import "OOPlanetEntity.h" #import "StationEntity.h" #import "Comparison.h" #import "OOLegacyScriptWhitelist.h" #import "OOJavaScriptEngine.h" #import "OOEquipmentType.h" #import "HeadUpDisplay.h" #import "OOSystemDescriptionManager.h" static NSString * const kOOLogScriptAddShipsFailed = @"script.addShips.failed"; static NSString * const kOOLogScriptMissionDescNoText = @"script.missionDescription.noMissionText"; static NSString * const kOOLogScriptMissionDescNoKey = @"script.missionDescription.noMissionKey"; static NSString * const kOOLogDebugOnMetaClass = @"$scriptDebugOn"; static NSString * const kOOLogDebugMessage = @"script.debug.message"; static NSString * const kOOLogDebugOnOff = @"script.debug.onOff"; static NSString * const kOOLogDebugAddPlanet = @"script.debug.addPlanet"; static NSString * const kOOLogDebugReplaceVariablesInString = @"script.debug.replaceVariablesInString"; static NSString * const kOOLogDebugProcessSceneStringAddScene = @"script.debug.processSceneString.addScene"; static NSString * const kOOLogDebugProcessSceneStringAddModel = @"script.debug.processSceneString.addModel"; static NSString * const kOOLogDebugProcessSceneStringAddMiniPlanet = @"script.debug.processSceneString.addMiniPlanet"; static NSString * const kOOLogNoteRemoveAllCargo = @"script.debug.note.removeAllCargo"; static NSString * const kOOLogNoteUseSpecialCargo = @"script.debug.note.useSpecialCargo"; static NSString * const kOOLogNoteAddShips = @"script.debug.note.addShips"; static NSString * const kOOLogNoteSet = @"script.debug.note.set"; static NSString * const kOOLogNoteShowShipModel = @"script.debug.note.showShipModel"; static NSString * const kOOLogNoteFuelLeak = @"script.debug.note.setFuelLeak"; static NSString * const kOOLogNoteAddPlanet = @"script.debug.note.addPlanet"; static NSString * const kOOLogNoteProcessSceneString = @"script.debug.note.processSceneString"; static NSString * const kOOLogSyntaxSetPlanetInfo = @"script.debug.syntax.setPlanetInfo"; static NSString * const kOOLogSyntaxAwardCargo = @"script.debug.syntax.awardCargo"; static NSString * const kOOLogSyntaxAwardEquipment = @"script.debug.syntax.awardEquipment"; static NSString * const kOOLogSyntaxRemoveEquipment = @"script.debug.syntax.removeEquipment"; static NSString * const kOOLogSyntaxMessageShipAIs = @"script.debug.syntax.messageShipAIs"; static NSString * const kOOLogSyntaxAddShips = @"script.debug.syntax.addShips"; static NSString * const kOOLogSyntaxSet = @"script.debug.syntax.set"; static NSString * const kOOLogSyntaxReset = @"script.debug.syntax.reset"; static NSString * const kOOLogSyntaxIncrement = @"script.debug.syntax.increment"; static NSString * const kOOLogSyntaxDecrement = @"script.debug.syntax.decrement"; static NSString * const kOOLogSyntaxAdd = @"script.debug.syntax.add"; static NSString * const kOOLogSyntaxSubtract = @"script.debug.syntax.subtract"; static NSString * const kOOLogRemoveAllCargoNotDocked = @"script.error.removeAllCargo.notDocked"; #define ACTIONS_TEMP_PREFIX "__oolite_actions_temp" static NSString * const kActionTempPrefix = @ ACTIONS_TEMP_PREFIX; static NSString *sMissionStringValue = nil; static NSString *sCurrentMissionKey = nil; static ShipEntity *scriptTarget = nil; @interface PlayerEntity (ScriptingPrivate) - (BOOL) scriptTestCondition:(NSArray *)scriptCondition; - (NSString *) expandScriptRightHandSide:(NSArray *)rhsComponents; - (void) scriptActions:(NSArray *)actions forTarget:(ShipEntity *)target missionKey:(NSString *)missionKey; - (NSString *) expandMessage:(NSString *)valueString; @end @implementation PlayerEntity (Scripting) static NSString *CurrentScriptNameOr(NSString *alternative) { if (sCurrentMissionKey != nil && ![sCurrentMissionKey hasPrefix:kActionTempPrefix]) { return [NSString stringWithFormat:@"\"%@\"", sCurrentMissionKey]; } return alternative; } OOINLINE NSString *CurrentScriptDesc(void) { return CurrentScriptNameOr(@""); } static void PerformScriptActions(NSArray *actions, Entity *target); static void PerformConditionalStatment(NSArray *actions, Entity *target); static void PerformActionStatment(NSArray *statement, Entity *target); static BOOL TestScriptConditions(NSArray *conditions); static void PerformScriptActions(NSArray *actions, Entity *target) { NSArray *statement = nil; foreach (statement, actions) { if ([[statement objectAtIndex:0] boolValue]) { PerformConditionalStatment(statement, target); } else { PerformActionStatment(statement, target); } } } static void PerformConditionalStatment(NSArray *statement, Entity *target) { /* A sanitized conditional statement takes the form of an array: (true, conditions, trueActions, falseActions) The first element is always true. The second is an array of conditions. The third and four elements are actions to perform if the conditions evaluate to true or false, respectively. */ NSArray *conditions = nil; NSArray *actions = nil; conditions = [statement objectAtIndex:1]; if (TestScriptConditions(conditions)) { actions = [statement objectAtIndex:2]; } else { actions = [statement objectAtIndex:3]; } PerformScriptActions(actions, target); } static void PerformActionStatment(NSArray *statement, Entity *target) { /* A sanitized action statement takes the form of an array: (false, selector [, argument]) The first element is always false. The second is the method selector (as a string). If the method takes an argument, the third argument is the argument string. The sanitizer is responsible for ensuring that there is an argument, even if it's the empty string, for any selector with a colon at the end, and no arguments for selectors without colons. The runner can therefore use the list's element count as a flag without examining the selector. */ NSString *selectorString = nil; NSString *argumentString = nil; NSString *expandedString = nil; SEL selector = NULL; NSMutableDictionary *locals = nil; PlayerEntity *player = PLAYER; selectorString = [statement objectAtIndex:1]; if ([statement count] > 2) argumentString = [statement objectAtIndex:2]; selector = NSSelectorFromString(selectorString); if (target == nil || ![target respondsToSelector:selector]) { target = player; } if (argumentString != nil) { // Method with argument; substitute [description] expressions. locals = [player localVariablesForMission:sCurrentMissionKey]; expandedString = OOExpandDescriptionString(OOStringExpanderDefaultRandomSeed(), argumentString, nil, locals, nil, kOOExpandNoOptions); [target performSelector:selector withObject:expandedString]; } else { // Method without argument. [target performSelector:selector]; } } static BOOL TestScriptConditions(NSArray *conditions) { NSEnumerator *condEnum = nil; NSArray *condition = nil; PlayerEntity *player = PLAYER; for (condEnum = [conditions objectEnumerator]; (condition = [condEnum nextObject]); ) { if (![player scriptTestCondition:condition]) return NO; } return YES; } - (void) setScriptTarget:(ShipEntity *)ship { scriptTarget = ship; } - (ShipEntity*) scriptTarget { return scriptTarget; } OOINLINE OOEntityStatus RecursiveRemapStatus(OOEntityStatus status) { // Some player stutuses should only be seen once per "event". // This remaps them to something innocuous in case of recursion. if (status == STATUS_DOCKING || status == STATUS_LAUNCHING || status == STATUS_ENTERING_WITCHSPACE || status == STATUS_EXITING_WITCHSPACE) { return STATUS_IN_FLIGHT; } else { return status; } } static BOOL sRunningScript = NO; // Return the world scripts that care about -checkScript. - (NSDictionary *) worldScriptsRequiringTickle { if (worldScriptsRequiringTickle != nil) return worldScriptsRequiringTickle; NSMutableDictionary *tickleScripts = [NSMutableDictionary dictionaryWithCapacity:[worldScripts count]]; NSString *scriptName; foreachkey (scriptName, worldScripts) { OOScript *candidateScript = [worldScripts objectForKey:scriptName]; if ([candidateScript requiresTickle]) { [tickleScripts setObject:candidateScript forKey:scriptName]; } } worldScriptsRequiringTickle = [tickleScripts copy]; return worldScriptsRequiringTickle; } - (void) checkScript { BOOL wasRunningScript = sRunningScript; OOEntityStatus status, restoreStatus; NSDictionary *tickleScripts = [self worldScriptsRequiringTickle]; if ([tickleScripts count] == 0) { // Quick exit if we only have JS scripts. return; } [self setScriptTarget:self]; /* World scripts can potentially be invoked recursively, through scriptActionOnTarget: and possibly other mechanisms. This is bad, but that's the way it is. Legacy world scripts rely on only seeing certain player statuses once per "event". To ensure this, we must lie about the player's status when invoked recursively. Of course, there are also methods in the game that rely on status not lying. However, I don't believe any that rely on these particular statuses can be legitimately invoked by scripts. The alternative would be to track the "status-as-seen-by-scripts" separately from the "real" status, which'd risk synchronization problems. In summary, scriptActionOnTarget: is bad, and calling it from scripts rather than AIs is very bad. -- Ahruman, 20080302 Addendum: scriptActionOnTarget: is currently not in the whitelist for script methods. Let's hope this doesn't turn out to be a problem. -- Ahruman, 20090208 */ status = [self status]; restoreStatus = status; @try { if (sRunningScript) { status = RecursiveRemapStatus(status); [self setStatus:status]; } sRunningScript = YES; // After all that, actually running the scripts is trivial. [[tickleScripts allValues] makeObjectsPerformSelector:@selector(runWithTarget:) withObject:self]; } @catch (NSException *exception) { OOLog(kOOLogException, @"***** Exception running world scripts: %@ : %@", [exception name], [exception reason]); } // Restore anti-recursion measures. sRunningScript = wasRunningScript; if (status != restoreStatus) [self setStatus:restoreStatus]; } - (void)runScriptActions:(NSArray *)actions withContextName:(NSString *)contextName forTarget:(ShipEntity *)target { NSAutoreleasePool *pool = nil; NSString *oldMissionKey = nil; NSString * volatile theMissionKey = contextName; // Work-around for silly exception macros pool = [[NSAutoreleasePool alloc] init]; // FIXME: does this actually make sense in the context of non-missions? oldMissionKey = sCurrentMissionKey; sCurrentMissionKey = theMissionKey; @try { PerformScriptActions(actions, target); } @catch (NSException *exception) { OOLog(@"script.error.exception", @"***** EXCEPTION %@: %@ while handling legacy script actions for %@", [exception name], [exception reason], [theMissionKey hasPrefix:kActionTempPrefix] ? [target shortDescription] : theMissionKey); // Suppress exception } sCurrentMissionKey = oldMissionKey; [pool release]; } - (void) runUnsanitizedScriptActions:(NSArray *)actions allowingAIMethods:(BOOL)allowAIMethods withContextName:(NSString *)contextName forTarget:(ShipEntity *)target { [self runScriptActions:OOSanitizeLegacyScript(actions, contextName, allowAIMethods) withContextName:contextName forTarget:target]; } - (BOOL) scriptTestConditions:(NSArray *)array { BOOL result = NO; @try { result = TestScriptConditions(array); } @catch (NSException *exception) { OOLog(@"script.error.exception", @"***** EXCEPTION %@: %@ while testing legacy script conditions.", [exception name], [exception reason]); // Suppress exception } return result; } - (BOOL) scriptTestCondition:(NSArray *)scriptCondition { /* Test a script condition sanitized by OOLegacyScriptWhitelist. A sanitized condition is an array of the form: (opType, rawString, selector, comparisonType, operandArray). opType and comparisonType are NSNumbers containing OOOperationType and OOComparisonType enumerators, respectively. rawString is the original textual representation of the condition for display purposes. selector is a string, either a method selector or a mission/local variable name. operandArray is an array of operands. Each operand is itself an array of two items: a boolean indicating whether it's a method selector (true) or a literal string (false), and a string. The special opType OP_FALSE doesn't require any other elements in the array. All other valid opTypes require the array to have five elements. For performance reasons, this method assumes the script condition will have been generated by OOSanitizeLegacyScriptConditions() and doesn't perform extensive validity checks. */ OOOperationType opType; NSString *selectorString = nil; SEL selector = NULL; OOComparisonType comparator; NSArray *operandArray = nil; NSString *lhsString = nil; NSString *expandedRHS = nil; NSArray *rhsComponents = nil; NSString *rhsItem = nil; NSUInteger i, count; NSCharacterSet *whitespace = nil; double lhsValue, rhsValue; BOOL lhsFlag, rhsFlag; opType = [scriptCondition oo_unsignedIntAtIndex:0]; if (opType == OP_FALSE) return NO; selectorString = [scriptCondition oo_stringAtIndex:2]; comparator = [scriptCondition oo_unsignedIntAtIndex:3]; operandArray = [scriptCondition oo_arrayAtIndex:4]; // Transform mission/local var ops into string ops. if (opType == OP_MISSION_VAR) { sMissionStringValue = [mission_variables objectForKey:selectorString]; selector = @selector(mission_string); opType = OP_STRING; } else if (opType == OP_LOCAL_VAR) { sMissionStringValue = [[self localVariablesForMission:sCurrentMissionKey] objectForKey:selectorString]; selector = @selector(mission_string); opType = OP_STRING; } else { selector = NSSelectorFromString(selectorString); } expandedRHS = [self expandScriptRightHandSide:operandArray]; if (opType == OP_STRING) { lhsString = [self performSelector:selector]; #define DOUBLEVAL(x) ((x != nil) ? [x doubleValue] : 0.0) switch (comparator) { case COMPARISON_UNDEFINED: return lhsString == nil; case COMPARISON_EQUAL: return [lhsString isEqualToString:expandedRHS]; case COMPARISON_NOTEQUAL: return ![lhsString isEqualToString:expandedRHS]; case COMPARISON_LESSTHAN: return DOUBLEVAL(lhsString) < DOUBLEVAL(expandedRHS); case COMPARISON_GREATERTHAN: return DOUBLEVAL(lhsString) > DOUBLEVAL(expandedRHS); case COMPARISON_ONEOF: { rhsComponents = [expandedRHS componentsSeparatedByString:@","]; count = [rhsComponents count]; whitespace = [NSCharacterSet whitespaceCharacterSet]; lhsString = [lhsString stringByTrimmingCharactersInSet:whitespace]; for (i = 0; i < count; i++) { rhsItem = [[rhsComponents objectAtIndex:i] stringByTrimmingCharactersInSet:whitespace]; if ([lhsString isEqualToString:rhsItem]) { return YES; } } } return NO; } } else if (opType == OP_NUMBER) { lhsValue = [[self performSelector:selector] doubleValue]; if (comparator == COMPARISON_ONEOF) { rhsComponents = [expandedRHS componentsSeparatedByString:@","]; count = [rhsComponents count]; for (i = 0; i < count; i++) { rhsItem = [rhsComponents objectAtIndex:i]; rhsValue = [rhsItem doubleValue]; if (lhsValue == rhsValue) { return YES; } } return NO; } else { rhsValue = [expandedRHS doubleValue]; switch (comparator) { case COMPARISON_EQUAL: return lhsValue == rhsValue; case COMPARISON_NOTEQUAL: return lhsValue != rhsValue; case COMPARISON_LESSTHAN: return lhsValue < rhsValue; case COMPARISON_GREATERTHAN: return lhsValue > rhsValue; case COMPARISON_UNDEFINED: case COMPARISON_ONEOF: // "Can't happen" - undefined should have been caught by the sanitizer, oneof is handled above. OOLog(@"script.error.unexpectedOperator", @"***** SCRIPT ERROR: in %@, operator %@ is not valid for numbers, evaluating to false.", CurrentScriptDesc(), OOComparisonTypeToString(comparator)); return NO; } } } else if (opType == OP_BOOL) { lhsFlag = [[self performSelector:selector] isEqualToString:@"YES"]; rhsFlag = [expandedRHS isEqualToString:@"YES"]; switch (comparator) { case COMPARISON_EQUAL: return lhsFlag == rhsFlag; case COMPARISON_NOTEQUAL: return lhsFlag != rhsFlag; case COMPARISON_LESSTHAN: case COMPARISON_GREATERTHAN: case COMPARISON_UNDEFINED: case COMPARISON_ONEOF: // "Can't happen" - should have been caught by the sanitizer. OOLog(@"script.error.unexpectedOperator", @"***** SCRIPT ERROR: in %@, operator %@ is not valid for booleans, evaluating to false.", CurrentScriptDesc(), OOComparisonTypeToString(comparator)); return NO; } } // What are we doing here? OOLog(@"script.error.fallthrough", @"***** SCRIPT ERROR: in %@, unhandled condition '%@' (%@). %@", CurrentScriptDesc(), [scriptCondition objectAtIndex:1], scriptCondition, @"This is an internal error, please report it."); return NO; } - (NSString *) expandScriptRightHandSide:(NSArray *)rhsComponents { NSMutableArray *result = nil; NSEnumerator *componentEnum = nil; NSArray *component = nil; NSString *value = nil; result = [NSMutableArray arrayWithCapacity:[rhsComponents count]]; for (componentEnum = [rhsComponents objectEnumerator]; (component = [componentEnum nextObject]); ) { /* Each component is a two-element array. The second element is a string. The first element is a boolean indicating whether the string is a selector (true) or a literal (false). All valid selectors return a string or an NSNumber; in either case, -description gives us a useful value to substitute into the expanded string. */ value = [component oo_stringAtIndex:1]; if ([[component objectAtIndex:0] boolValue]) { value = [[self performSelector:NSSelectorFromString(value)] description]; if (value == nil) value = @"(null)"; // for backwards compatibility } [result addObject:value]; } return [result componentsJoinedByString:@" "]; } - (NSDictionary *) missionVariables { return mission_variables; } - (NSString *)missionVariableForKey:(NSString *)key { NSString *result = nil; if (key != nil) result = [mission_variables objectForKey:key]; return result; } - (void)setMissionVariable:(NSString *)value forKey:(NSString *)key { if (key != nil) { if (value != nil) [mission_variables setObject:value forKey:key]; else [mission_variables removeObjectForKey:key]; } } - (NSMutableDictionary *)localVariablesForMission:(NSString *)missionKey { NSMutableDictionary *result = nil; if (missionKey == nil) return nil; result = [localVariables objectForKey:missionKey]; if (result == nil) { result = [NSMutableDictionary dictionary]; [localVariables setObject:result forKey:missionKey]; } return result; } - (NSString *)localVariableForKey:(NSString *)variableName andMission:(NSString *)missionKey { return [[localVariables oo_dictionaryForKey:missionKey] objectForKey:variableName]; } - (void)setLocalVariable:(NSString *)value forKey:(NSString *)variableName andMission:(NSString *)missionKey { NSMutableDictionary *locals = nil; if (variableName != nil && missionKey != nil) { locals = [self localVariablesForMission:missionKey]; if (value != nil) { [locals setObject:value forKey:variableName]; } else { [locals removeObjectForKey:variableName]; } } } - (NSArray *) missionsList { NSEnumerator *scriptEnum = nil; NSString *scriptName = nil; NSString *vars = nil; NSMutableArray *result1 = nil; NSMutableArray *result2 = nil; result1 = [NSMutableArray array]; result2 = [NSMutableArray array]; NSArray* passengerManifest = [self passengerList]; NSArray* contractManifest = [self contractList]; NSArray* parcelManifest = [self parcelList]; if ([passengerManifest count] > 0) { [result2 addObject:[[NSArray arrayWithObject:DESC(@"manifest-passengers")] arrayByAddingObjectsFromArray:passengerManifest]]; } if ([parcelManifest count] > 0) { [result2 addObject:[[NSArray arrayWithObject:DESC(@"manifest-parcels")] arrayByAddingObjectsFromArray:parcelManifest]]; } if ([contractManifest count] > 0) { [result2 addObject:[[NSArray arrayWithObject:DESC(@"manifest-contracts")] arrayByAddingObjectsFromArray:contractManifest]]; } /* For proper display, array entries need to all be after string * entries, so sort them now */ for (scriptEnum = [worldScripts keyEnumerator]; (scriptName = [scriptEnum nextObject]); ) { vars = [mission_variables objectForKey:scriptName]; if (vars != nil) { if ([vars isKindOfClass:[NSString class]]) { [result1 addObject:vars]; } else if ([vars isKindOfClass:[NSArray class]]) { BOOL found = NO; NSArray *element = nil; foreach (element, result2) { if ([[element oo_stringAtIndex:0] isEqualToString:[(NSArray*)vars oo_stringAtIndex:0]]) { [result2 removeObject:element]; NSRange notTheHeader; notTheHeader.location = 1; notTheHeader.length = [(NSArray*)vars count]-1; [result2 addObject:[element arrayByAddingObjectsFromArray:[(NSArray*)vars subarrayWithRange:notTheHeader]]]; found = YES; break; } } if (!found) { [result2 addObject:vars]; } } } } return [result1 arrayByAddingObjectsFromArray:result2]; } - (NSString*) replaceVariablesInString:(NSString*) args { NSMutableDictionary *locals = [self localVariablesForMission:sCurrentMissionKey]; NSMutableString *resultString = [NSMutableString stringWithString: args]; NSString *valueString; unsigned i; NSMutableArray *tokens = ScanTokensFromString(args); for (i = 0; i < [tokens count]; i++) { valueString = [tokens objectAtIndex:i]; if ([valueString hasPrefix:@"mission_"] && [mission_variables objectForKey:valueString]) { [resultString replaceOccurrencesOfString:valueString withString:[mission_variables objectForKey:valueString] options:NSLiteralSearch range:NSMakeRange(0, [resultString length])]; } else if ([locals objectForKey:valueString]) { [resultString replaceOccurrencesOfString:valueString withString:[locals objectForKey:valueString] options:NSLiteralSearch range:NSMakeRange(0, [resultString length])]; } else if (([valueString hasSuffix:@"_number"])||([valueString hasSuffix:@"_bool"])||([valueString hasSuffix:@"_string"])) { SEL valueselector = NSSelectorFromString(valueString); if ([self respondsToSelector:valueselector]) { [resultString replaceOccurrencesOfString:valueString withString:[NSString stringWithFormat:@"%@", [self performSelector:valueselector]] options:NSLiteralSearch range:NSMakeRange(0, [resultString length])]; } } else if ([valueString hasPrefix:@"["]&&[valueString hasSuffix:@"]"]) { NSString* replaceString = OOExpand(valueString); [resultString replaceOccurrencesOfString:valueString withString:replaceString options:NSLiteralSearch range:NSMakeRange(0, [resultString length])]; } } OOLog(kOOLogDebugReplaceVariablesInString, @"EXPANSION: \"%@\" becomes \"%@\"", args, resultString); return [NSString stringWithString: resultString]; } /*-----------------------------------------------------*/ - (void) setMissionDescription:(NSString *)textKey { [self setMissionDescription:textKey forMission:sCurrentMissionKey]; } - (void) setMissionDescription:(NSString *)textKey forMission:(NSString *)key { NSString *text = [[UNIVERSE missiontext] oo_stringForKey:textKey]; if (!text) { OOLogERR(kOOLogScriptMissionDescNoText, @"in %@, no mission text set for key '%@' [UNIVERSE missiontext] is:\n%@ ", CurrentScriptDesc(), textKey, [UNIVERSE missiontext]); return; } [self setMissionInstructions:text forMission:key]; } // implementation of mission.setInstructions(), also final part of legacy setMissionDescription - (void) setMissionInstructions:(NSString *)text forMission:(NSString *)key { if (!key) { OOLogERR(kOOLogScriptMissionDescNoKey, @"in %@, mission key not set", CurrentScriptDesc()); return; } text = OOExpand(text); text = [self replaceVariablesInString: text]; [mission_variables setObject:text forKey:key]; } - (void) setMissionInstructionsList:(NSArray *)list forMission:(NSString *)key { if (!key) { OOLogERR(kOOLogScriptMissionDescNoKey, @"in %@, mission key not set", CurrentScriptDesc()); return; } NSString *text = nil; NSUInteger i,ct = [list count]; NSMutableArray *expandedList = [NSMutableArray arrayWithCapacity:ct]; for (i=0 ; i [self availableCargoSpace])) { OOLog(kOOLogSyntaxRemoveEquipment, @"***** SCRIPT ERROR: in %@, CANNOT remove cargo bay. Too much cargo.", CurrentScriptDesc()); return; } if ([self hasEquipmentItem:equipKey] || [self hasEquipmentItem:[equipKey stringByAppendingString:@"_DAMAGED"]]) { [self removeEquipmentItem:equipKey]; } } - (void) setPlanetinfo:(NSString *)key_valueString // uses key=value format { NSArray * tokens = [key_valueString componentsSeparatedByString:@"="]; NSString* keyString = nil; NSString* valueString = nil; if ([tokens count] != 2) { OOLog(kOOLogSyntaxSetPlanetInfo, @"***** SCRIPT ERROR: in %@, CANNOT setPlanetinfo: '%@' (bad parameter count)", CurrentScriptDesc(), key_valueString); return; } keyString = [[tokens objectAtIndex:0] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; valueString = [[tokens objectAtIndex:1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; /* Legacy script planetinfo settings are now non-persistent over save/load * Virtually nothing uses them any more, and expecting them to have a * manifest and identifying what it is if so seems unnecessary */ [UNIVERSE setSystemDataKey:keyString value:valueString fromManifest:@""]; } - (void) setSpecificPlanetInfo:(NSString *)key_valueString // uses galaxy#=planet#=key=value { NSArray * tokens = [key_valueString componentsSeparatedByString:@"="]; NSString* keyString = nil; NSString* valueString = nil; int gnum, pnum; if ([tokens count] != 4) { OOLog(kOOLogSyntaxSetPlanetInfo, @"***** SCRIPT ERROR: in %@, CANNOT setSpecificPlanetInfo: '%@' (bad parameter count)", CurrentScriptDesc(), key_valueString); return; } gnum = [tokens oo_intAtIndex:0]; pnum = [tokens oo_intAtIndex:1]; keyString = [[tokens objectAtIndex:2] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; valueString = [[tokens objectAtIndex:3] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; [UNIVERSE setSystemDataForGalaxy:gnum planet:pnum key:keyString value:valueString fromManifest:@"" forLayer:OO_LAYER_OXP_DYNAMIC]; } - (void) awardCargo:(NSString *)amount_typeString { if (scriptTarget != self) return; NSArray *tokens = ScanTokensFromString(amount_typeString); OOCargoQuantityDelta amount; OOCommodityType type; OOMassUnit unit; if ([tokens count] != 2) { OOLog(kOOLogSyntaxAwardCargo, @"***** SCRIPT ERROR: in %@, CANNOT awardCargo: '%@' (%@)", CurrentScriptDesc(), amount_typeString, @"bad parameter count"); return; } type = [tokens oo_stringAtIndex:1]; if (![[UNIVERSE commodities] goodDefined:type]) { OOLog(kOOLogSyntaxAwardCargo, @"***** SCRIPT ERROR: in %@, CANNOT awardCargo: '%@' (%@)", CurrentScriptDesc(), amount_typeString, @"unknown type"); return; } amount = [tokens oo_intAtIndex:0]; if (amount < 0) { OOLog(kOOLogSyntaxAwardCargo, @"***** SCRIPT ERROR: in %@, CANNOT awardCargo: '%@' (%@)", CurrentScriptDesc(), amount_typeString, @"negative quantity"); return; } unit = [shipCommodityData massUnitForGood:type]; if (specialCargo && unit == UNITS_TONS) { OOLog(kOOLogSyntaxAwardCargo, @"***** SCRIPT ERROR: in %@, CANNOT awardCargo: '%@' (%@)", CurrentScriptDesc(), amount_typeString, @"cargo hold full with special cargo"); return; } [self awardCommodityType:type amount:amount]; } - (void) removeAllCargo { [self removeAllCargo:NO]; } - (void) removeAllCargo:(BOOL)forceRemoval { // Misnamed method. It only removes cargo measured in TONS, g & Kg items are not removed. --Kaks 20091004 OOCommodityType type; if (scriptTarget != self) return; if ([self status] != STATUS_DOCKED && !forceRemoval) { OOLogWARN(kOOLogRemoveAllCargoNotDocked, @"%@removeAllCargo only works when docked.", [NSString stringWithFormat:@" in %@, ", CurrentScriptDesc()]); return; } OOLog(kOOLogNoteRemoveAllCargo, @"%@ removeAllCargo", forceRemoval ? @"Forcing" : @"Going to"); foreach(type, [shipCommodityData goods]) { if ([shipCommodityData massUnitForGood:type] == UNITS_TONS) { [shipCommodityData setQuantity:0 forGood:type]; } } if (forceRemoval && [self status] != STATUS_DOCKED) { NSInteger i; for (i = [cargo count] - 1; i >= 0; i--) { ShipEntity* canister = [cargo objectAtIndex:i]; if (!canister) break; // Since we are forcing cargo removal, we don't really care about the unit of measurement. Any // commodity at more than 1000kg or 1000000gr will be inside cargopods, so remove those too. [cargo removeObjectAtIndex:i]; } } DESTROY(specialCargo); [self calculateCurrentCargo]; } - (void) useSpecialCargo:(NSString *)descriptionString { if (scriptTarget != self) return; [self removeAllCargo:YES]; OOLog(kOOLogNoteUseSpecialCargo, @"Going to useSpecialCargo:'%@'", descriptionString); specialCargo = [OOExpand(descriptionString) retain]; } - (void) testForEquipment:(NSString *)equipString //eg. EQ_NAVAL_ENERGY_UNIT { found_equipment = [self hasEquipmentItem:equipString]; } - (void) awardFuel:(NSString *)valueString // add to fuel up to 7.0 LY { int delta = 10 * [valueString floatValue]; OOFuelQuantity scriptTargetFuelBeforeAward = [scriptTarget fuel]; if (delta < 0 && scriptTargetFuelBeforeAward < (unsigned)-delta) [scriptTarget setFuel:0]; else { [scriptTarget setFuel:(scriptTargetFuelBeforeAward + delta)]; } } - (void) messageShipAIs:(NSString *)roles_message { NSMutableArray* tokens = ScanTokensFromString(roles_message); NSString* roleString = nil; NSString* messageString = nil; if ([tokens count] < 2) { OOLog(kOOLogSyntaxMessageShipAIs, @"***** SCRIPT ERROR: in %@, CANNOT messageShipAIs: '%@' (bad parameter count)", CurrentScriptDesc(), roles_message); return; } roleString = [tokens objectAtIndex:0]; [tokens removeObjectAtIndex:0]; messageString = [tokens componentsJoinedByString:@" "]; [UNIVERSE sendShipsWithPrimaryRole:roleString messageToAI:messageString]; } - (void) ejectItem:(NSString *)itemKey { if (scriptTarget == nil) scriptTarget = self; [scriptTarget ejectShipOfType:itemKey]; } - (void) addShips:(NSString *)roles_number { NSMutableArray* tokens = ScanTokensFromString(roles_number); NSString* roleString = nil; NSString* numberString = nil; if ([tokens count] != 2) { OOLog(kOOLogSyntaxAddShips, @"***** SCRIPT ERROR: in %@, CANNOT addShips: '%@' (expected )", CurrentScriptDesc(), roles_number); return; } roleString = [tokens objectAtIndex:0]; numberString = [tokens objectAtIndex:1]; int number = [numberString intValue]; if (number < 0) { OOLog(kOOLogSyntaxAddShips, @"***** SCRIPT ERROR: in %@, can't add %i ships -- that's less than zero, y'know..", CurrentScriptDesc(), number); return; } OOLog(kOOLogNoteAddShips, @"DEBUG: Going to add %d ships with role '%@'", number, roleString); while (number--) [UNIVERSE witchspaceShipWithPrimaryRole:roleString]; } - (void) addSystemShips:(NSString *)roles_number_position { NSMutableArray* tokens = ScanTokensFromString(roles_number_position); NSString* roleString = nil; NSString* numberString = nil; NSString* positionString = nil; if ([tokens count] != 3) { OOLog(kOOLogSyntaxAddShips, @"***** SCRIPT ERROR: in %@, CANNOT addSystemShips: '%@' (expected )", CurrentScriptDesc(), roles_number_position); return; } roleString = [tokens objectAtIndex:0]; numberString = [tokens objectAtIndex:1]; positionString = [tokens objectAtIndex:2]; int number = [numberString intValue]; double posn = [positionString doubleValue]; if (number < 0) { OOLog(kOOLogSyntaxAddShips, @"***** SCRIPT ERROR: in %@, can't add %i ships -- that's less than zero, y'know..", CurrentScriptDesc(), number); return; } OOLog(kOOLogNoteAddShips, @"DEBUG: Going to add %d ships with role '%@' at a point %.3f along route1", number, roleString, posn); while (number--) [UNIVERSE addShipWithRole:roleString nearRouteOneAt:posn]; } - (void) addShipsAt:(NSString *)roles_number_system_x_y_z { NSMutableArray* tokens = ScanTokensFromString(roles_number_system_x_y_z); NSString* roleString = nil; NSString* numberString = nil; NSString* systemString = nil; NSString* xString = nil; NSString* yString = nil; NSString* zString = nil; if ([tokens count] != 6) { OOLog(kOOLogSyntaxAddShips, @"***** SCRIPT ERROR: in %@, CANNOT addShipsAt: '%@' (expected )", CurrentScriptDesc(), roles_number_system_x_y_z); return; } roleString = [tokens objectAtIndex:0]; numberString = [tokens objectAtIndex:1]; systemString = [tokens objectAtIndex:2]; xString = [tokens objectAtIndex:3]; yString = [tokens objectAtIndex:4]; zString = [tokens objectAtIndex:5]; HPVector posn = make_HPvector( [xString floatValue], [yString floatValue], [zString floatValue]); int number = [numberString intValue]; if (number < 1) { OOLog(kOOLogSyntaxAddShips, @"----- WARNING in %@ Tried to add %i ships -- no ship added.", CurrentScriptDesc(), number); return; } OOLog(kOOLogNoteAddShips, @"DEBUG: Going to add %d ship(s) with role '%@' at point (%.3f, %.3f, %.3f) using system %@", number, roleString, posn.x, posn.y, posn.z, systemString); if (![UNIVERSE addShips: number withRole:roleString nearPosition: posn withCoordinateSystem: systemString]) { OOLog(kOOLogScriptAddShipsFailed, @"***** SCRIPT ERROR: in %@, %@ could not add %u ships with role \"%@\"", CurrentScriptDesc(), @"addShipsAt:", number, roleString); } } - (void) addShipsAtPrecisely:(NSString *)roles_number_system_x_y_z { NSMutableArray* tokens = ScanTokensFromString(roles_number_system_x_y_z); NSString* roleString = nil; NSString* numberString = nil; NSString* systemString = nil; NSString* xString = nil; NSString* yString = nil; NSString* zString = nil; if ([tokens count] != 6) { OOLog(kOOLogSyntaxAddShips, @"***** SCRIPT ERROR: in %@,* CANNOT addShipsAtPrecisely: '%@' (expected )", CurrentScriptDesc(), roles_number_system_x_y_z); return; } roleString = [tokens objectAtIndex:0]; numberString = [tokens objectAtIndex:1]; systemString = [tokens objectAtIndex:2]; xString = [tokens objectAtIndex:3]; yString = [tokens objectAtIndex:4]; zString = [tokens objectAtIndex:5]; HPVector posn = make_HPvector( [xString floatValue], [yString floatValue], [zString floatValue]); int number = [numberString intValue]; if (number < 1) { OOLog(kOOLogSyntaxAddShips, @"----- WARNING: in %@, Can't add %i ships -- no ship added.", CurrentScriptDesc(), number); return; } OOLog(kOOLogNoteAddShips, @"DEBUG: Going to add %d ship(s) with role '%@' precisely at point (%.3f, %.3f, %.3f) using system %@", number, roleString, posn.x, posn.y, posn.z, systemString); if (![UNIVERSE addShips: number withRole:roleString atPosition: posn withCoordinateSystem: systemString]) { OOLog(kOOLogScriptAddShipsFailed, @"***** SCRIPT ERROR: in %@, %@ could not add %u ships with role '%@'", CurrentScriptDesc(), @"addShipsAtPrecisely:", number, roleString); } } - (void) addShipsWithinRadius:(NSString *)roles_number_system_x_y_z_r { NSMutableArray* tokens = ScanTokensFromString(roles_number_system_x_y_z_r); if ([tokens count] != 7) { OOLog(kOOLogSyntaxAddShips, @"***** SCRIPT ERROR: in %@, CANNOT 'addShipsWithinRadius: %@' (expected ))", CurrentScriptDesc(), roles_number_system_x_y_z_r); return; } NSString* roleString = [tokens objectAtIndex:0]; int number = [[tokens objectAtIndex:1] intValue]; NSString* systemString = [tokens objectAtIndex:2]; GLfloat x = [[tokens objectAtIndex:3] floatValue]; GLfloat y = [[tokens objectAtIndex:4] floatValue]; GLfloat z = [[tokens objectAtIndex:5] floatValue]; GLfloat r = [[tokens objectAtIndex:6] floatValue]; HPVector posn = make_HPvector( x, y, z); if (number < 1) { OOLog(kOOLogSyntaxAddShips, @"----- WARNING: in %@, can't add %i ships -- no ship added.", CurrentScriptDesc(), number); return; } OOLog(kOOLogNoteAddShips, @"DEBUG: Going to add %d ship(s) with role '%@' within %.2f radius about point (%.3f, %.3f, %.3f) using system %@", number, roleString, r, x, y, z, systemString); if (![UNIVERSE addShips:number withRole: roleString nearPosition: posn withCoordinateSystem: systemString withinRadius: r]) { OOLog(kOOLogScriptAddShipsFailed, @"***** SCRIPT ERROR :in %@, %@ could not add %u ships with role \"%@\"", CurrentScriptDesc(), @"addShipsWithinRadius:", number, roleString); } } - (void) spawnShip:(NSString *)ship_key { if ([UNIVERSE spawnShip:ship_key]) { OOLog(kOOLogNoteAddShips, @"DEBUG: Spawned ship with shipdata key '%@'.", ship_key); } else { OOLog(kOOLogScriptAddShipsFailed, @"***** SCRIPT ERROR: in %@, could not spawn ship with shipdata key '%@'.", CurrentScriptDesc(), ship_key); } } - (void) set:(NSString *)missionvariable_value { NSMutableArray *tokens = ScanTokensFromString(missionvariable_value); NSString *missionVariableString = nil; NSString *valueString = nil; BOOL hasMissionPrefix, hasLocalPrefix; if ([tokens count] < 2) { OOLog(kOOLogSyntaxSet, @"***** SCRIPT ERROR: in %@, CANNOT SET '%@' (expected mission_variable or local_variable followed by value expression)", CurrentScriptDesc(), missionvariable_value); return; } missionVariableString = [tokens objectAtIndex:0]; [tokens removeObjectAtIndex:0]; valueString = [tokens componentsJoinedByString:@" "]; hasMissionPrefix = [missionVariableString hasPrefix:@"mission_"]; hasLocalPrefix = [missionVariableString hasPrefix:@"local_"]; if (!hasMissionPrefix && !hasLocalPrefix) { OOLog(kOOLogSyntaxSet, @"***** SCRIPT ERROR: in %@, IDENTIFIER '%@' DOES NOT BEGIN WITH 'mission_' or 'local_'", CurrentScriptDesc(), missionVariableString); return; } OOLog(kOOLogNoteSet, @"DEBUG: script %@ is set to %@", missionVariableString, valueString); if (hasMissionPrefix) { [self setMissionVariable:valueString forKey:missionVariableString]; } else { [self setLocalVariable:valueString forKey:missionVariableString andMission:sCurrentMissionKey]; } } - (void) reset:(NSString *)missionvariable { NSString* missionVariableString = [missionvariable stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; BOOL hasMissionPrefix, hasLocalPrefix; hasMissionPrefix = [missionVariableString hasPrefix:@"mission_"]; hasLocalPrefix = [missionVariableString hasPrefix:@"local_"]; if (hasMissionPrefix) { [self setMissionVariable:nil forKey:missionVariableString]; } else if (hasLocalPrefix) { [self setLocalVariable:nil forKey:missionVariableString andMission:sCurrentMissionKey]; } else { OOLog(kOOLogSyntaxReset, @"***** SCRIPT ERROR: in %@, IDENTIFIER '%@' DOES NOT BEGIN WITH 'mission_' or 'local_'", CurrentScriptDesc(), missionVariableString); } } - (void) increment:(NSString *)missionVariableString { BOOL hasMissionPrefix, hasLocalPrefix; int value = 0; hasMissionPrefix = [missionVariableString hasPrefix:@"mission_"]; hasLocalPrefix = [missionVariableString hasPrefix:@"local_"]; if (hasMissionPrefix) { value = [[self missionVariableForKey:missionVariableString] intValue]; value++; [self setMissionVariable:[NSString stringWithFormat:@"%d", value] forKey:missionVariableString]; } else if (hasLocalPrefix) { value = [[self localVariableForKey:missionVariableString andMission:sCurrentMissionKey] intValue]; value++; [self setLocalVariable:[NSString stringWithFormat:@"%d", value] forKey:missionVariableString andMission:sCurrentMissionKey]; } else { OOLog(kOOLogSyntaxIncrement, @"***** SCRIPT ERROR: in %@, IDENTIFIER '%@' DOES NOT BEGIN WITH 'mission_' or 'local_'", CurrentScriptDesc(), missionVariableString); } } - (void) decrement:(NSString *)missionVariableString { BOOL hasMissionPrefix, hasLocalPrefix; int value = 0; hasMissionPrefix = [missionVariableString hasPrefix:@"mission_"]; hasLocalPrefix = [missionVariableString hasPrefix:@"local_"]; if (hasMissionPrefix) { value = [[self missionVariableForKey:missionVariableString] intValue]; value--; [self setMissionVariable:[NSString stringWithFormat:@"%d", value] forKey:missionVariableString]; } else if (hasLocalPrefix) { value = [[self localVariableForKey:missionVariableString andMission:sCurrentMissionKey] intValue]; value--; [self setLocalVariable:[NSString stringWithFormat:@"%d", value] forKey:missionVariableString andMission:sCurrentMissionKey]; } else { OOLog(kOOLogSyntaxDecrement, @"***** SCRIPT ERROR: in %@, IDENTIFIER '%@' DOES NOT BEGIN WITH 'mission_' or 'local_'", CurrentScriptDesc(), missionVariableString); } } - (void) add:(NSString *)missionVariableString_value { NSString* missionVariableString = nil; NSString* valueString; double value; NSMutableArray* tokens = ScanTokensFromString(missionVariableString_value); BOOL hasMissionPrefix, hasLocalPrefix; if ([tokens count] < 2) { OOLog(kOOLogSyntaxAdd, @"***** SCRIPT ERROR: in %@, CANNOT ADD: '%@'", CurrentScriptDesc(), missionVariableString_value); return; } missionVariableString = [tokens objectAtIndex:0]; [tokens removeObjectAtIndex:0]; valueString = [tokens componentsJoinedByString:@" "]; hasMissionPrefix = [missionVariableString hasPrefix:@"mission_"]; hasLocalPrefix = [missionVariableString hasPrefix:@"local_"]; if (hasMissionPrefix) { value = [[self missionVariableForKey:missionVariableString] doubleValue]; value += [valueString doubleValue]; [self setMissionVariable:[NSString stringWithFormat:@"%f", value] forKey:missionVariableString]; } else if (hasLocalPrefix) { value = [[self localVariableForKey:missionVariableString andMission:sCurrentMissionKey] doubleValue]; value += [valueString doubleValue]; [self setLocalVariable:[NSString stringWithFormat:@"%f", value] forKey:missionVariableString andMission:sCurrentMissionKey]; } else { OOLog(kOOLogSyntaxAdd, @"***** SCRIPT ERROR: in %@, CANNOT ADD: '%@' -- IDENTIFIER '%@' DOES NOT BEGIN WITH 'mission_' or 'local_'", CurrentScriptDesc(), missionVariableString_value, missionVariableString_value); } } - (void) subtract:(NSString *)missionVariableString_value { NSString* missionVariableString = nil; NSString* valueString; double value; NSMutableArray* tokens = ScanTokensFromString(missionVariableString_value); BOOL hasMissionPrefix, hasLocalPrefix; if ([tokens count] < 2) { OOLog(kOOLogSyntaxSubtract, @"***** SCRIPT ERROR: in %@, CANNOT SUBTRACT: '%@'", CurrentScriptDesc(), missionVariableString_value); return; } missionVariableString = [tokens objectAtIndex:0]; [tokens removeObjectAtIndex:0]; valueString = [tokens componentsJoinedByString:@" "]; hasMissionPrefix = [missionVariableString hasPrefix:@"mission_"]; hasLocalPrefix = [missionVariableString hasPrefix:@"local_"]; if (hasMissionPrefix) { value = [[self missionVariableForKey:missionVariableString] doubleValue]; value -= [valueString doubleValue]; [self setMissionVariable:[NSString stringWithFormat:@"%f", value] forKey:missionVariableString]; } else if (hasLocalPrefix) { value = [[self localVariableForKey:missionVariableString andMission:sCurrentMissionKey] doubleValue]; value -= [valueString doubleValue]; [self setLocalVariable:[NSString stringWithFormat:@"%f", value] forKey:missionVariableString andMission:sCurrentMissionKey]; } else { OOLog(kOOLogSyntaxSubtract, @"***** SCRIPT ERROR: in %@, CANNOT SUBTRACT: '%@' -- IDENTIFIER '%@' DOES NOT BEGIN WITH 'mission_' or 'local_'", CurrentScriptDesc(), missionVariableString_value, missionVariableString_value); } } - (void) checkForShips:(NSString *)roleString { shipsFound = [UNIVERSE countShipsWithPrimaryRole:roleString]; } - (void) resetScriptTimer { script_time = 0.0; script_time_check = SCRIPT_TIMER_INTERVAL; script_time_interval = SCRIPT_TIMER_INTERVAL; } - (void) addMissionText: (NSString *)textKey { NSString *text = nil; if ([textKey isEqualToString:lastTextKey]) return; // don't repeatedly add the same text [lastTextKey release]; lastTextKey = [textKey copy]; // Replace literal \n in strings with line breaks and perform expansions. text = [[UNIVERSE missiontext] oo_stringForKey:textKey]; if (text == nil) return; text = OOExpandWithOptions(OOStringExpanderDefaultRandomSeed(), kOOExpandBackslashN, text); text = [self replaceVariablesInString:text]; [self addLiteralMissionText:text]; } - (void) addLiteralMissionText:(NSString *)text { if (text != nil) { GuiDisplayGen *gui = [UNIVERSE gui]; NSString *para = nil; foreach (para, [text componentsSeparatedByString:@"\n"]) { missionTextRow = [gui addLongText:para startingAtRow:missionTextRow align:GUI_ALIGN_LEFT]; } } } - (void) setMissionChoiceByTextEntry:(BOOL)enable { MyOpenGLView *gameView = [UNIVERSE gameView]; _missionTextEntry = enable; [gameView resetTypedString]; } - (void) setMissionChoices:(NSString *)choicesKey // choicesKey is a key for a dictionary of { // choices/choice phrases in missiontext.plist and also.. NSDictionary *choicesDict = [[UNIVERSE missiontext] oo_dictionaryForKey:choicesKey]; if ([choicesDict count] == 0) { return; } [self setMissionChoicesDictionary:choicesDict]; } - (void) setMissionChoicesDictionary:(NSDictionary *)choicesDict { unsigned i; bool keysOK = true; GuiDisplayGen* gui = [UNIVERSE gui]; // TODO: MORE STUFF HERE // // What it does now: // find list of choices in missiontext.plist // add them to gui setting the key for each line to the key in the dict of choices // and the text of the line to the value in the dict of choices // and also set the selectable range // ++ change the mission screen's response to wait for a choice // and only if the selectable range is not present ask: // Press Space Commander... // NSUInteger end_row = 21; if ([[self hud] allowBigGui]) { end_row = 27; } NSArray *choiceKeys = [choicesDict allKeys]; /* Guard against potential for numeric keys in dictionary, which * would cause an unhandled exception in the sorter. See * OOJavaScriptEngine::OOJSDictionaryFromJSObject for further * thoughts. - CIM 15/2/13 */ for (i=0; i < [choiceKeys count]; i++) { if (![[choiceKeys objectAtIndex:i] isKindOfClass:[NSString class]]) { OOLog(@"test.script.error",@"Choices list in mission screen has non-string value %@",[choiceKeys objectAtIndex:i]); keysOK = false; } } if (keysOK) { // only try this if they're all strings choiceKeys = [choiceKeys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; } [gui setText:@"" forRow:end_row]; // clears out the 'Press spacebar' message [gui setKey:@"" forRow:end_row]; // clears the key to enable pollDemoControls to check for a selection [gui setSelectableRange:NSMakeRange(0,0)]; // clears the selectable range [UNIVERSE enterGUIViewModeWithMouseInteraction:YES]; // enables mouse selection of the choices list items OOGUIRow choicesRow = (end_row+1) - [choiceKeys count]; NSEnumerator *choiceEnum = nil; NSString *choiceKey = nil; id choiceValue = nil; NSString *choiceText = nil; BOOL selectableRowExists = NO; NSUInteger firstSelectableRow = end_row; for (choiceEnum = [choiceKeys objectEnumerator]; (choiceKey = [choiceEnum nextObject]); ) { choiceValue = [choicesDict objectForKey:choiceKey]; OOGUIAlignment alignment = GUI_ALIGN_CENTER; OOColor *rowColor = [OOColor yellowColor]; BOOL selectable = YES; if ([choiceValue isKindOfClass:[NSString class]]) { choiceText = [NSString stringWithFormat:@" %@ ",(NSString*)choiceValue]; } else if ([choiceValue isKindOfClass:[NSDictionary class]]) { NSDictionary *choiceOpts = (NSDictionary*)choiceValue; choiceText = [NSString stringWithFormat:@" %@ ",[choiceOpts oo_stringForKey:@"text"]]; NSString *alignmentChoice = [choiceOpts oo_stringForKey:@"alignment" defaultValue:@"CENTER"]; if ([alignmentChoice isEqualToString:@"LEFT"]) { alignment = GUI_ALIGN_LEFT; } else if ([alignmentChoice isEqualToString:@"RIGHT"]) { alignment = GUI_ALIGN_RIGHT; } id colorDesc = [choiceOpts objectForKey:@"color"]; if ([choiceOpts oo_boolForKey:@"unselectable"]) { selectable = NO; } if (colorDesc != nil) { rowColor = [OOColor colorWithDescription:colorDesc]; } else if (!selectable) // different default { rowColor = [OOColor darkGrayColor]; } } else { continue; // invalid type } choiceText = OOExpand(choiceText); choiceText = [self replaceVariablesInString:choiceText]; // allow blank rows if (![choiceText isEqualToString:@" "]) { [gui setText:choiceText forRow:choicesRow align: alignment]; if (selectable) { [gui setKey:choiceKey forRow:choicesRow]; } else { [gui setKey:GUI_KEY_SKIP forRow:choicesRow]; } [gui setColor:rowColor forRow:choicesRow]; if (selectable && !selectableRowExists) { selectableRowExists = YES; firstSelectableRow = choicesRow; } } else { [gui setKey:GUI_KEY_SKIP forRow:choicesRow]; } choicesRow++; } if (!selectableRowExists) { // just in case choices are set but they're all blank. [gui setText:@" " forRow:end_row align: GUI_ALIGN_CENTER]; [gui setKey:@"" forRow:end_row]; [gui setColor:[OOColor yellowColor] forRow:end_row]; } [gui setSelectableRange:NSMakeRange((end_row+1) - [choiceKeys count], [choiceKeys count])]; [gui setSelectedRow: firstSelectableRow]; [self resetMissionChoice]; } - (void) resetMissionChoice { [self setMissionChoice:nil]; } - (void) clearMissionScreen { [self setMissionOverlayDescriptor:nil]; [self setMissionBackgroundDescriptor:nil]; [self setMissionBackgroundSpecial:nil]; [self setMissionTitle:nil]; [self setMissionMusic:nil]; [self showShipModel:nil]; } - (void) addMissionDestination:(NSString *)destinations { unsigned j; int dest; NSMutableArray *tokens = ScanTokensFromString(destinations); for (j = 0; j < [tokens count]; j++) { dest = [tokens oo_intAtIndex:j]; if (dest < 0 || dest > 255) continue; [self addMissionDestinationMarker:[self defaultMarker:dest]]; } } - (void) removeMissionDestination:(NSString *)destinations { unsigned j; int dest; NSMutableArray *tokens = ScanTokensFromString(destinations); for (j = 0; j < [tokens count]; j++) { dest = [[tokens objectAtIndex:j] intValue]; if (dest < 0 || dest > 255) continue; [self removeMissionDestinationMarker:[self defaultMarker:dest]]; } } - (void) showShipModel:(NSString *)role { if ([role isEqualToString:@"none"] || [role length] == 0) { [UNIVERSE removeDemoShips]; return; } ShipEntity *ship = [UNIVERSE makeDemoShipWithRole:role spinning:YES]; OOLog(kOOLogNoteShowShipModel, @"::::: showShipModel:'%@' (%@) (%@)", role, ship, [ship name]); } - (void) setMissionMusic:(NSString *)value { if ([value length] == 0 || [[value lowercaseString] isEqualToString:@"none"]) { value = nil; } [[OOMusicController sharedController] setMissionMusic:value]; } - (NSString *) missionTitle { return _missionTitle; } - (void) setMissionTitle:(NSString *)value { if (_missionTitle != value) { [_missionTitle release]; _missionTitle = [value copy]; } } - (void) setMissionImage:(NSString *)value { if ([value length] != 0 && ![[value lowercaseString] isEqualToString:@"none"]) { [self setMissionOverlayDescriptor:[NSDictionary dictionaryWithObject:value forKey:@"name"]]; } else { [self setMissionOverlayDescriptor:nil]; } } - (void) setMissionBackground:(NSString *)value { if ([value length] != 0 && ![[value lowercaseString] isEqualToString:@"none"]) { [self setMissionBackgroundDescriptor:[NSDictionary dictionaryWithObject:value forKey:@"name"]]; } else { [self setMissionBackgroundDescriptor:nil]; } } - (void) setFuelLeak:(NSString *)value { if (scriptTarget != self) { [scriptTarget setFuel:0]; return; } fuel_leak_rate = [value doubleValue]; if (fuel_leak_rate > 0) { [self playFuelLeak]; [UNIVERSE addMessage:DESC(@"danger-fuel-leak") forCount:6]; OOLog(kOOLogNoteFuelLeak, @"FUEL LEAK activated!"); } } - (NSNumber *) fuelLeakRate_number { return [NSNumber numberWithFloat:[self fuelLeakRate]]; } - (void) setSunNovaIn:(NSString *)time_value { double time_until_nova = [time_value doubleValue]; [[UNIVERSE sun] setGoingNova:YES inTime: time_until_nova]; } - (void) launchFromStation { // ensure autosave is ready for the next unscripted launch if ([UNIVERSE autoSave]) [UNIVERSE setAutoSaveNow:YES]; if ([self status] == STATUS_DOCKING) [self setStatus:STATUS_DOCKED]; // needed here to prevent the normal update from continuing with docking. [self leaveDock:[self dockedStation]]; } - (void) blowUpStation { StationEntity *mainStation = nil; mainStation = [UNIVERSE station]; if (mainStation != nil) { [UNIVERSE unMagicMainStation]; [mainStation takeEnergyDamage:500000000.0 from:nil becauseOf:nil]; // 500 million should do it! } } - (void) sendAllShipsAway { if (!UNIVERSE) return; int ent_count = UNIVERSE->n_entities; Entity** uni_entities = UNIVERSE->sortedEntities; // grab the public sorted list Entity* my_entities[ent_count]; int i; for (i = 0; i < ent_count; i++) my_entities[i] = [uni_entities[i] retain]; // retained for (i = 1; i < ent_count; i++) { Entity* e1 = my_entities[i]; if ([e1 isShip]) { ShipEntity* se1 = (ShipEntity*)e1; int e_class = [e1 scanClass]; if (((e_class == CLASS_NEUTRAL)||(e_class == CLASS_POLICE)||(e_class == CLASS_MILITARY)||(e_class == CLASS_THARGOID)) && ! ([se1 isStation] && [se1 maxFlightSpeed] == 0) && // exclude only stations, not carriers. [se1 hasHyperspaceMotor]) // exclude non jumping ships. Escorts will still be able to follow a mother. { AI* se1AI = [se1 getAI]; [se1 setFuel:MAX(PLAYER_MAX_FUEL, [se1 fuelCapacity])]; [se1 setAITo:@"exitingTraderAI.plist"]; // lets them return to their previous state after the jump [se1AI setState:@"EXIT_SYSTEM"]; // The following should prevent all ships leaving at once (freezes oolite on slower machines) [se1AI setNextThinkTime:[UNIVERSE getTime] + 3 + (ranrot_rand() & 15)]; [se1 setPrimaryRole:@"oolite-none"]; // prevents new ship from appearing at witchpoint when this one leaves! } } } for (i = 0; i < ent_count; i++) { [my_entities[i] release]; // released } } - (OOPlanetEntity *) addPlanet: (NSString *)planetKey { OOLog(kOOLogNoteAddPlanet, @"addPlanet: %@", planetKey); if (!UNIVERSE) return nil; NSDictionary* dict = [[UNIVERSE systemManager] getPropertiesForSystemKey:planetKey]; if (!dict) { OOLog(@"script.error.addPlanet.keyNotFound", @"***** ERROR: could not find an entry in planetinfo.plist for '%@'", planetKey); return nil; } /*- add planet -*/ OOLog(kOOLogDebugAddPlanet, @"DEBUG: initPlanetFromDictionary: %@", dict); OOPlanetEntity *planet = [[[OOPlanetEntity alloc] initFromDictionary:dict withAtmosphere:YES andSeed:[[UNIVERSE systemManager] getRandomSeedForCurrentSystem] forSystem:system_id] autorelease]; Quaternion planetOrientation; if (ScanQuaternionFromString([dict objectForKey:@"orientation"], &planetOrientation)) { [planet setOrientation:planetOrientation]; } if (![dict objectForKey:@"position"]) { OOLog(@"script.error.addPlanet.noPosition", @"***** ERROR: you must specify a position for scripted planet '%@' before it can be created", planetKey); return nil; } NSString *positionString = [dict objectForKey:@"position"]; if([positionString hasPrefix:@"abs "] && ([UNIVERSE planet] != nil || [UNIVERSE sun] !=nil)) { 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",@"planet",planetKey); } HPVector posn = [UNIVERSE coordinatesFromCoordinateSystemString:positionString]; if (posn.x || posn.y || posn.z) { OOLog(kOOLogDebugAddPlanet, @"planet position (%.2f %.2f %.2f) derived from %@", posn.x, posn.y, posn.z, positionString); } else { ScanHPVectorFromString(positionString, &posn); OOLog(kOOLogDebugAddPlanet, @"planet position (%.2f %.2f %.2f) derived from %@", posn.x, posn.y, posn.z, positionString); } [planet setPosition: posn]; [UNIVERSE addEntity:planet]; return planet; } - (OOPlanetEntity *) addMoon: (NSString *)moonKey { OOLog(kOOLogNoteAddPlanet, @"DEBUG: addMoon '%@'", moonKey); if (!UNIVERSE) return nil; NSDictionary* dict = [[UNIVERSE systemManager] getPropertiesForSystemKey:moonKey]; if (!dict) { OOLog(@"script.error.addPlanet.keyNotFound", @"***** ERROR: could not find an entry in planetinfo.plist for '%@'", moonKey); return nil; } OOLog(kOOLogDebugAddPlanet, @"DEBUG: initMoonFromDictionary: %@", dict); OOPlanetEntity *planet = [[[OOPlanetEntity alloc] initFromDictionary:dict withAtmosphere:NO andSeed:[[UNIVERSE systemManager] getRandomSeedForCurrentSystem] forSystem:system_id] autorelease]; Quaternion planetOrientation; if (ScanQuaternionFromString([dict objectForKey:@"orientation"], &planetOrientation)) { [planet setOrientation:planetOrientation]; } if (![dict objectForKey:@"position"]) { OOLog(@"script.error.addPlanet.noPosition", @"***** ERROR: you must specify a position for scripted moon '%@' before it can be created", moonKey); return nil; } NSString *positionString = [dict objectForKey:@"position"]; if([positionString hasPrefix:@"abs "] && ([UNIVERSE planet] != nil || [UNIVERSE sun] !=nil)) { 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",@"moon",moonKey); } HPVector posn = [UNIVERSE coordinatesFromCoordinateSystemString:positionString]; if (posn.x || posn.y || posn.z) { OOLog(kOOLogDebugAddPlanet, @"moon position (%.2f %.2f %.2f) derived from %@", posn.x, posn.y, posn.z, positionString); } else { ScanHPVectorFromString(positionString, &posn); OOLog(kOOLogDebugAddPlanet, @"moon position (%.2f %.2f %.2f) derived from %@", posn.x, posn.y, posn.z, positionString); } [planet setPosition: posn]; [UNIVERSE addEntity:planet]; return planet; } - (void) debugOn { OOLogSetDisplayMessagesInClass(kOOLogDebugOnMetaClass, YES); OOLog(kOOLogDebugOnOff, @"SCRIPT debug messages ON"); } - (void) debugOff { OOLog(kOOLogDebugOnOff, @"SCRIPT debug messages OFF"); OOLogSetDisplayMessagesInClass(kOOLogDebugOnMetaClass, NO); } - (void) debugMessage:(NSString *)args { OOLog(kOOLogDebugMessage, @"SCRIPT debugMessage: %@", args); } - (void) playSound:(NSString *) soundName { [self playLegacyScriptSound:soundName]; } /*-----------------------------------------------------*/ - (void) doMissionCallback { // make sure we don't call the same callback twice _missionWithCallback = NO; [[OOJavaScriptEngine sharedEngine] runMissionCallback]; } - (void) clearMissionScreenID { [_missionScreenID release]; _missionScreenID = nil; } - (void) setMissionScreenID:(NSString *)msid { _missionScreenID = [msid retain]; } - (NSString *) missionScreenID { return _missionScreenID; } - (void) endMissionScreenAndNoteOpportunity { _missionAllowInterrupt = NO; [self clearMissionScreenID]; // Older scripts might intercept missionScreenEnded first, and call secondary mission screens. if(![self doWorldEventUntilMissionScreen:OOJSID("missionScreenEnded")]) { // if we're here, no mission screen is running. Opportunity! :) [self doWorldEventUntilMissionScreen:OOJSID("missionScreenOpportunity")]; } } - (void) setGuiToMissionScreen { // reset special background as legacy scripts can't use it, and this // is only called by legacy scripts [self setMissionBackgroundSpecial:nil]; // likewise exit screen target [self setMissionExitScreen:GUI_SCREEN_STATUS]; [self setGuiToMissionScreenWithCallback:NO]; } - (void) refreshMissionScreenTextEntry { MyOpenGLView *gameView = [UNIVERSE gameView]; GuiDisplayGen *gui = [UNIVERSE gui]; NSUInteger end_row = 21; if ([[self hud] allowBigGui]) { end_row = 27; } [gui setText:[NSString stringWithFormat:DESC(@"mission-screen-text-prompt-@"), [gameView typedString]] forRow:end_row align:GUI_ALIGN_LEFT]; [gui setColor:[OOColor cyanColor] forRow:end_row]; [gui setShowTextCursor:YES]; [gui setCurrentRow:end_row]; } - (void) setGuiToMissionScreenWithCallback:(BOOL) callback { GuiDisplayGen *gui = [UNIVERSE gui]; OOGUIScreenID oldScreen = gui_screen; NSUInteger end_row = 21; if ([[self hud] allowBigGui]) { end_row = 27; } // GUI stuff { [gui clear]; [gui setTitle:[self missionTitle] ?: DESC(@"mission-information")]; if (!_missionTextEntry) { [gui setText:DESC(@"press-space-commander") forRow:end_row align:GUI_ALIGN_CENTER]; [gui setColor:[OOColor yellowColor] forRow:end_row]; [gui setKey:@"spacebar" forRow:end_row]; [gui setShowTextCursor:NO]; } else { [self refreshMissionScreenTextEntry]; } [gui setSelectableRange:NSMakeRange(0,0)]; [gui setForegroundTextureDescriptor:[self missionOverlayDescriptorOrDefault]]; NSDictionary *background_desc = [self missionBackgroundDescriptorOrDefault]; [gui setBackgroundTextureDescriptor:background_desc]; // must set special second as setting the descriptor resets it BOOL overridden = ([self missionBackgroundDescriptor] != nil); [gui setBackgroundTextureSpecial:[self missionBackgroundSpecial] withBackground:!overridden]; } /* ends */ missionTextRow = 1; if (gui) gui_screen = GUI_SCREEN_MISSION; if (lastTextKey) { [lastTextKey release]; lastTextKey = nil; } [[OOMusicController sharedController] playMissionMusic]; // the following are necessary... [UNIVERSE enterGUIViewModeWithMouseInteraction:NO]; _missionWithCallback = callback; _missionAllowInterrupt = NO; [self noteGUIDidChangeFrom:oldScreen to:gui_screen]; } - (void) setBackgroundFromDescriptionsKey:(NSString*) d_key { NSArray * items = (NSArray *)[[UNIVERSE descriptions] objectForKey:d_key]; // if (!items) return; // [self addScene: items atOffset: kZeroVector]; // [self setShowDemoShips: YES]; } - (void) addScene:(NSArray *)items atOffset:(Vector)off { unsigned i; if (items == nil) return; for (i = 0; i < [items count]; i++) { id item = [items objectAtIndex:i]; if ([item isKindOfClass:[NSString class]]) { [self processSceneString:item atOffset: off]; } else if ([item isKindOfClass:[NSArray class]]) { [self addScene:item atOffset: off]; } else if ([item isKindOfClass:[NSDictionary class]]) { [self processSceneDictionary:item atOffset: off]; } } } - (BOOL) processSceneDictionary:(NSDictionary *) couplet atOffset:(Vector) off { NSArray *conditions = [couplet objectForKey:@"conditions"]; NSArray *actions = nil; if ([couplet objectForKey:@"do"]) actions = [NSArray arrayWithObject: [couplet objectForKey:@"do"]]; NSArray *else_actions = nil; if ([couplet objectForKey:@"else"]) else_actions = [NSArray arrayWithObject: [couplet objectForKey:@"else"]]; BOOL success = YES; if (conditions == nil) { OOLog(@"script.scene.couplet.badConditions", @"***** SCENE ERROR: %@ - conditions not %@, returning %@.", [couplet description], @" found",@"YES and performing 'do' actions"); } else { if (![conditions isKindOfClass:[NSArray class]]) { OOLog(@"script.scene.couplet.badConditions", @"***** SCENE ERROR: %@ - conditions not %@, returning %@.", [conditions description], @"an array",@"NO"); return NO; } } // check conditions.. success = TestScriptConditions(OOSanitizeLegacyScriptConditions(conditions, @"")); // perform successful actions... if ((success) && (actions) && [actions count]) [self addScene: actions atOffset: off]; // perform unsuccessful actions if ((!success) && (else_actions) && [else_actions count]) [self addScene: else_actions atOffset: off]; return success; } - (BOOL) processSceneString:(NSString*) item atOffset:(Vector) off { Vector model_p0; Quaternion model_q; if (!item) return NO; NSArray * i_info = ScanTokensFromString(item); if (!i_info) return NO; NSString* i_key = [(NSString*)[i_info objectAtIndex:0] lowercaseString]; OOLog(kOOLogNoteProcessSceneString, @"..... processing %@ (%@)", i_info, i_key); // // recursively add further scenes: // if ([i_key isEqualToString:@"scene"]) { if ([i_info count] != 5) // must be scene_key_x_y_z return NO; // 0.... 1.. 2 3 4 NSString* scene_key = (NSString*)[i_info objectAtIndex: 1]; Vector scene_offset = {0}; ScanVectorFromString([[i_info subarrayWithRange:NSMakeRange(2, 3)] componentsJoinedByString:@" "], &scene_offset); scene_offset.x += off.x; scene_offset.y += off.y; scene_offset.z += off.z; NSArray * scene_items = (NSArray *)[[UNIVERSE descriptions] objectForKey:scene_key]; OOLog(kOOLogDebugProcessSceneStringAddScene, @"::::: adding scene: '%@'", scene_key); // if (scene_items) { [self addScene: scene_items atOffset: scene_offset]; return YES; } else return NO; } // // Add ship models: // if ([i_key isEqualToString:@"ship"]||[i_key isEqualToString:@"model"]||[i_key isEqualToString:@"role"]) { if ([i_info count] != 10) // must be item_name_x_y_z_W_X_Y_Z_align { return NO; // 0... 1... 2 3 4 5 6 7 8 9.... } ShipEntity* ship = nil; if ([i_key isEqualToString:@"ship"]||[i_key isEqualToString:@"model"]) { ship = [UNIVERSE newShipWithName:[i_info oo_stringAtIndex: 1]]; } else if ([i_key isEqualToString:@"role"]) { ship = [UNIVERSE newShipWithRole:[i_info oo_stringAtIndex: 1]]; } if (!ship) return NO; ScanVectorAndQuaternionFromString([[i_info subarrayWithRange:NSMakeRange(2, 7)] componentsJoinedByString:@" "], &model_p0, &model_q); Vector model_offset = positionOffsetForShipInRotationToAlignment(ship, model_q, [i_info oo_stringAtIndex:9]); model_p0 = vector_add(model_p0, vector_subtract(off, model_offset)); OOLog(kOOLogDebugProcessSceneStringAddModel, @"::::: adding model to scene:'%@'", ship); [ship setOrientation: model_q]; [ship setPosition: vectorToHPVector(model_p0)]; [UNIVERSE setMainLightPosition:(Vector){ DEMO_LIGHT_POSITION }]; // set light origin [ship setScanClass: CLASS_NO_DRAW]; [ship switchAITo: @"nullAI.plist"]; [UNIVERSE addEntity: ship]; // STATUS_IN_FLIGHT, AI state GLOBAL [ship setStatus: STATUS_COCKPIT_DISPLAY]; [ship setRoll: 0.0]; [ship setPitch: 0.0]; [ship setVelocity: kZeroVector]; [ship setBehaviour: BEHAVIOUR_STOP_STILL]; [ship release]; return YES; } // // Add player ship model: // if ([i_key isEqualToString:@"player"]) { if ([i_info count] != 9) // must be player_x_y_z_W_X_Y_Z_align return NO; // 0..... 1 2 3 4 5 6 7 8.... ShipEntity* doppelganger = [UNIVERSE newShipWithName:[self shipDataKey]]; // retain count = 1 if (!doppelganger) return NO; ScanVectorAndQuaternionFromString([[i_info subarrayWithRange:NSMakeRange( 1, 7)] componentsJoinedByString:@" "], &model_p0, &model_q); Vector model_offset = positionOffsetForShipInRotationToAlignment( doppelganger, model_q, (NSString*)[i_info objectAtIndex:8]); model_p0.x += off.x - model_offset.x; model_p0.y += off.y - model_offset.y; model_p0.z += off.z - model_offset.z; OOLog(kOOLogDebugProcessSceneStringAddModel, @"::::: adding model to scene:'%@'", doppelganger); [doppelganger setOrientation: model_q]; [doppelganger setPosition: vectorToHPVector(model_p0)]; [UNIVERSE setMainLightPosition:(Vector){ DEMO_LIGHT_POSITION }]; // set light origin [doppelganger setScanClass: CLASS_NO_DRAW]; [doppelganger switchAITo: @"nullAI.plist"]; [UNIVERSE addEntity: doppelganger]; [doppelganger setStatus: STATUS_COCKPIT_DISPLAY]; [doppelganger setRoll: 0.0]; [doppelganger setPitch: 0.0]; [doppelganger setVelocity: kZeroVector]; [doppelganger setBehaviour: BEHAVIOUR_STOP_STILL]; [doppelganger release]; return YES; } // // Add planet model: selected via gui-scene-show-planet/-local-planet // if ([i_key isEqualToString:@"local-planet"] || [i_key isEqualToString:@"target-planet"]) { if ([i_info count] != 4) // must be xxxxx-planet_x_y_z return NO; // 0........... 1 2 3 // sunlight position for F7 screen is chosen pseudo randomly from 4 different positions. if (target_system_id & 8) { _sysInfoLight = (target_system_id & 2) ? (Vector){ -10000.0, 4000.0, -10000.0 } : (Vector){ -12000.0, -5000.0, -10000.0 }; } else { _sysInfoLight = (target_system_id & 2) ? (Vector){ 6000.0, -5000.0, -10000.0 } : (Vector){ 6000.0, 4000.0, -10000.0 }; } [UNIVERSE setMainLightPosition:_sysInfoLight]; // set light origin #if NEW_PLANETS OOPlanetEntity *originalPlanet = nil; if ([i_key isEqualToString:@"local-planet"]) { originalPlanet = [UNIVERSE planet]; } else { originalPlanet = [[[OOPlanetEntity alloc] initAsMainPlanetForSystem:target_system_id] autorelease]; } OOPlanetEntity *doppelganger = [originalPlanet miniatureVersion]; if (doppelganger == nil) return NO; #else OOPlanetEntity* doppelganger = nil; NSMutableDictionary *planetInfo = [NSMutableDictionary dictionaryWithDictionary:[UNIVERSE generateSystemData:target_system_seed]]; if ([i_key isEqualToString:@"local-planet"] && [UNIVERSE sun]) { OOPlanetEntity *mainPlanet = [UNIVERSE planet]; OOTexture *texture = [mainPlanet texture]; if (texture != nil) { [planetInfo setObject:texture forKey:@"_oo_textureObject"]; [planetInfo oo_setBool:[mainPlanet isExplicitlyTextured] forKey:@"_oo_isExplicitlyTextured"]; [planetInfo oo_setBool:YES forKey:@"mainForLocalSystem"]; //[planetInfo oo_setQuaternion:[mainPlanet orientation] forKey:@"orientation"]; // the orientation is overwritten later on, without regard for the real planet's orientation. } } doppelganger = [[OOPlanetEntity alloc] initFromDictionary:planetInfo withAtmosphere:YES andSeed:target_system_seed]; [doppelganger miniaturize]; [doppelganger autorelease]; if (doppelganger == nil) return NO; #endif ScanVectorFromString([[i_info subarrayWithRange:NSMakeRange(1, 3)] componentsJoinedByString:@" "], &model_p0); // miniature radii are roughly between 60 and 120. Place miniatures with a radius bigger than 60 a bit futher away. model_p0 = vector_multiply_scalar(model_p0, 1 - 0.5 * ((60 - [doppelganger radius]) / 60)); model_p0 = vector_add(model_p0, off); // TODO: find better quaternion values. #if NEW_PLANETS //Quaternion model_q = { 0.83, 0.365148, 0.182574, 0.0 }; // shows new planets' north pole. //Quaternion model_q = { 0.83, -0.365148, 0.182574, 0.0 }; // shows new planets' south pole. Quaternion model_q = { 0.83, 0.12, 0.44, 0.0 }; // new planets - default orientation. #else //model_q = make_quaternion( M_SQRT1_2, 0.314, M_SQRT1_2, 0.0 ); Quaternion model_q = { 0.833492, 0.333396, 0.440611, 0.0 }; #endif OOLog(kOOLogDebugProcessSceneStringAddMiniPlanet, @"::::: adding %@ to scene:'%@'", i_key, doppelganger); [doppelganger setOrientation: model_q]; // HPVect: mission screen coordinates are small enough that we don't need high-precision for calculations [doppelganger setPosition: vectorToHPVector(model_p0)]; /* MKW - add rotation based on current time * - necessary to duplicate the rotation already performed in PlanetEntity.m since we reset the orientation above. */ int deltaT = floor(fmod([self clockTimeAdjusted], 86400)); [doppelganger update: deltaT]; [UNIVERSE addEntity:doppelganger]; return YES; } return NO; } - (BOOL) addEqScriptForKey:(NSString *)eq_key { if (eq_key == nil) return NO; NSString *scriptName = [[OOEquipmentType equipmentTypeWithIdentifier:eq_key] scriptName]; OOLog(@"player.equipmentScript", @"Added equipment %@, with the following script property: '%@'.", eq_key, scriptName); if (scriptName == nil) return NO; NSMutableDictionary *properties = [NSMutableDictionary dictionary]; // no duplicates! NSArray *eqScript = nil; foreach (eqScript, eqScripts) { NSString *key = [eqScript oo_stringAtIndex:0]; if ([key isEqualToString: eq_key]) return NO; } [properties setObject:self forKey:@"ship"]; [properties setObject:eq_key forKey:@"equipmentKey"]; OOScript *s = [OOScript jsScriptFromFileNamed:scriptName properties:properties]; if (s == nil) return NO; OOLog(@"player.equipmentScript", @"Script '%@': installation %@successful.", scriptName,(s == nil ? @"un" : @"")); [eqScripts addObject:[NSArray arrayWithObjects:eq_key,s,nil]]; if (primedEquipment == [eqScripts count] - 1) primedEquipment++; // if primed-none, keep it as primed-none. OOLog(@"player.equipmentScript", @"Scriptable equipment available: %lu.", [eqScripts count]); return YES; } - (void) removeEqScriptForKey:(NSString *)eq_key { if (eq_key == nil) return; NSString *key = nil; NSUInteger i, count = [eqScripts count]; for (i = 0; i < count; i++) { key = [[eqScripts oo_arrayAtIndex:i] oo_stringAtIndex:0]; if ([key isEqualToString: eq_key]) { [eqScripts removeObjectAtIndex:i]; if (i == primedEquipment) primedEquipment = count; // primed-none else if (i < primedEquipment) primedEquipment--; // track the primed equipment if (count == primedEquipment) primedEquipment--; // the array has shrunk by one! OOLog(@"player.equipmentScript", @"Removed equipment %@, with the following script property: '%@'.", eq_key, [[OOEquipmentType equipmentTypeWithIdentifier:eq_key] scriptName]); } } } - (NSUInteger) eqScriptIndexForKey:(NSString *)eq_key { NSUInteger i, count = [eqScripts count]; if (eq_key != nil) { for (i = 0; i < count; i++) { NSString *key = [[eqScripts oo_arrayAtIndex:i] oo_stringAtIndex:0]; if ([key isEqualToString: eq_key]) return i; } } return count; } - (void) targetNearestHostile { [self scanForHostiles]; Entity *ent = [self foundTarget]; if (ent != nil) { ident_engaged = YES; missile_status = MISSILE_STATUS_TARGET_LOCKED; [self addTarget:ent]; } } - (void) targetNearestIncomingMissile { [self scanForNearestIncomingMissile]; Entity *ent = [self foundTarget]; if (ent != nil) { ident_engaged = YES; missile_status = MISSILE_STATUS_TARGET_LOCKED; [self addTarget:ent]; } } - (void) setGalacticHyperspaceBehaviourTo:(NSString *)galacticHyperspaceBehaviourString { OOGalacticHyperspaceBehaviour ghBehaviour = OOGalacticHyperspaceBehaviourFromString(galacticHyperspaceBehaviourString); if (ghBehaviour == GALACTIC_HYPERSPACE_BEHAVIOUR_UNKNOWN) { OOLog(@"player.setGalacticHyperspaceBehaviour.invalidInput", @"setGalacticHyperspaceBehaviourTo: called with unknown behaviour %@.", galacticHyperspaceBehaviourString); } [self setGalacticHyperspaceBehaviour:ghBehaviour]; } - (void) setGalacticHyperspaceFixedCoordsTo:(NSString *)galacticHyperspaceFixedCoordsString { NSArray *coord_vals = ScanTokensFromString(galacticHyperspaceFixedCoordsString); if ([coord_vals count] < 2) // Will be 0 if string is nil { OOLog(@"player.setGalacticHyperspaceFixedCoords.invalidInput", @"setGalacticHyperspaceFixedCoords: called with bad specifier. Defaulting to Oolite standard."); galacticHyperspaceFixedCoords.x = galacticHyperspaceFixedCoords.y = 0x60; } [self setGalacticHyperspaceFixedCoordsX:[coord_vals oo_unsignedCharAtIndex:0] y:[coord_vals oo_unsignedCharAtIndex:1]]; } @end NSString *OOComparisonTypeToString(OOComparisonType type) { switch (type) { case COMPARISON_EQUAL: return @"equal"; case COMPARISON_NOTEQUAL: return @"notequal"; case COMPARISON_LESSTHAN: return @"lessthan"; case COMPARISON_GREATERTHAN: return @"greaterthan"; case COMPARISON_ONEOF: return @"oneof"; case COMPARISON_UNDEFINED: return @"undefined"; } return @""; } oolite-1.82/src/Core/Entities/PlayerEntityLoadSave.h000066400000000000000000000055351256642440500224430ustar00rootroot00000000000000/* PlayerEntityLoadSave.h Created for the Oolite-Linux project (but is portable) LoadSave has been separated out into a separate category because PlayerEntity.m has gotten far too big and is in danger of becoming the whole general mish mash. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "PlayerEntity.h" #import "GuiDisplayGen.h" #import "MyOpenGLView.h" #import "Universe.h" #define EXITROW 1 #define LABELROW 2 #define BACKROW 3 #define STARTROW 4 #define ENDROW 17 #define MOREROW 17 #define NUMROWS 13 #define COLUMNS 2 #define INPUTROW 21 #define CDRDESCROW 19 #define SAVE_OVERWRITE_WARN_ROW 5 #define SAVE_OVERWRITE_YES_ROW 8 #define SAVE_OVERWRITE_NO_ROW 9 // Set to 1 to use custom load/save dialogs in windowed mode on Macs in debug builds. No effect on other platforms. #define USE_CUSTOM_LOAD_SAVE_ON_MAC_DEBUG 0 // OOLITE_USE_APPKIT_LOAD_SAVE is true if we ever want AppKit dialogs. #define OOLITE_USE_APPKIT_LOAD_SAVE (OOLITE_MAC_OS_X && !USE_CUSTOM_LOAD_SAVE_ON_MAC_DEBUG) // Mac 64-bit builds: never use custom load/save dialogs. #define OO_USE_APPKIT_LOAD_SAVE_ALWAYS (OOLITE_USE_APPKIT_LOAD_SAVE && OOLITE_64_BIT) // OO_USE_CUSTOM_LOAD_SAVE is true if we will ever want custom dialogs. #define OO_USE_CUSTOM_LOAD_SAVE (!OO_USE_APPKIT_LOAD_SAVE_ALWAYS) @interface PlayerEntity (LoadSave) - (BOOL) loadPlayer; // Returns NO on immediate failure, i.e. when using an OS X modal open panel which is cancelled. - (void) savePlayer; - (void) quicksavePlayer; - (void) autosavePlayer; - (void) setGuiToScenarioScreen:(int)page; - (void) addScenarioModel:(NSString *)shipKey; - (void) showScenarioDetails; - (BOOL) startScenario; #if OO_USE_CUSTOM_LOAD_SAVE // Interface for PlayerEntityControls - (NSString *) commanderSelector; - (void) saveCommanderInputHandler; - (void) overwriteCommanderInputHandler; #endif - (BOOL) loadPlayerFromFile:(NSString *)fileToOpen asNew:(BOOL)asNew; @end OOCreditsQuantity OODeciCreditsFromDouble(double doubleDeciCredits); /* Object is either a floating-point NSNumber or something that can be duck- typed to an integer using OOUnsignedLongLongFromObject(). */ OOCreditsQuantity OODeciCreditsFromObject(id object); oolite-1.82/src/Core/Entities/PlayerEntityLoadSave.m000066400000000000000000001231561256642440500224500ustar00rootroot00000000000000/* PlayerEntityLoadSave.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "PlayerEntityLoadSave.h" #import "PlayerEntityContracts.h" #import "PlayerEntityControls.h" #import "PlayerEntitySound.h" #import "NSFileManagerOOExtensions.h" #import "GameController.h" #import "ResourceManager.h" #import "OOStringExpander.h" #import "PlayerEntityControls.h" #import "ProxyPlayerEntity.h" #import "ShipEntityAI.h" #import "OOXMLExtensions.h" #import "OOSound.h" #import "OOColor.h" #import "OOStringParsing.h" #import "OOPListParsing.h" #import "StationEntity.h" #import "OOCollectionExtractors.h" #import "OOConstToString.h" #import "OOShipRegistry.h" #import "OOTexture.h" #import "NSStringOOExtensions.h" #import "NSNumberOOExtensions.h" #import "OOJavaScriptEngine.h" // Name of modifier key used to issue commands. See also -isCommandModifierKeyDown. #if OO_USE_CUSTOM_LOAD_SAVE #define COMMAND_MODIFIER_KEY "Ctrl" #endif static uint16_t PersonalityForCommanderDict(NSDictionary *dict); #if OO_USE_CUSTOM_LOAD_SAVE @interface MyOpenGLView (OOLoadSaveExtensions) - (BOOL)isCommandModifierKeyDown; @end #endif @interface PlayerEntity (OOLoadSavePrivate) #if OOLITE_USE_APPKIT_LOAD_SAVE - (BOOL) loadPlayerWithPanel; - (void) savePlayerWithPanel; #endif #if OO_USE_CUSTOM_LOAD_SAVE - (void) setGuiToLoadCommanderScreen; - (void) setGuiToSaveCommanderScreen: (NSString *)cdrName; - (void) setGuiToOverwriteScreen: (NSString *)cdrName; - (void) lsCommanders: (GuiDisplayGen *)gui directory: (NSString*)directory pageNumber: (int)page highlightName: (NSString *)highlightName; - (void) showCommanderShip: (int)cdrArrayIndex; - (int) findIndexOfCommander: (NSString *)cdrName; - (void) nativeSavePlayer: (NSString *)cdrName; - (BOOL) existingNativeSave: (NSString *)cdrName; #endif - (void) writePlayerToPath:(NSString *)path; @end @implementation PlayerEntity (LoadSave) - (BOOL)loadPlayer { BOOL OK = YES; #if OO_USE_APPKIT_LOAD_SAVE_ALWAYS OK = [self loadPlayerWithPanel]; #elif OOLITE_USE_APPKIT_LOAD_SAVE // OS X: use system open/save dialogs in windowed mode, custom interface in full-screen. if ([[UNIVERSE gameController] inFullScreenMode]) { [self setGuiToLoadCommanderScreen]; } else { OK = [self loadPlayerWithPanel]; } #else // Other platforms: use custom interface all the time. [self setGuiToLoadCommanderScreen]; #endif return OK; } - (void)savePlayer { #if OO_USE_APPKIT_LOAD_SAVE_ALWAYS [self savePlayerWithPanel]; #elif OOLITE_USE_APPKIT_LOAD_SAVE // OS X: use system open/save dialogs in windowed mode, custom interface in full-screen. if ([[UNIVERSE gameController] inFullScreenMode]) { [self setGuiToSaveCommanderScreen:self.lastsaveName]; } else { [self savePlayerWithPanel]; } #else // Other platforms: use custom interface all the time. [self setGuiToSaveCommanderScreen:[self lastsaveName]]; #endif } - (void) autosavePlayer { NSString *tmp_path = nil; NSString *tmp_name = nil; NSString *dir = [[UNIVERSE gameController] playerFileDirectory]; tmp_name = [self lastsaveName]; tmp_path = save_path; ShipScriptEventNoCx(self, "playerWillSaveGame", OOJSSTR("AUTO_SAVE")); NSString *saveName = [self lastsaveName]; NSString *autosaveSuffix = DESC(@"autosave-commander-suffix"); if (![saveName hasSuffix:autosaveSuffix]) { saveName = [saveName stringByAppendingString:autosaveSuffix]; } NSString *savePath = [dir stringByAppendingPathComponent:[saveName stringByAppendingString:@".oolite-save"]]; [self setLastsaveName:saveName]; @try { [self writePlayerToPath:savePath]; } @catch (id exception) { // Suppress exceptions silently. Warning the user about failed autosaves would be pretty unhelpful. } if (tmp_path != nil) { [save_path autorelease]; save_path = [tmp_path copy]; } [self setLastsaveName:tmp_name]; } - (void) quicksavePlayer { MyOpenGLView *gameView = [UNIVERSE gameView]; NSString *path = nil; path = save_path; if (!path) path = [[gameView gameController] playerFileToLoad]; if (!path) { OOLog(@"quickSave.failed.noName", @"ERROR no file name returned by [[gameView gameController] playerFileToLoad]"); [NSException raise:@"OoliteGameNotSavedException" format:@"ERROR no file name returned by [[gameView gameController] playerFileToLoad]"]; } ShipScriptEventNoCx(self, "playerWillSaveGame", OOJSSTR("QUICK_SAVE")); [self writePlayerToPath:path]; [[UNIVERSE gameView] supressKeysUntilKeyUp]; [self setGuiToStatusScreen]; } - (void) setGuiToScenarioScreen:(int)page { NSArray *scenarios = [UNIVERSE scenarios]; [UNIVERSE removeDemoShips]; // GUI stuff { GuiDisplayGen *gui = [UNIVERSE gui]; OOGUIRow start_row = GUI_ROW_SCENARIOS_START; OOGUIRow row = start_row; BOOL guiChanged = (gui_screen != GUI_SCREEN_NEWGAME); [gui clearAndKeepBackground:!guiChanged]; [gui setTitle:DESC(@"oolite-newgame-title")]; OOGUITabSettings tab_stops; tab_stops[0] = 0; tab_stops[1] = -480; [gui setTabStops:tab_stops]; unsigned n_rows = GUI_MAX_ROWS_SCENARIOS; NSUInteger i, count = [scenarios count]; NSDictionary *scenario = nil; [gui setArray:[NSArray arrayWithObjects:DESC(@"oolite-scenario-exit"), @" <----- ", nil] forRow:start_row - 2]; [gui setColor:[OOColor redColor] forRow:start_row - 2]; [gui setKey:@"exit" forRow:start_row - 2]; if (page > 0) { [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-back"), @" <-- ", nil] forRow:start_row - 1]; [gui setColor:[OOColor greenColor] forRow:start_row - 1]; [gui setKey:[NSString stringWithFormat:@"__page:%i",page-1] forRow:start_row - 1]; } [self setShowDemoShips:NO]; for (i = page*n_rows ; i < count && row < start_row + n_rows ; i++) { scenario = [[UNIVERSE scenarios] objectAtIndex:i]; NSString *scenarioName = [NSString stringWithFormat:@" %@ ",[scenario oo_stringForKey:@"name"]]; [gui setText:OOExpand(scenarioName) forRow:row]; [gui setKey:[NSString stringWithFormat:@"Scenario:%lu", (unsigned long)i] forRow:row]; ++row; } if ((page+1) * n_rows < count) { [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-more"), @" --> ", nil] forRow:row]; [gui setColor:[OOColor greenColor] forRow:row]; [gui setKey:[NSString stringWithFormat:@"__page:%i",page+1] forRow:row]; ++row; } [gui setSelectableRange:NSMakeRange(start_row - 2,3 + row - start_row)]; [gui setSelectedRow:start_row]; [self showScenarioDetails]; gui_screen = GUI_SCREEN_NEWGAME; if (guiChanged) { [gui setBackgroundTextureKey:@"newgame"]; [gui setForegroundTextureKey:@"newgame_overlay"]; } } [UNIVERSE enterGUIViewModeWithMouseInteraction:YES]; } - (void) addScenarioModel:(NSString *)shipKey { [self showShipModelWithKey:shipKey shipData:nil personality:0 factorX:1.2 factorY:0.8 factorZ:6.4 inContext:@"scenario"]; } - (void) showScenarioDetails { GuiDisplayGen* gui = [UNIVERSE gui]; NSString* key = [gui selectedRowKey]; [UNIVERSE removeDemoShips]; if ([key hasPrefix:@"Scenario"]) { int item = [[key componentsSeparatedByString:@":"] oo_intAtIndex:1]; NSDictionary *scenario = [[UNIVERSE scenarios] objectAtIndex:item]; [self setShowDemoShips:NO]; for (NSUInteger i=GUI_ROW_SCENARIOS_DETAIL;i<=27;i++) { [gui setText:@"" forRow:i]; } if (scenario) { [gui addLongText:OOExpand([scenario oo_stringForKey:@"description"]) startingAtRow:GUI_ROW_SCENARIOS_DETAIL align:GUI_ALIGN_LEFT]; NSString *shipKey = [scenario oo_stringForKey:@"model"]; if (shipKey != nil) { [self addScenarioModel:shipKey]; [self setShowDemoShips:YES]; } } } } - (BOOL) startScenario { GuiDisplayGen* gui = [UNIVERSE gui]; NSString* key = [gui selectedRowKey]; if ([key isEqualToString:@"exit"]) { // intended to return to main menu return NO; } if ([key hasPrefix:@"__page"]) { int page = [[key componentsSeparatedByString:@":"] oo_intAtIndex:1]; [self setGuiToScenarioScreen:page]; return YES; } int selection = [[key componentsSeparatedByString:@":"] oo_intAtIndex:1]; NSDictionary *scenario = [[UNIVERSE scenarios] objectAtIndex:selection]; NSString *file = [scenario oo_stringForKey:@"file" defaultValue:nil]; if (file == nil) { OOLog(@"scenario.init.error",@"No file entry found for scenario"); return NO; } NSString *path = [ResourceManager pathForFileNamed:file inFolder:@"Scenarios"]; if (path == nil) { OOLog(@"scenario.init.error",@"Game file not found for scenario %@",file); return NO; } BOOL result = [self loadPlayerFromFile:path asNew:YES]; if (!result) { return NO; } [scenarioKey release]; scenarioKey = [[scenario oo_stringForKey:@"scenario" defaultValue:nil] retain]; // don't drop the save game directory in return YES; } #if OO_USE_CUSTOM_LOAD_SAVE - (NSString *)commanderSelector { MyOpenGLView *gameView = [UNIVERSE gameView]; GuiDisplayGen *gui = [UNIVERSE gui]; NSString *dir = [[UNIVERSE gameController] playerFileDirectory]; int idx; if([self handleGUIUpDownArrowKeys]) { int guiSelectedRow=[gui selectedRow]; idx=(guiSelectedRow - STARTROW) + (currentPage * NUMROWS); if (guiSelectedRow != MOREROW && guiSelectedRow != BACKROW && guiSelectedRow != EXITROW) { [self showCommanderShip: idx]; } else { [UNIVERSE removeDemoShips]; [gui setText:@"" forRow:CDRDESCROW align:GUI_ALIGN_LEFT]; [gui setText:@"" forRow:CDRDESCROW + 1 align:GUI_ALIGN_LEFT]; [gui setText:@"" forRow:CDRDESCROW + 2 align:GUI_ALIGN_LEFT]; } } else { idx=([gui selectedRow] - STARTROW) + (currentPage * NUMROWS); } // handle page <-- and page --> keys if ([gameView isDown:gvArrowKeyLeft] && [[gui keyForRow:BACKROW] isEqual: GUI_KEY_OK]) { currentPage--; [self playMenuPagePrevious]; [self lsCommanders: gui directory: dir pageNumber: currentPage highlightName: nil]; [gameView supressKeysUntilKeyUp]; } // if ([gameView isDown:gvArrowKeyRight] && [[gui keyForRow:MOREROW] isEqual: GUI_KEY_OK]) { currentPage++; [self playMenuPageNext]; [self lsCommanders: gui directory: dir pageNumber: currentPage highlightName: nil]; [gameView supressKeysUntilKeyUp]; } // Enter pressed - find the commander name underneath. if ([gameView isDown:13]||[gameView isDown:gvMouseDoubleClick]) { NSDictionary *cdr; switch ([gui selectedRow]) { case EXITROW: if ([self status] == STATUS_START_GAME) { [self setGuiToIntroFirstGo:YES]; return nil; } break; case BACKROW: currentPage--; [self lsCommanders: gui directory: dir pageNumber: currentPage highlightName: nil]; [gameView supressKeysUntilKeyUp]; break; case MOREROW: currentPage++; [self lsCommanders: gui directory: dir pageNumber: currentPage highlightName: nil]; [gameView supressKeysUntilKeyUp]; break; default: cdr=[cdrDetailArray objectAtIndex: idx]; if ([cdr oo_boolForKey:@"isSavedGame"]) return [cdr oo_stringForKey:@"saved_game_path"]; else { if ([gameView isCommandModifierKeyDown]||[gameView isDown:gvMouseDoubleClick]) { // change directory to the selected path NSString* newDir = [cdr oo_stringForKey:@"saved_game_path"]; [[UNIVERSE gameController] setPlayerFileDirectory: newDir]; dir = newDir; currentPage = 0; [self lsCommanders: gui directory: dir pageNumber: currentPage highlightName: nil]; [gameView supressKeysUntilKeyUp]; } } } } if([gameView isDown: 27]) // escape key { [self setGuiToStatusScreen]; } return nil; } - (void) saveCommanderInputHandler { MyOpenGLView *gameView = [UNIVERSE gameView]; GuiDisplayGen *gui = [UNIVERSE gui]; NSString *dir = [[UNIVERSE gameController] playerFileDirectory]; if ([self handleGUIUpDownArrowKeys]) { int guiSelectedRow=[gui selectedRow]; int idx = (guiSelectedRow - STARTROW) + (currentPage * NUMROWS); if (guiSelectedRow != MOREROW && guiSelectedRow != BACKROW) { [self showCommanderShip: idx]; if ([(NSDictionary *)[cdrDetailArray objectAtIndex:idx] oo_boolForKey:@"isSavedGame"]) // don't show things that aren't saved games commanderNameString = [[cdrDetailArray oo_dictionaryAtIndex:idx] oo_stringForKey:@"player_save_name" defaultValue:[[cdrDetailArray oo_dictionaryAtIndex:idx] oo_stringForKey:@"player_name"]]; else commanderNameString = [gameView typedString]; } else { [UNIVERSE removeDemoShips]; [gui setText:@"" forRow:CDRDESCROW align:GUI_ALIGN_LEFT]; [gui setText:@"" forRow:CDRDESCROW + 1 align:GUI_ALIGN_LEFT]; [gui setText:@"" forRow:CDRDESCROW + 2 align:GUI_ALIGN_LEFT]; } } else { commanderNameString = [gameView typedString]; } [gameView setTypedString: commanderNameString]; [gui setText: [NSString stringWithFormat:DESC(@"savescreen-commander-name-@"), commanderNameString] forRow: INPUTROW]; [gui setColor:[OOColor cyanColor] forRow:INPUTROW]; // handle page <-- and page --> keys, and on-screen buttons if (((([gameView isDown:gvMouseDoubleClick] || [gameView isDown: 13]) && [gui selectedRow] == BACKROW) || [gameView isDown:gvArrowKeyLeft]) && [[gui keyForRow:BACKROW] isEqual: GUI_KEY_OK]) { currentPage--; [self lsCommanders: gui directory: dir pageNumber: currentPage highlightName: nil]; [gameView supressKeysUntilKeyUp]; } // if (((([gameView isDown:gvMouseDoubleClick] || [gameView isDown: 13]) && [gui selectedRow] == MOREROW) || [gameView isDown:gvArrowKeyRight]) && [[gui keyForRow:MOREROW] isEqual: GUI_KEY_OK]) { currentPage++; [self lsCommanders: gui directory: dir pageNumber: currentPage highlightName: nil]; [gameView supressKeysUntilKeyUp]; } if(([gameView isDown: 13]||[gameView isDown:gvMouseDoubleClick]) && [commanderNameString length]) { if ([gameView isCommandModifierKeyDown]||[gameView isDown:gvMouseDoubleClick]) { int guiSelectedRow=[gui selectedRow]; int idx = (guiSelectedRow - STARTROW) + (currentPage * NUMROWS); NSDictionary* cdr = [cdrDetailArray objectAtIndex:idx]; if (![cdr oo_boolForKey:@"isSavedGame"]) // don't open saved games { // change directory to the selected path NSString* newDir = [cdr oo_stringForKey:@"saved_game_path"]; [[UNIVERSE gameController] setPlayerFileDirectory: newDir]; dir = newDir; currentPage = 0; [self lsCommanders: gui directory: dir pageNumber: currentPage highlightName: nil]; [gameView supressKeysUntilKeyUp]; } } else { pollControls = YES; if ([self existingNativeSave: commanderNameString]) { [gameView supressKeysUntilKeyUp]; [self setGuiToOverwriteScreen: commanderNameString]; } else { [self nativeSavePlayer: commanderNameString]; [[UNIVERSE gameView] supressKeysUntilKeyUp]; [self setGuiToStatusScreen]; } } } if([gameView isDown: 27]) // escape key { // get out of here pollControls = YES; [[UNIVERSE gameView] resetTypedString]; [self setGuiToStatusScreen]; } } - (void) overwriteCommanderInputHandler { MyOpenGLView *gameView = [UNIVERSE gameView]; GuiDisplayGen *gui = [UNIVERSE gui]; [self handleGUIUpDownArrowKeys]; // Translation issue: we can't confidently use raw Y and N ascii as shortcuts. It's better to use the load-previous-commander keys. id valueYes = [[[UNIVERSE descriptions] oo_stringForKey:@"load-previous-commander-yes" defaultValue:@"y"] lowercaseString]; id valueNo = [[[UNIVERSE descriptions] oo_stringForKey:@"load-previous-commander-no" defaultValue:@"n"] lowercaseString]; unsigned char cYes, cNo; cYes = [valueYes characterAtIndex: 0] & 0x00ff; // Use lower byte of unichar. cNo = [valueNo characterAtIndex: 0] & 0x00ff; // Use lower byte of unichar. if (([gameView isDown:13] && ([gui selectedRow] == SAVE_OVERWRITE_YES_ROW))||[gameView isDown:cYes]||[gameView isDown:cYes - 32]) { pollControls=YES; [self nativeSavePlayer: commanderNameString]; [self playSaveOverwriteYes]; [[UNIVERSE gameView] supressKeysUntilKeyUp]; [self setGuiToStatusScreen]; } if (([gameView isDown:13] && ([gui selectedRow] == SAVE_OVERWRITE_NO_ROW))||[gameView isDown:27]||[gameView isDown:cNo]||[gameView isDown:cNo - 32]) { // esc or NO was pressed - get out of here pollControls=YES; [self playSaveOverwriteNo]; [self setGuiToSaveCommanderScreen:@""]; } } #endif - (BOOL) loadPlayerFromFile:(NSString *)fileToOpen asNew:(BOOL)asNew { /* TODO: it would probably be better to load by creating a new PlayerEntity, verifying that's OK, then replacing the global player. Actually, it'd be better to separate PlayerEntity into OOPlayer and OOPlayerShipEntity. And then move most of OOPlayerShipEntity into ShipEntity, and make NPC ships behave more like player ships. -- Ahruman */ BOOL loadedOK = YES; NSDictionary *fileDic = nil; NSString *fail_reason = nil; if (fileToOpen == nil) { fail_reason = DESC(@"loadfailed-no-file-specified"); loadedOK = NO; } if (loadedOK) { OOLog(@"load.progress",@"Reading file"); fileDic = OODictionaryFromFile(fileToOpen); if (fileDic == nil) { fail_reason = DESC(@"loadfailed-could-not-load-file"); loadedOK = NO; } } if (loadedOK) { OOLog(@"load.progress",@"Restricting scenario"); NSString *scenarioRestrict = [fileDic oo_stringForKey:@"scenario_restriction" defaultValue:nil]; if (scenarioRestrict == nil) { // older save game - use the 'strict' key instead BOOL strict = [fileDic oo_boolForKey:@"strict" defaultValue:NO]; if (strict) { scenarioRestrict = SCENARIO_OXP_DEFINITION_NONE; } else { scenarioRestrict = SCENARIO_OXP_DEFINITION_ALL; } } if (![UNIVERSE setUseAddOns:scenarioRestrict fromSaveGame:YES forceReinit:YES]) { fail_reason = DESC(@"loadfailed-saved-game-failed-to-load"); loadedOK = NO; } } if (loadedOK) { OOLog(@"load.progress",@"Creating player ship"); // Check that player ship exists NSString *shipKey = nil; NSDictionary *shipDict = nil; shipKey = [fileDic oo_stringForKey:@"ship_desc"]; shipDict = [[OOShipRegistry sharedRegistry] shipInfoForKey:shipKey]; if (shipDict == nil) { loadedOK = NO; if (shipKey != nil) fail_reason = [NSString stringWithFormat:DESC(@"loadfailed-could-not-find-ship-type-@-please-reinstall-the-appropriate-OXP"), shipKey]; else fail_reason = DESC(@"loadfailed-invalid-saved-game-no-ship-specified"); } } if (loadedOK) { OOLog(@"load.progress",@"Initialising player entity"); if (![self setUpAndConfirmOK:YES saveGame:YES]) { fail_reason = DESC(@"loadfailed-could-not-reset-javascript"); loadedOK = NO; } } if (loadedOK) { OOLog(@"load.progress",@"Loading commander data"); if (![self setCommanderDataFromDictionary:fileDic]) { // this could still be a reset js issue, if switching from strict / unrestricted // TODO: use "could not reset js message" if that's the case. fail_reason = DESC(@"loadfailed-could-not-set-up-player-ship"); loadedOK = NO; } } if (loadedOK) { OOLog(@"load.progress",@"Recording save path"); if (!asNew) { [save_path autorelease]; save_path = [fileToOpen retain]; [[[UNIVERSE gameView] gameController] setPlayerFileToLoad:fileToOpen]; [[[UNIVERSE gameView] gameController] setPlayerFileDirectory:fileToOpen]; } } else { OOLog(@"load.failed", @"***** Failed to load saved game \"%@\": %@", [fileToOpen lastPathComponent], fail_reason ? fail_reason : (NSString *)@"unknown error"); [[UNIVERSE gameController] setPlayerFileToLoad:nil]; [UNIVERSE handleGameOver]; [UNIVERSE clearPreviousMessage]; [UNIVERSE addMessage:DESC(@"loadfailed-saved-game-failed-to-load") forCount: 9.0]; if (fail_reason != nil) [UNIVERSE addMessage: fail_reason forCount: 9.0]; return NO; } OOLog(@"load.progress",@"Creating system"); [UNIVERSE setTimeAccelerationFactor:TIME_ACCELERATION_FACTOR_DEFAULT]; [UNIVERSE setSystemTo:system_id]; [UNIVERSE removeAllEntitiesExceptPlayer]; [UNIVERSE setGalaxyTo: galaxy_number andReinit:YES]; // set overridden planet names on long range map [UNIVERSE setUpSpace]; [UNIVERSE setAutoSaveNow:NO]; OOLog(@"load.progress",@"Resetting player flight variables"); [self setDockedAtMainStation]; StationEntity *dockedStation = [self dockedStation]; [UNIVERSE enterGUIViewModeWithMouseInteraction:NO]; if (dockedStation) { position = [dockedStation position]; [self setOrientation: kIdentityQuaternion]; v_forward = vector_forward_from_quaternion(orientation); v_right = vector_right_from_quaternion(orientation); v_up = vector_up_from_quaternion(orientation); } flightRoll = 0.0; flightPitch = 0.0; flightYaw = 0.0; flightSpeed = 0.0; [self setEntityPersonalityInt:PersonalityForCommanderDict(fileDic)]; OOLog(@"load.progress",@"Loading system market"); // dockedStation is always the main station at this point; // "localMarket" save key always refers to the main station (system) market NSArray *market = [fileDic oo_arrayForKey:@"localMarket"]; if (market != nil) { [dockedStation setLocalMarket:market]; } else { [dockedStation initialiseLocalMarket]; } [self calculateCurrentCargo]; OOLog(@"load.progress",@"Setting scenario key"); // set scenario key if the scenario allows saving and has one NSString *scenario = [fileDic oo_stringForKey:@"scenario_key" defaultValue:nil]; DESTROY(scenarioKey); if (scenario != nil) { scenarioKey = [scenario retain]; } OOLog(@"load.progress",@"Starting JS engine"); // Remember the savegame target, run js startUp. [self completeSetUpAndSetTarget:NO]; // run initial system population OOLog(@"load.progress",@"Populating initial system"); [UNIVERSE populateNormalSpace]; // might as well start off with a collected JS environment [[OOJavaScriptEngine sharedEngine] garbageCollectionOpportunity:YES]; // read saved position vector and primary role, check for an // appropriate station at those coordinates, if found, switch // docked station to that one. HPVector dockedPos = [fileDic oo_hpvectorForKey:@"docked_station_position"]; NSString *dockedRole = [fileDic oo_stringForKey:@"docked_station_role" defaultValue:@""]; StationEntity *saveStation = [UNIVERSE stationWithRole:dockedRole andPosition:dockedPos]; if (saveStation != nil && [saveStation allowsSaving]) { [self setDockedStation:saveStation]; position = [saveStation position]; } // and initialise markets for the secondary stations [UNIVERSE loadStationMarkets:[fileDic oo_arrayForKey:@"station_markets"]]; OOLog(@"load.progress",@"Completing JS startup"); [self startUpComplete]; [[UNIVERSE gameView] supressKeysUntilKeyUp]; gui_screen = GUI_SCREEN_LOAD; // force evaluation of new gui screen on startup [self setGuiToStatusScreen]; if (loadedOK) [self doWorldEventUntilMissionScreen:OOJSID("missionScreenOpportunity")]; // trigger missionScreenOpportunity immediately after loading OOLog(@"load.progress",@"Loading complete"); return loadedOK; } @end @implementation PlayerEntity (OOLoadSavePrivate) #if OOLITE_USE_APPKIT_LOAD_SAVE - (BOOL)loadPlayerWithPanel { NSOpenPanel *oPanel = [NSOpenPanel openPanel]; oPanel.allowsMultipleSelection = NO; oPanel.allowedFileTypes = [NSArray arrayWithObject:@"oolite-save"]; if ([oPanel runModal] == NSOKButton) { NSURL *url = oPanel.URL; if (url.isFileURL) { return [self loadPlayerFromFile:url.path asNew:NO]; } } return NO; } - (void) savePlayerWithPanel { NSSavePanel *sPanel = [NSSavePanel savePanel]; sPanel.allowedFileTypes = [NSArray arrayWithObject:@"oolite-save"]; sPanel.canSelectHiddenExtension = YES; sPanel.nameFieldStringValue = self.lastsaveName; if ([sPanel runModal] == NSOKButton) { NSURL *url = sPanel.URL; NSAssert(url.isFileURL, @"Save panel with default configuration should not provide non-file URLs."); NSString *path = url.path; NSString *newName = [path.lastPathComponent stringByDeletingPathExtension]; ShipScriptEventNoCx(self, "playerWillSaveGame", OOJSSTR("STANDARD_SAVE")); self.lastsaveName = newName; [self writePlayerToPath:path]; } [self setGuiToStatusScreen]; } #endif - (void) writePlayerToPath:(NSString *)path { NSString *errDesc = nil; NSDictionary *dict = nil; BOOL didSave = NO; [[UNIVERSE gameView] resetTypedString]; if (!path) { OOLog(@"save.failed", @"***** SAVE ERROR: %s called with nil path.", __PRETTY_FUNCTION__); return; } dict = [self commanderDataDictionary]; if (dict == nil) errDesc = @"could not construct commander data dictionary."; else didSave = [dict writeOOXMLToFile:path atomically:YES errorDescription:&errDesc]; if (didSave) { [UNIVERSE clearPreviousMessage]; // allow this to be given time and again [UNIVERSE addMessage:DESC(@"game-saved") forCount:2]; [save_path autorelease]; save_path = [path copy]; [[UNIVERSE gameController] setPlayerFileToLoad:save_path]; [[UNIVERSE gameController] setPlayerFileDirectory:save_path]; // no duplicated autosave immediately after a save. [UNIVERSE setAutoSaveNow:NO]; } else { OOLog(@"save.failed", @"***** SAVE ERROR: %@", errDesc); [NSException raise:@"OoliteException" format:@"Attempt to save game to file '%@' failed: %@", path, errDesc]; } [[UNIVERSE gameView] supressKeysUntilKeyUp]; [self setGuiToStatusScreen]; } - (void)nativeSavePlayer:(NSString *)cdrName { NSString* dir = [[UNIVERSE gameController] playerFileDirectory]; NSString *savePath = [dir stringByAppendingPathComponent:[cdrName stringByAppendingPathExtension:@"oolite-save"]]; ShipScriptEventNoCx(self, "playerWillSaveGame", OOJSSTR("STANDARD_SAVE")); [self setLastsaveName:cdrName]; [self writePlayerToPath:savePath]; } #if OO_USE_CUSTOM_LOAD_SAVE - (void) setGuiToLoadCommanderScreen { GuiDisplayGen *gui=[UNIVERSE gui]; NSString* dir = [[UNIVERSE gameController] playerFileDirectory]; gui_screen = GUI_SCREEN_LOAD; [gui clear]; [gui setTitle:DESC(@"loadscreen-title")]; currentPage = 0; [self lsCommanders:gui directory:dir pageNumber: currentPage highlightName:nil]; [gui setForegroundTextureKey:@"docked_overlay"]; [gui setBackgroundTextureKey:@"load_save"]; [[UNIVERSE gameView] supressKeysUntilKeyUp]; [self setShowDemoShips:YES]; [UNIVERSE enterGUIViewModeWithMouseInteraction:YES]; } - (void) setGuiToSaveCommanderScreen:(NSString *)cdrName { GuiDisplayGen *gui=[UNIVERSE gui]; MyOpenGLView *gameView = [UNIVERSE gameView]; NSString *dir = [[UNIVERSE gameController] playerFileDirectory]; pollControls = NO; gui_screen = GUI_SCREEN_SAVE; [gui clear]; [gui setTitle:DESC(@"savescreen-title")]; currentPage = 0; [self lsCommanders:gui directory:dir pageNumber: currentPage highlightName:nil]; [gui setText:DESC(@"savescreen-commander-name") forRow: INPUTROW]; [gui setColor:[OOColor cyanColor] forRow:INPUTROW]; [gui setShowTextCursor: YES]; [gui setCurrentRow: INPUTROW]; [gui setForegroundTextureKey:@"docked_overlay"]; [gui setBackgroundTextureKey:@"load_save"]; [gameView setTypedString:cdrName]; [gameView supressKeysUntilKeyUp]; [self setShowDemoShips:YES]; [UNIVERSE enterGUIViewModeWithMouseInteraction:YES]; } - (void) setGuiToOverwriteScreen:(NSString *)cdrName { GuiDisplayGen *gui=[UNIVERSE gui]; MyOpenGLView* gameView = [UNIVERSE gameView]; // Don't poll controls pollControls=NO; gui_screen = GUI_SCREEN_SAVE_OVERWRITE; [gui clear]; [gui setTitle:[NSString stringWithFormat:DESC(@"overwrite-save-commander-@"), cdrName]]; [gui setText:[NSString stringWithFormat:DESC(@"overwritescreen-commander-@-already-exists-overwrite-query"), cdrName] forRow:SAVE_OVERWRITE_WARN_ROW align: GUI_ALIGN_CENTER]; [gui setText:DESC(@"overwritescreen-yes") forRow: SAVE_OVERWRITE_YES_ROW align: GUI_ALIGN_CENTER]; [gui setKey:GUI_KEY_OK forRow: SAVE_OVERWRITE_YES_ROW]; [gui setText:DESC(@"overwritescreen-no") forRow: SAVE_OVERWRITE_NO_ROW align: GUI_ALIGN_CENTER]; [gui setKey:GUI_KEY_OK forRow: SAVE_OVERWRITE_NO_ROW]; [gui setSelectableRange: NSMakeRange(SAVE_OVERWRITE_YES_ROW, 2)]; [gui setSelectedRow: SAVE_OVERWRITE_NO_ROW]; // We can only leave this screen by answering yes or no, or esc. Therefore // use a specific overlay, to allow visual reminders of the available options. [gui setForegroundTextureKey:@"overwrite_overlay"]; [gui setBackgroundTextureKey:@"load_save"]; [self setShowDemoShips:NO]; [gameView setStringInput:gvStringInputNo]; [UNIVERSE enterGUIViewModeWithMouseInteraction:NO]; // FIXME: should be YES, but was NO before introducing new mouse mode stuff. If set to YES, choices can be selected but not activated. } NSComparisonResult sortCommanders(id cdr1, id cdr2, void *context) { return [[cdr1 objectForKey:@"saved_game_path"] localizedCompare:[cdr2 objectForKey:@"saved_game_path"]]; } - (void) lsCommanders: (GuiDisplayGen *)gui directory: (NSString*) directory pageNumber: (int)page highlightName: (NSString *)highlightName { NSFileManager *cdrFileManager=[NSFileManager defaultManager]; int rangeStart=STARTROW; unsigned lastIndex; unsigned i; int row=STARTROW; // cdrArray defined in PlayerEntity.h NSArray *cdrArray=[cdrFileManager commanderContentsOfPath: directory]; // get commander details so a brief rundown of the commander's details may // be displayed. if (!cdrDetailArray) cdrDetailArray=[[NSMutableArray alloc] init]; // alloc retains this so the retain further on in the code was unnecessary else [cdrDetailArray removeAllObjects]; [cdrDetailArray addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys: @"YES", @"isParentFolder", [directory stringByDeletingLastPathComponent], @"saved_game_path", nil]]; for(i = 0; i < [cdrArray count]; i++) { NSString* path = [cdrArray objectAtIndex:i]; BOOL exists, isDirectory = NO; exists = [cdrFileManager fileExistsAtPath:path isDirectory:&isDirectory]; if (exists) { if (!isDirectory && [[[path pathExtension] lowercaseString] isEqualToString:@"oolite-save"]) { NSDictionary *cdr = OODictionaryFromFile(path); if(cdr) { // okay use the same dictionary but add a 'saved_game_path' attribute NSMutableDictionary* cdr1 = [NSMutableDictionary dictionaryWithDictionary:cdr]; [cdr1 setObject: @"YES" forKey:@"isSavedGame"]; [cdr1 setObject: path forKey:@"saved_game_path"]; [cdrDetailArray addObject: cdr1]; } } if (isDirectory && ![[path lastPathComponent] hasPrefix:@"."]) { [cdrDetailArray addObject: [NSDictionary dictionaryWithObjectsAndKeys: @"YES", @"isFolder", path, @"saved_game_path", nil]]; } } } if(![cdrDetailArray count]) { // Empty directory; tell the user and exit immediately. [gui setText:DESC(@"loadsavescreen-no-commanders-found") forRow:STARTROW align:GUI_ALIGN_CENTER]; return; } [cdrDetailArray sortUsingFunction:sortCommanders context:NULL]; // Do we need to highlight a name? int highlightRowOnPage=STARTROW; int highlightIdx=0; if(highlightName) { highlightIdx=[self findIndexOfCommander: highlightName]; if(highlightIdx < 0) { OOLog(@"save.list.commanders.commanderNotFound", @"Commander %@ doesn't exist, very bad", highlightName); highlightIdx=0; } // figure out what page we need to be on page=highlightIdx/NUMROWS; highlightRowOnPage=highlightIdx % NUMROWS + STARTROW; } // We now know for certain what page we're on - // set the first index of the first commander on this page. unsigned firstIndex=page * NUMROWS; // Set up the GUI. OOGUITabSettings tabStop; tabStop[0]=0; tabStop[1]=160; tabStop[2]=270; [gui setTabStops: tabStop]; // clear text lines here for (i = EXITROW ; i < ENDROW + 1; i++) { [gui setText:@"" forRow:i align:GUI_ALIGN_LEFT]; [gui setColor: [OOColor yellowColor] forRow: i]; [gui setKey:GUI_KEY_SKIP forRow:i]; } [gui setColor: [OOColor greenColor] forRow: LABELROW]; [gui setArray: [NSArray arrayWithObjects: DESC(@"loadsavescreen-commander-name"), DESC(@"loadsavescreen-rating"), nil] forRow:LABELROW]; if (page) { [gui setColor:[OOColor greenColor] forRow:STARTROW-1]; [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-back"), @" <-- ", nil] forRow:STARTROW-1]; [gui setKey:GUI_KEY_OK forRow:STARTROW-1]; rangeStart=STARTROW-1; } if ([self status] == STATUS_START_GAME) { [gui setArray:[NSArray arrayWithObjects:DESC(@"oolite-loadsave-exit"), @" <----- ", nil] forRow:EXITROW]; [gui setColor:[OOColor redColor] forRow:EXITROW]; [gui setKey:GUI_KEY_OK forRow:EXITROW]; rangeStart = EXITROW; } if (firstIndex + NUMROWS >= [cdrDetailArray count]) { lastIndex=[cdrDetailArray count]; [gui setSelectableRange: NSMakeRange(rangeStart, rangeStart + NUMROWS + 2)]; } else { lastIndex=(page * NUMROWS) + NUMROWS; [gui setColor:[OOColor greenColor] forRow:ENDROW]; [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-more"), @" --> ", nil] forRow:ENDROW]; [gui setKey:GUI_KEY_OK forRow:ENDROW]; [gui setSelectableRange: NSMakeRange(rangeStart, MOREROW)]; } for (i=firstIndex; i < lastIndex; i++) { NSDictionary *cdr=[cdrDetailArray objectAtIndex: i]; if ([cdr oo_boolForKey:@"isSavedGame"]) { NSString *ratingDesc = OODisplayRatingStringFromKillCount([cdr oo_unsignedIntForKey:@"ship_kills"]); [gui setArray:[NSArray arrayWithObjects: [NSString stringWithFormat:@" %@ ",[cdr oo_stringForKey:@"player_save_name" defaultValue:[cdr oo_stringForKey:@"player_name"]]], [NSString stringWithFormat:@" %@ ",ratingDesc], nil] forRow:row]; if ([[self lastsaveName] isEqualToString:[cdr oo_stringForKey:@"player_save_name" defaultValue:[cdr oo_stringForKey:@"player_name"]]]) { highlightRowOnPage = row; } [gui setKey:GUI_KEY_OK forRow:row]; row++; } if ([cdr oo_boolForKey:@"isParentFolder"]) { [gui setArray:[NSArray arrayWithObjects: [NSString stringWithFormat:@" (..) %@ ", [[cdr oo_stringForKey:@"saved_game_path"] lastPathComponent]], @"", nil] forRow:row]; [gui setColor: [OOColor orangeColor] forRow: row]; [gui setKey:GUI_KEY_OK forRow:row]; row++; } if ([cdr oo_boolForKey:@"isFolder"]) { [gui setArray:[NSArray arrayWithObjects: [NSString stringWithFormat:@" >> %@ ", [[cdr oo_stringForKey:@"saved_game_path"] lastPathComponent]], @"", nil] forRow:row]; [gui setColor: [OOColor orangeColor] forRow: row]; [gui setKey:GUI_KEY_OK forRow:row]; row++; } } [gui setSelectedRow: highlightRowOnPage]; highlightIdx = (highlightRowOnPage - STARTROW) + (currentPage * NUMROWS); // show the first ship, this will be the selected row [self showCommanderShip: highlightIdx]; } // check for an existing saved game... - (BOOL) existingNativeSave: (NSString *)cdrName { NSString* dir = [[UNIVERSE gameController] playerFileDirectory]; NSString *savePath=[dir stringByAppendingPathComponent:[cdrName stringByAppendingPathExtension:@"oolite-save"]]; return [[NSFileManager defaultManager] fileExistsAtPath:savePath]; } // Get some brief details about the commander file. - (void) showCommanderShip:(int)cdrArrayIndex { GuiDisplayGen *gui=[UNIVERSE gui]; [UNIVERSE removeDemoShips]; NSDictionary *cdr=[cdrDetailArray objectAtIndex: cdrArrayIndex]; [gui setText:@"" forRow:CDRDESCROW align:GUI_ALIGN_LEFT]; [gui setText:@"" forRow:CDRDESCROW + 1 align:GUI_ALIGN_LEFT]; [gui setText:@"" forRow:CDRDESCROW + 2 align:GUI_ALIGN_LEFT]; if ([cdr oo_boolForKey:@"isFolder"]) { NSString *folderDesc=[NSString stringWithFormat: DESC(@"loadsavescreen-hold-@-and-press-return-to-open-folder-@"), @COMMAND_MODIFIER_KEY, [[cdr oo_stringForKey:@"saved_game_path"] lastPathComponent]]; [gui setColor: [OOColor orangeColor] forRow: CDRDESCROW]; [gui addLongText: folderDesc startingAtRow: CDRDESCROW align: GUI_ALIGN_LEFT]; return; } if ([cdr oo_boolForKey:@"isParentFolder"]) { NSString *folderDesc=[NSString stringWithFormat: DESC(@"loadsavescreen-hold-@-and-press-return-to-open-parent-folder-@"), @COMMAND_MODIFIER_KEY, [[cdr oo_stringForKey:@"saved_game_path"] lastPathComponent]]; [gui setColor: [OOColor orangeColor] forRow: CDRDESCROW]; [gui addLongText: folderDesc startingAtRow: CDRDESCROW align: GUI_ALIGN_LEFT]; return; } [gui setColor:[gui colorFromSetting:nil defaultValue:nil] forRow: CDRDESCROW]; if (![cdr oo_boolForKey:@"isSavedGame"]) return; // don't show things that aren't saved games if ([self dockedStation] == nil) [self setDockedAtMainStation]; // Display the commander's ship. NSString *shipDesc = [cdr oo_stringForKey:@"ship_desc"]; NSString *shipName = nil; NSDictionary *shipDict = nil; NSString *rating = nil; uint16_t personality = PersonalityForCommanderDict(cdr); shipDict = [[OOShipRegistry sharedRegistry] shipInfoForKey:shipDesc]; if(shipDict != nil) { NSMutableDictionary * dict = [[NSMutableDictionary alloc] initWithCapacity:[shipDict count] + 1]; [dict setDictionary:shipDict]; id subEntStatus = [cdr objectForKey:@"subentities_status"]; // don't add it to the dictionary if there's no subentities_status key if (subEntStatus != nil) [dict setObject:subEntStatus forKey:@"subentities_status"]; [self showShipyardModel:shipDesc shipData:dict personality:personality]; [dict release]; shipName = [shipDict oo_stringForKey:@"display_name"]; if (shipName == nil) shipName = [shipDict oo_stringForKey:KEY_NAME]; } else { [self showShipyardModel:@"oolite-unknown-ship" shipData:nil personality:personality]; shipName = [cdr oo_stringForKey:@"ship_name" defaultValue:@"unknown"]; if (![[UNIVERSE useAddOns] isEqualToString:SCENARIO_OXP_DEFINITION_ALL]) { shipName = [shipName stringByAppendingString:@" - OXPs disabled or not installed"]; } else { shipName = [shipName stringByAppendingString:@" - OXP not installed"]; } } // Make a short description of the commander NSString *legalDesc = OODisplayStringFromLegalStatus([cdr oo_intForKey:@"legal_status"]); rating = KillCountToRatingAndKillString([cdr oo_unsignedIntForKey:@"ship_kills"]); OOCreditsQuantity money = OODeciCreditsFromObject([cdr objectForKey:@"credits"]); // Nikos - Add some more information in the load game screen (current location, galaxy number and timestamp). //------------------------------------------------------------------------------------------------------------------------- int galNumber; NSString *timeStamp = nil; NSString *locationName = [cdr oo_stringForKey:@"current_system_name"]; // If there is no key containing the name of the current system in // the savefile, calculating what it should have been is going to // be tricky now that system generation isn't seed based - but // this implies a save game well over 5 years old. if (locationName == nil) { // Leaving the location blank in this case is probably okay locationName = @""; } galNumber = [cdr oo_intForKey:@"galaxy_number"] + 1; // Galaxy numbering starts at 0. timeStamp = ClockToString([cdr oo_doubleForKey:@"ship_clock" defaultValue:PLAYER_SHIP_CLOCK_START], NO); //------------------------------------------------------------------------------------------------------------------------- NSString *cdrDesc = nil; cdrDesc = [NSString stringWithFormat:DESC(@"loadsavescreen-commander-@-rated-@-has-@-legal-status-@-ship-@-location-@-g-@-timestamp-@"), [cdr oo_stringForKey:@"player_name"], rating, OOCredits(money), legalDesc, shipName, locationName, galNumber, timeStamp]; [gui addLongText:cdrDesc startingAtRow:CDRDESCROW align:GUI_ALIGN_LEFT]; } - (int) findIndexOfCommander: (NSString *)cdrName { unsigned i; for (i=0; i < [cdrDetailArray count]; i++) { NSString *currentName = [[cdrDetailArray oo_dictionaryAtIndex: i] oo_stringForKey:@"player_save_name" defaultValue:[[cdrDetailArray oo_dictionaryAtIndex: i] oo_stringForKey:@"player_name"]]; if([cdrName compare: currentName] == NSOrderedSame) { return i; } } // not found! return -1; } #endif @end #if OO_USE_CUSTOM_LOAD_SAVE @implementation MyOpenGLView (OOLoadSaveExtensions) - (BOOL)isCommandModifierKeyDown { return [self isCtrlDown]; } @end #endif static uint16_t PersonalityForCommanderDict(NSDictionary *dict) { uint16_t personality = [dict oo_unsignedShortForKey:@"entity_personality" defaultValue:ENTITY_PERSONALITY_INVALID]; if (personality == ENTITY_PERSONALITY_INVALID) { // For pre-1.74 saved games, generate a default personality based on some hashes. personality = [[dict oo_stringForKey:@"ship_desc"] oo_hash] * [[dict oo_stringForKey:@"player_name"] oo_hash]; } return personality & ENTITY_PERSONALITY_MAX; } OOCreditsQuantity OODeciCreditsFromDouble(double doubleDeciCredits) { /* Clamp value to 0..kOOMaxCredits. The important bit here is that kOOMaxCredits can't be represented exactly as a double, and casting it rounds it up; casting this value back to an OOCreditsQuantity truncates it. Comparing value directly to kOOMaxCredits promotes kOOMaxCredits to a double, giving us this problem. nextafter(kOOMaxCredits, -1) gives us the highest non-truncated credits value that's representable as a double (namely, 18 446 744 073 709 549 568 decicredits, or 2047 less than kOOMaxCredits). -- Ahruman 2011-02-27 */ if (doubleDeciCredits > 0) { doubleDeciCredits = round(doubleDeciCredits); double threshold = nextafter(kOOMaxCredits, -1); if (doubleDeciCredits <= threshold) { return doubleDeciCredits; } else { return kOOMaxCredits; } } else { return 0; } } OOCreditsQuantity OODeciCreditsFromObject(id object) { if ([object isKindOfClass:[NSNumber class]] && [object oo_isFloatingPointNumber]) { return OODeciCreditsFromDouble([object doubleValue]); } else { return OOUnsignedLongLongFromObject(object, 0); } } oolite-1.82/src/Core/Entities/PlayerEntityScriptMethods.h000066400000000000000000000047561256642440500235410ustar00rootroot00000000000000/* PlayerEntityScriptMethods.h Methods for use by scripting mechanisms. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "PlayerEntity.h" @interface PlayerEntity (ScriptMethods) - (unsigned) score; - (void) setScore:(unsigned)value; - (double) creditBalance; - (void) setCreditBalance:(double)value; - (NSString *) dockedStationName; - (NSString *) dockedStationDisplayName; - (BOOL) dockedAtMainStation; - (void) awardCommodityType:(NSString *)type amount:(OOCargoQuantity)amount; - (void) resetScannerZoom; - (OOGalaxyID) currentGalaxyID; - (OOSystemID) currentSystemID; - (void) setMissionChoice:(NSString *)newChoice; - (void) setMissionChoice:(NSString *)newChoice withEvent:(BOOL) withEvent; - (void) allowMissionInterrupt; - (OOTimeDelta) scriptTimer; - (unsigned) systemPseudoRandom100; - (unsigned) systemPseudoRandom256; - (double) systemPseudoRandomFloat; - (NSDictionary *) passengerContractMarker:(OOSystemID)system; - (NSDictionary *) parcelContractMarker:(OOSystemID)system; - (NSDictionary *) cargoContractMarker:(OOSystemID)system; - (NSDictionary *) defaultMarker:(OOSystemID)system; - (NSDictionary *) validatedMarker:(NSDictionary *)marker; - (NSString *) keyBindingDescription:(NSString *)binding; - (NSString *) keyCodeDescription:(OOKeyCode)code; @end /* OOGalacticCoordinatesFromInternal() Given internal coordinates ranging from 0 to 255 on each axis, return corresponding coordinates in user-meaningful coordinates by scaling by 0.4 on the X axis and 0.2 on the Y axis. OOInternalCoordinatesFromGalactic() Inverse operation. For valid floating-point comparisons, it is imperative that the same calculation be used consistently. */ Vector OOGalacticCoordinatesFromInternal(NSPoint internalCoordinates); NSPoint OOInternalCoordinatesFromGalactic(Vector galacticCoordinates); oolite-1.82/src/Core/Entities/PlayerEntityScriptMethods.m000066400000000000000000000273611256642440500235430ustar00rootroot00000000000000/* PlayerEntityScriptMethods.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "PlayerEntityScriptMethods.h" #import "PlayerEntityLoadSave.h" #import "Universe.h" #import "OOCollectionExtractors.h" #import "OOConstToString.h" #import "OOStringParsing.h" #import "OOStringExpander.h" #import "OOSystemDescriptionManager.h" #import "StationEntity.h" @implementation PlayerEntity (ScriptMethods) - (unsigned) score { return ship_kills; } - (void) setScore:(unsigned)value { ship_kills = value; } - (double) creditBalance { return 0.1 * credits; } - (void) setCreditBalance:(double)value { credits = OODeciCreditsFromDouble(value * 10.0); } - (NSString *) dockedStationName { return [[self dockedStation] name]; } - (NSString *) dockedStationDisplayName { return [[self dockedStation] displayName]; } - (BOOL) dockedAtMainStation { return [self status] == STATUS_DOCKED && [self dockedStation] == [UNIVERSE station]; } - (void) awardCommodityType:(OOCommodityType)type amount:(OOCargoQuantity)amount { OOMassUnit unit; if (![[UNIVERSE commodities] goodDefined:type]) { return; } OOLog(@"script.debug.note.awardCargo", @"Going to award cargo: %d x '%@'", amount, type); unit = [shipCommodityData massUnitForGood:type]; if ([self status] != STATUS_DOCKED) { // in-flight while (amount) { if (unit != UNITS_TONS) { if (specialCargo) { // is this correct behaviour? [shipCommodityData addQuantity:amount forGood:type]; } else { int amount_per_container = (unit == UNITS_KILOGRAMS)? 1000 : 1000000; while (amount > 0) { int smaller_quantity = 1 + ((amount - 1) % amount_per_container); if ([cargo count] < [self maxAvailableCargoSpace]) { ShipEntity* container = [UNIVERSE newShipWithRole:@"1t-cargopod"]; if (container) { // the cargopod ship is just being set up. If ejected, will call UNIVERSE addEntity // [container wasAddedToUniverse]; // seems to be not needed anymore for pods [container setScanClass: CLASS_CARGO]; [container setStatus:STATUS_IN_HOLD]; [container setCommodity:type andAmount:smaller_quantity]; [cargo addObject:container]; [container release]; } } amount -= smaller_quantity; } } } else if (!specialCargo) // no adding TCs while special cargo in hold { // put each ton in a separate container while (amount) { if ([cargo count] < [self maxAvailableCargoSpace]) { ShipEntity* container = [UNIVERSE newShipWithRole:@"1t-cargopod"]; if (container) { // the cargopod ship is just being set up. If ejected, will call UNIVERSE addEntity // [container wasAddedToUniverse]; // seems to be not needed anymore for pods [container setScanClass: CLASS_CARGO]; [container setStatus:STATUS_IN_HOLD]; [container setCommodity:type andAmount:1]; [cargo addObject:container]; [container release]; } } amount--; } } } } else { // docked // like purchasing a commodity int manifest_quantity = [shipCommodityData quantityForGood:type]; while ((amount)&&(current_cargo < [self maxAvailableCargoSpace])) { manifest_quantity++; amount--; if (unit == UNITS_TONS) current_cargo++; } [shipCommodityData setQuantity:manifest_quantity forGood:type]; } [self calculateCurrentCargo]; } - (void) resetScannerZoom { scanner_zoom_rate = SCANNER_ZOOM_RATE_DOWN; } - (OOGalaxyID) currentGalaxyID { return galaxy_number; } - (OOSystemID) currentSystemID { if ([UNIVERSE sun] == nil) return -1; // Interstellar space return [UNIVERSE currentSystemID]; } - (void) setMissionChoice:(NSString *)newChoice { [self setMissionChoice:newChoice withEvent:YES]; } - (void) setMissionChoice:(NSString *)newChoice withEvent:(BOOL)withEvent { BOOL equal = [newChoice isEqualToString:missionChoice] || (newChoice == missionChoice); // Catch both being nil as well if (!equal) { if (newChoice == nil) { NSString *oldChoice = missionChoice; [missionChoice autorelease]; missionChoice = nil; if (withEvent) [self doScriptEvent:OOJSID("missionChoiceWasReset") withArgument:oldChoice]; } else { [missionChoice autorelease]; missionChoice = [newChoice copy]; } } } - (void) allowMissionInterrupt { _missionAllowInterrupt = YES; } - (OOTimeDelta) scriptTimer { return script_time; } /* FIXME: these next three functions seed the RNG when called. That * could cause unwanted effects - should save its state, and then * reset it after generating the number. */ - (unsigned) systemPseudoRandom100 { seed_RNG_only_for_planet_description([[UNIVERSE systemManager] getRandomSeedForCurrentSystem]); return (gen_rnd_number() * 256 + gen_rnd_number()) % 100; } - (unsigned) systemPseudoRandom256 { seed_RNG_only_for_planet_description([[UNIVERSE systemManager] getRandomSeedForCurrentSystem]); return gen_rnd_number(); } - (double) systemPseudoRandomFloat { seed_RNG_only_for_planet_description([[UNIVERSE systemManager] getRandomSeedForCurrentSystem]); unsigned a = gen_rnd_number(); unsigned b = gen_rnd_number(); unsigned c = gen_rnd_number(); a = (a << 16) | (b << 8) | c; return (double)a / (double)0x01000000; } - (NSDictionary *) passengerContractMarker:(OOSystemID)system { return [[[NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:system], @"system", MISSION_DEST_LEGACY, @"name", @"orangeColor", @"markerColor", @"MARKER_DIAMOND", @"markerShape", nil] retain] autorelease]; } - (NSDictionary *) parcelContractMarker:(OOSystemID)system { return [[[NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:system], @"system", MISSION_DEST_LEGACY, @"name", @"orangeColor", @"markerColor", @"MARKER_PLUS", @"markerShape", nil] retain] autorelease]; } - (NSDictionary *) cargoContractMarker:(OOSystemID)system { return [[[NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:system], @"system", MISSION_DEST_LEGACY, @"name", @"orangeColor", @"markerColor", @"MARKER_SQUARE", @"markerShape", nil] retain] autorelease]; } - (NSDictionary *) defaultMarker:(OOSystemID)system { return [[[NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:system], @"system", MISSION_DEST_LEGACY, @"name", @"redColor", @"markerColor", @"MARKER_X", @"markerShape", nil] retain] autorelease]; } - (NSDictionary *) validatedMarker:(NSDictionary *)marker { OOSystemID dest = [marker oo_intForKey:@"system"]; // FIXME: parameters if (dest < 0 || dest > kOOMaximumSystemID) { return nil; } NSString *group = [marker oo_stringForKey:@"name" defaultValue:MISSION_DEST_LEGACY]; return [[[NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:dest], @"system", group, @"name", [marker oo_stringForKey:@"markerColor" defaultValue:@"redColor"], @"markerColor", [marker oo_stringForKey:@"markerShape" defaultValue:@"MARKER_X"], @"markerShape", [NSNumber numberWithFloat:[marker oo_floatForKey:@"markerScale" defaultValue:1.0]], @"markerScale", nil] retain] autorelease]; } // Implements string expansion code [credits_number]. - (NSString *) creditsFormattedForSubstitution { return OOStringFromDeciCredits([self deciCredits], YES, NO); } /* Implements string expansion code [_oo_legacy_credits_number]. Literal uses of [credits_number] in legacy scripts are converted to [_oo_legacy_credits_number] in the script sanitizer. These are shown unlocalized because legacy scripts may use it for arithmetic. */ - (NSString *) creditsFormattedForLegacySubstitution { OOCreditsQuantity tenthsOfCredits = [self deciCredits]; unsigned long long integerCredits = tenthsOfCredits / 10; unsigned long long tenths = tenthsOfCredits % 10; return [NSString stringWithFormat:@"%llu.%llu", integerCredits, tenths]; } // Implements string expansion code [commander_bounty]. - (NSString *) commanderBountyAsString { return [NSString stringWithFormat:@"%i", [self legalStatus]]; } // Implements string expansion code [commander_kills]. - (NSString *) commanderKillsAsString { return [NSString stringWithFormat:@"%i", [self score]]; } - (NSString *) keyBindingDescription:(NSString *)binding { if ([keyconfig_settings objectForKey:binding] == nil) { // no such setting return nil; } OOKeyCode key = (OOKeyCode)[keyconfig_settings oo_unsignedShortForKey:binding]; // 0 = key not set return [self keyCodeDescription:key]; } - (NSString *) keyCodeDescription:(OOKeyCode)code { switch (code) { case 0: return DESC(@"oolite-keycode-unset"); case 9: return DESC(@"oolite-keycode-tab"); case 27: return DESC(@"oolite-keycode-esc"); case 32: return DESC(@"oolite-keycode-space"); case gvFunctionKey1: return DESC(@"oolite-keycode-f1"); case gvFunctionKey2: return DESC(@"oolite-keycode-f2"); case gvFunctionKey3: return DESC(@"oolite-keycode-f3"); case gvFunctionKey4: return DESC(@"oolite-keycode-f4"); case gvFunctionKey5: return DESC(@"oolite-keycode-f5"); case gvFunctionKey6: return DESC(@"oolite-keycode-f6"); case gvFunctionKey7: return DESC(@"oolite-keycode-f7"); case gvFunctionKey8: return DESC(@"oolite-keycode-f8"); case gvFunctionKey9: return DESC(@"oolite-keycode-f9"); case gvFunctionKey10: return DESC(@"oolite-keycode-f10"); case gvFunctionKey11: return DESC(@"oolite-keycode-f11"); case gvArrowKeyRight: return DESC(@"oolite-keycode-right"); case gvArrowKeyLeft: return DESC(@"oolite-keycode-left"); case gvArrowKeyDown: return DESC(@"oolite-keycode-down"); case gvArrowKeyUp: return DESC(@"oolite-keycode-up"); case gvHomeKey: return DESC(@"oolite-keycode-home"); case gvEndKey: return DESC(@"oolite-keycode-end"); case gvInsertKey: return DESC(@"oolite-keycode-insert"); case gvDeleteKey: return DESC(@"oolite-keycode-delete"); case gvPageUpKey: return DESC(@"oolite-keycode-pageup"); case gvPageDownKey: return DESC(@"oolite-keycode-pagedown"); case gvNumberPadKey0: return DESC(@"oolite-keycode-numpad0"); case gvNumberPadKey1: return DESC(@"oolite-keycode-numpad1"); case gvNumberPadKey2: return DESC(@"oolite-keycode-numpad2"); case gvNumberPadKey3: return DESC(@"oolite-keycode-numpad3"); case gvNumberPadKey4: return DESC(@"oolite-keycode-numpad4"); case gvNumberPadKey5: return DESC(@"oolite-keycode-numpad5"); case gvNumberPadKey6: return DESC(@"oolite-keycode-numpad6"); case gvNumberPadKey7: return DESC(@"oolite-keycode-numpad7"); case gvNumberPadKey8: return DESC(@"oolite-keycode-numpad8"); case gvNumberPadKey9: return DESC(@"oolite-keycode-numpad9"); default: return [NSString stringWithFormat:@"%C",code]; } } @end Vector OOGalacticCoordinatesFromInternal(NSPoint internalCoordinates) { return (Vector){ (float)internalCoordinates.x * 0.4f, (float)internalCoordinates.y * 0.2f, 0.0f }; } NSPoint OOInternalCoordinatesFromGalactic(Vector galacticCoordinates) { return (NSPoint){ (float)galacticCoordinates.x * 2.5f, (float)galacticCoordinates.y * 5.0f }; } oolite-1.82/src/Core/Entities/PlayerEntitySound.h000066400000000000000000000077021256642440500220330ustar00rootroot00000000000000/* PlayerEntitySound.h Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "PlayerEntity.h" @interface PlayerEntity (Sound) - (void) setUpSound; - (void) destroySound; // Interface sounds; only one at a time - (BOOL) isBeeping; - (void) playIdentOn; - (void) playIdentOff; - (void) playIdentLockedOn; - (void) playMissileArmed; - (void) playMineArmed; - (void) playMissileSafe; - (void) playMissileLockedOn; - (void) playWeaponsOnline; - (void) playWeaponsOffline; - (void) playNextEquipmentSelected; - (void) playNextMissileSelected; - (void) playCargoJettisioned; - (void) playAutopilotOn; - (void) playAutopilotOff; - (void) playAutopilotOutOfRange; - (void) playAutopilotCannotDockWithTarget; - (void) playSaveOverwriteYes; - (void) playSaveOverwriteNo; - (void) playHoldFull; - (void) playJumpMassLocked; - (void) playTargetLost; - (void) playNoTargetInMemory; - (void) playTargetSwitched; - (void) playCloakingDeviceOn; - (void) playCloakingDeviceOff; - (void) playCloakingDeviceInsufficientEnergy; - (void) playMenuNavigationUp; - (void) playMenuNavigationDown; - (void) playMenuNavigationNot; - (void) playMenuPagePrevious; - (void) playMenuPageNext; - (void) playDismissedReportScreen; - (void) playDismissedMissionScreen; - (void) playChangedOption; - (void) updateFuelScoopSoundWithInterval:(OOTimeDelta)delta_t; - (void) startAfterburnerSound; - (void) stopAfterburnerSound; // Buy/sell get their own source. - (void) playBuyCommodity; - (void) playBuyShip; - (void) playSellCommodity; - (void) playCantBuyCommodity; - (void) playCantSellCommodity; - (void) playCantBuyShip; // Hyperspace alert sounds; logically hyperspace sounds, but played on the interface sound source. - (void) playHyperspaceNoTarget; - (void) playHyperspaceNoFuel; - (void) playHyperspaceBlocked; - (void) playHyperspaceDistanceTooGreat; /* Hyperspace sounds; only one at a time. These get their own pool since people might want something longer than beeps and boops (e.g. the existing hyperspace countdown one). Hyperspace-related alert sounds are with the normal interface sounds. */ - (void) playStandardHyperspace; - (void) playGalacticHyperspace; - (void) playHyperspaceAborted; // ECM; only one at a time - (void) playHitByECMSound; - (void) playFiredECMSound; - (void) playLaunchFromStation; - (void) playDockWithStation; - (void) playExitWitchspace; // Warning sounds - (void) playHostileWarning; - (void) playAlertConditionRed; - (void) playIncomingMissile:(Vector)missileVector; - (void) playEnergyLow; - (void) playDockingDenied; - (void) playWitchjumpFailure; - (void) playWitchjumpMisjump; - (void) playWitchjumpBlocked; - (void) playWitchjumpDistanceTooGreat; - (void) playWitchjumpInsufficientFuel; - (void) playFuelLeak; // Damage sounds - (void) playShieldHit:(Vector)attackVector; - (void) playDirectHit:(Vector)attackVector; - (void) playScrapeDamage:(Vector)attackVector; // Weapon sounds - (void) playLaserHit:(BOOL)hit offset:(Vector)weaponOffset; - (void) playWeaponOverheated:(Vector)weaponOffset; - (void) playMissileLaunched:(Vector)weaponOffset; - (void) playMineLaunched:(Vector)weaponOffset; // Miscellaneous sounds - (void) playEscapePodScooped; - (void) playAegisCloseToPlanet; - (void) playAegisCloseToStation; - (void) playGameOver; - (void) playLegacyScriptSound:(NSString *)key; @end oolite-1.82/src/Core/Entities/PlayerEntitySound.m000066400000000000000000000327641256642440500220460ustar00rootroot00000000000000/* PlayerEntitySound.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "PlayerEntitySound.h" #import "OOSound.h" #import "ResourceManager.h" #import "Universe.h" #import "OOSoundSourcePool.h" #import "OOMaths.h" // Sizes of sound source pools enum { kBuySellSourcePoolSize = 4, kWarningPoolSize = 6, kWeaponPoolSize = 3, kDamagePoolSize = 4, kMiscPoolSize = 2 }; static OOSoundSourcePool *sWarningSoundPool; static OOSoundSourcePool *sWeaponSoundPool; static OOSoundSourcePool *sDamageSoundPool; static OOSoundSourcePool *sMiscSoundPool; static OOSoundSource *sHyperspaceSoundSource; static OOSoundSource *sInterfaceBeepSource; static OOSoundSource *sEcmSource; static OOSoundSource *sBreakPatternSource; static OOSoundSourcePool *sBuySellSourcePool; static OOSoundSource *sAfterburnerSources[2]; static const Vector kInterfaceBeepPosition = { 0.0f, -0.2f, 0.5f }; static const Vector kInterfaceWarningPosition = { 0.0f, -0.2f, 0.4f }; static const Vector kBreakPatternPosition = { 0.0f, 0.0f, 1.0f }; static const Vector kEcmPosition = { 0.2f, 0.6f, -0.1f }; static const Vector kWitchspacePosition = { 0.0f, -0.3f, -0.3f }; // maybe these should actually track engine positions static const Vector kAfterburner1Position = { -0.1f, 0.0f, -1.0f }; static const Vector kAfterburner2Position = { 0.1f, 0.0f, -1.0f }; @implementation PlayerEntity (Sound) - (void) setUpSound { [self destroySound]; sInterfaceBeepSource = [[OOSoundSource alloc] init]; [sInterfaceBeepSource setPosition:kInterfaceBeepPosition]; sBreakPatternSource = [[OOSoundSource alloc] init]; [sBreakPatternSource setPosition:kBreakPatternPosition]; sEcmSource = [[OOSoundSource alloc] init]; [sEcmSource setPosition:kEcmPosition]; sHyperspaceSoundSource = [[OOSoundSource alloc] init]; [sHyperspaceSoundSource setPosition:kWitchspacePosition]; sBuySellSourcePool = [[OOSoundSourcePool alloc] initWithCount:kBuySellSourcePoolSize minRepeatTime:0.0]; sWarningSoundPool = [[OOSoundSourcePool alloc] initWithCount:kWarningPoolSize minRepeatTime:0.0]; sWeaponSoundPool = [[OOSoundSourcePool alloc] initWithCount:kWeaponPoolSize minRepeatTime:0.0]; sDamageSoundPool = [[OOSoundSourcePool alloc] initWithCount:kDamagePoolSize minRepeatTime:0.1]; // Repeat time limit is to avoid playing a scrape sound every frame on glancing scrapes. This does limit the number of laser hits that can be played in a furrball, though; maybe lasers and scrapes should use different pools. sMiscSoundPool = [[OOSoundSourcePool alloc] initWithCount:kMiscPoolSize minRepeatTime:0.0]; // Two sources with the same sound are used to simulate looping. OOSound *afterburnerSound = [ResourceManager ooSoundNamed:@"afterburner1.ogg" inFolder:@"Sounds"]; sAfterburnerSources[0] = [[OOSoundSource alloc] initWithSound:afterburnerSound]; [sAfterburnerSources[0] setPosition:kAfterburner1Position]; sAfterburnerSources[1] = [[OOSoundSource alloc] initWithSound:afterburnerSound]; [sAfterburnerSources[1] setPosition:kAfterburner2Position]; } - (void) destroySound { DESTROY(sInterfaceBeepSource); DESTROY(sBreakPatternSource); DESTROY(sEcmSource); DESTROY(sHyperspaceSoundSource); DESTROY(sAfterburnerSources[0]); DESTROY(sAfterburnerSources[1]); DESTROY(sBuySellSourcePool); DESTROY(sWarningSoundPool); DESTROY(sWeaponSoundPool); DESTROY(sDamageSoundPool); DESTROY(sMiscSoundPool); } - (void) playInterfaceBeep:(NSString *)beepKey { #if OOLITE_WINDOWS if ([self status] == STATUS_START_GAME) { return; } #endif [sInterfaceBeepSource playSound:[OOSound soundWithCustomSoundKey:beepKey]]; } - (BOOL) isBeeping { return [sInterfaceBeepSource isPlaying]; } - (void) boop { [self playInterfaceBeep:@"[general-boop]"]; } - (void) playIdentOn { [self playInterfaceBeep:@"[ident-on]"]; } - (void) playIdentOff { [self playInterfaceBeep:@"[ident-off]"]; } - (void) playIdentLockedOn { [self playInterfaceBeep:@"[ident-locked-on]"]; } - (void) playMissileArmed { [self playInterfaceBeep:@"[missile-armed]"]; } - (void) playMineArmed { [self playInterfaceBeep:@"[mine-armed]"]; } - (void) playMissileSafe { [self playInterfaceBeep:@"[missile-safe]"]; } - (void) playMissileLockedOn { [self playInterfaceBeep:@"[missile-locked-on]"]; } - (void) playNextEquipmentSelected { [self playInterfaceBeep:@"[next-equipment-selected]"]; } - (void) playNextMissileSelected { [self playInterfaceBeep:@"[next-missile-selected]"]; } - (void) playWeaponsOnline { [self playInterfaceBeep:@"[weapons-online]"]; } - (void) playWeaponsOffline { [self playInterfaceBeep:@"[weapons-offline]"]; } - (void) playCargoJettisioned { [self playInterfaceBeep:@"[cargo-jettisoned]"]; } - (void) playAutopilotOn { [self playInterfaceBeep:@"[autopilot-on]"]; } - (void) playAutopilotOff { // only if still alive if (energy > 0.0) { [self playInterfaceBeep:@"[autopilot-off]"]; } } - (void) playAutopilotOutOfRange { [self playInterfaceBeep:@"[autopilot-out-of-range]"]; } - (void) playAutopilotCannotDockWithTarget { [self playInterfaceBeep:@"[autopilot-cannot-dock-with-target]"]; } - (void) playSaveOverwriteYes { [self playInterfaceBeep:@"[save-overwrite-yes]"]; } - (void) playSaveOverwriteNo { [self playInterfaceBeep:@"[save-overwrite-no]"]; } - (void) playHoldFull { [self playInterfaceBeep:@"[hold-full]"]; } - (void) playJumpMassLocked { [self playInterfaceBeep:@"[jump-mass-locked]"]; } - (void) playTargetLost { [self playInterfaceBeep:@"[target-lost]"]; } - (void) playNoTargetInMemory { [self playInterfaceBeep:@"[no-target-in-memory]"]; } - (void) playTargetSwitched { [self playInterfaceBeep:@"[target-switched]"]; } - (void) playHyperspaceNoTarget { [self playInterfaceBeep:@"[witch-no-target]"]; } - (void) playHyperspaceNoFuel { [self playInterfaceBeep:@"[witch-no-fuel]"]; } - (void) playHyperspaceBlocked { [self playInterfaceBeep:@"[hyperspace-blocked]"]; } - (void) playHyperspaceDistanceTooGreat { [self playInterfaceBeep:@"[witch-too-far]"]; } - (void) playCloakingDeviceOn { [self playInterfaceBeep:@"[cloaking-device-on]"]; } - (void) playCloakingDeviceOff { [self playInterfaceBeep:@"[cloaking-device-off]"]; } - (void) playMenuNavigationUp { [self playInterfaceBeep:@"[menu-navigation-up]"]; } - (void) playMenuNavigationDown { [self playInterfaceBeep:@"[menu-navigation-down]"]; } - (void) playMenuNavigationNot { [self playInterfaceBeep:@"[menu-navigation-not]"]; } - (void) playMenuPagePrevious { [self playInterfaceBeep:@"[menu-next-page]"]; } - (void) playMenuPageNext { [self playInterfaceBeep:@"[menu-previous-page]"]; } - (void) playDismissedReportScreen { [self playInterfaceBeep:@"[dismissed-report-screen]"]; } - (void) playDismissedMissionScreen { [self playInterfaceBeep:@"[dismissed-mission-screen]"]; } - (void) playChangedOption { [self playInterfaceBeep:@"[changed-option]"]; } - (void) updateFuelScoopSoundWithInterval:(OOTimeDelta)delta_t { static double scoopSoundPlayTime = 0.0; scoopSoundPlayTime -= delta_t; if (scoopSoundPlayTime < 0.0) { if(![sInterfaceBeepSource isPlaying]) { /* TODO: this should use the scoop position, not the standard * interface beep position */ [self playInterfaceBeep:@"[scoop]"]; scoopSoundPlayTime = 0.5; } else scoopSoundPlayTime = 0.0; } if (![self scoopOverride]) { scoopSoundPlayTime = 0.0; } } // time delay method for playing afterburner sounds // this overlaps two sounds each 2 seconds long, but with a 0.75s // crossfade - (void) updateAfterburnerSound { static uint8_t which = 0; if (!afterburner_engaged) // end the loop cycle { afterburnerSoundLooping = NO; } if (afterburnerSoundLooping) { [sAfterburnerSources[which] play]; which = !which; [self performSelector:@selector(updateAfterburnerSound) withObject:NULL afterDelay:1.25]; // and swap sounds in 1.25s time } } - (void) startAfterburnerSound { if (!afterburnerSoundLooping) { afterburnerSoundLooping = YES; [self updateAfterburnerSound]; } } - (void) stopAfterburnerSound { // Do nothing, stop is detected in updateAfterburnerSound } - (void) playCloakingDeviceInsufficientEnergy { [self playInterfaceBeep:@"[cloaking-device-insufficent-energy]"]; } - (void) playBuyCommodity { [sBuySellSourcePool playSoundWithKey:@"[buy-commodity]"]; } - (void) playBuyShip { [sBuySellSourcePool playSoundWithKey:@"[buy-ship]"]; } - (void) playSellCommodity { [sBuySellSourcePool playSoundWithKey:@"[sell-commodity]"]; } - (void) playCantBuyCommodity { [sBuySellSourcePool playSoundWithKey:@"[could-not-buy-commodity]"]; } - (void) playCantSellCommodity { [sBuySellSourcePool playSoundWithKey:@"[could-not-sell-commodity]"]; } - (void) playCantBuyShip { [sBuySellSourcePool playSoundWithKey:@"[could-not-buy-ship]"]; } - (void) playStandardHyperspace { [sHyperspaceSoundSource playCustomSoundWithKey:@"[hyperspace-countdown-begun]"]; } - (void) playGalacticHyperspace { [sHyperspaceSoundSource playCustomSoundWithKey:@"[galactic-hyperspace-countdown-begun]"]; } - (void) playHyperspaceAborted { [sHyperspaceSoundSource playCustomSoundWithKey:@"[hyperspace-countdown-aborted]"]; } - (void) playHitByECMSound { if (![sEcmSource isPlaying]) [sEcmSource playCustomSoundWithKey:@"[player-hit-by-ecm]"]; } - (void) playFiredECMSound { if (![sEcmSource isPlaying]) [sEcmSource playCustomSoundWithKey:@"[player-fired-ecm]"]; } - (void) playLaunchFromStation { [sBreakPatternSource playCustomSoundWithKey:@"[player-launch-from-station]"]; } - (void) playDockWithStation { [sBreakPatternSource playCustomSoundWithKey:@"[player-dock-with-station]"]; } - (void) playExitWitchspace { [sBreakPatternSource playCustomSoundWithKey:@"[player-exit-witchspace]"]; } - (void) playHostileWarning { [sWarningSoundPool playSoundWithKey:@"[hostile-warning]" priority:1 position:kInterfaceWarningPosition]; } - (void) playAlertConditionRed { [sWarningSoundPool playSoundWithKey:@"[alert-condition-red]" priority:2 position:kInterfaceWarningPosition]; } - (void) playIncomingMissile:(Vector)missileVector { [sWarningSoundPool playSoundWithKey:@"[incoming-missile]" priority:3 position:missileVector]; } - (void) playEnergyLow { [sWarningSoundPool playSoundWithKey:@"[energy-low]" priority:0.5 position:kInterfaceWarningPosition]; } - (void) playDockingDenied { [sWarningSoundPool playSoundWithKey:@"[autopilot-denied]" priority:1 position:kInterfaceWarningPosition]; } - (void) playWitchjumpFailure { [sWarningSoundPool playSoundWithKey:@"[witchdrive-failure]" priority:1.5 position:kWitchspacePosition]; } - (void) playWitchjumpMisjump { [sWarningSoundPool playSoundWithKey:@"[witchdrive-malfunction]" priority:1.5 position:kWitchspacePosition]; } - (void) playWitchjumpBlocked { [sWarningSoundPool playSoundWithKey:@"[witch-blocked-by-@]" priority:1.3 position:kWitchspacePosition]; } - (void) playWitchjumpDistanceTooGreat { [sWarningSoundPool playSoundWithKey:@"[witch-too-far]" priority:1.3 position:kWitchspacePosition]; } - (void) playWitchjumpInsufficientFuel { [sWarningSoundPool playSoundWithKey:@"[witch-no-fuel]" priority:1.3 position:kWitchspacePosition]; } - (void) playFuelLeak { [sWarningSoundPool playSoundWithKey:@"[fuel-leak]" priority:0.5 position:kWitchspacePosition]; } - (void) playShieldHit:(Vector)attackVector { [sDamageSoundPool playSoundWithKey:@"[player-hit-by-weapon]" position:attackVector]; } - (void) playDirectHit:(Vector)attackVector { [sDamageSoundPool playSoundWithKey:@"[player-direct-hit]" position:attackVector]; } - (void) playScrapeDamage:(Vector)attackVector { [sDamageSoundPool playSoundWithKey:@"[player-scrape-damage]" position:attackVector]; } - (void) playLaserHit:(BOOL)hit offset:(Vector)weaponOffset { if (hit) { [sWeaponSoundPool playSoundWithKey:@"[player-laser-hit]" priority:1.0 expiryTime:0.05 overlap:YES position:weaponOffset]; } else { [sWeaponSoundPool playSoundWithKey:@"[player-laser-miss]" priority:1.0 expiryTime:0.05 overlap:YES position:weaponOffset]; } } - (void) playWeaponOverheated:(Vector)weaponOffset { [sWeaponSoundPool playSoundWithKey:@"[weapon-overheat]" overlap:NO position:weaponOffset]; } - (void) playMissileLaunched:(Vector)weaponOffset { [sWeaponSoundPool playSoundWithKey:@"[missile-launched]" position:weaponOffset]; } - (void) playMineLaunched:(Vector)weaponOffset { [sWeaponSoundPool playSoundWithKey:@"[mine-launched]" position:weaponOffset]; } - (void) playEscapePodScooped { [sMiscSoundPool playSoundWithKey:@"[escape-pod-scooped]" position:kInterfaceBeepPosition]; } - (void) playAegisCloseToPlanet { [sMiscSoundPool playSoundWithKey:@"[aegis-planet]" position:kInterfaceBeepPosition]; } - (void) playAegisCloseToStation { [sMiscSoundPool playSoundWithKey:@"[aegis-station]" position:kInterfaceBeepPosition]; } - (void) playGameOver { [sMiscSoundPool playSoundWithKey:@"[game-over]"]; } - (void) playLegacyScriptSound:(NSString *)key { [sMiscSoundPool playSoundWithKey:key priority:1.1]; } @end oolite-1.82/src/Core/Entities/PlayerEntityStickMapper.h000066400000000000000000000035761256642440500231720ustar00rootroot00000000000000/* PlayerEntityStickMapper.h Joystick support for SDL implementation of Oolite. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "PlayerEntity.h" #import "GuiDisplayGen.h" #import "MyOpenGLView.h" #import "Universe.h" #define MAX_ROWS_FUNCTIONS 12 #define GUI_ROW_STICKNAME 1 #define GUI_ROW_STICKPROFILE 2 #define GUI_ROW_HEADING 4 #define GUI_ROW_FUNCSTART 5 #define GUI_ROW_FUNCEND (GUI_ROW_FUNCSTART + MAX_ROWS_FUNCTIONS - 1) #define GUI_ROW_INSTRUCT 18 // Dictionary keys #define KEY_GUIDESC @"guiDesc" #define KEY_ALLOWABLE @"allowable" #define KEY_AXISFN @"axisfunc" #define KEY_BUTTONFN @"buttonfunc" @interface PlayerEntity (StickMapper) - (void) setGuiToStickMapperScreen: (unsigned)skip resetCurrentRow: (BOOL) resetCurrentRow; - (void) setGuiToStickMapperScreen: (unsigned)skip; - (void) stickMapperInputHandler: (GuiDisplayGen *)gui view: (MyOpenGLView *)gameView; // Callback method - (void) updateFunction: (NSDictionary *)hwDict; // Future: populate via plist - (NSDictionary *)makeStickGuiDict: (NSString *)what allowable: (int)allowable axisfn: (int)axisfn butfn: (int)butfn; @end oolite-1.82/src/Core/Entities/PlayerEntityStickMapper.m000066400000000000000000000465401256642440500231750ustar00rootroot00000000000000/* PlayerEntityStickMapper.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "PlayerEntityStickMapper.h" #import "PlayerEntityControls.h" #import "PlayerEntityStickProfile.h" #import "OOJoystickManager.h" #import "OOTexture.h" #import "OOCollectionExtractors.h" @interface PlayerEntity (StickMapperInternal) - (void) removeFunction:(int)selFunctionIdx; - (NSArray *)stickFunctionList; - (void)displayFunctionList:(GuiDisplayGen *)gui skip:(NSUInteger) skip; - (NSString *)describeStickDict:(NSDictionary *)stickDict; - (NSString *)hwToString:(int)hwFlags; @end @implementation PlayerEntity (StickMapper) - (void) setGuiToStickMapperScreen:(unsigned)skip { [self setGuiToStickMapperScreen: skip resetCurrentRow: NO]; } - (void) setGuiToStickMapperScreen:(unsigned)skip resetCurrentRow: (BOOL) resetCurrentRow { GuiDisplayGen *gui = [UNIVERSE gui]; OOJoystickManager *stickHandler = [OOJoystickManager sharedStickHandler]; NSArray *stickList = [stickHandler listSticks]; unsigned i; OOGUITabStop tabStop[GUI_MAX_COLUMNS]; tabStop[0] = 50; tabStop[1] = 210; tabStop[2] = 320; [gui setTabStops:tabStop]; gui_screen = GUI_SCREEN_STICKMAPPER; [gui clear]; [gui setTitle:[NSString stringWithFormat:@"Configure Joysticks"]]; for(i=0; i < [stickList count]; i++) { [gui setArray:[NSArray arrayWithObjects: [NSString stringWithFormat: @"Stick %d", i+1], [stickList objectAtIndex: i], nil] forRow:i + GUI_ROW_STICKNAME]; } [gui setArray: [NSArray arrayWithObjects: DESC(@"stickmapper-profile"), nil] forRow: GUI_ROW_STICKPROFILE]; [gui setKey: GUI_KEY_OK forRow: GUI_ROW_STICKPROFILE]; [self displayFunctionList:gui skip:skip]; [gui setArray:[NSArray arrayWithObject:@"Select a function and press Enter to modify or 'u' to unset."] forRow:GUI_ROW_INSTRUCT]; if (resetCurrentRow) { [gui setSelectedRow: GUI_ROW_STICKPROFILE]; } [[UNIVERSE gameView] supressKeysUntilKeyUp]; [gui setForegroundTextureKey:[self status] == STATUS_DOCKED ? @"docked_overlay" : @"paused_overlay"]; [gui setBackgroundTextureKey:@"settings"]; } - (void) stickMapperInputHandler:(GuiDisplayGen *)gui view:(MyOpenGLView *)gameView { OOJoystickManager *stickHandler = [OOJoystickManager sharedStickHandler]; // Don't do anything if the user is supposed to be selecting // a function - other than look for Escape. if(waitingForStickCallback) { if([gameView isDown: 27]) { [stickHandler clearCallback]; [gui setArray: [NSArray arrayWithObjects: @"Function setting aborted.", nil] forRow: GUI_ROW_INSTRUCT]; waitingForStickCallback=NO; } // Break out now. return; } [self handleGUIUpDownArrowKeys]; if ([gui selectedRow] == GUI_ROW_STICKPROFILE && [gameView isDown: 13]) { [self setGuiToStickProfileScreen: gui]; return; } NSString* key = [gui keyForRow: [gui selectedRow]]; if ([key hasPrefix:@"Index:"]) selFunctionIdx=[[[key componentsSeparatedByString:@":"] objectAtIndex: 1] intValue]; else selFunctionIdx=-1; if([gameView isDown: 13]) { if ([key hasPrefix:@"More:"]) { int from_function = [[[key componentsSeparatedByString:@":"] objectAtIndex: 1] intValue]; if (from_function < 0) from_function = 0; [self setGuiToStickMapperScreen:from_function]; if ([[UNIVERSE gui] selectedRow] < 0) [[UNIVERSE gui] setSelectedRow: GUI_ROW_FUNCSTART]; if (from_function == 0) [[UNIVERSE gui] setSelectedRow: GUI_ROW_FUNCSTART + MAX_ROWS_FUNCTIONS - 1]; return; } NSDictionary *entry=[stickFunctions objectAtIndex: selFunctionIdx]; int hw=[(NSNumber *)[entry objectForKey: KEY_ALLOWABLE] intValue]; [stickHandler setCallback: @selector(updateFunction:) object: self hardware: hw]; // Print instructions NSString *instructions; switch(hw) { case HW_AXIS: instructions = @"Fully deflect the axis you want to use for this function. Esc aborts."; break; case HW_BUTTON: instructions = @"Press the button you want to use for this function. Esc aborts."; break; default: instructions = @"Press the button or deflect the axis you want to use for this function."; } [gui setArray: [NSArray arrayWithObjects: instructions, nil] forRow: GUI_ROW_INSTRUCT]; waitingForStickCallback=YES; } if([gameView isDown: 'u']) { if (selFunctionIdx >= 0) [self removeFunction: selFunctionIdx]; } } // Callback function, called by JoystickHandler when the callback // is set. The dictionary contains the thing that was pressed/moved. - (void) updateFunction: (NSDictionary *)hwDict { OOJoystickManager *stickHandler = [OOJoystickManager sharedStickHandler]; waitingForStickCallback = NO; // Right time and the right place? if(gui_screen != GUI_SCREEN_STICKMAPPER) { OOLog(@"joystick.configure.error", @"%s called when not on stick mapper screen.", __PRETTY_FUNCTION__); return; } // What moved? int function; NSDictionary *entry = [stickFunctions objectAtIndex:selFunctionIdx]; if([hwDict oo_boolForKey:STICK_ISAXIS]) { function=[entry oo_intForKey: KEY_AXISFN]; if (function == AXIS_THRUST) { [stickHandler unsetButtonFunction:BUTTON_INCTHRUST]; [stickHandler unsetButtonFunction:BUTTON_DECTHRUST]; } #if OO_FOV_INFLIGHT_CONTROL_ENABLED if (function == AXIS_FIELD_OF_VIEW) { [stickHandler unsetButtonFunction:BUTTON_INC_FIELD_OF_VIEW]; [stickHandler unsetButtonFunction:BUTTON_DEC_FIELD_OF_VIEW]; } #endif if (function == AXIS_VIEWX) { [stickHandler unsetButtonFunction:BUTTON_VIEWPORT]; [stickHandler unsetButtonFunction:BUTTON_VIEWSTARBOARD]; } if (function == AXIS_VIEWY) { [stickHandler unsetButtonFunction:BUTTON_VIEWFORWARD]; [stickHandler unsetButtonFunction:BUTTON_VIEWAFT]; } } else { function = [entry oo_intForKey:KEY_BUTTONFN]; if (function == BUTTON_INCTHRUST || function == BUTTON_DECTHRUST) { [stickHandler unsetAxisFunction:AXIS_THRUST]; } #if OO_FOV_INFLIGHT_CONTROL_ENABLED if (function == BUTTON_INC_FIELD_OF_VIEW || function == BUTTON_DEC_FIELD_OF_VIEW) { [stickHandler unsetAxisFunction:AXIS_FIELD_OF_VIEW]; } #endif if (function == BUTTON_VIEWPORT || function == BUTTON_VIEWSTARBOARD) { [stickHandler unsetAxisFunction:AXIS_VIEWX]; } if (function == BUTTON_VIEWFORWARD || function == BUTTON_VIEWAFT) { [stickHandler unsetAxisFunction:AXIS_VIEWY]; } } [stickHandler setFunction:function withDict:hwDict]; [stickHandler saveStickSettings]; // Update the GUI (this will refresh the function list). unsigned skip; if (selFunctionIdx < MAX_ROWS_FUNCTIONS) { skip = 0; } else { skip = ((selFunctionIdx - 1) / (MAX_ROWS_FUNCTIONS - 2)) * (MAX_ROWS_FUNCTIONS - 2) + 1; } [self setGuiToStickMapperScreen:skip]; } - (void) removeFunction:(int)idx { OOJoystickManager *stickHandler = [OOJoystickManager sharedStickHandler]; NSDictionary *entry = [stickFunctions objectAtIndex:idx]; NSNumber *butfunc = [entry objectForKey:KEY_BUTTONFN]; NSNumber *axfunc = [entry objectForKey:KEY_AXISFN]; selFunctionIdx = idx; // Some things can have either axis or buttons - make sure we clear // both! if(butfunc) { [stickHandler unsetButtonFunction:[butfunc intValue]]; } if(axfunc) { [stickHandler unsetAxisFunction:[axfunc intValue]]; } [stickHandler saveStickSettings]; unsigned skip; if (selFunctionIdx < MAX_ROWS_FUNCTIONS) skip = 0; else skip = ((selFunctionIdx - 1) / (MAX_ROWS_FUNCTIONS - 2)) * (MAX_ROWS_FUNCTIONS - 2) + 1; [self setGuiToStickMapperScreen: skip]; } - (void) displayFunctionList:(GuiDisplayGen *)gui skip:(NSUInteger)skip { OOJoystickManager *stickHandler = [OOJoystickManager sharedStickHandler]; [gui setColor:[OOColor greenColor] forRow: GUI_ROW_HEADING]; [gui setArray:[NSArray arrayWithObjects: @"Function", @"Assigned to", @"Type", nil] forRow:GUI_ROW_HEADING]; if(!stickFunctions) { stickFunctions = [[self stickFunctionList] retain]; } NSDictionary *assignedAxes = [stickHandler axisFunctions]; NSDictionary *assignedButs = [stickHandler buttonFunctions]; NSUInteger i, n_functions = [stickFunctions count]; NSInteger n_rows, start_row, previous = 0; if (skip >= n_functions) skip = n_functions - 1; if (n_functions < MAX_ROWS_FUNCTIONS) { skip = 0; previous = 0; n_rows = MAX_ROWS_FUNCTIONS; start_row = GUI_ROW_FUNCSTART; } else { n_rows = MAX_ROWS_FUNCTIONS - 1; start_row = GUI_ROW_FUNCSTART; if (skip > 0) { n_rows -= 1; start_row += 1; if (skip > MAX_ROWS_FUNCTIONS) previous = skip - (MAX_ROWS_FUNCTIONS - 2); else previous = 0; } } if (n_functions > 0) { if (skip > 0) { [gui setColor:[OOColor greenColor] forRow:GUI_ROW_FUNCSTART]; [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-back"), @" <-- ", nil] forRow:GUI_ROW_FUNCSTART]; [gui setKey:[NSString stringWithFormat:@"More:%ld", previous] forRow:GUI_ROW_FUNCSTART]; } for(i=0; i < (n_functions - skip) && (int)i < n_rows; i++) { NSString *allowedThings; NSString *assignment; NSDictionary *entry = [stickFunctions objectAtIndex: i + skip]; NSString *axFuncKey = [entry oo_stringForKey:KEY_AXISFN]; NSString *butFuncKey = [entry oo_stringForKey:KEY_BUTTONFN]; int allowable = [entry oo_intForKey:KEY_ALLOWABLE]; switch(allowable) { case HW_AXIS: allowedThings=@"Axis"; assignment=[self describeStickDict: [assignedAxes objectForKey: axFuncKey]]; break; case HW_BUTTON: allowedThings=@"Button"; assignment=[self describeStickDict: [assignedButs objectForKey: butFuncKey]]; break; default: allowedThings=@"Axis/Button"; // axis has priority assignment=[self describeStickDict: [assignedAxes objectForKey: axFuncKey]]; if(!assignment) assignment=[self describeStickDict: [assignedButs objectForKey: butFuncKey]]; } // Find out what's assigned for this function currently. if (assignment == nil) { assignment = @" - "; } [gui setArray: [NSArray arrayWithObjects: [entry objectForKey: KEY_GUIDESC], assignment, allowedThings, nil] forRow: i + start_row]; //[gui setKey: GUI_KEY_OK forRow: i + start_row]; [gui setKey: [NSString stringWithFormat: @"Index:%ld", i + skip] forRow: i + start_row]; } if (i < n_functions - skip) { [gui setColor: [OOColor greenColor] forRow: start_row + i]; [gui setArray: [NSArray arrayWithObjects: DESC(@"gui-more"), @" --> ", nil] forRow: start_row + i]; [gui setKey: [NSString stringWithFormat: @"More:%ld", n_rows + skip] forRow: start_row + i]; i++; } [gui setSelectableRange: NSMakeRange(GUI_ROW_STICKPROFILE, i + start_row - GUI_ROW_STICKPROFILE)]; } } - (NSString *) describeStickDict: (NSDictionary *)stickDict { NSString *desc=nil; if(stickDict) { int thingNumber=[(NSNumber *)[stickDict objectForKey: STICK_AXBUT] intValue]; int stickNumber=[(NSNumber *)[stickDict objectForKey: STICK_NUMBER] intValue]; // Button or axis? if([(NSNumber *)[stickDict objectForKey: STICK_ISAXIS] boolValue]) { desc=[NSString stringWithFormat: @"Stick %d axis %d", stickNumber+1, thingNumber+1]; } else if(thingNumber >= MAX_REAL_BUTTONS) { static const char dir[][6] = { "up", "right", "down", "left" }; desc=[NSString stringWithFormat: @"Stick %d hat %d %s", stickNumber+1, (thingNumber - MAX_REAL_BUTTONS) / 4 + 1, dir[thingNumber & 3]]; } else { desc=[NSString stringWithFormat: @"Stick %d button %d", stickNumber+1, thingNumber+1]; } } return desc; } - (NSString *)hwToString: (int)hwFlags { NSString *hwString; switch(hwFlags) { case HW_AXIS: hwString = @"axis"; break; case HW_BUTTON: hwString = @"button"; break; default: hwString = @"axis/button"; } return hwString; } // TODO: This data could be put into a plist (i18n or just modifiable by // the user). It is otherwise an ugly method, but it'll do for testing. - (NSArray *)stickFunctionList { NSMutableArray *funcList = [NSMutableArray array]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-roll") allowable:HW_AXIS axisfn:AXIS_ROLL butfn:STICK_NOFUNCTION]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-pitch") allowable:HW_AXIS axisfn:AXIS_PITCH butfn:STICK_NOFUNCTION]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-yaw") allowable:HW_AXIS axisfn:AXIS_YAW butfn:STICK_NOFUNCTION]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-increase-thrust") allowable:HW_AXIS|HW_BUTTON axisfn:AXIS_THRUST butfn:BUTTON_INCTHRUST]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-decrease-thrust") allowable:HW_AXIS|HW_BUTTON axisfn:AXIS_THRUST butfn:BUTTON_DECTHRUST]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-primary-weapon") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_FIRE]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-secondary-weapon") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_LAUNCHMISSILE]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-arm-secondary") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_ARMMISSILE]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-disarm-secondary") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_UNARM]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-target-nearest-incoming-missile") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_TARGETINCOMINGMISSILE]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-cycle-secondary") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_CYCLEMISSILE]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-weapons-online-toggle") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_WEAPONSONLINETOGGLE]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-prime-equipment") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_PRIMEEQUIPMENT]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-activate-equipment") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_ACTIVATEEQUIPMENT]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-mode-equipment") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_MODEEQUIPMENT]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-fastactivate-a") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_CLOAK]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-fastactivate-b") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_ENERGYBOMB]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-ECM") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_ECM]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-toggle-ID") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_ID]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-previous-target") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_PREVTARGET]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-next-target") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_NEXTTARGET]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-fuel-injection") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_FUELINJECT]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-hyperspeed") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_HYPERSPEED]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-hyperdrive") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_HYPERDRIVE]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-roll/pitch-precision-toggle") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_PRECISION]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-snapshot") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_SNAPSHOT]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-escape-pod") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_ESCAPE]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-scanner-zoom") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_SCANNERZOOM]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-scanner-unzoom") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_SCANNERUNZOOM]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-jettison") allowable:HW_BUTTON axisfn:STICK_NOFUNCTION butfn:BUTTON_JETTISON]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-view-forward") allowable:HW_AXIS|HW_BUTTON axisfn:AXIS_VIEWY butfn:BUTTON_VIEWFORWARD]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-view-aft") allowable:HW_AXIS|HW_BUTTON axisfn:AXIS_VIEWY butfn:BUTTON_VIEWAFT]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-view-port") allowable:HW_AXIS|HW_BUTTON axisfn:AXIS_VIEWX butfn:BUTTON_VIEWPORT]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-view-starboard") allowable:HW_AXIS|HW_BUTTON axisfn:AXIS_VIEWX butfn:BUTTON_VIEWSTARBOARD]]; #if OO_FOV_INFLIGHT_CONTROL_ENABLED [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-increase-field-of-view") allowable:HW_AXIS|HW_BUTTON axisfn:AXIS_FIELD_OF_VIEW butfn:BUTTON_INC_FIELD_OF_VIEW]]; [funcList addObject: [self makeStickGuiDict:DESC(@"stickmapper-decrease-field-of-view") allowable:HW_AXIS|HW_BUTTON axisfn:AXIS_FIELD_OF_VIEW butfn:BUTTON_DEC_FIELD_OF_VIEW]]; #endif return funcList; } - (NSDictionary *)makeStickGuiDict:(NSString *)what allowable:(int)allowable axisfn:(int)axisfn butfn:(int)butfn { NSMutableDictionary *guiDict = [NSMutableDictionary dictionary]; if ([what length] > 30) what = [[what substringToIndex:28] stringByAppendingString:@"..."]; [guiDict setObject: what forKey: KEY_GUIDESC]; [guiDict setObject: [NSNumber numberWithInt: allowable] forKey: KEY_ALLOWABLE]; if(axisfn >= 0) [guiDict setObject: [NSNumber numberWithInt: axisfn] forKey: KEY_AXISFN]; if(butfn >= 0) [guiDict setObject: [NSNumber numberWithInt: butfn] forKey: KEY_BUTTONFN]; return guiDict; } @end oolite-1.82/src/Core/Entities/PlayerEntityStickProfile.h000066400000000000000000000033221256642440500233330ustar00rootroot00000000000000/* PlayerEntityStickProfile.h GUI for managing joystick profile settings Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "PlayerEntity.h" #import "GuiDisplayGen.h" #import "MyOpenGLView.h" #import "OOJoystickProfile.h" #import "Universe.h" @interface PlayerEntity (StickProfile) - (void) setGuiToStickProfileScreen: (GuiDisplayGen *) gui; - (void) stickProfileInputHandler: (GuiDisplayGen *) gui view: (MyOpenGLView *) gameView; - (void) stickProfileGraphAxisProfile: (GLfloat) alpha screenAt: (Vector) screenAt screenSize: (NSSize) screenSize; @end @interface StickProfileScreen: NSObject { @private OOJoystickManager *stickHandler; NSUInteger current_axis; OOJoystickAxisProfile *profiles[3][2]; GuiDisplayGen *gui; NSRect graphRect; NSInteger selected_control_point; NSInteger dragged_control_point; NSInteger double_click_control_point; } - (id) init; - (void) dealloc; - (void) startGui: (GuiDisplayGen *) gui_display_gen; - (void) mouseDown: (NSPoint) position; - (void) mouseUp; - (void) deleteSelected; @end oolite-1.82/src/Core/Entities/PlayerEntityStickProfile.m000066400000000000000000000440551256642440500233500ustar00rootroot00000000000000/* PlayerEntityStickProfile.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "PlayerEntity.h" #import "PlayerEntityStickProfile.h" #import "PlayerEntityStickMapper.h" #import "OOJoystickManager.h" #import "OOJoystickProfile.h" #import "PlayerEntityControls.h" #import "PlayerEntitySound.h" #import "OOOpenGL.h" #import "OOMacroOpenGL.h" #import "HeadUpDisplay.h" #define GUI_ROW_STICKPROFILE_BACK 20 #define GUI_ROW_STICKPROFILE_AXIS 1 #define GUI_ROW_STICKPROFILE_DEADZONE 2 #define GUI_ROW_STICKPROFILE_PROFILE_TYPE 3 #define GUI_ROW_STICKPROFILE_POWER 4 #define GUI_ROW_STICKPROFILE_PARAM 5 static BOOL stickProfileArrow_pressed; @interface StickProfileScreen (StickProfileInternal) - (void) showScreen; - (void) nextAxis; - (NSString *) currentAxis; - (void) previousAxis; - (void) increaseDeadzone; - (void) decreaseDeadzone; - (void) nextProfileType; - (void) previousProfileType; - (void) IncreasePower; - (BOOL) currentProfileIsSpline; - (void) DecreasePower; - (void) IncreaseParam; - (void) DecreaseParam; - (void) saveSettings; - (void) graphProfile: (GLfloat) alpha at: (Vector) at size: (NSSize) size; - (void) startEdit; - (NSString *) profileType; @end @implementation PlayerEntity (StickProfile) - (void) setGuiToStickProfileScreen: (GuiDisplayGen *) gui { gui_screen = GUI_SCREEN_STICKPROFILE; [stickProfileScreen startGui: gui]; return; } - (void) stickProfileInputHandler: (GuiDisplayGen *) gui view: (MyOpenGLView *) gameView { if ([gameView isDown: gvMouseLeftButton]) { NSPoint mouse_position = NSMakePoint( [gameView virtualJoystickPosition].x * [gui size].width, [gameView virtualJoystickPosition].y * [gui size].height ); [stickProfileScreen mouseDown: mouse_position]; } else { [stickProfileScreen mouseUp]; } if ([gameView isDown: gvDeleteKey]) { [stickProfileScreen deleteSelected]; } [self handleGUIUpDownArrowKeys]; if ([gameView isDown:13] && [gui selectedRow] == GUI_ROW_STICKPROFILE_BACK) { [stickProfileScreen saveSettings]; [self setGuiToStickMapperScreen: 0 resetCurrentRow: YES]; } switch ([gui selectedRow]) { case GUI_ROW_STICKPROFILE_AXIS: if ([gameView isDown:key_gui_arrow_left]) { if (!stickProfileArrow_pressed && ![gameView isDown: key_gui_arrow_right]) { [stickProfileScreen previousAxis]; stickProfileArrow_pressed = YES; } } else if ([gameView isDown: key_gui_arrow_right]) { if (!stickProfileArrow_pressed && ![gameView isDown: key_gui_arrow_left]) { [stickProfileScreen nextAxis]; stickProfileArrow_pressed = YES; } } else { stickProfileArrow_pressed = NO; } break; case GUI_ROW_STICKPROFILE_DEADZONE: if ([gameView isDown:key_gui_arrow_left]) { if (!stickProfileArrow_pressed && ![gameView isDown: key_gui_arrow_right]) { [stickProfileScreen decreaseDeadzone]; stickProfileArrow_pressed = YES; } } else if ([gameView isDown: key_gui_arrow_right]) { if (!stickProfileArrow_pressed && ![gameView isDown: key_gui_arrow_left]) { [stickProfileScreen increaseDeadzone]; stickProfileArrow_pressed = YES; } } else { stickProfileArrow_pressed = NO; } break; case GUI_ROW_STICKPROFILE_PROFILE_TYPE: if ([gameView isDown:key_gui_arrow_left]) { if (!stickProfileArrow_pressed && ![gameView isDown: key_gui_arrow_right]) { [stickProfileScreen previousProfileType]; stickProfileArrow_pressed = YES; } } else if ([gameView isDown: key_gui_arrow_right]) { if (!stickProfileArrow_pressed && ![gameView isDown: key_gui_arrow_left]) { [stickProfileScreen nextProfileType]; stickProfileArrow_pressed = YES; } } else { stickProfileArrow_pressed = NO; } break; } if (![stickProfileScreen currentProfileIsSpline]) { if ([gui selectedRow] == GUI_ROW_STICKPROFILE_POWER) { if ([gameView isDown:key_gui_arrow_left]) { if (!stickProfileArrow_pressed && ![gameView isDown: key_gui_arrow_right]) { [stickProfileScreen DecreasePower]; stickProfileArrow_pressed = YES; } } else if ([gameView isDown: key_gui_arrow_right]) { if (!stickProfileArrow_pressed && ![gameView isDown: key_gui_arrow_left]) { [stickProfileScreen IncreasePower]; stickProfileArrow_pressed = YES; } } else { stickProfileArrow_pressed = NO; } } else if ([gui selectedRow] == GUI_ROW_STICKPROFILE_PARAM) { if ([gameView isDown:key_gui_arrow_left]) { if (!stickProfileArrow_pressed && ![gameView isDown: key_gui_arrow_right]) { [stickProfileScreen DecreaseParam]; stickProfileArrow_pressed = YES; } } else if ([gameView isDown: key_gui_arrow_right]) { if (!stickProfileArrow_pressed && ![gameView isDown: key_gui_arrow_left]) { [stickProfileScreen IncreaseParam]; stickProfileArrow_pressed = YES; } } else { stickProfileArrow_pressed = NO; } } } return; } - (void) stickProfileGraphAxisProfile: (GLfloat) alpha screenAt: (Vector) screenAt screenSize: (NSSize) screenSize { [stickProfileScreen graphProfile: alpha at: make_vector(screenAt.x - 110.0, screenAt.y - 100, screenAt.z) size: NSMakeSize(220,220)]; return; } @end @implementation StickProfileScreen - (id) init { int i, j; if ((self = [super init])) { stickHandler = [OOJoystickManager sharedStickHandler]; current_axis = AXIS_ROLL; for (i = 0; i < 3; i++) { for (j = 0; j < 2; j++) { profiles[i][j] = nil; } } } return self; } - (void) dealloc { int i, j; for (i = 0; i < 3; i++) { for (j = 0; j < 2; j++) { [profiles[i][j] release]; } } [super dealloc]; } - (void) startGui: (GuiDisplayGen *) gui_display_gen { gui = gui_display_gen; [self startEdit]; [gui clear]; [gui setTitle:DESC(@"oolite-stickprofile-title")]; [self showScreen]; [gui setSelectedRow: GUI_ROW_STICKPROFILE_AXIS]; return; } - (void) mouseDown: (NSPoint) position { OOJoystickAxisProfile *profile = [stickHandler getProfileForAxis: current_axis]; OOJoystickSplineAxisProfile *spline_profile; NSPoint spline_position; if (![profile isKindOfClass: [OOJoystickSplineAxisProfile class]]) { return; } spline_profile = (OOJoystickSplineAxisProfile *)profile; spline_position.x = (position.x - graphRect.origin.x - 10) / (graphRect.size.width - 20); spline_position.y = (-position.y - graphRect.origin.y - 10) / (graphRect.size.height - 20); if (spline_position.x >= 0.0 && spline_position.x <= 1.0 && spline_position.y >= 0.0 && spline_position.y <= 1.0) { if (dragged_control_point < 0) { selected_control_point = [spline_profile addControl: spline_position]; dragged_control_point = selected_control_point; double_click_control_point = -1; } else { [spline_profile moveControl: dragged_control_point point: spline_position]; } [stickHandler saveStickSettings]; } return; } - (void) mouseUp { if (selected_control_point >= 0) { double_click_control_point = selected_control_point; } dragged_control_point = -1; return; } - (void) deleteSelected { OOJoystickAxisProfile *profile = [stickHandler getProfileForAxis: current_axis]; OOJoystickSplineAxisProfile *spline_profile; if ([profile isKindOfClass: [OOJoystickSplineAxisProfile class]] && selected_control_point >= 0) { spline_profile = (OOJoystickSplineAxisProfile *)profile; [spline_profile removeControl: selected_control_point]; selected_control_point = -1; dragged_control_point = -1; [stickHandler saveStickSettings]; } return; } @end @implementation StickProfileScreen (StickProfileInternal) - (void) nextAxis { if (current_axis == AXIS_ROLL) current_axis = AXIS_PITCH; else if (current_axis == AXIS_PITCH) current_axis = AXIS_YAW; [self showScreen]; return; } - (void) previousAxis { if (current_axis == AXIS_PITCH) current_axis = AXIS_ROLL; else if (current_axis == AXIS_YAW) current_axis = AXIS_PITCH; [self showScreen]; return; } - (NSString *) currentAxis { switch (current_axis) { case AXIS_ROLL: return DESC(@"stickmapper-roll"); case AXIS_PITCH: return DESC(@"stickmapper-pitch"); case AXIS_YAW: return DESC(@"stickmapper-yaw"); } return @""; } - (void) increaseDeadzone { OOJoystickAxisProfile *profile = [stickHandler getProfileForAxis: current_axis]; if (profile) { [profile setDeadzone: [profile deadzone] + STICK_MAX_DEADZONE / 20]; } [self showScreen]; return; } - (void) decreaseDeadzone { OOJoystickAxisProfile *profile = [stickHandler getProfileForAxis: current_axis]; if (profile) { [profile setDeadzone: [profile deadzone] - STICK_MAX_DEADZONE / 20]; } [self showScreen]; return; } - (void) nextProfileType { OOJoystickAxisProfile *profile = [stickHandler getProfileForAxis: current_axis]; double deadzone; if ([profile isKindOfClass: [OOJoystickStandardAxisProfile class]]) { deadzone = [profile deadzone]; [profiles[current_axis][0] release]; profiles[current_axis][0] = [profile retain]; if (!profiles[current_axis][1]) { profiles[current_axis][1] = [[OOJoystickSplineAxisProfile alloc] init]; } [profiles[current_axis][1] setDeadzone: deadzone]; [stickHandler setProfile: profiles[current_axis][1] forAxis: current_axis]; [stickHandler saveStickSettings]; } [self showScreen]; return; } - (void) previousProfileType { OOJoystickAxisProfile *profile = [stickHandler getProfileForAxis: current_axis]; double deadzone; if ([profile isKindOfClass: [OOJoystickSplineAxisProfile class]]) { deadzone = [profile deadzone]; [profiles[current_axis][1] release]; profiles[current_axis][1] = [profile retain]; if (!profiles[current_axis][0]) { profiles[current_axis][0] = [[OOJoystickStandardAxisProfile alloc] init]; } [profiles[current_axis][0] setDeadzone: deadzone]; [stickHandler setProfile: profiles[current_axis][0] forAxis: current_axis]; [stickHandler saveStickSettings]; } [self showScreen]; return; } - (BOOL) currentProfileIsSpline { if ([[stickHandler getProfileForAxis: current_axis] isKindOfClass: [OOJoystickSplineAxisProfile class]]) { return YES; } return NO; } - (void) IncreasePower { OOJoystickAxisProfile *profile = [stickHandler getProfileForAxis: current_axis]; OOJoystickStandardAxisProfile *standard_profile; if (profile && [profile isKindOfClass: [OOJoystickStandardAxisProfile class]]) { standard_profile = (OOJoystickStandardAxisProfile *) profile; [standard_profile setPower: [standard_profile power] + STICKPROFILE_MAX_POWER / 20]; [stickHandler saveStickSettings]; } [self showScreen]; return; } - (void) DecreasePower { OOJoystickAxisProfile *profile = [stickHandler getProfileForAxis: current_axis]; OOJoystickStandardAxisProfile *standard_profile; if (profile && [profile isKindOfClass: [OOJoystickStandardAxisProfile class]]) { standard_profile = (OOJoystickStandardAxisProfile *) profile; [standard_profile setPower: [standard_profile power] - STICKPROFILE_MAX_POWER / 20]; [stickHandler saveStickSettings]; } [self showScreen]; return; } - (void) IncreaseParam { OOJoystickAxisProfile *profile = [stickHandler getProfileForAxis: current_axis]; OOJoystickStandardAxisProfile *standard_profile; if (profile && [profile isKindOfClass: [OOJoystickStandardAxisProfile class]]) { standard_profile = (OOJoystickStandardAxisProfile *) profile; [standard_profile setParameter: [standard_profile parameter] + 0.05]; [stickHandler saveStickSettings]; } [self showScreen]; return; } - (void) DecreaseParam { OOJoystickAxisProfile *profile = [stickHandler getProfileForAxis: current_axis]; OOJoystickStandardAxisProfile *standard_profile; if (profile && [profile isKindOfClass: [OOJoystickStandardAxisProfile class]]) { standard_profile = (OOJoystickStandardAxisProfile *) profile; [standard_profile setParameter: [standard_profile parameter] - 0.05]; [stickHandler saveStickSettings]; } [self showScreen]; return; } - (void) graphProfile: (GLfloat) alpha at: (Vector) at size: (NSSize) size { OOJoystickAxisProfile *profile = [stickHandler getProfileForAxis: current_axis]; OOJoystickSplineAxisProfile *spline_profile; NSInteger i; NSPoint point; NSArray *control_points; if (!profile) return; graphRect = NSMakeRect(at.x, at.y, size.width, size.height); OO_ENTER_OPENGL(); OOGL(glColor4f(0.2,0.2,0.5,alpha)); OOGLBEGIN(GL_QUADS); glVertex3f(at.x,at.y,at.z); glVertex3f(at.x + size.width,at.y,at.z); glVertex3f(at.x + size.width,at.y + size.height,at.z); glVertex3f(at.x,at.y + size.height,at.z); OOGLEND(); OOGL(glColor4f(0.9,0.9,0.9,alpha)); OOGL(GLScaledLineWidth(2.0f)); OOGLBEGIN(GL_LINE_STRIP); for (i = 0; i <= size.width - 20; i++) { glVertex3f(at.x+i+10,at.y+10+(size.height-20)*[profile rawValue:((float)i)/(size.width-20)],at.z); } OOGLEND(); OOGL(glColor4f(0.5,0.0,0.5,alpha)); GLDrawFilledOval(at.x+10,at.y+10,at.z,NSMakeSize(4,4),20); GLDrawFilledOval(at.x+size.width-10,at.y+size.height-10,at.z,NSMakeSize(4,4),20); if ([profile isKindOfClass: [OOJoystickSplineAxisProfile class]]) { spline_profile = (OOJoystickSplineAxisProfile *)profile; control_points = [spline_profile controlPoints]; for (i = 0; i < (NSInteger)[control_points count]; i++) { if (i == selected_control_point) { OOGL(glColor4f(1.0,0.0,0.0,alpha)); } else { OOGL(glColor4f(0.0,1.0,0.0,alpha)); } point = [[control_points objectAtIndex: i] pointValue]; GLDrawFilledOval(at.x+10+point.x*(size.width - 20),at.y+10+point.y*(size.height-20),at.z,NSMakeSize(4,4),20); } } OOGL(glColor4f(0.9,0.9,0.0,alpha)); OODrawStringAligned(DESC(@"oolite-stickprofile-movement"), at.x + size.width - 5, at.y, at.z, NSMakeSize(8,10), YES); OODrawString(DESC(@"oolite-stickprofile-response"), at.x, at.y + size.height - 10, at.z, NSMakeSize(8,10)); return; } - (void) startEdit { int i, j; for (i = 0; i < 3; i++) { for (j = 0; j < 2; j++) { [profiles[i][j] release]; profiles[i][j] = nil; } } current_axis = AXIS_ROLL; selected_control_point = -1; dragged_control_point = -1; double_click_control_point = -1; return; } - (void) saveSettings { [stickHandler saveStickSettings]; return; } - (void) showScreen { OOJoystickAxisProfile *profile = [stickHandler getProfileForAxis: current_axis]; OOJoystickStandardAxisProfile *standard_profile; NSString *v1 = @"||||||||||||||||||||"; NSString *v2 = @"...................."; int bars; double value; double power; OOGUITabStop tabStop[GUI_MAX_COLUMNS]; tabStop[0] = 50; tabStop[1] = 140; [gui setTabStops:tabStop]; [gui setArray: [NSArray arrayWithObjects: DESC(@"oolite-stickprofile-axis"), [self currentAxis], nil ] forRow: GUI_ROW_STICKPROFILE_AXIS]; [gui setKey: GUI_KEY_OK forRow: GUI_ROW_STICKPROFILE_AXIS]; value = [profile deadzone]; bars = (int)(20 * value / STICK_MAX_DEADZONE + 0.5); if (bars < 0) bars = 0; if (bars > 20) bars = 20; [gui setArray: [NSArray arrayWithObjects: DESC(@"oolite-stickprofile-deadzone"), [NSString stringWithFormat: @"%@%@ (%0.4f)", [v1 substringToIndex: bars], [v2 substringToIndex: 20 - bars], value], nil] forRow: GUI_ROW_STICKPROFILE_DEADZONE]; [gui setKey: GUI_KEY_OK forRow: GUI_ROW_STICKPROFILE_DEADZONE]; [gui setArray: [NSArray arrayWithObjects: DESC(@"oolite-stickprofile-profile-type"), [self profileType], nil ] forRow: GUI_ROW_STICKPROFILE_PROFILE_TYPE]; [gui setKey: GUI_KEY_OK forRow: GUI_ROW_STICKPROFILE_PROFILE_TYPE]; if ([profile isKindOfClass:[OOJoystickStandardAxisProfile class]]) { standard_profile = (OOJoystickStandardAxisProfile*) profile; power = [standard_profile power]; bars = (int)(20*power / STICKPROFILE_MAX_POWER + 0.5); if (bars < 0) bars = 0; if (bars > 20) bars = 20; [gui setArray: [NSArray arrayWithObjects: DESC(@"oolite-stickprofile-range"), [NSString stringWithFormat: @"%@%@ (%.1f) ", [v1 substringToIndex: bars], [v2 substringToIndex: 20 - bars], power], nil] forRow: GUI_ROW_STICKPROFILE_POWER]; [gui setKey: GUI_KEY_OK forRow: GUI_ROW_STICKPROFILE_POWER]; value = [standard_profile parameter]; bars = 20*value; if (bars < 0) bars = 0; if (bars > 20) bars = 20; [gui setArray: [NSArray arrayWithObjects: DESC(@"oolite-stickprofile-sensitivity"), [NSString stringWithFormat: @"%@%@ (%0.2f) ", [v1 substringToIndex: bars], [v2 substringToIndex: 20 - bars], value], nil] forRow: GUI_ROW_STICKPROFILE_PARAM]; [gui setKey: GUI_KEY_OK forRow: GUI_ROW_STICKPROFILE_PARAM]; [gui setColor:[OOColor yellowColor] forRow: GUI_ROW_STICKPROFILE_PARAM]; } else { [gui setText: @"" forRow: GUI_ROW_STICKPROFILE_POWER]; [gui setKey: GUI_KEY_SKIP forRow: GUI_ROW_STICKPROFILE_POWER]; [gui setText: DESC(@"oolite-stickprofile-spline-instructions") forRow: GUI_ROW_STICKPROFILE_PARAM]; [gui setKey: GUI_KEY_SKIP forRow: GUI_ROW_STICKPROFILE_PARAM]; [gui setColor:[OOColor magentaColor] forRow: GUI_ROW_STICKPROFILE_PARAM]; } [gui setText: DESC(@"gui-back") forRow: GUI_ROW_STICKPROFILE_BACK]; [gui setKey: GUI_KEY_OK forRow: GUI_ROW_STICKPROFILE_BACK]; [gui setSelectableRange: NSMakeRange(1, GUI_ROW_STICKPROFILE_BACK)]; [[UNIVERSE gameView] supressKeysUntilKeyUp]; [gui setForegroundTextureKey:[PLAYER status] == STATUS_DOCKED ? @"docked_overlay" : @"paused_overlay"]; [gui setBackgroundTextureKey: @"settings"]; return; } - (NSString *) profileType { OOJoystickAxisProfile *profile = [stickHandler getProfileForAxis: current_axis]; if ([profile isKindOfClass: [OOJoystickStandardAxisProfile class]]) { return DESC(@"oolite-stickprofile-type-standard"); } if ([profile isKindOfClass: [OOJoystickSplineAxisProfile class]]) { return DESC(@"oolite-stickprofile-type-spline"); } return DESC(@"oolite-stickprofile-type-standard"); } @end oolite-1.82/src/Core/Entities/ProxyPlayerEntity.h000066400000000000000000000053521256642440500220630ustar00rootroot00000000000000/* ProxyPlayerEntity.h Ship entity which, in some respects, emulates a PlayerShip. In particular, at this time it implements the extra shader bindable methods of PlayerShip. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "PlayerEntity.h" @interface ProxyPlayerEntity: ShipEntity { @private float _fuelLeakRate; GLfloat _dialForwardShield; GLfloat _dialAftShield; OOMissileStatus _missileStatus; OOFuelScoopStatus _fuelScoopStatus; OOCompassMode _compassMode; OOAlertCondition _alertCondition; NSUInteger _trumbleCount; NSUInteger _tradeInFactor; unsigned _massLocked: 1, _atHyperspeed: 1, _dialIdentEngaged: 1; } - (void) copyValuesFromPlayer:(PlayerEntity *)player; // Default: 0 - (float) fuelLeakRate; - (void) setFuelLeakRate:(float)value; // Default: NO - (BOOL) massLocked; - (void) setMassLocked:(BOOL)value; // Default: NO - (BOOL) atHyperspeed; - (void) setAtHyperspeed:(BOOL)value; // Default: 1 - (GLfloat) dialForwardShield; - (void) setDialForwardShield:(GLfloat)value; // Default: 1 - (GLfloat) dialAftShield; - (void) setDialAftShield:(GLfloat)value; // Default: MISSILE_STATUS_SAFE - (OOMissileStatus) dialMissileStatus; - (void) setDialMissileStatus:(OOMissileStatus)value; // Default: SCOOP_STATUS_NOT_INSTALLED or SCOOP_STATUS_OKAY depending on equipment. - (OOFuelScoopStatus) dialFuelScoopStatus; - (void) setDialFuelScoopStatus:(OOFuelScoopStatus)value; // Default: COMPASS_MODE_BASIC or COMPASS_MODE_PLANET depending on equipment. - (OOCompassMode) compassMode; - (void) setCompassMode:(OOCompassMode)value; // Default: NO - (BOOL) dialIdentEngaged; - (void) setDialIdentEngaged:(BOOL)value; // Default: ALERT_CONDITION_DOCKED - (OOAlertCondition) alertCondition; - (void) setAlertCondition:(OOAlertCondition)condition; // Default: 0 - (NSUInteger) trumbleCount; - (void) setTrumbleCount:(NSUInteger)value; - (void) setTradeInFactor:(int)tif; - (int) tradeInFactor; @end @interface Entity (ProxyPlayer) // True for PlayerEntity or ProxyPlayerEntity. - (BOOL) isPlayerLikeShip; @end oolite-1.82/src/Core/Entities/ProxyPlayerEntity.m000066400000000000000000000075631256642440500220760ustar00rootroot00000000000000/* ProxyPlayerEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "ProxyPlayerEntity.h" @implementation ProxyPlayerEntity - (id)initWithKey:(NSString *)key definition:(NSDictionary *)dict { self = [super initWithKey:key definition:dict]; if (self != nil) { [self setDialForwardShield:1.0f]; [self setDialAftShield:1.0f]; [self setDialFuelScoopStatus:[self hasScoop] ? SCOOP_STATUS_OKAY : SCOOP_STATUS_NOT_INSTALLED]; [self setCompassMode:[self hasEquipmentItemProviding:@"EQ_ADVANCED_COMPASS"] ? COMPASS_MODE_PLANET : COMPASS_MODE_BASIC]; [self setTradeInFactor:95]; } return self; } - (void) copyValuesFromPlayer:(PlayerEntity *)player { if (player == nil) return; [self setFuelLeakRate:[player fuelLeakRate]]; [self setMassLocked:[player massLocked]]; [self setAtHyperspeed:[player atHyperspeed]]; [self setDialForwardShield:[player dialForwardShield]]; [self setDialAftShield:[player dialAftShield]]; [self setDialMissileStatus:[player dialMissileStatus]]; [self setDialFuelScoopStatus:[player dialFuelScoopStatus]]; [self setCompassMode:[player compassMode]]; [self setDialIdentEngaged:[player dialIdentEngaged]]; [self setAlertCondition:[player alertCondition]]; [self setTrumbleCount:[player trumbleCount]]; [self setTradeInFactor:[player tradeInFactor]]; } - (BOOL) isPlayerLikeShip { return YES; } - (float) fuelLeakRate { return _fuelLeakRate; } - (void) setFuelLeakRate:(float)value { _fuelLeakRate = fmax(value, 0.0f); } - (BOOL) massLocked { return _massLocked; } - (void) setMassLocked:(BOOL)value { _massLocked = !!value; } - (BOOL) atHyperspeed { return _atHyperspeed; } - (void) setAtHyperspeed:(BOOL)value { _atHyperspeed = !!value; } - (GLfloat) dialForwardShield { return _dialForwardShield; } - (void) setDialForwardShield:(GLfloat)value { _dialForwardShield = value; } - (GLfloat) dialAftShield { return _dialAftShield; } - (void) setDialAftShield:(GLfloat)value { _dialAftShield = value; } - (OOMissileStatus) dialMissileStatus { return _missileStatus; } - (void) setDialMissileStatus:(OOMissileStatus)value { _missileStatus = value; } - (OOFuelScoopStatus) dialFuelScoopStatus { return _fuelScoopStatus; } - (void) setDialFuelScoopStatus:(OOFuelScoopStatus)value { _fuelScoopStatus = value; } - (OOCompassMode) compassMode { return _compassMode; } - (void) setCompassMode:(OOCompassMode)value { _compassMode = value; } - (BOOL) dialIdentEngaged { return _dialIdentEngaged; } - (void) setDialIdentEngaged:(BOOL)value { _dialIdentEngaged = !!value; } - (OOAlertCondition) alertCondition { return _alertCondition; } - (void) setAlertCondition:(OOAlertCondition)value { _alertCondition = value; } - (NSUInteger) trumbleCount { return _trumbleCount; } - (void) setTrumbleCount:(NSUInteger)value { _trumbleCount = value; } - (void) setTradeInFactor:(int)tif { _tradeInFactor = tif; } - (int) tradeInFactor { return _tradeInFactor; } // If you're here to add more properties, don't forget to update -copyValuesFromPlayer:. @end @implementation Entity (ProxyPlayer) - (BOOL) isPlayerLikeShip { return NO; } @end @implementation PlayerEntity (ProxyPlayer) - (BOOL) isPlayerLikeShip { return YES; } @end oolite-1.82/src/Core/Entities/ShipEntity.h000066400000000000000000001354111256642440500204700ustar00rootroot00000000000000/* ShipEntity.h Entity subclass representing a ship, or various other flying things like cargo pods and stations (a subclass). Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOEntityWithDrawable.h" #import "OOPlanetEntity.h" #import "OOJSPropID.h" @class OOColor, StationEntity, WormholeEntity, AI, Octree, OOMesh, OOScript, OOJSScript, OORoleSet, OOShipGroup, OOEquipmentType, OOWeakSet, OOExhaustPlumeEntity, OOFlasherEntity; #define MAX_TARGETS 24 #define RAIDER_MAX_CARGO 5 #define MERCHANTMAN_MAX_CARGO 125 #define PIRATES_PREFER_PLAYER YES #define TURRET_MINIMUM_COS 0.20f #define SHIP_THRUST_FACTOR 5.0f #define AFTERBURNER_BURNRATE 0.25f #define CLOAKING_DEVICE_ENERGY_RATE 12.8f #define CLOAKING_DEVICE_MIN_ENERGY 128 #define CLOAKING_DEVICE_START_ENERGY 0.75f #define MILITARY_JAMMER_ENERGY_RATE 3 #define MILITARY_JAMMER_MIN_ENERGY 128 #define COMBAT_IN_RANGE_FACTOR 0.035f #define COMBAT_BROADSIDE_IN_RANGE_FACTOR 0.020f #define COMBAT_OUT_RANGE_FACTOR 0.500f #define COMBAT_BROADSIDE_RANGE_FACTOR 0.900f #define COMBAT_WEAPON_RANGE_FACTOR 1.200f #define COMBAT_JINK_OFFSET 500.0f #define SHIP_COOLING_FACTOR 0.1f // heat taken from energy damage depends on mass // but limit maximum rate since masses vary so much // Cobra III ~=215000 #define SHIP_ENERGY_DAMAGE_TO_HEAT_FACTOR (mass > 400000 ? 200000 / mass : 0.5) #define SHIP_INSULATION_FACTOR 0.00175f #define SHIP_MAX_CABIN_TEMP 256.0f #define SHIP_MIN_CABIN_TEMP 60.0f #define EJECTA_TEMP_FACTOR 0.85f #define DEFAULT_HYPERSPACE_SPIN_TIME 15.0f #define SUN_TEMPERATURE 1250.0f #define MAX_ESCORTS 16 #define ESCORT_SPACING_FACTOR 3.0 #define SHIPENTITY_MAX_MISSILES 32 #define TURRET_TYPICAL_ENERGY 25.0f #define TURRET_SHOT_SPEED 2000.0f #define TURRET_SHOT_DURATION 3.0 #define TURRET_SHOT_RANGE (TURRET_SHOT_SPEED * TURRET_SHOT_DURATION) #define TURRET_SHOT_FREQUENCY (TURRET_SHOT_DURATION * TURRET_SHOT_DURATION * TURRET_SHOT_DURATION / 100.0) #define NPC_PLASMA_SPEED 1500.0f #define MAIN_PLASMA_DURATION 5.0 #define NPC_PLASMA_RANGE (MAIN_PLASMA_DURATION * NPC_PLASMA_SPEED) #define PLAYER_PLASMA_SPEED 1000.0f #define PLAYER_PLASMA_RANGE (MAIN_PLASMA_DURATION * PLAYER_PLASMA_SPEED) #define TRACTOR_FORCE 2500.0f #define AIMS_AGGRESSOR_SWITCHED_TARGET @"AGGRESSOR_SWITCHED_TARGET" // number of vessels considered when scanning around #define MAX_SCAN_NUMBER 32 #define BASELINE_SHIELD_LEVEL 128.0f // Max shield level with no boosters. #define INITIAL_SHOT_TIME 100.0 #define MIN_FUEL 0 // minimum fuel required for afterburner use #ifdef OO_DUMP_PLANETINFO // debugging planetinfo needs rapid jumping #define MAX_JUMP_RANGE 150.0 #else #define MAX_JUMP_RANGE 7.0 // the 7 ly limit #endif #define ENTITY_PERSONALITY_MAX 0x7FFFU #define ENTITY_PERSONALITY_INVALID 0xFFFFU #define WEAPON_COOLING_FACTOR 6.0f #define NPC_MAX_WEAPON_TEMP 256.0f #define WEAPON_COOLING_CUTOUT 0.85f #define COMBAT_AI_WEAPON_TEMP_READY 0.25f * NPC_MAX_WEAPON_TEMP #define COMBAT_AI_WEAPON_TEMP_USABLE WEAPON_COOLING_CUTOUT * NPC_MAX_WEAPON_TEMP // factor determining how close to target AI has to be to be confident in aim // higher factor makes confident at longer ranges #define COMBAT_AI_CONFIDENCE_FACTOR 1250000.0f #define COMBAT_AI_ISNT_AWFUL 0.0f // removes BEHAVIOUR_ATTACK_FLY_TO_TARGET_SIX/TWELVE (unless thargoid) #define COMBAT_AI_IS_SMART 5.0f // adds BEHAVIOUR_(FLEE_)EVASIVE_ACTION #define COMBAT_AI_FLEES_BETTER 6.0f // adds BEHAVIOUR_ATTACK_BREAK_OFF_TARGET #define COMBAT_AI_DOGFIGHTER 6.5f // adds BEHAVIOUR_ATTACK_SLOW_DOGFIGHT #define COMBAT_AI_TRACKS_CLOSER 7.5f #define COMBAT_AI_USES_SNIPING 8.5f // adds BEHAVIOUR_ATTACK_SNIPER #define COMBAT_AI_FLEES_BETTER_2 9.0f // AI reacts to changes in target path in about 1.5 seconds. #define COMBAT_AI_STANDARD_REACTION_TIME 1.5f #define MAX_LANDING_SPEED 50.0 #define MAX_LANDING_SPEED2 (MAX_LANDING_SPEED * MAX_LANDING_SPEED) #define MAX_COS 0.995 // cos(5 degrees) is close enough in most cases for navigation #define MAX_COS2 (MAX_COS * MAX_COS) #define ENTRY(label, value) label = value, typedef enum OOBehaviour { #include "OOBehaviour.tbl" } OOBehaviour; #undef ENTRY /*typedef enum { WEAPON_NONE = 0U, WEAPON_PLASMA_CANNON = 1, WEAPON_PULSE_LASER = 2, WEAPON_BEAM_LASER = 3, WEAPON_MINING_LASER = 4, WEAPON_MILITARY_LASER = 5, WEAPON_THARGOID_LASER = 10, WEAPON_UNDEFINED } OOWeaponType; */ typedef OOEquipmentType* OOWeaponType; typedef enum { // Alert conditions are used by player and station entities. // NOTE: numerical values are available to scripts and shaders. ALERT_CONDITION_DOCKED = 0, ALERT_CONDITION_GREEN = 1, ALERT_CONDITION_YELLOW = 2, ALERT_CONDITION_RED = 3 } OOAlertCondition; typedef enum { #define DIFF_STRING_ENTRY(label, string) label, #include "OOShipDamageType.tbl" #undef DIFF_STRING_ENTRY kOOShipDamageTypeDefault = kOODamageTypeEnergy } OOShipDamageType; @interface ShipEntity: OOEntityWithDrawable { @public // derived variables OOTimeDelta shot_time; // time elapsed since last shot was fired // navigation Vector v_forward, v_up, v_right; // unit vectors derived from the direction faced // variables which are controlled by AI HPVector _destination; // for flying to/from a set point GLfloat desired_range; // range to which to journey/scan GLfloat desired_speed; // speed at which to travel // next three used to set desired attitude, flightRoll etc. gradually catch up to target GLfloat stick_roll; // stick roll GLfloat stick_pitch; // stick pitch GLfloat stick_yaw; // stick yaw OOBehaviour behaviour; // ship's behavioural state BoundingBox totalBoundingBox; // records ship configuration @protected //set-up NSDictionary *shipinfoDictionary; Quaternion subentityRotationalVelocity; //scripting OOJSScript *script; OOJSScript *aiScript; OOTimeAbsolute aiScriptWakeTime; //docking instructions NSDictionary *dockingInstructions; OOColor *laser_color; OOColor *default_laser_color; OOColor *exhaust_emissive_color; OOColor *scanner_display_color1; OOColor *scanner_display_color2; OOColor *scanner_display_color_hostile1; OOColor *scanner_display_color_hostile2; // per ship-type variables // GLfloat maxFlightSpeed; // top speed (160.0 for player) (200.0 for fast raider) GLfloat max_flight_roll; // maximum roll rate (2.0 for player) (3.0 for fast raider) GLfloat max_flight_pitch; // maximum pitch rate (1.0 for player) (1.5 for fast raider) also radians/sec for (* turrets *) GLfloat max_flight_yaw; GLfloat cruiseSpeed; // 80% of top speed GLfloat max_thrust; // acceleration GLfloat thrust; // acceleration float hyperspaceMotorSpinTime; // duration of hyperspace countdown unsigned military_jammer_active: 1, // military_jammer docking_match_rotation: 1, pitching_over: 1, // set to YES if executing a sharp loop rolling_over: 1, // set to YES if executing a sharp roll reportAIMessages: 1, // normally NO, suppressing AI message reporting being_mined: 1, // normally NO, set to Yes when fired on by mining laser being_fined: 1, isHulk: 1, // This is used to distinguish abandoned ships from cargo trackCloseContacts: 1, isNearPlanetSurface: 1, // check for landing on planet isFrangible: 1, // frangible => subEntities can be damaged individually cloaking_device_active: 1, // cloaking_device cloakPassive: 1, // cloak deactivates when main weapons or missiles are fired cloakAutomatic: 1, // cloak activates itself automatic during attack canFragment: 1, // Can it break into wreckage? isWreckage: 1, // Is it wreckage? _showDamage: 1, // Show damage? suppressExplosion: 1, // Avoid exploding on death (script hook) suppressAegisMessages: 1, // No script/AI messages sent by -checkForAegis, isMissile: 1, // Whether this was launched by fireMissile (used to track submunitions). _explicitlyUnpiloted: 1, // Is meant to not have crew hasScoopMessage: 1, // suppress scoop messages when false. // scripting scripted_misjump: 1, haveExecutedSpawnAction: 1, haveStartedJSAI: 1, noRocks: 1, _lightsActive: 1; GLfloat _scriptedMisjumpRange; GLfloat sunGlareFilter; // Range 0.0 - 1.0, where 0 means no sun glare filter, 1 means glare fully filtered OOFuelQuantity fuel; // witch-space fuel GLfloat fuel_accumulator; GLfloat afterburner_rate; GLfloat afterburner_speed_factor; OOCargoQuantity likely_cargo; // likely amount of cargo (for pirates, this is what is spilled as loot) OOCargoQuantity max_cargo; // capacity of cargo hold OOCargoQuantity extra_cargo; // capacity of cargo hold extension (if any) OOCargoQuantity equipment_weight; // amount of equipment using cargo space (excluding passenger_berth & extra_cargo_bay) OOCargoType cargo_type; // if this is scooped, this is indicates contents OOCargoFlag cargo_flag; // indicates contents for merchantmen OOCreditsQuantity bounty; // bounty (if any) GLfloat energy_recharge_rate; // recharge rate for energy banks OOWeaponFacingSet weapon_facings; // weapon mounts available (bitmask) OOWeaponType forward_weapon_type; // type of forward weapon (allows lasers, plasma cannon, others) OOWeaponType aft_weapon_type; // type of aft weapon (allows lasers, plasma cannon, others) OOWeaponType port_weapon_type; // type of port weapon OOWeaponType starboard_weapon_type; // type of starboard weapon GLfloat weapon_damage; // energy damage dealt by weapon GLfloat weapon_damage_override; // custom energy damage dealt by front laser, if applicable GLfloat weaponRange; // range of the weapon (in meters) OOWeaponFacing currentWeaponFacing; // not necessarily the same as view for the player GLfloat weapon_energy_use, weapon_temp, weapon_shot_temperature; // active weapon temp, delta-temp GLfloat forward_weapon_temp, aft_weapon_temp, port_weapon_temp, starboard_weapon_temp; // current weapon temperatures GLfloat scannerRange; // typically 25600 unsigned missiles; // number of on-board missiles unsigned max_missiles; // number of missile pylons NSString *_missileRole; OOTimeDelta missile_load_time; // minimum time interval between missile launches OOTimeAbsolute missile_launch_time; // time of last missile launch AI *shipAI; // ship's AI system NSString *name; // descriptive name NSString *shipUniqueName; // uniqish name e.g. "Terror of Lave" NSString *shipClassName; // e.g. "Cobra III" NSString *displayName; // name shown on screen NSString *scan_description; // scan class name OORoleSet *roleSet; // Roles a ship can take, eg. trader, hunter, police, pirate, scavenger &c. NSString *primaryRole; // "Main" role of the ship. NSArray *explosionType; // explosion.plist entries // AI stuff Vector jink; // x and y set factors for offsetting a pursuing ship's position HPVector coordinates; // for flying to/from a set point Vector reference; // a direction vector of magnitude 1 (* turrets *) NSUInteger _subIdx; // serialisation index - used only if this ship is a subentity NSUInteger _maxShipSubIdx; // serialisation index - the number of ship subentities inside the shipdata double launch_time; // time at which launched double launch_delay; // delay for thinking after launch OOUniversalID planetForLanding; // for landing GLfloat frustration, // degree of dissatisfaction with the current behavioural state, factor used to test this success_factor; int patrol_counter; // keeps track of where the ship is along a patrol route NSMutableDictionary *previousCondition; // restored after collision avoidance // derived variables float weapon_recharge_rate; // time between shots int shot_counter; // number of shots fired OOTimeAbsolute cargo_dump_time; // time cargo was last dumped OOTimeAbsolute last_shot_time; // time shot was last fired NSMutableArray *cargo; // cargo containers go in here OOCommodityType commodity_type; // type of commodity in a container OOCargoQuantity commodity_amount; // 1 if unit is TONNES (0), possibly more if precious metals KILOGRAMS (1) // or gem stones GRAMS (2) // navigation GLfloat flightSpeed; // current speed GLfloat flightRoll; // current roll rate GLfloat flightPitch; // current pitch rate GLfloat flightYaw; // current yaw rate GLfloat accuracy; GLfloat pitch_tolerance; GLfloat aim_tolerance; int _missed_shots; OOAegisStatus aegis_status; // set to YES when within the station's protective zone OOSystemID home_system; OOSystemID destination_system; double messageTime; // counts down the seconds a radio message is active for double next_spark_time; // time of next spark when throwing sparks Vector collision_vector; // direction of colliding thing. GLfloat _scaleFactor; // scale factor for size variation //position of gun ports Vector forwardWeaponOffset, aftWeaponOffset, portWeaponOffset, starboardWeaponOffset; // crew (typically one OOCharacter - the pilot) NSArray *crew; // close contact / collision tracking NSMutableDictionary *closeContactsInfo; NSString *lastRadioMessage; // scooping... Vector tractor_position; // from player entity moved here now we're doing more complex heat stuff float ship_temperature; // for advanced scanning etc. ShipEntity *scanned_ships[MAX_SCAN_NUMBER + 1]; GLfloat distance2_scanned_ships[MAX_SCAN_NUMBER + 1]; unsigned n_scanned_ships; // advanced navigation HPVector navpoints[32]; unsigned next_navpoint_index; unsigned number_of_navpoints; // Collision detection Octree *octree; #ifndef NDEBUG // DEBUGGING OOBehaviour debugLastBehaviour; #endif uint16_t entity_personality; // Per-entity random number. Exposed to shaders and scripts. NSDictionary *scriptInfo; // script_info dictionary from shipdata.plist, exposed to scripts. NSMutableArray *subEntities; OOEquipmentType *missile_list[SHIPENTITY_MAX_MISSILES]; // various types of target OOWeakReference *_primaryTarget; // for combat or rendezvous OOWeakReference *_primaryAggressor; // recorded after attack OOWeakReference *_targetStation; // for docking OOWeakReference *_foundTarget; // from scans OOWeakReference *_lastEscortTarget; // last target an escort was deployed after OOWeakReference *_thankedShip; // last ship thanked OOWeakReference *_rememberedShip; // ship being remembered OOWeakReference *_proximityAlert; // a ShipEntity within 2x collision_radius // Stuff for the target tracking curve. The ship records the position of the target every reactionTime/2 seconds, then fits a curve to the // last three recorded positions. Instead of tracking the primary target's actual position it uses the curve to calculate the target's position. // This introduces a small amount of lag to the target tracking making the NPC more human. float reactionTime; HPVector trackingCurvePositions[4]; OOTimeAbsolute trackingCurveTimes[4]; HPVector trackingCurveCoeffs[3]; @private OOWeakReference *_subEntityTakingDamage; // frangible => subEntities can be damaged individually NSString *_shipKey; NSMutableArray *_equipment; float _heatInsulation; OOWeakReference *_lastAegisLock; // remember last aegis planet/sun OOShipGroup *_group; OOShipGroup *_escortGroup; uint8_t _maxEscortCount; uint8_t _pendingEscortCount; // Cache of ship-relative positions, managed by -coordinatesForEscortPosition:. Vector _escortPositions[MAX_ESCORTS]; BOOL _escortPositionsValid; OOWeakSet *_defenseTargets; // defense targets // ships in this set can't be collided with OOWeakSet *_collisionExceptions; GLfloat _profileRadius; OOWeakReference *_shipHitByLaser; // entity hit by the last laser shot // beacons NSString *_beaconCode; NSString *_beaconLabel; OOWeakReference *_prevBeacon; OOWeakReference *_nextBeacon; id _beaconDrawable; double _nextAegisCheck; } // ship brains - (void) setStateMachine:(NSString *)ai_desc; - (void) setAI:(AI *)ai; - (AI *) getAI; - (BOOL) hasAutoAI; - (BOOL) hasNewAI; - (void) setShipScript:(NSString *)script_name; - (void) removeScript; - (OOScript *) shipScript; - (OOScript *) shipAIScript; - (OOTimeAbsolute) shipAIScriptWakeTime; - (void) setAIScriptWakeTime:(OOTimeAbsolute) t; - (double) frustration; - (void) setLaunchDelay:(double)delay; - (void) interpretAIMessage:(NSString *)message; - (GLfloat)accuracy; - (void)setAccuracy:(GLfloat) new_accuracy; - (OOMesh *)mesh; - (void)setMesh:(OOMesh *)mesh; - (BoundingBox) totalBoundingBox; - (Vector) forwardVector; - (Vector) upVector; - (Vector) rightVector; - (NSArray *)subEntities; - (NSUInteger) subEntityCount; - (BOOL) hasSubEntity:(Entity *)sub; - (NSEnumerator *)subEntityEnumerator; - (NSEnumerator *)shipSubEntityEnumerator; - (NSEnumerator *)flasherEnumerator; - (NSEnumerator *)exhaustEnumerator; - (ShipEntity *) subEntityTakingDamage; - (void) setSubEntityTakingDamage:(ShipEntity *)sub; - (void) clearSubEntities; // Releases and clears subentity array, after making sure subentities don't think ship is owner. - (Quaternion) subEntityRotationalVelocity; - (void) setSubEntityRotationalVelocity:(Quaternion)rv; // subentities management - (NSString *) serializeShipSubEntities; - (void) deserializeShipSubEntitiesFrom:(NSString *)string; - (NSUInteger) maxShipSubEntities; - (void) setSubIdx:(NSUInteger)value; - (NSUInteger) subIdx; - (Octree *) octree; - (float) volume; // octree collision hunting - (GLfloat)doesHitLine:(HPVector)v0 :(HPVector)v1; - (GLfloat)doesHitLine:(HPVector)v0 :(HPVector)v1 :(ShipEntity**)hitEntity; - (GLfloat)doesHitLine:(HPVector)v0 :(HPVector)v1 withPosition:(HPVector)o andIJK:(Vector)i :(Vector)j :(Vector)k; // for subentities - (BoundingBox) findBoundingBoxRelativeToPosition:(HPVector)opv InVectors:(Vector)i :(Vector)j :(Vector)k; - (HPVector)absoluteTractorPosition; // beacons // definitions now in protocol - (void) setIsBoulder:(BOOL)flag; - (BOOL) isBoulder; - (BOOL) isMinable; - (BOOL) countsAsKill; - (void) setUpEscorts; - (void) updateEscortFormation; - (id)initWithKey:(NSString *)key definition:(NSDictionary *)dict; - (BOOL)setUpFromDictionary:(NSDictionary *) shipDict; - (BOOL)setUpShipFromDictionary:(NSDictionary *) shipDict; - (BOOL)setUpSubEntities; - (BOOL) setUpOneStandardSubentity:(NSDictionary *) subentDict asTurret:(BOOL)asTurret; - (GLfloat)frustumRadius; - (NSString *) shipDataKey; - (NSString *) shipDataKeyAutoRole; - (void)setShipDataKey:(NSString *)key; - (NSDictionary *)shipInfoDictionary; - (void) setDefaultWeaponOffsets; - (Vector) aftWeaponOffset; - (Vector) forwardWeaponOffset; - (Vector) portWeaponOffset; - (Vector) starboardWeaponOffset; - (BOOL) hasAutoWeapons; - (BOOL) isFrangible; - (BOOL) suppressFlightNotifications; - (void) respondToAttackFrom:(Entity *)from becauseOf:(Entity *)other; // Equipment - (OOWeaponFacingSet) weaponFacings; - (BOOL) hasEquipmentItem:(id)equipmentKeys includeWeapons:(BOOL)includeWeapons whileLoading:(BOOL)loading; // This can take a string or an set or array of strings. If a collection, returns YES if ship has _any_ of the specified equipment. If includeWeapons is NO, missiles and primary weapons are not checked. - (BOOL) hasEquipmentItem:(id)equipmentKeys; // Short for hasEquipmentItem:foo includeWeapons:NO whileLoading:NO - (NSUInteger) countEquipmentItem:(NSString *)eqkey; - (NSString *) equipmentItemProviding:(NSString *)equipmentType; - (BOOL) hasEquipmentItemProviding:(NSString *)equipmentType; - (BOOL) hasAllEquipment:(id)equipmentKeys includeWeapons:(BOOL)includeWeapons whileLoading:(BOOL)loading; // Like hasEquipmentItem:includeWeapons:, but requires _all_ elements in collection. - (BOOL) hasAllEquipment:(id)equipmentKeys; // Short for hasAllEquipment:foo includeWeapons:NO - (BOOL) setWeaponMount:(OOWeaponFacing)facing toWeapon:(NSString *)eqKey; - (BOOL) canAddEquipment:(NSString *)equipmentKey inContext:(NSString *)context; // Test ability to add equipment, taking equipment-specific constriants into account. - (BOOL) equipmentValidToAdd:(NSString *)equipmentKey inContext:(NSString *)context; // Actual test if equipment satisfies validation criteria. - (BOOL) equipmentValidToAdd:(NSString *)equipmentKey whileLoading:(BOOL)loading inContext:(NSString *)context; - (BOOL) addEquipmentItem:(NSString *)equipmentKey inContext:(NSString *)context; - (BOOL) addEquipmentItem:(NSString *)equipmentKey withValidation:(BOOL)validateAddition inContext:(NSString *)context; - (BOOL) hasHyperspaceMotor; - (float) hyperspaceSpinTime; - (void) setHyperspaceSpinTime:(float)new; - (NSEnumerator *) equipmentEnumerator; - (NSUInteger) equipmentCount; - (void) removeEquipmentItem:(NSString *)equipmentKey; - (void) removeAllEquipment; - (OOEquipmentType *) selectMissile; - (OOCreditsQuantity) removeMissiles; // Internal, subject to change. Use the methods above instead. - (BOOL) hasOneEquipmentItem:(NSString *)itemKey includeWeapons:(BOOL)includeMissiles whileLoading:(BOOL)loading; - (BOOL) hasOneEquipmentItem:(NSString *)itemKey includeMissiles:(BOOL)includeMissiles whileLoading:(BOOL)loading; - (BOOL) hasPrimaryWeapon:(OOWeaponType)weaponType; - (BOOL) removeExternalStore:(OOEquipmentType *)eqType; // Passengers and parcels - not supported for NPCs, but interface is here for genericity. - (NSUInteger) parcelCount; - (NSUInteger) passengerCount; - (NSUInteger) passengerCapacity; - (NSUInteger) missileCount; - (NSUInteger) missileCapacity; - (NSUInteger) extraCargo; // Tests for the various special-cased equipment items // (Nowadays, more convenience methods) - (BOOL) hasScoop; - (BOOL) hasFuelScoop; - (BOOL) hasCargoScoop; - (BOOL) hasECM; - (BOOL) hasCloakingDevice; - (BOOL) hasMilitaryScannerFilter; - (BOOL) hasMilitaryJammer; - (BOOL) hasExpandedCargoBay; - (BOOL) hasShieldBooster; - (BOOL) hasMilitaryShieldEnhancer; - (BOOL) hasHeatShield; - (BOOL) hasFuelInjection; - (BOOL) hasCascadeMine; - (BOOL) hasEscapePod; - (BOOL) hasDockingComputer; - (BOOL) hasGalacticHyperdrive; // Shield information derived from equipment. NPCs can't have shields, but that should change at some point. - (float) shieldBoostFactor; - (float) maxForwardShieldLevel; - (float) maxAftShieldLevel; - (float) shieldRechargeRate; - (float) maxHyperspaceDistance; - (float) afterburnerFactor; - (float) afterburnerRate; - (void) setAfterburnerFactor:(GLfloat)new; - (void) setAfterburnerRate:(GLfloat)new; - (float) maxThrust; - (float) thrust; - (void) setMaxThrust:(GLfloat)new; - (void) setMaxFlightPitch:(GLfloat)new; - (void) setMaxFlightSpeed:(GLfloat)new; - (void) setMaxFlightRoll:(GLfloat)new; - (void) setMaxFlightYaw:(GLfloat)new; - (void) setEnergyRechargeRate:(GLfloat)new; - (void) processBehaviour:(OOTimeDelta)delta_t; // Behaviours - (void) behaviour_stop_still:(double) delta_t; - (void) behaviour_idle:(double) delta_t; - (void) behaviour_tumble:(double) delta_t; - (void) behaviour_tractored:(double) delta_t; - (void) behaviour_track_target:(double) delta_t; - (void) behaviour_intercept_target:(double) delta_t; - (void) behaviour_attack_target:(double) delta_t; - (void) behaviour_attack_slow_dogfight:(double) delta_t; - (void) behaviour_evasive_action:(double) delta_t; - (void) behaviour_attack_break_off_target:(double) delta_t; - (void) behaviour_fly_to_target_six:(double) delta_t; - (void) behaviour_attack_mining_target:(double) delta_t; - (void) behaviour_attack_fly_to_target:(double) delta_t; - (void) behaviour_attack_fly_from_target:(double) delta_t; - (void) behaviour_running_defense:(double) delta_t; - (void) behaviour_flee_target:(double) delta_t; - (void) behaviour_attack_broadside:(double) delta_t; - (void) behaviour_attack_broadside_left:(double) delta_t; - (void) behaviour_attack_broadside_right:(double) delta_t; - (void) behaviour_close_to_broadside_range:(double) delta_t; - (void) behaviour_close_with_target:(double) delta_t; - (void) behaviour_attack_broadside_target:(double) delta_t leftside:(BOOL)leftside; - (void) behaviour_attack_sniper:(double) delta_t; - (void) behaviour_fly_range_from_destination:(double) delta_t; - (void) behaviour_face_destination:(double) delta_t; - (void) behaviour_land_on_planet:(double) delta_t; - (void) behaviour_formation_form_up:(double) delta_t; - (void) behaviour_fly_to_destination:(double) delta_t; - (void) behaviour_fly_from_destination:(double) delta_t; - (void) behaviour_avoid_collision:(double) delta_t; - (void) behaviour_track_as_turret:(double) delta_t; - (void) behaviour_fly_thru_navpoints:(double) delta_t; - (void) behaviour_scripted_ai:(double) delta_t; - (float) reactionTime; - (void) setReactionTime: (float) newReactionTime; - (HPVector) calculateTargetPosition; - (void) startTrackingCurve; - (void) updateTrackingCurve; - (void) calculateTrackingCurve; - (GLfloat *) scannerDisplayColorForShip:(ShipEntity*)otherShip :(BOOL)isHostile :(BOOL)flash :(OOColor *)scannerDisplayColor1 :(OOColor *)scannerDisplayColor2 :(OOColor *)scannerDisplayColorH1 :(OOColor *)scannerDisplayColorH2; - (void)setScannerDisplayColor1:(OOColor *)color1; - (void)setScannerDisplayColor2:(OOColor *)color2; - (OOColor *)scannerDisplayColor1; - (OOColor *)scannerDisplayColor2; - (void)setScannerDisplayColorHostile1:(OOColor *)color1; - (void)setScannerDisplayColorHostile2:(OOColor *)color2; - (OOColor *)scannerDisplayColorHostile1; - (OOColor *)scannerDisplayColorHostile2; - (BOOL)isCloaked; - (void)setCloaked:(BOOL)cloak; - (BOOL)hasAutoCloak; - (void)setAutoCloak:(BOOL)automatic; - (void) applyThrust:(double) delta_t; - (void) applyAttitudeChanges:(double) delta_t; - (void) avoidCollision; - (void) resumePostProximityAlert; - (double) messageTime; - (void) setMessageTime:(double) value; - (OOShipGroup *) group; - (void) setGroup:(OOShipGroup *)group; - (OOShipGroup *) escortGroup; - (void) setEscortGroup:(OOShipGroup *)group; // Only for use in unconventional set-up situations. - (OOShipGroup *) stationGroup; // should probably be defined in stationEntity.m - (BOOL) hasEscorts; - (NSEnumerator *) escortEnumerator; - (NSArray *) escortArray; - (uint8_t) escortCount; // Pending escort count: number of escorts to set up "later". - (uint8_t) pendingEscortCount; - (void) setPendingEscortCount:(uint8_t)count; // allow adjustment of escort numbers from shipdata.plist levels - (uint8_t) maxEscortCount; - (void) setMaxEscortCount:(uint8_t)newCount; - (NSString *) name; - (NSString *) shipUniqueName; - (NSString *) shipClassName; - (NSString *) displayName; - (NSString *) scanDescription; - (NSString *) scanDescriptionForScripting; - (void) setName:(NSString *)inName; - (void) setShipUniqueName:(NSString *)inName; - (void) setShipClassName:(NSString *)inName; - (void) setDisplayName:(NSString *)inName; - (void) setScanDescription:(NSString *)inName; - (NSString *) identFromShip:(ShipEntity*) otherShip; // name displayed to other ships - (BOOL) hasRole:(NSString *)role; - (OORoleSet *)roleSet; - (void) addRole:(NSString *)role; - (void) addRole:(NSString *)role withProbability:(float)probability; - (void) removeRole:(NSString *)role; - (NSString *)primaryRole; - (void)setPrimaryRole:(NSString *)role; - (BOOL)hasPrimaryRole:(NSString *)role; - (BOOL)isPolice; // Scan class is CLASS_POLICE - (BOOL)isThargoid; // Scan class is CLASS_THARGOID - (BOOL)isTrader; // Primary role is "trader" || isPlayer - (BOOL)isPirate; // Primary role is "pirate" - (BOOL)isMissile; // Primary role has suffix "MISSILE" - (BOOL)isMine; // Primary role has suffix "MINE" - (BOOL)isWeapon; // isMissile || isWeapon - (BOOL)isEscort; // Primary role is "escort" or "wingman" - (BOOL)isShuttle; // Primary role is "shuttle" - (BOOL)isTurret; // Behaviour is BEHAVIOUR_TRACK_AS_TURRET - (BOOL)isPirateVictim; // Primary role is listed in pirate-victim-roles.plist - (BOOL)isExplicitlyUnpiloted; // Has unpiloted = yes in its shipdata.plist entry - (BOOL)isUnpiloted; // Explicitly unpiloted, hulk, rock, cargo, debris etc; an open-ended criterion that may grow. - (OOAlertCondition) alertCondition; // quick calc for shaders - (OOAlertCondition) realAlertCondition; // full calculation for scripting - (BOOL) hasHostileTarget; - (BOOL) isHostileTo:(Entity *)entity; // defense target handling - (NSUInteger) defenseTargetCount; - (NSArray *) allDefenseTargets; - (NSEnumerator *) defenseTargetEnumerator; - (void) validateDefenseTargets; - (BOOL) addDefenseTarget:(Entity *)target; - (BOOL) isDefenseTarget:(Entity *)target; - (void) removeDefenseTarget:(Entity *)target; - (void) removeAllDefenseTargets; // collision exceptions - (NSArray *) collisionExceptions; - (void) addCollisionException:(ShipEntity *)ship; - (void) removeCollisionException:(ShipEntity *)ship; - (BOOL) collisionExceptedFor:(ShipEntity *)ship; - (GLfloat) weaponRange; - (void) setWeaponRange:(GLfloat) value; - (void) setWeaponDataFromType:(OOWeaponType)weapon_type; - (float) energyRechargeRate; // final rate after energy units - (float) weaponRechargeRate; - (void) setWeaponRechargeRate:(float)value; - (void) setWeaponEnergy:(float)value; - (OOWeaponFacing) currentWeaponFacing; - (GLfloat) scannerRange; - (void) setScannerRange:(GLfloat)value; - (Vector) reference; - (void) setReference:(Vector)v; - (BOOL) reportAIMessages; - (void) setReportAIMessages:(BOOL)yn; - (void) transitionToAegisNone; - (OOPlanetEntity *) findNearestPlanet; - (Entity *) findNearestStellarBody; // NOTE: includes sun. - (OOPlanetEntity *) findNearestPlanetExcludingMoons; - (OOAegisStatus) checkForAegis; - (void) forceAegisCheck; - (BOOL) withinStationAegis; - (void) setLastAegisLock:(Entity *)lastAegisLock; - (OOSystemID) homeSystem; - (OOSystemID) destinationSystem; - (void) setHomeSystem:(OOSystemID)s; - (void) setDestinationSystem:(OOSystemID)s; - (NSArray *) crew; - (NSArray *) crewForScripting; - (void) setCrew:(NSArray *)crewArray; /** Convenience to set the crew to a single character of the given role, originating in the ship's home system. Does nothing if unpiloted. */ - (void) setSingleCrewWithRole:(NSString *)crewRole; // Fuel and capacity in tenths of light-years. - (OOFuelQuantity) fuel; - (void) setFuel:(OOFuelQuantity)amount; - (OOFuelQuantity) fuelCapacity; - (GLfloat) fuelChargeRate; - (void) setRoll:(double)amount; - (void) setRawRoll:(double)amount; // does not multiply by PI/2 - (void) setPitch:(double)amount; - (void) setYaw:(double)amount; - (void) setThrust:(double)amount; - (void) applySticks:(double)delta_t; - (void)setThrustForDemo:(float)factor; /* Sets the bounty on this ship to amount. Does not check to see if the ship is allowed to have a bounty, for example if it is police. */ - (void) setBounty:(OOCreditsQuantity)amount; - (void) setBounty:(OOCreditsQuantity)amount withReason:(OOLegalStatusReason)reason; - (void) setBounty:(OOCreditsQuantity)amount withReasonAsString:(NSString *)reason; - (OOCreditsQuantity) bounty; - (int) legalStatus; - (BOOL) isTemplateCargoPod; - (void) setUpCargoType:(NSString *)cargoString; - (void) setCommodity:(OOCommodityType)co_type andAmount:(OOCargoQuantity)co_amount; - (void) setCommodityForPod:(OOCommodityType)co_type andAmount:(OOCargoQuantity)co_amount; - (OOCommodityType) commodityType; - (OOCargoQuantity) commodityAmount; - (OOCargoQuantity) maxAvailableCargoSpace; - (void) setMaxAvailableCargoSpace:(OOCargoQuantity)new; - (OOCargoQuantity) availableCargoSpace; - (OOCargoQuantity) cargoQuantityOnBoard; - (OOCargoType) cargoType; - (NSArray *) cargoListForScripting; - (NSMutableArray *) cargo; - (void) setCargo:(NSArray *)some_cargo; - (BOOL) addCargo:(NSArray *) some_cargo; - (BOOL) removeCargo:(OOCommodityType)commodity amount:(OOCargoQuantity) amount; - (BOOL) showScoopMessage; - (NSArray *) passengerListForScripting; - (NSArray *) parcelListForScripting; - (NSArray *) contractListForScripting; - (NSArray *) equipmentListForScripting; - (OOWeaponType) weaponTypeIDForFacing:(OOWeaponFacing)facing strict:(BOOL)strict; - (OOEquipmentType *) weaponTypeForFacing:(OOWeaponFacing)facing strict:(BOOL)strict; - (NSArray *) missilesList; - (OOCargoFlag) cargoFlag; - (void) setCargoFlag:(OOCargoFlag)flag; - (void) setSpeed:(double)amount; - (double) desiredSpeed; - (void) setDesiredSpeed:(double)amount; - (double) desiredRange; - (void) setDesiredRange:(double)amount; - (double) cruiseSpeed; - (Vector) thrustVector; - (void) setTotalVelocity:(Vector)vel; // Set velocity to vel - thrustVector, effectively setting the instanteneous velocity to vel. - (void) increase_flight_speed:(double)delta; - (void) decrease_flight_speed:(double)delta; - (void) increase_flight_roll:(double)delta; - (void) decrease_flight_roll:(double)delta; - (void) increase_flight_pitch:(double)delta; - (void) decrease_flight_pitch:(double)delta; - (void) increase_flight_yaw:(double)delta; - (void) decrease_flight_yaw:(double)delta; - (GLfloat) flightRoll; - (GLfloat) flightPitch; - (GLfloat) flightYaw; - (GLfloat) flightSpeed; - (GLfloat) maxFlightPitch; - (GLfloat) maxFlightSpeed; - (GLfloat) maxFlightRoll; - (GLfloat) maxFlightYaw; - (GLfloat) speedFactor; - (GLfloat) temperature; - (void) setTemperature:(GLfloat) value; - (GLfloat) heatInsulation; - (void) setHeatInsulation:(GLfloat) value; - (float) randomEjectaTemperature; - (float) randomEjectaTemperatureWithMaxFactor:(float)factor; // the percentage of damage taken (100 is destroyed, 0 is fine) - (int) damage; - (void) dealEnergyDamage:(GLfloat) baseDamage atRange:(GLfloat) range withBias:(GLfloat) velocityBias; - (void) dealEnergyDamageWithinDesiredRange; - (void) dealMomentumWithinDesiredRange:(double)amount; // Dispatch shipTakingDamage() event. - (void) noteTakingDamage:(double)amount from:(Entity *)entity type:(OOShipDamageType)type; // Dispatch shipDied() and possibly shipKilledOther() events. This is only for use by getDestroyedBy:damageType:, but needs to be visible to PlayerEntity's version. - (void) noteKilledBy:(Entity *)whom damageType:(OOShipDamageType)type; - (void) getDestroyedBy:(Entity *)whom damageType:(OOShipDamageType)type; - (void) becomeExplosion; - (void) becomeLargeExplosion:(double) factor; - (void) becomeEnergyBlast; - (void) broadcastEnergyBlastImminent; - (void) setIsWreckage:(BOOL)isw; - (BOOL) showDamage; - (Vector) positionOffsetForAlignment:(NSString*) align; Vector positionOffsetForShipInRotationToAlignment(ShipEntity* ship, Quaternion q, NSString* align); - (void) collectBountyFor:(ShipEntity *)other; - (BoundingBox) findSubentityBoundingBox; - (Triangle) absoluteIJKForSubentity; - (GLfloat)weaponRecoveryTime; - (GLfloat)laserHeatLevel; - (GLfloat)laserHeatLevelAft; - (GLfloat)laserHeatLevelForward; - (GLfloat)laserHeatLevelPort; - (GLfloat)laserHeatLevelStarboard; - (GLfloat)hullHeatLevel; - (GLfloat)entityPersonality; - (GLint)entityPersonalityInt; - (void) setEntityPersonalityInt:(uint16_t)value; - (void)setSuppressExplosion:(BOOL)suppress; - (void) resetExhaustPlumes; - (void) removeExhaust:(OOExhaustPlumeEntity *)exhaust; - (void) removeFlasher:(OOFlasherEntity *)flasher; /*----------------------------------------- AI piloting methods -----------------------------------------*/ - (void) checkScanner; - (void) checkScannerIgnoringUnpowered; - (ShipEntity**) scannedShips; - (int) numberOfScannedShips; - (Entity *)foundTarget; - (Entity *)primaryAggressor; - (Entity *)lastEscortTarget; - (Entity *)thankedShip; - (Entity *)rememberedShip; - (Entity *)proximityAlert; - (void) setFoundTarget:(Entity *) targetEntity; - (void) setPrimaryAggressor:(Entity *) targetEntity; - (void) setLastEscortTarget:(Entity *) targetEntity; - (void) setThankedShip:(Entity *) targetEntity; - (void) setRememberedShip:(Entity *) targetEntity; - (void) setProximityAlert:(ShipEntity *) targetEntity; - (void) setTargetStation:(Entity *) targetEntity; - (BOOL) isValidTarget:(Entity *) target; - (void) addTarget:(Entity *) targetEntity; - (void) removeTarget:(Entity *) targetEntity; - (BOOL) canStillTrackPrimaryTarget; - (id) primaryTarget; - (id) primaryTargetWithoutValidityCheck; - (StationEntity *) targetStation; - (BOOL) isFriendlyTo:(ShipEntity *)otherShip; - (ShipEntity *) shipHitByLaser; - (void) noteLostTarget; - (void) noteLostTargetAndGoIdle; - (void) noteTargetDestroyed:(ShipEntity *)target; - (OOBehaviour) behaviour; - (void) setBehaviour:(OOBehaviour) cond; - (void) trackOntoTarget:(double) delta_t withDForward: (GLfloat) dp; - (double) ballTrackLeadingTarget:(double) delta_t atTarget:(Entity *)target; - (GLfloat) rollToMatchUp:(Vector) up_vec rotating:(GLfloat) match_roll; - (GLfloat) rangeToDestination; - (double) trackDestination:(double) delta_t :(BOOL) retreat; - (void) setCoordinate:(HPVector)coord; - (HPVector) coordinates; - (HPVector) destination; - (HPVector) distance_six: (GLfloat) dist; - (HPVector) distance_twelve: (GLfloat) dist withOffset:(GLfloat)offset; - (void) setEvasiveJink:(GLfloat) z; - (void) evasiveAction:(double) delta_t; - (double) trackPrimaryTarget:(double) delta_t :(BOOL) retreat; - (double) trackSideTarget:(double) delta_t :(BOOL) leftside; - (double) missileTrackPrimaryTarget:(double) delta_t; //return 0.0 if there is no primary target - (double) rangeToPrimaryTarget; - (double) approachAspectToPrimaryTarget; - (double) rangeToSecondaryTarget:(Entity *)target; - (BOOL) hasProximityAlertIgnoringTarget:(BOOL)ignore_target; - (GLfloat) currentAimTolerance; /* This method returns a value between 0.0f and 1.0f, depending on how directly our view point faces the sun and is used for generating the "staring at the sun" glare effect. 0.0f means that we are not facing the sun, 1.0f means that we are looking directly at it. The cosine of the threshold angle between view point and sun, below which we consider the ship as looking at the sun, is passed as parameter to the method. */ - (GLfloat) lookingAtSunWithThresholdAngleCos:(GLfloat) thresholdAngleCos; - (BOOL) onTarget:(OOWeaponFacing)direction withWeapon:(OOWeaponType)weapon; - (OOTimeDelta) shotTime; - (void) resetShotTime; - (BOOL) fireMainWeapon:(double)range; - (BOOL) fireAftWeapon:(double)range; - (BOOL) firePortWeapon:(double)range; - (BOOL) fireStarboardWeapon:(double)range; - (BOOL) fireTurretCannon:(double)range; - (void) setLaserColor:(OOColor *)color; - (void) setExhaustEmissiveColor:(OOColor *)color; - (OOColor *)laserColor; - (OOColor *)exhaustEmissiveColor; - (BOOL) fireSubentityLaserShot:(double)range; - (BOOL) fireDirectLaserShot:(double)range; - (BOOL) fireDirectLaserDefensiveShot; - (BOOL) fireDirectLaserShotAt:(Entity *)my_target; - (Vector) laserPortOffset:(OOWeaponFacing)direction; - (BOOL) fireLaserShotInDirection:(OOWeaponFacing)direction; - (void) adjustMissedShots:(int)delta; - (int) missedShots; - (BOOL) firePlasmaShotAtOffset:(double)offset speed:(double)speed color:(OOColor *)color; - (void) considerFiringMissile:(double)delta_t; - (Vector) missileLaunchPosition; - (ShipEntity *) fireMissile; - (ShipEntity *) fireMissileWithIdentifier:(NSString *) identifier andTarget:(Entity *) target; - (BOOL) isMissileFlagSet; - (void) setIsMissileFlag:(BOOL)newValue; - (OOTimeDelta) missileLoadTime; - (void) setMissileLoadTime:(OOTimeDelta)newMissileLoadTime; - (void) noticeECM; - (BOOL) fireECM; - (BOOL) cascadeIfAppropriateWithDamageAmount:(double)amount cascadeOwner:(Entity *)owner; - (BOOL) activateCloakingDevice; - (void) deactivateCloakingDevice; - (BOOL) launchCascadeMine; - (ShipEntity *) launchEscapeCapsule; - (OOCommodityType) dumpCargo; - (ShipEntity *) dumpCargoItem:(OOCommodityType)preferred; - (OOCargoType) dumpItem: (ShipEntity*) jetto; - (void) manageCollisions; - (BOOL) collideWithShip:(ShipEntity *)other; - (void) adjustVelocity:(Vector) xVel; - (void) addImpactMoment:(Vector) moment fraction:(GLfloat) howmuch; - (BOOL) canScoop:(ShipEntity *)other; - (void) getTractoredBy:(ShipEntity *)other; - (void) scoopIn:(ShipEntity *)other; - (void) scoopUp:(ShipEntity *)other; - (BOOL) abandonShip; - (void) takeScrapeDamage:(double) amount from:(Entity *) ent; - (void) takeHeatDamage:(double) amount; - (void) enterDock:(StationEntity *)station; - (void) leaveDock:(StationEntity *)station; - (void) enterWormhole:(WormholeEntity *) w_hole; - (void) enterWormhole:(WormholeEntity *) w_hole replacing:(BOOL)replacing; - (void) enterWitchspace; - (void) leaveWitchspace; - (BOOL) witchspaceLeavingEffects; /* Mark this ship as an offender, this is different to setBounty as some ships such as police are not markable. The final bounty may not be equal to existing bounty plus offence_value. */ - (void) markAsOffender:(int)offence_value; - (void) markAsOffender:(int)offence_value withReason:(OOLegalStatusReason)reason; - (void) switchLightsOn; - (void) switchLightsOff; - (BOOL) lightsActive; - (void) setDestination:(HPVector) dest; - (void) setEscortDestination:(HPVector) dest; - (BOOL) canAcceptEscort:(ShipEntity *)potentialEscort; - (BOOL) acceptAsEscort:(ShipEntity *) other_ship; - (void) deployEscorts; - (void) dockEscorts; - (void) setTargetToNearestFriendlyStation; - (void) setTargetToNearestStation; - (void) setTargetToSystemStation; - (void) landOnPlanet:(OOPlanetEntity *)planet; - (void) abortDocking; - (NSDictionary *) dockingInstructions; - (void) broadcastThargoidDestroyed; - (void) broadcastHitByLaserFrom:(ShipEntity*) aggressor_ship; // Sun glare filter - 0 for no filter, 1 for full filter - (GLfloat) sunGlareFilter; - (void) setSunGlareFilter:(GLfloat)newValue; // Unpiloted ships cannot broadcast messages, unless the unpilotedOverride is set to YES. - (void) sendExpandedMessage:(NSString *) message_text toShip:(ShipEntity*) other_ship; - (void) sendMessage:(NSString *) message_text toShip:(ShipEntity*) other_ship withUnpilotedOverride:(BOOL)unpilotedOverride; - (void) broadcastAIMessage:(NSString *) ai_message; - (void) broadcastMessage:(NSString *) message_text withUnpilotedOverride:(BOOL) unpilotedOverride; - (void) setCommsMessageColor; - (void) receiveCommsMessage:(NSString *) message_text from:(ShipEntity *) other; - (void) commsMessage:(NSString *)valueString withUnpilotedOverride:(BOOL)unpilotedOverride; - (BOOL) markedForFines; - (BOOL) markForFines; - (BOOL) isMining; - (void) spawn:(NSString *)roles_number; - (int) checkShipsInVicinityForWitchJumpExit; - (BOOL) trackCloseContacts; - (void) setTrackCloseContacts:(BOOL) value; /* * Changes a ship to a hulk, for example when the pilot ejects. * Aso unsets hulkiness for example when a new pilot gets in. */ - (void) setHulk:(BOOL) isNowHulk; - (BOOL) isHulk; #if OO_SALVAGE_SUPPORT - (void) claimAsSalvage; - (void) sendCoordinatesToPilot; - (void) pilotArrived; #endif - (OOJSScript *) script; - (NSDictionary *) scriptInfo; - (void) overrideScriptInfo:(NSDictionary *)override; // Add items from override (if not nil) to scriptInfo, replacing in case of duplicates. Used for subentities. - (BOOL) scriptedMisjump; - (void) setScriptedMisjump:(BOOL)newValue; - (GLfloat) scriptedMisjumpRange; - (void) setScriptedMisjumpRange:(GLfloat)newValue; - (Entity *)entityForShaderProperties; /* *** Script events. For NPC ships, these call doEvent: on the ship script. For the player, they do that and also call doWorldScriptEvent:. */ - (void) doScriptEvent:(jsid)message; - (void) doScriptEvent:(jsid)message withArgument:(id)argument; - (void) doScriptEvent:(jsid)message withArgument:(id)argument1 andArgument:(id)argument2; - (void) doScriptEvent:(jsid)message withArguments:(NSArray *)arguments; - (void) doScriptEvent:(jsid)message withArguments:(jsval *)argv count:(uintN)argc; - (void) doScriptEvent:(jsid)message inContext:(JSContext *)context withArguments:(jsval *)argv count:(uintN)argc; /* Convenience to send an event with raw JS values, for example: ShipScriptEventNoCx(ship, "doSomething", INT_TO_JSVAL(42)); */ #define ShipScriptEvent(context, ship, event, ...) do { \ jsval argv[] = { __VA_ARGS__ }; \ uintN argc = sizeof argv / sizeof *argv; \ [ship doScriptEvent:OOJSID(event) inContext:context withArguments:argv count:argc]; \ } while (0) #define ShipScriptEventNoCx(ship, event, ...) do { \ jsval argv[] = { __VA_ARGS__ }; \ uintN argc = sizeof argv / sizeof *argv; \ [ship doScriptEvent:OOJSID(event) withArguments:argv count:argc]; \ } while (0) - (void) reactToAIMessage:(NSString *)message context:(NSString *)debugContext; // Immediate message - (void) sendAIMessage:(NSString *)message; // Queued message - (void) doScriptEvent:(jsid)scriptEvent andReactToAIMessage:(NSString *)aiMessage; - (void) doScriptEvent:(jsid)scriptEvent withArgument:(id)argument andReactToAIMessage:(NSString *)aiMessage; @end #ifndef NDEBUG @interface ShipEntity (Debug) - (OOShipGroup *) rawEscortGroup; @end #endif @interface Entity (SubEntityRelationship) /* For the common case of testing whether foo is a ship, bar is a ship, bar is a subentity of foo and this relationship is represented sanely. */ - (BOOL) isShipWithSubEntityShip:(Entity *)other; @end NSDictionary *OODefaultShipShaderMacros(void); GLfloat getWeaponRangeFromType(OOWeaponType weapon_type); // Defined in OOConstToString.m NSString *OOStringFromBehaviour(OOBehaviour behaviour) CONST_FUNC; // Weapon strings prefixed with EQ_, used in shipyard.plist. NSString *OOEquipmentIdentifierFromWeaponType(OOWeaponType weapon) CONST_FUNC; OOWeaponType OOWeaponTypeFromEquipmentIdentifierSloppy(NSString *string) PURE_FUNC; // Uses suffix match for backwards compatibility. OOWeaponType OOWeaponTypeFromEquipmentIdentifierStrict(NSString *string) PURE_FUNC; OOWeaponType OOWeaponTypeFromEquipmentIdentifierLegacy(NSString *string); NSString *OOStringFromWeaponType(OOWeaponType weapon) CONST_FUNC; OOWeaponType OOWeaponTypeFromString(NSString *string) PURE_FUNC; BOOL isWeaponNone(OOWeaponType weapon); NSString *OODisplayStringFromAlertCondition(OOAlertCondition alertCondition); NSString *OOStringFromShipDamageType(OOShipDamageType type) CONST_FUNC; oolite-1.82/src/Core/Entities/ShipEntity.m000066400000000000000000013774041256642440500205100ustar00rootroot00000000000000/* ShipEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the impllied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "ShipEntity.h" #import "ShipEntityAI.h" #import "ShipEntityScriptMethods.h" #import "OOMaths.h" #import "Universe.h" #import "OOShaderMaterial.h" #import "OOOpenGLExtensionManager.h" #import "ResourceManager.h" #import "OOStringExpander.h" #import "OOStringParsing.h" #import "OOCollectionExtractors.h" #import "OOConstToString.h" #import "OOConstToJSString.h" #import "NSScannerOOExtensions.h" #import "OOFilteringEnumerator.h" #import "OORoleSet.h" #import "OOShipGroup.h" #import "OOExcludeObjectEnumerator.h" #import "OOWeakSet.h" #import "GameController.h" #import "MyOpenGLView.h" #import "OOSystemDescriptionManager.h" #import "OOCharacter.h" #import "AI.h" #import "OOMesh.h" #import "OOPlanetDrawable.h" #import "Octree.h" #import "OOColor.h" #import "OOPolygonSprite.h" #import "OOParticleSystem.h" #import "StationEntity.h" #import "DockEntity.h" #import "OOSunEntity.h" #import "OOPlanetEntity.h" #import "PlanetEntity.h" #import "PlayerEntity.h" #import "WormholeEntity.h" #import "OOFlasherEntity.h" #import "OOExhaustPlumeEntity.h" #import "OOSparkEntity.h" #import "OOECMBlastEntity.h" #import "OOPlasmaShotEntity.h" #import "OOFlashEffectEntity.h" #import "OOExplosionCloudEntity.h" #import "ProxyPlayerEntity.h" #import "OOLaserShotEntity.h" #import "OOQuiriumCascadeEntity.h" #import "OORingEffectEntity.h" #import "PlayerEntityLegacyScriptEngine.h" #import "PlayerEntitySound.h" #import "GuiDisplayGen.h" #import "HeadUpDisplay.h" #import "OOEntityFilterPredicate.h" #import "OOShipRegistry.h" #import "OOEquipmentType.h" #import "OODebugGLDrawing.h" #import "OODebugFlags.h" #import "OODebugStandards.h" #import "OOJSScript.h" #import "OOJSVector.h" #import "OOJSEngineTimeManagement.h" #define USEMASC 1 static NSString * const kOOLogSyntaxAddShips = @"script.debug.syntax.addShips"; #ifndef NDEBUG static NSString * const kOOLogEntityBehaviourChanged = @"entity.behaviour.changed"; #endif #if MASS_DEPENDENT_FUEL_PRICES static GLfloat calcFuelChargeRate (GLfloat myMass) { #define kMassCharge 0.65 // the closer to 1 this number is, the more the fuel price changes from ship to ship. #define kBaseCharge (1.0 - kMassCharge) // proportion of price that doesn't change with ship's mass. GLfloat baseMass = [PLAYER baseMass]; // if anything is wrong, use 1 (the default charge rate). if (myMass <= 0.0 || baseMass <=0.0) return 1.0; GLfloat result = (kMassCharge * myMass / baseMass) + kBaseCharge; // round the result to the second decimal digit. result = roundf(result * 100.0f) / 100.0f; // Make sure that the rate is clamped to between three times and a third of the standard charge rate. if (result > 3.0f) result = 3.0f; else if (result < 0.33f) result = 0.33f; return result; #undef kMassCharge #undef kBaseCharge } #endif @interface ShipEntity (Private) - (void)subEntityDied:(ShipEntity *)sub; - (void)subEntityReallyDied:(ShipEntity *)sub; #ifndef NDEBUG - (void) drawDebugStuff; #endif - (void) rescaleBy:(GLfloat)factor; - (BOOL) setUpOneSubentity:(NSDictionary *) subentDict; - (BOOL) setUpOneFlasher:(NSDictionary *) subentDict; - (Entity *) lastAegisLock; - (void) addSubEntity:(Entity *) subent; - (void) refreshEscortPositions; - (HPVector) coordinatesForEscortPosition:(unsigned)idx; - (void) setUpMixedEscorts; - (void) setUpOneEscort:(ShipEntity *)escorter inGroup:(OOShipGroup *)escortGroup withRole:(NSString *)escortRole atPosition:(HPVector)ex_pos andCount:(uint8_t)currentEscortCount; - (void) addSubentityToCollisionRadius:(Entity *) subent; - (ShipEntity *) launchPodWithCrew:(NSArray *)podCrew; - (BOOL) firePlasmaShotAtOffset:(double)offset speed:(double)speed color:(OOColor *)color direction:(OOWeaponFacing)direction; // equipment - (OOEquipmentType *) generateMissileEquipmentTypeFrom:(NSString *)role; - (void) setShipHitByLaser:(ShipEntity *)ship; - (void) noteFrustration:(NSString *)context; - (BOOL) cloakPassive; @end static ShipEntity *doOctreesCollide(ShipEntity *prime, ShipEntity *other); @implementation ShipEntity - (id) init { /* -init used to set up a bunch of defaults that were different from those in -reinit and -setUpShipFromDictionary:. However, it seems that no ships are ever used which are not -setUpShipFromDictionary: (which is as it should be), so these different defaults were meaningless. */ return [self initWithKey:@"" definition:nil]; } - (id) initBypassForPlayer { return [super init]; } // Designated initializer - (id)initWithKey:(NSString *)key definition:(NSDictionary *)dict { OOJS_PROFILE_ENTER NSParameterAssert(dict != nil); self = [super init]; if (self == nil) return nil; _shipKey = [key retain]; isShip = YES; entity_personality = Ranrot() & ENTITY_PERSONALITY_MAX; [self setStatus:STATUS_IN_FLIGHT]; zero_distance = SCANNER_MAX_RANGE2 * 2.0; weapon_recharge_rate = 6.0; shot_time = INITIAL_SHOT_TIME; ship_temperature = SHIP_MIN_CABIN_TEMP; weapon_temp = 0.0f; currentWeaponFacing = WEAPON_FACING_FORWARD; forward_weapon_temp = 0.0f; aft_weapon_temp = 0.0f; port_weapon_temp = 0.0f; starboard_weapon_temp = 0.0f; _nextAegisCheck = -0.1f; aiScriptWakeTime = 0; if (![self setUpShipFromDictionary:dict]) { [self release]; self = nil; } // Problem observed in testing -- Ahruman if (self != nil && !isfinite(maxFlightSpeed)) { OOLog(@"ship.sanityCheck.failed", @"Ship %@ %@ infinite top speed, clamped to 300.", self, @"generated with"); maxFlightSpeed = 300; } return self; OOJS_PROFILE_EXIT } - (BOOL) setUpFromDictionary:(NSDictionary *) shipDict { OOJS_PROFILE_ENTER // Settings shared by players & NPCs. // // In order for default values to work and float values to not be junk, // replace nil with empty dictionary. -- Ahruman 2008-04-28 shipinfoDictionary = [shipDict copy]; if (shipinfoDictionary == nil) shipinfoDictionary = [[NSDictionary alloc] init]; shipDict = shipinfoDictionary; // Ensure no mutation. // set these flags explicitly. haveExecutedSpawnAction = NO; haveStartedJSAI = NO; scripted_misjump = NO; _scriptedMisjumpRange = 0.5; being_fined = NO; isNearPlanetSurface = NO; suppressAegisMessages = NO; isMissile = NO; suppressExplosion = NO; _lightsActive = YES; // set things from dictionary from here out - default values might require adjustment -- Kaks 20091130 _scaleFactor = [shipDict oo_floatForKey:@"model_scale_factor" defaultValue:1.0f]; float defaultSpeed = isStation ? 0.0f : 160.0f; maxFlightSpeed = [shipDict oo_floatForKey:@"max_flight_speed" defaultValue:defaultSpeed]; max_flight_roll = [shipDict oo_floatForKey:@"max_flight_roll" defaultValue:2.0f]; max_flight_pitch = [shipDict oo_floatForKey:@"max_flight_pitch" defaultValue:1.0f]; max_flight_yaw = [shipDict oo_floatForKey:@"max_flight_yaw" defaultValue:max_flight_pitch]; // Note by default yaw == pitch cruiseSpeed = maxFlightSpeed*0.8f; max_thrust = [shipDict oo_floatForKey:@"thrust" defaultValue:15.0f]; thrust = max_thrust; afterburner_rate = [shipDict oo_floatForKey:@"injector_burn_rate" defaultValue:AFTERBURNER_BURNRATE]; afterburner_speed_factor = [shipDict oo_floatForKey:@"injector_speed_factor" defaultValue:7.0f]; if (afterburner_speed_factor < 1.0) { OOLog(@"ship.setup.injectorSpeed",@"injector_speed_factor cannot be lower than 1.0 for %@",self); afterburner_speed_factor = 1.0; } #if OO_VARIABLE_TORUS_SPEED else if (afterburner_speed_factor > MIN_HYPERSPEED_FACTOR) { OOLog(@"ship.setup.injectorSpeed",@"injector_speed_factor cannot be higher than minimum torus speed factor (%f) for %@.",MIN_HYPERSPEED_FACTOR,self); afterburner_speed_factor = MIN_HYPERSPEED_FACTOR; #else else if (afterburner_speed_factor > HYPERSPEED_FACTOR) { OOLog(@"ship.setup.injectorSpeed",@"injector_speed_factor cannot be higher than torus speed factor (%f) for %@.",HYPERSPEED_FACTOR,self); afterburner_speed_factor = HYPERSPEED_FACTOR; #endif } maxEnergy = [shipDict oo_floatForKey:@"max_energy" defaultValue:200.0f]; energy_recharge_rate = [shipDict oo_floatForKey:@"energy_recharge_rate" defaultValue:1.0f]; _showDamage = [shipDict oo_boolForKey:@"show_damage" defaultValue:(energy_recharge_rate > 0)]; // Each new ship should start in seemingly good operating condition, unless specifically told not to - this does not affect the ship's energy levels [self setThrowSparks:[shipDict oo_boolForKey:@"throw_sparks" defaultValue:NO]]; weapon_facings = [shipDict oo_intForKey:@"weapon_facings" defaultValue:VALID_WEAPON_FACINGS] & VALID_WEAPON_FACINGS; if (weapon_facings & WEAPON_FACING_FORWARD) forward_weapon_type = OOWeaponTypeFromString([shipDict oo_stringForKey:@"forward_weapon_type" defaultValue:@"EQ_WEAPON_NONE"]); if (weapon_facings & WEAPON_FACING_AFT) aft_weapon_type = OOWeaponTypeFromString([shipDict oo_stringForKey:@"aft_weapon_type" defaultValue:@"EQ_WEAPON_NONE"]); if (weapon_facings & WEAPON_FACING_PORT) port_weapon_type = OOWeaponTypeFromString([shipDict oo_stringForKey:@"port_weapon_type" defaultValue:@"EQ_WEAPON_NONE"]); if (weapon_facings & WEAPON_FACING_STARBOARD) starboard_weapon_type = OOWeaponTypeFromString([shipDict oo_stringForKey:@"starboard_weapon_type" defaultValue:@"EQ_WEAPON_NONE"]); cloaking_device_active = NO; military_jammer_active = NO; cloakPassive = [shipDict oo_boolForKey:@"cloak_passive" defaultValue:YES]; // Nikos - switched passive cloak default to YES 20120523 cloakAutomatic = [shipDict oo_boolForKey:@"cloak_automatic" defaultValue:YES]; missiles = [shipDict oo_intForKey:@"missiles" defaultValue:0]; max_missiles = [shipDict oo_intForKey:@"max_missiles" defaultValue:missiles]; if (max_missiles > SHIPENTITY_MAX_MISSILES) max_missiles = SHIPENTITY_MAX_MISSILES; if (missiles > max_missiles) missiles = max_missiles; missile_load_time = fmax(0.0, [shipDict oo_doubleForKey:@"missile_load_time" defaultValue:0.0]); // no negative load times missile_launch_time = [UNIVERSE getTime] + missile_load_time; // upgrades: equipment_weight = 0; if ([shipDict oo_fuzzyBooleanForKey:@"has_ecm"]) [self addEquipmentItem:@"EQ_ECM" inContext:@"npc"]; if ([shipDict oo_fuzzyBooleanForKey:@"has_scoop"]) [self addEquipmentItem:@"EQ_FUEL_SCOOPS" inContext:@"npc"]; if ([shipDict oo_fuzzyBooleanForKey:@"has_escape_pod"]) [self addEquipmentItem:@"EQ_ESCAPE_POD" inContext:@"npc"]; if ([shipDict oo_fuzzyBooleanForKey:@"has_cloaking_device"]) [self addEquipmentItem:@"EQ_CLOAKING_DEVICE" inContext:@"npc"]; if ([shipDict oo_floatForKey:@"has_energy_bomb"] > 0) { /* NOTE: has_energy_bomb actually refers to QC mines. max_missiles for NPCs is a newish addition, and ships have traditionally not needed to reserve a slot for a Q-mine added this way. If has_energy_bomb is possible, and max_missiles is not explicit, we add an extra missile slot to compensate. -- Ahruman 2011-03-25 */ if ([shipDict oo_fuzzyBooleanForKey:@"has_energy_bomb"]) { if (max_missiles == missiles && max_missiles < SHIPENTITY_MAX_MISSILES && [shipDict objectForKey:@"max_missiles"] == nil) { max_missiles++; } [self addEquipmentItem:@"EQ_QC_MINE" inContext:@"npc"]; } } if ([shipDict oo_fuzzyBooleanForKey:@"has_fuel_injection"]) [self addEquipmentItem:@"EQ_FUEL_INJECTION" inContext:@"npc"]; #if USEMASC if ([shipDict oo_fuzzyBooleanForKey:@"has_military_jammer"]) [self addEquipmentItem:@"EQ_MILITARY_JAMMER" inContext:@"npc"]; if ([shipDict oo_fuzzyBooleanForKey:@"has_military_scanner_filter"]) [self addEquipmentItem:@"EQ_MILITARY_SCANNER_FILTER" inContext:@"npc"]; #endif // can it be 'mined' for alloys? canFragment = [shipDict oo_fuzzyBooleanForKey:@"fragment_chance" defaultValue:0.9]; isWreckage = NO; // can subentities be destroyed separately? isFrangible = [shipDict oo_boolForKey:@"frangible" defaultValue:YES]; max_cargo = [shipDict oo_unsignedIntForKey:@"max_cargo"]; extra_cargo = [shipDict oo_unsignedIntForKey:@"extra_cargo" defaultValue:15]; hyperspaceMotorSpinTime = [shipDict oo_floatForKey:@"hyperspace_motor_spin_time" defaultValue:DEFAULT_HYPERSPACE_SPIN_TIME]; if(![shipDict oo_boolForKey:@"hyperspace_motor" defaultValue:YES]) hyperspaceMotorSpinTime = -1; [name autorelease]; name = [[shipDict oo_stringForKey:@"name" defaultValue:@"?"] copy]; [shipUniqueName autorelease]; shipUniqueName = [[shipDict oo_stringForKey:@"ship_name" defaultValue:@""] copy]; [shipClassName autorelease]; shipClassName = [[shipDict oo_stringForKey:@"ship_class_name" defaultValue:name] copy]; [displayName autorelease]; displayName = [[shipDict oo_stringForKey:@"display_name" defaultValue:nil] copy]; // Load the model (must be before subentities) NSString *modelName = [shipDict oo_stringForKey:@"model"]; if (modelName != nil) { OOMesh *mesh = nil; mesh = [OOMesh meshWithName:modelName cacheKey:[NSString stringWithFormat:@"%@-%.3f",_shipKey,_scaleFactor] materialDictionary:[shipDict oo_dictionaryForKey:@"materials"] shadersDictionary:[shipDict oo_dictionaryForKey:@"shaders"] smooth:[shipDict oo_boolForKey:@"smooth" defaultValue:NO] shaderMacros:OODefaultShipShaderMacros() shaderBindingTarget:self scaleFactor:_scaleFactor]; if (mesh == nil) return NO; [self setMesh:mesh]; } float density = [shipDict oo_floatForKey:@"density" defaultValue:1.0f]; if (octree) mass = (GLfloat)(density * 20.0 * [octree volume]); DESTROY(default_laser_color); default_laser_color = [[OOColor brightColorWithDescription:[shipDict objectForKey:@"laser_color"]] retain]; if (default_laser_color == nil) { [self setLaserColor:[OOColor redColor]]; } else { [self setLaserColor:default_laser_color]; } // exhaust emissive color OORGBAComponents defaultExhaustEmissiveColorComponents; // pale blue is exhaust default color defaultExhaustEmissiveColorComponents.r = 0.7f; defaultExhaustEmissiveColorComponents.g = 0.9f; defaultExhaustEmissiveColorComponents.b = 1.0f; defaultExhaustEmissiveColorComponents.a = 0.9f; OOColor *color = [OOColor brightColorWithDescription:[shipDict objectForKey:@"exhaust_emissive_color"]]; if (color == nil) color = [OOColor colorWithRGBAComponents:defaultExhaustEmissiveColorComponents]; [self setExhaustEmissiveColor:color]; [self clearSubEntities]; [self setUpSubEntities]; // correctly initialise weaponRange, etc. (must be after subentity setup) if (isWeaponNone(forward_weapon_type)) { OOWeaponType weapon_type = nil; BOOL hasTurrets = NO; NSEnumerator *subEnum = [self shipSubEntityEnumerator]; ShipEntity *se = nil; while (isWeaponNone(weapon_type) && (se = [subEnum nextObject])) { weapon_type = se->forward_weapon_type; if (se->behaviour == BEHAVIOUR_TRACK_AS_TURRET) { hasTurrets = YES; } } if (isWeaponNone(weapon_type) && hasTurrets) { // safety for ships only equipped with turrets weaponRange = 10000.0; } else { [self setWeaponDataFromType:weapon_type]; } } else { [self setWeaponDataFromType:forward_weapon_type]; } // rotating subentities subentityRotationalVelocity = kIdentityQuaternion; ScanQuaternionFromString([shipDict objectForKey:@"rotational_velocity"], &subentityRotationalVelocity); // set weapon offsets [self setDefaultWeaponOffsets]; if (EXPECT(_scaleFactor == 1.0)) { forwardWeaponOffset = [shipDict oo_vectorForKey:@"weapon_position_forward" defaultValue:forwardWeaponOffset]; aftWeaponOffset = [shipDict oo_vectorForKey:@"weapon_position_aft" defaultValue:aftWeaponOffset]; portWeaponOffset = [shipDict oo_vectorForKey:@"weapon_position_port" defaultValue:portWeaponOffset]; starboardWeaponOffset = [shipDict oo_vectorForKey:@"weapon_position_starboard" defaultValue:starboardWeaponOffset]; // fuel scoop destination position (where cargo gets sucked into) tractor_position = [shipDict oo_vectorForKey:@"scoop_position"]; } else { forwardWeaponOffset = vector_multiply_scalar([shipDict oo_vectorForKey:@"weapon_position_forward" defaultValue:forwardWeaponOffset],_scaleFactor); aftWeaponOffset = vector_multiply_scalar([shipDict oo_vectorForKey:@"weapon_position_aft" defaultValue:aftWeaponOffset],_scaleFactor); portWeaponOffset = vector_multiply_scalar([shipDict oo_vectorForKey:@"weapon_position_port" defaultValue:portWeaponOffset],_scaleFactor); starboardWeaponOffset = vector_multiply_scalar([shipDict oo_vectorForKey:@"weapon_position_starboard" defaultValue:starboardWeaponOffset],_scaleFactor); tractor_position = vector_multiply_scalar([shipDict oo_vectorForKey:@"scoop_position"],_scaleFactor); } // sun glare filter - default is no filter [self setSunGlareFilter:[shipDict oo_floatForKey:@"sun_glare_filter" defaultValue:0.0f]]; // Get scriptInfo dictionary, containing arbitrary stuff scripts might be interested in. scriptInfo = [[shipDict oo_dictionaryForKey:@"script_info" defaultValue:nil] retain]; explosionType = [[shipDict oo_arrayForKey:@"explosion_type" defaultValue:nil] retain]; return YES; OOJS_PROFILE_EXIT } - (BOOL) setUpShipFromDictionary:(NSDictionary *) shipDict { OOJS_PROFILE_ENTER if (![self setUpFromDictionary:shipDict]) return NO; // NPC-only settings. // orientation = kIdentityQuaternion; rotMatrix = kIdentityMatrix; v_forward = kBasisZVector; v_up = kBasisYVector; v_right = kBasisXVector; reference = v_forward; // reference vector for (* turrets *) isShip = YES; // scan class settings. 'scanClass' is in common usage, but we could also have a more standard 'scan_class' key with higher precedence. Kaks 20090810 // let's see if scan_class is set... scanClass = OOScanClassFromString([shipDict oo_stringForKey:@"scan_class" defaultValue:@"CLASS_NOT_SET"]); // if not, try 'scanClass'. NOTE: non-standard capitalization is documented and entrenched. if (scanClass == CLASS_NOT_SET) { scanClass = OOScanClassFromString([shipDict oo_stringForKey:@"scanClass" defaultValue:@"CLASS_NOT_SET"]); } scan_description = [shipDict oo_stringForKey:@"scan_description" defaultValue:nil]; // FIXME: give NPCs shields instead. if ([shipDict oo_fuzzyBooleanForKey:@"has_shield_booster"]) [self addEquipmentItem:@"EQ_SHIELD_BOOSTER" inContext:@"npc"]; if ([shipDict oo_fuzzyBooleanForKey:@"has_shield_enhancer"]) [self addEquipmentItem:@"EQ_SHIELD_ENHANCER" inContext:@"npc"]; // Start with full energy banks. energy = maxEnergy; weapon_temp = 0.0f; forward_weapon_temp = 0.0f; aft_weapon_temp = 0.0f; port_weapon_temp = 0.0f; starboard_weapon_temp = 0.0f; // setWeaponDataFromType inside setUpFromDictionary should set weapon_damage from the front laser. // no weapon_damage? It's a missile: set weapon_damage from shipdata! if (weapon_damage == 0.0) { weapon_damage_override = weapon_damage = [shipDict oo_floatForKey:@"weapon_energy" defaultValue:0]; // any damage value for missiles/bombs } else { weapon_damage_override = 0; } scannerRange = [shipDict oo_floatForKey:@"scanner_range" defaultValue:(float)SCANNER_MAX_RANGE]; fuel = [shipDict oo_unsignedShortForKey:@"fuel"]; // Does it make sense that this defaults to 0? Should it not be 70? -- Ahruman fuel_accumulator = 1.0; [self setBounty:[shipDict oo_unsignedIntForKey:@"bounty" defaultValue:0] withReason:kOOLegalStatusReasonSetup]; [shipAI autorelease]; shipAI = [[AI alloc] init]; [shipAI setOwner:self]; [self setAITo:[shipDict oo_stringForKey:@"ai_type" defaultValue:@"nullAI.plist"]]; likely_cargo = [shipDict oo_unsignedIntForKey:@"likely_cargo"]; noRocks = [shipDict oo_fuzzyBooleanForKey:@"no_boulders"]; commodity_amount = 0; commodity_type = nil; NSString *cargoString = [shipDict oo_stringForKey:@"cargo_carried"]; if (cargoString != nil) { if ([cargoString isEqualToString:@"SCARCE_GOODS"]) { cargo_flag = CARGO_FLAG_FULL_SCARCE; } else if ([cargoString isEqualToString:@"PLENTIFUL_GOODS"]) { cargo_flag = CARGO_FLAG_FULL_PLENTIFUL; } else { cargo_flag = CARGO_FLAG_FULL_UNIFORM; OOCommodityType c_commodity = nil; int c_amount = 1; NSScanner *scanner = [NSScanner scannerWithString:cargoString]; if ([scanner scanInt:&c_amount]) { [scanner ooliteScanCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:NULL]; // skip whitespace c_commodity = [[scanner string] substringFromIndex:[scanner scanLocation]]; if ([[UNIVERSE commodities] goodDefined:c_commodity]) { [self setCommodityForPod:c_commodity andAmount:c_amount]; } } else { c_amount = 1; c_commodity = [shipDict oo_stringForKey:@"cargo_carried"]; if ([[UNIVERSE commodities] goodDefined:c_commodity]) { [self setCommodityForPod:c_commodity andAmount:c_amount]; } } } } cargoString = [shipDict oo_stringForKey:@"cargo_type"]; if (cargoString) { if (cargo != nil) [cargo autorelease]; cargo = [[NSMutableArray alloc] initWithCapacity:max_cargo]; // alloc retains; [self setUpCargoType:cargoString]; } else if (scanClass != CLASS_CARGO) { if (cargo != nil) [cargo autorelease]; cargo = [[NSMutableArray alloc] initWithCapacity:max_cargo]; // alloc retains; // if not CLASS_CARGO, and no cargo type set, default to CARGO_NOT_CARGO cargo_type = CARGO_NOT_CARGO; } hasScoopMessage = [shipDict oo_boolForKey:@"has_scoop_message" defaultValue:YES]; [roleSet release]; roleSet = [[[OORoleSet roleSetWithString:[shipDict oo_stringForKey:@"roles"]] roleSetWithRemovedRole:@"player"] retain]; [primaryRole release]; primaryRole = nil; [self setOwner:self]; [self setHulk:[shipDict oo_boolForKey:@"is_hulk"]]; // these are the colors used for the "lollipop" of the ship. Any of the two (or both, for flash effect) can be defined. nil means use default from shipData. [self setScannerDisplayColor1:nil]; [self setScannerDisplayColor2:nil]; // and the same for the "hostile" colours [self setScannerDisplayColorHostile1:nil]; [self setScannerDisplayColorHostile2:nil]; // Populate the missiles here. Must come after scanClass. _missileRole = [shipDict oo_stringForKey:@"missile_role"]; unsigned i, j; for (i = 0, j = 0; i < missiles; i++) { missile_list[i] = [self selectMissile]; // could loop forever (if missile_role is badly defined, selectMissile might return nil in some cases) . Try 3 times, and if no luck, skip if (missile_list[i] == nil && j < 3) { j++; i--; } else { j = 0; if (missile_list[i] == nil) { missiles--; } } } // accuracy. Must come after scanClass, because we are using scanClass to determine if this is a missile. // missiles: range 0 to +10 // ships: range -5 to +10, but randomly only -5 <= accuracy < +5 // enables "better" AIs at +5 and above // police and military always have positive accuracy accuracy = [shipDict oo_floatForKey:@"accuracy" defaultValue:-100.0f]; // Out-of-range default if (accuracy < -5.0f || accuracy > 10.0f) { accuracy = (randf() * 10.0)-5.0; if (accuracy < 0.0f && (scanClass == CLASS_MILITARY || scanClass == CLASS_POLICE)) { // police and military pilots have a better average skill. accuracy = -accuracy; } } if (scanClass == CLASS_MISSILE) { // missile accuracy range is 0 to 10 accuracy = OOClamp_0_max_f(accuracy, 10.0f); } [self setAccuracy:accuracy]; // set derived variables _missed_shots = 0; // escorts _maxEscortCount = MIN([shipDict oo_unsignedCharForKey:@"escorts" defaultValue:0], (uint8_t)MAX_ESCORTS); _pendingEscortCount = _maxEscortCount; if (_pendingEscortCount == 0 && [shipDict oo_arrayForKey:@"escort_roles" defaultValue:nil] != nil) { // mostly ignored by setUpMixedEscorts, but needs to be high // enough that it doesn't end up at zero (e.g. by governmental // reductions in [Universe addShipAt] _pendingEscortCount = MAX_ESCORTS; } // beacons [self setBeaconCode:[shipDict oo_stringForKey:@"beacon"]]; [self setBeaconLabel:[shipDict oo_stringForKey:@"beacon_label" defaultValue:[shipDict oo_stringForKey:@"beacon"]]]; // contact tracking entities [self setTrackCloseContacts:[shipDict oo_boolForKey:@"track_contacts" defaultValue:NO]]; // ship skin insulation factor (1.0 is normal) [self setHeatInsulation:[shipDict oo_floatForKey:@"heat_insulation" defaultValue:[self hasHeatShield] ? 2.0 : 1.0]]; // unpiloted (like missiles asteroids etc.) _explicitlyUnpiloted = [shipDict oo_fuzzyBooleanForKey:@"unpiloted"]; if (_explicitlyUnpiloted) { [self setCrew:nil]; } else { // crew and passengers NSDictionary *cdict = [[UNIVERSE characters] objectForKey:[shipDict oo_stringForKey:@"pilot"]]; if (cdict != nil) { OOCharacter *pilot = [OOCharacter characterWithDictionary:cdict]; [self setCrew:[NSArray arrayWithObject:pilot]]; } } [self setShipScript:[shipDict oo_stringForKey:@"script"]]; home_system = [UNIVERSE currentSystemID]; destination_system = [UNIVERSE currentSystemID]; reactionTime = [shipDict oo_floatForKey: @"reaction_time" defaultValue: COMBAT_AI_STANDARD_REACTION_TIME]; return YES; OOJS_PROFILE_EXIT } - (void) setSubIdx:(NSUInteger)value { _subIdx = value; } - (NSUInteger) subIdx { return _subIdx; } - (NSUInteger) maxShipSubEntities { return _maxShipSubIdx; } - (NSString *) repeatString:(NSString *)str times:(NSUInteger)times { if (times == 0) return @""; NSMutableString *result = [NSMutableString stringWithCapacity:[str length] * times]; for (NSUInteger i = 0; i < times; i++) { [result appendString:str]; } return result; } - (NSString *) serializeShipSubEntities { NSMutableString *result = [NSMutableString stringWithCapacity:4]; NSEnumerator *subEnum = nil; ShipEntity *se = nil; NSUInteger diff, i = 0; for (subEnum = [self shipSubEntityEnumerator]; (se = [subEnum nextObject]); ) { diff = [se subIdx] - i; i += diff + 1; [result appendString:[self repeatString:@"0" times:diff]]; [result appendString:@"1"]; } // add trailing zeroes [result appendString:[self repeatString:@"0" times:[self maxShipSubEntities] - i]]; return result; } - (void) deserializeShipSubEntitiesFrom:(NSString *)string { NSArray *subEnts = [[self shipSubEntityEnumerator] allObjects]; NSInteger i,idx, start = [subEnts count] - 1; NSInteger strMaxIdx = [string length] - 1; ShipEntity *se = nil; for (i = start; i >= 0; i--) { se = (ShipEntity *)[subEnts objectAtIndex:i]; idx = [se subIdx]; // should be identical to i, but better safe than sorry... if (idx <= strMaxIdx && [[string substringWithRange:NSMakeRange(idx, 1)] isEqualToString:@"0"]) { [se setSuppressExplosion:NO]; [se setEnergy:1]; [se takeEnergyDamage:500000000.0 from:nil becauseOf:nil]; } } } - (BOOL) setUpSubEntities { OOJS_PROFILE_ENTER unsigned int i; NSDictionary *shipDict = [self shipInfoDictionary]; NSArray *plumes = [shipDict oo_arrayForKey:@"exhaust"]; _profileRadius = collision_radius; _maxShipSubIdx = 0; for (i = 0; i < [plumes count]; i++) { NSArray *definition = ScanTokensFromString([plumes oo_stringAtIndex:i]); OOExhaustPlumeEntity *exhaust = [OOExhaustPlumeEntity exhaustForShip:self withDefinition:definition andScale:_scaleFactor]; [self addSubEntity:exhaust]; } NSArray *subs = [shipDict oo_arrayForKey:@"subentities"]; totalBoundingBox = boundingBox; for (i = 0; i < [subs count]; i++) { [self setUpOneSubentity:[subs oo_dictionaryAtIndex:i]]; } no_draw_distance = _profileRadius * _profileRadius * NO_DRAW_DISTANCE_FACTOR * NO_DRAW_DISTANCE_FACTOR * 2.0; return YES; OOJS_PROFILE_EXIT } - (GLfloat) frustumRadius { OOScalar exhaust_length = 0; NSEnumerator *exEnum = nil; OOExhaustPlumeEntity *exEnt = nil; for (exEnum = [self exhaustEnumerator]; (exEnt = [exEnum nextObject]); ) { if ([exEnt findCollisionRadius] > exhaust_length) { exhaust_length = [exEnt findCollisionRadius]; } } return _profileRadius + exhaust_length; } - (BOOL) setUpOneSubentity:(NSDictionary *) subentDict { OOJS_PROFILE_ENTER NSString *type = nil; type = [subentDict oo_stringForKey:@"type"]; if ([type isEqualToString:@"flasher"]) { return [self setUpOneFlasher:subentDict]; } else { return [self setUpOneStandardSubentity:subentDict asTurret:[type isEqualToString:@"ball_turret"]]; } OOJS_PROFILE_EXIT } - (BOOL) setUpOneFlasher:(NSDictionary *) subentDict { OOFlasherEntity *flasher = [OOFlasherEntity flasherWithDictionary:subentDict]; [flasher setPosition:HPvector_multiply_scalar([subentDict oo_hpvectorForKey:@"position"],_scaleFactor)]; [flasher rescaleBy:_scaleFactor]; [self addSubEntity:flasher]; return YES; } - (BOOL) setUpOneStandardSubentity:(NSDictionary *)subentDict asTurret:(BOOL)asTurret { ShipEntity *subentity = nil; NSString *subentKey = nil; HPVector subPosition; Quaternion subOrientation; subentKey = [subentDict oo_stringForKey:@"subentity_key"]; if (subentKey == nil) { OOLog(@"setup.ship.badEntry.subentities",@"Failed to set up entity - no subentKey in %@",subentDict); return NO; } if (!asTurret && [self isStation] && [subentDict oo_boolForKey:@"is_dock"]) { subentity = [UNIVERSE newDockWithName:subentKey andScaleFactor:_scaleFactor]; } else { subentity = [UNIVERSE newSubentityWithName:subentKey andScaleFactor:_scaleFactor]; } if (subentity == nil) { OOLog(@"setup.ship.badEntry.subentities",@"Failed to set up entity %@",subentKey); return NO; } subPosition = HPvector_multiply_scalar([subentDict oo_hpvectorForKey:@"position"],_scaleFactor); subOrientation = [subentDict oo_quaternionForKey:@"orientation"]; [subentity setPosition:subPosition]; [subentity setOrientation:subOrientation]; [subentity setReference:vector_forward_from_quaternion(subOrientation)]; // subentities inherit parent personality [subentity setEntityPersonalityInt:[self entityPersonalityInt]]; if (asTurret) { [subentity setBehaviour:BEHAVIOUR_TRACK_AS_TURRET]; [subentity setWeaponRechargeRate:[subentDict oo_floatForKey:@"fire_rate" defaultValue:TURRET_SHOT_FREQUENCY]]; [subentity setWeaponEnergy:[subentDict oo_floatForKey:@"weapon_energy" defaultValue:TURRET_TYPICAL_ENERGY]]; [subentity setWeaponRange:[subentDict oo_floatForKey:@"weapon_range" defaultValue:TURRET_SHOT_RANGE]]; [subentity setStatus: STATUS_ACTIVE]; } else { [subentity setStatus:STATUS_INACTIVE]; } [subentity overrideScriptInfo:[subentDict oo_dictionaryForKey:@"script_info"]]; [self addSubEntity:subentity]; [subentity setSubIdx:_maxShipSubIdx]; _maxShipSubIdx++; // update subentities BoundingBox sebb = [subentity findSubentityBoundingBox]; bounding_box_add_vector(&totalBoundingBox, sebb.max); bounding_box_add_vector(&totalBoundingBox, sebb.min); if (!asTurret && [self isStation] && [subentDict oo_boolForKey:@"is_dock"]) { BOOL allow_docking = [subentDict oo_boolForKey:@"allow_docking" defaultValue:YES]; BOOL ddc = [subentDict oo_boolForKey:@"disallowed_docking_collides" defaultValue:NO]; BOOL allow_launching = [subentDict oo_boolForKey:@"allow_launching" defaultValue:YES]; // do not include this key in OOShipRegistry; should never be set by shipdata BOOL virtual_dock = [subentDict oo_boolForKey:@"_is_virtual_dock" defaultValue:NO]; if (virtual_dock) { [(DockEntity *)subentity setVirtual]; } [(DockEntity *)subentity setDimensionsAndCorridor:allow_docking:ddc:allow_launching]; [subentity setDisplayName:[subentDict oo_stringForKey:@"dock_label" defaultValue:@"the docking bay"]]; } [subentity release]; return YES; } - (BOOL) isTemplateCargoPod { return [[self primaryRole] isEqualToString:@"oolite-template-cargopod"]; } - (void) setUpCargoType:(NSString *) cargoString { cargo_type = StringToCargoType(cargoString); switch (cargo_type) { case CARGO_SLAVES: commodity_amount = 1; DESTROY(commodity_type); commodity_type = @"slaves"; cargo_type = CARGO_RANDOM; // not realy random, but it tells that cargo is selected. break; case CARGO_ALLOY: commodity_amount = 1; DESTROY(commodity_type); commodity_type = @"alloys"; cargo_type = CARGO_RANDOM; break; case CARGO_MINERALS: commodity_amount = 1; DESTROY(commodity_type); commodity_type = @"minerals"; cargo_type = CARGO_RANDOM; break; case CARGO_THARGOID: commodity_amount = 1; DESTROY(commodity_type); commodity_type = @"alien_items"; cargo_type = CARGO_RANDOM; break; case CARGO_SCRIPTED_ITEM: commodity_amount = 1; // value > 0 is needed to be recognised as cargo by scripts; DESTROY(commodity_type); // will be defined elsewhere when needed. break; case CARGO_RANDOM: // Could already be set by the cargo_carried key. If not, ensure at least one. if (commodity_amount == 0) commodity_amount = 1; break; default: break; } } - (void) dealloc { /* NOTE: we guarantee that entityDestroyed is sent immediately after the JS ship becomes invalid (as a result of dropping the weakref), i.e. with no intervening script activity. It has to be after the invalidation so that scripts can't directly or indirectly cause the ship to become strong-referenced. (Actually, we could handle that situation by breaking out of dealloc, but that's a nasty abuse of framework semantics and would require special-casing in subclasses.) -- Ahruman 2011-02-27 */ [weakSelf weakRefDrop]; weakSelf = nil; ShipScriptEventNoCx(self, "entityDestroyed"); [self setTrackCloseContacts:NO]; // deallocs tracking dictionary [[self parentEntity] subEntityReallyDied:self]; // Will do nothing if we're not really a subentity [self clearSubEntities]; DESTROY(_shipKey); DESTROY(shipinfoDictionary); DESTROY(shipAI); DESTROY(cargo); DESTROY(name); DESTROY(shipUniqueName); DESTROY(shipClassName); DESTROY(displayName); DESTROY(scan_description); DESTROY(roleSet); DESTROY(primaryRole); DESTROY(laser_color); DESTROY(default_laser_color); DESTROY(exhaust_emissive_color); DESTROY(scanner_display_color1); DESTROY(scanner_display_color2); DESTROY(scanner_display_color_hostile1); DESTROY(scanner_display_color_hostile2); DESTROY(script); DESTROY(aiScript); DESTROY(previousCondition); DESTROY(dockingInstructions); DESTROY(crew); DESTROY(lastRadioMessage); DESTROY(octree); DESTROY(_defenseTargets); DESTROY(_collisionExceptions); DESTROY(commodity_type); [self setSubEntityTakingDamage:nil]; [self removeAllEquipment]; [_group removeShip:self]; DESTROY(_group); [_escortGroup removeShip:self]; DESTROY(_escortGroup); DESTROY(_lastAegisLock); DESTROY(_beaconCode); DESTROY(_beaconLabel); DESTROY(_beaconDrawable); DESTROY(explosionType); [super dealloc]; } - (void) removeScript { [script autorelease]; script = nil; } - (void) clearSubEntities { [subEntities makeObjectsPerformSelector:@selector(setOwner:) withObject:nil]; // Ensure backlinks are broken [subEntities release]; subEntities = nil; // reset size & mass! collision_radius = [self findCollisionRadius]; _profileRadius = collision_radius; float density = [[self shipInfoDictionary] oo_floatForKey:@"density" defaultValue:1.0f]; if (octree) mass = (GLfloat)(density * 20.0 * [octree volume]); } - (Quaternion) subEntityRotationalVelocity { return subentityRotationalVelocity; } - (void) setSubEntityRotationalVelocity:(Quaternion)rv { subentityRotationalVelocity = rv; } - (NSString *)descriptionComponents { if (![self isSubEntity]) { return [NSString stringWithFormat:@"\"%@\" %@", [self name], [super descriptionComponents]]; } else { // ID, scanClass and status are of no interest for subentities. NSString *subtype = nil; if ([self behaviour] == BEHAVIOUR_TRACK_AS_TURRET) subtype = @"(turret)"; else subtype = @"(subentity)"; return [NSString stringWithFormat:@"\"%@\" position: %@ %@", [self name], HPVectorDescription([self position]), subtype]; } } - (NSString *) shortDescriptionComponents { return [NSString stringWithFormat:@"\"%@\"", [self name]]; } - (GLfloat) sunGlareFilter { return sunGlareFilter; } - (void) setSunGlareFilter:(GLfloat)newValue { sunGlareFilter = OOClamp_0_1_f(newValue); } - (GLfloat) accuracy { return accuracy; } - (void) setAccuracy:(GLfloat) new_accuracy { if (new_accuracy < 0.0f && scanClass == CLASS_MISSILE) { new_accuracy = 0.0; } else if (new_accuracy < -5.0f) { new_accuracy = -5.0; } else if (new_accuracy > 10.0f) { new_accuracy = 10.0; } accuracy = new_accuracy; pitch_tolerance = 0.01 * (85.0f + accuracy); // especially against small targets, less good pilots will waste some shots aim_tolerance = 240.0 - (18.0f * accuracy); if (accuracy >= COMBAT_AI_ISNT_AWFUL && missile_load_time < 0.1) { missile_load_time = 2.0; // smart enough not to waste all missiles on 1 ECM! } } - (OOMesh *)mesh { return (OOMesh *)[self drawable]; } - (void)setMesh:(OOMesh *)mesh { if (mesh != [self mesh]) { [self setDrawable:mesh]; [octree autorelease]; octree = [[mesh octree] retain]; } } - (BoundingBox) totalBoundingBox { return totalBoundingBox; } - (Vector) forwardVector { return v_forward; } - (Vector) upVector { return v_up; } - (Vector) rightVector { return v_right; } - (BOOL) scriptedMisjump { return scripted_misjump; } - (void) setScriptedMisjump:(BOOL)newValue { scripted_misjump = !!newValue; } - (GLfloat) scriptedMisjumpRange { return _scriptedMisjumpRange; } - (void) setScriptedMisjumpRange:(GLfloat)newValue { _scriptedMisjumpRange = newValue; } - (NSArray *) subEntities { return [[subEntities copy] autorelease]; } - (NSUInteger) subEntityCount { return [subEntities count]; } - (BOOL) hasSubEntity:(Entity *)sub { return [subEntities containsObject:sub]; } - (NSEnumerator *)subEntityEnumerator { return [[self subEntities] objectEnumerator]; } - (NSEnumerator *)shipSubEntityEnumerator { return [[self subEntities] objectEnumeratorFilteredWithSelector:@selector(isShip)]; } - (NSEnumerator *)flasherEnumerator { return [[self subEntities] objectEnumeratorFilteredWithSelector:@selector(isFlasher)]; } - (NSEnumerator *)exhaustEnumerator { return [[self subEntities] objectEnumeratorFilteredWithSelector:@selector(isExhaust)]; } - (ShipEntity *) subEntityTakingDamage { ShipEntity *result = [_subEntityTakingDamage weakRefUnderlyingObject]; #ifndef NDEBUG // Sanity check - there have been problems here, see fireLaserShotInDirection: // -parentEntity will take care of reporting insanity. if ([result parentEntity] != self) result = nil; #endif // Clear the weakref if the subentity is dead. if (result == nil) [self setSubEntityTakingDamage:nil]; return result; } - (void) setSubEntityTakingDamage:(ShipEntity *)sub { #ifndef NDEBUG // Sanity checks: sub must be a ship subentity of self, or nil. if (sub != nil) { if (![self hasSubEntity:sub]) { OOLog(@"ship.subentity.sanityCheck.failed.details", @"Attempt to set subentity taking damage of %@ to %@, which is not a subentity.", [self shortDescription], sub); sub = nil; } else if (![sub isShip]) { OOLog(@"ship.subentity.sanityCheck.failed", @"Attempt to set subentity taking damage of %@ to %@, which is not a ship.", [self shortDescription], sub); sub = nil; } } #endif [_subEntityTakingDamage release]; _subEntityTakingDamage = [sub weakRetain]; } - (OOScript *)shipScript { return script; } - (OOScript *)shipAIScript { return aiScript; } - (OOTimeAbsolute) shipAIScriptWakeTime { return aiScriptWakeTime; } - (void) setAIScriptWakeTime:(OOTimeAbsolute) t { aiScriptWakeTime = t; } - (BoundingBox)findBoundingBoxRelativeToPosition:(HPVector)opv InVectors:(Vector) _i :(Vector) _j :(Vector) _k { // HPVect: check that this conversion doesn't lose needed precision return [[self mesh] findBoundingBoxRelativeToPosition:HPVectorToVector(opv) basis:_i :_j :_k selfPosition:HPVectorToVector(position) selfBasis:v_right :v_up :v_forward]; } - (Octree *) octree { return octree; } - (float) volume { return [octree volume]; } - (GLfloat) doesHitLine:(HPVector)v0 :(HPVector)v1 { Vector u0 = HPVectorToVector(HPvector_between(position, v0)); // relative to origin of model / octree Vector u1 = HPVectorToVector(HPvector_between(position, v1)); Vector w0 = make_vector(dot_product(u0, v_right), dot_product(u0, v_up), dot_product(u0, v_forward)); // in ijk vectors Vector w1 = make_vector(dot_product(u1, v_right), dot_product(u1, v_up), dot_product(u1, v_forward)); return [octree isHitByLine:w0 :w1]; } - (GLfloat) doesHitLine:(HPVector)v0 :(HPVector)v1 :(ShipEntity **)hitEntity { if (hitEntity) hitEntity[0] = (ShipEntity*)nil; Vector u0 = HPVectorToVector(HPvector_between(position, v0)); // relative to origin of model / octree Vector u1 = HPVectorToVector(HPvector_between(position, v1)); Vector w0 = make_vector(dot_product(u0, v_right), dot_product(u0, v_up), dot_product(u0, v_forward)); // in ijk vectors Vector w1 = make_vector(dot_product(u1, v_right), dot_product(u1, v_up), dot_product(u1, v_forward)); GLfloat hit_distance = [octree isHitByLine:w0 :w1]; if (hit_distance) { if (hitEntity) hitEntity[0] = self; } NSEnumerator *subEnum = nil; ShipEntity *se = nil; for (subEnum = [self shipSubEntityEnumerator]; (se = [subEnum nextObject]); ) { HPVector p0 = [se absolutePositionForSubentity]; Triangle ijk = [se absoluteIJKForSubentity]; u0 = HPVectorToVector(HPvector_between(p0, v0)); u1 = HPVectorToVector(HPvector_between(p0, v1)); w0 = resolveVectorInIJK(u0, ijk); w1 = resolveVectorInIJK(u1, ijk); GLfloat hitSub = [se->octree isHitByLine:w0 :w1]; if (hitSub && (hit_distance == 0 || hit_distance > hitSub)) { hit_distance = hitSub; if (hitEntity) { *hitEntity = se; } } } return hit_distance; } - (GLfloat)doesHitLine:(HPVector)v0 :(HPVector)v1 withPosition:(HPVector)o andIJK:(Vector)i :(Vector)j :(Vector)k { Vector u0 = HPVectorToVector(HPvector_between(o, v0)); // relative to origin of model / octree Vector u1 = HPVectorToVector(HPvector_between(o, v1)); Vector w0 = make_vector(dot_product(u0, i), dot_product(u0, j), dot_product(u0, k)); // in ijk vectors Vector w1 = make_vector(dot_product(u1, j), dot_product(u1, j), dot_product(u1, k)); return [octree isHitByLine:w0 :w1]; } - (void) wasAddedToUniverse { [super wasAddedToUniverse]; // if we have a universal id then we can proceed to set up any // stuff that happens when we get added to the UNIVERSE if (universalID != NO_TARGET) { // set up escorts if (([self status] == STATUS_IN_FLIGHT || [self status] == STATUS_LAUNCHING) && _pendingEscortCount != 0) // just popped into existence { [self setUpEscorts]; } else { /* Earlier there was a silly log message here because I thought this would never happen, but wasn't entirely sure. Turns out it did! -- Ahruman 2009-09-13 */ _pendingEscortCount = 0; } } // Tell subentities, too [subEntities makeObjectsPerformSelector:@selector(wasAddedToUniverse)]; [self resetExhaustPlumes]; } - (void)wasRemovedFromUniverse { [subEntities makeObjectsPerformSelector:@selector(wasRemovedFromUniverse)]; } - (HPVector)absoluteTractorPosition { return HPvector_add(position, vectorToHPVector(quaternion_rotate_vector([self normalOrientation], tractor_position))); } - (NSString *) beaconCode { return _beaconCode; } - (void) setBeaconCode:(NSString *)bcode { if ([bcode length] == 0) bcode = nil; if (_beaconCode != bcode) { [_beaconCode release]; _beaconCode = [bcode copy]; DESTROY(_beaconDrawable); } // if not blanking code and label is currently blank, default label to code if (bcode != nil && (_beaconLabel == nil || [_beaconLabel length] == 0)) { [self setBeaconLabel:bcode]; } } - (NSString *) beaconLabel { return _beaconLabel; } - (void) setBeaconLabel:(NSString *)blabel { if ([blabel length] == 0) blabel = nil; if (_beaconLabel != blabel) { [_beaconLabel release]; _beaconLabel = [OOExpand(blabel) retain]; } } - (BOOL) isVisible { return cam_zero_distance <= no_draw_distance; } - (BOOL) isBeacon { return [self beaconCode] != nil; } - (id ) beaconDrawable { if (_beaconDrawable == nil) { NSString *beaconCode = [self beaconCode]; NSUInteger length = [beaconCode length]; if (length > 1) { NSArray *iconData = [[UNIVERSE descriptions] oo_arrayForKey:beaconCode]; if (iconData != nil) _beaconDrawable = [[OOPolygonSprite alloc] initWithDataArray:iconData outlineWidth:0.5 name:beaconCode]; } if (_beaconDrawable == nil) { if (length > 0) _beaconDrawable = [[beaconCode substringToIndex:1] retain]; else _beaconDrawable = @""; } } return _beaconDrawable; } - (Entity *) prevBeacon { return [_prevBeacon weakRefUnderlyingObject]; } - (Entity *) nextBeacon { return [_nextBeacon weakRefUnderlyingObject]; } - (void) setPrevBeacon:(Entity *)beaconShip { if (beaconShip != [self prevBeacon]) { [_prevBeacon release]; _prevBeacon = [beaconShip weakRetain]; } } - (void) setNextBeacon:(Entity *)beaconShip { if (beaconShip != [self nextBeacon]) { [_nextBeacon release]; _nextBeacon = [beaconShip weakRetain]; } } #define kBoulderRole (@"boulder") - (void) setIsBoulder:(BOOL)flag { if (flag) [self addRole:kBoulderRole]; else [self removeRole:kBoulderRole]; } - (BOOL) isBoulder { return [roleSet hasRole:kBoulderRole]; } - (BOOL) isMinable { if ([self hasRole:@"asteroid"] || [self isBoulder]) { if (!noRocks) { return YES; } } return NO; } - (BOOL) countsAsKill { return [[self shipInfoDictionary] oo_boolForKey:@"counts_as_kill" defaultValue:YES]; } - (void) setUpEscorts { // Ensure that we do not try to create escorts if we are an escort ship ourselves. // This could lead to circular reference memory overflows (e.g. "boa-mk2" trying to create 4 "boa-mk2" // escorts or the case of two ships specifying eachother as escorts) - Nikos 20090510 if ([self isEscort]) { OOLogWARN(@"ship.setUp.escortShipCircularReference", @"Ship %@ requested escorts, when it is an escort ship itself. Avoiding possible circular reference overflow by ignoring escort setup.", self); return; } if ([shipinfoDictionary objectForKey:@"escort_roles"] != nil) { [self setUpMixedEscorts]; return; } NSString *defaultRole = @"escort"; NSString *escortRole = nil; NSString *escortShipKey = nil; if (_pendingEscortCount == 0) return; if (_maxEscortCount < _pendingEscortCount) { if ([self hasPrimaryRole:@"police"] || [self hasPrimaryRole:@"hunter"]) { _maxEscortCount = MAX_ESCORTS; // police and hunters get up to MAX_ESCORTS, overriding the 'escorts' key. [self updateEscortFormation]; } else { _pendingEscortCount = _maxEscortCount; // other ships can only get what's defined inside their 'escorts' key. } } if ([self isPolice]) defaultRole = @"wingman"; escortRole = [shipinfoDictionary oo_stringForKey:@"escort_role" defaultValue:nil]; if (escortRole == nil) escortRole = [shipinfoDictionary oo_stringForKey:@"escort-role" defaultValue:defaultRole]; if (![escortRole isEqualToString: defaultRole]) { if (![[UNIVERSE newShipWithRole:escortRole] autorelease]) { escortRole = defaultRole; } } escortShipKey = [shipinfoDictionary oo_stringForKey:@"escort_ship" defaultValue:nil]; if (escortShipKey == nil) escortShipKey = [shipinfoDictionary oo_stringForKey:@"escort-ship"]; if (escortShipKey != nil) { if (![[UNIVERSE newShipWithName:escortShipKey] autorelease]) { escortShipKey = nil; } else { escortRole = [NSString stringWithFormat:@"[%@]",escortShipKey]; } } OOShipGroup *escortGroup = [self escortGroup]; if ([self group] == nil) { [self setGroup:escortGroup]; // should probably become a copy of the escortGroup post NMSR. } [escortGroup setLeader:self]; [self refreshEscortPositions]; uint8_t currentEscortCount = [escortGroup count] - 1; // always at least 0. while (_pendingEscortCount > 0 && ([self isThargoid] || currentEscortCount < _maxEscortCount)) { // The following line adds escort 1 in position 1, etc... up to MAX_ESCORTS. HPVector ex_pos = [self coordinatesForEscortPosition:currentEscortCount]; ShipEntity *escorter = nil; escorter = [UNIVERSE newShipWithRole:escortRole]; // retained if (escorter == nil) break; [self setUpOneEscort:escorter inGroup:escortGroup withRole:escortRole atPosition:ex_pos andCount:currentEscortCount]; [escorter release]; _pendingEscortCount--; currentEscortCount = [escortGroup count] - 1; } // done assigning escorts _pendingEscortCount = 0; } - (void) setUpMixedEscorts { NSArray *escortRoles = [shipinfoDictionary oo_arrayForKey:@"escort_roles" defaultValue:nil]; if (escortRoles == nil) { OOLogWARN(@"eship.setUp.escortShipRoles", @"Ship %@ has bad escort_roles definition.", self); return; } NSEnumerator *edefEnumerator = nil; NSDictionary *escortDefinition = nil; NSDictionary *systeminfo = nil; OOGovernmentID government; systeminfo = [UNIVERSE currentSystemData]; government = [systeminfo oo_unsignedCharForKey:KEY_GOVERNMENT]; OOShipGroup *escortGroup = [self escortGroup]; if ([self group] == nil) { [self setGroup:escortGroup]; // should probably become a copy of the escortGroup post NMSR. } [escortGroup setLeader:self]; _maxEscortCount = MAX_ESCORTS; [self refreshEscortPositions]; uint8_t currentEscortCount = [escortGroup count] - 1; // always at least 0 _maxEscortCount = 0; int8_t i = 0; for (edefEnumerator = [escortRoles objectEnumerator]; (escortDefinition = [edefEnumerator nextObject]); ) { if (currentEscortCount >= MAX_ESCORTS) { break; } // int rather than uint because, at least for min, there is a // use to giving a negative value int8_t min = [escortDefinition oo_intForKey:@"min" defaultValue:0]; int8_t max = [escortDefinition oo_intForKey:@"max" defaultValue:2]; NSString *escortRole = [escortDefinition oo_stringForKey:@"role" defaultValue:@"escort"]; int8_t desired = max; if (min < desired) { for (i = min ; i < max ; i++) { if (Ranrot()%11 < government+2) { desired--; } } } for (i = 0; i < desired; i++) { if (currentEscortCount >= MAX_ESCORTS) { break; } if (![escortRole isEqualToString:@""]) { HPVector ex_pos = [self coordinatesForEscortPosition:currentEscortCount]; ShipEntity *escorter = [UNIVERSE newShipWithRole:escortRole]; // retained if (escorter == nil) { break; } [self setUpOneEscort:escorter inGroup:escortGroup withRole:escortRole atPosition:ex_pos andCount:currentEscortCount]; [escorter release]; } currentEscortCount++; _maxEscortCount++; } } // done assigning escorts _pendingEscortCount = 0; } - (void) setUpOneEscort:(ShipEntity *)escorter inGroup:(OOShipGroup *)escortGroup withRole:(NSString *)escortRole atPosition:(HPVector)ex_pos andCount:(uint8_t)currentEscortCount { NSString *autoAI = nil; NSString *pilotRole = nil; NSDictionary *autoAIMap = nil; NSDictionary *escortShipDict = nil; AI *escortAI = nil; NSString *defaultRole = @"escort"; if ([self isPolice]) { defaultRole = @"wingman"; pilotRole = @"police"; // police are always insured. } else { pilotRole = bounty ? @"pirate" : @"hunter"; // hunters have insurancies, pirates not. } double dd = escorter->collision_radius; if (EXPECT(currentEscortCount < (uint8_t)MAX_ESCORTS)) { // spread them around a little randomly ex_pos.x += dd * 6.0 * (randf() - 0.5); ex_pos.y += dd * 6.0 * (randf() - 0.5); ex_pos.z += dd * 6.0 * (randf() - 0.5); } else { // Thargoid armada(!) Add more distance between the 'escorts'. ex_pos.x += dd * 12.0 * (randf() - 0.5); ex_pos.y += dd * 12.0 * (randf() - 0.5); ex_pos.z += dd * 12.0 * (randf() - 0.5); } [escorter setPosition:ex_pos]; // minimise lollipop flash if ([escorter crew] == nil) { [escorter setSingleCrewWithRole:pilotRole]; } [escorter setPrimaryRole:defaultRole]; //for mothership // in case this hasn't yet been set, make sure escorts get a real scan class // shouldn't happen very often, but is possible if (scanClass == CLASS_NOT_SET) { scanClass = CLASS_NEUTRAL; } [escorter setScanClass:scanClass]; // you are the same as I if ([self bounty] == 0) [escorter setBounty:0 withReason:kOOLegalStatusReasonSetup]; // Avoid dirty escorts for clean mothers // find the right autoAI. escortShipDict = [escorter shipInfoDictionary]; autoAIMap = [ResourceManager dictionaryFromFilesNamed:@"autoAImap.plist" inFolder:@"Config" andMerge:YES]; autoAI = [autoAIMap oo_stringForKey:defaultRole]; if (autoAI==nil) // no 'wingman' defined in autoAImap? { autoAI = [autoAIMap oo_stringForKey:@"escort" defaultValue:@"nullAI.plist"]; } escortAI = [escorter getAI]; // Let the populator decide which AI to use, unless we have a working alternative AI & we specify auto_ai = NO ! if ( (escortRole && [escortShipDict oo_fuzzyBooleanForKey:@"auto_ai" defaultValue:YES]) || ([[escortAI name] isEqualToString: @"nullAI.plist"] && ![autoAI isEqualToString:@"nullAI.plist"]) ) { [escorter switchAITo:autoAI]; } [escorter setGroup:escortGroup]; [escorter setOwner:self]; // mark self as group leader if ([self status] == STATUS_DOCKED) { [[self owner] addShipToLaunchQueue:escorter withPriority:NO]; } else { [UNIVERSE addEntity:escorter]; // STATUS_IN_FLIGHT, AI state GLOBAL [escortAI setState:@"FLYING_ESCORT"]; // Begin escort flight. (If the AI doesn't define FLYING_ESCORT, this has no effect.) [escorter doScriptEvent:OOJSID("spawnedAsEscort") withArgument:self]; } if([escorter heatInsulation] < [self heatInsulation]) [escorter setHeatInsulation:[self heatInsulation]]; // give escorts same protection as mother. if(([escorter maxFlightSpeed] < cruiseSpeed) && ([escorter maxFlightSpeed] > cruiseSpeed * 0.3)) cruiseSpeed = [escorter maxFlightSpeed] * 0.99; // adapt patrolSpeed to the slowest escort but ignore the very slow ones. if (bounty) { int extra = 1 | (ranrot_rand() & 15); // if mothership is offender, make sure escorter is too. [escorter markAsOffender:extra withReason:kOOLegalStatusReasonSetup]; } else { // otherwise force the escort to be clean [escorter setBounty:0 withReason:kOOLegalStatusReasonSetup]; } } - (NSString *)shipDataKey { return _shipKey; } - (NSString *)shipDataKeyAutoRole { return [[[NSString alloc] initWithFormat:@"[%@]",[self shipDataKey]] autorelease]; } - (void)setShipDataKey:(NSString *)key { DESTROY(_shipKey); _shipKey = [key copy]; } - (NSDictionary *)shipInfoDictionary { return shipinfoDictionary; } - (void) setDefaultWeaponOffsets { forwardWeaponOffset = kZeroVector; aftWeaponOffset = kZeroVector; portWeaponOffset = kZeroVector; starboardWeaponOffset = kZeroVector; } - (Vector) aftWeaponOffset { return aftWeaponOffset; } - (Vector) forwardWeaponOffset { return forwardWeaponOffset; } - (Vector) portWeaponOffset { return portWeaponOffset; } - (Vector) starboardWeaponOffset { return starboardWeaponOffset; } - (BOOL)isFrangible { return isFrangible; } - (BOOL) suppressFlightNotifications { return suppressAegisMessages; } - (OOScanClass) scanClass { if (cloaking_device_active) return CLASS_NO_DRAW; return scanClass; } ////////////////////////////////////////////// - (BOOL) canCollide { int status = [self status]; if (status == STATUS_COCKPIT_DISPLAY || status == STATUS_DEAD || status == STATUS_BEING_SCOOPED) { return NO; } if (isWreckage) { // wreckage won't collide return NO; } if (isMissile && [self shotTime] < 0.25) // not yet fused { return NO; } return YES; } ShipEntity* doOctreesCollide(ShipEntity* prime, ShipEntity* other) { // octree check Octree *prime_octree = prime->octree; Octree *other_octree = other->octree; HPVector prime_position = [prime absolutePositionForSubentity]; Triangle prime_ijk = [prime absoluteIJKForSubentity]; HPVector other_position = [other absolutePositionForSubentity]; Triangle other_ijk = [other absoluteIJKForSubentity]; Vector relative_position_of_other = resolveVectorInIJK(HPVectorToVector(HPvector_between(prime_position, other_position)), prime_ijk); Triangle relative_ijk_of_other; relative_ijk_of_other.v[0] = resolveVectorInIJK(other_ijk.v[0], prime_ijk); relative_ijk_of_other.v[1] = resolveVectorInIJK(other_ijk.v[1], prime_ijk); relative_ijk_of_other.v[2] = resolveVectorInIJK(other_ijk.v[2], prime_ijk); // check hull octree against other hull octree if ([prime_octree isHitByOctree:other_octree withOrigin:relative_position_of_other andIJK:relative_ijk_of_other]) { return other; } // check prime subentities against the other's hull NSArray *prime_subs = prime->subEntities; if (prime_subs) { NSUInteger i, n_subs = [prime_subs count]; for (i = 0; i < n_subs; i++) { Entity* se = [prime_subs objectAtIndex:i]; if ([se isShip] && [se canCollide] && doOctreesCollide((ShipEntity*)se, other)) return other; } } // check prime hull against the other's subentities NSArray *other_subs = other->subEntities; if (other_subs) { NSUInteger i, n_subs = [other_subs count]; for (i = 0; i < n_subs; i++) { Entity* se = [other_subs objectAtIndex:i]; if ([se isShip] && [se canCollide] && doOctreesCollide(prime, (ShipEntity*)se)) return (ShipEntity*)se; } } // check prime subenties against the other's subentities if ((prime_subs)&&(other_subs)) { NSUInteger i, n_osubs = [other_subs count]; for (i = 0; i < n_osubs; i++) { Entity* oe = [other_subs objectAtIndex:i]; if ([oe isShip] && [oe canCollide]) { NSUInteger j, n_psubs = [prime_subs count]; for (j = 0; j < n_psubs; j++) { Entity* pe = [prime_subs objectAtIndex:j]; if ([pe isShip] && [pe canCollide] && doOctreesCollide((ShipEntity*)pe, (ShipEntity*)oe)) return (ShipEntity*)oe; } } } } // fall through => no collision return nil; } - (BOOL) checkCloseCollisionWith:(Entity *)other { if (other == nil) return NO; if ([collidingEntities containsObject:other]) return NO; // we know about this already! ShipEntity *otherShip = nil; if ([other isShip]) otherShip = (ShipEntity *)other; if ([self canScoop:otherShip]) return YES; // quick test - could this improve scooping for small ships? I think so! if (otherShip != nil && trackCloseContacts) { // in update we check if close contacts have gone out of touch range (origin within our collision_radius) // here we check if something has come within that range HPVector otherPos = [otherShip position]; OOUniversalID otherID = [otherShip universalID]; NSString *other_key = [NSString stringWithFormat:@"%d", otherID]; if (![closeContactsInfo objectForKey:other_key] && HPdistance2(position, otherPos) < collision_radius * collision_radius) { // calculate position with respect to our own position and orientation Vector dpos = HPVectorToVector(HPvector_between(position, otherPos)); Vector rpos = make_vector(dot_product(dpos, v_right), dot_product(dpos, v_up), dot_product(dpos, v_forward)); [closeContactsInfo setObject:[NSString stringWithFormat:@"%f %f %f", rpos.x, rpos.y, rpos.z] forKey: other_key]; // send AI a message about the touch OOWeakReference *temp = _primaryTarget; _primaryTarget = [otherShip weakRetain]; [self doScriptEvent:OOJSID("shipCloseContact") withArgument:otherShip andReactToAIMessage:@"CLOSE CONTACT"]; _primaryTarget = temp; } } /* This does not appear to save a significant amount of time in * most situations. No significant change in frame rate with a * 350-segment planetary ring at 1400 collision candidates, even * on old hardware. There are perhaps situations in which it could * be a significant optimisation, but those are likely to also be * the situations where the effect of adding hundreds of extra * false-positive collisions leaves the player returning to a * mess... So, commented out: CIM 21 Jan 2014 if (zero_distance > CLOSE_COLLISION_CHECK_MAX_RANGE2) // don't work too hard on entities that are far from the player return YES; */ if (otherShip != nil) { // check hull octree versus other hull octree collider = doOctreesCollide(self, otherShip); return (collider != nil); } // default at this stage is to say YES they've collided! collider = other; return YES; } - (BoundingBox)findSubentityBoundingBox { return [[self mesh] findSubentityBoundingBoxWithPosition:HPVectorToVector(position) rotMatrix:rotMatrix]; } - (Triangle) absoluteIJKForSubentity { Triangle result = {{ kBasisXVector, kBasisYVector, kBasisZVector }}; Entity *last = nil; Entity *father = self; OOMatrix r_mat; while ((father)&&(father != last) && (father != NO_TARGET)) { r_mat = [father drawRotationMatrix]; result.v[0] = OOVectorMultiplyMatrix(result.v[0], r_mat); result.v[1] = OOVectorMultiplyMatrix(result.v[1], r_mat); result.v[2] = OOVectorMultiplyMatrix(result.v[2], r_mat); last = father; if (![last isSubEntity]) break; father = [father owner]; } return result; } - (void) addSubentityToCollisionRadius:(Entity *)subent { if (!subent) return; double distance = HPmagnitude([subent position]) + [subent findCollisionRadius]; if ([subent isKindOfClass:[ShipEntity class]]) // Solid subentity { if (distance > collision_radius) { collision_radius = distance; } mass += [subent mass]; } if (distance > _profileRadius) { _profileRadius = distance; } } - (ShipEntity *) launchPodWithCrew:(NSArray *)podCrew { ShipEntity *pod = nil; pod = [UNIVERSE newShipWithRole:[shipinfoDictionary oo_stringForKey:@"escape_pod_role"]]; // or nil if (!pod) { // _role not defined? it might have _model defined; pod = [UNIVERSE newShipWithRole:[shipinfoDictionary oo_stringForKey:@"escape_pod_model" defaultValue:@"escape-capsule"]]; if (!pod) { pod = [UNIVERSE newShipWithRole:@"escape-capsule"]; OOLog(@"shipEntity.noEscapePod", @"Ship %@ has no correct escape_pod_role defined. Now using default capsule.", self); } } if (pod) { [pod setOwner:self]; [pod setTemperature:[self randomEjectaTemperatureWithMaxFactor:0.9]]; [pod setCommodity:@"slaves" andAmount:1]; [pod setCrew:podCrew]; [pod switchAITo:@"oolite-shuttleAI.js"]; [self dumpItem:pod]; // CLASS_CARGO, STATUS_IN_FLIGHT, AI state GLOBAL [pod release]; //release } return pod; } - (BOOL) validForAddToUniverse { if (shipinfoDictionary == nil) { OOLog(@"shipEntity.notDict", @"Ship %@ was not set up from dictionary.", self); return NO; } return [super validForAddToUniverse]; } - (void) update:(OOTimeDelta)delta_t { if (shipinfoDictionary == nil) { OOLog(@"shipEntity.notDict", @"Ship %@ was not set up from dictionary.", self); [UNIVERSE removeEntity:self]; return; } if (!isfinite(maxFlightSpeed)) { OOLog(@"ship.sanityCheck.failed", @"Ship %@ %@ infinite top speed, clamped to 300.", self, @"had"); maxFlightSpeed = 300; } bool isSubEnt = [self isSubEntity]; if (!isSubEnt) { if (scanClass == CLASS_NOT_SET) { scanClass = CLASS_NEUTRAL; OOLog(@"ship.sanityCheck.failed", @"Ship %@ %@ with scanClass CLASS_NOT_SET; forced to CLASS_NEUTRAL.", self, [self primaryRole]); } [self updateTrackingCurve]; // // deal with collisions // [self manageCollisions]; // subentity collisions managed via parent entity // // reset any inadvertant legal mishaps // if (scanClass == CLASS_POLICE) { if (bounty > 0) { [self setBounty:0 withReason:kOOLegalStatusReasonPoliceAreClean]; } ShipEntity* target = [self primaryTarget]; if ((target)&&([target scanClass] == CLASS_POLICE)) { [self noteLostTarget]; } } if (trackCloseContacts) { // in checkCloseCollisionWith: we check if some thing has come within touch range (origin within our collision_radius) // here we check if it has gone outside that range NSString *other_key = nil; // create a temp copy to iterate over, since we may want to // change the original NSDictionary *closeContactsTemp = [[NSDictionary alloc] initWithDictionary:closeContactsInfo]; foreachkey (other_key, closeContactsTemp) { ShipEntity* other = [UNIVERSE entityForUniversalID:[other_key intValue]]; if ((other != nil) && (other->isShip)) { if (HPdistance2(position, other->position) > collision_radius * collision_radius) // moved beyond our sphere! { // calculate position with respect to our own position and orientation Vector dpos = HPVectorToVector(HPvector_between(position, other->position)); Vector pos1 = make_vector(dot_product(dpos, v_right), dot_product(dpos, v_up), dot_product(dpos, v_forward)); Vector pos0 = {0, 0, 0}; ScanVectorFromString([closeContactsInfo objectForKey: other_key], &pos0); // send AI messages about the contact OOWeakReference *temp = _primaryTarget; _primaryTarget = [other weakRetain]; if ((pos0.x < 0.0)&&(pos1.x > 0.0)) { [self doScriptEvent:OOJSID("shipTraversePositiveX") withArgument:other andReactToAIMessage:@"POSITIVE X TRAVERSE"]; } if ((pos0.x > 0.0)&&(pos1.x < 0.0)) { [self doScriptEvent:OOJSID("shipTraverseNegativeX") withArgument:other andReactToAIMessage:@"NEGATIVE X TRAVERSE"]; } if ((pos0.y < 0.0)&&(pos1.y > 0.0)) { [self doScriptEvent:OOJSID("shipTraversePositiveY") withArgument:other andReactToAIMessage:@"POSITIVE Y TRAVERSE"]; } if ((pos0.y > 0.0)&&(pos1.y < 0.0)) { [self doScriptEvent:OOJSID("shipTraverseNegativeY") withArgument:other andReactToAIMessage:@"NEGATIVE Y TRAVERSE"]; } if ((pos0.z < 0.0)&&(pos1.z > 0.0)) { [self doScriptEvent:OOJSID("shipTraversePositiveZ") withArgument:other andReactToAIMessage:@"POSITIVE Z TRAVERSE"]; } if ((pos0.z > 0.0)&&(pos1.z < 0.0)) { [self doScriptEvent:OOJSID("shipTraverseNegativeZ") withArgument:other andReactToAIMessage:@"NEGATIVE Z TRAVERSE"]; } _primaryTarget = temp; [closeContactsInfo removeObjectForKey: other_key]; } } else { [closeContactsInfo removeObjectForKey: other_key]; } } [closeContactsTemp release]; } // end if trackCloseContacts } // end if !isSubEntity #ifndef NDEBUG // DEBUGGING if (reportAIMessages && (debugLastBehaviour != behaviour)) { OOLog(kOOLogEntityBehaviourChanged, @"%@ behaviour is now %@", self, OOStringFromBehaviour(behaviour)); debugLastBehaviour = behaviour; } #endif // cool all weapons. weapon_temp = fmaxf(weapon_temp - (float)(WEAPON_COOLING_FACTOR * delta_t), 0.0f); forward_weapon_temp = fmaxf(forward_weapon_temp - (float)(WEAPON_COOLING_FACTOR * delta_t), 0.0f); aft_weapon_temp = fmaxf(aft_weapon_temp - (float)(WEAPON_COOLING_FACTOR * delta_t), 0.0f); port_weapon_temp = fmaxf(port_weapon_temp - (float)(WEAPON_COOLING_FACTOR * delta_t), 0.0f); starboard_weapon_temp = fmaxf(starboard_weapon_temp - (float)(WEAPON_COOLING_FACTOR * delta_t), 0.0f); // update time between shots shot_time += delta_t; // handle radio message effects if (messageTime > 0.0) { messageTime -= delta_t; if (messageTime < 0.0) messageTime = 0.0; } // temperature factors if(!isSubEnt) { double external_temp = 0.0; OOSunEntity *sun = [UNIVERSE sun]; if (sun != nil) { // set the ambient temperature here double sun_zd = HPdistance2(position, [sun position]); // square of distance double sun_cr = sun->collision_radius; double alt1 = sun_cr * sun_cr / sun_zd; external_temp = SUN_TEMPERATURE * alt1; if ([sun goneNova]) external_temp *= 100; if ([self hasFuelScoop] && alt1 > 0.75 && [self fuel] < [self fuelCapacity]) { fuel_accumulator += (float)(delta_t * flightSpeed * 0.010 / [self fuelChargeRate]); // are we fast enough to collect any fuel? while (fuel_accumulator > 1.0f) { [self setFuel:[self fuel] + 1]; fuel_accumulator -= 1.0f; [self doScriptEvent:OOJSID("shipScoopedFuel")]; } } } // work on the ship temperature // float heatThreshold = [self heatInsulation] * 100.0f; if (external_temp > heatThreshold && external_temp > ship_temperature) ship_temperature += (external_temp - ship_temperature) * delta_t * SHIP_INSULATION_FACTOR / [self heatInsulation]; else { if (ship_temperature > SHIP_MIN_CABIN_TEMP) { ship_temperature += (external_temp - heatThreshold - ship_temperature) * delta_t * SHIP_COOLING_FACTOR / [self heatInsulation]; if (ship_temperature < SHIP_MIN_CABIN_TEMP) ship_temperature = SHIP_MIN_CABIN_TEMP; } } } else //subents { ship_temperature = [[self owner] temperature]; } if (ship_temperature > SHIP_MAX_CABIN_TEMP) [self takeHeatDamage: delta_t * ship_temperature]; // are we burning due to low energy if ((energy < maxEnergy * 0.20)&&_showDamage) // prevents asteroid etc. from burning throw_sparks = YES; // burning effects if (throw_sparks) { next_spark_time -= delta_t; if (next_spark_time < 0.0) { [self throwSparks]; throw_sparks = NO; // until triggered again } } if (!isSubEnt) { // cloaking device if ([self hasCloakingDevice]) { if (cloaking_device_active) { energy -= delta_t * CLOAKING_DEVICE_ENERGY_RATE; if (energy < CLOAKING_DEVICE_MIN_ENERGY) { [self deactivateCloakingDevice]; if (energy < 0) energy = 0; } } } // military_jammer if ([self hasMilitaryJammer]) { if (military_jammer_active) { energy -= delta_t * MILITARY_JAMMER_ENERGY_RATE; if (energy < MILITARY_JAMMER_MIN_ENERGY) { military_jammer_active = NO; if (energy < 0) energy = 0; } } else { if (energy > 1.5 * MILITARY_JAMMER_MIN_ENERGY) military_jammer_active = YES; } } // check outside factors /* aegis checks are expensive, so only do them once every km or so of flight * unlikely to be important otherwise. (every 100m if already close to * planet, to watch for surface) * if have non-zero inertial velocity, need to check every frame, * as distanceTravelled does not include this component - CIM */ if (_nextAegisCheck < distanceTravelled || !vector_equal([super velocity],kZeroVector)) { aegis_status = [self checkForAegis]; // is a station or something nearby?? if (aegis_status == AEGIS_NONE) { // in open space: check every km _nextAegisCheck = distanceTravelled + 1000.0; } else { // near planets: check every 100m _nextAegisCheck = distanceTravelled + 100.0; } } } // end if !isSubEntity // scripting if (!haveExecutedSpawnAction) { // When crashing into a boulder, STATUS_LAUNCHING is sometimes skipped on scooping the resulting splinters. OOEntityStatus status = [self status]; if (script != nil && (status == STATUS_IN_FLIGHT || status == STATUS_LAUNCHING || status == STATUS_BEING_SCOOPED || (status == STATUS_ACTIVE && self == [UNIVERSE station]) )) { [PLAYER setScriptTarget:self]; [self doScriptEvent:OOJSID("shipSpawned")]; if ([self status] != STATUS_DEAD) [PLAYER doScriptEvent:OOJSID("shipSpawned") withArgument:self]; } haveExecutedSpawnAction = YES; } /* No point in starting the AI if still launching */ if (!haveStartedJSAI && [self status] != STATUS_LAUNCHING) { haveStartedJSAI = YES; [self doScriptEvent:OOJSID("aiStarted")]; } // behaviours according to status and behaviour // if ([self status] == STATUS_LAUNCHING) { if ([UNIVERSE getTime] > launch_time + launch_delay) // move for while before thinking { StationEntity *stationLaunchedFrom = [UNIVERSE nearestEntityMatchingPredicate:IsStationPredicate parameter:NULL relativeToEntity:self]; [self setStatus:STATUS_IN_FLIGHT]; // awaken JS-based AIs haveStartedJSAI = YES; [self doScriptEvent:OOJSID("aiStarted")]; [self doScriptEvent:OOJSID("shipLaunchedFromStation") withArgument:stationLaunchedFrom]; [shipAI reactToMessage:@"LAUNCHED OKAY" context:@"launched"]; } else { // ignore behaviour just keep moving... flightYaw = 0.0; [self applyAttitudeChanges:delta_t]; [self applyThrust:delta_t]; if (energy < maxEnergy) { energy += energy_recharge_rate * delta_t; if (energy > maxEnergy) { energy = maxEnergy; [self doScriptEvent:OOJSID("shipEnergyBecameFull")]; [shipAI message:@"ENERGY_FULL"]; } } if ([self subEntityCount] > 0) { // only copy the subent array if there are subentities ShipEntity *se = nil; foreach (se, [self subEntities]) { [se update:delta_t]; } } // super update [super update:delta_t]; return; } } // // double check scooped behaviour // if ([self status] == STATUS_BEING_SCOOPED) { //if we are being tractored, but we have no owner, then we have a problem if (behaviour != BEHAVIOUR_TRACTORED || [self owner] == nil || [self owner] == self || [self owner] == NO_TARGET) { // escaped tractor beam [self setStatus:STATUS_IN_FLIGHT]; // should correct 'uncollidable objects' bug behaviour = BEHAVIOUR_IDLE; frustration = 0.0; [self setOwner:self]; [shipAI exitStateMachineWithMessage:nil]; // Escapepods and others should continue their old AI here. } } if ([self status] == STATUS_COCKPIT_DISPLAY) { flightYaw = 0.0; [self applyAttitudeChanges:delta_t]; GLfloat range2 = 0.1 * HPdistance2(position, _destination) / (collision_radius * collision_radius); if ((range2 > 1.0)||(velocity.z > 0.0)) range2 = 1.0; position = HPvector_add(position, vectorToHPVector(vector_multiply_scalar(velocity, range2 * delta_t))); } else { [self processBehaviour:delta_t]; // manage energy if (energy < maxEnergy) { energy += energy_recharge_rate * delta_t; if (energy > maxEnergy) { energy = maxEnergy; [self doScriptEvent:OOJSID("shipEnergyBecameFull")]; [shipAI message:@"ENERGY_FULL"]; } } if (!isSubEnt) { // update destination position for escorts [self refreshEscortPositions]; if ([self hasEscorts]) { ShipEntity *escort = nil; unsigned i = 0; // Note: works on escortArray rather than escortEnumerator because escorts may be mutated. foreach(escort, [self escortArray]) { [escort setEscortDestination:[self coordinatesForEscortPosition:i++]]; } ShipEntity *leader = [[self escortGroup] leader]; if (leader != nil && ([leader scanClass] != [self scanClass])) { OOLog(@"ship.sanityCheck.failed", @"Ship %@ escorting %@ with wrong scanclass!", self, leader); [[self escortGroup] removeShip:self]; [self setEscortGroup:nil]; } } } } // subentity rotation if (isSubEnt) { if (!quaternion_equal(subentityRotationalVelocity, kIdentityQuaternion) && !quaternion_equal(subentityRotationalVelocity, kZeroQuaternion)) { Quaternion qf = subentityRotationalVelocity; qf.w *= (1.0 - delta_t); qf.x *= delta_t; qf.y *= delta_t; qf.z *= delta_t; [self setOrientation:quaternion_multiply(qf, orientation)]; } } // reset totalBoundingBox totalBoundingBox = boundingBox; // super update [super update:delta_t]; // update subentities if ([self subEntityCount] > 0) { // only copy the subent array if there are subentities ShipEntity *se = nil; foreach (se, [self subEntities]) { [se update:delta_t]; if ([se isShip]) { BoundingBox sebb = [se findSubentityBoundingBox]; bounding_box_add_vector(&totalBoundingBox, sebb.max); bounding_box_add_vector(&totalBoundingBox, sebb.min); } } } if (aiScriptWakeTime > 0 && [PLAYER clockTimeAdjusted] > aiScriptWakeTime) { aiScriptWakeTime = 0; [self doScriptEvent:OOJSID("aiAwoken")]; } } - (void) processBehaviour:(OOTimeDelta)delta_t { BOOL applyThrust = YES; switch (behaviour) { case BEHAVIOUR_TUMBLE : [self behaviour_tumble: delta_t]; break; case BEHAVIOUR_STOP_STILL : case BEHAVIOUR_STATION_KEEPING : [self behaviour_stop_still: delta_t]; break; case BEHAVIOUR_IDLE : if ([self isSubEntity]) { applyThrust = NO; } [self behaviour_idle: delta_t]; break; case BEHAVIOUR_TRACTORED : [self behaviour_tractored: delta_t]; break; case BEHAVIOUR_TRACK_TARGET : [self behaviour_track_target: delta_t]; break; case BEHAVIOUR_INTERCEPT_TARGET : case BEHAVIOUR_COLLECT_TARGET : [self behaviour_intercept_target: delta_t]; break; case BEHAVIOUR_ATTACK_TARGET : [self behaviour_attack_target: delta_t]; break; case BEHAVIOUR_ATTACK_FLY_TO_TARGET_SIX : case BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE : [self behaviour_fly_to_target_six: delta_t]; break; case BEHAVIOUR_ATTACK_MINING_TARGET : [self behaviour_attack_mining_target: delta_t]; break; case BEHAVIOUR_ATTACK_FLY_TO_TARGET : [self behaviour_attack_fly_to_target: delta_t]; break; case BEHAVIOUR_ATTACK_FLY_FROM_TARGET : [self behaviour_attack_fly_from_target: delta_t]; break; case BEHAVIOUR_ATTACK_BREAK_OFF_TARGET : [self behaviour_attack_break_off_target: delta_t]; break; case BEHAVIOUR_ATTACK_SLOW_DOGFIGHT : [self behaviour_attack_slow_dogfight: delta_t]; break; case BEHAVIOUR_RUNNING_DEFENSE : [self behaviour_running_defense: delta_t]; break; case BEHAVIOUR_ATTACK_BROADSIDE : [self behaviour_attack_broadside: delta_t]; break; case BEHAVIOUR_ATTACK_BROADSIDE_LEFT : [self behaviour_attack_broadside_left: delta_t]; break; case BEHAVIOUR_ATTACK_BROADSIDE_RIGHT : [self behaviour_attack_broadside_right: delta_t]; break; case BEHAVIOUR_CLOSE_TO_BROADSIDE_RANGE : [self behaviour_close_to_broadside_range: delta_t]; break; case BEHAVIOUR_CLOSE_WITH_TARGET : [self behaviour_close_with_target: delta_t]; break; case BEHAVIOUR_ATTACK_SNIPER : [self behaviour_attack_sniper: delta_t]; break; case BEHAVIOUR_EVASIVE_ACTION : case BEHAVIOUR_FLEE_EVASIVE_ACTION : [self behaviour_evasive_action: delta_t]; break; case BEHAVIOUR_FLEE_TARGET : [self behaviour_flee_target: delta_t]; break; case BEHAVIOUR_FLY_RANGE_FROM_DESTINATION : [self behaviour_fly_range_from_destination: delta_t]; break; case BEHAVIOUR_FACE_DESTINATION : [self behaviour_face_destination: delta_t]; break; case BEHAVIOUR_LAND_ON_PLANET : [self behaviour_land_on_planet: delta_t]; break; case BEHAVIOUR_FORMATION_FORM_UP : [self behaviour_formation_form_up: delta_t]; break; case BEHAVIOUR_FLY_TO_DESTINATION : [self behaviour_fly_to_destination: delta_t]; break; case BEHAVIOUR_FLY_FROM_DESTINATION : case BEHAVIOUR_FORMATION_BREAK : [self behaviour_fly_from_destination: delta_t]; break; case BEHAVIOUR_AVOID_COLLISION : [self behaviour_avoid_collision: delta_t]; break; case BEHAVIOUR_TRACK_AS_TURRET : applyThrust = NO; [self behaviour_track_as_turret: delta_t]; break; case BEHAVIOUR_FLY_THRU_NAVPOINTS : [self behaviour_fly_thru_navpoints: delta_t]; break; case BEHAVIOUR_SCRIPTED_AI: case BEHAVIOUR_SCRIPTED_ATTACK_AI: [self behaviour_scripted_ai: delta_t]; break; case BEHAVIOUR_ENERGY_BOMB_COUNTDOWN: applyThrust = NO; // Do nothing break; } // generally the checks above should be turning this *off* for subents if (applyThrust) { [self applyAttitudeChanges:delta_t]; [self applyThrust:delta_t]; } } // called when behaviour is unable to improve position - (void)noteFrustration:(NSString *)context { [shipAI reactToMessage:@"FRUSTRATED" context:context]; [self doScriptEvent:OOJSID("shipAIFrustrated") withArgument:context]; } - (void)respondToAttackFrom:(Entity *)from becauseOf:(Entity *)other { Entity *source = nil; if ([other isKindOfClass:[ShipEntity class]]) { source = other; // JSAIs handle friendly fire themselves if (![self hasNewAI]) { ShipEntity *hunter = (ShipEntity *)other; //if we are in the same group, then we have to be careful about how we handle things if ([self isPolice] && [hunter isPolice]) { //police never get into a fight with each other return; } OOShipGroup *group = [self group]; if (group != nil && group == [hunter group]) { //we are in the same group, do we forgive you? //criminals are less likely to forgive if (randf() < (0.8 - (bounty/100))) { //it was an honest mistake, lets get on with it return; } ShipEntity *groupLeader = [group leader]; if (hunter == groupLeader) { //oops we were attacked by our leader, desert him [group removeShip:self]; } else { //evict them from our group [group removeShip:hunter]; [groupLeader setFoundTarget:other]; [groupLeader setPrimaryAggressor:hunter]; [groupLeader respondToAttackFrom:from becauseOf:other]; } } } } else { source = from; } [self doScriptEvent:OOJSID("shipBeingAttacked") withArgument:source andReactToAIMessage:@"ATTACKED"]; if ([source isShip]) [(ShipEntity *)source doScriptEvent:OOJSID("shipAttackedOther") withArgument:self]; } // Equipment - (BOOL) hasOneEquipmentItem:(NSString *)itemKey includeWeapons:(BOOL)includeWeapons whileLoading:(BOOL)loading { if ([self hasOneEquipmentItem:itemKey includeMissiles:includeWeapons whileLoading:loading]) return YES; if (loading) { NSString *damaged = [itemKey stringByAppendingString:@"_DAMAGED"]; if ([_equipment containsObject:damaged]) return YES; } if (includeWeapons) { // Check for primary weapon OOWeaponType weaponType = OOWeaponTypeFromEquipmentIdentifierStrict(itemKey); if (!isWeaponNone(weaponType)) { if ([self hasPrimaryWeapon:weaponType]) return YES; } } return NO; } - (BOOL) hasOneEquipmentItem:(NSString *)itemKey includeMissiles:(BOOL)includeMissiles whileLoading:(BOOL)loading { if ([_equipment containsObject:itemKey]) return YES; if (loading) { NSString *damaged = [itemKey stringByAppendingString:@"_DAMAGED"]; if ([_equipment containsObject:damaged]) return YES; } if (includeMissiles && missiles > 0) { unsigned i; if ([itemKey isEqualToString:@"thargon"]) itemKey = @"EQ_THARGON"; for (i = 0; i < missiles; i++) { if ([[missile_list[i] identifier] isEqualTo:itemKey]) return YES; } } return NO; } - (BOOL) hasPrimaryWeapon:(OOWeaponType)weaponType { NSEnumerator *subEntEnum = nil; ShipEntity *subEntity = nil; if ([[forward_weapon_type identifier] isEqualToString:[weaponType identifier]] || [[aft_weapon_type identifier] isEqualToString:[weaponType identifier]] || [[port_weapon_type identifier] isEqualToString:[weaponType identifier]] || [[starboard_weapon_type identifier] isEqualToString:[weaponType identifier]]) { return YES; } for (subEntEnum = [self shipSubEntityEnumerator]; (subEntity = [subEntEnum nextObject]); ) { if ([subEntity hasPrimaryWeapon:weaponType]) return YES; } return NO; } - (NSUInteger) countEquipmentItem:(NSString *)eqkey { NSString *eq = nil; NSUInteger count = 0; foreach (eq, _equipment) { if ([eqkey isEqualToString:eq]) { ++count; } } return count; } - (BOOL) hasEquipmentItem:(id)equipmentKeys includeWeapons:(BOOL)includeWeapons whileLoading:(BOOL)loading { // this method is also used internally to find out if an equipped item is undamaged. if ([equipmentKeys isKindOfClass:[NSString class]]) { return [self hasOneEquipmentItem:equipmentKeys includeWeapons:includeWeapons whileLoading:loading]; } else { NSParameterAssert([equipmentKeys isKindOfClass:[NSArray class]] || [equipmentKeys isKindOfClass:[NSSet class]]); id key = nil; foreach (key, equipmentKeys) { if ([self hasOneEquipmentItem:key includeWeapons:includeWeapons whileLoading:loading]) return YES; } } return NO; } - (BOOL) hasEquipmentItem:(id)equipmentKeys { return [self hasEquipmentItem:equipmentKeys includeWeapons:NO whileLoading:NO]; } /* allows OXP equipment to provide core functions (or indeed OXP * functions, potentially) */ - (BOOL) hasEquipmentItemProviding:(NSString *)equipmentType { NSString *key = nil; foreach (key, _equipment) { if ([key isEqualToString:equipmentType]) { // equipment always provides itself return YES; } else { OOEquipmentType *et = [OOEquipmentType equipmentTypeWithIdentifier:key]; if (et != nil && [et provides:equipmentType]) { return YES; } } } return NO; } - (NSString *) equipmentItemProviding:(NSString *)equipmentType { NSString *key = nil; foreach (key, _equipment) { if ([key isEqualToString:equipmentType]) { // equipment always provides itself return [[key copy] autorelease]; } else { OOEquipmentType *et = [OOEquipmentType equipmentTypeWithIdentifier:key]; if (et != nil && [et provides:equipmentType]) { return [[key copy] autorelease]; } } } return nil; } - (BOOL) hasAllEquipment:(id)equipmentKeys includeWeapons:(BOOL)includeWeapons whileLoading:(BOOL)loading { NSEnumerator *keyEnum = nil; id key = nil; if (_equipment == nil) return NO; // Make sure it's an array or set, using a single-object set if it's a string. if ([equipmentKeys isKindOfClass:[NSString class]]) equipmentKeys = [NSArray arrayWithObject:equipmentKeys]; else if (![equipmentKeys isKindOfClass:[NSArray class]] && ![equipmentKeys isKindOfClass:[NSSet class]]) return NO; for (keyEnum = [equipmentKeys objectEnumerator]; (key = [keyEnum nextObject]); ) { if (![self hasOneEquipmentItem:key includeWeapons:includeWeapons whileLoading:loading]) return NO; } return YES; } - (BOOL) hasAllEquipment:(id)equipmentKeys { return [self hasAllEquipment:equipmentKeys includeWeapons:NO whileLoading:NO]; } - (BOOL) hasHyperspaceMotor { return hyperspaceMotorSpinTime >= 0; } - (float) hyperspaceSpinTime { return hyperspaceMotorSpinTime; } - (void) setHyperspaceSpinTime:(float)new { hyperspaceMotorSpinTime = new; } - (BOOL) canAddEquipment:(NSString *)equipmentKey inContext:(NSString *)context { if ([equipmentKey hasSuffix:@"_DAMAGED"]) { equipmentKey = [equipmentKey substringToIndex:[equipmentKey length] - [@"_DAMAGED" length]]; } NSString * lcEquipmentKey = [equipmentKey lowercaseString]; if ([equipmentKey hasSuffix:@"MISSILE"]||[equipmentKey hasSuffix:@"MINE"]||([self isThargoid] && ([lcEquipmentKey hasPrefix:@"thargon"] || [lcEquipmentKey hasSuffix:@"thargon"]))) { if (missiles >= max_missiles) return NO; } OOEquipmentType *eqType = [OOEquipmentType equipmentTypeWithIdentifier:equipmentKey]; if (![eqType canCarryMultiple] && [self hasEquipmentItem:equipmentKey]) return NO; if (![self equipmentValidToAdd:equipmentKey inContext:context]) return NO; return YES; } - (OOWeaponFacingSet) weaponFacings { return weapon_facings; } - (OOWeaponType) weaponTypeIDForFacing:(OOWeaponFacing)facing strict:(BOOL)strict { OOWeaponType weaponType = nil; if (facing & weapon_facings) { switch (facing) { case WEAPON_FACING_FORWARD: weaponType = forward_weapon_type; // if no forward weapon, and not carrying out a strict check, see if subentities have forward weapons, return the first one found. if (isWeaponNone(weaponType) && !strict) { NSEnumerator *subEntEnum = [self shipSubEntityEnumerator]; ShipEntity *subEntity = nil; while (isWeaponNone(weaponType) && (subEntity = [subEntEnum nextObject])) { weaponType = subEntity->forward_weapon_type; } } break; case WEAPON_FACING_AFT: weaponType = aft_weapon_type; break; case WEAPON_FACING_PORT: weaponType = port_weapon_type; break; case WEAPON_FACING_STARBOARD: weaponType = starboard_weapon_type; break; case WEAPON_FACING_NONE: break; } } return weaponType; } - (OOEquipmentType *) weaponTypeForFacing:(OOWeaponFacing)facing strict:(BOOL)strict { // OOWeaponType weaponType = [self weaponTypeIDForFacing:facing strict:strict]; // return [OOEquipmentType equipmentTypeWithIdentifier:OOEquipmentIdentifierFromWeaponType(weaponType)]; return [self weaponTypeIDForFacing:facing strict:strict]; } - (NSArray *) missilesList { return [NSArray arrayWithObjects:missile_list count:missiles]; } - (NSArray *) passengerListForScripting { return [NSArray array]; } - (NSArray *) parcelListForScripting { return [NSArray array]; } - (NSArray *) contractListForScripting { return [NSArray array]; } - (OOEquipmentType *) generateMissileEquipmentTypeFrom:(NSString *)role { /* The generated missile equipment type provides for backward compatibility with pre-1.74 OXPs missile_roles and follows this template: //NPC equipment, incompatible with player ship. Not buyable because of its TL. ( 100, 100000, "Missile", "EQ_X_MISSILE", "Unidentified missile type.", { is_external_store = true; } ) */ NSArray *itemInfo = [NSArray arrayWithObjects:@"100", @"100000", @"Missile", role, @"Unidentified missile type.", [NSDictionary dictionaryWithObjectsAndKeys: @"true", @"is_external_store", nil], nil]; [OOEquipmentType addEquipmentWithInfo:itemInfo]; return [OOEquipmentType equipmentTypeWithIdentifier:role]; } - (NSArray *) equipmentListForScripting { NSArray *eqTypes = [OOEquipmentType allEquipmentTypes]; NSMutableArray *quip = [NSMutableArray arrayWithCapacity:[eqTypes count]]; NSEnumerator *eqTypeEnum = nil; OOEquipmentType *eqType = nil; BOOL isDamaged; for (eqTypeEnum = [eqTypes objectEnumerator]; (eqType = [eqTypeEnum nextObject]); ) { // Equipment list, consistent with the rest of the API - Kaks if ([eqType canCarryMultiple]) { NSString *damagedIdentifier = [[eqType identifier] stringByAppendingString:@"_DAMAGED"]; NSUInteger i, count = 0; count += [self countEquipmentItem:[eqType identifier]]; count += [self countEquipmentItem:damagedIdentifier]; for (i=0;i 0) { eqType = [OOEquipmentType equipmentTypeWithIdentifier:@"EQ_PASSENGER_BERTH"]; //[quip addObject:[self eqDictionaryWithType:eqType isDamaged:NO]]; [quip addObject:eqType]; } return [[quip copy] autorelease]; } - (BOOL) equipmentValidToAdd:(NSString *)equipmentKey inContext:(NSString *)context { return [self equipmentValidToAdd:equipmentKey whileLoading:NO inContext:context]; } - (BOOL) equipmentValidToAdd:(NSString *)equipmentKey whileLoading:(BOOL)loading inContext:(NSString *)context { OOEquipmentType *eqType = nil; if ([equipmentKey hasSuffix:@"_DAMAGED"]) { equipmentKey = [equipmentKey substringToIndex:[equipmentKey length] - [@"_DAMAGED" length]]; } eqType = [OOEquipmentType equipmentTypeWithIdentifier:equipmentKey]; if (eqType == nil) return NO; // not all conditions make sence checking while loading a game with already purchaged equipment. // while loading, we mainly need to catch changes when the installed oxps set has changed since saving. if ([eqType requiresEmptyPylon] && [self missileCount] >= [self missileCapacity] && !loading) return NO; if ([eqType requiresMountedPylon] && [self missileCount] == 0 && !loading) return NO; if ([self availableCargoSpace] < [eqType requiredCargoSpace] && !loading) return NO; if ([eqType requiresEquipment] != nil && ![self hasAllEquipment:[eqType requiresEquipment] includeWeapons:YES whileLoading:loading]) return NO; if ([eqType requiresAnyEquipment] != nil && ![self hasEquipmentItem:[eqType requiresAnyEquipment] includeWeapons:YES whileLoading:loading]) return NO; if ([eqType incompatibleEquipment] != nil && [self hasEquipmentItem:[eqType incompatibleEquipment] includeWeapons:YES whileLoading:loading]) return NO; if ([eqType requiresCleanLegalRecord] && [self legalStatus] != 0 && !loading) return NO; if ([eqType requiresNonCleanLegalRecord] && [self legalStatus] == 0 && !loading) return NO; if ([eqType requiresFreePassengerBerth] && [self passengerCount] >= [self passengerCapacity]) return NO; if ([eqType requiresFullFuel] && [self fuel] < [self fuelCapacity] && !loading) return NO; if ([eqType requiresNonFullFuel] && [self fuel] >= [self fuelCapacity] && !loading) return NO; if (!loading) { NSString *condition_script = [eqType conditionScript]; if (condition_script != nil) { OOJSScript *condScript = [UNIVERSE getConditionScript:condition_script]; if (condScript != nil) // should always be non-nil, but just in case { JSContext *JScontext = OOJSAcquireContext(); BOOL OK; JSBool allow_addition = false; jsval result; jsval args[] = { OOJSValueFromNativeObject(JScontext, equipmentKey) , OOJSValueFromNativeObject(JScontext, self) , OOJSValueFromNativeObject(JScontext, context)}; OK = [condScript callMethod:OOJSID("allowAwardEquipment") inContext:JScontext withArguments:args count:sizeof args / sizeof *args result:&result]; if (OK) OK = JS_ValueToBoolean(JScontext, result, &allow_addition); OOJSRelinquishContext(JScontext); if (OK && !allow_addition) { /* if the script exists, the function exists, the function * returns a bool, and that bool is false, block * addition. Otherwise allow it as default */ return NO; } } } } if ([self isPlayer]) { if (![eqType isAvailableToPlayer]) return NO; if (![eqType isAvailableToAll]) { // find options that agree with this ship. Only player ships have these options. OOShipRegistry *registry = [OOShipRegistry sharedRegistry]; NSDictionary *shipyardInfo = [registry shipyardInfoForKey:[self shipDataKey]]; NSMutableSet *options = [NSMutableSet setWithArray:[shipyardInfo oo_arrayForKey:KEY_OPTIONAL_EQUIPMENT]]; [options addObjectsFromArray:[[shipyardInfo oo_dictionaryForKey:KEY_STANDARD_EQUIPMENT] oo_arrayForKey:KEY_EQUIPMENT_EXTRAS]]; if (![options containsObject:equipmentKey]) return NO; } } else { if (![eqType isAvailableToNPCs]) return NO; } return YES; } - (BOOL) setWeaponMount:(OOWeaponFacing)facing toWeapon:(NSString *)eqKey { // sets WEAPON_NONE if not recognised if (weapon_facings & facing) { OOWeaponType chosen_weapon = OOWeaponTypeFromEquipmentIdentifierStrict(eqKey); switch (facing) { case WEAPON_FACING_FORWARD: forward_weapon_type = chosen_weapon; break; case WEAPON_FACING_AFT: aft_weapon_type = chosen_weapon; break; case WEAPON_FACING_PORT: port_weapon_type = chosen_weapon; break; case WEAPON_FACING_STARBOARD: starboard_weapon_type = chosen_weapon; break; case WEAPON_FACING_NONE: break; } return YES; } else { return NO; } } - (BOOL) addEquipmentItem:(NSString *)equipmentKey inContext:(NSString *)context { return [self addEquipmentItem:equipmentKey withValidation:YES inContext:context]; } - (BOOL) addEquipmentItem:(NSString *)equipmentKey withValidation:(BOOL)validateAddition inContext:(NSString *)context { OOEquipmentType *eqType = nil; NSString *lcEquipmentKey = [equipmentKey lowercaseString]; NSString *damagedKey; BOOL isEqThargon = [lcEquipmentKey hasSuffix:@"thargon"] || [lcEquipmentKey hasPrefix:@"thargon"]; BOOL isRepairedEquipment = NO; if([lcEquipmentKey isEqualToString:@"thargon"]) equipmentKey = @"EQ_THARGON"; // canAddEquipment always checks if the undamaged version is equipped. if (validateAddition == YES && ![self canAddEquipment:equipmentKey inContext:context]) return NO; if ([equipmentKey hasSuffix:@"_DAMAGED"]) { eqType = [OOEquipmentType equipmentTypeWithIdentifier:[equipmentKey substringToIndex:[equipmentKey length] - [@"_DAMAGED" length]]]; } else { eqType = [OOEquipmentType equipmentTypeWithIdentifier:equipmentKey]; // in case we have the damaged version! if (![eqType canCarryMultiple]) { damagedKey = [equipmentKey stringByAppendingString:@"_DAMAGED"]; if ([_equipment containsObject:damagedKey]) { [_equipment removeObject:damagedKey]; isRepairedEquipment = YES; } } } // does this equipment actually exist? if (eqType == nil) return NO; // special cases if ([eqType isMissileOrMine] || ([self isThargoid] && isEqThargon)) { if (missiles >= max_missiles) return NO; missile_list[missiles] = eqType; missiles++; return YES; } // don't add any thargons to non-thargoid ships. if(isEqThargon) return NO; // we can theoretically add a damaged weapon, but not a working one. if([equipmentKey hasPrefix:@"EQ_WEAPON"] && ![equipmentKey hasSuffix:@"_DAMAGED"]) { return NO; } // end special cases if (_equipment == nil) _equipment = [[NSMutableArray alloc] init]; if (![equipmentKey isEqualToString:@"EQ_PASSENGER_BERTH"] && !isRepairedEquipment) { // Add to equipment_weight with all other equipment. equipment_weight += [eqType requiredCargoSpace]; if (equipment_weight > max_cargo) { // should not even happen with old save games. Reject equipment now. equipment_weight -= [eqType requiredCargoSpace]; return NO; } } if (!isPlayer) { if ([equipmentKey isEqual:@"EQ_CARGO_BAY"]) { max_cargo += extra_cargo; } else if([equipmentKey isEqualToString:@"EQ_SHIELD_BOOSTER"]) { maxEnergy += 256.0f; } if([equipmentKey isEqualToString:@"EQ_SHIELD_ENHANCER"]) { maxEnergy += 256.0f; energy_recharge_rate *= 1.5; } } // add the equipment [_equipment addObject:equipmentKey]; [self doScriptEvent:OOJSID("equipmentAdded") withArgument:equipmentKey]; return YES; } - (NSEnumerator *) equipmentEnumerator { return [_equipment objectEnumerator]; } - (NSUInteger) equipmentCount { return [_equipment count]; } - (void) removeEquipmentItem:(NSString *)equipmentKey { NSString *equipmentTypeCheckKey = equipmentKey; NSString *lcEquipmentKey = [equipmentKey lowercaseString]; NSUInteger equipmentIndex = NSNotFound; // determine the equipment type and make sure it works also in the case of damaged equipment if ([equipmentKey hasSuffix:@"_DAMAGED"]) { equipmentTypeCheckKey = [equipmentKey substringToIndex:[equipmentKey length] - [@"_DAMAGED" length]]; } OOEquipmentType *eqType = [OOEquipmentType equipmentTypeWithIdentifier:equipmentTypeCheckKey]; if (eqType == nil) return; if ([eqType isMissileOrMine] || ([self isThargoid] && ([lcEquipmentKey hasSuffix:@"thargon"] || [lcEquipmentKey hasPrefix:@"thargon"]))) { [self removeExternalStore:eqType]; } else { if ([_equipment containsObject:equipmentKey]) { if (![equipmentKey isEqualToString:@"EQ_PASSENGER_BERTH"]) { equipment_weight -= [eqType requiredCargoSpace]; // all other cases; } if ([equipmentKey isEqualToString:@"EQ_CLOAKING_DEVICE"]) { if ([self isCloaked]) [self setCloaked:NO]; } if (!isPlayer) { if([equipmentKey isEqualToString:@"EQ_SHIELD_BOOSTER"]) { maxEnergy -= 256.0f; if (maxEnergy < energy) energy = maxEnergy; } else if([equipmentKey isEqualToString:@"EQ_SHIELD_ENHANCER"]) { maxEnergy -= 256.0f; energy_recharge_rate /= 1.5; if (maxEnergy < energy) energy = maxEnergy; } else if ([equipmentKey isEqual:@"EQ_CARGO_BAY"]) { max_cargo -= extra_cargo; } } } if (![equipmentKey hasSuffix:@"_DAMAGED"] && ![eqType canCarryMultiple]) { NSString *damagedKey = [equipmentKey stringByAppendingString:@"_DAMAGED"]; if ([_equipment containsObject:damagedKey]) { equipmentIndex = [_equipment indexOfObject:damagedKey]; if (equipmentIndex != NSNotFound) { // remove damaged counterpart [_equipment removeObjectAtIndex:equipmentIndex]; } equipment_weight -= [eqType requiredCargoSpace]; } } equipmentIndex = [_equipment indexOfObject:equipmentKey]; if (equipmentIndex != NSNotFound) { [_equipment removeObjectAtIndex:equipmentIndex]; } // this event must come after the item is actually removed [self doScriptEvent:OOJSID("equipmentRemoved") withArgument:equipmentKey]; // if all docking computers are damaged while active if ([self isPlayer] && [self status] == STATUS_AUTOPILOT_ENGAGED && ![self hasDockingComputer]) { [(PlayerEntity *)self disengageAutopilot]; } if ([_equipment count] == 0) [self removeAllEquipment]; } } - (BOOL) removeExternalStore:(OOEquipmentType *)eqType { NSString *identifier = [eqType identifier]; unsigned i; for (i = 0; i < missiles; i++) { if ([[missile_list[i] identifier] isEqualTo:identifier]) { // now 'delete' [i] by compacting the array while ( ++i < missiles ) missile_list[i - 1] = missile_list[i]; missiles--; return YES; } } return NO; } - (OOEquipmentType *) verifiedMissileTypeFromRole:(NSString *)role { NSString *eqRole = nil; NSString *shipKey = nil; ShipEntity *missile = nil; OOEquipmentType *missileType = nil; BOOL isRandomMissile = [role isEqualToString:@"missile"]; if (isRandomMissile) { while (!shipKey) { shipKey = [UNIVERSE randomShipKeyForRoleRespectingConditions:role]; if (!shipKey) { OOLogWARN(@"ship.setUp.missiles", @"%@ \"%@\" used in ship \"%@\" needs a valid %@.plist entry.%@", @"random missile", shipKey, [self name], @"shipdata", @"Trying another missile."); } } } else { shipKey = [UNIVERSE randomShipKeyForRoleRespectingConditions:role]; if (!shipKey) { OOLogWARN(@"ship.setUp.missiles", @"%@ \"%@\" used in ship \"%@\" needs a valid %@.plist entry.%@", @"missile_role", role, [self name], @"shipdata", @" Using defaults instead."); return nil; } } eqRole = [OOEquipmentType getMissileRegistryRoleForShip:shipKey]; // eqRole != role for generic missiles. if (eqRole == nil) { missile = [UNIVERSE newShipWithName:shipKey]; if (!missile) { if (isRandomMissile) OOLogWARN(@"ship.setUp.missiles", @"%@ \"%@\" used in ship \"%@\" needs a valid %@.plist entry.%@", @"random missile", shipKey, [self name], @"shipdata", @"Trying another missile."); else OOLogWARN(@"ship.setUp.missiles", @"%@ \"%@\" used in ship \"%@\" needs a valid %@.plist entry.%@", @"missile_role", role, [self name], @"shipdata", @" Using defaults instead."); [OOEquipmentType setMissileRegistryRole:@"" forShip:shipKey]; // no valid role for this shipKey if (isRandomMissile) return [self verifiedMissileTypeFromRole:role]; else return nil; } if(isRandomMissile) { id value; NSEnumerator *enumerator = [[[missile roleSet] roles] objectEnumerator]; while ((value = [enumerator nextObject])) { role = (NSString *)value; missileType = [OOEquipmentType equipmentTypeWithIdentifier:role]; // ensure that we have a missile or mine if ([missileType isMissileOrMine]) break; } if (![missileType isMissileOrMine]) { role = shipKey; // unique identifier to use in lieu of a valid equipment type if none are defined inside the generic missile roleset. } } missileType = [OOEquipmentType equipmentTypeWithIdentifier:role]; if (!missileType) { OOLogWARN(@"ship.setUp.missiles", @"%@ \"%@\" used in ship \"%@\" needs a valid %@.plist entry.%@", (isRandomMissile ? @"random missile" : @"missile_role"), role, [self name], @"equipment", @" Enabling compatibility mode."); missileType = [self generateMissileEquipmentTypeFrom:role]; } [OOEquipmentType setMissileRegistryRole:role forShip:shipKey]; [missile release]; } else { if ([eqRole isEqualToString:@""]) { // wrong ship definition, already written to the log in a previous call. if (isRandomMissile) return [self verifiedMissileTypeFromRole:role]; // try and find a valid missile with role 'missile'. return nil; } missileType = [OOEquipmentType equipmentTypeWithIdentifier:eqRole]; } return missileType; } - (OOEquipmentType *) selectMissile { OOEquipmentType *missileType = nil; NSString *role = nil; double chance = randf(); BOOL thargoidMissile = NO; if ([self isThargoid]) { if (_missileRole != nil) missileType = [self verifiedMissileTypeFromRole:_missileRole]; if (missileType == nil) { _missileRole = @"EQ_THARGON"; // no valid missile_role defined, use thargoid fallback from now on. missileType = [self verifiedMissileTypeFromRole:_missileRole]; } } else { // All other ships: random role 10% of the cases, if a missile_role is defined. if (chance < 0.9f && _missileRole != nil) { missileType = [self verifiedMissileTypeFromRole:_missileRole]; } if (missileType == nil) // the random 10% , or no valid missile_role defined { if (chance < 0.9f && _missileRole != nil) // no valid missile_role defined? { _missileRole = nil; // use generic ship fallback from now on. } // assign random missiles 20% of the time without missile_role (or 10% with valid missile_role) if (chance > 0.8f) role = @"missile"; // otherwise use the standard role else role = @"EQ_MISSILE"; missileType = [self verifiedMissileTypeFromRole:role]; } } if (missileType == nil) OOLogERR(@"ship.setUp.missiles", @"could not resolve missile / mine type for ship \"%@\". Original missile role:\"%@\".", [self name],_missileRole); role = [[missileType identifier] lowercaseString]; thargoidMissile = [self isThargoid] && ([role hasSuffix:@"thargon"] || [role hasPrefix:@"thargon"]); if (thargoidMissile || (!thargoidMissile && [missileType isMissileOrMine])) { return missileType; } else { OOLogWARN(@"ship.setUp.missiles", @"missile_role \"%@\" is not a valid missile / mine type for ship \"%@\".%@", [missileType identifier] , [self name],@" No missile selected."); return nil; } } - (void) removeAllEquipment { [_equipment release]; _equipment = nil; } - (OOCreditsQuantity) removeMissiles { missiles = 0; return 0; } - (NSUInteger) parcelCount { return 0; } - (NSUInteger) passengerCount { return 0; } - (NSUInteger) passengerCapacity { return 0; } - (NSUInteger) missileCount { return missiles; } - (NSUInteger) missileCapacity { return max_missiles; } - (NSUInteger) extraCargo { return extra_cargo; } /* This is used for e.g. displaying the HUD icon */ - (BOOL) hasScoop { return [self hasEquipmentItemProviding:@"EQ_FUEL_SCOOPS"] || [self hasEquipmentItemProviding:@"EQ_CARGO_SCOOPS"]; } - (BOOL) hasFuelScoop { return [self hasEquipmentItemProviding:@"EQ_FUEL_SCOOPS"]; } /* No such core equipment item, but EQ_FUEL_SCOOPS provides it */ - (BOOL) hasCargoScoop { return [self hasEquipmentItemProviding:@"EQ_CARGO_SCOOPS"]; } - (BOOL) hasECM { return [self hasEquipmentItemProviding:@"EQ_ECM"]; } - (BOOL) hasCloakingDevice { /* TODO: Checks above stop this being 'providing'. */ return [self hasEquipmentItem:@"EQ_CLOAKING_DEVICE"]; } - (BOOL) hasMilitaryScannerFilter { #if USEMASC return [self hasEquipmentItemProviding:@"EQ_MILITARY_SCANNER_FILTER"]; #else return NO; #endif } - (BOOL) hasMilitaryJammer { #if USEMASC return [self hasEquipmentItemProviding:@"EQ_MILITARY_JAMMER"]; #else return NO; #endif } - (BOOL) hasExpandedCargoBay { /* Not 'providing' - controlled through scripts */ return [self hasEquipmentItem:@"EQ_CARGO_BAY"]; } - (BOOL) hasShieldBooster { /* Not 'providing' - controlled through scripts */ return [self hasEquipmentItem:@"EQ_SHIELD_BOOSTER"]; } - (BOOL) hasMilitaryShieldEnhancer { /* Not 'providing' - controlled through scripts */ return [self hasEquipmentItem:@"EQ_NAVAL_SHIELD_BOOSTER"]; } - (BOOL) hasHeatShield { return [self hasEquipmentItemProviding:@"EQ_HEAT_SHIELD"]; } - (BOOL) hasFuelInjection { return [self hasEquipmentItemProviding:@"EQ_FUEL_INJECTION"]; } - (BOOL) hasCascadeMine { /* TODO: this could be providing since theoretically OXP * deployable mines could also do cascade effects, but there are * probably better ways to manage OXP pylon AI */ return [self hasEquipmentItem:@"EQ_QC_MINE" includeWeapons:YES whileLoading:NO]; } - (BOOL) hasEscapePod { return [self hasEquipmentItemProviding:@"EQ_ESCAPE_POD"]; } - (BOOL) hasDockingComputer { return [self hasEquipmentItemProviding:@"EQ_DOCK_COMP"]; } - (BOOL) hasGalacticHyperdrive { return [self hasEquipmentItemProviding:@"EQ_GAL_DRIVE"]; } - (float) shieldBoostFactor { float boostFactor = 1.0f; if ([self hasShieldBooster]) boostFactor += 1.0f; if ([self hasMilitaryShieldEnhancer]) boostFactor += 1.0f; return boostFactor; } /* These next three are never called as of 12/12/2014, as NPCs don't * have shields and PlayerEntity overrides these. */ - (float) maxForwardShieldLevel { return BASELINE_SHIELD_LEVEL * [self shieldBoostFactor]; } - (float) maxAftShieldLevel { return BASELINE_SHIELD_LEVEL * [self shieldBoostFactor]; } - (float) shieldRechargeRate { return [self hasMilitaryShieldEnhancer] ? 3.0f : 2.0f; } - (float) maxHyperspaceDistance { return (float)MAX_JUMP_RANGE; } - (float) afterburnerFactor { return afterburner_speed_factor; } - (float) afterburnerRate { return afterburner_rate; } - (void) setAfterburnerFactor:(GLfloat)new { afterburner_speed_factor = new; } - (void) setAfterburnerRate:(GLfloat)new { afterburner_rate = new; } - (float) maxThrust { return max_thrust; } - (void) setMaxThrust:(GLfloat)new { max_thrust = new; } - (float) thrust { return thrust; } //////////////// // // // behaviours // // // - (void) behaviour_stop_still:(double) delta_t { stick_roll = 0.0; stick_pitch = 0.0; stick_yaw = 0.0; [self applySticks:delta_t]; } - (void) behaviour_idle:(double) delta_t { stick_yaw = 0.0; if ((!isStation)&&(scanClass != CLASS_BUOY)) { stick_roll = 0.0; } else { stick_roll = flightRoll; } if (scanClass != CLASS_BUOY) { stick_pitch = 0.0; } else { stick_pitch = flightPitch; } [self applySticks:delta_t]; } - (void) behaviour_tumble:(double) delta_t { [self applySticks:delta_t]; } - (void) behaviour_tractored:(double) delta_t { desired_range = collision_radius * 2.0; ShipEntity* hauler = (ShipEntity*)[self owner]; if ((hauler)&&([hauler isShip])) { _destination = [hauler absoluteTractorPosition]; double distance = [self rangeToDestination]; if (distance < desired_range) { [self performTumble]; [self setStatus:STATUS_IN_FLIGHT]; [hauler scoopUp:self]; return; } GLfloat tf = TRACTOR_FORCE / mass; // adjust for difference in velocity (spring rule) Vector dv = vector_between([self velocity], [hauler velocity]); GLfloat moment = delta_t * 0.25 * tf; velocity.x += moment * dv.x; velocity.y += moment * dv.y; velocity.z += moment * dv.z; // acceleration = force / mass // force proportional to distance (spring rule) HPVector dp = HPvector_between(position, _destination); moment = delta_t * 0.5 * tf; velocity.x += moment * dp.x; velocity.y += moment * dp.y; velocity.z += moment * dp.z; // force inversely proportional to distance GLfloat d2 = HPmagnitude2(dp); moment = (d2 > 0.0)? delta_t * 5.0 * tf / d2 : 0.0; if (d2 > 0.0) { velocity.x += moment * dp.x; velocity.y += moment * dp.y; velocity.z += moment * dp.z; } // if ([self status] == STATUS_BEING_SCOOPED) { BOOL lost_contact = (distance > hauler->collision_radius + collision_radius + 250.0f); // 250m range for tractor beam if ([hauler isPlayer]) { switch ([(PlayerEntity*)hauler dialFuelScoopStatus]) { case SCOOP_STATUS_NOT_INSTALLED: case SCOOP_STATUS_FULL_HOLD: lost_contact = YES; // don't draw break; case SCOOP_STATUS_OKAY: case SCOOP_STATUS_ACTIVE: break; } } if (lost_contact) // 250m range for tractor beam { // escaped tractor beam [self setStatus:STATUS_IN_FLIGHT]; behaviour = BEHAVIOUR_IDLE; [self setThrust:[self maxThrust]]; // restore old thrust. frustration = 0.0; [self setOwner:self]; [shipAI exitStateMachineWithMessage:nil]; // exit nullAI.plist return; } else if ([hauler isPlayer]) { [(PlayerEntity*)hauler setScoopsActive]; } } } // being tractored; sticks ignored - CIM flightYaw = 0.0; desired_speed = 0.0; thrust = 25.0; // used to damp velocity (must be less than hauler thrust) thrust = 0.0; // must reset thrust now } - (void) behaviour_track_target:(double) delta_t { if ([self primaryTarget] == nil) { [self noteLostTargetAndGoIdle]; return; } [self trackPrimaryTarget:delta_t:NO]; // applies sticks if ([self hasProximityAlertIgnoringTarget:YES]) { [self avoidCollision]; } } - (void) behaviour_intercept_target:(double) delta_t { double range = [self rangeToPrimaryTarget]; if (behaviour == BEHAVIOUR_INTERCEPT_TARGET) { desired_speed = maxFlightSpeed; if (range < desired_range) { [shipAI reactToMessage:@"DESIRED_RANGE_ACHIEVED" context:@"BEHAVIOUR_INTERCEPT_TARGET"]; [self doScriptEvent:OOJSID("shipAchievedDesiredRange")]; } desired_speed = maxFlightSpeed * [self trackPrimaryTarget:delta_t:NO]; } else { // = BEHAVIOUR_COLLECT_TARGET ShipEntity* target = [self primaryTarget]; // if somehow ended up in this state but target is not cargo, stop // trying to scoop it if (!target || [target scanClass] != CLASS_CARGO || [target cargoType] == CARGO_NOT_CARGO) { [self noteLostTargetAndGoIdle]; return; } double target_speed = [target speed]; double eta = range / (flightSpeed - target_speed); double last_success_factor = success_factor; double last_distance = last_success_factor; double distance = [self rangeToDestination]; success_factor = distance; // double slowdownTime = 96.0 / (thrust*SHIP_THRUST_FACTOR); // more thrust implies better slowing double minTurnSpeedFactor = 0.005 * max_flight_pitch * max_flight_roll; // faster turning implies higher speeds if ((eta < slowdownTime)&&(flightSpeed > maxFlightSpeed * minTurnSpeedFactor)) desired_speed = flightSpeed * 0.75; // cut speed by 50% to a minimum minTurnSpeedFactor of speed else desired_speed = maxFlightSpeed; if (desired_speed < target_speed) { desired_speed += target_speed; if (target_speed > maxFlightSpeed) { [self noteLostTargetAndGoIdle]; return; } } if (desired_speed > maxFlightSpeed) { // never use injectors for scooping desired_speed = maxFlightSpeed; } _destination = target->position; desired_range = 0.5 * target->collision_radius; [self trackDestination: delta_t : NO]; // if (distance < last_distance) // improvement { frustration -= delta_t; if (frustration < 0.0) frustration = 0.0; } else { frustration += delta_t * 0.9; if (frustration > 10.0) // 10s of frustration { [self noteFrustration:@"BEHAVIOUR_INTERCEPT_TARGET"]; frustration -= 5.0; //repeat after another five seconds' frustration } } } if ([self hasProximityAlertIgnoringTarget:YES]) { [self avoidCollision]; } } - (void) behaviour_attack_break_off_target:(double) delta_t { if (![self canStillTrackPrimaryTarget]) { [self noteLostTargetAndGoIdle]; return; } BOOL canBurn = [self hasFuelInjection] && (fuel > MIN_FUEL); float max_available_speed = maxFlightSpeed; double range = [self rangeToPrimaryTarget]; if (canBurn) max_available_speed *= [self afterburnerFactor]; desired_speed = max_available_speed; Entity* target = [self primaryTarget]; if (desired_speed > maxFlightSpeed) { double target_speed = [target speed]; if (desired_speed > target_speed * 3.0) { desired_speed = maxFlightSpeed; // don't overuse the injectors } } if (cloakAutomatic) [self activateCloakingDevice]; if ([self hasProximityAlertIgnoringTarget:NO]) { [self avoidCollision]; return; } frustration += delta_t; if (frustration > 15.0 && accuracy >= COMBAT_AI_DOGFIGHTER && !canBurn) { desired_speed = maxFlightSpeed / 2.0; } double aspect = [self approachAspectToPrimaryTarget]; if (range > 3000.0 || ([target isShip] && [(ShipEntity*)target primaryTarget] != self) || frustration - floor(frustration) > fmin(1.6/max_flight_roll,aspect)) { [self trackPrimaryTarget:delta_t:YES]; } else { // less useful at long range if not under direct fire [self evasiveAction:delta_t]; } if (range > COMBAT_OUT_RANGE_FACTOR * weaponRange) { behaviour = BEHAVIOUR_ATTACK_TARGET; } else if (aspect < -0.75 && accuracy >= COMBAT_AI_DOGFIGHTER) { behaviour = BEHAVIOUR_ATTACK_SLOW_DOGFIGHT; } else if (frustration > 10.0 && [self approachAspectToPrimaryTarget] < 0.85 && forward_weapon_temp < COMBAT_AI_WEAPON_TEMP_READY) { frustration = 0.0; if (accuracy >= COMBAT_AI_DOGFIGHTER) { behaviour = BEHAVIOUR_ATTACK_SLOW_DOGFIGHT; } else { behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET; } } flightYaw = 0.0; } - (void) behaviour_attack_slow_dogfight:(double) delta_t { if (![self canStillTrackPrimaryTarget]) { [self noteLostTargetAndGoIdle]; return; } if ([self hasProximityAlertIgnoringTarget:YES]) { [self avoidCollision]; return; } double range = [self rangeToPrimaryTarget]; ShipEntity* target = [self primaryTarget]; double aspect = [self approachAspectToPrimaryTarget]; if (range < 2.5*(collision_radius+target->collision_radius) && [self proximityAlert] == target && aspect > 0) { desired_speed = maxFlightSpeed; [self avoidCollision]; return; } if (aspect < -0.5 && range > COMBAT_IN_RANGE_FACTOR * weaponRange * 2.0) { behaviour = BEHAVIOUR_ATTACK_TARGET; } else if (aspect < -0.5) { // mostly behind target - try to stay there and keep up desired_speed = fmin(maxFlightSpeed * 0.5,[target speed]*0.5); } else if (aspect < 0.3) { // to side of target - slow right down desired_speed = maxFlightSpeed * 0.1; } else { // coming to front of target - accelerate for a quick getaway desired_speed = maxFlightSpeed * fmin(aspect*2.5,1.0); } if (aspect > 0.85) { behaviour = BEHAVIOUR_ATTACK_BREAK_OFF_TARGET; } if (aspect > 0.0) { frustration += delta_t; } else { frustration -= delta_t; } if (frustration > 10.0) { desired_speed /= 2.0; } else if (frustration < 0.0) frustration = 0.0; [self trackPrimaryTarget:delta_t:NO]; if (missiles) [self considerFiringMissile:delta_t]; if (cloakAutomatic) [self activateCloakingDevice]; } - (void) behaviour_evasive_action:(double) delta_t { BOOL canBurn = [self hasFuelInjection] && (fuel > MIN_FUEL); float max_available_speed = maxFlightSpeed; // double range = [self rangeToPrimaryTarget]; if (canBurn) max_available_speed *= [self afterburnerFactor]; desired_speed = max_available_speed; if (desired_speed > maxFlightSpeed) { ShipEntity* target = [self primaryTarget]; double target_speed = [target speed]; if (desired_speed > target_speed) { desired_speed = maxFlightSpeed; // don't overuse the injectors } } if (cloakAutomatic) [self activateCloakingDevice]; if ([self proximityAlert] != nil) { [self avoidCollision]; return; } [self evasiveAction:delta_t]; frustration += delta_t; if (frustration > 0.5) { if (behaviour == BEHAVIOUR_FLEE_EVASIVE_ACTION) { [self setEvasiveJink:400.0]; behaviour = BEHAVIOUR_FLEE_TARGET; } else { behaviour = BEHAVIOUR_ATTACK_TARGET; } } flightYaw = 0.0; // probably only useful for Thargoids, except for the occasional opportunist [self fireMainWeapon:[self rangeToPrimaryTarget]]; } - (void) behaviour_attack_target:(double) delta_t { double range = [self rangeToPrimaryTarget]; if (cloakAutomatic) [self activateCloakingDevice]; /* Start of behaviour selection: * Anything beyond the basics should require accuracy >= COMBAT_AI_ISNT_AWFUL * Anything fancy should require accuracy >= COMBAT_AI_IS_SMART * If precise aim is required, behaviour should have accuracy >= COMBAT_AI_TRACKS_CLOSER * - CIM */ OOWeaponType forward_weapon_real_type = forward_weapon_type; GLfloat forward_weapon_real_temp = forward_weapon_temp; // if forward weapon is actually on a subent if (isWeaponNone(forward_weapon_real_type)) { BOOL hasTurrets = NO; NSEnumerator *subEnum = [self shipSubEntityEnumerator]; ShipEntity *se = nil; while (isWeaponNone(forward_weapon_real_type) && (se = [subEnum nextObject])) { forward_weapon_real_type = se->forward_weapon_type; forward_weapon_real_temp = se->forward_weapon_temp; if (se->behaviour == BEHAVIOUR_TRACK_AS_TURRET) { hasTurrets = YES; } } if (isWeaponNone(forward_weapon_real_type) && hasTurrets) { // safety for ships only equipped with turrets forward_weapon_real_type = OOWeaponTypeFromEquipmentIdentifierSloppy(@"EQ_WEAPON_PULSE_LASER"); forward_weapon_real_temp = COMBAT_AI_WEAPON_TEMP_USABLE * 0.9; } } if ([forward_weapon_real_type isTurretLaser]) { behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE; } else { BOOL in_good_range = aim_tolerance*range < COMBAT_AI_CONFIDENCE_FACTOR; BOOL aft_weapon_ready = !isWeaponNone(aft_weapon_type) && (aft_weapon_temp < COMBAT_AI_WEAPON_TEMP_READY) && in_good_range; BOOL forward_weapon_ready = !isWeaponNone(forward_weapon_real_type) && (forward_weapon_real_temp < COMBAT_AI_WEAPON_TEMP_READY); // does not require in_good_range BOOL port_weapon_ready = !isWeaponNone(port_weapon_type) && (port_weapon_temp < COMBAT_AI_WEAPON_TEMP_READY) && in_good_range; BOOL starboard_weapon_ready = !isWeaponNone(starboard_weapon_type) && (starboard_weapon_temp < COMBAT_AI_WEAPON_TEMP_READY) && in_good_range; // if no weapons cool enough to be good choices, be less picky BOOL weapons_heating = NO; if (!forward_weapon_ready && !aft_weapon_ready && !port_weapon_ready && !starboard_weapon_ready) { weapons_heating = YES; aft_weapon_ready = !isWeaponNone(aft_weapon_type) && (aft_weapon_temp < COMBAT_AI_WEAPON_TEMP_USABLE) && in_good_range; forward_weapon_ready = !isWeaponNone(forward_weapon_real_type) && (forward_weapon_real_temp < COMBAT_AI_WEAPON_TEMP_USABLE); // does not require in_good_range port_weapon_ready = !isWeaponNone(port_weapon_type) && (port_weapon_temp < COMBAT_AI_WEAPON_TEMP_USABLE) && in_good_range; starboard_weapon_ready = !isWeaponNone(starboard_weapon_type) && (starboard_weapon_temp < COMBAT_AI_WEAPON_TEMP_USABLE) && in_good_range; } Entity* target = [self primaryTarget]; double aspect = [self approachAspectToPrimaryTarget]; if (!forward_weapon_ready && !aft_weapon_ready && !port_weapon_ready && !starboard_weapon_ready) { // no usable weapons! Either not fitted or overheated // if unarmed if (isWeaponNone(forward_weapon_real_type) && isWeaponNone(aft_weapon_type) && isWeaponNone(port_weapon_type) && isWeaponNone(starboard_weapon_type)) { behaviour = BEHAVIOUR_ATTACK_FLY_FROM_TARGET; } else if (aspect > 0) { if (in_good_range) { if (accuracy >= COMBAT_AI_IS_SMART && randf() < 0.75) { behaviour = BEHAVIOUR_EVASIVE_ACTION; } else { behaviour = BEHAVIOUR_ATTACK_FLY_FROM_TARGET; } } else { // ready to get more accurate shots later behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET; } } else { // if target is running away, stay on target // unless too close for safety if (range < COMBAT_IN_RANGE_FACTOR * weaponRange) { behaviour = BEHAVIOUR_ATTACK_FLY_FROM_TARGET; } else { behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET; } } } // if our current target isn't targeting us, and we have some idea of how to fight, and our weapons are running hot, and we're fairly nearby else if (weapons_heating && accuracy >= COMBAT_AI_ISNT_AWFUL && [target isShip] && [(ShipEntity *)target primaryTarget] != self && range < COMBAT_OUT_RANGE_FACTOR * weaponRange) { // then back off a bit for weapons to cool so we get a good attack run later, rather than weaving closer float relativeSpeed = magnitude(vector_subtract([self velocity], [target velocity])); [self setEvasiveJink:(range + COMBAT_JINK_OFFSET - relativeSpeed / max_flight_pitch)]; behaviour = BEHAVIOUR_ATTACK_FLY_FROM_TARGET; } else { BOOL nearby = range < COMBAT_IN_RANGE_FACTOR * getWeaponRangeFromType(forward_weapon_type); BOOL midrange = range < COMBAT_OUT_RANGE_FACTOR * getWeaponRangeFromType(aft_weapon_type); if (nearby && aft_weapon_ready) { jink = kZeroVector; // almost all behaviours behaviour = BEHAVIOUR_RUNNING_DEFENSE; } else if (nearby && (port_weapon_ready || starboard_weapon_ready)) { jink = kZeroVector; // almost all behaviours behaviour = BEHAVIOUR_ATTACK_BROADSIDE; } else if (nearby) { if (!pitching_over) // don't change jink in the middle of a sharp turn. { /* For most AIs, is behaviour_attack_target called as starting behaviour on every hit. Target can both fly towards or away from ourselves here. Both situations need a different jink.z for optimal collision avoidance at high speed approach and low speed dogfighting. The COMBAT_JINK_OFFSET intentionally over-compensates the range for collision radii to send ships towards the target at low speeds. */ float relativeSpeed = magnitude(vector_subtract([self velocity], [target velocity])); [self setEvasiveJink:(range + COMBAT_JINK_OFFSET - relativeSpeed / max_flight_pitch)]; } // good pilots use behaviour_attack_break_off_target instead if (accuracy >= COMBAT_AI_FLEES_BETTER) { behaviour = BEHAVIOUR_ATTACK_BREAK_OFF_TARGET; } else { behaviour = BEHAVIOUR_ATTACK_FLY_FROM_TARGET; } } else if (forward_weapon_ready) { jink = kZeroVector; // almost all behaviours // TODO: good pilots use behaviour_attack_sniper sometimes if (getWeaponRangeFromType(forward_weapon_real_type) > 12500 && range > 12500) { behaviour = BEHAVIOUR_ATTACK_SNIPER; } // generally not good tactics the next two else if (accuracy < COMBAT_AI_ISNT_AWFUL && aspect < 0) { behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET_SIX; } else if (accuracy < COMBAT_AI_ISNT_AWFUL) { behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE; } else { behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET; } } else if (port_weapon_ready || starboard_weapon_ready) { jink = kZeroVector; // almost all behaviours behaviour = BEHAVIOUR_ATTACK_BROADSIDE; } else if (aft_weapon_ready && midrange) { jink = kZeroVector; // almost all behaviours behaviour = BEHAVIOUR_RUNNING_DEFENSE; } else { jink = kZeroVector; // almost all behaviours behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET; } } } frustration = 0.0; // behaviour changed, so reset frustration } - (void) behaviour_attack_broadside:(double) delta_t { BOOL canBurn = [self hasFuelInjection] && (fuel > MIN_FUEL); float max_available_speed = maxFlightSpeed; double range = [self rangeToPrimaryTarget]; if (canBurn) max_available_speed *= [self afterburnerFactor]; if (cloakAutomatic) [self activateCloakingDevice]; if (![self canStillTrackPrimaryTarget]) { [self noteLostTargetAndGoIdle]; return; } desired_speed = max_available_speed; if (range < COMBAT_BROADSIDE_IN_RANGE_FACTOR * weaponRange) { behaviour = BEHAVIOUR_ATTACK_TARGET; } else { if (port_weapon_temp < starboard_weapon_temp) { if (isWeaponNone(port_weapon_type)) { behaviour = BEHAVIOUR_ATTACK_BROADSIDE_RIGHT; [self setWeaponDataFromType:starboard_weapon_type]; } else { behaviour = BEHAVIOUR_ATTACK_BROADSIDE_LEFT; [self setWeaponDataFromType:port_weapon_type]; } } else { if (isWeaponNone(starboard_weapon_type)) { behaviour = BEHAVIOUR_ATTACK_BROADSIDE_RIGHT; [self setWeaponDataFromType:starboard_weapon_type]; } else { behaviour = BEHAVIOUR_ATTACK_BROADSIDE_LEFT; [self setWeaponDataFromType:port_weapon_type]; } } jink = kZeroVector; if (weapon_damage == 0.0) { // safety in case side lasers no longer exist behaviour = BEHAVIOUR_ATTACK_TARGET; } else if (range > 0.9 * weaponRange) { behaviour = BEHAVIOUR_CLOSE_TO_BROADSIDE_RANGE; } } frustration = 0.0; // behaviour changed, so reset frustration } - (void) behaviour_attack_broadside_left:(double) delta_t { [self behaviour_attack_broadside_target:delta_t leftside:YES]; } - (void) behaviour_attack_broadside_right:(double) delta_t { [self behaviour_attack_broadside_target:delta_t leftside:NO]; } - (void) behaviour_attack_broadside_target:(double) delta_t leftside:(BOOL) leftside { BOOL canBurn = [self hasFuelInjection] && (fuel > MIN_FUEL); float max_available_speed = maxFlightSpeed; double range = [self rangeToPrimaryTarget]; if (canBurn) max_available_speed *= [self afterburnerFactor]; if ([self primaryTarget] == nil) { [self noteLostTargetAndGoIdle]; return; } GLfloat currentWeaponRange = getWeaponRangeFromType(leftside?port_weapon_type:starboard_weapon_type); if (range > COMBAT_BROADSIDE_RANGE_FACTOR * currentWeaponRange) { behaviour = BEHAVIOUR_CLOSE_TO_BROADSIDE_RANGE; return; } // can get closer on broadsides since there's less risk of a collision if ((range < COMBAT_BROADSIDE_IN_RANGE_FACTOR * currentWeaponRange)||([self proximityAlert] != nil)) { if (![self hasProximityAlertIgnoringTarget:YES]) { behaviour = BEHAVIOUR_ATTACK_TARGET; } else { [self avoidCollision]; return; } } else { if (![self canStillTrackPrimaryTarget]) { [self noteLostTargetAndGoIdle]; return; } } // control speed // BOOL isUsingAfterburner = canBurn && (flightSpeed > maxFlightSpeed); double slow_down_range = currentWeaponRange * COMBAT_WEAPON_RANGE_FACTOR * ((isUsingAfterburner)? 3.0 * [self afterburnerFactor] : 1.0); // double target_speed = [target speed]; if (range <= slow_down_range) desired_speed = fmin(0.8 * maxFlightSpeed, fmax((2.0-frustration)*maxFlightSpeed, 0.1 * maxFlightSpeed)); // within the weapon's range slow down to aim else desired_speed = max_available_speed; // use afterburner to approach double last_success_factor = success_factor; success_factor = [self trackSideTarget:delta_t:leftside]; // do the actual piloting if (weapon_temp > COMBAT_AI_WEAPON_TEMP_USABLE) { // will probably have more luck with the other laser or picking a different attack method if (leftside) { if (!isWeaponNone(starboard_weapon_type)) { behaviour = BEHAVIOUR_ATTACK_BROADSIDE_RIGHT; } else { behaviour = BEHAVIOUR_ATTACK_TARGET; } } else { if (!isWeaponNone(port_weapon_type)) { behaviour = BEHAVIOUR_ATTACK_BROADSIDE_LEFT; } else { behaviour = BEHAVIOUR_ATTACK_TARGET; } } } /* FIXME: again, basically all of this next bit common with standard attack */ if ((success_factor > 0.999)||(success_factor > last_success_factor)) { frustration -= delta_t; if (frustration < 0.0) frustration = 0.0; } else { frustration += delta_t; if (frustration > 3.0) // 3s of frustration { [self noteFrustration:@"BEHAVIOUR_ATTACK_BROADSIDE"]; [self setEvasiveJink:1000.0]; behaviour = BEHAVIOUR_ATTACK_FLY_FROM_TARGET; frustration = 0.0; desired_speed = maxFlightSpeed; } } if (missiles) [self considerFiringMissile:delta_t]; if (cloakAutomatic) [self activateCloakingDevice]; if (leftside) { [self firePortWeapon:range]; } else { [self fireStarboardWeapon:range]; } if (weapon_temp > COMBAT_AI_WEAPON_TEMP_USABLE) { behaviour = BEHAVIOUR_ATTACK_TARGET; } } - (void) behaviour_close_to_broadside_range:(double) delta_t { double range = [self rangeToPrimaryTarget]; if ([self proximityAlert] != nil) { if ([self proximityAlert] == [self primaryTarget]) { behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET; // this behaviour will handle proximity_alert. [self behaviour_attack_fly_from_target: delta_t]; // do it now. } else { [self avoidCollision]; } return; } if (![self canStillTrackPrimaryTarget]) { [self noteLostTargetAndGoIdle]; return; } behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE; [self behaviour_fly_to_target_six:delta_t]; if (!isWeaponNone(port_weapon_type)) { [self setWeaponDataFromType:port_weapon_type]; } else { [self setWeaponDataFromType:starboard_weapon_type]; } if (range <= COMBAT_BROADSIDE_RANGE_FACTOR * weaponRange) { behaviour = BEHAVIOUR_ATTACK_BROADSIDE; } else { behaviour = BEHAVIOUR_CLOSE_TO_BROADSIDE_RANGE; } } - (void) behaviour_close_with_target:(double) delta_t { double range = [self rangeToPrimaryTarget]; if ([self proximityAlert] != nil) { if ([self proximityAlert] == [self primaryTarget]) { behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET; // this behaviour will handle proximity_alert. [self behaviour_attack_fly_from_target: delta_t]; // do it now. } else { [self avoidCollision]; } return; } if (![self canStillTrackPrimaryTarget]) { [self noteLostTargetAndGoIdle]; return; } behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE; double saved_frustration = frustration; [self behaviour_fly_to_target_six:delta_t]; frustration = saved_frustration; // ignore fly-to-12 frustration frustration += delta_t; if (range <= COMBAT_IN_RANGE_FACTOR * weaponRange || frustration > 5.0) { behaviour = BEHAVIOUR_ATTACK_TARGET; } else { behaviour = BEHAVIOUR_CLOSE_WITH_TARGET; } } - (void) behaviour_attack_sniper:(double) delta_t { if (![self canStillTrackPrimaryTarget]) { [self noteLostTargetAndGoIdle]; return; } Entity* rawTarget = [self primaryTarget]; if (![rawTarget isShip]) { // can't attack a wormhole [self noteLostTargetAndGoIdle]; return; } ShipEntity *target = (ShipEntity *)rawTarget; double range = [self rangeToPrimaryTarget]; float max_available_speed = maxFlightSpeed; if (range < 15000) { behaviour = BEHAVIOUR_ATTACK_TARGET; } else { if (range > weaponRange || range > scannerRange * 0.8) { BOOL canBurn = [self hasFuelInjection] && (fuel > MIN_FUEL); if (canBurn && [target weaponRange] > weaponRange && range > weaponRange) { // if outside maximum weapon range, but inside target weapon range // close to fight ASAP! max_available_speed *= [self afterburnerFactor]; } desired_speed = max_available_speed; } else { desired_speed = max_available_speed / 10.0f; } double last_success_factor = success_factor; success_factor = [self trackPrimaryTarget:delta_t:NO]; if ((success_factor > 0.999)||(success_factor > last_success_factor)) { frustration -= delta_t; if (frustration < 0.0) frustration = 0.0; } else { frustration += delta_t; if (frustration > 3.0) // 3s of frustration { [self noteFrustration:@"BEHAVIOUR_ATTACK_SNIPER"]; [self setEvasiveJink:1000.0]; behaviour = BEHAVIOUR_ATTACK_TARGET; frustration = 0.0; desired_speed = maxFlightSpeed; } } } if (missiles) [self considerFiringMissile:delta_t]; if (cloakAutomatic) [self activateCloakingDevice]; [self fireMainWeapon:range]; if (weapon_temp > COMBAT_AI_WEAPON_TEMP_USABLE && accuracy >= COMBAT_AI_ISNT_AWFUL) { behaviour = BEHAVIOUR_ATTACK_TARGET; } } - (void) behaviour_fly_to_target_six:(double) delta_t { BOOL canBurn = [self hasFuelInjection] && (fuel > MIN_FUEL); float max_available_speed = maxFlightSpeed; double range = [self rangeToPrimaryTarget]; if (canBurn) max_available_speed *= [self afterburnerFactor]; // deal with collisions and lost targets if ([self proximityAlert] != nil) { if ([self proximityAlert] == [self primaryTarget]) { behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET; // this behaviour will handle proximity_alert. [self behaviour_attack_fly_from_target: delta_t]; // do it now. } else { [self avoidCollision]; } return; } if (![self canStillTrackPrimaryTarget]) { [self noteLostTargetAndGoIdle]; return; } // control speed BOOL isUsingAfterburner = canBurn && (flightSpeed > maxFlightSpeed); BOOL closeQuickly = (canBurn && range > weaponRange); double slow_down_range = weaponRange * COMBAT_WEAPON_RANGE_FACTOR * ((isUsingAfterburner)? 3.0 * [self afterburnerFactor] : 1.0); if (closeQuickly) { slow_down_range = weaponRange * COMBAT_OUT_RANGE_FACTOR; } double back_off_range = weaponRange * COMBAT_OUT_RANGE_FACTOR * ((isUsingAfterburner)? 3.0 * [self afterburnerFactor] : 1.0); Entity* rawTarget = [self primaryTarget]; if (![rawTarget isShip]) { [self noteLostTargetAndGoIdle]; return; } ShipEntity* target = (ShipEntity *)rawTarget; double target_speed = [target speed]; double last_success_factor = success_factor; double distance = [self rangeToDestination]; success_factor = distance; if (range < slow_down_range && (behaviour == BEHAVIOUR_ATTACK_FLY_TO_TARGET_SIX)) { if (range < back_off_range) { desired_speed = fmax(0.9 * target_speed, 0.4 * maxFlightSpeed); } else { desired_speed = fmax(target_speed * 1.2, maxFlightSpeed); } // avoid head-on collision if ((range < 0.5 * distance)&&(behaviour == BEHAVIOUR_ATTACK_FLY_TO_TARGET_SIX)) behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE; } else { if (range < back_off_range) { desired_speed = fmax(0.9 * target_speed, 0.8 * maxFlightSpeed); } else { desired_speed = max_available_speed; // use afterburner to approach } } // if within 0.75km of the target's six or twelve, or if target almost at standstill for 62.5% of non-thargoid ships (!), // then vector in attack. if (distance < 750.0 || (target_speed < 0.2 && ![self isThargoid] && ([self universalID] & 14) > 4)) { behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET; frustration = 0.0; desired_speed = fmax(target_speed, 0.4 * maxFlightSpeed); // within the weapon's range don't use afterburner } // target-six if (behaviour == BEHAVIOUR_ATTACK_FLY_TO_TARGET_SIX) { // head for a point weapon-range * 0.5 to the six of the target // _destination = [target distance_six:0.5 * weaponRange]; } // target-twelve if (behaviour == BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE) { if ([forward_weapon_type isTurretLaser]) { // head for a point near the target, avoiding common Galcop weapon mount locations // TODO: this should account for weapon ranges GLfloat offset = 1000.0; GLfloat spacing = 2000.0; if (accuracy > 0.0) { offset = accuracy * 750.0; spacing = 2000.0 + (accuracy * 500.0); } if (entity_personality & 1) { // half at random offset = -offset; } _destination = [target distance_twelve:spacing withOffset:offset]; } else { // head for a point 1.25km above the target _destination = [target distance_twelve:1250 withOffset:0]; } } pitching_over = NO; // in case it's set from elsewhere double confidenceFactor = [self trackDestination:delta_t :NO]; if(success_factor > last_success_factor || confidenceFactor < 0.85) frustration += delta_t; else if(frustration > 0.0) frustration -= delta_t * 0.75; double aspect = [self approachAspectToPrimaryTarget]; if(![forward_weapon_type isTurretLaser] && (frustration > 10 || aspect > 0.75)) { behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET; } // use weaponry if (missiles) [self considerFiringMissile:delta_t]; if (cloakAutomatic) [self activateCloakingDevice]; [self fireMainWeapon:range]; if (weapon_temp > COMBAT_AI_WEAPON_TEMP_USABLE) { behaviour = BEHAVIOUR_ATTACK_TARGET; } } - (void) behaviour_attack_mining_target:(double) delta_t { double range = [self rangeToPrimaryTarget]; if (![self canStillTrackPrimaryTarget]) { [self noteLostTargetAndGoIdle]; desired_speed = maxFlightSpeed * 0.375; return; } else if ((range < 650) || ([self proximityAlert] != nil)) { if ([self proximityAlert] == NO_TARGET) { desired_speed = range * maxFlightSpeed / (650.0 * 16.0); } else { [self avoidCollision]; } } else { //we have a target, its within scanner range, and outside 650 desired_speed = maxFlightSpeed * 0.875; } [self trackPrimaryTarget:delta_t:NO]; /* Don't open fire until within 3km - it doesn't take many mining * laser shots to destroy an asteroid, but some of these mining * ships are way too slow to effectively chase down the debris: * wait until reasonably close before trying to split it. */ if (range < 3000) { [self fireMainWeapon:range]; } } - (void) behaviour_attack_fly_to_target:(double) delta_t { BOOL canBurn = [self hasFuelInjection] && (fuel > MIN_FUEL); float max_available_speed = maxFlightSpeed; double range = [self rangeToPrimaryTarget]; if (canBurn) max_available_speed *= [self afterburnerFactor]; if ([self primaryTarget] == nil) { [self noteLostTargetAndGoIdle]; return; } Entity* rawTarget = [self primaryTarget]; if (![rawTarget isShip]) { // can't attack a wormhole [self noteLostTargetAndGoIdle]; return; } ShipEntity *target = (ShipEntity *)rawTarget; if ((range < COMBAT_IN_RANGE_FACTOR * weaponRange)||([self proximityAlert] != nil)) { if (![self hasProximityAlertIgnoringTarget:YES]) { behaviour = BEHAVIOUR_ATTACK_TARGET; } else { [self avoidCollision]; return; } } else { if (![self canStillTrackPrimaryTarget]) { [self noteLostTargetAndGoIdle]; return; } } // control speed // BOOL isUsingAfterburner = canBurn && (flightSpeed > maxFlightSpeed); BOOL closeQuickly = (canBurn && [target weaponRange] > weaponRange && range > weaponRange); double slow_down_range = weaponRange * COMBAT_WEAPON_RANGE_FACTOR * ((isUsingAfterburner)? 3.0 * [self afterburnerFactor] : 1.0); if (closeQuickly) { slow_down_range = weaponRange * COMBAT_OUT_RANGE_FACTOR; } double back_off_range = 10000 * COMBAT_OUT_RANGE_FACTOR * ((isUsingAfterburner)? 3.0 * [self afterburnerFactor] : 1.0); double target_speed = [target speed]; double aspect = [self approachAspectToPrimaryTarget]; if (range <= slow_down_range) { if (range < back_off_range) { if (accuracy < COMBAT_AI_IS_SMART || ([target primaryTarget] == self && aspect > 0.8) || aim_tolerance*range > COMBAT_AI_CONFIDENCE_FACTOR) { if (accuracy >= COMBAT_AI_FLEES_BETTER && aspect > 0.8) { desired_speed = fmax(target_speed * 1.25, 0.8 * maxFlightSpeed); // stay at high speed if might be taking return fire } else { desired_speed = fmax(target_speed * 1.05, 0.25 * maxFlightSpeed); // within the weapon's range match speed } } else { // smart, and not being shot at right now - slow down to attack desired_speed = fmax(0.1 * target_speed, 0.1 * maxFlightSpeed); } } else { if (accuracy < COMBAT_AI_IS_SMART || ([target isShip] && [(ShipEntity *)target primaryTarget] == self) || range > weaponRange / 2.0) { desired_speed = fmax(target_speed * 1.5, maxFlightSpeed); } else { // smart, and not being shot at right now - slow down to attack if (aspect > -0.25) { desired_speed = fmax(0.5 * target_speed, 0.5 * maxFlightSpeed); } else { desired_speed = fmax(1.25 * target_speed, 0.5 * maxFlightSpeed); } } } } else { if (closeQuickly) { desired_speed = max_available_speed; // use afterburner to approach } else { desired_speed = fmax(maxFlightSpeed,fmin(3.0 * target_speed, max_available_speed)); // possibly use afterburner to approach } } double last_success_factor = success_factor; success_factor = [self trackPrimaryTarget:delta_t:NO]; // do the actual piloting if ((success_factor > 0.999)||(success_factor > last_success_factor)) { frustration -= delta_t; if (frustration < 0.0) frustration = 0.0; } else { frustration += delta_t; if (frustration > 3.0) // 3s of frustration { [self noteFrustration:@"BEHAVIOUR_ATTACK_FLY_TO_TARGET"]; [self setEvasiveJink:1000.0]; behaviour = BEHAVIOUR_ATTACK_TARGET; frustration = 0.0; desired_speed = maxFlightSpeed; } } if (missiles) [self considerFiringMissile:delta_t]; if (cloakAutomatic) [self activateCloakingDevice]; [self fireMainWeapon:range]; if (weapon_temp > COMBAT_AI_WEAPON_TEMP_USABLE && accuracy >= COMBAT_AI_ISNT_AWFUL && aim_tolerance * range < COMBAT_AI_CONFIDENCE_FACTOR) { // don't do this if the target is fleeing and the front laser is // the only weapon, or if we're too far away to use non-front // lasers effectively if (aspect < 0 || !isWeaponNone(aft_weapon_type) || !isWeaponNone(port_weapon_type) || !isWeaponNone(starboard_weapon_type)) { frustration = 0.0; behaviour = BEHAVIOUR_ATTACK_TARGET; } } else if (accuracy >= COMBAT_AI_FLEES_BETTER_2) { // if we're right in their gunsights, dodge! // need to dodge sooner if in aft sights if ([target behaviour] != BEHAVIOUR_FLEE_TARGET && [target behaviour] != BEHAVIOUR_FLEE_EVASIVE_ACTION) { if ((aspect > 0.99999 && !isWeaponNone([target weaponTypeForFacing:WEAPON_FACING_FORWARD strict:NO])) || (aspect < -0.999 && !isWeaponNone([target weaponTypeForFacing:WEAPON_FACING_AFT strict:NO]))) { frustration = 0.0; behaviour = BEHAVIOUR_EVASIVE_ACTION; } } } } - (void) behaviour_attack_fly_from_target:(double) delta_t { double range = [self rangeToPrimaryTarget]; double last_success_factor = success_factor; success_factor = range; if ([self primaryTarget] == nil) { [self noteLostTargetAndGoIdle]; return; } if (last_success_factor > success_factor) // our target is closing in. { frustration += delta_t; } else { // not getting away fast enough? frustration += delta_t / 4.0 ; } if (frustration > 10.0) { if (randf() < 0.3) { desired_speed = maxFlightSpeed * (([self hasFuelInjection] && (fuel > MIN_FUEL)) ? [self afterburnerFactor] : 1); } else if (range > COMBAT_IN_RANGE_FACTOR * weaponRange && randf() < 0.3) { behaviour = BEHAVIOUR_ATTACK_TARGET; } GLfloat z = jink.z; if (randf() < 0.3) { z /= 2; // move the z-offset closer to the target to let him fly away from the target. desired_speed = flightSpeed * 2; // increase speed a bit. } [self setEvasiveJink:z]; frustration /= 2.0; } if (desired_speed > maxFlightSpeed) { ShipEntity* target = [self primaryTarget]; double target_speed = [target speed]; if (desired_speed > target_speed * 2.0) { desired_speed = maxFlightSpeed; // don't overuse the injectors } } else if (desired_speed < maxFlightSpeed * 0.5) { desired_speed = maxFlightSpeed; } if (range > COMBAT_OUT_RANGE_FACTOR * weaponRange + 15.0 * jink.x || flightSpeed > (scannerRange - range) * max_flight_pitch / 6.28) { jink = kZeroVector; behaviour = BEHAVIOUR_ATTACK_TARGET; frustration = 0.0; } [self trackPrimaryTarget:delta_t:YES]; if (missiles) [self considerFiringMissile:delta_t]; if (cloakAutomatic) [self activateCloakingDevice]; if ([self hasProximityAlertIgnoringTarget:YES]) [self avoidCollision]; if (accuracy >= COMBAT_AI_FLEES_BETTER_2) { double aspect = [self approachAspectToPrimaryTarget]; // if we're right in their gunsights, dodge! // need to dodge sooner if in aft sights if (aspect > 0.99999 || aspect < -0.999) { frustration = 0.0; behaviour = BEHAVIOUR_EVASIVE_ACTION; } } } - (void) behaviour_running_defense:(double) delta_t { if (![self canStillTrackPrimaryTarget]) { [self noteLostTargetAndGoIdle]; return; } double range = [self rangeToPrimaryTarget]; desired_speed = maxFlightSpeed; // not injectors jink = kZeroVector; if (range > weaponRange || range > 0.8 * scannerRange || range == 0) { behaviour = BEHAVIOUR_CLOSE_WITH_TARGET; if ([forward_weapon_type isTurretLaser]) { behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE; } frustration = 0.0; } [self trackPrimaryTarget:delta_t:YES]; if ([forward_weapon_type isTurretLaser]) { // most Thargoids will only have the forward weapon [self fireMainWeapon:range]; } else { [self fireAftWeapon:range]; } if (cloakAutomatic) [self activateCloakingDevice]; if ([self hasProximityAlertIgnoringTarget:YES]) [self avoidCollision]; if (behaviour != BEHAVIOUR_CLOSE_WITH_TARGET && weapon_temp > COMBAT_AI_WEAPON_TEMP_USABLE) { behaviour = BEHAVIOUR_ATTACK_TARGET; } // remember to look where you're going? if (accuracy >= COMBAT_AI_ISNT_AWFUL && [self hasProximityAlertIgnoringTarget:YES]) { [self avoidCollision]; } } - (void) behaviour_flee_target:(double) delta_t { BOOL canBurn = [self hasFuelInjection] && (fuel > MIN_FUEL); float max_available_speed = maxFlightSpeed; double range = [self rangeToPrimaryTarget]; if ([self primaryTarget] == nil) { [self noteLostTargetAndGoIdle]; return; } if (canBurn) max_available_speed *= [self afterburnerFactor]; double last_range = success_factor; success_factor = range; if (range > desired_range || range == 0) [shipAI message:@"REACHED_SAFETY"]; else desired_speed = max_available_speed; if (range > last_range) // improvement { frustration -= 0.25 * delta_t; if (frustration < 0.0) frustration = 0.0; } else { frustration += delta_t; if (frustration > 15.0) // 15s of frustration { [self noteFrustration:@"BEHAVIOUR_FLEE_TARGET"]; frustration = 0.0; } } [self trackPrimaryTarget:delta_t:YES]; Entity *target = [self primaryTarget]; if (missiles && [target isShip] && [(ShipEntity *)target primaryTarget] == self) { [self considerFiringMissile:delta_t]; } if (([self hasCascadeMine]) && (range < 10000.0) && canBurn) { float qbomb_chance = 0.01 * delta_t; if (randf() < qbomb_chance) { [self launchCascadeMine]; } } // thargoids won't normally be fleeing, but if they do, they can still shoot if ([forward_weapon_type isTurretLaser]) { [self fireMainWeapon:range]; } if (cloakAutomatic) [self activateCloakingDevice]; // remember to look where you're going? if (accuracy >= COMBAT_AI_ISNT_AWFUL && [self hasProximityAlertIgnoringTarget:YES]) { [self avoidCollision]; } } - (void) behaviour_fly_range_from_destination:(double) delta_t { double distance = [self rangeToDestination]; if (distance < desired_range) { behaviour = BEHAVIOUR_FLY_FROM_DESTINATION; if (desired_speed < maxFlightSpeed) { desired_speed = maxFlightSpeed; // Not all AI define speed when flying away. Start with max speed to stay compatible with such AI's, but allow faster flight if it's (e.g.) used to flee from coordinates rather than entity } } else { behaviour = BEHAVIOUR_FLY_TO_DESTINATION; } if ([self hasProximityAlertIgnoringTarget:YES]) { [self avoidCollision]; } frustration = 0.0; } - (void) behaviour_face_destination:(double) delta_t { double max_cos = MAX_COS; double distance = [self rangeToDestination]; double old_pitch = flightPitch; desired_speed = 0.0; if (desired_range > 1.0 && distance > desired_range) { max_cos = sqrt(1 - 0.90 * desired_range*desired_range/(distance * distance)); // Head for a point within 95% of desired_range (must match the value in trackDestination) } double confidenceFactor = [self trackDestination:delta_t:NO]; if (confidenceFactor >= max_cos && flightPitch == 0.0) { // desired facing achieved and movement stabilised. [shipAI message:@"FACING_DESTINATION"]; [self doScriptEvent:OOJSID("shipNowFacingDestination")]; frustration = 0.0; if(docking_match_rotation) // IDLE stops rotating while docking { behaviour = BEHAVIOUR_FLY_TO_DESTINATION; } else { behaviour = BEHAVIOUR_IDLE; } } if(flightSpeed == 0) frustration += delta_t; if (frustration > 15.0 / max_flight_pitch) // allow more time for slow ships. { frustration = 0.0; [self noteFrustration:@"BEHAVIOUR_FACE_DESTINATION"]; if(flightPitch == old_pitch) flightPitch = 0.5 * max_flight_pitch; // hack to get out of frustration. } /* 2009-7-18 Eric: the condition check below is intended to eliminate the flippering between two positions for fast turning ships during low FPS conditions. This flippering is particular frustrating on slow computers during docking. But with my current computer I can't induce those low FPS conditions so I can't properly test if it helps. I did try with the TAF time acceleration that also generated larger frame jumps and than it seemed to help. */ if(flightSpeed == 0 && frustration > 5 && confidenceFactor > 0.5 && ((flightPitch > 0 && old_pitch < 0) || (flightPitch < 0 && old_pitch > 0))) { flightPitch += 0.5 * old_pitch; // damping with last pitch value. } if ([self hasProximityAlertIgnoringTarget:YES]) { [self avoidCollision]; } } - (void) behaviour_land_on_planet:(double) delta_t { double max_cos = MAX_COS2; // trackDestination returns the squared confidence in reverse mode. desired_speed = 0.0; OOPlanetEntity* planet = [UNIVERSE entityForUniversalID:planetForLanding]; if (![planet isPlanet]) { behaviour = BEHAVIOUR_IDLE; aiScriptWakeTime = 1; // reconsider JSAI [shipAI message:@"NO_PLANET_NEARBY"]; return; } if (HPdistance(position, [planet position]) + [self collisionRadius] < [planet radius]) { // we have landed. (completely disappeared inside planet) [self landOnPlanet:planet]; return; } double confidenceFactor = [self trackDestination:delta_t:YES]; // turn away from destination if (confidenceFactor >= max_cos && flightSpeed == 0.0) { // We are now turned away from planet. Start landing by flying backward. thrust = 0.0; // stop forward acceleration. if (magnitude2(velocity) < MAX_LANDING_SPEED2) { [self adjustVelocity:vector_multiply_scalar([self forwardVector], -max_thrust * delta_t)]; } } if ([self hasProximityAlertIgnoringTarget:YES]) { [self avoidCollision]; } } - (void) behaviour_formation_form_up:(double) delta_t { // destination for each escort is set in update() from owner. ShipEntity* leadShip = [self owner]; double distance = [self rangeToDestination]; double eta = (distance - desired_range) / flightSpeed; if(eta < 0) eta = 0; if ((eta < 5.0)&&(leadShip)&&(leadShip->isShip)) desired_speed = [leadShip flightSpeed] * (1 + eta * 0.05); else desired_speed = maxFlightSpeed; double last_distance = success_factor; success_factor = distance; // do the actual piloting!! [self trackDestination:delta_t: NO]; eta = eta / 0.51; // 2% safety margin assuming an average of half current speed GLfloat slowdownTime = (thrust > 0.0)? flightSpeed / (thrust) : 4.0; GLfloat minTurnSpeedFactor = 0.05 * max_flight_pitch * max_flight_roll; // faster turning implies higher speeds if ((eta < slowdownTime)&&(flightSpeed > maxFlightSpeed * minTurnSpeedFactor)) desired_speed = flightSpeed * 0.50; // cut speed by 50% to a minimum minTurnSpeedFactor of speed if (distance < last_distance) // improvement { frustration -= 0.25 * delta_t; if (frustration < 0.0) frustration = 0.0; } else { frustration += delta_t; if (frustration > 15.0) { if (!leadShip) [self noteFrustration:@"BEHAVIOUR_FORMATION_FORM_UP"]; // escorts never reach their destination when following leader. else if (distance > 0.5 * scannerRange && !pitching_over) { pitching_over = YES; // Force the ship in a 180 degree turn. Do it here to allow escorts to break out formation for some seconds. } frustration = 0; } } if ([self hasProximityAlertIgnoringTarget:YES]) { [self avoidCollision]; } } - (void) behaviour_fly_to_destination:(double) delta_t { double distance = [self rangeToDestination]; // double desiredRange = (dockingInstructions != nil) ? 1.2 * desired_range : desired_range; // stop a bit earlyer when docking. if (distance < desired_range) // + collision_radius) { // desired range achieved [shipAI message:@"DESIRED_RANGE_ACHIEVED"]; [self doScriptEvent:OOJSID("shipAchievedDesiredRange")]; if(!docking_match_rotation) // IDLE stops rotating while docking { behaviour = BEHAVIOUR_IDLE; desired_speed = 0.0; } frustration = 0.0; } else { double last_distance = success_factor; success_factor = distance; // do the actual piloting!! double confidenceFactor = [self trackDestination:delta_t: NO]; if(confidenceFactor < 0.2) confidenceFactor = 0.2; // don't allow small or negative values. /* 2009-07-19 Eric: Estimated Time of Arrival (eta) should also take the "angle to target" into account (confidenceFactor = cos(angle to target)) and should not fuss about that last meter and use "distance + 1" instead of just "distance". trackDestination already did pitch regulation, use confidence here only for cutting down to high speeds. This should prevent ships crawling to their destination when they try to pull up close to their destination. To prevent ships circling around their target without reaching destination I added a limitation based on turnrate, speed and distance to target. Formula based on satelite orbit: orbitspeed = turnrate (rad/sec) * radius (m) or flightSpeed = max_flight_pitch * 2 Pi * distance Speed must be significant lower when not flying in direction of target (low confidenceFactor) or it can never approach its destination and the ships runs the risk flying in circles around the target. (exclude active escorts) */ GLfloat eta = ((distance + 1) - desired_range) / (0.51 * flightSpeed * confidenceFactor); // 2% safety margin assuming an average of half current speed GLfloat slowdownTime = (thrust > 0.0)? flightSpeed / (thrust) : 4.0; GLfloat minTurnSpeedFactor = 0.05 * max_flight_pitch * max_flight_roll; // faster turning implies higher speeds if (dockingInstructions != nil) { minTurnSpeedFactor /= 10.0; if (minTurnSpeedFactor * maxFlightSpeed > 20.0) { minTurnSpeedFactor /= 10.0; } } if (((eta < slowdownTime)&&(flightSpeed > maxFlightSpeed * minTurnSpeedFactor)) || (flightSpeed > max_flight_pitch * 5 * confidenceFactor * distance)) { desired_speed = flightSpeed * 0.50; // cut speed by 50% to a minimum minTurnSpeedFactor of speed } /* Flight correction block to prevent one possible form of * crashes in late docking process */ if (docking_match_rotation && confidenceFactor >= MAX_COS && dockingInstructions != nil && [dockingInstructions oo_intForKey:@"docking_stage"] >= 7) { // then at this point should be rotating to match the station StationEntity* station_for_docking = (StationEntity*)[self targetStation]; if ((station_for_docking)&&(station_for_docking->isStation)) { float rollMatch = dot_product([station_for_docking portUpVectorForShip:self],[self upVector]); if (rollMatch < MAX_COS && rollMatch > -MAX_COS) { // not matching rotating - stop until corrected desired_speed = 0.1; } else if (desired_speed <= 0.2) { // had previously paused, so return to normal speed desired_speed = [dockingInstructions oo_floatForKey:@"speed"]; } } } if (distance < last_distance) // improvement { frustration -= 0.25 * delta_t; if (frustration < 0.0) frustration = 0.0; } else { frustration += delta_t; if ((frustration > slowdownTime * 10.0 && slowdownTime > 0)||(frustration > 15.0)) // 10x slowdownTime or 15s of frustration { [self noteFrustration:@"BEHAVIOUR_FLY_TO_DESTINATION"]; frustration -= slowdownTime * 5.0; //repeat after another five units of frustration } } } if ([self hasProximityAlertIgnoringTarget:YES]) { [self avoidCollision]; } } - (void) behaviour_fly_from_destination:(double) delta_t { double distance = [self rangeToDestination]; if (distance > desired_range) { // desired range achieved [shipAI message:@"DESIRED_RANGE_ACHIEVED"]; [self doScriptEvent:OOJSID("shipAchievedDesiredRange")]; behaviour = BEHAVIOUR_IDLE; frustration = 0.0; desired_speed = 0.0; } [self trackDestination:delta_t:YES]; if ([self hasProximityAlertIgnoringTarget:YES]) { [self avoidCollision]; } } - (void) behaviour_avoid_collision:(double) delta_t { double distance = [self rangeToDestination]; if (distance > desired_range) { [self resumePostProximityAlert]; } else { ShipEntity* prox_ship = (ShipEntity*)[self proximityAlert]; if (prox_ship) { desired_range = prox_ship->collision_radius * PROXIMITY_AVOID_DISTANCE_FACTOR; _destination = prox_ship->position; } double dq = [self trackDestination:delta_t:YES]; // returns 0 when heading towards prox_ship // Heading towards target with desired_speed > 0, avoids collisions better than setting desired_speed to zero. // (tested with boa class cruiser on collisioncourse with buoy) desired_speed = maxFlightSpeed * (0.5 * dq + 0.5); } } - (void) behaviour_track_as_turret:(double) delta_t { double aim = -2.0; ShipEntity *turret_owner = (ShipEntity *)[self owner]; ShipEntity *turret_target = (ShipEntity *)[turret_owner primaryTarget]; if (turret_owner && turret_target && [turret_owner hasHostileTarget]) { aim = [self ballTrackLeadingTarget:delta_t atTarget:turret_target]; if (aim > -1.0) // potential target { HPVector p = HPvector_subtract([turret_target position], [turret_owner position]); double cr = [turret_owner collisionRadius]; if (aim > .95) { [self fireTurretCannon:HPmagnitude(p) - cr]; } return; } } // can't fire on primary target; track secondary targets instead NSEnumerator *targetEnum = [turret_owner defenseTargetEnumerator]; Entity *target = nil; while ((target = [[targetEnum nextObject] weakRefUnderlyingObject])) { // defense targets cannot be tracked while cloaked if ([target scanClass] == CLASS_NO_DRAW || [(ShipEntity *)target isCloaked] || [target energy] <= 0.0) { [turret_owner removeDefenseTarget:target]; } else { double range = [turret_owner rangeToSecondaryTarget:target]; if (range < weaponRange) { aim = [self ballTrackLeadingTarget:delta_t atTarget:target]; if (aim > -1.0) { // tracking... HPVector p = HPvector_subtract([target position], [turret_owner position]); double cr = [turret_owner collisionRadius]; if (aim > .95) { // fire! [self fireTurretCannon:HPmagnitude(p) - cr]; } return; } // else that target is out of range, try the next priority defense target } else if (range > scannerRange) { [turret_owner removeDefenseTarget:target]; } } } // turrets now don't return to neutral facing if no suitable target // better for shooting at targets that are on edge of fire arc } - (void) behaviour_fly_thru_navpoints:(double) delta_t { int navpoint_plus_index = (next_navpoint_index + 1) % number_of_navpoints; HPVector d1 = navpoints[next_navpoint_index]; // head for this one HPVector d2 = navpoints[navpoint_plus_index]; // but be facing this one HPVector rel = HPvector_between(d1, position); // vector from d1 to position HPVector ref = HPvector_between(d2, d1); // vector from d2 to d1 ref = HPvector_normal(ref); HPVector xp = make_HPvector(ref.y * rel.z - ref.z * rel.y, ref.z * rel.x - ref.x * rel.z, ref.x * rel.y - ref.y * rel.x); GLfloat v0 = 0.0; GLfloat r0 = HPdot_product(rel, ref); // proportion of rel in direction ref // if r0 is negative then we're the wrong side of things GLfloat r1 = HPmagnitude(xp); // distance of position from line BOOL in_cone = (r0 > 0.5 * r1); if (!in_cone) // are we in the approach cone ? r1 = 25.0 * flightSpeed; // aim a few km out! else r1 *= 2.0; GLfloat dist2 = HPmagnitude2(rel); if (dist2 < desired_range * desired_range) { // desired range achieved [self doScriptEvent:OOJSID("shipReachedNavPoint") andReactToAIMessage:@"NAVPOINT_REACHED"]; if (navpoint_plus_index == 0) { [self doScriptEvent:OOJSID("shipReachedEndPoint") andReactToAIMessage:@"ENDPOINT_REACHED"]; behaviour = BEHAVIOUR_IDLE; } next_navpoint_index = navpoint_plus_index; // loop as required } else { double last_success_factor = success_factor; double last_dist2 = last_success_factor; success_factor = dist2; // set destination spline point from r1 and ref _destination = make_HPvector(d1.x + r1 * ref.x, d1.y + r1 * ref.y, d1.z + r1 * ref.z); // do the actual piloting!! // // aim to within 1m GLfloat temp = desired_range; if (in_cone) desired_range = 1.0; else desired_range = 100.0; v0 = [self trackDestination:delta_t: NO]; desired_range = temp; if (dist2 < last_dist2) // improvement { frustration -= 0.25 * delta_t; if (frustration < 0.0) frustration = 0.0; } else { frustration += delta_t; if (frustration > 15.0) // 15s of frustration { [self noteFrustration:@"BEHAVIOUR_FLY_THRU_NAVPOINTS"]; frustration -= 15.0; //repeat after another 15s of frustration } } } GLfloat temp = desired_speed; desired_speed *= v0 * v0; desired_speed = temp; } - (void) behaviour_scripted_ai:(double) delta_t { JSContext *context = OOJSAcquireContext(); jsval rval = JSVAL_VOID; jsval deltaJS = JSVAL_VOID; NSDictionary *result = nil; BOOL OK = JS_NewNumberValue(context, delta_t, &deltaJS); if (OK) { OK = [[self script] callMethod:OOJSID("scriptedAI") inContext:context withArguments:&deltaJS count:1 result:&rval]; } if (!OK) { OOLog(@"ai.error",@"Could not call scriptedAI in ship script of %@, reverting to idle",self); behaviour = BEHAVIOUR_IDLE; OOJSRelinquishContext(context); return; } if (!JSVAL_IS_OBJECT(rval)) { OOLog(@"ai.error",@"Invalid return value of scriptedAI in ship script of %@, reverting to idle",self); behaviour = BEHAVIOUR_IDLE; OOJSRelinquishContext(context); return; } result = OOJSNativeObjectFromJSObject(context, JSVAL_TO_OBJECT(rval)); OOJSRelinquishContext(context); // roll or roll factor if ([result objectForKey:@"stickRollFactor"] != nil) { stick_roll = [result oo_floatForKey:@"stickRollFactor"] * max_flight_roll; } else { stick_roll = [result oo_floatForKey:@"stickRoll"]; } if (stick_roll > max_flight_roll) { stick_roll = max_flight_roll; } else if (stick_roll < -max_flight_roll) { stick_roll = -max_flight_roll; } // pitch or pitch factor if ([result objectForKey:@"stickPitchFactor"] != nil) { stick_pitch = [result oo_floatForKey:@"stickPitchFactor"] * max_flight_pitch; } else { stick_pitch = [result oo_floatForKey:@"stickPitch"]; } if (stick_pitch > max_flight_pitch) { stick_pitch = max_flight_pitch; } else if (stick_pitch < -max_flight_pitch) { stick_pitch = -max_flight_pitch; } // yaw or yaw factor if ([result objectForKey:@"stickYawFactor"] != nil) { stick_yaw = [result oo_floatForKey:@"stickYawFactor"] * max_flight_yaw; } else { stick_yaw = [result oo_floatForKey:@"stickYaw"]; } if (stick_yaw > max_flight_yaw) { stick_yaw = max_flight_yaw; } else if (stick_yaw < -max_flight_yaw) { stick_yaw = -max_flight_yaw; } // apply sticks to current flight profile [self applySticks:delta_t]; // desired speed if ([result objectForKey:@"desiredSpeedFactor"] != nil) { desired_speed = [result oo_floatForKey:@"desiredSpeedFactor"] * maxFlightSpeed; } else { desired_speed = [result oo_floatForKey:@"desiredSpeed"]; } if (desired_speed < 0.0) { desired_speed = 0.0; } // overspeed and injector use is handled by applyThrust if (behaviour == BEHAVIOUR_SCRIPTED_ATTACK_AI) { NSString* chosen_weapon = [result oo_stringForKey:@"chosenWeapon" defaultValue:@"FORWARD"]; double range = [self rangeToPrimaryTarget]; if ([chosen_weapon isEqualToString:@"FORWARD"]) { [self fireMainWeapon:range]; } else if ([chosen_weapon isEqualToString:@"AFT"]) { [self fireAftWeapon:range]; } else if ([chosen_weapon isEqualToString:@"PORT"]) { [self firePortWeapon:range]; } else if ([chosen_weapon isEqualToString:@"STARBOARD"]) { [self fireStarboardWeapon:range]; } } } - (float) reactionTime { return reactionTime; } - (void) setReactionTime: (float) newReactionTime { reactionTime = newReactionTime; } - (HPVector) calculateTargetPosition { Entity *target = [self primaryTarget]; if (target == nil) { return kZeroHPVector; } if (reactionTime <= 0.0) { return [target position]; } double t = [UNIVERSE getTime] - trackingCurveTimes[1]; return HPvector_add(HPvector_add(trackingCurveCoeffs[0], HPvector_multiply_scalar(trackingCurveCoeffs[1],t)), HPvector_multiply_scalar(trackingCurveCoeffs[2],t*t)); } - (void) startTrackingCurve { Entity *target = [self primaryTarget]; if (target == nil) { return; } OOTimeAbsolute now = [UNIVERSE getTime]; trackingCurvePositions[0] = [target position]; trackingCurvePositions[1] = [target position]; trackingCurvePositions[2] = [target position]; trackingCurvePositions[3] = [target position]; trackingCurveTimes[0] = now; trackingCurveTimes[1] = now - reactionTime/3.0; trackingCurveTimes[2] = now - reactionTime*2.0/3.0; trackingCurveTimes[3] = now - reactionTime; [self calculateTrackingCurve]; return; } - (void) updateTrackingCurve { Entity *target = [self primaryTarget]; OOTimeAbsolute now = [UNIVERSE getTime]; if (target == nil || reactionTime <= 0.0 || trackingCurveTimes[0] + reactionTime/3.0 > now) return; trackingCurvePositions[3] = trackingCurvePositions[2]; trackingCurvePositions[2] = trackingCurvePositions[1]; trackingCurvePositions[1] = trackingCurvePositions[0]; if (EXPECT_NOT([target isShip] && [(ShipEntity *)target isCloaked])) { // if target is cloaked, introduce some more inaccuracy // 0.02 seems to be enough to give them slight difficulty on // a straight-line target and real trouble on anything better trackingCurvePositions[0] = HPvector_add([target position],OOHPVectorRandomSpatial([(ShipEntity *)target flightSpeed]*reactionTime*0.02)); } else { trackingCurvePositions[0] = [target position]; } trackingCurveTimes[3] = trackingCurveTimes[2]; trackingCurveTimes[2] = trackingCurveTimes[1]; trackingCurveTimes[1] = trackingCurveTimes[0]; trackingCurveTimes[0] = now; [self calculateTrackingCurve]; return; } - (void) calculateTrackingCurve { if (reactionTime <= 0.0) { trackingCurveCoeffs[0] = trackingCurvePositions[0]; trackingCurveCoeffs[1] = kZeroHPVector; trackingCurveCoeffs[2] = kZeroHPVector; return; } double t1 = trackingCurveTimes[2] - trackingCurveTimes[1], t2 = trackingCurveTimes[3] - trackingCurveTimes[1]; trackingCurveCoeffs[0] = trackingCurvePositions[1]; trackingCurveCoeffs[1] = HPvector_add(HPvector_add( HPvector_multiply_scalar(trackingCurvePositions[1], -(t1+t2)/(t1*t2)), HPvector_multiply_scalar(trackingCurvePositions[2], -t2/(t1*(t1-t2)))), HPvector_multiply_scalar(trackingCurvePositions[3], t1/(t2*(t1-t2)))); trackingCurveCoeffs[2] = HPvector_add(HPvector_add( HPvector_multiply_scalar(trackingCurvePositions[1], 1/(t1*t2)), HPvector_multiply_scalar(trackingCurvePositions[2], 1/(t1*(t1-t2)))), HPvector_multiply_scalar(trackingCurvePositions[3], -1/(t2*(t1-t2)))); return; } - (void) drawImmediate:(bool)immediate translucent:(bool)translucent { if ((no_draw_distance < cam_zero_distance) || // Done redundantly to skip subentities (cloaking_device_active && randf() > 0.10)) { // Don't draw. return; } // Draw self. [super drawImmediate:immediate translucent:translucent]; #ifndef NDEBUG // Draw bounding boxes if we have to before going for the subentities. // TODO: the translucent flag here makes very little sense. Something's wrong with the matrices. if (translucent) [self drawDebugStuff]; else if (gDebugFlags & DEBUG_BOUNDING_BOXES && ![self isSubEntity]) { OODebugDrawBoundingBox([self boundingBox]); OODebugDrawColoredBoundingBox(totalBoundingBox, [OOColor purpleColor]); } #endif // Draw subentities. if (!immediate) // TODO: is this relevant any longer? { // save time by not copying the subentity array if it's empty - CIM if ([self subEntityCount] > 0) { Entity *subEntity = nil; foreach (subEntity, [self subEntities]) { NSAssert3([subEntity owner] == self, @"Subentity ownership broke - %@ should be owned by %@ but is owned by %@.", subEntity, self, [subEntity owner]); [subEntity drawSubEntityImmediate:immediate translucent:translucent]; } } } } #ifndef NDEBUG - (void) drawDebugStuff { // HPVect: imprecise here - needs camera relative if (0 && reportAIMessages) { OODebugDrawPoint(HPVectorToVector(_destination), [OOColor blueColor]); OODebugDrawColoredLine(HPVectorToVector([self position]), HPVectorToVector(_destination), [OOColor colorWithWhite:0.15 alpha:1.0]); Entity *pTarget = [self primaryTarget]; if (pTarget != nil) { OODebugDrawPoint(HPVectorToVector([pTarget position]), [OOColor redColor]); OODebugDrawColoredLine(HPVectorToVector([self position]), HPVectorToVector([pTarget position]), [OOColor colorWithRed:0.2 green:0.0 blue:0.0 alpha:1.0]); } Entity *sTarget = [self targetStation]; if (sTarget != pTarget && [sTarget isStation]) { OODebugDrawPoint(HPVectorToVector([sTarget position]), [OOColor cyanColor]); } Entity *fTarget = [self foundTarget]; if (fTarget != nil && fTarget != pTarget && fTarget != sTarget) { OODebugDrawPoint(HPVectorToVector([fTarget position]), [OOColor magentaColor]); } } } #endif - (void) drawSubEntityImmediate:(bool)immediate translucent:(bool)translucent { OOVerifyOpenGLState(); if (cam_zero_distance > no_draw_distance) // this test provides an opportunity to do simple LoD culling { return; // TOO FAR AWAY } OOGLPushModelView(); // HPVect: need to make camera-relative OOGLTranslateModelView(HPVectorToVector(position)); OOGLMultModelView(rotMatrix); [self drawImmediate:immediate translucent:translucent]; #ifndef NDEBUG if (gDebugFlags & DEBUG_BOUNDING_BOXES) { OODebugDrawBoundingBox([self boundingBox]); } #endif OOGLPopModelView(); OOVerifyOpenGLState(); } static GLfloat cargo_color[4] = { 0.9, 0.9, 0.9, 1.0}; // gray static GLfloat hostile_color[4] = { 1.0, 0.25, 0.0, 1.0}; // red/orange static GLfloat neutral_color[4] = { 1.0, 1.0, 0.0, 1.0}; // yellow static GLfloat friendly_color[4] = { 0.0, 1.0, 0.0, 1.0}; // green static GLfloat missile_color[4] = { 0.0, 1.0, 1.0, 1.0}; // cyan static GLfloat police_color1[4] = { 0.5, 0.0, 1.0, 1.0}; // purpley-blue static GLfloat police_color2[4] = { 1.0, 0.0, 0.5, 1.0}; // purpley-red static GLfloat jammed_color[4] = { 0.0, 0.0, 0.0, 0.0}; // clear black static GLfloat mascem_color1[4] = { 0.3, 0.3, 0.3, 1.0}; // dark gray static GLfloat mascem_color2[4] = { 0.4, 0.1, 0.4, 1.0}; // purple static GLfloat scripted_color[4] = { 0.0, 0.0, 0.0, 0.0}; // to be defined by script - (GLfloat *) scannerDisplayColorForShip:(ShipEntity*)otherShip :(BOOL)isHostile :(BOOL)flash :(OOColor *)scannerDisplayColor1 :(OOColor *)scannerDisplayColor2 :(OOColor *)scannerDisplayColorH1 :(OOColor *)scannerDisplayColorH2 { if (isHostile) { /* if there are any scripted scanner hostile display colours * for the ship, use them - otherwise fall through to the * normal scripted colours, then the scan class colours */ if (scannerDisplayColorH1 || scannerDisplayColorH2) { if (scannerDisplayColorH1 && !scannerDisplayColorH2) { [scannerDisplayColorH1 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]]; } if (!scannerDisplayColorH1 && scannerDisplayColorH2) { [scannerDisplayColorH2 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]]; } if (scannerDisplayColorH1 && scannerDisplayColorH2) { if (flash) [scannerDisplayColorH1 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]]; else [scannerDisplayColorH2 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]]; } return scripted_color; } } // if there are any scripted scanner display colors for the ship, use them if (scannerDisplayColor1 || scannerDisplayColor2) { if (scannerDisplayColor1 && !scannerDisplayColor2) { [scannerDisplayColor1 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]]; } if (!scannerDisplayColor1 && scannerDisplayColor2) { [scannerDisplayColor2 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]]; } if (scannerDisplayColor1 && scannerDisplayColor2) { if (flash) [scannerDisplayColor1 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]]; else [scannerDisplayColor2 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]]; } return scripted_color; } // no scripted scanner display colors defined, proceed as per standard if ([self isJammingScanning]) { if (![otherShip hasMilitaryScannerFilter]) return jammed_color; else { if (flash) return mascem_color1; else { if (isHostile) return hostile_color; else return mascem_color2; } } } switch (scanClass) { case CLASS_ROCK : case CLASS_CARGO : return cargo_color; case CLASS_THARGOID : if (flash) return hostile_color; else return friendly_color; case CLASS_MISSILE : return missile_color; case CLASS_STATION : return friendly_color; case CLASS_BUOY : if (flash) return friendly_color; else return neutral_color; case CLASS_POLICE : case CLASS_MILITARY : if ((isHostile)&&(flash)) return police_color2; else return police_color1; case CLASS_MINE : if (flash) return neutral_color; else return hostile_color; default : if (isHostile) return hostile_color; } return neutral_color; } - (void)setScannerDisplayColor1:(OOColor *)color { DESTROY(scanner_display_color1); if (color == nil) color = [OOColor colorWithDescription:[[self shipInfoDictionary] objectForKey:@"scanner_display_color1"]]; scanner_display_color1 = [color retain]; } - (void)setScannerDisplayColor2:(OOColor *)color { DESTROY(scanner_display_color2); if (color == nil) color = [OOColor colorWithDescription:[[self shipInfoDictionary] objectForKey:@"scanner_display_color2"]]; scanner_display_color2 = [color retain]; } - (OOColor *)scannerDisplayColor1 { return [[scanner_display_color1 retain] autorelease]; } - (OOColor *)scannerDisplayColor2 { return [[scanner_display_color2 retain] autorelease]; } - (void)setScannerDisplayColorHostile1:(OOColor *)color { DESTROY(scanner_display_color_hostile1); if (color == nil) color = [OOColor colorWithDescription:[[self shipInfoDictionary] objectForKey:@"scanner_hostile_display_color1"]]; scanner_display_color_hostile1 = [color retain]; } - (void)setScannerDisplayColorHostile2:(OOColor *)color { DESTROY(scanner_display_color_hostile2); if (color == nil) color = [OOColor colorWithDescription:[[self shipInfoDictionary] objectForKey:@"scanner_hostile_display_color2"]]; scanner_display_color_hostile2 = [color retain]; } - (OOColor *)scannerDisplayColorHostile1 { return [[scanner_display_color_hostile1 retain] autorelease]; } - (OOColor *)scannerDisplayColorHostile2 { return [[scanner_display_color_hostile2 retain] autorelease]; } - (BOOL)isCloaked { return cloaking_device_active; } - (BOOL) cloakPassive { return cloakPassive; } - (void)setCloaked:(BOOL)cloak { if (cloak) [self activateCloakingDevice]; else [self deactivateCloakingDevice]; } - (BOOL)hasAutoCloak { return cloakAutomatic; } - (void)setAutoCloak:(BOOL)automatic { cloakAutomatic = !!automatic; } - (BOOL) isJammingScanning { return ([self hasMilitaryJammer] && military_jammer_active); } - (void) addSubEntity:(Entity *)sub { if (sub == nil) return; if (subEntities == nil) subEntities = [[NSMutableArray alloc] init]; sub->isSubEntity = YES; // Order matters - need consistent state in setOwner:. -- Ahruman 2008-04-20 [subEntities addObject:sub]; [sub setOwner:self]; [self addSubentityToCollisionRadius:sub]; } - (void) setOwner:(Entity *)who_owns_entity { [super setOwner:who_owns_entity]; /* Reset shader binding target so that bind-to-super works. This is necessary since we don't know about the owner in setUpShipFromDictionary:, when the mesh is initially set up. -- Ahruman 2008-04-19 */ if (isSubEntity) { [[self drawable] setBindingTarget:self]; } } - (void) applyThrust:(double) delta_t { GLfloat dt_thrust = SHIP_THRUST_FACTOR * thrust * delta_t; BOOL canBurn = [self hasFuelInjection] && (fuel > MIN_FUEL); BOOL isUsingAfterburner = (canBurn && (flightSpeed > maxFlightSpeed) && (desired_speed >= flightSpeed)); float max_available_speed = maxFlightSpeed; if (canBurn) max_available_speed *= [self afterburnerFactor]; if (thrust) { // If we have Newtonian (non-thrust) velocity, brake it. GLfloat velmag = magnitude(velocity); if (velmag) { GLfloat vscale = fmaxf((velmag - dt_thrust) / velmag, 0.0f); scale_vector(&velocity, vscale); } } if (behaviour == BEHAVIOUR_TUMBLE) return; // check for speed if (desired_speed > max_available_speed) desired_speed = max_available_speed; if (flightSpeed > desired_speed) { [self decrease_flight_speed: dt_thrust]; if (flightSpeed < desired_speed) flightSpeed = desired_speed; } if (flightSpeed < desired_speed) { [self increase_flight_speed: dt_thrust]; if (flightSpeed > desired_speed) flightSpeed = desired_speed; } [self moveForward: delta_t*flightSpeed]; // burn fuel at the appropriate rate if (isUsingAfterburner) // no fuelconsumption on slowdown { fuel_accumulator -= delta_t * afterburner_rate; while (fuel_accumulator < 0.0) { fuel--; fuel_accumulator += 1.0; } } } - (void) orientationChanged { [super orientationChanged]; v_forward = vector_forward_from_quaternion(orientation); v_up = vector_up_from_quaternion(orientation); v_right = vector_right_from_quaternion(orientation); } - (void) applyRoll:(GLfloat) roll1 andClimb:(GLfloat) climb1 { Quaternion q1 = kIdentityQuaternion; if (!roll1 && !climb1 && !hasRotated) return; if (roll1) quaternion_rotate_about_z(&q1, -roll1); if (climb1) quaternion_rotate_about_x(&q1, -climb1); orientation = quaternion_multiply(q1, orientation); [self orientationChanged]; } - (void) applyRoll:(GLfloat) roll1 climb:(GLfloat) climb1 andYaw:(GLfloat) yaw1 { if ((roll1 == 0.0)&&(climb1 == 0.0)&&(yaw1 == 0.0)&&(!hasRotated)) return; Quaternion q1 = kIdentityQuaternion; if (roll1) quaternion_rotate_about_z(&q1, -roll1); if (climb1) quaternion_rotate_about_x(&q1, -climb1); if (yaw1) quaternion_rotate_about_y(&q1, -yaw1); orientation = quaternion_multiply(q1, orientation); [self orientationChanged]; } - (void) applyAttitudeChanges:(double) delta_t { [self applyRoll:flightRoll*delta_t climb:flightPitch*delta_t andYaw:flightYaw*delta_t]; } - (void) avoidCollision { if (scanClass == CLASS_MISSILE) return; // missiles are SUPPOSED to collide! ShipEntity* prox_ship = (ShipEntity*)[self proximityAlert]; if (prox_ship) { if (previousCondition) { [previousCondition release]; previousCondition = nil; } previousCondition = [[NSMutableDictionary dictionaryWithCapacity:5] retain]; [previousCondition oo_setInteger:behaviour forKey:@"behaviour"]; if ([self primaryTarget] != nil) { // must use the weak ref here to prevent potential over-retention [previousCondition setObject:[[self primaryTarget] weakSelf] forKey:@"primaryTarget"]; } [previousCondition oo_setFloat:desired_range forKey:@"desired_range"]; [previousCondition oo_setFloat:desired_speed forKey:@"desired_speed"]; [previousCondition oo_setHPVector:_destination forKey:@"destination"]; _destination = [prox_ship position]; _destination = OOHPVectorInterpolate(position, [prox_ship position], 0.5); // point between us and them desired_range = prox_ship->collision_radius * PROXIMITY_AVOID_DISTANCE_FACTOR; behaviour = BEHAVIOUR_AVOID_COLLISION; pitching_over = YES; } } - (void) resumePostProximityAlert { if (!previousCondition) return; behaviour = [previousCondition oo_intForKey:@"behaviour"]; [_primaryTarget release]; _primaryTarget = [[previousCondition objectForKey:@"primaryTarget"] weakRetain]; [self startTrackingCurve]; desired_range = [previousCondition oo_floatForKey:@"desired_range"]; desired_speed = [previousCondition oo_floatForKey:@"desired_speed"]; _destination = [previousCondition oo_hpvectorForKey:@"destination"]; [previousCondition release]; previousCondition = nil; frustration = 0.0; DESTROY(_proximityAlert); //[shipAI message:@"RESTART_DOCKING"]; // if docking, start over, other AIs will ignore this message } - (double) messageTime { return messageTime; } - (void) setMessageTime:(double) value { messageTime = value; } - (OOShipGroup *) group { return _group; } - (void) setGroup:(OOShipGroup *)group { if (group != _group) { if (_escortGroup != _group) { if (self == [_group leader]) [_group setLeader:nil]; [_group removeShip:self]; } [_group release]; [group addShip:self]; _group = [group retain]; [[group leader] updateEscortFormation]; } } - (OOShipGroup *) escortGroup { if (_escortGroup == nil) { _escortGroup = [[OOShipGroup alloc] initWithName:@"escort group"]; [_escortGroup setLeader:self]; } return _escortGroup; } - (void) setEscortGroup:(OOShipGroup *)group { if (group != _escortGroup) { [_escortGroup release]; _escortGroup = [group retain]; [group setLeader:self]; // A ship is always leader of its own escort group. [self updateEscortFormation]; } } #ifndef NDEBUG - (OOShipGroup *) rawEscortGroup { return _escortGroup; } #endif - (OOShipGroup *) stationGroup { if (_group == nil) { _group = [[OOShipGroup alloc] initWithName:@"station group"]; [_group setLeader:self]; } return _group; } - (BOOL) hasEscorts { if (_escortGroup == nil) return NO; return [_escortGroup count] > 1; // If only one member, it's self. } - (NSEnumerator *) escortEnumerator { if (_escortGroup == nil) return [[NSArray array] objectEnumerator]; return [[_escortGroup mutationSafeEnumerator] ooExcludingObject:self]; } - (NSArray *) escortArray { if (_escortGroup == nil) return [NSArray array]; return [[self escortEnumerator] allObjects]; } - (uint8_t) escortCount { if (_escortGroup == nil) return 0; return [_escortGroup count] - 1; } - (uint8_t) pendingEscortCount { return _pendingEscortCount; } - (void) setPendingEscortCount:(uint8_t)count { _pendingEscortCount = MIN(count, _maxEscortCount); } - (uint8_t) maxEscortCount { return _maxEscortCount; } - (void) setMaxEscortCount:(uint8_t)newCount { _maxEscortCount = newCount; } - (Entity*) proximityAlert { Entity* prox = [_proximityAlert weakRefUnderlyingObject]; if (prox == nil) { DESTROY(_proximityAlert); } return prox; } - (void) setProximityAlert:(ShipEntity*) other { if (!other) { DESTROY(_proximityAlert); return; } if ([other mass] < 2000) // we are not alerted by small objects. (a cargopod has a mass of about 1000) return; if (isStation) // stations don't worry about colliding with things return; /* Ignore station collision warnings if launching or docking */ if ((other->isStation) && ([self status] == STATUS_LAUNCHING || dockingInstructions != nil)) { return; } if (!crew) // Ships without pilot (cargo, rocks, missiles, buoys etc) will not get alarmed. (escape-pods have pilots) return; // check vectors Vector vdiff = HPVectorToVector(HPvector_between(position, other->position)); GLfloat d_forward = dot_product(vdiff, v_forward); GLfloat d_up = dot_product(vdiff, v_up); GLfloat d_right = dot_product(vdiff, v_right); if ((d_forward > 0.0)&&(flightSpeed > 0.0)) // it's ahead of us and we're moving forward d_forward *= 0.25 * maxFlightSpeed / flightSpeed; // extend the collision zone forward up to 400% double d2 = d_forward * d_forward + d_up * d_up + d_right * d_right; double cr2 = collision_radius * 2.0 + other->collision_radius; cr2 *= cr2; // check with twice the combined radius if (d2 > cr2) // we're okay return; if (behaviour == BEHAVIOUR_AVOID_COLLISION) // already avoiding something { ShipEntity* prox = (ShipEntity*)[self proximityAlert]; if ((prox)&&(prox != other)) { // check which subtends the greatest angle GLfloat sa_prox = prox->collision_radius * prox->collision_radius / HPdistance2(position, prox->position); GLfloat sa_other = other->collision_radius * other->collision_radius / HPdistance2(position, other->position); if (sa_prox < sa_other) return; } } [_proximityAlert release]; _proximityAlert = [other weakRetain]; } - (NSString *) name { return name; } - (NSString *) shipUniqueName { return shipUniqueName; } - (NSString *) shipClassName { return shipClassName; } - (NSString *) displayName { if (displayName == nil || [displayName length] == 0) { if ([shipUniqueName length] == 0) { if (shipClassName == nil) { return name; } else { return shipClassName; } } else { if (shipClassName == nil) { return [NSString stringWithFormat:@"%@: %@",name,shipUniqueName]; } else { return [NSString stringWithFormat:@"%@: %@",shipClassName,shipUniqueName]; } } } return displayName; } // needed so that scan_description = scan_description doesn't have odd effects - (NSString *)scanDescriptionForScripting { return scan_description; } - (NSString *)scanDescription { if (scan_description != nil) { return scan_description; } else { NSString *desc = nil; switch ([self scanClass]) { case CLASS_NEUTRAL: { int legal = [self legalStatus]; int legal_i = 0; if (legal > 0) { legal_i = (legal <= 50) ? 1 : 2; } desc = [[[UNIVERSE descriptions] oo_arrayForKey:@"legal_status"] oo_stringAtIndex:legal_i]; } break; case CLASS_THARGOID: desc = DESC(@"legal-desc-alien"); break; case CLASS_POLICE: desc = DESC(@"legal-desc-system-vessel"); break; case CLASS_MILITARY: desc = DESC(@"legal-desc-military-vessel"); break; default: break; } return desc; } } - (void) setName:(NSString *)inName { [name release]; name = [inName copy]; } - (void) setShipUniqueName:(NSString *)inName { [shipUniqueName release]; shipUniqueName = [inName copy]; } - (void) setShipClassName:(NSString *)inName { [shipClassName release]; shipClassName = [inName copy]; } - (void) setDisplayName:(NSString *)inName { [displayName release]; displayName = [inName copy]; } - (void) setScanDescription:(NSString *)inName { [scan_description release]; scan_description = [inName copy]; } - (NSString *) identFromShip:(ShipEntity*) otherShip { if ([self isJammingScanning] && ![otherShip hasMilitaryScannerFilter]) { return DESC(@"unknown-target"); } return [self displayName]; } - (BOOL) hasRole:(NSString *)role { return [roleSet hasRole:role] || [role isEqual:primaryRole] || [role isEqual:[self shipDataKeyAutoRole]]; } - (OORoleSet *)roleSet { if (roleSet == nil) roleSet = [[OORoleSet alloc] initWithRoleString:primaryRole]; return [[roleSet roleSetWithAddedRoleIfNotSet:primaryRole probability:1.0] roleSetWithAddedRoleIfNotSet:[self shipDataKeyAutoRole] probability:1.0]; } - (void) addRole:(NSString *)role { [self addRole:role withProbability:0.0f]; } - (void) addRole:(NSString *)role withProbability:(float)probability { if (![self hasRole:role]) { OORoleSet *newRoles = nil; if (roleSet != nil) newRoles = [roleSet roleSetWithAddedRole:role probability:probability]; else newRoles = [OORoleSet roleSetWithRole:role probability:probability]; if (newRoles != nil) { [roleSet release]; roleSet = [newRoles retain]; } } } - (void) removeRole:(NSString *)role { if ([self hasRole:role]) { OORoleSet *newRoles = [roleSet roleSetWithRemovedRole:role]; if (newRoles != nil) { [roleSet release]; roleSet = [newRoles retain]; } } } - (NSString *)primaryRole { if (primaryRole == nil) { primaryRole = [roleSet anyRole]; if (primaryRole == nil) primaryRole = @"trader"; [primaryRole retain]; OOLog(@"ship.noPrimaryRole", @"%@ had no primary role, randomly selected \"%@\".", [self name], primaryRole); } return primaryRole; } // Exposed to AI. - (void)setPrimaryRole:(NSString *)role { if (![role isEqual:primaryRole]) { [primaryRole release]; primaryRole = [role copy]; } } - (BOOL)hasPrimaryRole:(NSString *)role { return [[self primaryRole] isEqual:role]; } - (BOOL)isPolice { //bounty hunters have a police role, but are not police, so we must test by scan class, not by role return [self scanClass] == CLASS_POLICE; } - (BOOL)isThargoid { return [self scanClass] == CLASS_THARGOID; } - (BOOL)isTrader { return [UNIVERSE role:[self primaryRole] isInCategory:@"oolite-trader"]; } - (BOOL)isPirate { return [UNIVERSE role:[self primaryRole] isInCategory:@"oolite-pirate"]; } - (BOOL)isMissile { return ([[self primaryRole] hasSuffix:@"MISSILE"] || [self hasPrimaryRole:@"missile"]); } - (BOOL)isMine { return [[self primaryRole] hasSuffix:@"MINE"]; } - (BOOL)isWeapon { return [self isMissile] || [self isMine]; } - (BOOL)isEscort { return [UNIVERSE role:[self primaryRole] isInCategory:@"oolite-escort"]; } - (BOOL)isShuttle { return [UNIVERSE role:[self primaryRole] isInCategory:@"oolite-shuttle"]; } - (BOOL)isTurret { return behaviour == BEHAVIOUR_TRACK_AS_TURRET; } - (BOOL)isPirateVictim { return [UNIVERSE roleIsPirateVictim:[self primaryRole]]; } - (BOOL)isExplicitlyUnpiloted { return _explicitlyUnpiloted; } - (BOOL)isUnpiloted { return [self isExplicitlyUnpiloted] || [self isHulk] || [self scanClass] == CLASS_ROCK || [self scanClass] == CLASS_CARGO; } static BOOL IsBehaviourHostile(OOBehaviour behaviour) { switch (behaviour) { case BEHAVIOUR_ATTACK_TARGET: case BEHAVIOUR_ATTACK_FLY_TO_TARGET: case BEHAVIOUR_ATTACK_FLY_FROM_TARGET: case BEHAVIOUR_RUNNING_DEFENSE: case BEHAVIOUR_FLEE_TARGET: case BEHAVIOUR_ATTACK_BREAK_OFF_TARGET: case BEHAVIOUR_ATTACK_SLOW_DOGFIGHT: case BEHAVIOUR_EVASIVE_ACTION: case BEHAVIOUR_FLEE_EVASIVE_ACTION: case BEHAVIOUR_ATTACK_FLY_TO_TARGET_SIX: // case BEHAVIOUR_ATTACK_MINING_TARGET: case BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE: case BEHAVIOUR_ATTACK_BROADSIDE: case BEHAVIOUR_ATTACK_BROADSIDE_LEFT: case BEHAVIOUR_ATTACK_BROADSIDE_RIGHT: case BEHAVIOUR_CLOSE_TO_BROADSIDE_RANGE: case BEHAVIOUR_CLOSE_WITH_TARGET: case BEHAVIOUR_ATTACK_SNIPER: case BEHAVIOUR_SCRIPTED_ATTACK_AI: return YES; default: return NO; } return 100 < behaviour && behaviour < 120; } // Exposed to shaders. - (BOOL) hasHostileTarget { Entity *t = [self primaryTarget]; if (t == nil || ![t isShip]) { return NO; } if ([self isMissile]) { return YES; // missiles are always fired against a hostile target } if ((behaviour == BEHAVIOUR_AVOID_COLLISION)&&(previousCondition)) { int old_behaviour = [previousCondition oo_intForKey:@"behaviour"]; return IsBehaviourHostile(old_behaviour); } return IsBehaviourHostile(behaviour); } - (BOOL) isHostileTo:(Entity *)entity { return ([self hasHostileTarget] && [self primaryTarget] == entity); } - (GLfloat) weaponRange { return weaponRange; } - (void) setWeaponRange: (GLfloat) value { weaponRange = value; } - (void) setWeaponDataFromType: (OOWeaponType) weapon_type { weaponRange = getWeaponRangeFromType(weapon_type); weapon_energy_use = [weapon_type weaponEnergyUse]; weapon_recharge_rate = [weapon_type weaponRechargeRate]; weapon_shot_temperature = [weapon_type weaponShotTemperature]; weapon_damage = [weapon_type weaponDamage]; if (default_laser_color == nil) { OOColor *wcol = [weapon_type weaponColor]; if (wcol != nil) { [self setLaserColor:wcol]; } } } - (float) energyRechargeRate { return energy_recharge_rate; } - (void) setEnergyRechargeRate:(GLfloat)new { energy_recharge_rate = new; } - (float) weaponRechargeRate { return weapon_recharge_rate; } - (void) setWeaponRechargeRate:(float)value { weapon_recharge_rate = value; } - (void) setWeaponEnergy:(float)value { weapon_damage = value; } - (OOWeaponFacing) currentWeaponFacing { return currentWeaponFacing; } - (GLfloat) scannerRange { return scannerRange; } - (void) setScannerRange: (GLfloat) value { scannerRange = value; } - (Vector) reference { return reference; } - (void) setReference:(Vector) v { reference = v; } - (BOOL) reportAIMessages { return reportAIMessages; } - (void) setReportAIMessages:(BOOL) yn { reportAIMessages = yn; } - (void) transitionToAegisNone { if (!suppressAegisMessages && aegis_status != AEGIS_NONE) { Entity *lastAegisLock = [self lastAegisLock]; if (lastAegisLock != nil) { [self doScriptEvent:OOJSID("shipExitedPlanetaryVicinity") withArgument:lastAegisLock]; if (lastAegisLock == [UNIVERSE sun]) { [shipAI message:@"AWAY_FROM_SUN"]; } else { [shipAI message:@"AWAY_FROM_PLANET"]; } } if (aegis_status != AEGIS_CLOSE_TO_ANY_PLANET) { [shipAI message:@"AEGIS_NONE"]; } } aegis_status = AEGIS_NONE; } static float SurfaceDistanceSqaredV(HPVector reference, Entity *stellar) { float centerDistance = HPmagnitude2(HPvector_subtract([stellar position], reference)); float r = [stellar radius]; /* 1.35: empirical value used to help determine proximity when non-nested planets are close to each other */ return centerDistance - 1.35 * r * r; } static float SurfaceDistanceSqared(Entity *reference, Entity *stellar) { return SurfaceDistanceSqaredV([reference position], stellar); } NSComparisonResult ComparePlanetsBySurfaceDistance(id i1, id i2, void* context) { HPVector p = [(ShipEntity*) context position]; OOPlanetEntity* e1 = i1; OOPlanetEntity* e2 = i2; float p1 = SurfaceDistanceSqaredV(p, e1); float p2 = SurfaceDistanceSqaredV(p, e2); if (p1 < p2) return NSOrderedAscending; if (p1 > p2) return NSOrderedDescending; return NSOrderedSame; } - (OOPlanetEntity *) findNearestPlanet { /* Performance note: this method is called every frame by every ship, and has a significant profiler presence. -- Ahruman 2012-09-13 */ OOPlanetEntity *planet = nil, *bestPlanet = nil; float bestRange = INFINITY; HPVector myPosition = [self position]; // valgrind complains about this line here. Might be compiler/GNUstep bug? // should we go back to a traditional enumerator? - CIM // similar complaints about the other foreach() in this file foreach (planet, [UNIVERSE planets]) { // Ignore miniature planets. if ([planet planetType] == STELLAR_TYPE_MINIATURE) continue; float range = SurfaceDistanceSqaredV(myPosition, planet); if (range < bestRange) { bestPlanet = planet; bestRange = range; } } return bestPlanet; } - (Entity *) findNearestStellarBody { Entity *match = [self findNearestPlanet]; OOSunEntity *sun = [UNIVERSE sun]; if (sun != nil) { if (match == nil || SurfaceDistanceSqared(self, sun) < SurfaceDistanceSqared(self, match)) { match = sun; } } return match; } - (OOPlanetEntity *) findNearestPlanetExcludingMoons { OOPlanetEntity *result = nil; OOPlanetEntity *planet = nil; NSArray *bodies = nil; NSArray *planets = nil; unsigned i; bodies = [UNIVERSE planets]; planets = [NSMutableArray arrayWithCapacity:[bodies count]]; for (i=0; i < [bodies count]; i++) { planet = [bodies objectAtIndex:i]; if([planet planetType] == STELLAR_TYPE_NORMAL_PLANET) planets = [planets arrayByAddingObject:planet]; } if ([planets count] == 0) return nil; planets = [planets sortedArrayUsingFunction:ComparePlanetsBySurfaceDistance context:self]; result = [planets objectAtIndex:0]; return result; } - (OOAegisStatus) checkForAegis { Entity *nearest = [self findNearestStellarBody]; BOOL sunGoneNova = [[UNIVERSE sun] goneNova]; if (nearest == nil) { if (aegis_status != AEGIS_NONE) { // Planet disappeared! [self transitionToAegisNone]; } return AEGIS_NONE; } // check planet float cr = [nearest radius]; float cr2 = cr * cr; OOAegisStatus result = AEGIS_NONE; float d2 = HPmagnitude2(HPvector_subtract([nearest position], [self position])); // not scannerRange: aegis shouldn't depend on that float sd2 = SCANNER_MAX_RANGE2 * 10.0f; // check if nearing a surface unsigned wasNearPlanetSurface = isNearPlanetSurface; // isNearPlanetSurface is a bit flag, not an actual BOOL isNearPlanetSurface = (d2 - cr2) < (250000.0f + 1000.0f * cr); //less than 500m from the surface: (a+b)*(a+b) = a*a+b*b +2*a*b if (EXPECT_NOT((wasNearPlanetSurface != isNearPlanetSurface) && !suppressAegisMessages)) { if (isNearPlanetSurface) { [self doScriptEvent:OOJSID("shipApproachingPlanetSurface") withArgument:nearest]; [shipAI reactToMessage:@"APPROACHING_SURFACE" context:@"flight update"]; } else { [self doScriptEvent:OOJSID("shipLeavingPlanetSurface") withArgument:nearest]; [shipAI reactToMessage:@"LEAVING_SURFACE" context:@"flight update"]; } } // being close to the station takes precedence over planets StationEntity *the_station = [UNIVERSE station]; if (the_station) { sd2 = HPmagnitude2(HPvector_subtract([the_station position], [self position])); } // again, notional scanner range is intentional if (sd2 < SCANNER_MAX_RANGE2 * 4.0f) // double scanner range { result = AEGIS_IN_DOCKING_RANGE; } else if (EXPECT_NOT(isNearPlanetSurface || d2 < cr2 * 9.0f)) // to 3x radius of any planet/moon - or 500m of tiny ones, { result = AEGIS_CLOSE_TO_ANY_PLANET; if (EXPECT((OOPlanetEntity *)nearest == [UNIVERSE planet])) { result = AEGIS_CLOSE_TO_MAIN_PLANET; } } // need to do this check separately from above case to avoid oddity where // main planet and small moon are at just the wrong distance. - CIM if (result != AEGIS_CLOSE_TO_MAIN_PLANET && result != AEGIS_IN_DOCKING_RANGE && !sunGoneNova) { // are we also close to the main planet? OOPlanetEntity *mainPlanet = [UNIVERSE planet]; d2 = HPmagnitude2(HPvector_subtract([mainPlanet position], [self position])); cr2 = [mainPlanet radius]; cr2 *= cr2; if (d2 < cr2 * 9.0f) { nearest = mainPlanet; result = AEGIS_CLOSE_TO_MAIN_PLANET; } } /* Rewrote aegis stuff and tested it against redux.oxp that adds multiple planets and moons. Made sure AI scripts can differentiate between MAIN and NON-MAIN planets so they can decide if they can dock at the systemStation or just any station. Added sun detection so route2Patrol can turn before they heat up in the sun. -- Eric 2009-07-11 More rewriting of the aegis stuff, it's now a bit faster and works properly when moving from one secondary planet/moon vicinity to another one. -- Kaks 20120917 */ if (EXPECT(!suppressAegisMessages)) { // script/AI messages on change in status if (EXPECT_NOT(aegis_status == AEGIS_IN_DOCKING_RANGE && result != aegis_status)) { [self doScriptEvent:OOJSID("shipExitedStationAegis") withArgument:the_station]; [shipAI message:@"AEGIS_LEAVING_DOCKING_RANGE"]; } if (EXPECT_NOT(result == AEGIS_IN_DOCKING_RANGE && aegis_status != result)) { [self doScriptEvent:OOJSID("shipEnteredStationAegis") withArgument:the_station]; [shipAI message:@"AEGIS_IN_DOCKING_RANGE"]; if([self lastAegisLock] == nil && !sunGoneNova) // With small main planets the station aegis can come before planet aegis { [self doScriptEvent:OOJSID("shipEnteredPlanetaryVicinity") withArgument:[UNIVERSE planet]]; [self setLastAegisLock:[UNIVERSE planet]]; } } else if (EXPECT_NOT(result == AEGIS_NONE && aegis_status != result)) { if([self lastAegisLock] == nil && !sunGoneNova) { [self setLastAegisLock:[UNIVERSE planet]]; // in case of a first launch from a near-planet station. } [self transitionToAegisNone]; } // approaching.. else if (EXPECT_NOT((result == AEGIS_CLOSE_TO_ANY_PLANET || result == AEGIS_CLOSE_TO_MAIN_PLANET) && [self lastAegisLock] != nearest)) { if(aegis_status != AEGIS_NONE && [self lastAegisLock] != nil) // we were close to another stellar body { [self doScriptEvent:OOJSID("shipExitedPlanetaryVicinity") withArgument:[self lastAegisLock]]; [shipAI message:@"AWAY_FROM_PLANET"]; // fires for suns, planets and moons. } [self doScriptEvent:OOJSID("shipEnteredPlanetaryVicinity") withArgument:nearest]; [self setLastAegisLock:nearest]; if (EXPECT_NOT([nearest isSun])) { [shipAI message:@"CLOSE_TO_SUN"]; } else { [shipAI message:@"CLOSE_TO_PLANET"]; if (EXPECT(result == AEGIS_CLOSE_TO_MAIN_PLANET)) { // It's been years since 1.71 - it should be safe enough to comment out the line below for 1.77/1.78 -- Kaks 20120917 //[shipAI message:@"AEGIS_CLOSE_TO_PLANET"]; // fires only for main planets, kept for compatibility with pre-1.72 AI plists. [shipAI message:@"AEGIS_CLOSE_TO_MAIN_PLANET"]; // fires only for main planet. } else if (EXPECT_NOT([nearest planetType] == STELLAR_TYPE_MOON)) { [shipAI message:@"CLOSE_TO_MOON"]; } else { [shipAI message:@"CLOSE_TO_SECONDARY_PLANET"]; } } } } if (result == AEGIS_NONE) { [self setLastAegisLock:nil]; } aegis_status = result; // put this here return result; } - (void) forceAegisCheck { _nextAegisCheck = -1.0f; } - (BOOL) withinStationAegis { return aegis_status == AEGIS_IN_DOCKING_RANGE; } - (Entity *) lastAegisLock { Entity *stellar = [_lastAegisLock weakRefUnderlyingObject]; if (stellar == nil) { [_lastAegisLock release]; _lastAegisLock = nil; } return stellar; } - (void) setLastAegisLock:(Entity *)lastAegisLock { [_lastAegisLock release]; _lastAegisLock = [lastAegisLock weakRetain]; } - (OOSystemID) homeSystem { return home_system; } - (OOSystemID) destinationSystem { return destination_system; } - (void) setHomeSystem:(OOSystemID)s { home_system = s; } - (void) setDestinationSystem:(OOSystemID)s { destination_system = s; } - (void) setStatus:(OOEntityStatus) stat { if ([self status] == stat) return; [super setStatus:stat]; if (stat == STATUS_LAUNCHING) { launch_time = [UNIVERSE getTime]; } } - (void) setLaunchDelay:(double)delay { launch_delay = delay; } - (NSArray *) crew { return crew; } - (void) setCrew:(NSArray *)crewArray { if ([self isExplicitlyUnpiloted]) { //unpiloted ships cannot have crew // but may have crew before isExplicitlyUnpiloted set, so force *that* to clear too [crew autorelease]; crew = nil; return; } //do not set to hulk here when crew is nil (or 0). Some things like missiles have no crew. [crew autorelease]; crew = [crewArray copy]; } - (void) setSingleCrewWithRole:(NSString *)crewRole { if (![self isUnpiloted]) { OOCharacter *crewMember = [OOCharacter randomCharacterWithRole:crewRole andOriginalSystem:[self homeSystem]]; [self setCrew:[NSArray arrayWithObject:crewMember]]; } } - (NSArray *) crewForScripting { if (crew == nil) { return nil; } OOCharacter *crewMember = nil; NSMutableArray *result = [NSMutableArray arrayWithCapacity:4]; foreach (crewMember, crew) { NSDictionary *crewDict = [crewMember infoForScripting]; [result addObject:crewDict]; } return result; } - (void) setStateMachine:(NSString *)smName { [self setAITo:smName]; } - (void) setAI:(AI *)ai { [ai retain]; if (shipAI) { [shipAI clearAllData]; [shipAI autorelease]; } shipAI = ai; } - (AI *) getAI { return shipAI; } - (BOOL) hasAutoAI { return [[self shipInfoDictionary] oo_fuzzyBooleanForKey:@"auto_ai" defaultValue:YES]; } - (BOOL) hasNewAI { return [[[self getAI] name] isEqualToString:@"nullAI.plist"]; } - (BOOL) hasAutoWeapons { return [[self shipInfoDictionary] oo_fuzzyBooleanForKey:@"auto_weapons" defaultValue:NO]; } - (void) setShipScript:(NSString *)script_name { NSMutableDictionary *properties = nil; NSArray *actions = nil; properties = [NSMutableDictionary dictionary]; [properties setObject:self forKey:@"ship"]; [script autorelease]; script = [OOScript jsScriptFromFileNamed:script_name properties:properties]; if (script == nil) { actions = [shipinfoDictionary oo_arrayForKey:@"launch_actions"]; if (actions) { OOStandardsDeprecated([NSString stringWithFormat:@"The launch_actions ship key is deprecated on %@.",[self displayName]]); if (!OOEnforceStandards()) { [properties setObject:actions forKey:@"legacy_launchActions"]; } } actions = [shipinfoDictionary oo_arrayForKey:@"script_actions"]; if (actions) { OOStandardsDeprecated([NSString stringWithFormat:@"The script_actions ship key is deprecated on %@.",[self displayName]]); if (!OOEnforceStandards()) { [properties setObject:actions forKey:@"legacy_scriptActions"]; } } actions = [shipinfoDictionary oo_arrayForKey:@"death_actions"]; if (actions) { OOStandardsDeprecated([NSString stringWithFormat:@"The death_actions ship key is deprecated on %@.",[self displayName]]); if (!OOEnforceStandards()) { [properties setObject:actions forKey:@"legacy_deathActions"]; } } actions = [shipinfoDictionary oo_arrayForKey:@"setup_actions"]; if (actions) { OOStandardsDeprecated([NSString stringWithFormat:@"The setup_actions ship key is deprecated on %@.",[self displayName]]); if (!OOEnforceStandards()) { [properties setObject:actions forKey:@"legacy_setupActions"]; } } script = [OOScript jsScriptFromFileNamed:@"oolite-default-ship-script.js" properties:properties]; } [script retain]; } - (double)frustration { return frustration; } - (OOFuelQuantity) fuel { return fuel; } - (void) setFuel:(OOFuelQuantity) amount { if (amount > [self fuelCapacity]) amount = [self fuelCapacity]; fuel = amount; } - (OOFuelQuantity) fuelCapacity { // FIXME: shipdata.plist can allow greater fuel quantities (without extending hyperspace range). Need some consistency here. return PLAYER_MAX_FUEL; } - (GLfloat) fuelChargeRate { GLfloat rate = 1.0; // Standard (& strict play) charge rate. #if MASS_DEPENDENT_FUEL_PRICES if (EXPECT(PLAYER != nil && mass> 0 && mass != [PLAYER baseMass])) { rate = calcFuelChargeRate(mass); } OOLog(@"fuelPrices", @"\"%@\" fuel charge rate: %.2f (mass ratio: %.2f/%.2f)", [self shipDataKey], rate, mass, [PLAYER baseMass]); #endif return rate; } - (void) applySticks:(double)delta_t { double rate1 = 2.0 * delta_t; //roll double rate2 = 4.0 * delta_t; //pitch double rate3 = 4.0 * delta_t; //yaw if (((stick_roll > 0.0)&&(flightRoll < 0.0))||((stick_roll < 0.0)&&(flightRoll > 0.0))) rate1 *= 4.0; // much faster correction if (((stick_pitch > 0.0)&&(flightPitch < 0.0))||((stick_pitch < 0.0)&&(flightPitch > 0.0))) rate2 *= 4.0; // much faster correction if (((stick_yaw > 0.0)&&(flightYaw < 0.0))||((stick_yaw < 0.0)&&(flightYaw > 0.0))) rate3 *= 4.0; // much faster correction if (accuracy >= COMBAT_AI_TRACKS_CLOSER) { if (stick_roll == 0.0) rate1 *= 2.0; // faster correction if (stick_pitch == 0.0) rate2 *= 2.0; // faster correction if (stick_yaw == 0.0) rate3 *= 2.0; // faster correction } // apply stick movement limits if (flightRoll < stick_roll - rate1) { flightRoll = flightRoll + rate1; } else if (flightRoll > stick_roll + rate1) { flightRoll = flightRoll - rate1; } else { flightRoll = stick_roll; } if (flightPitch < stick_pitch - rate2) { flightPitch = flightPitch + rate2; } else if (flightPitch > stick_pitch + rate2) { flightPitch = flightPitch - rate2; } else { flightPitch = stick_pitch; } if (flightYaw < stick_yaw - rate3) { flightYaw = flightYaw + rate3; } else if (flightYaw > stick_yaw + rate3) { flightYaw = flightYaw - rate3; } else { flightYaw = stick_yaw; } } - (void) setRoll:(double) amount { flightRoll = amount * M_PI / 2.0; } - (void) setRawRoll:(double) amount { flightRoll = amount; } - (void) setPitch:(double) amount { flightPitch = amount * M_PI / 2.0; } - (void) setYaw:(double) amount { flightYaw = amount * M_PI / 2.0; } - (void) setThrust:(double) amount { thrust = amount; } - (void) setThrustForDemo:(float) factor { flightSpeed = factor * maxFlightSpeed; } - (void) setBounty:(OOCreditsQuantity) amount { [self setBounty:amount withReason:kOOLegalStatusReasonUnknown]; } - (void) setBounty:(OOCreditsQuantity) amount withReason:(OOLegalStatusReason)reason { if ([self isSubEntity]) { [[self parentEntity] setBounty:amount withReason:reason]; } else { if ((scanClass == CLASS_THARGOID || scanClass == CLASS_STATION) && reason != kOOLegalStatusReasonSetup && reason != kOOLegalStatusReasonByScript) { return; // no standard bounties for Thargoids / Stations } if (scanClass == CLASS_POLICE && amount != 0) { return; // police never have bounties } NSString* nReason = OOStringFromLegalStatusReason(reason); [self setBounty:amount withReasonAsString:nReason]; } } - (void) setBounty:(OOCreditsQuantity) amount withReasonAsString:(NSString*)reason { if ([self isSubEntity]) { [[self parentEntity] setBounty:amount withReasonAsString:reason]; } else { JSContext *context = OOJSAcquireContext(); jsval amountVal = JSVAL_VOID; JS_NewNumberValue(context, (int)amount-(int)bounty, &amountVal); bounty = amount; // can't set the new bounty until the size of the change is known jsval reasonVal = OOJSValueFromNativeObject(context,reason); ShipScriptEvent(context, self, "shipBountyChanged", amountVal, reasonVal); OOJSRelinquishContext(context); } } - (OOCreditsQuantity) bounty { if ([self isSubEntity]) { return [[self parentEntity] bounty]; } else { return bounty; } } - (int) legalStatus { if (scanClass == CLASS_THARGOID) return 5 * collision_radius; if (scanClass == CLASS_ROCK) return 0; return (int)[self bounty]; } - (void) setCommodity:(OOCommodityType)co_type andAmount:(OOCargoQuantity)co_amount { if (co_type != nil) { /* The tmp variable is needed as scoopUp can cause the method * to be passed a reference to self.commodity_type, so DESTROY * then copying the parameter segfaults */ NSString *tmp = [co_type copy]; DESTROY(commodity_type); commodity_type = tmp; commodity_amount = co_amount; } } - (void) setCommodityForPod:(OOCommodityType)co_type andAmount:(OOCargoQuantity)co_amount { // can be nil for pods if (co_type == nil) { DESTROY(commodity_type); commodity_amount = 0; return; } // pod content should never be greater than 1 ton or this will give cargo counting problems elsewhere in the code. // so do first a mass check for cargo added by script/plist. OOMassUnit unit = [[UNIVERSE commodityMarket] massUnitForGood:co_type]; if (unit == UNITS_TONS && co_amount > 1) co_amount = 1; else if (unit == UNITS_KILOGRAMS && co_amount > 1000) co_amount = 1000; else if (unit == UNITS_GRAMS && co_amount > 1000000) co_amount = 1000000; [self setCommodity:co_type andAmount:co_amount]; } - (OOCommodityType) commodityType { return commodity_type; } - (OOCargoQuantity) commodityAmount { return commodity_amount; } - (OOCargoQuantity) maxAvailableCargoSpace { return max_cargo - equipment_weight; } - (void) setMaxAvailableCargoSpace:(OOCargoQuantity)new { max_cargo = new + equipment_weight; } - (OOCargoQuantity) availableCargoSpace { // OOCargoQuantity is unsigned, we need to check for underflows. if (EXPECT_NOT([self cargoQuantityOnBoard] + equipment_weight >= max_cargo)) return 0; return [self maxAvailableCargoSpace] - [self cargoQuantityOnBoard]; } - (OOCargoQuantity) cargoQuantityOnBoard { NSUInteger result = [[self cargo] count]; NSAssert(result < UINT32_MAX, @"Cargo quantity out of bounds."); return (OOCargoQuantity)result; } - (OOCargoType) cargoType { return cargo_type; } /* Note: this array probably contains some template cargo pods. Do not * pass it to Javascript without reifying them first. */ - (NSMutableArray*) cargo { return cargo; } - (NSArray *) cargoListForScripting { NSMutableArray *list = [NSMutableArray array]; OOCommodityType good = nil; NSArray *goods = [[UNIVERSE commodityMarket] goods]; NSUInteger i, j, commodityCount = [goods count]; OOCargoQuantity quantityInHold[commodityCount]; for (i = 0; i < commodityCount; i++) { quantityInHold[i] = 0; } for (i = 0; i < [cargo count]; i++) { ShipEntity *container = [cargo objectAtIndex:i]; j = [goods indexOfObject:[container commodityType]]; quantityInHold[j] += [container commodityAmount]; } for (i = 0; i < commodityCount; i++) { if (quantityInHold[i] > 0) { NSMutableDictionary *commodity = [NSMutableDictionary dictionaryWithCapacity:4]; good = [goods objectAtIndex:i]; // commodity, quantity - keep consistency between .manifest and .contracts [commodity setObject:good forKey:@"commodity"]; [commodity setObject:[NSNumber numberWithUnsignedInt:quantityInHold[i]] forKey:@"quantity"]; [commodity setObject:[[UNIVERSE commodityMarket] nameForGood:good] forKey:@"displayName"]; [commodity setObject:DisplayStringForMassUnitForCommodity(good) forKey:@"unit"]; [list addObject:commodity]; } } return [[list copy] autorelease]; // return an immutable copy } - (void) setCargo:(NSArray *) some_cargo { [cargo removeAllObjects]; [cargo addObjectsFromArray:some_cargo]; } - (BOOL) addCargo:(NSArray *) some_cargo { if ([cargo count] + [some_cargo count] > [self maxAvailableCargoSpace]) { return NO; } else { [cargo addObjectsFromArray:some_cargo]; return YES; } } - (BOOL) removeCargo:(OOCommodityType)commodity amount:(OOCargoQuantity) amount { OOCargoQuantity found = 0; ShipEntity *pod = nil; foreach (pod, cargo) { if ([[pod commodityType] isEqualToString:commodity]) { found++; } } if (found < amount) { // don't remove any if there aren't enough to remove the full amount return NO; } NSUInteger i = [cargo count] - 1; // iterate downwards to be safe removing during iteration while (amount > 0) { if ([[[cargo objectAtIndex:i] commodityType] isEqualToString:commodity]) { amount--; [cargo removeObjectAtIndex:i]; } // check above means array index can't underflow here i--; } return YES; } - (BOOL) showScoopMessage { return hasScoopMessage; } - (OOCargoFlag) cargoFlag { return cargo_flag; } - (void) setCargoFlag:(OOCargoFlag) flag { if (cargo_flag != flag) { cargo_flag = flag; NSArray *newCargo = nil; unsigned num = 0; if (likely_cargo > 0) { num = likely_cargo * (0.5+randf()); if (num > [self maxAvailableCargoSpace]) { num = [self maxAvailableCargoSpace]; } } else { num = [self maxAvailableCargoSpace]; } if (num > 200) { num = 200; /* no core NPC ship carries this much when generated (the * Anaconda could, but doesn't): let's not waste time generating * thousands of pods - even if they are semi-virtual - for some * massive OXP ship */ } if (num > 0) { switch (cargo_flag) { case CARGO_FLAG_FULL_UNIFORM: newCargo = [UNIVERSE getContainersOfCommodity:[shipinfoDictionary oo_stringForKey:@"cargo_carried"] :num]; break; case CARGO_FLAG_FULL_PLENTIFUL: newCargo = [UNIVERSE getContainersOfGoods:num scarce:NO legal:YES]; break; case CARGO_FLAG_FULL_SCARCE: newCargo = [UNIVERSE getContainersOfGoods:num scarce:YES legal:YES]; break; case CARGO_FLAG_FULL_MEDICAL: newCargo = [UNIVERSE getContainersOfCommodity:@"Narcotics" :num]; break; case CARGO_FLAG_FULL_CONTRABAND: newCargo = [UNIVERSE getContainersOfGoods:num scarce:YES legal:NO]; break; case CARGO_FLAG_PIRATE: newCargo = [UNIVERSE getContainersOfGoods:(Ranrot() % (1+num/2)) scarce:YES legal:NO]; break; case CARGO_FLAG_FULL_PASSENGERS: // TODO: allow passengers to survive case CARGO_FLAG_NONE: default: break; } } [self setCargo:newCargo]; } } - (void) setSpeed:(double) amount { flightSpeed = amount; } - (void) setDesiredSpeed:(double) amount { desired_speed = amount; } - (double) desiredSpeed { return desired_speed; } - (double) desiredRange { return desired_range; } - (void) setDesiredRange:(double) amount { desired_range = amount; } - (double) cruiseSpeed { return cruiseSpeed; } - (void) increase_flight_speed:(double) delta { double factor = 1.0; if (desired_speed > maxFlightSpeed && [self hasFuelInjection] && fuel > MIN_FUEL) factor = [self afterburnerFactor]; if (flightSpeed < maxFlightSpeed * factor) flightSpeed += delta * factor; else flightSpeed = maxFlightSpeed * factor; } - (void) decrease_flight_speed:(double) delta { double factor = 1.0; if (flightSpeed > maxFlightSpeed) { factor = MIN_HYPERSPEED_FACTOR; } if (flightSpeed > factor * delta) { flightSpeed -= factor * delta; } else { flightSpeed = 0; } } - (void) increase_flight_roll:(double) delta { if (flightRoll < max_flight_roll) flightRoll += delta; if (flightRoll > max_flight_roll) flightRoll = max_flight_roll; } - (void) decrease_flight_roll:(double) delta { if (flightRoll > -max_flight_roll) flightRoll -= delta; if (flightRoll < -max_flight_roll) flightRoll = -max_flight_roll; } - (void) increase_flight_pitch:(double) delta { if (flightPitch < max_flight_pitch) flightPitch += delta; if (flightPitch > max_flight_pitch) flightPitch = max_flight_pitch; } - (void) decrease_flight_pitch:(double) delta { if (flightPitch > -max_flight_pitch) flightPitch -= delta; if (flightPitch < -max_flight_pitch) flightPitch = -max_flight_pitch; } - (void) increase_flight_yaw:(double) delta { if (flightYaw < max_flight_yaw) flightYaw += delta; if (flightYaw > max_flight_yaw) flightYaw = max_flight_yaw; } - (void) decrease_flight_yaw:(double) delta { if (flightYaw > -max_flight_yaw) flightYaw -= delta; if (flightYaw < -max_flight_yaw) flightYaw = -max_flight_yaw; } - (GLfloat) flightRoll { return flightRoll; } - (GLfloat) flightPitch { return flightPitch; } - (GLfloat) flightYaw { return flightYaw; } - (GLfloat) flightSpeed { return flightSpeed; } - (GLfloat) maxFlightPitch { return max_flight_pitch; } - (GLfloat) maxFlightSpeed { return maxFlightSpeed; } - (GLfloat) maxFlightRoll { return max_flight_roll; } - (GLfloat) maxFlightYaw { return max_flight_yaw; } - (void) setMaxFlightPitch:(GLfloat)new { max_flight_pitch = new; } - (void) setMaxFlightSpeed:(GLfloat)new { maxFlightSpeed = new; } - (void) setMaxFlightRoll:(GLfloat)new { max_flight_roll = new; } - (void) setMaxFlightYaw:(GLfloat)new { max_flight_yaw = new; } - (GLfloat) speedFactor { if (maxFlightSpeed <= 0.0) return 0.0; return flightSpeed / maxFlightSpeed; } - (GLfloat) temperature { return ship_temperature; } - (void) setTemperature:(GLfloat) value { ship_temperature = value; } - (float) randomEjectaTemperature { return [self randomEjectaTemperatureWithMaxFactor:0.99f]; } - (float) randomEjectaTemperatureWithMaxFactor:(float)factor { const float kRange = 0.02f; factor -= kRange; float parentTemp = [self temperature]; float adjusted = parentTemp * (bellf(5) * (kRange * 2.0f) - kRange + factor); if (adjusted > SHIP_MAX_CABIN_TEMP) { adjusted = SHIP_MAX_CABIN_TEMP; } // Interpolate so that result == parentTemp when parentTemp is SHIP_MIN_CABIN_TEMP float interp = OOClamp_0_1_f((parentTemp - SHIP_MIN_CABIN_TEMP) / (SHIP_MAX_CABIN_TEMP - SHIP_MIN_CABIN_TEMP)); return OOLerp(SHIP_MIN_CABIN_TEMP, adjusted, interp); } - (GLfloat) heatInsulation { return _heatInsulation; } - (void) setHeatInsulation:(GLfloat) value { _heatInsulation = value; } - (int) damage { return (int)(100 - (100 * energy / maxEnergy)); } - (void) dealEnergyDamage:(GLfloat) baseDamage atRange:(GLfloat) range withBias:(GLfloat) velocityBias { // this is limited to the player's scanner range GLfloat maxRange = fmin(range * sqrt(baseDamage), SCANNER_MAX_RANGE); OOLog(@"missile.damage.calc", @"Range: %f | Damage: %f | MaxRange: %f",range,baseDamage,maxRange); NSArray *targets = [UNIVERSE entitiesWithinRange:maxRange ofEntity:self]; if ([targets count] > 0) { unsigned i; for (i = 0; i < [targets count]; i++) { Entity *e2 = [targets objectAtIndex:i]; Vector p2 = [self vectorTo:e2]; double ecr = [e2 collisionRadius]; double d = (magnitude(p2) - ecr) / range; // base damage within defined range, inverse-square falloff outside double localDamage = baseDamage; OOLog(@"missile.damage.calc", @"Base damage: %f",baseDamage); if (velocityBias > 0) { Vector v2 = vector_subtract([self velocity], [e2 velocity]); double vSign = dot_product(vector_normal([self velocity]), vector_normal(p2)); // vSign should always be positive for the missile's actual target // but might be negative for other nearby ships which are // actually moving further away from the missile // double vMag = vSign > 0.0 ? magnitude(v2) : -magnitude(v2); double vMag = vSign * magnitude(v2); if (vMag > 1000.0) { vMag = 1000.0; // cap effective closing speed to 1.0LM or injector-collisions can still do // ridiculous damage } localDamage += vMag * velocityBias; OOLog(@"missile.damage.calc",@"Velocity magnitude + sign: %f , %f",magnitude(v2),vSign); OOLog(@"missile.damage.calc",@"Velocity magnitude factor: %f",vMag); OOLog(@"missile.damage.calc",@"Velocity corrected damage: %f",localDamage); } double damage = (d > 1) ? localDamage / (d * d) : localDamage; OOLog(@"missile.damage.calc",@"%f at range %f (d=%f)",damage,magnitude(p2)-ecr,d); if (damage > 0.0) { if ([self owner]) { [e2 takeEnergyDamage:damage from:self becauseOf:[self owner]]; } else { [e2 takeEnergyDamage:damage from:self becauseOf:self]; } } } } /* the actual damage can't go more than S_M_R, so cap the range * for exploding purposes so that the visual appearance isn't * larger than that */ if (range > SCANNER_MAX_RANGE / 4.0) { range = SCANNER_MAX_RANGE / 4.0; } // and a visual sign of the explosion // "fireball" explosion effect NSDictionary *explosion = [UNIVERSE explosionSetting:@"oolite-default-ship-explosion"]; [UNIVERSE addEntity:[OOExplosionCloudEntity explosionCloudFromEntity:self withSize:range*3.0 andSettings:explosion]]; } // dealEnergyDamage preferred // Exposed to AI - (void) dealEnergyDamageWithinDesiredRange { OOStandardsDeprecated([NSString stringWithFormat:@"dealEnergyDamageWithinDesiredRange is deprecated for %@",self]); // not over scannerRange NSArray* targets = [UNIVERSE entitiesWithinRange:(desired_range < SCANNER_MAX_RANGE ? desired_range : SCANNER_MAX_RANGE) ofEntity:self]; if ([targets count] > 0) { unsigned i; for (i = 0; i < [targets count]; i++) { Entity *e2 = [targets objectAtIndex:i]; Vector p2 = [self vectorTo:e2]; double ecr = [e2 collisionRadius]; double d = (magnitude(p2) - ecr) * 2.6; // 2.6 is a correction constant to stay in limits of the old code. double damage = (d > 0) ? weapon_damage * desired_range / (d * d) : weapon_damage; [e2 takeEnergyDamage:damage from:self becauseOf:[self owner]]; } } } - (void) dealMomentumWithinDesiredRange:(double)amount { NSArray* targets = [UNIVERSE entitiesWithinRange:desired_range ofEntity:self]; if ([targets count] > 0) { unsigned i; for (i = 0; i < [targets count]; i++) { ShipEntity *e2 = (ShipEntity*)[targets objectAtIndex:i]; if ([e2 isShip] && [e2 isInSpace]) { Vector p2 = [self vectorTo:e2]; double ecr = [e2 collisionRadius]; double d2 = magnitude2(p2) - ecr * ecr; // limit momentum transfer to relatively sensible levels if (d2 < 0.1) { d2 = 0.1; } double moment = amount*desired_range/d2; [e2 addImpactMoment:vector_normal(p2) fraction:moment]; } } } } - (BOOL) isHulk { return isHulk; } - (void) setHulk:(BOOL)isNowHulk { if (![self isSubEntity]) { isHulk = isNowHulk; } } - (void) noteTakingDamage:(double)amount from:(Entity *)entity type:(OOShipDamageType)type { if (amount < 0 || (amount == 0 && [[UNIVERSE gameController] isGamePaused])) return; JSContext *context = OOJSAcquireContext(); jsval amountVal = JSVAL_VOID; JS_NewNumberValue(context, amount, &amountVal); jsval entityVal = OOJSValueFromNativeObject(context, entity); jsval typeVal = OOJSValueFromShipDamageType(context, type); ShipScriptEvent(context, self, "shipTakingDamage", amountVal, entityVal, typeVal); OOJSRelinquishContext(context); if ([entity isShip]) { // ShipEntity* attacker = (ShipEntity *)entity; if ([self hasHostileTarget] && accuracy >= COMBAT_AI_IS_SMART && (randf()*10.0 < accuracy || desired_speed < 0.5 * maxFlightSpeed) && behaviour != BEHAVIOUR_EVASIVE_ACTION && behaviour != BEHAVIOUR_FLEE_EVASIVE_ACTION && behaviour != BEHAVIOUR_SCRIPTED_ATTACK_AI) { if (behaviour == BEHAVIOUR_FLEE_TARGET) { // jink should be sufficient to avoid being hit most of the time // if not, this will make a sharp turn and then select a new jink position behaviour = BEHAVIOUR_FLEE_EVASIVE_ACTION; } else { behaviour = BEHAVIOUR_EVASIVE_ACTION; } frustration = 0.0; } } } - (void) noteKilledBy:(Entity *)whom damageType:(OOShipDamageType)type { if ([self status] == STATUS_DEAD) return; [PLAYER setScriptTarget:self]; JSContext *context = OOJSAcquireContext(); jsval whomVal = OOJSValueFromNativeObject(context, whom); jsval typeVal = OOJSValueFromShipDamageType(context, type); OOEntityStatus originalStatus = [self status]; [self setStatus:STATUS_DEAD]; ShipScriptEvent(context, self, "shipDied", whomVal, typeVal); if ([whom isShip]) { jsval selfVal = OOJSValueFromNativeObject(context, self); ShipScriptEvent(context, (ShipEntity *)whom, "shipKilledOther", selfVal, typeVal); } [self setStatus:originalStatus]; OOJSRelinquishContext(context); } - (void) getDestroyedBy:(Entity *)whom damageType:(OOShipDamageType)type { [self noteKilledBy:whom damageType:type]; [self abortDocking]; [self becomeExplosion]; } - (void) rescaleBy:(GLfloat)factor { _scaleFactor *= factor; OOMesh *mesh = nil; NSDictionary *shipDict = [self shipInfoDictionary]; NSString *modelName = [shipDict oo_stringForKey:@"model"]; if (modelName != nil) { mesh = [OOMesh meshWithName:modelName cacheKey:[NSString stringWithFormat:@"%@-%.3f",_shipKey,_scaleFactor] materialDictionary:[shipDict oo_dictionaryForKey:@"materials"] shadersDictionary:[shipDict oo_dictionaryForKey:@"shaders"] smooth:[shipDict oo_boolForKey:@"smooth" defaultValue:NO] shaderMacros:OODefaultShipShaderMacros() shaderBindingTarget:self scaleFactor:factor]; if (mesh == nil) return; [self setMesh:mesh]; } // rescale subentities Entity *se = nil; foreach (se, [self subEntities]) { [se setPosition:HPvector_multiply_scalar([se position], factor)]; [se rescaleBy:factor]; } // rescale mass mass *= factor * factor * factor; } - (void) releaseCargoPodsDebris { HPVector xposition = position; NSUInteger i; Vector v; Quaternion q; int speed_low = 200; NSArray *jetsam = nil; // this will contain the stuff to get thrown out unsigned cargo_chance = 70; jetsam = [NSArray arrayWithArray:cargo]; // what the ship is carrying [cargo removeAllObjects]; // dispense with it! unsigned limit = 15; // Throw out cargo NSUInteger n_jetsam = [jetsam count]; for (i = 0; i < n_jetsam; i++) { if (Ranrot() % 100 < cargo_chance) // chance of any given piece of cargo surviving decompression { // a higher chance of getting at least a couple of bits of cargo out if (cargo_chance > 10) { if (EXPECT_NOT([self isPlayer])) { cargo_chance -= 20; } else { cargo_chance -= 30; } } limit--; ShipEntity* cargoObj = [jetsam objectAtIndex:i]; ShipEntity* container = [UNIVERSE reifyCargoPod:cargoObj]; /* TODO: this debris position/velocity setting code is * duplicated - sometimes not very cleanly - all over the * place. Unify to a single function - CIM */ HPVector rpos = xposition; Vector rrand = OORandomPositionInBoundingBox(boundingBox); rpos.x += rrand.x; rpos.y += rrand.y; rpos.z += rrand.z; rpos.x += (ranrot_rand() % 7) - 3; rpos.y += (ranrot_rand() % 7) - 3; rpos.z += (ranrot_rand() % 7) - 3; [container setPosition:rpos]; v.x = 0.1 *((ranrot_rand() % speed_low) - speed_low / 2); v.y = 0.1 *((ranrot_rand() % speed_low) - speed_low / 2); v.z = 0.1 *((ranrot_rand() % speed_low) - speed_low / 2); [container setVelocity:vector_add(v,[self velocity])]; quaternion_set_random(&q); [container setOrientation:q]; [container setTemperature:[self randomEjectaTemperature]]; [container setScanClass: CLASS_CARGO]; [UNIVERSE addEntity:container]; // STATUS_IN_FLIGHT, AI state GLOBAL AI *containerAI = [container getAI]; if ([containerAI hasSuspendedStateMachines]) // check if new or recycled cargo. { [containerAI exitStateMachineWithMessage:nil]; [container setThrust:[container maxThrust]]; // restore old value. Was set to zero on previous scooping. [container setOwner:container]; } } if (limit <= 0) { break; // even really big ships won't have too much cargo survive an explosion } } } - (void) setIsWreckage:(BOOL)isw { isWreckage = isw; } - (BOOL) showDamage { return _showDamage; } - (void) becomeExplosion { // check if we're destroying a subentity ShipEntity *parent = [self parentEntity]; if (parent != nil) { ShipEntity *this_ship = [self retain]; HPVector this_pos = [self absolutePositionForSubentity]; // remove this ship from its parent's subentity list [parent subEntityDied:self]; [UNIVERSE addEntity:this_ship]; [this_ship setPosition:this_pos]; [this_ship release]; if ([parent isPlayer]) { // make the parent ship less reliable. [(PlayerEntity *)parent adjustTradeInFactorBy:-PLAYER_SHIP_SUBENTITY_TRADE_IN_VALUE]; } } HPVector xposition = position; NSUInteger i; Vector v; Quaternion q; int speed_low = 200; GLfloat n_alloys = sqrtf(sqrtf(mass / 6000.0f)); NSUInteger numAlloys = 0; BOOL canReleaseSubWreckage = isWreckage && ([UNIVERSE detailLevel] >= DETAIL_LEVEL_EXTRAS); if ([self status] == STATUS_DEAD) { [UNIVERSE removeEntity:self]; return; } [self setStatus:STATUS_DEAD]; @try { if ([self isThargoid] && [roleSet hasRole:@"thargoid-mothership"]) [self broadcastThargoidDestroyed]; if (!suppressExplosion) { if (!isWreckage && mass > 500000.0f && randf() < 0.25f) // big! { // draw an expanding ring OORingEffectEntity *ring = [OORingEffectEntity ringFromEntity:self]; [ring setVelocity:vector_multiply_scalar([self velocity], 0.25f)]; [UNIVERSE addEntity:ring]; } BOOL add_debris = (UNIVERSE->n_entities < 0.95 * UNIVERSE_MAX_ENTITIES) && ([UNIVERSE getTimeDelta] < 0.125); // FPS > 8 // There are several parts to explosions, show only the main // explosion effect if UNIVERSE is almost full. if (add_debris) { if ([UNIVERSE reducedDetail]) { // Quick explosion effects for reduced detail mode // 1. fast sparks [UNIVERSE addEntity:[OOSmallFragmentBurstEntity fragmentBurstFromEntity:self]]; // 2. slow clouds [UNIVERSE addEntity:[OOBigFragmentBurstEntity fragmentBurstFromEntity:self]]; // 3. flash [UNIVERSE addEntity:[OOFlashEffectEntity explosionFlashFromEntity:self]]; /* This mode used to be the default for * cargo/munitions but this now must be explicitly * specified. */ } else { NSString *explosionKey = @"oolite-default-ship-explosion"; NSDictionary *explosion = nil; if (explosionType == nil) { explosion = [UNIVERSE explosionSetting:explosionKey]; [UNIVERSE addEntity:[OOExplosionCloudEntity explosionCloudFromEntity:self withSettings:explosion]]; // 3. flash [UNIVERSE addEntity:[OOFlashEffectEntity explosionFlashFromEntity:self]]; } for (NSUInteger i=0;i<[explosionType count];i++) { explosionKey = [explosionType oo_stringAtIndex:i defaultValue:nil]; if (explosionKey != nil) { // three special-case builtins if ([explosionKey isEqualToString:@"oolite-builtin-flash"]) { [UNIVERSE addEntity:[OOFlashEffectEntity explosionFlashFromEntity:self]]; } else if ([explosionKey isEqualToString:@"oolite-builtin-slowcloud"]) { [UNIVERSE addEntity:[OOBigFragmentBurstEntity fragmentBurstFromEntity:self]]; } else if ([explosionKey isEqualToString:@"oolite-builtin-fastspark"]) { [UNIVERSE addEntity:[OOSmallFragmentBurstEntity fragmentBurstFromEntity:self]]; } else { explosion = [UNIVERSE explosionSetting:explosionKey]; [UNIVERSE addEntity:[OOExplosionCloudEntity explosionCloudFromEntity:self withSettings:explosion]]; } } } // "fireball" explosion effect } } // If UNIVERSE is nearing limit for entities don't add to it! if (add_debris) { // we need to throw out cargo at this point. [self releaseCargoPodsDebris]; // Throw out rocks and alloys to be scooped up if ([self hasRole:@"asteroid"] || [self isBoulder]) { if (!noRocks && (being_mined || randf() < 0.20)) { NSString *defaultRole = @"boulder"; float defaultSpeed = 50.0; if ([self isBoulder]) { defaultRole = @"splinter"; defaultSpeed = 20.0; if (likely_cargo == 0) { likely_cargo = 4; // compatibility with older boulders } } else if ([[self primaryAggressor] isPlayer]) { [PLAYER addRoleForMining]; } NSUInteger n_rocks = 2 + (Ranrot() % (likely_cargo + 1)); NSString *debrisRole = [[self shipInfoDictionary] oo_stringForKey:@"debris_role" defaultValue:defaultRole]; for (i = 0; i < n_rocks; i++) { ShipEntity* rock = [UNIVERSE newShipWithRole:debrisRole]; // retain count = 1 if (rock) { float r_speed = [rock maxFlightSpeed] > 0 ? 2.0 * [rock maxFlightSpeed] : defaultSpeed; float cr = (collision_radius < rock->collision_radius) ? collision_radius : 2 * rock->collision_radius; v.x = ((randf() * r_speed) - r_speed / 2); v.y = ((randf() * r_speed) - r_speed / 2); v.z = ((randf() * r_speed) - r_speed / 2); [rock setVelocity:vector_add(v,[self velocity])]; HPVector rpos = HPvector_add(xposition,vectorToHPVector(vector_multiply_scalar(vector_normal(v),cr))); [rock setPosition:rpos]; quaternion_set_random(&q); [rock setOrientation:q]; [rock setTemperature:[self randomEjectaTemperature]]; if ([self isBoulder]) { [rock setScanClass: CLASS_CARGO]; [rock setBounty: 0 withReason:kOOLegalStatusReasonSetup]; [rock setCommodity:@"minerals" andAmount: 1]; } else { [rock setScanClass:CLASS_ROCK]; [rock setIsBoulder:YES]; } [UNIVERSE addEntity:rock]; // STATUS_IN_FLIGHT, AI state GLOBAL [rock release]; } } } return; } // throw out burning chunks of wreckage // if ((n_alloys && canFragment) || canReleaseSubWreckage) { NSUInteger n_wreckage = 0; if (UNIVERSE->n_entities < 0.50 * UNIVERSE_MAX_ENTITIES) { // Create wreckage only when UNIVERSE is less than half full. // (condition set in r906 - was < 0.75 before) --Kaks 2011.10.17 NSUInteger maxWrecks = 3; if (n_alloys == 0) { // must be sub-wreckage here n_wreckage = (mass > 600.0 && randf() < 0.2)?2:0; } else { n_wreckage = (n_alloys < maxWrecks)? floorf(randf()*(n_alloys+2)) : maxWrecks; } } for (i = 0; i < n_wreckage; i++) { Vector r1 = [octree randomPoint]; Vector dir = quaternion_rotate_vector([self normalOrientation], r1); HPVector rpos = HPvector_add(vectorToHPVector(dir), xposition); GLfloat lifetime = 750.0 * randf() + 250.0 * i + 100.0; ShipEntity *wreck = [UNIVERSE addWreckageFrom:self withRole:@"wreckage" at:rpos scale:1.0 lifetime:lifetime/2]; [wreck setVelocity:vector_add([wreck velocity],vector_multiply_scalar(vector_normal(dir),randf()*[wreck collisionRadius]))]; } n_alloys = randf() * n_alloys; } } if (!canFragment) { n_alloys = 0.0; } // If UNIVERSE is almost full, don't create more than 1 piece of scrap metal. else if (!add_debris) { n_alloys = (n_alloys > 1.0) ? 1.0 : 0.0; } // now convert to uint numAlloys = floorf(n_alloys); // Throw out scrap metal // for (i = 0; i < numAlloys; i++) { ShipEntity* plate = [UNIVERSE newShipWithRole:@"alloy"]; // retain count = 1 if (plate) { HPVector rpos = xposition; Vector rrand = OORandomPositionInBoundingBox(boundingBox); rpos.x += rrand.x; rpos.y += rrand.y; rpos.z += rrand.z; rpos.x += (ranrot_rand() % 7) - 3; rpos.y += (ranrot_rand() % 7) - 3; rpos.z += (ranrot_rand() % 7) - 3; [plate setPosition:rpos]; v.x = 0.1 *((ranrot_rand() % speed_low) - speed_low / 2); v.y = 0.1 *((ranrot_rand() % speed_low) - speed_low / 2); v.z = 0.1 *((ranrot_rand() % speed_low) - speed_low / 2); [plate setVelocity:vector_add(v,[self velocity])]; quaternion_set_random(&q); [plate setOrientation:q]; [plate setTemperature:[self randomEjectaTemperature]]; [plate setScanClass: CLASS_CARGO]; [plate setCommodity:@"alloys" andAmount:1]; [UNIVERSE addEntity:plate]; // STATUS_IN_FLIGHT, AI state GLOBAL [plate release]; } } } // Explode subentities. NSEnumerator *subEnum = nil; ShipEntity *se = nil; for (subEnum = [self shipSubEntityEnumerator]; (se = [subEnum nextObject]); ) { [se setSuppressExplosion:suppressExplosion]; [se becomeExplosion]; } [self clearSubEntities]; // momentum from explosions if (!suppressExplosion) { desired_range = collision_radius * 2.5f; [self dealMomentumWithinDesiredRange:0.125f * mass]; } if (self != PLAYER) // was if !isPlayer - but I think this may cause ghosts (Who's "I"? -- Ahruman) { if (isPlayer) { #ifndef NDEBUG OOLog(@"becomeExplosion.suspectedGhost.confirm", @"Ship spotted with isPlayer set when not actually the player."); #endif isPlayer = NO; } } } @finally { if (self != PLAYER) { [UNIVERSE removeEntity:self]; } } } // Exposed to AI - (void) becomeEnergyBlast { [UNIVERSE addEntity:[OOQuiriumCascadeEntity quiriumCascadeFromShip:self]]; [self broadcastEnergyBlastImminent]; [self noteKilledBy:nil damageType:kOODamageTypeCascadeWeapon]; [UNIVERSE removeEntity:self]; } // Exposed to AI - (void) broadcastEnergyBlastImminent { // anyone further away than typical scanner range probably doesn't need to hear NSArray* targets = [UNIVERSE entitiesWithinRange:SCANNER_MAX_RANGE ofEntity:self]; if ([targets count] > 0) { unsigned i; for (i = 0; i < [targets count]; i++) { Entity *e2 = [targets objectAtIndex:i]; if ([e2 isShip]) { ShipEntity *se = (ShipEntity *)e2; [se setFoundTarget:self]; [se reactToAIMessage:@"CASCADE_WEAPON_DETECTED" context:@"nearby Q-mine"]; [se doScriptEvent:OOJSID("cascadeWeaponDetected") withArgument:self]; } } } } - (void) removeExhaust:(OOExhaustPlumeEntity *)exhaust { [subEntities removeObject:exhaust]; [exhaust setOwner:nil]; } - (void) removeFlasher:(OOFlasherEntity *)flasher { [subEntities removeObject:flasher]; [flasher setOwner:nil]; } - (void)subEntityDied:(ShipEntity *)sub { if ([self subEntityTakingDamage] == sub) [self setSubEntityTakingDamage:nil]; [sub setOwner:nil]; // TODO? Recalculating collision radius should increase collision testing efficiency, // but for most ship models the difference would be marginal. -- Kaks 20110429 mass -= [sub mass]; // missing subents affect fuel charge rate, etc.. [subEntities removeObject:sub]; } - (void)subEntityReallyDied:(ShipEntity *)sub { if ([self subEntityTakingDamage] == sub) [self setSubEntityTakingDamage:nil]; if ([self hasSubEntity:sub]) { OOLogERR(@"shipEntity.bug.subEntityRetainUnderflow", @"Subentity of %@ died while still in subentity list! This is bad. Leaking subentity list to avoid crash. %@", self, @"This is an internal error, please report it."); // Leak subentity list. subEntities = nil; } } - (Vector) positionOffsetForAlignment:(NSString*) align { NSString* padAlign = [NSString stringWithFormat:@"%@---", align]; Vector result = kZeroVector; switch ([padAlign characterAtIndex:0]) { case (unichar)'c': case (unichar)'C': result.x = 0.5 * (boundingBox.min.x + boundingBox.max.x); break; case (unichar)'M': result.x = boundingBox.max.x; break; case (unichar)'m': result.x = boundingBox.min.x; break; } switch ([padAlign characterAtIndex:1]) { case (unichar)'c': case (unichar)'C': result.y = 0.5 * (boundingBox.min.y + boundingBox.max.y); break; case (unichar)'M': result.y = boundingBox.max.y; break; case (unichar)'m': result.y = boundingBox.min.y; break; } switch ([padAlign characterAtIndex:2]) { case (unichar)'c': case (unichar)'C': result.z = 0.5 * (boundingBox.min.z + boundingBox.max.z); break; case (unichar)'M': result.z = boundingBox.max.z; break; case (unichar)'m': result.z = boundingBox.min.z; break; } return result; } Vector positionOffsetForShipInRotationToAlignment(ShipEntity* ship, Quaternion q, NSString* align) { NSString* padAlign = [NSString stringWithFormat:@"%@---", align]; Vector i = vector_right_from_quaternion(q); Vector j = vector_up_from_quaternion(q); Vector k = vector_forward_from_quaternion(q); BoundingBox arbb = [ship findBoundingBoxRelativeToPosition:kZeroHPVector InVectors:i :j :k]; Vector result = kZeroVector; switch ([padAlign characterAtIndex:0]) { case (unichar)'c': case (unichar)'C': result.x = 0.5 * (arbb.min.x + arbb.max.x); break; case (unichar)'M': result.x = arbb.max.x; break; case (unichar)'m': result.x = arbb.min.x; break; } switch ([padAlign characterAtIndex:1]) { case (unichar)'c': case (unichar)'C': result.y = 0.5 * (arbb.min.y + arbb.max.y); break; case (unichar)'M': result.y = arbb.max.y; break; case (unichar)'m': result.y = arbb.min.y; break; } switch ([padAlign characterAtIndex:2]) { case (unichar)'c': case (unichar)'C': result.z = 0.5 * (arbb.min.z + arbb.max.z); break; case (unichar)'M': result.z = arbb.max.z; break; case (unichar)'m': result.z = arbb.min.z; break; } return result; } - (void) becomeLargeExplosion:(double)factor { if ([self status] == STATUS_DEAD) return; [self setStatus:STATUS_DEAD]; @try { // two parts to the explosion: // 1. fast sparks float how_many = factor; while (how_many > 0.5f) { [UNIVERSE addEntity:[OOSmallFragmentBurstEntity fragmentBurstFromEntity:self]]; how_many -= 1.0f; } // 2. slow clouds how_many = factor; while (how_many > 0.5f) { [UNIVERSE addEntity:[OOBigFragmentBurstEntity fragmentBurstFromEntity:self]]; how_many -= 1.0f; } [self releaseCargoPodsDebris]; NSEnumerator *subEnum = nil; ShipEntity *se = nil; for (subEnum = [self shipSubEntityEnumerator]; (se = [subEnum nextObject]); ) { [se setSuppressExplosion:suppressExplosion]; [se becomeExplosion]; } [self clearSubEntities]; } @finally { if (!isPlayer) [UNIVERSE removeEntity:self]; } } - (void) collectBountyFor:(ShipEntity *)other { if ([other isPolice]) // oops, we shot a copper! { [self markAsOffender:64 withReason:kOOLegalStatusReasonAttackedPolice]; } } - (NSComparisonResult) compareBeaconCodeWith:(Entity *) other { return [[self beaconCode] compare:[other beaconCode] options: NSCaseInsensitiveSearch]; } // for shaders, equivalent to 1.76's NPC laserHeatLevel - (GLfloat) weaponRecoveryTime { float result = (weapon_recharge_rate - [self shotTime]) / weapon_recharge_rate; return OOClamp_0_1_f(result); } - (GLfloat)laserHeatLevel { GLfloat result = weapon_temp / NPC_MAX_WEAPON_TEMP; return OOClamp_0_1_f(result); } - (GLfloat)laserHeatLevelAft { GLfloat result = aft_weapon_temp / NPC_MAX_WEAPON_TEMP; return OOClamp_0_1_f(result); } - (GLfloat)laserHeatLevelForward { GLfloat result = forward_weapon_temp / NPC_MAX_WEAPON_TEMP; if (isWeaponNone(forward_weapon_type)) { // must check subents OOWeaponType forward_weapon_real_type = nil; NSEnumerator *subEnum = [self shipSubEntityEnumerator]; ShipEntity *se = nil; while (isWeaponNone(forward_weapon_real_type) && (se = [subEnum nextObject])) { if (!isWeaponNone(se->forward_weapon_type)) { forward_weapon_real_type = se->forward_weapon_type; result = se->forward_weapon_temp / NPC_MAX_WEAPON_TEMP; } } } return OOClamp_0_1_f(result); } - (GLfloat)laserHeatLevelPort { GLfloat result = port_weapon_temp / NPC_MAX_WEAPON_TEMP; return OOClamp_0_1_f(result); } - (GLfloat)laserHeatLevelStarboard { GLfloat result = starboard_weapon_temp / NPC_MAX_WEAPON_TEMP; return OOClamp_0_1_f(result); } - (GLfloat)hullHeatLevel { GLfloat result = (GLfloat)ship_temperature / (GLfloat)SHIP_MAX_CABIN_TEMP; return OOClamp_0_1_f(result); } - (GLfloat)entityPersonality { return entity_personality / (float)ENTITY_PERSONALITY_MAX; } - (GLint)entityPersonalityInt { return entity_personality; } - (uint32_t) randomSeedForShaders { return entity_personality * 0x00010001; } - (void) setEntityPersonalityInt:(uint16_t)value { if (value <= ENTITY_PERSONALITY_MAX) { entity_personality = value; [[self mesh] rebindMaterials]; } } - (void)setSuppressExplosion:(BOOL)suppress { suppressExplosion = !!suppress; } - (void) resetExhaustPlumes { NSEnumerator *exEnum = nil; OOExhaustPlumeEntity *exEnt = nil; for (exEnum = [self exhaustEnumerator]; (exEnt = [exEnum nextObject]); ) { [exEnt resetPlume]; } } /*----------------------------------------- AI piloting methods -----------------------------------------*/ - (void) checkScanner { Entity* scan; n_scanned_ships = 0; // scan = z_previous; while ((scan)&&(scan->isShip == NO)) scan = scan->z_previous; // skip non-ships GLfloat scannerRange2 = scannerRange * scannerRange; while ((scan)&&(scan->position.z > position.z - scannerRange)&&(n_scanned_ships < MAX_SCAN_NUMBER)) { // can't scan cloaked ships if (scan->isShip && ![(ShipEntity*)scan isCloaked] && [self isValidTarget:scan]) { distance2_scanned_ships[n_scanned_ships] = HPdistance2(position, scan->position); if (distance2_scanned_ships[n_scanned_ships] < scannerRange2) scanned_ships[n_scanned_ships++] = (ShipEntity*)scan; } scan = scan->z_previous; while ((scan)&&(scan->isShip == NO)) scan = scan->z_previous; } // scan = z_next; while ((scan)&&(scan->isShip == NO)) scan = scan->z_next; // skip non-ships while ((scan)&&(scan->position.z < position.z + scannerRange)&&(n_scanned_ships < MAX_SCAN_NUMBER)) { if (scan->isShip && ![(ShipEntity*)scan isCloaked] && [self isValidTarget:scan]) { distance2_scanned_ships[n_scanned_ships] = HPdistance2(position, scan->position); if (distance2_scanned_ships[n_scanned_ships] < scannerRange2) scanned_ships[n_scanned_ships++] = (ShipEntity*)scan; } scan = scan->z_next; while ((scan)&&(scan->isShip == NO)) scan = scan->z_next; // skip non-ships } // scanned_ships[n_scanned_ships] = nil; // terminate array } - (void) checkScannerIgnoringUnpowered { Entity* scan; n_scanned_ships = 0; // GLfloat scannerRange2 = scannerRange * scannerRange; scan = z_previous; while ((scan)&&((scan->isShip == NO)||(scan->scanClass==CLASS_ROCK)||(scan->scanClass==CLASS_CARGO))) { scan = scan->z_previous; // skip non-ships } while ((scan)&&(scan->position.z > position.z - scannerRange)&&(n_scanned_ships < MAX_SCAN_NUMBER)) { if (scan->isShip && ![(ShipEntity*)scan isCloaked]) { distance2_scanned_ships[n_scanned_ships] = HPdistance2(position, scan->position); if (distance2_scanned_ships[n_scanned_ships] < scannerRange2) scanned_ships[n_scanned_ships++] = (ShipEntity*)scan; } scan = scan->z_previous; while ((scan)&&((scan->isShip == NO)||(scan->scanClass==CLASS_ROCK)||(scan->scanClass==CLASS_CARGO))) { scan = scan->z_previous; // skip non-ships } } // scan = z_next; while ((scan)&&((scan->isShip == NO)||(scan->scanClass==CLASS_ROCK)||(scan->scanClass==CLASS_CARGO))) { scan = scan->z_next; // skip non-ships } while ((scan)&&(scan->position.z < position.z + scannerRange)&&(n_scanned_ships < MAX_SCAN_NUMBER)) { if (scan->isShip && ![(ShipEntity*)scan isCloaked]) { distance2_scanned_ships[n_scanned_ships] = HPdistance2(position, scan->position); if (distance2_scanned_ships[n_scanned_ships] < scannerRange2) scanned_ships[n_scanned_ships++] = (ShipEntity*)scan; } scan = scan->z_next; while ((scan)&&((scan->isShip == NO)||(scan->scanClass==CLASS_ROCK)||(scan->scanClass==CLASS_CARGO))) { scan = scan->z_next; // skip non-ships } } // scanned_ships[n_scanned_ships] = nil; // terminate array } - (ShipEntity**) scannedShips { scanned_ships[n_scanned_ships] = nil; // terminate array return scanned_ships; } - (int) numberOfScannedShips { return n_scanned_ships; } - (Entity *) foundTarget { Entity *result = [_foundTarget weakRefUnderlyingObject]; if (result == nil || ![self isValidTarget:result]) { DESTROY(_foundTarget); return nil; } return result; } - (void) setFoundTarget:(Entity *) targetEntity { [_foundTarget release]; _foundTarget = [targetEntity weakRetain]; } - (Entity *) primaryAggressor { Entity *result = [_primaryAggressor weakRefUnderlyingObject]; if (result == nil || ![self isValidTarget:result]) { DESTROY(_primaryAggressor); return nil; } return result; } - (void) setPrimaryAggressor:(Entity *) targetEntity { [_primaryAggressor release]; _primaryAggressor = [targetEntity weakRetain]; } - (Entity *) lastEscortTarget { Entity *result = [_lastEscortTarget weakRefUnderlyingObject]; if (result == nil || ![self isValidTarget:result]) { DESTROY(_lastEscortTarget); return nil; } return result; } - (void) setLastEscortTarget:(Entity *) targetEntity { [_lastEscortTarget release]; _lastEscortTarget = [targetEntity weakRetain]; } - (Entity *) thankedShip { Entity *result = [_thankedShip weakRefUnderlyingObject]; if (result == nil || ![self isValidTarget:result]) { DESTROY(_thankedShip); return nil; } return result; } - (void) setThankedShip:(Entity *) targetEntity { [_thankedShip release]; _thankedShip = [targetEntity weakRetain]; } - (Entity *) rememberedShip { Entity *result = [_rememberedShip weakRefUnderlyingObject]; if (result == nil || ![self isValidTarget:result]) { DESTROY(_rememberedShip); return nil; } return result; } - (void) setRememberedShip:(Entity *) targetEntity { [_rememberedShip release]; _rememberedShip = [targetEntity weakRetain]; } - (StationEntity *) targetStation { StationEntity *result = [_targetStation weakRefUnderlyingObject]; if (result == nil || ![self isValidTarget:result]) { DESTROY(_targetStation); return nil; } return result; } - (void) setTargetStation:(Entity *) targetEntity { [_targetStation release]; _targetStation = [targetEntity weakRetain]; } /* Now we use weakrefs rather than universal ID this function checks * for targets which may have a valid reference but are not currently * targetable. */ - (BOOL) isValidTarget:(Entity *)target { if (target == nil) { return NO; } if ([target isShip]) { OOEntityStatus tstatus = [target status]; if (tstatus == STATUS_ENTERING_WITCHSPACE || tstatus == STATUS_IN_HOLD || tstatus == STATUS_DOCKED || tstatus == STATUS_DEAD) // 2013-01-13, Eric: added STATUS_DEAD because I keep seeing ships locked on dead ships in attack mode. { return NO; } return YES; } if ([target isWormhole] && [target scanClass] != CLASS_NO_DRAW) { return YES; } return NO; } - (void) addTarget:(Entity *) targetEntity { if (targetEntity == self) return; if (targetEntity != nil) { DESTROY(_primaryTarget); _primaryTarget = [targetEntity weakRetain]; [self startTrackingCurve]; } [[self shipSubEntityEnumerator] makeObjectsPerformSelector:@selector(addTarget:) withObject:targetEntity]; if (![self isSubEntity]) [self doScriptEvent:OOJSID("shipTargetAcquired") withArgument:targetEntity]; } - (void) removeTarget:(Entity *) targetEntity { if(targetEntity != nil) [self noteLostTarget]; else DESTROY(_primaryTarget); // targetEntity == nil is currently only true for mounted player missiles. // we don't want to send lostTarget messages while the missile is mounted. [[self shipSubEntityEnumerator] makeObjectsPerformSelector:@selector(removeTarget:) withObject:targetEntity]; } /* Checks if the primary target is still trackable. * * 1.80 behaviour: still exists, is in scanner range, is not cloaked * * 1.81 (planned) behaviour: * - track cloaked ships once primary targeted (but no missiles, and can't keep as a mere defense target, and probably some other penalties) * - track ships at 120% scanner range *if* they are also primary aggressor * * But first, just switch over to this method on 1.80 behaviour and check * that things still work. */ - (BOOL) canStillTrackPrimaryTarget { Entity *target = (Entity *)[self primaryTargetWithoutValidityCheck]; if (target == nil) { return NO; } if (![self isValidTarget:target]) { return NO; } double range2 = HPmagnitude2(HPvector_subtract([target position], position)); if (range2 > scannerRange * scannerRange * 1.5625) { // 1.5625 = 1.25*1.25 return NO; } // 1.81: can retain cloaked ships as a *primary* target now /* if ([target isShip] && [(ShipEntity*)target isCloaked]) { return NO; } */ return YES; } - (id) primaryTarget { id result = [_primaryTarget weakRefUnderlyingObject]; if ((result == nil && _primaryTarget != nil) || ![self isValidTarget:result]) { DESTROY(_primaryTarget); return nil; } else if (EXPECT_NOT(result == self)) { /* Added in response to a crash report showing recursion in [PlayerEntity hasHostileTarget]. -- Ahruman 2009-12-17 */ DESTROY(_primaryTarget); } return result; } // used when we need to check the target - perhaps for a potential // noteTargetLost - without invalidating the target first - (id) primaryTargetWithoutValidityCheck { id result = [_primaryTarget weakRefUnderlyingObject]; if (EXPECT_NOT(result == self)) { // just in case DESTROY(_primaryTarget); return nil; } return result; } - (BOOL) isFriendlyTo:(ShipEntity *)otherShip { BOOL isFriendly = NO; OOShipGroup *myGroup = [self group]; OOShipGroup *otherGroup = [otherShip group]; if ((otherShip == self) || ([self isPolice] && [otherShip isPolice]) || ([self isThargoid] && [otherShip isThargoid]) || (myGroup != nil && otherGroup != nil && (myGroup == otherGroup || [otherGroup leader] == self)) || ([self scanClass] == CLASS_MILITARY && [otherShip scanClass] == CLASS_MILITARY)) { isFriendly = YES; } return isFriendly; } - (ShipEntity *) shipHitByLaser { return [_shipHitByLaser weakRefUnderlyingObject]; } - (void) setShipHitByLaser:(ShipEntity *)ship { if (ship != [self shipHitByLaser]) { [_shipHitByLaser release]; _shipHitByLaser = [ship weakRetain]; } } - (void) noteLostTarget { id target = nil; if ([self primaryTarget] != nil) { ShipEntity* ship = [self primaryTarget]; if ([self isDefenseTarget:ship]) { [self removeDefenseTarget:ship]; } // for compatibility with 1.76 behaviour of this function, only pass // the target as a function parameter if the target is still a potential // valid target (e.g. not scooped, docked, hyperspaced, etc.) target = (ship && ship->isShip && [self isValidTarget:ship]) ? (id)ship : nil; if ([self primaryAggressor] == ship) { DESTROY(_primaryAggressor); } DESTROY(_primaryTarget); } // always do target lost [self doScriptEvent:OOJSID("shipTargetLost") withArgument:target]; if (target == nil) [shipAI message:@"TARGET_LOST"]; // stale target? no major urgency. else [shipAI reactToMessage:@"TARGET_LOST" context:@"flight updates"]; // execute immediately otherwise. } - (void) noteLostTargetAndGoIdle { behaviour = BEHAVIOUR_IDLE; frustration = 0.0; [self noteLostTarget]; } - (void) noteTargetDestroyed:(ShipEntity *)target { [self collectBountyFor:(ShipEntity *)target]; if ([self primaryTarget] == target) { [self removeTarget:target]; [self doScriptEvent:OOJSID("shipTargetDestroyed") withArgument:target]; [shipAI message:@"TARGET_DESTROYED"]; } if ([self isDefenseTarget:target]) { [self removeDefenseTarget:target]; [shipAI message:@"DEFENSE_TARGET_DESTROYED"]; [self doScriptEvent:OOJSID("defenseTargetDestroyed") withArgument:target]; } } - (OOBehaviour) behaviour { return behaviour; } - (void) setBehaviour:(OOBehaviour) cond { if (cond != behaviour) { frustration = 0.0; // change is a GOOD thing behaviour = cond; } } - (HPVector) destination { return _destination; } - (HPVector) coordinates { return coordinates; } - (void) setCoordinate:(HPVector) coord // The name "setCoordinates" is already used by AI scripting. { coordinates = coord; } - (HPVector) distance_six: (GLfloat) dist { HPVector six = position; six.x -= dist * v_forward.x; six.y -= dist * v_forward.y; six.z -= dist * v_forward.z; return six; } - (HPVector) distance_twelve: (GLfloat) dist withOffset:(GLfloat)offset { HPVector twelve = position; twelve.x += dist * v_up.x; twelve.y += dist * v_up.y; twelve.z += dist * v_up.z; twelve.x += offset * v_right.x; twelve.y += offset * v_right.y; twelve.z += offset * v_right.z; return twelve; } - (void) trackOntoTarget:(double) delta_t withDForward: (GLfloat) dp { Vector vector_to_target; Quaternion q_minarc; // Entity* target = [self primaryTarget]; // if (!target) return; vector_to_target = [self vectorTo:target]; // GLfloat range2 = magnitude2(vector_to_target); GLfloat targetRadius = 0.75 * target->collision_radius; GLfloat max_cos = sqrt(1 - targetRadius*targetRadius/range2); if (dp > max_cos) return; // ON TARGET! if (vector_to_target.x||vector_to_target.y||vector_to_target.z) vector_to_target = vector_normal(vector_to_target); else vector_to_target.z = 1.0; q_minarc = quaternion_rotation_between(v_forward, vector_to_target); orientation = quaternion_multiply(q_minarc, orientation); [self orientationChanged]; flightRoll = 0.0; flightPitch = 0.0; flightYaw = 0.0; stick_roll = 0.0; stick_pitch = 0.0; stick_yaw = 0.0; } - (double) ballTrackLeadingTarget:(double) delta_t atTarget:(Entity *)target { if (!target) { return -2.0; // no target } Vector vector_to_target; Vector axis_to_track_by; Vector my_aim = vector_forward_from_quaternion(orientation); Vector my_ref = reference; double aim_cos, ref_cos; Vector leading = [target velocity]; // need to get vector to target in terms of this entities coordinate system HPVector my_position = [self absolutePositionForSubentity]; vector_to_target = HPVectorToVector(HPvector_subtract([target position], my_position)); // this is in absolute coordinates, so now rotate it Entity *last = nil; Entity *father = [self parentEntity]; Quaternion q = kIdentityQuaternion; while ((father)&&(father != last) && (father != NO_TARGET)) { /* Fix orientation */ Quaternion fo = [father normalOrientation]; fo.w = -fo.w; /* The below code works for player turrets where the * orientation is different, but not for NPC turrets. Taking * the normal orientation with -w works: there is probably a * neater way which someone who understands quaternions can * find, but this works well enough for 1.82 - CIM */ q = quaternion_multiply(q,quaternion_conjugate(fo)); last = father; if (![last isSubEntity]) break; father = [father owner]; } q = quaternion_conjugate(q); // q now contains the rotation to the turret's reference system vector_to_target = quaternion_rotate_vector(q,vector_to_target); leading = quaternion_rotate_vector(q,leading); // rotate the vector to target and its velocity if (magnitude(vector_to_target) > weaponRange * 1.01) { return -2.0; // out of range } float lead = magnitude(vector_to_target) / TURRET_SHOT_SPEED; vector_to_target = vector_add(vector_to_target, vector_multiply_scalar(leading, lead)); vector_to_target = vector_normal_or_fallback(vector_to_target, kBasisZVector); // do the tracking! aim_cos = dot_product(vector_to_target, my_aim); ref_cos = dot_product(vector_to_target, my_ref); if (ref_cos > TURRET_MINIMUM_COS) // target is forward of self { axis_to_track_by = cross_product(vector_to_target, my_aim); } else { return -2.0; // target is out of fire arc } quaternion_rotate_about_axis(&orientation, axis_to_track_by, thrust * delta_t); [self orientationChanged]; [self setStatus:STATUS_ACTIVE]; return aim_cos; } - (void) setEvasiveJink:(GLfloat) z { if (accuracy < COMBAT_AI_ISNT_AWFUL) { jink = kZeroVector; } else { jink.x = (ranrot_rand() % 256) - 128.0; jink.y = (ranrot_rand() % 256) - 128.0; jink.z = z; if (accuracy >= COMBAT_AI_IS_SMART) { // make sure we don't accidentally have near-zero jink if (jink.x < 0.0) { jink.x -= 128.0; } else { jink.x += 128.0; } if (jink.y < 0) { jink.y -= 128.0; } else { jink.y += 128.0; } } } } - (void) evasiveAction:(double) delta_t { stick_roll = flightRoll; //desired roll and pitch stick_pitch = flightPitch; ShipEntity* target = [self primaryTarget]; if (!target) // leave now! { [self noteLostTargetAndGoIdle]; // NOTE: was AI message: rather than reactToMessage: return; } double agreement = dot_product(v_right,target->v_right); if (agreement > -0.3 && agreement < 0.3) { stick_roll = 0.0; } else { if (stick_roll >= 0.0) { stick_roll = max_flight_roll; } else { stick_roll = -max_flight_roll; } } if (stick_pitch >= 0.0) { stick_pitch = max_flight_pitch; } else { stick_pitch = -max_flight_pitch; } [self applySticks:delta_t]; } - (double) trackPrimaryTarget:(double) delta_t :(BOOL) retreat { Entity* target = [self primaryTarget]; if (!target) // leave now! { [self noteLostTargetAndGoIdle]; // NOTE: was AI message: rather than reactToMessage: return 0.0; } if (![self canStillTrackPrimaryTarget]) { [self noteLostTargetAndGoIdle]; // NOTE: was AI message: rather than reactToMessage: return 0.0; } /* 1.81 change: do the above check first: if a missile can't be * fired outside scanner range it should self-destruct if the * target gets far enough away (it's going to miss anyway) - * CIM */ if (scanClass == CLASS_MISSILE) return [self missileTrackPrimaryTarget: delta_t]; GLfloat d_forward, d_up, d_right; Vector relPos = HPVectorToVector(HPvector_subtract([self calculateTargetPosition], position)); double range2 = HPmagnitude2(HPvector_subtract([target position], position)); //jink if retreating if (retreat) // calculate jink position when flying away from target. { Vector vx, vy, vz; if (target->isShip) { ShipEntity* targetShip = (ShipEntity*)target; vx = targetShip->v_right; vy = targetShip->v_up; vz = targetShip->v_forward; } else { Quaternion q = target->orientation; vx = vector_right_from_quaternion(q); vy = vector_up_from_quaternion(q); vz = vector_forward_from_quaternion(q); } BOOL avoidCollision = NO; if (range2 < collision_radius * target->collision_radius * 100.0) // Check direction within 10 * collision radius. { Vector targetDirection = kBasisZVector; if (!vector_equal(relPos, kZeroVector)) targetDirection = vector_normal(relPos); avoidCollision = (dot_product(targetDirection, v_forward) > -0.1); // is flying toward target or only slightly outward. } GLfloat dist_adjust_factor = 1.0; if (accuracy >= COMBAT_AI_FLEES_BETTER) { double range = magnitude(relPos); if (range > 2000.0) { dist_adjust_factor = range / 2000.0; if (accuracy >= COMBAT_AI_FLEES_BETTER_2) { dist_adjust_factor *= 3; } } if (jink.x == 0.0 && behaviour != BEHAVIOUR_RUNNING_DEFENSE) { // test for zero jink and correct [self setEvasiveJink:400.0]; } } if (!avoidCollision) // it is safe to jink { relPos.x += (jink.x * vx.x + jink.y * vy.x + jink.z * vz.x) * dist_adjust_factor; relPos.y += (jink.x * vx.y + jink.y * vy.y + jink.z * vz.y) * dist_adjust_factor; relPos.z += (jink.x * vx.z + jink.y * vy.z + jink.z * vz.z); } } if (!vector_equal(relPos, kZeroVector)) relPos = vector_normal(relPos); else relPos.z = 1.0; double max_cos = [self currentAimTolerance]; stick_roll = 0.0; //desired roll and pitch stick_pitch = 0.0; double reverse = (retreat)? -1.0: 1.0; double min_d = 0.004; // ~= 40m at 10km int max_factor = 8; double r_max_factor = 0.125; if (!retreat) { if (accuracy >= COMBAT_AI_TRACKS_CLOSER) { // much greater precision in combat if (max_flight_pitch > 1.0) { max_factor = floor(max_flight_pitch/0.125); r_max_factor = 1.0/max_factor; } min_d = 0.0004; // 10 times more precision ~= 4m at 10km max_factor *= 3; r_max_factor /= 3.0; } else if (accuracy >= COMBAT_AI_ISNT_AWFUL) { // slowly improve precision to target, but only if missing min_d -= 0.0001 * [self missedShots]; if (min_d < 0.001) { min_d = 0.001; max_factor *= 2; r_max_factor /= 2.0; } } } d_right = dot_product(relPos, v_right); d_up = dot_product(relPos, v_up); d_forward = dot_product(relPos, v_forward); // == cos of angle between v_forward and vector to target if (d_forward * reverse > max_cos) // on_target! { return d_forward; } // begin rule-of-thumb manoeuvres stick_pitch = 0.0; stick_roll = 0.0; if ((reverse * d_forward < -0.5) && !pitching_over) // we're going the wrong way! pitching_over = YES; if (pitching_over) { if (reverse * d_up > 0) // pitch up stick_pitch = -max_flight_pitch; else stick_pitch = max_flight_pitch; pitching_over = (reverse * d_forward < 0.707); } // check if we are flying toward the destination.. if ((d_forward < max_cos)||(retreat)) // not on course so we must adjust controls.. { if (d_forward < -max_cos) // hack to avoid just flying away from the destination { d_up = min_d * 2.0; } if (d_up > min_d) { int factor = sqrt(fabs(d_right) / fabs(min_d)); if (factor > max_factor) factor = max_factor; if (d_right > min_d) stick_roll = - max_flight_roll * r_max_factor * factor; // note# if (d_right < -min_d) stick_roll = + max_flight_roll * r_max_factor * factor; // note# } if (d_up < -min_d) { int factor = sqrt(fabs(d_right) / fabs(min_d)); if (factor > max_factor) factor = max_factor; if (d_right > min_d) stick_roll = + max_flight_roll * r_max_factor * factor; // note# if (d_right < -min_d) stick_roll = - max_flight_roll * r_max_factor * factor; // note# } if (stick_roll == 0.0) { int factor = sqrt(fabs(d_up) / fabs(min_d)); if (factor > max_factor) factor = max_factor; if (d_up > min_d) stick_pitch = - max_flight_pitch * reverse * r_max_factor * factor; if (d_up < -min_d) stick_pitch = + max_flight_pitch * reverse * r_max_factor * factor; } if (accuracy >= COMBAT_AI_ISNT_AWFUL) { // don't overshoot target (helps accuracy at low frame rates) if (fabs(d_right) < fabs(stick_roll) * delta_t) { stick_roll = fabs(d_right) / delta_t * (stick_roll<0 ? -1 : 1); } if (fabs(d_up) < fabs(stick_pitch) * delta_t) { stick_pitch = fabs(d_up) / delta_t * (stick_pitch<0 ? -0.9 : 0.9); } } } /* # note Eric 9-9-2010: Removed the "reverse" variable from the stick_roll calculation. This was mathematical wrong and made the ship roll in the wrong direction, preventing the ship to fly away in a straight line from the target. This means all the places were a jink was set, this jink never worked correctly. The main reason a ship still managed to turn at close range was probably by the fail-safe mechanisme with the "pitching_over" variable. The jink was programmed to do nothing within 500 meters of the ship and just fly away in direct line from the target in that range. Because of the bug the ships always rolled to the wrong side needed to fly away in direct line resulting in making it a difficult target. After fixing the bug, the ship realy flew away in direct line during the first 500 meters, making it a easy target for the player. All jink settings are retested and changed to give a turning behaviour that felt like the old situation, but now more deliberately set. */ // end rule-of-thumb manoeuvres stick_yaw = 0.0; [self applySticks:delta_t]; if (retreat) d_forward *= d_forward; // make positive AND decrease granularity if (d_forward < 0.0) return 0.0; if ((!flightRoll)&&(!flightPitch)) // no correction return 1.0; return d_forward; } - (double) trackSideTarget:(double) delta_t :(BOOL) leftside { Entity* target = [self primaryTarget]; if (!target) // leave now! { [self noteLostTargetAndGoIdle]; // NOTE: was AI message: rather than reactToMessage: return 0.0; } if (![self canStillTrackPrimaryTarget]) { [self noteLostTargetAndGoIdle]; // NOTE: was AI message: rather than reactToMessage: return 0.0; } if (scanClass == CLASS_MISSILE) // never? return [self missileTrackPrimaryTarget: delta_t]; GLfloat d_forward, d_up, d_right; Vector relPos = HPVectorToVector(HPvector_subtract([self calculateTargetPosition], position)); if (!vector_equal(relPos, kZeroVector)) relPos = vector_normal(relPos); else relPos.z = 1.0; // worse shots with side lasers than fore/aft, in general double max_cos = [self currentAimTolerance]; stick_roll = 0.0; //desired roll and pitch stick_pitch = 0.0; stick_yaw = 0.0; double reverse = (leftside)? -1.0: 1.0; double min_d = 0.004; if (accuracy >= COMBAT_AI_TRACKS_CLOSER) { min_d = 0.002; } int max_factor = 8; double r_max_factor = 0.125; d_right = dot_product(relPos, v_right); d_up = dot_product(relPos, v_up); d_forward = dot_product(relPos, v_forward); // == cos of angle between v_forward and vector to target if (d_right * reverse > max_cos) // on_target! { return d_right * reverse; } // begin rule-of-thumb manoeuvres stick_pitch = 0.0; stick_roll = 0.0; stick_yaw = 0.0; // check if we are flying toward the destination.. if ((d_right * reverse < max_cos)) // not on course so we must adjust controls.. { if (d_right < -max_cos) // hack to avoid just pointing away from the destination { d_forward = min_d * 2.0; } if (d_forward > min_d) { int factor = sqrt(fabs(d_up) / fabs(min_d)); if (factor > max_factor) factor = max_factor; if (d_up > min_d) stick_pitch = + max_flight_pitch * r_max_factor * factor; // note# if (d_up < -min_d) stick_pitch = - max_flight_pitch * r_max_factor * factor; // note# } if (d_forward < -min_d) { int factor = sqrt(fabs(d_up) / fabs(min_d)); if (factor > max_factor) factor = max_factor; if (d_up > min_d) stick_pitch = + max_flight_pitch * r_max_factor * factor; // note# if (d_up < -min_d) stick_pitch = - max_flight_pitch * r_max_factor * factor; // note# } if (fabs(stick_pitch) == 0.0 || fabs(d_forward) > 0.5) { stick_pitch = 0.0; int factor = sqrt(fabs(d_forward) / fabs(min_d)); if (factor > max_factor) factor = max_factor; if (d_forward > min_d) stick_yaw = - max_flight_yaw * reverse * r_max_factor * factor; if (d_forward < -min_d) { if (factor < max_factor/2.0) // compensate for forward thrust factor *= 2.0; stick_yaw = + max_flight_yaw * reverse * r_max_factor * factor; } } } // end rule-of-thumb manoeuvres [self applySticks:delta_t]; if ((!flightPitch)&&(!flightYaw)) // no correction return 1.0; return d_right * reverse; } - (double) missileTrackPrimaryTarget:(double) delta_t { Vector relPos; GLfloat d_forward, d_up, d_right; ShipEntity *target = [self primaryTarget]; BOOL inPursuit = YES; if (!target || ![target isShip]) // leave now! return 0.0; double damping = 0.5 * delta_t; stick_roll = 0.0; //desired roll and pitch stick_pitch = 0.0; stick_yaw = 0.0; relPos = [self vectorTo:target]; // Adjust missile course by taking into account target's velocity and missile // accuracy. Modification on original code contributed by Cmdr James. float missileSpeed = (float)[self speed]; // Avoid getting ourselves in a divide by zero situation by setting a missileSpeed // low threshold. Arbitrarily chosen 0.01, since it seems to work quite well. // Missile accuracy is already clamped within the 0.0 to 10.0 range at initialization, // but doing these calculations every frame when accuracy equals 0.0 just wastes cycles. if (missileSpeed > 0.01f && accuracy > 0.0f) { inPursuit = (dot_product([target forwardVector], v_forward) > 0.0f); if (inPursuit) { Vector leading = [target velocity]; float lead = magnitude(relPos) / missileSpeed; // Adjust where we are going to take into account target's velocity. // Use accuracy value to determine how well missile will track target. relPos.x += (lead * leading.x * (accuracy / 10.0f)); relPos.y += (lead * leading.y * (accuracy / 10.0f)); relPos.z += (lead * leading.z * (accuracy / 10.0f)); } } if (!vector_equal(relPos, kZeroVector)) relPos = vector_normal(relPos); else relPos.z = 1.0; d_right = dot_product(relPos, v_right); // = cosine of angle between angle to target and v_right d_up = dot_product(relPos, v_up); // = cosine of angle between angle to target and v_up d_forward = dot_product(relPos, v_forward); // = cosine of angle between angle to target and v_forward // begin rule-of-thumb manoeuvres stick_roll = 0.0; if (pitching_over) pitching_over = (stick_pitch != 0.0); if ((d_forward < -pitch_tolerance) && (!pitching_over)) { pitching_over = YES; if (d_up >= 0) stick_pitch = -max_flight_pitch; if (d_up < 0) stick_pitch = max_flight_pitch; } if (pitching_over) { pitching_over = (d_forward < 0.5); } else { stick_pitch = -max_flight_pitch * d_up; stick_roll = -max_flight_roll * d_right; } // end rule-of-thumb manoeuvres // apply damping if (flightRoll < 0) flightRoll += (flightRoll < -damping) ? damping : -flightRoll; if (flightRoll > 0) flightRoll -= (flightRoll > damping) ? damping : flightRoll; if (flightPitch < 0) flightPitch += (flightPitch < -damping) ? damping : -flightPitch; if (flightPitch > 0) flightPitch -= (flightPitch > damping) ? damping : flightPitch; [self applySticks:delta_t]; // // return target confidence 0.0 .. 1.0 // if (d_forward < 0.0) return 0.0; return d_forward; } - (double) trackDestination:(double) delta_t :(BOOL) retreat { Vector relPos; GLfloat d_forward, d_up, d_right; BOOL we_are_docking = (nil != dockingInstructions); stick_roll = 0.0; //desired roll and pitch stick_pitch = 0.0; stick_yaw = 0.0; double reverse = 1.0; double reversePlayer = 1.0; double min_d = 0.004; double max_cos = MAX_COS; // should match default value of max_cos in behaviour_fly_to_destination! double precision = we_are_docking ? 0.25 : 0.9025; // lower values force a direction closer to the target. (resp. 50% and 95% within range) if (retreat) reverse = -reverse; if (isPlayer) { reverse = -reverse; reversePlayer = -1; } relPos = HPVectorToVector(HPvector_subtract(_destination, position)); double range2 = magnitude2(relPos); double desired_range2 = desired_range*desired_range; /* 2009-7-18 Eric: We need to aim well inide the desired_range sphere round the target and not at the surface of the sphere. Because of the framerate most ships normally overshoot the target and they end up flying clearly on a path through the sphere. Those ships give no problems, but ships with a very low turnrate will aim close to the surface and will than have large trouble with reaching their destination. When those ships enter the slowdown range, they have almost no speed vector in the direction of the target. I now used 95% of desired_range to aim at, but a smaller value might even be better. */ if (range2 > desired_range2) { max_cos = sqrt(1 - precision * desired_range2/range2); // Head for a point within 95% of desired_range. if (max_cos >= 0.99999) { max_cos = 0.99999; } } if (!vector_equal(relPos, kZeroVector)) relPos = vector_normal(relPos); else relPos.z = 1.0; d_right = dot_product(relPos, v_right); d_up = dot_product(relPos, v_up); d_forward = dot_product(relPos, v_forward); // == cos of angle between v_forward and vector to target // begin rule-of-thumb manoeuvres stick_pitch = 0.0; stick_roll = 0.0; // pitching_over is currently only set in behaviour_formation_form_up, for escorts and in avoidCollision. // This allows for immediate pitch corrections instead of first waiting untill roll has completed. if (pitching_over) { if (reverse * d_up > 0) // pitch up stick_pitch = -max_flight_pitch; else stick_pitch = max_flight_pitch; pitching_over = (reverse * d_forward < 0.707); } // check if we are flying toward (or away from) the destination.. if ((d_forward < max_cos)||(retreat)) // not on course so we must adjust controls.. { if (d_forward <= -max_cos || (retreat && d_forward >= max_cos)) // hack to avoid just flying away from the destination { d_up = min_d * 2.0; } if (d_up > min_d) { int factor = sqrt(fabs(d_right) / fabs(min_d)); if (factor > 8) factor = 8; if (d_right > min_d) stick_roll = - max_flight_roll * reversePlayer * 0.125 * factor; // only reverse sign for the player; if (d_right < -min_d) stick_roll = + max_flight_roll * reversePlayer * 0.125 * factor; if (fabs(d_right) < fabs(stick_roll) * delta_t) stick_roll = fabs(d_right) / delta_t * (stick_roll<0 ? -1 : 1); // don't overshoot heading } if (d_up < -min_d) { int factor = sqrt(fabs(d_right) / fabs(min_d)); if (factor > 8) factor = 8; if (d_right > min_d) stick_roll = + max_flight_roll * reversePlayer * 0.125 * factor; // only reverse sign for the player; if (d_right < -min_d) stick_roll = - max_flight_roll * reversePlayer * 0.125 * factor; if (fabs(d_right) < fabs(stick_roll) * delta_t) stick_roll = fabs(d_right) / delta_t * (stick_roll<0 ? -1 : 1); // don't overshoot heading } if (stick_roll == 0.0) { int factor = sqrt(fabs(d_up) / fabs(min_d)); if (factor > 8) factor = 8; if (d_up > min_d) stick_pitch = - max_flight_pitch * reverse * 0.125 * factor; //pitch_pitch * reverse; if (d_up < -min_d) stick_pitch = + max_flight_pitch * reverse * 0.125 * factor; if (fabs(d_up) < fabs(stick_pitch) * delta_t) stick_pitch = fabs(d_up) / delta_t * (stick_pitch<0 ? -1 : 1); // don't overshoot heading } if (stick_pitch == 0.0) { // not sufficiently on course yet, but min_d is too high // turn anyway slightly to adjust stick_pitch = 0.01; } } if (we_are_docking && docking_match_rotation && (d_forward > max_cos)) { /* we are docking and need to consider the rotation/orientation of the docking port */ StationEntity* station_for_docking = (StationEntity*)[self targetStation]; if ((station_for_docking)&&(station_for_docking->isStation)) { stick_roll = [self rollToMatchUp:[station_for_docking portUpVectorForShip:self] rotating:[station_for_docking flightRoll]]; } } // end rule-of-thumb manoeuvres [self applySticks:delta_t]; if (retreat) d_forward *= d_forward; // make positive AND decrease granularity if (d_forward < 0.0) return 0.0; if ((!flightRoll)&&(!flightPitch)) // no correction return 1.0; return d_forward; } - (GLfloat) rollToMatchUp:(Vector)up_vec rotating:(GLfloat)match_roll { GLfloat cosTheta = dot_product(up_vec, v_up); // == cos of angle between up vectors GLfloat sinTheta = dot_product(up_vec, v_right); if (!isPlayer) { match_roll = -match_roll; // make necessary corrections for a different viewpoint sinTheta = -sinTheta; } if (cosTheta < 0.0f) { cosTheta = -cosTheta; sinTheta = -sinTheta; } if (sinTheta > 0.0f) { // increase roll rate return cosTheta * cosTheta * match_roll + sinTheta * sinTheta * max_flight_roll; } else { // decrease roll rate return cosTheta * cosTheta * match_roll - sinTheta * sinTheta * max_flight_roll; } } - (GLfloat) rangeToDestination { return HPdistance(position, _destination); } - (NSArray *) collisionExceptions { if (_collisionExceptions == nil) { return [NSArray array]; } return [_collisionExceptions allObjects]; } - (void) addCollisionException:(ShipEntity *)ship { if (_collisionExceptions == nil) { // Allocate lazily for the benefit of the ships that never need this. _collisionExceptions = [[OOWeakSet alloc] init]; } [_collisionExceptions addObject:ship]; } - (void) removeCollisionException:(ShipEntity *)ship { if (_collisionExceptions != nil) { [_collisionExceptions removeObject:ship]; } } - (BOOL) collisionExceptedFor:(ShipEntity *)ship { if (_collisionExceptions == nil) { return NO; } return [_collisionExceptions containsObject:ship]; } - (NSUInteger) defenseTargetCount { return [_defenseTargets count]; } - (NSArray *) allDefenseTargets { return [_defenseTargets allObjects]; } - (NSEnumerator *) defenseTargetEnumerator { return [_defenseTargets objectEnumerator]; } - (BOOL) addDefenseTarget:(Entity *)target { if ([self defenseTargetCount] >= MAX_TARGETS) { return NO; } // primary target can be a wormhole, defense targets shouldn't be if (target == nil || [self isDefenseTarget:target] || ![target isShip]) { return NO; } if (_defenseTargets == nil) { // Allocate lazily for the benefit of the ships that never get in fights. _defenseTargets = [[OOWeakSet alloc] init]; } [_defenseTargets addObject:target]; return YES; } - (void) validateDefenseTargets { if (_defenseTargets == nil) { return; } // get enumerator from array as we'll be modifying original during enumeration NSEnumerator *defTargets = [[self allDefenseTargets] objectEnumerator]; Entity *target = nil; while ((target = [[defTargets nextObject] weakRefUnderlyingObject])) { if ([target status] == STATUS_DEAD) { [self removeDefenseTarget:target]; } } } - (BOOL) isDefenseTarget:(Entity *)target { return [_defenseTargets containsObject:target]; } // exposed to AI (as alias of clearDefenseTargets) - (void) removeAllDefenseTargets { [_defenseTargets removeAllObjects]; } - (void) removeDefenseTarget:(Entity *)target { [_defenseTargets removeObject:target]; } - (double) rangeToPrimaryTarget { return [self rangeToSecondaryTarget:[self primaryTarget]]; } - (double) rangeToSecondaryTarget:(Entity *)target { double dist; Vector delta; if (target == nil) // leave now! return 0.0; delta = HPVectorToVector(HPvector_subtract(target->position, position)); dist = magnitude(delta); dist -= target->collision_radius; dist -= collision_radius; return dist; } - (double) approachAspectToPrimaryTarget { Vector delta; Entity *target = [self primaryTarget]; if (target == nil || ![target isShip]) // leave now! { return 0.0; } ShipEntity *ship_target = (ShipEntity *)target; delta = HPVectorToVector(HPvector_subtract(position, target->position)); return dot_product(vector_normal(delta), ship_target->v_forward); } - (BOOL) hasProximityAlertIgnoringTarget:(BOOL)ignore_target { if (([self proximityAlert] != nil)&&(!ignore_target || ([self proximityAlert] != [self primaryTarget]))) { return YES; } return NO; } // lower is better. Defines angular size of circle in which ship // thinks is on target - (GLfloat) currentAimTolerance { GLfloat basic_aim = aim_tolerance; GLfloat best_cos = 0.99999; // ~45m in 10km (track won't go better than 40) if (accuracy >= COMBAT_AI_ISNT_AWFUL) { // better general targeting best_cos = 0.999999; // ~14m in 10km (track won't go better than 10) // if missing, aim better! basic_aim /= 1.0 + ((GLfloat)[self missedShots] / 4.0); } if (accuracy >= COMBAT_AI_TRACKS_CLOSER) { // deadly shots best_cos = 0.9999999; // ~4m in 10km (track won't go better than 4) // and start with extremely good aim circle basic_aim /= 5.0; } if (currentWeaponFacing == WEAPON_FACING_AFT && accuracy < COMBAT_AI_ISNT_AWFUL) { // bad shots with aft lasers basic_aim *= 1.3; } else if (currentWeaponFacing == WEAPON_FACING_PORT || currentWeaponFacing == WEAPON_FACING_STARBOARD) { // everyone a bit worse with side lasers if (accuracy < COMBAT_AI_ISNT_AWFUL) { // especially these basic_aim *= 1.3 + randf(); } else { basic_aim *= 1.3; } } // only apply glare if ship is not shadowed if (isSunlit) { OOSunEntity *sun = [UNIVERSE sun]; if (sun) { GLfloat sunGlareAngularSize = atan([sun radius]/HPdistance([self position], [sun position])) * SUN_GLARE_MULT_FACTOR + (SUN_GLARE_ADD_FACTOR); GLfloat glareLevel = [self lookingAtSunWithThresholdAngleCos:cos(sunGlareAngularSize)] * (1.0f - [self sunGlareFilter]); if (glareLevel > 0.1f) { // looking towards sun can seriously mess up aim (glareLevel 0..1) basic_aim *= (1.0 + glareLevel*3.0); // OOLog(@"aim.debug",@"Sun glare affecting aim: %f for %@",glareLevel,self); if (glareLevel > 0.5f) { // strong glare makes precise targeting impossible best_cos = 0.99999; } } } } GLfloat max_cos = sqrt(1-(basic_aim * basic_aim / 100000000.0)); if (max_cos < best_cos) { return max_cos; } return best_cos; } // much simpler than player version - (GLfloat) lookingAtSunWithThresholdAngleCos:(GLfloat) thresholdAngleCos { OOSunEntity *sun = [UNIVERSE sun]; GLfloat measuredCos = 999.0f, measuredCosAbs; GLfloat sunBrightness = 0.0f; Vector relativePosition, unitRelativePosition; if (EXPECT_NOT(!sun)) return 0.0f; relativePosition = HPVectorToVector(HPvector_subtract([self position], [sun position])); unitRelativePosition = vector_normal_or_zbasis(relativePosition); switch (currentWeaponFacing) { case WEAPON_FACING_FORWARD: measuredCos = -dot_product(unitRelativePosition, v_forward); break; case WEAPON_FACING_AFT: measuredCos = +dot_product(unitRelativePosition, v_forward); break; case WEAPON_FACING_PORT: measuredCos = +dot_product(unitRelativePosition, v_right); break; case WEAPON_FACING_STARBOARD: measuredCos = -dot_product(unitRelativePosition, v_right); break; default: break; } measuredCosAbs = fabs(measuredCos); if (thresholdAngleCos <= measuredCosAbs && measuredCosAbs <= 1.0f) // angle from viewpoint to sun <= desired threshold { sunBrightness = (measuredCos - thresholdAngleCos) / (1.0f - thresholdAngleCos); if (sunBrightness < 0.0f) sunBrightness = 0.0f; } return sunBrightness * sunBrightness * sunBrightness; } - (BOOL) onTarget:(OOWeaponFacing)direction withWeapon:(OOWeaponType)weapon_type { // initialize dq to a value that would normally return NO; dq is handled inside the defaultless switch(direction) statement // and should alaways be recalculated anyway. Initialization here needed to silence compiler warning - Nikos 20120526 GLfloat dq = -1.0f; GLfloat d2, radius, astq; Vector rel_pos, urp; if ([weapon_type isTurretLaser]) { return YES; } Entity *target = [self primaryTarget]; if (target == nil) return NO; if ([target status] == STATUS_DEAD) return NO; if (isSunlit && (target->isSunlit == NO) && (randf() < 0.75)) { return NO; // 3/4 of the time you can't see from a lit place into a darker place } radius = target->collision_radius; rel_pos = HPVectorToVector(HPvector_subtract([self calculateTargetPosition], position)); d2 = magnitude2(rel_pos); urp = vector_normal_or_zbasis(rel_pos); switch (direction) { case WEAPON_FACING_FORWARD: dq = +dot_product(urp, v_forward); // cosine of angle between v_forward and unit relative position break; case WEAPON_FACING_AFT: dq = -dot_product(urp, v_forward); // cosine of angle between v_forward and unit relative position break; case WEAPON_FACING_PORT: dq = -dot_product(urp, v_right); // cosine of angle between v_right and unit relative position break; case WEAPON_FACING_STARBOARD: dq = +dot_product(urp, v_right); // cosine of angle between v_right and unit relative position break; case WEAPON_FACING_NONE: break; } if (dq < 0.0) return NO; GLfloat aim = [self currentAimTolerance]; if (dq > aim*aim) return YES; // cosine of 1/3 of half angle subtended by target (mostly they'll // fire sooner anyway due to currentAimTolerance, but this should // almost always be a solid hit) astq = sqrt(1.0 - radius * radius / (d2 * 9)); return (fabs(dq) >= astq); } - (BOOL) fireWeapon:(OOWeaponType)weapon_type direction:(OOWeaponFacing)direction range:(double)range { weapon_temp = 0.0; switch (direction) { case WEAPON_FACING_FORWARD: weapon_temp = forward_weapon_temp; break; case WEAPON_FACING_AFT: weapon_temp = aft_weapon_temp; break; case WEAPON_FACING_PORT: weapon_temp = port_weapon_temp; break; case WEAPON_FACING_STARBOARD: weapon_temp = starboard_weapon_temp; break; case WEAPON_FACING_NONE: break; } if (weapon_temp / NPC_MAX_WEAPON_TEMP >= WEAPON_COOLING_CUTOUT) return NO; if (energy <= weapon_energy_use) return NO; if ([self shotTime] < weapon_recharge_rate) return NO; if (![weapon_type isTurretLaser]) { // thargoid laser may just pick secondary target in this case if (range > randf() * weaponRange * (accuracy+7.5)) return NO; if (range > weaponRange) return NO; } if (![self onTarget:direction withWeapon:weapon_type]) return NO; BOOL fired = NO; if (!isWeaponNone(weapon_type)) { if ([weapon_type isTurretLaser]) { [self fireDirectLaserShot:range]; fired = YES; } else { [self fireLaserShotInDirection:direction]; fired = YES; } } if (fired) { energy -= weapon_energy_use; switch (direction) { case WEAPON_FACING_FORWARD: forward_weapon_temp += weapon_shot_temperature; break; case WEAPON_FACING_AFT: aft_weapon_temp += weapon_shot_temperature; break; case WEAPON_FACING_PORT: port_weapon_temp += weapon_shot_temperature; break; case WEAPON_FACING_STARBOARD: starboard_weapon_temp += weapon_shot_temperature; break; case WEAPON_FACING_NONE: break; } } if (direction == WEAPON_FACING_FORWARD) { //can we fire lasers from our subentities? NSEnumerator *subEnum = nil; ShipEntity *se = nil; for (subEnum = [self shipSubEntityEnumerator]; (se = [subEnum nextObject]); ) { if ([se fireSubentityLaserShot:range]) fired = YES; } } if (fired && cloaking_device_active && cloakPassive) { [self deactivateCloakingDevice]; } return fired; } - (BOOL) fireMainWeapon:(double)range { // set the values from forward_weapon_type. // OXPs can override the default front laser energy damage. currentWeaponFacing = WEAPON_FACING_FORWARD; [self setWeaponDataFromType:forward_weapon_type]; // weapon damage override no longer effective // weapon_damage = weapon_damage_override; BOOL result = [self fireWeapon:forward_weapon_type direction:WEAPON_FACING_FORWARD range:range]; if (isWeaponNone(forward_weapon_type)) { // need to check subentities to avoid AI oddities // will already have fired them by now, though NSEnumerator *subEnum = [self shipSubEntityEnumerator]; ShipEntity *se = nil; OOWeaponType weapon_type = nil; BOOL hasTurrets = NO; while (isWeaponNone(weapon_type) && (se = [subEnum nextObject])) { weapon_type = se->forward_weapon_type; weapon_temp = se->forward_weapon_temp; if (se->behaviour == BEHAVIOUR_TRACK_AS_TURRET) { hasTurrets = YES; } } if (isWeaponNone(weapon_type) && hasTurrets) { // no forward weapon but has turrets, so set up range calculations accordingly weaponRange = 10000.0; } else { [self setWeaponDataFromType:weapon_type]; } } return result; } - (BOOL) fireAftWeapon:(double)range { // set the values from aft_weapon_type. currentWeaponFacing = WEAPON_FACING_AFT; [self setWeaponDataFromType:aft_weapon_type]; return [self fireWeapon:aft_weapon_type direction:WEAPON_FACING_AFT range:range]; } - (BOOL) firePortWeapon:(double)range { // set the values from port_weapon_type. currentWeaponFacing = WEAPON_FACING_PORT; [self setWeaponDataFromType:port_weapon_type]; return [self fireWeapon:port_weapon_type direction:WEAPON_FACING_PORT range:range]; } - (BOOL) fireStarboardWeapon:(double)range { // set the values from starboard_weapon_type. currentWeaponFacing = WEAPON_FACING_STARBOARD; [self setWeaponDataFromType:starboard_weapon_type]; return [self fireWeapon:starboard_weapon_type direction:WEAPON_FACING_STARBOARD range:range]; } - (OOTimeDelta) shotTime { return shot_time; } - (void) resetShotTime { shot_time = 0.0; } - (BOOL) fireTurretCannon:(double) range { if ([self shotTime] < weapon_recharge_rate) return NO; if (range > weaponRange * 1.01) // 1% more than max range - open up just slightly early return NO; ShipEntity *root = [self rootShipEntity]; if ([root isPlayer] && ![PLAYER weaponsOnline]) return NO; if ([root isCloaked] && [root cloakPassive]) { // can't fire turrets while cloaked return NO; } Vector vel; HPVector origin = [self position]; Entity *last = nil; Entity *father = [self parentEntity]; OOMatrix r_mat; vel = vector_forward_from_quaternion(orientation); // Facing // adjust velocity and position vectors to absolute coordinates while ((father)&&(father != last) && (father != NO_TARGET)) { r_mat = [father drawRotationMatrix]; origin = HPvector_add(OOHPVectorMultiplyMatrix(origin, r_mat), [father position]); vel = OOVectorMultiplyMatrix(vel, r_mat); last = father; if (![last isSubEntity]) break; father = [father owner]; } origin = HPvector_add(origin, vectorToHPVector(vector_multiply_scalar(vel, collision_radius + 0.5))); // Start just outside collision sphere vel = vector_multiply_scalar(vel, TURRET_SHOT_SPEED); // Shot velocity OOPlasmaShotEntity *shot = [[OOPlasmaShotEntity alloc] initWithPosition:origin velocity:vel energy:weapon_damage duration:weaponRange/TURRET_SHOT_SPEED color:laser_color]; [shot autorelease]; [UNIVERSE addEntity:shot]; [shot setOwner:[self rootShipEntity]]; // has to be done AFTER adding shot to the UNIVERSE [self resetShotTime]; return YES; } - (void) setLaserColor:(OOColor *) color { if (color) { [laser_color release]; laser_color = [color retain]; } } - (void) setExhaustEmissiveColor:(OOColor *) color { if (color) { [exhaust_emissive_color release]; exhaust_emissive_color = [color retain]; } } - (OOColor *)laserColor { return [[laser_color retain] autorelease]; } - (OOColor *)exhaustEmissiveColor { return [[exhaust_emissive_color retain] autorelease]; } - (BOOL) fireSubentityLaserShot:(double)range { [self setShipHitByLaser:nil]; if (isWeaponNone(forward_weapon_type)) return NO; [self setWeaponDataFromType:forward_weapon_type]; ShipEntity *parent = [self owner]; NSAssert([parent isShipWithSubEntityShip:self], @"-fireSubentityLaserShot: called on ship which is not a subentity."); // subentity lasers still draw power from the main entity if ([parent energy] <= weapon_energy_use) return NO; if ([self shotTime] < weapon_recharge_rate) return NO; if (forward_weapon_temp > WEAPON_COOLING_CUTOUT * NPC_MAX_WEAPON_TEMP) return NO; if (range > weaponRange) return NO; forward_weapon_temp += weapon_shot_temperature; [parent setEnergy:([parent energy] - weapon_energy_use)]; GLfloat hitAtRange = weaponRange; OOWeaponFacing direction = WEAPON_FACING_FORWARD; ShipEntity *victim = [UNIVERSE firstShipHitByLaserFromShip:self inDirection:direction offset:kZeroVector gettingRangeFound:&hitAtRange]; [self setShipHitByLaser:victim]; OOLaserShotEntity *shot = [OOLaserShotEntity laserFromShip:self direction:direction offset:kZeroVector]; [shot setColor:laser_color]; [shot setScanClass:CLASS_NO_DRAW]; if (victim != nil) { [self adjustMissedShots:-1]; if ([self isPlayer]) { [PLAYER addRoleForAggression:victim]; } ShipEntity *subent = [victim subEntityTakingDamage]; if (subent != nil && [victim isFrangible]) { // do 1% bleed-through damage... [victim takeEnergyDamage:0.01 * weapon_damage from:self becauseOf:parent]; victim = subent; } if (hitAtRange < weaponRange) { [victim takeEnergyDamage:weapon_damage from:self becauseOf:parent]; // a very palpable hit [shot setRange:hitAtRange]; Vector vd = vector_forward_from_quaternion([shot orientation]); HPVector flash_pos = HPvector_add([shot position], vectorToHPVector(vector_multiply_scalar(vd, hitAtRange))); [UNIVERSE addLaserHitEffectsAt:flash_pos against:victim damage:weapon_damage color:laser_color]; } } else { [self adjustMissedShots:+1]; // see ATTACKER_MISSED section of main entity laser routine if (![parent isCloaked]) { victim = [parent primaryTarget]; Vector shotDirection = vector_forward_from_quaternion([shot orientation]); Vector victimDirection = vector_normal(HPVectorToVector(HPvector_subtract([victim position], [parent position]))); if (dot_product(shotDirection, victimDirection) > 0.995) // Within 84.26 degrees { if ([self isPlayer]) { [PLAYER addRoleForAggression:victim]; } [victim setPrimaryAggressor:parent]; [victim setFoundTarget:parent]; [victim reactToAIMessage:@"ATTACKER_MISSED" context:@"attacker narrowly misses"]; [victim doScriptEvent:OOJSID("shipBeingAttackedUnsuccessfully") withArgument:parent]; } } } [UNIVERSE addEntity:shot]; [self resetShotTime]; return YES; } - (BOOL) fireDirectLaserShot:(double)range { Entity *my_target = [self primaryTarget]; if (my_target == nil) return [self fireDirectLaserDefensiveShot]; if (range > randf() * weaponRange * (accuracy+5.5)) return [self fireDirectLaserDefensiveShot]; if (range > weaponRange) return [self fireDirectLaserDefensiveShot]; return [self fireDirectLaserShotAt:my_target]; } - (BOOL) fireDirectLaserDefensiveShot { NSEnumerator *targetEnum = [self defenseTargetEnumerator]; Entity *target = nil; while ((target = [[targetEnum nextObject] weakRefUnderlyingObject])) { // can't fire defensively at cloaked ships if ([target scanClass] == CLASS_NO_DRAW || [(ShipEntity *)target isCloaked] || [target energy] <= 0.0) { [self removeDefenseTarget:target]; } else { double range = [self rangeToSecondaryTarget:target]; if (range < weaponRange) { return [self fireDirectLaserShotAt:target]; } else if (range > scannerRange) { [self removeDefenseTarget:target]; } } } return NO; } - (BOOL) fireDirectLaserShotAt:(Entity *)my_target { GLfloat hit_at_range; double range_limit2 = weaponRange*weaponRange; Vector r_pos; r_pos = vector_normal_or_zbasis([self vectorTo:my_target]); Quaternion q_laser = quaternion_rotation_between(r_pos, kBasisZVector); GLfloat acc_factor = (10.0 - accuracy) * 0.001; q_laser.x += acc_factor * (randf() - 0.5); // randomise aim a little (+/- 0.005 at accuracy 0, never miss at accuracy 10) q_laser.y += acc_factor * (randf() - 0.5); q_laser.z += acc_factor * (randf() - 0.5); quaternion_normalize(&q_laser); Quaternion q_save = orientation; // save rotation orientation = q_laser; // face in direction of laser // weapon offset for thargoid lasers is always zero ShipEntity *victim = [UNIVERSE firstShipHitByLaserFromShip:self inDirection:WEAPON_FACING_FORWARD offset:kZeroVector gettingRangeFound:&hit_at_range]; [self setShipHitByLaser:victim]; orientation = q_save; // restore rotation Vector vel = vector_multiply_scalar(v_forward, flightSpeed); // do special effects laser line OOLaserShotEntity *shot = [OOLaserShotEntity laserFromShip:self direction:WEAPON_FACING_FORWARD offset:kZeroVector]; [shot setColor:laser_color]; [shot setScanClass: CLASS_NO_DRAW]; [shot setPosition: position]; [shot setOrientation: q_laser]; [shot setVelocity: vel]; if (victim != nil) { ShipEntity *subent = [victim subEntityTakingDamage]; if (subent != nil && [victim isFrangible]) { // do 1% bleed-through damage... [victim takeEnergyDamage: 0.01 * weapon_damage from:self becauseOf:self]; victim = subent; } if (hit_at_range * hit_at_range < range_limit2) { [victim takeEnergyDamage:weapon_damage from:self becauseOf:self]; // a very palpable hit [shot setRange:hit_at_range]; Vector vd = vector_forward_from_quaternion([shot orientation]); HPVector flash_pos = HPvector_add([shot position], vectorToHPVector(vector_multiply_scalar(vd, hit_at_range))); [UNIVERSE addLaserHitEffectsAt:flash_pos against:victim damage:weapon_damage color:laser_color]; } } [UNIVERSE addEntity:shot]; [self resetShotTime]; return YES; } - (Vector) laserPortOffset:(OOWeaponFacing)direction { Vector laserPortOffset = kZeroVector; switch (direction) { case WEAPON_FACING_FORWARD: case WEAPON_FACING_NONE: laserPortOffset = forwardWeaponOffset; break; case WEAPON_FACING_AFT: laserPortOffset = aftWeaponOffset; break; case WEAPON_FACING_PORT: laserPortOffset = portWeaponOffset; break; case WEAPON_FACING_STARBOARD: laserPortOffset = starboardWeaponOffset; break; } return laserPortOffset; } - (BOOL) fireLaserShotInDirection:(OOWeaponFacing)direction { double range_limit2 = weaponRange * weaponRange; GLfloat hit_at_range; Vector vel = vector_multiply_scalar(v_forward, flightSpeed); Vector laserPortOffset = [self laserPortOffset:direction]; last_shot_time = [UNIVERSE getTime]; ShipEntity *victim = [UNIVERSE firstShipHitByLaserFromShip:self inDirection:direction offset:laserPortOffset gettingRangeFound:&hit_at_range]; [self setShipHitByLaser:victim]; OOLaserShotEntity *shot = [OOLaserShotEntity laserFromShip:self direction:direction offset:laserPortOffset]; [shot setColor:laser_color]; [shot setScanClass: CLASS_NO_DRAW]; [shot setVelocity: vel]; if (victim != nil) { [self adjustMissedShots:-1]; if ([self isPlayer]) { [PLAYER addRoleForAggression:victim]; } /* CRASH in [victim->sub_entities containsObject:subent] here (1.69, OS X/x86). Analysis: Crash is in _freedHandler called from CFEqual, indicating either a dead object in victim->sub_entities or dead victim->subentity_taking_damage. I suspect the latter. Probable solution: dying subentities must cause parent to clean up properly. This was probably obscured by the entity recycling scheme in the past. Fix: made subentity_taking_damage a weak reference accessed via a method. -- Ahruman 20070706, 20080304 */ ShipEntity *subent = [victim subEntityTakingDamage]; if (subent != nil && [victim isFrangible]) { // do 1% bleed-through damage... [victim takeEnergyDamage: 0.01 * weapon_damage from:self becauseOf:self]; victim = subent; } if (hit_at_range * hit_at_range < range_limit2) { [victim takeEnergyDamage:weapon_damage from:self becauseOf:self]; // a very palpable hit [shot setRange:hit_at_range]; Vector vd = vector_forward_from_quaternion([shot orientation]); HPVector flash_pos = HPvector_add([shot position], vectorToHPVector(vector_multiply_scalar(vd, hit_at_range))); [UNIVERSE addLaserHitEffectsAt:flash_pos against:victim damage:weapon_damage color:laser_color]; } } else { [self adjustMissedShots:+1]; // shot missed if (![self isCloaked]) { victim = [self primaryTarget]; if ([victim isShip]) // it might not be - fixes crash bug { /* player currently gets a bit of an advantage here if they ambush * without having their target actually targeted. Though in those * circumstances they shouldn't be missing their first shot * anyway. */ if (dot_product(vector_forward_from_quaternion([shot orientation]),vector_normal([self vectorTo:victim])) > 0.995) { /* plausibly aimed at target. Allows reaction before attacker * actually hits. But we need to be able to distinguish in AI * from ATTACKED so that ships in combat aren't bothered by * amateurs. So should only respond to ATTACKER_MISSED if not * already fighting */ if ([self isPlayer]) { [PLAYER addRoleForAggression:victim]; } [victim setPrimaryAggressor:self]; [victim setFoundTarget:self]; [victim reactToAIMessage:@"ATTACKER_MISSED" context:@"attacker narrowly misses"]; [victim doScriptEvent:OOJSID("shipBeingAttackedUnsuccessfully") withArgument:self]; } } } } [UNIVERSE addEntity:shot]; if ([self isPlayer]) { [(PlayerEntity *)self setLastShot:shot]; } [self resetShotTime]; return YES; } - (void) adjustMissedShots:(int) delta { if ([self isSubEntity]) { [[self owner] adjustMissedShots:delta]; } else { _missed_shots += delta; if (_missed_shots < 0) { _missed_shots = 0; } } } - (int) missedShots { if ([self isSubEntity]) { return [[self owner] missedShots]; } else { return _missed_shots; } } - (void) throwSparks { Vector offset = { randf() * (boundingBox.max.x - boundingBox.min.x) + boundingBox.min.x, randf() * (boundingBox.max.y - boundingBox.min.y) + boundingBox.min.y, randf() * boundingBox.max.z + boundingBox.min.z // rear section only }; HPVector origin = HPvector_add(position, vectorToHPVector(quaternion_rotate_vector([self normalOrientation], offset))); float w = boundingBox.max.x - boundingBox.min.x; float h = boundingBox.max.y - boundingBox.min.y; float m = (w < h) ? 0.25 * w: 0.25 * h; float sz = m * (1 + randf() + randf()); // half minimum dimension on average Vector vel = vector_multiply_scalar(HPVectorToVector(HPvector_subtract(origin, position)), 2.0); OOColor *color = [OOColor colorWithHue:0.08 + 0.17 * randf() saturation:1.0 brightness:1.0 alpha:1.0]; OOSparkEntity *spark = [[OOSparkEntity alloc] initWithPosition:origin velocity:vel duration:2.0 + 3.0 * randf() size:sz color:color]; [spark setOwner:self]; [UNIVERSE addEntity:spark]; [spark release]; next_spark_time = randf(); } - (BOOL) firePlasmaShotAtOffset:(double)offset speed:(double)speed color:(OOColor *)color { return [self firePlasmaShotAtOffset:offset speed:speed color:color direction:WEAPON_FACING_FORWARD]; } - (BOOL) firePlasmaShotAtOffset:(double)offset speed:(double)speed color:(OOColor *)color direction:(OOWeaponFacing)direction { Vector vel, rt; HPVector origin = position; double start = collision_radius + 0.5; speed += flightSpeed; if (++shot_counter % 2) offset = -offset; vel = v_forward; rt = v_right; Vector plasmaPortOffset = forwardWeaponOffset; if (isPlayer) // player can fire into multiple views! { switch ([UNIVERSE viewDirection]) { case VIEW_AFT : plasmaPortOffset = aftWeaponOffset; vel = vector_flip(v_forward); rt = vector_flip(v_right); break; case VIEW_STARBOARD : plasmaPortOffset = starboardWeaponOffset; vel = v_right; rt = vector_flip(v_forward); break; case VIEW_PORT : plasmaPortOffset = portWeaponOffset; vel = vector_flip(v_right); rt = v_forward; break; default: break; } } else { if (direction == (OOWeaponFacing)VIEW_AFT) // two different enums being compared here { plasmaPortOffset = aftWeaponOffset; vel = vector_flip(v_forward); rt = vector_flip(v_right); } } if (vector_equal(plasmaPortOffset, kZeroVector)) { origin = HPvector_add(origin, vectorToHPVector(vector_multiply_scalar(vel, start))); // no WeaponOffset defined } else { origin = HPvector_add(origin, vectorToHPVector(quaternion_rotate_vector([self normalOrientation], plasmaPortOffset))); } origin = HPvector_add(origin, vectorToHPVector(vector_multiply_scalar(rt, offset))); // With 'offset > 0' we get a twin-cannon. vel = vector_multiply_scalar(vel, speed); OOPlasmaShotEntity *shot = [[OOPlasmaShotEntity alloc] initWithPosition:origin velocity:vel energy:weapon_damage duration:MAIN_PLASMA_DURATION color:color]; [UNIVERSE addEntity:shot]; [shot setOwner:[self rootShipEntity]]; [shot release]; [self resetShotTime]; return YES; } - (void) considerFiringMissile:(double)delta_t { int missile_chance = 0; int rhs = 3.2 / delta_t; if (rhs) missile_chance = 1 + (ranrot_rand() % rhs); double hurt_factor = 16 * pow(energy/maxEnergy, 4.0); if (missiles > missile_chance * hurt_factor) { [self fireMissile]; } } - (Vector) missileLaunchPosition { Vector start; // default launching position start.x = 0.0f; // in the middle start.y = boundingBox.min.y - 4.0f; // 4m below bounding box start.z = boundingBox.max.z + 1.0f; // 1m ahead of bounding box // custom launching position start = [shipinfoDictionary oo_vectorForKey:@"missile_launch_position" defaultValue:start]; if (EXPECT_NOT(_scaleFactor != 1.0)) { start = vector_multiply_scalar(start,_scaleFactor); } if (start.x == 0.0f && start.y == 0.0f && start.z <= 0.0f) // The kZeroVector as start is illegal also. { OOLog(@"ship.missileLaunch.invalidPosition", @"***** ERROR: The missile_launch_position defines a position %@ behind the %@. In future versions such missiles may explode on launch because they have to travel through the ship.", VectorDescription(start), self); start.x = 0.0f; start.y = boundingBox.min.y - 4.0f; start.z = boundingBox.max.z + 1.0f; } return start; } - (ShipEntity *) fireMissile { return [self fireMissileWithIdentifier:nil andTarget:[self primaryTarget]]; } - (ShipEntity *) fireMissileWithIdentifier:(NSString *) identifier andTarget:(Entity *) target { // both players and NPCs! // ShipEntity *missile = nil; ShipEntity *target_ship = nil; Vector vel; Vector start, v_eject; if ([UNIVERSE getTime] < missile_launch_time) return nil; start = [self missileLaunchPosition]; double throw_speed = 250.0f; if ((missiles <= 0)||(target == nil)||([target scanClass] == CLASS_NO_DRAW)) // no missile lock! return nil; if ([target isShip]) { target_ship = (ShipEntity*)target; if ([target_ship isCloaked]) { return nil; } // missile fire requires being in scanner range if (HPmagnitude2(HPvector_subtract([target_ship position], position)) > scannerRange * scannerRange) { return nil; } if (![self hasMilitaryScannerFilter] && [target_ship isJammingScanning]) { return nil; } } unsigned i; if (identifier == nil) { // use a random missile from the list i = floor(randf()*(double)missiles); identifier = [missile_list[i] identifier]; missile = [UNIVERSE newShipWithRole:identifier]; if (EXPECT_NOT(missile == nil)) // invalid missile role. { // remove that invalid missile role from the missiles list. while ( ++i < missiles ) missile_list[i - 1] = missile_list[i]; missiles--; } } else missile = [UNIVERSE newShipWithRole:identifier]; if (EXPECT_NOT(missile == nil)) return nil; // By definition, the player will always have the specified missile. // What if the NPC didn't actually have the specified missile to begin with? if (!isPlayer && ![self removeExternalStore:[OOEquipmentType equipmentTypeWithIdentifier:identifier]]) { [missile release]; return nil; } double mcr = missile->collision_radius; v_eject = vector_normal(start); vel = kZeroVector; // starting velocity // check if start is within bounding box... while ( (start.x > boundingBox.min.x - mcr)&&(start.x < boundingBox.max.x + mcr)&& (start.y > boundingBox.min.y - mcr)&&(start.y < boundingBox.max.y + mcr)&& (start.z > boundingBox.min.z - mcr)&&(start.z < boundingBox.max.z + mcr) ) { start = vector_add(start, vector_multiply_scalar(v_eject, mcr)); } vel = vector_add(vel, vector_multiply_scalar(v_forward, flightSpeed + throw_speed)); Quaternion q1 = [self normalOrientation]; HPVector origin = HPvector_add(position, vectorToHPVector(quaternion_rotate_vector(q1, start))); if (isPlayer) [missile setScanClass: CLASS_MISSILE]; // special cases //We don't want real missiles in a group. Missiles could become escorts when the group is also used as escortGroup. if ([missile scanClass] == CLASS_THARGOID) { if([self group] == nil) [self setGroup:[OOShipGroup groupWithName:@"thargoid group"]]; ShipEntity *thisGroupLeader = [_group leader]; if ([thisGroupLeader escortGroup] != _group) // avoid adding tharons to escort groups { [missile setGroup:[self group]]; } } // is this a submunition? if (![self isMissileFlagSet]) [missile setOwner:self]; else [missile setOwner:[self owner]]; // end special cases [missile setPosition:origin]; [missile addTarget:target]; [missile setOrientation:q1]; [missile setIsMissileFlag:YES]; [missile setVelocity:vel]; [missile setSpeed:150.0f]; [missile setDistanceTravelled:0.0f]; [missile resetShotTime]; missile_launch_time = [UNIVERSE getTime] + missile_load_time; // set minimum launchtime for the next missile. [UNIVERSE addEntity:missile]; // STATUS_IN_FLIGHT, AI state GLOBAL [missile release]; //release // missile lives on after UNIVERSE addEntity if ([missile isMissile] && [target isShip]) { [self doScriptEvent:OOJSID("shipFiredMissile") withArgument:missile andArgument:target_ship]; [target_ship setPrimaryAggressor:self]; [target_ship doScriptEvent:OOJSID("shipAttackedWithMissile") withArgument:missile andArgument:self]; [target_ship reactToAIMessage:@"INCOMING_MISSILE" context:@"someone's shooting at me!"]; if (cloaking_device_active && cloakPassive) { // parity between player &NPCs, only deactivate cloak for missiles [self deactivateCloakingDevice]; } } else { [self doScriptEvent:OOJSID("shipReleasedEquipment") withArgument:missile]; } return missile; } - (BOOL) isMissileFlagSet { return isMissile; // were we created using fireMissile? (for tracking submunitions and preventing collisions at launch) } - (void) setIsMissileFlag:(BOOL)newValue { isMissile = !!newValue; // set the isMissile flag, used for tracking submunitions and preventing collisions at launch. } - (OOTimeDelta) missileLoadTime { return missile_load_time; } - (void) setMissileLoadTime:(OOTimeDelta)newMissileLoadTime { missile_load_time = fmax(0.0, newMissileLoadTime); } // reactions to ECM that are not dependent on current AI state here - (void) noticeECM { if (accuracy >= COMBAT_AI_ISNT_AWFUL && missiles > 0 && [[missile_list[0] identifier] isEqualTo:@"EQ_MISSILE"]) { // if we're being ECMd, and our missiles appear to be standard, and we // have some combat sense, wait a bit before firing the next one! missile_launch_time = [UNIVERSE getTime] + fmax(2.0,missile_load_time); // set minimum launchtime for the next missile. } } // Exposed to AI - (BOOL) fireECM { if (![self hasECM]) return NO; OOECMBlastEntity *ecmDevice = [[OOECMBlastEntity alloc] initFromShip:self]; [UNIVERSE addEntity:ecmDevice]; [ecmDevice release]; return YES; } - (BOOL) activateCloakingDevice { if (![self hasCloakingDevice] || cloaking_device_active) return cloaking_device_active; // no changes. if (!cloaking_device_active) cloaking_device_active = (energy > CLOAKING_DEVICE_START_ENERGY * maxEnergy); if (cloaking_device_active) [self doScriptEvent:OOJSID("shipCloakActivated")]; return cloaking_device_active; } - (void) deactivateCloakingDevice { if ([self hasCloakingDevice] && cloaking_device_active) { cloaking_device_active = NO; [self doScriptEvent:OOJSID("shipCloakDeactivated")]; } } - (BOOL) launchCascadeMine { if (![self hasCascadeMine]) return NO; [self setSpeed: maxFlightSpeed + 300]; ShipEntity* bomb = [UNIVERSE newShipWithRole:@"energy-bomb"]; if (bomb == nil) return NO; [self removeEquipmentItem:@"EQ_QC_MINE"]; double start = collision_radius + bomb->collision_radius; Quaternion random_direction; Vector vel; HPVector rpos; double random_roll = randf() - 0.5; // -0.5 to +0.5 double random_pitch = randf() - 0.5; // -0.5 to +0.5 quaternion_set_random(&random_direction); rpos = HPvector_subtract([self position], vectorToHPVector(vector_multiply_scalar(v_forward, start))); double eject_speed = -800.0; vel = vector_multiply_scalar(v_forward, [self flightSpeed] + eject_speed); eject_speed *= 0.5 * (randf() - 0.5); // -0.25x .. +0.25x vel = vector_add(vel, vector_multiply_scalar(v_up, eject_speed)); eject_speed *= 0.5 * (randf() - 0.5); // -0.0625x .. +0.0625x vel = vector_add(vel, vector_multiply_scalar(v_right, eject_speed)); [bomb setPosition:rpos]; [bomb setOrientation:random_direction]; [bomb setRoll:random_roll]; [bomb setPitch:random_pitch]; [bomb setVelocity:vel]; [bomb setScanClass:CLASS_MINE]; [bomb setEnergy:5.0]; // 5 second countdown [bomb setBehaviour:BEHAVIOUR_ENERGY_BOMB_COUNTDOWN]; [bomb setOwner:self]; [UNIVERSE addEntity:bomb]; // STATUS_IN_FLIGHT, AI state GLOBAL [bomb release]; if (cloaking_device_active && cloakPassive) { [self deactivateCloakingDevice]; } if (self != PLAYER) // get the heck out of here { [self addTarget:bomb]; [self setBehaviour:BEHAVIOUR_FLEE_TARGET]; frustration = 0.0; } return YES; } - (ShipEntity*)launchEscapeCapsule { ShipEntity *result = nil; ShipEntity *mainPod = nil; unsigned n_pods, i; NSMutableArray *passengers = nil; /* CHANGE: both player & NPCs can now launch escape pods in interstellar space. -- Kaks 20101113 */ // check number of pods aboard -- require at least one. n_pods = [shipinfoDictionary oo_unsignedIntForKey:@"has_escape_pod"]; if (n_pods > 65) n_pods = 65; // maximum of 64 passengers. if (n_pods > 1) passengers = [NSMutableArray arrayWithCapacity:n_pods-1]; if (crew) // transfer crew { // make sure crew inherit any legalStatus for (i = 0; i < [crew count]; i++) { OOCharacter *ch = (OOCharacter*)[crew objectAtIndex:i]; [ch setLegalStatus: [self legalStatus] | [ch legalStatus]]; } mainPod = [self launchPodWithCrew:crew]; if (mainPod) { result = mainPod; [self setCrew:nil]; [self setHulk:YES]; // we are without crew now. } } // launch other pods (passengers) for (i = 1; i < n_pods; i++) { ShipEntity *passenger = nil; passenger = [self launchPodWithCrew:[NSArray arrayWithObject:[OOCharacter randomCharacterWithRole:@"passenger" andOriginalSystem:gen_rnd_number()]]]; [passengers addObject:passenger]; } if (mainPod) [self doScriptEvent:OOJSID("shipLaunchedEscapePod") withArgument:mainPod andArgument:passengers]; return result; } // This is a documented AI method; do not change semantics. (Note: AIs don't have access to the return value.) - (OOCommodityType) dumpCargo { ShipEntity *jetto = [self dumpCargoItem:nil]; if (jetto != nil) { return [jetto commodityType]; } else { return nil; } } - (ShipEntity *) dumpCargoItem:(OOCommodityType)preferred { ShipEntity *jetto = nil; NSUInteger i = 0; if (([cargo count] > 0)&&([UNIVERSE getTime] - cargo_dump_time > 0.5)) // space them 0.5s or 10m apart { if (preferred == nil) { jetto = [[[cargo objectAtIndex:0] retain] autorelease]; } else { BOOL found = NO; for (i=0;i<[cargo count];i++) { if ([[[cargo objectAtIndex:i] commodityType] isEqualToString:preferred]) { jetto = [[[cargo objectAtIndex:i] retain] autorelease]; found = YES; break; } } if (found == NO) { // dump anything jetto = [[[cargo objectAtIndex:0] retain] autorelease]; i = 0; } } if (jetto != nil) { [self dumpItem:jetto]; // CLASS_CARGO, STATUS_IN_FLIGHT, AI state GLOBAL [cargo removeObjectAtIndex:i]; [self broadcastAIMessage:@"CARGO_DUMPED"]; // goes only to 16 nearby ships in range, but that should be enough. unsigned i; // only send script event to powered entities [self checkScannerIgnoringUnpowered]; for (i = 0; i < n_scanned_ships ; i++) { ShipEntity* other = scanned_ships[i]; [other doScriptEvent:OOJSID("cargoDumpedNearby") withArgument:jetto andArgument:self]; } } } return jetto; } - (OOCargoType) dumpItem: (ShipEntity*) cargoObj { if (!cargoObj) return 0; ShipEntity* jetto = [UNIVERSE reifyCargoPod:cargoObj]; int result = [jetto cargoType]; AI *jettoAI = nil; Vector start; // players get to see their old ship sailing forth, while NPCs run away more efficiently! // cargo is ejected at higher speed from any ship double eject_speed = EXPECT_NOT([jetto crew] && [jetto isPlayer]) ? 20.0 : 100.0; double eject_reaction = -eject_speed * [jetto mass] / [self mass]; double jcr = jetto->collision_radius; Quaternion jetto_orientation = kIdentityQuaternion; Vector vel, v_eject, v_eject_normal; HPVector rpos = [self absolutePositionForSubentity]; double jetto_roll = 0; double jetto_pitch = 0; // default launching position start.x = 0.0; // in the middle start.y = 0.0; // start.z = boundingBox.min.z - jcr; // 1m behind of bounding box // custom launching position start = [shipinfoDictionary oo_vectorForKey:@"aft_eject_position" defaultValue:start]; v_eject = vector_normal(start); // check if start is within bounding box... while ( (start.x > boundingBox.min.x - jcr)&&(start.x < boundingBox.max.x + jcr)&& (start.y > boundingBox.min.y - jcr)&&(start.y < boundingBox.max.y + jcr)&& (start.z > boundingBox.min.z - jcr)&&(start.z < boundingBox.max.z + jcr)) { start = vector_add(start, vector_multiply_scalar(v_eject, jcr)); } v_eject = quaternion_rotate_vector([self normalOrientation], start); rpos = HPvector_add(rpos, vectorToHPVector(v_eject)); v_eject = vector_normal(v_eject); v_eject_normal = v_eject; v_eject.x += (randf() - randf())/eject_speed; v_eject.y += (randf() - randf())/eject_speed; v_eject.z += (randf() - randf())/eject_speed; vel = vector_add(vector_multiply_scalar(v_forward, flightSpeed), vector_multiply_scalar(v_eject, eject_speed)); velocity = vector_add(velocity, vector_multiply_scalar(v_eject, eject_reaction)); [jetto setPosition:rpos]; if ([jetto crew]) // jetto has a crew, so assume it is an escape pod. { // orient the pod away from the ship to avoid colliding with it. jetto_orientation = quaternion_rotation_between(v_eject_normal, kBasisZVector); } else { // It is true cargo, let it tumble. jetto_roll = ((ranrot_rand() % 1024) - 512.0)/1024.0; // -0.5 to +0.5 jetto_pitch = ((ranrot_rand() % 1024) - 512.0)/1024.0; // -0.5 to +0.5 quaternion_set_random(&jetto_orientation); } [jetto setOrientation:jetto_orientation]; [jetto setRoll:jetto_roll]; [jetto setPitch:jetto_pitch]; [jetto setVelocity:vel]; [jetto setScanClass: CLASS_CARGO]; [jetto setTemperature:[self randomEjectaTemperature]]; [UNIVERSE addEntity:jetto]; // STATUS_IN_FLIGHT, AI state GLOBAL jettoAI = [jetto getAI]; if ([jettoAI hasSuspendedStateMachines]) // check if this was previous scooped cargo. { [jetto setThrust:[jetto maxThrust]]; // restore old thrust. [jetto setOwner:jetto]; [jettoAI exitStateMachineWithMessage:nil]; // exit nullAI. } [jetto doScriptEvent:OOJSID("shipWasDumped") withArgument:self]; cargo_dump_time = [UNIVERSE getTime]; return result; } - (void) manageCollisions { // deal with collisions // Entity* ent; ShipEntity* other_ship; while ([collidingEntities count] > 0) { // EMMSTRAN: investigate if doing this backwards would be more efficient. (Not entirely obvious, NSArray is kinda funky.) -- Ahruman 2011-02-12 ent = [[[collidingEntities objectAtIndex:0] retain] autorelease]; [collidingEntities removeObjectAtIndex:0]; if (ent) { if ([ent isShip]) { other_ship = (ShipEntity *)ent; [self collideWithShip:other_ship]; } else if ([ent isStellarObject]) { [self getDestroyedBy:ent damageType:[ent isSun] ? kOODamageTypeHitASun : kOODamageTypeHitAPlanet]; if (self == PLAYER) [self retain]; } else if ([ent isWormhole]) { if( [self isPlayer] ) [self enterWormhole:(WormholeEntity*)ent]; else [self enterWormhole:(WormholeEntity*)ent replacing:NO]; } } } } - (BOOL) collideWithShip:(ShipEntity *)other { HPVector hploc; Vector loc; double dam1, dam2; if (!other) return NO; ShipEntity* otherParent = [other parentEntity]; BOOL otherIsStation = other == [UNIVERSE station]; // calculate line of centers using centres hploc = HPvector_normal_or_zbasis(HPvector_subtract([other absolutePositionForSubentity], position)); loc = HPVectorToVector(hploc); if ([self canScoop:other]) { [self scoopIn:other]; return NO; } if ([other canScoop:self]) { [other scoopIn:self]; return NO; } if (universalID == NO_TARGET) return NO; if (other->universalID == NO_TARGET) return NO; // find velocity along line of centers // // momentum = mass x velocity // ke = mass x velocity x velocity // GLfloat m1 = mass; // mass of self GLfloat m2 = [other mass]; // mass of other // starting velocities: Vector v, vel1b = [self velocity]; if (otherParent != nil) { // Subentity /* TODO: if the subentity is rotating (subentityRotationalVelocity is not 1 0 0 0) we should calculate the tangential velocity from the other's position relative to our absolute position and add that in. */ v = [otherParent velocity]; } else { v = [other velocity]; } v = vector_subtract(vel1b, v); GLfloat v2b = dot_product(v, loc); // velocity of other along loc before collision GLfloat v1a = sqrt(v2b * v2b * m2 / m1); // velocity of self along loc after elastic collision if (v2b < 0.0f) v1a = -v1a; // in same direction as v2b // are they moving apart at over 1m/s already? if (v2b < 0.0f) { if (v2b < -1.0f) return NO; else { position = HPvector_subtract(position, hploc); // adjust self position v = kZeroVector; // go for the 1m/s solution } } // convert change in velocity into damage energy (KE) dam1 = m2 * v2b * v2b / 50000000; dam2 = m1 * v2b * v2b / 50000000; // calculate adjustments to velocity after collision Vector vel1a = vector_multiply_scalar(loc, -v1a); Vector vel2a = vector_multiply_scalar(loc, v2b); if (magnitude2(v) <= 0.1) // virtually no relative velocity - we must provide at least 1m/s to avoid conjoined objects { vel1a = vector_multiply_scalar(loc, -1); vel2a = loc; } // apply change in velocity if (otherParent != nil) { [otherParent adjustVelocity:vel2a]; // move the otherParent not the subentity } else { [other adjustVelocity:vel2a]; } [self adjustVelocity:vel1a]; BOOL selfDestroyed = (dam1 > energy); BOOL otherDestroyed = (dam2 > [other energy]) && !otherIsStation; if (dam1 > 0.05) { [self takeScrapeDamage: dam1 from:other]; if (selfDestroyed) // inelastic! - take xplosion velocity damage instead { vel2a = vector_multiply_scalar(vel2a, -1); [other adjustVelocity:vel2a]; } } if (dam2 > 0.05) { if (otherParent != nil && ![otherParent isFrangible]) { [otherParent takeScrapeDamage: dam2 from:self]; } else { [other takeScrapeDamage: dam2 from:self]; } if (otherDestroyed) // inelastic! - take explosion velocity damage instead { vel1a = vector_multiply_scalar(vel1a, -1); [self adjustVelocity:vel1a]; } } if (!selfDestroyed && !otherDestroyed) { float t = 10.0 * [UNIVERSE getTimeDelta]; // 10 ticks HPVector pos1a = HPvector_add([self position], vectorToHPVector(vector_multiply_scalar(loc, t * v1a))); [self setPosition:pos1a]; if (!otherIsStation) { HPVector pos2a = HPvector_add([other position], vectorToHPVector(vector_multiply_scalar(loc, t * v2b))); [other setPosition:pos2a]; } } // remove self from other's collision list [[other collisionArray] removeObject:self]; [self doScriptEvent:OOJSID("shipCollided") withArgument:other andReactToAIMessage:@"COLLISION"]; [other doScriptEvent:OOJSID("shipCollided") withArgument:self andReactToAIMessage:@"COLLISION"]; return YES; } - (Vector) thrustVector { return vector_multiply_scalar(v_forward, flightSpeed); } - (Vector) velocity { return vector_add([super velocity], [self thrustVector]); } - (void) setTotalVelocity:(Vector)vel { [self setVelocity:vector_subtract(vel, [self thrustVector])]; } - (void) adjustVelocity:(Vector) xVel { velocity = vector_add(velocity, xVel); } - (void) addImpactMoment:(Vector) moment fraction:(GLfloat) howmuch { velocity = vector_add(velocity, vector_multiply_scalar(moment, howmuch / mass)); } - (BOOL) canScoop:(ShipEntity*)other { if (other == nil) return NO; if (![self hasCargoScoop]) return NO; if ([cargo count] >= [self maxAvailableCargoSpace]) return NO; if (scanClass == CLASS_CARGO) return NO; // we have no power so we can't scoop if ([other scanClass] != CLASS_CARGO) return NO; if ([other cargoType] == CARGO_NOT_CARGO) return NO; if ([other isStation]) return NO; HPVector loc = HPvector_between(position, [other position]); if (dot_product(v_forward, HPVectorToVector(loc)) < 0.0f) return NO; // Must be in front of us if ([self isPlayer] && dot_product(v_up, HPVectorToVector(loc)) > 0.0f) return NO; // player has to scoop on underside, give more flexibility to NPCs return YES; } - (void) getTractoredBy:(ShipEntity *)other { if([self status] == STATUS_BEING_SCOOPED) return; // both cargo and ship call this. Act only once. desired_speed = 0.0; [self setAITo:@"nullAI.plist"]; // prevent AI from changing status or behaviour. behaviour = BEHAVIOUR_TRACTORED; [self setStatus:STATUS_BEING_SCOOPED]; [self addTarget:other]; [self setOwner:other]; // should we make this an all rather than first 16? - CIM // made it ignore other cargopods and similar at least. - CIM 28/7/2013 [self checkScannerIgnoringUnpowered]; unsigned i; ShipEntity *scooper; for (i = 0; i < n_scanned_ships ; i++) { scooper = (ShipEntity *)scanned_ships[i]; // 'Dibs!' - Stops other ships from trying to scoop/shoot this cargo. if (other != scooper && (id) self == [scooper primaryTarget]) { [scooper noteLostTarget]; } } } - (void) scoopIn:(ShipEntity *)other { [other getTractoredBy:self]; } - (void) suppressTargetLost { } - (void) scoopUp:(ShipEntity *)other { if (other == nil) return; OOCommodityType co_type = nil; OOCargoQuantity co_amount; // don't even think of trying to scoop if the cargo hold is already full if (max_cargo && [cargo count] >= [self maxAvailableCargoSpace]) { [other setStatus:STATUS_IN_FLIGHT]; return; } switch ([other cargoType]) { case CARGO_RANDOM: co_type = [other commodityType]; co_amount = [other commodityAmount]; break; case CARGO_SCRIPTED_ITEM: { //scripting PlayerEntity *player = PLAYER; [player setScriptTarget:self]; [other doScriptEvent:OOJSID("shipWasScooped") withArgument:self]; if ([other commodityType] != nil) { co_type = [other commodityType]; co_amount = [other commodityAmount]; // don't show scoop message now, will happen later. } else { if (isPlayer && [other showScoopMessage]) { [UNIVERSE clearPreviousMessage]; NSString *shipName = [other displayName]; [UNIVERSE addMessage:OOExpandKey(@"scripted-item-scooped", shipName) forCount:4]; } [other setCommodityForPod:nil andAmount:0]; co_amount = 0; co_type = nil; } } break; default : co_amount = 0; co_type = nil; break; } /* Bug: docking failed due to NSRangeException while looking for element NSNotFound of cargo mainfest in -[PlayerEntity unloadCargoPods]. Analysis: bad cargo pods being generated due to -[Universe commodityForName:] looking in wrong place for names. Fix 1: fix -[Universe commodityForName:]. Fix 2: catch NSNotFound here and substitute random cargo type. -- Ahruman 20070714 */ if (co_type == nil && co_amount > 0) { co_type = [UNIVERSE getRandomCommodity]; co_amount = [UNIVERSE getRandomAmountOfCommodity:co_type]; } if (co_amount > 0) { [other setCommodity:co_type andAmount:co_amount]; // belt and braces setting this! cargo_flag = CARGO_FLAG_CANISTERS; if (isPlayer) { if ([other crew]) { if ([other showScoopMessage]) { [UNIVERSE clearPreviousMessage]; unsigned i; for (i = 0; i < [[other crew] count]; i++) { OOCharacter *rescuee = [[other crew] objectAtIndex:i]; NSString *characterName = [rescuee name]; if ([rescuee legalStatus]) { [UNIVERSE addMessage:OOExpandKey(@"scoop-captured-character", characterName) forCount: 4.5]; } else if ([rescuee insuranceCredits]) { [UNIVERSE addMessage:OOExpandKey(@"scoop-rescued-character", characterName) forCount: 4.5]; } else { [UNIVERSE addMessage: DESC(@"scoop-got-slave") forCount: 4.5]; } } } [(PlayerEntity *)self playEscapePodScooped]; } else { if ([other showScoopMessage]) { [UNIVERSE clearPreviousMessage]; [UNIVERSE addMessage:[UNIVERSE describeCommodity:co_type amount:co_amount] forCount:4.5]; } } } [cargo insertObject:other atIndex:0]; // places most recently scooped object at eject position [other setStatus:STATUS_IN_HOLD]; [other performTumble]; [shipAI message:@"CARGO_SCOOPED"]; if (max_cargo && [cargo count] >= [self maxAvailableCargoSpace]) [shipAI message:@"HOLD_FULL"]; } [self doScriptEvent:OOJSID("shipScoopedOther") withArgument:other]; // always fire, even without commodity. // if shipScoopedOther does something strange to the object, we must // then remove it from the hold, or it will be over-retained if ([other status] != STATUS_IN_HOLD) { if ([cargo containsObject:other]) { [cargo removeObject:other]; } } [[other collisionArray] removeObject:self]; // so it can't be scooped twice! // make sure other ships trying to scoop it lose it // probably already happened, but some may have acquired it // after the scooping started, and they might get stuck in a scooping // attempt as a result [self checkScannerIgnoringUnpowered]; unsigned i; ShipEntity *scooper; for (i = 0; i < n_scanned_ships ; i++) { scooper = (ShipEntity *)scanned_ships[i]; if (self != scooper && (id) other == [scooper primaryTargetWithoutValidityCheck]) { [scooper noteLostTarget]; } } [self suppressTargetLost]; [UNIVERSE removeEntity:other]; } - (BOOL) cascadeIfAppropriateWithDamageAmount:(double)amount cascadeOwner:(Entity *)owner { BOOL cascade = NO; switch ([self scanClass]) { case CLASS_WORMHOLE: case CLASS_ROCK: case CLASS_CARGO: case CLASS_VISUAL_EFFECT: case CLASS_BUOY: // does not normally cascade if ((fuel > MIN_FUEL) || isStation) { //we have fuel onboard so we can still go pop, or we are a station which can } else break; case CLASS_STATION: case CLASS_MINE: case CLASS_PLAYER: case CLASS_POLICE: case CLASS_MILITARY: case CLASS_THARGOID: case CLASS_MISSILE: case CLASS_NOT_SET: case CLASS_NO_DRAW: case CLASS_NEUTRAL: case CLASS_TARGET: // ...start a chain reaction, if we're dying and have a non-trivial amount of energy. if (energy < amount && energy > 10 && [self countsAsKill]) { cascade = YES; // confirm we're cascading, then try to add our cascade to UNIVERSE. [UNIVERSE addEntity:[OOQuiriumCascadeEntity quiriumCascadeFromShip:self]]; } break; //no default thanks, we want the compiler to tell us if we missed a case. } return cascade; } - (void) takeEnergyDamage:(double)amount from:(Entity *)ent becauseOf:(Entity *)other { if ([self status] == STATUS_DEAD) return; if (amount <= 0.0) return; BOOL energyMine = [ent isCascadeWeapon]; BOOL cascade = NO; if (energyMine) { cascade = [self cascadeIfAppropriateWithDamageAmount:amount cascadeOwner:[ent owner]]; } energy -= amount; /* Heat increase from energy impacts will never directly cause * overheating - too easy for missile hits to cause an uncredited * death by overheating - CIM */ if (ship_temperature < SHIP_MAX_CABIN_TEMP) { ship_temperature += amount * SHIP_ENERGY_DAMAGE_TO_HEAT_FACTOR / [self heatInsulation]; if (ship_temperature > SHIP_MAX_CABIN_TEMP) { ship_temperature = SHIP_MAX_CABIN_TEMP; } } being_mined = NO; ShipEntity *hunter = nil; hunter = [other rootShipEntity]; if (hunter == nil && [other isShip]) hunter = (ShipEntity *)other; // must check for this before potentially deleting 'other' for cloaking if ((other)&&([other isShip])) { being_mined = [(ShipEntity *)other isMining]; } if (hunter !=nil && [self owner] != hunter) // our owner could be the same entity as the one responsible for our taking damage in the case of submunitions { if ([hunter isCloaked]) { [self doScriptEvent:OOJSID("shipBeingAttackedByCloaked") andReactToAIMessage:@"ATTACKED_BY_CLOAKED"]; // lose it! other = nil; hunter = nil; } } else { hunter = nil; } // if the other entity is a ship note it as an aggressor if (hunter != nil) { BOOL iAmTheLaw = [self isPolice]; BOOL uAreTheLaw = [hunter isPolice]; DESTROY(_lastEscortTarget); // we're being attacked, escorts can scramble! [self setPrimaryAggressor:hunter]; [self setFoundTarget:hunter]; // firing on an innocent ship is an offence [self broadcastHitByLaserFrom: hunter]; // tell ourselves we've been attacked if (energy > 0) { [self respondToAttackFrom:ent becauseOf:hunter]; } OOShipGroup *group = [self group]; // JSAIs manage group notifications themselves if (![self hasNewAI]) { // additionally, tell our group we've been attacked if (group != nil && group != [hunter group] && !(iAmTheLaw || uAreTheLaw)) { if ([self isTrader] || [self isEscort]) { ShipEntity *groupLeader = [group leader]; if (groupLeader != self) { [groupLeader setFoundTarget:hunter]; [groupLeader setPrimaryAggressor:hunter]; [groupLeader respondToAttackFrom:ent becauseOf:hunter]; //unsetting group leader for carriers can break stuff } } if ([self isPirate]) { NSEnumerator *groupEnum = nil; ShipEntity *otherPirate = nil; for (groupEnum = [group mutationSafeEnumerator]; (otherPirate = [groupEnum nextObject]); ) { if (otherPirate != self && randf() < 0.5) // 50% chance they'll help { [otherPirate setFoundTarget:hunter]; [otherPirate setPrimaryAggressor:hunter]; [otherPirate respondToAttackFrom:ent becauseOf:hunter]; } } } else if (iAmTheLaw) { NSEnumerator *groupEnum = nil; ShipEntity *otherPolice = nil; for (groupEnum = [group mutationSafeEnumerator]; (otherPolice = [groupEnum nextObject]); ) { if (otherPolice != self) { [otherPolice setFoundTarget:hunter]; [otherPolice setPrimaryAggressor:hunter]; [otherPolice respondToAttackFrom:ent becauseOf:hunter]; } } } } } // if I'm a copper and you're not, then mark the other as an offender! if (iAmTheLaw && !uAreTheLaw) { // JSAI's can choose not to do this for friendly fire purposes if (![self hasNewAI]) { [hunter markAsOffender:64 withReason:kOOLegalStatusReasonAttackedPolice]; } } if ((group != nil && [hunter group] == group) || (iAmTheLaw && uAreTheLaw)) { // avoid shooting each other if ([hunter behaviour] == BEHAVIOUR_ATTACK_FLY_TO_TARGET) // avoid me please! { [hunter setBehaviour:BEHAVIOUR_ATTACK_FLY_FROM_TARGET]; [hunter setDesiredSpeed:[hunter maxFlightSpeed]]; } } } OOShipDamageType damageType = kOODamageTypeEnergy; if (suppressExplosion) damageType = kOODamageTypeRemoved; else if (energyMine) damageType = kOODamageTypeCascadeWeapon; if (!suppressExplosion) { [self noteTakingDamage:amount from:other type:damageType]; if (cascade) energy = 0.0; // explicit set energy to zero in case an oxp raised the energy in previous line. } // die if I'm out of energy if (energy <= 0.0) { // backup check just in case scripts have reduced energy if (self != [UNIVERSE station]) { if (hunter != nil) [hunter noteTargetDestroyed:self]; [self getDestroyedBy:other damageType:damageType]; } } else { // warn if I'm low on energy if (energy < maxEnergy * 0.25) { [self doScriptEvent:OOJSID("shipEnergyIsLow") andReactToAIMessage:@"ENERGY_LOW"]; } if ((energy < maxEnergy *0.125 || (energy < 64 && energy < amount*2)) && [self hasEscapePod] && (ranrot_rand() & 3) == 0) // 25% chance he gets to an escape pod { [self abandonShip]; } } } - (BOOL) abandonShip { BOOL OK = NO; if ([self isPlayer] && [(PlayerEntity *)self isDocked]) { OOLog(@"ShipEntity.abandonShip.failed", @"Player cannot abandon ship while docked."); return OK; } if (![self hasEscapePod]) { OOLog(@"ShipEntity.abandonShip.failed", @"Ship abandonment was requested for %@, but this ship does not carry escape pod(s).", self); return OK; } if (EXPECT([self launchEscapeCapsule] != NO_TARGET)) // -launchEscapeCapsule takes care of everything for the player { if (![self isPlayer]) { OK = YES; // if multiple items providing escape pod, remove all of them (NPC process) while ([self hasEquipmentItemProviding:@"EQ_ESCAPE_POD"]) { [self removeEquipmentItem:[self equipmentItemProviding:@"EQ_ESCAPE_POD"]]; } [self setAITo:@"nullAI.plist"]; behaviour = BEHAVIOUR_IDLE; frustration = 0.0; [self setScanClass: CLASS_CARGO]; // we're unmanned now! thrust = thrust * 0.5; if (thrust > 5) thrust = 5; // 5 is the thrust of an escape-capsule desired_speed = 0.0; if ([self group]) [self setGroup:nil]; // remove self from group. if (![self isSubEntity] && [self owner]) [self setOwner:nil]; //unset owner, but not if we are a subent if ([self hasEscorts]) { OOShipGroup *escortGroup = [self escortGroup]; NSEnumerator *escortEnum = nil; ShipEntity *escort = nil; // Note: works on escortArray rather than escortEnumerator because escorts may be mutated. for (escortEnum = [[self escortArray] objectEnumerator]; (escort = [escortEnum nextObject]); ) { // act individually now! if ([escort group] == escortGroup) [escort setGroup:nil]; if ([escort owner] == self) [escort setOwner:escort]; } // We now have no escorts. [_escortGroup release]; _escortGroup = nil; } } } else if (EXPECT([self isSubEntity])) { // may still have launched passenger pods even if no crew // if multiple items providing escape pod, remove all of them (NPC process) while ([self hasEquipmentItemProviding:@"EQ_ESCAPE_POD"]) { [self removeEquipmentItem:[self equipmentItemProviding:@"EQ_ESCAPE_POD"]]; } } else { // this shouldn't happen any more! OOLog(@"ShipEntity.abandonShip.notPossible", @"Ship %@ cannot be abandoned at this time.", self); } return OK; } - (void) takeScrapeDamage:(double) amount from:(Entity *)ent { if ([self status] == STATUS_DEAD) return; if ([self status] == STATUS_LAUNCHING|| [ent status] == STATUS_LAUNCHING) { // no collisions during launches please return; } energy -= amount; [self noteTakingDamage:amount from:ent type:kOODamageTypeScrape]; // oops we hit too hard!!! if (energy <= 0.0) { float frag_chance = [ent mass]*10/[self mass]; /* impacts from heavier entities produce fragments * impacts from lighter entities might do but not always * asteroid-asteroid impacts likely to fragment * ship-asteroid impacts might, or might just vaporise it * projectile weapons just get the default chance */ if (randf() < frag_chance) { being_mined = YES; // same as using a mining laser } if ([ent isShip]) { [(ShipEntity *)ent noteTargetDestroyed:self]; } [self getDestroyedBy:ent damageType:kOODamageTypeScrape]; } else { // warn if I'm low on energy if (energy < maxEnergy * 0.25) { [self doScriptEvent:OOJSID("shipEnergyIsLow") andReactToAIMessage:@"ENERGY_LOW"]; } } } - (void) takeHeatDamage:(double)amount { if ([self status] == STATUS_DEAD) return; if ([self isSubEntity]) { ShipEntity* owner = [self owner]; if (![owner isFrangible]) { return; } } energy -= amount; throw_sparks = YES; [self noteTakingDamage:amount from:nil type:kOODamageTypeHeat]; // oops we're burning up! if (energy <= 0.0) { [self getDestroyedBy:nil damageType:kOODamageTypeHeat]; } else { // warn if I'm low on energy if (energy < maxEnergy * 0.25) { [self doScriptEvent:OOJSID("shipEnergyIsLow") andReactToAIMessage:@"ENERGY_LOW"]; } } } - (void) enterDock:(StationEntity *)station { // throw these away now we're docked... if (dockingInstructions != nil) { [dockingInstructions autorelease]; dockingInstructions = nil; } [self doScriptEvent:OOJSID("shipWillDockWithStation") withArgument:station]; [self doScriptEvent:OOJSID("shipDockedWithStation") withArgument:station]; [shipAI message:@"DOCKED"]; [station noteDockedShip:self]; [UNIVERSE removeEntity:self]; } - (void) leaveDock:(StationEntity *)station { // This code is never used. Currently npc ships are only launched from the stations launch queue. if (station == nil) return; [station launchShip:self]; } - (void) enterWormhole:(WormholeEntity *) w_hole { [self enterWormhole:w_hole replacing:YES]; } - (void) enterWormhole:(WormholeEntity *) w_hole replacing:(BOOL)replacing { if (w_hole == nil) return; if ([self status] == STATUS_ENTERING_WITCHSPACE) { return; // has already entered a different wormhole } // Replacement ships now handled by system repopulator // MKW 2011.02.27 - Moved here from ShipEntityAI so escorts reliably follow // mother in all wormhole cases, not just when the ship // creates the wormhole. [self addTarget:w_hole]; [self setFoundTarget:w_hole]; [shipAI reactToMessage:@"WITCHSPACE OKAY" context:@"performHyperSpaceExit"]; // must be a reaction, the ship is about to disappear // CIM 2012.07.22 above only covers those cases where ship expected to leave if ([[self escortArray] count] > 1) { // so wormhole escorts anyway if it leaves unexpectedly. [self wormholeEscorts]; } if ([self scriptedMisjump]) { [self setScriptedMisjump:NO]; [w_hole setMisjumpWithRange:[self scriptedMisjumpRange]]; [self setScriptedMisjumpRange:0.5]; } [w_hole suckInShip: self]; // removes ship from universe } - (void) enterWitchspace { [UNIVERSE addWitchspaceJumpEffectForShip:self]; [shipAI message:@"ENTERED_WITCHSPACE"]; if (![[UNIVERSE sun] willGoNova]) { // if the sun's not going nova, add a new ship like this one leaving. [UNIVERSE witchspaceShipWithPrimaryRole:[self primaryRole]]; } [UNIVERSE removeEntity:self]; } - (void) leaveWitchspace { Quaternion q1; quaternion_set_random(&q1); Vector v1 = vector_forward_from_quaternion(q1); double d1 = 0.0; GLfloat min_d1 = [UNIVERSE safeWitchspaceExitDistance]; while (fabs(d1) < min_d1) { // not scannerRange - has no effect on witchspace exit d1 = SCANNER_MAX_RANGE * (randf() - randf()); } HPVector exitposition = [UNIVERSE getWitchspaceExitPosition]; exitposition.x += v1.x * d1; // randomise exit position exitposition.y += v1.y * d1; exitposition.z += v1.z * d1; [self setPosition:exitposition]; [self witchspaceLeavingEffects]; } - (BOOL) witchspaceLeavingEffects { // all ships exiting witchspace will share the same orientation. orientation = [UNIVERSE getWitchspaceExitRotation]; flightRoll = 0.0; stick_roll = 0.0; flightPitch = 0.0; stick_pitch = 0.0; flightYaw = 0.0; stick_yaw = 0.0; flightSpeed = 50.0; // constant speed same for all ships // was a quarter of max speed, so the Anaconda speeds up and most // others slow down - CIM // will be overridden if left witchspace via a genuine wormhole velocity = kZeroVector; if (![UNIVERSE addEntity:self]) // AI and status get initialised here { return NO; } [self setStatus:STATUS_EXITING_WITCHSPACE]; [shipAI message:@"EXITED_WITCHSPACE"]; [UNIVERSE addWitchspaceJumpEffectForShip:self]; [self setStatus:STATUS_IN_FLIGHT]; return YES; } - (void) markAsOffender:(int)offence_value { [self markAsOffender:offence_value withReason:kOOLegalStatusReasonUnknown]; } - (void) markAsOffender:(int)offence_value withReason:(OOLegalStatusReason)reason { if (![self isPolice] && ![self isCloaked] && self != [UNIVERSE station]) { if ([self isSubEntity]) { [[self parentEntity] markAsOffender:offence_value withReason:reason]; } else { if ((scanClass == CLASS_THARGOID || scanClass == CLASS_STATION) && reason != kOOLegalStatusReasonSetup && reason != kOOLegalStatusReasonByScript) { return; // no non-scripted bounties for thargoids and stations } JSContext *context = OOJSAcquireContext(); jsval amountVal = JSVAL_VOID; JS_NewNumberValue(context, (bounty | offence_value)-bounty, &amountVal); bounty |= offence_value; // can't set the new bounty until the size of the change is known jsval reasonVal = OOJSValueFromLegalStatusReason(context, reason); ShipScriptEvent(context, self, "shipBountyChanged", amountVal, reasonVal); OOJSRelinquishContext(context); } } } // Exposed to AI - (void) switchLightsOn { NSEnumerator *subEnum = nil; OOFlasherEntity *se = nil; ShipEntity *sub = nil; _lightsActive = YES; for (subEnum = [self flasherEnumerator]; (se = [subEnum nextObject]); ) { [se setActive:YES]; } for (subEnum = [self shipSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { [sub switchLightsOn]; } } // Exposed to AI - (void) switchLightsOff { NSEnumerator *subEnum = nil; OOFlasherEntity *se = nil; ShipEntity *sub = nil; _lightsActive = NO; for (subEnum = [self flasherEnumerator]; (se = [subEnum nextObject]); ) { [se setActive:NO]; } for (subEnum = [self shipSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { [sub switchLightsOff]; } } - (BOOL) lightsActive { return _lightsActive; } - (void) setDestination:(HPVector) dest { _destination = dest; frustration = 0.0; // new destination => no frustration! } - (void) setEscortDestination:(HPVector) dest { _destination = dest; // don't reset frustration for escorts. } - (BOOL) canAcceptEscort:(ShipEntity *)potentialEscort { if (dockingInstructions) // we are busy with docking. { return NO; } if (scanClass != [potentialEscort scanClass]) // this makes sure that wingman can only select police, thargons only thargoids. { return NO; } if ([self bounty] == 0 && [potentialEscort bounty] != 0) // clean mothers can only accept clean escorts { return NO; } if (![self isEscort]) // self is NOT wingman or escort or thargon { return [potentialEscort isEscort]; // is wingman or escort or thargon } return NO; } - (BOOL) acceptAsEscort:(ShipEntity *) other_ship { // can't pair with self if (self == other_ship) return NO; // no longer in flight, probably entered wormhole without telling escorts. if ([self status] != STATUS_IN_FLIGHT) return NO; //increased stack depth at which it can accept escorts to avoid rejections at this stage. //doesn't seem to have any adverse effect for now. - Kaks. if ([shipAI stackDepth] > 3) { OOLog(@"ship.escort.reject", @"%@ rejecting escort %@ because AI stack depth is %lu.",self, other_ship, [shipAI stackDepth]); return NO; } if ([self canAcceptEscort:other_ship]) { OOShipGroup *escortGroup = [self escortGroup]; if ([escortGroup containsShip:other_ship]) return YES; // check total number acceptable // the system's patrols don't have escorts set inside their dictionary, but accept max escorts. if (_maxEscortCount == 0 && ([self hasPrimaryRole:@"police"] || [self hasPrimaryRole:@"hunter"] || [self hasRole:@"thargoid-mothership"])) { _maxEscortCount = MAX_ESCORTS; } NSUInteger maxEscorts = _maxEscortCount; // never bigger than MAX_ESCORTS. NSUInteger escortCount = [escortGroup count] - 1; // always 0 or higher. if (escortCount < maxEscorts) { [other_ship setGroup:escortGroup]; if ([self group] == nil) { [self setGroup:escortGroup]; } else if ([self group] != escortGroup) [[self group] addShip:other_ship]; if (([other_ship maxFlightSpeed] < cruiseSpeed) && ([other_ship maxFlightSpeed] > cruiseSpeed * 0.3)) { cruiseSpeed = [other_ship maxFlightSpeed] * 0.99; } OOLog(@"ship.escort.accept", @"%@ accepting escort %@.", self, other_ship); [self doScriptEvent:OOJSID("shipAcceptedEscort") withArgument:other_ship]; [other_ship doScriptEvent:OOJSID("escortAccepted") withArgument:self]; [shipAI message:@"ACCEPTED_ESCORT"]; return YES; } else { OOLog(@"ship.escort.reject", @"%@ already got max escorts(%ld). Escort rejected: %@.", self, escortCount, other_ship); } } else { OOLog(@"ship.escort.reject", @"%@ failed canAcceptEscort for escort %@.", self, other_ship); } return NO; } // Exposed to AI - (void) updateEscortFormation { _escortPositionsValid = NO; } /* NOTE: it's tempting to call refreshEscortPositions from coordinatesForEscortPosition: as needed, but that would cause unnecessary extra work if the formation callback itself calls updateEscortFormation. */ - (void) refreshEscortPositions { if (!_escortPositionsValid) { JSContext *context = OOJSAcquireContext(); jsval result; jsval args[] = { INT_TO_JSVAL(0), INT_TO_JSVAL(_maxEscortCount) }; BOOL OK; // Reset validity first so updateEscortFormation can be called from the update callback. _escortPositionsValid = YES; uint8_t i; for (i = 0; i < _maxEscortCount; i++) { args[0] = INT_TO_JSVAL(i); OK = [script callMethod:OOJSID("coordinatesForEscortPosition") inContext:context withArguments:args count:sizeof args / sizeof *args result:&result]; if (OK) OK = JSValueToVector(context, result, &_escortPositions[i]); if (!OK) _escortPositions[i] = kZeroVector; } OOJSRelinquishContext(context); } } - (HPVector) coordinatesForEscortPosition:(unsigned)idx { /* This function causes problems with Thargoids: their missiles (aka Thargons) are automatically added to the escorts group, and when a mother ship dies all thargons will attach themselves as escorts to the surviving battleships. This can lead to huge escort groups. TODO: better handling of Thargoid groups: - put thargons (& all other thargon missiles) in their own non-escort group perhaps? */ // The _escortPositions array is always MAX_ESCORTS long. // Kludge: return the same last escort position if we have escorts above MAX_ESCORTS... idx = MIN(idx, (unsigned)(MAX_ESCORTS - 1)); return HPvector_add(self->position, vectorToHPVector(quaternion_rotate_vector([self normalOrientation], _escortPositions[idx]))); } // Exposed to AI - (void) deployEscorts { NSEnumerator *escortEnum = nil; ShipEntity *escort = nil; ShipEntity *target = nil; NSMutableSet *idleEscorts = nil; unsigned deployCount; if ([self primaryTarget] == nil || _escortGroup == nil) return; OOShipGroup *escortGroup = [self escortGroup]; NSUInteger escortCount = [escortGroup count] - 1; // escorts minus leader. if (escortCount == 0) return; if ([self group] == nil) [self setGroup:escortGroup]; if ([self primaryTarget] == [self lastEscortTarget]) { // already deployed escorts onto this target! return; } [self setLastEscortTarget:[self primaryTarget]]; // Find idle escorts idleEscorts = [NSMutableSet set]; for (escortEnum = [self escortEnumerator]; (escort = [escortEnum nextObject]); ) { if (![[[escort getAI] name] isEqualToString:@"interceptAI.plist"] && ![escort hasNewAI]) { [idleEscorts addObject:escort]; } else if ([escort hasNewAI]) { // JS-based escorts get a help request [escort doScriptEvent:OOJSID("helpRequestReceived") withArgument:self andArgument:[self primaryTarget]]; } } escortCount = [idleEscorts count]; if (escortCount == 0) return; deployCount = ranrot_rand() % escortCount + 1; // Deploy deployCount idle escorts. target = [self primaryTarget]; for (escortEnum = [idleEscorts objectEnumerator]; (escort = [escortEnum nextObject]); ) { [escort addTarget:target]; [escort setAITo:@"interceptAI.plist"]; [escort doScriptEvent:OOJSID("escortAttack") withArgument:target]; if (--deployCount == 0) break; } [self updateEscortFormation]; } // Exposed to AI - (void) dockEscorts { if (![self hasEscorts]) return; OOShipGroup *escortGroup = [self escortGroup]; NSEnumerator *escortEnum = nil; ShipEntity *escort = nil; ShipEntity *target = [self primaryTarget]; unsigned i = 0; // Note: works on escortArray rather than escortEnumerator because escorts may be mutated. for (escortEnum = [[self escortArray] objectEnumerator]; (escort = [escortEnum nextObject]); ) { float delay = i++ * 3.0 + 1.5; // send them off at three second intervals AI *ai = [escort getAI]; // act individually now! if ([escort group] == escortGroup) [escort setGroup:nil]; if ([escort owner] == self) [escort setOwner:escort]; if(target && [target isStation]) [escort setTargetStation:target]; // JSAI: handles own delay if (![escort hasNewAI]) { [escort setAITo:@"dockingAI.plist"]; [ai setState:@"ABORT" afterDelay:delay + 0.25]; } [escort doScriptEvent:OOJSID("escortDock") withArgument:[NSNumber numberWithFloat:delay]]; } // We now have no escorts. [_escortGroup release]; _escortGroup = nil; } - (void) setTargetToNearestStationIncludingHostiles:(BOOL) includeHostiles { // check if the groupID (parent ship) points to a station... Entity *mother = [[self group] leader]; if ([mother isStation]) { [self addTarget:mother]; [self setTargetStation:mother]; return; // head for mother! } /*- selects the nearest station it can find -*/ if (!UNIVERSE) return; int ent_count = UNIVERSE->n_entities; Entity **uni_entities = UNIVERSE->sortedEntities; // grab the public sorted list Entity *my_entities[ent_count]; int i; int station_count = 0; for (i = 0; i < ent_count; i++) if (uni_entities[i]->isStation) my_entities[station_count++] = [uni_entities[i] retain]; // retained // StationEntity *thing = nil, *station = nil; double range2, nearest2 = SCANNER_MAX_RANGE2 * 1000000.0; // 1000x typical scanner range (25600 km), squared. for (i = 0; i < station_count; i++) { thing = (StationEntity *)my_entities[i]; range2 = HPdistance2(position, thing->position); if (range2 < nearest2 && (includeHostiles || ![thing isHostileTo:self])) { station = thing; nearest2 = range2; } } for (i = 0; i < station_count; i++) [my_entities[i] release]; // released // if (station) { [self addTarget:station]; [self setTargetStation:station]; } else { [shipAI message:@"NO_STATION_FOUND"]; } } // Exposed to AI - (void) setTargetToNearestFriendlyStation { [self setTargetToNearestStationIncludingHostiles:NO]; } // Exposed to AI - (void) setTargetToNearestStation { [self setTargetToNearestStationIncludingHostiles:YES]; } // Exposed to AI - (void) setTargetToSystemStation { StationEntity* system_station = [UNIVERSE station]; if (!system_station) { [shipAI message:@"NOTHING_FOUND"]; [shipAI message:@"NO_STATION_FOUND"]; DESTROY(_primaryTarget); [self setTargetStation:nil]; return; } if (!system_station->isStation) { [shipAI message:@"NOTHING_FOUND"]; [shipAI message:@"NO_STATION_FOUND"]; DESTROY(_primaryTarget); [self setTargetStation:nil]; return; } [self addTarget:system_station]; [self setTargetStation:system_station]; return; } - (void) landOnPlanet:(OOPlanetEntity *)planet { if (planet && [self isShuttle]) { [planet welcomeShuttle:self]; } [self doScriptEvent:OOJSID("shipLandedOnPlanet") withArgument:planet andReactToAIMessage:@"LANDED_ON_PLANET"]; #ifndef NDEBUG if ([self reportAIMessages]) { OOLog(@"planet.collide.shuttleLanded", @"DEBUG: %@ landed on planet %@", self, planet); } #endif [UNIVERSE removeEntity:self]; } // Exposed to AI - (void) abortDocking { [[UNIVERSE findEntitiesMatchingPredicate:IsStationPredicate parameter:nil inRange:-1 ofEntity:nil] makeObjectsPerformSelector:@selector(abortDockingForShip:) withObject:self]; } - (NSDictionary *) dockingInstructions { return dockingInstructions; } - (void) broadcastThargoidDestroyed { [[UNIVERSE findShipsMatchingPredicate:HasRolePredicate parameter:@"tharglet" inRange:SCANNER_MAX_RANGE ofEntity:self] makeObjectsPerformSelector:@selector(sendAIMessage:) withObject:@"THARGOID_DESTROYED"]; } static BOOL AuthorityPredicate(Entity *entity, void *parameter) { ShipEntity *victim = parameter; // Select main station, if victim is in aegis if (entity == [UNIVERSE station] && [victim withinStationAegis]) { return YES; } // Select police units in typical scanner range if ([entity scanClass] == CLASS_POLICE && HPdistance2([victim position], [entity position]) < SCANNER_MAX_RANGE2) { return YES; } // Reject others return NO; } - (void) broadcastHitByLaserFrom:(ShipEntity *) aggressor_ship { /*-- If you're clean, locates all police and stations in range and tells them OFFENCE_COMMITTED --*/ if (!UNIVERSE) return; if ([self bounty]) return; if (!aggressor_ship) return; if ( (scanClass == CLASS_NEUTRAL)|| (scanClass == CLASS_STATION)|| (scanClass == CLASS_BUOY)|| (scanClass == CLASS_POLICE)|| (scanClass == CLASS_MILITARY)|| (scanClass == CLASS_PLAYER)) // only for active ships... { NSArray *authorities = nil; NSEnumerator *authEnum = nil; ShipEntity *auth = nil; authorities = [UNIVERSE findShipsMatchingPredicate:AuthorityPredicate parameter:self inRange:-1 ofEntity:nil]; authEnum = [authorities objectEnumerator]; while ((auth = [authEnum nextObject])) { [auth setFoundTarget:aggressor_ship]; [auth doScriptEvent:OOJSID("offenceCommittedNearby") withArgument:aggressor_ship andArgument:self]; [auth reactToAIMessage:@"OFFENCE_COMMITTED" context:@"combat update"]; } } } - (void) sendMessage:(NSString *) message_text toShip:(ShipEntity*) other_ship withUnpilotedOverride:(BOOL)unpilotedOverride { if (!other_ship || !message_text) return; if (!crew && !unpilotedOverride) return; double d2 = HPdistance2(position, [other_ship position]); if (d2 > scannerRange * scannerRange) return; // out of comms range NSString *expandedMessage = OOExpand(message_text); // consistent with broadcast message. if (other_ship->isPlayer) { [self setCommsMessageColor]; [(PlayerEntity *)other_ship receiveCommsMessage:expandedMessage from:self]; messageTime = 6.0; [UNIVERSE resetCommsLogColor]; } else [other_ship receiveCommsMessage:expandedMessage from:self]; } - (void) sendExpandedMessage:(NSString *)message_text toShip:(ShipEntity *)other_ship { if (!other_ship || !crew) return; // nobody to receive or send the signal if ((lastRadioMessage) && (messageTime > 0.0) && [message_text isEqual:lastRadioMessage]) return; // don't send the same message too often [lastRadioMessage autorelease]; lastRadioMessage = [message_text retain]; double d2 = HPdistance2(position, [other_ship position]); if (d2 > scannerRange * scannerRange) { // out of comms range return; } Random_Seed very_random_seed; very_random_seed.a = rand() & 255; very_random_seed.b = rand() & 255; very_random_seed.c = rand() & 255; very_random_seed.d = rand() & 255; very_random_seed.e = rand() & 255; very_random_seed.f = rand() & 255; seed_RNG_only_for_planet_description(very_random_seed); NSDictionary *specials = [NSDictionary dictionaryWithObjectsAndKeys: [self displayName], @"[self:name]", [other_ship identFromShip: self], @"[target:name]", nil]; NSString *expandedMessage = OOExpandDescriptionString(OOStringExpanderDefaultRandomSeed(), message_text, specials, nil, nil, kOOExpandNoOptions); [self sendMessage:expandedMessage toShip:other_ship withUnpilotedOverride:NO]; } - (void) broadcastAIMessage:(NSString *) ai_message { NSString *expandedMessage = OOExpand(ai_message); [self checkScanner]; unsigned i; for (i = 0; i < n_scanned_ships ; i++) { ShipEntity* ship = scanned_ships[i]; [[ship getAI] message: expandedMessage]; } } - (void) broadcastMessage:(NSString *) message_text withUnpilotedOverride:(BOOL) unpilotedOverride { NSString *expandedMessage = OOExpand(message_text); // consistent with broadcast message. if (!crew && !unpilotedOverride) return; // nobody to send the signal and no override for unpiloted craft is set [self checkScanner]; unsigned i; for (i = 0; i < n_scanned_ships ; i++) { ShipEntity* ship = scanned_ships[i]; if (![ship isPlayer]) [ship receiveCommsMessage:expandedMessage from:self]; } PlayerEntity *player = PLAYER; // make sure that the player always receives a message when in range // SCANNER_MAX_RANGE2 because it's the player's scanner range // which is important if (HPdistance2(position, [player position]) < SCANNER_MAX_RANGE2) { [self setCommsMessageColor]; [player receiveCommsMessage:expandedMessage from:self]; messageTime = 6.0; [UNIVERSE resetCommsLogColor]; } } - (void) setCommsMessageColor { float hue = 0.0625f * (universalID & 15); [[UNIVERSE commLogGUI] setTextColor:[OOColor colorWithHue:hue saturation:0.375f brightness:1.0f alpha:1.0f]]; if (scanClass == CLASS_THARGOID) [[UNIVERSE commLogGUI] setTextColor:[OOColor greenColor]]; if (scanClass == CLASS_POLICE) [[UNIVERSE commLogGUI] setTextColor:[OOColor cyanColor]]; } - (void) receiveCommsMessage:(NSString *) message_text from:(ShipEntity *) other { // Too complex for AI scripts to handle, JS event only. [self doScriptEvent:OOJSID("commsMessageReceived") withArgument:message_text andArgument:other]; } - (void) commsMessage:(NSString *)valueString withUnpilotedOverride:(BOOL)unpilotedOverride { Random_Seed very_random_seed; very_random_seed.a = rand() & 255; very_random_seed.b = rand() & 255; very_random_seed.c = rand() & 255; very_random_seed.d = rand() & 255; very_random_seed.e = rand() & 255; very_random_seed.f = rand() & 255; seed_RNG_only_for_planet_description(very_random_seed); [self broadcastMessage:valueString withUnpilotedOverride:unpilotedOverride]; } - (BOOL) markedForFines { return being_fined; } - (BOOL) markForFines { if (being_fined) return NO; // can't mark twice being_fined = ([self legalStatus] > 0); return being_fined; } - (BOOL) isMining { return ((behaviour == BEHAVIOUR_ATTACK_MINING_TARGET)&&([forward_weapon_type isMiningLaser])); } - (void) interpretAIMessage:(NSString *)ms { if ([ms hasPrefix:AIMS_AGGRESSOR_SWITCHED_TARGET]) { // if I'm under attack send a thank-you message to the rescuer // NSArray* tokens = ScanTokensFromString(ms); int switcher_id = [(NSString*)[tokens objectAtIndex:1] intValue]; // Attacker that switched targets. Entity* switcher = [UNIVERSE entityForUniversalID:switcher_id]; int rescuer_id = [(NSString*)[tokens objectAtIndex:2] intValue]; // New primary target of attacker. Entity* rescuer = [UNIVERSE entityForUniversalID:rescuer_id]; if ((switcher == [self primaryAggressor])&&(switcher == [self primaryTarget])&&(switcher)&&(rescuer)&&(rescuer->isShip)&&([self thankedShip] != rescuer)&&(scanClass != CLASS_THARGOID)) { ShipEntity* rescueShip = (ShipEntity*)rescuer; // ShipEntity* switchingShip = (ShipEntity*)switcher; if (scanClass == CLASS_POLICE) { [self sendExpandedMessage:@"[police-thanks-for-assist]" toShip:rescueShip]; [rescueShip setBounty:[rescueShip bounty] * 0.80 withReason:kOOLegalStatusReasonAssistingPolice]; // lower bounty by 20% } else { [self sendExpandedMessage:@"[thanks-for-assist]" toShip:rescueShip]; } [self setThankedShip:rescuer]; } } } - (BoundingBox) findBoundingBoxRelativeTo:(Entity *)other InVectors:(Vector) _i :(Vector) _j :(Vector) _k { HPVector opv = other ? other->position : position; return [self findBoundingBoxRelativeToPosition:opv InVectors:_i :_j :_k]; } // Exposed to AI and legacy scripts. - (void) spawn:(NSString *)roles_number { NSArray *tokens = ScanTokensFromString(roles_number); NSString *roleString = nil; NSString *numberString = nil; NSUInteger number; if ([tokens count] != 2) { OOLog(kOOLogSyntaxAddShips, @"***** Could not spawn: \"%@\" (must be two tokens, role and number)",roles_number); return; } roleString = [tokens oo_stringAtIndex:0]; numberString = [tokens oo_stringAtIndex:1]; number = [numberString intValue]; [self spawnShipsWithRole:roleString count:number]; } - (int) checkShipsInVicinityForWitchJumpExit { // checks if there are any large masses close by // since we want to place the space station at least 10km away // the formula we'll use is K x m / d2 < 1.0 // (m = mass, d2 = distance squared) // coriolis station is mass 455,223,200 // 10km is 10,000m, // 10km squared is 100,000,000 // therefore K is 0.22 (approx) int result = NO_TARGET; GLfloat k = 0.1; int ent_count = UNIVERSE->n_entities; Entity** uni_entities = UNIVERSE->sortedEntities; // grab the public sorted list ShipEntity* my_entities[ent_count]; int i; int ship_count = 0; for (i = 0; i < ent_count; i++) if ((uni_entities[i]->isShip)&&(uni_entities[i] != self)) my_entities[ship_count++] = (ShipEntity*)[uni_entities[i] retain]; // retained // for (i = 0; (i < ship_count)&&(result == NO_TARGET) ; i++) { ShipEntity* ship = my_entities[i]; HPVector delta = HPvector_between(position, ship->position); GLfloat d2 = HPmagnitude2(delta); if (![ship isPlayer] || ![PLAYER isDocked]) { // player doesn't block if docked if ((k * [ship mass] > d2)&&(d2 < SCANNER_MAX_RANGE2)) // if you go off (typical) scanner from a blocker - it ceases to block result = [ship universalID]; } } for (i = 0; i < ship_count; i++) [my_entities[i] release]; // released return result; } - (BOOL) trackCloseContacts { return trackCloseContacts; } - (void) setTrackCloseContacts:(BOOL) value { if (value == (BOOL)trackCloseContacts) return; trackCloseContacts = value; [closeContactsInfo release]; if (trackCloseContacts) { closeContactsInfo = [[NSMutableDictionary alloc] init]; } else { closeContactsInfo = nil; } } #if OO_SALVAGE_SUPPORT // Never used. - (void) claimAsSalvage { // Create a bouy and beacon where the hulk is. // Get the main GalCop station to launch a pilot boat to deliver a pilot to the hulk. OOLog(@"claimAsSalvage.called", @"claimAsSalvage called on %@ %@", [self name], [self roleSet]); // Not an abandoned hulk, so don't allow the salvage if (![self isHulk]) { OOLog(@"claimAsSalvage.failed.notHulk", @"claimAsSalvage failed because not a hulk"); return; } // Set target to main station, and return now if it can't be found [self setTargetToSystemStation]; if ([self primaryTarget] == nil) { OOLog(@"claimAsSalvage.failed.noStation", @"claimAsSalvage failed because did not find a station"); return; } // Get the station to launch a pilot boat to bring a pilot out to the hulk (use a viper for now) StationEntity *station = (StationEntity *)[self primaryTarget]; OOLog(@"claimAsSalvage.requestingPilot", @"claimAsSalvage asking station to launch a pilot boat"); [station launchShipWithRole:@"pilot"]; [self setReportAIMessages:YES]; OOLog(@"claimAsSalvage.success", @"claimAsSalvage setting own state machine to capturedShipAI.plist"); [self setAITo:@"capturedShipAI.plist"]; } - (void) sendCoordinatesToPilot { Entity *scan; ShipEntity *scanShip, *pilot; n_scanned_ships = 0; scan = z_previous; OOLog(@"ship.pilotage", @"searching for pilot boat"); while (scan &&(scan->isShip == NO)) { scan = scan->z_previous; // skip non-ships } pilot = nil; while (scan) { if (scan->isShip) { scanShip = (ShipEntity *)scan; if ([self hasRole:@"pilot"] == YES) { if ([scanShip primaryTarget] == nil) { OOLog(@"ship.pilotage", @"found pilot boat with no target, will use this one"); pilot = scanShip; [pilot setPrimaryRole:@"pilot"]; break; } } } scan = scan->z_previous; while (scan && (scan->isShip == NO)) { scan = scan->z_previous; } } if (pilot != nil) { OOLog(@"ship.pilotage", @"becoming pilot target and setting AI"); [pilot setReportAIMessages:YES]; [pilot addTarget:self]; [pilot setAITo:@"pilotAI.plist"]; [self reactToAIMessage:@"FOUND_PILOT" context:@"flight update"]; } } - (void) pilotArrived { [self setHulk:NO]; [self reactToAIMessage:@"PILOT_ARRIVED" context:@"flight update"]; } #endif #ifndef NDEBUG - (void)dumpSelfState { NSMutableArray *flags = nil; NSString *flagsString = nil; [super dumpSelfState]; OOLog(@"dumpState.shipEntity", @"Type: %@", [self shipDataKey]); OOLog(@"dumpState.shipEntity", @"Name: %@", name); OOLog(@"dumpState.shipEntity", @"Display Name: %@", [self displayName]); OOLog(@"dumpState.shipEntity", @"Roles: %@", [self roleSet]); OOLog(@"dumpState.shipEntity", @"Primary role: %@", primaryRole); OOLog(@"dumpState.shipEntity", @"Script: %@", script); OOLog(@"dumpState.shipEntity", @"Subentity count: %lu", [self subEntityCount]); OOLog(@"dumpState.shipEntity", @"Behaviour: %@", OOStringFromBehaviour(behaviour)); id target = [self primaryTarget]; if (target == nil) target = @""; OOLog(@"dumpState.shipEntity", @"Target: %@", target); OOLog(@"dumpState.shipEntity", @"Destination: %@", HPVectorDescription(_destination)); OOLog(@"dumpState.shipEntity", @"Other destination: %@", HPVectorDescription(coordinates)); OOLog(@"dumpState.shipEntity", @"Waypoint count: %u", number_of_navpoints); OOLog(@"dumpState.shipEntity", @"Desired speed: %g", desired_speed); OOLog(@"dumpState.shipEntity", @"Thrust: %g", thrust); if ([self escortCount] != 0) OOLog(@"dumpState.shipEntity", @"Escort count: %u", [self escortCount]); OOLog(@"dumpState.shipEntity", @"Fuel: %i", fuel); OOLog(@"dumpState.shipEntity", @"Fuel accumulator: %g", fuel_accumulator); OOLog(@"dumpState.shipEntity", @"Missile count: %u", missiles); if (shipAI != nil && OOLogWillDisplayMessagesInClass(@"dumpState.shipEntity.ai")) { OOLog(@"dumpState.shipEntity.ai", @"AI:"); OOLogPushIndent(); OOLogIndent(); @try { [shipAI dumpState]; } @catch (id exception) {} OOLogPopIndent(); } OOLog(@"dumpState.shipEntity", @"Jink position: %@", VectorDescription(jink)); OOLog(@"dumpState.shipEntity", @"Frustration: %g", frustration); OOLog(@"dumpState.shipEntity", @"Success factor: %g", success_factor); OOLog(@"dumpState.shipEntity", @"Shots fired: %u", shot_counter); OOLog(@"dumpState.shipEntity", @"Time since shot: %g", [self shotTime]); OOLog(@"dumpState.shipEntity", @"Spawn time: %g (%g seconds ago)", [self spawnTime], [self timeElapsedSinceSpawn]); if ([self isBeacon]) { OOLog(@"dumpState.shipEntity", @"Beacon code: %@", [self beaconCode]); } OOLog(@"dumpState.shipEntity", @"Hull temperature: %g", ship_temperature); OOLog(@"dumpState.shipEntity", @"Heat insulation: %g", [self heatInsulation]); flags = [NSMutableArray array]; #define ADD_FLAG_IF_SET(x) if (x) { [flags addObject:@#x]; } ADD_FLAG_IF_SET(military_jammer_active); ADD_FLAG_IF_SET(docking_match_rotation); ADD_FLAG_IF_SET(pitching_over); ADD_FLAG_IF_SET(reportAIMessages); ADD_FLAG_IF_SET(being_mined); ADD_FLAG_IF_SET(being_fined); ADD_FLAG_IF_SET(isHulk); ADD_FLAG_IF_SET(trackCloseContacts); ADD_FLAG_IF_SET(isNearPlanetSurface); ADD_FLAG_IF_SET(isFrangible); ADD_FLAG_IF_SET(cloaking_device_active); ADD_FLAG_IF_SET(canFragment); ADD_FLAG_IF_SET([self proximityAlert] != nil); flagsString = [flags count] ? [flags componentsJoinedByString:@", "] : (NSString *)@"none"; OOLog(@"dumpState.shipEntity", @"Flags: %@", flagsString); } #endif - (OOJSScript *)script { return script; } - (NSDictionary *)scriptInfo { return (scriptInfo != nil) ? scriptInfo : (NSDictionary *)[NSDictionary dictionary]; } - (void) overrideScriptInfo:(NSDictionary *)override { if (scriptInfo == nil) scriptInfo = [override retain]; else if (override != nil) { NSMutableDictionary *newInfo = [NSMutableDictionary dictionaryWithDictionary:scriptInfo]; [newInfo addEntriesFromDictionary:override]; [scriptInfo release]; scriptInfo = [newInfo copy]; } } - (Entity *)entityForShaderProperties { return [self rootShipEntity]; } // *** Script event dispatch. - (void) doScriptEvent:(jsid)message { JSContext *context = OOJSAcquireContext(); [self doScriptEvent:message inContext:context withArguments:NULL count:0]; OOJSRelinquishContext(context); } - (void) doScriptEvent:(jsid)message withArgument:(id)argument { JSContext *context = OOJSAcquireContext(); jsval value = OOJSValueFromNativeObject(context, argument); [self doScriptEvent:message inContext:context withArguments:&value count:1]; OOJSRelinquishContext(context); } - (void) doScriptEvent:(jsid)message withArgument:(id)argument1 andArgument:(id)argument2 { JSContext *context = OOJSAcquireContext(); jsval argv[2] = { OOJSValueFromNativeObject(context, argument1), OOJSValueFromNativeObject(context, argument2) }; [self doScriptEvent:message inContext:context withArguments:argv count:2]; OOJSRelinquishContext(context); } - (void) doScriptEvent:(jsid)message withArguments:(NSArray *)arguments { JSContext *context = OOJSAcquireContext(); uintN i, argc; jsval *argv = NULL; // Convert arguments to JS values and make them temporarily un-garbage-collectable. argc = (uintN)[arguments count]; if (argc != 0) { argv = malloc(sizeof *argv * argc); if (argv != NULL) { for (i = 0; i != argc; ++i) { argv[i] = [[arguments objectAtIndex:i] oo_jsValueInContext:context]; OOJSAddGCValueRoot(context, &argv[i], "event parameter"); } } else argc = 0; } [self doScriptEvent:message inContext:context withArguments:argv count:argc]; // Re-garbage-collectibalize the arguments and free the array. if (argv != NULL) { for (i = 0; i != argc; ++i) { JS_RemoveValueRoot(context, &argv[i]); } free(argv); } OOJSRelinquishContext(context); } - (void) doScriptEvent:(jsid)message withArguments:(jsval *)argv count:(uintN)argc { JSContext *context = OOJSAcquireContext(); [self doScriptEvent:message inContext:context withArguments:argv count:argc]; OOJSRelinquishContext(context); } - (void) doScriptEvent:(jsid)message inContext:(JSContext *)context withArguments:(jsval *)argv count:(uintN)argc { // This method is a bottleneck so that PlayerEntity can override at one point. [script callMethod:message inContext:context withArguments:argv count:argc result:NULL]; [aiScript callMethod:message inContext:context withArguments:argv count:argc result:NULL]; } - (void) reactToAIMessage:(NSString *)message context:(NSString *)debugContext { [shipAI reactToMessage:message context:debugContext]; } - (void) sendAIMessage:(NSString *)message { [shipAI message:message]; } - (void) doScriptEvent:(jsid)scriptEvent andReactToAIMessage:(NSString *)aiMessage { [self doScriptEvent:scriptEvent]; [self reactToAIMessage:aiMessage context:nil]; } - (void) doScriptEvent:(jsid)scriptEvent withArgument:(id)argument andReactToAIMessage:(NSString *)aiMessage { [self doScriptEvent:scriptEvent withArgument:argument]; [self reactToAIMessage:aiMessage context:nil]; } // exposed for shaders; fake alert level // since NPCs don't have torus drive, they're never at condition green - (OOAlertCondition) alertCondition { if ([self status] == STATUS_DOCKED) { return ALERT_CONDITION_DOCKED; } if ([self hasHostileTarget] || energy < maxEnergy / 4) { return ALERT_CONDITION_RED; } return ALERT_CONDITION_YELLOW; } - (OOAlertCondition) realAlertCondition { if ([self status] == STATUS_DOCKED) { return ALERT_CONDITION_DOCKED; } if ([self hasHostileTarget]) { return ALERT_CONDITION_RED; } else { NSEnumerator *sEnum = [_defenseTargets objectEnumerator]; ShipEntity *ship = nil; double scanrange2 = scannerRange * scannerRange; while ((ship = [sEnum nextObject])) { if ([ship hasHostileTarget] || ([ship isPlayer] && [PLAYER weaponsOnline])) { if (HPdistance2([ship position],position) < scanrange2) { return ALERT_CONDITION_RED; } } } // also need to check primary target separately if ([self hasHostileTarget]) { Entity *ptarget = [self primaryTargetWithoutValidityCheck]; if (ptarget != nil && [ptarget isShip]) { ship = (ShipEntity *)ptarget; if ([ship hasHostileTarget] || ([ship isPlayer] && [PLAYER weaponsOnline])) { if (HPdistance2([ship position],position) < scanrange2 * 1.5625) { return ALERT_CONDITION_RED; } } } } if (_group) { sEnum = [_group objectEnumerator]; while ((ship = [sEnum nextObject])) { if ([ship hasHostileTarget] || ([ship isPlayer] && [PLAYER weaponsOnline])) { if (HPdistance2([ship position],position) < scanrange2) { return ALERT_CONDITION_RED; } } } } if (_escortGroup && _group != _escortGroup) { sEnum = [_escortGroup objectEnumerator]; while ((ship = [sEnum nextObject])) { if ([ship hasHostileTarget] || ([ship isPlayer] && [PLAYER weaponsOnline])) { if (HPdistance2([ship position],position) < scanrange2) { return ALERT_CONDITION_RED; } } } } } return ALERT_CONDITION_YELLOW; } // Exposed to AI and scripts. - (void) doNothing { } #ifndef NDEBUG - (NSString *) descriptionForObjDump { NSString *desc = [super descriptionForObjDump]; desc = [NSString stringWithFormat:@"%@ mass %g", desc, [self mass]]; if (![self isPlayer]) { desc = [NSString stringWithFormat:@"%@ AI: %@", desc, [[self getAI] shortDescriptionComponents]]; } return desc; } #endif @end @implementation Entity (SubEntityRelationship) - (BOOL) isShipWithSubEntityShip:(Entity *)other { return NO; } - (void) drawSubEntityImmediate:(bool)immediate translucent:(bool)translucent { // Do nothing. } @end @implementation ShipEntity (SubEntityRelationship) - (BOOL) isShipWithSubEntityShip:(Entity *)other { assert ([self isShip]); if (![other isShip]) return NO; if (![other isSubEntity]) return NO; if ([other owner] != self) return NO; #ifndef NDEBUG // Sanity check; this should always be true. if (![self hasSubEntity:(ShipEntity *)other]) { OOLogERR(@"ship.subentity.sanityCheck.failed", @"%@ thinks it's a subentity of %@, but the supposed parent does not agree. %@", [other shortDescription], [self shortDescription], @"This is an internal error, please report it."); [other setOwner:nil]; return NO; } #endif return YES; } @end NSDictionary *OODefaultShipShaderMacros(void) { static NSDictionary *macros = nil; if (macros == nil) { macros = [[[ResourceManager materialDefaults] oo_dictionaryForKey:@"ship-prefix-macros" defaultValue:[NSDictionary dictionary]] retain]; } return macros; } // is this the right place for this function now? - CIM BOOL OOUniformBindingPermitted(NSString *propertyName, id bindingTarget) { static NSSet *entityWhitelist = nil; static NSSet *shipWhitelist = nil; static NSSet *playerShipWhitelist = nil; static NSSet *visualEffectWhitelist = nil; if (entityWhitelist == nil) { NSDictionary *wlDict = [ResourceManager whitelistDictionary]; entityWhitelist = [[NSSet alloc] initWithArray:[wlDict oo_arrayForKey:@"shader_entity_binding_methods"]]; shipWhitelist = [[NSSet alloc] initWithArray:[wlDict oo_arrayForKey:@"shader_ship_binding_methods"]]; playerShipWhitelist = [[NSSet alloc] initWithArray:[wlDict oo_arrayForKey:@"shader_player_ship_binding_methods"]]; visualEffectWhitelist = [[NSSet alloc] initWithArray:[wlDict oo_arrayForKey:@"shader_visual_effect_binding_methods"]]; } if ([bindingTarget isKindOfClass:[Entity class]]) { if ([entityWhitelist containsObject:propertyName]) return YES; if ([bindingTarget isShip]) { if ([shipWhitelist containsObject:propertyName]) return YES; } if ([bindingTarget isPlayerLikeShip]) { if ([playerShipWhitelist containsObject:propertyName]) return YES; } if ([bindingTarget isVisualEffect]) { if ([visualEffectWhitelist containsObject:propertyName]) return YES; } } return NO; } GLfloat getWeaponRangeFromType(OOWeaponType weapon_type) { return [weapon_type weaponRange]; } BOOL isWeaponNone(OOWeaponType weapon) { return weapon == nil || [[weapon identifier] isEqualToString:@"EQ_WEAPON_NONE"]; } oolite-1.82/src/Core/Entities/ShipEntityAI.h000066400000000000000000000037241256642440500207030ustar00rootroot00000000000000/* ShipEntityAI.h Additional methods relating to behaviour/artificial intelligence. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "ShipEntity.h" @class AI, Universe, OOPlanetEntity; @interface ShipEntity (AI) // AI methods also used in other code. - (void) setAITo:(NSString *)aiString; - (void) setAIScript:(NSString *)aiString; - (void) switchAITo:(NSString *)aiString; - (void) scanForHostiles; - (BOOL) performHyperSpaceToSpecificSystem:(OOSystemID)systemID; - (void) scanForNearestIncomingMissile; - (void) enterTargetWormhole; - (void) enterPlayerWormhole; - (void) wormholeEscorts; - (void) wormholeEntireGroup; - (BOOL) suggestEscortTo:(ShipEntity *)mother; - (void) groupAttackTarget; - (void) performAttack; - (void) performCollect; - (void) performEscort; - (void) performFaceDestination; - (void) performFlee; - (void) performFlyToRangeFromDestination; - (void) performHold; - (void) performIdle; - (void) performIntercept; - (void) performLandOnPlanet; - (void) performMining; - (void) performScriptedAI; - (void) performScriptedAttackAI; - (void) performStop; - (void) performTumble; - (void) broadcastDistressMessage; - (void) broadcastDistressMessageWithDumping:(BOOL)dumpCargo; - (void) requestDockingCoordinates; - (void) recallDockingInstructions; @end oolite-1.82/src/Core/Entities/ShipEntityAI.m000066400000000000000000002172511256642440500207120ustar00rootroot00000000000000/* ShipEntityAI.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "ShipEntityAI.h" #import "OOMaths.h" #import "Universe.h" #import "AI.h" #import "StationEntity.h" #import "OOSunEntity.h" #import "OOPlanetEntity.h" #import "WormholeEntity.h" #import "PlayerEntity.h" #import "PlayerEntityLegacyScriptEngine.h" #import "OOJavaScriptEngine.h" #import "OOJSFunction.h" #import "OOShipGroup.h" #import "OOStringExpander.h" #import "OOStringParsing.h" #import "OOEntityFilterPredicate.h" #import "OOConstToString.h" #import "OOConstToJSString.h" #import "OOCollectionExtractors.h" #import "ResourceManager.h" @interface ShipEntity (OOAIPrivate) - (void) checkFoundTarget; - (BOOL)performHyperSpaceExitReplace:(BOOL)replace; - (BOOL)performHyperSpaceExitReplace:(BOOL)replace toSystem:(OOSystemID)systemID; - (void)scanForNearestShipWithPredicate:(EntityFilterPredicate)predicate parameter:(void *)parameter; - (void)scanForNearestShipWithNegatedPredicate:(EntityFilterPredicate)predicate parameter:(void *)parameter; - (void) acceptDistressMessageFrom:(ShipEntity *)other; @end @interface StationEntity (OOAIPrivate) - (void) acceptDistressMessageFrom:(ShipEntity *)other; @end @interface ShipEntity (PureAI) // Methods used only by AI. - (void) setStateTo:(NSString *)state; - (void) pauseAI:(NSString *)intervalString; - (void) randomPauseAI:(NSString *)intervalString; - (void) dropMessages:(NSString *)messageString; - (void) debugDumpPendingMessages; - (void) setDestinationToCurrentLocation; - (void) setDesiredRangeTo:(NSString *)rangeString; - (void) setDesiredRangeForWaypoint; - (void) setSpeedTo:(NSString *)speedString; - (void) setSpeedFactorTo:(NSString *)speedString; - (void) setSpeedToCruiseSpeed; - (void) setThrustFactorTo:(NSString *)thrustFactorString; - (void) setTargetToPrimaryAggressor; - (void) scanForNearestMerchantman; - (void) scanForRandomMerchantman; - (void) scanForLoot; - (void) scanForRandomLoot; - (void) setTargetToFoundTarget; - (void) checkForFullHold; - (void) getWitchspaceEntryCoordinates; - (void) setDestinationFromCoordinates; - (void) setCoordinatesFromPosition; - (void) fightOrFleeMissile; - (void) setCourseToPlanet; - (void) setTakeOffFromPlanet; - (void) landOnPlanet; - (void) checkTargetLegalStatus; - (void) checkOwnLegalStatus; - (void) exitAIWithMessage:(NSString *)message; - (void) setDestinationToTarget; - (void) setDestinationWithinTarget; - (void) checkCourseToDestination; - (void) checkAegis; - (void) checkEnergy; - (void) checkHeatInsulation; - (void) scanForOffenders; - (void) setCourseToWitchpoint; - (void) setDestinationToWitchpoint; - (void) setDestinationToStationBeacon; - (void) performHyperSpaceExit; - (void) performHyperSpaceExitWithoutReplacing; - (void) wormholeGroup; - (void) commsMessage:(NSString *)valueString; - (void) commsMessageByUnpiloted:(NSString *)valueString; - (void) ejectCargo; - (void) scanForThargoid; - (void) scanForNonThargoid; - (void) thargonCheckMother; - (void) becomeUncontrolledThargon; - (void) checkDistanceTravelled; - (void) fightOrFleeHostiles; - (void) suggestEscort; - (void) escortCheckMother; - (void) checkGroupOddsVersusTarget; - (void) scanForFormationLeader; - (void) messageMother:(NSString *)msgString; - (void) setPlanetPatrolCoordinates; - (void) setSunSkimStartCoordinates; - (void) setSunSkimEndCoordinates; - (void) setSunSkimExitCoordinates; - (void) patrolReportIn; - (void) checkForMotherStation; - (void) sendTargetCommsMessage:(NSString *)message; - (void) markTargetForFines; - (void) markTargetForOffence:(NSString *)valueString; - (void) storeTarget; - (void) recallStoredTarget; - (void) scanForRocks; - (void) setDestinationToDockingAbort; - (void) requestNewTarget; - (void) rollD:(NSString *)die_number; - (void) scanForNearestShipWithPrimaryRole:(NSString *)scanRole; - (void) scanForNearestShipHavingRole:(NSString *)scanRole; - (void) scanForNearestShipWithAnyPrimaryRole:(NSString *)scanRoles; - (void) scanForNearestShipHavingAnyRole:(NSString *)scanRoles; - (void) scanForNearestShipWithScanClass:(NSString *)scanScanClass; - (void) scanForNearestShipWithoutPrimaryRole:(NSString *)scanRole; - (void) scanForNearestShipNotHavingRole:(NSString *)scanRole; - (void) scanForNearestShipWithoutAnyPrimaryRole:(NSString *)scanRoles; - (void) scanForNearestShipNotHavingAnyRole:(NSString *)scanRoles; - (void) scanForNearestShipWithoutScanClass:(NSString *)scanScanClass; - (void) setCoordinates:(NSString *)system_x_y_z; - (void) checkForNormalSpace; - (void) setTargetToRandomStation; - (void) setTargetToLastStation; - (void) addFuel:(NSString *) fuel_number; - (void) scriptActionOnTarget:(NSString *) action; - (void) sendScriptMessage:(NSString *)message; - (void) ai_throwSparks; - (void) explodeSelf; - (void) ai_debugMessage:(NSString *)message; // racing code. - (void) targetFirstBeaconWithCode:(NSString *) code; - (void) targetNextBeaconWithCode:(NSString *) code; - (void) setRacepointsFromTarget; - (void) performFlyRacepoints; // defense targets - (void) addPrimaryAggressorAsDefenseTarget; - (void) addFoundTargetAsDefenseTarget; - (void) findNewDefenseTarget; @end @implementation ShipEntity (AI) - (void) setAITo:(NSString *)aiString { // don't try to load real AIs if the game hasn't started yet if (![PLAYER scriptsLoaded]) { aiString = @"oolite-nullAI.js"; } if ([aiString hasSuffix:@".plist"]) { [[self getAI] setStateMachine:aiString withJSScript:@"oolite-nullAI.js"]; [self setAIScript:@"oolite-nullAI.js"]; } else if ([aiString hasSuffix:@".js"]) { [[self getAI] setStateMachine:@"nullAI.plist" withJSScript:aiString]; [self setAIScript:aiString]; } else { NSString *path = [ResourceManager pathForFileNamed:[aiString stringByAppendingString:@".js"] inFolder:@"AIs"]; if (path == nil) // no js, use plist { [self setAITo:[aiString stringByAppendingString:@".plist"]]; } else { [self setAITo:[aiString stringByAppendingString:@".js"]]; } } } - (void) setAIScript:(NSString *)aiString { NSMutableDictionary *properties = nil; properties = [NSMutableDictionary dictionary]; [properties setObject:self forKey:@"ship"]; [aiScript autorelease]; aiScript = [OOScript jsAIScriptFromFileNamed:aiString properties:properties]; if (aiScript == nil) { OOLog(@"ai.load.failed.unknownAI",@"Unable to load JS AI %@ for ship %@ (%@ for role %@)",aiString,self,[self shipDataKey],[self primaryRole]); aiScript = [OOScript jsAIScriptFromFileNamed:@"oolite-nullAI.js" properties:properties]; } else { aiScriptWakeTime = 0; haveStartedJSAI = NO; } [aiScript retain]; } - (void) switchAITo:(NSString *)aiString { [self setAITo:aiString]; [[self getAI] clearStack]; } - (void) scanForHostiles { /*-- Locates all the ships in range targeting the receiver and chooses the nearest --*/ DESTROY(_foundTarget); [self checkScanner]; unsigned i; GLfloat found_d2 = scannerRange * scannerRange; for (i = 0; i < n_scanned_ships ; i++) { ShipEntity *thing = scanned_ships[i]; GLfloat d2 = distance2_scanned_ships[i]; if ((d2 < found_d2) && ([thing isThargoid] || (([thing primaryTarget] == self) && [thing hasHostileTarget]) || [thing isDefenseTarget:self]) && ![thing isCloaked]) { [self setFoundTarget:thing]; found_d2 = d2; } } [self checkFoundTarget]; } - (void) groupAttackTarget { NSEnumerator *shipEnum = nil; ShipEntity *target = nil, *ship = nil; target = [self primaryTarget]; if (target == nil) return; if ([self group] == nil) // ship is alone! { [self setFoundTarget:target]; [shipAI reactToMessage:@"GROUP_ATTACK_TARGET" context:@"groupAttackTarget"]; [self doScriptEvent:OOJSID("helpRequestReceived") withArgument:self andArgument:target]; return; } for (shipEnum = [[self group] mutationSafeEnumerator]; (ship = [shipEnum nextObject]); ) { [ship setFoundTarget:target]; [ship reactToAIMessage:@"GROUP_ATTACK_TARGET" context:@"groupAttackTarget"]; [ship doScriptEvent:OOJSID("helpRequestReceived") withArgument:self andArgument:target]; if ([ship escortGroup] != [ship group] && [[ship escortGroup] count] > 1) // Ship has a seperate escort group. { ShipEntity *escort = nil; NSEnumerator *shipEnum = nil; NSArray *escortMembers = [[ship escortGroup] memberArrayExcludingLeader]; for (shipEnum = [escortMembers objectEnumerator]; (escort = [shipEnum nextObject]); ) { [escort setFoundTarget:target]; [escort reactToAIMessage:@"GROUP_ATTACK_TARGET" context:@"groupAttackTarget"]; [escort doScriptEvent:OOJSID("helpRequestReceived") withArgument:self andArgument:target]; } } } } - (void) performAttack { if (behaviour != BEHAVIOUR_EVASIVE_ACTION) { behaviour = BEHAVIOUR_ATTACK_TARGET; desired_range = 1250 * randf() + 750; // 750 til 2000 frustration = 0.0; } } - (void) performCollect { behaviour = BEHAVIOUR_COLLECT_TARGET; frustration = 0.0; } - (void) performEscort { if(behaviour != BEHAVIOUR_FORMATION_FORM_UP) { behaviour = BEHAVIOUR_FORMATION_FORM_UP; frustration = 0.0; // behavior changed, reset frustration. } } - (void) performFaceDestination { behaviour = BEHAVIOUR_FACE_DESTINATION; frustration = 0.0; } - (void) performFlee { if (behaviour != BEHAVIOUR_FLEE_EVASIVE_ACTION) { behaviour = BEHAVIOUR_FLEE_TARGET; [self setEvasiveJink:400.0]; frustration = 0.0; } } - (void) performFlyToRangeFromDestination { behaviour = BEHAVIOUR_FLY_RANGE_FROM_DESTINATION; frustration = 0.0; } - (void) performHold { desired_speed = 0.0; behaviour = BEHAVIOUR_TRACK_TARGET; frustration = 0.0; } - (void) performIdle { behaviour = BEHAVIOUR_IDLE; frustration = 0.0; } - (void) performIntercept { behaviour = BEHAVIOUR_INTERCEPT_TARGET; frustration = 0.0; } - (void) performLandOnPlanet { OOPlanetEntity *nearest = [self findNearestPlanet]; if (isNearPlanetSurface) { _destination = [nearest position]; behaviour = BEHAVIOUR_LAND_ON_PLANET; planetForLanding = [nearest universalID]; } else { behaviour = BEHAVIOUR_IDLE; [shipAI message:@"NO_PLANET_NEARBY"]; } frustration = 0.0; } - (void) performMining { Entity *target = [self primaryTarget]; // mining is not seen as hostile behaviour, so ensure it is only used against rocks. if (target && [target scanClass] == CLASS_ROCK) { behaviour = BEHAVIOUR_ATTACK_MINING_TARGET; frustration = 0.0; } else { [self noteLostTargetAndGoIdle]; } } - (void) performScriptedAI { behaviour = BEHAVIOUR_SCRIPTED_AI; frustration = 0.0; } - (void) performScriptedAttackAI { behaviour = BEHAVIOUR_SCRIPTED_ATTACK_AI; frustration = 0.0; } - (void) performBuoyTumble { stick_roll = 0.10; stick_pitch = 0.15; behaviour = BEHAVIOUR_TUMBLE; frustration = 0.0; } - (void) performStop { behaviour = BEHAVIOUR_STOP_STILL; desired_speed = 0.0; frustration = 0.0; } - (void) performTumble { stick_roll = max_flight_roll*2.0*(randf() - 0.5); stick_pitch = max_flight_pitch*2.0*(randf() - 0.5); behaviour = BEHAVIOUR_TUMBLE; frustration = 0.0; } - (BOOL) performHyperSpaceToSpecificSystem:(OOSystemID)systemID { return [self performHyperSpaceExitReplace:NO toSystem:systemID]; } - (void) requestDockingCoordinates { /*- requests coordinates from the target station if the target station can't be found then use the nearest it can find (which may be a rock hermit) -*/ StationEntity *station = nil; Entity *targStation = nil; NSString *message = nil; double distanceToStation2 = 0.0; targStation = [self targetStation]; if ([targStation isStation]) { station = (StationEntity*)targStation; } else { station = [UNIVERSE nearestShipMatchingPredicate:IsStationPredicate parameter:nil relativeToEntity:self]; } distanceToStation2 = HPdistance2([station position], [self position]); // Player check for being inside the aegis already exists in PlayerEntityControls. We just // check here that distance to station is less than 2.5 times scanner range to avoid problems with // NPC ships getting stuck with a dockingAI while just outside the aegis - Nikos 20090630, as proposed by Eric // On very busy systems (> 50 docking ships) docking ships can be sent to a hold position outside the range, // so also test for presence of dockingInstructions. - Eric 20091130 if (station != nil && (distanceToStation2 < SCANNER_MAX_RANGE2 * 6.25 || dockingInstructions != nil)) { // remember the instructions [dockingInstructions release]; dockingInstructions = [[station dockingInstructionsForShip:self] retain]; if (dockingInstructions != nil) { [self recallDockingInstructions]; message = [dockingInstructions objectForKey:@"ai_message"]; if (message != nil) [shipAI message:message]; message = [dockingInstructions objectForKey:@"comms_message"]; if (message != nil) [station sendExpandedMessage:message toShip:self]; } } else { DESTROY(dockingInstructions); } if (dockingInstructions == nil) { [shipAI message:@"NO_STATION_FOUND"]; } } - (void) recallDockingInstructions { if (dockingInstructions != nil) { _destination = [dockingInstructions oo_hpvectorForKey:@"destination"]; desired_speed = fmin([dockingInstructions oo_floatForKey:@"speed"], maxFlightSpeed); desired_range = [dockingInstructions oo_floatForKey:@"range"]; if ([dockingInstructions objectForKey:@"station"]) { StationEntity *targetStation = [[dockingInstructions objectForKey:@"station"] weakRefUnderlyingObject]; if (targetStation != nil) { [self addTarget:targetStation]; [self setTargetStation:targetStation]; } else { [self removeTarget:[self primaryTarget]]; } } docking_match_rotation = [dockingInstructions oo_boolForKey:@"match_rotation"]; } } - (void) scanForNearestIncomingMissile { BinaryOperationPredicateParameter param = { HasScanClassPredicate, [NSNumber numberWithInt:CLASS_MISSILE], IsHostileAgainstTargetPredicate, self }; [self scanForNearestShipWithPredicate:ANDPredicate parameter:¶m]; } - (void) enterPlayerWormhole { [self enterWormhole:[PLAYER wormhole] replacing:NO]; } - (void) enterTargetWormhole { WormholeEntity *whole = nil; ShipEntity *targEnt = [self primaryTarget]; double found_d2 = scannerRange * scannerRange; if (targEnt && (HPdistance2(position, [targEnt position]) < found_d2)) { if ([targEnt isWormhole]) whole = (WormholeEntity *)targEnt; else if ([targEnt isPlayer]) whole = [PLAYER wormhole]; } if (!whole) { // locate nearest wormhole int ent_count = UNIVERSE->n_entities; Entity** uni_entities = UNIVERSE->sortedEntities; // grab the public sorted list WormholeEntity* wormholes[ent_count]; int i; int wh_count = 0; for (i = 0; i < ent_count; i++) if (uni_entities[i]->isWormhole) wormholes[wh_count++] = [(WormholeEntity *)uni_entities[i] retain]; // //double found_d2 = scannerRange * scannerRange; for (i = 0; i < wh_count ; i++) { WormholeEntity *wh = wormholes[i]; double d2 = HPdistance2(position, wh->position); if (d2 < found_d2) { whole = wh; found_d2 = d2; } [wh release]; } } [self enterWormhole:whole replacing:NO]; } // FIXME: resolve this stuff. - (void) wormholeEscorts { NSEnumerator *shipEnum = nil; ShipEntity *ship = nil; NSString *context = nil; WormholeEntity *whole = nil; whole = [self primaryTarget]; if (![whole isWormhole]) return; #ifndef NDEBUG context = [NSString stringWithFormat:@"%@ wormholeEscorts", [self shortDescription]]; #endif for (shipEnum = [self escortEnumerator]; (ship = [shipEnum nextObject]); ) { [ship addTarget:whole]; [ship reactToAIMessage:@"ENTER WORMHOLE" context:context]; [ship doScriptEvent:OOJSID("wormholeSuggested") withArgument:whole]; } // We now have no escorts.. [_escortGroup release]; _escortGroup = nil; } - (void) wormholeEntireGroup { [self wormholeGroup]; [self wormholeEscorts]; } - (BOOL) suggestEscortTo:(ShipEntity *)mother { if (mother) { #ifndef NDEBUG if (reportAIMessages) { OOLog(@"ai.suggestEscort", @"DEBUG: %@ suggests escorting %@", self, mother); } #endif if ([mother acceptAsEscort:self]) { // copy legal status across if (([mother legalStatus] > 0)&&(bounty <= 0)) { int extra = 1 | (ranrot_rand() & 15); // [mother setBounty: [mother legalStatus] + extra withReason:kOOLegalStatusReasonAssistingOffender]; [self markAsOffender:extra withReason:kOOLegalStatusReasonAssistingOffender]; // bounty += extra; // obviously we're dodgier than we thought! } [self setOwner:mother]; [self setGroup:[mother escortGroup]]; [shipAI message:@"ESCORTING"]; return YES; } #ifndef NDEBUG if (reportAIMessages) { OOLog(@"ai.suggestEscort.refused", @"DEBUG: %@ refused by %@", self, mother); } #endif } [self setOwner:self]; [shipAI message:@"NOT_ESCORTING"]; [self doScriptEvent:OOJSID("escortRejected") withArgument:mother]; return NO; } - (void) broadcastDistressMessage { /*-- Locates all the stations, bounty hunters and police ships in range and tells them that you are under attack --*/ [self broadcastDistressMessageWithDumping:YES]; } - (void) broadcastDistressMessageWithDumping:(BOOL)dumpCargo { [self checkScannerIgnoringUnpowered]; DESTROY(_foundTarget); ShipEntity *aggressor_ship = (ShipEntity*)[self primaryAggressor]; if (aggressor_ship == nil) return; // don't send too many distress messages at once, space them out semi-randomly if (messageTime > 2.0 * randf()) return; NSString *distress_message = nil; BOOL is_buoy = (scanClass == CLASS_BUOY); if (is_buoy) distress_message = @"[buoy-distress-call]"; else distress_message = @"[distress-call]"; unsigned i; for (i = 0; i < n_scanned_ships; i++) { ShipEntity* ship = scanned_ships[i]; // dump cargo if energy is low if (dumpCargo && !is_buoy && [self primaryAggressor] == ship && energy < 0.375 * maxEnergy) { [self ejectCargo]; [self performFlee]; } // tell it! (only plist AIs send comms here; JS AIs are // expected to handle their own) if (ship->isPlayer && ![self hasNewAI]) { [ship doScriptEvent:OOJSID("distressMessageReceived") withArgument:aggressor_ship andArgument:self]; if (!is_buoy && [self primaryAggressor] == ship && energy < 0.375 * maxEnergy) { [self sendExpandedMessage:@"[beg-for-mercy]" toShip:ship]; } else if ([self bounty] == 0) { // only send distress message to player if plausibly sending // one more generally [self sendExpandedMessage:distress_message toShip:ship]; } // reset the thanked_ship_id DESTROY(_thankedShip); } else if ([self bounty] == 0 && [ship crew]) // Only clean ships can have their distress calls accepted { [ship doScriptEvent:OOJSID("distressMessageReceived") withArgument:aggressor_ship andArgument:self]; // we only can send distressMessages to ships that are known to have a "ACCEPT_DISTRESS_CALL" reaction // in their AI, or they might react wrong on the added found_target. // ship must have a plist AI for this next bit. JS AIs // should already have done something sensible on // distressMessageReceived if (![self hasNewAI]) { // FIXME: this test only works with core AIs if (ship->isStation || [ship hasPrimaryRole:@"police"] || [ship hasPrimaryRole:@"hunter"]) { [ship acceptDistressMessageFrom:self]; } } } } } @end @implementation ShipEntity (PureAI) - (void) setStateTo:(NSString *)state { [[self getAI] setState:state]; } - (void) pauseAI:(NSString *)intervalString { [shipAI setNextThinkTime:[UNIVERSE getTime] + [intervalString doubleValue]]; } - (void) randomPauseAI:(NSString *)intervalString { NSArray* tokens = ScanTokensFromString(intervalString); double start, end; if ([tokens count] != 2) { OOLog(@"ai.syntax.randomPauseAI", @"***** ERROR: cannot read min and max value for randomPauseAI:, needs 2 values: '%@'.", intervalString); return; } start = [tokens oo_doubleAtIndex:0]; end = [tokens oo_doubleAtIndex:1]; [shipAI setNextThinkTime:[UNIVERSE getTime] + (start + (end - start)*randf())]; } - (void) dropMessages:(NSString *)messageString { NSArray *messages = nil; NSEnumerator *messageEnum = nil; NSString *message = nil; NSCharacterSet *whiteSpace = [NSCharacterSet whitespaceCharacterSet]; messages = [messageString componentsSeparatedByString:@","]; for (messageEnum = [messages objectEnumerator]; (message = [messageEnum nextObject]); ) { [shipAI dropMessage:[message stringByTrimmingCharactersInSet:whiteSpace]]; } } - (void) debugDumpPendingMessages { [shipAI debugDumpPendingMessages]; } - (void) setDestinationToCurrentLocation { // randomly add a .5m variance _destination = HPvector_add(position, OOHPVectorRandomSpatial(0.5)); } - (void) setDestinationToJinkPosition { Vector front = vector_multiply_scalar([self forwardVector], flightSpeed / max_flight_pitch * 2); _destination = HPvector_add(position, vectorToHPVector(vector_add(front, OOVectorRandomSpatial(100)))); pitching_over = YES; // don't complete roll first, but immediately start with pitching. } - (void) setDesiredRangeTo:(NSString *)rangeString { desired_range = [rangeString doubleValue]; } - (void) setDesiredRangeForWaypoint { desired_range = fmax(maxFlightSpeed / max_flight_pitch / 6, 50.0); // some ships need a longer range to reach a waypoint. } - (void) setSpeedTo:(NSString *)speedString { desired_speed = [speedString doubleValue]; } - (void) setSpeedFactorTo:(NSString *)speedString { desired_speed = maxFlightSpeed * [speedString doubleValue]; } - (void) setSpeedToCruiseSpeed { desired_speed = cruiseSpeed; } - (void) setThrustFactorTo:(NSString *)thrustFactorString { thrust = OOClamp_0_1_f([thrustFactorString doubleValue]) * max_thrust; } - (void) setTargetToPrimaryAggressor { Entity *primeAggressor = [self primaryAggressor]; if (!primeAggressor) return; if ([self primaryTarget] == primeAggressor) return; // a more considered approach here: // if we're already busy attacking a target we don't necessarily want to break off // if ([self hasHostileTarget] && randf() < 0.75) // if I'm attacking, ignore 75% of new aggressor's attacks { // but add them as a secondary target anyway [self addDefenseTarget:(ShipEntity*)primeAggressor]; return; } // react only if the primary aggressor is not a friendly ship, else ignore it if ([primeAggressor isShip] && ![(ShipEntity *)primeAggressor isFriendlyTo:self]) { // inform our old target of our new target // Entity *primeTarget = [self primaryTarget]; if ((primeTarget)&&(primeTarget->isShip)) { ShipEntity *currentShip = [self primaryTarget]; [[currentShip getAI] message:[NSString stringWithFormat:@"%@ %d %d", AIMS_AGGRESSOR_SWITCHED_TARGET, universalID, [[self primaryAggressor] universalID]]]; [currentShip doScriptEvent:OOJSID("shipAttackerDistracted") withArgument:[self primaryAggressor]]; } // okay, so let's now target the aggressor [self addTarget:[self primaryAggressor]]; } } - (void) addPrimaryAggressorAsDefenseTarget { Entity *primeAggressor = [self primaryAggressor]; if (!primeAggressor) return; if ([self isDefenseTarget:primeAggressor]) return; if ([primeAggressor isShip] && ![(ShipEntity*)primeAggressor isFriendlyTo:self]) { [self addDefenseTarget:primeAggressor]; } } - (void) scanForNearestMerchantman { float d2, found_d2; unsigned i; ShipEntity *ship = nil; //-- Locates the nearest merchantman in range. [self checkScannerIgnoringUnpowered]; found_d2 = scannerRange * scannerRange; DESTROY(_foundTarget); for (i = 0; i < n_scanned_ships ; i++) { ship = scanned_ships[i]; if ([ship isPirateVictim] && ([ship status] != STATUS_DEAD) && ([ship status] != STATUS_DOCKED) && ![ship isCloaked]) { d2 = distance2_scanned_ships[i]; if (PIRATES_PREFER_PLAYER && (d2 < desired_range * desired_range) && ship->isPlayer && [self isPirate]) { d2 = 0.0; } else d2 = distance2_scanned_ships[i]; if (d2 < found_d2) { found_d2 = d2; [self setFoundTarget:ship]; } } } [self checkFoundTarget]; } - (void) scanForRandomMerchantman { unsigned n_found, i; //-- Locates one of the merchantman in range. [self checkScannerIgnoringUnpowered]; ShipEntity* ids_found[n_scanned_ships]; n_found = 0; DESTROY(_foundTarget); for (i = 0; i < n_scanned_ships ; i++) { ShipEntity *ship = scanned_ships[i]; if (([ship status] != STATUS_DEAD) && ([ship status] != STATUS_DOCKED) && [ship isPirateVictim] && ![ship isCloaked]) ids_found[n_found++] = ship; } if (n_found == 0) { [shipAI message:@"NOTHING_FOUND"]; } else { i = ranrot_rand() % n_found; // pick a number from 0 -> (n_found - 1) [self setFoundTarget:ids_found[i]]; [shipAI message:@"TARGET_FOUND"]; } } - (void) scanForLoot { /*-- Locates the nearest debris in range --*/ if (!isStation) { if (![self hasCargoScoop]) { [shipAI message:@"NOTHING_FOUND"]; //can't collect loot if you have no scoop! return; } if ([cargo count] >= [self maxAvailableCargoSpace]) { if (max_cargo) [shipAI message:@"HOLD_FULL"]; //can't collect loot if holds are full! [shipAI message:@"NOTHING_FOUND"]; //can't collect loot if holds are full! return; } } else { if (magnitude2([self velocity])) { [shipAI message:@"NOTHING_FOUND"]; //can't collect loot if you're a moving station return; } } [self checkScanner]; double found_d2 = scannerRange * scannerRange; DESTROY(_foundTarget); unsigned i; for (i = 0; i < n_scanned_ships; i++) { ShipEntity *other = (ShipEntity *)scanned_ships[i]; if ([other scanClass] == CLASS_CARGO && [other cargoType] != CARGO_NOT_CARGO && [other status] != STATUS_BEING_SCOOPED) { if ((![self isPolice]) || ([[other commodityType] isEqualToString:@"slaves"])) // police only rescue lifepods and slaves { GLfloat d2 = distance2_scanned_ships[i]; if (d2 < found_d2) { found_d2 = d2; [self setFoundTarget:other]; } } } } [self checkFoundTarget]; } - (void) scanForRandomLoot { /*-- Locates the all debris in range and chooses a piece at random from the first sixteen found --*/ if (![self isStation] && ![self hasCargoScoop]) { [shipAI message:@"NOTHING_FOUND"]; //can't collect loot if you have no scoop! return; } // [self checkScanner]; // ShipEntity* thing_uids_found[16]; unsigned things_found = 0; DESTROY(_foundTarget); unsigned i; for (i = 0; (i < n_scanned_ships)&&(things_found < 16) ; i++) { ShipEntity *other = scanned_ships[i]; if ([other scanClass] == CLASS_CARGO && [other cargoType] != CARGO_NOT_CARGO && [other status] != STATUS_BEING_SCOOPED) { thing_uids_found[things_found++] = other; } } if (things_found != 0) { [self setFoundTarget:thing_uids_found[ranrot_rand() % things_found]]; [shipAI message:@"TARGET_FOUND"]; } else [shipAI message:@"NOTHING_FOUND"]; } - (void) setTargetToFoundTarget { if ([self foundTarget] != nil) { [self addTarget:[self foundTarget]]; } else { [shipAI message:@"TARGET_LOST"]; // to prevent the ship going for a wrong, previous target. Should not be a reactToMessage. } } - (void) addFoundTargetAsDefenseTarget { Entity* fTarget = [self foundTarget]; if (fTarget != nil) { if ([fTarget isShip] && ![(ShipEntity *)fTarget isFriendlyTo:self]) { [self addDefenseTarget:fTarget]; } } } - (void) checkForFullHold { if (!max_cargo) { [shipAI message:@"NO_CARGO_BAY"]; } else if ([cargo count] >= [self maxAvailableCargoSpace]) { [shipAI message:@"HOLD_FULL"]; } else { [shipAI message:@"HOLD_NOT_FULL"]; } } - (void) getWitchspaceEntryCoordinates { /*- calculates coordinates from the nearest station it can find, or just fly 10s forward -*/ if (!UNIVERSE) { Vector vr = vector_multiply_scalar(v_forward, maxFlightSpeed * 10.0); // 10 second flying away coordinates = HPvector_add(position, vectorToHPVector(vr)); return; } // // find the nearest station... // // we don't use "checkScanner" because we must rely on finding a present station. // StationEntity *station = nil; station = [UNIVERSE nearestShipMatchingPredicate:IsStationPredicate parameter:nil relativeToEntity:self]; if (station && HPdistance2([station position], position) < SCANNER_MAX_RANGE2) // there is a station in range. { Vector vr = vector_multiply_scalar([station rightVector], 10000); // 10km from station coordinates = HPvector_add([station position], vectorToHPVector(vr)); } else { Vector vr = vector_multiply_scalar(v_forward, maxFlightSpeed * 10.0); // 10 second flying away coordinates = HPvector_add(position, vectorToHPVector(vr)); } } - (void) setDestinationFromCoordinates { _destination = coordinates; } - (void) setCoordinatesFromPosition { coordinates = position; } - (void) fightOrFleeMissile { // find an incoming missile... // ShipEntity *missile = nil; unsigned i; NSEnumerator *escortEnum = nil; ShipEntity *escort = nil; ShipEntity *target = nil; [self checkScannerIgnoringUnpowered]; for (i = 0; (i < n_scanned_ships)&&(missile == nil); i++) { ShipEntity *thing = scanned_ships[i]; if (thing->scanClass == CLASS_MISSILE) { target = [thing primaryTarget]; if (target == self) { missile = thing; } else { for (escortEnum = [self escortEnumerator]; (escort = [escortEnum nextObject]); ) { if (target == escort) { missile = thing; } } } } } if (missile == nil) return; [self addTarget:missile]; [self addDefenseTarget:missile]; // Notify own ship script that we are being attacked. ShipEntity *hunter = [missile owner]; [self doScriptEvent:OOJSID("shipBeingAttacked") withArgument:hunter]; [hunter doScriptEvent:OOJSID("shipAttackedOther") withArgument:self]; if ([self isPolice]) { // Notify other police in group of attacker. // Note: prior to 1.73 this was done only if we had ECM. NSEnumerator *policeEnum = nil; ShipEntity *police = nil; for (policeEnum = [[self group] mutationSafeEnumerator]; (police = [policeEnum nextObject]); ) { [police setFoundTarget:hunter]; [police setPrimaryAggressor:hunter]; } } // if I'm a copper and you're not, then mark the other as an offender! if ([self isPolice] && ![hunter isPolice]) [hunter markAsOffender:64 withReason:kOOLegalStatusReasonAttackedPolice]; if ([self hasECM]) { // use the ECM and battle on [self setPrimaryAggressor:hunter]; // lets get them now for that! [self setFoundTarget:hunter]; [self fireECM]; return; } // RUN AWAY !! desired_range = 10000; [self performFlee]; [shipAI message:@"FLEEING"]; } - (void) setCourseToPlanet { /*- selects the nearest planet it can find -*/ OOPlanetEntity *the_planet = [self findNearestPlanetExcludingMoons]; if (the_planet) { double variation = (aegis_status == AEGIS_NONE ? 0.5 : 0.2); // more random deviation when far from planet. HPVector p_pos = the_planet->position; double p_cr = the_planet->collision_radius; // the surface HPVector p1 = HPvector_between(p_pos, position); p1 = HPvector_normal(p1); // vector towards ship p1.x += variation * (randf() - variation); p1.y += variation * (randf() - variation); p1.z += variation * (randf() - variation); p1 = HPvector_normal(p1); _destination = HPvector_add(p_pos, HPvector_multiply_scalar(p1, p_cr)); // on surface desired_range = collision_radius + 100.0; // +100m from the destination } else { [shipAI message:@"NO_PLANET_FOUND"]; } } - (void) setTakeOffFromPlanet { /*- selects the nearest planet it can find -*/ OOPlanetEntity *the_planet = [self findNearestPlanet]; if (the_planet) { _destination = HPvector_add([the_planet position], HPvector_multiply_scalar( HPvector_normal(HPvector_subtract([the_planet position],position)),-10000.0-the_planet->collision_radius));// 10km straight up desired_range = 50.0; } else { OOLog(@"ai.setTakeOffFromPlanet.noPlanet", @"***** Error. Planet not found during take off!"); } } - (void) landOnPlanet { // Selects the nearest planet it can find. [self landOnPlanet:[self findNearestPlanet]]; } - (void) checkTargetLegalStatus { ShipEntity *other_ship = [self primaryTarget]; if (!other_ship) { [shipAI message:@"NO_TARGET"]; return; } else { int ls = [other_ship legalStatus]; if (ls > 50) { [shipAI message:@"TARGET_FUGITIVE"]; return; } if (ls > 20) { [shipAI message:@"TARGET_OFFENDER"]; return; } if (ls > 0) { [shipAI message:@"TARGET_MINOR_OFFENDER"]; return; } [shipAI message:@"TARGET_CLEAN"]; } } - (void) checkOwnLegalStatus { if (scanClass == CLASS_THARGOID) { [shipAI message:@"SELF_THARGOID"]; return; } int ls = [self legalStatus]; if (ls > 50) { [shipAI message:@"SELF_FUGITIVE"]; return; } if (ls > 20) { [shipAI message:@"SELF_OFFENDER"]; return; } if (ls > 0) { [shipAI message:@"SELF_MINOR_OFFENDER"]; return; } [shipAI message:@"SELF_CLEAN"]; } - (void) exitAIWithMessage:(NSString *)message { if ([message length] == 0) message = @"RESTARTED"; [shipAI exitStateMachineWithMessage:message]; } - (void) setDestinationToTarget { Entity *the_target = [self primaryTarget]; if (the_target) _destination = the_target->position; } - (void) setDestinationWithinTarget { Entity *the_target = [self primaryTarget]; if (the_target) { HPVector pos = the_target->position; Quaternion q; quaternion_set_random(&q); Vector v = vector_forward_from_quaternion(q); GLfloat d = (randf() - randf()) * the_target->collision_radius; _destination = make_HPvector(pos.x + d * v.x, pos.y + d * v.y, pos.z + d * v.z); } } - (void) checkCourseToDestination { Entity *hazard = [UNIVERSE hazardOnRouteFromEntity: self toDistance: desired_range fromPoint: _destination]; if (hazard == nil || ([hazard isShip] && HPdistance(position, [hazard position]) > scannerRange) || ([hazard isPlanet] && aegis_status == AEGIS_NONE)) [shipAI message:@"COURSE_OK"]; // Avoid going into a waypoint.plist for far away objects, it cripples the main AI a bit in its funtionality. else { if ([hazard isShip] && (weapon_damage * 24.0 > [hazard energy])) { [shipAI reactToMessage:@"HAZARD_CAN_BE_DESTROYED" context:@"checkCourseToDestination"]; } _destination = [UNIVERSE getSafeVectorFromEntity:self toDistance:desired_range fromPoint:_destination]; [shipAI message:@"WAYPOINT_SET"]; } } - (void) checkAegis { switch (aegis_status) { case AEGIS_CLOSE_TO_MAIN_PLANET: [shipAI message:@"AEGIS_CLOSE_TO_MAIN_PLANET"]; // It's been a few years since 1.71 - it should be safe enough to comment out the line below for 1.77/1.78 -- Kaks 20120917 //[shipAI message:@"AEGIS_CLOSE_TO_PLANET"]; // fires only for main planets, kept for compatibility with pre-1.72 AI plists. return; case AEGIS_CLOSE_TO_ANY_PLANET: { Entity *nearest = [self findNearestStellarBody]; if([nearest isSun]) { [shipAI message:@"CLOSE_TO_SUN"]; } else { [shipAI message:@"CLOSE_TO_PLANET"]; if ([nearest planetType] == STELLAR_TYPE_MOON) { [shipAI message:@"CLOSE_TO_MOON"]; } else { [shipAI message:@"CLOSE_TO_SECONDARY_PLANET"]; } } return; } case AEGIS_IN_DOCKING_RANGE: [shipAI message:@"AEGIS_IN_DOCKING_RANGE"]; return; case AEGIS_NONE: [shipAI message:@"AEGIS_NONE"]; return; } NSLog(@"Aegis status for %@ has taken on invalid value %i. This is an internal error, please report it.", self, aegis_status); aegis_status = AEGIS_NONE; [shipAI message:@"AEGIS_NONE"]; } - (void) checkEnergy { if (energy == maxEnergy) { [shipAI message:@"ENERGY_FULL"]; return; } if (energy >= maxEnergy * 0.75) { [shipAI message:@"ENERGY_HIGH"]; return; } if (energy <= maxEnergy * 0.25) { [shipAI message:@"ENERGY_LOW"]; return; } [shipAI message:@"ENERGY_MEDIUM"]; } - (void) checkHeatInsulation { float minInsulation = 1000 / [self maxFlightSpeed] + 1; if ([self heatInsulation] < minInsulation) { [shipAI message:@"INSULATION_POOR"]; return; } [shipAI message:@"INSULATION_OK"]; } - (void) findNewDefenseTarget { [self checkScanner]; unsigned i; for (i = 0; i < n_scanned_ships ; i++) { ShipEntity *ship = scanned_ships[i]; if (![ship isCloaked] && (([ship primaryTarget] == self && [ship hasHostileTarget]) || [ship isMine] || ([ship isThargoid] != [self isThargoid]))) { if (![self isDefenseTarget:ship]) { [self addDefenseTarget:ship]; return; } } } } - (void) scanForOffenders { /*-- Locates all the ships in range and compares their legal status or bounty against ranrot_rand() & 255 - chooses the worst offender --*/ NSDictionary *systeminfo = [UNIVERSE currentSystemData]; float gov_factor = 0.4 * [(NSNumber *)[systeminfo objectForKey:KEY_GOVERNMENT] intValue]; // 0 .. 7 (0 anarchic .. 7 most stable) --> [0.0, 0.4, 0.8, 1.2, 1.6, 2.0, 2.4, 2.8] // if ([UNIVERSE sun] == nil) gov_factor = 1.0; // DESTROY(_foundTarget); // find the worst offender on the scanner // [self checkScanner]; unsigned i; float worst_legal_factor = 0; GLfloat found_d2 = scannerRange * scannerRange; OOShipGroup *group = [self group]; for (i = 0; i < n_scanned_ships ; i++) { ShipEntity *ship = scanned_ships[i]; if ((ship->scanClass != CLASS_CARGO)&&([ship status] != STATUS_DEAD)&&([ship status] != STATUS_DOCKED)&& ![ship isCloaked]) { GLfloat d2 = distance2_scanned_ships[i]; float legal_factor = [ship legalStatus] * gov_factor; int random_factor = ranrot_rand() & 255; // 25% chance of spotting a fugitive in 15s if ((d2 < found_d2)&&(random_factor < legal_factor)&&(legal_factor > worst_legal_factor)) { if (group == nil || group != [ship group]) // fellows with bounty can't be offenders { [self setFoundTarget:ship]; worst_legal_factor = legal_factor; } } } } [self checkFoundTarget]; } - (void) setCourseToWitchpoint { if (UNIVERSE) { _destination = [UNIVERSE getWitchspaceExitPosition]; desired_range = 10000.0; // 10km away } } - (void) setDestinationToWitchpoint { _destination = [UNIVERSE getWitchspaceExitPosition]; } - (void) setDestinationToStationBeacon { if ([UNIVERSE station]) { _destination = [[UNIVERSE station] beaconPosition]; } } - (void) performHyperSpaceExit { [self performHyperSpaceExitReplace:YES]; } - (void) performHyperSpaceExitWithoutReplacing { [self performHyperSpaceExitReplace:NO]; } - (void) disengageAutopilot { OOLogERR(@"ai.invalid.notPlayer", @"Error in %@:%@, AI method endAutoPilot is only applicable to the player.", [shipAI name], [shipAI state]); } - (void) wormholeGroup { NSEnumerator *shipEnum = nil; ShipEntity *ship = nil; WormholeEntity *whole = nil; whole = [self primaryTarget]; if (![whole isWormhole]) return; for (shipEnum = [[self group] mutationSafeEnumerator]; (ship = [shipEnum nextObject]); ) { [ship addTarget:whole]; [ship reactToAIMessage:@"ENTER WORMHOLE" context:@"wormholeGroup"]; [ship doScriptEvent:OOJSID("wormholeSuggested") withArgument:whole]; } } - (void) commsMessage:(NSString *)valueString { [self commsMessage:valueString withUnpilotedOverride:NO]; } - (void) commsMessageByUnpiloted:(NSString *)valueString { [self commsMessage:valueString withUnpilotedOverride:YES]; } - (void) ejectCargo { OOCargoQuantity i, cargo_to_go = 0.1 * [self maxAvailableCargoSpace]; while (cargo_to_go > 15) { cargo_to_go = ranrot_rand() % cargo_to_go; } [self dumpCargo]; for (i = 1; i < cargo_to_go; i++) { [self performSelector:@selector(dumpCargo) withObject:nil afterDelay:0.75 * i]; // drop 3 canisters per 2 seconds } } - (void) scanForThargoid { return [self scanForNearestShipWithPrimaryRole:@"thargoid"]; } - (void) scanForNonThargoid { /*-- Locates all the non thargoid ships in range and chooses the nearest --*/ DESTROY(_foundTarget); [self checkScanner]; unsigned i; GLfloat found_d2 = scannerRange * scannerRange; for (i = 0; i < n_scanned_ships ; i++) { ShipEntity *thing = scanned_ships[i]; GLfloat d2 = distance2_scanned_ships[i]; if (([thing scanClass] != CLASS_CARGO) && ([thing status] != STATUS_DOCKED) && ![thing isThargoid] && ![thing isCloaked] && (d2 < found_d2)) { [self setFoundTarget:thing]; if ([thing isPlayer]) d2 = 0.0; // prefer the player found_d2 = d2; } } [self checkFoundTarget]; } - (void) thargonCheckMother { ShipEntity *mother = [self owner]; if (mother == nil && [self group]) mother = [[self group] leader]; double maxRange2 = scannerRange * scannerRange; if (mother && mother != self && HPdistance2(mother->position, position) < maxRange2) { [shipAI message:@"TARGET_FOUND"]; // no need for scanning, we still have our mother. } else { // we lost the old mother, search for a new one [self scanForNearestShipHavingRole:@"thargoid-mothership"]; // the scan will send further AI messages. if ([self foundTarget] != nil) { mother = (ShipEntity*)[self foundTarget]; [self setOwner:mother]; if ([mother group] != [mother escortGroup]) // avoid adding thargon to an escort group. { [self setGroup:[mother group]]; } }; } } - (void) becomeUncontrolledThargon { int ent_count = UNIVERSE->n_entities; Entity** uni_entities = UNIVERSE->sortedEntities; // grab the public sorted list int i; for (i = 0; i < ent_count; i++) if (uni_entities[i]->isShip) { ShipEntity *other = (ShipEntity*)uni_entities[i]; if ([other primaryTarget] == self) { [other removeTarget:self]; } if ([other isDefenseTarget:self]) { [other removeDefenseTarget:self]; } } // now we're just a bunch of alien artefacts! scanClass = CLASS_CARGO; reportAIMessages = NO; [self setAITo:@"dumbAI.plist"]; DESTROY(_primaryTarget); [self setSpeed: 0.0]; [self setGroup:nil]; } - (void) checkDistanceTravelled { if (distanceTravelled > desired_range) [shipAI message:@"GONE_BEYOND_RANGE"]; } - (void) fightOrFleeHostiles { [self addDefenseTarget:[self foundTarget]]; if ([self hasEscorts]) { Entity *leTarget = [self lastEscortTarget]; if (leTarget != nil) { [self setFoundTarget:leTarget]; [shipAI message:@"FLEEING"]; return; } [self setPrimaryAggressor:[self foundTarget]]; [self addTarget:[self foundTarget]]; [self deployEscorts]; [shipAI message:@"DEPLOYING_ESCORTS"]; [shipAI message:@"FLEEING"]; return; } // consider launching a missile if (missiles > 2) // keep a reserve { if (randf() < 0.50) { [self setPrimaryAggressor:[self foundTarget]]; [self addTarget:[self foundTarget]]; [self fireMissile]; [shipAI message:@"FLEEING"]; return; } } // consider fighting if (energy > maxEnergy * 0.80) { [self setPrimaryAggressor:[self foundTarget]]; //[self performAttack]; [shipAI message:@"FIGHTING"]; return; } [shipAI message:@"FLEEING"]; } - (void) suggestEscort { ShipEntity *mother = [self primaryTarget]; [self suggestEscortTo:mother]; } - (void) escortCheckMother { ShipEntity *mother = [self owner]; if ([mother acceptAsEscort:self]) { [self setOwner:mother]; [self setGroup:[mother escortGroup]]; [shipAI message:@"ESCORTING"]; } else { [self setOwner:self]; if ([self group] == [mother escortGroup]) [self setGroup:nil]; [shipAI message:@"NOT_ESCORTING"]; } } - (void) checkGroupOddsVersusTarget { NSUInteger ownGroupCount = [[self group] count] + (ranrot_rand() & 3); // add a random fudge factor NSUInteger targetGroupCount = [[[self primaryTarget] group] count] + (ranrot_rand() & 3); // add a random fudge factor if (ownGroupCount == targetGroupCount) { [shipAI message:@"ODDS_LEVEL"]; } else if (ownGroupCount > targetGroupCount) { [shipAI message:@"ODDS_GOOD"]; } else { [shipAI message:@"ODDS_BAD"]; } } - (void) scanForFormationLeader { //-- Locates the nearest suitable formation leader in range --// DESTROY(_foundTarget); [self checkScannerIgnoringUnpowered]; unsigned i; GLfloat found_d2 = scannerRange * scannerRange; for (i = 0; i < n_scanned_ships; i++) { ShipEntity *ship = scanned_ships[i]; if ((ship != self) && (!ship->isPlayer) && (ship->scanClass == scanClass) && [ship primaryTarget] != self && ![ship isCloaked]) // look for alike { GLfloat d2 = distance2_scanned_ships[i]; if ((d2 < found_d2) && [ship canAcceptEscort:self]) { found_d2 = d2; [self setFoundTarget:ship]; } } } if ([self foundTarget] != nil) [shipAI message:@"TARGET_FOUND"]; else { [shipAI message:@"NOTHING_FOUND"]; if ([self hasPrimaryRole:@"wingman"]) { // become free-lance police :) [self setAITo:@"route1patrolAI.plist"]; // use this to avoid referencing a released AI [self setPrimaryRole:@"police"]; // other wingman can now select this ship as leader. } } } - (void) messageMother:(NSString *)msgString { ShipEntity *mother = [self owner]; if (mother != nil && mother != self) { NSString *context = nil; #ifndef NDEBUG context = [NSString stringWithFormat:@"%@ messageMother", [self shortDescription]]; #endif [mother reactToAIMessage:msgString context:context]; } } - (void) messageSelf:(NSString *)msgString { [self sendAIMessage:msgString]; } - (void) setPlanetPatrolCoordinates { // check we've arrived near the last given coordinates HPVector r_pos = HPvector_subtract(position, coordinates); if (HPmagnitude2(r_pos) < 1000000 || patrol_counter == 0) { Entity *the_sun = [UNIVERSE sun]; ShipEntity *the_station = [[self group] leader]; if(!the_station || ![the_station isStation]) the_station = [UNIVERSE station]; if ((!the_sun)||(!the_station)) return; HPVector sun_pos = the_sun->position; HPVector stn_pos = the_station->position; HPVector sun_dir = HPvector_subtract(sun_pos,stn_pos); Vector vSun = make_vector(0, 0, 1); if (sun_dir.x||sun_dir.y||sun_dir.z) vSun = HPVectorToVector(HPvector_normal(sun_dir)); Vector v0 = [the_station forwardVector]; Vector v1 = cross_product(v0, vSun); Vector v2 = cross_product(v0, v1); switch (patrol_counter) { case 0: // first go to 5km ahead of the station coordinates = make_HPvector(stn_pos.x + 5000 * v0.x, stn_pos.y + 5000 * v0.y, stn_pos.z + 5000 * v0.z); desired_range = 250.0; break; case 1: // go to 25km N of the station coordinates = make_HPvector(stn_pos.x + 25000 * v1.x, stn_pos.y + 25000 * v1.y, stn_pos.z + 25000 * v1.z); desired_range = 250.0; break; case 2: // go to 25km E of the station coordinates = make_HPvector(stn_pos.x + 25000 * v2.x, stn_pos.y + 25000 * v2.y, stn_pos.z + 25000 * v2.z); desired_range = 250.0; break; case 3: // go to 25km S of the station coordinates = make_HPvector(stn_pos.x - 25000 * v1.x, stn_pos.y - 25000 * v1.y, stn_pos.z - 25000 * v1.z); desired_range = 250.0; break; case 4: // go to 25km W of the station coordinates = make_HPvector(stn_pos.x - 25000 * v2.x, stn_pos.y - 25000 * v2.y, stn_pos.z - 25000 * v2.z); desired_range = 250.0; break; default: // We should never come here coordinates = make_HPvector(stn_pos.x + 5000 * v0.x, stn_pos.y + 5000 * v0.y, stn_pos.z + 5000 * v0.z); desired_range = 250.0; break; } patrol_counter++; if (patrol_counter > 4) { if (randf() < .25) { // consider docking [self setTargetStation:the_station]; [self setAITo:@"dockingAI.plist"]; return; } else { // go around again patrol_counter = 1; } } } [shipAI message:@"APPROACH_COORDINATES"]; } - (void) setSunSkimStartCoordinates { if ([UNIVERSE sun] == nil) { [shipAI message:@"NO_SUN_FOUND"]; return; } HPVector v0 = [UNIVERSE getSunSkimStartPositionForShip:self]; if (!HPvector_equal(v0, kZeroHPVector)) { coordinates = v0; [shipAI message:@"APPROACH_COORDINATES"]; } else { [shipAI message:@"WAIT_FOR_SUN"]; } } - (void) setSunSkimEndCoordinates { if ([UNIVERSE sun] == nil) { [shipAI message:@"NO_SUN_FOUND"]; return; } coordinates = [UNIVERSE getSunSkimEndPositionForShip:self]; [shipAI message:@"APPROACH_COORDINATES"]; } - (void) setSunSkimExitCoordinates { Entity *the_sun = [UNIVERSE sun]; if (the_sun == nil) return; HPVector v1 = [UNIVERSE getSunSkimEndPositionForShip:self]; HPVector vs = the_sun->position; HPVector vout = HPvector_subtract(v1,vs); if (vout.x||vout.y||vout.z) vout = HPvector_normal(vout); else vout.z = 1.0; v1.x += 10000 * vout.x; v1.y += 10000 * vout.y; v1.z += 10000 * vout.z; coordinates = v1; [shipAI message:@"APPROACH_COORDINATES"]; } - (void) patrolReportIn { // Set a report time in the patrolled station to delay a new launch. ShipEntity *the_station = [[self group] leader]; if(!the_station || ![the_station isStation]) the_station = [UNIVERSE station]; [(StationEntity*)the_station acceptPatrolReportFrom:self]; } - (void) checkForMotherStation { ShipEntity *motherStation = [[self group] leader]; if ((!motherStation) || (!(motherStation->isStation))) { [shipAI message:@"NOTHING_FOUND"]; return; } double found_d2 = scannerRange * scannerRange; HPVector v0 = motherStation->position; if (HPdistance2(v0,position) > found_d2) { [shipAI message:@"NOTHING_FOUND"]; return; } [shipAI message:@"STATION_FOUND"]; } - (void) sendTargetCommsMessage:(NSString*) message { ShipEntity *ship = [self primaryTarget]; if ((ship == nil) || ([ship status] == STATUS_DEAD) || ([ship status] == STATUS_DOCKED)) { [self noteLostTarget]; return; } [self sendExpandedMessage:message toShip:[self primaryTarget]]; } - (void) markTargetForFines { ShipEntity *ship = [self primaryTarget]; if ((ship == nil) || ([ship status] == STATUS_DEAD) || ([ship status] == STATUS_DOCKED)) { [self noteLostTarget]; return; } if ([ship markForFines]) [shipAI message:@"TARGET_MARKED"]; } - (void) markTargetForOffence:(NSString *)valueString { if ((isStation)||(scanClass == CLASS_POLICE)) { ShipEntity *ship = [self primaryTarget]; if ((ship == nil) || ([ship status] == STATUS_DEAD) || ([ship status] == STATUS_DOCKED)) { [self noteLostTarget]; return; } NSString *finalValue = OOExpand(valueString); // expand values [ship markAsOffender:[finalValue intValue] withReason:kOOLegalStatusReasonSeenByPolice]; } } - (void) storeTarget { Entity *target = [self primaryTarget]; if (target) { [self setRememberedShip:target]; } else { DESTROY(_rememberedShip); } } - (void) recallStoredTarget { ShipEntity *oldTarget = (ShipEntity*)[self rememberedShip]; BOOL found = NO; if (oldTarget && ![oldTarget isCloaked]) { GLfloat range2 = HPdistance2([oldTarget position], position); if (range2 <= scannerRange * scannerRange && range2 <= SCANNER_MAX_RANGE2) { found = YES; } } if (found) { [self setFoundTarget:oldTarget]; [shipAI message:@"TARGET_FOUND"]; } else { if (oldTarget == nil) DESTROY(_rememberedShip); // ship no longer exists [shipAI message:@"NOTHING_FOUND"]; } } - (void) scanForRocks { /*-- Locates the all boulders and asteroids in range and selects nearest --*/ // find boulders then asteroids within range // DESTROY(_foundTarget); [self checkScanner]; unsigned i; GLfloat found_d2 = scannerRange * scannerRange; for (i = 0; i < n_scanned_ships; i++) { ShipEntity *thing = scanned_ships[i]; if ([thing isBoulder]) { GLfloat d2 = distance2_scanned_ships[i]; if (d2 < found_d2) { [self setFoundTarget:thing]; found_d2 = d2; } } } if ([self foundTarget] == nil) { for (i = 0; i < n_scanned_ships; i++) { ShipEntity *thing = scanned_ships[i]; if ([thing hasRole:@"asteroid"]) { GLfloat d2 = distance2_scanned_ships[i]; if (d2 < found_d2) { [self setFoundTarget:thing]; found_d2 = d2; } } } } [self checkFoundTarget]; } - (void) setDestinationToDockingAbort { Entity *the_target = [self targetStation]; double bo_distance = 8000; // 8km back off HPVector v0 = position; HPVector d0 = (the_target) ? the_target->position : kZeroHPVector; v0.x += (randf() - 0.5)*collision_radius; v0.y += (randf() - 0.5)*collision_radius; v0.z += (randf() - 0.5)*collision_radius; v0.x -= d0.x; v0.y -= d0.y; v0.z -= d0.z; v0 = HPvector_normal_or_fallback(v0, make_HPvector(0, 0, -1)); v0.x *= bo_distance; v0.y *= bo_distance; v0.z *= bo_distance; v0.x += d0.x; v0.y += d0.y; v0.z += d0.z; coordinates = v0; _destination = v0; } - (void) requestNewTarget { ShipEntity *mother = [[self group] leader]; if (mother == nil) { [shipAI message:@"MOTHER_LOST"]; return; } /*-- Locates all the ships in range targeting the mother ship and chooses the nearest/biggest --*/ DESTROY(_foundTarget); [self checkScanner]; unsigned i; GLfloat found_d2 = scannerRange * scannerRange; GLfloat max_e = 0; for (i = 0; i < n_scanned_ships ; i++) { ShipEntity *thing = scanned_ships[i]; GLfloat d2 = distance2_scanned_ships[i]; GLfloat e1 = [thing energy]; if ((d2 < found_d2) && ![thing isCloaked] && (([thing isThargoid] && ![mother isThargoid]) || (([thing primaryTarget] == mother) && [thing hasHostileTarget]))) { if (e1 > max_e) { [self setFoundTarget:thing]; max_e = e1; } } } [self checkFoundTarget]; } - (void) rollD:(NSString *)die_number { int die_sides = [die_number intValue]; if (die_sides > 0) { int die_roll = 1 + (ranrot_rand() % die_sides); NSString* result = [NSString stringWithFormat:@"ROLL_%d", die_roll]; [shipAI reactToMessage:result context:@"rollD:"]; } else { OOLog(@"ai.rollD.invalidValue", @"***** ERROR: invalid value supplied to rollD: '%@'.", die_number); } } - (void) scanForNearestShipWithPrimaryRole:(NSString *)scanRole { [self scanForNearestShipWithPredicate:HasPrimaryRolePredicate parameter:scanRole]; } - (void) scanForNearestShipHavingRole:(NSString *)scanRole { [self scanForNearestShipWithPredicate:HasRolePredicate parameter:scanRole]; } - (void) scanForNearestShipWithAnyPrimaryRole:(NSString *)scanRoles { NSSet *set = [NSSet setWithArray:ScanTokensFromString(scanRoles)]; [self scanForNearestShipWithPredicate:HasPrimaryRoleInSetPredicate parameter:set]; } - (void) scanForNearestShipHavingAnyRole:(NSString *)scanRoles { NSSet *set = [NSSet setWithArray:ScanTokensFromString(scanRoles)]; [self scanForNearestShipWithPredicate:HasRoleInSetPredicate parameter:set]; } - (void) scanForNearestShipWithScanClass:(NSString *)scanScanClass { NSNumber *parameter = [NSNumber numberWithInt:OOScanClassFromString(scanScanClass)]; [self scanForNearestShipWithPredicate:HasScanClassPredicate parameter:parameter]; } - (void) scanForNearestShipWithoutPrimaryRole:(NSString *)scanRole { [self scanForNearestShipWithNegatedPredicate:HasPrimaryRolePredicate parameter:scanRole]; } - (void) scanForNearestShipNotHavingRole:(NSString *)scanRole { [self scanForNearestShipWithNegatedPredicate:HasRolePredicate parameter:scanRole]; } - (void) scanForNearestShipWithoutAnyPrimaryRole:(NSString *)scanRoles { NSSet *set = [NSSet setWithArray:ScanTokensFromString(scanRoles)]; [self scanForNearestShipWithNegatedPredicate:HasPrimaryRoleInSetPredicate parameter:set]; } - (void) scanForNearestShipNotHavingAnyRole:(NSString *)scanRoles { NSSet *set = [NSSet setWithArray:ScanTokensFromString(scanRoles)]; [self scanForNearestShipWithNegatedPredicate:HasRoleInSetPredicate parameter:set]; } - (void) scanForNearestShipWithoutScanClass:(NSString *)scanScanClass { NSNumber *parameter = [NSNumber numberWithInt:OOScanClassFromString(scanScanClass)]; [self scanForNearestShipWithNegatedPredicate:HasScanClassPredicate parameter:parameter]; } - (void) scanForNearestShipMatchingPredicate:(NSString *)predicateExpression { /* Takes a boolean-valued JS expression where "ship" is the ship being evaluated and "this" is our ship's ship script. the expression is turned into a JS function of the form: function _oo_AIScanPredicate(ship) { return $expression; } Examples of expressions: ship.isWeapon this.someComplicatedPredicate(ship) function (ship) { ...do something complicated... } () */ static NSMutableDictionary *scriptCache = nil; NSString *aiName = nil; NSString *key = nil; OOJSFunction *function = nil; JSContext *context = NULL; context = OOJSAcquireContext(); if (predicateExpression == nil) predicateExpression = @"false"; aiName = [[self getAI] name]; #ifndef NDEBUG /* In debug/test release builds, scripts are cached per AI in order to be able to report errors correctly. For end-user releases, we only cache one copy of each predicate, potentially leading to error messages for the wrong AI. */ key = [NSString stringWithFormat:@"%@\n%@", aiName, predicateExpression]; #else key = predicateExpression; #endif // Look for cached function function = [scriptCache objectForKey:key]; if (function == nil) { NSString *predicateCode = nil; const char *argNames[] = { "ship" }; // Stuff expression in a function. predicateCode = [NSString stringWithFormat:@"return %@;", predicateExpression]; function = [[OOJSFunction alloc] initWithName:@"_oo_AIScanPredicate" scope:NULL code:predicateCode argumentCount:1 argumentNames:argNames fileName:aiName lineNumber:0 context:context]; [function autorelease]; // Cache function. if (function != nil) { if (scriptCache == nil) scriptCache = [[NSMutableDictionary alloc] init]; [scriptCache setObject:function forKey:key]; } } if (function != nil) { JSFunctionPredicateParameter param = { .context = context, .function = [function functionValue], .jsThis = OOJSObjectFromNativeObject(context, self) }; [self scanForNearestShipWithPredicate:JSFunctionPredicate parameter:¶m]; } else { // Report error (once per occurrence) static NSMutableSet *errorCache = nil; if (![errorCache containsObject:key]) { OOLog(@"ai.scanForNearestShipMatchingPredicate.compile.failed", @"Could not compile JavaScript predicate \"%@\" for AI %@.", predicateExpression, [[self getAI] name]); if (errorCache == nil) errorCache = [[NSMutableSet alloc] init]; [errorCache addObject:key]; } // Select nothing DESTROY(_foundTarget); [[self getAI] message:@"NOTHING_FOUND"]; } JS_ReportPendingException(context); OOJSRelinquishContext(context); } - (void) setCoordinates:(NSString *)system_x_y_z { NSArray* tokens = ScanTokensFromString(system_x_y_z); NSString* systemString = nil; NSString* xString = nil; NSString* yString = nil; NSString* zString = nil; if ([tokens count] != 4) { OOLog(@"ai.syntax.setCoordinates", @"***** ERROR: cannot setCoordinates: '%@'.",system_x_y_z); return; } systemString = (NSString *)[tokens objectAtIndex:0]; xString = (NSString *)[tokens objectAtIndex:1]; if ([xString hasPrefix:@"rand:"]) xString = [NSString stringWithFormat:@"%.3f", bellf([(NSString*)[[xString componentsSeparatedByString:@":"] objectAtIndex:1] intValue])]; yString = (NSString *)[tokens objectAtIndex:2]; if ([yString hasPrefix:@"rand:"]) yString = [NSString stringWithFormat:@"%.3f", bellf([(NSString*)[[yString componentsSeparatedByString:@":"] objectAtIndex:1] intValue])]; zString = (NSString *)[tokens objectAtIndex:3]; if ([zString hasPrefix:@"rand:"]) zString = [NSString stringWithFormat:@"%.3f", bellf([(NSString*)[[zString componentsSeparatedByString:@":"] objectAtIndex:1] intValue])]; HPVector posn = make_HPvector([xString floatValue], [yString floatValue], [zString floatValue]); GLfloat scalar = 1.0; coordinates = [UNIVERSE coordinatesForPosition:posn withCoordinateSystem:systemString returningScalar:&scalar]; [shipAI message:@"APPROACH_COORDINATES"]; } - (void) checkForNormalSpace { if ([UNIVERSE sun] && [UNIVERSE planet]) [shipAI message:@"NORMAL_SPACE"]; else [shipAI message:@"INTERSTELLAR_SPACE"]; } - (void) setTargetToRandomStation { /*- selects the nearest station it can find -*/ int ent_count = UNIVERSE->n_entities; Entity **uni_entities = UNIVERSE->sortedEntities; // grab the public sorted list Entity *my_entities[ent_count]; StationEntity *station = nil, *my_station = nil; double maxRange2 = desired_range * desired_range; int i; int station_count = 0; for (i = 0; i < ent_count; i++) { // find stations within range but exclude carriers. if (uni_entities[i]->isStation) { my_station = (StationEntity*)uni_entities[i]; if ([my_station maxFlightSpeed] == 0 && [my_station hasNPCTraffic] && HPdistance2(position, [my_station position]) < maxRange2) { my_entities[station_count++] = [uni_entities[i] retain]; // retained } } } if (station_count != 0) { // select a random station station = (StationEntity *)my_entities[ranrot_rand() % station_count]; // if more than one candidate do not select main station if (station == [UNIVERSE station] && station_count > 1) { while (station == [UNIVERSE station]) { station = (StationEntity *)my_entities[ranrot_rand() % station_count]; } } } for (i = 0; i < station_count; i++) [my_entities[i] release]; // released // if (station) { [self addTarget:station]; [self setTargetStation:station]; [shipAI message:@"STATION_FOUND"]; } else { [shipAI message:@"NO_STATION_IN_RANGE"]; } } - (void) setTargetToLastStation { Entity *station = [self targetStation]; if (station != nil && [station isStation]) { [self addTarget:station]; } else { [shipAI message:@"NO_STATION_FOUND"]; [self setTargetStation:nil]; } } - (void) addFuel:(NSString*) fuel_number { [self setFuel:[self fuel] + [fuel_number intValue] * 10]; } - (void) scriptActionOnTarget:(NSString *)action { PlayerEntity *player = PLAYER; ShipEntity *targEnt = [self primaryTarget]; ShipEntity *oldTarget = nil; #ifndef NDEBUG static BOOL deprecationWarning = NO; if (!deprecationWarning) { deprecationWarning = YES; OOLog(@"script.deprecated.scriptActionOnTarget", @"----- WARNING in AI %@: the AI method scriptActionOnTarget: is deprecated and should not be used. It is slow and has unpredictable side effects. The recommended alternative is to use sendScriptMessage: to call a function in a ship's JavaScript ship script instead. scriptActionOnTarget: should not be used at all from scripts. An alternative is safeScriptActionOnTarget:, which is similar to scriptActionOnTarget: but has less side effects.", [AI currentlyRunningAIDescription]); } else { OOLog(@"script.deprecated.scriptActionOnTarget.repeat", @"----- WARNING in AI %@: the AI method scriptActionOnTarget: is deprecated and should not be used.", [AI currentlyRunningAIDescription]); } #endif if ([targEnt isShip]) { oldTarget = [player scriptTarget]; [player setScriptTarget:(ShipEntity*)targEnt]; [player runUnsanitizedScriptActions:[NSArray arrayWithObject:action] allowingAIMethods:YES withContextName:[NSString stringWithFormat:@"", [[self getAI] name], [[self getAI] state]] forTarget:targEnt]; [player checkScript]; // react immediately to any changes this makes [player setScriptTarget:oldTarget]; } } - (void) safeScriptActionOnTarget:(NSString *)action { PlayerEntity *player = PLAYER; ShipEntity *targEnt = [self primaryTarget]; ShipEntity *oldTarget = nil; if ([targEnt isShip]) { oldTarget = [player scriptTarget]; [player setScriptTarget:(ShipEntity*)targEnt]; [player runUnsanitizedScriptActions:[NSArray arrayWithObject:action] allowingAIMethods:YES withContextName:[NSString stringWithFormat:@"", [[self getAI] name], [[self getAI] state]] forTarget:targEnt]; [player setScriptTarget:oldTarget]; } } // Send own ship script a message. - (void) sendScriptMessage:(NSString *)message { NSArray *components = ScanTokensFromString(message); if ([components count] == 1) { [self doScriptEvent:OOJSIDFromString(message)]; } else { NSString *function = [components objectAtIndex:0]; components = [components subarrayWithRange:NSMakeRange(1, [components count] - 1)]; [self doScriptEvent:OOJSIDFromString(function) withArgument:components]; } } - (void) ai_throwSparks { [self setThrowSparks:YES]; } - (void) explodeSelf { [self getDestroyedBy:nil damageType:kOODamageTypeEnergy]; } - (void) ai_debugMessage:(NSString *)message { NSString *desc = [NSString stringWithFormat:@"%@ %d", [self name], [self universalID]]; if ([self isPlayer]) desc = @"player autopilot"; OOLog(@"ai.takeAction.debugMessage", @"DEBUG: AI MESSAGE from %@: %@", desc, message); } // racing code TODO - (void) targetFirstBeaconWithCode:(NSString*) code { NSArray *all_beacons = [UNIVERSE listBeaconsWithCode: code]; if ([all_beacons count]) { [self addTarget:(ShipEntity*)[all_beacons objectAtIndex:0]]; [shipAI message:@"TARGET_FOUND"]; } else [shipAI message:@"NOTHING_FOUND"]; } - (void) targetNextBeaconWithCode:(NSString*) code { NSArray *all_beacons = [UNIVERSE listBeaconsWithCode: code]; ShipEntity *current_beacon = [self primaryTarget]; if ((!current_beacon)||(![current_beacon isBeacon])) { [shipAI message:@"NO_CURRENT_BEACON"]; [shipAI message:@"NOTHING_FOUND"]; return; } // find the current beacon in the list.. NSUInteger i = [all_beacons indexOfObject:current_beacon]; if (i == NSNotFound) { [shipAI message:@"NOTHING_FOUND"]; return; } i++; // next index if (i < [all_beacons count]) { // locate current target in list [self addTarget:(ShipEntity*)[all_beacons objectAtIndex:i]]; [shipAI message:@"TARGET_FOUND"]; } else { [shipAI message:@"LAST_BEACON"]; [shipAI message:@"NOTHING_FOUND"]; } } - (void) setRacepointsFromTarget { // two point - one at z - cr one at z + cr ShipEntity *ship = [self primaryTarget]; if (ship == nil) { [shipAI message:@"NOTHING_FOUND"]; return; } Vector k = ship->v_forward; GLfloat c = ship->collision_radius; HPVector o = ship->position; navpoints[0] = make_HPvector(o.x - c * k.x, o.y - c * k.y, o.z - c * k.z); navpoints[1] = make_HPvector(o.x + c * k.x, o.y + c * k.y, o.z + c * k.z); navpoints[2] = make_HPvector(o.x + 2.0 * c * k.x, o.y + 2.0 * c * k.y, o.z + 2.0 * c * k.z); number_of_navpoints = 2; next_navpoint_index = 0; _destination = navpoints[0]; [shipAI message:@"RACEPOINTS_SET"]; } - (void) performFlyRacepoints { next_navpoint_index = 0; desired_range = collision_radius; behaviour = BEHAVIOUR_FLY_THRU_NAVPOINTS; } @end @implementation ShipEntity (OOAIPrivate) - (void) checkFoundTarget { if ([self foundTarget] != nil) { [shipAI message:@"TARGET_FOUND"]; } else { [shipAI message:@"NOTHING_FOUND"]; } } - (BOOL) performHyperSpaceExitReplace:(BOOL)replace { return [self performHyperSpaceExitReplace:replace toSystem:-1]; } - (BOOL) performHyperSpaceExitReplace:(BOOL)replace toSystem:(OOSystemID)systemID { if(![self hasHyperspaceMotor]) { [shipAI reactToMessage:@"WITCHSPACE UNAVAILABLE" context:@"performHyperSpaceExit"]; return NO; } if([self status] == STATUS_ENTERING_WITCHSPACE) { // already in a wormhole return NO; } NSArray *sDests = nil; OOSystemID targetSystem; NSUInteger i = 0; // get a list of destinations within range sDests = [UNIVERSE nearbyDestinationsWithinRange: 0.1f * fuel]; NSUInteger n_dests = [sDests count]; // if none available report to the AI and exit if (n_dests == 0) { [shipAI reactToMessage:@"WITCHSPACE UNAVAILABLE" context:@"performHyperSpaceExit"]; // If no systems exist near us, the AI is switched to a different state, so we do not need // the nearby destinations array anymore. return NO; } // check if we're clear of nearby masses ShipEntity *blocker = [UNIVERSE entityForUniversalID:[self checkShipsInVicinityForWitchJumpExit]]; if (blocker) { [self setFoundTarget:blocker]; [shipAI reactToMessage:@"WITCHSPACE BLOCKED" context:@"performHyperSpaceExit"]; [self doScriptEvent:OOJSID("shipWitchspaceBlocked") withArgument:blocker]; return NO; } if (systemID == -1) { // select one at random if (n_dests > 1) { i = ranrot_rand() % n_dests; } targetSystem = [[sDests oo_dictionaryAtIndex:i] oo_intForKey:@"sysID"]; } else { targetSystem = systemID; for (i = 0; i < n_dests; i++) { if (systemID == [[sDests oo_dictionaryAtIndex:i] oo_intForKey:@"sysID"]) break; } if (i == n_dests) // no match found { return NO; } } float dist = [[sDests oo_dictionaryAtIndex:i] oo_floatForKey:@"distance"]; if (dist > [self maxHyperspaceDistance] || dist > fuel/10) { OOLogWARN(@"script.debug", @"DEBUG: %@ Jumping %f which is further than allowed. I have %d fuel", self, dist, fuel); } fuel -= 10 * dist; // create wormhole WormholeEntity *whole = [[[WormholeEntity alloc] initWormholeTo: targetSystem fromShip:self] autorelease]; [UNIVERSE addEntity: whole]; [self enterWormhole:whole replacing:replace]; // we've no need for the destinations array anymore. return YES; } - (void) scanForNearestShipWithPredicate:(EntityFilterPredicate)predicate parameter:(void *)parameter { // Locates all the ships in range for which predicate returns YES, and chooses the nearest. unsigned i; ShipEntity *candidate; float d2, found_d2 = scannerRange * scannerRange; DESTROY(_foundTarget); [self checkScanner]; if (predicate == NULL) return; for (i = 0; i < n_scanned_ships ; i++) { candidate = scanned_ships[i]; d2 = distance2_scanned_ships[i]; if ((d2 < found_d2) && (candidate->scanClass != CLASS_CARGO) && ([candidate status] != STATUS_DOCKED) && predicate(candidate, parameter) && ![candidate isCloaked]) { [self setFoundTarget:candidate]; found_d2 = d2; } } [self checkFoundTarget]; } - (void) scanForNearestShipWithNegatedPredicate:(EntityFilterPredicate)predicate parameter:(void *)parameter { ChainedEntityPredicateParameter param = { predicate, parameter }; [self scanForNearestShipWithPredicate:NOTPredicate parameter:¶m]; } - (void) acceptDistressMessageFrom:(ShipEntity *)other { [self setFoundTarget:[other primaryTarget]]; if ([self isPolice]) { [(ShipEntity*)[self foundTarget] markAsOffender:8 withReason:kOOLegalStatusReasonDistressCall]; // you have been warned!! } NSString *context = nil; #ifndef NDEBUG context = [NSString stringWithFormat:@"%@ broadcastDistressMessage", [other shortDescription]]; #endif [shipAI reactToMessage:@"ACCEPT_DISTRESS_CALL" context:context]; } @end @implementation StationEntity (OOAIPrivate) - (void) acceptDistressMessageFrom:(ShipEntity *)other { if (self != [UNIVERSE station]) return; OOWeakReference *old_target = _primaryTarget; _primaryTarget = [[[other primaryTarget] weakRetain] autorelease]; [(ShipEntity *)[other primaryTarget] markAsOffender:8 withReason:kOOLegalStatusReasonDistressCall]; // mark their card [self launchDefenseShip]; _primaryTarget = old_target; } @end @implementation ShipEntity (OOAIStationStubs) // AI methods for stations, have no effect on normal ships. #define STATION_STUB_BASE(PROTO, NAME) PROTO { OOLog(@"ai.invalid.notAStation", @"Attempt to use station AI method \"%s\" on non-station %@.", NAME, self); } #define STATION_STUB_NOARG(NAME) STATION_STUB_BASE(- (void) NAME, #NAME) #define STATION_STUB_ARG(NAME) STATION_STUB_BASE(- (void) NAME (NSString *)param, #NAME) STATION_STUB_NOARG(increaseAlertLevel) STATION_STUB_NOARG(decreaseAlertLevel) STATION_STUB_NOARG(launchPolice) STATION_STUB_NOARG(launchDefenseShip) STATION_STUB_NOARG(launchScavenger) STATION_STUB_NOARG(launchMiner) STATION_STUB_NOARG(launchPirateShip) STATION_STUB_NOARG(launchShuttle) STATION_STUB_NOARG(launchTrader) STATION_STUB_NOARG(launchEscort) - (BOOL) launchPatrol { OOLog(@"ai.invalid.notAStation", @"Attempt to use station AI method \"%s\" on non-station %@.", "launchPatrol", self); return NO; } STATION_STUB_ARG(launchShipWithRole:) STATION_STUB_NOARG(abortAllDockings) @end oolite-1.82/src/Core/Entities/ShipEntityLoadRestore.h000066400000000000000000000032441256642440500226320ustar00rootroot00000000000000/* ShipEntityLoadRestore.h Support for saving and restoring individual non-player ships. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "ShipEntity.h" @interface ShipEntity (LoadRestore) /* Produces a property list representation of a specific ship. Intended for use with wormholes, but should probably generalize quite well. The optional "context" is a mutable dictionary used to synchronise certain state when saving multiple ships - currently, groups. It is not a property list and does not need to be saved alongside the ships. */ - (NSDictionary *) savedShipDictionaryWithContext:(NSMutableDictionary *)context; /* Restore a ship from a property list representation generated with -savedShipDictionary. If the ship can't be restored and fallback is YES, an attempt will be made to generate a new ship with the same primary role. */ + (id) shipRestoredFromDictionary:(NSDictionary *)dictionary useFallback:(BOOL)fallback context:(NSMutableDictionary *)context; @end oolite-1.82/src/Core/Entities/ShipEntityLoadRestore.m000066400000000000000000000307231256642440500226410ustar00rootroot00000000000000/* ShipEntityLoadRestore.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "ShipEntityLoadRestore.h" #import "Universe.h" #import "OOShipRegistry.h" #import "OORoleSet.h" #import "OOCollectionExtractors.h" #import "OOConstToString.h" #import "OOShipGroup.h" #import "OOEquipmentType.h" #import "AI.h" #import "ShipEntityAI.h" #define KEY_SHIP_KEY @"ship_key" #define KEY_SHIPDATA_OVERRIDES @"shipdata_overrides" #define KEY_SHIPDATA_DELETES @"shipdata_deletes" #define KEY_PRIMARY_ROLE @"primary_role" #define KEY_POSITION @"position" #define KEY_ORIENTATION @"orientation" #define KEY_ROLES @"roles" #define KEY_FUEL @"fuel" #define KEY_BOUNTY @"bounty" #define KEY_ENERGY_LEVEL @"energy_level" #define KEY_EQUIPMENT @"equipment" #define KEY_MISSILES @"missiles" #define KEY_FORWARD_WEAPON @"forward_weapon_type" #define KEY_AFT_WEAPON @"aft_weapon_type" #define KEY_SCAN_CLASS @"scan_class" // AI is a complete pickled AI state. #define KEY_AI @"AI" // Group IDs are numbers synchronised through the context object. #define KEY_GROUP_ID @"group" #define KEY_GROUP_NAME @"group_name" #define KEY_IS_GROUP_LEADER @"is_group_leader" #define KEY_ESCORT_GROUP_ID @"escort_group" static void StripIgnoredKeys(NSMutableDictionary *dict); static NSUInteger GroupIDForGroup(OOShipGroup *group, NSMutableDictionary *context); static OOShipGroup *GroupForGroupID(NSUInteger groupID, NSMutableDictionary *context); @interface ShipEntity (LoadRestoreInternal) - (void) simplifyShipdata:(NSMutableDictionary *)data andGetDeletes:(NSArray **)deletes; @end @implementation ShipEntity (LoadRestore) - (NSDictionary *) savedShipDictionaryWithContext:(NSMutableDictionary *)context { NSMutableDictionary *result = [NSMutableDictionary dictionary]; if (context == nil) context = [NSMutableDictionary dictionary]; [result setObject:_shipKey forKey:KEY_SHIP_KEY]; NSMutableDictionary *updatedShipInfo = [NSMutableDictionary dictionaryWithDictionary:shipinfoDictionary]; [updatedShipInfo setObject:[[self roleSet] roleString] forKey:KEY_ROLES]; [updatedShipInfo oo_setUnsignedInteger:fuel forKey:KEY_FUEL]; [updatedShipInfo oo_setUnsignedLongLong:bounty forKey:KEY_BOUNTY]; [updatedShipInfo setObject:OOStringFromWeaponType(forward_weapon_type) forKey:KEY_FORWARD_WEAPON]; [updatedShipInfo setObject:OOStringFromWeaponType(aft_weapon_type) forKey:KEY_AFT_WEAPON]; [updatedShipInfo setObject:OOStringFromScanClass(scanClass) forKey:KEY_SCAN_CLASS]; NSArray *deletes = nil; [self simplifyShipdata:updatedShipInfo andGetDeletes:&deletes]; [result setObject:updatedShipInfo forKey:KEY_SHIPDATA_OVERRIDES]; if (deletes != nil) [result setObject:deletes forKey:KEY_SHIPDATA_DELETES]; if (!HPvector_equal([self position], kZeroHPVector)) { [result oo_setHPVector:[self position] forKey:KEY_POSITION]; } if (!quaternion_equal([self normalOrientation], kIdentityQuaternion)) { [result oo_setQuaternion:[self normalOrientation] forKey:KEY_ORIENTATION]; } if (energy != maxEnergy) [result oo_setFloat:(float)energy / (float)maxEnergy forKey:KEY_ENERGY_LEVEL]; [result setObject:[self primaryRole] forKey:KEY_PRIMARY_ROLE]; // Add equipment. NSArray *equipment = [[self equipmentEnumerator] allObjects]; if ([equipment count] != 0) [result setObject:equipment forKey:KEY_EQUIPMENT]; // Add missiles. if (missiles > 0) { NSMutableArray *missileArray = [NSMutableArray array]; unsigned i; for (i = 0; i < missiles; i++) { NSString *missileType = [missile_list[i] identifier]; if (missileType != nil) [missileArray addObject:missileType]; } [result setObject:missileArray forKey:KEY_MISSILES]; } // Add groups. if (_group != nil) { [result oo_setUnsignedInteger:GroupIDForGroup(_group, context) forKey:KEY_GROUP_ID]; if ([_group leader] == self) [result oo_setBool:YES forKey:KEY_IS_GROUP_LEADER]; NSString *groupName = [_group name]; if (groupName != nil) { [result setObject:groupName forKey:KEY_GROUP_NAME]; } } if (_escortGroup != nil) { [result oo_setUnsignedInteger:GroupIDForGroup(_escortGroup, context) forKey:KEY_ESCORT_GROUP_ID]; } /* Eric: The escortGroup property is removed from the lead ship, on entering witchspace. But it is needed in the save file to correctly restore an escorted group. */ else if (_group != nil && [_group leader] == self) { [result oo_setUnsignedInteger:GroupIDForGroup(_group, context) forKey:KEY_ESCORT_GROUP_ID]; } // FIXME: AI. // Eric: I think storing the AI name should be enough. On entering a wormhole, the stack is cleared so there are no preserved AI states. // Also the AI restarts itself with the GLOBAL state, so no need to store any old state. if ([[[self getAI] name] isEqualToString:@"nullAI.plist"]) { // might be a JS version [result setObject:[[self getAI] associatedJS] forKey:KEY_AI]; // if there isn't, loading nullAI.js will load nullAI.plist anyway } else { [result setObject:[[self getAI] name] forKey:KEY_AI]; } return result; } + (id) shipRestoredFromDictionary:(NSDictionary *)dict useFallback:(BOOL)fallback context:(NSMutableDictionary *)context { if (dict == nil) return nil; if (context == nil) context = [NSMutableDictionary dictionary]; ShipEntity *ship = nil; NSString *shipKey = [dict oo_stringForKey:KEY_SHIP_KEY]; NSDictionary *shipData = [[OOShipRegistry sharedRegistry] shipInfoForKey:shipKey]; if (shipData != nil) { NSMutableDictionary *mergedData = [NSMutableDictionary dictionaryWithDictionary:shipData]; StripIgnoredKeys(mergedData); NSArray *deletes = [dict oo_arrayForKey:KEY_SHIPDATA_DELETES]; if (deletes != nil) [mergedData removeObjectsForKeys:deletes]; [mergedData addEntriesFromDictionary:[dict oo_dictionaryForKey:KEY_SHIPDATA_OVERRIDES]]; [mergedData oo_setBool:NO forKey:@"auto_ai"]; [mergedData oo_setUnsignedInteger:0 forKey:@"escorts"]; Class shipClass = [UNIVERSE shipClassForShipDictionary:mergedData]; ship = [[[shipClass alloc] initWithKey:shipKey definition:mergedData] autorelease]; // FIXME: restore AI. [ship setAITo:[dict oo_stringForKey:KEY_AI defaultValue:@"nullAI.plist"]]; [ship setPrimaryRole:[dict oo_stringForKey:KEY_PRIMARY_ROLE]]; } else { // Unknown ship; fall back on role if desired and possible. NSString *shipPrimaryRole = [dict oo_stringForKey:KEY_PRIMARY_ROLE]; if (!fallback || shipPrimaryRole == nil) return nil; ship = [[UNIVERSE newShipWithRole:shipPrimaryRole] autorelease]; if (ship == nil) return nil; } // The following stuff is deliberately set up the same way even if using role fallback. [ship setPosition:[dict oo_hpvectorForKey:KEY_POSITION]]; [ship setNormalOrientation:[dict oo_quaternionForKey:KEY_ORIENTATION]]; float energyLevel = [dict oo_floatForKey:KEY_ENERGY_LEVEL defaultValue:1.0f]; [ship setEnergy:energyLevel * [ship maxEnergy]]; [ship removeAllEquipment]; NSEnumerator *eqEnum = nil; NSString *eqKey = nil; for (eqEnum = [[dict oo_arrayForKey:KEY_EQUIPMENT] objectEnumerator]; (eqKey = [eqEnum nextObject]); ) { [ship addEquipmentItem:eqKey withValidation:NO inContext:@"loading"]; } [ship removeMissiles]; for (eqEnum = [[dict oo_arrayForKey:KEY_MISSILES] objectEnumerator]; (eqKey = [eqEnum nextObject]); ) { [ship addEquipmentItem:eqKey withValidation:NO inContext:@"loading"]; } // Groups. NSUInteger groupID = [dict oo_integerForKey:KEY_GROUP_ID defaultValue:NSNotFound]; if (groupID != NSNotFound) { OOShipGroup *group = GroupForGroupID(groupID, context); [ship setGroup:group]; // Handles adding to group if ([dict oo_boolForKey:KEY_IS_GROUP_LEADER]) [group setLeader:ship]; NSString *groupName = [dict oo_stringForKey:KEY_GROUP_NAME]; if (groupName != nil) [group setName:groupName]; if ([ship hasPrimaryRole:@"escort"] && ship != [group leader]) { [ship setOwner:[group leader]]; } } groupID = [dict oo_integerForKey:KEY_ESCORT_GROUP_ID defaultValue:NSNotFound]; if (groupID != NSNotFound) { OOShipGroup *group = GroupForGroupID(groupID, context); [group setLeader:ship]; [group setName:@"escort group"]; [ship setEscortGroup:group]; } return ship; } - (void) simplifyShipdata:(NSMutableDictionary *)data andGetDeletes:(NSArray **)deletes { NSParameterAssert(data != nil && deletes != NULL); *deletes = nil; // Get original ship data. NSMutableDictionary *referenceData = [NSMutableDictionary dictionaryWithDictionary:[[OOShipRegistry sharedRegistry] shipInfoForKey:[self shipDataKey]]]; // Discard stuff that we handle separately. StripIgnoredKeys(referenceData); StripIgnoredKeys(data); // Note items that are in referenceData, but not data. NSMutableArray *foundDeletes = [NSMutableArray array]; NSEnumerator *enumerator = nil; NSString *key = nil; for (enumerator = [referenceData keyEnumerator]; (key = [enumerator nextObject]); ) { if ([data objectForKey:key] == nil) { [foundDeletes addObject:key]; } } if ([foundDeletes count] != 0) *deletes = foundDeletes; // after rev3010 this loop was using cycles without doing anything - commenting this whole loop out for now. -- kaks 20100207 /* // Discard anything that hasn't changed. for (enumerator = [data keyEnumerator]; (key = [enumerator nextObject]); ) { id referenceVal = [referenceData objectForKey:key]; id myVal = [data objectForKey:key]; if ([referenceVal isEqual:myVal]) { // [data removeObjectForKey:key]; } } */ } @end static void StripIgnoredKeys(NSMutableDictionary *dict) { static NSArray *ignoredKeys = nil; if (ignoredKeys == nil) ignoredKeys = [[NSArray alloc] initWithObjects:@"ai_type", @"has_ecm", @"has_scoop", @"has_escape_pod", @"has_energy_bomb", @"has_fuel_injection", @"has_cloaking_device", @"has_military_jammer", @"has_military_scanner_filter", @"has_shield_booster", @"has_shield_enhancer", @"escorts", @"escort_role", @"escort-ship", @"conditions", @"missiles", @"auto_ai", nil]; NSEnumerator *keyEnum = nil; NSString *key = nil; for (keyEnum = [ignoredKeys objectEnumerator]; (key = [keyEnum nextObject]); ) { [dict removeObjectForKey:key]; } } static NSUInteger GroupIDForGroup(OOShipGroup *group, NSMutableDictionary *context) { NSMutableDictionary *groupIDs = [context objectForKey:@"groupIDs"]; if (groupIDs == nil) { groupIDs = [NSMutableDictionary dictionary]; [context setObject:groupIDs forKey:@"groupIDs"]; } NSValue *key = [NSValue valueWithNonretainedObject:group]; NSNumber *groupIDObj = [groupIDs objectForKey:key]; unsigned groupID; if (groupIDObj == nil) { // Assign a new group ID. groupID = [context oo_unsignedIntForKey:@"nextGroupID"]; groupIDObj = [NSNumber numberWithUnsignedInt:groupID]; [context oo_setUnsignedInteger:groupID + 1 forKey:@"nextGroupID"]; [groupIDs setObject:groupIDObj forKey:key]; /* Also keep references to the groups. This isn't necessary at the time of writing, but would be if we e.g. switched to pickling ships in wormholes all the time (each wormhole would then need a persistent context). We can't simply use the groups instead of NSValues as keys, becuase dictionary keys must be copyable. */ NSMutableSet *groups = [context objectForKey:@"groups"]; if (groups == nil) { groups = [NSMutableSet set]; [context setObject:groups forKey:@"groups"]; } [groups addObject:group]; } else { groupID = [groupIDObj unsignedIntValue]; } return groupID; } static OOShipGroup *GroupForGroupID(NSUInteger groupID, NSMutableDictionary *context) { NSNumber *key = [NSNumber numberWithUnsignedInteger:groupID]; NSMutableDictionary *groups = [context objectForKey:@"groupsByID"]; if (groups == nil) { groups = [NSMutableDictionary dictionary]; [context setObject:groups forKey:@"groupsByID"]; } OOShipGroup *group = [groups objectForKey:key]; if (group == nil) { group = [[[OOShipGroup alloc] init] autorelease]; [groups setObject:group forKey:key]; } return group; } oolite-1.82/src/Core/Entities/ShipEntityScriptMethods.h000066400000000000000000000021231256642440500231720ustar00rootroot00000000000000/* ShipEntityScriptMethods.h Methods for use by scripting mechanisms. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "ShipEntity.h" @interface ShipEntity (ScriptMethods) - (ShipEntity *) ejectShipOfType:(NSString *)shipKey; // Note: ship type, not role. - (ShipEntity *) ejectShipOfRole:(NSString *)role; - (NSArray *) spawnShipsWithRole:(NSString *)role count:(NSUInteger)count; @end oolite-1.82/src/Core/Entities/ShipEntityScriptMethods.m000066400000000000000000000046201256642440500232030ustar00rootroot00000000000000/* ShipEntityScriptMethods.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "ShipEntityScriptMethods.h" #import "Universe.h" #import "OOCollectionExtractors.h" static NSString * const kOOLogNoteAddShips = @"script.debug.note.addShips"; @implementation ShipEntity (ScriptMethods) - (ShipEntity *) ejectShipOfType:(NSString *)shipKey { ShipEntity *item = nil; if (shipKey != nil) { item = [[UNIVERSE newShipWithName:shipKey] autorelease]; if (item != nil) [self dumpItem:item]; } return item; } - (ShipEntity *) ejectShipOfRole:(NSString *)role { ShipEntity *item = nil; if (role != nil) { item = [[UNIVERSE newShipWithRole:role] autorelease]; if (item != nil) [self dumpItem:item]; } return item; } - (NSArray *) spawnShipsWithRole:(NSString *)role count:(NSUInteger)count { ShipEntity *ship = [self rootShipEntity]; // FIXME: (EMMSTRAN) implement an -absolutePosition method, use that in spawnShipWithRole:near:, and use self instead of root. ShipEntity *spawned = nil; NSMutableArray *result = nil; if (count == 0) return [NSArray array]; OOLog(kOOLogNoteAddShips, @"Spawning %ld x '%@' near %@ %d", count, role, [self shortDescription], [self universalID]); result = [NSMutableArray arrayWithCapacity:count]; do { spawned = [UNIVERSE spawnShipWithRole:role near:ship]; if (spawned != nil) { [spawned setTemperature:[self randomEjectaTemperature]]; if ([self isMissileFlagSet] && [[spawned shipInfoDictionary] oo_boolForKey:@"is_submunition"]) { [spawned setOwner:[self owner]]; [spawned addTarget:[self primaryTarget]]; [spawned setIsMissileFlag:YES]; } [result addObject:spawned]; } } while (--count); return result; } @end oolite-1.82/src/Core/Entities/SkyEntity.h000066400000000000000000000022171256642440500203300ustar00rootroot00000000000000/* SkyEntity.h Entity subclass implementing the game backdrop of stars and nebulae. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOEntityWithDrawable.h" @class OOColor; @interface SkyEntity: OOEntityWithDrawable { OOColor *skyColor; } - (id) initWithColors:(OOColor *)col1 :(OOColor *)col2 andSystemInfo:(NSDictionary *)systemInfo; - (BOOL) changeProperty:(NSString *)key withDictionary:(NSDictionary*) dict; - (OOColor *)skyColor; @end oolite-1.82/src/Core/Entities/SkyEntity.m000066400000000000000000000212011256642440500203270ustar00rootroot00000000000000/* SkyEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "SkyEntity.h" #import "OOSkyDrawable.h" #import "PlayerEntity.h" #import "OOMaths.h" #import "Universe.h" #import "MyOpenGLView.h" #import "OOColor.h" #import "OOStringParsing.h" #import "OOCollectionExtractors.h" #import "OOMaterial.h" #define SKY_BASIS_STARS 4800 #define SKY_BASIS_BLOBS 1280 #define SKY_clusterChance 0.80 #define SKY_alpha 0.10 #define SKY_scale 10.0 @interface SkyEntity (OOPrivate) - (BOOL)readColor1:(OOColor **)ioColor1 andColor2:(OOColor **)ioColor2 andColor3:(OOColor **)ioColor3 andColor4:(OOColor **)ioColor4 fromDictionary:(NSDictionary *)dictionary; @end @implementation SkyEntity - (id) initWithColors:(OOColor *)col1 :(OOColor *)col2 andSystemInfo:(NSDictionary *)systemInfo { OOSkyDrawable *skyDrawable; float clusterChance, alpha, scale; signed starCount, // Need to be able to hold -1... nebulaCount; unsigned starCountMultiplier, nebulaCountMultiplier; self = [super init]; if (self == nil) return nil; OOColor *col3 = [OOColor colorWithDescription:col1]; OOColor *col4 = [OOColor colorWithDescription:col2]; // Load colours BOOL nebulaColorSet = [self readColor1:&col1 andColor2:&col2 andColor3:&col3 andColor4:&col4 fromDictionary:systemInfo]; skyColor = [[OOColor colorWithDescription:[systemInfo objectForKey:@"sun_color"]] retain]; if (skyColor == nil) { skyColor = [[col2 blendedColorWithFraction:0.5 ofColor:col1] retain]; } // Load distribution values clusterChance = [systemInfo oo_floatForKey:@"sky_blur_cluster_chance" defaultValue:SKY_clusterChance]; alpha = [systemInfo oo_floatForKey:@"sky_blur_alpha" defaultValue:SKY_alpha]; scale = [systemInfo oo_floatForKey:@"sky_blur_scale" defaultValue:SKY_scale]; // Load star count starCount = [systemInfo oo_floatForKey:@"sky_n_stars" defaultValue:-1]; starCountMultiplier = [systemInfo oo_unsignedIntForKey:@"star_count_multiplier" defaultValue:1]; nebulaCountMultiplier = [systemInfo oo_unsignedIntForKey:@"nebula_count_multiplier" defaultValue:1]; if (0 <= starCount) { // changed for 1.82, default set to 1 // lets OXPers modify the broad number without stopping variation starCount *= starCountMultiplier; } else { starCount = starCountMultiplier * SKY_BASIS_STARS * (0.5 + randf()); } // ...and nebula count. (Note: simplifying this would change the appearance of stars/blobs.) nebulaCount = [systemInfo oo_floatForKey:@"sky_n_blurs" defaultValue:-1]; if (0 <= nebulaCount) { // changed for 1.82, default set to 1 // lets OXPers modify the broad number without stopping variation nebulaCount *= nebulaCountMultiplier; } else { nebulaCount = nebulaCountMultiplier * SKY_BASIS_BLOBS * (0.5 + randf()); } if ([UNIVERSE reducedDetail]) { // limit stars and blobs to basis levels, and halve stars again if (starCount > SKY_BASIS_STARS) { starCount = SKY_BASIS_STARS; } starCount /= 2; if (nebulaCount > SKY_BASIS_BLOBS) { nebulaCount = SKY_BASIS_BLOBS; } } skyDrawable = [[OOSkyDrawable alloc] initWithColor1:col1 Color2:col2 Color3:col3 Color4:col4 starCount:starCount nebulaCount:nebulaCount nebulaHueFix:nebulaColorSet clusterFactor:clusterChance alpha:alpha scale:scale]; [self setDrawable:skyDrawable]; [skyDrawable release]; [self setStatus:STATUS_EFFECT]; return self; } - (void) dealloc { [skyColor release]; [super dealloc]; } - (OOColor *) skyColor { return skyColor; } - (BOOL) changeProperty:(NSString *)key withDictionary:(NSDictionary*)dict { id object = [dict objectForKey:key]; // TODO: properties requiring reInit? if ([key isEqualToString:@"sun_color"]) { OOColor *col=[[OOColor colorWithDescription:object] retain]; if (col != nil) { [skyColor release]; skyColor = [col copy]; [col release]; [UNIVERSE setLighting]; } } else { OOLogWARN(@"script.warning", @"Change to property '%@' not applied, will apply only on leaving and re-entering this system.",key); return NO; } return YES; } - (void) update:(OOTimeDelta) delta_t { PlayerEntity *player = PLAYER; zero_distance = MAX_CLEAR_DEPTH * MAX_CLEAR_DEPTH; cam_zero_distance = zero_distance; if (player != nil) { position = [player viewpointPosition]; } else { OOLog(@"sky.warning",@"PLAYER is nil"); } } - (BOOL) isSky { return YES; } - (BOOL) isVisible { return YES; } - (BOOL) canCollide { return NO; } - (GLfloat) cameraRangeFront { return MAX_CLEAR_DEPTH; } - (GLfloat) cameraRangeBack { return MAX_CLEAR_DEPTH; } - (void) drawImmediate:(bool)immediate translucent:(bool)translucent { if ([UNIVERSE breakPatternHide]) return; [super drawImmediate:immediate translucent:translucent]; OOCheckOpenGLErrors(@"SkyEntity after drawing %@", self); } #ifndef NDEBUG - (NSString *) descriptionForObjDump { // Don't include range and visibility flag as they're irrelevant. return [self descriptionForObjDumpBasic]; } #endif @end @implementation SkyEntity (OOPrivate) - (BOOL)readColor1:(OOColor **)ioColor1 andColor2:(OOColor **)ioColor2 andColor3:(OOColor **)ioColor3 andColor4:(OOColor **)ioColor4 fromDictionary:(NSDictionary *)dictionary { NSString *string = nil; NSArray *tokens = nil; id colorDesc = nil; OOColor *color = nil; BOOL nebulaSet = NO; assert(ioColor1 != NULL && ioColor2 != NULL); string = [dictionary oo_stringForKey:@"sky_rgb_colors"]; if (string != nil) { tokens = ScanTokensFromString(string); if ([tokens count] == 6) { float r1 = OOClamp_0_1_f([tokens oo_floatAtIndex:0]); float g1 = OOClamp_0_1_f([tokens oo_floatAtIndex:1]); float b1 = OOClamp_0_1_f([tokens oo_floatAtIndex:2]); float r2 = OOClamp_0_1_f([tokens oo_floatAtIndex:3]); float g2 = OOClamp_0_1_f([tokens oo_floatAtIndex:4]); float b2 = OOClamp_0_1_f([tokens oo_floatAtIndex:5]); *ioColor1 = [OOColor colorWithRed:r1 green:g1 blue:b1 alpha:1.0]; *ioColor2 = [OOColor colorWithRed:r2 green:g2 blue:b2 alpha:1.0]; } else { OOLogWARN(@"sky.fromDict", @"could not interpret \"%@\" as two RGB colours (must be six numbers).", string); } } colorDesc = [dictionary objectForKey:@"sky_color_1"]; if (colorDesc != nil) { color = [[OOColor colorWithDescription:colorDesc] premultipliedColor]; if (color != nil) *ioColor1 = color; else OOLogWARN(@"sky.fromDict", @"could not interpret \"%@\" as a colour.", colorDesc); } colorDesc = [dictionary objectForKey:@"sky_color_2"]; if (colorDesc != nil) { color = [[OOColor colorWithDescription:colorDesc] premultipliedColor]; if (color != nil) *ioColor2 = color; else OOLogWARN(@"sky.fromDict", @"could not interpret \"%@\" as a colour.", colorDesc); } colorDesc = [dictionary objectForKey:@"nebula_color_1"]; if (colorDesc != nil) { color = [[OOColor colorWithDescription:colorDesc] premultipliedColor]; if (color != nil) { *ioColor3 = color; nebulaSet = YES; } else OOLogWARN(@"sky.fromDict", @"could not interpret \"%@\" as a colour.", colorDesc); } else { colorDesc = [dictionary objectForKey:@"sky_color_1"]; if (colorDesc != nil) { color = [[OOColor colorWithDescription:colorDesc] premultipliedColor]; if (color != nil) *ioColor3 = color; else OOLogWARN(@"sky.fromDict", @"could not interpret \"%@\" as a colour.", colorDesc); } } colorDesc = [dictionary objectForKey:@"nebula_color_2"]; if (colorDesc != nil) { color = [[OOColor colorWithDescription:colorDesc] premultipliedColor]; if (color != nil) { *ioColor4 = color; nebulaSet = YES; } else OOLogWARN(@"sky.fromDict", @"could not interpret \"%@\" as a colour.", colorDesc); } else { colorDesc = [dictionary objectForKey:@"sky_color_2"]; if (colorDesc != nil) { color = [[OOColor colorWithDescription:colorDesc] premultipliedColor]; if (color != nil) *ioColor4 = color; else OOLogWARN(@"sky.fromDict", @"could not interpret \"%@\" as a colour.", colorDesc); } } return nebulaSet; } @end oolite-1.82/src/Core/Entities/StationEntity.h000066400000000000000000000152321256642440500212040ustar00rootroot00000000000000/* StationEntity.h ShipEntity subclass representing a space station or dockable ship. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "ShipEntity.h" #import "OOJSInterfaceDefinition.h" #import "Universe.h" @class OOWeakSet; typedef enum { STATION_ALERT_LEVEL_GREEN = ALERT_CONDITION_GREEN, STATION_ALERT_LEVEL_YELLOW = ALERT_CONDITION_YELLOW, STATION_ALERT_LEVEL_RED = ALERT_CONDITION_RED } OOStationAlertLevel; #define STATION_MAX_POLICE 8 #define STATION_DELAY_BETWEEN_LAUNCHES 6.0 #define STATION_LAUNCH_RETRY_INTERVAL 2.0 #define MAX_DOCKING_STAGES 16 #define DOCKING_CLEARANCE_WINDOW 126.0 @interface StationEntity: ShipEntity { @private OOWeakSet *_shipsOnHold; DockEntity *player_reserved_dock; double last_launch_time; double approach_spacing; OOStationAlertLevel alertLevel; unsigned max_police; // max no. of police ships allowed unsigned max_defense_ships; // max no. of defense ships allowed unsigned defenders_launched; unsigned max_scavengers; // max no. of scavenger ships allowed unsigned scavengers_launched; OOTechLevelID equivalentTechLevel; float equipmentPriceFactor; Vector port_dimensions; double port_radius; unsigned no_docking_while_launching: 1, hasNPCTraffic: 1; BOOL hasPatrolShips; OOUniversalID planet; NSString *allegiance; OOCommodityMarket *localMarket; OOCargoQuantity marketCapacity; NSArray *marketDefinition; NSString *marketScriptName; // NSMutableArray *localPassengers; // NSMutableArray *localContracts; NSMutableArray *localShipyard; NSMutableDictionary *localInterfaces; unsigned docked_shuttles; double last_shuttle_launch_time; double shuttle_launch_interval; unsigned docked_traders; double last_trader_launch_time; double trader_launch_interval; double last_patrol_report_time; double patrol_launch_interval; unsigned suppress_arrival_reports: 1, requiresDockingClearance: 1, interstellarUndockingAllowed: 1, allowsFastDocking: 1, allowsSaving: 1, allowsAutoDocking: 1, hasBreakPattern: 1, marketMonitored: 1, marketBroadcast: 1; } - (OOCargoQuantity) marketCapacity; - (NSArray *) marketDefinition; - (NSString *) marketScriptName; - (BOOL) marketMonitored; - (BOOL) marketBroadcast; - (OOCreditsQuantity) legalStatusOfManifest:(OOCommodityMarket *)manifest export:(BOOL)export; - (OOCommodityMarket *) localMarket; - (void) setLocalMarket:(NSArray *)market; - (NSDictionary *) localMarketForScripting; - (void) setPrice:(NSUInteger) price forCommodity:(OOCommodityType) commodity; - (void) setQuantity:(NSUInteger) quantity forCommodity:(OOCommodityType) commodity; /*- (NSMutableArray *) localPassengers; - (void) setLocalPassengers:(NSArray *)market; - (NSMutableArray *) localContracts; - (void) setLocalContracts:(NSArray *)market; */ - (NSMutableArray *) localShipyard; - (void) setLocalShipyard:(NSArray *)market; - (NSMutableDictionary *) localInterfaces; - (void) setInterfaceDefinition:(OOJSInterfaceDefinition *)definition forKey:(NSString *)key; - (OOCommodityMarket *) initialiseLocalMarket; - (OOTechLevelID) equivalentTechLevel; - (void) setEquivalentTechLevel:(OOTechLevelID)value; - (NSEnumerator *) dockSubEntityEnumerator; - (Vector) virtualPortDimensions; - (DockEntity*) playerReservedDock; - (HPVector) beaconPosition; - (float) equipmentPriceFactor; - (void) setPlanet:(OOPlanetEntity *)planet; - (OOPlanetEntity *) planet; - (void) setAllegiance:(NSString *)newAllegiance; - (NSString *)allegiance; - (unsigned) countOfDockedContractors; - (unsigned) countOfDockedPolice; - (unsigned) countOfDockedDefenders; - (void) sanityCheckShipsOnApproach; - (void) autoDockShipsOnApproach; - (Vector) portUpVectorForShip:(ShipEntity *)ship; - (NSDictionary *) dockingInstructionsForShip:(ShipEntity *)ship; - (BOOL) shipIsInDockingCorridor:(ShipEntity *)ship; - (BOOL) dockingCorridorIsEmpty; - (void) clearDockingCorridor; - (void) clear; - (void) abortAllDockings; - (void) abortDockingForShip:(ShipEntity *)ship; - (BOOL) hasMultipleDocks; - (BOOL) hasClearDock; - (BOOL) hasLaunchDock; - (DockEntity *) selectDockForDocking; - (unsigned) currentlyInLaunchingQueues; - (unsigned) currentlyInDockingQueues; - (void) launchShip:(ShipEntity *)ship; - (ShipEntity *) launchIndependentShip:(NSString *)role; - (void) noteDockedShip:(ShipEntity *)ship; - (BOOL) interstellarUndockingAllowed; - (BOOL) hasNPCTraffic; - (void) setHasNPCTraffic:(BOOL)flag; - (OOStationAlertLevel) alertLevel; - (void) setAlertLevel:(OOStationAlertLevel)level signallingScript:(BOOL)signallingScript; ////////////////////////////////////////////////////////////// AI methods... - (void) increaseAlertLevel; - (void) decreaseAlertLevel; - (NSArray *) launchPolice; - (ShipEntity *) launchDefenseShip; - (ShipEntity *) launchScavenger; - (ShipEntity *) launchMiner; /**Lazygun** added the following line*/ - (ShipEntity *) launchPirateShip; - (ShipEntity *) launchShuttle; - (ShipEntity *) launchEscort; - (ShipEntity *) launchPatrol; - (void) launchShipWithRole:(NSString *)role; - (void) acceptPatrolReportFrom:(ShipEntity *)patrol_ship; - (NSString *) acceptDockingClearanceRequestFrom:(ShipEntity *)other; - (BOOL) requiresDockingClearance; - (void) setRequiresDockingClearance:(BOOL)newValue; - (BOOL) allowsFastDocking; - (void) setAllowsFastDocking:(BOOL)newValue; - (BOOL) allowsAutoDocking; - (void) setAllowsAutoDocking:(BOOL)newValue; - (BOOL) allowsSaving; // no setting this after station creation - (NSString *) marketOverrideName; - (BOOL) isRotatingStation; - (BOOL) hasShipyard; - (BOOL) suppressArrivalReports; - (void) setSuppressArrivalReports:(BOOL)newValue; - (BOOL) hasBreakPattern; - (void) setHasBreakPattern:(BOOL)newValue; @end NSDictionary *OOMakeDockingInstructions(StationEntity *station, HPVector coords, float speed, float range, NSString *ai_message, NSString *comms_message, BOOL match_rotation, int docking_stage); oolite-1.82/src/Core/Entities/StationEntity.m000066400000000000000000002016661256642440500212210ustar00rootroot00000000000000/* StationEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "StationEntity.h" #import "DockEntity.h" #import "ShipEntityAI.h" #import "OOCollectionExtractors.h" #import "OOStringParsing.h" #import "OOFilteringEnumerator.h" #import "Universe.h" #import "GameController.h" #import "HeadUpDisplay.h" #import "OOConstToString.h" #import "PlayerEntityLegacyScriptEngine.h" #import "OOLegacyScriptWhitelist.h" #import "OOPlanetEntity.h" #import "OOShipGroup.h" #import "OOQuiriumCascadeEntity.h" #import "AI.h" #import "OOCharacter.h" #import "OOJSScript.h" #import "OODebugGLDrawing.h" #import "OODebugFlags.h" #import "OODebugStandards.h" #import "OOWeakSet.h" @interface StationEntity (OOPrivate) - (BOOL) fitsInDock:(ShipEntity *)ship; - (void) pullInShipIfPermitted:(ShipEntity *)ship; - (void) addShipToStationCount:(ShipEntity *)ship; - (void) addShipToLaunchQueue:(ShipEntity *)ship withPriority:(BOOL)priority; - (unsigned) countOfShipsInLaunchQueueWithPrimaryRole:(NSString *)role; - (NSDictionary *) holdPositionInstructionForShip:(ShipEntity *)ship; @end #ifndef NDEBUG @interface StationEntity (mwDebug) - (NSArray *) dbgGetShipsOnApproach; - (NSArray *) dbgGetIdLocks; - (NSString *) dbgDumpIdLocks; @end #endif @implementation StationEntity /* Override ShipEntity: stations of CLASS_ROCK or CLASS_CARGO are not automatically unpiloted. */ - (BOOL)isUnpiloted { return [self isExplicitlyUnpiloted] || [self isHulk]; } - (OOTechLevelID) equivalentTechLevel { return equivalentTechLevel; } - (void) setEquivalentTechLevel:(OOTechLevelID) value { equivalentTechLevel = value; } - (Vector) virtualPortDimensions { return port_dimensions; } - (DockEntity*) playerReservedDock { return player_reserved_dock; } - (HPVector) beaconPosition { double buoy_distance = 10000.0; // distance from station entrance Vector v_f = vector_forward_from_quaternion([self orientation]); HPVector result = HPvector_add([self position], vectorToHPVector(vector_multiply_scalar(v_f, buoy_distance))); return result; } - (float) equipmentPriceFactor { return equipmentPriceFactor; } - (OOCargoQuantity) marketCapacity { return marketCapacity; } - (NSArray *) marketDefinition { return marketDefinition; } - (NSString *) marketScriptName { return marketScriptName; } - (BOOL) marketMonitored { if (self == [UNIVERSE station]) { return YES; } return marketMonitored; } - (BOOL) marketBroadcast { if (self == [UNIVERSE station]) { return YES; } return marketBroadcast; } - (OOCreditsQuantity) legalStatusOfManifest:(OOCommodityMarket *)manifest export:(BOOL)export { OOCreditsQuantity penalty, status = 0; OOCommodityMarket *market = [self localMarket]; OOCommodityType good = nil; foreach (good, [market goods]) { if (export) { penalty = [market exportLegalityForGood:good]; } else { penalty = [market importLegalityForGood:good]; } status += penalty * [manifest quantityForGood:good]; } return status; } - (OOCommodityMarket *) localMarket { if (self == [UNIVERSE station]) { // main stations use the system market // just return a reference return [UNIVERSE commodityMarket]; } if (!localMarket) { [self initialiseLocalMarket]; } return localMarket; } - (void) setLocalMarket:(NSArray *) some_market { [[self localMarket] loadStationAmounts:some_market]; } - (NSDictionary *) localMarketForScripting { return [[self localMarket] dictionaryForScripting]; } - (void) setPrice:(NSUInteger)price forCommodity:(OOCommodityType)commodity { [[self localMarket] setPrice:price forGood:commodity]; } - (void) setQuantity:(NSUInteger)quantity forCommodity:(OOCommodityType)commodity { [[self localMarket] setQuantity:quantity forGood:commodity]; } - (NSMutableArray *) localShipyard { return localShipyard; } - (void) setLocalShipyard:(NSArray *) some_market { if (localShipyard) [localShipyard release]; localShipyard = [[NSMutableArray alloc] initWithArray:some_market]; } - (NSMutableDictionary *) localInterfaces { return localInterfaces; } - (void) setInterfaceDefinition:(OOJSInterfaceDefinition *)definition forKey:(NSString *)key { if (definition == nil) { [localInterfaces removeObjectForKey:key]; } else { [localInterfaces setObject:definition forKey:key]; } } - (OOCommodityMarket *) initialiseLocalMarket { DESTROY(localMarket); localMarket = [[[UNIVERSE commodities] generateMarketForStation:self] retain]; return localMarket; } - (void) setPlanet:(OOPlanetEntity *)planet_entity { if (planet_entity) planet = [planet_entity universalID]; else planet = NO_TARGET; } - (OOPlanetEntity *) planet { return [UNIVERSE entityForUniversalID:planet]; } - (unsigned) countOfDockedContractors { return max_scavengers > scavengers_launched ? max_scavengers - scavengers_launched : 0; } - (unsigned) countOfDockedPolice { return max_police > defenders_launched ? max_police - defenders_launched : 0; } - (unsigned) countOfDockedDefenders { return max_defense_ships > defenders_launched ? max_defense_ships - defenders_launched : 0; } - (NSEnumerator *)dockSubEntityEnumerator { return [[self subEntities] objectEnumeratorFilteredWithSelector:@selector(isDock)]; } - (void) sanityCheckShipsOnApproach { NSEnumerator *subEnum = nil; DockEntity* sub = nil; unsigned soa = 0; for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { soa += [sub pruneAndCountShipsOnApproach]; } if (soa == 0) { // if all docks have no ships on approach [shipAI message:@"DOCKING_COMPLETE"]; [self doScriptEvent:OOJSID("stationDockingQueuesAreEmpty")]; } } // only used by player - everything else ends up in a Dock's launch queue - (void) launchShip:(ShipEntity *)ship { NSEnumerator *subEnum = nil; DockEntity *sub = nil; // try to find an unused dock first for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { if ([sub allowsLaunching] && [sub countOfShipsInLaunchQueue] == 0) { [sub launchShip:ship]; return; } } // otherwise any launchable dock will do for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { if ([sub allowsLaunching]) { [sub launchShip:ship]; return; } } // ship has no launch docks specified; just use the last one if (sub != nil) { [sub launchShip:ship]; return; } // guaranteed to always be a dock as virtual dock will suffice } // Exposed to AI - (void) abortAllDockings { NSEnumerator *subEnum = nil; DockEntity *sub = nil; for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { [sub abortAllDockings]; } [_shipsOnHold makeObjectsPerformSelector:@selector(sendAIMessage:) withObject:@"DOCKING_ABORTED"]; NSEnumerator *holdEnum = nil; ShipEntity *hold = nil; for (holdEnum = [_shipsOnHold objectEnumerator]; (hold = [holdEnum nextObject]); ) { [hold doScriptEvent:OOJSID("stationWithdrewDockingClearance")]; } PlayerEntity *player = PLAYER; if ([player getTargetDockStation] == self && [player getDockingClearanceStatus] >= DOCKING_CLEARANCE_STATUS_REQUESTED) { // then docking clearance is requested but hasn't been cancelled // yet by a DockEntity [self sendExpandedMessage:@"[station-docking-clearance-abort-cancelled]" toShip:player]; [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE]; [player doScriptEvent:OOJSID("stationWithdrewDockingClearance")]; } [_shipsOnHold removeAllObjects]; [shipAI message:@"DOCKING_COMPLETE"]; [self doScriptEvent:OOJSID("stationDockingQueuesAreEmpty")]; } - (void) autoDockShipsOnHold { NSEnumerator *onHoldEnum = [_shipsOnHold objectEnumerator]; ShipEntity *ship = nil; while ((ship = [onHoldEnum nextObject])) { [self pullInShipIfPermitted:ship]; } [_shipsOnHold removeAllObjects]; } - (void) autoDockShipsOnApproach { NSEnumerator *subEnum = nil; DockEntity *sub = nil; for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { [sub autoDockShipsOnApproach]; } [self autoDockShipsOnHold]; [shipAI message:@"DOCKING_COMPLETE"]; [self doScriptEvent:OOJSID("stationDockingQueuesAreEmpty")]; } - (Vector) portUpVectorForShip:(ShipEntity*) ship { NSEnumerator *subEnum = nil; DockEntity *sub = nil; for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { if ([sub shipIsInDockingQueue:ship]) { return [sub portUpVectorForShipsBoundingBox:[ship totalBoundingBox]]; } } return kZeroVector; } NSDictionary *OOMakeDockingInstructions(StationEntity *station, HPVector coords, float speed, float range, NSString *ai_message, NSString *comms_message, BOOL match_rotation, int docking_stage) { NSMutableDictionary *acc = [NSMutableDictionary dictionaryWithCapacity:8]; [acc oo_setHPVector:coords forKey:@"destination"]; [acc oo_setFloat:speed forKey:@"speed"]; [acc oo_setFloat:range forKey:@"range"]; [acc setObject:[[station weakRetain] autorelease] forKey:@"station"]; [acc oo_setBool:match_rotation forKey:@"match_rotation"]; [acc oo_setInteger:docking_stage forKey:@"docking_stage"]; if (ai_message) { [acc setObject:ai_message forKey:@"ai_message"]; } if (comms_message) { [acc setObject:comms_message forKey:@"comms_message"]; } return [NSDictionary dictionaryWithDictionary:acc]; } // this method does initial traffic control, before passing the ship // to an appropriate dock for docking coordinates and instructions. // used for NPCs, and the player when they use the docking computer - (NSDictionary *) dockingInstructionsForShip:(ShipEntity *) ship { if (ship == nil) return nil; [self doScriptEvent:OOJSID("stationReceivedDockingRequest") withArgument:ship]; if ([ship isPlayer]) { player_reserved_dock = nil; // clear any dock reservation for manual docking } if ([ship isPlayer] && [ship legalStatus] > 50) // note: non-player fugitives dock as normal { // refuse docking to the fugitive player return OOMakeDockingInstructions(self, [ship position], 0, 100, @"DOCKING_REFUSED", @"[station-docking-refused-to-fugitive]", NO, -1); } if (magnitude2(velocity) > 1.0 || fabs(flightPitch) > 0.01 || fabs(flightYaw) > 0.01) { // no docking while station is moving, pitching or yawing return [self holdPositionInstructionForShip:ship]; } PlayerEntity *player = PLAYER; BOOL player_is_ahead = (![ship isPlayer] && [player getDockingClearanceStatus] == DOCKING_CLEARANCE_STATUS_REQUESTED && (self == [player getTargetDockStation])); NSEnumerator *subEnum = nil; DockEntity *chosenDock = nil; NSString *docking = nil; DockEntity *sub = nil; NSUInteger queue = 100; BOOL alldockstoosmall = YES; for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { if ([sub shipIsInDockingQueue:ship]) { // if already claimed a docking queue, use that one chosenDock = sub; alldockstoosmall = NO; break; } if (player_is_ahead) { // can't allocate a new queue while player is manually docking continue; } if (sub != player_reserved_dock || [ship isPlayer]) { docking = [sub canAcceptShipForDocking:ship]; if ([docking isEqualToString:@"DOCK_CLOSED"]) { JSContext *context = OOJSAcquireContext(); jsval rval = JSVAL_VOID; jsval args[] = { OOJSValueFromNativeObject(context, sub), OOJSValueFromNativeObject(context, ship) }; JSBool tempreject = NO; BOOL OK = [[self script] callMethod:OOJSID("willOpenDockingPortFor") inContext:context withArguments:args count:2 result:&rval]; if (OK) OK = JS_ValueToBoolean(context, rval, &tempreject); if (!OK) tempreject = NO; // default to permreject if (tempreject) { docking = @"TRY_AGAIN_LATER"; } else { docking = @"TOO_BIG_TO_DOCK"; } OOJSRelinquishContext(context); } if ([docking isEqualToString:@"DOCKING_POSSIBLE"] && [sub countOfShipsInDockingQueue] < queue) { // try to select the dock with the fewest ships already enqueued chosenDock = sub; queue = [sub countOfShipsInDockingQueue]; alldockstoosmall = NO; } else if (![docking isEqualToString:@"TOO_BIG_TO_DOCK"]) { alldockstoosmall = NO; } } else { alldockstoosmall = NO; } } if (chosenDock == nil) { if (player_is_ahead || ([docking isEqualToString:@"TOO_BIG_TO_DOCK"] && !alldockstoosmall) || docking == nil) { // either player is manually docking and we can't allocate new docks, // or the last dock was too small, and there may be an acceptable one // not tested yet or returning TRY_AGAIN_LATER docking = @"TRY_AGAIN_LATER"; } // no docks accept this ship (or the player is blocking them) return OOMakeDockingInstructions(self, [ship position], 200, 100, docking, nil, NO, -1); } // rolling is okay for some if (fabs(flightRoll) > 0.01 && [chosenDock isOffCentre]) { return [self holdPositionInstructionForShip:ship]; } // we made it through holding! [_shipsOnHold removeObject:ship]; [shipAI reactToMessage:@"DOCKING_REQUESTED" context:@"requestDockingCoordinates"]; // react to the request [self doScriptEvent:OOJSID("stationAcceptedDockingRequest") withArgument:ship]; return [chosenDock dockingInstructionsForShip:ship]; } - (NSDictionary *)holdPositionInstructionForShip:(ShipEntity *)ship { if (![_shipsOnHold containsObject:ship]) { [self sendExpandedMessage:@"[station-acknowledges-hold-position]" toShip:ship]; [_shipsOnHold addObject:ship]; } return OOMakeDockingInstructions(self, [ship position], 0, 100, @"HOLD_POSITION", nil, NO, -1); } - (void) abortDockingForShip:(ShipEntity *) ship { [ship sendAIMessage:@"DOCKING_ABORTED"]; [ship doScriptEvent:OOJSID("stationWithdrewDockingClearance")]; [_shipsOnHold removeObject:ship]; NSEnumerator *subEnum = nil; DockEntity *sub = nil; for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { [sub abortDockingForShip:ship]; } if ([ship isPlayer]) { player_reserved_dock = nil; } [self sanityCheckShipsOnApproach]; } //////////////////////////////////////////////// from superclass - (id)initWithKey:(NSString *)key definition:(NSDictionary *)dict { OOJS_PROFILE_ENTER self = [super initWithKey:key definition:dict]; if (self != nil) { isStation = YES; _shipsOnHold = [[OOWeakSet alloc] init]; hasBreakPattern = YES; localInterfaces = [[NSMutableDictionary alloc] init]; } return self; OOJS_PROFILE_EXIT } - (void) dealloc { DESTROY(_shipsOnHold); DESTROY(marketDefinition); DESTROY(marketScriptName); DESTROY(localMarket); DESTROY(allegiance); // DESTROY(localPassengers); // DESTROY(localContracts); DESTROY(localShipyard); DESTROY(localInterfaces); [super dealloc]; } - (BOOL) setUpShipFromDictionary:(NSDictionary *) dict { OOJS_PROFILE_ENTER isShip = YES; isStation = YES; alertLevel = STATION_ALERT_LEVEL_GREEN; port_radius = [dict oo_nonNegativeDoubleForKey:@"port_radius" defaultValue:500.0]; // port_dimensions is deprecated port_dimensions = make_vector(69, 69, 250); NSString *portDimensionsStr = [dict oo_stringForKey:@"port_dimensions"]; if (portDimensionsStr != nil) { OOStandardsDeprecated(@"The port_dimensions key is deprecated"); if (!OOEnforceStandards()) { NSArray* tokens = [portDimensionsStr componentsSeparatedByString:@"x"]; if ([tokens count] == 3) { port_dimensions = make_vector([[tokens objectAtIndex:0] floatValue], [[tokens objectAtIndex:1] floatValue], [[tokens objectAtIndex:2] floatValue]); } } } if (![super setUpShipFromDictionary:dict]) return NO; equivalentTechLevel = [dict oo_unsignedIntegerForKey:@"equivalent_tech_level" defaultValue:NSNotFound]; max_scavengers = [dict oo_unsignedIntForKey:@"max_scavengers" defaultValue:3]; max_defense_ships = [dict oo_unsignedIntForKey:@"max_defense_ships" defaultValue:3]; max_police = [dict oo_unsignedIntForKey:@"max_police" defaultValue:STATION_MAX_POLICE]; equipmentPriceFactor = [dict oo_nonNegativeFloatForKey:@"equipment_price_factor" defaultValue:1.0]; equipmentPriceFactor = fmax(equipmentPriceFactor, 0.5f); hasNPCTraffic = [dict oo_fuzzyBooleanForKey:@"has_npc_traffic" defaultValue:(maxFlightSpeed == 0)]; // carriers default to NO hasPatrolShips = [dict oo_fuzzyBooleanForKey:@"has_patrol_ships" defaultValue:NO]; suppress_arrival_reports = [dict oo_boolForKey:@"suppress_arrival_reports" defaultValue:NO]; [self setAllegiance:[dict oo_stringForKey:@"allegiance"]]; marketCapacity = [dict oo_unsignedIntegerForKey:@"market_capacity" defaultValue:MAIN_SYSTEM_MARKET_LIMIT]; marketDefinition = [[dict oo_arrayForKey:@"market_definition" defaultValue:nil] retain]; marketScriptName = [[dict oo_stringForKey:@"market_script" defaultValue:nil] retain]; marketMonitored = [dict oo_boolForKey:@"market_monitored" defaultValue:NO]; marketBroadcast = [dict oo_boolForKey:@"market_broadcast" defaultValue:YES]; // Non main stations may have requiresDockingClearance set to yes as a result of the code below, // but this variable should be irrelevant for them, as they do not make use of it anyway. requiresDockingClearance = [dict oo_boolForKey:@"requires_docking_clearance" defaultValue:[UNIVERSE dockingClearanceProtocolActive]]; allowsFastDocking = [dict oo_boolForKey:@"allows_fast_docking" defaultValue:NO]; allowsAutoDocking = [dict oo_boolForKey:@"allows_auto_docking" defaultValue:YES]; allowsSaving = [UNIVERSE deterministicPopulation]; interstellarUndockingAllowed = [dict oo_boolForKey:@"interstellar_undocking" defaultValue:NO]; double unitime = [UNIVERSE getTime]; if ([self hasNPCTraffic]) // removed the 'isRotatingStation' restriction. { docked_shuttles = ranrot_rand() & 3; // 0..3; shuttle_launch_interval = 15.0 * 60.0; // every 15 minutes last_shuttle_launch_time = unitime - (ranrot_rand() & 63) * shuttle_launch_interval / 60.0; docked_traders = 3 + (ranrot_rand() & 7); // 1..3; trader_launch_interval = 3600.0 / docked_traders; // every few minutes last_trader_launch_time = unitime + 60.0 - trader_launch_interval; // in one minute's time } else { docked_shuttles = 0; docked_traders = 0; // 1..3; } patrol_launch_interval = 300.0; // 5 minutes last_patrol_report_time = unitime - patrol_launch_interval; if ([self crew] == nil) { [self setSingleCrewWithRole:@"police"]; } if ([self group] == nil) { [self setGroup:[self stationGroup]]; } return YES; OOJS_PROFILE_EXIT } // used to set up a virtual dock if necessary - (BOOL) setUpSubEntities { if (![super setUpSubEntities]) { return NO; } NSEnumerator *subEnum = nil; #ifndef NDEBUG ShipEntity *subEntity = nil; for (subEnum = [self shipSubEntityEnumerator]; (subEntity = [subEnum nextObject]); ) { if ([subEntity isStation]) { OOLog(@"setup.ship.badType.subentities",@"Subentity %@ (%@) of station %@ is itself a StationEntity. This is an internal error - please report it. ",subEntity,[subEntity shipDataKey],[self displayName]); } } #endif // and now check for docks DockEntity *sub = nil; for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { return YES; } OOStandardsDeprecated([NSString stringWithFormat:@"No docks set up for %@",self]); OOLog(@"ship.setup.docks",@"No docks set up for %@, making virtual dock",self); // no real docks, make a virtual one NSMutableDictionary *virtualDockDict = [NSMutableDictionary dictionaryWithCapacity:10]; [virtualDockDict setObject:@"standard" forKey:@"type"]; [virtualDockDict setObject:@"oolite-dock-virtual" forKey:@"subentity_key"]; [virtualDockDict oo_setVector:make_vector(0,0,port_radius) forKey:@"position"]; [virtualDockDict oo_setQuaternion:kIdentityQuaternion forKey:@"orientation"]; [virtualDockDict oo_setBool:YES forKey:@"is_dock"]; [virtualDockDict setObject:@"the docking bay" forKey:@"dock_label"]; [virtualDockDict oo_setBool:YES forKey:@"allow_docking"]; [virtualDockDict oo_setBool:NO forKey:@"disallowed_docking_collides"]; [virtualDockDict oo_setBool:YES forKey:@"allow_launching"]; [virtualDockDict oo_setBool:YES forKey:@"_is_virtual_dock"]; if (![self setUpOneStandardSubentity:virtualDockDict asTurret:NO]) { return NO; } return YES; } - (BOOL) shipIsInDockingCorridor:(ShipEntity *)ship { if (![ship isShip]) return NO; if ([ship isPlayer] && [ship status] == STATUS_DEAD) return NO; NSEnumerator *subEnum = nil; DockEntity* sub = nil; for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { if ([sub shipIsInDockingCorridor:ship]) { return YES; } } return NO; } - (void) pullInShipIfPermitted:(ShipEntity *)ship { [ship enterDock:self]; // dock performs permitted checks } - (BOOL) dockingCorridorIsEmpty { if (!UNIVERSE) return NO; NSEnumerator *subEnum = nil; DockEntity* sub = nil; for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { if ([sub dockingCorridorIsEmpty]) { return YES; // if any are } } return NO; } - (void) clearDockingCorridor { if (!UNIVERSE) return; NSEnumerator *subEnum = nil; DockEntity* sub = nil; for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { [sub clearDockingCorridor]; } return; } - (void) update:(OOTimeDelta) delta_t { BOOL isRockHermit = (scanClass == CLASS_ROCK); BOOL isMainStation = (self == [UNIVERSE station]); double unitime = [UNIVERSE getTime]; if (!isMainStation && localMarket == nil) { [self initialiseLocalMarket]; } [super update:delta_t]; PlayerEntity *player = PLAYER; BOOL isDockingStation = (self == [player getTargetDockStation]); if (isDockingStation && [player status] == STATUS_IN_FLIGHT) { if ([player getDockingClearanceStatus] >= DOCKING_CLEARANCE_STATUS_GRANTED) { if (last_launch_time-30 < unitime && [player getDockingClearanceStatus] != DOCKING_CLEARANCE_STATUS_TIMING_OUT) { [self sendExpandedMessage:@"[station-docking-clearance-about-to-expire]" toShip:player]; [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_TIMING_OUT]; } else if (last_launch_time < unitime) { [self sendExpandedMessage:@"[station-docking-clearance-expired]" toShip:player]; [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE]; // Docking clearance for player has expired. if ([self currentlyInDockingQueues] == 0) { [[self getAI] message:@"DOCKING_COMPLETE"]; [self doScriptEvent:OOJSID("stationDockingQueuesAreEmpty")]; } player_reserved_dock = nil; } } else if ([player getDockingClearanceStatus] == DOCKING_CLEARANCE_STATUS_NOT_REQUIRED) { if (last_launch_time < unitime) { [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE]; if ([self currentlyInDockingQueues] == 0) { [[self getAI] message:@"DOCKING_COMPLETE"]; [self doScriptEvent:OOJSID("stationDockingQueuesAreEmpty")]; } } } else if ([player getDockingClearanceStatus] == DOCKING_CLEARANCE_STATUS_REQUESTED && [self hasClearDock]) { DockEntity *dock = [self selectDockForDocking]; last_launch_time = unitime + DOCKING_CLEARANCE_WINDOW; if ([self hasMultipleDocks]) { [self sendExpandedMessage:[NSString stringWithFormat: DESC(@"station-docking-clearance-granted-in-@-until-@"), [dock displayName], ClockToString([player clockTime] + DOCKING_CLEARANCE_WINDOW, NO)] toShip:player]; } else { [self sendExpandedMessage:[NSString stringWithFormat: DESC(@"station-docking-clearance-granted-until-@"), ClockToString([player clockTime] + DOCKING_CLEARANCE_WINDOW, NO)] toShip:player]; } player_reserved_dock = dock; [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_GRANTED]; } } if (approach_spacing > 0.0) { approach_spacing -= delta_t * 10.0; // reduce by 10 m/s if (approach_spacing < 0.0) approach_spacing = 0.0; } /* JSAI: JS-based AIs handle their own traffic either alone or * in conjunction with the system repopulator */ if (![self hasNewAI]) { // begin launch of shuttles, traders, patrols if ((docked_shuttles > 0)&&(!isRockHermit)) { if (unitime > last_shuttle_launch_time + shuttle_launch_interval) { if (([self hasNPCTraffic])&&(aegis_status != AEGIS_NONE)) { [self launchShuttle]; } last_shuttle_launch_time = unitime; } } if ((docked_traders > 0)&&(!isRockHermit)) { if (unitime > last_trader_launch_time + trader_launch_interval) { if ([self hasNPCTraffic]) { [self launchIndependentShip:@"trader"]; docked_traders--; } last_trader_launch_time = unitime; } } // testing patrols if (unitime > (last_patrol_report_time + patrol_launch_interval)) { if (!((isMainStation && [self hasNPCTraffic]) || hasPatrolShips) || [self launchPatrol] != nil) last_patrol_report_time = unitime; } } } - (void) clear { NSEnumerator *subEnum = nil; DockEntity* sub = nil; for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { [sub clear]; } [_shipsOnHold removeAllObjects]; } - (BOOL) hasMultipleDocks { NSEnumerator *subEnum = nil; DockEntity* sub = nil; unsigned docks = 0; for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { docks++; if (docks > 1) { return YES; } } return NO; } // is there a dock free for the player to dock manually? // not used for NPCs - (BOOL) hasClearDock { NSEnumerator *subEnum = nil; DockEntity* sub = nil; for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { if ([sub allowsDocking] && [sub countOfShipsInLaunchQueue] == 0 && [sub countOfShipsInDockingQueue] == 0) { if ([[sub canAcceptShipForDocking:PLAYER] isEqualToString:@"DOCKING_POSSIBLE"]) { return YES; } } } return NO; } - (BOOL) hasEligibleDock { NSEnumerator *subEnum = nil; DockEntity* sub = nil; for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { // TRY_AGAIN_LATER in this context means "ships launching now" if ([sub allowsDocking] && ([[sub canAcceptShipForDocking:PLAYER] isEqualToString:@"DOCKING_POSSIBLE"] || [[sub canAcceptShipForDocking:PLAYER] isEqualToString:@"TRY_AGAIN_LATER"])) { return YES; } } return NO; } // is there any dock which may launch ships? - (BOOL) hasLaunchDock { NSEnumerator *subEnum = nil; DockEntity* sub = nil; for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { if ([sub allowsLaunching]) { return YES; } } return NO; } // only used to pick a dock for the player - (DockEntity *) selectDockForDocking { NSEnumerator *subEnum = nil; DockEntity* sub = nil; for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { if ([sub allowsDocking] && [sub countOfShipsInLaunchQueue] == 0 && [sub countOfShipsInDockingQueue] == 0) { return sub; } } return nil; } - (void) addShipToLaunchQueue:(ShipEntity *)ship withPriority:(BOOL)priority { NSEnumerator *subEnum = nil; DockEntity *sub = nil; unsigned threshold = 0; // quickest launch if we assign ships to those bays with no incoming ships // and spread the ships evenly around those bays // much easier if the station has at least one launch-only dock while (threshold < 16) { for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { if (sub != player_reserved_dock) { if ([sub countOfShipsInDockingQueue] == 0) { if ([sub allowsLaunching] && [sub countOfShipsInLaunchQueue] <= threshold) { if ([sub allowsLaunchingOf:ship]) { [sub addShipToLaunchQueue:ship withPriority:priority]; return; } } } } } threshold++; } // if we get this far, all docks have at least some incoming traffic. // usually most efficient (since launching is far faster than docking) // to assign all ships to the *same* dock with the smallest incoming queue // rather than to try spreading them out across several queues // also stops escorts being launched before their mothership threshold = 0; while (threshold < 16) { for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { /* so this time as long as it allows launching only check * the docking queue size so long as enumerator order is * deterministic, this will assign every launch this * update to the same dock (edge case where new docking * ship appears in the middle, probably not a problem) */ if ([sub allowsLaunching] && [sub countOfShipsInDockingQueue] <= threshold) { if ([sub allowsLaunchingOf:ship]) { [sub addShipToLaunchQueue:ship withPriority:priority]; return; } } } threshold++; } OOLog(@"station.launchShip.failed", @"Cancelled launch for a %@ with role %@, as the %@ has too many ships in its launch queue(s) or no suitable launch docks.", [ship displayName], [ship primaryRole], [self displayName]); } - (unsigned) countOfShipsInLaunchQueueWithPrimaryRole:(NSString *)role { unsigned result = 0; NSEnumerator *subEnum = nil; DockEntity* sub = nil; for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { result += [sub countOfShipsInLaunchQueueWithPrimaryRole:role]; } return result; } - (BOOL) fitsInDock:(ShipEntity *) ship { if (![ship isShip]) return NO; NSEnumerator *subEnum = nil; DockEntity* sub = nil; for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { if ([sub allowsLaunchingOf:ship]) { return YES; } } OOLog(@"station.launchShip.failed", @"Cancelled launch for a %@ with role %@, as it is too large for the docking port of the %@.", [ship displayName], [ship primaryRole], self); return NO; } - (void) noteDockedShip:(ShipEntity *) ship { if (ship == nil) return; PlayerEntity *player = PLAYER; // set last launch time to avoid clashes with outgoing ships if ([player getDockingClearanceStatus] != DOCKING_CLEARANCE_STATUS_GRANTED) { // avoid interfering with docking clearance on another bay last_launch_time = [UNIVERSE getTime]; } [self addShipToStationCount: ship]; NSEnumerator *subEnum = nil; DockEntity* sub = nil; for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { [sub noteDockingForShip:ship]; } [self sanityCheckShipsOnApproach]; [self doScriptEvent:OOJSID("otherShipDocked") withArgument:ship]; BOOL isDockingStation = (self == [player getTargetDockStation]); if (isDockingStation && [player status] == STATUS_IN_FLIGHT && [player getDockingClearanceStatus] == DOCKING_CLEARANCE_STATUS_REQUESTED) { if (![self hasClearDock]) { // then say why if ([self currentlyInDockingQueues]) { [self sendExpandedMessage:[NSString stringWithFormat: DESC(@"station-docking-clearance-holding-d-ships-approaching"), [self currentlyInDockingQueues]+1] toShip:player]; } else if([self currentlyInLaunchingQueues]) { [self sendExpandedMessage:[NSString stringWithFormat: DESC(@"station-docking-clearance-holding-d-ships-departing"), [self currentlyInLaunchingQueues]+1] toShip:player]; } } } if ([ship isPlayer]) { player_reserved_dock = nil; } } - (void) addShipToStationCount:(ShipEntity *) ship { if ([ship isShuttle]) docked_shuttles++; else if ([ship isTrader] && ![ship isPlayer]) docked_traders++; else if (([ship isPolice] && ![ship isEscort]) || [ship hasPrimaryRole:@"defense_ship"]) { if (0 < defenders_launched) defenders_launched--; } else if ([ship hasPrimaryRole:@"scavenger"] || [ship hasPrimaryRole:@"miner"]) // treat miners and scavengers alike! { if (0 < scavengers_launched) scavengers_launched--; } } - (BOOL) interstellarUndockingAllowed { return interstellarUndockingAllowed; } - (BOOL)hasNPCTraffic { return hasNPCTraffic; } - (void)setHasNPCTraffic:(BOOL)flag { hasNPCTraffic = flag != NO; } - (BOOL) collideWithShip:(ShipEntity *)other { /* There used to be a [self abortAllDockings] here. Removed as there doesn't appear to be a good reason for it and it interferes with docking clearance. -- Micha 2010-06-10 Reformatted, Ahruman 2012-08-26 */ return [super collideWithShip:other]; } - (BOOL) hasHostileTarget { return [super hasHostileTarget] || ([self primaryTarget] != nil && ((alertLevel == STATION_ALERT_LEVEL_YELLOW) || (alertLevel == STATION_ALERT_LEVEL_RED))); } - (void) takeEnergyDamage:(double)amount from:(Entity *)ent becauseOf:(Entity *)other { // stations must ignore friendly fire, otherwise the defenders' AI gets stuck. BOOL isFriend = NO; OOShipGroup *group = [self group]; if ([other isShip] && group != nil) { OOShipGroup *otherGroup = [(ShipEntity *)other group]; isFriend = otherGroup == group || [otherGroup leader] == self; } // If this is the system's main station... if (self == [UNIVERSE station] && !isFriend) { //...get angry BOOL isEnergyMine = [ent isCascadeWeapon]; // JSAIs might ignore friendly fire from conventional weapons if ([self hasNewAI] || isEnergyMine) { unsigned b=isEnergyMine ? 96 : 64; if ([(ShipEntity*)other bounty] >= b) //already a hardened criminal? { b *= 1.5; //bigger bounty! } [(ShipEntity*)other markAsOffender:b withReason:kOOLegalStatusReasonAttackedMainStation]; [self setPrimaryAggressor:other]; [self setFoundTarget:other]; [self launchPolice]; } if (isEnergyMine) //don't blow up! { [self increaseAlertLevel]; [self respondToAttackFrom:ent becauseOf:other]; return; } } // Stop damage if main station & close to death! if (!isFriend && (self != [UNIVERSE station] || amount < energy) ) { // Handle damage like a ship. [super takeEnergyDamage:amount from:ent becauseOf:other]; } } - (void) adjustVelocity:(Vector) xVel { if (self != [UNIVERSE station]) [super adjustVelocity:xVel]; //dont get moved } - (void)takeScrapeDamage:(double)amount from:(Entity *)ent { // Stop damage if main station if (self != [UNIVERSE station]) [super takeScrapeDamage:amount from:ent]; } - (void) takeHeatDamage:(double)amount { // Stop damage if main station if (self != [UNIVERSE station]) [super takeHeatDamage:amount]; } - (NSString *) allegiance { return allegiance; } - (void) setAllegiance:(NSString *)newAllegiance { [allegiance release]; allegiance = [newAllegiance copy]; } - (OOStationAlertLevel) alertLevel { return alertLevel; } - (void) setAlertLevel:(OOStationAlertLevel)level signallingScript:(BOOL)signallingScript { if (level < STATION_ALERT_LEVEL_GREEN) level = STATION_ALERT_LEVEL_GREEN; if (level > STATION_ALERT_LEVEL_RED) level = STATION_ALERT_LEVEL_RED; if (alertLevel != level) { OOStationAlertLevel oldLevel = alertLevel; alertLevel = level; if (signallingScript) { ShipScriptEventNoCx(self, "alertConditionChanged", INT_TO_JSVAL(level), INT_TO_JSVAL(oldLevel)); } switch (level) { case STATION_ALERT_LEVEL_GREEN: [shipAI reactToMessage:@"GREEN_ALERT" context:nil]; break; case STATION_ALERT_LEVEL_YELLOW: [shipAI reactToMessage:@"YELLOW_ALERT" context:nil]; break; case STATION_ALERT_LEVEL_RED: [shipAI reactToMessage:@"RED_ALERT" context:nil]; break; } } } // Exposed to AI - (ShipEntity *) launchIndependentShip:(NSString*) role { if (![self hasLaunchDock]) { OOLog(@"station.launchShip.impossible", @"Cancelled launch for a ship with role %@, as the %@ has no launch docks.", role, [self displayName]); return nil; } BOOL trader = [role isEqualToString:@"trader"]; BOOL sunskimmer = ([role isEqualToString:@"sunskim-trader"]); ShipEntity *ship = nil; if((trader && (randf() < 0.1)) || sunskimmer) { ship = [UNIVERSE newShipWithRole:@"sunskim-trader"]; sunskimmer = true; trader = true; role = @"trader"; // make sure also sunskimmers get trader role. } else { ship = [UNIVERSE newShipWithRole:role]; } if (![self fitsInDock:ship]) { [ship release]; return nil; } if (ship) { if (![ship crew]) { [ship setSingleCrewWithRole:role]; } [ship setPrimaryRole:role]; if(trader || ship->scanClass == CLASS_NOT_SET) [ship setScanClass: CLASS_NEUTRAL]; // keep defined scanclasses for non-traders. if (trader) { [ship setBounty:0 withReason:kOOLegalStatusReasonSetup]; [ship setCargoFlag:CARGO_FLAG_FULL_PLENTIFUL]; if (sunskimmer) { [ship setFuel:(Ranrot()&31)]; [UNIVERSE makeSunSkimmer:ship andSetAI:YES]; } else { // JSAI: not needed - oolite-traderAI.js handles exiting if full fuel and plentiful cargo // [ship switchAITo:@"exitingTraderAI.plist"]; if([ship fuel] == 0) [ship setFuel:70]; // if ([ship hasRole:@"sunskim-trader"]) [UNIVERSE makeSunSkimmer:ship andSetAI:NO]; } } [self addShipToLaunchQueue:ship withPriority:NO]; OOShipGroup *escortGroup = [ship escortGroup]; if ([ship group] == nil) [ship setGroup:escortGroup]; // Eric: Escorts are defined both as _group and as _escortGroup because friendly attacks are only handled within _group. [escortGroup setLeader:ship]; // add escorts to the trader unsigned escorts = [ship pendingEscortCount]; if(escorts > 0) { [ship setOwner:self]; // makes escorts get added to station launch queue [ship setUpEscorts]; [ship setOwner:ship]; } [ship setPendingEscortCount:0]; [ship autorelease]; } return ship; } //////////////////////////////////////////////// extra AI routines // Exposed to AI - (void) increaseAlertLevel { [self setAlertLevel:[self alertLevel] + 1 signallingScript:YES]; } // Exposed to AI - (void) decreaseAlertLevel { [self setAlertLevel:[self alertLevel] - 1 signallingScript:YES]; } // Exposed to AI - (NSArray *) launchPolice { if (![self hasLaunchDock]) { OOLog(@"station.launchShip.impossible", @"Cancelled launch for a police ship, as the %@ has no launch docks.", [self displayName]); return [NSArray array]; } OOUniversalID police_target = [[self primaryTarget] universalID]; unsigned i; NSMutableArray *result = nil; OOTechLevelID techlevel = [self equivalentTechLevel]; if (techlevel == NSNotFound) techlevel = 6; result = [NSMutableArray arrayWithCapacity:4]; for (i = 0; (i < 4)&&(defenders_launched < max_police) ; i++) { ShipEntity *police_ship = nil; if (![UNIVERSE entityForUniversalID:police_target]) { [self noteLostTarget]; return [NSArray array]; } /* this is more likely to give interceptors than the * equivalent populator function: save them for defense * ships */ if ((Ranrot() & 3) + 9 < techlevel) { police_ship = [UNIVERSE newShipWithRole:@"interceptor"]; // retain count = 1 } else { police_ship = [UNIVERSE newShipWithRole:@"police"]; // retain count = 1 } if (police_ship && [self fitsInDock:police_ship]) { if (![police_ship crew]) { [police_ship setSingleCrewWithRole:@"police"]; } [police_ship setGroup:[self stationGroup]]; // who's your Daddy [police_ship setPrimaryRole:@"police"]; [police_ship addTarget:[UNIVERSE entityForUniversalID:police_target]]; if ([police_ship scanClass] == CLASS_NOT_SET) [police_ship setScanClass: CLASS_POLICE]; [police_ship setBounty:0 withReason:kOOLegalStatusReasonSetup]; if ([police_ship heatInsulation] < [self heatInsulation]) [police_ship setHeatInsulation:[self heatInsulation]]; [police_ship switchAITo:@"oolite-defenseShipAI.js"]; [self addShipToLaunchQueue:police_ship withPriority:YES]; defenders_launched++; [result addObject:police_ship]; } [police_ship autorelease]; } [self abortAllDockings]; return result; } // Exposed to AI - (ShipEntity *) launchDefenseShip { if (![self hasLaunchDock]) { OOLog(@"station.launchShip.impossible", @"Cancelled launch for a defense ship, as the %@ has no launch docks.", [self displayName]); return nil; } OOUniversalID defense_target = [[self primaryTarget] universalID]; ShipEntity *defense_ship = nil; NSString *defense_ship_key = nil, *defense_ship_role = nil, *default_defense_ship_role = nil; NSString *defense_ship_ai = @"oolite-defenseShipAI.js"; OOTechLevelID techlevel; techlevel = [self equivalentTechLevel]; if (techlevel == NSNotFound) techlevel = 6; if ((Ranrot() & 7) + 6 <= techlevel) default_defense_ship_role = @"interceptor"; else default_defense_ship_role = @"police"; if (scanClass == CLASS_ROCK) default_defense_ship_role = @"hermit-ship"; if (defenders_launched >= max_defense_ships) // shuttles are to rockhermits what police ships are to stations return nil; if (![UNIVERSE entityForUniversalID:defense_target]) { [self noteLostTarget]; return nil; } defense_ship_key = [shipinfoDictionary oo_stringForKey:@"defense_ship"]; if (defense_ship_key != nil) { defense_ship = [UNIVERSE newShipWithName:defense_ship_key]; } if (!defense_ship) { defense_ship_role = [shipinfoDictionary oo_stringForKey:@"defense_ship_role" defaultValue:default_defense_ship_role]; defense_ship = [UNIVERSE newShipWithRole:defense_ship_role]; } if (!defense_ship && default_defense_ship_role != defense_ship_role) defense_ship = [UNIVERSE newShipWithRole:default_defense_ship_role]; if (!defense_ship || ![self fitsInDock:defense_ship]) { [defense_ship release]; return nil; } if ([defense_ship isPolice] || [defense_ship hasPrimaryRole:@"hermit-ship"]) { [defense_ship switchAITo:defense_ship_ai]; } [defense_ship setPrimaryRole:@"defense_ship"]; defenders_launched++; if (![defense_ship crew]) { if ([defense_ship isPolice]) { [defense_ship setSingleCrewWithRole:@"police"]; } else { [defense_ship setSingleCrewWithRole:@"hunter"]; } } [defense_ship setOwner: self]; if ([self group] == nil) { [self setGroup:[self stationGroup]]; } [defense_ship setGroup:[self stationGroup]]; // who's your Daddy [defense_ship addTarget:[UNIVERSE entityForUniversalID:defense_target]]; if ((scanClass != CLASS_ROCK)&&(scanClass != CLASS_STATION)) { [defense_ship setScanClass: scanClass]; // same as self } else if ([defense_ship scanClass] == CLASS_NOT_SET) { [defense_ship setScanClass: CLASS_NEUTRAL]; } if ([defense_ship heatInsulation] < [self heatInsulation]) { [defense_ship setHeatInsulation:[self heatInsulation]]; } [self addShipToLaunchQueue:defense_ship withPriority:YES]; [defense_ship autorelease]; [self abortAllDockings]; return defense_ship; } // Exposed to AI - (ShipEntity *) launchScavenger { if (![self hasLaunchDock]) { OOLog(@"station.launchShip.impossible", @"Cancelled launch for a scavenger ship, as the %@ has no launch docks.", [self displayName]); return nil; } ShipEntity *scavenger_ship; unsigned scavs = [UNIVERSE countShipsWithPrimaryRole:@"scavenger" inRange:SCANNER_MAX_RANGE ofEntity:self] + [self countOfShipsInLaunchQueueWithPrimaryRole:@"scavenger"]; if (scavs >= max_scavengers) return nil; if (scavengers_launched >= max_scavengers) return nil; scavenger_ship = [UNIVERSE newShipWithRole:@"scavenger"]; // retain count = 1 if (![self fitsInDock:scavenger_ship]) { [scavenger_ship release]; return nil; } if (scavenger_ship) { if (![scavenger_ship crew]) { [scavenger_ship setSingleCrewWithRole:@"miner"]; } scavengers_launched++; [scavenger_ship setScanClass: CLASS_NEUTRAL]; if ([scavenger_ship heatInsulation] < [self heatInsulation]) [scavenger_ship setHeatInsulation:[self heatInsulation]]; [scavenger_ship setGroup:[self stationGroup]]; // who's your Daddy -- FIXME: should we have a separate group for non-escort auxiliaires? [scavenger_ship switchAITo:@"oolite-scavengerAI.js"]; [self addShipToLaunchQueue:scavenger_ship withPriority:NO]; [scavenger_ship autorelease]; } return scavenger_ship; } // Exposed to AI - (ShipEntity *) launchMiner { if (![self hasLaunchDock]) { OOLog(@"station.launchShip.impossible", @"Cancelled launch for a miner ship, as the %@ has no launch docks.", [self displayName]); return nil; } ShipEntity *miner_ship; int n_miners = [UNIVERSE countShipsWithPrimaryRole:@"miner" inRange:SCANNER_MAX_RANGE ofEntity:self] + [self countOfShipsInLaunchQueueWithPrimaryRole:@"miner"]; if (n_miners >= 1) // just the one return nil; // count miners as scavengers... if (scavengers_launched >= max_scavengers) return nil; miner_ship = [UNIVERSE newShipWithRole:@"miner"]; // retain count = 1 if (![self fitsInDock:miner_ship]) { [miner_ship release]; return nil; } if (miner_ship) { if (![miner_ship crew]) { [miner_ship setSingleCrewWithRole:@"miner"]; } scavengers_launched++; [miner_ship setScanClass:CLASS_NEUTRAL]; if ([miner_ship heatInsulation] < [self heatInsulation]) [miner_ship setHeatInsulation:[self heatInsulation]]; [miner_ship setGroup:[self stationGroup]]; // who's your Daddy -- FIXME: should we have a separate group for non-escort auxiliaires? [miner_ship switchAITo:@"oolite-scavengerAI.js"]; [self addShipToLaunchQueue:miner_ship withPriority:NO]; [miner_ship autorelease]; } return miner_ship; } /**Lazygun** added the following method. A complete rip-off of launchDefenseShip. */ // Exposed to AI - (ShipEntity *) launchPirateShip { if ([self hasLaunchDock]) { OOLog(@"station.launchShip.impossible", @"Cancelled launch for a pirate ship, as the %@ has no launch docks.", [self displayName]); return nil; } //Pirate ships are launched from the same pool as defence ships. OOUniversalID defense_target = [[self primaryTarget] universalID]; ShipEntity *pirate_ship = nil; if (defenders_launched >= max_defense_ships) return nil; // shuttles are to rockhermits what police ships are to stations if (![UNIVERSE entityForUniversalID:defense_target]) { [self noteLostTarget]; return nil; } // Yep! The standard hermit defence ships, even if they're the aggressor. pirate_ship = [UNIVERSE newShipWithRole:@"pirate"]; // retain count = 1 // Nope, use standard pirates in a generic method. if (![self fitsInDock:pirate_ship]) { [pirate_ship release]; return nil; } if (pirate_ship) { if (![pirate_ship crew]) { [pirate_ship setSingleCrewWithRole:@"pirate"]; } defenders_launched++; // set the owner of the ship to the station so that it can check back for docking later [pirate_ship setOwner:self]; [pirate_ship setGroup:[self stationGroup]]; // who's your Daddy [pirate_ship setPrimaryRole:@"defense_ship"]; [pirate_ship addTarget:[UNIVERSE entityForUniversalID:defense_target]]; [pirate_ship setScanClass: CLASS_NEUTRAL]; if ([pirate_ship heatInsulation] < [self heatInsulation]) [pirate_ship setHeatInsulation:[self heatInsulation]]; //**Lazygun** added 30 Nov 04 to put a bounty on those pirates' heads. [pirate_ship setBounty: 10 + floor(randf() * 20) withReason:kOOLegalStatusReasonSetup]; // modified for variety [self addShipToLaunchQueue:pirate_ship withPriority:NO]; [pirate_ship autorelease]; [self abortAllDockings]; } return pirate_ship; } // Exposed to AI - (ShipEntity *) launchShuttle { if (![self hasLaunchDock]) { OOLog(@"station.launchShip.impossible", @"Cancelled launch for a shuttle ship, as the %@ has no launch docks.", [self displayName]); return nil; } ShipEntity *shuttle_ship; shuttle_ship = [UNIVERSE newShipWithRole:@"shuttle"]; // retain count = 1 if (![self fitsInDock:shuttle_ship]) { [shuttle_ship release]; return nil; } if (shuttle_ship) { if (![shuttle_ship crew]) { [shuttle_ship setSingleCrewWithRole:@"trader"]; } docked_shuttles--; [shuttle_ship setScanClass: CLASS_NEUTRAL]; [shuttle_ship setCargoFlag:CARGO_FLAG_FULL_SCARCE]; [shuttle_ship switchAITo:@"oolite-shuttleAI.js"]; [self addShipToLaunchQueue:shuttle_ship withPriority:NO]; [shuttle_ship autorelease]; } return shuttle_ship; } // Exposed to AI - (ShipEntity *) launchEscort { if (![self hasLaunchDock]) { OOLog(@"station.launchShip.impossible", @"Cancelled launch for an escort ship, as the %@ has no launch docks.", [self displayName]); return nil; } ShipEntity *escort_ship; escort_ship = [UNIVERSE newShipWithRole:@"escort"]; // retain count = 1 if (escort_ship && [self fitsInDock:escort_ship]) { if (![escort_ship crew]) { [escort_ship setSingleCrewWithRole:@"hunter"]; } [escort_ship setScanClass: CLASS_NEUTRAL]; [escort_ship setCargoFlag: CARGO_FLAG_FULL_PLENTIFUL]; [escort_ship switchAITo:@"oolite-escortAI.js"]; [self addShipToLaunchQueue:escort_ship withPriority:NO]; } [escort_ship release]; return escort_ship; } // Exposed to AI - (ShipEntity *) launchPatrol { if (![self hasLaunchDock]) { OOLog(@"station.launchShip.impossible", @"Cancelled launch for a patrol ship, as the %@ has no launch docks.", [self displayName]); return nil; } if (defenders_launched < max_police) { ShipEntity *patrol_ship = nil; OOTechLevelID techlevel; techlevel = [self equivalentTechLevel]; if (techlevel == NSNotFound) techlevel = 6; if ((Ranrot() & 7) + 6 <= techlevel) patrol_ship = [UNIVERSE newShipWithRole:@"interceptor"]; // retain count = 1 else patrol_ship = [UNIVERSE newShipWithRole:@"police"]; // retain count = 1 if (![self fitsInDock:patrol_ship]) { [patrol_ship release]; return nil; } if (patrol_ship) { if (![patrol_ship crew]) { [patrol_ship setSingleCrewWithRole:@"police"]; } defenders_launched++; [patrol_ship switchLightsOff]; if ([patrol_ship scanClass] == CLASS_NOT_SET) [patrol_ship setScanClass: CLASS_POLICE]; if ([patrol_ship heatInsulation] < [self heatInsulation]) [patrol_ship setHeatInsulation:[self heatInsulation]]; [patrol_ship setPrimaryRole:@"police-station-patrol"]; [patrol_ship setBounty:0 withReason:kOOLegalStatusReasonSetup]; [patrol_ship setGroup:[self stationGroup]]; // who's your Daddy [patrol_ship switchAITo:@"oolite-policeAI.js"]; [self addShipToLaunchQueue:patrol_ship withPriority:NO]; [self acceptPatrolReportFrom:patrol_ship]; [patrol_ship autorelease]; return patrol_ship; } } return nil; } // Exposed to AI - (void) launchShipWithRole:(NSString*) role { if (![self hasLaunchDock]) { OOLog(@"station.launchShip.impossible", @"Cancelled launch for a ship with role %@, as the %@ has no launch docks.", role, [self displayName]); return; } ShipEntity *ship = [UNIVERSE newShipWithRole: role]; // retain count = 1 if (ship && [self fitsInDock:ship]) { if (![ship crew]) { [ship setSingleCrewWithRole:role]; } if (ship->scanClass == CLASS_NOT_SET) [ship setScanClass: CLASS_NEUTRAL]; [ship setPrimaryRole:role]; [ship setGroup:[self stationGroup]]; // who's your Daddy [self addShipToLaunchQueue:ship withPriority:NO]; } [ship release]; } // Exposed to AI - (void) becomeExplosion { if (self == [UNIVERSE station]) return; // launch docked ships if possible PlayerEntity* player = PLAYER; if ((player)&&([player status] == STATUS_DOCKED || [player status] == STATUS_DOCKING)&&([player dockedStation] == self)) { // undock the player! [player leaveDock:self]; [UNIVERSE setViewDirection:VIEW_FORWARD]; [[UNIVERSE gameController] setMouseInteractionModeForFlight]; [player warnAboutHostiles]; // sound a klaxon } if (scanClass == CLASS_ROCK) // ie we're a rock hermit or similar { // set the role so that we break up into rocks! [self setPrimaryRole:@"asteroid"]; being_mined = YES; } // finally bite the bullet [super becomeExplosion]; } // Exposed to AI - (void) becomeEnergyBlast { if (self == [UNIVERSE station]) return; [super becomeEnergyBlast]; } - (void) becomeLargeExplosion:(double) factor { if (self == [UNIVERSE station]) return; [super becomeLargeExplosion:factor]; } - (void) acceptPatrolReportFrom:(ShipEntity*) patrol_ship { last_patrol_report_time = [UNIVERSE getTime]; } // used by player - "other" should always be a reference to the player // there are some checks in the function from possibly when this wasn't true? - (NSString *) acceptDockingClearanceRequestFrom:(ShipEntity *)other { NSString *result = nil; double timeNow = [UNIVERSE getTime]; PlayerEntity *player = PLAYER; [self doScriptEvent:OOJSID("stationReceivedDockingRequest") withArgument:other]; [UNIVERSE clearPreviousMessage]; [self sanityCheckShipsOnApproach]; // Docking clearance not required - clear it just in case it's been // set for another nearby station. if (![self requiresDockingClearance]) { // TODO: We're potentially cancelling docking at another station, so // ensure we clear the timer to allow NPC traffic. If we // don't, normal traffic will resume once the timer runs out. // No clearance is needed, but don't send friendly messages to hostile ships! if (!(([other isPlayer] && [other hasHostileTarget]) || (self == [UNIVERSE station] && [other bounty] > 50))) { [self sendExpandedMessage:@"[station-docking-clearance-not-required]" toShip:other]; } if ([other isPlayer]) { [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NOT_REQUIRED]; } [shipAI reactToMessage:@"DOCKING_REQUESTED" context:nil]; // react to the request [self doScriptEvent:OOJSID("stationAcceptedDockingRequest") withArgument:other]; last_launch_time = timeNow + DOCKING_CLEARANCE_WINDOW; result = @"DOCKING_CLEARANCE_NOT_REQUIRED"; } // Docking clearance already granted for this station - check for // time-out or cancellation (but only for the Player). if( result == nil && [other isPlayer] && self == [player getTargetDockStation]) { switch( [player getDockingClearanceStatus] ) { case DOCKING_CLEARANCE_STATUS_TIMING_OUT: if (!no_docking_while_launching) { last_launch_time = timeNow + DOCKING_CLEARANCE_WINDOW; [self sendExpandedMessage:[NSString stringWithFormat: DESC(@"station-docking-clearance-extended-until-@"), ClockToString([player clockTime] + DOCKING_CLEARANCE_WINDOW, NO)] toShip:other]; [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_GRANTED]; result = @"DOCKING_CLEARANCE_EXTENDED"; break; } // else, continue with canceling. case DOCKING_CLEARANCE_STATUS_REQUESTED: case DOCKING_CLEARANCE_STATUS_GRANTED: last_launch_time = timeNow; [self sendExpandedMessage:@"[station-docking-clearance-cancelled]" toShip:other]; [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE]; result = @"DOCKING_CLEARANCE_CANCELLED"; player_reserved_dock = nil; if ([self currentlyInDockingQueues] == 0) { [shipAI message:@"DOCKING_COMPLETE"]; [self doScriptEvent:OOJSID("stationDockingQueuesAreEmpty")]; } break; case DOCKING_CLEARANCE_STATUS_NONE: case DOCKING_CLEARANCE_STATUS_NOT_REQUIRED: break; } } // First we must set the status to REQUESTED to avoid problems when // switching docking targets - even if we later set it back to NONE. if (result == nil && [other isPlayer] && self != [player getTargetDockStation]) { player_reserved_dock = nil; // and clear any previously reserved dock [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_REQUESTED]; } // Deny docking for fugitives at the main station // TODO: Should this be another key in shipdata.plist and/or should this // apply to all stations? if (result == nil && self == [UNIVERSE station] && [other bounty] > 50) // do not grant docking clearance to fugitives { [self sendExpandedMessage:@"[station-docking-clearance-H-clearance-refused]" toShip:other]; if ([other isPlayer]) [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE]; result = @"DOCKING_CLEARANCE_DENIED_SHIP_FUGITIVE"; } if (result == nil && [other hasHostileTarget]) // do not grant docking clearance to hostile ships. { [self sendExpandedMessage:@"[station-docking-clearance-denied]" toShip:other]; if ([other isPlayer]) [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE]; result = @"DOCKING_CLEARANCE_DENIED_SHIP_HOSTILE"; } if (![self hasEligibleDock]) // make sure at least one dock could plausibly accept the player { if ([other isPlayer]) { [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE]; } [self sendExpandedMessage:@"[station-docking-clearance-denied-no-docks]" toShip:other]; result = @"DOCKING_CLEARANCE_DENIED_NO_DOCKS"; } else if (![self hasClearDock]) // skip check if at least one dock clear { // Put ship in queue if we've got incoming or outgoing traffic or // if the player is waiting for manual clearance and we are not // the player if (result == nil && (([self currentlyInDockingQueues] && last_launch_time < timeNow) || (![other isPlayer] && [player getDockingClearanceStatus] == DOCKING_CLEARANCE_STATUS_REQUESTED))) { [self sendExpandedMessage:[NSString stringWithFormat: DESC(@"station-docking-clearance-acknowledged-d-ships-approaching"), [self currentlyInDockingQueues]+1] toShip:other]; // No need to set status to REQUESTED as we've already done that earlier. result = @"DOCKING_CLEARANCE_DENIED_TRAFFIC_INBOUND"; } if (result == nil && [self currentlyInLaunchingQueues]) { [self sendExpandedMessage:[NSString stringWithFormat: DESC(@"station-docking-clearance-acknowledged-d-ships-departing"), [self currentlyInLaunchingQueues]+1] toShip:other]; // No need to set status to REQUESTED as we've already done that earlier. result = @"DOCKING_CLEARANCE_DENIED_TRAFFIC_OUTBOUND"; } if (result == nil) { // if this happens, the station has no docks which allow // docking, so deny clearance if ([other isPlayer]) { [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE]; } result = @"DOCKING_CLEARANCE_DENIED_NO_DOCKS"; // but can check to see if we'll open some for later. NSEnumerator *subEnum = nil; DockEntity* sub = nil; BOOL openLater = NO; for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { NSString *docking = [sub canAcceptShipForDocking:other]; if ([docking isEqualToString:@"DOCK_CLOSED"]) { JSContext *context = OOJSAcquireContext(); jsval rval = JSVAL_VOID; jsval args[] = { OOJSValueFromNativeObject(context, sub), OOJSValueFromNativeObject(context, other) }; JSBool tempreject = NO; BOOL OK = [[self script] callMethod:OOJSID("willOpenDockingPortFor") inContext:context withArguments:args count:2 result:&rval]; if (OK) OK = JS_ValueToBoolean(context, rval, &tempreject); if (!OK) tempreject = NO; // default to permreject if (tempreject) { openLater = YES; } OOJSRelinquishContext(context); } if (openLater) break; } if (openLater) { [self sendExpandedMessage:@"[station-docking-clearance-denied-no-docks-yet]" toShip:other]; } else { [self sendExpandedMessage:@"[station-docking-clearance-denied-no-docks]" toShip:other]; } } } // Ship has passed all checks - grant docking! if (result == nil) { last_launch_time = timeNow + DOCKING_CLEARANCE_WINDOW; if ([other isPlayer]) { [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_GRANTED]; player_reserved_dock = [self selectDockForDocking]; } if ([self hasMultipleDocks] && [other isPlayer]) { [self sendExpandedMessage:[NSString stringWithFormat: DESC(@"station-docking-clearance-granted-in-@-until-@"), [player_reserved_dock displayName], ClockToString([player clockTime] + DOCKING_CLEARANCE_WINDOW, NO)] toShip:other]; } else { [self sendExpandedMessage:[NSString stringWithFormat: DESC(@"station-docking-clearance-granted-until-@"), ClockToString([player clockTime] + DOCKING_CLEARANCE_WINDOW, NO)] toShip:other]; } result = @"DOCKING_CLEARANCE_GRANTED"; [shipAI reactToMessage:@"DOCKING_REQUESTED" context:nil]; // react to the request [self doScriptEvent:OOJSID("stationAcceptedDockingRequest") withArgument:other]; } return result; } - (unsigned) currentlyInDockingQueues { NSEnumerator *subEnum = nil; DockEntity* sub = nil; unsigned soa = 0; for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { soa += [sub countOfShipsInDockingQueue]; } soa += [_shipsOnHold count]; return soa; } - (unsigned) currentlyInLaunchingQueues { NSEnumerator *subEnum = nil; DockEntity* sub = nil; unsigned soa = 0; for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); ) { soa += [sub countOfShipsInLaunchQueue]; } return soa; } - (BOOL) requiresDockingClearance { return requiresDockingClearance; } - (void) setRequiresDockingClearance:(BOOL)newValue { requiresDockingClearance = !!newValue; // Ensure yes or no } - (BOOL) allowsFastDocking { return allowsFastDocking; } - (void) setAllowsFastDocking:(BOOL)newValue { allowsFastDocking = !!newValue; // Ensure yes or no } - (BOOL) allowsAutoDocking { return allowsAutoDocking; } - (void) setAllowsAutoDocking:(BOOL)newValue { allowsAutoDocking = !!newValue; // Ensure yes or no } - (BOOL) allowsSaving { // fixed stations only, not carriers! return allowsSaving && ([self maxFlightSpeed] == 0); } - (BOOL) isRotatingStation { if ([shipinfoDictionary oo_boolForKey:@"rotating" defaultValue:NO]) return YES; return [[shipinfoDictionary objectForKey:@"roles"] rangeOfString:@"rotating-station"].location != NSNotFound; // legacy } - (NSString *) marketOverrideName { // 2010.06.14 - Micha - we can't default to the primary role as otherwise the logic // generating the market in [Universe commodityDataForEconomy:] doesn't // work properly with the various overrides. The primary role will get // used if either there is no market override, or the market wasn't // defined. return [shipinfoDictionary oo_stringForKey:@"market"]; } - (BOOL) hasShipyard { if ([UNIVERSE station] == self) return YES; id determinant = [shipinfoDictionary objectForKey:@"has_shipyard"]; if (!determinant) determinant = [shipinfoDictionary objectForKey:@"hasShipyard"]; // NOTE: non-standard capitalization is documented and entrenched. if (determinant) { if ([determinant isKindOfClass:[NSArray class]]) { return [PLAYER scriptTestConditions:OOSanitizeLegacyScriptConditions(determinant, nil)]; } else { return OOFuzzyBooleanFromObject(determinant, 0.0f); } } else { return NO; } } - (BOOL) suppressArrivalReports { return suppress_arrival_reports; } - (void) setSuppressArrivalReports:(BOOL)newValue { suppress_arrival_reports = !!newValue; // ensure YES or NO } - (BOOL) hasBreakPattern { return hasBreakPattern; } - (void) setHasBreakPattern:(BOOL)newValue { hasBreakPattern = !!newValue; } - (NSString *) descriptionComponents { return [NSString stringWithFormat:@"\"%@\" %@", name, [super descriptionComponents]]; } - (void)dumpSelfState { NSMutableArray *flags = nil; NSString *flagsString = nil; NSString *alertString = @"*** ERROR: UNKNOWN ALERT LEVEL ***"; [super dumpSelfState]; switch (alertLevel) { case STATION_ALERT_LEVEL_GREEN: alertString = @"green"; break; case STATION_ALERT_LEVEL_YELLOW: alertString = @"yellow"; break; case STATION_ALERT_LEVEL_RED: alertString = @"red"; break; } OOLog(@"dumpState.stationEntity", @"Alert level: %@", alertString); OOLog(@"dumpState.stationEntity", @"Max police: %u", max_police); OOLog(@"dumpState.stationEntity", @"Max defense ships: %u", max_defense_ships); OOLog(@"dumpState.stationEntity", @"Defenders launched: %u", defenders_launched); OOLog(@"dumpState.stationEntity", @"Max scavengers: %u", max_scavengers); OOLog(@"dumpState.stationEntity", @"Scavengers launched: %u", scavengers_launched); OOLog(@"dumpState.stationEntity", @"Docked shuttles: %u", docked_shuttles); OOLog(@"dumpState.stationEntity", @"Docked traders: %u", docked_traders); OOLog(@"dumpState.stationEntity", @"Equivalent tech level: %li", equivalentTechLevel); OOLog(@"dumpState.stationEntity", @"Equipment price factor: %g", equipmentPriceFactor); flags = [NSMutableArray array]; #define ADD_FLAG_IF_SET(x) if (x) { [flags addObject:@#x]; } ADD_FLAG_IF_SET(no_docking_while_launching); if ([self isRotatingStation]) { [flags addObject:@"rotatingStation"]; } if (![self dockingCorridorIsEmpty]) { [flags addObject:@"dockingCorridorIsBusy"]; } flagsString = [flags count] ? [flags componentsJoinedByString:@", "] : (NSString *)@"none"; OOLog(@"dumpState.stationEntity", @"Flags: %@", flagsString); // approach and hold lists. // Ships on hold list, only used with moving stations (= carriers) if([_shipsOnHold count] > 0) { OOLog(@"dumpState.stationEntity", @"%li Ships on hold (unsorted):", [_shipsOnHold count]); OOLogIndent(); NSEnumerator *onHoldEnum = [_shipsOnHold objectEnumerator]; ShipEntity *ship = nil; unsigned i = 1; while ((ship = [onHoldEnum nextObject])) { OOLog(@"dumpState.stationEntity", @"Nr %i: %@ at distance %g with role: %@", i++, [ship displayName], HPdistance([self position], [ship position]), [ship primaryRole]); } OOLogOutdent(); } } @end oolite-1.82/src/Core/Entities/WormholeEntity.h000066400000000000000000000073141256642440500213610ustar00rootroot00000000000000/* WormholeEntity.h Entity subclass representing a wormhole between systems. (This is -- to use technical terminology -- the blue blobby thing you see hanging in space. The purple tunnel is RingEntity.) Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "Entity.h" #define WORMHOLE_EXPIRES_TIMEINTERVAL 900.0 #define WORMHOLE_SHRINK_RATE 4000.0 #define WORMHOLE_LEADER_SPEED_FACTOR 0.25 @class ShipEntity, Universe; typedef enum { WH_SCANINFO_NONE = 0, WH_SCANINFO_SCANNED, WH_SCANINFO_COLLAPSE_TIME, WH_SCANINFO_ARRIVAL_TIME, WH_SCANINFO_DESTINATION, WH_SCANINFO_SHIP, } WORMHOLE_SCANINFO; @interface WormholeEntity: Entity { @private double expiry_time; // Time when wormhole entrance closes double travel_time; // Time taken for a ship to traverse the wormhole double arrival_time; // Time when wormhole exit opens double estimated_arrival_time; // Time when wormhole should open (be different to arrival_time for misjump wormholes) double scan_time; // Time when wormhole was scanned OOSystemID origin; OOSystemID destination; NSPoint originCoords; // May not equal our origin system if the wormhole opens from Interstellar Space NSPoint destinationCoords; // May not equal the destination system if the wormhole misjumps NSMutableArray *shipsInTransit; double witch_mass; double shrink_factor; // used during nova mission // not used yet double exit_speed; // exit speed of ships in this wormhole WORMHOLE_SCANINFO scan_info; BOOL hasExitPosition; BOOL _misjump; GLfloat _misjumpRange; BOOL containsPlayer; } - (WormholeEntity*) initWithDict:(NSDictionary*)dict; - (WormholeEntity*) initWormholeTo:(OOSystemID) s fromShip:(ShipEntity *) ship; - (BOOL) suckInShip:(ShipEntity *) ship; - (void) disgorgeShips; - (void) setExitPosition:(HPVector)pos; - (OOSystemID) origin; - (OOSystemID) destination; - (NSPoint) originCoordinates; - (NSPoint) destinationCoordinates; - (void) setMisjump; // Flags up a wormhole as 'misjumpy' - (void) setMisjumpWithRange:(GLfloat)range; // Flags up a wormhole as 'misjumpy' - (BOOL) withMisjump; - (GLfloat) misjumpRange; - (double) exitSpeed; // exit speed from this wormhole - (void) setExitSpeed:(double) speed; // set exit speed from this wormhole - (double) expiryTime; // Time at which the wormholes entrance closes - (double) arrivalTime; // Time at which the wormholes exit opens - (double) estimatedArrivalTime; // Time when wormhole should open (different from arrival_time for misjump wormholes) - (double) travelTime; // Time needed for a ship to traverse the wormhole - (double) scanTime; // Time when wormhole was scanned - (void) setScannedAt:(double)time; - (void) setContainsPlayer:(BOOL)val; // mark the wormhole as waiting for player exit - (BOOL) isScanned; // True if the wormhole has been scanned by the player - (WORMHOLE_SCANINFO) scanInfo; // Stage of scanning - (void)setScanInfo:(WORMHOLE_SCANINFO) scanInfo; - (NSArray*) shipsInTransit; - (NSString *) identFromShip:(ShipEntity*) ship; - (NSDictionary *)getDict; @end oolite-1.82/src/Core/Entities/WormholeEntity.m000066400000000000000000000640211256642440500213640ustar00rootroot00000000000000/* WormholeEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "WormholeEntity.h" #import "ShipEntity.h" #import "OOSunEntity.h" #import "OOPlanetEntity.h" #import "PlayerEntity.h" #import "ShipEntityLoadRestore.h" #import "Universe.h" #import "AI.h" #import "OORoleSet.h" #import "OOShipRegistry.h" #import "OOShipGroup.h" #import "OOStringParsing.h" #import "OOCollectionExtractors.h" #import "OOLoggingExtended.h" #import "OOSystemDescriptionManager.h" // Hidden interface @interface WormholeEntity (Private) -(id) init; @end // Static local functions static void DrawWormholeCorona(GLfloat inner_radius, GLfloat outer_radius, int step, GLfloat z_distance, GLfloat *col4v1); @implementation WormholeEntity (Private) -(id) init { if ((self = [super init])) { witch_mass = 0.0; shipsInTransit = [[NSMutableArray arrayWithCapacity:4] retain]; collision_radius = 0.0; [self setStatus:STATUS_EFFECT]; scanClass = CLASS_WORMHOLE; isWormhole = YES; scan_info = WH_SCANINFO_NONE; scan_time = 0; hasExitPosition = NO; containsPlayer = NO; exit_speed = 50.0; } return self; } @end // Private interface implementation // // Public Wormhole Implementation // @implementation WormholeEntity - (WormholeEntity*)initWithDict:(NSDictionary*)dict { assert(dict != nil); if ((self = [self init])) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; // wormholes from pre-1.80 savegames using "origin_seed" and "dest_seed" // currently get defaults set; will probably disappear unnoticed origin = [dict oo_intForKey:@"origin_id" defaultValue:0]; destination = [dict oo_intForKey:@"dest_id" defaultValue:255]; originCoords = [[UNIVERSE systemManager] getCoordinatesForSystem:origin inGalaxy:[PLAYER galaxyNumber]]; destinationCoords = [[UNIVERSE systemManager] getCoordinatesForSystem:destination inGalaxy:[PLAYER galaxyNumber]]; // We only ever init from dictionary if we're loaded by the player, so // by definition we have been scanned scan_info = WH_SCANINFO_SCANNED; // Remember, times are stored as Ship Clock - but anything // saving/restoring wormholes from dictionaries should know this! expiry_time = [dict oo_doubleForKey:@"expiry_time"]; arrival_time = [dict oo_doubleForKey:@"arrival_time"]; // just in case an old save game has one with crossed times if (expiry_time > arrival_time) { expiry_time = arrival_time - 1.0; } // Since this is new for 1.75.1, we must give it a default values as we could be loading an old savegame estimated_arrival_time = [dict oo_doubleForKey:@"estimated_arrival_time" defaultValue:arrival_time]; position = [dict oo_hpvectorForKey:@"position"]; _misjump = [dict oo_boolForKey:@"misjump" defaultValue:NO]; // Setup shipsInTransit NSArray * shipDictsArray = [dict oo_arrayForKey:@"ships"]; NSEnumerator *shipDicts = [shipDictsArray objectEnumerator]; NSDictionary *currShipDict = nil; [shipsInTransit removeAllObjects]; NSMutableDictionary *restoreContext = [NSMutableDictionary dictionary]; while ((currShipDict = [shipDicts nextObject]) != nil) { NSDictionary *shipInfo = [currShipDict oo_dictionaryForKey:@"ship_info"]; if (shipInfo != nil) { ShipEntity *ship = [ShipEntity shipRestoredFromDictionary:shipInfo useFallback:YES context:restoreContext]; if (ship != nil) { [shipsInTransit addObject:[NSDictionary dictionaryWithObjectsAndKeys: ship, @"ship", [currShipDict objectForKey:@"time_delta"], @"time", nil]]; } else { OOLog(@"wormhole.load.warning", @"Wormhole ship \"%@\" failed to initialize - missing OXP or old-style saved wormhole data.", [shipInfo oo_stringForKey:@"ship_key"]); } } } [pool release]; } return self; } - (WormholeEntity*) initWormholeTo:(OOSystemID) s fromShip:(ShipEntity *) ship { assert(ship != nil); if ((self = [self init])) { double now = [PLAYER clockTimeAdjusted]; double distance; OOSunEntity *sun = [UNIVERSE sun]; _misjump = NO; origin = [UNIVERSE currentSystemID]; destination = s; originCoords = [PLAYER galaxy_coordinates]; destinationCoords = [[UNIVERSE systemManager] getCoordinatesForSystem:destination inGalaxy:[PLAYER galaxyNumber]]; distance = distanceBetweenPlanetPositions(originCoords.x, originCoords.y, destinationCoords.x, destinationCoords.y); distance = fmax(distance, 0.1); witch_mass = 200000.0; // MKW 2010.11.21 - originally the ship's mass was added twice - once here and once in suckInShip. Instead, we give each wormhole a minimum mass. if ([ship isPlayer]) witch_mass += [ship mass]; // The player ship never gets sucked in, so add its mass here. if (sun && ([sun willGoNova] || [sun goneNova]) && [ship mass] > 240000) shrink_factor = [ship mass] / 240000; // don't allow longstanding wormholes in nova systems. (60 sec * WORMHOLE_SHRINK_RATE = 240 000) else shrink_factor = 1; collision_radius = 0.5 * M_PI * pow(witch_mass, 1.0/3.0); expiry_time = now + (witch_mass / WORMHOLE_SHRINK_RATE / shrink_factor); travel_time = (distance * distance * 3600); // Taken from PlayerEntity.h arrival_time = now + travel_time; estimated_arrival_time = arrival_time; /* There are a number of bugs where the arrival time is < than the * expiry time (i.e. both ends open at once). * Rather than try to flatten all of them, having been unsuccessful twice * it seems easier to declare as a matter of wormhole physics that it * _can't_ be open at both ends at once. - CIM: 13/12/12 */ if (expiry_time > arrival_time) { expiry_time = arrival_time - 1.0; } position = [ship position]; zero_distance = HPdistance2([PLAYER position], position); } return self; } - (void) setMisjump { // Test for misjump first - it's entirely possibly that the wormhole // has already been marked for misjumping when another ship enters it. if (!_misjump) { double distance = distanceBetweenPlanetPositions(originCoords.x, originCoords.y, destinationCoords.x, destinationCoords.y); double time_adjust = distance * distance * (3600 - 2700); // NB: Time adjustment is calculated using original distance. Formula matches the one in [PlayerEntity witchJumpTo] arrival_time -= time_adjust; travel_time -= time_adjust; destinationCoords.x = (originCoords.x + destinationCoords.x) / 2; destinationCoords.y = (originCoords.y + destinationCoords.y) / 2; _misjump = YES; } } - (void) setMisjumpWithRange:(GLfloat)range { if (range <= 0.0 || range >= 1.0) { range = 0.5; // for safety, though nothing should be setting this } _misjumpRange = range; // Test for misjump first - it's entirely possibly that the wormhole // has already been marked for misjumping when another ship enters it. if (!_misjump) { double distance = distanceBetweenPlanetPositions(originCoords.x, originCoords.y, destinationCoords.x, destinationCoords.y); double time_adjust = (distance * (1-_misjumpRange))*(distance * (1-_misjumpRange))*3600.0; // time adjustment ensures that misjumps not faster than normal jumps // formulae for time and distance by mwerle at http://developer.berlios.de/pm/task.php?func=detailtask&project_task_id=4703&group_id=3577&group_project_id=1753 arrival_time -= time_adjust; travel_time -= time_adjust; destinationCoords.x = (originCoords.x * (1-_misjumpRange)) + (destinationCoords.x * _misjumpRange); destinationCoords.y = (originCoords.y * (1-_misjumpRange)) + (destinationCoords.y * _misjumpRange); _misjump = YES; } } - (BOOL) withMisjump { return _misjump; } - (GLfloat) misjumpRange { return _misjumpRange; } - (BOOL) suckInShip:(ShipEntity *) ship { if (!ship || [ship status] == STATUS_ENTERING_WITCHSPACE) { return NO; } if (origin != [UNIVERSE currentSystemID]) { // if we're no longer in the origin system, can't suck in return NO; } if ([PLAYER galaxy_coordinates].x != originCoords.x || [PLAYER galaxy_coordinates].y != originCoords.y) { // if we're no longer at the origin coordinates, can't suck in (handles interstellar space case) return NO; } double now = [PLAYER clockTimeAdjusted]; /* CIM: removed test. Not valid for wormholes which last longer than their travel time. Most likely for short distances e.g. zero-distance doubles. equal_seeds test above should cover it, with expiry_time test for safety. */ /* if (now > arrival_time) return NO; // far end of the wormhole! */ if( now > expiry_time ) return NO; // MKW 2010.11.18 - calculate time it takes for ship to reach wormhole // This is for AI ships which get told to enter the wormhole even though they // may still be some distance from it when the player exits the system float d = HPdistance(position, [ship position]); d -= [ship collisionRadius] + [self collisionRadius]; if (d > 0.0f) { float afterburnerFactor = [ship hasFuelInjection] && [ship fuel] > MIN_FUEL ? [ship afterburnerFactor] : 1.0; float shipSpeed = [ship maxFlightSpeed] * afterburnerFactor; // MKW 2011.02.27 - calculate speed based on group leader, if any, to // try and prevent escorts from entering the wormhole before their mother. ShipEntity *leader = [[ship group] leader]; if (leader && (leader != ship)) { afterburnerFactor = [leader hasFuelInjection] && [leader fuel] > MIN_FUEL ? [leader afterburnerFactor] : 1.0; float leaderShipSpeed = [leader maxFlightSpeed] * afterburnerFactor; if (leaderShipSpeed < shipSpeed ) shipSpeed = leaderShipSpeed; } if (shipSpeed <= 0.0f ) shipSpeed = 0.1f; now += d / shipSpeed; if( now > expiry_time ) { return NO; } } [shipsInTransit addObject:[NSDictionary dictionaryWithObjectsAndKeys: ship, @"ship", [NSNumber numberWithDouble: now + travel_time - arrival_time], @"time", [ship beaconCode], @"shipBeacon", // in case a beacon code has been set, nil otherwise nil]]; witch_mass += [ship mass]; expiry_time = now + (witch_mass / WORMHOLE_SHRINK_RATE / shrink_factor); // and, again, cap to be earlier than arrival time if (expiry_time > arrival_time) { expiry_time = arrival_time - 1.0; } collision_radius = 0.5 * M_PI * pow(witch_mass, 1.0/3.0); [UNIVERSE addWitchspaceJumpEffectForShip:ship]; // Should probably pass the wormhole, but they have no JS representation [ship setStatus:STATUS_ENTERING_WITCHSPACE]; [ship doScriptEvent:OOJSID("shipWillEnterWormhole")]; [[ship getAI] message:@"ENTERED_WITCHSPACE"]; [UNIVERSE removeEntity:ship]; [[ship getAI] clearStack]; // get rid of any preserved states if ([ship isStation]) { if ([PLAYER dockedStation] == (StationEntity*)ship) { // the carrier has jumped while the player is docked [ship retain]; [UNIVERSE carryPlayerOn:(StationEntity*)ship inWormhole:self]; [ship release]; } } return YES; } - (void) disgorgeShips { double now = [PLAYER clockTimeAdjusted]; NSMutableArray* shipsStillInTransit = [[NSMutableArray alloc] initWithCapacity:[shipsInTransit count]]; BOOL hasShiftedExitPosition = NO; BOOL useExitXYScatter = NO; NSDictionary *shipInfo = nil; foreach (shipInfo, shipsInTransit) { ShipEntity *ship = [shipInfo objectForKey:@"ship"]; NSString *shipBeacon = [shipInfo objectForKey:@"shipBeacon"]; double ship_arrival_time = arrival_time + [shipInfo oo_doubleForKey:@"time"]; double time_passed = now - ship_arrival_time; if ([ship status] == STATUS_DEAD) continue; // skip dead ships. if (ship_arrival_time > now) { [shipsStillInTransit addObject:shipInfo]; } else { // Only calculate exit position once so that all ships arrive from the same point if (!hasExitPosition) { position = [UNIVERSE getWitchspaceExitPosition]; // no need to reset PRNG. GLfloat min_d1 = [UNIVERSE safeWitchspaceExitDistance]; Quaternion q1; quaternion_set_random(&q1); double d1 = SCANNER_MAX_RANGE*((ranrot_rand() % 256)/256.0 - 0.5); Vector v1 = vector_forward_from_quaternion(q1); if (dot_product(v1,kBasisZVector) < -0.99) { // a bit more safe distance if right behind the buoy min_d1 *= 3.0; } if (fabs(d1) < min_d1) // no closer than 750m to edge of buoy { d1 += ((d1 > 0.0)? min_d1: -min_d1); } position.x += v1.x * d1; // randomise exit position position.y += v1.y * d1; position.z += v1.z * d1; } if (hasExitPosition && (!containsPlayer || useExitXYScatter)) { HPVector shippos; Vector exit_vector_x = vector_right_from_quaternion([UNIVERSE getWitchspaceExitRotation]); Vector exit_vector_y = vector_up_from_quaternion([UNIVERSE getWitchspaceExitRotation]); // entry wormhole has a radius of around 100m (or perhaps more) // so randomise exit positions slightly too for second and subsequent ships // helps avoid collisions when two ships enter wormhole at same time double offset_x = randf()*150.0-75.0; double offset_y = randf()*150.0-75.0; shippos.x = position.x + (offset_x*exit_vector_x.x)+(offset_y*exit_vector_y.x); shippos.y = position.y + (offset_x*exit_vector_x.y)+(offset_y*exit_vector_y.y); shippos.z = position.z + (offset_x*exit_vector_x.z)+(offset_y*exit_vector_y.z); [ship setPosition:shippos]; } else { // this is the first ship out of the wormhole [self setExitSpeed:[ship maxFlightSpeed]*WORMHOLE_LEADER_SPEED_FACTOR]; if (containsPlayer) { // reset the player's speed to the new speed [PLAYER setSpeed:exit_speed]; } useExitXYScatter = YES; [ship setPosition:position]; } if (shipBeacon != nil) { [ship setBeaconCode:shipBeacon]; } // Don't reduce bounty on misjump. Fixes #17992 // - MKW 2011.03.10 if (!_misjump) [ship setBounty:[ship bounty]/2 withReason:kOOLegalStatusReasonNewSystem]; // adjust legal status for new system // now the cargo is defined in advance, this is unnecessary /* if ([ship cargoFlag] == CARGO_FLAG_FULL_PLENTIFUL) { [ship setCargoFlag: CARGO_FLAG_FULL_SCARCE]; }*/ if (time_passed < 2.0) { [ship witchspaceLeavingEffects]; // adds the ship to the universe with effects. } else { // arrived 2 seconds or more before the player. Rings have faded out. [ship setOrientation: [UNIVERSE getWitchspaceExitRotation]]; [ship setPitch: 0.0]; [ship setRoll: 0.0]; [ship setVelocity: kZeroVector]; [UNIVERSE addEntity:ship]; // AI and status get initialised here } [ship setSpeed:[self exitSpeed]]; // all ships from this wormhole have same velocity // awaken JS-based AIs [ship doScriptEvent:OOJSID("aiStarted")]; // Wormholes now have a JS representation, so we could provide it // but is it worth it for the exit wormhole? [ship doScriptEvent:OOJSID("shipExitedWormhole") andReactToAIMessage:@"EXITED WITCHSPACE"]; // update the ships's position if (!hasExitPosition) { hasExitPosition = YES; hasShiftedExitPosition = YES; // exitPosition is shifted towards the lead ship update position. [ship update: time_passed]; // do this only for one ship or the next ships might appear at very different locations. position = [ship position]; // e.g. when the player docks first before following, time_passed is already > 10 minutes. } else if (time_passed > 1) // Only update the ship position if it was some time ago, otherwise we're in 'real time'. { if (hasShiftedExitPosition) { // only update the time delay to the lead ship. Sign is not correct but updating gives a small spacial distribution. [ship update: (ship_arrival_time - arrival_time)]; } else { // Exit position was externally set, e.g. by player ship following through this wormhole. // Use the real time difference. [ship update:time_passed]; } } } } [shipsInTransit release]; shipsInTransit = shipsStillInTransit; if (containsPlayer) { // ships exiting the wormhole after now are following the player // so appear behind them position = HPvector_add([PLAYER position], vectorToHPVector(vector_multiply_scalar([PLAYER forwardVector], -500.0f))); containsPlayer = NO; } // else, the wormhole doesn't now (or never) contained the player, so // no need to move it } - (void) setContainsPlayer:(BOOL)val { containsPlayer = val; } - (void) setExitPosition:(HPVector)pos { [self setPosition: pos]; hasExitPosition = YES; } - (OOSystemID) origin { return origin; } - (OOSystemID) destination { return destination; } - (NSPoint) originCoordinates { return originCoords; } - (NSPoint) destinationCoordinates { return destinationCoords; } - (double) exitSpeed { return exit_speed; } - (void) setExitSpeed:(double) speed { exit_speed = speed; } - (double) expiryTime { return expiry_time; } - (double) arrivalTime { return arrival_time; } - (double) estimatedArrivalTime { return estimated_arrival_time; } - (double) travelTime { return travel_time; } - (double) scanTime { return scan_time; } - (BOOL) isScanned { return scan_info > WH_SCANINFO_NONE; } - (void) setScannedAt:(double)p_scanTime { if( scan_info == WH_SCANINFO_NONE ) { scan_time = p_scanTime; scan_info = WH_SCANINFO_SCANNED; } // else we previously scanned this wormhole } - (WORMHOLE_SCANINFO) scanInfo { return scan_info; } - (void) setScanInfo:(WORMHOLE_SCANINFO)p_scanInfo { scan_info = p_scanInfo; } - (NSArray*) shipsInTransit { return shipsInTransit; } - (void) dealloc { [shipsInTransit release]; [super dealloc]; } - (NSString *) descriptionComponents { double now = [PLAYER clockTime]; return [NSString stringWithFormat:@"destination: %@ ttl: %.2fs arrival: %@", _misjump ? (NSString *)@"Interstellar Space" : [UNIVERSE getSystemName:destination], expiry_time - now, ClockToString(arrival_time, false)]; } - (NSString *) identFromShip:(ShipEntity*)ship { if ([ship hasEquipmentItem:@"EQ_WORMHOLE_SCANNER"]) { if ([self scanInfo] >= WH_SCANINFO_DESTINATION) { return [NSString stringWithFormat:DESC(@"wormhole-to-@"), [UNIVERSE getSystemName:destination]]; } else { return DESC(@"wormhole-desc"); } } else { OOLogERR(kOOLogInconsistentState, @"Wormhole identified when ship has no EQ_WORMHOLE_SCANNER."); /* This was previously an assertion, but a player reported hitting it. http://aegidian.org/bb/viewtopic.php?p=128110#p128110 -- Ahruman 2011-01-27 */ return nil; } } - (BOOL) canCollide { /* Correct test for far end of wormhole */ if (origin != [UNIVERSE currentSystemID]) { // if we're no longer in the origin system, can't suck in return NO; } if ([PLAYER galaxy_coordinates].x != originCoords.x || [PLAYER galaxy_coordinates].y != originCoords.y) { // if we're no longer at the origin coordinates, can't suck in (handles interstellar space case) return NO; } return (witch_mass > 0.0); } - (BOOL) checkCloseCollisionWith:(Entity *)other { return ![other isEffect]; } - (void) update:(OOTimeDelta) delta_t { [super update:delta_t]; PlayerEntity *player = PLAYER; assert(player != nil); rotMatrix = OOMatrixForBillboard(position, [player viewpointPosition]); double now = [player clockTimeAdjusted]; if (witch_mass > 0.0) { witch_mass -= WORMHOLE_SHRINK_RATE * delta_t * shrink_factor; witch_mass = fmax(witch_mass, 0.0); collision_radius = 0.5 * M_PI * pow(witch_mass, 1.0/3.0); no_draw_distance = collision_radius * collision_radius * NO_DRAW_DISTANCE_FACTOR * NO_DRAW_DISTANCE_FACTOR; } scanClass = (witch_mass > 0.0)? CLASS_WORMHOLE : CLASS_NO_DRAW; if (now > expiry_time) { scanClass = CLASS_NO_DRAW; // witch_mass not certain to be limiting factor on extremely short jumps, so make sure now // If we're a saved wormhole waiting to disgorge more ships, it's safe // to remove self from UNIVERSE, but we need the current position! [UNIVERSE removeEntity: self]; } } - (void) drawImmediate:(bool)immediate translucent:(bool)translucent { if ([UNIVERSE breakPatternHide]) return; // DON'T DRAW DURING BREAK PATTERN if (cam_zero_distance > no_draw_distance) return; // TOO FAR AWAY TO SEE if (witch_mass <= 0.0) return; if (collision_radius <= 0.0) return; if ([self scanClass] == CLASS_NO_DRAW) return; if (translucent) { // for now, a simple copy of the energy bomb draw routine float srzd = sqrt(cam_zero_distance); GLfloat color_fv[4] = { 0.0, 0.0, 1.0, 0.25 }; OOSetOpenGLState(OPENGL_STATE_TRANSLUCENT_PASS); OOGL(glDisable(GL_CULL_FACE)); OOGL(glEnable(GL_BLEND)); OOGL(glColor4fv(color_fv)); OOGLBEGIN(GL_TRIANGLE_FAN); GLDrawBallBillboard(collision_radius, 4, srzd); OOGLEND(); DrawWormholeCorona(0.67 * collision_radius, collision_radius, 4, srzd, color_fv); OOGL(glEnable(GL_CULL_FACE)); OOGL(glDisable(GL_BLEND)); } OOVerifyOpenGLState(); OOCheckOpenGLErrors(@"WormholeEntity after drawing %@", self); } static void DrawWormholeCorona(GLfloat inner_radius, GLfloat outer_radius, int step, GLfloat z_distance, GLfloat *col4v1) { if (outer_radius >= z_distance) // inside the sphere return; int i; NSRange activity = { 0.34, 1.0 }; GLfloat s0, c0, s1, c1; GLfloat r0, r1; GLfloat rv0, rv1, q; GLfloat theta, delta, halfStep; r0 = outer_radius * z_distance / sqrt(z_distance * z_distance - outer_radius * outer_radius); r1 = inner_radius * z_distance / sqrt(z_distance * z_distance - inner_radius * inner_radius); delta = step * M_PI / 180.0f; halfStep = 0.5f * delta; theta = 0.0f; OOGLBEGIN(GL_TRIANGLE_STRIP); for (i = 0; i < 360; i += step ) { theta += delta; rv0 = randf(); rv1 = randf(); q = activity.location + rv0 * activity.length; s0 = r0 * sin(theta); c0 = r0 * cos(theta); glColor4f(col4v1[0] * q, col4v1[1] * q, col4v1[2] * q, col4v1[3] * rv0); glVertex3f(s0, c0, 0.0); s1 = r1 * sin(theta - halfStep) * 0.5 * (1.0 + rv1); c1 = r1 * cos(theta - halfStep) * 0.5 * (1.0 + rv1); glColor4f(col4v1[0], col4v1[1], col4v1[2], 0.0); glVertex3f(s1, c1, 0.0); } // repeat last values to close rv0 = randf(); rv1 = randf(); q = activity.location + rv0 * activity.length; s0 = 0.0f; // r0 * sin(0); c0 = r0; // r0 * cos(0); glColor4f(col4v1[0] * q, col4v1[1] * q, col4v1[2] * q, col4v1[3] * rv0); glVertex3f(s0, c0, 0.0); s1 = r1 * sin(halfStep) * 0.5 * (1.0 + rv1); c1 = r1 * cos(halfStep) * 0.5 * (1.0 + rv1); glColor4f(col4v1[0], col4v1[1], col4v1[2], 0.0); glVertex3f(s1, c1, 0.0); OOGLEND(); } - (NSDictionary *) getDict { NSMutableDictionary *myDict = [NSMutableDictionary dictionary]; [myDict oo_setInteger:origin forKey:@"origin_id"]; [myDict oo_setInteger:destination forKey:@"dest_id"]; [myDict setObject:StringFromPoint(originCoords) forKey:@"origin_coords"]; [myDict setObject:StringFromPoint(destinationCoords) forKey:@"dest_coords"]; // Anything converting a wormhole to a dictionary should already have // modified its time to shipClock time [myDict oo_setFloat:(expiry_time) forKey:@"expiry_time"]; [myDict oo_setFloat:(arrival_time) forKey:@"arrival_time"]; [myDict oo_setFloat:(estimated_arrival_time) forKey:@"estimated_arrival_time"]; [myDict oo_setHPVector:position forKey:@"position"]; [myDict oo_setBool:_misjump forKey:@"misjump"]; NSMutableArray * shipArray = [NSMutableArray arrayWithCapacity:[shipsInTransit count]]; NSEnumerator * ships = [shipsInTransit objectEnumerator]; NSDictionary * currShipDict = nil; NSMutableDictionary *context = [NSMutableDictionary dictionary]; while ((currShipDict = [ships nextObject]) != nil) { id ship = [currShipDict objectForKey:@"ship"]; [shipArray addObject:[NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithDouble:[currShipDict oo_doubleForKey:@"time"]], @"time_delta", [ship savedShipDictionaryWithContext:context], @"ship_info", nil]]; } [myDict setObject:shipArray forKey:@"ships"]; return myDict; } - (NSString *) scanInfoString { switch(scan_info) { case WH_SCANINFO_NONE: return @"WH_SCANINFO_NONE"; case WH_SCANINFO_SCANNED: return @"WH_SCANINFO_SCANNED"; case WH_SCANINFO_COLLAPSE_TIME: return @"WH_SCANINFO_COLLAPSE_TIME"; case WH_SCANINFO_ARRIVAL_TIME: return @"WH_SCANINFO_ARRIVAL_TIME"; case WH_SCANINFO_DESTINATION: return @"WH_SCANINFO_DESTINATION"; case WH_SCANINFO_SHIP: return @"WH_SCANINFO_SHIP"; } return @"WH_SCANINFO_UNDEFINED"; // should never get here } - (void)dumpSelfState { [super dumpSelfState]; OOLog(@"dumpState.wormholeEntity", @"Origin : %@", [UNIVERSE getSystemName:origin]); OOLog(@"dumpState.wormholeEntity", @"Destination : %@", [UNIVERSE getSystemName:destination]); OOLog(@"dumpState.wormholeEntity", @"Expiry Time : %@", ClockToString(expiry_time, false)); OOLog(@"dumpState.wormholeEntity", @"Arrival Time : %@", ClockToString(arrival_time, false)); OOLog(@"dumpState.wormholeEntity", @"Projected Arrival Time : %@", ClockToString(estimated_arrival_time, false)); OOLog(@"dumpState.wormholeEntity", @"Scanned Time : %@", ClockToString(scan_time, false)); OOLog(@"dumpState.wormholeEntity", @"Scanned State : %@", [self scanInfoString]); OOLog(@"dumpState.wormholeEntity", @"Mass : %.2lf", witch_mass); OOLog(@"dumpState.wormholeEntity", @"Ships : %ld", [shipsInTransit count]); unsigned i; for (i = 0; i < [shipsInTransit count]; ++i) { NSDictionary *shipDict = [shipsInTransit oo_dictionaryAtIndex:i]; ShipEntity* ship = (ShipEntity*)[shipDict objectForKey:@"ship"]; double ship_arrival_time = arrival_time + [shipDict oo_doubleForKey:@"time"]; OOLog(@"dumpState.wormholeEntity.ships", @"Ship %d: %@ mass %.2f arrival time %@", i+1, ship, [ship mass], ClockToString(ship_arrival_time, false)); } } @end oolite-1.82/src/Core/GameController+FullScreen.m000066400000000000000000000104761256642440500215670ustar00rootroot00000000000000/* GameController+FullScreen.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "GameController.h" #import "MyOpenGLView.h" #import "OOCollectionExtractors.h" #if OOLITE_MAC_OS_X // TEMP, should be used for SDL too #if OOLITE_MAC_OS_X #import "OOMacSnowLeopardFullScreenController.h" #import "OOMacSystemStandardFullScreenController.h" #import "OOPrimaryWindow.h" @interface GameController (OOPrimaryWindowDelegate) @end #endif @implementation GameController (FullScreen) - (void) setUpDisplayModes { #if OOLITE_MAC_OS_X OOFullScreenController *fullScreenController = nil; #if OO_MAC_SUPPORT_SYSTEM_STANDARD_FULL_SCREEN if ([OOMacSystemStandardFullScreenController shouldUseSystemStandardFullScreenController]) { fullScreenController = [[OOMacSystemStandardFullScreenController alloc] initWithGameView:gameView]; } #endif if (fullScreenController == nil) { fullScreenController = [[OOMacSnowLeopardFullScreenController alloc] initWithGameView:gameView]; } #endif // Load preferred display mode, falling back to current mode if no preferences set. NSDictionary *currentMode = [fullScreenController currentDisplayMode]; NSUInteger width = [currentMode oo_unsignedIntegerForKey:kOODisplayWidth]; NSUInteger height = [currentMode oo_unsignedIntegerForKey:kOODisplayHeight]; NSUInteger refresh = [currentMode oo_unsignedIntegerForKey:kOODisplayRefreshRate]; NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; width = [userDefaults oo_unsignedIntegerForKey:@"display_width" defaultValue:width]; height = [userDefaults oo_unsignedIntegerForKey:@"display_height" defaultValue:height]; refresh = [userDefaults oo_unsignedIntegerForKey:@"display_refresh" defaultValue:refresh]; [fullScreenController setDisplayWidth:width height:height refreshRate:refresh]; _fullScreenController = fullScreenController; } #if OOLITE_MAC_OS_X - (IBAction) toggleFullScreenAction:(id)sender { [self setFullScreenMode:![self inFullScreenMode]]; } - (void) toggleFullScreenCalledForWindow:(OOPrimaryWindow *)window withSender:(id)sender { [self toggleFullScreenAction:sender]; } #endif - (BOOL) inFullScreenMode { return [_fullScreenController inFullScreenMode]; } - (void) setFullScreenMode:(BOOL)value { if (value == [self inFullScreenMode]) return; [[NSUserDefaults standardUserDefaults] setBool:value forKey:@"fullscreen"]; if (value) { [_fullScreenController setFullScreenMode:YES]; } else { [_fullScreenController setFullScreenMode:NO]; } } - (void) exitFullScreenMode { [self setFullScreenMode:NO]; } - (BOOL) setDisplayWidth:(unsigned int)width Height:(unsigned int)height Refresh:(unsigned int)refreshRate { if ([_fullScreenController setDisplayWidth:width height:height refreshRate:refreshRate]) { NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; [userDefaults setInteger:width forKey:@"display_width"]; [userDefaults setInteger:height forKey:@"display_height"]; [userDefaults setInteger:refreshRate forKey:@"display_refresh"]; [userDefaults synchronize]; return YES; } else { return NO; } } - (NSDictionary *) findDisplayModeForWidth:(unsigned int)d_width Height:(unsigned int)d_height Refresh:(unsigned int)d_refresh { return [_fullScreenController findDisplayModeForWidth:d_width height:d_height refreshRate:d_refresh]; } - (NSArray *) displayModes { return [_fullScreenController displayModes]; } - (NSUInteger) indexOfCurrentDisplayMode { return [_fullScreenController indexOfCurrentDisplayMode]; } - (void) pauseFullScreenModeToPerform:(SEL)selector onTarget:(id)target { [target performSelector:selector]; } @end #endif oolite-1.82/src/Core/GameController.h000066400000000000000000000124331256642440500175170ustar00rootroot00000000000000/* GameController.h Main application controller class. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #import "OOFunctionAttributes.h" #import "OOFullScreenController.h" #import "OOMouseInteractionMode.h" #if OOLITE_MAC_OS_X #import // For PDFKit. #endif #define MINIMUM_GAME_TICK 0.25 // * reduced from 0.5s for tgape * // @class MyOpenGLView, OOFullScreenController; // TEMP: whether to use separate OOFullScreenController object, will hopefully be used for all builds soon. #define OO_USE_FULLSCREEN_CONTROLLER OOLITE_MAC_OS_X @interface GameController: NSObject { @private #if OOLITE_MAC_OS_X IBOutlet NSTextField *splashProgressTextField; IBOutlet NSView *splashView; IBOutlet NSWindow *gameWindow; IBOutlet PDFView *helpView; IBOutlet NSMenu *dockMenu; #endif IBOutlet MyOpenGLView *gameView; NSTimeInterval last_timeInterval; double delta_t; int my_mouse_x, my_mouse_y; NSString *playerFileDirectory; NSString *playerFileToLoad; NSMutableArray *expansionPathsToInclude; NSTimer *timer; NSTimeInterval _animationTimerInterval; NSDate *_splashStart; SEL pauseSelector; NSObject *pauseTarget; BOOL gameIsPaused; OOMouseInteractionMode _mouseMode; OOMouseInteractionMode _resumeMode; // Fullscreen mode stuff. #if OO_USE_FULLSCREEN_CONTROLLER OOFullScreenController *_fullScreenController; #elif OOLITE_SDL NSRect fsGeometry; MyOpenGLView *switchView; NSMutableArray *displayModes; unsigned int width, height; unsigned int refresh; BOOL fullscreen; NSDictionary *originalDisplayMode; NSDictionary *fullscreenDisplayMode; BOOL stayInFullScreenMode; #endif } + (GameController *) sharedController; - (void) applicationDidFinishLaunching:(NSNotification *)notification; - (BOOL) isGamePaused; - (void) setGamePaused:(BOOL)value; - (OOMouseInteractionMode) mouseInteractionMode; - (void) setMouseInteractionMode:(OOMouseInteractionMode)mode; - (void) setMouseInteractionModeForFlight; // Chooses mouse control mode appropriately. - (void) setMouseInteractionModeForUIWithMouseInteraction:(BOOL)interaction; - (void) performGameTick:(id)sender; #if OOLITE_MAC_OS_X - (IBAction) showLogAction:(id)sender; - (IBAction) showLogFolderAction:(id)sender; - (IBAction) showSnapshotsAction:(id)sender; - (IBAction) showAddOnsAction:(id)sender; - (void) recenterVirtualJoystick; #endif - (void) exitAppWithContext:(NSString *)context; - (void) exitAppCommandQ; - (NSString *) playerFileToLoad; - (void) setPlayerFileToLoad:(NSString *)filename; - (NSString *) playerFileDirectory; - (void) setPlayerFileDirectory:(NSString *)filename; - (void) loadPlayerIfRequired; - (void) beginSplashScreen; - (void) logProgress:(NSString *)message; #if OO_DEBUG - (void) debugLogProgress:(NSString *)format, ... OO_TAKES_FORMAT_STRING(1, 2); - (void) debugLogProgress:(NSString *)format arguments:(va_list)arguments OO_TAKES_FORMAT_STRING(1, 0); - (void) debugPushProgressMessage:(NSString *)format, ... OO_TAKES_FORMAT_STRING(1, 2); - (void) debugPopProgressMessage; #endif - (void) endSplashScreen; - (void) startAnimationTimer; - (void) stopAnimationTimer; - (MyOpenGLView *) gameView; - (void) setGameView:(MyOpenGLView *)view; - (void)windowDidResize:(NSNotification *)aNotification; - (void)setUpBasicOpenGLStateWithSize:(NSSize)viewSize; - (NSURL *) snapshotsURLCreatingIfNeeded:(BOOL)create; @end @interface GameController (FullScreen) #if OO_USE_FULLSCREEN_CONTROLLER #if OOLITE_MAC_OS_X - (IBAction) toggleFullScreenAction:(id)sender; #endif - (void) setFullScreenMode:(BOOL)value; #endif - (void) exitFullScreenMode; // FIXME: should be setFullScreenMode:NO - (BOOL) inFullScreenMode; - (BOOL) setDisplayWidth:(unsigned int) d_width Height:(unsigned int)d_height Refresh:(unsigned int) d_refresh; - (NSDictionary *) findDisplayModeForWidth:(unsigned int)d_width Height:(unsigned int) d_height Refresh:(unsigned int) d_refresh; - (NSArray *) displayModes; - (NSUInteger) indexOfCurrentDisplayMode; - (void) pauseFullScreenModeToPerform:(SEL) selector onTarget:(id) target; // Internal use only. - (void) setUpDisplayModes; @end #if OO_DEBUG #define OO_DEBUG_PROGRESS(...) [[GameController sharedController] debugLogProgress:__VA_ARGS__] #define OO_DEBUG_PUSH_PROGRESS(...) [[GameController sharedController] debugPushProgressMessage:__VA_ARGS__] #define OO_DEBUG_POP_PROGRESS() [[GameController sharedController] debugPopProgressMessage] #else #define OO_DEBUG_PROGRESS(...) do {} while (0) #define OO_DEBUG_PUSH_PROGRESS(...) do {} while (0) #define OO_DEBUG_POP_PROGRESS() do {} while (0) #endif oolite-1.82/src/Core/GameController.m000066400000000000000000000600211256642440500175200ustar00rootroot00000000000000/* GameController.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "GameController.h" #import "Universe.h" #import "ResourceManager.h" #import "MyOpenGLView.h" #import "OOSound.h" #import "OOOpenGL.h" #import "PlayerEntityLoadSave.h" #include #import "OOCollectionExtractors.h" #import "OOOXPVerifier.h" #import "OOLoggingExtended.h" #import "NSFileManagerOOExtensions.h" #import "OOLogOutputHandler.h" #import "OODebugFlags.h" #import "OOJSFrameCallbacks.h" #import "OOOpenGLExtensionManager.h" #import "OOOpenALController.h" #import "OODebugSupport.h" #import "legacy_random.h" #import "OOOXZManager.h" #import "OOOpenGLMatrixManager.h" #if OOLITE_MAC_OS_X #import "JAPersistentFileReference.h" #import #import "OoliteApp.h" #import "OOMacJoystickManager.h" static void SetUpSparkle(void); #elif (OOLITE_GNUSTEP && !defined(NDEBUG)) #import "OODebugMonitor.h" #endif static GameController *sSharedController = nil; @interface GameController (OOPrivate) - (void)reportUnhandledStartupException:(NSException *)exception; - (void)doPerformGameTick; @end @implementation GameController + (GameController *) sharedController { if (sSharedController == nil) { sSharedController = [[self alloc] init]; } return sSharedController; } - (id) init { if (sSharedController != nil) { [self release]; [NSException raise:NSInternalInconsistencyException format:@"%s: expected only one GameController to exist at a time.", __PRETTY_FUNCTION__]; } if ((self = [super init])) { last_timeInterval = [NSDate timeIntervalSinceReferenceDate]; delta_t = 0.01; // one hundredth of a second _animationTimerInterval = [[NSUserDefaults standardUserDefaults] oo_doubleForKey:@"animation_timer_interval" defaultValue:0.005]; // rather than seeding this with the date repeatedly, seed it // once here at startup ranrot_srand((uint32_t)[[NSDate date] timeIntervalSince1970]); // reset randomiser with current time _splashStart = [[NSDate alloc] init]; } return self; } - (void) dealloc { #if OOLITE_MAC_OS_X [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:UNIVERSE]; #endif [timer release]; [gameView release]; [UNIVERSE release]; [playerFileToLoad release]; [playerFileDirectory release]; [expansionPathsToInclude release]; [super dealloc]; } - (BOOL) isGamePaused { return gameIsPaused; } - (void) setGamePaused:(BOOL)value { if (value && !gameIsPaused) { _resumeMode = [self mouseInteractionMode]; [self setMouseInteractionModeForUIWithMouseInteraction:NO]; gameIsPaused = YES; [PLAYER doScriptEvent:OOJSID("gamePaused")]; } else if (!value && gameIsPaused) { [self setMouseInteractionMode:_resumeMode]; gameIsPaused = NO; [PLAYER doScriptEvent:OOJSID("gameResumed")]; } } - (OOMouseInteractionMode) mouseInteractionMode { return _mouseMode; } - (void) setMouseInteractionMode:(OOMouseInteractionMode)mode { OOMouseInteractionMode oldMode = _mouseMode; if (mode == oldMode) return; _mouseMode = mode; OOLog(@"input.mouseMode.changed", @"Mouse interaction mode changed from %@ to %@", OOStringFromMouseInteractionMode(oldMode), OOStringFromMouseInteractionMode(mode)); #if OO_USE_FULLSCREEN_CONTROLLER if ([self inFullScreenMode]) { [_fullScreenController noteMouseInteractionModeChangedFrom:oldMode to:mode]; } else #endif { [[self gameView] noteMouseInteractionModeChangedFrom:oldMode to:mode]; } } - (void) setMouseInteractionModeForFlight { [self setMouseInteractionMode:[PLAYER isMouseControlOn] ? MOUSE_MODE_FLIGHT_WITH_MOUSE_CONTROL : MOUSE_MODE_FLIGHT_NO_MOUSE_CONTROL]; } - (void) setMouseInteractionModeForUIWithMouseInteraction:(BOOL)interaction { [self setMouseInteractionMode:interaction ? MOUSE_MODE_UI_SCREEN_WITH_INTERACTION : MOUSE_MODE_UI_SCREEN_NO_INTERACTION]; } - (MyOpenGLView *) gameView { return gameView; } - (void) setGameView:(MyOpenGLView *)view { [gameView release]; gameView = [view retain]; [gameView setGameController:self]; [UNIVERSE setGameView:gameView]; } - (void) applicationDidFinishLaunching:(NSNotification *)notification { NSAutoreleasePool *pool = nil; unsigned i; pool = [[NSAutoreleasePool alloc] init]; @try { // if not verifying oxps, ensure that gameView is drawn to using beginSplashScreen // OpenGL is initialised and that allows textures to initialise too. #if OO_OXP_VERIFIER_ENABLED if ([OOOXPVerifier runVerificationIfRequested]) { [self exitAppWithContext:@"OXP verifier run"]; } else { [self beginSplashScreen]; } #else [self beginSplashScreen]; #endif #if OOLITE_MAC_OS_X [OOJoystickManager setStickHandlerClass:[OOMacJoystickManager class]]; SetUpSparkle(); #endif [self setUpDisplayModes]; // moved to before the Universe is created if (expansionPathsToInclude) { for (i = 0; i < [expansionPathsToInclude count]; i++) { [ResourceManager addExternalPath: (NSString*)[expansionPathsToInclude objectAtIndex: i]]; } } // initialise OXZ manager [OOOXZManager sharedManager]; // moved here to try to avoid initialising this before having an Open GL context //[self logProgress:DESC(@"Initialising universe")]; // DESC expansions only possible after Universe init [[Universe alloc] initWithGameView:gameView]; [self loadPlayerIfRequired]; [self logProgress:@""]; // get the run loop and add the call to performGameTick: [self startAnimationTimer]; [self endSplashScreen]; } @catch (NSException *exception) { [self reportUnhandledStartupException:exception]; exit(EXIT_FAILURE); } OOLog(@"startup.complete", @"========== Loading complete in %.2f seconds. ==========", -[_splashStart timeIntervalSinceNow]); #if OO_USE_FULLSCREEN_CONTROLLER [self setFullScreenMode:[[NSUserDefaults standardUserDefaults] boolForKey:@"fullscreen"]]; #endif // Release anything allocated above that is not required. [pool release]; #if !OOLITE_MAC_OS_X [[NSRunLoop currentRunLoop] run]; #endif } - (void) loadPlayerIfRequired { if (playerFileToLoad != nil) { [self logProgress:DESC(@"loading-player")]; // fix problem with non-shader lighting when starting skips // the splash screen [UNIVERSE useGUILightSource:YES]; [UNIVERSE useGUILightSource:NO]; [PLAYER loadPlayerFromFile:playerFileToLoad asNew:NO]; } } - (void) beginSplashScreen { #if !OOLITE_MAC_OS_X if(!gameView) { gameView = [MyOpenGLView alloc]; [gameView init]; [gameView setGameController:self]; [gameView initSplashScreen]; } #else [gameView updateScreen]; #endif } #if OOLITE_MAC_OS_X - (void) performGameTick:(id)sender { [self doPerformGameTick]; } #else - (void) performGameTick:(id)sender { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [gameView pollControls]; [self doPerformGameTick]; [pool release]; } #endif - (void) doPerformGameTick { @try { if (gameIsPaused) delta_t = 0.0; // no movement! else { delta_t = [NSDate timeIntervalSinceReferenceDate] - last_timeInterval; last_timeInterval += delta_t; if (delta_t > MINIMUM_GAME_TICK) delta_t = MINIMUM_GAME_TICK; // peg the maximum pause (at 0.5->1.0 seconds) to protect against when the machine sleeps } [UNIVERSE update:delta_t]; if (EXPECT_NOT([PLAYER status] == STATUS_RESTART_GAME)) { [UNIVERSE reinitAndShowDemo:YES]; } [OOSound update]; if (!gameIsPaused) { OOJSFrameCallbacksInvoke(delta_t); } } @catch (id exception) { OOLog(@"exception.backtrace",@"%@",[exception callStackSymbols]); } @try { [gameView display]; } @catch (id exception) {} } - (void) startAnimationTimer { if (timer == nil) { NSTimeInterval ti = _animationTimerInterval; // default one two-hundredth of a second (should be a fair bit faster than expected frame rate ~60Hz to avoid problems with phase differences) timer = [[NSTimer timerWithTimeInterval:ti target:self selector:@selector(performGameTick:) userInfo:nil repeats:YES] retain]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; #if OOLITE_MAC_OS_X [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSEventTrackingRunLoopMode]; #endif } } - (void) stopAnimationTimer { if (timer != nil) { [timer invalidate]; [timer release]; timer = nil; } } #if OOLITE_MAC_OS_X - (void) recenterVirtualJoystick { // FIXME: does this really need to be spread across GameController and MyOpenGLView? -- Ahruman 2011-01-22 my_mouse_x = my_mouse_y = 0; // center mouse [gameView setVirtualJoystick:0.0 :0.0]; } - (IBAction) showLogAction:sender { [[NSWorkspace sharedWorkspace] openFile:[OOLogHandlerGetLogBasePath() stringByAppendingPathComponent:@"Previous.log"]]; } - (IBAction) showLogFolderAction:sender { [[NSWorkspace sharedWorkspace] openFile:OOLogHandlerGetLogBasePath()]; } // Helpers to allow -snapshotsURLCreatingIfNeeded: code to be identical here and in dock tile plug-in. static id GetPreference(NSString *key, Class expectedClass) { id result = [[NSUserDefaults standardUserDefaults] objectForKey:key]; if (expectedClass != Nil && ![result isKindOfClass:expectedClass]) result = nil; return result; } static void SetPreference(NSString *key, id value) { [[NSUserDefaults standardUserDefaults] setObject:value forKey:key]; } static void RemovePreference(NSString *key) { [[NSUserDefaults standardUserDefaults] removeObjectForKey:key]; } #define kSnapshotsDirRefKey @"snapshots-directory-reference" #define kSnapshotsDirNameKey @"snapshots-directory-name" - (NSURL *) snapshotsURLCreatingIfNeeded:(BOOL)create { BOOL stale = NO; NSDictionary *snapshotDirDict = GetPreference(kSnapshotsDirRefKey, [NSDictionary class]); NSURL *url = nil; NSString *name = DESC(@"snapshots-directory-name-mac"); if (snapshotDirDict != nil) { url = JAURLFromPersistentFileReference(snapshotDirDict, kJAPersistentFileReferenceWithoutUI | kJAPersistentFileReferenceWithoutMounting, &stale); if (url != nil) { NSString *existingName = [[url path] lastPathComponent]; if ([existingName compare:name options:NSCaseInsensitiveSearch] != 0) { // Check name from previous access, because we might have changed localizations. NSString *originalOldName = GetPreference(kSnapshotsDirNameKey, [NSString class]); if (originalOldName == nil || [existingName compare:originalOldName options:NSCaseInsensitiveSearch] != 0) { url = nil; } } // did we put the old directory in the trash? Boolean inTrash = false; const UInt8* utfPath = (UInt8*)[[url path] UTF8String]; OSStatus err = DetermineIfPathIsEnclosedByFolder(kOnAppropriateDisk, kTrashFolderType, utfPath, false, &inTrash); // if so, create a new directory. if (err == noErr && inTrash == true) url = nil; } } if (url == nil) { NSString *path = nil; NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES); if ([searchPaths count] > 0) { path = [[searchPaths objectAtIndex:0] stringByAppendingPathComponent:name]; } url = [NSURL fileURLWithPath:path]; if (url != nil) { stale = YES; if (create) { NSFileManager *fmgr = [NSFileManager defaultManager]; if (![fmgr fileExistsAtPath:path]) { [fmgr oo_createDirectoryAtPath:path attributes:nil]; } } } } if (stale) { snapshotDirDict = JAPersistentFileReferenceFromURL(url); if (snapshotDirDict != nil) { SetPreference(kSnapshotsDirRefKey, snapshotDirDict); SetPreference(kSnapshotsDirNameKey, [[url path] lastPathComponent]); } else { RemovePreference(kSnapshotsDirRefKey); } } return url; } - (IBAction) showSnapshotsAction:sender { [[NSWorkspace sharedWorkspace] openURL:[self snapshotsURLCreatingIfNeeded:YES]]; } - (IBAction) showAddOnsAction:sender { NSArray *paths = ResourceManager.userRootPaths; // Look for an AddOns directory that actually contains some AddOns. for (NSString *path in paths) { if ([self addOnsExistAtPath:path]) { [self openPath:path]; return; } } // If that failed, look for an AddOns directory that actually exists. for (NSString *path in paths) { if ([self isDirectoryAtPath:path]) { [self openPath:path]; return; } } // None found, create the default path. [NSFileManager.defaultManager createDirectoryAtPath:paths[0] withIntermediateDirectories:YES attributes:nil error:NULL]; [self openPath:paths[0]]; } - (BOOL) isDirectoryAtPath:(NSString *)path { BOOL isDirectory; return [NSFileManager.defaultManager fileExistsAtPath:path isDirectory:&isDirectory] && isDirectory; } - (BOOL) addOnsExistAtPath:(NSString *)path { if (![self isDirectoryAtPath:path]) return NO; NSWorkspace *workspace = NSWorkspace.sharedWorkspace; for (NSString *subPath in [NSFileManager.defaultManager enumeratorAtPath:path]) { subPath = [path stringByAppendingPathComponent:subPath]; NSString *type = [workspace typeOfFile:subPath error:NULL]; if ([workspace type:type conformsToType:@"org.aegidian.oolite.expansion"]) return YES; } return NO; } - (void) openPath:(NSString *)path { [NSWorkspace.sharedWorkspace openURL:[NSURL fileURLWithPath:path]]; } - (BOOL) validateMenuItem:(NSMenuItem *)menuItem { SEL action = menuItem.action; if (action == @selector(showLogAction:)) { // the first path is always Resources return ([[NSFileManager defaultManager] fileExistsAtPath:[OOLogHandlerGetLogBasePath() stringByAppendingPathComponent:@"Previous.log"]]); } if (action == @selector(showAddOnsAction:)) { // Always enabled in unrestricted mode, to allow users to add OXPs more easily. return [ResourceManager useAddOns]; } if (action == @selector(showSnapshotsAction:)) { BOOL pathIsDirectory; if(![[NSFileManager defaultManager] fileExistsAtPath:[self snapshotsURLCreatingIfNeeded:NO].path isDirectory:&pathIsDirectory]) { return NO; } return pathIsDirectory; } if (action == @selector(toggleFullScreenAction:)) { if (_fullScreenController.fullScreenMode) { // NOTE: not DESC, because menu titles are not generally localizable. menuItem.title = NSLocalizedString(@"Exit Full Screen", NULL); } else { menuItem.title = NSLocalizedString(@"Enter Full Screen", NULL); } } // default return YES; } - (NSMenu *)applicationDockMenu:(NSApplication *)sender { return dockMenu; } #elif OOLITE_SDL - (NSURL *) snapshotsURLCreatingIfNeeded:(BOOL)create { NSURL *url = [NSURL fileURLWithPath:[NSHomeDirectory() stringByAppendingPathComponent:DESC(@"snapshots-directory-name")]]; if (create) { NSString *path = [url path]; NSFileManager *fmgr = [NSFileManager defaultManager]; if (![fmgr fileExistsAtPath:path]) { [fmgr createDirectoryAtPath:path attributes:nil]; } } return url; } #else #error Unknown environment! #endif - (void) logProgress:(NSString *)message { if (![UNIVERSE doingStartUp]) return; #if OOLITE_MAC_OS_X [splashProgressTextField setStringValue:message]; [splashProgressTextField display]; OOProfilerPointMarker(message); #endif if([message length] > 0) { OOLog(@"startup.progress", @"===== [%.2f s] %@", -[_splashStart timeIntervalSinceNow], message); } } #if OO_DEBUG #if OOLITE_MAC_OS_X - (BOOL) debugMessageTrackingIsOn { return splashProgressTextField != nil; } - (NSString *) debugMessageCurrentString { return [splashProgressTextField stringValue]; } #else - (BOOL) debugMessageTrackingIsOn { return OOLogWillDisplayMessagesInClass(@"startup.progress"); } - (NSString *) debugMessageCurrentString { return @""; } #endif - (void) debugLogProgress:(NSString *)format, ... { va_list args; va_start(args, format); [self debugLogProgress:format arguments:args]; va_end(args); } - (void) debugLogProgress:(NSString *)format arguments:(va_list)arguments { NSString *message = [[[NSString alloc] initWithFormat:format arguments:arguments] autorelease]; [self logProgress:message]; } static NSMutableArray *sMessageStack; - (void) debugPushProgressMessage:(NSString *)format, ... { if ([self debugMessageTrackingIsOn]) { if (sMessageStack == nil) sMessageStack = [[NSMutableArray alloc] init]; [sMessageStack addObject:[self debugMessageCurrentString]]; va_list args; va_start(args, format); [self debugLogProgress:format arguments:args]; va_end(args); } OOLogIndentIf(@"startup.progress"); } - (void) debugPopProgressMessage { OOLogOutdentIf(@"startup.progress"); if ([sMessageStack count] > 0) { NSString *message = [sMessageStack lastObject]; if ([message length] > 0) [self logProgress:message]; [sMessageStack removeLastObject]; } } #endif - (void) endSplashScreen { OOLogSetDisplayMessagesInClass(@"startup.progress", NO); #if OOLITE_MAC_OS_X // These views will be released when we replace the content view. splashProgressTextField = nil; splashView = nil; [gameWindow setAcceptsMouseMovedEvents:YES]; [gameWindow setContentView:gameView]; [gameWindow makeFirstResponder:gameView]; #elif OOLITE_SDL [gameView endSplashScreen]; #endif } #if OOLITE_MAC_OS_X // NIB methods - (void)awakeFromNib { NSString *path = nil; // Set contents of Help window path = [[NSBundle mainBundle] pathForResource:@"OoliteReadMe" ofType:@"pdf"]; if (path != nil) { PDFDocument *document = [[PDFDocument alloc] initWithURL:[NSURL fileURLWithPath:path]]; [helpView setDocument:document]; [document release]; } [helpView setBackgroundColor:[NSColor whiteColor]]; } // delegate methods - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename { if ([[filename pathExtension] isEqual:@"oolite-save"]) { [self setPlayerFileToLoad:filename]; [self setPlayerFileDirectory:filename]; return YES; } if ([[filename pathExtension] isEqualToString:@"oxp"]) { BOOL dir_test; [[NSFileManager defaultManager] fileExistsAtPath:filename isDirectory:&dir_test]; if (dir_test) { if (expansionPathsToInclude == nil) { expansionPathsToInclude = [[NSMutableArray alloc] init]; } [expansionPathsToInclude addObject:filename]; return YES; } } return NO; } - (void) exitAppWithContext:(NSString *)context { [gameView.window orderOut:nil]; [(OoliteApp *)NSApp setExitContext:context]; [NSApp terminate:self]; } - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { [[OOCacheManager sharedCache] finishOngoingFlush]; OOLoggingTerminate(); return NSTerminateNow; } #elif OOLITE_SDL - (void) exitAppWithContext:(NSString *)context { OOLog(@"exit.context", @"Exiting: %@.", context); #if (OOLITE_GNUSTEP && !defined(NDEBUG)) [[OODebugMonitor sharedDebugMonitor] applicationWillTerminate]; #endif [[NSUserDefaults standardUserDefaults] synchronize]; OOLog(@"gameController.exitApp",@".GNUstepDefaults synchronized."); OOLoggingTerminate(); SDL_Quit(); [[OOOpenALController sharedController] shutdown]; exit(0); } #else #error Unknown environment! #endif - (void) exitAppCommandQ { [self exitAppWithContext:@"Command-Q"]; } - (void)windowDidResize:(NSNotification *)aNotification { [gameView updateScreen]; } - (NSString *) playerFileToLoad { return playerFileToLoad; } - (void) setPlayerFileToLoad:(NSString *)filename { if (playerFileToLoad) [playerFileToLoad autorelease]; playerFileToLoad = nil; if ([[[filename pathExtension] lowercaseString] isEqual:@"oolite-save"]) playerFileToLoad = [filename copy]; } - (NSString *) playerFileDirectory { if (playerFileDirectory == nil) { playerFileDirectory = [[NSUserDefaults standardUserDefaults] stringForKey:@"save-directory"]; if (playerFileDirectory != nil && ![[NSFileManager defaultManager] fileExistsAtPath:playerFileDirectory]) { playerFileDirectory = nil; } if (playerFileDirectory == nil) playerFileDirectory = [[NSFileManager defaultManager] defaultCommanderPath]; [playerFileDirectory retain]; } return playerFileDirectory; } - (void) setPlayerFileDirectory:(NSString *)filename { if (playerFileDirectory != nil) { [playerFileDirectory autorelease]; playerFileDirectory = nil; } if ([[[filename pathExtension] lowercaseString] isEqual:@"oolite-save"]) { filename = [filename stringByDeletingLastPathComponent]; } playerFileDirectory = [filename retain]; [[NSUserDefaults standardUserDefaults] setObject:filename forKey:@"save-directory"]; } - (void)reportUnhandledStartupException:(NSException *)exception { OOLog(@"startup.exception", @"***** Unhandled exception during startup: %@ (%@).", [exception name], [exception reason]); #if OOLITE_MAC_OS_X // Display an error alert. // TODO: provide better information on reporting bugs in the manual, and refer to it here. NSRunCriticalAlertPanel(@"Oolite failed to start up, because an unhandled exception occurred.", @"An exception of type %@ occurred. If this problem persists, please file a bug report.", @"OK", NULL, NULL, [exception name]); #endif } - (void)setUpBasicOpenGLStateWithSize:(NSSize)viewSize { OOOpenGLExtensionManager *extMgr = [OOOpenGLExtensionManager sharedManager]; float ratio = 0.5; float aspect = viewSize.height/viewSize.width; OOGL(glClearColor(0.0, 0.0, 0.0, 0.0)); OOGL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); OOGL(glClearDepth(1.0)); OOGL(glViewport(0, 0, viewSize.width, viewSize.height)); OOGLResetProjection(); // reset matrix OOGLFrustum(-ratio, ratio, -aspect*ratio, aspect*ratio, 1.0, MAX_CLEAR_DEPTH); // set projection matrix OOGL(glDepthFunc(GL_LESS)); // depth buffer if (UNIVERSE) { [UNIVERSE setLighting]; } else { GLfloat black[4] = {0.0, 0.0, 0.0, 1.0}; GLfloat white[] = {1.0, 1.0, 1.0, 1.0}; GLfloat stars_ambient[] = {0.25, 0.2, 0.25, 1.0}; OOGL(glLightfv(GL_LIGHT1, GL_AMBIENT, black)); OOGL(glLightfv(GL_LIGHT1, GL_SPECULAR, white)); OOGL(glLightfv(GL_LIGHT1, GL_DIFFUSE, white)); OOGL(glLightfv(GL_LIGHT1, GL_POSITION, black)); OOGL(glLightModelfv(GL_LIGHT_MODEL_AMBIENT, stars_ambient)); } if ([extMgr usePointSmoothing]) OOGL(glEnable(GL_POINT_SMOOTH)); if ([extMgr useLineSmoothing]) OOGL(glEnable(GL_LINE_SMOOTH)); // world's simplest OpenGL optimisations... #if GL_APPLE_transform_hint if ([extMgr haveExtension:@"GL_APPLE_transform_hint"]) { OOGL(glHint(GL_TRANSFORM_HINT_APPLE, GL_FASTEST)); } #endif OOGL(glDisable(GL_NORMALIZE)); OOGL(glDisable(GL_RESCALE_NORMAL)); #if GL_VERSION_1_2 // For OpenGL 1.2 or later, we want GL_SEPARATE_SPECULAR_COLOR all the time. if ([extMgr versionIsAtLeastMajor:1 minor:2]) { OOGL(glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR)); } #endif } #ifndef NDEBUG /* This method exists purely to suppress Clang static analyzer warnings that these ivars are unused (but may be used by categories, which they are). */ - (BOOL) suppressClangStuff { return pauseSelector && pauseTarget; } #endif @end #if OOLITE_MAC_OS_X static void SetUpSparkle(void) { #define FEED_URL_BASE "http://www.oolite.org/updates/" #define TEST_RELEASE_FEED_NAME "oolite-mac-test-release-appcast.xml" #define DEPLOYMENT_FEED_NAME "oolite-mac-appcast.xml" #define TEST_RELEASE_FEED_URL (@ FEED_URL_BASE TEST_RELEASE_FEED_NAME) #define DEPLOYMENT_FEED_URL (@ FEED_URL_BASE DEPLOYMENT_FEED_NAME) // Default to test releases in test release or debug builds, and stable releases for deployment builds. #ifdef NDEBUG #define DEFAULT_TEST_RELEASE 0 #else #define DEFAULT_TEST_RELEASE 1 #endif BOOL useTestReleases = [[NSUserDefaults standardUserDefaults] oo_boolForKey:@"use-test-release-updates" defaultValue:DEFAULT_TEST_RELEASE]; SUUpdater *updater = [SUUpdater sharedUpdater]; [updater setFeedURL:[NSURL URLWithString:useTestReleases ? TEST_RELEASE_FEED_URL : DEPLOYMENT_FEED_URL]]; } #endif oolite-1.82/src/Core/GuiDisplayGen.h000066400000000000000000000324761256642440500173170ustar00rootroot00000000000000/* GuiDisplayGen.h Class handling interface elements, primarily text, that are not part of the 3D game world, together with GuiDisplayGen. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #import "OOMaths.h" #import "OOTypes.h" #include #define GUI_DEFAULT_COLUMNS 6 #define GUI_DEFAULT_ROWS 30 #define GUI_MAX_ROWS 64 #define GUI_MAX_COLUMNS 40 #define MAIN_GUI_PIXEL_HEIGHT 480 #define MAIN_GUI_PIXEL_WIDTH 480 #define MAIN_GUI_ROW_HEIGHT 16 #define MAIN_GUI_ROW_WIDTH 16 #define MAIN_GUI_PIXEL_ROW_START 40 typedef enum { GUI_ALIGN_LEFT, GUI_ALIGN_RIGHT, GUI_ALIGN_CENTER } OOGUIAlignment; typedef enum { GUI_BACKGROUND_SPECIAL_NONE, GUI_BACKGROUND_SPECIAL_SHORT, GUI_BACKGROUND_SPECIAL_LONG, GUI_BACKGROUND_SPECIAL_LONG_ANA_SHORTEST, GUI_BACKGROUND_SPECIAL_LONG_ANA_QUICKEST } OOGUIBackgroundSpecial; #define GUI_KEY_OK @"OK" #define GUI_KEY_SKIP @"SKIP-ROW" // globals static NSString * const kGuiDefaultTextColor = @"default_text_color"; static NSString * const kGuiScreenTitleColor = @"screen_title_color"; static NSString * const kGuiScreenDividerColor = @"screen_divider_color"; static NSString * const kGuiSelectedRowBackgroundColor = @"selected_row_background_color"; static NSString * const kGuiSelectedRowColor = @"selected_row_color"; static NSString * const kGuiTextInputCursorColor = @"text_input_cursor_color"; // F3 static NSString * const kGuiEquipmentCashColor = @"equipment_cash_color"; static NSString * const kGuiEquipmentUnavailableColor = @"equipment_unavailable_color"; static NSString * const kGuiEquipmentScrollColor = @"equipment_scroll_color"; static NSString * const kGuiEquipmentOptionColor = @"equipment_option_color"; static NSString * const kGuiEquipmentRepairColor = @"equipment_repair_color"; static NSString * const kGuiEquipmentDescriptionColor = @"equipment_description_color"; static NSString * const kGuiEquipmentLaserColor = @"equipment_laser_color"; static NSString * const kGuiEquipmentLaserFittedColor = @"equipment_laser_fitted_color"; static NSString * const kGuiEquipmentTabs = @"equipment_tabs"; // F3 F3 static NSString * const kGuiShipyardHeadingColor = @"shipyard_heading_color"; static NSString * const kGuiShipyardScrollColor = @"shipyard_scroll_color"; static NSString * const kGuiShipyardEntryColor = @"shipyard_entry_color"; static NSString * const kGuiShipyardNoshipColor = @"shipyard_noship_color"; static NSString * const kGuiShipyardTradeinColor = @"shipyard_tradein_color"; static NSString * const kGuiShipyardDescriptionColor = @"shipyard_description_color"; static NSString * const kGuiShipyardTabs = @"shipyard_tabs"; // F4 static NSString * const kGuiInterfaceHeadingColor = @"interface_heading_color"; static NSString * const kGuiInterfaceScrollColor = @"interface_scroll_color"; static NSString * const kGuiInterfaceEntryColor = @"interface_entry_color"; static NSString * const kGuiInterfaceDescriptionColor = @"interface_description_color"; static NSString * const kGuiInterfaceNoneColor = @"interface_none_color"; static NSString * const kGuiInterfaceTabs = @"interface_tabs"; // F5 static NSString * const kGuiStatusShipnameColor = @"status_shipname_color"; static NSString * const kGuiStatusDataColor = @"status_data_color"; static NSString * const kGuiStatusEquipmentHeadingColor = @"status_equipment_heading_color"; static NSString * const kGuiStatusEquipmentScrollColor = @"status_equipment_scroll_color"; static NSString * const kGuiStatusEquipmentOkColor = @"status_equipment_ok_color"; static NSString * const kGuiStatusEquipmentDamagedColor = @"status_equipment_damaged_color"; static NSString * const kGuiStatusTabs = @"status_tabs"; static NSString * const kGuiStatusPrioritiseDamaged = @"status_prioritise_damaged"; // F5 F5 static NSString * const kGuiManifestSubheadColor = @"manifest_subhead_color"; static NSString * const kGuiManifestEntryColor = @"manifest_entry_color"; static NSString * const kGuiManifestScrollColor = @"manifest_scroll_color"; static NSString * const kGuiManifestNoScrollColor = @"manifest_no_scroll_color"; static NSString * const kGuiManifestTabs = @"manifest_tabs"; // F6 static NSString * const kGuiChartLabelScale = @"chart_label_scale"; static NSString * const kGuiChartCircleScale = @"chart_circle_scale"; static NSString * const kGuiChartLabelColor = @"chart_label_color"; static NSString * const kGuiChartLabelReachableColor = @"chart_labelreachable_color"; static NSString * const kGuiChartRangeColor = @"chart_range_color"; static NSString * const kGuiChartCrosshairColor = @"chart_crosshair_color"; static NSString * const kGuiChartCursorColor = @"chart_cursor_color"; static NSString * const kGuiChartMatchBoxColor = @"chart_match_box_color"; static NSString * const kGuiChartMatchLabelColor = @"chart_match_label_color"; static NSString * const kGuiChartConnectionColor = @"chart_connection_color"; static NSString * const kGuiChartCurrentJumpStartColor = @"chart_currentjumpstart_color"; static NSString * const kGuiChartCurrentJumpEndColor = @"chart_currentjumpend_color"; static NSString * const kGuiChartRouteShortColor = @"chart_route_short_color"; static NSString * const kGuiChartRouteQuickColor = @"chart_route_quick_color"; static NSString * const kGuiChartTraveltimeTabs = @"chart_traveltime_tabs"; static NSString * const kGuiChartEconomyUColor = @"chart_economy_%lu_color"; static NSString * const kGuiChartGovernmentUColor = @"chart_government_%lu_color"; static NSString * const kGuiChartTechColor = @"chart_tech_color"; // F7 static NSString * const kGuiSystemdataFactsColor = @"systemdata_facts_color"; static NSString * const kGuiSystemdataDescriptionColor = @"systemdata_description_color"; static NSString * const kGuiSystemdataTabs = @"systemdata_tabs"; // F8 static NSString * const kGuiMarketHeadingColor = @"market_heading_color"; static NSString * const kGuiMarketCommodityColor = @"market_commodity_color"; static NSString * const kGuiMarketScrollColor = @"market_scroll_color"; static NSString * const kGuiMarketFilteredAllColor = @"market_filtered_all_color"; static NSString * const kGuiMarketFilterInfoColor = @"market_filter_info_color"; static NSString * const kGuiMarketCashColor = @"market_cash_color"; // F8 F8 extras static NSString * const kGuiMarketContractedColor = @"market_contracted_color"; static NSString * const kGuiMarketDescriptionColor = @"market_description_color"; static NSString * const kGuiMarketTabs = @"market_tabs"; // Docking report static NSString * const kGuiDockingReportColor = @"docking_report_color"; static NSString * const kGuiDockingSummaryColor = @"docking_summary_color"; static NSString * const kGuiDockingContinueColor = @"docking_continue_color"; @class OOSound, OOColor, OOTexture, OOTextureSprite, HeadUpDisplay; typedef NSInteger OOGUIRow; // -1 for none typedef int OOGUITabStop; // negative value = right align text typedef OOGUITabStop OOGUITabSettings[GUI_MAX_COLUMNS]; @interface GuiDisplayGen: NSObject { @private NSSize size_in_pixels; unsigned n_columns; unsigned n_rows; int pixel_row_center; unsigned pixel_row_height; int pixel_row_start; NSSize pixel_text_size; BOOL showAdvancedNavArray; NSSize pixel_title_size; OOColor *backgroundColor; OOColor *textColor; OOTextureSprite *backgroundSprite; OOTextureSprite *foregroundSprite; OOGUIBackgroundSpecial backgroundSpecial; NSString *title; NSMutableArray *rowText; NSMutableArray *rowKey; NSMutableArray *rowColor; Vector drawPosition; NSPoint rowPosition[GUI_MAX_ROWS]; OOGUIAlignment rowAlignment[GUI_MAX_ROWS]; float rowFadeTime[GUI_MAX_ROWS]; OOGUITabSettings tabStops; NSDictionary *guiUserSettings; NSRange rowRange; OOGUIRow selectedRow; NSRange selectableRange; BOOL showTextCursor; OOGUIRow currentRow; GLfloat max_alpha; // main alpha setting GLfloat fade_alpha; // for fade-in / fade-out GLfloat fade_sign; // -1.0 to 1.0 NSUInteger statusPage; // status screen: paging equipped items OOSystemID foundSystem; } - (id) init; - (id) initWithPixelSize:(NSSize)gui_size columns:(int)gui_cols rows:(int)gui_rows rowHeight:(int)gui_row_height rowStart:(int)gui_row_start title:(NSString*)gui_title; - (void) resizeWithPixelSize:(NSSize)gui_size columns:(int)gui_cols rows:(int)gui_rows rowHeight:(int)gui_row_height rowStart:(int)gui_row_start title:(NSString*) gui_title; - (void) resizeTo:(NSSize)gui_size characterHeight:(int)csize title:(NSString*)gui_title; - (NSSize)size; - (unsigned)columns; - (unsigned)rows; - (unsigned)rowHeight; - (int)rowStart; - (NSString *)title; - (void) setTitle:(NSString *)str; - (void) dealloc; - (void) setDrawPosition:(Vector) vector; - (Vector) drawPosition; - (NSDictionary *) userSettings; - (void) fadeOutFromTime:(OOTimeAbsolute) now_time overDuration:(OOTimeDelta) duration; - (void) stopFadeOuts; - (GLfloat) alpha; - (void) setAlpha:(GLfloat) an_alpha; - (void) setMaxAlpha:(GLfloat) an_alpha; - (void) setBackgroundColor:(OOColor*) color; - (void) setTextColor:(OOColor*) color; - (OOColor *) colorFromSetting:(NSString *)setting defaultValue:(OOColor *)def; - (void) setGLColorFromSetting:(NSString *)setting defaultValue:(OOColor *)def alpha:(GLfloat)alpha; - (void) setCharacterSize:(NSSize) character_size; - (void) setShowAdvancedNavArray:(BOOL)inFlag; - (void) setColor:(OOColor *)color forRow:(OOGUIRow)row; - (id) objectForRow:(OOGUIRow)row; - (NSString *) keyForRow:(OOGUIRow)row; - (OOGUIRow) rowForKey:(NSString*)key; - (OOGUIRow) selectedRow; - (BOOL) setSelectedRow:(OOGUIRow)row; - (BOOL) setNextRow:(int) direction; - (BOOL) setFirstSelectableRow; - (BOOL) setLastSelectableRow; - (void) setNoSelectedRow; - (NSString *) selectedRowText; - (NSString *) selectedRowKey; - (void) setShowTextCursor:(BOOL) yesno; - (void) setCurrentRow:(OOGUIRow) value; - (NSRange) selectableRange; - (void) setSelectableRange:(NSRange) range; - (void) setTabStops:(OOGUITabSettings)stops; - (void) overrideTabs:(OOGUITabSettings)stops from:(NSString *)setting length:(NSUInteger)len; - (void) clear; - (void) clearAndKeepBackground:(BOOL)keepBackground; - (void) setKey:(NSString *)str forRow:(OOGUIRow)row; - (void) setText:(NSString *)str forRow:(OOGUIRow)row; - (void) setText:(NSString *)str forRow:(OOGUIRow)row align:(OOGUIAlignment)alignment; - (NSString *) reflowTextForMFD:(NSString *)input; - (OOGUIRow) addLongText:(NSString *)str startingAtRow:(OOGUIRow)row align:(OOGUIAlignment)alignment; - (void) printLongText:(NSString *)str align:(OOGUIAlignment)alignment color:(OOColor *)text_color fadeTime:(float)text_fade key:(NSString *)text_key addToArray:(NSMutableArray *)text_array; - (void) printLineNoScroll:(NSString *)str align:(OOGUIAlignment)alignment color:(OOColor *)text_color fadeTime:(float)text_fade key:(NSString *)text_key addToArray:(NSMutableArray *)text_array; - (void) setArray:(NSArray *)arr forRow:(OOGUIRow)row; - (void) insertItemsFromArray:(NSArray *)items withKeys:(NSArray *)item_keys intoRow:(OOGUIRow)row color:(OOColor *)text_color; ///////////////////////////////////////////////////// - (void) scrollUp:(int) how_much; /* allows the use of special dynamic backgrounds */ - (void) setBackgroundTextureSpecial:(OOGUIBackgroundSpecial)spec withBackground:(BOOL)withBackground; /* A background/foreground texture descriptor is a dictionary with a string property keyed "name" and optional number properties keyed "width" and "height". */ - (BOOL) setBackgroundTextureDescriptor:(NSDictionary *)descriptor; - (BOOL) setForegroundTextureDescriptor:(NSDictionary *)descriptor; - (BOOL) setBackgroundTextureKey:(NSString *)key; - (BOOL) setForegroundTextureKey:(NSString *)key; - (BOOL) preloadGUITexture:(NSDictionary *)descriptor; /* Interpret a JavaScript value as a texture descriptor for -[GUIDisplayGen set{Background|Foreground}TextureDescriptor:]. Also starts preloading the texture. callerDescription is a string describing the context in which this was called, generally a method name (like "mission.runScreen()") for warning generation. Requires a request on context. */ - (NSDictionary *) textureDescriptorFromJSValue:(jsval)value inContext:(JSContext *)context callerDescription:(NSString *)callerDescription; - (void) clearBackground; - (void) leaveLastLine; - (NSArray *) getLastLines; - (int) drawGUI:(GLfloat) alpha drawCursor:(BOOL) drawCursor; - (void) drawGUIBackground; - (void) setStatusPage:(NSInteger) pageNum; - (NSUInteger) statusPage; - (void) refreshStarChart; - (void) setStarChartTitle; - (OOSystemID) targetNextFoundSystem:(int)direction; @end oolite-1.82/src/Core/GuiDisplayGen.m000066400000000000000000002266041256642440500173220ustar00rootroot00000000000000/* GuiDisplayGen.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "GuiDisplayGen.h" #import "Universe.h" #import "GameController.h" #import "PlayerEntity.h" #import "PlayerEntityControls.h" #import "OOTextureSprite.h" #import "ResourceManager.h" #import "OOSound.h" #import "OOStringExpander.h" #import "OOStringParsing.h" #import "HeadUpDisplay.h" #import "OOCollectionExtractors.h" #import "OOTexture.h" #import "OOJavaScriptEngine.h" #import "PlayerEntityStickProfile.h" #import "OOSystemDescriptionManager.h" OOINLINE BOOL RowInRange(OOGUIRow row, NSRange range) { return ((int)range.location <= row && row < (int)(range.location + range.length)); } @interface GuiDisplayGen (Internal) - (void) drawGLDisplay:(GLfloat)x :(GLfloat)y :(GLfloat)z :(GLfloat) alpha; - (void) drawCrossHairsWithSize:(GLfloat) size x:(GLfloat)x y:(GLfloat)y z:(GLfloat)z; - (void) drawStarChart:(GLfloat)x :(GLfloat)y :(GLfloat)z :(GLfloat) alpha; - (void) drawGalaxyChart:(GLfloat)x :(GLfloat)y :(GLfloat)z :(GLfloat) alpha; - (void) drawSystemMarkers:(NSArray *)marker atX:(GLfloat)x andY:(GLfloat)y andZ:(GLfloat)z withAlpha:(GLfloat)alpha andScale:(GLfloat)scale; - (void) drawSystemMarker:(NSDictionary *)marker atX:(GLfloat)x andY:(GLfloat)y andZ:(GLfloat)z withAlpha:(GLfloat)alpha andScale:(GLfloat)scale; - (void) drawEquipmentList:(NSArray *)eqptList z:(GLfloat)z; - (void) drawAdvancedNavArrayAtX:(float)x y:(float)y z:(float)z alpha:(float)alpha usingRoute:(NSDictionary *) route optimizedBy:(OORouteType) optimizeBy zoom: (OOScalar) zoom; @end @implementation GuiDisplayGen static BOOL _refreshStarChart = NO; - (id) init { if ((self = [super init])) { size_in_pixels = NSMakeSize(MAIN_GUI_PIXEL_WIDTH, MAIN_GUI_PIXEL_HEIGHT); n_columns = GUI_DEFAULT_COLUMNS; n_rows = GUI_DEFAULT_ROWS; pixel_row_center = size_in_pixels.width / 2; pixel_row_height = MAIN_GUI_ROW_HEIGHT; pixel_row_start = MAIN_GUI_PIXEL_ROW_START; // first position down the page... max_alpha = 1.0; pixel_text_size = NSMakeSize(0.9f * pixel_row_height, pixel_row_height); // main gui has 18x20 characters pixel_title_size = NSMakeSize(pixel_row_height * 1.75f, pixel_row_height * 1.5f); int stops[6] = {0, 192, 256, 320, 384, 448}; unsigned i; rowRange = NSMakeRange(0,n_rows); rowText = [[NSMutableArray alloc] initWithCapacity:n_rows]; // alloc retains rowKey = [[NSMutableArray alloc] initWithCapacity:n_rows]; // alloc retains rowColor = [[NSMutableArray alloc] initWithCapacity:n_rows]; // alloc retains for (i = 0; i < n_rows; i++) { [rowText addObject:@"."]; [rowKey addObject:[NSString stringWithFormat:@"%d",i]]; [rowColor addObject:[OOColor yellowColor]]; rowPosition[i].x = 0.0f; rowPosition[i].y = size_in_pixels.height - (pixel_row_start + i * pixel_row_height); rowAlignment[i] = GUI_ALIGN_LEFT; } for (i = 0; i < n_columns; i++) { tabStops[i] = stops[i]; } title = @""; textColor = [[OOColor yellowColor] retain]; drawPosition = make_vector(0.0f, 0.0f, 640.0f); backgroundSpecial = GUI_BACKGROUND_SPECIAL_NONE; guiUserSettings = [[ResourceManager dictionaryFromFilesNamed:@"gui-settings.plist" inFolder:@"Config" andMerge:YES] retain]; } return self; } - (id) initWithPixelSize:(NSSize)gui_size columns:(int)gui_cols rows:(int)gui_rows rowHeight:(int)gui_row_height rowStart:(int)gui_row_start title:(NSString*)gui_title { self = [super init]; size_in_pixels = gui_size; n_columns = gui_cols; n_rows = gui_rows; pixel_row_center = size_in_pixels.width / 2; pixel_row_height = gui_row_height; pixel_row_start = gui_row_start; // first position down the page... max_alpha = 1.0; pixel_text_size = NSMakeSize(pixel_row_height, pixel_row_height); pixel_title_size = NSMakeSize(pixel_row_height * 1.75f, pixel_row_height * 1.5f); unsigned i; rowRange = NSMakeRange(0,n_rows); rowText = [[NSMutableArray alloc] initWithCapacity:n_rows]; // alloc retains rowKey = [[NSMutableArray alloc] initWithCapacity:n_rows]; // alloc retains rowColor = [[NSMutableArray alloc] initWithCapacity:n_rows]; // alloc retains for (i = 0; i < n_rows; i++) { [rowText addObject:@""]; [rowKey addObject:@""]; [rowColor addObject:[OOColor greenColor]]; rowPosition[i].x = 0.0f; rowPosition[i].y = size_in_pixels.height - (pixel_row_start + i * pixel_row_height); rowAlignment[i] = GUI_ALIGN_LEFT; } title = [gui_title retain]; textColor = [[OOColor yellowColor] retain]; return self; } - (void) dealloc { [backgroundSprite release]; [foregroundSprite release]; [backgroundColor release]; [textColor release]; [title release]; [rowText release]; [rowKey release]; [rowColor release]; [guiUserSettings release]; [super dealloc]; } - (void) resizeWithPixelSize:(NSSize)gui_size columns:(int)gui_cols rows:(int)gui_rows rowHeight:(int)gui_row_height rowStart:(int)gui_row_start title:(NSString*) gui_title { [self clear]; // size_in_pixels = gui_size; n_columns = gui_cols; n_rows = gui_rows; pixel_row_center = size_in_pixels.width / 2; pixel_row_height = gui_row_height; pixel_row_start = gui_row_start; // first position down the page... pixel_text_size = NSMakeSize(pixel_row_height, pixel_row_height); pixel_title_size = NSMakeSize(pixel_row_height * 1.75f, pixel_row_height * 1.5f); rowRange = NSMakeRange(0,n_rows); [self clear]; // [self setTitle: gui_title]; } - (void) resizeTo:(NSSize)gui_size characterHeight:(int)csize title:(NSString*)gui_title { [self clear]; // size_in_pixels = gui_size; n_columns = gui_size.width / csize; n_rows = (int)gui_size.height / csize; [self setTitle: gui_title]; pixel_row_center = gui_size.width / 2; pixel_row_height = csize; currentRow = n_rows - 1; // first position down the page... if (title != nil) pixel_row_start = 2.75f * csize + 0.5f * (gui_size.height - n_rows * csize); else pixel_row_start = csize + 0.5f * (gui_size.height - n_rows * csize); [rowText removeAllObjects]; [rowKey removeAllObjects]; [rowColor removeAllObjects]; unsigned i; for (i = 0; i < n_rows; i++) { [rowText addObject:@""]; [rowKey addObject:@""]; [rowColor addObject:[OOColor greenColor]]; rowPosition[i].x = 0.0f; rowPosition[i].y = size_in_pixels.height - (pixel_row_start + i * pixel_row_height); rowAlignment[i] = GUI_ALIGN_LEFT; } pixel_text_size = NSMakeSize(csize, csize); pixel_title_size = NSMakeSize(csize * 1.75f, csize * 1.5f); OOLog(@"gui.reset", @"gui %@ reset to rows:%d columns:%d start:%d", self, n_rows, n_columns, pixel_row_start); rowRange = NSMakeRange(0,n_rows); [self clear]; } - (NSSize)size { return size_in_pixels; } - (unsigned)columns { return n_columns; } - (unsigned)rows { return n_rows; } - (unsigned)rowHeight { return pixel_row_height; } - (int)rowStart { return pixel_row_start; } - (NSString *)title { return title; } - (void) setTitle:(NSString *)str { if (str != title) { [title release]; if ([str length] == 0) str = nil; title = [str copy]; } } - (void) setDrawPosition:(Vector) vector { drawPosition = vector; } - (Vector) drawPosition { return drawPosition; } - (NSDictionary *) userSettings { return guiUserSettings; } - (void) fadeOutFromTime:(OOTimeAbsolute) now_time overDuration:(OOTimeDelta) duration { if (fade_alpha <= 0.0f) { return; } if (duration == 0.0) fade_sign = -1000.0f; else fade_sign = (float)(-fade_alpha / duration); } - (void) stopFadeOuts { fade_sign = 0.0f; } - (GLfloat) alpha { return fade_alpha; } - (void) setAlpha:(GLfloat) an_alpha { fade_alpha = an_alpha * max_alpha; } - (void) setMaxAlpha:(GLfloat) an_alpha { max_alpha = an_alpha; } - (void) setBackgroundColor:(OOColor*) color { [backgroundColor release]; backgroundColor = [color retain]; } // default text colour for rows - (void) setTextColor:(OOColor*) color { [textColor release]; if (color == nil) color = [[OOColor yellowColor] retain]; textColor = [color retain]; } - (OOColor *) colorFromSetting:(NSString *)setting defaultValue:(OOColor *)def { OOColor *col = nil; if (setting != nil) { col = [OOColor colorWithDescription:[guiUserSettings objectForKey:setting]]; } if (col == nil) { if (def != nil) { col = def; // def = nil => use default_text_color } else { col = textColor; } } return [[col copy] autorelease]; } - (void) setGLColorFromSetting:(NSString *)setting defaultValue:(OOColor *)def alpha:(GLfloat)alpha { GLfloat r,g,b,a; OOColor *col = [self colorFromSetting:setting defaultValue:def]; [col getRed:&r green:&g blue:&b alpha:&a]; OOGL(glColor4f(r, g, b, a*alpha)); } - (void) setCharacterSize:(NSSize) character_size { pixel_text_size = character_size; } - (void)setShowAdvancedNavArray:(BOOL)inFlag { showAdvancedNavArray = inFlag; } - (void) setColor:(OOColor *) color forRow:(OOGUIRow)row { if (RowInRange(row, rowRange)) [rowColor replaceObjectAtIndex:row withObject:color]; } - (id) objectForRow:(OOGUIRow)row { if (RowInRange(row, rowRange)) return [rowText objectAtIndex:row]; else return NULL; } - (OOGUIRow) rowForKey:(NSString*)key { for (unsigned i=0;i<[rowKey count];i++) { if ([key isEqualToString:[rowKey objectAtIndex:i]]) { return (OOGUIRow)i; } } return -1; } - (NSString*) keyForRow:(OOGUIRow)row { if (RowInRange(row, rowRange)) return [rowKey objectAtIndex:row]; else return NULL; } - (OOGUIRow) selectedRow { if (RowInRange(selectedRow, selectableRange)) return selectedRow; else return -1; } - (BOOL) setSelectedRow:(OOGUIRow)row { if ((row == selectedRow)&&RowInRange(row, selectableRange)) return YES; if (RowInRange(row, selectableRange)) { if (![[rowKey objectAtIndex:row] isEqual:GUI_KEY_SKIP]) { selectedRow = row; return YES; } } return NO; } - (BOOL) setNextRow:(int) direction { OOGUIRow row = selectedRow + direction; while (RowInRange(row, selectableRange)) { if (![[rowKey objectAtIndex:row] isEqual:GUI_KEY_SKIP]) { selectedRow = row; return YES; } row += direction; } return NO; } - (BOOL) setFirstSelectableRow { NSUInteger row = selectableRange.location; while (RowInRange(row, selectableRange)) { if (![[rowKey objectAtIndex:row] isEqual:GUI_KEY_SKIP]) { selectedRow = row; return YES; } row++; } selectedRow = -1; return NO; } - (BOOL) setLastSelectableRow { NSUInteger row = selectableRange.location + selectableRange.length - 1; while (RowInRange(row, selectableRange)) { if (![[rowKey objectAtIndex:row] isEqual:GUI_KEY_SKIP]) { selectedRow = row; return YES; } row--; } selectedRow = -1; return NO; } - (void) setNoSelectedRow { selectedRow = -1; } - (NSString *) selectedRowText { if ([[rowText objectAtIndex:selectedRow] isKindOfClass:[NSString class]]) return (NSString *)[rowText objectAtIndex:selectedRow]; if ([[rowText objectAtIndex:selectedRow] isKindOfClass:[NSArray class]]) return (NSString *)[[rowText objectAtIndex:selectedRow] objectAtIndex:0]; return NULL; } - (NSString *) selectedRowKey { if ((selectedRow < 0)||((unsigned)selectedRow > [rowKey count])) return nil; else return (NSString *)[rowKey objectAtIndex:selectedRow]; } - (void) setShowTextCursor:(BOOL) yesno { showTextCursor = yesno; } - (void) setCurrentRow:(OOGUIRow) value { if ((value < 0)||((unsigned)value >= n_rows)) { showTextCursor = NO; currentRow = -1; } else { currentRow = value; } } - (NSRange) selectableRange { return selectableRange; } - (void) setSelectableRange:(NSRange) range { selectableRange = range; } - (void) setTabStops:(OOGUITabSettings)stops { if (stops != NULL) memmove(tabStops, stops, sizeof tabStops); } - (void) overrideTabs:(OOGUITabSettings)stops from:(NSString *)setting length:(NSUInteger)len { NSArray *override = [guiUserSettings oo_arrayForKey:setting defaultValue:nil]; NSUInteger i; if (stops != NULL && override != nil) { if (len > GUI_MAX_COLUMNS) { len = GUI_MAX_COLUMNS; } for (i=0;i 0)) { [string1 appendString:(NSString *)[words objectAtIndex:0]]; [string1 appendString:@" "]; [words removeObjectAtIndex:0]; strsize = OORectFromString(string1, 0.0f, 0.0f, chSize).size; if ([words count] > 0) strsize.width += OORectFromString((NSString *)[words objectAtIndex:0], 0.0f, 0.0f, chSize).size.width; } [string2 appendString:[words componentsJoinedByString:@" "]]; [self setText:string1 forRow:row align:alignment]; return [self addLongText:string2 startingAtRow:row+1 align:alignment]; } } - (NSString *) reflowTextForMFD:(NSString *)input { NSMutableString *output = [[NSMutableString alloc] initWithCapacity:512]; NSArray *lines = [input componentsSeparatedByString:@"\n"]; NSSize chSize = pixel_text_size; NSUInteger limit = chSize.width * 15; NSString *line = nil; foreach (line, lines) { NSMutableArray *words = ScanTokensFromString(line); NSMutableString *accum = [NSMutableString stringWithCapacity:64]; if ([words count] > 0) { while ([words count] > 1) { [accum appendString:[words oo_stringAtIndex:0]]; [accum appendString:@" "]; if (OORectFromString(accum, 0.0f, 0.0f,chSize).size.width + OORectFromString([words oo_stringAtIndex:1], 0.0f, 0.0f,chSize).size.width > limit) { // can't fit next word on this line [output appendString:[accum stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]]; [output appendString:@"\n"]; [accum setString:@""]; } [words removeObjectAtIndex:0]; } [output appendString:accum]; [output appendString:[words oo_stringAtIndex:0]]; } [output appendString:@"\n"]; } return [output autorelease]; } - (void) leaveLastLine { unsigned i; for (i=0; i < n_rows-1; i++) { [rowText replaceObjectAtIndex:i withObject:@""]; [rowColor replaceObjectAtIndex:i withObject:textColor]; [rowKey replaceObjectAtIndex:i withObject:@""]; rowAlignment[i] = GUI_ALIGN_LEFT; rowFadeTime[i] = 0.0f; } rowFadeTime[i] = 0.4f; // fade the last line... } - (NSArray *) getLastLines // text, colour, fade time - text, colour, fade time { if (n_rows <1) return nil; // we have at least 1 row! unsigned i = n_rows-1; OORGBAComponents col = [(OOColor *)[rowColor objectAtIndex:i] rgbaComponents]; if (i>0) { // we have at least 2 rows! OORGBAComponents col0 = [(OOColor *)[rowColor objectAtIndex:i-1] rgbaComponents]; return [NSArray arrayWithObjects:[rowText oo_stringAtIndex:i-1], [NSString stringWithFormat:@"%.3g %.3g %.3g %.3g", col0.r, col0.g, col0.b, col0.a], [NSNumber numberWithFloat:rowFadeTime[i-1]], [rowText oo_stringAtIndex:i], [NSString stringWithFormat:@"%.3g %.3g %.3g %.3g", col.r, col.g, col.b, col.a], [NSNumber numberWithFloat:rowFadeTime[i]], nil]; } else { return [NSArray arrayWithObjects:[rowText oo_stringAtIndex:i], [NSString stringWithFormat:@"%.3g %.3g %.3g %.3g", col.r, col.g, col.b, col.a], [NSNumber numberWithFloat:rowFadeTime[i]], nil]; } } - (void) printLongText:(NSString *)str align:(OOGUIAlignment) alignment color:(OOColor *)text_color fadeTime:(float)text_fade key:(NSString *)text_key addToArray:(NSMutableArray *)text_array { // print a multi-line message // if ([str rangeOfString:@"\n"].location != NSNotFound) { NSArray *lines = [str componentsSeparatedByString:@"\n"]; unsigned i; for (i = 0; i < [lines count]; i++) [self printLongText:[lines oo_stringAtIndex:i] align:alignment color:text_color fadeTime:text_fade key:text_key addToArray:text_array]; return; } OOGUIRow row = currentRow; if (row == (OOGUIRow)n_rows - 1) [self scrollUp:1]; NSSize chSize = pixel_text_size; NSSize strsize = OORectFromString(str, 0.0f, 0.0f, chSize).size; if (strsize.width < size_in_pixels.width) { [self setText:str forRow:row align:alignment]; if (text_color) [self setColor:text_color forRow:row]; if (text_key) [self setKey:text_key forRow:row]; rowFadeTime[row] = text_fade; if (currentRow < (OOGUIRow)n_rows - 1) currentRow++; if (text_array) [text_array addObject:str]; } else { NSMutableArray *words = ScanTokensFromString(str); NSMutableString *string1 = [NSMutableString stringWithCapacity:256]; NSMutableString *string2 = [NSMutableString stringWithCapacity:256]; strsize.width = 0.0f; while ((strsize.width < size_in_pixels.width)&&([words count] > 0)) { [string1 appendString:(NSString *)[words objectAtIndex:0]]; [string1 appendString:@" "]; [words removeObjectAtIndex:0]; strsize = OORectFromString(string1, 0.0f, 0.0f, chSize).size; if ([words count] > 0) strsize.width += OORectFromString([words oo_stringAtIndex:0], 0.0f, 0.0f, chSize).size.width; } [self setText:string1 forRow:row align:alignment]; [string2 appendString:[words componentsJoinedByString:@" "]]; if (text_color) [self setColor:text_color forRow:row]; if (text_key) [self setKey:text_key forRow:row]; if (text_array) [text_array addObject:string1]; rowFadeTime[row] = text_fade; [self printLongText:string2 align:alignment color:text_color fadeTime:text_fade key:text_key addToArray:text_array]; } } - (void) printLineNoScroll:(NSString *)str align:(OOGUIAlignment)alignment color:(OOColor *)text_color fadeTime:(float)text_fade key:(NSString *)text_key addToArray:(NSMutableArray *)text_array { [self setText:str forRow:currentRow align:alignment]; if (text_color) [self setColor:text_color forRow:currentRow]; if (text_key) [self setKey:text_key forRow:currentRow]; if (text_array) [text_array addObject:str]; rowFadeTime[currentRow] = text_fade; } - (void) setArray:(NSArray *)arr forRow:(OOGUIRow)row { if (RowInRange(row, rowRange)) [rowText replaceObjectAtIndex:row withObject:arr]; } - (void) insertItemsFromArray:(NSArray *)items withKeys:(NSArray *)item_keys intoRow:(OOGUIRow)row color:(OOColor *)text_color { if (!items) return; if([items count] == 0) return; NSUInteger n_items = [items count]; if ((item_keys)&&([item_keys count] != n_items)) { // throw exception [NSException raise:@"ArrayLengthMismatchException" format:@"The NSArray sent as 'item_keys' to insertItemsFromArray::: must contain the same number of objects as the NSArray 'items'"]; } unsigned i; for (i = n_rows; i >= row + n_items ; i--) { [self setKey:[self keyForRow:i - n_items] forRow:i]; id old_row_info = [self objectForRow:i - n_items]; if ([old_row_info isKindOfClass:[NSArray class]]) [self setArray:old_row_info forRow:i]; if ([old_row_info isKindOfClass:[NSString class]]) [self setText:(NSString *)old_row_info forRow:i]; } for (i = 0; i < n_items; i++) { id new_row_info = [items objectAtIndex:i]; if (text_color) [self setColor:text_color forRow: row + i]; else [self setColor:textColor forRow: row + i]; if ([new_row_info isKindOfClass:[NSArray class]]) [self setArray:new_row_info forRow: row + i]; if ([new_row_info isKindOfClass:[NSString class]]) [self setText:(NSString *)new_row_info forRow: row + i]; if (item_keys) [self setKey:[item_keys objectAtIndex:i] forRow: row + i]; else [self setKey:@"" forRow: row + i]; } } - (void) scrollUp:(int) how_much { unsigned i; for (i = 0; i + how_much < n_rows; i++) { [rowText replaceObjectAtIndex:i withObject:[rowText objectAtIndex: i + how_much]]; [rowColor replaceObjectAtIndex:i withObject:[rowColor objectAtIndex: i + how_much]]; [rowKey replaceObjectAtIndex:i withObject:[rowKey objectAtIndex: i + how_much]]; rowAlignment[i] = rowAlignment[i + how_much]; rowFadeTime[i] = rowFadeTime[i + how_much]; } for (; i < n_rows; i++) { [rowText replaceObjectAtIndex:i withObject:@""]; [rowColor replaceObjectAtIndex:i withObject:textColor]; [rowKey replaceObjectAtIndex:i withObject:@""]; rowAlignment[i] = GUI_ALIGN_LEFT; rowFadeTime[i] = 0.0f; } } - (void) clearBackground { [self setBackgroundTextureDescriptor:nil]; [self setForegroundTextureDescriptor:nil]; } static OOTexture *TextureForGUITexture(NSDictionary *descriptor) { return [OOTexture textureWithName:[descriptor oo_stringForKey:@"name"] inFolder:@"Images" options:kOOTextureDefaultOptions | kOOTextureNoShrink anisotropy:kOOTextureDefaultAnisotropy lodBias:kOOTextureDefaultLODBias]; } /* Load a texture sprite given a descriptor. The caller owns a reference to the result. */ static OOTextureSprite *NewTextureSpriteWithDescriptor(NSDictionary *descriptor) { OOTexture *texture = nil; NSSize size; texture = TextureForGUITexture(descriptor); if (texture == nil) return nil; double specifiedWidth = [descriptor oo_doubleForKey:@"width" defaultValue:-INFINITY]; double specifiedHeight = [descriptor oo_doubleForKey:@"height" defaultValue:-INFINITY]; BOOL haveWidth = isfinite(specifiedWidth); BOOL haveHeight = isfinite(specifiedHeight); if (haveWidth && haveHeight) { // Both specified, use directly without calling -originalDimensions (which may block). size.width = specifiedWidth; size.height = specifiedHeight; } else { NSSize originalDimensions = [texture originalDimensions]; if (haveWidth) { // Width specified, but not height; preserve aspect ratio. CGFloat ratio = originalDimensions.height / originalDimensions.width; size.width = specifiedWidth; size.height = ratio * size.width; } else if (haveHeight) { // Height specified, but not width; preserve aspect ratio. CGFloat ratio = originalDimensions.width / originalDimensions.height; size.height = specifiedHeight; size.width = ratio * size.height; } else { // Neither specified; use backwards-compatible behaviour. size = originalDimensions; } } return [[OOTextureSprite alloc] initWithTexture:texture size:size]; } - (void) setBackgroundTextureSpecial:(OOGUIBackgroundSpecial)spec withBackground:(BOOL)withBackground { if (withBackground) { NSDictionary *bgDescriptor = nil; OOGalaxyID galaxy_number = [PLAYER galaxyNumber]; switch (spec) { case GUI_BACKGROUND_SPECIAL_SHORT: bgDescriptor = [UNIVERSE screenTextureDescriptorForKey:@"short_range_chart_mission"]; if (bgDescriptor == nil) { bgDescriptor = [UNIVERSE screenTextureDescriptorForKey:@"short_range_chart"]; } break; case GUI_BACKGROUND_SPECIAL_LONG: case GUI_BACKGROUND_SPECIAL_LONG_ANA_SHORTEST: case GUI_BACKGROUND_SPECIAL_LONG_ANA_QUICKEST: bgDescriptor = [UNIVERSE screenTextureDescriptorForKey:[NSString stringWithFormat:@"long_range_chart%d_mission", galaxy_number+1]]; if (bgDescriptor == nil) { bgDescriptor = [UNIVERSE screenTextureDescriptorForKey:@"long_range_chart_mission"]; if (bgDescriptor == nil) { bgDescriptor = [UNIVERSE screenTextureDescriptorForKey:[NSString stringWithFormat:@"long_range_chart%d", galaxy_number+1]]; if (bgDescriptor == nil) { bgDescriptor = [UNIVERSE screenTextureDescriptorForKey:@"long_range_chart"]; } } } break; case GUI_BACKGROUND_SPECIAL_NONE: break; } if (bgDescriptor != nil) { [self setBackgroundTextureDescriptor:bgDescriptor]; } } backgroundSpecial = spec; [self refreshStarChart]; } - (BOOL) setBackgroundTextureDescriptor:(NSDictionary *)descriptor { [backgroundSprite autorelease]; backgroundSpecial = GUI_BACKGROUND_SPECIAL_NONE; // reset backgroundSprite = NewTextureSpriteWithDescriptor(descriptor); return backgroundSprite != nil; } - (BOOL) setForegroundTextureDescriptor:(NSDictionary *)descriptor { [foregroundSprite autorelease]; foregroundSprite = NewTextureSpriteWithDescriptor(descriptor); return foregroundSprite != nil; } - (BOOL) setBackgroundTextureKey:(NSString *)key { return [self setBackgroundTextureDescriptor:[UNIVERSE screenTextureDescriptorForKey:key]]; } - (BOOL) setForegroundTextureKey:(NSString *)key { return [self setForegroundTextureDescriptor:[UNIVERSE screenTextureDescriptorForKey:key]]; } - (BOOL) preloadGUITexture:(NSDictionary *)descriptor { return TextureForGUITexture(descriptor) != nil; } - (NSDictionary *) textureDescriptorFromJSValue:(jsval)value inContext:(JSContext *)context callerDescription:(NSString *)callerDescription { OOJS_PROFILE_ENTER NSDictionary *result = nil; if (JSVAL_IS_OBJECT(value)) { // Null may be used to indicate no texture. if (JSVAL_IS_NULL(value)) return [NSDictionary dictionary]; JSObject *objValue = JSVAL_TO_OBJECT(value); if (OOJSGetClass(context, objValue) != [[OOJavaScriptEngine sharedEngine] stringClass]) { result = OOJSDictionaryFromJSObject(context, objValue); } } if (result == nil) { NSString *name = OOStringFromJSValue(context, value); if (name != nil) { result = [NSDictionary dictionaryWithObject:name forKey:@"name"]; if ([name length] == 0) return result; // Explicit empty string may be used to indicate no texture. } } // Start loading the texture, and return nil if it doesn't exist. if (result != nil && ![self preloadGUITexture:result]) { OOJSReportWarning(context, @"%@: texture \"%@\" could not be found.", callerDescription, [result oo_stringForKey:@"name"]); result = nil; } return result; OOJS_PROFILE_EXIT } - (void) setStatusPage:(NSInteger)pageNum { if (pageNum==0 || (pageNum < 0 && -pageNum >= statusPage)) { statusPage=1; } else { statusPage += pageNum; } } - (NSUInteger) statusPage { return statusPage; } - (void) drawEquipmentList:(NSArray *)eqptList z:(GLfloat)z { if ([eqptList count] == 0) return; OOGUIRow firstRow = STATUS_EQUIPMENT_FIRST_ROW; NSUInteger maxRows = STATUS_EQUIPMENT_MAX_ROWS; if ([[PLAYER hud] allowBigGui]) { maxRows += 6; } NSUInteger itemsPerColumn = maxRows; NSInteger firstY = 40; // firstRow =10 :-> 40 - firstRow=11 -> 24 etc... NSUInteger eqptCount = [eqptList count]; NSUInteger pageCount = 1; NSUInteger i; NSInteger start; NSArray *info = nil; NSString *name = nil; BOOL damaged; // Paging calculations. Assuming 10 lines we get - one page:20 items per page (ipp) // two pages: 18 ipp - three+ pages: 1st & last 18pp, middle pages 16ipp i = itemsPerColumn * 2 + 2; if (eqptCount > i) // don't fit in one page? { [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:YES]; i = itemsPerColumn * 4; // total items in the first and last pages itemsPerColumn--; // for all the middle pages. if (eqptCount <= i) // two pages { pageCount++; if (statusPage == 1) { start = 0; } else { statusPage = 2; start = i/statusPage; // for the for loop } } else // three or more { pageCount = ceil((float)(eqptCount-i)/(itemsPerColumn*2)) + 2; statusPage = (NSInteger)OOClampInteger(statusPage, 1, pageCount); start = (statusPage == 1) ? 0 : (statusPage-1) * itemsPerColumn * 2 + 2; } } else { statusPage = pageCount; // one page start = 0; } if (statusPage > 1) { [self setColor:[self colorFromSetting:kGuiStatusEquipmentScrollColor defaultValue:[OOColor greenColor]] forRow:firstRow]; [self setArray:[NSArray arrayWithObjects:DESC(@"gui-back"), @"", @" <-- ",nil] forRow:firstRow]; [self setKey:GUI_KEY_OK forRow:firstRow]; firstY -= 16; // start 1 row down! if (statusPage == pageCount) { [self setSelectableRange:NSMakeRange(firstRow, 1)]; [self setSelectedRow:firstRow]; } } if (statusPage < pageCount) { [self setColor:[self colorFromSetting:kGuiStatusEquipmentScrollColor defaultValue:[OOColor greenColor]] forRow:firstRow + maxRows]; [self setArray:[NSArray arrayWithObjects:DESC(@"gui-more"), @"", @" --> ",nil] forRow:firstRow + maxRows]; [self setKey:GUI_KEY_OK forRow:firstRow + maxRows]; if (statusPage == 1) { [self setSelectableRange:NSMakeRange(firstRow + maxRows, 1)]; [self setSelectedRow:firstRow + maxRows]; } } if (statusPage > 1 && statusPage < pageCount) { [self setSelectableRange:NSMakeRange(firstRow, firstRow + maxRows)]; // default selected row to 'More -->' if we are looking at one of the middle pages if ([self selectedRow] == -1) [self setSelectedRow:firstRow + maxRows]; } if (statusPage == 1 || statusPage == pageCount) itemsPerColumn++; eqptCount = (NSInteger)OOClampInteger(eqptCount, 1, start + itemsPerColumn * 2); for (i = start; i < eqptCount; i++) { info = [eqptList oo_arrayAtIndex:i]; name = [info oo_stringAtIndex:0]; if([name length] > 42) name = [[name substringToIndex:40] stringByAppendingString:@"..."]; damaged = ![info oo_boolAtIndex:1]; if (damaged) { // Damaged items show up orange. [self setGLColorFromSetting:@"status_equipment_damaged_color" defaultValue:[OOColor orangeColor] alpha:1.0]; } else { // Normal items in default colour [self setGLColorFromSetting:@"status_equipment_ok_color" defaultValue:nil alpha:1.0]; } if (i - start < itemsPerColumn) { OODrawString(name, -220, firstY - 16 * (NSInteger)(i - start), z, NSMakeSize(15, 15)); } else { OODrawString(name, 50, firstY - 16 * (NSInteger)(i - itemsPerColumn - start), z, NSMakeSize(15, 15)); } } } - (void) drawGUIBackground { GLfloat x = drawPosition.x; GLfloat y = drawPosition.y; GLfloat z = [[UNIVERSE gameView] display_z]; if (backgroundSprite!=nil) { [backgroundSprite blitBackgroundCentredToX:x Y:y Z:z alpha:1.0f]; } } - (void) refreshStarChart { _refreshStarChart = YES; } - (int) drawGUI:(GLfloat) alpha drawCursor:(BOOL) drawCursor { GLfloat x = drawPosition.x; GLfloat y = drawPosition.y; GLfloat z = [[UNIVERSE gameView] display_z]; if (alpha > 0.05f) { PlayerEntity* player = PLAYER; [self drawGLDisplay:x - 0.5f * size_in_pixels.width :y - 0.5f * size_in_pixels.height :z :alpha]; if (self == [UNIVERSE gui]) { if ([player guiScreen] == GUI_SCREEN_SHORT_RANGE_CHART || [player guiScreen] == GUI_SCREEN_LONG_RANGE_CHART || backgroundSpecial == GUI_BACKGROUND_SPECIAL_SHORT) { [self drawStarChart:x - 0.5f * size_in_pixels.width :y - 0.5f * size_in_pixels.height :z :alpha]; } if (backgroundSpecial == GUI_BACKGROUND_SPECIAL_LONG || backgroundSpecial == GUI_BACKGROUND_SPECIAL_LONG_ANA_QUICKEST || backgroundSpecial == GUI_BACKGROUND_SPECIAL_LONG_ANA_SHORTEST) { [self drawGalaxyChart:x - 0.5f * size_in_pixels.width :y - 0.5f * size_in_pixels.height :z :alpha]; } if ([player guiScreen] == GUI_SCREEN_STATUS) { [self drawEquipmentList:[player equipmentList] z:z]; } if ([player guiScreen] == GUI_SCREEN_STICKPROFILE) { [player stickProfileGraphAxisProfile: alpha screenAt: make_vector(x,y,z) screenSize: size_in_pixels]; } } if (fade_sign) { fade_alpha += (float)(fade_sign * [UNIVERSE getTimeDelta]); if (fade_alpha < 0.05f) // done fading out { fade_alpha = 0.0f; fade_sign = 0.0f; } if (fade_alpha >= max_alpha) // done fading in { fade_alpha = max_alpha; fade_sign = 0.0f; } } } int cursor_row = 0; if (drawCursor) { NSPoint vjpos = [[UNIVERSE gameView] virtualJoystickPosition]; double cursor_x = size_in_pixels.width * vjpos.x; if (cursor_x < -size_in_pixels.width * 0.5) cursor_x = -size_in_pixels.width * 0.5f; if (cursor_x > size_in_pixels.width * 0.5) cursor_x = size_in_pixels.width * 0.5f; double cursor_y = -size_in_pixels.height * vjpos.y; if (cursor_y < -size_in_pixels.height * 0.5) cursor_y = -size_in_pixels.height * 0.5f; if (cursor_y > size_in_pixels.height * 0.5) cursor_y = size_in_pixels.height * 0.5f; cursor_row = 1 + (float)floor((0.5f * size_in_pixels.height - pixel_row_start - cursor_y) / pixel_row_height); GLfloat h1 = 3.0f; GLfloat h3 = 9.0f; OOGL(glColor4f(0.6f, 0.6f, 1.0f, 0.5f)); // original value of (0.2f, 0.2f, 1.0f, 0.5f) too dark - Nikos 20130616 OOGL(GLScaledLineWidth(2.0f)); cursor_x += x; cursor_y += y; [[UNIVERSE gameView] setVirtualJoystick:cursor_x/size_in_pixels.width :-cursor_y/size_in_pixels.height]; OOGLBEGIN(GL_LINES); glVertex3f((float)cursor_x - h1, (float)cursor_y, z); glVertex3f((float)cursor_x - h3, (float)cursor_y, z); glVertex3f((float)cursor_x + h1, (float)cursor_y, z); glVertex3f((float)cursor_x + h3, (float)cursor_y, z); glVertex3f((float)cursor_x, (float)cursor_y - h1, z); glVertex3f((float)cursor_x, (float)cursor_y - h3, z); glVertex3f((float)cursor_x, (float)cursor_y + h1, z); glVertex3f((float)cursor_x, (float)cursor_y + h3, z); OOGLEND(); OOGL(GLScaledLineWidth(1.0f)); } return cursor_row; } - (void) drawGLDisplay:(GLfloat)x :(GLfloat)y :(GLfloat)z :(GLfloat) alpha { NSSize strsize; unsigned i; OOTimeDelta delta_t = [UNIVERSE getTimeDelta]; NSSize characterSize = pixel_text_size; NSSize titleCharacterSize = pixel_title_size; // do backdrop // if (backgroundColor) { OOGL(glColor4f([backgroundColor redComponent], [backgroundColor greenComponent], [backgroundColor blueComponent], alpha * [backgroundColor alphaComponent])); OOGLBEGIN(GL_QUADS); glVertex3f(x + 0.0f, y + 0.0f, z); glVertex3f(x + size_in_pixels.width, y + 0.0f, z); glVertex3f(x + size_in_pixels.width, y + size_in_pixels.height, z); glVertex3f(x + 0.0f, y + size_in_pixels.height, z); OOGLEND(); } // show the 'foreground', aka overlay! if (foregroundSprite != nil) { [foregroundSprite blitCentredToX:x + 0.5f * size_in_pixels.width Y:y + 0.5f * size_in_pixels.height Z:z alpha:alpha]; } if (!RowInRange(selectedRow, selectableRange)) selectedRow = -1; // out of Range; //// // drawing operations here if (title != nil) { // // draw the title // strsize = OORectFromString(title, 0.0f, 0.0f, titleCharacterSize).size; [self setGLColorFromSetting:kGuiScreenTitleColor defaultValue:[OOColor redColor] alpha:alpha]; OODrawString(title, x + pixel_row_center - strsize.width/2.0, y + size_in_pixels.height - pixel_title_size.height, z, titleCharacterSize); // draw a horizontal divider // [self setGLColorFromSetting:kGuiScreenDividerColor defaultValue:[OOColor colorWithWhite:0.75 alpha:1.0] alpha:alpha]; OOGLBEGIN(GL_QUADS); glVertex3f(x + 0, y + size_in_pixels.height - pixel_title_size.height + 4, z); glVertex3f(x + size_in_pixels.width, y + size_in_pixels.height - pixel_title_size.height + 4, z); glVertex3f(x + size_in_pixels.width, y + size_in_pixels.height - pixel_title_size.height + 2, z); glVertex3f(x + 0, y + size_in_pixels.height - pixel_title_size.height + 2, z); OOGLEND(); } // draw each row of text // OOStartDrawingStrings(); for (i = 0; i < n_rows; i++) { OOColor* row_color = (OOColor *)[rowColor objectAtIndex:i]; GLfloat row_alpha = alpha; if (rowFadeTime[i] > 0.0f) { rowFadeTime[i] -= (float)delta_t; if (rowFadeTime[i] <= 0.0f) { [rowText replaceObjectAtIndex:i withObject:@""]; rowFadeTime[i] = 0.0f; } if ((rowFadeTime[i] > 0.0f)&&(rowFadeTime[i] < 1.0)) row_alpha *= rowFadeTime[i]; } glColor4f([row_color redComponent], [row_color greenComponent], [row_color blueComponent], row_alpha); if ([[rowText objectAtIndex:i] isKindOfClass:[NSString class]]) { NSString* text = (NSString *)[rowText objectAtIndex:i]; if (![text isEqual:@""]) { strsize = OORectFromString(text, 0.0f, 0.0f, characterSize).size; switch (rowAlignment[i]) { case GUI_ALIGN_LEFT : rowPosition[i].x = 0.0f; break; case GUI_ALIGN_RIGHT : rowPosition[i].x = size_in_pixels.width - strsize.width; break; case GUI_ALIGN_CENTER : rowPosition[i].x = (size_in_pixels.width - strsize.width)/2.0f; break; } if (i == (unsigned)selectedRow) { NSRect block = OORectFromString(text, x + rowPosition[i].x + 2, y + rowPosition[i].y + 2, characterSize); OOStopDrawingStrings(); [self setGLColorFromSetting:kGuiSelectedRowBackgroundColor defaultValue:[OOColor redColor] alpha:alpha]; OOGLBEGIN(GL_QUADS); glVertex3f(block.origin.x, block.origin.y, z); glVertex3f(block.origin.x + block.size.width, block.origin.y, z); glVertex3f(block.origin.x + block.size.width, block.origin.y + block.size.height, z); glVertex3f(block.origin.x, block.origin.y + block.size.height, z); OOGLEND(); [self setGLColorFromSetting:kGuiSelectedRowColor defaultValue:[OOColor blackColor] alpha:alpha]; OOStartDrawingStrings(); } OODrawStringQuadsAligned(text, x + rowPosition[i].x, y + rowPosition[i].y, z, characterSize, NO); // draw cursor at end of current Row // if ((showTextCursor)&&(i == (unsigned)currentRow)) { NSRect tr = OORectFromString(text, 0.0f, 0.0f, characterSize); NSPoint cu = NSMakePoint(x + rowPosition[i].x + tr.size.width + 0.2f * characterSize.width, y + rowPosition[i].y); tr.origin = cu; tr.size.width = 0.5f * characterSize.width; GLfloat g_alpha = 0.5f * (1.0f + (float)sin(6 * [UNIVERSE getTime])); OOStopDrawingStrings(); [self setGLColorFromSetting:kGuiTextInputCursorColor defaultValue:[OOColor redColor] alpha:row_alpha*g_alpha]; OOGLBEGIN(GL_QUADS); glVertex3f(tr.origin.x, tr.origin.y, z); glVertex3f(tr.origin.x + tr.size.width, tr.origin.y, z); glVertex3f(tr.origin.x + tr.size.width, tr.origin.y + tr.size.height, z); glVertex3f(tr.origin.x, tr.origin.y + tr.size.height, z); OOGLEND(); OOStartDrawingStrings(); } } } if ([[rowText objectAtIndex:i] isKindOfClass:[NSArray class]]) { NSArray *array = [rowText oo_arrayAtIndex:i]; NSUInteger j, max_columns = MIN([array count], n_columns); BOOL isLeftAligned; for (j = 0; j < max_columns; j++) { NSString* text = [array oo_stringAtIndex:j]; if ([text length] != 0) { isLeftAligned = tabStops[j] >= 0; rowPosition[i].x = abs(tabStops[j]); // we don't want to highlight leading space(s) or narrow spaces (\037s) NSString *hilitedText = [text stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" \037"]]; NSRange txtRange = [text rangeOfString:hilitedText]; unsigned leadingSpaces = 0; if (EXPECT_NOT(txtRange.location == NSNotFound)) { // This never happens! hilitedText = text; } else if (txtRange.location > 0) { // padded string! NSRange padRange; padRange.location = 0; padRange.length = txtRange.location; NSRect charBlock = OORectFromString([text substringWithRange:padRange], 0, 0, characterSize); leadingSpaces = (unsigned)charBlock.size.width; /* // if we're displaying commodity-quantity-none, let's try and be pixel perfect! NSString *qtyNone = DESC(@"commodity-quantity-none"); txtRange = [hilitedText rangeOfString:qtyNone]; if (txtRange.location == 0) // bingo! { rowPosition[i].x += OORectFromString(@"0", 0, 0, characterSize).size.width - OORectFromString(qtyNone, 0, 0, characterSize).size.width; } */ } // baseline text rect, needed for correct highlight positioning. NSRect block = OORectFromString(text, x + rowPosition[i].x + 2, y + rowPosition[i].y + 2, characterSize); if(!isLeftAligned) { rowPosition[i].x -= block.size.width + 3; } block = OORectFromString(hilitedText, x + rowPosition[i].x + 1 + leadingSpaces, y + rowPosition[i].y + 2, characterSize); block.size.width += 3; if (i == (unsigned)selectedRow) { OOStopDrawingStrings(); [self setGLColorFromSetting:kGuiSelectedRowBackgroundColor defaultValue:[OOColor redColor] alpha:alpha]; OOGLBEGIN(GL_QUADS); glVertex3f(block.origin.x, block.origin.y, z); glVertex3f(block.origin.x + block.size.width, block.origin.y, z); glVertex3f(block.origin.x + block.size.width, block.origin.y + block.size.height, z); glVertex3f(block.origin.x, block.origin.y + block.size.height, z); OOGLEND(); [self setGLColorFromSetting:kGuiSelectedRowColor defaultValue:[OOColor blackColor] alpha:alpha]; OOStartDrawingStrings(); } OODrawStringQuadsAligned(text, x + rowPosition[i].x, y + rowPosition[i].y, z, characterSize,NO); } } } } OOStopDrawingStrings(); [OOTexture applyNone]; } - (void) drawCrossHairsWithSize:(GLfloat) size x:(GLfloat)x y:(GLfloat)y z:(GLfloat)z { OOGLBEGIN(GL_QUADS); glVertex3f(x - 1, y - size, z); glVertex3f(x + 1, y - size, z); glVertex3f(x + 1, y + size, z); glVertex3f(x - 1, y + size, z); glVertex3f(x - size, y - 1, z); glVertex3f(x + size, y - 1, z); glVertex3f(x + size, y + 1, z); glVertex3f(x - size, y + 1, z); OOGLEND(); } - (void) setStarChartTitle { PlayerEntity *player = PLAYER; OOGalaxyID galaxy_number = [player galaxyNumber]; NSInteger system_id = [UNIVERSE findSystemNumberAtCoords:[player cursor_coordinates] withGalaxy:[player galaxyNumber]]; NSString *location_key = [NSString stringWithFormat:@"long-range-chart-title-%d-%ld", galaxy_number, (long)system_id]; if ([[UNIVERSE descriptions] valueForKey:location_key] == nil) { NSString *gal_key = [NSString stringWithFormat:@"long-range-chart-title-%d", galaxy_number]; if ([[UNIVERSE descriptions] valueForKey:gal_key] == nil) { [self setTitle:[NSString stringWithFormat:DESC(@"long-range-chart-title-d"), galaxy_number+1]]; } else { [self setTitle:[UNIVERSE descriptionForKey:gal_key]]; } } else { [self setTitle:[UNIVERSE descriptionForKey:location_key]]; } } - (void) drawStarChart:(GLfloat)x :(GLfloat)y :(GLfloat)z :(GLfloat) alpha { PlayerEntity* player = PLAYER; if (!player) return; OOSystemDescriptionManager *systemManager = [UNIVERSE systemManager]; OOScalar zoom = [player chart_zoom]; NSPoint chart_centre_coordinates = [player adjusted_chart_centre]; NSPoint galaxy_coordinates = [player galaxy_coordinates]; NSPoint cursor_coordinates = [player cursor_coordinates]; OOLongRangeChartMode chart_mode = [player longRangeChartMode]; OOGalaxyID galaxy_id = [player galaxyNumber]; GLfloat r = 1.0, g = 1.0, b = 1.0; BOOL noNova; NSPoint cu; NSUInteger systemParameter; double fuel = 35.0 * [player dialFuel]; double hcenter = size_in_pixels.width/2.0; double hscale = size_in_pixels.width / (CHART_WIDTH_AT_MAX_ZOOM*zoom); double vscale = -size_in_pixels.height / (2*CHART_HEIGHT_AT_MAX_ZOOM*zoom); double vcenter = CHART_SCREEN_VERTICAL_CENTRE; double hoffset = hcenter - chart_centre_coordinates.x*hscale; double voffset = size_in_pixels.height - vcenter - chart_centre_coordinates.y*vscale; int i; double d, distance = 0.0, time = 0.0; NSPoint star; OOScalar pixelRatio; NSRect clipRect; OORouteType advancedNavArrayMode = [player ANAMode]; BOOL routeExists = NO; BOOL *systemsFound = [UNIVERSE systemsFound]; NSSize viewSize = [[UNIVERSE gameView] viewSize]; double aspect_ratio = viewSize.width / viewSize.height; #if OOLITE_MAC_OS_X // Fix for issue 136 BOOL issue_136_fix = [[NSUserDefaults standardUserDefaults] integerForKey: @"issue_136_fix"]; #endif // default colours - match those in HeadUpDisplay:OODrawPlanetInfo GLfloat govcol[] = { 0.5, 0.0, 0.7, 0.7, 0.5, 0.3, 0.0, 1.0, 0.3, 1.0, 0.8, 0.1, 1.0, 0.0, 0.0, 0.1, 0.5, 1.0, 0.7, 0.7, 0.7, 0.7, 1.0, 1.0}; if (aspect_ratio > 4.0/3.0) { pixelRatio = viewSize.height / 480.0; } else { pixelRatio = viewSize.width / 640.0; } clipRect = NSMakeRect((viewSize.width - size_in_pixels.width*pixelRatio)/2.0, (viewSize.height + size_in_pixels.height*pixelRatio)/2.0 - (pixel_title_size.height + 15 + (GUI_ROW_CHART_SYSTEM-2)*MAIN_GUI_ROW_HEIGHT) * pixelRatio, size_in_pixels.width * pixelRatio, (GUI_ROW_CHART_SYSTEM-1) * MAIN_GUI_ROW_HEIGHT * pixelRatio); #if OOLITE_MAC_OS_X // Fix for issue 136 if (issue_136_fix) { clipRect.origin.x *= 2; clipRect.origin.y *= 2; clipRect.size.width *= 2; clipRect.size.height *= 2; } #endif OOSystemID target = [PLAYER targetSystemID]; NSString *targetName = [UNIVERSE getSystemName:target]; double dx, dy; // get a list of systems marked as contract destinations NSDictionary* markedDestinations = [player markedDestinations]; // get present location cu = NSMakePoint((float)(hscale*galaxy_coordinates.x+hoffset),(float)(vscale*galaxy_coordinates.y+voffset)); // enable draw clipping; limit drawing area within the chart area OOGL(glEnable(GL_SCISSOR_TEST)); OOGL(glScissor(clipRect.origin.x, clipRect.origin.y, clipRect.size.width, clipRect.size.height)); if ([player hasHyperspaceMotor]) { // draw fuel range circle OOGL(GLScaledLineWidth(2.0f)); [self setGLColorFromSetting:kGuiChartRangeColor defaultValue:[OOColor greenColor] alpha:alpha]; GLDrawOval(x + cu.x, y + cu.y, z, NSMakeSize((float)(fuel*hscale), 2*(float)(fuel*vscale)), 5); } // Cache nearby systems so that [UNIVERSE generateSystemData:] does not get called on every frame // Caching code submitted by Y A J, 20091022 static OOGalaxyID saved_galaxy_id; static struct saved_system { OOSystemID sysid; int tec, eco, gov; NSString* p_name; BOOL nova; } nearby_systems[ 256 ]; static int num_nearby_systems = 0; if ( _refreshStarChart || galaxy_id != saved_galaxy_id) { // saved systems are stale; recompute _refreshStarChart = NO; for (i = 0; i < num_nearby_systems; i++) [nearby_systems[ i ].p_name release]; num_nearby_systems = 0; for (i = 0; i < 256; i++) { NSDictionary* sys_info = [UNIVERSE generateSystemData:i]; if (EXPECT_NOT([sys_info oo_boolForKey:@"sun_gone_nova"])) { nearby_systems[ num_nearby_systems ].gov = -1; // Flag up nova systems! } else { nearby_systems[ num_nearby_systems ].tec = [sys_info oo_intForKey:KEY_TECHLEVEL]; nearby_systems[ num_nearby_systems ].eco = [sys_info oo_intForKey:KEY_ECONOMY]; nearby_systems[ num_nearby_systems ].gov = [sys_info oo_intForKey:KEY_GOVERNMENT]; } nearby_systems[ num_nearby_systems ].sysid = i; nearby_systems[ num_nearby_systems ].p_name = [[sys_info oo_stringForKey:KEY_NAME] retain]; nearby_systems[ num_nearby_systems ].nova = [[UNIVERSE generateSystemData:i] oo_boolForKey:@"sun_gone_nova"]; num_nearby_systems++; } saved_galaxy_id = [player galaxyNumber]; } OOSystemID savedPlanetNumber = 0; OOSystemID savedDestNumber = 0; static NSDictionary *routeInfo = nil; if (advancedNavArrayMode != OPTIMIZED_BY_NONE && [player hasEquipmentItemProviding:@"EQ_ADVANCED_NAVIGATIONAL_ARRAY"]) { OOSystemID planetNumber = [PLAYER systemID]; OOSystemID destNumber = [PLAYER targetSystemID]; if (routeInfo == nil || planetNumber != savedPlanetNumber || destNumber != savedDestNumber) { [routeInfo release]; routeInfo = [[UNIVERSE routeFromSystem:planetNumber toSystem:destNumber optimizedBy:advancedNavArrayMode] retain]; savedPlanetNumber = planetNumber; savedDestNumber = destNumber; } target = destNumber; // if the ANA has been activated and we are in string input mode (i.e. planet search), // get out of it so that distance and time data can be displayed //if ([[[UNIVERSE gameView] typedString] length] > 0) [player clearPlanetSearchString]; if (routeInfo) routeExists = YES; [self drawAdvancedNavArrayAtX:x+hoffset y:y+voffset z:z alpha:alpha usingRoute: (planetNumber != destNumber ? (id)routeInfo : nil) optimizedBy:advancedNavArrayMode zoom: zoom]; if (routeExists) { distance = [routeInfo oo_doubleForKey:@"distance"]; time = [routeInfo oo_doubleForKey:@"time"]; if (distance == 0.0 && planetNumber != destNumber) { // zero-distance double fix distance = 0.1; time = 0.01; } } } else { [self drawAdvancedNavArrayAtX:x+hoffset y:y+voffset z:z alpha:alpha usingRoute:nil optimizedBy:OPTIMIZED_BY_NONE zoom: zoom]; } NSPoint targetCoordinates = (NSPoint){0,0}; if (!routeExists) { target = [UNIVERSE findSystemAtCoords:cursor_coordinates withGalaxy:galaxy_id]; targetCoordinates = [systemManager getCoordinatesForSystem:target inGalaxy:galaxy_id]; distance = distanceBetweenPlanetPositions(targetCoordinates.x,targetCoordinates.y,galaxy_coordinates.x,galaxy_coordinates.y); if (distance == 0.0) { if (target != [PLAYER systemID]) { // looking at the other half of a zero-distance double // distance is treated as 0.1 LY distance = 0.1; } } if ([player hasHyperspaceMotor] && distance <= [player fuel]/10.0) { time = distance * distance; } else { time = 0.0; } } // draw marks and stars // OOGL(GLScaledLineWidth(1.5f)); OOGL(glColor4f(1.0f, 1.0f, 0.75f, alpha)); // pale yellow for (i = 0; i < num_nearby_systems; i++) { NSPoint sys_coordinates = [systemManager getCoordinatesForSystem:i inGalaxy:galaxy_id]; dx = fabs(chart_centre_coordinates.x - sys_coordinates.x); dy = fabs(chart_centre_coordinates.y - sys_coordinates.y); if ((dx > zoom*(CHART_WIDTH_AT_MAX_ZOOM/2.0 + CHART_CLIP_BORDER))||(dy > zoom*(CHART_HEIGHT_AT_MAX_ZOOM + CHART_CLIP_BORDER))) continue; float blob_factor = [guiUserSettings oo_floatForKey:kGuiChartCircleScale defaultValue:0.0017]; float blob_size = (1.0f + blob_factor * [[systemManager getProperty:@"radius" forSystem:i inGalaxy:galaxy_id] floatValue])/zoom; if (blob_size < 0.5) blob_size = 0.5; star.x = (float)(sys_coordinates.x * hscale + hoffset); star.y = (float)(sys_coordinates.y * vscale + voffset); noNova = !nearby_systems[i].nova; NSAssert1(chart_mode <= OOLRC_MODE_TECHLEVEL, @"Long range chart mode %i out of range", (int)chart_mode); NSArray *markers = [markedDestinations objectForKey:[NSNumber numberWithInt:i]]; if (markers != nil) // is marked { GLfloat base_size = 0.5f * blob_size + 2.5f; [self drawSystemMarkers:markers atX:x+star.x andY:y+star.y andZ:z withAlpha:alpha andScale:base_size]; } switch (chart_mode) { case OOLRC_MODE_ECONOMY: if (EXPECT(noNova)) { systemParameter = nearby_systems[i].eco; GLfloat ce1 = 1.0f - 0.125f * systemParameter; [self setGLColorFromSetting:[NSString stringWithFormat:kGuiChartEconomyUColor, (unsigned long)systemParameter] defaultValue:[OOColor colorWithRed:ce1 green:1.0f blue:0.0f alpha:1.0f] alpha:1.0]; } else { r = g = b = 0.3; OOGL(glColor4f(r, g, b, alpha)); } break; case OOLRC_MODE_GOVERNMENT: if (EXPECT(noNova)) { systemParameter = nearby_systems[i].gov; [self setGLColorFromSetting:[NSString stringWithFormat:kGuiChartGovernmentUColor, (unsigned long)systemParameter] defaultValue:[OOColor colorWithRed:govcol[systemParameter*3] green:govcol[1+(systemParameter*3)] blue:govcol[2+(systemParameter*3)] alpha:1.0f] alpha:1.0]; } else { r = g = b = 0.3; OOGL(glColor4f(r, g, b, alpha)); } break; case OOLRC_MODE_TECHLEVEL: if (EXPECT(noNova)) { systemParameter = nearby_systems[i].tec; r = 0.6; g = b = 0.20 + (0.05 * (GLfloat)systemParameter); } else { r = g = b = 0.3; } OOGL(glColor4f(r, g, b, alpha)); break; case OOLRC_MODE_NORMAL: if (EXPECT(noNova)) { r = g = b = 1.0; OOColor *sunColor = [OOColor colorWithDescription:[[UNIVERSE systemManager] getProperty:@"sun_color" forSystem:i inGalaxy:galaxy_id]]; if (sunColor != nil) { [sunColor getRed:&r green:&g blue:&b alpha:&alpha]; alpha = 1.0; // reset } } else { r = 1.0; g = 0.2; b = 0.0; } OOGL(glColor4f(r, g, b, alpha)); break; } GLDrawFilledOval(x + star.x, y + star.y, z, NSMakeSize(blob_size,blob_size), 15); } // draw found stars and captions // GLfloat systemNameScale = [guiUserSettings oo_floatForKey:kGuiChartLabelScale defaultValue:1.0]; OOGL(GLScaledLineWidth(1.5f)); [self setGLColorFromSetting:kGuiChartMatchBoxColor defaultValue:[OOColor greenColor] alpha:alpha]; int n_matches = 0, foundIndex = -1; for (i = 0; i < 256; i++) if (systemsFound[i]) { if(foundSystem == n_matches) foundIndex = i; n_matches++; } if (n_matches == 0) { foundSystem = 0; } else if (backgroundSpecial == GUI_BACKGROUND_SPECIAL_LONG_ANA_SHORTEST || backgroundSpecial == GUI_BACKGROUND_SPECIAL_LONG_ANA_QUICKEST || backgroundSpecial == GUI_BACKGROUND_SPECIAL_LONG) { // do nothing at this stage } else { for (i = 0; i < 256; i++) { BOOL mark = systemsFound[i]; float marker_size = 8.0/zoom; NSPoint sys_coordinates = [systemManager getCoordinatesForSystem:i inGalaxy:galaxy_id]; dx = fabs(chart_centre_coordinates.x - sys_coordinates.x); dy = fabs(chart_centre_coordinates.y - sys_coordinates.y); if (mark && (dx <= zoom*(CHART_WIDTH_AT_MAX_ZOOM/2.0+CHART_CLIP_BORDER))&&(dy <= zoom*(CHART_HEIGHT_AT_MAX_ZOOM+CHART_CLIP_BORDER))) { star.x = (float)(sys_coordinates.x * hscale + hoffset); star.y = (float)(sys_coordinates.y * vscale + voffset); OOGLBEGIN(GL_LINE_LOOP); glVertex3f(x + star.x - marker_size, y + star.y - marker_size, z); glVertex3f(x + star.x + marker_size, y + star.y - marker_size, z); glVertex3f(x + star.x + marker_size, y + star.y + marker_size, z); glVertex3f(x + star.x - marker_size, y + star.y + marker_size, z); OOGLEND(); if (i == foundIndex || n_matches == 1) { if (n_matches == 1) foundSystem = 0; if (zoom > CHART_ZOOM_SHOW_LABELS && advancedNavArrayMode == OPTIMIZED_BY_NONE) { [self setGLColorFromSetting:kGuiChartMatchLabelColor defaultValue:[OOColor cyanColor] alpha:alpha]; OODrawString([UNIVERSE systemNameIndex:i] , x + star.x + 2.0, y + star.y - 10.0f, z, NSMakeSize(10*systemNameScale,10*systemNameScale)); [self setGLColorFromSetting:kGuiChartMatchBoxColor defaultValue:[OOColor greenColor] alpha:alpha]; } } else if (zoom > CHART_ZOOM_SHOW_LABELS) { OODrawString([UNIVERSE systemNameIndex:i] , x + star.x + 2.0, y + star.y - 10.0f, z, NSMakeSize(10*systemNameScale,10*systemNameScale)); } } } } // draw names // // OOGL(glColor4f(1.0f, 1.0f, 0.0f, alpha)); // yellow int targetIdx = -1; struct saved_system *sys; NSSize chSize = NSMakeSize(pixel_row_height*systemNameScale/zoom,pixel_row_height*systemNameScale/zoom); double jumpRange = MAX_JUMP_RANGE * [PLAYER dialFuel]; for (i = 0; i < num_nearby_systems; i++) { sys = nearby_systems + i; NSPoint sys_coordinates = [systemManager getCoordinatesForSystem:sys->sysid inGalaxy:galaxy_id]; dx = fabs(chart_centre_coordinates.x - sys_coordinates.x); dy = fabs(chart_centre_coordinates.y - sys_coordinates.y); if ((dx > zoom*(CHART_WIDTH_AT_MAX_ZOOM/2.0+CHART_CLIP_BORDER))||(dy > zoom*(CHART_HEIGHT_AT_MAX_ZOOM+CHART_CLIP_BORDER))) continue; star.x = (float)(sys_coordinates.x * hscale + hoffset); star.y = (float)(sys_coordinates.y * vscale + voffset); if (sys->sysid == target) // not overlapping twin? (example: Divees & Tezabi in galaxy 5) { targetIdx = i; // we have a winner! } if (zoom < CHART_ZOOM_SHOW_LABELS) { if (![player showInfoFlag]) // System's name { d = distanceBetweenPlanetPositions(galaxy_coordinates.x, galaxy_coordinates.y, sys_coordinates.x, sys_coordinates.y); if (d <= jumpRange) { [self setGLColorFromSetting:kGuiChartLabelReachableColor defaultValue:[OOColor yellowColor] alpha:alpha]; } else { [self setGLColorFromSetting:kGuiChartLabelColor defaultValue:[OOColor yellowColor] alpha:alpha]; } OODrawString(sys->p_name, x + star.x + 2.0, y + star.y, z, chSize); } else if (EXPECT(sys->gov >= 0)) // Not a nova? Show the info. { OODrawPlanetInfo(sys->gov, sys->eco, sys->tec, x + star.x + 2.0, y + star.y + 2.0, z, chSize); } } } // highlight the name of the currently selected system // (needed to get things right in closely-overlapping systems) if( targetIdx != -1 && zoom <= CHART_ZOOM_SHOW_LABELS) { sys = nearby_systems + targetIdx; NSPoint sys_coordinates = [systemManager getCoordinatesForSystem:sys->sysid inGalaxy:galaxy_id]; star.x = (float)(sys_coordinates.x * hscale + hoffset); star.y = (float)(sys_coordinates.y * vscale + voffset); if (![player showInfoFlag]) { d = distanceBetweenPlanetPositions(galaxy_coordinates.x, galaxy_coordinates.y, sys_coordinates.x, sys_coordinates.y); if (d <= jumpRange) { [self setGLColorFromSetting:kGuiChartLabelReachableColor defaultValue:[OOColor yellowColor] alpha:alpha]; } else { [self setGLColorFromSetting:kGuiChartLabelColor defaultValue:[OOColor yellowColor] alpha:alpha]; } OODrawHilightedString(sys->p_name, x + star.x + 2.0, y + star.y, z, chSize); } else if (sys->gov >= 0) // Not a nova? Show the info. { OODrawHilightedPlanetInfo(sys->gov, sys->eco, sys->tec, x + star.x + 2.0, y + star.y + 2.0, z, chSize); } } OOGUITabSettings tab_stops; tab_stops[0] = 0; tab_stops[1] = 96; tab_stops[2] = 288; [self overrideTabs:tab_stops from:kGuiChartTraveltimeTabs length:3]; [self setTabStops:tab_stops]; targetName = [[UNIVERSE getSystemName:target] retain]; // distance-f & est-travel-time-f are identical between short & long range charts in standard Oolite, however can be alterered separately via OXPs NSString *travelDistLine = @""; if (distance > 0) { travelDistLine = OOExpandKey(@"long-range-chart-distance", distance); } NSString *travelTimeLine = @""; if (time > 0) { travelTimeLine = OOExpandKey(@"long-range-chart-est-travel-time", time); } [self setArray:[NSArray arrayWithObjects:targetName, travelDistLine,travelTimeLine,nil] forRow:GUI_ROW_CHART_SYSTEM]; [targetName release]; // draw crosshairs over current location // [self setGLColorFromSetting:kGuiChartCrosshairColor defaultValue:[OOColor greenColor] alpha:alpha]; [self drawCrossHairsWithSize:14 x:x + cu.x y:y + cu.y z:z]; // draw crosshairs over cursor // [self setGLColorFromSetting:kGuiChartCursorColor defaultValue:[OOColor redColor] alpha:alpha]; cu = NSMakePoint((float)(hscale*cursor_coordinates.x+hoffset),(float)(vscale*cursor_coordinates.y+voffset)); [self drawCrossHairsWithSize:7 x:x + cu.x y:y + cu.y z:z]; // disable draw clipping OOGL(glDisable(GL_SCISSOR_TEST)); // Draw bottom divider [self setGLColorFromSetting:kGuiScreenDividerColor defaultValue:[OOColor colorWithWhite:0.75 alpha:1.0] alpha:alpha]; OOGLBEGIN(GL_QUADS); glVertex3f(x + 0, (float)(y + size_in_pixels.height - (GUI_ROW_CHART_SYSTEM-1)*MAIN_GUI_ROW_HEIGHT - pixel_title_size.height), z); glVertex3f(x + size_in_pixels.width, (GLfloat)(y + size_in_pixels.height - (GUI_ROW_CHART_SYSTEM-1)*MAIN_GUI_ROW_HEIGHT - pixel_title_size.height), z); glVertex3f(x + size_in_pixels.width, (GLfloat)(y + size_in_pixels.height - (GUI_ROW_CHART_SYSTEM-1)*MAIN_GUI_ROW_HEIGHT - pixel_title_size.height - 2), z); glVertex3f(x + 0, (GLfloat)(y + size_in_pixels.height - (GUI_ROW_CHART_SYSTEM-1)*MAIN_GUI_ROW_HEIGHT - pixel_title_size.height - 2), z); OOGLEND(); } - (void) drawSystemMarkers:(NSArray *)markers atX:(GLfloat)x andY:(GLfloat)y andZ:(GLfloat)z withAlpha:(GLfloat)alpha andScale:(GLfloat)scale { NSEnumerator *mEnum; NSDictionary *marker; for (mEnum = [markers objectEnumerator]; (marker = [mEnum nextObject]); ) { [self drawSystemMarker:marker atX:x andY:y andZ:z withAlpha:alpha andScale:scale]; } } - (void) drawSystemMarker:(NSDictionary *)marker atX:(GLfloat)x andY:(GLfloat)y andZ:(GLfloat)z withAlpha:(GLfloat)alpha andScale:(GLfloat)scale { NSString *colorDesc = [marker oo_stringForKey:@"markerColor" defaultValue:@"redColor"]; OORGBAComponents color = [[OOColor colorWithDescription:colorDesc] rgbaComponents]; OOGL(glColor4f(color.r, color.g, color.b, alpha)); // red GLfloat mark_size = [marker oo_floatForKey:@"markerScale" defaultValue:1.0]; if (mark_size > 2.0) { mark_size = 2.0; } else if (mark_size < 0.5) { mark_size = 0.5; } mark_size *= scale; NSString *shape = [marker oo_stringForKey:@"markerShape" defaultValue:@"MARKER_X"]; OOGLBEGIN(GL_LINES); if ([shape isEqualToString:@"MARKER_X"]) { glVertex3f(x - mark_size, y - mark_size, z); glVertex3f(x + mark_size, y + mark_size, z); glVertex3f(x - mark_size, y + mark_size, z); glVertex3f(x + mark_size, y - mark_size, z); } else if ([shape isEqualToString:@"MARKER_PLUS"]) { mark_size *= 1.4; // match volumes glVertex3f(x, y - mark_size, z); glVertex3f(x, y + mark_size, z); glVertex3f(x - mark_size, y, z); glVertex3f(x + mark_size, y, z); } else if ([shape isEqualToString:@"MARKER_SQUARE"]) { glVertex3f(x - mark_size, y - mark_size, z); glVertex3f(x - mark_size, y + mark_size, z); glVertex3f(x - mark_size, y + mark_size, z); glVertex3f(x + mark_size, y + mark_size, z); glVertex3f(x + mark_size, y + mark_size, z); glVertex3f(x + mark_size, y - mark_size, z); glVertex3f(x + mark_size, y - mark_size, z); glVertex3f(x - mark_size, y - mark_size, z); } else if ([shape isEqualToString:@"MARKER_DIAMOND"]) { mark_size *= 1.4; // match volumes glVertex3f(x, y - mark_size, z); glVertex3f(x - mark_size, y, z); glVertex3f(x - mark_size, y, z); glVertex3f(x, y + mark_size, z); glVertex3f(x, y + mark_size, z); glVertex3f(x + mark_size, y, z); glVertex3f(x + mark_size, y, z); glVertex3f(x, y - mark_size, z); } OOGLEND(); } - (OOSystemID) targetNextFoundSystem:(int)direction // +1 , 0 , -1 { OOSystemID sys = [PLAYER targetSystemID]; if ([PLAYER guiScreen] != GUI_SCREEN_SHORT_RANGE_CHART && [PLAYER guiScreen] != GUI_SCREEN_LONG_RANGE_CHART) return sys; BOOL *systemsFound = [UNIVERSE systemsFound]; unsigned i, first = 0, last = 0, count = 0; int systemIndex = foundSystem + direction; if (direction == 0) systemIndex = 0; for (i = 0; i <= kOOMaximumSystemID; i++) { if (systemsFound[i]) { if (count == 0) { first = last = i; } else { last = i; } if (systemIndex == (int)count) { sys = i; } count++; } } if (count == 0) return sys; // empty systemFound list. // loop back if needed. if (systemIndex < 0) { systemIndex = count - 1; sys = last; } if (systemIndex >= (int)count) { systemIndex = 0; sys = first; } foundSystem = systemIndex; return sys; } // used only on mission screens for long-range charts there // takes up slightly less vertical space than the normal chart // FIXME: make this a mode of drawStarChart to reduce code duplication - (void) drawGalaxyChart:(GLfloat)x :(GLfloat)y :(GLfloat)z :(GLfloat) alpha { PlayerEntity *player = PLAYER; NSPoint galaxy_coordinates = [player galaxy_coordinates]; NSPoint cursor_coordinates = [player cursor_coordinates]; OOGalaxyID galaxy_id = [player galaxyNumber]; double fuel = 35.0 * [player dialFuel]; OOSystemDescriptionManager *systemManager = [UNIVERSE systemManager]; // get a list of systems marked as contract destinations NSDictionary *markedDestinations = [player markedDestinations]; NSDictionary *systemData = nil; GLfloat r = 1.0, g = 1.0, b = 1.0; BOOL noNova; NSPoint star, cu; double hscale = size_in_pixels.width / 256.0; double vscale = -1.0 * size_in_pixels.height / 512.0; double hoffset = 0.0f; double voffset = size_in_pixels.height - pixel_title_size.height - 5; OORouteType advancedNavArrayMode = OPTIMIZED_BY_NONE; BOOL routeExists = YES; int i; double distance = 0.0, time = 0.0; if (backgroundSpecial == GUI_BACKGROUND_SPECIAL_LONG_ANA_SHORTEST) { advancedNavArrayMode = OPTIMIZED_BY_JUMPS; } else if (backgroundSpecial == GUI_BACKGROUND_SPECIAL_LONG_ANA_QUICKEST) { advancedNavArrayMode = OPTIMIZED_BY_TIME; } if (advancedNavArrayMode != OPTIMIZED_BY_NONE && [player hasEquipmentItemProviding:@"EQ_ADVANCED_NAVIGATIONAL_ARRAY"]) { OOSystemID planetNumber = [PLAYER systemID]; OOSystemID destNumber = [PLAYER targetSystemID]; NSDictionary *routeInfo = [UNIVERSE routeFromSystem:planetNumber toSystem:destNumber optimizedBy:advancedNavArrayMode]; if (!routeInfo) routeExists = NO; [self drawAdvancedNavArrayAtX:x+hoffset y:y+voffset z:z alpha:alpha usingRoute: (planetNumber != destNumber ? (id)routeInfo : nil) optimizedBy:advancedNavArrayMode zoom: CHART_MAX_ZOOM]; if (routeExists) { distance = [routeInfo oo_doubleForKey:@"distance"]; time = [routeInfo oo_doubleForKey:@"time"]; if (distance == 0.0 && planetNumber != destNumber) { // zero-distance double fix distance = 0.1; time = 0.01; } } } else { [self drawAdvancedNavArrayAtX:x+hoffset y:y+voffset z:z alpha:alpha usingRoute:nil optimizedBy:OPTIMIZED_BY_NONE zoom: CHART_MAX_ZOOM]; OOSystemID dest = [UNIVERSE findSystemAtCoords:cursor_coordinates withGalaxy:galaxy_id]; NSPoint dest_coordinates = [systemManager getCoordinatesForSystem:dest inGalaxy:galaxy_id]; distance = distanceBetweenPlanetPositions(dest_coordinates.x,dest_coordinates.y,galaxy_coordinates.x,galaxy_coordinates.y); if (distance == 0.0 && dest != [PLAYER systemID]) { distance = 0.1; // fix for zero-distance doubles } time = distance * distance; } OOGUITabSettings tab_stops; tab_stops[0] = 0; tab_stops[1] = 96; tab_stops[2] = 288; [self overrideTabs:tab_stops from:kGuiChartTraveltimeTabs length:3]; [self setTabStops:tab_stops]; NSString *targetSystemName = [[UNIVERSE getSystemName:[PLAYER targetSystemID]] retain]; if (routeExists) { // distance-f & est-travel-time-f are identical between short & long range charts in standard Oolite, however can be alterered separately via OXPs NSString *travelDistLine = OOExpandKey(@"long-range-chart-distance", distance); NSString *travelTimeLine = @""; if (advancedNavArrayMode != OPTIMIZED_BY_NONE && distance > 0) { travelTimeLine = OOExpandKey(@"long-range-chart-est-travel-time", time); } [self setArray:[NSArray arrayWithObjects:targetSystemName, travelDistLine,travelTimeLine,nil] forRow:16]; } else { [self setArray:[NSArray arrayWithObjects:targetSystemName, DESC(@"long-range-chart-system-unreachable"), nil] forRow:16]; } [targetSystemName release]; [self setGLColorFromSetting:kGuiChartRangeColor defaultValue:[OOColor greenColor] alpha:alpha]; OOGL(GLScaledLineWidth(2.0f)); cu = NSMakePoint((float)(hscale*galaxy_coordinates.x+hoffset),(float)(vscale*galaxy_coordinates.y+voffset)); if ([player hasHyperspaceMotor]) { // draw fuel range circle GLDrawOval(x + cu.x, y + cu.y, z, NSMakeSize((float)(fuel*hscale), 2*(float)(fuel*vscale)), 5); } // draw cross-hairs over current location // [self setGLColorFromSetting:kGuiChartCrosshairColor defaultValue:[OOColor greenColor] alpha:alpha]; [self drawCrossHairsWithSize:12 x:x + cu.x y:y + cu.y z:z]; // draw cross hairs over cursor // [self setGLColorFromSetting:kGuiChartCursorColor defaultValue:[OOColor redColor] alpha:alpha]; OOGL(glColor4f(1.0f, 0.0f, 0.0f, alpha)); // red cu = NSMakePoint((float)(hscale*cursor_coordinates.x+hoffset),(float)(vscale*cursor_coordinates.y+voffset)); [self drawCrossHairsWithSize:6 x:x + cu.x y:y + cu.y z:z]; // draw marks // OOGL(GLScaledLineWidth(1.5f)); for (i = 0; i < 256; i++) { NSArray *markers = [markedDestinations objectForKey:[NSNumber numberWithInt:i]]; if (markers != nil) { NSPoint sys_coordinates = [systemManager getCoordinatesForSystem:i inGalaxy:galaxy_id]; star.x = (float)(sys_coordinates.x * hscale + hoffset); star.y = (float)(sys_coordinates.y * vscale + voffset); [self drawSystemMarkers:markers atX:x+star.x andY:y+star.y andZ:z withAlpha:alpha andScale:2.5f]; } } // draw stars // OOGL(glColor4f(1.0f, 1.0f, 1.0f, alpha)); for (i = 0; i < 256; i++) { systemData = [UNIVERSE generateSystemData:i]; noNova = ![systemData oo_boolForKey:@"sun_gone_nova"]; // this chart does not support chartmode if (EXPECT(noNova)) { r = g = b = 1.0; } else { r = 1.0; g = 0.2; b = 0.0; } OOGL(glColor4f(r, g, b, alpha)); NSPoint sys_coordinates = [systemManager getCoordinatesForSystem:i inGalaxy:galaxy_id]; star.x = (float)(sys_coordinates.x * hscale + hoffset); star.y = (float)(sys_coordinates.y * vscale + voffset); float sz = (1.0f + 0.5f * ([[systemManager getProperty:@"radius" forSystem:i inGalaxy:galaxy_id] floatValue]/300.0f))/7.0f; OOGLBEGIN(GL_QUADS); glVertex3f(x + star.x, y + star.y + sz, z); glVertex3f(x + star.x + sz, y + star.y, z); glVertex3f(x + star.x, y + star.y - sz, z); glVertex3f(x + star.x - sz, y + star.y, z); OOGLEND(); } // draw bottom horizontal divider // [self setGLColorFromSetting:kGuiScreenDividerColor defaultValue:[OOColor colorWithWhite:0.75 alpha:1.0] alpha:alpha]; OOGLBEGIN(GL_QUADS); glVertex3f(x + 0, (float)(y + voffset + 260.0f*vscale + 0), z); glVertex3f(x + size_in_pixels.width, y + (float)(voffset + 260.0f*vscale + 0), z); glVertex3f(x + size_in_pixels.width, (float)(y + voffset + 260.0f*vscale - 2), z); glVertex3f(x + 0, (float)(y + voffset + 260.0f*vscale - 2), z); OOGLEND(); } // Advanced Navigation Array -- galactic chart route mapping - contributed by Nikos Barkas (another_commander). - (void) drawAdvancedNavArrayAtX:(float)x y:(float)y z:(float)z alpha:(float)alpha usingRoute:(NSDictionary *) routeInfo optimizedBy:(OORouteType) optimizeBy zoom: (OOScalar) zoom { GLfloat lr,lg,lb,la,lr2,lg2,lb2,la2; double hscale = size_in_pixels.width / (CHART_WIDTH_AT_MAX_ZOOM*zoom); double vscale = -1.0 * size_in_pixels.height / (2*CHART_HEIGHT_AT_MAX_ZOOM*zoom); NSPoint star = NSZeroPoint, star2 = NSZeroPoint, starabs = NSZeroPoint, star2abs = NSZeroPoint; OOSystemDescriptionManager *systemManager = [UNIVERSE systemManager]; OOGalaxyID g = [PLAYER galaxyNumber]; OOSystemID planetNumber = [PLAYER systemID]; OOColor *defaultConnectionColor = [self colorFromSetting:kGuiChartConnectionColor defaultValue:[OOColor colorWithWhite:0.25 alpha:1.0]]; OOColor *currentJumpColorStart = [self colorFromSetting:kGuiChartCurrentJumpStartColor defaultValue:[OOColor colorWithWhite:0.25 alpha:0.0]]; OOColor *currentJumpColorEnd = [self colorFromSetting:kGuiChartCurrentJumpEndColor defaultValue:[OOColor colorWithWhite:0.25 alpha:0.0]]; OOColor *thisConnectionColor = nil; float jumpRange = MAX_JUMP_RANGE * ((optimizeBy == OPTIMIZED_BY_NONE) ? [PLAYER dialFuel] : 1.0); OOGLBEGIN(GL_LINES); for (OOSystemID i = 0; i < 256; i++) { if (optimizeBy == OPTIMIZED_BY_NONE && i != planetNumber) { continue; } starabs = [systemManager getCoordinatesForSystem:i inGalaxy:g]; star.x = (float)(starabs.x * hscale); star.y = (float)(starabs.y * vscale); // if in non-route mode, we're always starting from i, so need // to do CHART_ZOOM_SHOW_LABELS) { OODrawString([UNIVERSE systemNameIndex:loc], x + star.x + 2.0, y + star.y, z, NSMakeSize(8,8)); } } // Label the destination, which was not included in the above loop. if (zoom > CHART_ZOOM_SHOW_LABELS) { loc = [[routeInfo objectForKey:@"route"] oo_intAtIndex:i]; OODrawString([UNIVERSE systemNameIndex:loc], x + star2.x + 2.0, y + star2.y, z, NSMakeSize(10,10)); } } } @end oolite-1.82/src/Core/HeadUpDisplay.h000066400000000000000000000267141256642440500173050ustar00rootroot00000000000000/* HeadUpDisplay.h Class handling the player ship’s heads-up display, and 2D drawing functions. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #import "OOOpenGL.h" #import "OOTypes.h" #import "OOMaths.h" #import "MyOpenGLView.h" #import "ShipEntity.h" @class OOCrosshairs, OOColor; @protocol OOHUDBeaconIcon; #define SCANNER_CENTRE_X 0 #define SCANNER_CENTRE_Y -180 #define SCANNER_SCALE 256 #define SCANNER_WIDTH 288 #define SCANNER_HEIGHT 72 #define SCANNER_MAX_ZOOM 5.0 #define SCANNER_ZOOM_LEVELS 5 #define ZOOM_INDICATOR_CENTRE_X 108 #define ZOOM_INDICATOR_CENTRE_Y -216 #define ZOOM_INDICATOR_WIDTH 11.0f #define ZOOM_INDICATOR_HEIGHT 14.0f #define ZOOM_LEVELS_IMAGE @"zoom.png" #define COMPASS_IMAGE @"compass.png" #define COMPASS_CENTRE_X 132 #define COMPASS_CENTRE_Y -216 #define COMPASS_SIZE 56 #define COMPASS_HALF_SIZE 28 #define COMPASS_REDDOT_IMAGE @"reddot.png" #define COMPASS_GREENDOT_IMAGE @"greendot.png" #define COMPASS_DOT_SIZE 16 #define COMPASS_HALF_DOT_SIZE 8 #define AEGIS_IMAGE @"aegis.png" #define AEGIS_CENTRE_X -132 #define AEGIS_CENTRE_Y -216 #define AEGIS_WIDTH 24 #define AEGIS_HEIGHT 24 #define SPEED_BAR_CENTRE_X 200 #define SPEED_BAR_CENTRE_Y -145 #define SPEED_BAR_WIDTH 80 #define SPEED_BAR_HEIGHT 8 #define SPEED_BAR_DRAW_SURROUND YES #define ROLL_BAR_CENTRE_X 200 #define ROLL_BAR_CENTRE_Y -160 #define ROLL_BAR_WIDTH 80 #define ROLL_BAR_HEIGHT 8 #define ROLL_BAR_DRAW_SURROUND YES #define PITCH_BAR_CENTRE_X 200 #define PITCH_BAR_CENTRE_Y -170 #define PITCH_BAR_WIDTH 80 #define PITCH_BAR_HEIGHT 8 #define PITCH_BAR_DRAW_SURROUND YES #define ENERGY_GAUGE_CENTRE_X 200 #define ENERGY_GAUGE_CENTRE_Y -205 #define ENERGY_GAUGE_WIDTH 80 #define ENERGY_GAUGE_HEIGHT 48 #define ENERGY_GAUGE_DRAW_SURROUND YES #define FORWARD_SHIELD_BAR_CENTRE_X -200 #define FORWARD_SHIELD_BAR_CENTRE_Y -146 #define FORWARD_SHIELD_BAR_WIDTH 80 #define FORWARD_SHIELD_BAR_HEIGHT 8 #define FORWARD_SHIELD_BAR_DRAW_SURROUND YES #define AFT_SHIELD_BAR_CENTRE_X -200 #define AFT_SHIELD_BAR_CENTRE_Y -162 #define AFT_SHIELD_BAR_WIDTH 80 #define AFT_SHIELD_BAR_HEIGHT 8 #define AFT_SHIELD_BAR_DRAW_SURROUND YES #define FUEL_BAR_CENTRE_X -200 #define FUEL_BAR_CENTRE_Y -179 #define FUEL_BAR_WIDTH 80 #define FUEL_BAR_HEIGHT 8 #define WITCHDEST_CENTRE_X -200 #define WITCHDEST_CENTRE_Y -179 #define WITCHDEST_WIDTH 80 #define WITCHDEST_HEIGHT 8 #define CABIN_TEMP_BAR_CENTRE_X -200 #define CABIN_TEMP_BAR_CENTRE_Y -189 #define CABIN_TEMP_BAR_WIDTH 80 #define CABIN_TEMP_BAR_HEIGHT 8 #define WEAPON_TEMP_BAR_CENTRE_X -200 #define WEAPON_TEMP_BAR_CENTRE_Y -199 #define WEAPON_TEMP_BAR_WIDTH 80 #define WEAPON_TEMP_BAR_HEIGHT 8 #define ALTITUDE_BAR_CENTRE_X -200 #define ALTITUDE_BAR_CENTRE_Y -209 #define ALTITUDE_BAR_WIDTH 80 #define ALTITUDE_BAR_HEIGHT 8 #define MISSILES_DISPLAY_X -228 #define MISSILES_DISPLAY_Y -224 #define MISSILES_DISPLAY_SPACING 16 #define MISSILE_ICON_WIDTH 12 #define MISSILE_ICON_HEIGHT MISSILE_ICON_WIDTH #define PRIMED_DISPLAY_X -144 #define PRIMED_DISPLAY_Y -256 #define PRIMED_DISPLAY_WIDTH 12 #define PRIMED_DISPLAY_HEIGHT 12 #define ASCTARGET_DISPLAY_X 64 #define ASCTARGET_DISPLAY_Y -234 #define ASCTARGET_DISPLAY_WIDTH 10 #define ASCTARGET_DISPLAY_HEIGHT 10 #define CLOCK_DISPLAY_X -44 #define CLOCK_DISPLAY_Y -234 #define CLOCK_DISPLAY_WIDTH 12 #define CLOCK_DISPLAY_HEIGHT 12 #define WEAPONSOFFLINETEXT_DISPLAY_X -175 #define WEAPONSOFFLINETEXT_DISPLAY_Y 2 #define WEAPONSOFFLINETEXT_WIDTH 8 #define WEAPONSOFFLINETEXT_HEIGHT 8 #define FPSINFO_DISPLAY_X -300 #define FPSINFO_DISPLAY_Y 220 #define FPSINFO_DISPLAY_WIDTH 12 #define FPSINFO_DISPLAY_HEIGHT 12 #define STATUS_LIGHT_CENTRE_X -108 #define STATUS_LIGHT_CENTRE_Y -216 #define STATUS_LIGHT_WIDTH 8 #define STATUS_LIGHT_HEIGHT 8 #define HIT_INDICATOR_CENTRE_X 200 #define HIT_INDICATOR_CENTRE_Y 0 #define SCOOPSTATUS_CENTRE_X -132 #define SCOOPSTATUS_CENTRE_Y -152 #define SCOOPSTATUS_WIDTH 16.0 #define SCOOPSTATUS_HEIGHT 16.0 #define MFD_TEXT_WIDTH 10 #define MFD_TEXT_HEIGHT 10 #define DIALS_KEY @"dials" #define LEGENDS_KEY @"legends" #define MFDS_KEY @"multi_function_displays" #define X_KEY @"x" #define Y_KEY @"y" #define X_ORIGIN_KEY @"x_origin" #define Y_ORIGIN_KEY @"y_origin" #define SPACING_KEY @"spacing" #define ALPHA_KEY @"alpha" #define SELECTOR_KEY @"selector" #define IMAGE_KEY @"image" #define WIDTH_KEY @"width" #define HEIGHT_KEY @"height" #define SPRITE_KEY @"sprite" #define DRAW_SURROUND_KEY @"draw_surround" #define EQUIPMENT_REQUIRED_KEY @"equipment_required" #define ALERT_CONDITIONS_KEY @"alert_conditions" #define VIEWSCREEN_KEY @"viewscreen_only" #define DIAL_REQUIRED_KEY @"with_dial" #define LABELLED_KEY @"labelled" #define TEXT_KEY @"text" #define RGB_COLOR_KEY @"rgb_color" #define COLOR_KEY @"color" #define COLOR_KEY_LOW @"color_low" #define COLOR_KEY_MEDIUM @"color_medium" #define COLOR_KEY_HIGH @"color_high" #define COLOR_KEY_CRITICAL @"color_critical" #define COLOR_KEY_SURROUND @"color_surround" #define N_BARS_KEY @"n_bars" #define CUSTOM_DIAL_KEY @"data_source" #define ROWS_KEY @"rows" #define COLUMNS_KEY @"columns" #define ROW_HEIGHT_KEY @"row_height" #define ROW_START_KEY @"row_start" #define TITLE_KEY @"title" #define BACKGROUND_RGBA_KEY @"background_rgba" #define OVERALL_ALPHA_KEY @"overall_alpha" #define NONLINEAR_SCANNER @"nonlinear_scanner" #define Z1 [(MyOpenGLView *)[[player universe] gameView] display_z] #define ONE_EIGHTH 0.125 #define MAX_ACCURACY_RANGE 7000 // 7.000km #define ACCURACY_PROBABILITY_DECREASE_FACTOR 0.000035f // for every 1000km decrease by 3.5% the chance of high accuracy #define MIN_PROBABILITY_ACCURACY 0.35f // floor value for probability of high accuracy is 35% @class Entity, PlayerEntity, OOTextureSprite; @interface HeadUpDisplay: NSObject { @private NSMutableArray *legendArray; NSMutableArray *dialArray; NSMutableArray *mfdArray; // zoom level GLfloat scanner_zoom; //where to draw it GLfloat z1; GLfloat lineWidth; NSString *hudName; NSString *deferredHudName; // Usually it will be nil. If not nil, then it means that we have a deferred HUD waiting to be drawn This may happen // for example when a script handler attempts to switch HUD while it is being rendered. - Nikos 20110628 BOOL hudUpdating; GLfloat overallAlpha; BOOL reticleTargetSensitive; // TO DO: Move this into the propertiesReticleTargetSensitive structure (Getafix - 2010/08/21) NSMutableDictionary *propertiesReticleTargetSensitive; BOOL cloakIndicatorOnStatusLight; BOOL hudHidden; BOOL allowBigGui; int last_transmitter; NSMutableSet *_hiddenSelectors; // Crosshairs OOCrosshairs *_crosshairs; OOWeaponType _lastWeaponType; GLfloat _lastOverallAlpha; BOOL _lastWeaponsOnline; NSDictionary *_crosshairOverrides; OOColor *_crosshairColor; GLfloat _crosshairScale; GLfloat _crosshairWidth; NSString *crosshairDefinition; BOOL _compassActive; // Nonlinear scanner BOOL nonlinear_scanner; BOOL scanner_ultra_zoom; } - (id) initWithDictionary:(NSDictionary *)hudinfo; - (id) initWithDictionary:(NSDictionary *)hudinfo inFile:(NSString *)hudFileName; - (void) resetGuis:(NSDictionary *)info; - (NSString *) hudName; - (void) setHudName:(NSString *)newHudName; - (GLfloat) scannerZoom; - (void) setScannerZoom:(GLfloat)value; - (GLfloat) overallAlpha; - (void) setOverallAlpha:(GLfloat)newAlphaValue; - (BOOL) reticleTargetSensitive; - (void) setReticleTargetSensitive:(BOOL)newReticleTargetSensitiveValue; - (NSMutableDictionary *) propertiesReticleTargetSensitive; - (BOOL) isHidden; - (void) setHidden:(BOOL)newValue; - (BOOL) allowBigGui; - (BOOL) hasHidden:(NSString *)selectorName; - (void) setHiddenSelector:(NSString *)selectorName hidden:(BOOL)hide; - (void) clearHiddenSelectors; - (BOOL) isCompassActive; - (void) setCompassActive:(BOOL)newValue; - (BOOL) isUpdating; - (void) setDeferredHudName:(NSString *)newDeferredHudName; - (NSString *) deferredHudName; - (NSString *) crosshairDefinition; - (BOOL) setCrosshairDefinition:(NSString *)newDefinition; - (void) addLegend:(NSDictionary *)info; - (void) addDial:(NSDictionary *)info; - (void) addMFD:(NSDictionary *)info; - (NSUInteger) mfdCount; - (void) renderHUD; - (void) refreshLastTransmitter; - (void) setLineWidth:(GLfloat)value; - (GLfloat) lineWidth; + (Vector) nonlinearScannerScale:(Vector) V Zoom:(GLfloat) zoom Scale:(double) scale; - (BOOL) nonlinearScanner; - (void) setNonlinearScanner: (BOOL)newValue; - (BOOL) scannerUltraZoom; - (void) setScannerUltraZoom: (BOOL)newValue; @end @interface NSString (OODisplayEncoding) // Return a C string in the 8-bit encoding used for display. - (const char *) cStringUsingOoliteEncoding; // Return a C string in the 8-bit encoding used for display, with substitutions performed. - (const char *) cStringUsingOoliteEncodingAndRemapping; @end /* Protocol for things that can be used as HUD compass items. Really ought to grow into a general protocol for HUD elements. */ @protocol OOHUDBeaconIcon - (void) oo_drawHUDBeaconIconAt:(NSPoint)where size:(NSSize)size alpha:(GLfloat)alpha z:(GLfloat)z; @end @interface NSString (OOHUDBeaconIcon) @end void OODrawString(NSString *text, GLfloat x, GLfloat y, GLfloat z, NSSize siz); void OODrawStringAligned(NSString *text, GLfloat x, GLfloat y, GLfloat z, NSSize siz, BOOL rightAlign); /* OODrawString(Aligned) handles all the string drawing, but because * it does texture application and GL_QUADS beginning once per string * it's quite slow. * * Where efficiency is needed, call OOStartDrawingStrings(), then * OODrawStringQuadsAligned for each bit of text, then * OOStopDrawingStrings(). * * Trying to draw anything else between OOStartDrawingStrings() and * OOStopDrawingStrings() will have messy results. You can safely call * Stop, draw the other thing you want, then call Start again - it's * just a little inefficient. Similarly calling * OODrawStringQuadsAligned() without calling OOStartDrawingStrings() * won't work very well. * * - CIM */ void OOStartDrawingStrings(); void OODrawStringQuadsAligned(NSString *text, GLfloat x, GLfloat y, GLfloat z, NSSize siz, BOOL rightAlign); void OOStopDrawingStrings(); void OODrawHilightedString(NSString *text, GLfloat x, GLfloat y, GLfloat z, NSSize siz); void OODrawPlanetInfo(int gov, int eco, int tec, GLfloat x, GLfloat y, GLfloat z, NSSize siz); void OODrawHilightedPlanetInfo(int gov, int eco, int tec, GLfloat x, GLfloat y, GLfloat z, NSSize siz); NSRect OORectFromString(NSString *text, GLfloat x, GLfloat y, NSSize siz); CGFloat OOStringWidthInEm(NSString *text); void OOHUDResetTextEngine(void); oolite-1.82/src/Core/HeadUpDisplay.m000066400000000000000000003765641256642440500173250ustar00rootroot00000000000000/* HeadUpDisplay.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "HeadUpDisplay.h" #import "GameController.h" #import "ResourceManager.h" #import "PlayerEntity.h" #import "OOSunEntity.h" #import "OOPlanetEntity.h" #import "StationEntity.h" #import "OOVisualEffectEntity.h" #import "OOQuiriumCascadeEntity.h" #import "OOWaypointEntity.h" #import "Universe.h" #import "OOTrumble.h" #import "OOColor.h" #import "GuiDisplayGen.h" #import "OOTexture.h" #import "OOTextureSprite.h" #import "OOPolygonSprite.h" #import "OOCollectionExtractors.h" #import "OOEncodingConverter.h" #import "OOCrosshairs.h" #import "OOConstToString.h" #import "OOStringParsing.h" #import "OOJoystickManager.h" #import "OOJavaScriptEngine.h" #import "OOStringExpander.h" #define ONE_SIXTEENTH 0.0625 #define ONE_SIXTYFOURTH 0.015625 #define DEFAULT_OVERALL_ALPHA 0.75 #define GLYPH_SCALE_FACTOR 0.13 // 0.13 is an inherited magic number #define IDENTIFY_SCANNER_LOLLIPOPS ( 0 && !defined(NDEBUG)) #define NOT_DEFINED INFINITY #define WIDGET_INFO 0 #define WIDGET_CACHE 1 #define WIDGET_SELECTOR 2 #define WIDGET_SELECTOR_NAME 3 /* Convenience macros to make set-colour-or-default quicker. 'info' must be the NSDictionary and 'alpha' must be the overall alpha or these won't work */ #define DO_SET_COLOR(t,d) SetGLColourFromInfo(info,t,d,alpha) #define SET_COLOR(d) DO_SET_COLOR(COLOR_KEY,d) #define SET_COLOR_LOW(d) DO_SET_COLOR(COLOR_KEY_LOW,d) #define SET_COLOR_MEDIUM(d) DO_SET_COLOR(COLOR_KEY_MEDIUM,d) #define SET_COLOR_HIGH(d) DO_SET_COLOR(COLOR_KEY_HIGH,d) #define SET_COLOR_CRITICAL(d) DO_SET_COLOR(COLOR_KEY_CRITICAL,d) #define SET_COLOR_SURROUND(d) DO_SET_COLOR(COLOR_KEY_SURROUND,d) struct CachedInfo { float x, y, x0, y0; float width, height, alpha; }; static NSArray *sCurrentDrawItem; OOINLINE float useDefined(float val, float validVal) { return (val == NOT_DEFINED) ? validVal : val; } static void DrawSpecialOval(GLfloat x, GLfloat y, GLfloat z, NSSize siz, GLfloat step, GLfloat* color4v); static void SetGLColourFromInfo(NSDictionary *info, NSString *key, const GLfloat defaultColor[4], GLfloat alpha); static void GetRGBAArrayFromInfo(NSDictionary *info, GLfloat ioColor[4]); static void hudDrawIndicatorAt(GLfloat x, GLfloat y, GLfloat z, NSSize siz, GLfloat amount); static void hudDrawMarkerAt(GLfloat x, GLfloat y, GLfloat z, NSSize siz, GLfloat amount); static void hudDrawBarAt(GLfloat x, GLfloat y, GLfloat z, NSSize siz, GLfloat amount); static void hudDrawSurroundAt(GLfloat x, GLfloat y, GLfloat z, NSSize siz); static void hudDrawStatusIconAt(int x, int y, int z, NSSize siz); static void hudDrawReticleOnTarget(Entity* target, PlayerEntity* player1, GLfloat z1, GLfloat alpha, BOOL reticleTargetSensitive, NSMutableDictionary *propertiesReticleTargetSensitive, BOOL colourFromScannerColour, BOOL showText, NSDictionary *info); static void hudDrawWaypoint(OOWaypointEntity *waypoint, PlayerEntity *player1, GLfloat z1, GLfloat alpha, BOOL selected, GLfloat scale); static void hudRotateViewpointForVirtualDepth(PlayerEntity * player1, Vector p1); static void drawScannerGrid(GLfloat x, GLfloat y, GLfloat z, NSSize siz, int v_dir, GLfloat thickness, GLfloat zoom, BOOL nonlinear); static GLfloat nonlinearScannerFunc(GLfloat distance, GLfloat zoom, GLfloat scale); static void GLDrawNonlinearCascadeWeapon( GLfloat x, GLfloat y, GLfloat z, NSSize siz, Vector centre, GLfloat radius, GLfloat zoom, GLfloat alpha ); static OOTexture *sFontTexture = nil; static OOEncodingConverter *sEncodingCoverter = nil; enum { kFontTextureOptions = kOOTextureMinFilterMipMap | kOOTextureMagFilterLinear | kOOTextureNoShrink | kOOTextureAlphaMask }; @interface HeadUpDisplay (Private) - (void) drawCrosshairs; - (void) drawLegends; - (void) drawDials; - (void) drawMFDs; - (void) drawLegend:(NSDictionary *)info; - (void) drawHUDItem:(NSDictionary *)info; - (void) drawScanner:(NSDictionary *)info; - (void) drawScannerZoomIndicator:(NSDictionary *)info; - (void) drawCompass:(NSDictionary *)info; - (void) drawCompassPlanetBlipAt:(Vector) relativePosition Size:(NSSize) siz Alpha:(GLfloat) alpha; - (void) drawCompassStationBlipAt:(Vector) relativePosition Size:(NSSize) siz Alpha:(GLfloat) alpha; - (void) drawCompassSunBlipAt:(Vector) relativePosition Size:(NSSize) siz Alpha:(GLfloat) alpha; - (void) drawCompassTargetBlipAt:(Vector) relativePosition Size:(NSSize) siz Alpha:(GLfloat) alpha; - (void) drawCompassBeaconBlipAt:(Vector) relativePosition Size:(NSSize) siz Alpha:(GLfloat) alpha; - (void) drawAegis:(NSDictionary *)info; - (void) drawSpeedBar:(NSDictionary *)info; - (void) drawRollBar:(NSDictionary *)info; - (void) drawPitchBar:(NSDictionary *)info; - (void) drawYawBar:(NSDictionary *)info; - (void) drawEnergyGauge:(NSDictionary *)info; - (void) drawForwardShieldBar:(NSDictionary *)info; - (void) drawAftShieldBar:(NSDictionary *)info; - (void) drawFuelBar:(NSDictionary *)info; - (void) drawWitchspaceDestination:(NSDictionary *)info; - (void) drawCabinTempBar:(NSDictionary *)info; - (void) drawWeaponTempBar:(NSDictionary *)info; - (void) drawAltitudeBar:(NSDictionary *)info; - (void) drawMissileDisplay:(NSDictionary *)info; - (void) drawTargetReticle:(NSDictionary *)info; - (void) drawSecondaryTargetReticle:(NSDictionary *)info; - (void) drawWaypoints:(NSDictionary *)info; - (void) drawStatusLight:(NSDictionary *)info; - (void) drawDirectionCue:(NSDictionary *)info; - (void) drawClock:(NSDictionary *)info; - (void) drawPrimedEquipmentText:(NSDictionary *)info; - (void) drawASCTarget:(NSDictionary *)info; - (void) drawWeaponsOfflineText:(NSDictionary *)info; - (void) drawMultiFunctionDisplay:(NSDictionary *)info withText:(NSString *)text asIndex:(NSUInteger)index; - (void) drawFPSInfoCounter:(NSDictionary *)info; - (void) drawScoopStatus:(NSDictionary *)info; - (void) drawStickSensitivityIndicator:(NSDictionary *)info; - (void) drawCustomBar:(NSDictionary *)info; - (void) drawCustomText:(NSDictionary *)info; - (void) drawCustomIndicator:(NSDictionary *)info; - (void) drawCustomLight:(NSDictionary *)info; - (void) drawCustomImage:(NSDictionary *)info; - (void) drawSurroundInternal:(NSDictionary *)info color:(const GLfloat[4])color; - (void) drawSurround:(NSDictionary *)info; - (void) drawGreenSurround:(NSDictionary *)info; - (void) drawYellowSurround:(NSDictionary *)info; - (void) drawTrumbles:(NSDictionary *)info; - (NSArray *) crosshairDefinitionForWeaponType:(OOWeaponType)weapon; - (BOOL) checkPlayerInFlight; - (BOOL) checkPlayerInSystemFlight; @end @implementation HeadUpDisplay static const GLfloat red_color[4] = {1.0, 0.0, 0.0, 1.0}; static const GLfloat redplus_color[4] = {1.0, 0.0, 0.5, 1.0}; static const GLfloat yellow_color[4] = {1.0, 1.0, 0.0, 1.0}; static const GLfloat green_color[4] = {0.0, 1.0, 0.0, 1.0}; static const GLfloat darkgreen_color[4] = {0.0, 0.75, 0.0, 1.0}; static const GLfloat cyan_color[4] = {0.0, 1.0, 1.0, 1.0}; static const GLfloat blue_color[4] = {0.0, 0.0, 1.0, 1.0}; static const GLfloat black_color[4] = {0.0, 0.0, 0.0, 1.0}; static const GLfloat lightgray_color[4] = {0.25, 0.25, 0.25, 1.0}; static float sGlyphWidths[256]; static float sF6KernGovt; static float sF6KernTL; static BOOL _compassUpdated; static GLfloat drawCharacterQuad(uint8_t chr, GLfloat x, GLfloat y, GLfloat z, NSSize siz); static void InitTextEngine(void); static void prefetchData(NSDictionary *info, struct CachedInfo *data); OOINLINE void GLColorWithOverallAlpha(const GLfloat *color, GLfloat alpha) { // NO OOGL(), this is called within immediate mode blocks. glColor4f(color[0], color[1], color[2], color[3] * alpha); } - (id) initWithDictionary:(NSDictionary *)hudinfo { return [self initWithDictionary:hudinfo inFile:nil]; } - (id) initWithDictionary:(NSDictionary *)hudinfo inFile:(NSString *)hudFileName { unsigned i; BOOL isCompassToBeDrawn = NO; BOOL areTrumblesToBeDrawn = NO; self = [super init]; lineWidth = 1.0; if (sFontTexture == nil) InitTextEngine(); deferredHudName = nil; // if not nil, it means that we have a deferred HUD which is to be drawn at first available opportunity hudName = [hudFileName copy]; // init arrays dialArray = [[NSMutableArray alloc] initWithCapacity:16]; // alloc retains legendArray = [[NSMutableArray alloc] initWithCapacity:16]; // alloc retains mfdArray = [[NSMutableArray alloc] initWithCapacity:8]; // alloc retains // populate arrays NSArray *dials = [hudinfo oo_arrayForKey:DIALS_KEY]; for (i = 0; i < [dials count]; i++) { NSDictionary *dial_info = [dials oo_dictionaryAtIndex:i]; if (!areTrumblesToBeDrawn && [[dial_info oo_stringForKey:SELECTOR_KEY] isEqualToString:@"drawTrumbles:"]) areTrumblesToBeDrawn = YES; if (!isCompassToBeDrawn && [[dial_info oo_stringForKey:SELECTOR_KEY] isEqualToString:@"drawCompass:"]) isCompassToBeDrawn = YES; [self addDial:dial_info]; } if (!areTrumblesToBeDrawn) // naughty - a hud with no built-in drawTrumbles: - one must be added! { NSDictionary *trumble_dial_info = [NSDictionary dictionaryWithObjectsAndKeys: @"drawTrumbles:", SELECTOR_KEY, nil]; [self addDial:trumble_dial_info]; } _compassActive = isCompassToBeDrawn; _lastWeaponType = nil; NSArray *legends = [hudinfo oo_arrayForKey:LEGENDS_KEY]; for (i = 0; i < [legends count]; i++) { [self addLegend:[legends oo_dictionaryAtIndex:i]]; } NSArray *mfds = [hudinfo oo_arrayForKey:MFDS_KEY]; for (i = 0; i < [mfds count]; i++) { [self addMFD:[mfds oo_dictionaryAtIndex:i]]; } hudHidden = NO; _hiddenSelectors = [[NSMutableSet alloc] initWithCapacity:16]; hudUpdating = NO; overallAlpha = [hudinfo oo_floatForKey:@"overall_alpha" defaultValue:DEFAULT_OVERALL_ALPHA]; reticleTargetSensitive = [hudinfo oo_boolForKey:@"reticle_target_sensitive" defaultValue:NO]; propertiesReticleTargetSensitive = [[NSMutableDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithBool:YES], @"isAccurate", [NSNumber numberWithDouble:[UNIVERSE getTime]], @"timeLastAccuracyProbabilityCalculation", nil]; cloakIndicatorOnStatusLight = [hudinfo oo_boolForKey:@"cloak_indicator_on_status_light" defaultValue:YES]; allowBigGui = [hudinfo oo_boolForKey:@"allow_big_gui" defaultValue:NO]; last_transmitter = NO_TARGET; [crosshairDefinition release]; NSString *crossfile = [[hudinfo oo_stringForKey:@"crosshair_file"] retain]; if (crossfile == nil) { _crosshairOverrides = [[hudinfo oo_dictionaryForKey:@"crosshairs"] retain]; crosshairDefinition = nil; } else { [self setCrosshairDefinition:crossfile]; } [crossfile release]; id crosshairColor = [hudinfo oo_objectForKey:@"crosshair_color" defaultValue:@"greenColor"]; _crosshairColor = [[OOColor colorWithDescription:crosshairColor] retain]; _crosshairScale = [hudinfo oo_floatForKey:@"crosshair_scale" defaultValue:32.0f]; _crosshairWidth = [hudinfo oo_floatForKey:@"crosshair_width" defaultValue:1.5f]; nonlinear_scanner = [hudinfo oo_boolForKey:@"scanner_non_linear" defaultValue:NO]; scanner_ultra_zoom = [hudinfo oo_boolForKey:@"scanner_ultra_zoom" defaultValue:NO]; return self; } - (void) dealloc { DESTROY(legendArray); DESTROY(dialArray); DESTROY(mfdArray); DESTROY(hudName); DESTROY(deferredHudName); DESTROY(propertiesReticleTargetSensitive); DESTROY(_crosshairOverrides); DESTROY(_crosshairColor); DESTROY(crosshairDefinition); DESTROY(_hiddenSelectors); [super dealloc]; } //------------------------------------------------------------------------------------// - (void) resetGui:(GuiDisplayGen*)gui withInfo:(NSDictionary *)gui_info { Vector pos = [gui drawPosition]; if ([gui_info objectForKey:X_KEY]) pos.x = [gui_info oo_floatForKey:X_KEY] + [[UNIVERSE gameView] x_offset] * [gui_info oo_floatForKey:X_ORIGIN_KEY defaultValue:0.0]; if ([gui_info objectForKey:Y_KEY]) pos.y = [gui_info oo_floatForKey:Y_KEY] + [[UNIVERSE gameView] y_offset] * [gui_info oo_floatForKey:Y_ORIGIN_KEY defaultValue:0.0]; [gui setDrawPosition:pos]; NSSize siz = [gui size]; int rht = [gui rowHeight]; NSString* title = [gui title]; if ([gui_info objectForKey:WIDTH_KEY]) siz.width = [gui_info oo_floatForKey:WIDTH_KEY]; if ([gui_info objectForKey:HEIGHT_KEY]) siz.height = [gui_info oo_floatForKey:HEIGHT_KEY]; if ([gui_info objectForKey:ROW_HEIGHT_KEY]) rht = [gui_info oo_floatForKey:ROW_HEIGHT_KEY]; if ([gui_info objectForKey:TITLE_KEY]) title = [gui_info oo_stringForKey:TITLE_KEY]; [gui resizeTo:siz characterHeight:rht title:title]; if ([gui_info objectForKey:BACKGROUND_RGBA_KEY]) [gui setBackgroundColor:[OOColor colorFromString:[gui_info oo_stringForKey:BACKGROUND_RGBA_KEY]]]; if ([gui_info objectForKey:ALPHA_KEY]) [gui setMaxAlpha: OOClamp_0_max_f([gui_info oo_floatForKey:ALPHA_KEY],1.0f)]; else [gui setMaxAlpha: 1.0f]; } - (void) resetGuis:(NSDictionary *)info { // check for entries in hud.plist for message_gui and comm_log_gui // then resize and reposition them accordingly. GuiDisplayGen* gui = [UNIVERSE messageGUI]; NSDictionary* gui_info = [info oo_dictionaryForKey:@"message_gui"]; if (gui && [gui_info count] > 0) { /* If switching message guis, remember the last 2 message lines. Present GUI limitations make it impractical to transfer anything more... TODO: a more usable GUI code! - Kaks 2011.11.05 */ NSArray* lastLines = [gui getLastLines]; // text, colour, fade time - text, colour, fade time BOOL line1 = ![[lastLines oo_stringAtIndex:0] isEqualToString:@""]; [self resetGui:gui withInfo:gui_info]; BOOL permanent = [gui_info oo_boolForKey:@"permanent" defaultValue:NO]; [UNIVERSE setPermanentMessageLog:permanent]; if (line1) { [gui printLongText:[lastLines oo_stringAtIndex:0] align:GUI_ALIGN_CENTER color:[OOColor colorFromString:[lastLines oo_stringAtIndex:1]] fadeTime:(permanent?0.0:[lastLines oo_floatAtIndex:2]) key:nil addToArray:nil]; } if ([lastLines count] > 3 && (line1 || ![[lastLines oo_stringAtIndex:3] isEqualToString:@""])) { [gui printLongText:[lastLines oo_stringAtIndex:3] align:GUI_ALIGN_CENTER color:[OOColor colorFromString:[lastLines oo_stringAtIndex:4]] fadeTime:(permanent?0.0:[lastLines oo_floatAtIndex:5]) key:nil addToArray:nil]; } } if (gui_info != nil && [gui_info count] == 0) { // exists and it's empty. complete reset. [gui setCurrentRow:8]; [gui setDrawPosition: make_vector(0.0, -40.0, 640.0)]; [gui resizeTo:NSMakeSize(480, 160) characterHeight:19 title:nil]; [gui setCharacterSize:NSMakeSize(16,20)]; // narrow characters [UNIVERSE setPermanentMessageLog:NO]; } [gui setAlpha: 1.0]; // message_gui is always visible. // And now set up the comms log gui = [UNIVERSE commLogGUI]; gui_info = [info oo_dictionaryForKey:@"comm_log_gui"]; if (gui && [gui_info count] > 0) { [UNIVERSE setAutoCommLog:[gui_info oo_boolForKey:@"automatic" defaultValue:YES]]; [UNIVERSE setPermanentCommLog:[gui_info oo_boolForKey:@"permanent" defaultValue:NO]]; /* We need to repopulate the comms log after resetting it. At the moment the colour information is set on a per-line basis, rather than a per-text basis. A comms message can span multiple lines, and two consecutive messages can share the same colour, so trying to match the colour information from the GUI with each message won't work. Bottom line: colour information is lost on comms log gui reset. And yes, this is yet another reason for the following TODO: a more usable GUI code! - Kaks 2011.11.05 */ NSArray *cLog = [PLAYER commLog]; NSUInteger i, commCount = [cLog count]; [self resetGui:gui withInfo:gui_info]; for (i = 0; i < commCount; i++) { [gui printLongText:[cLog oo_stringAtIndex:i] align:GUI_ALIGN_LEFT color:nil fadeTime:0.0 key:nil addToArray:nil]; } } if (gui_info != nil && [gui_info count] == 0) { // exists and it's empty. complete reset. [UNIVERSE setAutoCommLog:YES]; [UNIVERSE setPermanentCommLog:NO]; [gui setCurrentRow:9]; [gui setDrawPosition: make_vector(0.0, 180.0, 640.0)]; [gui resizeTo:NSMakeSize(360, 120) characterHeight:12 title:nil]; [gui setBackgroundColor:[OOColor colorWithRed:0.0 green:0.05 blue:0.45 alpha:0.5]]; [gui setTextColor:[OOColor whiteColor]]; [gui printLongText:DESC(@"communications-log-string") align:GUI_ALIGN_CENTER color:[OOColor yellowColor] fadeTime:0 key:nil addToArray:nil]; } if ([UNIVERSE permanentCommLog]) { [gui stopFadeOuts]; [gui setAlpha:1.0]; } else { [gui setAlpha:0.0]; } } - (NSString *) hudName { return hudName; } - (void) setHudName:(NSString *)newHudName { if (newHudName != nil) { [hudName release]; hudName = [newHudName copy]; } } - (GLfloat) scannerZoom { return scanner_zoom; } - (void) setScannerZoom:(GLfloat)value { scanner_zoom = value; } - (GLfloat) overallAlpha { return overallAlpha; } - (void) setOverallAlpha:(GLfloat) newAlphaValue { overallAlpha = OOClamp_0_1_f(newAlphaValue); } - (BOOL) reticleTargetSensitive { return reticleTargetSensitive; } - (void) setReticleTargetSensitive:(BOOL) newReticleTargetSensitiveValue { reticleTargetSensitive = !!newReticleTargetSensitiveValue; // ensure YES or NO. } - (NSMutableDictionary *) propertiesReticleTargetSensitive { return propertiesReticleTargetSensitive; } - (BOOL) isHidden { return hudHidden; } - (void) setHidden:(BOOL)newValue { hudHidden = !!newValue; // ensure YES or NO } - (BOOL) allowBigGui { return allowBigGui || hudHidden; } - (BOOL) hasHidden:(NSString *)selectorName { if (selectorName == nil) { return NO; } return [_hiddenSelectors containsObject:selectorName]; } - (void) setHiddenSelector:(NSString *)selectorName hidden:(BOOL)hide { if (hide) { [_hiddenSelectors addObject:selectorName]; } else { [_hiddenSelectors removeObject:selectorName]; } } - (void) clearHiddenSelectors { [_hiddenSelectors removeAllObjects]; } - (BOOL) isCompassActive { return _compassActive; } - (void) setCompassActive:(BOOL)newValue { _compassActive = !!newValue; } - (BOOL) isUpdating { return hudUpdating; } - (void) setDeferredHudName:(NSString *)newDeferredHudName { [deferredHudName release]; deferredHudName = [newDeferredHudName copy]; } - (NSString *) deferredHudName { return deferredHudName; } - (void) addLegend:(NSDictionary *)info { NSString *imageName = nil; OOTexture *texture = nil; NSSize imageSize; OOTextureSprite *legendSprite = nil; NSMutableDictionary *legendDict = nil; struct CachedInfo cache; // prefetch data associated with this legend prefetchData(info, &cache); imageName = [info oo_stringForKey:IMAGE_KEY]; if (imageName != nil) { texture = [OOTexture textureWithName:imageName inFolder:@"Images" options:kOOTextureDefaultOptions | kOOTextureNoShrink anisotropy:kOOTextureDefaultAnisotropy lodBias:kOOTextureDefaultLODBias]; if (texture == nil) { OOLogERR(kOOLogFileNotFound, @"HeadUpDisplay couldn't get an image texture name for %@", imageName); return; } imageSize = [texture dimensions]; imageSize.width = [info oo_floatForKey:WIDTH_KEY defaultValue:imageSize.width]; imageSize.height = [info oo_floatForKey:HEIGHT_KEY defaultValue:imageSize.height]; legendSprite = [[OOTextureSprite alloc] initWithTexture:texture size:imageSize]; legendDict = [info mutableCopy]; [legendDict setObject:legendSprite forKey:SPRITE_KEY]; // add WIDGET_INFO, WIDGET_CACHE to array [legendArray addObject:[NSArray arrayWithObjects:legendDict, [NSValue valueWithBytes:&cache objCType:@encode(struct CachedInfo)], nil]]; [legendDict release]; [legendSprite release]; } else if ([info oo_stringForKey:TEXT_KEY] != nil) { // add WIDGET_INFO, WIDGET_CACHE to array [legendArray addObject:[NSArray arrayWithObjects:info, [NSValue valueWithBytes:&cache objCType:@encode(struct CachedInfo)], nil]]; } } - (void) addDial:(NSDictionary *)info { static NSSet *allowedSelectors = nil; if (allowedSelectors == nil) { NSDictionary *whitelist = [ResourceManager whitelistDictionary]; allowedSelectors = [[NSSet alloc] initWithArray:[whitelist oo_arrayForKey:@"hud_dial_methods"]]; } NSString *selectorString = [info oo_stringForKey:SELECTOR_KEY]; if (selectorString == nil) { OOLogERR(@"hud.dial.noSelector", @"HUD dial in %@ is missing selector.", hudName); return; } if (![allowedSelectors containsObject:selectorString]) { OOLogERR(@"hud.dial.invalidSelector", @"HUD dial in %@ uses selector \"%@\" which is not in whitelist, and will be ignored.", hudName, selectorString); return; } SEL selector = NSSelectorFromString(selectorString); NSAssert2([self respondsToSelector:selector], @"HUD dial in %@ uses selector \"%@\" which is in whitelist, but not implemented.", hudName, selectorString); // handle the case above with NS_BLOCK_ASSERTIONS too. if (![self respondsToSelector:selector]) { OOLogERR(@"hud.dial.invalidSelector", @"HUD dial in %@ uses selector \"%@\" which is in whitelist, but not implemented, and will be ignored.", hudName, selectorString); return; } // valid dial, now prefetch data struct CachedInfo cache; prefetchData(info, &cache); // add WIDGET_INFO, WIDGET_CACHE, WIDGET_SELECTOR, WIDGET_SELECTOR_NAME to array [dialArray addObject:[NSArray arrayWithObjects:info, [NSValue valueWithBytes:&cache objCType:@encode(struct CachedInfo)], [NSValue valueWithPointer:selector], selectorString, nil]]; } - (void) addMFD:(NSDictionary *)info { struct CachedInfo cache; prefetchData(info, &cache); [mfdArray addObject:[NSArray arrayWithObjects:info, [NSValue valueWithBytes:&cache objCType:@encode(struct CachedInfo)],nil]]; } - (NSUInteger) mfdCount { return [mfdArray count]; } /* SLOW_CODE As of 2012-09-13 (r5320), HUD rendering is taking 25%-30% of rendering time, or 15%-20% of game tick time, as tested on a couple of Macs using the default HUD and models. This could be worse - there used to be a note here saying 30%-40% of tick time - but could still improve. In a top-down perspective, of HUD rendering time, 67% is in -drawDials and 27% is in -drawLegends. Bottom-up, one profile shows: 21.2% OODrawString() (Caching the glyph conversion here was a win, but caching geometry in vertex arrays/VBOs would be better.) 8.9% -[HeadUpDisplay drawHudItem:] 5.1% OOFloatFromObject (Reifying HUD info instead of parsing plists each frame would be a win.) 4.4% hudDrawBarAt() (Using fixed geometery and a vertex shader could help here, especially if bars are grouped together and drawn at once if possible.) 4.3% -[OOCrosshairs render] (Uses vertex arrays, but does more GL state manipulation than strictly necessary.) */ - (void) renderHUD { hudUpdating = YES; OOVerifyOpenGLState(); if (_crosshairWidth * lineWidth > 0) { OOGL(GLScaledLineWidth(_crosshairWidth * lineWidth)); [self drawCrosshairs]; } if (lineWidth > 0) { OOGL(GLScaledLineWidth(lineWidth)); [self drawLegends]; } [self drawDials]; [self drawMFDs]; OOCheckOpenGLErrors(@"After drawing HUD"); OOVerifyOpenGLState(); hudUpdating = NO; } - (void) drawLegends { /* Since the order of legend drawing is significant, this loop must be kept * as an incrementing one for compatibility with previous Oolite versions. * CIM: 28/9/12 */ z1 = [[UNIVERSE gameView] display_z]; NSUInteger i, nLegends = [legendArray count]; for (i = 0; i < nLegends; i++) { sCurrentDrawItem = [legendArray oo_arrayAtIndex:i]; [self drawLegend:[sCurrentDrawItem oo_dictionaryAtIndex:WIDGET_INFO]]; } } - (void) drawDials { z1 = [[UNIVERSE gameView] display_z]; // reset drawScanner flag. _compassUpdated = NO; // tight loop, we assume dialArray doesn't change in mid-draw. NSUInteger i, nDials = [dialArray count]; for (i = 0; i < nDials; i++) { sCurrentDrawItem = [dialArray oo_arrayAtIndex:i]; [self drawHUDItem:[sCurrentDrawItem oo_dictionaryAtIndex:WIDGET_INFO]]; } if (EXPECT_NOT(!_compassUpdated && _compassActive && [self checkPlayerInSystemFlight])) // compass gone / broken / disabled ? { // trigger the targetChanged event with whom == null _compassActive = NO; [PLAYER doScriptEvent:OOJSID("compassTargetChanged") withArguments:[NSArray arrayWithObjects:[NSNull null], OOStringFromCompassMode([PLAYER compassMode]), nil]]; } } - (void) drawMFDs { NSUInteger i, nMFDs = [mfdArray count]; NSString *text = nil; for (i = 0; i < nMFDs; i++) { text = [PLAYER multiFunctionText:i]; if (text != nil) { sCurrentDrawItem = [mfdArray oo_arrayAtIndex:i]; [self drawMultiFunctionDisplay:[sCurrentDrawItem oo_dictionaryAtIndex:WIDGET_INFO] withText:text asIndex:i]; } } } - (void) drawCrosshairs { OOViewID viewID = [UNIVERSE viewDirection]; OOWeaponType weapon = [PLAYER currentWeapon]; BOOL weaponsOnline = [PLAYER weaponsOnline]; NSArray *points = nil; if (viewID == VIEW_CUSTOM || overallAlpha == 0.0f || !([PLAYER status] == STATUS_IN_FLIGHT || [PLAYER status] == STATUS_WITCHSPACE_COUNTDOWN) || [UNIVERSE displayGUI] ) { // Don't draw crosshairs return; } if (weapon != _lastWeaponType || overallAlpha != _lastOverallAlpha || weaponsOnline != _lastWeaponsOnline) { DESTROY(_crosshairs); } if (_crosshairs == nil) { GLfloat useAlpha = weaponsOnline ? overallAlpha : overallAlpha * 0.5f; // Make new crosshairs object points = [self crosshairDefinitionForWeaponType:weapon]; _crosshairs = [[OOCrosshairs alloc] initWithPoints:points scale:_crosshairScale color:_crosshairColor overallAlpha:useAlpha]; _lastWeaponType = weapon; _lastOverallAlpha = useAlpha; _lastWeaponsOnline = weaponsOnline; } [_crosshairs render]; } - (NSString *) crosshairDefinition { return crosshairDefinition; } - (BOOL) setCrosshairDefinition:(NSString *)newDefinition { // force crosshair redraw [_crosshairs release]; _crosshairs = nil; [_crosshairOverrides release]; _crosshairOverrides = [[ResourceManager dictionaryFromFilesNamed:newDefinition inFolder:@"Config" andMerge:YES] retain]; if (_crosshairOverrides == nil || [_crosshairOverrides count] == 0) { // invalid file [_crosshairOverrides release]; _crosshairOverrides = [[ResourceManager dictionaryFromFilesNamed:@"crosshairs.plist" inFolder:@"Config" andMerge:YES] retain]; crosshairDefinition = @"crosshairs.plist"; return NO; } crosshairDefinition = [newDefinition copy]; return YES; } - (NSArray *) crosshairDefinitionForWeaponType:(OOWeaponType)weapon { NSString *weaponName = nil; NSString *weaponName2 = nil; static NSDictionary *crosshairDefs = nil; NSArray *result = nil; /* Search order: (hud.plist).crosshairs.WEAPON_NAME (hud.plist).crosshairs.OTHER (crosshairs.plist).WEAPON_NAME (crosshairs.plist).OTHER */ weaponName = OOStringFromWeaponType(weapon); weaponName2 = [weaponName substringFromIndex:3]; // strip "EQ_" result = [_crosshairOverrides oo_arrayForKey:weaponName]; if (result == nil) { result = [_crosshairOverrides oo_arrayForKey:weaponName2]; } if (result == nil) result = [_crosshairOverrides oo_arrayForKey:@"OTHER"]; if (result == nil) { if (crosshairDefs == nil) { crosshairDefs = [ResourceManager dictionaryFromFilesNamed:@"crosshairs.plist" inFolder:@"Config" andMerge:YES]; [crosshairDefs retain]; } result = [crosshairDefs oo_arrayForKey:weaponName]; if (result == nil) { result = [crosshairDefs oo_arrayForKey:weaponName2]; } if (result == nil) result = [crosshairDefs oo_arrayForKey:@"OTHER"]; } return result; } - (void) drawLegend:(NSDictionary *)info { // check if equipment is required NSString *equipmentRequired = [info oo_stringForKey:EQUIPMENT_REQUIRED_KEY]; if (equipmentRequired != nil && ![PLAYER hasEquipmentItemProviding:equipmentRequired]) { return; } // check alert condition NSUInteger alertMask = [info oo_unsignedIntForKey:ALERT_CONDITIONS_KEY defaultValue:15]; // 1=docked, 2=green, 4=yellow, 8=red if (alertMask < 15) { OOAlertCondition alertCondition = [PLAYER alertCondition]; /* Because one of the items here is the scanner, which changes * the alert condition, this may give inconsistent results * mid-frame. This is unlikely to be crucial, but it's yet * another reason to get around to separating out scanner * display and alert level calculation - CIM */ if (~alertMask & (1 << alertCondition)) { return; } } BOOL viewOnly = [info oo_unsignedIntForKey:VIEWSCREEN_KEY defaultValue:NO]; // 1=docked, 2=green, 4=yellow, 8=red if (viewOnly && [PLAYER guiScreen] != GUI_SCREEN_MAIN) { return; } // check association with hidden dials if ([self hasHidden:[info oo_stringForKey:DIAL_REQUIRED_KEY defaultValue:nil]]) { return; } OOTextureSprite *legendSprite = nil; NSString *legendText = nil; float x, y; NSSize size; GLfloat alpha = overallAlpha; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; // if either x or y is missing, use 0 instead x = useDefined(cached.x, 0.0f) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, 0.0f) + [[UNIVERSE gameView] y_offset] * cached.y0; alpha *= cached.alpha; legendSprite = [info objectForKey:SPRITE_KEY]; if (legendSprite != nil) { [legendSprite blitCentredToX:x Y:y Z:z1 alpha:alpha]; } else { legendText = [info oo_stringForKey:TEXT_KEY]; if (legendText != nil) { // randomly chosen default width & height size.width = useDefined(cached.width, 14.0f); size.height = useDefined(cached.height, 8.0f); SET_COLOR(green_color); if ([info oo_intForKey:@"align"] == 1) { OODrawStringAligned(legendText, x, y, z1, size, YES); } else { OODrawStringAligned(legendText, x, y, z1, size, NO); } } } } - (void) drawHUDItem:(NSDictionary *)info { NSString *equipment = [info oo_stringForKey:EQUIPMENT_REQUIRED_KEY]; if (equipment != nil && ![PLAYER hasEquipmentItemProviding:equipment]) { return; } // check alert condition NSUInteger alertMask = [info oo_unsignedIntForKey:ALERT_CONDITIONS_KEY defaultValue:15]; // 1=docked, 2=green, 4=yellow, 8=red if (alertMask < 15) { OOAlertCondition alertCondition = [PLAYER alertCondition]; if (~alertMask & (1 << alertCondition)) { return; } } BOOL viewOnly = [info oo_unsignedIntForKey:VIEWSCREEN_KEY defaultValue:NO]; // 1=docked, 2=green, 4=yellow, 8=red if (viewOnly && [PLAYER guiScreen] != GUI_SCREEN_MAIN) { return; } if (EXPECT_NOT([self hasHidden:[sCurrentDrawItem objectAtIndex:WIDGET_SELECTOR_NAME]])) { return; } // use the selector value stored during init. [self performSelector:[(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_SELECTOR] pointerValue] withObject:info]; OOCheckOpenGLErrors(@"HeadUpDisplay after drawHUDItem %@", info); OOVerifyOpenGLState(); } - (BOOL) checkPlayerInFlight { return [PLAYER isInSpace] && [PLAYER status] != STATUS_DOCKING; } - (BOOL) checkPlayerInSystemFlight { OOSunEntity *the_sun = [UNIVERSE sun]; OOPlanetEntity *the_planet = [UNIVERSE planet]; return [self checkPlayerInFlight] // be in the right mode && the_sun && the_planet // and be in a system && ![the_sun goneNova]; } static void prefetchData(NSDictionary *info, struct CachedInfo *data) { data->x = [info oo_floatForKey:X_KEY defaultValue:NOT_DEFINED]; data->x0 = [info oo_floatForKey:X_ORIGIN_KEY defaultValue:0.0]; data->y = [info oo_floatForKey:Y_KEY defaultValue:NOT_DEFINED]; data->y0 = [info oo_floatForKey:Y_ORIGIN_KEY defaultValue:0.0]; data->width = [info oo_floatForKey:WIDTH_KEY defaultValue:NOT_DEFINED]; data->height = [info oo_floatForKey:HEIGHT_KEY defaultValue:NOT_DEFINED]; data->alpha = [info oo_nonNegativeFloatForKey:ALPHA_KEY defaultValue:1.0f]; } //---------------------------------------------------------------------// - (void) drawScanner:(NSDictionary *)info { int i, x, y; NSSize siz; GLfloat scanner_color[4] = { 1.0, 0.0, 0.0, 1.0 }; BOOL emptyDial = ([info oo_floatForKey:ALPHA_KEY] == 0.0f); BOOL isHostile = NO; if (emptyDial) { // we can skip a lot of code. x = y = 0; scanner_color[3] = 0.0; // nothing to see! siz = NSMakeSize(1.0, 1.0); // avoid divide by 0s } else { struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, SCANNER_CENTRE_X) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, SCANNER_CENTRE_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, SCANNER_WIDTH); siz.height = useDefined(cached.height, SCANNER_HEIGHT); GetRGBAArrayFromInfo(info, scanner_color); scanner_color[3] *= overallAlpha; } GLfloat alpha = scanner_color[3]; GLfloat col[4] = { 1.0, 1.0, 1.0, alpha }; // temporary colour variable GLfloat z_factor = siz.height / siz.width; // approx 1/4 GLfloat y_factor = 1.0 - sqrt(z_factor); // approx 1/2 int scanner_cx = x; int scanner_cy = y; int scannerFootprint = SCANNER_MAX_RANGE * 2.5 / siz.width; GLfloat zoom = scanner_zoom; if (scanner_ultra_zoom) zoom = pow(2, zoom - 1.0); GLfloat max_zoomed_range2 = SCANNER_SCALE * SCANNER_SCALE * 10000.0; if (!nonlinear_scanner) { max_zoomed_range2 /= zoom * zoom; } GLfloat max_zoomed_range = sqrt(max_zoomed_range2); if (PLAYER == nil) return; OOMatrix rotMatrix = [PLAYER rotationMatrix]; Vector relativePosition; int flash = ((int)([UNIVERSE getTime] * 4))&1; // use a non-mutable copy so this can't be changed under us. int ent_count = UNIVERSE->n_entities; Entity **uni_entities = UNIVERSE->sortedEntities; // grab the public sorted list Entity *my_entities[ent_count]; Entity *scannedEntity = nil; for (i = 0; i < ent_count; i++) { my_entities[i] = [uni_entities[i] retain]; // retained } if (!emptyDial) { OOGL(glColor4fv(scanner_color)); drawScannerGrid(x, y, z1, siz, [UNIVERSE viewDirection], lineWidth, zoom, nonlinear_scanner); } if ([self checkPlayerInFlight]) { GLfloat upscale = zoom * 1.25 / scannerFootprint; GLfloat max_blip = 0.0; int drawClass; OOVerifyOpenGLState(); // Debugging code for nonlinear scanner - draws three fake cascade weapons, which looks pretty and enables me // to debug the code without the mass slaughter of innocent civillians. //if (nonlinear_scanner) //{ // Vector p = OOVectorMultiplyMatrix(make_vector(10000.0, 0.0, 0.0), rotMatrix); // GLDrawNonlinearCascadeWeapon( scanner_cx, scanner_cy, z1, siz, p, 5000, zoom, alpha ); // p = OOVectorMultiplyMatrix(make_vector(10000.0, 4500.0, 0.0), rotMatrix); // GLDrawNonlinearCascadeWeapon( scanner_cx, scanner_cy, z1, siz, p, 2000, zoom, alpha ); // p = OOVectorMultiplyMatrix(make_vector(0.0, 0.0, 20000.0), rotMatrix); // GLDrawNonlinearCascadeWeapon( scanner_cx, scanner_cy, z1, siz, p, 6000, zoom, alpha ); //} for (i = 0; i < ent_count; i++) // scanner lollypops { scannedEntity = my_entities[i]; drawClass = [scannedEntity scanClass]; // cloaked ships - and your own one - don't show up on the scanner. if (EXPECT_NOT(drawClass == CLASS_PLAYER || ([scannedEntity isShip] && [(ShipEntity *)scannedEntity isCloaked]))) { drawClass = CLASS_NO_DRAW; } if (drawClass != CLASS_NO_DRAW) { GLfloat x1,y1,y2; float ms_blip = 0.0; if (emptyDial) continue; if (isnan(scannedEntity->zero_distance)) continue; // exit if it's too far away GLfloat act_dist = sqrt(scannedEntity->zero_distance); GLfloat lim_dist = act_dist - scannedEntity->collision_radius; // for efficiency, assume no scannable entity > 10km radius if (act_dist > max_zoomed_range + 10000.0) break; if (lim_dist > max_zoomed_range) continue; // has it sent a recent message // if ([scannedEntity isShip]) ms_blip = 2.0 * [(ShipEntity *)scannedEntity messageTime]; if (ms_blip > max_blip) { max_blip = ms_blip; last_transmitter = [scannedEntity universalID]; } ms_blip -= floor(ms_blip); relativePosition = [PLAYER vectorTo:scannedEntity]; double fuzz = [PLAYER scannerFuzziness]; if (fuzz > 0 && ![[UNIVERSE gameController] isGamePaused]) { relativePosition = vector_add(relativePosition,OOVectorRandomRadial(fuzz)); } Vector rp = relativePosition; if (act_dist > max_zoomed_range) scale_vector(&relativePosition, max_zoomed_range / act_dist); // rotate the view relativePosition = OOVectorMultiplyMatrix(relativePosition, rotMatrix); Vector rrp = relativePosition; // scale the view if (nonlinear_scanner) { relativePosition = [HeadUpDisplay nonlinearScannerScale: relativePosition Zoom: zoom Scale: 0.5*siz.width]; } else { scale_vector(&relativePosition, upscale); } x1 = relativePosition.x; y1 = z_factor * relativePosition.z; y2 = y1 + y_factor * relativePosition.y; isHostile = NO; if ([scannedEntity isShip]) { ShipEntity *ship = (ShipEntity *)scannedEntity; isHostile = (([ship hasHostileTarget])&&([ship primaryTarget] == PLAYER)); GLfloat *base_col = [ship scannerDisplayColorForShip:PLAYER :isHostile :flash :[ship scannerDisplayColor1] :[ship scannerDisplayColor2] :[ship scannerDisplayColorHostile1] :[ship scannerDisplayColorHostile2] ]; col[0] = base_col[0]; col[1] = base_col[1]; col[2] = base_col[2]; col[3] = alpha * base_col[3]; } else if ([scannedEntity isVisualEffect]) { OOVisualEffectEntity *vis = (OOVisualEffectEntity *)scannedEntity; GLfloat* base_col = [vis scannerDisplayColorForShip:flash :[vis scannerDisplayColor1] :[vis scannerDisplayColor2]]; col[0] = base_col[0]; col[1] = base_col[1]; col[2] = base_col[2]; col[3] = alpha * base_col[3]; } if ([scannedEntity isWormhole]) { col[0] = blue_color[0]; col[1] = (flash)? 1.0 : blue_color[1]; col[2] = blue_color[2]; col[3] = alpha * blue_color[3]; } // position the scanner x1 += scanner_cx; y1 += scanner_cy; y2 += scanner_cy; if ([scannedEntity isShip]) { ShipEntity* ship = (ShipEntity*)scannedEntity; if ((!nonlinear_scanner && ship->collision_radius * upscale > 4.5) || (nonlinear_scanner && nonlinearScannerFunc(act_dist, zoom, siz.width) - nonlinearScannerFunc(lim_dist, zoom, siz.width) > 4.5 )) { Vector bounds[6]; BoundingBox bb = ship->totalBoundingBox; bounds[0] = ship->v_forward; scale_vector(&bounds[0], bb.max.z); bounds[1] = ship->v_forward; scale_vector(&bounds[1], bb.min.z); bounds[2] = ship->v_right; scale_vector(&bounds[2], bb.max.x); bounds[3] = ship->v_right; scale_vector(&bounds[3], bb.min.x); bounds[4] = ship->v_up; scale_vector(&bounds[4], bb.max.y); bounds[5] = ship->v_up; scale_vector(&bounds[5], bb.min.y); // rotate the view int i; for (i = 0; i < 6; i++) { bounds[i] = OOVectorMultiplyMatrix(vector_add(bounds[i], rp), rotMatrix); if (nonlinear_scanner) { bounds[i] = [HeadUpDisplay nonlinearScannerScale:bounds[i] Zoom: zoom Scale: 0.5*siz.width]; } else { scale_vector(&bounds[i], upscale); } bounds[i] = make_vector(bounds[i].x + scanner_cx, bounds[i].z * z_factor + bounds[i].y * y_factor + scanner_cy, z1 ); } // draw the diamond // OOGLBEGIN(GL_QUADS); glColor4f(col[0], col[1], col[2], 0.33333 * col[3]); glVertex3f(bounds[0].x, bounds[0].y, bounds[0].z); glVertex3f(bounds[4].x, bounds[4].y, bounds[4].z); glVertex3f(bounds[1].x, bounds[1].y, bounds[1].z); glVertex3f(bounds[5].x, bounds[5].y, bounds[5].z); glVertex3f(bounds[2].x, bounds[2].y, bounds[2].z); glVertex3f(bounds[4].x, bounds[4].y, bounds[4].z); glVertex3f(bounds[3].x, bounds[3].y, bounds[3].z); glVertex3f(bounds[5].x, bounds[5].y, bounds[5].z); glVertex3f(bounds[2].x, bounds[2].y, bounds[2].z); glVertex3f(bounds[0].x, bounds[0].y, bounds[0].z); glVertex3f(bounds[3].x, bounds[3].y, bounds[3].z); glVertex3f(bounds[1].x, bounds[1].y, bounds[1].z); OOGLEND(); } } if (ms_blip > 0.0) { DrawSpecialOval(x1 - 0.5, y2 + 1.5, z1, NSMakeSize(16.0 * (1.0 - ms_blip), 8.0 * (1.0 - ms_blip)), 30, col); } if ([scannedEntity isCascadeWeapon]) { if (nonlinear_scanner) { GLDrawNonlinearCascadeWeapon( scanner_cx, scanner_cy, z1, siz, rrp, scannedEntity->collision_radius, zoom, alpha ); } else { GLfloat r1 = 2.5 + scannedEntity->collision_radius * upscale; GLfloat l2 = r1 * r1 - relativePosition.y * relativePosition.y; GLfloat r0 = (l2 > 0)? sqrt(l2): 0; if (r0 > 0) { OOGL(glColor4f(1.0, 0.5, 1.0, alpha)); GLDrawOval(x1 - 0.5, y1 + 1.5, z1, NSMakeSize(r0, r0 * siz.height / siz.width), 20); } OOGL(glColor4f(0.5, 0.0, 1.0, 0.33333 * alpha)); GLDrawFilledOval(x1 - 0.5, y2 + 1.5, z1, NSMakeSize(r1, r1), 15); } } else { #if IDENTIFY_SCANNER_LOLLIPOPS if ([scannedEntity isShip]) { glColor4f(1.0, 1.0, 0.5, alpha); OODrawString([(ShipEntity *)scannedEntity displayName], x1 + 2, y2 + 2, z1, NSMakeSize(8, 8)); } #endif OOGLBEGIN(GL_QUADS); glColor4fv(col); glVertex3f(x1-3, y2, z1); glVertex3f(x1+2, y2, z1); glVertex3f(x1+2, y2+3, z1); glVertex3f(x1-3, y2+3, z1); col[3] *= 0.3333; // one third the alpha glColor4fv(col); glVertex3f(x1, y1, z1); glVertex3f(x1+2, y1, z1); glVertex3f(x1+2, y2, z1); glVertex3f(x1, y2, z1); OOGLEND(); } } } } for (i = 0; i < ent_count; i++) { [my_entities[i] release]; // released } OOVerifyOpenGLState(); } + (Vector) nonlinearScannerScale: (Vector) V Zoom:(GLfloat)zoom Scale:(double) scale { OOScalar mag = magnitude(V); Vector unit = vector_normal(V); return vector_multiply_scalar(unit, nonlinearScannerFunc(mag, zoom, scale)); } - (BOOL) nonlinearScanner { return nonlinear_scanner; } - (void) setNonlinearScanner: (BOOL) newValue { nonlinear_scanner = !!newValue; } - (BOOL) scannerUltraZoom { return scanner_ultra_zoom; } - (void) setScannerUltraZoom: (BOOL) newValue { scanner_ultra_zoom = !!newValue; } - (void) refreshLastTransmitter { Entity* lt = [UNIVERSE entityForUniversalID:last_transmitter]; if ((lt == nil)||(!(lt->isShip))) return; ShipEntity* st = (ShipEntity*)lt; if ([st messageTime] <= 0.0) [st setMessageTime:2.5]; } - (void) drawScannerZoomIndicator:(NSDictionary *)info { int x, y; NSSize siz; GLfloat alpha; GLfloat zoom_color[4] = { 1.0f, 0.1f, 0.0f, 1.0f }; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, ZOOM_INDICATOR_CENTRE_X) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, ZOOM_INDICATOR_CENTRE_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, ZOOM_INDICATOR_WIDTH); siz.height = useDefined(cached.height, ZOOM_INDICATOR_HEIGHT); GetRGBAArrayFromInfo(info, zoom_color); zoom_color[3] *= overallAlpha; alpha = zoom_color[3]; GLfloat cx = x - 0.3 * siz.width; GLfloat cy = y - 0.75 * siz.height; int zl = scanner_zoom; if (zl < 1) zl = 1; if (zl > SCANNER_ZOOM_LEVELS) zl = SCANNER_ZOOM_LEVELS; if (zl == 1) zoom_color[3] *= 0.75; if (scanner_ultra_zoom) zl = pow(2, zl - 1); GLColorWithOverallAlpha(zoom_color, alpha); OOGL(glEnable(GL_TEXTURE_2D)); [sFontTexture apply]; OOGLBEGIN(GL_QUADS); if (zl / 10 > 0) drawCharacterQuad(48 + zl / 10, cx - 0.8 * siz.width, cy, z1, siz); drawCharacterQuad(48 + zl % 10, cx - 0.4 * siz.width, cy, z1, siz); drawCharacterQuad(58, cx, cy, z1, siz); drawCharacterQuad(49, cx + 0.3 * siz.width, cy, z1, siz); OOGLEND(); [OOTexture applyNone]; OOGL(glDisable(GL_TEXTURE_2D)); } - (void) drawCompass:(NSDictionary *)info { int x, y; NSSize siz; GLfloat alpha; GLfloat compass_color[4] = { 0.0f, 0.0f, 1.0f, 1.0f }; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, COMPASS_CENTRE_X) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, COMPASS_CENTRE_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, COMPASS_HALF_SIZE); siz.height = useDefined(cached.height, COMPASS_HALF_SIZE); GetRGBAArrayFromInfo(info, compass_color); compass_color[3] *= overallAlpha; alpha = compass_color[3]; // draw the compass OOMatrix rotMatrix = [PLAYER rotationMatrix]; GLfloat h1 = siz.height * 0.125; GLfloat h3 = siz.height * 0.375; GLfloat w1 = siz.width * 0.125; GLfloat w3 = siz.width * 0.375; OOGL(GLScaledLineWidth(2.0 * lineWidth)); // thicker OOGL(glColor4f(compass_color[0], compass_color[1], compass_color[2], alpha)); GLDrawOval(x, y, z1, siz, 12); OOGL(glColor4f(compass_color[0], compass_color[1], compass_color[2], 0.5f * alpha)); OOGLBEGIN(GL_LINES); glVertex3f(x - w1, y, z1); glVertex3f(x - w3, y, z1); glVertex3f(x + w1, y, z1); glVertex3f(x + w3, y, z1); glVertex3f(x, y - h1, z1); glVertex3f(x, y - h3, z1); glVertex3f(x, y + h1, z1); glVertex3f(x, y + h3, z1); OOGLEND(); OOGL(GLScaledLineWidth(lineWidth)); // thinner if ([self checkPlayerInSystemFlight] && [PLAYER status] != STATUS_LAUNCHING) // normal system { Entity *reference = [PLAYER compassTarget]; // translate and rotate the view Vector relativePosition = [PLAYER vectorTo:reference]; relativePosition = OOVectorMultiplyMatrix(relativePosition, rotMatrix); relativePosition = vector_normal_or_fallback(relativePosition, kBasisZVector); relativePosition.x *= siz.width * 0.4; relativePosition.y *= siz.height * 0.4; relativePosition.x += x; relativePosition.y += y; siz.width *= 0.2; siz.height *= 0.2; OOGL(GLScaledLineWidth(2.0*lineWidth)); switch ([PLAYER compassMode]) { case COMPASS_MODE_INACTIVE: break; case COMPASS_MODE_BASIC: [self drawCompassPlanetBlipAt:relativePosition Size:siz Alpha:alpha]; break; case COMPASS_MODE_PLANET: [self drawCompassPlanetBlipAt:relativePosition Size:siz Alpha:alpha]; break; case COMPASS_MODE_STATION: [self drawCompassStationBlipAt:relativePosition Size:siz Alpha:alpha]; break; case COMPASS_MODE_SUN: [self drawCompassSunBlipAt:relativePosition Size:siz Alpha:alpha]; break; case COMPASS_MODE_TARGET: [self drawCompassTargetBlipAt:relativePosition Size:siz Alpha:alpha]; break; case COMPASS_MODE_BEACONS: [self drawCompassBeaconBlipAt:relativePosition Size:siz Alpha:alpha]; Entity *beacon = [PLAYER nextBeacon]; [[beacon beaconDrawable] oo_drawHUDBeaconIconAt:NSMakePoint(x, y) size:siz alpha:alpha z:z1]; break; } OOGL(GLScaledLineWidth(lineWidth)); // reset _compassUpdated = YES; _compassActive = YES; } } OOINLINE void SetCompassBlipColor(GLfloat relativeZ, GLfloat alpha) { if (relativeZ >= 0.0f) { OOGL(glColor4f(0.0f, 1.0f, 0.0f, alpha)); } else { OOGL(glColor4f(1.0f, 0.0f, 0.0f, alpha)); } } - (void) drawCompassPlanetBlipAt:(Vector)relativePosition Size:(NSSize)siz Alpha:(GLfloat)alpha { if (relativePosition.z >= 0) { OOGL(glColor4f(0.0,1.0,0.0,0.75 * alpha)); GLDrawFilledOval(relativePosition.x, relativePosition.y, z1, siz, 30); OOGL(glColor4f(0.0,1.0,0.0,alpha)); GLDrawOval(relativePosition.x, relativePosition.y, z1, siz, 30); } else { OOGL(glColor4f(1.0,0.0,0.0,alpha)); GLDrawOval(relativePosition.x, relativePosition.y, z1, siz, 30); } } - (void) drawCompassStationBlipAt:(Vector) relativePosition Size:(NSSize) siz Alpha:(GLfloat) alpha { SetCompassBlipColor(relativePosition.z, alpha); OOGLBEGIN(GL_LINE_LOOP); glVertex3f(relativePosition.x - 0.5 * siz.width, relativePosition.y - 0.5 * siz.height, z1); glVertex3f(relativePosition.x + 0.5 * siz.width, relativePosition.y - 0.5 * siz.height, z1); glVertex3f(relativePosition.x + 0.5 * siz.width, relativePosition.y + 0.5 * siz.height, z1); glVertex3f(relativePosition.x - 0.5 * siz.width, relativePosition.y + 0.5 * siz.height, z1); OOGLEND(); } - (void) drawCompassSunBlipAt:(Vector) relativePosition Size:(NSSize) siz Alpha:(GLfloat) alpha { OOGL(glColor4f(1.0, 1.0, 0.0, 0.75 * alpha)); GLDrawFilledOval(relativePosition.x, relativePosition.y, z1, siz, 30); SetCompassBlipColor(relativePosition.z, alpha); GLDrawOval(relativePosition.x, relativePosition.y, z1, siz, 30); } - (void) drawCompassTargetBlipAt:(Vector) relativePosition Size:(NSSize) siz Alpha:(GLfloat) alpha { SetCompassBlipColor(relativePosition.z, alpha); OOGLBEGIN(GL_LINES); glVertex3f(relativePosition.x - siz.width, relativePosition.y, z1); glVertex3f(relativePosition.x + siz.width, relativePosition.y, z1); glVertex3f(relativePosition.x, relativePosition.y - siz.height, z1); glVertex3f(relativePosition.x, relativePosition.y + siz.height, z1); OOGLEND(); GLDrawOval(relativePosition.x, relativePosition.y, z1, siz, 30); } - (void) drawCompassBeaconBlipAt:(Vector) relativePosition Size:(NSSize) siz Alpha:(GLfloat) alpha { SetCompassBlipColor(relativePosition.z, alpha); OOGLBEGIN(GL_LINES); /* glVertex3f(relativePosition.x - 0.5 * siz.width, relativePosition.y - 0.5 * siz.height, z1); glVertex3f(relativePosition.x, relativePosition.y + 0.5 * siz.height, z1); glVertex3f(relativePosition.x + 0.5 * siz.width, relativePosition.y - 0.5 * siz.height, z1); glVertex3f(relativePosition.x, relativePosition.y + 0.5 * siz.height, z1); glVertex3f(relativePosition.x - 0.5 * siz.width, relativePosition.y - 0.5 * siz.height, z1); glVertex3f(relativePosition.x + 0.5 * siz.width, relativePosition.y - 0.5 * siz.height, z1); */ glVertex3f(relativePosition.x + 0.6 * siz.width, relativePosition.y, z1); glVertex3f(relativePosition.x, relativePosition.y + 0.6 * siz.height, z1); glVertex3f(relativePosition.x - 0.6 * siz.width, relativePosition.y, z1); glVertex3f(relativePosition.x, relativePosition.y + 0.6 * siz.height, z1); glVertex3f(relativePosition.x + 0.6 * siz.width, relativePosition.y, z1); glVertex3f(relativePosition.x, relativePosition.y - 0.6 * siz.height, z1); glVertex3f(relativePosition.x - 0.6 * siz.width, relativePosition.y, z1); glVertex3f(relativePosition.x, relativePosition.y - 0.6 * siz.height, z1); OOGLEND(); } - (void) drawAegis:(NSDictionary *)info { if (([UNIVERSE viewDirection] == VIEW_GUI_DISPLAY)||([UNIVERSE sun] == nil)||([PLAYER checkForAegis] != AEGIS_IN_DOCKING_RANGE)) return; // don't draw int x, y; NSSize siz; GLfloat alpha = 0.5f * overallAlpha; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, AEGIS_CENTRE_X) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, AEGIS_CENTRE_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, AEGIS_WIDTH); siz.height = useDefined(cached.height, AEGIS_HEIGHT); alpha *= cached.alpha; // draw the aegis indicator // GLfloat w = siz.width / 16.0; GLfloat h = siz.height / 16.0; GLfloat strip[] = { -7,8, -6,5, 5,8, 3,5, 7,2, 4,2, 6,-1, 4,2, -4,-1, -6,2, -4,-1, -7,-1, -3,-4, -5,-7, 6,-4, 7,-7 }; #if 1 OOGL(glColor4f(0.0f, 1.0f, 0.0f, alpha)); OOGLBEGIN(GL_QUAD_STRIP); int i; for (i = 0; i < 32; i += 2) { glVertex3f(x + w * strip[i], y - h * strip[i + 1], z1); } OOGLEND(); #else OOGLPushModelView(); OOGLTranslateModelView(make_vector(x, y, z1)); OOGLScaleModelView(make_vector(w, -h, 1.0f)); OOGL(glColor4f(0.0f, 1.0f, 0.0f, alpha)); OOGL(glVertexPointer(2, GL_FLOAT, 0, strip)); OOGL(glEnableClientState(GL_VERTEX_ARRAY)); OOGL(glDisableClientState(GL_COLOR_ARRAY)); OOGL(glDrawArrays(GL_QUAD_STRIP, 0, sizeof strip / sizeof *strip / 2)); OOGL(glDisableClientState(GL_VERTEX_ARRAY)); OOGLPopModelView(); #endif } - (void) drawCustomBar:(NSDictionary *)info { int x, y; NSSize siz; BOOL draw_surround; GLfloat alpha = overallAlpha; GLfloat ds = OOClamp_0_1_f([PLAYER dialCustomFloat:[info oo_stringForKey:CUSTOM_DIAL_KEY]]); struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, 0) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, 0) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, 50); siz.height = useDefined(cached.height, 8); alpha *= cached.alpha; draw_surround = [info oo_boolForKey:DRAW_SURROUND_KEY defaultValue:NO]; SET_COLOR_SURROUND(green_color); if (draw_surround) { // draw custom surround hudDrawSurroundAt(x, y, z1, siz); } // draw custom bar if (ds > .75) { SET_COLOR_HIGH(green_color); } else if (ds > .25) { SET_COLOR_MEDIUM(yellow_color); } else { SET_COLOR_LOW(red_color); } hudDrawBarAt(x, y, z1, siz, ds); } - (void) drawCustomText:(NSDictionary *)info { int x, y; NSSize size; GLfloat alpha = overallAlpha; NSString *text = [PLAYER dialCustomString:[info oo_stringForKey:CUSTOM_DIAL_KEY]]; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, 0) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, 0) + [[UNIVERSE gameView] y_offset] * cached.y0; alpha *= cached.alpha; SET_COLOR(yellow_color); size.width = useDefined(cached.width, 10.0f); size.height = useDefined(cached.height, 10.0f); if ([info oo_intForKey:@"align"] == 1) { OODrawStringAligned(text, x, y, z1, size, YES); } else { OODrawStringAligned(text, x, y, z1, size, NO); } } - (void) drawCustomIndicator:(NSDictionary *)info { int x, y; NSSize siz; BOOL draw_surround; GLfloat alpha = overallAlpha; GLfloat iv = OOClamp_n1_1_f([PLAYER dialCustomFloat:[info oo_stringForKey:CUSTOM_DIAL_KEY]]); struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, 0) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, 0) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, 50); siz.height = useDefined(cached.height, 8); alpha *= cached.alpha; draw_surround = [info oo_boolForKey:DRAW_SURROUND_KEY defaultValue:NO]; if (draw_surround) { // draw custom surround SET_COLOR_SURROUND(green_color); hudDrawSurroundAt(x, y, z1, siz); } // draw custom indicator SET_COLOR(yellow_color); hudDrawIndicatorAt(x, y, z1, siz, iv); } - (void) drawCustomLight:(NSDictionary *)info { int x, y; NSSize siz; GLfloat alpha = overallAlpha; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, 0) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, 0) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, 8); siz.height = useDefined(cached.height, 8); alpha *= cached.alpha; GLfloat light_color[4] = { 0.25, 0.25, 0.25, 0.0}; OOColor *color = [PLAYER dialCustomColor:[info oo_stringForKey:CUSTOM_DIAL_KEY]]; [color getRed:&light_color[0] green:&light_color[1] blue:&light_color[2] alpha:&light_color[3]]; GLColorWithOverallAlpha(light_color, alpha); OOGLBEGIN(GL_POLYGON); hudDrawStatusIconAt(x, y, z1, siz); OOGLEND(); OOGL(glColor4f(0.25, 0.25, 0.25, alpha)); OOGLBEGIN(GL_LINE_LOOP); hudDrawStatusIconAt(x, y, z1, siz); OOGLEND(); } - (void) drawCustomImage:(NSDictionary *)info { int x, y; GLfloat alpha = overallAlpha; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, 0) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, 0) + [[UNIVERSE gameView] y_offset] * cached.y0; alpha *= cached.alpha; NSString *textureFile = [PLAYER dialCustomString:[info oo_stringForKey:CUSTOM_DIAL_KEY]]; if (textureFile == nil || [textureFile length] == 0) { return; } OOTexture *texture = [OOTexture textureWithName:textureFile inFolder:@"Images" options:kOOTextureDefaultOptions | kOOTextureNoShrink anisotropy:kOOTextureDefaultAnisotropy lodBias:kOOTextureDefaultLODBias]; if (texture == nil) { OOLogERR(kOOLogFileNotFound, @"HeadUpDisplay couldn't get an image texture name for %@", textureFile); return; } NSSize imageSize = [texture dimensions]; imageSize.width = useDefined(cached.width, imageSize.width); imageSize.height = useDefined(cached.height, imageSize.height); /* There's possibly some optimisation which could be done by * caching the sprite, but regenerating it each frame doesn't * appear to take any significant amount of time compared with the * time taken to actually render it and the texture will be * returned from the cache anyway. - CIM */ OOTextureSprite *sprite = [[OOTextureSprite alloc] initWithTexture:texture size:imageSize]; [sprite blitCentredToX:x Y:y Z:z1 alpha:alpha]; [sprite release]; } - (void) drawSpeedBar:(NSDictionary *)info { int x, y; NSSize siz; BOOL draw_surround; GLfloat alpha = overallAlpha; GLfloat ds = [PLAYER dialSpeed]; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, SPEED_BAR_CENTRE_X) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, SPEED_BAR_CENTRE_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, SPEED_BAR_WIDTH); siz.height = useDefined(cached.height, SPEED_BAR_HEIGHT); alpha *= cached.alpha; draw_surround = [info oo_boolForKey:DRAW_SURROUND_KEY defaultValue:SPEED_BAR_DRAW_SURROUND]; SET_COLOR_SURROUND(green_color); if (draw_surround) { // draw speed surround hudDrawSurroundAt(x, y, z1, siz); } // draw speed bar if (ds > .80) { SET_COLOR_HIGH(red_color); } else if (ds > .25) { SET_COLOR_MEDIUM(yellow_color); } else { SET_COLOR_LOW(green_color); } hudDrawBarAt(x, y, z1, siz, ds); } - (void) drawRollBar:(NSDictionary *)info { int x, y; NSSize siz; BOOL draw_surround; GLfloat alpha = overallAlpha; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, ROLL_BAR_CENTRE_X) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, ROLL_BAR_CENTRE_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, ROLL_BAR_WIDTH); siz.height = useDefined(cached.height, ROLL_BAR_HEIGHT); alpha *= cached.alpha; draw_surround = [info oo_boolForKey:DRAW_SURROUND_KEY defaultValue:ROLL_BAR_DRAW_SURROUND]; if (draw_surround) { // draw ROLL surround SET_COLOR_SURROUND(green_color); hudDrawSurroundAt(x, y, z1, siz); } // draw ROLL bar SET_COLOR(yellow_color); hudDrawIndicatorAt(x, y, z1, siz, [PLAYER dialRoll]); } - (void) drawPitchBar:(NSDictionary *)info { int x, y; NSSize siz; BOOL draw_surround; GLfloat alpha = overallAlpha; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, PITCH_BAR_CENTRE_X) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, PITCH_BAR_CENTRE_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, PITCH_BAR_WIDTH); siz.height = useDefined(cached.height, PITCH_BAR_HEIGHT); alpha *= cached.alpha; draw_surround = [info oo_boolForKey:DRAW_SURROUND_KEY defaultValue:PITCH_BAR_DRAW_SURROUND]; if (draw_surround) { // draw PITCH surround SET_COLOR_SURROUND(green_color); hudDrawSurroundAt(x, y, z1, siz); } // draw PITCH bar SET_COLOR(yellow_color); hudDrawIndicatorAt(x, y, z1, siz, [PLAYER dialPitch]); } - (void) drawYawBar:(NSDictionary *)info { int x, y; NSSize siz; BOOL draw_surround; GLfloat alpha = overallAlpha; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; // No standard YAW definitions - using PITCH ones instead. x = useDefined(cached.x, PITCH_BAR_CENTRE_X) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, PITCH_BAR_CENTRE_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, PITCH_BAR_WIDTH); siz.height = useDefined(cached.height, PITCH_BAR_HEIGHT); alpha *= cached.alpha; draw_surround = [info oo_boolForKey:DRAW_SURROUND_KEY defaultValue:PITCH_BAR_DRAW_SURROUND]; if (draw_surround) { // draw YAW surround SET_COLOR_SURROUND(green_color); hudDrawSurroundAt(x, y, z1, siz); } // draw YAW bar SET_COLOR(yellow_color); hudDrawIndicatorAt(x, y, z1, siz, [PLAYER dialYaw]); } - (void) drawEnergyGauge:(NSDictionary *)info { int x, y; unsigned i; NSSize siz; BOOL drawSurround, labelled, energyCritical = NO; GLfloat alpha = overallAlpha; GLfloat bankHeight, bankY; PlayerEntity *player = PLAYER; unsigned n_bars = [player dialMaxEnergy]/64.0; n_bars = [info oo_unsignedIntForKey:N_BARS_KEY defaultValue:n_bars]; if (n_bars < 1) { n_bars = 1; } GLfloat energy = [player dialEnergy] * n_bars; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, ENERGY_GAUGE_CENTRE_X) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, ENERGY_GAUGE_CENTRE_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, ENERGY_GAUGE_WIDTH); siz.height = useDefined(cached.height, ENERGY_GAUGE_HEIGHT); alpha *= cached.alpha; drawSurround = [info oo_boolForKey:DRAW_SURROUND_KEY defaultValue:ENERGY_GAUGE_DRAW_SURROUND]; labelled = [info oo_boolForKey:LABELLED_KEY defaultValue:YES]; if (n_bars > 8) labelled = NO; if (drawSurround) { // draw energy surround SET_COLOR_SURROUND(yellow_color); hudDrawSurroundAt(x, y, z1, siz); } bankHeight = siz.height / n_bars; // draw energy banks NSSize barSize = NSMakeSize(siz.width, bankHeight - 2.0); // leave a gap between bars GLfloat midBank = bankHeight / 2.0f; bankY = y - (n_bars - 1) * midBank - 1.0; // avoid constant colour switching... if (labelled) { GLColorWithOverallAlpha(green_color, alpha); GLfloat labelStartX = x + 0.5f * barSize.width + 3.0f; NSSize labelSize = NSMakeSize(9.0, (bankHeight < 18.0)? bankHeight : 18.0); for (i = 0; i < n_bars; i++) { OODrawString([NSString stringWithFormat:@"E%x", n_bars - i], labelStartX, bankY - midBank, z1, labelSize); bankY += bankHeight; } } if (energyCritical) { SET_COLOR_LOW(red_color); } else { SET_COLOR_MEDIUM(yellow_color); } bankY = y - (n_bars - 1) * midBank; for (i = 0; i < n_bars; i++) { if (energy > 1.0) { hudDrawBarAt(x, bankY, z1, barSize, 1.0); } else if (energy > 0.0) { hudDrawBarAt(x, bankY, z1, barSize, energy); } energy -= 1.0; bankY += bankHeight; } } - (void) drawForwardShieldBar:(NSDictionary *)info { int x, y; NSSize siz; BOOL draw_surround; GLfloat alpha = overallAlpha; GLfloat shield = [PLAYER dialForwardShield]; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, FORWARD_SHIELD_BAR_CENTRE_X) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, FORWARD_SHIELD_BAR_CENTRE_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, FORWARD_SHIELD_BAR_WIDTH); siz.height = useDefined(cached.height, FORWARD_SHIELD_BAR_HEIGHT); alpha *= cached.alpha; draw_surround = [info oo_boolForKey:DRAW_SURROUND_KEY defaultValue:FORWARD_SHIELD_BAR_DRAW_SURROUND]; if (draw_surround) { // draw forward_shield surround SET_COLOR_SURROUND(green_color); hudDrawSurroundAt(x, y, z1, siz); } // draw forward_shield bar if (shield < .25) { SET_COLOR_LOW(red_color); } else if (shield < .80) { SET_COLOR_MEDIUM(yellow_color); } else { SET_COLOR_HIGH(green_color); } hudDrawBarAt(x, y, z1, siz, shield); } - (void) drawAftShieldBar:(NSDictionary *)info { int x, y; NSSize siz; BOOL draw_surround; GLfloat alpha = overallAlpha; GLfloat shield = [PLAYER dialAftShield]; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, AFT_SHIELD_BAR_CENTRE_X) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, AFT_SHIELD_BAR_CENTRE_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, AFT_SHIELD_BAR_WIDTH); siz.height = useDefined(cached.height, AFT_SHIELD_BAR_HEIGHT); alpha *= cached.alpha; draw_surround = [info oo_boolForKey:DRAW_SURROUND_KEY defaultValue:AFT_SHIELD_BAR_DRAW_SURROUND]; if (draw_surround) { // draw forward_shield surround SET_COLOR_SURROUND(green_color); hudDrawSurroundAt(x, y, z1, siz); } // draw forward_shield bar if (shield < .25) { SET_COLOR_LOW(red_color); } else if (shield < .80) { SET_COLOR_MEDIUM(yellow_color); } else { SET_COLOR_HIGH(green_color); } hudDrawBarAt(x, y, z1, siz, shield); } - (void) drawFuelBar:(NSDictionary *)info { int x, y; NSSize siz; BOOL draw_surround; float fu, hr; GLfloat alpha = overallAlpha; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, FUEL_BAR_CENTRE_X) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, FUEL_BAR_CENTRE_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, FUEL_BAR_WIDTH); siz.height = useDefined(cached.height, FUEL_BAR_HEIGHT); alpha *= cached.alpha; draw_surround = [info oo_boolForKey:DRAW_SURROUND_KEY defaultValue:NO]; if (draw_surround) { SET_COLOR_SURROUND(green_color); hudDrawSurroundAt(x, y, z1, siz); } fu = [PLAYER dialFuel]; hr = [PLAYER dialHyperRange]; // draw fuel bar SET_COLOR_MEDIUM(yellow_color); hudDrawBarAt(x, y, z1, siz, fu); // draw range indicator if (hr > 0.0f && hr <= 1.0f) { if ([PLAYER hasSufficientFuelForJump]) { SET_COLOR_HIGH(green_color); } else { SET_COLOR_LOW(red_color); } hudDrawMarkerAt(x, y, z1, siz, hr); } } - (void) drawWitchspaceDestination:(NSDictionary *)info { // A zero-distance jump counts as 0.1LY if ([PLAYER dialHyperRange] == 0.0f) { return; } int x, y; NSSize siz; GLfloat alpha = overallAlpha; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, WITCHDEST_CENTRE_X) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, WITCHDEST_CENTRE_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, WITCHDEST_WIDTH); siz.height = useDefined(cached.height, WITCHDEST_HEIGHT); alpha *= cached.alpha; SET_COLOR(green_color); if ([info oo_intForKey:@"align"] == 1) { OODrawStringAligned([UNIVERSE getSystemName:[PLAYER targetSystemID]], x, y, z1, siz, YES); } else { OODrawStringAligned([UNIVERSE getSystemName:[PLAYER targetSystemID]], x, y, z1, siz, NO); } } - (void) drawCabinTempBar:(NSDictionary *)info { int x, y; NSSize siz; BOOL draw_surround; GLfloat temp = [PLAYER hullHeatLevel]; GLfloat alpha = overallAlpha; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, CABIN_TEMP_BAR_CENTRE_X) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, CABIN_TEMP_BAR_CENTRE_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, CABIN_TEMP_BAR_WIDTH); siz.height = useDefined(cached.height, CABIN_TEMP_BAR_HEIGHT); alpha *= cached.alpha; draw_surround = [info oo_boolForKey:DRAW_SURROUND_KEY defaultValue:NO]; if (draw_surround) { SET_COLOR_SURROUND(green_color); hudDrawSurroundAt(x, y, z1, siz); } int flash = (int)([UNIVERSE getTime] * 4); flash &= 1; // what color are we? if (temp > .80) { if (temp > .90 && flash) SET_COLOR_CRITICAL(redplus_color); else SET_COLOR_HIGH(red_color); } else { if (temp > .25) SET_COLOR_MEDIUM(yellow_color); else SET_COLOR_LOW(green_color); } hudDrawBarAt(x, y, z1, siz, temp); } - (void) drawWeaponTempBar:(NSDictionary *)info { int x, y; NSSize siz; BOOL draw_surround; GLfloat temp = [PLAYER laserHeatLevel]; GLfloat alpha = overallAlpha; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, WEAPON_TEMP_BAR_CENTRE_X) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, WEAPON_TEMP_BAR_CENTRE_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, WEAPON_TEMP_BAR_WIDTH); siz.height = useDefined(cached.height, WEAPON_TEMP_BAR_HEIGHT); alpha *= cached.alpha; draw_surround = [info oo_boolForKey:DRAW_SURROUND_KEY defaultValue:NO]; if (draw_surround) { SET_COLOR_SURROUND(green_color); hudDrawSurroundAt(x, y, z1, siz); } // draw weapon_temp bar (only need to call GLColor() once!) if (temp > .80) SET_COLOR_HIGH(red_color); else if (temp > .25) SET_COLOR_MEDIUM(yellow_color); else SET_COLOR_LOW(green_color); hudDrawBarAt(x, y, z1, siz, temp); } - (void) drawAltitudeBar:(NSDictionary *)info { int x, y; NSSize siz; BOOL draw_surround; GLfloat alt = [PLAYER dialAltitude]; GLfloat alpha = overallAlpha; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, ALTITUDE_BAR_CENTRE_X) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, ALTITUDE_BAR_CENTRE_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, ALTITUDE_BAR_WIDTH); siz.height = useDefined(cached.height, ALTITUDE_BAR_HEIGHT); alpha *= cached.alpha; draw_surround = [info oo_boolForKey:DRAW_SURROUND_KEY defaultValue:NO]; if (draw_surround) { SET_COLOR_SURROUND(yellow_color); hudDrawSurroundAt(x, y, z1, siz); } int flash = (int)([UNIVERSE getTime] * 4); flash &= 1; // draw altitude bar (evaluating the least amount of ifs per go) if (alt < .25) { if (alt < .10 && flash) SET_COLOR_CRITICAL(redplus_color); else SET_COLOR_HIGH(red_color); } else { if (alt < .75) SET_COLOR_MEDIUM(yellow_color); else SET_COLOR_LOW(green_color); } hudDrawBarAt(x, y, z1, siz, alt); } static NSString * const kDefaultMissileIconKey = @"oolite-default-missile-icon"; static NSString * const kDefaultMineIconKey = @"oolite-default-mine-icon"; static const GLfloat kOutlineWidth = 0.5f; static OOPolygonSprite *IconForMissileRole(NSString *role) { static NSMutableDictionary *sIcons = nil; OOPolygonSprite *result = nil; result = [sIcons objectForKey:role]; if (result == nil) { NSString *key = role; NSArray *iconDef = [[UNIVERSE descriptions] oo_arrayForKey:key]; if (iconDef != nil) result = [[OOPolygonSprite alloc] initWithDataArray:iconDef outlineWidth:kOutlineWidth name:key]; if (result == nil) // No custom icon or bad data { /* Backwards compatibility note: The old implementation used suffixes "MISSILE" and "MINE" (without the underscore), and didn't draw anything if neither was found. I believe any difference in practical behavour due to the change here will be positive. -- Ahruman 2009-10-09 */ if ([role hasSuffix:@"_MISSILE"]) key = kDefaultMissileIconKey; else key = kDefaultMineIconKey; iconDef = [[UNIVERSE descriptions] oo_arrayForKey:key]; result = [[OOPolygonSprite alloc] initWithDataArray:iconDef outlineWidth:kOutlineWidth name:key]; } if (result != nil) { if (sIcons == nil) sIcons = [[NSMutableDictionary alloc] init]; [sIcons setObject:result forKey:role]; [result release]; // Balance alloc } } return result; } - (void) drawIconForMissile:(ShipEntity *)missile selected:(BOOL)selected status:(OOMissileStatus)status x:(int)x y:(int)y width:(GLfloat)width height:(GLfloat)height alpha:(GLfloat)alpha { OOPolygonSprite *sprite = IconForMissileRole([missile primaryRole]); if (selected) { // Draw yellow outline. OOGLPushModelView(); OOGLTranslateModelView(make_vector(x - width * 2.0f, y - height * 2.0f, z1)); OOGLScaleModelView(make_vector(width, height, 1.0f)); GLColorWithOverallAlpha(yellow_color, alpha); [sprite drawOutline]; OOGLPopModelView(); // Draw black backing, so outline colour isn’t blended into missile colour. OOGLPushModelView(); OOGLTranslateModelView(make_vector(x - width * 2.0f, y - height * 2.0f, z1)); OOGLScaleModelView(make_vector(width, height, 1.0f)); GLColorWithOverallAlpha(black_color, alpha); [sprite drawFilled]; OOGLPopModelView(); switch (status) { case MISSILE_STATUS_SAFE: GLColorWithOverallAlpha(green_color, alpha); break; case MISSILE_STATUS_ARMED: GLColorWithOverallAlpha(yellow_color, alpha); break; case MISSILE_STATUS_TARGET_LOCKED: GLColorWithOverallAlpha(red_color, alpha); break; } } else { if ([missile primaryTarget] == nil) GLColorWithOverallAlpha(green_color, alpha); else GLColorWithOverallAlpha(red_color, alpha); } OOGLPushModelView(); OOGLTranslateModelView(make_vector(x - width * 2.0f, y - height * 2.0f, z1)); OOGLScaleModelView(make_vector(width, height, 1.0f)); [sprite drawFilled]; OOGLPopModelView(); } - (void) drawIconForEmptyPylonAtX:(int)x y:(int)y width:(GLfloat)width height:(GLfloat)height alpha:(GLfloat)alpha { OOPolygonSprite *sprite = IconForMissileRole(kDefaultMissileIconKey); // Draw gray outline. OOGLPushModelView(); OOGLTranslateModelView(make_vector(x - width * 2.0f, y - height * 2.0f, z1)); OOGLScaleModelView(make_vector(width, height, 1.0f)); GLColorWithOverallAlpha(lightgray_color, alpha); [sprite drawOutline]; OOGLPopModelView(); } - (void) drawMissileDisplay:(NSDictionary *)info { int x, y, sp; NSSize siz; GLfloat alpha = overallAlpha; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, MISSILES_DISPLAY_X) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, MISSILES_DISPLAY_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, MISSILE_ICON_WIDTH); siz.height = useDefined(cached.height, MISSILE_ICON_HEIGHT); alpha *= cached.alpha; sp = [info oo_unsignedIntForKey:SPACING_KEY defaultValue:MISSILES_DISPLAY_SPACING]; BOOL weaponsOnline = [PLAYER weaponsOnline]; if (!weaponsOnline) alpha *= 0.2f; // darken missile display if weapons are offline if (![PLAYER dialIdentEngaged]) { OOMissileStatus status = [PLAYER dialMissileStatus]; NSUInteger i, n_mis = [PLAYER dialMaxMissiles]; for (i = 0; i < n_mis; i++) { ShipEntity *missile = [PLAYER missileForPylon:i]; if (missile) { [self drawIconForMissile:missile selected:weaponsOnline && i == [PLAYER activeMissile] status:status x:x + (int)i * sp + 2 y:y width:siz.width * 0.25f height:siz.height * 0.25f alpha:alpha]; } else { [self drawIconForEmptyPylonAtX:x + (int)i * sp + 2 y:y width:siz.width * 0.25f height:siz.height * 0.25f alpha:alpha]; } } } else { x -= siz.width; y -= siz.height * 0.75; siz.width *= 0.80; sp *= 0.75; switch ([PLAYER dialMissileStatus]) { case MISSILE_STATUS_SAFE: GLColorWithOverallAlpha(green_color, alpha); break; case MISSILE_STATUS_ARMED: GLColorWithOverallAlpha(yellow_color, alpha); break; case MISSILE_STATUS_TARGET_LOCKED: GLColorWithOverallAlpha(red_color, alpha); break; } OOGLBEGIN(GL_QUADS); glVertex3i(x , y, z1); glVertex3i(x + siz.width, y, z1); glVertex3i(x + siz.width, y + siz.height, z1); glVertex3i(x , y + siz.height, z1); OOGLEND(); GLColorWithOverallAlpha(green_color, alpha); OODrawString([PLAYER dialTargetName], x + sp, y - 1, z1, NSMakeSize(siz.width, siz.height)); } } - (void) drawTargetReticle:(NSDictionary *)info { GLfloat alpha = [info oo_nonNegativeFloatForKey:ALPHA_KEY defaultValue:1.0f] * overallAlpha; if ([PLAYER primaryTarget] != nil) { hudDrawReticleOnTarget([PLAYER primaryTarget], PLAYER, z1, alpha, reticleTargetSensitive, propertiesReticleTargetSensitive, NO, YES, info); [self drawDirectionCue:info]; } // extra feature if extra equipment installed if ([PLAYER hasEquipmentItemProviding:@"EQ_INTEGRATED_TARGETING_SYSTEM"]) { [self drawSecondaryTargetReticle:info]; } } - (void) drawSecondaryTargetReticle:(NSDictionary *)info { GLfloat alpha = [info oo_nonNegativeFloatForKey:ALPHA_KEY defaultValue:1.0f] * overallAlpha * 0.4; PlayerEntity *player = PLAYER; if ([player hasEquipmentItemProviding:@"EQ_TARGET_MEMORY"]) { // needs target memory to be working in addition to any other equipment // this item may be bound to NSMutableArray *targetMemory = [player targetMemory]; ShipEntity *primary = [player primaryTarget]; for (unsigned i = 0; i < PLAYER_TARGET_MEMORY_SIZE; i++) { id sec_id = [targetMemory objectAtIndex:i]; // isProxy = weakref ; not = NSNull (in this case...) // can't use isKindOfClass because that throws // NSInvalidArgumentException when called on a weakref // with a dropped object. // TODO: fix OOWeakReference so isKindOfClass works if (sec_id != nil && [sec_id isProxy]) { ShipEntity *secondary = [(OOWeakReference *)sec_id weakRefUnderlyingObject]; if (secondary != nil && secondary != primary) { if ([secondary zeroDistance] <= SCANNER_MAX_RANGE2 && [secondary isInSpace]) { hudDrawReticleOnTarget(secondary, PLAYER, z1, alpha, NO, nil, YES, NO, info); } } } } } } - (void) drawWaypoints:(NSDictionary *)info { GLfloat alpha = [info oo_nonNegativeFloatForKey:ALPHA_KEY defaultValue:1.0f] * overallAlpha; GLfloat scale = [info oo_floatForKey:@"reticle_scale" defaultValue:ONE_SIXTYFOURTH]; NSEnumerator *waypoints = [[UNIVERSE currentWaypoints] objectEnumerator]; OOWaypointEntity *waypoint = nil; Entity *compass = [PLAYER compassTarget]; while ((waypoint = [waypoints nextObject])) { hudDrawWaypoint(waypoint, PLAYER, z1, alpha, waypoint==compass, scale); } } - (void) drawStatusLight:(NSDictionary *)info { int x, y; NSSize siz; GLfloat alpha = overallAlpha; BOOL blueAlert = cloakIndicatorOnStatusLight && [PLAYER isCloaked]; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, STATUS_LIGHT_CENTRE_X) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, STATUS_LIGHT_CENTRE_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, STATUS_LIGHT_HEIGHT); siz.height = useDefined(cached.height, STATUS_LIGHT_HEIGHT); alpha *= cached.alpha; GLfloat status_color[4] = { 0.25, 0.25, 0.25, 1.0}; int alertCondition = [PLAYER alertCondition]; GLfloat flash_alpha = 0.333 * (2.0f + sin((GLfloat)[UNIVERSE getTime] * 2.5f * alertCondition)); switch(alertCondition) { case ALERT_CONDITION_RED: status_color[0] = red_color[0]; status_color[1] = red_color[1]; status_color[2] = blueAlert ? blue_color[2] : red_color[2]; break; case ALERT_CONDITION_GREEN: status_color[0] = green_color[0]; status_color[1] = green_color[1]; status_color[2] = blueAlert ? blue_color[2] : green_color[2]; break; case ALERT_CONDITION_YELLOW: status_color[0] = yellow_color[0]; status_color[1] = yellow_color[1]; status_color[2] = blueAlert ? blue_color[2] : yellow_color[2]; break; default: case ALERT_CONDITION_DOCKED: break; } status_color[3] = flash_alpha; GLColorWithOverallAlpha(status_color, alpha); OOGLBEGIN(GL_POLYGON); hudDrawStatusIconAt(x, y, z1, siz); OOGLEND(); OOGL(glColor4f(0.25, 0.25, 0.25, alpha)); OOGLBEGIN(GL_LINE_LOOP); hudDrawStatusIconAt(x, y, z1, siz); OOGLEND(); } - (void) drawDirectionCue:(NSDictionary *)info { GLfloat alpha = overallAlpha; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; alpha *= cached.alpha; if ([UNIVERSE displayGUI]) return; GLfloat clear_color[4] = {0.0f, 1.0f, 0.0f, 0.0f}; Entity *target = [PLAYER primaryTarget]; if (target == nil) return; // draw the direction cue OOMatrix rotMatrix; rotMatrix = [PLAYER rotationMatrix]; if ([UNIVERSE viewDirection] != VIEW_GUI_DISPLAY) { const GLfloat innerSize = CROSSHAIR_SIZE; const GLfloat width = CROSSHAIR_SIZE * ONE_EIGHTH; const GLfloat outerSize = CROSSHAIR_SIZE * (1.0f + ONE_EIGHTH + ONE_EIGHTH); const float visMin = 0.994521895368273f; // cos(6 degrees) const float visMax = 0.984807753012208f; // cos(10 degrees) // Transform the view Vector rpn = [PLAYER vectorTo:target]; rpn = OOVectorMultiplyMatrix(rpn, rotMatrix); Vector drawPos = rpn; Vector forward = kZeroVector; switch ([UNIVERSE viewDirection]) { case VIEW_FORWARD: forward = kBasisZVector; break; case VIEW_AFT: drawPos.x = - drawPos.x; forward = vector_flip(kBasisZVector); break; case VIEW_PORT: drawPos.x = drawPos.z; forward = vector_flip(kBasisXVector); break; case VIEW_STARBOARD: drawPos.x = -drawPos.z; forward = kBasisXVector; break; case VIEW_CUSTOM: return; default: break; } float cosAngle = dot_product(vector_normal(rpn), forward); float visibility = 1.0f - ((visMax - cosAngle) * (1.0f / (visMax - visMin))); alpha *= OOClamp_0_1_f(visibility); if (alpha > 0.0f) { drawPos.z = 0.0f; // flatten vector drawPos = vector_normal(drawPos); OOGLBEGIN(GL_LINE_STRIP); glColor4fv(clear_color); glVertex3f(drawPos.x * innerSize - drawPos.y * width, drawPos.y * innerSize + drawPos.x * width, z1); GLColorWithOverallAlpha(green_color, alpha); glVertex3f(drawPos.x * outerSize, drawPos.y * outerSize, z1); glColor4fv(clear_color); glVertex3f(drawPos.x * innerSize + drawPos.y * width, drawPos.y * innerSize - drawPos.x * width, z1); OOGLEND(); } } } - (void) drawClock:(NSDictionary *)info { int x, y; NSSize siz; GLfloat itemColor[4] = { 0.0f, 1.0f, 0.0f, 1.0f }; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, CLOCK_DISPLAY_X) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, CLOCK_DISPLAY_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, CLOCK_DISPLAY_WIDTH); siz.height = useDefined(cached.height, CLOCK_DISPLAY_HEIGHT); GetRGBAArrayFromInfo(info, itemColor); itemColor[3] *= overallAlpha; OOGL(glColor4f(itemColor[0], itemColor[1], itemColor[2], itemColor[3])); OODrawString([PLAYER dial_clock], x, y, z1, siz); } - (void) drawPrimedEquipment:(NSDictionary *)info { if ([PLAYER status] == STATUS_DOCKED) { // Can't activate equipment while docked return; } GLfloat itemColor[4] = { 0.0f, 1.0f, 0.0f, 1.0f }; struct CachedInfo cached; NSUInteger lines = [info oo_intForKey:@"n_bars" defaultValue:1]; NSInteger pec = (NSInteger)[PLAYER primedEquipmentCount]; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; NSInteger x = useDefined(cached.x, PRIMED_DISPLAY_X) + [[UNIVERSE gameView] x_offset] * cached.x0; NSInteger y = useDefined(cached.y, PRIMED_DISPLAY_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; NSSize size = { .width = useDefined(cached.width, PRIMED_DISPLAY_WIDTH), .height = useDefined(cached.height, PRIMED_DISPLAY_HEIGHT) }; if (pec == 0) { // Don't display if no primed equipment fitted return; } GetRGBAArrayFromInfo(info, itemColor); itemColor[3] *= overallAlpha; if (lines == 1) { OOGL(glColor4f(itemColor[0], itemColor[1], itemColor[2], itemColor[3])); NSString *equipmentName = [PLAYER primedEquipmentName:0]; OODrawString(OOExpandKey(@"equipment-primed-hud", equipmentName), x, y, z1, size); } else { NSInteger negative = (lines - 1) / 2; NSInteger positive = lines / 2; for (NSInteger i = -negative; i <= positive; i++) { if (i >= -(pec) / 2 && i <= (pec + 1) / 2) { // don't display loops if we have more equipment than lines // instead compact the display towards its centre GLfloat alphaScale = 1.0/((i<0)?(1.0-i):(1.0+i)); OOGL(glColor4f(itemColor[0], itemColor[1], itemColor[2], itemColor[3]*alphaScale)); OODrawString([PLAYER primedEquipmentName:i], x, y, z1, size); } y -= size.height; } } } - (void) drawASCTarget:(NSDictionary *)info { if (!([self checkPlayerInSystemFlight] && [PLAYER status] != STATUS_LAUNCHING)) // normal system { // Can't have compass target when docked, etc. (matches blip condition) return; } GLfloat itemColor[4] = { 0.0f, 0.0f, 1.0f, 1.0f }; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; NSInteger x = useDefined(cached.x, ASCTARGET_DISPLAY_X) + [[UNIVERSE gameView] x_offset] * cached.x0; NSInteger y = useDefined(cached.y, ASCTARGET_DISPLAY_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; NSSize size = { .width = useDefined(cached.width, ASCTARGET_DISPLAY_WIDTH), .height = useDefined(cached.height, ASCTARGET_DISPLAY_HEIGHT) }; GetRGBAArrayFromInfo(info, itemColor); itemColor[3] *= overallAlpha; OOGL(glColor4f(itemColor[0], itemColor[1], itemColor[2], itemColor[3])); if ([info oo_intForKey:@"align"] == 1) { OODrawStringAligned([PLAYER compassTargetLabel], x, y, z1, size,YES); } else { OODrawStringAligned([PLAYER compassTargetLabel], x, y, z1, size,NO); } } - (void) drawWeaponsOfflineText:(NSDictionary *)info { OOViewID viewID = [UNIVERSE viewDirection]; if (viewID == VIEW_CUSTOM || overallAlpha == 0.0f || !([PLAYER status] == STATUS_IN_FLIGHT || [PLAYER status] == STATUS_WITCHSPACE_COUNTDOWN) || [UNIVERSE displayGUI] ) { // Don't draw weapons offline text return; } if (![PLAYER weaponsOnline]) { int x, y; NSSize siz; GLfloat alpha = overallAlpha; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, WEAPONSOFFLINETEXT_DISPLAY_X) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, WEAPONSOFFLINETEXT_DISPLAY_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, WEAPONSOFFLINETEXT_WIDTH); siz.height = useDefined(cached.height, WEAPONSOFFLINETEXT_HEIGHT); alpha *= cached.alpha; GLColorWithOverallAlpha(green_color, alpha); // TODO: some caching required... OODrawString(DESC(@"weapons-systems-offline"), x, y, z1, siz); } } - (void) drawFPSInfoCounter:(NSDictionary *)info { if (![UNIVERSE displayFPS]) return; int x, y; NSSize siz; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, FPSINFO_DISPLAY_X) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, FPSINFO_DISPLAY_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, FPSINFO_DISPLAY_WIDTH); siz.height = useDefined(cached.height, FPSINFO_DISPLAY_HEIGHT); HPVector playerPos = [PLAYER position]; NSString *positionInfo = [UNIVERSE expressPosition:playerPos inCoordinateSystem:@"pwm"]; positionInfo = [NSString stringWithFormat:@"abs %.2f %.2f %.2f / %@", playerPos.x, playerPos.y, playerPos.z, positionInfo]; // We would normally set a variable alpha value here, but in this case we don't. // We prefer the FPS counter to be always visible - Nikos 20100405 OOGL(glColor4f(0.0, 1.0, 0.0, 1.0)); OODrawString([PLAYER dial_fpsinfo], x, y, z1, siz); #ifndef NDEBUG NSSize siz08 = NSMakeSize(0.8 * siz.width, 0.8 * siz.width); NSString *collDebugInfo = [NSString stringWithFormat:@"%@ - %@", [PLAYER dial_objinfo], [UNIVERSE collisionDescription]]; OODrawString(collDebugInfo, x, y - siz.height, z1, siz); OODrawString(positionInfo, x, y - 1.8 * siz.height, z1, siz08); NSString *timeAccelerationFactorInfo = [NSString stringWithFormat:@"TAF: %@%.2f", DESC(@"multiplication-sign"), [UNIVERSE timeAccelerationFactor]]; OODrawString(timeAccelerationFactorInfo, x, y - 3.2 * siz08.height, z1, siz08); #endif } - (void) drawScoopStatus:(NSDictionary *)info { int i, x, y; NSSize siz; GLfloat alpha; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, SCOOPSTATUS_CENTRE_X) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, SCOOPSTATUS_CENTRE_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, SCOOPSTATUS_WIDTH); siz.height = useDefined(cached.height, SCOOPSTATUS_HEIGHT); // default alpha value different from all others, won't use cached.alpha alpha = [info oo_nonNegativeFloatForKey:ALPHA_KEY defaultValue:0.75f]; const GLfloat* s0_color = red_color; GLfloat s1c[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; GLfloat s2c[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; GLfloat s3c[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; int scoop_status = [PLAYER dialFuelScoopStatus]; GLfloat t = [UNIVERSE getTime]; GLfloat a1 = alpha * 0.5f * (1.0f + sin(t * 8.0f)); GLfloat a2 = alpha * 0.5f * (1.0f + sin(t * 8.0f - 1.0f)); GLfloat a3 = alpha * 0.5f * (1.0f + sin(t * 8.0f - 2.0f)); switch (scoop_status) { case SCOOP_STATUS_NOT_INSTALLED: return; // don't draw case SCOOP_STATUS_FULL_HOLD: s0_color = darkgreen_color; alpha *= 0.75; break; case SCOOP_STATUS_ACTIVE: case SCOOP_STATUS_OKAY: s0_color = green_color; break; } for (i = 0; i < 3; i++) { s1c[i] = s0_color[i]; s2c[i] = s0_color[i]; s3c[i] = s0_color[i]; } if (scoop_status == SCOOP_STATUS_FULL_HOLD) { s3c[0] = red_color[0]; s3c[1] = red_color[1]; s3c[2] = red_color[2]; } if (scoop_status == SCOOP_STATUS_ACTIVE) { s1c[3] = alpha * a1; s2c[3] = alpha * a2; s3c[3] = alpha * a3; } else { s1c[3] = alpha; s2c[3] = alpha; s3c[3] = alpha; } GLfloat w1 = siz.width / 8.0; GLfloat w2 = 2.0 * w1; // GLfloat w3 = 3.0 * w1; GLfloat w4 = 4.0 * w1; GLfloat h1 = siz.height / 8.0; GLfloat h2 = 2.0 * h1; GLfloat h3 = 3.0 * h1; GLfloat h4 = 4.0 * h1; OOGL(glDisable(GL_TEXTURE_2D)); OOGLBEGIN(GL_QUADS); // section 1 GLColorWithOverallAlpha(s1c, overallAlpha); glVertex3f(x, y + h1, z1); glVertex3f(x - w2, y + h2, z1); glVertex3f(x, y + h3, z1); glVertex3f(x + w2, y + h2, z1); // section 2 GLColorWithOverallAlpha(s2c, overallAlpha); glVertex3f(x, y - h1, z1); glVertex3f(x - w4, y + h1, z1); glVertex3f(x - w4, y + h2, z1); glVertex3f(x, y, z1); glVertex3f(x, y - h1, z1); glVertex3f(x + w4, y + h1, z1); glVertex3f(x + w4, y + h2, z1); glVertex3f(x, y, z1); // section 3 GLColorWithOverallAlpha(s3c, overallAlpha); glVertex3f(x, y - h4, z1); glVertex3f(x - w2, y - h2, z1); glVertex3f(x - w2, y - h1, z1); glVertex3f(x, y - h2, z1); glVertex3f(x, y - h4, z1); glVertex3f(x + w2, y - h2, z1); glVertex3f(x + w2, y - h1, z1); glVertex3f(x, y - h2, z1); OOGLEND(); } - (void) drawStickSensitivityIndicator:(NSDictionary *)info { GLfloat x, y; NSSize siz; GLfloat alpha = overallAlpha; BOOL mouse = [PLAYER isMouseControlOn]; OOJoystickManager *stickHandler = [OOJoystickManager sharedStickHandler]; struct CachedInfo cached; if (![stickHandler joystickCount]) { return; // no need to draw if no joystick fitted } [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = useDefined(cached.x, STATUS_LIGHT_CENTRE_X) + [[UNIVERSE gameView] x_offset] * cached.x0; y = useDefined(cached.y, STATUS_LIGHT_CENTRE_Y) + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, STATUS_LIGHT_HEIGHT); siz.height = useDefined(cached.height, STATUS_LIGHT_HEIGHT); alpha *= cached.alpha; GLfloat div = [stickHandler getSensitivity]; GLColorWithOverallAlpha(black_color, alpha / 4); GLDrawFilledOval(x, y, z1, siz, 10); GLColorWithOverallAlpha((div < 1.0 || mouse) ? lightgray_color : green_color, alpha); OOGL(GLScaledLineWidth(_crosshairWidth * lineWidth)); if (div >= 1.0) { if (!mouse) { NSSize siz8th = { siz.width / 8, siz.height / 8 }; GLDrawFilledOval(x, y, z1, siz8th, 30); if (div == 1.0) // normal mode GLColorWithOverallAlpha(lightgray_color, alpha); } siz.width -= _crosshairWidth * lineWidth / 2; siz.height -= _crosshairWidth * lineWidth / 2; GLDrawOval(x, y, z1, siz, 10); } else if (div < 1.0) // insensitive mode (shouldn't happen) GLDrawFilledOval(x, y, z1, siz, 10); OOGL(GLScaledLineWidth(lineWidth)); // reset } - (void) drawSurroundInternal:(NSDictionary *)info color:(const GLfloat[4])color { NSInteger x, y; NSSize siz; GLfloat alpha = overallAlpha; struct CachedInfo cached; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; if (cached.x == NOT_DEFINED || cached.y == NOT_DEFINED || cached.width == NOT_DEFINED || cached.height == NOT_DEFINED) { return; } x = cached.x + [[UNIVERSE gameView] x_offset] * cached.x0; y = cached.y + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width, WEAPONSOFFLINETEXT_WIDTH); siz.height = useDefined(cached.height, WEAPONSOFFLINETEXT_HEIGHT); alpha *= cached.alpha; // draw the surround GLColorWithOverallAlpha(color, alpha); hudDrawSurroundAt(x, y, z1, siz); } - (void) drawSurround:(NSDictionary *)info { GLfloat itemColor[4] = { 0.0f, 1.0f, 0.0f, 1.0f }; id colorDesc = [info objectForKey:COLOR_KEY]; if (colorDesc != nil) { OOColor *color = [OOColor colorWithDescription:colorDesc]; if (color != nil) { itemColor[0] = [color redComponent]; itemColor[1] = [color greenComponent]; itemColor[2] = [color blueComponent]; } } [self drawSurroundInternal:info color:itemColor]; } - (void) drawGreenSurround:(NSDictionary *)info { [self drawSurroundInternal:info color:green_color]; } - (void) drawYellowSurround:(NSDictionary *)info { [self drawSurroundInternal:info color:yellow_color]; } - (void) drawTrumbles:(NSDictionary *)info { OOTrumble** trumbles = [PLAYER trumbleArray]; NSUInteger i; for (i = [PLAYER trumbleCount]; i > 0; i--) { OOTrumble* trum = trumbles[i - 1]; [trum drawTrumble: z1]; } } - (void) drawMultiFunctionDisplay:(NSDictionary *)info withText:(NSString *)text asIndex:(NSUInteger)index { PlayerEntity *player1 = PLAYER; struct CachedInfo cached; NSInteger i, x, y; NSSize siz, tmpsiz; if ([player1 guiScreen] != GUI_SCREEN_MAIN) // don't draw on text screens { return; } GLfloat alpha = [info oo_nonNegativeFloatForKey:ALPHA_KEY defaultValue:1.0f] * overallAlpha; GLfloat mfd_color[4] = {0.0, 1.0, 0.0, 0.9*alpha}; OOColor *mfdcol = [OOColor colorWithDescription:[info objectForKey:COLOR_KEY]]; if (mfdcol != nil) { [mfdcol getRed:&mfd_color[0] green:&mfd_color[1] blue:&mfd_color[2] alpha:&mfd_color[3]]; } if (index != [player1 activeMFD]) { mfd_color[3] *= 0.75; } [self drawSurroundInternal:info color:mfd_color]; [(NSValue *)[sCurrentDrawItem objectAtIndex:WIDGET_CACHE] getValue:&cached]; x = cached.x + [[UNIVERSE gameView] x_offset] * cached.x0; y = cached.y + [[UNIVERSE gameView] y_offset] * cached.y0; siz.width = useDefined(cached.width / 15, MFD_TEXT_WIDTH); siz.height = useDefined(cached.height / 10, MFD_TEXT_HEIGHT); GLfloat x0 = (GLfloat)(x - cached.width/2); GLfloat y0 = (GLfloat)(y + cached.height/2); GLfloat x1 = (GLfloat)(x + cached.width/2); GLfloat y1 = (GLfloat)(y - cached.height/2); GLColorWithOverallAlpha(mfd_color, alpha*0.3); OOGLBEGIN(GL_QUADS); glVertex3f(x0-2,y0+2,z1); glVertex3f(x0-2,y1-2,z1); glVertex3f(x1+2,y1-2,z1); glVertex3f(x1+2,y0+2,z1); OOGLEND(); NSString *line = nil; NSArray *lines = [text componentsSeparatedByString:@"\n"]; // text at full opacity GLColorWithOverallAlpha(mfd_color, alpha); for (i = 0; i < 10 ; i++) { line = [lines oo_stringAtIndex:i defaultValue:nil]; if (line != nil) { y0 -= siz.height; // all lines should be shorter than the size of the MFD GLfloat textwidth = OORectFromString(line, 0.0f, 0.0f, siz).size.width; if (textwidth <= cached.width) { OODrawString(line, x0, y0, z1, siz); } else { // compress it so it fits tmpsiz.height = siz.height; tmpsiz.width = siz.width * cached.width / textwidth; OODrawString(line, x0, y0, z1, tmpsiz); } } else { break; } } } //---------------------------------------------------------------------// static void hudDrawIndicatorAt(GLfloat x, GLfloat y, GLfloat z, NSSize siz, GLfloat amount) { if (siz.width > siz.height) { GLfloat dial_oy = y - siz.height/2; GLfloat position = x + amount * siz.width / 2; OOGLBEGIN(GL_QUADS); glVertex3f(position, dial_oy, z); glVertex3f(position+2, y, z); glVertex3f(position, dial_oy+siz.height, z); glVertex3f(position-2, y, z); OOGLEND(); } else { GLfloat dial_ox = x - siz.width/2; GLfloat position = y + amount * siz.height / 2; OOGLBEGIN(GL_QUADS); glVertex3f(dial_ox, position, z); glVertex3f(x, position+2, z); glVertex3f(dial_ox + siz.width, position, z); glVertex3f(x, position-2, z); OOGLEND(); } } static void hudDrawMarkerAt(GLfloat x, GLfloat y, GLfloat z, NSSize siz, GLfloat amount) { if (siz.width > siz.height) { GLfloat dial_oy = y - siz.height/2; GLfloat position = x + amount * siz.width - siz.width/2; OOGLBEGIN(GL_QUADS); glVertex3f(position+1, dial_oy+1, z); glVertex3f(position+1, dial_oy+siz.height-1, z); glVertex3f(position-1, dial_oy+siz.height-1, z); glVertex3f(position-1, dial_oy+1, z); OOGLEND(); } else { GLfloat dial_ox = x - siz.width/2; GLfloat position = y + amount * siz.height - siz.height/2; OOGLBEGIN(GL_QUADS); glVertex3f(dial_ox+1, position+1, z); glVertex3f(dial_ox + siz.width-1, position+1, z); glVertex3f(dial_ox + siz.width-1, position-1, z); glVertex3f(dial_ox+1, position-1, z); OOGLEND(); } } static void hudDrawBarAt(GLfloat x, GLfloat y, GLfloat z, NSSize siz, GLfloat amount) { GLfloat dial_ox = x - siz.width/2; GLfloat dial_oy = y - siz.height/2; if (fabs(siz.width) > fabs(siz.height)) { GLfloat position = dial_ox + amount * siz.width; OOGLBEGIN(GL_QUADS); glVertex3f(dial_ox, dial_oy, z); glVertex3f(position, dial_oy, z); glVertex3f(position, dial_oy+siz.height, z); glVertex3f(dial_ox, dial_oy+siz.height, z); OOGLEND(); } else { GLfloat position = dial_oy + amount * siz.height; OOGLBEGIN(GL_QUADS); glVertex3f(dial_ox, dial_oy, z); glVertex3f(dial_ox, position, z); glVertex3f(dial_ox+siz.width, position, z); glVertex3f(dial_ox+siz.width, dial_oy, z); OOGLEND(); } } static void hudDrawSurroundAt(GLfloat x, GLfloat y, GLfloat z, NSSize siz) { GLfloat dial_ox = x - siz.width/2; GLfloat dial_oy = y - siz.height/2; OOGLBEGIN(GL_LINE_LOOP); glVertex3f(dial_ox-2, dial_oy-2, z); glVertex3f(dial_ox+siz.width+2, dial_oy-2, z); glVertex3f(dial_ox+siz.width+2, dial_oy+siz.height+2, z); glVertex3f(dial_ox-2, dial_oy+siz.height+2, z); OOGLEND(); } static void hudDrawStatusIconAt(int x, int y, int z, NSSize siz) { int ox = x - siz.width / 2.0; int oy = y - siz.height / 2.0; int w = siz.width / 4.0; int h = siz.height / 4.0; glVertex3i(ox, oy + h, z); glVertex3i(ox, oy + 3 * h, z); glVertex3i(ox + w, oy + 4 * h, z); glVertex3i(ox + 3 * w, oy + 4 * h, z); glVertex3i(ox + 4 * w, oy + 3 * h, z); glVertex3i(ox + 4 * w, oy + h, z); glVertex3i(ox + 3 * w, oy, z); glVertex3i(ox + w, oy, z); } static void hudDrawReticleOnTarget(Entity *target, PlayerEntity *player1, GLfloat z1, GLfloat alpha, BOOL reticleTargetSensitive, NSMutableDictionary *propertiesReticleTargetSensitive, BOOL colourFromScannerColour, BOOL showText, NSDictionary *info) { if (target == nil || player1 == nil) { return; } if ([player1 guiScreen] != GUI_SCREEN_MAIN) // don't draw on text screens { return; } ShipEntity *target_ship = nil; NSString *legal_desc = nil; GLfloat scale = [info oo_floatForKey:@"reticle_scale" defaultValue:ONE_SIXTYFOURTH]; if ([target isShip]) { target_ship = (ShipEntity *)target; legal_desc = [target_ship scanDescription]; } if ([target_ship isCloaked]) return; Vector p1; // by definition close enough that single precision is fine p1 = HPVectorToVector(HPvector_subtract([target position], [player1 viewpointPosition])); GLfloat rdist = magnitude(p1); GLfloat rsize = [target collisionRadius] / (2 * [[UNIVERSE gameView] fov:YES]); // FIXME integrate 2 into fov to remove magic number if (rsize < rdist * scale) rsize = rdist * scale; GLfloat rs0 = rsize; GLfloat rs2 = rsize * 0.50; OOGLPushModelView(); hudRotateViewpointForVirtualDepth(player1,p1); // draw the reticle float range = sqrt(target->zero_distance) - target->collision_radius; int flash = (int)([UNIVERSE getTime] * 4); flash &= 1; // Draw reticle cyan for Wormholes if ([target isWormhole]) { GLColorWithOverallAlpha(cyan_color, alpha); } else { // Reticle sensitivity accuracy calculation BOOL isTargeted = NO; GLfloat probabilityAccuracy; if (propertiesReticleTargetSensitive != nil) { // Only if target is within player's weapon range, we mind for reticle accuracy if (range < [player1 weaponRange]) { // After MAX_ACCURACY_RANGE km start decreasing high accuracy probability by ACCURACY_PROBABILITY_DECREASE_FACTOR% if (range > MAX_ACCURACY_RANGE) { // Every one second re-evaluate accuracy if ([UNIVERSE getTime] > [propertiesReticleTargetSensitive oo_doubleForKey:@"timeLastAccuracyProbabilityCalculation"] + 1) { probabilityAccuracy = 1-(range-MAX_ACCURACY_RANGE)*ACCURACY_PROBABILITY_DECREASE_FACTOR; // Make sure probability does not go below a minimum probabilityAccuracy = probabilityAccuracy < MIN_PROBABILITY_ACCURACY ? MIN_PROBABILITY_ACCURACY : probabilityAccuracy; [propertiesReticleTargetSensitive setObject:[NSNumber numberWithBool:((randf() < probabilityAccuracy) ? YES : NO)] forKey:@"isAccurate"]; // Store the time the last accuracy probability has been performed [propertiesReticleTargetSensitive setObject:[NSNumber numberWithDouble:[UNIVERSE getTime]] forKey:@"timeLastAccuracyProbabilityCalculation"]; } if ([propertiesReticleTargetSensitive oo_boolForKey:@"isAccurate"]) { // high accuracy reticle isTargeted = ([UNIVERSE firstEntityTargetedByPlayerPrecisely] == target); } else { // low accuracy reticle isTargeted = ([UNIVERSE firstEntityTargetedByPlayer] == target); } } else { // high accuracy reticle isTargeted = ([UNIVERSE firstEntityTargetedByPlayerPrecisely] == target); } } } // If reticle is target sensitive, draw target box in red // when target passes through laser hit-point(with decreasing accuracy) // and is within hit-range. // // NOTE: The following condition also considers (indirectly) the player's weapon range. // 'isTargeted' is initialised to FALSE. Only if target is within the player's weapon range, // it might change value. Therefore, it is not necessary to add '&& range < [player1 weaponRange]' // to the following condition. if (colourFromScannerColour) { if ([target isShip]) { ShipEntity *ship = (ShipEntity *)target; BOOL isHostile = (([ship hasHostileTarget])&&([ship primaryTarget] == PLAYER)); GLColorWithOverallAlpha([ship scannerDisplayColorForShip:PLAYER :isHostile :flash :[ship scannerDisplayColor1] :[ship scannerDisplayColor2] :[ship scannerDisplayColorHostile1] :[ship scannerDisplayColorHostile2]],alpha); } else if ([target isVisualEffect]) { OOVisualEffectEntity *vis = (OOVisualEffectEntity *)target; GLColorWithOverallAlpha([vis scannerDisplayColorForShip:flash :[vis scannerDisplayColor1] :[vis scannerDisplayColor2]],alpha); } else { GLColorWithOverallAlpha(green_color, alpha); } } else { if (reticleTargetSensitive && isTargeted) { GLColorWithOverallAlpha(red_color, alpha); } else { GLColorWithOverallAlpha(green_color, alpha); } } } OOGLBEGIN(GL_LINES); glVertex2f(rs0,rs2); glVertex2f(rs0,rs0); glVertex2f(rs0,rs0); glVertex2f(rs2,rs0); glVertex2f(rs0,-rs2); glVertex2f(rs0,-rs0); glVertex2f(rs0,-rs0); glVertex2f(rs2,-rs0); glVertex2f(-rs0,rs2); glVertex2f(-rs0,rs0); glVertex2f(-rs0,rs0); glVertex2f(-rs2,rs0); glVertex2f(-rs0,-rs2); glVertex2f(-rs0,-rs0); glVertex2f(-rs0,-rs0); glVertex2f(-rs2,-rs0); OOGLEND(); if (showText) { // add text for reticle here range *= 0.001f; if (range < 0.001f) range = 0.0f; // avoids the occasional -0.001 km distance. NSSize textsize = NSMakeSize(rdist * scale, rdist * scale); float line_height = rdist * scale; NSString* infoline = [NSString stringWithFormat:@"%0.3f km", range]; if (legal_desc != nil) infoline = [NSString stringWithFormat:@"%@ (%@)", infoline, legal_desc]; // no need to set colour here OODrawString([player1 dialTargetName], rs0, 0.5 * rs2, 0, textsize); OODrawString(infoline, rs0, 0.5 * rs2 - line_height, 0, textsize); if ([target isWormhole]) { // Note: No break statements in the following switch() since every case // falls through to the next. Cases arranged in reverse order. switch([(WormholeEntity *)target scanInfo]) { case WH_SCANINFO_SHIP: // TOOD: Render anything on the HUD for this? case WH_SCANINFO_DESTINATION: // Rendered above in dialTargetName, so no need to do anything here // unless we want a separate line Destination: XXX ? case WH_SCANINFO_ARRIVAL_TIME: { NSString *wormholeETA = [NSString stringWithFormat:DESC(@"wormhole-ETA-@"), ClockToString([(WormholeEntity *)target estimatedArrivalTime], NO)]; OODrawString(wormholeETA, rs0, 0.5 * rs2 - 3 * line_height, 0, textsize); } case WH_SCANINFO_COLLAPSE_TIME: { OOTimeDelta timeForCollapsing = [(WormholeEntity *)target expiryTime] - [player1 clockTimeAdjusted]; int minutesToCollapse = floor (timeForCollapsing / 60.0); int secondsToCollapse = (int)timeForCollapsing % 60; NSString *wormholeExpiringIn = [NSString stringWithFormat:DESC(@"wormhole-collapsing-in-mm:ss"), minutesToCollapse, secondsToCollapse]; OODrawString(wormholeExpiringIn, rs0, 0.5 * rs2 - 2 * line_height, 0, textsize); } case WH_SCANINFO_SCANNED: case WH_SCANINFO_NONE: break; } } } OOGLPopModelView(); } static void hudDrawWaypoint(OOWaypointEntity *waypoint, PlayerEntity *player1, GLfloat z1, GLfloat alpha, BOOL selected, GLfloat scale) { if ([player1 guiScreen] != GUI_SCREEN_MAIN) // don't draw on text screens { return; } Vector p1 = HPVectorToVector(HPvector_subtract([waypoint position], [player1 viewpointPosition])); OOGLPushModelView(); hudRotateViewpointForVirtualDepth(player1,p1); // either close enough that single precision is fine or far enough // away that precision is irrelevant GLfloat rdist = magnitude(p1); GLfloat rsize = rdist * scale; GLfloat rs0 = rsize; GLfloat rs2 = rsize * 0.50; if (selected) { GLColorWithOverallAlpha(blue_color, alpha); } else { GLColorWithOverallAlpha(blue_color, alpha*0.25); } OOGLBEGIN(GL_LINES); glVertex2f(rs0,rs2); glVertex2f(rs2,rs2); glVertex2f(rs2,rs0); glVertex2f(rs2,rs2); glVertex2f(-rs0,rs2); glVertex2f(-rs2,rs2); glVertex2f(-rs2,rs0); glVertex2f(-rs2,rs2); glVertex2f(-rs0,-rs2); glVertex2f(-rs2,-rs2); glVertex2f(-rs2,-rs0); glVertex2f(-rs2,-rs2); glVertex2f(rs0,-rs2); glVertex2f(rs2,-rs2); glVertex2f(rs2,-rs0); glVertex2f(rs2,-rs2); // glVertex2f(0,-rs2); glVertex2f(0,rs2); // glVertex2f(rs2,0); glVertex2f(-rs2,0); OOGLEND(); if (selected) { GLfloat range = HPdistance([player1 position],[waypoint position]) * 0.001f; if (range < 0.001f) range = 0.0f; // avoids the occasional -0.001 km distance. NSSize textsize = NSMakeSize(rdist * scale, rdist * scale); float line_height = rdist * scale; NSString* infoline = [NSString stringWithFormat:@"%0.3f km", range]; OODrawString(infoline, rs0 * 0.5, -rs2 - line_height, 0, textsize); } OOGLPopModelView(); } static void hudRotateViewpointForVirtualDepth(PlayerEntity * player1, Vector p1) { Quaternion back_q = [player1 orientation]; back_q.w = -back_q.w; // invert Vector v1 = vector_up_from_quaternion(back_q); NSSize viewSize = [[UNIVERSE gameView] viewSize]; float aspect = viewSize.width / viewSize.height; // The field of view transformation is really a scale operation on the view window. // We must unapply it through these transformations for them to be right. // We must also take into account the window aspect ratio. float ratio = 2 * [[UNIVERSE gameView] fov:YES]; // FIXME 2 is magic number; fov should integrate it if (3.0f * aspect >= 4.0f) { OOGLScaleModelView(make_vector(1/ratio, 1/ratio, 1.0f)); } else { OOGLScaleModelView(make_vector((4.0f/3.0f)/(aspect*ratio), (4.0f/3.0f)/(aspect*ratio), 1.0f)); } // deal with view directions Vector view_dir, view_up = kBasisYVector; switch ([UNIVERSE viewDirection]) { default: case VIEW_FORWARD: view_dir.x = 0.0; view_dir.y = 0.0; view_dir.z = 1.0; break; case VIEW_AFT: view_dir.x = 0.0; view_dir.y = 0.0; view_dir.z = -1.0; quaternion_rotate_about_axis(&back_q, v1, M_PI); break; case VIEW_PORT: view_dir.x = -1.0; view_dir.y = 0.0; view_dir.z = 0.0; quaternion_rotate_about_axis(&back_q, v1, 0.5 * M_PI); break; case VIEW_STARBOARD: view_dir.x = 1.0; view_dir.y = 0.0; view_dir.z = 0.0; quaternion_rotate_about_axis(&back_q, v1, -0.5 * M_PI); break; case VIEW_CUSTOM: view_dir = [player1 customViewForwardVector]; view_up = [player1 customViewUpVector]; back_q = quaternion_multiply([player1 customViewQuaternion], back_q); break; } OOGLLookAt(view_dir, kZeroVector, view_up); // rotate the view OOGLMultModelView([player1 rotationMatrix]); // translate the view OOGLTranslateModelView(p1); // rotate to face player1 OOGLMultModelView(OOMatrixForQuaternionRotation(back_q)); // We come back now to the previous scale. OOGLScaleModelView(make_vector(ratio, ratio, 1.0f)); // draw the waypoint } static void InitTextEngine(void) { NSDictionary *fontSpec = nil; NSArray *widths = nil; NSString *texName = nil; NSUInteger i, count; fontSpec = [ResourceManager dictionaryFromFilesNamed:@"oolite-font.plist" inFolder:@"Config" andMerge:NO]; texName = [fontSpec oo_stringForKey:@"texture" defaultValue:@"oolite-font.png"]; sFontTexture = [OOTexture textureWithName:texName inFolder:@"Textures" options:kFontTextureOptions anisotropy:0.0f lodBias:-0.75f]; [sFontTexture retain]; sF6KernGovt = [fontSpec oo_floatForKey:@"f6KernGovernment" defaultValue:1.0]; sF6KernTL = [fontSpec oo_floatForKey:@"f6KernTechLevel" defaultValue:2.0]; sEncodingCoverter = [[OOEncodingConverter alloc] initWithFontPList:fontSpec]; widths = [fontSpec oo_arrayForKey:@"widths"]; count = [widths count]; if (count > 256) count = 256; for (i = 0; i != count; ++i) { sGlyphWidths[i] = [widths oo_floatAtIndex:i] * GLYPH_SCALE_FACTOR; } } void OOHUDResetTextEngine(void) { DESTROY(sFontTexture); DESTROY(sEncodingCoverter); } static GLfloat drawCharacterQuad(uint8_t chr, GLfloat x, GLfloat y, GLfloat z, NSSize siz) { // 31 (narrow space) and 32 (space) are non-printing characters, so // don't print them, just return their width to move the pointer if (chr > 32 || chr < 31) { GLfloat texture_x = ONE_SIXTEENTH * (chr & 0x0f); GLfloat texture_y = ONE_SIXTEENTH * (chr >> 4); if (chr > 32) y += ONE_EIGHTH * siz.height; // Adjust for baseline offset change in 1.71 (needed to keep accented characters in box) glTexCoord2f(texture_x, texture_y + ONE_SIXTEENTH); glVertex3f(x, y, z); glTexCoord2f(texture_x + ONE_SIXTEENTH, texture_y + ONE_SIXTEENTH); glVertex3f(x + siz.width, y, z); glTexCoord2f(texture_x + ONE_SIXTEENTH, texture_y); glVertex3f(x + siz.width, y + siz.height, z); glTexCoord2f(texture_x, texture_y); glVertex3f(x, y + siz.height, z); } return siz.width * sGlyphWidths[chr]; } NSRect OORectFromString(NSString *text, GLfloat x, GLfloat y, NSSize siz) { GLfloat w = 0; NSData *data = nil; const uint8_t *bytes = NULL; NSUInteger i, length; data = [sEncodingCoverter convertString:text]; bytes = [data bytes]; length = [data length]; for (i = 0; i < length; i++) { w += siz.width * sGlyphWidths[bytes[i]]; } return NSMakeRect(x, y, w, siz.height); } CGFloat OOStringWidthInEm(NSString *text) { return OORectFromString(text, 0, 0, NSMakeSize(1.0 / (GLYPH_SCALE_FACTOR * 8.0), 1.0)).size.width; } void drawHighlight(GLfloat x, GLfloat y, GLfloat z, NSSize siz, GLfloat alpha) { // Rounded corners, fading 'shadow' version OOGL(glColor4f(0.0f, 0.0f, 0.0f, alpha * 0.4f)); // dark translucent shadow OOGLBEGIN(GL_POLYGON); // thin 'halo' around the 'solid' highlight glVertex3f(x + 1.0f , y + siz.height + 2.5f, z); glVertex3f(x + siz.width + 3.0f, y + siz.height + 2.5f, z); glVertex3f(x + siz.width + 4.5f, y + siz.height + 1.0f, z); glVertex3f(x + siz.width + 4.5f, y + 3.0f, z); glVertex3f(x + siz.width + 3.0f, y + 1.5f, z); glVertex3f(x + 1.0f, y + 1.5f, z); glVertex3f(x - 0.5f, y + 3.0f, z); glVertex3f(x - 0.5f, y + siz.height + 1.0f, z); OOGLEND(); OOGLBEGIN(GL_POLYGON); glVertex3f(x + 1.0f, y + siz.height + 2.0f, z); glVertex3f(x + siz.width + 3.0f, y + siz.height + 2.0f, z); glVertex3f(x + siz.width + 4.0f, y + siz.height + 1.0f, z); glVertex3f(x + siz.width + 4.0f, y + 3.0f, z); glVertex3f(x + siz.width + 3.0f, y + 2.0f, z); glVertex3f(x + 1.0f, y + 2.0f, z); glVertex3f(x, y + 3.0f, z); glVertex3f(x, y + siz.height + 1.0f, z); OOGLEND(); } void OODrawString(NSString *text, GLfloat x, GLfloat y, GLfloat z, NSSize siz) { OODrawStringAligned(text,x,y,z,siz,NO); } void OODrawStringAligned(NSString *text, GLfloat x, GLfloat y, GLfloat z, NSSize siz, BOOL rightAlign) { OOStartDrawingStrings(); OODrawStringQuadsAligned(text,x,y,z,siz,rightAlign); OOStopDrawingStrings(); } void OOStartDrawingStrings() { OOSetOpenGLState(OPENGL_STATE_OVERLAY); OOGL(glEnable(GL_TEXTURE_2D)); [sFontTexture apply]; OOGLBEGIN(GL_QUADS); } void OODrawStringQuadsAligned(NSString *text, GLfloat x, GLfloat y, GLfloat z, NSSize siz, BOOL rightAlign) { GLfloat cx = x; NSInteger i, length; NSData *data = nil; const uint8_t *bytes = NULL; data = [sEncodingCoverter convertString:text]; length = [data length]; bytes = [data bytes]; if (EXPECT_NOT(rightAlign)) { cx -= OORectFromString(text, 0.0f, 0.0f, siz).size.width; } for (i = 0; i < length; i++) { cx += drawCharacterQuad(bytes[i], cx, y, z, siz); } } void OOStopDrawingStrings() { OOGLEND(); [OOTexture applyNone]; OOGL(glDisable(GL_TEXTURE_2D)); OOVerifyOpenGLState(); } void OODrawHilightedString(NSString *text, GLfloat x, GLfloat y, GLfloat z, NSSize siz) { GLfloat color[4]; // get the physical dimensions of the string NSSize strsize = OORectFromString(text, 0.0f, 0.0f, siz).size; strsize.width += 0.5f; OOSetOpenGLState(OPENGL_STATE_OVERLAY); OOGL(glPushAttrib(GL_CURRENT_BIT)); // save the text colour OOGL(glGetFloatv(GL_CURRENT_COLOR, color)); // we need the original colour's alpha. drawHighlight(x, y, z, strsize, color[3]); OOGL(glPopAttrib()); //restore the colour OODrawString(text, x, y, z, siz); OOVerifyOpenGLState(); } void OODrawPlanetInfo(int gov, int eco, int tec, GLfloat x, GLfloat y, GLfloat z, NSSize siz) { GLfloat govcol[] = { 0.5, 0.0, 0.7, 0.7, 0.5, 0.3, 0.0, 1.0, 0.3, 1.0, 0.8, 0.1, 1.0, 0.0, 0.0, 0.1, 0.5, 1.0, 0.7, 0.7, 0.7, 0.7, 1.0, 1.0}; GLfloat cx = x; int tl = tec + 1; GLfloat ce1 = 1.0f - 0.125f * eco; OOSetOpenGLState(OPENGL_STATE_OVERLAY); OOGL(glEnable(GL_TEXTURE_2D)); [sFontTexture apply]; OOGLBEGIN(GL_QUADS); { [[UNIVERSE gui] setGLColorFromSetting:[NSString stringWithFormat:kGuiChartEconomyUColor, (unsigned long)eco] defaultValue:[OOColor colorWithRed:ce1 green:1.0f blue:0.0f alpha:1.0f] alpha:1.0]; // see OODrawHilightedPlanetInfo cx += drawCharacterQuad(23 - eco, cx, y, z, siz); // characters 16..23 are economy symbols [[UNIVERSE gui] setGLColorFromSetting:[NSString stringWithFormat:kGuiChartGovernmentUColor, (unsigned long)gov] defaultValue:[OOColor colorWithRed:govcol[gov*3] green:govcol[1+(gov*3)] blue:govcol[2+(gov*3)] alpha:1.0f] alpha:1.0]; cx += drawCharacterQuad(gov, cx, y, z, siz) - sF6KernGovt; // charcters 0..7 are government symbols [[UNIVERSE gui] setGLColorFromSetting:kGuiChartTechColor defaultValue:[OOColor colorWithRed:0.5 green:1.0f blue:1.0f alpha:1.0f] alpha:1.0]; if (tl > 9) { // display TL clamped between 1..16, this must be a '1'! cx += drawCharacterQuad(49, cx, y - 2, z, siz) - sF6KernTL; } cx += drawCharacterQuad(48 + (tl % 10), cx, y - 2.0f, z, siz); } OOGLEND(); (void)cx; // Suppress "value not used" analyzer issue. [OOTexture applyNone]; OOGL(glDisable(GL_TEXTURE_2D)); OOVerifyOpenGLState(); } void OODrawHilightedPlanetInfo(int gov, int eco, int tec, GLfloat x, GLfloat y, GLfloat z, NSSize siz) { float color[4]; int tl = tec + 1; NSSize hisize; // get the physical dimensions hisize.height = siz.height; hisize.width = 0.0f; // see OODrawPlanetInfo hisize.width += siz.width * sGlyphWidths[23 - eco]; hisize.width += siz.width * sGlyphWidths[gov] - 1.0; if (tl > 9) hisize.width += siz.width * sGlyphWidths[49] - 2.0; hisize.width += siz.width * sGlyphWidths[48 + (tl % 10)]; OOSetOpenGLState(OPENGL_STATE_OVERLAY); OOGL(glPushAttrib(GL_CURRENT_BIT)); // save the text colour OOGL(glGetFloatv(GL_CURRENT_COLOR, color)); // we need the original colour's alpha. drawHighlight(x, y - 2.0f, z, hisize, color[3]); OOGL(glPopAttrib()); //restore the colour OODrawPlanetInfo(gov, eco, tec, x, y, z, siz); OOVerifyOpenGLState(); } static void GLDrawNonlinearCascadeWeapon( GLfloat x, GLfloat y, GLfloat z, NSSize siz, Vector centre, GLfloat radius, GLfloat zoom, GLfloat alpha ) { Vector spacepos, scannerpos; GLfloat theta, phi; GLfloat z_factor = siz.height / siz.width; // approx 1/4 GLfloat y_factor = 1.0 - sqrt(z_factor); // approx 1/2 OOGLVector *points = malloc(sizeof(OOGLVector)*25); int i, j; if (radius*radius > centre.y*centre.y) { GLfloat r0 = sqrt(radius*radius-centre.y*centre.y); OOGL(glColor4f(1.0, 0.5, 1.0, alpha)); spacepos.y = 0; for (i = 0; i < 24; i++) { theta = i*2*M_PI/24; spacepos.x = centre.x + r0 * cos(theta); spacepos.z = centre.z + r0 * sin(theta); scannerpos = [HeadUpDisplay nonlinearScannerScale: spacepos Zoom: zoom Scale: 0.5*siz.width]; points[i].x = x + scannerpos.x; points[i].y = y + scannerpos.z * z_factor + scannerpos.y * y_factor; points[i].z = z; } spacepos.x = centre.x + r0; spacepos.y = 0; spacepos.z = centre.z; scannerpos = [HeadUpDisplay nonlinearScannerScale: spacepos Zoom: zoom Scale: 0.5*siz.width]; points[24].x = x + scannerpos.x; points[24].y = y + scannerpos.z * z_factor + scannerpos.y * y_factor; points[24].z = z; GLDrawPoints(points,25); } OOGL(glColor4f(0.5, 0.0, 1.0, 0.33333 * alpha)); free(points); // Here, we draw a sphere distorted by the nonlinear function. We draw the sphere as a set of horizontal strips // The even indices of points are the points on the upper edge of the strip, while odd indices are points // on the bottom edge. points = malloc(sizeof(OOGLVector)*50); spacepos.x = centre.x; spacepos.y = centre.y + radius; spacepos.z = centre.z; scannerpos = [HeadUpDisplay nonlinearScannerScale: spacepos Zoom: zoom Scale: 0.5*siz.width]; for (i = 0; i <= 24; i++) { points[2*i+1].x = x + scannerpos.x; points[2*i+1].y = y + scannerpos.y * y_factor + scannerpos.z * z_factor; points[2*i+1].z = z; } for (i = 1; i <= 24; i++) { theta = i*M_PI/24; for (j = 0; j <= 24; j++) { phi = j*M_PI/12; // copy point from bottom edge of previous strip into top edge position points[2*j] = points[2*j+1]; spacepos.x = centre.x + radius * sin(theta) * cos(phi); spacepos.y = centre.y + radius * cos(theta); spacepos.z = centre.z + radius * sin(theta) * sin(phi); scannerpos = [HeadUpDisplay nonlinearScannerScale: spacepos Zoom: zoom Scale: 0.5*siz.width]; points[2*j+1].x = x + scannerpos.x; points[2*j+1].y = y + scannerpos.y * y_factor + scannerpos.z * z_factor; points[2*j+1].z = z; } GLDrawQuadStrip(points, 50); } free(points); return; } static GLfloat nonlinearScannerFunc( GLfloat distance, GLfloat zoom, GLfloat scale ) { GLfloat x = fabs(distance / SCANNER_MAX_RANGE); if (x >= 1.0) return scale; if (zoom <= 1.0) return scale * x; GLfloat c = 1 / ( zoom - 1 ); GLfloat b = c * ( c + 1 ); GLfloat a = c + 1; return scale * ( a - b / ( x + c ) ); } static void drawScannerGrid(GLfloat x, GLfloat y, GLfloat z, NSSize siz, int v_dir, GLfloat thickness, GLfloat zoom, BOOL nonlinear) { OOSetOpenGLState(OPENGL_STATE_OVERLAY); GLfloat w1, h1; GLfloat ww = 0.5 * siz.width; GLfloat hh = 0.5 * siz.height; GLfloat w2 = 0.250 * siz.width; GLfloat h2 = 0.250 * siz.height; GLfloat km_scan; GLfloat hdiv; GLfloat wdiv; BOOL drawdiv = NO, drawdiv1 = NO, drawdiv5 = NO; int i, ii; OOGL(GLScaledLineWidth(2.0 * thickness)); GLDrawOval(x, y, z, siz, 4); OOGL(GLScaledLineWidth(thickness)); // reset (thickness = lineWidth) OOGLBEGIN(GL_LINES); glVertex3f(x, y - hh, z); glVertex3f(x, y + hh, z); glVertex3f(x - ww, y, z); glVertex3f(x + ww, y, z); if (nonlinear) { if (nonlinearScannerFunc(4000.0, zoom, hh)-nonlinearScannerFunc(3000.0, zoom ,hh) > 2) drawdiv1 = YES; if (nonlinearScannerFunc(10000.0, zoom, hh)-nonlinearScannerFunc(5000.0, zoom, hh) > 2) drawdiv5 = YES; wdiv = ww/(0.001*SCANNER_MAX_RANGE); for (i = 1; 1000.0*i < SCANNER_MAX_RANGE; i++) { drawdiv = drawdiv1; w1 = wdiv; if (i % 10 == 0) { w1 = wdiv*4; drawdiv = YES; if (nonlinearScannerFunc((i+5)*1000,zoom,hh) - nonlinearScannerFunc(i*1000.0,zoom,hh)>2) { drawdiv5 = YES; } else { drawdiv5 = NO; } } else if (i % 5 == 0) { w1 = wdiv*2; drawdiv = drawdiv5; if (nonlinearScannerFunc((i+1)*1000,zoom,hh) - nonlinearScannerFunc(i*1000.0,zoom,hh)>2) { drawdiv1 = YES; } else { drawdiv1 = NO; } } if (drawdiv) { h1 = nonlinearScannerFunc(i*1000.0,zoom,hh); glVertex3f(x - w1, y + h1, z); glVertex3f(x + w1, y + h1, z); glVertex3f(x - w1, y - h1, z); glVertex3f(x + w1, y - h1, z); } } } else { km_scan = 0.001 * SCANNER_MAX_RANGE / zoom; // calculate kilometer divisions hdiv = 0.5 * siz.height / km_scan; wdiv = 0.25 * siz.width / km_scan; if (wdiv < 4.0) { wdiv *= 2.0; ii = 5; } else { ii = 1; } for (i = ii; 2.0 * hdiv * i < siz.height; i += ii) { h1 = i * hdiv; w1 = wdiv; if (i % 5 == 0) w1 = w1 * 2.5; if (i % 10 == 0) w1 = w1 * 2.0; if (w1 > 3.5) // don't draw tiny marks { glVertex3f(x - w1, y + h1, z); glVertex3f(x + w1, y + h1, z); glVertex3f(x - w1, y - h1, z); glVertex3f(x + w1, y - h1, z); } } } switch (v_dir) { case VIEW_BREAK_PATTERN: case VIEW_GUI_DISPLAY: case VIEW_FORWARD: case VIEW_NONE: glVertex3f(x, y, z); glVertex3f(x - w2, y + hh, z); glVertex3f(x, y, z); glVertex3f(x + w2, y + hh, z); break; case VIEW_AFT: glVertex3f(x, y, z); glVertex3f(x - w2, y - hh, z); glVertex3f(x, y, z); glVertex3f(x + w2, y - hh, z); break; case VIEW_PORT: glVertex3f(x, y, z); glVertex3f(x - ww, y + h2, z); glVertex3f(x, y, z); glVertex3f(x - ww, y - h2, z); break; case VIEW_STARBOARD: glVertex3f(x, y, z); glVertex3f(x + ww, y + h2, z); glVertex3f(x, y, z); glVertex3f(x + ww, y - h2, z); break; } OOGLEND(); OOVerifyOpenGLState(); } static void DrawSpecialOval(GLfloat x, GLfloat y, GLfloat z, NSSize siz, GLfloat step, GLfloat *color4v) { GLfloat ww = 0.5 * siz.width; GLfloat hh = 0.5 * siz.height; GLfloat theta; GLfloat delta; GLfloat s; delta = step * M_PI / 180.0f; OOGLBEGIN(GL_LINE_LOOP); for (theta = 0.0f; theta < (2.0f * M_PI); theta += delta) { s = sin(theta); glColor4f(color4v[0], color4v[1], color4v[2], fabs(s * color4v[3])); glVertex3f(x + ww * s, y + hh * cos(theta), z); } OOGLEND(); } - (void) setLineWidth:(GLfloat) value { lineWidth = value; } - (GLfloat) lineWidth { return lineWidth; } @end @implementation NSString (OODisplayEncoding) - (const char *) cStringUsingOoliteEncoding { if (sEncodingCoverter == nil) InitTextEngine(); // Note: the data will be autoreleased, so the bytes behave as though they're autoreleased too. return [[self dataUsingEncoding:[sEncodingCoverter encoding] allowLossyConversion:YES] bytes]; } - (const char *) cStringUsingOoliteEncodingAndRemapping { if (sEncodingCoverter == nil) InitTextEngine(); // Note: the data will be autoreleased, so the bytes behave as though they're autoreleased too. return [[sEncodingCoverter convertString:self] bytes]; } @end @implementation OOPolygonSprite (OOHUDBeaconIcon) - (void) oo_drawHUDBeaconIconAt:(NSPoint)where size:(NSSize)size alpha:(GLfloat)alpha z:(GLfloat)z { GLfloat x = where.x - size.width; GLfloat y = where.y - 1.5 * size.height; GLfloat ox = x - size.width * 0.5; GLfloat oy = y - size.height * 0.5; GLfloat width = size.width * (1.0f / 6.0f); GLfloat height = size.height * (1.0f / 6.0f); OOGLPushModelView(); OOGLTranslateModelView(make_vector(ox, oy, z)); OOGLScaleModelView(make_vector(width, height, 1.0f)); [self drawFilled]; glColor4f(0.0, 0.0, 0.0, 0.5 * alpha); [self drawOutline]; OOGLPopModelView(); } @end @implementation NSString (OOHUDBeaconIcon) - (void) oo_drawHUDBeaconIconAt:(NSPoint)where size:(NSSize)size alpha:(GLfloat)alpha z:(GLfloat)z { OODrawString(self, where.x - 2.5 * size.width, where.y - 3.0 * size.height, z, NSMakeSize(size.width * 2, size.height * 2)); } @end static void SetGLColourFromInfo(NSDictionary *info, NSString *key, const GLfloat defaultColor[4], GLfloat alpha) { id colorDesc = nil; OOColor *color = nil; colorDesc = [info objectForKey:key]; if (colorDesc != nil) { color = [OOColor colorWithDescription:colorDesc]; if (color != nil) { GLfloat ioColor[4]; [color getRed:&ioColor[0] green:&ioColor[1] blue:&ioColor[2] alpha:&ioColor[3]]; GLColorWithOverallAlpha(ioColor,alpha); return; } } GLColorWithOverallAlpha(defaultColor,alpha); } static void GetRGBAArrayFromInfo(NSDictionary *info, GLfloat ioColor[4]) { id colorDesc = nil; OOColor *color = nil; // First, look for general colour specifier. colorDesc = [info objectForKey:RGB_COLOR_KEY]; if (colorDesc != nil && ![info objectForKey:ALPHA_KEY]) { color = [OOColor colorWithDescription:colorDesc]; if (color != nil) { [color getRed:&ioColor[0] green:&ioColor[1] blue:&ioColor[2] alpha:&ioColor[3]]; return; } } // Failing that, look for rgb_color and alpha. colorDesc = [info oo_arrayForKey:RGB_COLOR_KEY]; if (colorDesc != nil && [colorDesc count] == 3) { ioColor[0] = [colorDesc oo_nonNegativeFloatAtIndex:0]; ioColor[1] = [colorDesc oo_nonNegativeFloatAtIndex:1]; ioColor[2] = [colorDesc oo_nonNegativeFloatAtIndex:2]; } ioColor[3] = [info oo_nonNegativeFloatForKey:ALPHA_KEY defaultValue:ioColor[3]]; } oolite-1.82/src/Core/Materials/000077500000000000000000000000001256642440500163475ustar00rootroot00000000000000oolite-1.82/src/Core/Materials/OOBasicMaterial.h000066400000000000000000000073641256642440500214700ustar00rootroot00000000000000/* OOBasicMaterial.h Material using basic OpenGL properties. Normal materials (OOSingleTextureMaterial, OOShaderMaterial) are subclasses of this. It may be desireable to have a material which does not use normal GL material properties, in which case it should be based on OOMaterial directly. Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOMaterial.h" #import "OOColor.h" @interface OOBasicMaterial: OOMaterial { @private NSString *materialName; // Colours GLfloat diffuse[4], specular[4], ambient[4], emission[4]; // Specular exponent uint8_t shininess; // Default: 0.0 } /* Initialize with default values (historical Olite defaults, not GL defaults): diffuse { 1.0, 1.0, 1.0, 1.0 } specular { 0.0, 0.0, 0.0, 1.0 } ambient { 1.0, 1.0, 1.0, 1.0 } emission { 0.0, 0.0, 0.0, 1.0 } shininess 0 */ - (id)initWithName:(NSString *)name; /* Initialize with dictionary. Accepted keys: diffuse colour description specular colour description ambient colour description emission colour description shininess integer "Colour description" refers to anything +[OOColor colorWithDescription:] will accept. */ - (id)initWithName:(NSString *)name configuration:(NSDictionary *)configuration; - (OOColor *)diffuseColor; - (void)setDiffuseColor:(OOColor *)color; - (void)setAmbientAndDiffuseColor:(OOColor *)color; - (OOColor *)specularColor; - (void)setSpecularColor:(OOColor *)color; - (OOColor *)ambientColor; - (void)setAmbientColor:(OOColor *)color; - (OOColor *)emmisionColor; - (void)setEmissionColor:(OOColor *)color; - (void)getDiffuseComponents:(GLfloat[4])outComponents; - (void)setDiffuseComponents:(const GLfloat[4])components; - (void)setAmbientAndDiffuseComponents:(const GLfloat[4])components; - (void)getSpecularComponents:(GLfloat[4])outComponents; - (void)setSpecularComponents:(const GLfloat[4])components; - (void)getAmbientComponents:(GLfloat[4])outComponents; - (void)setAmbientComponents:(const GLfloat[4])components; - (void)getEmissionComponents:(GLfloat[4])outComponents; - (void)setEmissionComponents:(const GLfloat[4])components; - (void)setDiffuseRed:(GLfloat)r green:(GLfloat)g blue:(GLfloat)b alpha:(GLfloat)a; - (void)setAmbientAndDiffuseRed:(GLfloat)r green:(GLfloat)g blue:(GLfloat)b alpha:(GLfloat)a; - (void)setSpecularRed:(GLfloat)r green:(GLfloat)g blue:(GLfloat)b alpha:(GLfloat)a; - (void)setAmbientRed:(GLfloat)r green:(GLfloat)g blue:(GLfloat)b alpha:(GLfloat)a; - (void)setEmissionRed:(GLfloat)r green:(GLfloat)g blue:(GLfloat)b alpha:(GLfloat)a; - (uint8_t)shininess; - (void)setShininess:(uint8_t)value; // Clamped to [0, 128] /* For subclasses: return true to permit specular settings, false to deny them. By default, this is ![UNIVERSE reducedDetail]. */ - (BOOL) permitSpecular; @end oolite-1.82/src/Core/Materials/OOBasicMaterial.m000066400000000000000000000163161256642440500214720ustar00rootroot00000000000000/* OOBasicMaterial.m Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOBasicMaterial.h" #import "OOCollectionExtractors.h" #import "OOFunctionAttributes.h" #import "Universe.h" #import "OOMaterialSpecifier.h" #import "OOTexture.h" static OOBasicMaterial *sDefaultMaterial = nil; #define FACE GL_FRONT_AND_BACK @implementation OOBasicMaterial - (id)initWithName:(NSString *)name { self = [super init]; if (EXPECT_NOT(self == nil)) return nil; materialName = [name copy]; [self setDiffuseRed:1.0f green:1.0f blue:1.0f alpha:1.0f]; [self setAmbientRed:1.0f green:1.0f blue:1.0f alpha:1.0f]; specular[3] = 1.0; emission[3] = 1.0; return self; } - (id)initWithName:(NSString *)name configuration:(NSDictionary *)configuration { id colorDesc = nil; int specularExponent; self = [self initWithName:name]; if (EXPECT_NOT(self == nil)) return nil; if (configuration == nil) configuration = [NSDictionary dictionary]; colorDesc = [configuration oo_diffuseColor]; if (colorDesc != nil) [self setDiffuseColor:[OOColor colorWithDescription:colorDesc]]; colorDesc = [configuration oo_ambientColor]; if (colorDesc != nil) [self setAmbientColor:[OOColor colorWithDescription:colorDesc]]; else [self setAmbientColor:[self diffuseColor]]; colorDesc = [configuration oo_emissionColor]; if (colorDesc != nil) [self setEmissionColor:[OOColor colorWithDescription:colorDesc]]; specularExponent = [configuration oo_specularExponent]; if (specularExponent != 0 && [self permitSpecular]) { colorDesc = [configuration oo_specularColor]; [self setShininess:specularExponent]; if (colorDesc != nil) [self setSpecularColor:[OOColor colorWithDescription:colorDesc]]; } return self; } - (void)dealloc { [super willDealloc]; [materialName release]; [super dealloc]; } - (NSString *)name { return materialName; } - (BOOL)doApply { OOGL(glMaterialfv(FACE, GL_DIFFUSE, diffuse)); OOGL(glMaterialfv(FACE, GL_SPECULAR, specular)); OOGL(glMaterialfv(FACE, GL_AMBIENT, ambient)); OOGL(glMaterialfv(FACE, GL_EMISSION, emission)); OOGL(glMateriali(FACE, GL_SHININESS, shininess)); if ([self isMemberOfClass:[OOBasicMaterial class]]) { [OOTexture applyNone]; } return YES; } - (void)unapplyWithNext:(OOMaterial *)next { if (![next isKindOfClass:[OOBasicMaterial class]]) { if (EXPECT_NOT(sDefaultMaterial == nil)) sDefaultMaterial = [[OOBasicMaterial alloc] initWithName:@""]; [sDefaultMaterial doApply]; } } - (OOColor *)diffuseColor { return [OOColor colorWithRed:diffuse[0] green:diffuse[1] blue:diffuse[2] alpha:diffuse[3]]; } - (void)setDiffuseColor:(OOColor *)color { if (color != nil) { [self setDiffuseRed:[color redComponent] green:[color greenComponent] blue:[color blueComponent] alpha:[color alphaComponent]]; } } - (void)setAmbientAndDiffuseColor:(OOColor *)color { [self setAmbientColor:color]; [self setDiffuseColor:color]; } - (OOColor *)specularColor { return [OOColor colorWithRed:specular[0] green:specular[1] blue:specular[2] alpha:specular[3]]; } - (void)setSpecularColor:(OOColor *)color { if (color != nil) { [self setSpecularRed:[color redComponent] green:[color greenComponent] blue:[color blueComponent] alpha:[color alphaComponent]]; } } - (OOColor *)ambientColor { return [OOColor colorWithRed:ambient[0] green:ambient[1] blue:ambient[2] alpha:ambient[3]]; } - (void)setAmbientColor:(OOColor *)color { if (color != nil) { [self setAmbientRed:[color redComponent] green:[color greenComponent] blue:[color blueComponent] alpha:[color alphaComponent]]; } } - (OOColor *)emmisionColor { return [OOColor colorWithRed:emission[0] green:emission[1] blue:emission[2] alpha:emission[3]]; } - (void)setEmissionColor:(OOColor *)color { if (color != nil) { [self setEmissionRed:[color redComponent] green:[color greenComponent] blue:[color blueComponent] alpha:[color alphaComponent]]; } } - (void)getDiffuseComponents:(GLfloat[4])outComponents { memcpy(outComponents, diffuse, 4 * sizeof *outComponents); } - (void)setDiffuseComponents:(const GLfloat[4])components { memcpy(diffuse, components, 4 * sizeof *components); } - (void)setAmbientAndDiffuseComponents:(const GLfloat[4])components { [self setAmbientComponents:components]; [self setDiffuseComponents:components]; } - (void)getSpecularComponents:(GLfloat[4])outComponents { memcpy(outComponents, specular, 4 * sizeof *outComponents); } - (void)setSpecularComponents:(const GLfloat[4])components { memcpy(specular, components, 4 * sizeof *components); } - (void)getAmbientComponents:(GLfloat[4])outComponents { memcpy(outComponents, ambient, 4 * sizeof *outComponents); } - (void)setAmbientComponents:(const GLfloat[4])components { memcpy(ambient, components, 4 * sizeof *components); } - (void)getEmissionComponents:(GLfloat[4])outComponents { memcpy(outComponents, emission, 4 * sizeof *outComponents); } - (void)setEmissionComponents:(const GLfloat[4])components { memcpy(emission, components, 4 * sizeof *components); } - (void)setDiffuseRed:(GLfloat)r green:(GLfloat)g blue:(GLfloat)b alpha:(GLfloat)a { diffuse[0] = r; diffuse[1] = g; diffuse[2] = b; diffuse[3] = a; } - (void)setAmbientAndDiffuseRed:(GLfloat)r green:(GLfloat)g blue:(GLfloat)b alpha:(GLfloat)a { [self setAmbientRed:r green:g blue:b alpha:a]; [self setDiffuseRed:r green:g blue:b alpha:a]; } - (void)setSpecularRed:(GLfloat)r green:(GLfloat)g blue:(GLfloat)b alpha:(GLfloat)a { specular[0] = r; specular[1] = g; specular[2] = b; specular[3] = a; } - (void)setAmbientRed:(GLfloat)r green:(GLfloat)g blue:(GLfloat)b alpha:(GLfloat)a { ambient[0] = r; ambient[1] = g; ambient[2] = b; ambient[3] = a; } - (void)setEmissionRed:(GLfloat)r green:(GLfloat)g blue:(GLfloat)b alpha:(GLfloat)a { emission[0] = r; emission[1] = g; emission[2] = b; emission[3] = a; } - (uint8_t)shininess { return shininess; } - (void)setShininess:(uint8_t)value { shininess = MIN(value, 128); } - (BOOL) permitSpecular { return ![UNIVERSE reducedDetail]; } #ifndef NDEBUG - (NSSet *) allTextures { return [NSSet set]; } #endif @end oolite-1.82/src/Core/Materials/OOCombinedEmissionMapGenerator.h000066400000000000000000000050561256642440500245200ustar00rootroot00000000000000/* OOCombinedEmissionMapGenerator.h Generator which renders a single RGB emission map from some combination of emission_map, emission, illumination_map, illumination_color and emission_and_illumination_map parameters. Copyright (C) 2010-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOTextureGenerator.h" @class OOColor; @interface OOCombinedEmissionMapGenerator: OOTextureGenerator { @private NSString *_cacheKey; NSDictionary *_emissionSpec; NSDictionary *_illuminationSpec; OOTexture *_diffuseMap; OOPixMap _emissionPx; OOPixMap _diffusePx; OOPixMap _illuminationPx; OOColor *_emissionColor; OOColor *_illuminationColor; BOOL _isCombinedMap; uint32_t _textureOptions; GLfloat _anisotropy; GLfloat _lodBias; #ifndef NDEBUG NSString *_emissionDesc; NSString *_illuminationDesc; NSString *_diffuseDesc; #endif } - (id) initWithEmissionMapSpec:(NSDictionary *)emissionMapSpec emissionColor:(OOColor *)emissionColor diffuseMap:(OOTexture *)diffuseMap diffuseColor:(OOColor *)diffuseColor illuminationMapSpec:(NSDictionary *)illuminationMapSpec illuminationColor:(OOColor *)illuminationColor optionsSpecifier:(NSDictionary *)spec; - (id) initWithEmissionAndIlluminationMapSpec:(NSDictionary *)emissionAndIlluminationMapSpec diffuseMap:(OOTexture *)diffuseMap diffuseColor:(OOColor *)diffuseColor emissionColor:(OOColor *)emissionColor illuminationColor:(OOColor *)illuminationColor optionsSpecifier:(NSDictionary *)spec; @end oolite-1.82/src/Core/Materials/OOCombinedEmissionMapGenerator.m000066400000000000000000000331611256642440500245230ustar00rootroot00000000000000/* OOCombinedEmissionMapGenerator.m Copyright (C) 2010-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCombinedEmissionMapGenerator.h" #import "OOColor.h" #import "OOPixMapChannelOperations.h" #import "OOTextureScaling.h" #import "OOTextureInternal.h" #import "OOMaterialSpecifier.h" #import "OOCollectionExtractors.h" #define DUMP_COMBINER 0 static OOColor *ModulateColor(OOColor *a, OOColor *b); static void ScaleToMatch(OOPixMap *pmA, OOPixMap *pmB); @interface OOCombinedEmissionMapGenerator (Private) - (id) initWithEmissionMapSpec:(NSDictionary *)emissionMapSpec emissionColor:(OOColor *)emissionColor diffuseMap:(OOTexture *)diffuseMap diffuseColor:(OOColor *)diffuseColor illuminationMapSpec:(NSDictionary *)illuminationMapSpec illuminationColor:(OOColor *)illuminationColor isCombinedMap:(BOOL)isCombinedMap optionsSpecifier:(NSDictionary *)spec; - (NSString *)constructCacheKey; @end @implementation OOCombinedEmissionMapGenerator - (id) initWithEmissionMapSpec:(NSDictionary *)emissionMapSpec emissionColor:(OOColor *)emissionColor diffuseMap:(OOTexture *)diffuseMap diffuseColor:(OOColor *)diffuseColor illuminationMapSpec:(NSDictionary *)illuminationMapSpec illuminationColor:(OOColor *)illuminationColor optionsSpecifier:(NSDictionary *)spec { return [self initWithEmissionMapSpec:emissionMapSpec emissionColor:emissionColor diffuseMap:diffuseMap diffuseColor:diffuseColor illuminationMapSpec:illuminationMapSpec illuminationColor:illuminationColor isCombinedMap:NO optionsSpecifier:spec]; } - (id) initWithEmissionAndIlluminationMapSpec:(NSDictionary *)emissionAndIlluminationMapSpec diffuseMap:(OOTexture *)diffuseMap diffuseColor:(OOColor *)diffuseColor emissionColor:(OOColor *)emissionColor illuminationColor:(OOColor *)illuminationColor optionsSpecifier:(NSDictionary *)spec { return [self initWithEmissionMapSpec:emissionAndIlluminationMapSpec emissionColor:emissionColor diffuseMap:diffuseMap diffuseColor:diffuseColor illuminationMapSpec:nil illuminationColor:illuminationColor isCombinedMap:YES optionsSpecifier:spec]; } - (id) initWithEmissionMapSpec:(NSDictionary *)emissionMapSpec emissionColor:(OOColor *)emissionColor diffuseMap:(OOTexture *)diffuseMap diffuseColor:(OOColor *)diffuseColor illuminationMapSpec:(NSDictionary *)illuminationMapSpec illuminationColor:(OOColor *)illuminationColor isCombinedMap:(BOOL)isCombinedMap optionsSpecifier:(NSDictionary *)spec { if (emissionMapSpec == nil && illuminationMapSpec == nil) { [self release]; return nil; } NSParameterAssert(illuminationMapSpec == nil || !isCombinedMap); uint32_t options; GLfloat anisotropy; GLfloat lodBias; OOInterpretTextureSpecifier(spec, NULL, &options, &anisotropy, &lodBias, YES); options = OOApplyTextureOptionDefaults(options); if ((self = [super initWithPath:@"" options:options])) { /* Illumination contribution is: illuminationMap * illuminationColor * diffuseMap * diffuseColor Since illuminationColor and diffuseColor aren't used otherwise, we may as well combine them up front. */ illuminationColor = ModulateColor(diffuseColor, illuminationColor); if ([emissionColor isWhite]) emissionColor = nil; if ([illuminationColor isWhite]) illuminationColor = nil; if (!isCombinedMap && illuminationMapSpec == nil) diffuseMap = nil; // Diffuse map is only used with illumination // Insert extraShrink flag here instead of using extraOptions later because we need it in cache key too. NSMutableDictionary *emissionSpec = [emissionMapSpec mutableCopy]; [emissionSpec oo_setBool:YES forKey:kOOTextureSpecifierExtraShrinkKey]; _emissionSpec = emissionSpec; NSMutableDictionary *illuminationSpec = [illuminationMapSpec mutableCopy]; [illuminationSpec oo_setBool:YES forKey:kOOTextureSpecifierExtraShrinkKey]; _illuminationSpec = illuminationSpec; _diffuseMap = [diffuseMap retain]; _emissionColor = [emissionColor retain]; _illuminationColor = [illuminationColor retain]; _isCombinedMap = isCombinedMap; _textureOptions = options; _anisotropy = anisotropy; _lodBias = lodBias; _cacheKey = [self constructCacheKey]; if ([OOTexture existingTextureForKey:_cacheKey] == nil) { /* Extract pixmap from diffuse map. This must be done in the main thread even if scheduling is fixed, because it might involve reading back pixels from OpenGL. */ if (diffuseMap != nil) { _diffusePx = [diffuseMap copyPixMapRepresentation]; #ifndef NDEBUG _diffuseDesc = [[diffuseMap shortDescription] copy]; #endif } /* Extract emission and illumination pixmaps from loaders. Ideally, this would be done asynchronously, but that requires dependency management in OOAsyncWorkManager. */ OOTextureDataFormat format; if (_emissionSpec != nil) { OOTextureLoader *emissionMapLoader = [OOTextureLoader loaderWithTextureSpecifier:_emissionSpec extraOptions:0 folder:@"Textures"]; [emissionMapLoader getResult:&_emissionPx format:&format originalWidth:NULL originalHeight:NULL]; #ifndef NDEBUG _emissionDesc = [[emissionMapLoader shortDescription] copy]; #endif } if (_illuminationSpec != nil) { OOTextureLoader *illuminationMapLoader = [OOTextureLoader loaderWithTextureSpecifier:_illuminationSpec extraOptions:0 folder:@"Textures"]; [illuminationMapLoader getResult:&_illuminationPx format:&format originalWidth:NULL originalHeight:NULL]; #ifndef NDEBUG _illuminationDesc = [[illuminationMapLoader shortDescription] copy]; #endif } } } return self; } - (NSString *)constructCacheKey { NSMutableString *cacheKey = [NSMutableString string]; if (_isCombinedMap) [cacheKey appendString:@"combined emission and illumination map;"]; else if (_emissionSpec == nil) [cacheKey appendString:@"illumination map;"]; else if (_illuminationSpec == nil) [cacheKey appendString:@"emission map;"]; else [cacheKey appendString:@"merged emission and illumination map;"]; NSString *emissionDesc = nil; if (_emissionSpec != nil) { emissionDesc = OOTextureCacheKeyForSpecifier(_emissionSpec); [cacheKey appendFormat:@"emission:{%@}", emissionDesc]; if (_emissionColor != nil) [cacheKey appendFormat:@"*%@", [_emissionColor rgbaDescription]]; [cacheKey appendString:@";"]; } NSString *illuminationDesc = nil; if (_isCombinedMap) { illuminationDesc = [NSString stringWithFormat:@"%@:a", emissionDesc]; } else if (_illuminationSpec != nil) { illuminationDesc = OOTextureCacheKeyForSpecifier(_illuminationSpec); } if (illuminationDesc != nil) { [cacheKey appendFormat:@"illumination:{%@}*{%@}", illuminationDesc, [_diffuseMap cacheKey]]; if (_illuminationColor != nil) [cacheKey appendFormat:@"*%@", [_illuminationColor rgbaDescription]]; [cacheKey appendString:@";"]; } return cacheKey; } - (void) dealloc { DESTROY(_emissionSpec); DESTROY(_illuminationSpec); DESTROY(_diffuseMap); OOFreePixMap(&_emissionPx); OOFreePixMap(&_illuminationPx); OOFreePixMap(&_diffusePx); DESTROY(_emissionColor); DESTROY(_illuminationColor); #ifndef NDEBUG DESTROY(_diffuseDesc); DESTROY(_emissionDesc); DESTROY(_illuminationDesc); #endif [super dealloc]; } #ifndef NDEBUG - (NSString *) descriptionComponents { NSMutableString *result = [NSMutableString string]; BOOL haveIllumination = NO; if (_emissionDesc != nil) { [result appendFormat:@"emission map: %@", _emissionDesc]; if (_isCombinedMap) { [result appendString:@".rgb"]; } if (_emissionColor != nil) { [result appendFormat:@" * %@", [_emissionColor rgbaDescription]]; } if (_isCombinedMap) { [result appendFormat:@", illumination map: %@.a", _emissionDesc]; haveIllumination = YES; } } if (_illuminationDesc != nil) { if (_emissionDesc != nil) [result appendString:@", "]; [result appendFormat:@"illumination map: %@", _illuminationDesc]; haveIllumination = YES; } if (haveIllumination) { if (_diffuseDesc != nil) { [result appendFormat:@" * %@", _diffuseDesc]; } if (_illuminationColor != nil) { [result appendFormat:@" * %@", [_illuminationColor rgbaDescription]]; } } return result; } #endif - (uint32_t) textureOptions { return _textureOptions; } - (GLfloat) anisotropy { return _anisotropy; } - (GLfloat) lodBias { return _lodBias; } - (NSString *) cacheKey { return _cacheKey; } - (void) loadTexture { OOPixMap illuminationPx = kOONullPixMap; BOOL haveEmission = NO, haveIllumination = NO, haveDiffuse = NO; #if DUMP_COMBINER static unsigned sTexID = 0; unsigned texID = ++sTexID, dumpCount = 0; #define DUMP(pm, label) OODumpPixMap(pm, [NSString stringWithFormat:@"lightmap %u.%u - %@", texID, ++dumpCount, label]); #else #define DUMP(pm, label) do {} while (0) #endif haveEmission = !OOIsNullPixMap(_emissionPx); if (haveEmission) DUMP(_emissionPx, @"source emission map"); // Extract illumination component if emission_and_illumination_map. if (haveEmission && _isCombinedMap && OOPixMapFormatHasAlpha(_emissionPx.format)) { OOPixMapToRGBA(&_emissionPx); illuminationPx = OODuplicatePixMap(_emissionPx, 0); OOExtractPixMapChannel(&illuminationPx, 3, YES); haveIllumination = YES; DUMP(illuminationPx, @"extracted illumination map"); } // Tint emission map if necessary. if (haveEmission && _emissionColor != nil) { OOPixMapModulateUniform(&_emissionPx, [_emissionColor redComponent], [_emissionColor greenComponent], [_emissionColor blueComponent], 1.0); DUMP(_emissionPx, @"modulated emission map"); } if (!OOIsNullPixMap(_illuminationPx)) { NSAssert(!_isCombinedMap, @"OOCombinedEmissionMapGenerator configured with both illumination map and combined emission/illumination map."); illuminationPx = _illuminationPx; _illuminationPx.pixels = NULL; haveIllumination = YES; DUMP(illuminationPx, @"source illumination map"); } // Tint illumination map if necessary. if (haveIllumination && _illuminationColor != nil) { OOPixMapModulateUniform(&illuminationPx, [_illuminationColor redComponent], [_illuminationColor greenComponent], [_illuminationColor blueComponent], 1.0); DUMP(illuminationPx, @"modulated illumination map"); } // Load diffuse map and combine with illumination map. haveDiffuse = !OOIsNullPixMap(_diffusePx); if (haveDiffuse) DUMP(_diffusePx, @"source diffuse map"); if (haveIllumination && haveDiffuse) { // Modulate illumination with diffuse map. ScaleToMatch(&_diffusePx, &illuminationPx); OOPixMapToRGBA(&_diffusePx); OOPixMapModulatePixMap(&illuminationPx, _diffusePx); DUMP(illuminationPx, @"combined diffuse and illumination map"); } OOFreePixMap(&_diffusePx); if (haveIllumination) { if (haveEmission) { OOPixMapToRGBA(&illuminationPx); OOPixMapAddPixMap(&_emissionPx, illuminationPx); OOFreePixMap(&illuminationPx); DUMP(_emissionPx, @"combined emission and illumination map"); } else if (haveIllumination) { // No explicit emission map -> modulated illumination map is our only emission map. _emissionPx = illuminationPx; haveEmission = YES; illuminationPx.pixels = NULL; } haveIllumination = NO; // Either way, illumination is now baked into emission. } (void)haveEmission; (void)haveIllumination; // Done: emissionPx now contains combined emission map. OOCompactPixMap(&_emissionPx); if (OOIsValidPixMap(_emissionPx)) { _data = _emissionPx.pixels; _width = _emissionPx.width; _height = _emissionPx.height; _rowBytes = _emissionPx.rowBytes; _format = _emissionPx.format; _emissionPx.pixels = NULL; // So it won't be freed by -dealloc. } if (_data == NULL) { OOLogERR(@"texture.combinedEmissionMap.error", @"Unknown error loading %@", self); } } @end static OOColor *ModulateColor(OOColor *a, OOColor *b) { if (a == nil) return b; if (b == nil) return a; OORGBAComponents ac, bc; ac = [a rgbaComponents]; bc = [b rgbaComponents]; ac.r *= bc.r; ac.g *= bc.g; ac.b *= bc.b; ac.a *= bc.a; return [OOColor colorWithRGBAComponents:ac]; } static void ScaleToMatch(OOPixMap *pmA, OOPixMap *pmB) { NSCParameterAssert(pmA != NULL && pmB != NULL && OOIsValidPixMap(*pmA) && OOIsValidPixMap(*pmB)); OOPixMapDimension minWidth = MIN(pmA->width, pmB->width); OOPixMapDimension minHeight = MIN(pmA->height, pmB->height); if (pmA->width != minWidth || pmA->height != minHeight) { *pmA = OOScalePixMap(*pmA, minWidth, minHeight, NO); } if (pmB->width != minWidth || pmB->height != minHeight) { *pmB = OOScalePixMap(*pmB, minWidth, minHeight, NO); } } oolite-1.82/src/Core/Materials/OOConcreteTexture.h000066400000000000000000000044141256642440500221040ustar00rootroot00000000000000/* OOConcreteTexture.h Standard implementation of OOTexture. This is an implementation detail, use OOTexture instead. Copyright (C) 2007-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOTexture.h" #define OOTEXTURE_RELOADABLE 1 @interface OOConcreteTexture: OOTexture { @private #if OOTEXTURE_RELOADABLE NSString *_path; #endif NSString *_key; uint8_t _loaded: 1, _uploaded: 1, #if GL_EXT_texture_rectangle _isRectTexture: 1, #endif #if OO_TEXTURE_CUBE_MAP _isCubeMap: 1, #endif _valid: 1; uint8_t _mipLevels; OOTextureLoader *_loader; void *_bytes; GLuint _textureName; uint32_t _width, _height, _originalWidth, _originalHeight; OOTextureDataFormat _format; uint32_t _options; #if GL_EXT_texture_lod_bias GLfloat _lodBias; #endif #if GL_EXT_texture_filter_anisotropic float _anisotropy; #endif #ifndef NDEBUG NSString *_name; #endif } - (id) initWithLoader:(OOTextureLoader *)loader key:(NSString *)key options:(uint32_t)options anisotropy:(GLfloat)anisotropy lodBias:(GLfloat)lodBias; - (id)initWithPath:(NSString *)path key:(NSString *)key options:(uint32_t)options anisotropy:(float)anisotropy lodBias:(GLfloat)lodBias; @end oolite-1.82/src/Core/Materials/OOConcreteTexture.m000066400000000000000000000347051256642440500221170ustar00rootroot00000000000000/* OOConcreteTexture.m Copyright (C) 2007-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOTextureInternal.h" #import "OOConcreteTexture.h" #import "OOTextureLoader.h" #import "OOCollectionExtractors.h" #import "Universe.h" #import "ResourceManager.h" #import "OOOpenGLExtensionManager.h" #import "OOMacroOpenGL.h" #import "OOCPUInfo.h" #import "OOPixMap.h" #ifndef NDEBUG #import "OOTextureGenerator.h" #endif #if OOLITE_BIG_ENDIAN #define RGBA_IMAGE_TYPE GL_UNSIGNED_INT_8_8_8_8_REV #elif OOLITE_LITTLE_ENDIAN #define RGBA_IMAGE_TYPE GL_UNSIGNED_BYTE #else #error Neither OOLITE_BIG_ENDIAN nor OOLITE_LITTLE_ENDIAN is defined as nonzero! #endif @interface OOConcreteTexture (Private) - (void)setUpTexture; - (void)uploadTexture; - (void)uploadTextureDataWithMipMap:(BOOL)mipMap format:(OOTextureDataFormat)format; #if OO_TEXTURE_CUBE_MAP - (void) uploadTextureCubeMapDataWithMipMap:(BOOL)mipMap format:(OOTextureDataFormat)format; #endif - (GLenum) glTextureTarget; #if OOTEXTURE_RELOADABLE - (BOOL) isReloadable; #endif @end static BOOL DecodeFormat(OOTextureDataFormat format, uint32_t options, GLenum *outFormat, GLenum *outInternalFormat, GLenum *outType); @implementation OOConcreteTexture - (id) initWithLoader:(OOTextureLoader *)loader key:(NSString *)key options:(uint32_t)options anisotropy:(GLfloat)anisotropy lodBias:(GLfloat)lodBias { if (loader == nil) { [self release]; return nil; } self = [super init]; if (EXPECT_NOT(self == nil)) return nil; _loader = [loader retain]; _options = options; #if GL_EXT_texture_filter_anisotropic _anisotropy = OOClamp_0_1_f(anisotropy) * gOOTextureInfo.anisotropyScale; #endif #if GL_EXT_texture_lod_bias _lodBias = lodBias; #endif #ifndef NDEBUG if ([loader isKindOfClass:[OOTextureGenerator class]]) { _name = [[NSString alloc] initWithFormat:@"<%@>", [loader class]]; } #endif _key = [key copy]; [self addToCaches]; return self; } - (id)initWithPath:(NSString *)path key:(NSString *)key options:(uint32_t)options anisotropy:(float)anisotropy lodBias:(GLfloat)lodBias { OOTextureLoader *loader = [OOTextureLoader loaderWithPath:path options:options]; if (loader == nil) { [self release]; return nil; } if ((self = [self initWithLoader:loader key:key options:options anisotropy:anisotropy lodBias:lodBias])) { #if OOTEXTURE_RELOADABLE _path = [path retain]; #endif } return self; } - (void)dealloc { #ifndef NDEBUG OOLog(_trace ? @"texture.allocTrace.dealloc" : @"texture.dealloc", @"Deallocating and uncaching texture %p", self); #endif #if OOTEXTURE_RELOADABLE DESTROY(_path); #endif if (_loaded) { if (_textureName != 0) { OO_ENTER_OPENGL(); OOGL(glDeleteTextures(1, &_textureName)); _textureName = 0; } free(_bytes); _bytes = NULL; } #ifndef OOTEXTURE_NO_CACHE [self removeFromCaches]; [_key autorelease]; _key = nil; #endif DESTROY(_loader); #ifndef NDEBUG DESTROY(_name); #endif [super dealloc]; } - (NSString *) descriptionComponents { NSString *stateDesc = nil; if (_loaded) { if (_valid) { stateDesc = [NSString stringWithFormat:@"%u x %u", _width, _height]; } else { stateDesc = @"LOAD ERROR"; } } else { stateDesc = @"loading"; } return [NSString stringWithFormat:@"%@, %@", _key, stateDesc]; } - (NSString *) shortDescriptionComponents { return _key; } #ifndef NDEBUG - (NSString *) name { if (_name != nil) return _name; #if OOTEXTURE_RELOADABLE NSString *name = [_path lastPathComponent]; #else NSString *name = [[[[self cacheKey] componentsSeparatedByString:@":"] objectAtIndex:0] lastPathComponent]; #endif NSString *channelSuffix = nil; switch (_options & kOOTextureExtractChannelMask) { case kOOTextureExtractChannelR: channelSuffix = @":r"; break; case kOOTextureExtractChannelG: channelSuffix = @":g"; break; case kOOTextureExtractChannelB: channelSuffix = @":b"; break; case kOOTextureExtractChannelA: channelSuffix = @":a"; break; } if (channelSuffix != nil) name = [name stringByAppendingString:channelSuffix]; return name; } #endif - (void)apply { OO_ENTER_OPENGL(); if (EXPECT_NOT(!_loaded)) [self setUpTexture]; else if (EXPECT_NOT(!_uploaded)) [self uploadTexture]; else OOGL(glBindTexture([self glTextureTarget], _textureName)); #if GL_EXT_texture_lod_bias if (gOOTextureInfo.textureLODBiasAvailable) OOGL(glTexEnvf(GL_TEXTURE_FILTER_CONTROL_EXT, GL_TEXTURE_LOD_BIAS_EXT, _lodBias)); #endif } - (void)ensureFinishedLoading { if (!_loaded) [self setUpTexture]; } - (BOOL) isFinishedLoading { return _loaded || [_loader isReady]; } - (NSString *) cacheKey { return _key; } - (NSSize)dimensions { [self ensureFinishedLoading]; return NSMakeSize(_width, _height); } - (NSSize) originalDimensions { [self ensureFinishedLoading]; return NSMakeSize(_originalWidth, _originalHeight); } - (BOOL) isMipMapped { [self ensureFinishedLoading]; return _mipLevels != 0; } - (struct OOPixMap) copyPixMapRepresentation { [self ensureFinishedLoading]; OOPixMap px = kOONullPixMap; if (_bytes != NULL) { // If possible, just copy our existing buffer. px = OOMakePixMap(_bytes, _width, _height, _format, 0, 0); px = OODuplicatePixMap(px, 0); } #if OOTEXTURE_RELOADABLE else { // Otherwise, read it back from OpenGL. OO_ENTER_OPENGL(); GLenum format, internalFormat, type; if (!DecodeFormat(_format, _options, &format, &internalFormat, &type)) { return kOONullPixMap; } if (![self isCubeMap]) { px = OOAllocatePixMap(_width, _height, _format, 0, 0); if (!OOIsValidPixMap(px)) return kOONullPixMap; glGetTexImage(GL_TEXTURE_2D, 0, format, type, px.pixels); } #if OO_TEXTURE_CUBE_MAP else { px = OOAllocatePixMap(_width, _width * 6, _format, 0, 0); if (!OOIsValidPixMap(px)) return kOONullPixMap; uint8_t *pixels = px.pixels; unsigned i; for (i = 0; i < 6; i++) { glGetTexImage(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, format, type, pixels); pixels += OOPixMapBytesPerPixelForFormat(_format) * _width * _width; } } #endif } #endif return px; } - (BOOL) isRectangleTexture { #if GL_EXT_texture_rectangle return _isRectTexture; #else return NO; #endif } - (BOOL) isCubeMap { #if OO_TEXTURE_CUBE_MAP return _isCubeMap; #else return NO; #endif } - (NSSize)texCoordsScale { #if GL_EXT_texture_rectangle if (_loaded) { if (!_isRectTexture) { return NSMakeSize(1.0f, 1.0f); } else { return NSMakeSize(_width, _height); } } else { // Not loaded if (!_options & kOOTextureAllowRectTexture) { return NSMakeSize(1.0f, 1.0f); } else { // Finishing may clear the rectangle texture flag (if the texture turns out to be POT) [self ensureFinishedLoading]; return [self texCoordsScale]; } } #else return NSMakeSize(1.0f, 1.0f); #endif } - (GLint)glTextureName { [self ensureFinishedLoading]; return _textureName; } @end @implementation OOConcreteTexture (Private) - (void)setUpTexture { OOPixMap pm; // This will block until loading is completed, if necessary. if ([_loader getResult:&pm format:&_format originalWidth:&_originalWidth originalHeight:&_originalHeight]) { _bytes = pm.pixels; _width = pm.width; _height = pm.height; #if OO_TEXTURE_CUBE_MAP if (_options & kOOTextureAllowCubeMap && _height == _width * 6 && gOOTextureInfo.cubeMapAvailable) { _isCubeMap = YES; } #endif #if !defined(NDEBUG) && OOTEXTURE_RELOADABLE if (_trace) { static unsigned dumpID = 0; NSString *name = [NSString stringWithFormat:@"tex dump %u \"%@\"", ++dumpID,[self name]]; OOLog(@"texture.trace.dump", @"Dumped traced texture %@ to \'%@.png\'", self, name); OODumpPixMap(pm, name); } #endif [self uploadTexture]; } else { _textureName = 0; _valid = NO; _uploaded = YES; } _loaded = YES; DESTROY(_loader); } - (void) uploadTexture { GLint filter; BOOL mipMap = NO; OO_ENTER_OPENGL(); if (!_uploaded) { GLenum texTarget = [self glTextureTarget]; OOGL(glGenTextures(1, &_textureName)); OOGL(glBindTexture(texTarget, _textureName)); // Select wrap mode GLint clampMode = gOOTextureInfo.clampToEdgeAvailable ? GL_CLAMP_TO_EDGE : GL_CLAMP; GLint wrapS = (_options & kOOTextureRepeatS) ? GL_REPEAT : clampMode; GLint wrapT = (_options & kOOTextureRepeatT) ? GL_REPEAT : clampMode; #if OO_TEXTURE_CUBE_MAP if (texTarget == GL_TEXTURE_CUBE_MAP) { wrapS = wrapT = clampMode; OOGL(glTexParameteri(texTarget, GL_TEXTURE_WRAP_R, clampMode)); } #endif OOGL(glTexParameteri(texTarget, GL_TEXTURE_WRAP_S, wrapS)); OOGL(glTexParameteri(texTarget, GL_TEXTURE_WRAP_T, wrapT)); // Select min filter filter = _options & kOOTextureMinFilterMask; if (filter == kOOTextureMinFilterNearest) filter = GL_NEAREST; else if (filter == kOOTextureMinFilterMipMap) { mipMap = YES; filter = GL_LINEAR_MIPMAP_LINEAR; } else filter = GL_LINEAR; OOGL(glTexParameteri(texTarget, GL_TEXTURE_MIN_FILTER, filter)); #if GL_EXT_texture_filter_anisotropic if (gOOTextureInfo.anisotropyAvailable && mipMap && 1.0 < _anisotropy) { OOGL(glTexParameterf(texTarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, _anisotropy)); } #endif // Select mag filter filter = _options & kOOTextureMagFilterMask; if (filter == kOOTextureMagFilterNearest) filter = GL_NEAREST; else filter = GL_LINEAR; OOGL(glTexParameteri(texTarget, GL_TEXTURE_MAG_FILTER, filter)); // if (gOOTextureInfo.clientStorageAvailable) EnableClientStorage(); if (texTarget == GL_TEXTURE_2D) { [self uploadTextureDataWithMipMap:mipMap format:_format]; OOLog(@"texture.upload", @"Uploaded texture %u (%ux%u pixels, %@)", _textureName, _width, _height, _key); } #if OO_TEXTURE_CUBE_MAP else if (texTarget == GL_TEXTURE_CUBE_MAP) { [self uploadTextureCubeMapDataWithMipMap:mipMap format:_format]; OOLog(@"texture.upload", @"Uploaded cube map texture %u (%ux%ux6 pixels, %@)", _textureName, _width, _width, _key); } #endif else { [NSException raise:NSInternalInconsistencyException format:@"Unhandled texture target 0x%X.", texTarget]; } _valid = YES; _uploaded = YES; #if OOTEXTURE_RELOADABLE if ([self isReloadable]) { free(_bytes); _bytes = NULL; } #endif } } - (void)uploadTextureDataWithMipMap:(BOOL)mipMap format:(OOTextureDataFormat)format { GLenum glFormat = 0, internalFormat = 0, type = 0; unsigned w = _width, h = _height, level = 0; char *bytes = _bytes; uint8_t components = OOTextureComponentsForFormat(format); OO_ENTER_OPENGL(); if (!DecodeFormat(format, _options, &glFormat, &internalFormat, &type)) return; while (0 < w && 0 < h) { OOGL(glTexImage2D(GL_TEXTURE_2D, level++, internalFormat, w, h, 0, glFormat, type, bytes)); if (!mipMap) return; bytes += w * components * h; w >>= 1; h >>= 1; } // Note: we only reach here if (mipMap). _mipLevels = level - 1; OOGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, _mipLevels)); } #if OO_TEXTURE_CUBE_MAP - (void) uploadTextureCubeMapDataWithMipMap:(BOOL)mipMap format:(OOTextureDataFormat)format { OO_ENTER_OPENGL(); GLenum glFormat = 0, internalFormat = 0, type = 0; if (!DecodeFormat(format, _options, &glFormat, &internalFormat, &type)) return; uint8_t components = OOTextureComponentsForFormat(format); // Calculate stride between cube map sides. size_t sideSize = _width * _width * components; if (mipMap) { sideSize = sideSize * 4 / 3; sideSize = (sideSize + 15) & ~15; } unsigned side; for (side = 0; side < 6; side++) { char *bytes = _bytes; bytes += side * sideSize; unsigned w = _width, level = 0; while (0 < w) { OOGL(glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + side, level++, internalFormat, w, w, 0, glFormat, type, bytes)); if (!mipMap) break; bytes += w * w * components; w >>= 1; } } } #endif - (GLenum) glTextureTarget { GLenum texTarget = GL_TEXTURE_2D; #if OO_TEXTURE_CUBE_MAP if (_isCubeMap) { texTarget = GL_TEXTURE_CUBE_MAP; } #endif return texTarget; } - (void) forceRebind { if (_loaded && _uploaded && _valid) { OO_ENTER_OPENGL(); _uploaded = NO; OOGL(glDeleteTextures(1, &_textureName)); _textureName = 0; #if OOTEXTURE_RELOADABLE if ([self isReloadable]) { OOLog(@"texture.reload", @"Reloading texture %@", self); free(_bytes); _bytes = NULL; _loaded = NO; _uploaded = NO; _valid = NO; _loader = [[OOTextureLoader loaderWithPath:_path options:_options] retain]; } #endif } } #if OOTEXTURE_RELOADABLE - (BOOL) isReloadable { return _path != nil; } #endif @end static BOOL DecodeFormat(OOTextureDataFormat format, uint32_t options, GLenum *outFormat, GLenum *outInternalFormat, GLenum *outType) { NSCParameterAssert(outFormat != NULL && outInternalFormat != NULL && outType != NULL); switch (format) { case kOOTextureDataRGBA: *outFormat = GL_RGBA; *outInternalFormat = GL_RGBA; *outType = RGBA_IMAGE_TYPE; return YES; case kOOTextureDataGrayscale: if (options & kOOTextureAlphaMask) { *outFormat = GL_ALPHA; *outInternalFormat = GL_ALPHA8; } else { *outFormat = GL_LUMINANCE; *outInternalFormat = GL_LUMINANCE8; } *outType = GL_UNSIGNED_BYTE; return YES; case kOOTextureDataGrayscaleAlpha: *outFormat = GL_LUMINANCE_ALPHA; *outInternalFormat = GL_LUMINANCE8_ALPHA8; *outType = GL_UNSIGNED_BYTE; return YES; default: OOLog(kOOLogParameterError, @"Unexpected texture format %u.", format); return NO; } } oolite-1.82/src/Core/Materials/OODefaultShaderSynthesizer.h000066400000000000000000000027131256642440500237440ustar00rootroot00000000000000/* OODefaultShaderSynthesizer.h Function to automatically write a shader that implements a given material specification. Copyright © 2011–2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish,0 distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import @class OOMesh; BOOL OOSynthesizeMaterialShader(NSDictionary *materialConfiguration, NSString *materialKey, NSString *entityName, NSString **outVertexShader, NSString **outFragmentShader, NSArray **outTextureSpecs, NSDictionary **outUniformSpecs); oolite-1.82/src/Core/Materials/OODefaultShaderSynthesizer.m000066400000000000000000001412211256642440500237470ustar00rootroot00000000000000/* OODefaultShaderSynthesizer.m Copyright © 2011-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OODefaultShaderSynthesizer.h" #import "OOMesh.h" #import "OOTexture.h" #import "OOColor.h" #import "NSStringOOExtensions.h" #import "OOCollectionExtractors.h" #import "NSDictionaryOOExtensions.h" #import "OOMaterialSpecifier.h" #import "ResourceManager.h" /* * GNUstep 1.20.1 does not support NSIntegerHashCallBacks but uses * NSIntHashCallBacks instead. NSIntHashCallBacks was deprecated in favor of * NSIntegerHashCallBacks in GNUstep versions later than 1.20.1. If we move to * a newer GNUstep version for Oolite the #define below may not be necessary * anymore but for now we need it to be able to build. - Nikos 20120208. */ #if OOLITE_GNUSTEP #define NSIntegerHashCallBacks NSIntHashCallBacks #endif static NSDictionary *CanonicalizeMaterialSpecifier(NSDictionary *spec, NSString *materialKey); static NSString *FormatFloat(double value); @interface OODefaultShaderSynthesizer: NSObject { @private NSDictionary *_configuration; NSString *_materialKey; NSString *_entityName; NSString *_vertexShader; NSString *_fragmentShader; NSMutableArray *_textures; NSMutableDictionary *_uniforms; NSMutableString *_attributes; NSMutableString *_varyings; NSMutableString *_vertexUniforms; NSMutableString *_fragmentUniforms; NSMutableString *_vertexHelpers; NSMutableString *_fragmentHelpers; NSMutableString *_vertexBody; NSMutableString *_fragmentPreTextures; NSMutableString *_fragmentTextureLookups; NSMutableString *_fragmentBody; // _texturesByName: dictionary mapping texture file names to texture specifications. NSMutableDictionary *_texturesByName; // _textureIDs: dictionary mapping texture file names to numerical IDs used to name variables. NSMutableDictionary *_textureIDs; // _sampledTextures: hash of integer texture IDs for which we’ve set up a sample. NSHashTable *_sampledTextures; NSMutableDictionary *_uniformBindingNames; NSUInteger _usesNormalMap: 1, _usesDiffuseTerm: 1, _constZNormal: 1, _haveDiffuseLight: 1, // Completion flags for various generation stages. _completed_writeFinalColorComposite: 1, _completed_writeDiffuseColorTerm: 1, _completed_writeSpecularLighting: 1, _completed_writeLightMaps: 1, _completed_writeDiffuseLighting: 1, _completed_writeDiffuseColorTermIfNeeded: 1, _completed_writeVertexPosition: 1, _completed_writeNormalIfNeeded: 1, // _completedwriteNormal: 1, _completed_writeLightVector: 1, _completed_writeEyeVector: 1, _completed_writeTotalColor: 1, _completed_writeTextureCoordRead: 1, _completed_writeVertexTangentBasis: 1; #ifndef NDEBUG NSHashTable *_stagesInProgress; #endif } - (id) initWithMaterialConfiguration:(NSDictionary *)configuration materialKey:(NSString *)materialKey entityName:(NSString *)name; - (BOOL) run; - (NSString *) vertexShader; - (NSString *) fragmentShader; - (NSArray *) textureSpecifications; - (NSDictionary *) uniformSpecifications; - (NSString *) materialKey; - (NSString *) entityName; - (void) createTemporaries; - (void) destroyTemporaries; - (void) composeVertexShader; - (void) composeFragmentShader; // Write various types of declarations. - (void) appendVariable:(NSString *)name ofType:(NSString *)type withPrefix:(NSString *)prefix to:(NSMutableString *)buffer; - (void) addAttribute:(NSString *)name ofType:(NSString *)type; - (void) addVarying:(NSString *)name ofType:(NSString *)type; - (void) addVertexUniform:(NSString *)name ofType:(NSString *)type; - (void) addFragmentUniform:(NSString *)name ofType:(NSString *)type; // Create or retrieve a uniform variable name for a given binding. - (NSString *) defineBindingUniform:(NSDictionary *)binding ofType:(NSString *)type; - (NSString *) readRGBForTextureSpec:(NSDictionary *)textureSpec mapName:(NSString *)mapName; // Generate a read for an RGB value, or a single channel splatted across RGB. - (NSString *) readOneChannelForTextureSpec:(NSDictionary *)textureSpec mapName:(NSString *)mapName; // Generate a read for a single channel. // Details of texture setup; generally use -read*ForTextureSpec:mapName: instead. - (NSUInteger) textureIDForSpec:(NSDictionary *)textureSpec; - (void) setUpOneTexture:(NSDictionary *)textureSpec; - (void) getSampleName:(NSString **)outSampleName andSwizzleOp:(NSString **)outSwizzleOp forTextureSpec:(NSDictionary *)textureSpec; /* Stages. These should only be called through the REQUIRE_STAGE macro to avoid duplicated code and ensure data depedencies are met. */ /* writeTextureCoordRead Generate vec2 texCoords. */ - (void) writeTextureCoordRead; /* writeDiffuseColorTermIfNeeded Generates and populates the fragment shader value vec3 diffuseColor, unless the diffuse term is black. If a diffuseColor is generated, _usesDiffuseTerm is set. The value will be const if possible. See also: writeDiffuseColorTerm. */ - (void) writeDiffuseColorTermIfNeeded; /* writeDiffuseColorTerm Generates vec3 diffuseColor unconditionally – that is, even if the diffuse term is black. See also: writeDiffuseColorTermIfNeeded. */ - (void) writeDiffuseColorTerm; /* writeDiffuseLighting Generate the fragment variable vec3 diffuseLight and add Lambertian and ambient terms to it. */ - (void) writeDiffuseLighting; /* writeLightVector Generate the fragment variable vec3 lightVector (unit vector) for temporary lighting. Calling this if lighting mode is kLightingUniform will cause an exception. */ - (void) writeLightVector; /* writeEyeVector Generate vec3 lightVector, the normalized direction from the fragment to the light source. */ - (void) writeEyeVector; /* writeVertexTangentBasis Generates tangent space basis matrix (TBN) in vertex shader, if in tangent- space lighting mode. If not, an exeception is raised. */ - (void) writeVertexTangentBasis; /* writeNormalIfNeeded Writes fragment variable vec3 normal if necessary. Otherwise, it sets _constZNormal, indicating that the normal is always (0, 0, 1). See also: writeNormal. */ - (void) writeNormalIfNeeded; /* writeNormal Generates vec3 normal unconditionally – if _constZNormal is set, normal will be const vec3 normal = vec3 (0.0, 0.0, 1.0). */ - (void) writeNormal; /* writeSpecularLighting Calculate specular writing and add it to totalColor. */ - (void) writeSpecularLighting; /* writeLightMaps Add emission and illumination maps to totalColor. */ - (void) writeLightMaps; /* writeVertexPosition Calculate vertex position and write it to gl_Position. */ - (void) writeVertexPosition; /* writeTotalColor Generate vec3 totalColor, the accumulator for output colour values. */ - (void) writeTotalColor; /* writeFinalColorComposite This stage writes the final fragment shader. It also pulls in other stages through dependencies. */ - (void) writeFinalColorComposite; /* REQUIRE_STAGE(): pull in the required stage. A stage must have a zero-parameter method and a matching _completed_stage instance variable. In debug/testrelease builds, this dispatches through performStage: which checks for recursive calls. */ #ifndef NDEBUG #define REQUIRE_STAGE(NAME) if (!_completed_##NAME) { [self performStage:@selector(NAME)]; _completed_##NAME = YES; } - (void) performStage:(SEL)stage; #else #define REQUIRE_STAGE(NAME) if (!_completed_##NAME) { [self NAME]; _completed_##NAME = YES; } #endif @end static NSString *GetExtractMode(NSDictionary *textureSpecifier); BOOL OOSynthesizeMaterialShader(NSDictionary *configuration, NSString *materialKey, NSString *entityName, NSString **outVertexShader, NSString **outFragmentShader, NSArray **outTextureSpecs, NSDictionary **outUniformSpecs) { NSCParameterAssert(configuration != nil && outVertexShader != NULL && outFragmentShader != NULL && outTextureSpecs != NULL && outUniformSpecs != NULL); NSAutoreleasePool *pool = [NSAutoreleasePool new]; OODefaultShaderSynthesizer *synthesizer = [[OODefaultShaderSynthesizer alloc] initWithMaterialConfiguration:configuration materialKey:materialKey entityName:entityName]; [synthesizer autorelease]; BOOL OK = [synthesizer run]; if (OK) { *outVertexShader = [[synthesizer vertexShader] retain]; *outFragmentShader = [[synthesizer fragmentShader] retain]; *outTextureSpecs = [[synthesizer textureSpecifications] retain]; *outUniformSpecs = [[synthesizer uniformSpecifications] retain]; } else { *outVertexShader = nil; *outFragmentShader = nil; *outTextureSpecs = nil; *outUniformSpecs = nil; } [pool release]; [*outVertexShader autorelease]; [*outFragmentShader autorelease]; [*outTextureSpecs autorelease]; [*outUniformSpecs autorelease]; return YES; } @implementation OODefaultShaderSynthesizer - (id) initWithMaterialConfiguration:(NSDictionary *)configuration materialKey:(NSString *)materialKey entityName:(NSString *)name { if ((self = [super init])) { _configuration = [CanonicalizeMaterialSpecifier(configuration, materialKey) retain]; _materialKey = [materialKey copy]; _entityName = [_entityName copy]; } return self; } - (void) dealloc { [self destroyTemporaries]; DESTROY(_configuration); DESTROY(_materialKey); DESTROY(_entityName); DESTROY(_vertexShader); DESTROY(_fragmentShader); DESTROY(_textures); [super dealloc]; } - (NSString *) vertexShader { return _vertexShader; } - (NSString *) fragmentShader { return _fragmentShader; } - (NSArray *) textureSpecifications { #ifndef NDEBUG return [NSArray arrayWithArray:_textures]; #else return _textures; #endif } - (NSDictionary *) uniformSpecifications { #ifndef NDEBUG return [NSDictionary dictionaryWithDictionary:_uniforms]; #else return _uniforms; #endif } - (BOOL) run { [self createTemporaries]; _uniforms = [[NSMutableDictionary alloc] init]; [_vertexBody appendString:@"void main(void)\n{\n"]; [_fragmentPreTextures appendString:@"void main(void)\n{\n"]; @try { REQUIRE_STAGE(writeFinalColorComposite); [self composeVertexShader]; [self composeFragmentShader]; } @catch (NSException *exception) { // Error should have been reported already. return NO; } @finally { [self destroyTemporaries]; } return YES; } - (NSString *) materialKey { return _materialKey; } - (NSString *) entityName { return _entityName; } // MARK: - Utilities static void AppendIfNotEmpty(NSMutableString *buffer, NSString *segment, NSString *name) { if ([segment length] > 0) { if ([buffer length] > 0) [buffer appendString:@"\n\n"]; if ([name length] > 0) [buffer appendFormat:@"// %@\n", name]; [buffer appendString:segment]; } } static NSString *GetExtractMode(NSDictionary *textureSpecifier) { NSString *result = nil; NSString *rawMode = [textureSpecifier oo_stringForKey:kOOTextureSpecifierSwizzleKey]; if (rawMode != nil) { NSUInteger length = [rawMode length]; if (1 <= length && length <= 4) { static NSCharacterSet *nonRGBACharset = nil; if (nonRGBACharset == nil) { nonRGBACharset = [[[NSCharacterSet characterSetWithCharactersInString:@"rgba"] invertedSet] retain]; } if ([rawMode rangeOfCharacterFromSet:nonRGBACharset].location == NSNotFound) { result = rawMode; } } } return result; } - (void) appendVariable:(NSString *)name ofType:(NSString *)type withPrefix:(NSString *)prefix to:(NSMutableString *)buffer { NSUInteger typeDeclLength = [prefix length] + [type length] + 1; NSUInteger padding = (typeDeclLength < 20) ? (23 - typeDeclLength) / 4 : 1; [buffer appendFormat:@"%@ %@%@%@;\n", prefix, type, OOTabString(padding), name]; } - (void) addAttribute:(NSString *)name ofType:(NSString *)type { [self appendVariable:name ofType:type withPrefix:@"attribute" to:_attributes]; } - (void) addVarying:(NSString *)name ofType:(NSString *)type { [self appendVariable:name ofType:type withPrefix:@"varying" to:_varyings]; } - (void) addVertexUniform:(NSString *)name ofType:(NSString *)type { [self appendVariable:name ofType:type withPrefix:@"uniform" to:_vertexUniforms]; } - (void) addFragmentUniform:(NSString *)name ofType:(NSString *)type { [self appendVariable:name ofType:type withPrefix:@"uniform" to:_fragmentUniforms]; } - (NSString *) defineBindingUniform:(NSDictionary *)binding ofType:(NSString *)type { NSString *name = [binding oo_stringForKey:@"binding"]; NSParameterAssert([name length] > 0); NSMutableDictionary *bindingSpec = [[binding mutableCopy] autorelease]; if ([bindingSpec oo_stringForKey:@"type"] == nil) [bindingSpec setObject:@"binding" forKey:@"type"]; // Use existing uniform if one is defined. NSString *uniformName = [_uniformBindingNames objectForKey:bindingSpec]; if (uniformName != nil) return uniformName; // Capitalize first char of name, and prepend u. unichar firstChar = toupper([name characterAtIndex:0]); NSString *baseName = [NSString stringWithFormat:@"u%C%@", firstChar, [name substringFromIndex:1]]; // Ensure name is unique. name = baseName; unsigned idx = 1; while ([_uniforms objectForKey:name] != nil) { name = [NSString stringWithFormat:@"%@%u", baseName, ++idx]; } [self addFragmentUniform:name ofType:type]; [_uniforms setObject:bindingSpec forKey:name]; [_uniformBindingNames setObject:name forKey:bindingSpec]; return name; } - (void) composeVertexShader { while ([_vertexBody hasSuffix:@"\t\n"]) { [_vertexBody deleteCharactersInRange:(NSRange){ [_vertexBody length] - 2, 2 }]; } [_vertexBody appendString:@"}"]; NSMutableString *vertexShader = [NSMutableString string]; AppendIfNotEmpty(vertexShader, _attributes, @"Attributes"); AppendIfNotEmpty(vertexShader, _vertexUniforms, @"Uniforms"); AppendIfNotEmpty(vertexShader, _varyings, @"Varyings"); AppendIfNotEmpty(vertexShader, _vertexHelpers, @"Helper functions"); AppendIfNotEmpty(vertexShader, _vertexBody, nil); #ifndef NDEBUG _vertexShader = [vertexShader copy]; #else _vertexShader = [vertexShader retain]; #endif } - (void) composeFragmentShader { while ([_fragmentBody hasSuffix:@"\t\n"]) { [_fragmentBody deleteCharactersInRange:(NSRange){ [_fragmentBody length] - 2, 2 }]; } NSMutableString *fragmentShader = [NSMutableString string]; AppendIfNotEmpty(fragmentShader, _fragmentUniforms, @"Uniforms"); AppendIfNotEmpty(fragmentShader, _varyings, @"Varyings"); AppendIfNotEmpty(fragmentShader, _fragmentHelpers, @"Helper functions"); AppendIfNotEmpty(fragmentShader, _fragmentPreTextures, nil); if ([_fragmentTextureLookups length] > 0) { [fragmentShader appendString:@"\t\n\t// Texture lookups\n"]; [fragmentShader appendString:_fragmentTextureLookups]; } [fragmentShader appendString:@"\t\n"]; [fragmentShader appendString:_fragmentBody]; [fragmentShader appendString:@"}"]; #ifndef NDEBUG _fragmentShader = [fragmentShader copy]; #else _fragmentShader = [fragmentShader retain]; #endif } /* Build a key for a texture specifier, taking all texture configuration options into account and ignoring the other stuff that might be there. FIXME: efficiency and stuff. */ static NSString *KeyFromTextureParameters(NSString *name, OOTextureFlags options, float anisotropy, float lodBias) { #ifndef NDEBUG options = OOApplyTextureOptionDefaults(options); #endif // Extraction modes are ignored in synthesized shaders, since we use swizzling instead. options &= ~kOOTextureExtractChannelMask; return [NSString stringWithFormat:@"%@:%X:%g:%g", name, options, anisotropy, lodBias]; } static NSString *KeyFromTextureSpec(NSDictionary *spec) { NSString *texName = nil; OOTextureFlags texOptions; float anisotropy, lodBias; if (!OOInterpretTextureSpecifier(spec, &texName, &texOptions, &anisotropy, &lodBias, YES)) { // OOInterpretTextureSpecifier() will have logged something. [NSException raise:NSGenericException format:@"Invalid texture specifier"]; } return KeyFromTextureParameters(texName, texOptions, anisotropy, lodBias); } - (NSUInteger) assignIDForTexture:(NSDictionary *)spec { NSParameterAssert(spec != nil); // extract_channel doesn't affect uniqueness, and we don't want OOTexture to do actual extraction. if ([spec objectForKey:kOOTextureSpecifierSwizzleKey] != nil) { spec = [spec dictionaryByRemovingObjectForKey:kOOTextureSpecifierSwizzleKey]; } NSString *texName = nil; OOTextureFlags texOptions; float anisotropy, lodBias; if (!OOInterpretTextureSpecifier(spec, &texName, &texOptions, &anisotropy, &lodBias, YES)) { // OOInterpretTextureSpecifier() will have logged something. [NSException raise:NSGenericException format:@"Invalid texture specifier"]; } if (texOptions & kOOTextureAllowCubeMap) { // cube_map = true; fail regardless of whether actual texture qualifies. OOLogERR(@"material.synthesis.error.cubeMap", @"The material \"%@\" of \"%@\" specifies a cube map texture, but doesn't have custom shaders. Cube map textures are not supported with the default shaders.", [self materialKey], [self entityName]); [NSException raise:NSGenericException format:@"Invalid material"]; } NSString *key = KeyFromTextureParameters(texName, texOptions, anisotropy, lodBias); NSUInteger texID; NSObject *existing = [_texturesByName objectForKey:key]; if (existing == nil) { texID = [_texturesByName count]; NSNumber *texIDObj = [NSNumber numberWithUnsignedInteger:texID]; NSString *texUniform = [NSString stringWithFormat:@"uTexture%lu", texID]; #ifndef NDEBUG BOOL useInternalFormat = NO; #else BOOL useInternalFormat = YES; #endif [_textures addObject:OOMakeTextureSpecifier(texName, texOptions, anisotropy, lodBias, useInternalFormat)]; [_texturesByName setObject:spec forKey:key]; [_textureIDs setObject:texIDObj forKey:key]; [_uniforms setObject:[NSDictionary dictionaryWithObjectsAndKeys:@"texture", @"type", texIDObj, @"value", nil] forKey:texUniform]; [self addFragmentUniform:texUniform ofType:@"sampler2D"]; } else { texID = [_textureIDs oo_unsignedIntegerForKey:texName]; } return texID; } - (NSUInteger) textureIDForSpec:(NSDictionary *)textureSpec { return [_textureIDs oo_unsignedIntegerForKey:KeyFromTextureSpec(textureSpec)]; } - (void) setUpOneTexture:(NSDictionary *)textureSpec { if (textureSpec == nil) return; REQUIRE_STAGE(writeTextureCoordRead); NSUInteger texID = [self assignIDForTexture:textureSpec]; if ((NSUInteger)NSHashGet(_sampledTextures, (const void *)(texID + 1)) == 0) { NSHashInsertKnownAbsent(_sampledTextures, (const void *)(texID + 1)); [_fragmentTextureLookups appendFormat:@"\tvec4 tex%luSample = texture2D(uTexture%lu, texCoords); // %@\n", texID, texID, [textureSpec oo_stringForKey:kOOTextureSpecifierNameKey]]; } } - (void) getSampleName:(NSString **)outSampleName andSwizzleOp:(NSString **)outSwizzleOp forTextureSpec:(NSDictionary *)textureSpec { NSParameterAssert(outSampleName != NULL && outSwizzleOp != NULL && textureSpec != nil); [self setUpOneTexture:textureSpec]; NSUInteger texID = [self textureIDForSpec:textureSpec]; *outSampleName = [NSString stringWithFormat:@"tex%luSample", texID]; *outSwizzleOp = GetExtractMode(textureSpec); } - (NSString *) readRGBForTextureSpec:(NSDictionary *)textureSpec mapName:(NSString *)mapName { NSString *sample, *swizzle; [self getSampleName:&sample andSwizzleOp:&swizzle forTextureSpec:textureSpec]; if (swizzle == nil) { return [sample stringByAppendingString:@".rgb"]; } NSUInteger channelCount = [swizzle length]; if (channelCount == 1) { return [NSString stringWithFormat:@"%@.%@%@%@", sample, swizzle, swizzle, swizzle]; } else if (channelCount == 3) { return [NSString stringWithFormat:@"%@.%@", sample, swizzle]; } OOLogWARN(@"material.synthesis.warning.extractionMismatch", @"The %@ map for material \"%@\" of \"%@\" specifies %lu channels to extract, but only %@ may be used.", mapName, [self materialKey], [self entityName], channelCount, @"1 or 3"); return nil; } - (NSString *) readOneChannelForTextureSpec:(NSDictionary *)textureSpec mapName:(NSString *)mapName { NSString *sample, *swizzle; [self getSampleName:&sample andSwizzleOp:&swizzle forTextureSpec:textureSpec]; if (swizzle == nil) { return [sample stringByAppendingString:@".r"]; } NSUInteger channelCount = [swizzle length]; if (channelCount == 1) { return [NSString stringWithFormat:@"%@.%@", sample, swizzle]; } OOLogWARN(@"material.synthesis.warning.extractionMismatch", @"The %@ map for material \"%@\" of \"%@\" specifies %lu channels to extract, but only %@ may be used.", mapName, [self materialKey], [self entityName], channelCount, @"1"); return nil; } #ifndef NDEBUG - (void) performStage:(SEL)stage { // Ensure that we aren’t recursing. if (NSHashGet(_stagesInProgress, stage) != NULL) { OOLogERR(@"material.synthesis.error.recursion", @"Shader synthesis recursion for stage %@.", NSStringFromSelector(stage)); [NSException raise:NSInternalInconsistencyException format:@"stage recursion"]; } NSHashInsertKnownAbsent(_stagesInProgress, stage); [self performSelector:stage]; NSHashRemove(_stagesInProgress, stage); } #endif - (void) createTemporaries { _attributes = [[NSMutableString alloc] init]; _varyings = [[NSMutableString alloc] init]; _vertexUniforms = [[NSMutableString alloc] init]; _fragmentUniforms = [[NSMutableString alloc] init]; _vertexHelpers = [[NSMutableString alloc] init]; _fragmentHelpers = [[NSMutableString alloc] init]; _vertexBody = [[NSMutableString alloc] init]; _fragmentPreTextures = [[NSMutableString alloc] init]; _fragmentTextureLookups = [[NSMutableString alloc] init]; _fragmentBody = [[NSMutableString alloc] init]; _textures = [[NSMutableArray alloc] init]; _texturesByName = [[NSMutableDictionary alloc] init]; _textureIDs = [[NSMutableDictionary alloc] init]; _sampledTextures = NSCreateHashTable(NSIntegerHashCallBacks, 0); _uniformBindingNames = [[NSMutableDictionary alloc] init]; #ifndef NDEBUG _stagesInProgress = NSCreateHashTable(NSNonOwnedPointerHashCallBacks, 0); #endif } - (void) destroyTemporaries { DESTROY(_attributes); DESTROY(_varyings); DESTROY(_vertexUniforms); DESTROY(_fragmentUniforms); DESTROY(_vertexHelpers); DESTROY(_fragmentHelpers); DESTROY(_vertexBody); DESTROY(_fragmentPreTextures); DESTROY(_fragmentTextureLookups); DESTROY(_fragmentBody); DESTROY(_texturesByName); DESTROY(_textureIDs); if (_sampledTextures != NULL) { NSFreeHashTable(_sampledTextures); _sampledTextures = NULL; } DESTROY(_uniformBindingNames); #ifndef NDEBUG if (_stagesInProgress != NULL) { NSFreeHashTable(_stagesInProgress); _stagesInProgress = NULL; } #endif } // MARK: - Synthesis stages - (void) writeTextureCoordRead { [self addVarying:@"vTexCoords" ofType:@"vec2"]; [_vertexBody appendString:@"\tvTexCoords = gl_MultiTexCoord0.st;\n\t\n"]; BOOL haveTexCoords = NO; NSDictionary *parallaxMap = [_configuration oo_parallaxMapSpecifier]; if (parallaxMap != nil) { float parallaxScale = [_configuration oo_parallaxScale]; if (parallaxScale != 0.0f) { /* We can’t call -getSampleName:... here because the standard texture loading mechanism has to occur after determining texture coordinates (duh). */ NSString *swizzle = GetExtractMode(parallaxMap) ?: (NSString *)@"a"; NSUInteger channelCount = [swizzle length]; if (channelCount == 1) { haveTexCoords = YES; REQUIRE_STAGE(writeEyeVector); [_fragmentPreTextures appendString:@"\t// Parallax mapping\n"]; NSUInteger texID = [self assignIDForTexture:parallaxMap]; [_fragmentPreTextures appendFormat:@"\tfloat parallax = texture2D(uTexture%lu, vTexCoords).%@;\n", texID, swizzle]; if (parallaxScale != 1.0f) { [_fragmentPreTextures appendFormat:@"\tparallax *= %@; // Parallax scale\n", FormatFloat(parallaxScale)]; } float parallaxBias = [_configuration oo_parallaxBias]; if (parallaxBias != 0.0) { [_fragmentPreTextures appendFormat:@"\tparallax += %@; // Parallax bias\n", FormatFloat(parallaxBias)]; } [_fragmentPreTextures appendString:@"\tvec2 texCoords = vTexCoords - parallax * eyeVector.xy * vec2(1.0, -1.0);\n"]; } else { OOLogWARN(@"material.synthesis.warning.extractionMismatch", @"The %@ map for material \"%@\" of \"%@\" specifies %lu channels to extract, but only %@ may be used.", @"parallax", [self materialKey], [self entityName], channelCount, @"1"); } } } if (!haveTexCoords) { [_fragmentPreTextures appendString:@"\tvec2 texCoords = vTexCoords;\n"]; } } - (void) writeDiffuseColorTermIfNeeded { NSDictionary *diffuseMap = [_configuration oo_diffuseMapSpecifierWithDefaultName:[self materialKey]]; OOColor *diffuseColor = [_configuration oo_diffuseColor] ?: [OOColor whiteColor]; if ([diffuseColor isBlack]) return; _usesDiffuseTerm = YES; BOOL haveDiffuseColor = NO; if (diffuseMap != nil) { NSString *readInstr = [self readRGBForTextureSpec:diffuseMap mapName:@"diffuse"]; if (EXPECT_NOT(readInstr == nil)) { [_fragmentBody appendString:@"\t// INVALID EXTRACTION KEY\n\t\n"]; } else { [_fragmentBody appendFormat:@"\tvec3 diffuseColor = %@;\n", readInstr]; haveDiffuseColor = YES; } } if (!haveDiffuseColor || ![diffuseColor isWhite]) { float rgba[4]; [diffuseColor getRed:&rgba[0] green:&rgba[1] blue:&rgba[2] alpha:&rgba[3]]; NSString *format = nil; if (haveDiffuseColor) { format = @"\tdiffuseColor *= vec3(%@, %@, %@);\n"; } else { format = @"\tconst vec3 diffuseColor = vec3(%@, %@, %@);\n"; haveDiffuseColor = YES; } [_fragmentBody appendFormat:format, FormatFloat(rgba[0]), FormatFloat(rgba[1]), FormatFloat(rgba[2])]; } (void) haveDiffuseColor; [_fragmentBody appendString:@"\t\n"]; } - (void) writeDiffuseColorTerm { REQUIRE_STAGE(writeDiffuseColorTermIfNeeded); if (!_usesDiffuseTerm) { [_fragmentBody appendString:@"\tconst vec3 diffuseColor = vec3(0.0); // Diffuse colour is black.\n\t\n"]; } } - (void) writeDiffuseLighting { REQUIRE_STAGE(writeDiffuseColorTermIfNeeded); if (!_usesDiffuseTerm) return; REQUIRE_STAGE(writeTotalColor); REQUIRE_STAGE(writeVertexPosition); REQUIRE_STAGE(writeNormalIfNeeded); REQUIRE_STAGE(writeLightVector); // FIXME: currently uncoloured diffuse and ambient lighting. NSString *normalDotLight = _constZNormal ? @"lightVector.z" : @"dot(normal, lightVector)"; [_fragmentBody appendFormat: @"\t// Diffuse (Lambertian) and ambient lighting\n" "\tvec3 diffuseLight = (gl_LightSource[1].diffuse * max(0.0, %@) + gl_LightModel.ambient).rgb;\n\t\n", normalDotLight]; _haveDiffuseLight = YES; } - (void) writeLightVector { REQUIRE_STAGE(writeVertexPosition); REQUIRE_STAGE(writeNormalIfNeeded); [self addVarying:@"vLightVector" ofType:@"vec3"]; [_vertexBody appendString: @"\tvec3 lightVector = gl_LightSource[1].position.xyz;\n" "\tvLightVector = lightVector * TBN;\n\t\n"]; [_fragmentBody appendFormat:@"\tvec3 lightVector = normalize(vLightVector);\n\t\n"]; } - (void) writeEyeVector { REQUIRE_STAGE(writeVertexPosition); REQUIRE_STAGE(writeVertexTangentBasis); [self addVarying:@"vEyeVector" ofType:@"vec3"]; [_vertexBody appendString:@"\tvEyeVector = position.xyz * TBN;\n\t\n"]; [_fragmentPreTextures appendString:@"\tvec3 eyeVector = normalize(vEyeVector);\n\t\n"]; } - (void) writeVertexTangentBasis { [self addAttribute:@"tangent" ofType:@"vec3"]; [_vertexBody appendString: @"\t// Build tangent space basis\n" "\tvec3 n = gl_NormalMatrix * gl_Normal;\n" "\tvec3 t = gl_NormalMatrix * tangent;\n" "\tvec3 b = cross(n, t);\n" "\tmat3 TBN = mat3(t, b, n);\n\t\n"]; } - (void) writeNormalIfNeeded { REQUIRE_STAGE(writeVertexPosition); REQUIRE_STAGE(writeVertexTangentBasis); NSDictionary *normalMap = [_configuration oo_normalMapSpecifier]; if (normalMap == nil) { // FIXME: this stuff should be handled in OOMaterialSpecifier.m when synthesizer takes over the world. -- Ahruman 2012-02-08 normalMap = [_configuration oo_normalAndParallaxMapSpecifier]; } if (normalMap != nil) { NSString *sample, *swizzle; [self getSampleName:&sample andSwizzleOp:&swizzle forTextureSpec:normalMap]; if (swizzle == nil) swizzle = @"rgb"; if ([swizzle length] == 3) { [_fragmentBody appendFormat:@"\tvec3 normal = normalize(%@.%@ - 0.5);\n\t\n", sample, swizzle]; _usesNormalMap = YES; return; } else { OOLogWARN(@"material.synthesis.warning.extractionMismatch", @"The %@ map for material \"%@\" of \"%@\" specifies %lu channels to extract, but only %@ may be used.", @"normal", [self materialKey], [self entityName], [swizzle length], @"3"); } } _constZNormal = YES; } - (void) writeNormal { REQUIRE_STAGE(writeNormalIfNeeded); if (_constZNormal) { [_fragmentBody appendString:@"\tconst vec3 normal = vec3(0.0, 0.0, 1.0);\n\t\n"]; } } - (void) writeSpecularLighting { float specularExponent = [_configuration oo_specularExponent]; if (specularExponent <= 0) return; NSDictionary *specularColorMap = [_configuration oo_specularColorMapSpecifier]; NSDictionary *specularExponentMap = [_configuration oo_specularExponentMapSpecifier]; float scaleFactor = 1.0f; if (specularColorMap) { scaleFactor = [specularColorMap oo_doubleForKey:kOOTextureSpecifierScaleFactorKey defaultValue:1.0f]; } OOColor *specularColor = nil; if (specularColorMap == nil) { specularColor = [_configuration oo_specularColor]; } else { specularColor = [_configuration oo_specularModulateColor]; } if ([specularColor isBlack]) return; BOOL modulateWithDiffuse = [specularColorMap oo_boolForKey:kOOTextureSpecifierSelfColorKey]; REQUIRE_STAGE(writeTotalColor); REQUIRE_STAGE(writeNormalIfNeeded); REQUIRE_STAGE(writeEyeVector); REQUIRE_STAGE(writeLightVector); if (modulateWithDiffuse) { REQUIRE_STAGE(writeDiffuseColorTerm); } [_fragmentBody appendString:@"\t// Specular (Blinn-Phong) lighting\n"]; BOOL haveSpecularColor = NO; if (specularColorMap != nil) { NSString *readInstr = [self readRGBForTextureSpec:specularColorMap mapName:@"specular colour"]; if (EXPECT_NOT(readInstr == nil)) { [_fragmentBody appendString:@"\t// INVALID EXTRACTION KEY\n\t\n"]; return; } [_fragmentBody appendFormat:@"\tvec3 specularColor = %@;\n", readInstr]; haveSpecularColor = YES; } if (!haveSpecularColor || ![specularColor isWhite]) { float rgba[4]; [specularColor getRed:&rgba[0] green:&rgba[1] blue:&rgba[2] alpha:&rgba[3]]; NSString *comment = (scaleFactor == 1.0f) ? @"Constant colour" : @"Constant colour and scale factor"; // Handle scale factor, colour, and colour alpha scaling as one multiply. scaleFactor *= rgba[3]; rgba[0] *= scaleFactor; rgba[1] *= scaleFactor; rgba[2] *= scaleFactor; // Avoid reapplying scaleFactor below. scaleFactor = 1.0; NSString *format = nil; if (haveSpecularColor) { format = @"\tspecularColor *= vec3(%@, %@, %@); // %@\n"; } else { format = @"\tvec3 specularColor = vec3(%@, %@, %@); // %@\n"; haveSpecularColor = YES; } [_fragmentBody appendFormat:format, FormatFloat(rgba[0]), FormatFloat(rgba[1]), FormatFloat(rgba[2]), comment]; } // Handle scale_factor if no constant colour. if (haveSpecularColor && scaleFactor != 1.0f) { [_fragmentBody appendFormat:@"\tspecularColor *= %@; // Scale factor\n", FormatFloat(scaleFactor)]; } // Handle self_color. if (modulateWithDiffuse) { [_fragmentBody appendString:@"\tspecularColor *= diffuseColor; // Self-colouring\n"]; } // Specular exponent. BOOL haveSpecularExponent = NO; if (specularExponentMap != nil) { NSString *readInstr = [self readOneChannelForTextureSpec:specularExponentMap mapName:@"specular exponent"]; if (EXPECT_NOT(readInstr == nil)) { [_fragmentBody appendString:@"\t// INVALID EXTRACTION KEY\n\t\n"]; return; } [_fragmentBody appendFormat:@"\tfloat specularExponent = %@ * %.1f;\n", readInstr, specularExponent]; haveSpecularExponent = YES; } if (!haveSpecularExponent) { [_fragmentBody appendFormat:@"\tconst float specularExponent = %.1f;\n", specularExponent]; } if (_usesNormalMap) { [_fragmentBody appendFormat:@"\tvec3 reflection = reflect(lightVector, normal);\n"]; } else { /* reflect(I, N) is defined as I - 2 * dot(N, I) * N If N is (0,0,1), this becomes (I.x,I.y,-I.z). */ [_fragmentBody appendFormat:@"\tvec3 reflection = vec3(lightVector.x, lightVector.y, -lightVector.z); // Equivalent to reflect(lightVector, normal) since normal is known to be (0, 0, 1) in tangent space.\n"]; } [_fragmentBody appendFormat: @"\tfloat specIntensity = dot(reflection, eyeVector);\n" "\tspecIntensity = pow(max(0.0, specIntensity), specularExponent);\n" "\ttotalColor += specIntensity * specularColor * gl_LightSource[1].specular.rgb;\n\t\n"]; } - (void) writeLightMaps { NSArray *lightMaps = [_configuration oo_arrayForKey:kOOMaterialLightMapsName]; NSUInteger idx, count = [lightMaps count]; if (count == 0) return; REQUIRE_STAGE(writeTotalColor); // Check if we need the diffuse colour term. for (idx = 0; idx < count; idx++) { NSDictionary *lightMapSpec = [lightMaps oo_dictionaryAtIndex:idx]; if ([lightMapSpec oo_boolForKey:kOOTextureSpecifierIlluminationModeKey]) { REQUIRE_STAGE(writeDiffuseColorTerm); REQUIRE_STAGE(writeDiffuseLighting); break; } } [_fragmentBody appendString:@"\tvec3 lightMapColor;\n"]; for (idx = 0; idx < count; idx++) { NSDictionary *lightMapSpec = [lightMaps oo_dictionaryAtIndex:idx]; NSDictionary *textureSpec = OOTextureSpecFromObject(lightMapSpec, nil); NSArray *color = [lightMapSpec oo_arrayForKey:kOOTextureSpecifierModulateColorKey]; float rgba[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; BOOL isIllumination = [lightMapSpec oo_boolForKey:kOOTextureSpecifierIlluminationModeKey]; if (EXPECT_NOT(color == nil && textureSpec == nil)) { [_fragmentBody appendString:@"\t// Light map with neither colour nor texture has no effect.\n\t\n"]; continue; } if (color != nil) { NSUInteger idx, count = [color count]; if (count > 4) count = 4; for (idx = 0; idx < count; idx++) { rgba[idx] = [color oo_doubleAtIndex:idx]; } rgba[0] *= rgba[3]; rgba[1] *= rgba[3]; rgba[2] *= rgba[3]; } if (EXPECT_NOT((rgba[0] == 0.0f && rgba[1] == 0.0f && rgba[2] == 0.0f) || (!_usesDiffuseTerm && isIllumination))) { [_fragmentBody appendString:@"\t// Light map tinted black has no effect.\n\t\n"]; continue; } if (textureSpec != nil) { NSString *readInstr = [self readRGBForTextureSpec:textureSpec mapName:@"light"]; if (EXPECT_NOT(readInstr == nil)) { [_fragmentBody appendString:@"\t// INVALID EXTRACTION KEY\n\n"]; continue; } [_fragmentBody appendFormat:@"\tlightMapColor = %@;\n", readInstr]; if (rgba[0] != 1.0f || rgba[1] != 1.0f || rgba[2] != 1.0f) { [_fragmentBody appendFormat:@"\tlightMapColor *= vec3(%@, %@, %@);\n", FormatFloat(rgba[0]), FormatFloat(rgba[1]), FormatFloat(rgba[2])]; } } else { [_fragmentBody appendFormat:@"\tlightMapColor = vec3(%@, %@, %@);\n", FormatFloat(rgba[0]), FormatFloat(rgba[1]), FormatFloat(rgba[2])]; } NSDictionary *binding = [textureSpec oo_dictionaryForKey:kOOTextureSpecifierBindingKey]; if (binding != nil) { NSString *bindingName = [binding oo_stringForKey:@"binding"]; NSDictionary *typeDict = [[ResourceManager shaderBindingTypesDictionary] oo_dictionaryForKey:@"player"]; // FIXME: select appropriate binding subset. NSString *bindingType = [typeDict oo_stringForKey:bindingName]; NSString *glslType = nil; NSString *swizzle = @""; if ([bindingType isEqualToString:@"float"]) { glslType = @"float"; } else if ([bindingType isEqualToString:@"vector"]) { glslType = @"vec3"; } else if ([bindingType isEqualToString:@"color"]) { glslType = @"vec4"; swizzle = @".rgb"; } if (glslType != nil) { NSString *uniformName = [self defineBindingUniform:binding ofType:bindingType]; [_fragmentBody appendFormat:@"\tlightMapColor *= %@%@;\n", uniformName, swizzle]; } else { if (bindingType == nil) { OOLogERR(@"material.binding.error.unknown", @"Cannot bind light map to unknown attribute \"%@\".", bindingName); } else { OOLogERR(@"material.binding.error.badType", @"Cannot bind light map to attribute \"%@\" of type %@.", bindingName, bindingType); } [_fragmentBody appendString:@"\tlightMapColor = vec3(0.0); // Bad binding, see log.\n"]; } } if (!isIllumination) { [_fragmentBody appendString:@"\ttotalColor += lightMapColor;\n\t\n"]; } else { [_fragmentBody appendString:@"\tdiffuseLight += lightMapColor;\n\t\n"]; } } } - (void) writeVertexPosition { [_vertexBody appendString: @"\tvec4 position = gl_ModelViewMatrix * gl_Vertex;\n" "\tgl_Position = gl_ProjectionMatrix * position;\n\t\n"]; } - (void) writeTotalColor { [_fragmentPreTextures appendString:@"\tvec3 totalColor = vec3(0.0);\n\t\n"]; } - (void) writeFinalColorComposite { REQUIRE_STAGE(writeTotalColor); // Needed even if none of the following stages does anything. REQUIRE_STAGE(writeDiffuseLighting); REQUIRE_STAGE(writeSpecularLighting); REQUIRE_STAGE(writeLightMaps); if (_haveDiffuseLight) { [_fragmentBody appendString:@"\ttotalColor += diffuseColor * diffuseLight;\n"]; } [_fragmentBody appendString:@"\tgl_FragColor = vec4(totalColor, 1.0);\n\t\n"]; } @end /* Convert any legacy properties and simplified forms in a material specifier to the standard form. FIXME: this should be done up front in OOShipRegistry. When doing that, it also need to be done when materials are set on the fly through JS, */ static NSDictionary *CanonicalizeMaterialSpecifier(NSDictionary *spec, NSString *materialKey) { NSMutableDictionary *result = [NSMutableDictionary dictionary]; OOColor *col = nil; id texSpec = nil; // Colours. col = [OOColor colorWithDescription:[spec objectForKey:kOOMaterialDiffuseColorName]]; if (col == nil) col = [OOColor colorWithDescription:[spec objectForKey:kOOMaterialDiffuseColorLegacyName]]; if (col != nil) [result setObject:[col normalizedArray] forKey:kOOMaterialDiffuseColorName]; col = [OOColor colorWithDescription:[spec objectForKey:kOOMaterialAmbientColorName]]; if (col == nil) col = [OOColor colorWithDescription:[spec objectForKey:kOOMaterialAmbientColorLegacyName]]; if (col != nil) [result setObject:[col normalizedArray] forKey:kOOMaterialAmbientColorName]; col = [OOColor colorWithDescription:[spec objectForKey:kOOMaterialSpecularColorName]]; if (col == nil) col = [OOColor colorWithDescription:[spec objectForKey:kOOMaterialSpecularColorLegacyName]]; if (col != nil) [result setObject:[col normalizedArray] forKey:kOOMaterialSpecularColorName]; col = [OOColor colorWithDescription:[spec objectForKey:kOOMaterialSpecularModulateColorName]]; if (col != nil) [result setObject:[col normalizedArray] forKey:kOOMaterialSpecularModulateColorName]; col = [OOColor colorWithDescription:[spec objectForKey:kOOMaterialEmissionColorName]]; if (col == nil) col = [OOColor colorWithDescription:[spec objectForKey:kOOMaterialEmissionColorLegacyName]]; if (col != nil) [result setObject:[col normalizedArray] forKey:kOOMaterialEmissionColorName]; // Diffuse map. texSpec = [spec objectForKey:kOOMaterialDiffuseMapName]; if ([texSpec isKindOfClass:[NSString class]]) { if ([texSpec length] > 0) { texSpec = [NSDictionary dictionaryWithObject:texSpec forKey:kOOTextureSpecifierNameKey]; } } else if ([texSpec isKindOfClass:[NSDictionary class]]) { /* Special case for diffuse map: no name is changed to name = materialKey, while name = "" is changed to no name. */ NSString *name = [texSpec objectForKey:kOOTextureSpecifierNameKey]; if (name == nil) texSpec = [texSpec dictionaryByAddingObject:materialKey forKey:kOOTextureSpecifierNameKey]; else if ([name length] == 0) { texSpec = [texSpec dictionaryByRemovingObjectForKey:kOOTextureSpecifierNameKey]; } } else { // Special case for unspecified diffuse map. texSpec = [NSDictionary dictionaryWithObject:materialKey forKey:kOOTextureSpecifierNameKey]; } [result setObject:texSpec forKey:kOOMaterialDiffuseMapName]; // Specular maps. { BOOL haveNewSpecular = NO; texSpec = [spec objectForKey:kOOMaterialSpecularColorMapName]; if ([texSpec isKindOfClass:[NSString class]]) { texSpec = [NSDictionary dictionaryWithObject:texSpec forKey:kOOTextureSpecifierNameKey]; } else if (![texSpec isKindOfClass:[NSDictionary class]]) { texSpec = nil; } if (texSpec != nil) { haveNewSpecular = YES; [result setObject:texSpec forKey:kOOMaterialSpecularColorMapName]; } texSpec = [spec objectForKey:kOOMaterialSpecularExponentMapName]; if ([texSpec isKindOfClass:[NSString class]]) { texSpec = [NSDictionary dictionaryWithObject:texSpec forKey:kOOTextureSpecifierNameKey]; } else if (![texSpec isKindOfClass:[NSDictionary class]]) { texSpec = nil; } if (texSpec != nil) { haveNewSpecular = YES; [result setObject:texSpec forKey:kOOMaterialSpecularExponentMapName]; } if (!haveNewSpecular) { // Fall back to legacy combined specular map if defined. texSpec = [spec objectForKey:kOOMaterialCombinedSpecularMapName]; if ([texSpec isKindOfClass:[NSString class]]) { texSpec = [NSDictionary dictionaryWithObject:texSpec forKey:kOOTextureSpecifierNameKey]; } else if (![texSpec isKindOfClass:[NSDictionary class]]) { texSpec = nil; } if (texSpec != nil) { [result setObject:texSpec forKey:kOOMaterialSpecularColorMapName]; texSpec = [texSpec dictionaryByAddingObject:@"a" forKey:kOOTextureSpecifierSwizzleKey]; [result setObject:texSpec forKey:kOOMaterialSpecularExponentMapName]; } } } // Normal and parallax maps. { BOOL haveParallax = NO; BOOL haveNewNormal = NO; texSpec = [spec objectForKey:kOOMaterialNormalMapName]; if ([texSpec isKindOfClass:[NSString class]]) { texSpec = [NSDictionary dictionaryWithObject:texSpec forKey:kOOTextureSpecifierNameKey]; } else if (![texSpec isKindOfClass:[NSDictionary class]]) { texSpec = nil; } if (texSpec != nil) { haveNewNormal = YES; [result setObject:texSpec forKey:kOOMaterialNormalMapName]; } texSpec = [spec objectForKey:kOOMaterialParallaxMapName]; if ([texSpec isKindOfClass:[NSString class]]) { texSpec = [NSDictionary dictionaryWithObject:texSpec forKey:kOOTextureSpecifierNameKey]; } else if (![texSpec isKindOfClass:[NSDictionary class]]) { texSpec = nil; } if (texSpec != nil) { haveNewNormal = YES; haveParallax = YES; [result setObject:texSpec forKey:kOOMaterialParallaxMapName]; } if (!haveNewNormal) { // Fall back to legacy combined normal and parallax map if defined. texSpec = [spec objectForKey:kOOMaterialNormalAndParallaxMapName]; if ([texSpec isKindOfClass:[NSString class]]) { texSpec = [NSDictionary dictionaryWithObject:texSpec forKey:kOOTextureSpecifierNameKey]; } else if (![texSpec isKindOfClass:[NSDictionary class]]) { texSpec = nil; } if (texSpec != nil) { haveParallax = YES; [result setObject:texSpec forKey:kOOMaterialNormalMapName]; texSpec = [texSpec dictionaryByAddingObject:@"a" forKey:kOOTextureSpecifierSwizzleKey]; [result setObject:texSpec forKey:kOOMaterialParallaxMapName]; } } // Additional parallax parameters. if (haveParallax) { float parallaxScale = [spec oo_floatForKey:kOOMaterialParallaxScaleName defaultValue:kOOMaterialDefaultParallaxScale]; [result oo_setFloat:parallaxScale forKey:kOOMaterialParallaxScaleName]; float parallaxBias = [spec oo_floatForKey:kOOMaterialParallaxBiasName]; [result oo_setFloat:parallaxBias forKey:kOOMaterialParallaxBiasName]; } } // Light maps. { NSMutableArray *lightMaps = [NSMutableArray array]; id lightMapSpecs = [spec objectForKey:kOOMaterialLightMapsName]; if (lightMapSpecs != nil && ![lightMapSpecs isKindOfClass:[NSArray class]]) { lightMapSpecs = [NSArray arrayWithObject:lightMapSpecs]; } id lmSpec = nil; foreach (lmSpec, lightMapSpecs) { if ([lmSpec isKindOfClass:[NSString class]]) { lmSpec = [NSMutableDictionary dictionaryWithObject:lmSpec forKey:kOOTextureSpecifierNameKey]; } else if ([lmSpec isKindOfClass:[NSDictionary class]]) { lmSpec = [[lmSpec mutableCopy] autorelease]; } else { continue; } id modulateColor = [lmSpec objectForKey:kOOTextureSpecifierModulateColorKey]; if (modulateColor != nil && ![modulateColor isKindOfClass:[NSArray class]]) { // Don't convert arrays here, because we specifically don't want the behaviour of treating numbers greater than 1 as 0..255 components. col = [OOColor colorWithDescription:modulateColor]; [lmSpec setObject:[col normalizedArray] forKey:kOOTextureSpecifierModulateColorKey]; } id binding = [lmSpec objectForKey:kOOTextureSpecifierBindingKey]; if (binding != nil) { if ([binding isKindOfClass:[NSString class]]) { NSDictionary *expandedBinding = [NSDictionary dictionaryWithObjectsAndKeys:@"binding", @"type", binding, @"binding", nil]; [lmSpec setObject:expandedBinding forKey:kOOTextureSpecifierBindingKey]; } else if (![binding isKindOfClass:[NSDictionary class]] || [[binding oo_stringForKey:@"binding"] length] == 0) { [lmSpec removeObjectForKey:kOOTextureSpecifierBindingKey]; } } [lightMaps addObject:[[lmSpec copy] autorelease]]; } if ([lightMaps count] == 0) { // If light_map isn't use, handle legacy emission_map, illumination_map and emission_and_illumination_map. id emissionSpec = [spec objectForKey:kOOMaterialEmissionMapName]; id illuminationSpec = [spec objectForKey:kOOMaterialIlluminationMapName]; if (emissionSpec == nil && illuminationSpec == nil) { emissionSpec = [spec objectForKey:kOOMaterialEmissionAndIlluminationMapName]; if ([emissionSpec isKindOfClass:[NSString class]]) { // Redundantish check required because we want to modify this as a dictionary to make illuminationSpec. emissionSpec = [NSDictionary dictionaryWithObject:emissionSpec forKey:kOOTextureSpecifierNameKey]; } else if (![emissionSpec isKindOfClass:[NSDictionary class]]) { emissionSpec = nil; } if (emissionSpec != nil) { illuminationSpec = [emissionSpec dictionaryByAddingObject:@"a" forKey:kOOTextureSpecifierSwizzleKey]; } } if (emissionSpec != nil) { if ([emissionSpec isKindOfClass:[NSString class]]) { emissionSpec = [NSDictionary dictionaryWithObject:emissionSpec forKey:kOOTextureSpecifierNameKey]; } if ([emissionSpec isKindOfClass:[NSDictionary class]]) { col = [OOColor colorWithDescription:[spec objectForKey:kOOMaterialEmissionModulateColorName]]; if (col != nil) emissionSpec = [emissionSpec dictionaryByAddingObject:[col normalizedArray] forKey:kOOTextureSpecifierModulateColorKey]; [lightMaps addObject:emissionSpec]; } } if (illuminationSpec != nil) { if ([illuminationSpec isKindOfClass:[NSString class]]) { illuminationSpec = [NSDictionary dictionaryWithObject:illuminationSpec forKey:kOOTextureSpecifierNameKey]; } if ([illuminationSpec isKindOfClass:[NSDictionary class]]) { col = [OOColor colorWithDescription:[spec objectForKey:kOOMaterialIlluminationModulateColorName]]; if (col != nil) illuminationSpec = [illuminationSpec dictionaryByAddingObject:[col normalizedArray] forKey:kOOTextureSpecifierModulateColorKey]; illuminationSpec = [illuminationSpec dictionaryByAddingObject:[NSNumber numberWithBool:YES] forKey:kOOTextureSpecifierIlluminationModeKey]; [lightMaps addObject:illuminationSpec]; } } } [result setObject:lightMaps forKey:kOOMaterialLightMapsName]; } OOLog(@"material.canonicalForm", @"Canonicalized material %@:\nORIGINAL:\n%@\n\n@CANONICAL:\n%@", materialKey, spec, result); return result; } static NSString *FormatFloat(double value) { long long intValue = value; if (value == intValue) { return [NSString stringWithFormat:@"%lli.0", intValue]; } else { return [NSString stringWithFormat:@"%g", value]; } } oolite-1.82/src/Core/Materials/OOMaterial.h000066400000000000000000000053571256642440500205260ustar00rootroot00000000000000/* OOMaterial.h A material which can be applied to an OpenGL object, or more accurately, to the current OpenGL render state. This is an abstract class; actual materials should be subclasses. Currently, only shader materials are supported. Direct use of textures should also be replaced with an OOMaterial subclass. Copyright (C) 2007-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import #import "OOOpenGL.h" #import "OOWeakReference.h" #import "OOOpenGLExtensionManager.h" @interface OOMaterial: NSObject // Called once at startup (by -[Universe init]). + (void) setUp; - (NSString *) name; // Make this the current material. - (void) apply; /* Make no material the current material, tearing down anything set up by the current material. */ + (void) applyNone; /* Get current material. */ + (OOMaterial *) current; /* Ensure material is ready to be used in a display list. This is not required before using a material directly. */ - (void) ensureFinishedLoading; - (BOOL) isFinishedLoading; // Only used by shader material, but defined for all materials for convenience. - (void) setBindingTarget:(id)target; // True if material wants three-component cube map texture coordinates. - (BOOL) wantsNormalsAsTextureCoordinates; #if OO_MULTITEXTURE // Nasty hack: number of texture units for which the drawable should set its basic texture coordinates. - (NSUInteger) countOfTextureUnitsWithBaseCoordinates; #endif #ifndef NDEBUG - (NSSet *) allTextures; #endif @end @interface OOMaterial (OOSubclassInterface) // Subclass responsibilities - don't call directly. - (BOOL) doApply; // Override instead of -apply - (void) unapplyWithNext:(OOMaterial *)next; // Call at top of dealloc - (void) willDealloc; @end oolite-1.82/src/Core/Materials/OOMaterial.m000066400000000000000000000055541256642440500205320ustar00rootroot00000000000000/* OOMaterial.m Copyright (C) 2007-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOMaterial.h" #import "OOFunctionAttributes.h" #import "OOLogging.h" static OOMaterial *sActiveMaterial = nil; @implementation OOMaterial + (void)setUp { // I thought we'd need this, but the stuff I needed it for turned out to be problematic. Maybe in future. -- Ahruman } - (void)dealloc { // Ensure cleanup happens; doing it more than once is safe. [self willDealloc]; [super dealloc]; } - (NSString *)descriptionComponents { return [NSString stringWithFormat:@"\"%@\"", [self name]]; } - (NSString *)name { OOLogGenericParameterError(); return nil; } // Make this the current GL shader program. - (void)apply { [sActiveMaterial unapplyWithNext:self]; [sActiveMaterial release]; sActiveMaterial = nil; if ([self doApply]) { sActiveMaterial = [self retain]; } } + (void)applyNone { [sActiveMaterial unapplyWithNext:nil]; [sActiveMaterial release]; sActiveMaterial = nil; } + (OOMaterial *)current { return [[sActiveMaterial retain] autorelease]; } - (void)ensureFinishedLoading { } - (BOOL) isFinishedLoading { return YES; } - (void)setBindingTarget:(id)target { } - (BOOL) wantsNormalsAsTextureCoordinates { return NO; } #if OO_MULTITEXTURE - (NSUInteger) countOfTextureUnitsWithBaseCoordinates { return 1; } #endif #ifndef NDEBUG - (NSSet *) allTextures { return nil; } #endif @end @implementation OOMaterial (OOSubclassInterface) - (BOOL)doApply { OOLogGenericSubclassResponsibility(); return NO; } - (void)unapplyWithNext:(OOMaterial *)next { // Do nothing. } - (void)willDealloc { if (EXPECT_NOT(sActiveMaterial == self)) { OOLog(@"shader.dealloc.imbalance", @"***** Material deallocated while active, indicating a retain/release imbalance."); [self unapplyWithNext:nil]; sActiveMaterial = nil; } } @end oolite-1.82/src/Core/Materials/OOMaterialConvenienceCreators.h000066400000000000000000000044211256642440500243750ustar00rootroot00000000000000/* OOMaterialConvenienceCreators.h Methods for easy creation of materials. Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOMaterial.h" @class OOColor; @interface OOMaterial (OOConvenienceCreators) /* Get a material based on configuration. The result will be an OOBasicMaterial, OOSingleTextureMaterial or OOShaderMaterial (the latter only if shaders are available). cacheKey is used for caching of synthesized shader materials; nil may be passed for no caching. */ + (OOMaterial *) materialWithName:(NSString *)name cacheKey:(NSString *)cacheKey configuration:(NSDictionary *)configuration macros:(NSDictionary *)macros bindingTarget:(id)object forSmoothedMesh:(BOOL)smooth; /* Select an appropriate material description (based on availability of shaders and content of dictionaries, which may be nil) and call +materialWithName:forModelNamed:configuration:macros:bindTarget:forSmoothedMesh:. */ + (OOMaterial *) materialWithName:(NSString *)name cacheKey:(NSString *)cacheKey materialDictionary:(NSDictionary *)materialDict shadersDictionary:(NSDictionary *)shadersDict macros:(NSDictionary *)macros bindingTarget:(id)object forSmoothedMesh:(BOOL)smooth; @end oolite-1.82/src/Core/Materials/OOMaterialConvenienceCreators.m000066400000000000000000000466031256642440500244120ustar00rootroot00000000000000/* OOMaterialConvenienceCreators.m Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef USE_NEW_SHADER_SYNTHESIZER #define USE_NEW_SHADER_SYNTHESIZER 0 #endif #import "OOMaterialConvenienceCreators.h" #import "OOMaterialSpecifier.h" #if USE_NEW_SHADER_SYNTHESIZER #import "OODefaultShaderSynthesizer.h" #import "ResourceManager.h" #endif #import "OOOpenGLExtensionManager.h" #import "OOShaderMaterial.h" #import "OOSingleTextureMaterial.h" #import "OOMultiTextureMaterial.h" #import "OOCollectionExtractors.h" #import "Universe.h" #import "OOCacheManager.h" #import "OOTexture.h" #import "OODebugFlags.h" #if !USE_NEW_SHADER_SYNTHESIZER typedef struct { NSDictionary *inConfig; NSMutableDictionary *outConfig; NSUInteger texturesUsed; NSUInteger maxTextures; NSMutableDictionary *macros; NSMutableArray *textures; NSMutableDictionary *uniforms; } OOMaterialSynthContext; static void SetUniform(NSMutableDictionary *uniforms, NSString *key, NSString *type, id value); static void SetUniformFloat(OOMaterialSynthContext *context, NSString *key, float value); /* AddTexture(): add a texture to the configuration being synthesized. * specifier is added to the textures array. * uniformName is mapped to the appropriate texture unit in the uniforms dictionary. * If nonShaderKey is not nil, nonShaderKey (e.g. diffuse_map) is set to specifier. * If macroName is not nil, macroName is set to 1 in the macros dictionary. */ static void AddTexture(OOMaterialSynthContext *context, NSString *uniformName, NSString *nonShaderKey, NSString *macroName, NSDictionary *specifier); static void AddColorIfAppropriate(OOMaterialSynthContext *context, SEL selector, NSString *key, NSString *macroName); static void AddMacroColorIfAppropriate(OOMaterialSynthContext *context, SEL selector, NSString *macroName); static void SynthDiffuse(OOMaterialSynthContext *context, NSString *name); static void SynthEmissionAndIllumination(OOMaterialSynthContext *context); static void SynthNormalMap(OOMaterialSynthContext *context); static void SynthSpecular(OOMaterialSynthContext *context); #endif @implementation OOMaterial (OOConvenienceCreators) #if !USE_NEW_SHADER_SYNTHESIZER + (NSDictionary *)synthesizeMaterialDictionaryWithName:(NSString *)name configuration:(NSDictionary *)configuration macros:(NSDictionary *)macros { if (configuration == nil) configuration = [NSDictionary dictionary]; OOMaterialSynthContext context = { .inConfig = configuration, .outConfig = [NSMutableDictionary dictionary], .maxTextures = [[OOOpenGLExtensionManager sharedManager] textureImageUnitCount], .macros = [NSMutableDictionary dictionaryWithDictionary:macros], .textures = [NSMutableArray array], .uniforms = [NSMutableDictionary dictionary] }; if ([UNIVERSE reducedDetail]) { context.maxTextures = 3; } // Basic stuff. /* Set up the various material attributes. Order is significant here, because it determines the order in which features will be dropped if we exceed the hardware's texture image unit limit. */ SynthDiffuse(&context, name); SynthEmissionAndIllumination(&context); SynthNormalMap(&context); SynthSpecular(&context); if ([UNIVERSE detailLevel] >= DETAIL_LEVEL_SHADERS) { // Add uniforms required for hull heat glow. [context.uniforms setObject:@"hullHeatLevel" forKey:@"uHullHeatLevel"]; [context.uniforms setObject:@"timeElapsedSinceSpawn" forKey:@"uTime"]; } // Stuff in the general properties. [context.outConfig setObject:@"true" forKey:@"_oo_is_synthesized_config"]; [context.outConfig setObject:@"oolite-tangent-space-vertex.vertex" forKey:@"vertex_shader"]; [context.outConfig setObject:@"oolite-default-shader.fragment" forKey:@"fragment_shader"]; if ([context.textures count] != 0) [context.outConfig setObject:context.textures forKey:@"textures"]; if ([context.uniforms count] != 0) [context.outConfig setObject:context.uniforms forKey:@"uniforms"]; if ([context.macros count] != 0) [context.outConfig setObject:context.macros forKey:@"_oo_synthesized_material_macros"]; return context.outConfig; } + (OOMaterial *)defaultShaderMaterialWithName:(NSString *)name cacheKey:(NSString *)cacheKey configuration:(NSDictionary *)configuration macros:(NSDictionary *)macros bindingTarget:(id)target { OOCacheManager *cache = nil; NSDictionary *synthesizedConfig = nil; OOMaterial *result = nil; // Avoid looping (can happen if shader fails to compile). if ([configuration objectForKey:@"_oo_is_synthesized_config"] != nil) { OOLog(@"material.synthesize.loop", @"Synthesis loop for material %@.", name); return nil; } if (cacheKey != nil) { cache = [OOCacheManager sharedCache]; // configuration must be in cache key, as otherwise changes in // non-diffuse map can end up miscached cacheKey = [NSString stringWithFormat:@"%@/%@/%@", cacheKey, name, configuration]; synthesizedConfig = [cache objectForKey:cacheKey inCache:@"synthesized shader materials"]; } if (synthesizedConfig == nil) { synthesizedConfig = [self synthesizeMaterialDictionaryWithName:name configuration:configuration macros:macros]; if (synthesizedConfig != nil && cacheKey != nil) { [cache setObject:synthesizedConfig forKey:cacheKey inCache:@"synthesized shader materials"]; } } if (synthesizedConfig != nil) { result = [self materialWithName:name cacheKey:cacheKey configuration:synthesizedConfig macros:[synthesizedConfig objectForKey:@"_oo_synthesized_material_macros"] bindingTarget:target forSmoothedMesh:YES]; } return result; } #else #ifndef NDEBUG static BOOL sDumpShaderSource = NO; + (void) initialize { sDumpShaderSource = [[NSUserDefaults standardUserDefaults] boolForKey:@"dump-synthesized-shaders"]; } #endif + (OOMaterial *) defaultShaderMaterialWithName:(NSString *)name cacheKey:(NSString *)cacheKey configuration:(NSDictionary *)configuration macros:(NSDictionary *)macros bindingTarget:(id)target { NSString *vertexShader = nil; NSString *fragmentShader = nil; NSArray *textureSpecs = nil; NSDictionary *uniformSpecs = nil; if (!OOSynthesizeMaterialShader(configuration, name, cacheKey /* FIXME: entity name for error reporting */, &vertexShader, &fragmentShader, &textureSpecs, &uniformSpecs)) { return nil; } NSDictionary *synthesizedConfig = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:YES], kOOIsSynthesizedMaterialConfigurationKey, textureSpecs, kOOTexturesKey, uniformSpecs, kOOUniformsKey, vertexShader, kOOVertexShaderSourceKey, fragmentShader, kOOFragmentShaderSourceKey, nil]; #ifndef NDEBUG if (sDumpShaderSource) { NSString *dumpPath = [NSString stringWithFormat:@"Synthesized Materials/%@/%@", cacheKey, name]; [ResourceManager writeDiagnosticString:vertexShader toFileNamed:[dumpPath stringByAppendingPathExtension:@"vertex"]]; [ResourceManager writeDiagnosticString:fragmentShader toFileNamed:[dumpPath stringByAppendingPathExtension:@"fragment"]]; // Hide internal keys in the synthesized config before writing it. NSMutableDictionary *humanFriendlyConfig = [[synthesizedConfig mutableCopy] autorelease]; [humanFriendlyConfig removeObjectForKey:kOOVertexShaderSourceKey]; [humanFriendlyConfig removeObjectForKey:kOOFragmentShaderSourceKey]; [humanFriendlyConfig removeObjectForKey:kOOIsSynthesizedMaterialConfigurationKey]; [humanFriendlyConfig setObject:[NSString stringWithFormat:@"%@.vertex", name] forKey:kOOVertexShaderNameKey]; [humanFriendlyConfig setObject:[NSString stringWithFormat:@"%@.fragment", name] forKey:kOOFragmentShaderNameKey]; [ResourceManager writeDiagnosticPList:humanFriendlyConfig toFileNamed:[dumpPath stringByAppendingPathExtension:@"plist"]]; [ResourceManager writeDiagnosticPList:configuration toFileNamed:[[dumpPath stringByAppendingString:@"-original"] stringByAppendingPathExtension:@"plist"]]; } #endif return [self materialWithName:name cacheKey:cacheKey configuration:synthesizedConfig macros:nil bindingTarget:target forSmoothedMesh:YES]; } #endif + (OOMaterial *) materialWithName:(NSString *)name cacheKey:(NSString *)cacheKey configuration:(NSDictionary *)configuration macros:(NSDictionary *)macros bindingTarget:(id)object forSmoothedMesh:(BOOL)smooth // Internally, this flg really means "force use of shaders". { id result = nil; #if OO_SHADERS if ([UNIVERSE useShaders]) { if ([OOShaderMaterial configurationDictionarySpecifiesShaderMaterial:configuration]) { result = [OOShaderMaterial shaderMaterialWithName:name configuration:configuration macros:macros bindingTarget:object]; } // Use default shader if smoothing is on, or shader detail is full, DEBUG_NO_SHADER_FALLBACK is set, or material uses an effect map. if (result == nil && (smooth || gDebugFlags & DEBUG_NO_SHADER_FALLBACK || [UNIVERSE detailLevel] >= DETAIL_LEVEL_SHADERS || [configuration oo_combinedSpecularMapSpecifier] != nil || [configuration oo_normalMapSpecifier] != nil || [configuration oo_parallaxMapSpecifier] != nil || [configuration oo_normalAndParallaxMapSpecifier] != nil || [configuration oo_emissionMapSpecifier] != nil || [configuration oo_illuminationMapSpecifier] != nil || [configuration oo_emissionAndIlluminationMapSpecifier] != nil )) { result = [self defaultShaderMaterialWithName:name cacheKey:cacheKey configuration:configuration macros:macros bindingTarget:(id)object]; } } #endif #if OO_MULTITEXTURE if (result == nil /*&& ![UNIVERSE reducedDetail]*/) { if ([configuration oo_emissionMapSpecifier] != nil || [configuration oo_illuminationMapSpecifier] || [configuration oo_emissionAndIlluminationMapSpecifier] != nil) { result = [[OOMultiTextureMaterial alloc] initWithName:name configuration:configuration]; [result autorelease]; } } #endif if (result == nil) { if ([configuration oo_diffuseMapSpecifierWithDefaultName:name] == nil) { result = [[OOBasicMaterial alloc] initWithName:name configuration:configuration]; } else { result = [[OOSingleTextureMaterial alloc] initWithName:name configuration:configuration]; } if (result == nil) { result = [[OOBasicMaterial alloc] initWithName:name configuration:configuration]; } [result autorelease]; } return result; } + (OOMaterial *) materialWithName:(NSString *)name cacheKey:(NSString *)cacheKey materialDictionary:(NSDictionary *)materialDict shadersDictionary:(NSDictionary *)shadersDict macros:(NSDictionary *)macros bindingTarget:(id)object forSmoothedMesh:(BOOL)smooth { NSDictionary *configuration = nil; #if OO_SHADERS if ([UNIVERSE useShaders]) { configuration = [shadersDict oo_dictionaryForKey:name]; } #endif if (configuration == nil) { configuration = [materialDict oo_dictionaryForKey:name]; } if (configuration == nil) { // Use fallback material for non-existent simple texture. // Texture caching means this won't be wasted in the general case. OOTexture *texture = [OOTexture textureWithName:name inFolder:@"Textures"]; if (texture == nil) return nil; configuration = [NSDictionary dictionary]; } return [self materialWithName:name cacheKey:cacheKey configuration:configuration macros:macros bindingTarget:object forSmoothedMesh:smooth]; } @end #if !USE_NEW_SHADER_SYNTHESIZER static void SetUniform(NSMutableDictionary *uniforms, NSString *key, NSString *type, id value) { [uniforms setObject:[NSDictionary dictionaryWithObjectsAndKeys:type, @"type", value, @"value", nil] forKey:key]; } static void SetUniformFloat(OOMaterialSynthContext *context, NSString *key, float value) { SetUniform(context->uniforms, key, @"float", [NSNumber numberWithFloat:value]); } static void AddTexture(OOMaterialSynthContext *context, NSString *uniformName, NSString *nonShaderKey, NSString *macroName, NSDictionary *specifier) { NSCParameterAssert(context->texturesUsed < context->maxTextures); context->texturesUsed++; SetUniform(context->uniforms, uniformName, @"texture", [NSNumber numberWithUnsignedInteger:[context->textures count]]); [context->textures addObject:specifier]; if (nonShaderKey != nil) { [context->outConfig setObject:specifier forKey:kOOMaterialDiffuseMapName]; } if (macroName != nil) { [context->macros setObject:@"1" forKey:macroName]; } } static void AddColorIfAppropriate(OOMaterialSynthContext *context, SEL selector, NSString *key, NSString *macroName) { OOColor *color = [context->inConfig performSelector:selector]; if (color != nil) { [context->outConfig setObject:[color normalizedArray] forKey:key]; if (macroName != nil) [context->macros setObject:@"1" forKey:macroName]; } } static void AddMacroColorIfAppropriate(OOMaterialSynthContext *context, SEL selector, NSString *macroName) { OOColor *color = [context->inConfig performSelector:selector]; if (color != nil) { NSString *macroText = [NSString stringWithFormat:@"vec4(%g, %g, %g, %g)", [color redComponent], [color greenComponent], [color blueComponent], [color alphaComponent]]; [context->macros setObject:macroText forKey:macroName]; } } static void SynthDiffuse(OOMaterialSynthContext *context, NSString *name) { // Set up diffuse map if appropriate. NSDictionary *diffuseMapSpec = [context->inConfig oo_diffuseMapSpecifierWithDefaultName:name]; if (diffuseMapSpec != nil && context->texturesUsed < context->maxTextures) { AddTexture(context, @"uDiffuseMap", kOOMaterialDiffuseMapName, @"OOSTD_DIFFUSE_MAP", diffuseMapSpec); if ([diffuseMapSpec oo_boolForKey:@"cube_map"]) { [context->macros setObject:@"1" forKey:@"OOSTD_DIFFUSE_MAP_IS_CUBE_MAP"]; } } else { // No diffuse map must be specified explicitly. [context->outConfig setObject:@"" forKey:kOOMaterialDiffuseMapName]; } // Set up diffuse colour if any. AddColorIfAppropriate(context, @selector(oo_diffuseColor), kOOMaterialDiffuseColorName, nil); } static void SynthEmissionAndIllumination(OOMaterialSynthContext *context) { // Read the various emission and illumination textures, and decide what to do with them. NSDictionary *emissionMapSpec = [context->inConfig oo_emissionMapSpecifier]; NSDictionary *illuminationMapSpec = [context->inConfig oo_illuminationMapSpecifier]; NSDictionary *emissionAndIlluminationSpec = [context->inConfig oo_emissionAndIlluminationMapSpecifier]; BOOL isCombinedSpec = NO; BOOL haveIlluminationMap = NO; if (emissionMapSpec == nil && emissionAndIlluminationSpec != nil) { emissionMapSpec = emissionAndIlluminationSpec; if (illuminationMapSpec == nil) isCombinedSpec = YES; // Else use only emission part of emission_and_illumination_map, combined with full illumination_map. } if (emissionMapSpec != nil && context->texturesUsed < context->maxTextures) { /* FIXME: at this point, if there is an illumination map, we should consider merging it into the emission map using OOCombinedEmissionMapGenerator if the total number of texture specifiers is greater than context->maxTextures. This will require adding a new type of texture specifier - not a big deal. -- Ahruman 2010-05-21 */ AddTexture(context, @"uEmissionMap", nil, isCombinedSpec ? @"OOSTD_EMISSION_AND_ILLUMINATION_MAP" : @"OOSTD_EMISSION_MAP", emissionMapSpec); /* Note that this sets emission_color, not emission_modulate_color. This is because the emission colour value is sent through the standard OpenGL emission colour attribute by OOBasicMaterial. */ AddColorIfAppropriate(context, @selector(oo_emissionModulateColor), kOOMaterialEmissionColorName, @"OOSTD_EMISSION"); haveIlluminationMap = isCombinedSpec; } else { // No emission map, use overall emission colour if specified. AddColorIfAppropriate(context, @selector(oo_emissionColor), kOOMaterialEmissionColorName, @"OOSTD_EMISSION"); } if (illuminationMapSpec != nil && context->texturesUsed < context->maxTextures) { AddTexture(context, @"uIlluminationMap", nil, @"OOSTD_ILLUMINATION_MAP", illuminationMapSpec); haveIlluminationMap = YES; } if (haveIlluminationMap) { AddMacroColorIfAppropriate(context, @selector(oo_illuminationModulateColor), @"OOSTD_ILLUMINATION_COLOR"); } } static void SynthNormalMap(OOMaterialSynthContext *context) { if (context->texturesUsed < context->maxTextures) { BOOL hasParallax = YES; NSDictionary *normalMapSpec = [context->inConfig oo_normalAndParallaxMapSpecifier]; if (normalMapSpec == nil) { hasParallax = NO; normalMapSpec = [context->inConfig oo_normalMapSpecifier]; } if (normalMapSpec != nil) { AddTexture(context, @"uNormalMap", nil, @"OOSTD_NORMAL_MAP", normalMapSpec); if (hasParallax) { [context->macros setObject:@"1" forKey:@"OOSTD_NORMAL_AND_PARALLAX_MAP"]; SetUniformFloat(context, @"uParallaxScale", [context->inConfig oo_parallaxScale]); SetUniformFloat(context, @"uParallaxBias", [context->inConfig oo_parallaxBias]); } } } } static void SynthSpecular(OOMaterialSynthContext *context) { GLint shininess = [context->inConfig oo_specularExponent]; if (shininess <= 0) return; NSDictionary *specularMapSpec = nil; OOColor *specularColor = nil; if (context->texturesUsed < context->maxTextures) { specularMapSpec = [context->inConfig oo_combinedSpecularMapSpecifier]; } if (specularMapSpec != nil) specularColor = [context->inConfig oo_specularModulateColor]; else specularColor = [context->inConfig oo_specularColor]; if ([specularColor isBlack]) return; [context->outConfig setObject:[NSNumber numberWithUnsignedInt:shininess] forKey:kOOMaterialSpecularExponentLegacyName]; if (specularMapSpec != nil) { AddTexture(context, @"uSpecularMap", kOOMaterialDiffuseMapName, @"OOSTD_SPECULAR_MAP", specularMapSpec); } if (specularColor != nil) { /* As with emission colour, specular_modulate_color is transformed to specular_color here because the shader reads it from the standard material specular colour property set by OOBasicMaterial. */ [context->outConfig setObject:[specularColor normalizedArray] forKey:kOOMaterialSpecularColorName]; } [context->macros setObject:@"1" forKey:@"OOSTD_SPECULAR"]; } #endif oolite-1.82/src/Core/Materials/OOMaterialSpecifier.h000066400000000000000000000072111256642440500223470ustar00rootroot00000000000000/* OOMaterialSpecifier.h Key declarations and convenience methods for material specifiers. Copyright (C) 2010-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" @class OOColor; // Convenience methods to extract properties from material dictionaries. @interface NSDictionary (OOMateralProperties) - (OOColor *) oo_diffuseColor; - (OOColor *) oo_ambientColor; - (OOColor *) oo_specularColor; - (OOColor *) oo_specularModulateColor; - (OOColor *) oo_emissionColor; - (OOColor *) oo_emissionModulateColor; - (OOColor *) oo_illuminationModulateColor; - (NSDictionary *) oo_diffuseMapSpecifierWithDefaultName:(NSString *)name; - (NSDictionary *) oo_combinedSpecularMapSpecifier; - (NSDictionary *) oo_specularColorMapSpecifier; - (NSDictionary *) oo_specularExponentMapSpecifier; - (NSDictionary *) oo_normalMapSpecifier; - (NSDictionary *) oo_parallaxMapSpecifier; - (NSDictionary *) oo_normalAndParallaxMapSpecifier; - (NSDictionary *) oo_emissionMapSpecifier; - (NSDictionary *) oo_illuminationMapSpecifier; - (NSDictionary *) oo_emissionAndIlluminationMapSpecifier; - (float) oo_parallaxScale; - (float) oo_parallaxBias; - (int) oo_specularExponent; @end extern NSString * const kOOMaterialDiffuseColorName; extern NSString * const kOOMaterialDiffuseColorLegacyName; extern NSString * const kOOMaterialAmbientColorName; extern NSString * const kOOMaterialAmbientColorLegacyName; extern NSString * const kOOMaterialSpecularColorName; extern NSString * const kOOMaterialSpecularColorLegacyName; extern NSString * const kOOMaterialSpecularModulateColorName; extern NSString * const kOOMaterialEmissionColorName; extern NSString * const kOOMaterialEmissionColorLegacyName; extern NSString * const kOOMaterialEmissionModulateColorName; extern NSString * const kOOMaterialIlluminationModulateColorName; extern NSString * const kOOMaterialDiffuseMapName; extern NSString * const kOOMaterialSpecularColorMapName; extern NSString * const kOOMaterialSpecularExponentMapName; extern NSString * const kOOMaterialCombinedSpecularMapName; extern NSString * const kOOMaterialNormalMapName; extern NSString * const kOOMaterialParallaxMapName; extern NSString * const kOOMaterialNormalAndParallaxMapName; extern NSString * const kOOMaterialEmissionMapName; extern NSString * const kOOMaterialIlluminationMapName; extern NSString * const kOOMaterialEmissionAndIlluminationMapName; extern NSString * const kOOMaterialParallaxScaleName; extern NSString * const kOOMaterialParallaxBiasName; extern NSString * const kOOMaterialSpecularExponentName; extern NSString * const kOOMaterialSpecularExponentLegacyName; extern NSString * const kOOMaterialLightMapsName; #define kOOMaterialDefaultParallaxScale (0.01f) oolite-1.82/src/Core/Materials/OOMaterialSpecifier.m000066400000000000000000000202471256642440500223600ustar00rootroot00000000000000/* OOMaterialSpecifier.m Copyright (C) 2010-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOMaterialSpecifier.h" #import "OOColor.h" #import "OOCollectionExtractors.h" #import "OOTexture.h" #import "Universe.h" #import "NSDictionaryOOExtensions.h" NSString * const kOOMaterialDiffuseColorName = @"diffuse_color"; NSString * const kOOMaterialDiffuseColorLegacyName = @"diffuse"; NSString * const kOOMaterialAmbientColorName = @"ambient_color"; NSString * const kOOMaterialAmbientColorLegacyName = @"ambient"; NSString * const kOOMaterialSpecularColorName = @"specular_color"; NSString * const kOOMaterialSpecularColorLegacyName = @"specular"; NSString * const kOOMaterialSpecularModulateColorName = @"specular_modulate_color"; NSString * const kOOMaterialEmissionColorName = @"emission_color"; NSString * const kOOMaterialEmissionColorLegacyName = @"emission"; NSString * const kOOMaterialEmissionModulateColorName = @"emission_modulate_color"; NSString * const kOOMaterialIlluminationModulateColorName = @"illumination_modulate_color"; NSString * const kOOMaterialDiffuseMapName = @"diffuse_map"; NSString * const kOOMaterialSpecularColorMapName = @"specular_color_map"; NSString * const kOOMaterialSpecularExponentMapName = @"specular_exponent_map"; NSString * const kOOMaterialCombinedSpecularMapName = @"specular_map"; // Combined specular_color_map and specular_exponent_map (unfortunate name required for backwards-compatibility). NSString * const kOOMaterialNormalMapName = @"normal_map"; NSString * const kOOMaterialParallaxMapName = @"parallax_map"; NSString * const kOOMaterialNormalAndParallaxMapName = @"normal_and_parallax_map"; NSString * const kOOMaterialEmissionMapName = @"emission_map"; NSString * const kOOMaterialIlluminationMapName = @"illumination_map"; NSString * const kOOMaterialEmissionAndIlluminationMapName = @"emission_and_illumination_map"; NSString * const kOOMaterialParallaxScaleName = @"parallax_scale"; NSString * const kOOMaterialParallaxBiasName = @"parallax_bias"; NSString * const kOOMaterialSpecularExponentName = @"specular_exponent"; NSString * const kOOMaterialSpecularExponentLegacyName = @"shininess"; NSString * const kOOMaterialLightMapsName = @"light_map"; @implementation NSDictionary (OOMateralProperties) // Internal. Used to avoid mutual recusion between -oo_specularExponentMapSpecifier and -oo_specularExponent. - (int) oo_rawSpecularExponentValue { NSObject *value = [self objectForKey:kOOMaterialSpecularExponentName]; if (value == nil) value = [self objectForKey:kOOMaterialSpecularExponentLegacyName]; return OOIntFromObject(value, -1); } - (OOColor *) oo_diffuseColor { OOColor *result = [OOColor colorWithDescription:[self objectForKey:kOOMaterialDiffuseColorName]]; if (result == nil) result = [OOColor colorWithDescription:[self objectForKey:kOOMaterialDiffuseColorLegacyName]]; if ([result isWhite]) result = nil; return result; } - (OOColor *) oo_ambientColor { OOColor *result = [OOColor colorWithDescription:[self objectForKey:kOOMaterialAmbientColorName]]; if (result == nil) result = [OOColor colorWithDescription:[self objectForKey:kOOMaterialAmbientColorLegacyName]]; return result; } - (OOColor *) oo_specularColor { OOColor *result = [OOColor colorWithDescription:[self objectForKey:kOOMaterialSpecularColorName]]; if (result == nil) result = [OOColor colorWithDescription:[self objectForKey:kOOMaterialSpecularColorLegacyName]]; if (result == nil) { result = [OOColor colorWithWhite:0.2f alpha:1.0f]; } return result; } - (OOColor *) oo_specularModulateColor { OOColor *result = [OOColor colorWithDescription:[self objectForKey:kOOMaterialSpecularModulateColorName]]; if (result == nil) result = [OOColor whiteColor]; return result; } - (OOColor *) oo_emissionColor { OOColor *result = [OOColor colorWithDescription:[self objectForKey:kOOMaterialEmissionColorName]]; if (result == nil) result = [OOColor colorWithDescription:[self objectForKey:kOOMaterialEmissionColorLegacyName]]; if ([result isBlack]) result = nil; return result; } - (OOColor *) oo_emissionModulateColor { OOColor *result = [OOColor colorWithDescription:[self objectForKey:kOOMaterialEmissionModulateColorName]]; if ([result isWhite]) result = nil; return result; } - (OOColor *) oo_illuminationModulateColor { OOColor *result = [OOColor colorWithDescription:[self objectForKey:kOOMaterialIlluminationModulateColorName]]; if ([result isWhite]) result = nil; return result; } - (NSDictionary *) oo_diffuseMapSpecifierWithDefaultName:(NSString *)name { return [self oo_textureSpecifierForKey:kOOMaterialDiffuseMapName defaultName:name]; } - (NSDictionary *) oo_combinedSpecularMapSpecifier { if ([self oo_rawSpecularExponentValue] == 0) return nil; return [self oo_textureSpecifierForKey:kOOMaterialCombinedSpecularMapName defaultName:nil]; } - (NSDictionary *) oo_specularColorMapSpecifier { if ([self oo_rawSpecularExponentValue] == 0) return nil; NSDictionary *result = [self oo_textureSpecifierForKey:kOOMaterialSpecularColorMapName defaultName:nil]; if (result == nil) result = [self oo_combinedSpecularMapSpecifier]; return result; } - (NSDictionary *) oo_specularExponentMapSpecifier { if ([self oo_rawSpecularExponentValue] == 0) return nil; NSDictionary *result = [self oo_textureSpecifierForKey:kOOMaterialSpecularExponentMapName defaultName:nil]; if (result == nil) result = [[self oo_combinedSpecularMapSpecifier] dictionaryByAddingObject:@"a" forKey:@"extract_channel"]; return result; } - (NSDictionary *) oo_normalMapSpecifier { if ([self oo_normalAndParallaxMapSpecifier] != nil) return nil; return [self oo_textureSpecifierForKey:kOOMaterialNormalMapName defaultName:nil]; } - (NSDictionary *) oo_parallaxMapSpecifier { id spec = [self oo_textureSpecifierForKey:kOOMaterialParallaxMapName defaultName:nil]; if (spec == nil) { // Default is alpha channel of normal_and_parallax_map. spec = [[self oo_normalAndParallaxMapSpecifier] dictionaryByAddingObject:@"a" forKey:@"extract_channel"]; } return spec; } - (NSDictionary *) oo_normalAndParallaxMapSpecifier { return [self oo_textureSpecifierForKey:kOOMaterialNormalAndParallaxMapName defaultName:nil]; } - (NSDictionary *) oo_emissionMapSpecifier { return [self oo_textureSpecifierForKey:kOOMaterialEmissionMapName defaultName:nil]; } - (NSDictionary *) oo_illuminationMapSpecifier { return [self oo_textureSpecifierForKey:kOOMaterialIlluminationMapName defaultName:nil]; } - (NSDictionary *) oo_emissionAndIlluminationMapSpecifier { if ([self oo_emissionMapSpecifier] != nil || [self oo_illuminationMapSpecifier] != nil) return nil; return [self oo_textureSpecifierForKey:kOOMaterialEmissionAndIlluminationMapName defaultName:nil]; } - (float) oo_parallaxScale { return [self oo_floatForKey:kOOMaterialParallaxScaleName defaultValue:kOOMaterialDefaultParallaxScale]; } - (float) oo_parallaxBias { return [self oo_floatForKey:kOOMaterialParallaxBiasName]; } - (int) oo_specularExponent { int result = [self oo_rawSpecularExponentValue]; if (result < 0) { if ([UNIVERSE useShaders] && [self oo_specularExponentMapSpecifier] != nil) { result = 128; } else { result = 10; } } return result; } @end oolite-1.82/src/Core/Materials/OOMultiTextureMaterial.h000066400000000000000000000030341256642440500231100ustar00rootroot00000000000000/* OOMultiTextureMaterial.h A material that uses multitexturing and texture combiners. Copyright (C) 2010-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOOpenGLExtensionManager.h" #if OO_MULTITEXTURE #import "OOBasicMaterial.h" @class OOTexture; @interface OOMultiTextureMaterial: OOBasicMaterial { @private OOTexture *_diffuseMap; OOTexture *_emissionMap; NSUInteger _unitsUsed; } - (id)initWithName:(NSString *)name configuration:(NSDictionary *)configuration; - (NSUInteger) textureUnitCount; @end #endif /* OO_MULTITEXTURE */ oolite-1.82/src/Core/Materials/OOMultiTextureMaterial.m000066400000000000000000000151431256642440500231210ustar00rootroot00000000000000/* OOMultiTextureMaterial.m Copyright (C) 2010-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOMultiTextureMaterial.h" #import "OOCombinedEmissionMapGenerator.h" #import "OOOpenGLExtensionManager.h" #import "OOTexture.h" #import "OOMacroOpenGL.h" #import "NSDictionaryOOExtensions.h" #import "OOMaterialSpecifier.h" #if OO_MULTITEXTURE @implementation OOMultiTextureMaterial - (id)initWithName:(NSString *)name configuration:(NSDictionary *)configuration { if (![[OOOpenGLExtensionManager sharedManager] textureCombinersSupported]) { [self release]; return nil; } NSDictionary *diffuseSpec = [configuration oo_diffuseMapSpecifierWithDefaultName:name]; NSDictionary *emissionSpec = [configuration oo_emissionMapSpecifier]; NSDictionary *illuminationSpec = [configuration oo_illuminationMapSpecifier]; NSDictionary *emissionAndIlluminationSpec = [configuration oo_emissionAndIlluminationMapSpecifier]; OOColor *diffuseColor = [configuration oo_diffuseColor]; OOColor *emissionColor = nil; OOColor *illuminationColor = [configuration oo_illuminationModulateColor]; NSMutableDictionary *mutableConfiguration = [NSMutableDictionary dictionaryWithDictionary:configuration]; if (emissionSpec != nil || emissionAndIlluminationSpec != nil) { emissionColor = [configuration oo_emissionModulateColor]; /* If an emission map and an emission colour are both specified, stop the superclass (OOBasicMaterial) from applying the emission colour. */ [mutableConfiguration removeObjectForKey:kOOMaterialEmissionColorName]; [mutableConfiguration removeObjectForKey:kOOMaterialEmissionColorLegacyName]; } if ((self = [super initWithName:name configuration:mutableConfiguration])) { if (diffuseSpec != nil) { _diffuseMap = [[OOTexture textureWithConfiguration:diffuseSpec] retain]; if (_diffuseMap != nil) _unitsUsed++; } // Check for simplest cases, where we don't need to bake a derived emission map. if (emissionSpec != nil && illuminationSpec == nil && emissionAndIlluminationSpec == nil && emissionColor == nil) { _emissionMap = [[OOTexture textureWithConfiguration:emissionSpec extraOptions:kOOTextureExtraShrink] retain]; if (_emissionMap != nil) _unitsUsed++; } else { OOCombinedEmissionMapGenerator *generator = nil; if (emissionAndIlluminationSpec != nil) { generator = [[OOCombinedEmissionMapGenerator alloc] initWithEmissionAndIlluminationMapSpec:emissionAndIlluminationSpec diffuseMap:_diffuseMap diffuseColor:diffuseColor emissionColor:emissionColor illuminationColor:illuminationColor optionsSpecifier:emissionAndIlluminationSpec]; } else { generator = [[OOCombinedEmissionMapGenerator alloc] initWithEmissionMapSpec:emissionSpec emissionColor:emissionColor diffuseMap:_diffuseMap diffuseColor:diffuseColor illuminationMapSpec:illuminationSpec illuminationColor:illuminationColor optionsSpecifier:emissionSpec ?: illuminationSpec]; } _emissionMap = [[OOTexture textureWithGenerator:[generator autorelease]] retain]; if (_emissionMap != nil) _unitsUsed++; } } return self; } - (void) dealloc { [self willDealloc]; DESTROY(_diffuseMap); DESTROY(_emissionMap); [super dealloc]; } - (NSString *) descriptionComponents { NSMutableArray *bits = [NSMutableArray array]; if (_diffuseMap) [bits addObject:[NSString stringWithFormat:@"diffuse map: %@", [_diffuseMap shortDescription]]]; if (_emissionMap) [bits addObject:[NSString stringWithFormat:@"emission map: %@", [_emissionMap shortDescription]]]; NSString *result = [super descriptionComponents]; if ([bits count] > 0) result = [result stringByAppendingFormat:@" - %@", [bits componentsJoinedByString:@","]]; return result; } - (NSUInteger) textureUnitCount { return _unitsUsed; } - (NSUInteger) countOfTextureUnitsWithBaseCoordinates { return _unitsUsed; } - (void) ensureFinishedLoading { [_diffuseMap ensureFinishedLoading]; [_emissionMap ensureFinishedLoading]; } - (void) apply { OO_ENTER_OPENGL(); [super apply]; GLenum textureUnit = GL_TEXTURE0_ARB; if (_diffuseMap != nil) { OOGL(glActiveTextureARB(textureUnit++)); OOGL(glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB)); OOGL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE)); [_diffuseMap apply]; } if (_emissionMap != nil) { OOGL(glActiveTextureARB(textureUnit++)); OOGL(glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB)); OOGL(glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_ADD)); [_emissionMap apply]; } NSAssert2(textureUnit - GL_TEXTURE0_ARB == _unitsUsed, @"OOMultiTextureMaterial texture unit count invalid (expected %lu, actually using %u)", _unitsUsed, textureUnit - GL_TEXTURE0_ARB); if (textureUnit > GL_TEXTURE1_ARB) { OOGL(glActiveTextureARB(GL_TEXTURE0_ARB)); } } - (void) unapplyWithNext:(OOMaterial *)next { OO_ENTER_OPENGL(); [super unapplyWithNext:next]; NSUInteger i; i = [next isKindOfClass:[OOMultiTextureMaterial class]] ? [(OOMultiTextureMaterial *)next textureUnitCount] : 0; for (; i != _unitsUsed; ++i) { OOGL(glActiveTextureARB(GL_TEXTURE0_ARB + i)); [OOTexture applyNone]; OOGL(glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE)); } OOGL(glActiveTextureARB(GL_TEXTURE0_ARB)); } #ifndef NDEBUG - (NSSet *) allTextures { if (_diffuseMap == nil) return [NSSet setWithObject:_emissionMap]; return [NSSet setWithObjects:_diffuseMap, _emissionMap, nil]; } #endif @end #endif /* OO_MULTITEXTURE */ oolite-1.82/src/Core/Materials/OONullTexture.h000066400000000000000000000024501256642440500212520ustar00rootroot00000000000000/* OONullTexture.h Singleton subclass of OOTexture representing the empty texture. Applying OONullTexture is equivalent to [OOTexture applyNone]. Copyright (C) 2008-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOTexture.h" @interface OONullTexture: OOTexture + (OONullTexture *) sharedNullTexture; @end oolite-1.82/src/Core/Materials/OONullTexture.m000066400000000000000000000042571256642440500212660ustar00rootroot00000000000000/* OONullTexture.m Copyright (C) 2008-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OONullTexture.h" #import "OOCocoa.h" #import "OOTextureInternal.h" static OONullTexture *sSingleton = nil; @implementation OONullTexture + (OONullTexture *) sharedNullTexture { // NOTE: assumes single-threaded access. if (sSingleton == nil) { sSingleton = [[self alloc] init]; } return sSingleton; } - (void) apply { [OOTexture applyNone]; } - (NSSize) dimensions { return NSZeroSize; } - (BOOL) isMipMapped { return NO; } - (void) forceRebind { } #ifndef NDEBUG - (NSString *) name { return @""; } #endif @end @implementation OONullTexture (Singleton) /* Canonical singleton boilerplate. See Cocoa Fundamentals Guide: Creating a Singleton Instance. See also +nullTexture above. NOTE: assumes single-threaded access. */ + (id)allocWithZone:(NSZone *)inZone { if (sSingleton == nil) { sSingleton = [super allocWithZone:inZone]; return sSingleton; } return nil; } - (id)copyWithZone:(NSZone *)inZone { return self; } - (id)retain { return self; } - (NSUInteger)retainCount { return UINT_MAX; } - (void)release {} - (id)autorelease { return self; } @end oolite-1.82/src/Core/Materials/OOPNGTextureLoader.h000066400000000000000000000025501256642440500221140ustar00rootroot00000000000000/* OOPNGTextureLoader.h It's a texture loader. Which loads PNGs. Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "png.h" #import "OOTextureLoader.h" @interface OOPNGTextureLoader: OOTextureLoader { @private png_structp png; png_infop pngInfo; png_infop pngEndInfo; NSData *fileData; size_t length; size_t offset; } @end oolite-1.82/src/Core/Materials/OOPNGTextureLoader.m000066400000000000000000000137501256642440500221250ustar00rootroot00000000000000/* OOPNGTextureLoader.m Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOPNGTextureLoader.h" #import "OOFunctionAttributes.h" #import "OOLogging.h" #import "OOCPUInfo.h" #import "NSDataOOExtensions.h" //void png_error(png_structp, png_const_charp) NO_RETURN_FUNC; static void PNGError(png_structp png, png_const_charp message); static void PNGWarning(png_structp png, png_const_charp message); static void PNGRead(png_structp png, png_bytep bytes, png_size_t size); @interface OOPNGTextureLoader (OOPrivate) - (void)doLoadTexture; - (void)readBytes:(png_bytep)bytes count:(png_size_t)count; @end @implementation OOPNGTextureLoader - (void)loadTexture { // Get data from file fileData = [[NSData oo_dataWithOXZFile:_path] retain]; if (fileData == nil) return; length = [fileData length]; [self doLoadTexture]; [fileData release]; fileData = nil; } - (void)dealloc { [fileData release]; if (png != NULL) { png_destroy_read_struct(&png, &pngInfo, &pngEndInfo); } [super dealloc]; } @end @implementation OOPNGTextureLoader (OOPrivate) - (void)doLoadTexture { png_bytepp rows = NULL; png_uint_32 pngWidth, pngHeight; int depth, colorType; uint32_t i; // Set up PNG decoding png = png_create_read_struct(PNG_LIBPNG_VER_STRING, self, PNGError, PNGWarning); if (png != NULL) pngInfo = png_create_info_struct(png); if (pngInfo != NULL) pngEndInfo = png_create_info_struct(png); if (pngEndInfo == NULL) { OOLog(@"texture.load.png.setup.failed", @"***** Error preparing to read %@.", _path); goto FAIL; } if (EXPECT_NOT(setjmp(png_jmpbuf(png)))) { // libpng will jump here on error. if (_data) { free(_data); _data = NULL; } goto FAIL; } png_set_read_fn(png, self, PNGRead); png_read_info(png, pngInfo); // Read header, get format info and check that it meets our expectations. if (EXPECT_NOT(!png_get_IHDR(png, pngInfo, &pngWidth, &pngHeight, &depth, &colorType, NULL, NULL, NULL))) { OOLog(@"texture.load.png.failed", @"Failed to get metadata from PNG %@", _path); goto FAIL; } png_set_strip_16(png); // 16 bits per channel -> 8 bpc if (depth < 8 || colorType == PNG_COLOR_TYPE_PALETTE) { png_set_expand(png); // Paletted -> RGB, greyscale -> 8 bpc } if (colorType == PNG_COLOR_TYPE_GRAY) { _format = kOOTextureDataGrayscale; } else if (colorType == PNG_COLOR_TYPE_GRAY_ALPHA) { _format = kOOTextureDataGrayscaleAlpha; } else { _format = kOOTextureDataRGBA; #if OOLITE_BIG_ENDIAN png_set_bgr(png); png_set_swap_alpha(png); // RGBA->ARGB png_set_filler(png, 0xFF, PNG_FILLER_BEFORE); #elif OOLITE_LITTLE_ENDIAN png_set_filler(png, 0xFF, PNG_FILLER_AFTER); #else #error Unknown handle byte order. #endif } png_read_update_info(png, pngInfo); png_set_interlace_handling(png); // Metadata is acceptable; load data. _width = pngWidth; _height = pngHeight; _rowBytes = png_get_rowbytes(png, pngInfo); // png_read_png rows = malloc(sizeof *rows * _height); _data = malloc(_rowBytes * _height); if (EXPECT_NOT(rows == NULL || _data == NULL)) { if (rows != NULL) { free(rows); rows = NULL; } if (_data != NULL) { free(_data); _data = NULL; } OOLog(kOOLogAllocationFailure, @"Failed to allocate space (%zu bytes) for texture %@", _rowBytes * _height, _path); goto FAIL; } for (i = 0; i != _height; ++i) { rows[i] = ((png_bytep)_data) + i * _rowBytes; } png_read_image(png, rows); png_read_end(png, pngEndInfo); FAIL: free(rows); png_destroy_read_struct(&png, &pngInfo, &pngEndInfo); } - (void)readBytes:(png_bytep)bytes count:(png_size_t)count { // Check that we're within the file's bounds if (EXPECT_NOT(length - offset < count)) { NSString *message = [NSString stringWithFormat:@"attempt to read beyond end of file (%@), file may be truncated.", _path]; png_error(png, [message UTF8String]); // Will not return } assert(bytes != NULL); // Copy bytes memcpy(bytes, [fileData bytes] + offset, count); offset += count; } @end /* Minor detail: libpng 1.4.0 removed trailing .s from error and warning messages. It's also binary-incompatible with 1.2 and earlier, so it's reasonable to make this test at build time. */ #if PNG_LIBPNG_VER >= 10400 #define MSG_TERMINATOR "." #else #define MSG_TERMINATOR "" #endif static void PNGError(png_structp png, png_const_charp message) { OOPNGTextureLoader *loader = png_get_io_ptr(png); OOLog(@"texture.load.png.error", @"***** A PNG loading error occurred for %@: %s" MSG_TERMINATOR, [loader path], message); #if PNG_LIBPNG_VER >= 10500 png_longjmp(png, 1); #else longjmp(png_jmpbuf(png), 1); #endif } static void PNGWarning(png_structp png, png_const_charp message) { OOPNGTextureLoader *loader = png_get_io_ptr(png); OOLog(@"texture.load.png.warning", @"----- A PNG loading warning occurred for %@: %s" MSG_TERMINATOR, [loader path], message); } static void PNGRead(png_structp png, png_bytep bytes, png_size_t size) { OOPNGTextureLoader *loader = png_get_io_ptr(png); [loader readBytes:bytes count:size]; } oolite-1.82/src/Core/Materials/OOPixMap.h000066400000000000000000000102011256642440500201460ustar00rootroot00000000000000/* OOPixMap.h Types for low-level pixel map manipulation. Copyright (C) 2010-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOMaths.h" typedef uint_fast32_t OOPixMapDimension; // Note: dimensions are assumed to be less than 1048576 (2^20) pixels. #define OORoundUpToPowerOf2_PixMap OORoundUpToPowerOf2_32 typedef enum { kOOPixMapInvalidFormat = 0, kOOPixMapGrayscale = 1, kOOPixMapGrayscaleAlpha = 2, kOOPixMapRGBA = 4 } OOPixMapFormat; typedef struct OOPixMap { void *pixels; OOPixMapDimension width, height; OOPixMapFormat format; size_t rowBytes; size_t bufferSize; } OOPixMap; extern const OOPixMap kOONullPixMap; OOINLINE BOOL OOIsNullPixMap(OOPixMap pixMap) { return pixMap.pixels == NULL; } BOOL OOIsValidPixMap(OOPixMap pixMap); OOINLINE size_t OOMinimumPixMapBufferSize(OOPixMap pixMap) { return pixMap.rowBytes * pixMap.height; } /* OOMakePixMap() Stuff an OOPixMap struct. Returns kOONullPixMap if the result would be invalid. If rowBytes or bufferSize are zero, minimum valid values will be used. */ OOPixMap OOMakePixMap(void *pixels, OOPixMapDimension width, OOPixMapDimension height, OOPixMapFormat format, size_t rowBytes, size_t bufferSize); /* OOAllocatePixMap() Create an OOPixMap, allocating storage. If rowBytes or bufferSize are zero, minimum valid values will be used. */ OOPixMap OOAllocatePixMap(OOPixMapDimension width, OOPixMapDimension height, OOPixMapFormat format, size_t rowBytes, size_t bufferSize); /* OOFreePixMap() Deallocate a pixmap's buffer (with free()), and clear out the struct. */ void OOFreePixMap(OOPixMap *ioPixMap); /* OODuplicatePixMap() Create a pixmap with the same pixel contents as a source pixmap, and optional padding. If desiredSize is less than the required space for the pixmap, it will be ignored. The contents of padding bytes are unspecified. */ OOPixMap OODuplicatePixMap(OOPixMap srcPixMap, size_t desiredSize); /* OOResizePixMap() Set the size of a pixmap's buffer. Fails if specified size is smaller than required to fit the current pixels. */ BOOL OOResizePixMap(OOPixMap *ioPixMap, size_t desiredSize); /* OOCompactPixMap() Remove any trailing space in a pixmap's buffer, if possible. */ OOINLINE void OOCompactPixMap(OOPixMap *ioPixMap) { OOResizePixMap(ioPixMap, OOMinimumPixMapBufferSize(*ioPixMap)); } /* OOExpandPixMap() Expand pixmap to at least desiredSize bytes. Returns false on failure. */ BOOL OOExpandPixMap(OOPixMap *ioPixMap, size_t desiredSize); #ifndef NDEBUG void OODumpPixMap(OOPixMap pixMap, NSString *name); #else #define OODumpPixMap(p, n) do {} while (0) #endif BOOL OOIsValidPixMapFormat(OOPixMapFormat format); #ifndef NDEBUG unsigned short OOPixMapBytesPerPixelForFormat(OOPixMapFormat format) PURE_FUNC; #else OOINLINE unsigned short OOPixMapBytesPerPixelForFormat(OOPixMapFormat format) { // Currently, format values are component counts. This is subject to change. return format; } #endif OOINLINE unsigned short OOPixMapBytesPerPixel(OOPixMap pixMap) { return OOPixMapBytesPerPixelForFormat(pixMap.format); } NSString *OOPixMapFormatName(OOPixMapFormat format) PURE_FUNC; BOOL OOPixMapFormatHasAlpha(OOPixMapFormat format) PURE_FUNC; oolite-1.82/src/Core/Materials/OOPixMap.m000066400000000000000000000137031256642440500201650ustar00rootroot00000000000000/* OOPixMap.c Copyright (C) 2010-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOPixMap.h" const OOPixMap kOONullPixMap = { .pixels = NULL, .width = 0, .height = 0, .format = kOOPixMapInvalidFormat, .rowBytes = 0, .bufferSize = 0 }; BOOL OOIsValidPixMap(OOPixMap pixMap) { return pixMap.pixels != NULL && pixMap.width > 0 && pixMap.height > 0 && OOIsValidPixMapFormat(pixMap.format) && pixMap.rowBytes >= pixMap.width * OOPixMapBytesPerPixelForFormat(pixMap.format) && pixMap.bufferSize >= pixMap.rowBytes * pixMap.height; } OOPixMap OOMakePixMap(void *pixels, OOPixMapDimension width, OOPixMapDimension height, OOPixMapFormat format, size_t rowBytes, size_t bufferSize) { if (rowBytes == 0) rowBytes = width * OOPixMapBytesPerPixelForFormat(format); if (bufferSize == 0) bufferSize = rowBytes * height; OOPixMap result = { .pixels = pixels, .width = width, .height = height, .format = format, .rowBytes = rowBytes, .bufferSize = bufferSize }; if (OOIsValidPixMap(result)) return result; else return kOONullPixMap; } OOPixMap OOAllocatePixMap(OOPixMapDimension width, OOPixMapDimension height, OOPixMapFormat format, size_t rowBytes, size_t bufferSize) { // Create pixmap struct with dummy pixel pointer to test validity. OOPixMap pixMap = OOMakePixMap((void *)-1, width, height, format, rowBytes, bufferSize); if (EXPECT_NOT(!OOIsValidPixMap(pixMap))) return kOONullPixMap; pixMap.pixels = malloc(pixMap.bufferSize); if (EXPECT_NOT(pixMap.pixels == NULL)) return kOONullPixMap; return pixMap; } void OOFreePixMap(OOPixMap *ioPixMap) { if (EXPECT_NOT(ioPixMap == NULL)) return; free(ioPixMap->pixels); *ioPixMap = kOONullPixMap; } OOPixMap OODuplicatePixMap(OOPixMap srcPixMap, size_t desiredSize) { if (EXPECT_NOT(!OOIsValidPixMap(srcPixMap))) return kOONullPixMap; size_t minSize = OOMinimumPixMapBufferSize(srcPixMap); if (desiredSize < minSize) desiredSize = minSize; OOPixMap result = OOAllocatePixMap(srcPixMap.width, srcPixMap.width, srcPixMap.format, srcPixMap.rowBytes, desiredSize); if (EXPECT_NOT(!OOIsValidPixMap(result))) return kOONullPixMap; memcpy(result.pixels, srcPixMap.pixels, minSize); return result; } BOOL OOResizePixMap(OOPixMap *ioPixMap, size_t desiredSize) { if (EXPECT_NOT(ioPixMap == NULL || !OOIsValidPixMap(*ioPixMap))) return NO; if (desiredSize == ioPixMap->bufferSize) return YES; if (desiredSize < OOMinimumPixMapBufferSize(*ioPixMap)) return NO; void *newPixels = realloc(ioPixMap->pixels, desiredSize); if (newPixels != NULL) { ioPixMap->pixels = newPixels; ioPixMap->bufferSize = desiredSize; return YES; } else { return NO; } } BOOL OOExpandPixMap(OOPixMap *ioPixMap, size_t desiredSize) { if (EXPECT_NOT(ioPixMap == NULL || !OOIsValidPixMap(*ioPixMap))) return NO; if (desiredSize <= ioPixMap->bufferSize) return YES; return OOResizePixMap(ioPixMap, desiredSize); } #ifndef NDEBUG #import "Universe.h" #import "MyOpenGLView.h" void OODumpPixMap(OOPixMap pixMap, NSString *name) { if (!OOIsValidPixMap(pixMap)) return; MyOpenGLView *gameView = [UNIVERSE gameView]; switch (pixMap.format) { case kOOPixMapInvalidFormat: break; case kOOPixMapGrayscale: [gameView dumpGrayToFileNamed:name bytes:pixMap.pixels width:pixMap.width height:pixMap.height rowBytes:pixMap.rowBytes]; break; case kOOPixMapGrayscaleAlpha: [gameView dumpGrayAlphaToFileNamed:name bytes:pixMap.pixels width:pixMap.width height:pixMap.height rowBytes:pixMap.rowBytes]; break; case kOOPixMapRGBA: [gameView dumpRGBAToRGBFileNamed:[name stringByAppendingString:@" rgb"] andGrayFileNamed:[name stringByAppendingString:@" alpha"] bytes:pixMap.pixels width:pixMap.width height:pixMap.height rowBytes:pixMap.rowBytes]; break; } } #endif BOOL OOIsValidPixMapFormat(OOPixMapFormat format) { switch (format) { case kOOPixMapInvalidFormat: return NO; case kOOPixMapGrayscale: case kOOPixMapGrayscaleAlpha: case kOOPixMapRGBA: return YES; } return NO; } #ifndef NDEBUG unsigned short OOPixMapBytesPerPixelForFormat(OOPixMapFormat format) { switch (format) { case kOOPixMapInvalidFormat: return 0; case kOOPixMapGrayscale: return 1; case kOOPixMapGrayscaleAlpha: return 2; case kOOPixMapRGBA: return 4; } return -1; } #endif NSString *OOPixMapFormatName(OOPixMapFormat format) { switch (format) { case kOOPixMapInvalidFormat: return @"invalid"; case kOOPixMapGrayscale: return @"grayscale"; case kOOPixMapGrayscaleAlpha: return @"grayscale+alpha"; case kOOPixMapRGBA: return @"RGBA"; } return [NSString stringWithFormat:@"invalid<%i>", (int)format]; } BOOL OOPixMapFormatHasAlpha(OOPixMapFormat format) { switch (format) { case kOOPixMapInvalidFormat: return NO; case kOOPixMapGrayscale: return NO; case kOOPixMapGrayscaleAlpha: return YES; case kOOPixMapRGBA: return YES; } return NO; } oolite-1.82/src/Core/Materials/OOPixMapChannelOperations.h000066400000000000000000000054511256642440500235160ustar00rootroot00000000000000/* OOPixMapChannelOperations.h Utility to convert one channel of an RGBA texture into a greyscale texture. Copyright (C) 2010-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOPixMap.h" /* OOExtractPixMapChannel() Given a 4-channel pixmap, extract one channel, producing a single-channel pixmap. This is done in place, destroying the original data. ChannelIndex specifies which component to extract. This is a per-pixel byte index: 0 means bytes bytes 0, 4, 8 etc. will be used, 2 means bytes 2, 6, 10 etc. will be used. Returns false (without modifying the pixmap) if passed NULL, an invalid pixmap, a pixmap whose channel count is not 4, or a channel index greater than 3. */ BOOL OOExtractPixMapChannel(OOPixMap *ioPixMap, uint8_t channelIndex, BOOL compactWhenDone); /* OOPixMapToRGBA() Convert a pixmap to RGBA format. NOTE: if successful, this will free() the original buffer and replace it. */ BOOL OOPixMapToRGBA(OOPixMap *ioPixMap); /* OOPixMapModulateUniform() Multiply all pixels by specified per-component factors. Pixmap must be in RGBA format. The effect of using factors outside the range [0..1] is undefined. OOPixMapToRGBA() is called on ioPixMap. */ BOOL OOPixMapModulateUniform(OOPixMap *ioPixMap, float f0, float f1, float f2, float f3); /* OOPixMapModulatePixMap() Multiply each pixel of ioDstPixMap by the corresponding pixel of otherPixMap, writing the result to ioDstPixMap. OOPixMapToRGBA() is called on ioDstPixMap; otherPixMap must be RGBA. */ BOOL OOPixMapModulatePixMap(OOPixMap *ioDstPixMap, OOPixMap otherPixMap); /* OOPixMapAddPixMap() Add each pixel of otherPixMap to the corresponding pixel of ioDstPixMap, writing the result to ioDstPixMap. OOPixMapToRGBA() is called on ioDstPixMap; otherPixMap must be RGBA. */ BOOL OOPixMapAddPixMap(OOPixMap *ioDstPixMap, OOPixMap otherPixMap); oolite-1.82/src/Core/Materials/OOPixMapChannelOperations.m000066400000000000000000000215651256642440500235270ustar00rootroot00000000000000/* OOPixMapChannelOperations.m Copyright (C) 2010-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "OOPixMapChannelOperations.h" #import "OOCPUInfo.h" static void ExtractChannel_4(OOPixMap *ioPixMap, uint8_t channelIndex); static void ToRGBA_1(OOPixMap srcPx, OOPixMap dstPx); static void ToRGBA_2(OOPixMap srcPx, OOPixMap dstPx); static void ModulateUniform_4(OOPixMap pixMap, uint16_t f0, uint16_t f1, uint16_t f2, uint16_t f3); static void ModulatePixMap_4(OOPixMap mainPx, OOPixMap otherPx); static void AddPixMap_4(OOPixMap mainPx, OOPixMap otherPx); BOOL OOExtractPixMapChannel(OOPixMap *ioPixMap, uint8_t channelIndex, BOOL compactWhenDone) { if (EXPECT_NOT(ioPixMap == NULL || !OOIsValidPixMap(*ioPixMap) || ioPixMap->format != kOOPixMapRGBA || channelIndex > 3)) { return NO; } ExtractChannel_4(ioPixMap, channelIndex); ioPixMap->format = kOOPixMapGrayscale; ioPixMap->rowBytes = ioPixMap->width; if (compactWhenDone) { OOCompactPixMap(ioPixMap); } return YES; } static void ExtractChannel_4(OOPixMap *ioPixMap, uint8_t channelIndex) { NSCParameterAssert(ioPixMap != NULL); uint32_t *src; uint8_t *dst; uint_fast8_t shift; uint_fast32_t xCount, y; dst = ioPixMap->pixels; shift = 8 * channelIndex; for (y = 0; y < ioPixMap->height; y++) { src = (uint32_t *)((char *)ioPixMap->pixels + y * ioPixMap->rowBytes); xCount = ioPixMap->width; do { *dst++ = (*src++ >> shift) & 0xFF; } while (--xCount); } } BOOL OOPixMapToRGBA(OOPixMap *ioPixMap) { if (EXPECT_NOT(ioPixMap == NULL || !OOIsValidPixMap(*ioPixMap))) return NO; if (ioPixMap->format == kOOPixMapRGBA) return YES; OOPixMap temp = OOAllocatePixMap(ioPixMap->width, ioPixMap->height, 4, 0, 0); if (EXPECT_NOT(OOIsNullPixMap(temp))) return NO; BOOL OK = NO; switch (ioPixMap->format) { case kOOPixMapGrayscale: ToRGBA_1(*ioPixMap, temp); OK = YES; break; case kOOPixMapGrayscaleAlpha: ToRGBA_2(*ioPixMap, temp); OK = YES; break; case kOOPixMapRGBA: case kOOPixMapInvalidFormat: OK = NO; break; // No default, because -Wswitch-enum is our friend. } if (OK) { free(ioPixMap->pixels); *ioPixMap = temp; } else { free(temp.pixels); } return OK; } static void ToRGBA_1(OOPixMap srcPx, OOPixMap dstPx) { NSCParameterAssert(OOPixMapBytesPerPixel(srcPx) == 1 && dstPx.format == kOOPixMapRGBA && srcPx.width == dstPx.width && srcPx.height == dstPx.height); uint8_t *src; uint32_t *dst; uint_fast32_t xCount, y; dst = dstPx.pixels; for (y = 0; y < srcPx.height; y++) { src = (uint8_t *)((char *)srcPx.pixels + y * srcPx.rowBytes); xCount = srcPx.width; do { *dst++ = (*src++ * 0x00010101) | 0xFF000000; } while (--xCount); } } static void ToRGBA_2(OOPixMap srcPx, OOPixMap dstPx) { NSCParameterAssert(OOPixMapBytesPerPixel(srcPx) == 2 && dstPx.format == kOOPixMapRGBA && srcPx.width == dstPx.width && srcPx.height == dstPx.height); uint16_t *src; uint_fast32_t px; uint32_t *dst; uint_fast32_t xCount, y; dst = dstPx.pixels; for (y = 0; y < srcPx.height; y++) { src = (uint16_t *)((char *)srcPx.pixels + y * srcPx.rowBytes); xCount = srcPx.width; do { px = *src++; #if OOLITE_BIG_ENDIAN *dst++ = (((px & 0xFF00) >> 8) * 0x00010101) | ((px & 0x00FF) << 24); #elif OOLITE_LITTLE_ENDIAN *dst++ = ((px & 0x00FF) * 0x00010101) | ((px & 0xFF00) << 16); #else #error Unknown byte order. #endif } while (--xCount); } } BOOL OOPixMapModulateUniform(OOPixMap *ioPixMap, float f0, float f1, float f2, float f3) { if (EXPECT_NOT(ioPixMap == NULL || !OOIsValidPixMap(*ioPixMap))) return NO; if (EXPECT_NOT(!OOPixMapToRGBA(ioPixMap))) return NO; ModulateUniform_4(*ioPixMap, f0 * 256.0f, f1 * 256.0f, f2 * 256.0f, f3 * 256.0f); return YES; } static void ModulateUniform_4(OOPixMap pixMap, uint16_t f3, uint16_t f2, uint16_t f1, uint16_t f0) { NSCParameterAssert(OOPixMapBytesPerPixel(pixMap) == 4); uint32_t *curr; uint_fast32_t px; uint_fast32_t p0, p1, p2, p3; uint_fast32_t xCount, y; for (y = 0; y < pixMap.height; y++) { curr = (uint32_t *)((char *)pixMap.pixels + y * pixMap.rowBytes); xCount = pixMap.width; do { px = *curr; /* Principle of operation: Each pixel component is in the range 0..0xFF. Each constant factor component is in the range 0..0x100. Multiplying them therefore gives us a result in the range 0x0000..0xFF00. The bottom byte is discarded by shifting and masking. */ p0 = px & 0xFF000000; p0 = ((p0 >> 8) * f0) & 0xFF000000; p1 = px & 0x00FF0000; p1 = ((p1 * f1) >> 8) & 0x00FF0000; p2 = px & 0x0000FF00; p2 = ((p2 * f2) >> 8) & 0x0000FF00; p3 = px & 0x000000FF; p3 = ((p3 * f3) >> 8) & 0x000000FF; px = p0 | p1 | p2 | p3; *curr++ = px; } while (--xCount); } } BOOL OOPixMapModulatePixMap(OOPixMap *ioDstPixMap, OOPixMap otherPixMap) { if (EXPECT_NOT(ioDstPixMap == NULL || !OOIsValidPixMap(*ioDstPixMap))) return NO; if (EXPECT_NOT(!OOIsValidPixMap(otherPixMap) || otherPixMap.format != kOOPixMapRGBA)) return NO; if (EXPECT_NOT(!OOPixMapToRGBA(ioDstPixMap))) return NO; if (EXPECT_NOT(ioDstPixMap->width != otherPixMap.width || ioDstPixMap->height != otherPixMap.height)) return NO; ModulatePixMap_4(*ioDstPixMap, otherPixMap); return YES; } static void ModulatePixMap_4(OOPixMap mainPx, OOPixMap otherPx) { uint32_t *dst, *other; uint_fast32_t px; uint_fast16_t m0, m1, m2, m3; uint_fast16_t o0, o1, o2, o3; uint_fast32_t xCount, y; for (y = 0; y < mainPx.height; y++) { dst = (uint32_t *)((char *)mainPx.pixels + y * mainPx.rowBytes); other = (uint32_t *)((char *)otherPx.pixels + y * otherPx.rowBytes); xCount = mainPx.width; do { px = *dst; m0 = (px >> 24) & 0xFF; m1 = (px >> 16) & 0xFF; m2 = (px >> 8) & 0xFF; m3 = px & 0xFF; px = *other; o0 = (px >> 24) & 0xFF; o1 = (px >> 16) & 0xFF; o2 = (px >> 8) & 0xFF; o3 = px & 0xFF; /* Unlike in ModulateUniform(), neither side here goes to 256, so we have to divide by 255 rather than shifting. However, the compiler should be able to optimize this to a multiplication by a magic number. */ m0 = (m0 * o0) / 255; m1 = (m1 * o1) / 255; m2 = (m2 * o2) / 255; m3 = (m3 * o3) / 255; *dst++ = ((uint_fast32_t)m0 << 24) | ((uint_fast32_t)m1 << 16) | (m2 << 8) | m3; other++; } while (--xCount); } } BOOL OOPixMapAddPixMap(OOPixMap *ioDstPixMap, OOPixMap otherPixMap) { if (EXPECT_NOT(ioDstPixMap == NULL || !OOIsValidPixMap(*ioDstPixMap))) return NO; if (EXPECT_NOT(!OOIsValidPixMap(otherPixMap) || otherPixMap.format != kOOPixMapRGBA)) return NO; if (EXPECT_NOT(!OOPixMapToRGBA(ioDstPixMap))) return NO; if (EXPECT_NOT(ioDstPixMap->width != otherPixMap.width || ioDstPixMap->height != otherPixMap.height)) return NO; AddPixMap_4(*ioDstPixMap, otherPixMap); return YES; } static void AddPixMap_4(OOPixMap mainPx, OOPixMap otherPx) { uint32_t *dst, *other; uint_fast32_t px; uint_fast32_t m02, m13; uint_fast32_t o02, o13; uint_fast32_t xCount, y; for (y = 0; y < mainPx.height; y++) { dst = (uint32_t *)((char *)mainPx.pixels + y * mainPx.rowBytes); other = (uint32_t *)((char *)otherPx.pixels + y * otherPx.rowBytes); xCount = mainPx.width; do { px = *dst; m02 = (px & 0xFF00FF00) >> 8; m13 = px & 0x00FF00FF; px = *other; o02 = (px & 0xFF00FF00) >> 8; o13 = px & 0x00FF00FF; /* Saturated adds, two components at a time. By masking out the overflow bits of each component, multiplying them by 0xFF and shifting right one byte, we get a mask that's oxFF for components that overflowed and 0x00 for components that did not, without any conditionals. */ m02 += o02; m02 |= ((m02 & 0x01000100) * 0xFF) >> 8; m13 += o13; m13 |= ((m13 & 0x01000100) * 0xFF) >> 8; *dst++ = ((m02 << 8) & 0xFF00FF00) | (m13 & 0x00FF00FF); other++; } while (--xCount); } } oolite-1.82/src/Core/Materials/OOPixMapTextureLoader.h000066400000000000000000000026451256642440500226730ustar00rootroot00000000000000/* OOPixMapTextureLoader.h Load a texture from a pixmap. The loader takes ownership of the pixmap. Copyright (C) 2010-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOTextureGenerator.h" #import "OOPixMap.h" @interface OOPixMapTextureLoader: OOTextureGenerator { @private OOPixMap _pixMap; uint32_t _texOptions; } - (id) initWithPixMap:(OOPixMap)pixMap textureOptions:(uint32_t)options freeWhenDone:(BOOL)freeWhenDone; @end oolite-1.82/src/Core/Materials/OOPixMapTextureLoader.m000066400000000000000000000047241256642440500227000ustar00rootroot00000000000000/* OOPixMapTextureLoader.m Copyright (C) 2010-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOPixMapTextureLoader.h" #import "OOTextureScaling.h" @implementation OOPixMapTextureLoader - (id) initWithPixMap:(OOPixMap)pixMap textureOptions:(uint32_t)options freeWhenDone:(BOOL)freeWhenDone { if ((self = [super initWithPath:[NSString stringWithFormat:@"OOPixMap@%p", self] options:options])) { if (freeWhenDone) _pixMap = pixMap; else _pixMap = OODuplicatePixMap(_pixMap, 0); _texOptions = OOApplyTextureOptionDefaults(options); if (!OOIsValidPixMap(_pixMap)) DESTROY(self); } return self; } - (void) dealloc { OOFreePixMap(&_pixMap); [super dealloc]; } - (void) loadTexture { // Generate mip maps if needed. if ((_texOptions & kOOTextureMinFilterMask) == kOOTextureMinFilterMipMap) { size_t size = OOMinimumPixMapBufferSize(_pixMap) * 4 / 3; BOOL generateMipMaps = OOExpandPixMap(&_pixMap, size); if (generateMipMaps) { OOGenerateMipMaps(_pixMap.pixels,_pixMap.width, _pixMap.height, _pixMap.format); } else { _texOptions = (_texOptions & ~kOOTextureMinFilterMask) | kOOTextureMinFilterMipMap; } } // Set up output ivars as per OOTextureLoader contract. _data = _pixMap.pixels; _width = _pixMap.width; _height = _pixMap.height; _rowBytes = _pixMap.rowBytes; _format = _pixMap.format; // Explicitly do not free pixels - ownership passes to texture. _pixMap.pixels = NULL; } - (uint32_t) textureOptions { return _texOptions; } @end oolite-1.82/src/Core/Materials/OOPlanetTextureGenerator.h000066400000000000000000000050311256642440500234300ustar00rootroot00000000000000/* OOPlanetTextureGenerator.h Generator for planet diffuse maps. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOTextureGenerator.h" #import "OOMaths.h" #define PERLIN_3D 0 @class OOPlanetNormalMapGenerator, OOPlanetAtmosphereGenerator; typedef struct OOPlanetTextureGeneratorInfo { RANROTSeed seed; unsigned width; unsigned height; // Planet parameters. float landFraction; float polarFraction; FloatRGB landColor; FloatRGB seaColor; FloatRGB deepSeaColor; FloatRGB paleLandColor; FloatRGB polarSeaColor; FloatRGB paleSeaColor; // Planet mixing coefficients. float mix_hi; float mix_oh; float mix_ih; float mix_polarCap; // Atmosphere parameters. float cloudAlpha; float cloudFraction; FloatRGB airColor; FloatRGB cloudColor; FloatRGB paleCloudColor; // Noise generation stuff. float *fbmBuffer; float *qBuffer; #if PERLIN_3D uint16_t *permutations; #endif } OOPlanetTextureGeneratorInfo; @interface OOPlanetTextureGenerator: OOTextureGenerator { @private OOPlanetTextureGeneratorInfo _info; unsigned _planetScale; OOPlanetNormalMapGenerator *_nMapGenerator; OOPlanetAtmosphereGenerator *_atmoGenerator; } - (id) initWithPlanetInfo:(NSDictionary *)planetInfo; + (OOTexture *) planetTextureWithInfo:(NSDictionary *)planetInfo; + (BOOL) generatePlanetTexture:(OOTexture **)texture andAtmosphere:(OOTexture **)atmosphere withInfo:(NSDictionary *)planetInfo; + (BOOL) generatePlanetTexture:(OOTexture **)texture secondaryTexture:(OOTexture **)secondaryTexture withInfo:(NSDictionary *)planetInfo; + (BOOL) generatePlanetTexture:(OOTexture **)texture secondaryTexture:(OOTexture **)secondaryTexture andAtmosphere:(OOTexture **)atmosphere withInfo:(NSDictionary *)planetInfo; @end oolite-1.82/src/Core/Materials/OOPlanetTextureGenerator.m000066400000000000000000001101101256642440500234300ustar00rootroot00000000000000/* OOPlanetTextureGenerator.m Generator for planet diffuse maps. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOStellarBody.h" #if NEW_PLANETS #define DEBUG_DUMP ( 0 && !defined(NDEBUG)) #define DEBUG_DUMP_RAW ( 1 && DEBUG_DUMP) #define POLAR_CAPS 1 #define ALBEDO_FACTOR 0.7f // Overall darkening of everything, allowing better contrast for snow and specular highlights. #import "OOPlanetTextureGenerator.h" #import "OOCollectionExtractors.h" #import "OOColor.h" #ifndef TEXGEN_TEST_RIG #import "OOTexture.h" #import "Universe.h" #endif #if DEBUG_DUMP #import "MyOpenGLView.h" #endif #define FREE(x) do { if (0) { void *x__ = x; x__ = x__; } /* Preceeding is for type checking only. */ void **x_ = (void **)&(x); free(*x_); *x_ = NULL; } while (0) #define PLANET_TEXTURE_OPTIONS (kOOTextureMinFilterLinear | kOOTextureMagFilterLinear | kOOTextureRepeatS | kOOTextureNoShrink) enum { kRandomBufferSize = 128 }; @interface OOPlanetTextureGenerator (Private) - (NSString *) cacheKeyForType:(NSString *)type; - (OOTextureGenerator *) normalMapGenerator; // Must be called before generator is enqueued for rendering. - (OOTextureGenerator *) atmosphereGenerator; // Must be called before generator is enqueued for rendering. #if DEBUG_DUMP_RAW - (void) dumpNoiseBuffer:(float *)noise; #endif @end /* The planet generator actually generates two textures when shaders are active, but the texture loader interface assumes we only load/generate one texture per loader. Rather than complicate that, we use a mock generator for the normal/light map. */ @interface OOPlanetNormalMapGenerator: OOTextureGenerator { @private NSString *_cacheKey; RANROTSeed _seed; } - (id) initWithCacheKey:(NSString *)cacheKey seed:(RANROTSeed)seed; - (void) completeWithData:(void *)data width:(unsigned)width height:(unsigned)height; @end // Doing the same as above for the atmosphere. @interface OOPlanetAtmosphereGenerator: OOTextureGenerator { @private NSString *_cacheKey; RANROTSeed _seed; OOPlanetTextureGenerator *_parent; } - (id) initWithCacheKey:(NSString *)cacheKey seed:(RANROTSeed)seed andParent:(OOPlanetTextureGenerator *)parent; - (void) completeWithData:(void *)data width:(unsigned)width height:(unsigned)height; @end static FloatRGB FloatRGBFromDictColor(NSDictionary *dictionary, NSString *key); static BOOL FillFBMBuffer(OOPlanetTextureGeneratorInfo *info); static float QFactor(float *accbuffer, int x, int y, unsigned width, float polar_y_value, float bias, float polar_y); static float GetQ(float *qbuffer, int x, int y, unsigned width, unsigned height, unsigned widthMask, unsigned heightMask); static FloatRGB Blend(float fraction, FloatRGB a, FloatRGB b); static float BlendAlpha(float fraction, float a, float b); static void SetMixConstants(OOPlanetTextureGeneratorInfo *info, float temperatureFraction); static FloatRGBA CloudMix(OOPlanetTextureGeneratorInfo *info, float q, float nearPole); static FloatRGBA PlanetMix(OOPlanetTextureGeneratorInfo *info, float q, float nearPole); enum { #if PERLIN_3D && !TEXGEN_TEST_RIG kPlanetAspectRatio = 2, #else kPlanetAspectRatio = 1, // Ideally, aspect ratio would be 2:1 - keeping it as 1:1 for now - Kaks 20091211 #endif kPlanetScaleOffset = 8 - kPlanetAspectRatio, kPlanetScale256x256 = 1, kPlanetScale512x512, kPlanetScale1024x1024, kPlanetScale2048x2048, kPlanetScale4096x4096, kPlanetScaleReducedDetail = kPlanetScale512x512, kPlanetScaleFullDetail = kPlanetScale1024x1024, kPlanetScaleExtraDetail = kPlanetScale2048x2048 }; @implementation OOPlanetTextureGenerator - (id) initWithPlanetInfo:(NSDictionary *)planetInfo { OOLog(@"texture.planet.generate",@"Initialising planetary generator"); // AllowCubeMap not used yet but might be in future if ((self = [super initWithPath:[NSString stringWithFormat:@"OOPlanetTexture@%p", self] options:kOOTextureAllowCubeMap])) { OOLog(@"texture.planet.generate",@"Extracting parameters for generator %@",self); _info.landFraction = OOClamp_0_1_f([planetInfo oo_floatForKey:@"land_fraction" defaultValue:0.3]); _info.polarFraction = OOClamp_0_1_f([planetInfo oo_floatForKey:@"polar_fraction" defaultValue:0.05]); _info.landColor = FloatRGBFromDictColor(planetInfo, @"land_color"); _info.seaColor = FloatRGBFromDictColor(planetInfo, @"sea_color"); _info.paleLandColor = FloatRGBFromDictColor(planetInfo, @"polar_land_color"); _info.polarSeaColor = FloatRGBFromDictColor(planetInfo, @"polar_sea_color"); [[planetInfo objectForKey:@"noise_map_seed"] getValue:&_info.seed]; if ([planetInfo objectForKey:@"cloud_alpha"]) { OOLog(@"texture.planet.generate",@"Extracting atmosphere parameters"); // we have an atmosphere: _info.cloudAlpha = [planetInfo oo_floatForKey:@"cloud_alpha" defaultValue:1.0f]; _info.cloudFraction = OOClamp_0_1_f([planetInfo oo_floatForKey:@"cloud_fraction" defaultValue:0.3]); _info.cloudColor = FloatRGBFromDictColor(planetInfo, @"cloud_color"); _info.paleCloudColor = FloatRGBFromDictColor(planetInfo, @"polar_cloud_color"); } #ifndef TEXGEN_TEST_RIG if ([UNIVERSE detailLevel] < DETAIL_LEVEL_SHADERS || [planetInfo oo_boolForKey:@"isMiniature" defaultValue:NO]) { _planetScale = kPlanetScaleReducedDetail; } else if ([UNIVERSE detailLevel] == DETAIL_LEVEL_SHADERS) { _planetScale = kPlanetScaleFullDetail; } else { _planetScale = kPlanetScaleExtraDetail; } #else _planetScale = kPlanetScale4096x4096; #endif } return self; } + (OOTexture *) planetTextureWithInfo:(NSDictionary *)planetInfo { OOTexture *result = nil; OOPlanetTextureGenerator *generator = [[self alloc] initWithPlanetInfo:planetInfo]; if (generator != nil) { result = [OOTexture textureWithGenerator:generator]; [generator release]; } return result; } + (BOOL) generatePlanetTexture:(OOTexture **)texture andAtmosphere:(OOTexture **)atmosphere withInfo:(NSDictionary *)planetInfo { NSParameterAssert(texture != NULL); OOPlanetTextureGenerator *diffuseGen = [[[self alloc] initWithPlanetInfo:planetInfo] autorelease]; if (diffuseGen == nil) return NO; OOTextureGenerator *atmoGen = [diffuseGen atmosphereGenerator]; if (atmoGen == nil) return NO; *atmosphere = [OOTexture textureWithGenerator:atmoGen]; if (*atmosphere == nil) return NO; *texture = [OOTexture textureWithGenerator:diffuseGen]; return *texture != nil; } + (BOOL) generatePlanetTexture:(OOTexture **)texture secondaryTexture:(OOTexture **)secondaryTexture withInfo:(NSDictionary *)planetInfo { NSParameterAssert(texture != NULL); OOPlanetTextureGenerator *diffuseGen = [[[self alloc] initWithPlanetInfo:planetInfo] autorelease]; if (diffuseGen == nil) return NO; if (secondaryTexture != NULL) { OOTextureGenerator *normalGen = [diffuseGen normalMapGenerator]; if (normalGen == nil) return NO; *secondaryTexture = [OOTexture textureWithGenerator:normalGen]; if (*secondaryTexture == nil) return NO; } *texture = [OOTexture textureWithGenerator:diffuseGen]; return *texture != nil; } + (BOOL) generatePlanetTexture:(OOTexture **)texture secondaryTexture:(OOTexture **)secondaryTexture andAtmosphere:(OOTexture **)atmosphere withInfo:(NSDictionary *)planetInfo { NSParameterAssert(texture != NULL); OOPlanetTextureGenerator *diffuseGen = [[[self alloc] initWithPlanetInfo:planetInfo] autorelease]; if (diffuseGen == nil) return NO; if (secondaryTexture != NULL) { OOTextureGenerator *normalGen = [diffuseGen normalMapGenerator]; if (normalGen == nil) return NO; *secondaryTexture = [OOTexture textureWithGenerator:normalGen]; if (*secondaryTexture == nil) return NO; } OOTextureGenerator *atmoGen = [diffuseGen atmosphereGenerator]; if (atmoGen == nil) return NO; *atmosphere = [OOTexture textureWithGenerator:atmoGen]; if (*atmosphere == nil) { if (secondaryTexture != NULL) { *secondaryTexture = nil; } return NO; } OOLog(@"texture.planet.generate",@"Generator %@ has atmosphere %@",diffuseGen,*atmosphere); *texture = [OOTexture textureWithGenerator:diffuseGen]; return *texture != nil; } - (void) dealloc { DESTROY(_nMapGenerator); DESTROY(_atmoGenerator); [super dealloc]; } - (NSString *) descriptionComponents { return [NSString stringWithFormat:@"seed: %u,%u land: %g", _info.seed.high, _info.seed.low, _info.landFraction]; } - (uint32_t) textureOptions { return PLANET_TEXTURE_OPTIONS; } - (NSString *) cacheKey { NSString *type =(_nMapGenerator == nil) ? @"diffuse-baked" : @"diffuse-raw"; if (_atmoGenerator != nil) type = [NSString stringWithFormat:@"%@-atmo", type]; return [self cacheKeyForType:type]; } - (NSString *) cacheKeyForType:(NSString *)type { return [NSString stringWithFormat:@"OOPlanetTextureGenerator-%@@%u\n%u,%u/%g/%u,%u/%f,%f,%f/%f,%f,%f/%f,%f,%f/%f,%f,%f", type, _planetScale, _info.width, _info.height, _info.landFraction, _info.seed.high, _info.seed.low, _info.landColor.r, _info.landColor.g, _info.landColor.b, _info.seaColor.r, _info.seaColor.g, _info.seaColor.b, _info.paleLandColor.r, _info.paleLandColor.g, _info.paleLandColor.b, _info.polarSeaColor.r, _info.polarSeaColor.g, _info.polarSeaColor.b]; } - (OOTextureGenerator *) normalMapGenerator { if (_nMapGenerator == nil) { _nMapGenerator = [[OOPlanetNormalMapGenerator alloc] initWithCacheKey:[self cacheKeyForType:@"normal"] seed:_info.seed]; } return _nMapGenerator; } - (OOTextureGenerator *) atmosphereGenerator { if (_atmoGenerator == nil) { _atmoGenerator = [[OOPlanetAtmosphereGenerator alloc] initWithCacheKey:[self cacheKeyForType:@"atmo"] seed:_info.seed andParent:self]; } return _atmoGenerator; } - (BOOL)getResult:(OOPixMap *)outData format:(OOTextureDataFormat *)outFormat width:(uint32_t *)outWidth height:(uint32_t *)outHeight { BOOL waiting = NO; if (![self isReady]) { waiting = true; OOLog(@"texture.planet.generate.wait", @"%s generator %@", "Waiting for", self); } BOOL result = [super getResult:outData format:outFormat originalWidth:outWidth originalHeight:outHeight]; if (waiting) { OOLog(@"texture.planet.generate.dequeue", @"%s generator %@", result ? "Dequeued" : "Failed to dequeue", self); } else { OOLog(@"texture.planet.generate.dequeue", @"%s generator %@ without waiting.", result ? "Dequeued" : "Failed to dequeue", self); } return result; } - (void) loadTexture { OOLog(@"texture.planet.generate.begin", @"Started generator %@", self); BOOL success = NO; BOOL generateNormalMap = (_nMapGenerator != nil); BOOL generateAtmosphere = (_atmoGenerator != nil); uint8_t *buffer = NULL, *px = NULL; uint8_t *nBuffer = NULL, *npx = NULL; uint8_t *aBuffer = NULL, *apx = NULL; float *randomBuffer = NULL; _height = _info.height = 1 << (_planetScale + kPlanetScaleOffset); _width = _info.width = _height * kPlanetAspectRatio; #define FAIL_IF(cond) do { if (EXPECT_NOT(cond)) goto END; } while (0) #define FAIL_IF_NULL(x) FAIL_IF((x) == NULL) buffer = malloc(4 * _width * _height); FAIL_IF_NULL(buffer); px = buffer; if (generateNormalMap) { nBuffer = malloc(4 * _width * _height); FAIL_IF_NULL(nBuffer); npx = nBuffer; } if (generateAtmosphere) { aBuffer = malloc(4 * _width * _height); FAIL_IF_NULL(aBuffer); apx = aBuffer; } FAIL_IF(!FillFBMBuffer(&_info)); #if DEBUG_DUMP_RAW [self dumpNoiseBuffer:_info.fbmBuffer]; #endif float paleClouds = (_info.cloudFraction * _info.fbmBuffer[0] < 1.0f - _info.cloudFraction) ? 0.0f : 1.0f; float poleValue = (_info.landFraction > 0.5f) ? 0.5f * _info.landFraction : 0.0f; float seaBias = _info.landFraction - 1.0f; _info.paleSeaColor = Blend(0.35f, _info.polarSeaColor, Blend(0.7f, _info.seaColor, _info.landColor)); float normalScale = 1 << _planetScale; if (!generateNormalMap) normalScale *= 3.0f; // Deep sea colour: sea darker past the continental shelf. _info.deepSeaColor = Blend(0.85f, _info.seaColor, (FloatRGB){ 0, 0, 0 }); int x, y; FloatRGBA color; Vector norm; float q, yN, yS, yW, yE, nearPole; GLfloat shade; float rHeight = 1.0f / _height; float fy, fHeight = _height; // The second parameter is the temperature fraction. Most favourable: 1.0f, little ice. Most unfavourable: 0.0f, frozen planet. TODO: make it dependent on ranrot / planetinfo key... SetMixConstants(&_info, 1.0f-_info.polarFraction); // no need to recalculate them inside each loop! // first pass, calculate q. _info.qBuffer = malloc(_width * _height * sizeof (float)); FAIL_IF_NULL(_info.qBuffer); for (y = (int)_height - 1, fy = (float)y; y >=0; y--, fy--) { nearPole = (2.0f * fy - fHeight) * rHeight; nearPole *= nearPole; for (x = (int)_width - 1; x >=0; x--) { _info.qBuffer[y * _width + x] = QFactor(_info.fbmBuffer, x, y, _width, poleValue, seaBias, nearPole); } } // second pass, use q. float cloudFraction = _info.cloudFraction; unsigned widthMask = _width - 1; unsigned heightMask = _height - 1; for (y = (int)_height - 1, fy = (float)y; y >= 0; y--, fy--) { nearPole = (2.0f * fy - fHeight) * rHeight; nearPole *= nearPole; for (x = (int)_width - 1; x >= 0; x--) { q = _info.qBuffer[y * _width + x]; // no need to use GetQ, x and y are always within bounds. yN = GetQ(_info.qBuffer, x, y - 1, _width, _height, widthMask, heightMask); // recalculates x & y if they go out of bounds. yS = GetQ(_info.qBuffer, x, y + 1, _width, _height, widthMask, heightMask); yW = GetQ(_info.qBuffer, x - 1, y, _width, _height, widthMask, heightMask); yE = GetQ(_info.qBuffer, x + 1, y, _width, _height, widthMask, heightMask); color = PlanetMix(&_info, q, nearPole); norm = vector_normal(make_vector(normalScale * (yE - yW), normalScale * (yN - yS), 1.0f)); if (generateNormalMap) { shade = 1.0f; // Flatten the sea. norm = OOVectorInterpolate(norm, kBasisZVector, color.a); // Put norm in normal map, scaled from [-1..1] to [0..255]. *npx++ = 127.5f * (norm.y + 1.0f); *npx++ = 127.5f * (-norm.x + 1.0f); *npx++ = 127.5f * (norm.z + 1.0f); *npx++ = 255.0f * color.a; // Specular channel. } else { // Terrain shading - lambertian lighting from straight above. shade = norm.z; /* We don't want terrain shading in the sea. The alpha channel of color is a measure of "seaishness" for the specular map, so we can recycle that to avoid branching. -- Ahruman */ shade += color.a - color.a * shade; // equivalent to - but slightly faster than - previous implementation. } *px++ = 255.0f * color.r * shade; *px++ = 255.0f * color.g * shade; *px++ = 255.0f * color.b * shade; *px++ = 0; // FIXME: light map goes here. if (generateAtmosphere) { q = QFactor(_info.fbmBuffer, x, y, _width, paleClouds, cloudFraction, nearPole); color = CloudMix(&_info, q, nearPole); *apx++ = 255.0f * color.r; *apx++ = 255.0f * color.g; *apx++ = 255.0f * color.b; *apx++ = 255.0f * color.a * _info.cloudAlpha; } } } success = YES; _format = kOOTextureDataRGBA; END: FREE(_info.fbmBuffer); FREE(_info.qBuffer); FREE(randomBuffer); if (success) { _data = buffer; if (generateNormalMap) [_nMapGenerator completeWithData:nBuffer width:_width height:_height]; if (generateAtmosphere) [_atmoGenerator completeWithData:aBuffer width:_width height:_height]; } else { FREE(buffer); FREE(nBuffer); FREE(aBuffer); } DESTROY(_nMapGenerator); DESTROY(_atmoGenerator); OOLog(@"texture.planet.generate.complete", @"Completed generator %@ %@successfully", self, success ? @"" : @"un"); #if DEBUG_DUMP if (success) { NSString *diffuseName = [NSString stringWithFormat:@"planet-%u-%u-diffuse-new", _info.seed.high, _info.seed.low]; NSString *lightsName = [NSString stringWithFormat:@"planet-%u-%u-lights-new", _info.seed.high, _info.seed.low]; [[UNIVERSE gameView] dumpRGBAToRGBFileNamed:diffuseName andGrayFileNamed:lightsName bytes:buffer width:_width height:_height rowBytes:_width * 4]; } #endif } #if DEBUG_DUMP_RAW - (void) dumpNoiseBuffer:(float *)noise { NSString *noiseName = [NSString stringWithFormat:@"planet-%u-%u-noise-new", _info.seed.high, _info.seed.low]; uint8_t *noisePx = malloc(_width * _height); unsigned x, y; for (y = 0; y < _height; y++) { for (x = 0; x < _width; x++) { noisePx[y * _width + x] = 255.0f * noise[y * _width + x]; } } [[UNIVERSE gameView] dumpGrayToFileNamed:noiseName bytes:noisePx width:_width height:_height rowBytes:_width]; FREE(noisePx); } #endif @end OOINLINE float Lerp(float v0, float v1, float fraction) { // Linear interpolation - equivalent to v0 * (1.0f - fraction) + v1 * fraction. return v0 + fraction * (v1 - v0); } static FloatRGB Blend(float fraction, FloatRGB a, FloatRGB b) { return (FloatRGB) { Lerp(b.r, a.r, fraction), Lerp(b.g, a.g, fraction), Lerp(b.b, a.b, fraction) }; } static float BlendAlpha(float fraction, float a, float b) { return Lerp(b, a, fraction); } static FloatRGBA CloudMix(OOPlanetTextureGeneratorInfo *info, float q, float nearPole) { //#define AIR_ALPHA (0.15f) //#define CLOUD_ALPHA (1.0f) // CIM: make distinction between cloud and not-cloud bigger #define AIR_ALPHA (0.05f) #define CLOUD_ALPHA (2.0f) #define POLAR_BOUNDARY (0.33f) #define CLOUD_BOUNDARY (0.5f) #define RECIP_CLOUD_BOUNDARY (1.0f / CLOUD_BOUNDARY) FloatRGB cloudColor = info->cloudColor; float alpha = info->cloudAlpha, portion = 0.0f; q -= CLOUD_BOUNDARY * 0.5f; if (nearPole > POLAR_BOUNDARY) { portion = nearPole > POLAR_BOUNDARY + 0.2f ? 1.0f : (nearPole - POLAR_BOUNDARY) * 5.0f; cloudColor = Blend(portion, info->paleCloudColor, cloudColor); portion = nearPole > POLAR_BOUNDARY + 0.625f ? 1.0f : (nearPole - POLAR_BOUNDARY) * 1.6f; } if (q <= 0.0f) { if (q >= -CLOUD_BOUNDARY) { alpha *= BlendAlpha(-q * 0.5f * RECIP_CLOUD_BOUNDARY + 0.5f, CLOUD_ALPHA, AIR_ALPHA); } else { alpha *= CLOUD_ALPHA; } } else { if (q < CLOUD_BOUNDARY) { alpha *= BlendAlpha( q * 0.5f * RECIP_CLOUD_BOUNDARY + 0.5f, AIR_ALPHA,CLOUD_ALPHA); } else { alpha *= AIR_ALPHA; } } // magic numbers! at the poles we have fairly thin air. alpha *= BlendAlpha(portion, 0.6f, 1.0f); if (alpha > 1.0) { alpha = 1.0; } return (FloatRGBA){ cloudColor.r, cloudColor.g, cloudColor.b, alpha }; } static FloatRGBA PlanetMix(OOPlanetTextureGeneratorInfo *info, float q, float nearPole) { #define RECIP_COASTLINE_PORTION (160.0f) #define COASTLINE_PORTION (1.0f / RECIP_COASTLINE_PORTION) #define SHALLOWS (2.0f * COASTLINE_PORTION) // increased shallows area. #define RECIP_SHALLOWS (1.0f / SHALLOWS) // N.B.: DEEPS can't be more than RECIP_COASTLINE_PORTION * COASTLINE_PORTION! #define DEEPS (40.0f * COASTLINE_PORTION) #define RECIP_DEEPS (1.0f / DEEPS) const FloatRGB white = { 1.0f, 1.0f, 1.0f }; FloatRGB diffuse; // windows specular 'fix': 0 was showing pitch black continents when on the dark side, 0.01 shows the same shading as on Macs. // TODO: a less hack-like fix. float specular = 0.01f; if (q <= 0.0f) { // Below datum - sea. if (q > -SHALLOWS) { // Coastal waters. diffuse = Blend(-q * RECIP_SHALLOWS, info->seaColor, info->paleSeaColor); specular = 1.0f; } else { // Open sea. if (q > -DEEPS) diffuse = Blend(-q * RECIP_DEEPS, info->deepSeaColor, info->seaColor); else diffuse = info->deepSeaColor; specular = Lerp(1.0f, 0.85f, -q); } } else if (q < COASTLINE_PORTION) { // Coastline. specular = q * RECIP_COASTLINE_PORTION; diffuse = Blend(specular, info->landColor, info->paleSeaColor); specular = 1.0f - specular; } else if (q > 1.0f) { // High up - snow-capped peaks. With overrides q can range between -2 to +2. diffuse = white; } else if (q > info->mix_hi) { diffuse = Blend((q - info->mix_hi) * info->mix_ih, white, info->paleLandColor); // Snowline. } else { // Normal land. diffuse = Blend((info->mix_hi - q) * info->mix_oh, info->landColor, info->paleLandColor); } #if POLAR_CAPS // (q > mix_polarCap + mix_polarCap - nearPole) == ((nearPole + q) / 2 > mix_polarCap) float phi = info->mix_polarCap + info->mix_polarCap - nearPole; if (q > phi) // (nearPole + q) / 2 > pole { // thinner to thicker ice. specular = q > phi + 0.02f ? 1.0f : 0.2f + (q - phi) * 40.0f; // (q - phi) * 40 == ((q-phi) / 0.02) * 0.8 //diffuse = info->polarSeaColor; diffuse = Blend(specular, info->polarSeaColor, diffuse); specular = specular * 0.5f; // softer contours under ice, but still contours. } #endif return (FloatRGBA){ diffuse.r, diffuse.g, diffuse.b, specular }; } static FloatRGB FloatRGBFromDictColor(NSDictionary *dictionary, NSString *key) { OOColor *color = [dictionary objectForKey:key]; NSCAssert1([color isKindOfClass:[OOColor class]], @"Expected OOColor, got %@", [color class]); return (FloatRGB){ [color redComponent] * ALBEDO_FACTOR, [color greenComponent] * ALBEDO_FACTOR, [color blueComponent] * ALBEDO_FACTOR }; } OOINLINE float Hermite(float q) { return 3.0f * q * q - 2.0f * q * q * q; } #if __BIG_ENDIAN__ #define iman_ 1 #else #define iman_ 0 #endif // (same behaviour as, but faster than, FLOAT->INT) //Works OK for -32728 to 32727.99999236688 OOINLINE int32_t fast_floor(double val) { val += 68719476736.0 * 1.5; return (((int32_t*)&val)[iman_] >> 16); } static BOOL GenerateFBMNoise(OOPlanetTextureGeneratorInfo *info); static BOOL FillFBMBuffer(OOPlanetTextureGeneratorInfo *info) { NSCParameterAssert(info != NULL); // Allocate result buffer. info->fbmBuffer = calloc(info->width * info->height, sizeof (float)); if (info->fbmBuffer != NULL) { GenerateFBMNoise(info); return YES; } return NO; } #if PERLIN_3D enum { // Size of permutation buffer used to map integer coordinates to gradients. Must be power of two. kPermutationCount = 1 << 10, kPermutationMask = kPermutationCount - 1, // Number of different gradient vectors used. The most important thing is that the gradients are evenly distributed and sum to 0. kGradientCount = 12 }; #define LUT_DOT 1 #if !LUT_DOT static const Vector kGradients[kGradientCount] = { { 1, 1, 0 }, { -1, 1, 0 }, { 1, -1, 0 }, { -1, -1, 0 }, { 1, 0, 1 }, { -1, 0, 1 }, { 1, 0, -1 }, { -1, 0, -1 }, { 0, 1, 1 }, { 0, -1, 1 }, { 0, 1, -1 }, { 0, -1, -1 } }; #else static const uint8_t kGradients[kGradientCount][3] = { { 2, 2, 1 }, { 0, 2, 1 }, { 2, 0, 1 }, { 0, 0, 1 }, { 2, 1, 2 }, { 0, 1, 2 }, { 2, 1, 0 }, { 0, 1, 0 }, { 1, 2, 2 }, { 1, 0, 2 }, { 1, 2, 0 }, { 1, 0, 0 } }; /* Attempted speedup that didn't pan out, but might inspire something better. Since our gradient vectors' components are all -1, 0 or 1, we should be able to calculate the dot product without any multiplication at all, by simply summing the right combination of (x, y, z), (0, 0, 0) and (-x, -y, -z). This turns out to be slightly slower than using multiplication, even if the negations are precalculated. */ OOINLINE float TDot3(const uint8_t grad[3], float x, float y, float z) { float xt[3] = { -x, 0.0f, x }; float yt[3] = { -y, 0.0f, y }; float zt[3] = { -z, 0.0f, z }; return xt[grad[0]] + yt[grad[1]] + zt[grad[2]]; } #endif // Sample 3D noise function defined by kGradients and permutation table at point p. static float SampleNoise3D(OOPlanetTextureGeneratorInfo *info, Vector p) { uint16_t *permutations = info->permutations; // Split coordinates into integer and fractional parts. float fx = floor(p.x); float fy = floor(p.y); float fz = floor(p.z); int X = fx; int Y = fy; int Z = fz; float x = p.x - fx; float y = p.y - fy; float z = p.z - fz; // Select gradient for each corner. #define PERM(v) permutations[(v) & kPermutationMask] unsigned PZ0 = PERM(Z); unsigned PZ1 = PERM(Z + 1); unsigned PY0Z0 = PERM(Y + PZ0); unsigned PY1Z0 = PERM(Y + 1 + PZ0); unsigned PY0Z1 = PERM(Y + PZ1); unsigned PY1Z1 = PERM(Y + 1 + PZ1); unsigned gi000 = PERM(X + PY0Z0); unsigned gi010 = PERM(X + PY1Z0); unsigned gi100 = PERM(X + 1 + PY0Z0); unsigned gi110 = PERM(X + 1 + PY1Z0); unsigned gi001 = PERM(X + PY0Z1); unsigned gi011 = PERM(X + PY1Z1); unsigned gi101 = PERM(X + 1 + PY0Z1); unsigned gi111 = PERM(X + 1 + PY1Z1); #undef PERM // Calculate noise contributions from each of the eight corners. #if !LUT_DOT #define DOT3(idx, x_, y_, z_) dot_product(kGradients[(idx) % kGradientCount], (Vector){ (x_), (y_), (z_) }) #else #define DOT3(idx, x_, y_, z_) TDot3(kGradients[(idx) % kGradientCount], (x_), (y_), (z_)) #endif float x1 = x - 1.0f; float y1 = y - 1.0f; float z1 = z - 1.0f; float n000 = DOT3(gi000, x , y , z ); float n010 = DOT3(gi010, x , y1, z ); float n100 = DOT3(gi100, x1, y , z ); float n110 = DOT3(gi110, x1, y1, z ); float n001 = DOT3(gi001, x , y , z1); float n011 = DOT3(gi011, x , y1, z1); float n101 = DOT3(gi101, x1, y , z1); float n111 = DOT3(gi111, x1, y1, z1); #undef DOT3 // Compute the fade curve value for each of x, y, z float u = Hermite(x); float v = Hermite(y); float w = Hermite(z); // Interpolate along the contributions from each of the corners. float nx00 = Lerp(n000, n100, u); float nx01 = Lerp(n001, n101, u); float nx10 = Lerp(n010, n110, u); float nx11 = Lerp(n011, n111, u); float nxy0 = Lerp(nx00, nx10, v); float nxy1 = Lerp(nx01, nx11, v); float nxyz = Lerp(nxy0, nxy1, w); return nxyz; } /* Generate shuffled permutation order - each value from 0 to kPermutationCount - 1 occurs exactly once. This shuffling provides all the randomness in the resulting noise. Don't worry, though - for kPermutationCount = 1024 this allows for 4e2567 different noise maps, which is a lot more than RanRot will actually give us. */ static BOOL MakePermutationTable(OOPlanetTextureGeneratorInfo *info) { uint16_t *perms = malloc(sizeof *info->permutations * kPermutationCount); if (EXPECT_NOT(perms == NULL)) return NO; /* Fisher-Yates/Durstenfeld/Knuth shuffle, "inside-out" variant. Based on pseudocode from http://en.wikipedia.org/wiki/Fisher-Yates_shuffle When comparing to the pseudocode, note that it generates a one-based series, but this version generates a zero-based series. */ perms[0] = 0; uint16_t *curr = perms; uint16_t n; for (n = 1; n < kPermutationCount; n++) { uint16_t j = RanrotWithSeed(&info->seed) & kPermutationMask; *++curr = perms[j]; perms[j] = n - 1; } info->permutations = perms; return YES; } static BOOL GenerateFBMNoise(OOPlanetTextureGeneratorInfo *info) { BOOL OK = NO; FAIL_IF(!MakePermutationTable(info)); unsigned x, y, width = info->width, height = info->height; float lon, lat; // Longitude and latitude in radians. float dlon = 2.0f * M_PI / width; float dlat = M_PI / height; float *px = info->fbmBuffer; for (y = 0, lat = -M_PI_2; y < height; y++, lat += dlat) { float las = sin(lat); float lac = cos(lat); for (x = 0, lon = -M_PI; x < width; x++, lon += dlon) { // FIXME: in real life, we really don't want sin and cos per pixel. // Convert spherical coordinates to vector. float los = sin(lon); float loc = cos(lon); Vector p = { los * lac, las, loc * lac }; #if 1 // fBM unsigned octaveMask = 4; float octave = octaveMask; octaveMask -= 1; float scale = 0.4f; float sum = 0; while ((octaveMask + 1) < height) { Vector ps = vector_multiply_scalar(p, octave); sum += scale * SampleNoise3D(info, ps); octave *= 2.0f; octaveMask = (octaveMask << 1) | 1; scale *= 0.5f; } #else // Single octave p = vector_multiply_scalar(p, 4.0f); float sum = 0.5f * SampleNoise3D(info, p); #endif *px++ = sum + 0.5f; } } END: FREE(info->permutations); return OK; } #else // Old 2D value noise. static void FillRandomBuffer(float *randomBuffer, RANROTSeed seed) { unsigned i, len = kRandomBufferSize * kRandomBufferSize; for (i = 0; i < len; i++) { randomBuffer[i] = randfWithSeed(&seed); } } static void AddNoise(OOPlanetTextureGeneratorInfo *info, float *randomBuffer, float octave, unsigned octaveMask, float scale, float *qxBuffer, int *ixBuffer) { unsigned x, y; unsigned width = info->width, height = info->height; int ix, jx, iy, jy; float rr = octave / width; float fx, fy, qx, qy, rix, rjx, rfinal; float *dst = info->fbmBuffer; for (fy = 0, y = 0; y < height; fy++, y++) { qy = fy * rr; iy = fast_floor(qy); jy = (iy + 1) & octaveMask; qy = Hermite(qy - iy); iy &= (kRandomBufferSize - 1); jy &= (kRandomBufferSize - 1); for (fx = 0, x = 0; x < width; fx++, x++) { if (y == 0) { // first pass: initialise buffers. qx = fx * rr; ix = fast_floor(qx); qx -= ix; ix &= (kRandomBufferSize - 1); ixBuffer[x] = ix; qxBuffer[x] = Hermite(qx); } else { // later passes: grab the stored values. ix = ixBuffer[x]; qx = qxBuffer[x]; } jx = (ix + 1) & octaveMask; jx &= (kRandomBufferSize - 1); rix = Lerp(randomBuffer[iy * kRandomBufferSize + ix], randomBuffer[iy * kRandomBufferSize + jx], qx); rjx = Lerp(randomBuffer[jy * kRandomBufferSize + ix], randomBuffer[jy * kRandomBufferSize + jx], qx); rfinal = Lerp(rix, rjx, qy); *dst++ += scale * rfinal; } } } static BOOL GenerateFBMNoise(OOPlanetTextureGeneratorInfo *info) { // Allocate the temporary buffers we need in one fell swoop, to avoid administrative overhead. size_t randomBufferSize = kRandomBufferSize * kRandomBufferSize * sizeof (float); size_t qxBufferSize = info->width * sizeof (float); size_t ixBufferSize = info->width * sizeof (int); char *sharedBuffer = malloc(randomBufferSize + qxBufferSize + ixBufferSize); if (sharedBuffer == NULL) return NO; float *randomBuffer = (float *)sharedBuffer; float *qxBuffer = (float *)(sharedBuffer + randomBufferSize); int *ixBuffer = (int *)(sharedBuffer + randomBufferSize + qxBufferSize); // Get us some value noise. FillRandomBuffer(randomBuffer, info->seed); // Generate basic fBM noise. unsigned height = info->height; unsigned octaveMask = 8 * kPlanetAspectRatio; float octave = octaveMask; octaveMask -= 1; float scale = 0.5f; while ((octaveMask + 1) < height) { AddNoise(info, randomBuffer, octave, octaveMask, scale, qxBuffer, ixBuffer); octave *= 2.0f; octaveMask = (octaveMask << 1) | 1; scale *= 0.5f; } FREE(sharedBuffer); return YES; } #endif static float QFactor(float *accbuffer, int x, int y, unsigned width, float polar_y_value, float bias, float polar_y) { float q = accbuffer[y * width + x]; // 0.0 -> 1.0 q += bias; // Polar Y smooth. q = q * (1.0f - polar_y) + polar_y * polar_y_value; return q; } static float GetQ(float *qbuffer, int x, int y, unsigned width, unsigned height, unsigned widthMask, unsigned heightMask) { // Correct Y wrapping mode, unoptimised. //if (y < 0) { y = -y - 1; x += width / 2; } //else if (y >= height) { y = height - (y - height) - 1; x += width / 2; } // now let's wrap x. //x = x % width; // Correct Y wrapping mode, faster method. In the following lines of code, both // width and height are assumed to be powers of 2: 512, 1024, 2048, etc... if (y & height) { y = (y ^ heightMask) & heightMask; x += width >> 1; } // x wrapping. x &= widthMask; return qbuffer[y * width + x]; } static void SetMixConstants(OOPlanetTextureGeneratorInfo *info, float temperatureFraction) { info->mix_hi = 0.66667f * info->landFraction; info->mix_oh = 1.0f / info->mix_hi; info->mix_ih = 1.0f / (1.0f - info->mix_hi); info->mix_polarCap = temperatureFraction * (0.28f + 0.24f * info->landFraction); // landmasses make the polar cap proportionally bigger, but not too much bigger. } @implementation OOPlanetNormalMapGenerator - (id) initWithCacheKey:(NSString *)cacheKey seed:(RANROTSeed)seed { // AllowCubeMap not used yet but might be in future if ((self = [super initWithPath:[NSString stringWithFormat:@"OOPlanetNormalTexture@%p", self] options:kOOTextureAllowCubeMap])) { _cacheKey = [cacheKey copy]; _seed = seed; } return self; } - (void) dealloc { DESTROY(_cacheKey); [super dealloc]; } - (NSString *) cacheKey { return _cacheKey; } - (uint32_t) textureOptions { return PLANET_TEXTURE_OPTIONS; } - (BOOL) enqueue { /* This generator doesn't do any work, so it doesn't need to be queued at the normal time. (The alternative would be for it to block a work thread waiting for the real generator to complete, which seemed silly.) */ return YES; } - (void) loadTexture { // Do nothing. } - (void) completeWithData:(void *)data_ width:(unsigned)width_ height:(unsigned)height_ { _data = data_; _width = width_; _height = height_; _format = kOOTextureDataRGBA; // Enqueue so superclass can apply texture options and so forth. [super enqueue]; #if DEBUG_DUMP NSString *normalName = [NSString stringWithFormat:@"planet-%u-%u-normal-new", _seed.high, _seed.low]; NSString *specularName = [NSString stringWithFormat:@"planet-%u-%u-specular-new", _seed.high, _seed.low]; [[UNIVERSE gameView] dumpRGBAToRGBFileNamed:normalName andGrayFileNamed:specularName bytes:_data width:_width height:_height rowBytes:_width * 4]; #endif } @end @implementation OOPlanetAtmosphereGenerator - (id) initWithCacheKey:(NSString *)cacheKey seed:(RANROTSeed)seed andParent:(OOPlanetTextureGenerator *)parent { OOLog(@"texture.planet.generate",@"Initialising atmosphere generator %@",cacheKey); // AllowCubeMap not used yet but might be in future if ((self = [super initWithPath:[NSString stringWithFormat:@"OOPlanetAtmoTexture@%p", self] options:kOOTextureAllowCubeMap])) { _cacheKey = [cacheKey copy]; _seed = seed; _parent = [parent retain]; } return self; } - (void) dealloc { DESTROY(_cacheKey); DESTROY(_parent); [super dealloc]; } - (NSString *) cacheKey { return _cacheKey; } - (uint32_t) textureOptions { return PLANET_TEXTURE_OPTIONS; } - (BOOL) enqueue { return YES; } - (void) loadTexture { // Do nothing. } /* Because of the hack to avoid making texture loaders need to know * about multiple textures, when we call the atmosphere generator on a * planet with a from-file texture, things can occasionally go wrong * because this isn't queued until after it's ready, but might be * requested from the queue before then. So, when getResult is called, * it waits for its parent task to complete if it's not itself ready - * by the time the parent task is complete, this texture loader will * have been enqueued, and [super getResult] will then work * properly. - CIM 2014-04-05 */ - (BOOL) getResult:(OOPixMap *)result format:(OOTextureDataFormat *)outFormat originalWidth:(uint32_t *)outWidth originalHeight:(uint32_t *)outHeight { if (!_ready) { [[OOAsyncWorkManager sharedAsyncWorkManager] waitForTaskToComplete:_parent]; } return [super getResult:result format:outFormat originalWidth:outWidth originalHeight:outHeight]; } - (void) completeWithData:(void *)data_ width:(unsigned)width_ height:(unsigned)height_ { OOLog(@"texture.planet.generate",@"Completing atmosphere generator"); _data = data_; _width = width_; _height = height_; _format = kOOTextureDataRGBA; // Enqueue so superclass can apply texture options and so forth. [super enqueue]; #if DEBUG_DUMP NSString *rgbName = [NSString stringWithFormat:@"planet-%u-%u-atmosphere-rgb-new", _seed.high, _seed.low]; NSString *alphaName = [NSString stringWithFormat:@"planet-%u-%u-atmosphere-alpha-new", _seed.high, _seed.low]; [[UNIVERSE gameView] dumpRGBAToRGBFileNamed:rgbName andGrayFileNamed:alphaName bytes:_data width:_width height:_height rowBytes:_width * 4]; #endif } @end #endif // NEW_PLANETS oolite-1.82/src/Core/Materials/OOShaderMaterial.h000066400000000000000000000152121256642440500216440ustar00rootroot00000000000000/* OOShaderMaterial.h Managers a combination of a shader program, textures and uniforms. Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOBasicMaterial.h" #import "OOWeakReference.h" #import "OOMaths.h" #if OO_SHADERS @class OOShaderProgram, OOTexture; enum { // Conversion settings for uniform bindings kOOUniformConvertClamp = 0x0001U, kOOUniformConvertNormalize = 0x0002U, kOOUniformConvertToMatrix = 0x0004U, kOOUniformBindToSuperTarget = 0x0008U, kOOUniformConvertDefaults = kOOUniformConvertToMatrix | kOOUniformBindToSuperTarget }; typedef uint16_t OOUniformConvertOptions; @interface OOShaderMaterial: OOBasicMaterial { @private OOShaderProgram *shaderProgram; NSMutableDictionary *uniforms; uint32_t texCount; OOTexture **textures; OOWeakReference *bindingTarget; } + (BOOL)configurationDictionarySpecifiesShaderMaterial:(NSDictionary *)configuration; /* Set up an OOShaderMaterial. Configuration should be a dictionary equivalent to an entry in a shipdata.plist "shaders" dictionary. Specifically, keys OOShaderMaterial will look for are currently: textures array of texture file names. vertex_shader name of vertex shader file. fragment_shader name of fragment shader file. uniforms dictionary of uniforms. Values are either reals or dictionaries containing: type "int", "texture" or "float" value number Macros is a dictionary which is converted to macro definitions and prepended to shader source code. It should be used to specify the availability if uniforms you tend to register, and other macros such as bug fix identifiers. For example, the dictionary: { "OO_ENGINE_LEVEL" = 1; } will be transformed into: #define OO_ENGINE_LEVEL 1 */ + (instancetype) shaderMaterialWithName:(NSString *)name configuration:(NSDictionary *)configuration macros:(NSDictionary *)macros bindingTarget:(id)target; - (id) initWithName:(NSString *)name configuration:(NSDictionary *)configuration macros:(NSDictionary *)macros bindingTarget:(id)target; /* Bind a uniform to a property of an object. SelectorName should specify a method of source which returns the desired value; it will be called every time -apply is, assuming uniformName is used in the shader. (If not, OOShaderMaterial will not track the binding.) A bound method must not take any parameters, and must return one of the following types: * Any integer or float type. * NSNumber. * Vector. * Quaternion. * OOMatrix. * OOColor. The "convert" flag has different meanings for different types: * For int, float or NSNumber, it clamps to the range [0..1]. * For Vector, it normalizes. * For Quaternion, it converts to a rotation matrix (instead of a vector). NOTE: this method *does not* check against the whitelist. See -bindSafeUniform:toObject:propertyNamed:convertOptions: below. */ - (BOOL) bindUniform:(NSString *)uniformName toObject:(id)target property:(SEL)selector convertOptions:(OOUniformConvertOptions)options; /* Bind a uniform to a property of an object. This is similar to -bindUniform:toObject:property:convertOptions:, except that it checks against OOUniformBindingPermitted(). */ - (BOOL) bindSafeUniform:(NSString *)uniformName toObject:(id)target propertyNamed:(NSString *)property convertOptions:(OOUniformConvertOptions)options; /* Set a uniform value. */ - (void) setUniform:(NSString *)uniformName intValue:(int)value; - (void) setUniform:(NSString *)uniformName floatValue:(float)value; - (void) setUniform:(NSString *)uniformName vectorValue:(GLfloat[4])value; - (void) setUniform:(NSString *)uniformName vectorObjectValue:(id)value; // Array of four numbers, or something that can be OOVectorFromObject()ed. - (void) setUniform:(NSString *)uniformName quaternionValue:(Quaternion)value asMatrix:(BOOL)asMatrix; /* Add constant uniforms. Same format as uniforms dictionary of configuration parameter to -initWithConfiguration:macros:. The target parameter is used for bindings. Additionally, the target may implement the following method, used to seed any random bindings: - (uint32_t) randomSeedForShaders; */ -(void) addUniformsFromDictionary:(NSDictionary *)uniformDefs withBindingTarget:(id)target; @end @interface NSObject (ShaderBindingHierarchy) /* Informal protocol for objects to "forward" their shader bindings up a hierarchy (for instance, subentities to parent entities). */ - (id) superShaderBindingTarget; @end enum { /* ID of vertex attribute used for tangents. A fixed ID is used for simplicty. NOTE: on Nvidia hardware, attribute 15 is aliased to gl_MultiTexCoord7. This is not expected to become a problem. */ kTangentAttributeIndex = 15 }; /* OOUniformBindingPermitted() Predicate determining whether a given property may be used as a binding. Client code is responsible for implementing this. */ BOOL OOUniformBindingPermitted(NSString *propertyName, id bindingTarget); @interface NSObject (OOShaderMaterialTargetOptional) - (uint32_t) randomSeedForShaders; @end // Material specifier dictionary keys. extern NSString * const kOOVertexShaderSourceKey; extern NSString * const kOOVertexShaderNameKey; extern NSString * const kOOFragmentShaderSourceKey; extern NSString * const kOOFragmentShaderNameKey; extern NSString * const kOOTexturesKey; extern NSString * const kOOTextureObjectsKey; extern NSString * const kOOUniformsKey; extern NSString * const kOOIsSynthesizedMaterialConfigurationKey; extern NSString * const kOOIsSynthesizedMaterialMacrosKey; #endif // OO_SHADERS oolite-1.82/src/Core/Materials/OOShaderMaterial.m000066400000000000000000000570711256642440500216620ustar00rootroot00000000000000/* OOShaderMaterial.m Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOShaderMaterial.h" #if OO_SHADERS #import "ResourceManager.h" #import "OOShaderUniform.h" #import "OOFunctionAttributes.h" #import "OOCollectionExtractors.h" #import "OOShaderProgram.h" #import "OOTexture.h" #import "OOOpenGLExtensionManager.h" #import "OOMacroOpenGL.h" #import "Universe.h" #import "OOIsNumberLiteral.h" #import "OOLogging.h" #import "OODebugFlags.h" #import "OOStringParsing.h" NSString * const kOOVertexShaderSourceKey = @"_oo_vertex_shader_source"; NSString * const kOOVertexShaderNameKey = @"vertex_shader"; NSString * const kOOFragmentShaderSourceKey = @"_oo_fragment_shader_source"; NSString * const kOOFragmentShaderNameKey = @"fragment_shader"; NSString * const kOOTexturesKey = @"textures"; NSString * const kOOTextureObjectsKey = @"_oo_texture_objects"; NSString * const kOOUniformsKey = @"uniforms"; NSString * const kOOIsSynthesizedMaterialConfigurationKey = @"_oo_is_synthesized_config"; NSString * const kOOIsSynthesizedMaterialMacrosKey = @"_oo_synthesized_material_macros"; static BOOL GetShaderSource(NSString *fileName, NSString *shaderType, NSString *prefix, NSString **outResult); static NSString *MacrosToString(NSDictionary *macros); @interface OOShaderMaterial (OOPrivate) // Convert a "textures" array to an "_oo_texture_objects" array. - (NSArray *) loadTexturesFromArray:(NSArray *)textureSpecs unitCount:(GLuint)max; // Load up an array of texture objects. - (void) addTexturesFromArray:(NSArray *)textureObjects unitCount:(GLuint)max; @end @implementation OOShaderMaterial + (BOOL)configurationDictionarySpecifiesShaderMaterial:(NSDictionary *)configuration { if (configuration == nil) return NO; if ([configuration oo_stringForKey:kOOVertexShaderSourceKey] != nil) return YES; if ([configuration oo_stringForKey:kOOFragmentShaderSourceKey] != nil) return YES; if ([configuration oo_stringForKey:kOOVertexShaderNameKey] != nil) return YES; if ([configuration oo_stringForKey:kOOVertexShaderNameKey] != nil) return YES; return NO; } + (instancetype) shaderMaterialWithName:(NSString *)name configuration:(NSDictionary *)configuration macros:(NSDictionary *)macros bindingTarget:(id)target { return [[[self alloc] initWithName:name configuration:configuration macros:macros bindingTarget:target] autorelease]; } - (id) initWithName:(NSString *)name configuration:(NSDictionary *)configuration macros:(NSDictionary *)macros bindingTarget:(id)target { BOOL OK = YES; NSString *macroString = nil; NSString *vertexShader = nil; NSString *fragmentShader = nil; GLint textureUnits = [[OOOpenGLExtensionManager sharedManager] textureImageUnitCount]; NSMutableDictionary *modifiedMacros = nil; NSString *vsName = @""; NSString *fsName = @""; NSString *vsCacheKey = nil; NSString *fsCacheKey = nil; if (configuration == nil) OK = NO; self = [super initWithName:name configuration:configuration]; if (self == nil) OK = NO; if (OK) { modifiedMacros = macros ? [macros mutableCopy] : [[NSMutableDictionary alloc] init]; [modifiedMacros autorelease]; [modifiedMacros setObject:[NSNumber numberWithUnsignedInt:textureUnits] forKey:@"OO_TEXTURE_UNIT_COUNT"]; // used to test for simplified shaders - OO_REDUCED_COMPLEXITY - here macroString = MacrosToString(modifiedMacros); } if (OK) { vertexShader = [configuration oo_stringForKey:kOOVertexShaderSourceKey]; if (vertexShader == nil) { vsName = [configuration oo_stringForKey:kOOVertexShaderNameKey]; vsCacheKey = vsName; if (vsName != nil) { if (!GetShaderSource(vsName, @"vertex", macroString, &vertexShader)) OK = NO; } } else { vsCacheKey = vertexShader; } } if (OK) { fragmentShader = [configuration oo_stringForKey:kOOFragmentShaderSourceKey]; if (fragmentShader == nil) { fsName = [configuration oo_stringForKey:kOOFragmentShaderNameKey]; fsCacheKey = fsName; if (fsName != nil) { if (!GetShaderSource(fsName, @"fragment", macroString, &fragmentShader)) OK = NO; } } else { fsCacheKey = fragmentShader; } } if (OK) { if (vertexShader != nil || fragmentShader != nil) { static NSDictionary *attributeBindings = nil; if (attributeBindings == nil) { attributeBindings = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kTangentAttributeIndex] forKey:@"tangent"]; [attributeBindings retain]; } NSString *cacheKey = [NSString stringWithFormat:@"$VERTEX:\n%@\n\n$FRAGMENT:\n%@\n\n$MACROS:\n%@\n", vsCacheKey, fsCacheKey, macroString]; OOLogIndent(); shaderProgram = [OOShaderProgram shaderProgramWithVertexShader:vertexShader fragmentShader:fragmentShader vertexShaderName:vsName fragmentShaderName:fsName prefix:macroString attributeBindings:attributeBindings cacheKey:cacheKey]; OOLogOutdent(); // no reduced complexity mode now #if 0 if (shaderProgram == nil) { BOOL canFallBack = ![modifiedMacros oo_boolForKey:@"OO_REDUCED_COMPLEXITY"]; #ifndef NDEBUG if (gDebugFlags & DEBUG_NO_SHADER_FALLBACK) canFallBack = NO; #endif if (canFallBack) { OOLogWARN(@"shader.load.fullModeFailed", @"Could not build shader %@/%@ in full complexity mode, trying simple mode.", vsName, fsName); [modifiedMacros setObject:[NSNumber numberWithInt:1] forKey:@"OO_REDUCED_COMPLEXITY"]; macroString = MacrosToString(modifiedMacros); cacheKey = [cacheKey stringByAppendingString:@"\n$SIMPLIFIED FALLBACK\n"]; OOLogIndent(); shaderProgram = [OOShaderProgram shaderProgramWithVertexShader:vertexShader fragmentShader:fragmentShader vertexShaderName:vsName fragmentShaderName:fsName prefix:macroString attributeBindings:attributeBindings cacheKey:cacheKey]; OOLogOutdent(); if (shaderProgram != nil) { OOLog(@"shader.load.fallbackSuccess", @"Simple mode fallback successful."); } } } #endif if (shaderProgram == nil) { OOLogERR(@"shader.load.failed", @"Could not build shader %@/%@.", vsName, fsName); } } else { OOLog(@"shader.load.noShader", @"***** Error: no vertex or fragment shader specified in shader dictionary:\n%@", configuration); } OK = (shaderProgram != nil); if (OK) [shaderProgram retain]; } if (OK) { // Load uniforms and textures, which are a flavour of uniform for our purpose. NSDictionary *uniformDefs = [configuration oo_dictionaryForKey:kOOUniformsKey]; NSArray *textureArray = [configuration oo_arrayForKey:kOOTextureObjectsKey]; if (textureArray == nil) { NSArray *textureSpecs = [configuration oo_arrayForKey:kOOTexturesKey]; if (textureSpecs != nil) { textureArray = [self loadTexturesFromArray:textureSpecs unitCount:textureUnits]; } } uniforms = [[NSMutableDictionary alloc] initWithCapacity:[uniformDefs count] + [textureArray count]]; [self addUniformsFromDictionary:uniformDefs withBindingTarget:target]; [self addTexturesFromArray:textureArray unitCount:textureUnits]; } if (!OK) { [self release]; self = nil; } return self; } - (void)dealloc { uint32_t i; [self willDealloc]; [shaderProgram release]; [uniforms release]; if (textures != NULL) { for (i = 0; i != texCount; ++i) { [textures[i] release]; } free(textures); } [bindingTarget release]; [super dealloc]; } - (BOOL)bindUniform:(NSString *)uniformName toObject:(id)source property:(SEL)selector convertOptions:(OOUniformConvertOptions)options { OOShaderUniform *uniform = nil; if (uniformName == nil) return NO; uniform = [[OOShaderUniform alloc] initWithName:uniformName shaderProgram:shaderProgram boundToObject:source property:selector convertOptions:options]; if (uniform != nil) { OOLog(@"shader.uniform.set", @"Set up uniform %@", uniform); [uniforms setObject:uniform forKey:uniformName]; [uniform release]; return YES; } else { OOLog(@"shader.uniform.unSet", @"Did not set uniform \"%@\"", uniformName); [uniforms removeObjectForKey:uniformName]; return NO; } } - (BOOL)bindSafeUniform:(NSString *)uniformName toObject:(id)target propertyNamed:(NSString *)property convertOptions:(OOUniformConvertOptions)options { SEL selector = NULL; selector = NSSelectorFromString(property); if (selector != NULL && OOUniformBindingPermitted(property, target)) { return [self bindUniform:uniformName toObject:target property:selector convertOptions:options]; } else { OOLog(@"shader.uniform.unpermittedMethod", @"Did not bind uniform \"%@\" to property -[%@ %@] - unpermitted method.", uniformName, [target class], property); } return NO; } - (void)setUniform:(NSString *)uniformName intValue:(int)value { OOShaderUniform *uniform = nil; if (uniformName == nil) return; uniform = [[OOShaderUniform alloc] initWithName:uniformName shaderProgram:shaderProgram intValue:value]; if (uniform != nil) { OOLog(@"shader.uniform.set", @"Set up uniform %@", uniform); [uniforms setObject:uniform forKey:uniformName]; [uniform release]; } else { OOLog(@"shader.uniform.unSet", @"Did not set uniform \"%@\"", uniformName); [uniforms removeObjectForKey:uniformName]; } } - (void)setUniform:(NSString *)uniformName floatValue:(float)value { OOShaderUniform *uniform = nil; if (uniformName == nil) return; uniform = [[OOShaderUniform alloc] initWithName:uniformName shaderProgram:shaderProgram floatValue:value]; if (uniform != nil) { OOLog(@"shader.uniform.set", @"Set up uniform %@", uniform); [uniforms setObject:uniform forKey:uniformName]; [uniform release]; } else { OOLog(@"shader.uniform.unSet", @"Did not set uniform \"%@\"", uniformName); [uniforms removeObjectForKey:uniformName]; } } - (void)setUniform:(NSString *)uniformName vectorValue:(GLfloat[4])value { OOShaderUniform *uniform = nil; if (uniformName == nil) return; uniform = [[OOShaderUniform alloc] initWithName:uniformName shaderProgram:shaderProgram vectorValue:value]; if (uniform != nil) { OOLog(@"shader.uniform.set", @"Set up uniform %@", uniform); [uniforms setObject:uniform forKey:uniformName]; [uniform release]; } else { OOLog(@"shader.uniform.unSet", @"Did not set uniform \"%@\"", uniformName); [uniforms removeObjectForKey:uniformName]; } } - (void)setUniform:(NSString *)uniformName vectorObjectValue:(id)value { if (uniformName == nil) return; GLfloat vecArray[4]; if ([value isKindOfClass:[NSArray class]] && [value count] == 4) { for (unsigned i = 0; i < 4; i++) { vecArray[i] = OOFloatFromObject([value objectAtIndex:i], 0.0f); } } else { Vector vec = OOVectorFromObject(value, kZeroVector); vecArray[0] = vec.x; vecArray[1] = vec.y; vecArray[2] = vec.z; vecArray[3] = 1.0; } OOShaderUniform *uniform = [[OOShaderUniform alloc] initWithName:uniformName shaderProgram:shaderProgram vectorValue:vecArray]; if (uniform != nil) { OOLog(@"shader.uniform.set", @"Set up uniform %@", uniform); [uniforms setObject:uniform forKey:uniformName]; [uniform release]; } else { OOLog(@"shader.uniform.unSet", @"Did not set uniform \"%@\"", uniformName); [uniforms removeObjectForKey:uniformName]; } } - (void)setUniform:(NSString *)uniformName quaternionValue:(Quaternion)value asMatrix:(BOOL)asMatrix { OOShaderUniform *uniform = nil; if (uniformName == nil) return; uniform = [[OOShaderUniform alloc] initWithName:uniformName shaderProgram:shaderProgram quaternionValue:value asMatrix:asMatrix]; if (uniform != nil) { OOLog(@"shader.uniform.set", @"Set up uniform %@", uniform); [uniforms setObject:uniform forKey:uniformName]; [uniform release]; } else { OOLog(@"shader.uniform.unSet", @"Did not set uniform \"%@\"", uniformName); [uniforms removeObjectForKey:uniformName]; } } -(void)addUniformsFromDictionary:(NSDictionary *)uniformDefs withBindingTarget:(id)target { NSEnumerator *uniformEnum = nil; NSString *name = nil; id definition = nil; id value = nil; NSString *binding = nil; NSString *type = nil; GLfloat floatValue; BOOL gotValue; OOUniformConvertOptions convertOptions; BOOL quatAsMatrix = YES; GLfloat scale = 1.0; uint32_t randomSeed; RANROTSeed savedSeed; NSArray *keys = nil; if ([target respondsToSelector:@selector(randomSeedForShaders)]) { randomSeed = [(id)target randomSeedForShaders]; } else { randomSeed = (uint32_t)(uintptr_t)self; } savedSeed = RANROTGetFullSeed(); ranrot_srand(randomSeed); keys = [[uniformDefs allKeys] sortedArrayUsingSelector:@selector(compare:)]; for (uniformEnum = [keys objectEnumerator]; (name = [uniformEnum nextObject]); ) { gotValue = NO; definition = [uniformDefs objectForKey:name]; type = nil; value = nil; binding = nil; if ([definition isKindOfClass:[NSDictionary class]]) { value = [(NSDictionary *)definition objectForKey:@"value"]; binding = [(NSDictionary *)definition oo_stringForKey:@"binding"]; type = [(NSDictionary *)definition oo_stringForKey:@"type"]; scale = [(NSDictionary *)definition oo_floatForKey:@"scale" defaultValue:1.0]; if (type == nil) { if (value == nil && binding != nil) type = @"binding"; else type = @"float"; } } else if ([definition isKindOfClass:[NSNumber class]]) { value = definition; type = @"float"; } else if ([definition isKindOfClass:[NSString class]]) { if (OOIsNumberLiteral(definition, NO)) { value = definition; type = @"float"; } else { binding = definition; type = @"binding"; } } else if ([definition isKindOfClass:[NSArray class]]) { binding = definition; type = @"vector"; } // Transform random values to concrete values if ([type isEqualToString:@"randomFloat"]) { type = @"float"; value = [NSNumber numberWithFloat:randf() * scale]; } else if ([type isEqualToString:@"randomUnitVector"]) { type = @"vector"; value = OOPropertyListFromVector(vector_multiply_scalar(OORandomUnitVector(), scale)); } else if ([type isEqualToString:@"randomVectorSpatial"]) { type = @"vector"; value = OOPropertyListFromVector(OOVectorRandomSpatial(scale)); } else if ([type isEqualToString:@"randomVectorRadial"]) { type = @"vector"; value = OOPropertyListFromVector(OOVectorRandomRadial(scale)); } else if ([type isEqualToString:@"randomQuaternion"]) { type = @"quaternion"; value = OOPropertyListFromQuaternion(OORandomQuaternion()); } if ([type isEqualToString:@"float"] || [type isEqualToString:@"real"]) { gotValue = YES; if ([value respondsToSelector:@selector(floatValue)]) floatValue = [value floatValue]; else if ([value respondsToSelector:@selector(doubleValue)]) floatValue = [value doubleValue]; else if ([value respondsToSelector:@selector(intValue)]) floatValue = [value intValue]; else gotValue = NO; if (gotValue) { [self setUniform:name floatValue:floatValue]; } } else if ([type isEqualToString:@"int"] || [type isEqualToString:@"integer"] || [type isEqualToString:@"texture"]) { /* "texture" is allowed as a synonym for "int" because shader uniforms are mapped to texture units by specifying an integer index. uniforms = { diffuseMap = { type = texture; value = 0; }; }; means "bind uniform diffuseMap to texture unit 0" (which will have the first texture in the textures array). */ if ([value respondsToSelector:@selector(intValue)]) { [self setUniform:name intValue:[value intValue]]; gotValue = YES; } } else if ([type isEqualToString:@"vector"]) { [self setUniform:name vectorObjectValue:value]; gotValue = YES; } else if ([type isEqualToString:@"quaternion"]) { if ([definition isKindOfClass:[NSDictionary class]]) { quatAsMatrix = [definition oo_boolForKey:@"asMatrix" defaultValue:quatAsMatrix]; } [self setUniform:name quaternionValue:OOQuaternionFromObject(value, kIdentityQuaternion) asMatrix:quatAsMatrix]; gotValue = YES; } else if (target != nil && [type isEqualToString:@"binding"]) { if ([definition isKindOfClass:[NSDictionary class]]) { convertOptions = 0; if ([definition oo_boolForKey:@"clamped" defaultValue:NO]) convertOptions |= kOOUniformConvertClamp; if ([definition oo_boolForKey:@"normalized" defaultValue:[definition oo_boolForKey:@"normalised" defaultValue:NO]]) { convertOptions |= kOOUniformConvertNormalize; } if ([definition oo_boolForKey:@"asMatrix" defaultValue:YES]) convertOptions |= kOOUniformConvertToMatrix; if (![definition oo_boolForKey:@"bindToSubentity" defaultValue:NO]) convertOptions |= kOOUniformBindToSuperTarget; } else { convertOptions = kOOUniformConvertDefaults; } [self bindSafeUniform:name toObject:target propertyNamed:binding convertOptions:convertOptions]; gotValue = YES; } if (!gotValue) { OOLog(@"shader.uniform.badDescription", @"----- Warning: could not bind uniform \"%@\" for target %@ -- could not interpret definition:\n%@", name, target, definition); } } RANROTSetFullSeed(savedSeed); } - (BOOL)doApply { NSEnumerator *uniformEnum = nil; OOShaderUniform *uniform = nil; uint32_t i; OO_ENTER_OPENGL(); [super doApply]; [shaderProgram apply]; for (i = 0; i != texCount; ++i) { OOGL(glActiveTextureARB(GL_TEXTURE0_ARB + i)); [textures[i] apply]; } if (texCount > 1) OOGL(glActiveTextureARB(GL_TEXTURE0_ARB)); @try { for (uniformEnum = [uniforms objectEnumerator]; (uniform = [uniformEnum nextObject]); ) { [uniform apply]; } } @catch (id exception) {} return YES; } - (void)ensureFinishedLoading { uint32_t i; if (textures != NULL) { for (i = 0; i != texCount; ++i) { [textures[i] ensureFinishedLoading]; } } } - (BOOL) isFinishedLoading { uint32_t i; if (textures != NULL) { for (i = 0; i != texCount; ++i) { if (![textures[i] isFinishedLoading]) return NO; } } return YES; } - (void)unapplyWithNext:(OOMaterial *)next { uint32_t i, count; if (![next isKindOfClass:[OOShaderMaterial class]]) // Avoid redundant state change { OO_ENTER_OPENGL(); [OOShaderProgram applyNone]; /* BUG: unapplyWithNext: was failing to clear texture state. If a shader material was followed by a basic material (with no texture), the shader's #0 texture would be used. It is necessary to clear at least one texture for the case where a shader material with textures is followed by a shader material without textures, then a basic material. -- Ahruman 2007-08-13 */ count = texCount ? texCount : 1; for (i = 0; i != count; ++i) { OOGL(glActiveTextureARB(GL_TEXTURE0_ARB + i)); [OOTexture applyNone]; } if (count != 1) OOGL(glActiveTextureARB(GL_TEXTURE0_ARB)); } } - (void)setBindingTarget:(id)target { [[uniforms allValues] makeObjectsPerformSelector:@selector(setBindingTarget:) withObject:target]; [bindingTarget release]; bindingTarget = [target weakRetain]; } - (BOOL) permitSpecular { return YES; } #ifndef NDEBUG - (NSSet *) allTextures { return [NSSet setWithObjects:textures count:texCount]; } #endif @end @implementation OOShaderMaterial (OOPrivate) - (NSArray *) loadTexturesFromArray:(NSArray *)textureSpecs unitCount:(GLuint)max { GLuint i, count = (GLuint)MIN([textureSpecs count], (NSUInteger)max); NSMutableArray *result = [NSMutableArray arrayWithCapacity:count]; for (i = 0; i < count; i++) { id textureSpec = [textureSpecs objectAtIndex:i]; OOTexture *texture = [OOTexture textureWithConfiguration:textureSpec]; if (texture == nil) texture = [OOTexture nullTexture]; [result addObject:texture]; } return result; } - (void) addTexturesFromArray:(NSArray *)textureObjects unitCount:(GLuint)max { // Allocate space for texture object name array texCount = (uint32_t)MIN([textureObjects count], (NSUInteger)max); if (texCount == 0) return; textures = malloc(texCount * sizeof *textures); if (textures == NULL) { texCount = 0; return; } // Set up texture object names and appropriate uniforms unsigned i; for (i = 0; i != texCount; ++i) { textures[i] = [textureObjects objectAtIndex:i]; [textures[i] retain]; } } @end static NSString *MacrosToString(NSDictionary *macros) { NSMutableString *result = nil; NSEnumerator *macroEnum = nil; id key = nil, value = nil; if (macros == nil) return nil; result = [NSMutableString string]; for (macroEnum = [macros keyEnumerator]; (key = [macroEnum nextObject]); ) { if (![key isKindOfClass:[NSString class]]) continue; value = [macros objectForKey:key]; [result appendFormat:@"#define %@ %@\n", key, value]; } if ([result length] == 0) return nil; [result appendString:@"\n\n"]; return result; } #endif /* Attempt to load fragment or vertex shader source from a file. Returns YES if source was loaded or no shader was specified, and NO if an external shader was specified but could not be found. */ static BOOL GetShaderSource(NSString *fileName, NSString *shaderType, NSString *prefix, NSString **outResult) { NSString *result = nil; NSArray *extensions = nil; NSEnumerator *extEnum = nil; NSString *extension = nil; NSString *nameWithExtension = nil; if (fileName == nil) return YES; // It's OK for one or the other of the shaders to be undefined. result = [ResourceManager stringFromFilesNamed:fileName inFolder:@"Shaders"]; if (result == nil) { extensions = [NSArray arrayWithObjects:shaderType, [shaderType substringToIndex:4], nil]; // vertex and vert, or fragment and frag // Futureproofing -- in future, we may wish to support automatic selection between supported shader languages. if (![fileName pathHasExtensionInArray:extensions]) { for (extEnum = [extensions objectEnumerator]; (extension = [extEnum nextObject]); ) { nameWithExtension = [fileName stringByAppendingPathExtension:extension]; result = [ResourceManager stringFromFilesNamed:nameWithExtension inFolder:@"Shaders"]; if (result != nil) break; } } if (result == nil) { OOLog(kOOLogFileNotFound, @"GLSL ERROR: failed to find %@ program %@.", shaderType, fileName); return NO; } } if (outResult != NULL) *outResult = result; return YES; } oolite-1.82/src/Core/Materials/OOShaderProgram.h000066400000000000000000000047451256642440500215260ustar00rootroot00000000000000/* OOShaderProgram.h Encapsulates a vertex + fragment shader combo. In general, this should only be used though OOShaderMaterial. The point of this separation is that more than one OOShaderMaterial can use the same OOShaderProgram. Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import #import "OOOpenGL.h" #import "OOOpenGLExtensionManager.h" #if OO_SHADERS @interface OOShaderProgram: NSObject { @private GLhandleARB program; NSString *key; NSArray *standardMatrixUniformLocations; } + (id) shaderProgramWithVertexShader:(NSString *)vertexShaderSource fragmentShader:(NSString *)fragmentShaderSource vertexShaderName:(NSString *)vertexShaderName fragmentShaderName:(NSString *)fragmentShaderName prefix:(NSString *)prefixString // String prepended to program source (both vs and fs) attributeBindings:(NSDictionary *)attributeBindings // Maps vertex attribute names to "locations". cacheKey:(NSString *)cacheKey; // Loads a shader from a file, caching and sharing shader program instances. + (id) shaderProgramWithVertexShaderName:(NSString *)vertexShaderName fragmentShaderName:(NSString *)fragmentShaderName prefix:(NSString *)prefixString // String prepended to program source (both vs and fs) attributeBindings:(NSDictionary *)attributeBindings; // Maps vertex attribute names to "locations". - (void) apply; + (void) applyNone; - (GLhandleARB) program; @end #endif // OO_SHADERS oolite-1.82/src/Core/Materials/OOShaderProgram.m000066400000000000000000000344631256642440500215330ustar00rootroot00000000000000/* OOShaderProgram.m Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOOpenGLExtensionManager.h" #if OO_SHADERS #import "OOShaderProgram.h" #import "OOFunctionAttributes.h" #import "OOStringParsing.h" #import "ResourceManager.h" #import "OOOpenGLExtensionManager.h" #import "OOMacroOpenGL.h" #import "OOCollectionExtractors.h" #import "OODebugFlags.h" #import "Universe.h" #import "MyOpenGLView.h" static NSMutableDictionary *sShaderCache = nil; static OOShaderProgram *sActiveProgram = nil; static BOOL GetShaderSource(NSString *fileName, NSString *shaderType, NSString *prefix, NSString **outResult); static NSString *GetGLSLInfoLog(GLhandleARB shaderObject); @interface OOShaderProgram (OOPrivate) - (id)initWithVertexShaderSource:(NSString *)vertexSource fragmentShaderSource:(NSString *)fragmentSource prefixString:(NSString *)prefixString vertexName:(NSString *)vertexName fragmentName:(NSString *)fragmentName attributeBindings:(NSDictionary *)attributeBindings key:(NSString *)key; - (void) bindAttributes:(NSDictionary *)attributeBindings; - (void) bindStandardMatrixUniforms; @end @implementation OOShaderProgram + (id) shaderProgramWithVertexShader:(NSString *)vertexShaderSource fragmentShader:(NSString *)fragmentShaderSource vertexShaderName:(NSString *)vertexShaderName fragmentShaderName:(NSString *)fragmentShaderName prefix:(NSString *)prefixString // String prepended to program source (both vs and fs) attributeBindings:(NSDictionary *)attributeBindings // Maps vertex attribute names to "locations". cacheKey:(NSString *)cacheKey { OOShaderProgram *result = nil; if ([prefixString length] == 0) prefixString = nil; // Use cache to avoid creating duplicate shader programs -- saves on GPU resources and potentially state changes. // FIXME: probably needs to respond to graphics resets. result = [[sShaderCache objectForKey:cacheKey] pointerValue]; if (result == nil) { // No cached program; create one... result = [[OOShaderProgram alloc] initWithVertexShaderSource:vertexShaderSource fragmentShaderSource:fragmentShaderSource prefixString:prefixString vertexName:vertexShaderName fragmentName:fragmentShaderName attributeBindings:attributeBindings key:cacheKey]; [result autorelease]; if (result != nil && cacheKey != nil) { // ...and add it to the cache. if (sShaderCache == nil) sShaderCache = [[NSMutableDictionary alloc] init]; [sShaderCache setObject:[NSValue valueWithPointer:result] forKey:cacheKey]; // Use NSValue so dictionary doesn't retain program } } return result; } + (id)shaderProgramWithVertexShaderName:(NSString *)vertexShaderName fragmentShaderName:(NSString *)fragmentShaderName prefix:(NSString *)prefixString attributeBindings:(NSDictionary *)attributeBindings { NSString *cacheKey = nil; OOShaderProgram *result = nil; NSString *vertexSource = nil; NSString *fragmentSource = nil; if ([prefixString length] == 0) prefixString = nil; // Use cache to avoid creating duplicate shader programs -- saves on GPU resources and potentially state changes. // FIXME: probably needs to respond to graphics resets. cacheKey = [NSString stringWithFormat:@"vertex:%@\nfragment:%@\n----\n%@", vertexShaderName, fragmentShaderName, prefixString ?: (NSString *)@""]; result = [[sShaderCache objectForKey:cacheKey] pointerValue]; if (result == nil) { // No cached program; create one... if (!GetShaderSource(vertexShaderName, @"vertex", prefixString, &vertexSource)) return nil; if (!GetShaderSource(fragmentShaderName, @"fragment", prefixString, &fragmentSource)) return nil; result = [[OOShaderProgram alloc] initWithVertexShaderSource:vertexSource fragmentShaderSource:fragmentSource prefixString:prefixString vertexName:vertexShaderName fragmentName:fragmentShaderName attributeBindings:attributeBindings key:cacheKey]; if (result != nil) { // ...and add it to the cache. [result autorelease]; if (sShaderCache == nil) sShaderCache = [[NSMutableDictionary alloc] init]; [sShaderCache setObject:[NSValue valueWithPointer:result] forKey:cacheKey]; // Use NSValue so dictionary doesn't retain program } } return result; } - (void)dealloc { OO_ENTER_OPENGL(); #ifndef NDEBUG if (EXPECT_NOT(sActiveProgram == self)) { OOLog(@"shader.dealloc.imbalance", @"***** OOShaderProgram deallocated while active, indicating a retain/release imbalance. Expect imminent crash."); [OOShaderProgram applyNone]; } #endif if (key != nil) { [sShaderCache removeObjectForKey:key]; [key release]; } if (standardMatrixUniformLocations != nil) { [standardMatrixUniformLocations release]; } OOGL(glDeleteObjectARB(program)); [super dealloc]; } - (void)apply { OO_ENTER_OPENGL(); if (sActiveProgram != self) { [sActiveProgram release]; sActiveProgram = [self retain]; OOGL(glUseProgramObjectARB(program)); [self bindStandardMatrixUniforms]; } } + (void)applyNone { OO_ENTER_OPENGL(); if (sActiveProgram != nil) { [sActiveProgram release]; sActiveProgram = nil; OOGL(glUseProgramObjectARB(NULL_SHADER)); } } - (GLhandleARB)program { return program; } @end static BOOL ValidateShaderObject(GLhandleARB object, NSString *name) { GLint type, subtype = 0, status; GLenum statusType; NSString *subtypeString = nil; NSString *actionString = nil; OO_ENTER_OPENGL(); OOGL(glGetObjectParameterivARB(object, GL_OBJECT_TYPE_ARB, &type)); BOOL linking = type == GL_PROGRAM_OBJECT_ARB; if (linking) { subtypeString = @"shader program"; actionString = @"linking"; statusType = GL_OBJECT_LINK_STATUS_ARB; } else { // FIXME OOGL(glGetObjectParameterivARB(object, GL_OBJECT_SUBTYPE_ARB, &subtype)); switch (subtype) { case GL_VERTEX_SHADER_ARB: subtypeString = @"vertex shader"; break; case GL_FRAGMENT_SHADER_ARB: subtypeString = @"fragment shader"; break; #if GL_EXT_geometry_shader4 case GL_GEOMETRY_SHADER_EXT: subtypeString = @"geometry shader"; break; #endif default: subtypeString = [NSString stringWithFormat:@"", subtype]; } actionString = @"compilation"; statusType = GL_OBJECT_COMPILE_STATUS_ARB; } OOGL(glGetObjectParameterivARB(object, statusType, &status)); if (status == GL_FALSE) { NSString *msgClass = [NSString stringWithFormat:@"shader.%@.failure", linking ? @"link" : @"compile"]; OOLogERR(msgClass, @"GLSL %@ %@ failed for %@:\n>>>>> GLSL log:\n%@\n", subtypeString, actionString, name, GetGLSLInfoLog(object)); return NO; } #ifndef NDEBUG if (gDebugFlags & DEBUG_SHADER_VALIDATION && 0) { OOGL(glValidateProgramARB(object)); OOGL(glGetObjectParameterivARB(object, GL_OBJECT_VALIDATE_STATUS_ARB, &status)); if (status == GL_FALSE) { NSString *msgClass = [NSString stringWithFormat:@"shader.%@.validationFailure", linking ? @"link" : @"compile"]; OOLogWARN(msgClass, @"GLSL %@ %@ failed for %@:\n>>>>> GLSL log:\n%@\n", subtypeString, @"validation", name, GetGLSLInfoLog(object)); return NO; } } #endif return YES; } @implementation OOShaderProgram (OOPrivate) - (id)initWithVertexShaderSource:(NSString *)vertexSource fragmentShaderSource:(NSString *)fragmentSource prefixString:(NSString *)prefixString vertexName:(NSString *)vertexName fragmentName:(NSString *)fragmentName attributeBindings:(NSDictionary *)attributeBindings key:(NSString *)inKey { BOOL OK = YES; const GLcharARB *sourceStrings[3] = { "", "#line 0\n", NULL }; GLhandleARB vertexShader = NULL_SHADER; GLhandleARB fragmentShader = NULL_SHADER; OO_ENTER_OPENGL(); self = [super init]; if (self == nil) OK = NO; standardMatrixUniformLocations = nil; if (OK && vertexSource == nil && fragmentSource == nil) OK = NO; // Must have at least one shader! if (OK && prefixString != nil) { sourceStrings[0] = [prefixString UTF8String]; } if (OK && vertexSource != nil) { // Compile vertex shader. OOGL(vertexShader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB)); if (vertexShader != NULL_SHADER) { sourceStrings[2] = [vertexSource UTF8String]; OOGL(glShaderSourceARB(vertexShader, 3, sourceStrings, NULL)); OOGL(glCompileShaderARB(vertexShader)); OK = ValidateShaderObject(vertexShader, vertexName); } else OK = NO; } if (OK && fragmentSource != nil) { // Compile fragment shader. OOGL(fragmentShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB)); if (fragmentShader != NULL_SHADER) { sourceStrings[2] = [fragmentSource UTF8String]; OOGL(glShaderSourceARB(fragmentShader, 3, sourceStrings, NULL)); OOGL(glCompileShaderARB(fragmentShader)); OK = ValidateShaderObject(fragmentShader, fragmentName); } else OK = NO; } if (OK) { // Link shader. OOGL(program = glCreateProgramObjectARB()); if (program != NULL_SHADER) { if (vertexShader != NULL_SHADER) OOGL(glAttachObjectARB(program, vertexShader)); if (fragmentShader != NULL_SHADER) OOGL(glAttachObjectARB(program, fragmentShader)); [self bindAttributes:attributeBindings]; OOGL(glLinkProgramARB(program)); OK = ValidateShaderObject(program, [NSString stringWithFormat:@"%@/%@", vertexName, fragmentName]); } else OK = NO; } if (OK) { key = [inKey copy]; } if (vertexShader != NULL_SHADER) OOGL(glDeleteObjectARB(vertexShader)); if (fragmentShader != NULL_SHADER) OOGL(glDeleteObjectARB(fragmentShader)); if (OK) { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; standardMatrixUniformLocations = [matrixManager standardMatrixUniformLocations: program]; } else { if (self != nil && program != NULL_SHADER) { OOGL(glDeleteObjectARB(program)); program = NULL_SHADER; } [self release]; self = nil; } return self; } - (void) bindAttributes:(NSDictionary *)attributeBindings { OO_ENTER_OPENGL(); NSString *attrKey = nil; NSEnumerator *keyEnum = nil; for (keyEnum = [attributeBindings keyEnumerator]; (attrKey = [keyEnum nextObject]); ) { OOGL(glBindAttribLocationARB(program, [attributeBindings oo_unsignedIntForKey:attrKey], [attrKey UTF8String])); } } - (void) bindStandardMatrixUniforms { if (standardMatrixUniformLocations != nil) { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; NSEnumerator *enumerator = [standardMatrixUniformLocations objectEnumerator]; id obj; NSArray *pair; OO_ENTER_OPENGL(); [matrixManager syncModelView]; while ((obj = [enumerator nextObject])) { if ([obj isKindOfClass:[NSArray class]]) { pair = (NSArray*)obj; if ([[pair oo_stringAtIndex: 2] compare: @"mat3"] == 0) { OOGL(GLUniformMatrix3([pair oo_intAtIndex: 0], [matrixManager getMatrix: [pair oo_intAtIndex: 1]])); } else { GLUniformMatrix([pair oo_intAtIndex: 0], [matrixManager getMatrix: [pair oo_intAtIndex: 1]]); } } } } return; } @end /* Attempt to load fragment or vertex shader source from a file. Returns YES if source was loaded or no shader was specified, and NO if an external shader was specified but could not be found. */ static BOOL GetShaderSource(NSString *fileName, NSString *shaderType, NSString *prefix, NSString **outResult) { NSString *result = nil; NSArray *extensions = nil; NSEnumerator *extEnum = nil; NSString *extension = nil; NSString *nameWithExtension = nil; if (fileName == nil) return YES; // It's OK for one or the other of the shaders to be undefined. result = [ResourceManager stringFromFilesNamed:fileName inFolder:@"Shaders"]; if (result == nil) { extensions = [NSArray arrayWithObjects:shaderType, [shaderType substringToIndex:4], nil]; // vertex and vert, or fragment and frag // Futureproofing -- in future, we may wish to support automatic selection between supported shader languages. if (![fileName pathHasExtensionInArray:extensions]) { for (extEnum = [extensions objectEnumerator]; (extension = [extEnum nextObject]); ) { nameWithExtension = [fileName stringByAppendingPathExtension:extension]; result = [ResourceManager stringFromFilesNamed:nameWithExtension inFolder:@"Shaders"]; if (result != nil) break; } } if (result == nil) { OOLog(kOOLogFileNotFound, @"GLSL ERROR: failed to find fragment program %@.", fileName); return NO; } } /* if (result != nil && prefix != nil) { result = [prefix stringByAppendingString:result]; } */ if (outResult != NULL) *outResult = result; return YES; } static NSString *GetGLSLInfoLog(GLhandleARB shaderObject) { GLint length; GLcharARB *log = NULL; NSString *result = nil; OO_ENTER_OPENGL(); if (EXPECT_NOT(shaderObject == NULL_SHADER)) return nil; OOGL(glGetObjectParameterivARB(shaderObject, GL_OBJECT_INFO_LOG_LENGTH_ARB, &length)); log = malloc(length); if (log == NULL) { length = 1024; log = malloc(length); if (log == NULL) return @""; } OOGL(glGetInfoLogARB(shaderObject, length, NULL, log)); result = [NSString stringWithUTF8String:log]; if (result == nil) result = [[[NSString alloc] initWithBytes:log length:length - 1 encoding:NSISOLatin1StringEncoding] autorelease]; free(log); return result; } #endif // OO_SHADERS oolite-1.82/src/Core/Materials/OOShaderUniform.h000066400000000000000000000061731256642440500215330ustar00rootroot00000000000000/* OOShaderUniform.h Manages a uniform variable for OOShaderMaterial. Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOShaderMaterial.h" #if OO_SHADERS #import "OOMaths.h" @class OOColor; @interface OOShaderUniform: NSObject { @private NSString *name; GLint location; uint8_t isBinding: 1, // flags that apply only to bindings: isActiveBinding: 1, convertClamp: 1, convertNormalize: 1, convertToMatrix: 1, bindToSuper: 1; uint8_t type; union { GLint constInt; GLfloat constFloat; GLfloat constVector[4]; OOMatrix constMatrix; struct { OOWeakReference *object; SEL selector; IMP method; } binding; } value; } - (id)initWithName:(NSString *)uniformName shaderProgram:(OOShaderProgram *)shaderProgram intValue:(GLint)constValue; - (id)initWithName:(NSString *)uniformName shaderProgram:(OOShaderProgram *)shaderProgram floatValue:(GLfloat)constValue; - (id)initWithName:(NSString *)uniformName shaderProgram:(OOShaderProgram *)shaderProgram vectorValue:(GLfloat[4])constValue; - (id)initWithName:(NSString *)uniformName shaderProgram:(OOShaderProgram *)shaderProgram colorValue:(OOColor *)constValue; // Converted to vector - (id)initWithName:(NSString *)uniformName shaderProgram:(OOShaderProgram *)shaderProgram quaternionValue:(Quaternion)constValue asMatrix:(BOOL)asMatrix; // Converted to vector (in xyzw order, not wxyz!) or rotation matrix. - (id)initWithName:(NSString *)uniformName shaderProgram:(OOShaderProgram *)shaderProgram matrixValue:(OOMatrix)constValue; /* "Convert" has different meanings for different types. For float and int types, it clamps to the range [0, 1]. For vector types, it normalizes. For quaternions, it converts to rotation matrix (instead of vec4). */ - (id)initWithName:(NSString *)uniformName shaderProgram:(OOShaderProgram *)shaderProgram boundToObject:(id)target property:(SEL)selector convertOptions:(OOUniformConvertOptions)options; - (void)apply; - (void)setBindingTarget:(id)target; @end #endif // OO_SHADERS oolite-1.82/src/Core/Materials/OOShaderUniform.m000066400000000000000000000342611256642440500215370ustar00rootroot00000000000000/* OOShaderUniform.m Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOShaderUniform.h" #if OO_SHADERS #import "OOShaderProgram.h" #import "OOFunctionAttributes.h" #include #import "OOMaths.h" #import "OOOpenGLExtensionManager.h" #import "OOShaderUniformMethodType.h" @interface OOShaderUniform (OOPrivate) - (id)initWithName:(NSString *)uniformName shaderProgram:(OOShaderProgram *)shaderProgram; - (void)applySimple; - (void)applyBinding; @end @implementation OOShaderUniform - (id)initWithName:(NSString *)uniformName shaderProgram:(OOShaderProgram *)shaderProgram intValue:(GLint)constValue { self = [self initWithName:uniformName shaderProgram:shaderProgram]; if (self != nil) { type = kOOShaderUniformTypeInt; value.constInt = constValue; } return self; } - (id)initWithName:(NSString *)uniformName shaderProgram:(OOShaderProgram *)shaderProgram floatValue:(GLfloat)constValue { self = [self initWithName:uniformName shaderProgram:shaderProgram]; if (self != nil) { type = kOOShaderUniformTypeFloat; value.constFloat = constValue; } return self; } - (id)initWithName:(NSString *)uniformName shaderProgram:(OOShaderProgram *)shaderProgram vectorValue:(GLfloat[4])constValue { self = [self initWithName:uniformName shaderProgram:shaderProgram]; if (self != nil) { type = kOOShaderUniformTypeVector; memcpy(value.constVector, constValue, sizeof value.constVector); } return self; } - (id)initWithName:(NSString *)uniformName shaderProgram:(OOShaderProgram *)shaderProgram colorValue:(OOColor *)constValue { if (EXPECT_NOT(constValue == nil)) { [self release]; return nil; } self = [self initWithName:uniformName shaderProgram:shaderProgram]; if (self != nil) { type = kOOShaderUniformTypeVector; value.constVector[0] = [constValue redComponent]; value.constVector[1] = [constValue greenComponent]; value.constVector[2] = [constValue blueComponent]; value.constVector[3] = [constValue alphaComponent]; } return self; } - (id)initWithName:(NSString *)uniformName shaderProgram:(OOShaderProgram *)shaderProgram quaternionValue:(Quaternion)constValue asMatrix:(BOOL)asMatrix { self = [self initWithName:uniformName shaderProgram:shaderProgram]; if (self != nil) { if (asMatrix) { type = kOOShaderUniformTypeMatrix; value.constMatrix = OOMatrixForQuaternionRotation(constValue); } else { type = kOOShaderUniformTypeVector; value.constVector[0] = constValue.x; value.constVector[1] = constValue.y; value.constVector[2] = constValue.z; value.constVector[3] = constValue.w; } } return self; } - (id)initWithName:(NSString *)uniformName shaderProgram:(OOShaderProgram *)shaderProgram matrixValue:(OOMatrix)constValue { self = [self initWithName:uniformName shaderProgram:shaderProgram]; if (self != nil) { type = kOOShaderUniformTypeMatrix; value.constMatrix = constValue; } return self; } - (id)initWithName:(NSString *)uniformName shaderProgram:(OOShaderProgram *)shaderProgram boundToObject:(id)target property:(SEL)selector convertOptions:(OOUniformConvertOptions)options { BOOL OK = YES; if (EXPECT_NOT(uniformName == NULL || shaderProgram == NULL || selector == NULL)) OK = NO; if (OK) { self = [super init]; if (self == nil) OK = NO; } if (OK) { location = glGetUniformLocationARB([shaderProgram program], [uniformName UTF8String]); if (location == -1) { OK = NO; OOLog(@"shader.uniform.bind.failed", @"Could not bind uniform \"%@\" to -[%@ %@] (no uniform of that name could be found).", uniformName, [target class], NSStringFromSelector(selector)); } } // If we're still OK, it's a bindable method. if (OK) { name = [uniformName retain]; isBinding = YES; value.binding.selector = selector; convertClamp = (options & kOOUniformConvertClamp) != 0; convertNormalize = (options & kOOUniformConvertNormalize) != 0; convertToMatrix = (options & kOOUniformConvertToMatrix) != 0; bindToSuper = (options & kOOUniformBindToSuperTarget) != 0; if (target != nil) [self setBindingTarget:target]; } if (!OK) { [self release]; self = nil; } return self; } - (void)dealloc { [name release]; if (isBinding) [value.binding.object release]; [super dealloc]; } - (NSString *)description { NSString *valueDesc = nil; NSString *valueType = nil; id object; if (isBinding) { object = [value.binding.object weakRefUnderlyingObject]; if (object != nil) { valueDesc = [NSString stringWithFormat:@"[<%@ %p> %@]", [object class], value.binding.object, NSStringFromSelector(value.binding.selector)]; } else { valueDesc = @"0"; } } else { switch (type) { case kOOShaderUniformTypeInt: valueDesc = [NSString stringWithFormat:@"%i", value.constInt]; break; case kOOShaderUniformTypeFloat: valueDesc = [NSString stringWithFormat:@"%g", value.constFloat]; break; case kOOShaderUniformTypeVector: { Vector v = { value.constVector[0], value.constVector[1], value.constVector[2] }; valueDesc = VectorDescription(v); } break; case kOOShaderUniformTypeMatrix: valueDesc = OOMatrixDescription(value.constMatrix); break; } } switch (type) { case kOOShaderUniformTypeChar: case kOOShaderUniformTypeUnsignedChar: case kOOShaderUniformTypeShort: case kOOShaderUniformTypeUnsignedShort: case kOOShaderUniformTypeInt: case kOOShaderUniformTypeUnsignedInt: case kOOShaderUniformTypeLong: case kOOShaderUniformTypeUnsignedLong: valueType = @"int"; break; case kOOShaderUniformTypeFloat: case kOOShaderUniformTypeDouble: valueType = @"float"; break; case kOOShaderUniformTypeVector: case kOOShaderUniformTypeHPVector: valueType = @"vec4"; break; case kOOShaderUniformTypeQuaternion: valueType = @"vec4 (quaternion)"; break; case kOOShaderUniformTypeMatrix: valueType = @"matrix"; break; case kOOShaderUniformTypePoint: valueType = @"vec2"; break; case kOOShaderUniformTypeObject: valueType = @"object-binding"; break; } if (valueType == nil) valueDesc = @"INVALID"; if (valueDesc == nil) valueDesc = @"INVALID"; /* Examples: {1: int tex1 = 1;} {3: float laser_heat_level = [ laserHeatLevel];} */ return [NSString stringWithFormat:@"<%@ %p>{%i: %@ %@ = %@;}", [self class], self, location, valueType, name, valueDesc]; } - (void)apply { if (isBinding) { if (isActiveBinding) [self applyBinding]; } else [self applySimple]; } - (void)setBindingTarget:(id)target { BOOL OK = YES; NSMethodSignature *signature = nil; NSUInteger argCount; NSString *methodProblem = nil; id superCandidate = nil; if (!isBinding) return; // Resolve "supertarget" if applicable if (bindToSuper) { for (;;) { if (![target respondsToSelector:@selector(superShaderBindingTarget)]) break; superCandidate = [(id)target superShaderBindingTarget]; if (superCandidate == nil || superCandidate == target) break; target = superCandidate; } } [value.binding.object release]; value.binding.object = [target weakRetain]; if (target == nil) { isActiveBinding = NO; return; } if (OK) { if (![target respondsToSelector:value.binding.selector]) { methodProblem = @"target does not respond to selector"; OK = NO; } } if (OK) { value.binding.method = [(id)target methodForSelector:value.binding.selector]; if (value.binding.method == NULL) { methodProblem = @"could not retrieve method implementation"; OK = NO; } } if (OK) { signature = [(id)target methodSignatureForSelector:value.binding.selector]; if (signature == nil) { methodProblem = @"could not retrieve method signature"; OK = NO; } } if (OK) { argCount = [signature numberOfArguments]; if (argCount != 2) // "no-arguments" methods actually take two arguments, self and _msg. { methodProblem = @"only methods which do not require arguments may be bound to"; OK = NO; } } if (OK) { type = OOShaderUniformTypeFromMethodSignature(signature); if (type == kOOShaderUniformTypeInvalid) { OK = NO; methodProblem = [NSString stringWithFormat:@"unsupported type \"%s\"", [signature methodReturnType]]; } } isActiveBinding = OK; if (!OK) OOLog(@"shader.uniform.bind.failed", @"Shader could not bind uniform \"%@\" to -[%@ %@] (%@).", name, [target class], NSStringFromSelector(value.binding.selector), methodProblem); } @end @implementation OOShaderUniform (OOPrivate) // Designated initializer. - (id)initWithName:(NSString *)uniformName shaderProgram:(OOShaderProgram *)shaderProgram { BOOL OK = YES; if (EXPECT_NOT(uniformName == NULL || shaderProgram == NULL)) OK = NO; if (OK) { self = [super init]; if (self == nil) OK = NO; } if (OK) { location = glGetUniformLocationARB([shaderProgram program], [uniformName UTF8String]); if (location == -1) OK = NO; } if (OK) { name = [uniformName copy]; } if (!OK) { [self release]; self = nil; } return self; } - (void)applySimple { switch (type) { case kOOShaderUniformTypeInt: OOGL(glUniform1iARB(location, value.constInt)); break; case kOOShaderUniformTypeFloat: OOGL(glUniform1fARB(location, value.constFloat)); break; case kOOShaderUniformTypeVector: OOGL(glUniform4fvARB(location, 1, value.constVector)); break; case kOOShaderUniformTypeMatrix: GLUniformMatrix(location, value.constMatrix); } } - (void)applyBinding { id object = nil; GLint iVal; GLfloat fVal; Vector vVal; HPVector hpvVal; GLfloat expVVal[4]; OOMatrix mVal; Quaternion qVal; NSPoint pVal = {0}; BOOL isInt = NO, isFloat = NO, isVector = NO, isMatrix = NO, isPoint = NO; id objVal = nil; /* Design note: if the object has been dealloced, or an exception occurs, do nothing. Shaders can specify a default value for uniforms, which will be used when no setting has been provided by the host program. I considered clearing value.binding.object if the underlying object is gone, but adding code to save a small amount of spacein a case that shouldn't occur in normal usage is silly. */ object = [value.binding.object weakRefUnderlyingObject]; if (object == nil) return; switch (type) { case kOOShaderUniformTypeChar: case kOOShaderUniformTypeUnsignedChar: case kOOShaderUniformTypeShort: case kOOShaderUniformTypeUnsignedShort: case kOOShaderUniformTypeInt: case kOOShaderUniformTypeUnsignedInt: case kOOShaderUniformTypeLong: case kOOShaderUniformTypeUnsignedLong: iVal = (GLint)OOCallIntegerMethod(object, value.binding.selector, value.binding.method, type); isInt = YES; break; case kOOShaderUniformTypeFloat: case kOOShaderUniformTypeDouble: fVal = OOCallFloatMethod(object, value.binding.selector, value.binding.method, type); isFloat = YES; break; case kOOShaderUniformTypeVector: vVal = ((VectorReturnMsgSend)value.binding.method)(object, value.binding.selector); if (convertNormalize) vVal = vector_normal(vVal); expVVal[0] = vVal.x; expVVal[1] = vVal.y; expVVal[2] = vVal.z; expVVal[3] = 1.0f; isVector = YES; break; case kOOShaderUniformTypeHPVector: hpvVal = ((HPVectorReturnMsgSend)value.binding.method)(object, value.binding.selector); if (convertNormalize) hpvVal = HPvector_normal(hpvVal); expVVal[0] = (GLfloat)hpvVal.x; expVVal[1] = (GLfloat)hpvVal.y; expVVal[2] = (GLfloat)hpvVal.z; expVVal[3] = 1.0f; isVector = YES; break; case kOOShaderUniformTypeQuaternion: qVal = ((QuaternionReturnMsgSend)value.binding.method)(object, value.binding.selector); if (convertToMatrix) { mVal = OOMatrixForQuaternionRotation(qVal); isMatrix = YES; } else { expVVal[0] = qVal.x; expVVal[1] = qVal.y; expVVal[2] = qVal.z; expVVal[3] = qVal.w; isVector = YES; } break; case kOOShaderUniformTypeMatrix: mVal = ((MatrixReturnMsgSend)value.binding.method)(object, value.binding.selector); isMatrix = YES; break; case kOOShaderUniformTypePoint: pVal = ((PointReturnMsgSend)value.binding.method)(object, value.binding.selector); isPoint = YES; break; case kOOShaderUniformTypeObject: objVal = ((ObjectReturnMsgSend)value.binding.method)(object, value.binding.selector); if ([objVal isKindOfClass:[NSNumber class]]) { fVal = [objVal floatValue]; isFloat = YES; } else if ([objVal isKindOfClass:[OOColor class]]) { OOColor *color = objVal; expVVal[0] = [color redComponent]; expVVal[1] = [color greenComponent]; expVVal[2] = [color blueComponent]; expVVal[3] = [color alphaComponent]; isVector = YES; } break; } if (isFloat) { if (convertClamp) fVal = OOClamp_0_1_f(fVal); OOGL(glUniform1fARB(location, fVal)); } else if (isInt) { if (convertClamp) iVal = iVal ? 1 : 0; OOGL(glUniform1iARB(location, iVal)); } else if (isPoint) { GLfloat v2[2] = { pVal.x, pVal.y }; OOGL(glUniform2fvARB(location, 1, v2)); } else if (isVector) { OOGL(glUniform4fvARB(location, 1, expVVal)); } else if (isMatrix) { GLUniformMatrix(location, mVal); } } @end #endif // OO_SHADERS oolite-1.82/src/Core/Materials/OOShaderUniformMethodType.h000066400000000000000000000071071256642440500235340ustar00rootroot00000000000000/* OOShaderUniformMethodType.h Type code declarations and OpenStep implementation agnostic method type matching for uniform bindings. Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOOpenGLExtensionManager.h" #if OO_SHADERS || !defined(NDEBUG) #import "OOMaths.h" #import "OOHPVector.h" typedef enum { kOOShaderUniformTypeInvalid, // Not valid for bindings or constants kOOShaderUniformTypeChar, // Binding only kOOShaderUniformTypeUnsignedChar, // Binding only kOOShaderUniformTypeShort, // Binding only kOOShaderUniformTypeUnsignedShort, // Binding only kOOShaderUniformTypeInt, // Binding or constant kOOShaderUniformTypeUnsignedInt, // Binding only kOOShaderUniformTypeLong, // Binding only kOOShaderUniformTypeUnsignedLong, // Binding only kOOShaderUniformTypeLongLong, // Binding only kOOShaderUniformTypeUnsignedLongLong, // Binding only kOOShaderUniformTypeFloat, // Binding or constant kOOShaderUniformTypeDouble, // Binding only kOOShaderUniformTypeVector, // Binding or constant kOOShaderUniformTypeHPVector, // Binding only kOOShaderUniformTypeQuaternion, // Binding or constant kOOShaderUniformTypeMatrix, // Binding or constant kOOShaderUniformTypePoint, // Binding only kOOShaderUniformTypeObject, // Binding only kOOShaderUniformTypeCount // Not valid for bindings or constants } OOShaderUniformType; OOShaderUniformType OOShaderUniformTypeFromMethodSignature(NSMethodSignature *signature); long long OOCallIntegerMethod(id object, SEL selector, IMP method, OOShaderUniformType type); double OOCallFloatMethod(id object, SEL selector, IMP method, OOShaderUniformType type); typedef char (*CharReturnMsgSend)(id, SEL); typedef unsigned char (*UnsignedCharReturnMsgSend)(id, SEL); typedef short (*ShortReturnMsgSend)(id, SEL); typedef unsigned short (*UnsignedShortReturnMsgSend)(id, SEL); typedef int (*IntReturnMsgSend)(id, SEL); typedef unsigned int (*UnsignedIntReturnMsgSend)(id, SEL); typedef long (*LongReturnMsgSend)(id, SEL); typedef unsigned long (*UnsignedLongReturnMsgSend)(id, SEL); typedef long long (*LongLongReturnMsgSend)(id, SEL); typedef unsigned long long (*UnsignedLongLongReturnMsgSend)(id, SEL); typedef float (*FloatReturnMsgSend)(id, SEL); typedef double (*DoubleReturnMsgSend)(id, SEL); typedef Vector (*VectorReturnMsgSend)(id, SEL); typedef HPVector (*HPVectorReturnMsgSend)(id, SEL); typedef Quaternion (*QuaternionReturnMsgSend)(id, SEL); typedef OOMatrix (*MatrixReturnMsgSend)(id, SEL); typedef NSPoint (*PointReturnMsgSend)(id, SEL); typedef id (*ObjectReturnMsgSend)(id, SEL); #endif // OO_SHADERS oolite-1.82/src/Core/Materials/OOShaderUniformMethodType.m000066400000000000000000000165021256642440500235400ustar00rootroot00000000000000/* OOShaderUniformMethodType.m Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* For shader uniform binding to work, it is necessary to be able to tell the return type of a method. This is done by comparing the methodReturnType of a method's NSMethodSignature to those of methods in a template class, one method for each supported return type. Under OS X, the methodReturnType for a type foo is simply @encode(foo), but under the GNU runtime, it is the @encode() string for the entire method signature. In general, this is platform-defined. In order to maintain an implementation-agnostic approach, we get the signature from a known method of each time at runtime. NOTE: the GNU runtime's approach means that the methodReturnType differs between different method signatures with the same return type. For instance, a method -(id)foo:(int) will have a different methodReturnType than a method -(id)foo. As far as I can see this is a bug, but Oolite only supports binding to methods with no parameters, so this is not a problem. */ #import "OOShaderUniformMethodType.h" #if OO_SHADERS || !defined(NDEBUG) #import "OOMaths.h" static BOOL sInited = NO; static const char *sTemplates[kOOShaderUniformTypeCount]; static void InitTemplates(void); static const char *CopyTemplateForSelector(SEL selector); OOShaderUniformType OOShaderUniformTypeFromMethodSignature(NSMethodSignature *signature) { unsigned i; const char *typeCode = NULL; if (EXPECT_NOT(sInited == NO)) InitTemplates(); typeCode = [signature methodReturnType]; if (EXPECT_NOT(typeCode == NULL)) return kOOShaderUniformTypeInvalid; for (i = kOOShaderUniformTypeInvalid + 1; i != kOOShaderUniformTypeCount; ++i) { if (sTemplates[i] != NULL && strcmp(sTemplates[i], typeCode) == 0) return i; } return kOOShaderUniformTypeInvalid; } @interface OOShaderUniformTypeMethodSignatureTemplateClass: NSObject - (float)floatMethod; - (double)doubleMethod; - (signed char)signedCharMethod; - (unsigned char)unsignedCharMethod; - (signed short)signedShortMethod; - (unsigned short)unsignedShortMethod; - (signed int)signedIntMethod; - (unsigned int)unsignedIntMethod; - (signed long)signedLongMethod; - (unsigned long)unsignedLongMethod; - (Vector)vectorMethod; - (HPVector)hpvectorMethod; - (Quaternion)quaternionMethod; - (OOMatrix)matrixMethod; - (NSPoint)pointMethod; - (id)idMethod; @end static void InitTemplates(void) { #define GET_TEMPLATE(enumValue, sel) do { \ sTemplates[enumValue] = CopyTemplateForSelector(@selector(sel)); \ } while (0) GET_TEMPLATE(kOOShaderUniformTypeChar, signedCharMethod); GET_TEMPLATE(kOOShaderUniformTypeUnsignedChar, unsignedCharMethod); GET_TEMPLATE(kOOShaderUniformTypeShort, signedShortMethod); GET_TEMPLATE(kOOShaderUniformTypeUnsignedShort, unsignedShortMethod); GET_TEMPLATE(kOOShaderUniformTypeInt, signedIntMethod); GET_TEMPLATE(kOOShaderUniformTypeUnsignedInt, unsignedIntMethod); GET_TEMPLATE(kOOShaderUniformTypeLong, signedLongMethod); GET_TEMPLATE(kOOShaderUniformTypeUnsignedLong, unsignedLongMethod); GET_TEMPLATE(kOOShaderUniformTypeFloat, floatMethod); GET_TEMPLATE(kOOShaderUniformTypeDouble, doubleMethod); GET_TEMPLATE(kOOShaderUniformTypeVector, vectorMethod); GET_TEMPLATE(kOOShaderUniformTypeHPVector, hpvectorMethod); GET_TEMPLATE(kOOShaderUniformTypeQuaternion, quaternionMethod); GET_TEMPLATE(kOOShaderUniformTypeMatrix, matrixMethod); GET_TEMPLATE(kOOShaderUniformTypePoint, pointMethod); GET_TEMPLATE(kOOShaderUniformTypeObject, idMethod); sInited = YES; } static const char *CopyTemplateForSelector(SEL selector) { NSMethodSignature *signature = nil; const char *typeCode = NULL; signature = [OOShaderUniformTypeMethodSignatureTemplateClass instanceMethodSignatureForSelector:selector]; typeCode = [signature methodReturnType]; /* typeCode is *probably* a constant, but this isn't formally guaranteed as far as I'm aware, so we make a copy of it. */ return typeCode ? strdup(typeCode) : NULL; } @implementation OOShaderUniformTypeMethodSignatureTemplateClass: NSObject - (signed char)signedCharMethod { return 0; } - (unsigned char)unsignedCharMethod { return 0; } - (signed short)signedShortMethod { return 0; } - (unsigned short)unsignedShortMethod { return 0; } - (signed int)signedIntMethod { return 0; } - (unsigned int)unsignedIntMethod { return 0; } - (signed long)signedLongMethod { return 0; } - (unsigned long)unsignedLongMethod { return 0; } - (float)floatMethod { return 0.0f; } - (double)doubleMethod { return 0.0; } - (Vector)vectorMethod { Vector v = {0}; return v; } - (HPVector)hpvectorMethod { HPVector v = {0}; return v; } - (Quaternion)quaternionMethod { Quaternion q = {0}; return q; } - (OOMatrix)matrixMethod { return kZeroMatrix; } - (NSPoint)pointMethod { return NSZeroPoint; } - (id)idMethod { return nil; } @end long long OOCallIntegerMethod(id object, SEL selector, IMP method, OOShaderUniformType type) { switch (type) { case kOOShaderUniformTypeChar: return ((CharReturnMsgSend)method)(object, selector); case kOOShaderUniformTypeUnsignedChar: return ((UnsignedCharReturnMsgSend)method)(object, selector); case kOOShaderUniformTypeShort: return ((ShortReturnMsgSend)method)(object, selector); case kOOShaderUniformTypeUnsignedShort: return ((UnsignedShortReturnMsgSend)method)(object, selector); case kOOShaderUniformTypeInt: return ((IntReturnMsgSend)method)(object, selector); case kOOShaderUniformTypeUnsignedInt: return ((UnsignedIntReturnMsgSend)method)(object, selector); case kOOShaderUniformTypeLong: return ((LongReturnMsgSend)method)(object, selector); case kOOShaderUniformTypeUnsignedLong: return ((UnsignedLongReturnMsgSend)method)(object, selector); case kOOShaderUniformTypeLongLong: return ((LongLongReturnMsgSend)method)(object, selector); case kOOShaderUniformTypeUnsignedLongLong: return ((UnsignedLongLongReturnMsgSend)method)(object, selector); default: return 0; } } double OOCallFloatMethod(id object, SEL selector, IMP method, OOShaderUniformType type) { switch (type) { case kOOShaderUniformTypeFloat: return ((FloatReturnMsgSend)method)(object, selector); case kOOShaderUniformTypeDouble: return ((DoubleReturnMsgSend)method)(object, selector); default: return 0; } } #endif // OO_SHADERS oolite-1.82/src/Core/Materials/OOSingleTextureMaterial.h000066400000000000000000000035121256642440500232400ustar00rootroot00000000000000/* OOSingleTextureMaterial.h A material with a single texture (and no shaders). Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOBasicMaterial.h" @class OOTexture; @interface OOSingleTextureMaterial: OOBasicMaterial { @private OOTexture *_texture; } /* In addition to OOBasicMateral configuration keys, an OOTexture configuration dictionary may be used. If there is a "texture" entry, it will be used; otherwise, if there is a "textures" array, its first member will be used. If the found OOTexture config dictionary contains a "name" key, it will be used in preference to the name parameter. */ - (id)initWithName:(NSString *)name configuration:(NSDictionary *)configuration; // Designated initializer - (id) initWithName:(NSString *)name texture:(OOTexture *)texture configuration:(NSDictionary *)configuration; @end oolite-1.82/src/Core/Materials/OOSingleTextureMaterial.m000066400000000000000000000052431256642440500232500ustar00rootroot00000000000000/* OOSingleTextureMaterial.h Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOSingleTextureMaterial.h" #import "OOTexture.h" #import "OOCollectionExtractors.h" #import "OOFunctionAttributes.h" @implementation OOSingleTextureMaterial - (id)initWithName:(NSString *)name configuration:(NSDictionary *)configuration { id texSpec = nil; if (configuration != nil) { texSpec = [configuration oo_textureSpecifierForKey:@"diffuse_map" defaultName:name]; } else { texSpec = name; } return [self initWithName:name texture:[OOTexture textureWithConfiguration:texSpec] configuration:configuration]; } - (id) initWithName:(NSString *)name texture:(OOTexture *)texture configuration:(NSDictionary *)configuration { if (name != nil && texture != nil) { self = [super initWithName:name configuration:configuration]; if (self != nil) { _texture = [texture retain]; } } else { DESTROY(self); } return self; } - (void)dealloc { [self willDealloc]; [_texture release]; [super dealloc]; } - (NSString *) descriptionComponents { return [_texture description]; } - (BOOL)doApply { if (EXPECT_NOT(![super doApply])) return NO; [_texture apply]; return YES; } - (void)unapplyWithNext:(OOMaterial *)next { if (![next isKindOfClass:[OOSingleTextureMaterial class]]) [OOTexture applyNone]; [super unapplyWithNext:next]; } - (void)ensureFinishedLoading { [_texture ensureFinishedLoading]; } - (BOOL) isFinishedLoading { return [_texture isFinishedLoading]; } - (BOOL) wantsNormalsAsTextureCoordinates { return [_texture isCubeMap]; } #ifndef NDEBUG - (NSSet *) allTextures { return [NSSet setWithObject:_texture]; } #endif @end oolite-1.82/src/Core/Materials/OOStandaloneAtmosphereGenerator.h000066400000000000000000000033451256642440500247520ustar00rootroot00000000000000/* OOStandaloneAtmosphereGenerator.h Generator for planet atmospheres when the planet is using a non-generated diffuse map. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOTextureGenerator.h" #import "OOMaths.h" #define PERLIN_3D 0 typedef struct OOStandaloneAtmosphereGeneratorInfo { RANROTSeed seed; unsigned width; unsigned height; // Atmosphere parameters. float cloudAlpha; float cloudFraction; FloatRGB airColor; FloatRGB cloudColor; FloatRGB paleCloudColor; // Noise generation stuff. float *fbmBuffer; #if PERLIN_3D uint16_t *permutations; #endif } OOStandaloneAtmosphereGeneratorInfo; @interface OOStandaloneAtmosphereGenerator: OOTextureGenerator { @private OOStandaloneAtmosphereGeneratorInfo _info; unsigned _planetScale; } - (id) initWithPlanetInfo:(NSDictionary *)planetInfo; + (OOTexture *) planetTextureWithInfo:(NSDictionary *)planetInfo; + (BOOL) generateAtmosphereTexture:(OOTexture **)texture withInfo:(NSDictionary *)planetInfo; @end oolite-1.82/src/Core/Materials/OOStandaloneAtmosphereGenerator.m000066400000000000000000000510231256642440500247530ustar00rootroot00000000000000/* OOStandaloneAtmosphereGenerator.m Generator for atmosphere textures when the planet is using a non-generated diffuse map. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOStellarBody.h" #if NEW_PLANETS #define DEBUG_DUMP ( 0 && !defined(NDEBUG)) #define DEBUG_DUMP_RAW ( 1 && DEBUG_DUMP) #define ALBEDO_FACTOR 0.7f // Overall darkening of everything, allowing better contrast for snow and specular highlights. #import "OOStandaloneAtmosphereGenerator.h" #import "OOCollectionExtractors.h" #import "OOColor.h" #ifndef TEXGEN_TEST_RIG #import "OOTexture.h" #import "Universe.h" #endif #if DEBUG_DUMP #import "MyOpenGLView.h" #endif #define FREE(x) do { if (0) { void *x__ = x; x__ = x__; } /* Preceeding is for type checking only. */ void **x_ = (void **)&(x); free(*x_); *x_ = NULL; } while (0) #define PLANET_TEXTURE_OPTIONS (kOOTextureMinFilterLinear | kOOTextureMagFilterLinear | kOOTextureRepeatS | kOOTextureNoShrink) enum { kRandomBufferSize = 128 }; @interface OOStandaloneAtmosphereGenerator (Private) #if DEBUG_DUMP_RAW - (void) dumpNoiseBuffer:(float *)noise; #endif @end static FloatRGB FloatRGBFromDictColor(NSDictionary *dictionary, NSString *key); static BOOL FillFBMBuffer(OOStandaloneAtmosphereGeneratorInfo *info); static float QFactor(float *accbuffer, int x, int y, unsigned width, float polar_y_value, float bias, float polar_y); static float BlendAlpha(float fraction, float a, float b); static FloatRGBA CloudMix(OOStandaloneAtmosphereGeneratorInfo *info, float q, float nearPole); enum { #if PERLIN_3D && !TEXGEN_TEST_RIG kPlanetAspectRatio = 2, #else kPlanetAspectRatio = 1, // Ideally, aspect ratio would be 2:1 - keeping it as 1:1 for now - Kaks 20091211 #endif kPlanetScaleOffset = 8 - kPlanetAspectRatio, kPlanetScale256x256 = 1, kPlanetScale512x512, kPlanetScale1024x1024, kPlanetScale2048x2048, kPlanetScale4096x4096, kPlanetScaleReducedDetail = kPlanetScale512x512, kPlanetScaleFullDetail = kPlanetScale1024x1024 }; @implementation OOStandaloneAtmosphereGenerator - (id) initWithPlanetInfo:(NSDictionary *)planetInfo { OOLog(@"texture.planet.generate",@"Initialising standalone atmosphere generator"); // AllowCubeMap not used yet but might be in future if ((self = [super initWithPath:[NSString stringWithFormat:@"OOStandaloneAtmosphereTexture@%p", self] options:kOOTextureAllowCubeMap])) { OOLog(@"texture.planet.generate",@"Extracting parameters for generator %@",self); [[planetInfo objectForKey:@"noise_map_seed"] getValue:&_info.seed]; OOLog(@"texture.planet.generate",@"Extracting atmosphere parameters"); // we are an atmosphere: _info.cloudAlpha = [planetInfo oo_floatForKey:@"cloud_alpha" defaultValue:1.0f]; _info.cloudFraction = OOClamp_0_1_f([planetInfo oo_floatForKey:@"cloud_fraction" defaultValue:0.3]); _info.cloudColor = FloatRGBFromDictColor(planetInfo, @"cloud_color"); _info.paleCloudColor = FloatRGBFromDictColor(planetInfo, @"polar_cloud_color"); #ifndef TEXGEN_TEST_RIG if ([UNIVERSE detailLevel] < DETAIL_LEVEL_SHADERS) { _planetScale = kPlanetScaleReducedDetail; } else { _planetScale = kPlanetScaleFullDetail; } #else _planetScale = kPlanetScale4096x4096; #endif } return self; } + (OOTexture *) planetTextureWithInfo:(NSDictionary *)planetInfo { OOTexture *result = nil; OOStandaloneAtmosphereGenerator *generator = [[self alloc] initWithPlanetInfo:planetInfo]; if (generator != nil) { result = [OOTexture textureWithGenerator:generator]; [generator release]; } return result; } + (BOOL) generateAtmosphereTexture:(OOTexture **)texture withInfo:(NSDictionary *)planetInfo { NSParameterAssert(texture != NULL); OOStandaloneAtmosphereGenerator *atmoGen = [[[self alloc] initWithPlanetInfo:planetInfo] autorelease]; if (atmoGen == nil) return NO; *texture = [OOTexture textureWithGenerator:atmoGen]; return *texture != nil; } - (void) dealloc { [super dealloc]; } - (NSString *) descriptionComponents { return [NSString stringWithFormat:@"seed: %u,%u", _info.seed.high, _info.seed.low]; } - (uint32_t) textureOptions { return PLANET_TEXTURE_OPTIONS; } - (NSString *) cacheKey { return [NSString stringWithFormat:@"OOStandaloneAtmosphereGenerator-@%u\n%u,%u/%u,%u/%f/%f/%f,%f,%f/%f,%f,%f/%f,%f,%f", _planetScale, _info.width, _info.height, _info.seed.high, _info.seed.low, _info.cloudAlpha, _info.cloudFraction, _info.airColor.r, _info.airColor.g, _info.airColor.b, _info.cloudColor.r, _info.cloudColor.g, _info.cloudColor.b, _info.paleCloudColor.r, _info.paleCloudColor.g, _info.paleCloudColor.b ]; } - (BOOL)getResult:(OOPixMap *)outData format:(OOTextureDataFormat *)outFormat width:(uint32_t *)outWidth height:(uint32_t *)outHeight { BOOL waiting = NO; if (![self isReady]) { waiting = true; OOLog(@"texture.planet.generate.wait", @"%s generator %@", "Waiting for", self); } BOOL result = [super getResult:outData format:outFormat originalWidth:outWidth originalHeight:outHeight]; if (waiting) { OOLog(@"texture.planet.generate.dequeue", @"%s generator %@", result ? "Dequeued" : "Failed to dequeue", self); } else { OOLog(@"texture.planet.generate.dequeue", @"%s generator %@ without waiting.", result ? "Dequeued" : "Failed to dequeue", self); } return result; } /* TODO: fix duplication between here and OOPlanetTextureGenerator of * various noise, interpolation, etc. functions */ - (void) loadTexture { OOLog(@"texture.planet.generate.begin", @"Started generator %@", self); BOOL success = NO; uint8_t *aBuffer = NULL, *apx = NULL; float *randomBuffer = NULL; _height = _info.height = 1 << (_planetScale + kPlanetScaleOffset); _width = _info.width = _height * kPlanetAspectRatio; #define FAIL_IF(cond) do { if (EXPECT_NOT(cond)) goto END; } while (0) #define FAIL_IF_NULL(x) FAIL_IF((x) == NULL) aBuffer = malloc(4 * _width * _height); FAIL_IF_NULL(aBuffer); apx = aBuffer; FAIL_IF(!FillFBMBuffer(&_info)); #if DEBUG_DUMP_RAW [self dumpNoiseBuffer:_info.fbmBuffer]; #endif float paleClouds = (_info.cloudFraction * _info.fbmBuffer[0] < 1.0f - _info.cloudFraction) ? 0.0f : 1.0f; int x, y; FloatRGBA color; float q, nearPole; float rHeight = 1.0f / _height; float fy, fHeight = _height; float cloudFraction = _info.cloudFraction; for (y = (int)_height - 1, fy = (float)y; y >= 0; y--, fy--) { nearPole = (2.0f * fy - fHeight) * rHeight; nearPole *= nearPole; for (x = (int)_width - 1; x >= 0; x--) { q = QFactor(_info.fbmBuffer, x, y, _width, paleClouds, cloudFraction, nearPole); color = CloudMix(&_info, q, nearPole); *apx++ = 255.0f * color.r; *apx++ = 255.0f * color.g; *apx++ = 255.0f * color.b; *apx++ = 255.0f * color.a * _info.cloudAlpha; } } success = YES; _format = kOOTextureDataRGBA; END: FREE(_info.fbmBuffer); FREE(randomBuffer); if (success) { _data = aBuffer; } else { FREE(aBuffer); } OOLog(@"texture.planet.generate.complete", @"Completed generator %@ %@successfully", self, success ? @"" : @"un"); #if DEBUG_DUMP if (success) { NSString *diffuseName = [NSString stringWithFormat:@"atmosphere-%u-%u-diffuse-new", _info.seed.high, _info.seed.low]; NSString *lightsName = [NSString stringWithFormat:@"atmosphere-%u-%u-alpha-new", _info.seed.high, _info.seed.low]; [[UNIVERSE gameView] dumpRGBAToRGBFileNamed:diffuseName andGrayFileNamed:lightsName bytes:aBuffer width:_width height:_height rowBytes:_width * 4]; } #endif } #if DEBUG_DUMP_RAW - (void) dumpNoiseBuffer:(float *)noise { NSString *noiseName = [NSString stringWithFormat:@"atmosphere-%u-%u-noise-new", _info.seed.high, _info.seed.low]; uint8_t *noisePx = malloc(_width * _height); unsigned x, y; for (y = 0; y < _height; y++) { for (x = 0; x < _width; x++) { noisePx[y * _width + x] = 255.0f * noise[y * _width + x]; } } [[UNIVERSE gameView] dumpGrayToFileNamed:noiseName bytes:noisePx width:_width height:_height rowBytes:_width]; FREE(noisePx); } #endif @end OOINLINE float Lerp(float v0, float v1, float fraction) { // Linear interpolation - equivalent to v0 * (1.0f - fraction) + v1 * fraction. return v0 + fraction * (v1 - v0); } static FloatRGB Blend(float fraction, FloatRGB a, FloatRGB b) { return (FloatRGB) { Lerp(b.r, a.r, fraction), Lerp(b.g, a.g, fraction), Lerp(b.b, a.b, fraction) }; } static float BlendAlpha(float fraction, float a, float b) { return Lerp(b, a, fraction); } static FloatRGBA CloudMix(OOStandaloneAtmosphereGeneratorInfo *info, float q, float nearPole) { //#define AIR_ALPHA (0.15f) //#define CLOUD_ALPHA (1.0f) // CIM: make distinction between cloud and not-cloud bigger #define AIR_ALPHA (0.05f) #define CLOUD_ALPHA (2.0f) #define POLAR_BOUNDARY (0.33f) #define CLOUD_BOUNDARY (0.5f) #define RECIP_CLOUD_BOUNDARY (1.0f / CLOUD_BOUNDARY) FloatRGB cloudColor = info->cloudColor; float alpha = info->cloudAlpha, portion = 0.0f; q -= CLOUD_BOUNDARY * 0.5f; if (nearPole > POLAR_BOUNDARY) { portion = nearPole > POLAR_BOUNDARY + 0.2f ? 1.0f : (nearPole - POLAR_BOUNDARY) * 5.0f; cloudColor = Blend(portion, info->paleCloudColor, cloudColor); portion = nearPole > POLAR_BOUNDARY + 0.625f ? 1.0f : (nearPole - POLAR_BOUNDARY) * 1.6f; } if (q <= 0.0f) { if (q >= -CLOUD_BOUNDARY) { alpha *= BlendAlpha(-q * 0.5f * RECIP_CLOUD_BOUNDARY + 0.5f, CLOUD_ALPHA, AIR_ALPHA); } else { alpha *= CLOUD_ALPHA; } } else { if (q < CLOUD_BOUNDARY) { alpha *= BlendAlpha( q * 0.5f * RECIP_CLOUD_BOUNDARY + 0.5f, AIR_ALPHA,CLOUD_ALPHA); } else { alpha *= AIR_ALPHA; } } // magic numbers! at the poles we have fairly thin air. alpha *= BlendAlpha(portion, 0.6f, 1.0f); if (alpha > 1.0) { alpha = 1.0; } return (FloatRGBA){ cloudColor.r, cloudColor.g, cloudColor.b, alpha }; } static FloatRGB FloatRGBFromDictColor(NSDictionary *dictionary, NSString *key) { OOColor *color = [dictionary objectForKey:key]; NSCAssert1([color isKindOfClass:[OOColor class]], @"Expected OOColor, got %@", [color class]); return (FloatRGB){ [color redComponent] * ALBEDO_FACTOR, [color greenComponent] * ALBEDO_FACTOR, [color blueComponent] * ALBEDO_FACTOR }; } OOINLINE float Hermite(float q) { return 3.0f * q * q - 2.0f * q * q * q; } #if __BIG_ENDIAN__ #define iman_ 1 #else #define iman_ 0 #endif // (same behaviour as, but faster than, FLOAT->INT) //Works OK for -32728 to 32727.99999236688 OOINLINE int32_t fast_floor(double val) { val += 68719476736.0 * 1.5; return (((int32_t*)&val)[iman_] >> 16); } static BOOL GenerateFBMNoise(OOStandaloneAtmosphereGeneratorInfo *info); static BOOL FillFBMBuffer(OOStandaloneAtmosphereGeneratorInfo *info) { NSCParameterAssert(info != NULL); // Allocate result buffer. info->fbmBuffer = calloc(info->width * info->height, sizeof (float)); if (info->fbmBuffer != NULL) { GenerateFBMNoise(info); return YES; } return NO; } #if PERLIN_3D enum { // Size of permutation buffer used to map integer coordinates to gradients. Must be power of two. kPermutationCount = 1 << 10, kPermutationMask = kPermutationCount - 1, // Number of different gradient vectors used. The most important thing is that the gradients are evenly distributed and sum to 0. kGradientCount = 12 }; #define LUT_DOT 1 #if !LUT_DOT static const Vector kGradients[kGradientCount] = { { 1, 1, 0 }, { -1, 1, 0 }, { 1, -1, 0 }, { -1, -1, 0 }, { 1, 0, 1 }, { -1, 0, 1 }, { 1, 0, -1 }, { -1, 0, -1 }, { 0, 1, 1 }, { 0, -1, 1 }, { 0, 1, -1 }, { 0, -1, -1 } }; #else static const uint8_t kGradients[kGradientCount][3] = { { 2, 2, 1 }, { 0, 2, 1 }, { 2, 0, 1 }, { 0, 0, 1 }, { 2, 1, 2 }, { 0, 1, 2 }, { 2, 1, 0 }, { 0, 1, 0 }, { 1, 2, 2 }, { 1, 0, 2 }, { 1, 2, 0 }, { 1, 0, 0 } }; /* Attempted speedup that didn't pan out, but might inspire something better. Since our gradient vectors' components are all -1, 0 or 1, we should be able to calculate the dot product without any multiplication at all, by simply summing the right combination of (x, y, z), (0, 0, 0) and (-x, -y, -z). This turns out to be slightly slower than using multiplication, even if the negations are precalculated. */ OOINLINE float TDot3(const uint8_t grad[3], float x, float y, float z) { float xt[3] = { -x, 0.0f, x }; float yt[3] = { -y, 0.0f, y }; float zt[3] = { -z, 0.0f, z }; return xt[grad[0]] + yt[grad[1]] + zt[grad[2]]; } #endif // Sample 3D noise function defined by kGradients and permutation table at point p. static float SampleNoise3D(OOStandaloneAtmosphereGeneratorInfo *info, Vector p) { uint16_t *permutations = info->permutations; // Split coordinates into integer and fractional parts. float fx = floor(p.x); float fy = floor(p.y); float fz = floor(p.z); int X = fx; int Y = fy; int Z = fz; float x = p.x - fx; float y = p.y - fy; float z = p.z - fz; // Select gradient for each corner. #define PERM(v) permutations[(v) & kPermutationMask] unsigned PZ0 = PERM(Z); unsigned PZ1 = PERM(Z + 1); unsigned PY0Z0 = PERM(Y + PZ0); unsigned PY1Z0 = PERM(Y + 1 + PZ0); unsigned PY0Z1 = PERM(Y + PZ1); unsigned PY1Z1 = PERM(Y + 1 + PZ1); unsigned gi000 = PERM(X + PY0Z0); unsigned gi010 = PERM(X + PY1Z0); unsigned gi100 = PERM(X + 1 + PY0Z0); unsigned gi110 = PERM(X + 1 + PY1Z0); unsigned gi001 = PERM(X + PY0Z1); unsigned gi011 = PERM(X + PY1Z1); unsigned gi101 = PERM(X + 1 + PY0Z1); unsigned gi111 = PERM(X + 1 + PY1Z1); #undef PERM // Calculate noise contributions from each of the eight corners. #if !LUT_DOT #define DOT3(idx, x_, y_, z_) dot_product(kGradients[(idx) % kGradientCount], (Vector){ (x_), (y_), (z_) }) #else #define DOT3(idx, x_, y_, z_) TDot3(kGradients[(idx) % kGradientCount], (x_), (y_), (z_)) #endif float x1 = x - 1.0f; float y1 = y - 1.0f; float z1 = z - 1.0f; float n000 = DOT3(gi000, x , y , z ); float n010 = DOT3(gi010, x , y1, z ); float n100 = DOT3(gi100, x1, y , z ); float n110 = DOT3(gi110, x1, y1, z ); float n001 = DOT3(gi001, x , y , z1); float n011 = DOT3(gi011, x , y1, z1); float n101 = DOT3(gi101, x1, y , z1); float n111 = DOT3(gi111, x1, y1, z1); #undef DOT3 // Compute the fade curve value for each of x, y, z float u = Hermite(x); float v = Hermite(y); float w = Hermite(z); // Interpolate along the contributions from each of the corners. float nx00 = Lerp(n000, n100, u); float nx01 = Lerp(n001, n101, u); float nx10 = Lerp(n010, n110, u); float nx11 = Lerp(n011, n111, u); float nxy0 = Lerp(nx00, nx10, v); float nxy1 = Lerp(nx01, nx11, v); float nxyz = Lerp(nxy0, nxy1, w); return nxyz; } /* Generate shuffled permutation order - each value from 0 to kPermutationCount - 1 occurs exactly once. This shuffling provides all the randomness in the resulting noise. Don't worry, though - for kPermutationCount = 1024 this allows for 4e2567 different noise maps, which is a lot more than RanRot will actually give us. */ static BOOL MakePermutationTable(OOStandaloneAtmosphereGeneratorInfo *info) { uint16_t *perms = malloc(sizeof *info->permutations * kPermutationCount); if (EXPECT_NOT(perms == NULL)) return NO; /* Fisher-Yates/Durstenfeld/Knuth shuffle, "inside-out" variant. Based on pseudocode from http://en.wikipedia.org/wiki/Fisher-Yates_shuffle When comparing to the pseudocode, note that it generates a one-based series, but this version generates a zero-based series. */ perms[0] = 0; uint16_t *curr = perms; uint16_t n; for (n = 1; n < kPermutationCount; n++) { uint16_t j = RanrotWithSeed(&info->seed) & kPermutationMask; *++curr = perms[j]; perms[j] = n - 1; } info->permutations = perms; return YES; } static BOOL GenerateFBMNoise(OOStandaloneAtmosphereGeneratorInfo *info) { BOOL OK = NO; FAIL_IF(!MakePermutationTable(info)); unsigned x, y, width = info->width, height = info->height; float lon, lat; // Longitude and latitude in radians. float dlon = 2.0f * M_PI / width; float dlat = M_PI / height; float *px = info->fbmBuffer; for (y = 0, lat = -M_PI_2; y < height; y++, lat += dlat) { float las = sin(lat); float lac = cos(lat); for (x = 0, lon = -M_PI; x < width; x++, lon += dlon) { // FIXME: in real life, we really don't want sin and cos per pixel. // Convert spherical coordinates to vector. float los = sin(lon); float loc = cos(lon); Vector p = { los * lac, las, loc * lac }; #if 1 // fBM unsigned octaveMask = 4; float octave = octaveMask; octaveMask -= 1; float scale = 0.4f; float sum = 0; while ((octaveMask + 1) < height) { Vector ps = vector_multiply_scalar(p, octave); sum += scale * SampleNoise3D(info, ps); octave *= 2.0f; octaveMask = (octaveMask << 1) | 1; scale *= 0.5f; } #else // Single octave p = vector_multiply_scalar(p, 4.0f); float sum = 0.5f * SampleNoise3D(info, p); #endif *px++ = sum + 0.5f; } } END: FREE(info->permutations); return OK; } #else // Old 2D value noise. static void FillRandomBuffer(float *randomBuffer, RANROTSeed seed) { unsigned i, len = kRandomBufferSize * kRandomBufferSize; for (i = 0; i < len; i++) { randomBuffer[i] = randfWithSeed(&seed); } } static void AddNoise(OOStandaloneAtmosphereGeneratorInfo *info, float *randomBuffer, float octave, unsigned octaveMask, float scale, float *qxBuffer, int *ixBuffer) { unsigned x, y; unsigned width = info->width, height = info->height; int ix, jx, iy, jy; float rr = octave / width; float fx, fy, qx, qy, rix, rjx, rfinal; float *dst = info->fbmBuffer; for (fy = 0, y = 0; y < height; fy++, y++) { qy = fy * rr; iy = fast_floor(qy); jy = (iy + 1) & octaveMask; qy = Hermite(qy - iy); iy &= (kRandomBufferSize - 1); jy &= (kRandomBufferSize - 1); for (fx = 0, x = 0; x < width; fx++, x++) { if (y == 0) { // first pass: initialise buffers. qx = fx * rr; ix = fast_floor(qx); qx -= ix; ix &= (kRandomBufferSize - 1); ixBuffer[x] = ix; qxBuffer[x] = Hermite(qx); } else { // later passes: grab the stored values. ix = ixBuffer[x]; qx = qxBuffer[x]; } jx = (ix + 1) & octaveMask; jx &= (kRandomBufferSize - 1); rix = Lerp(randomBuffer[iy * kRandomBufferSize + ix], randomBuffer[iy * kRandomBufferSize + jx], qx); rjx = Lerp(randomBuffer[jy * kRandomBufferSize + ix], randomBuffer[jy * kRandomBufferSize + jx], qx); rfinal = Lerp(rix, rjx, qy); *dst++ += scale * rfinal; } } } static BOOL GenerateFBMNoise(OOStandaloneAtmosphereGeneratorInfo *info) { // Allocate the temporary buffers we need in one fell swoop, to avoid administrative overhead. size_t randomBufferSize = kRandomBufferSize * kRandomBufferSize * sizeof (float); size_t qxBufferSize = info->width * sizeof (float); size_t ixBufferSize = info->width * sizeof (int); char *sharedBuffer = malloc(randomBufferSize + qxBufferSize + ixBufferSize); if (sharedBuffer == NULL) return NO; float *randomBuffer = (float *)sharedBuffer; float *qxBuffer = (float *)(sharedBuffer + randomBufferSize); int *ixBuffer = (int *)(sharedBuffer + randomBufferSize + qxBufferSize); // Get us some value noise. FillRandomBuffer(randomBuffer, info->seed); // Generate basic fBM noise. unsigned height = info->height; unsigned octaveMask = 8 * kPlanetAspectRatio; float octave = octaveMask; octaveMask -= 1; float scale = 0.5f; while ((octaveMask + 1) < height) { AddNoise(info, randomBuffer, octave, octaveMask, scale, qxBuffer, ixBuffer); octave *= 2.0f; octaveMask = (octaveMask << 1) | 1; scale *= 0.5f; } FREE(sharedBuffer); return YES; } #endif static float QFactor(float *accbuffer, int x, int y, unsigned width, float polar_y_value, float bias, float polar_y) { float q = accbuffer[y * width + x]; // 0.0 -> 1.0 q += bias; // Polar Y smooth. q = q * (1.0f - polar_y) + polar_y * polar_y_value; return q; } #endif // NEW_PLANETS oolite-1.82/src/Core/Materials/OOTexture.h000066400000000000000000000255461256642440500204320ustar00rootroot00000000000000/* OOTexture.h Load, track and manage textures. In general, this should be used through an OOMaterial. Note: OOTexture is abstract. The factory methods return instances of OOConcreteTexture, but special-case implementations are possible. Copyright (C) 2007-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import #import "OOOpenGL.h" #import "OOPixMap.h" #import "OOWeakReference.h" @class OOTextureLoader, OOTextureGenerator; enum { kOOTextureMinFilterDefault = 0x000000UL, kOOTextureMinFilterNearest = 0x000001UL, kOOTextureMinFilterLinear = 0x000002UL, kOOTextureMinFilterMipMap = 0x000003UL, kOOTextureMagFilterNearest = 0x000000UL, kOOTextureMagFilterLinear = 0x000004UL, kOOTextureNoShrink = 0x000010UL, kOOTextureExtraShrink = 0x000020UL, kOOTextureRepeatS = 0x000040UL, kOOTextureRepeatT = 0x000080UL, kOOTextureAllowRectTexture = 0x000100UL, // Indicates that GL_TEXTURE_RECTANGLE_EXT may be used instead of GL_TEXTURE_2D. See -texCoordsScale for a discussion of rectangle textures. kOOTextureNoFNFMessage = 0x000200UL, // Don't log file not found error kOOTextureNeverScale = 0x000400UL, // Don't rescale texture, even if rect textures are not available. This *must not* be used for regular textures, but may be passed to OOTextureLoader when being used for other purposes. kOOTextureAlphaMask = 0x000800UL, // Single-channel texture should be GL_ALPHA, not GL_LUMINANCE. No effect for multi-channel textures. kOOTextureAllowCubeMap = 0x001000UL, kOOTextureExtractChannelMask = 0x700000UL, kOOTextureExtractChannelNone = 0x000000UL, kOOTextureExtractChannelR = 0x100000UL, // 001 kOOTextureExtractChannelG = 0x300000UL, // 011 kOOTextureExtractChannelB = 0x500000UL, // 101 kOOTextureExtractChannelA = 0x700000UL, // 111 kOOTextureMinFilterMask = 0x000003UL, kOOTextureMagFilterMask = 0x000004UL, kOOTextureFlagsMask = ~(kOOTextureMinFilterMask | kOOTextureMagFilterMask), kOOTextureDefaultOptions = kOOTextureMinFilterDefault | kOOTextureMagFilterLinear, kOOTextureDefinedFlags = kOOTextureMinFilterMask | kOOTextureMagFilterMask | kOOTextureNoShrink | kOOTextureExtraShrink | kOOTextureAllowRectTexture | kOOTextureAllowCubeMap | kOOTextureRepeatS | kOOTextureRepeatT | kOOTextureNoFNFMessage | kOOTextureNeverScale | kOOTextureAlphaMask | kOOTextureExtractChannelMask, kOOTextureFlagsAllowedForRectangleTexture = kOOTextureDefinedFlags & ~(kOOTextureRepeatS | kOOTextureRepeatT), kOOTextureFlagsAllowedForCubeMap = kOOTextureDefinedFlags & ~(kOOTextureRepeatS | kOOTextureRepeatT) }; typedef uint32_t OOTextureFlags; #define kOOTextureDefaultAnisotropy 0.5 #define kOOTextureDefaultLODBias -0.25 enum { kOOTextureDataInvalid = kOOPixMapInvalidFormat, kOOTextureDataRGBA = kOOPixMapRGBA, // GL_RGBA kOOTextureDataGrayscale = kOOPixMapGrayscale, // GL_LUMINANCE (or GL_ALPHA with kOOTextureAlphaMask) kOOTextureDataGrayscaleAlpha = kOOPixMapGrayscaleAlpha // GL_LUMINANCE_ALPHA }; typedef OOPixMapFormat OOTextureDataFormat; @interface OOTexture: OOWeakRefObject { #ifndef NDEBUG @protected BOOL _trace; #endif } /* Load a texture, looking in Textures directories. NOTE: anisotropy is normalized to the range [0, 1]. 1 means as high an anisotropy setting as the hardware supports. This method may change; +textureWithConfiguration is generally more appropriate. */ + (id) textureWithName:(NSString *)name inFolder:(NSString *)directory options:(OOTextureFlags)options anisotropy:(GLfloat)anisotropy lodBias:(GLfloat)lodBias; /* Equivalent to textureWithName:name inFolder:directory options:kOOTextureDefaultOptions anisotropy:kOOTextureDefaultAnisotropy lodBias:kOOTextureDefaultLODBias */ + (id) textureWithName:(NSString *)name inFolder:(NSString*)directory; /* Load a texure, looking in Textures directories, using configuration dictionary or name. (That is, configuration may be either an NSDictionary or an NSString.) Supported keys: name (string, required) min_filter (string, one of "default", "nearest", "linear", "mipmap") max_filter (string, one of "default", "nearest", "linear") noShrink (boolean) repeat_s (boolean) repeat_t (boolean) cube_map (boolean) anisotropy (real) texture_LOD_bias (real) extract_channel (string, one of "r", "g", "b", "a") */ + (id) textureWithConfiguration:(id)configuration; + (id) textureWithConfiguration:(id)configuration extraOptions:(OOTextureFlags)extraOptions; /* Return the "null texture", a texture object representing an empty texture. Applying the null texture is equivalent to calling [OOTexture applyNone]. */ + (id) nullTexture; /* Load a texture from a generator. */ + (id) textureWithGenerator:(OOTextureGenerator *)generator; /* Bind the texture to the current texture unit. This will block until loading is completed. */ - (void) apply; + (void) applyNone; /* Ensure texture is loaded. This is required because setting up textures inside display lists isn't allowed. */ - (void) ensureFinishedLoading; /* Check whether a texture has loaded. NOTE: this does not do the setup that -ensureFinishedLoading does, so -ensureFinishedLoading is still required before using the texture in a display list. */ - (BOOL) isFinishedLoading; - (NSString *) cacheKey; /* Dimensions in pixels. This will block until loading is completed. */ - (NSSize) dimensions; /* Original file dimensions in pixels. This will block until loading is completed. */ - (NSSize) originalDimensions; /* Check whether texture is mip-mapped. This will block until loading is completed. */ - (BOOL) isMipMapped; /* Create a new pixmap with a copy of the texture data. The caller is responsible for free()ing the resulting buffer. */ - (OOPixMap) copyPixMapRepresentation; /* Identify special texture types. */ - (BOOL) isRectangleTexture; - (BOOL) isCubeMap; /* Dimensions in texture coordinates. If kOOTextureAllowRectTexture is set, and GL_EXT_texture_rectangle is available, textures whose dimensions are not powers of two will be loaded as rectangle textures. Rectangle textures use unnormalized co-ordinates; that is, co-oridinates range from 0 to the actual size of the texture rather than 0 to 1. Thus, for rectangle textures, -texCoordsScale returns -dimensions (with the required wait for loading) for a rectangle texture. For non-rectangle textures, (1, 1) is returned without delay. If the texture has power-of-two dimensions, it will be loaded as a normal texture. Rectangle textures have additional limitations: kOOTextureMinFilterMipMap is not supported (kOOTextureMinFilterLinear will be used instead), and kOOTextureRepeatS/kOOTextureRepeatT will be ignored. Note that 'rectangle texture' is a misnomer; non-rectangle textures may be rectangular, as long as their sides are powers of two. Non-power-of-two textures would be more descriptive, but this phrase is used for the extension that allows 'normal' textures to have non-power-of-two sides without additional restrictions. It is intended that OOTexture should support this in future, but this shouldn’t affect the interface, only avoid the scaling-to-power-of-two stage. */ - (NSSize) texCoordsScale; /* OpenGL texture name. Not reccomended, but required for legacy TextureStore. */ - (GLint) glTextureName; // Forget all cached textures so new texture objects will reload. + (void) clearCache; // Called by OOGraphicsResetManager as necessary. + (void) rebindAllTextures; #ifndef NDEBUG - (void) setTrace:(BOOL)trace; + (NSArray *) cachedTexturesByAge; + (NSSet *) allTextures; - (size_t) dataSize; - (NSString *) name; #endif @end @interface NSDictionary (OOTextureConveniences) - (NSDictionary *) oo_textureSpecifierForKey:(id)key defaultName:(NSString *)name; @end @interface NSArray (OOTextureConveniences) - (NSDictionary *) oo_textureSpecifierAtIndex:(unsigned)index defaultName:(NSString *)name; @end NSDictionary *OOTextureSpecFromObject(id object, NSString *defaultName); uint8_t OOTextureComponentsForFormat(OOTextureDataFormat format); BOOL OOCubeMapsAvailable(void); /* OOInterpretTextureSpecifier() Interpret a texture specifier (string or dictionary). All out parameters may be NULL. */ BOOL OOInterpretTextureSpecifier(id specifier, NSString **outName, OOTextureFlags *outOptions, float *outAnisotropy, float *outLODBias, BOOL ignoreExtract); /* OOMakeTextureSpecifier() Create a texture specifier. If internal is used, an optimized form unsuitable for serialization may be used. */ NSDictionary *OOMakeTextureSpecifier(NSString *name, OOTextureFlags options, float anisotropy, float lodBias, BOOL internal); /* OOApplyTextureOptionDefaults() Replace all default/automatic options with their current default values. */ OOTextureFlags OOApplyTextureOptionDefaults(OOTextureFlags options); // Texture specifier keys. extern NSString * const kOOTextureSpecifierNameKey; extern NSString * const kOOTextureSpecifierSwizzleKey; extern NSString * const kOOTextureSpecifierMinFilterKey; extern NSString * const kOOTextureSpecifierMagFilterKey; extern NSString * const kOOTextureSpecifierNoShrinkKey; extern NSString * const kOOTextureSpecifierExtraShrinkKey; extern NSString * const kOOTextureSpecifierRepeatSKey; extern NSString * const kOOTextureSpecifierRepeatTKey; extern NSString * const kOOTextureSpecifierCubeMapKey; extern NSString * const kOOTextureSpecifierAnisotropyKey; extern NSString * const kOOTextureSpecifierLODBiasKey; // Keys not used in texture setup, but put in specific texture specifiers to simplify plists. extern NSString * const kOOTextureSpecifierModulateColorKey; extern NSString * const kOOTextureSpecifierIlluminationModeKey; extern NSString * const kOOTextureSpecifierSelfColorKey; extern NSString * const kOOTextureSpecifierScaleFactorKey; extern NSString * const kOOTextureSpecifierBindingKey; oolite-1.82/src/Core/Materials/OOTexture.m000066400000000000000000000627401256642440500204340ustar00rootroot00000000000000/* OOTexture.m Copyright (C) 2007-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOTexture.h" #import "OOTextureInternal.h" #import "OOConcreteTexture.h" #import "OONullTexture.h" #import "OOTextureLoader.h" #import "OOTextureGenerator.h" #import "OOCollectionExtractors.h" #import "Universe.h" #import "ResourceManager.h" #import "OOOpenGLExtensionManager.h" #import "OOMacroOpenGL.h" #import "OOCPUInfo.h" #import "OOCache.h" #import "OOPixMap.h" NSString * const kOOTextureSpecifierNameKey = @"name"; NSString * const kOOTextureSpecifierSwizzleKey = @"extract_channel"; NSString * const kOOTextureSpecifierMinFilterKey = @"min_filter"; NSString * const kOOTextureSpecifierMagFilterKey = @"mag_filter"; NSString * const kOOTextureSpecifierNoShrinkKey = @"no_shrink"; NSString * const kOOTextureSpecifierExtraShrinkKey = @"extra_shrink"; NSString * const kOOTextureSpecifierRepeatSKey = @"repeat_s"; NSString * const kOOTextureSpecifierRepeatTKey = @"repeat_t"; NSString * const kOOTextureSpecifierCubeMapKey = @"cube_map"; NSString * const kOOTextureSpecifierAnisotropyKey = @"anisotropy"; NSString * const kOOTextureSpecifierLODBiasKey = @"texture_LOD_bias"; NSString * const kOOTextureSpecifierModulateColorKey = @"color"; NSString * const kOOTextureSpecifierIlluminationModeKey = @"illumination_mode"; NSString * const kOOTextureSpecifierSelfColorKey = @"self_color"; NSString * const kOOTextureSpecifierScaleFactorKey = @"scale_factor"; NSString * const kOOTextureSpecifierBindingKey = @"binding"; // Used only by "internal" specifiers from OOMakeTextureSpecifier. static NSString * const kOOTextureSpecifierFlagValueInternalKey = @"_oo_internal_flags"; /* Texture caching: two and a half parallel caching mechanisms are used. sLiveTextureCache tracks all live texture objects with cache keys, without retaining them (using NSValues to refer to the objects). sAllLiveTextures tracks all textures, including ones without cache keys, so that they can be notified of graphics resets. This also uses NSValues to avoid retaining the textures. sRecentTextures tracks up to kRecentTexturesCount textures which have been used recently, and retains them. This means that the number of live texture objects will never fall below 80% of kRecentTexturesCount (80% comes from the behaviour of OOCache), but old textures will eventually be released. If the number of active textures exceeds kRecentTexturesCount, all of them will be reusable through sLiveTextureCache, but only a most-recently-fetched subset will be kept around by the cache when the number drops. Note the textures in sRecentTextures are a superset of the textures in sLiveTextureCache, and the textures in sLiveTextureCache are a superset of sRecentTextures. */ enum { kRecentTexturesCount = 50 }; static NSMutableDictionary *sLiveTextureCache; static NSMutableSet *sAllLiveTextures; static OOCache *sRecentTextures; static BOOL sCheckedExtensions; OOTextureInfo gOOTextureInfo; @interface OOTexture (OOPrivate) - (void) addToCaches; + (OOTexture *) existingTextureForKey:(NSString *)key; - (void) forceRebind; + (void)checkExtensions; #ifndef NDEBUG - (id) retainInContext:(NSString *)context; - (void) releaseInContext:(NSString *)context; - (id) autoreleaseInContext:(NSString *)context; #endif @end #ifndef NDEBUG static NSString *sGlobalTraceContext = nil; #define SET_TRACE_CONTEXT(str) do { sGlobalTraceContext = (str); } while (0) #else #define SET_TRACE_CONTEXT(str) do { } while (0) #endif #define CLEAR_TRACE_CONTEXT() SET_TRACE_CONTEXT(nil) @implementation OOTexture + (id)textureWithName:(NSString *)name inFolder:(NSString*)directory options:(OOTextureFlags)options anisotropy:(GLfloat)anisotropy lodBias:(GLfloat)lodBias { NSString *key = nil; OOTexture *result = nil; NSString *path = nil; BOOL noFNF; if (EXPECT_NOT(name == nil)) return nil; if (EXPECT_NOT(!sCheckedExtensions)) [self checkExtensions]; if (!gOOTextureInfo.anisotropyAvailable || (options & kOOTextureMinFilterMask) != kOOTextureMinFilterMipMap) { anisotropy = 0.0f; } if (!gOOTextureInfo.textureLODBiasAvailable || (options & kOOTextureMinFilterMask) != kOOTextureMinFilterMipMap) { lodBias = 0.0f; } noFNF = (options & kOOTextureNoFNFMessage) != 0; options = OOApplyTextureOptionDefaults(options & ~kOOTextureNoFNFMessage); // Look for existing texture key = OOGenerateTextureCacheKey(directory, name, options, anisotropy, lodBias); result = [OOTexture existingTextureForKey:key]; if (result == nil) { path = [ResourceManager pathForFileNamed:name inFolder:directory]; if (path == nil) { if (!noFNF) OOLogWARN(kOOLogFileNotFound, @"Could not find texture file \"%@\".", name); return nil; } // No existing texture, load texture. result = [[[OOConcreteTexture alloc] initWithPath:path key:key options:options anisotropy:anisotropy lodBias:lodBias] autorelease]; } return result; } + (id)textureWithName:(NSString *)name inFolder:(NSString*)directory { return [self textureWithName:name inFolder:directory options:kOOTextureDefaultOptions anisotropy:kOOTextureDefaultAnisotropy lodBias:kOOTextureDefaultLODBias]; } + (id)textureWithConfiguration:(id)configuration { return [self textureWithConfiguration:configuration extraOptions:0]; } + (id) textureWithConfiguration:(id)configuration extraOptions:(OOTextureFlags)extraOptions { NSString *name = nil; OOTextureFlags options = 0; GLfloat anisotropy = 0.0f; GLfloat lodBias = 0.0f; if (!OOInterpretTextureSpecifier(configuration, &name, &options, &anisotropy, &lodBias, NO)) return nil; return [self textureWithName:name inFolder:@"Textures" options:options | extraOptions anisotropy:anisotropy lodBias:lodBias]; } + (id) nullTexture { return [OONullTexture sharedNullTexture]; } + (id) textureWithGenerator:(OOTextureGenerator *)generator { if (generator == nil) return nil; #ifndef OOTEXTURE_NO_CACHE OOTexture *existing = [OOTexture existingTextureForKey:[generator cacheKey]]; if (existing != nil) return [[existing retain] autorelease]; #endif if (![generator enqueue]) { OOLogERR(@"texture.generator.queue.failed", @"Failed to queue generator %@", generator); return nil; } OOLog(@"texture.generator.queue", @"Queued texture generator %@", generator); OOTexture *result = [[[OOConcreteTexture alloc] initWithLoader:generator key:[generator cacheKey] options:OOApplyTextureOptionDefaults([generator textureOptions]) anisotropy:[generator anisotropy] lodBias:[generator lodBias]] autorelease]; return result; } - (id) init { if ((self = [super init])) { if (EXPECT_NOT(sAllLiveTextures == nil)) sAllLiveTextures = [[NSMutableSet alloc] init]; [sAllLiveTextures addObject:[NSValue valueWithPointer:self]]; } return self; } - (void) dealloc { [sAllLiveTextures removeObject:[NSValue valueWithPointer:self]]; [super dealloc]; } - (void)apply { OOLogGenericSubclassResponsibility(); } + (void)applyNone { OO_ENTER_OPENGL(); OOGL(glBindTexture(GL_TEXTURE_2D, 0)); #if OO_TEXTURE_CUBE_MAP if (OOCubeMapsAvailable()) OOGL(glBindTexture(GL_TEXTURE_CUBE_MAP, 0)); #endif #if GL_EXT_texture_lod_bias if (gOOTextureInfo.textureLODBiasAvailable) OOGL(glTexEnvf(GL_TEXTURE_FILTER_CONTROL_EXT, GL_TEXTURE_LOD_BIAS_EXT, 0)); #endif } - (void)ensureFinishedLoading { } - (BOOL) isFinishedLoading { return YES; } - (NSString *) cacheKey { return nil; } - (NSSize) dimensions { OOLogGenericSubclassResponsibility(); return NSZeroSize; } - (NSSize) originalDimensions { return [self dimensions]; } - (BOOL) isMipMapped { OOLogGenericSubclassResponsibility(); return NO; } - (struct OOPixMap) copyPixMapRepresentation { return kOONullPixMap; } - (BOOL) isRectangleTexture { return NO; } - (BOOL) isCubeMap { return NO; } - (NSSize)texCoordsScale { return NSMakeSize(1.0f, 1.0f); } - (GLint)glTextureName { OOLogGenericSubclassResponsibility(); return 0; } + (void)clearCache { /* Does not clear sAllLiveTextures - that really must refer to all live texture objects. */ SET_TRACE_CONTEXT(@"clearing sLiveTextureCache"); [sLiveTextureCache autorelease]; sLiveTextureCache = nil; SET_TRACE_CONTEXT(@"clearing sRecentTextures"); [sRecentTextures autorelease]; sRecentTextures = nil; CLEAR_TRACE_CONTEXT(); } + (void)rebindAllTextures { NSEnumerator *textureEnum = nil; id texture = nil; // Keeping around unused, cached textures is unhelpful at this point. DESTROY(sRecentTextures); for (textureEnum = [sAllLiveTextures objectEnumerator]; (texture = [[textureEnum nextObject] pointerValue]); ) { [texture forceRebind]; } } #ifndef NDEBUG - (void) setTrace:(BOOL)trace { if (trace && !_trace) { OOLog(@"texture.allocTrace.begin", @"Started tracing texture %p with retain count %lu.", self, [self retainCount]); } _trace = trace; } + (NSArray *) cachedTexturesByAge { return [sRecentTextures objectsByAge]; } + (NSSet *) allTextures { NSMutableSet *result = [NSMutableSet setWithCapacity:[sAllLiveTextures count]]; NSValue *box = nil; NSEnumerator *texEnum = nil; for (texEnum = [sAllLiveTextures objectEnumerator]; (box = [texEnum nextObject]); ) { [result addObject:[box pointerValue]]; } return result; } - (size_t) dataSize { NSSize dimensions = [self dimensions]; size_t size = dimensions.width * dimensions.height; if ([self isCubeMap]) size *= 6; if ([self isMipMapped]) size = size * 4 / 3; return size; } - (NSString *) name { OOLogGenericSubclassResponsibility(); return nil; } #endif - (void) forceRebind { OOLogGenericSubclassResponsibility(); } - (void) addToCaches { #ifndef OOTEXTURE_NO_CACHE NSString *cacheKey = [self cacheKey]; if (cacheKey == nil) return; // Add self to in-use textures cache, wrapped in an NSValue so the texture isn't retained by the cache. if (EXPECT_NOT(sLiveTextureCache == nil)) sLiveTextureCache = [[NSMutableDictionary alloc] init]; SET_TRACE_CONTEXT(@"in-use textures cache - SHOULD NOT RETAIN"); [sLiveTextureCache setObject:[NSValue valueWithPointer:self] forKey:cacheKey]; CLEAR_TRACE_CONTEXT(); // Add self to recent textures cache. if (EXPECT_NOT(sRecentTextures == nil)) { sRecentTextures = [[OOCache alloc] init]; [sRecentTextures setName:@"recent textures"]; [sRecentTextures setAutoPrune:YES]; [sRecentTextures setPruneThreshold:kRecentTexturesCount]; } SET_TRACE_CONTEXT(@"adding to recent textures cache"); [sRecentTextures setObject:self forKey:cacheKey]; CLEAR_TRACE_CONTEXT(); #endif } - (void) removeFromCaches { #ifndef OOTEXTURE_NO_CACHE NSString *cacheKey = [self cacheKey]; if (cacheKey == nil) return; [sLiveTextureCache removeObjectForKey:cacheKey]; if (EXPECT_NOT([sRecentTextures objectForKey:cacheKey] == self)) { /* Experimental for now: I think the recent crash problems may * be because if the last reference to a texture is in * sRecentTextures, and the texture is regenerated, it * replaces the texture, causing a release. Therefore, if this * texture *isn't* overretained in the texture cache, the 2009 * crash avoider will delete its replacement from the cache * ... possibly before that texture has been fully added to * the cache itself. So, the texture is only removed from the * cache by key if it was in it with that key. The extra time * needed to generate a planet texture compared with loading a * standard one may be why this problem shows up. - CIM 20140122 */ NSAssert2(0, @"Texture retain count error for %@; cacheKey is %@.", self, cacheKey); //miscount in autorelease // The following line is needed in order to avoid crashes when there's a 'texture retain count error'. Please do not delete. -- Kaks 20091221 [sRecentTextures removeObjectForKey:cacheKey]; // make sure there's no reference left inside sRecentTexture ( was a show stopper for 1.73) } #endif } + (OOTexture *) existingTextureForKey:(NSString *)key { #ifndef OOTEXTURE_NO_CACHE if (key != nil) { return (OOTexture *)[[sLiveTextureCache objectForKey:key] pointerValue]; } return nil; #else return nil; #endif } + (void)checkExtensions { OO_ENTER_OPENGL(); sCheckedExtensions = YES; OOOpenGLExtensionManager *extMgr = [OOOpenGLExtensionManager sharedManager]; BOOL ver120 = [extMgr versionIsAtLeastMajor:1 minor:2]; BOOL ver130 = [extMgr versionIsAtLeastMajor:1 minor:3]; #if GL_EXT_texture_filter_anisotropic gOOTextureInfo.anisotropyAvailable = [extMgr haveExtension:@"GL_EXT_texture_filter_anisotropic"]; OOGL(glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gOOTextureInfo.anisotropyScale)); gOOTextureInfo.anisotropyScale *= OOClamp_0_1_f([[NSUserDefaults standardUserDefaults] oo_floatForKey:@"texture-anisotropy-scale" defaultValue:0.5]); #endif #ifdef GL_CLAMP_TO_EDGE gOOTextureInfo.clampToEdgeAvailable = ver120 || [extMgr haveExtension:@"GL_SGIS_texture_edge_clamp"]; #endif #if OO_GL_CLIENT_STORAGE gOOTextureInfo.clientStorageAvailable = [extMgr haveExtension:@"GL_APPLE_client_storage"]; #endif gOOTextureInfo.textureMaxLevelAvailable = ver120 || [extMgr haveExtension:@"GL_SGIS_texture_lod"]; #if GL_EXT_texture_lod_bias if ([[NSUserDefaults standardUserDefaults] oo_boolForKey:@"use-texture-lod-bias" defaultValue:YES]) { gOOTextureInfo.textureLODBiasAvailable = [extMgr haveExtension:@"GL_EXT_texture_lod_bias"]; } else { gOOTextureInfo.textureLODBiasAvailable = NO; } #endif #if GL_EXT_texture_rectangle gOOTextureInfo.rectangleTextureAvailable = [extMgr haveExtension:@"GL_EXT_texture_rectangle"]; #endif #if OO_TEXTURE_CUBE_MAP if (![[NSUserDefaults standardUserDefaults] boolForKey:@"disable-cube-maps"]) { gOOTextureInfo.cubeMapAvailable = ver130 || [extMgr haveExtension:@"GL_ARB_texture_cube_map"]; } else { gOOTextureInfo.cubeMapAvailable = NO; } #endif } #ifndef NDEBUG - (id) retainInContext:(NSString *)context { if (_trace) { if (context) OOLog(@"texture.allocTrace.retain", @"Texture %p retained (retain count -> %lu) - %@.", self, [self retainCount] + 1, context); else OOLog(@"texture.allocTrace.retain", @"Texture %p retained (retain count -> %lu).", self, [self retainCount] + 1); } return [super retain]; } - (void) releaseInContext:(NSString *)context { if (_trace) { if (context) OOLog(@"texture.allocTrace.release", @"Texture %p released (retain count -> %lu) - %@.", self, [self retainCount] - 1, context); else OOLog(@"texture.allocTrace.release", @"Texture %p released (retain count -> %lu).", self, [self retainCount] - 1); } [super release]; } - (id) autoreleaseInContext:(NSString *)context { if (_trace) { if (context) OOLog(@"texture.allocTrace.autoreleased", @"Texture %p autoreleased - %@.", self, context); else OOLog(@"texture.allocTrace.autoreleased", @"Texture %p autoreleased.", self); } return [super autorelease]; } - (id) retain { return [self retainInContext:sGlobalTraceContext]; } - (oneway void) release { [self releaseInContext:sGlobalTraceContext]; } - (id) autorelease { return [self autoreleaseInContext:sGlobalTraceContext]; } #endif @end @implementation NSDictionary (OOTextureConveniences) - (NSDictionary *) oo_textureSpecifierForKey:(id)key defaultName:(NSString *)name { return OOTextureSpecFromObject([self objectForKey:key], name); } @end @implementation NSArray (OOTextureConveniences) - (NSDictionary *) oo_textureSpecifierAtIndex:(unsigned)index defaultName:(NSString *)name { return OOTextureSpecFromObject([self objectAtIndex:index], name); } @end NSDictionary *OOTextureSpecFromObject(id object, NSString *defaultName) { if (object == nil) object = defaultName; if ([object isKindOfClass:[NSString class]]) { if ([object isEqualToString:@""]) return nil; return [NSDictionary dictionaryWithObject:object forKey:@"name"]; } if (![object isKindOfClass:[NSDictionary class]]) return nil; // If we're here, it's a dictionary. if (defaultName == nil || [object oo_stringForKey:@"name"] != nil) return object; // If we get here, there's no "name" key and there is a default, so we fill it in: NSMutableDictionary *mutableResult = [NSMutableDictionary dictionaryWithDictionary:object]; [mutableResult setObject:[[defaultName copy] autorelease] forKey:@"name"]; return mutableResult; } uint8_t OOTextureComponentsForFormat(OOTextureDataFormat format) { switch (format) { case kOOTextureDataRGBA: return 4; case kOOTextureDataGrayscale: return 1; case kOOTextureDataGrayscaleAlpha: return 2; case kOOTextureDataInvalid: break; } return 0; } BOOL OOCubeMapsAvailable(void) { return gOOTextureInfo.cubeMapAvailable; } BOOL OOInterpretTextureSpecifier(id specifier, NSString **outName, OOTextureFlags *outOptions, float *outAnisotropy, float *outLODBias, BOOL ignoreExtract) { NSString *name = nil; OOTextureFlags options = kOOTextureDefaultOptions; float anisotropy = kOOTextureDefaultAnisotropy; float lodBias = kOOTextureDefaultLODBias; if ([specifier isKindOfClass:[NSString class]]) { name = specifier; } else if ([specifier isKindOfClass:[NSDictionary class]]) { name = [specifier oo_stringForKey:kOOTextureSpecifierNameKey]; if (name == nil) { OOLog(@"texture.load.noName", @"Invalid texture configuration dictionary (must specify name):\n%@", specifier); return NO; } int quickFlags = [specifier oo_intForKey:kOOTextureSpecifierFlagValueInternalKey defaultValue:-1]; if (quickFlags != -1) { options = quickFlags; } else { NSString *filterString = [specifier oo_stringForKey:kOOTextureSpecifierMinFilterKey defaultValue:@"default"]; if ([filterString isEqualToString:@"nearest"]) options |= kOOTextureMinFilterNearest; else if ([filterString isEqualToString:@"linear"]) options |= kOOTextureMinFilterLinear; else if ([filterString isEqualToString:@"mipmap"]) options |= kOOTextureMinFilterMipMap; else options |= kOOTextureMinFilterDefault; // Covers "default" filterString = [specifier oo_stringForKey:kOOTextureSpecifierMagFilterKey defaultValue:@"default"]; if ([filterString isEqualToString:@"nearest"]) options |= kOOTextureMagFilterNearest; else options |= kOOTextureMagFilterLinear; // Covers "default" and "linear" if ([specifier oo_boolForKey:kOOTextureSpecifierNoShrinkKey defaultValue:NO]) options |= kOOTextureNoShrink; if ([specifier oo_boolForKey:kOOTextureSpecifierExtraShrinkKey defaultValue:NO]) options |= kOOTextureExtraShrink; if ([specifier oo_boolForKey:kOOTextureSpecifierRepeatSKey defaultValue:NO]) options |= kOOTextureRepeatS; if ([specifier oo_boolForKey:kOOTextureSpecifierRepeatTKey defaultValue:NO]) options |= kOOTextureRepeatT; if ([specifier oo_boolForKey:kOOTextureSpecifierCubeMapKey defaultValue:NO]) options |= kOOTextureAllowCubeMap; if (!ignoreExtract) { NSString *extractChannel = [specifier oo_stringForKey:@"extract_channel"]; if (extractChannel != nil) { if ([extractChannel isEqualToString:@"r"]) options |= kOOTextureExtractChannelR; else if ([extractChannel isEqualToString:@"g"]) options |= kOOTextureExtractChannelG; else if ([extractChannel isEqualToString:@"b"]) options |= kOOTextureExtractChannelB; else if ([extractChannel isEqualToString:@"a"]) options |= kOOTextureExtractChannelA; else { OOLogWARN(@"texture.load.extractChannel.invalid", @"Unknown value \"%@\" for extract_channel in specifier \"%@\" (should be \"r\", \"g\", \"b\" or \"a\").", extractChannel,specifier); } } } } anisotropy = [specifier oo_floatForKey:@"anisotropy" defaultValue:kOOTextureDefaultAnisotropy]; lodBias = [specifier oo_floatForKey:@"texture_LOD_bias" defaultValue:kOOTextureDefaultLODBias]; } else { // Bad type if (specifier != nil) OOLog(kOOLogParameterError, @"%s: expected string or dictionary, got %@.", __PRETTY_FUNCTION__, [specifier class]); return NO; } if ([name length] == 0) return NO; if (outName != NULL) *outName = name; if (outOptions != NULL) *outOptions = options; if (outAnisotropy != NULL) *outAnisotropy = anisotropy; if (outLODBias != NULL) *outLODBias = lodBias; return YES; } NSDictionary *OOMakeTextureSpecifier(NSString *name, OOTextureFlags options, float anisotropy, float lodBias, BOOL internal) { NSMutableDictionary *result = [NSMutableDictionary dictionary]; [result setObject:name forKey:kOOTextureSpecifierNameKey]; if (anisotropy != kOOTextureDefaultAnisotropy) [result oo_setFloat:anisotropy forKey:kOOTextureSpecifierAnisotropyKey]; if (lodBias != kOOTextureDefaultLODBias) [result oo_setFloat:lodBias forKey:kOOTextureSpecifierLODBiasKey]; if (internal) { [result oo_setUnsignedInteger:options forKey:kOOTextureSpecifierFlagValueInternalKey]; } else { NSString *value = nil; switch (options & kOOTextureMinFilterMask) { case kOOTextureMinFilterDefault: break; case kOOTextureMinFilterNearest: value = @"nearest"; break; case kOOTextureMinFilterLinear: value = @"linear"; break; case kOOTextureMinFilterMipMap: value = @"mipmap"; break; } if (value != nil) [result setObject:value forKey:kOOTextureSpecifierNoShrinkKey]; value = nil; switch (options & kOOTextureMagFilterMask) { case kOOTextureMagFilterNearest: value = @"nearest"; break; case kOOTextureMagFilterLinear: break; } if (value != nil) [result setObject:value forKey:kOOTextureSpecifierMagFilterKey]; value = nil; switch (options & kOOTextureExtractChannelMask) { case kOOTextureExtractChannelNone: break; case kOOTextureExtractChannelR: value = @"r"; break; case kOOTextureExtractChannelG: value = @"g"; break; case kOOTextureExtractChannelB: value = @"b"; break; case kOOTextureExtractChannelA: value = @"a"; break; } if (value != nil) [result setObject:value forKey:kOOTextureSpecifierSwizzleKey]; if (options & kOOTextureNoShrink) [result oo_setBool:YES forKey:kOOTextureSpecifierNoShrinkKey]; if (options & kOOTextureRepeatS) [result oo_setBool:YES forKey:kOOTextureSpecifierRepeatSKey]; if (options & kOOTextureRepeatT) [result oo_setBool:YES forKey:kOOTextureSpecifierRepeatTKey]; if (options & kOOTextureAllowCubeMap) [result oo_setBool:YES forKey:kOOTextureSpecifierCubeMapKey]; } return result; } OOTextureFlags OOApplyTextureOptionDefaults(OOTextureFlags options) { // Set default flags if needed if ((options & kOOTextureMinFilterMask) == kOOTextureMinFilterDefault) { if ([UNIVERSE reducedDetail]) { options |= kOOTextureMinFilterLinear; } else { options |= kOOTextureMinFilterMipMap; } } if (!gOOTextureInfo.textureMaxLevelAvailable) { /* In the unlikely case of an OpenGL system without GL_SGIS_texture_lod, disable mip-mapping completely. Strictly this is only needed for non-square textures, but extra logic for such a rare case isn't worth it. */ if ((options & kOOTextureMinFilterMask) == kOOTextureMinFilterMipMap) { options ^= kOOTextureMinFilterMipMap ^ kOOTextureMinFilterLinear; } } if (options & kOOTextureAllowRectTexture) { // Apply rectangle texture restrictions (regardless of whether rectangle textures are available, for consistency) options &= kOOTextureFlagsAllowedForRectangleTexture; if ((options & kOOTextureMinFilterMask) == kOOTextureMinFilterMipMap) { options = (kOOTextureMinFilterMask & ~kOOTextureMinFilterMask) | kOOTextureMinFilterLinear; } #if GL_EXT_texture_rectangle if (!gOOTextureInfo.rectangleTextureAvailable) { options &= ~kOOTextureAllowRectTexture; } #else options &= ~kOOTextureAllowRectTexture; #endif } options &= kOOTextureDefinedFlags; return options; } NSString *OOGenerateTextureCacheKey(NSString *directory, NSString *name, OOTextureFlags options, float anisotropy, float lodBias) { if (!gOOTextureInfo.anisotropyAvailable || (options & kOOTextureMinFilterMask) != kOOTextureMinFilterMipMap) { anisotropy = 0.0f; } if (!gOOTextureInfo.textureLODBiasAvailable || (options & kOOTextureMinFilterMask) != kOOTextureMinFilterMipMap) { lodBias = 0.0f; } options = OOApplyTextureOptionDefaults(options & ~kOOTextureNoFNFMessage); return [NSString stringWithFormat:@"%@%@%@:0x%.4X/%g/%g", directory ? directory : (NSString *)@"", directory ? @"/" : @"", name, options, anisotropy, lodBias]; } NSString *OOTextureCacheKeyForSpecifier(id specifier) { NSString *name; OOTextureFlags options; float anisotropy; float lodBias; OOInterpretTextureSpecifier(specifier, &name, &options, &anisotropy, &lodBias, NO); return OOGenerateTextureCacheKey(@"Textures", name, options, anisotropy, lodBias); } oolite-1.82/src/Core/Materials/OOTextureGenerator.h000066400000000000000000000033031256642440500222640ustar00rootroot00000000000000/* OOTextureGenerator.h A texture "loader" which doesn't require an input file. Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOTextureLoader.h" typedef struct { float r, g, b; } FloatRGB; typedef struct { float r, g, b, a; } FloatRGBA; @interface OOTextureGenerator: OOTextureLoader // Generators, unlike normal loaders, get to specify their own flags and other settings. - (uint32_t) textureOptions; // Default: kOOTextureDefaultOptions - (GLfloat) anisotropy; // Default: kOOTextureDefaultAnisotropy - (GLfloat) lodBias; // Default: kOOTextureDefaultLODBias // Key for in-memory cache; nil for no cache. - (NSString *) cacheKey; // For use by OOTexture. - (BOOL) enqueue; @end oolite-1.82/src/Core/Materials/OOTextureGenerator.m000066400000000000000000000030231256642440500222700ustar00rootroot00000000000000/* OOTextureGenerator.m Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOTextureGenerator.h" #import "OOAsyncWorkManager.h" @implementation OOTextureGenerator - (uint32_t) textureOptions { return kOOTextureDefaultOptions; } - (GLfloat) anisotropy { return kOOTextureDefaultAnisotropy; } - (GLfloat) lodBias { return kOOTextureDefaultLODBias; } - (NSString *) cacheKey { return nil; } - (BOOL) enqueue { return [[OOAsyncWorkManager sharedAsyncWorkManager] addTask:self priority:kOOAsyncPriorityMedium]; } @end oolite-1.82/src/Core/Materials/OOTextureInternal.h000066400000000000000000000072441256642440500221220ustar00rootroot00000000000000/* OOTextureInternal.h Subclass interface for OOTexture. Copyright (C) 2007-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOTexture.h" #import "OOOpenGLExtensionManager.h" @interface OOTexture (SubclassInterface) - (void) addToCaches; - (void) removeFromCaches; // Must be called on -dealloc (while -cacheKey is still valid) for cacheable textures. + (OOTexture *) existingTextureForKey:(NSString *)key; @end @interface OOTexture (SubclassResponsibilities) - (void)apply; - (NSSize)dimensions; - (void) forceRebind; @end @interface OOTexture (SubclassOptional) - (void)ensureFinishedLoading; // Default: does nothing - (BOOL) isFinishedLoading; // Default: YES - (NSString *) cacheKey; // Default: nil - (BOOL) isRectangleTexture; // Default: NO - (BOOL) isCubeMap; // Default: NO - (NSSize)texCoordsScale; // Default: 1,1 - (struct OOPixMap) copyPixMapRepresentation; // Default: kOONullPixMap @end /* OOGenerateTextureCacheKey() OOTextureCacheKeyForSpecifier() Generate texture cache keys of the form used by normal file-based textures. */ NSString *OOGenerateTextureCacheKey(NSString *directory, NSString *name, OOTextureFlags options, float anisotropy, float lodBias); NSString *OOTextureCacheKeyForSpecifier(id specifier); typedef struct OOTextureInfo { GLfloat anisotropyScale; unsigned anisotropyAvailable: 1, clampToEdgeAvailable: 1, clientStorageAvailable: 1, textureLODBiasAvailable: 1, rectangleTextureAvailable: 1, cubeMapAvailable: 1, textureMaxLevelAvailable: 1; } OOTextureInfo; extern OOTextureInfo gOOTextureInfo; #ifndef GL_EXT_texture_filter_anisotropic #warning GL_EXT_texture_filter_anisotropic unavailable -- are you using an up-to-date glext.h? #endif #ifndef GL_CLAMP_TO_EDGE #ifdef GL_SGIS_texture_edge_clamp #define GL_CLAMP_TO_EDGE GL_CLAMP_TO_EDGE_SGIS #else #warning GL_CLAMP_TO_EDGE (OpenGL 1.2) and GL_SGIS_texture_edge_clamp are unavailable -- are you using an up-to-date gl.h? #define GL_CLAMP_TO_EDGE GL_CLAMP #endif #endif #if defined(GL_APPLE_client_storage) && !OOTEXTURE_RELOADABLE #define OO_GL_CLIENT_STORAGE (1) #define EnableClientStorage() OOGL(glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE)) #else #define OO_GL_CLIENT_STORAGE (0) #define EnableClientStorage() do {} while (0) #endif #ifndef GL_TEXTURE_MAX_LEVEL #ifdef GL_TEXTURE_MAX_LEVEL_SGIS #define GL_TEXTURE_MAX_LEVEL GL_TEXTURE_MAX_LEVEL_SGIS #else #define GL_TEXTURE_MAX_LEVEL 0x813D #endif #endif #if OO_TEXTURE_CUBE_MAP #ifndef GL_TEXTURE_CUBE_MAP #define GL_TEXTURE_CUBE_MAP GL_TEXTURE_CUBE_MAP_ARB #define GL_TEXTURE_CUBE_MAP_POSITIVE_X GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB #endif #endif oolite-1.82/src/Core/Materials/OOTextureLoader.h000066400000000000000000000073361256642440500215560ustar00rootroot00000000000000/* OOTextureLoader.h Abstract base class for asynchronous texture loaders, which are dispatched by OOTextureLoadDispatcher. In general, this should be used through OOTexture. Note: interface is likely to change in future to support other buffer types (like S3TC/DXT#). Copyright (C) 2007-2014 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOTexture.h" #import "OOAsyncWorkManager.h" @interface OOTextureLoader: NSObject { @protected NSString *_path; OOTextureFlags _options; uint8_t _generateMipMaps: 1, _scaleAsNormalMap: 1, _avoidShrinking: 1, _noScalingWhatsoever: 1, _extractChannel: 1, _allowCubeMap: 1, _isCubeMap: 1, _ready: 1; uint8_t _extractChannelIndex; OOTextureDataFormat _format; void *_data; uint32_t _width, _height, _originalWidth, _originalHeight, _shrinkThreshold, _maxSize; size_t _rowBytes; } + (id)loaderWithPath:(NSString *)path options:(uint32_t)options; /* Convenience method to load images not destined for normal texture use. Specifier is a string or a dictionary as with textures. ExtraOptions is ored into the option flags interpreted from the specifier. Folder is the directory to look in, typically Textures or Images. Options in the specifier which are applied at the OOTexture level will be ignored. */ + (id)loaderWithTextureSpecifier:(id)specifier extraOptions:(uint32_t)extraOptions folder:(NSString *)folder; - (BOOL)isReady; /* Return value indicates success. This may only be called once (subsequent attempts will return failure), and only on the main thread. */ - (BOOL) getResult:(OOPixMap *)result format:(OOTextureDataFormat *)outFormat originalWidth:(uint32_t *)outWidth originalHeight:(uint32_t *)outHeight; /* Hopefully-unique string for texture loader; analagous, but not identical, to corresponding texture cacheKey. */ - (NSString *) cacheKey; /*** Subclass interface; do not use on pain of pain. Unless you're subclassing. ***/ // Subclasses shouldn't do much on init, because of the whole asynchronous thing. - (id)initWithPath:(NSString *)path options:(uint32_t)options; - (NSString *)path; /* Load data, setting up _data, _format, _width, and _height; also _rowBytes if it's not _width * OOTextureComponentsForFormat(_format), and _originalWidth/_originalHeight if _width and _height for some reason aren't the original pixel dimensions. Thread-safety concerns: this will be called in a worker thread, and there may be several worker threads. The caller takes responsibility for autorelease pools and exception safety. Superclass will handle scaling and mip-map generation. Data must be allocated with malloc() family. */ - (void)loadTexture; @end oolite-1.82/src/Core/Materials/OOTextureLoader.m000066400000000000000000000334071256642440500215610ustar00rootroot00000000000000/* OOTextureLoader.m Copyright (C) 2007-2014 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOPNGTextureLoader.h" #import "OOTextureLoader.h" #import "OOFunctionAttributes.h" #import "OOCollectionExtractors.h" #import "OOMaths.h" #import "Universe.h" #import "OOTextureScaling.h" #import "OOPixMapChannelOperations.h" #import "OOConvertCubeMapToLatLong.h" #include #import "ResourceManager.h" #import "OOOpenGLExtensionManager.h" #import "OODebugStandards.h" #define DUMP_CONVERTED_CUBE_MAPS 0 enum { // Thresholds for reduced-detail texture shrinking in different circumstances. kNeverShrinkThreshold = UINT32_MAX, kDefaultShrinkThreshold = 512, kExtraShrinkThreshold = 128, kExtraShrinkMaxSize = 256, kCubeShrinkThreshold = 256 }; static unsigned sGLMaxSize; static uint32_t sUserMaxSize; static BOOL sReducedDetail; static BOOL sHaveNPOTTextures = NO; // TODO: support "true" non-power-of-two textures. static BOOL sHaveSetUp = NO; @interface OOTextureLoader (OOPrivate) + (void)setUp; - (void)applySettings; - (void)getDesiredWidth:(OOPixMapDimension *)outDesiredWidth andHeight:(OOPixMapDimension *)outDesiredHeight; @end @implementation OOTextureLoader + (id)loaderWithPath:(NSString *)inPath options:(uint32_t)options { NSString *extension = nil; id result = nil; if (EXPECT_NOT(inPath == nil)) return nil; if (EXPECT_NOT(!sHaveSetUp)) [self setUp]; // Get reduced detail setting (every time, in case it changes; we don't want to call through to Universe on the loading thread in case the implementation becomes non-trivial). sReducedDetail = [UNIVERSE reducedDetail]; // Get a suitable loader. FIXME -- this should sniff the data instead of relying on extensions. extension = [[inPath pathExtension] lowercaseString]; if ([extension isEqualToString:@"png"]) { result = [[[OOPNGTextureLoader alloc] initWithPath:inPath options:options] autorelease]; } else { OOLog(@"texture.load.unknownType", @"Can't use %@ as a texture - extension \"%@\" does not identify a known type.", inPath, extension); } if (result != nil) { if (![[OOAsyncWorkManager sharedAsyncWorkManager] addTask:result priority:kOOAsyncPriorityMedium]) result = nil; } return result; } + (id)loaderWithTextureSpecifier:(id)specifier extraOptions:(uint32_t)extraOptions folder:(NSString *)folder { NSString *name = nil; NSString *path = nil; uint32_t options = 0; if (!OOInterpretTextureSpecifier(specifier, &name, &options, NULL, NULL, NO)) return nil; options |= extraOptions; path = [ResourceManager pathForFileNamed:name inFolder:folder]; if (path == nil) { if (!(options & kOOTextureNoFNFMessage)) { OOLogWARN(kOOLogFileNotFound, @"Could not find texture file \"%@\".", name); OOStandardsError(@"Texture file not found"); } return nil; } return [self loaderWithPath:path options:options]; } - (id)initWithPath:(NSString *)inPath options:(uint32_t)options { self = [super init]; if (self == nil) return nil; _path = [inPath copy]; if (EXPECT_NOT(_path == nil)) { [self release]; return nil; } _options = options; _maxSize = MIN(sUserMaxSize, sGLMaxSize); _generateMipMaps = (options & kOOTextureMinFilterMask) == kOOTextureMinFilterMipMap; _avoidShrinking = (options & kOOTextureNoShrink) != 0; _noScalingWhatsoever = (options & kOOTextureNeverScale) != 0; if (_avoidShrinking || _noScalingWhatsoever) { _shrinkThreshold = kNeverShrinkThreshold; } else if (options & kOOTextureExtraShrink) { _shrinkThreshold = kExtraShrinkThreshold; _maxSize = MIN(_maxSize, (uint32_t)kExtraShrinkMaxSize); } else { _shrinkThreshold = kDefaultShrinkThreshold; } #if OO_TEXTURE_CUBE_MAP _allowCubeMap = (options & kOOTextureAllowCubeMap) != 0; #endif if (options & kOOTextureExtractChannelMask) { _extractChannel = YES; switch (options & kOOTextureExtractChannelMask) { case kOOTextureExtractChannelR: _extractChannelIndex = 0; break; case kOOTextureExtractChannelG: _extractChannelIndex = 1; break; case kOOTextureExtractChannelB: _extractChannelIndex = 2; break; case kOOTextureExtractChannelA: _extractChannelIndex = 3; break; default: OOLogERR(@"texture.load.unknownExtractChannelMask", @"Unknown texture extract channel mask (0x%.4X). This is an internal error, please report it.", options & kOOTextureExtractChannelMask); _extractChannel = NO; } } return self; } - (void)dealloc { [_path autorelease]; _path = nil; free(_data); _data = NULL; [super dealloc]; } - (NSString *)descriptionComponents { NSString *state = nil; if (_ready) { if (_data != NULL) state = @"ready"; else state = @"failed"; } else { state = @"loading"; #if INSTRUMENT_TEXTURE_LOADING if (debugHasLoaded) state = @"loaded"; #endif } return [NSString stringWithFormat:@"{%@ -- %@}", _path, state]; } - (NSString *)shortDescriptionComponents { return [_path lastPathComponent]; } - (NSString *)path { return _path; } - (BOOL)isReady { return _ready; } - (BOOL) getResult:(OOPixMap *)result format:(OOTextureDataFormat *)outFormat originalWidth:(uint32_t *)outWidth originalHeight:(uint32_t *)outHeight { NSParameterAssert(result != NULL && outFormat != NULL); BOOL OK = YES; if (!_ready) { [[OOAsyncWorkManager sharedAsyncWorkManager] waitForTaskToComplete:self]; } if (_data == NULL) OK = NO; if (OK) { *result = OOMakePixMap(_data, _width, _height, OOTextureComponentsForFormat(_format), 0, 0); _data = NULL; *outFormat = _format; OK = OOIsValidPixMap(*result); if (outWidth != NULL) *outWidth = _originalWidth; if (outHeight != NULL) *outHeight = _originalHeight; } if (!OK) { *result = kOONullPixMap; *outFormat = kOOTextureDataInvalid; } return OK; } - (NSString *) cacheKey { return [NSString stringWithFormat:@"%@:0x%.4X", [[self path] lastPathComponent], _options]; } - (void)loadTexture { OOLogGenericSubclassResponsibility(); } + (void)setUp { // Load two maximum sizes - graphics hardware limit and user-specified limit. GLint maxSize; OOGL(glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize)); sGLMaxSize = MAX(maxSize, 64); OOLog(@"texture.load.rescale.maxSize", @"GL maximum texture size: %u", sGLMaxSize); // Why 0x80000000? Because it's the biggest number OORoundUpToPowerOf2() can handle. sUserMaxSize = [[NSUserDefaults standardUserDefaults] oo_unsignedIntForKey:@"max-texture-size" defaultValue:0x80000000]; if (sUserMaxSize < 0x80000000) OOLog(@"texture.load.rescale.maxSize", @"User maximum texture size: %u", sUserMaxSize); sUserMaxSize = OORoundUpToPowerOf2_32(sUserMaxSize); sUserMaxSize = MAX(sUserMaxSize, 64U); sHaveSetUp = YES; } /*** Methods performed on the loader thread. ***/ - (void)performAsyncTask { @try { OOLog(@"texture.load.asyncLoad", @"Loading texture %@", [_path lastPathComponent]); [self loadTexture]; // Catch an error I've seen but not diagnosed yet. if (_data != NULL && OOTextureComponentsForFormat(_format) == 0) { OOLog(@"texture.load.failed.internalError", @"Texture loader internal error for %@: data is non-null but data format is invalid (%u).", _path, _format); free(_data); _data = NULL; } if (_data != NULL) [self applySettings]; OOLog(@"texture.load.asyncLoad.done", @"Loading complete."); } @catch (NSException *exception) { OOLog(@"texture.load.asyncLoad.exception", @"***** Exception loading texture %@: %@ (%@).", _path, [exception name], [exception reason]); // Be sure to signal load failure. free(_data); _data = NULL; } } - (void) generateMipMapsForCubeMap { // Generate mip maps for each cube face. NSParameterAssert(_data != NULL); uint8_t components = OOTextureComponentsForFormat(_format); size_t srcSideSize = _width * _width * components; // Space for one side without mip-maps. size_t newSideSize = srcSideSize * 4 / 3; // Space for one side with mip-maps. newSideSize = (newSideSize + 15) & ~15; // Round up to multiple of 16 bytes. size_t newSize = newSideSize * 6; // Space for all six sides. void *newData = malloc(newSize); if (EXPECT_NOT(newData == NULL)) { _generateMipMaps = NO; _options = (_options & ~kOOTextureMinFilterMask) | kOOTextureMinFilterLinear; return; } unsigned i; for (i = 0; i < 6; i++) { void *srcBytes = ((uint8_t *)_data) + srcSideSize * i; void *dstBytes = ((uint8_t *)newData) + newSideSize * i; memcpy(dstBytes, srcBytes, srcSideSize); OOGenerateMipMaps(dstBytes, _width, _width, _format); } free(_data); _data = newData; } - (void)applySettings { OOPixMapDimension desiredWidth, desiredHeight; BOOL rescale; size_t newSize; uint8_t components; OOPixMap pixMap; components = OOTextureComponentsForFormat(_format); // Apply defaults. if (_originalWidth == 0) _originalWidth = _width; if (_originalHeight == 0) _originalHeight = _height; if (_rowBytes == 0) _rowBytes = _width * components; pixMap = OOMakePixMap(_data, _width, _height, components, _rowBytes, 0); if (_extractChannel) { if (OOExtractPixMapChannel(&pixMap, _extractChannelIndex, NO)) { _format = kOOTextureDataGrayscale; components = 1; } else { OOLogWARN(@"texture.load.extractChannel.invalid", @"Cannot extract channel from texture \"%@\"", [_path lastPathComponent]); } } [self getDesiredWidth:&desiredWidth andHeight:&desiredHeight]; if (_isCubeMap && !OOCubeMapsAvailable()) { OOPixMapToRGBA(&pixMap); desiredHeight = MIN(desiredWidth * 2, 512U); if (sReducedDetail && desiredHeight > kCubeShrinkThreshold) desiredHeight /= 2; desiredWidth = desiredHeight * 2; OOPixMap converted = OOConvertCubeMapToLatLong(pixMap, desiredHeight, _generateMipMaps); OOFreePixMap(&pixMap); pixMap = converted; _isCubeMap = NO; #if DUMP_CONVERTED_CUBE_MAPS OODumpPixMap(pixMap, [NSString stringWithFormat:@"converted cube map %@", [[_path lastPathComponent] stringByDeletingPathExtension]]); #endif } // Rescale if needed. rescale = (_width != desiredWidth || _height != desiredHeight); if (rescale) { BOOL leaveSpaceForMipMaps = _generateMipMaps; #if OO_TEXTURE_CUBE_MAP if (_isCubeMap) leaveSpaceForMipMaps = NO; #endif OOLog(@"texture.load.rescale", @"Rescaling texture \"%@\" from %u x %u to %u x %u.", [_path lastPathComponent], pixMap.width, pixMap.height, desiredWidth, desiredHeight); pixMap = OOScalePixMap(pixMap, desiredWidth, desiredHeight, leaveSpaceForMipMaps); if (EXPECT_NOT(!OOIsValidPixMap(pixMap))) return; _data = pixMap.pixels; _width = pixMap.width; _height = pixMap.height; _rowBytes = pixMap.rowBytes; } #if OO_TEXTURE_CUBE_MAP if (_isCubeMap) { if (_generateMipMaps) { [self generateMipMapsForCubeMap]; } return; } #endif // Generate mip maps if needed. if (_generateMipMaps) { // Make space if needed. newSize = desiredWidth * components * desiredHeight; newSize = (newSize * 4) / 3; // +1 to fix overrun valgrind spotted - CIM _generateMipMaps = OOExpandPixMap(&pixMap, newSize+1); _data = pixMap.pixels; _width = pixMap.width; _height = pixMap.height; _rowBytes = pixMap.rowBytes; } if (_generateMipMaps) { OOGenerateMipMaps(_data, _width, _height, _format); } // All done. } - (void)getDesiredWidth:(OOPixMapDimension *)outDesiredWidth andHeight:(OOPixMapDimension *)outDesiredHeight { OOPixMapDimension desiredWidth, desiredHeight; // Work out appropriate final size for textures. if (!_noScalingWhatsoever) { // Cube maps are six times as high as they are wide, and we need to preserve that. if (_allowCubeMap && _height == _width * 6) { _isCubeMap = YES; desiredWidth = OORoundUpToPowerOf2_PixMap((2 * _width) / 3); desiredWidth = MIN(desiredWidth, sGLMaxSize / 8); if (sReducedDetail) { if (256 < desiredWidth) desiredWidth /= 2; } desiredWidth = MIN(desiredWidth, sUserMaxSize / 4); desiredHeight = desiredWidth * 6; } else { if (!sHaveNPOTTextures) { // Round to nearest power of two. NOTE: this is duplicated in OOTextureVerifierStage.m. desiredWidth = OORoundUpToPowerOf2_PixMap((2 * _width) / 3); desiredHeight = OORoundUpToPowerOf2_PixMap((2 * _height) / 3); } else { desiredWidth = _width; desiredHeight = _height; } desiredWidth = MIN(desiredWidth, sGLMaxSize); desiredHeight = MIN(desiredHeight, sGLMaxSize); if (!_avoidShrinking) { if (sReducedDetail) { if (_shrinkThreshold < desiredWidth) desiredWidth /= 2; if (_shrinkThreshold < desiredHeight) desiredHeight /= 2; } desiredWidth = MIN(desiredWidth, _maxSize); desiredHeight = MIN(desiredHeight, _maxSize); } } } else { desiredWidth = _width; desiredHeight = _height; } if (outDesiredWidth != NULL) *outDesiredWidth = desiredWidth; if (outDesiredHeight != NULL) *outDesiredHeight = desiredHeight; } - (void) completeAsyncTask { _ready = YES; } @end oolite-1.82/src/Core/NSDataOOExtensions.h000066400000000000000000000023371256642440500202340ustar00rootroot00000000000000/* NSDataOOExtensions.h Extensions to NSData. Copyright (C) 2008-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" @interface NSData (OOExtensions) + (instancetype) oo_dataWithOXZFile:(NSString *)path; @end oolite-1.82/src/Core/NSDataOOExtensions.m000066400000000000000000000112531256642440500202360ustar00rootroot00000000000000/* NSDataOOExtensions.m Extensions to NSData. Copyright (C) 2008-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" #import "unzip.h" #define ZIP_BUFFER_SIZE 8192 @implementation NSData (OOExtensions) + (instancetype) oo_dataWithOXZFile:(NSString *)path { unsigned i, cl; NSArray *components = [path pathComponents]; cl = [components count]; for (i = 0 ; i < cl ; i++) { NSString *component = [components objectAtIndex:i]; if ([[[component pathExtension] lowercaseString] isEqualToString:@"oxz"]) { break; } } // if i == cl then the path is entirely uncompressed if (i == cl) { /* -initWithContentsOfMappedFile fails quietly under OS X if there's no file, but GNUstep complains. */ #if OOLITE_MAC_OS_X return [[[NSData alloc] initWithContentsOfMappedFile:path] autorelease]; #else NSFileManager *fmgr = [NSFileManager defaultManager]; BOOL dir; if ([fmgr fileExistsAtPath:path isDirectory:&dir]) { if (!dir) { if ([[fmgr fileAttributesAtPath:path traverseLink:NO] fileSize] == 0) { OOLog(kOOLogFileNotFound, @"Expected file but found empty file at %@", path); } else { return [[[NSData alloc] initWithContentsOfMappedFile:path] autorelease]; } } else { OOLog(kOOLogFileNotFound, @"Expected file but found directory at %@", path); } } return nil; #endif } // otherwise components 0..i are the OXZ path, and i+1..n are the // path inside the OXZ NSRange range; range.location = 0; range.length = i+1; NSString *zipFile = [NSString pathWithComponents:[components subarrayWithRange:range]]; range.location = i+1; range.length = cl-(i+1); NSString *containedFile = [NSString pathWithComponents:[components subarrayWithRange:range]]; unzFile uf = NULL; const char* zipname = [zipFile UTF8String]; if (zipname != NULL) { uf = unzOpen64(zipname); } if (uf == NULL) { // This is not necessarily an error - the OXZ manager tries to // do this as a test for the presence of managed OXZs // OOLog(kOOLogFileNotFound, @"Could not unzip OXZ at %@", zipFile); return nil; } const char* filename = [containedFile UTF8String]; // unzLocateFile(*, *, 1) = case-sensitive extract if (unzLocateFile(uf, filename, 1) != UNZ_OK) { unzClose(uf); /* Much of the time this function is called with the * expectation that the file may not necessarily exist - * e.g. on plist merges, config scans, etc. So don't add log * entries for this failure mode */ // OOLog(kOOLogFileNotFound, @"Could not find %@ within OXZ at %@", containedFile, zipFile); return nil; } int err = UNZ_OK; unz_file_info64 file_info = {0}; err = unzGetCurrentFileInfo64(uf, &file_info, NULL, 0, NULL, 0, NULL, 0); if (err != UNZ_OK) { unzClose(uf); OOLog(kOOLogFileNotFound, @"Could not get properties of %@ within OXZ at %@", containedFile, zipFile); return nil; } err = unzOpenCurrentFile(uf); if (err != UNZ_OK) { unzClose(uf); OOLog(kOOLogFileNotFound, @"Could not read %@ within OXZ at %@", containedFile, zipFile); return nil; } NSMutableData *tmp = [NSMutableData dataWithCapacity:file_info.uncompressed_size]; void *buf = (void*)malloc(ZIP_BUFFER_SIZE); do { err = unzReadCurrentFile(uf, buf, ZIP_BUFFER_SIZE); if (err < 0) { OOLog(kOOLogFileNotFound, @"Could not read %@ within OXZ at %@ (err %d)", containedFile, zipFile, err); break; } if (err == 0) { break; } [tmp appendBytes:buf length:err]; } while (err > 0); free(buf); err = unzCloseCurrentFile(uf); if (err != UNZ_OK) { unzClose(uf); OOLog(kOOLogFileNotFound, @"Could not close %@ within OXZ at %@", containedFile, zipFile); return nil; } unzClose(uf); return [[tmp retain] autorelease]; } @end oolite-1.82/src/Core/NSDictionaryOOExtensions.h000066400000000000000000000030241256642440500214620ustar00rootroot00000000000000/* NSDictionaryOOExtensions.h Extensions to NSDictionary. Copyright (C) 2008-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" @interface NSDictionary (OOExtensions) // These all return self if passed nil paramters. They all return a new immutable dictionary if sent to a mutable dictionary. - (NSDictionary *) dictionaryByAddingObject:(id)object forKey:(id)key; - (NSDictionary *) dictionaryByRemovingObjectForKey:(id)key; - (NSDictionary *) dictionaryByAddingEntriesFromDictionary:(NSDictionary *)dictionary; @end oolite-1.82/src/Core/NSDictionaryOOExtensions.m000066400000000000000000000047431256642440500215000ustar00rootroot00000000000000/* NSDictionaryOOExtensions.m Copyright (C) 2008-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "NSDictionaryOOExtensions.h" @implementation NSDictionary (OOExtensions) - (NSDictionary *) dictionaryByAddingObject:(id)object forKey:(id)key { // Note: object lifetime issues aside, we need to copy and autorelease so that the right thing happens for mutable dictionaries. if (object == nil || key == nil) return [[self copy] autorelease]; NSMutableDictionary *temp = [self mutableCopy]; [temp setObject:object forKey:key]; NSDictionary *result = [[temp copy] autorelease]; [temp release]; return result; } - (NSDictionary *) dictionaryByRemovingObjectForKey:(id)key { // Note: object lifetime issues aside, we need to copy and autorelease so that the right thing happens for mutable dictionaries. if (key == nil) return [[self copy] autorelease]; NSMutableDictionary *temp = [self mutableCopy]; [temp removeObjectForKey:key]; NSDictionary *result = [[temp copy] autorelease]; [temp release]; return result; } - (NSDictionary *) dictionaryByAddingEntriesFromDictionary:(NSDictionary *)dictionary { // Note: object lifetime issues aside, we need to copy and autorelease so that the right thing happens for mutable dictionaries. if (dictionary == nil) return [[self copy] autorelease]; NSMutableDictionary *temp = [self mutableCopy]; [temp addEntriesFromDictionary:dictionary]; NSDictionary *result = [[temp copy] autorelease]; [temp release]; return result; } @end oolite-1.82/src/Core/NSFileManagerOOExtensions.h000066400000000000000000000036341256642440500215360ustar00rootroot00000000000000/* NSFileManagerOOExtensions.h This extends NSFileManager and adds some methods to insulate the main oolite code from the gory details of creating/chdiring to the commander save directory, as well as handling OXZ inspection Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #define SAVEDIR "oolite-saves" #if OOLITE_SDL #define SNAPSHOTDIR "snapshots" #endif @interface NSFileManager (OOExtensions) - (NSArray *) commanderContentsOfPath:(NSString*)savePath; - (NSString *) defaultCommanderPath; /* Wrappers for methods that were deprecated in Leopard but whose replacements still aren't in GNUstep. */ - (NSArray *) oo_directoryContentsAtPath:(NSString *)path; - (BOOL) oo_createDirectoryAtPath:(NSString *)path attributes:(NSDictionary *)attributes; - (NSDictionary *) oo_fileAttributesAtPath:(NSString *)path traverseLink:(BOOL)yorn; - (NSDictionary *) oo_fileSystemAttributesAtPath:(NSString *)path; - (BOOL) oo_removeItemAtPath:(NSString *)path; - (BOOL) oo_moveItemAtPath:(NSString *)src toPath:(NSString *)dest; #if OOLITE_SDL - (BOOL) chdirToSnapshotPath; #endif // this also checks inside OXZ files; always returns NO for directories - (BOOL) oo_oxzFileExistsAtPath:(NSString *)path; @end oolite-1.82/src/Core/NSFileManagerOOExtensions.m000066400000000000000000000171361256642440500215450ustar00rootroot00000000000000/* NSFileManagerOOExtensions.m This extends NSFileManager and adds some methods to insulate the main oolite code from the gory details of creating/chdiring to the commander save directory. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #import "ResourceManager.h" #import "OOPListParsing.h" #import "GameController.h" #import "NSFileManagerOOExtensions.h" #import "unzip.h" @implementation NSFileManager (OOExtensions) - (NSArray *) commanderContentsOfPath:(NSString *)savePath { BOOL pathIsDirectory = NO; if ([[NSFileManager defaultManager] fileExistsAtPath:savePath isDirectory:&pathIsDirectory] && pathIsDirectory) { NSMutableArray *contents = [NSMutableArray arrayWithArray:[self oo_directoryContentsAtPath:savePath]]; // at this point we should strip out any files not loadable as Oolite saved games unsigned i; for (i = 0; i < [contents count]; i++) { NSString* path = [savePath stringByAppendingPathComponent: (NSString*)[contents objectAtIndex:i]]; // ensure it's not a directory if (![[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&pathIsDirectory] && pathIsDirectory) { // check file extension if (![[path pathExtension] isEqual:@"oolite-save"]) { [contents removeObjectAtIndex: i--]; continue; } // check to see if we can parse the file okay NSDictionary *cdr = OODictionaryFromFile(path); if (!cdr) { OOLog(@"savedGame.read.fail.notDictionary", @">>>> %@ could not be parsed as a saved game.", path); [contents removeObjectAtIndex: i--]; continue; } } // all okay - we can use this path! [contents replaceObjectAtIndex: i withObject: path]; } return contents; } else { OOLogERR(@"savedGame.read.fail.fileNotFound", @"File at path '%@' could not be found.", savePath); return nil; } } - (NSString *) defaultCommanderPath { NSString *savedir = [NSHomeDirectory() stringByAppendingPathComponent:@SAVEDIR]; BOOL pathIsDirectory = NO; // does it exist? if (![[NSFileManager defaultManager] fileExistsAtPath:savedir isDirectory:&pathIsDirectory]) { // it doesn't exist. if([self oo_createDirectoryAtPath:savedir attributes:nil]) { return savedir; } else { OOLogERR(@"savedGame.defaultPath.create.failed", @"Unable to create '%@'. Saved games will go to the home directory.", savedir); return NSHomeDirectory(); } } // is it a directory? if (!pathIsDirectory) { OOLogERR(@"savedGame.defaultPath.notDirectory", @"'%@' is not a directory, saved games will go to the home directory.", savedir); return NSHomeDirectory(); } return savedir; } #if OOLITE_MAC_OS_X - (NSArray *) oo_directoryContentsAtPath:(NSString *)path { return [self contentsOfDirectoryAtPath:path error:NULL]; } - (BOOL) oo_createDirectoryAtPath:(NSString *)path attributes:(NSDictionary *)attributes { return [self createDirectoryAtPath:path withIntermediateDirectories:YES attributes:attributes error:NULL]; } - (NSDictionary *) oo_fileAttributesAtPath:(NSString *)path traverseLink:(BOOL)traverseLink { if (traverseLink) { NSString *linkDest = nil; do { linkDest = [self destinationOfSymbolicLinkAtPath:path error:NULL]; if (linkDest != nil) path = linkDest; } while (linkDest != nil); } return [self attributesOfItemAtPath:path error:NULL]; } - (NSDictionary *) oo_fileSystemAttributesAtPath:(NSString *)path { return [self attributesOfFileSystemForPath:path error:NULL]; } - (BOOL) oo_removeItemAtPath:(NSString *)path { return [self removeItemAtPath:path error:NULL]; } - (BOOL) oo_moveItemAtPath:(NSString *)src toPath:(NSString *)dest { return [self moveItemAtPath:src toPath:dest error:NULL]; } #else - (NSArray *) oo_directoryContentsAtPath:(NSString *)path { return [self directoryContentsAtPath:path]; } - (BOOL) oo_createDirectoryAtPath:(NSString *)path attributes:(NSDictionary *)attributes { #if OOLITE_WINDOWS return [self createDirectoryAtPath:path attributes:attributes]; #else // has been in GNUStep since 2008, so it's probably safe to use now. // .... why do I say things like that, of course it's not safe - CIM return [self createDirectoryAtPath:path withIntermediateDirectories:YES attributes:attributes error:NULL]; #endif } - (NSDictionary *) oo_fileAttributesAtPath:(NSString *)path traverseLink:(BOOL)yorn { return [self fileAttributesAtPath:path traverseLink:yorn]; } - (NSDictionary *) oo_fileSystemAttributesAtPath:(NSString *)path { return [self fileSystemAttributesAtPath:path]; } - (BOOL) oo_removeItemAtPath:(NSString *)path { return [self removeFileAtPath:path handler:nil]; } - (BOOL) oo_moveItemAtPath:(NSString *)src toPath:(NSString *)dest { return [self movePath:src toPath:dest handler:nil]; } #endif #if OOLITE_SDL - (BOOL) chdirToSnapshotPath { // SDL: the default path for snapshots is oolite.app/oolite-saves/snapshots NSString *savedir = [[NSHomeDirectory() stringByAppendingPathComponent:@SAVEDIR] stringByAppendingPathComponent:@SNAPSHOTDIR]; if (![self changeCurrentDirectoryPath: savedir]) { // it probably doesn't exist. if (![self createDirectoryAtPath: savedir attributes: nil]) { OOLog(@"savedSnapshot.defaultPath.create.failed", @"Unable to create directory %@", savedir); return NO; } if (![self changeCurrentDirectoryPath: savedir]) { OOLog(@"savedSnapshot.defaultPath.chdir.failed", @"Created %@ but couldn't make it the current directory.", savedir); return NO; } } return YES; } #endif - (BOOL) oo_oxzFileExistsAtPath:(NSString *)path { unsigned i, cl; NSArray *components = [path pathComponents]; cl = [components count]; for (i = 0 ; i < cl ; i++) { NSString *component = [components objectAtIndex:i]; if ([[[component pathExtension] lowercaseString] isEqualToString:@"oxz"]) { break; } } // if i == cl then the path is entirely uncompressed if (i == cl) { BOOL directory = NO; BOOL result = [self fileExistsAtPath:path isDirectory:&directory]; if (directory) { return NO; } return result; } NSRange range; range.location = 0; range.length = i+1; NSString *zipFile = [NSString pathWithComponents:[components subarrayWithRange:range]]; range.location = i+1; range.length = cl-(i+1); NSString *containedFile = [NSString pathWithComponents:[components subarrayWithRange:range]]; unzFile uf = NULL; const char* zipname = [zipFile cStringUsingEncoding:NSUTF8StringEncoding]; if (zipname != NULL) { uf = unzOpen64(zipname); } if (uf == NULL) { // no such zip file return NO; } const char* filename = [containedFile cStringUsingEncoding:NSUTF8StringEncoding]; // unzLocateFile(*, *, 1) = case-sensitive extract BOOL result = YES; if (unzLocateFile(uf, filename, 1) != UNZ_OK) { result = NO; } else { int err = UNZ_OK; unz_file_info64 file_info = {0}; err = unzGetCurrentFileInfo64(uf, &file_info, NULL, 0, NULL, 0, NULL, 0); if (err != UNZ_OK) { result = NO; } else { } } unzClose(uf); return result; } @end oolite-1.82/src/Core/NSMutableDictionaryOOExtensions.h000066400000000000000000000017361256642440500230040ustar00rootroot00000000000000/* MutableDictionaryExtension.h Convenience extensions to NSMutableDictionary. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import @interface NSMutableDictionary (OOExtensions) - (void)mergeEntriesFromDictionary:(NSDictionary *)otherDictionary; @end oolite-1.82/src/Core/NSMutableDictionaryOOExtensions.m000066400000000000000000000042551256642440500230100ustar00rootroot00000000000000/* NSMutableDictionaryOOExtensions.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #import "NSMutableDictionaryOOExtensions.h" @implementation NSMutableDictionary (OOExtensions) - (void)mergeEntriesFromDictionary:(NSDictionary *)otherDictionary { NSEnumerator *otherKeysEnum = nil; id key = nil; for (otherKeysEnum = [otherDictionary keyEnumerator]; (key = [otherKeysEnum nextObject]); ) { if (![self objectForKey:key]) [self setObject:[otherDictionary objectForKey:key] forKey:key]; else { BOOL merged = NO; id thisObject = [self objectForKey:key]; id otherObject = [otherDictionary objectForKey:key]; if ([thisObject isKindOfClass:[NSDictionary class]]&&[otherObject isKindOfClass:[NSDictionary class]]&&(![thisObject isEqual:otherObject])) { NSMutableDictionary* mergeObject = [NSMutableDictionary dictionaryWithDictionary:(NSDictionary*)thisObject]; [mergeObject mergeEntriesFromDictionary:(NSDictionary*)otherObject]; [self setObject:mergeObject forKey:key]; merged = YES; } if ([thisObject isKindOfClass:[NSArray class]]&&[otherObject isKindOfClass:[NSArray class]]&&(![thisObject isEqual:otherObject])) { NSMutableArray* mergeObject = [NSMutableArray arrayWithArray:(NSArray*)thisObject]; [mergeObject addObjectsFromArray:(NSArray*)otherObject]; [self setObject:mergeObject forKey:key]; merged = YES; } if (!merged) [self setObject:otherObject forKey:key]; } } } @end oolite-1.82/src/Core/NSNumberOOExtensions.h000066400000000000000000000027031256642440500206100ustar00rootroot00000000000000/* NSNumberOOExtensions.h Convenience methods for NSNumbers. Copyright (C) 2009-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import @interface NSNumber (OOExtensions) - (BOOL) oo_isFloatingPointNumber; // True if it represents a float or double. - (BOOL) oo_isBoolean; // True for booleans. WARNING: apparently also true for 0 and 1 in GNUstep 1.20 and later, as there is no distinction between boolean NSNumbers and int NSNumbers. @end oolite-1.82/src/Core/NSNumberOOExtensions.m000066400000000000000000000044771256642440500206270ustar00rootroot00000000000000/* NSNumberOOExtensions.m Copyright (C) 2009-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "NSNumberOOExtensions.h" #import "OOFunctionAttributes.h" @implementation NSNumber (OOExtensions) - (BOOL) oo_isFloatingPointNumber { #if __COREFOUNDATION_CFNUMBER__ return CFNumberIsFloatType((CFNumberRef)self); #else /* This happily assumes the compiler will inline strcmp() where one argument is a single-character constant string. Verified under apple-gcc 4.0 (even with -O0). */ const char *type = [self objCType]; return (strcmp(type, @encode(double)) == 0 || strcmp(type, @encode(float)) == 0); #endif } - (BOOL) oo_isBoolean { /* There's no explicit way to test this. However, on Mac OS X boolean NSNumbers are required to be constant objects because they're toll- free bridged with kCFBooleanTrue and kCFBooleanFalse, so comparison to those values has to work. In GNUstep, constant objects are also used, because they're not about to miss such an obvious optimization. */ #if __COREFOUNDATION_CFNUMBER__ return self == (NSNumber *)kCFBooleanTrue || self == (NSNumber *)kCFBooleanFalse; #else static NSNumber *sTrue = nil, *sFalse; if (EXPECT_NOT(sTrue == nil)) { sTrue = [[NSNumber numberWithBool:YES] retain]; sFalse = [[NSNumber numberWithBool:NO] retain]; } return self == sTrue || self == sFalse; #endif } @end oolite-1.82/src/Core/NSObjectOOExtensions.h000066400000000000000000000023541256642440500205700ustar00rootroot00000000000000/* NSObjectOOExtensions.h Extensions to NSObject. Copyright (C) 2010-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" @interface NSObject (OOExtensions) #ifndef NDEBUG + (size_t) oo_instanceSize; - (size_t) oo_objectSize; #endif @end oolite-1.82/src/Core/NSObjectOOExtensions.m000066400000000000000000000025601256642440500205740ustar00rootroot00000000000000/* NSObjectOOExtensions.m Copyright (C) 2010-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "NSObjectOOExtensions.h" #if OOLITE_MAC_OS_X #import #endif @implementation NSObject (OOExtensions) #ifndef NDEBUG + (size_t) oo_instanceSize { return class_getInstanceSize(self); } - (size_t) oo_objectSize { return [[self class] oo_instanceSize]; } #endif @end oolite-1.82/src/Core/NSScannerOOExtensions.h000066400000000000000000000022361256642440500207520ustar00rootroot00000000000000/* NSScannerOOExtensions.h Additions to NSScanner to work around bugs. FIXME: does this work around bugs that actually exist in any system we're targetting? It's a conundrum. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" @interface NSScanner (OOExtensions) - (BOOL) ooliteScanCharactersFromSet:(NSCharacterSet *)set intoString:(NSString **)value; - (BOOL) ooliteScanUpToCharactersFromSet:(NSCharacterSet *)set intoString:(NSString **)value; @end oolite-1.82/src/Core/NSScannerOOExtensions.m000066400000000000000000000043001256642440500207510ustar00rootroot00000000000000/* NSScannerOOExtensions.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "NSScannerOOExtensions.h" @implementation NSScanner (OOExtensions) - (BOOL) ooliteScanCharactersFromSet:(NSCharacterSet *)set intoString:(NSString **)value { NSUInteger currentLocation = [self scanLocation]; NSRange matchedRange = NSMakeRange( currentLocation, 0); NSString *scanString = [self string]; NSUInteger scanLength = [scanString length]; while ((currentLocation < scanLength)&&([set characterIsMember:[scanString characterAtIndex:currentLocation]])) { currentLocation++; } [self setScanLocation:currentLocation]; matchedRange.length = currentLocation - matchedRange.location; if (!matchedRange.length) return NO; if (value != NULL) { *value = [scanString substringWithRange:matchedRange]; } return YES; } - (BOOL) ooliteScanUpToCharactersFromSet:(NSCharacterSet *)set intoString:(NSString **)value { NSUInteger currentLocation = [self scanLocation]; NSRange matchedRange = NSMakeRange( currentLocation, 0); NSString *scanString = [self string]; NSUInteger scanLength = [scanString length]; while ((currentLocation < scanLength)&&(![set characterIsMember:[scanString characterAtIndex:currentLocation]])) { currentLocation++; } [self setScanLocation:currentLocation]; matchedRange.length = currentLocation - matchedRange.location; if (!matchedRange.length) return NO; if (value != NULL) { *value = [scanString substringWithRange:matchedRange]; } return YES; } @end oolite-1.82/src/Core/NSStringOOExtensions.h000066400000000000000000000041001256642440500206170ustar00rootroot00000000000000/* NSStringOOExtensions.h Convenience extensions to NSString. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" @interface NSString (OOExtensions) /* +stringWithContentsOfUnicodeFile: Like +stringWithContentsOfFile:, but biased towards Unicode encodings and cross-system consistency. Specifically: * If the file starts with a UTF-16 BOM, assume UTF-16. * Otherwise, if the file can be interpreted as UTF-8, assume UTF-8. * Otherwise, assume ISO-Latin-1. */ + (instancetype) stringWithContentsOfUnicodeFile:(NSString *)path; /* +stringWithUTF16String: Takes a NUL-terminated native-endian UTF-16 string. */ + (instancetype) stringWithUTF16String:(const unichar *)chars; /* -utf16DataWithBOM: Convert to native-endian UTF-16 data. */ - (NSData *) utf16DataWithBOM:(BOOL)includeByteOrderMark; /* - oo_hash Hash function for when we want consistency across platforms and versions. It implements modified djb2 (with xor rather than addition) in terms of UTF-16 code elements. */ - (uint32_t) oo_hash; @end @interface NSMutableString (OOExtensions) - (void) appendLine:(NSString *)line; - (void) appendFormatLine:(NSString *)fmt, ...; - (void) appendFormatLine:(NSString *)fmt arguments:(va_list)args; - (void) deleteCharacterAtIndex:(unsigned long)index; @end /* OOTabString(count) Return a string of tabs. */ NSString *OOTabString(NSUInteger count); oolite-1.82/src/Core/NSStringOOExtensions.m000066400000000000000000000115761256642440500206430ustar00rootroot00000000000000/* NSStringOOExtensions.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "NSStringOOExtensions.h" #import "NSDataOOExtensions.h" #import "OOCocoa.h" @implementation NSString (OOExtensions) + (instancetype) stringWithContentsOfUnicodeFile:(NSString *)path { id result = nil; BOOL OK = YES; NSData *data = nil; const uint8_t *bytes = NULL; size_t length = 0; const uint8_t *effectiveBytes = NULL; size_t effectiveLength = 0; data = [[NSData oo_dataWithOXZFile:path] retain]; if (data == nil) OK = NO; if (OK) { length = [data length]; bytes = [data bytes]; } if (OK && 2 <= length && (length % sizeof(unichar)) == 0) { // Could be UTF-16 unichar firstChar = bytes[0]; firstChar = (firstChar << 8) | bytes[1]; // Endianism doesn't matter, because we test both orders of BOM. if (firstChar == 0xFFFE || firstChar == 0xFEFF) { // Consider it to be UTF-16. result = [NSString stringWithCharacters:(unichar *)(bytes + sizeof(unichar)) length:(length / sizeof(unichar)) - 1]; if (result == nil) OK = NO; } } if (OK && result == nil) { // Not UTF-16. Try UTF-8. if (3 <= length && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) { // File starts with UTF-8 BOM; skip it. effectiveBytes = bytes + 3; effectiveLength = length + 3; } else { effectiveBytes = bytes; effectiveLength = length; } // Attempt to interpret as UTF-8 result = [[[NSString alloc] initWithBytes:effectiveBytes length:effectiveLength encoding:NSUTF8StringEncoding] autorelease]; } if (OK && result == nil) { // Not UTF-16 or UTF-8. Use ISO-Latin-1 (which should work for any byte sequence). result = [[[NSString alloc] initWithBytes:effectiveBytes length:effectiveLength encoding:NSISOLatin1StringEncoding] autorelease]; } [data release]; return result; } + (instancetype) stringWithUTF16String:(const unichar *)chars { size_t length; const unichar *end; if (chars == NULL) return nil; // Find length of string. end = chars; while (*end++) {} length = end - chars - 1; return [NSString stringWithCharacters:chars length:length]; } - (NSData *) utf16DataWithBOM:(BOOL)includeByteOrderMark { size_t lengthInChars; size_t lengthInBytes; unichar *buffer = NULL; unichar *characters = NULL; // Calculate sizes lengthInChars = [self length]; lengthInBytes = lengthInChars * sizeof(unichar); if (includeByteOrderMark) lengthInBytes += sizeof(unichar); // Allocate buffer buffer = malloc(lengthInBytes); if (buffer == NULL) return nil; // write BOM (native-endian) if desired characters = buffer; if (includeByteOrderMark) { *characters++ = 0xFEFF; } // Get the contents [self getCharacters:characters]; // NSData takes ownership of the buffer. return [NSData dataWithBytesNoCopy:buffer length:lengthInBytes freeWhenDone:YES]; } - (uint32_t) oo_hash { NSUInteger i, length = [self length]; uint32_t hash = 5381; for (i = 0; i < length; i++) { hash = ((hash << 5) + hash) /* 33 * hash */ ^ [self characterAtIndex:i]; } return hash; } @end @implementation NSMutableString (OOExtensions) - (void) appendLine:(NSString *)line { [self appendString:line ? [line stringByAppendingString:@"\n"] : (NSString *)@"\n"]; } - (void) appendFormatLine:(NSString *)fmt, ... { va_list args; va_start(args, fmt); [self appendFormatLine:fmt arguments:args]; va_end(args); } - (void) appendFormatLine:(NSString *)fmt arguments:(va_list)args { NSString *formatted = [[NSString alloc] initWithFormat:fmt arguments:args]; [self appendLine:formatted]; [formatted release]; } - (void) deleteCharacterAtIndex:(unsigned long)index { [self deleteCharactersInRange:NSMakeRange(index, 1)]; } @end NSString *OOTabString(NSUInteger count) { NSString * const staticTabs[] = { @"", @"\t", @"\t\t", @"\t\t\t", @"\t\t\t\t", @"\t\t\t\t\t", // 5 @"\t\t\t\t\t\t", @"\t\t\t\t\t\t\t", @"\t\t\t\t\t\t\t\t", @"\t\t\t\t\t\t\t\t\t", @"\t\t\t\t\t\t\t\t\t\t" // 10 }; enum { kStaticTabCount = sizeof staticTabs / sizeof *staticTabs }; if (count < kStaticTabCount) { return staticTabs[count]; } else { return [staticTabs[kStaticTabCount - 1] stringByAppendingString:OOTabString(count - (kStaticTabCount - 1))]; } } oolite-1.82/src/Core/NSThreadOOExtensions.h000066400000000000000000000025701256642440500205710ustar00rootroot00000000000000/* NSThreadOOExtensions.h Utility methods for NSThread. Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import #import "OOFunctionAttributes.h" @interface NSThread (OOExtensions) // Set name of current thread for identification during debugging. // Under OS X, this does more than -setName:. + (void) ooSetCurrentThreadName:(NSString *)name; @end oolite-1.82/src/Core/NSThreadOOExtensions.m000066400000000000000000000032711256642440500205750ustar00rootroot00000000000000/* NSThreadOOExtensions.m Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "NSThreadOOExtensions.h" #import "OOCocoa.h" #include #define OO_HAVE_PTHREAD_SETNAME_NP OOLITE_MAC_OS_X @implementation NSThread (OOExtensions) + (void) ooSetCurrentThreadName:(NSString *)name { // We may be called with no pool in place. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [[NSThread currentThread] setName:name]; #if OO_HAVE_PTHREAD_SETNAME_NP /* Under Mac OS X 10.6, the name set by pthread_setname_np() is used in crash reports, but, annoyingly, -[NSThread setName:] does not call it. */ pthread_setname_np([name UTF8String]); #endif [pool release]; } @end oolite-1.82/src/Core/OOALBufferedSound.h000066400000000000000000000025431256642440500200110ustar00rootroot00000000000000/* OOALBufferedSound.h OOALBufferedSound - OpenAL sound implementation for Oolite. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOSound.h" #import "OOALSoundDecoder.h" @interface OOALBufferedSound: OOSound { @private char *_buffer; size_t _size; double _sampleRate; NSString *_name; BOOL _stereo; } - (id)initWithDecoder:(OOALSoundDecoder *)inDecoder; @end oolite-1.82/src/Core/OOALBufferedSound.m000066400000000000000000000043621256642440500200170ustar00rootroot00000000000000/* OOALBufferedSound.m OOALBufferedSound - OpenAL sound implementation for Oolite. Copyright (C) 2005-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOALBufferedSound.h" #import "OOALSoundDecoder.h" @implementation OOALBufferedSound - (void)dealloc { free(_buffer); _buffer = NULL; DESTROY(_name); [super dealloc]; } - (NSString *)name { return _name; } - (id)initWithDecoder:(OOALSoundDecoder *)inDecoder { BOOL OK = YES; [OOSound setUp]; if (![OOSound isSoundOK] || nil == inDecoder) OK = NO; if (OK) { self = [super init]; if (nil == self) OK = NO; } if (OK) { _name = [[inDecoder name] copy]; _sampleRate = [inDecoder sampleRate]; OK = [inDecoder readCreatingBuffer:&_buffer withFrameCount:&_size]; _stereo = [inDecoder isStereo]; } if (!OK) { [self release]; self = nil; } return self; } - (ALuint) soundBuffer { ALuint buffer; ALint error; OOAL(alGenBuffers(1,&buffer)); if ((error = alGetError()) != AL_NO_ERROR) { OOLog(kOOLogSoundLoadingError,@"Could not create OpenAL buffer"); return 0; } else { if (!_stereo) { alBufferData(buffer,AL_FORMAT_MONO16,_buffer,_size,_sampleRate); } else { alBufferData(buffer,AL_FORMAT_STEREO16,_buffer,_size,_sampleRate); } return buffer; } } @end oolite-1.82/src/Core/OOALMusic.h000066400000000000000000000026621256642440500163400ustar00rootroot00000000000000/* OOALMusic.h Subclass of OOSound with additional controls specific to music playback. Only one instance of OOMusic may be playing at a time. OOALSound - OpenAL sound implementation for Oolite. Copyright (C) 2005-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import #import "OOALSound.h" @interface OOMusic: OOSound { @private OOSound *sound; } - (void) playLooped:(BOOL)looped; - (void) stop; - (BOOL) isPlaying; @end oolite-1.82/src/Core/OOALMusic.m000066400000000000000000000043141256642440500163410ustar00rootroot00000000000000/* OOALMusic.m OOALSound - OpenAL sound implementation for Oolite. Copyright (C) 2005-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOALMusic.h" #import "OOSoundSource.h" static OOMusic *sPlayingMusic = nil; static OOSoundSource *sMusicSource = nil; @implementation OOMusic + (id)allocWithZone:(NSZone *)inZone { return NSAllocateObject([OOMusic class], 0, inZone); } - (void)dealloc { if (sPlayingMusic == self) [self stop]; [sound release]; [super dealloc]; } - (id)initWithContentsOfFile:(NSString *)inPath { self = [super init]; if (nil != self) { sound = [[OOSound alloc] initWithContentsOfFile:inPath]; if (nil == sound) { [self release]; self = nil; } } return self; } - (NSString *)name { return [sound name]; } - (void)playLooped:(BOOL)inLoop { if (sPlayingMusic != self) { if (nil == sMusicSource) { sMusicSource = [[OOSoundSource alloc] init]; } [sMusicSource stop]; [sMusicSource setLoop:inLoop]; [sMusicSource setSound:sound]; [sMusicSource play]; sPlayingMusic = self; } } - (BOOL)isPlaying { return sPlayingMusic == self && [sMusicSource isPlaying]; } - (void)stop { if (sPlayingMusic == self) { sPlayingMusic = nil; [sMusicSource stop]; } } @end oolite-1.82/src/Core/OOALSound.h000066400000000000000000000026701256642440500163470ustar00rootroot00000000000000/* OOALSound.h OOALSound - OpenAL sound implementation for Oolite. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import #import "OOOpenALController.h" @interface OOSound: NSObject + (BOOL) setUp; + (void) update; + (void) setMasterVolume:(float) fraction; + (float) masterVolume; - (id) initWithContentsOfFile:(NSString *)path; - (NSString *)name; + (BOOL) isSoundOK; - (ALuint) soundBuffer; - (BOOL) soundIncomplete; - (void) rewind; @end oolite-1.82/src/Core/OOALSound.m000066400000000000000000000072471256642440500163610ustar00rootroot00000000000000/* OOALSound.m OOALSound - OpenAL sound implementation for Oolite. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOALSound.h" #import "OOLogging.h" #import "OOCollectionExtractors.h" #import "OOMaths.h" #import "OOALSoundDecoder.h" #import "OOOpenALController.h" #import "OOALBufferedSound.h" #import "OOALStreamedSound.h" #import "OOALSoundMixer.h" #define KEY_VOLUME_CONTROL @"volume_control" static const size_t kMaxBufferedSoundSize = 1 << 20; // 1 MB static BOOL sIsSetUp = NO; static BOOL sIsSoundOK = NO; @implementation OOSound + (BOOL) setUp { if (!sIsSetUp) { sIsSetUp = YES; OOOpenALController* controller = [OOOpenALController sharedController]; if (controller != nil) { sIsSoundOK = YES; float volume = [[NSUserDefaults standardUserDefaults] oo_floatForKey:KEY_VOLUME_CONTROL defaultValue:1.0]; [self setMasterVolume:volume]; } } return sIsSoundOK; } + (void) setMasterVolume:(float) fraction { if (!sIsSetUp && ![self setUp]) return; fraction = OOClamp_0_1_f(fraction); OOOpenALController *controller = [OOOpenALController sharedController]; if (fraction != [controller masterVolume]) { [controller setMasterVolume:fraction]; [[NSUserDefaults standardUserDefaults] setFloat:[controller masterVolume] forKey:KEY_VOLUME_CONTROL]; } } + (float) masterVolume { if (!sIsSetUp && ![self setUp] ) return 0.0; OOOpenALController *controller = [OOOpenALController sharedController]; return [controller masterVolume]; } - (id) init { if (!sIsSetUp) [OOSound setUp]; return [super init]; } - (id) initWithContentsOfFile:(NSString *)path { [self release]; if (!sIsSetUp && ![OOSound setUp]) return nil; OOALSoundDecoder *decoder; decoder = [[OOALSoundDecoder alloc] initWithPath:path]; if (nil == decoder) return nil; if ([decoder sizeAsBuffer] <= kMaxBufferedSoundSize) { self = [[OOALBufferedSound alloc] initWithDecoder:decoder]; } else { self = [[OOALStreamedSound alloc] initWithDecoder:decoder]; } [decoder release]; if (nil != self) { #ifndef NDEBUG OOLog(kOOLogSoundLoadingSuccess, @"Loaded sound %@", path); #endif } else { OOLog(kOOLogSoundLoadingError, @"Failed to load sound \"%@\"", path); } return self; } - (id)initWithDecoder:(OOALSoundDecoder *)inDecoder { [self release]; return nil; } - (NSString *)name { OOLogGenericSubclassResponsibility(); return @""; } + (void) update { OOSoundMixer * mixer = [OOSoundMixer sharedMixer]; if( sIsSoundOK && mixer) [mixer update]; } + (BOOL) isSoundOK { return sIsSoundOK; } - (ALuint) soundBuffer { OOLogGenericSubclassResponsibility(); return 0; } - (BOOL) soundIncomplete { return NO; } - (void) rewind { // doesn't need to do anything on seekable FDs } @end oolite-1.82/src/Core/OOALSoundChannel.h000066400000000000000000000041461256642440500176400ustar00rootroot00000000000000/* OOALSoundChannel.h A channel for audio playback. This class is an implementation detail. Do not use it directly; use an OOSoundSource to play an OOSound. OOALSound - OpenAL sound implementation for Oolite. Copyright (C) 2006-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import #import "OOOpenALController.h" #import "OOMaths.h" @class OOSound; @interface OOSoundChannel: NSObject { OOSoundChannel *_next; id _delegate; OOSound *_sound; ALuint _buffer; ALuint _lastBuffer; BOOL _bigSound; ALuint _source; BOOL _playing; BOOL _loop; } - (void) update; - (void) setDelegate:(id)delegate; // Unretained pointer used to maintain simple stack - (OOSoundChannel *) next; - (void) setNext:(OOSoundChannel *)next; // set sound position relative to listener - (void) setPosition:(Vector) vector; - (void) setGain:(float) gain; - (BOOL) playSound:(OOSound *)sound looped:(BOOL)loop; - (void)stop; - (OOSound *)sound; @end @interface NSObject(OOSoundChannelDelegate) - (void)channel:(OOSoundChannel *)inChannel didFinishPlayingSound:(OOSound *)inSound; @end oolite-1.82/src/Core/OOALSoundChannel.m000066400000000000000000000122571256642440500176470ustar00rootroot00000000000000/* OOALSoundChannel.m OOALSound - OpenAL sound implementation for Oolite. Copyright (C) 2006-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOALSoundChannel.h" #import "OOALSound.h" #import "OOLogging.h" #import "OOMaths.h" @interface OOSoundChannel (Private) - (BOOL) enqueueBuffer:(OOSound *)sound; - (void) hasStopped; - (void) getNextSoundBuffer; @end @implementation OOSoundChannel - (id) init { if ((self = [super init])) { ALuint error; OOAL(alGenSources(1,&_source)); if ((error = alGetError()) != AL_NO_ERROR) { OOLog(kOOLogSoundInitError, @"Could not create OpenAL source"); [self release]; self = nil; } // sources are all relative to listener, defaulting to zero vector OOAL(alSourcei(_source, AL_SOURCE_RELATIVE, AL_TRUE)); OOAL(alSource3f(_source, AL_POSITION, 0.0f, 0.0f, 0.0f)); } return self; } - (void) dealloc { [self hasStopped]; // make sure buffers are dequeued and deleted OOAL(alDeleteSources(1, &_source)); [super dealloc]; } - (void) update { // Check if we've reached the end of a sound. if (_sound != nil) { ALint check; OOAL(alGetSourcei(_source,AL_SOURCE_STATE,&check)); if (check == AL_STOPPED) { [self hasStopped]; } else if ([_sound soundIncomplete]) // streaming and not finished loading { OOLog(@"sound.buffer", @"Incomplete, trying next for %@", [_sound name]); [self getNextSoundBuffer]; } else if (_loop) { OOLog(@"sound.buffer", @"Looping, trying restart for %@", [_sound name]); // sound is complete, but needs to be looped, so start it again [_sound rewind]; [self getNextSoundBuffer]; } } } - (void) getNextSoundBuffer { if (!_bigSound) { // we've only loaded one buffer so far _bigSound = YES; _lastBuffer = _buffer; [self enqueueBuffer:_sound]; } else { // _lastBuffer has something in it, so only queue up // another one if we've finished with that ALint processed = 0; OOAL(alGetSourcei(_source, AL_BUFFERS_PROCESSED, &processed)); if (processed > 0) // slot free { // dequeue and delete lastBuffer ALuint buffer; OOAL(alSourceUnqueueBuffers(_source, 1, &buffer)); assert(buffer == _lastBuffer); OOAL(alDeleteBuffers(1,&_lastBuffer)); // shuffle along, and grab the next bit _lastBuffer = _buffer; [self enqueueBuffer:_sound]; } } } - (void) setDelegate:(id)delegate { _delegate = delegate; } - (OOSoundChannel *) next { return _next; } - (void) setNext:(OOSoundChannel *)next { _next = next; } - (void) setPosition:(Vector) vector { OOAL(alSource3f(_source, AL_POSITION, vector.x, vector.y, vector.z)); } - (void) setGain:(float) gain { OOAL(alSourcef(_source, AL_GAIN, gain)); } - (BOOL) playSound:(OOSound *)sound looped:(BOOL)loop { if (sound == nil) return NO; if (_sound != nil) [self stop]; _loop = loop; _bigSound = NO; [sound rewind]; if ([self enqueueBuffer:sound]) { _sound = [sound retain]; return YES; } else { return NO; } } - (void) stop { if (_sound != nil) { OOAL(alSourceStop(_source)); [self hasStopped]; } } - (void) hasStopped { ALint queued; OOAL(alGetSourcei(_source, AL_BUFFERS_QUEUED, &queued)); while (queued--) { ALuint buffer; OOAL(alSourceUnqueueBuffers(_source, 1, &buffer)); } OOAL(alDeleteBuffers(1,&_buffer)); if (_bigSound) { // then we have two buffers to cleanup OOAL(alDeleteBuffers(1,&_lastBuffer)); } _bigSound = NO; OOSound *sound = _sound; _sound = nil; if (nil != _delegate && [_delegate respondsToSelector:@selector(channel:didFinishPlayingSound:)]) { [_delegate channel:self didFinishPlayingSound:sound]; } [sound release]; } - (BOOL) enqueueBuffer:(OOSound *)sound { // get sound data _buffer = [sound soundBuffer]; // bind sound data to buffer OOAL(alSourceQueueBuffers(_source, 1, &_buffer)); ALuint error; if ((error = alGetError()) != AL_NO_ERROR) { OOLog(@"ov.debug",@"Error %d queueing buffers",error); return NO; } ALint playing = 0; OOAL(alGetSourcei(_source,AL_SOURCE_STATE,&playing)); if (playing != AL_PLAYING) { OOAL(alSourcePlay(_source)); if ((error = alGetError()) != AL_NO_ERROR) { OOLog(@"ov.debug",@"Error %d playing source",error); return NO; } } return YES; } - (OOSound *)sound { return _sound; } @end oolite-1.82/src/Core/OOALSoundDecoder.h000066400000000000000000000035631256642440500176370ustar00rootroot00000000000000/* OOALSoundDecoder.h Class responsible for converting a sound to a PCM buffer for playback. This class is an implementation detail. Do not use it directly; use OOSound to load sounds. OOALSound - OpenAL sound implementation for Oolite. Copyright (C) 2005-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import #define OOAL_STREAM_CHUNK_SIZE (sizeof(char) * 409600) @interface OOALSoundDecoder: NSObject - (id)initWithPath:(NSString *)inPath; + (OOALSoundDecoder *)codecWithPath:(NSString *)inPath; // Full-buffer reading. - (BOOL)readCreatingBuffer:(char **)outBuffer withFrameCount:(size_t *)outSize; // Stream reading. - (size_t)streamToBuffer:(char *)buffer; // Returns the size of the data -readMonoCreatingBuffer:withFrameCount: will create. - (size_t)sizeAsBuffer; - (BOOL)isStereo; - (long)sampleRate; // For streaming - (void) reset; - (NSString *)name; @end oolite-1.82/src/Core/OOALSoundDecoder.m000066400000000000000000000246571256642440500176530ustar00rootroot00000000000000/* OOALSoundDecoder.m OOALSound - OpenAL sound implementation for Oolite. Copyright (C) 2005-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOALSoundDecoder.h" #import "NSDataOOExtensions.h" #import #import "OOLogging.h" #import "unzip.h" enum { kMaxDecodeSize = 1 << 20 // 2^20 frames = 4 MB }; static size_t OOReadOXZVorbis (void *ptr, size_t size, size_t nmemb, void *datasource); static int OOCloseOXZVorbis (void *datasource); // not practical to implement these //static int OOSeekOXZVorbis (void *datasource, ogg_int64_t offset, int whence); //static long OOTellOXZVorbis (void *datasource); @interface OOALSoundVorbisCodec: OOALSoundDecoder { OggVorbis_File _vf; NSString *_name; BOOL _readStarted; BOOL _seekableStream; @public unzFile uf; } - (NSDictionary *)comments; @end @implementation OOALSoundDecoder - (id)initWithPath:(NSString *)inPath { [self release]; self = nil; if ([[inPath pathExtension] isEqual:@"ogg"]) { self = [[OOALSoundVorbisCodec alloc] initWithPath:inPath]; } return self; } + (OOALSoundDecoder *)codecWithPath:(NSString *)inPath { if ([[inPath pathExtension] isEqual:@"ogg"]) { return [[[OOALSoundVorbisCodec alloc] initWithPath:inPath] autorelease]; } return nil; } - (size_t)streamToBuffer:(char *)ioBuffer { return 0; } - (BOOL)readCreatingBuffer:(char **)outBuffer withFrameCount:(size_t *)outSize { if (NULL != outBuffer) *outBuffer = NULL; if (NULL != outSize) *outSize = 0; return NO; } - (size_t)sizeAsBuffer { return 0; } - (BOOL)isStereo { return NO; } - (long)sampleRate { return 0; } - (void) reset { // nothing } - (NSString *)name { return @""; } @end @implementation OOALSoundVorbisCodec - (id)initWithPath:(NSString *)path { if ((self = [super init])) { BOOL OK = NO; _name = [[path lastPathComponent] retain]; unsigned i, cl; NSArray *components = [path pathComponents]; cl = [components count]; for (i = 0 ; i < cl ; i++) { NSString *component = [components objectAtIndex:i]; if ([[[component pathExtension] lowercaseString] isEqualToString:@"oxz"]) { break; } } // if i == cl then the path is entirely uncompressed if (i == cl) { /* Get vorbis data from a standard file stream */ int err; FILE *file; _seekableStream = YES; if (nil != path) { file = fopen([path UTF8String], "rb"); if (NULL != file) { err = ov_open_callbacks(file, &_vf, NULL, 0, OV_CALLBACKS_DEFAULT); if (0 == err) { OK = YES; } } } if (!OK) { [self release]; self = nil; } } else { _seekableStream = NO; NSRange range; range.location = 0; range.length = i+1; NSString *zipFile = [NSString pathWithComponents:[components subarrayWithRange:range]]; range.location = i+1; range.length = cl-(i+1); NSString *containedFile = [NSString pathWithComponents:[components subarrayWithRange:range]]; const char* zipname = [zipFile UTF8String]; if (zipname != NULL) { uf = unzOpen64(zipname); } if (uf == NULL) { OOLog(kOOLogFileNotFound, @"Could not unzip OXZ at %@", zipFile); [self release]; self = nil; } else { const char* filename = [containedFile UTF8String]; // unzLocateFile(*, *, 1) = case-sensitive extract if (unzLocateFile(uf, filename, 1) != UNZ_OK) { unzClose(uf); [self release]; self = nil; } else { int err = UNZ_OK; unz_file_info64 file_info = {0}; err = unzGetCurrentFileInfo64(uf, &file_info, NULL, 0, NULL, 0, NULL, 0); if (err != UNZ_OK) { unzClose(uf); OOLog(kOOLogFileNotFound, @"Could not get properties of %@ within OXZ at %@", containedFile, zipFile); [self release]; self = nil; } else { err = unzOpenCurrentFile(uf); if (err != UNZ_OK) { unzClose(uf); OOLog(kOOLogFileNotFound, @"Could not read %@ within OXZ at %@", containedFile, zipFile); [self release]; self = nil; } else { ov_callbacks _callbacks = { OOReadOXZVorbis, // read sequentially NULL, // no seek OOCloseOXZVorbis, // close file NULL, // no tell }; err = ov_open_callbacks(self, &_vf, NULL, 0, _callbacks); if (0 == err) { OK = YES; _readStarted = NO; } if (!OK) { unzClose(uf); [self release]; self = nil; } } } } } } } #ifdef OOLITE_DEBUG_SOUND_FILE_OPENING if (self != nil) { OOLog(@"sound.retain",@"%@",_name); } #endif return self; } - (void)dealloc { #ifdef OOLITE_DEBUG_SOUND_FILE_OPENING if (self != nil) { OOLog(@"sound.release",@"%@",_name); } #endif [_name release]; ov_clear(&_vf); unzClose(uf); [super dealloc]; } - (NSDictionary *)comments { vorbis_comment *comments; unsigned i, count; NSMutableDictionary *result = nil; NSString *comment, *key, *value; NSRange range; comments = ov_comment(&_vf, -1); if (NULL != comments) { count = comments->comments; if (0 != count) { result = [NSMutableDictionary dictionaryWithCapacity:count]; for (i = 0; i != count; ++i) { comment = [[NSString alloc] initWithBytesNoCopy:comments->user_comments[i] length:comments->comment_lengths[i] encoding:NSUTF8StringEncoding freeWhenDone:NO]; range = [comment rangeOfString:@"="]; if (0 != range.length) { key = [comment substringToIndex:range.location]; value = [comment substringFromIndex:range.location + 1]; } else { key = comment; value = @""; } [result setObject:value forKey:key]; [comment release]; } } } return result; } - (BOOL)readCreatingBuffer:(char **)outBuffer withFrameCount:(size_t *)outSize { char *buffer = NULL, *dst; size_t sizeInFrames = 0; int remaining; long framesRead; // 16-bit samples, either two or one track int frameSize = [self isStereo] ? 4 : 2; ogg_int64_t totalSizeInFrames; BOOL OK = YES; if (NULL != outBuffer) *outBuffer = NULL; if (NULL != outSize) *outSize = 0; if (NULL == outBuffer || NULL == outSize) OK = NO; if (OK) { totalSizeInFrames = ov_pcm_total(&_vf, -1); assert ((uint64_t)totalSizeInFrames < (uint64_t)SIZE_MAX); // Should have been checked by caller sizeInFrames = (size_t)totalSizeInFrames; } if (OK) { buffer = malloc(sizeof (char) * frameSize * sizeInFrames); if (!buffer) OK = NO; } if (OK && sizeInFrames) { remaining = (int)MIN(frameSize * sizeInFrames, (size_t)INT_MAX); dst = buffer; char pcmout[4096]; do { int toRead = sizeof(pcmout); if (remaining < toRead) { toRead = remaining; } framesRead = ov_read(&_vf, pcmout, toRead, 0, 2, 1, NULL); if (framesRead <= 0) { if (OV_HOLE == framesRead) continue; //else: break; } memcpy(dst, &pcmout, sizeof (char) * framesRead); remaining -= framesRead; dst += framesRead; } while (0 < remaining); sizeInFrames -= remaining; // In case we stopped at an error } if (OK) { *outBuffer = buffer; *outSize = sizeInFrames*frameSize; } else { if (buffer) free(buffer); } return OK; } - (size_t)streamToBuffer:(char *)buffer { int remaining = OOAL_STREAM_CHUNK_SIZE; size_t streamed = 0; long framesRead; char *dst = buffer; char pcmout[4096]; _readStarted = YES; do { int toRead = sizeof(pcmout); if (remaining < toRead) { toRead = remaining; } framesRead = ov_read(&_vf, pcmout, toRead, 0, 2, 1, NULL); if (framesRead <= 0) { if (OV_HOLE == framesRead) continue; //else: break; } memcpy(dst, &pcmout, sizeof (char) * framesRead); remaining -= sizeof(char) * framesRead; dst += sizeof(char) * framesRead; streamed += sizeof(char) * framesRead; } while (0 < remaining); return streamed; } - (size_t)sizeAsBuffer { ogg_int64_t size; size = ov_pcm_total(&_vf, -1); size *= sizeof(char) * ([self isStereo] ? 4 : 2); if ((uint64_t)SIZE_MAX < (uint64_t)size) size = (ogg_int64_t)SIZE_MAX; return (size_t)size; } - (BOOL)isStereo { return 1 < ov_info(&_vf, -1)->channels; } - (NSString *)description { return [NSString stringWithFormat:@"<%@ %p>{\"%@\", comments=%@}", [self className], self, _name, [self comments]]; } - (long)sampleRate { return ov_info(&_vf, -1)->rate; } - (void) reset { if (!_readStarted) { return; // don't need to do anything } if (_seekableStream) { ov_pcm_seek(&_vf, 0); return; } // reset current file pointer in OXZ unzOpenCurrentFile(uf); // reopen OGG streamer ov_clear(&_vf); ov_callbacks _callbacks = { OOReadOXZVorbis, // read sequentially NULL, // no seek OOCloseOXZVorbis, // close file NULL, // no tell }; ov_open_callbacks(self, &_vf, NULL, 0, _callbacks); _readStarted = NO; } - (NSString *)name { return [[_name retain] autorelease]; } @end static size_t OOReadOXZVorbis (void *ptr, size_t size, size_t nmemb, void *datasource) { OOALSoundVorbisCodec *src = (OOALSoundVorbisCodec *)datasource; size_t toRead = size*nmemb; void *buf = (void*)malloc(toRead); int err = UNZ_OK; err = unzReadCurrentFile(src->uf, buf, toRead); // OOLog(@"sound.replay",@"Read %d blocks, got %d",toRead,err); if (err > 0) { memcpy(ptr, buf, err); } if (err < 0) { return OV_EREAD; } return err; } static int OOCloseOXZVorbis (void *datasource) { // doing this prevents replaying // OOALSoundVorbisCodec *src = (OOALSoundVorbisCodec *)datasource; // unzClose(src->uf); return 0; } oolite-1.82/src/Core/OOALSoundMixer.h000066400000000000000000000033101256642440500173440ustar00rootroot00000000000000/* OOALSoundMixer.h Class responsible for managing and mixing sound channels. This class is an implementation detail. Do not use it directly; use an OOSoundSource to play an OOSound. OOALSound - OpenAL sound implementation for Oolite. Copyright (C) 2006-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import @class OOSoundChannel; enum { kMixerGeneralChannels = 32 }; @interface OOSoundMixer: NSObject { OOSoundChannel *_channels[kMixerGeneralChannels]; OOSoundChannel *_freeList; uint32_t _maxChannels; uint32_t _playMask; } // Singleton accessor + (id) sharedMixer; - (void) update; - (OOSoundChannel *) popChannel; - (void) pushChannel:(OOSoundChannel *)channel; @end oolite-1.82/src/Core/OOALSoundMixer.m000066400000000000000000000061421256642440500173570ustar00rootroot00000000000000/* OOALSoundMixer.m OOALSound - OpenAL sound implementation for Oolite. Copyright (C) 2006-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #import "OOALSoundMixer.h" #import "OOCocoa.h" #import "OOALSound.h" #import "OOALSoundChannel.h" static OOSoundMixer *sSingleton = nil; @implementation OOSoundMixer + (id) sharedMixer { if (nil == sSingleton) { [[self alloc] init]; } return sSingleton; } - (id) init { BOOL OK = YES; uint32_t idx = 0, count = kMixerGeneralChannels; OOSoundChannel *channel; if (!(self = [super init])) return nil; if (![OOSound setUp]) OK = NO; if (OK) { // Allocate channels do { channel = [[OOSoundChannel alloc] init]; if (nil != channel) { _channels[idx++] = channel; [self pushChannel:channel]; } } while (--count); } if (!OK) { [super release]; // static analyser complains about this next line; probably nothing - CIM self = nil; } else { sSingleton = self; } return sSingleton; } // only to be called at app shutdown by OOOpenALController::shutdown - (void) shutdown { uint32_t i; for (i = 0; i < kMixerGeneralChannels; ++i) { DESTROY(_channels[i]); } } - (void) update { uint32_t i; for (i = 0; i < kMixerGeneralChannels; ++i) { [_channels[i] update]; } } - (OOSoundChannel *) popChannel { OOSoundChannel *channel = _freeList; _freeList = [channel next]; [channel setNext:nil]; return channel; } - (void) pushChannel:(OOSoundChannel *)channel { assert(channel != nil); [channel setNext:_freeList]; _freeList = channel; } @end @implementation OOSoundMixer (Singleton) /* Canonical singleton boilerplate. See Cocoa Fundamentals Guide: Creating a Singleton Instance. See also +sharedMixer above. NOTE: assumes single-threaded access. */ + (id)allocWithZone:(NSZone *)inZone { if (sSingleton == nil) { sSingleton = [super allocWithZone:inZone]; return sSingleton; } return nil; } - (id)copyWithZone:(NSZone *)inZone { return self; } - (id)retain { return self; } - (NSUInteger)retainCount { return UINT_MAX; } - (void)release {} - (id)autorelease { return self; } @end oolite-1.82/src/Core/OOALStreamedSound.h000066400000000000000000000026251256642440500200340ustar00rootroot00000000000000/* OOALStreamedSound.h OOALStreamedSound - OpenAL sound implementation for Oolite. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOSound.h" #import "OOALSoundDecoder.h" @interface OOALStreamedSound: OOSound { @private char *_buffer; size_t _size; double _sampleRate; NSString *_name; BOOL _stereo; OOALSoundDecoder *decoder; BOOL _reachedEnd; } - (id)initWithDecoder:(OOALSoundDecoder *)inDecoder; @end oolite-1.82/src/Core/OOALStreamedSound.m000066400000000000000000000050641256642440500200410ustar00rootroot00000000000000/* OOALStreamedSound.m OOALStreamedSound - OpenAL sound implementation for Oolite. Copyright (C) 2005-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOALStreamedSound.h" #import "OOALSoundDecoder.h" @implementation OOALStreamedSound - (void)dealloc { free(_buffer); _buffer = NULL; [decoder release]; [super dealloc]; } - (NSString *)name { return _name; } - (id)initWithDecoder:(OOALSoundDecoder *)inDecoder { BOOL OK = YES; [OOSound setUp]; if (![OOSound isSoundOK] || nil == inDecoder) OK = NO; if (OK) { self = [super init]; if (nil == self) OK = NO; } if (OK) { _name = [[inDecoder name] copy]; _sampleRate = [inDecoder sampleRate]; _stereo = [inDecoder isStereo]; _reachedEnd = NO; _buffer = malloc(OOAL_STREAM_CHUNK_SIZE); decoder = [inDecoder retain]; [self rewind]; } if (!OK) { [self release]; self = nil; } return self; } - (void) rewind { [decoder reset]; _reachedEnd = NO; } - (BOOL) soundIncomplete { return !_reachedEnd; } - (ALuint) soundBuffer { size_t transferred = [decoder streamToBuffer:_buffer]; if (transferred < OOAL_STREAM_CHUNK_SIZE) { // otherwise keep going _reachedEnd = YES; } ALuint buffer; ALint error; OOAL(alGenBuffers(1,&buffer)); if ((error = alGetError()) != AL_NO_ERROR) { OOLog(kOOLogSoundLoadingError,@"Could not create OpenAL buffer"); return 0; } else { if (!_stereo) { alBufferData(buffer,AL_FORMAT_MONO16,_buffer,transferred,_sampleRate); } else { alBufferData(buffer,AL_FORMAT_STEREO16,_buffer,transferred,_sampleRate); } return buffer; } } @end oolite-1.82/src/Core/OOAsyncQueue.h000066400000000000000000000035651256642440500171300ustar00rootroot00000000000000/* OOAsyncQueue.h By Jens Ayton OOAsyncQueue is used to pass messages (in the form of arbitrary objects) between threads. It is many-to-many capable, i.e. it is safe to send messages from any number of threads and to read messages from any number of threads. Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import @interface OOAsyncQueue: NSObject { @private NSConditionLock *_lock; struct OOAsyncQueueElement *_head, *_tail, *_pool; unsigned _elemCount, _poolCount; } - (BOOL)enqueue:(id)object; // Returns NO on failure, or if object is nil. - (id)dequeue; // Blocks until the queue is non-empty. - (id)tryDequeue; // Returns nil if empty. // Due to the asynchronous nature of the queue, these values are immediately out of date. - (BOOL)empty; - (unsigned)count; - (void)emptyQueue; // Releases all elements. @end oolite-1.82/src/Core/OOAsyncQueue.m000066400000000000000000000141061256642440500171260ustar00rootroot00000000000000/* OOAsyncQueue.m By Jens Ayton Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #import "OOAsyncQueue.h" #import "OOFunctionAttributes.h" #import "OOLogging.h" #import "NSThreadOOExtensions.h" #include #ifndef OO_BUGGY_PTHREADS #if OOLITE_WINDOWS // Maybe add #if OOLITE_64_BIT too? #define OO_BUGGY_PTHREADS 1 #else #define OO_BUGGY_PTHREADS 0 #endif #endif enum { kConditionNoData = 1, kConditionQueuedData, kConditionDead }; enum { kMaxPoolElements = 5 }; typedef struct OOAsyncQueueElement OOAsyncQueueElement; struct OOAsyncQueueElement { OOAsyncQueueElement *next; id object; }; OOINLINE OOAsyncQueueElement *AllocElement(void) { return malloc(sizeof (OOAsyncQueueElement)); } OOINLINE void FreeElement(OOAsyncQueueElement *element) { free(element); } @interface OOAsyncQueue (OOPrivate) - (void)doEmptyQueueWithAcquiredLock; - (id)doDequeAndUnlockWithAcquiredLock; - (void)recycleElementWithAcquiredLock:(OOAsyncQueueElement *)element; @end @implementation OOAsyncQueue - (id)init { self = [super init]; if (self != nil) { _lock = [[NSConditionLock alloc] initWithCondition:kConditionNoData]; [_lock setName:@"OOAsyncQueue lock"]; if (_lock == nil) { [self release]; self = nil; } } return self; } - (void)dealloc { OOAsyncQueueElement *element = NULL; [_lock lock]; if (_elemCount != 0) { OOLogWARN(@"asyncQueue.nonEmpty", @"%@ deallocated while non-empty, flushing.", self); [self doEmptyQueueWithAcquiredLock]; } // Free element pool. while (_pool != NULL) { element = _pool; _pool = element->next; free(element); } [_lock unlockWithCondition:kConditionDead]; [_lock release]; [super dealloc]; } - (NSString *)description { // Don't bother locking, the value would be out of date immediately anyway. return [NSString stringWithFormat:@"<%@ %p>{%u elements}", [self class], self, _elemCount]; } - (BOOL)enqueue:(id)object { OOAsyncQueueElement *element = NULL; BOOL success = NO; if (EXPECT_NOT(object == nil)) return NO; [_lock lock]; // Get an element. if (_pool != NULL) { element = _pool; _pool = element->next; --_poolCount; } else { element = AllocElement(); if (element == NULL) goto FAIL; } // Set element fields. element->object = [object retain]; element->next = NULL; // Insert in queue. if (_head == NULL) { // Queue was empty, element is entire queue. _head = _tail = element; element->next = NULL; assert(_elemCount == 0); _elemCount = 1; } else { assert(_tail != NULL); assert(_tail->next == NULL); assert(_elemCount != 0); _tail->next = element; _tail = element; ++_elemCount; } success = YES; FAIL: [_lock unlockWithCondition:kConditionQueuedData]; return success; } - (id)dequeue { [_lock lockWhenCondition:kConditionQueuedData]; return [self doDequeAndUnlockWithAcquiredLock]; } - (id)tryDequeue { #if OO_BUGGY_PTHREADS /* pthread_mutex_trylock is buggy on 64-bit windows with the pthread * library in use, so avoid doing things which use it This is a little * more blocking, but no thread should be hanging on to _lock for very * long, so hopefully it won't be noticeable. */ [_lock lock]; if ([_lock condition] != kConditionQueuedData) { [_lock unlock]; return NO; } #else // Mac and Linux can do it properly if (![_lock tryLockWhenCondition:kConditionQueuedData]) return NO; #endif return [self doDequeAndUnlockWithAcquiredLock]; } - (BOOL)empty { return _head != NULL; } - (unsigned)count { return _elemCount; } - (void)emptyQueue { [_lock lock]; [self doEmptyQueueWithAcquiredLock]; assert(_head == NULL && _tail == NULL && _elemCount == 0); [_lock unlockWithCondition:kConditionNoData]; } @end @implementation OOAsyncQueue (OOPrivate) - (void)doEmptyQueueWithAcquiredLock { OOAsyncQueueElement *element = NULL; // Loop over queue. while (_head != NULL) { // Dequeue element. element = _head; _head = _head->next; --_elemCount; // We don't need the payload any longer. [element->object release]; // Or the element. [self recycleElementWithAcquiredLock:element]; } _tail = NULL; } - (id)doDequeAndUnlockWithAcquiredLock { OOAsyncQueueElement *element = NULL; id result; if (_head == NULL) { // Can happen if you enter debugger. return nil; } // assert(_head != NULL); // Dequeue element. element = _head; _head = _head->next; if (_head == NULL) _tail = NULL; --_elemCount; // Unpack payload. result = [element->object autorelease]; // Recycle element. [self recycleElementWithAcquiredLock:element]; // Ensure sane status. assert((_head == NULL && _tail == NULL && _elemCount == 0) || (_head != NULL && _tail != NULL && _elemCount != 0)); // Unlock with appropriate state. [_lock unlockWithCondition:(_head == NULL) ? kConditionNoData : kConditionQueuedData]; return result; } - (void)recycleElementWithAcquiredLock:(OOAsyncQueueElement *)element { if (_poolCount < kMaxPoolElements) { // Add to pool for reuse. element->next = _pool; _pool = element; ++_poolCount; } else { // Delete. FreeElement(element); } } @end oolite-1.82/src/Core/OOAsyncWorkManager.h000066400000000000000000000041541256642440500202540ustar00rootroot00000000000000/* OOAsyncWorkManager.h Simple thread pool/work unit manager. Copyright (C) 2009-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" @class OOAsyncQueue; @protocol OOAsyncWorkTask; typedef enum { kOOAsyncPriorityLow, kOOAsyncPriorityMedium, kOOAsyncPriorityHigh } OOAsyncWorkPriority; @interface OOAsyncWorkManager: NSObject + (OOAsyncWorkManager *) sharedAsyncWorkManager; - (BOOL) addTask:(id)task priority:(OOAsyncWorkPriority)priority; /* Complete any tasks whose asynchronous portion is ready, but without waiting. */ - (void) completePendingTasks; /* Wait for a task to complete. WARNING: if task is not an existing task, or does not implement -completeAsyncTask, this will never return. IMPORTANT: May only be called on the main thread. */ - (void) waitForTaskToComplete:(id)task; @end @protocol OOAsyncWorkTask // Called on a worker thread. There may be multiple worker threads. - (void) performAsyncTask; // @optional OOLITE_OPTIONAL(OOAsyncWorkTask) /* Called on main thread some time after -performAsyncTask completes. */ - (void) completeAsyncTask; @end oolite-1.82/src/Core/OOAsyncWorkManager.m000066400000000000000000000223721256642440500202630ustar00rootroot00000000000000/* OOAsyncWorkManager.m Copyright (C) 2009-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOAsyncWorkManager.h" #import "OOAsyncQueue.h" #import "OOCPUInfo.h" #import "OOCollectionExtractors.h" #import "NSThreadOOExtensions.h" #import "OONSOperation.h" #define USE_PTHREAD_ONCE (!OOLITE_WINDOWS) #if USE_PTHREAD_ONCE #include #endif static OOAsyncWorkManager *sSingleton = nil; @interface NSThread (MethodsThatMayExistDependingOnSystem) - (BOOL) isMainThread; + (BOOL) isMainThread; @end /* OOAsyncWorkManagerInternal: shared superclass of our two implementations, which implements shared functionality but is not itself concrete. */ @interface OOAsyncWorkManagerInternal: OOAsyncWorkManager { @private OOAsyncQueue *_readyQueue; NSMutableSet *_pendingCompletableOperations; NSLock *_pendingOpsLock; } - (void) queueResult:(id)task; - (void) noteTaskQueued:(id)task; @end #if !OO_HAVE_NSOPERATION @interface OOManualDispatchAsyncWorkManager: OOAsyncWorkManagerInternal { @private OOAsyncQueue *_taskQueue; } - (void) queueTask:(NSNumber *)threadNumber; @end #endif @interface OOOperationQueueAsyncWorkManager: OOAsyncWorkManagerInternal { @private OONSOperationQueue _operationQueue; } #if !OO_HAVE_NSOPERATION + (BOOL) canBeUsed; #endif - (void) dispatchTask:(id)task; @end #if !USE_PTHREAD_ONCE static NSLock *sInitLock = nil; #endif static void InitAsyncWorkManager(void) { NSCAssert(sSingleton == nil, @"Async Work Manager singleton not nil in one-time init"); #if !OO_HAVE_NSOPERATION if ([OOOperationQueueAsyncWorkManager canBeUsed]) { sSingleton = [[OOOperationQueueAsyncWorkManager alloc] init]; } if (sSingleton == nil) { sSingleton = [[OOManualDispatchAsyncWorkManager alloc] init]; } #else sSingleton = [[OOOperationQueueAsyncWorkManager alloc] init]; #endif if (sSingleton == nil) { OOLog(@"asyncWorkManager.setUpDispatcher.failed", @"***** FATAL ERROR: could not set up async work manager!"); exit(EXIT_FAILURE); } OOLog(@"asyncWorkManager.dispatchMethod", @"Selected async work manager: %@", [sSingleton class]); } @implementation OOAsyncWorkManager #if !USE_PTHREAD_ONCE + (void) initialize { if (sInitLock == nil) { sInitLock = [[NSLock alloc] init]; NSAssert(sInitLock != nil, @"Async Work Manager init failed"); } } #endif + (OOAsyncWorkManager *) sharedAsyncWorkManager { #if USE_PTHREAD_ONCE static pthread_once_t once = PTHREAD_ONCE_INIT; pthread_once(&once, InitAsyncWorkManager); NSAssert(sSingleton != nil, @"Async Work Manager init failed"); #else [sInitLock lock]; if (sSingleton == nil) { InitAsyncWorkManager(); NSAssert(sSingleton != nil, @"Async Work Manager init failed"); } [sInitLock unlock]; #endif return sSingleton; } + (id) allocWithZone:(NSZone *)inZone { if (sSingleton == nil) { sSingleton = [super allocWithZone:inZone]; return sSingleton; } return nil; } - (void) dealloc { abort(); [super dealloc]; } - (oneway void) release {} - (id) retain { return self; } - (NSUInteger) retainCount { return UINT_MAX; } - (BOOL) addTask:(id)task priority:(OOAsyncWorkPriority)priority { OOLogGenericSubclassResponsibility(); return NO; } - (void) completePendingTasks { OOLogGenericSubclassResponsibility(); } - (void) waitForTaskToComplete:(id)task { OOLogGenericSubclassResponsibility(); [NSException raise:NSInternalInconsistencyException format:@"%s called.", __PRETTY_FUNCTION__]; } @end @implementation OOAsyncWorkManagerInternal - (id) init { if ((self = [super init])) { _readyQueue = [[OOAsyncQueue alloc] init]; if (_readyQueue == nil) { [self release]; return nil; } _pendingCompletableOperations = [[NSMutableSet alloc] init]; _pendingOpsLock = [[NSLock alloc] init]; if (_pendingCompletableOperations == nil || _pendingOpsLock == nil) { [self release]; return nil; } } return self; } - (void) completePendingTasks { id next = nil; [_pendingOpsLock lock]; for (;;) { next = [_readyQueue tryDequeue]; if (next == nil) break; [_pendingCompletableOperations removeObject:next]; [next completeAsyncTask]; } [_pendingOpsLock unlock]; } - (void) waitForTaskToComplete:(id)task { if (task == nil) return; #if OO_DEBUG NSParameterAssert([(id)task respondsToSelector:@selector(completeAsyncTask)]); NSAssert1(![NSThread respondsToSelector:@selector(isMainThread)] || [[NSThread self] isMainThread], @"%s can only be called from the main thread.", __PRETTY_FUNCTION__); #endif [_pendingOpsLock lock]; BOOL exists = [_pendingCompletableOperations containsObject:task]; if (exists) [_pendingCompletableOperations removeObject:task]; [_pendingOpsLock unlock]; if (!exists) return; id next = nil; do { // Dequeue a task and complete it. next = [_readyQueue dequeue]; [_pendingOpsLock lock]; [_pendingCompletableOperations removeObject:next]; [_pendingOpsLock unlock]; [next completeAsyncTask]; } while (next != task); // We don't control order, so keep looking until we get the one we care about. } - (void) queueResult:(id)task { if ([task respondsToSelector:@selector(completeAsyncTask)]) { [_readyQueue enqueue:task]; } } - (void) noteTaskQueued:(id)task { [_pendingOpsLock lock]; [_pendingCompletableOperations addObject:task]; [_pendingOpsLock unlock]; } @end /******* OOManualDispatchAsyncWorkManager - manual thread management *******/ enum { kMaxWorkThreads = 8 }; #if !OO_HAVE_NSOPERATION @implementation OOManualDispatchAsyncWorkManager - (id) init { if ((self = [super init])) { // Set up work queue. _taskQueue = [[OOAsyncQueue alloc] init]; if (_taskQueue == nil) { [self release]; return nil; } // Set up loading threads. NSUInteger threadCount, threadNumber = 1; #if OO_DEBUG threadCount = kMaxWorkThreads; #else threadCount = MIN(OOCPUCount(), (unsigned)kMaxWorkThreads); #endif do { [NSThread detachNewThreadSelector:@selector(queueTask:) toTarget:self withObject:[NSNumber numberWithInt:threadNumber++]]; } while (--threadCount > 0); } return self; } - (BOOL) addTask:(id)task priority:(OOAsyncWorkPriority)priority { if (EXPECT_NOT(task == nil)) return NO; [super noteTaskQueued:task]; // Priority is ignored. return [_taskQueue enqueue:task]; } - (void) queueTask:(NSNumber *)threadNumber { NSAutoreleasePool *rootPool = nil, *pool = nil; rootPool = [[NSAutoreleasePool alloc] init]; [NSThread setThreadPriority:0.5]; [NSThread ooSetCurrentThreadName:[NSString stringWithFormat:@"OOAsyncWorkManager thread %@", threadNumber]]; for (;;) { pool = [[NSAutoreleasePool alloc] init]; id task = [_taskQueue dequeue]; @try { [task performAsyncTask]; } @catch (id exception) {} [self queueResult:task]; [pool release]; } [rootPool release]; } @end #endif /******* OOOperationQueueAsyncWorkManager - dispatch through NSOperationQueue if available *******/ @implementation OOOperationQueueAsyncWorkManager #if !OO_HAVE_NSOPERATION + (BOOL) canBeUsed { if ([[NSUserDefaults standardUserDefaults] boolForKey:@"disable-operation-queue-work-manager"]) return NO; return [OONSInvocationOperationClass() class] != Nil; } #endif - (id) init { if ((self = [super init])) { _operationQueue = [[OONSOperationQueueClass() alloc] init]; if (_operationQueue == nil) { [self release]; return nil; } } return self; } - (void) dealloc { [_operationQueue release]; [super dealloc]; } - (BOOL) addTask:(id)task priority:(OOAsyncWorkPriority)priority { if (EXPECT_NOT(task == nil)) return NO; id operation = [[OONSInvocationOperationClass() alloc] initWithTarget:self selector:@selector(dispatchTask:) object:task]; if (operation == nil) return NO; if (priority == kOOAsyncPriorityLow) [operation setQueuePriority:OONSOperationQueuePriorityLow]; else if (priority == kOOAsyncPriorityHigh) [operation setQueuePriority:OONSOperationQueuePriorityHigh]; [_operationQueue addOperation:operation]; [operation release]; [super noteTaskQueued:task]; return YES; } - (void) dispatchTask:(id)task { @try { [task performAsyncTask]; } @catch (id exception) {} [self queueResult:task]; } @end oolite-1.82/src/Core/OOBasicSoundReferencePoint.h000066400000000000000000000022441256642440500217220ustar00rootroot00000000000000/* OOBasicSoundReferencePoint.h No-op implementation of OOSoundReferencePoint; see OOSound.h for information about sound architecture. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #import "OOMaths.h" @interface OOSoundReferencePoint: NSObject // Positional audio attributes are ignored in this implementation - (void)setPosition:(Vector)inPosition; - (void)setVelocity:(Vector)inVelocity; - (void)setOrientation:(Vector)inOrientation; @end oolite-1.82/src/Core/OOBasicSoundReferencePoint.m000066400000000000000000000017701256642440500217320ustar00rootroot00000000000000/* OOBasicSoundReferencePoint.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOBasicSoundReferencePoint.h" @implementation OOSoundReferencePoint - (void)setPosition:(Vector)inPosition { } - (void)setVelocity:(Vector)inVelocity { } - (void)setOrientation:(Vector)inOrientation { } @end oolite-1.82/src/Core/OOBoundingBox.h000066400000000000000000000063111256642440500172540ustar00rootroot00000000000000/* OOBoundingBox.h Mathematical framework for Oolite. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef INCLUDED_OOMATHS_h #error Do not include OOBoundingBox.h directly; include OOMaths.h. #else typedef struct { Vector min; Vector max; } BoundingBox; extern const BoundingBox kZeroBoundingBox; /* (0, 0, 0), (0, 0, 0) */ /* Extend bounding box to contain specified point. */ OOINLINE void bounding_box_add_vector(BoundingBox *box, Vector vec) ALWAYS_INLINE_FUNC NONNULL_FUNC; OOINLINE void bounding_box_add_xyz(BoundingBox *box, GLfloat x, GLfloat y, GLfloat z) ALWAYS_INLINE_FUNC NONNULL_FUNC; /* Reset bounding box to kZeroBoundingBox. */ OOINLINE void bounding_box_reset(BoundingBox *box) NONNULL_FUNC; /* Reset bounding box to a zero-sized box surrounding specified vector. */ OOINLINE void bounding_box_reset_to_vector(BoundingBox *box, Vector vec) ALWAYS_INLINE_FUNC NONNULL_FUNC; OOINLINE void bounding_box_get_dimensions(BoundingBox bb, GLfloat *xSize, GLfloat *ySize, GLfloat *zSize) ALWAYS_INLINE_FUNC; OOINLINE Vector OOBoundingBoxCenter(BoundingBox bb) INLINE_CONST_FUNC; Vector OORandomPositionInBoundingBox(BoundingBox bb); HPVector OOHPRandomPositionInBoundingBox(BoundingBox bb); /*** Only inline definitions beyond this point ***/ OOINLINE void bounding_box_add_vector(BoundingBox *box, Vector vec) { assert(box != NULL); box->min.x = fmin(box->min.x, vec.x); box->max.x = fmax(box->max.x, vec.x); box->min.y = fmin(box->min.y, vec.y); box->max.y = fmax(box->max.y, vec.y); box->min.z = fmin(box->min.z, vec.z); box->max.z = fmax(box->max.z, vec.z); } OOINLINE void bounding_box_add_xyz(BoundingBox *box, GLfloat x, GLfloat y, GLfloat z) { assert(box != NULL); box->min.x = fmin(box->min.x, x); box->max.x = fmax(box->max.x, x); box->min.y = fmin(box->min.y, y); box->max.y = fmax(box->max.y, y); box->min.z = fmin(box->min.z, z); box->max.z = fmax(box->max.z, z); } OOINLINE void bounding_box_reset(BoundingBox *box) { assert(box != NULL); *box = kZeroBoundingBox; } OOINLINE void bounding_box_reset_to_vector(BoundingBox *box, Vector vec) { assert(box != NULL); box->min = vec; box->max = vec; } OOINLINE void bounding_box_get_dimensions(BoundingBox bb, GLfloat *xSize, GLfloat *ySize, GLfloat *zSize) { if (xSize != NULL) *xSize = bb.max.x - bb.min.x; if (ySize != NULL) *ySize = bb.max.y - bb.min.y; if (zSize != NULL) *zSize = bb.max.z - bb.min.z; } OOINLINE Vector OOBoundingBoxCenter(BoundingBox bb) { return vector_multiply_scalar(vector_add(bb.min, bb.max), 0.5f); } #endif oolite-1.82/src/Core/OOCPUInfo.h000066400000000000000000000062371256642440500163100ustar00rootroot00000000000000/* OOCPUInfo.h Capabilities and features of CPUs. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #include void OOCPUInfoInit(void); /* Number of processors (whether they be individual or cores), used to select number of threads to use for things like texture loading. */ NSUInteger OOCPUCount(void); #if OOLITE_WINDOWS typedef BOOL (WINAPI *IW64PFP)(HANDLE, BOOL *); // for checking for 64/32 bit system BOOL is64BitSystem(void); NSString* operatingSystemFullVersion(void); #endif /* Set up OOLITE_BIG_ENDIAN and OOLITE_LITTLE_ENDIAN macros. Exactly one must be non-zero. If you're porting Oolite to a middle-endian platform, you'll need to work out what to do with endian-sensitive stuff -- currently, that means texture loading. (The data cache automatically rejects cached data of the wrong byte sex.) */ #if !defined(OOLITE_BIG_ENDIAN) && !defined(OOLITE_LITTLE_ENDIAN) #if __BIG_ENDIAN__ #define OOLITE_BIG_ENDIAN 1 #endif #if __LITTLE_ENDIAN__ #define OOLITE_LITTLE_ENDIAN 1 #endif #if !defined(OOLITE_BIG_ENDIAN) && !defined(OOLITE_LITTLE_ENDIAN) #if defined(__i386__) || defined(__amd64__) || defined(__x86_64__) #define OOLITE_LITTLE_ENDIAN 1 #endif #if defined(__sgi__) || defined(__mips__) #define OOLITE_BIG_ENDIAN 1 #endif // Do not assume PPC == big endian, it can be either. #endif // inner none defined #endif // outer none defined #ifndef OOLITE_BIG_ENDIAN #define OOLITE_BIG_ENDIAN 0 #endif #ifndef OOLITE_LITTLE_ENDIAN #define OOLITE_LITTLE_ENDIAN 0 #endif #if !OOLITE_BIG_ENDIAN && !OOLITE_LITTLE_ENDIAN #error Neither OOLITE_BIG_ENDIAN nor OOLITE_LITTLE_ENDIAN is defined as nonzero! #undef OOLITE_BIG_ENDIAN #undef OOLITE_LITTLE_ENDIAN // Cause errors where the macros are used #define OOLITE_BIG_ENDIAN "BUG" #define OOLITE_LITTLE_ENDIAN "BUG" #endif /* Set up OOLITE_NATIVE_64_BIT. This is intended for 64-bit optimizations (see OOTextureScaling.m). It is not set for systems where 64-bitness may be determined at runtime (such as 32-bit OS X binaries), because I can't be bothered to do the set-up required to use switch to a 64-bit code path at runtime while being cross-platform. -- Ahruman */ #ifndef OOLITE_NATIVE_64_BIT #ifdef _UINT64_T #ifdef __ppc64__ #define OOLITE_NATIVE_64_BIT 1 #elif __amd64__ #define OOLITE_NATIVE_64_BIT 1 #elif __x86_64__ #define OOLITE_NATIVE_64_BIT 1 #endif #endif // _UINT64_T #ifndef OOLITE_NATIVE_64_BIT #define OOLITE_NATIVE_64_BIT 0 #endif #endif // defined(OOLITE_NATIVE_64_BIT) oolite-1.82/src/Core/OOCPUInfo.m000066400000000000000000000064731256642440500163170ustar00rootroot00000000000000/* OOCPUInfo.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCPUInfo.h" #include #if OOLITE_MAC_OS_X #include #elif (OOLITE_LINUX || OOLITE_WINDOWS) // Workaround for clang/glibc incompatibility. #define __block __glibc_block #include #undef __block #endif #if 0 // Confirm settings #if OOLITE_BIG_ENDIAN #warning Big-endian. #endif #if OOLITE_LITTLE_ENDIAN #warning Little-endian. #endif #if OOLITE_NATIVE_64_BIT #warning 64-bit. #else #warning 32-bit. #endif #endif static BOOL sInited = NO; static NSUInteger sNumberOfCPUs = 0; // Yes, really 0. void OOCPUInfoInit(void) { if (sInited) return; // Verify correctness of endian macros uint8_t endianTag[4] = {0x12, 0x34, 0x56, 0x78}; #if OOLITE_BIG_ENDIAN if (*(uint32_t*)endianTag != 0x12345678) { OOLog(@"cpuInfo.endianTest.failed", @"OOLITE_BIG_ENDIAN is set, but the system is not big-endian -- aborting."); exit(EXIT_FAILURE); } #endif #if OOLITE_LITTLE_ENDIAN if (*(uint32_t*)endianTag != 0x78563412) { OOLog(@"cpuInfo.endianTest.failed", @"OOLITE_LITTLE_ENDIAN is set, but the system is not little-endian -- aborting."); exit(EXIT_FAILURE); } #endif // Count processors #if OOLITE_MAC_OS_X sNumberOfCPUs = [[NSProcessInfo processInfo] processorCount]; #elif OOLITE_WINDOWS SYSTEM_INFO sysInfo; GetSystemInfo(&sysInfo); sNumberOfCPUs = sysInfo.dwNumberOfProcessors; #elif defined _SC_NPROCESSORS_ONLN sNumberOfCPUs = sysconf(_SC_NPROCESSORS_ONLN); #else #warning Do not know how to find number of CPUs on this architecture. #endif // OS selection sInited = YES; } NSUInteger OOCPUCount(void) { if (!sInited) OOCPUInfoInit(); return (sNumberOfCPUs != 0) ? sNumberOfCPUs : 1; } #if OOLITE_WINDOWS NSString* operatingSystemFullVersion(void) { OSVERSIONINFOW osver; osver.dwOSVersionInfoSize = sizeof(osver); GetVersionExW (&osver); return [NSString stringWithFormat:@"%d.%d.%d %S", osver.dwMajorVersion, osver.dwMinorVersion, osver.dwBuildNumber, osver.szCSDVersion]; } /* is64bitSystem: Detect operating system bitness. This function is based mainly on code by Mark S. Kolich, as seen in http://mark.koli.ch/2009/10/reliably-checking-os-bitness-32-or-64-bit-on-windows-with-a-tiny-c-app.html */ BOOL is64BitSystem(void) { #if defined(_WIN64) // if we have been compiled as a 64-bit app and we are running, we are obviously on a 64-bit system return YES; #else BOOL is64Bit = NO; IW64PFP IW64P = (IW64PFP)GetProcAddress(GetModuleHandle("kernel32"), "IsWow64Process"); if(IW64P != NULL) { IW64P(GetCurrentProcess(), &is64Bit); } return is64Bit; #endif } #endif // OOLITE_WINDOWS oolite-1.82/src/Core/OOCache.h000066400000000000000000000050251256642440500160420ustar00rootroot00000000000000/* OOCache.h By Jens Ayton An OOCache handles storage of a limited number of elements for quick reuse. It may be used directly for in-memory cache, or indirectly through OOCacheManager for on-disk cache. Every OOCache has a 'prune threshold', which controls how many elements it contains, and an 'auto-prune' flag, which determines how pruning is managed. If auto-pruning is on, the cache will pruned to 80% of the prune threshold whenever the prune threshold is exceeded. If auto-pruning is off, the cache can be pruned to the prune threshold by explicitly calling -prune. While OOCacheManager-managed caches must have string keys and property list values, OOCaches used directly may have any keys allowable for a mutable dictionary (that is, keys should conform to and values may be arbitrary objects) -- an 'unmanaged' cache is essentially a mutable dictionary with a prune limit. (Project: with the addition of a -keyEnumerator method and sutiable NSEnumerator subclass, and a -count method, it could be turned into a subclass of NSMutableDictionary.) Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import enum { kOOCacheMinimumPruneThreshold = 25U, kOOCacheDefaultPruneThreshold = 200U, kOOCacheNoPrune = 0xFFFFFFFFU }; @interface OOCache: NSObject { @private struct OOCacheImpl *cache; unsigned pruneThreshold; BOOL autoPrune; BOOL dirty; } - (id)init; - (id)initWithPList:(id)pList; - (id)pListRepresentation; - (id)objectForKey:(id)key; - (void)setObject:(id)value forKey:(id)key; - (void)removeObjectForKey:(id)key; - (void)setPruneThreshold:(unsigned)threshold; - (unsigned)pruneThreshold; - (void)setAutoPrune:(BOOL)flag; - (BOOL)autoPrune; - (void)prune; - (BOOL)dirty; - (void)markClean; - (NSString *)name; - (void)setName:(NSString *)name; - (NSArray *) objectsByAge; @end oolite-1.82/src/Core/OOCache.m000066400000000000000000000706241256642440500160560ustar00rootroot00000000000000/* OOCache.m By Jens Ayton Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* IMPLEMENTATION NOTES A cache needs to be able to implement three types of operation efficiently: * Retrieving: looking up an element by key. * Inserting: setting the element associated with a key. * Deleting: removing a single element. * Pruning: removing one or more least-recently used elements. An NSMutableDictionary performs the first three operations efficiently but has no support for pruning - specifically no support for finding the least-recently-accessed element. Using standard Foundation containers, it would be necessary to use several dictionaries and arrays, which would be quite inefficient since small NSArrays aren’t very good at head insertion or deletion. Alternatively, a standard dictionary whose value objects maintain an age-sorted list could be used. I chose instead to implement a custom scheme from scratch. It uses two parallel data structures: a doubly-linked list sorted by age, and a splay tree to implement insertion/deletion. The implementation is largely procedural C. Deserialization, pruning and modification tracking is done in the ObjC class; everything else is done in C functions. A SPLAY TREE is a type of semi-balanced binary search tree with certain useful properties: * Simplicity. All look-up and restructuring operations are based on a single operation, splaying, which brings the node with the desired key (or the node whose key is "left" of the desired key, if there is no exact match) to the root, while maintaining the binary search tree invariant. Splaying itself is sufficient for look-up; insertion and deletion work by splaying and then manipulating at the root. * Self-optimization. Because each look-up brings the sought element to the root, often-used elements tend to stay near the top. (Oolite often performs sequences of identical look-ups, for instance when creating an asteroid field, or the racing ring set-up which uses lots of identical ring segments; during combat, missiles, canisters and hull plates will be commonly used.) Also, this means that for a retrieve- attempt/insert sequence, the retrieve attempt will optimize the tree for the insertion. * Efficiency. In addition to the self-optimization, splay trees have a small code size and no storage overhead for flags. The amortized worst-case cost of splaying (cost averaged over a worst-case sequence of operations) is O(log n); a single worst-case splay is O(n), but that worst-case also improves the balance of the tree, so you can't have two worst cases in a row. Insertion and deletion are also O(log n), consisting of a splay plus an O(1) operation. References for splay trees: * http://www.cs.cmu.edu/~sleator/papers/self-adjusting.pdf Original research paper. * http://www.ibr.cs.tu-bs.de/courses/ss98/audii/applets/BST/SplayTree-Example.html Java applet demonstrating splaying. * http://www.link.cs.cmu.edu/link/ftp-site/splaying/top-down-splay.c Sample implementation by one of the inventors. The TreeSplay(), TreeInsert() and CacheRemove() functions are based on this. The AGE LIST is a doubly-linked list, ordered from oldest to youngest. Whenever an element is retrieved or inserted, it is promoted to the youngest end of the age list. Pruning proceeds from the oldest end of the age list. if (autoPrune) { PRUNING is batched, handling 20% of the cache at once. This is primarily because deletion somewhat pessimizes the tree (see "Self-optimization" below). It also provides a bit of code coherency. To reduce pruning batches while in flight, pruning is also performed before serialization (which in turn is done, if the cache has changed, whenever the user docks). This has the effect that the number of items in the cache on disk never exceeds 80% of the prune threshold. This is probably not actually poinful, since pruning should be a very small portion of the per-frame run time in any case. Premature optimization and all that jazz. Pruning performs at most 0.2n deletions, and is thus O(n log n). } else { PRUNING is performed manually by calling -prune. } If the macro OOCACHE_PERFORM_INTEGRITY_CHECKS is set to a non-zero value, the integrity of the tree and the age list will be checked before and after each high-level operation. This is an inherently O(n) operation. */ #import "OOCache.h" #import "OOStringParsing.h" #ifndef OOCACHE_PERFORM_INTEGRITY_CHECKS #define OOCACHE_PERFORM_INTEGRITY_CHECKS 0 #endif // Protocol used internally to squash idiotic warnings in gnu-gcc. @protocol OOCacheComparable - (NSComparisonResult) compare:(id)other; - (id) copy; @end typedef struct OOCacheImpl OOCacheImpl; typedef struct OOCacheNode OOCacheNode; enum { kCountUnknown = -1U }; static NSString * const kSerializedEntryKeyKey = @"key"; static NSString * const kSerializedEntryKeyValue = @"value"; static OOCacheImpl *CacheAllocate(void); static void CacheFree(OOCacheImpl *cache); static BOOL CacheInsert(OOCacheImpl *cache, id key, id value); static BOOL CacheRemove(OOCacheImpl *cache, id key); static BOOL CacheRemoveOldest(OOCacheImpl *cache, NSString *logKey); static id CacheRetrieve(OOCacheImpl *cache, id key); static unsigned CacheGetCount(OOCacheImpl *cache); static NSArray *CacheArrayOfContentsByAge(OOCacheImpl *cache); static NSArray *CacheArrayOfNodesByAge(OOCacheImpl *cache); static NSString *CacheGetName(OOCacheImpl *cache); static void CacheSetName(OOCacheImpl *cache, NSString *name); #if OOCACHE_PERFORM_INTEGRITY_CHECKS static NSString * const kOOLogCacheIntegrityCheck = @"dataCache.integrityCheck"; static void CacheCheckIntegrity(OOCacheImpl *cache, NSString *context); #define CHECK_INTEGRITY(context) CacheCheckIntegrity(cache, (context)) #else #define CHECK_INTEGRITY(context) do {} while (0) #endif @interface OOCache (Private) - (void)loadFromArray:(NSArray *)inArray; @end @implementation OOCache - (void)dealloc { CHECK_INTEGRITY(@"dealloc"); CacheFree(cache); [super dealloc]; } - (NSString *)description { return [NSString stringWithFormat:@"<%@ %p>{\"%@\", %u elements, prune threshold=%u, auto-prune=%s dirty=%s}", [self class], self, [self name], CacheGetCount(cache), pruneThreshold, autoPrune ? "yes" : "no", dirty ? "yes" : "no"]; } - (id)init { return [self initWithPList:nil]; } - (id)initWithPList:(id)pList { BOOL OK = YES; self = [super init]; OK = self != nil; if (OK) { cache = CacheAllocate(); if (cache == NULL) OK = NO; } if (pList != nil) { if (OK) OK = [pList isKindOfClass:[NSArray class]]; if (OK) [self loadFromArray:pList]; } if (OK) { pruneThreshold = kOOCacheDefaultPruneThreshold; autoPrune = YES; } if (!OK) { [self release]; self = nil; } return self; } - (id)pListRepresentation { return CacheArrayOfNodesByAge(cache); return nil; } - (id)objectForKey:(id)key { id result = nil; CHECK_INTEGRITY(@"objectForKey: before"); result = CacheRetrieve(cache, key); // Note: while reordering the age list technically makes the cache dirty, it's not worth rewriting it just for that, so we don't flag it. CHECK_INTEGRITY(@"objectForKey: after"); return [[result retain] autorelease]; } - (void)setObject:inObject forKey:(id)key { CHECK_INTEGRITY(@"setObject:forKey: before"); if (CacheInsert(cache, key, inObject)) { dirty = YES; if (autoPrune) [self prune]; } CHECK_INTEGRITY(@"setObject:forKey: after"); } - (void)removeObjectForKey:(id)key { CHECK_INTEGRITY(@"removeObjectForKey: before"); if (CacheRemove(cache, key)) dirty = YES; CHECK_INTEGRITY(@"removeObjectForKey: after"); } - (void)setPruneThreshold:(unsigned)threshold { threshold = MAX(threshold, (unsigned)kOOCacheMinimumPruneThreshold); if (threshold != pruneThreshold) { pruneThreshold = threshold; if (autoPrune) [self prune]; } } - (unsigned)pruneThreshold { return pruneThreshold; } - (void)setAutoPrune:(BOOL)flag { BOOL prune = (flag != NO); if (prune != autoPrune) { autoPrune = prune; [self prune]; } } - (BOOL)autoPrune { return autoPrune; } - (void)prune { unsigned pruneCount; unsigned desiredCount; unsigned count; // Order of operations is to ensure rounding down. if (autoPrune) desiredCount = (pruneThreshold * 4) / 5; else desiredCount = pruneThreshold; if (pruneThreshold == kOOCacheNoPrune || (count = CacheGetCount(cache)) <= pruneThreshold) return; pruneCount = count - desiredCount; NSString *logKey = [NSString stringWithFormat:@"dataCache.prune.%@", CacheGetName(cache)]; OOLog(logKey, @"Pruning cache \"%@\" - removing %u entries", CacheGetName(cache), pruneCount); OOLogIndentIf(logKey); while (pruneCount--) CacheRemoveOldest(cache, logKey); OOLogOutdentIf(logKey); } - (BOOL)dirty { return dirty; } - (void)markClean { dirty = NO; } - (NSString *)name { return CacheGetName(cache); } - (void)setName:(NSString *)name { CacheSetName(cache, name); } - (NSArray *) objectsByAge { return CacheArrayOfContentsByAge(cache); } @end @implementation OOCache (Private) - (void)loadFromArray:(NSArray *)array { NSEnumerator *entryEnum = nil; NSDictionary *entry = nil; NSString *key = nil; id value = nil; if (array == nil) return; for (entryEnum = [array objectEnumerator]; (entry = [entryEnum nextObject]); ) { if ([entry isKindOfClass:[NSDictionary class]]) { key = [entry objectForKey:kSerializedEntryKeyKey]; value = [entry objectForKey:kSerializedEntryKeyValue]; if ([key isKindOfClass:[NSString class]] && value != nil) { [self setObject:value forKey:key]; } } } } @end /***** Most of the implementation. In C. Because I'm inconsistent and slightly m. *****/ struct OOCacheImpl { // Splay tree root OOCacheNode *root; // Ends of age list OOCacheNode *oldest, *youngest; unsigned count; NSString *name; }; struct OOCacheNode { // Payload id key; id value; // Splay tree OOCacheNode *leftChild, *rightChild; // Age list OOCacheNode *younger, *older; }; static OOCacheNode *CacheNodeAllocate(id key, id value); static void CacheNodeFree(OOCacheImpl *cache, OOCacheNode *node); static id CacheNodeGetValue(OOCacheNode *node); static void CacheNodeSetValue(OOCacheNode *node, id value); #if OOCACHE_PERFORM_INTEGRITY_CHECKS static NSString *CacheNodeGetDescription(OOCacheNode *node); #endif static OOCacheNode *TreeSplay(OOCacheNode **root, id key); static OOCacheNode *TreeInsert(OOCacheImpl *cache, id key, id value); #if OOCACHE_PERFORM_INTEGRITY_CHECKS static unsigned TreeCountNodes(OOCacheNode *node); static OOCacheNode *TreeCheckIntegrity(OOCacheImpl *cache, OOCacheNode *node, OOCacheNode *expectedParent, NSString *context); #endif static void AgeListMakeYoungest(OOCacheImpl *cache, OOCacheNode *node); static void AgeListRemove(OOCacheImpl *cache, OOCacheNode *node); #if OOCACHE_PERFORM_INTEGRITY_CHECKS static void AgeListCheckIntegrity(OOCacheImpl *cache, NSString *context); #endif /***** CacheImpl functions *****/ static OOCacheImpl *CacheAllocate(void) { return calloc(sizeof (OOCacheImpl), 1); } static void CacheFree(OOCacheImpl *cache) { if (cache == NULL) return; CacheNodeFree(cache, cache->root); [cache->name autorelease]; free(cache); } static BOOL CacheInsert(OOCacheImpl *cache, id key, id value) { OOCacheNode *node = NULL; if (cache == NULL || key == nil || value == nil) return NO; node = TreeInsert(cache, key, value); if (node != NULL) { AgeListMakeYoungest(cache, node); return YES; } else return NO; } static BOOL CacheRemove(OOCacheImpl *cache, id key) { OOCacheNode *node = NULL, *newRoot = NULL; node = TreeSplay(&cache->root, key); if (node != NULL) { if (node->leftChild == NULL) newRoot = node->rightChild; else { newRoot = node->leftChild; TreeSplay(&newRoot, key); newRoot->rightChild = node->rightChild; } node->leftChild = NULL; node->rightChild = NULL; cache->root = newRoot; --cache->count; AgeListRemove(cache, node); CacheNodeFree(cache, node); return YES; } else return NO; } static BOOL CacheRemoveOldest(OOCacheImpl *cache, NSString *logKey) { // This could be more efficient, but does it need to be? if (cache == NULL || cache->oldest == NULL) return NO; OOLog(logKey, @"Pruning cache \"%@\": removing %@", cache->name, cache->oldest->key); return CacheRemove(cache, cache->oldest->key); } static id CacheRetrieve(OOCacheImpl *cache, id key) { OOCacheNode *node = NULL; id result = nil; if (cache == NULL || key == NULL) return nil; node = TreeSplay(&cache->root, key); if (node != NULL) { result = CacheNodeGetValue(node); AgeListMakeYoungest(cache, node); } return result; } static NSArray *CacheArrayOfContentsByAge(OOCacheImpl *cache) { OOCacheNode *node = NULL; NSMutableArray *result = nil; if (cache == NULL || cache->count == 0) return nil; result = [NSMutableArray arrayWithCapacity:cache->count]; for (node = cache->youngest; node != NULL; node = node->older) { [result addObject:node->value]; } return result; } static NSArray *CacheArrayOfNodesByAge(OOCacheImpl *cache) { OOCacheNode *node = NULL; NSMutableArray *result = nil; if (cache == NULL || cache->count == 0) return nil; result = [NSMutableArray arrayWithCapacity:cache->count]; for (node = cache->oldest; node != NULL; node = node->younger) { [result addObject:[NSDictionary dictionaryWithObjectsAndKeys:node->key, kSerializedEntryKeyKey, node->value, kSerializedEntryKeyValue, nil]]; } return result; } static NSString *CacheGetName(OOCacheImpl *cache) { return cache->name; } static void CacheSetName(OOCacheImpl *cache, NSString *name) { [cache->name autorelease]; cache->name = [name copy]; } static unsigned CacheGetCount(OOCacheImpl *cache) { return cache->count; } #if OOCACHE_PERFORM_INTEGRITY_CHECKS static void CacheCheckIntegrity(OOCacheImpl *cache, NSString *context) { unsigned trueCount; cache->root = TreeCheckIntegrity(cache, cache->root, NULL, context); trueCount = TreeCountNodes(cache->root); if (kCountUnknown == cache->count) cache->count = trueCount; else if (cache->count != trueCount) { OOLog(kOOLogCacheIntegrityCheck, @"Integrity check (%@ for \"%@\"): count is %u, but should be %u.", context, cache->name, cache->count, trueCount); cache->count = trueCount; } AgeListCheckIntegrity(cache, context); } #endif // OOCACHE_PERFORM_INTEGRITY_CHECKS /***** CacheNode functions *****/ // CacheNodeAllocate(): create a cache node for a key, value pair, without inserting it in the structures. static OOCacheNode *CacheNodeAllocate(id key, id value) { OOCacheNode *result = NULL; if (key == nil || value == nil) return NULL; result = calloc(sizeof *result, 1); if (result != NULL) { result->key = [key copy]; result->value = [value retain]; } return result; } // CacheNodeFree(): recursively delete a cache node and its children in the splay tree. To delete an individual node, first clear its child pointers. static void CacheNodeFree(OOCacheImpl *cache, OOCacheNode *node) { id key, value; if (node == NULL) return; AgeListRemove(cache, node); key = node->key; node->key = nil; [key release]; value = node->value; node->value = nil; [value release]; CacheNodeFree(cache, node->leftChild); CacheNodeFree(cache, node->rightChild); free(node); } // CacheNodeGetValue(): retrieve the value of a cache node static id CacheNodeGetValue(OOCacheNode *node) { if (node == NULL) return nil; return node->value; } // CacheNodeSetValue(): change the value of a cache node (as when setObject:forKey: is called for an existing key). static void CacheNodeSetValue(OOCacheNode *node, id value) { if (node == NULL) return; id tmp = node->value; node->value = [value retain]; [tmp release]; } #if OOCACHE_PERFORM_INTEGRITY_CHECKS // CacheNodeGetDescription(): get a description of a cache node for debugging purposes. static NSString *CacheNodeGetDescription(OOCacheNode *node) { if (node == NULL) return @"0[null]"; return [NSString stringWithFormat:@"%p[\"%@\"]", node, node->key]; } #endif // OOCACHE_PERFORM_INTEGRITY_CHECKS /***** Tree functions *****/ /* TreeSplay() This is the fundamental operation of a splay tree. It searches for a node with a given key, and rebalances the tree so that the found node becomes the root. If no match is found, the node moved to the root is the one that would have been found before the target, and will thus be a neighbour of the target if the key is subsequently inserted. */ static OOCacheNode *TreeSplay(OOCacheNode **root, id key) { NSComparisonResult order; OOCacheNode N = { .leftChild = NULL, .rightChild = NULL }; OOCacheNode *node = NULL, *temp = NULL, *l = &N, *r = &N; BOOL exact = NO; if (root == NULL || *root == NULL || key == nil) return NULL; node = *root; for (;;) { #ifndef NDEBUG if (node == NULL) { OOLog(@"node.error",@"node is NULL"); } else if (node->key == NULL) { OOLog(@"node.error",@"node->key is NULL"); } #endif order = [key compare:node->key]; if (order == NSOrderedAscending) { // Closest match is in left subtree if (node->leftChild == NULL) break; if ([key compare:node->leftChild->key] == NSOrderedAscending) { // Rotate right temp = node->leftChild; node->leftChild = temp->rightChild; temp->rightChild = node; node = temp; if (node->leftChild == NULL) break; } // Link right r->leftChild = node; r = node; node = node->leftChild; } else if (order == NSOrderedDescending) { // Closest match is in right subtree if (node->rightChild == NULL) break; if ([key compare:node->rightChild->key] == NSOrderedDescending) { // Rotate left temp = node->rightChild; node->rightChild = temp->leftChild; temp->leftChild = node; node = temp; if (node->rightChild == NULL) break; } // Link left l->rightChild = node; l = node; node = node->rightChild; } else { // Found exact match exact = YES; break; } } // Assemble l->rightChild = node->leftChild; r->leftChild = node->rightChild; node->leftChild = N.rightChild; node->rightChild = N.leftChild; *root = node; return exact ? node : NULL; } static OOCacheNode *TreeInsert(OOCacheImpl *cache, id key, id value) { OOCacheNode *closest = NULL, *node = NULL; NSComparisonResult order; if (cache == NULL || key == nil || value == nil) return NULL; if (cache->root == NULL) { node = CacheNodeAllocate(key, value); cache->root = node; cache->count = 1; } else { node = TreeSplay(&cache->root, key); if (node != NULL) { // Exact match: key already exists, reuse its node CacheNodeSetValue(node, value); } else { closest = cache->root; node = CacheNodeAllocate(key, value); if (EXPECT_NOT(node == NULL)) return NULL; order = [key compare:closest->key]; if (order == NSOrderedAscending) { // Insert to left node->leftChild = closest->leftChild; node->rightChild = closest; closest->leftChild = NULL; cache->root = node; ++cache->count; } else if (order == NSOrderedDescending) { // Insert to right node->rightChild = closest->rightChild; node->leftChild = closest; closest->rightChild = NULL; cache->root = node; ++cache->count; } else { // Key already exists, which we should have caught above OOLog(@"dataCache.inconsistency", @"%s() internal inconsistency for cache \"%@\", insertion failed.", __PRETTY_FUNCTION__, cache->name); CacheNodeFree(cache, node); return NULL; } } } return node; } #if OOCACHE_PERFORM_INTEGRITY_CHECKS static unsigned TreeCountNodes(OOCacheNode *node) { if (node == NULL) return 0; return 1 + TreeCountNodes(node->leftChild) + TreeCountNodes(node->rightChild); } // TreeCheckIntegrity(): verify the links and contents of a (sub-)tree. If successful, returns the root of the subtree (which could theoretically be changed), otherwise returns NULL. static OOCacheNode *TreeCheckIntegrity(OOCacheImpl *cache, OOCacheNode *node, OOCacheNode *expectedParent, NSString *context) { NSComparisonResult order; BOOL OK = YES; if (node == NULL) return NULL; if (OK && node->key == nil) { OOLog(kOOLogCacheIntegrityCheck, @"Integrity check (%@ for \"%@\"): node \"%@\" has nil key; deleting subtree.", context, cache->name, CacheNodeGetDescription(node)); OK = NO; } if (OK && node->value == nil) { OOLog(kOOLogCacheIntegrityCheck, @"Integrity check (%@ for \"%@\"): node \"%@\" has nil value, deleting.", context, cache->name, CacheNodeGetDescription(node)); OK = NO; } if (OK && node->leftChild != NULL) { order = [node->key compare:node->leftChild->key]; if (order != NSOrderedDescending) { OOLog(kOOLogCacheIntegrityCheck, @"Integrity check (%@ for \"%@\"): node %@'s left child %@ is not correctly ordered. Deleting subtree.", context, cache->name, CacheNodeGetDescription(node), CacheNodeGetDescription(node->leftChild)); CacheNodeFree(cache, node->leftChild); node->leftChild = NULL; cache->count = kCountUnknown; } else { node->leftChild = TreeCheckIntegrity(cache, node->leftChild, node, context); } } if (node->rightChild != NULL) { order = [node->key compare:node->rightChild->key]; if (order != NSOrderedAscending) { OOLog(kOOLogCacheIntegrityCheck, @"Integrity check (%@ for \"%@\"): node \"%@\"'s right child \"%@\" is not correctly ordered. Deleting subtree.", context, cache->name, CacheNodeGetDescription(node), CacheNodeGetDescription(node->rightChild)); CacheNodeFree(cache, node->rightChild); node->rightChild = NULL; cache->count = kCountUnknown; } else { node->rightChild = TreeCheckIntegrity(cache, node->rightChild, node, context); } } if (OK) return node; else { cache->count = kCountUnknown; CacheNodeFree(cache, node); return NULL; } } #endif // OOCACHE_PERFORM_INTEGRITY_CHECKS /***** Age list functions *****/ // AgeListMakeYoungest(): place a given cache node at the youngest end of the age list. static void AgeListMakeYoungest(OOCacheImpl *cache, OOCacheNode *node) { if (cache == NULL || node == NULL) return; AgeListRemove(cache, node); node->older = cache->youngest; if (NULL != cache->youngest) cache->youngest->younger = node; cache->youngest = node; if (cache->oldest == NULL) cache->oldest = node; } // AgeListRemove(): remove a cache node from the age-sorted tree. Does not affect its position in the splay tree. static void AgeListRemove(OOCacheImpl *cache, OOCacheNode *node) { OOCacheNode *younger = NULL; OOCacheNode *older = NULL; if (node == NULL) return; younger = node->younger; older = node->older; if (cache->youngest == node) cache->youngest = older; if (cache->oldest == node) cache->oldest = younger; node->younger = NULL; node->older = NULL; if (younger != NULL) younger->older = older; if (older != NULL) older->younger = younger; } #if OOCACHE_PERFORM_INTEGRITY_CHECKS static void AgeListCheckIntegrity(OOCacheImpl *cache, NSString *context) { OOCacheNode *node = NULL, *next = NULL; unsigned seenCount = 0; if (cache == NULL || context == NULL) return; node = cache->youngest; if (node) for (;;) { next = node->older; ++seenCount; if (next == NULL) break; if (next->younger != node) { OOLog(kOOLogCacheIntegrityCheck, @"Integrity check (%@ for \"%@\"): node \"%@\" has invalid older link (should be \"%@\", is \"%@\"); repairing.", context, cache->name, CacheNodeGetDescription(next), CacheNodeGetDescription(node), CacheNodeGetDescription(next->older)); next->older = node; } node = next; } if (seenCount != cache->count) { // This is especially bad since this function is called just after verifying that the count field reflects the number of objects in the tree. OOLog(kOOLogCacheIntegrityCheck, @"Integrity check (%@ for \"%@\"): expected %u nodes, found %u. Cannot repair; clearing cache.", context, cache->name, cache->count, seenCount); /* Start of temporary extra logging */ node = cache->youngest; if (node) { for (;;) { next = node->older; ++seenCount; if (next == NULL) break; if (node->key != NULL) { OOLog(kOOLogCacheIntegrityCheck,@"Key is: %@",node->key); } else { OOLog(kOOLogCacheIntegrityCheck,@"Key is: NULL"); } if (node->value != NULL) { OOLog(kOOLogCacheIntegrityCheck,@"Value is: %@",node->value); } else { OOLog(kOOLogCacheIntegrityCheck,@"Value is: NULL"); } node = next; } } /* End of temporary extra logging */ cache->count = 0; CacheNodeFree(cache, cache->root); cache->root = NULL; cache->youngest = NULL; cache->oldest = NULL; return; } if (node != cache->oldest) { OOLog(kOOLogCacheIntegrityCheck, @"Integrity check (%@ for \"%@\"): oldest pointer in cache is wrong (should be \"%@\", is \"%@\"); repairing.", context, cache->name, CacheNodeGetDescription(node), CacheNodeGetDescription(cache->oldest)); cache->oldest = node; } } #endif // OOCACHE_PERFORM_INTEGRITY_CHECKS #if DEBUG_GRAPHVIZ /* NOTE: enabling AGE_LIST can result in graph rendering times of many hours, because determining paths for non-constraint arcs is NP-hard. In particular, I gave up on rendering a dump of a fairly minimal cache manager after three and a half hours. Individual caches were fine. */ #define AGE_LIST 0 @implementation OOCache (DebugGraphViz) - (void) appendNodesFromSubTree:(OOCacheNode *)subTree toString:(NSMutableString *)ioString { [ioString appendFormat:@"\tn%p [label=\" | %@ | \"];\n", subTree, EscapedGraphVizString([subTree->key description])]; if (subTree->leftChild != NULL) { [self appendNodesFromSubTree:subTree->leftChild toString:ioString]; [ioString appendFormat:@"\tn%p:f0 -> n%p:f1;\n", subTree, subTree->leftChild]; } if (subTree->rightChild != NULL) { [self appendNodesFromSubTree:subTree->rightChild toString:ioString]; [ioString appendFormat:@"\tn%p:f2 -> n%p:f1;\n", subTree, subTree->rightChild]; } } - (NSString *) generateGraphVizBodyWithRootNamed:(NSString *)rootName { NSMutableString *result = nil; result = [NSMutableString string]; // Root node representing cache [result appendFormat:@"\t%@ [label=\"Cache \\\"%@\\\"\" shape=box];\n" "\tnode [shape=record];\n\t\n", rootName, EscapedGraphVizString([self name])]; if (cache == NULL) return result; // Cache [self appendNodesFromSubTree:cache->root toString:result]; // Arc from cache object to root node [result appendString:@"\tedge [color=black constraint=true];\n"]; [result appendFormat:@"\t%@ -> n%p:f1;\n", rootName, cache->root]; #if AGE_LIST OOCacheNode *node = NULL; // Arcs representing age list [result appendString:@"\t\n\t// Age-sorted list in blue\n\tedge [color=blue constraint=false];\n"]; node = cache->oldest; while (node->younger != NULL) { [result appendFormat:@"\tn%p:f2 -> n%p:f0;\n", node, node->younger]; node = node->younger; } #endif return result; } - (NSString *) generateGraphViz { NSMutableString *result = nil; result = [NSMutableString string]; // Header [result appendFormat: @"// OOCache dump\n\n" "digraph cache\n" "{\n" "\tgraph [charset=\"UTF-8\", label=\"OOCache \"%@\" debug dump\", labelloc=t, labeljust=l];\n\t\n", [self name]]; [result appendString:[self generateGraphVizBodyWithRootNamed:@"cache"]]; [result appendString:@"}\n"]; return result; } - (void) writeGraphVizToURL:(NSURL *)url { NSString *graphViz = nil; NSData *data = nil; graphViz = [self generateGraphViz]; data = [graphViz dataUsingEncoding:NSUTF8StringEncoding]; if (data != nil) { [data writeToURL:url atomically:YES]; } } - (void) writeGraphVizToPath:(NSString *)path { [self writeGraphVizToURL:[NSURL fileURLWithPath:path]]; } @end #endif oolite-1.82/src/Core/OOCacheManager.h000066400000000000000000000035031256642440500173340ustar00rootroot00000000000000/* OOCacheManager.h By Jens Ayton Singleton class responsible for handling Oolite's data cache. The cache manager stores arbitrary property lists in separate namespaces (referred to simply as caches). The cache is emptied if it was created with a different verison of Oolite, or if it was created on a system with a different byte sex. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" @interface OOCacheManager: NSObject { @private NSMutableDictionary *_caches; id _scheduledWrite; BOOL _permitWrites; BOOL _dirty; } + (OOCacheManager *)sharedCache; - (id)objectForKey:(NSString *)inKey inCache:(NSString *)inCacheKey; - (void)setObject:(id)inElement forKey:(NSString *)inKey inCache:(NSString *)inCacheKey; - (void)removeObjectForKey:(NSString *)inKey inCache:(NSString *)inCacheKey; - (void)clearCache:(NSString *)inCacheKey; - (void)clearAllCaches; - (void) reloadAllCaches; - (void)setAllowCacheWrites:(BOOL)flag; - (NSString *)cacheDirectoryPathCreatingIfNecessary:(BOOL)create; - (void)flush; - (void)finishOngoingFlush; // Wait for flush to complete. Does nothing if async flushing is disabled. @end oolite-1.82/src/Core/OOCacheManager.m000066400000000000000000000422761256642440500173530ustar00rootroot00000000000000/* OOCacheManager.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCacheManager.h" #import "OOPListParsing.h" #import "OODeepCopy.h" #import "OOCollectionExtractors.h" #import "OOJavaScriptEngine.h" #import "NSFileManagerOOExtensions.h" #define WRITE_ASYNC 1 #define PROFILE_WRITES 0 // Use the (presumed) most efficient plist format for each platform. #if OOLITE_MAC_OS_X #define CACHE_PLIST_FORMAT NSPropertyListBinaryFormat_v1_0 #else #define CACHE_PLIST_FORMAT NSPropertyListGNUstepBinaryFormat #endif #if WRITE_ASYNC #import "OOAsyncWorkManager.h" #endif #if PROFILE_WRITES #import "OOProfilingStopwatch.h" #endif static NSString * const kOOLogDataCacheFound = @"dataCache.found"; static NSString * const kOOLogDataCacheNotFound = @"dataCache.notFound"; static NSString * const kOOLogDataCacheRebuild = @"dataCache.rebuild"; static NSString * const kOOLogDataCacheWriteSuccess = @"dataCache.write.success"; static NSString * const kOOLogDataCacheWriteFailed = @"dataCache.write.failed"; static NSString * const kOOLogDataCacheRetrieveSuccess = @"dataCache.retrieve.success"; static NSString * const kOOLogDataCacheRetrieveFailed = @"dataCache.retrieve.failed"; static NSString * const kOOLogDataCacheSetSuccess = @"dataCache.set.success"; static NSString * const kOOLogDataCacheSetFailed = @"dataCache.set.failed"; static NSString * const kOOLogDataCacheRemoveSuccess = @"dataCache.remove.success"; static NSString * const kOOLogDataCacheClearSuccess = @"dataCache.clear.success"; static NSString * const kOOLogDataCacheBuildPathError = @"dataCache.write.buildPath.failed"; static NSString * const kOOLogDataCacheSerializationError = @"dataCache.write.serialize.failed"; static NSString * const kCacheKeyVersion = @"version"; static NSString * const kCacheKeyEndianTag = @"endian tag"; static NSString * const kCacheKeyFormatVersion = @"format version"; static NSString * const kCacheKeyCaches = @"caches"; enum { kEndianTagValue = 0x0123456789ABCDEFULL, kFormatVersionValue = 217 }; static OOCacheManager *sSingleton = nil; @interface OOCacheManager (Private) - (void)loadCache; - (void)write; - (void)clear; - (BOOL)dirty; - (void)markClean; - (NSDictionary *)loadDict; - (BOOL)writeDict:(NSDictionary *)inDict; - (void)buildCachesFromDictionary:(NSDictionary *)inDict; - (NSDictionary *)dictionaryOfCaches; - (BOOL)directoryExists:(NSString *)inPath create:(BOOL)inCreate; @end @interface OOCacheManager (PlatformSpecific) - (NSString *)cachePathCreatingIfNecessary:(BOOL)inCreate; @end #if WRITE_ASYNC @interface OOAsyncCacheWriter: NSObject { @private NSDictionary *_cacheContents; } - (id) initWithCacheContents:(NSDictionary *)cacheContents; @end #endif @implementation OOCacheManager - (id)init { self = [super init]; if (self != nil) { _permitWrites = YES; [self loadCache]; } return self; } - (void)dealloc { [self clear]; [super dealloc]; } - (NSString *)description { return [NSString stringWithFormat:@"<%@ %p>{dirty=%s}", [self class], self, [self dirty] ? "yes" : "no"]; } + (OOCacheManager *) sharedCache { // NOTE: assumes single-threaded access. if (sSingleton == nil) { sSingleton = [[self alloc] init]; } return sSingleton; } - (id)objectForKey:(NSString *)inKey inCache:(NSString *)inCacheKey { NSMutableDictionary *cache = nil; id result = nil; NSParameterAssert(inKey != nil && inCacheKey != nil); cache = [_caches objectForKey:inCacheKey]; if (cache != nil) { result = [cache objectForKey:inKey]; if (result != nil) { OODebugLog(kOOLogDataCacheRetrieveSuccess, @"Retrieved \"%@\" cache object %@.", inCacheKey, inKey); } else { OODebugLog(kOOLogDataCacheRetrieveFailed, @"Failed to retrieve \"%@\" cache object %@ -- no such entry.", inCacheKey, inKey); } } else { OODebugLog(kOOLogDataCacheRetrieveFailed, @"Failed to retreive \"%@\" cache object %@ -- no such cache.", inCacheKey, inKey); } return result; } - (void)setObject:(id)inObject forKey:(NSString *)inKey inCache:(NSString *)inCacheKey { NSMutableDictionary *cache = nil; NSParameterAssert(inObject != nil && inKey != nil && inCacheKey != nil); if (EXPECT_NOT(_caches == nil)) return; cache = [_caches objectForKey:inCacheKey]; if (cache == nil) { cache = [NSMutableDictionary dictionary]; if (cache == nil) { OODebugLog(kOOLogDataCacheSetFailed, @"Failed to create cache for key \"%@\".", inCacheKey); return; } [_caches setObject:cache forKey:inCacheKey]; } [cache setObject:inObject forKey:inKey]; _dirty = YES; OODebugLog(kOOLogDataCacheSetSuccess, @"Updated entry %@ in cache \"%@\".", inKey, inCacheKey); } - (void)removeObjectForKey:(NSString *)inKey inCache:(NSString *)inCacheKey { NSMutableDictionary *cache = nil; NSParameterAssert(inKey != nil && inCacheKey != nil); cache = [_caches objectForKey:inCacheKey]; if (cache != nil) { if (nil != [cache objectForKey:inKey]) { [cache removeObjectForKey:inKey]; _dirty = YES; OODebugLog(kOOLogDataCacheRemoveSuccess, @"Removed entry keyed %@ from cache \"%@\".", inKey, inCacheKey); } else { OODebugLog(kOOLogDataCacheRemoveSuccess, @"No need to remove non-existent entry keyed %@ from cache \"%@\".", inKey, inCacheKey); } } else { OODebugLog(kOOLogDataCacheRemoveSuccess, @"No need to remove entry keyed %@ from non-existent cache \"%@\".", inKey, inCacheKey); } } - (void)clearCache:(NSString *)inCacheKey { NSParameterAssert(inCacheKey != nil); if (nil != [_caches objectForKey:inCacheKey]) { [_caches removeObjectForKey:inCacheKey]; _dirty = YES; OODebugLog(kOOLogDataCacheClearSuccess, @"Cleared cache \"%@\".", inCacheKey); } else { OODebugLog(kOOLogDataCacheClearSuccess, @"No need to clear non-existent cache \"%@\".", inCacheKey); } } - (void)clearAllCaches { [self clear]; _caches = [[NSMutableDictionary alloc] init]; _dirty = YES; } - (void) reloadAllCaches { [self clear]; [self loadCache]; } - (void)flush { if (_permitWrites && [self dirty] && _scheduledWrite == nil) { [self write]; [self markClean]; } } - (void)finishOngoingFlush { #if WRITE_ASYNC [[OOAsyncWorkManager sharedAsyncWorkManager] waitForTaskToComplete:_scheduledWrite]; #endif } - (void)setAllowCacheWrites:(BOOL)flag { _permitWrites = (flag != NO); } - (NSString *)cacheDirectoryPathCreatingIfNecessary:(BOOL)create { /* Construct the path to the directory for cache files, which is: ~/Library/Caches/org.aegidian.oolite/ or ~/GNUStep/Library/Caches/org.aegidian.oolite/ In addition to generally being the right place to put caches, ~/Library/Caches has the particular advantage of not being indexed by Spotlight or backed up by Time Machine. */ NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]; if (![self directoryExists:cachePath create:create]) return nil; #if !OOLITE_MAC_OS_X // the old cache file on GNUstep was one level up, so remove it if it exists [[NSFileManager defaultManager] removeFileAtPath:[cachePath stringByAppendingPathComponent:@"Oolite-cache.plist"] handler:nil]; #endif cachePath = [cachePath stringByAppendingPathComponent:@"org.aegidian.oolite"]; if (![self directoryExists:cachePath create:create]) return nil; return cachePath; } @end @implementation OOCacheManager (Private) - (void)loadCache { NSDictionary *cache = nil; NSString *cacheVersion = nil; NSString *ooliteVersion = nil; NSData *endianTag = nil; NSNumber *formatVersion = nil; BOOL accept = YES; uint64_t endianTagValue = 0; ooliteVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]; [self clear]; cache = [self loadDict]; if (cache != nil) { // We have a cache OOLog(kOOLogDataCacheFound, @"Found data cache."); OOLogIndentIf(kOOLogDataCacheFound); cacheVersion = [cache objectForKey:kCacheKeyVersion]; if (![cacheVersion isEqual:ooliteVersion]) { OOLog(kOOLogDataCacheRebuild, @"Data cache version (%@) does not match Oolite version (%@), rebuilding cache.", cacheVersion, ooliteVersion); accept = NO; } formatVersion = [cache objectForKey:kCacheKeyFormatVersion]; if (accept && [formatVersion unsignedIntValue] != kFormatVersionValue) { OOLog(kOOLogDataCacheRebuild, @"Data cache format (%@) is not supported format (%u), rebuilding cache.", formatVersion, kFormatVersionValue); accept = NO; } if (accept) { endianTag = [cache objectForKey:kCacheKeyEndianTag]; if (![endianTag isKindOfClass:[NSData class]] || [endianTag length] != sizeof endianTagValue) { OOLog(kOOLogDataCacheRebuild, @"Data cache endian tag is invalid, rebuilding cache."); accept = NO; } else { endianTagValue = *(const uint64_t *)[endianTag bytes]; if (endianTagValue != kEndianTagValue) { OOLog(kOOLogDataCacheRebuild, @"Data cache endianness is inappropriate for this system, rebuilding cache."); accept = NO; } } } if (accept) { // We have a cache, and it's the right format. [self buildCachesFromDictionary:[cache objectForKey:kCacheKeyCaches]]; } OOLogOutdentIf(kOOLogDataCacheFound); } else { // No cache OOLog(kOOLogDataCacheNotFound, @"No data cache found, starting from scratch."); } // If loading failed, or there was a version or endianness conflict if (_caches == nil) _caches = [[NSMutableDictionary alloc] init]; [self markClean]; } - (void)write { NSMutableDictionary *newCache = nil; NSString *ooliteVersion = nil; NSData *endianTag = nil; NSNumber *formatVersion = nil; NSDictionary *pListRep = nil; uint64_t endianTagValue = kEndianTagValue; if (_caches == nil) return; if (_scheduledWrite != nil) return; #if PROFILE_WRITES OOProfilingStopwatch *stopwatch = [OOProfilingStopwatch stopwatch]; #endif #if WRITE_ASYNC OOLog(@"dataCache.willWrite", @"Scheduling data cache write."); #else OOLog(@"dataCache.willWrite", @"About to write cache."); #endif ooliteVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]; endianTag = [NSData dataWithBytes:&endianTagValue length:sizeof endianTagValue]; formatVersion = [NSNumber numberWithUnsignedInt:kFormatVersionValue]; pListRep = [self dictionaryOfCaches]; if (ooliteVersion == nil || endianTag == nil || formatVersion == nil || pListRep == nil) { OOLog(@"dataCache.cantWrite", @"Failed to write data cache -- prerequisites not fulfilled. %@",@"This is an internal error, please report it."); return; } newCache = [NSMutableDictionary dictionaryWithCapacity:4]; [newCache setObject:ooliteVersion forKey:kCacheKeyVersion]; [newCache setObject:formatVersion forKey:kCacheKeyFormatVersion]; [newCache setObject:endianTag forKey:kCacheKeyEndianTag]; [newCache setObject:pListRep forKey:kCacheKeyCaches]; #if PROFILE_WRITES && !WRITE_ASYNC OOTimeDelta prepareT = [stopwatch reset]; #endif #if WRITE_ASYNC NSDictionary *cacheData = newCache; _scheduledWrite = [[OOAsyncCacheWriter alloc] initWithCacheContents:cacheData]; #if PROFILE_WRITES OOTimeDelta endT = [stopwatch reset]; OOLog(@"dataCache.profile", @"Time to prepare cache data: %g seconds.", endT); #endif [[OOAsyncWorkManager sharedAsyncWorkManager] addTask:_scheduledWrite priority:kOOAsyncPriorityLow]; #else #if PROFILE_WRITES OOLog(@"dataCache.profile", @"Time to prepare cache data: %g seconds.", prepareT); #endif if ([self writeDict:newCache]) { [self markClean]; OOLog(kOOLogDataCacheWriteSuccess, @"Wrote data cache."); } else { OOLog(kOOLogDataCacheWriteFailed, @"Failed to write data cache."); } #endif } - (void)clear { [_caches release]; _caches = nil; } - (BOOL)dirty { return _dirty; } - (void)markClean { _dirty = NO; } - (NSDictionary *)loadDict { NSString *path = nil; NSData *data = nil; NSString *errorString = nil; id contents = nil; path = [self cachePathCreatingIfNecessary:NO]; if (path == nil) return nil; @try { data = [NSData dataWithContentsOfFile:path]; if (data == nil) return nil; contents = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:NULL errorDescription:&errorString]; } @catch (NSException *exception) { errorString = [exception reason]; contents = nil; } if (errorString != nil) { OOLog(@"dataCache.badData", @"Could not read data cache: %@", errorString); #if OOLITE_RELEASE_PLIST_ERROR_STRINGS [errorString release]; #endif return nil; } if (![contents isKindOfClass:[NSDictionary class]]) return nil; return contents; } - (BOOL)writeDict:(NSDictionary *)inDict { NSString *path = nil; NSData *plist = nil; NSString *errorDesc = nil; path = [self cachePathCreatingIfNecessary:YES]; if (path == nil) return NO; #if PROFILE_WRITES OOProfilingStopwatch *stopwatch = [OOProfilingStopwatch stopwatch]; #endif plist = [NSPropertyListSerialization dataFromPropertyList:inDict format:CACHE_PLIST_FORMAT errorDescription:&errorDesc]; if (plist == nil) { #if OOLITE_RELEASE_PLIST_ERROR_STRINGS [errorDesc autorelease]; #endif OOLog(kOOLogDataCacheSerializationError, @"Could not convert data cache to property list data: %@", errorDesc); return NO; } #if PROFILE_WRITES OOTimeDelta serializeT = [stopwatch reset]; #endif BOOL result = [plist writeToFile:path atomically:NO]; #if PROFILE_WRITES OOTimeDelta writeT = [stopwatch reset]; OOLog(@"dataCache.profile", @"Time to serialize cache: %g seconds. Time to write data: %g seconds.", serializeT, writeT); #endif #if WRITE_ASYNC DESTROY(_scheduledWrite); #endif return result; } - (void)buildCachesFromDictionary:(NSDictionary *)inDict { NSEnumerator *keyEnum = nil; id key = nil; id value = nil; NSMutableDictionary *cache = nil; if (inDict == nil ) return; [_caches release]; _caches = [[NSMutableDictionary alloc] initWithCapacity:[inDict count]]; for (keyEnum = [inDict keyEnumerator]; (key = [keyEnum nextObject]); ) { value = [inDict oo_dictionaryForKey:key]; if (value != nil) { cache = [NSMutableDictionary dictionaryWithDictionary:value]; if (cache != nil) { [_caches setObject:cache forKey:key]; } } } } - (NSDictionary *)dictionaryOfCaches { return [OODeepCopy(_caches) autorelease]; } - (BOOL)directoryExists:(NSString *)inPath create:(BOOL)inCreate { BOOL exists, directory; NSFileManager *fmgr = [NSFileManager defaultManager]; exists = [fmgr fileExistsAtPath:inPath isDirectory:&directory]; if (exists && !directory) { OOLog(kOOLogDataCacheBuildPathError, @"Expected %@ to be a folder, but it is a file.", inPath); return NO; } if (!exists) { if (!inCreate) return NO; if (![fmgr oo_createDirectoryAtPath:inPath attributes:nil]) { OOLog(kOOLogDataCacheBuildPathError, @"Could not create folder %@.", inPath); return NO; } } return YES; } #if OOLITE_MAC_OS_X - (NSString *)cachePathCreatingIfNecessary:(BOOL)create { NSString *cachePath = [self cacheDirectoryPathCreatingIfNecessary:create]; return [cachePath stringByAppendingPathComponent:@"Data Cache.plist"]; } #else - (NSString *)cachePathCreatingIfNecessary:(BOOL)create { NSString *cachePath = [self cacheDirectoryPathCreatingIfNecessary:create]; return [cachePath stringByAppendingPathComponent:@"Oolite-cache.plist"]; } #endif @end @implementation OOCacheManager (Singleton) /* Canonical singleton boilerplate. See Cocoa Fundamentals Guide: Creating a Singleton Instance. See also +sharedCache above. NOTE: assumes single-threaded access. */ + (id)allocWithZone:(NSZone *)inZone { if (sSingleton == nil) { sSingleton = [super allocWithZone:inZone]; return sSingleton; } return nil; } - (id)copyWithZone:(NSZone *)inZone { return self; } - (id)retain { return self; } - (NSUInteger)retainCount { return UINT_MAX; } - (void)release {} - (id)autorelease { return self; } @end #if WRITE_ASYNC @implementation OOAsyncCacheWriter - (id) initWithCacheContents:(NSDictionary *)cacheContents { if ((self = [super init])) { _cacheContents = [cacheContents copy]; if (_cacheContents == nil) { [self release]; self = nil; } } return self; } - (void) dealloc { DESTROY(_cacheContents); [super dealloc]; } - (void) performAsyncTask { if ([[OOCacheManager sharedCache] writeDict:_cacheContents]) { OOLog(kOOLogDataCacheWriteSuccess, @"Wrote data cache."); } else { OOLog(kOOLogDataCacheWriteFailed, @"Failed to write data cache."); } DESTROY(_cacheContents); } - (void) completeAsyncTask { // Don't need to do anything, but this needs to be here so we can wait on it. } @end #endif // WRITE_ASYNC oolite-1.82/src/Core/OOCharacter.h000066400000000000000000000042501256642440500167320ustar00rootroot00000000000000/* OOCharacter.h Represents an NPC person (as opposed to an NPC ship). Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #import "OOTypes.h" #import "legacy_random.h" #import "OOJSPropID.h" @class OOJSScript; @interface OOCharacter: NSObject { @private NSString *_name; NSString *_shortDescription; OOSystemID _originSystem; Random_Seed _genSeed; int _legalStatus; OOCreditsQuantity _insuranceCredits; NSArray *_scriptActions; OOJSScript *_script; } - (id) initWithRole:(NSString *)role andOriginalSystem:(OOSystemID)s; + (OOCharacter *) characterWithRole:(NSString *)c_role andOriginalSystem:(OOSystemID)s; + (OOCharacter *) randomCharacterWithRole:(NSString *)c_role andOriginalSystem:(OOSystemID)s; + (OOCharacter *) characterWithDictionary:(NSDictionary *)c_dict; - (NSString*) planetOfOrigin; - (OOSystemID) planetIDOfOrigin; - (NSString*) species; - (void) basicSetUp; - (BOOL) castInRole:(NSString *)role; - (NSString *) name; - (void) setName:(NSString *)value; - (NSString *) shortDescription; - (void) setShortDescription:(NSString *)value; - (int) legalStatus; - (void) setLegalStatus:(int)value; - (OOCreditsQuantity) insuranceCredits; - (void) setInsuranceCredits:(OOCreditsQuantity)value; - (NSArray *) legacyScript; - (void) setLegacyScript:(NSArray *)scriptActions; - (OOJSScript *)script; - (void) setCharacterScript:(NSString *)scriptName; - (void) doScriptEvent:(jsid)message; - (NSDictionary *) infoForScripting; @end oolite-1.82/src/Core/OOCharacter.m000066400000000000000000000301021256642440500167320ustar00rootroot00000000000000/* OOCharacter.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCharacter.h" #import "Universe.h" #import "OOStringExpander.h" #import "OOStringParsing.h" #import "OOCollectionExtractors.h" #import "OOJSScript.h" @interface OOCharacter (Private) - (id) initWithGenSeed:(Random_Seed)characterSeed andOriginalSystem:(OOSystemID)systemSeed; - (void) setCharacterFromDictionary:(NSDictionary *)dict; - (void)setOriginSystem:(OOSystemID)value; - (Random_Seed)genSeed; @end @implementation OOCharacter - (NSString *) descriptionComponents { return [NSString stringWithFormat:@"%@, %@. bounty: %i insurance: %llu", [self name], [self shortDescription], [self legalStatus], [self insuranceCredits]]; } - (NSString *) oo_jsClassName { return @"Character"; } - (void) dealloc { [_name release]; [_shortDescription release]; [_scriptActions release]; DESTROY(_script); [super dealloc]; } - (id) initWithGenSeed:(Random_Seed)characterSeed andOriginalSystem:(OOSystemID)system { if ((self = [super init])) { // do character set-up _genSeed = characterSeed; _originSystem = system; [self basicSetUp]; } return self; } - (id) initWithRole:(NSString *)role andOriginalSystem:(OOSystemID)system { Random_Seed seed; make_pseudo_random_seed(&seed); if ((self = [self initWithGenSeed:seed andOriginalSystem:system])) { [self castInRole:role]; } return self; } + (OOCharacter *) characterWithRole:(NSString *)role andOriginalSystem:(OOSystemID)system { return [[[self alloc] initWithRole:role andOriginalSystem:system] autorelease]; } + (OOCharacter *) randomCharacterWithRole:(NSString *)role andOriginalSystem:(OOSystemID)system { Random_Seed seed; seed.a = (Ranrot() & 0xff); seed.b = (Ranrot() & 0xff); seed.c = (Ranrot() & 0xff); seed.d = (Ranrot() & 0xff); seed.e = (Ranrot() & 0xff); seed.f = (Ranrot() & 0xff); OOCharacter *character = [[[OOCharacter alloc] initWithGenSeed:seed andOriginalSystem:system] autorelease]; [character castInRole:role]; return character; } + (OOCharacter *) characterWithDictionary:(NSDictionary *)dict { OOCharacter *character = [[[OOCharacter alloc] init] autorelease]; [character setCharacterFromDictionary:dict]; return character; } - (NSString *) planetOfOrigin { // determine the planet of origin NSDictionary *originInfo = [UNIVERSE generateSystemData:[self planetIDOfOrigin]]; return [originInfo objectForKey:KEY_NAME]; } - (OOSystemID) planetIDOfOrigin { // determine the planet of origin return _originSystem; } - (NSString *) species { // determine the character's species int species = [self genSeed].f & 0x03; // 0-1 native to home system, 2 human colonial, 3 other NSString* speciesString = nil; if (species == 3) speciesString = [UNIVERSE getSystemInhabitants:[self genSeed].e plural:NO]; else speciesString = [UNIVERSE getSystemInhabitants:[self planetIDOfOrigin] plural:NO]; if (![[UNIVERSE descriptions] oo_boolForKey:@"lowercase_ignore"]) { speciesString = [speciesString lowercaseString]; } return [speciesString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; } - (void) basicSetUp { // save random seeds for restoration later RNG_Seed savedRNGSeed = currentRandomSeed(); RANROTSeed savedRANROTSeed = RANROTGetFullSeed(); // set RNG to character seed Random_Seed genSeed = [self genSeed]; seed_for_planet_description(genSeed); // determine the planet of origin NSDictionary *originInfo = [UNIVERSE generateSystemData:[self planetIDOfOrigin]]; NSString *planet = [originInfo oo_stringForKey:KEY_NAME]; OOGovernmentID government = [originInfo oo_intForKey:KEY_GOVERNMENT]; // 0 .. 7 (0 anarchic .. 7 most stable) int criminalTendency = government ^ 0x07; // determine the character's species NSString *species = [self species]; // determine the character's name seed_RNG_only_for_planet_description(genSeed); NSString *genName = nil; if ([species hasPrefix:@"human"]) { genName = [NSString stringWithFormat:@"%@ %@", OOExpandWithSeed(genSeed, @"%R"), OOExpandKeyWithSeed(genSeed, @"nom")]; } else { /* NOTE: we can't use "%R %R" because that will produce the same string twice. TODO: is there a reason not to use %N and kOOExpandGoodRNG here? Is there some context where we rely on being able to get the same name for a given genSeed? */ genName = [NSString stringWithFormat:@"%@ %@", OOExpandWithSeed(genSeed, @"%R"), OOExpandWithSeed(genSeed, @"%R")]; } [self setName:genName]; [self setShortDescription:OOExpandKeyWithSeed(genSeed, @"character-generic-description", species, planet)]; // determine _legalStatus for a completely random character [self setLegalStatus:0]; // clean int legalIndex = gen_rnd_number() & gen_rnd_number() & 0x03; while (((gen_rnd_number() & 0xf) < criminalTendency) && (legalIndex < 3)) { legalIndex++; } if (legalIndex == 3) { // criminal [self setLegalStatus:criminalTendency + criminalTendency * (gen_rnd_number() & 0x03) + (gen_rnd_number() & gen_rnd_number() & 0x7f)]; } legalIndex = 0; if (_legalStatus > 0) legalIndex = (_legalStatus <= 50) ? 1 : 2; // if clean - determine insurance level (if any) [self setInsuranceCredits:0]; if (legalIndex == 0) { int insuranceIndex = gen_rnd_number() & gen_rnd_number() & 0x03; switch (insuranceIndex) { case 1: [self setInsuranceCredits:125]; break; case 2: [self setInsuranceCredits:250]; break; case 3: [self setInsuranceCredits:500]; } } // restore random seed setRandomSeed( savedRNGSeed); RANROTSetFullSeed(savedRANROTSeed); } - (BOOL) castInRole:(NSString *)role { BOOL specialSetUpDone = NO; role = [role lowercaseString]; if ([role hasPrefix:@"pirate"]) { // determine _legalStatus for a completely random character Random_Seed genSeed = [self genSeed]; int sins = 0x08 | (genSeed.a & genSeed.b); [self setLegalStatus:sins & 0x7f]; specialSetUpDone = YES; } else if ([role hasPrefix:@"trader"]) { [self setLegalStatus:0]; // clean int insuranceIndex = gen_rnd_number() & 0x03; switch (insuranceIndex) { case 0: [self setInsuranceCredits:0]; break; case 1: [self setInsuranceCredits:125]; break; case 2: [self setInsuranceCredits:250]; break; case 3: [self setInsuranceCredits:500]; } specialSetUpDone = YES; } else if ([role hasPrefix:@"hunter"]) { [self setLegalStatus:0]; // clean int insuranceIndex = gen_rnd_number() & 0x03; if (insuranceIndex == 3) [self setInsuranceCredits:500]; specialSetUpDone = YES; } else if ([role hasPrefix:@"police"]) { [self setLegalStatus:0]; // clean [self setInsuranceCredits:125]; specialSetUpDone = YES; } else if ([role isEqual:@"miner"]) { [self setLegalStatus:0]; // clean [self setInsuranceCredits:25]; specialSetUpDone = YES; } else if ([role isEqual:@"passenger"]) { [self setLegalStatus:0]; // clean int insuranceIndex = gen_rnd_number() & 0x03; switch (insuranceIndex) { case 0: [self setInsuranceCredits:25]; break; case 1: [self setInsuranceCredits:125]; break; case 2: [self setInsuranceCredits:250]; break; case 3: [self setInsuranceCredits:500]; } specialSetUpDone = YES; } else if ([role isEqual:@"slave"]) { [self setLegalStatus:0]; // clean [self setInsuranceCredits:0]; specialSetUpDone = YES; } else if ([role isEqual:@"thargoid"]) { [self setLegalStatus:100]; [self setInsuranceCredits:0]; [self setName:DESC(@"character-thargoid-name")]; [self setShortDescription:DESC(@"character-a-thargoid")]; specialSetUpDone = YES; } // do long description here return specialSetUpDone; } - (NSString *)name { return _name; } - (NSString *)shortDescription { return _shortDescription; } - (Random_Seed)genSeed { return _genSeed; } - (int)legalStatus { return _legalStatus; } - (OOCreditsQuantity)insuranceCredits { return _insuranceCredits; } - (NSArray *)legacyScript { return _scriptActions; } - (NSDictionary *) infoForScripting { return [NSDictionary dictionaryWithObjectsAndKeys: [self name], @"name", [self shortDescription], @"description", [self species], @"species", [NSNumber numberWithInt:[self legalStatus]], @"legalStatus", [NSNumber numberWithUnsignedInt:[self insuranceCredits]], @"insuranceCredits", [NSNumber numberWithInt:[self planetIDOfOrigin]], @"homeSystem", nil]; } - (void)setName:(NSString *)value { [_name autorelease]; _name = [value copy]; } - (void)setShortDescription:(NSString *)value { [_shortDescription autorelease]; _shortDescription = [value copy]; } - (void)setOriginSystem:(OOSystemID)value { _originSystem = value; } - (void)setGenSeed:(Random_Seed)value { _genSeed = value; } - (void)setLegalStatus:(int)value { _legalStatus = value; } - (void)setInsuranceCredits:(OOCreditsQuantity)value { _insuranceCredits = value; } - (void)setLegacyScript:(NSArray *)some_actions { [_scriptActions autorelease]; _scriptActions = [some_actions copy]; } - (OOJSScript *)script { return _script; } - (void) setCharacterScript:(NSString *)scriptName { [_script autorelease]; _script = [OOScript jsScriptFromFileNamed:scriptName properties:[NSDictionary dictionaryWithObject:self forKey:@"character"]]; [_script retain]; } - (void) doScriptEvent:(jsid)message { JSContext *context = OOJSAcquireContext(); [_script callMethod:message inContext:context withArguments:NULL count:0 result:NULL]; OOJSRelinquishContext(context); } - (void) setCharacterFromDictionary:(NSDictionary *)dict { id origin = nil; Random_Seed seed; origin = [dict objectForKey:@"origin"]; if ([origin isKindOfClass:[NSNumber class]] || ([origin respondsToSelector:@selector(intValue)] && ([origin intValue] != 0 || [origin isEqual:@"0"]))) { // Number or numerical string [self setOriginSystem:[origin intValue]]; } else if ([origin isKindOfClass:[NSString class]]) { OOSystemID sys = [UNIVERSE findSystemFromName:origin]; if (sys < 0) { OOLogERR(@"character.load.unknownSystem", @"could not find a system named '%@' in this galaxy.", origin); [self setOriginSystem:(ranrot_rand() & 0xff)]; } else { [self setOriginSystem:sys]; } } else { // no origin defined, select one at random. [self setOriginSystem:(ranrot_rand() & 0xff)]; } if ([dict objectForKey:@"random_seed"]) { seed = RandomSeedFromString([dict oo_stringForKey:@"random_seed"]); // returns kNilRandomSeed on failure } else { seed.a = (ranrot_rand() & 0xff); seed.b = (ranrot_rand() & 0xff); seed.c = (ranrot_rand() & 0xff); seed.d = (ranrot_rand() & 0xff); seed.e = (ranrot_rand() & 0xff); seed.f = (ranrot_rand() & 0xff); } [self setGenSeed:seed]; [self basicSetUp]; if ([dict oo_stringForKey:@"role"]) [self castInRole:[dict oo_stringForKey:@"role"]]; if ([dict oo_stringForKey:@"name"]) [self setName:[dict oo_stringForKey:@"name"]]; if ([dict oo_stringForKey:@"short_description"]) [self setShortDescription:[dict oo_stringForKey:@"short_description"]]; if ([dict objectForKey:@"legal_status"]) [self setLegalStatus:[dict oo_intForKey:@"legal_status"]]; if ([dict objectForKey:@"bounty"]) [self setLegalStatus:[dict oo_intForKey:@"bounty"]]; if ([dict objectForKey:@"insurance"]) [self setInsuranceCredits:[dict oo_unsignedLongLongForKey:@"insurance"]]; if ([dict oo_stringForKey:@"script"]) [self setCharacterScript:[dict oo_stringForKey:@"script"]]; if ([dict oo_arrayForKey:@"script_actions"]) [self setLegacyScript:[dict oo_arrayForKey:@"script_actions"]]; } @end oolite-1.82/src/Core/OOCocoa.h000066400000000000000000000270671256642440500160750ustar00rootroot00000000000000/* OOCocoa.h Import OpenStep main headers and define some Macisms and other compatibility stuff. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifdef NDEBUG #define NS_BLOCK_ASSERTIONS 1 #endif #include #include #import #ifdef GNUSTEP #define OOLITE_GNUSTEP 1 #if (GNUSTEP_BASE_MAJOR_VERSION == 1 && GNUSTEP_BASE_MINOR_VERSION >= 20) || (GNUSTEP_BASE_MAJOR_VERSION > 1) #define OOLITE_GNUSTEP_1_20 1 #else #error Oolite for non-Mac targets requires GNUstep 1.20. #endif #ifndef NSIntegerMax // Missing in GNUstep-base prior to 1.23. #define NSIntegerMax INTPTR_MAX #define NSIntegerMin INTPTR_MIN #define NSUIntegerMax UINTPTR_MAX #endif #else #import #define OOLITE_MAC_OS_X 1 #define OOLITE_SPEECH_SYNTH 1 #if __LP64__ #define OOLITE_64_BIT 1 #endif /* Enforce type-clean use of nil and Nil under OS X. (They are untyped in Cocoa, apparently for compatibility with legacy Mac OS code, but typed in GNUstep.) */ #undef nil #define nil ((id)0) #undef Nil #define Nil ((Class)nil) /* Useful macro copied from GNUstep. */ #ifndef DESTROY #define DESTROY(x) do { id x_ = x; x = nil; [x_ release]; } while (0) #endif #if defined MAC_OS_X_VERSION_10_7 && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7 #define OOLITE_MAC_OS_X_10_7 1 #endif #if defined MAC_OS_X_VERSION_10_8 && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8 #define OOLITE_MAC_OS_X_10_8 1 #endif #endif #ifndef OOLITE_MAC_OS_X_10_7 #define OOLITE_MAC_OS_X_10_7 0 #endif #ifndef OOLITE_MAC_OS_X_10_8 #define OOLITE_MAC_OS_X_10_8 0 #endif #if defined(__GNUC__) && !defined(__clang__) // GCC version; for instance, 40300 for 4.3.0. Deliberately undefined in Clang (which defines fake __GNUC__ macros for compatibility). #define OOLITE_GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) #endif #if OOLITE_GNUSTEP #include #include // to get UINT_MAX #define OOLITE_SDL 1 #ifdef WIN32 #define OOLITE_WINDOWS 1 #if defined(_WIN64) #define OOLITE_64_BIT 1 #endif #endif #ifdef LINUX #define OOLITE_LINUX 1 #endif #define true 1 #define false 0 #if !defined(MAX) #define MAX(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a > __b ? __a : __b; }) #endif #if !defined(MIN) #define MIN(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; }) #endif #ifdef HAVE_LIBESPEAK #define OOLITE_SPEECH_SYNTH 1 #define OOLITE_ESPEAK 1 #endif // Pseudo-keywords used for AppKit UI bindings. #define IBOutlet /**/ #define IBAction void #import "Comparison.h" /* Define AppKit constants for events */ enum { NSUpArrowFunctionKey = 0xF700, NSDownArrowFunctionKey = 0xF701, NSLeftArrowFunctionKey = 0xF702, NSRightArrowFunctionKey = 0xF703, NSF1FunctionKey = 0xF704, NSF2FunctionKey = 0xF705, NSF3FunctionKey = 0xF706, NSF4FunctionKey = 0xF707, NSF5FunctionKey = 0xF708, NSF6FunctionKey = 0xF709, NSF7FunctionKey = 0xF70A, NSF8FunctionKey = 0xF70B, NSF9FunctionKey = 0xF70C, NSF10FunctionKey = 0xF70D, NSF11FunctionKey = 0xF70E, NSF12FunctionKey = 0xF70F, NSF13FunctionKey = 0xF710, NSF14FunctionKey = 0xF711, NSF15FunctionKey = 0xF712, NSF16FunctionKey = 0xF713, NSF17FunctionKey = 0xF714, NSF18FunctionKey = 0xF715, NSF19FunctionKey = 0xF716, NSF20FunctionKey = 0xF717, NSF21FunctionKey = 0xF718, NSF22FunctionKey = 0xF719, NSF23FunctionKey = 0xF71A, NSF24FunctionKey = 0xF71B, NSF25FunctionKey = 0xF71C, NSF26FunctionKey = 0xF71D, NSF27FunctionKey = 0xF71E, NSF28FunctionKey = 0xF71F, NSF29FunctionKey = 0xF720, NSF30FunctionKey = 0xF721, NSF31FunctionKey = 0xF722, NSF32FunctionKey = 0xF723, NSF33FunctionKey = 0xF724, NSF34FunctionKey = 0xF725, NSF35FunctionKey = 0xF726, NSInsertFunctionKey = 0xF727, NSDeleteFunctionKey = 0xF728, NSHomeFunctionKey = 0xF729, NSBeginFunctionKey = 0xF72A, NSEndFunctionKey = 0xF72B, NSPageUpFunctionKey = 0xF72C, NSPageDownFunctionKey = 0xF72D, NSPrintScreenFunctionKey = 0xF72E, NSScrollLockFunctionKey = 0xF72F, NSPauseFunctionKey = 0xF730, NSSysReqFunctionKey = 0xF731, NSBreakFunctionKey = 0xF732, NSResetFunctionKey = 0xF733, NSStopFunctionKey = 0xF734, NSMenuFunctionKey = 0xF735, NSUserFunctionKey = 0xF736, NSSystemFunctionKey = 0xF737, NSPrintFunctionKey = 0xF738, NSClearLineFunctionKey = 0xF739, NSClearDisplayFunctionKey = 0xF73A, NSInsertLineFunctionKey = 0xF73B, NSDeleteLineFunctionKey = 0xF73C, NSInsertCharFunctionKey = 0xF73D, NSDeleteCharFunctionKey = 0xF73E, NSPrevFunctionKey = 0xF73F, NSNextFunctionKey = 0xF740, NSSelectFunctionKey = 0xF741, NSExecuteFunctionKey = 0xF742, NSUndoFunctionKey = 0xF743, NSRedoFunctionKey = 0xF744, NSFindFunctionKey = 0xF745, NSHelpFunctionKey = 0xF746, NSModeSwitchFunctionKey = 0xF747 }; #endif #ifndef OOLITE_GNUSTEP #define OOLITE_GNUSTEP 0 #endif #ifndef OOLITE_MAC_OS_X #define OOLITE_MAC_OS_X 0 #endif #ifndef OOLITE_WINDOWS #define OOLITE_WINDOWS 0 #endif #ifndef OOLITE_LINUX #define OOLITE_LINUX 0 #endif #ifndef OOLITE_SDL #define OOLITE_SDL 0 #endif #ifndef OOLITE_SPEECH_SYNTH #define OOLITE_SPEECH_SYNTH 0 #endif #ifndef OOLITE_ESPEAK #define OOLITE_ESPEAK 0 #endif #ifndef OOLITE_64_BIT #define OOLITE_64_BIT 0 #endif #define OOLITE_PROPERTY_SYNTAX (OOLITE_MAC_OS_X || defined(__clang__)) #import "OOLogging.h" @interface NSObject (OODescriptionComponents) /* In order to allow implementations of -description to inherit description components from superclasses, and to allow implementations of -description and -oo_jsDescription to share code, both are implemented as wrappers around -descriptionComponents. -descriptionComponents should provide information about an object without a class name or surrounding punctuation. -description will wrap the components like this: {descriptionComponents} and -oo_jsDescription will wrap them like this: [oo_jsClassName descriptionComponents] */ - (NSString *)descriptionComponents; /* A lot of Oolite's -description implementations are rather long, and many embed other descriptions. -shortDescription provides a truncated alternative, while -shortDescriptionComponents provides a -descriptionComponents-like mechanism to simplify implementation. */ - (NSString *) shortDescription; - (NSString *) shortDescriptionComponents; @end #if OOLITE_MAC_OS_X #define OOLITE_RELEASE_PLIST_ERROR_STRINGS 1 #else #define OOLITE_RELEASE_PLIST_ERROR_STRINGS 0 #endif /* For some reason, return types for some comparison callbacks are typed NSInteger/int under OS X but (more sensibly) NSComparisonResult under GNUstep. */ #if OOLITE_MAC_OS_X typedef NSInteger OOComparisonResult; #else typedef NSComparisonResult OOComparisonResult; #endif /* Fast enumeration (for (x in y) syntax) is supported in all Mac compilers when targeting 10.5 or later, and in gcc 4.6 with the GNU libobjc runtime. At the time of writing, GNUstep stable does not support gcc 4.6, but it already has support for the fast enumeration protocol in its collection classes. All release versions of clang support fast enumeration, assuming libobjc2 or ObjectiveC2.framework is being used. We shall make that assumption. References: http://lists.gnu.org/archive/html/discuss-gnustep/2011-02/msg00019.html http://wiki.gnustep.org/index.php/ObjC2_FAQ -- Ahruman 2011-02-04 */ #if OOLITE_MAC_OS_X #define OOLITE_FAST_ENUMERATION 1 #else #if __clang__ #define OOLITE_FAST_ENUMERATION 1 #elif defined (OOLITE_GNUSTEP) #define OOLITE_FAST_ENUMERATION (OOLITE_GCC_VERSION >= 40600) #endif #endif #ifndef OOLITE_FAST_ENUMERATION #define OOLITE_FAST_ENUMERATION 0 #endif /* Enumeration macros: foreach(VAR, COLLECTION) enumerates the members of an array or set, setting the variable VAR to a member on each pass. foreachkey(VAR, DICT) enumerates the keys of a dictionary the same way. Example: id element = nil; foreach (element, array) { OOLog(@"element", @"%@", element); } These are based on macros by Jens Alfke. */ #if OOLITE_FAST_ENUMERATION #define foreach(VAR, COLLECTION) for(VAR in COLLECTION) #define foreachkey(VAR, DICT) for(VAR in DICT) #else #define foreach(VAR, COLLECTION) for (NSEnumerator *ooForEachEnum = [(COLLECTION) objectEnumerator]; ((VAR) = [ooForEachEnum nextObject]); ) #define foreachkey(VAR, DICT) for (NSEnumerator *ooForEachEnum = [(DICT) keyEnumerator]; ((VAR) = [ooForEachEnum nextObject]); ) #endif /* Support for foreach() with NSEnumerators in GCC. It works without this with for (x in y) support, but we leave it defined to reduce differences between different build environments. */ @interface NSEnumerator (OOForEachSupport) - (NSEnumerator *) objectEnumerator; @end /* @optional directive for protocols: added in Objective-C 2.0. As a nasty, nasty hack, the OOLITE_OPTIONAL(foo) macro allows an optional section with or without @optional. If @optional is not available, it actually ends the protocol and starts an appropriately-named informal protocol, i.e. a category on NSObject. Since it ends the protocol, there can only be one and there's no way to switch back to @required. */ #ifndef OOLITE_HAVE_PROTOCOL_OPTIONAL #define OOLITE_HAVE_PROTOCOL_OPTIONAL (OOLITE_MAC_OS_X || defined(__clang__) || OOLITE_GCC_VERSION >= 40700) #endif #if OOLITE_HAVE_PROTOCOL_OPTIONAL #define OOLITE_OPTIONAL(protocolName) @optional #else #define OOLITE_OPTIONAL(protocolName) @end @interface NSObject (protocolName ## Optional) #endif /* instancetype contextual keyword; added in Clang 3.0ish. Pseudo-type indicating that the return value of an instance method is an instance of the same class as the receiver, or for a class mothod, is an instance of that class. For example, given: @interface Foo: NSObject + (instancetype) fooWithProperty:(id)property; @end @interface Bar: Foo @end the type of [Bar fooWithProperty] is inferred to be Bar *. Clang treats methods of type id as instancetype when their names begin with +alloc, +new, -init, -autorelease, -retain, or -self. For compilers without instancetype support, id is appropriate but less type-safe. NOTE: it is not appropriate to use instancetype for a factory method which chooses which publicly-visible subclass to instantiate based on parameters. For instance, calling one of the OOMaterial convenience factory methods on OOShaderMaterial might return an OOSingleTextureMaterial, so the correct return type is either OOMaterial or id. On the other hand, it is appropriate on factory methods which just wrap the corresponding -init and/or -init + configuration through properties. (Such factory methods should be implemented in terms of [[self alloc] init...].) */ #if __OBJC__ && !__has_feature(objc_instancetype) typedef id instancetype; #endif #ifndef OO_DEBUG // Defined by makefile/Xcode in debug builds. #define OO_DEBUG 0 #endif oolite-1.82/src/Core/OOCocoa.m000066400000000000000000000042121256642440500160650ustar00rootroot00000000000000/* OOCocoa.m Runtime-like and Cocoa/GNUstep compatibility methods. Copyright (C) 2008-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" #import "OOFunctionAttributes.h" @implementation NSObject (OODescriptionComponents) - (NSString *)descriptionComponents { return nil; } - (NSString *)description { NSString *components = nil; components = [self descriptionComponents]; if (components != nil) { return [NSString stringWithFormat:@"<%@ %p>{%@}", [self class], self, components]; } else { return [NSString stringWithFormat:@"<%@ %p>", [self class], self]; } } - (NSString *) shortDescription { NSString *components = nil; components = [self shortDescriptionComponents]; if (components != nil) { return [NSString stringWithFormat:@"<%@ %p>{%@}", [self class], self, components]; } else { return [NSString stringWithFormat:@"<%@ %p>", [self class], self]; } } - (NSString *) shortDescriptionComponents { return nil; } @end @implementation NSEnumerator (OOForEachSupport) - (NSEnumerator *) objectEnumerator { return self; } @end #ifndef NDEBUG id OOConsumeReference(id OO_NS_CONSUMED value) { return value; } #endif oolite-1.82/src/Core/OOCollectionExtractors.h000066400000000000000000000454241256642440500212200ustar00rootroot00000000000000/* OOCollectionExtractors.h Convenience extensions to Foundation collections to extract optional values. In addition to being convenient, these perform type checking. Which is, y'know, good to have. Note on types: ideally, stdint.h types would be used for integers. However, NSNumber doesn't do this, so doing so portably would add new complications. Starting with Oolite 1.69.1, the various integer methods will always clamp values to the range of the return type, rather than truncating like NSNumber. Before that, they weren't entirely inconsistent. The "non-negative float"/"non-negative double" will clamp read values to zero if negative, but will return a negative defaultValue unchanged. Copyright (C) 2007-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import #import "OOFunctionAttributes.h" #include #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE #import "OOMaths.h" #endif @interface NSArray (OOExtractor) - (char) oo_charAtIndex:(NSUInteger)index defaultValue:(char)value; - (short) oo_shortAtIndex:(NSUInteger)index defaultValue:(short)value; - (int) oo_intAtIndex:(NSUInteger)index defaultValue:(int)value; - (long) oo_longAtIndex:(NSUInteger)index defaultValue:(long)value; - (long long) oo_longLongAtIndex:(NSUInteger)index defaultValue:(long long)value; - (NSInteger) oo_integerAtIndex:(NSUInteger)index defaultValue:(NSInteger)value; - (unsigned char) oo_unsignedCharAtIndex:(NSUInteger)index defaultValue:(unsigned char)value; - (unsigned short) oo_unsignedShortAtIndex:(NSUInteger)index defaultValue:(unsigned short)value; - (unsigned int) oo_unsignedIntAtIndex:(NSUInteger)index defaultValue:(unsigned int)value; - (unsigned long) oo_unsignedLongAtIndex:(NSUInteger)index defaultValue:(unsigned long)value; - (unsigned long long) oo_unsignedLongLongAtIndex:(NSUInteger)index defaultValue:(unsigned long long)value; - (NSUInteger) oo_unsignedIntegerAtIndex:(NSUInteger)index defaultValue:(NSUInteger)value; - (BOOL) oo_boolAtIndex:(NSUInteger)index defaultValue:(BOOL)value; #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (BOOL) oo_fuzzyBooleanAtIndex:(NSUInteger)index defaultValue:(float)value; // Reads a float in the range [0, 1], and returns YES with that probability. #endif - (float) oo_floatAtIndex:(NSUInteger)index defaultValue:(float)value; - (double) oo_doubleAtIndex:(NSUInteger)index defaultValue:(double)value; - (float) oo_nonNegativeFloatAtIndex:(NSUInteger)index defaultValue:(float)value; - (double) oo_nonNegativeDoubleAtIndex:(NSUInteger)index defaultValue:(double)value; - (id) oo_objectAtIndex:(NSUInteger)index defaultValue:(id)value; - (id) oo_objectOfClass:(Class)class atIndex:(NSUInteger)index defaultValue:(id)value; - (NSString *) oo_stringAtIndex:(NSUInteger)index defaultValue:(NSString *)value; - (NSArray *) oo_arrayAtIndex:(NSUInteger)index defaultValue:(NSArray *)value; - (NSSet *) oo_setAtIndex:(NSUInteger)index defaultValue:(NSSet *)value; - (NSDictionary *) oo_dictionaryAtIndex:(NSUInteger)index defaultValue:(NSDictionary *)value; - (NSData *) oo_dataAtIndex:(NSUInteger)index defaultValue:(NSData *)value; #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (Vector) oo_vectorAtIndex:(NSUInteger)index defaultValue:(Vector)value; - (Quaternion) oo_quaternionAtIndex:(NSUInteger)index defaultValue:(Quaternion)value; #endif // Default: 0 - (char) oo_charAtIndex:(NSUInteger)index; - (short) oo_shortAtIndex:(NSUInteger)index; - (int) oo_intAtIndex:(NSUInteger)index; - (long) oo_longAtIndex:(NSUInteger)index; - (long long) oo_longLongAtIndex:(NSUInteger)index; - (NSInteger) oo_integerAtIndex:(NSUInteger)index; - (unsigned char) oo_unsignedCharAtIndex:(NSUInteger)index; - (unsigned short) oo_unsignedShortAtIndex:(NSUInteger)index; - (unsigned int) oo_unsignedIntAtIndex:(NSUInteger)index; - (unsigned long) oo_unsignedLongAtIndex:(NSUInteger)index; - (unsigned long long) oo_unsignedLongLongAtIndex:(NSUInteger)index; - (NSUInteger) oo_unsignedIntegerAtIndex:(NSUInteger)index; // Default: NO - (BOOL) oo_boolAtIndex:(NSUInteger)index; #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (BOOL) oo_fuzzyBooleanAtIndex:(NSUInteger)index; // Reads a float in the range [0, 1], and returns YES with that probability. #endif // Default: 0.0 - (float) oo_floatAtIndex:(NSUInteger)index; - (double) oo_doubleAtIndex:(NSUInteger)index; - (float) oo_nonNegativeFloatAtIndex:(NSUInteger)index; - (double) oo_nonNegativeDoubleAtIndex:(NSUInteger)index; // Default: nil - (id) oo_objectAtIndex:(NSUInteger)index; // Differs from objectAtIndex: in that it returns nil rather than throwing NSRangeException. - (id) oo_objectOfClass:(Class)class atIndex:(NSUInteger)index; - (NSString *) oo_stringAtIndex:(NSUInteger)index; - (NSArray *) oo_arrayAtIndex:(NSUInteger)index; - (NSSet *) oo_setAtIndex:(NSUInteger)index; - (NSDictionary *) oo_dictionaryAtIndex:(NSUInteger)index; - (NSData *) oo_dataAtIndex:(NSUInteger)index; #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE // Default: kZeroVector - (Vector) oo_vectorAtIndex:(NSUInteger)index; // Default: kIdentityQuaternion - (Quaternion) oo_quaternionAtIndex:(NSUInteger)index; #endif @end @interface NSDictionary (OOExtractor) - (char) oo_charForKey:(id)key defaultValue:(char)value; - (short) oo_shortForKey:(id)key defaultValue:(short)value; - (int) oo_intForKey:(id)key defaultValue:(int)value; - (long) oo_longForKey:(id)key defaultValue:(long)value; - (long long) oo_longLongForKey:(id)key defaultValue:(long long)value; - (NSInteger) oo_integerForKey:(id)key defaultValue:(NSInteger)value; - (unsigned char) oo_unsignedCharForKey:(id)key defaultValue:(unsigned char)value; - (unsigned short) oo_unsignedShortForKey:(id)key defaultValue:(unsigned short)value; - (unsigned int) oo_unsignedIntForKey:(id)key defaultValue:(unsigned int)value; - (unsigned long) oo_unsignedLongForKey:(id)key defaultValue:(unsigned long)value; - (unsigned long long) oo_unsignedLongLongForKey:(id)key defaultValue:(unsigned long long)value; - (NSUInteger) oo_unsignedIntegerForKey:(id)key defaultValue:(NSUInteger)value; - (BOOL) oo_boolForKey:(id)key defaultValue:(BOOL)value; #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (BOOL) oo_fuzzyBooleanForKey:(id)key defaultValue:(float)value; // Reads a float in the range [0, 1], and returns YES with that probability. #endif - (float) oo_floatForKey:(id)key defaultValue:(float)value; - (double) oo_doubleForKey:(id)key defaultValue:(double)value; - (float) oo_nonNegativeFloatForKey:(id)key defaultValue:(float)value; - (double) oo_nonNegativeDoubleForKey:(id)key defaultValue:(double)value; - (id) oo_objectForKey:(id)key defaultValue:(id)value; - (id) oo_objectOfClass:(Class)class forKey:(id)key defaultValue:(id)value; - (NSString *) oo_stringForKey:(id)key defaultValue:(NSString *)value; - (NSArray *) oo_arrayForKey:(id)key defaultValue:(NSArray *)value; - (NSSet *) oo_setForKey:(id)key defaultValue:(NSSet *)value; - (NSDictionary *) oo_dictionaryForKey:(id)key defaultValue:(NSDictionary *)value; - (NSMutableDictionary *) oo_mutableDictionaryForKey:(id)key defaultValue:(NSDictionary *)value; - (NSData *) oo_dataForKey:(id)key defaultValue:(NSData *)value; #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (Vector) oo_vectorForKey:(id)key defaultValue:(Vector)value; - (HPVector) oo_hpvectorForKey:(id)key defaultValue:(HPVector)value; - (Quaternion) oo_quaternionForKey:(id)key defaultValue:(Quaternion)value; #endif // Default: 0 - (char) oo_charForKey:(id)key; - (short) oo_shortForKey:(id)key; - (int) oo_intForKey:(id)key; - (long) oo_longForKey:(id)key; - (long long) oo_longLongForKey:(id)key; - (NSInteger) oo_integerForKey:(id)key; - (unsigned char) oo_unsignedCharForKey:(id)key; - (unsigned short) oo_unsignedShortForKey:(id)key; - (unsigned int) oo_unsignedIntForKey:(id)key; - (unsigned long) oo_unsignedLongForKey:(id)key; - (unsigned long long) oo_unsignedLongLongForKey:(id)key; - (NSUInteger) oo_unsignedIntegerForKey:(id)key; // Default: NO - (BOOL) oo_boolForKey:(id)key; #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (BOOL) oo_fuzzyBooleanForKey:(id)key; // Reads a float in the range [0, 1], and returns YES with that probability. #endif // Default: 0.0 - (float) oo_floatForKey:(id)key; - (double) oo_doubleForKey:(id)key; - (float) oo_nonNegativeFloatForKey:(id)key; - (double) oo_nonNegativeDoubleForKey:(id)key; // Default: nil // - (id)objectForKey:(id)key; // Already defined - (id) oo_objectOfClass:(Class)class forKey:(id)key; - (NSString *) oo_stringForKey:(id)key; - (NSArray *) oo_arrayForKey:(id)key; - (NSSet *) oo_setForKey:(id)key; - (NSDictionary *) oo_dictionaryForKey:(id)key; - (NSMutableDictionary *) oo_mutableDictionaryForKey:(id)key; - (NSData *) oo_dataForKey:(id)key; #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE // Default: kZeroVector - (Vector) oo_vectorForKey:(id)key; // Default: kZeroHPVector - (HPVector) oo_hpvectorForKey:(id)key; // Default: kIdentityQuaternion - (Quaternion) oo_quaternionForKey:(id)key; #endif @end @interface NSUserDefaults (OOExtractor) - (char) oo_charForKey:(id)key defaultValue:(char)value; - (short) oo_shortForKey:(id)key defaultValue:(short)value; - (int) oo_intForKey:(id)key defaultValue:(int)value; - (long) oo_longForKey:(id)key defaultValue:(long)value; - (long long) oo_longLongForKey:(id)key defaultValue:(long long)value; - (NSInteger) oo_integerForKey:(id)key defaultValue:(NSInteger)value; - (unsigned char) oo_unsignedCharForKey:(id)key defaultValue:(unsigned char)value; - (unsigned short) oo_unsignedShortForKey:(id)key defaultValue:(unsigned short)value; - (unsigned int) oo_unsignedIntForKey:(id)key defaultValue:(unsigned int)value; - (unsigned long) oo_unsignedLongForKey:(id)key defaultValue:(unsigned long)value; - (unsigned long long) oo_unsignedLongLongForKey:(id)key defaultValue:(unsigned long long)value; - (NSUInteger) oo_unsignedIntegerForKey:(id)key defaultValue:(NSUInteger)value; - (BOOL) oo_boolForKey:(id)key defaultValue:(BOOL)value; #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (BOOL) oo_fuzzyBooleanForKey:(id)key defaultValue:(float)value; // Reads a float in the range [0, 1], and returns YES with that probability. #endif - (float) oo_floatForKey:(id)key defaultValue:(float)value; - (double) oo_doubleForKey:(id)key defaultValue:(double)value; - (float) oo_nonNegativeFloatForKey:(id)key defaultValue:(float)value; - (double) oo_nonNegativeDoubleForKey:(id)key defaultValue:(double)value; - (id) oo_objectForKey:(id)key defaultValue:(id)value; - (id) oo_objectOfClass:(Class)class forKey:(id)key defaultValue:(id)value; - (NSString *) oo_stringForKey:(id)key defaultValue:(NSString *)value; - (NSArray *) oo_arrayForKey:(id)key defaultValue:(NSArray *)value; - (NSSet *) oo_setForKey:(id)key defaultValue:(NSSet *)value; - (NSDictionary *) oo_dictionaryForKey:(id)key defaultValue:(NSDictionary *)value; - (NSData *) oo_dataForKey:(id)key defaultValue:(NSData *)value; // Default: 0 - (char) oo_charForKey:(id)key; - (short) oo_shortForKey:(id)key; - (int) oo_intForKey:(id)key; - (long) oo_longForKey:(id)key; - (long long) oo_longLongForKey:(id)key; - (unsigned char) oo_unsignedCharForKey:(id)key; - (unsigned short) oo_unsignedShortForKey:(id)key; - (unsigned int) oo_unsignedIntForKey:(id)key; - (unsigned long) oo_unsignedLongForKey:(id)key; - (unsigned long long) oo_unsignedLongLongForKey:(id)key; // Default: NO // - (BOOL) boolForKey:(id)key; #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (BOOL) oo_fuzzyBooleanForKey:(id)key; // Reads a float in the range [0, 1], and returns YES with that probability. #endif // Default: 0.0 // - (float) floatForKey:(id)key; - (double) oo_doubleForKey:(NSString *)key; - (float) oo_nonNegativeFloatForKey:(id)key; - (double) oo_nonNegativeDoubleForKey:(id)key; // Default: nil // - (id) objectForKey:(id)key; // Already defined - (id) oo_objectOfClass:(Class)class forKey:(id)key; // - (NSString *) stringForKey:(id)key; // - (NSArray *) arrayForKey:(id)key; - (NSSet *) oo_setForKey:(id)key; // - (NSDictionary *) dictionaryForKey:(id)key; // - (NSData *) dataForKey:(id)key; @end @interface NSMutableArray (OOInserter) - (void) oo_addInteger:(long)value; - (void) oo_addUnsignedInteger:(unsigned long)value; - (void) oo_addFloat:(double)value; - (void) oo_addBool:(BOOL)value; #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (void) oo_addVector:(Vector)value; - (void) oo_addQuaternion:(Quaternion)value; #endif - (void) oo_insertInteger:(long)value atIndex:(NSUInteger)index; - (void) oo_insertUnsignedInteger:(unsigned long)value atIndex:(NSUInteger)index; - (void) oo_insertFloat:(double)value atIndex:(NSUInteger)index; - (void) oo_insertBool:(BOOL)value atIndex:(NSUInteger)index; #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (void) oo_insertVector:(Vector)value atIndex:(NSUInteger)index; - (void) oo_insertQuaternion:(Quaternion)value atIndex:(NSUInteger)index; #endif @end @interface NSMutableDictionary (OOInserter) - (void) oo_setInteger:(long)value forKey:(id)key; - (void) oo_setUnsignedInteger:(unsigned long)value forKey:(id)key; - (void) oo_setLongLong:(long long)value forKey:(id)key; - (void) oo_setUnsignedLongLong:(unsigned long long)value forKey:(id)key; - (void) oo_setFloat:(double)value forKey:(id)key; - (void) oo_setBool:(BOOL)value forKey:(id)key; #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (void) oo_setVector:(Vector)value forKey:(id)key; - (void) oo_setHPVector:(HPVector)value forKey:(id)key; - (void) oo_setQuaternion:(Quaternion)value forKey:(id)key; #endif @end @interface NSMutableSet (OOInserter) - (void)oo_addInteger:(long)value; - (void)oo_addUnsignedInteger:(unsigned long)value; - (void)oo_addFloat:(double)value; - (void)oo_addBool:(BOOL)value; #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (void)oo_addVector:(Vector)value; - (void)oo_addQuaternion:(Quaternion)value; #endif @end // *** Value extraction utilities *** /* Utility function to interpret a boolean. May be an NSNumber or any of the following strings (case-insensitive): yes true on no false off */ BOOL OOBooleanFromObject(id object, BOOL defaultValue); #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE /* Utility function to interpret a fuzzy boolean. May be any of the strings accepted by OOBooleanFromObject(), or a number indicating probability of a yes (between 0 and 1). */ BOOL OOFuzzyBooleanFromObject(id object, float defaultValue); #endif float OOFloatFromObject(id object, float defaultValue); double OODoubleFromObject(id object, double defaultValue); float OONonNegativeFloatFromObject(id object, float defaultValue); double OONonNegativeDoubleFromObject(id object, double defaultValue); #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE // These take strings, dictionaries or arrays. Vector OOVectorFromObject(id object, Vector defaultValue); HPVector OOHPVectorFromObject(id object, HPVector defaultValue); Quaternion OOQuaternionFromObject(id object, Quaternion defaultValue); NSDictionary *OOPropertyListFromVector(Vector value); NSDictionary *OOPropertyListFromHPVector(HPVector value); NSDictionary *OOPropertyListFromQuaternion(Quaternion value); #endif OOINLINE long long OOClampInteger(long long value, long long minValue, long long maxValue) ALWAYS_INLINE_FUNC; long long OOLongLongFromObject(id object, long long defaultValue); unsigned long long OOUnsignedLongLongFromObject(id object, unsigned long long defaultValue); OOINLINE long long OOClampInteger(long long value, long long minValue, long long maxValue) { return (minValue < value) ? ((value < maxValue) ? value : maxValue) : minValue; } /* Define an inline function to clamp a give type and its unsigned counterpart. Example: OO_DEFINE_CLAMP_PAIR(char, Char, CHAR) expands to OOINLINE char OOCharFromObject(id object, char defaultValue) { return OOClampInteger(OOLongLongFromObject(object, defaultValue), CHAR_MIN, CHAR_MAX); } OOINLINE unsigned char OOUnsignedCharFromObject(id object, unsigned char defaultValue) { return OOClampInteger(OOLongLongFromObject(object, defaultValue), 0, UCHAR_MAX); } */ #define OO_DEFINE_CLAMP(type, typeName, min, max) \ OOINLINE type OO ## typeName ## FromObject(id object, type defaultValue) \ { \ return (type)OOClampInteger(OOLongLongFromObject(object, defaultValue), min, max); \ } #define OO_DEFINE_CLAMP_PAIR(type, typeName, minMaxSymb) \ OO_DEFINE_CLAMP(type, typeName, minMaxSymb ## _MIN, minMaxSymb ## _MAX) \ OO_DEFINE_CLAMP(unsigned type, Unsigned ## typeName, 0, U ## minMaxSymb ## _MAX) OO_DEFINE_CLAMP_PAIR(char, Char, CHAR) OO_DEFINE_CLAMP_PAIR(short, Short, SHRT) /* When ints or longs are as large as long longs, we can't do any clamping because the clamping code will overflow (unless we add pointless complexity). Instead, we alias the long long versions which don't clamp. Inlines are used instead of macros so that the functions always have the precise type they should; this is necessary for stuff that uses @encode, notably the SenTestingKit framework. */ #define OO_ALIAS_CLAMP_LONG_LONG(type, typeName) \ static inline type OO##typeName##FromObject(id object, type defaultValue) \ { \ return OOLongLongFromObject(object, defaultValue); \ } #define OO_ALIAS_CLAMP_PAIR_LONG_LONG(type, typeName) \ OO_ALIAS_CLAMP_LONG_LONG(type, typeName) \ OO_ALIAS_CLAMP_LONG_LONG(unsigned type, Unsigned##typeName) #if INT_MAX == LLONG_MAX // Should never get here under Mac OS X, but may under GNUstep. OO_ALIAS_CLAMP_PAIR_LONG_LONG(int, Int) #else OO_DEFINE_CLAMP_PAIR(int, Int, INT) #endif #if LONG_MAX == LLONG_MAX OO_ALIAS_CLAMP_PAIR_LONG_LONG(long, Long) #else OO_DEFINE_CLAMP_PAIR(long, Long, LONG) #endif #if OOLITE_64_BIT OOINLINE NSInteger OOIntegerFromObject(id object, NSInteger defaultValue) { return OOLongLongFromObject(object, defaultValue); } OOINLINE NSInteger OOUIntegerFromObject(id object, NSUInteger defaultValue) { return OOUnsignedLongLongFromObject(object, defaultValue); } #else OOINLINE NSInteger OOIntegerFromObject(id object, NSInteger defaultValue) { return OOLongFromObject(object, defaultValue); } OOINLINE NSInteger OOUIntegerFromObject(id object, NSUInteger defaultValue) { return OOUnsignedLongFromObject(object, defaultValue); } #endif #undef OO_DEFINE_CLAMP #undef OO_DEFINE_CLAMP_PAIR #undef OO_ALIAS_CLAMP_LONG_LONG #undef OO_ALIAS_CLAMP_PAIR_LONG_LONG oolite-1.82/src/Core/OOCollectionExtractors.m000066400000000000000000001075331256642440500212250ustar00rootroot00000000000000/* OOCollectionExtractors.m Copyright (C) 2007-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE #import "OOCocoa.h" #import "OOStringParsing.h" #endif #import "OOCollectionExtractors.h" #include #import "OOMaths.h" static NSSet *SetForObject(id object, NSSet *defaultValue); static NSString *StringForObject(id object, NSString *defaultValue); @implementation NSArray (OOExtractor) - (char) oo_charAtIndex:(NSUInteger)index defaultValue:(char)value { return OOCharFromObject([self oo_objectAtIndex:index], value); } - (short) oo_shortAtIndex:(NSUInteger)index defaultValue:(short)value { return OOShortFromObject([self oo_objectAtIndex:index], value); } - (int) oo_intAtIndex:(NSUInteger)index defaultValue:(int)value { return OOIntFromObject([self oo_objectAtIndex:index], value); } - (long) oo_longAtIndex:(NSUInteger)index defaultValue:(long)value { return OOLongFromObject([self oo_objectAtIndex:index], value); } - (long long) oo_longLongAtIndex:(NSUInteger)index defaultValue:(long long)value { return OOLongLongFromObject([self oo_objectAtIndex:index], value); } - (NSInteger) oo_integerAtIndex:(NSUInteger)index defaultValue:(NSInteger)value { return OOIntegerFromObject([self oo_objectAtIndex:index], value); } - (unsigned char) oo_unsignedCharAtIndex:(NSUInteger)index defaultValue:(unsigned char)value { return OOUnsignedCharFromObject([self oo_objectAtIndex:index], value); } - (unsigned short) oo_unsignedShortAtIndex:(NSUInteger)index defaultValue:(unsigned short)value { return OOUnsignedShortFromObject([self oo_objectAtIndex:index], value); } - (unsigned int) oo_unsignedIntAtIndex:(NSUInteger)index defaultValue:(unsigned int)value { return OOUnsignedIntFromObject([self oo_objectAtIndex:index], value); } - (unsigned long) oo_unsignedLongAtIndex:(NSUInteger)index defaultValue:(unsigned long)value { return OOUnsignedLongFromObject([self oo_objectAtIndex:index], value); } - (unsigned long long) oo_unsignedLongLongAtIndex:(NSUInteger)index defaultValue:(unsigned long long)value { return OOUnsignedLongLongFromObject([self oo_objectAtIndex:index], value); } - (NSUInteger) oo_unsignedIntegerAtIndex:(NSUInteger)index defaultValue:(NSUInteger)value { return OOUIntegerFromObject([self oo_objectAtIndex:index], value); } - (BOOL) oo_boolAtIndex:(NSUInteger)index defaultValue:(BOOL)value { return OOBooleanFromObject([self oo_objectAtIndex:index], value); } #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (BOOL) oo_fuzzyBooleanAtIndex:(NSUInteger)index defaultValue:(float)value { return OOFuzzyBooleanFromObject([self oo_objectAtIndex:index], value); } #endif - (float) oo_floatAtIndex:(NSUInteger)index defaultValue:(float)value { return OOFloatFromObject([self oo_objectAtIndex:index], value); } - (double) oo_doubleAtIndex:(NSUInteger)index defaultValue:(double)value { return OODoubleFromObject([self oo_objectAtIndex:index], value); } - (float) oo_nonNegativeFloatAtIndex:(NSUInteger)index defaultValue:(float)value { return OONonNegativeFloatFromObject([self oo_objectAtIndex:index], value); } - (double) oo_nonNegativeDoubleAtIndex:(NSUInteger)index defaultValue:(double)value { return OONonNegativeDoubleFromObject([self oo_objectAtIndex:index], value); } - (id) oo_objectAtIndex:(NSUInteger)index defaultValue:(id)value { id objVal = [self oo_objectAtIndex:index]; id result; if (objVal != nil) result = objVal; else result = value; return result; } - (id) oo_objectOfClass:(Class)class atIndex:(NSUInteger)index defaultValue:(id)value { id objVal = [self oo_objectAtIndex:index]; NSString *result; if ([objVal isKindOfClass:class]) result = objVal; else result = value; return result; } - (NSString *) oo_stringAtIndex:(NSUInteger)index defaultValue:(NSString *)value { return StringForObject([self oo_objectAtIndex:index], value); } - (NSArray *) oo_arrayAtIndex:(NSUInteger)index defaultValue:(NSArray *)value { return [self oo_objectOfClass:[NSArray class] atIndex:index defaultValue:value]; } - (NSSet *) oo_setAtIndex:(NSUInteger)index defaultValue:(NSSet *)value { return SetForObject([self oo_objectAtIndex:index], value); } - (NSDictionary *) oo_dictionaryAtIndex:(NSUInteger)index defaultValue:(NSDictionary *)value { return [self oo_objectOfClass:[NSDictionary class] atIndex:index defaultValue:value]; } - (NSData *) oo_dataAtIndex:(NSUInteger)index defaultValue:(NSData *)value { return [self oo_objectOfClass:[NSData class] atIndex:index defaultValue:value]; } #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (Vector) oo_vectorAtIndex:(NSUInteger)index defaultValue:(Vector)value { return OOVectorFromObject([self oo_objectAtIndex:index], value); } - (Quaternion) oo_quaternionAtIndex:(NSUInteger)index defaultValue:(Quaternion)value { return OOQuaternionFromObject([self oo_objectAtIndex:index], value); } #endif - (char) oo_charAtIndex:(NSUInteger)index { return [self oo_charAtIndex:index defaultValue:0]; } - (short) oo_shortAtIndex:(NSUInteger)index { return [self oo_shortAtIndex:index defaultValue:0]; } - (int) oo_intAtIndex:(NSUInteger)index { return [self oo_intAtIndex:index defaultValue:0]; } - (long) oo_longAtIndex:(NSUInteger)index { return [self oo_longAtIndex:index defaultValue:0]; } - (long long) oo_longLongAtIndex:(NSUInteger)index { return [self oo_longLongAtIndex:index defaultValue:0]; } - (NSInteger) oo_integerAtIndex:(NSUInteger)index { return [self oo_integerAtIndex:index defaultValue:0]; } - (unsigned char) oo_unsignedCharAtIndex:(NSUInteger)index { return [self oo_unsignedCharAtIndex:index defaultValue:0]; } - (unsigned short) oo_unsignedShortAtIndex:(NSUInteger)index { return [self oo_unsignedShortAtIndex:index defaultValue:0]; } - (unsigned int) oo_unsignedIntAtIndex:(NSUInteger)index { return [self oo_unsignedIntAtIndex:index defaultValue:0]; } - (unsigned long) oo_unsignedLongAtIndex:(NSUInteger)index { return [self oo_unsignedLongAtIndex:index defaultValue:0]; } - (unsigned long long) oo_unsignedLongLongAtIndex:(NSUInteger)index { return [self oo_unsignedLongLongAtIndex:index defaultValue:0]; } - (NSUInteger) oo_unsignedIntegerAtIndex:(NSUInteger)index { return [self oo_unsignedIntegerAtIndex:index defaultValue:0]; } - (BOOL) oo_boolAtIndex:(NSUInteger)index { return [self oo_boolAtIndex:index defaultValue:NO]; } #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (BOOL) oo_fuzzyBooleanAtIndex:(NSUInteger)index { return [self oo_fuzzyBooleanAtIndex:index defaultValue:0.0f]; } #endif - (float) oo_floatAtIndex:(NSUInteger)index { return OOFloatFromObject([self oo_objectAtIndex:index], 0.0f); } - (double) oo_doubleAtIndex:(NSUInteger)index { return OODoubleFromObject([self oo_objectAtIndex:index], 0.0); } - (float) oo_nonNegativeFloatAtIndex:(NSUInteger)index { return OONonNegativeFloatFromObject([self oo_objectAtIndex:index], 0.0f); } - (double) oo_nonNegativeDoubleAtIndex:(NSUInteger)index { return OONonNegativeDoubleFromObject([self oo_objectAtIndex:index], 0.0); } - (id) oo_objectAtIndex:(NSUInteger)index { if (index < [self count]) return [self objectAtIndex:index]; else return nil; } - (id) oo_objectOfClass:(Class)class atIndex:(NSUInteger)index { return [self oo_objectOfClass:class atIndex:index defaultValue:nil]; } - (NSString *) oo_stringAtIndex:(NSUInteger)index { return [self oo_stringAtIndex:index defaultValue:nil]; } - (NSArray *) oo_arrayAtIndex:(NSUInteger)index { return [self oo_arrayAtIndex:index defaultValue:nil]; } - (NSSet *) oo_setAtIndex:(NSUInteger)index { return [self oo_setAtIndex:index defaultValue:nil]; } - (NSDictionary *) oo_dictionaryAtIndex:(NSUInteger)index { return [self oo_dictionaryAtIndex:index defaultValue:nil]; } - (NSData *) oo_dataAtIndex:(NSUInteger)index { return [self oo_dataAtIndex:index defaultValue:nil]; } #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (Vector) oo_vectorAtIndex:(NSUInteger)index { return [self oo_vectorAtIndex:index defaultValue:kZeroVector]; } - (Quaternion) oo_quaternionAtIndex:(NSUInteger)index { return [self oo_quaternionAtIndex:index defaultValue:kIdentityQuaternion]; } #endif @end @implementation NSDictionary (OOExtractor) - (char) oo_charForKey:(id)key defaultValue:(char)value { return OOCharFromObject([self objectForKey:key], value); } - (short) oo_shortForKey:(id)key defaultValue:(short)value { return OOShortFromObject([self objectForKey:key], value); } - (int) oo_intForKey:(id)key defaultValue:(int)value { return OOIntFromObject([self objectForKey:key], value); } - (long) oo_longForKey:(id)key defaultValue:(long)value { return OOLongFromObject([self objectForKey:key], value); } - (long long) oo_longLongForKey:(id)key defaultValue:(long long)value { return OOLongLongFromObject([self objectForKey:key], value); } - (NSInteger) oo_integerForKey:(id)key defaultValue:(NSInteger)value { return OOIntegerFromObject([self objectForKey:key], value); } - (unsigned char) oo_unsignedCharForKey:(id)key defaultValue:(unsigned char)value { return OOUnsignedCharFromObject([self objectForKey:key], value); } - (unsigned short) oo_unsignedShortForKey:(id)key defaultValue:(unsigned short)value { return OOUnsignedShortFromObject([self objectForKey:key], value); } - (unsigned int) oo_unsignedIntForKey:(id)key defaultValue:(unsigned int)value { return OOUnsignedIntFromObject([self objectForKey:key], value); } - (unsigned long) oo_unsignedLongForKey:(id)key defaultValue:(unsigned long)value { return OOUnsignedLongFromObject([self objectForKey:key], value); } - (unsigned long long) oo_unsignedLongLongForKey:(id)key defaultValue:(unsigned long long)value { return OOUnsignedLongLongFromObject([self objectForKey:key], value); } - (NSUInteger) oo_unsignedIntegerForKey:(id)key defaultValue:(NSUInteger)value { return OOUIntegerFromObject([self objectForKey:key], value); } - (BOOL) oo_boolForKey:(id)key defaultValue:(BOOL)value { return OOBooleanFromObject([self objectForKey:key], value); } #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (BOOL) oo_fuzzyBooleanForKey:(id)key defaultValue:(float)value { return OOFuzzyBooleanFromObject([self objectForKey:key], value); } #endif - (float) oo_floatForKey:(id)key defaultValue:(float)value { return OOFloatFromObject([self objectForKey:key], value); } - (double) oo_doubleForKey:(id)key defaultValue:(double)value { return OODoubleFromObject([self objectForKey:key], value); } - (float) oo_nonNegativeFloatForKey:(id)key defaultValue:(float)value { return OONonNegativeFloatFromObject([self objectForKey:key], value); } - (double) oo_nonNegativeDoubleForKey:(id)key defaultValue:(double)value { return OONonNegativeDoubleFromObject([self objectForKey:key], value); } - (id) oo_objectForKey:(id)key defaultValue:(id)value { id objVal = [self objectForKey:key]; id result; if (objVal != nil) result = objVal; else result = value; return result; } - (id) oo_objectOfClass:(Class)class forKey:(id)key defaultValue:(id)value { id objVal = [self objectForKey:key]; id result; if ([objVal isKindOfClass:class]) result = objVal; else result = value; return result; } - (NSString *) oo_stringForKey:(id)key defaultValue:(NSString *)value { return StringForObject([self objectForKey:key], value); } - (NSArray *) oo_arrayForKey:(id)key defaultValue:(NSArray *)value { return [self oo_objectOfClass:[NSArray class] forKey:key defaultValue:value]; } - (NSSet *) oo_setForKey:(id)key defaultValue:(NSSet *)value { return SetForObject([self objectForKey:key], value); } - (NSDictionary *) oo_dictionaryForKey:(id)key defaultValue:(NSDictionary *)value { return [self oo_objectOfClass:[NSDictionary class] forKey:key defaultValue:value]; } - (NSMutableDictionary *) oo_mutableDictionaryForKey:(id)key defaultValue:(NSDictionary *)value { return [self oo_objectOfClass:[NSMutableDictionary class] forKey:key defaultValue:value]; } - (NSData *) oo_dataForKey:(id)key defaultValue:(NSData *)value { return [self oo_objectOfClass:[NSData class] forKey:key defaultValue:value]; } #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (Vector) oo_vectorForKey:(id)key defaultValue:(Vector)value { return OOVectorFromObject([self objectForKey:key], value); } - (HPVector) oo_hpvectorForKey:(id)key defaultValue:(HPVector)value { return OOHPVectorFromObject([self objectForKey:key], value); } - (Quaternion) oo_quaternionForKey:(id)key defaultValue:(Quaternion)value { return OOQuaternionFromObject([self objectForKey:key], value); } #endif - (char) oo_charForKey:(id)key { return [self oo_charForKey:key defaultValue:0]; } - (short) oo_shortForKey:(id)key { return [self oo_shortForKey:key defaultValue:0]; } - (int) oo_intForKey:(id)key { return [self oo_intForKey:key defaultValue:0]; } - (long) oo_longForKey:(id)key { return [self oo_longForKey:key defaultValue:0]; } - (long long) oo_longLongForKey:(id)key { return [self oo_longLongForKey:key defaultValue:0]; } - (NSInteger) oo_integerForKey:(id)key { return [self oo_integerForKey:key defaultValue:0]; } - (unsigned char) oo_unsignedCharForKey:(id)key { return [self oo_unsignedCharForKey:key defaultValue:0]; } - (unsigned short) oo_unsignedShortForKey:(id)key { return [self oo_unsignedShortForKey:key defaultValue:0]; } - (unsigned int) oo_unsignedIntForKey:(id)key { return [self oo_unsignedIntForKey:key defaultValue:0]; } - (unsigned long) oo_unsignedLongForKey:(id)key { return [self oo_unsignedLongForKey:key defaultValue:0]; } - (NSUInteger) oo_unsignedIntegerForKey:(id)key { return [self oo_unsignedIntegerForKey:key defaultValue:0]; } - (unsigned long long) oo_unsignedLongLongForKey:(id)key { return [self oo_unsignedLongLongForKey:key defaultValue:0]; } - (BOOL) oo_boolForKey:(id)key { return [self oo_boolForKey:key defaultValue:NO]; } #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (BOOL) oo_fuzzyBooleanForKey:(id)key { return [self oo_fuzzyBooleanForKey:key defaultValue:0.0f]; } #endif - (float) oo_floatForKey:(id)key { return OOFloatFromObject([self objectForKey:key], 0.0f); } - (double) oo_doubleForKey:(id)key { return OODoubleFromObject([self objectForKey:key], 0.0); } - (float) oo_nonNegativeFloatForKey:(id)key { return OONonNegativeFloatFromObject([self objectForKey:key], 0.0f); } - (double) oo_nonNegativeDoubleForKey:(id)key { return OONonNegativeDoubleFromObject([self objectForKey:key], 0.0); } - (id) oo_objectOfClass:(Class)class forKey:(id)key { return [self oo_objectOfClass:class forKey:key defaultValue:nil]; } - (NSString *) oo_stringForKey:(id)key { return [self oo_stringForKey:key defaultValue:nil]; } - (NSArray *) oo_arrayForKey:(id)key { return [self oo_arrayForKey:key defaultValue:nil]; } - (NSSet *) oo_setForKey:(id)key { return [self oo_setForKey:key defaultValue:nil]; } - (NSDictionary *) oo_dictionaryForKey:(id)key { return [self oo_dictionaryForKey:key defaultValue:nil]; } - (NSMutableDictionary *) oo_mutableDictionaryForKey:(id)key { return [self oo_mutableDictionaryForKey:key defaultValue:nil]; } - (NSData *) oo_dataForKey:(id)key { return [self oo_dataForKey:key defaultValue:nil]; } #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (Vector) oo_vectorForKey:(id)key { return [self oo_vectorForKey:key defaultValue:kZeroVector]; } - (HPVector) oo_hpvectorForKey:(id)key { return [self oo_hpvectorForKey:key defaultValue:kZeroHPVector]; } - (Quaternion) oo_quaternionForKey:(id)key { return [self oo_quaternionForKey:key defaultValue:kIdentityQuaternion]; } #endif @end @implementation NSUserDefaults (OOExtractor) - (char) oo_charForKey:(id)key defaultValue:(char)value { return OOCharFromObject([self objectForKey:key], value); } - (short) oo_shortForKey:(id)key defaultValue:(short)value { return OOShortFromObject([self objectForKey:key], value); } - (int) oo_intForKey:(id)key defaultValue:(int)value { return OOIntFromObject([self objectForKey:key], value); } - (long) oo_longForKey:(id)key defaultValue:(long)value { return OOLongFromObject([self objectForKey:key], value); } - (long long) oo_longLongForKey:(id)key defaultValue:(long long)value { return OOLongLongFromObject([self objectForKey:key], value); } - (NSInteger) oo_integerForKey:(id)key defaultValue:(NSInteger)value { return OOIntegerFromObject([self objectForKey:key], value); } - (unsigned char) oo_unsignedCharForKey:(id)key defaultValue:(unsigned char)value { return OOUnsignedCharFromObject([self objectForKey:key], value); } - (unsigned short) oo_unsignedShortForKey:(id)key defaultValue:(unsigned short)value { return OOUnsignedShortFromObject([self objectForKey:key], value); } - (unsigned int) oo_unsignedIntForKey:(id)key defaultValue:(unsigned int)value { return OOUnsignedIntFromObject([self objectForKey:key], value); } - (unsigned long) oo_unsignedLongForKey:(id)key defaultValue:(unsigned long)value { return OOUnsignedLongFromObject([self objectForKey:key], value); } - (unsigned long long) oo_unsignedLongLongForKey:(id)key defaultValue:(unsigned long long)value { return OOUnsignedLongLongFromObject([self objectForKey:key], value); } - (NSUInteger) oo_unsignedIntegerForKey:(id)key defaultValue:(NSUInteger)value { return OOUIntegerFromObject([self objectForKey:key], value); } - (BOOL) oo_boolForKey:(id)key defaultValue:(BOOL)value { return OOBooleanFromObject([self objectForKey:key], value); } #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (BOOL) oo_fuzzyBooleanForKey:(id)key defaultValue:(float)value { return OOFuzzyBooleanFromObject([self objectForKey:key], value); } #endif - (float) oo_floatForKey:(id)key defaultValue:(float)value { return OOFloatFromObject([self objectForKey:key], value); } - (double) oo_doubleForKey:(id)key defaultValue:(double)value { return OODoubleFromObject([self objectForKey:key], value); } - (float) oo_nonNegativeFloatForKey:(id)key defaultValue:(float)value { return OONonNegativeFloatFromObject([self objectForKey:key], value); } - (double) oo_nonNegativeDoubleForKey:(id)key defaultValue:(double)value { return OONonNegativeDoubleFromObject([self objectForKey:key], value); } - (id) oo_objectForKey:(id)key defaultValue:(id)value { id objVal = [self objectForKey:key]; id result; if (objVal != nil) result = objVal; else result = value; return result; } - (id) oo_objectOfClass:(Class)class forKey:(id)key defaultValue:(id)value { id objVal = [self objectForKey:key]; id result; if ([objVal isKindOfClass:class]) result = objVal; else result = value; return result; } - (NSString *) oo_stringForKey:(id)key defaultValue:(NSString *)value { return StringForObject([self objectForKey:key], value); } - (NSArray *) oo_arrayForKey:(id)key defaultValue:(NSArray *)value { return [self oo_objectOfClass:[NSArray class] forKey:key defaultValue:value]; } - (NSSet *) oo_setForKey:(id)key defaultValue:(NSSet *)value { return SetForObject([self objectForKey:key], value); } - (NSDictionary *) oo_dictionaryForKey:(id)key defaultValue:(NSDictionary *)value { return [self oo_objectOfClass:[NSDictionary class] forKey:key defaultValue:value]; } - (NSData *) oo_dataForKey:(id)key defaultValue:(NSData *)value { return [self oo_objectOfClass:[NSData class] forKey:key defaultValue:value]; } - (char) oo_charForKey:(id)key { return [self oo_charForKey:key defaultValue:0]; } - (short) oo_shortForKey:(id)key { return [self oo_shortForKey:key defaultValue:0]; } - (int) oo_intForKey:(id)key { return [self oo_intForKey:key defaultValue:0]; } - (long) oo_longForKey:(id)key { return [self oo_longForKey:key defaultValue:0]; } - (long long) oo_longLongForKey:(id)key { return [self oo_longLongForKey:key defaultValue:0]; } - (unsigned char) oo_unsignedCharForKey:(id)key { return [self oo_unsignedCharForKey:key defaultValue:0]; } - (unsigned short) oo_unsignedShortForKey:(id)key { return [self oo_unsignedShortForKey:key defaultValue:0]; } - (unsigned int) oo_unsignedIntForKey:(id)key { return [self oo_unsignedIntForKey:key defaultValue:0]; } - (unsigned long) oo_unsignedLongForKey:(id)key { return [self oo_unsignedLongForKey:key defaultValue:0]; } - (unsigned long long) oo_unsignedLongLongForKey:(id)key { return [self oo_unsignedLongLongForKey:key defaultValue:0]; } #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (BOOL) oo_fuzzyBooleanForKey:(id)key { return [self oo_fuzzyBooleanForKey:key defaultValue:0.0f]; } #endif - (double) oo_doubleForKey:(NSString *)key { return OODoubleFromObject([self objectForKey:key], 0.0); } - (float) oo_nonNegativeFloatForKey:(id)key { return OONonNegativeFloatFromObject([self objectForKey:key], 0.0f); } - (double) oo_nonNegativeDoubleForKey:(id)key { return OONonNegativeDoubleFromObject([self objectForKey:key], 0.0); } - (id) oo_objectOfClass:(Class)class forKey:(id)key { return [self oo_objectOfClass:class forKey:key defaultValue:nil]; } - (NSSet *) oo_setForKey:(id)key { return [self oo_setForKey:key defaultValue:nil]; } @end @implementation NSMutableArray (OOInserter) - (void) oo_addInteger:(long)value { [self addObject:[NSNumber numberWithLong:value]]; } - (void) oo_addUnsignedInteger:(unsigned long)value { [self addObject:[NSNumber numberWithUnsignedLong:value]]; } - (void) oo_addFloat:(double)value { [self addObject:[NSNumber numberWithDouble:value]]; } - (void) oo_addBool:(BOOL)value { [self addObject:[NSNumber numberWithBool:value]]; } #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (void) oo_addVector:(Vector)value { [self addObject:OOPropertyListFromVector(value)]; } - (void) oo_addQuaternion:(Quaternion)value { [self addObject:OOPropertyListFromQuaternion(value)]; } #endif - (void) oo_insertInteger:(long)value atIndex:(NSUInteger)index { [self insertObject:[NSNumber numberWithLong:value] atIndex:index]; } - (void) oo_insertUnsignedInteger:(unsigned long)value atIndex:(NSUInteger)index { [self insertObject:[NSNumber numberWithUnsignedLong:value] atIndex:index]; } - (void) oo_insertFloat:(double)value atIndex:(NSUInteger)index { [self insertObject:[NSNumber numberWithDouble:value] atIndex:index]; } - (void) oo_insertBool:(BOOL)value atIndex:(NSUInteger)index { [self insertObject:[NSNumber numberWithBool:value] atIndex:index]; } #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (void) oo_insertVector:(Vector)value atIndex:(NSUInteger)index { [self insertObject:OOPropertyListFromVector(value) atIndex:index]; } - (void) oo_insertQuaternion:(Quaternion)value atIndex:(NSUInteger)index { [self insertObject:OOPropertyListFromQuaternion(value) atIndex:index]; } #endif @end @implementation NSMutableDictionary (OOInserter) - (void) oo_setInteger:(long)value forKey:(id)key { [self setObject:[NSNumber numberWithLong:value] forKey:key]; } - (void) oo_setUnsignedInteger:(unsigned long)value forKey:(id)key { [self setObject:[NSNumber numberWithUnsignedLong:value] forKey:key]; } - (void) oo_setLongLong:(long long)value forKey:(id)key { [self setObject:[NSNumber numberWithLongLong:value] forKey:key]; } - (void) oo_setUnsignedLongLong:(unsigned long long)value forKey:(id)key { [self setObject:[NSNumber numberWithUnsignedLongLong:value] forKey:key]; } - (void) oo_setFloat:(double)value forKey:(id)key { [self setObject:[NSNumber numberWithDouble:value] forKey:key]; } - (void) oo_setBool:(BOOL)value forKey:(id)key { [self setObject:[NSNumber numberWithBool:value] forKey:key]; } #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (void) oo_setVector:(Vector)value forKey:(id)key { [self setObject:OOPropertyListFromVector(value) forKey:key]; } - (void) oo_setHPVector:(HPVector)value forKey:(id)key { [self setObject:OOPropertyListFromHPVector(value) forKey:key]; } - (void) oo_setQuaternion:(Quaternion)value forKey:(id)key { [self setObject:OOPropertyListFromQuaternion(value) forKey:key]; } #endif @end @implementation NSMutableSet (OOInserter) - (void) oo_addInteger:(long)value { [self addObject:[NSNumber numberWithLong:value]]; } - (void) oo_addUnsignedInteger:(unsigned long)value { [self addObject:[NSNumber numberWithUnsignedLong:value]]; } - (void) oo_addFloat:(double)value { [self addObject:[NSNumber numberWithDouble:value]]; } - (void) oo_addBool:(BOOL)value { [self addObject:[NSNumber numberWithBool:value]]; } #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE - (void) oo_addVector:(Vector)value { [self addObject:OOPropertyListFromVector(value)]; } - (void) oo_addQuaternion:(Quaternion)value { [self addObject:OOPropertyListFromQuaternion(value)]; } #endif @end long long OOLongLongFromObject(id object, long long defaultValue) { long long llValue; if ([object respondsToSelector:@selector(longLongValue)]) llValue = [object longLongValue]; else if ([object respondsToSelector:@selector(longValue)]) llValue = [object longValue]; else if ([object respondsToSelector:@selector(intValue)]) llValue = [object intValue]; else llValue = defaultValue; return llValue; } unsigned long long OOUnsignedLongLongFromObject(id object, unsigned long long defaultValue) { unsigned long long ullValue; if ([object respondsToSelector:@selector(unsignedLongLongValue)]) ullValue = [object unsignedLongLongValue]; else if ([object respondsToSelector:@selector(unsignedLongValue)]) ullValue = [object unsignedLongValue]; else if ([object respondsToSelector:@selector(unsignedIntValue)]) ullValue = [object unsignedIntValue]; else if ([object respondsToSelector:@selector(intValue)]) ullValue = [object intValue]; else ullValue = defaultValue; return ullValue; } static inline BOOL IsSpaceOrTab(int value) { return value == ' ' || value == '\t'; } static BOOL IsZeroString(NSString *string) { /* I don't particularly like regexps, but there are occasions... To match NSString's behaviour for intValue etc. with non-zero numbers, we need to skip any leading spaces or tabs (but not line breaks), get an optional minus sign, then at least one 0. Any trailing junk is ignored. It is assumed that this function is called for strings whose numerical value has already been determined to be 0. */ unsigned long i = 0, count = [string length]; #define PEEK() ((i >= count) ? -1 : [string characterAtIndex:i]) while (IsSpaceOrTab(PEEK())) ++i; // Skip spaces and tabs if (PEEK() == ' ') ++i; // Skip optional hyphen-minus return PEEK() == '0'; // If this is a 0, it's a numerical string. #undef PEEK } static BOOL BooleanFromString(NSString *string, BOOL defaultValue) { if (NSOrderedSame == [string caseInsensitiveCompare:@"yes"] || NSOrderedSame == [string caseInsensitiveCompare:@"true"] || NSOrderedSame == [string caseInsensitiveCompare:@"on"] || [string doubleValue] != 0.0) // Floating point is used so values like @"0.1" are treated as nonzero. { return YES; } else if (NSOrderedSame == [string caseInsensitiveCompare:@"no"] || NSOrderedSame == [string caseInsensitiveCompare:@"false"] || NSOrderedSame == [string caseInsensitiveCompare:@"off"] || IsZeroString(string)) { return NO; } return defaultValue; } #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE static float FuzzyBooleanProbabilityFromString(NSString *string, float defaultValue) { if (NSOrderedSame == [string caseInsensitiveCompare:@"yes"] || NSOrderedSame == [string caseInsensitiveCompare:@"true"] || NSOrderedSame == [string caseInsensitiveCompare:@"on"] || [string doubleValue] != 0.0) // Floating point is used so values like @"0.1" are treated as nonzero. { return 1.0f; } else if (NSOrderedSame == [string caseInsensitiveCompare:@"no"] || NSOrderedSame == [string caseInsensitiveCompare:@"false"] || NSOrderedSame == [string caseInsensitiveCompare:@"off"] || IsZeroString(string)) { return 0.0f; } return defaultValue; } #endif BOOL OOBooleanFromObject(id object, BOOL defaultValue) { BOOL result; if ([object isKindOfClass:[NSString class]]) { result = BooleanFromString(object, defaultValue); } else { if ([object respondsToSelector:@selector(boolValue)]) result = [object boolValue]; else if ([object respondsToSelector:@selector(intValue)]) result = [object intValue] != 0; else result = defaultValue; } return result; } #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE BOOL OOFuzzyBooleanFromObject(id object, float defaultValue) { float probability; if ([object isKindOfClass:[NSString class]]) { probability = [object floatValue]; // If our string represents zero, it might be erroneous input or simply yes/no, // true/false or on/off valid boolean strings. Act on it. if (probability == 0.0f && !IsZeroString(object)) { probability = FuzzyBooleanProbabilityFromString(object, defaultValue); } } else { probability = OOFloatFromObject(object, defaultValue); } /* This will always be NO for negative values and YES for values greater than 1, as expected. randf() is always less than 1, so < is the correct operator here. */ return randf() < probability; } #endif float OOFloatFromObject(id object, float defaultValue) { float result; if ([object respondsToSelector:@selector(floatValue)]) { result = [object floatValue]; if (result == 0.0f && [object isKindOfClass:[NSString class]] && !IsZeroString(object)) result = defaultValue; } else if ([object respondsToSelector:@selector(doubleValue)]) result = [object doubleValue]; else if ([object respondsToSelector:@selector(intValue)]) result = [object intValue]; else result = defaultValue; return result; } double OODoubleFromObject(id object, double defaultValue) { double result; if ([object respondsToSelector:@selector(doubleValue)]) { result = [object doubleValue]; if (result == 0.0 && [object isKindOfClass:[NSString class]] && !IsZeroString(object)) result = defaultValue; } else if ([object respondsToSelector:@selector(floatValue)]) result = [object floatValue]; else if ([object respondsToSelector:@selector(intValue)]) result = [object intValue]; else result = defaultValue; return result; } float OONonNegativeFloatFromObject(id object, float defaultValue) { float result; if ([object respondsToSelector:@selector(floatValue)]) result = [object floatValue]; else if ([object respondsToSelector:@selector(doubleValue)]) result = [object doubleValue]; else if ([object respondsToSelector:@selector(intValue)]) result = [object intValue]; else return defaultValue; // Don't clamp default return fmax(result, 0.0f); } double OONonNegativeDoubleFromObject(id object, double defaultValue) { double result; if ([object respondsToSelector:@selector(doubleValue)]) result = [object doubleValue]; else if ([object respondsToSelector:@selector(floatValue)]) result = [object floatValue]; else if ([object respondsToSelector:@selector(intValue)]) result = [object intValue]; else return defaultValue; // Don't clamp default return fmax(result, 0.0); } #ifndef OOCOLLECTIONEXTRACTORS_SIMPLE Vector OOVectorFromObject(id object, Vector defaultValue) { Vector result = defaultValue; NSDictionary *dict = nil; if ([object isKindOfClass:[NSString class]]) { // This will only write result if a valid vector is found, and will write an error message otherwise. ScanVectorFromString(object, &result); } else if ([object isKindOfClass:[NSArray class]] && [object count] == 3) { result.x = [object oo_floatAtIndex:0]; result.y = [object oo_floatAtIndex:1]; result.z = [object oo_floatAtIndex:2]; } else if ([object isKindOfClass:[NSDictionary class]]) { dict = object; // Require at least one of the keys x, y, or z if ([dict objectForKey:@"x"] != nil || [dict objectForKey:@"y"] != nil || [dict objectForKey:@"z"] != nil) { // Note: uses 0 for unknown components rather than components of defaultValue. result.x = [dict oo_floatForKey:@"x" defaultValue:0.0f]; result.y = [dict oo_floatForKey:@"y" defaultValue:0.0f]; result.z = [dict oo_floatForKey:@"z" defaultValue:0.0f]; } } return result; } HPVector OOHPVectorFromObject(id object, HPVector defaultValue) { HPVector result = defaultValue; NSDictionary *dict = nil; if ([object isKindOfClass:[NSString class]]) { // This will only write result if a valid vector is found, and will write an error message otherwise. ScanHPVectorFromString(object, &result); } else if ([object isKindOfClass:[NSArray class]] && [object count] == 3) { result.x = [object oo_doubleAtIndex:0]; result.y = [object oo_doubleAtIndex:1]; result.z = [object oo_doubleAtIndex:2]; } else if ([object isKindOfClass:[NSDictionary class]]) { dict = object; // Require at least one of the keys x, y, or z if ([dict objectForKey:@"x"] != nil || [dict objectForKey:@"y"] != nil || [dict objectForKey:@"z"] != nil) { // Note: uses 0 for unknown components rather than components of defaultValue. result.x = [dict oo_doubleForKey:@"x" defaultValue:0.0]; result.y = [dict oo_doubleForKey:@"y" defaultValue:0.0]; result.z = [dict oo_doubleForKey:@"z" defaultValue:0.0]; } } return result; } Quaternion OOQuaternionFromObject(id object, Quaternion defaultValue) { Quaternion result = defaultValue; NSDictionary *dict = nil; if ([object isKindOfClass:[NSString class]]) { // This will only write result if a valid quaternion is found, and will write an error message otherwise. ScanQuaternionFromString(object, &result); } else if ([object isKindOfClass:[NSArray class]] && [object count] == 4) { result.w = [object oo_floatAtIndex:0]; result.x = [object oo_floatAtIndex:1]; result.y = [object oo_floatAtIndex:2]; result.z = [object oo_floatAtIndex:3]; } else if ([object isKindOfClass:[NSDictionary class]]) { dict = object; // Require at least one of the keys w, x, y, or z if ([dict objectForKey:@"w"] != nil || [dict objectForKey:@"x"] != nil || [dict objectForKey:@"y"] != nil || [dict objectForKey:@"z"] != nil) { // Note: uses 0 for unknown components rather than components of defaultValue. result.w = [dict oo_floatForKey:@"w" defaultValue:0.0f]; result.x = [dict oo_floatForKey:@"x" defaultValue:0.0f]; result.y = [dict oo_floatForKey:@"y" defaultValue:0.0f]; result.z = [dict oo_floatForKey:@"z" defaultValue:0.0f]; } } return result; } NSDictionary *OOPropertyListFromVector(Vector value) { return [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithFloat:value.x], @"x", [NSNumber numberWithFloat:value.y], @"y", [NSNumber numberWithFloat:value.z], @"z", nil]; } NSDictionary *OOPropertyListFromHPVector(HPVector value) { return [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithDouble:value.x], @"x", [NSNumber numberWithDouble:value.y], @"y", [NSNumber numberWithDouble:value.z], @"z", nil]; } NSDictionary *OOPropertyListFromQuaternion(Quaternion value) { return [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithFloat:value.w], @"w", [NSNumber numberWithFloat:value.x], @"x", [NSNumber numberWithFloat:value.y], @"y", [NSNumber numberWithFloat:value.z], @"z", nil]; } #endif static NSSet *SetForObject(id object, NSSet *defaultValue) { if ([object isKindOfClass:[NSArray class]]) return [NSSet setWithArray:object]; else if ([object isKindOfClass:[NSSet class]]) return [[object copy] autorelease]; return defaultValue; } static NSString *StringForObject(id object, NSString *defaultValue) { if ([object isKindOfClass:[NSString class]]) return object; else if ([object respondsToSelector:@selector(stringValue)]) return [object stringValue]; return defaultValue; } oolite-1.82/src/Core/OOColor.h000066400000000000000000000104571256642440500161220ustar00rootroot00000000000000/* OOColor.h An RGBA colour in device colour space. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #import "OOOpenGL.h" typedef struct { float r, g, b, a; } OORGBAComponents; typedef struct { float h, s, b, a; } OOHSBAComponents; @interface OOColor: NSObject { @private float rgba[4]; } + (OOColor *) colorWithHue:(float)hue saturation:(float)saturation brightness:(float)brightness alpha:(float)alpha; // Note: hue in 0..1 + (OOColor *) colorWithRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha; + (OOColor *) colorWithWhite:(float)white alpha:(float)alpha; + (OOColor *) colorWithRGBAComponents:(OORGBAComponents)components; + (OOColor *) colorWithHSBAComponents:(OOHSBAComponents)components; // Note: hue in 0..360 // Flexible color creator; takes a selector name, a string with components, or an array. + (OOColor *) colorWithDescription:(id)description; // Like +colorWithDescription:, but forces brightness of at least 0.5. + (OOColor *) brightColorWithDescription:(id)description; /* Like +colorWithDescription:, but multiplies saturation by provided factor. If the colour is an HSV dictionary, it may specify a saturation greater than 1.0 to override the scaling. */ + (OOColor *) colorWithDescription:(id)description saturationFactor:(float)factor; // Creates a colour given a string with components. + (OOColor *) colorFromString:(NSString*) colorFloatString; + (OOColor *) blackColor; // 0.0 white + (OOColor *) darkGrayColor; // 0.333 white + (OOColor *) lightGrayColor; // 0.667 white + (OOColor *) whiteColor; // 1.0 white + (OOColor *) grayColor; // 0.5 white + (OOColor *) redColor; // 1.0, 0.0, 0.0 RGB + (OOColor *) greenColor; // 0.0, 1.0, 0.0 RGB + (OOColor *) blueColor; // 0.0, 0.0, 1.0 RGB + (OOColor *) cyanColor; // 0.0, 1.0, 1.0 RGB + (OOColor *) yellowColor; // 1.0, 1.0, 0.0 RGB + (OOColor *) magentaColor; // 1.0, 0.0, 1.0 RGB + (OOColor *) orangeColor; // 1.0, 0.5, 0.0 RGB + (OOColor *) purpleColor; // 0.5, 0.0, 0.5 RGB + (OOColor *) brownColor; // 0.6, 0.4, 0.2 RGB + (OOColor *) clearColor; // 0.0 white, 0.0 alpha // Linear blend in working colour space (no attempt at gamma correction). - (OOColor *) blendedColorWithFraction:(float)fraction ofColor:(OOColor *)color; // Get the red, green, or blue components. - (float) redComponent; - (float) greenComponent; - (float) blueComponent; - (void) getRed:(float *)red green:(float *)green blue:(float *)blue alpha:(float *)alpha; - (OORGBAComponents) rgbaComponents; - (BOOL) isBlack; - (BOOL) isWhite; /* Get the components as hue, saturation, or brightness. IMPORTANT: for reasons of bugwards compatibility, these return hue values in the range [0, 360], but +colorWithedHue:... expects values in the range [0, 1]. */ - (float) hueComponent; - (float) saturationComponent; - (float) brightnessComponent; - (void) getHue:(float *)hue saturation:(float *)saturation brightness:(float *)brightness alpha:(float *)alpha; - (OOHSBAComponents) hsbaComponents; // Get the alpha component. - (float) alphaComponent; /* Returns the colour, premultiplied by its alpha channel, and with an alpha of 1.0. If the reciever's alpha is 1.0, it will return itself. */ - (OOColor *) premultipliedColor; // Multiply r, g and b components of a colour by specified factor, clamped to [0..1]. - (OOColor *) colorWithBrightnessFactor:(float)factor; // r,g,b,a array in 0..1 range. - (NSArray *) normalizedArray; - (NSString *) rgbaDescription; - (NSString *) hsbaDescription; @end NSString *OORGBAComponentsDescription(OORGBAComponents components); NSString *OOHSBAComponentsDescription(OOHSBAComponents components); oolite-1.82/src/Core/OOColor.m000066400000000000000000000322641256642440500161270ustar00rootroot00000000000000/* OOColor.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOColor.h" #import "OOCollectionExtractors.h" #import "OOMaths.h" @implementation OOColor // Set methods are internal, because OOColor is immutable (as seen from outside). - (void) setRed:(float)r green:(float)g blue:(float)b alpha:(float)a { rgba[0] = r; rgba[1] = g; rgba[2] = b; rgba[3] = a; } - (void) setHue:(float)h saturation:(float)s brightness:(float)b alpha:(float)a { rgba[3] = a; if (s == 0.0f) { rgba[0] = rgba[1] = rgba[2] = b; return; } float f, p, q, t; int i; h = fmod(h, 360.0f); if (h < 0.0) h += 360.0f; h /= 60.0f; i = floor(h); f = h - i; p = b * (1.0f - s); q = b * (1.0f - (s * f)); t = b * (1.0f - (s * (1.0f - f))); switch (i) { case 0: rgba[0] = b; rgba[1] = t; rgba[2] = p; break; case 1: rgba[0] = q; rgba[1] = b; rgba[2] = p; break; case 2: rgba[0] = p; rgba[1] = b; rgba[2] = t; break; case 3: rgba[0] = p; rgba[1] = q; rgba[2] = b; break; case 4: rgba[0] = t; rgba[1] = p; rgba[2] = b; break; case 5: rgba[0] = b; rgba[1] = p; rgba[2] = q; break; } } - (id) copyWithZone:(NSZone *)zone { // Copy is implemented as retain since OOColor is immutable. return [self retain]; } + (OOColor *) colorWithHue:(float)hue saturation:(float)saturation brightness:(float)brightness alpha:(float)alpha { OOColor* result = [[OOColor alloc] init]; [result setHue:360.0f * hue saturation:saturation brightness:brightness alpha:alpha]; return [result autorelease]; } + (OOColor *) colorWithRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha { OOColor* result = [[OOColor alloc] init]; [result setRed:red green:green blue:blue alpha:alpha]; return [result autorelease]; } + (OOColor *) colorWithWhite:(float)white alpha:(float)alpha { return [OOColor colorWithRed:white green:white blue:white alpha:alpha]; } + (OOColor *) colorWithRGBAComponents:(OORGBAComponents)components { return [self colorWithRed:components.r green:components.g blue:components.b alpha:components.a]; } + (OOColor *) colorWithHSBAComponents:(OOHSBAComponents)components { return [self colorWithHue:components.h / 360.0f saturation:components.s brightness:components.b alpha:components.a]; } + (OOColor *) colorWithDescription:(id)description { return [self colorWithDescription:description saturationFactor:1.0f]; } + (OOColor *) colorWithDescription:(id)description saturationFactor:(float)factor { NSDictionary *dict = nil; OOColor *result = nil; if (description == nil) return nil; if ([description isKindOfClass:[OOColor class]]) { result = [[description copy] autorelease]; } else if ([description isKindOfClass:[NSString class]]) { if ([description hasSuffix:@"Color"]) { // +fooColor selector SEL selector = NSSelectorFromString(description); if ([self respondsToSelector:selector]) result = [self performSelector:selector]; } else { // Some other string result = [self colorFromString:description]; } } else if ([description isKindOfClass:[NSArray class]]) { result = [self colorFromString:[description componentsJoinedByString:@" "]]; } else if ([description isKindOfClass:[NSDictionary class]]) { dict = description; // Workaround for gnu-gcc's more agressive "multiple methods named..." warnings. if ([dict objectForKey:@"hue"] != nil) { // Treat as HSB(A) dictionary float h = [dict oo_floatForKey:@"hue"]; float s = [dict oo_floatForKey:@"saturation" defaultValue:1.0f]; float b = [dict oo_floatForKey:@"brightness" defaultValue:-1.0f]; if (b < 0.0f) b = [dict oo_floatForKey:@"value" defaultValue:1.0f]; float a = [dict oo_floatForKey:@"alpha" defaultValue:-1.0f]; if (a < 0.0f) a = [dict oo_floatForKey:@"opacity" defaultValue:1.0f]; // Not "result =", because we handle the saturation scaling here to allow oversaturation. return [OOColor colorWithHue:h / 360.0f saturation:s * factor brightness:b alpha:a]; } else { // Treat as RGB(A) dictionary float r = [dict oo_floatForKey:@"red"]; float g = [dict oo_floatForKey:@"green"]; float b = [dict oo_floatForKey:@"blue"]; float a = [dict oo_floatForKey:@"alpha" defaultValue:-1.0f]; if (a < 0.0f) a = [dict oo_floatForKey:@"opacity" defaultValue:1.0f]; result = [OOColor colorWithRed:r green:g blue:b alpha:a]; } } if (factor != 1.0f && result != nil) { float h, s, b, a; [result getHue:&h saturation:&s brightness:&b alpha:&a]; h *= 1.0 / 360.0f; // See note in header. s *= factor; result = [self colorWithHue:h saturation:s brightness:b alpha:a]; } return result; } + (OOColor *) brightColorWithDescription:(id)description { OOColor *color = [OOColor colorWithDescription:description]; if (color == nil || 0.5f <= [color brightnessComponent]) return color; return [OOColor colorWithHue:[color hueComponent] / 360.0f saturation:[color saturationComponent] brightness:0.5f alpha:1.0f]; } + (OOColor *) colorFromString:(NSString*) colorFloatString { float rgbaValue[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; NSScanner *scanner = [NSScanner scannerWithString:colorFloatString]; float factor = 1.0f; int i; for (i = 0; i != 4; ++i) { if (![scanner scanFloat:&rgbaValue[i]]) { // Less than three floats or non-float, can't parse -> quit if (i < 3) return nil; // If we get here, we only got three components. Make sure alpha is at correct scale: rgbaValue[3] /= factor; } if (1.0f < rgbaValue[i]) factor = 1.0f / 255.0f; } return [OOColor colorWithRed:rgbaValue[0] * factor green:rgbaValue[1] * factor blue:rgbaValue[2] * factor alpha:rgbaValue[3] * factor]; } + (OOColor *) blackColor // 0.0 white { return [OOColor colorWithWhite:0.0f alpha:1.0f]; } + (OOColor *) darkGrayColor // 0.333 white { return [OOColor colorWithWhite:1.0f/3.0f alpha:1.0f]; } + (OOColor *) lightGrayColor // 0.667 white { return [OOColor colorWithWhite:2.0f/3.0f alpha:1.0f]; } + (OOColor *) whiteColor // 1.0 white { return [OOColor colorWithWhite:1.0f alpha:1.0f]; } + (OOColor *) grayColor // 0.5 white { return [OOColor colorWithWhite:0.5f alpha:1.0f]; } + (OOColor *) redColor // 1.0, 0.0, 0.0 RGB { return [OOColor colorWithRed:1.0f green:0.0f blue:0.0f alpha:1.0f]; } + (OOColor *) greenColor // 0.0, 1.0, 0.0 RGB { return [OOColor colorWithRed:0.0f green:1.0f blue:0.0f alpha:1.0f]; } + (OOColor *) blueColor // 0.0, 0.0, 1.0 RGB { return [OOColor colorWithRed:0.0f green:0.0f blue:1.0f alpha:1.0f]; } + (OOColor *) cyanColor // 0.0, 1.0, 1.0 RGB { return [OOColor colorWithRed:0.0f green:1.0f blue:1.0f alpha:1.0f]; } + (OOColor *) yellowColor // 1.0, 1.0, 0.0 RGB { return [OOColor colorWithRed:1.0f green:1.0f blue:0.0f alpha:1.0f]; } + (OOColor *) magentaColor // 1.0, 0.0, 1.0 RGB { return [OOColor colorWithRed:1.0f green:0.0f blue:1.0f alpha:1.0f]; } + (OOColor *) orangeColor // 1.0, 0.5, 0.0 RGB { return [OOColor colorWithRed:1.0f green:0.5f blue:0.0f alpha:1.0f]; } + (OOColor *) purpleColor // 0.5, 0.0, 0.5 RGB { return [OOColor colorWithRed:0.5f green:0.0f blue:0.5f alpha:1.0f]; } + (OOColor *)brownColor // 0.6, 0.4, 0.2 RGB { return [OOColor colorWithRed:0.6f green:0.4f blue:0.2f alpha:1.0f]; } + (OOColor *) clearColor // 0.0 white, 0.0 alpha { return [OOColor colorWithWhite:0.0f alpha:0.0f]; } - (OOColor *) blendedColorWithFraction:(float)fraction ofColor:(OOColor *)color { float rgba1[4]; [color getRed:&rgba1[0] green:&rgba1[1] blue:&rgba1[2] alpha:&rgba1[3]]; OOColor *result = [[OOColor alloc] init]; [result setRed:OOLerp(rgba[0], rgba1[0], fraction) green:OOLerp(rgba[1], rgba1[1], fraction) blue:OOLerp(rgba[2], rgba1[2], fraction) alpha:OOLerp(rgba[3], rgba1[3], fraction)]; return [result autorelease]; } - (NSString *) descriptionComponents { return [NSString stringWithFormat:@"%g, %g, %g, %g", rgba[0], rgba[1], rgba[2], rgba[3]]; } // Get the red, green, or blue components. - (float) redComponent { return rgba[0]; } - (float) greenComponent { return rgba[1]; } - (float) blueComponent { return rgba[2]; } - (void) getRed:(float *)red green:(float *)green blue:(float *)blue alpha:(float *)alpha { NSParameterAssert(red != NULL && green != NULL && blue != NULL && alpha != NULL); *red = rgba[0]; *green = rgba[1]; *blue = rgba[2]; *alpha = rgba[3]; } - (OORGBAComponents) rgbaComponents { OORGBAComponents c = { rgba[0], rgba[1], rgba[2], rgba[3] }; return c; } - (BOOL) isBlack { return rgba[0] == 0.0f && rgba[1] == 0.0f && rgba[2] == 0.0f; } - (BOOL) isWhite { return rgba[0] == 1.0f && rgba[1] == 1.0f && rgba[2] == 1.0f && rgba[3] == 1.0f; } // Get the components as hue, saturation, or brightness. - (float) hueComponent { float maxrgb = (rgba[0] > rgba[1])? ((rgba[0] > rgba[2])? rgba[0]:rgba[2]):((rgba[1] > rgba[2])? rgba[1]:rgba[2]); float minrgb = (rgba[0] < rgba[1])? ((rgba[0] < rgba[2])? rgba[0]:rgba[2]):((rgba[1] < rgba[2])? rgba[1]:rgba[2]); if (maxrgb == minrgb) { return 0.0f; } float delta = maxrgb - minrgb; float hue = 0.0f; if (rgba[0] == maxrgb) { hue = (rgba[1] - rgba[2]) / delta; } else if (rgba[1] == maxrgb) { hue = 2.0f + (rgba[2] - rgba[0]) / delta; } else if (rgba[2] == maxrgb) { hue = 4.0f + (rgba[0] - rgba[1]) / delta; } hue *= 60.0f; while (hue < 0.0f) hue += 360.0f; return hue; } - (float) saturationComponent { float maxrgb = (rgba[0] > rgba[1])? ((rgba[0] > rgba[2])? rgba[0]:rgba[2]):((rgba[1] > rgba[2])? rgba[1]:rgba[2]); float minrgb = (rgba[0] < rgba[1])? ((rgba[0] < rgba[2])? rgba[0]:rgba[2]):((rgba[1] < rgba[2])? rgba[1]:rgba[2]); float brightness = 0.5f * (maxrgb + minrgb); if (maxrgb == minrgb) return 0.0f; float delta = maxrgb - minrgb; return (brightness <= 0.5f) ? (delta / (maxrgb + minrgb)) : (delta / (2.0f - (maxrgb + minrgb))); } - (float) brightnessComponent { float maxrgb = (rgba[0] > rgba[1])? ((rgba[0] > rgba[2])? rgba[0]:rgba[2]):((rgba[1] > rgba[2])? rgba[1]:rgba[2]); float minrgb = (rgba[0] < rgba[1])? ((rgba[0] < rgba[2])? rgba[0]:rgba[2]):((rgba[1] < rgba[2])? rgba[1]:rgba[2]); return 0.5f * (maxrgb + minrgb); } - (void) getHue:(float *)hue saturation:(float *)saturation brightness:(float *)brightness alpha:(float *)alpha { NSParameterAssert(hue != NULL && saturation != NULL && brightness != NULL && alpha != NULL); *alpha = rgba[3]; int maxrgb = (rgba[0] > rgba[1])? ((rgba[0] > rgba[2])? 0:2):((rgba[1] > rgba[2])? 1:2); int minrgb = (rgba[0] < rgba[1])? ((rgba[0] < rgba[2])? 0:2):((rgba[1] < rgba[2])? 1:2); *brightness = 0.5f * (rgba[maxrgb] + rgba[minrgb]); if (rgba[maxrgb] == rgba[minrgb]) { *saturation = 0.0f; *hue = 0.0f; return; } float delta = rgba[maxrgb] - rgba[minrgb]; *saturation = (*brightness <= 0.5f) ? (delta / (rgba[maxrgb] + rgba[minrgb])) : (delta / (2.0f - (rgba[maxrgb] + rgba[minrgb]))); if (maxrgb == 0) { *hue = (rgba[1] - rgba[2]) / delta; } else if (maxrgb == 1) { *hue = 2.0f + (rgba[2] - rgba[0]) / delta; } else if (maxrgb == 2) { *hue = 4.0f + (rgba[0] - rgba[1]) / delta; } *hue *= 60.0f; while (*hue < 0.0f) *hue += 360.0f; } - (OOHSBAComponents) hsbaComponents { OOHSBAComponents c; [self getHue:&c.h saturation:&c.s brightness:&c.b alpha:&c.a]; return c; } // Get the alpha component. - (float) alphaComponent { return rgba[3]; } - (OOColor *) premultipliedColor { if (rgba[3] == 1.0f) return [[self retain] autorelease]; return [OOColor colorWithRed:rgba[0] * rgba[3] green:rgba[1] * rgba[3] blue:rgba[2] * rgba[3] alpha:1.0f]; } - (OOColor *) colorWithBrightnessFactor:(float)factor { return [OOColor colorWithRed:OOClamp_0_1_f(rgba[0] * factor) green:OOClamp_0_1_f(rgba[1] * factor) blue:OOClamp_0_1_f(rgba[2] * factor) alpha:rgba[3]]; } - (NSArray *) normalizedArray { float r, g, b, a; [self getRed:&r green:&g blue:&b alpha:&a]; return [NSArray arrayWithObjects: [NSNumber numberWithFloat:r], [NSNumber numberWithFloat:g], [NSNumber numberWithFloat:b], [NSNumber numberWithFloat:a], nil]; } - (NSString *) rgbaDescription { return OORGBAComponentsDescription([self rgbaComponents]); } - (NSString *) hsbaDescription { return OOHSBAComponentsDescription([self hsbaComponents]); } @end NSString *OORGBAComponentsDescription(OORGBAComponents components) { return [NSString stringWithFormat:@"{%.3g, %.3g, %.3g, %.3g}", components.r, components.g, components.b, components.a]; } NSString *OOHSBAComponentsDescription(OOHSBAComponents components) { return [NSString stringWithFormat:@"{%i, %.3g, %.3g, %.3g}", (int)components.h, components.s, components.b, components.a]; } oolite-1.82/src/Core/OOCommodities.h000066400000000000000000000106211256642440500173110ustar00rootroot00000000000000/* OOCommodities.h Commodity price and quantity manager Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOTypes.h" #define MAIN_SYSTEM_MARKET_LIMIT 127 // keys in trade-goods.plist static NSString * const kOOCommodityName = @"name"; static NSString * const kOOCommodityClasses = @"classes"; static NSString * const kOOCommodityContainer = @"quantity_unit"; static NSString * const kOOCommodityPeakExport = @"peak_export"; static NSString * const kOOCommodityPeakImport = @"peak_import"; static NSString * const kOOCommodityPriceAverage = @"price_average"; static NSString * const kOOCommodityPriceEconomic = @"price_economic"; static NSString * const kOOCommodityPriceRandom = @"price_random"; // next one cannot be set from file - named for compatibility static NSString * const kOOCommodityPriceCurrent = @"price"; static NSString * const kOOCommodityQuantityAverage = @"quantity_average"; static NSString * const kOOCommodityQuantityEconomic= @"quantity_economic"; static NSString * const kOOCommodityQuantityRandom = @"quantity_random"; // next one cannot be set from file - named for compatibility static NSString * const kOOCommodityQuantityCurrent = @"quantity"; static NSString * const kOOCommodityLegalityExport = @"legality_export"; static NSString * const kOOCommodityLegalityImport = @"legality_import"; static NSString * const kOOCommodityTrumbleOpinion = @"trumble_opinion"; static NSString * const kOOCommoditySortOrder = @"sort_order"; static NSString * const kOOCommodityCapacity = @"capacity"; static NSString * const kOOCommodityScript = @"market_script"; static NSString * const kOOCommodityComment = @"comment"; static NSString * const kOOCommodityShortComment = @"short_comment"; // next one cannot be set from file - named for compatibility static NSString * const kOOCommodityKey = @"key"; // keys in secondary market definitions static NSString * const kOOCommodityMarketType = @"type"; static NSString * const kOOCommodityMarketName = @"name"; static NSString * const kOOCommodityMarketPriceAdder = @"price_adder"; static NSString * const kOOCommodityMarketPriceMultiplier = @"price_multiplier"; static NSString * const kOOCommodityMarketPriceRandomiser = @"price_randomiser"; static NSString * const kOOCommodityMarketQuantityAdder = @"quantity_adder"; static NSString * const kOOCommodityMarketQuantityMultiplier = @"quantity_multiplier"; static NSString * const kOOCommodityMarketQuantityRandomiser = @"quantity_randomiser"; static NSString * const kOOCommodityMarketLegalityExport = @"legality_export"; static NSString * const kOOCommodityMarketLegalityImport = @"legality_import"; static NSString * const kOOCommodityMarketCapacity = @"capacity"; // values for "type" in the plist static NSString * const kOOCommodityMarketTypeValueDefault = @"default"; static NSString * const kOOCommodityMarketTypeValueClass = @"class"; static NSString * const kOOCommodityMarketTypeValueGood = @"good"; @class OOCommodityMarket, StationEntity; @interface OOCommodities: NSObject { @private NSDictionary *_commodityLists; } + (OOCommodityType) legacyCommodityType:(NSUInteger)i; - (OOCommodityMarket *) generateManifestForPlayer; - (OOCommodityMarket *) generateBlankMarket; - (OOCommodityMarket *) generateMarketForSystemWithEconomy:(OOEconomyID)economy andScript:(NSString *)scriptName; - (OOCommodityMarket *) generateMarketForStation:(StationEntity *)station; - (OOCreditsQuantity) samplePriceForCommodity:(OOCommodityType)commodity inEconomy:(OOEconomyID)economy withScript:(NSString *)scriptName inSystem:(OOSystemID)system; - (NSUInteger) count; - (NSArray *) goods; - (BOOL) goodDefined:(NSString *)key; - (NSString *) getRandomCommodity; - (OOMassUnit) massUnitForGood:(NSString *)good; @end oolite-1.82/src/Core/OOCommodities.m000066400000000000000000000431231256642440500173210ustar00rootroot00000000000000/* OOCommodities.m Oolite Copyright (C) 2004-2014 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCommodities.h" #import "OOCommodityMarket.h" #import "StationEntity.h" #import "ResourceManager.h" #import "legacy_random.h" #import "OOCollectionExtractors.h" #import "OOJSScript.h" #import "PlayerEntity.h" @interface OOCommodities (OOPrivate) - (NSDictionary *) modifyGood:(NSDictionary *)good withScript:(OOScript *)script atStation:(StationEntity *)station inSystem:(OOSystemID)system localMode:(BOOL)local; - (NSDictionary *) createDefinitionFrom:(NSDictionary *) good price:(OOCreditsQuantity)p andQuantity:(OOCreditsQuantity)q forKey:(OOCommodityType)key atStation:(StationEntity *)station inSystem:(OOSystemID)system; - (OOCargoQuantity) generateQuantityForGood:(NSDictionary *)good inEconomy:(OOEconomyID)economy; - (OOCreditsQuantity) generatePriceForGood:(NSDictionary *)good inEconomy:(OOEconomyID)economy; - (float) economicBiasForGood:(NSDictionary *)good inEconomy:(OOEconomyID)economy; - (NSDictionary *) firstModifierForGood:(OOCommodityType)good inClasses:(NSArray *)classes fromList:(NSArray *)definitions; - (OOCreditsQuantity) adjustPrice:(OOCreditsQuantity)price byRule:(NSDictionary *)rule; - (OOCargoQuantity) adjustQuantity:(OOCargoQuantity)quantity byRule:(NSDictionary *)rule; - (NSDictionary *) updateInfoFor:(NSDictionary *)good byRule:(NSDictionary *)rule maxCapacity:(OOCargoQuantity)maxCapacity; @end @implementation OOCommodities /* Older save games store some commodity information by its old index. */ + (OOCommodityType) legacyCommodityType:(NSUInteger)i { switch (i) { case 0: return @"food"; case 1: return @"textiles"; case 2: return @"radioactives"; case 3: return @"slaves"; case 4: return @"liquor_wines"; case 5: return @"luxuries"; case 6: return @"narcotics"; case 7: return @"computers"; case 8: return @"machinery"; case 9: return @"alloys"; case 10: return @"firearms"; case 11: return @"furs"; case 12: return @"minerals"; case 13: return @"gold"; case 14: return @"platinum"; case 15: return @"gem_stones"; case 16: return @"alien_items"; } // shouldn't happen return @"food"; } - (id) init { self = [super init]; if (self == nil) return nil; NSDictionary *rawCommodityLists = [ResourceManager dictionaryFromFilesNamed:@"trade-goods.plist" inFolder:@"Config" mergeMode:MERGE_SMART cache:YES]; /* // TODO: validation of inputs // TODO: convert 't', 'kg', 'g' in quantity_unit to 0, 1, 2 // for now it needs them entering as the ints NSMutableDictionary *validatedCommodityLists = [NSMutableDictionary dictionaryWithCapacity:[rawCommodityLists count]]; NSString *commodityName = nil; foreachkey (commodityName, rawCommodityLists) { // validate } // _commodityLists = [[NSDictionary dictionaryWithDictionary:validatedCommodityLists] retain]; */ _commodityLists = [[NSDictionary dictionaryWithDictionary:rawCommodityLists] retain]; return self; } - (void) dealloc { DESTROY(_commodityLists); [super dealloc]; } - (OOCommodityMarket *) generateManifestForPlayer { OOCommodityMarket *market = [[OOCommodityMarket alloc] init]; NSString *commodity = nil; NSMutableDictionary *good = nil; foreachkey (commodity, _commodityLists) { good = [NSMutableDictionary dictionaryWithDictionary:[_commodityLists oo_dictionaryForKey:commodity]]; [good oo_setUnsignedInteger:0 forKey:kOOCommodityPriceCurrent]; [good oo_setUnsignedInteger:0 forKey:kOOCommodityQuantityCurrent]; /* The actual capacity of the player ship is a total, not * per-good, so is managed separately through PlayerEntity */ [good oo_setUnsignedInteger:UINT32_MAX forKey:kOOCommodityCapacity]; [good setObject:commodity forKey:kOOCommodityKey]; [market setGood:commodity withInfo:good]; } return [market autorelease]; } - (OOCommodityMarket *) generateBlankMarket { OOCommodityMarket *market = [[OOCommodityMarket alloc] init]; NSString *commodity = nil; NSMutableDictionary *good = nil; foreachkey (commodity, _commodityLists) { good = [NSMutableDictionary dictionaryWithDictionary:[_commodityLists oo_dictionaryForKey:commodity]]; [good oo_setUnsignedInteger:0 forKey:kOOCommodityPriceCurrent]; [good oo_setUnsignedInteger:0 forKey:kOOCommodityQuantityCurrent]; [good oo_setUnsignedInteger:0 forKey:kOOCommodityCapacity]; [good setObject:commodity forKey:kOOCommodityKey]; [market setGood:commodity withInfo:good]; } return [market autorelease]; } - (NSDictionary *) createDefinitionFrom:(NSDictionary *) good price:(OOCreditsQuantity)p andQuantity:(OOCreditsQuantity)q forKey:(OOCommodityType)key atStation:(StationEntity *)station inSystem:(OOSystemID)system { NSMutableDictionary *definition = [NSMutableDictionary dictionaryWithDictionary:good]; [definition oo_setUnsignedInteger:p forKey:kOOCommodityPriceCurrent]; [definition oo_setUnsignedInteger:q forKey:kOOCommodityQuantityCurrent]; if (station == nil && [definition objectForKey:kOOCommodityCapacity] == nil) { [definition oo_setInteger:MAIN_SYSTEM_MARKET_LIMIT forKey:kOOCommodityCapacity]; } [definition setObject:key forKey:kOOCommodityKey]; if (station != nil && ![station marketMonitored]) { // clear legal status indicators if the market is not monitored [definition oo_setUnsignedInteger:0 forKey:kOOCommodityLegalityExport]; [definition oo_setUnsignedInteger:0 forKey:kOOCommodityLegalityImport]; } NSString *goodScriptName = [definition oo_stringForKey:kOOCommodityScript]; if (goodScriptName == nil) { return definition; } OOScript *goodScript = [PLAYER commodityScriptNamed:goodScriptName]; if (goodScript == nil) { return definition; } return [self modifyGood:definition withScript:goodScript atStation:station inSystem:system localMode:NO]; } - (NSDictionary *) modifyGood:(NSDictionary *)good withScript:(OOScript *)script atStation:(StationEntity *)station inSystem:(OOSystemID)system localMode:(BOOL)localMode { NSDictionary *result = nil; JSContext *context = OOJSAcquireContext(); jsval rval; jsval args[] = { [good oo_jsValueInContext:context], [station oo_jsValueInContext:context], INT_TO_JSVAL(system) }; BOOL OK = YES; NSString *errorType = nil; if (localMode) { errorType = @"local"; OK = [script callMethod:OOJSID("updateLocalCommodityDefinition") inContext:context withArguments:args count:3 result:&rval]; } else { errorType = @"general"; OK = [script callMethod:OOJSID("updateGeneralCommodityDefinition") inContext:context withArguments:args count:3 result:&rval]; } if (!OK) { OOLog(@"script.commodityScript.error",@"Could not update %@ commodity definition for %@ - unable to call updateLocalCommodityDefinition",errorType,[good oo_stringForKey:kOOCommodityName]); OOJSRelinquishContext(context); return good; } if (!JSVAL_IS_OBJECT(rval)) { OOLog(@"script.commodityScript.error",@"Could not update %@ commodity definition for %@ - return value invalid",errorType,[good oo_stringForKey:kOOCommodityKey]); OOJSRelinquishContext(context); return good; } result = OOJSNativeObjectFromJSObject(context, JSVAL_TO_OBJECT(rval)); OOJSRelinquishContext(context); if (![result isKindOfClass:[NSDictionary class]]) { OOLog(@"script.commodityScript.error",@"Could not update %@ commodity definition for %@ - return value invalid",errorType,[good oo_stringForKey:kOOCommodityKey]); return good; } return result; } - (OOCommodityMarket *) generateMarketForSystemWithEconomy:(OOEconomyID)economy andScript:(NSString *)scriptName { OOScript *script = [PLAYER commodityScriptNamed:scriptName]; OOCommodityMarket *market = [[OOCommodityMarket alloc] init]; NSString *commodity = nil; NSDictionary *good = nil; foreachkey (commodity, _commodityLists) { good = [_commodityLists oo_dictionaryForKey:commodity]; OOCargoQuantity q = [self generateQuantityForGood:good inEconomy:economy]; // main system market limited to 127 units of each item if (q > MAIN_SYSTEM_MARKET_LIMIT) { q = MAIN_SYSTEM_MARKET_LIMIT; } OOCreditsQuantity p = [self generatePriceForGood:good inEconomy:economy]; good = [self createDefinitionFrom:good price:p andQuantity:q forKey:commodity atStation:nil inSystem:[UNIVERSE currentSystemID]]; if (script != nil) { good = [self modifyGood:good withScript:script atStation:nil inSystem:[UNIVERSE currentSystemID] localMode:YES]; } [market setGood:commodity withInfo:good]; } return [market autorelease]; } - (OOCommodityMarket *) generateMarketForStation:(StationEntity *)station { NSArray *marketDefinition = [station marketDefinition]; NSString *marketScriptName = [station marketScriptName]; OOScript *marketScript = [PLAYER commodityScriptNamed:marketScriptName]; if (marketDefinition == nil && marketScript == nil) { OOCommodityMarket *market = [self generateBlankMarket]; return market; } OOCommodityMarket *market = [[OOCommodityMarket alloc] init]; OOCargoQuantity capacity = [station marketCapacity]; OOCommodityMarket *mainMarket = [UNIVERSE commodityMarket]; NSString *commodity = nil; NSDictionary *good = nil; foreachkey (commodity, _commodityLists) { good = [_commodityLists oo_dictionaryForKey:commodity]; OOCargoQuantity baseCapacity = [good oo_unsignedIntegerForKey:kOOCommodityCapacity defaultValue:MAIN_SYSTEM_MARKET_LIMIT]; OOCargoQuantity q = [mainMarket quantityForGood:commodity]; OOCreditsQuantity p = [mainMarket priceForGood:commodity]; if (marketScript == nil) { NSDictionary *modifier = [self firstModifierForGood:commodity inClasses:[good oo_arrayForKey:kOOCommodityClasses] fromList:marketDefinition]; good = [self updateInfoFor:good byRule:modifier maxCapacity:capacity]; p = [self adjustPrice:p byRule:modifier]; // first, scale to this station's capacity for this good OOCargoQuantity localCapacity = [good oo_unsignedIntegerForKey:kOOCommodityCapacity]; if (localCapacity > capacity) { localCapacity = capacity; } q = (q * localCapacity) / baseCapacity; q = [self adjustQuantity:q byRule:modifier]; if (q > localCapacity) { q = localCapacity; // cap } } else { // only scale to market at this stage q = (q * capacity) / baseCapacity; } good = [self createDefinitionFrom:good price:p andQuantity:q forKey:commodity atStation:station inSystem:[UNIVERSE currentSystemID]]; if (marketScript != nil) { good = [self modifyGood:good withScript:marketScript atStation:station inSystem:[UNIVERSE currentSystemID] localMode:YES]; } [market setGood:commodity withInfo:good]; } return [market autorelease]; } - (NSUInteger) count { return [_commodityLists count]; } - (NSArray *) goods { return [_commodityLists allKeys]; } - (BOOL) goodDefined:(NSString *)key { return ([_commodityLists oo_dictionaryForKey:key] != nil); } - (NSString *) getRandomCommodity { NSArray *keys = [_commodityLists allKeys]; NSUInteger idx = Ranrot() % [keys count]; return [keys oo_stringAtIndex:idx]; } - (OOMassUnit) massUnitForGood:(NSString *)good { NSDictionary *definition = [_commodityLists oo_dictionaryForKey:good]; if (definition == nil) { return UNITS_TONS; } return [definition oo_unsignedIntegerForKey:kOOCommodityContainer]; } - (OOCargoQuantity) generateQuantityForGood:(NSDictionary *)good inEconomy:(OOEconomyID)economy { float bias = [self economicBiasForGood:good inEconomy:economy]; float base = [good oo_floatForKey:kOOCommodityQuantityAverage]; float econ = base * [good oo_floatForKey:kOOCommodityQuantityEconomic] * bias; float random = base * [good oo_floatForKey:kOOCommodityQuantityRandom] * (randf() - randf()); base += econ + random; if (base < 0.0) { return 0; } else { return (OOCargoQuantity)base; } } - (OOCreditsQuantity) generatePriceForGood:(NSDictionary *)good inEconomy:(OOEconomyID)economy { float bias = [self economicBiasForGood:good inEconomy:economy]; float base = [good oo_floatForKey:kOOCommodityPriceAverage]; float econ = base * [good oo_floatForKey:kOOCommodityPriceEconomic] * -bias; float random = base * [good oo_floatForKey:kOOCommodityPriceRandom] * (randf() - randf()); base += econ + random; if (base < 0.0) { return 0; } else { return (OOCreditsQuantity)base; } } - (OOCreditsQuantity) samplePriceForCommodity:(OOCommodityType)commodity inEconomy:(OOEconomyID)economy withScript:(NSString *)scriptName inSystem:(OOSystemID)system { NSDictionary *good = [_commodityLists oo_dictionaryForKey:commodity]; if (good == nil) { return 0; } OOCreditsQuantity p = [self generatePriceForGood:good inEconomy:economy]; good = [self createDefinitionFrom:good price:p andQuantity:0 forKey:commodity atStation:nil inSystem:system]; if (scriptName != nil) { OOScript *script = [PLAYER commodityScriptNamed:scriptName]; if (script != nil) { good = [self modifyGood:good withScript:script atStation:nil inSystem:system localMode:YES]; } } return [good oo_unsignedIntegerForKey:kOOCommodityPriceCurrent]; } // positive = exporter; negative = importer; range -1.0 .. +1.0 - (float) economicBiasForGood:(NSDictionary *)good inEconomy:(OOEconomyID)economy { OOEconomyID exporter = [good oo_intForKey:kOOCommodityPeakExport]; OOEconomyID importer = [good oo_intForKey:kOOCommodityPeakImport]; // *2 and /2 to work in ints at this stage int exDiff = abs(economy-exporter)*2; int imDiff = abs(economy-importer)*2; int distance = (exDiff+imDiff)/2; if (exDiff == imDiff) { // neutral economy return 0.0; } else if (exDiff > imDiff) { // closer to the importer, so return -ve return -(1.0-((float)imDiff/(float)distance)); } else { // closer to the exporter, so return +ve return 1.0-((float)exDiff/(float)distance); } } - (NSDictionary *) firstModifierForGood:(OOCommodityType)good inClasses:(NSArray *)classes fromList:(NSArray *)definitions { NSUInteger i; for (i = 0; i < [definitions count]; i++) { NSDictionary *definition = [definitions oo_dictionaryAtIndex:i]; if (definition != nil) { NSString *applicationType = [definition oo_stringForKey:kOOCommodityMarketType defaultValue:kOOCommodityMarketTypeValueDefault]; NSString *applicationName = [definition oo_stringForKey:kOOCommodityMarketName defaultValue:@""]; if ( [applicationType isEqualToString:kOOCommodityMarketTypeValueDefault] || ([applicationType isEqualToString:kOOCommodityMarketTypeValueGood] && [applicationName isEqualToString:good]) || ([applicationType isEqualToString:kOOCommodityMarketTypeValueClass] && [classes containsObject:applicationName]) ) { return definition; } } } // return a blank dictionary - default values will do the rest return [NSDictionary dictionary]; } - (OOCreditsQuantity) adjustPrice:(OOCreditsQuantity)price byRule:(NSDictionary *)rule { float p = (float)price; // work in floats to avoid rounding problems float pa = [rule oo_floatForKey:kOOCommodityMarketPriceAdder defaultValue:0.0]; float pm = [rule oo_floatForKey:kOOCommodityMarketPriceMultiplier defaultValue:1.0]; if (pm <= 0.0 && pa <= 0.0) { // setting a price multiplier of 0 forces the price to zero return 0; } float pr = [rule oo_floatForKey:kOOCommodityMarketPriceRandomiser defaultValue:0.0]; p += pa; p = (p * pm) + (p * pr * (randf()-randf())); if (p < 1.0) { // random variation and non-zero price multiplier can't reduce // price below 1 decicredit p = 1.0; } return (OOCreditsQuantity) p; } - (OOCargoQuantity) adjustQuantity:(OOCargoQuantity)quantity byRule:(NSDictionary *)rule { float q = (float)quantity; // work in floats to avoid rounding problems float qa = [rule oo_floatForKey:kOOCommodityMarketQuantityAdder defaultValue:0.0]; float qm = [rule oo_floatForKey:kOOCommodityMarketQuantityMultiplier defaultValue:1.0]; if (qm <= 0.0 && qa <= 0.0) { // setting a price multiplier of 0 forces the price to zero return 0; } float qr = [rule oo_floatForKey:kOOCommodityMarketQuantityRandomiser defaultValue:0.0]; q += qa; q = (q * qm) + (q * qr * (randf()-randf())); if (q < 0.0) { // random variation and non-zero price multiplier can't reduce // quantity below zero q = 0.0; } // may be over station capacity - that gets capped later return (OOCargoQuantity) q; } - (NSDictionary *) updateInfoFor:(NSDictionary *)good byRule:(NSDictionary *)rule maxCapacity:(OOCargoQuantity)maxCapacity { NSMutableDictionary *tmp = [NSMutableDictionary dictionaryWithDictionary:good]; NSInteger import = [rule oo_integerForKey:kOOCommodityMarketLegalityImport defaultValue:-1]; if (import >= 0) { [tmp oo_setInteger:import forKey:kOOCommodityLegalityImport]; } NSInteger export = [rule oo_integerForKey:kOOCommodityMarketLegalityExport defaultValue:-1]; if (export >= 0) { [tmp oo_setInteger:import forKey:kOOCommodityLegalityExport]; } NSInteger capacity = [rule oo_integerForKey:kOOCommodityMarketCapacity defaultValue:-1]; if (capacity >= 0 && capacity <= (NSInteger)maxCapacity) { [tmp oo_setInteger:capacity forKey:kOOCommodityCapacity]; } else { // set to the station max capacity [tmp oo_setInteger:maxCapacity forKey:kOOCommodityCapacity]; } return [[tmp copy] autorelease]; } @end oolite-1.82/src/Core/OOCommodityMarket.h000066400000000000000000000047031256642440500201510ustar00rootroot00000000000000/* OOCommodityMarket.h Commodity price and quantity list for a particular station/system Also used for the player ship's docked manifest Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCommodities.h" #import "OOTypes.h" @interface OOCommodityMarket: NSObject { @private NSMutableDictionary *_commodityList; NSArray *_sortedKeys; } - (NSUInteger) count; - (void) setGood:(OOCommodityType)key withInfo:(NSDictionary *)info; - (NSArray *) goods; - (NSDictionary *) dictionaryForScripting; - (BOOL) setPrice:(OOCreditsQuantity)price forGood:(OOCommodityType)good; - (BOOL) setQuantity:(OOCargoQuantity)quantity forGood:(OOCommodityType)good; - (BOOL) addQuantity:(OOCargoQuantity)quantity forGood:(OOCommodityType)good; - (BOOL) removeQuantity:(OOCargoQuantity)quantity forGood:(OOCommodityType)good; - (void) removeAllGoods; - (BOOL) setComment:(NSString *)comment forGood:(OOCommodityType)good; - (BOOL) setShortComment:(NSString *)comment forGood:(OOCommodityType)good; - (NSString *) nameForGood:(OOCommodityType)good; - (NSString *) commentForGood:(OOCommodityType)good; - (NSString *) shortCommentForGood:(OOCommodityType)good; - (OOCreditsQuantity) priceForGood:(OOCommodityType)good; - (OOCargoQuantity) quantityForGood:(OOCommodityType)good; - (OOMassUnit) massUnitForGood:(OOCommodityType)good; - (NSUInteger) exportLegalityForGood:(OOCommodityType)good; - (NSUInteger) importLegalityForGood:(OOCommodityType)good; - (OOCargoQuantity) capacityForGood:(OOCommodityType)good; - (float) trumbleOpinionForGood:(OOCommodityType)good; - (NSDictionary *) definitionForGood:(OOCommodityType)good; - (NSArray *) savePlayerAmounts; - (void) loadPlayerAmounts:(NSArray *)amounts; - (NSArray *) saveStationAmounts; - (void) loadStationAmounts:(NSArray *)amounts; @end oolite-1.82/src/Core/OOCommodityMarket.m000066400000000000000000000237531256642440500201640ustar00rootroot00000000000000/* OOCommodityMarket.m Oolite Copyright (C) 2004-2014 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCommodities.h" #import "OOCommodityMarket.h" #import "OOCollectionExtractors.h" #import "OOStringExpander.h" static NSComparisonResult goodsSorter(id a, id b, void *context); @implementation OOCommodityMarket - (id) init { self = [super init]; if (self == nil) return nil; _commodityList = [[NSMutableDictionary dictionaryWithCapacity:24] retain]; _sortedKeys = nil; return self; } - (void) dealloc { DESTROY(_commodityList); DESTROY(_sortedKeys); [super dealloc]; } - (NSUInteger) count { return [_commodityList count]; } - (void) setGood:(OOCommodityType)key withInfo:(NSDictionary *)info { NSMutableDictionary *definition = [NSMutableDictionary dictionaryWithDictionary:info]; [_commodityList setObject:definition forKey:key]; DESTROY(_sortedKeys); // reset } - (NSArray *) goods { if (_sortedKeys == nil) { NSArray *keys = [_commodityList allKeys]; _sortedKeys = [[keys sortedArrayUsingFunction:goodsSorter context:_commodityList] retain]; } return _sortedKeys; } - (NSDictionary *) dictionaryForScripting { return [[_commodityList copy] autorelease]; } - (BOOL) setPrice:(OOCreditsQuantity)price forGood:(OOCommodityType)good { NSMutableDictionary *definition = [_commodityList oo_mutableDictionaryForKey:good]; if (definition == nil) { return NO; } [definition oo_setUnsignedInteger:price forKey:kOOCommodityPriceCurrent]; return YES; } - (BOOL) setQuantity:(OOCargoQuantity)quantity forGood:(OOCommodityType)good { NSMutableDictionary *definition = [_commodityList oo_mutableDictionaryForKey:good]; if (definition == nil || quantity > [self capacityForGood:good]) { return NO; } [definition oo_setUnsignedInteger:quantity forKey:kOOCommodityQuantityCurrent]; return YES; } - (BOOL) addQuantity:(OOCargoQuantity)quantity forGood:(OOCommodityType)good { OOCargoQuantity current = [self quantityForGood:good]; if (current + quantity > [self capacityForGood:good]) { return NO; } [self setQuantity:(current+quantity) forGood:good]; return YES; } - (BOOL) removeQuantity:(OOCargoQuantity)quantity forGood:(OOCommodityType)good { OOCargoQuantity current = [self quantityForGood:good]; if (current < quantity) { return NO; } [self setQuantity:(current-quantity) forGood:good]; return YES; } - (void) removeAllGoods { OOCommodityType good = nil; foreach (good, [_commodityList allKeys]) { [self setQuantity:0 forGood:good]; } } - (BOOL) setComment:(NSString *)comment forGood:(OOCommodityType)good { NSMutableDictionary *definition = [_commodityList oo_mutableDictionaryForKey:good]; if (definition == nil) { return NO; } [definition setObject:comment forKey:kOOCommodityComment]; return YES; } - (BOOL) setShortComment:(NSString *)comment forGood:(OOCommodityType)good { NSMutableDictionary *definition = [_commodityList oo_mutableDictionaryForKey:good]; if (definition == nil) { return NO; } [definition setObject:comment forKey:kOOCommodityShortComment]; return YES; } - (NSString *) nameForGood:(OOCommodityType)good { NSDictionary *definition = [_commodityList oo_dictionaryForKey:good]; if (definition == nil) { return OOExpand(@"[oolite-unknown-commodity-name]"); } return OOExpand([definition oo_stringForKey:kOOCommodityName defaultValue:@"[oolite-unknown-commodity-name]"]); } - (NSString *) commentForGood:(OOCommodityType)good { NSDictionary *definition = [_commodityList oo_dictionaryForKey:good]; if (definition == nil) { return OOExpand(@"[oolite-unknown-commodity-name]"); } return OOExpand([definition oo_stringForKey:kOOCommodityComment defaultValue:@"[oolite-commodity-no-comment]"]); } - (NSString *) shortCommentForGood:(OOCommodityType)good { NSDictionary *definition = [_commodityList oo_dictionaryForKey:good]; if (definition == nil) { return OOExpand(@"[oolite-unknown-commodity-name]"); } return OOExpand([definition oo_stringForKey:kOOCommodityShortComment defaultValue:@"[oolite-commodity-no-short-comment]"]); } - (OOCreditsQuantity) priceForGood:(OOCommodityType)good { NSDictionary *definition = [_commodityList oo_dictionaryForKey:good]; if (definition == nil) { return 0; } return [definition oo_unsignedIntegerForKey:kOOCommodityPriceCurrent]; } - (OOCargoQuantity) quantityForGood:(OOCommodityType)good { NSDictionary *definition = [_commodityList oo_dictionaryForKey:good]; if (definition == nil) { return 0; } return [definition oo_unsignedIntegerForKey:kOOCommodityQuantityCurrent]; } - (OOMassUnit) massUnitForGood:(OOCommodityType)good { NSDictionary *definition = [_commodityList oo_dictionaryForKey:good]; if (definition == nil) { return UNITS_TONS; } return [definition oo_unsignedIntegerForKey:kOOCommodityContainer]; } - (NSUInteger) exportLegalityForGood:(OOCommodityType)good { NSDictionary *definition = [_commodityList oo_dictionaryForKey:good]; if (definition == nil) { return 0; } return [definition oo_unsignedIntegerForKey:kOOCommodityLegalityExport]; } - (NSUInteger) importLegalityForGood:(OOCommodityType)good { NSDictionary *definition = [_commodityList oo_dictionaryForKey:good]; if (definition == nil) { return 0; } return [definition oo_unsignedIntegerForKey:kOOCommodityLegalityImport]; } - (OOCargoQuantity) capacityForGood:(OOCommodityType)good { NSDictionary *definition = [_commodityList oo_dictionaryForKey:good]; if (definition == nil) { return 0; } // should only be undefined for main system markets, not secondary stations // meaningless for player ship, though return [definition oo_unsignedIntegerForKey:kOOCommodityCapacity defaultValue:MAIN_SYSTEM_MARKET_LIMIT]; } - (float) trumbleOpinionForGood:(OOCommodityType)good { NSDictionary *definition = [_commodityList oo_dictionaryForKey:good]; if (definition == nil) { return 0; } return [definition oo_floatForKey:kOOCommodityTrumbleOpinion]; } - (NSDictionary *) definitionForGood:(OOCommodityType)good { return [[[_commodityList oo_dictionaryForKey:good] copy] autorelease]; } - (NSArray *) savePlayerAmounts { NSMutableArray *amounts = [NSMutableArray arrayWithCapacity:[self count]]; OOCommodityType good = nil; foreach (good, [self goods]) { [amounts addObject:[NSArray arrayWithObjects:good,[NSNumber numberWithUnsignedInt:[self quantityForGood:good]],nil]]; } return [NSArray arrayWithArray:amounts]; } - (void) loadPlayerAmounts:(NSArray *)amounts { OOCargoQuantity q; BOOL loadedOK; NSString *good = nil; foreach (good, [self goods]) { // make sure that any goods not defined in the save game are zeroed [self setQuantity:0 forGood:good]; } NSArray *loaded = nil; foreach (loaded, amounts) { loadedOK = NO; good = [loaded oo_stringAtIndex:0]; q = [loaded oo_unsignedIntegerAtIndex:1]; // old save games might have more in the array, but we don't care if (![self setQuantity:q forGood:good]) { // then it's an array from a 1.80-or-earlier save game and // the good name is the description string (maybe a // translated one) OOCommodityType key = nil; foreach (key, [self goods]) { if ([good isEqualToString:[self nameForGood:key]]) { [self setQuantity:q forGood:key]; loadedOK = YES; break; } } } else { loadedOK = YES; } if (!loadedOK) { OOLog(@"setCommanderDataFromDictionary.warning.cargo",@"Cargo %@ (%u units) could not be loaded from the saved game, as it is no longer defined",good,q); } } } - (NSArray *) saveStationAmounts { NSMutableArray *amounts = [NSMutableArray arrayWithCapacity:[self count]]; OOCommodityType good = nil; foreach (good, [self goods]) { [amounts addObject:[NSArray arrayWithObjects:good,[NSNumber numberWithUnsignedInt:[self quantityForGood:good]],[NSNumber numberWithUnsignedInt:[self priceForGood:good]],nil]]; } return [NSArray arrayWithArray:amounts]; } - (void) loadStationAmounts:(NSArray *)amounts { OOCargoQuantity q; OOCreditsQuantity p; BOOL loadedOK; NSString *good = nil; NSArray *loaded = nil; foreach (loaded, amounts) { loadedOK = NO; good = [loaded oo_stringAtIndex:0]; q = [loaded oo_unsignedIntegerAtIndex:1]; p = [loaded oo_unsignedIntegerAtIndex:2]; // old save games might have more in the array, but we don't care if (![self setQuantity:q forGood:good]) { // then it's an array from a 1.80-or-earlier save game and // the good name is the description string (maybe a // translated one) OOCommodityType key = nil; foreach (key, [self goods]) { if ([good isEqualToString:[self nameForGood:key]]) { [self setQuantity:q forGood:key]; [self setPrice:p forGood:key]; loadedOK = YES; break; } } } else { [self setPrice:p forGood:good]; loadedOK = YES; } if (!loadedOK) { OOLog(@"load.warning.cargo",@"Station market good %@ (%u units) could not be loaded from the saved game, as it is no longer defined",good,q); } } } @end static NSComparisonResult goodsSorter(id a, id b, void *context) { NSDictionary *commodityList = (NSDictionary *)context; int v1 = [[commodityList oo_dictionaryForKey:(NSString *)a] oo_intForKey:kOOCommoditySortOrder]; int v2 = [[commodityList oo_dictionaryForKey:(NSString *)b] oo_intForKey:kOOCommoditySortOrder]; if (v1 < v2) { return NSOrderedAscending; } else if (v1 > v2) { return NSOrderedDescending; } else { return NSOrderedSame; } } oolite-1.82/src/Core/OOConstToString.h000066400000000000000000000070651256642440500176250ustar00rootroot00000000000000/* OOConstToString.h Convert various sets of integer constants to strings. To consider: replacing the integer constants with string constants. See also: OOConstToJSString.h. This has grown beyond "const-to-string" at this point. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #import "OOFunctionAttributes.h" #import "OOTypes.h" enum { // Values used for unknown strings. kOOCargoTypeDefault = CARGO_NOT_CARGO, // kOOCommodityTypeDefault = COMMODITY_UNDEFINED, kOOEnergyUnitTypeDefault = ENERGY_UNIT_NONE, kOORouteTypeDefault = OPTIMIZED_BY_JUMPS }; /* To avoid pulling in unnecessary headers, some functions defined in OOConstToString.m are declared in the header with the appropriate type declaration, in particular: Entity.h: OOStringFromEntityStatus() OOEntityStatusFromString() OOStringFromScanClass() OOScanClassFromString() ShipEntity.h: OOStringFromBehaviour() OOEquipmentIdentifierFromWeaponType() OOWeaponTypeFromEquipmentIdentifierSloppy() OOWeaponTypeFromEquipmentIdentifierStrict() OOStringFromWeaponType() OOWeaponTypeFromString() OODisplayStringFromAlertCondition() PlayerEntity.h: OODisplayRatingStringFromKillCount() KillCountToRatingAndKillString() OODisplayStringFromLegalStatus() OOStringFromGUIScreenID() OOGUIScreenIDFromString() OOGalacticHyperspaceBehaviourFromString() OOStringFromGalacticHyperspaceBehaviour() Universe.h: OODisplayStringFromGovernmentID() OODisplayStringFromEconomyID() OOOpenGL.h: OOShaderSettingFromString() OOStringFromShaderSetting() OODisplayStringFromShaderSetting() */ NSString *JSTypeToString(int /* JSType */ type) CONST_FUNC; NSString *CargoTypeToString(OOCargoType cargo) CONST_FUNC; OOCargoType StringToCargoType(NSString *string) PURE_FUNC; //NSString *CommodityTypeToString(OOCommodityType commodity) CONST_FUNC; // returns the commodity identifier //OOCommodityType StringToCommodityType(NSString *string) PURE_FUNC; // needs commodity identifier NSString *EnergyUnitTypeToString(OOEnergyUnitType unit) CONST_FUNC; OOEnergyUnitType StringToEnergyUnitType(NSString *string) PURE_FUNC; NSString *CommodityDisplayNameForSymbolicName(NSString *symbolicName); NSString *CommodityDisplayNameForCommodityArray(NSArray *commodityDefinition); NSString *DisplayStringForMassUnit(OOMassUnit unit); NSString *DisplayStringForMassUnitForCommodity(OOCommodityType commodity); NSString *OOStringFromCompassMode(OOCompassMode mode); OOCompassMode OOCompassModeFromString(NSString *string); NSString *OOStringFromLegalStatusReason(OOLegalStatusReason reason); NSString *RouteTypeToString(OORouteType routeType); OORouteType StringToRouteType(NSString *string); NSString *DockingClearanceStatusToString(OODockingClearanceStatus dockingClearanceStatus) PURE_FUNC; NSString *OOStringFromGraphicsDetail(OOGraphicsDetail detail); OOGraphicsDetail OOGraphicsDetailFromString(NSString *string); oolite-1.82/src/Core/OOConstToString.m000066400000000000000000000302321256642440500176220ustar00rootroot00000000000000/* OOConstToString.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version ); of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., ); Franklin Street, Fifth Floor, Boston, MA );-);, USA. */ #import "OOConstToString.h" #include #import "OOCollectionExtractors.h" #import "Universe.h" #import "PlayerEntity.h" #import "OOEquipmentType.h" #define CASE(foo) case foo: return @#foo; #define REVERSE_CASE(foo) if ([string isEqualToString:@#foo]) return foo; #define ENTRY(label, value) case label: return @#label; #define GALACTIC_HYPERSPACE_ENTRY(label, value) case GALACTIC_HYPERSPACE_##label: return @#label; #define DIFF_STRING_ENTRY(label, string) case label: return @string; NSString *OOStringFromEntityStatus(OOEntityStatus value) { switch (value) { #include "OOEntityStatus.tbl" } return @"UNDEFINED"; } NSString *OOStringFromBehaviour(OOBehaviour value) { switch (value) { #include "OOBehaviour.tbl" } return @"** BEHAVIOUR UNKNOWN **"; } NSString *OOStringFromCompassMode(OOCompassMode value) { switch (value) { #include "OOCompassMode.tbl" } return @"UNDEFINED"; } NSString *OOStringFromGalacticHyperspaceBehaviour(OOGalacticHyperspaceBehaviour value) { switch (value) { #include "OOGalacticHyperspaceBehaviour.tbl" } return @"UNDEFINED"; } NSString *OOStringFromGUIScreenID(OOGUIScreenID value) { switch (value) { #include "OOGUIScreenID.tbl" } return @"UNDEFINED"; } NSString *OOStringFromScanClass(OOScanClass value) { switch (value) { #include "OOScanClass.tbl" } return @"UNDEFINED"; } NSString *OOStringFromShipDamageType(OOShipDamageType value) { switch (value) { #include "OOShipDamageType.tbl" } return @"UNDEFINED"; } NSString *OOStringFromLegalStatusReason(OOLegalStatusReason value) { switch (value) { #include "OOLegalStatusReason.tbl" } return @"UNDEFINED"; } #undef ENTRY #undef GALACTIC_HYPERSPACE_ENTRY #define ENTRY(label, value) if ([string isEqualToString:@#label]) return label; #define GALACTIC_HYPERSPACE_ENTRY(label, value) if ([string isEqualToString:@#label]) return GALACTIC_HYPERSPACE_##label; OOEntityStatus OOEntityStatusFromString(NSString *string) { #include "OOEntityStatus.tbl" return kOOEntityStatusDefault; } OOCompassMode OOCompassModeFromString(NSString *string) { #include "OOCompassMode.tbl" return kOOCompassModeDefault; } OOGalacticHyperspaceBehaviour OOGalacticHyperspaceBehaviourFromString(NSString *string) { #include "OOGalacticHyperspaceBehaviour.tbl" // Transparently (but inefficiently) support american spelling. FIXME: remove in EMMSTRAN. if ([string hasPrefix:@"BEHAVIOR_"]) { string = [string substringFromIndex:[@"BEHAVIOR_" length]]; string = [@"BEHAVIOUR_" stringByAppendingString:string]; return OOGalacticHyperspaceBehaviourFromString(string); } return kOOGalacticHyperspaceBehaviourDefault; } OOGUIScreenID OOGUIScreenIDFromString(NSString *string) { #include "OOGUIScreenID.tbl" return kOOGUIScreenIDDefault; } OOScanClass OOScanClassFromString(NSString *string) { #include "OOScanClass.tbl" return kOOScanClassDefault; } #undef ENTRY #undef GALACTIC_HYPERSPACE_ENTRY NSString *RouteTypeToString(OORouteType routeType) { switch (routeType) { CASE(OPTIMIZED_BY_NONE); CASE(OPTIMIZED_BY_JUMPS); CASE(OPTIMIZED_BY_TIME); } return @"** ROUTE TYPE UNKNOWN **"; } NSString *OODisplayStringFromGovernmentID(OOGovernmentID government) { NSArray *strings = nil; NSString *value = nil; strings = [[UNIVERSE descriptions] objectForKey:@"government"]; if ([strings isKindOfClass:[NSArray class]] && government < [strings count]) { value = [strings objectAtIndex:government]; if ([value isKindOfClass:[NSString class]]) return value; } return nil; } NSString *OODisplayStringFromEconomyID(OOEconomyID economy) { NSArray *strings = nil; NSString *value = nil; strings = [[UNIVERSE descriptions] objectForKey:@"economy"]; if ([strings isKindOfClass:[NSArray class]] && economy < [strings count]) { value = [strings objectAtIndex:economy]; if ([value isKindOfClass:[NSString class]]) return value; } return nil; } NSString *JSTypeToString(int /* JSType */ type) { switch ((JSType)type) { CASE(JSTYPE_VOID); CASE(JSTYPE_OBJECT); CASE(JSTYPE_FUNCTION); CASE(JSTYPE_STRING); CASE(JSTYPE_NUMBER); CASE(JSTYPE_BOOLEAN); CASE(JSTYPE_NULL); CASE(JSTYPE_XML); CASE(JSTYPE_LIMIT); } return [NSString stringWithFormat:@"unknown (%u)", type]; } NSString *OOStringFromWeaponType(OOWeaponType weapon) { if (weapon == nil) { return @"EQ_WEAPON_NONE"; } else { return [weapon identifier]; } } OOWeaponType OOWeaponTypeFromString(NSString *string) { return OOWeaponTypeFromEquipmentIdentifierSloppy(string); } NSString *OOEquipmentIdentifierFromWeaponType(OOWeaponType weapon) { return [weapon identifier]; } OOWeaponType OOWeaponTypeFromEquipmentIdentifierSloppy(NSString *string) { OOWeaponType w = [OOEquipmentType equipmentTypeWithIdentifier:string]; if (w == nil) { if (![string hasPrefix:@"EQ_"]) { w = [OOEquipmentType equipmentTypeWithIdentifier:[NSString stringWithFormat:@"EQ_%@",string]]; if (w != nil) { return w; } } return [OOEquipmentType equipmentTypeWithIdentifier:@"EQ_WEAPON_NONE"]; } return w; } /* Previous save games will have weapon types stored as ints to the * various weapon types */ OOWeaponType OOWeaponTypeFromEquipmentIdentifierLegacy(NSString *string) { if ([string intValue] > 0) { switch ([string intValue]) { case 2: return OOWeaponTypeFromEquipmentIdentifierSloppy(@"EQ_WEAPON_PULSE_LASER"); case 3: return OOWeaponTypeFromEquipmentIdentifierSloppy(@"EQ_WEAPON_BEAM_LASER"); case 4: return OOWeaponTypeFromEquipmentIdentifierSloppy(@"EQ_WEAPON_MINING_LASER"); case 5: return OOWeaponTypeFromEquipmentIdentifierSloppy(@"EQ_WEAPON_MILITARY_LASER"); case 10: return OOWeaponTypeFromEquipmentIdentifierSloppy(@"EQ_WEAPON_THARGOID_LASER"); default: return OOWeaponTypeFromEquipmentIdentifierSloppy(string); } } return OOWeaponTypeFromEquipmentIdentifierSloppy(string); } OOWeaponType OOWeaponTypeFromEquipmentIdentifierStrict(NSString *string) { // there is no difference between the two any more return OOWeaponTypeFromEquipmentIdentifierSloppy(string); } NSString *CargoTypeToString(OOCargoType cargo) { switch (cargo) { CASE(CARGO_NOT_CARGO); CASE(CARGO_SLAVES); CASE(CARGO_ALLOY); CASE(CARGO_MINERALS); CASE(CARGO_THARGOID); CASE(CARGO_RANDOM); CASE(CARGO_SCRIPTED_ITEM); CASE(CARGO_CHARACTER); } return @"Unknown cargo"; } OOCargoType StringToCargoType(NSString *string) { REVERSE_CASE(CARGO_NOT_CARGO); REVERSE_CASE(CARGO_SLAVES); REVERSE_CASE(CARGO_ALLOY); REVERSE_CASE(CARGO_MINERALS); REVERSE_CASE(CARGO_THARGOID); REVERSE_CASE(CARGO_RANDOM); REVERSE_CASE(CARGO_SCRIPTED_ITEM); REVERSE_CASE(CARGO_CHARACTER); // Backwards compatibility. if ([string isEqual:@"CARGO_CARRIED"]) return CARGO_RANDOM; return kOOCargoTypeDefault; } NSString *EnergyUnitTypeToString(OOEnergyUnitType unit) { switch (unit) { CASE(ENERGY_UNIT_NONE); CASE(ENERGY_UNIT_NORMAL); CASE(ENERGY_UNIT_NAVAL); CASE(ENERGY_UNIT_NORMAL_DAMAGED); CASE(ENERGY_UNIT_NAVAL_DAMAGED); case OLD_ENERGY_UNIT_NORMAL: case OLD_ENERGY_UNIT_NAVAL: break; } return @"Unsupported energy unit"; } OOEnergyUnitType StringToEnergyUnitType(NSString *string) { REVERSE_CASE(ENERGY_UNIT_NONE); REVERSE_CASE(ENERGY_UNIT_NORMAL); REVERSE_CASE(ENERGY_UNIT_NAVAL); REVERSE_CASE(ENERGY_UNIT_NORMAL_DAMAGED); REVERSE_CASE(ENERGY_UNIT_NAVAL_DAMAGED); return kOOEnergyUnitTypeDefault; } NSString *OODisplayRatingStringFromKillCount(unsigned kills) { enum { kRatingCount = 9 }; NSArray *ratingNames = nil; const unsigned killThresholds[kRatingCount - 1] = { 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, 0x0200, 0x0A00, 0x1900 }; unsigned i; ratingNames = [[UNIVERSE descriptions] oo_arrayForKey:@"rating"]; for (i = 0; i < kRatingCount - 1; ++i) { if (kills < killThresholds[i]) return [ratingNames oo_stringAtIndex:i]; } return [ratingNames oo_stringAtIndex:kRatingCount - 1]; } NSString *KillCountToRatingAndKillString(unsigned kills) { return [NSString stringWithFormat:@"%@ (%u)", OODisplayRatingStringFromKillCount(kills), kills]; } NSString *OODisplayStringFromLegalStatus(int legalStatus) { enum { kStatusCount = 3 }; NSArray *statusNames = nil; const int statusThresholds[kStatusCount - 1] = { 1, 51 }; unsigned i; statusNames = [[UNIVERSE descriptions] oo_arrayForKey:@"legal_status"]; for (i = 0; i != kStatusCount - 1; ++i) { if (legalStatus < statusThresholds[i]) return [statusNames oo_stringAtIndex:i]; } return [statusNames oo_stringAtIndex:kStatusCount - 1]; } NSString *OODisplayStringFromAlertCondition(OOAlertCondition alertCondition) { NSArray *conditionNames = [[UNIVERSE descriptions] oo_arrayForKey:@"condition"]; return [conditionNames oo_stringAtIndex:alertCondition]; } NSString *OODisplayStringFromShaderSetting(OOShaderSetting setting) { switch (setting) { case SHADERS_NOT_SUPPORTED: return DESC(@"shaderfx-not-available"); case SHADERS_OFF: return DESC(@"shaderfx-off"); case SHADERS_SIMPLE: return DESC(@"shaderfx-simple"); case SHADERS_FULL: return DESC(@"shaderfx-full"); } return @"??"; } NSString *OOStringFromShaderSetting(OOShaderSetting setting) { switch (setting) { CASE(SHADERS_OFF); CASE(SHADERS_SIMPLE); CASE(SHADERS_FULL); CASE(SHADERS_NOT_SUPPORTED); } return @"UNDEFINED"; } OOShaderSetting OOShaderSettingFromString(NSString *string) { REVERSE_CASE(SHADERS_OFF); REVERSE_CASE(SHADERS_SIMPLE); REVERSE_CASE(SHADERS_FULL); REVERSE_CASE(SHADERS_NOT_SUPPORTED); return kOOShaderSettingDefault; } NSString *CommodityDisplayNameForSymbolicName(NSString *symbolicName) { NSString *ret = [UNIVERSE descriptionForKey:[@"commodity-name " stringByAppendingString:[symbolicName lowercaseString]]]; return ret ? ret : symbolicName; } NSString *CommodityDisplayNameForCommodityArray(NSArray *commodityDefinition) { return CommodityDisplayNameForSymbolicName([commodityDefinition oo_stringAtIndex:MARKET_NAME]); } NSString *DisplayStringForMassUnit(OOMassUnit unit) { switch (unit) { case UNITS_TONS: return DESC(@"cargo-tons-symbol"); case UNITS_KILOGRAMS: return DESC(@"cargo-kilograms-symbol"); case UNITS_GRAMS: return DESC(@"cargo-grams-symbol"); } return @"??"; } NSString *DisplayStringForMassUnitForCommodity(OOCommodityType commodity) { return DisplayStringForMassUnit([[UNIVERSE commodityMarket] massUnitForGood:commodity]); } OORouteType StringToRouteType(NSString *string) { REVERSE_CASE(OPTIMIZED_BY_NONE); REVERSE_CASE(OPTIMIZED_BY_JUMPS); REVERSE_CASE(OPTIMIZED_BY_TIME); return kOORouteTypeDefault; } NSString *DockingClearanceStatusToString(OODockingClearanceStatus dockingClearanceStatus) { switch (dockingClearanceStatus) { CASE(DOCKING_CLEARANCE_STATUS_NONE); CASE(DOCKING_CLEARANCE_STATUS_REQUESTED); CASE(DOCKING_CLEARANCE_STATUS_NOT_REQUIRED); CASE(DOCKING_CLEARANCE_STATUS_GRANTED); CASE(DOCKING_CLEARANCE_STATUS_TIMING_OUT); } return @"DOCKING_CLEARANCE_STATUS_UNKNOWN"; } NSString *OOStringFromGraphicsDetail(OOGraphicsDetail detail) { switch (detail) { CASE(DETAIL_LEVEL_MINIMUM); CASE(DETAIL_LEVEL_NORMAL); CASE(DETAIL_LEVEL_SHADERS); CASE(DETAIL_LEVEL_EXTRAS); } return @"DETAIL_LEVEL_UNKNOWN"; } OOGraphicsDetail OOGraphicsDetailFromString(NSString *string) { REVERSE_CASE(DETAIL_LEVEL_MINIMUM); REVERSE_CASE(DETAIL_LEVEL_NORMAL); REVERSE_CASE(DETAIL_LEVEL_SHADERS); REVERSE_CASE(DETAIL_LEVEL_EXTRAS); return DETAIL_LEVEL_MINIMUM; } oolite-1.82/src/Core/OOConvertCubeMapToLatLong.h000066400000000000000000000031621256642440500215000ustar00rootroot00000000000000/* OOConvertCubeMapToLatLong.h Convert a cube map texture to a lat/long texture. Copyright (C) 2010-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOPixMap.h" /* OOConvertCubeMapToLatLong Given an RGBA pixmap containing a cube map texture, convert it to a latlong texture of (height * 2) by height pixels. The texture is also flipped, to account for Oolite texture coordianate conventions. The source pix map must have four components and be six times as high as it is wide. 2 x 2 pixel supersampling is used. */ OOPixMap OOConvertCubeMapToLatLong(OOPixMap sourcePixMap, OOPixMapDimension height, BOOL leaveSpaceForMipMaps); oolite-1.82/src/Core/OOConvertCubeMapToLatLong.m000066400000000000000000000100511256642440500215000ustar00rootroot00000000000000/* OOConvertCubeMapToLatLong.m Convert a cube map texture to a lat/long texture. Copyright (C) 2010-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOConvertCubeMapToLatLong.h" #import "OOTextureScaling.h" #define kPiF (3.14159265358979323846264338327950288f) OOPixMap OOConvertCubeMapToLatLong(OOPixMap sourcePixMap, OOPixMapDimension height, BOOL leaveSpaceForMipMaps) { if (!OOIsValidPixMap(sourcePixMap) || sourcePixMap.format != kOOPixMapRGBA || sourcePixMap.height != sourcePixMap.width * 6) { return kOONullPixMap; } NSCParameterAssert(height > 0); height *= 2; OOPixMapDimension width = height * 2; OOPixMap outPixMap = OOAllocatePixMap(width, height, 4, 0, 0); if (!OOIsValidPixMap(outPixMap)) return kOONullPixMap; OOPixMapDimension x, y; uint32_t *pixel = outPixMap.pixels; float fheight = height; float rheight = 1.0f / fheight; float halfSize = (sourcePixMap.width - 1) * 0.5f; uint8_t *srcBytes = sourcePixMap.pixels; // Build tables of sin/cos of longitude. float sinTable[width]; float cosTable[width]; for (x = 0; x < width; x++) { float lon = ((float)x * rheight) * kPiF; sinTable[x] = sin(lon); cosTable[x] = cos(lon); } for (y = 0; y < height; y++) { // Calcuate sin/cos of latitude. /* Clang static analyzer (Xcode 3.2.5 version, through to Xcode 4.4 and freestanding checker-268 at least) says: "Assigned value is garbage or undefined." Memsetting sinTable to all-zeros moves this to the cosTable line. Since every value in each of those tables is in fact defined, this is an error in the analyzer. -- Ahruman 2011-01-25/2012-09-14 */ float cy = -sinTable[width * 3 / 4 - y]; float lac = -cosTable[width * 3 / 4 - y]; float ay = fabs(cy); for (x = 0; x < width; x++) { float cx = sinTable[x] * lac; float cz = cosTable[x] * lac; float ax = fabs(cx); float az = fabs(cz); // Y offset of start of this face in image. OOPixMapDimension yOffset; // Coordinates within selected face. float x, y, r; // Select source face. if (ax >= ay && ax >= az) { x = cz; y = -cy; r = ax; if (0.0f < cx) { yOffset = 0; } else { x = -x; yOffset = 1; } } else if (ay >= ax && ay >= az) { x = cx; y = cz; r = ay; if (0.0f < cy) { y = -y; yOffset = 2; } else { yOffset = 3; } } else { x = cx; y = -cy; r = az; if (0.0f < cz) { x = -x; yOffset = 5; } else { yOffset = 4; } } // Scale coordinates. r = 1.0f / r; OOPixMapDimension ix = (x * r + 1.0f) * halfSize; OOPixMapDimension iy = (y * r + 1.0f) * halfSize; #ifndef NDEBUG assert(ix < sourcePixMap.width && iy < sourcePixMap.width); #endif // Look up pixel. iy += sourcePixMap.width * yOffset; uint32_t *row = (uint32_t *)(srcBytes + iy * sourcePixMap.rowBytes); *pixel++ = row[ix]; } } // Scale to half size for supersamplingness. return OOScalePixMap(outPixMap, width / 2, height / 2, leaveSpaceForMipMaps); } oolite-1.82/src/Core/OOConvertSystemDescriptions.h000066400000000000000000000041341256642440500222530ustar00rootroot00000000000000/* OOConvertSystemDescriptions.m Oolite Functions to convert system descriptions between dictionary and array formats. The array format is used in descriptions.plist. Each set of strings is an array, the sets are stored in a master array, and cross-references are indices into the master array. The dictionary format is more human-friendly, with string sets identified by name and cross-references using names. The indicesToKeys parameter is optional; if it is nil or incomplete, indices or (index-based) keys will be synthesized. Copyright (C) 2008 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import NSArray *OOConvertSystemDescriptionsToArrayFormat(NSDictionary *descriptionsInDictionaryFormat, NSDictionary *indicesToKeys); NSDictionary *OOConvertSystemDescriptionsToDictionaryFormat(NSArray *descriptionsInArrayFormat, NSDictionary *indicesToKeys); NSString *OOStringifySystemDescriptionLine(NSString *line, NSDictionary *indicesToKeys, BOOL useFallback); // Higher-level functions to drive the entire conversion. void CompileSystemDescriptions(BOOL asXML); void ExportSystemDescriptions(BOOL asXML); oolite-1.82/src/Core/OOConvertSystemDescriptions.m000066400000000000000000000304031256642440500222560ustar00rootroot00000000000000/* OOConvertSystemDescriptions.m Oolite Copyright (C) 2008 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "Universe.h" #if OO_LOCALIZATION_TOOLS #import "OOConvertSystemDescriptions.h" #import "OldSchoolPropertyListWriting.h" #import "OOCollectionExtractors.h" #import "ResourceManager.h" static NSMutableDictionary *InitKeyToIndexDict(NSDictionary *dict, NSMutableSet **outUsedIndices); static NSString *IndexToKey(NSUInteger index, NSDictionary *indicesToKeys, BOOL useFallback); static NSArray *ConvertIndicesToKeys(NSArray *entry, NSDictionary *indicesToKeys); static NSNumber *KeyToIndex(NSString *key, NSMutableDictionary *ioKeysToIndices, NSMutableSet *ioUsedIndicies, NSUInteger *ioSlotCache); static NSArray *ConvertKeysToIndices(NSArray *entry, NSMutableDictionary *ioKeysToIndices, NSMutableSet *ioUsedIndicies, NSUInteger *ioSlotCache); static NSUInteger HighestIndex(NSMutableDictionary *sparseArray); // Actually returns highest index + 1, which is fine. void CompileSystemDescriptions(BOOL asXML) { NSDictionary *sysDescDict = nil; NSArray *sysDescArray = nil; NSDictionary *keyMap = nil; NSData *data = nil; NSString *error = nil; sysDescDict = [ResourceManager dictionaryFromFilesNamed:@"sysdesc.plist" inFolder:@"Config" andMerge:NO]; if (sysDescDict == nil) { OOLog(@"sysdesc.compile.failed.fileNotFound", @"Could not load a dictionary from sysdesc.plist, ignoring --compile-sysdesc option."); return; } keyMap = [ResourceManager dictionaryFromFilesNamed:@"sysdesc_key_table.plist" inFolder:@"Config" andMerge:NO]; // keyMap is optional, so no nil check sysDescArray = OOConvertSystemDescriptionsToArrayFormat(sysDescDict, keyMap); if (sysDescArray == nil) { OOLog(@"sysdesc.compile.failed.conversion", @"Could not convert sysdesc.plist to descriptions.plist format for some reason."); return; } sysDescDict = [NSDictionary dictionaryWithObject:sysDescArray forKey:@"system_description"]; if (asXML) { data = [NSPropertyListSerialization dataFromPropertyList:sysDescDict format:NSPropertyListXMLFormat_v1_0 errorDescription:&error]; } else { data = [sysDescDict oldSchoolPListFormatWithErrorDescription:&error]; } if (data == nil) { OOLog(@"sysdesc.compile.failed.XML", @"Could not convert translated sysdesc.plist to property list: %@.", error); return; } if ([ResourceManager writeDiagnosticData:data toFileNamed:@"sysdesc-compiled.plist"]) { OOLog(@"sysdesc.compile.success", @"Wrote translated sysdesc.plist to sysdesc-compiled.plist."); } else { OOLog(@"sysdesc.compile.failed.writeFailure", @"Could not write translated sysdesc.plist to sysdesc-compiled.plist."); } } void ExportSystemDescriptions(BOOL asXML) { NSArray *sysDescArray = nil; NSDictionary *sysDescDict = nil; NSDictionary *keyMap = nil; NSData *data = nil; NSString *error = nil; sysDescArray = [[UNIVERSE descriptions] oo_arrayForKey:@"system_description"]; keyMap = [ResourceManager dictionaryFromFilesNamed:@"sysdesc_key_table.plist" inFolder:@"Config" andMerge:NO]; // keyMap is optional, so no nil check sysDescDict = OOConvertSystemDescriptionsToDictionaryFormat(sysDescArray, keyMap); if (sysDescArray == nil) { OOLog(@"sysdesc.export.failed.conversion", @"Could not convert system_description do sysdesc.plist format for some reason."); return; } if (asXML) { data = [NSPropertyListSerialization dataFromPropertyList:sysDescDict format:NSPropertyListXMLFormat_v1_0 errorDescription:&error]; } else { data = [sysDescDict oldSchoolPListFormatWithErrorDescription:&error]; } if (data == nil) { OOLog(@"sysdesc.export.failed.XML", @"Could not convert translated system_description to XML property list: %@.", error); return; } if ([ResourceManager writeDiagnosticData:data toFileNamed:@"sysdesc.plist"]) { OOLog(@"sysdesc.export.success", @"Wrote translated system_description to sysdesc.plist."); } else { OOLog(@"sysdesc.export.failed.writeFailure", @"Could not write translated system_description to sysdesc.plist."); } } NSArray *OOConvertSystemDescriptionsToArrayFormat(NSDictionary *descriptionsInDictionaryFormat, NSDictionary *indicesToKeys) { NSMutableDictionary *result = nil; NSAutoreleasePool *pool = nil; NSString *key = nil; NSArray *entry = nil; NSEnumerator *keyEnum = nil; NSMutableDictionary *keysToIndices = nil; NSMutableSet *usedIndices = nil; NSUInteger slotCache = 0; NSNumber *index = nil; NSUInteger i, count; NSMutableArray *realResult = nil; pool = [[NSAutoreleasePool alloc] init]; // Use a dictionary as a sparse array. result = [NSMutableDictionary dictionaryWithCapacity:[descriptionsInDictionaryFormat count]]; keysToIndices = InitKeyToIndexDict(indicesToKeys, &usedIndices); for (keyEnum = [descriptionsInDictionaryFormat keyEnumerator]; (key = [keyEnum nextObject]); ) { entry = ConvertKeysToIndices([descriptionsInDictionaryFormat objectForKey:key], keysToIndices, usedIndices, &slotCache); index = KeyToIndex(key, keysToIndices, usedIndices, &slotCache); [result setObject:entry forKey:index]; } count = HighestIndex(result); realResult = [NSMutableArray arrayWithCapacity:count]; for (i = 0; i < count; i++) { entry = [result objectForKey:[NSNumber numberWithUnsignedInteger:i]]; if (entry == nil) entry = [NSArray array]; [realResult addObject:entry]; } [realResult retain]; [pool release]; return [realResult autorelease]; } NSDictionary *OOConvertSystemDescriptionsToDictionaryFormat(NSArray *descriptionsInArrayFormat, NSDictionary *indicesToKeys) { NSMutableDictionary *result = nil; NSAutoreleasePool *pool = nil; NSArray *entry = nil; NSEnumerator *entryEnum = nil; NSString *key = nil; NSUInteger i = 0; result = [NSMutableDictionary dictionaryWithCapacity:[descriptionsInArrayFormat count]]; pool = [[NSAutoreleasePool alloc] init]; for (entryEnum = [descriptionsInArrayFormat objectEnumerator]; (entry = [entryEnum nextObject]); ) { entry = ConvertIndicesToKeys(entry, indicesToKeys); key = IndexToKey(i, indicesToKeys, YES); ++i; [result setObject:entry forKey:key]; } [pool release]; return result; } NSString *OOStringifySystemDescriptionLine(NSString *line, NSDictionary *indicesToKeys, BOOL useFallback) { NSUInteger p1, p2; NSRange searchRange; NSString *before = nil, *after = nil, *middle = nil; NSString *key = nil; searchRange.location = 0; searchRange.length = [line length]; while ([line rangeOfString:@"[" options:NSLiteralSearch range:searchRange].location != NSNotFound) { p1 = [line rangeOfString:@"[" options:NSLiteralSearch range:searchRange].location; p2 = [line rangeOfString:@"]" options:NSLiteralSearch range:searchRange].location + 1; before = [line substringWithRange:NSMakeRange(0, p1)]; after = [line substringWithRange:NSMakeRange(p2,[line length] - p2)]; middle = [line substringWithRange:NSMakeRange(p1 + 1 , p2 - p1 - 2)]; if ([[middle stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"0123456789"]] isEqual:@""] && ![middle isEqual:@""]) { // Found [] around integers only key = IndexToKey([middle intValue], indicesToKeys, useFallback); if (key != nil) { line = [NSString stringWithFormat:@"%@[#%@]%@", before, key, after]; } } searchRange.length -= p2 - searchRange.location; searchRange.location = [line length] - searchRange.length; } return line; } static NSMutableDictionary *InitKeyToIndexDict(NSDictionary *dict, NSMutableSet **outUsedIndices) { NSEnumerator *keyEnum = nil; NSString *key = nil; NSNumber *number = nil; NSMutableDictionary *result = nil; NSMutableSet *used = nil; assert(outUsedIndices != NULL); result = [NSMutableDictionary dictionaryWithCapacity:[dict count]]; used = [NSMutableSet setWithCapacity:[dict count]]; for (keyEnum = [dict keyEnumerator]; (key = [keyEnum nextObject]); ) { // Convert keys of dict to array indices number = [NSNumber numberWithInt:[key intValue]]; [result setObject:number forKey:[dict objectForKey:key]]; [used addObject:number]; } *outUsedIndices = used; return result; } static NSString *IndexToKey(NSUInteger index, NSDictionary *indicesToKeys, BOOL useFallback) { NSString *result = [indicesToKeys objectForKey:[NSString stringWithFormat:@"%lu", index]]; if (result == nil && useFallback) result = [NSString stringWithFormat:@"block_%lu", index]; return result; } static NSArray *ConvertIndicesToKeys(NSArray *entry, NSDictionary *indicesToKeys) { NSEnumerator *lineEnum = nil; NSString *line = nil; NSMutableArray *result = nil; result = [NSMutableArray arrayWithCapacity:[entry count]]; for (lineEnum = [entry objectEnumerator]; (line = [lineEnum nextObject]); ) { [result addObject:OOStringifySystemDescriptionLine(line, indicesToKeys, YES)]; } return result; } static NSNumber *KeyToIndex(NSString *key, NSMutableDictionary *ioKeysToIndices, NSMutableSet *ioUsedIndicies, NSUInteger *ioSlotCache) { NSNumber *result = nil; assert(ioSlotCache != NULL); result = [ioKeysToIndices objectForKey:key]; if (result == nil) { // Search for free index do { result = [NSNumber numberWithUnsignedInteger:(*ioSlotCache)++]; } while ([ioUsedIndicies containsObject:result]); [ioKeysToIndices setObject:result forKey:key]; [ioUsedIndicies addObject:result]; OOLog(@"sysdesc.compile.unknownKey", @"Assigning key \"%@\" to index %@.", key, result); } return result; } static NSArray *ConvertKeysToIndices(NSArray *entry, NSMutableDictionary *ioKeysToIndices, NSMutableSet *ioUsedIndicies, NSUInteger *ioSlotCache) { NSEnumerator *lineEnum = nil; NSString *line = nil; NSUInteger p1, p2; NSRange searchRange; NSMutableArray *result = nil; NSString *before = nil, *after = nil, *middle = nil; result = [NSMutableArray arrayWithCapacity:[entry count]]; for (lineEnum = [entry objectEnumerator]; (line = [lineEnum nextObject]); ) { searchRange.location = 0; searchRange.length = [line length]; while ([line rangeOfString:@"[" options:NSLiteralSearch range:searchRange].location != NSNotFound) { p1 = [line rangeOfString:@"[" options:NSLiteralSearch range:searchRange].location; p2 = [line rangeOfString:@"]" options:NSLiteralSearch range:searchRange].location + 1; before = [line substringWithRange:NSMakeRange(0, p1)]; after = [line substringWithRange:NSMakeRange(p2,[line length] - p2)]; middle = [line substringWithRange:NSMakeRange(p1 + 1 , p2 - p1 - 2)]; if ([middle length] > 1 && [middle hasPrefix:@"#"]) { // Found [] around key line = [NSString stringWithFormat:@"%@[%@]%@", before, KeyToIndex([middle substringFromIndex:1], ioKeysToIndices, ioUsedIndicies, ioSlotCache), after]; } searchRange.length -= p2 - searchRange.location; searchRange.location = [line length] - searchRange.length; } [result addObject:line]; } return result; } static NSUInteger HighestIndex(NSMutableDictionary *sparseArray) { NSEnumerator *keyEnum = nil; NSNumber *key = nil; NSUInteger curr, highest = 0; for (keyEnum = [sparseArray keyEnumerator]; (key = [keyEnum nextObject]); ) { curr = [key intValue]; if (highest < curr) highest = curr; } return highest; } #endif oolite-1.82/src/Core/OOCrosshairs.h000066400000000000000000000025551256642440500171640ustar00rootroot00000000000000/* OOCrosshairs.h Oolite Copyright (C) 2008 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" #import "OOOpenGL.h" @class OOColor; @interface OOCrosshairs: NSObject { @private NSUInteger _count; GLfloat *_data; } - (id) initWithPoints:(NSArray *)points scale:(GLfloat)scale color:(OOColor *)color overallAlpha:(GLfloat)alpha; - (void) render; @end oolite-1.82/src/Core/OOCrosshairs.m000066400000000000000000000117051256642440500171660ustar00rootroot00000000000000/* OOCrosshairs.m Oolite Copyright (C) 2008 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCrosshairs.h" #import "OOColor.h" #import "OOCollectionExtractors.h" #import "Universe.h" #import "MyOpenGLView.h" #import "OOMacroOpenGL.h" @interface OOCrosshairs (Private) - (void) setUpDataWithPoints:(NSArray *)points scale:(GLfloat)scale color:(OOColor *)color overallAlpha:(GLfloat)alpha; - (void) setUpDataForOnePoint:(NSArray *)pointInfo scale:(GLfloat)scale colorComps:(float[4])colorComps overallAlpha:(GLfloat)alpha data:(GLfloat *)ioBuffer; @end @implementation OOCrosshairs - (id) initWithPoints:(NSArray *)points scale:(GLfloat)scale color:(OOColor *)color overallAlpha:(GLfloat)alpha { if ((self = [super init])) { if (alpha > 0.0f && (color == nil || [color alphaComponent] != 0.0f)) { [self setUpDataWithPoints:points scale:scale color:color overallAlpha:alpha]; } } return self; } - (void) dealloc { free(_data); [super dealloc]; } - (void) render { if (_data != NULL) { OO_ENTER_OPENGL(); OOSetOpenGLState(OPENGL_STATE_OVERLAY); OOGL(glPushAttrib(GL_ENABLE_BIT)); OOGL(glDisable(GL_LIGHTING)); OOGL(glDisable(GL_TEXTURE_2D)); OOGLPushModelView(); OOGLTranslateModelView(make_vector(0.0, 0.0, [[UNIVERSE gameView] display_z])); OOGL(glVertexPointer(2, GL_FLOAT, sizeof (GLfloat) * 6, _data)); OOGL(glColorPointer(4, GL_FLOAT, sizeof (GLfloat) * 6, _data + 2)); OOGL(glEnableClientState(GL_VERTEX_ARRAY)); OOGL(glEnableClientState(GL_COLOR_ARRAY)); OOGL(glDrawArrays(GL_LINES, 0, _count * 2)); OOGL(glDisableClientState(GL_VERTEX_ARRAY)); OOGL(glDisableClientState(GL_COLOR_ARRAY)); OOGLPopModelView(); OOGL(glPopAttrib()); OOVerifyOpenGLState(); OOCheckOpenGLErrors(@"OOCrosshairs after rendering"); } } - (void) setUpDataWithPoints:(NSArray *)points scale:(GLfloat)scale color:(OOColor *)color overallAlpha:(GLfloat)alpha { NSUInteger i; float colorComps[4] = { 0.0f, 1.0f, 0.0f, 1.0f }; GLfloat *data = NULL; _count = [points count]; if (_count == 0) return; _data = malloc(sizeof (GLfloat) * 12 * _count); // 2 coordinates, 4 colour components for each endpoint of each line segment [color getRed:&colorComps[0] green:&colorComps[1] blue:&colorComps[2] alpha:&colorComps[3]]; // Turn NSArray into GL-friendly element array data = _data; for (i = 0; i < _count; i++) { [self setUpDataForOnePoint:[points oo_arrayAtIndex:i] scale:scale colorComps:colorComps overallAlpha:alpha data:data]; data += 12; } } - (void) setUpDataForOnePoint:(NSArray *)pointInfo scale:(GLfloat)scale colorComps:(float[4])colorComps overallAlpha:(GLfloat)alpha data:(GLfloat *)ioBuffer { GLfloat x1, y1, a1, x2, y2, a2; GLfloat r, g, b, a; if ([pointInfo count] >= 6) { a1 = [pointInfo oo_floatAtIndex:0]; x1 = [pointInfo oo_floatAtIndex:1] * scale; y1 = [pointInfo oo_floatAtIndex:2] * scale; a2 = [pointInfo oo_floatAtIndex:3]; x2 = [pointInfo oo_floatAtIndex:4] * scale; y2 = [pointInfo oo_floatAtIndex:5] * scale; r = colorComps[0]; g = colorComps[1]; b = colorComps[2]; a = colorComps[3]; /* a1/a2 * a is hud.plist and crosshairs.plist - specified alpha, which must be clamped to 0..1 so the plist-specified alpha can't "escape" the overall alpha range. The result of scaling this by overall HUD alpha is then clamped again for robustness. */ a1 = OOClamp_0_1_f(OOClamp_0_1_f(a1 * a) * alpha); a2 = OOClamp_0_1_f(OOClamp_0_1_f(a2 * a) * alpha); } else { // Bad entry, write red point in middle. x1 = -0.01f; x2 = 0.01f; y1 = y2 = 0.0f; r = 1.0f; g = b = 0.0f; a1 = a2 = 1.0; } *ioBuffer++ = x1; *ioBuffer++ = y1; *ioBuffer++ = r; *ioBuffer++ = g; *ioBuffer++ = b; *ioBuffer++ = a1; *ioBuffer++ = x2; *ioBuffer++ = y2; *ioBuffer++ = r; *ioBuffer++ = g; *ioBuffer++ = b; *ioBuffer++ = a2; (void)ioBuffer; } @end oolite-1.82/src/Core/OODebugGLDrawing.h000066400000000000000000000116721256642440500176310ustar00rootroot00000000000000/* OODebugDrawing.h A set of functions for drawing debug stuff like bounding boxes. These are drawn in wireframe without Z buffer reads or writes. Copyright (C) 2007-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOMaths.h" #import "OOColor.h" #if !defined(OODEBUGLDRAWING_DISABLE) && defined(NDEBUG) #define OODEBUGLDRAWING_DISABLE 1 #endif #ifndef OODEBUGLDRAWING_DISABLE @class OOMaterial; typedef struct { OOMaterial *material; } OODebugWFState; OODebugWFState OODebugBeginWireframe(BOOL ignoreZ); void OODebugEndWireframe(OODebugWFState state); OOINLINE void OODebugDrawBoundingBox(BoundingBox box); OOINLINE void OODebugDrawBoundingBoxBetween(Vector min, Vector max); OOINLINE void OODebugDrawColoredBoundingBox(BoundingBox box, OOColor *color); void OODebugDrawColoredBoundingBoxBetween(Vector min, Vector max, OOColor *color); // Normals are drawn as cyan lines OOINLINE void OODebugDrawNormal(Vector position, Vector normal, GLfloat scale); OOINLINE void OODebugDrawNormalAtOrigin(Vector normal, GLfloat scale); // Other vectors are drawn as magenta lines by default OOINLINE void OODebugDrawVector(Vector position, Vector v); OOINLINE void OODebugDrawColoredVector(Vector position, Vector v, OOColor *color); OOINLINE void OODebugDrawVectorAtOrigin(Vector v); OOINLINE void OODebugDrawColoredVectorAtOrigin(Vector v, OOColor *color); // Lines are drawn white by default OOINLINE void OODebugDrawLine(Vector start, Vector end); void OODebugDrawColoredLine(Vector start, Vector end, OOColor *color); // Bases are drawn as one red, one green and one blue vector, representing x, y and z axes in the current coordinate frame. void OODebugDrawBasis(Vector position, GLfloat scale); OOINLINE void OODebugDrawBasisAtOrigin(GLfloat scale); void OODebugDrawPoint(Vector position, OOColor *color); /*** Only inline definitions beyond this point ***/ OOINLINE void OODebugDrawBoundingBoxBetween(Vector min, Vector max) { OODebugDrawColoredBoundingBoxBetween(min, max, [OOColor blueColor]); } OOINLINE void OODebugDrawBoundingBox(BoundingBox box) { OODebugDrawBoundingBoxBetween(box.min, box.max); } OOINLINE void OODebugDrawColoredBoundingBox(BoundingBox box, OOColor *color) { OODebugDrawColoredBoundingBoxBetween(box.min, box.max, color); } OOINLINE void OODebugDrawNormal(Vector position, Vector normal, GLfloat scale) { OODebugDrawColoredVector(position, vector_add(position, vector_multiply_scalar(normal, scale)), [OOColor cyanColor]); } OOINLINE void OODebugDrawNormalAtOrigin(Vector normal, GLfloat scale) { OODebugDrawNormal(kZeroVector, normal, scale); } OOINLINE void OODebugDrawColoredVector(Vector position, Vector v, OOColor *color) { OODebugDrawColoredLine(position, vector_add(position, v), color); } OOINLINE void OODebugDrawVector(Vector position, Vector v) { OODebugDrawColoredVector(position, v, [OOColor magentaColor]); } OOINLINE void OODebugDrawVectorAtOrigin(Vector v) { OODebugDrawVector(kZeroVector, v); } OOINLINE void OODebugDrawColoredVectorAtOrigin(Vector v, OOColor *color) { OODebugDrawColoredVector(kZeroVector, v, color); } OOINLINE void OODebugDrawLine(Vector start, Vector end) { OODebugDrawColoredLine(start, end, [OOColor whiteColor]); } OOINLINE void OODebugDrawBasisAtOrigin(GLfloat scale) { OODebugDrawBasis(kZeroVector, scale); } #else // OODEBUGLDRAWING_DISABLE #define OODRAW_NOOP do {} while (0) #define OODebugDrawBoundingBox(box) OODRAW_NOOP #define OODebugDrawBoundingBoxBetween(min, max) OODRAW_NOOP #define OODebugDrawNormal(position, normal, scale) OODRAW_NOOP #define OODebugDrawNormalAtOrigin(normal, scale) OODRAW_NOOP #define OODebugDrawVector(position, v) OODRAW_NOOP #define OODebugDrawColoredVector(position, v, color) OODRAW_NOOP #define OODebugDrawVectorAtOrigin(v) OODRAW_NOOP #define OODebugDrawColoredVectorAtOrigin(v, color) OODRAW_NOOP #define OODebugDrawBasis(position, scale) OODRAW_NOOP #define OODebugDrawBasisAtOrigin(scale) OODRAW_NOOP #endif // OODEBUGLDRAWING_DISABLE oolite-1.82/src/Core/OODebugGLDrawing.m000066400000000000000000000102411256642440500176250ustar00rootroot00000000000000/* OODebugDrawing.m Copyright (C) 2007-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OODebugGLDrawing.h" #import "OOMacroOpenGL.h" #import "OOMaterial.h" #ifndef OODEBUGLDRAWING_DISABLE OOINLINE void ApplyColor(OOColor *color) { GLfloat r, g, b, a; OO_ENTER_OPENGL(); if (EXPECT_NOT(color == nil)) color = [OOColor lightGrayColor]; [color getRed:&r green:&g blue:&b alpha:&a]; OOGL(glColor4f(r, g, b, a)); } void OODebugDrawColoredBoundingBoxBetween(Vector min, Vector max, OOColor *color) { OODebugWFState state = OODebugBeginWireframe(YES); OO_ENTER_OPENGL(); ApplyColor(color); OOGLBEGIN(GL_LINE_LOOP); glVertex3f(min.x, min.y, min.z); glVertex3f(max.x, min.y, min.z); glVertex3f(max.x, max.y, min.z); glVertex3f(min.x, max.y, min.z); glVertex3f(min.x, max.y, max.z); glVertex3f(max.x, max.y, max.z); glVertex3f(max.x, min.y, max.z); glVertex3f(min.x, min.y, max.z); OOGLEND(); OOGLBEGIN(GL_LINES); glVertex3f(max.x, min.y, min.z); glVertex3f(max.x, min.y, max.z); glVertex3f(max.x, max.y, min.z); glVertex3f(max.x, max.y, max.z); glVertex3f(min.x, min.y, min.z); glVertex3f(min.x, max.y, min.z); glVertex3f(min.x, min.y, max.z); glVertex3f(min.x, max.y, max.z); OOGLEND(); OODebugEndWireframe(state); } void OODebugDrawColoredLine(Vector start, Vector end, OOColor *color) { OODebugWFState state = OODebugBeginWireframe(YES); OO_ENTER_OPENGL(); ApplyColor(color); OOGLBEGIN(GL_LINES); glVertex3f(start.x, start.y, start.z); glVertex3f(end.x, end.y, end.z); OOGLEND(); OODebugEndWireframe(state); } void OODebugDrawBasis(Vector position, GLfloat scale) { OODebugWFState state = OODebugBeginWireframe(YES); OO_ENTER_OPENGL(); OOGLBEGIN(GL_LINES); glColor4f(1.0f, 0.0f, 0.0f, 1.0f); glVertex3f(position.x, position.y, position.z); glVertex3f(position.x + scale, position.y, position.z); glColor4f(0.0f, 1.0f, 0.0f, 1.0f); glVertex3f(position.x, position.y, position.z); glVertex3f(position.x, position.y + scale, position.z); glColor4f(0.0f, 0.0f, 1.0f, 1.0f); glVertex3f(position.x, position.y, position.z); glVertex3f(position.x, position.y, position.z + scale); OOGLEND(); OODebugEndWireframe(state); } void OODebugDrawPoint(Vector position, OOColor *color) { OODebugWFState state = OODebugBeginWireframe(YES); OO_ENTER_OPENGL(); ApplyColor(color); OOGL(GLScaledPointSize(10)); OOGLBEGIN(GL_POINTS); glVertex3f(position.x, position.y, position.z); OOGLEND(); OODebugEndWireframe(state); } OODebugWFState OODebugBeginWireframe(BOOL ignoreZ) { OO_ENTER_OPENGL(); OODebugWFState state = { .material = [OOMaterial current] }; [OOMaterial applyNone]; OOGL(glPushAttrib(GL_ENABLE_BIT | GL_DEPTH_BUFFER_BIT | GL_LINE_BIT | GL_POINT_BIT | GL_CURRENT_BIT)); OOGL(glDisable(GL_LIGHTING)); OOGL(glDisable(GL_TEXTURE_2D)); OOGL(glDisable(GL_FOG)); if (ignoreZ) { OOGL(glDisable(GL_DEPTH_TEST)); OOGL(glDepthMask(GL_FALSE)); } else { OOGL(glEnable(GL_DEPTH_TEST)); OOGL(glDepthMask(GL_TRUE)); } OOGL(GLScaledLineWidth(1.0f)); return state; } void OODebugEndWireframe(OODebugWFState state) { OO_ENTER_OPENGL(); OOGL(glPopAttrib()); [state.material apply]; } #endif oolite-1.82/src/Core/OODeepCopy.h000066400000000000000000000041651256642440500165530ustar00rootroot00000000000000/* OODeepCopy.h Informal protocol and utility function for making efficient deep copies of immutable collections. It is implemented in such a way that all objects can be deep copied. Objects that implement the NSCopying protocol are automatically copied, while others are retained. The following special cases exist: * NSStrings and NSValues (including NSNumbers) are uniqued - that is, the resulting collection will only include one (immutable) copy of any string or number. * Arrays, sets and dictionaries deep copy their contents. For objects where the mutable/immutable distinction exists, the result should be expected to be immutable. This self-optimizing behaviour is similar to that performed by binary plist export. NOTE: in accordance with Cocoa coding conventions, methods and functions with Copy in the name return objects owned by the receiver. Copyright (C) 2009-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" #import "OOFunctionAttributes.h" id OODeepCopy(id object) OO_RETURNS_RETAINED; @interface NSObject (OODeepCopy) - (id) ooDeepCopyWithSharedObjects:(NSMutableSet *)objects OO_RETURNS_RETAINED; @end oolite-1.82/src/Core/OODeepCopy.m000066400000000000000000000140571256642440500165610ustar00rootroot00000000000000/* OODeepCopy.m Copyright (C) 2009-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OODeepCopy.h" id OODeepCopy(id object) { NSAutoreleasePool *pool = nil; NSMutableSet *objects = nil; if (object == nil) return nil; pool = [[NSAutoreleasePool alloc] init]; objects = [NSMutableSet set]; object = [object ooDeepCopyWithSharedObjects:objects]; [pool release]; return object; } @implementation NSObject (OODeepCopy) - (id) ooDeepCopyWithSharedObjects:(NSMutableSet *)objects { if ([self conformsToProtocol:@protocol(NSCopying)]) { return [self copy]; } else { return [self retain]; } } @end @implementation NSString (OODeepCopy) - (id) ooDeepCopyWithSharedObjects:(NSMutableSet *)objects { NSUInteger length = [self length]; if (length == 0) return [[NSString string] retain]; if (length > 128) return [self copy]; id object = [objects member:self]; if (object != nil && [object isKindOfClass:[NSString class]]) { return [object retain]; } else { object = [self copy]; [objects addObject:object]; return object; } } @end @implementation NSValue (OODeepCopy) // Includes NSNumber - (id) ooDeepCopyWithSharedObjects:(NSMutableSet *)objects { id object = [objects member:self]; if (object != nil && [object isKindOfClass:[NSValue class]]) { return [object retain]; } else { object = [self copy]; [objects addObject:object]; return object; } } @end @implementation NSArray (OODeepCopy) - (id) ooDeepCopyWithSharedObjects:(NSMutableSet *)objects { NSUInteger i, count; id *members = NULL; NSArray *result = nil; BOOL tempObjects = NO; count = [self count]; if (count == 0) return [[NSArray array] retain]; members = calloc(sizeof *members, count); if (members == NULL) { [NSException raise:NSMallocException format:@"Failed to allocate space for %lu objects in %s.", (unsigned long)count, __PRETTY_FUNCTION__]; } // Ensure there's an objects set even if passed nil. if (objects == nil) { objects = [[NSMutableSet alloc] init]; tempObjects = YES; } [self getObjects:members]; @try { // Deep copy members. for (i = 0; i < count; i++) { members[i] = [members[i] ooDeepCopyWithSharedObjects:objects]; } // Make NSArray of results. result = [[NSArray alloc] initWithObjects:members count:count]; } @finally { // Release objects. for (i = 0; i < count; i++) { [members[i] release]; } free(members); if (tempObjects) [objects release]; } // Collections are not reused because comparing them is arbitrarily slow. return result; } @end @implementation NSSet (OODeepCopy) - (id) ooDeepCopyWithSharedObjects:(NSMutableSet *)objects { NSUInteger i, count; id *members = NULL; NSSet *result = nil; BOOL tempObjects = NO; count = [self count]; if (count == 0) return [[NSSet set] retain]; members = malloc(sizeof *members * count); if (members == NULL) { [NSException raise:NSMallocException format:@"Failed to allocate space for %lu objects in %s.", (unsigned long)count, __PRETTY_FUNCTION__]; } // Ensure there's an objects set even if passed nil. if (objects == nil) { objects = [[NSMutableSet alloc] init]; tempObjects = YES; } @try { i = 0; id member = nil; // Deep copy members. foreach (member, self) { members[i] = [member ooDeepCopyWithSharedObjects:objects]; i++; } // Make NSSet of results. result = [[NSSet alloc] initWithObjects:members count:count]; } @finally { // Release objects. for (i = 0; i < count; i++) { [members[i] release]; } free(members); if (tempObjects) [objects release]; } // Collections are not reused because comparing them is arbitrarily slow. return result; } @end @implementation NSDictionary (OODeepCopy) - (id) ooDeepCopyWithSharedObjects:(NSMutableSet *)objects { NSUInteger i, count; id *keys = NULL; id *values = NULL; NSDictionary *result = nil; BOOL tempObjects = NO; count = [self count]; if (count == 0) return [[NSDictionary dictionary] retain]; keys = malloc(sizeof *keys * count); values = malloc(sizeof *values * count); if (keys == NULL || values == NULL) { free(keys); free(values); [NSException raise:NSMallocException format:@"Failed to allocate space for %lu objects in %s.", (unsigned long)count, __PRETTY_FUNCTION__]; } // Ensure there's an objects set even if passed nil. if (objects == nil) { objects = [[NSMutableSet alloc] init]; tempObjects = YES; } @try { i = 0; id key = nil; // Deep copy members. foreachkey (key, self) { keys[i] = [key ooDeepCopyWithSharedObjects:objects]; values[i] = [[self objectForKey:key] ooDeepCopyWithSharedObjects:objects]; i++; } // Make NSDictionary of results. result = [[NSDictionary alloc] initWithObjects:values forKeys:keys count:count]; } @finally { // Release objects. for (i = 0; i < count; i++) { [keys[i] release]; [values[i] release]; } free(keys); free(values); if (tempObjects) [objects release]; } // Collections are not reused because comparing them is arbitrarily slow. return result; } @end oolite-1.82/src/Core/OODrawable.h000066400000000000000000000032561256642440500165640ustar00rootroot00000000000000/* OODrawable.h Abstract base class for objects which can draw themselves. Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" #import "OOOpenGL.h" #import "OOMaths.h" #import "OOWeakReference.h" @interface OODrawable: NSObject - (void)renderOpaqueParts; - (void)renderTranslucentParts; - (BOOL)hasOpaqueParts; - (BOOL)hasTranslucentParts; - (GLfloat)collisionRadius; - (GLfloat)maxDrawDistance; - (BoundingBox)boundingBox; // Passed to all materials. - (void)setBindingTarget:(id)target; - (void)dumpSelfState; #ifndef NDEBUG - (NSSet *) allTextures; - (size_t) totalSize; // Size including dynamic data, not counting textures. #endif @end oolite-1.82/src/Core/OODrawable.m000066400000000000000000000032511256642440500165640ustar00rootroot00000000000000/* OODrawable.m Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OODrawable.h" #import "NSObjectOOExtensions.h" @implementation OODrawable - (void)renderOpaqueParts { } - (void)renderTranslucentParts { } - (BOOL)hasOpaqueParts { return NO; } - (BOOL)hasTranslucentParts { return NO; } - (GLfloat)collisionRadius { return 0.0f; } - (GLfloat)maxDrawDistance { return 0.0f; } - (BoundingBox)boundingBox { return kZeroBoundingBox; } - (void)setBindingTarget:(id)target { } - (void)dumpSelfState { } #ifndef NDEBUG - (NSSet *) allTextures { return nil; } - (size_t) totalSize { return [self oo_objectSize]; } #endif @end oolite-1.82/src/Core/OOEncodingConverter.h000066400000000000000000000044461256642440500204630ustar00rootroot00000000000000/* OOEncodingConverter.h Convert a string to an 8-bit encoding, with some Oolite-specific remappings specified at init time (currently always from oolite-font.plist). Copyright (C) 2008-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef OOENCODINGCONVERTER_EXCLUDE // For the convenience of fonttexgen #import "OOCocoa.h" @class OOCache; @interface OOEncodingConverter: NSObject { @private NSStringEncoding _encoding; OOCache *_cache; NSDictionary *_substitutions; } - (id) initWithEncoding:(NSStringEncoding)encoding substitutions:(NSDictionary *)substitutions; - (id) initWithFontPList:(NSDictionary *)fontPList; - (NSData *) convertString:(NSString *)string; - (NSStringEncoding) encoding; @end #endif //OOENCODINGCONVERTER_EXCLUDE /* There are a variety of overlapping naming schemes for text encoding. We ignore them and use a fixed list: "windows-latin-1" NSWindowsCP1252StringEncoding "windows-latin-2" NSWindowsCP1250StringEncoding "windows-cyrillic" NSWindowsCP1251StringEncoding "windows-greek" NSWindowsCP1253StringEncoding "windows-turkish" NSWindowsCP1254StringEncoding */ NSString *StringFromEncoding(NSStringEncoding encoding); // Returns nil for unknown NSStringEncoding EncodingFromString(NSString *name); // Returns (NSStringEncoding)NSNotFound for unknown oolite-1.82/src/Core/OOEncodingConverter.m000066400000000000000000000151711256642440500204650ustar00rootroot00000000000000/* OOEncodingConverter.m Copyright (C) 2008-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef OOENCODINGCONVERTER_EXCLUDE #import "OOEncodingConverter.h" #import "OOCache.h" #import "OOCollectionExtractors.h" #import "OOLogging.h" /* Using compatibility mapping - converting strings to Unicode form KC - would reduce potential complications in localizing Oolite. However, the method to perform the transformation is not available in GNUstep. I'm currently not using it under OS X either, for cross-platform consistency. -- Ahruman 2008-01-27 */ #if OOLITE_MAC_OS_X #define USE_COMPATIBILITY_MAPPING 0 #else #define USE_COMPATIBILITY_MAPPING 0 #endif #define PROFILE_ENCODING_CONVERTER 0 static const NSUInteger kCachePruneThreshold = 200; #if PROFILE_ENCODING_CONVERTER static OOEncodingConverter *sProfiledConverter = nil; static NSTimer *sProfileTimer = nil; static unsigned sCacheHits = 0; static unsigned sCacheMisses = 0; #endif @interface OOEncodingConverter (Private) - (NSData *) performConversionForString:(NSString *)string; @end @implementation OOEncodingConverter - (id) initWithEncoding:(NSStringEncoding)encoding substitutions:(NSDictionary *)substitutions { self = [super init]; if (self != nil) { _cache = [[OOCache alloc] init]; [_cache setPruneThreshold:kCachePruneThreshold]; [_cache setName:@"Text encoding"]; _substitutions = [substitutions copy]; _encoding = encoding; #if PROFILE_ENCODING_CONVERTER if (sProfiledConverter == nil) { sProfiledConverter = self; sProfileTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(profileFire:) userInfo:nil repeats:YES]; } #endif } return self; } - (id) initWithFontPList:(NSDictionary *)fontPList { return [self initWithEncoding:EncodingFromString([fontPList oo_stringForKey:@"encoding"]) substitutions:[fontPList oo_dictionaryForKey:@"substitutions"]]; } - (void) dealloc { [_cache release]; [_substitutions release]; #if PROFILE_ENCODING_CONVERTER sProfiledConverter = nil; [sProfileTimer invalidate]; sProfileTimer = nil; sCacheHits = 0; sCacheMisses = 0; #endif [super dealloc]; } - (NSString *) descriptionComponents { return [NSString stringWithFormat:@"encoding: %lu", _encoding]; } - (NSData *) convertString:(NSString *)string { NSData *data = nil; #if USE_COMPATIBILITY_MAPPING // Convert to Unicode Normalization Form KC (that is, minimize the use of combining modifiers while avoiding precomposed ligatures) string = [string precomposedStringWithCompatibilityMapping]; #endif if (string == nil) return [NSData data]; data = [_cache objectForKey:string]; if (data == nil) { data = [self performConversionForString:string]; if (data != nil) [_cache setObject:data forKey:string]; #if PROFILE_ENCODING_CONVERTER ++sCacheMisses; } else { ++sCacheHits; #endif } return data; } - (NSStringEncoding) encoding { return _encoding; } @end @implementation OOEncodingConverter (Private) - (NSData *) performConversionForString:(NSString *)string { NSString *subst = nil; NSEnumerator *substEnum = nil; NSMutableString *mutable = nil; mutable = [[string mutableCopy] autorelease]; if (mutable == nil) return nil; for (substEnum = [_substitutions keyEnumerator]; (subst = [substEnum nextObject]); ) { [mutable replaceOccurrencesOfString:subst withString:[_substitutions objectForKey:subst] options:0 range:NSMakeRange(0, [mutable length])]; } return [mutable dataUsingEncoding:_encoding allowLossyConversion:YES]; } #if PROFILE_ENCODING_CONVERTER /* Profiling observations: * The clock generates one new string per second. * The trade screens each use over 100 strings, so cache sizes below 150 are undesireable. * Cache hit ratio is extremely near 100% at most times. */ - (void) profileFire:(id)junk { float ratio = (float)sCacheHits / (float)(sCacheHits + sCacheMisses); OOLog(@"strings.encoding.profile", @"Cache hits: %u, misses: %u, ratio: %.2g", sCacheHits, sCacheMisses, ratio); sCacheHits = sCacheMisses = 0; } #endif @end #endif //OOENCODINGCONVERTER_EXCLUDE /* There are a variety of overlapping naming schemes for text encoding. We ignore them and use a fixed list: "windows-latin-1" NSWindowsCP1252StringEncoding "windows-latin-2" NSWindowsCP1250StringEncoding "windows-cyrillic" NSWindowsCP1251StringEncoding "windows-greek" NSWindowsCP1253StringEncoding "windows-turkish" NSWindowsCP1254StringEncoding */ #define kWindowsLatin1Str @"windows-latin-1" #define kWindowsLatin2Str @"windows-latin-2" #define kWindowsCyrillicStr @"windows-cyrillic" #define kWindowsGreekStr @"windows-greek" #define kWindowsTurkishStr @"windows-turkish" NSString *StringFromEncoding(NSStringEncoding encoding) { switch (encoding) { case NSWindowsCP1252StringEncoding: return kWindowsLatin1Str; case NSWindowsCP1250StringEncoding: return kWindowsLatin2Str; case NSWindowsCP1251StringEncoding: return kWindowsCyrillicStr; case NSWindowsCP1253StringEncoding: return kWindowsGreekStr; case NSWindowsCP1254StringEncoding: return kWindowsTurkishStr; default: return nil; } } NSStringEncoding EncodingFromString(NSString *name) { if ([name isEqualToString:kWindowsLatin1Str]) return NSWindowsCP1252StringEncoding; if ([name isEqualToString:kWindowsLatin2Str]) return NSWindowsCP1250StringEncoding; if ([name isEqualToString:kWindowsCyrillicStr]) return NSWindowsCP1251StringEncoding; if ([name isEqualToString:kWindowsGreekStr]) return NSWindowsCP1253StringEncoding; if ([name isEqualToString:kWindowsTurkishStr]) return NSWindowsCP1254StringEncoding; return (NSStringEncoding)NSNotFound; } oolite-1.82/src/Core/OOEntityFilterPredicate.h000066400000000000000000000071571256642440500213120ustar00rootroot00000000000000/* OOEntityFilterPredicate.h Filters used to select entities in various contexts. Callers are required to ensure that the "entity" argument is non-nil and the "parameter" argument is valid and relevant. To reduce header spaghetti, the EntityFilterPredicate type is declared in Universe.h, which is included just about everywhere anyway. This file just declares a set of widely-useful predicates. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "Universe.h" typedef struct { EntityFilterPredicate predicate; void *parameter; } ChainedEntityPredicateParameter; typedef struct { EntityFilterPredicate predicate1; void *parameter1; EntityFilterPredicate predicate2; void *parameter2; } BinaryOperationPredicateParameter; BOOL YESPredicate(Entity *entity, void *parameter); // Parameter: ignored. Always returns YES. BOOL NOPredicate(Entity *entity, void *parameter); // Parameter: ignored. Always returns NO. BOOL NOTPredicate(Entity *entity, void *parameter); // Parameter: ChainedEntityPredicateParameter. Reverses effect of chained predicate. BOOL ANDPredicate(Entity *entity, void *parameter); // Parameter: BinaryOperationPredicateParameter. Short-circuiting AND operator. BOOL ORPredicate(Entity *entity, void *parameter); // Parameter: BinaryOperationPredicateParameter. Short-circuiting OR operator. BOOL NORPredicate(Entity *entity, void *parameter); // Parameter: BinaryOperationPredicateParameter. Short-circuiting NOR operator. BOOL XORPredicate(Entity *entity, void *parameter); // Parameter: BinaryOperationPredicateParameter. XOR operator. BOOL NANDPredicate(Entity *entity, void *parameter); // Parameter: BinaryOperationPredicateParameter. NAND operator. BOOL HasScanClassPredicate(Entity *entity, void *parameter); // Parameter: NSNumber (int) BOOL HasClassPredicate(Entity *entity, void *parameter); // Parameter: Class BOOL IsShipPredicate(Entity *entity, void *parameter); // Parameter: ignored. Tests isShip and !isSubentity. BOOL IsStationPredicate(Entity *entity, void *parameter); // Parameter: ignored. Tests isStation. BOOL IsPlanetPredicate(Entity *entity, void *parameter); // Parameter: ignored. Tests isPlanet and planetType == STELLAR_TYPE_NORMAL_PLANET. BOOL IsSunPredicate(Entity *entity, void *parameter); // Parameter: ignored. Tests isSun. BOOL IsVisualEffectPredicate(Entity *entity, void *parameter); // Parameter: ignored. Tests isVisualEffect and !isSubentity. // These predicates assume their parameter is a ShipEntity. BOOL HasRolePredicate(Entity *ship, void *parameter); // Parameter: NSString BOOL HasPrimaryRolePredicate(Entity *ship, void *parameter); // Parameter: NSString BOOL HasRoleInSetPredicate(Entity *ship, void *parameter); // Parameter: NSSet BOOL HasPrimaryRoleInSetPredicate(Entity *ship, void *parameter); // Parameter: NSSet BOOL IsHostileAgainstTargetPredicate(Entity *ship, void *parameter); // Parameter: ShipEntity oolite-1.82/src/Core/OOEntityFilterPredicate.m000066400000000000000000000101561256642440500213100ustar00rootroot00000000000000/* OOEntityFilterPredicate.h Filters used to select entities in various contexts. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOEntityFilterPredicate.h" #import "Entity.h" #import "ShipEntity.h" #import "OOPlanetEntity.h" #import "OORoleSet.h" BOOL YESPredicate(Entity *entity, void *parameter) { return YES; } BOOL NOPredicate(Entity *entity, void *parameter) { return NO; } BOOL NOTPredicate(Entity *entity, void *parameter) { ChainedEntityPredicateParameter *param = parameter; if (param == NULL || param->predicate == NULL) return NO; return !param->predicate(entity, param->parameter); } BOOL ANDPredicate(Entity *entity, void *parameter) { BinaryOperationPredicateParameter *param = parameter; if (!param->predicate1(entity, param->parameter1)) return NO; if (!param->predicate2(entity, param->parameter2)) return NO; return YES; } BOOL ORPredicate(Entity *entity, void *parameter) { BinaryOperationPredicateParameter *param = parameter; if (param->predicate1(entity, param->parameter1)) return YES; if (param->predicate2(entity, param->parameter2)) return YES; return NO; } BOOL NORPredicate(Entity *entity, void *parameter) { BinaryOperationPredicateParameter *param = parameter; if (param->predicate1(entity, param->parameter1)) return NO; if (param->predicate2(entity, param->parameter2)) return NO; return YES; } BOOL XORPredicate(Entity *entity, void *parameter) { BinaryOperationPredicateParameter *param = parameter; BOOL A, B; A = param->predicate1(entity, param->parameter1); B = param->predicate2(entity, param->parameter2); return (A || B) && !(A && B); } BOOL NANDPredicate(Entity *entity, void *parameter) { BinaryOperationPredicateParameter *param = parameter; BOOL A, B; A = param->predicate1(entity, param->parameter1); B = param->predicate2(entity, param->parameter2); return !(A && B); } BOOL HasScanClassPredicate(Entity *entity, void *parameter) { return [(id)parameter intValue] == [entity scanClass]; } BOOL HasClassPredicate(Entity *entity, void *parameter) { return [entity isKindOfClass:(Class)parameter]; } BOOL IsShipPredicate(Entity *entity, void *parameter) { return [entity isShip] && ![entity isSubEntity]; } BOOL IsStationPredicate(Entity *entity, void *parameter) { return [entity isStation]; } BOOL IsPlanetPredicate(Entity *entity, void *parameter) { if (![entity isPlanet]) return NO; OOStellarBodyType type = [(OOPlanetEntity *)entity planetType]; return (type == STELLAR_TYPE_NORMAL_PLANET || type == STELLAR_TYPE_MOON); } BOOL IsSunPredicate(Entity *entity, void *parameter) { return [entity isSun]; } BOOL IsVisualEffectPredicate(Entity *entity, void *parameter) { return [entity isVisualEffect] && ![entity isSubEntity]; } BOOL HasRolePredicate(Entity *ship, void *parameter) { return [(ShipEntity *)ship hasRole:(NSString *)parameter]; } BOOL HasPrimaryRolePredicate(Entity *ship, void *parameter) { return [(ShipEntity *)ship hasPrimaryRole:(NSString *)parameter]; } BOOL HasRoleInSetPredicate(Entity *ship, void *parameter) { return [[(ShipEntity *)ship roleSet] intersectsSet:(NSSet *)parameter]; } BOOL HasPrimaryRoleInSetPredicate(Entity *ship, void *parameter) { return [(NSSet *)parameter containsObject:[(ShipEntity *)ship primaryRole]]; } BOOL IsHostileAgainstTargetPredicate(Entity *ship, void *parameter) { return [(ShipEntity *)ship hasHostileTarget] && [(ShipEntity *)ship primaryTarget] == (ShipEntity *)parameter; } oolite-1.82/src/Core/OOEnvironmentCubeMap.h000066400000000000000000000027341256642440500206040ustar00rootroot00000000000000/* OOEnvironmentCubeMap.h Oolite Manage rendering of environment to a cube map. Copyright (C) 2010-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOOpenGLExtensionManager.h" #if OO_USE_FBO && OO_TEXTURE_CUBE_MAP #import "OOTexture.h" @interface OOEnvironmentCubeMap: OOTexture { @private GLuint _size; GLuint _fbos[6]; GLuint _depthBuffers[6]; GLuint _textureName; BOOL _planets; } - (id) initWithSideLength:(GLuint)size; - (void) render; @end #endif oolite-1.82/src/Core/OOEnvironmentCubeMap.m000066400000000000000000000162631256642440500206130ustar00rootroot00000000000000/* OOEnvironmentCubeMap.m Copyright (C) 2010-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOEnvironmentCubeMap.h" #import "OOTexture.h" #import "OOTextureInternal.h" #import "MyOpenGLView.h" #import "OOPixMap.h" #import "OOMacroOpenGL.h" #import "Universe.h" #import "PlayerEntity.h" #import "SkyEntity.h" #import "OOSunEntity.h" #import "OOPlanetEntity.h" #import "OODrawable.h" #import "OOEntityFilterPredicate.h" #if OO_USE_FBO && OO_TEXTURE_CUBE_MAP @interface OOEnvironmentCubeMap (Private) - (void) setUp; - (void) renderOnePassWithSky:(OODrawable *)sky sun:(OOSunEntity *)sun planets:(NSArray *)planets; @end @implementation OOEnvironmentCubeMap - (id) initWithSideLength:(GLuint)size { if (![[OOOpenGLExtensionManager sharedManager] fboSupported] || !OOCubeMapsAvailable()) { [self release]; return nil; } if ((self = [super init])) { _size = size; } return self; } - (void) dealloc { [self forceRebind]; [super dealloc]; } - (void) render { if (_textureName == 0) [self setUp]; OO_ENTER_OPENGL(); // Save stuff. OOGL(glPushAttrib(GL_VIEWPORT_BIT | GL_ENABLE_BIT)); OOGLPushModelView(); OOGLPushProjection(); OOGL(glViewport(0, 0, _size, _size)); OOGLScaleProjection(make_vector(-1.0, 1.0, 1.0)); // flip left and right /* TODO: once confirmed working (and rendering everything in the right orientation), replace with glLoadMatrix and the following: 1 0 0 0 0 1 0 0 0 0 -1 -2 0 0 -1 0 ...and appropriate rotations thereof. */ OOGLResetProjection(); OOGLPerspective(90.0, 1.0, 1.0, MAX_CLEAR_DEPTH); OODrawable *sky = [[UNIVERSE nearestEntityMatchingPredicate:HasClassPredicate parameter:[SkyEntity class] relativeToEntity:nil] drawable]; OOSunEntity *sun = [UNIVERSE sun]; NSArray *planets = [UNIVERSE planets]; unsigned i; Vector centers[6] = { { 1, 0, 0 }, { -1, 0, 0 }, { 0, 1, 0 }, { 0, -1, 0 }, { 0, 0, 1 }, { 0, 0, -1 } }; Vector ups[6] = { { 0, -1, 0 }, { 0, -1, 0 }, { 0, 0, 1 }, { 0, 0, -1 }, { 0, -1, 0 }, { 0, -1, 0 } }; for (i = 0; i < 6; i++) { OOGLPushProjection(); Vector center = centers[i]; Vector up = ups[i]; OOGLLookAt(kZeroVector, center, up); OOGL(glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _fbos[i])); [self renderOnePassWithSky:sky sun:sun planets:planets]; OOGLPopProjection(); } OOGLPopProjection(); OOGLPopModelView(); OOGL(glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)); OOGL(glPopAttrib()); } - (void) renderOnePassWithSky:(OODrawable *)sky sun:(OOSunEntity *)sun planets:(NSArray *)planets { OO_ENTER_OPENGL(); OOGL(glClearColor(0.0f, 0.0f, 0.0f, 1.0f)); OOGL(glClearDepth(MAX_CLEAR_DEPTH)); OOGL(glClear(_planets ? GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT : GL_COLOR_BUFFER_BIT)); OOGL(glDepthMask(GL_FALSE)); [sky renderOpaqueParts]; OOGL(glDepthMask(GL_TRUE)); OOGLPushModelView(); OOGLResetModelView(); OOGLTranslateModelView(vector_flip([PLAYER position])); NSEnumerator *planetEnum = nil; OOPlanetEntity *planet = nil; for (planetEnum = [planets objectEnumerator]; (planet = [planetEnum nextObject]); ) { OOGLPushModelView(); OOGLTranslateModelView(planet position]); #if NEW_PLANETS [[planet drawable] renderOpaqueParts]; #else [planet drawUnconditionally]; #endif OOGLPopModelView(); } OOGLPushModelView(); OOGLTranslateModelView([sun position]); [sun drawUnconditionally]; OOGLPopModelView(); OOGLPopModelView(); } - (void) setUp { OO_ENTER_OPENGL(); if (_textureName != 0) return; _planets = [UNIVERSE reducedDetail]; OOGL(glGenTextures(1, &_textureName)); OOGL(glBindTexture(GL_TEXTURE_CUBE_MAP, _textureName)); OOGL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); OOGL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); OOGL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE)); OOGL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); OOGL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); OOGL(glGenFramebuffersEXT(6, _fbos)); OOGL(glGenRenderbuffersEXT(6, _depthBuffers)); unsigned i; for (i = 0; i < 6; i++) { GLenum textarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + i; OOGL(glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _fbos[i])); OOGL(glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, _depthBuffers[i])); OOGL(glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, _size, _size)); OOGL(glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, _depthBuffers[i])); OOGL(glTexImage2D(textarget, 0, GL_RGBA8, _size, _size, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL)); OOGL(glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, textarget, _textureName, 0)); } #ifndef NDEBUG GLenum status; OOGL(status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT)); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { OOLogERR(@"environmentCube.fbo.setup.failed", @"Failed to set up FBO for environment cube map - status is %u.", status); DESTROY(self); } OOCheckOpenGLErrors(@"after setting up environment cube map FBO"); #endif OOGL(glBindTexture(GL_TEXTURE_CUBE_MAP, 0)); OOGL(glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)); OOGL(glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0)); } // OOTexture stuff. - (void) apply { OO_ENTER_OPENGL(); OOGL(glBindTexture(GL_TEXTURE_CUBE_MAP, _textureName)); } - (NSSize) dimensions { return NSMakeSize(_size, _size); } - (void) forceRebind { OO_ENTER_OPENGL(); if (_textureName == 0) return; OOGL(glDeleteTextures(1, &_textureName)); _textureName = 0; OOGL(glDeleteFramebuffersEXT(6, _fbos)); OOGL(glDeleteRenderbuffersEXT(6, _depthBuffers)); } - (BOOL) isCubeMap { return YES; } - (struct OOPixMap) copyPixMapRepresentation { OOPixMap pixmap = OOAllocatePixMap(_size, _size * 6, 4, 0, 0); if (!OOIsValidPixMap(pixmap)) return kOONullPixMap; OO_ENTER_OPENGL(); unsigned i; for (i = 0; i < 6; i++) { OOGL(glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _fbos[i])); OOGL(glReadBuffer(GL_COLOR_ATTACHMENT0_EXT)); uint8_t *target = pixmap.pixels; target += pixmap.rowBytes * _size * i; OOGL(glReadPixels(0, 0, _size, _size, GL_RGBA, GL_UNSIGNED_BYTE, target)); } OOGL(glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)); return pixmap; } @end #endif oolite-1.82/src/Core/OOEquipmentType.h000066400000000000000000000112211256642440500176430ustar00rootroot00000000000000/* OOEquipmentType.h Class representing a type of ship equipment. Exposed to JavaScript as EquipmentInfo. Copyright (C) 2008-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" #import "OOTypes.h" #import "OOScript.h" #import "Universe.h" @interface OOEquipmentType: NSObject { @private OOTechLevelID _techLevel; OOCreditsQuantity _price; NSString *_name; NSString *_identifier; NSString *_description; unsigned _isAvailableToAll: 1, _requiresEmptyPylon: 1, _requiresMountedPylon: 1, _requiresClean: 1, _requiresNotClean: 1, _portableBetweenShips: 1, _requiresFreePassengerBerth: 1, _requiresFullFuel: 1, _requiresNonFullFuel: 1, _isMissileOrMine: 1, _isVisible: 1, _isAvailableToPlayer: 1, _isAvailableToNPCs: 1, _fastAffinityA: 1, _fastAffinityB: 1, _canCarryMultiple: 1; NSUInteger _installTime; NSUInteger _repairTime; GLfloat _damageProbability; OOCargoQuantity _requiredCargoSpace; NSSet *_requiresEquipment; NSSet *_requiresAnyEquipment; NSSet *_incompatibleEquipment; NSArray *_conditions; NSArray *_provides; NSDictionary *_scriptInfo; NSDictionary *_weaponInfo; NSString *_script; NSString *_condition_script; struct JSObject *_jsSelf; } + (void) loadEquipment; // Load equipment data; called on loading and when changing to/from strict mode. + (void) addEquipmentWithInfo:(NSArray *)itemInfo; // Used to generate equipment from missile_role entries. + (NSString *) getMissileRegistryRoleForShip:(NSString *)shipKey; + (void) setMissileRegistryRole:(NSString *)roles forShip:(NSString *)shipKey; + (NSArray *) allEquipmentTypes; + (NSEnumerator *) equipmentEnumerator; + (NSEnumerator *) reverseEquipmentEnumerator; + (OOEquipmentType *) equipmentTypeWithIdentifier:(NSString *)identifier; - (NSString *) identifier; - (NSString *) damagedIdentifier; - (NSString *) name; // localized - (NSString *) descriptiveText; // localized - (OOTechLevelID) techLevel; - (OOCreditsQuantity) price; // Tenths of credits - (BOOL) isAvailableToAll; - (BOOL) requiresEmptyPylon; - (BOOL) requiresMountedPylon; - (BOOL) requiresCleanLegalRecord; - (BOOL) requiresNonCleanLegalRecord; - (BOOL) requiresFreePassengerBerth; - (BOOL) requiresFullFuel; - (BOOL) requiresNonFullFuel; - (BOOL) isPrimaryWeapon; - (BOOL) isMissileOrMine; - (BOOL) isPortableBetweenShips; - (BOOL) canCarryMultiple; - (GLfloat) damageProbability; - (BOOL) canBeDamaged; - (BOOL) isVisible; // Visible in UI? - (BOOL) isAvailableToPlayer; - (BOOL) isAvailableToNPCs; - (OOCargoQuantity) requiredCargoSpace; - (NSSet *) requiresEquipment; // Set of equipment identifiers; all items required - (NSSet *) requiresAnyEquipment; // Set of equipment identifiers; any item required - (NSSet *) incompatibleEquipment; // Set of equipment identifiers; all items prohibited // FIXME: should have general mechanism to handle scripts or legacy conditions. - (NSArray *) conditions; - (NSString *) conditionScript; - (NSDictionary *) scriptInfo; - (NSString *) scriptName; - (BOOL) fastAffinityDefensive; - (BOOL) fastAffinityOffensive; - (NSUInteger) installTime; - (NSUInteger) repairTime; - (NSArray *) providesForScripting; - (BOOL) provides:(NSString *)key; // weapon properties - (BOOL) isTurretLaser; - (BOOL) isMiningLaser; - (GLfloat) weaponRange; - (GLfloat) weaponEnergyUse; - (GLfloat) weaponDamage; - (GLfloat) weaponRechargeRate; - (GLfloat) weaponShotTemperature; - (GLfloat) weaponThreatAssessment; - (OOColor *) weaponColor; @end @interface OOEquipmentType (Conveniences) - (OOTechLevelID) effectiveTechLevel; @end oolite-1.82/src/Core/OOEquipmentType.m000066400000000000000000000411001256642440500176470ustar00rootroot00000000000000/* OOEquipmentType.m Copyright (C) 2008-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOEquipmentType.h" #import "Universe.h" #import "OOCollectionExtractors.h" #import "OOLegacyScriptWhitelist.h" #import "OOCacheManager.h" #import "OODebugStandards.h" static NSArray *sEquipmentTypes = nil; static NSDictionary *sEquipmentTypesByIdentifier = nil; static NSDictionary *sMissilesRegistry = nil; @interface OOEquipmentType (Private) - (id) initWithInfo:(NSArray *)info; @end @implementation OOEquipmentType + (void) loadEquipment { NSArray *equipmentData = nil; NSMutableArray *equipmentTypes = nil; NSMutableDictionary *equipmentTypesByIdentifier = nil; NSArray *itemInfo = nil; OOEquipmentType *item = nil; NSEnumerator *itemEnum = nil; NSMutableArray *conditionScripts = nil; equipmentData = [UNIVERSE equipmentData]; [sEquipmentTypes release]; sEquipmentTypes = nil; equipmentTypes = [NSMutableArray arrayWithCapacity:[equipmentData count]]; conditionScripts = [NSMutableArray arrayWithCapacity:[equipmentData count]]; DESTROY(sEquipmentTypesByIdentifier); equipmentTypesByIdentifier = [NSMutableDictionary dictionaryWithCapacity:[equipmentData count]]; for (itemEnum = [equipmentData objectEnumerator]; (itemInfo = [itemEnum nextObject]); ) { item = [[[OOEquipmentType alloc] initWithInfo:itemInfo] autorelease]; if (item != nil) { [equipmentTypes addObject:item]; [equipmentTypesByIdentifier setObject:item forKey:[item identifier]]; } NSString* condition_script = [item conditionScript]; if (condition_script != nil) { if (![conditionScripts containsObject:condition_script]) { [conditionScripts addObject:condition_script]; } } } [[OOCacheManager sharedCache] setObject:conditionScripts forKey:@"equipment conditions" inCache:@"condition scripts"]; sEquipmentTypes = [equipmentTypes copy]; sEquipmentTypesByIdentifier = [[NSDictionary alloc] initWithDictionary:equipmentTypesByIdentifier]; } + (void) addEquipmentWithInfo:(NSArray *)itemInfo { NSMutableArray *equipmentTypes = [NSMutableArray arrayWithArray:sEquipmentTypes]; NSMutableDictionary *equipmentTypesByIdentifier = [[NSMutableDictionary alloc] initWithDictionary:sEquipmentTypesByIdentifier]; OOEquipmentType *item = [[[OOEquipmentType alloc] initWithInfo:itemInfo] autorelease]; if (item != nil) { [equipmentTypes addObject:item]; [equipmentTypesByIdentifier setObject:item forKey:[item identifier]]; [sEquipmentTypes release]; sEquipmentTypes = nil; DESTROY(sEquipmentTypesByIdentifier); sEquipmentTypes = [equipmentTypes copy]; sEquipmentTypesByIdentifier = [[NSDictionary alloc] initWithDictionary:equipmentTypesByIdentifier]; } DESTROY(equipmentTypesByIdentifier); } + (NSString *) getMissileRegistryRoleForShip:(NSString *)shipKey { return [sMissilesRegistry oo_stringForKey:shipKey]; } + (void) setMissileRegistryRole:(NSString *)role forShip:(NSString *)shipKey { NSMutableDictionary *missilesRegistry = [[NSMutableDictionary alloc] initWithDictionary:sMissilesRegistry]; if (role != nil && shipKey != nil && ![shipKey isEqualToString:@""]) { [missilesRegistry setValue:role forKey:shipKey]; DESTROY(sMissilesRegistry); sMissilesRegistry = [[NSDictionary alloc] initWithDictionary:missilesRegistry]; } DESTROY(missilesRegistry); } + (NSArray *) allEquipmentTypes { return sEquipmentTypes; } + (NSEnumerator *) equipmentEnumerator { return [sEquipmentTypes objectEnumerator]; } + (NSEnumerator *) reverseEquipmentEnumerator { return [sEquipmentTypes reverseObjectEnumerator]; } + (OOEquipmentType *) equipmentTypeWithIdentifier:(NSString *)identifier { return [sEquipmentTypesByIdentifier objectForKey:identifier]; } - (id) initWithInfo:(NSArray *)info { BOOL OK = YES; NSDictionary *extra = nil; NSArray *conditions = nil; NSString *condition_script = nil; self = [super init]; if (self == nil) OK = NO; if (OK && [info count] <= EQUIPMENT_LONG_DESC_INDEX) OK = NO; if (OK) { // Read required attributes _techLevel = [info oo_unsignedIntAtIndex:EQUIPMENT_TECH_LEVEL_INDEX]; _price = [info oo_unsignedIntAtIndex:EQUIPMENT_PRICE_INDEX]; _name = [[info oo_stringAtIndex:EQUIPMENT_SHORT_DESC_INDEX] retain]; _identifier = [[info oo_stringAtIndex:EQUIPMENT_KEY_INDEX] retain]; _description = [[info oo_stringAtIndex:EQUIPMENT_LONG_DESC_INDEX] retain]; if (_name == nil || _identifier == nil || _description == nil) { OOLog(@"equipment.load", @"***** ERROR: Invalid equipment.plist entry - missing name, identifier or description (\"%@\", %@, \"%@\")", _name, _identifier, _description); OK = NO; } } if (OK) { // Implied attributes for backwards-compatibility if ([_identifier hasSuffix:@"_MISSILE"] || [_identifier hasSuffix:@"_MINE"]) { _isMissileOrMine = YES; _requiresEmptyPylon = YES; } else if ([_identifier isEqualToString:@"EQ_PASSENGER_BERTH_REMOVAL"]) { _requiresFreePassengerBerth = YES; } else if ([_identifier isEqualToString:@"EQ_FUEL"]) { _requiresNonFullFuel = YES; } _isVisible = YES; _isAvailableToPlayer = YES; _isAvailableToNPCs = YES; _damageProbability = 1.0; } if (OK && [info count] > EQUIPMENT_EXTRA_INFO_INDEX) { // Read extra info dictionary extra = [info oo_dictionaryAtIndex:EQUIPMENT_EXTRA_INFO_INDEX]; if (extra != nil) { _isAvailableToAll = [extra oo_boolForKey:@"available_to_all" defaultValue:_isAvailableToAll]; _isAvailableToPlayer = [extra oo_boolForKey:@"available_to_player" defaultValue:_isAvailableToPlayer]; _isAvailableToNPCs = [extra oo_boolForKey:@"available_to_NPCs" defaultValue:_isAvailableToNPCs]; _isMissileOrMine = [extra oo_boolForKey:@"is_external_store" defaultValue:_isMissileOrMine]; _requiresEmptyPylon = [extra oo_boolForKey:@"requires_empty_pylon" defaultValue:_requiresEmptyPylon]; _requiresMountedPylon = [extra oo_boolForKey:@"requires_mounted_pylon" defaultValue:_requiresMountedPylon]; _requiresClean = [extra oo_boolForKey:@"requires_clean" defaultValue:_requiresClean]; _requiresNotClean = [extra oo_boolForKey:@"requires_not_clean" defaultValue:_requiresNotClean]; _portableBetweenShips = [extra oo_boolForKey:@"portable_between_ships" defaultValue:_portableBetweenShips]; _requiresFreePassengerBerth = [extra oo_boolForKey:@"requires_free_passenger_berth" defaultValue:_requiresFreePassengerBerth]; _requiresFullFuel = [extra oo_boolForKey:@"requires_full_fuel" defaultValue:_requiresFullFuel]; _requiresNonFullFuel = [extra oo_boolForKey:@"requires_non_full_fuel" defaultValue:_requiresNonFullFuel]; _isVisible = [extra oo_boolForKey:@"visible" defaultValue:_isVisible]; _canCarryMultiple = [extra oo_boolForKey:@"can_carry_multiple" defaultValue:NO]; _requiredCargoSpace = [extra oo_unsignedIntForKey:@"requires_cargo_space" defaultValue:_requiredCargoSpace]; _installTime = [extra oo_unsignedIntForKey:@"installation_time" defaultValue:0]; _repairTime = [extra oo_unsignedIntForKey:@"repair_time" defaultValue:0]; _provides = [[extra oo_arrayForKey:@"provides" defaultValue:[NSArray array]] retain]; _weaponInfo = [[extra oo_dictionaryForKey:@"weapon_info" defaultValue:[NSDictionary dictionary]] retain]; _damageProbability = [extra oo_floatForKey:@"damage_probability" defaultValue:(_isMissileOrMine?0.0:1.0)]; id object = [extra objectForKey:@"requires_equipment"]; if ([object isKindOfClass:[NSString class]]) _requiresEquipment = [[NSSet setWithObject:object] retain]; else if ([object isKindOfClass:[NSArray class]]) _requiresEquipment = [[NSSet setWithArray:object] retain]; else if (object != nil) { OOLog(@"equipment.load", @"***** ERROR: %@ for equipment item %@ is not a string or an array.", @"requires_equipment", _identifier); } object = [extra objectForKey:@"requires_any_equipment"]; if ([object isKindOfClass:[NSString class]]) _requiresAnyEquipment = [[NSSet setWithObject:object] retain]; else if ([object isKindOfClass:[NSArray class]]) _requiresAnyEquipment = [[NSSet setWithArray:object] retain]; else if (object != nil) { OOLog(@"equipment.load", @"***** ERROR: %@ for equipment item %@ is not a string or an array.", @"requires_any_equipment", _identifier); } object = [extra objectForKey:@"incompatible_with_equipment"]; if ([object isKindOfClass:[NSString class]]) _incompatibleEquipment = [[NSSet setWithObject:object] retain]; else if ([object isKindOfClass:[NSArray class]]) _incompatibleEquipment = [[NSSet setWithArray:object] retain]; else if (object != nil) { OOLog(@"equipment.load", @"***** ERROR: %@ for equipment item %@ is not a string or an array.", @"incompatible_with_equipment", _identifier); } object = [extra objectForKey:@"conditions"]; if ([object isKindOfClass:[NSString class]]) conditions = [NSArray arrayWithObject:object]; else if ([object isKindOfClass:[NSArray class]]) conditions = object; else if (object != nil) { OOLog(@"equipment.load", @"***** ERROR: %@ for equipment item %@ is not a string or an array.", @"conditions", _identifier); } if (conditions != nil) { OOStandardsDeprecated([NSString stringWithFormat:@"The conditions key is deprecated for equipment %@",_name]); if (!OOEnforceStandards()) { _conditions = OOSanitizeLegacyScriptConditions(conditions, [NSString stringWithFormat:@"", _name]); [_conditions retain]; } } object = [extra objectForKey:@"condition_script"]; if ([object isKindOfClass:[NSString class]]) { condition_script = object; } else if (object != nil) { OOLog(@"equipment.load", @"***** ERROR: %@ for equipment item %@ is not a string.", @"condition_script", _identifier); } if (condition_script != nil) { _condition_script = [condition_script retain]; } /* Condition scripts are shared: all equipment/ships using the * same condition script use one shared instance. Equipment * scripts and ship scripts are not shared and get one instance * per item. */ _scriptInfo = [extra oo_dictionaryForKey:@"script_info"]; [_scriptInfo retain]; _script = [extra oo_stringForKey:@"script"]; if (_script != nil && ![OOScript jsScriptFromFileNamed:_script properties:nil]) _script = nil; [_script retain]; if (_script != nil) { _fastAffinityA = !![extra oo_boolForKey:@"fast_affinity_defensive"]; _fastAffinityB = !![extra oo_boolForKey:@"fast_affinity_offensive"]; } } } if (!OK) { [self release]; self = nil; } return self; } - (void) dealloc { DESTROY(_name); DESTROY(_identifier); DESTROY(_description); DESTROY(_requiresEquipment); DESTROY(_requiresAnyEquipment); DESTROY(_incompatibleEquipment); DESTROY(_conditions); DESTROY(_condition_script); DESTROY(_provides); DESTROY(_weaponInfo); DESTROY(_scriptInfo); DESTROY(_script); [super dealloc]; } - (id) copyWithZone:(NSZone *)zone { // OOEquipmentTypes are immutable. return [self retain]; } - (NSString *) descriptionComponents { return [NSString stringWithFormat:@"%@ \"%@\"", _identifier, _name]; } - (NSString *) identifier { return _identifier; } - (NSString *) damagedIdentifier { return [_identifier stringByAppendingString:@"_DAMAGED"]; } - (NSString *) name { return _name; } - (NSString *) descriptiveText { return _description; } - (OOTechLevelID) techLevel { return _techLevel; } - (OOCreditsQuantity) price { return _price; } - (BOOL) isAvailableToAll { return _isAvailableToAll; } - (BOOL) requiresEmptyPylon { return _requiresEmptyPylon; } - (BOOL) requiresMountedPylon { return _requiresMountedPylon; } - (BOOL) requiresCleanLegalRecord { return _requiresClean; } - (BOOL) requiresNonCleanLegalRecord { return _requiresNotClean; } - (BOOL) requiresFreePassengerBerth { return _requiresFreePassengerBerth; } - (BOOL) requiresFullFuel { return _requiresFullFuel; } - (BOOL) requiresNonFullFuel { return _requiresNonFullFuel; } - (BOOL) isPrimaryWeapon { return [[self identifier] hasPrefix:@"EQ_WEAPON"]; } - (BOOL) isMissileOrMine { return _isMissileOrMine; } - (BOOL) isPortableBetweenShips { return _portableBetweenShips; } - (BOOL) canCarryMultiple { if ([self isMissileOrMine]) return YES; // technically multiple can be fitted, but not to the same mount. if ([self isPrimaryWeapon]) return NO; // hard-coded as special items if ([_identifier isEqualToString:@"EQ_PASSENGER_BERTH"] || [_identifier isEqualToString:@"EQ_TRUMBLE"]) { return YES; } return _canCarryMultiple; } - (GLfloat) damageProbability { if ([self isMissileOrMine]) return 0.0; return _damageProbability; } - (BOOL) canBeDamaged { if ([self isMissileOrMine]) return NO; if ([self damageProbability] > 0.0) { return YES; } return NO; } - (BOOL) isVisible { return _isVisible; } - (BOOL) isAvailableToPlayer { return _isAvailableToPlayer; } - (BOOL) isAvailableToNPCs { return _isAvailableToNPCs; } - (OOCargoQuantity) requiredCargoSpace { return _requiredCargoSpace; } - (NSSet *) requiresEquipment { return _requiresEquipment; } - (NSSet *) requiresAnyEquipment { return _requiresAnyEquipment; } - (NSSet *) incompatibleEquipment { return _incompatibleEquipment; } - (NSArray *) conditions { return _conditions; } - (NSString *) conditionScript { return _condition_script; } - (NSDictionary *) scriptInfo { return _scriptInfo; } - (NSString *) scriptName { return _script; } - (BOOL) fastAffinityDefensive { return _fastAffinityA; } - (BOOL) fastAffinityOffensive { return _fastAffinityB; } - (NSUInteger) installTime { return _installTime; } - (NSUInteger) repairTime { if (_repairTime > 0) { return _repairTime; } else { return _installTime / 2; } } - (NSArray *) providesForScripting { return [[_provides copy] autorelease]; } - (BOOL) provides:(NSString *)key { return [_provides containsObject:key]; } // weapon properties follow - (BOOL) isTurretLaser { return [_weaponInfo oo_boolForKey:@"is_turret_laser" defaultValue:NO]; } - (BOOL) isMiningLaser { return [_weaponInfo oo_boolForKey:@"is_mining_laser" defaultValue:NO]; } - (GLfloat) weaponRange { return [_weaponInfo oo_floatForKey:@"range" defaultValue:12500.0]; } - (GLfloat) weaponEnergyUse { return [_weaponInfo oo_floatForKey:@"energy" defaultValue:0.8]; } - (GLfloat) weaponDamage { return [_weaponInfo oo_floatForKey:@"damage" defaultValue:15.0]; } - (GLfloat) weaponRechargeRate { return [_weaponInfo oo_floatForKey:@"recharge_rate" defaultValue:0.5]; } - (GLfloat) weaponShotTemperature { return [_weaponInfo oo_floatForKey:@"shot_temperature" defaultValue:7.0]; } - (GLfloat) weaponThreatAssessment { return [_weaponInfo oo_floatForKey:@"threat_assessment" defaultValue:1.0]; } - (OOColor *) weaponColor { return [OOColor brightColorWithDescription:[_weaponInfo objectForKey:@"color"]]; } /* This method exists purely to suppress Clang static analyzer warnings that this ivar is unused (but may be used by categories, which it is). FIXME: there must be a feature macro we can use to avoid actually building this into the app, but I can't find it in docs. */ - (BOOL) suppressClangStuff { return !_jsSelf; } @end #import "PlayerEntityLegacyScriptEngine.h" @implementation OOEquipmentType (Conveniences) - (OOTechLevelID) effectiveTechLevel { OOTechLevelID tl; id missionVar = nil; tl = [self techLevel]; if (tl == kOOVariableTechLevel) { OOStandardsDeprecated([NSString stringWithFormat:@"TL99 is deprecated for %@",[self identifier]]); if (!OOEnforceStandards()) { missionVar = [PLAYER missionVariableForKey:[@"mission_TL_FOR_" stringByAppendingString:[self identifier]]]; tl = OOUIntegerFromObject(missionVar, tl); } } return tl; } @end oolite-1.82/src/Core/OOExcludeObjectEnumerator.h000066400000000000000000000022261256642440500216210ustar00rootroot00000000000000/* OOExcludeObjectEnumerator.h Simple filter to exclude one object from an NSEnumerator. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" @interface OOExcludeObjectEnumerator: NSEnumerator { @private NSEnumerator *_enumerator; id _excludeObject; } + (id) enumeratorWithEnumerator:(NSEnumerator *)enumerator excludingObject:(id)object; @end @interface NSEnumerator (OOExcludingObject) - (id) ooExcludingObject:(id)object; @end oolite-1.82/src/Core/OOExcludeObjectEnumerator.m000066400000000000000000000034001256642440500216210ustar00rootroot00000000000000/* OOExcludeObjectEnumerator.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOExcludeObjectEnumerator.h" @implementation OOExcludeObjectEnumerator - (id) initWithEnumerator:(NSEnumerator *)enumerator excludingObject:(id)object { if ((self = [super init])) { _enumerator = [enumerator retain]; _excludeObject = [object retain]; } return self; } - (void) dealloc { [_enumerator release]; [_excludeObject release]; [super dealloc]; } + (id) enumeratorWithEnumerator:(NSEnumerator *)enumerator excludingObject:(id)object { if (object == nil) return enumerator; if (enumerator == nil) return nil; return [[[self alloc] initWithEnumerator:enumerator excludingObject:object] autorelease]; } - (id) nextObject { id result = nil; do { result = [_enumerator nextObject]; } while (result == _excludeObject && result != nil); return result; } @end @implementation NSEnumerator (OOExcludingObject) - (id) ooExcludingObject:(id)object { return [OOExcludeObjectEnumerator enumeratorWithEnumerator:self excludingObject:object]; } @end oolite-1.82/src/Core/OOFastArithmetic.h000066400000000000000000000052641256642440500177530ustar00rootroot00000000000000/* OOFastArithmetic.h Mathematical framework for Oolite. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef INCLUDED_OOMATHS_h #error Do not include OOFastArithmetic.h directly; include OOMaths.h. #else /* Clamp to range. */ OOINLINE float OOClamp_0_1_f(float value) INLINE_CONST_FUNC; OOINLINE float OOClamp_n1_1_f(float value) INLINE_CONST_FUNC; OOINLINE double OOClamp_0_1_d(double value) INLINE_CONST_FUNC; OOINLINE float OOClamp_0_max_f(float value, float max) INLINE_CONST_FUNC; OOINLINE double OOClamp_0_max_d(double value, double max) INLINE_CONST_FUNC; /* Linear interpolation. */ OOINLINE float OOLerp(float v0, float v1, float fraction) INLINE_CONST_FUNC; /* Round integer up to nearest power of 2. NOTE: these return 0 if the high bit of value is set. */ OOINLINE INLINE_CONST_FUNC uint32_t OORoundUpToPowerOf2_32(uint32_t value) { return 0x80000000U >> (__builtin_clz(value - 1) - 1); } OOINLINE INLINE_CONST_FUNC uint64_t OORoundUpToPowerOf2_64(uint64_t value) { return 0x8000000000000000ULL >> (__builtin_clzll(value - 1) - 1); } #if __OBJC__ #if OOLITE_64_BIT OOINLINE INLINE_CONST_FUNC NSUInteger OORoundUpToPowerOf2_NS(NSUInteger value) { return OORoundUpToPowerOf2_64(value); } #else OOINLINE INLINE_CONST_FUNC NSUInteger OORoundUpToPowerOf2_NS(NSUInteger value) { return OORoundUpToPowerOf2_32(value); } #endif #endif OOINLINE float OOClamp_0_1_f(float value) { return fmax(0.0f, fmin(value, 1.0f)); } OOINLINE float OOClamp_n1_1_f(float value) { return fmax(-1.0f, fmin(value, 1.0f)); } OOINLINE double OOClamp_0_1_d(double value) { return fmax(0.0f, fmin(value, 1.0f)); } OOINLINE float OOClamp_0_max_f(float value, float max) { return fmax(0.0f, fmin(value, max)); } OOINLINE double OOClamp_0_max_d(double value, double max) { return fmax(0.0, fmin(value, max)); } OOINLINE float OOLerp(float v0, float v1, float fraction) { // Linear interpolation - equivalent to v0 * (1.0f - fraction) + v1 * fraction. return v0 + fraction * (v1 - v0); } #endif /* INCLUDED_OOMATHS_h */ oolite-1.82/src/Core/OOFilteringEnumerator.h000066400000000000000000000070241256642440500210250ustar00rootroot00000000000000/* OOFilteringEnumerator.h By Jens Ayton NSEnumerator which takes an existing enumerator and filters out the objects that return NO from a given method. The method may take 0 or 1 arguments Example of use: NSArray *cats = [self cats]; NSEnumerator *happyCatEnum = [[cats objectEnumerator] filteredWithSelector:@selector(isHappy)]; id happyCat = nil; while ((happyCat = [happyCatEnum nextObject])) { ... } Filters can be trivially chained. For instance, to get happy red cats, use: NSEnumeratore *happyRedCatEnum = [[[cats objectEnumerator] filteredWithSelector:@selector(isHappy)] filteredWithSelector:@selector(hasColor:) andArgument:[NSColor redColor]]; Objects that do not respond to the filter selector are treated as if they had returned NO. Bonus feature: adds NSArray-like (but non-exception-throwing) makeObjectsPerformSelector: to all enumerators. Copyright (C) 2008-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" @interface OOFilteringEnumerator: NSEnumerator { @private NSEnumerator *_underlyingEnum; SEL _selector; id _argument; BOOL _takesArgument; } + (id) filterEnumerator:(NSEnumerator *)enumerator withSelector:(SEL)selector; + (id) filterEnumerator:(NSEnumerator *)enumerator withSelector:(SEL)selector andArgument:(id)argument; - (id) initWithUnderlyingEnumerator:(NSEnumerator *)enumerator withSelector:(SEL)selector takingArgument:(BOOL)takesArgument argumentValue:(id)argument; @end @interface NSEnumerator (OOFilteringEnumerator) - (id) filteredWithSelector:(SEL)selector; - (id) filteredWithSelector:(SEL)selector andArgument:(id)argument; @end @interface NSArray (OOFilteringEnumerator) - (id) objectEnumeratorFilteredWithSelector:(SEL)selector; - (id) objectEnumeratorFilteredWithSelector:(SEL)selector andArgument:(id)argument; @end @interface NSSet (OOFilteringEnumerator) - (id) objectEnumeratorFilteredWithSelector:(SEL)selector; - (id) objectEnumeratorFilteredWithSelector:(SEL)selector andArgument:(id)argument; @end @interface NSDictionary (OOFilteringEnumerator) - (id) objectEnumeratorFilteredWithSelector:(SEL)selector; - (id) objectEnumeratorFilteredWithSelector:(SEL)selector andArgument:(id)argument; - (id) keyEnumeratorFilteredWithSelector:(SEL)selector; - (id) keyEnumeratorFilteredWithSelector:(SEL)selector andArgument:(id)argument; @end @interface NSEnumerator (OOMakeObjectsPerformSelector) - (void)makeObjectsPerformSelector:(SEL)selector; - (void)makeObjectsPerformSelector:(SEL)selector withObject:(id)argument; @end oolite-1.82/src/Core/OOFilteringEnumerator.m000066400000000000000000000134301256642440500210300ustar00rootroot00000000000000/* OOFilteringEnumerator.m By Jens Ayton Copyright (C) 2008-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOFilteringEnumerator.h" typedef BOOL (*BoolReturnMsgSend)(id, SEL); typedef BOOL (*BoolReturnWithParamMsgSend)(id, SEL, id); @implementation OOFilteringEnumerator + (id) filterEnumerator:(NSEnumerator *)enumerator withSelector:(SEL)selector { if (selector == NULL) return [[enumerator retain] autorelease]; return [[[self alloc] initWithUnderlyingEnumerator:enumerator withSelector:selector takingArgument:NO argumentValue:nil] autorelease]; } + (id) filterEnumerator:(NSEnumerator *)enumerator withSelector:(SEL)selector andArgument:(id)argument { if (selector == NULL) return [[enumerator retain] autorelease]; return [[[self alloc] initWithUnderlyingEnumerator:enumerator withSelector:selector takingArgument:YES argumentValue:argument] autorelease]; } - (id) initWithUnderlyingEnumerator:(NSEnumerator *)enumerator withSelector:(SEL)selector takingArgument:(BOOL)takesArgument argumentValue:(id)argument { self = [super init]; if (self != nil) { _underlyingEnum = [enumerator retain]; _selector = selector; _takesArgument = takesArgument; if (_takesArgument) { _argument = [argument retain]; } } return self; } - (void) dealloc { [_underlyingEnum release]; [_argument release]; [super dealloc]; } - (NSString *) descriptionComponents { NSString *subDesc = NSStringFromSelector(_selector); if (_takesArgument) { subDesc = [subDesc stringByAppendingString:[_argument shortDescription]]; } return [NSString stringWithFormat:@"%@ matching %@", [_underlyingEnum shortDescription], subDesc]; } - (NSString *) shortDescriptionComponents { return NSStringFromSelector(_selector); } - (id) nextObject { for (;;) { // Get next object id obj = [_underlyingEnum nextObject]; BOOL filter; if (obj == nil) { // End of enumeration if (_underlyingEnum != nil) { [_underlyingEnum release]; _underlyingEnum = nil; [_argument release]; _argument = nil; } return nil; } // Check against filter IMP predicate = [obj methodForSelector:_selector]; if (predicate != NULL) { if (!_takesArgument) { filter = ((BoolReturnMsgSend)predicate)(obj, _selector); } else { filter = ((BoolReturnWithParamMsgSend)predicate)(obj, _selector, _argument); } } else { // Unsupported method filter = NO; } // If object passed, return it. if (filter) return obj; } } @end @implementation NSEnumerator (OOFilteringEnumerator) - (id) filteredWithSelector:(SEL)selector { return [OOFilteringEnumerator filterEnumerator:self withSelector:selector]; } - (id) filteredWithSelector:(SEL)selector andArgument:(id)argument { return [OOFilteringEnumerator filterEnumerator:self withSelector:selector andArgument:argument]; } @end @implementation NSArray (OOFilteringEnumerator) - (id) objectEnumeratorFilteredWithSelector:(SEL)selector { return [[self objectEnumerator] filteredWithSelector:selector]; } - (id) objectEnumeratorFilteredWithSelector:(SEL)selector andArgument:(id)argument { return [[self objectEnumerator] filteredWithSelector:selector andArgument:argument]; } @end @implementation NSSet (OOFilteringEnumerator) - (id) objectEnumeratorFilteredWithSelector:(SEL)selector { return [[self objectEnumerator] filteredWithSelector:selector]; } - (id) objectEnumeratorFilteredWithSelector:(SEL)selector andArgument:(id)argument { return [[self objectEnumerator] filteredWithSelector:selector andArgument:argument]; } @end @implementation NSDictionary (OOFilteringEnumerator) - (id) objectEnumeratorFilteredWithSelector:(SEL)selector { return [[self objectEnumerator] filteredWithSelector:selector]; } - (id) objectEnumeratorFilteredWithSelector:(SEL)selector andArgument:(id)argument { return [[self objectEnumerator] filteredWithSelector:selector andArgument:argument]; } - (id) keyEnumeratorFilteredWithSelector:(SEL)selector { return [[self keyEnumerator] filteredWithSelector:selector]; } - (id) keyEnumeratorFilteredWithSelector:(SEL)selector andArgument:(id)argument { return [[self keyEnumerator] filteredWithSelector:selector andArgument:argument]; } @end @implementation NSEnumerator (OOMakeObjectsPerformSelector) - (void)makeObjectsPerformSelector:(SEL)selector { id object = nil; while ((object = [self nextObject])) { if (selector != NULL && [object respondsToSelector:selector]) { [object performSelector:selector]; } } } - (void)makeObjectsPerformSelector:(SEL)selector withObject:(id)argument { id object = nil; while ((object = [self nextObject])) { if (selector != NULL && [object respondsToSelector:selector]) { [object performSelector:selector withObject:argument]; } } } @end oolite-1.82/src/Core/OOFullScreenController.h000066400000000000000000000052431256642440500211470ustar00rootroot00000000000000/* OOFullScreenController.h Abstract base class for full screen mode controllers. Concrete implementations exist for different target platforms. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #import "OOMouseInteractionMode.h" @class MyOpenGLView; #if OOLITE_MAC_OS_X #define kOODisplayWidth ((NSString *)kCGDisplayWidth) #define kOODisplayHeight ((NSString *)kCGDisplayHeight) #define kOODisplayRefreshRate ((NSString *)kCGDisplayRefreshRate) #define kOODisplayBitsPerPixel ((NSString *)kCGDisplayBitsPerPixel) #define kOODisplayIOFlags ((NSString *)kCGDisplayIOFlags) #else #define kOODisplayWidth (@"Width") #define kOODisplayHeight (@"Height") #define kOODisplayRefreshRate (@"RefreshRate") #endif #define DISPLAY_MIN_COLOURS 32 #define DISPLAY_MIN_WIDTH 640 #define DISPLAY_MIN_HEIGHT 480 #define DISPLAY_MAX_WIDTH 5040 // to cope with DaddyHoggy's 3840x1024 & up to 3 x 1680x1050 displays... #define DISPLAY_MAX_HEIGHT 1800 @interface OOFullScreenController: NSObject { @private MyOpenGLView *_gameView; } - (id) initWithGameView:(MyOpenGLView *)view; #if OOLITE_PROPERTY_SYNTAX @property (nonatomic, readonly) MyOpenGLView *gameView; @property (nonatomic, getter=inFullScreenMode) BOOL fullScreenMode; @property (nonatomic, readonly) NSArray *displayModes; @property (nonatomic, readonly) NSDictionary *currentDisplayMode; @property (nonatomic, readonly) NSUInteger indexOfCurrentDisplayMode; #else - (MyOpenGLView *) gameView; - (BOOL) inFullScreenMode; - (void) setFullScreenMode:(BOOL)value; - (NSArray *) displayModes; - (NSDictionary *) currentDisplayMode; - (NSUInteger) indexOfCurrentDisplayMode; #endif - (BOOL) setDisplayWidth:(NSUInteger)width height:(NSUInteger)height refreshRate:(NSUInteger)refresh; - (NSDictionary *) findDisplayModeForWidth:(NSUInteger)width height:(NSUInteger)height refreshRate:(NSUInteger)d_refresh; - (void) noteMouseInteractionModeChangedFrom:(OOMouseInteractionMode)oldMode to:(OOMouseInteractionMode)newMode; @end oolite-1.82/src/Core/OOFullScreenController.m000066400000000000000000000040241256642440500211500ustar00rootroot00000000000000/* OOFullScreenController.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOFullScreenController.h" #import "OOLogging.h" @implementation OOFullScreenController - (id) initWithGameView:(MyOpenGLView *)view { if ((self = [super init])) { _gameView = [view retain]; } return self; } - (void) dealloc { DESTROY(_gameView); [super dealloc]; } - (MyOpenGLView *) gameView { return _gameView; } - (BOOL) inFullScreenMode { OOLogGenericSubclassResponsibility(); return NO; } - (void) setFullScreenMode:(BOOL)value { OOLogGenericSubclassResponsibility(); } - (NSArray *) displayModes { OOLogGenericSubclassResponsibility(); return nil; } - (NSDictionary *) currentDisplayMode { return [[self displayModes] objectAtIndex:[self indexOfCurrentDisplayMode]]; } - (NSUInteger) indexOfCurrentDisplayMode { OOLogGenericSubclassResponsibility(); return NSNotFound; } - (BOOL) setDisplayWidth:(NSUInteger)width height:(NSUInteger)height refreshRate:(NSUInteger)refresh { OOLogGenericSubclassResponsibility(); return NO; } - (NSDictionary *) findDisplayModeForWidth:(NSUInteger)width height:(NSUInteger)height refreshRate:(NSUInteger)d_refresh { OOLogGenericSubclassResponsibility(); return nil; } - (void) noteMouseInteractionModeChangedFrom:(OOMouseInteractionMode)oldMode to:(OOMouseInteractionMode)newMode { } @end oolite-1.82/src/Core/OOFunctionAttributes.h000066400000000000000000000101251256642440500206700ustar00rootroot00000000000000#ifndef INCLUDED_OOFUNCTIONATTRIBUTES_h #define INCLUDED_OOFUNCTIONATTRIBUTES_h #ifndef GCC_ATTR #ifdef __GNUC__ #define GCC_ATTR(x) __attribute__(x) #else #define GCC_ATTR(x) #endif #endif // Clang feature testing extensions. #ifndef __has_feature #define __has_feature(x) (0) #endif #ifndef __has_attribute #define __has_attribute(x) (0) #endif #ifndef __has_extension #define __has_extension(x) (0) #endif #if __cplusplus #define OOINLINE inline #else #define OOINLINE static inline #endif #if !OO_DEBUG #define ALWAYS_INLINE_FUNC GCC_ATTR((always_inline)) // Force inlining of function #else #define ALWAYS_INLINE_FUNC // Don't force inlining of function (because gdb is silly) #endif #define PURE_FUNC GCC_ATTR((pure)) // result dependent only on params and globals #define CONST_FUNC GCC_ATTR((const)) // pure + no pointer dereferences or globals #define NONNULL_FUNC GCC_ATTR((nonnull)) // Pointer parameters may not be NULL #define DEPRECATED_FUNC GCC_ATTR((deprecated)) // Warn if this function is used #define NO_RETURN_FUNC GCC_ATTR((noreturn)) // Function can never return #define NO_INLINE_FUNC GCC_ATTR((noinline)) // Function must never be inlined #define INLINE_PURE_FUNC ALWAYS_INLINE_FUNC PURE_FUNC #define INLINE_CONST_FUNC ALWAYS_INLINE_FUNC CONST_FUNC #if __has_extension(attribute_deprecated_with_message) #define DEPRECATED_MSG(msg) __attribute__((deprecated(msg))) #else #define DEPRECATED_MSG(msg) DEPRECATED_FUNC #endif #if __clang__ #define DEPRECATED_METHOD(msg) DEPRECATED_MSG(msg) #else // GCC doesn't support attributes on Objective-C methods. #define DEPRECATED_METHOD(msg) #endif #ifdef __GNUC__ #define EXPECT(x) __builtin_expect((x), 1) #define EXPECT_NOT(x) __builtin_expect((x), 0) #else #define EXPECT(x) (x) #define EXPECT_NOT(x) (x) #endif // OO_RETURNS_RETAINED: indicates the caller of a method owns a reference to the return value. #if __has_feature(attribute_ns_returns_retained) #define OO_RETURNS_RETAINED __attribute__((ns_returns_retained)) #else #define OO_RETURNS_RETAINED #endif // OO_NS_CONSUMED: indicates that a reference to an object parameter is "consumed". #ifndef OO_NS_CONSUMED #if __has_feature(attribute_ns_consumed) #define OO_NS_CONSUMED __attribute__((ns_consumed)) #else #define OO_NS_CONSUMED #endif #endif // OO_UNREACHABLE(): a statement that should never be executed (Clang optimization hint). #if __has_feature(__builtin_unreachable) #define OO_UNREACHABLE() __builtin_unreachable() #else #define OO_UNREACHABLE() do {} while (0) #endif /* OO_TAKES_FORMAT_STRING(stringIndex, firstToCheck): marks a function that applies [NSString stringWithFormat:]-type formatting to arguments. According to the fine manuals, mainline GCC supports basic checking of NSString format strings since 4.6, but doesn't validate the arguments. Update: apparently GCC 4.6.3 doesn't recognize Objective-C string literals as being string literals for this purpose, and errors out. -- Ahruman 2012-10-06 */ #if __has_attribute(format) /*|| (defined(OOLITE_GCC_VERSION) && OOLITE_GCC_VERSION >= 40600)*/ #define OO_TAKES_FORMAT_STRING(stringIndex, firstToCheck) __attribute__((format(NSString, stringIndex, firstToCheck))) #else #define OO_TAKES_FORMAT_STRING(stringIndex, firstToCheck) #endif #if __OBJC__ /* OOConsumeReference() Decrements the Clang Static Analyzer's notion of an object's reference count. This is used to work around cases where the analyzer claims an object is being leaked but it actually isn't, due to a pattern the analyzer doesn't understand (like singletons, or references being stored in JavaScript objects' private field). Do not use this blindly. If you aren't absolutely certain it's appropriate, don't use it. -- Ahruman 2011-01-28 */ #if NDEBUG OOINLINE id OOConsumeReference(id OO_NS_CONSUMED value) ALWAYS_INLINE_FUNC; OOINLINE id OOConsumeReference(id OO_NS_CONSUMED value) { return value; } #else // Externed to work around analyzer being too "clever" and ignoring attributes // when it's inlined. id OOConsumeReference(id OO_NS_CONSUMED value); #endif #endif #endif /* INCLUDED_OOFUNCTIONATTRIBUTES_h */ oolite-1.82/src/Core/OOGraphicsResetManager.h000066400000000000000000000036731256642440500211040ustar00rootroot00000000000000/* OOGraphicsResetManager.h Tracks objects with state that needs to be reset when the graphics context is modified (for instance, when switching between windowed and full-screen mode in SDL builds). This means re-uploading all textures, and also resetting any display lists relying on old texture names. All objects which have display lists must therefore register with the OOGraphicsResetManager on init, and unregister on dealloc. Copyright (C) 2007-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" @protocol OOGraphicsResetClient - (void) resetGraphicsState; @end @interface OOGraphicsResetManager: NSObject { @private NSMutableSet *clients; } + (OOGraphicsResetManager *) sharedManager; // Clients are not retained. - (void) registerClient:(id)client; - (void) unregisterClient:(id)client; // Forwarded to all clients, after resetting textures. - (void) resetGraphicsState; @end oolite-1.82/src/Core/OOGraphicsResetManager.m000066400000000000000000000062151256642440500211040ustar00rootroot00000000000000/* OOGraphicsResetManager.m Copyright (C) 2007-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOGraphicsResetManager.h" #import "OOTexture.h" #import "OOOpenGLExtensionManager.h" static OOGraphicsResetManager *sSingleton = nil; @implementation OOGraphicsResetManager - (void) dealloc { if (sSingleton == self) sSingleton = nil; [clients release]; [super dealloc]; } + (OOGraphicsResetManager *) sharedManager { if (sSingleton == nil) sSingleton = [[self alloc] init]; return sSingleton; } - (void) registerClient:(id)client { if (client != nil) { if (clients == nil) clients = [[NSMutableSet alloc] init]; [clients addObject:[NSValue valueWithPointer:client]]; } } - (void) unregisterClient:(id)client { [clients removeObject:[NSValue valueWithPointer:client]]; } - (void) resetGraphicsState { NSEnumerator *clientEnum = nil; id client = nil; OOGL(glFinish()); OOLog(@"rendering.reset.start", @"Resetting graphics state."); OOLogIndentIf(@"rendering.reset.start"); [[OOOpenGLExtensionManager sharedManager] reset]; [OOTexture rebindAllTextures]; for (clientEnum = [clients objectEnumerator]; (client = [[clientEnum nextObject] pointerValue]); ) { @try { [client resetGraphicsState]; } @catch (NSException *exception) { OOLog(kOOLogException, @"***** EXCEPTION -- %@ : %@ -- ignored during graphics reset.", [exception name], [exception reason]); } } OOLogOutdentIf(@"rendering.reset.start"); OOLog(@"rendering.reset.end", @"End of graphics state reset."); } @end @implementation OOGraphicsResetManager (Singleton) /* Canonical singleton boilerplate. See Cocoa Fundamentals Guide: Creating a Singleton Instance. See also +sharedManager above. // NOTE: assumes single-threaded first access. */ + (id) allocWithZone:(NSZone *)inZone { if (sSingleton == nil) { sSingleton = [super allocWithZone:inZone]; return sSingleton; } return nil; } - (id) copyWithZone:(NSZone *)inZone { return self; } - (id) retain { return self; } - (NSUInteger) retainCount { return UINT_MAX; } - (void) release {} - (id) autorelease { return self; } @end oolite-1.82/src/Core/OOHPVector.h000066400000000000000000000232701256642440500165330ustar00rootroot00000000000000/* OOHPVector.h Mathematical framework for Oolite. High-precision vectors for world-space coordinates Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef INCLUDED_OOMATHS_h #error Do not include OOHPVector.h directly; include OOMaths.h. #else #ifndef OOMATHS_EXTERNAL_VECTOR_TYPES typedef struct HPVector { OOHPScalar x; OOHPScalar y; OOHPScalar z; } HPVector; typedef struct HPVector2D { OOHPScalar x; OOHPScalar y; } HPVector2D; #endif extern const HPVector kZeroHPVector, /* 0, 0, 0 */ kBasisXHPVector, /* 1, 0, 0 */ kBasisYHPVector, /* 0, 1, 0 */ kBasisZHPVector; /* 0, 0, 1 */ extern const HPVector2D kZeroHPVector2D, /* 0, 0 */ kBasisXHPVector2D, /* 1, 0 */ kBasisYHPVector2D; /* 0, 1 */ /* Construct vector */ OOINLINE HPVector make_HPvector(OOHPScalar vx, OOHPScalar vy, OOHPScalar vz) INLINE_CONST_FUNC; OOINLINE HPVector2D MakeHPVector2D(OOHPScalar vx, OOHPScalar vy) INLINE_CONST_FUNC; OOINLINE HPVector vectorToHPVector(Vector v) INLINE_CONST_FUNC; OOINLINE Vector HPVectorToVector(HPVector v) INLINE_CONST_FUNC; #if !OOMATHS_STANDALONE /* Generate random vectors. */ HPVector OORandomUnitHPVector(void); HPVector OOHPVectorRandomSpatial(OOHPScalar maxLength); // Random vector uniformly distributed in radius-maxLength sphere. (Longer vectors are more common.) HPVector OOHPVectorRandomRadial(OOHPScalar maxLength); // Random vector with uniform distribution of direction and radius in radius-maxLength sphere. (Causes clustering at centre.) HPVector OORandomPositionInCylinder(HPVector centre1, OOHPScalar exclusion1, HPVector centre2, OOHPScalar exclusion2, OOHPScalar radius); HPVector OORandomPositionInShell(HPVector centre, OOHPScalar inner, OOHPScalar outer); /* returns the projection of 'point' to the plane defined by the point 'plane' and the normal vector 'normal' */ HPVector OOProjectHPVectorToPlane(HPVector point, HPVector plane, HPVector normal); #endif /* Multiply vector by scalar (in place) */ OOINLINE void HPscale_vector(HPVector *outHPVector, OOHPScalar factor) ALWAYS_INLINE_FUNC NONNULL_FUNC; /* Multiply vector by scalar */ OOINLINE HPVector HPvector_multiply_scalar(HPVector v, OOHPScalar s) INLINE_CONST_FUNC; /* Addition and subtraction of vectors */ OOINLINE HPVector HPvector_add(HPVector a, HPVector b) INLINE_CONST_FUNC; OOINLINE HPVector HPvector_subtract(HPVector a, HPVector b) INLINE_CONST_FUNC; #define HPvector_between(a, b) HPvector_subtract(b, a) OOINLINE HPVector HPvector_flip(HPVector v) INLINE_CONST_FUNC; /* HPVector linear interpolation */ OOINLINE HPVector OOHPVectorInterpolate(HPVector a, HPVector b, OOHPScalar where) INLINE_CONST_FUNC; OOINLINE HPVector OOHPVectorTowards(HPVector a, HPVector b, OOHPScalar where) INLINE_CONST_FUNC; /* Comparison of vectors */ OOINLINE bool HPvector_equal(HPVector a, HPVector b) INLINE_CONST_FUNC; /* Square of magnitude of vector */ OOINLINE OOHPScalar HPmagnitude2(HPVector vec) INLINE_CONST_FUNC; /* Magnitude of vector */ OOINLINE OOHPScalar HPmagnitude(HPVector vec) INLINE_CONST_FUNC; /* Normalize vector */ OOINLINE HPVector HPvector_normal(HPVector vec) INLINE_CONST_FUNC; /* Normalize vector, returning fallback if zero vector. */ OOINLINE HPVector HPvector_normal_or_fallback(HPVector vec, HPVector fallback) INLINE_CONST_FUNC; OOINLINE HPVector HPvector_normal_or_xbasis(HPVector vec) INLINE_CONST_FUNC; OOINLINE HPVector HPvector_normal_or_ybasis(HPVector vec) INLINE_CONST_FUNC; OOINLINE HPVector HPvector_normal_or_zbasis(HPVector vec) INLINE_CONST_FUNC; /* Square of distance between vectors */ OOINLINE OOHPScalar HPdistance2(HPVector v1, HPVector v2) INLINE_CONST_FUNC; /* Distance between vectors */ OOINLINE OOHPScalar HPdistance(HPVector v1, HPVector v2) INLINE_CONST_FUNC; /* Dot product */ OOINLINE OOHPScalar HPdot_product (HPVector first, HPVector second) INLINE_CONST_FUNC; /* NORMALIZED cross product */ OOINLINE HPVector HPcross_product(HPVector first, HPVector second) INLINE_CONST_FUNC; /* General cross product */ OOINLINE HPVector HPtrue_cross_product(HPVector first, HPVector second) CONST_FUNC; /* Triple product */ OOINLINE OOHPScalar HPtriple_product(HPVector first, HPVector second, HPVector third) INLINE_CONST_FUNC; /* Given three points on a surface, returns the normal to the surface. */ OOINLINE HPVector HPnormal_to_surface(HPVector v1, HPVector v2, HPVector v3) CONST_FUNC; #if __OBJC__ NSString *HPVectorDescription(HPVector vector); // @"(x, y, z)" NSArray *ArrayFromHPVector(HPVector vector); #endif #if OOMATHS_OPENGL_INTEGRATION /* OpenGL conveniences. Need to be macros to work with OOMacroOpenGL. */ #define GLVertexOOHPVector(v) do { HPVector v_ = v; glVertex3f(v_.x, v_.y, v_.z); } while (0) #define GLTranslateOOHPVector(v) do { HPVector v_ = v; OOGL(glTranslatef(v_.x, v_.y, v_.z)); } while (0) #endif /*** Only inline definitions beyond this point ***/ OOINLINE HPVector make_HPvector (OOHPScalar vx, OOHPScalar vy, OOHPScalar vz) { HPVector result; result.x = vx; result.y = vy; result.z = vz; return result; } OOINLINE HPVector2D MakeHPVector2D(OOHPScalar vx, OOHPScalar vy) { HPVector2D result; result.x = vx; result.y = vy; return result; } OOINLINE HPVector vectorToHPVector(Vector v) { HPVector result; result.x = (OOHPScalar)v.x; result.y = (OOHPScalar)v.y; result.z = (OOHPScalar)v.z; return result; } OOINLINE Vector HPVectorToVector(HPVector v) { Vector result; result.x = (OOScalar)v.x; result.y = (OOScalar)v.y; result.z = (OOScalar)v.z; return result; } OOINLINE void HPscale_vector(HPVector *vec, OOHPScalar factor) { /* Clang static analyzer: reports an unintialized value here when called from -[HeadUpDisplay rescaleByFactor:]. This is blatantly wrong, as the array the vector comes from is fully initialized in the range being looped over. -- Ahruman 2012-09-14 */ vec->x *= factor; vec->y *= factor; vec->z *= factor; } OOINLINE HPVector HPvector_multiply_scalar(HPVector v, OOHPScalar s) { /* Clang static analyzer: reports a garbage value here when called from -[OOMesh rescaleByFactor:], apparently on baseless assumption that OOMesh._vertices points to only one vertex. -- Ahruman 2012-09-14 */ HPVector r; r.x = v.x * s; r.y = v.y * s; r.z = v.z * s; return r; } OOINLINE HPVector HPvector_add(HPVector a, HPVector b) { HPVector r; r.x = a.x + b.x; r.y = a.y + b.y; r.z = a.z + b.z; return r; } OOINLINE HPVector OOHPVectorInterpolate(HPVector a, HPVector b, OOHPScalar where) { return make_HPvector(OOLerp(a.x, b.x, where), OOLerp(a.y, b.y, where), OOLerp(a.z, b.z, where)); } OOINLINE HPVector OOHPVectorTowards(HPVector a, HPVector b, OOHPScalar where) { return make_HPvector(a.x + b.x * where, a.y + b.y * where, a.z + b.z * where); } OOINLINE HPVector HPvector_subtract(HPVector a, HPVector b) { HPVector r; r.x = a.x - b.x; r.y = a.y - b.y; r.z = a.z - b.z; return r; } OOINLINE HPVector HPvector_flip(HPVector v) { return HPvector_subtract(kZeroHPVector, v); } OOINLINE bool HPvector_equal(HPVector a, HPVector b) { return a.x == b.x && a.y == b.y && a.z == b.z; } OOINLINE OOHPScalar HPmagnitude2(HPVector vec) { return vec.x * vec.x + vec.y * vec.y + vec.z * vec.z; } OOINLINE OOHPScalar HPmagnitude(HPVector vec) { return sqrt(HPmagnitude2(vec)); } OOINLINE HPVector HPvector_normal_or_fallback(HPVector vec, HPVector fallback) { OOHPScalar mag2 = HPmagnitude2(vec); if (EXPECT_NOT(mag2 == 0.0f)) return fallback; return HPvector_multiply_scalar(vec, 1.0f / sqrt(mag2)); } OOINLINE HPVector HPvector_normal_or_xbasis(HPVector vec) { return HPvector_normal_or_fallback(vec, kBasisXHPVector); } OOINLINE HPVector HPvector_normal_or_ybasis(HPVector vec) { return HPvector_normal_or_fallback(vec, kBasisYHPVector); } OOINLINE HPVector HPvector_normal_or_zbasis(HPVector vec) { return HPvector_normal_or_fallback(vec, kBasisZHPVector); } OOINLINE HPVector HPvector_normal(HPVector vec) { return HPvector_normal_or_fallback(vec, kZeroHPVector); } OOINLINE OOHPScalar HPdistance2(HPVector v1, HPVector v2) { return HPmagnitude2(HPvector_subtract(v1, v2)); } OOINLINE OOHPScalar HPdistance(HPVector v1, HPVector v2) { return HPmagnitude(HPvector_subtract(v1, v2)); } OOINLINE HPVector HPtrue_cross_product(HPVector first, HPVector second) { HPVector result; result.x = (first.y * second.z) - (first.z * second.y); result.y = (first.z * second.x) - (first.x * second.z); result.z = (first.x * second.y) - (first.y * second.x); return result; } OOINLINE HPVector HPcross_product(HPVector first, HPVector second) { return HPvector_normal(HPtrue_cross_product(first, second)); } OOINLINE OOHPScalar HPdot_product (HPVector a, HPVector b) { return (a.x * b.x) + (a.y * b.y) + (a.z * b.z); } OOINLINE OOHPScalar HPtriple_product(HPVector first, HPVector second, HPVector third) { return HPdot_product(first, HPtrue_cross_product(second, third)); } OOINLINE HPVector HPnormal_to_surface(HPVector v1, HPVector v2, HPVector v3) { HPVector d0, d1; d0 = HPvector_subtract(v2, v1); d1 = HPvector_subtract(v3, v2); return HPcross_product(d0, d1); } #endif /* INCLUDED_OOMATHS_h */ oolite-1.82/src/Core/OOHPVector.m000066400000000000000000000110631256642440500165350ustar00rootroot00000000000000/* OOHPVector.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "OOMaths.h" const HPVector kZeroHPVector = { 0.0f, 0.0f, 0.0f }; const HPVector kBasisXHPVector = { 1.0f, 0.0f, 0.0f }; const HPVector kBasisYHPVector = { 0.0f, 1.0f, 0.0f }; const HPVector kBasisZHPVector = { 0.0f, 0.0f, 1.0f }; const HPVector2D kZeroHPVector2D = { 0.0f, 0.0f }; const HPVector2D kBasisXHPVector2D = { 1.0f, 0.0f }; const HPVector2D kBasisYHPVector2D = { 0.0f, 1.0f }; /*#if !OOMATHS_STANDALONE const BoundingBox kZeroBoundingBox = {{ 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f }}; #endif*/ #if __OBJC__ NSString *HPVectorDescription(HPVector vector) { return [NSString stringWithFormat:@"(%g, %g, %g)", vector.x, vector.y, vector.z]; } NSArray *ArrayFromHPVector(HPVector vec) { return [NSArray arrayWithObjects:[NSNumber numberWithDouble:vec.x], [NSNumber numberWithDouble:vec.y], [NSNumber numberWithDouble:vec.z], nil]; } #endif #if !OOMATHS_STANDALONE /* This generates random vectors distrubuted evenly over the surface of the unit sphere. It does this the simple way, by generating vectors in the half-unit cube and rejecting those outside the half-unit sphere (and the zero vector), then normalizing the result. (Half-unit measures are used to avoid unnecessary multiplications of randf() values.) In principle, using three normally-distributed co-ordinates (and again normalizing the result) would provide the right result without looping, but I don't trust bellf() so I'll go with the simple approach for now. */ HPVector OORandomUnitHPVector(void) { HPVector v; OOHPScalar m; do { v = make_HPvector(randf() - 0.5f, randf() - 0.5f, randf() - 0.5f); m = HPmagnitude2(v); } while (m > 0.25f || m == 0.0f); // We're confining to a sphere of radius 0.5 using the sqared magnitude; 0.5 squared is 0.25. return HPvector_normal(v); } HPVector OOHPVectorRandomSpatial(OOHPScalar maxLength) { HPVector v; OOHPScalar m; do { v = make_HPvector(randf() - 0.5f, randf() - 0.5f, randf() - 0.5f); m = HPmagnitude2(v); } while (m > 0.25f); // We're confining to a sphere of radius 0.5 using the sqared magnitude; 0.5 squared is 0.25. return HPvector_multiply_scalar(v, maxLength * 2.0f); // 2.0 is to compensate for the 0.5-radius sphere. } HPVector OOHPVectorRandomRadial(OOHPScalar maxLength) { return HPvector_multiply_scalar(OORandomUnitHPVector(), randf() * maxLength); } HPVector OOHPRandomPositionInBoundingBox(BoundingBox bb) { HPVector result; result.x = (OOHPScalar)(bb.min.x + randf() * (bb.max.x - bb.min.x)); result.y = (OOHPScalar)(bb.min.y + randf() * (bb.max.y - bb.min.y)); result.z = (OOHPScalar)(bb.min.z + randf() * (bb.max.z - bb.min.z)); return result; } HPVector OORandomPositionInCylinder(HPVector centre1, OOHPScalar exclusion1, HPVector centre2, OOHPScalar exclusion2, OOHPScalar radius) { OOHPScalar exc12 = exclusion1*exclusion1; OOHPScalar exc22 = exclusion2*exclusion2; if (HPdistance(centre1,centre2) < (exclusion1+exclusion2)*1.2) { OOLog(@"position.cylinder.error",@"Trying to generate cylinder position in range %f long with exclusions %f and %f",HPdistance(centre1,centre2),exclusion1,exclusion2); } HPVector result; do { result = HPvector_add(OOHPVectorInterpolate(centre1,centre2,randf()),OOHPVectorRandomSpatial(radius)); } while(HPdistance2(result,centre1)] [] basicNumber [] [] whitespace ::= [] whitespaceChar ::= " " | "\t" sign ::= "+" | "-" basicNumber = integer [decimal] | decimal integer ::= [] digit ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" decimal ::= decimalPoint ::= "." exponent = [] e ::= "e" | "E" if allowSpaces = NO, the [] terms are excluded. */ BOOL OOIsNumberLiteral(NSString *string, BOOL allowSpaces); oolite-1.82/src/Core/OOIsNumberLiteral.m000066400000000000000000000060301256642440500201020ustar00rootroot00000000000000/* OOISNumberLiteral.m Copyright (C) 2008-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOIsNumberLiteral.h" #if 0 #define FAIL(s) do { NSLog(@"OOIsNumberLiteral failed for \"%@\": %@.", string, @s); return NO; } while (0) #else #define FAIL(s) do { return NO; } while (0) #endif BOOL OOIsNumberLiteral(NSString *string, BOOL allowSpaces) { BOOL leadingSpace = allowSpaces, trailingSpace = NO, allowSign = YES, allowE = NO, hadE = NO, hadExp = NO, allowDec = YES, hadNumber = NO; NSUInteger i, count; if (string == nil) return NO; count = [string length]; for (i = 0; i != count; ++i) { switch ([string characterAtIndex:i]) { // case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': leadingSpace = NO; if (trailingSpace) FAIL("Digit after trailing whitespace"); if (!hadE) allowE = YES; else hadExp = YES; allowSign = NO; hadNumber = YES; break; // case ' ': case '\t': if (leadingSpace || trailingSpace) break; if (hadNumber && allowSpaces) { trailingSpace = YES; allowSign = allowE = allowDec = NO; break; } FAIL("Space in unpermitted position"); // case '-': case '+': leadingSpace = NO; if (allowSign) { allowSign = NO; break; } FAIL("Sign (+ or -) in unpermitted position"); // case '.': leadingSpace = NO; if (allowDec) { allowDec = NO; continue; } FAIL("Sign (+ or -) in unpermitted position"); // case 'e': case 'E': leadingSpace = NO; if (allowE) { allowE = NO; allowSign = YES; allowDec = NO; hadE = YES; continue; } FAIL("E in unpermitted position"); default: FAIL ("Unpermitted character"); } } if (hadE && !hadExp) FAIL("E with no exponent"); if (!hadNumber) FAIL("No digits in string"); return YES; } oolite-1.82/src/Core/OOJoystickManager.h000066400000000000000000000211321256642440500201260ustar00rootroot00000000000000/* OOJoystickManager.h By Dylan Smith modified by Alex Smith and Jens Ayton JoystickHandler handles joystick events from SDL, and translates them into the appropriate action via a lookup table. The lookup table is stored as a simple array rather than an ObjC dictionary since this will be examined fairly often (once per frame during gameplay). Conversion methods are provided to convert between the internal representation and an NSDictionary (for loading/saving user defaults and for use in areas where portability/ease of coding are more important than performance such as the GUI) Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" // Enums are used here rather than a more complex ObjC object because // these are required very frequently (once per frame) so must be light // on CPU cycles (try and avoid too many objc sendmsgs). // Controls that can be an axis enum { AXIS_ROLL, AXIS_PITCH, AXIS_YAW, AXIS_PRECISION, AXIS_THRUST, AXIS_VIEWX, AXIS_VIEWY, #if OO_FOV_INFLIGHT_CONTROL_ENABLED AXIS_FIELD_OF_VIEW, #endif AXIS_end }; // Controls that can be a button enum { BUTTON_INCTHRUST, BUTTON_DECTHRUST, BUTTON_SCANNERZOOM, BUTTON_SCANNERUNZOOM, BUTTON_JETTISON, BUTTON_COMPASSMODE, BUTTON_COMMSLOG, BUTTON_DOCKCPU, BUTTON_DOCKCPUFAST, BUTTON_FUELINJECT, BUTTON_HYPERSPEED, BUTTON_HYPERDRIVE, BUTTON_GALACTICDRIVE, BUTTON_FIRE, BUTTON_ARMMISSILE, BUTTON_LAUNCHMISSILE, BUTTON_PRIMEEQUIPMENT, BUTTON_ACTIVATEEQUIPMENT, BUTTON_UNARM, BUTTON_TARGETINCOMINGMISSILE, BUTTON_CYCLEMISSILE, BUTTON_ENERGYBOMB, // now fast activate B BUTTON_WEAPONSONLINETOGGLE, BUTTON_ID, BUTTON_ECM, BUTTON_ESCAPE, BUTTON_CLOAK, // now fast activate A BUTTON_PRECISION, BUTTON_VIEWFORWARD, BUTTON_VIEWAFT, BUTTON_VIEWPORT, BUTTON_VIEWSTARBOARD, BUTTON_SNAPSHOT, BUTTON_PREVTARGET, BUTTON_NEXTTARGET, BUTTON_MODEEQUIPMENT, #if OO_FOV_INFLIGHT_CONTROL_ENABLED BUTTON_INC_FIELD_OF_VIEW, BUTTON_DEC_FIELD_OF_VIEW, #endif BUTTON_end }; // Stick constants #define MAX_STICKS 2 #define MAX_AXES 16 #define MAX_REAL_BUTTONS 64 #define MAX_HATS 4 #define MAX_BUTTONS (MAX_REAL_BUTTONS + 4 * MAX_HATS) #define STICK_NOFUNCTION -1 #define STICK_AXISUNASSIGNED -10.0 #define STICK_PRECISIONFAC 3 #define STICK_NORMALDIV 32768 #define STICK_PRECISIONDIV (STICK_PRECISIONFAC*STICK_NORMALDIV) #if OOLITE_MAC_OS_X #define STICK_DEADZONE 0.0025 #else #define STICK_DEADZONE 0.05 #endif #define STICK_MAX_DEADZONE (STICK_DEADZONE * 2) // Kind of stick device (these are bits - if any more are added, // the next one is 4 and so on). #define HW_AXIS 1 #define HW_BUTTON 2 // The threshold at which an axis can trigger a call back. // The max of abs(axis) is 32767. #define AXCBTHRESH 20000 // Dictionary keys - used in the defaults file #define AXIS_SETTINGS @"JoystickAxes" // NSUserDefaults #define BUTTON_SETTINGS @"JoystickButs" // NSUserDefaults #define STICK_ISAXIS @"isAxis" // YES=axis NO=button #define STICK_NUMBER @"stickNum" // Stick number 0 to 4 #define STICK_AXBUT @"stickAxBt" // Axis or button number #define STICK_FUNCTION @"stickFunc" // Function of axis/button #define STICK_ROLL_AXIS_PROFILE_SETTING @"RollAxisProfile" // Joystick Profiles #define STICK_PITCH_AXIS_PROFILE_SETTING @"PitchAxisProfile" // Joystick Profiles #define STICK_YAW_AXIS_PROFILE_SETTING @"YawAxisProfile" // Joystick Profiles // shortcut to make code more readable when using enum as key for // an NSDictionary #define ENUMKEY(x) [NSString stringWithFormat: @"%d", x] //SDL Abstracted constants #if OOLITE_SDL #import enum { JOYAXISMOTION = SDL_JOYAXISMOTION, JOYBUTTONDOWN = SDL_JOYBUTTONDOWN, JOYBUTTONUP = SDL_JOYBUTTONUP, JOYBUTTON_PRESSED = SDL_PRESSED, JOYBUTTON_RELEASED = SDL_RELEASED, JOYHAT_MOTION = SDL_JOYHATMOTION, JOYHAT_CENTERED = SDL_HAT_CENTERED, JOYHAT_UP = SDL_HAT_UP, JOYHAT_RIGHT = SDL_HAT_RIGHT, JOYHAT_DOWN = SDL_HAT_DOWN, JOYHAT_LEFT = SDL_HAT_LEFT, JOYHAT_RIGHTUP = SDL_HAT_RIGHTUP, JOYHAT_RIGHTDOWN = SDL_HAT_RIGHTDOWN, JOYHAT_LEFTUP = SDL_HAT_LEFTUP, JOYHAT_LEFTDOWN = SDL_HAT_LEFTDOWN, }; typedef SDL_JoyButtonEvent JoyButtonEvent; typedef SDL_JoyAxisEvent JoyAxisEvent; typedef SDL_JoyHatEvent JoyHatEvent; #else enum { JOYAXISMOTION, JOYBUTTONDOWN, JOYBUTTONUP, JOYBUTTON_PRESSED, JOYBUTTON_RELEASED, JOYHAT_MOTION, JOYHAT_CENTERED = 0x00, JOYHAT_UP = 0x01, JOYHAT_RIGHT = 0x02, JOYHAT_DOWN = 0x04, JOYHAT_LEFT = 0x08, JOYHAT_RIGHTUP = (JOYHAT_RIGHT|JOYHAT_UP), JOYHAT_RIGHTDOWN = (JOYHAT_RIGHT|JOYHAT_DOWN), JOYHAT_LEFTUP = (JOYHAT_LEFT|JOYHAT_UP), JOYHAT_LEFTDOWN = (JOYHAT_LEFT|JOYHAT_DOWN), }; // Abstracted SDL event types typedef struct { uint32_t type; uint8_t which; uint8_t axis; int value; } JoyAxisEvent; typedef struct { uint32_t type; uint8_t which; uint8_t button; int state; } JoyButtonEvent; typedef struct { uint32_t type; uint8_t which; uint8_t hat; uint8_t value; uint8_t padding; } JoyHatEvent; #endif //OOLITE_SDL #import "OOJoystickProfile.h" @interface OOJoystickManager: NSObject { @private // Axis/button mapping arrays int8_t axismap[MAX_STICKS][MAX_AXES]; int8_t buttonmap[MAX_STICKS][MAX_BUTTONS]; double axstate[AXIS_end]; BOOL butstate[BUTTON_end]; uint8_t hatstate[MAX_STICKS][MAX_HATS]; BOOL precisionMode; OOJoystickAxisProfile *roll_profile; OOJoystickAxisProfile *pitch_profile; OOJoystickAxisProfile *yaw_profile; // Handle callbacks - the object, selector to call // the desired function, and the hardware (axis or button etc.) id cbObject; SEL cbSelector; char cbHardware; BOOL invertPitch; } + (id) sharedStickHandler; + (BOOL) setStickHandlerClass:(Class)aClass; // General. // Note: handleSDLEvent returns a BOOL (YES we handled it or NO we // didn't) so in the future when more handler classes are written, // the GameView event loop can just go through an NSArray of handlers // until it finds a handler that handles the event. - (id) init; // Roll/pitch axis - (NSPoint) rollPitchAxis; // View axis - (NSPoint) viewAxis; // convert a dictionary into the internal function map - (void) setFunction:(int)function withDict: (NSDictionary *)stickFn; - (void) unsetAxisFunction:(int)function; - (void) unsetButtonFunction:(int)function; // Accessors and discovery about the hardware. // These work directly on the internal lookup table so to be fast // since they are likely to be called by the game loop. - (NSUInteger) joystickCount; - (BOOL) getButtonState:(int)function; - (double) getAxisState:(int)function; - (double) getSensitivity; // Axis profile handling - (void) setProfile: (OOJoystickAxisProfile *) profile forAxis:(int) axis; - (OOJoystickAxisProfile *) getProfileForAxis: (int) axis; - (void) saveProfileForAxis: (int) axis; - (void) loadProfileForAxis: (int) axis; // This one just returns a pointer to the entire state array to // allow for multiple lookups with only one objc_sendMsg - (const BOOL *) getAllButtonStates; // Hardware introspection. - (NSArray *) listSticks; // These use NSDictionary/NSArray since they are used outside the game // loop and are needed for loading/saving defaults. - (NSDictionary *) axisFunctions; - (NSDictionary *) buttonFunctions; // Set a callback for the next moved axis/pressed button. hwflags // is in the form HW_AXIS | HW_BUTTON (or just one of). - (void)setCallback:(SEL)selector object:(id)obj hardware:(char)hwflags; - (void)clearCallback; // Methods generally only used by this class. - (void) setDefaultMapping; - (void) clearMappings; - (void) clearStickStates; - (void) clearStickButtonState: (int)stickButton; - (void) decodeAxisEvent: (JoyAxisEvent *)evt; - (void) decodeButtonEvent: (JoyButtonEvent *)evt; - (void) decodeHatEvent: (JoyHatEvent *)evt; - (void) saveStickSettings; - (void) loadStickSettings; //Methods that should be overridden by all subclasses - (NSString *) nameOfJoystick:(NSUInteger)stickNumber; - (int16_t) getAxisWithStick:(NSUInteger) stickNum axis:(NSUInteger)axisNum; @end oolite-1.82/src/Core/OOJoystickManager.m000066400000000000000000000424521256642440500201430ustar00rootroot00000000000000/* OOJoystickManager.m By Dylan Smith modified by Alex Smith Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJoystickManager.h" #import "OOLogging.h" #import "OOCollectionExtractors.h" static Class sStickHandlerClass = Nil; static id sSharedStickHandler = nil; @interface OOJoystickManager (Private) // Setting button and axis functions - (void) setFunctionForAxis:(int)axis function:(int)function stick:(int)stickNum; - (void) setFunctionForButton:(int)button function:(int)function stick:(int)stickNum; @end @implementation OOJoystickManager + (id) sharedStickHandler { if (sSharedStickHandler == nil) { if (sStickHandlerClass == Nil) sStickHandlerClass = [OOJoystickManager class]; sSharedStickHandler = [[sStickHandlerClass alloc] init]; } return sSharedStickHandler; } + (BOOL) setStickHandlerClass:(Class)aClass { NSAssert(sStickHandlerClass == nil, @"Can't set joystick handler class after joystick handler is initialized."); NSParameterAssert(aClass == Nil || [aClass isSubclassOfClass:[OOJoystickManager class]]); sStickHandlerClass = aClass; return YES; } - (id) init { if ((self = [super init])) { // set initial values for stick buttons/axes (NO for buttons, // STICK_AXISUNASSIGNED for axes). Caution: calling this again // after axes have been assigned will set all the axes to // STICK_AXISUNASSIGNED so if there is a need to do something // like this, then do it some other way, or change this method // so it doesn't do that. [self clearStickStates]; // Make some sensible mappings. This also ensures unassigned // axes and buttons are set to unassigned (STICK_NOFUNCTION). [self loadStickSettings]; invertPitch = NO; precisionMode = NO; } return self; } - (NSPoint) rollPitchAxis { return NSMakePoint([self getAxisState:AXIS_ROLL], [self getAxisState:AXIS_PITCH]); } - (NSPoint) viewAxis { return NSMakePoint(axstate[AXIS_VIEWX], axstate[AXIS_VIEWY]); } - (BOOL) getButtonState: (int)function { return butstate[function]; } - (const BOOL *)getAllButtonStates { return butstate; } - (double) getAxisState: (int)function { if (axstate[function] == STICK_AXISUNASSIGNED) { return STICK_AXISUNASSIGNED; } switch (function) { case AXIS_ROLL: if (precisionMode) { return [roll_profile value:axstate[function]] / STICK_PRECISIONFAC; } else { return [roll_profile value:axstate[function]]; } case AXIS_PITCH: if (precisionMode) { return [pitch_profile value:axstate[function]] / STICK_PRECISIONFAC; } else { return [pitch_profile value:axstate[function]]; } case AXIS_YAW: if (precisionMode) { return [yaw_profile value:axstate[function]] / STICK_PRECISIONFAC; } else { return [yaw_profile value:axstate[function]]; } default: return axstate[function]; } } - (double) getSensitivity { return precisionMode ? STICK_PRECISIONFAC : 1.0; } - (void) setProfile: (OOJoystickAxisProfile *) profile forAxis: (int) axis { switch (axis) { case AXIS_ROLL: [roll_profile release]; roll_profile = [profile retain]; break; case AXIS_PITCH: [pitch_profile release]; pitch_profile = [profile retain]; break; case AXIS_YAW: [yaw_profile release]; yaw_profile = [profile retain]; break; } return; } - (OOJoystickAxisProfile *) getProfileForAxis: (int) axis { switch (axis) { case AXIS_ROLL: return roll_profile; case AXIS_PITCH: return pitch_profile; case AXIS_YAW: return yaw_profile; } return nil; } - (void) saveProfileForAxis: (int) axis { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; OOJoystickAxisProfile *profile; OOJoystickStandardAxisProfile *standard_profile; OOJoystickSplineAxisProfile *spline_profile; NSArray *controlPoints; NSMutableArray *points; NSPoint point; NSUInteger i; profile = [self getProfileForAxis: axis]; if (!profile) return; [dict setObject: [NSNumber numberWithDouble: [profile deadzone]] forKey: @"Deadzone"]; if ([profile isKindOfClass: [OOJoystickStandardAxisProfile class]]) { standard_profile = (OOJoystickStandardAxisProfile *) profile; [dict setObject: @"Standard" forKey: @"Type"]; [dict setObject: [NSNumber numberWithDouble: [standard_profile power]] forKey: @"Power"]; [dict setObject: [NSNumber numberWithDouble: [standard_profile parameter]] forKey: @"Parameter"]; } else if ([profile isKindOfClass: [OOJoystickSplineAxisProfile class]]) { spline_profile = (OOJoystickSplineAxisProfile *) profile; [dict setObject: @"Spline" forKey: @"Type"]; controlPoints = [NSArray arrayWithArray: [spline_profile controlPoints]]; points = [[NSMutableArray alloc] initWithCapacity: [controlPoints count]]; for (i = 0; i < [controlPoints count]; i++) { point = [[controlPoints objectAtIndex: i] pointValue]; [points addObject: [NSArray arrayWithObjects: [NSNumber numberWithFloat: point.x], [NSNumber numberWithFloat: point.y], nil ]]; } [dict setObject: points forKey: @"ControlPoints"]; } else { [dict setObject: @"Standard" forKey: @"Type"]; } if (axis == AXIS_ROLL) { [defaults setObject: dict forKey: STICK_ROLL_AXIS_PROFILE_SETTING]; } else if (axis == AXIS_PITCH) { [defaults setObject: dict forKey: STICK_PITCH_AXIS_PROFILE_SETTING]; } else if (axis == AXIS_YAW) { [defaults setObject: dict forKey: STICK_YAW_AXIS_PROFILE_SETTING]; } return; } - (void) loadProfileForAxis: (int) axis { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSDictionary *dict; OOJoystickStandardAxisProfile *standard_profile; OOJoystickSplineAxisProfile *spline_profile; if (axis == AXIS_ROLL) { dict = [defaults objectForKey: STICK_ROLL_AXIS_PROFILE_SETTING]; } else if (axis == AXIS_PITCH) { dict = [defaults objectForKey: STICK_PITCH_AXIS_PROFILE_SETTING]; } else if (axis == AXIS_YAW) { dict = [defaults objectForKey: STICK_YAW_AXIS_PROFILE_SETTING]; } else { return; } NSString *type = [dict objectForKey: @"Type"]; if ([type isEqualToString: @"Standard"]) { standard_profile = [[OOJoystickStandardAxisProfile alloc] init]; [standard_profile setDeadzone: [[dict objectForKey: @"Deadzone"] doubleValue]]; [standard_profile setPower: [[dict objectForKey: @"Power"] doubleValue]]; [standard_profile setParameter: [[dict objectForKey: @"Parameter"] doubleValue]]; [self setProfile: [standard_profile autorelease] forAxis: axis]; } else if([type isEqualToString: @"Spline"]) { spline_profile = [[OOJoystickSplineAxisProfile alloc] init]; [spline_profile setDeadzone: [[dict objectForKey: @"Deadzone"] doubleValue]]; NSArray *points = [dict objectForKey: @"ControlPoints"], *pointArray; NSPoint point; NSUInteger i; for (i = 0; i < [points count]; i++) { pointArray = [points objectAtIndex: i]; if ([pointArray count] >= 2) { point = NSMakePoint([[pointArray objectAtIndex: 0] floatValue], [[pointArray objectAtIndex: 1] floatValue]); [spline_profile addControl: point]; } } [self setProfile: [spline_profile autorelease] forAxis: axis]; } else { [self setProfile: [[[OOJoystickStandardAxisProfile alloc] init] autorelease] forAxis: axis]; } } - (NSArray *)listSticks { NSUInteger i, stickCount = [self joystickCount]; NSMutableArray *stickList = [NSMutableArray array]; for (i = 0; i < stickCount; i++) { [stickList addObject:[self nameOfJoystick:i]]; } return stickList; } - (NSDictionary *) axisFunctions { int i,j; NSMutableDictionary *fnList = [NSMutableDictionary dictionary]; // Add axes for (i = 0; i < MAX_AXES; i++) { for (j = 0; j < MAX_STICKS; j++) { if(axismap[j][i] >= 0) { NSDictionary *fnDict=[NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:YES], STICK_ISAXIS, [NSNumber numberWithInt:j], STICK_NUMBER, [NSNumber numberWithInt:i], STICK_AXBUT, nil]; [fnList setValue: fnDict forKey: ENUMKEY(axismap[j][i])]; } } } return fnList; } - (NSDictionary *)buttonFunctions { int i, j; NSMutableDictionary *fnList = [NSMutableDictionary dictionary]; // Add buttons for (i = 0; i < MAX_BUTTONS; i++) { for (j = 0; j < MAX_STICKS; j++) { if(buttonmap[j][i] >= 0) { NSDictionary *fnDict = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:NO], STICK_ISAXIS, [NSNumber numberWithInt:j], STICK_NUMBER, [NSNumber numberWithInt:i], STICK_AXBUT, nil]; [fnList setValue:fnDict forKey:ENUMKEY(buttonmap[j][i])]; } } } return fnList; } - (void) setFunction:(int)function withDict:(NSDictionary *)stickFn { BOOL isAxis = [stickFn oo_boolForKey:STICK_ISAXIS]; int stickNum = [stickFn oo_intForKey:STICK_NUMBER]; int stickAxBt = [stickFn oo_intForKey:STICK_AXBUT]; if (isAxis) { [self setFunctionForAxis:stickAxBt function:function stick:stickNum]; } else { [self setFunctionForButton:stickAxBt function:function stick:stickNum]; } } - (void) setFunctionForAxis:(int)axis function:(int)function stick:(int)stickNum { NSParameterAssert(axis < MAX_AXES && stickNum < MAX_STICKS); int16_t axisvalue = [self getAxisWithStick:stickNum axis:axis]; [self unsetAxisFunction:function]; axismap[stickNum][axis] = function; // initialize the throttle to what it's set to now (or else the // commander has to waggle the throttle to wake it up). Other axes // set as default. if(function == AXIS_THRUST) { axstate[function] = (float)(65536 - (axisvalue + 32768)) / 65536; } else { axstate[function] = (float)axisvalue / STICK_NORMALDIV; } } - (void) setFunctionForButton:(int)button function:(int)function stick:(int)stickNum { NSParameterAssert(button < MAX_BUTTONS && stickNum < MAX_STICKS); int i, j; for (i = 0; i < MAX_BUTTONS; i++) { for (j = 0; j < MAX_STICKS; j++) { if (buttonmap[j][i] == function) { buttonmap[j][i] = STICK_NOFUNCTION; break; } } } buttonmap[stickNum][button] = function; } - (void) unsetAxisFunction:(int)function { int i, j; for (i = 0; i < MAX_AXES; i++) { for (j = 0; j < MAX_STICKS; j++) { if (axismap[j][i] == function) { axismap[j][i] = STICK_NOFUNCTION; axstate[function] = STICK_AXISUNASSIGNED; break; } } } } - (void) unsetButtonFunction:(int)function { int i,j; for (i = 0; i < MAX_BUTTONS; i++) { for (j = 0; j < MAX_STICKS; j++) { if(buttonmap[j][i] == function) { buttonmap[j][i] = STICK_NOFUNCTION; break; } } } } - (void) setDefaultMapping { // assign the simplest mapping: stick 0 having // axis 0/1 being roll/pitch and button 0 being fire, 1 being missile // All joysticks should at least have two axes and two buttons. axismap[0][0] = AXIS_ROLL; axismap[0][1] = AXIS_PITCH; buttonmap[0][0] = BUTTON_FIRE; buttonmap[0][1] = BUTTON_LAUNCHMISSILE; } - (void) clearMappings { memset(axismap, STICK_NOFUNCTION, sizeof axismap); memset(buttonmap, STICK_NOFUNCTION, sizeof buttonmap); } - (void) clearStickStates { int i; for (i = 0; i < AXIS_end; i++) { axstate[i] = STICK_AXISUNASSIGNED; } for (i = 0; i < BUTTON_end; i++) { butstate[i] = 0; } } - (void) clearStickButtonState:(int)stickButton { if (stickButton >= 0 && stickButton < BUTTON_end) { butstate[stickButton] = 0; } } - (void)setCallback:(SEL) selector object:(id) obj hardware:(char)hwflags { cbObject = obj; cbSelector = selector; cbHardware = hwflags; } - (void)clearCallback { cbObject = nil; cbHardware = 0; } - (void)decodeAxisEvent: (JoyAxisEvent *)evt { // Which axis moved? Does the value need to be made to fit a // certain function? Convert axis value to a double. double axisvalue = (double)evt->value; // First check if there is a callback and... if(cbObject && (cbHardware & HW_AXIS)) { // ...then check if axis moved more than AXCBTHRESH - (fix for BUG #17482) if(axisvalue > AXCBTHRESH) { NSDictionary *fnDict = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool: YES], STICK_ISAXIS, [NSNumber numberWithInt: evt->which], STICK_NUMBER, [NSNumber numberWithInt: evt->axis], STICK_AXBUT, nil]; cbHardware = 0; [cbObject performSelector:cbSelector withObject:fnDict]; cbObject = nil; } // we are done. return; } // SDL seems to have some bizarre (perhaps a bug) behaviour when // events get queued up because the game isn't ready to handle // them (perhaps it's loading a commander and initializing the // universe, and the main event loop is blocked). // What happens is SDL lies about the axis that was triggered. For // each queued event it adds 1 to the axis number!! This does // not seem to happen with buttons. int function; if (evt->axis < MAX_AXES) { function = axismap[evt->which][evt->axis]; } else { OOLog(@"decodeAxisEvent", @"Stick axis out of range - axis was %d", evt->axis); return; } switch (function) { case STICK_NOFUNCTION: // do nothing break; case AXIS_THRUST: // Normalize the thrust setting. axstate[function] = (float)(65536 - (axisvalue + 32768)) / 65536; break; case AXIS_ROLL: case AXIS_PITCH: case AXIS_YAW: case AXIS_VIEWX: case AXIS_VIEWY: axstate[function] = axisvalue / STICK_NORMALDIV; break; // TODO AXIS_FIELD_OF_VIEW default: // set the state with no modification. axstate[function] = axisvalue / 32768; } if ((function == AXIS_PITCH) && invertPitch) axstate[function] = -1.0*axstate[function]; } - (void) decodeButtonEvent:(JoyButtonEvent *)evt { BOOL bs = NO; // Is there a callback we need to make? if(cbObject && (cbHardware & HW_BUTTON)) { NSDictionary *fnDict = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool: NO], STICK_ISAXIS, [NSNumber numberWithInt: evt->which], STICK_NUMBER, [NSNumber numberWithInt: evt->button], STICK_AXBUT, nil]; cbHardware = 0; [cbObject performSelector:cbSelector withObject:fnDict]; cbObject = nil; // we are done. return; } // Defensive measure - see comments in the axis handler for why. int function; if (evt->button < MAX_BUTTONS) { function = buttonmap[evt->which][evt->button]; } else { OOLog(@"decodeButtonEvent", @"Joystick button out of range: %d", evt->button); return; } if (evt->type == JOYBUTTONDOWN) { bs = YES; if(function == BUTTON_PRECISION) precisionMode = !precisionMode; } if (function >= 0) { butstate[function]=bs; } } - (void) decodeHatEvent:(JoyHatEvent *)evt { // HACK: handle this as a set of buttons int i; JoyButtonEvent btn; btn.which = evt->which; for (i = 0; i < 4; ++i) { if ((evt->value ^ hatstate[evt->which][evt->hat]) & (1 << i)) { btn.type = (evt->value & (1 << i)) ? JOYBUTTONDOWN : JOYBUTTONUP; btn.button = MAX_REAL_BUTTONS + i + evt->which * 4; btn.state = (evt->value & (1 << i)) ? JOYBUTTON_PRESSED : JOYBUTTON_RELEASED; [self decodeButtonEvent:&btn]; } } hatstate[evt->which][evt->hat] = evt->value; } - (NSUInteger) joystickCount { return 0; } - (void) saveStickSettings { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; [defaults setObject:[self axisFunctions] forKey:AXIS_SETTINGS]; [defaults setObject:[self buttonFunctions] forKey:BUTTON_SETTINGS]; [self saveProfileForAxis: AXIS_ROLL]; [self saveProfileForAxis: AXIS_PITCH]; [self saveProfileForAxis: AXIS_YAW]; [defaults synchronize]; } - (void) loadStickSettings { unsigned i; [self clearMappings]; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSDictionary *axisSettings = [defaults objectForKey: AXIS_SETTINGS]; NSDictionary *buttonSettings = [defaults objectForKey: BUTTON_SETTINGS]; if(axisSettings) { NSArray *keys = [axisSettings allKeys]; for (i = 0; i < [keys count]; i++) { NSString *key = [keys objectAtIndex: i]; [self setFunction: [key intValue] withDict: [axisSettings objectForKey: key]]; } } if(buttonSettings) { NSArray *keys = [buttonSettings allKeys]; for (i = 0; i < [keys count]; i++) { NSString *key = [keys objectAtIndex: i]; [self setFunction:[key intValue] withDict:[buttonSettings objectForKey: key]]; } } else { // Nothing to load - set useful defaults [self setDefaultMapping]; } [self loadProfileForAxis: AXIS_ROLL]; [self loadProfileForAxis: AXIS_PITCH]; [self loadProfileForAxis: AXIS_YAW]; } // These get overidden by subclasses - (NSString *) nameOfJoystick:(NSUInteger)stickNumber { return @"Dummy joystick"; } - (int16_t) getAxisWithStick:(NSUInteger)stickNum axis:(NSUInteger)axisNum { return 0; } @end oolite-1.82/src/Core/OOJoystickProfile.h000066400000000000000000000047271256642440500201670ustar00rootroot00000000000000/* OOJoystickProfile.h JoystickProfile maintains settings such as deadzone and the mapping from joystick movement to response. JoystickSpline manages the mapping of the physical joystick movements to the joystick response. It holds a series of control points, with the points (0,0) and (1,1) being assumed. It then interpolates splines between the set of control points - the segment between (0,0) and the first control point is linear, the remaining segments quadratic with the gradients matching at the control point. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #define STICKPROFILE_TYPE_STANDARD 1 #define STICKPROFILE_TYPE_SPLINE 2 #define STICKPROFILE_MAX_POWER 10.0 @interface OOJoystickAxisProfile : NSObject { @private double deadzone; } - (id) init; - (id) copyWithZone: (NSZone *) zone; - (double) rawValue: (double) x; - (double) value: (double) x; - (double) deadzone; - (void) setDeadzone: (double) newValue; @end @interface OOJoystickStandardAxisProfile: OOJoystickAxisProfile { @private double power; double parameter; } - (id) init; - (id) copyWithZone: (NSZone *) zone; - (void) setPower: (double) newValue; - (double) power; - (void) setParameter: (double) newValue; - (double) parameter; - (double) rawValue: (double) x; @end @interface OOJoystickSplineAxisProfile: OOJoystickAxisProfile { @private NSMutableArray *controlPoints; NSArray *segments; } - (id) init; - (void) dealloc; - (id) copyWithZone: (NSZone *) zone; - (int) addControl: (NSPoint) point; - (NSPoint) pointAtIndex: (NSInteger) index; - (int) countPoints; - (void) removeControl: (NSInteger) index; - (void) clearControlPoints; - (void) moveControl: (NSInteger) index point: (NSPoint) point; - (double) rawValue: (double) x; - (double) gradient: (double) x; - (NSArray *) controlPoints; @end oolite-1.82/src/Core/OOJoystickProfile.m000066400000000000000000000340731256642440500201710ustar00rootroot00000000000000/* OOJoystickProfile.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJoystickManager.h" #import "OOJoystickProfile.h" #import "OOMaths.h" #import "OOLoggingExtended.h" #import "Universe.h" #define SPLINE_POINT_MIN_SPACING 0.02 @interface OOJoystickSplineSegment: NSObject { @private double start; double end; double a[4]; } - (id) init; // Linear spline from left point to right point. Returns nil if right.x - left.x <= 0.0. - (id) initWithData: (NSPoint) left right: (NSPoint) right; // Quadratic spline from left point to right point, with gradient specified at left. returns nil if right.x - left.x <= 0.0. - (id) initWithData: (NSPoint) left right: (NSPoint) right gradientleft: (double) gradientleft; // Quadratic spline from left point to right point, with gradient specified at right. returns nil if right.x - left.x <= 0.0. - (id) initWithData: (NSPoint) left right: (NSPoint) right gradientright: (double) gradientright; // Cubic spline from left point to right point, with gradients specified at end points. returns nil if right.x - left.x <= 0.0. - (id) initWithData: (NSPoint) left right: (NSPoint) right gradientleft: (double) gradientleft gradientright: (double) gradientright; // Linear spline from left point to right point. Returns nil if right.x - left.x <= 0.0. + (id) segmentWithData: (NSPoint) left right: (NSPoint) right; // Quadratic spline from left point to right point, with gradient specified at left. returns nil if right.x - left.x <= 0.0. + (id) segmentWithData: (NSPoint) left right: (NSPoint) right gradientleft: (double) gradientleft; // Quadratic spline from left point to right point, with gradient specified at right. returns nil if right.x - left.x <= 0.0. + (id) segmentWithData: (NSPoint) left right: (NSPoint) right gradientright: (double) gradientright; // Cubic spline from left point to right point, with gradients specified at end points. returns nil if right.x - left.x <= 0.0. + (id) segmentWithData: (NSPoint) left right: (NSPoint) right gradientleft: (double) gradientleft gradientright: (double) gradientright; - (id) copyWithZone: (NSZone *) zone; - (double) start; - (double) end; - (double) value: (double) t; - (double) gradient: (double) t; @end @interface OOJoystickSplineAxisProfile (Private) // Create the segments from the control points. If there's a problem, e.g. control points not in order or overlapping, // leave segments as they are and return NO. Otherwise return YES. - (BOOL) makeSegments; @end @implementation OOJoystickAxisProfile - (id) init { if ((self = [super init])) { deadzone = STICK_DEADZONE; } return self; } - (id) copyWithZone: (NSZone *) zone { OOJoystickAxisProfile *copy = [[[self class] alloc] init]; return copy; } - (double) rawValue: (double) x { return x; } - (double) value: (double) x { if (fabs(x) < deadzone) { return 0.0; } return x < 0 ? -[self rawValue: (-x-deadzone)/(1.0-deadzone)] : [self rawValue: (x-deadzone)/(1.0-deadzone)]; } - (double) deadzone { return deadzone; } - (void) setDeadzone: (double) newValue { deadzone = OOClamp_0_max_d(newValue, STICK_MAX_DEADZONE); } @end @implementation OOJoystickStandardAxisProfile - (id) init { if ((self = [super init])) { power = 1.0; parameter = 1.0; } return self; } - (id) copyWithZone: (NSZone *) zone { OOJoystickStandardAxisProfile *copy = [[[self class] alloc] init]; copy->power = power; copy->parameter = parameter; return copy; } - (void) setPower: (double) newValue { if (newValue < 1.0) { power = 1.0; } else if (newValue > STICKPROFILE_MAX_POWER) { power = STICKPROFILE_MAX_POWER; } else { power = newValue; } return; } - (double) power { return power; } - (void) setParameter: (double) newValue { parameter = OOClamp_0_1_d(newValue); return; } - (double) parameter { return parameter; } - (double) rawValue: (double) x { if (x < 0) { return -OOClamp_0_1_d(parameter * pow(-x,power)-(parameter - 1.0)*(-x)); } return OOClamp_0_1_d(parameter * pow(x,power)-(parameter - 1.0)*(x)); } @end @implementation OOJoystickSplineSegment - (id) init { if ((self = [super init])) { start = 0.0; end = 1.0; a[0] = 0.0; a[1] = 1.0; a[2] = 0.0; a[3] = 0.0; } return self; } - (id) copyWithZone: (NSZone *) zone { OOJoystickSplineSegment *copy = [[OOJoystickSplineSegment allocWithZone: zone] init]; copy->start = start; copy->end = end; copy->a[0] = a[0]; copy->a[1] = a[1]; copy->a[2] = a[2]; copy->a[3] = a[3]; return copy; } - (id) initWithData: (NSPoint) left right: (NSPoint) right { double dx = right.x - left.x; if (dx <= 0.0) { return nil; } if ((self = [super init])) { start = left.x; end = right.x; a[1] = (right.y - left.y)/dx; a[0] = left.y-a[1]*left.x; a[2] = 0.0; a[3] = 0.0; } return self; } - (id) initWithData:(NSPoint) left right: (NSPoint) right gradientleft: (double) gradientleft { double dx = right.x - left.x; if (dx <= 0.0) { return nil; } if ((self = [super init])) { start = left.x; end = right.x; a[0] = left.y*right.x*(right.x - 2*left.x)/(dx*dx) + right.y*left.x*left.x/(dx*dx) - gradientleft*left.x*right.x/dx; a[1] = 2*left.x*(left.y-right.y)/(dx*dx) + gradientleft*(left.x+right.x)/dx; a[2] = (right.y-left.y)/(dx*dx) - gradientleft/dx; } return self; } - (id) initWithData: (NSPoint) left right: (NSPoint) right gradientright: (double) gradientright { double dx = right.x - left.x; if (dx <= 0.0) { return nil; } if ((self = [super init])) { start = left.x; end = right.x; a[0] = (left.y*right.x*right.x + right.y*left.x*(left.x-2*right.x))/(dx*dx) + gradientright*left.x*right.x/dx; a[1] = 2*right.x*(right.y-left.y)/(dx*dx) - gradientright*(left.x+right.x)/dx; a[2] = (left.y-right.y)/(dx*dx) + gradientright/dx; } return self; } - (id) initWithData: (NSPoint) left right: (NSPoint) right gradientleft: (double) gradientleft gradientright: (double) gradientright { double dx = right.x - left.x; if (dx <= 0.0) { return nil; } if ((self = [super init])) { start = left.x; end = right.x; a[0] = (left.y*right.x*right.x*(right.x-3*left.x) - right.y*left.x*left.x*(left.x-3*right.x))/(dx*dx*dx) - (gradientleft*right.x + gradientright*left.x)*left.x*right.x/(dx*dx); a[1] = 6*left.x*right.x*(left.y-right.y)/(dx*dx*dx) + (gradientleft*right.x*(right.x+2*left.x) + gradientright*left.x*(left.x+2*right.x))/(dx*dx); a[2] = 3*(left.x+right.x)*(right.y-left.y)/(dx*dx*dx) - (gradientleft*(2*right.x+left.x)+gradientright*(2*left.x+right.x))/(dx*dx); a[3] = 2*(left.y-right.y)/(dx*dx*dx) + (gradientleft+gradientright)/(dx*dx); } return self; } + (id) segmentWithData: (NSPoint) left right: (NSPoint) right { OOJoystickSplineSegment *segment = [[OOJoystickSplineSegment alloc] initWithData: left right:right]; return [segment autorelease]; } + (id) segmentWithData: (NSPoint) left right: (NSPoint) right gradientleft: (double) gradientleft { OOJoystickSplineSegment *segment = [[OOJoystickSplineSegment alloc] initWithData: left right:right gradientleft:gradientleft]; return [segment autorelease]; } + (id) segmentWithData: (NSPoint) left right: (NSPoint) right gradientright: (double) gradientright { OOJoystickSplineSegment *segment = [[OOJoystickSplineSegment alloc] initWithData: left right:right gradientright:gradientright]; return [segment autorelease]; } + (id) segmentWithData: (NSPoint) left right: (NSPoint) right gradientleft: (double) gradientleft gradientright: (double) gradientright { OOJoystickSplineSegment *segment = [[OOJoystickSplineSegment alloc] initWithData: left right:right gradientleft:gradientleft gradientright:gradientright]; return [segment autorelease]; } - (double) start { return start; } - (double) end { return end; } - (double) value: (double) x { return a[0] + (a[1] + (a[2] + a[3]*x)*x)*x; } - (double) gradient: (double) x { return a[1]+(2*a[2] + 3*a[3]*x)*x; } @end @implementation OOJoystickSplineAxisProfile - (id) init { if ((self = [super init])) { controlPoints = [[NSMutableArray alloc] initWithCapacity: 2]; segments = nil; [self makeSegments]; } return self; } - (void) dealloc { [controlPoints release]; [segments release]; [super dealloc]; return; } - (id) copyWithZone: (NSZone *) zone { OOJoystickSplineAxisProfile *copy = [[[self class] alloc] init]; copy->controlPoints = [controlPoints copyWithZone: zone]; copy->segments = [segments copyWithZone: zone]; return copy; } - (int) addControl: (NSPoint) point { NSPoint left, right; NSUInteger i; if (point.x <= SPLINE_POINT_MIN_SPACING || point.x >= 1 - SPLINE_POINT_MIN_SPACING ) { return -1; } left.x = 0.0; left.y = 0.0; for (i = 0; i <= [controlPoints count]; i++ ) { if (i < [controlPoints count]) { right = [[controlPoints objectAtIndex: i] pointValue]; } else { right = NSMakePoint(1.0,1.0); } if ((point.x - left.x) < SPLINE_POINT_MIN_SPACING) { if (i == 0) { return -1; } [controlPoints replaceObjectAtIndex: i - 1 withObject: [NSValue valueWithPoint: point]]; [self makeSegments]; return i - 1; } if ((right.x - point.x) >= SPLINE_POINT_MIN_SPACING) { [controlPoints insertObject: [NSValue valueWithPoint: point] atIndex: i]; [self makeSegments]; return i; } left = right; } return -1; } - (NSPoint) pointAtIndex: (NSInteger) index { NSPoint point; if (index < 0) { point.x = 0.0; point.y = 0.0; } else if (index >= (NSInteger)[controlPoints count]) { point.x = 1.0; point.y = 1.0; } else { point = [[controlPoints objectAtIndex: index] pointValue]; } return point; } - (int) countPoints { return [controlPoints count]; } - (NSArray *) controlPoints { return [NSArray arrayWithArray: controlPoints]; } // Calculate segments from control points - (BOOL) makeSegments { NSUInteger i; NSPoint left, right, next; double gradientleft, gradientright; OOJoystickSplineSegment* segment; BOOL first_segment = YES; NSMutableArray *new_segments = [NSMutableArray arrayWithCapacity: ([controlPoints count] + 1)]; left.x = 0.0; left.y = 0.0; if ([controlPoints count] == 0) { right.x = 1.0; right.y = 1.0; segment = [OOJoystickSplineSegment segmentWithData: left right: right]; [new_segments addObject:segment]; } else { gradientleft = 1.0; right = [[controlPoints objectAtIndex: 0] pointValue]; for (i = 0; i < [controlPoints count]; i++) { next = [self pointAtIndex: i + 1]; if (next.x - left.x > 0.0) { // we make the gradient at right equal to the gradient of a straight line between the neighcouring points gradientright = (next.y - left.y)/(next.x - left.x); if (first_segment) { segment = [OOJoystickSplineSegment segmentWithData: left right: right gradientright: gradientright]; } else { segment = [OOJoystickSplineSegment segmentWithData: left right: right gradientleft: gradientleft gradientright: gradientright]; } if (segment == nil) { return NO; } else { [new_segments addObject: segment]; gradientleft = gradientright; first_segment = NO; left = right; } } right = next; } right.x = 1.0; right.y = 1.0; segment = [OOJoystickSplineSegment segmentWithData: left right: right gradientleft: gradientleft]; if (segment == nil) { return NO; } [new_segments addObject: segment]; } [segments release]; segments = [[NSArray arrayWithArray: new_segments] retain]; return YES; } - (void) removeControl: (NSInteger) index { if (index >= 0 && index < (NSInteger)[controlPoints count]) { [controlPoints removeObjectAtIndex: index]; [self makeSegments]; } return; } - (void) clearControlPoints { [controlPoints removeAllObjects]; [self makeSegments]; } - (void) moveControl: (NSInteger) index point: (NSPoint) point { NSPoint left, right; point.x = OOClamp_0_1_d(point.x); point.y = OOClamp_0_1_d(point.y); if (index < 0 || index >= (NSInteger)[controlPoints count]) { return; } if (index == 0) { left.x = 0.0; right.x = 0.0; } else { left = [[controlPoints objectAtIndex: (index-1)] pointValue]; } if (index == (NSInteger)[controlPoints count] - 1) { right.x = 1.0; right.y = 1.0; } else { right = [[controlPoints objectAtIndex: (index+1)] pointValue]; } // preserve order of control points - if we attempt to move this control point beyond // either of its neighbours, move it back inside. Also keep neighbours a distance of at least SPLINE_POINT_MIN_SPACING apart if (point.x - left.x < SPLINE_POINT_MIN_SPACING) { point.x = left.x + SPLINE_POINT_MIN_SPACING; if (right.x - point.x < SPLINE_POINT_MIN_SPACING) { point.x = (left.x + right.x)/2; } } else if (right.x - point.x < SPLINE_POINT_MIN_SPACING) { point.x = right.x - SPLINE_POINT_MIN_SPACING; if (point.x - left.x < SPLINE_POINT_MIN_SPACING) { point.x = (left.x + right.x)/2; } } [controlPoints replaceObjectAtIndex: index withObject: [NSValue valueWithPoint: point]]; [self makeSegments]; return; } - (double) rawValue: (double) x { NSUInteger i; OOJoystickSplineSegment *segment; double sign; if (x < 0) { sign = -1.0; x = -x; } else { sign = 1.0; } for (i = 0; i < [segments count]; i++) { segment = [segments objectAtIndex: i]; if ([segment end] > x) { return sign * OOClamp_0_1_d([segment value:x]); } } return 1.0; } - (double) gradient: (double) x { NSUInteger i; OOJoystickSplineSegment *segment; for (i = 0; i < [segments count]; i++) { segment = [segments objectAtIndex: i]; if ([segment end] > x) { return [segment gradient:x]; } } return 1.0; } @end oolite-1.82/src/Core/OOLogHeader.h000066400000000000000000000024421256642440500166710ustar00rootroot00000000000000/* OOLogHeader.h Print system info to log, to make us less reliant on users actually telling us stuff. Called from OOLoggingInit() to ensure it appears near the top of the log. Copyright (C) 2008-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" void OOPrintLogHeader(void); oolite-1.82/src/Core/OOLogHeader.m000066400000000000000000000225611256642440500167020ustar00rootroot00000000000000/* OOLogHeader.m Copyright (C) 2007-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOLogHeader.h" #import "OOCPUInfo.h" #import "OOLogging.h" #import "OOOXPVerifier.h" #import "Universe.h" #import "OOStellarBody.h" #import "OOJavaScriptEngine.h" #import "OOSound.h" static NSString *AdditionalLogHeaderInfo(void); NSString *OOPlatformDescription(void); #ifdef ALLOW_PROCEDURAL_PLANETS #warning ALLOW_PROCEDURAL_PLANETS is no longer optional and the macro should no longer be defined. #endif #ifdef DOCKING_CLEARANCE_ENABLED #warning DOCKING_CLEARANCE_ENABLED is no longer optional and the macro should no longer be defined. #endif #ifdef WORMHOLE_SCANNER #warning WORMHOLE_SCANNER is no longer optional and the macro should no longer be defined. #endif #ifdef TARGET_INCOMING_MISSILES #warning TARGET_INCOMING_MISSILES is no longer optional and the macro should no longer be defined. #endif void OOPrintLogHeader(void) { // Bunch of string literal macros which are assembled into a CPU info string. #if defined (__ppc__) #define CPU_TYPE_STRING "PPC-32" #elif defined (__ppc64__) #define CPU_TYPE_STRING "PPC-64" #elif defined (__i386__) #define CPU_TYPE_STRING "x86-32" #elif defined (__x86_64__) #define CPU_TYPE_STRING "x86-64" #else #if OOLITE_BIG_ENDIAN #define CPU_TYPE_STRING "" #elif OOLITE_LITTLE_ENDIAN #define CPU_TYPE_STRING "" #else #define CPU_TYPE_STRING "" #endif #endif #if OOLITE_MAC_OS_X #define OS_TYPE_STRING "Mac OS X" #elif OOLITE_WINDOWS #define OS_TYPE_STRING "Windows" #elif OOLITE_LINUX #define OS_TYPE_STRING "Linux" // Hmm, what about other unices? #elif OOLITE_SDL #define OS_TYPE_STRING "unknown SDL system" #else #define OS_TYPE_STRING "unknown system" #endif #if OO_DEBUG #define RELEASE_VARIANT_STRING " debug" #elif !defined (NDEBUG) #define RELEASE_VARIANT_STRING " test release" #else #define RELEASE_VARIANT_STRING "" #endif NSArray *featureStrings = [NSArray arrayWithObjects: // User features #if OOLITE_OPENAL @"OpenAL", #endif #if OO_SHADERS @"GLSL shaders", #endif #if NEW_PLANETS @"new planets", #endif // Debug features #if OO_CHECK_GL_HEAVY @"heavy OpenGL error checking", #endif #ifndef OO_EXCLUDE_DEBUG_SUPPORT @"JavaScript console support", #if OOLITE_MAC_OS_X // Under Mac OS X, Debug.oxp adds more than console support. @"Debug plug-in support", #endif #endif #if OO_OXP_VERIFIER_ENABLED @"OXP verifier", #endif #if OO_LOCALIZATION_TOOLS @"localization tools", #endif #if DEBUG_GRAPHVIZ @"debug GraphViz support", #endif #if OOJS_PROFILE #ifdef MOZ_TRACE_JSCALLS @"JavaScript profiling", #else @"JavaScript native callback profiling", #endif #endif #if OO_FOV_INFLIGHT_CONTROL_ENABLED @"FOV in-flight control", #endif nil]; // systemString: NSString with system type and possibly version. #if (OOLITE_MAC_OS_X || (OOLITE_GNUSTEP_1_20 && !OOLITE_WINDOWS)) NSString *systemString = [NSString stringWithFormat:@OS_TYPE_STRING " %@", [[NSProcessInfo processInfo] operatingSystemVersionString]]; #elif OOLITE_WINDOWS NSString *systemString = [NSString stringWithFormat:@OS_TYPE_STRING " %@ %@-bit", operatingSystemFullVersion(), is64BitSystem() ? @"64":@"32"]; #else #define systemString @OS_TYPE_STRING #endif NSString *versionString = nil; #if (defined (SNAPSHOT_BUILD) && defined (OOLITE_SNAPSHOT_VERSION)) versionString = @"development version " OOLITE_SNAPSHOT_VERSION; #else versionString = [NSString stringWithFormat:@"version %@", [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]]; #endif if (versionString == nil) versionString = @""; NSMutableString *miscString = [NSMutableString stringWithFormat:@"Opening log for Oolite %@ (" CPU_TYPE_STRING RELEASE_VARIANT_STRING ") under %@ at %@.\n", versionString, systemString, [NSDate date]]; [miscString appendString:AdditionalLogHeaderInfo()]; NSString *featureDesc = [featureStrings componentsJoinedByString:@", "]; if ([featureDesc length] == 0) featureDesc = @"none"; [miscString appendFormat:@"\nBuild options: %@.\n", featureDesc]; [miscString appendString:@"\nNote that the contents of the log file can be adjusted by editing logcontrol.plist."]; OOLog(@"log.header", @"%@\n", miscString); } NSString *OOPlatformDescription(void) { #if OOLITE_MAC_OS_X NSString *systemString = [NSString stringWithFormat:@OS_TYPE_STRING " %@", [[NSProcessInfo processInfo] operatingSystemVersionString]]; #else #define systemString @OS_TYPE_STRING #endif return [NSString stringWithFormat:@"%@ ("CPU_TYPE_STRING RELEASE_VARIANT_STRING")", systemString]; } // System-specific stuff to append to log header. #if OOLITE_MAC_OS_X #include static NSString *GetSysCtlString(const char *name); static unsigned long long GetSysCtlInt(const char *name); static NSString *GetCPUDescription(void); static NSString *AdditionalLogHeaderInfo(void) { NSString *sysModel = nil; unsigned long long sysPhysMem; sysModel = GetSysCtlString("hw.model"); sysPhysMem = GetSysCtlInt("hw.memsize"); return [NSString stringWithFormat:@"Machine type: %@, %llu MiB memory, %@.", sysModel, sysPhysMem >> 20, GetCPUDescription()]; } #ifndef CPUFAMILY_INTEL_HASWELL #define CPUFAMILY_INTEL_HASWELL 0x10b282dc #endif static NSString *GetCPUDescription(void) { NSString *typeStr = nil, *subTypeStr = nil; unsigned long long sysCPUType = GetSysCtlInt("hw.cputype"); unsigned long long sysCPUFamily = GetSysCtlInt("hw.cpufamily"); unsigned long long sysCPUFrequency = GetSysCtlInt("hw.cpufrequency"); unsigned long long sysCPUCount = GetSysCtlInt("hw.physicalcpu"); unsigned long long sysLogicalCPUCount = GetSysCtlInt("hw.logicalcpu"); /* Note: CPU_TYPE_STRING tells us the build architecture. This gets the physical CPU type. They may differ, for instance, when running under Rosetta. The code is written for flexibility, although ruling out x86 code running on PPC would be entirely reasonable. */ switch (sysCPUType) { case CPU_TYPE_POWERPC: typeStr = @"PowerPC"; break; case CPU_TYPE_I386: typeStr = @"x86"; switch (sysCPUFamily) { case CPUFAMILY_INTEL_MEROM: subTypeStr = @" (Core 2/Merom)"; break; case CPUFAMILY_INTEL_PENRYN: subTypeStr = @" (Penryn)"; break; case CPUFAMILY_INTEL_NEHALEM: subTypeStr = @" (Nehalem)"; break; case CPUFAMILY_INTEL_WESTMERE: subTypeStr = @" (Westmere)"; break; case CPUFAMILY_INTEL_SANDYBRIDGE: subTypeStr = @" (Sandy Bridge)"; break; case CPUFAMILY_INTEL_IVYBRIDGE: subTypeStr = @" (Ivy Bridge)"; break; case CPUFAMILY_INTEL_HASWELL: subTypeStr = @" (Haswell)"; break; default: subTypeStr = [NSString stringWithFormat:@" (family 0x%llx)", sysCPUFamily]; } break; case CPU_TYPE_ARM: typeStr = @"ARM"; } if (typeStr == nil) typeStr = [NSString stringWithFormat:@"CPU type %llu", sysCPUType]; NSString *countStr = nil; if (sysCPUCount == sysLogicalCPUCount) countStr = [NSString stringWithFormat:@"%llu", sysCPUCount]; else countStr = [NSString stringWithFormat:@"%llu (%llu logical)", sysCPUCount, sysLogicalCPUCount]; return [NSString stringWithFormat:@"%@ x %@%@ @ %llu MHz", countStr, typeStr, subTypeStr, (sysCPUFrequency + 500000) / 1000000]; } static NSString *GetSysCtlString(const char *name) { char *buffer = NULL; size_t size = 0; // Get size sysctlbyname(name, NULL, &size, NULL, 0); if (size == 0) return nil; buffer = alloca(size); if (sysctlbyname(name, buffer, &size, NULL, 0) != 0) return nil; return [NSString stringWithUTF8String:buffer]; } static unsigned long long GetSysCtlInt(const char *name) { unsigned long long llresult = 0; unsigned int intresult = 0; size_t size; size = sizeof llresult; if (sysctlbyname(name, &llresult, &size, NULL, 0) != 0) return 0; if (size == sizeof llresult) return llresult; size = sizeof intresult; if (sysctlbyname(name, &intresult, &size, NULL, 0) != 0) return 0; if (size == sizeof intresult) return intresult; return 0; } #else static NSString *AdditionalLogHeaderInfo(void) { unsigned cpuCount = OOCPUCount(); return [NSString stringWithFormat:@"%u processor%@ detected.", cpuCount, cpuCount != 1 ? @"s" : @""]; } #endif oolite-1.82/src/Core/OOLogOutputHandler.h000066400000000000000000000040401256642440500202730ustar00rootroot00000000000000/* OOLogOutputHandler.h By Jens Ayton Output handler for OOLogging. This does two things: 1. It writes log output to ~/Logs/Oolite/Latest.log under Mac OS X or ~/.Oolite/Logs/Latest.log under Linux, handling thread serialization. 2. It installs a filter to capture NSLogs and convert them to OOLogs. This is different to the macro in OOLogging.h, which acts at compile time; the filter catches logging in included frameworks. OOLogOutputHandlerPrint() is thread-safe. Other functions are not. Copyright (C) 2007-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import void OOLogOutputHandlerInit(void); void OOLogOutputHandlerClose(void); void OOLogOutputHandlerPrint(NSString *string); // This will attempt to ensure the containing directory exists. If it fails, it will return nil. NSString *OOLogHandlerGetLogPath(void); NSString *OOLogHandlerGetLogBasePath(void); void OOLogOutputHandlerChangeLogFile(NSString *newLogName); void OOLogOutputHandlerStartLoggingToStdout(); void OOLogOutputHandlerStopLoggingToStdout(); oolite-1.82/src/Core/OOLogOutputHandler.m000066400000000000000000000427051256642440500203120ustar00rootroot00000000000000/* OOLogOutputHandler.m By Jens Ayton Copyright (C) 2007-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #define OOLOG_POISON_NSLOG 0 #import "OOLogOutputHandler.h" #import "OOLogging.h" #import "OOAsyncQueue.h" #include #include #import "NSThreadOOExtensions.h" #import "NSFileManagerOOExtensions.h" #undef NSLog // We need to be able to call the real NSLog. #if OOLITE_MAC_OS_X #include #ifndef NDEBUG #define SET_CRASH_REPORTER_INFO 1 // Function to set "Application Specific Information" field in crash reporter log in Leopard. // Extremely unsupported, so not used in release builds. static void InitCrashReporterInfo(void); static void SetCrashReporterInfo(const char *info); static BOOL sCrashReporterInfoAvailable = NO; #endif typedef void (*LogCStringFunctionProc)(const char *string, unsigned length, BOOL withSyslogBanner); typedef LogCStringFunctionProc (*LogCStringFunctionGetterProc)(void); typedef void (*LogCStringFunctionSetterProc)(LogCStringFunctionProc); static LogCStringFunctionGetterProc _NSLogCStringFunction = NULL; static LogCStringFunctionSetterProc _NSSetLogCStringFunction = NULL; static void LoadLogCStringFunctions(void); static void OONSLogCStringFunction(const char *string, unsigned length, BOOL withSyslogBanner); static NSString *GetAppName(void); static LogCStringFunctionProc sDefaultLogCStringFunction = NULL; #elif OOLITE_GNUSTEP static void OONSLogPrintfHandler(NSString *message); #else #error Unknown platform! #endif static BOOL DirectoryExistCreatingIfNecessary(NSString *path); #define kFlushInterval 2.0 // Lower bound on interval between explicit log file flushes. @interface OOAsyncLogger: NSObject { @private OOAsyncQueue *messageQueue; NSConditionLock *threadStateMonitor; NSFileHandle *logFile; NSTimer *flushTimer; } - (void)asyncLogMessage:(NSString *)message; - (void)endLogging; - (void)changeFile; // Internal - (BOOL)startLogging; - (void)loggerThread; - (void)flushLog; @end static BOOL sInited = NO; static BOOL sWriteToStderr = YES; static BOOL sWriteToStdout = NO; static BOOL sSaturated = NO; static OOAsyncLogger *sLogger = nil; static NSString *sLogFileName = @"Latest.log"; void OOLogOutputHandlerInit(void) { if (sInited) return; #if SET_CRASH_REPORTER_INFO InitCrashReporterInfo(); #endif sLogger = [[OOAsyncLogger alloc] init]; sInited = YES; if (sLogger != nil) { sWriteToStderr = [[NSUserDefaults standardUserDefaults] boolForKey:@"logging-echo-to-stderr"]; } else { sWriteToStderr = YES; } #if OOLITE_MAC_OS_X LoadLogCStringFunctions(); if (_NSSetLogCStringFunction != NULL) { sDefaultLogCStringFunction = _NSLogCStringFunction(); _NSSetLogCStringFunction(OONSLogCStringFunction); } else { OOLog(@"logging.nsLogFilter.install.failed", @"Failed to install NSLog() filter; system messages will not be logged in log file."); } #elif GNUSTEP NSRecursiveLock *lock = GSLogLock(); [lock lock]; _NSLog_printf_handler = OONSLogPrintfHandler; [lock unlock]; #endif atexit(OOLogOutputHandlerClose); } void OOLogOutputHandlerClose(void) { if (sInited) { sWriteToStderr = YES; sInited = NO; [sLogger endLogging]; DESTROY(sLogger); #if OOLITE_MAC_OS_X if (sDefaultLogCStringFunction != NULL && _NSSetLogCStringFunction != NULL) { _NSSetLogCStringFunction(sDefaultLogCStringFunction); sDefaultLogCStringFunction = NULL; } #elif GNUSTEP NSRecursiveLock *lock = GSLogLock(); [lock lock]; _NSLog_printf_handler = NULL; [lock unlock]; #endif } } void OOLogOutputHandlerStartLoggingToStdout() { sWriteToStdout = true; } void OOLogOutputHandlerStopLoggingToStdout() { sWriteToStdout = false; } void OOLogOutputHandlerPrint(NSString *string) { if (sInited && sLogger != nil && !sWriteToStdout) [sLogger asyncLogMessage:string]; BOOL doCStringStuff = sWriteToStderr || sWriteToStdout; #if SET_CRASH_REPORTER_INFO doCStringStuff = doCStringStuff || sCrashReporterInfoAvailable; #endif if (doCStringStuff) { const char *cStr = [[string stringByAppendingString:@"\n"] UTF8String]; if (sWriteToStdout) fputs(cStr, stdout); else if (sWriteToStderr) fputs(cStr, stderr); #if SET_CRASH_REPORTER_INFO if (sCrashReporterInfoAvailable) SetCrashReporterInfo(cStr); #endif } } NSString *OOLogHandlerGetLogPath(void) { return [OOLogHandlerGetLogBasePath() stringByAppendingPathComponent:sLogFileName]; } void OOLogOutputHandlerChangeLogFile(NSString *newLogName) { if (![sLogFileName isEqual:newLogName]) { sLogFileName = [newLogName copy]; [sLogger changeFile]; } } enum { kConditionReadyToDealloc = 1, kConditionWorking }; @implementation OOAsyncLogger - (id)init { BOOL OK = YES; NSString *logPath = nil; NSString *oldPath = nil; NSFileManager *fmgr = nil; self = [super init]; if (self == nil) OK = NO; if (OK) { fmgr = [NSFileManager defaultManager]; logPath = OOLogHandlerGetLogPath(); // If there is an existing file, move it to Previous.log. if ([fmgr fileExistsAtPath:logPath]) { oldPath = [OOLogHandlerGetLogBasePath() stringByAppendingPathComponent:@"Previous.log"]; [fmgr oo_removeItemAtPath:oldPath]; if (![fmgr oo_moveItemAtPath:logPath toPath:oldPath]) { if (![fmgr oo_removeItemAtPath:logPath]) { NSLog(@"Log setup: could not move or delete existing log at %@, will log to stdout instead.", logPath); OK = NO; } } } } if (OK) OK = [self startLogging]; if (!OK) DESTROY(self); return self; } - (void)dealloc { DESTROY(messageQueue); DESTROY(threadStateMonitor); DESTROY(logFile); // We don't own a reference to flushTimer. [super dealloc]; } - (BOOL)startLogging { BOOL OK = YES; NSString *logPath = nil; NSFileManager *fmgr = nil; fmgr = [NSFileManager defaultManager]; if (OK) { messageQueue = [[OOAsyncQueue alloc] init]; if (messageQueue == nil) OK = NO; } if (OK) { // set up threadStateMonitor -- used as a binary semaphore of sorts to check when the worker thread starts and stops. threadStateMonitor = [[NSConditionLock alloc] initWithCondition:kConditionReadyToDealloc]; if (threadStateMonitor == nil) OK = NO; [threadStateMonitor setName:@"OOLogOutputHandler thread state monitor"]; } if (OK) { // Create work thread to actually handle messages. // This needs to be done early to avoid messy state if something goes wrong. [NSThread detachNewThreadSelector:@selector(loggerThread) toTarget:self withObject:nil]; // Wait for it to start. if (![threadStateMonitor lockWhenCondition:kConditionWorking beforeDate:[NSDate dateWithTimeIntervalSinceNow:5.0]]) { // If it doesn't signal a start within five seconds, assume something's wrong. // Send kill signal, just in case it comes to life... [messageQueue enqueue:@"die"]; // ...and stop -dealloc from waiting for thread death [threadStateMonitor release]; threadStateMonitor = nil; OK = NO; } [threadStateMonitor unlockWithCondition:kConditionWorking]; } if (OK) { logPath = OOLogHandlerGetLogPath(); OK = (logPath != nil); } if (OK) { // Create shiny new log file OK = [fmgr createFileAtPath:logPath contents:nil attributes:nil]; if (OK) { logFile = [[NSFileHandle fileHandleForWritingAtPath:logPath] retain]; OK = (logFile != nil); } if (!OK) { NSLog(@"Log setup: could not open log at %@, will log to stdout instead.", logPath); OK = NO; } } return OK; } - (void)endLogging { NSString *postamble = nil; if (messageQueue != nil && threadStateMonitor != nil) { // We're fully inited; write postamble, wait for worker thread to terminate cleanly, and close file. postamble = [NSString stringWithFormat:@"\nClosing log at %@.", [NSDate date]]; [self asyncLogMessage:postamble]; [messageQueue enqueue:@"die"]; // Kill message [threadStateMonitor lockWhenCondition:kConditionReadyToDealloc]; [threadStateMonitor unlock]; [logFile closeFile]; } } - (void)changeFile { [self endLogging]; if (![self startLogging]) sWriteToStderr = YES; } - (void)asyncLogMessage:(NSString *)message { // Don't log of saturated flag is set. if (sSaturated) return; if (message != nil) { message = [message stringByAppendingString:@"\n"]; #if OOLITE_WINDOWS // Convert Unix line endings to Windows ones. NSArray *messageComponents = [message componentsSeparatedByString:@"\n"]; message = [messageComponents componentsJoinedByString:@"\r\n"]; #endif [messageQueue enqueue:[message dataUsingEncoding:NSUTF8StringEncoding]]; if (flushTimer == nil) { // No pending flush flushTimer = [NSTimer scheduledTimerWithTimeInterval:kFlushInterval target:self selector:@selector(flushLog) userInfo:nil repeats:NO]; } } } - (void)flushLog { flushTimer = nil; [messageQueue enqueue:@"flush"]; } - (void)loggerThread { id message = nil; NSAutoreleasePool *rootPool = nil, *pool = nil; NSUInteger size = 0; rootPool = [[NSAutoreleasePool alloc] init]; [NSThread ooSetCurrentThreadName:@"OOLogOutputHandler logging thread"]; // Signal readiness [messageQueue retain]; [threadStateMonitor lock]; [threadStateMonitor unlockWithCondition:kConditionWorking]; @try { for (;;) { pool = [[NSAutoreleasePool alloc] init]; message = [messageQueue dequeue]; if (!sSaturated && [message isKindOfClass:[NSData class]]) { size += [message length]; if (size > 1 << 30) // 1 GiB { sSaturated = YES; #if OOLITE_WINDOWS message = @"\r\n\r\n\r\n***** LOG TRUNCATED DUE TO EXCESSIVE LENGTH *****\r\n"; #else message = @"\n\n\n***** LOG TRUNCATED DUE TO EXCESSIVE LENGTH *****\n"; #endif message = [message dataUsingEncoding:NSUTF8StringEncoding]; } [logFile writeData:message]; } else if ([message isEqual:@"flush"]) { [logFile synchronizeFile]; } else if ([message isEqual:@"die"]) { break; } [pool release]; } } @catch (NSException *exception) {} [pool release]; // Clean up; after this, ivars are out of bounds. [messageQueue release]; [threadStateMonitor lock]; [threadStateMonitor unlockWithCondition:kConditionReadyToDealloc]; [rootPool release]; } @end #if OOLITE_MAC_OS_X /* LoadLogCStringFunctions() We wish to make NSLogv() call our custom function OONSLogCStringFunction() rather than printing to stdout, by calling _NSSetLogCStringFunction(). Additionally, in order to close the logger cleanly, we wish to be able to restore the standard logger, which requires us to call _NSLogCStringFunction(). These functions are private. _NSLogCStringFunction() is undocumented. _NSSetLogCStringFunction() is documented at http://docs.info.apple.com/article.html?artnum=70081 , with the warning: Be aware that this code references private APIs; this is an unsupported workaround and users should use these instructions at their own risk. Apple will not guarantee or provide support for this procedure. The approach taken here is to load the functions dynamically. This makes us safe in the case of Apple removing the functions. In the unlikely event that they change the functions' paramters without renaming them, we would have a problem. */ static void LoadLogCStringFunctions(void) { CFBundleRef foundationBundle = NULL; LogCStringFunctionGetterProc getter = NULL; LogCStringFunctionSetterProc setter = NULL; foundationBundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.Foundation")); if (foundationBundle != NULL) { getter = CFBundleGetFunctionPointerForName(foundationBundle, CFSTR("_NSLogCStringFunction")); setter = CFBundleGetFunctionPointerForName(foundationBundle, CFSTR("_NSSetLogCStringFunction")); if (getter != NULL && setter != NULL) { _NSLogCStringFunction = getter; _NSSetLogCStringFunction = setter; } } } static void OONSLogCStringFunction(const char *string, unsigned length, BOOL withSyslogBanner) { if (OOLogWillDisplayMessagesInClass(@"system")) { OOLogWithFunctionFileAndLine(@"system", NULL, NULL, 0, @"%s", string); } } #elif OOLITE_GNUSTEP static void OONSLogPrintfHandler(NSString *message) { if (OOLogWillDisplayMessagesInClass(@"gnustep")) { OOLogWithFunctionFileAndLine(@"gnustep", NULL, NULL, 0, @"%@", message); } } #endif static BOOL DirectoryExistCreatingIfNecessary(NSString *path) { BOOL exists, directory; NSFileManager *fmgr = [NSFileManager defaultManager]; exists = [fmgr fileExistsAtPath:path isDirectory:&directory]; if (exists && !directory) { NSLog(@"Log setup: expected %@ to be a folder, but it is a file.", path); return NO; } if (!exists) { if (![fmgr oo_createDirectoryAtPath:path attributes:nil]) { NSLog(@"Log setup: could not create folder %@.", path); return NO; } } return YES; } #if OOLITE_MAC_OS_X static void ExcludeFromTimeMachine(NSString *path); NSString *OOLogHandlerGetLogBasePath(void) { static NSString *basePath = nil; if (basePath == nil) { // ~/Library basePath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]; // ~/Library/Logs basePath = [basePath stringByAppendingPathComponent:@"Logs"]; if (!DirectoryExistCreatingIfNecessary(basePath)) return nil; // ~/Library/Logs/Oolite basePath = [basePath stringByAppendingPathComponent:GetAppName()]; if (!DirectoryExistCreatingIfNecessary(basePath)) return nil; ExcludeFromTimeMachine(basePath); [basePath retain]; } return basePath; } static void ExcludeFromTimeMachine(NSString *path) { OSStatus (*CSBackupSetItemExcluded)(NSURL *item, Boolean exclude, Boolean excludeByPath) = NULL; CFBundleRef carbonCoreBundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.CoreServices.CarbonCore")); if (carbonCoreBundle) { CSBackupSetItemExcluded = CFBundleGetFunctionPointerForName(carbonCoreBundle, CFSTR("CSBackupSetItemExcluded")); if (CSBackupSetItemExcluded != NULL) { (void)CSBackupSetItemExcluded([NSURL fileURLWithPath:path], YES, NO); } } } static NSString *GetAppName(void) { static NSString *appName = nil; NSBundle *bundle = nil; if (appName == nil) { bundle = [NSBundle mainBundle]; appName = [bundle objectForInfoDictionaryKey:@"CFBundleName"]; if (appName == nil) appName = [bundle bundleIdentifier]; if (appName == nil) appName = @""; [appName retain]; } return appName; } #elif OOLITE_LINUX NSString *OOLogHandlerGetLogBasePath(void) { static NSString *basePath = nil; if (basePath == nil) { // ~ basePath = NSHomeDirectory(); // ~/.Oolite basePath = [basePath stringByAppendingPathComponent:@".Oolite"]; if (!DirectoryExistCreatingIfNecessary(basePath)) return nil; // ~/.Oolite/Logs basePath = [basePath stringByAppendingPathComponent:@"Logs"]; if (!DirectoryExistCreatingIfNecessary(basePath)) return nil; [basePath retain]; } return basePath; } #elif OOLITE_WINDOWS NSString *OOLogHandlerGetLogBasePath(void) { static NSString *basePath = nil; if (basePath == nil) { // \Oolite basePath = NSHomeDirectory(); // \Oolite\Logs basePath = [basePath stringByAppendingPathComponent:@"Logs"]; if (!DirectoryExistCreatingIfNecessary(basePath)) return nil; [basePath retain]; } return basePath; } #endif #if SET_CRASH_REPORTER_INFO static char **sCrashReporterInfo = NULL; static char *sOldCrashReporterInfo = NULL; static NSLock *sCrashReporterInfoLock = nil; // Evil hackery based on http://www.allocinit.net/blog/2008/01/04/application-specific-information-in-leopard-crash-reports/ static void InitCrashReporterInfo(void) { sCrashReporterInfo = dlsym(RTLD_DEFAULT, "__crashreporter_info__"); if (sCrashReporterInfo != NULL) { sCrashReporterInfoLock = [[NSLock alloc] init]; if (sCrashReporterInfoLock != nil) { sCrashReporterInfoAvailable = YES; } else { sCrashReporterInfo = NULL; } } } static void SetCrashReporterInfo(const char *info) { char *copy = NULL, *old = NULL; /* Don't do anything if setup failed or the string is NULL or empty. (The NULL and empty checks may not be desirable in other uses.) */ if (!sCrashReporterInfoAvailable || info == NULL || *info == '\0') return; // Copy the string, which we assume to be dynamic... copy = strdup(info); if (copy == NULL) return; /* ...and swap it in. Note that we keep a separate pointer to the old value, in case something else overwrites __crashreporter_info__. */ [sCrashReporterInfoLock lock]; *sCrashReporterInfo = copy; old = sOldCrashReporterInfo; sOldCrashReporterInfo = copy; [sCrashReporterInfoLock unlock]; // Delete our old string. if (old != NULL) free(old); } #endif oolite-1.82/src/Core/OOLogging.h000066400000000000000000000166251256642440500164350ustar00rootroot00000000000000/* OOLogging.h By Jens Ayton More flexible alternative to NSLog(). Copyright (C) 2007-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" #import "OOFunctionAttributes.h" #include #ifndef OOLOG_POISON_NSLOG #define OOLOG_POISON_NSLOG 0 #endif #ifndef OOLOG_FUNCTION_NAME #if defined (__GNUC__) && __GNUC__ >= 2 #define OOLOG_FUNCTION_NAME __FUNCTION__ #elif 199901L <= __STDC_VERSION__ #define OOLOG_FUNCTION_NAME __func__ #else #define OOLOG_FUNCTION_NAME NULL #endif #endif #ifndef OOLOG_FILE_NAME #ifdef OOLOG_NO_FILE_NAME #define OOLOG_FILE_NAME NULL #else #define OOLOG_FILE_NAME __FILE__ #endif #endif /* OOLOG_SHORT_CIRCUIT: If nonzero, the test of whether to display a message before evaluating the other parameters of the call. This saves time, but could cause weird bugs if the parameters involve calls with side effects. */ #ifndef OOLOG_SHORT_CIRCUIT #define OOLOG_SHORT_CIRCUIT 1 #endif /* General usage: OOLog(messageClass, format, parameters); is conceptually equivalent to: NSLog(format, parameters); except that it will do nothing if logging is disabled for messageClass. A message class is a hierarchical string, such as: @"all.script.debug" To determine whether scripting is enabled for this class, a setting for @"all.script.debug" is looked up in a settings table. If it is not found, @"all.script" is tried, followed by @"all". Message class display settings can be manipulated with OOLogSetDisplayMessagesInClass() and tested with OOLogWillDisplayMessagesInClass(). */ #if OOLOG_SHORT_CIRCUIT #define OOLog(class, format, ...) do { if (OOLogWillDisplayMessagesInClass(class)) { OOLogWithFunctionFileAndLine(class, OOLOG_FUNCTION_NAME, OOLOG_FILE_NAME, __LINE__, format, ## __VA_ARGS__); }} while (0) #define OOLogWithArguments(class, format, args) do { if (OOLogWillDisplayMessagesInClass(class)) { OOLogWithFunctionFileAndLineAndArguments(class, OOLOG_FUNCTION_NAME, OOLOG_FILE_NAME, __LINE__, format, args); }} while (0) #else #define OOLog(class, format, ...) OOLogWithFunctionFileAndLine(class, OOLOG_FUNCTION_NAME, OOLOG_FILE_NAME, __LINE__, format, ## __VA_ARGS__) #define OOLogWithArguments(class, format, args) OOLogWithFunctionFileAndLineAndArguments(class, OOLOG_FUNCTION_NAME, OOLOG_FILE_NAME, __LINE__, format, args) #endif BOOL OOLogWillDisplayMessagesInClass(NSString *inMessageClass); void OOLogIndent(void); void OOLogOutdent(void); #if OOLOG_SHORT_CIRCUIT #define OOLogIndentIf(class) do { if (OOLogWillDisplayMessagesInClass(class)) OOLogIndent(); } while (0) #define OOLogOutdentIf(class) do { if (OOLogWillDisplayMessagesInClass(class)) OOLogOutdent(); } while (0) #else void OOLogIndentIf(NSString *inMessageClass); void OOLogOutdentIf(NSString *inMessageClass); #endif #define OOLOG_ERROR_PREFIX @"***** ERROR: " #define OOLOG_WARNING_PREFIX @"----- WARNING: " #define OOLogERR(class, format, ...) OOLogWithPrefix(class, OOLOG_FUNCTION_NAME, OOLOG_FILE_NAME, __LINE__, OOLOG_ERROR_PREFIX ,format, ## __VA_ARGS__) #define OOLogWARN(class, format, ...) OOLogWithPrefix(class, OOLOG_FUNCTION_NAME, OOLOG_FILE_NAME, __LINE__, OOLOG_WARNING_PREFIX, format, ## __VA_ARGS__) // Remember/restore indent levels, for cases where an exception may occur while indented. void OOLogPushIndent(void); void OOLogPopIndent(void); void OOLogWithPrefix(NSString *inMessageClass, const char *inFunction, const char *inFile, unsigned long inLine, NSString *inPrefix, NSString *inFormat, ...) OO_TAKES_FORMAT_STRING(6, 7); void OOLogWithFunctionFileAndLine(NSString *inMessageClass, const char *inFunction, const char *inFile, unsigned long inLine, NSString *inFormat, ...) OO_TAKES_FORMAT_STRING(5, 6); void OOLogWithFunctionFileAndLineAndArguments(NSString *inMessageClass, const char *inFunction, const char *inFile, unsigned long inLine, NSString *inFormat, va_list inArguments) OO_TAKES_FORMAT_STRING(5, 0); // OOLogGenericParameterError(): general parameter error message, "***** $function_name: bad parameters. (This is an internal programming error, please report it.)" #define OOLogGenericParameterError() OOLogGenericParameterErrorForFunction(OOLOG_FUNCTION_NAME) void OOLogGenericParameterErrorForFunction(const char *inFunction); // OOLogGenericSubclassResponsibility(): general subclass responsibility message, "***** $function_name is a subclass responsibility. (This is an internal programming error, please report it.)" #define OOLogGenericSubclassResponsibility() OOLogGenericSubclassResponsibilityForFunction(OOLOG_FUNCTION_NAME) void OOLogGenericSubclassResponsibilityForFunction(const char *inFunction); #if OOLOG_POISON_NSLOG #pragma GCC poison NSLog // Use OOLog instead #elif !OOLOG_NO_HIJACK_NSLOG // Hijack NSLog. Buahahahaha. #define NSLog(format, ...) OOLog(kOOLogUnconvertedNSLog, format, ## __VA_ARGS__) #define NSLogv(format, args) OOLogWithArguments(kOOLogUnconvertedNSLog, format, args) #endif // OODebugLog() is only included in debug builds. #if OO_DEBUG #define OODebugLog OOLog #else #define OODebugLog(class, format, ...) do { (void)class; if (0) (void)format; } while (0) #endif // OOExtraLog() is included in debug and test-release builds, but not deployment builds. #ifndef NDEBUG #define OOExtraLog OOLog #else #define OOExtraLog(class, format, ...) do { (void)class; if (0) (void)format; } while (0) #endif // *** Predefined message classes. /* These are general coding error types. Generally a subclass should be used for each instance -- for instance, -[Entity warnAboutHostiles] uses @"general.error.subclassResponsibility.Entity-warnAboutHostiles". */ extern NSString * const kOOLogSubclassResponsibility; // @"general.error.subclassResponsibility" extern NSString * const kOOLogParameterError; // @"general.error.parameterError" extern NSString * const kOOLogDeprecatedMethod; // @"general.error.deprecatedMethod" extern NSString * const kOOLogAllocationFailure; // @"general.error.allocationFailure" extern NSString * const kOOLogInconsistentState; // @"general.error.inconsistentState" extern NSString * const kOOLogException; // @"exception" extern NSString * const kOOLogFileNotFound; // @"files.notFound" extern NSString * const kOOLogFileNotLoaded; // @"files.notLoaded" extern NSString * const kOOLogOpenGLError; // @"rendering.opengl.error" // Don't use. However, #defining it as @"unclassified.module" can be used as a stepping stone to OOLog support. extern NSString * const kOOLogUnconvertedNSLog; // @"unclassified" oolite-1.82/src/Core/OOLogging.m000066400000000000000000000603011256642440500164300ustar00rootroot00000000000000/* OOLogging.m Copyright (C) 2007-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #define OOLOG_POISON_NSLOG 0 #import "OOLoggingExtended.h" #import "OOPListParsing.h" #import "OOFunctionAttributes.h" #import "ResourceManager.h" #import "OOCollectionExtractors.h" #import "NSThreadOOExtensions.h" #import "OOLogHeader.h" #import "OOLogOutputHandler.h" #undef NSLog // We need to be able to call the real NSLog. #define PER_THREAD_INDENTATION 1 #if PER_THREAD_INDENTATION #if OOLITE_USE_TLS // Define to use __thread keyword where supported #define USE_INDENT_GLOBALS 1 #define THREAD_LOCAL __thread #else #define USE_INDENT_GLOBALS 0 static NSString * const kIndentLevelKey = @"org.aegidian.oolite.oolog.indentLevel"; static NSString * const kIndentStackKey = @"org.aegidian.oolite.oolog.indentStack"; #endif #else #define USE_INDENT_GLOBALS 1 #define THREAD_LOCAL #endif // Control flags for OOLogInternal() - like message classes, but less cool. #define OOLOG_NOT_INITED 1 #define OOLOG_SETTING_SET 0 #define OOLOG_SETTING_RETRIEVE 0 #define OOLOG_UNDEFINED_METACLASS 1 #define OOLOG_BAD_SETTING 1 #define OOLOG_BAD_DEFAULT_SETTING 1 #define OOLOG_BAD_POP_INDENT 1 #define OOLOG_EXCEPTION_IN_LOG 1 // Used to track OOLogPushIndent()/OOLogPopIndent() state. typedef struct OOLogIndentStackElement OOLogIndentStackElement; struct OOLogIndentStackElement { OOLogIndentStackElement *link; unsigned indent; }; // We could probably use less state variables. static BOOL sInited = NO; static NSLock *sLock = nil; static NSMutableDictionary *sExplicitSettings = nil; static NSMutableDictionary *sDerivedSettingsCache = nil; #if USE_INDENT_GLOBALS static THREAD_LOCAL unsigned sIndentLevel = 0; static THREAD_LOCAL OOLogIndentStackElement *sIndentStack = NULL; #endif static BOOL sShowFunction = NO; static BOOL sShowFileAndLine = NO; static BOOL sShowTime = NO; // This is defaulted to YES when loading settings, but after printing log header. static BOOL sShowClass = NO; // Ditto. static BOOL sDefaultDisplay = YES; static BOOL sOverrideInEffect = NO; static BOOL sOverrideValue = NO; // These specific values are used for true, false and inherit in the cache and explicitSettings dictionaries so we can use pointer comparison. static NSString * const kTrueToken = @"on"; static NSString * const kFalseToken = @"off"; static NSString * const kInheritToken = @"inherit"; // To avoid recursion/self-dependencies, OOLog gets its own logging function. #define OOLogInternal(cond, format, ...) do { if ((cond)) { OOLogInternal_(OOLOG_FUNCTION_NAME, format, ## __VA_ARGS__); }} while (0) static void OOLogInternal_(const char *inFunction, NSString *inFormat, ...); // Functions used internally static void LoadExplicitSettings(void); static void LoadExplicitSettingsFromDictionary(NSDictionary *inDict); static id ResolveDisplaySetting(NSString *inMessageClass); static id ResolveMetaClassReference(NSString *inMetaClass, NSMutableSet *ioSeenMetaClasses); OOINLINE unsigned GetIndentLevel(void) PURE_FUNC; OOINLINE void SetIndentLevel(unsigned level); #ifndef OOLOG_NO_FILE_NAME static NSMapTable *sFileNamesCache = NULL; #endif // Given a boolean, return the appropriate value for the cache dictionary. OOINLINE id CacheValue(BOOL inValue) PURE_FUNC; OOINLINE id CacheValue(BOOL inValue) { return inValue ? kTrueToken : kFalseToken; } /* Inited() Test wether OOLoggingInit() has been called. */ OOINLINE BOOL Inited(void) { if (EXPECT(sInited)) return YES; OOLogInternal(OOLOG_NOT_INITED, @"***** ERROR: OOLoggingInit() has not been called."); return NO; } BOOL OOLogWillDisplayMessagesInClass(NSString *inMessageClass) { id value = nil; if (!Inited()) return NO; if (sOverrideInEffect) return sOverrideValue; [sLock lock]; // Look for cached value value = [sDerivedSettingsCache objectForKey:inMessageClass]; if (EXPECT_NOT(value == nil)) { @try { // No cached value. value = ResolveDisplaySetting(inMessageClass); if (value != nil) { if (EXPECT_NOT(sDerivedSettingsCache == nil)) sDerivedSettingsCache = [[NSMutableDictionary alloc] init]; [sDerivedSettingsCache setObject:value forKey:inMessageClass]; } } @catch (id exception) { [sLock unlock]; @throw exception; } } [sLock unlock]; OOLogInternal(OOLOG_SETTING_RETRIEVE, @"%@ is %s", inMessageClass, (value == kTrueToken) ? "on" : "off"); return value == kTrueToken; } void OOLogSetDisplayMessagesInClass(NSString *inClass, BOOL inFlag) { id value = nil; if (!Inited()) return; [sLock lock]; value = [sExplicitSettings objectForKey:inClass]; if (value == nil || value != CacheValue(inFlag)) { OOLogInternal(OOLOG_SETTING_SET, @"Setting %@ to %s", inClass, inFlag ? "ON" : "OFF"); [sExplicitSettings setObject:CacheValue(inFlag) forKey:inClass]; // Clear cache and let it be rebuilt as needed. DESTROY(sDerivedSettingsCache); } else { OOLogInternal(OOLOG_SETTING_SET, @"Keeping %@ %s", inClass, inFlag ? "ON" : "OFF"); } [sLock unlock]; } NSString *OOLogGetParentMessageClass(NSString *inClass) { NSRange range; if (inClass == nil) return nil; range = [inClass rangeOfString:@"." options:NSCaseInsensitiveSearch | NSLiteralSearch | NSBackwardsSearch]; // Only NSBackwardsSearch is important, others are optimizations if (range.location == NSNotFound) return nil; return [inClass substringToIndex:range.location]; } #if !OOLOG_SHORT_CIRCUIT void OOLogIndentIf(NSString *inMessageClass) { if (OOLogWillDisplayMessagesInClass(inMessageClass)) OOLogIndent(); } void OOLogOutdentIf(NSString *inMessageClass) { if (OOLogWillDisplayMessagesInClass(inMessageClass)) OOLogOutdent(); } #endif #if USE_INDENT_GLOBALS #if OOLITE_USE_TLS #define INDENT_LOCK() do {} while (0) #define INDENT_UNLOCK() do {} while (0) #else #define INDENT_LOCK() [sLock lock] #define INDENT_UNLOCK() [sLock unlock] #endif OOINLINE unsigned GetIndentLevel(void) { return sIndentLevel; } OOINLINE void SetIndentLevel(unsigned value) { sIndentLevel = value; } void OOLogPushIndent(void) { OOLogIndentStackElement *elem = NULL; elem = malloc(sizeof *elem); if (elem != NULL) { INDENT_LOCK(); elem->indent = sIndentLevel; elem->link = sIndentStack; sIndentStack = elem; INDENT_UNLOCK(); } } void OOLogPopIndent(void) { INDENT_LOCK(); OOLogIndentStackElement *elem = sIndentStack; if (elem != NULL) { sIndentStack = elem->link; sIndentLevel = elem->indent; free(elem); } else { OOLogInternal(OOLOG_BAD_POP_INDENT, @"OOLogPopIndent(): state stack underflow."); } INDENT_UNLOCK(); } #else // !USE_INDENT_GLOBALS #define INDENT_LOCK() do {} while (0) #define INDENT_UNLOCK() do {} while (0) OOINLINE unsigned GetIndentLevel(void) { NSMutableDictionary *threadDict = [[NSThread currentThread] threadDictionary]; return [[threadDict objectForKey:kIndentLevelKey] unsignedIntValue]; } OOINLINE void SetIndentLevel(unsigned value) { NSMutableDictionary *threadDict = [[NSThread currentThread] threadDictionary]; [threadDict setObject:[NSNumber numberWithUnsignedInt:value] forKey:kIndentLevelKey]; } void OOLogPushIndent(void) { OOLogIndentStackElement *elem = NULL; NSMutableDictionary *threadDict = nil; NSValue *val = nil; elem = malloc(sizeof *elem); if (elem != NULL) { threadDict = [[NSThread currentThread] threadDictionary]; val = [threadDict objectForKey:kIndentStackKey]; elem->indent = [[threadDict objectForKey:kIndentLevelKey] intValue]; elem->link = [val pointerValue]; /* Clang static analyzer reports elem not released here. It is in fact released in OOLogPopIndent(). */ [threadDict setObject:[NSValue valueWithPointer:elem] forKey:kIndentStackKey]; } } void OOLogPopIndent(void) { OOLogIndentStackElement *elem = NULL; NSMutableDictionary *threadDict = nil; NSValue *val = nil; threadDict = [[NSThread currentThread] threadDictionary]; val = [threadDict objectForKey:kIndentStackKey]; elem = [val pointerValue]; if (elem != NULL) { [threadDict setObject:[NSNumber numberWithUnsignedInt:elem->indent] forKey:kIndentLevelKey]; [threadDict setObject:[NSValue valueWithPointer:elem->link] forKey:kIndentStackKey]; free(elem); } else { OOLogInternal(OOLOG_BAD_POP_INDENT, @"OOLogPopIndent(): state stack underflow."); } } #endif // USE_INDENT_GLOBALS void OOLogIndent(void) { INDENT_LOCK(); SetIndentLevel(GetIndentLevel() + 1); INDENT_UNLOCK(); } void OOLogOutdent(void) { INDENT_LOCK(); unsigned indentLevel = GetIndentLevel(); if (indentLevel != 0) SetIndentLevel(indentLevel - 1); INDENT_UNLOCK(); } void OOLogWithPrefix(NSString *inMessageClass, const char *inFunction, const char *inFile, unsigned long inLine, NSString *inPrefix, NSString *inFormat, ...) { if (!OOLogWillDisplayMessagesInClass(inMessageClass)) return; va_list args; va_start(args, inFormat); OOLogWithFunctionFileAndLineAndArguments(inMessageClass, inFunction, inFile, inLine, [inPrefix stringByAppendingString:inFormat], args); va_end(args); } void OOLogWithFunctionFileAndLine(NSString *inMessageClass, const char *inFunction, const char *inFile, unsigned long inLine, NSString *inFormat, ...) { va_list args; va_start(args, inFormat); OOLogWithFunctionFileAndLineAndArguments(inMessageClass, inFunction, inFile, inLine, inFormat, args); va_end(args); } void OOLogWithFunctionFileAndLineAndArguments(NSString *inMessageClass, const char *inFunction, const char *inFile, unsigned long inLine, NSString *inFormat, va_list inArguments) { NSAutoreleasePool *pool = nil; NSString *formattedMessage = nil; unsigned indentLevel; if (inFormat == nil) return; #if !OOLOG_SHORT_CIRCUIT if (!OOLogWillDisplayMessagesInClass(inMessageClass)) return; #endif pool = [[NSAutoreleasePool alloc] init]; @try { // Do argument substitution formattedMessage = [[[NSString alloc] initWithFormat:inFormat arguments:inArguments] autorelease]; // Apply various prefix options #ifndef OOLOG_NO_FILE_NAME if (sShowFileAndLine && inFile != NULL) { if (sShowFunction) { formattedMessage = [NSString stringWithFormat:@"%s (%@:%lu): %@", inFunction, OOLogAbbreviatedFileName(inFile), inLine, formattedMessage]; } else { formattedMessage = [NSString stringWithFormat:@"%@:%lu: %@", OOLogAbbreviatedFileName(inFile), inLine, formattedMessage]; } } else #endif { if (sShowFunction) { formattedMessage = [NSString stringWithFormat:@"%s: %@", inFunction, formattedMessage]; } } if (sShowClass) { if (sShowFunction || sShowFileAndLine) { formattedMessage = [NSString stringWithFormat:@"[%@] %@", inMessageClass, formattedMessage]; } else { formattedMessage = [NSString stringWithFormat:@"[%@]: %@", inMessageClass, formattedMessage]; } } if (sShowTime) { formattedMessage = [NSString stringWithFormat:@"%@ %@", [[NSDate date] descriptionWithCalendarFormat:@"%H:%M:%S.%F" timeZone:nil locale:nil], formattedMessage]; } // Apply indentation indentLevel = GetIndentLevel(); if (indentLevel != 0) { #define INDENT_FACTOR 2 /* Spaces per indent level */ #define MAX_INDENT 64 /* Maximum number of indentation _spaces_ */ unsigned indent; // String of 64 spaces (null-terminated) const char spaces[MAX_INDENT + 1] = " "; const char *indentString; indent = INDENT_FACTOR * indentLevel; if (MAX_INDENT < indent) indent = MAX_INDENT; indentString = &spaces[MAX_INDENT - indent]; formattedMessage = [NSString stringWithFormat:@"%s%@", indentString, formattedMessage]; } OOLogOutputHandlerPrint(formattedMessage); } @catch (NSException *exception) { OOLogInternal(OOLOG_EXCEPTION_IN_LOG, @"***** Exception thrown during logging: %@ : %@", [exception name], [exception reason]); } [pool release]; } void OOLogGenericParameterErrorForFunction(const char *inFunction) { OOLog(kOOLogParameterError, @"***** %s: bad parameters. (This is an internal programming error, please report it.)", inFunction); } void OOLogGenericSubclassResponsibilityForFunction(const char *inFunction) { OOLog(kOOLogSubclassResponsibility, @"***** %s is a subclass responsibility. (This is an internal programming error, please report it.)", inFunction); } BOOL OOLogShowFunction(void) { return sShowFunction; } void OOLogSetShowFunction(BOOL flag) { flag = !!flag; // YES or NO, not 42. if (flag != sShowFunction) { sShowFunction = flag; [[NSUserDefaults standardUserDefaults] setBool:flag forKey:@"logging-show-function"]; } } BOOL OOLogShowFileAndLine(void) { return sShowFileAndLine; } void OOLogSetShowFileAndLine(BOOL flag) { flag = !!flag; // YES or NO, not 42. if (flag != sShowFileAndLine) { sShowFileAndLine = flag; [[NSUserDefaults standardUserDefaults] setBool:flag forKey:@"logging-show-file-and-line"]; } } BOOL OOLogShowTime(void) { return sShowTime; } void OOLogSetShowTime(BOOL flag) { flag = !!flag; // YES or NO, not 42. if (flag != sShowTime) { sShowTime = flag; [[NSUserDefaults standardUserDefaults] setBool:flag forKey:@"logging-show-time"]; } } BOOL OOLogShowMessageClass(void) { return sShowClass; } void OOLogSetShowMessageClass(BOOL flag) { flag = !!flag; // YES or NO, not 42. if (flag != sShowClass) { sShowClass = flag; [[NSUserDefaults standardUserDefaults] setBool:flag forKey:@"logging-show-class"]; } } void OOLogSetShowMessageClassTemporary(BOOL flag) { sShowClass = !!flag; // YES or NO, not 42. } void OOLoggingInit(void) { NSAutoreleasePool *pool = nil; if (sInited) return; pool = [[NSAutoreleasePool alloc] init]; sLock = [[NSLock alloc] init]; [sLock setName:@"OOLogging lock"]; if (sLock == nil) exit(EXIT_FAILURE); #ifndef OOLOG_NO_FILE_NAME sFileNamesCache = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks, NSObjectMapValueCallBacks, 100); #endif sInited = YES; // Must be before OOLogOutputHandlerInit(). OOLogOutputHandlerInit(); OOPrintLogHeader(); LoadExplicitSettings(); [pool release]; } void OOLoggingTerminate(void) { if (!sInited) return; OOLogOutputHandlerClose(); /* We do not set sInited to NO. Instead, the output handler is required to be able to handle working even after being closed. Under OS X, this is done by writing to stderr in this case; on other platforms, NSLog() is used and OOLogOutputHandlerClose() is a no-op. */ } void OOLoggingReloadSettings(void) { LoadExplicitSettings(); } void OOLogInsertMarker(void) { static unsigned lastMarkerID = 0; unsigned thisMarkerID; NSString *marker = nil; [sLock lock]; thisMarkerID = ++lastMarkerID; [sLock unlock]; marker = [NSString stringWithFormat:@"\n\n========== [Marker %u] ==========", thisMarkerID]; OOLogOutputHandlerPrint(marker); } NSString * const kOOLogSubclassResponsibility = @"general.error.subclassResponsibility"; NSString * const kOOLogParameterError = @"general.error.parameterError"; NSString * const kOOLogDeprecatedMethod = @"general.error.deprecatedMethod"; NSString * const kOOLogAllocationFailure = @"general.error.allocationFailure"; NSString * const kOOLogInconsistentState = @"general.error.inconsistentState"; NSString * const kOOLogException = @"exception"; NSString * const kOOLogFileNotFound = @"files.notFound"; NSString * const kOOLogFileNotLoaded = @"files.notLoaded"; NSString * const kOOLogOpenGLError = @"rendering.opengl.error"; NSString * const kOOLogUnconvertedNSLog = @"unclassified"; /* OOLogInternal_() Implementation of OOLogInternal(), private logging function used by OOLogging so it doesn’t depend on itself (and risk recursiveness). */ static void OOLogInternal_(const char *inFunction, NSString *inFormat, ...) { va_list args; NSString *formattedMessage = nil; NSAutoreleasePool *pool = nil; pool = [[NSAutoreleasePool alloc] init]; @try { va_start(args, inFormat); formattedMessage = [[[NSString alloc] initWithFormat:inFormat arguments:args] autorelease]; va_end(args); formattedMessage = [NSString stringWithFormat:@"OOLogging internal - %s: %@", inFunction, formattedMessage]; OOLogOutputHandlerPrint(formattedMessage); } @catch (NSException *exception) { fprintf(stderr, "***** Exception in OOLogInternal_(): %s : %s", [[exception name] UTF8String], [[exception reason] UTF8String]); } [pool release]; } /* LoadExplicitSettings() Read settings from logcontrol.plists, merge in settings from preferences. Log settings are loaded from the following locations, from lowest to highest priority: * logcontrol.plist inside OXPs, but only in hierarchies not defined by the built-in plist. * Built-in logcontrol.plist. * Loose logcontrol.plist files in AddOns folders. * Preferences (settable through the debug console). Because the settings are loaded very early (OOLoggingInit() time), before the state of strict mode has been set, OXP settings are always loaded. Since these generally shouldn't have any effect in strict mode, that's fine. */ static void LoadExplicitSettings(void) { // Load display settings. NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; sShowFunction = [prefs oo_boolForKey:@"logging-show-function" defaultValue:NO]; sShowFileAndLine = [prefs oo_boolForKey:@"logging-show-file-and-line" defaultValue:NO]; sShowTime = [prefs oo_boolForKey:@"logging-show-time" defaultValue:YES]; sShowClass = [prefs oo_boolForKey:@"logging-show-class" defaultValue:YES]; NSDictionary *oldSettings = sExplicitSettings; /* HACK: we look up search paths while loading settings, which touches the cache, which logs stuff. To avoid spam like dataCache.retrieve.success, we first load the built-in logcontrol.plist only and use it while loading the full settings. */ NSString *path = [[[ResourceManager builtInPath] stringByAppendingPathComponent:@"Config"] stringByAppendingPathComponent:@"logcontrol.plist"]; sExplicitSettings = [NSMutableDictionary dictionary]; LoadExplicitSettingsFromDictionary(OODictionaryFromFile(path)); // Release previous sExplicitSettings. [oldSettings release]; // Load new settings. NSDictionary *settings = [ResourceManager logControlDictionary]; // Replace the old dictionary. sExplicitSettings = [[NSMutableDictionary alloc] init]; // Populate. LoadExplicitSettingsFromDictionary(settings); // Get _default and _override values. id value = [sExplicitSettings objectForKey:@"_default"]; if (value != nil && [value respondsToSelector:@selector(boolValue)]) { if (value == kTrueToken) sDefaultDisplay = YES; else if (value == kFalseToken) sDefaultDisplay = NO; else OOLogInternal(OOLOG_BAD_DEFAULT_SETTING, @"_default may not be set to a metaclass, ignoring."); [sExplicitSettings removeObjectForKey:@"_default"]; } value = [sExplicitSettings objectForKey:@"_override"]; if (value != nil && [value respondsToSelector:@selector(boolValue)]) { if (value == kTrueToken) { sOverrideInEffect = YES; sOverrideValue = YES; } else if (value == kFalseToken) { sOverrideInEffect = YES; sOverrideValue = NO; } else OOLogInternal(OOLOG_BAD_DEFAULT_SETTING, @"_override may not be set to a metaclass, ignoring."); [sExplicitSettings removeObjectForKey:@"_override"]; } // Invalidate cache. DESTROY(sDerivedSettingsCache); OOLogInternal(OOLOG_SETTING_SET, @"Settings: %@", sExplicitSettings); } /* LoadExplicitSettingsFromDictionary() Helper for LoadExplicitSettings(). */ static void LoadExplicitSettingsFromDictionary(NSDictionary *inDict) { id key = nil; foreachkey (key, inDict) { id value = [inDict objectForKey:key]; /* Supported values: "yes", "true" or "on" -> kTrueToken "no", "false" or "off" -> kFalseToken "inherit" or "inherited" -> nil NSNumber -> kTrueToken or kFalseToken "$metaclass" -> "$metaclass" */ if ([value isKindOfClass:[NSString class]]) { if (NSOrderedSame == [value caseInsensitiveCompare:@"yes"] || NSOrderedSame == [value caseInsensitiveCompare:@"true"] || NSOrderedSame == [value caseInsensitiveCompare:@"on"]) { value = kTrueToken; } else if (NSOrderedSame == [value caseInsensitiveCompare:@"no"] || NSOrderedSame == [value caseInsensitiveCompare:@"false"] || NSOrderedSame == [value caseInsensitiveCompare:@"off"]) { value = kFalseToken; } else if (NSOrderedSame == [value caseInsensitiveCompare:@"inherit"] || NSOrderedSame == [value caseInsensitiveCompare:@"inherited"]) { value = nil; [sExplicitSettings removeObjectForKey:key]; } else if (![value hasPrefix:@"$"]) { OOLogInternal(OOLOG_BAD_SETTING, @"Bad setting value \"%@\" (expected yes, no, inherit or $metaclass).", value); value = nil; } } else if ([value respondsToSelector:@selector(boolValue)]) { value = CacheValue([value boolValue]); } else { OOLogInternal(OOLOG_BAD_SETTING, @"Bad setting value \"%@\" (expected yes, no, inherit or $metaclass).", value); value = nil; } if (value != nil) { [sExplicitSettings setObject:value forKey:key]; } } } /* OOLogAbbreviatedFileName() Map full file paths provided by __FILE__ to more mananagable file names, with caching. */ NSString *OOLogAbbreviatedFileName(const char *inName) { NSString *name = nil; if (EXPECT_NOT(inName == NULL)) return @"unspecified file"; [sLock lock]; name = NSMapGet(sFileNamesCache, inName); if (EXPECT_NOT(name == nil)) { name = [[NSString stringWithUTF8String:inName] lastPathComponent]; NSMapInsertKnownAbsent(sFileNamesCache, inName, name); } [sLock unlock]; return name; } /* Look up setting for a message class in explicit settings, resolving inheritance and metaclasses. */ static id ResolveDisplaySetting(NSString *inMessageClass) { id value = nil; NSMutableSet *seenMetaClasses = nil; if (inMessageClass == nil) return CacheValue(sDefaultDisplay); value = [sExplicitSettings objectForKey:inMessageClass]; // Simple case: explicit setting for this value if (value == kTrueToken || value == kFalseToken) return value; // Simplish case: use inherited value if (value == nil || value == kInheritToken) return ResolveDisplaySetting(OOLogGetParentMessageClass(inMessageClass)); // Less simple case: should be a metaclass. seenMetaClasses = [NSMutableSet set]; return ResolveMetaClassReference(value, seenMetaClasses); } /* Resolve a metaclass reference, recursively if necessary. The ioSeenMetaClasses dictionary is used to avoid loops. */ static id ResolveMetaClassReference(NSString *inMetaClass, NSMutableSet *ioSeenMetaClasses) { id value = nil; // All values should have been checked at load time, but what the hey. if (![inMetaClass isKindOfClass:[NSString class]] || ![inMetaClass hasPrefix:@"$"]) { OOLogInternal(OOLOG_BAD_SETTING, @"Bad setting value \"%@\" (expected yes, no, inherit or $metaclass). Falling back to _default.", inMetaClass); return CacheValue(sDefaultDisplay); } [ioSeenMetaClasses addObject:inMetaClass]; value = [sExplicitSettings objectForKey:inMetaClass]; if (value == kTrueToken || value == kFalseToken) return value; if (value == nil) { OOLogInternal(OOLOG_UNDEFINED_METACLASS, @"Reference to undefined metaclass %@, falling back to _default.", inMetaClass); return CacheValue(sDefaultDisplay); } // If we get here, it should be a metaclass reference. return ResolveMetaClassReference(value, ioSeenMetaClasses); } oolite-1.82/src/Core/OOLoggingExtended.h000066400000000000000000000036701256642440500201120ustar00rootroot00000000000000/* OOLoggingExtended.h By Jens Ayton Configuration functions for OOLogging.h. Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOLogging.h" void OOLogSetDisplayMessagesInClass(NSString *inClass, BOOL inFlag); NSString *OOLogGetParentMessageClass(NSString *inClass); void OOLoggingInit(void); void OOLoggingTerminate(void); void OOLogInsertMarker(void); // Get/set display settings. These are stored in user defaults. BOOL OOLogShowFunction(void); void OOLogSetShowFunction(BOOL flag); BOOL OOLogShowFileAndLine(void); void OOLogSetShowFileAndLine(BOOL flag); BOOL OOLogShowTime(void); void OOLogSetShowTime(BOOL flag); BOOL OOLogShowMessageClass(void); void OOLogSetShowMessageClass(BOOL flag); // Change message class visibility without saving to user defaults. void OOLogSetShowMessageClassTemporary(BOOL flag); // Utility function to strip path components from __FILE__ strings. NSString *OOLogAbbreviatedFileName(const char *inName); oolite-1.82/src/Core/OOMacroOpenGL.h000066400000000000000000000027731256642440500171540ustar00rootroot00000000000000/* OOMacroOpenGL.h Under Mac OS X, OpenGL performance can be improved somewhat by using macros that call through to a function table directly, avoiding calls to functions that just look up the current context and pass their parameters through to a context-specific implementation function. This header abstracts that behaviour for cross-platformity. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #if OOLITE_MAC_OS_X && !defined(OOLITE_NO_CGL_MACRO) #if MAC_OS_X_VERSION_10_4 <= MAC_OS_X_VERSION_MAX_ALLOWED #define CGL_MACRO_CACHE_RENDERER #import #define OO_ENTER_OPENGL CGL_MACRO_DECLARE_VARIABLES #else #import #define OO_ENTER_OPENGL() CGLContextObj CGL_MACRO_CONTEXT = CGLGetCurrentContext(); \ #endif #else // Not OS X #define OO_ENTER_OPENGL() do {} while (0) #endif oolite-1.82/src/Core/OOManifestProperties.h000066400000000000000000000060221256642440500206600ustar00rootroot00000000000000/* OOManifestProperties.h The property keys used in manifest.plist entries Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ static NSString * const kOOManifestIdentifier = @"identifier"; static NSString * const kOOManifestVersion = @"version"; static NSString * const kOOManifestRequiredOoliteVersion= @"required_oolite_version"; static NSString * const kOOManifestMaximumOoliteVersion = @"maximum_oolite_version"; static NSString * const kOOManifestTitle = @"title"; static NSString * const kOOManifestRequiresOXPs = @"requires_oxps"; static NSString * const kOOManifestConflictOXPs = @"conflict_oxps"; static NSString * const kOOManifestDescription = @"description"; static NSString * const kOOManifestCategory = @"category"; static NSString * const kOOManifestDownloadURL = @"download_url"; static NSString * const kOOManifestFileSize = @"file_size"; static NSString * const kOOManifestInformationURL = @"information_url"; static NSString * const kOOManifestAuthor = @"author"; static NSString * const kOOManifestLicense = @"license"; static NSString * const kOOManifestTags = @"tags"; /* these properties are not contained in the manifest.plist (and would be overwritten if they were...) but are calculated by Oolite */ static NSString * const kOOManifestFilePath = @"file_path"; static NSString * const kOOManifestRequiredBy = @"required_by"; static NSString * const kOOManifestAvailableVersion = @"available_version"; /* these properties are not contained in the manifest.plist but are * provided by in the manifest*s* list by the API */ static NSString * const kOOManifestUploadDate = @"upload_date"; // following manifest.plist properties not (yet?) used by Oolite // but may be used by other manifest reading applications #if 0 static NSString * const kOOManifestOptionalOXPs = @"optional_oxps"; #endif // properties for within requires/optional/conflicts entries static NSString * const kOOManifestRelationIdentifier = @"identifier"; static NSString * const kOOManifestRelationVersion = @"version"; static NSString * const kOOManifestRelationMaxVersion = @"maximum_version"; static NSString * const kOOManifestRelationDescription = @"description"; // 'magic' value for a tag to exclude an OXP from loading except when // required by a scenario static NSString * const kOOManifestTagScenarioOnly = @"oolite-scenario-only"; oolite-1.82/src/Core/OOMaths.h000066400000000000000000000062221256642440500161130ustar00rootroot00000000000000/* OOMaths.h Mathematical framework for Oolite. Provides utility routines for Vectors, Quaternions, rotation matrices, and conversion to OpenGL transformation matrices. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef INCLUDED_OOMATHS_h #define INCLUDED_OOMATHS_h #ifdef __cplusplus extern "C" { #endif #ifndef OOMATHS_STANDALONE #define OOMATHS_STANDALONE 0 #endif #ifndef OOMATHS_OPENGL_INTEGRATION #define OOMATHS_OPENGL_INTEGRATION !OOMATHS_STANDALONE #endif #ifdef __OBJC__ #import #endif #include "OOFunctionAttributes.h" #include #include #include #include #include #include #if OOMATHS_OPENGL_INTEGRATION #include "OOOpenGL.h" #endif #if OOMATHS_OPENGL_INTEGRATION typedef GLfloat OOScalar; #else typedef float OOScalar; #endif typedef double OOHPScalar; #ifndef M_PI #define M_PI 3.14159265358979323846 /* pi */ #endif #ifndef M_PI_2 #define M_PI_2 1.57079632679489661923 /* pi/2 */ #endif #ifndef M_PI_4 #define M_PI_4 0.78539816339744830962 /* pi/4 */ #endif #ifndef M_1_PI #define M_1_PI 0.31830988618379067154 /* 1/pi */ #endif #ifndef M_2_PI #define M_2_PI 0.63661977236758134308 /* 2/pi */ #endif #ifndef M_2_SQRTPI #define M_2_SQRTPI 1.12837916709551257390 /* 2/sqrt(pi) */ #endif #ifndef M_SQRT2 #define M_SQRT2 1.41421356237309504880 /* sqrt(2) */ #endif #ifndef M_SQRT1_2 #define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */ #endif #if defined(__GNUC__) && !defined(__STRICT_ANSI__) #ifndef MIN #define MIN(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; }) #endif #if !defined(MAX) #define MAX(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; }) #endif #if !defined(ABS) #define ABS(A) ({ __typeof__(A) __a = (A); __a < 0 ? -__a : __a; }) #endif #else /* These definitions are unsafe in that the "winning" expression is evaluated twice. */ #if !defined(MIN) #define MIN(A,B) ((A) < (B) ? (A) : (B)) #endif #if !defined(MAX) #define MAX(A,B) ((A) > (B) ? (A) : (B)) #endif #if !defined(ABS) #define ABS(A) ((A) < 0 ? (-(A)) : (A)) #endif #endif #include "OOFastArithmetic.h" #include "OOVector.h" #include "OOHPVector.h" #include "OOQuaternion.h" #include "OOMatrix.h" #if !OOMATHS_STANDALONE #include "OOVoxel.h" #include "OOTriangle.h" #include "OOBoundingBox.h" #include "legacy_random.h" #endif #ifdef __cplusplus } #endif #endif /* INCLUDED_OOMATHS_h */ oolite-1.82/src/Core/OOMatrix.h000066400000000000000000000255771256642440500163210ustar00rootroot00000000000000/* OOMatrix.h Mathematical framework for Oolite. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef INCLUDED_OOMATHS_h #error Do not include OOMatrix.h directly; include OOMaths.h. #else typedef struct OOMatrix { OOScalar m[4][4]; } OOMatrix; extern const OOMatrix kIdentityMatrix; /* {1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1} */ extern const OOMatrix kZeroMatrix; /* {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0} */ /* Matrix construction and standard primitive matrices */ OOINLINE OOMatrix OOMatrixConstruct(OOScalar aa, OOScalar ab, OOScalar ac, OOScalar ad, OOScalar ba, OOScalar bb, OOScalar bc, OOScalar bd, OOScalar ca, OOScalar cb, OOScalar cc, OOScalar cd, OOScalar da, OOScalar db, OOScalar dc, OOScalar dd) INLINE_CONST_FUNC; OOINLINE OOMatrix OOMatrixFromOrientationAndPosition(Quaternion orientation, Vector position) INLINE_CONST_FUNC; OOINLINE OOMatrix OOMatrixFromBasisVectorsAndPosition(Vector i, Vector j, Vector k, Vector position) INLINE_CONST_FUNC; OOINLINE OOMatrix OOMatrixFromBasisVectors(Vector i, Vector j, Vector k) INLINE_CONST_FUNC; OOINLINE OOMatrix OOMatrixForScale(OOScalar sx, OOScalar sy, OOScalar sz) INLINE_CONST_FUNC; OOINLINE OOMatrix OOMatrixForScaleUniform(OOScalar s) INLINE_CONST_FUNC; OOINLINE OOMatrix OOMatrixForRotationX(OOScalar angle) INLINE_CONST_FUNC; OOINLINE OOMatrix OOMatrixForRotationY(OOScalar angle) INLINE_CONST_FUNC; OOINLINE OOMatrix OOMatrixForRotationZ(OOScalar angle) INLINE_CONST_FUNC; OOMatrix OOMatrixForRotation(Vector axis, OOScalar angle) CONST_FUNC; OOMatrix OOMatrixForQuaternionRotation(Quaternion orientation); OOINLINE OOMatrix OOMatrixForTranslation(Vector v) INLINE_CONST_FUNC; OOINLINE OOMatrix OOMatrixForTranslationComponents(OOScalar dx, OOScalar dy, OOScalar dz) INLINE_CONST_FUNC; OOMatrix OOMatrixForBillboard(HPVector bbPos, HPVector eyePos) CONST_FUNC; /* Matrix transformations */ OOINLINE OOMatrix OOMatrixTranslate(OOMatrix m, Vector offset) INLINE_CONST_FUNC; OOINLINE OOMatrix OOMatrixHPTranslate(OOMatrix m, HPVector offset) INLINE_CONST_FUNC; OOINLINE OOMatrix OOMatrixTranslateComponents(OOMatrix m, OOScalar dx, OOScalar dy, OOScalar dz) INLINE_CONST_FUNC; OOINLINE OOMatrix OOMatrixScale(OOMatrix m, OOScalar sx, OOScalar sy, OOScalar sz) INLINE_CONST_FUNC; OOINLINE OOMatrix OOMatrixScaleUniform(OOMatrix m, OOScalar s) INLINE_CONST_FUNC; OOINLINE OOMatrix OOMatrixRotateX(OOMatrix m, OOScalar angle) INLINE_CONST_FUNC; OOINLINE OOMatrix OOMatrixRotateY(OOMatrix m, OOScalar angle) INLINE_CONST_FUNC; OOINLINE OOMatrix OOMatrixRotateZ(OOMatrix m, OOScalar angle) INLINE_CONST_FUNC; OOINLINE OOMatrix OOMatrixRotate(OOMatrix m, Vector axis, OOScalar angle) INLINE_CONST_FUNC; OOINLINE OOMatrix OOMatrixRotateQuaternion(OOMatrix m, Quaternion quat) INLINE_CONST_FUNC; OOINLINE OOMatrix OOMatrixTranspose(OOMatrix m) INLINE_CONST_FUNC; bool OOMatrixEqual(OOMatrix a, OOMatrix b) CONST_FUNC; OOINLINE bool OOMatrixIsIdentity(OOMatrix m) INLINE_CONST_FUNC; /* Matrix multiplication */ OOMatrix OOMatrixMultiply(OOMatrix a, OOMatrix b) CONST_FUNC; Vector OOVectorMultiplyMatrix(Vector v, OOMatrix m) CONST_FUNC; HPVector OOHPVectorMultiplyMatrix(HPVector v, OOMatrix m) CONST_FUNC; /* Extraction */ OOINLINE void OOMatrixGetBasisVectors(OOMatrix m, Vector *outRight, Vector *outUp, Vector *outForward) NONNULL_FUNC ALWAYS_INLINE_FUNC; /* Orthogonalizion - avoidance of distortions due to numerical inaccuracy. */ OOMatrix OOMatrixOrthogonalize(OOMatrix m) CONST_FUNC; #if OOMATHS_OPENGL_INTEGRATION /* OpenGL conveniences. Need to be macros to work with OOMacroOpenGL. */ #define OOMatrixValuesForOpenGL(M) (&(M).m[0][0]) #define GLMultOOMatrix(M) do { OOMatrix m_ = M; OOGL(glMultMatrixf(OOMatrixValuesForOpenGL(m_))); } while (0) #define GLLoadOOMatrix(M) do { OOMatrix m_ = M; OOGL(glLoadMatrixf(OOMatrixValuesForOpenGL(m_))); } while (0) #define GLMultTransposeOOMatrix(M) do { OOMatrix m_ = M; OOGL(glMultTransposeMatrixf(OOMatrixValuesForOpenGL(m_))); } while (0) #define GLLoadTransposeOOMatrix(M) do { OOMatrix m_ = M; OOGL(glLoadTransposeMatrixf(OOMatrixValuesForOpenGL(m_))); } while (0) #define GLUniformMatrix(location, M) do { OOGL(glUniformMatrix4fvARB(location, 1, NO, OOMatrixValuesForOpenGL(M))); } while (0) void GLUniformMatrix3(GLint location, OOMatrix M); OOINLINE OOMatrix OOMatrixLoadGLMatrix(GLenum matrixID) ALWAYS_INLINE_FUNC; #endif #if __OBJC__ NSString *OOMatrixDescription(OOMatrix matrix); // @"{{#, #, #, #}, {#, #, #, #}, {#, #, #, #}, {#, #, #, #}}" #endif // Row operations // swap row1 and row2 of M void OOMatrixRowSwap(OOMatrix *M, int row1, int row2); // scale row of M by factor void OOMatrixRowScale(OOMatrix *m, int row, OOScalar factor); // replace row 1 of M with factor1 * row1 + factor2 * row2 void OOMatrixRowOperation(OOMatrix *M, int row1, OOScalar factor1, int row2, OOScalar factor2 ); // Column operations // swap column1 and column2 of M void OOMatrixColumnSwap(OOMatrix *M, int column1, int column2); // scale column of M by factor void OOMatrixColumnScale(OOMatrix *M, int column, OOScalar factor); //replace column1 of M with factor1 * column1 + factor2 + row2 void OOMatrixColumnOperation(OOMatrix *M, int column1, OOScalar factor1, int column2, OOScalar factor2); // Transforms between square matrices // return matrix X such that XA = B, or zero matrix if X doesn't exist. OOMatrix OOMatrixLeftTransform(OOMatrix A, OOMatrix B); // return matrix X such that AX = B, or zero matrix if X doesn't exist. OOMatrix OOMatrixRightTransform(OOMatrix A, OOMatrix B); // Matrix inversion OOMatrix OOMatrixInverse(OOMatrix M); // Inverts matrix returning determinant of inverse in OOMatrix OOMatrixInverseWithDeterminant(OOMatrix M, OOScalar *determinant); /*** Only inline definitions beyond this point ***/ OOINLINE OOMatrix OOMatrixConstruct(OOScalar aa, OOScalar ab, OOScalar ac, OOScalar ad, OOScalar ba, OOScalar bb, OOScalar bc, OOScalar bd, OOScalar ca, OOScalar cb, OOScalar cc, OOScalar cd, OOScalar da, OOScalar db, OOScalar dc, OOScalar dd) { OOMatrix r = {{ { aa, ab, ac, ad }, { ba, bb, bc, bd }, { ca, cb, cc, cd }, { da, db, dc, dd } }}; return r; } OOINLINE OOMatrix OOMatrixFromOrientationAndPosition(Quaternion orientation, Vector position) { OOMatrix m = OOMatrixForQuaternionRotation(orientation); return OOMatrixTranslate(m, position); } OOINLINE OOMatrix OOMatrixFromBasisVectorsAndPosition(Vector i, Vector j, Vector k, Vector p) { return OOMatrixConstruct ( i.x, i.y, i.z, 0.0f, j.x, j.y, j.z, 0.0f, k.x, k.y, k.z, 0.0f, p.x, p.y, p.z, 1.0f ); } OOINLINE OOMatrix OOMatrixFromBasisVectors(Vector i, Vector j, Vector k) { return OOMatrixFromBasisVectorsAndPosition(i, j, k, kZeroVector); } /* Standard primitive transformation matrices: */ OOMatrix OOMatrixForRotationX(OOScalar angle) { OOScalar s, c; s = sin(angle); c = cos(angle); return OOMatrixConstruct ( 1, 0, 0, 0, 0, c, s, 0, 0, -s, c, 0, 0, 0, 0, 1 ); } OOMatrix OOMatrixForRotationY(OOScalar angle) { OOScalar s, c; s = sin(angle); c = cos(angle); return OOMatrixConstruct ( c, 0, -s, 0, 0, 1, 0, 0, s, 0, c, 0, 0, 0, 0, 1 ); } OOMatrix OOMatrixForRotationZ(OOScalar angle) { OOScalar s, c; s = sin(angle); c = cos(angle); return OOMatrixConstruct ( c, s, 0, 0, -s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ); } OOINLINE OOMatrix OOMatrixForTranslationComponents(OOScalar dx, OOScalar dy, OOScalar dz) { return OOMatrixConstruct ( 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, dx, dy, dz, 1 ); } OOINLINE OOMatrix OOMatrixForTranslation(Vector v) { return OOMatrixForTranslationComponents(v.x, v.y, v.z); } OOINLINE OOMatrix OOMatrixTranslateComponents(OOMatrix m, OOScalar dx, OOScalar dy, OOScalar dz) { m.m[3][0] += dx; m.m[3][1] += dy; m.m[3][2] += dz; return m; } OOINLINE OOMatrix OOMatrixTranslate(OOMatrix m, Vector offset) { return OOMatrixTranslateComponents(m, offset.x, offset.y, offset.z); } OOINLINE OOMatrix OOMatrixHPTranslate(OOMatrix m, HPVector offset) { return OOMatrixTranslateComponents(m, (OOScalar)offset.x, (OOScalar)offset.y, (OOScalar)offset.z); } OOINLINE OOMatrix OOMatrixForScale(OOScalar sx, OOScalar sy, OOScalar sz) { return OOMatrixConstruct ( sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1 ); } OOINLINE OOMatrix OOMatrixForScaleUniform(OOScalar s) { return OOMatrixForScale(s, s, s); } OOINLINE OOMatrix OOMatrixScale(OOMatrix m, OOScalar sx, OOScalar sy, OOScalar sz) { return OOMatrixMultiply(m, OOMatrixForScale(sx, sy, sz)); } OOINLINE OOMatrix OOMatrixScaleUniform(OOMatrix m, OOScalar s) { return OOMatrixScale(m, s, s, s); } OOINLINE OOMatrix OOMatrixRotateX(OOMatrix m, OOScalar angle) { return OOMatrixMultiply(m, OOMatrixForRotationX(angle)); } OOINLINE OOMatrix OOMatrixRotateY(OOMatrix m, OOScalar angle) { return OOMatrixMultiply(m, OOMatrixForRotationY(angle)); } OOINLINE OOMatrix OOMatrixRotateZ(OOMatrix m, OOScalar angle) { return OOMatrixMultiply(m, OOMatrixForRotationZ(angle)); } OOINLINE OOMatrix OOMatrixRotate(OOMatrix m, Vector axis, OOScalar angle) { return OOMatrixMultiply(m, OOMatrixForRotation(axis, angle)); } OOINLINE OOMatrix OOMatrixRotateQuaternion(OOMatrix m, Quaternion quat) { return OOMatrixMultiply(m, OOMatrixForQuaternionRotation(quat)); } OOINLINE bool OOMatrixIsIdentity(OOMatrix m) { return OOMatrixEqual(m, kIdentityMatrix); } OOINLINE void OOMatrixGetBasisVectors(OOMatrix m, Vector *outRight, Vector *outUp, Vector *outForward) { assert(outRight != NULL && outUp != NULL && outForward != NULL); *outRight = make_vector(m.m[0][0], m.m[1][0], m.m[2][0]); *outUp = make_vector(m.m[0][1], m.m[1][1], m.m[2][1]); *outForward = make_vector(m.m[0][2], m.m[1][2], m.m[2][2]); } OOINLINE OOMatrix OOMatrixTranspose(OOMatrix m) { return OOMatrixConstruct ( m.m[0][0], m.m[1][0], m.m[2][0], m.m[3][0], m.m[0][1], m.m[1][1], m.m[2][1], m.m[3][1], m.m[0][2], m.m[1][2], m.m[2][2], m.m[3][2], m.m[0][3], m.m[1][3], m.m[2][3], m.m[3][3] ); } #if OOMATHS_OPENGL_INTEGRATION OOINLINE OOMatrix OOMatrixLoadGLMatrix(GLenum matrixID) { OOMatrix m; glGetFloatv(matrixID, OOMatrixValuesForOpenGL(m)); return m; } #endif #endif /* INCLUDED_OOMATHS_h */ oolite-1.82/src/Core/OOMatrix.m000066400000000000000000000244601256642440500163140ustar00rootroot00000000000000/* OOMatrix.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "OOMaths.h" #import "OOOpenGLExtensionManager.h" const OOMatrix kIdentityMatrix = { .m = { {1.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 0.0f, 1.0f} }}; const OOMatrix kZeroMatrix = { .m = { {0.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f, 0.0f}, }}; OOMatrix OOMatrixForRotation(Vector axis, OOScalar angle) { axis = vector_normal(axis); OOScalar x = axis.x, y = axis.y, z = axis.z; OOScalar s = sin(angle), c = cos(angle); OOScalar t = 1.0f - c; // Lots of opportunity for common subexpression elimintation here, but I'll leave it to the compiler for now. return OOMatrixConstruct ( t * x * x + c, t * x * y + s * z, t * x * z - s * y, 0.0f, t * x * y - s * z, t * y * y + c, t * y * z + s * x, 0.0f, t * x * y + s * y, t * y * z - s * x, t * z * z + c, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f ); } OOMatrix OOMatrixForQuaternionRotation(Quaternion orientation) { OOScalar w, wz, wy, wx; OOScalar x, xz, xy, xx; OOScalar y, yz, yy; OOScalar z, zz; Quaternion q = orientation; quaternion_normalize(&q); w = q.w; z = q.z; y = q.y; x = q.x; xx = 2.0f * x; yy = 2.0f * y; zz = 2.0f * z; wx = w * xx; wy = w * yy; wz = w * zz; xx = x * xx; xy = x * yy; xz = x * zz; yy = y * yy; yz = y * zz; zz = z * zz; return OOMatrixConstruct ( 1.0f - yy - zz, xy - wz, xz + wy, 0.0f, xy + wz, 1.0f - xx - zz, yz - wx, 0.0f, xz - wy, yz + wx, 1.0f - xx - yy, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f ); } bool OOMatrixEqual(OOMatrix a, OOMatrix b) { OOScalar *ma = &a.m[0][0]; OOScalar *mb = &b.m[0][0]; unsigned i; for (i = 0; i < 16; i++) { if (*ma++ != *mb++) return false; } return true; } OOMatrix OOMatrixMultiply(OOMatrix a, OOMatrix b) { unsigned i = 0; OOMatrix r; // This is amenable to significant optimization with Altivec, and presumably also SSE. for (i = 0; i != 4; ++i) { r.m[i][0] = a.m[i][0] * b.m[0][0] + a.m[i][1] * b.m[1][0] + a.m[i][2] * b.m[2][0] + a.m[i][3] * b.m[3][0]; r.m[i][1] = a.m[i][0] * b.m[0][1] + a.m[i][1] * b.m[1][1] + a.m[i][2] * b.m[2][1] + a.m[i][3] * b.m[3][1]; r.m[i][2] = a.m[i][0] * b.m[0][2] + a.m[i][1] * b.m[1][2] + a.m[i][2] * b.m[2][2] + a.m[i][3] * b.m[3][2]; r.m[i][3] = a.m[i][0] * b.m[0][3] + a.m[i][1] * b.m[1][3] + a.m[i][2] * b.m[2][3] + a.m[i][3] * b.m[3][3]; } return r; } Vector OOVectorMultiplyMatrix(Vector v, OOMatrix m) { OOScalar x, y, z, w; x = m.m[0][0] * v.x + m.m[1][0] * v.y + m.m[2][0] * v.z + m.m[3][0]; y = m.m[0][1] * v.x + m.m[1][1] * v.y + m.m[2][1] * v.z + m.m[3][1]; z = m.m[0][2] * v.x + m.m[1][2] * v.y + m.m[2][2] * v.z + m.m[3][2]; w = m.m[0][3] * v.x + m.m[1][3] * v.y + m.m[2][3] * v.z + m.m[3][3]; w = 1.0f/w; return make_vector(x * w, y * w, z * w); } // HPVect: this loses precision because the matrix is single-precision // Best used for rotation matrices only - use HPvector_add for // translations of vectors if possible HPVector OOHPVectorMultiplyMatrix(HPVector v, OOMatrix m) { OOScalar x, y, z, w; x = m.m[0][0] * v.x + m.m[1][0] * v.y + m.m[2][0] * v.z + m.m[3][0]; y = m.m[0][1] * v.x + m.m[1][1] * v.y + m.m[2][1] * v.z + m.m[3][1]; z = m.m[0][2] * v.x + m.m[1][2] * v.y + m.m[2][2] * v.z + m.m[3][2]; w = m.m[0][3] * v.x + m.m[1][3] * v.y + m.m[2][3] * v.z + m.m[3][3]; w = 1.0f/w; return make_HPvector(x * w, y * w, z * w); } OOMatrix OOMatrixOrthogonalize(OOMatrix m) { // Simple orthogonalization: make everything orthogonal to everything else. Vector i;// = { m.m[0][0], m.m[1][0], m.m[2][0] }; // Overwritten without being used Vector j = { m.m[0][1], m.m[1][1], m.m[2][1] }; Vector k = { m.m[0][2], m.m[1][2], m.m[2][2] }; k = vector_normal(k); i = vector_normal(cross_product(j, k)); j = cross_product(k, i); m.m[0][0] = i.x; m.m[1][0] = i.y; m.m[2][0] = i.z; m.m[0][1] = j.x; m.m[1][1] = j.y; m.m[2][1] = j.z; m.m[0][2] = k.x; m.m[1][2] = k.y; m.m[2][2] = k.z; return m; } #if __OBJC__ NSString *OOMatrixDescription(OOMatrix matrix) { return [NSString stringWithFormat:@"{{%g, %g, %g, %g}, {%g, %g, %g, %g}, {%g, %g, %g, %g}, {%g, %g, %g, %g}}", matrix.m[0][0], matrix.m[0][1], matrix.m[0][2], matrix.m[0][3], matrix.m[1][0], matrix.m[1][1], matrix.m[1][2], matrix.m[1][3], matrix.m[2][0], matrix.m[2][1], matrix.m[2][2], matrix.m[2][3], matrix.m[3][0], matrix.m[3][1], matrix.m[3][2], matrix.m[3][3]]; } #endif OOMatrix OOMatrixForBillboard(HPVector bbPos, HPVector eyePos) { Vector v0, v1, v2, arbv; v0 = HPVectorToVector(HPvector_subtract(bbPos, eyePos)); // once the subtraction is done safe to lose precision since // we're now dealing with relatively small vectors v0 = vector_normal_or_fallback(v0, kBasisZVector); // arbitrary axis - not aligned with v0 if (EXPECT_NOT(v0.x == 0.0 && v0.y == 0.0)) arbv = kBasisXVector; else arbv = kBasisZVector; v1 = cross_product(v0, arbv); // 90 degrees to (v0 x arb1) v2 = cross_product(v0, v1); // 90 degrees to (v0 x v1) return OOMatrixFromBasisVectors(v1, v2, v0); } void OOMatrixRowSwap(OOMatrix *M, int row1, int row2) { if (row1<0 || row1 >= 4 || row2 < 0 || row2 >= 4 || row1 == row2 ) { return; } float x; int i; for (i = 0; i < 4; i++) { x = M->m[row1][i]; M->m[row1][i] = M->m[row2][i]; M->m[row2][i] = x; } return; } void OOMatrixRowScale(OOMatrix *M, int row, OOScalar factor) { if (row < 0 || row >= 4) { return; } int i; for (i = 0; i < 4; i++) { M->m[row][i] *= factor; } return; } void OOMatrixRowOperation(OOMatrix *M, int row1, OOScalar factor1, int row2, OOScalar factor2) { if (row1 < 0 || row1 >= 4 || row2 < 0 || row2 >= 4) { return; } int i; for (i = 0; i < 4; i++) { M->m[row1][i] = factor1 * M->m[row1][i] + factor2 * M->m[row2][i]; } return; } void OOMatrixColumnSwap(OOMatrix *M, int column1, int column2) { if (column1<0 || column1 >= 4 || column2 < 0 || column2 >= 4 || column1 == column2 ) { return; } float x; int i; for (i = 0; i < 4; i++) { x = M->m[i][column1]; M->m[i][column1] = M->m[i][column2]; M->m[i][column2] = x; } return; } void OOMatrixColumnScale(OOMatrix *M, int column, OOScalar factor) { if (column < 0 || column >= 4) { return; } int i; for (i = 0; i < 4; i++) { M->m[i][column] *= factor; } return; } void OOMatrixColumnOperation(OOMatrix *M, int column1, OOScalar factor1, int column2, OOScalar factor2) { if (column1 < 0 || column1 >= 4 || column2 < 0 || column2 >= 4) { return; } int i; for (i = 0; i < 4; i++) { M->m[i][column1] = factor1 * M->m[i][column1] + factor2 * M->m[i][column2]; } return; } OOMatrix OOMatrixLeftTransform(OOMatrix A, OOMatrix B) { int i, j; BOOL found; for (i = 0; i < 4; i++) { if (A.m[i][i] == 0.0) { found = NO; for (j = i+1; j < 4; j++) { if (A.m[j][i] != 0.0) { found = YES; OOMatrixColumnSwap(&A,i,j); OOMatrixColumnSwap(&B,i,j); break; } } if (!found) { return kZeroMatrix; } } OOMatrixColumnScale(&B, i, 1/A.m[i][i]); OOMatrixColumnScale(&A, i, 1/A.m[i][i]); for (j = i+1; j < 4; j++) { OOMatrixColumnOperation(&B, j, 1, i, -A.m[i][j]); OOMatrixColumnOperation(&A, j, 1, i, -A.m[i][j]); } } for (i = 3; i > 0; i--) { for (j = 0; j < i; j++) { OOMatrixColumnOperation(&B, j, 1, i, -A.m[i][j]); OOMatrixColumnOperation(&A, j, 1, i, -A.m[i][j]); } } return B; } OOMatrix OOMatrixRightTransform(OOMatrix A, OOMatrix B) { int i, j; BOOL found; for (i = 0; i < 4; i++) { if (A.m[i][i] == 0.0) { found = NO; for (j = i+1; j < 4; j++) { if (A.m[j][i] != 0.0) { found = YES; OOMatrixRowSwap(&A,i,j); OOMatrixRowSwap(&B,i,j); break; } } if (!found) { return kZeroMatrix; } } OOMatrixRowScale(&B, i, 1/A.m[i][i]); OOMatrixRowScale(&A, i, 1/A.m[i][i]); for (j = i+1; j < 4; j++) { if (A.m[j][i] != 0.0) { OOMatrixRowOperation(&B, j, 1, i, -A.m[j][i]); OOMatrixRowOperation(&A, j, 1, i, -A.m[j][i]); } } } for (i = 3; i > 0; i--) { for (j = 0; j < i; j++) { if (A.m[j][i] != 0.0) { OOMatrixRowOperation(&B, j, 1, i, -A.m[j][i]); OOMatrixRowOperation(&A, j, 1, i, -A.m[j][i]); } } } return B; } OOMatrix OOMatrixInverse(OOMatrix M) { return OOMatrixLeftTransform(M, kIdentityMatrix); } OOMatrix OOMatrixInverseWithDeterminant(OOMatrix M, OOScalar *d) { int i, j; OOMatrix B = kIdentityMatrix; BOOL found; *d = 1.0; for (i = 0; i < 4; i++) { if (M.m[i][i] == 0.0) { found = NO; for (j = i+1; j < 4; j++) { if (M.m[j][i] != 0.0) { found = YES; OOMatrixRowSwap(&M,i,j); OOMatrixRowSwap(&B,i,j); *d *= -1; break; } } if (!found) { *d = 0.0; return kZeroMatrix; } } *d /= M.m[i][i]; OOMatrixRowScale(&B, i, 1/M.m[i][i]); OOMatrixRowScale(&M, i, 1/M.m[i][i]); for (j = i+1; j < 4; j++) { if (M.m[j][i] != 0.0) { OOMatrixRowOperation(&B, j, 1, i, -M.m[j][i]); OOMatrixRowOperation(&M, j, 1, i, -M.m[j][i]); } } } for (i = 3; i > 0; i--) { for (j = 0; j < i; j++) { if (M.m[j][i] != 0.0) { OOMatrixRowOperation(&B, j, 1, i, -M.m[j][i]); OOMatrixRowOperation(&M, j, 1, i, -M.m[j][i]); } } } return B; } #if OOMATHS_OPENGL_INTEGRATION void GLUniformMatrix3(int location, OOMatrix M) { OOScalar m[9]; m[0] = M.m[0][0]; m[1] = M.m[0][1]; m[2] = M.m[0][2]; m[3] = M.m[1][0]; m[4] = M.m[1][1]; m[5] = M.m[1][2]; m[6] = M.m[2][0]; m[7] = M.m[2][1]; m[8] = M.m[2][2]; OOGL(glUniformMatrix3fvARB(location, 1, NO, m)); } #endif oolite-1.82/src/Core/OOMesh.h000066400000000000000000000114271256642440500157360ustar00rootroot00000000000000/* OOMesh.h Standard OODrawable for static meshes from DAT files. OOMeshes are immutable (and can therefore be shared). Avoid the temptation to add externally-visible mutator methods as it will break such sharing. (Sharing will be implemented when ship types are turned into objects instead of dictionaries; this is currently slated for post-1.70. -- Ahruman) Hmm. On further consideration, sharing will be problematic because of material bindings. Two possible solutions: separate mesh data into shared object with each mesh instance having its own set of materials but shared data, or retarget bindings each frame. -- Ahruman Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OODrawable.h" #import "OOOpenGL.h" #import "OOWeakReference.h" #import "OOOpenGLExtensionManager.h" @class OOMaterial, Octree; #define OOMESH_PROFILE 0 #if OOMESH_PROFILE @class OOProfilingStopwatch; #endif enum { kOOMeshMaxMaterials = 8 }; typedef uint16_t OOMeshSmoothGroup; typedef uint8_t OOMeshMaterialIndex, OOMeshMaterialCount; typedef uint32_t OOMeshVertexCount; typedef uint32_t OOMeshFaceCount; typedef uint8_t OOMeshFaceVertexCount; typedef struct { OOMeshSmoothGroup smoothGroup; OOMeshMaterialIndex materialIndex; GLuint vertex[3]; Vector normal; Vector tangent; GLfloat s[3]; GLfloat t[3]; } OOMeshFace; typedef struct { GLint *indexArray; GLfloat *textureUVArray; Vector *vertexArray; Vector *normalArray; Vector *tangentArray; GLuint count; } OOMeshDisplayLists; @interface OOMesh: OODrawable { @private uint8_t _normalMode: 2, brokenInRender: 1, listsReady: 1; OOMeshMaterialCount materialCount; OOMeshVertexCount vertexCount; OOMeshFaceCount faceCount; NSString *baseFile; Vector *_vertices; Vector *_normals; Vector *_tangents; OOMeshFace *_faces; // Redundancy! Needs fixing. OOMeshDisplayLists _displayLists; NSRange triangle_range[kOOMeshMaxMaterials]; NSString *materialKeys[kOOMeshMaxMaterials]; OOMaterial *materials[kOOMeshMaxMaterials]; GLuint displayList0; GLfloat collisionRadius; GLfloat maxDrawDistance; BoundingBox boundingBox; Octree *octree; NSMutableDictionary *_retainedObjects; NSDictionary *_materialDict; NSDictionary *_shadersDict; NSString *_cacheKey; NSDictionary *_shaderMacros; id _shaderBindingTarget; Vector _lastPosition; OOMatrix _lastRotMatrix; BoundingBox _lastBoundingBox; #if OO_MULTITEXTURE NSUInteger _textureUnitCount; #endif #if OOMESH_PROFILE OOProfilingStopwatch *_stopwatch; double _stopwatchLastTime; #endif } + (instancetype) meshWithName:(NSString *)name cacheKey:(NSString *)cacheKey materialDictionary:(NSDictionary *)materialDict shadersDictionary:(NSDictionary *)shadersDict smooth:(BOOL)smooth shaderMacros:(NSDictionary *)macros shaderBindingTarget:(id)object; + (instancetype) meshWithName:(NSString *)name cacheKey:(NSString *)cacheKey materialDictionary:(NSDictionary *)materialDict shadersDictionary:(NSDictionary *)shadersDict smooth:(BOOL)smooth shaderMacros:(NSDictionary *)macros shaderBindingTarget:(id)object scaleFactor:(float)factor; + (OOMaterial *) placeholderMaterial; - (NSString *) modelName; - (void) rebindMaterials; - (NSDictionary *) materials; - (NSDictionary *) shaders; - (size_t) vertexCount; - (size_t) faceCount; - (Octree *) octree; // This needs a better name. - (BoundingBox) findBoundingBoxRelativeToPosition:(Vector)opv basis:(Vector)ri :(Vector)rj :(Vector)rk selfPosition:(Vector)position selfBasis:(Vector)si :(Vector)sj :(Vector)sk; - (BoundingBox) findSubentityBoundingBoxWithPosition:(Vector)position rotMatrix:(OOMatrix)rotMatrix; - (OOMesh *) meshRescaledBy:(GLfloat)scaleFactor; @end #import "OOCacheManager.h" @interface OOCacheManager (Octree) + (Octree *)octreeForModel:(NSString *)inKey; + (void)setOctree:(Octree *)inOctree forModel:(NSString *)inKey; @end oolite-1.82/src/Core/OOMesh.m000066400000000000000000001663371256642440500157560ustar00rootroot00000000000000/* OOMesh.m A note on memory management: The dynamically-sized buffers used by OOMesh (_vertex etc) are the byte arrays of NSDatas, which are tracked using the _retainedObjects dictionary. This simplifies the implementation of -dealloc, but more importantly, it means bytes are refcounted. This means bytes read from the cache don't need to be copied, we just need to retain the relevant NSData object (by sticking it in _retainedObjects). Since _retainedObjects is a dictionary its members can be replaced, potentially allowing mutable meshes, although we have no use for this at present. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOMesh.h" #import "Universe.h" #import "OOMeshToOctreeConverter.h" #import "ResourceManager.h" #import "Entity.h" // for NO_DRAW_DISTANCE_FACTOR. #import "Octree.h" #import "OOMaterialConvenienceCreators.h" #import "OOBasicMaterial.h" #import "OOCollectionExtractors.h" #import "OOOpenGLExtensionManager.h" #import "OOGraphicsResetManager.h" #import "OODebugGLDrawing.h" #import "OOShaderMaterial.h" #import "OOMacroOpenGL.h" #import "OOProfilingStopwatch.h" #import "OODebugFlags.h" #import "NSObjectOOExtensions.h" #import "OOJavaScriptEngine.h" #import "OODebugStandards.h" // If set, collision octree depth varies depending on the size of the mesh. #define ADAPTIVE_OCTREE_DEPTH 1 // If set, cachable memory is scribbled with FEEDFACE to identify junk in cache. #define SCRIBBLE 0 enum { kBaseOctreeDepth = 5, // 32x32x32 // kMaxOctreeDepth declared in Octree.h. kSmallOctreeDepth = 4, // 16x16x16 kOctreeSizeThreshold = 900, // Size at which we start increasing octree depth kOctreeSmallSizeThreshold = 20 }; typedef enum { kNormalModePerFace, kNormalModeSmooth, kNormalModeExplicit } OOMeshNormalMode; static NSString * const kOOLogMeshDataNotFound = @"mesh.load.failed.fileNotFound"; static NSString * const kOOLogMeshTooManyMaterials = @"mesh.load.failed.tooManyMaterials"; #if OOMESH_PROFILE #define PROFILE(tag) do { _stopwatchLastTime = Profile(tag, _stopwatch, _stopwatchLastTime); } while (0) static OOTimeDelta Profile(NSString *tag, OOProfilingStopwatch *stopwatch, OOTimeDelta lastTime) { OOTimeDelta now = [stopwatch currentTime]; OOLog(@"mesh.profile", @"Mesh profile: stage %@, %g seconds (delta %g)", tag, now, now - lastTime); return now; } #else #define PROFILE(tag) do {} while (0) #endif /* VertexFaceRef List of indices of faces used by a given vertex. Always access using the provided functions. NOTE: VFRAddFace may use autoreleased memory. All accesses to a given VFR must be inside the same autorelease pool. */ enum { #if OOLITE_64_BIT kVertexFaceDefInternalCount = 11 // sizeof (VertexFaceRef) = 32 #else kVertexFaceDefInternalCount = 5 // sizeof (VertexFaceRef) = 16 #endif }; typedef struct VertexFaceRef { uint16_t internCount; uint16_t internFaces[kVertexFaceDefInternalCount]; NSMutableArray *extra; } VertexFaceRef; static void VFRAddFace(VertexFaceRef *vfr, NSUInteger index); static NSUInteger VFRGetCount(VertexFaceRef *vfr); static NSUInteger VFRGetFaceAtIndex(VertexFaceRef *vfr, NSUInteger index); @interface OOMesh (Private) - (id)initWithName:(NSString *)name cacheKey:(NSString *)cacheKey materialDictionary:(NSDictionary *)materialDict shadersDictionary:(NSDictionary *)shadersDict smooth:(BOOL)smooth shaderMacros:(NSDictionary *)macros shaderBindingTarget:(id)object scaleFactor:(float)scale; - (BOOL) loadData:(NSString *)filename scaleFactor:(float)scale; - (void) checkNormalsAndAdjustWinding; - (void) generateFaceTangents; - (void) calculateVertexNormalsAndTangentsWithFaceRefs:(VertexFaceRef *)faceRefs; - (void) calculateVertexTangentsWithFaceRefs:(VertexFaceRef *)faceRefs; - (void) deleteDisplayLists; - (NSDictionary*) modelData; - (BOOL) setModelFromModelData:(NSDictionary*) dict name:(NSString *)fileName; - (void) getNormal:(Vector *)outNormal andTangent:(Vector *)outTangent forVertex:(OOMeshVertexCount)v_index inSmoothGroup:(OOMeshSmoothGroup)smoothGroup; - (BOOL) setUpVertexArrays; - (void) calculateBoundingVolumes; - (void) rescaleByFactor:(GLfloat)factor; #ifndef NDEBUG - (void)debugDrawNormals; #endif // Manage set of objects we need to hang on to, particularly NSDatas owning buffers. - (void) setRetainedObject:(id)object forKey:(NSString *)key; - (void *) allocateBytesWithSize:(size_t)size count:(NSUInteger)count key:(NSString *)key; // Allocate all per-vertex/per-face buffers. - (BOOL) allocateVertexBuffersWithCount:(NSUInteger)count; - (BOOL) allocateNormalBuffersWithCount:(NSUInteger)count; - (BOOL) allocateFaceBuffersWithCount:(NSUInteger)count; - (BOOL) allocateVertexArrayBuffersWithCount:(NSUInteger)count; - (void) renameTexturesFrom:(NSString *)from to:(NSString *)to; @end @interface OOCacheManager (OOMesh) + (NSDictionary *)meshDataForName:(NSString *)inShipName; + (void)setMeshData:(NSDictionary *)inData forName:(NSString *)inShipName; @end static BOOL IsLegacyNormalMode(OOMeshNormalMode mode) { /* True for modes that predate the "normal mode" concept, i.e. per-face and smooth. These modes require automatic winding correction. */ switch (mode) { case kNormalModePerFace: case kNormalModeSmooth: return YES; case kNormalModeExplicit: return NO; } #ifndef NDEBUG [NSException raise:NSInvalidArgumentException format:@"Unexpected normal mode in %s", __PRETTY_FUNCTION__]; #endif return NO; } static BOOL IsPerVertexNormalMode(OOMeshNormalMode mode) { /* True for modes that have per-vertex normals, i.e. not per-face mode. */ switch (mode) { case kNormalModePerFace: return NO; case kNormalModeSmooth: case kNormalModeExplicit: return YES; } #ifndef NDEBUG [NSException raise:NSInvalidArgumentException format:@"Unexpected normal mode in %s", __PRETTY_FUNCTION__]; #endif return NO; } @implementation OOMesh + (instancetype) meshWithName:(NSString *)name cacheKey:(NSString *)cacheKey materialDictionary:(NSDictionary *)materialDict shadersDictionary:(NSDictionary *)shadersDict smooth:(BOOL)smooth shaderMacros:(NSDictionary *)macros shaderBindingTarget:(id)object { return [[[self alloc] initWithName:name cacheKey:cacheKey materialDictionary:materialDict shadersDictionary:shadersDict smooth:smooth shaderMacros:macros shaderBindingTarget:object scaleFactor:1.0f] autorelease]; } + (instancetype) meshWithName:(NSString *)name cacheKey:(NSString *)cacheKey materialDictionary:(NSDictionary *)materialDict shadersDictionary:(NSDictionary *)shadersDict smooth:(BOOL)smooth shaderMacros:(NSDictionary *)macros shaderBindingTarget:(id)object scaleFactor:(float)scale { return [[[self alloc] initWithName:name cacheKey:cacheKey materialDictionary:materialDict shadersDictionary:shadersDict smooth:smooth shaderMacros:macros shaderBindingTarget:object scaleFactor:scale] autorelease]; } + (OOMaterial *)placeholderMaterial { static OOBasicMaterial *placeholderMaterial = nil; if (placeholderMaterial == nil) { placeholderMaterial = [[OOBasicMaterial alloc] initWithName:@"/placeholder/" configuration:[[ResourceManager materialDefaults] oo_dictionaryForKey:@"no-textures-material"]]; } return placeholderMaterial; } - (id)init { self = [super init]; if (self == nil) return nil; baseFile = @"No Model"; #if OO_MULTITEXTURE _textureUnitCount = NSNotFound; #endif _lastPosition = kZeroVector; _lastRotMatrix = kZeroMatrix; // not identity _lastBoundingBox = kZeroBoundingBox; return self; } - (void) dealloc { unsigned i; DESTROY(baseFile); DESTROY(octree); [self deleteDisplayLists]; for (i = 0; i != kOOMeshMaxMaterials; ++i) { DESTROY(materials[i]); DESTROY(materialKeys[i]); } [[OOGraphicsResetManager sharedManager] unregisterClient:self]; DESTROY(_retainedObjects); DESTROY(_materialDict); DESTROY(_shadersDict); DESTROY(_cacheKey); DESTROY(_shaderMacros); DESTROY(_shaderBindingTarget); #if OOMESH_PROFILE DESTROY(_stopwatch); #endif [super dealloc]; } static NSString *NormalModeDescription(OOMeshNormalMode mode) { switch (mode) { case kNormalModePerFace: return @"per-face"; case kNormalModeSmooth: return @"smooth"; case kNormalModeExplicit: return @"explicit"; } return @"unknown"; } - (NSString *)descriptionComponents { return [NSString stringWithFormat:@"\"%@\", %zu vertices, %zu faces, radius: %g m normals: %@", [self modelName], [self vertexCount], [self faceCount], [self collisionRadius], NormalModeDescription(_normalMode)]; } - (id)copyWithZone:(NSZone *)zone { if (zone == [self zone]) return [self retain]; // OK because we're immutable seen from the outside else return [self mutableCopyWithZone:zone]; } - (NSString *) modelName { return baseFile; } - (size_t)vertexCount { return vertexCount; } - (size_t)faceCount { return faceCount; } - (void)renderOpaqueParts { OO_ENTER_OPENGL(); OOSetOpenGLState(OPENGL_STATE_OPAQUE); OOGL(glVertexPointer(3, GL_FLOAT, 0, _displayLists.vertexArray)); OOGL(glNormalPointer(GL_FLOAT, 0, _displayLists.normalArray)); #if OO_SHADERS if ([[OOOpenGLExtensionManager sharedManager] shadersSupported]) { OOGL(glEnableVertexAttribArrayARB(kTangentAttributeIndex)); OOGL(glVertexAttribPointerARB(kTangentAttributeIndex, 3, GL_FLOAT, GL_FALSE, 0, _displayLists.tangentArray)); } #endif BOOL usingNormalsAsTexCoords = NO; OOMeshMaterialIndex ti; /* FIXME: really, really horrible hack to set up texture coordinates for each texture unit. Very messy and still fails to handle some possibly- basic stuff, like switching usingNormalsAsTexCoords per texture unit. The right way to do this is probably to move attribute setup into the material model. -- Ahruman 2010-04-12 */ #if OO_MULTITEXTURE if (_textureUnitCount == NSNotFound) { _textureUnitCount = 0; for (ti = 0; ti < materialCount; ti++) { NSUInteger count = [materials[ti] countOfTextureUnitsWithBaseCoordinates]; if (_textureUnitCount < count) _textureUnitCount = count; } } NSUInteger unit; if (_textureUnitCount <= 1) { OOGL(glEnableClientState(GL_TEXTURE_COORD_ARRAY)); } else { /* It should not be possible to have multiple texture units if texture combiners are not available. */ NSAssert2([[OOOpenGLExtensionManager sharedManager] textureCombinersSupported], @"Mesh %@ uses %lu texture units, but multitexturing is not available.", [self shortDescription], _textureUnitCount); for (unit = 0; unit < _textureUnitCount; unit++) { OOGL(glClientActiveTextureARB(GL_TEXTURE0_ARB + unit)); OOGL(glEnableClientState(GL_TEXTURE_COORD_ARRAY)); } } #else OOGL(glEnableClientState(GL_TEXTURE_COORD_ARRAY)); #endif @try { if (!listsReady) { OOGL(displayList0 = glGenLists(materialCount)); // Ensure all textures are loaded for (ti = 0; ti < materialCount; ti++) { [materials[ti] ensureFinishedLoading]; } } for (ti = 0; ti < materialCount; ti++) { BOOL wantsNormalsAsTextureCoordinates = [materials[ti] wantsNormalsAsTextureCoordinates]; if (ti == 0 || wantsNormalsAsTextureCoordinates != usingNormalsAsTexCoords) { // FIXME: enabling/disabling texturing should be handled by the material. #if OO_MULTITEXTURE for (unit = 0; unit < _textureUnitCount; unit++) { if (_textureUnitCount > 1) { OOGL(glClientActiveTextureARB(GL_TEXTURE0_ARB + unit)); OOGL(glActiveTextureARB(GL_TEXTURE0_ARB + unit)); } #endif if (!wantsNormalsAsTextureCoordinates) { OOGL(glDisable(GL_TEXTURE_CUBE_MAP)); OOGL(glTexCoordPointer(2, GL_FLOAT, 0, _displayLists.textureUVArray)); /* FIXME: Not including the line below breaks multitexturing in no-shaders mode. However, the OpenGL state manager should probably be handling this; TEXTURE_2D is part of OPENGL_STATE_OPAQUE, which has already been set. - Nikos 20130103 */ OOGL(glEnable(GL_TEXTURE_2D)); } else { OOGL(glDisable(GL_TEXTURE_2D)); OOGL(glTexCoordPointer(3, GL_FLOAT, 0, _displayLists.vertexArray)); OOGL(glEnable(GL_TEXTURE_CUBE_MAP)); } #if OO_MULTITEXTURE } #endif usingNormalsAsTexCoords = wantsNormalsAsTextureCoordinates; } [materials[ti] apply]; OOGL(glDrawArrays(GL_TRIANGLES, triangle_range[ti].location, triangle_range[ti].length)); } listsReady = YES; brokenInRender = NO; } @catch (NSException *exception) { if (!brokenInRender) { OOLog(kOOLogException, @"***** %s for %@ encountered exception: %@ : %@ *****", __PRETTY_FUNCTION__, self, [exception name], [exception reason]); brokenInRender = YES; } if ([[exception name] hasPrefix:@"Oolite"]) [UNIVERSE handleOoliteException:exception]; // handle these ourself else @throw exception; // pass these on } #if OO_SHADERS if ([[OOOpenGLExtensionManager sharedManager] shadersSupported]) { OOGL(glDisableVertexAttribArrayARB(kTangentAttributeIndex)); } #endif [OOMaterial applyNone]; OOCheckOpenGLErrors(@"OOMesh after drawing %@", self); #if OO_MULTITEXTURE if (_textureUnitCount <= 1) { OOGL(glDisableClientState(GL_TEXTURE_COORD_ARRAY)); } else { for (unit = 0; unit < _textureUnitCount; unit++) { OOGL(glClientActiveTextureARB(GL_TEXTURE0_ARB + unit)); OOGL(glDisableClientState(GL_TEXTURE_COORD_ARRAY)); } OOGL(glClientActiveTextureARB(GL_TEXTURE0_ARB)); OOGL(glActiveTextureARB(GL_TEXTURE0_ARB)); } #else OOGL(glDisableClientState(GL_TEXTURE_COORD_ARRAY)); #endif #ifndef NDEBUG if (gDebugFlags & DEBUG_DRAW_NORMALS) [self debugDrawNormals]; if (gDebugFlags & DEBUG_OCTREE_DRAW) [[self octree] drawOctree]; #endif OOVerifyOpenGLState(); } - (void) rebindMaterials { OOMeshMaterialCount i; OOMaterial *material = nil; if (materialCount != 0) { for (i = 0; i != materialCount; ++i) { OOMaterial *oldMaterial = materials[i]; if (![materialKeys[i] isEqualToString:@"_oo_placeholder_material"]) { material = [OOMaterial materialWithName:materialKeys[i] cacheKey:_cacheKey materialDictionary:_materialDict shadersDictionary:_shadersDict macros:_shaderMacros bindingTarget:[_shaderBindingTarget weakRefUnderlyingObject] // Windows DEP fix. forSmoothedMesh:IsPerVertexNormalMode(_normalMode)]; } else { material = nil; } if (material != nil) { materials[i] = [material retain]; } else { materials[i] = [[OOMesh placeholderMaterial] retain]; } /* Release is deferred to here to ensure we don't end up releasing a texture that's not in the recent-cache and then reloading it. */ [oldMaterial release]; } } } - (NSDictionary *) materials { return _materialDict; } - (NSDictionary *) shaders { return _shadersDict; } - (BOOL)hasOpaqueParts { return YES; } - (GLfloat)collisionRadius { return collisionRadius; } - (GLfloat)maxDrawDistance { return maxDrawDistance; } #if ADAPTIVE_OCTREE_DEPTH - (unsigned) octreeDepth { float threshold = kOctreeSizeThreshold; unsigned result = kBaseOctreeDepth; GLfloat xs, ys, zs, t, size; bounding_box_get_dimensions(boundingBox, &xs, &ys, &zs); // Shuffle dimensions around so zs is smallest if (xs < zs) { t = zs; zs = xs; xs = t; } if (ys < zs) { t = zs; zs = ys; ys = t; } size = (xs + ys) / 2.0f; // Use average of two largest if (size < kOctreeSmallSizeThreshold) result = kSmallOctreeDepth; else while (result < kMaxOctreeDepth) { if (size < threshold) break; threshold *= 2.0f; result++; } OOLog(@"mesh.load.octree.size", @"Selected octree depth %u for size %g for %@", result, size, baseFile); return result; } #else - (unsigned) octreeDepth { return kBaseOctreeDepth; } #endif - (Octree *)octree { if (octree == nil) { octree = [[OOCacheManager octreeForModel:baseFile] retain]; if (octree == nil) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; OOMeshToOctreeConverter *converter = [OOMeshToOctreeConverter converterWithCapacity:faceCount]; OOMeshFaceCount i; for (i = 0; i < faceCount; i++) { // Somewhat surprisingly, this method doesn't even show up in profiles. -- Ahruman 2012-09-22 Triangle tri; tri.v[0] = _vertices[_faces[i].vertex[0]]; tri.v[1] = _vertices[_faces[i].vertex[1]]; tri.v[2] = _vertices[_faces[i].vertex[2]]; [converter addTriangle:tri]; } octree = [converter findOctreeToDepth:[self octreeDepth]]; [octree retain]; [OOCacheManager setOctree:octree forModel:baseFile]; [pool release]; } } return octree; } - (BoundingBox) findBoundingBoxRelativeToPosition:(Vector)opv basis:(Vector)ri :(Vector)rj :(Vector)rk selfPosition:(Vector)position selfBasis:(Vector)si :(Vector)sj :(Vector)sk { BoundingBox result; Vector pv, rv; // FIXME: rewrite with matrices Vector rpos = vector_subtract(position, opv); // model origin relative to opv rv.x = dot_product(ri,rpos); rv.y = dot_product(rj,rpos); rv.z = dot_product(rk,rpos); // model origin rel to opv in ijk if (EXPECT_NOT(vertexCount < 1)) { bounding_box_reset_to_vector(&result, rv); } else { pv.x = rpos.x + si.x * _vertices[0].x + sj.x * _vertices[0].y + sk.x * _vertices[0].z; pv.y = rpos.y + si.y * _vertices[0].x + sj.y * _vertices[0].y + sk.y * _vertices[0].z; pv.z = rpos.z + si.z * _vertices[0].x + sj.z * _vertices[0].y + sk.z * _vertices[0].z; // _vertices[0] position rel to opv rv.x = dot_product(ri, pv); rv.y = dot_product(rj, pv); rv.z = dot_product(rk, pv); // _vertices[0] position rel to opv in ijk bounding_box_reset_to_vector(&result, rv); } OOMeshVertexCount i; for (i = 1; i < vertexCount; i++) { pv.x = rpos.x + si.x * _vertices[i].x + sj.x * _vertices[i].y + sk.x * _vertices[i].z; pv.y = rpos.y + si.y * _vertices[i].x + sj.y * _vertices[i].y + sk.y * _vertices[i].z; pv.z = rpos.z + si.z * _vertices[i].x + sj.z * _vertices[i].y + sk.z * _vertices[i].z; rv.x = dot_product(ri, pv); rv.y = dot_product(rj, pv); rv.z = dot_product(rk, pv); bounding_box_add_vector(&result, rv); } return result; } - (BoundingBox)findSubentityBoundingBoxWithPosition:(Vector)position rotMatrix:(OOMatrix)rotMatrix { // HACK! Should work out what the various bounding box things do and make it neat and consistent. // FIXME: this is a bottleneck. // Try to fix bottleneck by caching for common case where subentity // pos+rot is constant from frame to frame. - CIM if (vector_equal(position,_lastPosition) && OOMatrixEqual(rotMatrix,_lastRotMatrix)) { return _lastBoundingBox; } BoundingBox result; Vector v; v = vector_add(position, OOVectorMultiplyMatrix(_vertices[0], rotMatrix)); bounding_box_reset_to_vector(&result,v); OOMeshVertexCount i; for (i = 1; i < vertexCount; i++) { v = vector_add(position, OOVectorMultiplyMatrix(_vertices[i], rotMatrix)); bounding_box_add_vector(&result,v); } _lastBoundingBox = result; _lastPosition = position; _lastRotMatrix = rotMatrix; return result; } - (OOMesh *)meshRescaledBy:(GLfloat)scaleFactor { OOMesh *result = [self mutableCopy]; [result rescaleByFactor:scaleFactor]; return [result autorelease]; } - (void)setBindingTarget:(id)target { unsigned i; for (i = 0; i != kOOMeshMaxMaterials; ++i) { [materials[i] setBindingTarget:target]; } } #ifndef NDEBUG - (void)dumpSelfState { [super dumpSelfState]; if (baseFile != nil) OOLog(@"dumpState.mesh", @"Model file: %@", baseFile); OOLog(@"dumpState.mesh", @"Vertex count: %u, face count: %u", vertexCount, faceCount); OOLog(@"dumpState.mesh", @"Normals: %@", NormalModeDescription(_normalMode)); } #endif #ifndef NDEBUG - (NSSet *) allTextures { NSMutableSet *result = [NSMutableSet set]; OOMeshMaterialCount i; for (i = 0; i != materialCount; i++) { [result unionSet:[materials[i] allTextures]]; } return result; } - (size_t) totalSize { size_t result = [super totalSize]; if (_vertices != NULL) result += sizeof *_vertices * vertexCount; if (_normals != NULL) result += sizeof *_normals * vertexCount; if (_tangents != NULL) result += sizeof *_tangents * vertexCount; if (_faces != NULL) result += sizeof *_faces * faceCount; result += _displayLists.count * (sizeof (GLint) + sizeof (GLfloat) + sizeof (Vector) * 3); OOMeshMaterialCount i; for (i = 0; i != materialCount; i++) { result += [materials[i] oo_objectSize]; } result += [octree totalSize]; return result; } #endif /* This method exists purely to suppress Clang static analyzer warnings that these ivars are unused (but may be used by categories, which they are). FIXME: there must be a feature macro we can use to avoid actually building this into the app, but I can't find it in docs. */ - (BOOL) suppressClangStuff { return _normals && _tangents && _faces && boundingBox.min.x; } @end @implementation OOMesh (Private) - (id)initWithName:(NSString *)name cacheKey:(NSString *)cacheKey materialDictionary:(NSDictionary *)materialDict shadersDictionary:(NSDictionary *)shadersDict smooth:(BOOL)smooth shaderMacros:(NSDictionary *)macros shaderBindingTarget:(id)target scaleFactor:(float)scale { OOJS_PROFILE_ENTER self = [super init]; if (self == nil) return nil; NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; _normalMode = smooth ? kNormalModeSmooth : kNormalModePerFace; #if OOMESH_PROFILE _stopwatch = [[OOProfilingStopwatch alloc] init]; #endif if ([self loadData:name scaleFactor:scale]) { [self calculateBoundingVolumes]; PROFILE(@"finished calculateBoundingVolumes (again\?\?)"); baseFile = [name copy]; /* New in r3033: save the material-defining parameters here so we can rebind the materials at any time. -- Ahruman 2010-02-17 */ _materialDict = [materialDict copy]; _shadersDict = [shadersDict copy]; _cacheKey = [cacheKey copy]; _shaderMacros = [macros copy]; _shaderBindingTarget = [target weakRetain]; [self rebindMaterials]; PROFILE(@"finished material setup"); [[OOGraphicsResetManager sharedManager] registerClient:self]; } else { [self release]; self = nil; } #if OOMESH_PROFILE DESTROY(_stopwatch); #endif #if OO_MULTITEXTURE if (EXPECT(self != nil)) { _textureUnitCount = NSNotFound; } #endif [pool release]; return self; OOJS_PROFILE_EXIT } - (id)mutableCopyWithZone:(NSZone *)zone { OOMesh *result = nil; OOMeshMaterialCount i; result = (OOMesh *)NSCopyObject(self, 0, zone); if (result != nil) { [result->baseFile retain]; [result->octree retain]; [result->_retainedObjects retain]; [result->_materialDict retain]; [result->_shadersDict retain]; [result->_cacheKey retain]; [result->_shaderMacros retain]; [result->_shaderBindingTarget retain]; for (i = 0; i != kOOMeshMaxMaterials; ++i) { [result->materialKeys[i] retain]; [result->materials[i] retain]; } // Reset unsharable GL state result->listsReady = NO; [[OOGraphicsResetManager sharedManager] registerClient:result]; } return result; } - (void) deleteDisplayLists { if (listsReady) { OO_ENTER_OPENGL(); OOGL(glDeleteLists(displayList0, materialCount)); listsReady = NO; } } - (void) resetGraphicsState { [self deleteDisplayLists]; [self rebindMaterials]; _textureUnitCount = NSNotFound; } - (NSDictionary *)modelData { OOJS_PROFILE_ENTER NSNumber *vertCnt = nil, *faceCnt = nil; NSData *vertData = nil, *normData = nil, *tanData = nil, *faceData = nil; NSArray *mtlKeys = nil; NSNumber *normMode = nil; BOOL includeNormals = IsPerVertexNormalMode(_normalMode); // Prepare cache data elements. vertCnt = [NSNumber numberWithUnsignedInt:vertexCount]; faceCnt = [NSNumber numberWithUnsignedInt:faceCount]; vertData = [_retainedObjects objectForKey:@"vertices"]; faceData = [_retainedObjects objectForKey:@"faces"]; if (includeNormals) { normData = [_retainedObjects objectForKey:@"normals"]; tanData = [_retainedObjects objectForKey:@"tangents"]; } if (materialCount != 0) { mtlKeys = [NSArray arrayWithObjects:materialKeys count:materialCount]; } else { mtlKeys = [NSArray array]; } normMode = [NSNumber numberWithUnsignedChar:_normalMode]; // Ensure we have all the required data elements. if (vertCnt == nil || faceCnt == nil || vertData == nil || faceData == nil || mtlKeys == nil || normMode == nil) { return nil; } if (includeNormals) { if (normData == nil || tanData == nil) return nil; } // All OK; stick 'em in a dictionary. return [NSDictionary dictionaryWithObjectsAndKeys: vertCnt, @"vertex count", vertData, @"vertex data", faceCnt, @"face count", faceData, @"face data", mtlKeys, @"material keys", normMode, @"normal mode", /* NOTE: order matters. Since normData and tanData are last, if they're nil the dictionary will be built without them, which is desired behaviour. */ normData, @"normal data", tanData, @"tangent data", nil]; OOJS_PROFILE_EXIT } - (BOOL)setModelFromModelData:(NSDictionary *)dict name:(NSString *)fileName { OOJS_PROFILE_ENTER NSData *vertData = nil, *normData = nil, *tanData = nil, *faceData = nil; NSArray *mtlKeys = nil; NSString *key = nil; unsigned i; if (dict == nil || ![dict isKindOfClass:[NSDictionary class]]) return NO; vertexCount = [dict oo_unsignedIntForKey:@"vertex count"]; faceCount = [dict oo_unsignedIntForKey:@"face count"]; if (vertexCount == 0 || faceCount == 0) return NO; // Read data elements from dictionary. vertData = [dict oo_dataForKey:@"vertex data"]; faceData = [dict oo_dataForKey:@"face data"]; mtlKeys = [dict oo_arrayForKey:@"material keys"]; _normalMode = [dict oo_unsignedCharForKey:@"normal mode"]; BOOL includeNormals = IsPerVertexNormalMode(_normalMode); // Ensure we have all the required data elements. if (vertData == nil || faceData == nil || mtlKeys == nil) { OOLog(@"mesh.load.error.badCacheData", @"Ignoring bad cache data for mesh \"%@\".", fileName); return NO; } if (includeNormals) { normData = [dict oo_dataForKey:@"normal data"]; tanData = [dict oo_dataForKey:@"tangent data"]; if (normData == nil || tanData == nil) { OOLog(@"mesh.load.error.badCacheData", @"Ignoring bad normal/tangent cache data for mesh \"%@\".", fileName); return NO; } } // Ensure data objects are of correct size. if ([vertData length] != sizeof *_vertices * vertexCount) return NO; if ([faceData length] != sizeof *_faces * faceCount) return NO; if (includeNormals) { if ([normData length] != sizeof *_normals * vertexCount) return NO; if ([tanData length] != sizeof *_tangents * vertexCount) return NO; } // Retain data. _vertices = (Vector *)[vertData bytes]; [self setRetainedObject:vertData forKey:@"vertices"]; _faces = (OOMeshFace *)[faceData bytes]; [self setRetainedObject:faceData forKey:@"faces"]; if (includeNormals) { _normals = (Vector *)[normData bytes]; [self setRetainedObject:normData forKey:@"normals"]; _tangents = (Vector *)[tanData bytes]; [self setRetainedObject:tanData forKey:@"tangents"]; } else { _normals = NULL; _tangents = NULL; } // Copy material keys. materialCount = [mtlKeys count]; for (i = 0; i != materialCount; ++i) { key = [mtlKeys oo_stringAtIndex:i]; if (key != nil) materialKeys[i] = [key copy]; else { OOLog(@"mesh.load.error.badCacheData", @"Ignoring bad cache data for mesh \"%@\".", fileName); return NO; } } return YES; OOJS_PROFILE_EXIT } - (BOOL)loadData:(NSString *)filename scaleFactor:(float)scale { OOJS_PROFILE_ENTER NSScanner *scanner; NSDictionary *cacheData = nil; BOOL failFlag = NO; NSString *failString = @"***** "; unsigned i, j; NSMutableDictionary *texFileName2Idx = nil; NSString *cacheKey = nil; BOOL using_preloaded = NO; cacheKey = [NSString stringWithFormat:@"%@:%u:%.3f", filename, _normalMode, scale]; cacheData = [OOCacheManager meshDataForName:cacheKey]; if (cacheData != nil) { if ([self setModelFromModelData:cacheData name:filename]) { using_preloaded = YES; PROFILE(@"loaded from cache"); OOLog(@"mesh.load.cached", @"Retrieved mesh \"%@\" from cache.", filename); } } if (!using_preloaded) { OOLog(@"mesh.load.uncached", @"Mesh \"%@\" is not in cache, loading.", filename); NSCharacterSet *whitespaceCharSet = [NSCharacterSet whitespaceCharacterSet]; NSCharacterSet *whitespaceAndNewlineCharSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; #if OOLITE_MAC_OS_X NSCharacterSet *newlineCharSet = [NSCharacterSet newlineCharacterSet]; #else static NSCharacterSet *newlineCharSet = nil; if (newlineCharSet == nil) { NSMutableCharacterSet *temp = [[whitespaceAndNewlineCharSet mutableCopy] autorelease]; [temp formIntersectionWithCharacterSet:[whitespaceCharSet invertedSet]]; newlineCharSet = [temp copy]; } #endif texFileName2Idx = [NSMutableDictionary dictionary]; { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; NSString *data = [ResourceManager stringFromFilesNamed:filename inFolder:@"Models" cache:NO]; if (data == nil) { // Model not found OOLog(kOOLogMeshDataNotFound, @"***** ERROR: could not find %@", filename); OOStandardsError(@"Model file not found"); return NO; } // strip out comments and commas between values NSMutableArray *lines = [NSMutableArray arrayWithArray:[data componentsSeparatedByString:@"\n"]]; for (i = 0; i < [lines count]; i++) { NSString *line = [lines objectAtIndex:i]; NSArray *parts; // // comments // parts = [line componentsSeparatedByString:@"#"]; line = [parts objectAtIndex:0]; parts = [line componentsSeparatedByString:@"//"]; line = [parts objectAtIndex:0]; // // commas // line = [[line componentsSeparatedByString:@","] componentsJoinedByString:@" "]; // [lines replaceObjectAtIndex:i withObject:line]; } data = [lines componentsJoinedByString:@"\n"]; scanner = [NSScanner scannerWithString:data]; [scanner retain]; [pool release]; [scanner autorelease]; } PROFILE(@"finished preprocessing"); // get number of vertices // [scanner setScanLocation:0]; //reset if ([scanner scanString:@"NVERTS" intoString:NULL]) { int n_v; if ([scanner scanInt:&n_v]) vertexCount = n_v; else { failFlag = YES; failString = [NSString stringWithFormat:@"%@Failed to read value of NVERTS\n",failString]; } } else { failFlag = YES; failString = [NSString stringWithFormat:@"%@Failed to read NVERTS\n",failString]; } if (![self allocateVertexBuffersWithCount:vertexCount]) { OOLog(kOOLogAllocationFailure, @"***** ERROR: failed to allocate memory for model %@ (%u vertices).", filename, vertexCount); return NO; } // get number of faces if ([scanner scanString:@"NFACES" intoString:NULL]) { int n_f; if ([scanner scanInt:&n_f]) { faceCount = n_f; } else { failFlag = YES; failString = [NSString stringWithFormat:@"%@Failed to read value of NFACES\n",failString]; } } else { failFlag = YES; failString = [NSString stringWithFormat:@"%@Failed to read NFACES\n",failString]; } // Allocate face->vertex table. size_t faceRefSize = sizeof (VertexFaceRef) * vertexCount; VertexFaceRef *faceRefs = calloc(1, faceRefSize); if (faceRefs != NULL) { // use an NSData to effectively autorelease it. NSData *faceRefHolder = [NSData dataWithBytesNoCopy:faceRefs length:faceRefSize freeWhenDone:YES]; if (faceRefHolder == nil) { free(faceRefs); faceRefs = NULL; } } if (faceRefs == NULL || ![self allocateFaceBuffersWithCount:faceCount]) { OOLog(kOOLogAllocationFailure, @"***** ERROR: failed to allocate memory for model %@ (%u vertices, %u faces).", filename, vertexCount, faceCount); return NO; } // get vertex data if ([scanner scanString:@"VERTEX" intoString:NULL]) { for (j = 0; j < vertexCount; j++) { float x, y, z; if (!failFlag) { if (![scanner scanFloat:&x]) failFlag = YES; if (![scanner scanFloat:&y]) failFlag = YES; if (![scanner scanFloat:&z]) failFlag = YES; if (!failFlag) { _vertices[j] = make_vector(x*scale, y*scale, z*scale); } else { failString = [NSString stringWithFormat:@"%@Failed to read a value for vertex[%d] in %@\n", failString, j, @"VERTEX"]; } } } } else { failFlag = YES; failString = [NSString stringWithFormat:@"%@Failed to find VERTEX data\n",failString]; } // get face data if ([scanner scanString:@"FACES" intoString:NULL]) { for (j = 0; j < faceCount; j++) { int r, g, b; float nx, ny, nz; int n_v; if (!failFlag) { // colors if (![scanner scanInt:&r]) failFlag = YES; if (![scanner scanInt:&g]) failFlag = YES; if (![scanner scanInt:&b]) failFlag = YES; if (!failFlag) { _faces[j].smoothGroup = r; } else { failString = [NSString stringWithFormat:@"%@Failed to read a color for face[%d] in FACES\n", failString, j]; } // normal if (![scanner scanFloat:&nx]) failFlag = YES; if (![scanner scanFloat:&ny]) failFlag = YES; if (![scanner scanFloat:&nz]) failFlag = YES; if (!failFlag) { _faces[j].normal = vector_normal(make_vector(nx, ny, nz)); } else { failString = [NSString stringWithFormat:@"%@Failed to read a normal for face[%d] in FACES\n", failString, j]; } // vertices if ([scanner scanInt:&n_v]) { if (n_v < 3) { failFlag = YES; failString = [NSString stringWithFormat:@"%@Face[%u] has fewer than three vertices.\n", failString, j]; } else if (n_v > 3) { OOLogWARN(@"mesh.load.warning.nonTriangular", @"Face[%u] of %@ has %u vertices specified. Only the first three will be used.", j, baseFile, n_v); n_v = 3; } } else { failFlag = YES; failString = [NSString stringWithFormat:@"%@Failed to read number of vertices for face[%d] in FACES\n", failString, j]; } if (!failFlag) { int vi; for (i = 0; (int)i < n_v; i++) { if ([scanner scanInt:&vi]) { _faces[j].vertex[i] = vi; if (faceRefs != NULL) VFRAddFace(&faceRefs[vi], j); } else { failFlag = YES; failString = [NSString stringWithFormat:@"%@Failed to read vertex[%d] for face[%d] in FACES\n", failString, i, j]; } } } } } } else { failFlag = YES; failString = [NSString stringWithFormat:@"%@Failed to find FACES data\n",failString]; } // Get textures data. if ([scanner scanString:@"TEXTURES" intoString:NULL]) { for (j = 0; j < faceCount; j++) { NSString *materialKey; float max_x, max_y; float s, t; if (!failFlag) { // materialKey // [scanner scanCharactersFromSet:whitespaceAndNewlineCharSet intoString:NULL]; if (![scanner scanUpToCharactersFromSet:whitespaceCharSet intoString:&materialKey]) { failFlag = YES; failString = [NSString stringWithFormat:@"%@Failed to read texture filename for face[%d] in TEXTURES\n", failString, j]; } else { NSNumber *index = [texFileName2Idx objectForKey:materialKey]; if (index != nil) { _faces[j].materialIndex = [index unsignedIntValue]; } else { if (materialCount == kOOMeshMaxMaterials) { OOLog(kOOLogMeshTooManyMaterials, @"***** ERROR: model %@ has too many materials (maximum is %d)", filename, kOOMeshMaxMaterials); return NO; } _faces[j].materialIndex = materialCount; materialKeys[materialCount] = [materialKey retain]; index = [NSNumber numberWithUnsignedInt:materialCount]; [texFileName2Idx setObject:index forKey:materialKey]; ++materialCount; } } // texture size // if (!failFlag) { if (![scanner scanFloat:&max_x]) failFlag = YES; if (![scanner scanFloat:&max_y]) failFlag = YES; if (failFlag) failString = [NSString stringWithFormat:@"%@Failed to read texture size for max_x and max_y in face[%d] in TEXTURES\n", failString, j]; } // vertices // if (!failFlag) { for (i = 0; i < 3; i++) { if (![scanner scanFloat:&s]) failFlag = YES; if (![scanner scanFloat:&t]) failFlag = YES; if (!failFlag) { _faces[j].s[i] = s / max_x; _faces[j].t[i] = t / max_y; } else failString = [NSString stringWithFormat:@"%@Failed to read s t coordinates for vertex[%d] in face[%d] in TEXTURES\n", failString, i, j]; } } } } } else { failFlag = YES; failString = [failString stringByAppendingString:@"Failed to find TEXTURES data (will use placeholder material)\n"]; materialKeys[0] = @"_oo_placeholder_material"; materialCount = 1; for (j = 0; j < faceCount; j++) { _faces[j].materialIndex = 0; } } if ([scanner scanString:@"NAMES" intoString:NULL]) { unsigned int count; if (![scanner scanInt:(int *)&count]) { failFlag = YES; failString = [failString stringByAppendingString:@"Expected count after NAMES\n"]; } else { for (j = 0; j < count; j++) { NSString *name = nil; [scanner scanCharactersFromSet:whitespaceAndNewlineCharSet intoString:NULL]; if (![scanner scanUpToCharactersFromSet:newlineCharSet intoString:&name]) { failFlag = YES; failString = [failString stringByAppendingString:@"Expected file name\n"]; } else { [self renameTexturesFrom:[NSString stringWithFormat:@"%u", j] to:name]; } } } } BOOL explicitTangents = NO; // Get explicit normals. if ([scanner scanString:@"NORMALS" intoString:NULL]) { _normalMode = kNormalModeExplicit; if (![self allocateNormalBuffersWithCount:vertexCount]) { OOLog(kOOLogAllocationFailure, @"***** ERROR: failed to allocate memory for model %@ (%u vertices).", filename, vertexCount); return NO; } for (j = 0; j < vertexCount; j++) { float x, y, z; if (!failFlag) { if (![scanner scanFloat:&x]) failFlag = YES; if (![scanner scanFloat:&y]) failFlag = YES; if (![scanner scanFloat:&z]) failFlag = YES; if (!failFlag) { _normals[j] = vector_normal(make_vector(x, y, z)); } else { failString = [NSString stringWithFormat:@"%@Failed to read a value for vertex[%d] in %@\n", failString, j, @"NORMALS"]; } } } // Get explicit tangents (only together with vertices). if ([scanner scanString:@"TANGENTS" intoString:NULL]) { for (j = 0; j < vertexCount; j++) { float x, y, z; if (!failFlag) { if (![scanner scanFloat:&x]) failFlag = YES; if (![scanner scanFloat:&y]) failFlag = YES; if (![scanner scanFloat:&z]) failFlag = YES; if (!failFlag) { _tangents[j] = vector_normal(make_vector(x, y, z)); } else { failString = [NSString stringWithFormat:@"%@Failed to read a value for vertex[%d] in %@\n", failString, j, @"TANGENTS"]; } } } } } PROFILE(@"finished parsing"); if (IsLegacyNormalMode(_normalMode)) { [self checkNormalsAndAdjustWinding]; PROFILE(@"finished checkNormalsAndAdjustWinding"); } if (!explicitTangents) { [self generateFaceTangents]; PROFILE(@"finished generateFaceTangents"); } // check for smooth shading and recalculate normals if (_normalMode == kNormalModeSmooth) { if (![self allocateNormalBuffersWithCount:vertexCount]) { OOLog(kOOLogAllocationFailure, @"***** ERROR: failed to allocate memory for model %@ (%u vertices).", filename, vertexCount); return NO; } [self calculateVertexNormalsAndTangentsWithFaceRefs:faceRefs]; PROFILE(@"finished calculateVertexNormalsAndTangents"); } else if (IsPerVertexNormalMode(_normalMode) && !explicitTangents) { [self calculateVertexTangentsWithFaceRefs:faceRefs]; PROFILE(@"finished calculateVertexTangents"); } // save the resulting data for possible reuse [OOCacheManager setMeshData:[self modelData] forName:cacheKey]; PROFILE(@"saved to cache"); if (failFlag) { OOLog(@"mesh.error", @"%@ ..... from %@ %@", failString, filename, (using_preloaded)? @"(from preloaded data)" :@"(from file)"); } } [self calculateBoundingVolumes]; PROFILE(@"finished calculateBoundingVolumes"); // set up vertex arrays for drawing if (![self setUpVertexArrays]) return NO; PROFILE(@"finished setUpVertexArrays"); return YES; OOJS_PROFILE_EXIT } - (void) checkNormalsAndAdjustWinding { OOJS_PROFILE_ENTER Vector calculatedNormal; OOMeshFaceCount i; NSParameterAssert(_normalMode != kNormalModeExplicit); for (i = 0; i < faceCount; i++) { Vector v0, v1, v2, norm; v0 = _vertices[_faces[i].vertex[0]]; v1 = _vertices[_faces[i].vertex[1]]; v2 = _vertices[_faces[i].vertex[2]]; norm = _faces[i].normal; calculatedNormal = normal_to_surface(v2, v1, v0); if (vector_equal(norm, kZeroVector)) { norm = vector_flip(calculatedNormal); _faces[i].normal = norm; } /* FIXME: for 2.0, either require explicit normals for every model or change to: if (dot_product(norm, calculatedNormal) < 0.0f) -- Ahruman 2010-01-23 */ if (norm.x * calculatedNormal.x < 0 || norm.y * calculatedNormal.y < 0 || norm.z * calculatedNormal.z < 0) { // normal lies in the WRONG direction! // reverse the winding int v0 = _faces[i].vertex[0]; _faces[i].vertex[0] = _faces[i].vertex[2]; _faces[i].vertex[2] = v0; GLfloat f0 = _faces[i].s[0]; _faces[i].s[0] = _faces[i].s[2]; _faces[i].s[2] = f0; f0 = _faces[i].t[0]; _faces[i].t[0] = _faces[i].t[2]; _faces[i].t[2] = f0; } } OOJS_PROFILE_EXIT_VOID } - (void) generateFaceTangents { OOJS_PROFILE_ENTER BOOL lighting_fix = [[NSUserDefaults standardUserDefaults] boolForKey: @"lighting-fix-tentative"]; OOMeshFaceCount i; for (i = 0; i < faceCount; i++) { OOMeshFace *face = _faces + i; /* Generate tangents, i.e. vectors that run in the direction of the s texture coordinate. Based on code I found in a forum somewhere and then lost track of. Sorry to whomever I should be crediting. -- Ahruman 2008-11-23 */ Vector vAB = vector_subtract(_vertices[face->vertex[1]], _vertices[face->vertex[0]]); Vector vAC = vector_subtract(_vertices[face->vertex[2]], _vertices[face->vertex[0]]); Vector nA = face->normal; // projAB = aB - (nA . vAB) * nA Vector vProjAB = vector_subtract(vAB, vector_multiply_scalar(nA, dot_product(nA, vAB))); Vector vProjAC = vector_subtract(vAC, vector_multiply_scalar(nA, dot_product(nA, vAC))); // delta s/t GLfloat dsAB = face->s[1] - face->s[0]; GLfloat dsAC = face->s[2] - face->s[0]; GLfloat dtAB = face->t[1] - face->t[0]; GLfloat dtAC = face->t[2] - face->t[0]; if (lighting_fix) { if (dsAC * dtAB > dsAB * dtAC) { face->tangent = vector_subtract(vector_multiply_scalar(vProjAC, dtAB), vector_multiply_scalar(vProjAB, dtAC)); } else { face->tangent = vector_subtract(vector_multiply_scalar(vProjAB, dtAC), vector_multiply_scalar(vProjAC, dtAB)); } } else { if (dsAC * dtAB > dsAB * dtAC) { dsAB = -dsAB; dsAC = -dsAC; } Vector tangent = vector_subtract(vector_multiply_scalar(vProjAB, dsAC), vector_multiply_scalar(vProjAC, dsAB)); face->tangent = cross_product(nA, tangent); // Rotate 90 degrees. Done this way because I'm too lazy to grok the code above. } } OOJS_PROFILE_EXIT_VOID } static float FaceArea(GLuint *vertIndices, Vector *vertices) { /* Calculate areas using Heron's formula. */ float a2 = distance2(vertices[vertIndices[0]], vertices[vertIndices[1]]); float b2 = distance2(vertices[vertIndices[1]], vertices[vertIndices[2]]); float c2 = distance2(vertices[vertIndices[2]], vertices[vertIndices[0]]); return sqrt((2.0f * (a2 * b2 + b2 * c2 + c2 * a2) - (a2 * a2 + b2 * b2 +c2 * c2)) * 0.0625f); } - (void) calculateVertexNormalsAndTangentsWithFaceRefs:(VertexFaceRef *)faceRefs { OOJS_PROFILE_ENTER NSParameterAssert(faceRefs != NULL); NSUInteger i,j; float triangle_area[faceCount]; NSAssert1(_normals != NULL && _tangents != NULL, @"Normal/tangent buffers not allocated in %s", __PRETTY_FUNCTION__); for (i = 0 ; i < faceCount; i++) { triangle_area[i] = FaceArea(_faces[i].vertex, _vertices); } for (i = 0; i < vertexCount; i++) { Vector normal_sum = kZeroVector; Vector tangent_sum = kZeroVector; VertexFaceRef *vfr = &faceRefs[i]; NSUInteger fIter, fCount = VFRGetCount(vfr); for (fIter = 0; fIter < fCount; fIter++) { j = VFRGetFaceAtIndex(vfr, fIter); float t = triangle_area[j]; // weight sum by area normal_sum = vector_add(normal_sum, vector_multiply_scalar(_faces[j].normal, t)); tangent_sum = vector_add(tangent_sum, vector_multiply_scalar(_faces[j].tangent, t)); } normal_sum = vector_normal_or_fallback(normal_sum, kBasisZVector); tangent_sum = vector_normal_or_fallback(tangent_sum, kBasisXVector); _normals[i] = normal_sum; _tangents[i] = tangent_sum; } OOJS_PROFILE_EXIT_VOID } static float FaceAreaCorrect(GLuint *vertIndices, Vector *vertices) { /* Calculate area of triangle. The magnitude of the cross product of two vectors is the area of the parallelogram they span. The area of a triangle is half the area of a parallelogram sharing two of its sides. Since we only use the area of the triangle as a weight factor, constant terms are irrelevant, so we don't bother halving the value. */ Vector AB = vector_subtract(vertices[vertIndices[1]], vertices[vertIndices[0]]); Vector AC = vector_subtract(vertices[vertIndices[2]], vertices[vertIndices[0]]); return magnitude(true_cross_product(AB, AC)); } - (void) calculateVertexTangentsWithFaceRefs:(VertexFaceRef *)faceRefs { OOJS_PROFILE_ENTER NSParameterAssert(faceRefs != NULL); /* This is conceptually broken. At the moment, it's calculating one tangent per "input" vertex. It should be calculating one tangent per "real" vertex, where a "real" vertex is defined as a combination of position, normal, material and texture coordinates. Currently, we don't have a format with unique "real" vertices. This basically means explicit-normal models without explicit tangents can't usefully be normal mapped. I don't intend to do anything about this pre-MSNR, although it might be possible to fix it by moving tangent generation to the same stage as smooth-grouped normal generation. -- Ahruman 2010-05-22 */ NSUInteger i,j; float triangle_area[faceCount]; for (i = 0 ; i < faceCount; i++) { triangle_area[i] = FaceAreaCorrect(_faces[i].vertex, _vertices); } for (i = 0; i < vertexCount; i++) { Vector tangent_sum = kZeroVector; VertexFaceRef *vfr = &faceRefs[i]; NSUInteger fIter, fCount = VFRGetCount(vfr); for (fIter = 0; fIter < fCount; fIter++) { j = VFRGetFaceAtIndex(vfr, fIter); float t = triangle_area[j]; // weight sum by area tangent_sum = vector_add(tangent_sum, vector_multiply_scalar(_faces[j].tangent, t)); } tangent_sum = vector_normal_or_fallback(tangent_sum, kBasisXVector); _tangents[i] = tangent_sum; } OOJS_PROFILE_EXIT_VOID } /* profiling suggests this function takes a lot of time - almost all * the overhead of setting up a new ship is here. - CIM */ - (void) getNormal:(Vector *)outNormal andTangent:(Vector *)outTangent forVertex:(OOMeshVertexCount)v_index inSmoothGroup:(OOMeshSmoothGroup)smoothGroup { OOJS_PROFILE_ENTER assert(outNormal != NULL && outTangent != NULL); NSUInteger j; Vector normal_sum = kZeroVector; Vector tangent_sum = kZeroVector; for (j = 0; j < faceCount; j++) { if (_faces[j].smoothGroup == smoothGroup) { if ((_faces[j].vertex[0] == v_index)||(_faces[j].vertex[1] == v_index)||(_faces[j].vertex[2] == v_index)) { float area = FaceArea(_faces[j].vertex, _vertices); normal_sum = vector_add(normal_sum, vector_multiply_scalar(_faces[j].normal, area)); tangent_sum = vector_add(tangent_sum, vector_multiply_scalar(_faces[j].tangent, area)); } } } *outNormal = vector_normal_or_fallback(normal_sum, kBasisZVector); *outTangent = vector_normal_or_fallback(tangent_sum, kBasisXVector); OOJS_PROFILE_EXIT_VOID } - (BOOL) setUpVertexArrays { OOJS_PROFILE_ENTER NSUInteger fi, vi, mi; if (![self allocateVertexArrayBuffersWithCount:faceCount]) return NO; // if smoothed, find any vertices that are between faces of different // smoothing groups and mark them as being on an edge and therefore NOT // smooth shaded BOOL is_edge_vertex[vertexCount]; GLfloat smoothGroup[vertexCount]; for (vi = 0; vi < vertexCount; vi++) { is_edge_vertex[vi] = NO; smoothGroup[vi] = -1; } if (_normalMode == kNormalModeSmooth) { for (fi = 0; fi < faceCount; fi++) { GLfloat rv = _faces[fi].smoothGroup; int i; for (i = 0; i < 3; i++) { vi = _faces[fi].vertex[i]; if (smoothGroup[vi] < 0.0) // unassigned smoothGroup[vi] = rv; else if (smoothGroup[vi] != rv) // a different colour is_edge_vertex[vi] = YES; } } } // base model, flat or smooth shaded, all triangles int tri_index = 0; int uv_index = 0; int vertex_index = 0; // Iterate over material names for (mi = 0; mi != materialCount; ++mi) { triangle_range[mi].location = tri_index; for (fi = 0; fi < faceCount; fi++) { Vector normal, tangent; if (_faces[fi].materialIndex == mi) { for (vi = 0; vi < 3; vi++) { int v = _faces[fi].vertex[vi]; if (IsPerVertexNormalMode(_normalMode)) { if (is_edge_vertex[v]) { [self getNormal:&normal andTangent:&tangent forVertex:v inSmoothGroup:_faces[fi].smoothGroup]; } else { NSAssert1(_normals != NULL && _tangents != NULL, @"Normal/tangent buffers not allocated in %s", __PRETTY_FUNCTION__); normal = _normals[v]; tangent = _tangents[v]; } } else { normal = _faces[fi].normal; tangent = _faces[fi].tangent; } // FIXME: avoid redundant vertices so index array is actually useful. _displayLists.indexArray[tri_index++] = vertex_index; _displayLists.normalArray[vertex_index] = normal; _displayLists.tangentArray[vertex_index] = tangent; _displayLists.vertexArray[vertex_index++] = _vertices[v]; _displayLists.textureUVArray[uv_index++] = _faces[fi].s[vi]; _displayLists.textureUVArray[uv_index++] = _faces[fi].t[vi]; } } } triangle_range[mi].length = tri_index - triangle_range[mi].location; } _displayLists.count = tri_index; // total number of triangle vertices return YES; OOJS_PROFILE_EXIT } - (void) calculateBoundingVolumes { OOJS_PROFILE_ENTER OOMeshVertexCount i; double d_squared, length_longest_axis, length_shortest_axis; GLfloat result; result = 0.0f; if (vertexCount) bounding_box_reset_to_vector(&boundingBox, _vertices[0]); else bounding_box_reset(&boundingBox); for (i = 0; i < vertexCount; i++) { d_squared = magnitude2(_vertices[i]); if (d_squared > result) result = d_squared; bounding_box_add_vector(&boundingBox, _vertices[i]); } length_longest_axis = boundingBox.max.x - boundingBox.min.x; if (boundingBox.max.y - boundingBox.min.y > length_longest_axis) length_longest_axis = boundingBox.max.y - boundingBox.min.y; if (boundingBox.max.z - boundingBox.min.z > length_longest_axis) length_longest_axis = boundingBox.max.z - boundingBox.min.z; length_shortest_axis = boundingBox.max.x - boundingBox.min.x; if (boundingBox.max.y - boundingBox.min.y < length_shortest_axis) length_shortest_axis = boundingBox.max.y - boundingBox.min.y; if (boundingBox.max.z - boundingBox.min.z < length_shortest_axis) length_shortest_axis = boundingBox.max.z - boundingBox.min.z; d_squared = (length_longest_axis + length_shortest_axis) * (length_longest_axis + length_shortest_axis) * 0.25; // square of average length maxDrawDistance = d_squared * NO_DRAW_DISTANCE_FACTOR * NO_DRAW_DISTANCE_FACTOR; // no longer based on the collision radius collisionRadius = sqrt(result); OOJS_PROFILE_EXIT_VOID } - (void) rescaleByFactor:(GLfloat)factor { // Rescale base vertices used for geometry calculations. OOMeshVertexCount i; Vector *vertex = NULL; for (i = 0; i < vertexCount; i++) { vertex = &_vertices[i]; *vertex = vector_multiply_scalar(*vertex, factor); } // Rescale actual display vertices. for (i = 0; i < _displayLists.count; i++) { vertex = &_displayLists.vertexArray[i]; *vertex = vector_multiply_scalar(*vertex, factor); } [self calculateBoundingVolumes]; DESTROY(octree); DESTROY(baseFile); // Avoid octree cache. } - (BoundingBox)boundingBox { return boundingBox; } #ifndef NDEBUG - (void)debugDrawNormals { GLuint i; Vector v, n, t, b; float length, blend; GLfloat color[3]; OODebugWFState state; OO_ENTER_OPENGL(); state = OODebugBeginWireframe(NO); // Draw OOGLBEGIN(GL_LINES); for (i = 0; i < _displayLists.count; ++i) { v = _displayLists.vertexArray[i]; n = _displayLists.normalArray[i]; t = _displayLists.tangentArray[i]; b = true_cross_product(n, t); // Draw normal length = magnitude2(n); blend = fabs(length - 1) * 5.0; color[0] = MIN(blend, 1.0f); color[1] = 1.0f - color[0]; color[2] = color[1]; glColor3fv(color); glVertex3f(v.x, v.y, v.z); scale_vector(&n, 5.0f); n = vector_add(n, v); glVertex3f(n.x, n.y, n.z); // Draw tangent glColor3f(1.0f, 1.0f, 0.0f); t = vector_add(v, vector_multiply_scalar(t, 3.0f)); glVertex3f(v.x, v.y, v.z); glVertex3f(t.x, t.y, t.z); // Draw bitangent glColor3f(0.0f, 1.0f, 0.0f); b = vector_add(v, vector_multiply_scalar(b, 3.0f)); glVertex3f(v.x, v.y, v.z); glVertex3f(b.x, b.y, b.z); } OOGLEND(); OODebugEndWireframe(state); } #endif - (void) setRetainedObject:(id)object forKey:(NSString *)key { assert(key != nil); if (object != nil) { if (_retainedObjects == nil) _retainedObjects = [[NSMutableDictionary alloc] init]; [_retainedObjects setObject:object forKey:key]; } } #if SCRIBBLE static void Scribble(void *bytes, size_t size) { #if OOLITE_BIG_ENDIAN enum { kScribble = 0xFEEDFACE }; #else enum { kScribble = 0xCEFAEDFE }; #endif size /= sizeof (uint32_t); uint32_t *mem = bytes; while (size--) *mem++ = kScribble; } #else #define Scribble(bytes, size) do {} while (0) #endif /* valgrind complains that the memory allocated here isn't initialised at the time OOCacheManager::writeDict is pushing it to the cache. Not sure if that's a problem or not. - CIM */ - (void *) allocateBytesWithSize:(size_t)size count:(NSUInteger)count key:(NSString *)key { if (count == 0) { count=1; } size *= count; void *bytes = malloc(size); if (bytes != NULL) { Scribble(bytes, size); NSData *holder = [NSData dataWithBytesNoCopy:bytes length:size freeWhenDone:YES]; [self setRetainedObject:holder forKey:key]; } return bytes; } - (BOOL) allocateVertexBuffersWithCount:(NSUInteger)count { _vertices = [self allocateBytesWithSize:sizeof *_vertices count:vertexCount key:@"vertices"]; return _vertices != NULL; } - (BOOL) allocateNormalBuffersWithCount:(NSUInteger)count { _normals = [self allocateBytesWithSize:sizeof *_normals count:vertexCount key:@"normals"]; _tangents = [self allocateBytesWithSize:sizeof *_tangents count:vertexCount key:@"tangents"]; return _normals != NULL && _tangents != NULL; } - (BOOL) allocateFaceBuffersWithCount:(NSUInteger)count { _faces = [self allocateBytesWithSize:sizeof *_faces count:faceCount key:@"faces"]; return _faces != NULL; } - (BOOL) allocateVertexArrayBuffersWithCount:(NSUInteger)count { _displayLists.indexArray = [self allocateBytesWithSize:sizeof *_displayLists.indexArray count:count * 3 key:@"indexArray"]; _displayLists.textureUVArray = [self allocateBytesWithSize:sizeof *_displayLists.textureUVArray count:count * 6 key:@"textureUVArray"]; _displayLists.vertexArray = [self allocateBytesWithSize:sizeof *_displayLists.vertexArray count:count * 3 key:@"vertexArray"]; _displayLists.normalArray = [self allocateBytesWithSize:sizeof *_displayLists.normalArray count:count * 3 key:@"normalArray"]; _displayLists.tangentArray = [self allocateBytesWithSize:sizeof *_displayLists.tangentArray count:count * 3 key:@"tangentArray"]; return _faces != NULL && _displayLists.indexArray != NULL && _displayLists.textureUVArray != NULL && _displayLists.vertexArray != NULL && _displayLists.normalArray != NULL && _displayLists.tangentArray != NULL; } - (void) renameTexturesFrom:(NSString *)from to:(NSString *)to { /* IMPORTANT: this has to be called before setUpMaterials..., so it can only be used during loading. */ OOMeshMaterialCount i; for (i = 0; i != materialCount; i++) { if ([materialKeys[i] isEqualToString:from]) { [materialKeys[i] release]; materialKeys[i] = [to copy]; } } } @end static NSString * const kOOCacheMeshes = @"OOMesh"; @implementation OOCacheManager (OOMesh) + (NSDictionary *)meshDataForName:(NSString *)inShipName { return [[self sharedCache] objectForKey:inShipName inCache:kOOCacheMeshes]; } + (void)setMeshData:(NSDictionary *)inData forName:(NSString *)inShipName { if (inData != nil && inShipName != nil) { [[self sharedCache] setObject:inData forKey:inShipName inCache:kOOCacheMeshes]; } } @end static NSString * const kOOCacheOctrees = @"octrees"; @implementation OOCacheManager (Octree) + (Octree *)octreeForModel:(NSString *)inKey { NSDictionary *dict = nil; Octree *result = nil; if (inKey == nil) return nil; dict = [[self sharedCache] objectForKey:inKey inCache:kOOCacheOctrees]; if (dict != nil) { result = [[Octree alloc] initWithDictionary:dict]; [result autorelease]; } return result; } + (void)setOctree:(Octree *)inOctree forModel:(NSString *)inKey { if (inOctree != nil && inKey != nil) { [[self sharedCache] setObject:[inOctree dictionaryRepresentation] forKey:inKey inCache:kOOCacheOctrees]; } } static void VFRAddFace(VertexFaceRef *vfr, NSUInteger index) { NSCParameterAssert(vfr != NULL); if (index < UINT16_MAX && vfr->internCount < kVertexFaceDefInternalCount) { vfr->internFaces[vfr->internCount++] = index; } else { if (vfr->extra == nil) vfr->extra = [NSMutableArray array]; [vfr->extra addObject:[NSNumber numberWithInteger:index]]; } } static NSUInteger VFRGetCount(VertexFaceRef *vfr) { NSCParameterAssert(vfr != NULL); return vfr->internCount + [vfr->extra count]; } static NSUInteger VFRGetFaceAtIndex(VertexFaceRef *vfr, NSUInteger index) { NSCParameterAssert(vfr != NULL && index < VFRGetCount(vfr)); if (index < vfr->internCount) return vfr->internFaces[index]; else return [vfr->extra oo_unsignedIntegerAtIndex:index - vfr->internCount]; } @end oolite-1.82/src/Core/OOMeshToOctreeConverter.h000066400000000000000000000027071256642440500212740ustar00rootroot00000000000000/* OOMeshToOctreeConverter.h Class to manage the construction of octrees from triangle soups. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #import "OOMaths.h" @class ShipEntity, Octree; enum { kOOMeshToOctreeConverterSmallDataCapacity = 16 }; @interface OOMeshToOctreeConverter: NSObject { @private struct OOMeshToOctreeConverterInternalData { Triangle *triangles; uint_fast32_t count; uint_fast32_t capacity; uint_fast32_t pendingCapacity; Triangle smallData[kOOMeshToOctreeConverterSmallDataCapacity]; } _data; } - (id) initWithCapacity:(NSUInteger)capacity; + (instancetype) converterWithCapacity:(NSUInteger)capacity; - (void) addTriangle:(Triangle)tri; - (Octree *) findOctreeToDepth:(NSUInteger)depth; @end oolite-1.82/src/Core/OOMeshToOctreeConverter.m000066400000000000000000000641571256642440500213100ustar00rootroot00000000000000/* OOMeshToOctreeConverter.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOMeshToOctreeConverter.h" /* DESIGN NOTES OOMeshToOctreeConverter is responsible for analyzing a set of triangles from an OOMesh and turning them into an Octree using OOOctreeBuilder. This is a relatively heavyweight operation which is run on the main thread when loading a ship that isn't cached. Since ship set-up affects startup time and in-game stutter, this is performance-critical. The octree generation algorithm works as follows: Given a set of triangles within a bounding cube, and an iteration limit, do the following: * If the set of triangles is empty, produce an empty octree node. * Otherwise, if the iteration limit is reached, produce a solid node. * Otherwise, divide the set of triangles into eight equally-sized child cubes (splitting triangles if needed); create an inner node in the octree; and repeat the algorithm for each of the eight children. OOOctreeBuilder performs a simple structural optimization where an inner node whose child nodes are all solid is replaced with a solid node. This is effective recursively and very cheap. As such, there is no need for OOMeshToOctreeConverter to try to detect this situation. On a typical cold startup with no OXPs loaded, well over two million octree nodes are processed by OOMeshToOctreeConverter. The highest number of nodes in existence at once is 53. Our main performance concerns are to minimize the amount of memory managment per node and to avoid dynamic dispatch in critical areas, while minimizing the number of allocations per node is not very important. The current implementation uses a struct (defined in the header as OOMeshToOctreeConverterInternalData, and locally known as GeometryData) to store the triangle set. The root GeometryData is an instance variable of OOMeshToOctreeConverter, and children are created on the stack while iterating. Up to sixteen triangles can be stored directly in the GeometryData. If more than sixteen are needed, a heap-allocated array is used instead. At one point, I attempted to be cleverer about the storage using unions and implied capacity, but this was significantly slower with only small amounts of stack space saved. Profiling shows that over 93 % of nodes use sixteen or fewer triangles in vanilla Oolite. The proportion is lower when using OXPs with more complex models. */ #import "OOMaths.h" #import "Octree.h" #import "OOLogging.h" // MARK: GeometryData operations. /* GeometryData Struct tracking a set of triangles. Must be initialized using InitGeometryData() before other operations. capacity is an estimate of the required size. If the number of triangles added exceeds kOOMeshToOctreeConverterSmallDataCapacity, the capacity will be used as the initial heap-allocated size, unless it's smaller than a minimum threshold. Triangle *triangles Pointer to the triangle array. Initially points at smallData. uint_fast32_t count The number of triangles currently in the GeometryData. uint_fast32_t capacity The number of slots in triangles. Invariant: count <= capacity. uint_fast32_t pendingCapacity The capacity hint passed to InitGeometryData(). Used by AddTriangle_slow(). Triangle smallData[] Initial triangle storage. Should not be accessed directly; if it's relevant, triangles points to it. */ typedef struct OOMeshToOctreeConverterInternalData GeometryData; /* InitGeometryData(data, capacity) Prepare a GeometryData struct for use. The data has to be by reference rather than a return value so that the triangles pointer can be pointed into the struct. */ OOINLINE void InitGeometryData(GeometryData *data, uint_fast32_t capacity); /* DestroyGeometryData(data) Deallocates dynamic storage if necessary. Leaves the GeometryData in an invalid state. */ OOINLINE void DestroyGeometryData(GeometryData *data); /* AddTriangle(data, tri) Add a triangle to a GeometryData. Will either succeed or abort. AddTriangle() is designed so that its fast case will be inlined (at least, if assertions are disabled as in Deployment builds) and its slow case will not. AddTriangle_slow(data, tri) Slow path for AddTriangle(), used when more space is needed. Should not be called directly. Invariant: may only be called when count == capacity. */ OOINLINE void AddTriangle(GeometryData *data, Triangle tri); static NO_INLINE_FUNC void AddTriangle_slow(GeometryData *data, Triangle tri); /* MaxDimensionFromOrigin(data) Calculates the half-width of a bounding cube around data centered at the origin. */ static OOScalar MaxDimensionFromOrigin(GeometryData *data); /* BuildSubOctree(data, builder, halfWidth, depth) Recursively apply the octree generation algorithm. data: input geometry data. builder: OOOctreeBuilder where results are accumulated. Each call will write one complete subtree, which may be a single leaf node. halfWidth: the half-width of the bounding cube of data. depth: recursion limit. */ void BuildSubOctree(GeometryData *data, OOOctreeBuilder *builder, OOScalar halfWidth, NSUInteger depth); /* SplitGeometry{X|Y|Z}(data, dPlus, dMinus, offset) Divide data across its local zero plane perpendicular to the specified axis, putting triangles with coordinates >= 0 in dPlus and triangles with coordinates <= 0 in dMinus. Triangles will be split if necessary. Generated triangles will be offset by the specified amount (half of data's half-width) so that the split planes of dPlus and dMinus will be in the middle of their data sets. */ static void SplitGeometryX(GeometryData *data, GeometryData *dPlus, GeometryData *dMinus, OOScalar x); static void SplitGeometryY(GeometryData *data, GeometryData *dPlus, GeometryData *dMinus, OOScalar y); static void SplitGeometryZ(GeometryData *data, GeometryData *dPlus, GeometryData *dMinus, OOScalar z); // MARK: Inline function bodies. void InitGeometryData(GeometryData *data, uint_fast32_t capacity) { NSCParameterAssert(data != NULL); data->count = 0; data->capacity = kOOMeshToOctreeConverterSmallDataCapacity; data->pendingCapacity = capacity; data->triangles = data->smallData; } OOINLINE void DestroyGeometryData(GeometryData *data) { NSCParameterAssert(data != 0 && data->capacity >= kOOMeshToOctreeConverterSmallDataCapacity); #if OO_DEBUG Triangle * const kScribbleValue = (Triangle *)-1L; NSCAssert(data->triangles != kScribbleValue, @"Attempt to destroy a GeometryData twice."); #endif if (data->capacity != kOOMeshToOctreeConverterSmallDataCapacity) { // If capacity is kOOMeshToOctreeConverterSmallDataCapacity, triangles points to smallData. free(data->triangles); } #if OO_DEBUG data->triangles = kScribbleValue; #endif } OOINLINE void AddTriangle(GeometryData *data, Triangle tri) { NSCParameterAssert(data != NULL); if (data->count < data->capacity) { data->triangles[data->count++] = tri; } else { AddTriangle_slow(data, tri); } } @implementation OOMeshToOctreeConverter - (id) initWithCapacity:(NSUInteger)capacity { NSParameterAssert(capacity < UINT32_MAX); if (capacity == 0) capacity = 1; // Happens for models with no faces. if ((self = [super init])) { InitGeometryData(&_data, (uint_fast32_t)capacity); } return self; } - (void) dealloc { DestroyGeometryData(&_data); [super dealloc]; } + (instancetype) converterWithCapacity:(NSUInteger)capacity { return [[[self alloc] initWithCapacity:capacity] autorelease]; } - (NSString *) descriptionComponents { return [NSString stringWithFormat:@"%u triangles", _data.count]; } - (void) addTriangle:(Triangle)tri { if (!OOTriangleIsDegenerate(tri)) { AddTriangle(&_data, tri); } } - (Octree *) findOctreeToDepth:(NSUInteger)depth { OOOctreeBuilder *builder = [[[OOOctreeBuilder alloc] init] autorelease]; OOScalar halfWidth = 0.5f + MaxDimensionFromOrigin(&_data); // pad out from geometry by a half meter BuildSubOctree(&_data, builder, halfWidth, depth); return [builder buildOctreeWithRadius:halfWidth]; } @end static OOScalar MaxDimensionFromOrigin(GeometryData *data) { NSCParameterAssert(data != NULL); OOScalar result = 0.0f; uint_fast32_t i, j; for (i = 0; i < data->count; i++) for (j = 0; j < 3; j++) { Vector v = data->triangles[i].v[j]; result = fmax(result, fabs(v.x)); result = fmax(result, fabs(v.y)); result = fmax(result, fabs(v.z)); } return result; } void BuildSubOctree(GeometryData *data, OOOctreeBuilder *builder, OOScalar halfWidth, NSUInteger depth) { NSCParameterAssert(data != NULL); OOScalar subHalfWidth = 0.5f * halfWidth; if (data->count == 0) { // No geometry here. [builder writeEmpty]; return; } if (halfWidth <= OCTREE_MIN_HALF_WIDTH || depth <= 0) { // Maximum resolution reached and not full. [builder writeSolid]; return; } /* To avoid reallocations, we want a reasonably pessimistic estimate of sub-data size. This table shows observed performance for several heuristics using vanilla Oolite r5352 (plus instrumentation). Values aren't precisely reproducible, but are reasonably stable. Heuristic: expression used to initialize subCapacity. PER: number of geometries per reallocation; in other words, a realloc is needed one time per PER geometries. MEM: high water mark for total memory consumption (triangles arrays only) across all live Geometries. Heuristic PER MEM n_triangles 3-4 71856 n_triangles * 2 100 111384 MAX(n_triangles * 2, 16) 300 111384 MAX(n_triangles * 2, 21) 500 148512 n_triangles * 3 500 165744 MAX(n_triangles * 3, 16) 12000 165744 MAX(n_triangles * 3, 21) 20000 165744 The value 21 was chosen for reasons which, on reflection, were entirely wrong. Performance profiling shows no discernible difference between 2,16 and 3,21. As of r5374, up to 16 entries are stored on the stack and there is no benefit to specifying a minimum here any longer. */ enum { kFactor = 2 }; uint_fast32_t subCapacity = data->count * kFactor; #define DECL_GEOMETRY(NAME, CAP) GeometryData NAME; InitGeometryData(&NAME, CAP); DECL_GEOMETRY(g_000, subCapacity); DECL_GEOMETRY(g_001, subCapacity); DECL_GEOMETRY(g_010, subCapacity); DECL_GEOMETRY(g_011, subCapacity); DECL_GEOMETRY(g_100, subCapacity); DECL_GEOMETRY(g_101, subCapacity); DECL_GEOMETRY(g_110, subCapacity); DECL_GEOMETRY(g_111, subCapacity); DECL_GEOMETRY(g_xx1, subCapacity); DECL_GEOMETRY(g_xx0, subCapacity); SplitGeometryZ(data, &g_xx1, &g_xx0, subHalfWidth); if (g_xx0.count != 0) { DECL_GEOMETRY(g_x00, subCapacity); DECL_GEOMETRY(g_x10, subCapacity); SplitGeometryY(&g_xx0, &g_x10, &g_x00, subHalfWidth); if (g_x00.count != 0) { SplitGeometryX(&g_x00, &g_100, &g_000, subHalfWidth); } if (g_x10.count != 0) { SplitGeometryX(&g_x10, &g_110, &g_010, subHalfWidth); } DestroyGeometryData(&g_x00); DestroyGeometryData(&g_x10); } if (g_xx1.count != 0) { DECL_GEOMETRY(g_x01, subCapacity); DECL_GEOMETRY(g_x11, subCapacity); SplitGeometryY(&g_xx1, &g_x11, &g_x01, subHalfWidth); if (g_x01.count != 0) { SplitGeometryX(&g_x01, &g_101, &g_001, subHalfWidth); } if (g_x11.count != 0) { SplitGeometryX(&g_x11, &g_111, &g_011, subHalfWidth); } DestroyGeometryData(&g_x01); DestroyGeometryData(&g_x11); } DestroyGeometryData(&g_xx0); DestroyGeometryData(&g_xx1); [builder beginInnerNode]; depth--; BuildSubOctree(&g_000, builder, subHalfWidth, depth); BuildSubOctree(&g_001, builder, subHalfWidth, depth); BuildSubOctree(&g_010, builder, subHalfWidth, depth); BuildSubOctree(&g_011, builder, subHalfWidth, depth); BuildSubOctree(&g_100, builder, subHalfWidth, depth); BuildSubOctree(&g_101, builder, subHalfWidth, depth); BuildSubOctree(&g_110, builder, subHalfWidth, depth); BuildSubOctree(&g_111, builder, subHalfWidth, depth); [builder endInnerNode]; DestroyGeometryData(&g_000); DestroyGeometryData(&g_001); DestroyGeometryData(&g_010); DestroyGeometryData(&g_011); DestroyGeometryData(&g_100); DestroyGeometryData(&g_101); DestroyGeometryData(&g_110); DestroyGeometryData(&g_111); } static void TranslateGeometryX(GeometryData *data, OOScalar offset) { NSCParameterAssert(data != NULL); // Optimization note: offset is never zero, so no early return. uint_fast32_t i, count = data->count; for (i = 0; i < count; i++) { data->triangles[i].v[0].x += offset; data->triangles[i].v[1].x += offset; data->triangles[i].v[2].x += offset; } } static void TranslateGeometryY(GeometryData *data, OOScalar offset) { NSCParameterAssert(data != NULL); // Optimization note: offset is never zero, so no early return. uint_fast32_t i, count = data->count; for (i = 0; i < count; i++) { data->triangles[i].v[0].y += offset; data->triangles[i].v[1].y += offset; data->triangles[i].v[2].y += offset; } } static void TranslateGeometryZ(GeometryData *data, OOScalar offset) { NSCParameterAssert(data != NULL); // Optimization note: offset is never zero, so no early return. uint_fast32_t i, count = data->count; for (i = 0; i < count; i++) { data->triangles[i].v[0].z += offset; data->triangles[i].v[1].z += offset; data->triangles[i].v[2].z += offset; } } static void SplitGeometryX(GeometryData *data, GeometryData *dPlus, GeometryData *dMinus, OOScalar x) { NSCParameterAssert(data != NULL && dPlus != NULL && dMinus != NULL); // test each triangle splitting against x == 0.0 uint_fast32_t i, count = data->count; for (i = 0; i < count; i++) { bool done_tri = false; Vector v0 = data->triangles[i].v[0]; Vector v1 = data->triangles[i].v[1]; Vector v2 = data->triangles[i].v[2]; if (v0.x >= 0.0f && v1.x >= 0.0f && v2.x >= 0.0f) { AddTriangle(dPlus, data->triangles[i]); done_tri = true; } else if (v0.x <= 0.0f && v1.x <= 0.0f && v2.x <= 0.0f) { AddTriangle(dMinus, data->triangles[i]); done_tri = true; } if (!done_tri) // triangle must cross x == 0.0 { OOScalar i01, i12, i20; if (v0.x == v1.x) i01 = -1.0f; else i01 = v0.x / (v0.x - v1.x); if (v1.x == v2.x) i12 = -1.0f; else i12 = v1.x / (v1.x - v2.x); if (v2.x == v0.x) i20 = -1.0f; else i20 = v2.x / (v2.x - v0.x); Vector v01 = make_vector(0.0f, i01 * (v1.y - v0.y) + v0.y, i01 * (v1.z - v0.z) + v0.z); Vector v12 = make_vector(0.0f, i12 * (v2.y - v1.y) + v1.y, i12 * (v2.z - v1.z) + v1.z); Vector v20 = make_vector(0.0f, i20 * (v0.y - v2.y) + v2.y, i20 * (v0.z - v2.z) + v2.z); // cases where a vertex is on the split. if (v0.x == 0.0f) { if (v1.x > 0) { AddTriangle(dPlus, make_triangle(v0, v1, v12)); AddTriangle(dMinus, make_triangle(v0, v12, v2)); } else { AddTriangle(dMinus, make_triangle(v0, v1, v12)); AddTriangle(dPlus, make_triangle(v0, v12, v2)); } } if (v1.x == 0.0f) { if (v2.x > 0) { AddTriangle(dPlus, make_triangle(v1, v2, v20)); AddTriangle(dMinus, make_triangle(v1, v20, v0)); } else { AddTriangle(dMinus, make_triangle(v1, v2, v20)); AddTriangle(dPlus, make_triangle(v1, v20, v0)); } } if (v2.x == 0.0f) { if (v0.x > 0) { AddTriangle(dPlus, make_triangle(v2, v0, v01)); AddTriangle(dMinus, make_triangle(v2, v01, v1)); } else { AddTriangle(dMinus, make_triangle(v2, v0, v01)); AddTriangle(dPlus, make_triangle(v2, v01, v1)); } } if (v0.x > 0.0f && v1.x > 0.0f && v2.x < 0.0f) { AddTriangle(dPlus, make_triangle(v0, v12, v20)); AddTriangle(dPlus, make_triangle(v0, v1, v12)); AddTriangle(dMinus, make_triangle(v20, v12, v2)); } if (v0.x > 0.0f && v1.x < 0.0f && v2.x > 0.0f) { AddTriangle(dPlus, make_triangle(v2, v01, v12)); AddTriangle(dPlus, make_triangle(v2, v0, v01)); AddTriangle(dMinus, make_triangle(v12, v01, v1)); } if (v0.x > 0.0f && v1.x < 0.0f && v2.x < 0.0f) { AddTriangle(dPlus, make_triangle(v20, v0, v01)); AddTriangle(dMinus, make_triangle(v2, v20, v1)); AddTriangle(dMinus, make_triangle(v20, v01, v1)); } if (v0.x < 0.0f && v1.x > 0.0f && v2.x > 0.0f) { AddTriangle(dMinus, make_triangle(v01, v20, v0)); AddTriangle(dPlus, make_triangle(v1, v20, v01)); AddTriangle(dPlus, make_triangle(v1, v2, v20)); } if (v0.x < 0.0f && v1.x > 0.0f && v2.x < 0.0f) { AddTriangle(dPlus, make_triangle(v01, v1, v12)); AddTriangle(dMinus, make_triangle(v0, v01, v2)); AddTriangle(dMinus, make_triangle(v01, v12, v2)); } if (v0.x < 0.0f && v1.x < 0.0f && v2.x > 0.0f) { AddTriangle(dPlus, make_triangle(v12, v2, v20)); AddTriangle(dMinus, make_triangle(v1, v12, v0)); AddTriangle(dMinus, make_triangle(v12, v20, v0)); } } } TranslateGeometryX(dPlus, -x); TranslateGeometryX(dMinus, x); } static void SplitGeometryY(GeometryData *data, GeometryData *dPlus, GeometryData *dMinus, OOScalar y) { NSCParameterAssert(data != NULL && dPlus != NULL && dMinus != NULL); // test each triangle splitting against y == 0.0 uint_fast32_t i, count = data->count; for (i = 0; i < count; i++) { bool done_tri = false; Vector v0 = data->triangles[i].v[0]; Vector v1 = data->triangles[i].v[1]; Vector v2 = data->triangles[i].v[2]; if (v0.y >= 0.0f && v1.y >= 0.0f && v2.y >= 0.0f) { AddTriangle(dPlus, data->triangles[i]); done_tri = true; } if (v0.y <= 0.0f && v1.y <= 0.0f && v2.y <= 0.0f) { AddTriangle(dMinus, data->triangles[i]); done_tri = true; } if (!done_tri) // triangle must cross y == 0.0 { OOScalar i01, i12, i20; if (v0.y == v1.y) i01 = -1.0f; else i01 = v0.y / (v0.y - v1.y); if (v1.y == v2.y) i12 = -1.0f; else i12 = v1.y / (v1.y - v2.y); if (v2.y == v0.y) i20 = -1.0f; else i20 = v2.y / (v2.y - v0.y); Vector v01 = make_vector(i01 * (v1.x - v0.x) + v0.x, 0.0f, i01 * (v1.z - v0.z) + v0.z); Vector v12 = make_vector(i12 * (v2.x - v1.x) + v1.x, 0.0f, i12 * (v2.z - v1.z) + v1.z); Vector v20 = make_vector(i20 * (v0.x - v2.x) + v2.x, 0.0f, i20 * (v0.z - v2.z) + v2.z); // cases where a vertex is on the split. if (v0.y == 0.0f) { if (v1.y > 0) { AddTriangle(dPlus, make_triangle(v0, v1, v12)); AddTriangle(dMinus, make_triangle(v0, v12, v2)); } else { AddTriangle(dMinus, make_triangle(v0, v1, v12)); AddTriangle(dPlus, make_triangle(v0, v12, v2)); } } if (v1.y == 0.0f) { if (v2.y > 0) { AddTriangle(dPlus, make_triangle(v1, v2, v20)); AddTriangle(dMinus, make_triangle(v1, v20, v0)); } else { AddTriangle(dMinus, make_triangle(v1, v2, v20)); AddTriangle(dPlus, make_triangle(v1, v20, v0)); } } if (v2.y == 0.0f) { if (v0.y > 0) { AddTriangle(dPlus, make_triangle(v2, v0, v01)); AddTriangle(dMinus, make_triangle(v2, v01, v1)); } else { AddTriangle(dMinus, make_triangle(v2, v0, v01)); AddTriangle(dPlus, make_triangle(v2, v01, v1)); } } if (v0.y > 0.0f && v1.y > 0.0f && v2.y < 0.0f) { AddTriangle(dPlus, make_triangle(v0, v12, v20)); AddTriangle(dPlus, make_triangle(v0, v1, v12)); AddTriangle(dMinus, make_triangle(v20, v12, v2)); } if (v0.y > 0.0f && v1.y < 0.0f && v2.y > 0.0f) { AddTriangle(dPlus, make_triangle(v2, v01, v12)); AddTriangle(dPlus, make_triangle(v2, v0, v01)); AddTriangle(dMinus, make_triangle(v12, v01, v1)); } if (v0.y > 0.0f && v1.y < 0.0f && v2.y < 0.0f) { AddTriangle(dPlus, make_triangle(v20, v0, v01)); AddTriangle(dMinus, make_triangle(v2, v20, v1)); AddTriangle(dMinus, make_triangle(v20, v01, v1)); } if (v0.y < 0.0f && v1.y > 0.0f && v2.y > 0.0f) { AddTriangle(dMinus, make_triangle(v01, v20, v0)); AddTriangle(dPlus, make_triangle(v1, v20, v01)); AddTriangle(dPlus, make_triangle(v1, v2, v20)); } if (v0.y < 0.0f && v1.y > 0.0f && v2.y < 0.0f) { AddTriangle(dPlus, make_triangle(v01, v1, v12)); AddTriangle(dMinus, make_triangle(v0, v01, v2)); AddTriangle(dMinus, make_triangle(v01, v12, v2)); } if (v0.y < 0.0f && v1.y < 0.0f && v2.y > 0.0f) { AddTriangle(dPlus, make_triangle(v12, v2, v20)); AddTriangle(dMinus, make_triangle(v1, v12, v0)); AddTriangle(dMinus, make_triangle(v12, v20, v0)); } } } TranslateGeometryY(dPlus, -y); TranslateGeometryY(dMinus, y); } static void SplitGeometryZ(GeometryData *data, GeometryData *dPlus, GeometryData *dMinus, OOScalar z) { NSCParameterAssert(data != NULL && dPlus != NULL && dMinus != NULL); // test each triangle splitting against z == 0.0 uint_fast32_t i, count = data->count; for (i = 0; i < count; i++) { bool done_tri = false; Vector v0 = data->triangles[i].v[0]; Vector v1 = data->triangles[i].v[1]; Vector v2 = data->triangles[i].v[2]; if (v0.z >= 0.0f && v1.z >= 0.0f && v2.z >= 0.0f) { AddTriangle(dPlus, data->triangles[i]); done_tri = true; } else if (v0.z <= 0.0f && v1.z <= 0.0f && v2.z <= 0.0f) { AddTriangle(dMinus, data->triangles[i]); done_tri = true; } if (!done_tri) // triangle must cross z == 0.0 { OOScalar i01, i12, i20; if (v0.z == v1.z) i01 = -1.0f; else i01 = v0.z / (v0.z - v1.z); if (v1.z == v2.z) i12 = -1.0f; else i12 = v1.z / (v1.z - v2.z); if (v2.z == v0.z) i20 = -1.0f; else i20 = v2.z / (v2.z - v0.z); Vector v01 = make_vector(i01 * (v1.x - v0.x) + v0.x, i01 * (v1.y - v0.y) + v0.y, 0.0f); Vector v12 = make_vector(i12 * (v2.x - v1.x) + v1.x, i12 * (v2.y - v1.y) + v1.y, 0.0f); Vector v20 = make_vector(i20 * (v0.x - v2.x) + v2.x, i20 * (v0.y - v2.y) + v2.y, 0.0f); // cases where a vertex is on the split. if (v0.z == 0.0f) { if (v1.z > 0) { AddTriangle(dPlus, make_triangle(v0, v1, v12)); AddTriangle(dMinus, make_triangle(v0, v12, v2)); } else { AddTriangle(dMinus, make_triangle(v0, v1, v12)); AddTriangle(dPlus, make_triangle(v0, v12, v2)); } } if (v1.z == 0.0f) { if (v2.z > 0) { AddTriangle(dPlus, make_triangle(v1, v2, v20)); AddTriangle(dMinus, make_triangle(v1, v20, v0)); } else { AddTriangle(dMinus, make_triangle(v1, v2, v20)); AddTriangle(dPlus, make_triangle(v1, v20, v0)); } } if (v2.z == 0.0f) { if (v0.z > 0) { AddTriangle(dPlus, make_triangle(v2, v0, v01)); AddTriangle(dMinus, make_triangle(v2, v01, v1)); } else { AddTriangle(dMinus, make_triangle(v2, v0, v01)); AddTriangle(dPlus, make_triangle(v2, v01, v1)); } } if (v0.z > 0.0f && v1.z > 0.0f && v2.z < 0.0f) { AddTriangle(dPlus, make_triangle(v0, v12, v20)); AddTriangle(dPlus, make_triangle(v0, v1, v12)); AddTriangle(dMinus, make_triangle(v20, v12, v2)); } if (v0.z > 0.0f && v1.z < 0.0f && v2.z > 0.0f) { AddTriangle(dPlus, make_triangle(v2, v01, v12)); AddTriangle(dPlus, make_triangle(v2, v0, v01)); AddTriangle(dMinus, make_triangle(v12, v01, v1)); } if (v0.z > 0.0f && v1.z < 0.0f && v2.z < 0.0f) { AddTriangle(dPlus, make_triangle(v20, v0, v01)); AddTriangle(dMinus, make_triangle(v2, v20, v1)); AddTriangle(dMinus, make_triangle(v20, v01, v1)); } if (v0.z < 0.0f && v1.z > 0.0f && v2.z > 0.0f) { AddTriangle(dMinus, make_triangle(v01, v20, v0)); AddTriangle(dPlus, make_triangle(v1, v20, v01)); AddTriangle(dPlus, make_triangle(v1, v2, v20)); } if (v0.z < 0.0f && v1.z > 0.0f && v2.z < 0.0f) { AddTriangle(dPlus, make_triangle(v01, v1, v12)); AddTriangle(dMinus, make_triangle(v0, v01, v2)); AddTriangle(dMinus, make_triangle(v01, v12, v2)); } if (v0.z < 0.0f && v1.z < 0.0f && v2.z > 0.0f) { AddTriangle(dPlus, make_triangle(v12, v2, v20)); AddTriangle(dMinus, make_triangle(v1, v12, v0)); AddTriangle(dMinus, make_triangle(v12, v20, v0)); } } } TranslateGeometryZ(dPlus, -z); TranslateGeometryZ(dMinus, z); } /* void AddTriangle_slow(GeometryData *data, Triangle tri) Slow path for AddTriangle(). Ensure that there is enough space to add a triangle, then actually add it. If no memory has been allocated yet, capacity is kOOMeshToOctreeConverterSmallDataCapacity, triangles points at smallData and pendingCapacity is the capacity passed to InitGeometryData(). Otherwise, triangles is a malloced pointer. This is marked noinline so that the fast path in AddTriangle() can be inlined. Without the attribute, clang (and probably gcc too) will inline AddTriangles_slow() into AddTriangle() (because it only has one call site), making AddTriangle() too heavy to inline. */ static NO_INLINE_FUNC void AddTriangle_slow(GeometryData *data, Triangle tri) { NSCParameterAssert(data != NULL); NSCParameterAssert(data->count == data->capacity); if (data->capacity == kOOMeshToOctreeConverterSmallDataCapacity) { data->capacity = MAX(data->pendingCapacity, (uint_fast32_t)kOOMeshToOctreeConverterSmallDataCapacity * 2); data->triangles = malloc(data->capacity * sizeof(Triangle)); memcpy(data->triangles, data->smallData, sizeof data->smallData); } else { // create more space by doubling the capacity of this geometry. data->capacity = 1 + data->capacity * 2; data->triangles = realloc(data->triangles, data->capacity * sizeof(Triangle)); // N.b.: we leak here if realloc() failed, but we're about to abort anyway. } if (EXPECT_NOT(data->triangles == NULL)) { OOLog(kOOLogAllocationFailure, @"!!!!! Ran out of memory to allocate more geometry!"); exit(EXIT_FAILURE); } data->triangles[data->count++] = tri; } oolite-1.82/src/Core/OOMouseInteractionMode.h000066400000000000000000000035561256642440500211430ustar00rootroot00000000000000/* OOMouseInteractionMode.h Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" /* OOMouseInteractionMode Mouse interaction states, defined in game-centric terms. The precise semantics may vary across platforms. The primary distinction is between UI screens and flight screens. Flight screens are screens 1-4 when neither docked nor paused, or the break pattern screen. Every other screen is a UI screen, including screens 1-4 when paused in flight. UI screens are divided between ones with clickable controls (like the star chart, outfitting screen and config screen), and ones without (like the manifest screen, system data screen and pause screen). Flight screens have two modes, one for mouse control enabled and one for mouse control disabled. */ typedef enum { MOUSE_MODE_UI_SCREEN_NO_INTERACTION, MOUSE_MODE_UI_SCREEN_WITH_INTERACTION, MOUSE_MODE_FLIGHT_NO_MOUSE_CONTROL, MOUSE_MODE_FLIGHT_WITH_MOUSE_CONTROL } OOMouseInteractionMode; NSString *OOStringFromMouseInteractionMode(OOMouseInteractionMode mode); BOOL OOMouseInteractionModeIsUIScreen(OOMouseInteractionMode mode); BOOL OOMouseInteractionModeIsFlightMode(OOMouseInteractionMode mode); oolite-1.82/src/Core/OOMouseInteractionMode.m000066400000000000000000000036651256642440500211510ustar00rootroot00000000000000/* OOMouseInteractionMode.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOMouseInteractionMode.h" NSString *OOStringFromMouseInteractionMode(OOMouseInteractionMode mode) { switch (mode) { case MOUSE_MODE_UI_SCREEN_NO_INTERACTION: return @"UI_SCREEN_NO_INTERACTION"; case MOUSE_MODE_UI_SCREEN_WITH_INTERACTION: return @"UI_SCREEN_WITH_INTERACTION"; case MOUSE_MODE_FLIGHT_NO_MOUSE_CONTROL: return @"FLIGHT_NO_MOUSE_CONTROL"; case MOUSE_MODE_FLIGHT_WITH_MOUSE_CONTROL: return @"FLIGHT_WITH_MOUSE_CONTROL"; } return [NSString stringWithFormat:@"", mode]; } BOOL OOMouseInteractionModeIsUIScreen(OOMouseInteractionMode mode) { switch (mode) { case MOUSE_MODE_UI_SCREEN_NO_INTERACTION: return YES; case MOUSE_MODE_UI_SCREEN_WITH_INTERACTION: return YES; case MOUSE_MODE_FLIGHT_NO_MOUSE_CONTROL: return NO; case MOUSE_MODE_FLIGHT_WITH_MOUSE_CONTROL: return NO; } return NO; } BOOL OOMouseInteractionModeIsFlightMode(OOMouseInteractionMode mode) { switch (mode) { case MOUSE_MODE_UI_SCREEN_NO_INTERACTION: return NO; case MOUSE_MODE_UI_SCREEN_WITH_INTERACTION: return NO; case MOUSE_MODE_FLIGHT_NO_MOUSE_CONTROL: return YES; case MOUSE_MODE_FLIGHT_WITH_MOUSE_CONTROL: return YES; } return NO; } oolite-1.82/src/Core/OOMusic.m000066400000000000000000000004421256642440500161220ustar00rootroot00000000000000// OOMusic.m: Selects the appropriate music class source file // depending on the operating system defined. // // Add new OS imports here. The -DOS_NAME flag in the GNUmakefile // will select which one gets compiled. // // David Taylor, 2005-05-04 #ifdef LINUX #import "SDLMusic.m" #endif oolite-1.82/src/Core/OOMusicController.h000066400000000000000000000036211256642440500201630ustar00rootroot00000000000000/* OOMusicController.h Singleton controller for music playback. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" @class OOMusic; #define OOLITE_ITUNES_SUPPORT OOLITE_MAC_OS_X typedef enum { kOOMusicOff, kOOMusicOn, kOOMusicITunes, #if OOLITE_ITUNES_SUPPORT kOOMusicModeMax = kOOMusicITunes #else kOOMusicModeMax = kOOMusicOn #endif } OOMusicMode; @interface OOMusicController: NSObject { @private OOMusicMode _mode; NSString *_missionMusic; OOMusic *_current; uint8_t _special; } + (OOMusicController *) sharedController; - (void) playMusicNamed:(NSString *)name loop:(BOOL)loop; - (void) playThemeMusic; - (void) playDockingMusic; - (void) playDockedMusic; - (void) setMissionMusic:(NSString *)missionMusicName; - (void) playMissionMusic; - (void) justStop; - (void) stop; - (void) stopMusicNamed:(NSString *)name; // Stop only if name == playingMusic - (void) stopThemeMusic; - (void) stopDockingMusic; - (void) stopMissionMusic; - (void) toggleDockingMusic; // Start docking music if none playing, stop docking music if currently playing docking music. - (NSString *) playingMusic; - (BOOL) isPlaying; - (OOMusicMode) mode; - (void) setMode:(OOMusicMode)mode; @end oolite-1.82/src/Core/OOMusicController.m000066400000000000000000000153731256642440500201770ustar00rootroot00000000000000/* OOMusicController.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOMusicController.h" #import "OOSound.h" #import "OOCollectionExtractors.h" #import "ResourceManager.h" static id sSingleton = nil; @interface OOMusicController (Private) - (void) playiTunesPlaylist:(NSString *)playlistName; - (void) pauseiTunes; @end // Values for _special enum { kSpecialNone, kSpecialTheme, kSpecialDocking, kSpecialDocked, kSpecialMission }; @implementation OOMusicController + (OOMusicController *) sharedController { if (sSingleton == nil) { sSingleton = [[self alloc] init]; } return sSingleton; } - (id) init { self = [super init]; if (self != nil) { NSString *modeString = [[NSUserDefaults standardUserDefaults] stringForKey:@"music mode"]; if ([modeString isEqualToString:@"off"]) _mode = kOOMusicOff; else if ([modeString isEqualToString:@"iTunes"]) _mode = kOOMusicITunes; else _mode = kOOMusicOn; // Handle unlikely case of taking prefs from iTunes-enabled system to other. if (_mode > kOOMusicModeMax) _mode = kOOMusicModeMax; [self setMissionMusic:@"OoliteTheme.ogg"]; } return self; } - (void) playMusicNamed:(NSString *)name loop:(BOOL)loop { if ([self isPlaying] && [name isEqual:[self playingMusic]]) return; if (_mode == kOOMusicOn || (_mode == kOOMusicITunes && [name isEqualToString:@"OoliteTheme.ogg"])) { OOMusic *music = [ResourceManager ooMusicNamed:name inFolder:@"Music"]; if (music != nil) { [_current stop]; [music playLooped:loop]; [_current release]; _current = [music retain]; } } } - (void) playThemeMusic { _special = kSpecialTheme; [self playMusicNamed:@"OoliteTheme.ogg" loop:YES]; } - (void) playDockingMusic { _special = kSpecialDocking; if (_mode == kOOMusicITunes) { [self playiTunesPlaylist:@"Oolite-Docking"]; } else { [self playMusicNamed:@"BlueDanube.ogg" loop:YES]; } } - (void) playDockedMusic { _special = kSpecialDocked; if (_mode == kOOMusicITunes) { [self playiTunesPlaylist:@"Oolite-Docked"]; } else { [self playMusicNamed:@"OoliteDocked.ogg" loop:NO]; } } - (void) setMissionMusic:(NSString *)missionMusicName { [_missionMusic autorelease]; _missionMusic = [missionMusicName copy]; } - (void) playMissionMusic { if (_missionMusic != nil) { _special = kSpecialMission; [self playMusicNamed:_missionMusic loop:NO]; } } // Stop without switching iTunes to in-flight music. - (void) justStop { [_current stop]; [_current release]; _current = nil; _special = kSpecialNone; } - (void) stop { [self justStop]; if (_mode == kOOMusicITunes) { [self playiTunesPlaylist:@"Oolite-Inflight"]; } } - (void) stopMusicNamed:(NSString *)name { if ([name isEqual:[self playingMusic]]) [self stop]; } - (void) stopThemeMusic { if (_special == kSpecialTheme) { [self justStop]; [self playDockedMusic]; } } - (void) stopDockingMusic { if (_special == kSpecialDocking) [self stop]; } - (void) stopMissionMusic { if (_special == kSpecialMission) [self stop]; } - (void) toggleDockingMusic { if (_mode != kOOMusicOn) return; if (![self isPlaying]) [self playDockingMusic]; else if (_special == kSpecialDocking) [self stop]; } - (NSString *) playingMusic { return [_current name]; } - (BOOL) isPlaying { return [_current isPlaying]; } - (OOMusicMode) mode { return _mode; } - (void) setMode:(OOMusicMode)mode { if (mode <= kOOMusicModeMax && _mode != mode) { if (_mode == kOOMusicITunes) [self pauseiTunes]; _mode = mode; if (_mode == kOOMusicOff) [self stop]; else switch (_special) { case kSpecialNone: [self stop]; break; case kSpecialTheme: [self playThemeMusic]; break; case kSpecialDocked: [self playDockedMusic]; break; case kSpecialDocking: [self playDockingMusic]; break; case kSpecialMission: [self playMissionMusic]; break; } NSString *modeString = nil; switch (_mode) { case kOOMusicOff: modeString = @"off"; break; case kOOMusicOn: modeString = @"on"; break; case kOOMusicITunes: modeString = @"iTunes"; break; } [[NSUserDefaults standardUserDefaults] setObject:modeString forKey:@"music mode"]; } } @end @implementation OOMusicController (Singleton) /* Canonical singleton boilerplate. See Cocoa Fundamentals Guide: Creating a Singleton Instance. See also +sharedController above. NOTE: assumes single-threaded access. */ + (id) allocWithZone:(NSZone *)inZone { if (sSingleton == nil) { sSingleton = [super allocWithZone:inZone]; return sSingleton; } return nil; } - (id) copyWithZone:(NSZone *)inZone { return self; } - (id) retain { return self; } - (NSUInteger) retainCount { return UINT_MAX; } - (void) release {} - (id) autorelease { return self; } @end @implementation OOMusicController (Private) #if OOLITE_MAC_OS_X - (void) playiTunesPlaylist:(NSString *)playlistName { NSString *ootunesScriptString = [NSString stringWithFormat: @"with timeout of 1 second\n" " tell application \"iTunes\"\n" " copy playlist \"%@\" to thePlaylist\n" " if thePlaylist exists then\n" " play some track of thePlaylist\n" " end if\n" " end tell\n" "end timeout", playlistName]; NSAppleScript *ootunesScript = [[[NSAppleScript alloc] initWithSource:ootunesScriptString] autorelease]; NSDictionary *errDict = nil; [ootunesScript executeAndReturnError:&errDict]; if (errDict) OOLog(@"sound.music.iTunesIntegration.failed", @"ootunes returned :%@", errDict); } - (void) pauseiTunes { NSString *ootunesScriptString = [NSString stringWithFormat:@"try\nignoring application responses\ntell application \"iTunes\" to pause\nend ignoring\nend try"]; NSAppleScript *ootunesScript = [[NSAppleScript alloc] initWithSource:ootunesScriptString]; NSDictionary *errDict = nil; [ootunesScript executeAndReturnError:&errDict]; if (errDict) OOLog(@"sound.music.iTunesIntegration.failed", @"ootunes returned :%@", errDict); [ootunesScript release]; } #else - (void) playiTunesPlaylist:(NSString *)playlistName {} - (void) pauseiTunes {} #endif @end oolite-1.82/src/Core/OONSOperation.h000066400000000000000000000110521256642440500172350ustar00rootroot00000000000000/* NSOperationQueue was introduced in Mac OS X 10.5 and GNUstep 0.19.x. This header helps us use it if it's available at runtime without requiring it (see OOAsyncWorkManager.m). -- Ahruman 2009-09-04 Copyright (C) 2009-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef OO_HAVE_NSOPERATION #if OOLITE_MAC_OS_X #define OO_HAVE_NSOPERATION (1) #elif OOLITE_GNUSTEP // #define OO_HAVE_NSOPERATION OOLITE_GNUSTEP_1_20 && OS_API_VERSION(100500, GS_API_LATEST) // GNUstep (even current trunk - 1.21 @ 2010.06.06) only contains an // incomplete implementation of NSOperation. Namely, it's missing // NSInvocationOperation which is used in OOAsyncWorkManager.m #define OO_HAVE_NSOPERATION (0) #endif #endif #ifndef OO_ALLOW_NSOPERATION #define OO_ALLOW_NSOPERATION 1 #endif #if OO_ALLOW_NSOPERATION #if !OO_HAVE_NSOPERATION #import "OOFunctionAttributes.h" #define OONSOperationQueue id #define OONSOperation id #define OONSInvocationOperation id /* NOTE: if OO_HAVE_NSOPERATION, these will compile to class names, which are not values. If you want an actual Class object, use [OONSOperationClass() class]. */ OOINLINE Class OONSOperationQueueClass() PURE_FUNC; OOINLINE Class OONSOperationClass() PURE_FUNC; OOINLINE Class OONSInvocationOperationClass() PURE_FUNC; OOINLINE Class OONSOperationQueueClass() { return NSClassFromString(@"NSOperationQueue"); } OOINLINE Class OONSOperationClass() { return NSClassFromString(@"NSOperation"); } OOINLINE Class OONSInvocationOperationClass() { return NSClassFromString(@"NSInvocationOperation"); } @class NSOperationQueue; @class NSOperation; @class NSInvocationOperation; enum { OONSOperationQueuePriorityVeryLow = -8, OONSOperationQueuePriorityLow = -4, OONSOperationQueuePriorityNormal = 0, OONSOperationQueuePriorityHigh = 4, OONSOperationQueuePriorityVeryHigh = 8 }; /* These classes are (deliberately) not implemented. Their declarations exist only so that the type system will know about the methods when they're used on id variables. */ @interface OONSOperationProto - (void) start; - (void) main; - (BOOL) isCancelled; - (void) cancel; - (BOOL) isExecuting; - (BOOL) isFinished; - (BOOL) isConcurrent; - (BOOL) isReady; - (void) addDependency:(NSOperation *)op; - (void) removeDependency:(NSOperation *)op; - (NSArray *) dependencies; - (NSInteger) queuePriority; - (void) setQueuePriority:(NSInteger)p; @end @interface OONSInvocationOperationProto - (id) initWithTarget:(id)target selector:(SEL)sel object:(id)arg; - (id) initWithInvocation:(NSInvocation *)inv; - (NSInvocation *) invocation; - (id) result; @end @interface OONSOperationQueueProto - (void) addOperation:(NSOperation *)op; - (NSArray *) operations; - (NSInteger) maxConcurrentOperationCount; - (void) setMaxConcurrentOperationCount:(NSInteger)cnt; - (void) setSuspended:(BOOL)b; - (BOOL) isSuspended; - (void) cancelAllOperations; - (void) waitUntilAllOperationsAreFinished; @end #else #define OONSOperationQueue NSOperationQueue * #define OONSOperation NSOperation * #define OONSInvocationOperation NSInvocationOperation * #define OONSOperationQueueClass() NSOperationQueue #define OONSOperationClass() NSOperation #define OONSInvocationOperationClass() NSInvocationOperation enum { OONSOperationQueuePriorityVeryLow = NSOperationQueuePriorityVeryLow, OONSOperationQueuePriorityLow = NSOperationQueuePriorityLow, OONSOperationQueuePriorityNormal = NSOperationQueuePriorityNormal, OONSOperationQueuePriorityHigh = NSOperationQueuePriorityHigh, OONSOperationQueuePriorityVeryHigh = NSOperationQueuePriorityVeryHigh }; #endif #endif oolite-1.82/src/Core/OOOXZManager.h000066400000000000000000000052751256642440500170210ustar00rootroot00000000000000/* OOOXZManager.h Responsible for installing and uninstalling OXZs Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #import "OOOpenGL.h" #import "NSFileManagerOOExtensions.h" #import "OOTypes.h" #import "GuiDisplayGen.h" typedef enum { OXZ_DOWNLOAD_NONE = 0, OXZ_DOWNLOAD_STARTED = 1, OXZ_DOWNLOAD_RECEIVING = 2, OXZ_DOWNLOAD_COMPLETE = 10, OXZ_DOWNLOAD_ERROR = 99 } OXZDownloadStatus; typedef enum { OXZ_STATE_NODATA, OXZ_STATE_MAIN, OXZ_STATE_UPDATING, OXZ_STATE_PICK_INSTALL, OXZ_STATE_PICK_INSTALLED, OXZ_STATE_PICK_REMOVE, OXZ_STATE_INSTALLING, OXZ_STATE_DEPENDENCIES, OXZ_STATE_REMOVING, OXZ_STATE_TASKDONE, OXZ_STATE_RESTARTING, OXZ_STATE_SETFILTER, OXZ_STATE_EXTRACT, OXZ_STATE_EXTRACTDONE } OXZInterfaceState; @interface OOOXZManager : NSObject { @private NSArray *_oxzList; NSArray *_managedList; NSArray *_filteredList; NSString *_currentFilter; OXZInterfaceState _interfaceState; BOOL _interfaceShowingOXZDetail; BOOL _changesMade; NSURLConnection *_currentDownload; NSString *_currentDownloadName; OXZDownloadStatus _downloadStatus; NSUInteger _downloadProgress; NSUInteger _downloadExpected; NSFileHandle *_fileWriter; NSUInteger _item; BOOL _downloadAllDependencies; NSUInteger _offset; NSString *_progressStatus; NSMutableSet *_dependencyStack; } + (OOOXZManager *) sharedManager; - (NSString *) installPath; - (BOOL) updateManifests; - (BOOL) cancelUpdate; - (NSArray *) manifests; - (NSArray *) managedOXZs; - (void) gui; - (BOOL) isRestarting; - (BOOL) isAcceptingTextInput; - (BOOL) isAcceptingGUIInput; - (void) processSelection; - (void) processTextInput:(NSString *)input; - (void) refreshTextInput:(NSString *)input; - (void) processFilterKey; - (void) processShowInfoKey; - (void) processExtractKey; - (OOGUIRow) showInstallOptions; - (OOGUIRow) showRemoveOptions; - (void) showOptionsUpdate; - (void) showOptionsPrev; - (void) showOptionsNext; - (void) processOptionsPrev; - (void) processOptionsNext; @end oolite-1.82/src/Core/OOOXZManager.m000066400000000000000000002155461256642440500170320ustar00rootroot00000000000000/* OOOXZManager.m Responsible for installing and uninstalling OXZs Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOOXZManager.h" #import "OOPListParsing.h" #import "OOStringParsing.h" #import "ResourceManager.h" #import "OOCacheManager.h" #import "Universe.h" #import "GuiDisplayGen.h" #import "PlayerEntity.h" #import "PlayerEntitySound.h" #import "OOCollectionExtractors.h" #import "NSFileManagerOOExtensions.h" #import "NSDataOOExtensions.h" #import "OOColor.h" #import "OOStringExpander.h" #import "MyOpenGLView.h" #import "unzip.h" #import "OOManifestProperties.h" /* The URL for the manifest.plist array. */ static NSString * const kOOOXZDataURL = @"http://addons.oolite.org/api/1.0/overview"; /* The config parameter to use a non-default URL at runtime */ static NSString * const kOOOXZDataConfig = @"oxz-index-url"; /* The filename to store the downloaded manifest.plist array */ static NSString * const kOOOXZManifestCache = @"Oolite-manifests.plist"; /* The filename to temporarily store the downloaded OXZ. Has an OXZ extension since we might want to read its manifest.plist out of it; */ static NSString * const kOOOXZTmpPath = @"Oolite-download.oxz"; /* The filename to temporarily store the downloaded plists. */ static NSString * const kOOOXZTmpPlistPath = @"Oolite-download.plist"; /* Log file record types */ static NSString * const kOOOXZErrorLog = @"oxz.manager.error"; static NSString * const kOOOXZDebugLog = @"oxz.manager.debug"; /* Filter components */ static NSString * const kOOOXZFilterAll = @"*"; static NSString * const kOOOXZFilterUpdates = @"u"; static NSString * const kOOOXZFilterInstallable = @"i"; static NSString * const kOOOXZFilterKeyword = @"k:"; static NSString * const kOOOXZFilterAuthor = @"a:"; static NSString * const kOOOXZFilterDays = @"d:"; static NSString * const kOOOXZFilterTag = @"t:"; typedef enum { OXZ_INSTALLABLE_OKAY, OXZ_INSTALLABLE_UPDATE, OXZ_INSTALLABLE_DEPENDENCIES, OXZ_INSTALLABLE_CONFLICTS, // for things to work, _ALREADY must be the first UNINSTALLABLE state // and all the INSTALLABLE ones must be before all the UNINSTALLABLE ones OXZ_UNINSTALLABLE_ALREADY, OXZ_UNINSTALLABLE_NOREMOTE, OXZ_UNINSTALLABLE_VERSION, OXZ_UNINSTALLABLE_MANUAL } OXZInstallableState; enum { OXZ_GUI_ROW_LISTHEAD = 0, OXZ_GUI_ROW_FIRSTRUN = 1, OXZ_GUI_ROW_PROGRESS = 1, OXZ_GUI_ROW_FILTERHELP = 1, OXZ_GUI_ROW_LISTPREV = 1, OXZ_GUI_ROW_LISTSTART = 2, OXZ_GUI_NUM_LISTROWS = 10, OXZ_GUI_ROW_LISTNEXT = 12, OXZ_GUI_ROW_LISTFILTER = 22, OXZ_GUI_ROW_LISTSTATUS = 14, OXZ_GUI_ROW_LISTDESC = 16, OXZ_GUI_ROW_LISTINFO1 = 20, OXZ_GUI_ROW_LISTINFO2 = 21, OXZ_GUI_ROW_INSTALL = 23, OXZ_GUI_ROW_INSTALLED = 24, OXZ_GUI_ROW_REMOVE = 25, OXZ_GUI_ROW_PROCEED = 25, OXZ_GUI_ROW_UPDATE = 26, OXZ_GUI_ROW_CANCEL = 26, OXZ_GUI_ROW_FILTERCURRENT = 26, OXZ_GUI_ROW_INPUT = 27, OXZ_GUI_ROW_EXIT = 27 }; NSComparisonResult oxzSort(id m1, id m2, void *context); static OOOXZManager *sSingleton = nil; // protocol was only formalised in 10.7 #if OOLITE_MAC_OS_X_10_7 @interface OOOXZManager (OOPrivate) #else @interface OOOXZManager (NSURLConnectionDataDelegate) #endif - (NSString *) manifestPath; - (NSString *) downloadPath; - (NSString *) extractionBasePathForIdentifier:(NSString *)identifier andVersion:(NSString *)version; - (NSString *) dataURL; - (NSString *) humanSize:(NSUInteger)bytes; - (BOOL) ensureInstallPath; - (BOOL) beginDownload:(NSMutableURLRequest *)request; - (BOOL) processDownloadedManifests; - (BOOL) processDownloadedOXZ; - (OXZInstallableState) installableState:(NSDictionary *)manifest; - (OOColor *) colorForManifest:(NSDictionary *)manifest; - (NSString *) installStatusForManifest:(NSDictionary *)manifest; - (BOOL) validateFilter:(NSString *)input; - (void) setOXZList:(NSArray *)list; - (void) setFilteredList:(NSArray *)list; - (NSArray *) applyCurrentFilter:(NSArray *)list; - (void) setCurrentDownload:(NSURLConnection *)download withLabel:(NSString *)label; - (void) setProgressStatus:(NSString *)newStatus; - (BOOL) installOXZ:(NSUInteger)item; - (BOOL) removeOXZ:(NSUInteger)item; - (NSArray *) installOptions; - (NSArray *) removeOptions; - (NSString *) extractOXZ:(NSUInteger)item; /* Delegates for URL downloader */ - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error; - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response; - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data; - (void)connectionDidFinishLoading:(NSURLConnection *)connection; @end @interface OOOXZManager (OOFilterRules) - (BOOL) applyFilterByNoFilter:(NSDictionary *)manifest; - (BOOL) applyFilterByUpdateRequired:(NSDictionary *)manifest; - (BOOL) applyFilterByInstallable:(NSDictionary *)manifest; - (BOOL) applyFilterByKeyword:(NSDictionary *)manifest keyword:(NSString *)keyword; - (BOOL) applyFilterByAuthor:(NSDictionary *)manifest author:(NSString *)author; - (BOOL) applyFilterByDays:(NSDictionary *)manifest days:(NSString *)days; - (BOOL) applyFilterByTag:(NSDictionary *)manifest tag:(NSString *)tag; @end @implementation OOOXZManager + (OOOXZManager *)sharedManager { // NOTE: assumes single-threaded first access. if (sSingleton == nil) sSingleton = [[self alloc] init]; return sSingleton; } - (id) init { self = [super init]; if (self != nil) { _downloadStatus = OXZ_DOWNLOAD_NONE; // if the file has not been downloaded, this will be nil [self setOXZList:OOArrayFromFile([self manifestPath])]; OOLog(kOOOXZDebugLog,@"Initialised with %@",_oxzList); _interfaceState = OXZ_STATE_NODATA; _currentFilter = @"*"; _interfaceShowingOXZDetail = NO; _changesMade = NO; _downloadAllDependencies = NO; _dependencyStack = [[NSMutableSet alloc] initWithCapacity:8]; [self setProgressStatus:@""]; } return self; } - (void)dealloc { if (sSingleton == self) sSingleton = nil; [self setCurrentDownload:nil withLabel:nil]; DESTROY(_oxzList); DESTROY(_managedList); DESTROY(_filteredList); [super dealloc]; } /* The install path for OXZs downloaded by * Oolite. Library/ApplicationSupport seems to be the most appropriate * location. */ - (NSString *) installPath { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory,NSUserDomainMask,YES); NSString *appPath = [paths objectAtIndex:0]; if (appPath != nil) { appPath = [appPath stringByAppendingPathComponent:@"Oolite"]; #if OOLITE_MAC_OS_X appPath = [appPath stringByAppendingPathComponent:@"Managed AddOns"]; #else /* GNUStep uses "ApplicationSupport" rather than "Application * Support" so match convention by not putting a space in the * path either */ appPath = [appPath stringByAppendingPathComponent:@"ManagedAddOns"]; #endif return appPath; } return nil; } - (NSString *) extractionBasePathForIdentifier:(NSString *)identifier andVersion:(NSString *)version { NSString *basePath = [[ResourceManager userRootPaths] lastObject]; NSString *rawMainDir = [NSString stringWithFormat:@"%@-%@.off",identifier,version]; NSCharacterSet *blacklist = [NSCharacterSet characterSetWithCharactersInString:@"'#%^&{}[]/~|\\?<,:\" "]; return [[[basePath stringByAppendingPathComponent:[[rawMainDir componentsSeparatedByCharactersInSet:blacklist] componentsJoinedByString:@""]] retain] autorelease]; } - (BOOL) ensureInstallPath { BOOL exists, directory; NSFileManager *fmgr = [NSFileManager defaultManager]; NSString *path = [self installPath]; exists = [fmgr fileExistsAtPath:path isDirectory:&directory]; if (exists && !directory) { OOLog(kOOOXZErrorLog, @"Expected %@ to be a folder, but it is a file.", path); return NO; } if (!exists) { if (![fmgr oo_createDirectoryAtPath:path attributes:nil]) { OOLog(kOOOXZErrorLog, @"Could not create folder %@.", path); return NO; } } return YES; } - (NSString *) manifestPath { return [[[OOCacheManager sharedCache] cacheDirectoryPathCreatingIfNecessary:YES] stringByAppendingPathComponent:kOOOXZManifestCache]; } /* Download mechanism could destroy a correct file if it failed * half-way and was downloaded on top of the old one. So this loads it * off to the side a bit */ - (NSString *) downloadPath { if (_interfaceState == OXZ_STATE_UPDATING) { return [[[OOCacheManager sharedCache] cacheDirectoryPathCreatingIfNecessary:YES] stringByAppendingPathComponent:kOOOXZTmpPlistPath]; } else { return [[[OOCacheManager sharedCache] cacheDirectoryPathCreatingIfNecessary:YES] stringByAppendingPathComponent:kOOOXZTmpPath]; } } - (NSString *) dataURL { /* Not expected to be set in general, but might be useful for some users */ NSString *url = [[NSUserDefaults standardUserDefaults] stringForKey:kOOOXZDataConfig]; if (url != nil) { return url; } return kOOOXZDataURL; } - (NSString *) humanSize:(NSUInteger)bytes { if (bytes == 0) { return DESC(@"oolite-oxzmanager-missing-field"); } else if (bytes < 1024) { return @"<1 kB"; } else if (bytes < 1024*1024) { return [NSString stringWithFormat:@"%lu kB",bytes>>10]; } else { return [NSString stringWithFormat:@"%.2f MB",((float)(bytes>>10))/1024]; } } - (void) setOXZList:(NSArray *)list { DESTROY(_oxzList); if (list != nil) { _oxzList = [[list sortedArrayUsingFunction:oxzSort context:NULL] retain]; // needed for update to available versions DESTROY(_managedList); } } - (void) setFilteredList:(NSArray *)list { DESTROY(_filteredList); _filteredList = [list copy]; // copy retains } - (void) setFilter:(NSString *)filter { DESTROY(_currentFilter); _currentFilter = [[filter lowercaseString] copy]; // copy retains } - (NSArray *) applyCurrentFilter:(NSArray *)list { SEL filterSelector = @selector(applyFilterByNoFilter:); NSString *parameter = nil; if ([_currentFilter isEqualToString:kOOOXZFilterUpdates]) { filterSelector = @selector(applyFilterByUpdateRequired:); } else if ([_currentFilter isEqualToString:kOOOXZFilterInstallable]) { filterSelector = @selector(applyFilterByInstallable:); } else if ([_currentFilter hasPrefix:kOOOXZFilterKeyword]) { filterSelector = @selector(applyFilterByKeyword:keyword:); parameter = [_currentFilter substringFromIndex:[kOOOXZFilterKeyword length]]; } else if ([_currentFilter hasPrefix:kOOOXZFilterAuthor]) { filterSelector = @selector(applyFilterByAuthor:author:); parameter = [_currentFilter substringFromIndex:[kOOOXZFilterAuthor length]]; } else if ([_currentFilter hasPrefix:kOOOXZFilterDays]) { filterSelector = @selector(applyFilterByDays:days:); parameter = [_currentFilter substringFromIndex:[kOOOXZFilterDays length]]; } else if ([_currentFilter hasPrefix:kOOOXZFilterTag]) { filterSelector = @selector(applyFilterByTag:tag:); parameter = [_currentFilter substringFromIndex:[kOOOXZFilterTag length]]; } NSMutableArray *filteredList = [NSMutableArray arrayWithCapacity:[list count]]; NSDictionary *manifest = nil; NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[[self class] instanceMethodSignatureForSelector:filterSelector]]; [invocation setSelector:filterSelector]; [invocation setTarget:self]; if (parameter != nil) { [invocation setArgument:¶meter atIndex:3]; } foreach(manifest, list) { [invocation setArgument:&manifest atIndex:2]; [invocation invoke]; BOOL filterAccepted = NO; [invocation getReturnValue:&filterAccepted]; if (filterAccepted) { [filteredList addObject:manifest]; } } // any bad filter that gets this far is also treated as '*' // so don't need to explicitly test for '*' or '' return [[filteredList copy] autorelease]; } /*** Start filters ***/ - (BOOL) applyFilterByNoFilter:(NSDictionary *)manifest { return YES; } - (BOOL) applyFilterByUpdateRequired:(NSDictionary *)manifest { return ([self installableState:manifest] == OXZ_INSTALLABLE_UPDATE); } - (BOOL) applyFilterByInstallable:(NSDictionary *)manifest { return ([self installableState:manifest] < OXZ_UNINSTALLABLE_ALREADY); } - (BOOL) applyFilterByKeyword:(NSDictionary *)manifest keyword:(NSString *)keyword { NSString *parameter = nil; NSArray *parameters = [NSArray arrayWithObjects:kOOManifestTitle,kOOManifestDescription,kOOManifestCategory,nil]; foreach (parameter,parameters) { if ([[manifest oo_stringForKey:parameter] rangeOfString:keyword options:NSCaseInsensitiveSearch].location != NSNotFound) { return YES; } } // tags are slightly different parameters = [manifest oo_arrayForKey:kOOManifestTags]; foreach (parameter,parameters) { if ([parameter rangeOfString:keyword options:NSCaseInsensitiveSearch].location != NSNotFound) { return YES; } } return NO; } - (BOOL) applyFilterByAuthor:(NSDictionary *)manifest author:(NSString *)author { NSString *mAuth = [manifest oo_stringForKey:kOOManifestAuthor]; return ([mAuth rangeOfString:author options:NSCaseInsensitiveSearch].location != NSNotFound); } - (BOOL) applyFilterByDays:(NSDictionary *)manifest days:(NSString *)days { NSInteger i = [days integerValue]; if (i < 1) { return NO; } else { NSUInteger updated = [manifest oo_unsignedIntegerForKey:kOOManifestUploadDate]; NSUInteger now = (NSUInteger)[[NSDate date] timeIntervalSince1970]; return (updated + (86400 * i) > now); } } - (BOOL) applyFilterByTag:(NSDictionary *)manifest tag:(NSString *)tag { NSString *parameter = nil; NSArray *parameters = [manifest oo_arrayForKey:kOOManifestTags]; foreach (parameter,parameters) { if ([parameter rangeOfString:tag options:NSCaseInsensitiveSearch].location != NSNotFound) { return YES; } } return NO; } /*** End filters ***/ - (BOOL) validateFilter:(NSString *)input { NSString *filter = [input lowercaseString]; if (([filter length] == 0) // empty is valid || ([filter isEqualToString:kOOOXZFilterAll]) || ([filter isEqualToString:kOOOXZFilterUpdates]) || ([filter isEqualToString:kOOOXZFilterInstallable]) || ([filter hasPrefix:kOOOXZFilterKeyword] && [filter length] > [kOOOXZFilterKeyword length]) || ([filter hasPrefix:kOOOXZFilterAuthor] && [filter length] > [kOOOXZFilterAuthor length]) || ([filter hasPrefix:kOOOXZFilterDays] && [[filter substringFromIndex:[kOOOXZFilterDays length]] intValue] > 0) || ([filter hasPrefix:kOOOXZFilterTag] && [filter length] > [kOOOXZFilterTag length]) ) { return YES; } return NO; } - (void) setCurrentDownload:(NSURLConnection *)download withLabel:(NSString *)label { if (_currentDownload != nil) { [_currentDownload cancel]; // releases via delegate } _currentDownload = [download retain]; DESTROY(_currentDownloadName); _currentDownloadName = [label copy]; } - (void) setProgressStatus:(NSString *)new { DESTROY(_progressStatus); _progressStatus = [new copy]; } - (BOOL) updateManifests { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[self dataURL]]]; if (_downloadStatus != OXZ_DOWNLOAD_NONE) { return NO; } _downloadStatus = OXZ_DOWNLOAD_STARTED; _interfaceState = OXZ_STATE_UPDATING; [self setProgressStatus:@""]; return [self beginDownload:request]; } - (BOOL) beginDownload:(NSMutableURLRequest *)request { NSString *userAgent = [NSString stringWithFormat:@"Oolite/%@", [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]]; [request setValue:userAgent forHTTPHeaderField:@"User-Agent"]; NSURLConnection *download = [[NSURLConnection alloc] initWithRequest:request delegate:self]; if (download) { _downloadProgress = 0; _downloadExpected = 0; NSString *label = DESC(@"oolite-oxzmanager-download-label-list"); if (_interfaceState != OXZ_STATE_UPDATING) { NSDictionary *expectedManifest = nil; expectedManifest = [_filteredList objectAtIndex:_item]; label = [expectedManifest oo_stringForKey:kOOManifestTitle defaultValue:DESC(@"oolite-oxzmanager-download-label-oxz")]; } [self setCurrentDownload:download withLabel:label]; // retains it [download release]; OOLog(kOOOXZDebugLog,@"Download request received, using %@ and downloading to %@",[request URL],[self downloadPath]); return YES; } else { OOLog(kOOOXZErrorLog,@"Unable to start downloading file at %@",[request URL]); _downloadStatus = OXZ_DOWNLOAD_ERROR; return NO; } } - (BOOL) cancelUpdate { if (!(_interfaceState == OXZ_STATE_UPDATING || _interfaceState == OXZ_STATE_INSTALLING) || _downloadStatus == OXZ_DOWNLOAD_NONE) { return NO; } OOLog(kOOOXZDebugLog,@"Trying to cancel file download"); if (_currentDownload != nil) { [_currentDownload cancel]; } else if (_downloadStatus == OXZ_DOWNLOAD_COMPLETE) { NSString *path = [self downloadPath]; [[NSFileManager defaultManager] oo_removeItemAtPath:path]; } _downloadStatus = OXZ_DOWNLOAD_NONE; if (_interfaceState == OXZ_STATE_INSTALLING) { _interfaceState = OXZ_STATE_PICK_INSTALL; } else { _interfaceState = OXZ_STATE_MAIN; } [self gui]; return YES; } - (NSArray *) manifests { return _oxzList; } - (NSArray *) managedOXZs { if (_managedList == nil) { // if this list is being reset, also reset the current install list [ResourceManager resetManifestKnowledgeForOXZManager]; NSArray *managedOXZs = [[NSFileManager defaultManager] oo_directoryContentsAtPath:[self installPath]]; NSMutableArray *manifests = [NSMutableArray arrayWithCapacity:[managedOXZs count]]; NSString *filename = nil; NSString *fullpath = nil; NSDictionary *manifest = nil; foreach (filename, managedOXZs) { fullpath = [[self installPath] stringByAppendingPathComponent:filename]; manifest = OODictionaryFromFile([fullpath stringByAppendingPathComponent:@"manifest.plist"]); if (manifest != nil) { NSMutableDictionary *adjManifest = [NSMutableDictionary dictionaryWithDictionary:manifest]; [adjManifest setObject:fullpath forKey:kOOManifestFilePath]; NSDictionary *stored = nil; /* The list is already sorted to put the latest * versions first. This flag means that it stops * checking the list for versions once it finds one * that is plausibly installable */ BOOL foundInstallable = NO; foreach (stored, _oxzList) { if ([[stored oo_stringForKey:kOOManifestIdentifier] isEqualToString:[manifest oo_stringForKey:kOOManifestIdentifier]]) { if (foundInstallable == NO) { [adjManifest setObject:[stored oo_stringForKey:kOOManifestVersion] forKey:kOOManifestAvailableVersion]; [adjManifest setObject:[stored oo_stringForKey:kOOManifestDownloadURL] forKey:kOOManifestDownloadURL]; if ([ResourceManager checkVersionCompatibility:manifest forOXP:nil]) { foundInstallable = YES; } } } } [manifests addObject:adjManifest]; } } [manifests sortUsingFunction:oxzSort context:NULL]; _managedList = [manifests copy]; } return _managedList; } - (BOOL) processDownloadedManifests { if (_downloadStatus != OXZ_DOWNLOAD_COMPLETE) { return NO; } [self setOXZList:OOArrayFromFile([self downloadPath])]; if (_oxzList != nil) { [_oxzList writeToFile:[self manifestPath] atomically:YES]; // and clean up the temp file [[NSFileManager defaultManager] oo_removeItemAtPath:[self downloadPath]]; // invalidate the managed list DESTROY(_managedList); _interfaceState = OXZ_STATE_TASKDONE; [self gui]; return YES; } else { _downloadStatus = OXZ_DOWNLOAD_ERROR; OOLog(kOOOXZErrorLog,@"Downloaded manifest was not a valid plist, has been left in %@",[self downloadPath]); // revert to the old one [self setOXZList:OOArrayFromFile([self manifestPath])]; _interfaceState = OXZ_STATE_TASKDONE; [self gui]; return NO; } } - (BOOL) processDownloadedOXZ { if (_downloadStatus != OXZ_DOWNLOAD_COMPLETE) { return NO; } NSDictionary *downloadedManifest = OODictionaryFromFile([[self downloadPath] stringByAppendingPathComponent:@"manifest.plist"]); if (downloadedManifest == nil) { _downloadStatus = OXZ_DOWNLOAD_ERROR; OOLog(kOOOXZErrorLog,@"Downloaded OXZ does not contain a manifest.plist, has been left in %@",[self downloadPath]); _interfaceState = OXZ_STATE_TASKDONE; [self gui]; return NO; } NSDictionary *expectedManifest = nil; expectedManifest = [_filteredList objectAtIndex:_item]; if (expectedManifest == nil || (![[downloadedManifest oo_stringForKey:kOOManifestIdentifier] isEqualToString:[expectedManifest oo_stringForKey:kOOManifestIdentifier]]) || (![[downloadedManifest oo_stringForKey:kOOManifestVersion] isEqualToString:[expectedManifest oo_stringForKey:kOOManifestAvailableVersion defaultValue:[expectedManifest oo_stringForKey:kOOManifestVersion]]]) ) { _downloadStatus = OXZ_DOWNLOAD_ERROR; OOLog(kOOOXZErrorLog,@"Downloaded OXZ does not have the same identifer and version as expected. This might be due to your manifests list being out of date - try updating it."); _interfaceState = OXZ_STATE_TASKDONE; [self gui]; return NO; } // this appears to be the OXZ we expected // filename is going to be identifier.oxz NSString *filename = [[downloadedManifest oo_stringForKey:kOOManifestIdentifier] stringByAppendingString:@".oxz"]; if (![self ensureInstallPath]) { _downloadStatus = OXZ_DOWNLOAD_ERROR; OOLog(kOOOXZErrorLog,@"Unable to create installation folder."); _interfaceState = OXZ_STATE_TASKDONE; [self gui]; return NO; } // delete filename if it exists from OXZ folder NSString *destination = [[self installPath] stringByAppendingPathComponent:filename]; [[NSFileManager defaultManager] oo_removeItemAtPath:destination]; // move the temp file on to it if (![[NSFileManager defaultManager] oo_moveItemAtPath:[self downloadPath] toPath:destination]) { _downloadStatus = OXZ_DOWNLOAD_ERROR; OOLog(kOOOXZErrorLog,@"Downloaded OXZ could not be installed."); _interfaceState = OXZ_STATE_TASKDONE; [self gui]; return NO; } _changesMade = YES; DESTROY(_managedList); // will need updating // do this now to cope with circular dependencies on download [ResourceManager resetManifestKnowledgeForOXZManager]; /** * If downloadedManifest is in _dependencyStack, remove it * Get downloadedManifest requires_oxp list * Add entries ones to _dependencyStack * If _dependencyStack has contents, update _progressStatus * ...and start the download of the 'first' item in _dependencyStack * ...which isn't already installed (_dependencyStack is unordered * ...so 'first' isn't really defined) * * ...if the item in _dependencyStack is not findable (e.g. wrong * ...version) then stop here. */ NSArray *requires = [downloadedManifest oo_arrayForKey:kOOManifestRequiresOXPs defaultValue:nil]; if (requires == nil) { // just in case the requirements are only specified in the online copy requires = [expectedManifest oo_arrayForKey:kOOManifestRequiresOXPs defaultValue:nil]; } NSDictionary *requirement = nil; NSMutableString *progress = [NSMutableString stringWithCapacity:2048]; OOLog(kOOOXZDebugLog,@"Dependency stack has %lu elements",[_dependencyStack count]); if ([_dependencyStack count] > 0) { // will remove as iterate, so create a temp copy to iterate over NSSet *tempStack = [NSSet setWithSet:_dependencyStack]; foreach (requirement, tempStack) { OOLog(kOOOXZDebugLog,@"Dependency stack: checking %@",[requirement oo_stringForKey:kOOManifestRelationIdentifier]); if (![ResourceManager manifest:downloadedManifest HasUnmetDependency:requirement logErrors:NO]) { [progress appendFormat:DESC(@"oolite-oxzmanager-progress-now-has-@"),[requirement oo_stringForKey:kOOManifestRelationDescription defaultValue:[requirement oo_stringForKey:kOOManifestRelationIdentifier]]]; // it was unmet, but now it's met [_dependencyStack removeObject:requirement]; OOLog(kOOOXZDebugLog,@"Dependency stack: requirement met"); } else if ([[requirement oo_stringForKey:kOOManifestRelationIdentifier] isEqualToString:[downloadedManifest oo_stringForKey:kOOManifestIdentifier]]) { // remove the requirement for the just downloaded OXP [_dependencyStack removeObject:requirement]; } } } if (requires != nil) { foreach (requirement, requires) { if ([ResourceManager manifest:downloadedManifest HasUnmetDependency:requirement logErrors:NO]) { OOLog(kOOOXZDebugLog,@"Dependency stack: adding %@",[requirement oo_stringForKey:kOOManifestRelationIdentifier]); [_dependencyStack addObject:requirement]; [progress appendFormat:DESC(@"oolite-oxzmanager-progress-requires-@"),[requirement oo_stringForKey:kOOManifestRelationDescription defaultValue:[requirement oo_stringForKey:kOOManifestRelationIdentifier]]]; } } } if ([_dependencyStack count] > 0) { // get an object from the requirements list, and download it // if it can be found BOOL undownloadedRequirement = NO; NSDictionary *availableDownload = nil; BOOL foundDownload = NO; NSUInteger index = 0; NSString *needsIdentifier = nil; do { undownloadedRequirement = YES; requirement = [_dependencyStack anyObject]; OOLog(kOOOXZDebugLog,@"Dependency stack: next is %@",[requirement oo_stringForKey:kOOManifestRelationIdentifier]); if (!_downloadAllDependencies) { [progress appendString:DESC(@"oolite-oxzmanager-progress-get-required")]; } needsIdentifier = [requirement oo_stringForKey:kOOManifestRelationIdentifier]; foreach (availableDownload, _oxzList) { if ([[availableDownload oo_stringForKey:kOOManifestIdentifier] isEqualToString:needsIdentifier]) { if ([ResourceManager matchVersions:requirement withVersion:[availableDownload oo_stringForKey:kOOManifestVersion]]) { OOLog(kOOOXZDebugLog,@"Dependency stack: found download for next item"); foundDownload = YES; index = [_oxzList indexOfObject:availableDownload]; break; } } } if (foundDownload) { if ([self installableState:[_oxzList objectAtIndex:index]] == OXZ_UNINSTALLABLE_ALREADY) { OOLog(kOOOXZDebugLog,@"Dependency stack: %@ is downloaded but not yet loadable, removing from list.",[requirement oo_stringForKey:kOOManifestRelationIdentifier]); // then this has already been downloaded, but // can't be configured yet presumably because // another dependency is still to be loaded [_dependencyStack removeObject:requirement]; if ([_dependencyStack count] > 0) { // try again undownloadedRequirement = NO; } else { // this case should probably never happen // is handled below just in case foundDownload = NO; } } } } while (!undownloadedRequirement); if (foundDownload) { // must clear filters entirely at this point [self setFilteredList:_oxzList]; // then download that item _downloadStatus = OXZ_DOWNLOAD_NONE; if (_downloadAllDependencies) { OOLog(kOOOXZDebugLog,@"Dependency stack: installing %lu from list",index); if (![self installOXZ:index]) { // if a required dependency is somehow uninstallable // e.g. required+maximum version don't match this Oolite [progress appendFormat:DESC(@"oolite-oxzmanager-progress-required-@-not-found"),[requirement oo_stringForKey:kOOManifestRelationDescription defaultValue:[requirement oo_stringForKey:kOOManifestRelationIdentifier]]]; [self setProgressStatus:progress]; OOLog(kOOOXZErrorLog,@"OXZ dependency %@ could not be found for automatic download.",needsIdentifier); _downloadStatus = OXZ_DOWNLOAD_ERROR; OOLog(kOOOXZErrorLog,@"Downloaded OXZ could not be installed."); _interfaceState = OXZ_STATE_TASKDONE; [self gui]; return NO; } } else { _interfaceState = OXZ_STATE_DEPENDENCIES; _item = index; } [self setProgressStatus:progress]; [self gui]; return YES; } // this is probably always the case, see above else if ([_dependencyStack count] > 0) { [progress appendFormat:DESC(@"oolite-oxzmanager-progress-required-@-not-found"),[requirement oo_stringForKey:kOOManifestRelationDescription defaultValue:[requirement oo_stringForKey:kOOManifestRelationIdentifier]]]; [self setProgressStatus:progress]; OOLog(kOOOXZErrorLog,@"OXZ dependency %@ could not be found for automatic download.",needsIdentifier); _downloadStatus = OXZ_DOWNLOAD_ERROR; OOLog(kOOOXZErrorLog,@"Downloaded OXZ could not be installed."); _interfaceState = OXZ_STATE_TASKDONE; [self gui]; return NO; } } [self setProgressStatus:@""]; _interfaceState = OXZ_STATE_TASKDONE; [_dependencyStack removeAllObjects]; // just in case _downloadAllDependencies = NO; [self gui]; return YES; } - (NSDictionary *) installedManifestForIdentifier:(NSString *)identifier { NSArray *installed = [self managedOXZs]; NSDictionary *manifest = nil; foreach (manifest,installed) { if ([[manifest oo_stringForKey:kOOManifestIdentifier] isEqualToString:identifier]) { return manifest; } } return nil; } - (OXZInstallableState) installableState:(NSDictionary *)manifest { NSString *title = [manifest oo_stringForKey:kOOManifestTitle defaultValue:nil]; NSString *identifier = [manifest oo_stringForKey:kOOManifestIdentifier defaultValue:nil]; /* Check Oolite version */ if (![ResourceManager checkVersionCompatibility:manifest forOXP:title]) { return OXZ_UNINSTALLABLE_VERSION; } /* Check for current automated install */ NSDictionary *installed = [self installedManifestForIdentifier:identifier]; if (installed == nil) { // check for manual install installed = [ResourceManager manifestForIdentifier:identifier]; } if (installed != nil) { if (![[installed oo_stringForKey:kOOManifestFilePath] hasPrefix:[self installPath]]) { // installed manually return OXZ_UNINSTALLABLE_MANUAL; } if ([[installed oo_stringForKey:kOOManifestVersion] isEqualToString:[manifest oo_stringForKey:kOOManifestAvailableVersion defaultValue:[manifest oo_stringForKey:kOOManifestVersion]]] && [[NSFileManager defaultManager] fileExistsAtPath:[installed oo_stringForKey:kOOManifestFilePath]]) { // installed this exact version already, and haven't // uninstalled it since entering the manager, and it's // still available return OXZ_UNINSTALLABLE_ALREADY; } else if ([installed oo_stringForKey:kOOManifestAvailableVersion defaultValue:nil] == nil) { // installed, but no remote copy is indexed any more return OXZ_UNINSTALLABLE_NOREMOTE; } } /* Check for dependencies being met */ if ([ResourceManager manifestHasConflicts:manifest logErrors:NO]) { return OXZ_INSTALLABLE_CONFLICTS; } if ([ResourceManager manifestHasMissingDependencies:manifest logErrors:NO]) { return OXZ_INSTALLABLE_DEPENDENCIES; } else { if (installed != nil) { OOLog(@"version.debug",@"%@ mv:%@ mav:%@",identifier,[installed oo_stringForKey:kOOManifestVersion],[manifest oo_stringForKey:kOOManifestVersion]); // if (CompareVersions(ComponentsFromVersionString([installed oo_stringForKey:kOOManifestVersion]),ComponentsFromVersionString([installed oo_stringForKey:kOOManifestAvailableVersion])) == NSOrderedDescending) if (CompareVersions(ComponentsFromVersionString([installed oo_stringForKey:kOOManifestVersion]),ComponentsFromVersionString([manifest oo_stringForKey:kOOManifestVersion])) == NSOrderedDescending) { // the installed copy is more recent than the server copy return OXZ_UNINSTALLABLE_NOREMOTE; } return OXZ_INSTALLABLE_UPDATE; } return OXZ_INSTALLABLE_OKAY; } } - (OOColor *) colorForManifest:(NSDictionary *)manifest { switch ([self installableState:manifest]) { case OXZ_INSTALLABLE_OKAY: return [OOColor yellowColor]; case OXZ_INSTALLABLE_UPDATE: return [OOColor cyanColor]; case OXZ_INSTALLABLE_DEPENDENCIES: return [OOColor orangeColor]; case OXZ_INSTALLABLE_CONFLICTS: return [OOColor brownColor]; case OXZ_UNINSTALLABLE_ALREADY: return [OOColor whiteColor]; case OXZ_UNINSTALLABLE_MANUAL: return [OOColor redColor]; case OXZ_UNINSTALLABLE_VERSION: return [OOColor grayColor]; case OXZ_UNINSTALLABLE_NOREMOTE: return [OOColor blueColor]; } return [OOColor yellowColor]; // never } - (NSString *) installStatusForManifest:(NSDictionary *)manifest { switch ([self installableState:manifest]) { case OXZ_INSTALLABLE_OKAY: return DESC(@"oolite-oxzmanager-installable-okay"); case OXZ_INSTALLABLE_UPDATE: return DESC(@"oolite-oxzmanager-installable-update"); case OXZ_INSTALLABLE_DEPENDENCIES: return DESC(@"oolite-oxzmanager-installable-depend"); case OXZ_INSTALLABLE_CONFLICTS: return DESC(@"oolite-oxzmanager-installable-conflicts"); case OXZ_UNINSTALLABLE_ALREADY: return DESC(@"oolite-oxzmanager-installable-already"); case OXZ_UNINSTALLABLE_MANUAL: return DESC(@"oolite-oxzmanager-installable-manual"); case OXZ_UNINSTALLABLE_VERSION: return DESC(@"oolite-oxzmanager-installable-version"); case OXZ_UNINSTALLABLE_NOREMOTE: return DESC(@"oolite-oxzmanager-installable-noremote"); } return nil; // never } - (void) gui { GuiDisplayGen *gui = [UNIVERSE gui]; OOGUIRow startRow = OXZ_GUI_ROW_EXIT; #if OOLITE_WINDOWS /* unlock OXZs ahead of potential changes by making sure sound * files aren't being held open */ [ResourceManager clearCaches]; [PLAYER destroySound]; #endif [gui clearAndKeepBackground:YES]; [gui setTitle:DESC(@"oolite-oxzmanager-title")]; /* This switch will give warnings unless all states are * covered. */ switch (_interfaceState) { case OXZ_STATE_SETFILTER: [gui setTitle:DESC(@"oolite-oxzmanager-title-setfilter")]; [gui setText:[NSString stringWithFormat:DESC(@"oolite-oxzmanager-currentfilter-is-@"),_currentFilter] forRow:OXZ_GUI_ROW_FILTERCURRENT align:GUI_ALIGN_LEFT]; [gui addLongText:DESC(@"oolite-oxzmanager-filterhelp") startingAtRow:OXZ_GUI_ROW_FILTERHELP align:GUI_ALIGN_LEFT]; return; // don't do normal row selection stuff case OXZ_STATE_NODATA: if (_oxzList == nil) { [gui addLongText:DESC(@"oolite-oxzmanager-firstrun") startingAtRow:OXZ_GUI_ROW_FIRSTRUN align:GUI_ALIGN_LEFT]; [gui setText:DESC(@"oolite-oxzmanager-download-list") forRow:OXZ_GUI_ROW_UPDATE align:GUI_ALIGN_CENTER]; [gui setKey:@"_UPDATE" forRow:OXZ_GUI_ROW_UPDATE]; startRow = OXZ_GUI_ROW_UPDATE; } else { // update data [gui addLongText:DESC(@"oolite-oxzmanager-secondrun") startingAtRow:OXZ_GUI_ROW_FIRSTRUN align:GUI_ALIGN_LEFT]; [gui setText:DESC(@"oolite-oxzmanager-download-noupdate") forRow:OXZ_GUI_ROW_PROCEED align:GUI_ALIGN_CENTER]; [gui setKey:@"_MAIN" forRow:OXZ_GUI_ROW_PROCEED]; [gui setText:DESC(@"oolite-oxzmanager-update-list") forRow:OXZ_GUI_ROW_UPDATE align:GUI_ALIGN_CENTER]; [gui setKey:@"_UPDATE" forRow:OXZ_GUI_ROW_UPDATE]; startRow = OXZ_GUI_ROW_PROCEED; } break; case OXZ_STATE_RESTARTING: [gui addLongText:DESC(@"oolite-oxzmanager-restart") startingAtRow:OXZ_GUI_ROW_FIRSTRUN align:GUI_ALIGN_LEFT]; return; // yes, return, not break: controls are pointless here case OXZ_STATE_MAIN: [gui addLongText:DESC(@"oolite-oxzmanager-intro") startingAtRow:OXZ_GUI_ROW_FIRSTRUN align:GUI_ALIGN_LEFT]; // fall through case OXZ_STATE_PICK_INSTALL: case OXZ_STATE_PICK_INSTALLED: case OXZ_STATE_PICK_REMOVE: if (_interfaceState != OXZ_STATE_MAIN) { [gui setText:[NSString stringWithFormat:DESC(@"oolite-oxzmanager-currentfilter-is-@-@"),OOExpand(@"[oolite_key_oxzmanager_setfilter]"),_currentFilter] forRow:OXZ_GUI_ROW_LISTFILTER align:GUI_ALIGN_LEFT]; [gui setColor:[OOColor greenColor] forRow:OXZ_GUI_ROW_LISTFILTER]; } [gui setText:DESC(@"oolite-oxzmanager-install") forRow:OXZ_GUI_ROW_INSTALL align:GUI_ALIGN_CENTER]; [gui setKey:@"_INSTALL" forRow:OXZ_GUI_ROW_INSTALL]; [gui setText:DESC(@"oolite-oxzmanager-installed") forRow:OXZ_GUI_ROW_INSTALLED align:GUI_ALIGN_CENTER]; [gui setKey:@"_INSTALLED" forRow:OXZ_GUI_ROW_INSTALLED]; [gui setText:DESC(@"oolite-oxzmanager-remove") forRow:OXZ_GUI_ROW_REMOVE align:GUI_ALIGN_CENTER]; [gui setKey:@"_REMOVE" forRow:OXZ_GUI_ROW_REMOVE]; [gui setText:DESC(@"oolite-oxzmanager-update-list") forRow:OXZ_GUI_ROW_UPDATE align:GUI_ALIGN_CENTER]; [gui setKey:@"_UPDATE" forRow:OXZ_GUI_ROW_UPDATE]; startRow = OXZ_GUI_ROW_INSTALL; break; case OXZ_STATE_UPDATING: case OXZ_STATE_INSTALLING: [gui setTitle:DESC(@"oolite-oxzmanager-title-downloading")]; [gui addLongText:[NSString stringWithFormat:DESC(@"oolite-oxzmanager-progress-@-is-@-of-@"),_currentDownloadName,[self humanSize:_downloadProgress],[self humanSize:_downloadExpected]] startingAtRow:OXZ_GUI_ROW_PROGRESS align:GUI_ALIGN_LEFT]; [gui addLongText:_progressStatus startingAtRow:OXZ_GUI_ROW_PROGRESS+2 align:GUI_ALIGN_LEFT]; [gui setText:DESC(@"oolite-oxzmanager-cancel") forRow:OXZ_GUI_ROW_CANCEL align:GUI_ALIGN_CENTER]; [gui setKey:@"_CANCEL" forRow:OXZ_GUI_ROW_CANCEL]; startRow = OXZ_GUI_ROW_UPDATE; break; case OXZ_STATE_DEPENDENCIES: [gui setTitle:DESC(@"oolite-oxzmanager-title-dependencies")]; [gui setText:DESC(@"oolite-oxzmanager-dependencies-decision") forRow:OXZ_GUI_ROW_PROGRESS align:GUI_ALIGN_LEFT]; [gui addLongText:_progressStatus startingAtRow:OXZ_GUI_ROW_PROGRESS+2 align:GUI_ALIGN_LEFT]; startRow = OXZ_GUI_ROW_INSTALLED; [gui setText:DESC(@"oolite-oxzmanager-dependencies-yes-all") forRow:OXZ_GUI_ROW_INSTALLED align:GUI_ALIGN_CENTER]; [gui setKey:@"_PROCEED_ALL" forRow:OXZ_GUI_ROW_INSTALLED]; [gui setText:DESC(@"oolite-oxzmanager-dependencies-yes") forRow:OXZ_GUI_ROW_PROCEED align:GUI_ALIGN_CENTER]; [gui setKey:@"_PROCEED" forRow:OXZ_GUI_ROW_PROCEED]; [gui setText:DESC(@"oolite-oxzmanager-dependencies-no") forRow:OXZ_GUI_ROW_CANCEL align:GUI_ALIGN_CENTER]; [gui setKey:@"_CANCEL" forRow:OXZ_GUI_ROW_CANCEL]; break; case OXZ_STATE_REMOVING: [gui addLongText:DESC(@"oolite-oxzmanager-removal-done") startingAtRow:OXZ_GUI_ROW_PROGRESS align:GUI_ALIGN_LEFT]; [gui setText:DESC(@"oolite-oxzmanager-acknowledge") forRow:OXZ_GUI_ROW_UPDATE align:GUI_ALIGN_CENTER]; [gui setKey:@"_ACK" forRow:OXZ_GUI_ROW_UPDATE]; startRow = OXZ_GUI_ROW_UPDATE; break; case OXZ_STATE_TASKDONE: if (_downloadStatus == OXZ_DOWNLOAD_COMPLETE) { [gui addLongText:[NSString stringWithFormat:DESC(@"oolite-oxzmanager-progress-done-%u-%u"),[_oxzList count],[[self managedOXZs] count]] startingAtRow:OXZ_GUI_ROW_PROGRESS align:GUI_ALIGN_LEFT]; } else { [gui addLongText:OOExpandKey(@"oolite-oxzmanager-progress-error") startingAtRow:OXZ_GUI_ROW_PROGRESS align:GUI_ALIGN_LEFT]; } [gui addLongText:_progressStatus startingAtRow:OXZ_GUI_ROW_PROGRESS+4 align:GUI_ALIGN_LEFT]; [gui setText:DESC(@"oolite-oxzmanager-acknowledge") forRow:OXZ_GUI_ROW_UPDATE align:GUI_ALIGN_CENTER]; [gui setKey:@"_ACK" forRow:OXZ_GUI_ROW_UPDATE]; startRow = OXZ_GUI_ROW_UPDATE; break; case OXZ_STATE_EXTRACT: { NSDictionary *manifest = [_filteredList oo_dictionaryAtIndex:_item]; NSString *title = [manifest oo_stringForKey:kOOManifestTitle]; NSString *version = [manifest oo_stringForKey:kOOManifestVersion]; NSString *identifier = [manifest oo_stringForKey:kOOManifestIdentifier]; [gui setTitle:DESC(@"oolite-oxzmanager-title-extract")]; [gui setText:[NSString stringWithFormat:DESC(@"oolite-oxzmanager-infopage-title-@-version-@"), title, version] forRow:0 align:GUI_ALIGN_LEFT]; [gui addLongText:DESC(@"oolite-oxzmanager-extract-info") startingAtRow:2 align:GUI_ALIGN_LEFT]; #ifdef NDEBUG [gui addLongText:DESC(@"oolite-oxzmanager-extract-releasebuild") startingAtRow:7 align:GUI_ALIGN_LEFT]; [gui setColor:[OOColor orangeColor] forRow:7]; [gui setColor:[OOColor orangeColor] forRow:8]; #endif NSString *path = [self extractionBasePathForIdentifier:identifier andVersion:version]; if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { [gui addLongText:[NSString stringWithFormat:DESC(@"oolite-oxzmanager-extract-@-already-exists"), path] startingAtRow:10 align:GUI_ALIGN_LEFT]; startRow = OXZ_GUI_ROW_CANCEL; [gui setText:DESC(@"oolite-oxzmanager-extract-unavailable") forRow:OXZ_GUI_ROW_PROCEED align:GUI_ALIGN_CENTER]; [gui setColor:[OOColor grayColor] forRow:OXZ_GUI_ROW_PROCEED]; } else { [gui addLongText:[NSString stringWithFormat:DESC(@"oolite-oxzmanager-extract-to-@"), path] startingAtRow:10 align:GUI_ALIGN_LEFT]; startRow = OXZ_GUI_ROW_PROCEED; [gui setText:DESC(@"oolite-oxzmanager-extract-proceed") forRow:OXZ_GUI_ROW_PROCEED align:GUI_ALIGN_CENTER]; [gui setKey:@"_PROCEED" forRow:OXZ_GUI_ROW_PROCEED]; } [gui setText:DESC(@"oolite-oxzmanager-extract-cancel") forRow:OXZ_GUI_ROW_CANCEL align:GUI_ALIGN_CENTER]; [gui setKey:@"_CANCEL" forRow:OXZ_GUI_ROW_CANCEL]; } break; case OXZ_STATE_EXTRACTDONE: [gui addLongText:_progressStatus startingAtRow:1 align:GUI_ALIGN_LEFT]; [gui setText:DESC(@"oolite-oxzmanager-acknowledge") forRow:OXZ_GUI_ROW_UPDATE align:GUI_ALIGN_CENTER]; [gui setKey:@"_ACK" forRow:OXZ_GUI_ROW_UPDATE]; startRow = OXZ_GUI_ROW_UPDATE; break; } if (_interfaceState == OXZ_STATE_PICK_INSTALL) { [gui setTitle:DESC(@"oolite-oxzmanager-title-install")]; [self setFilteredList:[self applyCurrentFilter:_oxzList]]; startRow = [self showInstallOptions]; } else if (_interfaceState == OXZ_STATE_PICK_INSTALLED) { [gui setTitle:DESC(@"oolite-oxzmanager-title-installed")]; [self setFilteredList:[self applyCurrentFilter:[self managedOXZs]]]; startRow = [self showInstallOptions]; } else if (_interfaceState == OXZ_STATE_PICK_REMOVE) { [gui setTitle:DESC(@"oolite-oxzmanager-title-remove")]; [self setFilteredList:[self applyCurrentFilter:[self managedOXZs]]]; startRow = [self showRemoveOptions]; } if (_changesMade) { [gui setText:DESC(@"oolite-oxzmanager-exit-restart") forRow:OXZ_GUI_ROW_EXIT align:GUI_ALIGN_CENTER]; } else { [gui setText:DESC(@"oolite-oxzmanager-exit") forRow:OXZ_GUI_ROW_EXIT align:GUI_ALIGN_CENTER]; } [gui setKey:@"_EXIT" forRow:OXZ_GUI_ROW_EXIT]; [gui setSelectableRange:NSMakeRange(startRow,2+(OXZ_GUI_ROW_EXIT-startRow))]; if (startRow < OXZ_GUI_ROW_INSTALL) { [gui setSelectedRow:OXZ_GUI_ROW_INSTALL]; } else if (_interfaceState == OXZ_STATE_NODATA) { [gui setSelectedRow:OXZ_GUI_ROW_UPDATE]; } else { [gui setSelectedRow:startRow]; } } - (BOOL) isRestarting { // for the restart if (EXPECT_NOT(_interfaceState == OXZ_STATE_RESTARTING)) { // Rebuilds OXP search [ResourceManager reset]; [UNIVERSE reinitAndShowDemo:YES]; _changesMade = NO; _interfaceState = OXZ_STATE_MAIN; _downloadStatus = OXZ_DOWNLOAD_NONE; // clear error state return YES; } else { return NO; } } - (void) processSelection { GuiDisplayGen *gui = [UNIVERSE gui]; OOGUIRow selection = [gui selectedRow]; if (selection == OXZ_GUI_ROW_EXIT) { [self cancelUpdate]; // doesn't hurt if no update in progress [_dependencyStack removeAllObjects]; // cleanup _downloadAllDependencies = NO; _downloadStatus = OXZ_DOWNLOAD_NONE; // clear error state if (_changesMade) { _interfaceState = OXZ_STATE_RESTARTING; } else { [PLAYER setGuiToIntroFirstGo:YES]; if (_oxzList != nil) { _interfaceState = OXZ_STATE_MAIN; } else { _interfaceState = OXZ_STATE_NODATA; } return; } } else if (selection == OXZ_GUI_ROW_UPDATE) // also == _CANCEL { if (_interfaceState == OXZ_STATE_REMOVING) { _interfaceState = OXZ_STATE_PICK_REMOVE; _downloadStatus = OXZ_DOWNLOAD_NONE; } else if (_interfaceState == OXZ_STATE_TASKDONE || _interfaceState == OXZ_STATE_DEPENDENCIES) { [_dependencyStack removeAllObjects]; _downloadAllDependencies = NO; _interfaceState = OXZ_STATE_PICK_INSTALL; _downloadStatus = OXZ_DOWNLOAD_NONE; } else if (_interfaceState == OXZ_STATE_EXTRACTDONE) { [_dependencyStack removeAllObjects]; _downloadAllDependencies = NO; _interfaceState = OXZ_STATE_PICK_INSTALLED; _downloadStatus = OXZ_DOWNLOAD_NONE; } else if (_interfaceState == OXZ_STATE_INSTALLING || _interfaceState == OXZ_STATE_UPDATING) { [self cancelUpdate]; // sets interface state and download status } else if (_interfaceState == OXZ_STATE_EXTRACT) { _interfaceState = OXZ_STATE_MAIN; } else { [self updateManifests]; } } else if (selection == OXZ_GUI_ROW_INSTALL) { _interfaceState = OXZ_STATE_PICK_INSTALL; } else if (selection == OXZ_GUI_ROW_INSTALLED) { if (_interfaceState == OXZ_STATE_DEPENDENCIES) // also == _PROCEED_ALL { _downloadAllDependencies = YES; [self installOXZ:_item]; } else { _interfaceState = OXZ_STATE_PICK_INSTALLED; } } else if (selection == OXZ_GUI_ROW_REMOVE) // also == _PROCEED { if (_interfaceState == OXZ_STATE_DEPENDENCIES) { [self installOXZ:_item]; } else if (_interfaceState == OXZ_STATE_NODATA) { _interfaceState = OXZ_STATE_MAIN; } else if (_interfaceState == OXZ_STATE_EXTRACT) { [self setProgressStatus:[self extractOXZ:_item]]; _interfaceState = OXZ_STATE_EXTRACTDONE; } else { _interfaceState = OXZ_STATE_PICK_REMOVE; } } else if (selection == OXZ_GUI_ROW_LISTPREV) { [self processOptionsPrev]; return; } else if (selection == OXZ_GUI_ROW_LISTNEXT) { [self processOptionsNext]; return; } else { NSUInteger item = _offset + selection - OXZ_GUI_ROW_LISTSTART; if (_interfaceState == OXZ_STATE_PICK_REMOVE) { [self removeOXZ:item]; } else if (_interfaceState == OXZ_STATE_PICK_INSTALL) { OOLog(kOOOXZDebugLog, @"Trying to install index %lu", (unsigned long)item); [self installOXZ:item]; } else if (_interfaceState == OXZ_STATE_PICK_INSTALLED) { OOLog(kOOOXZDebugLog, @"Trying to install index %lu", (unsigned long)item); [self installOXZ:item]; } } [self gui]; // update GUI } - (BOOL) isAcceptingTextInput { return (_interfaceState == OXZ_STATE_SETFILTER); } - (BOOL) isAcceptingGUIInput { return !_interfaceShowingOXZDetail; } - (void) processTextInput:(NSString *)input { if ([self validateFilter:input]) { if ([input length] > 0) { [self setFilter:input]; } // else keep previous filter _interfaceState = OXZ_STATE_PICK_INSTALL; [self gui]; } // else nothing } - (void) refreshTextInput:(NSString *)input { GuiDisplayGen *gui = [UNIVERSE gui]; [gui setText:[NSString stringWithFormat:DESC(@"oolite-oxzmanager-text-prompt-@"), input] forRow:OXZ_GUI_ROW_INPUT align:GUI_ALIGN_LEFT]; if ([self validateFilter:input]) { [gui setColor:[OOColor cyanColor] forRow:OXZ_GUI_ROW_INPUT]; } else { [gui setColor:[OOColor orangeColor] forRow:OXZ_GUI_ROW_INPUT]; } } - (void) processFilterKey { if (_interfaceShowingOXZDetail) { _interfaceShowingOXZDetail = NO; } if (_interfaceState == OXZ_STATE_PICK_INSTALL || _interfaceState == OXZ_STATE_PICK_INSTALLED || _interfaceState == OXZ_STATE_PICK_REMOVE || _interfaceState == OXZ_STATE_MAIN) { _interfaceState = OXZ_STATE_SETFILTER; [[UNIVERSE gameView] resetTypedString]; [self gui]; } // else this key does nothing } - (void) processShowInfoKey { if (_interfaceState == OXZ_STATE_PICK_INSTALL || _interfaceState == OXZ_STATE_PICK_INSTALLED || _interfaceState == OXZ_STATE_PICK_REMOVE) { GuiDisplayGen *gui = [UNIVERSE gui]; if (_interfaceShowingOXZDetail) { _interfaceShowingOXZDetail = NO; [self gui]; // restore screen // reset list selection position [gui setSelectedRow:(_item - _offset + OXZ_GUI_ROW_LISTSTART)]; // and do the GUI again with the correct positions [self showOptionsUpdate]; // restore screen } else { OOGUIRow selection = [gui selectedRow]; if (selection < OXZ_GUI_ROW_LISTSTART || selection >= OXZ_GUI_ROW_LISTSTART + OXZ_GUI_NUM_LISTROWS) { // not on an OXZ return; } _item = _offset + selection - OXZ_GUI_ROW_LISTSTART; NSDictionary *manifest = [_filteredList oo_dictionaryAtIndex:_item]; _interfaceShowingOXZDetail = YES; [gui clearAndKeepBackground:YES]; [gui setTitle:DESC(@"oolite-oxzmanager-title-infopage")]; // title, version [gui setText:[NSString stringWithFormat:DESC(@"oolite-oxzmanager-infopage-title-@-version-@"), [manifest oo_stringForKey:kOOManifestTitle], [manifest oo_stringForKey:kOOManifestVersion]] forRow:0 align:GUI_ALIGN_LEFT]; // author [gui setText:[NSString stringWithFormat:DESC(@"oolite-oxzmanager-infopage-author-@"), [manifest oo_stringForKey:kOOManifestAuthor]] forRow:1 align:GUI_ALIGN_LEFT]; // license [gui addLongText:[NSString stringWithFormat:DESC(@"oolite-oxzmanager-infopage-license-@"), [manifest oo_stringForKey:kOOManifestLicense]] startingAtRow:2 align:GUI_ALIGN_LEFT]; // tags [gui addLongText:[NSString stringWithFormat:DESC(@"oolite-oxzmanager-infopage-tags-@"),[[manifest oo_arrayForKey:kOOManifestTags] componentsJoinedByString: @", "]] startingAtRow:4 align:GUI_ALIGN_LEFT]; // description [gui addLongText:[NSString stringWithFormat:DESC(@"oolite-oxzmanager-infopage-description-@"),[manifest oo_stringForKey:kOOManifestDescription]] startingAtRow:7 align:GUI_ALIGN_LEFT]; // infoURL [gui setText:[NSString stringWithFormat:DESC(@"oolite-oxzmanager-infopage-infourl-@"), [manifest oo_stringForKey:kOOManifestInformationURL]] forRow:25 align:GUI_ALIGN_LEFT]; // instructions [gui setText:OOExpand(DESC(@"oolite-oxzmanager-infopage-return")) forRow:27 align:GUI_ALIGN_CENTER]; [gui setColor:[OOColor greenColor] forRow:27]; } } } - (void) processExtractKey { // TODO: Extraction functionality - converts an installed OXZ to // an OXP in the main AddOns folder if it's safe to do so. if (!_interfaceShowingOXZDetail && (_interfaceState == OXZ_STATE_PICK_INSTALLED || _interfaceState == OXZ_STATE_PICK_REMOVE)) { GuiDisplayGen *gui = [UNIVERSE gui]; OOGUIRow selection = [gui selectedRow]; if (selection < OXZ_GUI_ROW_LISTSTART || selection >= OXZ_GUI_ROW_LISTSTART + OXZ_GUI_NUM_LISTROWS) { // not on an OXZ return; } _item = _offset + selection - OXZ_GUI_ROW_LISTSTART; _interfaceState = OXZ_STATE_EXTRACT; [self gui]; } } - (BOOL) installOXZ:(NSUInteger)item { NSArray *picklist = _filteredList; if ([picklist count] <= item) { return NO; } NSDictionary *manifest = [picklist objectAtIndex:item]; _item = item; if ([self installableState:manifest] >= OXZ_UNINSTALLABLE_ALREADY) { OOLog(kOOOXZDebugLog,@"Cannot install %@",manifest); // can't be installed on this version of Oolite, or already is installed return NO; } NSString *url = [manifest objectForKey:kOOManifestDownloadURL]; if (url == nil) { OOLog(kOOOXZErrorLog,@"Manifest does not have a download URL - cannot install"); return NO; } NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]]; if (_downloadStatus != OXZ_DOWNLOAD_NONE) { return NO; } _downloadStatus = OXZ_DOWNLOAD_STARTED; _interfaceState = OXZ_STATE_INSTALLING; [self setProgressStatus:@""]; return [self beginDownload:request]; } - (NSArray *) installOptions { NSUInteger start = _offset; if (start >= [_filteredList count]) { start = 0; _offset = 0; } NSUInteger end = start + OXZ_GUI_NUM_LISTROWS; if (end > [_filteredList count]) { end = [_filteredList count]; } return [_filteredList subarrayWithRange:NSMakeRange(start,end-start)]; } - (OOGUIRow) showInstallOptions { // shows the current installation options page OOGUIRow startRow = OXZ_GUI_ROW_LISTPREV; NSArray *options = [self installOptions]; NSUInteger optCount = [_filteredList count]; GuiDisplayGen *gui = [UNIVERSE gui]; OOGUITabSettings tab_stops; tab_stops[0] = 0; tab_stops[1] = 100; tab_stops[2] = 320; tab_stops[3] = 400; [gui setTabStops:tab_stops]; [gui setArray:[NSArray arrayWithObjects:DESC(@"oolite-oxzmanager-heading-category"), DESC(@"oolite-oxzmanager-heading-title"), DESC(@"oolite-oxzmanager-heading-installed"), DESC(@"oolite-oxzmanager-heading-downloadable"), nil] forRow:OXZ_GUI_ROW_LISTHEAD]; if (_offset > 0) { [gui setColor:[OOColor greenColor] forRow:OXZ_GUI_ROW_LISTPREV]; [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-back"), @"",@"",@" <-- ", nil] forRow:OXZ_GUI_ROW_LISTPREV]; [gui setKey:@"_BACK" forRow:OXZ_GUI_ROW_LISTPREV]; } else { if ([gui selectedRow] == OXZ_GUI_ROW_LISTPREV) { [gui setSelectedRow:OXZ_GUI_ROW_LISTSTART]; } [gui setText:@"" forRow:OXZ_GUI_ROW_LISTPREV align:GUI_ALIGN_LEFT]; [gui setKey:GUI_KEY_SKIP forRow:OXZ_GUI_ROW_LISTPREV]; } if (_offset + 10 < optCount) { [gui setColor:[OOColor greenColor] forRow:OXZ_GUI_ROW_LISTNEXT]; [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-more"), @"",@"",@" --> ", nil] forRow:OXZ_GUI_ROW_LISTNEXT]; [gui setKey:@"_NEXT" forRow:OXZ_GUI_ROW_LISTNEXT]; } else { if ([gui selectedRow] == OXZ_GUI_ROW_LISTNEXT) { [gui setSelectedRow:OXZ_GUI_ROW_LISTSTART]; } [gui setText:@"" forRow:OXZ_GUI_ROW_LISTNEXT align:GUI_ALIGN_LEFT]; [gui setKey:GUI_KEY_SKIP forRow:OXZ_GUI_ROW_LISTNEXT]; } // clear any previous longtext for (NSUInteger i = OXZ_GUI_ROW_LISTSTATUS; i < OXZ_GUI_ROW_INSTALL-1; i++) { [gui setText:@"" forRow:i align:GUI_ALIGN_LEFT]; [gui setKey:GUI_KEY_SKIP forRow:i]; } // and any previous listed entries for (NSUInteger i = OXZ_GUI_ROW_LISTSTART; i < OXZ_GUI_ROW_LISTNEXT; i++) { [gui setText:@"" forRow:i align:GUI_ALIGN_LEFT]; [gui setKey:GUI_KEY_SKIP forRow:i]; } OOGUIRow row = OXZ_GUI_ROW_LISTSTART; NSDictionary *manifest = nil; BOOL oxzLineSelected = NO; foreach (manifest, options) { NSDictionary *installed = [ResourceManager manifestForIdentifier:[manifest oo_stringForKey:kOOManifestIdentifier]]; NSString *localPath = [[[self installPath] stringByAppendingPathComponent:[manifest oo_stringForKey:kOOManifestIdentifier]] stringByAppendingPathExtension:@"oxz"]; if (installed == nil) { // check that there's not one just been downloaded installed = OODictionaryFromFile([localPath stringByAppendingPathComponent:@"manifest.plist"]); } else { // check for a more recent download if ([[NSFileManager defaultManager] fileExistsAtPath:localPath]) { installed = OODictionaryFromFile([localPath stringByAppendingPathComponent:@"manifest.plist"]); } else { // check if this was a managed OXZ which has been deleted if ([[installed oo_stringForKey:kOOManifestFilePath] hasPrefix:[self installPath]]) { installed = nil; } } } NSString *installedVersion = DESC(@"oolite-oxzmanager-version-none"); if (installed != nil) { installedVersion = [installed oo_stringForKey:kOOManifestVersion defaultValue:DESC(@"oolite-oxzmanager-version-none")]; } /* If the filter is in use, the available_version key will * contain the version which can be downloaded. */ [gui setArray:[NSArray arrayWithObjects: [manifest oo_stringForKey:kOOManifestCategory defaultValue:DESC(@"oolite-oxzmanager-missing-field")], [manifest oo_stringForKey:kOOManifestTitle defaultValue:DESC(@"oolite-oxzmanager-missing-field")], installedVersion, [manifest oo_stringForKey:kOOManifestAvailableVersion defaultValue:[manifest oo_stringForKey:kOOManifestVersion defaultValue:DESC(@"oolite-oxzmanager-version-none")]], nil] forRow:row]; [gui setKey:[manifest oo_stringForKey:kOOManifestIdentifier] forRow:row]; /* yellow for installable, orange for dependency issues, grey and unselectable for version issues, white and unselectable for already installed (manually or otherwise) at the current version, red and unselectable for already installed manually at a different version. */ [gui setColor:[self colorForManifest:manifest] forRow:row]; if (row == [gui selectedRow]) { oxzLineSelected = YES; [gui setText:[self installStatusForManifest:manifest] forRow:OXZ_GUI_ROW_LISTSTATUS]; [gui setColor:[OOColor greenColor] forRow:OXZ_GUI_ROW_LISTSTATUS]; [gui addLongText:[[[manifest oo_stringForKey:kOOManifestDescription] componentsSeparatedByString:@"\n"] oo_stringAtIndex:0] startingAtRow:OXZ_GUI_ROW_LISTDESC align:GUI_ALIGN_LEFT]; NSString *infoUrl = [manifest oo_stringForKey:kOOManifestInformationURL]; if (infoUrl != nil) { [gui setArray:[NSArray arrayWithObjects:DESC(@"oolite-oxzmanager-infoline-url"),infoUrl,nil] forRow:OXZ_GUI_ROW_LISTINFO1]; } NSUInteger size = [manifest oo_unsignedIntForKey:kOOManifestFileSize defaultValue:0]; NSString *updatedDesc = nil; NSUInteger timestamp = [manifest oo_unsignedIntegerForKey:kOOManifestUploadDate defaultValue:0]; if (timestamp > 0) { // list of installable OXZs NSDate *updated = [NSDate dateWithTimeIntervalSince1970:timestamp]; //keep only the first part of the date string description, which should be in YYYY-MM-DD format updatedDesc = [[[updated description] componentsSeparatedByString:@" "] oo_stringAtIndex:0]; [gui setArray:[NSArray arrayWithObjects:DESC(@"oolite-oxzmanager-infoline-size"),[self humanSize:size],DESC(@"oolite-oxzmanager-infoline-date"),updatedDesc,nil] forRow:OXZ_GUI_ROW_LISTINFO2]; } else if (size > 0) { // list of installed/removable OXZs [gui setArray:[NSArray arrayWithObjects:DESC(@"oolite-oxzmanager-infoline-size"),[self humanSize:size],nil] forRow:OXZ_GUI_ROW_LISTINFO2]; } } row++; } if (!oxzLineSelected) { if (_interfaceState == OXZ_STATE_PICK_INSTALLED) { // installeD [gui addLongText:OOExpand(DESC(@"oolite-oxzmanager-installed-nonepicked")) startingAtRow:OXZ_GUI_ROW_LISTDESC align:GUI_ALIGN_LEFT]; } else { // installeR [gui addLongText:OOExpand(DESC(@"oolite-oxzmanager-installer-nonepicked")) startingAtRow:OXZ_GUI_ROW_LISTDESC align:GUI_ALIGN_LEFT]; } } return startRow; } - (BOOL) removeOXZ:(NSUInteger)item { NSArray *remList = _filteredList; if ([remList count] <= item) { OOLog(kOOOXZDebugLog, @"Unable to remove item %lu as only %lu in list", (unsigned long)item, (unsigned long)[remList count]); return NO; } NSString *filename = [[remList objectAtIndex:item] oo_stringForKey:kOOManifestFilePath]; if (filename == nil) { OOLog(kOOOXZDebugLog, @"Unable to remove item %lu as filename not found", (unsigned long)item); return NO; } if (![[NSFileManager defaultManager] oo_removeItemAtPath:filename]) { OOLog(kOOOXZErrorLog, @"Unable to remove file %@", filename); return NO; } _changesMade = YES; DESTROY(_managedList); // will need updating _interfaceState = OXZ_STATE_REMOVING; [self gui]; return YES; } - (NSArray *) removeOptions { NSArray *remList = _filteredList; if ([remList count] == 0) { return nil; } NSUInteger start = _offset; if (start >= [remList count]) { start = 0; _offset = 0; } NSUInteger end = start + OXZ_GUI_NUM_LISTROWS; if (end > [remList count]) { end = [remList count]; } return [remList subarrayWithRange:NSMakeRange(start,end-start)]; } - (OOGUIRow) showRemoveOptions { // shows the current installation options page OOGUIRow startRow = OXZ_GUI_ROW_LISTPREV; NSArray *options = [self removeOptions]; GuiDisplayGen *gui = [UNIVERSE gui]; if (options == nil) { [gui addLongText:DESC(@"oolite-oxzmanager-nothing-removable") startingAtRow:OXZ_GUI_ROW_PROGRESS align:GUI_ALIGN_LEFT]; return startRow; } OOGUITabSettings tab_stops; tab_stops[0] = 0; tab_stops[1] = 100; tab_stops[2] = 400; [gui setTabStops:tab_stops]; [gui setArray:[NSArray arrayWithObjects:DESC(@"oolite-oxzmanager-heading-category"), DESC(@"oolite-oxzmanager-heading-title"), DESC(@"oolite-oxzmanager-heading-version"), nil] forRow:OXZ_GUI_ROW_LISTHEAD]; if (_offset > 0) { [gui setColor:[OOColor greenColor] forRow:OXZ_GUI_ROW_LISTPREV]; [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-back"), @"",@" <-- ", nil] forRow:OXZ_GUI_ROW_LISTPREV]; [gui setKey:@"_BACK" forRow:OXZ_GUI_ROW_LISTPREV]; } else { if ([gui selectedRow] == OXZ_GUI_ROW_LISTPREV) { [gui setSelectedRow:OXZ_GUI_ROW_LISTSTART]; } [gui setText:@"" forRow:OXZ_GUI_ROW_LISTPREV align:GUI_ALIGN_LEFT]; [gui setKey:GUI_KEY_SKIP forRow:OXZ_GUI_ROW_LISTPREV]; } if (_offset + OXZ_GUI_NUM_LISTROWS < [[self managedOXZs] count]) { [gui setColor:[OOColor greenColor] forRow:OXZ_GUI_ROW_LISTNEXT]; [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-more"), @"",@" --> ", nil] forRow:OXZ_GUI_ROW_LISTNEXT]; [gui setKey:@"_NEXT" forRow:OXZ_GUI_ROW_LISTNEXT]; } else { if ([gui selectedRow] == OXZ_GUI_ROW_LISTNEXT) { [gui setSelectedRow:OXZ_GUI_ROW_LISTSTART]; } [gui setText:@"" forRow:OXZ_GUI_ROW_LISTNEXT align:GUI_ALIGN_LEFT]; [gui setKey:GUI_KEY_SKIP forRow:OXZ_GUI_ROW_LISTNEXT]; } // clear any previous longtext for (NSUInteger i = OXZ_GUI_ROW_LISTDESC; i < OXZ_GUI_ROW_INSTALL-1; i++) { [gui setText:@"" forRow:i align:GUI_ALIGN_LEFT]; [gui setKey:GUI_KEY_SKIP forRow:i]; } // and any previous listed entries for (NSUInteger i = OXZ_GUI_ROW_LISTSTART; i < OXZ_GUI_ROW_LISTNEXT; i++) { [gui setText:@"" forRow:i align:GUI_ALIGN_LEFT]; [gui setKey:GUI_KEY_SKIP forRow:i]; } OOGUIRow row = OXZ_GUI_ROW_LISTSTART; NSDictionary *manifest = nil; BOOL oxzSelected = NO; foreach (manifest, options) { [gui setArray:[NSArray arrayWithObjects: [manifest oo_stringForKey:kOOManifestCategory defaultValue:DESC(@"oolite-oxzmanager-missing-field")], [manifest oo_stringForKey:kOOManifestTitle defaultValue:DESC(@"oolite-oxzmanager-missing-field")], [manifest oo_stringForKey:kOOManifestVersion defaultValue:DESC(@"oolite-oxzmanager-missing-field")], nil] forRow:row]; NSString *identifier = [manifest oo_stringForKey:kOOManifestIdentifier]; [gui setKey:identifier forRow:row]; [gui setColor:[self colorForManifest:manifest] forRow:row]; if (row == [gui selectedRow]) { [gui setText:[self installStatusForManifest:manifest] forRow:OXZ_GUI_ROW_LISTSTATUS]; [gui setColor:[OOColor greenColor] forRow:OXZ_GUI_ROW_LISTSTATUS]; [gui addLongText:[[[manifest oo_stringForKey:kOOManifestDescription] componentsSeparatedByString:@"\n"] oo_stringAtIndex:0] startingAtRow:OXZ_GUI_ROW_LISTDESC align:GUI_ALIGN_LEFT]; oxzSelected = YES; } row++; } if (!oxzSelected) { [gui addLongText:DESC(@"oolite-oxzmanager-remover-nonepicked") startingAtRow:OXZ_GUI_ROW_LISTDESC align:GUI_ALIGN_LEFT]; } return startRow; } - (void) showOptionsUpdate { if (_interfaceState == OXZ_STATE_PICK_INSTALL) { [self setFilteredList:[self applyCurrentFilter:_oxzList]]; [self showInstallOptions]; } else if (_interfaceState == OXZ_STATE_PICK_INSTALLED) { [self setFilteredList:[self applyCurrentFilter:[self managedOXZs]]]; [self showInstallOptions]; } else if (_interfaceState == OXZ_STATE_PICK_REMOVE) { [self setFilteredList:[self applyCurrentFilter:[self managedOXZs]]]; [self showRemoveOptions]; } // else nothing necessary } - (void) showOptionsPrev { GuiDisplayGen *gui = [UNIVERSE gui]; if (_interfaceState == OXZ_STATE_PICK_INSTALL || _interfaceState == OXZ_STATE_PICK_REMOVE || _interfaceState == OXZ_STATE_PICK_INSTALLED) { if ([gui selectedRow] == OXZ_GUI_ROW_LISTPREV) { [self processSelection]; } } } - (void) processOptionsPrev { if (_offset < OXZ_GUI_NUM_LISTROWS) { _offset = 0; } else { _offset -= OXZ_GUI_NUM_LISTROWS; } [self showOptionsUpdate]; } - (void) processOptionsNext { if (_offset + OXZ_GUI_NUM_LISTROWS < [_filteredList count]) { _offset += OXZ_GUI_NUM_LISTROWS; } [self showOptionsUpdate]; return; } - (void) showOptionsNext { GuiDisplayGen *gui = [UNIVERSE gui]; if (_interfaceState == OXZ_STATE_PICK_INSTALL || _interfaceState == OXZ_STATE_PICK_REMOVE || _interfaceState == OXZ_STATE_PICK_INSTALLED) { if ([gui selectedRow] == OXZ_GUI_ROW_LISTNEXT) { [self processSelection]; } } } - (NSString *) extractOXZ:(NSUInteger)item { NSFileManager *fmgr = [NSFileManager defaultManager]; NSMutableString *extractionLog = [[NSMutableString alloc] init]; NSDictionary *manifest = [_filteredList oo_dictionaryAtIndex:item]; NSString *version = [manifest oo_stringForKey:kOOManifestVersion]; NSString *identifier = [manifest oo_stringForKey:kOOManifestIdentifier]; NSString *path = [self extractionBasePathForIdentifier:identifier andVersion:version]; // OXZ errors should really never happen unless someone is messing // directly with the managed folder while Oolite is running, but // it's possible. NSString *oxzfile = [manifest oo_stringForKey:kOOManifestFilePath]; if (![fmgr fileExistsAtPath:oxzfile]) { OOLog(kOOOXZErrorLog,@"OXZ %@ could not be found",oxzfile); [extractionLog appendString:DESC(@"oolite-oxzmanager-extract-log-no-original")]; return [extractionLog autorelease]; } const char* zipname = [oxzfile UTF8String]; unzFile uf = NULL; uf = unzOpen64(zipname); if (uf == NULL) { OOLog(kOOOXZErrorLog,@"Could not open .oxz at %@ as zip file",path); [extractionLog appendString:DESC(@"oolite-oxzmanager-extract-log-bad-original")]; return [extractionLog autorelease]; } if ([fmgr fileExistsAtPath:path]) { OOLog(kOOOXZErrorLog,@"Path %@ already exists",path); [extractionLog appendString:DESC(@"oolite-oxzmanager-extract-log-main-exists")]; unzClose(uf); return [extractionLog autorelease]; } if (![fmgr oo_createDirectoryAtPath:path attributes:nil]) { OOLog(kOOOXZErrorLog,@"Path %@ could not be created",path); [extractionLog appendString:DESC(@"oolite-oxzmanager-extract-log-main-unmakeable")]; unzClose(uf); return [extractionLog autorelease]; } [extractionLog appendString:DESC(@"oolite-oxzmanager-extract-log-main-created")]; NSUInteger counter = 0; char rawComponentName[512]; BOOL error = NO; unz_file_info64 file_info = {0}; if (unzGoToFirstFile(uf) == UNZ_OK) { do { unzGetCurrentFileInfo64(uf, &file_info, rawComponentName, 512, NULL, 0, NULL, 0); NSString *componentName = [NSString stringWithUTF8String:rawComponentName]; if ([componentName hasSuffix:@"/"]) { // folder if (![fmgr oo_createDirectoryAtPath:[path stringByAppendingPathComponent:componentName] attributes:nil]) { OOLog(kOOOXZErrorLog,@"Subpath %@ could not be created",componentName); [extractionLog appendString:DESC(@"oolite-oxzmanager-extract-log-sub-failed")]; error = YES; break; } else { OOLog(kOOOXZDebugLog,@"Subpath %@ created OK",componentName); } } else { // file // usually folder must now exist, but just in case... NSString *folder = [[path stringByAppendingPathComponent:componentName] stringByDeletingLastPathComponent]; if ([folder length] > 0 && ![fmgr fileExistsAtPath:folder] && ![fmgr oo_createDirectoryAtPath:folder attributes:nil]) { OOLog(kOOOXZErrorLog,@"Subpath %@ could not be created",folder); [extractionLog appendString:DESC(@"oolite-oxzmanager-extract-log-sub-failed")]; error = YES; break; } // This is less efficient in memory use than just // streaming out of the ZIP file onto disk // but it makes error handling easier NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSData *tmp = [NSData oo_dataWithOXZFile:[oxzfile stringByAppendingPathComponent:componentName]]; if (tmp == nil) { OOLog(kOOOXZErrorLog,@"Sub file %@ could not be extracted from the OXZ",componentName); [extractionLog appendString:DESC(@"oolite-oxzmanager-extract-log-sub-failed")]; error = YES; [pool release]; break; } else { if (![tmp writeToFile:[path stringByAppendingPathComponent:componentName] atomically:YES]) { OOLog(kOOOXZErrorLog,@"Sub file %@ could not be created",componentName); [extractionLog appendString:DESC(@"oolite-oxzmanager-extract-log-sub-failed")]; error = YES; [pool release]; break; } else { ++counter; } } [pool release]; } } while (unzGoToNextFile(uf) == UNZ_OK); } unzClose(uf); if (!error) { [extractionLog appendFormat:DESC(@"oolite-oxzmanager-extract-log-num-u-extracted"),counter]; [extractionLog appendFormat:DESC(@"oolite-oxzmanager-extract-log-extracted-to-@"),path]; } return [extractionLog autorelease]; } - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { _downloadStatus = OXZ_DOWNLOAD_RECEIVING; OOLog(kOOOXZDebugLog,@"Download receiving"); _downloadExpected = [response expectedContentLength]; _downloadProgress = 0; DESTROY(_fileWriter); [[NSFileManager defaultManager] createFileAtPath:[self downloadPath] contents:nil attributes:nil]; _fileWriter = [[NSFileHandle fileHandleForWritingAtPath:[self downloadPath]] retain]; if (_fileWriter == nil) { // file system is full or read-only or something OOLog(kOOOXZErrorLog,@"Unable to create download file"); [self cancelUpdate]; } } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { OOLog(kOOOXZDebugLog,@"Downloaded %lu bytes",[data length]); [_fileWriter seekToEndOfFile]; [_fileWriter writeData:data]; _downloadProgress += [data length]; [self gui]; // update GUI #if OOLITE_WINDOWS /* Irritating fix to issue https://github.com/OoliteProject/oolite/issues/95 * * The problem is that on MINGW, GNUStep makes all socket streams * blocking, which causes problems with the run loop. Calling this * method of the run loop forces it to execute all already * scheduled items with a time in the past, before any more items * are placed on it, which means that the main game update gets a * chance to run. * * This stops the interface freezing - and Oolite appearing to * have stopped responding to the OS - when downloading large * (>20Mb) OXZ files. * * CIM 6 July 2014 */ [[NSRunLoop currentRunLoop] limitDateForMode:NSDefaultRunLoopMode]; #endif } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { _downloadStatus = OXZ_DOWNLOAD_COMPLETE; OOLog(kOOOXZDebugLog,@"Download complete"); [_fileWriter synchronizeFile]; [_fileWriter closeFile]; DESTROY(_fileWriter); DESTROY(_currentDownload); if (_interfaceState == OXZ_STATE_UPDATING) { if (![self processDownloadedManifests]) { _downloadStatus = OXZ_DOWNLOAD_ERROR; } } else if (_interfaceState == OXZ_STATE_INSTALLING) { if (![self processDownloadedOXZ]) { _downloadStatus = OXZ_DOWNLOAD_ERROR; } } else { OOLog(kOOOXZErrorLog,@"Error: download completed in unexpected state %d. This is an internal error - please report it.",_interfaceState); _downloadStatus = OXZ_DOWNLOAD_ERROR; } } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { _downloadStatus = OXZ_DOWNLOAD_ERROR; OOLog(kOOOXZErrorLog,@"Error downloading file: %@",[error description]); [_fileWriter closeFile]; DESTROY(_fileWriter); DESTROY(_currentDownload); } @end /* Sort by category, then title, then version - and that should be unique */ NSComparisonResult oxzSort(id m1, id m2, void *context) { NSComparisonResult result = [[m1 oo_stringForKey:kOOManifestCategory defaultValue:@"zz"] localizedCompare:[m2 oo_stringForKey:kOOManifestCategory defaultValue:@"zz"]]; if (result == NSOrderedSame) { result = [[m1 oo_stringForKey:kOOManifestTitle defaultValue:@"zz"] localizedCompare:[m2 oo_stringForKey:kOOManifestTitle defaultValue:@"zz"]]; if (result == NSOrderedSame) { result = [[m2 oo_stringForKey:kOOManifestVersion defaultValue:@"0"] localizedCompare:[m1 oo_stringForKey:kOOManifestVersion defaultValue:@"0"]]; } } return result; } oolite-1.82/src/Core/OOOpenAL.h000066400000000000000000000021301256642440500161470ustar00rootroot00000000000000/* OOOpenAL.h Platform-independent access to OpenAL headers and AL utilities Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #if OOLITE_MAC_OS_X #import #import #else #include #include #endif // alGetError should always be called before openAL calls to reset the // error stack #define OOAL(cmd) do { alGetError(); cmd; } while (0) oolite-1.82/src/Core/OOOpenALController.h000066400000000000000000000024421256642440500202210ustar00rootroot00000000000000/* OOOpenALController.h Singleton controller for Open AL interfaces Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOOpenAL.h" static NSString * const kOOLogSoundInitError = @"sound.initialization.error"; static NSString * const kOOLogSoundLoadingSuccess = @"sound.load.success"; static NSString * const kOOLogSoundLoadingError = @"sound.load.error"; @interface OOOpenALController : NSObject { @private ALCdevice *device; ALCcontext *context; } + (OOOpenALController *) sharedController; - (void) setMasterVolume:(ALfloat) fraction; - (ALfloat) masterVolume; - (void) shutdown; @end oolite-1.82/src/Core/OOOpenALController.m000066400000000000000000000041611256642440500202260ustar00rootroot00000000000000/* OOOpenALController.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOOpenALController.h" #import "OOLogging.h" #import "OOALSoundMixer.h" static id sSingleton = nil; @implementation OOOpenALController + (OOOpenALController *) sharedController { if (sSingleton == nil) { sSingleton = [[self alloc] init]; } return sSingleton; } - (id) init { self = [super init]; if (self != nil) { ALuint error; device = alcOpenDevice(NULL); // default device if (!device) { OOLog(kOOLogSoundInitError,@"Failed to open default sound device"); [self release]; return nil; } context = alcCreateContext(device,NULL); // default context if (!alcMakeContextCurrent(context)) { OOLog(kOOLogSoundInitError,@"Failed to create default sound context"); [self release]; return nil; } if ((error = alGetError()) != AL_NO_ERROR) { OOLog(kOOLogSoundInitError,@"Error %d creating sound context",error); } OOAL(alDistanceModel(AL_NONE)); } return self; } - (void) setMasterVolume:(ALfloat) fraction { OOAL(alListenerf(AL_GAIN,fraction)); } - (ALfloat) masterVolume { ALfloat fraction = 0.0; OOAL(alGetListenerf(AL_GAIN,&fraction)); return fraction; } // only to be called at app shutdown // is there a better way to handle this? - (void) shutdown { [[OOSoundMixer sharedMixer] shutdown]; OOAL(alcMakeContextCurrent(NULL)); OOAL(alcDestroyContext(context)); OOAL(alcCloseDevice(device)); } @end oolite-1.82/src/Core/OOOpenGL.h000066400000000000000000000205261256642440500161660ustar00rootroot00000000000000/* OOOpenGL.h Do whatever is appropriate to get gl.h, glu.h and glext.h included. Also declares OpenGL-related utility functions. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #import "OOOpenGLOnly.h" typedef enum { // NOTE: numerical values are available to scripts. SHADERS_NOT_SUPPORTED = 0, SHADERS_OFF = 1, SHADERS_SIMPLE = 2, SHADERS_FULL = 3 } OOShaderSetting; #define NULL_SHADER ((GLhandleARB)0) // Whether to use state verifier. Will be changed to equal OO_CHECK_GL_HEAVY in future. #define OO_GL_STATE_VERIFICATION (!defined(NDEBUG)) /* OOSetOpenGLState(stateType) Set OpenGL state to one of two standard states. In Deployment builds, this only modifies the state that differs between the two standard states, and only when the last set state is not the same as the specified target. In Debug builds, it tests many different OpenGL state variables, complains if they aren't in the expected state, and corrects them. The correct procedure for using these functions is: 1. Call OOSetOpenGLState() 2. Make any state changes needed 3. Draw 4. Reverse state changes from stage 2 5. Call OOVerifyOpenGLState() 6. Call OOCheckOpenGLErrors(). The states are: OPENGL_STATE_OPAQUE - standard state for drawing solid objects like OOMesh and planets. This can be considered the Oolite baseline state. Differs from standard GL state as follows: GL_LIGHTING is on GL_LIGHT1 is on GL_TEXTURE_2D is on Blend mode is GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA (but GL_BLEND is off) GL_FOG may be either on or off (as it's set in Universe's draw loop) GL_VERTEX_ARRAY is on GL_NORMAL_ARRAY is on GL_DEPTH_TEST is on GL_CULL_FACE is on OPENGL_STATE_TRANSLUCENT_PASS is used during the translucent phase of the universe draw loop. This usage needs to be cleaned up. Differs from OPENGL_STATE_OPAQUE as follows: GL_LIGHTING is off GL_TEXTURE_2D is off GL_VERTEX_ARRAY is off GL_NORMAL_ARRAY is off GL_DEPTH_WRITEMASK is off OPENGL_STATE_ADDITIVE_BLENDING is used for glowy special effects. Differs from OPENGL_STATE_OPAQUE as follows: GL_LIGHTING is off GL_TEXTURE_2D is off GL_BLEND is on Blend mode is GL_SRC_ALPHA, GL_ONE GL_NORMAL_ARRAY is off GL_DEPTH_WRITEMASK is off GL_CULL_FACE is off OPENGL_STATE_OVERLAY is used for UI elements, which are unlit and don't use z-buffering. Differs from OPENGL_STATE_OPAQUE as follows: GL_LIGHTING is off GL_TEXTURE_2D is off GL_BLEND is on GL_FOG is off GL_NORMAL_ARRAY is off GL_DEPTH_TEST is off GL_DEPTH_WRITEMASK is off GL_CULL_FACE is off */ typedef enum { OPENGL_STATE_OPAQUE, OPENGL_STATE_TRANSLUCENT_PASS, OPENGL_STATE_ADDITIVE_BLENDING, OPENGL_STATE_OVERLAY, OPENGL_STATE_INTERNAL_USE_ONLY } OOOpenGLStateID; #if OO_GL_STATE_VERIFICATION void OOSetOpenGLState_(OOOpenGLStateID state, const char *function, unsigned line); void OOVerifyOpenGLState_(const char *function, unsigned line); #define OOSetOpenGLState(STATE) OOSetOpenGLState_(STATE, __FUNCTION__, __LINE__) #define OOVerifyOpenGLState() OOVerifyOpenGLState_(__FUNCTION__, __LINE__) #else void OOSetOpenGLState(OOOpenGLStateID state); #define OOVerifyOpenGLState() do {} while (0) #endif // Inform the SetState/VerifyState mechanism that the OpenGL context has been reset to its initial state. void OOResetGLStateVerifier(void); /* OOCheckOpenGLErrors() Check for and log OpenGL errors, and returns YES if an error occurred. NOTE: this is controlled by the log message class rendering.opengl.error. If logging is disabled, no error checking will occur. This is done because glGetError() is quite expensive, requiring a full OpenGL state sync. */ BOOL OOCheckOpenGLErrors(NSString *format, ...); /* LogOpenGLState() Write a bunch of OpenGL state information to the log. */ void LogOpenGLState(void); /* GLScaledLineWidth() GLScaledPointSize() GLGetDisplayScaleFactor() GLSetDisplayScaleFactor() These functions wrap glLineWidth() and glPointSize(), and multiply the specified size by a "display scale factor". This is currently used to support Retina display modes in Mac OS X 10.7 and later. The default display scale factor is 1.0. */ void GLScaledLineWidth(GLfloat width); void GLScaledPointSize(GLfloat size); GLfloat GLGetDisplayScaleFactor(void); void GLSetDisplayScaleFactor(GLfloat factor); /* OOGLWireframeModeOn() OOGLWireframeModeOff() Enable/disable polygon-to-lines wireframe rendering with line width of 1. */ void OOGLWireframeModeOn(void); void OOGLWireframeModeOff(void); /* GLDrawBallBillboard() Draws a circle corresponding to a sphere of given radius at given distance. Assumes Z buffering will be disabled. */ void GLDrawBallBillboard(GLfloat radius, GLfloat step, GLfloat z_distance); /* GLDrawOval(), GLDrawFilledOval() Draw axis-alligned ellipses, as outline and fill respectively. */ void GLDrawOval(GLfloat x, GLfloat y, GLfloat z, NSSize siz, GLfloat step); void GLDrawFilledOval(GLfloat x, GLfloat y, GLfloat z, NSSize siz, GLfloat step); /* GLDrawPoints(), GLDrawFilledPoints() Draw array of points, as outline and fill respectively. */ typedef struct { GLfloat x; GLfloat y; GLfloat z; } OOGLVector; void GLDrawPoints(OOGLVector *points, int n); void GLDrawFilledPoints(OOGLVector *points, int n); void GLDrawQuadStrip(OOGLVector *points, int n); /* OO_CHECK_GL_HEAVY and error-checking stuff If OO_CHECK_GL_HEAVY is non-zero, the following error-checking facilities come into play: OOGL(foo) checks for GL errors before and after performing the statement foo. OOGLBEGIN(mode) checks for GL errors, then calls glBegin(mode). OOGLEND() calls glEnd(), then checks for GL errors. CheckOpenGLErrorsHeavy() checks for errors exactly like OOCheckOpenGLErrors(). If OO_CHECK_GL_HEAVY is zero, these macros don't perform error checking, but otherwise continue to work as before, so: OOGL(foo) performs the statement foo. OOGLBEGIN(mode) calls glBegin(mode); OOGLEND() calls glEnd(). CheckOpenGLErrorsHeavy() does nothing (including not performing any parameter side-effects). */ #ifndef OO_CHECK_GL_HEAVY #define OO_CHECK_GL_HEAVY 0 #endif #if OO_CHECK_GL_HEAVY #if OO_GL_STATE_VERIFICATION void OOGLNoteCurrentFunction(const char *func, unsigned line); #else #define OOGLNoteCurrentFunction(FUNC, line) do {} while (0) #endif NSString *OOLogAbbreviatedFileName(const char *inName); #define OOGL_PERFORM_CHECK(label, code) OOCheckOpenGLErrors(@"%s %@:%u (%s)%s", label, OOLogAbbreviatedFileName(__FILE__), __LINE__, __PRETTY_FUNCTION__, code) #define OOGL(statement) do { OOGLNoteCurrentFunction(__FUNCTION__, __LINE__); OOGL_PERFORM_CHECK("PRE", " -- " #statement); statement; OOGL_PERFORM_CHECK("POST", " -- " #statement); } while (0) #define CheckOpenGLErrorsHeavy OOCheckOpenGLErrors #define OOGLBEGIN(mode) do { OOGLNoteCurrentFunction(__FUNCTION__, __LINE__); OOGL_PERFORM_CHECK("PRE-BEGIN", " -- " #mode); glBegin(mode); } while (0) #define OOGLEND() do { glEnd(); OOGLNoteCurrentFunction(__FUNCTION__, __LINE__); OOGL_PERFORM_CHECK("POST-END", ""); } while (0) #else #define OOGL(statement) do { statement; } while (0) #define CheckOpenGLErrorsHeavy(...) do {} while (0) #define OOGLBEGIN glBegin #define OOGLEND glEnd #endif enum { kOOShaderSettingDefault = SHADERS_NOT_SUPPORTED }; // Programmer-readable shader mode strings. OOShaderSetting OOShaderSettingFromString(NSString *string); NSString *OOStringFromShaderSetting(OOShaderSetting setting); // Localized shader mode strings. NSString *OODisplayStringFromShaderSetting(OOShaderSetting setting); #ifndef NDEBUG NSString *OOGLColorToString(GLfloat color[4]); NSString *OOGLEnumToString(GLenum value); NSString *OOGLFlagToString(bool value); #endif oolite-1.82/src/Core/OOOpenGL.m000066400000000000000000000323331256642440500161720ustar00rootroot00000000000000/* OOOpenGL.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOOpenGL.h" #import "OOLogging.h" #import "OOMaths.h" #import "OOMacroOpenGL.h" #import "OOFunctionAttributes.h" #import "OOOpenGLExtensionManager.h" #ifndef NDEBUG static NSString * const kOOLogOpenGLStateDump = @"rendering.opengl.stateDump"; #endif static GLfloat sDisplayScaleFactor = 1.0f; BOOL OOCheckOpenGLErrors(NSString *format, ...) { GLenum errCode; const GLubyte *errString = NULL; BOOL errorOccurred = NO; va_list args; static BOOL noReenter; if (noReenter) return NO; noReenter = YES; OO_ENTER_OPENGL(); // Short-circut here, because glGetError() is quite expensive. if (OOLogWillDisplayMessagesInClass(kOOLogOpenGLError)) { for (;;) { errCode = glGetError(); if (errCode == GL_NO_ERROR) break; errorOccurred = YES; errString = gluErrorString(errCode); if (format == nil) format = @""; va_start(args, format); format = [[[NSString alloc] initWithFormat:format arguments:args] autorelease]; va_end(args); OOLog(kOOLogOpenGLError, @"OpenGL error: \"%s\" (%#x), context: %@", errString, errCode, format); } } #if OO_CHECK_GL_HEAVY if (errorOccurred) { OOLogIndent(); LogOpenGLState(); OOLogOutdent(); while (glGetError() != 0) {} // Suppress any errors caused by LogOpenGLState(). } #endif noReenter = NO; return errorOccurred; } void OOGLWireframeModeOn(void) { OO_ENTER_OPENGL(); /* Because of where this is called relative to the * OpenGLStateManager methods, pushAttrib/popAttrib causes the * OpenGL state to move away from where it should be. So, instead, * just switch polygon mode as needed. Linewidth is set already by * everything that needs it and not handled by the state * manager. - CIM*/ // OOGL(glPushAttrib(GL_POLYGON_BIT | GL_LINE_BIT | GL_TEXTURE_BIT)); OOGL(GLScaledLineWidth(1.0f)); OOGL(glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)); } void OOGLWireframeModeOff(void) { OO_ENTER_OPENGL(); OOGL(glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)); // OOGL(glPopAttrib()); } void GLDrawBallBillboard(GLfloat radius, GLfloat step, GLfloat z_distance) { if (EXPECT_NOT((radius <= 0)||(step < 1))) return; if (EXPECT_NOT(radius >= z_distance)) return; // inside the sphere GLfloat i, delta; GLfloat s, c; GLfloat r; OO_ENTER_OPENGL(); r = radius * z_distance / sqrt(z_distance * z_distance - radius * radius); delta = step * M_PI / 180.0f; // Convert step from degrees to radians glVertex3i(0, 0, 0); for (i = 0; i < (M_PI * 2.0); i += delta) { s = r * sin(i); c = r * cos(i); glVertex3f(s, c, 0.0); } glVertex3f(0.0, r, 0.0); //repeat the zero value to close } static void GLDrawOvalPoints(GLfloat x, GLfloat y, GLfloat z, NSSize siz, GLfloat step) { GLfloat ww = 0.5 * siz.width; GLfloat hh = 0.5 * siz.height; GLfloat theta; GLfloat delta; OO_ENTER_OPENGL(); delta = step * M_PI / 180.0f; for (theta = 0.0f; theta < (2.0f * M_PI); theta += delta) { glVertex3f(x + ww * sin(theta), y + hh * cos(theta), z); } glVertex3f(x, y + hh, z); } void GLDrawOval(GLfloat x, GLfloat y, GLfloat z, NSSize siz, GLfloat step) { OO_ENTER_OPENGL(); OOGLBEGIN(GL_LINE_STRIP); GLDrawOvalPoints(x, y, z, siz, step); OOGLEND(); } void GLDrawFilledOval(GLfloat x, GLfloat y, GLfloat z, NSSize siz, GLfloat step) { OO_ENTER_OPENGL(); OOGLBEGIN(GL_TRIANGLE_FAN); GLDrawOvalPoints(x, y, z, siz, step); OOGLEND(); } void GLDrawPoints(OOGLVector *points, int n) { int i; OO_ENTER_OPENGL(); OOGLBEGIN(GL_LINE_STRIP); for (i = 0; i < n; i++) { glVertex3f(points->x, points->y, points->z); points++; } OOGLEND(); return; } void GLDrawFilledPoints(OOGLVector *points, int n) { int i; OO_ENTER_OPENGL(); OOGLBEGIN(GL_TRIANGLE_FAN); for (i = 0; i < n; i++) { glVertex3f(points->x, points->y, points->z); points++; } OOGLEND(); return; } void GLDrawQuadStrip(OOGLVector *points, int n) { int i; OO_ENTER_OPENGL(); OOGLBEGIN(GL_QUAD_STRIP); for (i = 0; i < n; i++) { glVertex3f(points->x, points->y, points->z ); points++; } OOGLEND(); return; } void GLScaledLineWidth(GLfloat width) { OO_ENTER_OPENGL(); glLineWidth(width * sDisplayScaleFactor); } void GLScaledPointSize(GLfloat size) { OO_ENTER_OPENGL(); glPointSize(size * sDisplayScaleFactor); } GLfloat GLGetDisplayScaleFactor(void) { return sDisplayScaleFactor; } void GLSetDisplayScaleFactor(GLfloat factor) { NSCParameterAssert(factor >= 0.0f && isfinite(factor)); sDisplayScaleFactor = factor; } // MARK: LogOpenGLState() and helpers #ifndef NDEBUG static void GLDumpLightState(unsigned lightIdx); static void GLDumpMaterialState(void); static void GLDumpCullingState(void); static void GLDumpFogState(void); static void GLDumpStateFlags(void); void LogOpenGLState(void) { unsigned i; if (!OOLogWillDisplayMessagesInClass(kOOLogOpenGLStateDump)) return; OO_ENTER_OPENGL(); OOLog(kOOLogOpenGLStateDump, @"OpenGL state dump:"); OOLogIndent(); GLDumpMaterialState(); GLDumpCullingState(); if (glIsEnabled(GL_LIGHTING)) { OOLog(kOOLogOpenGLStateDump, @"Lighting: ENABLED"); for (i = 0; i != 8; ++i) { GLDumpLightState(i); } } else { OOLog(kOOLogOpenGLStateDump, @"Lighting: disabled"); } GLDumpFogState(); GLDumpStateFlags(); OOCheckOpenGLErrors(@"After state dump"); OOLogOutdent(); } NSString *OOGLColorToString(GLfloat color[4]) { #define COLOR_EQUAL(color, r, g, b, a) (color[0] == (r) && color[1] == (g) && color[2] == (b) && color[3] == (a)) #define COLOR_CASE(r, g, b, a, str) do { if (COLOR_EQUAL(color, (r), (g), (b), (a))) return (str); } while (0) COLOR_CASE(1, 1, 1, 1, @"white"); COLOR_CASE(0, 0, 0, 1, @"black"); COLOR_CASE(0, 0, 0, 0, @"clear"); COLOR_CASE(1, 0, 0, 1, @"red"); COLOR_CASE(0, 1, 0, 1, @"green"); COLOR_CASE(0, 0, 1, 1, @"blue"); COLOR_CASE(0, 1, 1, 1, @"cyan"); COLOR_CASE(1, 0, 1, 1, @"magenta"); COLOR_CASE(1, 1, 0, 1, @"yellow"); return [NSString stringWithFormat:@"(%.2ff, %.2ff, %.2ff, %.2ff)", color[0], color[1], color[2], color[3]]; } static void GLDumpLightState(unsigned lightIdx) { BOOL enabled; GLenum lightID = GL_LIGHT0 + lightIdx; GLfloat color[4]; OO_ENTER_OPENGL(); OOGL(enabled = glIsEnabled(lightID)); OOLog(kOOLogOpenGLStateDump, @"Light %u: %@", lightIdx, OOGLFlagToString(enabled)); if (enabled) { OOLogIndent(); OOGL(glGetLightfv(GL_LIGHT1, GL_AMBIENT, color)); OOLog(kOOLogOpenGLStateDump, @"Ambient: %@", OOGLColorToString(color)); OOGL(glGetLightfv(GL_LIGHT1, GL_DIFFUSE, color)); OOLog(kOOLogOpenGLStateDump, @"Diffuse: %@", OOGLColorToString(color)); OOGL(glGetLightfv(GL_LIGHT1, GL_SPECULAR, color)); OOLog(kOOLogOpenGLStateDump, @"Specular: %@", OOGLColorToString(color)); OOLogOutdent(); } } static void GLDumpMaterialState(void) { GLfloat color[4]; GLfloat shininess; GLint shadeModel, blendSrc, blendDst, texMode; BOOL blending; OO_ENTER_OPENGL(); OOLog(kOOLogOpenGLStateDump, @"Material state:"); OOLogIndent(); OOGL(glGetMaterialfv(GL_FRONT, GL_AMBIENT, color)); OOLog(kOOLogOpenGLStateDump, @"Ambient: %@", OOGLColorToString(color)); OOGL(glGetMaterialfv(GL_FRONT, GL_DIFFUSE, color)); OOLog(kOOLogOpenGLStateDump, @"Diffuse: %@", OOGLColorToString(color)); OOGL(glGetMaterialfv(GL_FRONT, GL_EMISSION, color)); OOLog(kOOLogOpenGLStateDump, @"Emission: %@", OOGLColorToString(color)); OOGL(glGetMaterialfv(GL_FRONT, GL_SPECULAR, color)); OOLog(kOOLogOpenGLStateDump, @"Specular: %@", OOGLColorToString(color)); OOGL(glGetMaterialfv(GL_FRONT, GL_SHININESS, &shininess)); OOLog(kOOLogOpenGLStateDump, @"Shininess: %g", shininess); OOGL(OOLog(kOOLogOpenGLStateDump, @"Colour material: %@", OOGLFlagToString(glIsEnabled(GL_COLOR_MATERIAL)))); OOGL(glGetFloatv(GL_CURRENT_COLOR, color)); OOLog(kOOLogOpenGLStateDump, @"Current color: %@", OOGLColorToString(color)); OOGL(glGetIntegerv(GL_SHADE_MODEL, &shadeModel)); OOLog(kOOLogOpenGLStateDump, @"Shade model: %@", OOGLEnumToString(shadeModel)); OOGL(blending = glIsEnabled(GL_BLEND)); OOLog(kOOLogOpenGLStateDump, @"Blending: %@", OOGLFlagToString(blending)); if (blending) { OOGL(glGetIntegerv(GL_BLEND_SRC, &blendSrc)); OOGL(glGetIntegerv(GL_BLEND_DST, &blendDst)); OOLog(kOOLogOpenGLStateDump, @"Blend function: %@, %@", OOGLEnumToString(blendSrc), OOGLEnumToString(blendDst)); } OOGL(glGetTexEnviv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &texMode)); OOLog(kOOLogOpenGLStateDump, @"Texture env mode: %@", OOGLEnumToString(texMode)); #if OO_MULTITEXTURE if ([[OOOpenGLExtensionManager sharedManager] textureUnitCount] > 1) { GLint textureUnit; OOGL(glGetIntegerv(GL_ACTIVE_TEXTURE_ARB, &textureUnit)); OOLog(kOOLogOpenGLStateDump, @"Active texture unit: %@", OOGLEnumToString(textureUnit)); OOGL(glGetIntegerv(GL_CLIENT_ACTIVE_TEXTURE_ARB, &textureUnit)); OOLog(kOOLogOpenGLStateDump, @"Active client texture unit: %@", OOGLEnumToString(textureUnit)); } #endif OOLogOutdent(); } static void GLDumpCullingState(void) { OO_ENTER_OPENGL(); bool enabled; OOGL(enabled = glIsEnabled(GL_CULL_FACE)); OOLog(kOOLogOpenGLStateDump, @"Face culling: %@", OOGLFlagToString(enabled)); if (enabled) { GLint value; OOLogIndent(); OOGL(glGetIntegerv(GL_CULL_FACE_MODE, &value)); OOLog(kOOLogOpenGLStateDump, @"Cull face mode: %@", OOGLEnumToString(value)); OOGL(glGetIntegerv(GL_FRONT_FACE, &value)); OOLog(kOOLogOpenGLStateDump, @"Front face direction: %@", OOGLEnumToString(value)); OOLogOutdent(); } } static void GLDumpFogState(void) { BOOL enabled; GLint value; GLfloat start, end, density, index; GLfloat color[4]; OO_ENTER_OPENGL(); OOGL(enabled = glIsEnabled(GL_FOG)); OOLog(kOOLogOpenGLStateDump, @"Fog: %@", OOGLFlagToString(enabled)); if (enabled) { OOLogIndent(); OOGL(glGetIntegerv(GL_FOG_MODE, &value)); OOLog(kOOLogOpenGLStateDump, @"Fog mode: %@", OOGLEnumToString(value)); OOGL(glGetFloatv(GL_FOG_COLOR, color)); OOLog(kOOLogOpenGLStateDump, @"Fog colour: %@", OOGLColorToString(color)); OOGL(glGetFloatv(GL_FOG_START, &start)); OOGL(glGetFloatv(GL_FOG_START, &end)); OOLog(kOOLogOpenGLStateDump, @"Fog start, end: %g, %g", start, end); OOGL(glGetFloatv(GL_FOG_DENSITY, &density)); OOLog(kOOLogOpenGLStateDump, @"Fog density: %g", density); OOGL(glGetFloatv(GL_FOG_DENSITY, &index)); OOLog(kOOLogOpenGLStateDump, @"Fog index: %g", index); OOLogOutdent(); } } static void GLDumpStateFlags(void) { OO_ENTER_OPENGL(); #define DUMP_STATE_FLAG(x) OOLog(kOOLogOpenGLStateDump, @ #x ": %@", OOGLFlagToString(glIsEnabled(x))) #define DUMP_GET_FLAG(x) do { GLboolean flag; glGetBooleanv(x, &flag); OOLog(kOOLogOpenGLStateDump, @ #x ": %@", OOGLFlagToString(flag)); } while (0) OOLog(kOOLogOpenGLStateDump, @"Selected state flags:"); OOLogIndent(); DUMP_STATE_FLAG(GL_VERTEX_ARRAY); DUMP_STATE_FLAG(GL_NORMAL_ARRAY); DUMP_STATE_FLAG(GL_TEXTURE_COORD_ARRAY); DUMP_STATE_FLAG(GL_COLOR_ARRAY); DUMP_STATE_FLAG(GL_TEXTURE_2D); DUMP_STATE_FLAG(GL_DEPTH_TEST); DUMP_GET_FLAG(GL_DEPTH_WRITEMASK); OOLogOutdent(); #undef DUMP_STATE_FLAG } #define CASE(x) case x: return @#x NSString *OOGLEnumToString(GLenum value) { switch (value) { // ShadingModel CASE(GL_FLAT); CASE(GL_SMOOTH); // BlendingFactorSrc/BlendingFactorDest CASE(GL_ZERO); CASE(GL_ONE); CASE(GL_DST_COLOR); CASE(GL_SRC_COLOR); CASE(GL_ONE_MINUS_DST_COLOR); CASE(GL_ONE_MINUS_SRC_COLOR); CASE(GL_SRC_ALPHA); CASE(GL_DST_ALPHA); CASE(GL_ONE_MINUS_SRC_ALPHA); CASE(GL_ONE_MINUS_DST_ALPHA); CASE(GL_SRC_ALPHA_SATURATE); // TextureEnvMode CASE(GL_MODULATE); CASE(GL_DECAL); CASE(GL_BLEND); CASE(GL_REPLACE); // FrontFaceDirection CASE(GL_CW); CASE(GL_CCW); // CullFaceMode CASE(GL_FRONT); CASE(GL_BACK); CASE(GL_FRONT_AND_BACK); // FogMode CASE(GL_LINEAR); CASE(GL_EXP); CASE(GL_EXP2); #if OO_MULTITEXTURE // Texture units #ifdef GL_TEXTURE0 #define TEXCASE CASE #else #define TEXCASE(x) CASE(x##_ARB) #endif TEXCASE(GL_TEXTURE0); TEXCASE(GL_TEXTURE1); TEXCASE(GL_TEXTURE2); TEXCASE(GL_TEXTURE3); TEXCASE(GL_TEXTURE4); TEXCASE(GL_TEXTURE5); TEXCASE(GL_TEXTURE6); TEXCASE(GL_TEXTURE7); TEXCASE(GL_TEXTURE8); TEXCASE(GL_TEXTURE9); TEXCASE(GL_TEXTURE10); TEXCASE(GL_TEXTURE11); TEXCASE(GL_TEXTURE12); TEXCASE(GL_TEXTURE13); TEXCASE(GL_TEXTURE14); TEXCASE(GL_TEXTURE15); #endif default: return [NSString stringWithFormat:@"unknown: %u", value]; } } NSString *OOGLFlagToString(bool value) { return value ? @"ENABLED" : @"disabled"; } #else void LogOpenGLState(void) { } #endif oolite-1.82/src/Core/OOOpenGLExtensionManager.h000066400000000000000000000175651256642440500213670ustar00rootroot00000000000000/* OOOpenGLExtensionManager.h Handles checking for and using OpenGL extensions and related information. This is thread safe, except for initialization; that is, +sharedManager should be called from the main thread at an early point. The OpenGL context must be set up by then. Copyright (C) 2007-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" #import "OOOpenGL.h" #import "OOFunctionAttributes.h" #import "OOTypes.h" #ifndef OO_SHADERS #ifdef NO_SHADERS #define OO_SHADERS 0 #else #define OO_SHADERS 1 #endif #endif #if OO_SHADERS // Certain extensions are required for shader support. #ifndef GL_ARB_multitexture #warning NO_SHADERS not defined and GL_ARB_multitexture not defined; if possible, use a newer version of glext.h. #endif #ifndef GL_ARB_shader_objects #warning NO_SHADERS not defined and GL_ARB_shader_objects not defined; if possible, use a newer version of glext.h. #endif #ifndef GL_ARB_shading_language_100 #warning NO_SHADERS not defined and GL_ARB_shading_language_100 not defined; if possible, use a newer version of glext.h. #endif #ifndef GL_ARB_fragment_shader #warning NO_SHADERS not defined and GL_ARB_fragment_shader not defined; if possible, use a newer version of glext.h. #endif #ifndef GL_ARB_vertex_shader #warning NO_SHADERS not defined and GL_ARB_vertex_shader not defined; if possible, use a newer version of glext.h. #endif #endif //OO_SHADERS #if GL_ARB_vertex_buffer_object #define OO_USE_VBO 0 // Disabled due to crashes on certain systems (and limited VBO use) #else #define OO_USE_VBO 0 #warning Building without vertex buffer object support, are your OpenGL headers up to date? #endif #if GL_EXT_framebuffer_object #define OO_USE_FBO 1 #else #define OO_USE_VBO 0 #warning Building without frame buffer object support, are your OpenGL headers up to date? #endif /* Multitexturing requires GL_ARB_multitexture, but we only have use for it if GL_ARB_texture_env_combine is available, and GL_ARB_texture_env_combine requires GL_ARB_multitexture. Both GL_ARB_multitexture and GL_ARB_texture_env_combine were promoted to core in OpenGL 1.3. */ #if GL_ARB_texture_env_combine #define OO_MULTITEXTURE 1 #else #warning Building without texture combiner support, are your OpenGL headers up to date? #define OO_MULTITEXTURE 0 #endif #if defined(GL_ARB_texture_cube_map) || defined(GL_VERSION_1_3) #define OO_TEXTURE_CUBE_MAP 1 #else #warning Building without cube map support, are your OpenGL headers up to date? #define OO_TEXTURE_CUBE_MAP 0 #endif #define OOOPENGLEXTMGR_LOCK_SET_ACCESS (!OOLITE_MAC_OS_X) @interface OOOpenGLExtensionManager: NSObject { @private #if OOOPENGLEXTMGR_LOCK_SET_ACCESS NSLock *lock; #endif NSSet *extensions; NSString *vendor; NSString *renderer; unsigned major, minor, release; BOOL usePointSmoothing; BOOL useLineSmoothing; BOOL useDustShader; #if OO_SHADERS BOOL shadersAvailable; OOShaderSetting defaultShaderSetting; OOShaderSetting maximumShaderSetting; GLint textureImageUnitCount; #endif #if OO_USE_VBO BOOL vboSupported; #endif #if OO_USE_FBO BOOL fboSupported; #endif #if OO_MULTITEXTURE BOOL textureCombinersSupported; GLint textureUnitCount; #endif } + (OOOpenGLExtensionManager *) sharedManager; - (void) reset; - (BOOL)haveExtension:(NSString *)extension; - (BOOL)shadersSupported; - (OOGraphicsDetail)defaultDetailLevel; - (OOGraphicsDetail)maximumDetailLevel; - (GLint)textureImageUnitCount; // Fragment shader sampler count limit. Does not apply to fixed function multitexturing. (GL_MAX_TEXTURE_IMAGE_UNITS_ARB) - (BOOL)vboSupported; // Vertex buffer objects - (BOOL)fboSupported; // Frame buffer objects - (BOOL)textureCombinersSupported; - (GLint)textureUnitCount; // Fixed function multitexture limit, does not apply to shaders. (GL_MAX_TEXTURE_UNITS_ARB) - (NSUInteger)majorVersionNumber; - (NSUInteger)minorVersionNumber; - (NSUInteger)releaseVersionNumber; - (void)getVersionMajor:(unsigned *)outMajor minor:(unsigned *)outMinor release:(unsigned *)outRelease; - (BOOL) versionIsAtLeastMajor:(unsigned)maj minor:(unsigned)min; - (NSString *) vendorString; - (NSString *) rendererString; // GL_POINT_SMOOTH is slow or non-functional on some GPUs. - (BOOL) usePointSmoothing; - (BOOL) useLineSmoothing; // Using vertex shader for dust transformation is counterproductive on systems which run vertex shaders on the CPU. - (BOOL) useDustShader; @end OOINLINE BOOL OOShadersSupported(void) INLINE_PURE_FUNC; OOINLINE BOOL OOShadersSupported(void) { return [[OOOpenGLExtensionManager sharedManager] shadersSupported]; } #if OOLITE_WINDOWS /* Declare the function pointers for the OpenGL extensions used in the game (required for Windows only). */ #if OO_SHADERS // Shader functions PFNGLUSEPROGRAMOBJECTARBPROC glUseProgramObjectARB; PFNGLGETUNIFORMLOCATIONARBPROC glGetUniformLocationARB; PFNGLUNIFORM1IARBPROC glUniform1iARB; PFNGLUNIFORM1FARBPROC glUniform1fARB; PFNGLUNIFORMMATRIX3FVARBPROC glUniformMatrix3fvARB; PFNGLUNIFORMMATRIX4FVARBPROC glUniformMatrix4fvARB; PFNGLUNIFORM4FVARBPROC glUniform4fvARB; PFNGLGETOBJECTPARAMETERIVARBPROC glGetObjectParameterivARB; PFNGLCREATESHADEROBJECTARBPROC glCreateShaderObjectARB; PFNGLGETINFOLOGARBPROC glGetInfoLogARB; PFNGLCREATEPROGRAMOBJECTARBPROC glCreateProgramObjectARB; PFNGLATTACHOBJECTARBPROC glAttachObjectARB; PFNGLDELETEOBJECTARBPROC glDeleteObjectARB; PFNGLLINKPROGRAMARBPROC glLinkProgramARB; PFNGLCOMPILESHADERARBPROC glCompileShaderARB; PFNGLSHADERSOURCEARBPROC glShaderSourceARB; PFNGLUNIFORM2FVARBPROC glUniform2fvARB; PFNGLBINDATTRIBLOCATIONARBPROC glBindAttribLocationARB; PFNGLENABLEVERTEXATTRIBARRAYARBPROC glEnableVertexAttribArrayARB; PFNGLVERTEXATTRIBPOINTERARBPROC glVertexAttribPointerARB; PFNGLDISABLEVERTEXATTRIBARRAYARBPROC glDisableVertexAttribArrayARB; PFNGLVALIDATEPROGRAMARBPROC glValidateProgramARB; #endif // OO_SHADERS #if OO_SHADERS || OO_MULTITEXTURE PFNGLACTIVETEXTUREARBPROC glActiveTextureARB; #endif #if OO_MULTITEXTURE PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB; #endif #if OO_USE_VBO PFNGLGENBUFFERSARBPROC glGenBuffersARB; PFNGLDELETEBUFFERSARBPROC glDeleteBuffersARB; PFNGLBINDBUFFERARBPROC glBindBufferARB; PFNGLBUFFERDATAARBPROC glBufferDataARB; #endif #if OO_USE_FBO PFNGLGENFRAMEBUFFERSEXTPROC glGenFramebuffersEXT; PFNGLBINDFRAMEBUFFEREXTPROC glBindFramebufferEXT; PFNGLGENRENDERBUFFERSEXTPROC glGenRenderbuffersEXT; PFNGLBINDRENDERBUFFEREXTPROC glBindRenderbufferEXT; PFNGLRENDERBUFFERSTORAGEEXTPROC glRenderbufferStorageEXT; PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC glFramebufferRenderbufferEXT; PFNGLFRAMEBUFFERTEXTURE2DEXTPROC glFramebufferTexture2DEXT; PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC glCheckFramebufferStatusEXT; PFNGLDELETEFRAMEBUFFERSEXTPROC glDeleteFramebuffersEXT; PFNGLDELETERENDERBUFFERSEXTPROC glDeleteRenderbuffersEXT; #endif #endif // OOLITE_WINDOWS oolite-1.82/src/Core/OOOpenGLExtensionManager.m000066400000000000000000000574721256642440500213750ustar00rootroot00000000000000/* OOOpenGLExtensionManager.m Copyright (C) 2007-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOOpenGLExtensionManager.h" #import "OOLogging.h" #import "OOFunctionAttributes.h" #include #import "NSThreadOOExtensions.h" #import "ResourceManager.h" #import "OOCollectionExtractors.h" #import "OORegExpMatcher.h" #import "OOConstToString.h" /* OpenGL version required, currently 1.1 or later (basic stuff like glBindTexture(), glDrawArrays()). We probably have implicit requirements for later versions, but I don't feel like auditing. -- Ahruman */ enum { kMinMajorVersion = 1, kMinMinorVersion = 1 }; #if OOLITE_WINDOWS /* Define the function pointers for the OpenGL extensions used in the game (required for Windows only). */ static void OOBadOpenGLExtensionUsed(void) GCC_ATTR((noreturn, used)); #if OO_SHADERS PFNGLUSEPROGRAMOBJECTARBPROC glUseProgramObjectARB = (PFNGLUSEPROGRAMOBJECTARBPROC)&OOBadOpenGLExtensionUsed; PFNGLGETUNIFORMLOCATIONARBPROC glGetUniformLocationARB = (PFNGLGETUNIFORMLOCATIONARBPROC)&OOBadOpenGLExtensionUsed; PFNGLUNIFORM1IARBPROC glUniform1iARB = (PFNGLUNIFORM1IARBPROC)&OOBadOpenGLExtensionUsed; PFNGLUNIFORM1FARBPROC glUniform1fARB = (PFNGLUNIFORM1FARBPROC)&OOBadOpenGLExtensionUsed; PFNGLUNIFORMMATRIX3FVARBPROC glUniformMatrix3fvARB = (PFNGLUNIFORMMATRIX3FVARBPROC)&OOBadOpenGLExtensionUsed; PFNGLUNIFORMMATRIX4FVARBPROC glUniformMatrix4fvARB = (PFNGLUNIFORMMATRIX4FVARBPROC)&OOBadOpenGLExtensionUsed; PFNGLUNIFORM4FVARBPROC glUniform4fvARB = (PFNGLUNIFORM4FVARBPROC)&OOBadOpenGLExtensionUsed; PFNGLGETOBJECTPARAMETERIVARBPROC glGetObjectParameterivARB = (PFNGLGETOBJECTPARAMETERIVARBPROC)&OOBadOpenGLExtensionUsed; PFNGLCREATESHADEROBJECTARBPROC glCreateShaderObjectARB = (PFNGLCREATESHADEROBJECTARBPROC)&OOBadOpenGLExtensionUsed; PFNGLGETINFOLOGARBPROC glGetInfoLogARB = (PFNGLGETINFOLOGARBPROC)&OOBadOpenGLExtensionUsed; PFNGLCREATEPROGRAMOBJECTARBPROC glCreateProgramObjectARB = (PFNGLCREATEPROGRAMOBJECTARBPROC)&OOBadOpenGLExtensionUsed; PFNGLATTACHOBJECTARBPROC glAttachObjectARB = (PFNGLATTACHOBJECTARBPROC)&OOBadOpenGLExtensionUsed; PFNGLDELETEOBJECTARBPROC glDeleteObjectARB = (PFNGLDELETEOBJECTARBPROC)&OOBadOpenGLExtensionUsed; PFNGLLINKPROGRAMARBPROC glLinkProgramARB = (PFNGLLINKPROGRAMARBPROC)&OOBadOpenGLExtensionUsed; PFNGLCOMPILESHADERARBPROC glCompileShaderARB = (PFNGLCOMPILESHADERARBPROC)&OOBadOpenGLExtensionUsed; PFNGLSHADERSOURCEARBPROC glShaderSourceARB = (PFNGLSHADERSOURCEARBPROC)&OOBadOpenGLExtensionUsed; PFNGLUNIFORM2FVARBPROC glUniform2fvARB = (PFNGLUNIFORM2FVARBPROC)&OOBadOpenGLExtensionUsed; PFNGLBINDATTRIBLOCATIONARBPROC glBindAttribLocationARB = (PFNGLBINDATTRIBLOCATIONARBPROC)&OOBadOpenGLExtensionUsed; PFNGLENABLEVERTEXATTRIBARRAYARBPROC glEnableVertexAttribArrayARB = (PFNGLENABLEVERTEXATTRIBARRAYARBPROC)&OOBadOpenGLExtensionUsed; PFNGLVERTEXATTRIBPOINTERARBPROC glVertexAttribPointerARB = (PFNGLVERTEXATTRIBPOINTERARBPROC)&OOBadOpenGLExtensionUsed; PFNGLDISABLEVERTEXATTRIBARRAYARBPROC glDisableVertexAttribArrayARB = (PFNGLDISABLEVERTEXATTRIBARRAYARBPROC)&OOBadOpenGLExtensionUsed; PFNGLVALIDATEPROGRAMARBPROC glValidateProgramARB = (PFNGLVALIDATEPROGRAMARBPROC)&OOBadOpenGLExtensionUsed; #endif #if OO_SHADERS || OO_MULTITEXTURE PFNGLACTIVETEXTUREARBPROC glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)&OOBadOpenGLExtensionUsed; #endif #if OO_MULTITEXTURE PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC)&OOBadOpenGLExtensionUsed; #endif #if OO_USE_VBO PFNGLGENBUFFERSARBPROC glGenBuffersARB = (PFNGLGENBUFFERSARBPROC)&OOBadOpenGLExtensionUsed; PFNGLDELETEBUFFERSARBPROC glDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC)&OOBadOpenGLExtensionUsed; PFNGLBINDBUFFERARBPROC glBindBufferARB = (PFNGLBINDBUFFERARBPROC)&OOBadOpenGLExtensionUsed; PFNGLBUFFERDATAARBPROC glBufferDataARB = (PFNGLBUFFERDATAARBPROC)&OOBadOpenGLExtensionUsed; #endif #if OO_USE_FBO PFNGLGENFRAMEBUFFERSEXTPROC glGenFramebuffersEXT = (PFNGLGENFRAMEBUFFERSEXTPROC)&OOBadOpenGLExtensionUsed; PFNGLBINDFRAMEBUFFEREXTPROC glBindFramebufferEXT = (PFNGLBINDFRAMEBUFFEREXTPROC)&OOBadOpenGLExtensionUsed; PFNGLGENRENDERBUFFERSEXTPROC glGenRenderbuffersEXT = (PFNGLGENRENDERBUFFERSEXTPROC)&OOBadOpenGLExtensionUsed; PFNGLBINDRENDERBUFFEREXTPROC glBindRenderbufferEXT = (PFNGLBINDRENDERBUFFEREXTPROC)&OOBadOpenGLExtensionUsed; PFNGLRENDERBUFFERSTORAGEEXTPROC glRenderbufferStorageEXT = (PFNGLRENDERBUFFERSTORAGEEXTPROC)&OOBadOpenGLExtensionUsed; PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC glFramebufferRenderbufferEXT = (PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC)&OOBadOpenGLExtensionUsed; PFNGLFRAMEBUFFERTEXTURE2DEXTPROC glFramebufferTexture2DEXT = (PFNGLFRAMEBUFFERTEXTURE2DEXTPROC)&OOBadOpenGLExtensionUsed; PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC glCheckFramebufferStatusEXT = (PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC)&OOBadOpenGLExtensionUsed; PFNGLDELETEFRAMEBUFFERSEXTPROC glDeleteFramebuffersEXT = (PFNGLDELETEFRAMEBUFFERSEXTPROC)&OOBadOpenGLExtensionUsed; PFNGLDELETERENDERBUFFERSEXTPROC glDeleteRenderbuffersEXT = (PFNGLDELETERENDERBUFFERSEXTPROC)&OOBadOpenGLExtensionUsed; #endif #endif static NSString * const kOOLogOpenGLShaderSupport = @"rendering.opengl.shader.support"; static OOOpenGLExtensionManager *sSingleton = nil; // Read integer from string, advancing string to end of read data. static unsigned IntegerFromString(const GLubyte **ioString); @interface OOOpenGLExtensionManager (OOPrivate) #if OO_SHADERS - (void)checkShadersSupported; #endif #if OO_USE_VBO - (void)checkVBOSupported; #endif #if OO_USE_FBO - (void)checkFBOSupported; #endif #if GL_ARB_texture_env_combine - (void)checkTextureCombinersSupported; #endif - (NSDictionary *) lookUpPerGPUSettingsWithVersionString:(NSString *)version extensionsString:(NSString *)extensionsStr; @end static NSArray *ArrayOfExtensions(NSString *extensionString) { NSArray *components = [extensionString componentsSeparatedByString:@" "]; NSMutableArray *result = [NSMutableArray arrayWithCapacity:[components count]]; NSEnumerator *extEnum = nil; NSString *extStr = nil; for (extEnum = [components objectEnumerator]; (extStr = [extEnum nextObject]); ) { if ([extStr length] > 0) [result addObject:extStr]; } return result; } @implementation OOOpenGLExtensionManager - (id)init { self = [super init]; if (self != nil) { #if OOOPENGLEXTMGR_LOCK_SET_ACCESS lock = [[NSLock alloc] init]; [lock setName:@"OOOpenGLExtensionManager extension set lock"]; #endif [self reset]; } return self; } - (void) reset { const GLubyte *versionString = NULL, *curr = NULL; DESTROY(extensions); DESTROY(vendor); DESTROY(renderer); NSString *extensionsStr = [NSString stringWithUTF8String:(char *)glGetString(GL_EXTENSIONS)]; extensions = [[NSSet alloc] initWithArray:ArrayOfExtensions(extensionsStr)]; vendor = [[NSString alloc] initWithUTF8String:(const char *)glGetString(GL_VENDOR)]; renderer = [[NSString alloc] initWithUTF8String:(const char *)glGetString(GL_RENDERER)]; versionString = glGetString(GL_VERSION); if (versionString != NULL) { /* String is supposed to be "major.minorFOO" or "major.minor.releaseFOO" where FOO is an empty string or a string beginning with space. */ curr = versionString; major = IntegerFromString(&curr); if (*curr == '.') { curr++; minor = IntegerFromString(&curr); } if (*curr == '.') { curr++; release = IntegerFromString(&curr); } } /* For aesthetic reasons, cause the ResourceManager to initialize its search paths here. If we don't, the search path dump ends up in the middle of the OpenGL stuff. */ [ResourceManager paths]; OOLog(@"rendering.opengl.version", @"OpenGL renderer version: %u.%u.%u (\"%s\"). Vendor: \"%@\". Renderer: \"%@\".", major, minor, release, versionString, vendor, renderer); OOLog(@"rendering.opengl.extensions", @"OpenGL extensions (%lu):\n%@", [extensions count], [[extensions allObjects] componentsJoinedByString:@", "]); if (![self versionIsAtLeastMajor:kMinMajorVersion minor:kMinMinorVersion]) { OOLog(@"rendering.opengl.version.insufficient", @"***** Oolite requires OpenGL version %u.%u or later.", kMinMajorVersion, kMinMinorVersion); [NSException raise:@"OoliteOpenGLTooOldException" format:@"Oolite requires at least OpenGL %u.%u. You have %u.%u (\"%s\").", kMinMajorVersion, kMinMinorVersion, major, minor, versionString]; } NSString *versionStr = [[[NSString alloc] initWithUTF8String:(const char *)versionString] autorelease]; NSDictionary *gpuConfig = [self lookUpPerGPUSettingsWithVersionString:versionStr extensionsString:extensionsStr]; #if OO_SHADERS [self checkShadersSupported]; if (shadersAvailable) { defaultShaderSetting = OOShaderSettingFromString([gpuConfig oo_stringForKey:@"default_shader_level" defaultValue:@"SHADERS_FULL"]); maximumShaderSetting = OOShaderSettingFromString([gpuConfig oo_stringForKey:@"maximum_shader_level" defaultValue:@"SHADERS_FULL"]); if (maximumShaderSetting <= SHADERS_OFF) { shadersAvailable = NO; maximumShaderSetting = SHADERS_NOT_SUPPORTED; OOLog(kOOLogOpenGLShaderSupport, @"Shaders will not be used (disallowed for GPU type \"%@\").", [gpuConfig oo_stringForKey:@"name" defaultValue:renderer]); } if (maximumShaderSetting < defaultShaderSetting) { defaultShaderSetting = maximumShaderSetting; } if (shadersAvailable) { OOLog(kOOLogOpenGLShaderSupport, @"Shaders are supported."); } } else { defaultShaderSetting = SHADERS_NOT_SUPPORTED; maximumShaderSetting = SHADERS_NOT_SUPPORTED; } GLint texImageUnitOverride = [gpuConfig oo_intForKey:@"texture_image_units" defaultValue:textureImageUnitCount]; if (texImageUnitOverride < textureImageUnitCount) textureImageUnitCount = MAX(texImageUnitOverride, 0); #endif #if OO_USE_VBO [self checkVBOSupported]; #endif #if OO_USE_FBO [self checkFBOSupported]; #endif #if OO_MULTITEXTURE [self checkTextureCombinersSupported]; GLint texUnitOverride = [gpuConfig oo_intForKey:@"texture_units" defaultValue:textureUnitCount]; if (texUnitOverride < textureUnitCount) textureUnitCount = MAX(texUnitOverride, 0); #endif usePointSmoothing = [gpuConfig oo_boolForKey:@"smooth_points" defaultValue:YES]; useLineSmoothing = [gpuConfig oo_boolForKey:@"smooth_lines" defaultValue:YES]; useDustShader = [gpuConfig oo_boolForKey:@"use_dust_shader" defaultValue:YES]; } - (void)dealloc { if (sSingleton == self) sSingleton = nil; #if OOOPENGLEXTMGR_LOCK_SET_ACCESS [lock release]; #endif DESTROY(extensions); DESTROY(vendor); DESTROY(renderer); [super dealloc]; } + (OOOpenGLExtensionManager *)sharedManager { // NOTE: assumes single-threaded first access. See header. if (sSingleton == nil) sSingleton = [[self alloc] init]; return sSingleton; } - (BOOL)haveExtension:(NSString *)extension { // NSSet is documented as thread-safe under OS X, but I'm not sure about GNUstep. -- Ahruman #if OOOPENGLEXTMGR_LOCK_SET_ACCESS [lock lock]; #endif BOOL result = [extensions containsObject:extension]; #if OOOPENGLEXTMGR_LOCK_SET_ACCESS [lock unlock]; #endif return result; } - (BOOL)shadersSupported { #if OO_SHADERS return shadersAvailable; #else return NO; #endif } - (OOGraphicsDetail)defaultDetailLevel { #if OO_SHADERS if (defaultShaderSetting < SHADERS_FULL) { return DETAIL_LEVEL_MINIMUM; } else { return DETAIL_LEVEL_MAXIMUM; } #else return SHADERS_NOT_SUPPORTED; #endif } - (OOGraphicsDetail)maximumDetailLevel { #if OO_SHADERS if (maximumShaderSetting < SHADERS_FULL) { return DETAIL_LEVEL_MINIMUM; } else { return DETAIL_LEVEL_MAXIMUM; } #else return DETAIL_LEVEL_MINIMUM; #endif } - (GLint)textureImageUnitCount { #if OO_SHADERS return textureImageUnitCount; #else return 0; #endif } - (BOOL)vboSupported { #if OO_USE_VBO return vboSupported; #else return NO; #endif } - (BOOL)fboSupported { #if OO_USE_FBO return fboSupported; #else return NO; #endif } - (BOOL)textureCombinersSupported { #if OO_MULTITEXTURE return textureCombinersSupported; #else return NO; #endif } - (GLint)textureUnitCount { #if OO_MULTITEXTURE return textureUnitCount; #else return 0; #endif } - (NSUInteger)majorVersionNumber { return major; } - (NSUInteger)minorVersionNumber { return minor; } - (NSUInteger)releaseVersionNumber { return release; } - (void)getVersionMajor:(unsigned *)outMajor minor:(unsigned *)outMinor release:(unsigned *)outRelease { if (outMajor != NULL) *outMajor = major; if (outMinor != NULL) *outMinor = minor; if (outRelease != NULL) *outRelease = release; } - (BOOL) versionIsAtLeastMajor:(unsigned)maj minor:(unsigned)min { return major > maj || (major == maj && minor >= min); } - (NSString *) vendorString { return vendor; } - (NSString *) rendererString { return renderer; } - (BOOL) usePointSmoothing { return usePointSmoothing; } - (BOOL) useLineSmoothing { return useLineSmoothing; } - (BOOL) useDustShader { return useDustShader; } @end static unsigned IntegerFromString(const GLubyte **ioString) { if (EXPECT_NOT(ioString == NULL)) return 0; unsigned result = 0; const GLubyte *curr = *ioString; while ('0' <= *curr && *curr <= '9') { result = result * 10 + *curr++ - '0'; } *ioString = curr; return result; } @implementation OOOpenGLExtensionManager (OOPrivate) #if OO_SHADERS - (void)checkShadersSupported { shadersAvailable = NO; /* Some cards claim to support shaders but do so extremely * badly. These are listed in gpu-settings.plist where we know * about them; for those we don't being able to run with * -noshaders may help get the game up and running at a frame rate * where thegraphics settings can be changed. - CIM */ NSArray *arguments = [[NSProcessInfo processInfo] arguments]; NSEnumerator *argEnum = nil; NSString *arg = nil; // scan for shader overrides: -noshaders || --noshaders for (argEnum = [arguments objectEnumerator]; (arg = [argEnum nextObject]); ) { if ([arg isEqual:@"-noshaders"] || [arg isEqual:@"--noshaders"]) { OOLog(kOOLogOpenGLShaderSupport, @"Shaders will not be used (disabled on command line)."); return; } } NSString * const requiredExtension[] = { @"GL_ARB_shading_language_100", @"GL_ARB_fragment_shader", @"GL_ARB_vertex_shader", @"GL_ARB_multitexture", @"GL_ARB_shader_objects", nil // sentinel - don't remove! }; NSString * const *required = NULL; for (required = requiredExtension; *required != nil; ++required) { if (![self haveExtension:*required]) { OOLog(kOOLogOpenGLShaderSupport, @"Shaders will not be used (OpenGL extension %@ is not available).", *required); return; } } #if OOLITE_WINDOWS glGetObjectParameterivARB = (PFNGLGETOBJECTPARAMETERIVARBPROC)wglGetProcAddress("glGetObjectParameterivARB"); glCreateShaderObjectARB = (PFNGLCREATESHADEROBJECTARBPROC)wglGetProcAddress("glCreateShaderObjectARB"); glGetInfoLogARB = (PFNGLGETINFOLOGARBPROC)wglGetProcAddress("glGetInfoLogARB"); glCreateProgramObjectARB = (PFNGLCREATEPROGRAMOBJECTARBPROC)wglGetProcAddress("glCreateProgramObjectARB"); glAttachObjectARB = (PFNGLATTACHOBJECTARBPROC)wglGetProcAddress("glAttachObjectARB"); glDeleteObjectARB = (PFNGLDELETEOBJECTARBPROC)wglGetProcAddress("glDeleteObjectARB"); glLinkProgramARB = (PFNGLLINKPROGRAMARBPROC)wglGetProcAddress("glLinkProgramARB"); glCompileShaderARB = (PFNGLCOMPILESHADERARBPROC)wglGetProcAddress("glCompileShaderARB"); glShaderSourceARB = (PFNGLSHADERSOURCEARBPROC)wglGetProcAddress("glShaderSourceARB"); glUseProgramObjectARB = (PFNGLUSEPROGRAMOBJECTARBPROC)wglGetProcAddress("glUseProgramObjectARB"); glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress("glActiveTextureARB"); glGetUniformLocationARB = (PFNGLGETUNIFORMLOCATIONARBPROC)wglGetProcAddress("glGetUniformLocationARB"); glUniform1iARB = (PFNGLUNIFORM1IARBPROC)wglGetProcAddress("glUniform1iARB"); glUniform1fARB = (PFNGLUNIFORM1FARBPROC)wglGetProcAddress("glUniform1fARB"); glUniformMatrix3fvARB = (PFNGLUNIFORMMATRIX3FVARBPROC)wglGetProcAddress("glUniformMatrix3fvARB"); glUniformMatrix4fvARB = (PFNGLUNIFORMMATRIX4FVARBPROC)wglGetProcAddress("glUniformMatrix4fvARB"); glUniform4fvARB = (PFNGLUNIFORM4FVARBPROC)wglGetProcAddress("glUniform4fvARB"); glUniform2fvARB = (PFNGLUNIFORM2FVARBPROC)wglGetProcAddress("glUniform2fvARB"); glBindAttribLocationARB = (PFNGLBINDATTRIBLOCATIONARBPROC)wglGetProcAddress("glBindAttribLocationARB"); glEnableVertexAttribArrayARB = (PFNGLENABLEVERTEXATTRIBARRAYARBPROC)wglGetProcAddress("glEnableVertexAttribArrayARB"); glVertexAttribPointerARB = (PFNGLVERTEXATTRIBPOINTERARBPROC)wglGetProcAddress("glVertexAttribPointerARB"); glDisableVertexAttribArrayARB = (PFNGLDISABLEVERTEXATTRIBARRAYARBPROC)wglGetProcAddress("glDisableVertexAttribArrayARB"); glValidateProgramARB = (PFNGLVALIDATEPROGRAMARBPROC)wglGetProcAddress("glValidateProgramARB"); #endif glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS_ARB, &textureImageUnitCount); shadersAvailable = YES; } #endif #if OO_USE_VBO - (void)checkVBOSupported { vboSupported = NO; if ([self versionIsAtLeastMajor:1 minor:5] || [self haveExtension:@"GL_ARB_vertex_buffer_object"]) { vboSupported = YES; } #if OOLITE_WINDOWS if (vboSupported) { glGenBuffersARB = (PFNGLGENBUFFERSARBPROC)wglGetProcAddress("glGenBuffersARB"); glDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC)wglGetProcAddress("glDeleteBuffersARB"); glBindBufferARB = (PFNGLBINDBUFFERARBPROC)wglGetProcAddress("glBindBufferARB"); glBufferDataARB = (PFNGLBUFFERDATAARBPROC)wglGetProcAddress("glBufferDataARB"); } #endif } #endif #if OO_USE_FBO - (void)checkFBOSupported { fboSupported = NO; if ([self haveExtension:@"GL_EXT_framebuffer_object"]) { fboSupported = YES; } #if OOLITE_WINDOWS if (fboSupported) { glGenFramebuffersEXT = (PFNGLGENFRAMEBUFFERSEXTPROC)wglGetProcAddress("glGenFramebuffersEXT"); glBindFramebufferEXT = (PFNGLBINDFRAMEBUFFEREXTPROC)wglGetProcAddress("glBindFramebufferEXT"); glGenRenderbuffersEXT = (PFNGLGENRENDERBUFFERSEXTPROC)wglGetProcAddress("glGenRenderbuffersEXT"); glBindRenderbufferEXT = (PFNGLBINDRENDERBUFFEREXTPROC)wglGetProcAddress("glBindRenderbufferEXT"); glRenderbufferStorageEXT = (PFNGLRENDERBUFFERSTORAGEEXTPROC)wglGetProcAddress("glRenderbufferStorageEXT"); glFramebufferRenderbufferEXT = (PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC)wglGetProcAddress("glFramebufferRenderbufferEXT"); glFramebufferTexture2DEXT = (PFNGLFRAMEBUFFERTEXTURE2DEXTPROC)wglGetProcAddress("glFramebufferTexture2DEXT"); glCheckFramebufferStatusEXT = (PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC)wglGetProcAddress("glCheckFramebufferStatusEXT"); glDeleteFramebuffersEXT = (PFNGLDELETEFRAMEBUFFERSEXTPROC)wglGetProcAddress("glDeleteFramebuffersEXT"); glDeleteRenderbuffersEXT = (PFNGLDELETERENDERBUFFERSEXTPROC)wglGetProcAddress("glDeleteRenderbuffersEXT"); } #endif } #endif #if OO_MULTITEXTURE - (void)checkTextureCombinersSupported { textureCombinersSupported = [self haveExtension:@"GL_ARB_texture_env_combine"]; if (textureCombinersSupported) { OOGL(glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &textureUnitCount)); #if OOLITE_WINDOWS // Duplicated in checkShadersSupported. but that's not really a problem. glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress("glActiveTextureARB"); glClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC)wglGetProcAddress("glClientActiveTextureARB"); #endif } else { textureUnitCount = 1; } } #endif // regexps may be a single string or an array of strings (in which case results are ANDed). static BOOL CheckRegExps(NSString *string, id regexps) { if (regexps == nil) return YES; // No restriction == match. if ([regexps isKindOfClass:[NSString class]]) { return [string oo_matchesRegularExpression:regexps]; } if ([regexps isKindOfClass:[NSArray class]]) { NSEnumerator *regexpEnum = nil; NSString *regexp = nil; for (regexpEnum = [regexps objectEnumerator]; (regexp = [regexpEnum nextObject]); ) { if (EXPECT_NOT(![regexp isKindOfClass:[NSString class]])) { // Invalid type -- match fails. return NO; } if (![string oo_matchesRegularExpression:regexp]) return NO; } return YES; } // Invalid type -- match fails. return NO; } NSComparisonResult CompareGPUSettingsByPriority(id a, id b, void *context) { NSString *keyA = a; NSString *keyB = b; NSDictionary *configurations = context; NSDictionary *dictA = [configurations oo_dictionaryForKey:keyA]; NSDictionary *dictB = [configurations oo_dictionaryForKey:keyB]; double precedenceA = [dictA oo_doubleForKey:@"precedence" defaultValue:1]; double precedenceB = [dictB oo_doubleForKey:@"precedence" defaultValue:1]; if (precedenceA > precedenceB) return NSOrderedAscending; if (precedenceA < precedenceB) return NSOrderedDescending; return [keyA caseInsensitiveCompare:keyB]; } - (NSDictionary *) lookUpPerGPUSettingsWithVersionString:(NSString *)versionStr extensionsString:(NSString *)extensionsStr { NSDictionary *configurations = [ResourceManager dictionaryFromFilesNamed:@"gpu-settings.plist" inFolder:@"Config" andMerge:YES]; NSArray *keys = [[configurations allKeys] sortedArrayUsingFunction:CompareGPUSettingsByPriority context:configurations]; NSEnumerator *keyEnum = nil; NSString *key = nil; NSDictionary *config = nil; for (keyEnum = [keys objectEnumerator]; (key = [keyEnum nextObject]); ) { config = [configurations oo_dictionaryForKey:key]; if (EXPECT_NOT(config == nil)) continue; NSDictionary *match = [config oo_dictionaryForKey:@"match"]; NSString *expr = nil; expr = [match objectForKey:@"vendor"]; if (!CheckRegExps(vendor, expr)) continue; expr = [match oo_stringForKey:@"renderer"]; if (!CheckRegExps(renderer, expr)) continue; expr = [match oo_stringForKey:@"version"]; if (!CheckRegExps(versionStr, expr)) continue; expr = [match oo_stringForKey:@"extensions"]; if (!CheckRegExps(extensionsStr, expr)) continue; OOLog(@"rendering.opengl.gpuSpecific", @"Matched GPU configuration \"%@\".", key); return config; } return [NSDictionary dictionary]; } @end @implementation OOOpenGLExtensionManager (Singleton) /* Canonical singleton boilerplate. See Cocoa Fundamentals Guide: Creating a Singleton Instance. See also +sharedManager above. // NOTE: assumes single-threaded first access. */ + (id)allocWithZone:(NSZone *)inZone { if (sSingleton == nil) { sSingleton = [super allocWithZone:inZone]; return sSingleton; } return nil; } - (id)copyWithZone:(NSZone *)inZone { return self; } - (id)retain { return self; } - (NSUInteger)retainCount { return UINT_MAX; } - (void)release {} - (id)autorelease { return self; } @end #if OOLITE_WINDOWS static void OOBadOpenGLExtensionUsed(void) { OOLog(@"rendering.opengl.badExtension", @"***** An uninitialized OpenGL extension function has been called, terminating. This is a serious error, please report it. *****"); exit(EXIT_FAILURE); } #endif oolite-1.82/src/Core/OOOpenGLMatrixManager.h000066400000000000000000000102171256642440500206420ustar00rootroot00000000000000/* OOOpenGLMatrixManager.h Manages OpenGL Model, View, etc. matrices. Oolite Copyright (C) 2004-2014 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOMaths.h" extern const char* ooliteStandardMatrixUniforms[]; enum { OOLITE_GL_MATRIX_MODELVIEW, OOLITE_GL_MATRIX_PROJECTION, OOLITE_GL_MATRIX_MODELVIEW_PROJECTION, OOLITE_GL_MATRIX_NORMAL, OOLITE_GL_MATRIX_MODELVIEW_INVERSE, OOLITE_GL_MATRIX_PROJECTION_INVERSE, OOLITE_GL_MATRIX_MODELVIEW_PROJECTION_INVERSE, OOLITE_GL_MATRIX_MODELVIEW_TRANSPOSE, OOLITE_GL_MATRIX_PROJECTION_TRANSPOSE, OOLITE_GL_MATRIX_MODELVIEW_PROJECTION_TRANSPOSE, OOLITE_GL_MATRIX_MODELVIEW_INVERSE_TRANSPOSE, OOLITE_GL_MATRIX_PROJECTION_INVERSE_TRANSPOSE, OOLITE_GL_MATRIX_MODELVIEW_PROJECTION_INVERSE_TRANSPOSE, OOLITE_GL_MATRIX_END }; @interface OOOpenGLMatrixStack: NSObject { @private NSMutableArray *stack; } - (id) init; - (void) dealloc; - (void) push: (OOMatrix) matrix; - (OOMatrix) pop; - (unsigned int) stackCount; @end @interface OOOpenGLMatrixManager: NSObject { @private OOMatrix matrices[OOLITE_GL_MATRIX_END]; BOOL valid[OOLITE_GL_MATRIX_END]; OOOpenGLMatrixStack *modelViewStack; OOOpenGLMatrixStack *projectionStack; } - (id) init; - (void) dealloc; - (void) loadModelView: (OOMatrix) matrix; - (void) resetModelView; - (void) multModelView: (OOMatrix) matrix; - (void) translateModelView: (Vector) vector; - (void) rotateModelView: (GLfloat) angle axis: (Vector) axis; - (void) scaleModelView: (Vector) scale; - (void) lookAtWithEye: (Vector) eye center: (Vector) center up: (Vector) up; - (void) pushModelView; - (OOMatrix) popModelView; - (OOMatrix) getModelView; - (unsigned int) countModelView; - (void) syncModelView; - (void) loadProjection: (OOMatrix) matrix; - (void) multProjection: (OOMatrix) matrix; - (void) translateProjection: (Vector) vector; - (void) rotateProjection: (GLfloat) angle axis: (Vector) axis; - (void) scaleProjection: (Vector) scale; - (void) frustumLeft: (double) l right: (double) r bottom: (double) b top: (double) t near: (double) n far: (double) f; - (void) orthoLeft: (double) l right: (double) r bottom: (double) b top: (double) t near: (double) n far: (double) f; - (void) perspectiveFovy: (double) fovy aspect: (double) aspect zNear: (double) zNear zFar: (double) zFar; - (void) resetProjection; - (void) pushProjection; - (OOMatrix) popProjection; - (OOMatrix) getProjection; - (void) syncProjection; - (OOMatrix) getMatrix: (int) which; - (NSArray*) standardMatrixUniformLocations: (GLuint) program; @end void OOGLPushModelView(); OOMatrix OOGLPopModelView(); OOMatrix OOGLGetModelView(); void OOGLResetModelView(); void OOGLLoadModelView(OOMatrix matrix); void OOGLMultModelView(OOMatrix matrix); void OOGLTranslateModelView(Vector vector); void OOGLRotateModelView(GLfloat angle, Vector axis); void OOGLScaleModelView(Vector scale); void OOGLLookAt(Vector eye, Vector center, Vector up); void OOGLResetProjection(); void OOGLPushProjection(); OOMatrix OOGLPopProjection(); OOMatrix OOGLGetProjection(); void OOGLLoadProjection(OOMatrix matrix); void OOGLMultProjection(OOMatrix matrix); void OOGLTranslateProjection(Vector vector); void OOGLRotateProjection(GLfloat angle, Vector axis); void OOGLScaleProjection(Vector scale); void OOGLFrustum(double left, double right, double bottom, double top, double near, double far); void OOGLOrtho(double left, double right, double bottom, double top, double near, double far); void OOGLPerspective(double fovy, double aspect, double zNear, double zFar); OOMatrix OOGLGetModelViewProjection(); oolite-1.82/src/Core/OOOpenGLMatrixManager.m000066400000000000000000000374741256642440500206650ustar00rootroot00000000000000/* OOOpenGLMatrixManager.m Manages OpenGL Model, View, etc. matrices. Oolite Copyright (C) 2004-2014 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOOpenGLExtensionManager.h" #import "OOOpenGLMatrixManager.h" #import "MyOpenGLView.h" #import "Universe.h" #import "OOMacroOpenGL.h" const char* ooliteStandardMatrixUniforms[] = { "ooliteModelView", "ooliteProjection", "ooliteModelViewProjection", "ooliteNormalMatrix", "ooliteModelViewInverse", "ooliteProjectionInverse", "ooliteModelViewProjectionInverse", "ooliteModelViewTranspose", "ooliteProjectionTranspose", "ooliteModelViewProjectionTranspose", "ooliteModelViewInverseTranspose", "ooliteProjectionInverseTranspose", "ooliteModelViewProjectionInverseTranspose" }; @implementation OOOpenGLMatrixStack - (id) init { if ((self = [super init])) { stack = [[NSMutableArray alloc] init]; } return self; } - (void) dealloc { [stack release]; [super dealloc]; } - (void) push: (OOMatrix) matrix { [stack addObject: [NSValue valueWithBytes: &matrix objCType: @encode(OOMatrix)]]; } - (OOMatrix) pop { if ([stack count] == 0) { return kIdentityMatrix; } OOMatrix matrix; [[stack lastObject] getValue: &matrix]; [stack removeLastObject]; return matrix; } - (unsigned int) stackCount { return [stack count]; } @end @interface OOOpenGLMatrixManager(Private) - (void) updateModelView; - (void) updateProjection; @end @implementation OOOpenGLMatrixManager(Private) - (void) updateModelView { valid[OOLITE_GL_MATRIX_MODELVIEW_PROJECTION] = NO; valid[OOLITE_GL_MATRIX_NORMAL] = NO; valid[OOLITE_GL_MATRIX_MODELVIEW_INVERSE] = NO; valid[OOLITE_GL_MATRIX_PROJECTION_INVERSE] = NO; valid[OOLITE_GL_MATRIX_MODELVIEW_PROJECTION_INVERSE] = NO; valid[OOLITE_GL_MATRIX_MODELVIEW_TRANSPOSE] = NO; valid[OOLITE_GL_MATRIX_PROJECTION_TRANSPOSE] = NO; valid[OOLITE_GL_MATRIX_MODELVIEW_PROJECTION_TRANSPOSE] = NO; valid[OOLITE_GL_MATRIX_MODELVIEW_INVERSE_TRANSPOSE] = NO; valid[OOLITE_GL_MATRIX_PROJECTION_INVERSE_TRANSPOSE] = NO; valid[OOLITE_GL_MATRIX_MODELVIEW_PROJECTION_INVERSE_TRANSPOSE] = NO; } - (void) updateProjection { valid[OOLITE_GL_MATRIX_MODELVIEW_PROJECTION] = NO; valid[OOLITE_GL_MATRIX_PROJECTION_INVERSE] = NO; valid[OOLITE_GL_MATRIX_MODELVIEW_PROJECTION_INVERSE] = NO; valid[OOLITE_GL_MATRIX_PROJECTION_TRANSPOSE] = NO; valid[OOLITE_GL_MATRIX_MODELVIEW_PROJECTION_TRANSPOSE] = NO; valid[OOLITE_GL_MATRIX_PROJECTION_INVERSE_TRANSPOSE] = NO; valid[OOLITE_GL_MATRIX_MODELVIEW_PROJECTION_INVERSE_TRANSPOSE] = NO; } @end @implementation OOOpenGLMatrixManager - (id) init { if ((self = [super init])) { int i; for (i = 0; i < OOLITE_GL_MATRIX_END; i++) { switch(i) { case OOLITE_GL_MATRIX_MODELVIEW: case OOLITE_GL_MATRIX_PROJECTION: matrices[i] = kIdentityMatrix; valid[i] = YES; break; default: valid[i] = NO; break; } } modelViewStack = [[OOOpenGLMatrixStack alloc] init]; projectionStack = [[OOOpenGLMatrixStack alloc] init]; } return self; } - (void) dealloc { [modelViewStack release]; [projectionStack release]; [super dealloc]; } - (void) loadModelView: (OOMatrix) matrix { matrices[OOLITE_GL_MATRIX_MODELVIEW] = matrix; [self updateModelView]; return; } - (void) resetModelView { matrices[OOLITE_GL_MATRIX_MODELVIEW] = kIdentityMatrix; [self updateModelView]; } - (void) multModelView: (OOMatrix) matrix { matrices[OOLITE_GL_MATRIX_MODELVIEW] = OOMatrixMultiply(matrix, matrices[OOLITE_GL_MATRIX_MODELVIEW]); [self updateModelView]; } - (void) translateModelView: (Vector) vector { OOMatrix matrix = kIdentityMatrix; matrix.m[3][0] = vector.x; matrix.m[3][1] = vector.y; matrix.m[3][2] = vector.z; [self multModelView: matrix]; } - (void) rotateModelView: (GLfloat) angle axis: (Vector) axis { [self multModelView: OOMatrixForRotation(axis, angle)]; } - (void) scaleModelView: (Vector) scale { [self multModelView: OOMatrixForScale(scale.x, scale.y, scale.z)]; } - (void) lookAtWithEye: (Vector) eye center: (Vector) center up: (Vector) up { Vector k = vector_normal(vector_subtract(eye, center)); Vector i = cross_product(up, k); Vector j = cross_product(k, i); OOMatrix m1 = OOMatrixConstruct ( i.x, j.x, k.x, 0.0, i.y, j.y, k.y, 0.0, i.z, j.z, k.z, 0.0, -eye.x, -eye.y, -eye.z, 1.0 ); [self multModelView: m1]; return; } - (void) pushModelView { [modelViewStack push: matrices[OOLITE_GL_MATRIX_MODELVIEW]]; } - (OOMatrix) popModelView { matrices[OOLITE_GL_MATRIX_MODELVIEW] = [modelViewStack pop]; [self updateModelView]; return matrices[OOLITE_GL_MATRIX_MODELVIEW]; } - (OOMatrix) getModelView { return matrices[OOLITE_GL_MATRIX_MODELVIEW]; } - (unsigned int) countModelView { return [modelViewStack stackCount]; } - (void) syncModelView { OO_ENTER_OPENGL(); OOGL(glMatrixMode(GL_MODELVIEW)); GLLoadOOMatrix([self getModelView]); return; } - (void) loadProjection: (OOMatrix) matrix { matrices[OOLITE_GL_MATRIX_PROJECTION] = matrix; [self updateProjection]; return; } - (void) multProjection: (OOMatrix) matrix { matrices[OOLITE_GL_MATRIX_PROJECTION] = OOMatrixMultiply(matrix, matrices[OOLITE_GL_MATRIX_PROJECTION]); [self updateProjection]; } - (void) translateProjection: (Vector) vector { OOMatrix matrix = kIdentityMatrix; matrix.m[0][3] = vector.x; matrix.m[1][3] = vector.y; matrix.m[2][3] = vector.z; [self multProjection: matrix]; } - (void) rotateProjection: (GLfloat) angle axis: (Vector) axis { [self multProjection: OOMatrixForRotation(axis, angle)]; } - (void) scaleProjection: (Vector) scale { [self multProjection: OOMatrixForScale(scale.x, scale.y, scale.z)]; } - (void) frustumLeft: (double) l right: (double) r bottom: (double) b top: (double) t near: (double) n far: (double) f { if (l == r || t == b || n == f || n <= 0 || f <= 0) return; [self multProjection: OOMatrixConstruct ( 2*n/(r-l), 0.0, 0.0, 0.0, 0.0, 2*n/(t-b), 0.0, 0.0, (r+l)/(r-l), (t+b)/(t-b), -(f+n)/(f-n), -1.0, 0.0, 0.0, -2*f*n/(f-n), 0.0 )]; } - (void) orthoLeft: (double) l right: (double) r bottom: (double) b top: (double) t near: (double) n far: (double) f { if (l == r || t == b || n == f) return; [self multProjection: OOMatrixConstruct ( 2/(r-l), 0.0, 0.0, 0.0, 0.0, 2/(t-b), 0.0, 0.0, 0.0, 0.0, 2/(n-f), 0.0, (l+r)/(l-r), (b+t)/(b-t), (n+f)/(n-f), 1.0 )]; } - (void) perspectiveFovy: (double) fovy aspect: (double) aspect zNear: (double) zNear zFar: (double) zFar { if (aspect == 0.0 || zNear == zFar) return; double f = 1.0/tan(M_PI * fovy / 360); [self multProjection: OOMatrixConstruct ( f/aspect, 0.0, 0.0, 0.0, 0.0, f, 0.0, 0.0, 0.0, 0.0, (zFar + zNear)/(zNear - zFar), -1.0, 0.0, 0.0, 2*zFar*zNear/(zNear - zFar), 0.0 )]; } - (void) resetProjection { matrices[OOLITE_GL_MATRIX_PROJECTION] = kIdentityMatrix; [self updateProjection]; } - (void) pushProjection { [projectionStack push: matrices[OOLITE_GL_MATRIX_PROJECTION]]; } - (OOMatrix) popProjection { matrices[OOLITE_GL_MATRIX_PROJECTION] = [projectionStack pop]; [self updateProjection]; return matrices[OOLITE_GL_MATRIX_PROJECTION]; } - (OOMatrix) getProjection { return matrices[OOLITE_GL_MATRIX_PROJECTION]; } - (void) syncProjection { OO_ENTER_OPENGL(); OOGL(glMatrixMode(GL_PROJECTION)); GLLoadOOMatrix([self getProjection]); return; } - (OOMatrix) getMatrix: (int) which { if (which < 0 || which >= OOLITE_GL_MATRIX_END) return kIdentityMatrix; if (valid[which]) return matrices[which]; OOScalar d; switch(which) { case OOLITE_GL_MATRIX_MODELVIEW_PROJECTION: matrices[which] = OOMatrixMultiply(matrices[OOLITE_GL_MATRIX_MODELVIEW], matrices[OOLITE_GL_MATRIX_PROJECTION]); break; case OOLITE_GL_MATRIX_NORMAL: matrices[which] = matrices[OOLITE_GL_MATRIX_MODELVIEW]; matrices[which].m[3][0] = 0.0; matrices[which].m[3][1] = 0.0; matrices[which].m[3][2] = 0.0; matrices[which].m[0][3] = 0.0; matrices[which].m[1][3] = 0.0; matrices[which].m[2][3] = 0.0; matrices[which].m[3][3] = 1.0; matrices[which] = OOMatrixTranspose(OOMatrixInverseWithDeterminant(matrices[which], &d)); if (d != 0.0) { d = pow(fabs(d), 1.0/3); for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { matrices[which].m[i][j] /= d; } } } break; case OOLITE_GL_MATRIX_MODELVIEW_INVERSE: matrices[which] = OOMatrixInverse(matrices[OOLITE_GL_MATRIX_MODELVIEW]); break; case OOLITE_GL_MATRIX_PROJECTION_INVERSE: matrices[which] = OOMatrixInverse(matrices[OOLITE_GL_MATRIX_PROJECTION]); break; case OOLITE_GL_MATRIX_MODELVIEW_PROJECTION_INVERSE: matrices[which] = OOMatrixInverse([self getMatrix: OOLITE_GL_MATRIX_MODELVIEW_PROJECTION]); break; case OOLITE_GL_MATRIX_MODELVIEW_TRANSPOSE: matrices[which] = OOMatrixTranspose(matrices[OOLITE_GL_MATRIX_MODELVIEW]); break; case OOLITE_GL_MATRIX_PROJECTION_TRANSPOSE: matrices[which] = OOMatrixTranspose(matrices[OOLITE_GL_MATRIX_PROJECTION]); break; case OOLITE_GL_MATRIX_MODELVIEW_PROJECTION_TRANSPOSE: matrices[which] = OOMatrixTranspose([self getMatrix: OOLITE_GL_MATRIX_MODELVIEW_PROJECTION]); break; case OOLITE_GL_MATRIX_MODELVIEW_INVERSE_TRANSPOSE: matrices[which] = OOMatrixTranspose([self getMatrix: OOLITE_GL_MATRIX_MODELVIEW_INVERSE]); break; case OOLITE_GL_MATRIX_PROJECTION_INVERSE_TRANSPOSE: matrices[which] = OOMatrixTranspose([self getMatrix: OOLITE_GL_MATRIX_PROJECTION_INVERSE]); break; case OOLITE_GL_MATRIX_MODELVIEW_PROJECTION_INVERSE_TRANSPOSE: matrices[which] = OOMatrixTranspose([self getMatrix: OOLITE_GL_MATRIX_MODELVIEW_PROJECTION_INVERSE]); break; } valid[which] = YES; return matrices[which]; } - (NSArray*) standardMatrixUniformLocations: (GLuint) program { GLint location; NSUInteger i; NSMutableArray *locationSet = [[[NSMutableArray alloc] init] autorelease]; OO_ENTER_OPENGL(); for (i = 0; i < OOLITE_GL_MATRIX_END; i++) { location = glGetUniformLocationARB(program, ooliteStandardMatrixUniforms[i]); if (location >= 0) { if (i == OOLITE_GL_MATRIX_NORMAL) { [locationSet addObject: [NSArray arrayWithObjects: [NSNumber numberWithInt: location], [NSNumber numberWithInt: i], @"mat3", nil]]; } else { [locationSet addObject: [NSArray arrayWithObjects: [NSNumber numberWithInt: location], [NSNumber numberWithInt: i], @"mat4", nil]]; } } } return [[NSArray arrayWithArray: locationSet] retain]; } @end void OOGLPushModelView() { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; [matrixManager pushModelView]; [matrixManager syncModelView]; } OOMatrix OOGLPopModelView() { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; OOMatrix matrix = [matrixManager popModelView]; [matrixManager syncModelView]; return matrix; } OOMatrix OOGLGetModelView() { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; OOMatrix matrix = [matrixManager getModelView]; return matrix; } void OOGLResetModelView() { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; [matrixManager resetModelView]; [matrixManager syncModelView]; } void OOGLLoadModelView(OOMatrix matrix) { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; [matrixManager loadModelView: matrix]; [matrixManager syncModelView]; } void OOGLMultModelView(OOMatrix matrix) { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; [matrixManager multModelView: matrix]; [matrixManager syncModelView]; } void OOGLTranslateModelView(Vector vector) { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; [matrixManager translateModelView: vector]; [matrixManager syncModelView]; } void OOGLRotateModelView(GLfloat angle, Vector axis) { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; [matrixManager rotateModelView: angle axis: axis]; [matrixManager syncModelView]; } void OOGLScaleModelView(Vector scale) { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; [matrixManager scaleModelView: scale]; [matrixManager syncModelView]; } void OOGLLookAt(Vector eye, Vector center, Vector up) { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; [matrixManager lookAtWithEye: eye center: center up: up]; [matrixManager syncModelView]; } void OOGLResetProjection() { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; [matrixManager resetProjection]; [matrixManager syncProjection]; } void OOGLPushProjection() { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; [matrixManager pushProjection]; [matrixManager syncProjection]; } OOMatrix OOGLPopProjection() { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; OOMatrix matrix = [matrixManager popProjection]; [matrixManager syncProjection]; return matrix; } OOMatrix OOGLGetProjection() { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; OOMatrix matrix = [matrixManager getProjection]; return matrix; } void OOGLLoadProjection(OOMatrix matrix) { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; [matrixManager loadProjection: matrix]; [matrixManager syncProjection]; } void OOGLMultProjection(OOMatrix matrix) { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; [matrixManager multProjection: matrix]; [matrixManager syncProjection]; } void OOGLTranslateProjection(Vector vector) { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; [matrixManager translateProjection: vector]; [matrixManager syncProjection]; } void OOGLRotateProjection(GLfloat angle, Vector axis) { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; [matrixManager rotateProjection: angle axis: axis]; [matrixManager syncProjection]; } void OOGLScaleProjection(Vector scale) { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; [matrixManager scaleProjection: scale]; [matrixManager syncProjection]; } void OOGLFrustum(double frleft, double frright, double frbottom, double frtop, double frnear, double frfar) { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; [matrixManager frustumLeft: frleft right: frright bottom: frbottom top: frtop near: frnear far: frfar]; [matrixManager syncProjection]; } void OOGLOrtho(double orleft, double orright, double orbottom, double ortop, double ornear, double orfar) { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; [matrixManager orthoLeft: orleft right: orright bottom: orbottom top: ortop near: ornear far: orfar]; [matrixManager syncProjection]; } void OOGLPerspective(double fovy, double aspect, double zNear, double zFar) { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; [matrixManager perspectiveFovy: fovy aspect: aspect zNear: zNear zFar: zFar]; [matrixManager syncProjection]; } OOMatrix OOGLGetModelViewProjection() { OOOpenGLMatrixManager *matrixManager = [[UNIVERSE gameView] getOpenGLMatrixManager]; return [matrixManager getMatrix: OOLITE_GL_MATRIX_MODELVIEW_PROJECTION]; } oolite-1.82/src/Core/OOOpenGLOnly.h000066400000000000000000000034211256642440500170230ustar00rootroot00000000000000/* OOOpenGL.h Do whatever is appropriate to get gl.h, glu.h and glext.h included. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef OOLITE_SDL #if (!OOLITE_MAC_OS_X && GNUSTEP) && !defined(OOLITE_SDL_MAC) #define OOLITE_SDL 1 #endif #endif #if OOLITE_MAC_OS_X // Apple OpenGL includes... #include #include #include #include #elif OOLITE_SDL // SDL OpenGL includes... // prevent the including of SDL_opengl.h loading a previous version of glext.h #define NO_SDL_GLEXT // GL_GLEXT_PROTOTYPES must be defined for the Linux build to use shaders. #if OOLITE_LINUX #ifndef GL_GLEXT_PROTOTYPES #define GL_GLEXT_PROTOTYPES #define __DEFINED_GL_GLEXT_PROTOTYPES #endif // GL_GLEXT_PROTOTYPES #endif // OOLITE_LINUX && !OOLITE_WINDOWS // the standard SDL_opengl.h #include // include an up-to-date version of glext.h #include #ifdef __DEFINED_GL_GLEXT_PROTOTYPES #undef GL_GLEXT_PROTOTYPES #undef __DEFINED_GL_GLEXT_PROTOTYPES #endif #else // Not OS X or SDL #error OOOpenGL.h: unknown target! #endif oolite-1.82/src/Core/OOOpenGLStateManager.m000066400000000000000000000437011256642440500204670ustar00rootroot00000000000000/* OOOpenGLStateManager.m Implementation of OOSetOpenGLState()/OOVerifyOpenGLState(). Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOOpenGL.h" #import "OOLogging.h" #import "OOMaths.h" #import "OOMacroOpenGL.h" #import "OOFunctionAttributes.h" #import "OOOpenGLExtensionManager.h" /* DESIGN NOTES The state manager is heavily based on macro metaprogramming, to avoid copy- and-paste errors. The state it manages is defined in OOOpenGLStates.tbl, which is included each time something needs to be done for each state item (currently six times in total). The exception is the GL blend mode, which is represented by two variables but set with a single glBlendFunc() call; this needs to be managed separately. For the meanings of the different ITEM_FOO macro used, see OOOpenGLStates.tbl. The states are defined as structs but referred to by index so that the definitions are all in one place. This somewhat reduces the chance of missing one when updating OOOpenGLStates.tbl. The actual definitions are at the bottom of this file. */ typedef enum { kStateFalse = false, kStateTrue = true, kStateMaybe } StateFlag; typedef struct { const char *name; #define ITEM_STATEFLAG(NAME) StateFlag NAME #define ITEM_CLIENTSTATEFLAG(NAME) bool NAME #define ITEM_SPECIAL(NAME, TYPE, _) TYPE NAME #define ITEM_INT(NAME) GLint NAME #include "OOOpenGLStates.tbl" // These require extra-special handling, because they're set with one function. GLint BLEND_SRC; GLint BLEND_DST; #undef ITEM_STATEFLAG #undef ITEM_CLIENTSTATEFLAG #undef ITEM_SPECIAL #undef ITEM_INT } OOOpenGLState; static const OOOpenGLState kStandardStates[OPENGL_STATE_INTERNAL_USE_ONLY + 1]; static OOOpenGLStateID sCurrentStateID = OPENGL_STATE_INTERNAL_USE_ONLY; /* SwitchOpenGLStateInternal(sourceState, targetState) Applies the differences between sourceState and targetState. It is assumed that sourceState accurately reflects the current state. */ static void SwitchOpenGLStateInternal(const OOOpenGLState *sourceState, const OOOpenGLState *targetState) NONNULL_FUNC; /* Accessors These functions and macros are used to read and write ITEM_SPECIAL state items. The GetState_ accessors are used only in debug mode, while the SetState_ acccessors are used in either mode. */ #ifndef NDEBUG static inline bool GetState_DEPTH_WRITEMASK(void) { OO_ENTER_OPENGL(); GLboolean value; OOGL(glGetBooleanv(GL_DEPTH_WRITEMASK, &value)); return value; } #endif #define SetState_DEPTH_WRITEMASK(VALUE) OOGL(glDepthMask(VALUE)) #define SetState_SHADE_MODEL(VALUE) OOGL(glShadeModel(VALUE)) #ifndef NDEBUG static inline GLenum GetState_TEXTURE_ENV_MODE(void) { OO_ENTER_OPENGL(); GLint value; OOGL(glGetTexEnviv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &value)); return value; } #endif #define SetState_TEXTURE_ENV_MODE(VALUE) OOGL(glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, VALUE)) #if OO_MULTITEXTURE #ifndef NDEBUG static inline GLenum GetState_ACTIVE_TEXTURE(void) { OO_ENTER_OPENGL(); GLint value; OOGL(glGetIntegerv(GL_ACTIVE_TEXTURE_ARB, &value)); return value; } #endif #define SetState_ACTIVE_TEXTURE(VALUE) OOGL(glActiveTextureARB(VALUE)) #ifndef NDEBUG static inline GLenum GetState_CLIENT_ACTIVE_TEXTURE(void) { OO_ENTER_OPENGL(); GLint value; OOGL(glGetIntegerv(GL_CLIENT_ACTIVE_TEXTURE_ARB, &value)); return value; } #endif #define SetState_CLIENT_ACTIVE_TEXTURE(VALUE) OOGL(glClientActiveTextureARB(VALUE)) #else static inline GLenum GetState_ACTIVE_TEXTURE(void) {} static inline void SetState_ACTIVE_TEXTURE(GLenum value) {} static inline GLenum GetState_CLIENT_ACTIVE_TEXTURE(void) {} static inline void SetState_CLIENT_ACTIVE_TEXTURE(GLenum value) {} #endif static inline void SetState_CULL_FACE_MODE(GLint value) { OO_ENTER_OPENGL(); OOGL(glCullFace(value)); } #define SetState_FRONT_FACE(VALUE) OOGL(glFrontFace(VALUE)) #if OO_GL_STATE_VERIFICATION /* Debug mode implementation. */ static NSString * const kOOLogOpenGLVerifyDump = @"rendering.opengl.state"; /* VerifyOpenGLStateInternal(caller, nominalCaller, line) Tests whether the current OpenGL state matches the last set nominal state. If not, it logs the differences, then reverts to the last set nominal state (using SwitchOpenGLStateInternal()). */ static void VerifyOpenGLStateInternal(const char *caller, const char *nominalCaller, unsigned line) NONNULL_FUNC; /* GetCurrentOpenGLState(state) Retrieves the current OpenGL state. */ static void GetCurrentOpenGLState(OOOpenGLState *state) NONNULL_FUNC; /* StatesEqual(a, b) Test whether two states are identical. */ static bool StatesEqual(const OOOpenGLState *a, const OOOpenGLState *b) NONNULL_FUNC; #if OO_CHECK_GL_HEAVY /* OOGLNoteCurrentFunction(function, line) If OO_GL_STATE_VERIFICATION && OO_CHECK_GL_HEAVY, OOGL() calls OOGLNoteCurrentFunction() to help us keep track of where OpenGL calls have been made recently. The penultimate seen function is logged when we come across a state error, which is occasionally actually helpful. */ static const char *sLatestFunction = ""; static unsigned sLatestLine; static const char *sPreviousFunction = ""; static unsigned sPreviousLine; static bool sGLFunctionTracking = true; void OOGLNoteCurrentFunction(const char *function, unsigned line) { if (sGLFunctionTracking) { if (function != sLatestFunction) { sPreviousFunction = sLatestFunction; sPreviousLine = sLatestLine; sLatestFunction = function; } sLatestLine = line; } } /* SetFunctionTracking() Enable or disable OOGLNoteCurrentFunction(). It is disabled within the state manager implementation. */ static inline void SetFunctionTracking(bool value) { sGLFunctionTracking = value; } #else static inline void OOGLNoteCurrentFunction(const char *function, unsigned line) {} static inline void SetFunctionTracking(bool value) {} #endif void OOSetOpenGLState_(OOOpenGLStateID state, const char *function, unsigned line) { NSCParameterAssert((unsigned)state < OPENGL_STATE_INTERNAL_USE_ONLY); OOGLNoteCurrentFunction(function, line); SetFunctionTracking(false); VerifyOpenGLStateInternal("OOSetOpenGLState", function, line); if (state != sCurrentStateID) { SwitchOpenGLStateInternal(&kStandardStates[sCurrentStateID], &kStandardStates[state]); sCurrentStateID = state; } SetFunctionTracking(true); } void OOVerifyOpenGLState_(const char *function, unsigned line) { OOGLNoteCurrentFunction(function, line); SetFunctionTracking(false); VerifyOpenGLStateInternal("OOVerifyOpenGLState", function, line); SetFunctionTracking(true); } static void GetCurrentOpenGLState(OOOpenGLState *state) { static const char *name = ""; NSCParameterAssert(state != NULL); OO_ENTER_OPENGL(); memset(state, 0, sizeof *state); state->name = name; #define ITEM_STATEFLAG(NAME) OOGL(state->NAME = glIsEnabled(GL_##NAME)) #define ITEM_CLIENTSTATEFLAG(NAME) OOGL(state->NAME = glIsEnabled(GL_##NAME)) #define ITEM_SPECIAL(NAME, _, __) state->NAME = GetState_##NAME() #define ITEM_INT(NAME) OOGL(glGetIntegerv(GL_##NAME, &state->NAME)) #include "OOOpenGLStates.tbl" OOGL(glGetIntegerv(GL_BLEND_SRC, &state->BLEND_SRC)); OOGL(glGetIntegerv(GL_BLEND_DST, &state->BLEND_DST)); #undef ITEM_STATEFLAG #undef ITEM_CLIENTSTATEFLAG #undef ITEM_SPECIAL #undef ITEM_INT } static bool StatesEqual(const OOOpenGLState *a, const OOOpenGLState *b) { NSCParameterAssert(a != NULL && b != NULL); #define ITEM_STATEFLAG(NAME) do { if (a->NAME != b->NAME && a->NAME != kStateMaybe && b->NAME != kStateMaybe) return false; } while (0) #define ITEM_CLIENTSTATEFLAG(NAME) do { if (a->NAME != b->NAME) return false; } while (0) #define ITEM_SPECIAL(NAME, _, __) do { if (a->NAME != b->NAME) return false; } while (0) #define ITEM_INT(NAME) do { if (a->NAME != b->NAME) return false; } while (0) #include "OOOpenGLStates.tbl" #undef ITEM_STATEFLAG #undef ITEM_CLIENTSTATEFLAG #undef ITEM_SPECIAL #undef ITEM_INT return true; } static void VerifyOpenGLStateInternal(const char *caller, const char *nominalCaller, unsigned line) { OOOpenGLState currentState; GetCurrentOpenGLState(¤tState); NSCParameterAssert(sCurrentStateID <= OPENGL_STATE_INTERNAL_USE_ONLY); const OOOpenGLState *expectedState = &kStandardStates[sCurrentStateID]; if (!StatesEqual(¤tState, expectedState)) { if (OOLogWillDisplayMessagesInClass(kOOLogOpenGLVerifyDump)) { OOLog(kOOLogOpenGLVerifyDump, @"Incorrect OpenGL state in %s (line %u)->%s", nominalCaller, line, caller); #if OO_CHECK_GL_HEAVY OOLog(kOOLogOpenGLVerifyDump, @"Previous OpenGL-using function: %s (line %u)", sPreviousFunction, sPreviousLine); #endif OOLog(kOOLogOpenGLVerifyDump, @"Expected previous state: %s", expectedState->name); OOLogIndent(); #define TEST_ITEM(NAME_, DISP_) \ if (currentState.NAME_ != expectedState->NAME_) \ { \ OOLog(kOOLogOpenGLVerifyDump, @"GL_%@ should be %@ but is %@.", @#NAME_, DISP_(expectedState->NAME_), DISP_(currentState.NAME_)); \ } #define ITEM_STATEFLAG(NAME) if (expectedState->NAME != kStateMaybe) { TEST_ITEM(NAME, OOGLFlagToString) } #define ITEM_CLIENTSTATEFLAG(NAME) TEST_ITEM(NAME, OOGLFlagToString) #define ITEM_SPECIAL(NAME, _, __) TEST_ITEM(NAME, OOGLFlagToString) #define ITEM_INT(NAME) TEST_ITEM(NAME, OOGLEnumToString) #include "OOOpenGLStates.tbl" if (currentState.BLEND_SRC != expectedState->BLEND_SRC || currentState.BLEND_DST != expectedState->BLEND_DST) { OOLog(kOOLogOpenGLVerifyDump, @"GL blend mode should be %@, %@ but is %@, %@.", OOGLEnumToString(expectedState->BLEND_SRC), OOGLEnumToString(expectedState->BLEND_DST), OOGLEnumToString(currentState.BLEND_SRC), OOGLEnumToString(currentState.BLEND_DST)); } #undef ITEM_STATEFLAG #undef ITEM_CLIENTSTATEFLAG #undef ITEM_SPECIAL #undef ITEM_INT #undef TEST_ITEM OOLogOutdent(); } SwitchOpenGLStateInternal(¤tState, expectedState); } } #else // OO_GL_STATE_VERIFICATION /* Non-debug mode implementation. OOSetOpenGLState() performs a switch from the previous nominal state to the new nominal state, without checking that the previous nominal state matches the actual state. OOVerifyOpenGLState is a do-nothing macro. */ void OOSetOpenGLState(OOOpenGLStateID state) { NSCParameterAssert((unsigned)state < OPENGL_STATE_INTERNAL_USE_ONLY); if (state != sCurrentStateID) { SwitchOpenGLStateInternal(&kStandardStates[sCurrentStateID], &kStandardStates[state]); sCurrentStateID = state; } } #endif // OO_GL_STATE_VERIFICATION static void SwitchOpenGLStateInternal(const OOOpenGLState *sourceState, const OOOpenGLState *targetState) { NSCParameterAssert(sourceState != NULL && targetState != NULL); OO_ENTER_OPENGL(); #define ITEM_STATEFLAG(NAME) \ if (sourceState->NAME != targetState->NAME && sourceState->NAME != kStateMaybe && targetState->NAME != kStateMaybe) \ { \ if (targetState->NAME) \ { \ OOGL(glEnable(GL_##NAME)); \ } \ else \ { \ OOGL(glDisable(GL_##NAME)); \ } \ } #define ITEM_CLIENTSTATEFLAG(NAME) \ if (sourceState->NAME != targetState->NAME) \ { \ if (targetState->NAME) \ { \ OOGL(glEnableClientState(GL_##NAME)); \ } \ else \ { \ OOGL(glDisableClientState(GL_##NAME)); \ } \ } #define ITEM_SPECIAL(NAME, TYPE, _) \ if (sourceState->NAME != targetState->NAME) \ { \ SetState_##NAME(targetState->NAME); \ } #define ITEM_INT(NAME) \ if (sourceState->NAME != targetState->NAME) \ { \ SetState_##NAME(targetState->NAME); \ } #include "OOOpenGLStates.tbl" /* some implementations, in non-shader mode, seem to throw out/reset * BlendFunc if Blend is disabled. Therefore, if coming from Blend=false * always reset the blend function even if this should be unnecessary * CIM: 3/1/13 */ if (sourceState->BLEND_SRC != targetState->BLEND_SRC || sourceState->BLEND_DST != targetState->BLEND_DST || !sourceState->BLEND) { OOGL(glBlendFunc(targetState->BLEND_SRC, targetState->BLEND_DST)); } #undef ITEM_STATEFLAG #undef ITEM_CLIENTSTATEFLAG #undef ITEM_SPECIAL #undef ITEM_INT } void OOResetGLStateVerifier(void) { // State has been reset behind our backs, so to speak; don't verify. sCurrentStateID = OPENGL_STATE_INTERNAL_USE_ONLY; } // The state definitions. static const OOOpenGLState kStandardStates[OPENGL_STATE_INTERNAL_USE_ONLY + 1] = { [OPENGL_STATE_INTERNAL_USE_ONLY] = { .name = "", .LIGHTING = false, .LIGHT0 = false, .LIGHT1 = false, .LIGHT2 = false, .LIGHT3 = false, .LIGHT4 = false, .LIGHT5 = false, .LIGHT6 = false, .LIGHT7 = false, .TEXTURE_2D = false, .COLOR_MATERIAL = false, .SHADE_MODEL = GL_SMOOTH, .TEXTURE_ENV_MODE = GL_MODULATE, .ACTIVE_TEXTURE = GL_TEXTURE0, .CLIENT_ACTIVE_TEXTURE = GL_TEXTURE0, .BLEND = false, .BLEND_SRC = GL_ONE, .BLEND_DST = GL_ZERO, .FOG = false, .VERTEX_ARRAY = false, .NORMAL_ARRAY = false, .COLOR_ARRAY = false, .INDEX_ARRAY = false, .TEXTURE_COORD_ARRAY = false, .EDGE_FLAG_ARRAY = false, .NORMALIZE = false, .RESCALE_NORMAL = false, .DEPTH_TEST = false, .DEPTH_WRITEMASK = true, .CULL_FACE = false, .CULL_FACE_MODE = GL_BACK, .FRONT_FACE = GL_CCW, }, [OPENGL_STATE_OPAQUE] = { .name = "OPENGL_STATE_OPAQUE", .LIGHTING = true, .LIGHT0 = kStateMaybe, /* FIXME: in shader mode, these should not be kStateMaybe, but * false for 0 and true for 1. On the other hand, in shader mode * these settings are supposed to be irrelevant. */ .LIGHT1 = kStateMaybe, // FIXME: see above .LIGHT2 = false, .LIGHT3 = false, .LIGHT4 = false, .LIGHT5 = false, .LIGHT6 = false, .LIGHT7 = false, .TEXTURE_2D = true, .COLOR_MATERIAL = false, .SHADE_MODEL = GL_SMOOTH, .TEXTURE_ENV_MODE = GL_MODULATE, .ACTIVE_TEXTURE = GL_TEXTURE0, .CLIENT_ACTIVE_TEXTURE = GL_TEXTURE0, .BLEND = false, .BLEND_SRC = GL_SRC_ALPHA, .BLEND_DST = GL_ONE_MINUS_SRC_ALPHA, .FOG = kStateMaybe, .VERTEX_ARRAY = true, .NORMAL_ARRAY = true, .COLOR_ARRAY = false, .INDEX_ARRAY = false, .TEXTURE_COORD_ARRAY = false, .EDGE_FLAG_ARRAY = false, .NORMALIZE = false, .RESCALE_NORMAL = false, .DEPTH_TEST = true, .DEPTH_WRITEMASK = true, .CULL_FACE = true, .CULL_FACE_MODE = GL_BACK, .FRONT_FACE = GL_CCW, }, [OPENGL_STATE_TRANSLUCENT_PASS] = { .name = "OPENGL_STATE_TRANSLUCENT_PASS", .LIGHTING = false, .LIGHT0 = kStateMaybe, // FIXME: see above .LIGHT1 = kStateMaybe, // FIXME: see above .LIGHT2 = false, .LIGHT3 = false, .LIGHT4 = false, .LIGHT5 = false, .LIGHT6 = false, .LIGHT7 = false, .TEXTURE_2D = false, .COLOR_MATERIAL = false, .SHADE_MODEL = GL_SMOOTH, .TEXTURE_ENV_MODE = GL_MODULATE, .ACTIVE_TEXTURE = GL_TEXTURE0, .CLIENT_ACTIVE_TEXTURE = GL_TEXTURE0, .BLEND = false, .BLEND_SRC = GL_SRC_ALPHA, .BLEND_DST = GL_ONE_MINUS_SRC_ALPHA, .FOG = kStateMaybe, .VERTEX_ARRAY = false, .NORMAL_ARRAY = false, .COLOR_ARRAY = false, .INDEX_ARRAY = false, .TEXTURE_COORD_ARRAY = false, .EDGE_FLAG_ARRAY = false, .NORMALIZE = false, .RESCALE_NORMAL = false, .DEPTH_TEST = true, .DEPTH_WRITEMASK = false, .CULL_FACE = true, .CULL_FACE_MODE = GL_BACK, .FRONT_FACE = GL_CCW, }, [OPENGL_STATE_ADDITIVE_BLENDING] = { .name = "OPENGL_STATE_ADDITIVE_BLENDING", .LIGHTING = false, .LIGHT0 = kStateMaybe, // FIXME: see above .LIGHT1 = kStateMaybe, // FIXME: see above .LIGHT2 = false, .LIGHT3 = false, .LIGHT4 = false, .LIGHT5 = false, .LIGHT6 = false, .LIGHT7 = false, .TEXTURE_2D = false, .COLOR_MATERIAL = false, .SHADE_MODEL = GL_SMOOTH, .TEXTURE_ENV_MODE = GL_MODULATE, // Should be GL_BLEND? .ACTIVE_TEXTURE = GL_TEXTURE0, .CLIENT_ACTIVE_TEXTURE = GL_TEXTURE0, .BLEND = true, .BLEND_SRC = GL_SRC_ALPHA, .BLEND_DST = GL_ONE, .FOG = kStateMaybe, .VERTEX_ARRAY = true, .NORMAL_ARRAY = false, .COLOR_ARRAY = false, .INDEX_ARRAY = false, .TEXTURE_COORD_ARRAY = false, .EDGE_FLAG_ARRAY = false, .NORMALIZE = false, .RESCALE_NORMAL = false, .DEPTH_TEST = true, .DEPTH_WRITEMASK = false, .CULL_FACE = false, .CULL_FACE_MODE = GL_BACK, .FRONT_FACE = GL_CCW, }, [OPENGL_STATE_OVERLAY] = { .name = "OPENGL_STATE_OVERLAY", .LIGHTING = false, .LIGHT0 = kStateMaybe, // FIXME: see above .LIGHT1 = kStateMaybe, // FIXME: see above .LIGHT2 = false, .LIGHT3 = false, .LIGHT4 = false, .LIGHT5 = false, .LIGHT6 = false, .LIGHT7 = false, .TEXTURE_2D = false, .COLOR_MATERIAL = false, .SHADE_MODEL = GL_SMOOTH, .TEXTURE_ENV_MODE = GL_MODULATE, .ACTIVE_TEXTURE = GL_TEXTURE0, .CLIENT_ACTIVE_TEXTURE = GL_TEXTURE0, .BLEND = true, .BLEND_SRC = GL_SRC_ALPHA, .BLEND_DST = GL_ONE_MINUS_SRC_ALPHA, .FOG = false, .VERTEX_ARRAY = false, .NORMAL_ARRAY = false, .COLOR_ARRAY = false, .INDEX_ARRAY = false, .TEXTURE_COORD_ARRAY = false, .EDGE_FLAG_ARRAY = false, .NORMALIZE = false, .RESCALE_NORMAL = false, .DEPTH_TEST = false, .DEPTH_WRITEMASK = false, .CULL_FACE = false, // ?? .CULL_FACE_MODE = GL_BACK, .FRONT_FACE = GL_CCW, } }; oolite-1.82/src/Core/OOPListParsing.h000066400000000000000000000027511256642440500174210ustar00rootroot00000000000000/* OOPListParsing.h Property list parser. Tries to use native Foundation property list parsing, then falls back on Oolite ad-hoc parser for backwards-compatibility (Oolite's XML plist parser is more lenient than Foundation on OS X). Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import // whereFrom is an optional description of the data source, for error reporting. id OOPropertyListFromData(NSData *data, NSString *whereFrom); id OOPropertyListFromFile(NSString *path); // Wrappers which ensure that the plist contains the right type of object. NSDictionary *OODictionaryFromData(NSData *data, NSString *whereFrom); NSDictionary *OODictionaryFromFile(NSString *path); NSArray *OOArrayFromData(NSData *data, NSString *whereFrom); NSArray *OOArrayFromFile(NSString *path); oolite-1.82/src/Core/OOPListParsing.m000066400000000000000000000144741256642440500174330ustar00rootroot00000000000000/* OOPListParsing.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOPListParsing.h" #import "OOLogging.h" #import "OOStringParsing.h" #import "NSDataOOExtensions.h" #include #include #if !OOLITE_GNUSTEP #define NO_DYNAMIC_PLIST_DTD_CHANGE #endif static NSString * const kOOLogPListFoundationParseError = @"plist.parse.failed"; static NSString * const kOOLogPListWrongType = @"plist.wrongType"; #ifndef NO_DYNAMIC_PLIST_DTD_CHANGE static NSData *ChangeDTDIfApplicable(NSData *data); #endif static NSData *CopyDataFromFile(NSString *path); static id ValueIfClass(id value, Class class); id OOPropertyListFromData(NSData *data, NSString *whereFrom) { id result = nil; NSString *error = nil; if (data != nil) { #ifndef NO_DYNAMIC_PLIST_DTD_CHANGE data = ChangeDTDIfApplicable(data); #endif result = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:NULL errorDescription:&error]; if (result == nil) // Foundation parser failed { #if OOLITE_RELEASE_PLIST_ERROR_STRINGS [error autorelease]; #endif // Ensure we can say something sensible... if (error == nil) error = @""; if (whereFrom == nil) whereFrom = @""; OOLog(kOOLogPListFoundationParseError, @"Failed to parse %@ as a property list.\n%@", whereFrom, error); } } return result; } id OOPropertyListFromFile(NSString *path) { id result = nil; NSData *data = nil; if (path != nil) { // Load file, if it exists... data = CopyDataFromFile(path); if (data != nil) { // ...and parse it result = OOPropertyListFromData(data, path); [data release]; } // Non-existent file is not an error. } return result; } #ifndef NO_DYNAMIC_PLIST_DTD_CHANGE static NSData *ChangeDTDIfApplicable(NSData *data) { const uint8_t *bytes = NULL; uint8_t *newBytes = NULL; size_t length, newLength, offset = 0, newOffset = 0; const char xmlDeclLine[] = "<\?xml version=\"1.0\" encoding=\"UTF-8\"\?>"; const char *appleDTDLines[] = { "", "", NULL }; const char gstepDTDLine[] = ""; const char *srcDTDLine = NULL; size_t srcDTDLineSize = 0; unsigned i; length = [data length]; if (length < sizeof xmlDeclLine) return data; bytes = [data bytes]; // Check if it starts with an XML declaration. Bogus: there are valid XML declarations which don't match xmlDeclLine. if (memcmp(bytes, xmlDeclLine, sizeof xmlDeclLine - 1) != 0) return data; offset += sizeof xmlDeclLine - 1; while (offset < length && isspace(bytes[offset])) ++offset; // Check if first non-blank stuff after XML declaration is any known Apple plist DTD. Also somewhat bogus. for (i = 0; ; i++) { srcDTDLine = appleDTDLines[i]; if (srcDTDLine == NULL) return data; // No matches srcDTDLineSize = strlen(appleDTDLines[i]); if (srcDTDLineSize <= length - offset && memcmp(bytes + offset, srcDTDLine, srcDTDLineSize) == 0) { // Match break; } } offset += srcDTDLineSize; newLength = length - offset + sizeof xmlDeclLine + sizeof gstepDTDLine - 1; newBytes = malloc(newLength); if (newBytes == NULL) return data; // Construct modified version with altered DTD line memcpy(newBytes, xmlDeclLine, sizeof xmlDeclLine - 1); newOffset = sizeof xmlDeclLine - 1; newBytes[newOffset++] = '\n'; memcpy(newBytes + newOffset, gstepDTDLine, sizeof gstepDTDLine - 1); newOffset += sizeof gstepDTDLine - 1; memcpy(newBytes + newOffset, bytes + offset, length - offset); return [NSData dataWithBytes:newBytes length:newLength]; } #endif /* Load data from file. Returns a retained pointer. -initWithContentsOfMappedFile fails quietly under OS X if there's no file, but GNUstep complains. */ static NSData *CopyDataFromFile(NSString *path) { return [[NSData oo_dataWithOXZFile:path] retain]; #if 0 // without OXZ extension. Code to be deleted once everything is working #if OOLITE_MAC_OS_X return [[NSData alloc] initWithContentsOfMappedFile:path]; #else NSFileManager *fmgr = [NSFileManager defaultManager]; BOOL dir; if ([fmgr fileExistsAtPath:path isDirectory:&dir]) { if (!dir) { return [[NSData alloc] initWithContentsOfMappedFile:path]; } else { OOLog(kOOLogFileNotFound, @"Expected property list but found directory at %@", path); } } return nil; #endif #endif } // Wrappers which ensure that the plist contains the right type of object. NSDictionary *OODictionaryFromData(NSData *data, NSString *whereFrom) { id result = OOPropertyListFromData(data, whereFrom); return ValueIfClass(result, [NSDictionary class]); } NSDictionary *OODictionaryFromFile(NSString *path) { id result = OOPropertyListFromFile(path); return ValueIfClass(result, [NSDictionary class]); } NSArray *OOArrayFromData(NSData *data, NSString *whereFrom) { id result = OOPropertyListFromData(data, whereFrom); return ValueIfClass(result, [NSArray class]); } NSArray *OOArrayFromFile(NSString *path) { id result = OOPropertyListFromFile(path); return ValueIfClass(result, [NSArray class]); } // Ensure that object is of desired class. static id ValueIfClass(id value, Class class) { if (value != nil && ![value isKindOfClass:class]) { OOLog(kOOLogPListWrongType, @"Property list is wrong type - expected %@, got %@.", class, [value class]); value = nil; } return value; } oolite-1.82/src/Core/OOPlanetDrawable.h000066400000000000000000000033211256642440500177210ustar00rootroot00000000000000/* OOPlanetDrawable.h Draw a ball, such as might be used to represent a planet. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OODrawable.h" #import "OOMaths.h" @class OOMaterial; @interface OOPlanetDrawable: OODrawable { @private OOMaterial *_material; BOOL _isAtmosphere; float _radius; OOMatrix _transform; unsigned _lod; } + (instancetype) planetWithTextureName:(NSString *)textureName radius:(float)radius; + (instancetype) atmosphereWithRadius:(float)radius; - (id) initAsAtmosphere; - (OOMaterial *) material; - (void) setMaterial:(OOMaterial *)material; - (NSString *) textureName; - (void) setTextureName:(NSString *)textureName; // Radius, in game metres. - (float) radius; - (void) setRadius:(float)radius; // Level of detail, [0..1]. Granularity is implementation-defined. - (float) levelOfDetail; - (void) setLevelOfDetail:(float)lod; - (void) calculateLevelOfDetailForViewDistance:(float)distance; // depth-buffer hack - (void) renderTranslucentPartsOnOpaquePass; @end oolite-1.82/src/Core/OOPlanetDrawable.m000066400000000000000000000207711256642440500177360ustar00rootroot00000000000000/* OOPlanetDrawable.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOStellarBody.h" #if NEW_PLANETS #import "OOPlanetDrawable.h" #import "OOPlanetData.h" #import "OOSingleTextureMaterial.h" #import "OOOpenGL.h" #import "OOMacroOpenGL.h" #import "Universe.h" #import "MyOpenGLView.h" #ifndef NDEBUG #import "Entity.h" #import "OODebugGLDrawing.h" #import "OODebugFlags.h" #endif #define LOD_GRANULARITY ((float)(kOOPlanetDataLevels - 1)) #define LOD_FACTOR (1.0 / 4.0) @interface OOPlanetDrawable (Private) - (void) recalculateTransform; - (void) debugDrawNormals; - (void) renderCommonParts; @end @implementation OOPlanetDrawable + (instancetype) planetWithTextureName:(NSString *)textureName radius:(float)radius { OOPlanetDrawable *result = [[[self alloc] init] autorelease]; [result setTextureName:textureName]; [result setRadius:radius]; return result; } + (instancetype) atmosphereWithRadius:(float)radius { OOPlanetDrawable *result = [[[self alloc] initAsAtmosphere] autorelease]; [result setRadius:radius]; return result; } - (id) init { if ((self = [super init])) { _radius = 1.0f; [self recalculateTransform]; [self setLevelOfDetail:0.5f]; } return self; } - (id) initAsAtmosphere { if ((self = [self init])) { _isAtmosphere = YES; } return self; } - (void) dealloc { DESTROY(_material); [super dealloc]; } - (id) copyWithZone:(NSZone *)zone { OOPlanetDrawable *copy = [[[self class] allocWithZone:zone] init]; [copy setMaterial:[self material]]; copy->_isAtmosphere = _isAtmosphere; copy->_radius = _radius; copy->_transform = _transform; copy->_lod = _lod; return copy; } - (OOMaterial *) material { return _material; } - (void) setMaterial:(OOMaterial *)material { [_material autorelease]; _material = [material retain]; } - (NSString *) textureName { return [_material name]; } - (void) setTextureName:(NSString *)textureName { if (![textureName isEqual:[self textureName]]) { [_material release]; NSDictionary *spec = [@"{diffuse_map={repeat_s=yes;cube_map=yes};}" propertyList]; _material = [[OOSingleTextureMaterial alloc] initWithName:textureName configuration:spec]; } } - (float) radius { return _radius; } - (void) setRadius:(float)radius { _radius = fabs(radius); [self recalculateTransform]; } - (float) levelOfDetail { return (float)_lod / LOD_GRANULARITY; } - (void) setLevelOfDetail:(float)lod { _lod = round(OOClamp_0_1_f(lod) * LOD_GRANULARITY); } - (void) calculateLevelOfDetailForViewDistance:(float)distance { BOOL simple = [UNIVERSE reducedDetail]; float drawFactor = [[UNIVERSE gameView] viewSize].width / (simple ? 100.0f : 40.0f); float drawRatio2 = drawFactor * _radius / sqrt(distance); // proportional to size on screen in pixels float lod = sqrt(drawRatio2 * LOD_FACTOR); if (simple) { lod -= 0.5f / LOD_GRANULARITY; // Make LOD transitions earlier. lod = OOClamp_0_max_f(lod, (LOD_GRANULARITY - 1) / LOD_GRANULARITY); // Don't use highest LOD. } [self setLevelOfDetail:lod]; } - (void) renderOpaqueParts { assert(_lod < kOOPlanetDataLevels); OOSetOpenGLState(OPENGL_STATE_OPAQUE); [self renderCommonParts]; OOVerifyOpenGLState(); } - (void) renderTranslucentParts { assert(_lod < kOOPlanetDataLevels); // yes, opaque - necessary changes made later OOSetOpenGLState(OPENGL_STATE_OPAQUE); [self renderCommonParts]; OOVerifyOpenGLState(); } - (void) renderTranslucentPartsOnOpaquePass { assert(_lod < kOOPlanetDataLevels); OO_ENTER_OPENGL(); // yes, opaque - necessary changes made later OOSetOpenGLState(OPENGL_STATE_OPAQUE); OOGL(glDisable(GL_DEPTH_TEST)); [self renderCommonParts]; OOGL(glEnable(GL_DEPTH_TEST)); OOVerifyOpenGLState(); } - (void) renderCommonParts { const OOPlanetDataLevel *data = &kPlanetData[_lod]; OO_ENTER_OPENGL(); OOGL(glPushAttrib(GL_ENABLE_BIT | GL_DEPTH_BUFFER_BIT)); OOGL(glShadeModel(GL_SMOOTH)); if (_isAtmosphere) { OOGL(glEnable(GL_BLEND)); OOGL(glDepthMask(GL_FALSE)); } else { OOGL(glDisable(GL_BLEND)); } // Scale the ball. OOGLPushModelView(); OOGLMultModelView(_transform); [_material apply]; OOGL(glEnable(GL_LIGHTING)); OOGL(glEnable(GL_TEXTURE_2D)); #if OO_TEXTURE_CUBE_MAP if ([_material wantsNormalsAsTextureCoordinates]) { OOGL(glDisable(GL_TEXTURE_2D)); OOGL(glEnable(GL_TEXTURE_CUBE_MAP)); } #endif OOGL(glDisableClientState(GL_COLOR_ARRAY)); OOGL(glEnableClientState(GL_TEXTURE_COORD_ARRAY)); OOGL(glVertexPointer(3, GL_FLOAT, 0, kOOPlanetVertices)); if ([_material wantsNormalsAsTextureCoordinates]) { OOGL(glTexCoordPointer(3, GL_FLOAT, 0, kOOPlanetVertices)); } else { OOGL(glTexCoordPointer(2, GL_FLOAT, 0, kOOPlanetTexCoords)); } // FIXME: instead of GL_RESCALE_NORMAL, consider copying and transforming the vertex array for each planet. OOGL(glEnable(GL_RESCALE_NORMAL)); OOGL(glNormalPointer(GL_FLOAT, 0, kOOPlanetVertices)); OOGL(glDrawElements(GL_TRIANGLES, data->faceCount*3, data->type, data->indices)); #ifndef NDEBUG if ([UNIVERSE wireframeGraphics]) { OODebugDrawBasisAtOrigin(1.5); } #endif #if OO_TEXTURE_CUBE_MAP if ([_material wantsNormalsAsTextureCoordinates]) { OOGL(glEnable(GL_TEXTURE_2D)); OOGL(glDisable(GL_TEXTURE_CUBE_MAP)); } #endif OOGLPopModelView(); #ifndef NDEBUG if (gDebugFlags & DEBUG_DRAW_NORMALS) [self debugDrawNormals]; #endif [OOMaterial applyNone]; OOGL(glPopAttrib()); OOGL(glDisableClientState(GL_TEXTURE_COORD_ARRAY)); } - (BOOL) hasOpaqueParts { return !_isAtmosphere; } - (BOOL) hasTranslucentParts { return _isAtmosphere; } - (GLfloat) collisionRadius { return _radius; } - (GLfloat) maxDrawDistance { // FIXME return INFINITY; } - (BoundingBox) boundingBox { return (BoundingBox){{ -_radius, -_radius, -_radius }, { _radius, _radius, _radius }}; } - (void) setBindingTarget:(id)target { [_material setBindingTarget:target]; } - (void) dumpSelfState { [super dumpSelfState]; OOLog(@"dumpState.planetDrawable", @"radius: %g", [self radius]); OOLog(@"dumpState.planetDrawable", @"LOD: %g", [self levelOfDetail]); } - (void) recalculateTransform { _transform = OOMatrixForScaleUniform(_radius); } #ifndef NDEBUG - (void) debugDrawNormals { OODebugWFState state; OO_ENTER_OPENGL(); state = OODebugBeginWireframe(NO); const OOPlanetDataLevel *data = &kPlanetData[_lod]; unsigned i; OOGLBEGIN(GL_LINES); for (i = 0; i < data->vertexCount; i++) { /* Fun sphere facts: the normalized coordinates of a point on a sphere at the origin is equal to the object-space normal of the surface at that point. Furthermore, we can construct the binormal (a vector pointing westward along the surface) as the cross product of the normal with the Y axis. (This produces singularities at the pole, but there have to be singularities according to the Hairy Ball Theorem.) The tangent (a vector north along the surface) is then the inverse of the cross product of the normal and binormal. (This comment courtesy of the in-development planet shader.) */ Vector v = make_vector(kOOPlanetVertices[i * 3], kOOPlanetVertices[i * 3 + 1], kOOPlanetVertices[i * 3 + 2]); Vector n = v; v = OOVectorMultiplyMatrix(v, _transform); glColor3f(0.0f, 1.0f, 1.0f); GLVertexOOVector(v); GLVertexOOVector(vector_add(v, vector_multiply_scalar(n, _radius * 0.05))); Vector b = cross_product(n, kBasisYVector); Vector t = vector_flip(true_cross_product(n, b)); glColor3f(1.0f, 1.0f, 0.0f); GLVertexOOVector(v); GLVertexOOVector(vector_add(v, vector_multiply_scalar(t, _radius * 0.03))); glColor3f(0.0f, 1.0f, 0.0f); GLVertexOOVector(v); GLVertexOOVector(vector_add(v, vector_multiply_scalar(b, _radius * 0.03))); } OOGLEND(); OODebugEndWireframe(state); } - (NSSet *) allTextures { return [[self material] allTextures]; } #endif @end #endif /* NEW_PLANETS */ oolite-1.82/src/Core/OOPointMaths.h000066400000000000000000000015421256642440500171250ustar00rootroot00000000000000#import "OOFunctionAttributes.h" #include // Utilities for working with NSPoints as 2D vectors. OOINLINE NSPoint PtAdd(NSPoint a, NSPoint b) { return NSMakePoint(a.x + b.x, a.y + b.y); } OOINLINE NSPoint PtSub(NSPoint a, NSPoint b) { return NSMakePoint(a.x - b.x, a.y - b.y); } OOINLINE NSPoint PtScale(NSPoint p, CGFloat scale) { return NSMakePoint(p.x * scale, p.y * scale); } OOINLINE CGFloat PtDot(NSPoint a, NSPoint b) { return a.x * b.x + a.y * b.y; } OOINLINE CGFloat PtCross(NSPoint a, NSPoint b) { return a.x * b.y - b.x * a.y; } OOINLINE NSPoint PtRotCW(NSPoint p) { // Rotate 90 degrees clockwise. return NSMakePoint(p.y, -p.x); } OOINLINE NSPoint PtRotACW(NSPoint p) { // Rotate 90 degrees anticlockwise. return NSMakePoint(-p.y, p.x); } OOINLINE NSPoint PtNormal(NSPoint p) { return PtScale(p, 1.0f / sqrt(PtDot(p, p))); } oolite-1.82/src/Core/OOPolygonSprite.h000066400000000000000000000037371256642440500176650ustar00rootroot00000000000000/* OOPolygonSprite.h Oolite Two-dimensional polygon object for UI things such as missile icons. Copyright (C) 2009-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" #import "OOOpenGL.h" #import "OOOpenGLExtensionManager.h" @interface OOPolygonSprite: NSObject { @private GLfloat *_solidData; size_t _solidCount; GLfloat *_outlineData; size_t _outlineCount; #if OO_USE_VBO GLuint _solidVBO; GLuint _outlineVBO; #endif #ifndef NDEBUG NSString *_name; #endif } /* DataArray is either an array of pairs of numbers, or an array of such arrays (representing one or more contours). OutlineWidth is the width of the tesselated outline, in the same scale as the vertices. Name is used for debugging only. */ - (id) initWithDataArray:(NSArray *)dataArray outlineWidth:(GLfloat)outlineWidth name:(NSString *)name; - (void) drawFilled; - (void) drawOutline; @end #import "HeadUpDisplay.h" @interface OOPolygonSprite (OOHUDBeaconIcon) @end oolite-1.82/src/Core/OOPolygonSprite.m000066400000000000000000000616701256642440500176720ustar00rootroot00000000000000/* OOPolygonSprite.m Oolite Copyright (C) 2009-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* Implementation note: The polygon data is tesselated (on object creation) using a GLU tesselator. Although GLU produces a mix of triangles, triangle fans and triangle strips, we convert those to a single triangle soup since the polygons are unlikely to be large enough that using multiple primitives would be a win. Uniquing vertices and using indices, and using the same vertex array for outline and filled mode, would in principle be more efficient, but not worth the added complexity in the preprocessing given the simplicity of the icons we're likely to encounter. */ #import "OOPolygonSprite.h" #import "OOCollectionExtractors.h" #import "OOMacroOpenGL.h" #import "OOMaths.h" #import "OOPointMaths.h" #import "OOGraphicsResetManager.h" #ifndef APIENTRY #define APIENTRY #endif #define kCosMitreLimit 0.866f // Approximately cos(30 deg) @interface OOPolygonSprite (Private) - (BOOL) loadPolygons:(NSArray *)dataArray outlineWidth:(float)outlineWidth; @end typedef struct { GLfloat *data; size_t count; // Number of vertices in use, i.e. half of number of data elements used. size_t capacity; // Half of number of floats there is space for in data. GLenum mode; // Current primitive mode. size_t vCount; // Number of vertices so far in primitive. NSPoint pending0, pending1; // Used for splitting GL_TRIANGLE_STRIP/GL_TRIANGLE_FAN primitives. BOOL OK; // Set to false to indicate error. #ifndef NDEBUG BOOL generatingOutline; unsigned svgID; NSString *name; NSMutableString *debugSVG; #endif } TessPolygonData; static NSArray *DataArrayToPoints(TessPolygonData *data, NSArray *dataArray); static NSArray *BuildOutlineContour(NSArray *dataArray, GLfloat width, BOOL inner); static void SubmitVertices(GLUtesselator *tesselator, TessPolygonData *polygonData, NSArray *contour); static BOOL GrowTessPolygonData(TessPolygonData *data, size_t capacityHint); // Returns true if capacity grew by at least one. static BOOL AppendVertex(TessPolygonData *data, NSPoint vertex); #ifndef NDEBUG static void SVGDumpBegin(TessPolygonData *data); static void SVGDumpEnd(TessPolygonData *data); static void SVGDumpBeginGroup(TessPolygonData *data, NSString *name); static void SVGDumpEndGroup(TessPolygonData *data); static void SVGDumpAppendBaseContour(TessPolygonData *data, NSArray *points); static void SVGDumpBeginPrimitive(TessPolygonData *data); static void SVGDumpEndPrimitive(TessPolygonData *data); static void SVGDumpAppendTriangle(TessPolygonData *data, NSPoint v0, NSPoint v1, NSPoint v2); #else #define SVGDumpEnd(data) do {} while (0) #define SVGDumpBeginGroup(data, name) do {} while (0) #define SVGDumpEndGroup(data) do {} while (0) #define SVGDumpAppendBaseContour(data, points) do {} while (0) #define SVGDumpBeginPrimitive(data) do {} while (0) #define SVGDumpEndPrimitive(data) do {} while (0) #define SVGDumpAppendTriangle(data, v0, v1, v2) do {} while (0) #endif static void APIENTRY TessBeginCallback(GLenum type, void *polygonData); static void APIENTRY TessVertexCallback(void *vertexData, void *polygonData); static void APIENTRY TessCombineCallback(GLdouble coords[3], void *vertexData[4], GLfloat weight[4], void **outData, void *polygonData); static void APIENTRY TessEndCallback(void *polygonData); static void APIENTRY ErrorCallback(GLenum error, void *polygonData); @implementation OOPolygonSprite - (id) initWithDataArray:(NSArray *)dataArray outlineWidth:(GLfloat)outlineWidth name:(NSString *)name { if ((self = [super init])) { #ifndef NDEBUG _name = [name copy]; #endif if ([dataArray count] == 0) { [self release]; return nil; } // Normalize data to array-of-arrays form. if (![[dataArray objectAtIndex:0] isKindOfClass:[NSArray class]]) { dataArray = [NSArray arrayWithObject:dataArray]; } if (![self loadPolygons:dataArray outlineWidth:outlineWidth]) { [self release]; return nil; } [[OOGraphicsResetManager sharedManager] registerClient:self]; } return self; } - (void) dealloc { [[OOGraphicsResetManager sharedManager] unregisterClient:self]; #ifndef NDEBUG DESTROY(_name); #endif free(_solidData); free(_outlineData); [super dealloc]; } #ifndef NDEBUG - (NSString *) descriptionComponents { return _name; } #endif - (void) drawWithData:(GLfloat *)data count:(size_t)count VBO:(GLuint *)vbo { if (count == 0) return; NSParameterAssert(vbo != NULL && data != NULL); OO_ENTER_OPENGL(); OOSetOpenGLState(OPENGL_STATE_OVERLAY); #if OO_USE_VBO BOOL useVBO = [[OOOpenGLExtensionManager sharedManager] vboSupported]; if (useVBO) { if (*vbo == 0) { OOGL(glGenBuffersARB(1, vbo)); if (*vbo != 0) { OOGL(glBindBufferARB(GL_ARRAY_BUFFER, *vbo)); OOGL(glBufferDataARB(GL_ARRAY_BUFFER, sizeof (GLfloat) * count * 2, data, GL_STATIC_DRAW)); } } else { OOGL(glBindBufferARB(GL_ARRAY_BUFFER, *vbo)); } if (*vbo != 0) data = NULL; // Must pass NULL pointer to glVertexPointer to use VBO. } #endif OOGL(glEnableClientState(GL_VERTEX_ARRAY)); OOGL(glVertexPointer(2, GL_FLOAT, 0, data)); OOGL(glDrawArrays(GL_TRIANGLES, 0, count)); OOGL(glDisableClientState(GL_VERTEX_ARRAY)); #if OO_USE_VBO if (useVBO) OOGL(glBindBufferARB(GL_ARRAY_BUFFER, 0)); #endif OOVerifyOpenGLState(); OOCheckOpenGLErrors(@"OOPolygonSprite after rendering %@", self); } - (void) drawFilled { #if !OO_USE_VBO GLuint _solidVBO; // Unusued #endif [self drawWithData:_solidData count:_solidCount VBO:&_solidVBO]; } - (void) drawOutline { #if !OO_USE_VBO GLuint _outlineVBO; // Unusued #endif [self drawWithData:_outlineData count:_outlineCount VBO:&_outlineVBO]; } - (void)resetGraphicsState { #if OO_USE_VBO OO_ENTER_OPENGL(); if (_solidVBO != 0) glDeleteBuffersARB(1, &_solidVBO); if (_outlineVBO != 0) glDeleteBuffersARB(1, &_outlineVBO); _solidVBO = 0; _outlineVBO = 0; #endif } // FIXME: this method is absolutely horrible. - (BOOL) loadPolygons:(NSArray *)dataArray outlineWidth:(float)outlineWidth { NSParameterAssert(dataArray != nil); NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; GLUtesselator *tesselator = NULL; TessPolygonData polygonData; memset(&polygonData, 0, sizeof polygonData); polygonData.OK = YES; #ifndef NDEBUG polygonData.name = _name; if ([[NSUserDefaults standardUserDefaults] boolForKey:@"polygon-sprite-dump-svg"]) SVGDumpBegin(&polygonData); #endif // For efficiency, grow to more than big enough for most cases to avoid regrowing. if (!GrowTessPolygonData(&polygonData, 100)) { polygonData.OK = NO; goto END; } tesselator = gluNewTess(); if (tesselator == NULL) { polygonData.OK = NO; goto END; } dataArray = DataArrayToPoints(&polygonData, dataArray); /*** Tesselate polygon fill ***/ gluTessCallback(tesselator, GLU_TESS_BEGIN_DATA, TessBeginCallback); gluTessCallback(tesselator, GLU_TESS_VERTEX_DATA, TessVertexCallback); gluTessCallback(tesselator, GLU_TESS_END_DATA, TessEndCallback); gluTessCallback(tesselator, GLU_TESS_ERROR_DATA, ErrorCallback); gluTessCallback(tesselator, GLU_TESS_COMBINE_DATA, TessCombineCallback); gluTessBeginPolygon(tesselator, &polygonData); SVGDumpBeginGroup(&polygonData, @"Fill"); NSUInteger contourCount = [dataArray count], contourIndex; for (contourIndex = 0; contourIndex < contourCount && polygonData.OK; contourIndex++) { NSArray *contour = [dataArray oo_arrayAtIndex:contourIndex]; if (contour == nil) { polygonData.OK = NO; break; } SubmitVertices(tesselator, &polygonData, contour); } gluTessEndPolygon(tesselator); SVGDumpEndGroup(&polygonData); if (polygonData.OK) { _solidCount = polygonData.count; if (_solidCount != 0) { _solidData = realloc(polygonData.data, polygonData.count * sizeof (GLfloat) * 2); if (_solidData != NULL) polygonData.data = NULL; // realloc succeded. else { // Unlikely, but legal: realloc failed to shrink buffer. _solidData = polygonData.data; if (_solidData == NULL) polygonData.OK = NO; } } else { // Empty polygon. _solidData = NULL; } } if (!polygonData.OK) goto END; /*** Tesselate polygon outline ***/ gluDeleteTess(tesselator); tesselator = gluNewTess(); if (tesselator == NULL) { polygonData.OK = NO; goto END; } polygonData.count = 0; polygonData.capacity = 0; if (!GrowTessPolygonData(&polygonData, 100)) { polygonData.OK = NO; goto END; } #ifndef NDEBUG polygonData.generatingOutline = YES; #endif gluTessCallback(tesselator, GLU_TESS_BEGIN_DATA, TessBeginCallback); gluTessCallback(tesselator, GLU_TESS_VERTEX_DATA, TessVertexCallback); gluTessCallback(tesselator, GLU_TESS_END_DATA, TessEndCallback); gluTessCallback(tesselator, GLU_TESS_ERROR_DATA, ErrorCallback); gluTessCallback(tesselator, GLU_TESS_COMBINE_DATA, TessCombineCallback); gluTessProperty(tesselator, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_POSITIVE); gluTessBeginPolygon(tesselator, &polygonData); SVGDumpBeginGroup(&polygonData, @"Outline"); outlineWidth *= 0.5f; // Half the width in, half the width out. contourCount = [dataArray count]; for (contourIndex = 0; contourIndex < contourCount && polygonData.OK; contourIndex++) { NSArray *contour = [dataArray oo_arrayAtIndex:contourIndex]; if (contour == nil) { polygonData.OK = NO; break; } SubmitVertices(tesselator, &polygonData, BuildOutlineContour(contour, outlineWidth, NO)); SubmitVertices(tesselator, &polygonData, BuildOutlineContour(contour, outlineWidth, YES)); } gluTessEndPolygon(tesselator); SVGDumpEndGroup(&polygonData); if (polygonData.OK) { if (polygonData.count != 0) { _outlineCount = polygonData.count; _outlineData = realloc(polygonData.data, polygonData.count * sizeof (GLfloat) * 2); if (_outlineData != NULL) polygonData.data = NULL; // realloc succeded. else { // Unlikely, but legal: realloc failed to shrink buffer. _outlineData = polygonData.data; if (_outlineData == NULL) polygonData.OK = NO; } } else { // Empty polygon. _outlineCount = 0; _outlineData = NULL; } } END: SVGDumpEnd(&polygonData); free(polygonData.data); gluDeleteTess(tesselator); [pool release]; #ifndef NDEBUG DESTROY(polygonData.debugSVG); #endif return polygonData.OK; } @end static void SubmitVertices(GLUtesselator *tesselator, TessPolygonData *polygonData, NSArray *contour) { NSUInteger vertexCount = [contour count], vertexIndex; if (vertexCount > 2) { gluTessBeginContour(tesselator); for (vertexIndex = 0; vertexIndex < vertexCount && polygonData->OK; vertexIndex++) { NSValue *pointValue = [contour objectAtIndex:vertexIndex]; NSPoint p = [pointValue pointValue]; GLdouble vert[3] = { p.x, p.y, 0.0 }; gluTessVertex(tesselator, vert, pointValue); } gluTessEndContour(tesselator); } } static NSArray *DataArrayToPoints(TessPolygonData *data, NSArray *dataArray) { /* This converts an icon definition in the form of an array of array of numbers to internal data in the form of an array of arrays of NSValues containing NSPoint data. In addition to repacking the data, it performs the following data processing: * Sequences of duplicate vertices are removed (including across the beginning and end, in case of manually closed contours). * Vertices containing nans or infinities are skipped, Just In Case. * The signed area of each contour is calculated; if it is negative, the contour is clockwise, and we need to flip it. */ SVGDumpBeginGroup(data, @"Base contours"); NSUInteger polyIter, polyCount = [dataArray count]; NSArray *subArrays[polyCount]; for (polyIter = 0; polyIter < polyCount; polyIter++) { NSArray *polyDef = [dataArray objectAtIndex:polyIter]; NSUInteger vertIter, vertCount = [polyDef count] / 2; NSMutableArray *newPolyDef = [NSMutableArray arrayWithCapacity:vertCount]; CGFloat area = 0; CGFloat oldX = [polyDef oo_doubleAtIndex:(vertCount -1) * 2]; CGFloat oldY = [polyDef oo_doubleAtIndex:(vertCount -1) * 2 + 1]; for (vertIter = 0; vertIter < vertCount; vertIter++) { CGFloat x = [polyDef oo_doubleAtIndex:vertIter * 2]; CGFloat y = [polyDef oo_doubleAtIndex:vertIter * 2 + 1]; // Skip bad or duplicate vertices. if (x == oldX && y == oldY) continue; if (isnan(x) || isnan(y)) continue; if (!isfinite(x) || !isfinite(y)) continue; area += x * oldY - oldX * y; oldX = x; oldY = y; [newPolyDef addObject:[NSValue valueWithPoint:NSMakePoint(x, y)]]; } // Eliminate duplicates at ends - the initialization of oldX and oldY will catch one pair, but not extra-silly cases. while ([newPolyDef count] > 1 && [[newPolyDef objectAtIndex:0] isEqual:[newPolyDef lastObject]]) { [newPolyDef removeLastObject]; } if (area >= 0) { subArrays[polyIter] = newPolyDef; } else { subArrays[polyIter] = [[newPolyDef reverseObjectEnumerator] allObjects]; } SVGDumpAppendBaseContour(data, subArrays[polyIter]); } SVGDumpEndGroup(data); return [NSArray arrayWithObjects:subArrays count:polyCount]; } static NSArray *BuildOutlineContour(NSArray *dataArray, GLfloat width, BOOL inner) { NSUInteger i, count = [dataArray count]; if (count < 2) return dataArray; /* Generate inner or outer boundary for a contour, offset by the specified width inwards/outwards from the line. At anticlockwise (convex) corners sharper than acos(kCosMitreLimit), the corner is mitred, i.e. an additional line segment is generated so the outline doesn't protrude arbitratrily far. Overview of the maths: For each vertex, we consider a normalized vector A from the previous vertex and a normalized vector B to the next vertex. (These are always defined since the polygons are closed.) The dot product of A and B is the cosine of the angle, which we compare to kCosMitreLimit to determine mitreing. If the dot product is exactly 1, the vectors are antiparallel and we have a cap; the mitreing case handles this implicitly. (The non-mitreing case would result in a divide-by-zero.) Non-mitreing case: To position the vertex, we need a vector N normal to the corner. We observe that A + B is tangent to the corner, and a 90 degree rotation in 2D is trivial. The offset along this line is proportional to the secant of the angle between N and the 90 degree rotation of A (or B; the angle is by definition the same), or width / (N dot rA). Since both N and rA are rotated by ninety degrees in the same direction, we can cut out both rotations (i.e., using the tangent and A) and get the same result. Mitreing case: The two new vertices are the original vertex offset by scale along the ninety-degree rotation of A and B respectively. */ NSPoint prev, current, next; if (inner) { prev = [[dataArray objectAtIndex:0] pointValue]; current = [[dataArray objectAtIndex:count -1] pointValue]; next = [[dataArray objectAtIndex:count - 2] pointValue]; } else { prev = [[dataArray objectAtIndex:count - 1] pointValue]; current = [[dataArray objectAtIndex:0] pointValue]; next = [[dataArray objectAtIndex:1] pointValue]; } NSMutableArray *result = [NSMutableArray arrayWithCapacity:count]; for (i = 0; i < count; i++) { NSPoint a = PtNormal(PtSub(current, prev)); NSPoint b = PtNormal(PtSub(next, current)); CGFloat dot = PtDot(a, b); BOOL clockwise = PtCross(a, b) < 0.0f; if (-dot < kCosMitreLimit || !clockwise) { // Non-mitreing case. NSPoint t = PtNormal(PtAdd(a, b)); NSPoint v = PtScale(PtRotACW(t), width / PtDot(t, a)); if (!isnan(v.x) && !isnan(v.y)) { [result addObject:[NSValue valueWithPoint:PtAdd(v, current)]]; } } else { // Mitreing case. NSPoint v1 = PtScale(PtAdd(PtRotACW(a), a), width); NSPoint v2 = PtScale(PtSub(PtRotACW(b), b), width); if (!isnan(v1.x) && !isnan(v1.y)) { [result addObject:[NSValue valueWithPoint:PtAdd(v1, current)]]; } if (!isnan(v2.x) && !isnan(v2.y)) { [result addObject:[NSValue valueWithPoint:PtAdd(v2, current)]]; } } prev = current; current = next; if (inner) { next = [[dataArray objectAtIndex:(count * 2 - 3 - i) % count] pointValue]; } else { next = [[dataArray objectAtIndex:(i + 2) % count] pointValue]; } } return result; } static BOOL GrowTessPolygonData(TessPolygonData *data, size_t capacityHint) { NSCParameterAssert(data != NULL); size_t minCapacity = data->capacity + 1; size_t desiredCapacity = MAX(capacityHint, minCapacity); size_t newCapacity = 0; GLfloat *newData = realloc(data->data, desiredCapacity * sizeof (GLfloat) * 2); if (newData != NULL) { newCapacity = desiredCapacity; } else { desiredCapacity = minCapacity; newData = realloc(data->data, desiredCapacity * sizeof (GLfloat) * 2); if (newData != NULL) newCapacity = desiredCapacity; } if (newData == NULL) return NO; NSCAssert(newCapacity > data->capacity, @"Buffer regrow logic error"); data->data = newData; data->capacity = newCapacity; return YES; } static BOOL AppendVertex(TessPolygonData *data, NSPoint vertex) { NSCParameterAssert(data != NULL); if (data->capacity == data->count && !GrowTessPolygonData(data, data->capacity * 2)) return NO; data->data[data->count * 2] = vertex.x; data->data[data->count * 2 + 1] = vertex.y; data->count++; return YES; } static void APIENTRY TessBeginCallback(GLenum type, void *polygonData) { TessPolygonData *data = polygonData; NSCParameterAssert(data != NULL); data->mode = type; data->vCount = 0; SVGDumpBeginPrimitive(data); } static void APIENTRY TessVertexCallback(void *vertexData, void *polygonData) { TessPolygonData *data = polygonData; NSValue *vertValue = vertexData; NSCParameterAssert(vertValue != NULL && data != NULL); if (!data->OK) return; NSPoint p = [vertValue pointValue]; NSPoint vertex = { p.x, p.y }; size_t vCount = data->vCount++; switch (data->mode) { case GL_TRIANGLES: data->OK = AppendVertex(data, vertex); #ifndef NDEBUG switch (vCount % 3) { case 0: data->pending0 = vertex; break; case 1: data->pending1 = vertex; break; case 2: SVGDumpAppendTriangle(data, data->pending0, data->pending1, vertex); } #endif break; case GL_TRIANGLE_FAN: if (vCount == 0) data->pending0 = vertex; else if (vCount == 1) data->pending1 = vertex; else { data->OK = AppendVertex(data, data->pending0) && AppendVertex(data, data->pending1) && AppendVertex(data, vertex); SVGDumpAppendTriangle(data, data->pending0, data->pending1, vertex); data->pending1 = vertex; } break; case GL_TRIANGLE_STRIP: if (vCount == 0) data->pending0 = vertex; else if (vCount == 1) data->pending1 = vertex; else { /* In order to produce consistent winding, the vertex->triangle order for GL_TRIANGLE_STRIP is: 0, 1, 2 2, 1, 3 2, 3, 4 4, 3, 5 4, 5, 6 6, 5, 7 6, 7, 8 Vertices 0 and 1 are special-cased above, and the first time we get here it's time for the first triangle, which is pending0, pending1, v. v (i.e., vertex 2) then goes into pending0. For the second triangle, the triangle is again pending0, pending1, v, and we then put v (i.e., vertex 3) into pending1. The third triangle follows the same pattern as the first, and the fourthe the same as the second. In other words, after storing each triangle, v goes into pending0 for even vertex indicies, and pending1 for odd vertex indices. */ data->OK = AppendVertex(data, data->pending0) && AppendVertex(data, data->pending1) && AppendVertex(data, vertex); SVGDumpAppendTriangle(data, data->pending0, data->pending1, vertex); if ((vCount % 2) == 0) data->pending0 = vertex; else data->pending1 = vertex; } break; default: OOLog(@"polygonSprite.tesselate.error", @"Unexpected tesselator primitive mode %u.", data->mode); data->OK = NO; } } static void APIENTRY TessCombineCallback(GLdouble coords[3], void *vertexData[4], GLfloat weight[4], void **outData, void *polygonData) { NSPoint point = { coords[0], coords[1] }; *outData = [NSValue valueWithPoint:point]; } static void APIENTRY TessEndCallback(void *polygonData) { TessPolygonData *data = polygonData; NSCParameterAssert(data != NULL); data->mode = 0; data->vCount = 0; SVGDumpEndPrimitive(data); } static void APIENTRY ErrorCallback(GLenum error, void *polygonData) { TessPolygonData *data = polygonData; NSCParameterAssert(data != NULL); NSString *name = @""; #ifndef NDEBUG name = [NSString stringWithFormat:@" \"%@\"", data->name]; #endif char *errStr = (char *)gluErrorString(error); OOLog(@"polygonSprite.tesselate.error", @"Error %s (%u) while tesselating polygon%@.", errStr, error, name); data->OK = NO; } #ifndef NDEBUG #import "ResourceManager.h" #import "legacy_random.h" static void SVGDumpBegin(TessPolygonData *data) { DESTROY(data->debugSVG); data->debugSVG = [[NSMutableString alloc] initWithString: @"\n" "\n" "\n" "\tOolite polygon sprite debug dump.\n" "\t\n" ]; } static void SVGDumpEnd(TessPolygonData *data) { if (data->debugSVG == nil) return; [data->debugSVG appendString:@"\n"]; [ResourceManager writeDiagnosticString:data->debugSVG toFileNamed:[NSString stringWithFormat:@"Polygon Sprites/%@.svg", data->name]]; DESTROY(data->debugSVG); } static void SVGDumpBeginGroup(TessPolygonData *data, NSString *name) { if (data->debugSVG == nil) return; [data->debugSVG appendFormat:@"\t\n", name, data->svgID++]; } static void SVGDumpEndGroup(TessPolygonData *data) { if (data->debugSVG == nil) return; [data->debugSVG appendString:@"\t\n"]; } static void SVGDumpAppendBaseContour(TessPolygonData *data, NSArray *points) { if (data->debugSVG == nil) return; NSString *groupName = [NSString stringWithFormat:@"contour %u", data->svgID++]; [data->debugSVG appendFormat:@"\t\t\n\t\tdebugSVG appendFormat:@"%c %f %f ", (i == 0) ? 'M' : 'L', p.x, -p.y]; } // Close and add a circle at the first vertex. (SVG has support for end markers, but this isn’t reliable across implementations.) NSPoint p = [[points objectAtIndex:0] pointValue]; [data->debugSVG appendFormat:@"z\"/>\n\t\t\t\n\t\t\n", p.x, -p.y]; } static void SVGDumpBeginPrimitive(TessPolygonData *data) { if (data->debugSVG == nil) return; NSString *groupName = @"Unknown primitive"; switch (data->mode) { case GL_TRIANGLES: groupName = @"Triangle soup"; break; case GL_TRIANGLE_FAN: groupName = @"Triangle fan"; break; case GL_TRIANGLE_STRIP: groupName = @"Triangle strip"; break; } groupName = [groupName stringByAppendingFormat:@" %u", data->svgID++]; // Pick random colour for the primitive. uint8_t red = (Ranrot() & 0x3F) + 0x20; uint8_t green = (Ranrot() & 0x3F) + 0x20; uint8_t blue = (Ranrot() & 0x3F) + 0x20; if (!data->generatingOutline) { red += 0x80; green += 0x80; blue += 0x80; } [data->debugSVG appendFormat:@"\t\t\n", groupName, red, green, blue, data->generatingOutline ? @"#060" : @"#008"]; } static void SVGDumpEndPrimitive(TessPolygonData *data) { if (data->debugSVG == nil) return; [data->debugSVG appendString:@"\t\t\n"]; } static void SVGDumpAppendTriangle(TessPolygonData *data, NSPoint v0, NSPoint v1, NSPoint v2) { if (data->debugSVG == nil) return; [data->debugSVG appendFormat:@"\t\t\t\n", v0.x, -v0.y, v1.x, -v1.y, v2.x, -v2.y]; } #endif oolite-1.82/src/Core/OOPriorityQueue.h000066400000000000000000000062041256642440500176650ustar00rootroot00000000000000/* OOPriorityQueue.h A prority queue is a collection into which objects may be inserted in any order, but (primarily) extracted in sorted order. The order is defined by the comparison selector specified at creation time, which is assumed to have the same signature as a compare method used for array sorting: - (NSComparisonResult)compare:(id)other and must define a partial order on the objects in the priority queue. The behaviour when provided with an inconsistent comparison method is undefined. The implementation is the standard one, a binary heap. It is described in detail in most algorithm textbooks. This collection is *not* thread-safe. Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" @interface OOPriorityQueue: NSObject { @private SEL _comparator; id *_heap; NSUInteger _count, _capacity; } // Note: -init is equivalent to -initWithComparator:@selector(compare:) + (instancetype) queueWithComparator:(SEL)comparator; - (id) initWithComparator:(SEL)comparator; - (void) addObject:(id)object; // May throw NSInvalidArgumentException or NSMallocException. - (void) removeObject:(id)object; // Uses comparator (looking for NSOrderedEqual) to find object. Note: relatively expensive. - (void) removeExactObject:(id)object; // Uses pointer comparison to find object. Note: still relatively expensive. - (NSUInteger) count; - (id) nextObject; - (id) peekAtNextObject; // Returns next object without removing it. - (void) removeNextObject; - (void) addObjects:(id)collection; // collection must respond to -nextObject, or implement -objectEnumerator to return something that implements -nextObject -- such as an NSEnumerator. - (NSArray *) sortedObjects; // Returns the objects in -nextObject order and empties the heap. To get the objects without emptying the heap, copy the priority queue first. - (NSEnumerator *) objectEnumerator; // Enumerator which pulls objects off the heap until it's empty. Note however that the queue itself behaves like an enumerator, as -nextObject has similar semantics (except that the enumerator's -nextObject can never start returning objects after it returns nil). @end oolite-1.82/src/Core/OOPriorityQueue.m000066400000000000000000000345331256642440500177000ustar00rootroot00000000000000/* OOPriorityQueue.m Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #import "OOPriorityQueue.h" #import "OOFunctionAttributes.h" /* Capacity grows by 50% each time. kMinCapacity must be at least 2 or Bad Things will happen. Some examples of growth patterns based on kMinCapacity: 2: 2 3 4 6 9 13 19 28 42 63 94 141 211 316 474 711 1066... 4: 4 6 9 13 19 28 42 63 94 141 211 316 474 711 1066 1599... 8: 8 12 18 27 40 60 90 135 202 303 454 681 1021 1531 2296... 16: 16 24 36 54 81 121 181 271 406 609 913 1369 2053 3079... 32: 32 48 72 108 162 243 364 546 819 1228 1842 2763 4144... Special cases: when an OOPriorityQueue is copied, the copy's capacity is set to the same as its count, which may be less than kMinCapacity. However, when it is grown, it will be set to at least kMinCapacity (except in the case of a reallocation failure, in which case an attempt will be made to grow capacity by one element). */ enum { kMinCapacity = 16 }; /* Functions to navigate the binary heap. Using one-based indices, the following hold: * The left child of a node, L(n), is 2n. * The right child of a node, R(n), is 2n + 1. * The parent of a node, P(n), is n/2 for n > 1. Using zero-based indices, we must convert to and from one-based indices to perform these calculations, giving: * Lz(n) = L(n+1)-1 = (2(n+1))-1 = 2n+1 * Rz(n) = R(n+1)-1 = (2(n+1)+1)-1 = 2n+2 * Pz(n) = P(n+1)-1 = ((n+1)/2)-1 */ OOINLINE NSUInteger PQLeftChild(NSUInteger n) INLINE_CONST_FUNC; OOINLINE NSUInteger PQRightChild(NSUInteger n) INLINE_CONST_FUNC; OOINLINE NSUInteger PQParent(NSUInteger n) INLINE_CONST_FUNC; OOINLINE NSUInteger PQLeftChild(NSUInteger n) { return (n << 1) + 1; } OOINLINE NSUInteger PQRightChild(NSUInteger n) { return (n << 1) + 2; } OOINLINE NSUInteger PQParent(NSUInteger n) { return ((n + 1) >> 1) - 1; } typedef NSComparisonResult (*CompareIMP)(id self, SEL _cmd, id other); OOINLINE NSComparisonResult PQCompare(id a, id b, SEL comparator) { CompareIMP compare = NULL; NSComparisonResult result; // This is equivalent to [a performSelector:comparator withObject:b], except the resulting value isn't an object. compare = (CompareIMP)[a methodForSelector:comparator]; result = compare(a, comparator, b); return result; } // Private priority queue methods. @interface OOPriorityQueue (Private) - (void) makeObjectsPerformSelector:(SEL)selector; - (void) bubbleUpFrom:(NSUInteger)i; - (void) bubbleDownFrom:(NSUInteger)i; - (void) growBuffer; - (void) shrinkBuffer; - (void)removeObjectAtIndex:(NSUInteger)i; #if OO_DEBUG - (void) appendDebugDataToString:(NSMutableString *)string index:(NSUInteger)i depth:(NSUInteger)depth; #endif @end // NSEnumerator subclass to pull objects from queue. @interface OOPriorityQueueEnumerator: NSEnumerator { @private OOPriorityQueue *_queue; } - (id) initWithPriorityQueue:(OOPriorityQueue *)queue; @end @implementation OOPriorityQueue + (instancetype) queueWithComparator:(SEL)comparator { return [[[self alloc] initWithComparator:comparator] autorelease]; } - (id) initWithComparator:(SEL)comparator { if (comparator == NULL) { [self release]; return nil; } self = [super init]; if (self != nil) { _comparator = comparator; } return self; } - (id) init { return [self initWithComparator:@selector(compare:)]; } - (void) dealloc { [self makeObjectsPerformSelector:@selector(release)]; free(_heap); [super dealloc]; } - (NSString *) description { return [NSString stringWithFormat:@"<%@ %p>{count=%lu, capacity=%lu}", [self class], self, _count, _capacity]; } #if OO_DEBUG - (NSString *) debugDescription { NSMutableString *result = nil; result = [NSMutableString string]; [result appendFormat:@"<%@ %p> (count=%lu, capacity=%lu, comparator=%@)", [self class], self, _count, _capacity, NSStringFromSelector(_comparator)]; if (_count != 0) { [result appendString:@"\n{\n"]; [self appendDebugDataToString:result index:0 depth:0]; [result appendString:@"}"]; } else { [result appendString:@" {}"]; } return result; } #endif - (BOOL) isEqual:(id)object { NSUInteger i; OOPriorityQueue *selfCopy = nil, *otherCopy = nil; BOOL identical = YES; if (object == self) return YES; if (![object isKindOfClass:[self class]]) return NO; if (_count != [object count]) return NO; if (_count == 0) return YES; selfCopy = [self copy]; otherCopy = [object copy]; i = _count; while (i--) { if (![[selfCopy nextObject] isEqual:[otherCopy nextObject]]) { identical = NO; break; } } [selfCopy release]; [otherCopy release]; return identical; } - (NSUInteger) hash { if (_count == 0) return NSNotFound; return _count ^ [_heap[0] hash]; } - (id) copyWithZone:(NSZone *)zone { OOPriorityQueue *copy = nil; copy = [[self class] allocWithZone:zone]; if (copy != nil) { copy->_comparator = _comparator; copy->_count = _count; copy->_capacity = _count; copy->_heap = malloc(_count * sizeof(id)); if (copy->_heap != NULL) { memcpy(copy->_heap, _heap, _count * sizeof(id)); [copy makeObjectsPerformSelector:@selector(retain)]; } else if (_count != 0) { [copy release]; copy = nil; } } return copy; } - (void) addObject:(id)object { NSUInteger i; // Validate object if (object == nil) { [NSException raise:NSInvalidArgumentException format:@"Attempt to insert nil into OOPriorityQueue."]; } if (![object respondsToSelector:_comparator]) { [NSException raise:NSInvalidArgumentException format:@"Attempt to insert object (%@) which does not support comparator %@ into OOPriorityQueue.", object, NSStringFromSelector(_comparator)]; } // Ensure there is sufficent space. if (_count == _capacity) [self growBuffer]; // insert object at end of buffer. i = _count++; _heap[i] = object; [self bubbleUpFrom:i]; [object retain]; } - (void) removeObject:(id)object { NSUInteger i; /* Perform linear search for object (using comparator). A depth-first search could skip leaves of lower priority, but I don't expect this to be called very often. */ if (object == nil) return; for (i = 0; i < _count; ++i) { if (PQCompare(object, _heap[i], _comparator) == 0) { [self removeObjectAtIndex:i]; } } } - (void) removeExactObject:(id)object { NSUInteger i; if (object == nil) return; for (i = 0; i < _count; ++i) { if (object == _heap[i]) { [self removeObjectAtIndex:i]; } } } - (NSUInteger) count { return _count; } - (id) nextObject { id result = [self peekAtNextObject]; [self removeNextObject]; return result; } - (id) peekAtNextObject { if (_count == 0) return nil; // return [[_heap[0] retain] autorelease]; return _heap[0]; } - (void) removeNextObject { [self removeObjectAtIndex:0]; } - (void) addObjects:(id)collection { id value = nil; if ([collection respondsToSelector:@selector(objectEnumerator)]) collection = [collection objectEnumerator]; if (![collection respondsToSelector:@selector(nextObject)]) return; while ((value = [collection nextObject])) [self addObject:value]; } - (NSArray *) sortedObjects { return [[self objectEnumerator] allObjects]; } - (NSEnumerator *) objectEnumerator { return [[[OOPriorityQueueEnumerator alloc] initWithPriorityQueue:self] autorelease]; } @end @implementation OOPriorityQueue (Private) - (void) makeObjectsPerformSelector:(SEL)selector { NSUInteger i; if (selector == NULL) return; for (i = 0; i != _count; ++i) { [_heap[i] performSelector:selector]; } } - (void) bubbleUpFrom:(NSUInteger)i { NSUInteger pi; id obj = nil, par = nil; while (0 < i) { pi = PQParent(i); obj = _heap[i]; par = _heap[pi]; if (PQCompare(obj, par, _comparator) < 0) { _heap[i] = par; _heap[pi] = obj; i = pi; } else break; } } - (void) bubbleDownFrom:(NSUInteger)i { NSUInteger end = _count - 1; NSUInteger li, ri, next; id obj = nil; obj = _heap[i]; while (PQLeftChild(i) <= end) { li = PQLeftChild(i); ri = PQRightChild(i); // If left child has lower priority than right child, or there is only one child... if (li == end || PQCompare(_heap[li], _heap[ri], _comparator) < 0) { next = li; } else { next = ri; } if (PQCompare(_heap[next], obj, _comparator) < 0) { // Exchange parent with lowest-priority child _heap[i] = _heap[next]; _heap[next] = obj; i = next; } else break; } } - (void) growBuffer { id *newBuffer = NULL; NSUInteger newCapacity; newCapacity = _capacity * 3 / 2; if (newCapacity < kMinCapacity) newCapacity = kMinCapacity; // Note: realloc(NULL, size) with non-zero size is equivalent to malloc(size), so this is OK starting from a NULL buffer. newBuffer = realloc(_heap, newCapacity * sizeof(id)); if (newBuffer == NULL) { // Attempt to grow by just one waffer-thin slot. newCapacity = _capacity + 1; newBuffer = realloc(_heap, newCapacity * sizeof(id)); if (newBuffer == NULL) { // Failed to grow. [NSException raise:NSMallocException format:@"Could not expand capacity of OOPriorityQueue."]; } } _heap = newBuffer; _capacity = newCapacity; assert(_count < _capacity); } - (void)shrinkBuffer { NSUInteger amountToRemove; id *newBuffer = NULL; NSUInteger newCapacity; if (kMinCapacity < _capacity) { // Remove two thirds of free space, if at least three slots are free. amountToRemove = (_capacity - _count) * 2 / 3; if (2 < amountToRemove) { newCapacity = _capacity - amountToRemove; newBuffer = realloc(_heap, newCapacity * sizeof(id)); if (newBuffer != NULL) { _heap = newBuffer; _capacity = newCapacity; } } } } - (void)removeObjectAtIndex:(NSUInteger)i { id object = nil; if (_count <= i) return; object = _heap[i]; if (i < --_count) { // Overwrite object with last object in array _heap[i] = _heap[_count]; // Push previously-last object down until tree is partially ordered. [self bubbleDownFrom:i]; } else { // Special case: removing last (or only) object. No bubbling needed. } [object autorelease]; if (_count * 2 <= _capacity) [self shrinkBuffer]; } #if OO_DEBUG - (void) appendDebugDataToString:(NSMutableString *)string index:(NSUInteger)i depth:(NSUInteger)depth { NSUInteger spaces; if (_count <= i) return; spaces = 2 + depth; while (spaces--) [string appendString:@" "]; [string appendString:[_heap[i] description]]; [string appendString:@"\n"]; [self appendDebugDataToString:string index:PQLeftChild(i) depth:depth + 1]; [self appendDebugDataToString:string index:PQRightChild(i) depth:depth + 1]; } #endif @end @implementation OOPriorityQueueEnumerator - (id) initWithPriorityQueue:(OOPriorityQueue *)queue { if (queue == nil) { [self release]; return nil; } self = [super init]; if (self != nil) { _queue = [queue retain]; } return self; } - (void) dealloc { [_queue release]; [super dealloc]; } - (id) nextObject { id value = [_queue nextObject]; if (value == nil) { // Maintain enumerator semantics by ensuring we don't start returning new values after returning nil. [_queue release]; _queue = nil; } return value; } @end #if DEBUG_GRAPHVIZ @implementation OOPriorityQueue (DebugGraphViz) static NSString *EscapedString(NSString *string) { NSString *srcStrings[] = { //Note: backslash must be first. @"\\", @"\"", @"\'", @"\r", @"\n", @"\t", nil }; NSString *subStrings[] = { //Note: must be same order. @"\\\\", @"\\\"", @"\\\'", @"\\r", @"\\n", @"\\t", nil }; NSString **src = srcStrings, **sub = subStrings; NSMutableString *mutable = nil; NSString *result = nil; mutable = [string mutableCopy]; while (*src != nil) { [mutable replaceOccurrencesOfString:*src++ withString:*sub++ options:0 range:NSMakeRange(0, [mutable length])]; } if ([mutable length] == [string length]) { result = string; } else { result = [[mutable copy] autorelease]; } [mutable release]; return result; } - (NSString *) generateGraphViz { NSMutableString *result = nil; NSUInteger i; id node = nil; NSString *desc = nil; result = [NSMutableString string]; // Header [result appendString: @"// OOPriorityQueue partially ordered tree dump\n\n" "digraph heap\n" "{\n" "\tgraph [charset=\"UTF-8\", label=\"OOPriorityQueue heap dump\", labelloc=t, labeljust=l];\n" "\tnode [shape=record];\n\t\n\t"]; // Nodes for (i = 0; i < _count; ++i) { node = _heap[i]; desc = [node description]; if ([desc length] > 70) { desc = [[desc substringToIndex:64] stringByAppendingString:@"..."]; } [result appendFormat:@"\tnode_%lu [label=\" | %@ | \"];\n", i, EscapedString(desc)]; } // Arcs for (i = 0; PQLeftChild(i) < _count; ++i) { [result appendFormat:@"\tnode_%lu:f0 -> node_%lu:f1;\n", i, PQLeftChild(i)]; if (PQRightChild(i) < _count) [result appendFormat:@"\tnode_%lu:f2 -> node_%lu:f1;\n", i, PQRightChild(i)]; } [result appendString:@"}\n"]; return result; } - (void) writeGraphVizToURL:(NSURL *)url { NSString *graphViz = nil; NSData *data = nil; graphViz = [self generateGraphViz]; data = [graphViz dataUsingEncoding:NSUTF8StringEncoding]; if (data != nil) { [data writeToURL:url atomically:YES]; } } - (void) writeGraphVizToPath:(NSString *)path { [self writeGraphVizToURL:[NSURL fileURLWithPath:path]]; } @end #endif oolite-1.82/src/Core/OOProbabilisticTextureManager.h000066400000000000000000000047441256642440500225100ustar00rootroot00000000000000/* OOProbabilisticTextureManager.h Manages a set of textures, specified in a property list, with associated probabilities. To avoid interfering with other PRNG-based code, it uses its own ranrot state. Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import #import "OOTexture.h" #import "OOMaths.h" @interface OOProbabilisticTextureManager: NSObject { @private unsigned _count; OOTexture **_textures; float *_prob; float _probMax; RANROTSeed _seed; } /* plistName is the name of the property list specifying the actual textures to use. The plist will be loaded from Config directories and merged. It should contain an array of dictionaries; each dictionary must have a "texture" entry specifying the texture file name (in Textures directories) and an optional "probability" entry (default: 1.0). As a convenience, an entry may also be a string, in which case probability will be 1.0. If no seed is specified, the current seed will be copied. */ - (id)initWithPListName:(NSString *)plistName options:(uint32_t)options anisotropy:(GLfloat)anisotropy lodBias:(GLfloat)lodBias; - (id)initWithPListName:(NSString *)plistName options:(uint32_t)options anisotropy:(GLfloat)anisotropy lodBias:(GLfloat)lodBias seed:(RANROTSeed)seed; /* Select a texture, weighted-randomly. */ - (OOTexture *)selectTexture; - (unsigned)textureCount; - (void)ensureTexturesLoaded; - (RANROTSeed)seed; - (void)setSeed:(RANROTSeed)seed; @end oolite-1.82/src/Core/OOProbabilisticTextureManager.m000066400000000000000000000103371256642440500225100ustar00rootroot00000000000000/* OOProbabilisticTextureManager.m Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOProbabilisticTextureManager.h" #import "ResourceManager.h" #import "OOTexture.h" #import "OOCollectionExtractors.h" @implementation OOProbabilisticTextureManager - (id)initWithPListName:(NSString *)plistName options:(uint32_t)options anisotropy:(GLfloat)anisotropy lodBias:(GLfloat)lodBias { return [self initWithPListName:plistName options:options anisotropy:anisotropy lodBias:lodBias seed:RANROTGetFullSeed()]; } - (id)initWithPListName:(NSString *)plistName options:(uint32_t)options anisotropy:(GLfloat)anisotropy lodBias:(GLfloat)lodBias seed:(RANROTSeed)seed { BOOL OK = YES; NSArray *config = nil; NSUInteger i, count; id entry = nil; NSString *name = nil; float probability; OOTexture *texture = nil; self = [super init]; if (self == nil) OK = NO; if (OK) { config = [ResourceManager arrayFromFilesNamed:plistName inFolder:@"Config" andMerge:YES]; if (config == nil) OK = NO; } if (OK) { count = [config count]; _textures = malloc(sizeof *_textures * count); _prob = malloc(sizeof *_prob * count); if (_textures == NULL || _prob == NULL) OK = NO; } if (OK) { // Go through list and load textures. for (i = 0; i != count; ++i) { entry = [config objectAtIndex:i]; if ([entry isKindOfClass:[NSDictionary class]]) { name = [(NSDictionary *)entry oo_stringForKey:@"texture"]; probability = [entry oo_floatForKey:@"probability" defaultValue:1.0f]; } else if ([entry isKindOfClass:[NSString class]]) { name = entry; probability = 1.0f; } else { name = nil; } if (name != nil && 0.0f < probability) { texture = [OOTexture textureWithName:name inFolder:@"Textures" options:options anisotropy:anisotropy lodBias:lodBias]; if (texture != nil) { _textures[_count] = [texture retain]; _prob[_count] = probability + _probMax; _probMax += probability; ++_count; } } } if (_count == 0) OK = NO; } if (OK) _seed = seed; if (!OK) { [self release]; self = nil; } return self; } - (void)dealloc { unsigned i; if (_textures != NULL) { for (i = 0; i != _count; ++i) { [_textures[i] release]; } free(_textures); } if (_prob != NULL) free(_prob); [super dealloc]; } - (NSString *)description { return [NSString stringWithFormat:@"<%@ %p>{%u textures, cumulative probability=%g}", [self class], self, _count, _probMax]; } - (OOTexture *)selectTexture { float selection; unsigned i; selection = randfWithSeed(&_seed); selection *= _probMax; for (i = 0; i != _count; ++i) { if (selection <= _prob[i]) return _textures[i]; } OOLog(@"probabilisticTextureManager.internalFailure", @"%s: overrun! Choosing last texture.", __PRETTY_FUNCTION__); return _textures[_count - 1]; } - (unsigned)textureCount { return _count; } - (void)ensureTexturesLoaded { unsigned i; for (i = 0; i != _count; ++i) { [_textures[i] ensureFinishedLoading]; } } - (RANROTSeed)seed { return _seed; } - (void)setSeed:(RANROTSeed)seed { _seed = seed; } @end oolite-1.82/src/Core/OOProbabilitySet.h000066400000000000000000000052631256642440500177770ustar00rootroot00000000000000/* OOProbabilitySet.h A collection for selecting objects randomly, with probability weighting. Probability weights can be 0 - an object may be in the set but not selectable. Comes in mutable and immutable variants. Performance characteristics: * -randomObject, the primary method, is O(log n) for immutable OOProbabilitySets and O(n) for mutable ones. * -containsObject: and -probabilityForObject: are O(n). This could be optimized, but there's currently no need. Copyright (C) 2008-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" @interface OOProbabilitySet: NSObject + (id) probabilitySet; + (id) probabilitySetWithObjects:(id *)objects weights:(float *)weights count:(NSUInteger)count; + (id) probabilitySetWithPropertyListRepresentation:(NSDictionary *)plist; - (id) init; - (id) initWithObjects:(id *)objects weights:(float *)weights count:(NSUInteger)count; - (id) initWithPropertyListRepresentation:(NSDictionary *)plist; // propertyListRepresentation is only valid if objects are property list objects. - (NSDictionary *) propertyListRepresentation; - (NSUInteger) count; - (id) randomObject; - (float) weightForObject:(id)object; // Returns -1 for unknown objects. - (float) sumOfWeights; - (NSArray *) allObjects; @end @interface OOProbabilitySet (OOExtendedProbabilitySet) - (BOOL) containsObject:(id)object; - (NSEnumerator *) objectEnumerator; - (float) probabilityForObject:(id)object; // Returns -1 for unknown objects, or a value from 0 to 1 inclusive for known objects. @end @interface OOMutableProbabilitySet: OOProbabilitySet - (void) setWeight:(float)weight forObject:(id)object; // Adds object if needed. - (void) removeObject:(id)object; @end oolite-1.82/src/Core/OOProbabilitySet.m000066400000000000000000000530611256642440500200030ustar00rootroot00000000000000/* OOProbabilitySet.m Copyright (C) 2008-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. IMPLEMENTATION NOTES OOProbabilitySet is implemented as a class cluster with two abstract classes, two special-case implementations for immutable sets with zero or one object, and two general implementations (one mutable and one immutable). The general implementations are the only non-trivial ones. The general immutable implementation, OOConcreteProbabilitySet, consists of two parallel arrays, one of objects and one of cumulative weights. The "cumulative weight" for an entry is the sum of its weight and the cumulative weight of the entry to the left (i.e., with a lower index), with the implicit entry -1 having a cumulative weight of 0. Since weight cannot be negative, this means that cumulative weights increase to the right (not strictly increasing, though, since weights may be zero). We can thus find an object with a given cumulative weight through a binary search. OOConcreteMutableProbabilitySet is a naïve implementation using arrays. It could be optimized, but isn't expected to be used much except for building sets that will then be immutablized. */ #import "OOProbabilitySet.h" #import "OOFunctionAttributes.h" #import "OOCollectionExtractors.h" #import "legacy_random.h" static NSString * const kObjectsKey = @"objects"; static NSString * const kWeightsKey = @"weights"; @protocol OOProbabilitySetEnumerable - (id) privObjectAtIndex:(NSUInteger)index; @end @interface OOProbabilitySet (OOPrivate) // Designated initializer. This must be used by subclasses, since init is overriden for public use. - (id) initPriv; @end @interface OOEmptyProbabilitySet: OOProbabilitySet + (OOEmptyProbabilitySet *) singleton OO_RETURNS_RETAINED; @end @interface OOSingleObjectProbabilitySet: OOProbabilitySet { @private id _object; float _weight; } - (id) initWithObject:(id)object weight:(float)weight; @end @interface OOConcreteProbabilitySet: OOProbabilitySet { @private NSUInteger _count; id *_objects; float *_cumulativeWeights; // Each cumulative weight is weight of object at this index + weight of all objects to left. float _sumOfWeights; } @end @interface OOConcreteMutableProbabilitySet: OOMutableProbabilitySet { @private NSMutableArray *_objects; NSMutableArray *_weights; float _sumOfWeights; } - (id) initPrivWithObjectArray:(NSMutableArray *)objects weightsArray:(NSMutableArray *)weights sum:(float)sumOfWeights; @end @interface OOProbabilitySetEnumerator: NSEnumerator { @private id _enumerable; NSUInteger _index; } - (id) initWithEnumerable:(id)enumerable; @end static void ThrowAbstractionViolationException(id obj) GCC_ATTR((noreturn)); @implementation OOProbabilitySet // Abstract class just tosses allocations over to concrete class, and throws exception if you try to use it directly. + (id) probabilitySet { return [[OOEmptyProbabilitySet singleton] autorelease]; } + (id) probabilitySetWithObjects:(id *)objects weights:(float *)weights count:(NSUInteger)count { return [[[self alloc] initWithObjects:objects weights:weights count:count] autorelease]; } + (id) probabilitySetWithPropertyListRepresentation:(NSDictionary *)plist { return [[[self alloc] initWithPropertyListRepresentation:plist] autorelease]; } - (id) init { [self release]; return [OOEmptyProbabilitySet singleton]; } - (id) initWithObjects:(id *)objects weights:(float *)weights count:(NSUInteger)count { NSZone *zone = [self zone]; DESTROY(self); // Zero objects: return empty-set singleton. if (count == 0) return [OOEmptyProbabilitySet singleton]; // If count is not zero and one of the paramters is nil, we've got us a programming error. if (objects == NULL || weights == NULL) { [NSException raise:NSInvalidArgumentException format:@"Attempt to create %@ with non-zero count but nil objects or weights.", @"OOProbabilitySet"]; } // Single object: simple one-object set. Expected to be quite common. if (count == 1) return [[OOSingleObjectProbabilitySet allocWithZone:zone] initWithObject:objects[0] weight:weights[0]]; // Otherwise, use general implementation. return [[OOConcreteProbabilitySet allocWithZone:zone] initWithObjects:objects weights:weights count:count]; } - (id) initWithPropertyListRepresentation:(NSDictionary *)plist { NSArray *objects = nil; NSArray *weights = nil; NSUInteger i = 0, count = 0; id *rawObjects = NULL; float *rawWeights = NULL; objects = [plist oo_arrayForKey:kObjectsKey]; weights = [plist oo_arrayForKey:kWeightsKey]; // Validate if (objects == nil || weights == nil) { [self release]; return nil; } count = [objects count]; if (count != [weights count]) { [self release]; return nil; } // Extract contents. rawObjects = malloc(sizeof *rawObjects * count); rawWeights = malloc(sizeof *rawWeights * count); if (rawObjects != NULL || rawWeights != NULL) { // Extract objects. [objects getObjects:rawObjects]; // Extract and convert weights. for (i = 0; i < count; ++i) { rawWeights[i] = fmax([weights oo_floatAtIndex:i], 0.0f); } self = [self initWithObjects:rawObjects weights:rawWeights count:count]; } else { [self release]; self = nil; } // Clean up. free(rawObjects); free(rawWeights); return self; } - (id) initPriv { return [super init]; } - (NSString *) descriptionComponents { return [NSString stringWithFormat:@"count=%lu", [self count]]; } - (NSDictionary *) propertyListRepresentation { ThrowAbstractionViolationException(self); } - (id) randomObject { ThrowAbstractionViolationException(self); } - (float) weightForObject:(id)object { ThrowAbstractionViolationException(self); } - (float) sumOfWeights { ThrowAbstractionViolationException(self); } - (NSUInteger) count { ThrowAbstractionViolationException(self); } - (NSArray *) allObjects { ThrowAbstractionViolationException(self); } - (id) copyWithZone:(NSZone *)zone { if (zone == [self zone]) { return [self retain]; } else { return [[OOProbabilitySet allocWithZone:zone] initWithPropertyListRepresentation:[self propertyListRepresentation]]; } } - (id) mutableCopyWithZone:(NSZone *)zone { return [[OOMutableProbabilitySet allocWithZone:zone] initWithPropertyListRepresentation:[self propertyListRepresentation]]; } @end @implementation OOProbabilitySet (OOExtendedProbabilitySet) - (BOOL) containsObject:(id)object { return [self weightForObject:object] >= 0.0f; } - (NSEnumerator *) objectEnumerator { return [[self allObjects] objectEnumerator]; } - (float) probabilityForObject:(id)object { float weight = [self weightForObject:object]; if (weight > 0) weight /= [self sumOfWeights]; return weight; } @end static OOEmptyProbabilitySet *sOOEmptyProbabilitySetSingleton = nil; @implementation OOEmptyProbabilitySet: OOProbabilitySet + (OOEmptyProbabilitySet *) singleton { if (sOOEmptyProbabilitySetSingleton == nil) { sOOEmptyProbabilitySetSingleton = [[self alloc] init]; } return sOOEmptyProbabilitySetSingleton; } - (NSDictionary *) propertyListRepresentation { NSArray *empty = [NSArray array]; return [NSDictionary dictionaryWithObjectsAndKeys:empty, kObjectsKey, empty, kWeightsKey, nil]; } - (id) randomObject { return nil; } - (float) weightForObject:(id)object { return -1.0f; } - (float) sumOfWeights { return 0.0f; } - (NSUInteger) count { return 0; } - (NSArray *) allObjects { return [NSArray array]; } - (id) mutableCopyWithZone:(NSZone *)zone { // A mutable copy of an empty probability set is equivalent to a new empty mutable probability set. return [[OOConcreteMutableProbabilitySet allocWithZone:zone] initPriv]; } @end @implementation OOEmptyProbabilitySet (Singleton) /* Canonical singleton boilerplate. See Cocoa Fundamentals Guide: Creating a Singleton Instance. See also +singleton above. NOTE: assumes single-threaded access. */ + (id) allocWithZone:(NSZone *)inZone { if (sOOEmptyProbabilitySetSingleton == nil) { sOOEmptyProbabilitySetSingleton = [super allocWithZone:inZone]; return sOOEmptyProbabilitySetSingleton; } return nil; } - (id) copyWithZone:(NSZone *)inZone { return self; } - (id) retain { return self; } - (NSUInteger) retainCount { return UINT_MAX; } - (void) release {} - (id) autorelease { return self; } @end @implementation OOSingleObjectProbabilitySet: OOProbabilitySet - (id) initWithObject:(id)object weight:(float)weight { if (object == nil) { [self release]; return nil; } if ((self = [super initPriv])) { _object = [object retain]; _weight = fmax(weight, 0.0f); } return self; } - (void) dealloc { [_object release]; [super dealloc]; } - (NSDictionary *) propertyListRepresentation { return [NSDictionary dictionaryWithObjectsAndKeys: [NSArray arrayWithObject:_object], kObjectsKey, [NSArray arrayWithObject:[NSNumber numberWithFloat:_weight]], kWeightsKey, nil]; } - (id) randomObject { return _object; } - (float) weightForObject:(id)object { if ([_object isEqual:object]) return _weight; else return -1.0f; } - (float) sumOfWeights { return _weight; } - (NSUInteger) count { return 1; } - (NSArray *) allObjects { return [NSArray arrayWithObject:_object]; } - (id) mutableCopyWithZone:(NSZone *)zone { return [[OOConcreteMutableProbabilitySet allocWithZone:zone] initWithObjects:&_object weights:&_weight count:1]; } @end @implementation OOConcreteProbabilitySet - (id) initWithObjects:(id *)objects weights:(float *)weights count:(NSUInteger)count { NSUInteger i = 0; float cuWeight = 0.0f; assert(count > 1 && objects != NULL && weights != NULL); if ((self = [super initPriv])) { // Allocate arrays _objects = malloc(sizeof *objects * count); _cumulativeWeights = malloc(sizeof *_cumulativeWeights * count); if (_objects == NULL || _cumulativeWeights == NULL) { [self release]; return nil; } // Fill in arrays, retain objects, add up weights. for (i = 0; i != count; ++i) { _objects[i] = [objects[i] retain]; cuWeight += weights[i]; _cumulativeWeights[i] = cuWeight; } _count = count; _sumOfWeights = cuWeight; } return self; } - (void) dealloc { NSUInteger i = 0; if (_objects != NULL) { for (i = 0; i < _count; ++i) { [_objects[i] release]; } free(_objects); _objects = NULL; } if (_cumulativeWeights != NULL) { free(_cumulativeWeights); _cumulativeWeights = NULL; } [super dealloc]; } - (NSDictionary *) propertyListRepresentation { NSArray *objects = nil; NSMutableArray *weights = nil; float cuWeight = 0.0f, sum = 0.0f; NSUInteger i = 0; objects = [NSArray arrayWithObjects:_objects count:_count]; weights = [NSMutableArray arrayWithCapacity:_count]; for (i = 0; i < _count; ++i) { cuWeight = _cumulativeWeights[i]; [weights oo_addFloat:cuWeight - sum]; sum = cuWeight; } return [NSDictionary dictionaryWithObjectsAndKeys: objects, kObjectsKey, [[weights copy] autorelease], kWeightsKey, nil]; } - (NSUInteger) count { return _count; } - (id) privObjectForWeight:(float)target { /* Select an object at random. This is a binary search in the cumulative weights array. Since weights of zero are allowed, there may be several objects with the same cumulative weight, in which case we select the leftmost, i.e. the one where the delta is non-zero. */ NSUInteger low = 0, high = _count - 1, idx = 0; float weight = 0.0f; while (low < high) { idx = (low + high) / 2; weight = _cumulativeWeights[idx]; if (weight > target) { if (EXPECT_NOT(idx == 0)) break; high = idx - 1; } else if (weight < target) low = idx + 1; else break; } if (weight > target) { while (idx > 0 && _cumulativeWeights[idx - 1] >= target) --idx; } else { while (idx < (_count - 1) && _cumulativeWeights[idx] < target) ++idx; } assert(idx < _count); id result = _objects[idx]; return result; } - (id) randomObject { if (_sumOfWeights <= 0.0f) return nil; return [self privObjectForWeight:randf() * _sumOfWeights]; } - (float) weightForObject:(id)object { NSUInteger i; // Can't have nil in collection. if (object == nil) return -1.0f; // Perform linear search, then get weight by subtracting cumulative weight from cumulative weight to left. for (i = 0; i < _count; ++i) { if ([_objects[i] isEqual:object]) { float leftWeight = (i != 0) ? _cumulativeWeights[i - 1] : 0.0f; return _cumulativeWeights[i] - leftWeight; } } // If we got here, object not found. return -1.0f; } - (float) sumOfWeights { return _sumOfWeights; } - (NSArray *) allObjects { return [NSArray arrayWithObjects:_objects count:_count]; } - (NSEnumerator *) objectEnumerator { return [[[OOProbabilitySetEnumerator alloc] initWithEnumerable:self] autorelease]; } - (id) privObjectAtIndex:(NSUInteger)index { return (index < _count) ? _objects[index] : nil; } - (id) mutableCopyWithZone:(NSZone *)zone { id result = nil; float *weights = NULL; NSUInteger i = 0; float weight = 0.0f, sum = 0.0f; // Convert cumulative weights to "plain" weights. weights = malloc(sizeof *weights * _count); if (weights == NULL) return nil; for (i = 0; i < _count; ++i) { weight = _cumulativeWeights[i]; weights[i] = weight - sum; sum += weights[i]; } result = [[OOConcreteMutableProbabilitySet allocWithZone:zone] initWithObjects:_objects weights:weights count:_count]; free(weights); return result; } @end @implementation OOMutableProbabilitySet + (id) probabilitySet { return [[[OOConcreteMutableProbabilitySet alloc] initPriv] autorelease]; } - (id) init { NSZone *zone = [self zone]; [self release]; return [[OOConcreteMutableProbabilitySet allocWithZone:zone] initPriv]; } - (id) initWithObjects:(id *)objects weights:(float *)weights count:(NSUInteger)count { NSZone *zone = [self zone]; [self release]; return [[OOConcreteMutableProbabilitySet allocWithZone:zone] initWithObjects:objects weights:weights count:count]; } - (id) initWithPropertyListRepresentation:(NSDictionary *)plist { NSZone *zone = [self zone]; [self release]; return [[OOConcreteMutableProbabilitySet allocWithZone:zone] initWithPropertyListRepresentation:plist]; } - (id) copyWithZone:(NSZone *)zone { return [[OOProbabilitySet allocWithZone:zone] initWithPropertyListRepresentation:[self propertyListRepresentation]]; } - (void) setWeight:(float)weight forObject:(id)object { ThrowAbstractionViolationException(self); } - (void) removeObject:(id)object { ThrowAbstractionViolationException(self); } @end @implementation OOConcreteMutableProbabilitySet - (id) initPriv { if ((self = [super initPriv])) { _objects = [[NSMutableArray alloc] init]; _weights = [[NSMutableArray alloc] init]; } return self; } // For internal use by mutableCopy - (id) initPrivWithObjectArray:(NSMutableArray *)objects weightsArray:(NSMutableArray *)weights sum:(float)sumOfWeights { assert(objects != nil && weights != nil && [objects count] == [weights count] && sumOfWeights >= 0.0f); if ((self = [super initPriv])) { _objects = [objects retain]; _weights = [weights retain]; _sumOfWeights = sumOfWeights; } return self; } - (id) initWithObjects:(id *)objects weights:(float *)weights count:(NSUInteger)count { NSUInteger i = 0; // Validate parameters. if (count != 0 && (objects == NULL || weights == NULL)) { [self release]; [NSException raise:NSInvalidArgumentException format:@"Attempt to create %@ with non-zero count but nil objects or weights.", @"OOMutableProbabilitySet"]; } // Set up & go. if ((self = [self initPriv])) { for (i = 0; i != count; ++i) { [self setWeight:fmax(weights[i], 0.0f) forObject:objects[i]]; } } return self; } - (id) initWithPropertyListRepresentation:(NSDictionary *)plist { BOOL OK = YES; NSArray *objects = nil; NSArray *weights = nil; NSUInteger i = 0, count = 0; if (!(self = [super initPriv])) OK = NO; if (OK) { objects = [plist oo_arrayForKey:kObjectsKey]; weights = [plist oo_arrayForKey:kWeightsKey]; // Validate if (objects == nil || weights == nil) OK = NO; count = [objects count]; if (count != [weights count]) OK = NO; } if (OK) { for (i = 0; i < count; ++i) { [self setWeight:[weights oo_floatAtIndex:i] forObject:[objects objectAtIndex:i]]; } } if (!OK) { [self release]; self = nil; } return self; } - (void) dealloc { [_objects release]; [_weights release]; [super dealloc]; } - (NSDictionary *) propertyListRepresentation { return [NSDictionary dictionaryWithObjectsAndKeys: _objects, kObjectsKey, _weights, kWeightsKey, nil]; } - (NSUInteger) count { return [_objects count]; } - (id) randomObject { float target = 0.0f, sum = 0.0f, sumOfWeights; NSUInteger i = 0, count = 0; sumOfWeights = [self sumOfWeights]; target = randf() * sumOfWeights; count = [_objects count]; if (count == 0 || sumOfWeights <= 0.0f) return nil; for (i = 0; i < count; ++i) { sum += [_weights oo_floatAtIndex:i]; if (sum >= target) return [_objects objectAtIndex:i]; } OOLog(@"probabilitySet.broken", @"%s fell off end, returning first object. Nominal sum = %f, target = %f, actual sum = %f, count = %lu. %@", __PRETTY_FUNCTION__, sumOfWeights, target, sum, count,@"This is an internal error, please report it."); return [_objects objectAtIndex:0]; } - (float) weightForObject:(id)object { float result = -1.0f; if (object != nil) { NSUInteger index = [_objects indexOfObject:object]; if (index != NSNotFound) { result = [_weights oo_floatAtIndex:index]; if (index != 0) result -= [_weights oo_floatAtIndex:index - 1]; } } return result; } - (float) sumOfWeights { if (_sumOfWeights < 0.0f) { NSUInteger i, count; count = [self count]; _sumOfWeights = 0.0f; for (i = 0; i < count; ++i) { _sumOfWeights += [_weights oo_floatAtIndex:i]; } } return _sumOfWeights; } - (NSArray *) allObjects { return [[_objects copy] autorelease]; } - (NSEnumerator *) objectEnumerator { return [_objects objectEnumerator]; } - (void) setWeight:(float)weight forObject:(id)object { if (object == nil) return; weight = fmax(weight, 0.0f); NSUInteger index = [_objects indexOfObject:object]; if (index == NSNotFound) { [_objects addObject:object]; [_weights oo_addFloat:weight]; if (_sumOfWeights >= 0) { _sumOfWeights += weight; } // Else, _sumOfWeights is invalid and will need to be recalculated on demand. } else { _sumOfWeights = -1.0f; // Simply subtracting the relevant weight doesn't work if the weight is large, due to floating-point precision issues. [_weights replaceObjectAtIndex:index withObject:[NSNumber numberWithFloat:weight]]; } } - (void) removeObject:(id)object { if (object == nil) return; NSUInteger index = [_objects indexOfObject:object]; if (index != NSNotFound) { [_objects removeObjectAtIndex:index]; _sumOfWeights = -1.0f; // Simply subtracting the relevant weight doesn't work if the weight is large, due to floating-point precision issues. [_weights removeObjectAtIndex:index]; } } - (id) copyWithZone:(NSZone *)zone { id result = nil; id *objects = NULL; float *weights = NULL; NSUInteger i = 0, count = 0; count = [_objects count]; if (EXPECT_NOT(count == 0)) return [OOEmptyProbabilitySet singleton]; objects = malloc(sizeof *objects * count); weights = malloc(sizeof *weights * count); if (objects != NULL && weights != NULL) { [_objects getObjects:objects]; for (i = 0; i < count; ++i) { weights[i] = [_weights oo_floatAtIndex:i]; } result = [[OOProbabilitySet probabilitySetWithObjects:objects weights:weights count:count] retain]; } if (objects != NULL) free(objects); if (weights != NULL) free(weights); return result; } - (id) mutableCopyWithZone:(NSZone *)zone { return [[OOConcreteMutableProbabilitySet alloc] initPrivWithObjectArray:[[_objects mutableCopyWithZone:zone] autorelease] weightsArray:[[_weights mutableCopyWithZone:zone] autorelease] sum:_sumOfWeights]; } @end @implementation OOProbabilitySetEnumerator - (id) initWithEnumerable:(id)enumerable { if ((self = [super init])) { _enumerable = [enumerable retain]; } return self; } - (void) dealloc { [_enumerable release]; [super dealloc]; } - (id) nextObject { if (_index < [_enumerable count]) { return [_enumerable privObjectAtIndex:_index++]; } else { [_enumerable release]; _enumerable = nil; return nil; } } @end static void ThrowAbstractionViolationException(id obj) { [NSException raise:NSGenericException format:@"Attempt to use abstract class %@ - this indicates an incorrect initialization.", [obj class]]; abort(); // unreachable } oolite-1.82/src/Core/OOProfilingStopwatch.h000066400000000000000000000105461256642440500206710ustar00rootroot00000000000000/* OOProfilingStopwatch.h Oolite Testing utility to monitor elapsed times at high precision. Copyright (C) 2010-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef OOSTOPWATCH_STANDALONE #import "OOCocoa.h" #import "OOFunctionAttributes.h" #import "OOTypes.h" #endif /* Platform-specific high-resolution timer: OOHighResTimeValue is a time value. This could be a scalar, struct or pointer. OOHighResTimeValue OOGetHighResTime(void) returns the current time. OODisposeHighResTime() destroys an existing OOHighResTimeValue, if necessary. It must do nothing if the value passed is zeroed out. OOCopyHighResTime(x) returns a timer value equal to x. OOTimeDelta OOHighResTimeDeltaInSeconds(OOHighResTimeValue startTime, OOHighResTimeValue endTime) returns the difference between two time values, in seconds. */ #if OOLITE_MAC_OS_X // Mac OS X: always use MACH_ABSOLUTE_TIME. #define OO_PROFILING_STOPWATCH_MACH_ABSOLUTE_TIME 1 #import typedef uint64_t OOHighResTimeValue; #define OOGetHighResTime mach_absolute_time #define OODisposeHighResTime(time) do { (void)time; } while (0) #define OOCopyHighResTime(time) ((OOHighResTimeValue)time) #elif OOLITE_WINDOWS // Windows: if standalone, use timeGetTime... #if OOSTOPWATCH_STANDALONE #define OO_PROFILING_STOPWATCH_WINDOWS 1 typedef DWORD OOHighResTimeValue; // Rolls over once every 50 days, but we can live with that. // Note: timeGetTime returns time in milliseconds. This results in lower time resolution in Windows, but at this stage I // don't think we need to do something about it. If we really need microseond precision, we might consider an implementation // based on the Win32 API QueryPerformanceCounter function - Nikos 20100615. #define OOGetHighResTime timeGetTime #define OODisposeHighResTime(time) do { (void)time; } while (0) #define OOCopyHighResTime(time) ((OOHighResTimeValue)time) #else /* ...otherwise, use JS_Now() for higher precision. The Windows implementation does the messy work of calibrating performance counters against low-res timers. */ #define OO_PROFILING_STOPWATCH_JS_NOW 1 #endif #else // Other platforms (presumed unixy): use gettimeofday(). #define OO_PROFILING_STOPWATCH_GETTIMEOFDAY 1 #include typedef struct timeval OOHighResTimeValue; OOINLINE OOHighResTimeValue OOGetHighResTime(void) { struct timeval tv; gettimeofday(&tv, NULL); return tv; } #define OODisposeHighResTime(time) do { (void)time; } while (0) #define OOCopyHighResTime(time) ((OOHighResTimeValue)time) #endif #if OO_PROFILING_STOPWATCH_JS_NOW #include typedef int64 OOHighResTimeValue; #define OOGetHighResTime JS_Now #define OODisposeHighResTime(time) do { (void)time; } while (0) #define OOCopyHighResTime(time) ((OOHighResTimeValue)time) #endif OOTimeDelta OOHighResTimeDeltaInSeconds(OOHighResTimeValue startTime, OOHighResTimeValue endTime); @interface OOProfilingStopwatch: NSObject { @private OOHighResTimeValue _start; OOHighResTimeValue _end; BOOL _running; } + (instancetype) stopwatch; // New stopwatch is initially started. - (void) start; - (void) stop; - (OOTimeDelta) currentTime; // Returns stop time - start time if stopped, or now - start time if running. /* Resets timer to zero, returning the current value. This is drift-free, i.e. if it is called twice in a row while running the sum is an accurate time since the timer started. */ - (OOTimeDelta) reset; @end oolite-1.82/src/Core/OOProfilingStopwatch.m000066400000000000000000000064301256642440500206730ustar00rootroot00000000000000/* OOProfilingStopwatch.m Oolite Testing utility to monitor elapsed times at high precision. Copyright (C) 2010-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOProfilingStopwatch.h" @implementation OOProfilingStopwatch - (id) init { if ((self = [super init])) { _start = OOGetHighResTime(); _end = OOCopyHighResTime(_start); _running = YES; } return self; } + (instancetype) stopwatch { return [[[self alloc] init] autorelease]; } - (void) dealloc { OODisposeHighResTime(_start); OODisposeHighResTime(_end); [super dealloc]; } - (void) start { OOHighResTimeValue temp = _start; _start = OOGetHighResTime(); OODisposeHighResTime(temp); _running = YES; } - (void) stop { OOHighResTimeValue temp = _start; _end = OOGetHighResTime(); OODisposeHighResTime(temp); _running = NO; } - (OOTimeDelta) currentTime { if (_running) { OOHighResTimeValue temp = _end; _end = OOGetHighResTime(); OODisposeHighResTime(temp); } return OOHighResTimeDeltaInSeconds(_start, _end); } - (OOTimeDelta) reset { OOTimeDelta result; if (_running) { OOHighResTimeValue now = OOGetHighResTime(); result = OOHighResTimeDeltaInSeconds(_start, now); OODisposeHighResTime(_start); _start = now; } else { result = OOHighResTimeDeltaInSeconds(_start, _end); OODisposeHighResTime(_end); _end = OOCopyHighResTime(_start); } return result; } @end OOTimeDelta OOHighResTimeDeltaInSeconds(OOHighResTimeValue startTime, OOHighResTimeValue endTime) { #if OO_PROFILING_STOPWATCH_MACH_ABSOLUTE_TIME uint64_t diff = endTime - startTime; static double conversion = 0.0; if (EXPECT_NOT(conversion == 0.0)) { mach_timebase_info_data_t info; kern_return_t err = mach_timebase_info(&info); if (err == 0) { conversion = 1e-9 * (double)info.numer / (double)info.denom; } } return conversion * (double)diff; #elif OO_PROFILING_STOPWATCH_WINDOWS return 1e-3 * (double)(endTime - startTime); #elif OO_PROFILING_STOPWATCH_GETTIMEOFDAY int_fast32_t deltaS = (int_fast32_t)endTime.tv_sec - (int_fast32_t)startTime.tv_sec; int_fast32_t deltaU = (int_fast32_t)endTime.tv_usec - (int_fast32_t)startTime.tv_usec; double result = deltaU; result = (result * 1e-6) + deltaS; return result; #elif OO_PROFILING_STOPWATCH_JS_NOW return 1e-6 * (double)(endTime - startTime); #endif } oolite-1.82/src/Core/OOQuaternion.h000066400000000000000000000122471256642440500171700ustar00rootroot00000000000000/* OOQuaternion.h Mathematical framework for Oolite. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef INCLUDED_OOMATHS_h #error Do not include OOQuaternion.h directly; include OOMaths.h. #else typedef struct Quaternion { OOScalar w; OOScalar x; OOScalar y; OOScalar z; } Quaternion; extern const Quaternion kIdentityQuaternion; // 1, 0, 0, 0 extern const Quaternion kZeroQuaternion; // 0, 0, 0, 0 /* Construct quaternion */ OOINLINE Quaternion make_quaternion(OOScalar qw, OOScalar qx, OOScalar qy, OOScalar qz) INLINE_CONST_FUNC; /* Comparison of quaternions */ OOINLINE bool quaternion_equal(Quaternion a, Quaternion b) INLINE_CONST_FUNC; /* Multiply quaternions */ Quaternion quaternion_multiply(Quaternion q1, Quaternion q2) CONST_FUNC; /* Negation, or additive inverse -- negate all components */ OOINLINE Quaternion quaternion_negate(Quaternion q) INLINE_CONST_FUNC; /* Conjugate, or spacial inverse -- negate x, y, z components */ OOINLINE Quaternion quaternion_conjugate(Quaternion q) INLINE_CONST_FUNC; #if !OOMATHS_STANDALONE /* Set quaternion to random unit quaternion */ void quaternion_set_random(Quaternion *quat) NONNULL_FUNC; OOINLINE Quaternion OORandomQuaternion(void) ALWAYS_INLINE_FUNC; #endif /* Build quaternion representing a rotation around a given axis */ OOINLINE void quaternion_set_rotate_about_axis(Quaternion *quat, Vector axis, OOScalar angle) NONNULL_FUNC; /* Inner product of two quaternions */ OOINLINE OOScalar quaternion_dot_product(Quaternion q1, Quaternion q2) CONST_FUNC; /* Create basis vectors from a quaternion. */ Vector vector_forward_from_quaternion(Quaternion quat) CONST_FUNC; Vector vector_up_from_quaternion(Quaternion quat) CONST_FUNC; Vector vector_right_from_quaternion(Quaternion quat) CONST_FUNC; HPVector HPvector_forward_from_quaternion(Quaternion quat) CONST_FUNC; void basis_vectors_from_quaternion(Quaternion quat, Vector *outRight, Vector *outUp, Vector *outForward); /* produce a quaternion representing an angle between two vectors. Assumes the vectors are normalized. */ Quaternion quaternion_rotation_between(Vector v0, Vector v1) CONST_FUNC; Quaternion quaternion_rotation_betweenHP(HPVector v0, HPVector v1) CONST_FUNC; /* produce a quaternion representing an angle between two vectors with a maximum arc */ Quaternion quaternion_limited_rotation_between(Vector v0, Vector v1, float maxArc) CONST_FUNC; /* Rotate a quaternion about a fixed axis. */ void quaternion_rotate_about_x(Quaternion *quat, OOScalar angle) NONNULL_FUNC; void quaternion_rotate_about_y(Quaternion *quat, OOScalar angle) NONNULL_FUNC; void quaternion_rotate_about_z(Quaternion *quat, OOScalar angle) NONNULL_FUNC; void quaternion_rotate_about_axis(Quaternion *quat, Vector axis, OOScalar angle) NONNULL_FUNC; /* Normalize quaternion */ OOINLINE void quaternion_normalize(Quaternion *quat) NONNULL_FUNC ALWAYS_INLINE_FUNC; #if __OBJC__ NSString *QuaternionDescription(Quaternion quaternion); // @"(w + xi + yj + zk)" #endif Vector quaternion_rotate_vector(Quaternion q, Vector vector) CONST_FUNC; HPVector quaternion_rotate_HPvector(Quaternion q, HPVector vector) CONST_FUNC; /*** Only inline definitions beyond this point ***/ OOINLINE Quaternion make_quaternion(OOScalar qw, OOScalar qx, OOScalar qy, OOScalar qz) { Quaternion result; result.w = qw; result.x = qx; result.y = qy; result.z = qz; return result; } OOINLINE bool quaternion_equal(Quaternion a, Quaternion b) { return a.w == b.w && a.x == b.x && a.y == b.y && a.z == b.z; } OOINLINE Quaternion quaternion_negate(Quaternion q) { return make_quaternion(-q.w, -q.x, -q.y, -q.z); } OOINLINE Quaternion quaternion_conjugate(Quaternion q) { return make_quaternion(q.w, -q.x, -q.y, -q.z); } OOINLINE void quaternion_set_rotate_about_axis(Quaternion *quat, Vector axis, OOScalar angle) { OOScalar a = angle * 0.5f; OOScalar scale = sin(a); quat->w = cos(a); quat->x = axis.x * scale; quat->y = axis.y * scale; quat->z = axis.z * scale; } OOINLINE OOScalar quaternion_dot_product(Quaternion q1, Quaternion q2) { return q1.w*q2.w + q1.x*q2.x + q1.y*q2.y + q1.z*q2.z; } OOINLINE void quaternion_normalize(Quaternion *quat) { OOScalar w = quat->w; OOScalar x = quat->x; OOScalar y = quat->y; OOScalar z = quat->z; OOScalar lv = 1.0f / sqrt(w*w + x*x + y*y + z*z); quat->w = lv * w; quat->x = lv * x; quat->y = lv * y; quat->z = lv * z; } #if !OOMATHS_STANDALONE OOINLINE Quaternion OORandomQuaternion(void) { Quaternion q; quaternion_set_random(&q); return q; } #endif #endif /* INCLUDED_OOMATHS_h */ oolite-1.82/src/Core/OOQuaternion.m000066400000000000000000000237751256642440500172050ustar00rootroot00000000000000/* OOQuaternion.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "OOMaths.h" const Quaternion kIdentityQuaternion = { 1.0f, 0.0f, 0.0f, 0.0f }; const Quaternion kZeroQuaternion = { 0.0f, 0.0f, 0.0f, 0.0f }; Quaternion quaternion_multiply(Quaternion q1, Quaternion q2) { Quaternion result; result.w = q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z; result.x = q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y; result.y = q1.w * q2.y + q1.y * q2.w + q1.z * q2.x - q1.x * q2.z; result.z = q1.w * q2.z + q1.z * q2.w + q1.x * q2.y - q1.y * q2.x; return result; } #if !OOMATHS_STANDALONE // NOTE: this is broken - its distribution is weighted towards corners of the hypercube. Probably doesn't matter, though. void quaternion_set_random(Quaternion *quat) { quat->w = (OOScalar)(Ranrot() % 1024) - 511.5f; // -511.5 to +511.5 quat->x = (OOScalar)(Ranrot() % 1024) - 511.5f; // -511.5 to +511.5 quat->y = (OOScalar)(Ranrot() % 1024) - 511.5f; // -511.5 to +511.5 quat->z = (OOScalar)(Ranrot() % 1024) - 511.5f; // -511.5 to +511.5 quaternion_normalize(quat); } #endif Vector vector_forward_from_quaternion(Quaternion quat) { OOScalar w, wy, wx; OOScalar x, xz, xx; OOScalar y, yz, yy; OOScalar z, zz; Vector res; w = quat.w; z = quat.z; y = quat.y; x = quat.x; xx = 2.0f * x; yy = 2.0f * y; zz = 2.0f * z; wx = w * xx; wy = w * yy; xx = x * xx; xz = x * zz; yy = y * yy; yz = y * zz; res.x = xz - wy; res.y = yz + wx; res.z = 1.0f - xx - yy; if (res.x||res.y||res.z) return vector_normal(res); else return make_vector(0.0f, 0.0f, 1.0f); } HPVector HPvector_forward_from_quaternion(Quaternion quat) { // HPVect: profile this later return vectorToHPVector(vector_forward_from_quaternion(quat)); } Vector vector_up_from_quaternion(Quaternion quat) { OOScalar w, wz, wx; OOScalar x, xy, xx; OOScalar y, yz, yy; OOScalar z, zz; Vector res; w = quat.w; z = quat.z; y = quat.y; x = quat.x; xx = 2.0f * x; yy = 2.0f * y; zz = 2.0f * z; wx = w * xx; wz = w * zz; xx = x * xx; xy = x * yy; yz = y * zz; zz = z * zz; res.x = xy + wz; res.y = 1.0f - xx - zz; res.z = yz - wx; if (res.x||res.y||res.z) return vector_normal(res); else return make_vector(0.0f, 1.0f, 0.0f); } Vector vector_right_from_quaternion(Quaternion quat) { OOScalar w, wz, wy; OOScalar x, xz, xy; OOScalar y, yy; OOScalar z, zz; Vector res; w = quat.w; z = quat.z; y = quat.y; x = quat.x; yy = 2.0f * y; zz = 2.0f * z; wy = w * yy; wz = w * zz; xy = x * yy; xz = x * zz; yy = y * yy; zz = z * zz; res.x = 1.0f - yy - zz; res.y = xy - wz; res.z = xz + wy; if (res.x||res.y||res.z) return vector_normal(res); else return make_vector(1.0f, 0.0f, 0.0f); } void basis_vectors_from_quaternion(Quaternion quat, Vector *outRight, Vector *outUp, Vector *outForward) { OOScalar w, wz, wy, wx; OOScalar x, xz, xy, xx; OOScalar y, yz, yy; OOScalar z, zz; w = quat.w; z = quat.z; y = quat.y; x = quat.x; xx = 2.0f * x; yy = 2.0f * y; zz = 2.0f * z; wx = w * xx; wy = w * yy; wz = w * zz; xx = x * xx; xy = x * yy; xz = x * zz; yy = y * yy; yz = y * zz; zz = z * zz; if (outRight != NULL) { outRight->x = 1.0f - yy - zz; outRight->y = xy - wz; outRight->z = xz + wy; if (outRight->x || outRight->y || outRight->z) *outRight = vector_normal(*outRight); else *outRight = make_vector(1.0f, 0.0f, 0.0f); } if (outUp != NULL) { outUp->x = xy + wz; outUp->y = 1.0f - xx - zz; outUp->z = yz - wx; if (outUp->x || outUp->y || outUp->z) *outUp = vector_normal(*outUp); else *outUp = make_vector(0.0f, 1.0f, 0.0f); } if (outForward != NULL) { outForward->x = xz - wy; outForward->y = yz + wx; outForward->z = 1.0f - xx - yy; if (outForward->x || outForward->y || outForward->z) *outForward = vector_normal(*outForward); else *outForward = make_vector(0.0f, 0.0f, 1.0f); } } Quaternion quaternion_rotation_between(Vector v0, Vector v1) { Quaternion q; OOScalar s = sqrt((1.0f + v0.x * v1.x + v0.y * v1.y + v0.z * v1.z) * 2.0f); if (EXPECT(s > 0.0f)) { OOScalar is = 1.0f / s; q.x = (v0.y * v1.z - v0.z * v1.y) * is; q.y = (v0.z * v1.x - v0.x * v1.z) * is; q.z = (v0.x * v1.y - v0.y * v1.x) * is; q.w = s * 0.5f; } else { // Is this actually a problem? if (vector_equal(v1, kBasisZVector) || vector_equal(v0, kBasisZVector)) { q = make_quaternion(0, 1, 0, 0); } else { q = kIdentityQuaternion; } // We arrive here for antiparallel vectors. Rotation axis is then undefined, but not rotating is // wrong. Probably the calling function should exclude this situation. For current // in-game use of this function we return (0,1,0,0), but generally that is also wrong. } return q; } Quaternion quaternion_rotation_betweenHP(HPVector v0, HPVector v1) { Quaternion q; OOScalar s = sqrt((1.0f + v0.x * v1.x + v0.y * v1.y + v0.z * v1.z) * 2.0f); if (EXPECT(s > 0.0f)) { OOScalar is = 1.0f / s; q.x = (v0.y * v1.z - v0.z * v1.y) * is; q.y = (v0.z * v1.x - v0.x * v1.z) * is; q.z = (v0.x * v1.y - v0.y * v1.x) * is; q.w = s * 0.5f; } else { // Is this actually a problem? if (HPvector_equal(v1, kBasisZHPVector) || HPvector_equal(v0, kBasisZHPVector)) { q = make_quaternion(0, 1, 0, 0); } else { q = kIdentityQuaternion; } // We arrive here for antiparallel vectors. Rotation axis is then undefined, but not rotating is // wrong. Probably the calling function should exclude this situation. For current // in-game use of this function we return (0,1,0,0), but generally that is also wrong. } return q; } Quaternion quaternion_limited_rotation_between(Vector v0, Vector v1, float maxArc) // vectors both normalised { Quaternion q; OOScalar min_s = 2.0f * cos(0.5f * maxArc); OOScalar s = sqrt((1.0f + v0.x * v1.x + v0.y * v1.y + v0.z * v1.z) * 2.0f); // for some antiparallel vectors, s returns a NaN instead of 0. Testing s > 0 catches both. if (EXPECT(s > 0.0f)) { if (s < min_s) // larger angle => smaller cos { OOScalar a = maxArc * 0.5f; OOScalar w = cos(a); OOScalar scale = sin(a); q.x = (v0.y * v1.z - v0.z * v1.y) * scale; q.y = (v0.z * v1.x - v0.x * v1.z) * scale; q.z = (v0.x * v1.y - v0.y * v1.x) * scale; q.w = w; } else { OOScalar is = 1.0f / s; q.x = (v0.y * v1.z - v0.z * v1.y) * is; q.y = (v0.z * v1.x - v0.x * v1.z) * is; q.z = (v0.x * v1.y - v0.y * v1.x) * is; q.w = s * 0.5f; } } else { // Is this actually a problem? q = kIdentityQuaternion; } return q; } void quaternion_rotate_about_x(Quaternion *quat, OOScalar angle) { Quaternion result; OOScalar a = angle * 0.5f; OOScalar w = cos(a); OOScalar scale = sin(a); result.w = quat->w * w - quat->x * scale; result.x = quat->w * scale + quat->x * w; result.y = quat->y * w + quat->z * scale; result.z = quat->z * w - quat->y * scale; quat->w = result.w; quat->x = result.x; quat->y = result.y; quat->z = result.z; } void quaternion_rotate_about_y(Quaternion *quat, OOScalar angle) { Quaternion result; OOScalar a = angle * 0.5f; OOScalar w = cos(a); OOScalar scale = sin(a); result.w = quat->w * w - quat->y * scale; result.x = quat->x * w - quat->z * scale; result.y = quat->w * scale + quat->y * w; result.z = quat->z * w + quat->x * scale; quat->w = result.w; quat->x = result.x; quat->y = result.y; quat->z = result.z; } void quaternion_rotate_about_z(Quaternion *quat, OOScalar angle) { Quaternion result; OOScalar a = angle * 0.5f; OOScalar w = cos(a); OOScalar scale = sin(a); result.w = quat->w * w - quat->z * scale; result.x = quat->x * w + quat->y * scale; result.y = quat->y * w - quat->x * scale; result.z = quat->w * scale + quat->z * w; quat->w = result.w; quat->x = result.x; quat->y = result.y; quat->z = result.z; } void quaternion_rotate_about_axis(Quaternion *quat, Vector axis, OOScalar angle) { Quaternion q2 /*, result */; OOScalar a = angle * 0.5f; OOScalar w = cos(a); OOScalar scale = sin(a); q2.w = w; q2.x = axis.x * scale; q2.y = axis.y * scale; q2.z = axis.z * scale; *quat = quaternion_multiply(*quat, q2); } #if __OBJC__ NSString *QuaternionDescription(Quaternion quaternion) { float x, y, z; char xs, ys, zs; x = fabs(quaternion.x); y = fabs(quaternion.y); z = fabs(quaternion.z); xs = (quaternion.x >= 0.0f) ? '+' : '-'; ys = (quaternion.y >= 0.0f) ? '+' : '-'; zs = (quaternion.z >= 0.0f) ? '+' : '-'; return [NSString stringWithFormat:@"(%g %c %gi %c %gj %c %gk)", quaternion.w, xs, x, ys, y, zs, z]; } #endif Vector quaternion_rotate_vector(Quaternion q, Vector v) { Quaternion qv; qv.w = 0.0f - q.x * v.x - q.y * v.y - q.z * v.z; qv.x = -q.w * v.x + q.y * v.z - q.z * v.y; qv.y = -q.w * v.y + q.z * v.x - q.x * v.z; qv.z = -q.w * v.z + q.x * v.y - q.y * v.x; v.x = qv.w * -q.x + qv.x * -q.w + qv.y * -q.z - qv.z * -q.y; v.y = qv.w * -q.y + qv.y * -q.w + qv.z * -q.x - qv.x * -q.z; v.z = qv.w * -q.z + qv.z * -q.w + qv.x * -q.y - qv.y * -q.x; return v; } HPVector quaternion_rotate_HPvector(Quaternion q, HPVector v) { Quaternion qv; qv.w = 0.0f - q.x * v.x - q.y * v.y - q.z * v.z; qv.x = -q.w * v.x + q.y * v.z - q.z * v.y; qv.y = -q.w * v.y + q.z * v.x - q.x * v.z; qv.z = -q.w * v.z + q.x * v.y - q.y * v.x; v.x = qv.w * -q.x + qv.x * -q.w + qv.y * -q.z - qv.z * -q.y; v.y = qv.w * -q.y + qv.y * -q.w + qv.z * -q.x - qv.x * -q.z; v.z = qv.w * -q.z + qv.z * -q.w + qv.x * -q.y - qv.y * -q.x; return v; } oolite-1.82/src/Core/OORegExpMatcher.h000066400000000000000000000036751256642440500175460ustar00rootroot00000000000000/* OORegExpMatcher.h Regular expression utility built on top of JavaScript regexp objects in lieu of Objective-C regexp support. Not thread-safe. If we had a performance-critical need for regexps, I'd want a real library, but this will do for light usage. Copyright (C) 2010-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" #include @class OOJSFunction, OOJSValue; enum { kOORegExpCaseInsensitive = JSREG_FOLD, kOORegExpMultiLine = JSREG_MULTILINE }; @interface OORegExpMatcher: NSObject { @private OOJSFunction *_tester; NSString *_cachedRegExpString; OOJSValue *_cachedRegExpObject; NSUInteger _cachedFlags; } + (instancetype) regExpMatcher; - (BOOL) string:(NSString *)string matchesExpression:(NSString *)regExp; - (BOOL) string:(NSString *)string matchesExpression:(NSString *)regExp flags:(NSUInteger)flags; @end @interface NSString (OORegExpMatcher) - (BOOL) oo_matchesRegularExpression:(NSString *)regExp; @end oolite-1.82/src/Core/OORegExpMatcher.m000066400000000000000000000100741256642440500175420ustar00rootroot00000000000000/* OORegExpMatcher.m Copyright (C) 2010-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OORegExpMatcher.h" #import "OOJSFunction.h" #import "OOJavaScriptEngine.h" // Pseudo-singleton: a single instance exists at a given time, but can be released. static OORegExpMatcher *sActiveInstance; @implementation OORegExpMatcher + (instancetype) regExpMatcher { NSAssert(![NSThread respondsToSelector:@selector(isMainThread)] || [[NSThread currentThread] isMainThread], @"OORegExpMatcher may only be used on the main thread."); if (sActiveInstance == nil) { sActiveInstance = [[[self alloc] init] autorelease]; } return sActiveInstance; } - (id) init { if ((self = [super init])) { const char *argumentNames[2] = { "string", "regexp" }; unsigned codeLine = __LINE__ + 1; // NB: should remain line before code. NSString *code = @"return regexp.test(string);"; [OOJavaScriptEngine sharedEngine]; // Summon the beast from the Pit. JSContext *context = OOJSAcquireContext(); _tester = [[OOJSFunction alloc] initWithName:@"matchesRegExp" scope:NULL code:code argumentCount:2 argumentNames:argumentNames fileName:[@__FILE__ lastPathComponent] lineNumber:codeLine context:context]; OOJSRelinquishContext(context); if (_tester == nil) DESTROY(self); } return self; } - (void) dealloc { if (sActiveInstance == self) sActiveInstance = nil; DESTROY(_tester); DESTROY(_cachedRegExpString); DESTROY(_cachedRegExpObject); [super dealloc]; } - (BOOL) string:(NSString *)string matchesExpression:(NSString *)regExp { return [self string:string matchesExpression:regExp flags:0]; } - (BOOL) string:(NSString *)string matchesExpression:(NSString *)regExp flags:(NSUInteger)flags { NSAssert(![NSThread respondsToSelector:@selector(isMainThread)] || [[NSThread currentThread] isMainThread], @"OORegExpMatcher may only be used on the main thread."); size_t expLength = [regExp length]; if (EXPECT_NOT(expLength == 0)) return NO; JSContext *context = OOJSAcquireContext(); // Create new RegExp object if necessary. if (flags != _cachedFlags || ![regExp isEqualToString:_cachedRegExpString]) { DESTROY(_cachedRegExpString); DESTROY(_cachedRegExpObject); unichar *buffer; buffer = malloc(expLength * sizeof *buffer); if (EXPECT_NOT(buffer == NULL)) return NO; [regExp getCharacters:buffer]; _cachedRegExpString = [regExp retain]; JSObject *regExpObj = JS_NewUCRegExpObjectNoStatics(context, buffer, expLength, (uintN)flags); _cachedRegExpObject = [[OOJSValue alloc] initWithJSObject:regExpObj inContext:context]; _cachedFlags = flags; free(buffer); } BOOL result = [_tester evaluatePredicateWithContext:context scope:nil arguments:[NSArray arrayWithObjects:string, _cachedRegExpObject, nil]]; OOJSRelinquishContext(context); return result; } @end @implementation NSString (OORegExpMatcher) - (BOOL) oo_matchesRegularExpression:(NSString *)regExp { return [[OORegExpMatcher regExpMatcher] string:self matchesExpression:regExp]; } @end oolite-1.82/src/Core/OORoleSet.h000066400000000000000000000046241256642440500164200ustar00rootroot00000000000000/* OORoleSet.h Manage a set of roles for a ship (or ship type), including probabilities. A role set is an immutable object. Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" @interface OORoleSet: NSObject { @private NSString *_roleString; NSDictionary *_rolesAndProbabilities; NSSet *_roles; float _totalProb; } + (instancetype) roleSetWithString:(NSString *)roleString; + (instancetype) roleSetWithRole:(NSString *)role probability:(float)probability; - (id)initWithRoleString:(NSString *)roleString; - (id)initWithRole:(NSString *)role probability:(float)probability; - (NSString *)roleString; - (BOOL)hasRole:(NSString *)role; - (float)probabilityForRole:(NSString *)role; - (BOOL)intersectsSet:(id)set; // set may be an OORoleSet or an NSSet. - (NSSet *)roles; - (NSArray *)sortedRoles; - (NSDictionary *)rolesAndProbabilities; // Returns a random role, taking probabilities into account. - (NSString *)anyRole; // Creating modified copies of role sets: - (id)roleSetWithAddedRole:(NSString *)role probability:(float)probability; - (id)roleSetWithAddedRoleIfNotSet:(NSString *)role probability:(float)probability; // Unlike the above, does not change probability if role exists. - (id)roleSetWithRemovedRole:(NSString *)role; @end // Returns a dictionary whose keys are roles and whose values are weights. NSDictionary *OOParseRolesFromString(NSString *string); oolite-1.82/src/Core/OORoleSet.m000066400000000000000000000211431256642440500164200ustar00rootroot00000000000000/* OORoleSet.m Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OORoleSet.h" #import "OOStringParsing.h" #import "OOCollectionExtractors.h" #import "OOLogging.h" @interface OORoleSet (OOPrivate) - (id)initWithRolesAndProbabilities:(NSDictionary *)dict; @end @implementation OORoleSet + (instancetype) roleSetWithString:(NSString *)roleString { return [[[self alloc] initWithRoleString:roleString] autorelease]; } + (instancetype) roleSetWithRole:(NSString *)role probability:(float)probability { return [[[self alloc] initWithRole:role probability:probability] autorelease]; } - (id)initWithRoleString:(NSString *)roleString { NSDictionary *dict = nil; dict = OOParseRolesFromString(roleString); return [self initWithRolesAndProbabilities:dict]; } - (id)initWithRole:(NSString *)role probability:(float)probability { NSDictionary *dict = nil; if (role != nil && 0 <= probability) { dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:probability] forKey:role]; } return [self initWithRolesAndProbabilities:dict]; } - (void)dealloc { [_roleString autorelease]; [_rolesAndProbabilities autorelease]; [_roles autorelease]; [super dealloc]; } - (NSString *)description { return [NSString stringWithFormat:@"<%@ %p>{%@}", [self class], self, [self roleString]]; } - (BOOL)isEqual:(id)other { if ([other isKindOfClass:[OORoleSet class]]) { return [_rolesAndProbabilities isEqual:[other rolesAndProbabilities]]; } else return NO; } - (NSUInteger)hash { return [_rolesAndProbabilities hash]; } - (id)copyWithZone:(NSZone *)zone { // Note: since object is immutable, a copy is no different from the original. return [self retain]; } - (NSString *)roleString { NSArray *roles = nil; NSEnumerator *roleEnum = nil; NSString *role = nil; float probability; NSMutableString *result = nil; BOOL first = YES; if (_roleString == nil) { // Construct role string. We always do this so that it's in a normalized form. result = [NSMutableString string]; roles = [self sortedRoles]; for (roleEnum = [roles objectEnumerator]; (role = [roleEnum nextObject]); ) { if (!first) [result appendString:@" "]; else first = NO; [result appendString:role]; probability = [self probabilityForRole:role]; if (probability != 1.0f) { [result appendFormat:@"(%g)", probability]; } } _roleString = [result copy]; } return _roleString; } - (BOOL)hasRole:(NSString *)role { return role != nil && [_rolesAndProbabilities objectForKey:role] != nil; } - (float)probabilityForRole:(NSString *)role { return [_rolesAndProbabilities oo_floatForKey:role defaultValue:0.0f]; } - (BOOL)intersectsSet:(id)set { if ([set isKindOfClass:[OORoleSet class]]) set = [set roles]; else if (![set isKindOfClass:[NSSet class]]) return NO; return [[self roles] intersectsSet:set]; } - (NSSet *)roles { if (_roles == nil) { _roles = [[NSSet alloc] initWithArray:[_rolesAndProbabilities allKeys]]; } return _roles; } - (NSArray *)sortedRoles { return [[_rolesAndProbabilities allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; } - (NSDictionary *)rolesAndProbabilities { return _rolesAndProbabilities; } - (NSString *)anyRole { NSEnumerator *roleEnum = nil; NSString *role = nil; float prob, selected; selected = randf() * _totalProb; prob = 0.0f; if ([_rolesAndProbabilities count] == 0) return nil; for (roleEnum = [_rolesAndProbabilities keyEnumerator]; (role = [roleEnum nextObject]); ) { prob += [_rolesAndProbabilities oo_floatForKey:role]; if (selected <= prob) break; } if (role == nil) { role = [[self roles] anyObject]; OOLog(@"roleSet.anyRole.failed", @"Could not get a weighted-random role from role set %@, returning unweighted selection %@. TotalProb: %g, selected: %g, prob at end: %f", self, role, _totalProb, selected, prob); } return role; } - (id)roleSetWithAddedRoleIfNotSet:(NSString *)role probability:(float)probability { NSMutableDictionary *dict = nil; if (role == nil || probability < 0 || ([self hasRole:role] && [self probabilityForRole:role] == probability)) { return [[self copy] autorelease]; } dict = [[_rolesAndProbabilities mutableCopy] autorelease]; [dict setObject:[NSNumber numberWithFloat:probability] forKey:role]; return [[[[self class] alloc] initWithRolesAndProbabilities:dict] autorelease]; } - (id)roleSetWithAddedRole:(NSString *)role probability:(float)probability { NSMutableDictionary *dict = nil; if (role == nil || probability < 0 || [self hasRole:role]) { return [[self copy] autorelease]; } dict = [[_rolesAndProbabilities mutableCopy] autorelease]; [dict setObject:[NSNumber numberWithFloat:probability] forKey:role]; return [[[[self class] alloc] initWithRolesAndProbabilities:dict] autorelease]; } - (id)roleSetWithRemovedRole:(NSString *)role { NSMutableDictionary *dict = nil; if (![self hasRole:role]) return [[self copy] autorelease]; dict = [[_rolesAndProbabilities mutableCopy] autorelease]; [dict removeObjectForKey:role]; return [[[[self class] alloc] initWithRolesAndProbabilities:dict] autorelease]; } @end @implementation OORoleSet (OOPrivate) - (id)initWithRolesAndProbabilities:(NSDictionary *)dict { NSEnumerator *roleEnum = nil; NSString *role = nil; float prob; if (dict == nil) { [self release]; return nil; } self = [super init]; if (self == nil) return nil; // Note: _roles and _roleString are derived on the fly as needed. // MKW 20090815 - if we are re-initialising this OORoleSet object, we need // to ensure that _roles and _roleString are cleared. // Why would we be re-initing? That's never valid. -- Ahruman 2010-02-06 assert(_roles == nil && _roleString == nil); NSMutableDictionary *tDict = [[dict mutableCopy] autorelease]; float thargProb = [dict oo_floatForKey:@"thargon" defaultValue:0.0f]; if ( thargProb > 0.0f && [dict objectForKey:@"EQ_THARGON"] == nil) { [tDict setObject:[NSNumber numberWithFloat:thargProb] forKey:@"EQ_THARGON"]; [tDict removeObjectForKey:@"thargon"]; } _rolesAndProbabilities = [tDict copy]; for (roleEnum = [dict keyEnumerator]; (role = [roleEnum nextObject]); ) { prob = [dict oo_floatForKey:role defaultValue:-1]; if (prob < 0) { OOLog(@"roleSet.badValue", @"Attempt to create a role set with negative or non-numerical probability for role %@.", role); [self release]; return nil; } _totalProb += prob; } return self; } @end NSDictionary *OOParseRolesFromString(NSString *string) { NSMutableDictionary *result = nil; NSArray *tokens = nil; NSUInteger i, count; NSString *role = nil; float probability; NSScanner *scanner = nil; // Split string at spaces, sanity checks, set-up. if (string == nil) return nil; tokens = ScanTokensFromString(string); count = [tokens count]; if (count == 0) return nil; result = [NSMutableDictionary dictionaryWithCapacity:count]; // Scan tokens, looking for probabilities. for (i = 0; i != count; ++i) { role = [tokens objectAtIndex:i]; probability = 1.0f; if ([role rangeOfString:@"("].location != NSNotFound) { scanner = [[NSScanner alloc] initWithString:role]; [scanner scanUpToString:@"(" intoString:&role]; [scanner scanString:@"(" intoString:NULL]; if (![scanner scanFloat:&probability]) probability = 1.0f; // Ignore rest of string [scanner release]; } // shipKey roles start with [ so other roles can't if (0 <= probability && ![role hasPrefix:@"["]) { [result setObject:[NSNumber numberWithFloat:probability] forKey:role]; } } if ([result count] == 0) result = nil; return result; } oolite-1.82/src/Core/OOShipGroup.h000066400000000000000000000037311256642440500167610ustar00rootroot00000000000000/* OOShipGroup.h A weak-referencing, mutable set of ships. Not thread safe. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #import "OOWeakReference.h" @class ShipEntity; @interface OOShipGroup: OOWeakRefObject #if OOLITE_FAST_ENUMERATION #endif { @private NSUInteger _count, _capacity; unsigned long _updateCount; OOWeakReference **_members; OOWeakReference *_leader; NSString *_name; struct JSObject *_jsSelf; } - (id) init; - (id) initWithName:(NSString *)name; + (instancetype) groupWithName:(NSString *)name; + (instancetype) groupWithName:(NSString *)name leader:(ShipEntity *)leader; - (NSString *) name; - (void) setName:(NSString *)name; - (ShipEntity *) leader; - (void) setLeader:(ShipEntity *)leader; - (NSEnumerator *) objectEnumerator; - (NSEnumerator *) mutationSafeEnumerator; // Enumerate over contents at time this is called, even if actual group is mutated. - (NSSet *) members; - (NSArray *) memberArray; // arbitrary order - (NSSet *) membersExcludingLeader; - (NSArray *) memberArrayExcludingLeader; // arbitrary order - (BOOL) containsShip:(ShipEntity *)ship; - (BOOL) addShip:(ShipEntity *)ship; - (BOOL) removeShip:(ShipEntity *)ship; - (NSUInteger) count; // NOTE: this is O(n). - (BOOL) isEmpty; @end oolite-1.82/src/Core/OOShipGroup.m000066400000000000000000000262121256642440500167650ustar00rootroot00000000000000/* OOShipGroup.m IMPLEMENTATION NOTE: This is implemented as a dynamic array rather than a hash table for the following reasons: * Ship groups are generally quite small, not motivating a more complex implementation. * The code ship groups replace was all array-based and not a significant bottleneck. * Ship groups are compacted (i.e., dead weak references removed) as a side effect of iteration. * Many uses of ship groups involve iterating over the whole group anyway. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "ShipEntity.h" #import "OOShipGroup.h" #import "OOMaths.h" enum { kMinSize = 4, kMaxFreeSpace = 128 }; @interface OOShipGroupEnumerator: NSEnumerator { // ivars are public so ShipGroupIterate() can peek at both these and OOShipGroup's. Naughty! @public OOShipGroup *_group; NSUInteger _index, _updateCount; BOOL _considerCleanup, _cleanupNeeded; } - (id) initWithShipGroup:(OOShipGroup *)group; - (NSUInteger) index; - (void) setPerformCleanup:(BOOL)flag; @end @interface OOShipGroup (Private) - (BOOL) resizeTo:(NSUInteger)newCapacity; - (void) cleanUp; - (NSUInteger) updateCount; @end static id ShipGroupIterate(OOShipGroupEnumerator *enumerator); @implementation OOShipGroup - (id) init { return [self initWithName:nil]; } - (id) initWithName:(NSString *)name { if ((self = [super init])) { _capacity = kMinSize; _members = malloc(sizeof *_members * _capacity); if (_members == NULL) { [self release]; return nil; } [self setName:name]; } return self; } + (instancetype) groupWithName:(NSString *)name { return [[[self alloc] initWithName:name] autorelease]; } + (instancetype) groupWithName:(NSString *)name leader:(ShipEntity *)leader { OOShipGroup *result = [self groupWithName:name]; [result setLeader:leader]; return result; } - (void) dealloc { NSUInteger i; for (i = 0; i < _count; i++) { [_members[i] release]; } free(_members); [_name release]; [super dealloc]; } - (NSString *) descriptionComponents { NSString *desc = [NSString stringWithFormat:@"%llu ships", (unsigned long long)_count]; if ([self name] != nil) { desc = [NSString stringWithFormat:@"\"%@\", %@", [self name], desc]; } if ([self leader] != nil) { desc = [NSString stringWithFormat:@"%@, leader: %@", desc, [[self leader] shortDescription]]; } return desc; } - (NSString *) name { return _name; } - (void) setName:(NSString *)name { _updateCount++; if (_name != name) { [_name release]; _name = [name retain]; } } - (ShipEntity *) leader { ShipEntity *result = [_leader weakRefUnderlyingObject]; // If reference is stale, delete weakref object. if (result == nil && _leader != nil) { [_leader release]; _leader = nil; } return result; } - (void) setLeader:(ShipEntity *)leader { _updateCount++; if (leader != [self leader]) { [_leader release]; [self addShip:leader]; _leader = [leader weakRetain]; } } - (NSEnumerator *) objectEnumerator { return [[[OOShipGroupEnumerator alloc] initWithShipGroup:self] autorelease]; } - (NSEnumerator *) mutationSafeEnumerator { return [[self memberArray] objectEnumerator]; } - (NSSet *) members { return [NSSet setWithArray:[self memberArray]]; } - (NSSet *) membersExcludingLeader { return [NSSet setWithArray:[self memberArrayExcludingLeader]]; } #if OOLITE_FAST_ENUMERATION - (NSArray *) memberArray { id *objects = NULL; NSUInteger count = 0; NSArray *result = nil; if (_count == 0) return [NSArray array]; objects = malloc(sizeof *objects * _count); for (id ship in self) { objects[count++] = ship; } result = [NSArray arrayWithObjects:objects count:count]; free(objects); return result; } - (NSArray *) memberArrayExcludingLeader { id *objects = NULL; NSUInteger count = 0; NSArray *result = nil; ShipEntity *leader = nil; if (_count == 0) return [NSArray array]; leader = self.leader; objects = malloc(sizeof *objects * _count); for (id ship in self) { if (ship != leader) { objects[count++] = ship; } } result = [NSArray arrayWithObjects:objects count:count]; free(objects); return result; } - (BOOL) containsShip:(ShipEntity *)ship { ShipEntity *containedShip = nil; for (containedShip in self) { if ([ship isEqual:containedShip]) { return YES; } } return NO; } #else - (NSArray *) memberArray { return [[self objectEnumerator] allObjects]; } - (NSArray *) memberArrayExcludingLeader { id *objects = NULL; NSUInteger count = 0; NSArray *result = nil; NSEnumerator *shipEnum = nil; ShipEntity *ship = nil; ShipEntity *leader = nil; if (_count == 0) return [NSArray array]; leader = [self leader]; if (leader == nil) return [self memberArray]; objects = malloc(sizeof *objects * _count); for (shipEnum = [self objectEnumerator]; (ship = [shipEnum nextObject]); ) { if (ship != leader) { objects[count++] = ship; } } result = [NSArray arrayWithObjects:objects count:count]; free(objects); return result; } - (BOOL) containsShip:(ShipEntity *)ship { OOShipGroupEnumerator *shipEnum = nil; ShipEntity *containedShip = nil; BOOL result = NO; shipEnum = (OOShipGroupEnumerator *)[self objectEnumerator]; [shipEnum setPerformCleanup:NO]; while ((containedShip = [shipEnum nextObject])) { if ([ship isEqual:containedShip]) { result = YES; break; } } // Clean up [self cleanUp]; return result; } #endif - (BOOL) addShip:(ShipEntity *)ship { _updateCount++; if ([self containsShip:ship]) return YES; // it's in the group already, result! // Ensure there's space. if (_count == _capacity) { if (![self resizeTo:(_capacity > kMaxFreeSpace) ? (_capacity + kMaxFreeSpace) : (_capacity * 2)]) { if (![self resizeTo:_capacity + 1]) { // Out of memory? return NO; } } } _members[_count++] = [ship weakRetain]; return YES; } - (BOOL) removeShip:(ShipEntity *)ship { OOShipGroupEnumerator *shipEnum = nil; ShipEntity *containedShip = nil; NSUInteger index; BOOL foundIt = NO; _updateCount++; if (ship == [self leader]) [self setLeader:nil]; shipEnum = (OOShipGroupEnumerator *)[self objectEnumerator]; [shipEnum setPerformCleanup:NO]; while ((containedShip = [shipEnum nextObject])) { if ([ship isEqual:containedShip]) { index = [shipEnum index] - 1; _members[index] = _members[--_count]; foundIt = YES; // Clean up [ship setGroup:nil]; [ship setOwner:ship]; [self cleanUp]; break; } } return foundIt; } /* TODO post-1.78: profiling indicates this is a noticeable * contributor to ShipEntity::update time. Consider optimisation: may * be possible to return _count if invalidation of weakref and group * removal in ShipEntity::dealloc keeps the data consistent anyway - * CIM */ - (NSUInteger) count { NSEnumerator *memberEnum = nil; NSUInteger result = 0; if (_count != 0) { memberEnum = [self objectEnumerator]; while ([memberEnum nextObject] != nil) result++; } assert(result == _count); return result; } - (BOOL) isEmpty { if (_count == 0) return YES; return [[self objectEnumerator] nextObject] == nil; } - (BOOL) resizeTo:(NSUInteger)newCapacity { OOWeakReference **temp = NULL; if (newCapacity < _count) return NO; temp = realloc(_members, newCapacity * sizeof *_members); if (temp == NULL) return NO; _members = temp; _capacity = newCapacity; return YES; } - (void) cleanUp { NSUInteger newCapacity = _capacity; if (_count >= kMaxFreeSpace) { if (_capacity > _count + kMaxFreeSpace) { newCapacity = _count + 1; // +1 keeps us at powers of two + multiples of kMaxFreespace. } } else { if (_capacity > _count * 2) { newCapacity = OORoundUpToPowerOf2_NS(_count); if (newCapacity < kMinSize) newCapacity = kMinSize; } } if (newCapacity != _capacity) [self resizeTo:newCapacity]; } - (NSUInteger) updateCount { return _updateCount; } static id ShipGroupIterate(OOShipGroupEnumerator *enumerator) { // The work is done here so that we can have access to both OOShipGroup's and OOShipGroupEnumerator's ivars. OOShipGroup *group = enumerator->_group; ShipEntity *result = nil; BOOL cleanupNeeded = NO; if (enumerator->_updateCount != group->_updateCount) { [NSException raise:NSGenericException format:@"Collection was mutated while being enumerated.", group]; } while (enumerator->_index < group->_count) { result = [group->_members[enumerator->_index] weakRefUnderlyingObject]; if (result != nil) { enumerator->_index++; break; } // If we got here, the group contains a stale reference to a dead ship. group->_members[enumerator->_index] = group->_members[--group->_count]; cleanupNeeded = YES; } // Clean-up handling. Only perform actual clean-up at end of iteration. if (enumerator->_considerCleanup) { enumerator->_cleanupNeeded = enumerator->_cleanupNeeded && cleanupNeeded; if (enumerator->_cleanupNeeded && result == nil) { [group cleanUp]; } } return result; } #if OOLITE_FAST_ENUMERATION - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len { NSUInteger srcIndex, dstIndex = 0; ShipEntity *item = nil; BOOL cleanupNeeded = NO; srcIndex = state->state; while (srcIndex < _count && dstIndex < len) { item = [_members[srcIndex] weakRefUnderlyingObject]; if (item != nil) { stackbuf[dstIndex++] = item; srcIndex++; } else { _members[srcIndex] = _members[--_count]; cleanupNeeded = YES; } } if (cleanupNeeded) [self cleanUp]; state->state = srcIndex; state->itemsPtr = stackbuf; state->mutationsPtr = &_updateCount; return dstIndex; } #endif /* This method exists purely to suppress Clang static analyzer warnings that this ivar is unused (but may be used by categories, which they are). FIXME: there must be a feature macro we can use to avoid actually building this into the app, but I can't find it in docs. */ - (BOOL) suppressClangStuff { return !_jsSelf; } @end @implementation OOShipGroupEnumerator - (id) initWithShipGroup:(OOShipGroup *)group { assert(group != nil); if ((self = [super init])) { _group = [group retain]; _considerCleanup = YES; _updateCount = [_group updateCount]; } return self; } - (void) dealloc { DESTROY(_group); [super dealloc]; } - (id) nextObject { return ShipGroupIterate(self); } - (NSUInteger) index { return _index; } - (void) setPerformCleanup:(BOOL)flag { _considerCleanup = flag; } @end oolite-1.82/src/Core/OOShipLibraryDescriptions.h000066400000000000000000000044031256642440500216550ustar00rootroot00000000000000/* OOShipLibraryDescriptions.h Default descriptions for ships Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "ShipEntity.h" static NSString * const kOODemoShipKey = @"ship"; static NSString * const kOODemoShipName = @"name"; // set internally only static NSString * const kOODemoShipClass = @"class"; static NSString * const kOODemoShipSummary = @"summary"; static NSString * const kOODemoShipDescription = @"description"; static NSString * const kOODemoShipShipData = @"ship_data"; static NSString * const kOODemoShipSpeed = @"speed"; static NSString * const kOODemoShipTurnRate = @"turn_rate"; static NSString * const kOODemoShipCargo = @"cargo"; static NSString * const kOODemoShipGenerator = @"generator"; static NSString * const kOODemoShipShields = @"shields"; static NSString * const kOODemoShipWitchspace = @"witchspace"; static NSString * const kOODemoShipWeapons = @"weapons"; static NSString * const kOODemoShipSize = @"size"; static NSString * const kOODemoShipConditions = @"condition_script"; NSString *OOShipLibraryCategorySingular(NSString *category); NSString *OOShipLibraryCategoryPlural(NSString *category); NSString *OOShipLibrarySpeed (ShipEntity *demo_ship); NSString *OOShipLibraryTurnRate (ShipEntity *demo_ship); NSString *OOShipLibraryCargo (ShipEntity *demo_ship); NSString *OOShipLibraryGenerator (ShipEntity *demo_ship); NSString *OOShipLibraryShields (ShipEntity *demo_ship); NSString *OOShipLibraryWitchspace (ShipEntity *demo_ship); NSString *OOShipLibraryWeapons (ShipEntity *demo_ship); NSString *OOShipLibrarySize (ShipEntity *demo_ship); oolite-1.82/src/Core/OOShipLibraryDescriptions.m000066400000000000000000000110421256642440500216570ustar00rootroot00000000000000/* OOShipLibraryDescriptions.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOShipLibraryDescriptions.h" #import "OOStringExpander.h" #import "Universe.h" NSString *OOShipLibraryCategorySingular(NSString *category) { return OOExpandKey(OOExpand(@"oolite-ship-library-category-[category]", category)); } NSString *OOShipLibraryCategoryPlural(NSString *category) { return OOExpandKey(OOExpand(@"oolite-ship-library-category-plural-[category]", category)); } NSString *OOShipLibrarySpeed (ShipEntity *demo_ship) { GLfloat param = [demo_ship maxFlightSpeed]; NSString *result = nil; if (param <= 1) { result = DESC(@"oolite-ship-library-speed-stationary"); } else if (param <= 150) { result = DESC(@"oolite-ship-library-speed-veryslow"); } else if (param <= 250) { result = DESC(@"oolite-ship-library-speed-slow"); } else if (param <= 325) { result = DESC(@"oolite-ship-library-speed-average"); } else if (param <= 425) { result = DESC(@"oolite-ship-library-speed-fast"); } else { result = DESC(@"oolite-ship-library-speed-veryfast"); } return result; } NSString *OOShipLibraryTurnRate (ShipEntity *demo_ship) { GLfloat param = [demo_ship maxFlightRoll] + (2*[demo_ship maxFlightPitch]); NSString *result = nil; if (param <= 2) { result = DESC(@"oolite-ship-library-turn-veryslow"); } else if (param <= 2.75) { result = DESC(@"oolite-ship-library-turn-slow"); } else if (param <= 4.5) { result = DESC(@"oolite-ship-library-turn-average"); } else if (param <= 6) { result = DESC(@"oolite-ship-library-turn-fast"); } else { result = DESC(@"oolite-ship-library-turn-veryfast"); } return result; } NSString *OOShipLibraryCargo (ShipEntity *demo_ship) { OOCargoQuantity param = [demo_ship maxAvailableCargoSpace]; NSString *result = nil; if (param == 0) { result = DESC(@"oolite-ship-library-cargo-none"); } else { result = [NSString stringWithFormat:DESC(@"oolite-ship-library-cargo-carried-u"),param]; } return result; } NSString *OOShipLibraryGenerator (ShipEntity *demo_ship) { float rate = [demo_ship energyRechargeRate]; NSString *result = nil; if (rate < 2.5) { result = DESC(@"oolite-ship-library-generator-weak"); } else if (rate < 3.75) { result = DESC(@"oolite-ship-library-generator-average"); } else { result = DESC(@"oolite-ship-library-generator-strong"); } return result; } NSString *OOShipLibraryShields (ShipEntity *demo_ship) { // when NPCs have actual shields, add those on as well float shields = [demo_ship maxEnergy]; NSString *result = nil; if (shields < 128) { result = DESC(@"oolite-ship-library-shields-veryweak"); } else if (shields < 192) { result = DESC(@"oolite-ship-library-shields-weak"); } else if (shields < 256) { result = DESC(@"oolite-ship-library-shields-average"); } else if (shields < 320) { result = DESC(@"oolite-ship-library-shields-strong"); } else { result = DESC(@"oolite-ship-library-shields-verystrong"); } return result; } NSString *OOShipLibraryWitchspace (ShipEntity *demo_ship) { if ([demo_ship hasHyperspaceMotor]) { return DESC(@"oolite-ship-library-witchspace-yes"); } else { return DESC(@"oolite-ship-library-witchspace-no"); } } NSString *OOShipLibraryWeapons (ShipEntity *demo_ship) { OOWeaponFacingSet facings = [demo_ship weaponFacings]; NSUInteger fixed = (facings&1)+(facings&2)/2+(facings&4)/4+(facings&8)/8; NSUInteger pylons = [demo_ship missileCapacity]; if (fixed == 0 && pylons == 0) { return DESC(@"oolite-ship-library-weapons-none"); } return [NSString stringWithFormat:DESC(@"oolite-ship-library-weapons-u-u"),fixed,pylons]; } NSString *OOShipLibrarySize (ShipEntity *demo_ship) { BoundingBox bb = [demo_ship totalBoundingBox]; return [NSString stringWithFormat:DESC(@"oolite-ship-library-size-u-u-u"),(unsigned)(bb.max.x-bb.min.x),(unsigned)(bb.max.y-bb.min.y),(unsigned)(bb.max.z-bb.min.z)]; } oolite-1.82/src/Core/OOShipRegistry.h000066400000000000000000000036141256642440500174750ustar00rootroot00000000000000/* OOShipRegistry.h Manage the set of installed ships. Copyright (C) 2008-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOCocoa.h" @class OOProbabilitySet; @interface OOShipRegistry: NSObject { @private NSDictionary *_shipData; NSDictionary *_effectData; NSArray *_demoShips; NSArray *_playerShips; NSDictionary *_probabilitySets; } + (OOShipRegistry *) sharedRegistry; + (void) reload; - (NSDictionary *) shipInfoForKey:(NSString *)key; - (NSDictionary *) effectInfoForKey:(NSString *)key; - (NSDictionary *) shipyardInfoForKey:(NSString *)key; - (OOProbabilitySet *) probabilitySetForRole:(NSString *)role; - (NSArray *) demoShipKeys; - (NSArray *) playerShipKeys; @end @interface OOShipRegistry (OOConveniences) - (NSArray *) shipKeys; - (NSArray *) shipRoles; - (NSArray *) shipKeysWithRole:(NSString *)role; - (NSString *) randomShipKeyForRole:(NSString *)role; @end oolite-1.82/src/Core/OOShipRegistry.m000066400000000000000000001635561256642440500175160ustar00rootroot00000000000000/* OOShipRegistry.m Copyright (C) 2008-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOShipRegistry.h" #import "OOCacheManager.h" #import "ResourceManager.h" #import "OOCollectionExtractors.h" #import "NSDictionaryOOExtensions.h" #import "OOProbabilitySet.h" #import "OORoleSet.h" #import "OOStringParsing.h" #import "OOMesh.h" #import "GameController.h" #import "OOLegacyScriptWhitelist.h" #import "OODeepCopy.h" #import "OOColor.h" #import "OOStringExpander.h" #import "OOShipLibraryDescriptions.h" #import "Universe.h" #import "OOJSScript.h" #import "OODebugStandards.h" #define PRELOAD 0 static void DumpStringAddrs(NSDictionary *dict, NSString *context); static NSComparisonResult SortDemoShipsByName (id a, id b, void* context); static NSComparisonResult SortDemoCategoriesByName (id a, id b, void* context); static OOShipRegistry *sSingleton = nil; static NSString * const kShipRegistryCacheName = @"ship registry"; static NSString * const kShipDataCacheKey = @"ship data"; static NSString * const kPlayerShipsCacheKey = @"player ships"; static NSString * const kRoleWeightsCacheKey = @"role weights"; static NSString * const kDefaultDemoShip = @"coriolis-station"; static NSString * const kVisualEffectRegistryCacheName = @"visual effect registry"; static NSString * const kVisualEffectDataCacheKey = @"visual effect data"; @interface OOShipRegistry (OODataLoader) - (void) loadShipData; - (void) loadDemoShipConditions; - (void) loadDemoShips; - (void) loadCachedRoleProbabilitySets; - (void) buildRoleProbabilitySets; - (BOOL) applyLikeShips:(NSMutableDictionary *)ioData withKey:(NSString *)likeKey; - (BOOL) loadAndMergeShipyard:(NSMutableDictionary *)ioData; - (BOOL) stripPrivateKeys:(NSMutableDictionary *)ioData; - (BOOL) makeShipEntriesMutable:(NSMutableDictionary *)ioData; - (BOOL) loadAndApplyShipDataOverrides:(NSMutableDictionary *)ioData; - (BOOL) canonicalizeAndTagSubentities:(NSMutableDictionary *)ioData; - (BOOL) removeUnusableEntries:(NSMutableDictionary *)ioData shipMode:(BOOL)shipMode; - (BOOL) sanitizeConditions:(NSMutableDictionary *)ioData; #if PRELOAD - (BOOL) preloadShipMeshes:(NSMutableDictionary *)ioData; #endif - (NSMutableDictionary *) mergeShip:(NSDictionary *)child withParent:(NSDictionary *)parent; - (void) mergeShipRoles:(NSString *)roles forShipKey:(NSString *)shipKey intoProbabilityMap:(NSMutableDictionary *)probabilitySets; - (NSDictionary *) canonicalizeSubentityDeclaration:(id)declaration forShip:(NSString *)shipKey shipData:(NSDictionary *)shipData fatalError:(BOOL *)outFatalError; - (NSDictionary *) translateOldStyleSubentityDeclaration:(NSString *)declaration forShip:(NSString *)shipKey shipData:(NSDictionary *)shipData fatalError:(BOOL *)outFatalError; - (NSDictionary *) translateOldStyleFlasherDeclaration:(NSArray *)tokens forShip:(NSString *)shipKey fatalError:(BOOL *)outFatalError; - (NSDictionary *) translateOldStandardBasicSubentityDeclaration:(NSArray *)tokens forShip:(NSString *)shipKey shipData:(NSDictionary *)shipData fatalError:(BOOL *)outFatalError; - (NSDictionary *) validateNewStyleSubentityDeclaration:(NSDictionary *)declaration forShip:(NSString *)shipKey fatalError:(BOOL *)outFatalError; - (NSDictionary *) validateNewStyleFlasherDeclaration:(NSDictionary *)declaration forShip:(NSString *)shipKey fatalError:(BOOL *)outFatalError; - (NSDictionary *) validateNewStyleStandardSubentityDeclaration:(NSDictionary *)declaration forShip:(NSString *)shipKey fatalError:(BOOL *)outFatalError; - (BOOL) shipIsBallTurretForKey:(NSString *)shipKey inShipData:(NSDictionary *)shipData; @end @implementation OOShipRegistry + (OOShipRegistry *) sharedRegistry { if (sSingleton == nil) { sSingleton = [[self alloc] init]; } return sSingleton; } + (void) reload { if (sSingleton != nil) { /* CIM: 'release' doesn't work - the class definition * overrides it, so this leaks memory. Needs a proper reset * method for reloading the ship registry data instead */ [sSingleton release]; sSingleton = nil; (void) [self sharedRegistry]; } } - (id) init { if ((self = [super init])) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; OOCacheManager *cache = [OOCacheManager sharedCache]; _shipData = [[cache objectForKey:kShipDataCacheKey inCache:kShipRegistryCacheName] retain]; _playerShips = [[cache objectForKey:kPlayerShipsCacheKey inCache:kShipRegistryCacheName] retain]; _effectData = [[cache objectForKey:kVisualEffectDataCacheKey inCache:kVisualEffectRegistryCacheName] retain]; if ([_shipData count] == 0) // Don't accept nil or empty { [self loadShipData]; if ([_shipData count] == 0) { [NSException raise:@"OOShipRegistryLoadFailure" format:@"Could not load any ship data."]; } if ([_playerShips count] == 0) { [NSException raise:@"OOShipRegistryLoadFailure" format:@"Could not load any player ships."]; } } [self loadDemoShipConditions]; [self loadDemoShips]; // testing only if ([_demoShips count] == 0) { [NSException raise:@"OOShipRegistryLoadFailure" format:@"Could not load or synthesize any demo ships."]; } [self loadCachedRoleProbabilitySets]; if (_probabilitySets == nil) { [self buildRoleProbabilitySets]; if ([_probabilitySets count] == 0) { [NSException raise:@"OOShipRegistryLoadFailure" format:@"Could not load or synthesize role probability sets."]; } } [pool release]; } return self; } - (void) dealloc { [_shipData release]; [_demoShips release]; [_playerShips release]; [_probabilitySets release]; [super dealloc]; } - (NSDictionary *) shipInfoForKey:(NSString *)key { return [_shipData objectForKey:key]; } - (NSDictionary *) effectInfoForKey:(NSString *)key { return [_effectData objectForKey:key]; } - (NSDictionary *) shipyardInfoForKey:(NSString *)key { return [[self shipInfoForKey:key] objectForKey:@"_oo_shipyard"]; } - (OOProbabilitySet *) probabilitySetForRole:(NSString *)role { if (role == nil) return nil; return [_probabilitySets objectForKey:role]; } - (NSArray *) demoShipKeys { // with condition scripts in use, can't cache this value [self loadDemoShips]; return [[_demoShips copy] autorelease]; } - (NSArray *) playerShipKeys { return _playerShips; } @end @implementation OOShipRegistry (OOConveniences) - (NSArray *) shipKeys { return [_shipData allKeys]; } - (NSArray *) shipRoles { return [_probabilitySets allKeys]; } - (NSArray *) shipKeysWithRole:(NSString *)role { return [[self probabilitySetForRole:role] allObjects]; } - (NSString *) randomShipKeyForRole:(NSString *)role { return [[self probabilitySetForRole:role] randomObject]; } @end @implementation OOShipRegistry (OODataLoader) /* -loadShipData Load the data for all ships. This consists of five stages: * Load merges shipdata.plist dictionary. * Apply all like_ship entries. * Load shipdata-overrides.plist and apply patches. * Load shipyard.plist, add shipyard data into ship dictionaries, and create _playerShips array. * Build role->ship type probability sets. */ - (void) loadShipData { NSMutableDictionary *result = nil; [_shipData release]; _shipData = nil; [_playerShips release]; _playerShips = nil; // Load shipdata.plist. result = [[[ResourceManager dictionaryFromFilesNamed:@"shipdata.plist" inFolder:@"Config" mergeMode:MERGE_BASIC cache:NO] mutableCopy] autorelease]; if (result == nil) return; DumpStringAddrs(result, @"shipdata.plist"); // Make each entry mutable to simplify later stages. Also removes any entries that aren't dictionaries. if (![self makeShipEntriesMutable:result]) return; OOLog(@"shipData.load.progress", @"Finished initial cleanup..."); // Apply patches. if (![self loadAndApplyShipDataOverrides:result]) return; OOLog(@"shipData.load.progress", @"Finished applying patches..."); // Strip private keys (anything starting with _oo_). if (![self stripPrivateKeys:result]) return; OOLog(@"shipData.load.progress", @"Finished stripping private keys..."); // Resolve like_ship entries. if (![self applyLikeShips:result withKey:@"like_ship"]) return; OOLog(@"shipData.load.progress", @"Finished resolving like_ships..."); // Clean up subentity declarations and tag subentities so they won't be pruned. if (![self canonicalizeAndTagSubentities:result]) return; OOLog(@"shipData.load.progress", @"Finished cleaning up subentities..."); // Clean out templates and invalid entries. if (![self removeUnusableEntries:result shipMode:YES]) return; OOLog(@"shipData.load.progress", @"Finished removing invalid entries..."); // Add shipyard entries into shipdata entries. if (![self loadAndMergeShipyard:result]) return; OOLog(@"shipData.load.progress", @"Finished adding shipyard entries..."); // Sanitize conditions. if (![self sanitizeConditions:result]) return; OOLog(@"shipData.load.progress", @"Finished validating data..."); #if PRELOAD // Preload and cache meshes. if (![self preloadShipMeshes:result]) return; OOLog(@"shipData.load.progress", @"Finished loading meshes..."); #endif _shipData = OODeepCopy(result); [[OOCacheManager sharedCache] setObject:_shipData forKey:kShipDataCacheKey inCache:kShipRegistryCacheName]; OOLog(@"shipData.load.done", @"Ship data loaded."); [_effectData release]; _effectData = nil; result = [[[ResourceManager dictionaryFromFilesNamed:@"effectdata.plist" inFolder:@"Config" mergeMode:MERGE_BASIC cache:NO] mutableCopy] autorelease]; if (result == nil) return; // Make each entry mutable to simplify later stages. Also removes any entries that aren't dictionaries. if (![self makeShipEntriesMutable:result]) return; OOLog(@"effectData.load.progress", @"Finished initial cleanup..."); // Strip private keys (anything starting with _oo_). if (![self stripPrivateKeys:result]) return; OOLog(@"effectData.load.progress", @"Finished stripping private keys..."); // Resolve like_effect entries. if (![self applyLikeShips:result withKey:@"like_effect"]) return; OOLog(@"effectData.load.progress", @"Finished resolving like_effects..."); // Clean up subentity declarations and tag subentities so they won't be pruned. if (![self canonicalizeAndTagSubentities:result]) return; OOLog(@"effectData.load.progress", @"Finished cleaning up subentities..."); // Clean out templates and invalid entries. if (![self removeUnusableEntries:result shipMode:NO]) return; OOLog(@"effectData.load.progress", @"Finished removing invalid entries..."); _effectData = OODeepCopy(result); [[OOCacheManager sharedCache] setObject:_effectData forKey:kVisualEffectDataCacheKey inCache:kVisualEffectRegistryCacheName]; OOLog(@"effectData.load.done", @"Effect data loaded."); } - (void) loadDemoShipConditions { NSMutableArray *conditionScripts = [[NSMutableArray alloc] init]; NSEnumerator *enumerator = nil; NSDictionary *key = nil; NSArray *initialDemoShips = nil; initialDemoShips = [ResourceManager arrayFromFilesNamed:@"shiplibrary.plist" inFolder:@"Config" andMerge:YES cache:NO]; for (enumerator = [initialDemoShips objectEnumerator]; (key = [enumerator nextObject]); ) { NSString *conditions = [key oo_stringForKey:kOODemoShipConditions defaultValue:nil]; if (conditions != nil) { [conditionScripts addObject:conditions]; } } [[OOCacheManager sharedCache] setObject:conditionScripts forKey:@"demoship conditions" inCache:@"condition scripts"]; [conditionScripts release]; } /* -loadDemoShips Load demoships.plist, and filter out non-existent ships. If no existing ships remain, try adding coriolis; if this fails, add any ship in shipdata. */ - (void) loadDemoShips { NSEnumerator *enumerator = nil; NSDictionary *key = nil; NSArray *initialDemoShips = nil; NSMutableArray *demoShips = nil; DESTROY(_demoShips); initialDemoShips = [ResourceManager arrayFromFilesNamed:@"shiplibrary.plist" inFolder:@"Config" andMerge:YES cache:NO]; demoShips = [NSMutableArray arrayWithArray:initialDemoShips]; // Note: iterate over initialDemoShips to avoid mutating the collection being enu,erated. for (enumerator = [initialDemoShips objectEnumerator]; (key = [enumerator nextObject]); ) { NSString *shipKey = [key oo_stringForKey:kOODemoShipKey]; if (![key isKindOfClass:[NSDictionary class]] || [self shipInfoForKey:shipKey] == nil) { [demoShips removeObject:key]; } else { NSString *conditions = [key oo_stringForKey:kOODemoShipConditions defaultValue:nil]; if (conditions != nil) { if ([PLAYER status] == STATUS_START_GAME) { // conditions always false here [demoShips removeObject:key]; } else { OOJSScript *condScript = [UNIVERSE getConditionScript:conditions]; if (condScript != nil) // should always be non-nil, but just in case { JSContext *context = OOJSAcquireContext(); BOOL OK; JSBool allow_use; jsval result; jsval args[] = { OOJSValueFromNativeObject(context, shipKey) }; OK = [condScript callMethod:OOJSID("allowShowLibraryShip") inContext:context withArguments:args count:sizeof args / sizeof *args result:&result]; if (OK) OK = JS_ValueToBoolean(context, result, &allow_use); OOJSRelinquishContext(context); if (OK && !allow_use) { /* if the script exists, the function exists, the function * returns a bool, and that bool is false, hide the * ship. Otherwise allow it as default */ [demoShips removeObject:key]; } } } } } } if ([demoShips count] == 0) { NSString *shipKey = nil; if ([self shipInfoForKey:kDefaultDemoShip] != nil) { shipKey = kDefaultDemoShip; } else { shipKey = [[_shipData allKeys] objectAtIndex:0]; } [demoShips addObject:[NSDictionary dictionaryWithObject:shipKey forKey:kOODemoShipKey]]; } // now separate out the demoships by class, and add some extra keys NSMutableDictionary *demoList = [NSMutableDictionary dictionaryWithCapacity:8]; NSMutableArray *demoClass = nil; foreach(key, demoShips) { NSString *class = [key oo_stringForKey:kOODemoShipClass defaultValue:@"ship"]; if ([OOShipLibraryCategoryPlural(class) length] == 0) { OOLog(@"shipdata.load.warning",@"Unexpected class '%@' in shiplibrary.plist for '%@'",class,[key oo_stringForKey:kOODemoShipKey]); class = @"ship"; } demoClass = [demoList objectForKey:class]; if (demoClass == nil) { [demoList setObject:[NSMutableArray array] forKey:class]; demoClass = [demoList objectForKey:class]; } NSMutableDictionary *demoEntry = [NSMutableDictionary dictionaryWithDictionary:key]; // add "name" object to dictionary from ship definition [demoEntry setObject:[[self shipInfoForKey:[demoEntry oo_stringForKey:@"ship"]] oo_stringForKey:kOODemoShipName] forKey:kOODemoShipName]; // set "class" object to standard ship if not otherwise set if (![[demoEntry oo_stringForKey:kOODemoShipClass defaultValue:nil] isEqualToString:class]) { [demoEntry setObject:class forKey:kOODemoShipClass]; } [demoClass addObject:demoEntry]; } // sort each ship list by name NSString *demoClassName = nil; foreach(demoClassName, demoList) { [[demoList objectForKey:demoClassName] sortUsingFunction:SortDemoShipsByName context:NULL]; } // and then sort the ship list list by class name _demoShips = [[[demoList allValues] sortedArrayUsingFunction:SortDemoCategoriesByName context:NULL] retain]; } - (void) loadCachedRoleProbabilitySets { NSDictionary *cachedSets = nil; NSMutableDictionary *restoredSets = nil; NSEnumerator *roleEnum = nil; NSString *role = nil; cachedSets = [[OOCacheManager sharedCache] objectForKey:kRoleWeightsCacheKey inCache:kShipRegistryCacheName]; if (cachedSets == nil) return; restoredSets = [NSMutableDictionary dictionaryWithCapacity:[cachedSets count]]; for (roleEnum = [cachedSets keyEnumerator]; (role = [roleEnum nextObject]); ) { [restoredSets setObject:[OOProbabilitySet probabilitySetWithPropertyListRepresentation:[cachedSets objectForKey:role]] forKey:role]; } _probabilitySets = [restoredSets copy]; } - (void) buildRoleProbabilitySets { NSMutableDictionary *probabilitySets = nil; NSEnumerator *shipEnum = nil; NSString *shipKey = nil; NSDictionary *shipEntry = nil; NSString *roles = nil; NSEnumerator *roleEnum = nil; NSString *role = nil; OOProbabilitySet *pset = nil; NSMutableDictionary *cacheEntry = nil; probabilitySets = [NSMutableDictionary dictionary]; // Build role sets for (shipEnum = [_shipData keyEnumerator]; (shipKey = [shipEnum nextObject]); ) { shipEntry = [_shipData objectForKey:shipKey]; roles = [shipEntry oo_stringForKey:@"roles"]; [self mergeShipRoles:roles forShipKey:shipKey intoProbabilityMap:probabilitySets]; } // Convert role sets to immutable form, and build cache entry. // Note: we iterate over a copy of the keys to avoid mutating while iterating. cacheEntry = [NSMutableDictionary dictionaryWithCapacity:[probabilitySets count]]; for (roleEnum = [[probabilitySets allKeys] objectEnumerator]; (role = [roleEnum nextObject]); ) { pset = [probabilitySets objectForKey:role]; pset = [[pset copy] autorelease]; [probabilitySets setObject:pset forKey:role]; [cacheEntry setObject:[pset propertyListRepresentation] forKey:role]; } _probabilitySets = [probabilitySets copy]; [[OOCacheManager sharedCache] setObject:cacheEntry forKey:kRoleWeightsCacheKey inCache:kShipRegistryCacheName]; } /* -applyLikeShips: Implement like_ship by copying inherited ship and overwriting with child ship values. Done iteratively to report recursive references of arbitrary depth. Also removes and reports ships whose like_ship entry does not resolve, and handles reference loops by removing all ships involved. We start with a set of keys all ships that have a like_ships entry. In each iteration, every ship whose like_ship entry does not refer to a ship which itself has a like_ship entry is finalized. If the set of pending ships does not shrink in an iteration, the remaining ships cannot be resolved (either their like_ships do not exist, or they form reference cycles) so we stop looping and report it. */ - (BOOL) applyLikeShips:(NSMutableDictionary *)ioData withKey:(NSString *)likeKey { NSMutableSet *remainingLikeShips = nil; NSEnumerator *enumerator = nil; NSString *key = nil; NSString *parentKey = nil; NSDictionary *shipEntry = nil; NSDictionary *parentEntry = nil; NSUInteger count, lastCount; NSMutableArray *reportedBadShips = nil; // Build set of ships with like_ship references remainingLikeShips = [NSMutableSet set]; for (enumerator = [ioData keyEnumerator]; (key = [enumerator nextObject]); ) { shipEntry = [ioData objectForKey:key]; if ([shipEntry oo_stringForKey:likeKey] != nil) { [remainingLikeShips addObject:key]; } } count = lastCount = [remainingLikeShips count]; while (count != 0) { for (enumerator = [[[remainingLikeShips copy] autorelease] objectEnumerator]; (key = [enumerator nextObject]); ) { // Look up like_ship entry shipEntry = [ioData objectForKey:key]; parentKey = [shipEntry objectForKey:likeKey]; if (![remainingLikeShips containsObject:parentKey]) { // If parent is fully resolved, we can resolve this child. parentEntry = [ioData objectForKey:parentKey]; shipEntry = [self mergeShip:shipEntry withParent:parentEntry]; if (shipEntry != nil) { [remainingLikeShips removeObject:key]; [ioData setObject:shipEntry forKey:key]; } } } count = [remainingLikeShips count]; if (count == lastCount) { /* Fail: we couldn't resolve all like_ship entries. Remove unresolved entries, building a list of the ones that don't have is_external_dependency set. */ reportedBadShips = [NSMutableArray array]; for (enumerator = [remainingLikeShips objectEnumerator]; (key = [enumerator nextObject]); ) { if (![[ioData oo_dictionaryForKey:key] oo_boolForKey:@"is_external_dependency"]) { [reportedBadShips addObject:key]; } [ioData removeObjectForKey:key]; } if ([reportedBadShips count] != 0) { [reportedBadShips sortUsingSelector:@selector(caseInsensitiveCompare:)]; OOLogERR(@"shipData.merge.failed", @"one or more shipdata.plist entries have %@ references that cannot be resolved: %@", likeKey, [reportedBadShips componentsJoinedByString:@", "]); // FIXME: distinguish shipdata and effectdata OOStandardsError(@"Likely missing a dependency in a manifest.plist"); } break; } lastCount = count; } return YES; } - (NSMutableDictionary *) mergeShip:(NSDictionary *)child withParent:(NSDictionary *)parent { NSMutableDictionary *result = [[parent mutableCopy] autorelease]; if (result == nil) return nil; [result addEntriesFromDictionary:child]; [result removeObjectForKey:@"like_ship"]; // Certain properties cannot be inherited. if ([child oo_stringForKey:@"display_name"] == nil) [result removeObjectForKey:@"display_name"]; if ([child oo_stringForKey:@"is_template"] == nil) [result removeObjectForKey:@"is_template"]; // Since both 'scanClass' and 'scan_class' are accepted as valid keys for the scanClass property, // we may end up with conflicting scanClass and scan_class keys from like_ship relationships getting // merged in the result dictionary. We want to always have the child overriding the parent setting // and we do that by determining which of the two keys belongs to the child dictionary and removing // the other one from the result - Nikos 20100512 if ([result oo_stringForKey:@"scan_class"] != nil && [result oo_stringForKey:@"scanClass"] != nil) { if ([child oo_stringForKey:@"scanClass"] != nil) [result removeObjectForKey:@"scan_class"]; else [result removeObjectForKey:@"scanClass"]; } // TODO: all normalised/non-normalised value name pairs need to be catered for. - Kaks 2010-05-13 if ([result oo_stringForKey:@"escort_role"] != nil && [result oo_stringForKey:@"escort-role"] != nil) { if ([child oo_stringForKey:@"escort-role"] != nil) [result removeObjectForKey:@"escort_role"]; else [result removeObjectForKey:@"escort-role"]; } if ([result oo_stringForKey:@"escort_ship"] != nil && [result oo_stringForKey:@"escort-ship"] != nil) { if ([child oo_stringForKey:@"escort-ship"] != nil) [result removeObjectForKey:@"escort_ship"]; else [result removeObjectForKey:@"escort-ship"]; } if ([result oo_stringForKey:@"is_carrier"] != nil && [result oo_stringForKey:@"isCarrier"] != nil) { if ([child oo_stringForKey:@"isCarrier"] != nil) [result removeObjectForKey:@"is_carrier"]; else [result removeObjectForKey:@"isCarrier"]; } if ([result oo_stringForKey:@"has_shipyard"] != nil && [result oo_stringForKey:@"hasShipyard"] != nil) { if ([child oo_stringForKey:@"hasShipyard"] != nil) [result removeObjectForKey:@"has_shipyard"]; else [result removeObjectForKey:@"hasShipyard"]; } return result; } - (BOOL) makeShipEntriesMutable:(NSMutableDictionary *)ioData { NSEnumerator *shipKeyEnum = nil; NSString *shipKey = nil; NSDictionary *shipEntry = nil; for (shipKeyEnum = [[ioData allKeys] objectEnumerator]; (shipKey = [shipKeyEnum nextObject]); ) { shipEntry = [ioData objectForKey:shipKey]; if (![shipEntry isKindOfClass:[NSDictionary class]]) { OOLogERR(@"shipData.load.badEntry", @"the shipdata.plist entry \"%@\" is not a dictionary.", shipKey); [ioData removeObjectForKey:shipKey]; } else { shipEntry = [shipEntry mutableCopy]; [ioData setObject:shipEntry forKey:shipKey]; [shipEntry release]; } } return YES; } - (BOOL) loadAndApplyShipDataOverrides:(NSMutableDictionary *)ioData { NSEnumerator *shipKeyEnum = nil; NSString *shipKey = nil; NSMutableDictionary *shipEntry = nil; NSDictionary *overrides = nil; NSDictionary *overridesEntry = nil; overrides = [ResourceManager dictionaryFromFilesNamed:@"shipdata-overrides.plist" inFolder:@"Config" mergeMode:MERGE_SMART cache:NO]; for (shipKeyEnum = [overrides keyEnumerator]; (shipKey = [shipKeyEnum nextObject]); ) { shipEntry = [ioData objectForKey:shipKey]; if (shipEntry != nil) { overridesEntry = [overrides objectForKey:shipKey]; if (![overridesEntry isKindOfClass:[NSDictionary class]]) { OOLogERR(@"shipData.load.error", @"the shipdata-overrides.plist entry \"%@\" is not a dictionary.", shipKey); } else { [shipEntry addEntriesFromDictionary:overridesEntry]; } } } return YES; } - (BOOL) stripPrivateKeys:(NSMutableDictionary *)ioData { NSEnumerator *shipKeyEnum = nil; NSString *shipKey = nil; NSMutableDictionary *shipEntry = nil; NSEnumerator *attrKeyEnum = nil; NSString *attrKey = nil; for (shipKeyEnum = [ioData keyEnumerator]; (shipKey = [shipKeyEnum nextObject]); ) { shipEntry = [ioData objectForKey:shipKey]; for (attrKeyEnum = [shipEntry keyEnumerator]; (attrKey = [attrKeyEnum nextObject]); ) { if ([attrKey hasPrefix:@"_oo_"]) { [shipEntry removeObjectForKey:attrKey]; } } } return YES; } /* -loadAndMergeShipyard: Load shipyard.plist, add its entries to appropriate shipyard entries as a dictionary under the key "shipyard", and build list of player ships. Before that, we strip out any "shipyard" entries already in shipdata, and apply any shipyard-overrides.plist stuff to shipyard. */ - (BOOL) loadAndMergeShipyard:(NSMutableDictionary *)ioData { NSEnumerator *shipKeyEnum = nil; NSString *shipKey = nil; NSMutableDictionary *shipEntry = nil; NSDictionary *shipyard = nil; NSDictionary *shipyardOverrides = nil; NSDictionary *shipyardEntry = nil; NSDictionary *shipyardOverridesEntry = nil; NSMutableArray *playerShips = nil; // Strip out any shipyard stuff in shipdata (there shouldn't be any). for (shipKeyEnum = [ioData keyEnumerator]; (shipKey = [shipKeyEnum nextObject]); ) { shipEntry = [ioData objectForKey:shipKey]; if ([shipEntry objectForKey:@"_oo_shipyard"] != nil) { [shipEntry removeObjectForKey:@"_oo_shipyard"]; } } shipyard = [ResourceManager dictionaryFromFilesNamed:@"shipyard.plist" inFolder:@"Config" mergeMode:MERGE_BASIC cache:NO]; shipyardOverrides = [ResourceManager dictionaryFromFilesNamed:@"shipyard-overrides.plist" inFolder:@"Config" mergeMode:MERGE_SMART cache:NO]; playerShips = [NSMutableArray arrayWithCapacity:[shipyard count]]; // Insert merged shipyard and shipyardOverrides entries. for (shipKeyEnum = [shipyard keyEnumerator]; (shipKey = [shipKeyEnum nextObject]); ) { shipEntry = [ioData objectForKey:shipKey]; if (shipEntry != nil) { shipyardEntry = [shipyard objectForKey:shipKey]; shipyardOverridesEntry = [shipyardOverrides objectForKey:shipKey]; shipyardEntry = [shipyardEntry dictionaryByAddingEntriesFromDictionary:shipyardOverridesEntry]; [shipEntry setObject:shipyardEntry forKey:@"_oo_shipyard"]; [playerShips addObject:shipKey]; } else { OOLogWARN(@"shipData.load.shipyard.unknown", @"the shipyard.plist entry \"%@\" does not have a corresponding shipdata.plist entry, ignoring.", shipKey); } } _playerShips = [playerShips copy]; [[OOCacheManager sharedCache] setObject:_playerShips forKey:kPlayerShipsCacheKey inCache:kShipRegistryCacheName]; return YES; } - (BOOL) canonicalizeAndTagSubentities:(NSMutableDictionary *)ioData { NSEnumerator *shipKeyEnum = nil; NSString *shipKey = nil; NSMutableDictionary *shipEntry = nil; NSArray *subentityDeclarations = nil; NSEnumerator *subentityEnum = nil; id subentityDecl = nil; NSDictionary *subentityDict = nil; NSString *subentityKey = nil; NSMutableDictionary *subentityShipEntry = nil; NSMutableSet *badSubentities = nil; NSString *badSubentitiesList = nil; NSMutableArray *okSubentities = nil; BOOL remove, fatal; // Convert all subentity declarations to dictionaries and add // _oo_is_subentity=YES to all entries used as subentities. // Iterate over all ships. (Iterates over a copy of keys since it mutates the dictionary.) for (shipKeyEnum = [[ioData allKeys] objectEnumerator]; (shipKey = [shipKeyEnum nextObject]); ) { shipEntry = [ioData objectForKey:shipKey]; remove = NO; badSubentities = nil; // Iterate over each subentity declaration of each ship subentityDeclarations = [shipEntry oo_arrayForKey:@"subentities"]; if (subentityDeclarations != nil) { okSubentities = [NSMutableArray arrayWithCapacity:[subentityDeclarations count]]; for (subentityEnum = [subentityDeclarations objectEnumerator]; (subentityDecl = [subentityEnum nextObject]); ) { subentityDict = [self canonicalizeSubentityDeclaration:subentityDecl forShip:shipKey shipData:ioData fatalError:&fatal]; // If entry is broken, we need to kill this ship. if (fatal) { OOStandardsError(@"Bad subentity definition found"); remove = YES; } else if (subentityDict != nil) { [okSubentities addObject:subentityDict]; // Tag subentities. if (![[subentityDict oo_stringForKey:@"type"] isEqualToString:@"flasher"]) { subentityKey = [subentityDict oo_stringForKey:@"subentity_key"]; subentityShipEntry = [ioData objectForKey:subentityKey]; if (subentityKey == nil || subentityShipEntry == nil) { // Oops, reference to non-existent subent. if (badSubentities == nil) badSubentities = [NSMutableSet set]; [badSubentities addObject:subentityKey]; } else { // Subent exists, add _oo_is_subentity so roles aren't required. [subentityShipEntry oo_setBool:YES forKey:@"_oo_is_subentity"]; } } } } // Set updated subentity list. if ([okSubentities count] != 0) { [shipEntry setObject:okSubentities forKey:@"subentities"]; } else { [shipEntry removeObjectForKey:@"subentities"]; } if (badSubentities != nil) { if (![shipEntry oo_boolForKey:@"is_external_dependency"]) { badSubentitiesList = [[[badSubentities allObjects] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] componentsJoinedByString:@", "]; OOLogERR(@"shipData.load.error", @"the shipdata.plist entry \"%@\" has unresolved subentit%@ %@.", shipKey, ([badSubentities count] == 1) ? @"y" : @"ies", badSubentitiesList); OOStandardsError(@"Bad subentity definition found"); } remove = YES; } if (remove) { // Removal is deferred to avoid bogus "entry doesn't exist" errors. [shipEntry oo_setBool:YES forKey:@"_oo_deferred_remove"]; } } } return YES; } - (BOOL) removeUnusableEntries:(NSMutableDictionary *)ioData shipMode:(BOOL)shipMode { NSEnumerator *shipKeyEnum = nil; NSString *shipKey = nil; NSMutableDictionary *shipEntry = nil; BOOL remove; NSString *modelName = nil; // Clean out invalid entries and templates. (Iterates over a copy of keys since it mutates the dictionary.) for (shipKeyEnum = [[ioData allKeys] objectEnumerator]; (shipKey = [shipKeyEnum nextObject]); ) { shipEntry = [ioData objectForKey:shipKey]; remove = NO; if ([shipEntry oo_boolForKey:@"is_template"] || [shipEntry oo_boolForKey:@"_oo_deferred_remove"]) remove = YES; else if (shipMode && [[shipEntry oo_stringForKey:@"roles"] length] == 0 && ![shipEntry oo_boolForKey:@"_oo_is_subentity"] && ![shipEntry oo_boolForKey:@"_oo_is_effect"]) { OOLogERR(@"shipData.load.error", @"the shipdata.plist entry \"%@\" specifies no %@.", shipKey, @"roles"); remove = YES; OOStandardsError(@"Error in shipdata.plist"); } else { modelName = [shipEntry oo_stringForKey:@"model"]; if (shipMode && [modelName length] == 0) { OOLogERR(@"shipData.load.error", @"the shipdata.plist entry \"%@\" specifies no %@.", shipKey, @"model"); OOStandardsError(@"Error in shipdata.plist"); remove = YES; } else if ([modelName length] != 0 && [ResourceManager pathForFileNamed:modelName inFolder:@"Models"] == nil) { OOLogERR(@"shipData.load.error", @"the shipdata.plist entry \"%@\" specifies non-existent model \"%@\".", shipKey, modelName); OOStandardsError(@"Error in shipdata.plist"); remove = YES; } } if (remove) [ioData removeObjectForKey:shipKey]; } return YES; } /* Transform conditions, determinant (if conditions array) and shipyard.conditions from hasShipyard to sanitized form. Also get list of condition_scripts */ - (BOOL) sanitizeConditions:(NSMutableDictionary *)ioData { NSEnumerator *shipKeyEnum = nil; NSString *shipKey = nil; NSMutableDictionary *shipEntry = nil; NSMutableDictionary *mutableShipyard = nil; NSArray *conditions = nil; NSArray *hasShipyard = nil; NSArray *shipyardConditions = nil; NSString *condition_script = nil; NSString *shipyard_condition_script = nil; NSMutableArray *conditionScripts = [[NSMutableArray alloc] init]; for (shipKeyEnum = [[ioData allKeys] objectEnumerator]; (shipKey = [shipKeyEnum nextObject]); ) { shipEntry = [ioData objectForKey:shipKey]; conditions = [shipEntry objectForKey:@"conditions"]; condition_script = [shipEntry oo_stringForKey:@"condition_script"]; if (condition_script != nil) { if (![conditionScripts containsObject:condition_script]) { [conditionScripts addObject:condition_script]; } } hasShipyard = [shipEntry objectForKey:@"has_shipyard"]; if (![hasShipyard isKindOfClass:[NSArray class]]) hasShipyard = nil; // May also be fuzzy boolean if (hasShipyard == nil) { hasShipyard = [shipEntry objectForKey:@"hasShipyard"]; if (![hasShipyard isKindOfClass:[NSArray class]]) hasShipyard = nil; // May also be fuzzy boolean } shipyardConditions = [[shipEntry oo_dictionaryForKey:@"_oo_shipyard"] objectForKey:@"conditions"]; shipyard_condition_script = [[shipEntry oo_dictionaryForKey:@"_oo_shipyard"] oo_stringForKey:@"condition_script"]; if (shipyard_condition_script != nil) { if (![conditionScripts containsObject:shipyard_condition_script]) { [conditionScripts addObject:shipyard_condition_script]; } } if (conditions == nil && hasShipyard && shipyardConditions == nil) continue; if (conditions != nil) { OOStandardsDeprecated([NSString stringWithFormat:@"The 'conditions' key is deprecated in shipdata entry %@",shipKey]); if (!OOEnforceStandards()) { if ([conditions isKindOfClass:[NSArray class]]) { conditions = OOSanitizeLegacyScriptConditions(conditions, [NSString stringWithFormat:@"", shipKey]); } else { OOLogWARN(@"shipdata.load.warning", @"conditions for shipdata.plist entry \"%@\" are not an array, ignoring.", shipKey); conditions = nil; } if (conditions != nil) { [shipEntry setObject:conditions forKey:@"conditions"]; } else { [shipEntry removeObjectForKey:@"conditions"]; } } } if (hasShipyard != nil) { hasShipyard = OOSanitizeLegacyScriptConditions(hasShipyard, [NSString stringWithFormat:@"", shipKey]); OOStandardsDeprecated([NSString stringWithFormat:@"Use of legacy script conditions in the 'has_shipyard' key is deprecated in shipyard entry %@",shipKey]); if (!OOEnforceStandards()) { if (hasShipyard != nil) { [shipEntry setObject:hasShipyard forKey:@"has_shipyard"]; } else { [shipEntry removeObjectForKey:@"hasShipyard"]; [shipEntry removeObjectForKey:@"has_shipyard"]; } } } if (shipyardConditions != nil) { OOStandardsDeprecated([NSString stringWithFormat:@"The 'conditions' key is deprecated in shipyard entry %@",shipKey]); if (!OOEnforceStandards()) { mutableShipyard = [[[shipEntry oo_dictionaryForKey:@"_oo_shipyard"] mutableCopy] autorelease]; if ([shipyardConditions isKindOfClass:[NSArray class]]) { shipyardConditions = OOSanitizeLegacyScriptConditions(shipyardConditions, [NSString stringWithFormat:@"", shipKey]); } else { OOLogWARN(@"shipdata.load.warning", @"conditions for shipyard.plist entry \"%@\" are not an array, ignoring.", shipKey); shipyardConditions = nil; } if (shipyardConditions != nil) { [mutableShipyard setObject:shipyardConditions forKey:@"conditions"]; } else { [mutableShipyard removeObjectForKey:@"conditions"]; } [shipEntry setObject:mutableShipyard forKey:@"_oo_shipyard"]; } } } [[OOCacheManager sharedCache] setObject:conditionScripts forKey:@"ship conditions" inCache:@"condition scripts"]; [conditionScripts release]; return YES; } #if PRELOAD - (BOOL) preloadShipMeshes:(NSMutableDictionary *)ioData { NSEnumerator *shipKeyEnum = nil; NSString *shipKey = nil; NSMutableDictionary *shipEntry = nil; BOOL remove; NSString *modelName = nil; OOMesh *mesh = nil; NSAutoreleasePool *pool = nil; NSUInteger i = 0, count; count = [ioData count]; // Preload ship meshes. (Iterates over a copy of keys since it mutates the dictionary.) for (shipKeyEnum = [[ioData allKeys] objectEnumerator]; (shipKey = [shipKeyEnum nextObject]); ) { pool = [[NSAutoreleasePool alloc] init]; [[GameController sharedController] setProgressBarValue:(float)i++ / (float)count]; shipEntry = [ioData objectForKey:shipKey]; remove = NO; modelName = [shipEntry oo_stringForKey:@"model"]; mesh = [OOMesh meshWithName:modelName materialDictionary:[shipEntry oo_dictionaryForKey:@"materials"] shadersDictionary:[shipEntry oo_dictionaryForKey:@"shaders"] smooth:[shipEntry oo_boolForKey:@"smooth"] shaderMacros:nil shaderBindingTarget:nil]; [pool release]; // NOTE: mesh is now invalid, but pointer nil check is OK. if (mesh == nil) { // FIXME: what if it's a subentity? Need to rearrange things. OOLogERR(@"shipData.load.error", @"model \"%@\" could not be loaded for ship \"%@\", removing.", modelName, shipKey); [ioData removeObjectForKey:shipKey]; } } [[GameController sharedController] setProgressBarValue:-1.0f]; return YES; } #endif - (void) mergeShipRoles:(NSString *)roles forShipKey:(NSString *)shipKey intoProbabilityMap:(NSMutableDictionary *)probabilitySets { NSDictionary *rolesAndWeights = nil; NSEnumerator *roleEnum = nil; NSString *role = nil; OOMutableProbabilitySet *probSet = nil; /* probabilitySets is a dictionary whose keys are roles and whose values are mutable probability sets, whose values are ship keys. When creating new ships Oolite looks up this probability map. To upgrade all soliton 'thargon' roles to 'EQ_THARGON' we need to swap these roles here. */ rolesAndWeights = OOParseRolesFromString(roles); // add default [shipKey] role NSMutableDictionary *mutable = [NSMutableDictionary dictionaryWithDictionary:rolesAndWeights]; [mutable setObject:[NSNumber numberWithFloat:1.0] forKey:[[[NSString alloc] initWithFormat:@"[%@]",shipKey] autorelease]]; rolesAndWeights = mutable; id thargonValue = [rolesAndWeights objectForKey:@"thargon"]; if (thargonValue != nil && [rolesAndWeights objectForKey:@"EQ_THARGON"] == nil) { NSMutableDictionary *mutable = [NSMutableDictionary dictionaryWithDictionary:rolesAndWeights]; [mutable setObject:thargonValue forKey:@"EQ_THARGON"]; rolesAndWeights = mutable; } for (roleEnum = [rolesAndWeights keyEnumerator]; (role = [roleEnum nextObject]); ) { probSet = [probabilitySets objectForKey:role]; if (probSet == nil) { probSet = [OOMutableProbabilitySet probabilitySet]; [probabilitySets setObject:probSet forKey:role]; } [probSet setWeight:[rolesAndWeights oo_floatForKey:role] forObject:shipKey]; } } - (NSDictionary *) canonicalizeSubentityDeclaration:(id)declaration forShip:(NSString *)shipKey shipData:(NSDictionary *)shipData fatalError:(BOOL *)outFatalError { NSDictionary *result = nil; assert(outFatalError != NULL); *outFatalError = NO; if ([declaration isKindOfClass:[NSString class]]) { // Update old-style string-based declaration. OOStandardsDeprecated([NSString stringWithFormat:@"Old style sub-entity declarations are deprecated in %@",shipKey]); if (!OOEnforceStandards()) { result = [self translateOldStyleSubentityDeclaration:declaration forShip:shipKey shipData:shipData fatalError:outFatalError]; } if (result != nil) { // Ensure internal translation made sense, and clean up a bit. result = [self validateNewStyleSubentityDeclaration:result forShip:shipKey fatalError:outFatalError]; } } else if ([declaration isKindOfClass:[NSDictionary class]]) { // Validate dictionary-based declaration. result = [self validateNewStyleSubentityDeclaration:declaration forShip:shipKey fatalError:outFatalError]; } else { OOLogERR(@"shipData.load.error.badSubentity", @"subentity declaration for ship %@ should be string or dictionary, found %@.", shipKey, [declaration class]); *outFatalError = YES; } // For frangible ships, bad subentities are non-fatal. if (*outFatalError && [[shipData oo_dictionaryForKey:shipKey] oo_boolForKey:@"frangible"]) *outFatalError = NO; return result; } - (NSDictionary *) translateOldStyleSubentityDeclaration:(NSString *)declaration forShip:(NSString *)shipKey shipData:(NSDictionary *)shipData fatalError:(BOOL *)outFatalError { NSArray *tokens = nil; NSString *subentityKey = nil; BOOL isFlasher; tokens = ScanTokensFromString(declaration); subentityKey = [tokens objectAtIndex:0]; isFlasher = [subentityKey isEqualToString:@"*FLASHER*"]; // Sanity check: require eight tokens. if ([tokens count] != 8) { if (!isFlasher) { OOLogERR(@"shipData.load.error.badSubentity", @"the shipdata.plist entry \"%@\" has a broken subentity definition \"%@\" (should have 8 tokens, has %lu).", shipKey, subentityKey, [tokens count]); *outFatalError = YES; } else { OOLogWARN(@"shipData.load.warning.badFlasher", @"the shipdata.plist entry \"%@\" has a broken flasher definition (should have 8 tokens, has %lu). This flasher will be ignored.", shipKey, [tokens count]); } return nil; } if (isFlasher) { return [self translateOldStyleFlasherDeclaration:tokens forShip:shipKey fatalError:outFatalError]; } else { return [self translateOldStandardBasicSubentityDeclaration:tokens forShip:shipKey shipData:shipData fatalError:outFatalError]; } } - (NSDictionary *) translateOldStyleFlasherDeclaration:(NSArray *)tokens forShip:(NSString *)shipKey fatalError:(BOOL *)outFatalError { Vector position; float size, frequency, phase, hue; NSDictionary *colorDict = nil; NSDictionary *result = nil; position.x = [tokens oo_floatAtIndex:1]; position.y = [tokens oo_floatAtIndex:2]; position.z = [tokens oo_floatAtIndex:3]; hue = [tokens oo_floatAtIndex:4]; frequency = [tokens oo_floatAtIndex:5]; phase = [tokens oo_floatAtIndex:6]; size = [tokens oo_floatAtIndex:7]; colorDict = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:hue] forKey:@"hue"]; result = [NSDictionary dictionaryWithObjectsAndKeys: @"flasher", @"type", OOPropertyListFromVector(position), @"position", [NSArray arrayWithObject:colorDict], @"colors", [NSNumber numberWithFloat:frequency], @"frequency", [NSNumber numberWithFloat:phase], @"phase", [NSNumber numberWithFloat:size], @"size", nil]; OOLog(@"shipData.translateSubentity.flasher", @"Translated flasher declaration \"%@\" to %@", [tokens componentsJoinedByString:@" "], result); return result; } - (NSDictionary *) translateOldStandardBasicSubentityDeclaration:(NSArray *)tokens forShip:(NSString *)shipKey shipData:(NSDictionary *)shipData fatalError:(BOOL *)outFatalError { NSString *subentityKey = nil; Vector position; Quaternion orientation; NSMutableDictionary *result = nil; BOOL isTurret, isDock = NO; subentityKey = [tokens oo_stringAtIndex:0]; isTurret = [self shipIsBallTurretForKey:subentityKey inShipData:shipData]; position.x = [tokens oo_floatAtIndex:1]; position.y = [tokens oo_floatAtIndex:2]; position.z = [tokens oo_floatAtIndex:3]; orientation.w = [tokens oo_floatAtIndex:4]; orientation.x = [tokens oo_floatAtIndex:5]; orientation.y = [tokens oo_floatAtIndex:6]; orientation.z = [tokens oo_floatAtIndex:7]; if(orientation.w == 0 && orientation.x == 0 && orientation.y == 0 && orientation.z == 0) { orientation.w = 1; // avoid dividing by zero. OOLogWARN(@"shipData.load.error", @"The ship %@ has an undefined orientation for its %@ subentity. Setting it now at (1,0,0,0)", shipKey, subentityKey); } quaternion_normalize(&orientation); if (!isTurret) { isDock = [subentityKey rangeOfString:@"dock"].location != NSNotFound; } result = [NSMutableDictionary dictionaryWithCapacity:5]; [result setObject:isTurret ? @"ball_turret" : @"standard" forKey:@"type"]; [result setObject:subentityKey forKey:@"subentity_key"]; [result oo_setVector:position forKey:@"position"]; [result oo_setQuaternion:orientation forKey:@"orientation"]; if (isDock) [result oo_setBool:YES forKey:@"is_dock"]; OOLog(@"shipData.translateSubentity.standard", @"Translated subentity declaration \"%@\" to %@", [tokens componentsJoinedByString:@" "], result); return [[result copy] autorelease]; } - (NSDictionary *) validateNewStyleSubentityDeclaration:(NSDictionary *)declaration forShip:(NSString *)shipKey fatalError:(BOOL *)outFatalError { NSString *type = nil; type = [declaration oo_stringForKey:@"type"]; if (type == nil) type = @"standard"; if ([type isEqualToString:@"flasher"]) { return [self validateNewStyleFlasherDeclaration:declaration forShip:shipKey fatalError:outFatalError]; } else if ([type isEqualToString:@"standard"] || [type isEqualToString:@"ball_turret"]) { return [self validateNewStyleStandardSubentityDeclaration:declaration forShip:shipKey fatalError:outFatalError]; } else { OOLogERR(@"shipData.load.error.badSubentity", @"subentity declaration for ship %@ does not declare a valid type (must be standard, flasher or ball_turret).", shipKey); *outFatalError = YES; return nil; } } - (NSDictionary *) validateNewStyleFlasherDeclaration:(NSDictionary *)declaration forShip:(NSString *)shipKey fatalError:(BOOL *)outFatalError { NSMutableDictionary *result = nil; Vector position = kZeroVector; NSArray *colors = nil; id colorDesc = nil; float size, frequency, phase, brightfraction; BOOL initiallyOn; #define kDefaultFlasherColor @"redColor" // "Validate" is really "clean up", since all values have defaults. colors = [declaration oo_arrayForKey:@"colors"]; if ([colors count] == 0) { colorDesc = [declaration objectForKey:@"color"]; if (colorDesc == nil) colorDesc = kDefaultFlasherColor; if ([colorDesc isKindOfClass:[NSArray class]]) { // an easy made error is adding an array to "color" instead of "colors" OOLogWARN(@"shipData.load.warning.flasher.badColor", @"changing flasher for ship %@ from a color to a colors definition.", shipKey); colors = colorDesc; } else { colors = [NSArray arrayWithObject:colorDesc]; } } // Validate colours. NSMutableArray *validColors = [NSMutableArray arrayWithCapacity:[colors count]]; foreach (colorDesc, colors) { OOColor *color = [OOColor colorWithDescription:colorDesc]; if (color != nil) { [validColors addObject:[color normalizedArray]]; } else { OOLogWARN(@"shipdata.load.warning.flasher.badColor", @"skipping invalid colour specifier for flasher for ship %@.", shipKey); } } // Ensure there's at least one. if ([validColors count] == 0) { [validColors addObject:kDefaultFlasherColor]; } colors = validColors; position = [declaration oo_vectorForKey:@"position"]; size = [declaration oo_floatForKey:@"size" defaultValue:8.0]; if (size <= 0) { OOLogWARN(@"shipData.load.warning.flasher.badSize", @"skipping flasher of invalid size %g for ship %@.", size, shipKey); return nil; } brightfraction = [declaration oo_floatForKey:@"bright_fraction" defaultValue:0.5]; if (brightfraction < 0.0 || brightfraction > 1.0) { OOLogWARN(@"shipData.load.warning.flasher.badFraction", @"skipping flasher of invalid bright fraction %g for ship %@.", brightfraction, shipKey); return nil; } frequency = [declaration oo_floatForKey:@"frequency" defaultValue:2.0]; phase = [declaration oo_floatForKey:@"phase" defaultValue:0.0]; initiallyOn = [declaration oo_boolForKey:@"initially_on" defaultValue:YES]; result = [NSMutableDictionary dictionaryWithCapacity:8]; [result setObject:@"flasher" forKey:@"type"]; [result setObject:colors forKey:@"colors"]; [result oo_setVector:position forKey:@"position"]; [result setObject:[NSNumber numberWithFloat:size] forKey:@"size"]; [result setObject:[NSNumber numberWithFloat:frequency] forKey:@"frequency"]; if (phase != 0) [result setObject:[NSNumber numberWithFloat:phase] forKey:@"phase"]; [result setObject:[NSNumber numberWithFloat:brightfraction] forKey:@"bright_fraction"]; [result setObject:[NSNumber numberWithBool:initiallyOn] forKey:@"initially_on"]; return [[result copy] autorelease]; } - (NSDictionary *) validateNewStyleStandardSubentityDeclaration:(NSDictionary *)declaration forShip:(NSString *)shipKey fatalError:(BOOL *)outFatalError { NSMutableDictionary *result = nil; NSString *subentityKey = nil; Vector position = kZeroVector; Quaternion orientation = kIdentityQuaternion; BOOL isTurret; BOOL isDock = NO; float fireRate = -1.0f; // out of range constants float weaponRange = -1.0f; float weaponEnergy = -1.0f; NSDictionary *scriptInfo = nil; subentityKey = [declaration objectForKey:@"subentity_key"]; if (subentityKey == nil) { OOLogERR(@"shipData.load.error.badSubentity", @"subentity declaration for ship %@ specifies no subentity_key.", shipKey); *outFatalError = YES; return nil; } isTurret = [[declaration oo_stringForKey:@"type"] isEqualToString:@"ball_turret"]; if (isTurret) { fireRate = [declaration oo_floatForKey:@"fire_rate" defaultValue:-1.0f]; if (fireRate < 0.25f && fireRate >= 0.0f) { OOLogWARN(@"shipData.load.warning.turret.badFireRate", @"ball turret fire rate of %g for subenitity of ship %@ is invalid, using 0.25.", fireRate, shipKey); fireRate = 0.25f; } weaponRange = [declaration oo_floatForKey:@"weapon_range" defaultValue:-1.0f]; if (weaponRange > 7500.0f) { OOLogWARN(@"shipData.load.warning.turret.badWeaponRange", @"ball turret weapon range of %g for subenitity of ship %@ is too high, using 7500.", weaponRange, shipKey); weaponRange = 7500.0f; // range of primary plasma canon. } weaponEnergy = [declaration oo_floatForKey:@"weapon_energy" defaultValue:-1.0f]; if (weaponEnergy > 100.0f) { OOLogWARN(@"shipData.load.warning.turret.badWeaponEnergy", @"ball turret weapon energy of %g for subenitity of ship %@ is too high, using 100.", weaponEnergy, shipKey); weaponEnergy = 100.0f; } } else { isDock = [declaration oo_boolForKey:@"is_dock"]; } position = [declaration oo_vectorForKey:@"position"]; orientation = [declaration oo_quaternionForKey:@"orientation"]; quaternion_normalize(&orientation); scriptInfo = [declaration oo_dictionaryForKey:@"script_info"]; result = [NSMutableDictionary dictionaryWithCapacity:10]; [result setObject:isTurret ? @"ball_turret" : @"standard" forKey:@"type"]; [result setObject:subentityKey forKey:@"subentity_key"]; [result oo_setVector:position forKey:@"position"]; [result oo_setQuaternion:orientation forKey:@"orientation"]; if (isDock) { [result oo_setBool:YES forKey:@"is_dock"]; NSString* docklabel = [declaration oo_stringForKey:@"dock_label" defaultValue:@"the docking bay"]; [result setObject:docklabel forKey:@"dock_label"]; BOOL dockable = [declaration oo_boolForKey:@"allow_docking" defaultValue:YES]; BOOL playerdockable = [declaration oo_boolForKey:@"disallowed_docking_collides" defaultValue:NO]; BOOL undockable = [declaration oo_boolForKey:@"allow_launching" defaultValue:YES]; [result oo_setBool:dockable forKey:@"allow_docking"]; [result oo_setBool:playerdockable forKey:@"disallowed_docking_collides"]; [result oo_setBool:undockable forKey:@"allow_launching"]; } if (isTurret) { // default constants are defined and set in shipEntity if (fireRate > 0) [result oo_setFloat:fireRate forKey:@"fire_rate"]; if (weaponRange >= 0) [result oo_setFloat:weaponRange forKey:@"weapon_range"]; if (weaponEnergy >= 0) [result oo_setFloat:weaponEnergy forKey:@"weapon_energy"]; } if (scriptInfo != nil) { [result setObject:scriptInfo forKey:@"script_info"]; } return [[result copy] autorelease]; } - (BOOL) shipIsBallTurretForKey:(NSString *)shipKey inShipData:(NSDictionary *)shipData { // Test for presence of setup_actions containing initialiseTurret. NSArray *setupActions = nil; NSEnumerator *actionEnum = nil; NSString *action = nil; setupActions = [[shipData oo_dictionaryForKey:shipKey] oo_arrayForKey:@"setup_actions"]; for (actionEnum = [setupActions objectEnumerator]; (action = [actionEnum nextObject]); ) { if ([[ScanTokensFromString(action) objectAtIndex:0] isEqualToString:@"initialiseTurret"]) return YES; } if ([shipKey isEqualToString:@"ballturret"]) { // compatibility for OXPs using old subentity declarations and the // core turret entity return YES; } return NO; } @end @implementation OOShipRegistry (Singleton) /* Canonical singleton boilerplate. See Cocoa Fundamentals Guide: Creating a Singleton Instance. See also +sharedRegistry above. NOTE: assumes single-threaded access. */ + (id) allocWithZone:(NSZone *)inZone { if (sSingleton == nil) { OOLog(@"shipData.load.begin", @"Loading ship data."); sSingleton = [super allocWithZone:inZone]; return sSingleton; } return nil; } - (id) copyWithZone:(NSZone *)inZone { return self; } - (id) retain { return self; } - (NSUInteger) retainCount { return UINT_MAX; } - (void) release {} - (id) autorelease { return self; } @end static void GatherStringAddrsDict(NSDictionary *dict, NSMutableSet *strings, NSString *context); static void GatherStringAddrsArray(NSArray *array, NSMutableSet *strings, NSString *context); static void GatherStringAddrs(id object, NSMutableSet *strings, NSString *context); static void DumpStringAddrs(NSDictionary *dict, NSString *context) { return; static FILE *dump = NULL; if (dump == NULL) dump = fopen("strings.txt", "w"); if (dump == NULL) return; NSAutoreleasePool *pool = [NSAutoreleasePool new]; NSMutableSet *strings = [NSMutableSet set]; GatherStringAddrs(dict, strings, context); NSEnumerator *entryEnum = nil; NSDictionary *entry = nil; for (entryEnum = [strings objectEnumerator]; (entry = [entryEnum nextObject]); ) { NSString *string = [entry objectForKey:@"string"]; NSString *context = [entry objectForKey:@"context"]; void *pointer = [[entry objectForKey:@"address"] pointerValue]; string = [NSString stringWithFormat:@"%p\t%@: \"%@\"", pointer, context, string]; fprintf(dump, "%s\n", [string UTF8String]); } fprintf(dump, "\n"); fflush(dump); [pool release]; } static void GatherStringAddrsDict(NSDictionary *dict, NSMutableSet *strings, NSString *context) { NSEnumerator *keyEnum = nil; id key = nil; NSString *keyContext = [context stringByAppendingString:@" key"]; for (keyEnum = [dict keyEnumerator]; (key = [keyEnum nextObject]); ) { GatherStringAddrs(key, strings, keyContext); GatherStringAddrs([dict objectForKey:key], strings, [context stringByAppendingFormat:@".%@", key]); } } static void GatherStringAddrsArray(NSArray *array, NSMutableSet *strings, NSString *context) { NSEnumerator *vEnum = nil; NSString *v = nil; unsigned i = 0; for (vEnum = [array objectEnumerator]; (v = [vEnum nextObject]); ) { GatherStringAddrs(v, strings, [context stringByAppendingFormat:@"[%u]", i++]); } } static void GatherStringAddrs(id object, NSMutableSet *strings, NSString *context) { if ([object isKindOfClass:[NSString class]]) { NSDictionary *entry = [NSDictionary dictionaryWithObjectsAndKeys:object, @"string", [NSValue valueWithPointer:object], @"address", context, @"context", nil]; [strings addObject:entry]; } else if ([object isKindOfClass:[NSArray class]]) { GatherStringAddrsArray(object, strings, context); } else if ([object isKindOfClass:[NSDictionary class]]) { GatherStringAddrsDict(object, strings, context); } } static NSComparisonResult SortDemoShipsByName (id a, id b, void* context) { return [[a oo_stringForKey:@"name"] compare:[b oo_stringForKey:@"name"]]; } static NSComparisonResult SortDemoCategoriesByName (id a, id b, void* context) { return [OOShipLibraryCategoryPlural([[a oo_dictionaryAtIndex:0] oo_stringForKey:@"class"]) compare:OOShipLibraryCategoryPlural([[b oo_dictionaryAtIndex:0] oo_stringForKey:@"class"])]; } oolite-1.82/src/Core/OOSkyDrawable.h000066400000000000000000000033351256642440500172510ustar00rootroot00000000000000/* OOSkyDrawable.h Drawable for the sky (i.e., the surrounding space, not planetary atmosphere). Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OODrawable.h" #import "OOOpenGL.h" @class OOColor, OOTexture; @interface OOSkyDrawable: OODrawable { @private unsigned _starCount; unsigned _nebulaCount; NSMutableArray *_quadSets; GLint _displayListName; } - (id)initWithColor1:(OOColor *)color1 Color2:(OOColor *)color2 Color3:(OOColor *)color3 Color4:(OOColor *)color4 starCount:(unsigned)starCount nebulaCount:(unsigned)nebulaCount nebulaHueFix:(BOOL)nebulaHueFix clusterFactor:(float)nebulaClusterFactor alpha:(float)nebulaAlpha scale:(float)nebulaScale; @end oolite-1.82/src/Core/OOSkyDrawable.m000066400000000000000000000432521256642440500172600ustar00rootroot00000000000000/* OOSkyDrawable.m Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOSkyDrawable.h" #import "ResourceManager.h" #import "OOTexture.h" #import "GameController.h" #import "OOColor.h" #import "OOProbabilisticTextureManager.h" #import "OOGraphicsResetManager.h" #import "Universe.h" #import "OOMacroOpenGL.h" #import "NSObjectOOExtensions.h" #define SKY_ELEMENT_SCALE_FACTOR (BILLBOARD_DEPTH / 500.0f) #define NEBULA_SHUFFLE_FACTOR 0.005f #define DEBUG_COLORS 0 // If set, rgb = xyz (offset to range from 0.1 to 1.0). /* Min and max coords are 0 and 1 normally, but the default sky-render-inset-coords can be used to modify them slightly as an attempted work-around for artefacts on buggy S3/Via renderers. */ static float sMinTexCoord = 0.0f, sMaxTexCoord = 1.0f; static BOOL sInited = NO; /* Struct used to describe quads initially. This form is optimized for reasoning about. */ typedef struct OOSkyQuadDesc { Vector corners[4]; OOColor *color; OOTexture *texture; } OOSkyQuadDesc; enum { kSkyQuadSetPositionEntriesPerVertex = 3, kSkyQuadSetTexCoordEntriesPerVertex = 2, kSkyQuadSetColorEntriesPerVertex = 4 }; /* Class containing a set of quads with the same texture. This form is optimized for rendering. */ @interface OOSkyQuadSet: NSObject { @private OOTexture *_texture; unsigned _count; GLfloat *_positions; // 3 entries per vertex, 12 per quad GLfloat *_texCoords; // 2 entries per vertex, 8 per quad GLfloat *_colors; // 4 entries per vertex, 16 per quad } + (void)addQuads:(OOSkyQuadDesc *)quads count:(unsigned)count toArray:(NSMutableArray *)ioArray; - (id)initWithQuadsWithTexture:(OOTexture *)texture inArray:(OOSkyQuadDesc *)array count:(unsigned)totalCount; - (void)render; #ifndef NDEBUG - (size_t) totalSize; - (OOTexture *) texture; #endif @end /* Textures are global because there is always a sky, but the sky isn't replaced very often, so the textures are likely to fall out of the cache. */ static OOProbabilisticTextureManager *sStarTextures; static OOProbabilisticTextureManager *sNebulaTextures; static OOColor *SaturatedColorInRange(OOColor *color1, OOColor *color2, BOOL hueFix); @interface OOSkyDrawable (OOPrivate) - (void)setUpStarsWithColor1:(OOColor *)color1 color2:(OOColor *)color2; - (void)setUpNebulaeWithColor1:(OOColor *)color1 color2:(OOColor *)color2 clusterFactor:(float)nebulaClusterFactor nebulaHueFix:(BOOL)nebulaHueFix alpha:(float)nebulaAlpha scale:(float)nebulaScale; - (void)loadStarTextures; - (void)loadNebulaTextures; - (void)addQuads:(OOSkyQuadDesc *)quads count:(unsigned)count; - (void)ensureTexturesLoaded; @end @implementation OOSkyDrawable - (id)initWithColor1:(OOColor *)color1 Color2:(OOColor *)color2 Color3:(OOColor *)color3 Color4:(OOColor *)color4 starCount:(unsigned)starCount nebulaCount:(unsigned)nebulaCount nebulaHueFix:(BOOL)nebulaHueFix clusterFactor:(float)nebulaClusterFactor alpha:(float)nebulaAlpha scale:(float)nebulaScale { NSAutoreleasePool *pool = nil; if (!sInited) { sInited = YES; if ([[NSUserDefaults standardUserDefaults] boolForKey:@"sky-render-inset-coords"]) { sMinTexCoord += 1.0f/128.0f; sMaxTexCoord -= 1.0f/128.0f; } } self = [super init]; if (self == nil) return nil; _starCount = starCount; _nebulaCount = nebulaCount; pool = [[NSAutoreleasePool alloc] init]; [self setUpStarsWithColor1:color1 color2:color2]; if (![UNIVERSE reducedDetail]) { [self setUpNebulaeWithColor1:color3 color2:color4 clusterFactor:nebulaClusterFactor nebulaHueFix:nebulaHueFix alpha:nebulaAlpha scale:nebulaScale]; } [pool release]; [[OOGraphicsResetManager sharedManager] registerClient:self]; return self; } - (void)dealloc { OO_ENTER_OPENGL(); [_quadSets release]; [[OOGraphicsResetManager sharedManager] unregisterClient:self]; if (_displayListName != 0) glDeleteLists(_displayListName, 1); [super dealloc]; } - (void)renderOpaqueParts { // While technically translucent, the sky doesn't need to be depth-sorted // since it'll be behind everything else anyway. OO_ENTER_OPENGL(); OOSetOpenGLState(OPENGL_STATE_ADDITIVE_BLENDING); OOGL(glDisable(GL_DEPTH_TEST)); // don't read the depth buffer OOGL(glEnable(GL_TEXTURE_2D)); // Make stars dim in atmosphere. Note: works OK on night side because sky is dark blue, not black. GLfloat fogColor[4] = {0.02, 0.02, 0.02, 1.0}; OOGL(glFogfv(GL_FOG_COLOR, fogColor)); if (_displayListName != 0) { OOGL(glCallList(_displayListName)); } else { // Set up display list [self ensureTexturesLoaded]; _displayListName = glGenLists(1); OOGL(glNewList(_displayListName, GL_COMPILE)); OOGL(glEnableClientState(GL_TEXTURE_COORD_ARRAY)); OOGL(glEnableClientState(GL_COLOR_ARRAY)); [_quadSets makeObjectsPerformSelector:@selector(render)]; OOGL(glDisableClientState(GL_TEXTURE_COORD_ARRAY)); OOGL(glDisableClientState(GL_COLOR_ARRAY)); OOGL(glEndList()); } // Restore state OOGL(glEnable(GL_DEPTH_TEST)); OOGL(glDisable(GL_TEXTURE_2D)); // Resetting fog is draw loop's responsibility. OOVerifyOpenGLState(); OOCheckOpenGLErrors(@"OOSkyDrawable after rendering"); } - (BOOL)hasOpaqueParts { return YES; } - (GLfloat)maxDrawDistance { return INFINITY; } #ifndef NDEBUG - (NSSet *) allTextures { NSMutableSet *result = [NSMutableSet setWithCapacity:[_quadSets count]]; NSEnumerator *quadSetEnum = nil; OOSkyQuadSet *quadSet = nil; for (quadSetEnum = [_quadSets objectEnumerator]; (quadSet = [quadSetEnum nextObject]); ) { [result addObject:[quadSet texture]]; } return result; } - (size_t) totalSize { size_t result = [super totalSize]; NSEnumerator *quadSetEnum = nil; OOSkyQuadSet *quadSet = nil; for (quadSetEnum = [_quadSets objectEnumerator]; (quadSet = [quadSetEnum nextObject]); ) { result += [quadSet totalSize]; } return result; } #endif @end #if DEBUG_COLORS static OOColor *DebugColor(Vector orientation) { Vector color = vector_add(make_vector(0.55, 0.55, 0.55), vector_multiply_scalar(vector_normal(orientation), 0.45)); return [OOColor colorWithCalibratedRed:color.x green:color.y blue:color.z alpha:1.0]; } #endif @implementation OOSkyDrawable (OOPrivate) - (void)setUpStarsWithColor1:(OOColor *)color1 color2:(OOColor *)color2 { OOSkyQuadDesc *quads = NULL, *currQuad = NULL; unsigned i; Quaternion q; Vector vi, vj, vk; float size; Vector middle, offset; [self loadStarTextures]; quads = malloc(sizeof *quads * _starCount); if (quads == NULL) return; currQuad = quads; for (i = 0; i != _starCount; ++i) { // Select a direction and rotation. q = OORandomQuaternion(); basis_vectors_from_quaternion(q, &vi, &vj, &vk); // Select colour and texture. #if DEBUG_COLORS currQuad->color = DebugColor(vk); #else currQuad->color = [color1 blendedColorWithFraction:randf() ofColor:color2]; #endif currQuad->texture = [sStarTextures selectTexture]; // Not retained, since sStarTextures is never released. // Select scale; calculate centre position and offset to first corner. size = (1 + (ranrot_rand() % 6)) * SKY_ELEMENT_SCALE_FACTOR; middle = vector_multiply_scalar(vk, BILLBOARD_DEPTH); offset = vector_multiply_scalar(vector_add(vi, vj), 0.5f * size); // Scale the "side" vectors. Vector vj2 = vector_multiply_scalar(vj, size); Vector vi2 = vector_multiply_scalar(vi, size); // Set up corners. currQuad->corners[0] = vector_subtract(middle, offset); currQuad->corners[1] = vector_add(currQuad->corners[0], vj2); currQuad->corners[2] = vector_add(currQuad->corners[1], vi2); currQuad->corners[3] = vector_add(currQuad->corners[0], vi2); ++currQuad; } [self addQuads:quads count:_starCount]; free(quads); } - (void)setUpNebulaeWithColor1:(OOColor *)color1 color2:(OOColor *)color2 clusterFactor:(float)nebulaClusterFactor nebulaHueFix:(BOOL)nebulaHueFix alpha:(float)nebulaAlpha scale:(float)nebulaScale { OOSkyQuadDesc *quads = NULL, *currQuad = NULL; unsigned i, actualCount = 0, clusters = 0; OOColor *color; Quaternion q; Vector vi, vj, vk; double size, r2; Vector middle, offset; int r1; [self loadNebulaTextures]; quads = malloc(sizeof *quads * _nebulaCount); if (quads == NULL) return; currQuad = quads; for (i = 0; i < _nebulaCount; ++i) { color = SaturatedColorInRange(color1, color2, nebulaHueFix); // Select a direction and rotation. q = OORandomQuaternion(); // Create a cluster of nebula quads. while ((i < _nebulaCount) && (randf() < nebulaClusterFactor)) { // Select size. r1 = 1 + (ranrot_rand() & 15); size = nebulaScale * r1 * SKY_ELEMENT_SCALE_FACTOR; // Calculate centre position and offset to first corner. basis_vectors_from_quaternion(q, &vi, &vj, &vk); // Select colour and texture. Smaller nebula quads are dimmer. #if DEBUG_COLORS currQuad->color = DebugColor(vk); #else currQuad->color = [color colorWithBrightnessFactor:nebulaAlpha * (0.5f + (float)r1 / 32.0f)]; #endif currQuad->texture = [sNebulaTextures selectTexture]; // Not retained, since sStarTextures is never released. middle = vector_multiply_scalar(vk, BILLBOARD_DEPTH); offset = vector_multiply_scalar(vector_add(vi, vj), 0.5f * size); // Rotate vi and vj by a random angle r2 = randf() * M_PI * 2.0; quaternion_rotate_about_axis(&q, vk, r2); vi = vector_right_from_quaternion(q); vj = vector_up_from_quaternion(q); // Scale the "side" vectors. vj = vector_multiply_scalar(vj, size); vi = vector_multiply_scalar(vi, size); // Set up corners. currQuad->corners[0] = vector_subtract(middle, offset); currQuad->corners[1] = vector_add(currQuad->corners[0], vj); currQuad->corners[2] = vector_add(currQuad->corners[1], vi); currQuad->corners[3] = vector_add(currQuad->corners[0], vi); // Shuffle direction quat around a bit to spread the cluster out. size = NEBULA_SHUFFLE_FACTOR / (nebulaScale * SKY_ELEMENT_SCALE_FACTOR); q.x += size * (randf() - 0.5); q.y += size * (randf() - 0.5); q.z += size * (randf() - 0.5); q.w += size * (randf() - 0.5); quaternion_normalize(&q); ++i; ++currQuad; ++actualCount; } ++clusters; } /* The above code generates less than _nebulaCount quads, because i is incremented once in the outer loop as well as in the inner loop. To keep skies looking the same, we leave the bug in and fill in the actual generated count here. */ _nebulaCount = actualCount; [self addQuads:quads count:_nebulaCount]; free(quads); } - (void)addQuads:(OOSkyQuadDesc *)quads count:(unsigned)count { if (_quadSets == nil) _quadSets = [[NSMutableArray alloc] init]; [OOSkyQuadSet addQuads:quads count:count toArray:_quadSets]; } - (void)loadStarTextures { if (sStarTextures == nil) { sStarTextures = [[OOProbabilisticTextureManager alloc] initWithPListName:@"startextures.plist" options:kOOTextureMinFilterMipMap | kOOTextureMagFilterLinear | kOOTextureAlphaMask anisotropy:0.0f lodBias:-0.0f]; if (sStarTextures == nil) { [NSException raise:OOLITE_EXCEPTION_DATA_NOT_FOUND format:@"No star textures could be loaded."]; } } [sStarTextures setSeed:RANROTGetFullSeed()]; } - (void)loadNebulaTextures { if (sNebulaTextures == nil) { sNebulaTextures = [[OOProbabilisticTextureManager alloc] initWithPListName:@"nebulatextures.plist" options:kOOTextureDefaultOptions | kOOTextureAlphaMask anisotropy:0.0f lodBias:0.0f]; if (sNebulaTextures == nil) { [NSException raise:OOLITE_EXCEPTION_DATA_NOT_FOUND format:@"No nebula textures could be loaded."]; } } [sNebulaTextures setSeed:RANROTGetFullSeed()]; } - (void)ensureTexturesLoaded { [sStarTextures ensureTexturesLoaded]; [sNebulaTextures ensureTexturesLoaded]; } - (void)resetGraphicsState { OO_ENTER_OPENGL(); if (_displayListName != 0) { glDeleteLists(_displayListName, 1); _displayListName = 0; } } @end @implementation OOSkyQuadSet + (void)addQuads:(OOSkyQuadDesc *)quads count:(unsigned)count toArray:(NSMutableArray *)ioArray { NSMutableSet *seenTextures = nil; OOTexture *texture = nil; OOSkyQuadSet *quadSet = nil; unsigned i; // Iterate over all quads. seenTextures = [NSMutableSet set]; for (i = 0; i != count; ++i) { texture = quads[i].texture; // If we haven't seen this quad's texture before... if (![seenTextures containsObject:texture]) { [seenTextures addObject:texture]; // ...create a quad set for this texture. quadSet = [[self alloc] initWithQuadsWithTexture:texture inArray:quads count:count]; if (quadSet != nil) { [ioArray addObject:quadSet]; [quadSet release]; } } } } - (id)initWithQuadsWithTexture:(OOTexture *)texture inArray:(OOSkyQuadDesc *)array count:(unsigned)totalCount { BOOL OK = YES; unsigned i, j, vertexCount; GLfloat *pos; GLfloat *tc; GLfloat *col; GLfloat r, g, b; size_t posSize, tcSize, colSize; unsigned count = 0; self = [super init]; if (self == nil) OK = NO; if (OK) { // Count the quads in the array using this texture. for (i = 0; i != totalCount; ++i) { if (array[i].texture == texture) ++_count; } if (_count == 0) OK = NO; } if (OK) { // Allocate arrays. vertexCount = _count * 4; posSize = sizeof *_positions * vertexCount * kSkyQuadSetPositionEntriesPerVertex; tcSize = sizeof *_texCoords * vertexCount * kSkyQuadSetTexCoordEntriesPerVertex; colSize = sizeof *_colors * vertexCount * kSkyQuadSetColorEntriesPerVertex; _positions = malloc(posSize); _texCoords = malloc(tcSize); _colors = malloc(colSize); if (_positions == NULL || _texCoords == NULL || _colors == NULL) OK = NO; pos = _positions; tc = _texCoords; col = _colors; } if (OK) { // Find the matching quads again, and convert them to renderable representation. for (i = 0; i != totalCount; ++i) { if (array[i].texture == texture) { r = [array[i].color redComponent]; g = [array[i].color greenComponent]; b = [array[i].color blueComponent]; // Loop over vertices for (j = 0; j != 4; ++j) { *pos++ = array[i].corners[j].x; *pos++ = array[i].corners[j].y; *pos++ = array[i].corners[j].z; // Colour is the same for each vertex *col++ = r; *col++ = g; *col++ = b; *col++ = 1.0f; // Alpha is unused but needs to be there } // Texture co-ordinates are the same for each quad. *tc++ = sMinTexCoord; *tc++ = sMinTexCoord; *tc++ = sMaxTexCoord; *tc++ = sMinTexCoord; *tc++ = sMaxTexCoord; *tc++ = sMaxTexCoord; *tc++ = sMinTexCoord; *tc++ = sMaxTexCoord; count++; } } _texture = [texture retain]; OOLog(@"sky.setup", @"Generated quadset with %u quads for texture %@", count, _texture); } if (!OK) { [self release]; self = nil; } return self; } - (void)dealloc { [_texture release]; if (_positions != NULL) free(_positions); if (_texCoords != NULL) free(_texCoords); if (_colors != NULL) free(_colors); [super dealloc]; } - (NSString *)description { return [NSString stringWithFormat:@"<%@ %p>{%u quads, texture: %@}", [self class], self, _count, _texture]; } - (void)render { OO_ENTER_OPENGL(); [_texture apply]; OOGL(glVertexPointer(kSkyQuadSetPositionEntriesPerVertex, GL_FLOAT, 0, _positions)); OOGL(glTexCoordPointer(kSkyQuadSetTexCoordEntriesPerVertex, GL_FLOAT, 0, _texCoords)); OOGL(glColorPointer(kSkyQuadSetColorEntriesPerVertex, GL_FLOAT, 0, _colors)); OOGL(glDrawArrays(GL_QUADS, 0, 4 * _count)); } #ifndef NDEBUG - (size_t) totalSize { return [self oo_objectSize] + _count * 4 * (sizeof *_positions + sizeof *_texCoords + sizeof *_colors); } - (OOTexture *) texture { return _texture; } #endif @end static OOColor *SaturatedColorInRange(OOColor *color1, OOColor *color2, BOOL hueFix) { OOColor *color = nil; float hue, saturation, brightness, alpha; color = [color1 blendedColorWithFraction:randf() ofColor:color2]; [color getHue:&hue saturation:&saturation brightness:&brightness alpha:&alpha]; saturation = 0.5 * saturation + 0.5; // move saturation up a notch! /* NOTE: this changes the hue, because getHue:... produces hue values in [0, 360], but colorWithCalibratedHue:... takes hue values in [0, 1]. */ if (hueFix) { /* now nebula colours can be independently set, correct the * range so they behave expectedly if they have actually been * set in planetinfo */ hue /= 360.0; } /* else keep it how it was before so the nebula hues are clearer */ return [OOColor colorWithHue:hue saturation:saturation brightness:brightness alpha:alpha]; } oolite-1.82/src/Core/OOSound.h000066400000000000000000000036361256642440500161350ustar00rootroot00000000000000/* OOSound.h Dispatch header to select the appropriate implementation of OOSound. Add new OS imports here. The -DOS_NAME flag in the GNUmakefile will select which one gets compiled. == Overview of Oolite sound architecture == There are four public sound classes: * OOSound: represents a sound, i.e. some data that can be played. * OOMusic: subclass of OOSound with support for looping, and the special constraint that only one OOMusic may play at a time. * OOSoundSource: a thing that can play a sound. Each sound played is conceptually played through a sound source, although this can be implicit using OOSound's -play method. * OOSoundReferencePoint: a point in space relative to which a sound source is positioned. Since positional sound is not implemented, this serves no practical purpose. Oolite Copyright (C) 2004-2014 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #define OOLITE_OPENAL 1 #if OOLITE_OPENAL #import "OOALSound.h" #import "OOALMusic.h" #import "OOBasicSoundReferencePoint.h" #else #warning No sound implementation selected. Currently, the only option is OOLITE_OPENAL. There are SDL and Mac CoreAudio implementations in the revision history. #endif #import "OOSoundSource.h" oolite-1.82/src/Core/OOSoundInternal.h000066400000000000000000000005521256642440500176240ustar00rootroot00000000000000#import "OOSound.h" #if OOLITE_OPENAL #import "OOALSoundMixer.h" #import "OOALSoundChannel.h" #define OOSoundAcquireLock() do {} while(0) #define OOSoundReleaseLock() do {} while(0) #else #warning No sound implementation selected. Currently, the only option is OOLITE_OPENAL. There are SDL and Mac CoreAudio implementations in the revision history. #endif oolite-1.82/src/Core/OOSoundSource.h000066400000000000000000000054141256642440500173120ustar00rootroot00000000000000/* OOSoundSource.h A sound source. Each playing sound is associated with a sound source, either explicitly or by creating one on the fly. Each sound source can play one sound at a time, and has a number of attributes related to positional audio (which is currently unimplemented). Copyright (C) 2006-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR */ #import "OOSoundSource.h" #import "OOMaths.h" @class OOSound, OOSoundChannel, OOSoundReferencePoint; @interface OOSoundSource: NSObject { @private OOSound *_sound; OOSoundChannel *_channel; BOOL _loop; uint8_t _repeatCount, _remainingCount; Vector _position; BOOL _positional; float _gain; } + (instancetype) sourceWithSound:(OOSound *)inSound; - (id) initWithSound:(OOSound *)inSound; // These options should be set before playing. Effect of setting them while playing is undefined. - (OOSound *) sound; - (void )setSound:(OOSound *)inSound; - (BOOL) loop; - (void) setLoop:(BOOL)inLoop; - (uint8_t) repeatCount; - (void) setRepeatCount:(uint8_t)inCount; - (BOOL) isPlaying; - (void) play; - (void) playOrRepeat; - (void) stop; + (void) stopAll; // Conveniences: - (void) playSound:(OOSound *)inSound; - (void) playSound:(OOSound *)inSound repeatCount:(uint8_t)inCount; - (void) playOrRepeatSound:(OOSound *)inSound; // Positional audio attributes are used in this implementation - (void) setPositional:(BOOL)inPositional; - (BOOL) positional; - (void) setPosition:(Vector)inPosition; - (Vector) position; - (void) setGain:(float)gain; - (float) gain; // *Advanced* positional audio attributes are ignored in this implementation - (void) setVelocity:(Vector)inVelocity; - (void) setOrientation:(Vector)inOrientation; - (void) setConeAngle:(float)inAngle; - (void) setGainInsideCone:(float)inInside outsideCone:(float)inOutside; - (void) positionRelativeTo:(OOSoundReferencePoint *)inPoint; @end oolite-1.82/src/Core/OOSoundSource.m000066400000000000000000000134711256642440500173210ustar00rootroot00000000000000/* OOSoundSource.m Copyright (C) 2006-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOSoundInternal.h" #import "OOLogging.h" #import "OOMaths.h" static NSMutableSet *sPlayingSoundSources; @implementation OOSoundSource + (instancetype) sourceWithSound:(OOSound *)inSound { return [[[self alloc] initWithSound:inSound] autorelease]; } - (id) init { self = [super init]; if (!self) return nil; _positional = NO; _position = kZeroVector; _gain = 1.0; return self; } - (id) initWithSound:(OOSound *)inSound { self = [self init]; if (!self) return nil; [self setSound:inSound]; return self; } - (void) dealloc { [self stop]; [_sound autorelease]; [super dealloc]; } - (NSString *) descriptionComponents { if ([self isPlaying]) { return [NSString stringWithFormat:@"sound=%@, loop=%s, repeatCount=%u, playing on channel %@", _sound, [self loop] ? "YES" : "NO", [self repeatCount], _channel]; } else { return [NSString stringWithFormat:@"sound=%@, loop=%s, repeatCount=%u, not playing", _sound, [self loop] ? "YES" : "NO", [self repeatCount]]; } } - (OOSound *) sound { return _sound; } - (void) setSound:(OOSound *)sound { if (_sound != sound) { [self stop]; [_sound autorelease]; _sound = [sound retain]; } } - (BOOL) loop { return _loop; } - (void) setLoop:(BOOL)loop { _loop = !!loop; } - (uint8_t) repeatCount { return _repeatCount ? _repeatCount : 1; } - (void) setRepeatCount:(uint8_t)count { _repeatCount = count; } - (BOOL) isPlaying { return _channel != nil; } - (void)play { if ([self sound] == nil) return; OOSoundAcquireLock(); if (_channel) [self stop]; _channel = [[OOSoundMixer sharedMixer] popChannel]; if (nil != _channel) { _remainingCount = [self repeatCount]; [_channel setDelegate:self]; [_channel setPosition:_position]; [_channel setGain:_gain]; [_channel playSound:[self sound] looped:[self loop]]; [self retain]; } if (EXPECT_NOT(sPlayingSoundSources == nil)) { sPlayingSoundSources = [[NSMutableSet alloc] init]; } [sPlayingSoundSources addObject:self]; OOSoundReleaseLock(); } - (void) playOrRepeat { if (![self isPlaying]) [self play]; else ++_remainingCount; } - (void)stop { OOSoundAcquireLock(); if (nil != _channel) { [_channel setDelegate:[self class]]; [_channel stop]; _channel = nil; [sPlayingSoundSources removeObject:self]; [self release]; } OOSoundReleaseLock(); } + (void) stopAll { /* We're not allowed to mutate sPlayingSoundSources during iteration. The normal solution would be to copy the set, but since we know it will end up empty we may as well use the original set and let a new one be set up lazily. */ NSMutableSet *playing = sPlayingSoundSources; sPlayingSoundSources = nil; [playing makeObjectsPerformSelector:@selector(stop)]; [playing release]; } - (void) playSound:(OOSound *)sound { [self playSound:sound repeatCount:_repeatCount]; } - (void) playSound:(OOSound *)sound repeatCount:(uint8_t)count { [self stop]; [self setSound:sound]; [self setRepeatCount:count]; [self play]; } - (void) playOrRepeatSound:(OOSound *)sound { if (_sound != sound) [self playSound:sound]; else [self playOrRepeat]; } - (void) setPositional:(BOOL)inPositional { if (inPositional) { _positional = YES; } else { /* OpenAL doesn't easily do non-positional sounds beyond the * stereo/mono distinction, but setting the position to the * zero vector is probably close enough */ _positional = NO; [self setPosition:kZeroVector]; } } - (BOOL) positional { return _positional; } - (void) setPosition:(Vector)inPosition { _position = inPosition; if (inPosition.x != 0.0 || inPosition.y != 0.0 || inPosition.z != 0.0) { _positional = YES; } if (_channel) { [_channel setPosition:_position]; } } - (Vector) position { return _position; } - (void) setGain:(float)gain { _gain = gain; if (_channel) { [_channel setGain:_gain]; } } - (float) gain { return _gain; } /* Following not yet implemented */ - (void) setVelocity:(Vector)inVelocity { } - (void) setOrientation:(Vector)inOrientation { } - (void) setConeAngle:(float)inAngle { } - (void) setGainInsideCone:(float)inInside outsideCone:(float)inOutside { } - (void) positionRelativeTo:(OOSoundReferencePoint *)inPoint { } // OOSoundChannelDelegate - (void)channel:(OOSoundChannel *)channel didFinishPlayingSound:(OOSound *)sound { assert(_channel == channel); OOSoundAcquireLock(); if (--_remainingCount) { [_channel playSound:[self sound] looped:NO]; } else { [_channel setDelegate:nil]; [[OOSoundMixer sharedMixer] pushChannel:_channel]; _channel = nil; [self release]; } OOSoundReleaseLock(); } + (void)channel:(OOSoundChannel *)inChannel didFinishPlayingSound:(OOSound *)inSound { // This delegate is used for a stopped source [[OOSoundMixer sharedMixer] pushChannel:inChannel]; } @end oolite-1.82/src/Core/OOSoundSourcePool.h000066400000000000000000000055221256642440500201440ustar00rootroot00000000000000/* OOSoundSourcePool.h Manages a fixed number of sound sources and distributes sounds between them. Each sound has a priority and an expiry time. When a new sound is played, it replaces (if possible) a sound of lower priority that has expired, a sound of the same priority that has expired, or a sound of lower priority that has not expired. All sounds are specified by customsounds.plist key. Copyright (C) 2008-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import #import "OOTypes.h" #import "OOMaths.h" @interface OOSoundSourcePool: NSObject { @private struct OOSoundSourcePoolElement *_sources; uint8_t _count; uint8_t _latest; uint8_t _reserved; OOTimeDelta _minRepeat; OOTimeAbsolute _nextRepeat; NSString *_lastKey; } + (instancetype) poolWithCount:(uint8_t)count minRepeatTime:(OOTimeDelta)minRepeat; - (id) initWithCount:(uint8_t)count minRepeatTime:(OOTimeDelta)minRepeat; - (void) playSoundWithKey:(NSString *)key priority:(float)priority expiryTime:(OOTimeDelta)expiryTime overlap:(BOOL)overlap position:(Vector)position; - (void) playSoundWithKey:(NSString *)key priority:(float)priority expiryTime:(OOTimeDelta)expiryTime; - (void) playSoundWithKey:(NSString *)key priority:(float)priority; // expiryTime:0.1 +/- 0.5 - (void) playSoundWithKey:(NSString *)key priority:(float)priority position:(Vector)position; // expiryTime:0.1 +/- 0.5 - (void) playSoundWithKey:(NSString *)key position:(Vector)position; // expiryTime:0.1 +/- 0.5 - (void) playSoundWithKey:(NSString *)key; // priority: 1.0, expiryTime:0.1 +/- 0.5 - (void) playSoundWithKey:(NSString *)key overlap:(BOOL)overlap; // if overlap == NO it waits for key to finish before playing key again - (void) playSoundWithKey:(NSString *)key overlap:(BOOL)overlap position:(Vector)position; @end oolite-1.82/src/Core/OOSoundSourcePool.m000066400000000000000000000140611256642440500201470ustar00rootroot00000000000000/* OOSoundSourcePool.m Copyright (C) 2008-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOSoundSourcePool.h" #import "OOSound.h" #import "Universe.h" enum { kNoSlot = UINT8_MAX }; typedef struct OOSoundSourcePoolElement { OOSoundSource *source; OOTimeAbsolute expiryTime; float priority; } PoolElement; @interface OOSoundSourcePool (Private) - (uint8_t) selectSlotForPriority:(float)priority; @end @implementation OOSoundSourcePool + (instancetype) poolWithCount:(uint8_t)count minRepeatTime:(OOTimeDelta)minRepeat { return [[[self alloc] initWithCount:count minRepeatTime:minRepeat] autorelease]; } - (id) initWithCount:(uint8_t)count minRepeatTime:(OOTimeDelta)minRepeat { if ((self = [super init])) { // Sanity-check count if (count == 0) count = 1; if (count == kNoSlot) --count; _count = count; _reserved = kNoSlot; if (minRepeat < 0.0) minRepeat = 0.0; _minRepeat = minRepeat; // Create source pool _sources = calloc(sizeof(PoolElement), count); if (_sources == NULL) { [self release]; self = nil; } } return self; } - (void) dealloc { uint8_t i; for (i = 0; i != _count; i++) { [_sources[i].source release]; } free(_sources); [_lastKey release]; [super dealloc]; } - (void) playSoundWithKey:(NSString *)key priority:(float)priority expiryTime:(OOTimeDelta)expiryTime overlap:(BOOL)overlap position:(Vector)position { uint8_t slot; OOTimeAbsolute now, absExpiryTime; PoolElement *element = NULL; OOSound *sound = NULL; // Convert expiry time to absolute now = [UNIVERSE getTime]; absExpiryTime = expiryTime + now; // Avoid repeats if required if (now < _nextRepeat && [key isEqualToString:_lastKey]) return; if (!overlap && _reserved != kNoSlot && [_sources[_reserved].source isPlaying]) return; // Look for a slot in the source list to use slot = [self selectSlotForPriority:priority]; if (slot == kNoSlot) return; element = &_sources[slot]; // Load sound sound = [OOSound soundWithCustomSoundKey:key]; if (sound == nil) return; // Stop playing sound or set up sound source as appropriate if (element->source != nil) [element->source stop]; else { element->source = [[OOSoundSource alloc] init]; if (element->source == nil) return; } if (slot == _reserved) _reserved = kNoSlot; // _reserved has finished playing! if (!overlap) _reserved = slot; // Play and store metadata [element->source setPosition:position]; [element->source playSound:sound]; element->expiryTime = absExpiryTime; element->priority = priority; if (_minRepeat > 0.0) { _nextRepeat = now + _minRepeat; [_lastKey release]; _lastKey = [key copy]; } // Set staring search location for next slot lookup _latest = slot; } - (void) playSoundWithKey:(NSString *)key priority:(float)priority expiryTime:(OOTimeDelta)expiryTime { [self playSoundWithKey:key priority:priority expiryTime:expiryTime overlap:YES position:kZeroVector]; } - (void) playSoundWithKey:(NSString *)key priority:(float)priority position:(Vector)position { [self playSoundWithKey:key priority:priority expiryTime:0.5 + randf() * 0.1 overlap:YES position:position]; } - (void) playSoundWithKey:(NSString *)key priority:(float)priority { [self playSoundWithKey:key priority:priority expiryTime:0.5 + randf() * 0.1]; } - (void) playSoundWithKey:(NSString *)key { [self playSoundWithKey:key priority:1.0]; } - (void) playSoundWithKey:(NSString *)key position:(Vector)position { [self playSoundWithKey:key priority:1.0 position:position]; } - (void) playSoundWithKey:(NSString *)key overlap:(BOOL)overlap { [self playSoundWithKey:key priority:1.0 expiryTime:0.5 overlap:overlap position:kZeroVector]; } - (void) playSoundWithKey:(NSString *)key overlap:(BOOL)overlap position:(Vector)position { [self playSoundWithKey:key priority:1.0 expiryTime:0.5 overlap:overlap position:position]; } @end @implementation OOSoundSourcePool (Private) - (uint8_t) selectSlotForPriority:(float)priority { uint8_t curr, count, expiredLower = kNoSlot, unexpiredLower = kNoSlot, expiredEqual = kNoSlot; PoolElement *element = NULL; OOTimeAbsolute now = [UNIVERSE getTime]; #define NEXT(x) (((x) + 1) % _count) curr = _latest; count = _count; do { curr = NEXT(curr); element = &_sources[curr]; if (element->source == nil || ![element->source isPlaying]) return curr; // Best type of slot: empty else if (element->priority < priority) { if (element->expiryTime <= now) expiredLower = curr; // Second-best type: expired lower-priority else if (curr != _reserved) unexpiredLower = curr; // Third-best type: unexpired lower-priority } else if (element->priority == priority && element->expiryTime <= now) { expiredEqual = curr; // Fourth-best type: expired equal-priority. } } while (--count); if (expiredLower != kNoSlot) return expiredLower; if (unexpiredLower != kNoSlot) return unexpiredLower; return expiredEqual; // Will be kNoSlot if none found } @end oolite-1.82/src/Core/OOSpatialReference.h000066400000000000000000000022771256642440500202610ustar00rootroot00000000000000/* OOSpatialReference.h Formal protocol for objects whose transformation matrix (and thus, position and orientation) can be observed. Currently used for cameras, potentially useful for stuff like positional audio (instead of OOSoundReferencePoint). Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #import "OOMaths.h" #import "Entity.h" @protocol OOSpatialReference - (OOMatrix) transformationMatrix; @end @interface Entity (OOSpatialReference) @end oolite-1.82/src/Core/OOSpatialReference.m000066400000000000000000000014521256642440500202600ustar00rootroot00000000000000/* OOSpatialReference.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOSpatialReference.h" oolite-1.82/src/Core/OOStringExpander.h000066400000000000000000000434371256642440500200050ustar00rootroot00000000000000/* OOStringExpander.h Functions for expanding escape codes and key references in strings. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the impllied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #import "OOMaths.h" // Option flags for OOExpandDescriptionString(). enum { kOOExpandForJavaScript = 0x00000001, ///< Report warnings through JavaScript runtime system instead of normal logging. kOOExpandBackslashN = 0x00000002, ///< Convert literal "\\n"s to line breaks (used for missiontext.plist for historical reasons). kOOExpandGoodRNG = 0x00000004, ///< Use RANDROT for selecting from description arrays and for %N expansion. kOOExpandReseedRNG = 0x00000008, ///< Set "really random" seeds while expanding. kOOExpandKey = 0x00000010, ///< Treat string as a key. Expand(@"foo", kOOExpandKey) == Expand(@"[foo]", kOOExpandNoOptions). kOOExpandDisallowPercentI = 0x00000020, ///< Disallow %I expansion (used when expanding %I itself). kOOExpandNoOptions = 0 }; typedef NSUInteger OOExpandOptions; /* OOExpandDescriptionString(string, seed, overrides, locals, systemName, recurse, options) Apply the following transformations to a string: * [commander_name] is replaced with the player character's name. * [commander_shipname] is replaced with the player ship's name. * [commander_shipdisplayname] is replaced with the player ship's display name. * [commander_rank] is replaced with the descriptive name of the player's kill rank. * [commander_legal_status] is replaced with the descriptive name for the player's bounty ("Clean" etc) * [commander_bounty] is replaced with the player's numerical bounty. * [credits_number] is replaced with the player's formatted credit balance. * [_oo_legacy_credits_number] is replaced with the player's credit balance in simple #.0 format (this is substituted for [credits_number] by the legacy script engine). * [N], where N is an integer, is looked up in system_description in descriptions.plist. This is an array of arrays of five strings each. Each [N] lookup selects entry N of the outer array, then selects a string from the Nth inner array at pseudo-random. The result is then expanded recursively. * [key], where key is any string not specified above, is handled as follows: - If it is found in , use the corresponding value. - Otherwise, look it up in descriptions.plist. If a string or number is found, use that; if an array is found, select an item from it at random. - Otherwise, if it is a mission variable name (prefixed with mission_), use the value of the mission variable. - Otherwise, if it is found in , use the corresponding value. - Otherwise, if it is a whitelisted legacy script property, look it up and insert the value. The resulting string is then recursively expanded. * %H is replaced with . If systemName is nil, a planet name is retrieved through -[Universe getSystemName:], treating as a system seed. * %I is equivalent to "%H[planetname-derivative-suffix]". * %N is replaced with a random "alien" name using the planet name digraphs. If used more than once in the same string, it will produce the same name on each occurence. * %R is like %N but, due to a bug, misses some possibilities. Deprecated. * %JNNN, where NNN is a three-digit integer, is replaced with the name of system ID NNN in the current galaxy. * %% is replaced with %. * %[ is replaced with [. * %] is replaced with ]. Syntax errors and, in non-Deployment builds, warnings may be generated. If , warnings are sent through OOJSReportWarning, otherwise they are logged. If , literal \n in strings (i.e., "\\n") are converted to line breaks. This is used for expanding missiontext.plist entries, which may have literal \n especially in XML format. */ NSString *OOExpandDescriptionString(Random_Seed seed, NSString *string, NSDictionary *overrides, NSDictionary *legacyLocals, NSString *systemName, OOExpandOptions options); /* OOGenerateSystemDescription(seed, name) Generates the default system description for the specified system seed. Equivalent to OOExpand(@"[system-description-string]"), except that it uses a special PRNG setting to tie the description to the seed. NOTE: this does not apply planetinfo overrides. To get the actual system description, use [UNIVERSE generateSystemData:]. */ NSString *OOGenerateSystemDescription(Random_Seed seed, NSString *name); /** Expand a string with default options. */ #define OOExpand(string, ...) OOExpandWithSeed(OOStringExpanderDefaultRandomSeed(), string, __VA_ARGS__) /** Expand a string as though it were surrounded by brackets; OOExpandKey(@"foo", ...) is equivalent to OOExpand(@"[foo]", ...). */ #define OOExpandKey(key, ...) OOExpandKeyWithSeed(OOStringExpanderDefaultRandomSeed(), key, __VA_ARGS__) /** Like OOExpandKey(), but uses a random-er random seed to avoid repeatability. */ #define OOExpandKeyRandomized(key, ...) OOExpandWithOptions(OOStringExpanderDefaultRandomSeed(), kOOExpandKey | kOOExpandGoodRNG | kOOExpandReseedRNG, key, __VA_ARGS__) #define OOExpandWithSeed(seed, string, ...) OOExpandWithOptions(seed, kOOExpandNoOptions, string, __VA_ARGS__) #define OOExpandKeyWithSeed(seed, key, ...) OOExpandWithOptions(seed, kOOExpandKey, key, __VA_ARGS__) #define OOExpandWithOptions(seed, options, string, ...) \ OOExpandDescriptionString(seed, string, OOEXPAND_ARG_DICTIONARY(__VA_ARGS__), nil, nil, options) // Equivalent to [[UNIVERSE systemManager] getRandomSeedForCurrentSystem], without pulling in Universe.h. Random_Seed OOStringExpanderDefaultRandomSeed(void); // MARK: Danger zone! Everything beyond this point is scary. /* Given an argument list, return a dictionary whose keys are the literal arguments and whose values are objects representing the arguments' values (as per OO_CAST_PARAMETER() below). Note that the argument list will be preprocessor-expanded at this point. */ #define OOEXPAND_ARG_DICTIONARY(...) ( \ (OOEXPAND_ARGUMENT_COUNT(__VA_ARGS__) == 0) ? \ nil : \ [NSDictionary dictionaryWithObjects:OOEXPAND_OBJECTS_FROM_ARGS(__VA_ARGS__) \ forKeys:OOEXPAND_NAMES_FROM_ARGS(__VA_ARGS__) \ count:OOEXPAND_ARGUMENT_COUNT(__VA_ARGS__)] ) #define OOEXPAND_NAME_FROM_ARG(ITEM) @#ITEM #define OOEXPAND_NAMES_FROM_ARGS(...) (NSString *[]){ OOEXPAND_MAP(OOEXPAND_NAME_FROM_ARG, __VA_ARGS__) } #define OOEXPAND_OBJECTS_FROM_ARGS(...) (id[]){ OOEXPAND_MAP(OO_CAST_PARAMETER, __VA_ARGS__) } /* Limited boxing mechanism. ITEM may be an NSString *, NSNumber *, any integer type or any floating point type; the result is an NSNumber *, except if the parameter is an NSString * in which case it is returned unmodified. */ #define OO_CAST_PARAMETER(ITEM) \ __builtin_choose_expr( \ OOEXPAND_IS_OBJECT(ITEM), \ OOCastParamObject, \ __builtin_choose_expr( \ OOEXPAND_IS_SIGNED_INTEGER(ITEM), \ OOCastParamSignedInteger, \ __builtin_choose_expr( \ OOEXPAND_IS_UNSIGNED_INTEGER(ITEM), \ OOCastParamUnsignedInteger, \ __builtin_choose_expr( \ OOEXPAND_IS_FLOAT(ITEM), \ OOCastParamFloat, \ __builtin_choose_expr( \ OOEXPAND_IS_UNSIGNED_INTEGER(ITEM), \ OOCastParamUnsignedInteger, \ __builtin_choose_expr( \ OOEXPAND_IS_DOUBLE(ITEM), \ OOCastParamDouble, \ (void)0 \ ) \ ) \ ) \ ) \ ) \ )(ITEM) // Test whether ITEM is a known object type. // NOTE: id works here in clang, but not gcc. #define OOEXPAND_IS_OBJECT(ITEM) ( \ __builtin_types_compatible_p(typeof(ITEM), NSString *) || \ __builtin_types_compatible_p(typeof(ITEM), NSNumber *)) // Test whether ITEM is a signed integer type. // Some redundancy to avoid silliness across platforms; probably not necessary. #define OOEXPAND_IS_SIGNED_INTEGER(ITEM) ( \ __builtin_types_compatible_p(typeof(ITEM), char) || \ __builtin_types_compatible_p(typeof(ITEM), short) || \ __builtin_types_compatible_p(typeof(ITEM), int) || \ __builtin_types_compatible_p(typeof(ITEM), long) || \ __builtin_types_compatible_p(typeof(ITEM), long long) || \ __builtin_types_compatible_p(typeof(ITEM), NSInteger) || \ __builtin_types_compatible_p(typeof(ITEM), intptr_t) || \ __builtin_types_compatible_p(typeof(ITEM), ssize_t) || \ __builtin_types_compatible_p(typeof(ITEM), off_t)) // Test whether ITEM is an unsigned integer type. // Some redundancy to avoid silliness across platforms; probably not necessary. #define OOEXPAND_IS_UNSIGNED_INTEGER(ITEM) ( \ __builtin_types_compatible_p(typeof(ITEM), unsigned char) || \ __builtin_types_compatible_p(typeof(ITEM), unsigned short) || \ __builtin_types_compatible_p(typeof(ITEM), unsigned int) || \ __builtin_types_compatible_p(typeof(ITEM), unsigned long) || \ __builtin_types_compatible_p(typeof(ITEM), unsigned long long) || \ __builtin_types_compatible_p(typeof(ITEM), NSUInteger) || \ __builtin_types_compatible_p(typeof(ITEM), uintptr_t) || \ __builtin_types_compatible_p(typeof(ITEM), size_t)) // Test whether ITEM is a float. // This is distinguished from double to expose optimization opportunities. #define OOEXPAND_IS_FLOAT(ITEM) ( \ __builtin_types_compatible_p(typeof(ITEM), float)) // Test whether ITEM is any other floating-point type. #define OOEXPAND_IS_DOUBLE(ITEM) ( \ __builtin_types_compatible_p(typeof(ITEM), double) || \ __builtin_types_compatible_p(typeof(ITEM), long double)) // OO_CAST_PARAMETER() boils down to one of these. static inline id OOCastParamObject(id object) { return object; } static inline id OOCastParamSignedInteger(long long value) { return [NSNumber numberWithLongLong:value]; } static inline id OOCastParamUnsignedInteger(unsigned long long value) { return [NSNumber numberWithUnsignedLongLong:value]; } static inline id OOCastParamFloat(float value) { return [NSNumber numberWithFloat:value]; } static inline id OOCastParamDouble(double value) { return [NSNumber numberWithDouble:value]; } /* Evil macro magic. OOEXPAND_ARGUMENT_COUNT returns the number of elements in a __VA_ARGS__ list. Trivially modified from code by Laurent Deniau and "arpad.goret...@gmail.com" (full name not available). Source: https://groups.google.com/forum/?fromgroups=#!topic/comp.std.c/d-6Mj5Lko_s This version relies on the GCC/Clang ##__VA_ARGS__ extension to handle zero-length lists. It supports up to 62 arguments. OOEXPAND_MAP applies a unary macro or function to each element of a parameter or initializer list. For example, "OOEXPAND_MAP(foo, 1, 2, 3)" is equivalent to "foo(1), foo(2), foo(3)". */ #define OOEXPAND_ARGUMENT_COUNT(...) \ OOEXPAND_ARGUMENT_COUNT_INNER(_0, ##__VA_ARGS__, OOEXPAND_ARGUMENT_COUNT_63_VALUES()) #define OOEXPAND_ARGUMENT_COUNT_INNER(...) \ OOEXPAND_ARGUMENT_COUNT_EXTRACT_64TH_ARG(__VA_ARGS__) #define OOEXPAND_ARGUMENT_COUNT_EXTRACT_64TH_ARG( \ _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \ _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \ _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \ _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \ _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \ _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \ _61,_62,_63,N,...) N #define OOEXPAND_ARGUMENT_COUNT_63_VALUES() \ 62,61,60, \ 59,58,57,56,55,54,53,52,51,50, \ 49,48,47,46,45,44,43,42,41,40, \ 39,38,37,36,35,34,33,32,31,30, \ 29,28,27,26,25,24,23,22,21,20, \ 19,18,17,16,15,14,13,12,11,10, \ 9,8,7,6,5,4,3,2,1,0 #define OOEXPAND_MAP(F, ...) \ OOEXPAND_MAP_INNER(F, OOEXPAND_ARGUMENT_COUNT(__VA_ARGS__), __VA_ARGS__) #define OOEXPAND_MAP_INNER(F, COUNTEXPR, ...) \ OOEXPAND_MAP_INNER2(F, COUNTEXPR, __VA_ARGS__) #define OOEXPAND_MAP_INNER2(F, COUNT, ...) \ OOEXPAND_MAP_INNER3(F, OOEXPAND_MAP_IMPL_ ## COUNT, __VA_ARGS__) #define OOEXPAND_MAP_INNER3(F, IMPL, ...) \ IMPL(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_0(F, HEAD) #define OOEXPAND_MAP_IMPL_1(F, HEAD) F(HEAD) #define OOEXPAND_MAP_IMPL_2(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_1(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_3(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_2(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_4(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_3(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_5(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_4(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_6(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_5(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_7(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_6(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_8(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_7(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_9(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_8(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_10(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_9(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_11(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_10(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_12(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_11(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_13(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_12(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_14(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_13(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_15(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_14(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_16(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_15(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_17(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_16(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_18(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_17(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_19(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_18(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_20(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_19(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_21(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_20(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_22(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_21(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_23(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_22(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_24(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_23(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_25(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_24(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_26(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_25(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_27(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_26(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_28(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_27(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_29(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_28(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_30(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_29(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_31(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_30(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_32(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_31(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_33(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_32(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_34(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_33(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_35(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_34(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_36(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_35(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_37(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_36(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_38(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_37(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_39(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_38(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_40(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_39(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_41(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_40(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_42(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_41(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_43(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_42(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_44(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_43(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_45(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_44(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_46(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_45(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_47(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_46(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_48(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_47(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_49(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_48(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_50(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_49(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_51(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_50(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_52(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_51(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_53(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_52(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_54(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_53(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_55(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_54(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_56(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_55(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_57(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_56(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_58(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_57(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_59(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_58(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_60(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_59(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_61(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_60(F, __VA_ARGS__) #define OOEXPAND_MAP_IMPL_62(F, HEAD, ...) F(HEAD), OOEXPAND_MAP_IMPL_61(F, __VA_ARGS__) oolite-1.82/src/Core/OOStringExpander.m000066400000000000000000001167211256642440500200070ustar00rootroot00000000000000/* OOStringExpander.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the impllied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOStringExpander.h" #import "Universe.h" #import "OOJavaScriptEngine.h" #import "OOCollectionExtractors.h" #import "OOStringParsing.h" #import "ResourceManager.h" #import "PlayerEntityScriptMethods.h" #import "PlayerEntity.h" // Don't bother with syntax warnings in Deployment builds. #define WARNINGS (!defined(NDEBUG)) #define OO_EXPANDER_RANDOM (context->useGoodRNG ? (Ranrot()&0xFF) : gen_rnd_number()) enum { /* Total stack limit for strings being parsed (in UTF-16 code elements, i.e. units of 2 bytes), used recursively - for instance, if the root string takes 10,000 characters, any string it recurses into gets kStackAllocationLimit - 10,000. If the limit would be exceeded, the unexpanded string is returned instead. The limit is expected to be much higher than necessary for any practical string, and exists only to catch pathological behaviour without crashing. */ kStackAllocationLimit = UINT16_MAX, /* Recursion limit, for much the same purpose. Without it, we crash about 22,000 stack frames deep when trying to expand a = "[a]" on a Mac. */ kRecursionLimit = 100 }; /* OOStringExpansionContext Struct used to store context and caches for the entire string expansion operation, including recursive calls (so it can't contain anything pertaining to the specific string being expanded). */ typedef struct { Random_Seed seed; NSString *systemName; NSDictionary *overrides; NSDictionary *legacyLocals; bool isJavaScript; bool convertBackslashN; bool hasPercentR; // Set to indicate we need an ExpandPercentR() pass. bool useGoodRNG; bool disallowPercentI; NSString *systemNameWithIan; // Cache for %I NSString *randomNameN; // Cache for %N NSString *randomNameR; // Cache for %R NSArray *systemDescriptions;// Cache for system_description, used for numbered keys. NSUInteger sysDescCount; // Count of systemDescriptions, valid after GetSystemDescriptions() called. } OOStringExpansionContext; /* Accessors for lazily-instantiated caches in context. */ static NSString *GetSystemName(OOStringExpansionContext *context); // %H static NSString *GetSystemNameIan(OOStringExpansionContext *context); // %I static NSString *GetRandomNameN(OOStringExpansionContext *context); // %N static NSString *GetRandomNameR(OOStringExpansionContext *context); // %R static NSArray *GetSystemDescriptions(OOStringExpansionContext *context); static void AppendCharacters(NSMutableString **result, const unichar *characters, NSUInteger start, NSUInteger end); static NSString *NewRandomDigrams(OOStringExpansionContext *context); static NSString *OldRandomDigrams(void); // Various bits of expansion logic, each with a comment of its very own at the implementation. static NSString *Expand(OOStringExpansionContext *context, NSString *string, NSUInteger sizeLimit, NSUInteger recursionLimit); static NSString *ExpandKey(OOStringExpansionContext *context, const unichar *characters, NSUInteger size, NSUInteger idx, NSUInteger *replaceLength, NSUInteger sizeLimit, NSUInteger recursionLimit); static NSString *ExpandDigitKey(OOStringExpansionContext *context, const unichar *characters, NSUInteger keyStart, NSUInteger keyLength, NSUInteger sizeLimit, NSUInteger recursionLimit); static NSString *ExpandStringKey(OOStringExpansionContext *context, NSString *key, NSUInteger sizeLimit, NSUInteger recursionLimit); static NSString *ExpandStringKeyOverride(OOStringExpansionContext *context, NSString *key); static NSString *ExpandStringKeySpecial(OOStringExpansionContext *context, NSString *key); static NSString *ExpandStringKeyKeyboardBinding(OOStringExpansionContext *context, NSString *key); static NSMapTable *SpecialSubstitutionSelectors(void); static NSString *ExpandStringKeyFromDescriptions(OOStringExpansionContext *context, NSString *key, NSUInteger sizeLimit, NSUInteger recursionLimit); static NSString *ExpandStringKeyMissionVariable(OOStringExpansionContext *context, NSString *key); static NSString *ExpandStringKeyLegacyLocalVariable(OOStringExpansionContext *context, NSString *key); static NSString *ExpandLegacyScriptSelectorKey(OOStringExpansionContext *context, NSString *key); static SEL LookUpLegacySelector(NSString *key); static NSString *ExpandPercentEscape(OOStringExpansionContext *context, const unichar *characters, NSUInteger size, NSUInteger idx, NSUInteger *replaceLength); static NSString *ExpandSystemNameEscape(OOStringExpansionContext *context, const unichar *characters, NSUInteger size, NSUInteger idx, NSUInteger *replaceLength); static NSString *ExpandPercentR(OOStringExpansionContext *context, NSString *input); #if WARNINGS static void ReportWarningForUnknownKey(OOStringExpansionContext *context, NSString *key); #endif static NSString *ApplyOperators(NSString *string, NSString *operatorsString); static NSString *ApplyOneOperator(NSString *string, NSString *op, NSString *param); /* SyntaxWarning(context, logMessageClass, format, ...) SyntaxError(context, logMessageClass, format, ...) Report warning or error for expansion syntax, including unknown keys. Warnings are reported as JS warnings or log messages (depending on the context->isJavaScript flag) if the relevant log message class is enabled. Warnings are completely disabled in Deployment builds. Errors are reported as JS warnings (not exceptions) or log messages (again depending on context->isJavaScript) in all configurations. Exceptions are not used to avoid breaking code that worked with the old expander, even if it was questionable. Errors that are not syntax or invalid keys are reported with OOLogERR(). */ static void SyntaxIssue(OOStringExpansionContext *context, const char *function, const char *fileName, NSUInteger line, NSString *logMessageClass, NSString *prefix, NSString *format, ...) OO_TAKES_FORMAT_STRING(7, 8); #define SyntaxError(CONTEXT, CLASS, FORMAT, ...) SyntaxIssue(CONTEXT, OOLOG_FUNCTION_NAME, OOLOG_FILE_NAME, __LINE__, CLASS, OOLOG_WARNING_PREFIX, FORMAT, ## __VA_ARGS__) #if WARNINGS #define SyntaxWarning(CONTEXT, CLASS, FORMAT, ...) SyntaxIssue(CONTEXT, OOLOG_FUNCTION_NAME, OOLOG_FILE_NAME, __LINE__, CLASS, OOLOG_WARNING_PREFIX, FORMAT, ## __VA_ARGS__) #else #define SyntaxWarning(...) do {} while (0) #endif // MARK: - // MARK: Public functions NSString *OOExpandDescriptionString(Random_Seed seed, NSString *string, NSDictionary *overrides, NSDictionary *legacyLocals, NSString *systemName, OOExpandOptions options) { if (string == nil) return nil; OOStringExpansionContext context = { .seed = seed, .systemName = [systemName retain], .overrides = [overrides retain], .legacyLocals = [legacyLocals retain], .isJavaScript = options & kOOExpandForJavaScript, .convertBackslashN = options & kOOExpandBackslashN, .useGoodRNG = options & kOOExpandGoodRNG }; // Avoid recursive %I expansion by pre-seeding cache with literal %I. if (options & kOOExpandDisallowPercentI) { context.systemNameWithIan = @"%I"; } OORandomState savedRandomState; if (options & kOOExpandReseedRNG) { savedRandomState = OOSaveRandomState(); OOSetReallyRandomRANROTAndRndSeeds(); } NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSString *result = nil, *intermediate = nil; @try { // TODO: profile caching the results. Would need to keep track of whether we've done something nondeterministic (array selection, %R etc). if (options & kOOExpandKey) { intermediate = ExpandStringKey(&context, string, kStackAllocationLimit, kRecursionLimit); } else { intermediate = Expand(&context, string, kStackAllocationLimit, kRecursionLimit); } if (!context.hasPercentR) { result = intermediate; } else { result = ExpandPercentR(&context, intermediate); } } @finally { [context.systemName release]; [context.overrides release]; [context.legacyLocals release]; [context.systemNameWithIan release]; [context.randomNameN release]; [context.randomNameR release]; [context.systemDescriptions release]; } if (options & kOOExpandReseedRNG) { OORestoreRandomState(savedRandomState); } result = [result copy]; [pool release]; return [result autorelease]; } NSString *OOGenerateSystemDescription(Random_Seed seed, NSString *name) { seed_RNG_only_for_planet_description(seed); return OOExpandDescriptionString(seed, @"system-description-string", nil, nil, name, kOOExpandKey); } Random_Seed OOStringExpanderDefaultRandomSeed(void) { return [[UNIVERSE systemManager] getRandomSeedForCurrentSystem]; } // MARK: - // MARK: Guts /* Expand(context, string, sizeLimit, recursionLimit) Top-level expander. Expands all types of substitution in a string. is the remaining budget for stack allocation of read buffers. (Expand() is the only function that creates such buffers.) limits the number of recursive calls of Expand() that are permitted. If one of the limits would be exceeded, Expand() returns the input string unmodified. */ static NSString *Expand(OOStringExpansionContext *context, NSString *string, NSUInteger sizeLimit, NSUInteger recursionLimit) { NSCParameterAssert(string != nil && context != NULL && sizeLimit <= kStackAllocationLimit); const NSUInteger size = [string length]; // Avoid stack overflow. if (EXPECT_NOT(size > sizeLimit || recursionLimit == 0)) return string; sizeLimit -= size; recursionLimit--; // Nothing to expand in an empty string, and the size-1 thing below would be trouble. if (size == 0) return string; unichar characters[size]; [string getCharacters:characters range:(NSRange){ 0, size }]; /* Beginning of current range of non-special characters. If we encounter a substitution, we'll be copying from here forward. */ NSUInteger copyRangeStart = 0; // Mutable string for result if we perform any substitutions. NSMutableString *result = nil; /* The iteration limit is size - 1 because every valid substitution is at least 2 characters long. This way, characters[idx + 1] is always valid. */ for (NSUInteger idx = 0; idx < size - 1; idx++) { /* Main parsing loop. If, at the end of the loop, replacement != nil, we copy the characters from copyRangeStart to idx into the result, the insert replacement, and skip replaceLength characters forward (minus one, because idx is incremented by the loop.) */ NSString *replacement = nil; NSUInteger replaceLength = 0; unichar thisChar = characters[idx]; if (thisChar == '[') { replacement = ExpandKey(context, characters, size, idx, &replaceLength, sizeLimit, recursionLimit); } else if (thisChar == '%') { replacement = ExpandPercentEscape(context, characters, size, idx, &replaceLength); } else if (thisChar == ']') { SyntaxWarning(context, @"strings.expand.warning.unbalancedClosingBracket", @"Unbalanced ] in string."); } else if (thisChar == '\\' && context->convertBackslashN) { if (characters[idx + 1] == 'n') { replaceLength = 2; replacement = @"\n"; } } else { // No token start character, so we definitely have no replacement. continue; } if (replacement != nil) { /* If replacement string is "\x7F", eat the following character. This is used in system_description for the one empty string in [22]. */ if ([replacement isEqualToString:@"\x7F"] && replaceLength < size) { replaceLength++; replacement = @""; } // Avoid copying if we're replacing the entire input string. if (copyRangeStart == 0 && replaceLength == size) { return replacement; } // Write the pending literal segment to result. This also allocates result if needed. AppendCharacters(&result, characters, copyRangeStart, idx); [result appendString:replacement]; // Skip over replaced part and start a new literal segment. idx += replaceLength - 1; copyRangeStart = idx + 1; } } if (result != nil) { // Append any trailing literal segment. AppendCharacters(&result, characters, copyRangeStart, size); // Don't turn result immutable; doing it once at top level is sufficient. return result; } else { // No substitutions, return original string. return string; } } /* ExpandKey(context, characters, size, idx, replaceLength, sizeLimit, recursionLimit) Expand a substitution key, i.e. a section surrounded by square brackets. On entry, is the offset to an opening bracket. ExpandKey() searches for the balancing closing bracket, and if it is found dispatches to either ExpandDigitKey() (for a key consisting only of digits) or ExpandStringKey() (for anything else). The key may be terminated by a vertical bar |, followed by an operator. An operator is an identifier, optionally followed by a colon and additional text, and may be terminated with another bar and operator. */ static NSString *ExpandKey(OOStringExpansionContext *context, const unichar *characters, NSUInteger size, NSUInteger idx, NSUInteger *replaceLength, NSUInteger sizeLimit, NSUInteger recursionLimit) { NSCParameterAssert(context != NULL && characters != NULL && replaceLength != NULL); NSCParameterAssert(characters[idx] == '['); // Find the balancing close bracket. NSUInteger end, balanceCount = 1, firstBar = 0; bool allDigits = true; for (end = idx + 1; end < size && balanceCount > 0; end++) { if (characters[end] == ']') balanceCount--; else { if (characters[end] == '[') balanceCount++; else if (characters[end] == '|' && firstBar == 0) firstBar = end; if (!isdigit(characters[end]) && firstBar == 0) allDigits = false; } } // Fail if no balancing bracket. if (EXPECT_NOT(balanceCount != 0)) { SyntaxWarning(context, @"strings.expand.warning.unbalancedOpeningBracket", @"Unbalanced [ in string."); return nil; } NSUInteger totalLength = end - idx; *replaceLength = totalLength; NSUInteger keyStart = idx + 1, keyLength = totalLength - 2; if (firstBar != 0) keyLength = firstBar - idx - 1; if (EXPECT_NOT(keyLength == 0)) { SyntaxWarning(context, @"strings.expand.warning.emptyKey", @"Invalid expansion code [] string. (To avoid this message, use %%[%%].)"); return nil; } NSString *expanded = nil; if (allDigits) { expanded = ExpandDigitKey(context, characters, keyStart, keyLength, sizeLimit, recursionLimit); } else { NSString *key = [NSString stringWithCharacters:characters + keyStart length:keyLength]; expanded = ExpandStringKey(context, key, sizeLimit, recursionLimit); } if (firstBar != 0) { NSString *operators = [NSString stringWithCharacters:characters + firstBar + 1 length:end - firstBar - 2]; expanded = ApplyOperators(expanded, operators); } return expanded; } /* ApplyOperators(string, operatorsString) Given a string and a series of formatting operators separated by vertical bars (or a single formatting operator), apply the operators, in sequence, to the string. */ static NSString *ApplyOperators(NSString *string, NSString *operatorsString) { NSArray *operators = [operatorsString componentsSeparatedByString:@"|"]; NSString *op = nil; foreach(op, operators) { NSString *param = nil; NSRange colon = [op rangeOfString:@":"]; if (colon.location != NSNotFound) { param = [op substringFromIndex:colon.location + colon.length]; op = [op substringToIndex:colon.location]; } string = ApplyOneOperator(string, op, param); } return string; } static NSString *Operator_cr(NSString *string, NSString *param) { return OOCredits([string doubleValue] * 10); } static NSString *Operator_dcr(NSString *string, NSString *param) { return OOCredits([string longLongValue]); } static NSString *Operator_icr(NSString *string, NSString *param) { return OOIntCredits([string longLongValue]); } static NSString *Operator_idcr(NSString *string, NSString *param) { return OOIntCredits(round([string doubleValue] / 10.0)); } static NSString *Operator_precision(NSString *string, NSString *param) { return [NSString stringWithFormat:@"%.*f", [param intValue], [string doubleValue]]; } static NSString *Operator_multiply(NSString *string, NSString *param) { return [NSString stringWithFormat:@"%g", [string doubleValue] * [param doubleValue]]; } static NSString *Operator_add(NSString *string, NSString *param) { return [NSString stringWithFormat:@"%g", [string doubleValue] + [param doubleValue]]; } /* ApplyOneOperator(string, op, param) Apply a single formatting operator to a string. For example, the expansion expression "[distance|precision:1]" will be expanded by a call to ApplyOneOperator(@"distance", @"precision", @"1"). may be nil, indicating an operator with no parameter (no colon). */ static NSString *ApplyOneOperator(NSString *string, NSString *op, NSString *param) { static NSDictionary *operators = nil; if (operators == nil) { #define OPERATOR(name) [NSValue valueWithPointer:Operator_##name], @#name operators = [[NSDictionary alloc] initWithObjectsAndKeys: OPERATOR(dcr), OPERATOR(cr), OPERATOR(icr), OPERATOR(idcr), OPERATOR(precision), OPERATOR(multiply), OPERATOR(add), nil]; } NSString *(*operator)(NSString *string, NSString *param) = [[operators objectForKey:op] pointerValue]; if (operator != NULL) { return operator(string, param); } OOLogERR(@"strings.expand.invalidOperator", @"Unknown string expansion operator %@", op); return string; } /* ExpandDigitKey(context, characters, keyStart, keyLength, sizeLimit, recursionLimit) Expand a key (as per ExpandKey()) consisting entirely of digits. and specify the range of characters containing the key. Digit-only keys are looked up in the system_description array in descriptions.plist, which is expected to contain only arrays of strings (no loose strings). When an array is retrieved, a string is selected from it at random and the result is expanded recursively by calling Expand(). */ static NSString *ExpandDigitKey(OOStringExpansionContext *context, const unichar *characters, NSUInteger keyStart, NSUInteger keyLength, NSUInteger sizeLimit, NSUInteger recursionLimit) { NSCParameterAssert(context != NULL && characters != NULL); NSUInteger keyValue = 0, idx; for (idx = keyStart; idx < (keyStart + keyLength); idx++) { NSCAssert2(isdigit(characters[idx]), @"%s called with non-numeric key [%@].", __FUNCTION__, [NSString stringWithCharacters:characters + keyStart length:keyLength]); keyValue = keyValue * 10 + characters[idx] - '0'; } // Retrieve selected system_description entry. NSArray *sysDescs = GetSystemDescriptions(context); NSArray *entry = [sysDescs oo_arrayAtIndex:keyValue]; if (EXPECT_NOT(entry == nil)) { if (keyValue >= context->sysDescCount) { SyntaxWarning(context, @"strings.expand.warning.outOfRangeKey", @"Out-of-range system description expansion key [%@] in string.", [NSString stringWithCharacters:characters + keyStart length:keyLength]); } else { // This is out of the scope of whatever triggered it, so shouldn't be a JS warning. OOLogERR(@"strings.expand.invalidData", @"descriptions.plist entry system_description must be an array of arrays of strings."); } return nil; } // Select a random sub-entry. NSUInteger selection, count = [entry count]; NSUInteger rnd = OO_EXPANDER_RANDOM; if (count == 5 && !context->useGoodRNG) { // Time-honoured Elite-compatible way for five items. if (rnd >= 0xCC) selection = 4; else if (rnd >= 0x99) selection = 3; else if (rnd >= 0x66) selection = 2; else if (rnd >= 0x33) selection = 1; else selection = 0; } else { // General way. selection = (rnd * count) / 256; } // Look up and recursively expand string. NSString *string = [entry oo_stringAtIndex:selection]; return Expand(context, string, sizeLimit, recursionLimit); } /* ExpandStringKey(context, key, sizeLimit, recursionLimit) Expand a key (as per ExpandKey()) which doesn't consist entirely of digits. Looks for the key in a number of different places in prioritized order. */ static NSString *ExpandStringKey(OOStringExpansionContext *context, NSString *key, NSUInteger sizeLimit, NSUInteger recursionLimit) { NSCParameterAssert(context != NULL && key != nil); // Overrides have top priority. NSString *result = ExpandStringKeyOverride(context, key); // Specials override descriptions.plist. if (result == nil) result = ExpandStringKeySpecial(context, key); // Now try descriptions.plist. if (result == nil) result = ExpandStringKeyFromDescriptions(context, key, sizeLimit, recursionLimit); // For efficiency, descriptions.plist overrides keybindings. // OXPers should therefore avoid oolite_key_ description keys if (result == nil) result = ExpandStringKeyKeyboardBinding(context, key); // Try mission variables. if (result == nil) result = ExpandStringKeyMissionVariable(context, key); // Try legacy local variables. if (result == nil) result = ExpandStringKeyLegacyLocalVariable(context, key); // Try legacy script methods. if (result == nil) ExpandLegacyScriptSelectorKey(context, key); #if WARNINGS // None of that worked, so moan a bit. if (result == nil) ReportWarningForUnknownKey(context, key); #endif return result; } /* ExpandStringKeyOverride(context, key) Attempt to expand a key by retriving it from the overrides dictionary of the context (ultimately from OOExpandDescriptionString()). Overrides are used to provide context-specific expansions, such as "[self:name]" in comms messages, and can also be used from JavaScript. The main difference between overrides and legacy locals is priority. */ static NSString *ExpandStringKeyOverride(OOStringExpansionContext *context, NSString *key) { NSCParameterAssert(context != NULL && key != nil); id value = [context->overrides objectForKey:key]; if (value != nil) { #if WARNINGS if (![value isKindOfClass:[NSString class]] && ![value isKindOfClass:[NSNumber class]]) { SyntaxWarning(context, @"strings.expand.warning.invalidOverride", @"String expansion override value %@ for [%@] is not a string or number.", [value shortDescription], key); } #endif return [value description]; } return nil; } /* ExpandStringKeySpecial(context, key) Attempt to expand a key by matching a set of special expansion codes that call PlayerEntity methods but aren't legacy script methods. Also unlike legacy script methods, all these methods return strings. */ static NSString *ExpandStringKeySpecial(OOStringExpansionContext *context, NSString *key) { NSCParameterAssert(context != NULL && key != nil); NSMapTable *specials = SpecialSubstitutionSelectors(); SEL selector = NSMapGet(specials, key); if (selector != NULL) { NSCAssert2([PLAYER respondsToSelector:selector], @"Special string expansion selector %@ for [%@] is not implemented.", NSStringFromSelector(selector), key); NSString *result = [PLAYER performSelector:selector]; if (result != nil) { NSCAssert2([result isKindOfClass:[NSString class]], @"Special string expansion [%@] expanded to %@, but expected a string.", key, [result shortDescription]); return result; } } return nil; } /* ExpandStringKeyKeyboardBinding(context, key) Attempt to expand a key by matching it against the keybindings */ static NSString *ExpandStringKeyKeyboardBinding(OOStringExpansionContext *context, NSString *key) { NSCParameterAssert(context != NULL && key != nil); if ([key hasPrefix:@"oolite_key_"]) { NSString *binding = [key substringFromIndex:7]; return [PLAYER keyBindingDescription:binding]; } return nil; } /* SpecialSubstitutionSelectors() Retrieve the mapping of special keys for ExpandStringKeySpecial() to selectors. */ static NSMapTable *SpecialSubstitutionSelectors(void) { static NSMapTable *specials = NULL; if (specials != NULL) return specials; struct { NSString *key; SEL selector; } selectors[] = { { @"commander_name", @selector(commanderName_string) }, { @"commander_shipname", @selector(commanderShip_string) }, { @"commander_shipdisplayname", @selector(commanderShipDisplayName_string) }, { @"commander_rank", @selector(commanderRank_string) }, { @"commander_kills", @selector(commanderKillsAsString) }, { @"commander_legal_status", @selector(commanderLegalStatus_string) }, { @"commander_bounty", @selector(commanderBountyAsString) }, { @"credits_number", @selector(creditsFormattedForSubstitution) }, { @"_oo_legacy_credits_number", @selector(creditsFormattedForLegacySubstitution) } }; unsigned i, count = sizeof selectors / sizeof *selectors; specials = NSCreateMapTable(NSObjectMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, count); for (i = 0; i < count; i++) { NSMapInsertKnownAbsent(specials, selectors[i].key, selectors[i].selector); } return specials; } /* ExpandStringKeyFromDescriptions(context, key, sizeLimit, recursionLimit) Attempt to expand a key by looking it up in descriptions.plist. Matches may be single strings or arrays of strings. For arrays, one of the strings is selected at random. Matched strings are expanded recursively by calling Expand(). */ static NSString *ExpandStringKeyFromDescriptions(OOStringExpansionContext *context, NSString *key, NSUInteger sizeLimit, NSUInteger recursionLimit) { id value = [[UNIVERSE descriptions] objectForKey:key]; if (value != nil) { if ([value isKindOfClass:[NSArray class]] && [value count] > 0) { NSUInteger rnd = OO_EXPANDER_RANDOM % [value count]; value = [value oo_objectAtIndex:rnd]; } if (![value isKindOfClass:[NSString class]]) { // This is out of the scope of whatever triggered it, so shouldn't be a JS warning. OOLogERR(@"strings.expand.invalidData", @"String expansion value %@ for [%@] from descriptions.plist is not a string or number.", [value shortDescription], key); return nil; } // Expand recursively. return Expand(context, value, sizeLimit, recursionLimit); } return nil; } /* ExpandStringKeyMissionVariable(context, key) Attempt to expand a key by matching it to a mission variable. */ static NSString *ExpandStringKeyMissionVariable(OOStringExpansionContext *context, NSString *key) { if ([key hasPrefix:@"mission_"]) { return [PLAYER missionVariableForKey:key]; } return nil; } /* ExpandStringKeyMissionVariable(context, key) Attempt to expand a key by matching it to a legacy local variable. The main difference between overrides and legacy locals is priority. */ static NSString *ExpandStringKeyLegacyLocalVariable(OOStringExpansionContext *context, NSString *key) { return [[context->legacyLocals objectForKey:key] description]; } /* ExpandLegacyScriptSelectorKey(context, key) Attempt to expand a key by treating it as a legacy script query method and invoking it. Only whitelisted methods are permitted, and aliases are respected. */ static NSString *ExpandLegacyScriptSelectorKey(OOStringExpansionContext *context, NSString *key) { NSCParameterAssert(context != NULL && key != nil); SEL selector = LookUpLegacySelector(key); if (selector != NULL) { return [[PLAYER performSelector:selector] description]; } else { return nil; } } /* LookUpLegacySelector(key) If is a whitelisted legacy script query method, or aliases to one, return the corresponding selector. */ static SEL LookUpLegacySelector(NSString *key) { SEL selector = NULL; static NSMapTable *selectorCache = NULL; // Try cache lookup. if (selectorCache != NULL) { selector = NSMapGet(selectorCache, key); } if (selector == NULL) { static NSDictionary *aliases = nil; static NSSet *whitelist = nil; if (whitelist == nil) { NSDictionary *whitelistDict = [ResourceManager whitelistDictionary]; whitelist = [[NSSet alloc] initWithArray:[whitelistDict oo_arrayForKey:@"query_methods"]]; aliases = [[whitelistDict oo_dictionaryForKey:@"query_method_aliases"] copy]; } NSString *selectorName = [aliases oo_stringForKey:key]; if (selectorName == nil) selectorName = key; if ([whitelist containsObject:selectorName]) { selector = NSSelectorFromString(selectorName); /* This is an assertion, not a warning, because whitelist.plist is part of the game and cannot be overriden by OXPs. If there is an invalid selector in the whitelist, it's a game bug. */ NSCAssert1([PLAYER respondsToSelector:selector], @"Player does not respond to whitelisted query selector %@.", key); } if (selector != NULL) { // Add it to cache. if (selectorCache == NULL) { selectorCache = NSCreateMapTable(NSObjectMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, [whitelist count]); } NSMapInsertKnownAbsent(selectorCache, key, selector); } } return selector; } #if WARNINGS /* ReportWarningForUnknownKey(context, key) Called when we fall through all the various ways of expanding string keys above. If the key looks like a legacy script query method, assume it is and report a bad selector. Otherwise, report it as an unknown key. */ static void ReportWarningForUnknownKey(OOStringExpansionContext *context, NSString *key) { if ([key hasSuffix:@"_string"] || [key hasSuffix:@"_number"] || [key hasSuffix:@"_bool"]) { SyntaxError(context, @"strings.expand.invalidSelector", @"Unpermitted legacy script method [%@] in string.", key); } else { SyntaxWarning(context, @"strings.expand.warning.unknownExpansion", @"Unknown expansion key [%@] in string.", key); } } #endif /* ExpandKey(context, characters, size, idx, replaceLength) Expand an escape code. is the index of the % sign introducing the escape code. Supported escape codes are: %H %I %N %R %J###, where ### are three digits %% %[ %] In addition, the codes %@, %d and %. are ignored, because they're used with -[NSString stringWithFormat:] on strings that have already been expanded. Any other code results in a warning. */ static NSString *ExpandPercentEscape(OOStringExpansionContext *context, const unichar *characters, NSUInteger size, NSUInteger idx, NSUInteger *replaceLength) { NSCParameterAssert(context != NULL && characters != NULL && replaceLength != NULL); NSCParameterAssert(characters[idx] == '%'); // All %-escapes except %J are 2 characters. *replaceLength = 2; unichar selector = characters[idx + 1]; switch (selector) { case 'H': return GetSystemName(context); case 'I': return GetSystemNameIan(context); case 'N': return GetRandomNameN(context); case 'R': // to keep planet description generation consistent with earlier versions // this must be done after all other substitutions in a second pass. context->hasPercentR = true; return @"%R"; case 'J': return ExpandSystemNameEscape(context, characters, size, idx, replaceLength); case '%': return @"%"; case '[': return @"["; case ']': return @"]"; /* These are NSString formatting specifiers that occur in descriptions.plist. The '.' is for floating-point (g and f) specifiers that have field widths specified. No unadorned %f or %g is found in vanilla Oolite descriptions.plist. Ideally, these would be replaced with the caller formatting the value and passing it as an override - it would be safer and make descriptions.plist clearer - but it would be a big job and uglify the callers without newfangled Objective-C dictionary literals. -- Ahruman 2012-10-05 */ case '@': case 'd': case '.': return nil; default: // Yay, percent signs! SyntaxWarning(context, @"strings.expand.warning.unknownPercentEscape", @"Unknown escape code in string: %%%lc. (To encode a %% sign without this warning, use %%%% - but prefer \"percent\" in prose writing.)", selector); return nil; } } /* ExpandPercentR(context, string) Replaces all %R in string with its expansion. Separate to allow this to be delayed to the end of the string expansion for compatibility with 1.76 expansion of %R in planet descriptions */ static NSString *ExpandPercentR(OOStringExpansionContext *context, NSString *input) { NSRange containsR = [input rangeOfString:@"%R"]; if (containsR.location == NSNotFound) { return input; // no %Rs to replace } NSString *percentR = GetRandomNameR(context); NSMutableString *output = [NSMutableString stringWithString:input]; /* This loop should be completely unnecessary, but for some reason * replaceOccurrencesOfString sometimes only replaces the first * instance of %R if percentR contains the non-ASCII * digrams-apostrophe character. (I guess * http://lists.gnu.org/archive/html/gnustep-dev/2011-10/msg00048.html * this bug in GNUstep's implementation here, which is in 1.22) So * to cover that case, if there are still %R in the string after * replacement, try again. Affects things like thargoid curses, and * particularly %Rful expansions of [nom]. Probably this can be * tidied up once GNUstep 1.22 is ancient history, but that'll be a * few years yet. - CIM 15/1/2013 */ do { [output replaceOccurrencesOfString:@"%R" withString:percentR options:NSLiteralSearch range:NSMakeRange(0, [output length])]; } while([output rangeOfString:@"%R"].location != NSNotFound); return [NSString stringWithString:output]; } /* ExpandSystemNameEscape(context, characters, size, idx, replaceLength) Expand a %J### code by looking up the corresponding system name in the current galaxy. */ static NSString *ExpandSystemNameEscape(OOStringExpansionContext *context, const unichar *characters, NSUInteger size, NSUInteger idx, NSUInteger *replaceLength) { NSCParameterAssert(context != NULL && characters != NULL && replaceLength != NULL); NSCParameterAssert(characters[idx + 1] == 'J'); // A valid %J escape is always five characters including the three digits. *replaceLength = 5; #define kInvalidJEscapeMessage @"String escape code %%J must be followed by three integers." if (EXPECT_NOT(size - idx < 5)) { // Too close to end of string to actually have three characters, let alone three digits. SyntaxError(context, @"strings.expand.invalidJEscape", kInvalidJEscapeMessage); return nil; } char hundreds = characters[idx + 2]; char tens = characters[idx + 3]; char units = characters[idx + 4]; if (!(isdigit(hundreds) && isdigit(tens) && isdigit(units))) { SyntaxError(context, @"strings.expand.invalidJEscape", kInvalidJEscapeMessage); return nil; } OOSystemID sysID = (hundreds - '0') * 100 + (tens - '0') * 10 + (units - '0'); if (sysID > kOOMaximumSystemID) { SyntaxError(context, @"strings.expand.invalidJEscape.range", @"String escape code %%J%3u is out of range (must be less than %u).", sysID, kOOMaximumSystemID + 1); return nil; } return [UNIVERSE getSystemName:sysID]; } static void AppendCharacters(NSMutableString **result, const unichar *characters, NSUInteger start, NSUInteger end) { NSCParameterAssert(result != NULL && characters != NULL && start <= end); if (*result == nil) { // Ensure there is a string. We want this even if the range is empty. *result = [NSMutableString string]; } if (start == end) return; /* What we want here is a method like -[NSMutableString appendCharacters:(unichar)characters length:(NSUInteger)length], which unfortunately doesn't exist. On Mac OS X, CoreFoundation provides an equivalent. For GNUstep, we have to use a temporary string. TODO: build the output string in a fixed-size stack buffer instead. */ #if OOLITE_MAC_OS_X CFStringAppendCharacters((CFMutableStringRef)*result, characters + start, end - start); #else NSString *temp = [[NSString alloc] initWithCharacters:characters + start length:end - start]; [*result appendString:temp]; [temp release]; #endif } static NSString *GetSystemName(OOStringExpansionContext *context) { NSCParameterAssert(context != NULL); if (context->systemName == nil) { context->systemName = [[UNIVERSE getSystemName:[PLAYER systemID]] retain]; } return context->systemName; } static NSString *GetSystemNameIan(OOStringExpansionContext *context) { NSCParameterAssert(context != NULL); if (context->systemNameWithIan == nil) { context->systemNameWithIan = [OOExpandWithOptions(context->seed, kOOExpandDisallowPercentI | kOOExpandGoodRNG | kOOExpandKey, @"planetname-possessive") retain]; } return context->systemNameWithIan; } static NSString *GetRandomNameN(OOStringExpansionContext *context) { NSCParameterAssert(context != NULL); if (context->randomNameN == nil) { context->randomNameN = [NewRandomDigrams(context) retain]; } return context->randomNameN; } static NSString *GetRandomNameR(OOStringExpansionContext *context) { NSCParameterAssert(context != NULL); if (context->randomNameR == nil) { context->randomNameR = [OldRandomDigrams() retain]; } return context->randomNameR; } static NSArray *GetSystemDescriptions(OOStringExpansionContext *context) { NSCParameterAssert(context != NULL); if (context->systemDescriptions == nil) { context->systemDescriptions = [[[UNIVERSE descriptions] oo_arrayForKey:@"system_description"] retain]; context->sysDescCount = [context->systemDescriptions count]; } return context->systemDescriptions; } /* Generates pseudo-random digram string using gen_rnd_number() (world-generation consistent PRNG), but misses some possibilities. Used for "%R" description string for backwards compatibility. */ static NSString *OldRandomDigrams(void) { /* The only point of using %R is for world generation, so there's * no point in checking the context */ unsigned len = gen_rnd_number() & 3; NSString *digrams = [[UNIVERSE descriptions] objectForKey:@"digrams"]; NSMutableString *name = [NSMutableString stringWithCapacity:256]; for (unsigned i = 0; i <=len; i++) { unsigned x = gen_rnd_number() & 0x3e; [name appendString:[digrams substringWithRange:NSMakeRange(x, 2)]]; } return [name capitalizedString]; } /* Generates pseudo-random digram string. Used for "%N" description string. */ static NSString *NewRandomDigrams(OOStringExpansionContext *context) { unsigned length = (OO_EXPANDER_RANDOM % 4) + 1; if ((OO_EXPANDER_RANDOM % 5) < ((length == 1) ? 3 : 1)) ++length; // Make two-letter names rarer and 10-letter names happen sometimes NSString *digrams = [[UNIVERSE descriptions] objectForKey:@"digrams"]; NSUInteger count = [digrams length] / 2; NSMutableString *name = [NSMutableString stringWithCapacity:length * 2]; for (unsigned i = 0; i != length; ++i) { [name appendString:[digrams substringWithRange:NSMakeRange((OO_EXPANDER_RANDOM % count) * 2, 2)]]; } return [name capitalizedString]; } static void SyntaxIssue(OOStringExpansionContext *context, const char *function, const char *fileName, NSUInteger line, NSString *logMessageClass, NSString *prefix, NSString *format, ...) { NSCParameterAssert(context != NULL); va_list args; va_start(args, format); if (OOLogWillDisplayMessagesInClass(logMessageClass)) { if (context->isJavaScript) { /* NOTE: syntax errors are reported as warnings when called from JS because we don't want to start throwing exceptions when the old expander didn't. */ JSContext *jsc = OOJSAcquireContext(); OOJSReportWarningWithArguments(jsc, format, args); OOJSRelinquishContext(jsc); } else { format = [prefix stringByAppendingString:format]; OOLogWithFunctionFileAndLineAndArguments(logMessageClass, function, fileName, line, format, args); } } va_end(args); } oolite-1.82/src/Core/OOStringParsing.h000066400000000000000000000074371256642440500176420ustar00rootroot00000000000000/* OOStringParsing.h Various functions for interpreting values from strings. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #import "OOMaths.h" #import "OOTypes.h" #import "legacy_random.h" @class Entity; NSMutableArray *ScanTokensFromString(NSString *values); // Note: these functions will leave their out values untouched if they fail (and return NO). They will not log an error if passed a NULL string (but will return NO). This means they can be used to, say, read dictionary entries which might not exist. They also ignore any extra components in the string. BOOL ScanVectorFromString(NSString *xyzString, Vector *outVector); BOOL ScanHPVectorFromString(NSString *xyzString, HPVector *outVector); BOOL ScanQuaternionFromString(NSString *wxyzString, Quaternion *outQuaternion); BOOL ScanVectorAndQuaternionFromString(NSString *xyzwxyzString, Vector *outVector, Quaternion *outQuaternion); Vector VectorFromString(NSString *xyzString, Vector defaultValue); Quaternion QuaternionFromString(NSString *wxyzString, Quaternion defaultValue); NSString *StringFromPoint(NSPoint point); NSPoint PointFromString(NSString *xyString); Random_Seed RandomSeedFromString(NSString *abcdefString); NSString *StringFromRandomSeed(Random_Seed seed); NSString *OOStringFromDeciCredits(OOCreditsQuantity tenthsOfCredits, BOOL includeDecimal, BOOL includeSymbol); OOINLINE NSString *OOStringFromIntCredits(OOCreditsQuantity integerCredits, BOOL includeSymbol) { return OOStringFromDeciCredits(integerCredits * 10, NO, includeSymbol); } OOINLINE NSString *OOCredits(OOCreditsQuantity tenthsOfCredits) { return OOStringFromDeciCredits(tenthsOfCredits, YES, YES); } OOINLINE NSString *OOIntCredits(OOCreditsQuantity integerCredits) { return OOStringFromIntCredits(integerCredits, YES); } NSString *OOPadStringToEms(NSString * string, float numEms); @interface NSString (OOUtilities) // Case-insensitive match of [self pathExtension] - (BOOL)pathHasExtension:(NSString *)extension; - (BOOL)pathHasExtensionInArray:(NSArray *)extensions; @end // Given a string of the form 1.2.3.4 (with arbitrarily many components), return an array of unsigned ints. NSArray *ComponentsFromVersionString(NSString *string); /* Compare two arrays of unsigned int NSNumbers, as returned by ComponentsFromVersionString(). Components are ordered from most to least significant, and a missing component is treated as 0. Thus "1.7" < "1.60", and "1.2.3.0" == "1.2.3". */ NSComparisonResult CompareVersions(NSArray *version1, NSArray *version2); NSString *ClockToString(double clock, BOOL adjusting); #if DEBUG_GRAPHVIZ NSString *EscapedGraphVizString(NSString *string); /* GraphVizTokenString() Generate a C-style identifier. Sequences of invalid characters and underscores are replaced with single underscores. If uniqueSet is not nil, uniqueness is achieved by appending numbers if necessary. This can be used for any C-based langauge, but note that it excludes the case-insensitive GraphViz keywords node, edge, graph, digraph, subgraph and strict. */ NSString *GraphVizTokenString(NSString *string, NSMutableSet *uniqueSet); #endif oolite-1.82/src/Core/OOStringParsing.m000066400000000000000000000335551256642440500176470ustar00rootroot00000000000000/* OOStringParsing.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOStringParsing.h" #import "OOLogging.h" #import "NSScannerOOExtensions.h" #import "legacy_random.h" #import "Universe.h" #import "PlayerEntity.h" #import "PlayerEntityLegacyScriptEngine.h" #import "OOFunctionAttributes.h" #import "OOCollectionExtractors.h" #import "ResourceManager.h" #import "HeadUpDisplay.h" #import "OOJavaScriptEngine.h" #import "OOJSEngineTimeManagement.h" static NSString * const kOOLogStringVectorConversion = @"strings.conversion.vector"; static NSString * const kOOLogStringQuaternionConversion = @"strings.conversion.quaternion"; static NSString * const kOOLogStringRandomSeedConversion = @"strings.conversion.randomSeed"; NSMutableArray *ScanTokensFromString(NSString *values) { NSMutableArray *result = nil; NSScanner *scanner = nil; NSString *token = nil; static NSCharacterSet *space_set = nil; // Note: Shark suggests we're getting a lot of early exits, but testing showed a pretty steady 2% early exit rate. if (EXPECT_NOT(values == nil)) return [NSMutableArray array]; if (EXPECT_NOT(space_set == nil)) space_set = [[NSCharacterSet whitespaceAndNewlineCharacterSet] retain]; result = [NSMutableArray array]; scanner = [NSScanner scannerWithString:values]; while (![scanner isAtEnd]) { [scanner ooliteScanCharactersFromSet:space_set intoString:NULL]; if ([scanner ooliteScanUpToCharactersFromSet:space_set intoString:&token]) { [result addObject:token]; } } return result; } BOOL ScanVectorFromString(NSString *xyzString, Vector *outVector) { GLfloat xyz[] = {0.0, 0.0, 0.0}; int i = 0; NSString *error = nil; NSScanner *scanner = nil; assert(outVector != NULL); if (xyzString == nil) return NO; if (!error) scanner = [NSScanner scannerWithString:xyzString]; while (![scanner isAtEnd] && i < 3 && !error) { if (![scanner scanFloat:&xyz[i++]]) error = @"could not scan a float value."; } if (!error && i < 3) error = @"found less than three float values."; if (!error) { *outVector = make_vector(xyz[0], xyz[1], xyz[2]); return YES; } else { OOLogERR(kOOLogStringVectorConversion, @"cannot make vector from '%@': %@", xyzString, error); return NO; } } BOOL ScanHPVectorFromString(NSString *xyzString, HPVector *outVector) { Vector scanVector; assert(outVector != NULL); BOOL result = ScanVectorFromString(xyzString, &scanVector); if (!result) { return NO; } *outVector = vectorToHPVector(scanVector); return YES; } BOOL ScanQuaternionFromString(NSString *wxyzString, Quaternion *outQuaternion) { GLfloat wxyz[] = {1.0, 0.0, 0.0, 0.0}; int i = 0; NSString *error = nil; NSScanner *scanner = nil; assert(outQuaternion != NULL); if (wxyzString == nil) return NO; if (!error) scanner = [NSScanner scannerWithString:wxyzString]; while (![scanner isAtEnd] && i < 4 && !error) { if (![scanner scanFloat:&wxyz[i++]]) error = @"could not scan a float value."; } if (!error && i < 4) error = @"found less than four float values."; if (!error) { outQuaternion->w = wxyz[0]; outQuaternion->x = wxyz[1]; outQuaternion->y = wxyz[2]; outQuaternion->z = wxyz[3]; quaternion_normalize(outQuaternion); return YES; } else { OOLogERR(kOOLogStringQuaternionConversion, @"cannot make quaternion from '%@': %@", wxyzString, error); return NO; } } BOOL ScanVectorAndQuaternionFromString(NSString *xyzwxyzString, Vector *outVector, Quaternion *outQuaternion) { GLfloat xyzwxyz[] = { 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0}; int i = 0; NSString *error = nil; NSScanner *scanner = nil; assert(outVector != NULL && outQuaternion != NULL); if (xyzwxyzString == nil) return NO; if (!error) scanner = [NSScanner scannerWithString:xyzwxyzString]; while (![scanner isAtEnd] && i < 7 && !error) { if (![scanner scanFloat:&xyzwxyz[i++]]) error = @"Could not scan a float value."; } if (!error && i < 7) error = @"Found less than seven float values."; if (error) { OOLogERR(kOOLogStringQuaternionConversion, @"cannot make vector and quaternion from '%@': %@", xyzwxyzString, error); return NO; } outVector->x = xyzwxyz[0]; outVector->y = xyzwxyz[1]; outVector->z = xyzwxyz[2]; outQuaternion->w = xyzwxyz[3]; outQuaternion->x = xyzwxyz[4]; outQuaternion->y = xyzwxyz[5]; outQuaternion->z = xyzwxyz[6]; return YES; } Vector VectorFromString(NSString *xyzString, Vector defaultValue) { Vector result; if (!ScanVectorFromString(xyzString, &result)) result = defaultValue; return result; } Quaternion QuaternionFromString(NSString *wxyzString, Quaternion defaultValue) { Quaternion result; if (!ScanQuaternionFromString(wxyzString, &result)) result = defaultValue; return result; } NSString *StringFromPoint(NSPoint point) { return [NSString stringWithFormat:@"%f %f", point.x, point.y]; } NSPoint PointFromString(NSString *xyString) { NSArray *tokens = ScanTokensFromString(xyString); NSPoint result = NSZeroPoint; NSUInteger n_tokens = [tokens count]; if (n_tokens == 2) { result.x = [[tokens objectAtIndex:0] floatValue]; result.y = [[tokens objectAtIndex:1] floatValue]; } return result; } Random_Seed RandomSeedFromString(NSString *abcdefString) { Random_Seed result; int abcdef[] = { 0, 0, 0, 0, 0, 0}; int i = 0; NSString *error = nil; NSScanner *scanner = [NSScanner scannerWithString:abcdefString]; while (![scanner isAtEnd] && i < 6 && !error) { if (![scanner scanInt:&abcdef[i++]]) error = @"could not scan a int value."; } if (!error && i < 6) error = @"found less than six int values."; if (!error) { result.a = abcdef[0]; result.b = abcdef[1]; result.c = abcdef[2]; result.d = abcdef[3]; result.e = abcdef[4]; result.f = abcdef[5]; } else { OOLogERR(kOOLogStringRandomSeedConversion, @"cannot make Random_Seed from '%@': %@", abcdefString, error); result = kNilRandomSeed; } return result; } NSString *StringFromRandomSeed(Random_Seed seed) { return [NSString stringWithFormat: @"%d %d %d %d %d %d", seed.a, seed.b, seed.c, seed.d, seed.e, seed.f]; } NSString *OOPadStringToEms(NSString * string, float padEms) { NSString *result = string; float numEms = padEms - OOStringWidthInEm(result); if (numEms>0) { numEms /= OOStringWidthInEm(@" "); // start with wide space result=[[@"" stringByPaddingToLength:(NSUInteger)numEms withString: @" " startingAtIndex:0] stringByAppendingString: result]; } // most of the way there, so switch to narrow space numEms = padEms - OOStringWidthInEm(result); if (numEms>0) { numEms /= OOStringWidthInEm(@"\037"); // 037 is narrow space result=[[@"" stringByPaddingToLength:(NSUInteger)numEms withString: @"\037" startingAtIndex:0] stringByAppendingString: result]; } return result; } NSString *OOStringFromDeciCredits(OOCreditsQuantity tenthsOfCredits, BOOL includeDecimal, BOOL includeSymbol) { JSContext *context = OOJSAcquireContext(); JSObject *global = [[OOJavaScriptEngine sharedEngine] globalObject]; JSObject *fakeRoot; jsval method; jsval rval; NSString *result = nil; jsval exception; BOOL hadException; /* Because the |cr etc. formatting operators call this, and the implementation may use string expansion, we need to ensure recursion can't happen. */ static BOOL reentrancyLock; if (reentrancyLock) return [NSString stringWithFormat:@"%0.1f", tenthsOfCredits * 0.1]; reentrancyLock = YES; hadException = JS_GetPendingException(context, &exception); JS_ClearPendingException(context); if (JS_GetMethodById(context, global, OOJSID("formatCredits"), &fakeRoot, &method)) { jsval args[3]; if (JS_NewNumberValue(context, tenthsOfCredits * 0.1, &args[0])) { args[1] = OOJSValueFromBOOL(includeDecimal); args[2] = OOJSValueFromBOOL(includeSymbol); OOJSStartTimeLimiter(); JS_CallFunctionValue(context, global, method, 3, args, &rval); OOJSStopTimeLimiter(); result = OOStringFromJSValue(context, rval); } } if (hadException) JS_SetPendingException(context, exception); OOJSRelinquishContext(context); if (EXPECT_NOT(result == nil)) result = [NSString stringWithFormat:@"%li", (long)(tenthsOfCredits) / 10]; reentrancyLock = NO; return result; } @implementation NSString (OOUtilities) - (BOOL)pathHasExtension:(NSString *)extension { return [[self pathExtension] caseInsensitiveCompare:extension] == NSOrderedSame; } - (BOOL)pathHasExtensionInArray:(NSArray *)extensions { NSEnumerator *extEnum = nil; NSString *extension = nil; for (extEnum = [extensions objectEnumerator]; (extension = [extEnum nextObject]); ) { if ([[self pathExtension] caseInsensitiveCompare:extension] == NSOrderedSame) return YES; } return NO; } @end NSArray *ComponentsFromVersionString(NSString *string) { NSArray *stringComponents = nil; NSMutableArray *result = nil; NSUInteger i, count; int value; id component; stringComponents = [string componentsSeparatedByString:@" "]; stringComponents = [[stringComponents objectAtIndex:0] componentsSeparatedByString:@"-"]; stringComponents = [[stringComponents objectAtIndex:0] componentsSeparatedByString:@"."]; count = [stringComponents count]; result = [NSMutableArray arrayWithCapacity:count]; for (i = 0; i != count; ++i) { component = [stringComponents objectAtIndex:i]; if ([component respondsToSelector:@selector(intValue)]) value = MAX([component intValue], 0); else value = 0; [result addObject:[NSNumber numberWithUnsignedInt:value]]; } return result; } NSComparisonResult CompareVersions(NSArray *version1, NSArray *version2) { NSEnumerator *leftEnum = nil, *rightEnum = nil; NSNumber *leftComponent = nil, *rightComponent = nil; unsigned leftValue, rightValue; leftEnum = [version1 objectEnumerator]; rightEnum = [version2 objectEnumerator]; for (;;) { leftComponent = [leftEnum nextObject]; rightComponent = [rightEnum nextObject]; if (leftComponent == nil && rightComponent == nil) break; // End of both versions // We'll get 0 if the component is nil, which is what we want. leftValue = [leftComponent unsignedIntValue]; rightValue = [rightComponent unsignedIntValue]; if (leftValue < rightValue) return NSOrderedAscending; if (leftValue > rightValue) return NSOrderedDescending; } // If there was a difference, we'd have returned already. return NSOrderedSame; } NSString *ClockToString(double clock, BOOL adjusting) { int days, hrs, mins, secs; NSString *format = nil; days = floor(clock / 86400.0); secs = floor(clock - days * 86400.0); hrs = floor(secs / 3600.0); secs %= 3600; mins = floor(secs / 60.0); secs %= 60; if (adjusting) format = DESC(@"clock-format-adjusting"); else format = DESC(@"clock-format"); return [NSString stringWithFormat:format, days, hrs, mins, secs]; } #if DEBUG_GRAPHVIZ NSString *EscapedGraphVizString(NSString *string) { NSString * const srcStrings[] = { //Note: backslash must be first. @"\\", @"\"", @"\'", @"\r", @"\n", @"\t", nil }; NSString * const subStrings[] = { //Note: must be same order. @"\\\\", @"\\\"", @"\\\'", @"\\r", @"\\n", @"\\t", nil }; NSString * const * src = srcStrings; NSString * const * sub = subStrings; NSMutableString *mutable = nil; NSString *result = nil; mutable = [string mutableCopy]; while (*src != nil) { [mutable replaceOccurrencesOfString:*src++ withString:*sub++ options:0 range:(NSRange){ 0, [mutable length] }]; } if ([mutable length] == [string length]) { result = string; } else { result = [[mutable copy] autorelease]; } [mutable release]; return result; } static BOOL NameIsTaken(NSString *name, NSSet *uniqueSet); NSString *GraphVizTokenString(NSString *string, NSMutableSet *uniqueSet) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; BOOL lastWasUnderscore = NO; NSUInteger i, length = [string length], ri = 0; unichar result[length]; NSString *token = nil; if (length > 0) { // Special case for first char - can't be digit. unichar c = [string characterAtIndex:0]; if (!isalpha(c)) { c = '_'; lastWasUnderscore = YES; } result[ri++] = c; for (i = 1; i < length; i++) { c = [string characterAtIndex:i]; if (!isalnum(c)) { if (lastWasUnderscore) continue; c = '_'; lastWasUnderscore = YES; } else { lastWasUnderscore = NO; } result[ri++] = c; } token = [NSString stringWithCharacters:result length:ri]; } else { token = @"_"; } if (NameIsTaken(token, uniqueSet)) { if (!lastWasUnderscore) token = [token stringByAppendingString:@"_"]; NSString *uniqueToken = nil; unsigned uniqueID = 2; for (;;) { uniqueToken = [NSString stringWithFormat:@"%@%u", token, uniqueID]; if (!NameIsTaken(uniqueToken, uniqueSet)) break; } token = uniqueToken; } [uniqueSet addObject:token]; [token retain]; [pool release]; return [token autorelease]; } static BOOL NameIsTaken(NSString *name, NSSet *uniqueSet) { if ([uniqueSet containsObject:name]) return YES; static NSSet *keywords = nil; if (keywords == nil) keywords = [[NSSet alloc] initWithObjects:@"node", @"edge", @"graph", @"digraph", @"subgraph", @"strict", nil]; return [keywords containsObject:[name lowercaseString]]; } #endif //DEBUG_GRAPHVIZ oolite-1.82/src/Core/OOSystemDescriptionManager.h000066400000000000000000000071671256642440500220330ustar00rootroot00000000000000/* OOSystemDescriptionManager.h Class responsible for planet description data. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #import "OOTypes.h" #import "legacy_random.h" typedef enum { OO_LAYER_CORE = 0, OO_LAYER_OXP_STATIC = 1, OO_LAYER_OXP_DYNAMIC = 2, OO_LAYER_OXP_PRIORITY = 3 } OOSystemLayer; #define OO_SYSTEM_LAYERS 4 #define OO_SYSTEMS_PER_GALAXY (kOOMaximumSystemID+1) #define OO_GALAXIES_AVAILABLE (kOOMaximumGalaxyID+1) #define OO_SYSTEMS_AVAILABLE OO_SYSTEMS_PER_GALAXY * OO_GALAXIES_AVAILABLE // don't bother caching interstellar properties #define OO_SYSTEM_CACHE_LENGTH OO_SYSTEMS_AVAILABLE @interface OOSystemDescriptionEntry : NSObject { @private NSMutableDictionary *layers[OO_SYSTEM_LAYERS]; } - (void) setProperty:(NSString *)property forLayer:(OOSystemLayer)layer toValue:(id)value; - (id) getProperty:(NSString *)property forLayer:(OOSystemLayer)layer; @end /** * Note: forSystem: inGalaxy: returns from the (fast) propertyCache * * forSystemKey calculates the values - but is necessary for * interstellar space */ @interface OOSystemDescriptionManager : NSObject { @private NSMutableDictionary *universalProperties; OOSystemDescriptionEntry *interstellarSpace; NSMutableDictionary *systemDescriptions; NSMutableDictionary *propertyCache[OO_SYSTEM_CACHE_LENGTH]; NSMutableSet *propertiesInUse; NSPoint coordinatesCache[OO_SYSTEM_CACHE_LENGTH]; NSMutableArray *neighbourCache[OO_SYSTEM_CACHE_LENGTH]; NSMutableDictionary *scriptedChanges; } // this needs to be re-called every time system coordinates change // changing system coordinates after plist loading is probably // too much of a can of worms *anyway*, so currently it's only // called just after the manager data is loaded. - (void) buildRouteCache; - (void) setUniversalProperties:(NSDictionary *)properties; - (void) setInterstellarProperties:(NSDictionary *)properties; // this is used by planetinfo.plist and has default layer 1 - (void) setProperties:(NSDictionary *)properties forSystemKey:(NSString *)key; // this is used by Javascript property setting - (void) setProperty:(NSString *)property forSystemKey:(NSString *)key andLayer:(OOSystemLayer)layer toValue:(id)value fromManifest:(NSString *)manifest; - (void) importScriptedChanges:(NSDictionary *)scripted; - (void) importLegacyScriptedChanges:(NSDictionary *)scripted; - (NSDictionary *) exportScriptedChanges; - (NSDictionary *) getPropertiesForSystemKey:(NSString *)key; - (NSDictionary *) getPropertiesForCurrentSystem; - (id) getProperty:(NSString *)property forSystemKey:(NSString *)key; - (id) getProperty:(NSString *)property forSystem:(OOSystemID)s inGalaxy:(OOGalaxyID)g; - (NSPoint) getCoordinatesForSystem:(OOSystemID)s inGalaxy:(OOGalaxyID)g; - (NSArray *) getNeighbourIDsForSystem:(OOSystemID)s inGalaxy:(OOGalaxyID)g; - (Random_Seed) getRandomSeedForCurrentSystem; - (Random_Seed) getRandomSeedForSystem:(OOSystemID)s inGalaxy:(OOGalaxyID)g; @end oolite-1.82/src/Core/OOSystemDescriptionManager.m000066400000000000000000000532541256642440500220360ustar00rootroot00000000000000/* OOPlanetDescriptionManager.h Singleton class responsible for planet description data. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOSystemDescriptionManager.h" #import "OOStringParsing.h" #import "OOCollectionExtractors.h" #import "OOTypes.h" #import "PlayerEntity.h" #import "Universe.h" #import "ResourceManager.h" // character sequence which can't be in property name, system key or layer // and is highly unlikely to be in a manifest identifier static NSString * const kOOScriptedChangeJoiner = @"~|~"; // likely maximum number of planetinfo properties to be applied to a system // just for efficiency - no harm in exceeding it #define OO_LIKELY_PROPERTIES_PER_SYSTEM 50 @interface OOSystemDescriptionManager (OOPrivate) - (void) setProperties:(NSDictionary *)properties inDescription:(OOSystemDescriptionEntry *)desc; - (NSDictionary *) calculatePropertiesForSystemKey:(NSString *)key; - (void) updateCacheEntry:(NSUInteger)i; - (void) updateCacheEntry:(NSUInteger)i forProperty:(NSString *)property; - (id) getProperty:(NSString *)property forSystemKey:(NSString *)key withUniversal:(BOOL)universal; /* some planetinfo properties have two ways to specify * need to get the one with higher layer (if they're both at the same layer, * go with property1) */ - (id) getProperty:(NSString *)property1 orProperty:(NSString *)property2 forSystemKey:(NSString *)key withUniversal:(BOOL)universal; - (void) saveScriptedChangeToProperty:(NSString *)property forSystemKey:(NSString *)key andLayer:(OOSystemLayer)layer toValue:(id)value fromManifest:(NSString *)manifest; @end static NSString *kOOSystemLayerProperty = @"layer"; @implementation OOSystemDescriptionManager - (id) init { self = [super init]; if (self != nil) { universalProperties = [[NSMutableDictionary alloc] initWithCapacity:OO_LIKELY_PROPERTIES_PER_SYSTEM]; interstellarSpace = [[OOSystemDescriptionEntry alloc] init]; // assume specific interstellar settings are rare systemDescriptions = [[NSMutableDictionary alloc] initWithCapacity:OO_SYSTEM_CACHE_LENGTH+20]; for (NSUInteger i=0;i= OO_SYSTEM_CACHE_LENGTH) { OOLog(@"system.description.error",@"'%@' is an invalid system key. This is an internal error. Please report it.",key); } else { [self updateCacheEntry:index]; } } } - (void) setProperty:(NSString *)property forSystemKey:(NSString *)key andLayer:(OOSystemLayer)layer toValue:(id)value fromManifest:(NSString *)manifest { OOSystemDescriptionEntry *desc = [systemDescriptions objectForKey:key]; if (desc == nil) { // create it desc = [[[OOSystemDescriptionEntry alloc] init] autorelease]; [systemDescriptions setObject:desc forKey:key]; } [desc setProperty:property forLayer:layer toValue:value]; [propertiesInUse addObject:property]; NSArray *tokens = ScanTokensFromString(key); if ([tokens count] == 2 && [tokens oo_unsignedIntegerAtIndex:0] < OO_GALAXIES_AVAILABLE && [tokens oo_unsignedIntegerAtIndex:1] < OO_SYSTEMS_PER_GALAXY) { [self saveScriptedChangeToProperty:property forSystemKey:key andLayer:layer toValue:value fromManifest:manifest]; OOGalaxyID g = [tokens oo_unsignedIntegerAtIndex:0]; OOSystemID s = [tokens oo_unsignedIntegerAtIndex:1]; NSUInteger index = (g * OO_SYSTEMS_PER_GALAXY) + s; if (index >= OO_SYSTEM_CACHE_LENGTH) { OOLog(@"system.description.error",@"'%@' is an invalid system key. This is an internal error. Please report it.",key); } else { [self updateCacheEntry:index forProperty:property]; } } // for interstellar updates, save but don't update cache else if ([tokens count] == 4 && [tokens oo_unsignedIntegerAtIndex:1] < OO_GALAXIES_AVAILABLE && [tokens oo_unsignedIntegerAtIndex:2] < OO_SYSTEMS_PER_GALAXY && [tokens oo_unsignedIntegerAtIndex:3] < OO_SYSTEMS_PER_GALAXY) { [self saveScriptedChangeToProperty:property forSystemKey:key andLayer:layer toValue:value fromManifest:manifest]; } } - (void) saveScriptedChangeToProperty:(NSString *)property forSystemKey:(NSString *)key andLayer:(OOSystemLayer)layer toValue:(id)value fromManifest:(NSString *)manifest { // if OXP doesn't have a manifest, cancel saving the change if (manifest == nil) { return; } // OOLog(@"saving change",@"%@ %@ %@ %d",manifest,key,property,layer); NSArray *overrideKey = [NSArray arrayWithObjects:manifest,key,property,[[NSNumber numberWithInt:layer] stringValue],nil]; // Obj-C copes with NSArray keys to dictionaries fine, but the // plist format doesn't, so they can't be saved. NSString *overrideKeyStr = [overrideKey componentsJoinedByString:kOOScriptedChangeJoiner]; if (value != nil) { [scriptedChanges setObject:value forKey:overrideKeyStr]; } else { [scriptedChanges removeObjectForKey:overrideKeyStr]; } } - (void) importScriptedChanges:(NSDictionary *)scripted { NSArray *key = nil; NSString *keyStr = nil; NSString *manifest = nil; foreachkey(keyStr, scripted) { key = [keyStr componentsSeparatedByString:kOOScriptedChangeJoiner]; if ([key count] == 4) { manifest = [key oo_stringAtIndex:0]; if ([ResourceManager manifestForIdentifier:manifest] != nil) { // OOLog(@"importing",@"%@ -> %@",keyStr,[scripted objectForKey:keyStr]); [self setProperty:[key oo_stringAtIndex:2] forSystemKey:[key oo_stringAtIndex:1] andLayer:[key oo_intAtIndex:3] toValue:[scripted objectForKey:keyStr] fromManifest:manifest]; // and doing this set stores it into the manager's copy // of scripted changes // this means in theory we could import more than one } // else OXP not installed, do not load } else { OOLog(@"systemManager.import",@"Key '%@' has unexpected format - skipping",keyStr); } } } // import of the old local_planetinfo_overrides dictionary - (void) importLegacyScriptedChanges:(NSDictionary *)scripted { NSString *systemKey = nil; NSString *propertyKey = nil; NSString *defaultManifest = @"org.oolite.oolite"; foreachkey(systemKey,scripted) { NSDictionary *legacyChanges = [scripted oo_dictionaryForKey:systemKey]; if ([legacyChanges objectForKey:@"sun_gone_nova"] != nil) { // then this is a change to import even if we don't know // if the OXP is still installed foreachkey (propertyKey, legacyChanges) { [self setProperty:propertyKey forSystemKey:systemKey andLayer:OO_LAYER_OXP_DYNAMIC toValue:[legacyChanges objectForKey:propertyKey] fromManifest:defaultManifest]; } /* Fix for older savegames not having a larger sun radius * property set from the Nova mission. */ id sr = [self getProperty:@"sun_radius" forSystemKey:systemKey]; float sr_num = [sr floatValue]; if (sr_num < 600000) { // fix sun radius values [self setProperty:@"sun_radius" forSystemKey:systemKey andLayer:OO_LAYER_OXP_DYNAMIC toValue:[NSNumber numberWithFloat:sr_num+600000.0] fromManifest:defaultManifest]; } } } } - (NSDictionary *) exportScriptedChanges { return [[scriptedChanges copy] autorelease]; } - (NSDictionary *) getPropertiesForCurrentSystem { OOSystemID s = [UNIVERSE currentSystemID]; if (s >= 0) { NSUInteger index = ([PLAYER galaxyNumber] * OO_SYSTEMS_PER_GALAXY) + s; if (index >= OO_SYSTEM_CACHE_LENGTH) { OOLog(@"system.description.error",@"'%lu' is an invalid system index for the current system. This is an internal error. Please report it.",index); return [NSDictionary dictionary]; } return propertyCache[index]; } else { OOLog(@"system.description.error",@"getPropertiesForCurrentSystem called while player in interstellar space. This is an internal error. Please report it."); // this shouldn't be called for interstellar space return [NSDictionary dictionary]; } } - (NSDictionary *) getPropertiesForSystemKey:(NSString *)key { NSArray *tokens = ScanTokensFromString(key); if ([tokens count] == 2 && [tokens oo_unsignedIntegerAtIndex:0] < OO_GALAXIES_AVAILABLE && [tokens oo_unsignedIntegerAtIndex:1] < OO_SYSTEMS_PER_GALAXY) { OOGalaxyID g = [tokens oo_unsignedIntegerAtIndex:0]; OOSystemID s = [tokens oo_unsignedIntegerAtIndex:1]; NSUInteger index = (g * OO_SYSTEMS_PER_GALAXY) + s; if (index >= OO_SYSTEM_CACHE_LENGTH) { OOLog(@"system.description.error",@"'%@' is an invalid system key. This is an internal error. Please report it.",key); return [NSDictionary dictionary]; } return propertyCache[index]; } // interstellar spaces aren't cached return [self calculatePropertiesForSystemKey:key]; } - (id) getProperty:(NSString *)property forSystemKey:(NSString *)key { return [self getProperty:property forSystemKey:key withUniversal:YES]; } - (id) getProperty:(NSString *)property forSystem:(OOSystemID)s inGalaxy:(OOGalaxyID)g { if (s < 0) { OOLog(@"system.description.error",@"'%d %d' is an invalid system key. This is an internal error. Please report it.",g,s); return nil; } NSUInteger index = (g * OO_SYSTEMS_PER_GALAXY) + s; if (index >= OO_SYSTEM_CACHE_LENGTH) { OOLog(@"system.description.error",@"'%d %d' is an invalid system key. This is an internal error. Please report it.",g,s); return nil; } return [propertyCache[index] objectForKey:property]; } - (id) getProperty:(NSString *)property forSystemKey:(NSString *)key withUniversal:(BOOL)universal { OOSystemDescriptionEntry *desc = nil; if (EXPECT_NOT([key isEqualToString:@"interstellar"])) { desc = interstellarSpace; } else { desc = [systemDescriptions objectForKey:key]; } if (desc == nil) { return nil; } id result = nil; result = [desc getProperty:property forLayer:OO_LAYER_OXP_PRIORITY]; if (result == nil) { result = [desc getProperty:property forLayer:OO_LAYER_OXP_DYNAMIC]; } if (result == nil) { result = [desc getProperty:property forLayer:OO_LAYER_OXP_STATIC]; } if (result == nil && universal) { result = [universalProperties objectForKey:property]; } if (result == nil) { result = [desc getProperty:property forLayer:OO_LAYER_CORE]; } return result; } - (id) getProperty:(NSString *)property1 orProperty:(NSString *)property2 forSystemKey:(NSString *)key withUniversal:(BOOL)universal { OOSystemDescriptionEntry *desc = [systemDescriptions objectForKey:key]; if (desc == nil) { return nil; } id result = nil; result = [desc getProperty:property1 forLayer:OO_LAYER_OXP_PRIORITY]; if (result == nil) { result = [desc getProperty:property2 forLayer:OO_LAYER_OXP_PRIORITY]; } if (result == nil) { result = [desc getProperty:property1 forLayer:OO_LAYER_OXP_DYNAMIC]; } if (result == nil) { result = [desc getProperty:property2 forLayer:OO_LAYER_OXP_DYNAMIC]; } if (result == nil) { result = [desc getProperty:property1 forLayer:OO_LAYER_OXP_STATIC]; } if (result == nil) { result = [desc getProperty:property2 forLayer:OO_LAYER_OXP_STATIC]; } if (universal) { if (result == nil) { result = [universalProperties objectForKey:property1]; } if (result == nil) { result = [universalProperties objectForKey:property2]; } } if (result == nil) { result = [desc getProperty:property1 forLayer:OO_LAYER_CORE]; } if (result == nil) { result = [desc getProperty:property2 forLayer:OO_LAYER_CORE]; } return result; } - (void) setProperties:(NSDictionary *)properties inDescription:(OOSystemDescriptionEntry *)desc { OOSystemLayer layer = [properties oo_unsignedIntegerForKey:kOOSystemLayerProperty defaultValue:OO_LAYER_OXP_STATIC]; if (layer > OO_LAYER_OXP_PRIORITY) { OOLog(@"system.description.error",@"Layer %u is not a valid layer number in system information.",layer); layer = OO_LAYER_OXP_PRIORITY; } NSString *key = nil; foreachkey(key, properties) { if (![key isEqualToString:kOOSystemLayerProperty]) { [propertiesInUse addObject:key]; [desc setProperty:key forLayer:layer toValue:[properties objectForKey:key]]; } } } - (NSDictionary *) calculatePropertiesForSystemKey:(NSString *)key { NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:OO_LIKELY_PROPERTIES_PER_SYSTEM]; NSString *property = nil; id val = nil; BOOL interstellar = [key hasPrefix:@"interstellar:"]; foreach (property, propertiesInUse) { // don't use universal properties on interstellar specific regions val = [self getProperty:property forSystemKey:key withUniversal:!interstellar]; if (val != nil) { [dict setObject:val forKey:property]; } else if (interstellar) { // interstellar is always overridden by specific regions // universal properties for interstellar get picked up here val = [self getProperty:property forSystemKey:@"interstellar"]; if (val != nil) { [dict setObject:val forKey:property]; } } } return dict; } - (void) updateCacheEntry:(NSUInteger)i { NSAssert(i < OO_SYSTEM_CACHE_LENGTH,@"Invalid cache entry number"); NSString *key = [NSString stringWithFormat:@"%lu %lu",i/OO_SYSTEMS_PER_GALAXY,i%OO_SYSTEMS_PER_GALAXY]; NSDictionary *current = [self calculatePropertiesForSystemKey:key]; [propertyCache[i] removeAllObjects]; [propertyCache[i] addEntriesFromDictionary:current]; } - (void) updateCacheEntry:(NSUInteger)i forProperty:(NSString *)property { NSAssert(i < OO_SYSTEM_CACHE_LENGTH,@"Invalid cache entry number"); NSString *key = [NSString stringWithFormat:@"%lu %lu",i/OO_SYSTEMS_PER_GALAXY,i%OO_SYSTEMS_PER_GALAXY]; id current = [self getProperty:property forSystemKey:key]; if (current == nil) { [propertyCache[i] removeObjectForKey:property]; } else { [propertyCache[i] setObject:current forKey:property]; } } - (NSPoint) getCoordinatesForSystem:(OOSystemID)s inGalaxy:(OOGalaxyID)g { if (s < 0) { OOLog(@"system.description.error",@"'%d %d' is an invalid system key. This is an internal error. Please report it.",g,s); return (NSPoint){0,0}; } NSUInteger index = (g * OO_SYSTEMS_PER_GALAXY) + s; if (index >= OO_SYSTEM_CACHE_LENGTH) { OOLog(@"system.description.error",@"'%d %d' is an invalid system key. This is an internal error. Please report it.",g,s); return (NSPoint){0,0}; } return coordinatesCache[index]; } - (NSArray *) getNeighbourIDsForSystem:(OOSystemID)s inGalaxy:(OOGalaxyID)g { if (s < 0) { OOLog(@"system.description.error",@"'%d %d' is an invalid system key. This is an internal error. Please report it.",g,s); return nil; } NSUInteger index = (g * OO_SYSTEMS_PER_GALAXY) + s; if (index >= OO_SYSTEM_CACHE_LENGTH) { OOLog(@"system.description.error",@"'%d %d' is an invalid system key. This is an internal error. Please report it.",g,s); return nil; } return neighbourCache[index]; } - (Random_Seed) getRandomSeedForCurrentSystem { if ([UNIVERSE currentSystemID] < 0) { return kNilRandomSeed; } else { OOSystemID s = [UNIVERSE currentSystemID]; NSUInteger index = ([PLAYER galaxyNumber] * OO_SYSTEMS_PER_GALAXY) + s; if (index >= OO_SYSTEM_CACHE_LENGTH) { OOLog(@"system.description.error",@"'%lu' is an invalid system index for the current system. This is an internal error. Please report it.",(unsigned long)index); return kNilRandomSeed; } return RandomSeedFromString([propertyCache[index] oo_stringForKey:@"random_seed"]); } } - (Random_Seed) getRandomSeedForSystem:(OOSystemID)s inGalaxy:(OOGalaxyID)g { if (s < 0) { OOLog(@"system.description.error",@"'%d %d' is an invalid system key. This is an internal error. Please report it.",g,s); return kNilRandomSeed; } NSUInteger index = (g * OO_SYSTEMS_PER_GALAXY) + s; if (index >= OO_SYSTEM_CACHE_LENGTH) { OOLog(@"system.description.error",@"'%d %d' is an invalid system key. This is an internal error. Please report it.",g,s); return kNilRandomSeed; } return RandomSeedFromString([propertyCache[index] oo_stringForKey:@"random_seed"]); } @end @interface OOSystemDescriptionEntry (OOPrivate) - (id) validateProperty:(NSString *)property withValue:(id)value; @end @implementation OOSystemDescriptionEntry - (id) init { self = [super init]; if (self != nil) { for (NSUInteger i=0;i #import "OOLogging.h" #import "OOMaths.h" #import "OOCPUInfo.h" #define DUMP_MIP_MAPS 0 #define DUMP_SCALE 0 /* Internal function declarations. NOTE: the function definitions are grouped together for best code cache coherence rather than the order listed here. */ static BOOL GenerateMipMaps1(void *textureBytes, OOPixMapDimension width, OOPixMapDimension height) NONNULL_FUNC; static BOOL GenerateMipMaps2(void *textureBytes, OOPixMapDimension width, OOPixMapDimension height) NONNULL_FUNC; static BOOL GenerateMipMaps4(void *textureBytes, OOPixMapDimension width, OOPixMapDimension height) NONNULL_FUNC; /* ScaleToHalf_P_xN functions These scale a texture with P planes (components) to half its size in each dimension, handling N pixels at a time. srcWidth must be a multiple of N. Parameters are not validated -- bad parameters will lead to bad data or a crash. Scaling is an unweighted average. 8 bits per channel assumed. It is safe and meaningful for srcBytes == dstBytes. */ static void ScaleToHalf_1_x1(void *srcBytes, void *dstBytes, OOPixMapDimension srcWidth, OOPixMapDimension srcHeight) NONNULL_FUNC; static void ScaleToHalf_2_x1(void *srcBytes, void *dstBytes, OOPixMapDimension srcWidth, OOPixMapDimension srcHeight) NONNULL_FUNC; static void ScaleToHalf_4_x1(void *srcBytes, void *dstBytes, OOPixMapDimension srcWidth, OOPixMapDimension srcHeight) NONNULL_FUNC; #if OOLITE_NATIVE_64_BIT static void ScaleToHalf_1_x8(void *srcBytes, void *dstBytes, OOPixMapDimension srcWidth, OOPixMapDimension srcHeight) NONNULL_FUNC; // static void ScaleToHalf_2_x4(void *srcBytes, void *dstBytes, OOPixMapDimension srcWidth, OOPixMapDimension srcHeight) NONNULL_FUNC; static void ScaleToHalf_4_x2(void *srcBytes, void *dstBytes, OOPixMapDimension srcWidth, OOPixMapDimension srcHeight) NONNULL_FUNC; #else static void ScaleToHalf_1_x4(void *srcBytes, void *dstBytes, OOPixMapDimension srcWidth, OOPixMapDimension srcHeight) NONNULL_FUNC; // static void ScaleToHalf_2_x2(void *srcBytes, void *dstBytes, OOPixMapDimension srcWidth, OOPixMapDimension srcHeight) NONNULL_FUNC; #endif OOINLINE void StretchVertically(OOPixMap srcPx, OOPixMap dstPx) ALWAYS_INLINE_FUNC; OOINLINE void SqueezeVertically(OOPixMap pixMap, OOPixMapDimension dstHeight) ALWAYS_INLINE_FUNC; OOINLINE void StretchHorizontally(OOPixMap srcPx, OOPixMap dstPx) ALWAYS_INLINE_FUNC; OOINLINE void SqueezeHorizontally(OOPixMap pixMap, OOPixMapDimension dstHeight) ALWAYS_INLINE_FUNC; static void StretchVerticallyN_x1(OOPixMap srcPx, OOPixMap dstPx); static void SqueezeVertically1(OOPixMap srcPx, OOPixMapDimension dstHeight); static void SqueezeVertically2(OOPixMap srcPx, OOPixMapDimension dstHeight); static void SqueezeVertically4(OOPixMap srcPx, OOPixMapDimension dstHeight); static void StretchHorizontally1(OOPixMap srcPx, OOPixMap dstPx); static void StretchHorizontally2(OOPixMap srcPx, OOPixMap dstPx); static void StretchHorizontally4(OOPixMap srcPx, OOPixMap dstPx); static void SqueezeHorizontally1(OOPixMap srcPx, OOPixMapDimension dstWidth); static void SqueezeHorizontally2(OOPixMap srcPx, OOPixMapDimension dstWidth); static void SqueezeHorizontally4(OOPixMap srcPx, OOPixMapDimension dstWidth); static BOOL EnsureCorrectDataSize(OOPixMap *pixMap, BOOL leaveSpaceForMipMaps) NONNULL_FUNC; #if !OOLITE_NATIVE_64_BIT static void StretchVerticallyN_x4(OOPixMap srcPx, OOPixMap dstPx); OOINLINE void StretchVertically(OOPixMap srcPx, OOPixMap dstPx) { if (!((srcPx.rowBytes) & 3)) { StretchVerticallyN_x4(srcPx, dstPx); } else { StretchVerticallyN_x1(srcPx, dstPx); } } #else // OOLITE_NATIVE_64_BIT static void StretchVerticallyN_x8(OOPixMap srcPx, OOPixMap dstPx); OOINLINE void StretchVertically(OOPixMap srcPx, OOPixMap dstPx) { if (!((srcPx.rowBytes) & 7)) { StretchVerticallyN_x8(srcPx, dstPx); } else { StretchVerticallyN_x1(srcPx, dstPx); } } #endif OOINLINE void SqueezeVertically(OOPixMap pixMap, OOPixMapDimension dstHeight) { switch (pixMap.format) { case kOOPixMapRGBA: SqueezeVertically4(pixMap, dstHeight); return; case kOOPixMapGrayscale: SqueezeVertically1(pixMap, dstHeight); return; case kOOPixMapGrayscaleAlpha: SqueezeVertically2(pixMap, dstHeight); return; case kOOPixMapInvalidFormat: break; } #ifndef NDEBUG [NSException raise:NSInternalInconsistencyException format:@"Unsupported pixmap format in scaler: %@", OOPixMapFormatName(pixMap.format)]; #else abort(); #endif } OOINLINE void StretchHorizontally(OOPixMap srcPx, OOPixMap dstPx) { NSCParameterAssert(srcPx.format == dstPx.format); switch (srcPx.format) { case kOOPixMapRGBA: StretchHorizontally4(srcPx, dstPx); return; case kOOPixMapGrayscale: StretchHorizontally1(srcPx, dstPx); return; case kOOPixMapGrayscaleAlpha: StretchHorizontally2(srcPx, dstPx); return; case kOOPixMapInvalidFormat: break; } #ifndef NDEBUG [NSException raise:NSInternalInconsistencyException format:@"Unsupported pixmap format in scaler: %@", OOPixMapFormatName(srcPx.format)]; #else abort(); #endif } OOINLINE void SqueezeHorizontally(OOPixMap pixMap, OOPixMapDimension dstHeight) { switch (pixMap.format) { case kOOPixMapRGBA: SqueezeHorizontally4(pixMap, dstHeight); return; case kOOPixMapGrayscale: SqueezeHorizontally1(pixMap, dstHeight); return; case kOOPixMapGrayscaleAlpha: SqueezeHorizontally2(pixMap, dstHeight); return; case kOOPixMapInvalidFormat: break; } #ifndef NDEBUG [NSException raise:NSInternalInconsistencyException format:@"Unsupported pixmap format in scaler: %@", OOPixMapFormatName(pixMap.format)]; #else abort(); #endif } #if DUMP_MIP_MAPS || DUMP_SCALE // NOTE: currently only works on OS X because of OSAtomicAdd32() (used to increment ID counter in thread-safe way). A simple increment would be sufficient if limited to a single thread (in OOTextureLoader). volatile int32_t sPreviousDumpID = 0; int32_t OSAtomicAdd32(int32_t __theAmount, volatile int32_t *__theValue); #endif #if DUMP_MIP_MAPS #define DUMP_CHANNELS -1 // Bitmap of channel counts - -1 for all dumps #define DUMP_MIP_MAP_PREPARE(pl) uint32_t dumpPlanes = pl; \ uint32_t dumpLevel = 0; \ BOOL dumpThis = (dumpPlanes & DUMP_CHANNELS) != 0; \ SInt32 dumpID = dumpThis ? OSAtomicAdd32(1, &sPreviousDumpID) : 0; #define DUMP_MIP_MAP_DUMP(px, w, h) if (dumpThis) DumpMipMap(px, w, h, dumpPlanes, dumpID, dumpLevel++); static void DumpMipMap(void *data, OOPixMapDimension width, OOPixMapDimension height, OOPixMapFormat format, SInt32 ID, uint32_t level); #else #define DUMP_MIP_MAP_PREPARE(pl) do { (void)pl; } while (0) #define DUMP_MIP_MAP_DUMP(px, w, h) do { (void)px; (void)w; (void)h; } while (0) #endif #if DUMP_SCALE #define DUMP_SCALE_PREPARE() SInt32 dumpID = OSAtomicAdd32(1, &sPreviousDumpID), dumpCount = 0; #define DUMP_SCALE_DUMP(PM, stage) do { OOPixMap *pm = &(PM); OODumpPixMap(*pm, [NSString stringWithFormat:@"scaling dump ID %u stage %u-%@ %ux%u", dumpID, dumpCount++, stage, pm->width, pm->height]); } while (0) #else #define DUMP_SCALE_PREPARE() #define DUMP_SCALE_DUMP(PM, stage) do {} while (0) #endif OOPixMap OOScalePixMap(OOPixMap srcPx, OOPixMapDimension dstWidth, OOPixMapDimension dstHeight, BOOL leaveSpaceForMipMaps) { OOPixMap dstPx = {0}, sparePx = {0}; BOOL OK = YES; // Sanity check. if (EXPECT_NOT(!OOIsValidPixMap(srcPx))) { OOLogGenericParameterError(); free(srcPx.pixels); return kOONullPixMap; } DUMP_SCALE_PREPARE(); DUMP_SCALE_DUMP(srcPx, @"initial"); if (srcPx.height < dstHeight) { // Stretch vertically. This requires a separate buffer. size_t dstSize = srcPx.rowBytes * dstHeight; if (leaveSpaceForMipMaps && dstWidth <= srcPx.width) dstSize = dstSize * 4 / 3; dstPx = OOAllocatePixMap(srcPx.width, dstHeight, srcPx.format, 0, dstSize); if (EXPECT_NOT(!OOIsValidPixMap(dstPx))) { OK = NO; goto FAIL; } StretchVertically(srcPx, dstPx); DUMP_SCALE_DUMP(dstPx, @"stretched vertically"); sparePx = srcPx; srcPx = dstPx; } else if (dstHeight < srcPx.height) { // Squeeze vertically. This can be done in-place. SqueezeVertically(srcPx, dstHeight); srcPx.height = dstHeight; DUMP_SCALE_DUMP(srcPx, @"squeezed vertically"); } if (srcPx.width < dstWidth) { // Stretch horizontally. This requires a separate buffer. size_t dstSize = OOPixMapBytesPerPixel(srcPx) * dstWidth * srcPx.height; if (leaveSpaceForMipMaps) dstSize = dstSize * 4 / 3; if (dstSize <= sparePx.bufferSize) { dstPx = OOMakePixMap(sparePx.pixels, dstWidth, srcPx.height, srcPx.format, 0, sparePx.bufferSize); sparePx = kOONullPixMap; } else { dstPx = OOAllocatePixMap(dstWidth, srcPx.height, srcPx.format, 0, dstSize); } if (EXPECT_NOT(!OOIsValidPixMap(dstPx))) { OK = NO; goto FAIL; } StretchHorizontally(srcPx, dstPx); DUMP_SCALE_DUMP(dstPx, @"stretched horizontally"); } else if (dstWidth < srcPx.width) { // Squeeze horizontally. This can be done in-place. SqueezeHorizontally(srcPx, dstWidth); dstPx = srcPx; dstPx.width = dstWidth; dstPx.rowBytes = dstPx.width * OOPixMapBytesPerPixel(dstPx); DUMP_SCALE_DUMP(dstPx, @"squeezed horizontally"); } else { // No horizontal scaling. dstPx = srcPx; } // Avoid a potential double free (if the realloc in EnsureCorrectDataSize() relocates the block). if (srcPx.pixels == dstPx.pixels) srcPx.pixels = NULL; // dstPx is now the result. OK = EnsureCorrectDataSize(&dstPx, leaveSpaceForMipMaps); FAIL: free(srcPx.pixels); if (sparePx.pixels != dstPx.pixels && sparePx.pixels != srcPx.pixels) { free(sparePx.pixels); } if (!OK) { free(dstPx.pixels); dstPx.pixels = NULL; } return OK ? dstPx : kOONullPixMap; } // FIXME: should take an OOPixMap. BOOL OOGenerateMipMaps(void *textureBytes, OOPixMapDimension width, OOPixMapDimension height, OOPixMapFormat format) { if (EXPECT_NOT(width != OORoundUpToPowerOf2_PixMap(width) || height != OORoundUpToPowerOf2_PixMap(height))) { OOLog(kOOLogParameterError, @"Non-power-of-two dimensions (%ux%u) passed to %s() - ignoring, data will be junk.", width, height, __PRETTY_FUNCTION__); return NO; } if (EXPECT_NOT(textureBytes == NULL)) { OOLog(kOOLogParameterError, @"NULL texutre pointer passed to GenerateMipMaps()."); return NO; } switch (format) { case kOOPixMapRGBA: return GenerateMipMaps4(textureBytes, width, height); case kOOPixMapGrayscale: return GenerateMipMaps1(textureBytes, width, height); case kOOPixMapGrayscaleAlpha: return GenerateMipMaps2(textureBytes, width, height); case kOOPixMapInvalidFormat: break; } OOLog(kOOLogParameterError, @"%s(): bad pixmap format (%@) - ignoring, data will be junk.", __PRETTY_FUNCTION__, OOPixMapFormatName(format)); return NO; } static BOOL GenerateMipMaps1(void *textureBytes, OOPixMapDimension width, OOPixMapDimension height) { OOPixMapDimension w = width, h = height; uint8_t *curr, *next; DUMP_MIP_MAP_PREPARE(1); curr = textureBytes; #if OOLITE_NATIVE_64_BIT while (8 < w && 1 < h) { DUMP_MIP_MAP_DUMP(curr, w, h); next = curr + w * h; ScaleToHalf_1_x8(curr, next, w, h); w >>= 1; h >>= 1; curr = next; } #else while (4 < w && 1 < h) { DUMP_MIP_MAP_DUMP(curr, w, h); next = curr + w * h; ScaleToHalf_1_x4(curr, next, w, h); w >>= 1; h >>= 1; curr = next; } #endif while (1 < w && 1 < h) { DUMP_MIP_MAP_DUMP(curr, w, h); next = curr + w * h; ScaleToHalf_1_x1(curr, next, w, h); w >>= 1; h >>= 1; curr = next; } DUMP_MIP_MAP_DUMP(curr, w, h); // TODO: handle residual 1xN/Nx1 mips. For now, we just limit maximum mip level for non-square textures. return YES; } static void ScaleToHalf_1_x1(void *srcBytes, void *dstBytes, OOPixMapDimension srcWidth, OOPixMapDimension srcHeight) { OOPixMapDimension x, y; uint8_t *src0, *src1, *dst; uint_fast8_t px00, px01, px10, px11; uint_fast16_t sum; src0 = srcBytes; src1 = src0 + srcWidth; dst = dstBytes; y = srcHeight >> 1; do { x = srcWidth >> 1; do { // Read four pixels in a square... px00 = *src0++; px01 = *src0++; px10 = *src1++; px11 = *src1++; // ...add them together... sum = px00 + px01 + px10 + px11; // ...shift the sum into place... sum >>= 2; // ...and write output pixel. *dst++ = sum; } while (--x); // Skip a row for each source row src0 = src1; src1 += srcWidth; } while (--y); } #if !OOLITE_NATIVE_64_BIT static void ScaleToHalf_1_x4(void *srcBytes, void *dstBytes, OOPixMapDimension srcWidth, OOPixMapDimension srcHeight) { OOPixMapDimension x, y; uint32_t *src0, *src1, *dst; uint_fast32_t px00, px01, px10, px11; uint_fast32_t sum0, sum1; srcWidth >>= 2; // Four (output) pixels at a time src0 = srcBytes; src1 = src0 + srcWidth; dst = dstBytes; y = srcHeight >> 1; do { x = srcWidth >> 1; do { // Read 8 pixels in a 4x2 rectangle... px00 = *src0++; px01 = *src0++; px10 = *src1++; px11 = *src1++; // ...add them together. sum0 = (px00 & 0x00FF00FF) + (px10 & 0x00FF00FF) + ((px00 & 0xFF00FF00) >> 8) + ((px10 & 0xFF00FF00) >> 8); sum1 = (px01 & 0x00FF00FF) + (px11 & 0x00FF00FF) + ((px01 & 0xFF00FF00) >> 8) + ((px11 & 0xFF00FF00) >> 8); // ...swizzle the sums around... #if OOLITE_BIG_ENDIAN sum0 = ((sum0 << 6) & 0xFF000000) | ((sum0 << 14) & 0x00FF0000); sum1 = ((sum1 >> 10) & 0x0000FF00) | ((sum1 >>2) & 0x000000FF); #elif OOLITE_LITTLE_ENDIAN sum0 = ((sum0 >> 10) & 0x0000FF00) | ((sum0 >>2) & 0x000000FF); sum1 = ((sum1 << 6) & 0xFF000000) | ((sum1 << 14) & 0x00FF0000); #else #error Neither OOLITE_BIG_ENDIAN nor OOLITE_LITTLE_ENDIAN is defined as nonzero! #endif // ...and write output pixel. *dst++ = sum0 | sum1; } while (--x); // Skip a row for each source row src0 = src1; src1 += srcWidth; } while (--y); } #else // OOLITE_NATIVE_64_BIT static void ScaleToHalf_1_x8(void *srcBytes, void *dstBytes, OOPixMapDimension srcWidth, OOPixMapDimension srcHeight) { OOPixMapDimension x, y; uint64_t *src0, *src1; uint64_t *dst; uint_fast64_t px00, px01, px10, px11; uint_fast64_t sum0, sum1; srcWidth >>= 3; // Eight (output) pixels at a time src0 = srcBytes; src1 = src0 + srcWidth; dst = dstBytes; y = srcHeight >> 1; do { x = srcWidth >> 1; do { // Read 16 pixels in an 8x2 rectangle... px00 = *src0++; px01 = *src0++; px10 = *src1++; px11 = *src1++; // ...add them together... sum0 = ((px00 & 0x00FF00FF00FF00FFULL)) + ((px10 & 0x00FF00FF00FF00FFULL)) + ((px00 & 0xFF00FF00FF00FF00ULL) >> 8) + ((px10 & 0xFF00FF00FF00FF00ULL) >> 8); sum1 = ((px01 & 0x00FF00FF00FF00FFULL)) + ((px11 & 0x00FF00FF00FF00FFULL)) + ((px01 & 0xFF00FF00FF00FF00ULL) >> 8) + ((px11 & 0xFF00FF00FF00FF00ULL) >> 8); // ...swizzle the sums around... #if OOLITE_BIG_ENDIAN sum0 = ((sum0 << 06) & 0xFF00000000000000ULL) | ((sum0 << 14) & 0x00FF000000000000ULL) | ((sum0 << 22) & 0x0000FF0000000000ULL) | ((sum0 << 30) & 0x000000FF00000000ULL); sum1 = ((sum1 >> 26) & 0x00000000FF000000ULL) | ((sum1 >> 18) & 0x0000000000FF0000ULL) | ((sum1 >> 10) & 0x000000000000FF00ULL) | ((sum1 >> 02) & 0x00000000000000FFULL); #elif OOLITE_LITTLE_ENDIAN sum0 = ((sum0 >> 26) & 0x00000000FF000000ULL) | ((sum0 >> 18) & 0x0000000000FF0000ULL) | ((sum0 >> 10) & 0x000000000000FF00ULL) | ((sum0 >> 02) & 0x00000000000000FFULL); sum1 = ((sum1 << 06) & 0xFF00000000000000ULL) | ((sum1 << 14) & 0x00FF000000000000ULL) | ((sum1 << 22) & 0x0000FF0000000000ULL) | ((sum1 << 30) & 0x000000FF00000000ULL); #else #error Neither OOLITE_BIG_ENDIAN nor OOLITE_LITTLE_ENDIAN is defined as nonzero! #endif // ...and write output pixel. *dst++ = sum0 | sum1; } while (--x); // Skip a row for each source row src0 = src1; src1 += srcWidth; } while (--y); } #endif static BOOL GenerateMipMaps2(void *textureBytes, OOPixMapDimension width, OOPixMapDimension height) { OOPixMapDimension w = width, h = height; uint16_t *curr, *next; DUMP_MIP_MAP_PREPARE(2); curr = textureBytes; // TODO: multiple pixel two-plane scalers. #if 0 #if OOLITE_NATIVE_64_BIT while (4 < w && 1 < h) { DUMP_MIP_MAP_DUMP(curr, w, h); next = curr + w * h; ScaleToHalf_2_x4(curr, next, w, h); w >>= 1; h >>= 1; curr = next; } #else while (2 < w && 1 < h) { DUMP_MIP_MAP_DUMP(curr, w, h); next = curr + w * h; ScaleToHalf_2_x2(curr, next, w, h); w >>= 1; h >>= 1; curr = next; } #endif #endif while (1 < w && 1 < h) { DUMP_MIP_MAP_DUMP(curr, w, h); next = curr + w * h; ScaleToHalf_2_x1(curr, next, w, h); w >>= 1; h >>= 1; curr = next; } DUMP_MIP_MAP_DUMP(curr, w, h); // TODO: handle residual 1xN/Nx1 mips. For now, we just limit maximum mip level for non-square textures. return YES; } static void ScaleToHalf_2_x1(void *srcBytes, void *dstBytes, OOPixMapDimension srcWidth, OOPixMapDimension srcHeight) { OOPixMapDimension x, y; uint16_t *src0, *src1, *dst; uint_fast16_t px00, px01, px10, px11; uint_fast32_t sumHi, sumLo; src0 = srcBytes; src1 = src0 + srcWidth; dst = dstBytes; y = srcHeight >> 1; do { x = srcWidth >> 1; do { // Read four pixels in a square... px00 = *src0++; px01 = *src0++; px10 = *src1++; px11 = *src1++; // ...add them together... sumHi = (px00 & 0xFF00) + (px01 & 0xFF00) + (px10 & 0xFF00) + (px11 & 0xFF00); sumLo = (px00 & 0x00FF) + (px01 & 0x00FF) + (px10 & 0x00FF) + (px11 & 0x00FF); // ...merge and shift the sum into place... sumLo = ((sumHi & 0x3FC00) | sumLo) >> 2; // ...and write output pixel. *dst++ = sumLo; } while (--x); // Skip a row for each source row src0 = src1; src1 += srcWidth; } while (--y); } static BOOL GenerateMipMaps4(void *textureBytes, OOPixMapDimension width, OOPixMapDimension height) { OOPixMapDimension w = width, h = height; uint32_t *curr, *next; DUMP_MIP_MAP_PREPARE(4); curr = textureBytes; #if OOLITE_NATIVE_64_BIT while (2 < w && 1 < h) { DUMP_MIP_MAP_DUMP(curr, w, h); next = curr + w * h; ScaleToHalf_4_x2(curr, next, w, h); w >>= 1; h >>= 1; curr = next; } if (EXPECT(1 < w && 1 < h)) { DUMP_MIP_MAP_DUMP(curr, w, h); next = curr + w * h; ScaleToHalf_4_x1(curr, next, w, h); w >>= 1; h >>= 1; } #else while (1 < w && 1 < h) { DUMP_MIP_MAP_DUMP(curr, w, h); next = curr + w * h; ScaleToHalf_4_x1(curr, next, w, h); w >>= 1; h >>= 1; curr = next; } #endif DUMP_MIP_MAP_DUMP(curr, w, h); // TODO: handle residual 1xN/Nx1 mips. For now, we just limit maximum mip level for non-square textures. return YES; } static void ScaleToHalf_4_x1(void *srcBytes, void *dstBytes, OOPixMapDimension srcWidth, OOPixMapDimension srcHeight) { OOPixMapDimension x, y; uint32_t *src0, *src1, *dst; uint_fast32_t px00, px01, px10, px11; /* We treat channel layout as ABGR -- actual layout doesn't matter since each channel is handled the same. We use two accumulators, with alternating channels, so overflow doesn't cross channel boundaries, while having less overhead than one accumulator per channel. */ uint_fast32_t ag, br; src0 = srcBytes; src1 = src0 + srcWidth; dst = dstBytes; y = srcHeight >> 1; do { x = srcWidth >> 1; do { // Read four pixels in a square... px00 = *src0++; px01 = *src0++; px10 = *src1++; px11 = *src1++; // ...and add them together, channel by channel. ag = (px00 & 0xFF00FF00) >> 8; br = (px00 & 0x00FF00FF); ag += (px01 & 0xFF00FF00) >> 8; br += (px01 & 0x00FF00FF); ag += (px10 & 0xFF00FF00) >> 8; br += (px10 & 0x00FF00FF); ag += (px11 & 0xFF00FF00) >> 8; br += (px11 & 0x00FF00FF); // Shift the sums into place... ag <<= 6; br >>= 2; // ...and write output pixel. *dst++ = (ag & 0xFF00FF00) | (br & 0x00FF00FF); } while (--x); // Skip a row for each source row src0 = src1; src1 += srcWidth; } while (--y); } #if OOLITE_NATIVE_64_BIT static void ScaleToHalf_4_x2(void *srcBytes, void *dstBytes, OOPixMapDimension srcWidth, OOPixMapDimension srcHeight) { OOPixMapDimension x, y; uint_fast64_t *src0, *src1, *dst; uint_fast64_t px00, px01, px10, px11; /* We treat channel layout as ABGR -- actual layout doesn't matter since each channel is handled the same. We use two accumulators, with alternating channels, so overflow doesn't cross channel boundaries, while having less overhead than one accumulator per channel. */ uint_fast64_t ag0, ag1, br0, br1; srcWidth >>= 1; // Two bytes at a time src0 = srcBytes; src1 = src0 + srcWidth; dst = dstBytes; y = srcHeight >> 1; do { x = srcWidth >> 1; do { // Read eight pixels (4x2)... px00 = *src0++; px01 = *src0++; px10 = *src1++; px11 = *src1++; // ...and add them together, channel by channel. ag0 = (px00 & 0xFF00FF00FF00FF00ULL) >> 8; br0 = (px00 & 0x00FF00FF00FF00FFULL); ag0 += (px10 & 0xFF00FF00FF00FF00ULL) >> 8; br0 += (px10 & 0x00FF00FF00FF00FFULL); ag1 = (px01 & 0xFF00FF00FF00FF00ULL) >> 8; br1 = (px01 & 0x00FF00FF00FF00FFULL); ag1 += (px11 & 0xFF00FF00FF00FF00ULL) >> 8; br1 += (px11 & 0x00FF00FF00FF00FFULL); #if OOLITE_BIG_ENDIAN // Shift and add some more... ag0 = ag0 + (ag0 << 32); br0 = br0 + (br0 << 32); ag1 = ag1 + (ag1 >> 32); br1 = br1 + (br1 >> 32); // ...merge and shift some more... ag0 = ((ag0 & 0x03FC03FC00000000ULL) | (ag1 & 0x0000000003FC03FCULL)) << 6; br0 = ((br0 & 0x03FC03FC00000000ULL) | (br1 & 0x0000000003FC03FCULL)) >> 2; #elif OOLITE_LITTLE_ENDIAN // Shift and add some more... ag0 = ag0 + (ag0 >> 32); br0 = br0 + (br0 >> 32); ag1 = ag1 + (ag1 << 32); br1 = br1 + (br1 << 32); // ...merge and shift some more... ag0 = ((ag0 & 0x0000000003FC03FCULL) | (ag1 & 0x03FC03FC00000000ULL)) << 6; br0 = ((br0 & 0x0000000003FC03FCULL) | (br1 & 0x03FC03FC00000000ULL)) >> 2; #else #error Unknown architecture. #endif // ...and write output pixel. *dst++ = ag0 | br0; } while (--x); // Skip a row for each source row src0 = src1; src1 += srcWidth; } while (--y); } #endif #if DUMP_MIP_MAPS static void DumpMipMap(void *data, OOPixMapDimension width, OOPixMapDimension height, OOPixMapFormat format, SInt32 ID, uint32_t level) { OOPixMap pixMap = OOMakePixMap(data, width, height, format, 0, 0); OODumpPixMap(pixMap, [NSString stringWithFormat:@"mipmap dump ID %u lv%u %@ %ux%u", ID, level, OOPixMapFormatName(format), width, height]); } #endif static void StretchVerticallyN_x1(OOPixMap srcPx, OOPixMap dstPx) { uint8_t *src, *src0, *src1, *prev, *dst; uint8_t px0, px1; uint_fast32_t x, y, xCount; size_t srcRowBytes; uint_fast16_t weight0, weight1; uint_fast32_t fractY; // Y coordinate, fixed-point (24.8) src = srcPx.pixels; srcRowBytes = srcPx.rowBytes; dst = dstPx.pixels; // Assumes dstPx.width == dstPx.rowBytes. src0 = prev = src; xCount = srcPx.width * OOPixMapBytesPerPixel(srcPx); for (y = 1; y != dstPx.height; ++y) { fractY = ((srcPx.height * y) << 8) / dstPx.height; src0 = prev; prev = src1 = src + srcRowBytes * (fractY >> 8); weight1 = fractY & 0xFF; weight0 = 0x100 - weight1; x = xCount; while (x--) { px0 = *src0++; px1 = *src1++; *dst++ = (px0 * weight0 + px1 * weight1) >> 8; } } // Copy last row (without referring to the last-plus-oneth row) x = xCount; while (x--) { *dst++ = *src0++; } } #if !OOLITE_NATIVE_64_BIT static void StretchVerticallyN_x4(OOPixMap srcPx, OOPixMap dstPx) { uint8_t *src; uint32_t *src0, *src1, *prev, *dst; uint32_t px0, px1, ag, br; uint_fast32_t x, y, xCount; size_t srcRowBytes; uint_fast16_t weight0, weight1; uint_fast32_t fractY; // Y coordinate, fixed-point (24.8) src = srcPx.pixels; srcRowBytes = srcPx.rowBytes; dst = dstPx.pixels; // Assumes no row padding. src0 = prev = (uint32_t *)src; xCount = (srcPx.width * OOPixMapBytesPerPixel(srcPx)) >> 2; for (y = 1; y != dstPx.height; ++y) { fractY = ((srcPx.height * y) << 8) / dstPx.height; src0 = prev; prev = src1 = (uint32_t *)(src + srcRowBytes * (fractY >> 8)); weight1 = fractY & 0xFF; weight0 = 0x100 - weight1; x = xCount; while (x--) { px0 = *src0++; px1 = *src1++; ag = ((px0 & 0xFF00FF00) >> 8) * weight0 + ((px1 & 0xFF00FF00) >> 8) * weight1; br = (px0 & 0x00FF00FF) * weight0 + (px1 & 0x00FF00FF) * weight1; *dst++ = (ag & 0xFF00FF00) | ((br >> 8) & 0x00FF00FF); } } // Copy last row (without referring to the last-plus-oneth row) x = xCount; while (x--) { *dst++ = *src0++; } } #else // OOLITE_NATIVE_64_BIT static void StretchVerticallyN_x8(OOPixMap srcPx, OOPixMap dstPx) { uint8_t *src; uint64_t *src0, *src1, *prev, *dst; uint64_t px0, px1, agag, brbr; uint_fast32_t x, y, xCount; size_t srcRowBytes; uint_fast16_t weight0, weight1; uint_fast32_t fractY; // Y coordinate, fixed-point (24.8) src = srcPx.pixels; srcRowBytes = srcPx.rowBytes; dst = dstPx.pixels; // Assumes dstPx.width == dstPx.rowBytes. src0 = prev = (uint64_t *)src; xCount = (srcPx.width * OOPixMapBytesPerPixel(srcPx)) >> 3; for (y = 1; y != dstPx.height; ++y) { fractY = ((srcPx.height * y) << 8) / dstPx.height; src0 = prev; prev = src1 = (uint64_t *)(src + srcRowBytes * (fractY >> 8)); weight1 = fractY & 0xFF; weight0 = 0x100 - weight1; x = xCount; while (x--) { px0 = *src0++; px1 = *src1++; agag = ((px0 & 0xFF00FF00FF00FF00ULL) >> 8) * weight0 + ((px1 & 0xFF00FF00FF00FF00ULL) >> 8) * weight1; brbr = (px0 & 0x00FF00FF00FF00FFULL) * weight0 + (px1 & 0x00FF00FF00FF00FFULL) * weight1; *dst++ = (agag & 0xFF00FF00FF00FF00ULL) | ((brbr >> 8) & 0x00FF00FF00FF00FFULL); } } // Copy last row (without referring to the last-plus-oneth row) x = xCount; while (x--) { *dst++ = *src0++; } } #endif static void StretchHorizontally1(OOPixMap srcPx, OOPixMap dstPx) { uint8_t *src, *srcStart, *dst; uint8_t px0, px1; uint_fast32_t x, y, xCount; size_t srcRowBytes; uint_fast16_t weight0, weight1; uint_fast32_t fractX, deltaX; // X coordinate, fixed-point (20.12), allowing widths up to 1 mebipixel NSCParameterAssert(OOIsValidPixMap(srcPx) && OOPixMapBytesPerPixel(srcPx) == 1 && OOIsValidPixMap(dstPx) && OOPixMapBytesPerPixel(dstPx) == 1); srcStart = srcPx.pixels; srcRowBytes = srcPx.rowBytes; xCount = dstPx.width; dst = dstPx.pixels; // Assumes no row padding deltaX = (srcPx.width << 12) / dstPx.width; px1 = *srcStart; for (y = 0; y < dstPx.height - 1; ++y) { fractX = 0; for (x = 0; x!= xCount; ++x) { fractX += deltaX; weight1 = (fractX >> 4) & 0xFF; weight0 = 0x100 - weight1; px0 = px1; src = srcStart + (fractX >> 12); px1 = *src; *dst++ = (px0 * weight0 + px1 * weight1) >> 8; } srcStart = (uint8_t *)((char *)srcStart + srcRowBytes); px1 = *srcStart; } // Copy last row without reading off end of buffer fractX = 0; for (x = 0; x!= xCount; ++x) { fractX += deltaX; weight1 = (fractX >> 4) & 0xFF; weight0 = 0x100 - weight1; px0 = px1; src = srcStart + (fractX >> 12); px1 = *src; *dst++ = (px0 * weight0 + px1 * weight1) >> 8; } } static void StretchHorizontally2(OOPixMap srcPx, OOPixMap dstPx) { uint16_t *src, *srcStart, *dst; uint16_t px0, px1; uint_fast32_t hi, lo; uint_fast32_t x, y, xCount; size_t srcRowBytes; uint_fast16_t weight0, weight1; uint_fast32_t fractX, deltaX; // X coordinate, fixed-point (20.12), allowing widths up to 1 mebipixel NSCParameterAssert(OOIsValidPixMap(srcPx) && OOPixMapBytesPerPixel(srcPx) == 2 && OOIsValidPixMap(dstPx) && OOPixMapBytesPerPixel(dstPx) == 2); srcStart = srcPx.pixels; srcRowBytes = srcPx.rowBytes; xCount = dstPx.width; dst = dstPx.pixels; // Assumes no row padding deltaX = (srcPx.width << 12) / dstPx.width; px1 = *srcStart; for (y = 0; y < dstPx.height - 1; ++y) { fractX = 0; for (x = 0; x!= xCount; ++x) { fractX += deltaX; weight1 = (fractX >> 4) & 0xFF; weight0 = 0x100 - weight1; px0 = px1; src = srcStart + (fractX >> 12); px1 = *src; hi = (px0 & 0xFF00) * weight0 + (px1 & 0xFF00) * weight1; lo = (px0 & 0x00FF) * weight0 + (px1 & 0x00FF) * weight1; *dst++ = ((hi & 0xFF0000) | (lo & 0x00FF00)) >> 8; } srcStart = (uint16_t *)((char *)srcStart + srcRowBytes); px1 = *srcStart; } // Copy last row without reading off end of buffer fractX = 0; for (x = 0; x!= xCount; ++x) { fractX += deltaX; weight1 = (fractX >> 4) & 0xFF; weight0 = 0x100 - weight1; px0 = px1; src = srcStart + (fractX >> 12); px1 = *src; hi = (px0 & 0xFF00) * weight0 + (px1 & 0xFF00) * weight1; lo = (px0 & 0x00FF) * weight0 + (px1 & 0x00FF) * weight1; *dst++ = ((hi & 0xFF0000) | (lo & 0x00FF00)) >> 8; } } static void StretchHorizontally4(OOPixMap srcPx, OOPixMap dstPx) { uint32_t *src, *srcStart, *dst; uint32_t px0, px1; uint32_t ag, br; uint_fast32_t x, y, xCount; size_t srcRowBytes; uint_fast16_t weight0, weight1; uint_fast32_t fractX, deltaX; // X coordinate, fixed-point (20.12), allowing widths up to 1 mebipixel NSCParameterAssert(OOIsValidPixMap(srcPx) && OOPixMapBytesPerPixel(srcPx) == 4 && OOIsValidPixMap(dstPx) && OOPixMapBytesPerPixel(dstPx) == 4); srcStart = srcPx.pixels; srcRowBytes = srcPx.rowBytes; xCount = dstPx.width; dst = dstPx.pixels; // Assumes no row padding deltaX = (srcPx.width << 12) / dstPx.width; px1 = *srcStart; for (y = 0; y < dstPx.height - 1; ++y) { fractX = 0; for (x = 0; x!= xCount; ++x) { fractX += deltaX; weight1 = (fractX >> 4) & 0xFF; weight0 = 0x100 - weight1; px0 = px1; src = srcStart + (fractX >> 12); px1 = *src; ag = ((px0 & 0xFF00FF00) >> 8) * weight0 + ((px1 & 0xFF00FF00) >> 8) * weight1; br = (px0 & 0x00FF00FF) * weight0 + (px1 & 0x00FF00FF) * weight1; *dst++ = (ag & 0xFF00FF00) | ((br & 0xFF00FF00) >> 8); } srcStart = (uint32_t *)((char *)srcStart + srcRowBytes); px1 = *srcStart; } // Copy last row without reading off end of buffer fractX = 0; for (x = 0; x!= xCount; ++x) { fractX += deltaX; weight1 = (fractX >> 4) & 0xFF; weight0 = 0x100 - weight1; px0 = px1; src = srcStart + (fractX >> 12); if (EXPECT(x < xCount - 1)) px1 = *src; ag = ((px0 & 0xFF00FF00) >> 8) * weight0 + ((px1 & 0xFF00FF00) >> 8) * weight1; br = (px0 & 0x00FF00FF) * weight0 + (px1 & 0x00FF00FF) * weight1; *dst++ = (ag & 0xFF00FF00) | ((br & 0xFF00FF00) >> 8); } } static void SqueezeHorizontally1(OOPixMap srcPx, OOPixMapDimension dstWidth) { uint8_t *src, *srcStart, *dst; uint8_t borderPx; uint_fast32_t x, y, xCount, endX; size_t srcRowBytes; uint_fast32_t endFractX, deltaX; uint_fast32_t accum, weight; uint_fast8_t borderWeight; NSCParameterAssert(OOIsValidPixMap(srcPx) && OOPixMapBytesPerPixel(srcPx) == 1); srcStart = srcPx.pixels; dst = srcStart; // Output is placed in same buffer, without line padding. srcRowBytes = srcPx.rowBytes; deltaX = (srcPx.width << 12) / dstWidth; for (y = 0; y != srcPx.height; ++y) { borderPx = *srcStart; endFractX = 0; borderWeight = 0; src = srcStart; x = 0; xCount = dstWidth; while (xCount--) { endFractX += deltaX; endX = endFractX >> 12; borderWeight = 0xFF - borderWeight; accum = borderPx * borderWeight; weight = borderWeight; borderWeight = (endFractX >> 4) & 0xFF; weight += borderWeight; for (;;) { ++x; if (EXPECT(x == endX)) { if (EXPECT(xCount)) borderPx = *++src; accum += borderPx * borderWeight; break; } else { accum += *++src * 0xFF; weight += 0xFF; } } *dst++ = accum / weight; } srcStart = (uint8_t *)((char *)srcStart + srcRowBytes); } } static void SqueezeVertically1(OOPixMap srcPx, OOPixMapDimension dstHeight) { uint8_t *src, *srcStart, *dst; uint_fast32_t x, y, xCount, startY, endY, lastRow; size_t srcRowBytes; uint_fast32_t endFractY, deltaY; uint_fast32_t accum, weight; uint_fast8_t startWeight, endWeight; NSCParameterAssert(OOIsValidPixMap(srcPx) && OOPixMapBytesPerPixel(srcPx) == 1); dst = srcPx.pixels; // Output is placed in same buffer, without line padding. srcRowBytes = srcPx.rowBytes; xCount = srcPx.width; deltaY = (srcPx.height << 12) / dstHeight; endFractY = 0; endWeight = 0; endY = 0; lastRow = srcPx.height - 1; while (endY < lastRow) { endFractY += deltaY; startY = endY; endY = endFractY >> 12; startWeight = 0xFF - endWeight; endWeight = (endFractY >> 4) & 0xFF; srcStart = (uint8_t *)((char *)srcPx.pixels + srcRowBytes * startY); for (x = 0; x != xCount; ++x) { src = srcStart++; accum = startWeight * *src; weight = startWeight + endWeight; y = startY; for (;;) { ++y; src = (uint8_t *)((char *)src + srcRowBytes); if (EXPECT_NOT(y == endY)) { if (EXPECT(endY < lastRow)) accum += *src * endWeight; break; } else { accum += *src * 0xFF; weight += 0xFF; } } *dst++ = accum / weight; } } } /* Macros to manage 2-channel accumulators in 2-channel squeeze scalers. accumHi is the sum of weighted high-channel pixels, shifted left 8 bits. accumLo is the sum of weighted low-channel pixels. weight is the sum of all pixel weights. */ #define ACCUM2(PX, WT) do { \ uint16_t px = PX; \ uint_fast32_t wt = WT; \ accumHi += (px & 0xFF00) * wt; \ accumLo += (px & 0x00FF) * wt; \ weight += wt; \ } while (0) #define CLEAR_ACCUM2() do { \ accumHi = 0; \ accumLo = 0; \ weight = 0; \ } while (0) #define ACCUM2TOPX() ( \ ((accumHi / weight) & 0xFF00) | \ ((accumLo / weight) & 0x00FF) \ ) static void SqueezeHorizontally2(OOPixMap srcPx, OOPixMapDimension dstWidth) { uint16_t *src, *srcStart, *dst; uint16_t borderPx; uint_fast32_t x, y, xCount, endX; size_t srcRowBytes; uint_fast32_t endFractX, deltaX; uint_fast32_t accumHi, accumLo, weight; uint_fast8_t borderWeight; NSCParameterAssert(OOIsValidPixMap(srcPx) && OOPixMapBytesPerPixel(srcPx) == 2); srcStart = srcPx.pixels; dst = srcStart; // Output is placed in same buffer, without line padding. srcRowBytes = srcPx.rowBytes; deltaX = (srcPx.width << 12) / dstWidth; for (y = 0; y != srcPx.height; ++y) { borderPx = *srcStart; endFractX = 0; borderWeight = 0; src = srcStart; x = 0; xCount = dstWidth; while (xCount--) { endFractX += deltaX; endX = endFractX >> 12; CLEAR_ACCUM2(); borderWeight = 0xFF - borderWeight; ACCUM2(borderPx, borderWeight); borderWeight = (endFractX >> 4) & 0xFF; for (;;) { ++x; if (EXPECT(x == endX)) { if (EXPECT(xCount)) borderPx = *++src; ACCUM2(borderPx, borderWeight); break; } else { ACCUM2(*++src, 0xFF); } } *dst++ = ACCUM2TOPX(); } srcStart = (uint16_t *)((char *)srcStart + srcRowBytes); } } static void SqueezeVertically2(OOPixMap srcPx, OOPixMapDimension dstHeight) { uint16_t *src, *srcStart, *dst; uint_fast32_t x, y, xCount, startY, endY, lastRow; size_t srcRowBytes; uint_fast32_t endFractY, deltaY; uint_fast32_t accumHi, accumLo, weight; uint_fast8_t startWeight, endWeight; NSCParameterAssert(OOIsValidPixMap(srcPx) && OOPixMapBytesPerPixel(srcPx) == 2); dst = srcPx.pixels; // Output is placed in same buffer, without line padding. srcRowBytes = srcPx.rowBytes; xCount = srcPx.width; deltaY = (srcPx.height << 12) / dstHeight; endFractY = 0; endWeight = 0; endY = 0; lastRow = srcPx.height - 1; while (endY < lastRow) { endFractY += deltaY; startY = endY; endY = endFractY >> 12; startWeight = 0xFF - endWeight; endWeight = (endFractY >> 4) & 0xFF; srcStart = (uint16_t *)((char *)srcPx.pixels + srcRowBytes * startY); for (x = 0; x != xCount; ++x) { src = srcStart++; CLEAR_ACCUM2(); ACCUM2(*src, startWeight); y = startY; for (;;) { ++y; src = (uint16_t *)((char *)src + srcRowBytes); if (EXPECT_NOT(y == endY)) { if (EXPECT(endY <= lastRow)) ACCUM2(*src, endWeight); break; } else { ACCUM2(*src, 0xFF); } } *dst++ = ACCUM2TOPX(); } } } /* Macros to manage 4-channel accumulators in 4-channel squeeze scalers. The approach is similar to the ACCUM2 family above, except that the wt multiplication works on two channels at a time before splitting into four accumulators, all of which are shifted to the low end of the value. */ #define ACCUM4(PX, WT) do { \ uint32_t px = PX; \ uint_fast32_t wt = WT; \ ag = ((px & 0xFF00FF00) >> 8) * wt; \ br = (px & 0x00FF00FF) * wt; \ accum1 += ag >> 16; \ accum2 += br >> 16; \ accum3 += ag & 0xFFFF; \ accum4 += br & 0xFFFF; \ weight += wt; \ } while (0) #define CLEAR_ACCUM4() do { \ accum1 = 0; \ accum2 = 0; \ accum3 = 0; \ accum4 = 0; \ weight = 0; \ } while (0) /* These integer divisions cause a stall -- this is the biggest bottleneck in this file. Unrolling the loop might help on PPC. Linear interpolation instead of box filtering would help, with a quality hit. Given that scaling doesn't happen very often, I think I'll leave it this way. -- Ahruman */ #define ACCUM4TOPX() ( \ (((accum1 / weight) & 0xFF) << 24) | \ (((accum3 / weight) & 0xFF) << 8) | \ (((accum2 / weight) & 0xFF) << 16) | \ ((accum4 / weight) & 0xFF) \ ) static void SqueezeHorizontally4(OOPixMap srcPx, OOPixMapDimension dstWidth) { uint32_t *src, *srcStart, *dst; uint32_t borderPx, ag, br; uint_fast32_t x, y, xCount, endX; size_t srcRowBytes; uint_fast32_t endFractX, deltaX; uint_fast32_t accum1, accum2, accum3, accum4, weight; uint_fast8_t borderWeight; NSCParameterAssert(OOIsValidPixMap(srcPx) && OOPixMapBytesPerPixel(srcPx) == 4); srcStart = srcPx.pixels; dst = srcStart; // Output is placed in same buffer, without line padding. srcRowBytes = srcPx.rowBytes; deltaX = (srcPx.width << 12) / dstWidth; for (y = 0; y != srcPx.height; ++y) { borderPx = *srcStart; endFractX = 0; borderWeight = 0; src = srcStart; x = 0; xCount = dstWidth; while (xCount--) { endFractX += deltaX; endX = endFractX >> 12; CLEAR_ACCUM4(); borderWeight = 0xFF - borderWeight; ACCUM4(borderPx, borderWeight); borderWeight = (endFractX >> 4) & 0xFF; for (;;) { ++x; if (EXPECT(x == endX)) { if (EXPECT(xCount)) borderPx = *++src; ACCUM4(borderPx, borderWeight); break; } else { ACCUM4(*++src, 0xFF); } } *dst++ = ACCUM4TOPX(); } srcStart = (uint32_t *)((char *)srcStart + srcRowBytes); } } static void SqueezeVertically4(OOPixMap srcPx, OOPixMapDimension dstHeight) { uint32_t *src, *srcStart, *dst; uint_fast32_t x, y, xCount, startY, endY, lastRow; size_t srcRowBytes; uint32_t ag, br; uint_fast32_t endFractY, deltaY; uint_fast32_t accum1, accum2, accum3, accum4, weight; uint_fast8_t startWeight, endWeight; NSCParameterAssert(OOIsValidPixMap(srcPx) && OOPixMapBytesPerPixel(srcPx) == 4); dst = srcPx.pixels; // Output is placed in same buffer, without line padding. srcRowBytes = srcPx.rowBytes; xCount = srcPx.width; deltaY = (srcPx.height << 12) / dstHeight; endFractY = 0; endWeight = 0; endY = 0; lastRow = srcPx.height - 1; while (endY < lastRow) { endFractY += deltaY; startY = endY; endY = endFractY >> 12; startWeight = 0xFF - endWeight; endWeight = (endFractY >> 4) & 0xFF; srcStart = (uint32_t *)((char *)srcPx.pixels + srcRowBytes * startY); for (x = 0; x != xCount; ++x) { src = srcStart++; CLEAR_ACCUM4(); ACCUM4(*src, startWeight); y = startY; for (;;) { ++y; src = (uint32_t *)((char *)src + srcRowBytes); if (EXPECT_NOT(y == endY)) { if (EXPECT(endY <= lastRow)) ACCUM4(*src, endWeight); break; } else { ACCUM4(*src, 0xFF); } } *dst++ = ACCUM4TOPX(); } } } static BOOL EnsureCorrectDataSize(OOPixMap *pixMap, BOOL leaveSpaceForMipMaps) { size_t correctSize; void *bytes = NULL; correctSize = pixMap->rowBytes * pixMap->height; // correctSize > 0 check is redundant, but static analyzer (checker-262) doesn't know that. -- Ahruman 2012-03-17 NSCParameterAssert(OOIsValidPixMap(*pixMap) && correctSize > 0); /* Ensure that the block is not too small. This needs to be done before adding the mip-map space, as the texture may have been shrunk in place without being grown for mip-maps. */ if (EXPECT_NOT(pixMap->bufferSize < correctSize)) { OOLogGenericParameterError(); return NO; } if (leaveSpaceForMipMaps) correctSize = correctSize * 4 / 3; if (correctSize != pixMap->bufferSize) { bytes = realloc(pixMap->pixels, correctSize); if (EXPECT_NOT(bytes == NULL)) free(pixMap->pixels); pixMap->pixels = bytes; pixMap->bufferSize = correctSize; } return YES; } oolite-1.82/src/Core/OOTextureSprite.h000066400000000000000000000025171256642440500176710ustar00rootroot00000000000000/* OOTextureSprite.h Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #import "OOOpenGL.h" @class OOTexture; #define OPEN_GL_SPRITE_MIN_WIDTH 64.0 #define OPEN_GL_SPRITE_MIN_HEIGHT 64.0 @interface OOTextureSprite: NSObject { @private OOTexture *texture; NSSize size; } - (id) initWithTexture:(OOTexture *)texture; - (id) initWithTexture:(OOTexture *)texture size:(NSSize)spriteSize; - (NSSize) size; - (void) blitToX:(float)x Y:(float)y Z:(float)z alpha:(float)a; - (void) blitCentredToX:(float)x Y:(float)y Z:(float)z alpha:(float)a; - (void) blitBackgroundCentredToX:(float)x Y:(float)y Z:(float)z alpha:(float)a; @end oolite-1.82/src/Core/OOTextureSprite.m000066400000000000000000000051511256642440500176730ustar00rootroot00000000000000/* OOTextureSprite.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOTextureSprite.h" #import "OOTexture.h" #import "OOMaths.h" #import "OOMacroOpenGL.h" @implementation OOTextureSprite - (id)initWithTexture:(OOTexture *)inTexture { return [self initWithTexture:inTexture size:[inTexture originalDimensions]]; } - (id)initWithTexture:(OOTexture *)inTexture size:(NSSize)spriteSize { if (inTexture == nil) { [self release]; return nil; } self = [super init]; if (self != nil) { texture = [inTexture retain]; size = spriteSize; } return self; } - (void)dealloc { [texture release]; [super dealloc]; } - (NSSize)size { return size; } - (void) blitToX:(float)x Y:(float)y Z:(float)z alpha:(float)a { OO_ENTER_OPENGL(); OOSetOpenGLState(OPENGL_STATE_OVERLAY); a = OOClamp_0_1_f(a); OOGL(glEnable(GL_TEXTURE_2D)); OOGL(glColor4f(1.0, 1.0, 1.0, a)); // Note that the textured Quad is drawn ACW from the top left. [texture apply]; OOGLBEGIN(GL_QUADS); glTexCoord2f(0.0, 0.0); glVertex3f(x, y+size.height, z); glTexCoord2f(0.0, 1.0); glVertex3f(x, y, z); glTexCoord2f(1.0, 1.0); glVertex3f(x+size.width, y, z); glTexCoord2f(1.0, 0.0); glVertex3f(x+size.width, y+size.height, z); OOGLEND(); OOGL(glDisable(GL_TEXTURE_2D)); OOVerifyOpenGLState(); } - (void) blitCentredToX:(float)x Y:(float)y Z:(float)z alpha:(float)a { float xs = x - size.width / 2.0; float ys = y - size.height / 2.0; [self blitToX:xs Y:ys Z:z alpha:a]; } - (void) blitBackgroundCentredToX:(float)x Y:(float)y Z:(float)z alpha:(float)a { // Without distance, coriolis stations would be rendered behind the background image. // Set an arbitrary value for distance, might not be sufficient for really huge ships. float distance = 512.0f; size.width *= distance; size.height *= distance; [self blitCentredToX:x Y:y Z:z * distance alpha:a]; size.width /= distance; size.height /= distance; } @end oolite-1.82/src/Core/OOTriangle.h000066400000000000000000000042361256642440500166070ustar00rootroot00000000000000/* OOTriangle.h Mathematical framework for Oolite. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef INCLUDED_OOMATHS_h #error Do not include OOTriangle.h directly; include OOMaths.h. #else typedef struct { Vector v[3]; } Triangle; /* Calculate normal for triangle. */ OOINLINE Vector calculateNormalForTriangle(Triangle *ioTriangle) NONNULL_FUNC; /* Generate a triangle from three vertices. */ OOINLINE Triangle make_triangle(Vector v0, Vector v1, Vector v2) CONST_FUNC; /* resolve vector in arbitrary ijk vectors */ OOINLINE Vector resolveVectorInIJK(Vector v0, Triangle ijk); /* Test whether triangle's area is 0. */ OOINLINE bool OOTriangleIsDegenerate(Triangle tri) CONST_FUNC; /*** Only inline definitions beyond this point ***/ OOINLINE Triangle make_triangle(Vector v0, Vector v1, Vector v2) { return (Triangle){{ v0, v1, v2 }}; } OOINLINE Vector calculateNormalForTriangle(Triangle *tri) { Vector v01 = vector_subtract(tri->v[1], tri->v[0]); Vector v12 = vector_subtract(tri->v[2], tri->v[1]); return cross_product(v01, v12); } OOINLINE Vector resolveVectorInIJK(Vector v0, Triangle ijk) { Vector result; result.x = dot_product(v0, ijk.v[0]); result.y = dot_product(v0, ijk.v[1]); result.z = dot_product(v0, ijk.v[2]); return result; } OOINLINE bool OOTriangleIsDegenerate(Triangle tri) { return vector_equal(tri.v[0], tri.v[1]) || vector_equal(tri.v[1], tri.v[2]) || vector_equal(tri.v[2], tri.v[0]); } #endif /* INCLUDED_OOMATHS_h */ oolite-1.82/src/Core/OOTrumble.h000066400000000000000000000110031256642440500164420ustar00rootroot00000000000000/* OOTrumble.h Implements cute, fuzzy trumbles. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #import "OOOpenGL.h" @class PlayerEntity, AI, OOSound, OOTexture; #define TRUMBLE_MAX_ROTATION 15.0 #define TRUMBLE_MAX_ROTATIONAL_VELOCITY 5.0 #define TRUMBLE_GROWTH_RATE 0.01 enum trumble_animation { TRUMBLE_ANIM_NONE = 0, TRUMBLE_ANIM_IDLE, TRUMBLE_ANIM_BLINK, TRUMBLE_ANIM_SNARL, TRUMBLE_ANIM_PROOT, TRUMBLE_ANIM_SHUDDER, TRUMBLE_ANIM_STONED, TRUMBLE_ANIM_SPAWN, TRUMBLE_ANIM_SLEEP, TRUMBLE_ANIM_DIE }; enum trumble_eyes { TRUMBLE_EYES_NONE = 0, TRUMBLE_EYES_OPEN, TRUMBLE_EYES_SHUT, TRUMBLE_EYES_WIDE }; enum trumble_mouth { TRUMBLE_MOUTH_NONE = 0, TRUMBLE_MOUTH_POUT, TRUMBLE_MOUTH_GROWL, TRUMBLE_MOUTH_SNARL, TRUMBLE_MOUTH_NORMAL }; @interface OOTrumble: NSObject { @private PlayerEntity *player; // owning entity (not retained) // unichar digram[2]; // seed for pseudo-randomly setting up Trumble (pair of characters) // GLfloat colorBase[4]; // color of Trumble GLfloat colorPoint1[4]; // color of Trumble (variation 1) GLfloat colorPoint2[4]; // color of Trumble (variation 2) GLfloat colorEyes[4]; // color of Trumble (eye color) GLfloat *pointColor[6]; // pointscheme // GLfloat hunger; // behaviour modifier 0.0 (satiated) to 1.0 (starving) GLfloat discomfort; // behaviour modifier 0.0 (very happy) to 1.0 (extremely uncomfortable) // GLfloat size; // 0.0 -> max_size GLfloat max_size; // 0.90 -> 1.25 GLfloat growth_rate; // diff to size per sec. // GLfloat rotation; // CW rotation in radians (starts at 0.0) GLfloat rotational_velocity; // +r (radians/sec) // NSPoint position; // x, y onscreen relative to center of screen NSPoint movement; // +x, +y (screen units / sec) // NSPoint eye_position; // current position of eyes relative to their starting position NSPoint mouth_position; // current position of eyes relative to their starting position // double animationTime; // set to 0.0 at start of current animation double animationDuration; // set to 0.0 at start of current animation // enum trumble_animation animation; // current animation sequence enum trumble_animation nextAnimation; // next animation sequence // int animationStage; // sub-sequence within animation // enum trumble_mouth mouthFrame; // which mouth position - determines what part of the texture to display enum trumble_eyes eyeFrame; // which eye position - determines what part of the texture to display // OOTexture *texture; // GLfloat saved_float1, saved_float2; // BOOL readyToSpawn; } - (id) initForPlayer:(PlayerEntity *)p1; - (id) initForPlayer:(PlayerEntity *)p1 digram:(NSString*) digramString; - (void) setupForPlayer:(PlayerEntity *)p1 digram:(NSString*) digramString; - (void) spawnFrom:(OOTrumble *)parentTrumble; - (void) calcGrowthRate; - (unichar *) digram; - (NSPoint) position; - (NSPoint) movement; - (GLfloat) rotation; - (GLfloat) size; - (GLfloat) hunger; - (GLfloat) discomfort; // AI methods here - (void) actionIdle; - (void) actionBlink; - (void) actionSnarl; - (void) actionProot; - (void) actionShudder; - (void) actionStoned; - (void) actionPop; - (void) actionSleep; - (void) actionSpawn; - (void) randomizeMotionX; - (void) randomizeMotionY; - (void) drawTrumble:(double) z; - (void) updateTrumble:(double) delta_t; - (void) updateIdle:(double) delta_t; - (void) updateBlink:(double) delta_t; - (void) updateSnarl:(double) delta_t; - (void) updateProot:(double) delta_t; - (void) updateShudder:(double) delta_t; - (void) updateStoned:(double) delta_t; - (void) updatePop:(double) delta_t; - (void) updateSleep:(double) delta_t; - (void) updateSpawn:(double) delta_t; - (NSDictionary *)dictionary; - (void) setFromDictionary:(NSDictionary *)dict; @end oolite-1.82/src/Core/OOTrumble.m000066400000000000000000000567521256642440500164730ustar00rootroot00000000000000/* OOTrumble.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOTrumble.h" #import "Universe.h" #import "PlayerEntity.h" #import "OOTexture.h" #import "ResourceManager.h" #import "OOSound.h" #import "OOStringParsing.h" #import "OOMaths.h" #import "MyOpenGLView.h" static void InitTrumbleSounds(void); static void PlayTrumbleIdle(void); static void PlayTrumbleSqueal(void); @implementation OOTrumble - (id) init { self = [super init]; int i; for (i = 0; i < 4; i++) { colorPoint1[i] = 1.0; colorPoint2[i] = 1.0; } return self; } - (id) initForPlayer:(PlayerEntity*) p1 { self = [super init]; [self setupForPlayer: p1 digram: @"a1"]; return self; } - (id) initForPlayer:(PlayerEntity*) p1 digram:(NSString*) digramString { self = [super init]; [self setupForPlayer: p1 digram: digramString]; return self; } - (void) setupForPlayer:(PlayerEntity*) p1 digram:(NSString*) digramString { // set digram // digram[0] = [digramString characterAtIndex:0]; digram[1] = [digramString characterAtIndex:1]; // set player // player = p1; // set color points int r0 = (int)digram[0]; int r1 = (int)digram[1]; int pointscheme[6] = { 1, 1, 0, 0, 1, 1}; int ps = r0 >> 2; // first digram determines pattern of points pointscheme[0] = (ps >> 3) & 1; pointscheme[1] = (ps >> 2) & 1; pointscheme[2] = (ps >> 1) & 1; pointscheme[3] = ps & 1; pointscheme[4] = (ps >> 2) & 1; pointscheme[5] = (ps >> 3) & 1; GLfloat c1[4] = { 1.0, 1.0, 1.0, 1.0}; GLfloat c2[4] = { 1.0, 0.0, 0.0, 1.0}; // I am missing something. Please clarify the intent of the following statement. // The next statement shift the result of adding two masked values // max_size = 0.90 + 0.50 * (((r0 & 0x38) + (r1 & 0x38)) >> 3) / 63.0; // inheritable // The next statement adds masked(r0) to shifted(masked(r1) // max_size = 0.90 + 0.50 * ((r0 & 0x38) + ((r1 & 0x38) >> 3)) / 63.0; // inheritable // Sorry, but I cannot determine what you intended to do here. // // GILES: It's the second one, we're just determining a pseudo random max_size from the first digram max_size = 0.90 + 0.50 * ((r0 & 0x38) + ((r1 & 0x38) >> 3)) / 63.0; // inheritable // seed the random number generator // ranrot_srand(r0 + r1 * 256); // set random colors int col1 = r0 & 7; int col2 = r1 & 7; while (((col1 == 7)||(col1 == 0)) && ((col2 == 7)||(col2 == 0))) // monochrome not allowed { if (col1 == col2) col1 = ranrot_rand() & 7; else col2 = ranrot_rand() & 7; } c1[0] = (GLfloat)(col1 & 1); c1[1] = (GLfloat)((col1 >> 1) & 1); c1[2] = (GLfloat)((col1 >> 2) & 1); c2[0] = (GLfloat)(col2 & 1); c2[1] = (GLfloat)((col2 >> 1) & 1); c2[2] = (GLfloat)((col2 >> 2) & 1); if (col1 == 0) { c1[0] = 0.5 + 0.1 * c2[1]; c1[1] = 0.5 + 0.1 * c2[2]; c1[2] = 0.5; } if (col1 == 7) { c1[0] = 1.0 - 0.1 * c2[1]; c1[1] = 1.0 - 0.1 * c2[2]; c1[2] = 0.9; } if (col2 == 0) { c2[0] = 0.5 + 0.1 * c1[2]; c2[1] = 0.5 + 0.1 * c1[0]; c2[2] = 0.5; } if (col2 == 7) { c2[0] = 1.0 - 0.1 * c1[2]; c2[1] = 1.0 - 0.1 * c1[0]; c2[2] = 0.9; } // position and motion // position.x = (ranrot_rand() & 15)* 28 - 210; position.y = (ranrot_rand() & 15)* 28 - 210; // [self randomizeMotionX]; [self randomizeMotionY]; // rotation // rotation = TRUMBLE_MAX_ROTATION * (randf() - randf()); rotational_velocity = TRUMBLE_MAX_ROTATIONAL_VELOCITY * (randf() - randf()); // int i; for (i = 0; i < 4; i++) { colorPoint1[i] = c1[i]; colorPoint2[i] = c2[i]; } // for (i = 0; i < 6; i++) { pointColor[i] = colorPoint1; if (pointscheme[i] == 0) pointColor[i] = colorPoint1; if (pointscheme[i] == 1) pointColor[i] = colorPoint2; } // for (i = 0; i < 4; i++) { colorEyes[i] = 0.2 * (2.0 * pointColor[3][i] + 2.0 * pointColor[1][i] + 1.0); // eyes - paler than average colorBase[i] = 0.5 * (pointColor[2][i] + pointColor[3][i]); // mouth } // size = 0.5 * (1.0 + randf()); [self calcGrowthRate]; hunger = 0.0; discomfort = 0.0; // eye_position = NSMakePoint( 0.0, 0.075 * (randf() - randf())); eyeFrame = TRUMBLE_EYES_OPEN; // mouth_position = NSMakePoint( 0.0, 0.035 * (randf() - randf())); mouthFrame = TRUMBLE_MOUTH_NORMAL; // animation = TRUMBLE_ANIM_IDLE; nextAnimation = TRUMBLE_ANIM_IDLE; animationTime = 0.0; animationDuration = 1.5 + randf() * 3.0; // time until next animation // texture = [OOTexture textureWithName:@"trumblekit.png" inFolder:@"Textures" options:kOOTextureDefaultOptions | kOOTextureNoShrink anisotropy:0.0f lodBias:kOOTextureDefaultLODBias]; [texture retain]; InitTrumbleSounds(); readyToSpawn = NO; } - (void) dealloc { [texture release]; [super dealloc]; } - (void) spawnFrom:(OOTrumble*) parentTrumble { if (parentTrumble) { // mutate.. unichar mutation1 = ranrot_rand() & ranrot_rand() & ranrot_rand() & 0xff; // each bit has a 1/8 chance of being set unichar mutation2 = ranrot_rand() & ranrot_rand() & ranrot_rand() & 0xff; // each bit has a 1/8 chance of being set unichar* parentdigram = [parentTrumble digram]; unichar newdigram[2]; newdigram[0] = parentdigram[0] ^ mutation1; newdigram[1] = parentdigram[1] ^ mutation2; // [self setupForPlayer: player digram: [NSString stringWithCharacters:newdigram length:2]]; // size = [parentTrumble size] * 0.4; if (size < 0.5) size = 0.5; // minimum size position = [parentTrumble position]; rotation = [parentTrumble rotation]; movement = [parentTrumble movement]; movement.y += 8.0; // emerge! } else { size = 0.5; // minimum size position.x = (ranrot_rand() & 15)* 28 - 210; position.y = (ranrot_rand() & 15)* 28 - 210; [self randomizeMotionX]; [self randomizeMotionY]; rotation = TRUMBLE_MAX_ROTATION * (randf() - randf()); rotational_velocity = TRUMBLE_MAX_ROTATIONAL_VELOCITY * (randf() - randf()); } hunger = 0.25; [self calcGrowthRate]; discomfort = 0.0; [self actionSleep]; } - (void) calcGrowthRate { float rsize = size / max_size; growth_rate = TRUMBLE_GROWTH_RATE * (1.0 - rsize); } - (unichar *) digram { return digram; } - (NSPoint) position { return position; } - (NSPoint) movement { return movement; } - (GLfloat) rotation { return rotation; } - (GLfloat) size { return size; } - (GLfloat) hunger { return hunger; } - (GLfloat) discomfort { return discomfort; } // AI methods here - (void) actionIdle { nextAnimation = TRUMBLE_ANIM_IDLE; animationDuration = 1.5 + 3.0 * randf(); // time until next animation } - (void) actionBlink { nextAnimation = TRUMBLE_ANIM_BLINK; animationDuration = 0.5 + 0.5 * randf(); // time until next animation } - (void) actionSnarl { nextAnimation = TRUMBLE_ANIM_SNARL; animationDuration = 4.0 + 1.0 * randf(); // time until next animation } - (void) actionProot { nextAnimation = TRUMBLE_ANIM_PROOT; animationDuration = 1.5 + 0.5 * randf(); // time until next animation } - (void) actionShudder { nextAnimation = TRUMBLE_ANIM_SHUDDER; animationDuration = 2.25 + randf() * 1.5; // time until next animation } - (void) actionStoned { nextAnimation = TRUMBLE_ANIM_STONED; animationDuration = 1.5 + randf() * 3.0; // time until next animation } - (void) actionPop { nextAnimation = TRUMBLE_ANIM_DIE; animationDuration = 1.5 + randf() * 3.0; // time until next animation } - (void) actionSleep { nextAnimation = TRUMBLE_ANIM_SLEEP; animationDuration = 12.0 + 12.0 * randf(); // time until next animation } - (void) actionSpawn { nextAnimation = TRUMBLE_ANIM_SPAWN; animationDuration = 9.0 + 3.0 * randf(); // time until next animation } - (void) randomizeMotionX { movement.x = 36 * (randf() - 0.5); movement.x += (movement.x > 0)? 2.0: -2.0; rotational_velocity = TRUMBLE_MAX_ROTATIONAL_VELOCITY * (randf() - randf()); } - (void) randomizeMotionY { movement.y = 36 * (randf() - 0.5); movement.y += (movement.y > 0)? 2.0: -2.0; rotational_velocity = TRUMBLE_MAX_ROTATIONAL_VELOCITY * (randf() - randf()); } - (void) drawTrumble:(double) z { /* draws a trumble body as a fan of triangles... 2-------3-------4 | \ | / | | \ | / | | \ | / | 1-------0-------5 */ GLfloat wd = 96 * size; GLfloat ht = 96 * size; OOGL(glShadeModel(GL_SMOOTH)); OOGL(glEnable(GL_TEXTURE_2D)); OOGLPushModelView(); OOGLTranslateModelView(make_vector(position.x, position.y, z)); OOGLMultModelView(OOMatrixForRotationZ(rotation)); [texture apply]; // // Body.. // OOGLBEGIN(GL_TRIANGLE_FAN); glColor4fv(pointColor[3]); glTexCoord2f( 0.25, 0.5); glVertex2f( 0.0, -0.5 * ht); glColor4fv(pointColor[0]); glTexCoord2f( 0.0, 0.5); glVertex2f( -0.5 * wd, -0.5 * ht); glColor4fv(pointColor[1]); glTexCoord2f( 0.0, 0.0); glVertex2f( -0.5 * wd, 0.5 * ht); glColor4fv(pointColor[2]); glTexCoord2f( 0.25, 0.0); glVertex2f( 0.0, 0.5 * ht); glColor4fv(pointColor[4]); glTexCoord2f( 0.5, 0.0); glVertex2f( 0.5 * wd, 0.5 * ht); glColor4fv(pointColor[5]); glTexCoord2f( 0.5, 0.5); glVertex2f( 0.5 * wd, -0.5 * ht); OOGLEND(); // // Eyes // GLfloat eyeTextureOffset = 0.0; switch(eyeFrame) { case TRUMBLE_EYES_NONE : case TRUMBLE_EYES_OPEN : eyeTextureOffset = 0.0; break; case TRUMBLE_EYES_SHUT : eyeTextureOffset = 0.25; break; case TRUMBLE_EYES_WIDE : eyeTextureOffset = 0.5; break; } OOGLTranslateModelView(make_vector(eye_position.x * wd, eye_position.y * ht, 0.0)); OOGL(glColor4fv(colorEyes)); OOGLBEGIN(GL_QUADS); glTexCoord2f( 0.5, eyeTextureOffset); glVertex2f( -0.5 * wd, 0.20 * ht); glTexCoord2f( 1.0, eyeTextureOffset); glVertex2f( 0.5 * wd, 0.20 * ht); glTexCoord2f( 1.0, eyeTextureOffset + 0.25); glVertex2f( 0.5 * wd, -0.30 * ht); glTexCoord2f( 0.5, eyeTextureOffset + 0.25); glVertex2f( -0.5 * wd, -0.30 * ht); OOGLEND(); // // Mouth // GLfloat mouthTextureOffset = 0.0; switch(mouthFrame) { case TRUMBLE_MOUTH_POUT : mouthTextureOffset = 0.500; break; case TRUMBLE_MOUTH_NONE : case TRUMBLE_MOUTH_NORMAL : mouthTextureOffset = 0.625; break; case TRUMBLE_MOUTH_GROWL: mouthTextureOffset = 0.750; break; case TRUMBLE_MOUTH_SNARL: mouthTextureOffset = 0.875; break; } OOGLTranslateModelView(make_vector(mouth_position.x * wd, mouth_position.y * ht, 0.0)); OOGL(glColor4fv(colorBase)); OOGLBEGIN(GL_QUADS); glTexCoord2f( 0.0, mouthTextureOffset); glVertex2f( -0.25 * wd, -0.10 * ht); glTexCoord2f( 0.25, mouthTextureOffset); glVertex2f( 0.25 * wd, -0.10 * ht); glTexCoord2f( 0.25, mouthTextureOffset + 0.125); glVertex2f( 0.25 * wd, -0.35 * ht); glTexCoord2f( 0.0, mouthTextureOffset + 0.125); glVertex2f( -0.25 * wd, -0.35 * ht); OOGLEND(); // finally.. OOGLPopModelView(); OOGL(glDisable(GL_TEXTURE_2D)); } - (void) updateTrumble:(double) delta_t { // player movement NSPoint p_mov = NSMakePoint(TRUMBLE_MAX_ROTATIONAL_VELOCITY * [player dialPitch], TRUMBLE_MAX_ROTATIONAL_VELOCITY * [player dialRoll]); switch ([UNIVERSE viewDirection]) { GLfloat t; case VIEW_AFT: p_mov.x = -p_mov.x; p_mov.y = -p_mov.y; break; case VIEW_STARBOARD: t = p_mov.x; p_mov.x = -p_mov.y; p_mov.y = t; break; case VIEW_PORT: t = p_mov.x; p_mov.x = p_mov.y; p_mov.y = -t; break; default: break; } p_mov.x *= -4.0; // movement // GLfloat wd = 0.5 * 96 * size; GLfloat ht = 0.5 * 96 * size; // GLfloat bumpx = 320 * 1.5 - wd; GLfloat bumpy = 240 * 1.5 - ht; // position.x += delta_t * movement.x; if ((position.x < -bumpx)||(position.x > bumpx)) { position.x = (position.x < -bumpx)? -bumpx : bumpx; [self randomizeMotionX]; } position.y += delta_t * (movement.y + p_mov.x); if ((position.y < -bumpy)||(position.y > bumpy)) { position.y = (position.y < -bumpy)? -bumpy : bumpy; [self randomizeMotionY]; } // rotation // rotation += delta_t * (rotational_velocity + p_mov.y); if (animation != TRUMBLE_ANIM_DIE) { if (rotation < -TRUMBLE_MAX_ROTATION) { rotation = -TRUMBLE_MAX_ROTATION; rotational_velocity = TRUMBLE_MAX_ROTATIONAL_VELOCITY * 0.5 * (randf() + randf()); } if (rotation > TRUMBLE_MAX_ROTATION) { rotation = TRUMBLE_MAX_ROTATION; rotational_velocity = -TRUMBLE_MAX_ROTATIONAL_VELOCITY * 0.5 * (randf() + randf()); } } // growth // size += delta_t * growth_rate; hunger += delta_t * (growth_rate + TRUMBLE_GROWTH_RATE); if (size > max_size) // fully_grown.. stop growing { size = max_size; growth_rate = 0.0; } [self calcGrowthRate]; if (hunger > 0.75) growth_rate = 0.0; if (hunger > 1.0) hunger = 1.0; // clamp // feelings // GLfloat temp = [player hullHeatLevel]; discomfort += delta_t * hunger * 0.02 * (1.0 - hunger); if (temp > 0.33) discomfort += delta_t * (temp - 0.33) * (temp - 0.33) * 0.05; if (discomfort > 1.0) discomfort = 1.0; // clamp // feeding & reproducing // // am I really hungry? if (hunger > 0.50) { // consult menu... ShipEntity *selectedCargopod = nil; float mostYummy = 0.0; NSMutableArray *cargopods = [player cargo]; // the cargo pods NSUInteger i, n_pods = [cargopods count]; for (i = 0 ; i < n_pods; i++) { ShipEntity *cargopod = [cargopods objectAtIndex:i]; OOCommodityType cargo_type = [cargopod commodityType]; float yumminess = (1.0 + randf()) * [[UNIVERSE commodityMarket] trumbleOpinionForGood:cargo_type]; if (yumminess > mostYummy) { selectedCargopod = cargopod; mostYummy = yumminess; } } if (selectedCargopod) { // feed float trumbleAppetiteAccumulator = [player trumbleAppetiteAccumulator]; trumbleAppetiteAccumulator += hunger; hunger = 0.0; discomfort -= mostYummy * 0.5; if (discomfort < 0.0) discomfort = 0.0; if (trumbleAppetiteAccumulator > 10.0) { // eaten all of this cargo! NSString* ms = [NSString stringWithFormat:DESC(@"trumbles-eat-@"), [UNIVERSE displayNameForCommodity:[selectedCargopod commodityType]]]; [UNIVERSE addMessage: ms forCount: 4.5]; [cargopods removeObject:selectedCargopod]; trumbleAppetiteAccumulator -= 10.0; // consider breeding - must be full grown and happy if ((size > 0.95)&&(discomfort < 0.25)) { readyToSpawn = YES; } [player setTrumbleAppetiteAccumulator:trumbleAppetiteAccumulator]; } } } // animations // switch (animation) { case TRUMBLE_ANIM_SNARL : [self updateSnarl: delta_t]; break; case TRUMBLE_ANIM_SHUDDER : [self updateShudder: delta_t]; break; case TRUMBLE_ANIM_STONED : [self updateStoned: delta_t]; break; case TRUMBLE_ANIM_DIE : [self updatePop: delta_t]; break; case TRUMBLE_ANIM_BLINK : [self updateBlink: delta_t]; break; case TRUMBLE_ANIM_PROOT : [self updateProot: delta_t]; break; case TRUMBLE_ANIM_SLEEP : [self updateSleep: delta_t]; break; case TRUMBLE_ANIM_SPAWN : [self updateSpawn: delta_t]; break; case TRUMBLE_ANIM_IDLE : default: [self updateIdle: delta_t]; break; } } - (void) updateIdle:(double) delta_t { animationTime += delta_t; if (animationTime > animationDuration) { // blink or proot or idle and/or change direction [self actionIdle]; if (randf() < 0.25) [self actionBlink]; if (randf() < 0.10) [self randomizeMotionX]; if (randf() < 0.10) [self randomizeMotionY]; if (randf() < 0.05) [self actionProot]; if (randf() < 0.01) [self actionSleep]; if (randf() < 0.01) [self actionSnarl]; // if (readyToSpawn) { [self actionSpawn]; readyToSpawn = NO; } // if (discomfort > 0.5 + randf()) { [self actionShudder]; } // if (discomfort > 0.96) { [self actionPop]; } // animation = nextAnimation; animationTime = 0.0; } } - (void) updateBlink:(double) delta_t { eyeFrame = TRUMBLE_EYES_SHUT; animationTime += delta_t; if (animationTime > animationDuration) { // blink or proot or idle [self actionIdle]; if (randf() < 0.05) [self actionBlink]; if (randf() < 0.1) [self actionProot]; animation = nextAnimation; animationTime = 0.0; eyeFrame = TRUMBLE_EYES_OPEN; } } - (void) updateSnarl:(double) delta_t { int pc = 100 * animationTime / animationDuration; if (pc < 25) { eyeFrame = TRUMBLE_EYES_SHUT; mouthFrame = TRUMBLE_MOUTH_GROWL; } if ((pc >=25)&&(pc < 90)) { double vibr = (pc & 1)? -1.0 : 1.0; if (digram[1] & 4) position.x += size * vibr * 0.5; else position.y += size * vibr * 0.5; eyeFrame = TRUMBLE_EYES_WIDE; if (pc & 2) mouthFrame = TRUMBLE_MOUTH_SNARL; else mouthFrame = TRUMBLE_MOUTH_GROWL; } if ((pc >=90)&&(pc < 100)) { eyeFrame = TRUMBLE_EYES_WIDE; mouthFrame = TRUMBLE_MOUTH_GROWL; } animationTime += delta_t; if (animationTime > animationDuration) { // blink or idle [self actionIdle]; if (randf() < 0.1) [self actionBlink]; animation = nextAnimation; animationTime = 0.0; eyeFrame = TRUMBLE_EYES_OPEN; mouthFrame = TRUMBLE_MOUTH_NORMAL; } } - (void) updateProot:(double) delta_t { if (!animationTime) { animationStage = 0; } int pc = 100 * animationTime / animationDuration; if (pc < 10) { eyeFrame = TRUMBLE_EYES_SHUT; mouthFrame = TRUMBLE_MOUTH_POUT; } if (pc >=10) { double vibr = (pc & 2)? -1.0 : 1.0; position.x += size * vibr * 0.25; eyeFrame = TRUMBLE_EYES_SHUT; mouthFrame = TRUMBLE_MOUTH_GROWL; if (!animationStage) { animationStage = 1; PlayTrumbleIdle(); } } animationTime += delta_t; if (animationTime > animationDuration) { // blink or idle [self actionIdle]; if (randf() < 0.1) [self actionBlink]; animation = nextAnimation; animationTime = 0.0; eyeFrame = TRUMBLE_EYES_OPEN; mouthFrame = TRUMBLE_MOUTH_NORMAL; } } - (void) updateShudder:(double) delta_t { if (!animationTime) { eyeFrame = TRUMBLE_EYES_WIDE; mouthFrame = TRUMBLE_MOUTH_GROWL; PlayTrumbleSqueal(); } int pc = 100 * animationTime / animationDuration; if (pc < 10) { eyeFrame = TRUMBLE_EYES_WIDE; mouthFrame = TRUMBLE_MOUTH_GROWL; } if (pc >= 10) { double vibr = (pc & 2)? -0.25 : 0.25; position.x += size * vibr; eyeFrame = TRUMBLE_EYES_OPEN; mouthFrame = TRUMBLE_MOUTH_GROWL; } animationTime += delta_t; if (animationTime > animationDuration) { // feel better discomfort *= 0.9; // blink or idle [self actionIdle]; if (randf() < 0.1) [self actionBlink]; animation = nextAnimation; animationTime = 0.0; eyeFrame = TRUMBLE_EYES_OPEN; mouthFrame = TRUMBLE_MOUTH_NORMAL; } } - (void) updateStoned:(double) delta_t { } - (void) updatePop:(double) delta_t { if (!animationTime) { eyeFrame = TRUMBLE_EYES_SHUT; mouthFrame = TRUMBLE_MOUTH_GROWL; movement.y = (ranrot_rand() & 7); if (randf() < 0.5) rotational_velocity = 63 + (ranrot_rand() & 127); else rotational_velocity = -63 - (ranrot_rand() & 127); // squeal here! PlayTrumbleSqueal(); } float pc = animationTime / animationDuration; // fading alpha colorPoint1[1] *= (1.0 - delta_t); colorPoint2[1] *= (1.0 - delta_t); colorPoint1[2] *= (1.0 - delta_t); colorPoint2[2] *= (1.0 - delta_t); colorPoint1[3] = (1.0 - pc); colorPoint2[3] = (1.0 - pc); colorBase[3] = (1.0 - pc); // falling movement.y -= delta_t * 98.0; rotational_velocity *= (1.0 + delta_t); // shrinking size -= delta_t * (1.0 - pc) * size; animationTime += delta_t; if (animationTime > animationDuration) { // kaputnik! [player removeTrumble:self]; } } - (void) updateSleep:(double) delta_t { if (!animationTime) { saved_float1 = eye_position.y; saved_float2 = mouth_position.y; } eyeFrame = TRUMBLE_EYES_SHUT; int pc = 512 * animationTime / animationDuration; if (pc & 16) { double vibr = (pc & 2)? -0.0025 : 0.0025; eye_position.y += size * vibr; mouth_position.y += size * vibr * -0.5; } else { eye_position.y = saved_float1; mouth_position.y = saved_float2; } animationTime += delta_t; if (animationTime > animationDuration) { // idle or proot eye_position.y = saved_float1; mouth_position.y = saved_float2; [self actionIdle]; if (randf() < 0.25) [self actionProot]; animation = nextAnimation; animationTime = 0.0; eyeFrame = TRUMBLE_EYES_OPEN; } } - (void) updateSpawn:(double) delta_t { movement.x *= (1.0 - delta_t); movement.y *= (1.0 - delta_t); rotation *= (1.0 - delta_t); rotational_velocity *= (1.0 - delta_t); eyeFrame = TRUMBLE_EYES_WIDE; mouthFrame = TRUMBLE_MOUTH_POUT; int pc = 256 * animationTime / animationDuration; double vibr = (pc & 2)? -0.002 * pc : 0.002 * pc; position.x += size * vibr; animationTime += delta_t; if (animationTime > animationDuration) { // proot eye_position.y = saved_float1; mouth_position.y = saved_float2; [self actionProot]; animation = nextAnimation; animationTime = 0.0; eyeFrame = TRUMBLE_EYES_OPEN; mouthFrame = TRUMBLE_MOUTH_NORMAL; [self randomizeMotionX]; [player addTrumble:self]; } } - (NSDictionary*) dictionary { return [NSDictionary dictionaryWithObjectsAndKeys: [NSString stringWithCharacters:digram length:2], @"digram", [NSNumber numberWithFloat:hunger], @"hunger", [NSNumber numberWithFloat:discomfort], @"discomfort", [NSNumber numberWithFloat:size], @"size", [NSNumber numberWithFloat:growth_rate], @"growth_rate", [NSNumber numberWithFloat:rotation], @"rotation", [NSNumber numberWithFloat:rotational_velocity], @"rotational_velocity", StringFromPoint(position), @"position", StringFromPoint(movement), @"movement", nil]; } - (void) setFromDictionary:(NSDictionary*) dict { NSString* digramString = (NSString*)[dict objectForKey:@"digram"]; [self setupForPlayer: player digram: digramString]; hunger = [[dict objectForKey: @"hunger"] floatValue]; discomfort = [[dict objectForKey: @"discomfort"] floatValue]; size = [[dict objectForKey: @"size"] floatValue]; growth_rate = [[dict objectForKey: @"growth_rate"] floatValue]; rotation = [[dict objectForKey: @"rotation"] floatValue]; rotational_velocity = [[dict objectForKey: @"rotational_velocity"] floatValue]; position = PointFromString([dict objectForKey: @"position"]); movement = PointFromString([dict objectForKey: @"movement"]); } @end static OOSoundSource *sTrumbleSoundSource; static OOSound *sTrumbleIdleSound; static OOSound *sTrumbleSqealSound; static void InitTrumbleSounds(void) { if (sTrumbleSoundSource == nil) { sTrumbleSoundSource = [[OOSoundSource alloc] init]; sTrumbleIdleSound = [[OOSound alloc] initWithCustomSoundKey:@"[trumble-idle]"]; sTrumbleSqealSound = [[OOSound alloc] initWithCustomSoundKey:@"[trumble-squeal]"]; } } static void PlayTrumbleIdle(void) { // Only play idle sound if no trumble is making noise. if (![sTrumbleSoundSource isPlaying]) { // trumble sound from random direction - where's it gone now? [sTrumbleSoundSource setPosition:OORandomUnitVector()]; [sTrumbleSoundSource playSound:sTrumbleIdleSound]; } } static void PlayTrumbleSqueal(void) { // Play squeal sound if no trumble is currently squealing, but trumping idle sound. if (![sTrumbleSoundSource isPlaying] || [sTrumbleSoundSource sound] == sTrumbleIdleSound) { // trumble sound from random direction - where's it gone now? [sTrumbleSoundSource setPosition:OORandomUnitVector()]; [sTrumbleSoundSource playSound:sTrumbleSqealSound]; } } oolite-1.82/src/Core/OOTypes.h000066400000000000000000000114271256642440500161460ustar00rootroot00000000000000/* OOTypes.h Various simple types that don't require us to pull in the associated class headers. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "OOFunctionAttributes.h" #include "OOCocoa.h" typedef enum { OPTIMIZED_BY_NONE, OPTIMIZED_BY_JUMPS, OPTIMIZED_BY_TIME } OORouteType; #define ENTRY(label, value) label, typedef enum { #include "OOViewID.tbl" kOOViewIDDefault = VIEW_NONE } OOViewID; #undef ENTRY typedef enum { AEGIS_NONE, AEGIS_CLOSE_TO_ANY_PLANET, AEGIS_CLOSE_TO_MAIN_PLANET, AEGIS_IN_DOCKING_RANGE } OOAegisStatus; typedef enum { CARGO_NOT_CARGO = -1, CARGO_SLAVES = 3, CARGO_ALLOY = 9, CARGO_MINERALS = 12, CARGO_THARGOID = 16, CARGO_RANDOM = 100, CARGO_SCRIPTED_ITEM = 200, CARGO_CHARACTER = 300 } OOCargoType; /* enum { COMMODITY_UNDEFINED = -1, // FIXME: hard-coded commodity types are used in OOJSManifest. Everything else is data-driven. COMMODITY_FOOD, // = 0 COMMODITY_TEXTILES, COMMODITY_RADIOACTIVES, COMMODITY_SLAVES, COMMODITY_LIQUOR_WINES, COMMODITY_LUXURIES, COMMODITY_NARCOTICS, COMMODITY_COMPUTERS, COMMODITY_MACHINERY, COMMODITY_ALLOYS, COMMODITY_FIREARMS, COMMODITY_FURS, COMMODITY_MINERALS, COMMODITY_GOLD, COMMODITY_PLATINUM, COMMODITY_GEM_STONES, COMMODITY_ALIEN_ITEMS }; typedef NSInteger OOCommodityType; */ typedef NSString* OOCommodityType; typedef enum { CARGO_FLAG_NONE = 400, CARGO_FLAG_FULL_PLENTIFUL = 501, CARGO_FLAG_FULL_SCARCE = 502, CARGO_FLAG_FULL_MEDICAL = 503, CARGO_FLAG_FULL_CONTRABAND = 504, CARGO_FLAG_PIRATE = 505, CARGO_FLAG_FULL_UNIFORM = 510, CARGO_FLAG_CANISTERS = 600, CARGO_FLAG_FULL_PASSENGERS = 700 } OOCargoFlag; typedef enum { UNITS_TONS, UNITS_KILOGRAMS, UNITS_GRAMS } OOMassUnit; typedef enum { ENERGY_UNIT_NONE, ENERGY_UNIT_NORMAL_DAMAGED, ENERGY_UNIT_NAVAL_DAMAGED, OLD_ENERGY_UNIT_NORMAL =15, OLD_ENERGY_UNIT_NAVAL = 20, ENERGY_UNIT_NORMAL = 8, ENERGY_UNIT_NAVAL = 16 } OOEnergyUnitType; #define ENTRY(label, value) label = value, typedef enum { #include "OOCompassMode.tbl" } OOCompassMode; enum { kOOCompassModeDefault = COMPASS_MODE_BASIC }; #undef ENTRY typedef enum { #define DIFF_STRING_ENTRY(label, string) label, #include "OOLegalStatusReason.tbl" #undef DIFF_STRING_ENTRY kOOLegalStatusReasonDefault = kOOLegalStatusReasonUnknown } OOLegalStatusReason; typedef enum { DOCKING_CLEARANCE_STATUS_NONE, DOCKING_CLEARANCE_STATUS_NOT_REQUIRED, DOCKING_CLEARANCE_STATUS_REQUESTED, DOCKING_CLEARANCE_STATUS_GRANTED, DOCKING_CLEARANCE_STATUS_TIMING_OUT, } OODockingClearanceStatus; typedef uint32_t OOCargoQuantity; typedef int32_t OOCargoQuantityDelta; typedef uint16_t OOFuelQuantity; typedef uint64_t OOCreditsQuantity; #define kOOMaxCredits ULLONG_MAX typedef uint16_t OOKeyCode; typedef uint16_t OOUniversalID; // Valid IDs range from 100 to 1000. enum { UNIVERSE_MAX_ENTITIES = 2048, NO_TARGET = 0, MIN_ENTITY_UID = 100, MAX_ENTITY_UID = MIN_ENTITY_UID + UNIVERSE_MAX_ENTITIES + 1 }; enum { kOOVariableTechLevel = 99 }; typedef NSUInteger OOTechLevelID; // 0..14, 99 is special. NSNotFound is used, so NSUInteger required. typedef uint8_t OOGovernmentID; // 0..7 typedef uint8_t OOEconomyID; // 0..7 typedef uint8_t OOGalaxyID; // 0..7 typedef int16_t OOSystemID; // 0..255, -1 for interstellar space (?) enum { kOOMaximumGalaxyID = 7, kOOMaximumSystemID = 255, kOOMinimumSystemID = -1, kOOSystemIDInterstellarSpace = kOOMinimumSystemID }; typedef double OOTimeAbsolute; typedef double OOTimeDelta; typedef enum { WEAPON_FACING_FORWARD = 1, WEAPON_FACING_AFT = 2, WEAPON_FACING_PORT = 4, WEAPON_FACING_STARBOARD = 8, WEAPON_FACING_NONE = 0 } OOWeaponFacing; typedef uint8_t OOWeaponFacingSet; // May have multiple bits set. #define VALID_WEAPON_FACINGS (WEAPON_FACING_NONE | WEAPON_FACING_FORWARD | WEAPON_FACING_AFT | WEAPON_FACING_PORT | WEAPON_FACING_STARBOARD) typedef enum { DETAIL_LEVEL_MINIMUM = 0, DETAIL_LEVEL_NORMAL = 1, DETAIL_LEVEL_SHADERS = 2, DETAIL_LEVEL_EXTRAS = 3, DETAIL_LEVEL_MAXIMUM = 3 } OOGraphicsDetail; oolite-1.82/src/Core/OOVector.h000066400000000000000000000207371256642440500163100ustar00rootroot00000000000000/* OOVector.h Mathematical framework for Oolite. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef INCLUDED_OOMATHS_h #error Do not include OOVector.h directly; include OOMaths.h. #else #ifndef OOMATHS_EXTERNAL_VECTOR_TYPES typedef struct Vector { OOScalar x; OOScalar y; OOScalar z; } Vector; typedef struct Vector2D { OOScalar x; OOScalar y; } Vector2D; #endif extern const Vector kZeroVector, /* 0, 0, 0 */ kBasisXVector, /* 1, 0, 0 */ kBasisYVector, /* 0, 1, 0 */ kBasisZVector; /* 0, 0, 1 */ extern const Vector2D kZeroVector2D, /* 0, 0 */ kBasisXVector2D, /* 1, 0 */ kBasisYVector2D; /* 0, 1 */ /* Construct vector */ OOINLINE Vector make_vector(OOScalar vx, OOScalar vy, OOScalar vz) INLINE_CONST_FUNC; OOINLINE Vector2D MakeVector2D(OOScalar vx, OOScalar vy) INLINE_CONST_FUNC; #if !OOMATHS_STANDALONE /* Generate random vectors. */ Vector OORandomUnitVector(void); Vector OOVectorRandomSpatial(OOScalar maxLength); // Random vector uniformly distributed in radius-maxLength sphere. (Longer vectors are more common.) Vector OOVectorRandomRadial(OOScalar maxLength); // Random vector with uniform distribution of direction and radius in radius-maxLength sphere. (Causes clustering at centre.) // only needed in high-precision forms so far //Vector OORandomPositionInCylinder(Vector centre1, OOScalar exclusion1, Vector centre2, OOScalar exclusion2, OOScalar radius); //Vector OORandomPositionInShell(Vector centre, OOScalar inner, OOScalar outer); #endif /* Multiply vector by scalar (in place) */ OOINLINE void scale_vector(Vector *outVector, OOScalar factor) ALWAYS_INLINE_FUNC NONNULL_FUNC; /* Multiply vector by scalar */ OOINLINE Vector vector_multiply_scalar(Vector v, OOScalar s) INLINE_CONST_FUNC; /* Addition and subtraction of vectors */ OOINLINE Vector vector_add(Vector a, Vector b) INLINE_CONST_FUNC; OOINLINE Vector vector_subtract(Vector a, Vector b) INLINE_CONST_FUNC; #define vector_between(a, b) vector_subtract(b, a) OOINLINE Vector vector_flip(Vector v) INLINE_CONST_FUNC; /* Vector linear interpolation */ OOINLINE Vector OOVectorInterpolate(Vector a, Vector b, OOScalar where) INLINE_CONST_FUNC; OOINLINE Vector OOVectorTowards(Vector a, Vector b, OOScalar where) INLINE_CONST_FUNC; /* Comparison of vectors */ OOINLINE bool vector_equal(Vector a, Vector b) INLINE_CONST_FUNC; /* Square of magnitude of vector */ OOINLINE OOScalar magnitude2(Vector vec) INLINE_CONST_FUNC; /* Magnitude of vector */ OOINLINE OOScalar magnitude(Vector vec) INLINE_CONST_FUNC; /* Normalize vector */ OOINLINE Vector vector_normal(Vector vec) INLINE_CONST_FUNC; /* Normalize vector, returning fallback if zero vector. */ OOINLINE Vector vector_normal_or_fallback(Vector vec, Vector fallback) INLINE_CONST_FUNC; OOINLINE Vector vector_normal_or_xbasis(Vector vec) INLINE_CONST_FUNC; OOINLINE Vector vector_normal_or_ybasis(Vector vec) INLINE_CONST_FUNC; OOINLINE Vector vector_normal_or_zbasis(Vector vec) INLINE_CONST_FUNC; /* Square of distance between vectors */ OOINLINE OOScalar distance2(Vector v1, Vector v2) INLINE_CONST_FUNC; /* Distance between vectors */ OOINLINE OOScalar distance(Vector v1, Vector v2) INLINE_CONST_FUNC; /* Dot product */ OOINLINE OOScalar dot_product (Vector first, Vector second) INLINE_CONST_FUNC; /* NORMALIZED cross product */ OOINLINE Vector cross_product(Vector first, Vector second) INLINE_CONST_FUNC; /* General cross product */ OOINLINE Vector true_cross_product(Vector first, Vector second) CONST_FUNC; /* Triple product */ OOINLINE OOScalar triple_product(Vector first, Vector second, Vector third) INLINE_CONST_FUNC; /* Given three points on a surface, returns the normal to the surface. */ OOINLINE Vector normal_to_surface(Vector v1, Vector v2, Vector v3) CONST_FUNC; #if __OBJC__ NSString *VectorDescription(Vector vector); // @"(x, y, z)" #endif #if OOMATHS_OPENGL_INTEGRATION /* OpenGL conveniences. Need to be macros to work with OOMacroOpenGL. */ #define GLVertexOOVector(v) do { Vector v_ = v; glVertex3f(v_.x, v_.y, v_.z); } while (0) #define GLTranslateOOVector(v) do { Vector v_ = v; OOGL(glTranslatef(v_.x, v_.y, v_.z)); } while (0) #endif /*** Only inline definitions beyond this point ***/ OOINLINE Vector make_vector (OOScalar vx, OOScalar vy, OOScalar vz) { Vector result; result.x = vx; result.y = vy; result.z = vz; return result; } OOINLINE Vector2D MakeVector2D(OOScalar vx, OOScalar vy) { Vector2D result; result.x = vx; result.y = vy; return result; } OOINLINE void scale_vector(Vector *vec, OOScalar factor) { /* Clang static analyzer: reports an unintialized value here when called from -[HeadUpDisplay rescaleByFactor:]. This is blatantly wrong, as the array the vector comes from is fully initialized in the range being looped over. -- Ahruman 2012-09-14 */ vec->x *= factor; vec->y *= factor; vec->z *= factor; } OOINLINE Vector vector_multiply_scalar(Vector v, OOScalar s) { /* Clang static analyzer: reports a garbage value here when called from -[OOMesh rescaleByFactor:], apparently on baseless assumption that OOMesh._vertices points to only one vertex. -- Ahruman 2012-09-14 */ Vector r; r.x = v.x * s; r.y = v.y * s; r.z = v.z * s; return r; } OOINLINE Vector vector_add(Vector a, Vector b) { Vector r; r.x = a.x + b.x; r.y = a.y + b.y; r.z = a.z + b.z; return r; } OOINLINE Vector OOVectorInterpolate(Vector a, Vector b, OOScalar where) { return make_vector(OOLerp(a.x, b.x, where), OOLerp(a.y, b.y, where), OOLerp(a.z, b.z, where)); } OOINLINE Vector OOVectorTowards(Vector a, Vector b, OOScalar where) { return make_vector(a.x + b.x * where, a.y + b.y * where, a.z + b.z * where); } OOINLINE Vector vector_subtract(Vector a, Vector b) { Vector r; r.x = a.x - b.x; r.y = a.y - b.y; r.z = a.z - b.z; return r; } OOINLINE Vector vector_flip(Vector v) { return vector_subtract(kZeroVector, v); } OOINLINE bool vector_equal(Vector a, Vector b) { return a.x == b.x && a.y == b.y && a.z == b.z; } OOINLINE OOScalar magnitude2(Vector vec) { return vec.x * vec.x + vec.y * vec.y + vec.z * vec.z; } OOINLINE OOScalar magnitude(Vector vec) { return sqrt(magnitude2(vec)); } OOINLINE Vector vector_normal_or_fallback(Vector vec, Vector fallback) { OOScalar mag2 = magnitude2(vec); if (EXPECT_NOT(mag2 == 0.0f)) return fallback; return vector_multiply_scalar(vec, 1.0f / sqrt(mag2)); } OOINLINE Vector vector_normal_or_xbasis(Vector vec) { return vector_normal_or_fallback(vec, kBasisXVector); } OOINLINE Vector vector_normal_or_ybasis(Vector vec) { return vector_normal_or_fallback(vec, kBasisYVector); } OOINLINE Vector vector_normal_or_zbasis(Vector vec) { return vector_normal_or_fallback(vec, kBasisZVector); } OOINLINE Vector vector_normal(Vector vec) { return vector_normal_or_fallback(vec, kZeroVector); } OOINLINE OOScalar distance2(Vector v1, Vector v2) { return magnitude2(vector_subtract(v1, v2)); } OOINLINE OOScalar distance(Vector v1, Vector v2) { return magnitude(vector_subtract(v1, v2)); } OOINLINE Vector true_cross_product(Vector first, Vector second) { Vector result; result.x = (first.y * second.z) - (first.z * second.y); result.y = (first.z * second.x) - (first.x * second.z); result.z = (first.x * second.y) - (first.y * second.x); return result; } OOINLINE Vector cross_product(Vector first, Vector second) { return vector_normal(true_cross_product(first, second)); } OOINLINE OOScalar dot_product (Vector a, Vector b) { return (a.x * b.x) + (a.y * b.y) + (a.z * b.z); } OOINLINE OOScalar triple_product(Vector first, Vector second, Vector third) { return dot_product(first, true_cross_product(second, third)); } OOINLINE Vector normal_to_surface(Vector v1, Vector v2, Vector v3) { Vector d0, d1; d0 = vector_subtract(v2, v1); d1 = vector_subtract(v3, v2); return cross_product(d0, d1); } #endif /* INCLUDED_OOMATHS_h */ oolite-1.82/src/Core/OOVector.m000066400000000000000000000074431256642440500163140ustar00rootroot00000000000000/* OOVector.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "OOMaths.h" const Vector kZeroVector = { 0.0f, 0.0f, 0.0f }; const Vector kBasisXVector = { 1.0f, 0.0f, 0.0f }; const Vector kBasisYVector = { 0.0f, 1.0f, 0.0f }; const Vector kBasisZVector = { 0.0f, 0.0f, 1.0f }; const Vector2D kZeroVector2D = { 0.0f, 0.0f }; const Vector2D kBasisXVector2D = { 1.0f, 0.0f }; const Vector2D kBasisYVector2D = { 0.0f, 1.0f }; #if !OOMATHS_STANDALONE const BoundingBox kZeroBoundingBox = {{ 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f }}; #endif #if __OBJC__ NSString *VectorDescription(Vector vector) { return [NSString stringWithFormat:@"(%g, %g, %g)", vector.x, vector.y, vector.z]; } #endif #if !OOMATHS_STANDALONE /* This generates random vectors distrubuted evenly over the surface of the unit sphere. It does this the simple way, by generating vectors in the half-unit cube and rejecting those outside the half-unit sphere (and the zero vector), then normalizing the result. (Half-unit measures are used to avoid unnecessary multiplications of randf() values.) In principle, using three normally-distributed co-ordinates (and again normalizing the result) would provide the right result without looping, but I don't trust bellf() so I'll go with the simple approach for now. */ Vector OORandomUnitVector(void) { Vector v; float m; do { v = make_vector(randf() - 0.5f, randf() - 0.5f, randf() - 0.5f); m = magnitude2(v); } while (m > 0.25f || m == 0.0f); // We're confining to a sphere of radius 0.5 using the sqared magnitude; 0.5 squared is 0.25. return vector_normal(v); } Vector OOVectorRandomSpatial(OOScalar maxLength) { Vector v; float m; do { v = make_vector(randf() - 0.5f, randf() - 0.5f, randf() - 0.5f); m = magnitude2(v); } while (m > 0.25f); // We're confining to a sphere of radius 0.5 using the sqared magnitude; 0.5 squared is 0.25. return vector_multiply_scalar(v, maxLength * 2.0f); // 2.0 is to compensate for the 0.5-radius sphere. } Vector OOVectorRandomRadial(OOScalar maxLength) { return vector_multiply_scalar(OORandomUnitVector(), randf() * maxLength); } Vector OORandomPositionInBoundingBox(BoundingBox bb) { Vector result; result.x = bb.min.x + randf() * (bb.max.x - bb.min.x); result.y = bb.min.y + randf() * (bb.max.y - bb.min.y); result.z = bb.min.z + randf() * (bb.max.z - bb.min.z); return result; } // only need high precision versions of these /*Vector OORandomPositionInCylinder(Vector centre1, OOScalar exclusion1, Vector centre2, OOScalar exclusion2, OOScalar radius) { OOScalar exc12 = exclusion1*exclusion1; OOScalar exc22 = exclusion2*exclusion2; Vector result; do { result = vector_add(OOVectorInterpolate(centre1,centre2,randf()),OOVectorRandomSpatial(radius)); } while(distance2(result,centre1) rd) faces |= CUBE_FACE_RIGHT; // right if (p.x < -rd) faces |= CUBE_FACE_LEFT; // left if (p.y > rd) faces |= CUBE_FACE_TOP; // above if (p.y < -rd) faces |= CUBE_FACE_BOTTOM; // below if (p.z > rd) faces |= CUBE_FACE_FRONT; // ahead if (p.z < -rd) faces |= CUBE_FACE_BACK; // behind return faces ; } int checkBevel(Vector p, GLfloat rd) { GLfloat r2 = rd * 2; int bevels = 0; if ( p.x + p.y > r2) bevels |= 0x001; if ( p.x - p.y > r2) bevels |= 0x002; if (-p.x + p.y > r2) bevels |= 0x004; if (-p.x - p.y > r2) bevels |= 0x008; if ( p.x + p.z > r2) bevels |= 0x010; if ( p.x - p.z > r2) bevels |= 0x020; if (-p.x + p.z > r2) bevels |= 0x040; if (-p.x - p.z > r2) bevels |= 0x080; if ( p.y + p.z > r2) bevels |= 0x100; if ( p.y - p.z > r2) bevels |= 0x200; if (-p.y + p.z > r2) bevels |= 0x400; if (-p.y - p.z > r2) bevels |= 0x800; return bevels; } int checkCorner(Vector p, GLfloat rd) { GLfloat r3 = rd * 3; int corners = 0; if (( p.x + p.y + p.z) > r3) corners |= 0x01; if (( p.x + p.y - p.z) > r3) corners |= 0x02; if (( p.x - p.y + p.z) > r3) corners |= 0x04; if (( p.x - p.y - p.z) > r3) corners |= 0x08; if ((-p.x + p.y + p.z) > r3) corners |= 0x10; if ((-p.x + p.y - p.z) > r3) corners |= 0x20; if ((-p.x - p.y + p.z) > r3) corners |= 0x40; if ((-p.x - p.y - p.z) > r3) corners |= 0x80; return corners; } Vector lineIntersectionWithFace(Vector p1, Vector p2, long mask, GLfloat rd) { if (CUBE_FACE_RIGHT & mask) return make_vector( rd, p1.y + (p2.y - p1.y) * (rd - p1.x) / (p2.x - p1.x), p1.z + (p2.z - p1.z) * (rd - p1.x) / (p2.x - p1.x)); if (CUBE_FACE_LEFT & mask) return make_vector( -rd, p1.y + (p2.y - p1.y) * (-rd - p1.x) / (p2.x - p1.x), p1.z + (p2.z - p1.z) * (-rd - p1.x) / (p2.x - p1.x)); if (CUBE_FACE_TOP & mask) return make_vector( p1.x + (p2.x - p1.x) * (rd - p1.y) / (p2.y - p1.y), rd, p1.z + (p2.z - p1.z) * (rd - p1.y) / (p2.y - p1.y)); if (CUBE_FACE_BOTTOM & mask) return make_vector( p1.x + (p2.x - p1.x) * (-rd - p1.y) / (p2.y - p1.y), -rd, p1.z + (p2.z - p1.z) * (-rd - p1.y) / (p2.y - p1.y)); if (CUBE_FACE_FRONT & mask) return make_vector( p1.x + (p2.x - p1.x) * (rd - p1.z) / (p2.z - p1.z), p1.y + (p2.y - p1.y) * (rd - p1.z) / (p2.z - p1.z), rd); if (CUBE_FACE_BACK & mask) return make_vector( p1.x + (p2.x - p1.x) * (-rd - p1.z) / (p2.z - p1.z), p1.y + (p2.y - p1.y) * (-rd - p1.z) / (p2.z - p1.z), -rd); return p1; } int checkPoint(Vector p1, Vector p2, GLfloat alpha, long mask, GLfloat rd) { Vector pp; pp.x = p1.x + alpha * (p2.x - p1.x); pp.y = p1.y + alpha * (p2.y - p1.y); pp.z = p1.z + alpha * (p2.z - p1.z); return (checkFace( pp, rd) & mask); } int checkLine(Vector p1, Vector p2, int mask, GLfloat rd) { int result = 0; if ((CUBE_FACE_RIGHT & mask) && (p1.x > p2.x) && (checkPoint( p1, p2, (rd-p1.x)/(p2.x-p1.x), 0x3f - CUBE_FACE_RIGHT, rd) == 0)) // right result |= CUBE_FACE_RIGHT; if ((CUBE_FACE_LEFT & mask) && (p1.x < p2.x) && (checkPoint( p1, p2, (-rd-p1.x)/(p2.x-p1.x), 0x3f - CUBE_FACE_LEFT, rd) == 0)) // left result |= CUBE_FACE_LEFT; if ((CUBE_FACE_TOP & mask) && (p1.y > p2.y) && (checkPoint( p1, p2, (rd-p1.y)/(p2.y-p1.y), 0x3f - CUBE_FACE_TOP, rd) == 0)) // above result |= CUBE_FACE_TOP; if ((CUBE_FACE_BOTTOM & mask) && (p1.y < p2.y) && (checkPoint( p1, p2, (-rd-p1.y)/(p2.y-p1.y), 0x3f - CUBE_FACE_BOTTOM, rd) == 0)) // below result |= CUBE_FACE_BOTTOM; if ((CUBE_FACE_FRONT & mask) && (p1.z > p2.z) && (checkPoint( p1, p2, (rd-p1.z)/(p2.z-p1.z), 0x3f - CUBE_FACE_FRONT, rd) == 0)) // ahead result |= CUBE_FACE_FRONT; if ((CUBE_FACE_BACK & mask) && (p1.z < p2.z) && (checkPoint( p1, p2, (-rd-p1.z)/(p2.z-p1.z), 0x3f - CUBE_FACE_BACK, rd) == 0)) // behind result |= CUBE_FACE_BACK; return result; } // line v0 to v1 is compared with a cube centered on the origin (corners at -rd,-rd,-rd to rd,rd,rd). // returns -1 if the line intersects the cube. int lineCubeIntersection(Vector v0, Vector v1, GLfloat rd) { int v0_test, v1_test; // compare both vertexes with all six face-planes // if ((v0_test = checkFace( v0, rd)) == 0) return -1; // v0 is inside the cube if ((v1_test = checkFace( v1, rd)) == 0) return -1; // v1 is inside the cube // check they're not both outside one face-plane // if ((v0_test & v1_test) != 0) return 0; // both v0 and v1 are outside the same face of the cube // Now do the same test for the 12 edge planes // v0_test |= checkBevel( v0, rd) << 8; v1_test |= checkBevel( v1, rd) << 8; if ((v0_test & v1_test) != 0) return 0; // v0 and v1 outside of the same bevel // Now do the same test for the 8 corner planes // v0_test |= checkCorner( v0, rd) << 24; v1_test |= checkCorner( v1, rd) << 24; if ((v0_test & v1_test) != 0) return 0; // v0 and v1 outside of same corner // see if the v0-->v1 line intersects the cube. // return checkLine( v0, v1, v0_test | v1_test, rd); } oolite-1.82/src/Core/OOWeakReference.h000066400000000000000000000062051256642440500175460ustar00rootroot00000000000000/* OOWeakReference.h Weak reference class for Cocoa/GNUstep/OpenStep. As it stands, this will not work as a weak reference in a garbage-collected environment. A weak reference allows code to maintain a reference to an object while allowing the object to reach a retain count of zero and deallocate itself. To function, the referenced object must implement the OOWeakReferenceSupport protocol. Client use is extremely simple: to get a weak reference to the object, call -weakRetain and use the returned proxy instead of the actual object. When finished, release the proxy. Messages sent to the proxy will be forwarded as long as the underlying object exists; beyond that, they will act exactly like messages to nil. (IMPORTANT: this means messages returning floating-point or struct values have undefined return values, so use -weakRefUnderlyingObject in such cases.) Example: @interface ThingWatcher: NSObject { @private Thing *thing; } @end @implementation ThingWatcher - (void)setThing:(Thing *)aThing { [thing release]; thing = [aThing weakRetain]; } - (void)frobThing { [thing frob]; } - (void)dealloc { [thing release]; [super dealloc]; } @end Note that the only reference to OOWeakReference being involved is the call to weakRetain instead of retain. However, the following would not work: thing = aThing; [thing weakRetain]; Additionally, it is not possible to access instance variables directly -- but then, that's a filthy habit. OOWeakReferenceSupport implementation is also simple: @interface Thing: NSObject { @private OOWeakReference *weakSelf; } @end @implementation Thing - (id)weakRetain { if (weakSelf == nil) weakSelf = [OOWeakReference weakRefWithObject:self]; return [weakSelf retain]; } - (void)weakRefDied:(OOWeakReference *)weakRef { if (weakRef == weakSelf) weakSelf = nil; } - (void)dealloc { [weakSelf weakRefDrop]; // Very important! [super dealloc]; } - (void)frob { NSBeep(); } @end Copyright (C) 2007-2013 Jens Ayton This code is hereby placed in the public domain. */ #import #import "OOFunctionAttributes.h" @class OOWeakReference; @protocol OOWeakReferenceSupport - (id)weakRetain OO_RETURNS_RETAINED; // Returns a retained OOWeakReference, which should be released when finished with. - (void)weakRefDied:(OOWeakReference *)weakRef; @end @interface OOWeakReference: NSProxy { id _object; } - (id)weakRefUnderlyingObject; - (id)weakRetain OO_RETURNS_RETAINED; // Returns [self retain] for weakrefs. // For referred object only: + (id)weakRefWithObject:(id)object; - (void)weakRefDrop; @end @interface NSObject (OOWeakReference) - (id)weakRefUnderlyingObject; // Always self for non-weakrefs (and of course nil for nil). @end /* OOWeakRefObject Simple object implementing OOWeakReferenceSupport, to subclass. This provides a full implementation for simplicity, but keep in mind that the protocol can be implemented by any class. */ @interface OOWeakRefObject: NSObject { OOWeakReference *weakSelf; } - (id)weakSelf; // Equivalent to [[self weakRetain] autorelease] @end oolite-1.82/src/Core/OOWeakReference.m000066400000000000000000000064361256642440500175610ustar00rootroot00000000000000/* OOWeakReference.m Written by Jens Ayton in 2007-2013 for Oolite. This code is hereby placed in the public domain. */ #import "OOWeakReference.h" @interface OOWeakReferenceTemplates: NSObject + (void)weakRefDrop; + (id)weakRefUnderlyingObject; + (id)nilMethod; @end @implementation OOWeakReference // *** Core functionality. + (id)weakRefWithObject:(id)object { if (object == nil) return nil; OOWeakReference *result = [OOWeakReference alloc]; // No init for proxies. result->_object = object; return [result autorelease]; } - (void)dealloc { [_object weakRefDied:self]; [super dealloc]; } - (NSString *)description { if (_object != nil) return [_object description]; else return [NSString stringWithFormat:@"", [self class], self]; } - (id)weakRefUnderlyingObject { return _object; } - (id)weakRetain { return [self retain]; } - (void)weakRefDrop { _object = nil; } // *** Proxy evilness beyond this point. - (Class) class { return [_object class]; } - (BOOL) isProxy { return YES; } - (void)forwardInvocation:(NSInvocation *)invocation { // Does the right thing even with nil _object. [invocation invokeWithTarget:_object]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { NSMethodSignature *result = nil; if (__builtin_expect( selector != @selector(weakRefDrop) && selector != @selector(weakRefUnderlyingObject), 1)) { // Not a proxy method; get signature from _object if it exists, otherwise generic signature for nil calls. if (__builtin_expect(_object != nil, 1)) result = [(id)_object methodSignatureForSelector:selector]; else result = [OOWeakReferenceTemplates methodSignatureForSelector:@selector(nilMethod)]; } else { // One of OOWeakReference's own methods. result = [OOWeakReferenceTemplates methodSignatureForSelector:selector]; } return result; } - (BOOL)respondsToSelector:(SEL)selector { if (__builtin_expect(_object != nil && selector != @selector(weakRefDrop) && selector != @selector(weakRefUnderlyingObject), 1)) { // _object exists and it's not one of our methods, ask _object. return [_object respondsToSelector:selector]; } else { // Selector we responds to, or _object is nil and therefore responds to everything. return YES; } } // New fast forwarding mechanism introduced in Mac OS X 10.5. // Note that -forwardInvocation: is still called if _object is nil. - (id)forwardingTargetForSelector:(SEL)sel { return _object; } @end @implementation NSObject (OOWeakReference) - (id)weakRefUnderlyingObject { return self; } @end @implementation OOWeakRefObject - (id)weakRetain { if (weakSelf == nil) weakSelf = [OOWeakReference weakRefWithObject:self]; return [weakSelf retain]; // Each caller releases this, as -weakRetain must be balanced with -release. } - (void)weakRefDied:(OOWeakReference *)weakRef { if (weakRef == weakSelf) weakSelf = nil; } - (void)dealloc { [weakSelf weakRefDrop]; // Very important! [super dealloc]; } - (id)weakSelf { return [[self weakRetain] autorelease]; } @end @implementation OOWeakReferenceTemplates // These are never called, but an implementation must exist so that -methodSignatureForSelector: works. + (void)weakRefDrop {} + (id)weakRefUnderlyingObject { return nil; } + (id)nilMethod { return nil; } @end oolite-1.82/src/Core/OOWeakSet.h000066400000000000000000000032251256642440500164020ustar00rootroot00000000000000/* OOWeakSet.h A mutable set of weak references to objects conforming to OOWeakReferenceSupport. Semantics: * When an object in the set is deallocated, the object is removed from the set and the set's count drops. There is no notification for this. As such, there is no such thing as an immutable weak set. * Objects are uniqued by pointer equality, not isEquals:. * OOWeakSet is not thread-safe. It not only requires that all operations on it happen on one thread, but also that objects it's watching are (finally) released on that thread. LIMITATION: fast enumeration and Oolite's foreach() macro are not supported. Written by Jens Ayton in 2012 for Oolite. This code is hereby placed in the public domain. */ #import "OOCocoa.h" #import "OOWeakReference.h" @interface OOWeakSet: NSObject { @private NSMutableSet *_objects; } - (id) init; - (id) initWithCapacity:(NSUInteger)capacity; // As with Foundation collections, capacity is only a hint. + (instancetype) set; + (instancetype) setWithCapacity:(NSUInteger)capacity; - (NSUInteger) count; - (BOOL) containsObject:(id)object; - (NSEnumerator *) objectEnumerator; - (void) addObject:(id)object; // Unlike NSSet, adding nil fails silently. - (void) removeObject:(id)object; // Like NSSet, does not complain if object is not already a member. - (void) addObjectsByEnumerating:(NSEnumerator *)enumerator; - (void) makeObjectsPerformSelector:(SEL)selector; - (void) makeObjectsPerformSelector:(SEL)selector withObject:(id)argument; - (NSArray *) allObjects; - (void) removeAllObjects; @end oolite-1.82/src/Core/OOWeakSet.m000066400000000000000000000126261256642440500164140ustar00rootroot00000000000000/* OOWeakSet.m Written by Jens Ayton in 2012 for Oolite. This code is hereby placed in the public domain. */ #import "OOWeakSet.h" @interface OOWeakRefUnpackingEnumerator: NSEnumerator { @private NSEnumerator *_enumerator; } - (id) initWithEnumerator:(NSEnumerator *)enumerator; + (instancetype) enumeratorWithCollection:(id)collection; // Collection must implement -objectEnumerator @end @interface OOWeakSet (OOPrivate) - (void) compact; // Remove any zeroed entries. @end @implementation OOWeakSet - (id) init { return [self initWithCapacity:0]; } - (id) initWithCapacity:(NSUInteger)capacity { if ((self = [super init])) { _objects = [[NSMutableSet alloc] initWithCapacity:capacity]; if (_objects == NULL) { [self release]; return nil; } } return self; } + (instancetype) set { return [[[self alloc] init] autorelease]; } + (instancetype) setWithCapacity:(NSUInteger)capacity { return [[[self alloc] initWithCapacity:capacity] autorelease]; } - (void) dealloc { DESTROY(_objects); [super dealloc]; } - (NSString *) description { NSMutableString *result = [NSMutableString stringWithFormat:@"<%@ %p>{", [self class], self]; NSEnumerator *selfEnum = [self objectEnumerator]; id object = nil; BOOL first = YES; while ((object = [selfEnum nextObject])) { if (!first) [result appendString:@", "]; else first = NO; NSString *desc = nil; if ([object respondsToSelector:@selector(shortDescription)]) desc = [object shortDescription]; else desc = [object description]; [result appendString:desc]; } [result appendString:@"}"]; return result; } // MARK: Protocol conformance - (id) copyWithZone:(NSZone *)zone { [self compact]; OOWeakSet *result = [[OOWeakSet allocWithZone:zone] init]; [result addObjectsByEnumerating:[self objectEnumerator]]; return result; } - (id) mutableCopyWithZone:(NSZone *)zone { return [self copyWithZone:zone]; } - (BOOL) isEqual:(id)other { if (![other isKindOfClass:[OOWeakSet class]]) return NO; if ([self count] != [other count]) return NO; BOOL result = YES; NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSEnumerator *selfEnum = [self objectEnumerator]; id object = nil; while ((object = [selfEnum nextObject])) { if (![other containsObject:object]) { result = NO; break; } } DESTROY(pool); return result; } // MARK: Meat and potatoes - (NSUInteger) count { [self compact]; return [_objects count]; } - (BOOL) containsObject:(id)object { [self compact]; OOWeakReference *weakObj = [object weakRetain]; BOOL result = [_objects containsObject:weakObj]; [weakObj release]; return result; } - (NSEnumerator *) objectEnumerator { return [OOWeakRefUnpackingEnumerator enumeratorWithCollection:_objects]; } - (void) addObject:(id)object { if (object == nil) return; NSAssert([object conformsToProtocol:@protocol(OOWeakReferenceSupport)], @"Attempt to add object to OOWeakSet which does not conform to OOWeakReferenceSupport."); OOWeakReference *weakObj = [object weakRetain]; [_objects addObject:weakObj]; [weakObj release]; } - (void) removeObject:(id)object { OOWeakReference *weakObj = [object weakRetain]; [_objects removeObject:weakObj]; [weakObj release]; } - (void) addObjectsByEnumerating:(NSEnumerator *)enumerator { id object = nil; [self compact]; while ((object = [enumerator nextObject])) { [self addObject:object]; } } - (void) makeObjectsPerformSelector:(SEL)selector { OOWeakReference *weakRef = nil; foreach (weakRef, _objects) { [[weakRef weakRefUnderlyingObject] performSelector:selector]; } } - (void) makeObjectsPerformSelector:(SEL)selector withObject:(id)argument { OOWeakReference *weakRef = nil; foreach (weakRef, _objects) { [[weakRef weakRefUnderlyingObject] performSelector:selector withObject:argument]; } } - (NSArray *) allObjects { NSMutableArray *result = [NSMutableArray arrayWithCapacity:[_objects count]]; OOWeakReference *weakRef = nil; foreach (weakRef, _objects) { id object = [weakRef weakRefUnderlyingObject]; if (object != nil) [result addObject:object]; } #ifdef NDEBUG return result; #else return [NSArray arrayWithArray:result]; #endif } - (void) removeAllObjects { [_objects removeAllObjects]; } - (void) compact { OOWeakReference *weakRef = nil; BOOL compactRequired = NO; foreach (weakRef, _objects) { if ([weakRef weakRefUnderlyingObject] == nil) { compactRequired = YES; break; } } if (compactRequired) { NSMutableSet *newObjects = [[NSMutableSet alloc] initWithCapacity:[_objects count]]; foreach (weakRef, _objects) { if ([weakRef weakRefUnderlyingObject] != nil) { [newObjects addObject:weakRef]; } } [_objects release]; _objects = newObjects; } } @end @implementation OOWeakRefUnpackingEnumerator - (id) initWithEnumerator:(NSEnumerator *)enumerator { if (enumerator == nil) { [self release]; return nil; } if ((self = [super init])) { _enumerator = [enumerator retain]; } return self; } + (instancetype) enumeratorWithCollection:(id)collection { return [[[self alloc] initWithEnumerator:[collection objectEnumerator]] autorelease]; } - (void) dealloc { [_enumerator release]; [super dealloc]; } - (id) nextObject { id next = nil; while ((next = [_enumerator nextObject])) { next = [next weakRefUnderlyingObject]; if (next != nil) return next; } return nil; } @end oolite-1.82/src/Core/OOXMLExtensions.h000066400000000000000000000025171256642440500175620ustar00rootroot00000000000000/* OOXMLExtensions.h Extensions to Foundation property list classes to export property lists in XML format, which both Cocoa and GNUstep can read. This is done because GNUstep defaults to writing a version of OpenStep text-based property lists that Cocoa can't understand. The XML format is understood by both implementations, although GNUstep complains about not being able to find the DTD. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import /* interfaces */ @interface NSDictionary (OOXMLExtensions) - (BOOL) writeOOXMLToFile:(NSString *)path atomically:(BOOL)flag errorDescription:(NSString **)outErrorDesc; @end oolite-1.82/src/Core/OOXMLExtensions.m000066400000000000000000000031041256642440500175600ustar00rootroot00000000000000/* OOXMLExtensions.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOXMLExtensions.h" @implementation NSDictionary (OOXMLExtensions) - (BOOL) writeOOXMLToFile:(NSString *)path atomically:(BOOL)flag errorDescription:(NSString **)outErrorDesc { NSData *data = nil; NSString *errorDesc = nil; data = [NSPropertyListSerialization dataFromPropertyList:self format:NSPropertyListXMLFormat_v1_0 errorDescription:outErrorDesc]; if (data == nil) { if (outErrorDesc != NULL) { *outErrorDesc = [NSString stringWithFormat:@"could not convert property list to XML: %@", errorDesc]; } #if OOLITE_RELEASE_PLIST_ERROR_STRINGS [errorDesc release]; #endif return NO; } if (![data writeToFile:path atomically:YES]) { if (outErrorDesc != NULL) { *outErrorDesc = [NSString stringWithFormat:@"could not write data to %@.", path]; } return NO; } return YES; } @end oolite-1.82/src/Core/OXPVerifier/000077500000000000000000000000001256642440500165705ustar00rootroot00000000000000oolite-1.82/src/Core/OXPVerifier/OOAIStateMachineVerifierStage.h000066400000000000000000000023661256642440500244450ustar00rootroot00000000000000/* OOAIStateMachineVerifierStage.h OOOXPVerifierStage which validates AI plists. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOFileScannerVerifierStage.h" #if OO_OXP_VERIFIER_ENABLED @interface OOAIStateMachineVerifierStage: OOFileHandlingVerifierStage { @private NSSet *_whitelist; NSMutableSet *_usedAIs; } // Returns name to be used in -dependents by other stages. + (NSString *) nameForReverseDependencyForVerifier:(OOOXPVerifier *)verifier; - (void) stateMachineNamed:(NSString *)name usedByShip:(NSString *)shipName; @end #endif oolite-1.82/src/Core/OXPVerifier/OOAIStateMachineVerifierStage.m000066400000000000000000000146451256642440500244550ustar00rootroot00000000000000/* OOAIStateMachineVerifierStage.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOAIStateMachineVerifierStage.h" #import "OOCollectionExtractors.h" #import "OOPListParsing.h" #if OO_OXP_VERIFIER_ENABLED #import "ResourceManager.h" static NSString * const kStageName = @"Validating AIs"; @interface OOAIStateMachineVerifierStage (Private) - (void) validateAI:(NSString *)aiName; @end @implementation OOAIStateMachineVerifierStage - (void) dealloc { [_whitelist release]; [_usedAIs release]; [super dealloc]; } - (NSString *) name { return kStageName; } - (BOOL) shouldRun { return [_usedAIs count] != 0; } - (void) run { NSArray *aiNames = nil; NSEnumerator *aiEnum = nil; NSString *aiName = nil; NSMutableSet *whitelist = nil; // Build whitelist. Note that we merge in aliases since the distinction doesn't matter when just validating. whitelist = [[NSMutableSet alloc] init]; [whitelist addObjectsFromArray:[[ResourceManager whitelistDictionary] oo_arrayForKey:@"ai_methods"]]; [whitelist addObjectsFromArray:[[ResourceManager whitelistDictionary] oo_arrayForKey:@"ai_and_action_methods"]]; [whitelist addObjectsFromArray:[[[ResourceManager whitelistDictionary] oo_dictionaryForKey:@"ai_method_aliases"] allKeys]]; _whitelist = [whitelist copy]; [whitelist release]; aiNames = [[_usedAIs allObjects] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; for (aiEnum = [aiNames objectEnumerator]; (aiName = [aiEnum nextObject]); ) { [self validateAI:aiName]; } [_whitelist release]; _whitelist = nil; } + (NSString *) nameForReverseDependencyForVerifier:(OOOXPVerifier *)verifier { return kStageName; } - (void) stateMachineNamed:(NSString *)name usedByShip:(NSString *)shipName { OOFileScannerVerifierStage *fileScanner = nil; if (name == nil) return; if ([_usedAIs containsObject:name]) return; if (_usedAIs == nil) _usedAIs = [[NSMutableSet alloc] init]; [_usedAIs addObject:name]; fileScanner = [[self verifier] fileScannerStage]; if (![fileScanner fileExists:name inFolder:@"AIs" referencedFrom:[NSString stringWithFormat:@"shipdata.plist entry \"%@\"", shipName] checkBuiltIn:YES]) { OOLog(@"verifyOXP.validateAI.notFound", @"----- WARNING: AI state machine \"%@\" referenced in shipdata.plist entry \"%@\" could not be found in %@ or in Oolite.", name, shipName, [[self verifier] oxpDisplayName]); } } @end @implementation OOAIStateMachineVerifierStage (Private) - (void) validateAI:(NSString *)aiName { NSString *path = nil; NSDictionary *aiStateMachine = nil; NSEnumerator *stateEnum = nil; NSString *stateKey = nil; NSDictionary *stateHandlers = nil; NSEnumerator *handlerEnum = nil; NSString *handlerKey = nil; NSArray *handlerActions = nil; NSEnumerator *actionEnum = nil; NSString *action = nil; NSRange spaceRange; NSString *selector = nil; NSMutableSet *badSelectors = nil; NSString *badSelectorDesc = nil; NSUInteger index = 0; OOLog(@"verifyOXP.verbose.validateAI", @"- Validating AI \"%@\".", aiName); OOLogIndentIf(@"verifyOXP.verbose.validateAI"); // Attempt to load AI. path = [[[self verifier] fileScannerStage] pathForFile:aiName inFolder:@"AIs" referencedFrom:@"AI list" checkBuiltIn:NO]; if (path == nil) return; badSelectors = [NSMutableSet set]; aiStateMachine = OODictionaryFromFile(path); if (aiStateMachine == nil) { OOLog(@"verifyOXP.validateAI.failed.notDictPlist", @"***** ERROR: could not interpret \"%@\" as a dictionary.", path); return; } // Validate each state. for (stateEnum = [aiStateMachine keyEnumerator]; (stateKey = [stateEnum nextObject]); ) { stateHandlers = [aiStateMachine objectForKey:stateKey]; if (![stateHandlers isKindOfClass:[NSDictionary class]]) { OOLog(@"verifyOXP.validateAI.failed.invalidFormat.state", @"***** ERROR: state \"%@\" in AI \"%@\" is not a dictionary.", stateKey, aiName); continue; } // Verify handlers for this state. for (handlerEnum = [stateHandlers keyEnumerator]; (handlerKey = [handlerEnum nextObject]); ) { handlerActions = [stateHandlers objectForKey:handlerKey]; if (![handlerActions isKindOfClass:[NSArray class]]) { OOLog(@"verifyOXP.validateAI.failed.invalidFormat.handler", @"***** ERROR: handler \"%@\" for state \"%@\" in AI \"%@\" is not an array, ignoring.", handlerKey, stateKey, aiName); continue; } // Verify commands for this handler. index = 0; for (actionEnum = [handlerActions objectEnumerator]; (action = [actionEnum nextObject]); ) { index++; if (![action isKindOfClass:[NSString class]]) { OOLog(@"verifyOXP.validateAI.failed.invalidFormat.action", @"***** ERROR: action %lu in handler \"%@\" for state \"%@\" in AI \"%@\" is not a string, ignoring.", index - 1, handlerKey, stateKey, aiName); continue; } // Trim spaces from beginning and end. action = [action stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; // Cut off parameters. spaceRange = [action rangeOfString:@" "]; if (spaceRange.location == NSNotFound) selector = action; else selector = [action substringToIndex:spaceRange.location]; // Check against whitelist. if (![_whitelist containsObject:selector]) { [badSelectors addObject:selector]; } } } } if ([badSelectors count] != 0) { badSelectorDesc = [[[badSelectors allObjects] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] componentsJoinedByString:@", "]; OOLog(@"verifyOXP.validateAI.failed.badSelector", @"***** ERROR: the AI \"%@\" uses %lu unpermitted method%s: %@", aiName, [badSelectors count], ([badSelectors count] == 1) ? "" : "s", badSelectorDesc); } OOLogOutdentIf(@"verifyOXP.verbose.validateAI"); } @end #endif oolite-1.82/src/Core/OXPVerifier/OOCheckDemoShipsPListVerifierStage.h000066400000000000000000000020351256642440500254640ustar00rootroot00000000000000/* OOCheckDemoShipsPListVerifierStage.h OOOXPVerifierStage which checks that demoships.plist only contains ships defined in shipdata.plist. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOFileScannerVerifierStage.h" #if OO_OXP_VERIFIER_ENABLED @interface OOCheckDemoShipsPListVerifierStage: OOFileHandlingVerifierStage @end #endif oolite-1.82/src/Core/OXPVerifier/OOCheckDemoShipsPListVerifierStage.m000066400000000000000000000057661256642440500255070ustar00rootroot00000000000000/* OOCheckDemoShipsPListVerifierStage.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCheckDemoShipsPListVerifierStage.h" #if OO_OXP_VERIFIER_ENABLED #import "OOFileScannerVerifierStage.h" static NSString * const kStageName = @"Checking demoships.plist"; @interface OOCheckDemoShipsPListVerifierStage (OOPrivate) - (void)runCheckWithDemoShips:(NSArray *)demoshipsPList shipData:(NSDictionary *)shipdataPList; @end @implementation OOCheckDemoShipsPListVerifierStage - (NSString *)name { return kStageName; } - (BOOL)shouldRun { OOFileScannerVerifierStage *fileScanner = nil; fileScanner = [[self verifier] fileScannerStage]; return [fileScanner fileExists:@"demoships.plist" inFolder:@"Config" referencedFrom:nil checkBuiltIn:NO]; } - (void)run { OOFileScannerVerifierStage *fileScanner = nil; NSArray *demoshipsPList = nil; NSDictionary *shipdataPList = nil; fileScanner = [[self verifier] fileScannerStage]; demoshipsPList = [fileScanner plistNamed:@"demoships.plist" inFolder:@"Config" referencedFrom:nil checkBuiltIn:NO]; if (demoshipsPList == nil) return; // Check that it's an array if (![demoshipsPList isKindOfClass:[NSArray class]]) { OOLog(@"verifyOXP.demoshipsPList.notArray", @"***** ERROR: demoships.plist is not an array."); return; } shipdataPList = [fileScanner plistNamed:@"shipdata.plist" inFolder:@"Config" referencedFrom:nil checkBuiltIn:NO]; if (shipdataPList == nil) return; // Check that it's a dictionary if (![shipdataPList isKindOfClass:[NSDictionary class]]) { OOLog(@"verifyOXP.demoshipsPList.notDict", @"***** ERROR: shipdata.plist is not a dictionary."); return; } [self runCheckWithDemoShips:demoshipsPList shipData:shipdataPList]; } @end @implementation OOCheckDemoShipsPListVerifierStage (OOPrivate) - (void)runCheckWithDemoShips:(NSArray *)demoshipsPList shipData:(NSDictionary *)shipdataPList { NSEnumerator *nameEnum = nil; NSString *name = nil; for (nameEnum = [demoshipsPList objectEnumerator]; (name = [nameEnum nextObject]); ) { if ([shipdataPList objectForKey:name] == nil) { OOLog(@"verifyOXP.demoshipsPList.unknownShip", @"----- WARNING: demoships.plist entry \"%@\" not found in shipdata.plist.", name); } } } @end #endif oolite-1.82/src/Core/OXPVerifier/OOCheckEquipmentPListVerifierStage.h000066400000000000000000000017541256642440500255470ustar00rootroot00000000000000/* OOCheckEquipmentPListVerifierStage.h OOOXPVerifierStage which verifies equipment.plist. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOFileScannerVerifierStage.h" #if OO_OXP_VERIFIER_ENABLED @interface OOCheckEquipmentPListVerifierStage: OOFileHandlingVerifierStage @end #endif oolite-1.82/src/Core/OXPVerifier/OOCheckEquipmentPListVerifierStage.m000066400000000000000000000121421256642440500255450ustar00rootroot00000000000000/* OOCheckEquipmentPListVerifierStage.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCheckEquipmentPListVerifierStage.h" #if OO_OXP_VERIFIER_ENABLED #import "OOFileScannerVerifierStage.h" #import "Universe.h" #import "OOCollectionExtractors.h" static NSString * const kStageName = @"Checking equipment.plist"; @interface OOCheckEquipmentPListVerifierStage (OOPrivate) - (void)runCheckWithEquipment:(NSArray *)equipmentPList; @end @implementation OOCheckEquipmentPListVerifierStage - (NSString *)name { return kStageName; } - (BOOL)shouldRun { OOFileScannerVerifierStage *fileScanner = nil; fileScanner = [[self verifier] fileScannerStage]; return [fileScanner fileExists:@"equipment.plist" inFolder:@"Config" referencedFrom:nil checkBuiltIn:NO]; } - (void)run { OOFileScannerVerifierStage *fileScanner = nil; NSArray *equipmentPList = nil; fileScanner = [[self verifier] fileScannerStage]; equipmentPList = [fileScanner plistNamed:@"equipment.plist" inFolder:@"Config" referencedFrom:nil checkBuiltIn:NO]; if (equipmentPList == nil) return; // Check that it's an array if (![equipmentPList isKindOfClass:[NSArray class]]) { OOLog(@"verifyOXP.equipmentPList.notArray", @"***** ERROR: equipment.plist is not an array."); return; } [self runCheckWithEquipment:equipmentPList]; } @end @implementation OOCheckEquipmentPListVerifierStage (OOPrivate) - (void)runCheckWithEquipment:(NSArray *)equipmentPList { NSEnumerator *entryEnum = nil; NSArray *entry = nil; unsigned entryIndex = 0; NSUInteger elemCount; NSString *name = nil; NSString *entryDesc = nil; for (entryEnum = [equipmentPList objectEnumerator]; (entry = [entryEnum nextObject]); ) { ++entryIndex; // Entries should be arrays. if (![entry isKindOfClass:[NSArray class]]) { OOLog(@"verifyOXP.equipmentPList.entryNotArray", @"***** ERROR: equipment.plist entry %u of equipment.plist is not an array.", entryIndex); continue; } elemCount = [entry count]; // Make a name for entry for display purposes. if (EQUIPMENT_KEY_INDEX < elemCount) name = [entry oo_stringAtIndex:EQUIPMENT_KEY_INDEX]; else name = nil; if (name != nil) entryDesc = [NSString stringWithFormat:@"%u (\"%@\")", entryIndex, name]; else entryDesc = [NSString stringWithFormat:@"%u", entryIndex]; // Check that the entry has an acceptable number of elements. if (elemCount < 5) { OOLog(@"verifyOXP.equipmentPList.badEntrySize", @"***** ERROR: equipment.plist entry %@ has too few elements (%lu, should be 5 or 6).", entryDesc, elemCount); continue; } if (6 < elemCount) { OOLog(@"verifyOXP.equipmentPList.badEntrySize", @"----- WARNING: equipment.plist entry %@ has too many elements (%lu, should be 5 or 6).", entryDesc, elemCount); } /* Check element types. The numbers are required to be unsigned integers; the use of a negative default will catch both negative values and unconvertable values. */ if ([entry oo_longAtIndex:EQUIPMENT_TECH_LEVEL_INDEX defaultValue:-1] < 0) { OOLog(@"verifyOXP.equipmentPList.badElementType", @"***** ERROR: tech level for entry %@ of equipment.plist is not a positive integer.", entryDesc); } if ([entry oo_longAtIndex:EQUIPMENT_PRICE_INDEX defaultValue:-1] < 0) { OOLog(@"verifyOXP.equipmentPList.badElementType", @"***** ERROR: price for entry %@ of equipment.plist is not a positive integer.", entryDesc); } if ([entry oo_stringAtIndex:EQUIPMENT_SHORT_DESC_INDEX] == nil) { OOLog(@"verifyOXP.equipmentPList.badElementType", @"***** ERROR: short description for entry %@ of equipment.plist is not a string.", entryDesc); } if ([entry oo_stringAtIndex:EQUIPMENT_KEY_INDEX] == nil) { OOLog(@"verifyOXP.equipmentPList.badElementType", @"***** ERROR: key for entry %@ of equipment.plist is not a string.", entryDesc); } if ([entry oo_stringAtIndex:EQUIPMENT_LONG_DESC_INDEX] == nil) { OOLog(@"verifyOXP.equipmentPList.badElementType", @"***** ERROR: long description for entry %@ of equipment.plist is not a string.", entryDesc); } if (5 < elemCount) { if ([entry oo_dictionaryAtIndex:EQUIPMENT_EXTRA_INFO_INDEX] == nil) { OOLog(@"verifyOXP.equipmentPList.badElementType", @"***** ERROR: equipment.plist entry %@'s extra information dictionary is not a dictionary.", entryDesc); } // TODO: verify contents of extra info dictionary. } } } @end #endif oolite-1.82/src/Core/OXPVerifier/OOCheckJSSyntaxVerifierStage.h000066400000000000000000000017451256642440500243470ustar00rootroot00000000000000/* OOCheckJSSyntaxVerifierStage.h OOOXPVerifierStage which checks that JS scripts compile Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOFileScannerVerifierStage.h" #if OO_OXP_VERIFIER_ENABLED @interface OOCheckJSSyntaxVerifierStage: OOFileHandlingVerifierStage @end #endif oolite-1.82/src/Core/OXPVerifier/OOCheckJSSyntaxVerifierStage.m000066400000000000000000000041531256642440500243500ustar00rootroot00000000000000/* OOCheckJSSyntaxVerifierStage.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCheckJSSyntaxVerifierStage.h" #if OO_OXP_VERIFIER_ENABLED #import "OOFileScannerVerifierStage.h" #import "OOStringParsing.h" #import "OOScript.h" #import "OOJSScript.h" #import "OOJavaScriptEngine.h" static NSString * const kStageName = @"Checking JS Script file syntax"; @implementation OOCheckJSSyntaxVerifierStage - (NSString *)name { return kStageName; } - (BOOL)shouldRun { OOFileScannerVerifierStage *fileScanner = nil; fileScanner = [[self verifier] fileScannerStage]; return ([[fileScanner filesInFolder:@"Scripts"] count] > 0); } - (void)run { OOFileScannerVerifierStage *fileScanner = nil; NSArray *scriptFiles = nil; NSString *scriptFile = nil; NSString *fileExt = nil; NSString *filePath = nil; fileScanner = [[self verifier] fileScannerStage]; scriptFiles = [fileScanner filesInFolder:@"Scripts"]; if (scriptFiles == nil) return; [[OOJavaScriptEngine sharedEngine] setShowErrorLocations:YES]; foreach (scriptFile, scriptFiles) { fileExt = [[scriptFile pathExtension] lowercaseString]; if ([fileExt isEqualToString:@"js"] || [fileExt isEqualToString:@"es"]) { filePath = [fileScanner pathForFile:scriptFile inFolder:@"Scripts" referencedFrom:nil checkBuiltIn:NO]; OOScript *script = [OOJSScript scriptWithPath:filePath properties:nil]; (void)script; } } } @end #endif oolite-1.82/src/Core/OXPVerifier/OOCheckPListSyntaxVerifierStage.h000066400000000000000000000017631256642440500250660ustar00rootroot00000000000000/* OOCheckPListSyntaxVerifierStage.h OOOXPVerifierStage which checks that plists have correct syntax Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOFileScannerVerifierStage.h" #if OO_OXP_VERIFIER_ENABLED @interface OOCheckPListSyntaxVerifierStage: OOFileHandlingVerifierStage @end #endif oolite-1.82/src/Core/OXPVerifier/OOCheckPListSyntaxVerifierStage.m000066400000000000000000000051141256642440500250650ustar00rootroot00000000000000/* OOCheckPListSyntaxVerifierStage.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCheckPListSyntaxVerifierStage.h" #import "OOCollectionExtractors.h" #if OO_OXP_VERIFIER_ENABLED #import "OOFileScannerVerifierStage.h" static NSString * const kStageName = @"Checking plist well-formedness"; @implementation OOCheckPListSyntaxVerifierStage - (NSString *)name { return kStageName; } - (BOOL)shouldRun { return YES; } - (void)run { OOFileScannerVerifierStage *fileScanner = nil; fileScanner = [[self verifier] fileScannerStage]; NSArray *plists = [[[self verifier] configurationDictionaryForKey:@"knownFiles"] oo_arrayForKey:@"Config"]; NSArray *arrayPlists = [[[self verifier] configurationDictionaryForKey:@"knownFiles"] oo_arrayForKey:@"ConfigArrays"]; NSArray *dictionaryPlists = [[[self verifier] configurationDictionaryForKey:@"knownFiles"] oo_arrayForKey:@"ConfigDictionaries"]; NSString *plistName = nil; foreach (plistName, plists) { if ([fileScanner fileExists:plistName inFolder:@"Config" referencedFrom:nil checkBuiltIn:NO]) { OOLog(@"verifyOXP.syntaxCheck",@"Checking %@",plistName); id retrieve = [fileScanner plistNamed:plistName inFolder:@"Config" referencedFrom:nil checkBuiltIn:NO]; if (retrieve != nil) { if ([retrieve isKindOfClass:[NSArray class]]) { if (![arrayPlists containsObject:plistName]) { OOLog(@"verifyOXP.syntaxCheck.error",@"%@ should be an array but isn't.",plistName); } } else if ([retrieve isKindOfClass:[NSDictionary class]]) { if (![dictionaryPlists containsObject:plistName]) { OOLog(@"verifyOXP.syntaxCheck.error",@"%@ should be an array but isn't.",plistName); } } else { OOLog(@"verifyOXP.syntaxCheck.error",@"%@ is neither an array nor a dictionary.",plistName); } } } } } @end #endif oolite-1.82/src/Core/OXPVerifier/OOCheckRequiresPListVerifierStage.h000066400000000000000000000020621256642440500253700ustar00rootroot00000000000000/* OOCheckRequiresPListVerifierStage.h OOOXPVerifierStage which checks that requires.plist only contains recognized keys and that the version numbers make sense. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOFileScannerVerifierStage.h" #if OO_OXP_VERIFIER_ENABLED @interface OOCheckRequiresPListVerifierStage: OOFileHandlingVerifierStage @end #endif oolite-1.82/src/Core/OXPVerifier/OOCheckRequiresPListVerifierStage.m000066400000000000000000000117351256642440500254040ustar00rootroot00000000000000/* OOCheckRequiresPListVerifierStage.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCheckRequiresPListVerifierStage.h" #if OO_OXP_VERIFIER_ENABLED #import "OOFileScannerVerifierStage.h" #import "OOStringParsing.h" static NSString * const kStageName = @"Checking requires.plist"; @implementation OOCheckRequiresPListVerifierStage - (NSString *)name { return kStageName; } - (BOOL)shouldRun { OOFileScannerVerifierStage *fileScanner = nil; fileScanner = [[self verifier] fileScannerStage]; return [fileScanner fileExists:@"requires.plist" inFolder:@"Config" referencedFrom:nil checkBuiltIn:NO]; } - (void)run { OOFileScannerVerifierStage *fileScanner = nil; NSDictionary *requiresPList = nil; NSSet *knownKeys = nil; NSMutableSet *actualKeys = nil; NSString *version = nil, *maxVersion = nil; NSArray *ooVersionComponents = nil, *versionComponents = nil, *maxVersionComponents = nil; fileScanner = [[self verifier] fileScannerStage]; requiresPList = [fileScanner plistNamed:@"requires.plist" inFolder:@"Config" referencedFrom:nil checkBuiltIn:NO]; if (requiresPList == nil) return; // Check that it's a dictionary if (![requiresPList isKindOfClass:[NSDictionary class]]) { OOLog(@"verifyOXP.requiresPList.notDict", @"***** ERROR: requires.plist is not a dictionary."); return; } // Check that all the keys are known. knownKeys = [[self verifier] configurationSetForKey:@"requiresPListSupportedKeys"]; actualKeys = [NSMutableSet setWithArray:[requiresPList allKeys]]; [actualKeys minusSet:knownKeys]; if ([actualKeys count] != 0) { OOLog(@"verifyOXP.requiresPList.unknownKeys", @"----- WARNING: requires.plist contains unknown keys. This OXP will not be loaded by this version of Oolite. Unknown keys are: %@.", [[actualKeys allObjects] componentsJoinedByString:@", "]); } // Sanity check the known keys. version = [requiresPList objectForKey:@"version"]; if (version != nil) { if (![version isKindOfClass:[NSString class]]) { OOLog(@"verifyOXP.requiresPList.badValue", @"***** ERROR: Value for 'version' is not a string."); version = nil; } } maxVersion = [requiresPList objectForKey:@"max_version"]; if (maxVersion != nil) { if (![maxVersion isKindOfClass:[NSString class]]) { OOLog(@"verifyOXP.requiresPList.badValue", @"***** ERROR: Value for 'max_version' is not a string."); maxVersion = nil; } } if (version != nil || maxVersion != nil) { ooVersionComponents = ComponentsFromVersionString([[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]); if (ooVersionComponents == nil) { OOLog(@"verifyOXP.requiresPList.cantFindOoliteVersion", @"----- WARNING: could not find Oolite's version for requires.plist sanity check."); } if (version != nil) { versionComponents = ComponentsFromVersionString(version); if (versionComponents == nil) { OOLog(@"verifyOXP.requiresPList.badValue", @"***** ERROR: could not interpret version string \"%@\" as version number.", version); } else if (ooVersionComponents != nil) { if (CompareVersions(ooVersionComponents, versionComponents) == NSOrderedAscending) { OOLog(@"verifyOXP.requiresPList.oxpRequiresNewerOolite", @"----- WARNING: this OXP requires a newer version of Oolite (%@) to work.", version); } } } if (maxVersion != nil) { maxVersionComponents = ComponentsFromVersionString(maxVersion); if (maxVersionComponents == nil) { OOLog(@"verifyOXP.requiresPList.badValue", @"***** ERROR: could not interpret max_version string \"%@\" as version number.", maxVersion); } else if (ooVersionComponents != nil) { if (CompareVersions(ooVersionComponents, maxVersionComponents) == NSOrderedDescending) { OOLog(@"verifyOXP.requiresPList.oxpRequiresOlderOolite", @"----- WARNING: this OXP requires an older version of Oolite (%@) to work.", maxVersion); } } } if (versionComponents != nil && maxVersionComponents != nil) { if (CompareVersions(versionComponents, maxVersionComponents) == NSOrderedDescending) { OOLog(@"verifyOXP.requiresPList.noVersionsInRange", @"***** ERROR: this OXP's maximum version (%@) is less than its minimum version (%@).", maxVersion, version); } } } } @end #endif oolite-1.82/src/Core/OXPVerifier/OOCheckShipDataPListVerifierStage.h000066400000000000000000000027751256642440500253010ustar00rootroot00000000000000/* OOCheckShipDataPListVerifierStage.h OOOXPVerifierStage which checks shipdata.plist. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOTextureVerifierStage.h" #if OO_OXP_VERIFIER_ENABLED @class OOPListSchemaVerifier, OOAIStateMachineVerifierStage; @interface OOCheckShipDataPListVerifierStage: OOTextureHandlingStage { @private NSDictionary *_shipdataPList; NSSet *_ooliteShipNames; NSSet *_basicKeys, *_stationKeys, *_playerKeys, *_allKeys; OOPListSchemaVerifier *_schemaVerifier; OOAIStateMachineVerifierStage *_aiVerifierStage; // Info about ship currently being checked. NSString *_name; NSDictionary *_info; NSSet *_roles; uint32_t _isStation: 1, _isPlayer: 1, _isTemplate: 1, _havePrintedMessage: 1; } @end #endif oolite-1.82/src/Core/OXPVerifier/OOCheckShipDataPListVerifierStage.m000066400000000000000000000243151256642440500253000ustar00rootroot00000000000000/* OOCheckShipDataPListVerifierStage.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCheckShipDataPListVerifierStage.h" #import "OOModelVerifierStage.h" #if OO_OXP_VERIFIER_ENABLED #import "OOFileScannerVerifierStage.h" #import "OOStringParsing.h" #import "ResourceManager.h" #import "OOCollectionExtractors.h" #import "OOStringParsing.h" #import "OOPListSchemaVerifier.h" #import "OOAIStateMachineVerifierStage.h" static NSString * const kStageName = @"Checking shipdata.plist"; @interface OOCheckShipDataPListVerifierStage (OOPrivate) - (void)verifyShipInfo:(NSDictionary *)info withName:(NSString *)name; - (void)message:(NSString *)format, ...; - (void)verboseMessage:(NSString *)format, ...; - (void)getRoles; - (void)checkKeys; - (void)checkSchema; - (void)checkModel; - (NSSet *)rolesFromString:(NSString *)string; @end @implementation OOCheckShipDataPListVerifierStage - (NSString *)name { return kStageName; } - (NSSet *)dependents { NSMutableSet *result = [[super dependents] mutableCopy]; [result addObject:[OOModelVerifierStage nameForReverseDependencyForVerifier:[self verifier]]]; [result addObject:[OOAIStateMachineVerifierStage nameForReverseDependencyForVerifier:[self verifier]]]; return [result autorelease]; } - (BOOL)shouldRun { OOFileScannerVerifierStage *fileScanner = nil; fileScanner = [[self verifier] fileScannerStage]; return [fileScanner fileExists:@"shipdata.plist" inFolder:@"Config" referencedFrom:nil checkBuiltIn:NO]; } - (void)run { OOFileScannerVerifierStage *fileScanner = nil; NSAutoreleasePool *pool = nil; NSEnumerator *shipEnum = nil; NSString *shipKey = nil; NSDictionary *shipInfo = nil; NSDictionary *ooliteShipData = nil; NSDictionary *settings = nil; NSMutableSet *mergeSet = nil; NSArray *shipList = nil; fileScanner = [[self verifier] fileScannerStage]; _shipdataPList = [fileScanner plistNamed:@"shipdata.plist" inFolder:@"Config" referencedFrom:nil checkBuiltIn:NO]; if (_shipdataPList == nil) return; // Get AI verifier stage (may be nil). _aiVerifierStage = [[self verifier] stageWithName:[OOAIStateMachineVerifierStage nameForReverseDependencyForVerifier:[self verifier]]]; ooliteShipData = [ResourceManager dictionaryFromFilesNamed:@"shipdata.plist" inFolder:@"Config" andMerge:YES]; // Check that it's a dictionary if (![_shipdataPList isKindOfClass:[NSDictionary class]]) { OOLog(@"verifyOXP.shipdataPList.notDict", @"***** ERROR: shipdata.plist is not a dictionary."); return; } // Keys that apply to all ships _ooliteShipNames = [NSSet setWithArray:[ooliteShipData allKeys]]; settings = [[self verifier] configurationDictionaryForKey:@"shipdataPListSettings"]; _basicKeys = [settings oo_setForKey:@"knownShipKeys"]; // Keys that apply to stations/carriers mergeSet = [_basicKeys mutableCopy]; [mergeSet addObjectsFromArray:[settings oo_arrayForKey:@"knownStationKeys"]]; _stationKeys = mergeSet; // Keys that apply to player ships mergeSet = [_basicKeys mutableCopy]; [mergeSet addObjectsFromArray:[settings oo_arrayForKey:@"knownPlayerKeys"]]; _playerKeys = [[mergeSet copy] autorelease]; // Keys that apply to _any_ ship -- union of the above [mergeSet unionSet:_stationKeys]; _allKeys = mergeSet; _schemaVerifier = [OOPListSchemaVerifier verifierWithSchema:[ResourceManager dictionaryFromFilesNamed:@"shipdataEntrySchema.plist" inFolder:@"Schemata" andMerge:NO]]; [_schemaVerifier setDelegate:self]; shipList = [[_shipdataPList allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; for (shipEnum = [shipList objectEnumerator]; (shipKey = [shipEnum nextObject]); ) { pool = [[NSAutoreleasePool alloc] init]; shipInfo = [_shipdataPList oo_dictionaryForKey:shipKey]; if (shipInfo == nil) { OOLog(@"verifyOXP.shipdata.badType", @"***** ERROR: shipdata.plist entry for \"%@\" is not a dictionary.", shipKey); } else { [self verifyShipInfo:shipInfo withName:shipKey]; } [pool release]; } _shipdataPList = nil; _ooliteShipNames = nil; _basicKeys = nil; _stationKeys = nil; _playerKeys = nil; } @end @implementation OOCheckShipDataPListVerifierStage (OOPrivate) - (void)verifyShipInfo:(NSDictionary *)info withName:(NSString *)name { _name = name; _info = info; _havePrintedMessage = NO; OOLogPushIndent(); [self getRoles]; [self checkKeys]; [self checkSchema]; [self checkModel]; NSString *aiName = [info oo_stringForKey:@"ai_type"]; if (aiName != nil) { if (![aiName hasSuffix:@".js"]) { [_aiVerifierStage stateMachineNamed:aiName usedByShip:name]; } } // Todo: check for pirates with 0 bounty OOLogPopIndent(); if (!_havePrintedMessage) { OOLog(@"verifyOXP.verbose.shipData.OK", @"- ship \"%@\" OK.", _name); } _name = nil; _info = nil; _roles = nil; } // Custom log method to group messages by ship. - (void)message:(NSString *)format, ... { va_list args; if (!_havePrintedMessage) { OOLog(@"verifyOXP.shipData.firstMessage", @"Ship \"%@\":", _name); OOLogIndent(); _havePrintedMessage = YES; } va_start(args, format); OOLogWithFunctionFileAndLineAndArguments(@"verifyOXP.shipData", NULL, NULL, 0, format, args); va_end(args); } - (void)verboseMessage:(NSString *)format, ... { va_list args; if (!OOLogWillDisplayMessagesInClass(@"verifyOXP.verbose.shipData")) return; if (!_havePrintedMessage) { OOLog(@"verifyOXP.shipData.firstMessage", @"Ship \"%@\":", _name); OOLogIndent(); _havePrintedMessage = YES; } va_start(args, format); OOLogWithFunctionFileAndLineAndArguments(@"verifyOXP.verbose.shipData", NULL, NULL, 0, format, args); va_end(args); } - (void)getRoles { NSString *rolesString = nil; rolesString = [_info objectForKey:@"roles"]; _roles = [self rolesFromString:rolesString]; _isPlayer = [_roles containsObject:@"player"]; _isStation = [_info oo_boolForKey:@"is_carrier" defaultValue:NO] || [_info oo_boolForKey:@"isCarrier" defaultValue:NO] || [rolesString rangeOfString:@"station"].location != NSNotFound || [rolesString rangeOfString:@"carrier"].location != NSNotFound; // the is_carrier or isCarrier key will be missed when it was insise a like_ship definition. _isTemplate = [_info oo_boolForKey:@"is_template" defaultValue:NO]; if (_isPlayer && _isStation) { [self message:@"***** ERROR: ship is both a player ship and a station. Treating as non-station."]; _isStation = NO; } } - (void)checkKeys { NSSet *referenceSet = nil; NSEnumerator *keyEnum = nil; NSString *key = nil; if (_isPlayer) referenceSet = _playerKeys; else if (_isStation) referenceSet = _stationKeys; else referenceSet = _basicKeys; for (keyEnum = [_info keyEnumerator]; (key = [keyEnum nextObject]); ) { if (![referenceSet containsObject:key]) { if ([_allKeys containsObject:key]) { if (!_isTemplate) { // if it's a template, this key might apply to a descendant // as happens in the core files [self message:@"----- WARNING: key \"%@\" does not apply to this category of ship.", key]; } } else { [self message:@"----- WARNING: unknown key \"%@\".", key]; } } } } - (void)checkSchema { [_schemaVerifier verifyPropertyList:_info named:_name]; } - (void)checkModel { id model = nil, materials = nil, shaders = nil; model = [_info oo_stringForKey:@"model"]; materials = [_info oo_dictionaryForKey:@"materials"]; shaders = [_info oo_dictionaryForKey:@"shaders"]; if (model != nil) { if (![[[self verifier] modelVerifierStage] modelNamed:model usedForEntry:_name inFile:@"shipdata.plist" withMaterials:materials andShaders:shaders]) { [self message:@"----- WARNING: model \"%@\" could not be found in %@ or in Oolite.", model, [[self verifier] oxpDisplayName]]; } } else { if ([_info oo_stringForKey:@"like_ship"] == nil) { [self message:@"***** ERROR: ship does not specify model or like_ship."]; } } } // Convert a roles string to a set of role names, discarding probabilities. - (NSSet *)rolesFromString:(NSString *)string { NSArray *parts = nil; NSMutableSet *result = nil; NSUInteger i, count; NSString *role = nil; NSRange parenRange; if (string == nil) return [NSSet set]; parts = ScanTokensFromString(string); count = [parts count]; if (count == 0) return [NSSet set]; result = [NSMutableSet setWithCapacity:count]; for (i = 0; i != count; ++i) { role = [parts objectAtIndex:i]; parenRange = [role rangeOfString:@"("]; if (parenRange.location != NSNotFound) { role = [role substringToIndex:parenRange.location]; } [result addObject:role]; } return result; } - (BOOL)verifier:(OOPListSchemaVerifier *)verifier withPropertyList:(id)rootPList named:(NSString *)name testProperty:(id)subPList atPath:(NSArray *)keyPath againstType:(NSString *)typeKey error:(NSError **)outError { [self verboseMessage:@"- Skipping verification for type %@ at %@.%@.", typeKey, _name, [OOPListSchemaVerifier descriptionForKeyPath:keyPath]]; return YES; } - (BOOL)verifier:(OOPListSchemaVerifier *)verifier withPropertyList:(id)rootPList named:(NSString *)name failedForProperty:(id)subPList withError:(NSError *)error expectedType:(NSDictionary *)localSchema { // FIXME: use fancy new error codes to provide useful error descriptions. [self message:@"***** ERROR: verification of ship \"%@\" failed at \"%@\": %@", name, [error plistKeyPathDescription], [error localizedFailureReason]]; return YES; } @end #endif oolite-1.82/src/Core/OXPVerifier/OOFileScannerVerifierStage.h000066400000000000000000000074401256642440500240550ustar00rootroot00000000000000/* OOFileScannerVerifierStage.h OOOXPVerifierStage which keeps track of which files are used and ensures file name capitalization is consistent. It also provides the file lookup service for other stages. Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOOXPVerifierStage.h" #if OO_OXP_VERIFIER_ENABLED @interface OOFileScannerVerifierStage: OOOXPVerifierStage { @private NSString *_basePath; NSMutableSet *_usedFiles; NSMutableSet *_caseWarnings; NSDictionary *_directoryListings; NSDictionary *_directoryCases; NSMutableSet *_badPLists; NSSet *_junkFileNames; NSSet *_skipDirectoryNames; } // Returns name to be used in -dependencies by other stages; also registers stage. + (NSString *)nameForDependencyForVerifier:(OOOXPVerifier *)verifier; /* This method does the following: A. Checks whether a file exists. B. Checks whether case matches, and logs a warning otherwise. C. Maintains list of files which are referred to. D. Optionally falls back on Oolite's built-in files. For example, to test whether a texture referenced in a shipdata.plist entry exists, one would use: [fileScanner fileExists:textureName inFolder:@"Textures" referencedFrom:@"shipdata.plist" checkBuiltIn:YES]; */ - (BOOL)fileExists:(NSString *)file inFolder:(NSString *)folder referencedFrom:(NSString *)context checkBuiltIn:(BOOL)checkBuiltIn; // This method performs all the checks the previous one does, but also returns a file path. - (NSString *)pathForFile:(NSString *)file inFolder:(NSString *)folder referencedFrom:(NSString *)context checkBuiltIn:(BOOL)checkBuiltIn; // Data getters based on above method. - (NSData *)dataForFile:(NSString *)file inFolder:(NSString *)folder referencedFrom:(NSString *)context checkBuiltIn:(BOOL)checkBuiltIn; - (id)plistNamed:(NSString *)file // Only uses "real" plist parser, not homebrew. inFolder:(NSString *)folder referencedFrom:(NSString *)context checkBuiltIn:(BOOL)checkBuiltIn; /* Utility to handle display names of files. If a file and folder are provided, returns folder/file, otherwise just file. */ - (id)displayNameForFile:(NSString *)file andFolder:(NSString *)folder; /* Get a list of files in a subfolder of the OXP. Order is undefined. */ - (NSArray *)filesInFolder:(NSString *)folder; @end @interface OOListUnusedFilesStage: OOOXPVerifierStage // Returns name to be used in -dependents by other stages; also registers stage. + (NSString *)nameForReverseDependencyForVerifier:(OOOXPVerifier *)verifier; @end @interface OOOXPVerifier(OOFileScannerVerifierStage) - (OOFileScannerVerifierStage *)fileScannerStage; @end // Convenience base class for stages that require OOFileScannerVerifierStage and OOListUnusedFilesStage. @interface OOFileHandlingVerifierStage: OOOXPVerifierStage @end #endif oolite-1.82/src/Core/OXPVerifier/OOFileScannerVerifierStage.m000066400000000000000000000536651256642440500240740ustar00rootroot00000000000000/* OOFileScannerVerifierStage.m Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* Design notes: In order to be able to look files up case-insenstively, but warn about case mismatches, the OOFileScannerVerifierStage builds its own representation of the file hierarchy. Dictionaries are used heavily: the _directoryListings is keyed by folder names mapped to lower case, and its entries map lowercase file names to actual case, that is, the case found in the file system. The companion dictionary _directoryCases maps lowercase directory names to actual case. The class design is based on the knowledge that Oolite uses a two-level namespace for files. Each file type has an appropriate folder, and files may either be in the appropriate folder or "bare". For instance, a texture file in an OXP may be either in the Textures subdirectory or in the root directory of the OXP. The root directory's contents are listed in _directoryListings with the empty string as key. This architecture means the OOFileScannerVerifierStage doesn't need to take full file system hierarchy into account. */ #import "OOFileScannerVerifierStage.h" #if OO_OXP_VERIFIER_ENABLED #import "OOCollectionExtractors.h" #import "ResourceManager.h" static NSString * const kFileScannerStageName = @"Scanning files"; static NSString * const kUnusedListerStageName = @"Checking for unused files"; static BOOL CheckNameConflict(NSString *lcName, NSDictionary *directoryCases, NSDictionary *rootFiles, NSString **outExisting, NSString **outExistingType); @interface OOFileScannerVerifierStage (OOPrivate) - (void)scanForFiles; - (void)checkRootFolders; - (void)checkKnownFiles; /* Given an array of strings, return a dictionary mapping lowercase strings to the canonicial case given in the array. For instance, given (Foo, BAR) it will return { foo = Foo; bar = BAR } */ - (NSDictionary *)lowercaseMap:(NSArray *)array; - (NSDictionary *)scanDirectory:(NSString *)path; - (void)checkPListFormat:(NSPropertyListFormat)format file:(NSString *)file folder:(NSString *)folder; - (NSSet *)constructReadMeNames; @end @implementation OOFileScannerVerifierStage - (void)dealloc { [_basePath release]; [_usedFiles release]; [_caseWarnings release]; [_directoryListings release]; [_directoryCases release]; [_badPLists release]; [super dealloc]; } - (NSString *)name { return kFileScannerStageName; } - (void)run { NSAutoreleasePool *pool = nil; _usedFiles = [[NSMutableSet alloc] init]; _caseWarnings = [[NSMutableSet alloc] init]; _badPLists = [[NSMutableSet alloc] init]; pool = [[NSAutoreleasePool alloc] init]; [self scanForFiles]; [pool release]; pool = [[NSAutoreleasePool alloc] init]; [self checkRootFolders]; [self checkKnownFiles]; [pool release]; } + (NSString *)nameForDependencyForVerifier:(OOOXPVerifier *)verifier { OOFileScannerVerifierStage *stage = [verifier stageWithName:kFileScannerStageName]; if (stage == nil) { stage = [[OOFileScannerVerifierStage alloc] init]; [verifier registerStage:stage]; [stage release]; } return kFileScannerStageName; } - (BOOL)fileExists:(NSString *)file inFolder:(NSString *)folder referencedFrom:(NSString *)context checkBuiltIn:(BOOL)checkBuiltIn { return [self pathForFile:file inFolder:folder referencedFrom:context checkBuiltIn:checkBuiltIn] != nil; } - (NSString *)pathForFile:(NSString *)file inFolder:(NSString *)folder referencedFrom:(NSString *)context checkBuiltIn:(BOOL)checkBuiltIn { NSString *lcName = nil, *lcDirName = nil, *realDirName = nil, *realFileName = nil, *path = nil, *expectedPath = nil; if (file == nil) return nil; lcName = [file lowercaseString]; if (folder != nil) { lcDirName = [folder lowercaseString]; realFileName = [[_directoryListings oo_dictionaryForKey:lcDirName] objectForKey:lcName]; if (realFileName != nil) { realDirName = [_directoryCases objectForKey:lcDirName]; path = [realDirName stringByAppendingPathComponent:realFileName]; } } if (path == nil) { realFileName = [[_directoryListings oo_dictionaryForKey:@""] objectForKey:lcName]; if (realFileName != nil) { path = realFileName; } } if (path != nil) { [_usedFiles addObject:path]; if (realDirName != nil && ![realDirName isEqual:folder]) { // Case mismatch for folder name if (![_caseWarnings containsObject:lcDirName]) { [_caseWarnings addObject:lcDirName]; OOLog(@"verifyOXP.files.caseMismatch", @"***** ERROR: case mismatch: directory '%@' should be called '%@'.", realDirName, folder); } } if (![realFileName isEqual:file]) { // Case mismatch for file name if (![_caseWarnings containsObject:lcName]) { [_caseWarnings addObject:lcName]; expectedPath = [self displayNameForFile:file andFolder:folder]; if (context != nil) context = [@" referenced in " stringByAppendingString:context]; else context = @""; OOLog(@"verifyOXP.files.caseMismatch", @"***** ERROR: case mismatch: request for file '%@'%@ resolved to '%@'.", expectedPath, context, path); } } return [_basePath stringByAppendingPathComponent:path]; } // If we get here, the file wasn't found in the OXP. // FIXME: should check case for built-in files. if (checkBuiltIn) return [ResourceManager pathForFileNamed:file inFolder:folder]; return nil; } - (NSData *)dataForFile:(NSString *)file inFolder:(NSString *)folder referencedFrom:(NSString *)context checkBuiltIn:(BOOL)checkBuiltIn { NSString *path = nil; path = [self pathForFile:file inFolder:folder referencedFrom:context checkBuiltIn:checkBuiltIn]; if (path == nil) return nil; return [NSData dataWithContentsOfMappedFile:path]; } - (id)plistNamed:(NSString *)file inFolder:(NSString *)folder referencedFrom:(NSString *)context checkBuiltIn:(BOOL)checkBuiltIn { NSData *data = nil; NSString *errorString = nil; NSPropertyListFormat format; id plist = nil; NSArray *errorLines = nil; NSEnumerator *errLineEnum = nil; NSString *displayName = nil, *errorKey = nil; NSAutoreleasePool *pool = nil; data = [self dataForFile:file inFolder:folder referencedFrom:context checkBuiltIn:checkBuiltIn]; if (data == nil) return nil; pool = [[NSAutoreleasePool alloc] init]; plist = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&errorString]; #if OOLITE_RELEASE_PLIST_ERROR_STRINGS [errorString autorelease]; #endif if (plist != nil) { // PList is readable; check that it's in an official Oolite format. [self checkPListFormat:format file:file folder:folder]; } else { /* Couldn't parse plist; report problem. This is complicated somewhat by the need to present a possibly multi-line error description while maintaining our indentation. */ displayName = [self displayNameForFile:file andFolder:folder]; errorKey = [displayName lowercaseString]; if (![_badPLists containsObject:errorKey]) { [_badPLists addObject:errorKey]; OOLog(@"verifyOXP.plist.parseError", @"Could not interpret property list %@.", displayName); OOLogIndent(); errorLines = [errorString componentsSeparatedByString:@"\n"]; for (errLineEnum = [errorLines objectEnumerator]; (errorString = [errLineEnum nextObject]); ) { while ([errorString hasPrefix:@"\t"]) { errorString = [@" " stringByAppendingString:[errorString substringFromIndex:1]]; } OOLog(@"verifyOXP.plist.parseError", @"%@", errorString); } OOLogOutdent(); } } [plist retain]; [pool release]; return [plist autorelease]; } - (id)displayNameForFile:(NSString *)file andFolder:(NSString *)folder { if (file != nil && folder != nil) return [folder stringByAppendingPathComponent:file]; return file; } - (NSArray *)filesInFolder:(NSString *)folder { if (folder == nil) return nil; return [[_directoryListings objectForKey:[folder lowercaseString]] allValues]; } @end @implementation OOFileScannerVerifierStage (OOPrivate) - (void)scanForFiles { NSDirectoryEnumerator *dirEnum = nil; NSString *name = nil, *path = nil, *type = nil, *lcName = nil, *existing = nil, *existingType = nil; NSMutableDictionary *directoryListings = nil, *directoryCases = nil, *rootFiles = nil; NSDictionary *dirFiles = nil; NSSet *readMeNames = nil; _basePath = [[[self verifier] oxpPath] copy]; _junkFileNames = [[self verifier] configurationSetForKey:@"junkFiles"]; _skipDirectoryNames = [[self verifier] configurationSetForKey:@"skipDirectories"]; directoryCases = [NSMutableDictionary dictionary]; directoryListings = [NSMutableDictionary dictionary]; rootFiles = [NSMutableDictionary dictionary]; readMeNames = [self constructReadMeNames]; dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:_basePath]; while ((name = [dirEnum nextObject])) { path = [_basePath stringByAppendingPathComponent:name]; type = [[dirEnum fileAttributes] fileType]; lcName = [name lowercaseString]; if ([type isEqualToString:NSFileTypeDirectory]) { [dirEnum skipDescendents]; if ([_skipDirectoryNames containsObject:name]) { // Silently skip .svn and CVS OOLog(@"verifyOXP.verbose.listFiles", @"- Skipping %@/", name); } else if (!CheckNameConflict(lcName, directoryCases, rootFiles, &existing, &existingType)) { OOLog(@"verifyOXP.verbose.listFiles", @"- %@/", name); OOLogIndentIf(@"verifyOXP.verbose.listFiles"); dirFiles = [self scanDirectory:path]; [directoryListings setObject:dirFiles forKey:lcName]; [directoryCases setObject:name forKey:lcName]; OOLogOutdentIf(@"verifyOXP.verbose.listFiles"); } else { OOLog(@"verifyOXP.scanFiles.overloadedName", @"***** ERROR: %@ '%@' conflicts with %@ named '%@', ignoring. (OXPs must work on case-insensitive file systems!)", @"directory", name, existingType, existing); } } else if ([type isEqualToString:NSFileTypeRegular]) { if ([_junkFileNames containsObject:name]) { OOLog(@"verifyOXP.scanFiles.skipJunk", @"NOTE: skipping junk file %@.", name); } else if ([readMeNames containsObject:lcName]) { OOLog(@"verifyOXP.scanFiles.readMe", @"----- WARNING: apparent Read Me file (\"%@\") inside OXP. This is the wrong place for a Read Me file, because it will not be read.", name); } else if (!CheckNameConflict(lcName, directoryCases, rootFiles, &existing, &existingType)) { OOLog(@"verifyOXP.verbose.listFiles", @"- %@", name); [rootFiles setObject:name forKey:lcName]; } else { OOLog(@"verifyOXP.scanFiles.overloadedName", @"***** ERROR: %@ '%@' conflicts with %@ named '%@', ignoring. (OXPs must work on case-insensitive file systems!)", @"file", name, existingType, existing); } } else if ([type isEqualToString:NSFileTypeSymbolicLink]) { OOLog(@"verifyOXP.scanFiles.symLink", @"----- WARNING: \"%@\" is a symbolic link, ignoring.", name); } else { OOLog(@"verifyOXP.scanFiles.nonStandardFile", @"----- WARNING: \"%@\" is a non-standard file (%@), ignoring.", name, type); } } _junkFileNames = nil; _skipDirectoryNames = nil; [directoryListings setObject:rootFiles forKey:@""]; _directoryListings = [directoryListings copy]; _directoryCases = [directoryCases copy]; } - (void)checkRootFolders { NSArray *knownNames = nil; NSEnumerator *nameEnum = nil; NSString *name = nil; NSString *lcName = nil; NSString *actual = nil; knownNames = [[self verifier] configurationArrayForKey:@"knownRootDirectories"]; for (nameEnum = [knownNames objectEnumerator]; (name = [nameEnum nextObject]); ) { if (![name isKindOfClass:[NSString class]]) continue; lcName = [name lowercaseString]; actual = [_directoryCases objectForKey:lcName]; if (actual == nil) continue; if (![actual isEqualToString:name]) { OOLog(@"verifyOXP.files.caseMismatch", @"***** ERROR: case mismatch: directory '%@' should be called '%@'.", actual, name); } [_caseWarnings addObject:lcName]; } } - (void)checkConfigFiles { NSArray *knownNames = nil; NSEnumerator *nameEnum = nil; NSString *name = nil, *lcName = nil, *realFileName = nil; BOOL inConfigDir; knownNames = [[self verifier] configurationArrayForKey:@"knownConfigFiles"]; for (nameEnum = [knownNames objectEnumerator]; (name = [nameEnum nextObject]); ) { if (![name isKindOfClass:[NSString class]]) continue; /* In theory, we could use -fileExists:inFolder:referencedFrom:checkBuiltIn: here, but we want a different error message. */ lcName = [name lowercaseString]; realFileName = [[_directoryListings oo_dictionaryForKey:@"config"] objectForKey:lcName]; inConfigDir = realFileName != nil; if (!inConfigDir) realFileName = [[_directoryListings oo_dictionaryForKey:@""] objectForKey:lcName]; if (realFileName == nil) continue; if (![realFileName isEqualToString:name]) { if (inConfigDir) realFileName = [@"Config" stringByAppendingPathComponent:realFileName]; OOLog(@"verifyOXP.files.caseMismatch", @"***** ERROR: case mismatch: configuration file '%@' should be called '%@'.", realFileName, name); } } } - (void)checkKnownFiles { NSDictionary *directories = nil; NSEnumerator *directoryEnum = nil; NSString *directory = nil, *lcDirectory = nil; NSArray *fileList = nil; NSEnumerator *nameEnum = nil; NSString *name = nil, *lcName = nil, *realFileName = nil; BOOL inDirectory; directories = [[self verifier] configurationDictionaryForKey:@"knownFiles"]; for (directoryEnum = [directories keyEnumerator]; (directory = [directoryEnum nextObject]); ) { fileList = [directories objectForKey:directory]; lcDirectory = [directory lowercaseString]; for (nameEnum = [fileList objectEnumerator]; (name = [nameEnum nextObject]); ) { if (![name isKindOfClass:[NSString class]]) continue; /* In theory, we could use -fileExists:inFolder:referencedFrom:checkBuiltIn: here, but we want a different error message. */ lcName = [name lowercaseString]; realFileName = [[_directoryListings oo_dictionaryForKey:lcDirectory] objectForKey:lcName]; inDirectory = (realFileName != nil); if (!inDirectory) { // Allow for files in root directory of OXP realFileName = [[_directoryListings oo_dictionaryForKey:@""] objectForKey:lcName]; } if (realFileName == nil) continue; if (![realFileName isEqualToString:name]) { if (inDirectory) realFileName = [directory stringByAppendingPathComponent:realFileName]; OOLog(@"verifyOXP.files.caseMismatch", @"***** ERROR: case mismatch: file '%@' should be called '%@'.", realFileName, name); } } } } - (NSDictionary *)lowercaseMap:(NSArray *)array { NSUInteger i, count; NSString *canonical = nil, *lowercase = nil; NSMutableDictionary *result = nil; count = [array count]; if (count == 0) return [NSDictionary dictionary]; result = [NSMutableDictionary dictionaryWithCapacity:count]; for (i = 0; i != count; ++i) { canonical = [array oo_stringAtIndex:i]; if (canonical != nil) { lowercase = [canonical lowercaseString]; [result setObject:canonical forKey:lowercase]; } } return result; } - (NSDictionary *)scanDirectory:(NSString *)path { NSDirectoryEnumerator *dirEnum = nil; NSMutableDictionary *result = nil; NSString *name = nil, *lcName = nil, *type = nil, *dirName = nil, *relativeName = nil, *existing = nil; result = [NSMutableDictionary dictionary]; dirName = [path lastPathComponent]; dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:path]; while ((name = [dirEnum nextObject])) { type = [[dirEnum fileAttributes] fileType]; relativeName = [dirName stringByAppendingPathComponent:name]; if ([_junkFileNames containsObject:name]) { OOLog(@"verifyOXP.scanFiles.skipJunk", @"NOTE: skipping junk file %@/%@.", dirName, name); } else if ([type isEqualToString:NSFileTypeRegular]) { lcName = [name lowercaseString]; existing = [result objectForKey:lcName]; if (existing == nil) { OOLog(@"verifyOXP.verbose.listFiles", @"- %@", name); [result setObject:name forKey:lcName]; } else { OOLog(@"verifyOXP.scanFiles.overloadedName", @"***** ERROR: %@ '%@' conflicts with %@ named '%@', ignoring. (OXPs must work on case-insensitive file systems!)", @"file", relativeName, @"file", [dirName stringByAppendingPathComponent:existing]); } } else { if ([type isEqualToString:NSFileTypeDirectory]) { [dirEnum skipDescendents]; if (![_skipDirectoryNames containsObject:name]) { OOLog(@"verifyOXP.scanFiles.directory", @"----- WARNING: \"%@\" is a nested directory, ignoring.", relativeName); } else { OOLog(@"verifyOXP.verbose.listFiles", @"- Skipping %@/%@/", dirName, name); } } else if ([type isEqualToString:NSFileTypeSymbolicLink]) { OOLog(@"verifyOXP.scanFiles.symLink", @"----- WARNING: \"%@\" is a symbolic link, ignoring.", relativeName); } else { OOLog(@"verifyOXP.scanFiles.nonStandardFile", @"----- WARNING: \"%@\" is a non-standard file (%@), ignoring.", relativeName, type); } } } return result; } - (void)checkPListFormat:(NSPropertyListFormat)format file:(NSString *)file folder:(NSString *)folder { NSString *weirdnessKey = nil; NSString *formatDesc = nil; NSString *displayPath = nil; if (format != NSPropertyListOpenStepFormat && format != NSPropertyListXMLFormat_v1_0) { displayPath = [self displayNameForFile:file andFolder:folder]; weirdnessKey = [displayPath lowercaseString]; if (![_badPLists containsObject:weirdnessKey]) { // Warn about "non-standard" format [_badPLists addObject:weirdnessKey]; switch (format) { case NSPropertyListBinaryFormat_v1_0: formatDesc = @"Apple binary format"; break; #if OOLITE_GNUSTEP case NSPropertyListGNUstepFormat: formatDesc = @"GNUstep text format"; break; case NSPropertyListGNUstepBinaryFormat: formatDesc = @"GNUstep binary format"; break; #endif default: formatDesc = [NSString stringWithFormat:@"unknown format (%i)", (int)format]; } OOLog(@"verifyOXP.plist.weirdFormat", @"----- WARNING: Property list %@ is in %@; OpenStep text format and XML format are the recommended formats for Oolite.", displayPath, formatDesc); } } } - (NSSet *)constructReadMeNames { NSDictionary *dict = nil; NSArray *stems = nil, *extensions = nil; NSMutableSet *result = nil; NSUInteger i, j, stemCount, extCount; NSString *stem = nil, *extension = nil; dict = [[self verifier] configurationDictionaryForKey:@"readMeNames"]; stems = [dict oo_arrayForKey:@"stems"]; extensions = [dict oo_arrayForKey:@"extensions"]; stemCount = [stems count]; extCount = [extensions count]; if (stemCount * extCount == 0) return nil; // Construct all stem+extension permutations result = [NSMutableSet setWithCapacity:stemCount * extCount]; for (i = 0; i != stemCount; ++i) { stem = [[stems oo_stringAtIndex:i] lowercaseString]; if (stem != nil) { for (j = 0; j != extCount; ++j) { extension = [[extensions oo_stringAtIndex:j] lowercaseString]; if (extension != nil) { [result addObject:[stem stringByAppendingString:extension]]; } } } } return result; } @end @implementation OOListUnusedFilesStage: OOOXPVerifierStage - (NSString *)name { return kUnusedListerStageName; } - (NSSet *)dependencies { return [NSSet setWithObject:kFileScannerStageName]; } - (void)run { OOLog(@"verifyOXP.unusedFiles.unimplemented", @"TODO: implement unused files check."); } + (NSString *)nameForReverseDependencyForVerifier:(OOOXPVerifier *)verifier { OOListUnusedFilesStage *stage = [verifier stageWithName:kUnusedListerStageName]; if (stage == nil) { stage = [[OOListUnusedFilesStage alloc] init]; [verifier registerStage:stage]; [stage release]; } return kUnusedListerStageName; } @end @implementation OOOXPVerifier(OOFileScannerVerifierStage) - (OOFileScannerVerifierStage *)fileScannerStage { return [self stageWithName:kFileScannerStageName]; } @end @implementation OOFileHandlingVerifierStage - (NSSet *)dependencies { return [NSSet setWithObject:[OOFileScannerVerifierStage nameForDependencyForVerifier:[self verifier]]]; } - (NSSet *)dependents { return [NSSet setWithObject:[OOListUnusedFilesStage nameForReverseDependencyForVerifier:[self verifier]]]; } @end static BOOL CheckNameConflict(NSString *lcName, NSDictionary *directoryCases, NSDictionary *rootFiles, NSString **outExisting, NSString **outExistingType) { NSString *existing = nil; existing = [directoryCases objectForKey:lcName]; if (existing != nil) { if (outExisting != NULL) *outExisting = existing; if (outExistingType != NULL) *outExistingType = @"directory"; return YES; } existing = [rootFiles objectForKey:lcName]; if (existing != nil) { if (outExisting != NULL) *outExisting = existing; if (outExistingType != NULL) *outExistingType = @"file"; return YES; } return NO; } #endif oolite-1.82/src/Core/OXPVerifier/OOModelVerifierStage.h000066400000000000000000000032221256642440500227160ustar00rootroot00000000000000/* OOModelVerifierStage.h OOOXPVerifierStage which keeps track of models that are used and ensures they are loadable. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOTextureVerifierStage.h" #if OO_OXP_VERIFIER_ENABLED @interface OOModelVerifierStage: OOTextureHandlingStage { @private NSMutableSet *_modelsToCheck; } // Returns name to be used in -dependents by other stages; also registers stage. + (NSString *)nameForReverseDependencyForVerifier:(OOOXPVerifier *)verifier; /* This can be called by other stages *before* the model stage runs. returns YES if the model is found, NO if it is not. Caller is responsible for complaining if it is not. */ - (BOOL)modelNamed:(NSString *)name usedForEntry:(NSString *)entryName inFile:(NSString *)fileName withMaterials:(NSDictionary *)materials andShaders:(NSDictionary *)shaders; @end @interface OOOXPVerifier(OOModelVerifierStage) - (OOModelVerifierStage *)modelVerifierStage; @end #endif oolite-1.82/src/Core/OXPVerifier/OOModelVerifierStage.m000066400000000000000000000102561256642440500227300ustar00rootroot00000000000000/* OOModelVerifierStage.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOModelVerifierStage.h" #if OO_OXP_VERIFIER_ENABLED #import "OOFileScannerVerifierStage.h" static NSString * const kStageName = @"Testing models"; static id NSNULL = nil; @interface OOModelVerifierStage (OOPrivate) - (void)checkModel:(NSString *)name context:(NSString *)context materials:(NSDictionary *)materials shaders:(NSDictionary *)shaders; @end @implementation OOModelVerifierStage - (id)init { self = [super init]; if (self != nil) { NSNULL = [[NSNull null] retain]; _modelsToCheck = [[NSMutableSet alloc] init]; } return self; } - (void)dealloc { [_modelsToCheck release]; [super dealloc]; } + (NSString *)nameForReverseDependencyForVerifier:(OOOXPVerifier *)verifier { OOModelVerifierStage *stage = [verifier stageWithName:kStageName]; if (stage == nil) { stage = [[OOModelVerifierStage alloc] init]; [verifier registerStage:stage]; [stage release]; } return kStageName; } - (NSString *)name { return kStageName; } - (BOOL)shouldRun { return [_modelsToCheck count] != 0; } - (void)run { NSEnumerator *nameEnum = nil; NSDictionary *info = nil; NSAutoreleasePool *pool = nil; NSString *name = nil, *context = nil; NSDictionary *materials = nil, *shaders = nil; OOLog(@"verifyOXP.models.unimplemented", @"TODO: implement model verifier."); for (nameEnum = [_modelsToCheck objectEnumerator]; (info = [nameEnum nextObject]); ) { pool = [[NSAutoreleasePool alloc] init]; name = [info objectForKey:@"name"]; context = [info objectForKey:@"context"]; if (context == NSNULL) context = nil; materials = [info objectForKey:@"materials"]; if (materials == NSNULL) materials = nil; shaders = [info objectForKey:@"shaders"]; if (shaders == NSNULL) shaders = nil; [self checkModel:name context:context materials:materials shaders:shaders]; [pool release]; } [_modelsToCheck release]; _modelsToCheck = nil; } - (BOOL) modelNamed:(NSString *)name usedForEntry:(NSString *)entryName inFile:(NSString *)fileName withMaterials:(NSDictionary *)materials andShaders:(NSDictionary *)shaders { OOFileScannerVerifierStage *fileScanner = nil; NSDictionary *info = nil; NSString *context = nil; if (name == nil) return NO; if (entryName != nil) context = [NSString stringWithFormat:@"entry \"%@\" of %@", entryName, fileName]; else context = fileName; fileScanner = [[self verifier] fileScannerStage]; if (![fileScanner fileExists:name inFolder:@"Models" referencedFrom:context checkBuiltIn:YES]) { return NO; } if (context == nil) context = NSNULL; if (materials == nil) materials = NSNULL; if (shaders == nil) shaders = NSNULL; info = [NSDictionary dictionaryWithObjectsAndKeys: name, @"name", context, @"context", materials, @"materials", shaders, @"shaders", nil]; [_modelsToCheck addObject:info]; return YES; } @end @implementation OOModelVerifierStage (OOPrivate) - (void)checkModel:(NSString *)name context:(NSString *)context materials:(NSDictionary *)materials shaders:(NSDictionary *)shaders { OOLog(@"verifyOXP.verbose.model.unimp", @"- Pretending to verify model %@ referenced in %@.", name, context); // FIXME: this should check DAT files. } @end @implementation OOOXPVerifier(OOModelVerifierStage) - (OOModelVerifierStage *)modelVerifierStage { return [self stageWithName:kStageName]; } @end #endif oolite-1.82/src/Core/OXPVerifier/OOOXPVerifier.h000066400000000000000000000053111256642440500213410ustar00rootroot00000000000000/* OOOXPVerifier.h Oolite expansion pack verification manager. NOTE: the overall design is discussed in OXP verifier design.txt. Copyright (C) 2007-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef OO_OXP_VERIFIER_ENABLED #ifdef NDEBUG #define OO_OXP_VERIFIER_ENABLED 0 #else #define OO_OXP_VERIFIER_ENABLED 1 #endif #endif #if OO_OXP_VERIFIER_ENABLED #import "OOCocoa.h" @class OOOXPVerifierStage; @interface OOOXPVerifier: NSObject { @private NSDictionary *_verifierPList; NSString *_basePath; NSString *_displayName; NSMutableDictionary *_stagesByName; NSMutableSet *_waitingStages; BOOL _openForRegistration; } /* Look for command-line arguments requesting OXP verification. If any are found, run the verification and return YES. Otherwise, return NO. At the moment, only one OXP may be verified per run; additional requests are ignored. */ + (BOOL)runVerificationIfRequested; /* Stage registration. Currently, stages are registered by OOOXPVerifier itself. Stages may also register other stages - substages, as it were - in their -initWithVerifier: methods, or when -dependencies or -dependents are called. Registration at later points is not permitted. */ - (void)registerStage:(OOOXPVerifierStage *)stage; // All other methods are for use by verifier stages. - (NSString *)oxpPath; - (NSString *)oxpDisplayName; - (id)stageWithName:(NSString *)name; // Read from verifyOXP.plist - (id)configurationValueForKey:(NSString *)key; - (NSArray *)configurationArrayForKey:(NSString *)key; - (NSDictionary *)configurationDictionaryForKey:(NSString *)key; - (NSString *)configurationStringForKey:(NSString *)key; - (NSSet *)configurationSetForKey:(NSString *)key; @end #endif oolite-1.82/src/Core/OXPVerifier/OOOXPVerifier.m000066400000000000000000000507621256642440500213600ustar00rootroot00000000000000/* OOOXPVerifier.m Copyright (C) 2007-2013 Jens Ayton and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* Design notes: see "verifier design.txt". */ #import "OOOXPVerifier.h" #if OO_OXP_VERIFIER_ENABLED #import "OOOXPVerifierStageInternal.h" #import "OOLoggingExtended.h" #import "ResourceManager.h" #import "OOCollectionExtractors.h" #import "GameController.h" #import "OOCacheManager.h" #import "OODebugStandards.h" static void SwitchLogFile(NSString *name); static void NoteVerificationStage(NSString *displayName, NSString *stage); static void OpenLogFile(NSString *name); @interface OOOXPVerifier (OOPrivate) - (id)initWithPath:(NSString *)path; - (void)run; - (void)setUpLogOverrides; - (void)registerBaseStages; - (void)buildDependencyGraph; - (void)runStages; - (BOOL)setUpDependencies:(NSSet *)dependencies forStage:(OOOXPVerifierStage *)stage; - (void)setUpDependents:(NSSet *)dependents forStage:(OOOXPVerifierStage *)stage; - (void)dumpDebugGraphviz; @end @implementation OOOXPVerifier + (BOOL)runVerificationIfRequested { NSArray *arguments = nil; NSEnumerator *argEnum = nil; NSString *arg = nil; NSString *foundPath = nil; BOOL exists, isDirectory; OOOXPVerifier *verifier = nil; NSAutoreleasePool *pool = nil; pool = [[NSAutoreleasePool alloc] init]; arguments = [[NSProcessInfo processInfo] arguments]; // Scan for -verify-oxp or --verify-oxp followed by relative path for (argEnum = [arguments objectEnumerator]; (arg = [argEnum nextObject]); ) { if ([arg isEqual:@"-verify-oxp"] || [arg isEqual:@"--verify-oxp"]) { foundPath = [argEnum nextObject]; if (foundPath == nil) { OOLog(@"verifyOXP.noPath", @"***** ERROR: %@ passed without path argument; nothing to verify.", arg); [pool release]; return YES; } foundPath = [foundPath stringByExpandingTildeInPath]; break; } } if (foundPath == nil) { [pool release]; return NO; } // We got a path; does it point to a directory? exists = [[NSFileManager defaultManager] fileExistsAtPath:foundPath isDirectory:&isDirectory]; if (!exists) { OOLog(@"verifyOXP.badPath", @"***** ERROR: no OXP exists at path \"%@\"; nothing to verify.", foundPath); } else if (!isDirectory) { OOLog(@"verifyOXP.badPath", @"***** ERROR: \"%@\" is a file, not an OXP directory; nothing to verify.", foundPath); } else { verifier = [[OOOXPVerifier alloc] initWithPath:foundPath]; [pool release]; pool = [[NSAutoreleasePool alloc] init]; [verifier run]; [verifier release]; } [pool release]; // Whether or not we got a valid path, -verify-oxp was passed. return YES; } - (void)dealloc { [_verifierPList release]; [_basePath release]; [_displayName release]; [_stagesByName release]; [_waitingStages release]; [super dealloc]; } - (void)registerStage:(OOOXPVerifierStage *)stage { NSString *name = nil; OOOXPVerifierStage *existing = nil; // Sanity checking if (stage == nil) return; if (![stage isKindOfClass:[OOOXPVerifierStage class]]) { OOLog(@"verifyOXP.registration.failed", @"Attempt to register class %@ as a verifier stage, but it is not a subclass of OOOXPVerifierStage; ignoring.", [stage class]); return; } if (!_openForRegistration) { OOLog(@"verifyOXP.registration.failed", @"Attempt to register verifier stage %@ after registration closed, ignoring.", stage); return; } name = [stage name]; if (name == nil) { OOLog(@"verifyOXP.registration.failed", @"Attempt to register verifier stage %@ with nil name, ignoring.", stage); return; } // We can only have one stage with a given name. Registering the same stage twice is OK, though. existing = [_stagesByName objectForKey:name]; if (existing == stage) return; if (existing != nil) { OOLog(@"verifyOXP.registration.failed", @"Attempt to register verifier stage %@ with same name as stage %@, ignoring.", stage, existing); return; } // Checks passed, store state. [stage setVerifier:self]; [_stagesByName setObject:stage forKey:name]; [_waitingStages addObject:stage]; } - (NSString *)oxpPath { return [[_basePath retain] autorelease]; } - (NSString *)oxpDisplayName { return [[_displayName retain] autorelease]; } - (id)stageWithName:(NSString *)name { if (name == nil) return nil; return [_stagesByName objectForKey:name]; } - (id)configurationValueForKey:(NSString *)key { return [_verifierPList objectForKey:key]; } - (NSArray *)configurationArrayForKey:(NSString *)key { return [_verifierPList oo_arrayForKey:key]; } - (NSDictionary *)configurationDictionaryForKey:(NSString *)key { return [_verifierPList oo_dictionaryForKey:key]; } - (NSString *)configurationStringForKey:(NSString *)key { return [_verifierPList oo_stringForKey:key]; } - (NSSet *)configurationSetForKey:(NSString *)key { NSArray *array = [_verifierPList oo_arrayForKey:key]; return array != nil ? [NSSet setWithArray:array] : nil; } @end @implementation OOOXPVerifier (OOPrivate) - (id)initWithPath:(NSString *)path { self = [super init]; OOSetStandardsForOXPVerifierMode(); NSString *verifierPListPath = [[[ResourceManager builtInPath] stringByAppendingPathComponent:@"Config"] stringByAppendingPathComponent:@"verifyOXP.plist"]; _verifierPList = [[NSDictionary dictionaryWithContentsOfFile:verifierPListPath] retain]; _basePath = [path copy]; _displayName = [[NSFileManager defaultManager] displayNameAtPath:_basePath]; if (_displayName == nil) _displayName = [_basePath lastPathComponent]; [_displayName retain]; _stagesByName = [[NSMutableDictionary alloc] init]; _waitingStages = [[NSMutableSet alloc] init]; if (_verifierPList == nil || _basePath == nil) { OOLog(@"verifyOXP.setup.failed", @"***** ERROR: failed to set up OXP verifier."); [self release]; return nil; } _openForRegistration = YES; return self; } - (void)run { NoteVerificationStage(_displayName, @""); [self setUpLogOverrides]; /* We need to be able to look up internal files, but not other OXP files. To do this without clobbering the disk cache, we disable cache writes. */ [[OOCacheManager sharedCache] flush]; [[OOCacheManager sharedCache] setAllowCacheWrites:NO]; /* FIXME: the OXP verifier should load files from OXPs which have * been explicitly listed as required_oxps in the * manifest. Reading the manifest from the OXP being verified and * setting 'id:' below will do this. */ [ResourceManager setUseAddOns:SCENARIO_OXP_DEFINITION_NONE]; SwitchLogFile(_displayName); OOLog(@"verifyOXP.start", @"Running OXP verifier for %@", _basePath);//_displayName); [self registerBaseStages]; [self buildDependencyGraph]; [self runStages]; NoteVerificationStage(_displayName, @""); OOLog(@"verifyOXP.done", @"OXP verification complete."); OpenLogFile(_displayName); } - (void)setUpLogOverrides { NSDictionary *overrides = nil; NSEnumerator *messageClassEnum = nil; NSString *messageClass = nil; id verbose = nil; OOLogSetShowMessageClassTemporary([_verifierPList oo_boolForKey:@"logShowMessageClassOverride" defaultValue:NO]); overrides = [_verifierPList oo_dictionaryForKey:@"logControlOverride"]; for (messageClassEnum = [overrides keyEnumerator]; (messageClass = [messageClassEnum nextObject]); ) { OOLogSetDisplayMessagesInClass(messageClass, [overrides oo_boolForKey:messageClass defaultValue:NO]); } /* Since actually editing logControlOverride is a pain, we also allow overriding verifyOXP.verbose through user defaults. This is at least as much a pain under GNUstep, but very convenient under OS X. */ verbose = [[NSUserDefaults standardUserDefaults] objectForKey:@"oxp-verifier-verbose-logging"]; if (verbose != nil) OOLogSetDisplayMessagesInClass(@"verifyOXP.verbose", OOBooleanFromObject(verbose, NO)); } - (void)registerBaseStages { NSAutoreleasePool *pool = nil; NSSet *stages = nil; NSSet *excludeStages = nil; NSEnumerator *stageEnum = nil; NSString *stageName = nil; Class stageClass = Nil; OOOXPVerifierStage *stage = nil; pool = [[NSAutoreleasePool alloc] init]; // Load stages specified as array of class names in verifyOXP.plist stages = [self configurationSetForKey:@"stages"]; excludeStages = [self configurationSetForKey:@"excludeStages"]; if ([excludeStages count] != 0) { stages = [[stages mutableCopy] autorelease]; [(NSMutableSet *)stages minusSet:excludeStages]; } for (stageEnum = [stages objectEnumerator]; (stageName = [stageEnum nextObject]); ) { if ([stageName isKindOfClass:[NSString class]]) { stageClass = NSClassFromString(stageName); if (stageClass == Nil) { OOLog(@"verifyOXP.registration.failed", @"Attempt to register unknown class %@ as a verifier stage, ignoring.", stageName); continue; } stage = [[stageClass alloc] init]; [self registerStage:stage]; [stage release]; } } [pool release]; } - (void)buildDependencyGraph { NSAutoreleasePool *pool = nil; NSArray *stageKeys = nil; NSEnumerator *stageEnum = nil; NSString *stageKey = nil; OOOXPVerifierStage *stage = nil; NSString *name = nil; NSMutableDictionary *dependenciesByStage = nil, *dependentsByStage = nil; NSSet *dependencies = nil, *dependents = nil; NSValue *key = nil; pool = [[NSAutoreleasePool alloc] init]; /* Iterate over all stages, getting dependency and dependent sets. This is done in advance so that -dependencies and -dependents may register stages. */ dependenciesByStage = [NSMutableDictionary dictionary]; dependentsByStage = [NSMutableDictionary dictionary]; for (;;) { /* Loop while there are stages whose dependency lists haven't been checked. This is an indeterminate loop since new ones can be added. */ stage = [_waitingStages anyObject]; if (stage == nil) break; [_waitingStages removeObject:stage]; key = [NSValue valueWithNonretainedObject:stage]; dependencies = [stage dependencies]; if (dependencies != nil) { [dependenciesByStage setObject:dependencies forKey:key]; } dependents = [stage dependents]; if (dependents != nil) { [dependentsByStage setObject:dependents forKey:key]; } } [_waitingStages release]; _waitingStages = nil; _openForRegistration = NO; // Iterate over all stages, resolving dependencies. stageKeys = [_stagesByName allKeys]; // Get the keys up front because we may need to remove entries from dictionary. for (stageEnum = [stageKeys objectEnumerator]; (stageKey = [stageEnum nextObject]); ) { stage = [_stagesByName objectForKey:stageKey]; if (stage == nil) continue; // Sanity check name = [stage name]; if (![stageKey isEqualToString:name]) { OOLog(@"verifyOXP.buildDependencyGraph.badName", @"***** Stage name appears to have changed from \"%@\" to \"%@\" for verifier stage %@, removing.", stageKey, name, stage); [_stagesByName removeObjectForKey:stageKey]; continue; } // Get dependency set key = [NSValue valueWithNonretainedObject:stage]; dependencies = [dependenciesByStage objectForKey:key]; if (dependencies != nil && ![self setUpDependencies:dependencies forStage:stage]) { [_stagesByName removeObjectForKey:stageKey]; } } /* Iterate over all stages again, resolving reverse dependencies. This is done in a separate pass because reverse dependencies are "weak" while forward dependencies are "strong". */ stageKeys = [_stagesByName allKeys]; for (stageEnum = [stageKeys objectEnumerator]; (stageKey = [stageEnum nextObject]); ) { stage = [_stagesByName objectForKey:stageKey]; if (stage == nil) continue; // Get dependent set key = [NSValue valueWithNonretainedObject:stage]; dependents = [dependentsByStage objectForKey:key]; if (dependents != nil) { [self setUpDependents:dependents forStage:stage]; } } _waitingStages = [[NSMutableSet alloc] initWithArray:[_stagesByName allValues]]; [_waitingStages makeObjectsPerformSelector:@selector(dependencyRegistrationComplete)]; if ([[NSUserDefaults standardUserDefaults] boolForKey:@"oxp-verifier-dump-debug-graphviz"]) { [self dumpDebugGraphviz]; } [pool release]; } - (void)runStages { NSAutoreleasePool *pool = nil; NSEnumerator *stageEnum = nil; OOOXPVerifierStage *candidateStage = nil, *stageToRun = nil; NSString *stageName = nil; // Loop while there are still stages to run. for (;;) { pool = [[NSAutoreleasePool alloc] init]; // Look through queue for a stage that's ready stageToRun = nil; for (stageEnum = [_waitingStages objectEnumerator]; (candidateStage = [stageEnum nextObject]); ) { if ([candidateStage canRun]) { stageToRun = candidateStage; break; } } if (stageToRun == nil) { // No more runnable stages [pool release]; break; } stageName = nil; OOLogPushIndent(); @try { stageName = [stageToRun name]; if ([stageToRun shouldRun]) { NoteVerificationStage(_displayName, stageName); OOLog(@"verifyOXP.runStage", @"%@", stageName); OOLogIndent(); [stageToRun performRun]; } else { OOLog(@"verifyOXP.verbose.skipStage", @"- Skipping stage: %@ (nothing to do).", stageName); [stageToRun noteSkipped]; } } @catch (NSException *exception) { if (stageName == nil) stageName = [[stageToRun class] description]; OOLog(@"verifyOXP.exception", @"***** Exception occurred when running OXP verifier stage \"%@\": %@: %@", stageName, [exception name], [exception reason]); } OOLogPopIndent(); [_waitingStages removeObject:stageToRun]; [pool release]; } pool = [[NSAutoreleasePool alloc] init]; if ([_waitingStages count] != 0) { OOLog(@"verifyOXP.incomplete", @"Some verifier stages could not be run:"); OOLogIndent(); for (stageEnum = [_waitingStages objectEnumerator]; (candidateStage = [stageEnum nextObject]); ) { OOLog(@"verifyOXP.incomplete.item", @"%@", candidateStage); } OOLogOutdent(); } [_waitingStages release]; _waitingStages = nil; [pool release]; } - (BOOL)setUpDependencies:(NSSet *)dependencies forStage:(OOOXPVerifierStage *)stage { NSString *depName = nil; NSEnumerator *depEnum = nil; OOOXPVerifierStage *depStage = nil; // Iterate over dependencies, connecting them up. for (depEnum = [dependencies objectEnumerator]; (depName = [depEnum nextObject]); ) { depStage = [_stagesByName objectForKey:depName]; if (depStage == nil) { OOLog(@"verifyOXP.buildDependencyGraph.unresolved", @"Verifier stage %@ has unresolved dependency \"%@\", skipping.", stage, depName); return NO; } if ([depStage isDependentOf:stage]) { OOLog(@"verifyOXP.buildDependencyGraph.circularReference", @"Verifier stages %@ and %@ have a dependency loop, skipping.", stage, depStage); [_stagesByName removeObjectForKey:depName]; return NO; } [stage registerDependency:depStage]; } return YES; } - (void)setUpDependents:(NSSet *)dependents forStage:(OOOXPVerifierStage *)stage { NSString *depName = nil; NSEnumerator *depEnum = nil; OOOXPVerifierStage *depStage = nil; // Iterate over dependents, connecting them up. for (depEnum = [dependents objectEnumerator]; (depName = [depEnum nextObject]); ) { depStage = [_stagesByName objectForKey:depName]; if (depStage == nil) { OOLog(@"verifyOXP.buildDependencyGraph.unresolved", @"Verifier stage %@ has unresolved dependent \"%@\".", stage, depName); continue; // Unresolved/conflicting dependents are non-fatal } if ([stage isDependentOf:depStage]) { OOLog(@"verifyOXP.buildDependencyGraph.circularReference", @"Verifier stage %@ lists %@ as both dependent and dependency (possibly indirectly); will execute %@ after %@.", stage, depStage, stage, depStage); continue; } [depStage registerDependency:stage]; } } - (void)dumpDebugGraphviz { NSMutableString *graphViz = nil; NSDictionary *graphVizTemplate = nil; NSString *template = nil, *startTemplate = nil, *endTemplate = nil; NSEnumerator *stageEnum = nil; OOOXPVerifierStage *stage = nil; NSSet *deps = nil; NSEnumerator *depEnum = nil; OOOXPVerifierStage *dep = nil; graphVizTemplate = [self configurationDictionaryForKey:@"debugGraphvizTempate"]; graphViz = [NSMutableString stringWithFormat:[graphVizTemplate oo_stringForKey:@"preamble"], [NSDate date]]; /* Pass 1: enumerate over graph setting node attributes for each stage. We use pointers as node names for simplicity of generation. */ template = [graphVizTemplate oo_stringForKey:@"node"]; for (stageEnum = [_stagesByName objectEnumerator]; (stage = [stageEnum nextObject]); ) { [graphViz appendFormat:template, stage, [stage class], [stage name]]; } [graphViz appendString:[graphVizTemplate oo_stringForKey:@"forwardPreamble"]]; /* Pass 2: enumerate over graph setting forward arcs for each dependency. */ template = [graphVizTemplate oo_stringForKey:@"forwardArc"]; startTemplate = [graphVizTemplate oo_stringForKey:@"startArc"]; for (stageEnum = [_stagesByName objectEnumerator]; (stage = [stageEnum nextObject]); ) { deps = [stage resolvedDependencies]; if ([deps count] != 0) { for (depEnum = [deps objectEnumerator]; (dep = [depEnum nextObject]); ) { [graphViz appendFormat:template, dep, stage]; } } else { [graphViz appendFormat:startTemplate, stage]; } } [graphViz appendString:[graphVizTemplate oo_stringForKey:@"backwardPreamble"]]; /* Pass 3: enumerate over graph setting backward arcs for each dependent. */ template = [graphVizTemplate oo_stringForKey:@"backwardArc"]; endTemplate = [graphVizTemplate oo_stringForKey:@"endArc"]; for (stageEnum = [_stagesByName objectEnumerator]; (stage = [stageEnum nextObject]); ) { deps = [stage resolvedDependents]; if ([deps count] != 0) { for (depEnum = [deps objectEnumerator]; (dep = [depEnum nextObject]); ) { [graphViz appendFormat:template, dep, stage]; } } else { [graphViz appendFormat:endTemplate, stage]; } } [graphViz appendString:[graphVizTemplate oo_stringForKey:@"postamble"]]; // Write file [ResourceManager writeDiagnosticString:graphViz toFileNamed:@"OXPVerifierStageDependencies.dot"]; } @end #import "OOLogOutputHandler.h" static void SwitchLogFile(NSString *name) { //#ifndef OOLITE_LINUX name = [name stringByAppendingPathExtension:@"log"]; OOLog(@"verifyOXP.switchingLog", @"Switching log files -- logging to \"%@\".", name); OOLogOutputHandlerChangeLogFile(name); //#else // OOLog(@"verifyOXP.switchingLog", @"Switching logging to ."); // OOLogOutputHandlerStartLoggingToStdout(); //#endif } static void NoteVerificationStage(NSString *displayName, NSString *stage) { [[GameController sharedController] logProgress:[NSString stringWithFormat:@"Verifying %@\n%@", displayName, stage]]; } static void OpenLogFile(NSString *name) { // Open log file in appropriate application / provide feedback. if ([[NSUserDefaults standardUserDefaults] oo_boolForKey:@"oxp-verifier-open-log" defaultValue:YES]) { #if OOLITE_MAC_OS_X [[NSWorkspace sharedWorkspace] openFile:OOLogHandlerGetLogPath()]; #elif OOLITE_WINDOWS // start wordpad (for historical reasons wordpad is called write from the command prompt) system([[NSString stringWithFormat:@"write \"Logs\\%@.log\"", name] UTF8String]); #elif OOLITE_LINUX // MKW - needed to suppress 'ignoring return value' warning for system() call // int ret; // CIM - and now the compiler complains about that too... casting return // value to void seems to keep it quiet for now // Nothing to do here, since we dump to stdout instead of to a file. //OOLogOutputHandlerStopLoggingToStdout(); (void) system([[NSString stringWithFormat:@"cat \"%@\"", OOLogHandlerGetLogPath()] UTF8String]); #else do {} while (0); #endif } } #endif // OO_OXP_VERIFIER_ENABLED oolite-1.82/src/Core/OXPVerifier/OOOXPVerifierStage.h000066400000000000000000000054501256642440500223310ustar00rootroot00000000000000/* OOOXPVerifierStage.h Pipeline stage for OXP verification pipeline managed by OOOXPVerifier. Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOOXPVerifier.h" #if OO_OXP_VERIFIER_ENABLED @interface OOOXPVerifierStage: NSObject { @private OOOXPVerifier *_verifier; NSMutableSet *_dependencies; NSMutableSet *_incompleteDependencies; NSMutableSet *_dependents; BOOL _canRun, _hasRun; } - (OOOXPVerifier *)verifier; - (BOOL)completed; // Subclass responsibilities: /* Name of stage. Used for display and for dependency resolution; must be unique. The name should be a phrase describing what will be done, like "Scanning files" or "Verifying plist scripts". */ - (NSString *)name; /* Dependencies and dependents: -dependencies returns a set of names of stages that must be run before this one. If it contains the name of a stage that's not registered, this stage cannot run. -dependents returns a set of names of stages that should not be run before this one. Unlike -dependencies, these are considered non-critical. */ - (NSSet *)dependencies; - (NSSet *)dependents; /* This is called once by the verifier. When it is called, all the verifier stages listed in -requiredStages will have run. At this point, it is possible to access them using the verifier's -stageWithName: method in order to query them about results. Stages whose dependencies have all run will be released, so the result of calling -stageWithName: with a name not in -requiredStages is undefined. shouldRun can be overridden to avoid running at all (without anything being logged). For dependency resolution purposes, returning NO from shouldRun counts as running; that is, it will stop this verifier stage from running but will not stop dependencies from running. */ - (BOOL)shouldRun; - (void)run; @end #endif oolite-1.82/src/Core/OXPVerifier/OOOXPVerifierStage.m000066400000000000000000000103071256642440500223330ustar00rootroot00000000000000/* OOOXPVerifierStage.m Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #import "OOOXPVerifierStageInternal.h" #if OO_OXP_VERIFIER_ENABLED @interface OOOXPVerifierStage (OOPrivate) - (void)registerDepedent:(OOOXPVerifierStage *)dependent; - (void)dependencyCompleted:(OOOXPVerifierStage *)dependency; @end @implementation OOOXPVerifierStage - (id)init { self = [super init]; if (self != nil) { _dependencies = [[NSMutableSet alloc] init]; _incompleteDependencies = [[NSMutableSet alloc] init]; _dependents = [[NSMutableSet alloc] init]; _canRun = NO; } return self; } - (void)dealloc { [_dependencies release]; [_incompleteDependencies release]; [_dependents release]; [super dealloc]; } - (id)description { return [NSString stringWithFormat:@"<%@ %p>{\"%@\"}", [self class], self, [self name]]; } - (OOOXPVerifier *)verifier { return [[_verifier retain] autorelease]; } - (BOOL)completed { return _hasRun; } - (NSString *)name { OOLogGenericSubclassResponsibility(); return nil; } - (NSSet *)dependencies { return nil; } - (NSSet *)dependents { return nil; } - (BOOL)shouldRun { return YES; } - (void)run { OOLogGenericSubclassResponsibility(); } @end @implementation OOOXPVerifierStage (OOInternal) - (void)setVerifier:(OOOXPVerifier *)verifier { _verifier = verifier; // Not retained. } - (BOOL)isDependentOf:(OOOXPVerifierStage *)stage { NSEnumerator *directDepEnum = nil; OOOXPVerifierStage *directDep = nil; if (stage == nil) return NO; // Direct dependency check. if ([_dependencies containsObject:stage]) return YES; // Recursive dependency check. for (directDepEnum = [_dependencies objectEnumerator]; (directDep = [directDepEnum nextObject]); ) { if ([directDep isDependentOf:stage]) return YES; } return NO; } - (void)registerDependency:(OOOXPVerifierStage *)dependency { [_dependencies addObject:dependency]; [_incompleteDependencies addObject:dependency]; [dependency registerDepedent:self]; } - (BOOL)canRun { return _canRun; } - (void)performRun { assert(_canRun && !_hasRun); OOLogPushIndent(); @try { [self run]; } @catch (NSException *exception) { OOLog(@"verifyOXP.exception", @"***** Exception while running verification stage \"%@\": %@", [self name], exception); } OOLogPopIndent(); _hasRun = YES; _canRun = NO; [_dependents makeObjectsPerformSelector:@selector(dependencyCompleted:) withObject:self]; } - (void)noteSkipped { assert(_canRun && !_hasRun); _hasRun = YES; _canRun = NO; [_dependents makeObjectsPerformSelector:@selector(dependencyCompleted:) withObject:self]; } - (void)dependencyRegistrationComplete { _canRun = [_incompleteDependencies count] == 0; } - (NSSet *)resolvedDependencies { return _dependencies; } - (NSSet *)resolvedDependents { return _dependents; } @end @implementation OOOXPVerifierStage (OOPrivate) - (void)registerDepedent:(OOOXPVerifierStage *)dependent { assert(![self isDependentOf:dependent]); [_dependents addObject:dependent]; } - (void)dependencyCompleted:(OOOXPVerifierStage *)dependency { [_incompleteDependencies removeObject:dependency]; if ([_incompleteDependencies count] == 0) _canRun = YES; } @end #endif //OO_OXP_VERIFIER_ENABLED oolite-1.82/src/Core/OXPVerifier/OOOXPVerifierStageInternal.h000066400000000000000000000032721256642440500240260ustar00rootroot00000000000000/* OOOXPVerifierStageInternal.h Private interface between OOOXPVerifierStage and OOOXPVerifier. Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOOXPVerifierStage.h" #if OO_OXP_VERIFIER_ENABLED @interface OOOXPVerifierStage (OOInternal) - (void)setVerifier:(OOOXPVerifier *)verifier; - (BOOL)isDependentOf:(OOOXPVerifierStage *)stage; - (void)registerDependency:(OOOXPVerifierStage *)dependency; - (void)dependencyRegistrationComplete; - (BOOL)canRun; - (void)performRun; - (void)noteSkipped; // These return sets of stages set up by -registerDependency, wheras -dependencies/dependents return sets of names. - (NSSet *)resolvedDependencies; - (NSSet *)resolvedDependents; @end #endif oolite-1.82/src/Core/OXPVerifier/OOPListSchemaVerifier.h000066400000000000000000000161331256642440500230530ustar00rootroot00000000000000/* OOPListSchemaVerifier.h Utility class to verify the structure of a property list based on a schema (which is itself a property list). Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOOXPVerifier.h" #if OO_OXP_VERIFIER_ENABLED #import #import "OOFunctionAttributes.h" @interface OOPListSchemaVerifier: NSObject { @private NSDictionary *_schema; NSDictionary *_definitions; id _delegate; uint32_t _badDelegateWarning: 1; } + (instancetype)verifierWithSchema:(NSDictionary *)schema; - (id)initWithSchema:(NSDictionary *)schema; - (void)setDelegate:(id)delegate; - (id)delegate; - (BOOL)verifyPropertyList:(id)plist named:(NSString *)name; /* Convert a key path (such as provided to the delegate method -verifier:withPropertyList:failedForProperty:atPath:expectedType:) to a human-readable string. Strings are separated by dots and numbers are give brackets. For instance, the key path ( "adder-player", "custom_views", 0, "view_description" ) is transfomed to "adder-player.custom_views[0].view_description". */ + (NSString *)descriptionForKeyPath:(NSArray *)keyPath; @end @interface NSObject (OOPListSchemaVerifierDelegate) // Handle "delegated types". Return YES for valid, NO for invalid. - (BOOL)verifier:(OOPListSchemaVerifier *)verifier withPropertyList:(id)rootPList named:(NSString *)name testProperty:(id)subPList atPath:(NSArray *)keyPath againstType:(NSString *)typeKey error:(NSError **)outError; /* Method notifying of verification failure. Return YES to continue verifying, NO to stop. */ - (BOOL)verifier:(OOPListSchemaVerifier *)verifier withPropertyList:(id)rootPList named:(NSString *)name failedForProperty:(id)subPList withError:(NSError *)error expectedType:(NSDictionary *)localSchema; @end // NSError domain and codes used to report schema verifier errors. extern NSString * const kOOPListSchemaVerifierErrorDomain; extern NSString * const kPListKeyPathErrorKey; // Array specifying key path in plist. extern NSString * const kSchemaKeyPathErrorKey; // Array specifying key path in schema. extern NSString * const kExpectedClassErrorKey; // Expected class. Nil for vector and quaternion. extern NSString * const kExpectedClassNameErrorKey; // String describing expected class. May be more specific (for instance, "boolean" or "positive integer" for NSNumber). extern NSString * const kUnknownKeyErrorKey; // Unallowed key found in dictionary. extern NSString * const kMissingRequiredKeysErrorKey; // Set of required keys not present in dictionary extern NSString * const kMissingSubStringErrorKey; // String or array of strings not found for kPListErrorStringPrefixMissing/kPListErrorStringSuffixMissing/kPListErrorStringSubstringMissing. extern NSString * const kUnnownFilterErrorKey; // Unrecognized filter specifier for kPListErrorSchemaUnknownFilter. Not specified if filter is not a string. extern NSString * const kErrorsByOptionErrorKey; // Dictionary of errors for oneOf types. extern NSString * const kUnknownTypeErrorKey; // Set for kPListErrorSchemaUnknownType. extern NSString * const kUndefinedMacroErrorKey; // Set for kPListErrorSchemaUndefiniedMacroReference. // All plist verifier errors have a short error description in their -localizedFailureReason. typedef enum { kPListErrorNone, kPListErrorInternal, // PList verifier did something dumb. // Verification errors -- property list doesn't match schema. kPListErrorTypeMismatch, // Basic type mismatch -- array instead of number, for instance. kPListErrorMinimumConstraintNotMet, // minimum/minCount/minLength constraint violated kPListErrorMaximumConstraintNotMet, // maximum/maxCount/maxLength constraint violated kPListErrorNumberIsNegative, // Negative number in positiveFloat. kPListErrorStringPrefixMissing, // String does not match requiredPrefix rule. kMissingSubStringErrorKey is set. kPListErrorStringSuffixMissing, // String does not match requiredSuffix rule. kMissingSubStringErrorKey is set. kPListErrorStringSubstringMissing, // String does not match requiredSuffix rule. kMissingSubStringErrorKey is set. kPListErrorDictionaryUnknownKey, // Unknown key for dictionary with allowOthers = NO. kPListErrorDictionaryMissingRequiredKeys, // requiredKeys rule is not fulfilled. The missing keys are listed in kMissingRequiredKeysErrorKey. kPListErrorEnumerationBadValue, // Enumeration type contains string that isn't in permitted set. kPListErrorOneOfNoMatch, // No match for oneOf type. kErrorsByOptionErrorKey is set to a dictionary of type specifiers to errors. Note that the keys in this dictionary can be either strings or dictionaries. kPListDelegatedTypeError, // Delegate's verification method failed. If it returned an error, this will be in NSUnderlyingErrorKey. // Schema errors -- schema is broken. kPListErrorStartOfSchemaErrors = 100, kPListErrorSchemaBadTypeSpecifier, // Bad type specifier - specifier is not a string or a dictionary, or is a dictionary with no type key. kUndefinedMacroErrorKey is set. kPListErrorSchemaUndefiniedMacroReference, // Reference to $macro not found in $definitions. kPListErrorSchemaUnknownType, // Unknown type specified in type specifier. kUnknownTypeErrorKey is set. kPListErrorSchemaNoOneOfOptions, // OneOf clause has no options array. kPListErrorSchemaNoEnumerationValues, // Enumeration clause has no values array. kPListErrorSchemaUnknownFilter, // Bad value for string/enumeration filter specifier. kPListErrorSchemaBadComparator, // String comparision requirement value (requiredPrefix etc.) is not a string. kPListErrorLastErrorCode } OOPListSchemaVerifierErrorCode; OOINLINE BOOL OOPlistErrorIsSchemaError(OOPListSchemaVerifierErrorCode error) { return kPListErrorStartOfSchemaErrors < error && error < kPListErrorLastErrorCode; } @interface NSError (OOPListSchemaVerifierConveniences) - (NSArray *)plistKeyPath; - (NSString *)plistKeyPathDescription; // Result of calling +[OOPListSchemaVerifier descriptionForKeyPath:] on kPListKeyPathErrorKey. - (NSSet *)missingRequiredKeys; - (Class)expectedClass; - (NSString *)expectedClassName; @end #endif // OO_OXP_VERIFIER_ENABLED oolite-1.82/src/Core/OXPVerifier/OOPListSchemaVerifier.m000066400000000000000000001331101256642440500230530ustar00rootroot00000000000000/* OOPListSchemaVerifier.m Copyright (C) 2007-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED ìAS ISî, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOPListSchemaVerifier.h" #if OO_OXP_VERIFIER_ENABLED #import "OOLoggingExtended.h" #import "OOCollectionExtractors.h" #import "OOMaths.h" #include #define PLIST_VERIFIER_DEBUG_DUMP_ENABLED 1 enum { // Largest allowable number of characters for string included in error message. kMaximumLengthForStringInErrorMessage = 100 }; // Internal error codes. enum { kStartOfPrivateErrorCodes = kPListErrorLastErrorCode, kPListErrorFailedAndErrorHasBeenReported }; #if PLIST_VERIFIER_DEBUG_DUMP_ENABLED static BOOL sDebugDump = NO; #define DebugDumpIndent() do { if (sDebugDump) OOLogIndent(); } while (0) #define DebugDumpOutdent() do { if (sDebugDump) OOLogOutdent(); } while (0) #define DebugDumpPushIndent() do { if (sDebugDump) OOLogPushIndent(); } while (0) #define DebugDumpPopIndent() do { if (sDebugDump) OOLogPopIndent(); } while (0) #define DebugDump(...) do { if (sDebugDump) OOLog(@"verifyOXP.verbose.plistDebugDump", __VA_ARGS__); } while (0) #else #define DebugDumpIndent() do { } while (0) #define DebugDumpOutdent() do { } while (0) #define DebugDumpPushIndent() do { } while (0) #define DebugDumpPopIndent() do { } while (0) #define DebugDump(...) do { } while (0) #endif NSString * const kOOPListSchemaVerifierErrorDomain = @"org.aegidian.oolite.OOPListSchemaVerifier.ErrorDomain"; NSString * const kPListKeyPathErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier plist key path"; NSString * const kSchemaKeyPathErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier schema key path"; NSString * const kExpectedClassErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier expected class"; NSString * const kExpectedClassNameErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier expected class name"; NSString * const kUnknownKeyErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier unknown key"; NSString * const kMissingRequiredKeysErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier missing required keys"; NSString * const kMissingSubStringErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier missing substring"; NSString * const kUnnownFilterErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier unknown filter"; NSString * const kErrorsByOptionErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier errors by option"; NSString * const kUnknownTypeErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier unknown type"; NSString * const kUndefinedMacroErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier undefined macro"; typedef enum { kTypeUnknown, kTypeString, kTypeArray, kTypeDictionary, kTypeInteger, kTypePositiveInteger, kTypeFloat, kTypePositiveFloat, kTypeOneOf, kTypeEnumeration, kTypeBoolean, kTypeFuzzyBoolean, kTypeVector, kTypeQuaternion, kTypeDelegatedType } SchemaType; typedef struct BackLinkChain BackLinkChain; struct BackLinkChain { BackLinkChain *link; id element; }; OOINLINE BackLinkChain BackLink(BackLinkChain *link, id element) { BackLinkChain result = { link, element }; return result; } OOINLINE BackLinkChain BackLinkIndex(BackLinkChain *link, NSUInteger index) { BackLinkChain result = { link, [NSNumber numberWithInteger:index] }; return result; } OOINLINE BackLinkChain BackLinkRoot(void) { BackLinkChain result = { NULL, NULL }; return result; } static SchemaType StringToSchemaType(NSString *string, NSError **outError); static NSString *ApplyStringFilter(NSString *string, id filterSpec, BackLinkChain keyPath, NSError **outError); static BOOL ApplyStringTest(NSString *string, id test, SEL testSelector, NSString *testDescription, BackLinkChain keyPath, NSError **outError); static NSArray *KeyPathToArray(BackLinkChain keyPath); static NSString *KeyPathToString(BackLinkChain keyPath); static NSString *StringForErrorReport(NSString *string); static NSString *ArrayForErrorReport(NSArray *array); static NSString *SetForErrorReport(NSSet *set); static NSString *StringOrArrayForErrorReport(id value, NSString *arrayPrefix); static NSError *Error(OOPListSchemaVerifierErrorCode errorCode, BackLinkChain *keyPath, NSString *format, ...); static NSError *ErrorWithProperty(OOPListSchemaVerifierErrorCode errorCode, BackLinkChain *keyPath, NSString *propKey, id propValue, NSString *format, ...); static NSError *ErrorWithDictionary(OOPListSchemaVerifierErrorCode errorCode, BackLinkChain *keyPath, NSDictionary *dict, NSString *format, ...); static NSError *ErrorWithDictionaryAndArguments(OOPListSchemaVerifierErrorCode errorCode, BackLinkChain *keyPath, NSDictionary *dict, NSString *format, va_list arguments); static NSError *ErrorTypeMismatch(Class expectedClass, NSString *expectedClassName, id actualObject, BackLinkChain keyPath); static NSError *ErrorFailureAlreadyReported(void); static BOOL IsFailureAlreadyReportedError(NSError *error); @interface OOPListSchemaVerifier (OOPrivate) // Call delegate methods. - (BOOL)delegateVerifierWithPropertyList:(id)rootPList named:(NSString *)name testProperty:(id)subPList atPath:(BackLinkChain)keyPath againstType:(NSString *)typeKey error:(NSError **)outError; - (BOOL)delegateVerifierWithPropertyList:(id)rootPList named:(NSString *)name failedForProperty:(id)subPList withError:(NSError *)error expectedType:(NSDictionary *)localSchema; - (BOOL)verifyPList:(id)rootPList named:(NSString *)name subProperty:(id)subProperty againstSchemaType:(id)subSchema atPath:(BackLinkChain)keyPath tentative:(BOOL)tentative error:(NSError **)outError stop:(BOOL *)outStop; - (NSDictionary *)resolveSchemaType:(id)specifier atPath:(BackLinkChain)keyPath error:(NSError **)outError; @end @interface NSString (OOPListSchemaVerifierHelpers) - (BOOL)ooPListVerifierHasSubString:(NSString *)string; @end #define VERIFY_PROTO(T) static NSError *Verify_##T(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) VERIFY_PROTO(String); VERIFY_PROTO(Array); VERIFY_PROTO(Dictionary); VERIFY_PROTO(Integer); VERIFY_PROTO(PositiveInteger); VERIFY_PROTO(Float); VERIFY_PROTO(PositiveFloat); VERIFY_PROTO(OneOf); VERIFY_PROTO(Enumeration); VERIFY_PROTO(Boolean); VERIFY_PROTO(FuzzyBoolean); VERIFY_PROTO(Vector); VERIFY_PROTO(Quaternion); VERIFY_PROTO(DelegatedType); @implementation OOPListSchemaVerifier + (id)verifierWithSchema:(NSDictionary *)schema { return [[[self alloc] initWithSchema:schema] autorelease]; } - (id)initWithSchema:(NSDictionary *)schema { self = [super init]; if (self != nil) { _schema = [schema retain]; _definitions = [[_schema oo_dictionaryForKey:@"$definitions"] retain]; sDebugDump = [[NSUserDefaults standardUserDefaults] boolForKey:@"plist-schema-verifier-dump-structure"]; if (sDebugDump) OOLogSetDisplayMessagesInClass(@"verifyOXP.verbose.plistDebugDump", YES); if (_schema == nil) { [self release]; self = nil; } } return self; } - (void)dealloc { [_schema release]; [_definitions release]; [super dealloc]; } - (void)setDelegate:(id)delegate { if (_delegate != delegate) { _delegate = delegate; _badDelegateWarning = NO; } } - (id)delegate { return _delegate; } - (BOOL)verifyPropertyList:(id)plist named:(NSString *)name { BOOL OK; BOOL stop = NO; OK = [self verifyPList:plist named:name subProperty:plist againstSchemaType:_schema atPath:BackLinkRoot() tentative:NO error:NULL stop:&stop]; return OK; } + (NSString *)descriptionForKeyPath:(NSArray *)keyPath { NSMutableString *result = nil; NSEnumerator *componentEnum = nil; id component = nil; BOOL first = YES; result = [NSMutableString string]; for (componentEnum = [keyPath objectEnumerator]; (component = [componentEnum nextObject]); ) { if ([component isKindOfClass:[NSNumber class]]) { [result appendFormat:@"[%@]", component]; } else if ([component isKindOfClass:[NSString class]]) { if (!first) [result appendString:@"."]; [result appendString:component]; } else return nil; first = NO; } if (first) { // Empty path return @"root"; } return result; } @end @implementation OOPListSchemaVerifier (OOPrivate) - (BOOL)delegateVerifierWithPropertyList:(id)rootPList named:(NSString *)name testProperty:(id)subPList atPath:(BackLinkChain)keyPath againstType:(NSString *)typeKey error:(NSError **)outError { BOOL result; NSError *error = nil; if ([_delegate respondsToSelector:@selector(verifier:withPropertyList:named:testProperty:atPath:againstType:error:)]) { @try { result = [_delegate verifier:self withPropertyList:rootPList named:name testProperty:subPList atPath:KeyPathToArray(keyPath) againstType:typeKey error:&error]; } @catch (NSException *exception) { OOLog(@"plistVerifier.delegateException", @"Property list schema verifier: delegate threw exception (%@) in -verifier:withPropertyList:named:testProperty:atPath:againstType: for type \"%@\" at %@ in %@ -- treating as failure.", [exception name], typeKey,KeyPathToString(keyPath), name); result = NO; error = nil; } if (outError != NULL) { if (!result || error != nil) { // Note: Generates an error if delegate returned NO (meaning stop) or if delegate produced an error but did not request a stop. *outError = ErrorWithProperty(kPListDelegatedTypeError, &keyPath, NSUnderlyingErrorKey, error, @"Value at %@ does not match delegated type \"%@\".", KeyPathToString(keyPath), typeKey); } else *outError = nil; } } else { if (!_badDelegateWarning) { OOLog(@"plistVerifier.badDelegate", @"Property list schema verifier: delegate does not handle delegated types."); _badDelegateWarning = YES; } result = YES; } return result; } - (BOOL)delegateVerifierWithPropertyList:(id)rootPList named:(NSString *)name failedForProperty:(id)subPList withError:(NSError *)error expectedType:(NSDictionary *)localSchema { BOOL result; if ([_delegate respondsToSelector:@selector(verifier:withPropertyList:named:failedForProperty:withError:expectedType:)]) { @try { result = [_delegate verifier:self withPropertyList:rootPList named:name failedForProperty:subPList withError:error expectedType:localSchema]; } @catch (NSException *exception) { OOLog(@"plistVerifier.delegateException", @"Property list schema verifier: delegate threw exception (%@) in -verifier:withPropertyList:named:failedForProperty:atPath:expectedType: at %@ in %@ -- stopping.", [exception name], [error plistKeyPathDescription], name); result = NO; } } else { OOLog(@"plistVerifier.failed", @"Verification of property list \"%@\" failed at %@: %@", name, [error plistKeyPathDescription], [error localizedFailureReason]); result = NO; } return result; } - (BOOL)verifyPList:(id)rootPList named:(NSString *)name subProperty:(id)subProperty againstSchemaType:(id)subSchema atPath:(BackLinkChain)keyPath tentative:(BOOL)tentative error:(NSError **)outError stop:(BOOL *)outStop { SchemaType type = kTypeUnknown; NSError *error = nil; NSDictionary *resolvedSpecifier = nil; NSAutoreleasePool *pool = nil; assert(outStop != NULL); pool = [[NSAutoreleasePool alloc] init]; DebugDumpPushIndent(); @try { DebugDumpIndent(); resolvedSpecifier = [self resolveSchemaType:subSchema atPath:keyPath error:&error]; if (resolvedSpecifier != nil) type = StringToSchemaType([resolvedSpecifier objectForKey:@"type"], &error); #define VERIFY_CASE(T) case kType##T: error = Verify_##T(self, subProperty, resolvedSpecifier, rootPList, name, keyPath, tentative, outStop); break; switch (type) { VERIFY_CASE(String); VERIFY_CASE(Array); VERIFY_CASE(Dictionary); VERIFY_CASE(Integer); VERIFY_CASE(PositiveInteger); VERIFY_CASE(Float); VERIFY_CASE(PositiveFloat); VERIFY_CASE(OneOf); VERIFY_CASE(Enumeration); VERIFY_CASE(Boolean); VERIFY_CASE(FuzzyBoolean); VERIFY_CASE(Vector); VERIFY_CASE(Quaternion); VERIFY_CASE(DelegatedType); case kTypeUnknown: // resolveSchemaType:... or StringToSchemaType() should have provided an error. *outStop = YES; } } @catch (NSException *exception) { error = Error(kPListErrorInternal, (BackLinkChain *)&keyPath, @"Uncaught exception %@: %@ in plist verifier for \"%@\" at %@.", [exception name], [exception reason], name, KeyPathToString(keyPath)); } DebugDumpPopIndent(); if (error != nil) { if (!tentative && !IsFailureAlreadyReportedError(error)) { *outStop = ![self delegateVerifierWithPropertyList:rootPList named:name failedForProperty:subProperty withError:error expectedType:subSchema]; } else if (tentative) *outStop = YES; } if (outError != NULL && error != nil) { *outError = [error retain]; [pool release]; [error autorelease]; } else { [pool release]; } return error == nil; } - (NSDictionary *)resolveSchemaType:(id)specifier atPath:(BackLinkChain)keyPath error:(NSError **)outError { id typeVal = nil; NSString *complaint = nil; assert(outError != NULL); if (![specifier isKindOfClass:[NSString class]] && ![specifier isKindOfClass:[NSDictionary class]]) goto BAD_TYPE; for (;;) { if ([specifier isKindOfClass:[NSString class]]) specifier = [NSDictionary dictionaryWithObject:specifier forKey:@"type"]; typeVal = [(NSDictionary *)specifier objectForKey:@"type"]; if ([typeVal isKindOfClass:[NSString class]]) { if ([typeVal hasPrefix:@"$"]) { // Macro reference; look it up in $definitions specifier = [_definitions objectForKey:typeVal]; if (specifier == nil) { *outError = ErrorWithProperty(kPListErrorSchemaUndefiniedMacroReference, &keyPath, kUndefinedMacroErrorKey, typeVal, @"Bad schema: reference to undefined macro \"%@\".", StringForErrorReport(typeVal)); return nil; } } else { // Non-macro string return specifier; } } else if ([typeVal isKindOfClass:[NSDictionary class]]) { specifier = typeVal; } else { goto BAD_TYPE; } } BAD_TYPE: // Error: bad type if (typeVal == nil) complaint = @"no type specified"; else complaint = @"not string or dictionary"; *outError = Error(kPListErrorSchemaBadTypeSpecifier, &keyPath, @"Bad schema: invalid type specifier for path %@ (%@).", KeyPathToString(keyPath), complaint); return nil; } @end static SchemaType StringToSchemaType(NSString *string, NSError **outError) { static NSDictionary *typeMap = nil; SchemaType result; if (typeMap == nil) { typeMap = [[NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithUnsignedInt:kTypeString], @"string", [NSNumber numberWithUnsignedInt:kTypeArray], @"array", [NSNumber numberWithUnsignedInt:kTypeDictionary], @"dictionary", [NSNumber numberWithUnsignedInt:kTypeInteger], @"integer", [NSNumber numberWithUnsignedInt:kTypePositiveInteger], @"positiveInteger", [NSNumber numberWithUnsignedInt:kTypeFloat], @"float", [NSNumber numberWithUnsignedInt:kTypePositiveFloat], @"positiveFloat", [NSNumber numberWithUnsignedInt:kTypeOneOf], @"oneOf", [NSNumber numberWithUnsignedInt:kTypeEnumeration], @"enumeration", [NSNumber numberWithUnsignedInt:kTypeBoolean], @"boolean", [NSNumber numberWithUnsignedInt:kTypeFuzzyBoolean], @"fuzzyBoolean", [NSNumber numberWithUnsignedInt:kTypeVector], @"vector", [NSNumber numberWithUnsignedInt:kTypeQuaternion], @"quaternion", [NSNumber numberWithUnsignedInt:kTypeDelegatedType], @"delegatedType", nil ] retain]; } result = [[typeMap objectForKey:string] unsignedIntValue]; if (result == kTypeUnknown && outError != NULL) { if ([string hasPrefix:@"$"]) { *outError = ErrorWithProperty(kPListErrorSchemaUnknownType, NULL, kUnknownTypeErrorKey, string, @"Bad schema: unresolved macro reference \"%@\".", string); } else { *outError = ErrorWithProperty(kPListErrorSchemaUnknownType, NULL, kUnknownTypeErrorKey, string, @"Bad schema: unknown type \"%@\".", string); } } return result; } static NSString *ApplyStringFilter(NSString *string, id filterSpec, BackLinkChain keyPath, NSError **outError) { NSEnumerator *filterEnum = nil; id filter = nil; NSRange range; assert(outError != NULL); if (filterSpec == nil) return string; if ([filterSpec isKindOfClass:[NSString class]]) { filterSpec = [NSArray arrayWithObject:filterSpec]; } if ([filterSpec isKindOfClass:[NSArray class]]) { for (filterEnum = [filterSpec objectEnumerator]; (filter = [filterEnum nextObject]); ) { if ([filter isKindOfClass:[NSString class]]) { if ([filter isEqual:@"lowerCase"]) string = [string lowercaseString]; else if ([filter isEqual:@"upperCase"]) string = [string uppercaseString]; else if ([filter isEqual:@"capitalized"]) string = [string capitalizedString]; else if ([filter hasPrefix:@"truncFront:"]) { string = [string substringToIndex:[[filter substringFromIndex:11] intValue]]; } else if ([filter hasPrefix:@"truncBack:"]) { string = [string substringToIndex:[[filter substringFromIndex:10] intValue]]; } else if ([filter hasPrefix:@"subStringTo:"]) { range = [string rangeOfString:[filter substringFromIndex:12]]; if (range.location != NSNotFound) { string = [string substringToIndex:range.location]; } } else if ([filter hasPrefix:@"subStringFrom:"]) { range = [string rangeOfString:[filter substringFromIndex:14]]; if (range.location != NSNotFound) { string = [string substringFromIndex:range.location + range.length]; } } else if ([filter hasPrefix:@"subStringToInclusive:"]) { range = [string rangeOfString:[filter substringFromIndex:21]]; if (range.location != NSNotFound) { string = [string substringToIndex:range.location + range.length]; } } else if ([filter hasPrefix:@"subStringFromInclusive:"]) { range = [string rangeOfString:[filter substringFromIndex:23]]; if (range.location != NSNotFound) { string = [string substringFromIndex:range.location]; } } else { *outError = ErrorWithProperty(kPListErrorSchemaUnknownFilter, &keyPath, kUnnownFilterErrorKey, filter, @"Bad schema: unknown string filter specifier \"%@\".", filter); } } else { *outError = Error(kPListErrorSchemaUnknownFilter, &keyPath, @"Bad schema: filter specifier is not a string."); } } } else { *outError = Error(kPListErrorSchemaUnknownFilter, &keyPath, @"Bad schema: \"filter\" must be a string or an array."); } return string; } static BOOL ApplyStringTest(NSString *string, id test, SEL testSelector, NSString *testDescription, BackLinkChain keyPath, NSError **outError) { BOOL (*testIMP)(id, SEL, NSString *); NSEnumerator *testEnum = nil; id subTest = nil; assert(outError != NULL); if (test == nil) return YES; testIMP = (BOOL(*)(id, SEL, NSString *))[string methodForSelector:testSelector]; if (testIMP == NULL) { *outError = Error(kPListErrorInternal, &keyPath, @"OOPListSchemaVerifier internal error: NSString does not respond to test selector %@.", NSStringFromSelector(testSelector)); return NO; } if ([test isKindOfClass:[NSString class]]) { test = [NSArray arrayWithObject:test]; } if ([test isKindOfClass:[NSArray class]]) { for (testEnum = [test objectEnumerator]; (subTest = [testEnum nextObject]); ) { if ([subTest isKindOfClass:[NSString class]]) { if (testIMP(string, testSelector, subTest)) return YES; } else { *outError = Error(kPListErrorSchemaBadComparator, &keyPath, @"Bad schema: required %@ is not a string.", testDescription); return NO; } } } else { *outError = Error(kPListErrorSchemaBadComparator, &keyPath, @"Bad schema: %@ requirement specification is not a string or array.", testDescription); } return NO; } static NSArray *KeyPathToArray(BackLinkChain keyPath) { NSMutableArray *result = nil; BackLinkChain *curr = NULL; result = [NSMutableArray array]; for (curr = &keyPath; curr != NULL; curr = curr->link) { if (curr->element != nil) [result insertObject:curr->element atIndex:0]; } return result; } static NSString *KeyPathToString(BackLinkChain keyPath) { return [OOPListSchemaVerifier descriptionForKeyPath:KeyPathToArray(keyPath)]; } static NSString *StringForErrorReport(NSString *string) { id result = nil; if (kMaximumLengthForStringInErrorMessage < [string length]) { string = [string substringToIndex:kMaximumLengthForStringInErrorMessage]; } result = [NSMutableString stringWithString:string]; [result replaceOccurrencesOfString:@"\t" withString:@" " options:0 range:NSMakeRange(0, [string length])]; [result replaceOccurrencesOfString:@"\r\n" withString:@" \\ " options:0 range:NSMakeRange(0, [string length])]; [result replaceOccurrencesOfString:@"\n" withString:@" \\ " options:0 range:NSMakeRange(0, [string length])]; [result replaceOccurrencesOfString:@"\r" withString:@" \\ " options:0 range:NSMakeRange(0, [string length])]; if (kMaximumLengthForStringInErrorMessage < [result length]) { result = [result substringToIndex:kMaximumLengthForStringInErrorMessage - 3]; result = [result stringByAppendingString:@"..."]; } return result; } static NSString *ArrayForErrorReport(NSArray *array) { NSString *result = nil; NSString *string = nil; NSUInteger i, count; NSAutoreleasePool *pool = nil; count = [array count]; if (count == 0) return @"( )"; pool = [[NSAutoreleasePool alloc] init]; result = [NSString stringWithFormat:@"(%@", [array objectAtIndex:0]]; for (i = 1; i != count; ++i) { string = [result stringByAppendingFormat:@", %@", [array objectAtIndex:i]]; if (kMaximumLengthForStringInErrorMessage < [string length]) { result = [result stringByAppendingString:@", ..."]; break; } result = string; } result = [result stringByAppendingString:@")"]; [result retain]; [pool release]; return [result autorelease]; } static NSString *SetForErrorReport(NSSet *set) { return ArrayForErrorReport([[set allObjects] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]); } static NSString *StringOrArrayForErrorReport(id value, NSString *arrayPrefix) { if ([value isKindOfClass:[NSString class]]) { return [NSString stringWithFormat:@"\"%@\"", StringForErrorReport(value)]; } if (arrayPrefix == nil) arrayPrefix = @""; if ([value isKindOfClass:[NSArray class]]) { return [arrayPrefix stringByAppendingString:ArrayForErrorReport(value)]; } if ([value isKindOfClass:[NSSet class]]) { return [arrayPrefix stringByAppendingString:SetForErrorReport(value)]; } if (value == nil) return @"(null)"; return @""; } // Specific type verifiers #define REQUIRE_TYPE(CLASSNAME, NAMESTRING) do { \ if (![value isKindOfClass:[CLASSNAME class]]) \ { \ return ErrorTypeMismatch([CLASSNAME class], NAMESTRING, value, keyPath); \ } \ } while (0) static NSError *Verify_String(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) { NSString *filteredString = nil; id testValue = nil; NSUInteger length; NSUInteger lengthConstraint; NSError *error = nil; REQUIRE_TYPE(NSString, @"string"); DebugDump(@"* string: \"%@\"", StringForErrorReport(value)); // Apply filters filteredString = ApplyStringFilter(value, [params objectForKey:@"filter"], keyPath, &error); if (filteredString == nil) return error; // Apply substring requirements testValue = [params objectForKey:@"requiredPrefix"]; if (testValue != nil) { if (!ApplyStringTest(filteredString, testValue, @selector(hasPrefix:), @"prefix", keyPath, &error)) { if (error == nil) error = ErrorWithProperty(kPListErrorStringPrefixMissing, &keyPath, kMissingSubStringErrorKey, testValue, @"String \"%@\" does not have required %@ %@.", StringForErrorReport(value), @"prefix", StringOrArrayForErrorReport(testValue, @"in ")); return error; } } testValue = [params objectForKey:@"requiredSuffix"]; if (testValue != nil) { if (!ApplyStringTest(filteredString, testValue, @selector(hasSuffix:), @"suffix", keyPath, &error)) { if (error == nil) error = ErrorWithProperty(kPListErrorStringSuffixMissing, &keyPath, kMissingSubStringErrorKey, testValue, @"String \"%@\" does not have required %@ %@.", StringForErrorReport(value), @"suffix", StringOrArrayForErrorReport(testValue, @"in ")); return error; } } testValue = [params objectForKey:@"requiredSubString"]; if (testValue != nil) { if (!ApplyStringTest(filteredString, testValue, @selector(ooPListVerifierHasSubString:), @"substring", keyPath, &error)) { if (error == nil) error = ErrorWithProperty(kPListErrorStringSubstringMissing, &keyPath, kMissingSubStringErrorKey, testValue, @"String \"%@\" does not have required %@ %@.", StringForErrorReport(value), @"substring", StringOrArrayForErrorReport(testValue, @"in ")); return error; } } // Apply length bounds. length = [filteredString length]; lengthConstraint = [params oo_unsignedIntegerForKey:@"minLength"]; if (length < lengthConstraint) { return Error(kPListErrorMinimumConstraintNotMet, &keyPath, @"String \"%@\" is too short (%u bytes, minimum is %u).", StringForErrorReport(filteredString), length, lengthConstraint); } lengthConstraint = [params oo_unsignedIntegerForKey:@"maxLength" defaultValue:NSUIntegerMax]; if (lengthConstraint < length) { return Error(kPListErrorMaximumConstraintNotMet, &keyPath, @"String \"%@\" is too long (%u bytes, maximum is %u).", StringForErrorReport(filteredString), length, lengthConstraint); } // All tests passed. return nil; } static NSError *Verify_Array(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) { id valueType = nil; BOOL OK = YES, stop = NO; NSUInteger i, count; id subProperty = nil; NSUInteger constraint; REQUIRE_TYPE(NSArray, @"array"); DebugDump(@"* array"); // Apply count bounds. count = [value count]; constraint = [params oo_unsignedIntegerForKey:@"minCount" defaultValue:0]; if (count < constraint) { return Error(kPListErrorMinimumConstraintNotMet, &keyPath, @"Array has too few members (%u, minimum is %u).", count, constraint); } constraint = [params oo_unsignedIntegerForKey:@"maxCount" defaultValue:NSUIntegerMax]; if (constraint < count) { return Error(kPListErrorMaximumConstraintNotMet, &keyPath, @"Array has too many members (%u, maximum is %u).", count, constraint); } // Test member objects. valueType = [params objectForKey:@"valueType"]; if (valueType != nil) { for (i = 0; i != count; ++i) { subProperty = [value objectAtIndex:i]; if (![verifier verifyPList:rootPList named:name subProperty:subProperty againstSchemaType:valueType atPath:BackLinkIndex(&keyPath, i) tentative:tentative error:NULL stop:&stop]) { OK = NO; } if ((stop && !tentative) || (tentative && !OK)) break; } } *outStop = stop && !tentative; if (!OK) return ErrorFailureAlreadyReported(); else return nil; } static NSError *Verify_Dictionary(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) { NSDictionary *schema = nil; id valueType = nil, typeSpec = nil; NSEnumerator *keyEnum = nil; NSString *key = nil; id subProperty = nil; BOOL OK = YES, stop = NO, prematureExit = NO; BOOL allowOthers; NSMutableSet *requiredKeys = nil; NSArray *requiredKeyList = nil; NSUInteger count, constraint; REQUIRE_TYPE(NSDictionary, @"dictionary"); DebugDump(@"* dictionary"); // Apply count bounds. count = [value count]; constraint = [params oo_unsignedIntegerForKey:@"minCount" defaultValue:0]; if (count < constraint) { return Error(kPListErrorMinimumConstraintNotMet, &keyPath, @"Dictionary has too few pairs (%u, minimum is %u).", count, constraint); } constraint = [params oo_unsignedIntegerForKey:@"maxCount" defaultValue:NSUIntegerMax]; if (constraint < count) { return Error(kPListErrorMaximumConstraintNotMet, &keyPath, @"Dictionary has too manu pairs (%u, maximum is %u).", count, constraint); } // Get schema. schema = [params oo_dictionaryForKey:@"schema"]; valueType = [params objectForKey:@"valueType"]; allowOthers = [params oo_boolForKey:@"allowOthers" defaultValue:YES]; requiredKeyList = [params oo_arrayForKey:@"requiredKeys"]; // If these conditions are met, all members must pass: if (schema == nil && valueType == nil && requiredKeyList == nil && allowOthers) return nil; if (requiredKeyList != nil) { requiredKeys = [NSMutableSet setWithArray:requiredKeyList]; } DebugDumpIndent(); // Test member objects. for (keyEnum = [value keyEnumerator]; (key = [keyEnum nextObject]) && !stop; ) { subProperty = [(NSDictionary *)value objectForKey:key]; typeSpec = [schema objectForKey:key]; if (typeSpec == nil) typeSpec = valueType; DebugDump(@"- \"%@\"", key); DebugDumpIndent(); if (typeSpec != nil) { if (![verifier verifyPList:rootPList named:name subProperty:subProperty againstSchemaType:typeSpec atPath:BackLink(&keyPath, key) tentative:tentative error:NULL stop:&stop]) { OK = NO; } } else if (!allowOthers && ![requiredKeys containsObject:key] && [schema objectForKey:key] == nil) { // Report error now rather than returning it, since there may be several unknown keys. if (!tentative) { NSError *error = ErrorWithProperty(kPListErrorDictionaryUnknownKey, &keyPath, kUnknownKeyErrorKey, key, @"Unpermitted key \"%@\" in dictionary.", StringForErrorReport(key)); stop = ![verifier delegateVerifierWithPropertyList:rootPList named:name failedForProperty:value withError:error expectedType:params]; } OK = NO; } DebugDumpOutdent(); [requiredKeys removeObject:key]; if ((stop && !tentative) || (tentative && !OK)) { prematureExit = YES; break; } } DebugDumpOutdent(); // Check that all required keys were present. if (!prematureExit && [requiredKeys count] != 0) { return ErrorWithProperty(kPListErrorDictionaryMissingRequiredKeys, &keyPath, kMissingRequiredKeysErrorKey, requiredKeys, @"Required keys %@ missing from dictionary.", SetForErrorReport(requiredKeys)); } *outStop = stop && !tentative; if (!OK) return ErrorFailureAlreadyReported(); else return nil; } static NSError *Verify_Integer(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) { long long numericValue; long long constraint; numericValue = OOLongLongFromObject(value, 0); DebugDump(@"* integer: %lli", numericValue); // Check basic parseability. If there's inequality here, the default value is being returned. if (numericValue != OOLongLongFromObject(value, 1)) { return ErrorTypeMismatch([NSNumber class], @"integer", value, keyPath); } // Check constraints. constraint = [params oo_longLongForKey:@"minimum" defaultValue:LLONG_MIN]; if (numericValue < constraint) { return Error(kPListErrorMinimumConstraintNotMet, &keyPath, @"Number is too small (%lli, minimum is %lli).", numericValue, constraint); } constraint = [params oo_longLongForKey:@"maximum" defaultValue:LLONG_MAX]; if (constraint < numericValue) { return Error(kPListErrorMaximumConstraintNotMet, &keyPath, @"Number is too large (%lli, maximum is %lli).", numericValue, constraint); } return nil; } static NSError *Verify_PositiveInteger(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) { unsigned long long numericValue; unsigned long long constraint; numericValue = OOUnsignedLongLongFromObject(value, 0); DebugDump(@"* positive integer: %llu", numericValue); // Check basic parseability. If there's inequality here, the default value is being returned. if (numericValue != OOUnsignedLongLongFromObject(value, 1)) { return ErrorTypeMismatch([NSNumber class], @"positive integer", value, keyPath); } // Check constraints. constraint = [params oo_unsignedLongLongForKey:@"minimum" defaultValue:0]; if (numericValue < constraint) { return Error(kPListErrorMinimumConstraintNotMet, &keyPath, @"Number is too small (%llu, minimum is %llu).", numericValue, constraint); } constraint = [params oo_unsignedLongLongForKey:@"maximum" defaultValue:ULLONG_MAX]; if (constraint < numericValue) { return Error(kPListErrorMaximumConstraintNotMet, &keyPath, @"Number is too large (%llu, maximum is %llu).", numericValue, constraint); } return nil; } static NSError *Verify_Float(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) { double numericValue; double constraint; numericValue = OODoubleFromObject(value, 0); DebugDump(@"* float: %g", numericValue); // Check basic parseability. If there's inequality here, the default value is being returned. if (numericValue != OODoubleFromObject(value, 1)) { return ErrorTypeMismatch([NSNumber class], @"number", value, keyPath); } // Check constraints. constraint = [params oo_doubleForKey:@"minimum" defaultValue:-INFINITY]; if (numericValue < constraint) { return Error(kPListErrorMinimumConstraintNotMet, &keyPath, @"Number is too small (%g, minimum is %g).", numericValue, constraint); } constraint = [params oo_doubleForKey:@"maximum" defaultValue:INFINITY]; if (constraint < numericValue) { return Error(kPListErrorMaximumConstraintNotMet, &keyPath, @"Number is too large (%g, maximum is %g).", numericValue, constraint); } return nil; } static NSError *Verify_PositiveFloat(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) { double numericValue; double constraint; numericValue = OODoubleFromObject(value, 0); DebugDump(@"* positive float: %g", numericValue); // Check basic parseability. If there's inequality here, the default value is being returned. if (numericValue != OODoubleFromObject(value, 1)) { return ErrorTypeMismatch([NSNumber class], @"positive number", value, keyPath); } if (numericValue < 0) { return Error(kPListErrorNumberIsNegative, &keyPath, @"Expected non-negative number, found %g.", numericValue); } // Check constraints. constraint = [params oo_doubleForKey:@"minimum" defaultValue:0]; if (numericValue < constraint) { return Error(kPListErrorMinimumConstraintNotMet, &keyPath, @"Number is too small (%g, minimum is %g).", numericValue, constraint); } constraint = [params oo_doubleForKey:@"maximum" defaultValue:INFINITY]; if (constraint < numericValue) { return Error(kPListErrorMaximumConstraintNotMet, &keyPath, @"Number is too large (%g, maximum is %g).", numericValue, constraint); } return nil; } static NSError *Verify_OneOf(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) { NSArray *options = nil; BOOL OK = NO, stop = NO; NSEnumerator *optionEnum = nil; id option = nil; NSError *error; NSMutableDictionary *errors = nil; DebugDump(@"* oneOf"); options = [params oo_arrayForKey:@"options"]; if (options == nil) { *outStop = YES; return Error(kPListErrorSchemaNoOneOfOptions, &keyPath, @"Bad schema: no options specified for oneOf type."); } errors = [[NSMutableDictionary alloc] initWithCapacity:[options count]]; for (optionEnum = [options objectEnumerator]; (option = [optionEnum nextObject]) ;) { if ([verifier verifyPList:rootPList named:name subProperty:value againstSchemaType:option atPath:keyPath tentative:YES error:&error stop:&stop]) { DebugDump(@"> Match."); OK = YES; break; } [errors setObject:error forKey:option]; } if (!OK) { DebugDump(@"! No match."); return ErrorWithProperty(kPListErrorOneOfNoMatch, &keyPath, kErrorsByOptionErrorKey, [errors autorelease], @"No matching type rule could be found."); } // Ignore stop in tentatives. [errors release]; return nil; } static NSError *Verify_Enumeration(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) { NSArray *values = nil; NSString *filteredString = nil; NSError *error = nil; DebugDump(@"* enumeration"); REQUIRE_TYPE(NSString, @"string"); values = [params oo_arrayForKey:@"values"]; DebugDump(@" - \"%@\" in %@", StringForErrorReport(value), ArrayForErrorReport(values)); if (values == nil) { *outStop = YES; return Error(kPListErrorSchemaNoEnumerationValues, &keyPath, @"Bad schema: no options specified for oneOf type."); } filteredString = ApplyStringFilter(value, [params objectForKey:@"filter"], keyPath, &error); if (filteredString == nil) return error; if ([values containsObject:filteredString]) return nil; return Error(kPListErrorEnumerationBadValue, &keyPath, @"Value \"%@\" not recognized, should be one of %@.", StringForErrorReport(value), ArrayForErrorReport(values)); } static NSError *Verify_Boolean(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) { DebugDump(@"* boolean: %@", value); // Check basic parseability. If there's inequality here, the default value is being returned. if (OOBooleanFromObject(value, 0) == OOBooleanFromObject(value, 1)) return nil; else return ErrorTypeMismatch([NSNumber class], @"boolean", value, keyPath); } static NSError *Verify_FuzzyBoolean(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) { DebugDump(@"* fuzzy boolean: %@", value); // Check basic parseability. If there's inequality here, the default value is being returned. if (OODoubleFromObject(value, 0) == OODoubleFromObject(value, 1)) return nil; else if (OOBooleanFromObject(value, 0) == OOBooleanFromObject(value, 1)) return nil; else return ErrorTypeMismatch([NSNumber class], @"fuzzy boolean", value, keyPath); } static NSError *Verify_Vector(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) { DebugDump(@"* vector: %@", value); // Check basic parseability. If there's inequality here, the default value is being returned. if (vector_equal(OOVectorFromObject(value, kZeroVector), OOVectorFromObject(value, kBasisXVector))) return nil; else return ErrorTypeMismatch(Nil, @"vector", value, keyPath); } static NSError *Verify_Quaternion(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) { DebugDump(@"* quaternion: %@", value); // Check basic parseability. If there's inequality here, the default value is being returned. if (quaternion_equal(OOQuaternionFromObject(value, kZeroQuaternion), OOQuaternionFromObject(value, kIdentityQuaternion))) return nil; else return ErrorTypeMismatch(Nil, @"quaternion", value, keyPath); } static NSError *Verify_DelegatedType(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop) { id baseType = nil; NSString *key = nil; BOOL stop = NO; NSError *error = nil; DebugDump(@"* delegated type: %@", [params objectForKey:@"key"]); baseType = [params objectForKey:@"baseType"]; if (baseType != nil) { if (![verifier verifyPList:rootPList named:name subProperty:value againstSchemaType:baseType atPath:keyPath tentative:tentative error:NULL stop:&stop]) { *outStop = stop; return nil; } } key = [params objectForKey:@"key"]; *outStop = ![verifier delegateVerifierWithPropertyList:rootPList named:name testProperty:value atPath:keyPath againstType:key error:&error]; return error; } @implementation NSString (OOPListSchemaVerifierHelpers) - (BOOL)ooPListVerifierHasSubString:(NSString *)string { return [self rangeOfString:string].location != NSNotFound; } @end @implementation NSError (OOPListSchemaVerifierConveniences) - (NSArray *)plistKeyPath { return [[self userInfo] oo_arrayForKey:kPListKeyPathErrorKey]; } - (NSString *)plistKeyPathDescription { return [OOPListSchemaVerifier descriptionForKeyPath:[self plistKeyPath]]; } - (NSSet *)missingRequiredKeys { return [[self userInfo] oo_setForKey:kMissingRequiredKeysErrorKey]; } - (Class)expectedClass { return [[self userInfo] objectForKey:kExpectedClassErrorKey]; } - (NSString *)expectedClassName { NSString *result = [[self userInfo] objectForKey:kExpectedClassNameErrorKey]; if (result == nil) result = [[self expectedClass] description]; return result; } @end static NSError *Error(OOPListSchemaVerifierErrorCode errorCode, BackLinkChain *keyPath, NSString *format, ...) { NSError *result = nil; va_list args; va_start(args, format); result = ErrorWithDictionaryAndArguments(errorCode, keyPath, nil, format, args); va_end(args); return result; } static NSError *ErrorWithProperty(OOPListSchemaVerifierErrorCode errorCode, BackLinkChain *keyPath, NSString *propKey, id propValue, NSString *format, ...) { NSError *result = nil; va_list args; NSDictionary *dict = nil; if (propKey != nil && propValue != nil) { dict = [NSDictionary dictionaryWithObject:propValue forKey:propKey]; } va_start(args, format); result = ErrorWithDictionaryAndArguments(errorCode, keyPath, dict, format, args); va_end(args); return result; } static NSError *ErrorWithDictionary(OOPListSchemaVerifierErrorCode errorCode, BackLinkChain *keyPath, NSDictionary *dict, NSString *format, ...) { NSError *result = nil; va_list args; va_start(args, format); result = ErrorWithDictionaryAndArguments(errorCode, keyPath, dict, format, args); va_end(args); return result; } static NSError *ErrorWithDictionaryAndArguments(OOPListSchemaVerifierErrorCode errorCode, BackLinkChain *keyPath, NSDictionary *dict, NSString *format, va_list arguments) { NSString *message = nil; NSMutableDictionary *userInfo = nil; message = [[NSString alloc] initWithFormat:format arguments:arguments]; userInfo = [NSMutableDictionary dictionaryWithDictionary:dict]; [userInfo setObject:message forKey:NSLocalizedFailureReasonErrorKey]; if (keyPath != NULL) { [userInfo setObject:KeyPathToArray(*keyPath) forKey:kPListKeyPathErrorKey]; } [message release]; return [NSError errorWithDomain:kOOPListSchemaVerifierErrorDomain code:errorCode userInfo:userInfo]; } static NSError *ErrorTypeMismatch(Class expectedClass, NSString *expectedClassName, id actualObject, BackLinkChain keyPath) { NSDictionary *dict = nil; NSString *className = nil; if (expectedClassName == nil) expectedClassName = [expectedClass description]; dict = [NSDictionary dictionaryWithObjectsAndKeys: expectedClassName, kExpectedClassNameErrorKey, expectedClass, kExpectedClassErrorKey, nil]; if (actualObject == nil) className = @"nothing"; else if ([actualObject isKindOfClass:[NSString class]]) className = @"string"; else if ([actualObject isKindOfClass:[NSNumber class]]) className = @"number"; else if ([actualObject isKindOfClass:[NSArray class]]) className = @"array"; else if ([actualObject isKindOfClass:[NSDictionary class]]) className = @"dictionary"; else if ([actualObject isKindOfClass:[NSData class]]) className = @"data"; else if ([actualObject isKindOfClass:[NSDate class]]) className = @"date"; else className = [[actualObject class] description]; return ErrorWithDictionary(kPListErrorTypeMismatch, &keyPath, dict, @"Expected %@, found %@.", expectedClassName, className); } static NSError *ErrorFailureAlreadyReported(void) { return [NSError errorWithDomain:kOOPListSchemaVerifierErrorDomain code:kPListErrorFailedAndErrorHasBeenReported userInfo:nil]; } static BOOL IsFailureAlreadyReportedError(NSError *error) { return [[error domain] isEqualToString:kOOPListSchemaVerifierErrorDomain] && [error code] == kPListErrorFailedAndErrorHasBeenReported; } #endif // OO_OXP_VERIFIER_ENABLED oolite-1.82/src/Core/OXPVerifier/OOTextureVerifierStage.h000066400000000000000000000034711256642440500233240ustar00rootroot00000000000000/* OOTextureVerifierStage.h OOOXPVerifierStage which keeps track of textures (and images) that are used and ensures they are loadable. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOFileScannerVerifierStage.h" #if OO_OXP_VERIFIER_ENABLED @interface OOTextureVerifierStage: OOFileHandlingVerifierStage { @private NSMutableSet *_usedTextures; } // Returns name to be used in -dependents by other stages. + (NSString *)nameForReverseDependencyForVerifier:(OOOXPVerifier *)verifier; /* This can be called by other stages *before* the texture stage runs. The context specifies where the texture is used; something like "fooShip.dat" or "shipdata.plist materials dictionary for ship \"foo\"". It should make sense with "Texture \"foo\" referenced in " in front of it. */ - (void) textureNamed:(NSString *)name usedInContext:(NSString *)context; @end // Convenience base class for stages that need to run before OOTextureHandlingStage. @interface OOTextureHandlingStage: OOFileHandlingVerifierStage @end @interface OOOXPVerifier(OOTextureVerifierStage) - (OOTextureVerifierStage *)textureVerifierStage; @end #endif oolite-1.82/src/Core/OXPVerifier/OOTextureVerifierStage.m000066400000000000000000000121461256642440500233300ustar00rootroot00000000000000/* OOTextureVerifierStage.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOTextureVerifierStage.h" #if OO_OXP_VERIFIER_ENABLED #import "OOTextureLoader.h" #import "OOFileScannerVerifierStage.h" #import "OOMaths.h" static NSString * const kStageName = @"Testing textures and images"; @interface OOTextureVerifierStage (OOPrivate) - (void)checkTextureNamed:(NSString *)name inFolder:(NSString *)folder; @end @implementation OOTextureVerifierStage - (id)init { self = [super init]; if (self != nil) { _usedTextures = [[NSMutableSet alloc] init]; } return self; } - (void)dealloc { [_usedTextures release]; [super dealloc]; } + (NSString *)nameForReverseDependencyForVerifier:(OOOXPVerifier *)verifier { return kStageName; } - (NSString *)name { return kStageName; } - (BOOL)shouldRun { return [_usedTextures count] != 0 || [[[self verifier] fileScannerStage] filesInFolder:@"Images"] != nil; } - (void)run { NSEnumerator *nameEnum = nil; NSString *name = nil; NSAutoreleasePool *pool = nil; for (nameEnum = [_usedTextures objectEnumerator]; (name = [nameEnum nextObject]); ) { pool = [[NSAutoreleasePool alloc] init]; [self checkTextureNamed:name inFolder:@"Textures"]; [pool release]; } [_usedTextures release]; _usedTextures = nil; // All "images" are considered used, since we don't have a reasonable way to look for images referenced in JavaScript scripts. nameEnum = [[[[self verifier] fileScannerStage] filesInFolder:@"Images"] objectEnumerator]; while ((name = [nameEnum nextObject])) { [self checkTextureNamed:name inFolder:@"Images"]; } } - (void) textureNamed:(NSString *)name usedInContext:(NSString *)context { OOFileScannerVerifierStage *fileScanner = nil; if (name == nil) return; if ([_usedTextures containsObject:name]) return; [_usedTextures addObject:name]; fileScanner = [[self verifier] fileScannerStage]; if (![fileScanner fileExists:name inFolder:@"Textures" referencedFrom:context checkBuiltIn:YES]) { OOLog(@"verifyOXP.texture.notFound", @"----- WARNING: texture \"%@\" referenced in %@ could not be found in %@ or in Oolite.", name, context, [[self verifier] oxpDisplayName]); } } @end @implementation OOTextureVerifierStage (OOPrivate) - (void)checkTextureNamed:(NSString *)name inFolder:(NSString *)folder { OOTextureLoader *loader = nil; NSString *path = nil; OOFileScannerVerifierStage *fileScanner = nil; NSString *displayName = nil; OOPixMapDimension rWidth, rHeight; BOOL success; OOPixMap pixmap; OOTextureDataFormat format; fileScanner = [[self verifier] fileScannerStage]; path = [fileScanner pathForFile:name inFolder:folder referencedFrom:nil checkBuiltIn:NO]; if (path == nil) return; loader = [OOTextureLoader loaderWithPath:path options:kOOTextureMinFilterNearest | kOOTextureMinFilterNearest | kOOTextureNoShrink | kOOTextureNoFNFMessage | kOOTextureNeverScale]; displayName = [fileScanner displayNameForFile:name andFolder:folder]; if (loader == nil) { OOLog(@"verifyOXP.texture.failed", @"***** ERROR: image %@ could not be read.", displayName); } else { success = [loader getResult:&pixmap format:&format originalWidth:NULL originalHeight:NULL]; if (success) { rWidth = OORoundUpToPowerOf2_PixMap((2 * pixmap.width) / 3); rHeight = OORoundUpToPowerOf2_PixMap((2 * pixmap.height) / 3); if (pixmap.width != rWidth || pixmap.height != rHeight) { OOLog(@"verifyOXP.texture.notPOT", @"----- WARNING: image %@ has non-power-of-two dimensions; it will have to be rescaled (from %ux%u pixels to %ux%u pixels) at runtime.", displayName, pixmap.width, pixmap.height, rWidth, rHeight); } else { OOLog(@"verifyOXP.verbose.texture.OK", @"- %@ (%ux%u px) OK.", displayName, pixmap.width, pixmap.height); } OOFreePixMap(&pixmap); } else { OOLog(@"verifyOXP.texture.failed", @"***** ERROR: texture loader failed to load %@.", displayName); } } } @end @implementation OOTextureHandlingStage - (NSSet *)dependents { NSMutableSet *result = [[super dependents] mutableCopy]; [result addObject:[OOTextureVerifierStage nameForReverseDependencyForVerifier:[self verifier]]]; return [result autorelease]; } @end @implementation OOOXPVerifier(OOTextureVerifierStage) - (OOTextureVerifierStage *)textureVerifierStage { return [self stageWithName:kStageName]; } @end #endif oolite-1.82/src/Core/Octree.h000066400000000000000000000063511256642440500160250ustar00rootroot00000000000000/* Octree.h Octtree class for collision detection. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #import "OOOpenGL.h" #import "OOMaths.h" #define OCTREE_MIN_HALF_WIDTH 1.0 #if !defined(OODEBUGLDRAWING_DISABLE) && defined(NDEBUG) #define OODEBUGLDRAWING_DISABLE 1 #endif @interface Octree: NSObject { @private GLfloat _radius; uint32_t _nodeCount; const int *_octree; BOOL _hasCollision; unsigned char *_collisionOctree; NSData *_data; } /* - (id) initWithDictionary: Deserialize an octree from cache representation. (To make a new octree, build it with OOOctreeBuilder.) */ - (id) initWithDictionary:(NSDictionary *)dictionary; - (Octree *) octreeScaledBy:(GLfloat)factor; #ifndef OODEBUGLDRAWING_DISABLE - (void) drawOctree; - (void) drawOctreeCollisions; #endif - (GLfloat) isHitByLine:(Vector)v0 :(Vector)v1; - (BOOL) isHitByOctree:(Octree *)other withOrigin:(Vector)origin andIJK:(Triangle)ijk; - (BOOL) isHitByOctree:(Octree *)other withOrigin:(Vector)origin andIJK:(Triangle)ijk andScales:(GLfloat)s1 :(GLfloat)s2; - (NSDictionary *) dictionaryRepresentation; - (GLfloat) volume; - (Vector) randomPoint; #ifndef NDEBUG - (size_t) totalSize; #endif @end enum { kMaxOctreeDepth = 7 // 128x128x128 }; @interface OOOctreeBuilder: NSObject { @private int *_octree; uint_fast32_t _nodeCount, _capacity; struct OOOctreeBuildState { uint32_t insertionPoint; uint32_t remaining; } _stateStack[kMaxOctreeDepth + 1]; uint_fast8_t _level; } /* -buildOctreeWithRadius:(GLfloat)radius Generate an octree with the current data in the builder and the specified radius, and clear the builder. If NDEBUG is undefined, throws an exception if the structure of the octree is invalid. */ - (Octree *) buildOctreeWithRadius:(GLfloat)radius; /* Append nodes to the octree. There are three types of nodes: solid, empty, and inner nodes. An inner node must have exactly eight children, which may be any type of node. Exactly one node must be added at root level. The order of child nodes is defined as follows: the index of a child node is a three bit number. The highest bit represents x, the middle bit represents y and the low bit represents z. A set bit indicates the high- coordinate half of the parent node, and a clear bit indicates the low- coordinate half. For instance, if the parent node is a cube ranging from -1 to 1 on each axis, the child 101 (5) represents x 0..1, y -1..0, z 0..1. */ - (void) writeSolid; - (void) writeEmpty; - (void) beginInnerNode; - (void) endInnerNode; @end oolite-1.82/src/Core/Octree.m000066400000000000000000000705071256642440500160360ustar00rootroot00000000000000/* Octree.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "Octree.h" #import "OOMaths.h" #import "Entity.h" #import "OOOpenGL.h" #import "OODebugGLDrawing.h" #import "OOMacroOpenGL.h" #import "OODebugFlags.h" #import "NSObjectOOExtensions.h" #import "OOCollectionExtractors.h" #ifndef NDEBUG #define OctreeDebugLog(format, ...) do { if (EXPECT_NOT(gDebugFlags & DEBUG_OCTREE_LOGGING)) OOLog(@"octree.debug", format, ## __VA_ARGS__); } while (0) #else #define OctreeDebugLog(...) do {} while (0) #endif typedef struct { GLfloat radius; const int *octree; unsigned char *octree_collision; } Octree_details; @interface Octree (Private) #ifndef OODEBUGLDRAWING_DISABLE - (void) drawOctreeFromLocation:(uint32_t)loc :(GLfloat)scale :(Vector)offset; - (void) drawOctreeCollisionFromLocation:(uint32_t)loc :(GLfloat)scale :(Vector)offset; - (BOOL) hasCollision; - (void) setHasCollision:(BOOL)value; #endif - (Octree_details) octreeDetails; @end static const int change_oct[] = { 0, 1, 2, 4, 3, 5, 6, 7 }; // used to move from nearest to furthest octant static BOOL isHitByLine(const int *octbuffer, unsigned char *collbuffer, int level, GLfloat rad, Vector v0, Vector v1, Vector off, int face_hit); static GLfloat volumeOfOctree(Octree_details octree_details, unsigned depthLimit); static Vector randomFullNodeFrom(Octree_details details, Vector offset); static BOOL isHitByOctree(Octree_details axialDetails, Octree_details otherDetails, Vector delta, Triangle other_ijk); @implementation Octree - (id) init { // -init makes no sense, since octrees are immutable. [self release]; [NSException raise:NSInternalInconsistencyException format:@"Call of invalid initializer %s", __FUNCTION__]; return nil; } // Designated initializer. - (id) initWithData:(NSData *)data radius:(GLfloat)radius { if ((self = [super init])) { _data = [data copy]; _radius = radius; NSUInteger nodeCount = [_data length] / sizeof *_octree; NSParameterAssert(nodeCount < UINT32_MAX); _nodeCount = (uint32_t)nodeCount; _octree = [_data bytes]; _collisionOctree = calloc(1, _nodeCount); if (_octree == NULL || _collisionOctree == NULL) { [self release]; return nil; } } return self; } - (id) initWithDictionary:(NSDictionary *)dict { NSData *data = [dict objectForKey:@"octree"]; if (![data isKindOfClass:[NSData class]] || ([data length] % sizeof (int)) != 0) { // Invalid representation. [self release]; return nil; } return [self initWithData:data radius:[dict oo_floatForKey:@"radius"]]; } - (void) dealloc { DESTROY(_data); free(_collisionOctree); [super dealloc]; } - (BOOL) hasCollision { return _hasCollision; } - (void) setHasCollision:(BOOL)value { _hasCollision = !!value; } - (Octree_details) octreeDetails { Octree_details details = { .octree = _octree, .radius = _radius, .octree_collision = _collisionOctree }; return details; } - (Octree *) octreeScaledBy:(GLfloat)factor { // Since octree data is immutable, we can share. return [[[Octree alloc] initWithData:_data radius:_radius * factor] autorelease]; } static Vector offsetForOctant(int oct, GLfloat r) { return make_vector((0.5f - (GLfloat)((oct >> 2) & 1)) * r, (0.5f - (GLfloat)((oct >> 1) & 1)) * r, (0.5f - (GLfloat)(oct & 1)) * r); } #ifndef OODEBUGLDRAWING_DISABLE - (void) drawOctree { OODebugWFState state = OODebugBeginWireframe(NO); OO_ENTER_OPENGL(); OOGL(glEnable(GL_BLEND)); OOGLBEGIN(GL_LINES); glColor4f(0.4f, 0.4f, 0.4f, 0.5f); // it's a series of cubes [self drawOctreeFromLocation:0 :_radius : kZeroVector]; OOGLEND(); OODebugEndWireframe(state); OOCheckOpenGLErrors(@"Octree after drawing %@", self); } #if 0 #define OCTREE_COLOR(r, g, b, a) glColor4f(r, g, b, a) #else #define OCTREE_COLOR(r, g, b, a) do {} while (0) #endif - (void) drawOctreeFromLocation:(uint32_t)loc :(GLfloat)scale :(Vector)offset { if (_octree[loc] == 0) { return; } OO_ENTER_OPENGL(); if (_octree[loc] == -1) // full { // draw a cube glVertex3f(-scale + offset.x, -scale + offset.y, -scale + offset.z); glVertex3f(-scale + offset.x, scale + offset.y, -scale + offset.z); glVertex3f(-scale + offset.x, scale + offset.y, -scale + offset.z); glVertex3f(scale + offset.x, scale + offset.y, -scale + offset.z); glVertex3f(scale + offset.x, scale + offset.y, -scale + offset.z); glVertex3f(scale + offset.x, -scale + offset.y, -scale + offset.z); glVertex3f(scale + offset.x, -scale + offset.y, -scale + offset.z); glVertex3f(-scale + offset.x, -scale + offset.y, -scale + offset.z); glVertex3f(-scale + offset.x, -scale + offset.y, scale + offset.z); glVertex3f(-scale + offset.x, scale + offset.y, scale + offset.z); glVertex3f(-scale + offset.x, scale + offset.y, scale + offset.z); glVertex3f(scale + offset.x, scale + offset.y, scale + offset.z); glVertex3f(scale + offset.x, scale + offset.y, scale + offset.z); glVertex3f(scale + offset.x, -scale + offset.y, scale + offset.z); glVertex3f(scale + offset.x, -scale + offset.y, scale + offset.z); glVertex3f(-scale + offset.x, -scale + offset.y, scale + offset.z); glVertex3f(-scale + offset.x, -scale + offset.y, -scale + offset.z); glVertex3f(-scale + offset.x, -scale + offset.y, scale + offset.z); glVertex3f(-scale + offset.x, scale + offset.y, -scale + offset.z); glVertex3f(-scale + offset.x, scale + offset.y, scale + offset.z); glVertex3f(scale + offset.x, scale + offset.y, -scale + offset.z); glVertex3f(scale + offset.x, scale + offset.y, scale + offset.z); glVertex3f(scale + offset.x, -scale + offset.y, -scale + offset.z); glVertex3f(scale + offset.x, -scale + offset.y, scale + offset.z); } else if (_octree[loc] > 0) { GLfloat sc = 0.5f * scale; OCTREE_COLOR(0.4f, 0.4f, 0.4f, 0.5f); [self drawOctreeFromLocation:loc + _octree[loc] + 0 :sc :make_vector(offset.x - sc, offset.y - sc, offset.z - sc)]; OCTREE_COLOR(0.0f, 0.0f, 1.0f, 0.5f); [self drawOctreeFromLocation:loc + _octree[loc] + 1 :sc :make_vector(offset.x - sc, offset.y - sc, offset.z + sc)]; OCTREE_COLOR(0.0f, 1.0f, 0.0f, 0.5f); [self drawOctreeFromLocation:loc + _octree[loc] + 2 :sc :make_vector(offset.x - sc, offset.y + sc, offset.z - sc)]; OCTREE_COLOR(0.0f, 1.0f, 1.0f, 0.5f); [self drawOctreeFromLocation:loc + _octree[loc] + 3 :sc :make_vector(offset.x - sc, offset.y + sc, offset.z + sc)]; OCTREE_COLOR(1.0f, 0.0f, 0.0f, 0.5f); [self drawOctreeFromLocation:loc + _octree[loc] + 4 :sc :make_vector(offset.x + sc, offset.y - sc, offset.z - sc)]; OCTREE_COLOR(1.0f, 0.0f, 1.0f, 0.5f); [self drawOctreeFromLocation:loc + _octree[loc] + 5 :sc :make_vector(offset.x + sc, offset.y - sc, offset.z + sc)]; OCTREE_COLOR(1.0f, 1.0f, 0.0f, 0.5f); [self drawOctreeFromLocation:loc + _octree[loc] + 6 :sc :make_vector(offset.x + sc, offset.y + sc, offset.z - sc)]; OCTREE_COLOR(1.0f, 1.0f, 1.0f, 0.5f); [self drawOctreeFromLocation:loc + _octree[loc] + 7 :sc :make_vector(offset.x + sc, offset.y + sc, offset.z + sc)]; } } static BOOL drawTestForCollisions; - (void) drawOctreeCollisions { OODebugWFState state = OODebugBeginWireframe(NO); // it's a series of cubes drawTestForCollisions = NO; if (_hasCollision) { [self drawOctreeCollisionFromLocation:0 :_radius :kZeroVector]; } _hasCollision = drawTestForCollisions; OODebugEndWireframe(state); OOCheckOpenGLErrors(@"Octree after drawing collisions for %@", self); } - (void) drawOctreeCollisionFromLocation:(uint32_t)loc :(GLfloat)scale :(Vector)offset { if (_octree[loc] == 0) { return; } if ((_octree[loc] != 0)&&(_collisionOctree[loc] != (unsigned char)0)) // full - draw { OO_ENTER_OPENGL(); GLfloat red = (GLfloat)(_collisionOctree[loc])/255.0; glColor4f(1.0, 0.0, 0.0, red); // 50% translucent drawTestForCollisions = YES; _collisionOctree[loc]--; // draw a cube OOGL(glDisable(GL_CULL_FACE)); // face culling OOGL(glDisable(GL_TEXTURE_2D)); OOGLBEGIN(GL_LINE_STRIP); glVertex3f(-scale + offset.x, -scale + offset.y, -scale + offset.z); glVertex3f(-scale + offset.x, scale + offset.y, -scale + offset.z); glVertex3f(scale + offset.x, scale + offset.y, -scale + offset.z); glVertex3f(scale + offset.x, -scale + offset.y, -scale + offset.z); glVertex3f(-scale + offset.x, -scale + offset.y, -scale + offset.z); OOGLEND(); OOGLBEGIN(GL_LINE_STRIP); glVertex3f(-scale + offset.x, -scale + offset.y, scale + offset.z); glVertex3f(-scale + offset.x, scale + offset.y, scale + offset.z); glVertex3f(scale + offset.x, scale + offset.y, scale + offset.z); glVertex3f(scale + offset.x, -scale + offset.y, scale + offset.z); glVertex3f(-scale + offset.x, -scale + offset.y, scale + offset.z); OOGLEND(); OOGLBEGIN(GL_LINES); glVertex3f(-scale + offset.x, -scale + offset.y, -scale + offset.z); glVertex3f(-scale + offset.x, -scale + offset.y, scale + offset.z); glVertex3f(-scale + offset.x, scale + offset.y, -scale + offset.z); glVertex3f(-scale + offset.x, scale + offset.y, scale + offset.z); glVertex3f(scale + offset.x, scale + offset.y, -scale + offset.z); glVertex3f(scale + offset.x, scale + offset.y, scale + offset.z); glVertex3f(scale + offset.x, -scale + offset.y, -scale + offset.z); glVertex3f(scale + offset.x, -scale + offset.y, scale + offset.z); OOGLEND(); OOGL(glEnable(GL_CULL_FACE)); // face culling } if (_octree[loc] > 0) { GLfloat sc = 0.5f * scale; [self drawOctreeCollisionFromLocation:loc + _octree[loc] + 0 :sc :make_vector(offset.x - sc, offset.y - sc, offset.z - sc)]; [self drawOctreeCollisionFromLocation:loc + _octree[loc] + 1 :sc :make_vector(offset.x - sc, offset.y - sc, offset.z + sc)]; [self drawOctreeCollisionFromLocation:loc + _octree[loc] + 2 :sc :make_vector(offset.x - sc, offset.y + sc, offset.z - sc)]; [self drawOctreeCollisionFromLocation:loc + _octree[loc] + 3 :sc :make_vector(offset.x - sc, offset.y + sc, offset.z + sc)]; [self drawOctreeCollisionFromLocation:loc + _octree[loc] + 4 :sc :make_vector(offset.x + sc, offset.y - sc, offset.z - sc)]; [self drawOctreeCollisionFromLocation:loc + _octree[loc] + 5 :sc :make_vector(offset.x + sc, offset.y - sc, offset.z + sc)]; [self drawOctreeCollisionFromLocation:loc + _octree[loc] + 6 :sc :make_vector(offset.x + sc, offset.y + sc, offset.z - sc)]; [self drawOctreeCollisionFromLocation:loc + _octree[loc] + 7 :sc :make_vector(offset.x + sc, offset.y + sc, offset.z + sc)]; } } #endif // OODEBUGLDRAWING_DISABLE static BOOL isHitByLineSub(const int *octbuffer, unsigned char *collbuffer, int nextLevel, GLfloat rad, GLfloat rd2, Vector v0, Vector v1, int octantMask) { if (octbuffer[nextLevel + octantMask]) { Vector moveLine = offsetForOctant(octantMask, rad); return isHitByLine(octbuffer, collbuffer, nextLevel + octantMask, rd2, v0, v1, moveLine, 0); } else return NO; } static BOOL hasCollided = NO; static GLfloat hit_dist = 0.0; static BOOL isHitByLine(const int *octbuffer, unsigned char *collbuffer, int level, GLfloat rad, Vector v0, Vector v1, Vector off, int face_hit) { // displace the line by the offset Vector u0 = make_vector(v0.x + off.x, v0.y + off.y, v0.z + off.z); Vector u1 = make_vector(v1.x + off.x, v1.y + off.y, v1.z + off.z); OctreeDebugLog(@"DEBUG octant: [%d] radius: %.2f vs. line: (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)", level, rad, u0.x, u0.y, u0.z, u1.x, u1.y, u1.z); if (octbuffer[level] == 0) { OctreeDebugLog(@"DEBUG Hit an empty octant: [%d]", level); return NO; } if (octbuffer[level] == -1) { OctreeDebugLog(@"DEBUG Hit a solid octant: [%d]", level); collbuffer[level] = 2; // green hit_dist = sqrt(u0.x * u0.x + u0.y * u0.y + u0.z * u0.z); return YES; } int faces = face_hit; if (faces == 0) faces = lineCubeIntersection(u0, u1, rad); if (faces == 0) { OctreeDebugLog(@"----> Line misses octant: [%d].", level); return NO; } int octantIntersected = 0; if (faces > 0) { Vector vi = lineIntersectionWithFace(u0, u1, faces, rad); if (CUBE_FACE_FRONT & faces) octantIntersected = ((vi.x < 0.0)? 1: 5) + ((vi.y < 0.0)? 0: 2); if (CUBE_FACE_BACK & faces) octantIntersected = ((vi.x < 0.0)? 0: 4) + ((vi.y < 0.0)? 0: 2); if (CUBE_FACE_RIGHT & faces) octantIntersected = ((vi.y < 0.0)? 4: 6) + ((vi.z < 0.0)? 0: 1); if (CUBE_FACE_LEFT & faces) octantIntersected = ((vi.y < 0.0)? 0: 2) + ((vi.z < 0.0)? 0: 1); if (CUBE_FACE_TOP & faces) octantIntersected = ((vi.x < 0.0)? 2: 6) + ((vi.z < 0.0)? 0: 1); if (CUBE_FACE_BOTTOM & faces) octantIntersected = ((vi.x < 0.0)? 0: 4) + ((vi.z < 0.0)? 0: 1); OctreeDebugLog(@"----> found intersection with face 0x%2x of cube of radius %.2f at (%.2f, %.2f, %.2f) octant:%d", faces, rad, vi.x, vi.y, vi.z, octantIntersected); } else { OctreeDebugLog(@"----> inside cube of radius %.2f octant:%d", rad, octantIntersected); } hasCollided = YES; collbuffer[level] = 1; // red OctreeDebugLog(@"----> testing octants..."); int nextLevel = level + octbuffer[level]; GLfloat rd2 = 0.5f * rad; // first test the intersected octant, then the three adjacent, then the next three adjacent, the finally the diagonal octant int oct0, oct1, oct2, oct3; // test oct0 then oct1, oct2, oct3 then (7 - oct1), (7 - oct2), (7 - oct3) then (7 - oct0) oct0 = octantIntersected; oct1 = oct0 ^ 0x01; // adjacent x oct2 = oct0 ^ 0x02; // adjacent y oct3 = oct0 ^ 0x04; // adjacent z OctreeDebugLog(@"----> testing first octant hit [+%d]", oct0); if (isHitByLineSub(octbuffer, collbuffer, nextLevel, rad, rd2, u0, u1, oct0)) return YES; // first octant // test the three adjacent octants OctreeDebugLog(@"----> testing next three octants [+%d] [+%d] [+%d]", oct1, oct2, oct3); if (isHitByLineSub(octbuffer, collbuffer, nextLevel, rad, rd2, u0, u1, oct1)) return YES; // second octant if (isHitByLineSub(octbuffer, collbuffer, nextLevel, rad, rd2, u0, u1, oct2)) return YES; // third octant if (isHitByLineSub(octbuffer, collbuffer, nextLevel, rad, rd2, u0, u1, oct3)) return YES; // fourth octant // go to the next four octants oct0 ^= 0x07; oct1 ^= 0x07; oct2 ^= 0x07; oct3 ^= 0x07; OctreeDebugLog(@"----> testing back three octants [+%d] [+%d] [+%d]", oct1, oct2, oct3); if (isHitByLineSub(octbuffer, collbuffer, nextLevel, rad, rd2, u0, u1, oct1)) return YES; // fifth octant if (isHitByLineSub(octbuffer, collbuffer, nextLevel, rad, rd2, u0, u1, oct2)) return YES; // sixth octant if (isHitByLineSub(octbuffer, collbuffer, nextLevel, rad, rd2, u0, u1, oct3)) return YES; // seventh octant // and check the last octant OctreeDebugLog(@"----> testing final octant [+%d]", oct0); if (isHitByLineSub(octbuffer, collbuffer, nextLevel, rad, rd2, u0, u1, oct0)) return YES; // last octant return NO; } - (GLfloat) isHitByLine:(Vector)v0 :(Vector)v1 { memset(_collisionOctree, 0, _nodeCount * sizeof *_collisionOctree); hasCollided = NO; if (isHitByLine(_octree, _collisionOctree, 0, _radius, v0, v1, kZeroVector, 0)) { OctreeDebugLog(@"DEBUG Hit at distance %.2f", hit_dist); _hasCollision = hasCollided; return hit_dist; } else { OctreeDebugLog(@"DEBUG Missed!"); _hasCollision = hasCollided; return 0.0f; } } static BOOL isHitByOctree(Octree_details axialDetails, Octree_details otherDetails, Vector otherPosition, Triangle other_ijk) { const int *axialBuffer = axialDetails.octree; const int *otherBuffer = otherDetails.octree; if (axialBuffer[0] == 0) { OctreeDebugLog(@"DEBUG Axial octree is empty."); return NO; } if (!otherBuffer) { OctreeDebugLog(@"DEBUG Other octree is undefined."); return NO; } if (otherBuffer[0] == 0) { OctreeDebugLog(@"DEBUG Other octree is empty."); return NO; } GLfloat axialRadius = axialDetails.radius; GLfloat otherRadius = otherDetails.radius; if (otherRadius < axialRadius) // test axial cube against other sphere { // 'crude and simple' - test sphere against cube... if ((otherPosition.x + otherRadius < -axialRadius)||(otherPosition.x - otherRadius > axialRadius)|| (otherPosition.y + otherRadius < -axialRadius)||(otherPosition.y - otherRadius > axialRadius)|| (otherPosition.z + otherRadius < -axialRadius)||(otherPosition.z - otherRadius > axialRadius)) { OctreeDebugLog(@"----> Other sphere does not intersect axial cube"); return NO; } } else // test axial sphere against other cube { Vector d2 = vector_flip(otherPosition); Vector axialPosition = resolveVectorInIJK(d2, other_ijk); if ((axialPosition.x + axialRadius < -otherRadius)||(axialPosition.x - axialRadius > otherRadius)|| (axialPosition.y + axialRadius < -otherRadius)||(axialPosition.y - axialRadius > otherRadius)|| (axialPosition.z + axialRadius < -otherRadius)||(axialPosition.z - axialRadius > otherRadius)) { OctreeDebugLog(@"----> Axial sphere does not intersect other cube"); return NO; } } // from here on, this Octree and the other Octree are considered to be intersecting unsigned char *axialCollisionBuffer = axialDetails.octree_collision; unsigned char *otherCollisionBuffer = otherDetails.octree_collision; if (axialBuffer[0] == -1) { // we are SOLID - is the other octree? if (otherBuffer[0] == -1) { // YES so octrees collide axialCollisionBuffer[0] = (unsigned char)255; // mark otherCollisionBuffer[0] = (unsigned char)255; // mark OctreeDebugLog(@"DEBUG Octrees collide!"); return YES; } // the other octree must be decomposed // and each of its octants tested against the axial octree // if any of them collides with this octant // then we have a solid collision OctreeDebugLog(@"----> testing other octants..."); // work out the nearest octant to the axial octree int nearest_oct = ((otherPosition.x > 0.0)? 0:4)|((otherPosition.y > 0.0)? 0:2)|((otherPosition.z > 0.0)? 0:1); int nextLevel = otherBuffer[0]; const int *nextBuffer = &otherBuffer[nextLevel]; unsigned char *nextCollisionBuffer = &otherCollisionBuffer[nextLevel]; Octree_details nextDetails; Vector voff, nextPosition; unsigned i, oct; nextDetails.radius = 0.5f * otherRadius; for (i = 0; i < 8; i++) { oct = nearest_oct ^ change_oct[i]; // work from nearest to furthest if (nextBuffer[oct]) // don't test empty octants { nextDetails.octree = &nextBuffer[oct]; nextDetails.octree_collision = &nextCollisionBuffer[oct]; voff = resolveVectorInIJK(offsetForOctant(oct, otherRadius), other_ijk); nextPosition = vector_subtract(otherPosition, voff); if (isHitByOctree(axialDetails, nextDetails, nextPosition, other_ijk)) // test octant return YES; } } // otherwise return NO; } // we are not solid // we must test each of our octants against // the other octree, if any of them collide // we have a solid collision OctreeDebugLog(@"----> testing axial octants..."); // work out the nearest octant to the other octree int nearest_oct = ((otherPosition.x > 0.0)? 4:0)|((otherPosition.y > 0.0)? 2:0)|((otherPosition.z > 0.0)? 1:0); int nextLevel = axialBuffer[0]; const int *nextBuffer = &axialBuffer[nextLevel]; unsigned char *nextCollisionBuffer = &axialCollisionBuffer[nextLevel]; Vector nextPosition; Octree_details nextDetails; unsigned i, oct; nextDetails.radius = 0.5f * axialRadius; for (i = 0; i < 8; i++) { oct = nearest_oct ^ change_oct[i]; // work from nearest to furthest if (nextBuffer[oct]) // don't test empty octants { nextDetails.octree = &nextBuffer[oct]; nextDetails.octree_collision = &nextCollisionBuffer[oct]; nextPosition = vector_add(otherPosition, offsetForOctant(oct, axialRadius)); if (isHitByOctree(nextDetails, otherDetails, nextPosition, other_ijk)) { return YES; // test octant } } } // otherwise we're done! return NO; } - (BOOL) isHitByOctree:(Octree *)other withOrigin:(Vector)v0 andIJK:(Triangle)ijk { if (other == nil) return NO; BOOL hit = isHitByOctree([self octreeDetails], [other octreeDetails], v0, ijk); _hasCollision = _hasCollision || hit; [other setHasCollision: [other hasCollision] || hit]; return hit; } - (BOOL) isHitByOctree:(Octree *)other withOrigin:(Vector)v0 andIJK:(Triangle)ijk andScales:(GLfloat) s1 :(GLfloat)s2 { Octree_details details1 = [self octreeDetails]; Octree_details details2 = [other octreeDetails]; details1.radius *= s1; details2.radius *= s2; BOOL hit = isHitByOctree(details1, details2, v0, ijk); _hasCollision = _hasCollision || hit; [other setHasCollision: [other hasCollision] || hit]; return hit; } - (NSDictionary *) dictionaryRepresentation { return [NSDictionary dictionaryWithObjectsAndKeys: _data, @"octree", [NSNumber numberWithFloat:_radius], @"radius", nil]; } static GLfloat volumeOfOctree(Octree_details octree_details, unsigned depthLimit) { const int *octBuffer = octree_details.octree; GLfloat octRadius = octree_details.radius; if (octBuffer[0] == 0) { return 0.0f; } if (octBuffer[0] == -1 || depthLimit == 0) { return octRadius * octRadius * octRadius; } depthLimit--; // We are not empty or solid. // We sum the volume of each of our octants. GLfloat sumVolume = 0.0f; Octree_details nextDetails; int nextLevel = octBuffer[0]; const int *nextBuffer = &octBuffer[nextLevel]; unsigned i; nextDetails.radius = 0.5f * octRadius; nextDetails.octree_collision = NULL; // Placate static analyzer for (i = 0; i < 8; i++) { if (nextBuffer[i]) // don't test empty octants { nextDetails.octree = &nextBuffer[i]; sumVolume += volumeOfOctree(nextDetails, depthLimit); } } return sumVolume; } - (GLfloat) volume { /* For backwards compatibility, limit octree iteration for volume calculation to five levels. Raising the limit means lower calculated volumes for large ships. EMMSTRAN: consider raising the limit but fudging mass lock calculations to compensate. See http://aegidian.org/bb/viewtopic.php?f=3&t=9176 . Then again, five levels of iteration might be fine. -- Ahruman 2011-03-10 */ return volumeOfOctree([self octreeDetails], 5); } static Vector randomFullNodeFrom(Octree_details details, Vector offset) { const int *octBuffer = details.octree; GLfloat octRadius = details.radius; if (octBuffer[0] == 0) return offset; if (octBuffer[0] == -1) return offset; // We are not empty or solid. // Pick a location from a random octant. Octree_details nextDetails; int nextLevel = octBuffer[0]; const int *nextBuffer = &octBuffer[nextLevel]; unsigned i, oct; nextDetails.radius = 0.5f * octRadius; nextDetails.octree_collision = NULL; oct = Ranrot() & 7; for (i = 0; i < 8; i++) { int octant = oct ^ i; if (nextBuffer[octant]) // don't test empty octants { nextDetails.octree = &nextBuffer[octant]; Vector voff = vector_add(offset, offsetForOctant(octant, octRadius)); return randomFullNodeFrom(nextDetails, voff); } } return offset; } - (Vector) randomPoint { return randomFullNodeFrom([self octreeDetails], kZeroVector); } #ifndef NDEBUG - (size_t) totalSize { return [self oo_objectSize] + _nodeCount * [_data oo_objectSize] + [_data length] + _nodeCount * sizeof *_collisionOctree; } #endif @end enum { /* Initial capacity of OOOctreeBuilder. 16 KibiEntries is enough to handle all but 2 built-in models; I expect similar results for OXPs, since geometry detail has limited effect on the octree. Large models with higher depth are more likely to need growth. */ kMinimumBuilderCapacity = 16 << 10 }; @implementation OOOctreeBuilder // Extract top of state stack. OOINLINE struct OOOctreeBuildState *State(OOOctreeBuilder *self) { return &self->_stateStack[self->_level]; } // SetNode(): set the value of an entry in _octree, making space if needed. static void SetNode_slow(OOOctreeBuilder *self, uint32_t index, int value) NO_INLINE_FUNC; OOINLINE void SetNode(OOOctreeBuilder *self, uint32_t index, int value) { if (index < self->_capacity) { self->_octree[index] = value; } else { SetNode_slow(self, index, value); } } // InsertNode(): set the node at the current insertion point, and increment insertion point. OOINLINE void InsertNode(OOOctreeBuilder *self, int value) { NSCAssert(State(self)->remaining > 0, @"Attempt to add node to a full parent in octree builder."); State(self)->remaining--; SetNode(self, State(self)->insertionPoint++, value); } - (id) init { if ((self = [super init])) { _capacity = kMinimumBuilderCapacity; _octree = malloc(_capacity * sizeof *_octree); if (_octree == NULL) { [self release]; return nil; } /* We're initially inserting into the root slot, which must exist and takes a single node. */ _nodeCount = 1; State(self)->remaining = 1; } return self; } - (void) dealloc { free(_octree); [super dealloc]; } - (Octree *) buildOctreeWithRadius:(GLfloat)radius { NSAssert(State(self)->remaining == 0 && _level == 0, @"Attempt to produce octree from an octree builder in an incomplete state."); size_t dataSize = _nodeCount * sizeof *_octree; int *resized = realloc(_octree, dataSize); if (resized == NULL) resized = _octree; /* Hand over the bytes to the data object, which will be used directly by the Octree. */ NSData *data = [NSData dataWithBytesNoCopy:resized length:dataSize freeWhenDone:YES]; _octree = NULL; _nodeCount = 0; _capacity = 0; return [[[Octree alloc] initWithData:data radius:radius] autorelease]; } - (void) writeSolid { InsertNode(self, -1); } - (void) writeEmpty { InsertNode(self, 0); } - (void) beginInnerNode { NSAssert(_level < kMaxOctreeDepth, @"Attempt to build octree exceeding maximum depth."); // Insert relative offset to next free space. uint32_t newInsertionPoint = _nodeCount; InsertNode(self, (int)_nodeCount - State(self)->insertionPoint); /* Leave space for eight nodes. NOTE: this may leave memory uninitialized or leave _nodeCount pointing past the end of the buffer. A valid sequence of eight child insertions will fix both these problems by writing to all of the new slots. */ _nodeCount += 8; // Push state and set up new "stack frame". _level++; State(self)->insertionPoint = newInsertionPoint; State(self)->remaining = 8; } - (void) endInnerNode { NSAssert(State(self)->remaining == 0, @"Attempt to end an inner octree node with fewer than eight children."); NSAssert1(_level > 0, @"Unbalanced call to %s", __FUNCTION__); _level--; /* Check if the last eight nodes are solid. If so, we just inserted an entirely solid subtree and can fold it into a single solid node. An entirely solid subtree will always use the last eight nodes (any solid subtrees within a child node will have been folded already), so this simple approach to folding will produce an optimal result. We could do the same for empty nodes, but OOMeshToOctreeConverter will never recurse into an empty subtree. */ NSAssert(_nodeCount > 8, @"After ending an inner node, there must be at least eight nodes in buffer."); for (uint_fast32_t node = _nodeCount - 8; node < _nodeCount; node++) { if (_octree[node] != -1) return; } // If we got here, subtree is solid; fold it into a solid node. _octree[State(self)->insertionPoint - 1] = -1; _nodeCount -= 8; } // Slow path for SetNode() when writing beyond _capacity: expand, then perform set. static void SetNode_slow(OOOctreeBuilder *self, uint32_t index, int value) { uint32_t newCapacity = MAX(self->_capacity * 2, (uint32_t)kMinimumBuilderCapacity); newCapacity = MAX(newCapacity, index + 1); int *newBuffer = realloc(self->_octree, newCapacity * sizeof *newBuffer); if (EXPECT_NOT(newBuffer == NULL)) { [NSException raise:NSMallocException format:@"Failed to allocate memory for octree."]; } self->_octree = newBuffer; self->_capacity = newCapacity; self->_octree[index] = value; } #ifndef NDEBUG /* This method exists purely to suppress Clang static analyzer warnings that these ivars are unused (but may be used by categories, which they are). */ - (BOOL) suppressClangStuff { return &_stateStack && 0; } #endif @end oolite-1.82/src/Core/OldSchoolPropertyListWriting.h000066400000000000000000000041321256642440500224320ustar00rootroot00000000000000/* OldSchoolPropertyListWriting.h Copyright 2006-2013 Jens Ayton A protocol for writing property lists in the OpenStep/simple text format. Why? Because as of Tiger, the system functions to write plists reject the format. I, however, like it, because it’s clear and legible. Fight the power! Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import @protocol OldSchoolPropertyListWriting - (NSString *)oldSchoolPListFormatWithIndentation:(unsigned)inIndentation errorDescription:(NSString **)outErrorDescription; @end @interface NSObject (OldSchoolPropertyListWriting) - (NSData *)oldSchoolPListFormatWithErrorDescription:(NSString **)outErrorDescription; @end @interface NSString (OldSchoolPropertyListWriting) @end @interface NSNumber (OldSchoolPropertyListWriting) @end @interface NSData (OldSchoolPropertyListWriting) @end @interface NSArray (OldSchoolPropertyListWriting) @end @interface NSDictionary (OldSchoolPropertyListWriting) @end oolite-1.82/src/Core/OldSchoolPropertyListWriting.m000066400000000000000000000226501256642440500224440ustar00rootroot00000000000000/* OldSchoolPropertyListWriting.m Copyright 2006-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #import "OldSchoolPropertyListWriting.h" #import "NSNumberOOExtensions.h" static void AppendNewLineAndIndent(NSMutableString *ioString, unsigned indentDepth); @implementation NSString (OldSchoolPropertyListWriting) - (NSString *)oldSchoolPListFormatWithIndentation:(unsigned)inIndentation errorDescription:(NSString **)outErrorDescription { NSCharacterSet *charSet; NSRange foundRange, searchRange; NSString *foundString; NSMutableString *newString; NSUInteger length; length = [self length]; if (0 != length && [self rangeOfCharacterFromSet:[[NSCharacterSet alphanumericCharacterSet] invertedSet]].location == NSNotFound && ![[NSCharacterSet decimalDigitCharacterSet] longCharacterIsMember:[self characterAtIndex:0]]) { // This is an alphanumeric string whose first character is not a digit return [[self copy] autorelease]; } else { charSet = [NSCharacterSet characterSetWithCharactersInString:@"\"\r\n\\"]; foundRange = [self rangeOfCharacterFromSet:charSet options:NSLiteralSearch]; if (NSNotFound == foundRange.location) { newString = (NSMutableString *)self; } else { // Escape quotes, backslashes and newlines newString = [[[self substringToIndex:foundRange.location] mutableCopy] autorelease]; for (;;) { // Append escaped character foundString = [self substringWithRange:foundRange]; if ([foundString isEqual:@"\""]) [newString appendString:@"\\\""]; else if ([foundString isEqual:@"\n"]) [newString appendString:@"\\\n"]; else if ([foundString isEqual:@"\r"]) [newString appendString:@"\\\r"]; else if ([foundString isEqual:@"\\"]) [newString appendString:@"\\\\"]; else { [NSException raise:NSInternalInconsistencyException format:@"%s: expected \" or newline, found %@", __PRETTY_FUNCTION__, foundString]; } // Use rest of string… searchRange.location = foundRange.location + foundRange.length; searchRange.length = length - searchRange.location; // …to search for next char needing escaping foundRange = [self rangeOfCharacterFromSet:charSet options:NSLiteralSearch range:searchRange]; if (NSNotFound == foundRange.location) { [newString appendString:[self substringWithRange:searchRange]]; break; } } } return [NSString stringWithFormat:@"\"%@\"", newString]; } } @end @implementation NSNumber (OldSchoolPropertyListWriting) - (NSString *)oldSchoolPListFormatWithIndentation:(unsigned)inIndentation errorDescription:(NSString **)outErrorDescription { NSString *result; double dVal; if ([self oo_isBoolean]) { if ([self boolValue]) result = @"true"; else result = @"false"; } else if ([self oo_isFloatingPointNumber]) { dVal = [self doubleValue]; result = [NSString stringWithFormat:@"%.8g", dVal]; } else result = [NSString stringWithFormat:@"%@", self]; // Allow infinities, but remember that they’ll be read in as strings #if 0 if ([result isEqual:@"inf"] || [result isEqual:@"-inf"]) { *outErrorDescription = @"infinities cannot be represented in old-school property lists"; return nil; } #endif return result; } @end @implementation NSData (OldSchoolPropertyListWriting) - (NSString *)oldSchoolPListFormatWithIndentation:(unsigned)inIndentation errorDescription:(NSString **)outErrorDescription { const uint8_t *srcBytes; uint8_t *dstBytes, *curr; NSUInteger i, j, srcLength, dstLength; const char hexTable[] = "0123456789ABCDEF"; NSString *result; srcBytes = [self bytes]; srcLength = [self length]; dstLength = 2 * srcLength + srcLength/8 + 2 + (srcLength/64 * (1 + inIndentation)); dstBytes = malloc(dstLength); if (dstBytes == NULL) { if (NULL != outErrorDescription) { *outErrorDescription = [NSString stringWithFormat:@"failed to allocate space (%lu bytes) for conversion of NSData to old-school property list representation", dstLength]; } return nil; } curr = dstBytes; *curr++ = '<'; for (i = 0; i != srcLength; ++i) { if (0 != i && 0 == (i & 3)) { if (0 == (i & 31)) { *curr++ = '\n'; j = inIndentation; while (--j) *curr++ = '\t'; } *curr++ = ' '; } *curr++ = hexTable[srcBytes[i] >> 4]; *curr++ = hexTable[srcBytes[i] & 0xF]; } *curr = '>'; assert((size_t)(curr - dstBytes) <= dstLength); result = [[NSString alloc] initWithBytesNoCopy:dstBytes length:dstLength encoding:NSASCIIStringEncoding freeWhenDone:YES]; return [result autorelease]; } @end @implementation NSArray (OldSchoolPropertyListWriting) - (NSString *)oldSchoolPListFormatWithIndentation:(unsigned)inIndentation errorDescription:(NSString **)outErrorDescription { NSMutableString *result; NSUInteger i, count; id object; result = [NSMutableString string]; [result appendString:@"("]; count = [self count]; AppendNewLineAndIndent(result, inIndentation + 1); for (i = 0; i != count; ++i) { if (0 != i) { [result appendString:@","]; AppendNewLineAndIndent(result, inIndentation + 1); } object = [self objectAtIndex:i]; if (![object conformsToProtocol:@protocol (OldSchoolPropertyListWriting)]) { if (nil != object && NULL != outErrorDescription) { *outErrorDescription = [NSString stringWithFormat:@"non-plist object in dictionary"]; } return nil; } object = [object oldSchoolPListFormatWithIndentation:inIndentation + 1 errorDescription:outErrorDescription]; if (nil == object) return nil; [result appendString:object]; } AppendNewLineAndIndent(result, inIndentation); [result appendString:@")"]; return result; } @end @implementation NSDictionary (OldSchoolPropertyListWriting) - (NSString *)oldSchoolPListFormatWithIndentation:(unsigned)inIndentation errorDescription:(NSString **)outErrorDescription { NSMutableString *result; NSUInteger i, count; NSArray *allKeys; id key, value; NSString *valueDesc; result = [NSMutableString string]; allKeys = [[self allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; count = [allKeys count]; [result appendString:@"{"]; AppendNewLineAndIndent(result, inIndentation + 1); for (i = 0; i != count; ++i) { if (0 != i) { AppendNewLineAndIndent(result, inIndentation + 1); } key = [allKeys objectAtIndex:i]; if (![key isKindOfClass:[NSString class]]) { if (NULL != outErrorDescription) *outErrorDescription = [NSString stringWithFormat:@"non-string key in dictionary"]; return nil; } value = [self objectForKey:key]; if (![value conformsToProtocol:@protocol(OldSchoolPropertyListWriting)]) { if (nil != value && NULL != outErrorDescription) { *outErrorDescription = [NSString stringWithFormat:@"non-plist object in dictionary"]; } return nil; } key = [key oldSchoolPListFormatWithIndentation:inIndentation + 1 errorDescription:outErrorDescription]; if (nil == key) return nil; valueDesc = [value oldSchoolPListFormatWithIndentation:inIndentation + 1 errorDescription:outErrorDescription]; if (nil == valueDesc) return nil; [result appendFormat:@"%@ =", key]; if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) { AppendNewLineAndIndent(result, inIndentation + 1); } else { [result appendString:@" "]; } [result appendFormat:@"%@;", valueDesc]; } AppendNewLineAndIndent(result, inIndentation); [result appendString:@"}"]; return result; } @end @interface NSObject (OldSchoolPropertyListWriting_Private) - (NSString *)oldSchoolPListFormatWithIndentation:(unsigned)inIndentation errorDescription:(NSString **)outErrorDescription; @end @implementation NSObject (OldSchoolPropertyListWriting) - (NSData *)oldSchoolPListFormatWithErrorDescription:(NSString **)outErrorDescription { NSString *string; string = [self oldSchoolPListFormatWithIndentation:0 errorDescription:outErrorDescription]; return [[string stringByAppendingString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding]; } - (NSString *)oldSchoolPListFormatWithIndentation:(unsigned)inIndentation errorDescription:(NSString **)outErrorDescription { if (NULL != outErrorDescription) { *outErrorDescription = [NSString stringWithFormat:@"Class %@ does not support OldSchoolPropertyListWriting", [self className]]; } return nil; } @end static void AppendNewLineAndIndent(NSMutableString *ioString, unsigned indentDepth) { [ioString appendString:@"\n"]; while (indentDepth--) [ioString appendString:@"\t"]; } oolite-1.82/src/Core/ReleaseLockProxy.h000066400000000000000000000017011256642440500200310ustar00rootroot00000000000000/* ReleaseLockProxy.h By Jens Ayton This code is hereby placed in the public domain. Hacky debug utility. A ReleaseLockProxy proxies an object, and stops the proxied object from being released until -rlpAllowRelease is called. All releases are logged, and one that would cause the object to die is stopped. Breakpoints in release may be handy. */ #import @interface ReleaseLockProxy: NSProxy { @private id _object; NSString *_name; BOOL _locked; } + (id)proxyWithObject:(id)object name:(NSString *)name; + (id)proxyWithRetainedObject:(id)object name:(NSString *)name; // Doesn't retain the object - (id)initWithObject:(id)object name:(NSString *)name; - (id)initWithRetainedObject:(id)object name:(NSString *)name; // Doesn't retain the object - (void)rlpAllowRelease; - (NSString *)rlpObjectDescription; // name if not nil, otherwise object description. @end oolite-1.82/src/Core/ReleaseLockProxy.m000066400000000000000000000102711256642440500200400ustar00rootroot00000000000000/* ReleaseLockProxy.h By Jens Ayton This code is hereby placed in the public domain. */ #import "ReleaseLockProxy.h" #define VERBOSE 0 #if VERBOSE #define VerboseLog NSLog #else #define VerboseLog(...) do {} while (0) #endif @interface ReleaseLockProxy_SignatureTemplateClass: NSObject // These exist purely to provide NSMethodSignatures for -[ReleaseLockProxy methodSignatureForSelector:]. + (id)initWithObject:(id)object name:(NSString *)name; + (id)initWithRetainedObject:(id)object name:(NSString *)name; + (void)rlpAllowRelease; + (NSString *)rlpObjectDescription; @end @implementation ReleaseLockProxy // *** Boilerplate + (id)proxyWithObject:(id)object name:(NSString *)name { return [[[self alloc] initWithObject:object name:name] autorelease]; } + (id)proxyWithRetainedObject:(id)object name:(NSString *)name { return [[[self alloc] initWithRetainedObject:object name:name] autorelease]; } - (id)initWithObject:(id)object name:(NSString *)name { return [self initWithRetainedObject:[object retain] name:name]; } - (id)initWithRetainedObject:(id)object name:(NSString *)name { if (object == nil) { NSLog(@"** ReleaseLockProxy: passed nil object, returning nil proxy."); [self release]; return nil; } // No super init for proxies. _object = object; _name = [name copy]; _locked = YES; return self; } - (void)dealloc { if (_locked) { NSLog(@"** ReleaseLockProxy (%@): deallocated while locked. This shouldn't happen, unless -dealloc is being called directly.", [self rlpObjectDescription]); } else { VerboseLog(@"-- ReleaseLockProxy (%@): deallocated while not locked.", [self rlpObjectDescription]); } [_object release]; [_name release]; [super dealloc]; } - (void)rlpAllowRelease { _locked = NO; } - (NSString *)rlpObjectDescription { return _name ? _name : [_object description]; } // *** Core functionality - (void)release { unsigned retainCount = [self retainCount]; if (_locked && retainCount == 1) { // Breakpoint here to catch what would otherwise be the last release before crashing. NSLog(@"** ReleaseLockProxy (%@): released while locked and retain count is one - intercepting retain. Something is broken.", [self rlpObjectDescription]); return; } if (_locked) { VerboseLog(@"-- ReleaseLockProxy (%@): released while locked, but retain count > 1; retain count going from %u to %u.", [self rlpObjectDescription], retainCount, retainCount - 1); } else { VerboseLog(@"-- ReleaseLockProxy (%@): released while not locked; retain count going from %u to %u.", [self rlpObjectDescription], retainCount, retainCount - 1); } [super release]; } - (id)autorelease { VerboseLog(@"-- ReleaseLockProxy (%@): autoreleased while %slocked and retain count at %u.", [self rlpObjectDescription], _locked ? "" : "not ", [self retainCount]); return [super autorelease]; } // *** Proxy stuff. - (void)forwardInvocation:(NSInvocation *)invocation { [invocation invokeWithTarget:_object]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { NSMethodSignature *result = nil; if (selector == @selector(initWithObject:name:) || selector == @selector(initWithRetainedObject:name:) || selector == @selector(rlpAllowRelease) || selector == @selector(rlpObjectDescription)) { result = [ReleaseLockProxy_SignatureTemplateClass methodSignatureForSelector:selector]; } else { result = [(id)_object methodSignatureForSelector:selector]; } return result; } + (BOOL)instancesRespondToSelector:(SEL)selector { if (selector == @selector(initWithObject:name:) || selector == @selector(initWithRetainedObject:name:) || selector == @selector(rlpAllowRelease) || selector == @selector(rlpObjectDescription)) { return YES; } else { return NO; } } - (BOOL)respondsToSelector:(SEL)selector { return [ReleaseLockProxy instancesRespondToSelector:selector]; } @end @implementation ReleaseLockProxy_SignatureTemplateClass + (id)initWithObject:(id)object name:(NSString *)name { return nil; } + (id)initWithRetainedObject:(id)object name:(NSString *)name { return nil; } + (void)rlpAllowRelease { } + (NSString *)rlpObjectDescription { return nil; } @end oolite-1.82/src/Core/ResourceManager.h000066400000000000000000000127511256642440500176670ustar00rootroot00000000000000/* ResourceManager.h Singleton class responsible for loading various data files. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #import "OOOpenGL.h" #import "NSFileManagerOOExtensions.h" @class OOSound, OOMusic, OOSystemDescriptionManager; typedef enum { MERGE_NONE, // Just use the last file in search order. MERGE_BASIC, // Merge files by adding the top-level items of each file. MERGE_SMART // Merge files by merging the top-level elements of each file (second-order merge, but not recursive) } OOResourceMergeMode; /* 'All' doesn't quite mean 'all' - OXPs with the tag * "oolite-scenario-only" will only be loaded if required by a * scenario. * * Note that this means that the scenario itself must be in a * different OXP, or it'll never be loaded when on the start-game * screen. */ #define SCENARIO_OXP_DEFINITION_ALL @"" #define SCENARIO_OXP_DEFINITION_NONE @"strict" #define SCENARIO_OXP_DEFINITION_BYID @"id:" #define SCENARIO_OXP_DEFINITION_BYTAG @"tag:" #define SCENARIO_OXP_DEFINITION_NOPLIST @"exc:" @interface ResourceManager : NSObject + (void) reset; + (void) resetManifestKnowledgeForOXZManager; + (NSArray *)rootPaths; // Places add-ons are searched for, not including add-on paths. + (NSArray *)userRootPaths; // Places users are expected to place add-ons, not including built-in data or managed add-ons directory. + (NSString *)builtInPath; // Path for built-in data only. + (NSArray *)pathsWithAddOns; // Root paths + add-on paths. + (NSArray *)paths; // builtInPath or pathsWithAddOns, depending on useAddOns state. + (NSString *)useAddOns; + (NSArray *)OXPsWithMessagesFound; + (void)setUseAddOns:(NSString *)useAddOns; + (void)addExternalPath:(NSString *)fileName; + (NSEnumerator *)pathEnumerator; + (NSEnumerator *)reversePathEnumerator; // get manifest data for identifier + (NSDictionary *)manifestForIdentifier:(NSString *)identifier; // compatibility checks + (BOOL) checkVersionCompatibility:(NSDictionary *)manifest forOXP:(NSString *)title; + (BOOL) manifestHasConflicts:(NSDictionary *)manifest logErrors:(BOOL)logErrors; + (BOOL) manifestHasMissingDependencies:(NSDictionary *)manifest logErrors:(BOOL)logErrors; + (BOOL) manifest:(NSDictionary *)manifest HasUnmetDependency:(NSDictionary *)required logErrors:(BOOL)logErrors; + (BOOL) matchVersions:(NSDictionary *)rangeDict withVersion:(NSString *)version; + (void)handleEquipmentListMerging: (NSMutableArray *)arrayToProcess forLookupIndex:(unsigned)lookupIndex; + (NSString *)errors; // Errors which occured during path scanning - essentially a list of OXPs whose requires.plist is bad. + (NSString *) pathForFileNamed:(NSString *)fileName inFolder:(NSString *)folderName; + (NSString *) pathForFileNamed:(NSString *)fileName inFolder:(NSString *)folderName cache:(BOOL)useCache; + (BOOL) corePlist:(NSString *)fileName excludedAt:(NSString *)path; + (NSDictionary *)dictionaryFromFilesNamed:(NSString *)fileName inFolder:(NSString *)folderName andMerge:(BOOL) mergeFiles; + (NSDictionary *)dictionaryFromFilesNamed:(NSString *)fileName inFolder:(NSString *)folderName mergeMode:(OOResourceMergeMode)mergeMode cache:(BOOL)useCache; + (NSArray *)arrayFromFilesNamed:(NSString *)fileName inFolder:(NSString *)folderName andMerge:(BOOL) mergeFiles; + (NSArray *)arrayFromFilesNamed:(NSString *)fileName inFolder:(NSString *)folderName andMerge:(BOOL) mergeFiles cache:(BOOL)useCache; // These are deliberately not merged like normal plists for security reasons. + (NSDictionary *) whitelistDictionary; + (NSDictionary *) shaderBindingTypesDictionary; // These have special merging rules. + (NSDictionary *) logControlDictionary; + (NSDictionary *) roleCategoriesDictionary; + (OOSystemDescriptionManager *) systemDescriptionManager; + (OOSound *)ooSoundNamed:(NSString *)fileName inFolder:(NSString *)folderName; + (OOMusic *)ooMusicNamed:(NSString *)fileName inFolder:(NSString *)folderName; + (NSString *) stringFromFilesNamed:(NSString *)fileName inFolder:(NSString *)folderName; + (NSString *) stringFromFilesNamed:(NSString *)fileName inFolder:(NSString *)folderName cache:(BOOL)useCache; + (NSDictionary *)loadScripts; /* +writeDiagnosticData:toFileNamed: +writeDiagnosticString:toFileNamed: +writeDiagnosticPList:toFileNamed: Write data to the specified path within the log directory. Slashes may be used as path separators in name. */ + (BOOL) writeDiagnosticData:(NSData *)data toFileNamed:(NSString *)name; + (BOOL) writeDiagnosticString:(NSString *)string toFileNamed:(NSString *)name; + (BOOL) writeDiagnosticPList:(id)plist toFileNamed:(NSString *)name; + (NSString *) diagnosticFileLocation; + (NSDictionary *) materialDefaults; // Clear ResourceManager-internal caches (not those handled by OOCacheManager) + (void) clearCaches; @end oolite-1.82/src/Core/ResourceManager.m000066400000000000000000001743371256642440500177050ustar00rootroot00000000000000/* ResourceManager.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "ResourceManager.h" #import "NSScannerOOExtensions.h" #import "NSMutableDictionaryOOExtensions.h" #import "NSStringOOExtensions.h" #import "OOSound.h" #import "OOCacheManager.h" #import "Universe.h" #import "OOStringParsing.h" #import "OOPListParsing.h" #import "MyOpenGLView.h" #import "OOCollectionExtractors.h" #import "OOLogOutputHandler.h" #import "NSFileManagerOOExtensions.h" #import "OldSchoolPropertyListWriting.h" #import "OOOXZManager.h" #import "unzip.h" #import "HeadUpDisplay.h" #import "OODebugStandards.h" #import "OOSystemDescriptionManager.h" #import "OOJSScript.h" #import "OOPListScript.h" #import "OOManifestProperties.h" static NSString * const kOOLogCacheUpToDate = @"dataCache.upToDate"; static NSString * const kOOLogCacheExplicitFlush = @"dataCache.rebuild.explicitFlush"; static NSString * const kOOLogCacheStalePaths = @"dataCache.rebuild.pathsChanged"; static NSString * const kOOLogCacheStaleDates = @"dataCache.rebuild.datesChanged"; static NSString * const kOOCacheSearchPathModDates = @"search path modification dates"; static NSString * const kOOCacheKeySearchPaths = @"search paths"; static NSString * const kOOCacheKeyModificationDates = @"modification dates"; extern NSDictionary* ParseOOSScripts(NSString* script); @interface ResourceManager (OOPrivate) + (void) checkOXPMessagesInPath:(NSString *)path; + (void) checkPotentialPath:(NSString *)path :(NSMutableArray *)searchPaths; + (BOOL) validateManifest:(NSDictionary*)manifest forOXP:(NSString *)path; + (BOOL) areRequirementsFulfilled:(NSDictionary*)requirements forOXP:(NSString *)path andFile:(NSString *)file; + (void) filterSearchPathsForConflicts:(NSMutableArray *)searchPaths; + (BOOL) filterSearchPathsForRequirements:(NSMutableArray *)searchPaths; + (void) filterSearchPathsToExcludeScenarioOnlyPaths:(NSMutableArray *)searchPaths; + (void) filterSearchPathsByScenario:(NSMutableArray *)searchPaths; + (BOOL) manifestAllowedByScenario:(NSDictionary *)manifest; + (BOOL) manifestAllowedByScenario:(NSDictionary *)manifest withIdentifier:(NSString *)identifier; + (BOOL) manifestAllowedByScenario:(NSDictionary *)manifest withTag:(NSString *)tag; + (void) addErrorWithKey:(NSString *)descriptionKey param1:(id)param1 param2:(id)param2; + (BOOL) checkCacheUpToDateForPaths:(NSArray *)searchPaths; + (void) logPaths; + (void) mergeRoleCategories:(NSDictionary *)catData intoDictionary:(NSMutableDictionary *)category; + (void) preloadFileLists; + (void) preloadFileListFromOXZ:(NSString *)path forFolders:(NSArray *)folders; + (void) preloadFileListFromFolder:(NSString *)path forFolders:(NSArray *)folders; + (void) preloadFilePathFor:(NSString *)fileName inFolder:(NSString *)subFolder atPath:(NSString *)path; @end static NSMutableArray *sSearchPaths; static NSString *sUseAddOns; static NSArray *sUseAddOnsParts; static BOOL sFirstRun = YES; static BOOL sAllMet = NO; static NSMutableArray *sOXPsWithMessagesFound; static NSMutableArray *sExternalPaths; static NSMutableArray *sErrors; static NSMutableDictionary *sOXPManifests; // caches allow us to load any given file once only // static NSMutableDictionary *sSoundCache; static NSMutableDictionary *sStringCache; @implementation ResourceManager + (void) reset { sFirstRun = YES; DESTROY(sUseAddOns); DESTROY(sUseAddOnsParts); DESTROY(sSearchPaths); DESTROY(sOXPsWithMessagesFound); DESTROY(sExternalPaths); DESTROY(sErrors); DESTROY(sOXPManifests); } + (void) resetManifestKnowledgeForOXZManager { DESTROY(sUseAddOns); DESTROY(sUseAddOnsParts); DESTROY(sSearchPaths); DESTROY(sOXPManifests); [ResourceManager pathsWithAddOns]; } + (NSString *) errors { NSArray *error = nil; NSUInteger i, count; NSMutableArray *result = nil; NSString *errStr = nil; count = [sErrors count]; if (count == 0) return nil; // Expand error messages. This is deferred for localizability. result = [NSMutableArray arrayWithCapacity:count]; for (i = 0; i != count; ++i) { error = [sErrors objectAtIndex:i]; errStr = [UNIVERSE descriptionForKey:[error oo_stringAtIndex:0]]; if (errStr != nil) { errStr = [NSString stringWithFormat:errStr, [error objectAtIndex:1], [error objectAtIndex:2]]; [result addObject:errStr]; } } [sErrors release]; sErrors = nil; return [result componentsJoinedByString:@"\n"]; } + (NSArray *)rootPaths { static NSArray *sRootPaths = nil; if (sRootPaths == nil) { /* Built-in data, then managed OXZs, then manually installed ones, * which may be useful for debugging/testing purposes. */ sRootPaths = [NSArray arrayWithObjects:[self builtInPath], [[OOOXZManager sharedManager] installPath], nil]; sRootPaths = [[sRootPaths arrayByAddingObjectsFromArray:[self userRootPaths]] retain]; } return sRootPaths; } + (NSArray *)userRootPaths { static NSArray *sUserRootPaths = nil; if (sUserRootPaths == nil) { // the paths are now in order of preference as per yesterday's talk. -- Kaks 2010-05-05 sUserRootPaths = [[NSArray alloc] initWithObjects: #if OOLITE_MAC_OS_X [[[[NSHomeDirectory() stringByAppendingPathComponent:@"Library"] stringByAppendingPathComponent:@"Application Support"] stringByAppendingPathComponent:@"Oolite"] stringByAppendingPathComponent:@"AddOns"], [[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"AddOns"], #elif OOLITE_WINDOWS @"../AddOns", #else @"AddOns", #endif #if !OOLITE_WINDOWS [[NSHomeDirectory() stringByAppendingPathComponent:@".Oolite"] stringByAppendingPathComponent:@"AddOns"], #endif nil]; } OOLog(@"searchPaths.debug",@"%@",sUserRootPaths); return sUserRootPaths; } + (NSString *)builtInPath { #if OOLITE_WINDOWS /* [[NSBundle mainBundle] resourcePath] causes complaints under Windows, because we don't have a properly-built bundle. */ return @"Resources"; #else return [[NSBundle mainBundle] resourcePath]; #endif } + (NSArray *)pathsWithAddOns { if ([sSearchPaths count] > 0) return sSearchPaths; if (sUseAddOns == nil) { sUseAddOns = [[NSString alloc] initWithString:SCENARIO_OXP_DEFINITION_ALL]; sUseAddOnsParts = [[sUseAddOns componentsSeparatedByString:@";"] retain]; } /* Handle special case of 'strict mode' efficiently */ // testing actual string if ([sUseAddOns isEqualToString:SCENARIO_OXP_DEFINITION_NONE]) { return (NSArray *)[NSArray arrayWithObject:[self builtInPath]]; } [sErrors release]; sErrors = nil; NSFileManager *fmgr = [NSFileManager defaultManager]; NSArray *rootPaths = nil; NSMutableArray *existingRootPaths = nil; NSEnumerator *pathEnum = nil; NSString *root = nil; NSDirectoryEnumerator *dirEnum = nil; NSString *subPath = nil; NSString *path = nil; BOOL isDirectory; // Copy those root paths that actually exist to search paths. rootPaths = [self rootPaths]; existingRootPaths = [NSMutableArray arrayWithCapacity:[rootPaths count]]; for (pathEnum = [rootPaths objectEnumerator]; (root = [pathEnum nextObject]); ) { if ([fmgr fileExistsAtPath:root isDirectory:&isDirectory] && isDirectory) { [existingRootPaths addObject:root]; } } // validate default search paths DESTROY(sSearchPaths); sSearchPaths = [NSMutableArray new]; foreach(path, existingRootPaths) { [self checkPotentialPath:path :sSearchPaths]; } // Iterate over root paths. for (pathEnum = [existingRootPaths objectEnumerator]; (root = [pathEnum nextObject]); ) { // Iterate over each root path's contents. if ([fmgr fileExistsAtPath:root isDirectory:&isDirectory] && isDirectory) { for (dirEnum = [fmgr enumeratorAtPath:root]; (subPath = [dirEnum nextObject]); ) { // Check if it's a directory. path = [root stringByAppendingPathComponent:subPath]; if ([fmgr fileExistsAtPath:path isDirectory:&isDirectory]) { if (isDirectory) { // If it is, is it an OXP?. if ([[[path pathExtension] lowercaseString] isEqualToString:@"oxp"]) { [self checkPotentialPath:path :sSearchPaths]; if ([sSearchPaths containsObject:path]) [self checkOXPMessagesInPath:path]; } else { // If not, don't search subdirectories. [dirEnum skipDescendents]; } } else { // If not a directory, is it an OXZ? if ([[[path pathExtension] lowercaseString] isEqualToString:@"oxz"]) { [self checkPotentialPath:path :sSearchPaths]; if ([sSearchPaths containsObject:path]) [self checkOXPMessagesInPath:path]; } } } } } } for (pathEnum = [sExternalPaths objectEnumerator]; (path = [pathEnum nextObject]); ) { [self checkPotentialPath:path :sSearchPaths]; if ([sSearchPaths containsObject:path]) [self checkOXPMessagesInPath:path]; } /* If a scenario restriction is *not* in place, remove * scenario-only OXPs. */ // test string if ([sUseAddOns isEqualToString:SCENARIO_OXP_DEFINITION_ALL]) { [self filterSearchPathsToExcludeScenarioOnlyPaths:sSearchPaths]; } /* This is a conservative filter. It probably gets rid of more * OXPs than it technically needs to in certain situations with * dependency chains, but really any conflict here needs to be * resolved by the user rather than Oolite. The point is to avoid * loading OXPs which we shouldn't; if doing so takes out other * OXPs which would have been safe, that's not important. */ [self filterSearchPathsForConflicts:sSearchPaths]; /* This one needs to be run repeatedly to be sure. Take the chain * A depends on B depends on C. A and B are installed. A is * checked first, and depends on B, which is thought to be * okay. So A is kept. Then B is checked and removed. A must then * be rechecked. This function therefore is run repeatedly until a * run of it removes no further items. * * There may well be more elegant and efficient ways to do this * but this is already fast enough for most purposes. */ while (![self filterSearchPathsForRequirements:sSearchPaths]) {} /* If a scenario restriction is in place, restrict OXPs to the * ones valid for the scenario only. */ // test string if (![sUseAddOns isEqualToString:SCENARIO_OXP_DEFINITION_ALL]) { [self filterSearchPathsByScenario:sSearchPaths]; } [self checkCacheUpToDateForPaths:sSearchPaths]; return sSearchPaths; } + (void) preloadFileLists { NSString *path = nil; NSEnumerator *pathEnum = nil; // folders which may contain files to be cached NSArray *folders = [NSArray arrayWithObjects:@"AIs",@"Images",@"Models",@"Music",@"Scenarios",@"Scripts",@"Shaders",@"Sounds",@"Textures",nil]; for (pathEnum = [[ResourceManager paths] reverseObjectEnumerator]; (path = [pathEnum nextObject]); ) { if ([path hasSuffix:@".oxz"]) { [self preloadFileListFromOXZ:path forFolders:folders]; } else { [self preloadFileListFromFolder:path forFolders:folders]; } } } + (void) preloadFileListFromOXZ:(NSString *)path forFolders:(NSArray *)folders { unzFile uf = NULL; const char* zipname = [path UTF8String]; char componentName[512]; if (zipname != NULL) { uf = unzOpen64(zipname); } if (uf == NULL) { OOLog(@"resourceManager.error",@"Could not open .oxz at %@ as zip file",path); return; } if (unzGoToFirstFile(uf) == UNZ_OK) { do { unzGetCurrentFileInfo64(uf, NULL, componentName, 512, NULL, 0, NULL, 0); NSString *zipEntry = [NSString stringWithUTF8String:componentName]; NSArray *pathBits = [zipEntry pathComponents]; if ([pathBits count] >= 2) { NSString *folder = [pathBits oo_stringAtIndex:0]; if ([folders containsObject:folder]) { NSRange bitRange; bitRange.location = 1; bitRange.length = [pathBits count]-1; NSString *file = [NSString pathWithComponents:[pathBits subarrayWithRange:bitRange]]; NSString *fullPath = [[path stringByAppendingPathComponent:folder] stringByAppendingPathComponent:file]; [self preloadFilePathFor:file inFolder:folder atPath:fullPath]; } } } while (unzGoToNextFile(uf) == UNZ_OK); } unzClose(uf); } + (void) preloadFileListFromFolder:(NSString *)path forFolders:(NSArray *)folders { NSFileManager *fmgr = [NSFileManager defaultManager]; NSString *subFolder = nil; NSString *subFolderPath = nil; NSArray *fileList = nil; NSString *fileName = nil; // search each subfolder for files foreach (subFolder, folders) { subFolderPath = [path stringByAppendingPathComponent:subFolder]; fileList = [fmgr oo_directoryContentsAtPath:subFolderPath]; foreach (fileName, fileList) { [self preloadFilePathFor:fileName inFolder:subFolder atPath:[subFolderPath stringByAppendingPathComponent:fileName]]; } } } + (void) preloadFilePathFor:(NSString *)fileName inFolder:(NSString *)subFolder atPath:(NSString *)path { OOCacheManager *cache = [OOCacheManager sharedCache]; NSString *cacheKey = [NSString stringWithFormat:@"%@/%@", subFolder, fileName]; NSString *result = [cache objectForKey:cacheKey inCache:@"resolved paths"]; // if nil, not found in another OXP already if (result == nil) { OOLog(@"resourceManager.foundFile.preLoad", @"Found %@/%@ at %@", subFolder, fileName, path); [cache setObject:path forKey:cacheKey inCache:@"resolved paths"]; } } + (NSArray *)paths { if (EXPECT_NOT(sSearchPaths == nil)) { sSearchPaths = [[NSMutableArray alloc] init]; } return [self pathsWithAddOns]; } + (NSString *)useAddOns { return sUseAddOns; } + (void)setUseAddOns:(NSString *)useAddOns { if (sFirstRun || ![useAddOns isEqualToString:sUseAddOns]) { [self reset]; sFirstRun = NO; DESTROY(sUseAddOnsParts); DESTROY(sUseAddOns); sUseAddOns = [useAddOns retain]; sUseAddOnsParts = [[sUseAddOns componentsSeparatedByString:@";"] retain]; [ResourceManager clearCaches]; OOHUDResetTextEngine(); OOCacheManager *cmgr = [OOCacheManager sharedCache]; /* only allow cache writes for the "all OXPs" default * * cache should be less necessary for restricted sets anyway */ // testing the actual string here if ([sUseAddOns isEqualToString:SCENARIO_OXP_DEFINITION_ALL]) { [cmgr reloadAllCaches]; [cmgr setAllowCacheWrites:YES]; } else { [cmgr clearAllCaches]; [cmgr setAllowCacheWrites:NO]; } [self checkCacheUpToDateForPaths:[self paths]]; [self logPaths]; /* preloading the file lists at this stage helps efficiency a * lot when many OXZs are installed */ [self preloadFileLists]; } } + (void) addExternalPath:(NSString *)path { if (sSearchPaths == nil) sSearchPaths = [[NSMutableArray alloc] init]; if (![sSearchPaths containsObject:path]) { [sSearchPaths addObject:path]; if (sExternalPaths == nil) sExternalPaths = [[NSMutableArray alloc] init]; [sExternalPaths addObject:path]; } } + (NSEnumerator *)pathEnumerator { return [[self paths] objectEnumerator]; } + (NSEnumerator *)reversePathEnumerator { return [[self paths] reverseObjectEnumerator]; } + (NSArray *)OXPsWithMessagesFound { return [[sOXPsWithMessagesFound copy] autorelease]; } + (NSDictionary *)manifestForIdentifier:(NSString *)identifier { return [sOXPManifests objectForKey:identifier]; } + (void) checkOXPMessagesInPath:(NSString *)path { NSArray *OXPMessageArray = OOArrayFromFile([path stringByAppendingPathComponent:@"OXPMessages.plist"]); if ([OXPMessageArray count] > 0) { unsigned i; for (i = 0; i < [OXPMessageArray count]; i++) { NSString *oxpMessage = [OXPMessageArray oo_stringAtIndex:i]; if (oxpMessage) { OOLog(@"oxp.message", @"%@: %@", path, oxpMessage); } } if (sOXPsWithMessagesFound == nil) sOXPsWithMessagesFound = [[NSMutableArray alloc] init]; [sOXPsWithMessagesFound addObject:[path lastPathComponent]]; } } // Given a path to an assumed OXP (or other location where files are permissible), check for a requires.plist or manifest.plist and add to search paths if acceptable. + (void)checkPotentialPath:(NSString *)path :(NSMutableArray *)searchPaths { NSDictionary *requirements = nil; NSDictionary *manifest = nil; BOOL requirementsMet = YES; if (![[[path pathExtension] lowercaseString] isEqualToString:@"oxz"]) { // OXZ format ignores requires.plist requirements = OODictionaryFromFile([path stringByAppendingPathComponent:@"requires.plist"]); requirementsMet = [self areRequirementsFulfilled:requirements forOXP:path andFile:@"requires.plist"]; } if (!requirementsMet) { NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]; OOLog(@"oxp.versionMismatch", @"OXP %@ is incompatible with version %@ of Oolite.", path, version); [self addErrorWithKey:@"oxp-is-incompatible" param1:[path lastPathComponent] param2:version]; return; } manifest = OODictionaryFromFile([path stringByAppendingPathComponent:@"manifest.plist"]); if (manifest == nil) { if ([[[path pathExtension] lowercaseString] isEqualToString:@"oxz"]) { OOLog(@"oxp.noManifest", @"OXZ %@ has no manifest.plist", path); [self addErrorWithKey:@"oxz-lacks-manifest" param1:[path lastPathComponent] param2:nil]; return; } else { if ([[[path pathExtension] lowercaseString] isEqualToString:@"oxp"]) { OOStandardsError([NSString stringWithFormat:@"OXP %@ has no manifest.plist", path]); if (OOEnforceStandards()) { [self addErrorWithKey:@"oxp-lacks-manifest" param1:[path lastPathComponent] param2:nil]; return; } } // make up a basic manifest in relaxed mode or for base folders manifest = [NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"__oolite.tmp.%@",path],kOOManifestIdentifier,@"1",kOOManifestVersion,@"OXP without manifest",kOOManifestTitle,@"1",kOOManifestRequiredOoliteVersion,nil]; } } requirementsMet = [self validateManifest:manifest forOXP:path]; if (requirementsMet) { [searchPaths addObject:path]; } } + (BOOL) validateManifest:(NSDictionary*)manifest forOXP:(NSString *)path { if (EXPECT_NOT(sOXPManifests == nil)) { sOXPManifests = [[NSMutableDictionary alloc] initWithCapacity:32]; } BOOL OK = YES; NSString *identifier = [manifest oo_stringForKey:kOOManifestIdentifier defaultValue:nil]; NSString *version = [manifest oo_stringForKey:kOOManifestVersion defaultValue:nil]; NSString *required = [manifest oo_stringForKey:kOOManifestRequiredOoliteVersion defaultValue:nil]; NSString *title = [manifest oo_stringForKey:kOOManifestTitle defaultValue:nil]; if (identifier == nil) { OOLog(@"oxp.noManifest", @"OXZ %@ manifest.plist has no '%@' field.", path, kOOManifestIdentifier); [self addErrorWithKey:@"oxp-manifest-incomplete" param1:title param2:kOOManifestIdentifier]; OK = NO; } if (version == nil) { OOLog(@"oxp.noManifest", @"OXZ %@ manifest.plist has no '%@' field.", path, kOOManifestVersion); [self addErrorWithKey:@"oxp-manifest-incomplete" param1:title param2:kOOManifestVersion]; OK = NO; } if (required == nil) { OOLog(@"oxp.noManifest", @"OXZ %@ manifest.plist has no '%@' field.", path, kOOManifestRequiredOoliteVersion); [self addErrorWithKey:@"oxp-manifest-incomplete" param1:title param2:kOOManifestRequiredOoliteVersion]; OK = NO; } if (title == nil) { OOLog(@"oxp.noManifest", @"OXZ %@ manifest.plist has no '%@' field.", path, kOOManifestTitle); [self addErrorWithKey:@"oxp-manifest-incomplete" param1:title param2:kOOManifestTitle]; OK = NO; } if (!OK) { return NO; } OK = [self checkVersionCompatibility:manifest forOXP:title]; if (!OK) { NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]; OOLog(@"oxp.versionMismatch", @"OXP %@ is incompatible with version %@ of Oolite.", path, version); [self addErrorWithKey:@"oxp-is-incompatible" param1:[path lastPathComponent] param2:version]; return NO; } NSDictionary *duplicate = [sOXPManifests objectForKey:identifier]; if (duplicate != nil) { OOLog(@"oxp.duplicate", @"OXP %@ has the same identifier (%@) as %@ which has already been loaded.",path,identifier,[duplicate oo_stringForKey:kOOManifestFilePath]); [self addErrorWithKey:@"oxp-manifest-duplicate" param1:path param2:[duplicate oo_stringForKey:kOOManifestFilePath]]; return NO; } NSMutableDictionary *mData = [NSMutableDictionary dictionaryWithDictionary:manifest]; [mData setObject:path forKey:kOOManifestFilePath]; // add an extra key [sOXPManifests setObject:mData forKey:identifier]; return YES; } + (BOOL) checkVersionCompatibility:(NSDictionary *)manifest forOXP:(NSString *)title { NSString *required = [manifest oo_stringForKey:kOOManifestRequiredOoliteVersion defaultValue:nil]; NSString *maxRequired = [manifest oo_stringForKey:kOOManifestMaximumOoliteVersion defaultValue:nil]; // ignore empty max version string rather than treating as "version 0" if (maxRequired == nil || [maxRequired length] == 0) { return [self areRequirementsFulfilled:[NSDictionary dictionaryWithObjectsAndKeys:required, @"version", nil] forOXP:title andFile:@"manifest.plist"]; } else { return [self areRequirementsFulfilled:[NSDictionary dictionaryWithObjectsAndKeys:required, @"version", maxRequired, @"max_version", nil] forOXP:title andFile:@"manifest.plist"]; } } + (BOOL) areRequirementsFulfilled:(NSDictionary*)requirements forOXP:(NSString *)path andFile:(NSString *)file { BOOL OK = YES; NSString *requiredVersion = nil; NSString *maxVersion = nil; unsigned conditionsHandled = 0; static NSArray *ooVersionComponents = nil; NSArray *oxpVersionComponents = nil; if (requirements == nil) return YES; if (ooVersionComponents == nil) { ooVersionComponents = ComponentsFromVersionString([[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]); [ooVersionComponents retain]; } // Check "version" (minimum version) if (OK) { // Not oo_stringForKey:, because we need to be able to complain about non-strings. requiredVersion = [requirements objectForKey:@"version"]; if (requiredVersion != nil) { ++conditionsHandled; if ([requiredVersion isKindOfClass:[NSString class]]) { oxpVersionComponents = ComponentsFromVersionString(requiredVersion); if (NSOrderedAscending == CompareVersions(ooVersionComponents, oxpVersionComponents)) OK = NO; } else { OOLog(@"requirements.wrongType", @"Expected %@ entry \"%@\" to be string, but got %@ in OXP %@.", file, @"version", [requirements class], [path lastPathComponent]); OK = NO; } } } // Check "max_version" (minimum max_version) if (OK) { // Not oo_stringForKey:, because we need to be able to complain about non-strings. maxVersion = [requirements objectForKey:@"max_version"]; if (maxVersion != nil) { ++conditionsHandled; if ([maxVersion isKindOfClass:[NSString class]]) { oxpVersionComponents = ComponentsFromVersionString(maxVersion); if (NSOrderedDescending == CompareVersions(ooVersionComponents, oxpVersionComponents)) OK = NO; } else { OOLog(@"requirements.wrongType", @"Expected %@ entry \"%@\" to be string, but got %@ in OXP %@.", file, @"max_version", [requirements class], [path lastPathComponent]); OK = NO; } } } if (OK && conditionsHandled < [requirements count]) { // There are unknown requirement keys - don't support. NOTE: this check was not made pre 1.69! OOLog(@"requirements.unknown", @"requires.plist for OXP %@ contains unknown keys, rejecting.", [path lastPathComponent]); OK = NO; } return OK; } + (BOOL) manifestHasConflicts:(NSDictionary *)manifest logErrors:(BOOL)logErrors { NSDictionary *conflicting = nil; NSDictionary *conflictManifest = nil; NSString *conflictID = nil; NSArray *conflicts = nil; conflicts = [manifest oo_arrayForKey:kOOManifestConflictOXPs defaultValue:nil]; // if it has a non-empty conflict_oxps list if (conflicts != nil && [conflicts count] > 0) { // iterate over that list foreach (conflicting, conflicts) { conflictID = [conflicting oo_stringForKey:kOOManifestRelationIdentifier]; conflictManifest = [sOXPManifests objectForKey:conflictID]; // if the other OXP is in the list if (conflictManifest != nil) { // then check versions if ([self matchVersions:conflicting withVersion:[conflictManifest oo_stringForKey:kOOManifestVersion]]) { if (logErrors) { [self addErrorWithKey:@"oxp-conflict" param1:[manifest oo_stringForKey:kOOManifestTitle] param2:[conflictManifest oo_stringForKey:kOOManifestTitle]]; OOLog(@"oxp.conflict",@"OXP %@ conflicts with %@ and was removed from the loading list",[[manifest oo_stringForKey:kOOManifestFilePath] lastPathComponent],[[conflictManifest oo_stringForKey:kOOManifestFilePath] lastPathComponent]); } return YES; } } } } return NO; } + (void) filterSearchPathsForConflicts:(NSMutableArray *)searchPaths { NSDictionary *manifest = nil; NSString *identifier = nil; NSArray *identifiers = [sOXPManifests allKeys]; // take a copy because we'll mutate the original // foreach identified add-on foreach (identifier, identifiers) { manifest = [sOXPManifests objectForKey:identifier]; if (manifest != nil) { if ([self manifestHasConflicts:manifest logErrors:YES]) { // then we have a conflict, so remove this path [searchPaths removeObject:[manifest oo_stringForKey:kOOManifestFilePath]]; [sOXPManifests removeObjectForKey:identifier]; } } } } + (BOOL) manifestHasMissingDependencies:(NSDictionary *)manifest logErrors:(BOOL)logErrors { NSDictionary *required = nil; NSArray *requireds = nil; requireds = [manifest oo_arrayForKey:kOOManifestRequiresOXPs defaultValue:nil]; // if it has a non-empty required_oxps list if (requireds != nil && [requireds count] > 0) { // iterate over that list foreach (required, requireds) { if ([ResourceManager manifest:manifest HasUnmetDependency:required logErrors:logErrors]) { return YES; } } } return NO; } + (BOOL) manifest:(NSDictionary *)manifest HasUnmetDependency:(NSDictionary *)required logErrors:(BOOL)logErrors { NSString *requiredID = [required oo_stringForKey:kOOManifestRelationIdentifier]; NSMutableDictionary *requiredManifest = [sOXPManifests objectForKey:requiredID]; // if the other OXP is in the list BOOL requirementsMet = NO; if (requiredManifest != nil) { // then check versions if ([self matchVersions:required withVersion:[requiredManifest oo_stringForKey:kOOManifestVersion]]) { requirementsMet = YES; /* Mark the requiredManifest as a dependency of the * requiring manifest */ NSSet *reqby = [requiredManifest oo_setForKey:kOOManifestRequiredBy defaultValue:[NSSet set]]; NSUInteger reqbycount = [reqby count]; /* then add this manifest to its required set. This is * done without checking if it's already there, because * the list of nested requirements may have changed. */ reqby = [reqby setByAddingObject:[manifest oo_stringForKey:kOOManifestIdentifier]]; // *and* anything that requires this OXP to be installed reqby = [reqby setByAddingObjectsFromSet:[manifest oo_setForKey:kOOManifestRequiredBy]]; if (reqbycount < [reqby count]) { /* Then the set has increased in size. To handle * potential cases with nested dependencies, need to * re-run the requirement filter until all the sets * stabilise. */ sAllMet = NO; } // and push back into the requiring manifest [requiredManifest setObject:reqby forKey:kOOManifestRequiredBy]; } } if (!requirementsMet) { if (logErrors) { [self addErrorWithKey:@"oxp-required" param1:[manifest oo_stringForKey:kOOManifestTitle] param2:[required oo_stringForKey:kOOManifestRelationDescription defaultValue:[required oo_stringForKey:kOOManifestRelationIdentifier]]]; OOLog(@"oxp.requirementMissing",@"OXP %@ had unmet requirements and was removed from the loading list",[[manifest oo_stringForKey:kOOManifestFilePath] lastPathComponent]); } return YES; } return NO; } + (BOOL) filterSearchPathsForRequirements:(NSMutableArray *)searchPaths { NSDictionary *manifest = nil; NSString *identifier = nil; NSArray *identifiers = [sOXPManifests allKeys]; sAllMet = YES; // take a copy because we'll mutate the original // foreach identified add-on foreach (identifier, identifiers) { manifest = [sOXPManifests objectForKey:identifier]; if (manifest != nil) { if ([self manifestHasMissingDependencies:manifest logErrors:YES]) { // then we have a missing requirement, so remove this path [searchPaths removeObject:[manifest oo_stringForKey:kOOManifestFilePath]]; [sOXPManifests removeObjectForKey:identifier]; sAllMet = NO; } } } return sAllMet; } + (BOOL) matchVersions:(NSDictionary *)rangeDict withVersion:(NSString *)version { NSString *minimum = [rangeDict oo_stringForKey:kOOManifestRelationVersion defaultValue:nil]; NSString *maximum = [rangeDict oo_stringForKey:kOOManifestRelationMaxVersion defaultValue:nil]; NSArray *isVersionComponents = ComponentsFromVersionString(version); NSArray *reqVersionComponents = nil; if (minimum != nil) { reqVersionComponents = ComponentsFromVersionString(minimum); if (NSOrderedAscending == CompareVersions(isVersionComponents, reqVersionComponents)) { // earlier than minimum version return NO; } } if (maximum != nil) { reqVersionComponents = ComponentsFromVersionString(maximum); if (NSOrderedDescending == CompareVersions(isVersionComponents, reqVersionComponents)) { // later than maximum version return NO; } } // either version was okay, or no version info so an unconditional match return YES; } + (void) filterSearchPathsToExcludeScenarioOnlyPaths:(NSMutableArray *)searchPaths { NSDictionary *manifest = nil; NSString *identifier = nil; NSArray *identifiers = [sOXPManifests allKeys]; // take a copy because we'll mutate the original // foreach identified add-on foreach (identifier, identifiers) { manifest = [sOXPManifests objectForKey:identifier]; if (manifest != nil) { if ([[manifest oo_arrayForKey:kOOManifestTags] containsObject:kOOManifestTagScenarioOnly]) { [searchPaths removeObject:[manifest oo_stringForKey:kOOManifestFilePath]]; [sOXPManifests removeObjectForKey:identifier]; } } } } + (void) filterSearchPathsByScenario:(NSMutableArray *)searchPaths { NSDictionary *manifest = nil; NSString *identifier = nil; NSArray *identifiers = [sOXPManifests allKeys]; // take a copy because we'll mutate the original // foreach identified add-on foreach (identifier, identifiers) { manifest = [sOXPManifests objectForKey:identifier]; if (manifest != nil) { if (![ResourceManager manifestAllowedByScenario:manifest]) { // then we don't need this one [searchPaths removeObject:[manifest oo_stringForKey:kOOManifestFilePath]]; [sOXPManifests removeObjectForKey:identifier]; } } } } + (BOOL) manifestAllowedByScenario:(NSDictionary *)manifest { /* Checks for a couple of "never happens" cases */ #ifndef NDEBUG // test string if ([sUseAddOns isEqualToString:SCENARIO_OXP_DEFINITION_ALL]) { OOLog(@"scenario.check",@"Checked scenario allowances in all state - this is an internal error; please report this"); return YES; } if ([sUseAddOns isEqualToString:SCENARIO_OXP_DEFINITION_NONE]) { OOLog(@"scenario.check",@"Checked scenario allowances in none state - this is an internal error; please report this"); return NO; } #endif if ([[manifest oo_stringForKey:kOOManifestIdentifier] isEqualToString:@"org.oolite.oolite"]) { // the core data is always allowed! return YES; } NSString *uaoBit = nil; BOOL result = NO; foreach (uaoBit, sUseAddOnsParts) { if ([uaoBit hasPrefix:SCENARIO_OXP_DEFINITION_BYID]) { result |= [ResourceManager manifestAllowedByScenario:manifest withIdentifier:[uaoBit substringFromIndex:[SCENARIO_OXP_DEFINITION_BYID length]]]; } else if ([uaoBit hasPrefix:SCENARIO_OXP_DEFINITION_BYTAG]) { result |= [ResourceManager manifestAllowedByScenario:manifest withTag:[uaoBit substringFromIndex:[SCENARIO_OXP_DEFINITION_BYTAG length]]]; } } return result; } + (BOOL) manifestAllowedByScenario:(NSDictionary *)manifest withIdentifier:(NSString *)identifier { if ([[manifest oo_stringForKey:kOOManifestIdentifier] isEqualToString:identifier]) { // manifest has the identifier - easy return YES; } // manifest is also allowed if a manifest with that identifier // requires it to be installed if ([[manifest oo_setForKey:kOOManifestRequiredBy] containsObject:identifier]) { return YES; } // otherwise, no return NO; } + (BOOL) manifestAllowedByScenario:(NSDictionary *)manifest withTag:(NSString *)tag { if ([[manifest oo_arrayForKey:kOOManifestTags] containsObject:tag]) { // manifest has the tag - easy return YES; } // manifest is also allowed if a manifest with that tag // requires it to be installed NSSet *reqby = [manifest oo_setForKey:kOOManifestRequiredBy]; if (reqby != nil) { NSString *identifier = nil; foreach (identifier, reqby) { NSDictionary *reqManifest = [sOXPManifests oo_dictionaryForKey:identifier defaultValue:nil]; // need to check for nil as this one may already have been ruled out if (reqManifest != nil && [[reqManifest oo_arrayForKey:kOOManifestTags] containsObject:tag]) { return YES; } } } // otherwise, no return NO; } + (void) addErrorWithKey:(NSString *)descriptionKey param1:(id)param1 param2:(id)param2 { if (descriptionKey != nil) { if (sErrors == nil) sErrors = [[NSMutableArray alloc] init]; [sErrors addObject:[NSArray arrayWithObjects:descriptionKey, param1 ?: (id)@"", param2 ?: (id)@"", nil]]; } } + (BOOL)checkCacheUpToDateForPaths:(NSArray *)searchPaths { /* Check if caches are up to date. The strategy is to use a two-entry cache. One entry is an array containing the search paths, the other an array of modification dates (in the same order). If either fails to match the correct settings, we delete both. */ OOCacheManager *cacheMgr = [OOCacheManager sharedCache]; NSFileManager *fmgr = [NSFileManager defaultManager]; BOOL upToDate = YES; id oldPaths = nil; NSMutableArray *modDates = nil; NSEnumerator *pathEnum = nil; NSString *path = nil; id modDate = nil; if (EXPECT_NOT([[NSUserDefaults standardUserDefaults] boolForKey:@"always-flush-cache"])) { OOLog(kOOLogCacheExplicitFlush, @"Cache explicitly flushed with always-flush-cache preference. Rebuilding from scratch."); upToDate = NO; } else if ([MyOpenGLView pollShiftKey]) { OOLog(kOOLogCacheExplicitFlush, @"Cache explicitly flushed with shift key. Rebuilding from scratch."); upToDate = NO; } oldPaths = [cacheMgr objectForKey:kOOCacheKeySearchPaths inCache:kOOCacheSearchPathModDates]; if (upToDate && ![oldPaths isEqual:searchPaths]) { // OXPs added/removed if (oldPaths != nil) OOLog(kOOLogCacheStalePaths, @"Cache is stale (search paths have changed). Rebuilding from scratch."); upToDate = NO; } // Build modification date list. (We need this regardless of whether the search paths matched.) modDates = [NSMutableArray arrayWithCapacity:[searchPaths count]]; for (pathEnum = [searchPaths objectEnumerator]; (path = [pathEnum nextObject]); ) { modDate = [[fmgr oo_fileAttributesAtPath:path traverseLink:YES] objectForKey:NSFileModificationDate]; if (modDate != nil) { // Converts to double because I'm not sure the cache can deal with dates under GNUstep. modDate = [NSNumber numberWithDouble:[modDate timeIntervalSince1970]]; [modDates addObject:modDate]; } } if (upToDate && ![[cacheMgr objectForKey:kOOCacheKeyModificationDates inCache:kOOCacheSearchPathModDates] isEqual:modDates]) { OOLog(kOOLogCacheStaleDates, @"Cache is stale (modification dates have changed). Rebuilding from scratch."); upToDate = NO; } if (!upToDate) { [cacheMgr clearAllCaches]; [cacheMgr setObject:searchPaths forKey:kOOCacheKeySearchPaths inCache:kOOCacheSearchPathModDates]; [cacheMgr setObject:modDates forKey:kOOCacheKeyModificationDates inCache:kOOCacheSearchPathModDates]; } else OOLog(kOOLogCacheUpToDate, @"Data cache is up to date."); return upToDate; } /* This method allows the exclusion of particular files from the plist * building when they're in builtInPath. The point of this is to allow * scenarios to avoid merging in core files without having to override * every individual entry (which may not always be possible * anyway). It only works on plists, but of course worldscripts can be * excluded by not including the plists which reference them, and * everything else can be excluded by not referencing it from a plist. */ + (BOOL) corePlist:(NSString *)fileName excludedAt:(NSString *)path { if (![path isEqualToString:[self builtInPath]]) { // non-core paths always okay return NO; } NSString *uaoBit = nil; foreach (uaoBit, sUseAddOnsParts) { if ([uaoBit hasPrefix:SCENARIO_OXP_DEFINITION_NOPLIST]) { NSString *plist = [uaoBit substringFromIndex:[SCENARIO_OXP_DEFINITION_NOPLIST length]]; if ([plist isEqualToString:fileName]) { // this core plist file should not be loaded at all return YES; } } } // then not excluded return NO; } + (NSDictionary *)dictionaryFromFilesNamed:(NSString *)fileName inFolder:(NSString *)folderName andMerge:(BOOL) mergeFiles { return [ResourceManager dictionaryFromFilesNamed:fileName inFolder:folderName mergeMode:mergeFiles ? MERGE_BASIC : MERGE_NONE cache:YES]; } + (NSDictionary *)dictionaryFromFilesNamed:(NSString *)fileName inFolder:(NSString *)folderName mergeMode:(OOResourceMergeMode)mergeMode cache:(BOOL)cache { id result = nil; NSMutableArray *results = nil; NSString *cacheKey = nil; NSString *mergeType = nil; OOCacheManager *cacheMgr = [OOCacheManager sharedCache]; NSEnumerator *enumerator = nil; NSString *path = nil; NSString *dictPath = nil; NSDictionary *dict = nil; if (fileName == nil) return nil; switch (mergeMode) { case MERGE_NONE: mergeType = @"none"; break; case MERGE_BASIC: mergeType = @"basic"; break; case MERGE_SMART: mergeType = @"smart"; break; } if (mergeType == nil) { OOLog(kOOLogParameterError, @"Unknown dictionary merge mode %u for %@. (This is an internal programming error, please report it.)", mergeMode, fileName); return nil; } if (cache) { if (folderName != nil) { cacheKey = [NSString stringWithFormat:@"%@/%@ merge:%@", folderName, fileName, mergeType]; } else { cacheKey = [NSString stringWithFormat:@"%@ merge:%@", fileName, mergeType]; } result = [cacheMgr objectForKey:cacheKey inCache:@"dictionaries"]; if (result != nil) return result; } if (mergeMode == MERGE_NONE) { // Find "last" matching dictionary for (enumerator = [ResourceManager reversePathEnumerator]; (path = [enumerator nextObject]); ) { if (folderName != nil) { dictPath = [[path stringByAppendingPathComponent:folderName] stringByAppendingPathComponent:fileName]; dict = OODictionaryFromFile(dictPath); if (dict != nil) break; } dictPath = [path stringByAppendingPathComponent:fileName]; dict = OODictionaryFromFile(dictPath); if (dict != nil) break; } result = dict; } else { // Find all matching dictionaries results = [NSMutableArray array]; for (enumerator = [ResourceManager pathEnumerator]; (path = [enumerator nextObject]); ) { if ([ResourceManager corePlist:fileName excludedAt:path]) { continue; } dictPath = [path stringByAppendingPathComponent:fileName]; dict = OODictionaryFromFile(dictPath); if (dict != nil) [results addObject:dict]; if (folderName != nil) { dictPath = [[path stringByAppendingPathComponent:folderName] stringByAppendingPathComponent:fileName]; dict = OODictionaryFromFile(dictPath); if (dict != nil) [results addObject:dict]; } } if ([results count] == 0) return nil; // Merge result result = [NSMutableDictionary dictionary]; for (enumerator = [results objectEnumerator]; (dict = [enumerator nextObject]); ) { if (mergeMode == MERGE_SMART) [result mergeEntriesFromDictionary:dict]; else [result addEntriesFromDictionary:dict]; } result = [[result copy] autorelease]; // Make immutable } if (cache && result != nil) [cacheMgr setObject:result forKey:cacheKey inCache:@"dictionaries"]; return result; } + (NSArray *) arrayFromFilesNamed:(NSString *)fileName inFolder:(NSString *)folderName andMerge:(BOOL) mergeFiles { return [self arrayFromFilesNamed:fileName inFolder:folderName andMerge:mergeFiles cache:YES]; } + (NSArray *) arrayFromFilesNamed:(NSString *)fileName inFolder:(NSString *)folderName andMerge:(BOOL) mergeFiles cache:(BOOL)useCache { id result = nil; NSMutableArray *results = nil; NSString *cacheKey = nil; OOCacheManager *cache = [OOCacheManager sharedCache]; NSEnumerator *enumerator = nil; NSString *path = nil; NSString *arrayPath = nil; NSMutableArray *array = nil; NSArray *arrayNonEditable = nil; if (fileName == nil) return nil; if (useCache) { cacheKey = [NSString stringWithFormat:@"%@%@ merge:%@", (folderName != nil) ? [folderName stringByAppendingString:@"/"] : (NSString *)@"", fileName, mergeFiles ? @"yes" : @"no"]; result = [cache objectForKey:cacheKey inCache:@"arrays"]; if (result != nil) return result; } if (!mergeFiles) { // Find "last" matching array for (enumerator = [ResourceManager reversePathEnumerator]; (path = [enumerator nextObject]); ) { if (folderName != nil) { arrayPath = [[path stringByAppendingPathComponent:folderName] stringByAppendingPathComponent:fileName]; arrayNonEditable = OOArrayFromFile(arrayPath); if (arrayNonEditable != nil) break; } arrayPath = [path stringByAppendingPathComponent:fileName]; arrayNonEditable = OOArrayFromFile(arrayPath); if (arrayNonEditable != nil) break; } result = arrayNonEditable; } else { // Find all matching arrays results = [NSMutableArray array]; for (enumerator = [ResourceManager pathEnumerator]; (path = [enumerator nextObject]); ) { if ([ResourceManager corePlist:fileName excludedAt:path]) { continue; } arrayPath = [path stringByAppendingPathComponent:fileName]; array = [[OOArrayFromFile(arrayPath) mutableCopy] autorelease]; if (array != nil) [results addObject:array]; // Special handling for arrays merging. Currently, equipment.plist only gets its objects merged. // A lookup index is required. For the equipment.plist items, this is the index corresponging to the // EQ_* string, which describes the role of an equipment item and is unique. if ([array count] != 0 && [[array objectAtIndex:0] isKindOfClass:[NSArray class]]) { if ([[fileName lowercaseString] isEqualToString:@"equipment.plist"]) [self handleEquipmentListMerging:results forLookupIndex:3]; // Index 3 is the role string (EQ_*). } if (folderName != nil) { arrayPath = [[path stringByAppendingPathComponent:folderName] stringByAppendingPathComponent:fileName]; array = [[OOArrayFromFile(arrayPath) mutableCopy] autorelease]; if (array != nil) [results addObject:array]; if ([array count] != 0 && [[array objectAtIndex:0] isKindOfClass:[NSArray class]]) { if ([[fileName lowercaseString] isEqualToString:@"equipment.plist"]) [self handleEquipmentListMerging:results forLookupIndex:3]; // Index 3 is the role string (EQ_*). } } } if ([results count] == 0) return nil; // Merge result result = [NSMutableArray array]; for (enumerator = [results objectEnumerator]; (array = [enumerator nextObject]); ) { [result addObjectsFromArray:array]; } result = [[result copy] autorelease]; // Make immutable } if (useCache && result != nil) [cache setObject:result forKey:cacheKey inCache:@"arrays"]; return [NSArray arrayWithArray:result]; } // A method for handling merging of arrays. Currently used with the equipment.plist entries. // The arrayToProcess array is scanned for repetitions of the item at lookup index location and, if found, // the latest entry replaces the earliest. + (void) handleEquipmentListMerging: (NSMutableArray *)arrayToProcess forLookupIndex:(unsigned)lookupIndex { NSUInteger i,j,k; NSMutableArray *refArray = [arrayToProcess objectAtIndex:[arrayToProcess count] - 1]; // Any change to arrayRef will directly modify arrayToProcess. for (i = 0; i < [refArray count]; i++) { for (j = 0; j < [arrayToProcess count] - 1; j++) { NSUInteger count = [[arrayToProcess oo_arrayAtIndex:j] count]; if (count == 0) continue; for (k=0; k < count; k++) { id processValue = [[[arrayToProcess oo_arrayAtIndex:j] oo_arrayAtIndex:k] oo_objectAtIndex:lookupIndex defaultValue:nil]; id refValue = [[refArray oo_arrayAtIndex:i] oo_objectAtIndex:lookupIndex defaultValue:nil]; if ([processValue isEqual:refValue]) { [[arrayToProcess objectAtIndex:j] replaceObjectAtIndex:k withObject:[refArray objectAtIndex:i]]; [refArray removeObjectAtIndex:i]; } } } } // arrayToProcess has been processed at this point. Any necessary merging has been done. } + (NSDictionary *) whitelistDictionary { static id whitelistDictionary = nil; if (whitelistDictionary == nil) { NSString *path = [[[ResourceManager builtInPath] stringByAppendingPathComponent:@"Config"] stringByAppendingPathComponent:@"whitelist.plist"]; whitelistDictionary = [NSDictionary dictionaryWithContentsOfFile:path]; if (whitelistDictionary == nil) whitelistDictionary = [NSNull null]; [whitelistDictionary retain]; } if (whitelistDictionary == [NSNull null]) return nil; return whitelistDictionary; } static NSString *LogClassKeyRoot(NSString *key) { NSRange dot = [key rangeOfString:@"."]; if (dot.location != NSNotFound) { return [key substringToIndex:dot.location]; } else { return key; } } + (NSDictionary *) logControlDictionary { // Load built-in copy of logcontrol.plist. NSString *path = [[[ResourceManager builtInPath] stringByAppendingPathComponent:@"Config"] stringByAppendingPathComponent:@"logcontrol.plist"]; NSMutableDictionary *logControl = [NSMutableDictionary dictionaryWithDictionary:OODictionaryFromFile(path)]; if (logControl == nil) logControl = [NSMutableDictionary dictionary]; // Build list of root log message classes that appear in the built-in list. NSMutableSet *coreRoots = [NSMutableSet set]; NSString *key = nil; foreachkey(key, logControl) { [coreRoots addObject:LogClassKeyRoot(key)]; } NSArray *rootPaths = [self rootPaths]; NSString *configPath = nil; NSDictionary *dict = nil; // Look for logcontrol.plists inside OXPs (but not in root paths). These are not allowed to define keys in hierarchies used by the build-in one. NSEnumerator *pathEnum = [self pathEnumerator]; while ((path = [pathEnum nextObject])) { if ([rootPaths containsObject:path]) continue; configPath = [[path stringByAppendingPathComponent:@"Config"] stringByAppendingPathComponent:@"logcontrol.plist"]; dict = OODictionaryFromFile(configPath); if (dict == nil) { configPath = [path stringByAppendingPathComponent:@"logcontrol.plist"]; dict = OODictionaryFromFile(configPath); } foreachkey (key, dict) { if (![coreRoots containsObject:LogClassKeyRoot(key)]) { [logControl setObject:[dict objectForKey:key] forKey:key]; } } } // Now, look for logcontrol.plists in root paths, i.e. not within OXPs. These are allowed to override the built-in copy. pathEnum = [rootPaths objectEnumerator]; while ((path = [pathEnum nextObject])) { configPath = [[path stringByAppendingPathComponent:@"Config"] stringByAppendingPathComponent:@"logcontrol.plist"]; dict = OODictionaryFromFile(configPath); if (dict == nil) { configPath = [path stringByAppendingPathComponent:@"logcontrol.plist"]; dict = OODictionaryFromFile(configPath); } foreachkey (key, dict) { [logControl setObject:[dict objectForKey:key] forKey:key]; } } // Finally, look in preferences, which can override all of the above. dict = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"logging-enable"]; if (dict != nil) [logControl addEntriesFromDictionary:dict]; return logControl; } + (NSDictionary *) roleCategoriesDictionary { NSMutableDictionary *roleCategories = [NSMutableDictionary dictionaryWithCapacity:16]; NSString *path = nil; NSString *configPath = nil; NSDictionary *categories = nil; NSEnumerator *pathEnum = [self pathEnumerator]; while ((path = [pathEnum nextObject])) { if ([ResourceManager corePlist:@"role-categories.plist" excludedAt:path]) { continue; } configPath = [[path stringByAppendingPathComponent:@"Config"] stringByAppendingPathComponent:@"role-categories.plist"]; categories = OODictionaryFromFile(configPath); if (categories != nil) { [ResourceManager mergeRoleCategories:categories intoDictionary:roleCategories]; } } /* If the old pirate-victim-roles files exist, merge them in */ NSArray *pirateVictims = [ResourceManager arrayFromFilesNamed:@"pirate-victim-roles.plist" inFolder:@"Config" andMerge:YES]; if (OOEnforceStandards() && [pirateVictims count] > 0) { OOStandardsDeprecated(@"pirate-victim-roles.plist is still being used."); } [ResourceManager mergeRoleCategories:[NSDictionary dictionaryWithObject:pirateVictims forKey:@"oolite-pirate-victim"] intoDictionary:roleCategories]; return [[roleCategories copy] autorelease]; } + (void) mergeRoleCategories:(NSDictionary *)catData intoDictionary:(NSMutableDictionary *)categories { NSMutableSet *contents = nil; NSArray *catDataEntry = nil; NSString *key; foreachkey(key, catData) { contents = [categories objectForKey:key]; if (contents == nil) { contents = [NSMutableSet setWithCapacity:16]; [categories setObject:contents forKey:key]; } catDataEntry = [catData oo_arrayForKey:key]; OOLog(@"shipData.load.roleCategories", @"Adding %ld entries for category %@", (unsigned long)[catDataEntry count], key); [contents addObjectsFromArray:catDataEntry]; } } + (OOSystemDescriptionManager *) systemDescriptionManager { OOLog(@"resourceManager.planetinfo.load",@"Initialising manager"); OOSystemDescriptionManager *manager = [[OOSystemDescriptionManager alloc] init]; NSString *path = nil; NSString *configPath = nil; NSDictionary *categories = nil; NSString *systemKey = nil; NSEnumerator *pathEnum = [self pathEnumerator]; while ((path = [pathEnum nextObject])) { if ([ResourceManager corePlist:@"planetinfo.plist" excludedAt:path]) { continue; } configPath = [[path stringByAppendingPathComponent:@"Config"] stringByAppendingPathComponent:@"planetinfo.plist"]; categories = OODictionaryFromFile(configPath); if (categories != nil) { foreachkey(systemKey,categories) { NSDictionary *values = [categories oo_dictionaryForKey:systemKey defaultValue:nil]; if (values != nil) { if ([systemKey isEqualToString:PLANETINFO_UNIVERSAL_KEY]) { [manager setUniversalProperties:values]; } else if ([systemKey isEqualToString:PLANETINFO_INTERSTELLAR_KEY]) { [manager setInterstellarProperties:values]; } else { [manager setProperties:values forSystemKey:systemKey]; } } } } } OOLog(@"resourceManager.planetinfo.load",@"Caching routes"); [manager buildRouteCache]; OOLog(@"resourceManager.planetinfo.load",@"Initialised manager"); return [manager autorelease]; } + (NSDictionary *) shaderBindingTypesDictionary { static id shaderBindingTypesDictionary = nil; if (shaderBindingTypesDictionary == nil) { NSAutoreleasePool *pool = [NSAutoreleasePool new]; NSString *path = [[[ResourceManager builtInPath] stringByAppendingPathComponent:@"Config"] stringByAppendingPathComponent:@"shader-uniform-bindings.plist"]; NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:path]; NSArray *keys = [dict allKeys]; // Resolve all $inherit keys. unsigned changeCount = 0; do { changeCount = 0; NSString *key = nil; foreach (key, keys) { NSDictionary *value = [dict oo_dictionaryForKey:key]; NSString *inheritKey = [value oo_stringForKey:@"$inherit"]; if (inheritKey != nil) { changeCount++; NSMutableDictionary *mutableValue = [[value mutableCopy] autorelease]; [mutableValue removeObjectForKey:@"$inherit"]; NSDictionary *inherited = [dict oo_dictionaryForKey:inheritKey]; if (inherited != nil) { [mutableValue addEntriesFromDictionary:inherited]; } [dict setObject:[[mutableValue copy] autorelease] forKey:key]; } } } while (changeCount != 0); shaderBindingTypesDictionary = [dict copy]; [pool release]; } return shaderBindingTypesDictionary; } + (NSString *) pathForFileNamed:(NSString *)fileName inFolder:(NSString *)folderName { return [self pathForFileNamed:fileName inFolder:folderName cache:YES]; } /* This is extremely expensive to call with useCache:NO */ + (NSString *) pathForFileNamed:(NSString *)fileName inFolder:(NSString *)folderName cache:(BOOL)useCache { NSString *result = nil; NSString *cacheKey = nil; OOCacheManager *cache = [OOCacheManager sharedCache]; NSEnumerator *pathEnum = nil; NSString *path = nil; NSString *filePath = nil; NSFileManager *fmgr = nil; if (fileName == nil) return nil; if (cache) { if (folderName != nil) cacheKey = [NSString stringWithFormat:@"%@/%@", folderName, fileName]; else cacheKey = fileName; result = [cache objectForKey:cacheKey inCache:@"resolved paths"]; if (result != nil) return result; } // Search for file fmgr = [NSFileManager defaultManager]; // reverse object enumerator allows OXPs to override core for (pathEnum = [[ResourceManager paths] reverseObjectEnumerator]; (path = [pathEnum nextObject]); ) { filePath = [[path stringByAppendingPathComponent:folderName] stringByAppendingPathComponent:fileName]; if ([fmgr oo_oxzFileExistsAtPath:filePath]) { result = filePath; break; } filePath = [path stringByAppendingPathComponent:fileName]; if ([fmgr oo_oxzFileExistsAtPath:filePath]) { result = filePath; break; } } if (result != nil) { OOLog(@"resourceManager.foundFile", @"Found %@/%@ at %@", folderName, fileName, filePath); if (useCache) { [cache setObject:result forKey:cacheKey inCache:@"resolved paths"]; } } return result; } /* use extreme caution in calling with usePathCache:NO - this can be * an extremely expensive operation */ + (id) retrieveFileNamed:(NSString *)fileName inFolder:(NSString *)folderName cache:(NSMutableDictionary **)ioCache key:(NSString *)key class:(Class)class usePathCache:(BOOL)useCache { id result = nil; NSString *path = nil; if (ioCache) { if (key == nil) key = [NSString stringWithFormat:@"%@:%@", folderName, fileName]; if (*ioCache != nil) { // return the cached object, if any result = [*ioCache objectForKey:key]; if (result) return result; } } path = [self pathForFileNamed:fileName inFolder:folderName cache:useCache]; if (path != nil) result = [[[class alloc] initWithContentsOfFile:path] autorelease]; if (result != nil && ioCache != NULL) { if (*ioCache == nil) *ioCache = [[NSMutableDictionary alloc] init]; [*ioCache setObject:result forKey:key]; } return result; } + (OOMusic *) ooMusicNamed:(NSString *)fileName inFolder:(NSString *)folderName { return [self retrieveFileNamed:fileName inFolder:folderName cache:NULL // Don't cache music objects; minimizing latency isn't really important. key:[NSString stringWithFormat:@"OOMusic:%@:%@", folderName, fileName] class:[OOMusic class] usePathCache:YES]; } + (OOSound *) ooSoundNamed:(NSString *)fileName inFolder:(NSString *)folderName { return [self retrieveFileNamed:fileName inFolder:folderName cache:&sSoundCache key:[NSString stringWithFormat:@"OOSound:%@:%@", folderName, fileName] class:[OOSound class] usePathCache:YES]; } + (NSString *) stringFromFilesNamed:(NSString *)fileName inFolder:(NSString *)folderName { return [self stringFromFilesNamed:fileName inFolder:folderName cache:YES]; } + (NSString *) stringFromFilesNamed:(NSString *)fileName inFolder:(NSString *)folderName cache:(BOOL)useCache { id result = nil; NSString *path = nil; NSString *key = nil; if (useCache) { key = [NSString stringWithFormat:@"%@:%@", folderName, fileName]; if (sStringCache != nil) { // return the cached object, if any result = [sStringCache objectForKey:key]; if (result) return result; } } path = [self pathForFileNamed:fileName inFolder:folderName cache:YES]; if (path != nil) result = [NSString stringWithContentsOfUnicodeFile:path]; if (result != nil && useCache) { if (sStringCache == nil) sStringCache = [[NSMutableDictionary alloc] init]; [sStringCache setObject:result forKey:key]; } return result; } + (NSDictionary *)loadScripts { NSMutableDictionary *loadedScripts = nil; NSArray *results = nil; NSArray *paths = nil; NSEnumerator *pathEnum = nil; NSString *path = nil; NSEnumerator *scriptEnum = nil; OOScript *script = nil; NSString *name = nil; NSAutoreleasePool *pool = nil; OOLog(@"script.load.world.begin", @"Loading world scripts..."); loadedScripts = [NSMutableDictionary dictionary]; paths = [ResourceManager paths]; for (pathEnum = [paths objectEnumerator]; (path = [pathEnum nextObject]); ) { // excluding world-scripts.plist also excludes script.js / script.plist // though as those core files don't and won't exist this is not // a problem. if (![ResourceManager corePlist:@"world-scripts.plist" excludedAt:path]) { pool = [[NSAutoreleasePool alloc] init]; @try { results = [OOScript worldScriptsAtPath:[path stringByAppendingPathComponent:@"Config"]]; if (results == nil) results = [OOScript worldScriptsAtPath:path]; if (results != nil) { for (scriptEnum = [results objectEnumerator]; (script = [scriptEnum nextObject]); ) { name = [script name]; if (name != nil) [loadedScripts setObject:script forKey:name]; else OOLog(@"script.load.unnamed", @"Discarding anonymous script %@", script); } } } @catch (NSException *exception) { OOLog(@"script.load.exception", @"***** %s encountered exception %@ (%@) while trying to load script from %@ -- ignoring this location.", __PRETTY_FUNCTION__, [exception name], [exception reason], path); // Ignore exception and keep loading other scripts. } [pool release]; } } if (OOLogWillDisplayMessagesInClass(@"script.load.world.listAll")) { NSUInteger count = [loadedScripts count]; if (count != 0) { NSMutableArray *displayNames = nil; NSEnumerator *scriptEnum = nil; OOScript *script = nil; NSString *displayString = nil; displayNames = [NSMutableArray arrayWithCapacity:count]; for (scriptEnum = [loadedScripts objectEnumerator]; (script = [scriptEnum nextObject]); ) { [displayNames addObject:[script displayName]]; } displayString = [[displayNames sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] componentsJoinedByString:@"\n "]; OOLog(@"script.load.world.listAll", @"Loaded %lu world scripts:\n %@", count, displayString); } else { OOLog(@"script.load.world.listAll", @"*** No world scripts loaded."); } } return loadedScripts; } + (BOOL) writeDiagnosticData:(NSData *)data toFileNamed:(NSString *)name { if (data == nil || name == nil) return NO; NSString *directory = [self diagnosticFileLocation]; if (directory == nil) return NO; NSArray *nameComponents = [name componentsSeparatedByString:@"/"]; NSUInteger count = [nameComponents count]; if (count > 1) { name = [nameComponents lastObject]; for (NSUInteger i = 0; i < count - 1; i++) { NSString *component = [nameComponents objectAtIndex:i]; if ([component hasPrefix:@"."]) { component = [@"!" stringByAppendingString:[component substringFromIndex:1]]; } directory = [directory stringByAppendingPathComponent:component]; [[NSFileManager defaultManager] oo_createDirectoryAtPath:directory attributes:nil]; } } return [data writeToFile:[directory stringByAppendingPathComponent:name] atomically:YES]; } + (BOOL) writeDiagnosticString:(NSString *)string toFileNamed:(NSString *)name { return [self writeDiagnosticData:[string dataUsingEncoding:NSUTF8StringEncoding] toFileNamed:name]; } + (BOOL) writeDiagnosticPList:(id)plist toFileNamed:(NSString *)name { NSData *data = [plist oldSchoolPListFormatWithErrorDescription:NULL]; if (data == nil) [NSPropertyListSerialization dataFromPropertyList:plist format:NSPropertyListXMLFormat_v1_0 errorDescription:NULL]; if (data == nil) return NO; return [self writeDiagnosticData:data toFileNamed:name]; } + (NSDictionary *) materialDefaults { return [self dictionaryFromFilesNamed:@"material-defaults.plist" inFolder:@"Config" andMerge:YES]; } + (BOOL)directoryExists:(NSString *)inPath create:(BOOL)inCreate { BOOL exists, directory; NSFileManager *fmgr = [NSFileManager defaultManager]; exists = [fmgr fileExistsAtPath:inPath isDirectory:&directory]; if (exists && !directory) { OOLog(@"resourceManager.write.buildPath.failed", @"Expected %@ to be a folder, but it is a file.", inPath); return NO; } if (!exists) { if (!inCreate) return NO; if (![fmgr oo_createDirectoryAtPath:inPath attributes:nil]) { OOLog(@"resourceManager.write.buildPath.failed", @"Could not create folder %@.", inPath); return NO; } } return YES; } + (NSString *) diagnosticFileLocation { return OOLogHandlerGetLogBasePath(); } + (void) logPaths { NSMutableArray *displayPaths = nil; NSEnumerator *pathEnum = nil; NSString *path = nil; // Prettify paths for logging. displayPaths = [NSMutableArray arrayWithCapacity:[sSearchPaths count]]; for (pathEnum = [sSearchPaths objectEnumerator]; (path = [pathEnum nextObject]); ) { [displayPaths addObject:[[path stringByStandardizingPath] stringByAbbreviatingWithTildeInPath]]; } OOLog(@"searchPaths.dumpAll", @"Resource paths: %@\n %@", sUseAddOns, [displayPaths componentsJoinedByString:@"\n "]); } + (void) clearCaches { [sSoundCache release]; sSoundCache = nil; [sStringCache release]; sStringCache = nil; } @end oolite-1.82/src/Core/Scripting/000077500000000000000000000000001256642440500163705ustar00rootroot00000000000000oolite-1.82/src/Core/Scripting/EntityOOJavaScriptExtensions.h000066400000000000000000000026421256642440500243260ustar00rootroot00000000000000/* EntityOOJavaScriptExtensions.h JavaScript support methods for entities. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "Entity.h" #import "OOJavaScriptEngine.h" @interface Entity (OOJavaScriptExtensions) - (BOOL) isVisibleToScripts; - (NSString *) oo_jsClassName; // Internal: - (void) getJSClass:(JSClass **)outClass andPrototype:(JSObject **)outPrototype; - (void) deleteJSSelf; @end @interface ShipEntity (OOJavaScriptExtensions) // "Normal" subentities, excluding flashers and exhaust plumes. - (NSArray *) subEntitiesForScript; - (void) setTargetForScript:(ShipEntity *)target; @end @interface PlayerEntity (OOJavaScriptExtensions) - (void) setJSSelf:(JSObject *)val context:(JSContext *)context; @end oolite-1.82/src/Core/Scripting/EntityOOJavaScriptExtensions.m000066400000000000000000000071501256642440500243320ustar00rootroot00000000000000/* EntityOOJavaScriptExtensions.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "EntityOOJavaScriptExtensions.h" #import "OOJSEntity.h" #import "OOJSShip.h" #import "OOJSStation.h" #import "StationEntity.h" #import "OOJSDock.h" #import "DockEntity.h" #import "OOPlanetEntity.h" #import "OOVisualEffectEntity.h" #import "OOJSVisualEffect.h" #import "WormholeEntity.h" #import "OOJSWormhole.h" @implementation Entity (OOJavaScriptExtensions) - (BOOL) isVisibleToScripts { return NO; } - (NSString *) oo_jsClassName { return @"Entity"; } - (jsval) oo_jsValueInContext:(JSContext *)context { JSClass *class = NULL; JSObject *prototype = NULL; jsval result = JSVAL_NULL; if (_jsSelf == NULL && [self isVisibleToScripts]) { // Create JS object [self getJSClass:&class andPrototype:&prototype]; _jsSelf = JS_NewObject(context, class, prototype, NULL); if (_jsSelf != NULL) { if (!JS_SetPrivate(context, _jsSelf, OOConsumeReference([self weakRetain]))) _jsSelf = NULL; } if (_jsSelf != NULL) { OOJSAddGCObjectRoot(context, &_jsSelf, "Entity jsSelf"); [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deleteJSSelf) name:kOOJavaScriptEngineWillResetNotification object:[OOJavaScriptEngine sharedEngine]]; } } if (_jsSelf != NULL) result = OBJECT_TO_JSVAL(_jsSelf); return result; // Analyzer: object leaked. [Expected, object is retained by JS object.] } - (void) getJSClass:(JSClass **)outClass andPrototype:(JSObject **)outPrototype { *outClass = JSEntityClass(); *outPrototype = JSEntityPrototype(); } - (void) deleteJSSelf { if (_jsSelf != NULL) { _jsSelf = NULL; JSContext *context = OOJSAcquireContext(); JS_RemoveObjectRoot(context, &_jsSelf); OOJSRelinquishContext(context); [[NSNotificationCenter defaultCenter] removeObserver:self name:kOOJavaScriptEngineWillResetNotification object:[OOJavaScriptEngine sharedEngine]]; } } @end @implementation ShipEntity (OOJavaScriptExtensions) - (BOOL) isVisibleToScripts { return YES; } - (void) getJSClass:(JSClass **)outClass andPrototype:(JSObject **)outPrototype { *outClass = JSShipClass(); *outPrototype = JSShipPrototype(); } - (NSString *) oo_jsClassName { return @"Ship"; } - (NSArray *) subEntitiesForScript { return [[self shipSubEntityEnumerator] allObjects]; } - (void) setTargetForScript:(ShipEntity *)target { ShipEntity *me = self; // Ensure coherence by not fiddling with subentities. while ([me isSubEntity]) { if (me == [me owner] || [me owner] == nil) break; me = (ShipEntity *)[me owner]; } while ([target isSubEntity]) { if (target == [target owner] || [target owner] == nil) break; target = (ShipEntity *)[target owner]; } if (![me isKindOfClass:[ShipEntity class]]) return; if (target != nil) { [me addTarget:target]; } else [me removeTarget:[me primaryTarget]]; } @end oolite-1.82/src/Core/Scripting/OOConstToJSString.h000066400000000000000000000234661256642440500220270ustar00rootroot00000000000000/* OOConstToJSString.h Convert various sets of integer constants to JavaScript strings and back again. See also: OOConstToString.h. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJavaScriptEngine.h" void OOConstToJSStringInit(JSContext *context); void OOConstToJSStringDestroy(void); struct ConstTable; // Private functions, don't use directly. JSString *OOJSStringFromConstantPRIVATE(JSContext *context, NSInteger value, struct ConstTable *table); NSUInteger OOConstantFromJSStringPRIVATE(JSContext *context, JSString *string, struct ConstTable *table, NSInteger defaultValue); NSUInteger OOConstantFromJSValuePRIVATE(JSContext *context, jsval value, struct ConstTable *table, NSInteger defaultValue); /* JSString *OOJSStringFromEntityStatus(JSContext *, OOEntityStatus) jsval OOJSValueFromEntityStatus(JSContext *, OOEntityStatus) OOEntityStatus OOEntityStatusFromJSString(JSContext *, JSString *) OOEntityStatus OOEntityStatusFromJSValue(JSContext *, jsval) Convert between JavaScript strings and OOEntityStatus. */ OOINLINE JSString *OOJSStringFromEntityStatus(JSContext *context, OOEntityStatus value) { extern struct ConstTable gOOEntityStatusConstTable; return OOJSStringFromConstantPRIVATE(context, value, &gOOEntityStatusConstTable); } OOINLINE jsval OOJSValueFromEntityStatus(JSContext *context, OOEntityStatus value) { return STRING_TO_JSVAL(OOJSStringFromEntityStatus(context, value)); } OOINLINE OOEntityStatus OOEntityStatusFromJSString(JSContext *context, JSString *string) { extern struct ConstTable gOOEntityStatusConstTable; return (OOEntityStatus)OOConstantFromJSStringPRIVATE(context, string, &gOOEntityStatusConstTable, kOOEntityStatusDefault); } OOINLINE OOEntityStatus OOEntityStatusFromJSValue(JSContext *context, jsval value) { extern struct ConstTable gOOEntityStatusConstTable; return (OOEntityStatus)OOConstantFromJSValuePRIVATE(context, value, &gOOEntityStatusConstTable, kOOEntityStatusDefault); } /* JSString *OOJSStringFromScanClass(JSContext *, OOScanClass) jsval OOJSValueFromScanClass(JSContext *, OOScanClass) OOScanClass OOScanClassFromJSString(JSContext *, JSString *) OOScanClass OOScanClassFromJSValue(JSContext *, jsval) Convert between JavaScript strings and OOScanClass. */ OOINLINE JSString *OOJSStringFromScanClass(JSContext *context, OOScanClass value) { extern struct ConstTable gOOScanClassConstTable; return OOJSStringFromConstantPRIVATE(context, value, &gOOScanClassConstTable); } OOINLINE jsval OOJSValueFromScanClass(JSContext *context, OOScanClass value) { return STRING_TO_JSVAL(OOJSStringFromScanClass(context, value)); } OOINLINE OOScanClass OOScanClassFromJSString(JSContext *context, JSString *string) { extern struct ConstTable gOOScanClassConstTable; return (OOScanClass)OOConstantFromJSStringPRIVATE(context, string, &gOOScanClassConstTable, kOOScanClassDefault); } OOINLINE OOScanClass OOScanClassFromJSValue(JSContext *context, jsval value) { extern struct ConstTable gOOScanClassConstTable; return (OOScanClass)OOConstantFromJSValuePRIVATE(context, value, &gOOScanClassConstTable, kOOScanClassDefault); } /* JSString *OOJSStringFromCompassMode(JSContext *, OOCompassMode) jsval OOJSValueFromCompassMode(JSContext *, OOCompassMode) OOCompassMode OOCompassModeFromJSString(JSContext *, JSString *) OOCompassMode OOCompassModeFromJSValue(JSContext *, jsval) Convert between JavaScript strings and OOCompassMode. */ OOINLINE JSString *OOJSStringFromCompassMode(JSContext *context, OOCompassMode value) { extern struct ConstTable gOOCompassModeConstTable; return OOJSStringFromConstantPRIVATE(context, value, &gOOCompassModeConstTable); } OOINLINE jsval OOJSValueFromCompassMode(JSContext *context, OOCompassMode value) { return STRING_TO_JSVAL(OOJSStringFromCompassMode(context, value)); } OOINLINE OOCompassMode OOCompassModeFromJSString(JSContext *context, JSString *string) { extern struct ConstTable gOOCompassModeConstTable; return (OOCompassMode)OOConstantFromJSStringPRIVATE(context, string, &gOOCompassModeConstTable, kOOCompassModeDefault); } OOINLINE OOCompassMode OOCompassModeFromJSValue(JSContext *context, jsval value) { extern struct ConstTable gOOCompassModeConstTable; return (OOCompassMode)OOConstantFromJSValuePRIVATE(context, value, &gOOCompassModeConstTable, kOOCompassModeDefault); } /* JSString *OOJSStringFromGUIScreenID(JSContext *, OOGUIScreenID) jsval OOJSValueFromGUIScreenID(JSContext *, OOGUIScreenID) OOGUIScreenID OOGUIScreenIDFromJSString(JSContext *, JSString *) OOGUIScreenID OOGUIScreenIDFromJSValue(JSContext *, jsval) Convert between JavaScript strings and OOGUIScreenID. */ OOINLINE JSString *OOJSStringFromGUIScreenID(JSContext *context, OOGUIScreenID value) { extern struct ConstTable gOOGUIScreenIDConstTable; return OOJSStringFromConstantPRIVATE(context, value, &gOOGUIScreenIDConstTable); } OOINLINE jsval OOJSValueFromGUIScreenID(JSContext *context, OOGUIScreenID value) { return STRING_TO_JSVAL(OOJSStringFromGUIScreenID(context, value)); } OOINLINE OOGUIScreenID OOGUIScreenIDFromJSString(JSContext *context, JSString *string) { extern struct ConstTable gOOGUIScreenIDConstTable; return (OOGUIScreenID)OOConstantFromJSStringPRIVATE(context, string, &gOOGUIScreenIDConstTable, kOOGUIScreenIDDefault); } OOINLINE OOGUIScreenID OOGUIScreenIDFromJSValue(JSContext *context, jsval value) { extern struct ConstTable gOOGUIScreenIDConstTable; return (OOGUIScreenID)OOConstantFromJSValuePRIVATE(context, value, &gOOGUIScreenIDConstTable, kOOGUIScreenIDDefault); } /* JSString *OOJSStringFromGalacticHyperspaceBehaviour(JSContext *, OOGalacticHyperspaceBehaviour) jsval OOJSValueFromGalacticHyperspaceBehaviour(JSContext *, OOGalacticHyperspaceBehaviour) OOGalacticHyperspaceBehaviour OOGalacticHyperspaceBehaviourFromJSString(JSContext *, JSString *) OOGalacticHyperspaceBehaviour OOGalacticHyperspaceBehaviourFromJSValue(JSContext *, jsval) Convert between JavaScript strings and OOGalacticHyperspaceBehaviour. */ OOINLINE JSString *OOJSStringFromGalacticHyperspaceBehaviour(JSContext *context, OOGalacticHyperspaceBehaviour value) { extern struct ConstTable gOOGalacticHyperspaceBehaviourConstTable; return OOJSStringFromConstantPRIVATE(context, value, &gOOGalacticHyperspaceBehaviourConstTable); } OOINLINE jsval OOJSValueFromGalacticHyperspaceBehaviour(JSContext *context, OOGalacticHyperspaceBehaviour value) { return STRING_TO_JSVAL(OOJSStringFromGalacticHyperspaceBehaviour(context, value)); } OOINLINE OOGalacticHyperspaceBehaviour OOGalacticHyperspaceBehaviourFromJSString(JSContext *context, JSString *string) { extern struct ConstTable gOOGalacticHyperspaceBehaviourConstTable; return (OOGalacticHyperspaceBehaviour)OOConstantFromJSStringPRIVATE(context, string, &gOOGalacticHyperspaceBehaviourConstTable, kOOGalacticHyperspaceBehaviourDefault); } OOINLINE OOGalacticHyperspaceBehaviour OOGalacticHyperspaceBehaviourFromJSValue(JSContext *context, jsval value) { extern struct ConstTable gOOGalacticHyperspaceBehaviourConstTable; return (OOGalacticHyperspaceBehaviour)OOConstantFromJSValuePRIVATE(context, value, &gOOGalacticHyperspaceBehaviourConstTable, kOOGalacticHyperspaceBehaviourDefault); } /* JSString *OOJSStringFromViewID(JSContext *, OOViewID) jsval OOJSValueFromViewID(JSContext *, OOViewID) OOViewID OOViewIDFromJSString(JSContext *, JSString *) OOViewID OOViewIDFromJSValue(JSContext *, jsval) Convert between JavaScript strings and OOViewID. */ OOINLINE JSString *OOJSStringFromViewID(JSContext *context, OOViewID value) { extern struct ConstTable gOOViewIDConstTable; return OOJSStringFromConstantPRIVATE(context, value, &gOOViewIDConstTable); } OOINLINE jsval OOJSValueFromViewID(JSContext *context, OOViewID value) { return STRING_TO_JSVAL(OOJSStringFromViewID(context, value)); } OOINLINE OOViewID OOViewIDFromJSString(JSContext *context, JSString *string) { extern struct ConstTable gOOViewIDConstTable; return (OOViewID)OOConstantFromJSStringPRIVATE(context, string, &gOOViewIDConstTable, kOOViewIDDefault); } OOINLINE OOViewID OOViewIDFromJSValue(JSContext *context, jsval value) { extern struct ConstTable gOOViewIDConstTable; return (OOViewID)OOConstantFromJSValuePRIVATE(context, value, &gOOViewIDConstTable, kOOViewIDDefault); } /* JSString *OOJSStringFromShipDamageType(JSContext *, OOShipDamageType) jsval OOJSValueFromShipDamageType(JSContext *, OOShipDamageType) Convert OOShipDamageType to JavaScript strings. */ OOINLINE JSString *OOJSStringFromShipDamageType(JSContext *context, OOShipDamageType value) { extern struct ConstTable gOOShipDamageTypeConstTable; return OOJSStringFromConstantPRIVATE(context, value, &gOOShipDamageTypeConstTable); } OOINLINE jsval OOJSValueFromShipDamageType(JSContext *context, OOShipDamageType value) { return STRING_TO_JSVAL(OOJSStringFromShipDamageType(context, value)); } OOINLINE JSString *OOJSStringFromLegalStatusReason(JSContext *context, OOLegalStatusReason value) { extern struct ConstTable gOOLegalStatusReasonConstTable; return OOJSStringFromConstantPRIVATE(context, value, &gOOLegalStatusReasonConstTable); } OOINLINE jsval OOJSValueFromLegalStatusReason(JSContext *context, OOLegalStatusReason value) { return STRING_TO_JSVAL(OOJSStringFromLegalStatusReason(context, value)); } oolite-1.82/src/Core/Scripting/OOConstToJSString.m000066400000000000000000000157631256642440500220350ustar00rootroot00000000000000/* OOConstToJSString.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "OOConstToJSString.h" /* Each type of constant has its own lookup table, which is a linear list of TableEntry structs plus a count. The list is statically initialized with the constant values and C strings. OOConstToJSStringInit() (through InitTable()) replaces the C strings with interned JSStrings (hence the void * type). These are subsequently constant and no other strings with the same value should occur, i.e. they should be comparable with pointer equality. Because I'm paranoid, we fall back to full string comparison if this fails. ConstTables are globals, which are accessed as local externs in the inlines in the header. All the advantages of globals with most of the advantages of encapsulation, sorta. -- Ahruman 2011-01-15 */ typedef struct { NSInteger value; const char *cString; JSString *jsString; } TableEntry; typedef struct ConstTable { NSUInteger count; TableEntry *entries; } ConstTable; #define TABLE(entries) { sizeof entries / sizeof *entries, entries } static BOOL sInited = NO; /* The interned string "UNDEFINED", returned by OOJSStringFromConstantPRIVATE() if passed a bogus constant value. */ static JSString *sUndefinedString; /* Initialize table contents (with C strings, see above) from table files. */ #define ENTRY(label, val) { .value = label, .cString = #label }, #define GALACTIC_HYPERSPACE_ENTRY(label, val) { .value = GALACTIC_HYPERSPACE_##label, .cString = #label }, #define DIFF_STRING_ENTRY(label, string) { .value = label, .cString = string }, static TableEntry sOOCompassModeTableEntries[] = { #include "OOCompassMode.tbl" }; static TableEntry sOOEntityStatusTableEntries[] = { #include "OOEntityStatus.tbl" }; static TableEntry sOOGalacticHyperspaceBehaviourTableEntries[] = { #include "OOGalacticHyperspaceBehaviour.tbl" }; static TableEntry sOOGUIScreenIDTableEntries[] = { #include "OOGUIScreenID.tbl" }; static TableEntry sOOScanClassTableEntries[] = { #include "OOScanClass.tbl" }; static TableEntry sOOViewIDTableEntries[] = { #include "OOViewID.tbl" }; static TableEntry sOOShipDamageTypeTableEntries[] = { #include "OOShipDamageType.tbl" }; static TableEntry sOOLegalStatusReasonTableEntries[] = { #include "OOLegalStatusReason.tbl" }; #undef ENTRY #undef GALACTIC_HYPERSPACE_ENTRY #undef DIFF_STRING_ENTRY ConstTable gOOCompassModeConstTable = TABLE(sOOCompassModeTableEntries); ConstTable gOOEntityStatusConstTable = TABLE(sOOEntityStatusTableEntries); ConstTable gOOGalacticHyperspaceBehaviourConstTable = TABLE(sOOGalacticHyperspaceBehaviourTableEntries); ConstTable gOOGUIScreenIDConstTable = TABLE(sOOGUIScreenIDTableEntries); ConstTable gOOScanClassConstTable = TABLE(sOOScanClassTableEntries); ConstTable gOOViewIDConstTable = TABLE(sOOViewIDTableEntries); ConstTable gOOShipDamageTypeConstTable = TABLE(sOOShipDamageTypeTableEntries); ConstTable gOOLegalStatusReasonConstTable = TABLE(sOOLegalStatusReasonTableEntries); static void InitTable(JSContext *context, ConstTable *table); // MARK: Initialization void OOConstToJSStringInit(JSContext *context) { NSCAssert(!sInited, @"OOConstToJSStringInit() called while already inited."); NSCParameterAssert(context != NULL && JS_IsInRequest(context)); sUndefinedString = JS_InternString(context, "UNDEFINED"); InitTable(context, &gOOEntityStatusConstTable); InitTable(context, &gOOCompassModeConstTable); InitTable(context, &gOOGalacticHyperspaceBehaviourConstTable); InitTable(context, &gOOGUIScreenIDConstTable); InitTable(context, &gOOScanClassConstTable); InitTable(context, &gOOViewIDConstTable); InitTable(context, &gOOShipDamageTypeConstTable); InitTable(context, &gOOLegalStatusReasonConstTable); sInited = YES; } void OOConstToJSStringDestroy(void) { NSCAssert(sInited, @"OOConstToJSStringDestroy() called while not inited."); sInited = NO; // jsString pointers are now officially junk. } static int CompareEntries(const void *a, const void *b) { const TableEntry *entA = a; const TableEntry *entB = b; if (entA->value < entB->value) return -1; if (entA->value > entB->value) return 1; return 0; } static void InitTable(JSContext *context, ConstTable *table) { NSCParameterAssert(context != NULL && JS_IsInRequest(context) && table != NULL); NSUInteger i; for(i = 0; i < table->count; i++) { table->entries[i].jsString = JS_InternString(context, table->entries[i].cString); } qsort(table->entries, table->count, sizeof *table->entries, CompareEntries); } // MARK: Lookup JSString *OOJSStringFromConstantPRIVATE(JSContext *context, NSInteger value, struct ConstTable *table) { NSCAssert1(sInited, @"%s called before OOConstToJSStringInit().", __PRETTY_FUNCTION__); NSCParameterAssert(context != NULL && JS_IsInRequest(context)); NSCParameterAssert(table != NULL && table->count > 0); // Binary search. NSUInteger min = 0, max = table->count - 1; NSInteger current; do { NSUInteger mid = (min + max) / 2; current = table->entries[mid].value; if (current < value) { min = mid + 1; } else if (current > value) { max = mid - 1; } else { return table->entries[mid].jsString; } } while (min <= max); return sUndefinedString; } NSUInteger OOConstantFromJSStringPRIVATE(JSContext *context, JSString *string, struct ConstTable *table, NSInteger defaultValue) { NSCAssert1(sInited, @"%s called before OOConstToJSStringInit().", __PRETTY_FUNCTION__); NSCParameterAssert(context != NULL && JS_IsInRequest(context) && table != NULL); // Quick pass: look for pointer-equal string. NSUInteger i, count = table->count; for(i = 0; i < count; i++) { if (table->entries[i].jsString == string) { return table->entries[i].value; } } // Slow pass: use string comparison. This is expected to fail. if (string != NULL) { for(i = 0; i < count; i++) { int32 result; if (JS_CompareStrings(context, string, table->entries[i].jsString, &result) && result == 0) { return table->entries[i].value; } } } // Fail. return defaultValue; } NSUInteger OOConstantFromJSValuePRIVATE(JSContext *context, jsval value, struct ConstTable *table, NSInteger defaultValue) { if (EXPECT(JSVAL_IS_STRING(value))) { return OOConstantFromJSStringPRIVATE(context, JSVAL_TO_STRING(value), table, defaultValue); } else { return defaultValue; } } oolite-1.82/src/Core/Scripting/OOJSCall.h000066400000000000000000000032401256642440500201060ustar00rootroot00000000000000/* OOJSCall.h Basic JavaScript-to-ObjC bridge implementation. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef NDEBUG #import #include /* OOJSCallObjCObjectMethod() Function for implementing JavaScript call() methods. The argument list is expected to be either a single string (selector), or a string ending with a : followed by arbitrary arguments which will be concatenated as a string. (This behaviour reflects Oolite's traditional scripting system and the expectations of its script methods. It also has the advantage that it can be done with GNUstep's buggy implementation of NSMethodSignature.) If the method returns an object, *outResult will be set to that object's -oo_jsValueInContext:. Otherwise, it will be left unchanged. argv is assumed to contain at least one value. */ BOOL OOJSCallObjCObjectMethod(JSContext *context, id object, NSString *oo_jsClassName, uintN argc, jsval *argv, jsval *outResult); #endif oolite-1.82/src/Core/Scripting/OOJSCall.m000066400000000000000000000156601256642440500201240ustar00rootroot00000000000000/* OOJSCall.h Basic JavaScript-to-ObjC bridge implementation. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef NDEBUG #import "OOJSCall.h" #import "OOJavaScriptEngine.h" #import "OOFunctionAttributes.h" #import "ShipEntity.h" #import "OOCollectionExtractors.h" #import "OOShaderUniformMethodType.h" #import "OOJSVector.h" #import "OOJSQuaternion.h" typedef enum { kMethodTypeInvalid = kOOShaderUniformTypeInvalid, kMethodTypeCharVoid = kOOShaderUniformTypeChar, kMethodTypeUnsignedCharVoid = kOOShaderUniformTypeUnsignedChar, kMethodTypeShortVoid = kOOShaderUniformTypeShort, kMethodTypeUnsignedShortVoid = kOOShaderUniformTypeUnsignedShort, kMethodTypeIntVoid = kOOShaderUniformTypeInt, kMethodTypeUnsignedIntVoid = kOOShaderUniformTypeUnsignedInt, kMethodTypeLongVoid = kOOShaderUniformTypeLong, kMethodTypeUnsignedLongVoid = kOOShaderUniformTypeUnsignedLong, kMethodTypeFloatVoid = kOOShaderUniformTypeFloat, kMethodTypeDoubleVoid = kOOShaderUniformTypeDouble, kMethodTypeVectorVoid = kOOShaderUniformTypeVector, kMethodTypeQuaternionVoid = kOOShaderUniformTypeQuaternion, kMethodTypeMatrixVoid = kOOShaderUniformTypeMatrix, kMethodTypePointVoid = kOOShaderUniformTypePoint, kMethodTypeObjectVoid = kOOShaderUniformTypeObject, kMethodTypeObjectObject, kMethodTypeVoidVoid, kMethodTypeVoidObject } MethodType; static MethodType GetMethodType(id object, SEL selector); OOINLINE BOOL MethodExpectsParameter(MethodType type) { return type == kMethodTypeVoidObject || type == kMethodTypeObjectObject; } BOOL OOJSCallObjCObjectMethod(JSContext *context, id object, NSString *oo_jsClassName, uintN argc, jsval *argv, jsval *outResult) { OOJS_PROFILE_ENTER NSString *selectorString = nil; SEL selector = NULL; NSString *paramString = nil; MethodType type; BOOL haveParameter = NO, error = NO; id result = nil; if (argc == 0) { OOJSReportError(context, @"%@.callObjC(): no selector specified.", oo_jsClassName); return NO; } if ([object isKindOfClass:[ShipEntity class]]) { [PLAYER setScriptTarget:object]; } selectorString = OOStringFromJSValue(context, argv[0]); // Join all parameters together with spaces. if (1 < argc && [selectorString hasSuffix:@":"]) { haveParameter = YES; paramString = [NSString concatenationOfStringsFromJavaScriptValues:argv + 1 count:argc - 1 separator:@" " inContext:context]; } selector = NSSelectorFromString(selectorString); if ([object respondsToSelector:selector]) { // Validate signature. type = GetMethodType(object, selector); if (MethodExpectsParameter(type) && !haveParameter) { OOJSReportError(context, @"%@.callObjC(): method %@ requires a parameter.", oo_jsClassName, selectorString); error = YES; } else { IMP method = [object methodForSelector:selector]; switch (type) { case kMethodTypeVoidObject: [object performSelector:selector withObject:paramString]; break; case kMethodTypeObjectObject: result = [object performSelector:selector withObject:paramString]; break; case kMethodTypeObjectVoid: result = [object performSelector:selector]; if ([selectorString hasSuffix:@"_bool"]) result = [NSNumber numberWithBool:OOBooleanFromObject(result, NO)]; break; case kMethodTypeVoidVoid: [object performSelector:selector]; break; case kMethodTypeCharVoid: case kMethodTypeUnsignedCharVoid: case kMethodTypeShortVoid: case kMethodTypeUnsignedShortVoid: case kMethodTypeIntVoid: case kMethodTypeUnsignedIntVoid: case kMethodTypeLongVoid: result = [NSNumber numberWithLongLong:OOCallIntegerMethod(object, selector, method, (OOShaderUniformType)type)]; break; case kMethodTypeUnsignedLongVoid: result = [NSNumber numberWithUnsignedLongLong:OOCallIntegerMethod(object, selector, method, (OOShaderUniformType)type)]; break; case kMethodTypeFloatVoid: case kMethodTypeDoubleVoid: result = [NSNumber numberWithDouble:OOCallFloatMethod(object, selector, method, (OOShaderUniformType)type)]; break; case kMethodTypeVectorVoid: { Vector v = ((VectorReturnMsgSend)method)(object, selector); *outResult = OBJECT_TO_JSVAL(JSVectorWithVector(context, v)); break; } case kMethodTypeQuaternionVoid: { Quaternion q = ((QuaternionReturnMsgSend)method)(object, selector); *outResult = OBJECT_TO_JSVAL(JSQuaternionWithQuaternion(context, q)); break; } case kMethodTypeMatrixVoid: case kMethodTypePointVoid: case kMethodTypeInvalid: OOJSReportError(context, @"%@.callObjC(): method %@ cannot be called from JavaScript.", oo_jsClassName, selectorString); error = YES; break; } if (result != nil) { *outResult = [result oo_jsValueInContext:context]; } } } else { OOJSReportError(context, @"%@.callObjC(): %@ does not respond to method %@.", oo_jsClassName, [object shortDescription], selectorString); error = YES; } return !error; OOJS_PROFILE_EXIT } // Template class providing method signature strings for the four signatures we support. @interface OOJSCallMethodSignatureTemplateClass: NSObject - (void)voidVoidMethod; - (void)voidObjectMethod:(id)object; - (id)objectObjectMethod:(id)object; @end static BOOL SignatureMatch(NSMethodSignature *sig, SEL selector) { NSMethodSignature *template = nil; template = [OOJSCallMethodSignatureTemplateClass instanceMethodSignatureForSelector:selector]; return [sig isEqual:template]; } static MethodType GetMethodType(id object, SEL selector) { NSMethodSignature *sig = [object methodSignatureForSelector:selector]; if (SignatureMatch(sig, @selector(voidVoidMethod))) return kMethodTypeVoidVoid; if (SignatureMatch(sig, @selector(voidObjectMethod:))) return kMethodTypeVoidObject; if (SignatureMatch(sig, @selector(objectObjectMethod:))) return kMethodTypeObjectObject; MethodType type = (MethodType)OOShaderUniformTypeFromMethodSignature(sig); if (type != kMethodTypeInvalid) return type; return kMethodTypeInvalid; } @implementation OOJSCallMethodSignatureTemplateClass: NSObject - (void)voidVoidMethod {} - (void)voidObjectMethod:(id)object {} - (id)objectObjectMethod:(id)object { return nil; } @end #endif oolite-1.82/src/Core/Scripting/OOJSClock.h000066400000000000000000000015621256642440500202730ustar00rootroot00000000000000/* OOJSClock.h JavaScript clock global object. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include void InitOOJSClock(JSContext *context, JSObject *global); oolite-1.82/src/Core/Scripting/OOJSClock.m000066400000000000000000000164501256642440500203020ustar00rootroot00000000000000/* OOJSClock.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSClock.h" #import "OOJavaScriptEngine.h" #import "Universe.h" #import "OOJSPlayer.h" #import "PlayerEntity.h" #import "PlayerEntityScriptMethods.h" #import "OOStringParsing.h" #import "OODebugStandards.h" static JSBool ClockGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); // Methods static JSBool JSClockToString(JSContext *context, uintN argc, jsval *vp); static JSBool ClockClockStringForTime(JSContext *context, uintN argc, jsval *vp); static JSBool ClockAddSeconds(JSContext *context, uintN argc, jsval *vp); static JSClass sClockClass = { "Clock", JSCLASS_HAS_PRIVATE, JS_PropertyStub, // addProperty JS_PropertyStub, // delProperty ClockGetProperty, // getProperty JS_StrictPropertyStub, // setProperty JS_EnumerateStub, // enumerate JS_ResolveStub, // resolve JS_ConvertStub, // convert JS_FinalizeStub, // finalize JSCLASS_NO_OPTIONAL_MEMBERS }; enum { // Property IDs kClock_absoluteSeconds, // game real time clock, double, read-only kClock_seconds, // game clock time, double, read-only kClock_minutes, // game clock time minutes (rounded down), integer double, read-only kClock_hours, // game clock time hours (rounded down), integer double, read-only kClock_days, // game clock time days (rounded down), int, read-only kClock_secondsComponent, // second component of game clock time, double, read-only kClock_minutesComponent, // minute component of game clock time (rounded down), int, read-only kClock_hoursComponent, // hour component of game clock time (rounded down), int, read-only kClock_daysComponent, // day component of game clock time (rounded down), int, read-only kClock_clockString, // game clock time as display string, string, read-only kClock_isAdjusting, // clock is adjusting, boolean, read-only kClock_adjustedSeconds, // game clock time, including adjustments, double, read-only kClock_legacy_scriptTimer // legacy scriptTimer_number, double, read-only }; static JSPropertySpec sClockProperties[] = { // JS name ID flags { "absoluteSeconds", kClock_absoluteSeconds, OOJS_PROP_READONLY_CB }, { "seconds", kClock_seconds, OOJS_PROP_READONLY_CB }, { "minutes", kClock_minutes, OOJS_PROP_READONLY_CB }, { "hours", kClock_hours, OOJS_PROP_READONLY_CB }, { "days", kClock_days, OOJS_PROP_READONLY_CB }, { "secondsComponent", kClock_secondsComponent, OOJS_PROP_READONLY_CB }, { "minutesComponent", kClock_minutesComponent, OOJS_PROP_READONLY_CB }, { "hoursComponent", kClock_hoursComponent, OOJS_PROP_READONLY_CB }, { "daysComponent", kClock_daysComponent, OOJS_PROP_READONLY_CB }, { "clockString", kClock_clockString, OOJS_PROP_READONLY_CB }, { "isAdjusting", kClock_isAdjusting, OOJS_PROP_READONLY_CB }, { "adjustedSeconds", kClock_adjustedSeconds, OOJS_PROP_READONLY_CB }, { "legacy_scriptTimer", kClock_legacy_scriptTimer, OOJS_PROP_READONLY_CB }, { 0 } }; static JSFunctionSpec sClockMethods[] = { // JS name Function min args { "toString", JSClockToString, 0 }, { "clockStringForTime", ClockClockStringForTime, 1 }, { "addSeconds", ClockAddSeconds, 1 }, { 0 } }; void InitOOJSClock(JSContext *context, JSObject *global) { JSObject *clockPrototype = JS_InitClass(context, global, NULL, &sClockClass, OOJSUnconstructableConstruct, 0, sClockProperties, sClockMethods, NULL, NULL); JS_DefineObject(context, global, "clock", &sClockClass, clockPrototype, OOJS_PROP_READONLY); } static JSBool ClockGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); double clockTime; clockTime = [player clockTime]; switch (JSID_TO_INT(propID)) { case kClock_absoluteSeconds: return JS_NewNumberValue(context, [UNIVERSE getTime], value); case kClock_seconds: return JS_NewNumberValue(context, clockTime, value); case kClock_minutes: return JS_NewNumberValue(context, floor(clockTime / 60.0), value); case kClock_hours: return JS_NewNumberValue(context, floor(clockTime /3600.0), value); return YES; case kClock_secondsComponent: *value = INT_TO_JSVAL(fmod(clockTime, 60.0)); return YES; case kClock_minutesComponent: *value = INT_TO_JSVAL(fmod(floor(clockTime / 60.0), 60.0)); return YES; case kClock_hoursComponent: *value = INT_TO_JSVAL(fmod(floor(clockTime / 3600.0), 24.0)); return YES; case kClock_days: case kClock_daysComponent: *value = INT_TO_JSVAL(floor(clockTime / 86400.0)); return YES; case kClock_clockString: *value = OOJSValueFromNativeObject(context, [player dial_clock]); return YES; case kClock_isAdjusting: *value = OOJSValueFromBOOL([player clockAdjusting]); return YES; case kClock_adjustedSeconds: return JS_NewNumberValue(context, [player clockTimeAdjusted], value); case kClock_legacy_scriptTimer: OOStandardsDeprecated(@"The legacy_scriptTimer property is deprecated"); return JS_NewNumberValue(context, [player scriptTimer], value); default: OOJSReportBadPropertySelector(context, this, propID, sClockProperties); return NO; } OOJS_NATIVE_EXIT } // *** Methods *** // toString() : String static JSBool JSClockToString(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOJS_RETURN_OBJECT([OOPlayerForScripting() dial_clock]); OOJS_NATIVE_EXIT } // clockStringForTime(time : Number) : String static JSBool ClockClockStringForTime(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) double time; if (EXPECT_NOT(argc < 1 || !JS_ValueToNumber(context, OOJS_ARGV[0], &time))) { jsval arg = JSVAL_VOID; if (argc > 0) arg = OOJS_ARGV[0]; OOJSReportBadArguments(context, @"Clock", @"clockStringForTime", 1, &arg, nil, @"number"); return NO; } OOJS_RETURN_OBJECT(ClockToString(time, NO)); OOJS_NATIVE_EXIT } // addSeconds(seconds : Number) : String static JSBool ClockAddSeconds(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) double time; const double kMaxTime = 30.0 * 24.0 * 3600.0; // 30 days if (EXPECT_NOT(argc < 1 || !JS_ValueToNumber(context, OOJS_ARGV[0], &time))) { jsval arg = JSVAL_VOID; if (argc > 0) arg = OOJS_ARGV[0]; OOJSReportBadArguments(context, @"Clock", @"addSeconds", 1, &arg, nil, @"number"); return NO; } if (time > kMaxTime || time < 1.0 || !isfinite(time)) { OOJS_RETURN_BOOL(NO); } [OOPlayerForScripting() addToAdjustTime:time]; OOJS_RETURN_BOOL(YES); OOJS_NATIVE_EXIT } oolite-1.82/src/Core/Scripting/OOJSDock.h000066400000000000000000000016501256642440500201160ustar00rootroot00000000000000/* OOJSDock.h JavaScript proxy for DockEntities. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #include @class DockEntity; void InitOOJSDock(JSContext *context, JSObject *global); oolite-1.82/src/Core/Scripting/OOJSDock.m000066400000000000000000000152361256642440500201300ustar00rootroot00000000000000/* OOJSDock.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSDock.h" #import "OOJSEntity.h" #import "OOJSShip.h" #import "OOJSPlayer.h" #import "OOJavaScriptEngine.h" #import "DockEntity.h" #import "GameController.h" static JSObject *sDockPrototype; static BOOL JSDockGetDockEntity(JSContext *context, JSObject *stationObj, DockEntity **outEntity); static BOOL JSDockGetShipEntity(JSContext *context, JSObject *shipObj, ShipEntity **outEntity); static JSBool DockIsQueued(JSContext *context, uintN argc, jsval *vp); static JSBool DockGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool DockSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); static JSClass sDockClass = { "Dock", JSCLASS_HAS_PRIVATE, JS_PropertyStub, // addProperty JS_PropertyStub, // delProperty DockGetProperty, // getProperty DockSetProperty, // setProperty JS_EnumerateStub, // enumerate JS_ResolveStub, // resolve JS_ConvertStub, // convert OOJSObjectWrapperFinalize,// finalize JSCLASS_NO_OPTIONAL_MEMBERS }; enum { // Property IDs kDock_allowsDocking, kDock_disallowedDockingCollides, kDock_allowsLaunching, kDock_dockingQueueLength, kDock_launchingQueueLength }; static JSPropertySpec sDockProperties[] = { // JS name ID flags { "allowsDocking", kDock_allowsDocking, OOJS_PROP_READWRITE_CB }, { "disallowedDockingCollides", kDock_disallowedDockingCollides, OOJS_PROP_READWRITE_CB }, { "allowsLaunching", kDock_allowsLaunching, OOJS_PROP_READWRITE_CB }, { "dockingQueueLength", kDock_dockingQueueLength, OOJS_PROP_READONLY_CB }, { "launchingQueueLength", kDock_launchingQueueLength, OOJS_PROP_READONLY_CB }, { 0 } }; static JSFunctionSpec sDockMethods[] = { // JS name Function min args { "isQueued", DockIsQueued, 1 }, { 0 } }; void InitOOJSDock(JSContext *context, JSObject *global) { sDockPrototype = JS_InitClass(context, global, JSShipPrototype(), &sDockClass, OOJSUnconstructableConstruct, 0, sDockProperties, sDockMethods, NULL, NULL); OOJSRegisterObjectConverter(&sDockClass, OOJSBasicPrivateObjectConverter); OOJSRegisterSubclass(&sDockClass, JSShipClass()); } static BOOL JSDockGetDockEntity(JSContext *context, JSObject *dockObj, DockEntity **outEntity) { OOJS_PROFILE_ENTER BOOL result; Entity *entity = nil; if (outEntity == NULL) return NO; *outEntity = nil; result = OOJSEntityGetEntity(context, dockObj, &entity); if (!result) return NO; if (![entity isKindOfClass:[DockEntity class]]) return NO; *outEntity = (DockEntity *)entity; return YES; OOJS_PROFILE_EXIT } static BOOL JSDockGetShipEntity(JSContext *context, JSObject *shipObj, ShipEntity **outEntity) { OOJS_PROFILE_ENTER BOOL result; Entity *entity = nil; if (outEntity == NULL) return NO; *outEntity = nil; result = OOJSEntityGetEntity(context, shipObj, &entity); if (!result) return NO; if (![entity isKindOfClass:[ShipEntity class]]) return NO; *outEntity = (ShipEntity *)entity; return YES; OOJS_PROFILE_EXIT } @implementation DockEntity (OOJavaScriptExtensions) - (void)getJSClass:(JSClass **)outClass andPrototype:(JSObject **)outPrototype { *outClass = &sDockClass; *outPrototype = sDockPrototype; } - (NSString *) oo_jsClassName { return @"Dock"; } @end static JSBool DockGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) DockEntity *entity = nil; if (!JSDockGetDockEntity(context, this, &entity)) return NO; if (entity == nil) { *value = JSVAL_VOID; return YES; } switch (JSID_TO_INT(propID)) { case kDock_allowsDocking: *value = OOJSValueFromBOOL([entity allowsDocking]); return YES; case kDock_disallowedDockingCollides: *value = OOJSValueFromBOOL([entity disallowedDockingCollides]); return YES; case kDock_allowsLaunching: *value = OOJSValueFromBOOL([entity allowsLaunching]); return YES; case kDock_dockingQueueLength: return JS_NewNumberValue(context, [entity countOfShipsInDockingQueue], value); case kDock_launchingQueueLength: return JS_NewNumberValue(context, [entity countOfShipsInLaunchQueue], value); default: OOJSReportBadPropertySelector(context, this, propID, sDockProperties); return NO; } OOJS_NATIVE_EXIT } static JSBool DockSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) DockEntity *entity = nil; JSBool bValue; // int32 iValue; if (!JSDockGetDockEntity(context, this, &entity)) return NO; if (entity == nil) return YES; switch (JSID_TO_INT(propID)) { case kDock_allowsDocking: if (JS_ValueToBoolean(context, *value, &bValue)) { [entity setAllowsDocking:bValue]; return YES; } break; case kDock_allowsLaunching: if (JS_ValueToBoolean(context, *value, &bValue)) { [entity setAllowsLaunching:bValue]; return YES; } break; case kDock_disallowedDockingCollides: if (JS_ValueToBoolean(context, *value, &bValue)) { [entity setDisallowedDockingCollides:bValue]; return YES; } break; default: OOJSReportBadPropertySelector(context, this, propID, sDockProperties); return NO; } OOJSReportBadPropertyValue(context, this, propID, sDockProperties, *value); return NO; OOJS_NATIVE_EXIT } // *** Methods *** static JSBool DockIsQueued(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) BOOL result = NO; DockEntity *dock = nil; JSDockGetDockEntity(context, OOJS_THIS, &dock); if (argc == 0) { OOJSReportBadArguments(context, @"Dock", @"isQueued", MIN(argc, 1U), OOJS_ARGV, nil, @"ship"); return NO; } ShipEntity *ship = nil; JSDockGetShipEntity(context, JSVAL_TO_OBJECT(OOJS_ARGV[0]), &ship); if (ship != nil) { result = [dock shipIsInDockingQueue:ship]; } OOJS_RETURN_BOOL(result); OOJS_NATIVE_EXIT } oolite-1.82/src/Core/Scripting/OOJSEngineDebuggerHelpers.m000066400000000000000000000155651256642440500234520ustar00rootroot00000000000000/* OOJSEngineDebuggerHelpers.m JavaScript support for Oolite Copyright (C) 2007-2013 David Taylor and Jens Ayton. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* These functions exist to help debugging JavaScript code. They can be called directly from gdb, for example: call (char *)JSValueToStrDbg(someValue) The functions are: const char *JSValueToStrDbg(jsval) const char *JSObjectToStrDbg(JSObject *) const char *JSStringToStrDbg(JSString *) Converts any JS value/object/JSString to a string, using the complete process and potentially calling into SpiderMonkey with a secondory context and invoking JS toString() methods. This might mess up SpiderMonkey internal state in some cases. const char *JSValueToStrSafeDbg(jsval) const char *JSObjectToStrSafeDbg(JSObject *) const char *JSStringToStrSafeDbg(JSString *) As above, but without calling into SpiderMonkey functions that require a context. In particular, as of the FF4b9 version of SpiderMonkey only interned strings can be converted, and for objects only the class name is provided. const char *JSIDToStrSafeDbg(jsid) Like JSValueToStrSafeDbg() for jsids. (String jsids must always be interned, so this is generally sufficient.) const char *JSValueTypeDbg(jsval) Returns the type of the jsval, or the class name if it's an object. All dynamic strings are autoreleased. Another useful function is OOJSDumpStack (results are found in the log): call OOJSDumpStack(context) A set of macros can be found in tools/gdb-macros.txt. In almost all Oolite functions that deal with JavaScript, there is a single JSContext called "context". In SpiderMonkey functions, it's called "cx". In addition to calling them from the debug console, Xcode users might want to use them in data formatters (by double-clicking the "Summary" field for a variable of the appropriate type). I recommend the following: jsval: {JSValueToStrSafeDbg($VAR)}:s jsval*: {JSValueToStrSafeDbg(*$VAR)}:s jsid: {JSIDToStrSafeDbg($VAR)}:s JSObject*: {JSObjectToStrSafeDbg($VAR)}:s JSString*: {JSStringToStrSafeDbg($VAR)}:s These, and a variety of Oolite type formatters, can be set up using Mac-specific/DataFormatters. */ #ifndef NDEBUG #import "OOJavaScriptEngine.h" const char *JSValueToStrDbg(jsval val) { JSContext *context = OOJSAcquireContext(); const char *result = [OOStringFromJSValueEvenIfNull(context, val) UTF8String]; OOJSRelinquishContext(context); return result; } const char *JSObjectToStrDbg(JSObject *obj) { if (obj == NULL) return "null"; return JSValueToStrDbg(OBJECT_TO_JSVAL(obj)); } const char *JSStringToStrDbg(JSString *str) { if (str == NULL) return "null"; return JSValueToStrDbg(STRING_TO_JSVAL(str)); } const char *JSValueTypeDbg(jsval val) { if (JSVAL_IS_INT(val)) return "integer"; if (JSVAL_IS_DOUBLE(val)) return "double"; if (JSVAL_IS_STRING(val)) return "string"; if (JSVAL_IS_BOOLEAN(val)) return "boolean"; if (JSVAL_IS_NULL(val)) return "null"; if (JSVAL_IS_VOID(val)) return "void"; #ifdef JS_USE_JSVAL_JSID_STRUCT_TYPES if (JSVAL_IS_MAGIC_IMPL(val)) { switch(val.s.payload.why) { case JS_ARRAY_HOLE: return "magic (array hole)"; case JS_ARGS_HOLE: return "magic (args hole)"; case JS_NATIVE_ENUMERATE: return "magic (native enumerate)"; case JS_NO_ITER_VALUE: return "magic (no iter value)"; case JS_GENERATOR_CLOSING: return "magic (generator closing)"; case JS_NO_CONSTANT: return "magic (no constant)"; case JS_THIS_POISON: return "magic (this poison)"; case JS_ARG_POISON: return "magic (arg poison)"; case JS_SERIALIZE_NO_NODE: return "magic (serialize no node)"; case JS_GENERIC_MAGIC: return "magic (generic)"; }; return "magic"; } #endif if (JSVAL_IS_OBJECT(val)) return OOJSGetClass(NULL, JSVAL_TO_OBJECT(val))->name; // Fun fact: although a context is required if JS_THREADSAFE is defined, it isn't actually used. return "unknown"; } // Doesn't follow pointers, mess with requests or otherwise poke the SpiderMonkey. const char *JSValueToStrSafeDbg(jsval val) { NSString *formatted = nil; if (JSVAL_IS_INT(val)) formatted = [NSString stringWithFormat:@"%i", JSVAL_TO_INT(val)]; else if (JSVAL_IS_DOUBLE(val)) formatted = [NSString stringWithFormat:@"%g", JSVAL_TO_DOUBLE(val)]; else if (JSVAL_IS_BOOLEAN(val)) formatted = (JSVAL_TO_BOOLEAN(val)) ? @"true" : @"false"; else if (JSVAL_IS_STRING(val)) { JSString *string = JSVAL_TO_STRING(val); const jschar *chars = NULL; size_t length = JS_GetStringLength(string); if (JS_StringHasBeenInterned(string)) { chars = JS_GetInternedStringChars(string); } // Flat strings can be extracted without a context, but cannot be detected. if (chars == NULL) formatted = [NSString stringWithFormat:@"string [%zu chars]", length]; else formatted = [NSString stringWithCharacters:chars length:length]; } else if (JSVAL_IS_VOID(val)) return "undefined"; else return JSValueTypeDbg(val); return [formatted UTF8String]; } const char *JSObjectToStrSafeDbg(JSObject *obj) { if (obj == NULL) return "null"; return JSValueToStrSafeDbg(OBJECT_TO_JSVAL(obj)); } const char *JSStringToStrSafeDbg(JSString *str) { if (str == NULL) return "null"; return JSValueToStrSafeDbg(STRING_TO_JSVAL(str)); } const char *JSIDToStrSafeDbg(jsid anID) { NSString *formatted = nil; if (JSID_IS_INT(anID)) formatted = [NSString stringWithFormat:@"%i", JSID_TO_INT(anID)]; else if (JSID_IS_VOID(anID)) return "void"; else if (JSID_IS_EMPTY(anID)) return "empty"; else if (JSID_IS_ZERO(anID)) return "0"; else if (JSID_IS_OBJECT(anID)) return OOJSGetClass(NULL, JSID_TO_OBJECT(anID))->name; else if (JSID_IS_DEFAULT_XML_NAMESPACE(anID)) return "default XML namespace"; else if (JSID_IS_STRING(anID)) { JSString *string = JSID_TO_STRING(anID); const jschar *chars = NULL; size_t length = JS_GetStringLength(string); if (JS_StringHasBeenInterned(string)) { chars = JS_GetInternedStringChars(string); } else { // Bug; jsid strings must be interned. return "*** uninterned string in jsid! ***"; } formatted = [NSString stringWithCharacters:chars length:length]; } else { formatted = [NSString stringWithFormat:@"unknown <0x%llX>", (long long)JSID_BITS(anID)]; } return [formatted UTF8String]; } #endif oolite-1.82/src/Core/Scripting/OOJSEngineNativeWrappers.h000066400000000000000000000130221256642440500233320ustar00rootroot00000000000000/* OOJSEngineNativeWrappers.h (Included by OOJavaScriptEngine.h) Exception safety and profiling macros. Every JavaScript native callback that could concievably cause an Objective-C exception should begin with OOJS_NATIVE_ENTER() and end with OOJS_NATIVE_EXIT. Callbacks which have been carefully audited for potential exceptions, and support functions called from JavaScript native callbacks, may start with OOJS_PROFILE_ENTER and end with OOJS_PROFILE_EXIT to be included in profiling reports. Functions using either of these pairs _must_ return before OOJS_NATIVE_EXIT/OOJS_PROFILE_EXIT, or they will crash. For functions with a non-scalar return type, OOJS_PROFILE_EXIT should be replaced with OOJS_PROFILE_EXIT_VAL(returnValue). The returnValue is never used (and should be a constant expression), but is required to placate the compiler. For values with void return, use OOJS_PROFILE_EXIT_VOID. It is not necessary to insert a return statement before OOJS_PROFILE_EXIT_VOID. JavaScript support for Oolite Copyright (C) 2007-2013 David Taylor and Jens Ayton. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef OOJS_PROFILE #define OOJS_PROFILE (!defined(NDEBUG)) #endif #if OOJS_PROFILE #define OOJS_PROFILE_ENTER_NAMED(NAME) \ { \ OOJS_DECLARE_PROFILE_STACK_FRAME(oojsProfilerStackFrame) \ @try { \ OOJSProfileEnter(&oojsProfilerStackFrame, NAME); #define OOJS_PROFILE_ENTER \ OOJS_PROFILE_ENTER_NAMED(__FUNCTION__) #define OOJS_PROFILE_EXIT_VAL(rval) \ } @finally { \ OOJSProfileExit(&oojsProfilerStackFrame); \ } \ OOJSUnreachable(__FUNCTION__, __FILE__, __LINE__); \ return rval; \ } #define OOJS_PROFILE_EXIT_VOID return; OOJS_PROFILE_EXIT_VAL() #define OOJS_PROFILE_ENTER_FOR_NATIVE OOJS_PROFILE_ENTER #else #define OOJS_PROFILE_ENTER { #define OOJS_PROFILE_EXIT_VAL(rval) } OOJSUnreachable(__FUNCTION__, __FILE__, __LINE__); return (rval); #define OOJS_PROFILE_EXIT_VOID } return; #define OOJS_PROFILE_ENTER_FOR_NATIVE @try { #endif // OOJS_PROFILE #define OOJS_NATIVE_ENTER(cx) \ { \ JSContext *oojsNativeContext = (cx); \ OOJS_PROFILE_ENTER_FOR_NATIVE #define OOJS_NATIVE_EXIT \ } @catch(id exception) { \ OOJSReportWrappedException(oojsNativeContext, exception); \ return NO; \ OOJS_PROFILE_EXIT_VAL(NO) \ } void OOJSReportWrappedException(JSContext *context, id exception); #ifndef NDEBUG void OOJSUnreachable(const char *function, const char *file, unsigned line) NO_RETURN_FUNC; #else #define OOJSUnreachable(function, file, line) OO_UNREACHABLE() #endif #define OOJS_PROFILE_EXIT OOJS_PROFILE_EXIT_VAL(0) #define OOJS_PROFILE_EXIT_JSVAL OOJS_PROFILE_EXIT_VAL(JSVAL_VOID) /* OOJS_BEGIN_FULL_NATIVE() and OOJS_END_FULL_NATIVE These macros are used to bracket sections of native Oolite code within JS callbacks which may take a long time. Thet do two things: pause the time limiter, and (in JS_THREADSAFE builds) suspend the current JS context request. These macros must be used in balanced pairs. They introduce a scope. JSAPI functions may not be used, directly or indirectily, between these macros unless explicitly opening a request first. */ #if JS_THREADSAFE #define OOJS_BEGIN_FULL_NATIVE(context) \ { \ OOJSPauseTimeLimiter(); \ JSContext *oojsRequestContext = (context); \ jsrefcount oojsRequestRefCount = JS_SuspendRequest(oojsRequestContext); \ @try \ { #define OOJS_END_FULL_NATIVE \ } \ @finally \ { \ JS_ResumeRequest(oojsRequestContext, oojsRequestRefCount); \ OOJSResumeTimeLimiter(); \ } \ } #else #define OOJS_BEGIN_FULL_NATIVE(context) \ { \ (void)(context); \ OOJSPauseTimeLimiter(); \ @try \ { #define OOJS_END_FULL_NATIVE \ } \ @finally \ { \ OOJSResumeTimeLimiter(); \ } \ } #endif #if OOJS_PROFILE #import "OOProfilingStopwatch.h" /* Profiler implementation details. This should be internal to OOJSTimeManagement.m, but needs to be declared on the stack by the macros above when profiling is enabled. */ typedef struct OOJSProfileStackFrame OOJSProfileStackFrame; struct OOJSProfileStackFrame { OOJSProfileStackFrame *back; // Stack link const void *key; // Key to look up profile entries. May be any pointer; currently const char * for native frames and JSFunction * for JS frames. const char *function; // Name of function, for native frames. OOHighResTimeValue startTime; // Time frame was entered. OOTimeDelta subTime; // Time spent in subroutine calls. OOTimeDelta *total; // Pointer to accumulator for this type of frame. void (*cleanup)(OOJSProfileStackFrame *); // Cleanup function if needed (used for JS frames). }; #define OOJS_DECLARE_PROFILE_STACK_FRAME(name) OOJSProfileStackFrame name; void OOJSProfileEnter(OOJSProfileStackFrame *frame, const char *function); void OOJSProfileExit(OOJSProfileStackFrame *frame); #else #define OOJS_DECLARE_PROFILE_STACK_FRAME(name) #define OOJSProfileEnter(frame, function) do {} while (0) #define OOJSProfileExit(frame) do {} while (0) #endif oolite-1.82/src/Core/Scripting/OOJSEngineTimeManagement.h000066400000000000000000000120371256642440500232600ustar00rootroot00000000000000/* OOJSEngineTimeManagement.h Functionality related to time limiting and profiling of JavaScript code. Copyright (C) 2010-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOJavaScriptEngine.h" /* Time Limiter The time limiter stops scripts from running an arbitrarily long time. The time limiter must be started before calling into JavaScript code. Calls to OOJSStartTimeLimiter() and OOJSStopTimeLimiter() must be balanced, and may be nested. OOJSStartTimeLimiterWithTimeLimit() is like OOJSStartTimeLimiter(), but specifies a custom time limit. This limit is only used if the limiter is actually stopped. The time limiter can be paused and resumed for native operations that are known to be slow. OOJSPauseTimeLimiter() and OOJSResumeTimeLimiter() must be balanced and can be nested, but the nest count may be negative - it is valid to call OOJSResumeTimeLimiter() followed by OOJSPauseTimeLimiter(). */ #define OOJSStartTimeLimiter() OOJSStartTimeLimiterWithTimeLimit(0.0) #ifndef NDEBUG #define OOJSStartTimeLimiterWithTimeLimit(limit) OOJSStartTimeLimiterWithTimeLimit_(limit, OOLOG_FILE_NAME, __LINE__) #define OOJSStopTimeLimiter() OOJSStopTimeLimiter_(OOLOG_FILE_NAME, __LINE__) void OOJSStartTimeLimiterWithTimeLimit_(OOTimeDelta limit, const char *file, unsigned line); void OOJSStopTimeLimiter_(const char *file, unsigned line); #else void OOJSStartTimeLimiterWithTimeLimit(OOTimeDelta limit); void OOJSStopTimeLimiter(void); #endif #define kOOJSLongTimeLimit (5.0) #if OOJS_PROFILE #import "OOProfilingStopwatch.h" /* Profiling support. OOJSBeginProfiling(trace), OOJSEndProfiling(), OOJSIsProfiling() Start, stop, and query profiling mode. It is a hard error to start profiling while already profiling. If trace is set, all profileable functions will be logged. The actual profile will be of little use in this case because of logging overhead. OOJSCopyTimeLimiterNominalStartTime() Copy the nominal start time for the time limiter. This is the actual time with any time extensions (paused periods) added in. OOJSResetTimeLimiter() Set the time limiter start time to now. OOJSGetTimeLimiterLimit() OOJSSetTimeLimiterLimit() Manipulate the timeout. */ @class OOTimeProfile, OOTimeProfileEntry; void OOJSBeginProfiling(BOOL trace); OOTimeProfile *OOJSEndProfiling(void); BOOL OOJSIsProfiling(void); OOHighResTimeValue OOJSCopyTimeLimiterNominalStartTime(void); void OOJSResetTimeLimiter(void); OOTimeDelta OOJSGetTimeLimiterLimit(void); void OOJSSetTimeLimiterLimit(OOTimeDelta limit); /* NOTE: the profiler declarations that need to be visible to functions that are profiled are found in OOJSEngineNativeWrappers.h. */ @interface OOTimeProfile: NSObject { @private double _totalTime; double _nativeTime; double _extensionTime; #ifdef MOZ_TRACE_JSCALLS double _javaScriptTime; #endif double _profilerOverhead; NSArray *_profileEntries; } - (double) totalTime; - (double) javaScriptTime; - (double) nativeTime; - (double) extensionTime; - (double) nonExtensionTime; - (double) profilerOverhead; - (NSArray *) profileEntries; // Array of OOTimeProfileEntry @end @interface OOTimeProfileEntry: NSObject { @private NSString *_function; unsigned long _hitCount; double _totalTimeSum; double _selfTimeSum; double _totalTimeMax; double _selfTimeMax; #ifdef MOZ_TRACE_JSCALLS JSFunction *_jsFunction; #endif } - (NSString *) description; - (NSString *) function; - (NSUInteger) hitCount; - (double) totalTimeSum; - (double) selfTimeSum; - (double) totalTimeAverage; - (double) selfTimeAverage; - (double) totalTimeMax; - (double) selfTimeMax; - (BOOL) isJavaScriptFrame; - (NSComparisonResult) compareByTotalTime:(OOTimeProfileEntry *)other; - (NSComparisonResult) compareByTotalTimeReverse:(OOTimeProfileEntry *)other; - (NSComparisonResult) compareBySelfTime:(OOTimeProfileEntry *)other; - (NSComparisonResult) compareBySelfTimeReverse:(OOTimeProfileEntry *)other; @end #endif @class OOJavaScriptEngine; void OOJSTimeManagementInit(OOJavaScriptEngine *engine, JSRuntime *runtime); oolite-1.82/src/Core/Scripting/OOJSEngineTimeManagement.m000066400000000000000000000574411256642440500232750ustar00rootroot00000000000000/* OOJSEngineTimeManagement.h Copyright (C) 2010-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #import "OOJSEngineTimeManagement.h" #import "OOProfilingStopwatch.h" #import "OOJSScript.h" #import "OOCollectionExtractors.h" #import "OOLoggingExtended.h" #if OOLITE_LINUX // Workaround for clang/glibc incompatibility. #define __block __glibc_block #endif #include #if OOLITE_LINUX #undef __block #endif #if OO_DEBUG #define OOJS_DEBUG_LIMITER 1 #else #define OOJS_DEBUG_LIMITER 0 #endif static unsigned sLimiterStartDepth; static int sLimiterPauseDepth; static OOHighResTimeValue sLimiterStart; static OOHighResTimeValue sLimiterPauseStart; static double sLimiterTimeLimit; #if OOJS_DEBUG_LIMITER #define OOJS_TIME_LIMIT (0.2) // seconds #else #define OOJS_TIME_LIMIT (1) // seconds #endif static BOOL sStop; #ifndef NDEBUG static const char *sLastStartedFile; static unsigned sLastStartedLine; static const char *sLastStoppedFile; static unsigned sLastStoppedLine; #endif #if OOJS_PROFILE && defined(MOZ_TRACE_JSCALLS) static void FunctionCallback(JSFunction *function, JSScript *script, JSContext *context, int entering); #endif #ifndef NDEBUG void OOJSStartTimeLimiterWithTimeLimit_(OOTimeDelta limit, const char *file, unsigned line) #else void OOJSStartTimeLimiterWithTimeLimit(OOTimeDelta limit) #endif { #if OOJS_DEBUG_LIMITER OOLog(@"script.javaScript.timeLimit.debug",@"Limiter starting: %u => %u",sLimiterStartDepth,sLimiterStartDepth+1); #endif if (sLimiterStartDepth++ == 0) { if (limit <= 0.0) limit = OOJS_TIME_LIMIT; sLimiterTimeLimit = limit; sLimiterPauseDepth = 0; OODisposeHighResTime(sLimiterStart); sLimiterStart = OOGetHighResTime(); } #ifndef NDEBUG sLastStartedFile = file; sLastStartedLine = line; #endif } #ifndef NDEBUG void OOJSStopTimeLimiter_(const char *file, unsigned line) #else void OOJSStopTimeLimiter(void) #endif { #ifndef NDEBUG if (sLimiterStartDepth == 0) { OOLog(@"bug.javaScript.limiterDepth", @"Attempt to stop JavaScript time limiter while it is already fully stopped. This is an internal bug, please report it. (Last start: %@:%u, last valid stop: %@:%u, this stop attempt: %@:%u.)", OOLogAbbreviatedFileName(sLastStartedFile), sLastStartedLine, OOLogAbbreviatedFileName(sLastStoppedFile), sLastStoppedLine, OOLogAbbreviatedFileName(file), line); return; } sLastStoppedFile = file; sLastStoppedLine = line; #if OOJS_DEBUG_LIMITER OOLog(@"script.javaScript.timeLimit.debug",@"Limiter ending: %u <= %u",sLimiterStartDepth-1,sLimiterStartDepth); #endif #endif if (--sLimiterStartDepth == 0) sLimiterTimeLimit = 0.0; } void OOJSPauseTimeLimiter(void) { if (sLimiterPauseDepth++ == 0) { OODisposeHighResTime(sLimiterPauseStart); sLimiterPauseStart = OOGetHighResTime(); } } void OOJSResumeTimeLimiter(void) { if (--sLimiterPauseDepth == 0) { OOHighResTimeValue now = OOGetHighResTime(); OOTimeDelta elapsed = OOHighResTimeDeltaInSeconds(sLimiterPauseStart, now); OODisposeHighResTime(now); sLimiterTimeLimit += elapsed; } } #ifndef NDEBUG OOHighResTimeValue OOJSCopyTimeLimiterNominalStartTime(void) { return sLimiterStart; } void OOJSResetTimeLimiter(void) { OODisposeHighResTime(sLimiterStart); sLimiterStart = OOGetHighResTime(); sStop = NO; } OOTimeDelta OOJSGetTimeLimiterLimit(void) { return sLimiterTimeLimit; } void OOJSSetTimeLimiterLimit(OOTimeDelta limit) { sLimiterTimeLimit = limit; } #endif @implementation OOJavaScriptEngine (WatchdogTimer) - (void) watchdogTimerThread { for (;;) { #if OOLITE_WINDOWS Sleep(OOJS_TIME_LIMIT * 1000); #else usleep(OOJS_TIME_LIMIT * 1000000); #endif if (EXPECT(sLimiterStartDepth == 0 || sLimiterPauseDepth > 0)) continue; // Most of the time, a script isn't running. // Note: if you add logging here, you need a manual autorelease pool. OOHighResTimeValue now = OOGetHighResTime(); OOTimeDelta elapsed = OOHighResTimeDeltaInSeconds(sLimiterStart, now); OODisposeHighResTime(now); if (EXPECT_NOT(elapsed > sLimiterTimeLimit)) { sStop = YES; JS_TriggerAllOperationCallbacks(_runtime); } } } @end static JSBool OperationCallback(JSContext *context) { if (!sStop) return YES; JS_ClearPendingException(context); OOHighResTimeValue now = OOGetHighResTime(); OOTimeDelta elapsed = OOHighResTimeDeltaInSeconds(sLimiterStart, now); OODisposeHighResTime(now); if (elapsed <= sLimiterTimeLimit) return YES; OOLogERR(@"script.javaScript.timeLimit", @"Script \"%@\" ran for %g seconds and has been terminated.", [[OOJSScript currentlyRunningScript] name], elapsed); #ifndef NDEBUG OOJSDumpStack(context); #endif // FIXME: we really should put something in the JS log here, but since that's implemented in JS there are complications. return NO; } static JSBool ContextCallback(JSContext *context, uintN contextOp) { if (contextOp == JSCONTEXT_NEW) { JS_SetOperationCallback(context, OperationCallback); #if OOJS_PROFILE && defined(MOZ_TRACE_JSCALLS) JS_SetFunctionCallback(context, (JSFunctionCallback)FunctionCallback); // Naughtily casts away consts, because const JSContexts and JSFunctions are useless. #endif } return YES; } void OOJSTimeManagementInit(OOJavaScriptEngine *engine, JSRuntime *runtime) { [NSThread detachNewThreadSelector:@selector(watchdogTimerThread) toTarget:engine withObject:nil]; JS_SetContextCallback(runtime, ContextCallback); } #if OOJS_PROFILE #ifndef MOZ_TRACE_JSCALLS #warning Profiling is enabled, but MOZ_TRACE_JSCALLS is disabled, so only native functions will be profiled. #endif static BOOL sProfiling = NO; static BOOL sTracing = NO; static OOJSProfileStackFrame *sProfileStack = NULL; static NSMapTable *sProfileInfo; static double sProfilerOverhead; static double sProfilerTotalNativeTime; static double sProfilerTotalJavaScriptTime; static double sProfilerEntryTimeLimit; static OOHighResTimeValue sProfilerStartTime; @interface OOTimeProfile (Private) - (void) setTotalTime:(double)value; - (void) setNativeTime:(double)value; #ifdef MOZ_TRACE_JSCALLS - (void) setJavaScriptTime:(double)value; #endif - (void) setProfilerOverhead:(double)value; - (void) setExtensionTime:(double)value; - (void) setProfileEntries:(NSArray *)value; - (NSDictionary *) propertyListRepresentation; @end @interface OOTimeProfileEntry (Private) - (id) initWithCName:(const char *)name; #ifdef MOZ_TRACE_JSCALLS - (id) initWithJSFunction:(JSFunction *)function context:(JSContext *)context; #endif - (void) addSampleWithTotalTime:(OOTimeDelta)totalTime selfTime:(OOTimeDelta)selfTime; - (NSDictionary *) propertyListRepresentation; @end void OOJSBeginProfiling(BOOL trace) { assert(sProfiling == NO); sProfiling = YES; sTracing = trace; sProfileInfo = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks, NSObjectMapValueCallBacks, 100); sProfilerOverhead = 0.0; sProfilerTotalNativeTime = 0.0; sProfilerTotalJavaScriptTime = 0.0; sProfilerEntryTimeLimit = OOJSGetTimeLimiterLimit(); // This should be last for precision. sProfilerStartTime = OOGetHighResTime(); if (trace) { OOLog(@"script.javaScript.trace", @">>>> Beginning trace."); OOLogIndent(); } } OOTimeProfile *OOJSEndProfiling(void) { // This should be at the top for precision. OOHighResTimeValue now = OOGetHighResTime(); // Time limiter should be as close to outermost as practical. OOJSPauseTimeLimiter(); assert(sProfiling && sProfileStack == NULL); sProfiling = NO; OOTimeProfile *result = [[OOTimeProfile alloc] init]; [result setTotalTime:OOHighResTimeDeltaInSeconds(sProfilerStartTime, now)]; [result setNativeTime:sProfilerTotalNativeTime]; #ifdef MOZ_TRACE_JSCALLS [result setJavaScriptTime:sProfilerTotalJavaScriptTime]; #endif [result setProfilerOverhead:sProfilerOverhead]; double currentTimeLimit = OOJSGetTimeLimiterLimit(); [result setExtensionTime:currentTimeLimit - sProfilerEntryTimeLimit]; [result setProfileEntries:[NSAllMapTableValues(sProfileInfo) sortedArrayUsingSelector:@selector(compareBySelfTimeReverse:)]]; if (sTracing) { OOLogOutdent(); OOLog(@"script.javaScript.trace", @"<<<< End of trace."); sTracing = NO; } // Clean up. NSFreeMapTable(sProfileInfo); OODisposeHighResTime(sProfilerStartTime); OODisposeHighResTime(now); OOJSResumeTimeLimiter(); return result; } BOOL OOJSIsProfiling(void) { return sProfiling; } void OOJSBeginTracing(void); void OOJSEndTracing(void); BOOL OOJSIsTracing(void); static void UpdateProfileForFrame(OOHighResTimeValue now, OOJSProfileStackFrame *frame); #ifdef MOZ_TRACE_JSCALLS static void CleanUpJSFrame(OOJSProfileStackFrame *frame) { free(frame); } static void TraceEnterJSFunction(JSContext *context, JSFunction *function, OOTimeProfileEntry *profileEntry) { NSMutableString *name = [NSMutableString stringWithFormat:@"%@(", [profileEntry function]]; BOOL isNative = JS_GetFunctionNative(context, function) != NULL; NSString *frameTag = nil; NSString *logMsgClass = nil; if (!isNative) { // Get stack frame and find arguments. JSStackFrame *frame = NULL; BOOL first = YES; jsval this; JSObject *scope; JSPropertyDescArray properties = { 0 , NULL }; unsigned i; // Temporarily disable profiling as we'll call out to profiled functions to get value descriptions. sProfiling = NO; if (JS_FrameIterator(context, &frame) != NULL) { if (JS_IsConstructorFrame(context, frame)) { [name insertString:@"new " atIndex:0]; } if (JS_GetFrameThis(context, frame, &this)) { [name appendFormat:@"this: %@", OOJSDescribeValue(context, this, YES)]; first = NO; } scope = JS_GetFrameScopeChain(context, frame); if (scope != NULL && JS_GetPropertyDescArray(context, scope, &properties)) { for (i = 0; i < properties.length; i++) { JSPropertyDesc *prop = &properties.array[i]; if (prop->flags & JSPD_ARGUMENT) { if (!first) [name appendFormat:@", "]; else first = NO; [name appendFormat:@"%@: %@", OOStringFromJSValueEvenIfNull(context, prop->id), OOJSDescribeValue(context, prop->value, YES)]; } } } } sProfiling = YES; frameTag = @"JS"; // JavaScript logMsgClass = @"script.javaScript.trace.JS"; } else { frameTag = @"NW"; // Native Wrapper logMsgClass = @"script.javaScript.trace.NW"; } [name appendString:@")"]; OOLog(logMsgClass, @">> %@ [%@]", name, frameTag); OOLogIndent(); } static void FunctionCallback(JSFunction *function, JSScript *script, JSContext *context, int entering) { if (EXPECT(!sProfiling)) return; // Ignore native functions. Ours get their own entries anyway, SpiderMonkey's are elided. if (!sTracing && JS_GetFunctionNative(context, function) != NULL) return; if (EXPECT_NOT(function == NULL)) return; OOHighResTimeValue start = OOGetHighResTime(); NSAutoreleasePool *pool = [NSAutoreleasePool new]; if (entering > 0) { // Create profile entry up front so we can shove the JS function in it. OOTimeProfileEntry *entry = NSMapGet(sProfileInfo, function); if (entry == nil) { entry = [[OOTimeProfileEntry alloc] initWithJSFunction:function context:context]; NSMapInsertKnownAbsent(sProfileInfo, function, entry); [entry release]; } if (EXPECT_NOT(sTracing)) { // We use EXPECT_NOT here because profiles are time-critical and traces are not. TraceEnterJSFunction(context, function, entry); } // Make a stack frame on the heap. OOJSProfileStackFrame *frame = malloc(sizeof(OOJSProfileStackFrame)); assert(frame != NULL); *frame = (OOJSProfileStackFrame) { .back = sProfileStack, .key = function, .startTime = start, .subTime = 0.0, .total = &sProfilerTotalJavaScriptTime, .cleanup = CleanUpJSFrame }; sProfileStack = frame; } else { // Exiting. assert(sProfileStack != NULL && sProfileStack->cleanup == CleanUpJSFrame); UpdateProfileForFrame(start, sProfileStack); } [pool release]; OOHighResTimeValue end = OOGetHighResTime(); double currentOverhead = OOHighResTimeDeltaInSeconds(start, end); sProfilerOverhead += currentOverhead; OODisposeHighResTime(start); OODisposeHighResTime(end); } #endif void OOJSProfileEnter(OOJSProfileStackFrame *frame, const char *function) { if (EXPECT(!sProfiling)) return; if (EXPECT_NOT(sTracing)) { // We use EXPECT_NOT here because profiles are time-critical and traces are not. OOLog(@"script.javaScript.trace.ON", @">> %s [ON]", function); OOLogIndent(); } *frame = (OOJSProfileStackFrame) { .back = sProfileStack, .key = function, .function = function, .startTime = OOGetHighResTime(), .total = &sProfilerTotalNativeTime }; sProfileStack = frame; } void OOJSProfileExit(OOJSProfileStackFrame *frame) { if (EXPECT(!sProfiling)) return; OOHighResTimeValue now = OOGetHighResTime(); NSAutoreleasePool *pool = [NSAutoreleasePool new]; BOOL done = NO; /* It's possible there could be JavaScript frames on top of this frame if a JS native returned false. Or possibly not. The semantics of JS_SetFunctionCallback() aren't specified in detail. -- Ahruman 2011-01-16 */ for (;;) { assert(sProfileStack != NULL); done = (sProfileStack == frame); UpdateProfileForFrame(now, sProfileStack); if (EXPECT(done)) break; } [pool release]; OODisposeHighResTime(frame->startTime); OOHighResTimeValue end = OOGetHighResTime(); double currentOverhead = OOHighResTimeDeltaInSeconds(now, end); sProfilerOverhead += currentOverhead; /* Equivalent of pausing/resuming time limiter, except that it guarantees excluded time will match profiler overhead if there are no other pauses happening. */ if (sLimiterPauseDepth == 0) sLimiterTimeLimit += currentOverhead; OODisposeHighResTime(now); OODisposeHighResTime(end); } static void UpdateProfileForFrame(OOHighResTimeValue now, OOJSProfileStackFrame *frame) { sProfileStack = frame->back; OOTimeProfileEntry *entry = NSMapGet(sProfileInfo, frame->key); if (entry == nil) { entry = [[OOTimeProfileEntry alloc] initWithCName:frame->function]; NSMapInsertKnownAbsent(sProfileInfo, frame->key, entry); [entry release]; } OOTimeDelta time = OOHighResTimeDeltaInSeconds(frame->startTime, now); OOTimeDelta selfTime = time - frame->subTime; [entry addSampleWithTotalTime:time selfTime:selfTime]; *(frame->total) += selfTime; if (sProfileStack != NULL) sProfileStack->subTime += time; if (frame->cleanup != NULL) frame->cleanup(frame); if (EXPECT_NOT(sTracing)) OOLogOutdent(); } @implementation OOTimeProfile - (void) dealloc { DESTROY(_profileEntries); [super dealloc]; } - (NSString *) description { double totalTime = [self totalTime]; NSMutableString *result = [NSMutableString stringWithFormat: @"Total time: %g ms\n" "JavaScript: %g ms, native: %g ms\n" "Counted towards limit: %g ms, excluded: %g ms\n" "Profiler overhead: %g ms", totalTime * 1000.0, [self javaScriptTime] * 1000.0, [self nativeTime] * 1000.0, [self nonExtensionTime] * 1000.0, [self extensionTime] * 1000.0, [self profilerOverhead] * 1000.0]; NSArray *profileEntries = [self profileEntries]; NSUInteger i, count = [profileEntries count]; if (count != 0) { [result appendString:@"\n NAME T COUNT TOTAL SELF TOTAL% SELF% SELFMAX"]; for (i = 0; i < count; i++) { // [result appendFormat:@"\n %@", [_profileEntries objectAtIndex:i]]; OOTimeProfileEntry *entry = [profileEntries objectAtIndex:i]; double totalPc = [entry totalTimeSum] * 100.0 / totalTime; double selfPc = [entry selfTimeSum] * 100.0 / totalTime; [result appendFormat:@"\n%60s %c%7lu %8.2f %8.2f %5.1f %5.1f %8.2f", [[entry function] UTF8String], [entry isJavaScriptFrame] ? 'J' : 'N', (unsigned long)[entry hitCount], [entry totalTimeSum] * 1000.0, [entry selfTimeSum] * 1000.0, totalPc, selfPc, [entry selfTimeMax] * 1000.0]; } } return result; } - (double) totalTime { return _totalTime; } - (void) setTotalTime:(double)value { _totalTime = value; } - (double) javaScriptTime { #ifdef MOZ_TRACE_JSCALLS return _javaScriptTime; #else return _totalTime - _nativeTime; #endif } #ifdef MOZ_TRACE_JSCALLS - (void) setJavaScriptTime:(double)value { _javaScriptTime = value; } #endif - (double) nativeTime { return _nativeTime; } - (void) setNativeTime:(double)value { _nativeTime = value; } - (double) extensionTime { return _extensionTime; } - (void) setExtensionTime:(double)value { _extensionTime = value; } - (double) nonExtensionTime { return _totalTime - _extensionTime; } - (double) profilerOverhead { return _profilerOverhead; } - (void) setProfilerOverhead:(double)value { _profilerOverhead = value; } - (NSArray *) profileEntries { return _profileEntries; } - (void) setProfileEntries:(NSArray *)value { if (_profileEntries != value) { [_profileEntries release]; _profileEntries = [value retain]; } } - (jsval) oo_jsValueInContext:(JSContext *)context { return OOJSValueFromNativeObject(context, [self propertyListRepresentation]); } - (NSDictionary *) propertyListRepresentation { NSArray *profileEntries = [self profileEntries]; NSMutableArray *convertedEntries = [NSMutableArray arrayWithCapacity:[profileEntries count]]; NSEnumerator *entryEnum = nil; OOTimeProfileEntry *entry = nil; for (entryEnum = [profileEntries objectEnumerator]; (entry = [entryEnum nextObject]); ) { [convertedEntries addObject:[entry propertyListRepresentation]]; } return [NSDictionary dictionaryWithObjectsAndKeys: profileEntries, @"profiles", [NSNumber numberWithDouble:[self totalTime]], @"totalTime", [NSNumber numberWithDouble:[self javaScriptTime]], @"javaScriptTime", [NSNumber numberWithDouble:[self nativeTime]], @"nativeTime", [NSNumber numberWithDouble:[self extensionTime]], @"extensionTime", [NSNumber numberWithDouble:[self nonExtensionTime]], @"nonExtensionTime", [NSNumber numberWithDouble:[self profilerOverhead]], @"profilerOverhead", nil]; } @end @implementation OOTimeProfileEntry - (id) initWithCName:(const char *)name { NSAssert(sProfiling, @"Can't create profile entries while not profiling."); if ((self = [super init])) { if (name != NULL) { _function = [[NSString stringWithUTF8String:name] retain]; } } return self; } #if MOZ_TRACE_JSCALLS - (id) initWithJSFunction:(JSFunction *)function context:(JSContext *)context { if ((self = [self initWithCName:NULL])) { // Temporarily disable profiling so we don't profile the profiler while it's profiling the profilee. sProfiling = NO; _jsFunction = function; NSString *funcName = nil; JSString *jsName = JS_GetFunctionId(_jsFunction); if (jsName != NULL) funcName = [OOStringFromJSString(context, jsName) retain]; else funcName = @""; // If it's a non-native function, get its source location. NSString *location = nil; if (JS_GetFunctionNative(context, function) == NULL) { JSStackFrame *frame = NULL; if (JS_FrameIterator(context, &frame) != NULL) { location = OOJSDescribeLocation(context, frame); } } if (location != nil) { _function = [[NSString alloc] initWithFormat:@"(%@) %@", location, funcName]; } else _function = [funcName retain]; sProfiling = YES; } return self; } #endif - (void) dealloc { DESTROY(_function); [super dealloc]; } - (void) addSampleWithTotalTime:(OOTimeDelta)totalTime selfTime:(OOTimeDelta)selfTime { _hitCount++; _totalTimeSum += totalTime; _selfTimeSum += selfTime; _totalTimeMax = fmax(_totalTimeMax, totalTime); _selfTimeMax = fmax(_selfTimeMax, selfTime); } - (NSString *) description { if (_hitCount == 0) return [NSString stringWithFormat:@"%@: --", _function]; // Convert everything to milliseconds. float totalTimeSum = _totalTimeSum * 1000.0; float selfTimeSum = _selfTimeSum * 1000.0; float totalTimeMax = _totalTimeMax * 1000.0; float selfTimeMax = _selfTimeMax * 1000.0; if (totalTimeSum == selfTimeSum && totalTimeMax == selfTimeMax) { if (_hitCount == 1) { return [NSString stringWithFormat:@"%@: 1 time, %g ms", _function, totalTimeSum]; } else { return [NSString stringWithFormat:@"%@: %lu times, total %g ms, avg %g ms, max %g ms", _function, _hitCount, totalTimeSum, totalTimeSum / _hitCount, totalTimeMax]; } } else { if (_hitCount == 1) { return [NSString stringWithFormat:@"%@: 1 time, %g ms (self %g ms)", _function, totalTimeSum, selfTimeSum]; } else { return [NSString stringWithFormat:@"%@: %lu times, total %g ms (self %g ms), avg %g ms (self %g ms), max %g ms, max self %g ms", _function, _hitCount, totalTimeSum, selfTimeSum, totalTimeSum / _hitCount, selfTimeSum / _hitCount, totalTimeMax, selfTimeMax]; } } } - (NSString *) function { return _function; } - (NSUInteger) hitCount { return _hitCount; } - (double) totalTimeSum { return _totalTimeSum; } - (double) selfTimeSum { return _selfTimeSum; } - (double) totalTimeAverage { return _hitCount ? (_totalTimeSum / _hitCount) : 0.0; } - (double) selfTimeAverage { return _hitCount ? (_selfTimeSum / _hitCount) : 0.0; } - (double) totalTimeMax { return _totalTimeMax; } - (double) selfTimeMax { return _selfTimeMax; } - (BOOL) isJavaScriptFrame { #if MOZ_TRACE_JSCALLS return _jsFunction != NULL; #else return NO; #endif } - (NSComparisonResult) compareByTotalTime:(OOTimeProfileEntry *)other { return -[self compareByTotalTimeReverse:other]; } - (NSComparisonResult) compareByTotalTimeReverse:(OOTimeProfileEntry *)other { double selfTotal = [self totalTimeSum]; double otherTotal = [other totalTimeSum]; if (selfTotal < otherTotal) return NSOrderedDescending; if (selfTotal > otherTotal) return NSOrderedAscending; return NSOrderedSame; } - (NSComparisonResult) compareBySelfTime:(OOTimeProfileEntry *)other { return -[self compareBySelfTimeReverse:other]; } - (NSComparisonResult) compareBySelfTimeReverse:(OOTimeProfileEntry *)other { double selfTotal = [self selfTimeSum]; double otherTotal = [other selfTimeSum]; if (selfTotal < otherTotal) return NSOrderedDescending; if (selfTotal > otherTotal) return NSOrderedAscending; return NSOrderedSame; } - (jsval) oo_jsValueInContext:(JSContext *)context { return OOJSValueFromNativeObject(context, [self propertyListRepresentation]); } - (NSDictionary *) propertyListRepresentation { return [NSDictionary dictionaryWithObjectsAndKeys: _function, @"name", [NSNumber numberWithUnsignedInteger:[self hitCount]], @"hitCount", [NSNumber numberWithDouble:[self totalTimeSum]], @"totalTimeSum", [NSNumber numberWithDouble:[self selfTimeSum]], @"selfTimeSum", [NSNumber numberWithDouble:[self totalTimeAverage]], @"totalTimeAverage", [NSNumber numberWithDouble:[self selfTimeAverage]], @"selfTimeAverage", [NSNumber numberWithDouble:[self totalTimeMax]], @"totalTimeMax", [NSNumber numberWithDouble:[self selfTimeMax]], @"selfTimeMax", [NSNumber numberWithBool:[self isJavaScriptFrame]], @"isJavaScriptFrame", nil]; } @end #endif oolite-1.82/src/Core/Scripting/OOJSEntity.h000066400000000000000000000045451256642440500205200ustar00rootroot00000000000000/* OOJSEntity.h JavaScript proxy for entities. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #import "OOJavaScriptEngine.h" #import "Universe.h" @class Entity; void InitOOJSEntity(JSContext *context, JSObject *global); BOOL JSValueToEntity(JSContext *context, jsval value, Entity **outEntity); extern JSClass gOOEntityJSClass; extern JSObject *gOOEntityJSPrototype; DEFINE_JS_OBJECT_GETTER(OOJSEntityGetEntity, &gOOEntityJSClass, gOOEntityJSPrototype, Entity) OOINLINE JSClass *JSEntityClass(void) { return &gOOEntityJSClass; } OOINLINE JSObject *JSEntityPrototype(void) { return gOOEntityJSPrototype; } /* EntityFromArgumentList() Construct a entity from an argument list containing a JS Entity object. The optional outConsumed argument can be used to find out how many parameters were used (currently, this will be 0 on failure, otherwise 1). On failure, it will return NO, annd the entity will be unaltered. If scriptClass and function are non-nil, a warning will be reported to the log. */ BOOL EntityFromArgumentList(JSContext *context, NSString *scriptClass, NSString *function, uintN argc, jsval *argv, Entity **outEntity, uintN *outConsumed); /* For scripting purposes, a JS entity object is a stale reference if its underlying ObjC object is nil, or if it refers to the player and the blockJSPlayerShipProps flag is in effect (i.e., the escape pod sequence is active). */ OOINLINE BOOL OOIsPlayerStale(void) { extern Entity *gOOJSPlayerIfStale; return gOOJSPlayerIfStale != nil; } OOINLINE BOOL OOIsStaleEntity(Entity *entity) { extern Entity *gOOJSPlayerIfStale; return entity == nil || (entity == gOOJSPlayerIfStale); } oolite-1.82/src/Core/Scripting/OOJSEntity.m000066400000000000000000000273211256642440500205220ustar00rootroot00000000000000/* OOJSEntity.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSEntity.h" #import "OOJSVector.h" #import "OOJSQuaternion.h" #import "OOJavaScriptEngine.h" #import "OOConstToJSString.h" #import "EntityOOJavaScriptExtensions.h" #import "OOJSCall.h" #import "OOJSPlayer.h" #import "PlayerEntity.h" #import "ShipEntity.h" JSObject *gOOEntityJSPrototype; static JSBool EntityGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool EntitySetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); #ifndef NDEBUG static JSBool EntityDumpState(JSContext *context, uintN argc, jsval *vp); #endif JSClass gOOEntityJSClass = { "Entity", JSCLASS_HAS_PRIVATE, JS_PropertyStub, // addProperty JS_PropertyStub, // delProperty EntityGetProperty, // getProperty EntitySetProperty, // setProperty JS_EnumerateStub, // enumerate JS_ResolveStub, // resolve JS_ConvertStub, // convert OOJSObjectWrapperFinalize,// finalize JSCLASS_NO_OPTIONAL_MEMBERS }; enum { // Property IDs kEntity_collisionRadius, // collision radius, double, read-only. kEntity_distanceTravelled, // distance travelled, double, read-only. kEntity_energy, // energy, double, read-write. kEntity_heading, // heading, vector, read-only (like orientation but ignoring twist angle) kEntity_mass, // mass, double, read-only kEntity_maxEnergy, // maxEnergy, double, read-only. kEntity_orientation, // orientation, quaternion, read/write kEntity_owner, // owner, Entity, read-only. (Parent ship for subentities, station for defense ships, launching ship for missiles etc) kEntity_position, // position in system space, Vector, read/write kEntity_scanClass, // scan class, string, read-only kEntity_spawnTime, // spawn time, double, read-only. kEntity_status, // entity status, string, read-only kEntity_isPlanet, // is planet, boolean, read-only. kEntity_isPlayer, // is player, boolean, read-only. kEntity_isShip, // is ship, boolean, read-only. kEntity_isStation, // is station, boolean, read-only. kEntity_isDock, // is dock, boolean, read-only. kEntity_isSubEntity, // is subentity, boolean, read-only. kEntity_isSun, // is sun, boolean, read-only. kEntity_isValid, // is not stale, boolean, read-only. kEntity_isInSpace, // is in space, boolean, read-only. kEntity_isVisible, // is within drawing distance, boolean, read-only. kEntity_isVisualEffect, // is visual effect, boolean, read-only. kEntity_isWormhole, // is visual effect, boolean, read-only. }; static JSPropertySpec sEntityProperties[] = { // JS name ID flags { "collisionRadius", kEntity_collisionRadius, OOJS_PROP_READONLY_CB }, { "distanceTravelled", kEntity_distanceTravelled, OOJS_PROP_READONLY_CB }, { "energy", kEntity_energy, OOJS_PROP_READWRITE_CB }, { "heading", kEntity_heading, OOJS_PROP_READONLY_CB }, { "mass", kEntity_mass, OOJS_PROP_READONLY_CB }, { "maxEnergy", kEntity_maxEnergy, OOJS_PROP_READWRITE_CB }, { "orientation", kEntity_orientation, OOJS_PROP_READWRITE_CB }, { "owner", kEntity_owner, OOJS_PROP_READONLY_CB }, { "position", kEntity_position, OOJS_PROP_READWRITE_CB }, { "scanClass", kEntity_scanClass, OOJS_PROP_READWRITE_CB }, { "spawnTime", kEntity_spawnTime, OOJS_PROP_READONLY_CB }, { "status", kEntity_status, OOJS_PROP_READONLY_CB }, { "isPlanet", kEntity_isPlanet, OOJS_PROP_READONLY_CB }, { "isPlayer", kEntity_isPlayer, OOJS_PROP_READONLY_CB }, { "isShip", kEntity_isShip, OOJS_PROP_READONLY_CB }, { "isDock", kEntity_isDock, OOJS_PROP_READONLY_CB }, { "isStation", kEntity_isStation, OOJS_PROP_READONLY_CB }, { "isSubEntity", kEntity_isSubEntity, OOJS_PROP_READONLY_CB }, { "isSun", kEntity_isSun, OOJS_PROP_READONLY_CB }, { "isValid", kEntity_isValid, OOJS_PROP_READONLY_CB }, { "isInSpace", kEntity_isInSpace, OOJS_PROP_READONLY_CB }, { "isVisible", kEntity_isVisible, OOJS_PROP_READONLY_CB }, { "isVisualEffect", kEntity_isVisualEffect, OOJS_PROP_READONLY_CB }, { "isWormhole", kEntity_isWormhole, OOJS_PROP_READONLY_CB }, { 0 } }; static JSFunctionSpec sEntityMethods[] = { // JS name Function min args { "toString", OOJSObjectWrapperToString, 0 }, #ifndef NDEBUG { "dumpState", EntityDumpState, 0 }, #endif { 0 } }; void InitOOJSEntity(JSContext *context, JSObject *global) { gOOEntityJSPrototype = JS_InitClass(context, global, NULL, &gOOEntityJSClass, OOJSUnconstructableConstruct, 0, sEntityProperties, sEntityMethods, NULL, NULL); OOJSRegisterObjectConverter(&gOOEntityJSClass, OOJSBasicPrivateObjectConverter); } BOOL JSValueToEntity(JSContext *context, jsval value, Entity **outEntity) { if (JSVAL_IS_OBJECT(value)) { return OOJSEntityGetEntity(context, JSVAL_TO_OBJECT(value), outEntity); } return NO; } BOOL EntityFromArgumentList(JSContext *context, NSString *scriptClass, NSString *function, uintN argc, jsval *argv, Entity **outEntity, uintN *outConsumed) { OOJS_PROFILE_ENTER // Sanity checks. if (outConsumed != NULL) *outConsumed = 0; if (EXPECT_NOT(argc == 0 || argv == NULL || outEntity == NULL)) { OOLogGenericParameterError(); return NO; } // Get value, if possible. if (EXPECT_NOT(!JSValueToEntity(context, argv[0], outEntity))) { // Failed; report bad parameters, if given a class and function. if (scriptClass != nil && function != nil) { OOJSReportWarning(context, @"%@.%@(): expected entity, got %@.", scriptClass, function, [NSString stringWithJavaScriptParameters:argv count:1 inContext:context]); return NO; } } // Success. if (outConsumed != NULL) *outConsumed = 1; return YES; OOJS_PROFILE_EXIT } static JSBool EntityGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) Entity *entity = nil; id result = nil; if (EXPECT_NOT(!OOJSEntityGetEntity(context, this, &entity))) return NO; if (OOIsStaleEntity(entity)) { if (JSID_TO_INT(propID) == kEntity_isValid) *value = JSVAL_FALSE; else { *value = JSVAL_VOID; } return YES; } switch (JSID_TO_INT(propID)) { case kEntity_collisionRadius: return JS_NewNumberValue(context, [entity collisionRadius], value); case kEntity_position: return HPVectorToJSValue(context, [entity position], value); case kEntity_orientation: return QuaternionToJSValue(context, [entity normalOrientation], value); case kEntity_heading: return VectorToJSValue(context, vector_forward_from_quaternion([entity normalOrientation]), value); case kEntity_status: *value = OOJSValueFromEntityStatus(context, [entity status]); return YES; case kEntity_scanClass: *value = OOJSValueFromScanClass(context, [entity scanClass]); return YES; case kEntity_mass: return JS_NewNumberValue(context, [entity mass], value); case kEntity_owner: result = [entity owner]; if (result == entity) result = nil; break; case kEntity_energy: return JS_NewNumberValue(context, [entity energy], value); case kEntity_maxEnergy: return JS_NewNumberValue(context, [entity maxEnergy], value); case kEntity_isValid: *value = [entity status] == STATUS_DEAD ? JSVAL_FALSE : JSVAL_TRUE; return YES; case kEntity_isInSpace: *value = OOJSValueFromBOOL([entity isInSpace]); return YES; case kEntity_isShip: *value = OOJSValueFromBOOL([entity isShip]); return YES; case kEntity_isStation: *value = OOJSValueFromBOOL([entity isStation]); return YES; case kEntity_isDock: *value = OOJSValueFromBOOL([entity isDock]); return YES; case kEntity_isSubEntity: *value = OOJSValueFromBOOL([entity isSubEntity]); return YES; case kEntity_isPlayer: *value = OOJSValueFromBOOL([entity isPlayer]); return YES; case kEntity_isPlanet: *value = OOJSValueFromBOOL([entity isPlanet]); return YES; case kEntity_isSun: *value = OOJSValueFromBOOL([entity isSun]); return YES; case kEntity_isVisible: *value = OOJSValueFromBOOL([entity isVisible]); return YES; case kEntity_isVisualEffect: *value = OOJSValueFromBOOL([entity isVisualEffect]); return YES; case kEntity_isWormhole: *value = OOJSValueFromBOOL([entity isWormhole]); return YES; case kEntity_distanceTravelled: return JS_NewNumberValue(context, [entity distanceTravelled], value); case kEntity_spawnTime: return JS_NewNumberValue(context, [entity spawnTime], value); default: OOJSReportBadPropertySelector(context, this, propID, sEntityProperties); } *value = OOJSValueFromNativeObject(context, result); return YES; OOJS_NATIVE_EXIT } static JSBool EntitySetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) Entity *entity = nil; double fValue; HPVector hpvValue; Quaternion qValue; if (EXPECT_NOT(!OOJSEntityGetEntity(context, this, &entity))) return NO; if (OOIsStaleEntity(entity)) return YES; switch (JSID_TO_INT(propID)) { case kEntity_position: if (JSValueToHPVector(context, *value, &hpvValue)) { [entity setPosition:hpvValue]; if ([entity isShip]) { [(ShipEntity *)entity resetExhaustPlumes]; [(ShipEntity *)entity forceAegisCheck]; } return YES; } break; case kEntity_orientation: if (JSValueToQuaternion(context, *value, &qValue)) { [entity setNormalOrientation:qValue]; return YES; } break; case kEntity_energy: if (JS_ValueToNumber(context, *value, &fValue)) { fValue = OOClamp_0_max_d(fValue, [entity maxEnergy]); [entity setEnergy:fValue]; return YES; } break; case kEntity_maxEnergy: if (JS_ValueToNumber(context, *value, &fValue)) { if (fValue <= 0.0) { OOJSReportError(context, @"entity.maxEnergy must be positive."); return NO; } [entity setMaxEnergy:fValue]; return YES; } break; case kEntity_scanClass: if ([entity isShip] && ![entity isPlayer]) { OOScanClass newClass = OOScanClassFromJSValue(context, *value); if (newClass == CLASS_NOT_SET || newClass == CLASS_NO_DRAW || newClass == CLASS_TARGET || newClass == CLASS_WORMHOLE || newClass == CLASS_PLAYER || newClass == CLASS_VISUAL_EFFECT) { OOJSReportError(context, @"entity.scanClass cannot be set to that value."); return NO; } [entity setScanClass:newClass]; return YES; } else { OOJSReportError(context, @"entity.scanClass is read-only except on NPC ships."); return NO; } default: OOJSReportBadPropertySelector(context, this, propID, sEntityProperties); return NO; } OOJSReportBadPropertyValue(context, this, propID, sEntityProperties, *value); return NO; OOJS_NATIVE_EXIT } #ifndef NDEBUG static JSBool EntityDumpState(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER Entity *thisEnt = nil; OOJSEntityGetEntity(context, OOJS_THIS, &thisEnt); [thisEnt dumpState]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } #endif oolite-1.82/src/Core/Scripting/OOJSEquipmentInfo.h000066400000000000000000000027731256642440500220300ustar00rootroot00000000000000/* OOJSEquipmentInfo.h JavaScript equipment introspection class, wrapper for OOEquipmentType. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #import "OOCocoa.h" @class OOEquipmentType; void InitOOJSEquipmentInfo(JSContext *context, JSObject *global); /* Given a jsval representing a string (equipment key) or a JS EquipmentInfo, return the corresponding EquipmentType or key. Note that JSValueToEquipmentKey() will not return arbitrary strings, only valid equipment keys. JSValueToEquipmentKeyRelaxed() will return any string that does not end with _DAMAGED. */ OOEquipmentType *JSValueToEquipmentType(JSContext *context, jsval value); NSString *JSValueToEquipmentKey(JSContext *context, jsval value); NSString *JSValueToEquipmentKeyRelaxed(JSContext *context, jsval value, BOOL *outExists); oolite-1.82/src/Core/Scripting/OOJSEquipmentInfo.m000066400000000000000000000357151256642440500220370ustar00rootroot00000000000000/* OOJSEquipmentInfo.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSEquipmentInfo.h" #import "OOJavaScriptEngine.h" #import "OOEquipmentType.h" #import "OOJSPlayer.h" #import "OODebugStandards.h" static JSObject *sEquipmentInfoPrototype; static JSBool EquipmentInfoGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool EquipmentInfoSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); static JSBool EquipmentInfoGetAllEqipment(JSContext *context, JSObject *this, jsid propID, jsval *value); // Methods static JSBool EquipmentInfoStaticInfoForKey(JSContext *context, uintN argc, jsval *vp); enum { // Property IDs kEquipmentInfo_canBeDamaged, kEquipmentInfo_canCarryMultiple, kEquipmentInfo_damageProbability, kEquipmentInfo_description, kEquipmentInfo_effectiveTechLevel, kEquipmentInfo_equipmentKey, kEquipmentInfo_fastAffinityDefensive, kEquipmentInfo_fastAffinityOffensive, kEquipmentInfo_incompatibleEquipment, kEquipmentInfo_isAvailableToAll, kEquipmentInfo_isAvailableToNPCs, kEquipmentInfo_isAvailableToPlayer, kEquipmentInfo_isExternalStore, // is missile or mine kEquipmentInfo_isPortableBetweenShips, kEquipmentInfo_isVisible, kEquipmentInfo_name, kEquipmentInfo_price, kEquipmentInfo_provides, kEquipmentInfo_requiredCargoSpace, kEquipmentInfo_requiresAnyEquipment, kEquipmentInfo_requiresCleanLegalRecord, kEquipmentInfo_requiresEmptyPylon, kEquipmentInfo_requiresEquipment, kEquipmentInfo_requiresFreePassengerBerth, kEquipmentInfo_requiresFullFuel, kEquipmentInfo_requiresMountedPylon, kEquipmentInfo_requiresNonCleanLegalRecord, kEquipmentInfo_requiresNonFullFuel, kEquipmentInfo_scriptInfo, // arbitrary data for scripts, dictionary, read-only kEquipmentInfo_scriptName, kEquipmentInfo_techLevel }; static JSPropertySpec sEquipmentInfoProperties[] = { // JS name ID flags { "canBeDamaged", kEquipmentInfo_canBeDamaged, OOJS_PROP_READONLY_CB }, { "canCarryMultiple", kEquipmentInfo_canCarryMultiple, OOJS_PROP_READONLY_CB }, { "damageProbability", kEquipmentInfo_damageProbability, OOJS_PROP_READONLY_CB }, { "description", kEquipmentInfo_description, OOJS_PROP_READONLY_CB }, { "effectiveTechLevel", kEquipmentInfo_effectiveTechLevel, OOJS_PROP_READWRITE_CB }, { "equipmentKey", kEquipmentInfo_equipmentKey, OOJS_PROP_READONLY_CB }, { "fastAffinityDefensive", kEquipmentInfo_fastAffinityDefensive, OOJS_PROP_READONLY_CB }, { "fastAffinityOffensive", kEquipmentInfo_fastAffinityOffensive, OOJS_PROP_READONLY_CB }, { "incompatibleEquipment", kEquipmentInfo_incompatibleEquipment, OOJS_PROP_READONLY_CB }, { "isAvailableToAll", kEquipmentInfo_isAvailableToAll, OOJS_PROP_READONLY_CB }, { "isAvailableToNPCs", kEquipmentInfo_isAvailableToNPCs, OOJS_PROP_READONLY_CB }, { "isAvailableToPlayer", kEquipmentInfo_isAvailableToPlayer, OOJS_PROP_READONLY_CB }, { "isExternalStore", kEquipmentInfo_isExternalStore, OOJS_PROP_READONLY_CB }, { "isPortableBetweenShips", kEquipmentInfo_isPortableBetweenShips, OOJS_PROP_READONLY_CB }, { "isVisible", kEquipmentInfo_isVisible, OOJS_PROP_READONLY_CB }, { "name", kEquipmentInfo_name, OOJS_PROP_READONLY_CB }, { "price", kEquipmentInfo_price, OOJS_PROP_READONLY_CB }, { "provides", kEquipmentInfo_provides, OOJS_PROP_READONLY_CB }, { "requiredCargoSpace", kEquipmentInfo_requiredCargoSpace, OOJS_PROP_READONLY_CB }, { "requiresAnyEquipment", kEquipmentInfo_requiresAnyEquipment, OOJS_PROP_READONLY_CB }, { "requiresCleanLegalRecord", kEquipmentInfo_requiresCleanLegalRecord, OOJS_PROP_READONLY_CB }, { "requiresEmptyPylon", kEquipmentInfo_requiresEmptyPylon, OOJS_PROP_READONLY_CB }, { "requiresEquipment", kEquipmentInfo_requiresEquipment, OOJS_PROP_READONLY_CB }, { "requiresFreePassengerBerth", kEquipmentInfo_requiresFreePassengerBerth, OOJS_PROP_READONLY_CB }, { "requiresFullFuel", kEquipmentInfo_requiresFullFuel, OOJS_PROP_READONLY_CB }, { "requiresMountedPylon", kEquipmentInfo_requiresMountedPylon, OOJS_PROP_READONLY_CB }, { "requiresNonCleanLegalRecord", kEquipmentInfo_requiresNonCleanLegalRecord, OOJS_PROP_READONLY_CB }, { "requiresNonFullFuel", kEquipmentInfo_requiresNonFullFuel, OOJS_PROP_READONLY_CB }, { "scriptInfo", kEquipmentInfo_scriptInfo, OOJS_PROP_READONLY_CB }, { "scriptName", kEquipmentInfo_scriptName, OOJS_PROP_READONLY_CB }, { "techLevel", kEquipmentInfo_techLevel, OOJS_PROP_READONLY_CB }, { 0 } }; static JSPropertySpec sEquipmentInfoStaticProperties[] = { { "allEquipment", 0, OOJS_PROP_READONLY_CB, EquipmentInfoGetAllEqipment }, { 0 } }; static JSFunctionSpec sEquipmentInfoMethods[] = { // JS name Function min args { "toString", OOJSObjectWrapperToString, 0 }, { 0 } }; static JSFunctionSpec sEquipmentInfoStaticMethods[] = { // JS name Function min args { "infoForKey", EquipmentInfoStaticInfoForKey, 0 }, { 0 } }; static JSClass sEquipmentInfoClass = { "EquipmentInfo", JSCLASS_HAS_PRIVATE, JS_PropertyStub, // addProperty JS_PropertyStub, // delProperty EquipmentInfoGetProperty, // getProperty EquipmentInfoSetProperty, // setProperty JS_EnumerateStub, // enumerate JS_ResolveStub, // resolve JS_ConvertStub, // convert OOJSObjectWrapperFinalize, // finalize JSCLASS_NO_OPTIONAL_MEMBERS }; DEFINE_JS_OBJECT_GETTER(JSEquipmentInfoGetEquipmentType, &sEquipmentInfoClass, sEquipmentInfoPrototype, OOEquipmentType); // *** Public *** void InitOOJSEquipmentInfo(JSContext *context, JSObject *global) { sEquipmentInfoPrototype = JS_InitClass(context, global, NULL, &sEquipmentInfoClass, OOJSUnconstructableConstruct, 0, sEquipmentInfoProperties, sEquipmentInfoMethods, sEquipmentInfoStaticProperties, sEquipmentInfoStaticMethods); OOJSRegisterObjectConverter(&sEquipmentInfoClass, OOJSBasicPrivateObjectConverter); } OOEquipmentType *JSValueToEquipmentType(JSContext *context, jsval value) { OOJS_PROFILE_ENTER if (JSVAL_IS_OBJECT(value)) { JSObject *object = JSVAL_TO_OBJECT(value); if (JS_InstanceOf(context, JSVAL_TO_OBJECT(value), &sEquipmentInfoClass, NULL)) { return (OOEquipmentType *)JS_GetPrivate(context, object); } } NSString *string = OOStringFromJSValue(context, value); if (string != nil) return [OOEquipmentType equipmentTypeWithIdentifier:string]; return nil; OOJS_PROFILE_EXIT } NSString *JSValueToEquipmentKey(JSContext *context, jsval value) { return [JSValueToEquipmentType(context, value) identifier]; } NSString *JSValueToEquipmentKeyRelaxed(JSContext *context, jsval value, BOOL *outExists) { OOJS_PROFILE_ENTER NSString *result = nil; BOOL exists = NO; id objValue = OOJSNativeObjectFromJSValue(context, value); if ([objValue isKindOfClass:[OOEquipmentType class]]) { result = [objValue identifier]; exists = YES; } else if ([objValue isKindOfClass:[NSString class]]) { /* To enforce deliberate backwards incompatibility, reject strings ending with _DAMAGED unless someone actually named an equip that way. */ exists = [OOEquipmentType equipmentTypeWithIdentifier:objValue] != nil; if (exists || ![objValue hasSuffix:@"_DAMAGED"]) { result = objValue; } } if (outExists != NULL) *outExists = exists; return result; OOJS_PROFILE_EXIT } // *** Implementation stuff *** static JSBool EquipmentInfoGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) OOEquipmentType *eqType = nil; id result = nil; if (EXPECT_NOT(!JSEquipmentInfoGetEquipmentType(context, this, &eqType))) return NO; switch (JSID_TO_INT(propID)) { case kEquipmentInfo_equipmentKey: result = [eqType identifier]; break; case kEquipmentInfo_name: result = [eqType name]; break; case kEquipmentInfo_canCarryMultiple: *value = OOJSValueFromBOOL([eqType canCarryMultiple]); return YES; case kEquipmentInfo_canBeDamaged: *value = OOJSValueFromBOOL([eqType canBeDamaged]); return YES; case kEquipmentInfo_description: result = [eqType descriptiveText]; break; case kEquipmentInfo_damageProbability: return JS_NewNumberValue(context, [eqType damageProbability], value); case kEquipmentInfo_fastAffinityDefensive: *value = OOJSValueFromBOOL([eqType fastAffinityDefensive]); return YES; case kEquipmentInfo_fastAffinityOffensive: *value = OOJSValueFromBOOL([eqType fastAffinityOffensive]); return YES; case kEquipmentInfo_techLevel: *value = INT_TO_JSVAL((int32_t)[eqType techLevel]); return YES; case kEquipmentInfo_effectiveTechLevel: *value = INT_TO_JSVAL((int32_t)[eqType effectiveTechLevel]); return YES; case kEquipmentInfo_price: return JS_NewNumberValue(context, [eqType price], value); case kEquipmentInfo_provides: result = [eqType providesForScripting]; break; case kEquipmentInfo_isAvailableToAll: *value = OOJSValueFromBOOL([eqType isAvailableToAll]); return YES; case kEquipmentInfo_isAvailableToNPCs: *value = OOJSValueFromBOOL([eqType isAvailableToNPCs]); return YES; case kEquipmentInfo_isAvailableToPlayer: *value = OOJSValueFromBOOL([eqType isAvailableToPlayer]); return YES; case kEquipmentInfo_requiresEmptyPylon: *value = OOJSValueFromBOOL([eqType requiresEmptyPylon]); return YES; case kEquipmentInfo_requiresMountedPylon: *value = OOJSValueFromBOOL([eqType requiresMountedPylon]); return YES; case kEquipmentInfo_requiresCleanLegalRecord: *value = OOJSValueFromBOOL([eqType requiresCleanLegalRecord]); return YES; case kEquipmentInfo_requiresNonCleanLegalRecord: *value = OOJSValueFromBOOL([eqType requiresNonCleanLegalRecord]); return YES; case kEquipmentInfo_requiresFreePassengerBerth: *value = OOJSValueFromBOOL([eqType requiresFreePassengerBerth]); return YES; case kEquipmentInfo_requiresFullFuel: *value = OOJSValueFromBOOL([eqType requiresFullFuel]); return YES; case kEquipmentInfo_requiresNonFullFuel: *value = OOJSValueFromBOOL([eqType requiresNonFullFuel]); return YES; case kEquipmentInfo_isExternalStore: *value = OOJSValueFromBOOL([eqType isMissileOrMine]); return YES; case kEquipmentInfo_isPortableBetweenShips: *value = OOJSValueFromBOOL([eqType isPortableBetweenShips]); return YES; case kEquipmentInfo_isVisible: *value = OOJSValueFromBOOL([eqType isVisible]); return YES; case kEquipmentInfo_requiredCargoSpace: *value = INT_TO_JSVAL((int32_t)[eqType requiredCargoSpace]); return YES; case kEquipmentInfo_requiresEquipment: result = [[eqType requiresEquipment] allObjects]; break; case kEquipmentInfo_requiresAnyEquipment: result = [[eqType requiresAnyEquipment] allObjects]; break; case kEquipmentInfo_incompatibleEquipment: result = [[eqType incompatibleEquipment] allObjects]; break; case kEquipmentInfo_scriptInfo: result = [eqType scriptInfo]; if (result == nil) result = [NSDictionary dictionary]; // empty rather than null break; case kEquipmentInfo_scriptName: result = [eqType scriptName]; if (result == nil) result = @""; break; default: OOJSReportBadPropertySelector(context, this, propID, sEquipmentInfoProperties); return NO; } *value = OOJSValueFromNativeObject(context, result); return YES; OOJS_NATIVE_EXIT } static JSBool EquipmentInfoSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) OOEquipmentType *eqType = nil; int32 iValue; if (EXPECT_NOT(!JSEquipmentInfoGetEquipmentType(context, this, &eqType))) return NO; switch (JSID_TO_INT(propID)) { case kEquipmentInfo_effectiveTechLevel: OOStandardsDeprecated([NSString stringWithFormat:@"TL99 for variable tech level is deprecated for %@",[eqType identifier]]); if (!OOEnforceStandards() && [eqType techLevel] == kOOVariableTechLevel) { if (JSVAL_IS_NULL(*value)) { // reset mission variable [OOPlayerForScripting() setMissionVariable:nil forKey:[@"mission_TL_FOR_" stringByAppendingString:[eqType identifier]]]; return YES; } if (JS_ValueToInt32(context, *value, &iValue)) { if (iValue < 0) iValue = 0; if (15 < iValue && iValue != kOOVariableTechLevel) iValue = 15; [OOPlayerForScripting() setMissionVariable:[NSString stringWithFormat:@"%u", iValue] forKey:[@"mission_TL_FOR_" stringByAppendingString:[eqType identifier]]]; return YES; } } else { OOJSReportWarning(context, @"Cannot modify effective tech level for %@, because its base tech level is not 99.", [eqType identifier]); return YES; } break; default: OOJSReportBadPropertySelector(context, this, propID, sEquipmentInfoProperties); return NO; } OOJSReportBadPropertyValue(context, this, propID, sEquipmentInfoProperties, *value); return NO; OOJS_NATIVE_EXIT } static JSBool EquipmentInfoGetAllEqipment(JSContext *context, JSObject *this, jsid propID, jsval *value) { OOJS_NATIVE_ENTER(context) *value = OOJSValueFromNativeObject(context, [OOEquipmentType allEquipmentTypes]); return YES; OOJS_NATIVE_EXIT } @implementation OOEquipmentType (OOJavaScriptExtensions) - (jsval) oo_jsValueInContext:(JSContext *)context { if (_jsSelf == NULL) { _jsSelf = JS_NewObject(context, &sEquipmentInfoClass, sEquipmentInfoPrototype, NULL); if (_jsSelf != NULL) { if (!JS_SetPrivate(context, _jsSelf, [self retain])) _jsSelf = NULL; } } return OBJECT_TO_JSVAL(_jsSelf); } - (NSString *) oo_jsClassName { return @"EquipmentInfo"; } - (void) oo_clearJSSelf:(JSObject *)selfVal { if (_jsSelf == selfVal) _jsSelf = NULL; } @end // *** Static methods *** // infoForKey(key : String): EquipmentInfo static JSBool EquipmentInfoStaticInfoForKey(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *key = nil; if (argc > 0) key = OOStringFromJSValue(context, OOJS_ARGV[0]); if (key == nil) { OOJSReportBadArguments(context, @"EquipmentInfo", @"infoForKey", MIN(argc, 1U), OOJS_ARGV, nil, @"string"); return NO; } OOJS_RETURN_OBJECT([OOEquipmentType equipmentTypeWithIdentifier:key]); OOJS_NATIVE_EXIT } oolite-1.82/src/Core/Scripting/OOJSExhaustPlume.h000066400000000000000000000022321256642440500216570ustar00rootroot00000000000000/* OOJSExhaustPlume.h JavaScript proxy for OOExhaustPlumeEntity. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #include #import "OOExhaustPlumeEntity.h" void InitOOJSExhaustPlume(JSContext *context, JSObject *global); @interface OOExhaustPlumeEntity (OOJavaScriptExtensions) - (void)getJSClass:(JSClass **)outClass andPrototype:(JSObject **)outPrototype; - (NSString *) oo_jsClassName; - (BOOL) isVisibleToScripts; @end oolite-1.82/src/Core/Scripting/OOJSExhaustPlume.m000066400000000000000000000125301256642440500216660ustar00rootroot00000000000000/* OOJSExhaustPlume.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOExhaustPlumeEntity.h" #import "OOJSExhaustPlume.h" #import "OOJSEntity.h" #import "OOJSVector.h" #import "OOJavaScriptEngine.h" #import "EntityOOJavaScriptExtensions.h" #import "ShipEntity.h" static JSObject *sExhaustPlumePrototype; static BOOL JSExhaustPlumeGetExhaustPlumeEntity(JSContext *context, JSObject *jsobj, OOExhaustPlumeEntity **outEntity); static JSBool ExhaustPlumeGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool ExhaustPlumeSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); static JSBool ExhaustPlumeRemove(JSContext *context, uintN argc, jsval *vp); static JSClass sExhaustPlumeClass = { "ExhaustPlume", JSCLASS_HAS_PRIVATE, JS_PropertyStub, // addProperty JS_PropertyStub, // delProperty ExhaustPlumeGetProperty, // getProperty ExhaustPlumeSetProperty, // setProperty JS_EnumerateStub, // enumerate JS_ResolveStub, // resolve JS_ConvertStub, // convert OOJSObjectWrapperFinalize,// finalize JSCLASS_NO_OPTIONAL_MEMBERS }; enum { // Property IDs kExhaustPlume_size }; static JSPropertySpec sExhaustPlumeProperties[] = { // JS name ID flags { "size", kExhaustPlume_size, OOJS_PROP_READWRITE_CB }, { 0 } }; static JSFunctionSpec sExhaustPlumeMethods[] = { // JS name Function min args { "remove", ExhaustPlumeRemove, 0 }, { 0 } }; void InitOOJSExhaustPlume(JSContext *context, JSObject *global) { sExhaustPlumePrototype = JS_InitClass(context, global, JSEntityPrototype(), &sExhaustPlumeClass, OOJSUnconstructableConstruct, 0, sExhaustPlumeProperties, sExhaustPlumeMethods, NULL, NULL); OOJSRegisterObjectConverter(&sExhaustPlumeClass, OOJSBasicPrivateObjectConverter); OOJSRegisterSubclass(&sExhaustPlumeClass, JSEntityClass()); } static BOOL JSExhaustPlumeGetExhaustPlumeEntity(JSContext *context, JSObject *jsobj, OOExhaustPlumeEntity **outEntity) { OOJS_PROFILE_ENTER BOOL result; Entity *entity = nil; if (outEntity == NULL) return NO; *outEntity = nil; result = OOJSEntityGetEntity(context, jsobj, &entity); if (!result) return NO; if (![entity isKindOfClass:[OOExhaustPlumeEntity class]]) return NO; *outEntity = (OOExhaustPlumeEntity *)entity; return YES; OOJS_PROFILE_EXIT } @implementation OOExhaustPlumeEntity (OOJavaScriptExtensions) - (void)getJSClass:(JSClass **)outClass andPrototype:(JSObject **)outPrototype { *outClass = &sExhaustPlumeClass; *outPrototype = sExhaustPlumePrototype; } - (NSString *) oo_jsClassName { return @"ExhaustPlume"; } - (BOOL) isVisibleToScripts { return YES; } @end static JSBool ExhaustPlumeGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) OOExhaustPlumeEntity *entity = nil; id result = nil; if (!JSExhaustPlumeGetExhaustPlumeEntity(context, this, &entity)) return NO; if (entity == nil) { *value = JSVAL_VOID; return YES; } switch (JSID_TO_INT(propID)) { case kExhaustPlume_size: return VectorToJSValue(context, [entity scale], value); default: OOJSReportBadPropertySelector(context, this, propID, sExhaustPlumeProperties); return NO; } *value = OOJSValueFromNativeObject(context, result); return YES; OOJS_NATIVE_EXIT } static JSBool ExhaustPlumeSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) OOExhaustPlumeEntity *entity = nil; Vector vValue; if (!JSExhaustPlumeGetExhaustPlumeEntity(context, this, &entity)) return NO; if (entity == nil) return YES; switch (JSID_TO_INT(propID)) { case kExhaustPlume_size: if (JSValueToVector(context, *value, &vValue)) { [entity setScale:vValue]; return YES; } break; default: OOJSReportBadPropertySelector(context, this, propID, sExhaustPlumeProperties); return NO; } OOJSReportBadPropertyValue(context, this, propID, sExhaustPlumeProperties, *value); return NO; OOJS_NATIVE_EXIT } // *** Methods *** #define GET_THIS_EXHAUSTPLUME(THISENT) do { \ if (EXPECT_NOT(!JSExhaustPlumeGetExhaustPlumeEntity(context, OOJS_THIS, &THISENT))) return NO; /* Exception */ \ if (OOIsStaleEntity(THISENT)) OOJS_RETURN_VOID; \ } while (0) static JSBool ExhaustPlumeRemove(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOExhaustPlumeEntity *thisEnt = nil; GET_THIS_EXHAUSTPLUME(thisEnt); ShipEntity *parent = [thisEnt owner]; [parent removeExhaust:thisEnt]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } oolite-1.82/src/Core/Scripting/OOJSFlasher.h000066400000000000000000000022011256642440500206130ustar00rootroot00000000000000/* OOJSFlasher.h JavaScript proxy for OOFlasherEntity. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #include #import "OOFlasherEntity.h" void InitOOJSFlasher(JSContext *context, JSObject *global); @interface OOFlasherEntity (OOJavaScriptExtensions) - (void)getJSClass:(JSClass **)outClass andPrototype:(JSObject **)outPrototype; - (NSString *) oo_jsClassName; - (BOOL) isVisibleToScripts; @end oolite-1.82/src/Core/Scripting/OOJSFlasher.m000066400000000000000000000160571256642440500206360ustar00rootroot00000000000000/* OOJSFlasher.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOFlasherEntity.h" #import "OOJSFlasher.h" #import "OOJSEntity.h" #import "OOJSVector.h" #import "OOJavaScriptEngine.h" #import "EntityOOJavaScriptExtensions.h" #import "ShipEntity.h" #import "OOVisualEffectEntity.h" static JSObject *sFlasherPrototype; static BOOL JSFlasherGetFlasherEntity(JSContext *context, JSObject *jsobj, OOFlasherEntity **outEntity); static JSBool FlasherGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool FlasherSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); static JSBool FlasherRemove(JSContext *context, uintN argc, jsval *vp); static JSClass sFlasherClass = { "Flasher", JSCLASS_HAS_PRIVATE, JS_PropertyStub, // addProperty JS_PropertyStub, // delProperty FlasherGetProperty, // getProperty FlasherSetProperty, // setProperty JS_EnumerateStub, // enumerate JS_ResolveStub, // resolve JS_ConvertStub, // convert OOJSObjectWrapperFinalize,// finalize JSCLASS_NO_OPTIONAL_MEMBERS }; enum { // Property IDs kFlasher_active, kFlasher_color, kFlasher_fraction, kFlasher_frequency, kFlasher_phase, kFlasher_size }; static JSPropertySpec sFlasherProperties[] = { // JS name ID flags { "active", kFlasher_active, OOJS_PROP_READWRITE_CB }, { "color", kFlasher_color, OOJS_PROP_READWRITE_CB }, { "fraction", kFlasher_fraction, OOJS_PROP_READWRITE_CB }, { "frequency", kFlasher_frequency, OOJS_PROP_READWRITE_CB }, { "phase", kFlasher_phase, OOJS_PROP_READWRITE_CB }, { "size", kFlasher_size, OOJS_PROP_READWRITE_CB }, { 0 } }; static JSFunctionSpec sFlasherMethods[] = { // JS name Function min args { "remove", FlasherRemove, 0 }, { 0 } }; void InitOOJSFlasher(JSContext *context, JSObject *global) { sFlasherPrototype = JS_InitClass(context, global, JSEntityPrototype(), &sFlasherClass, OOJSUnconstructableConstruct, 0, sFlasherProperties, sFlasherMethods, NULL, NULL); OOJSRegisterObjectConverter(&sFlasherClass, OOJSBasicPrivateObjectConverter); OOJSRegisterSubclass(&sFlasherClass, JSEntityClass()); } static BOOL JSFlasherGetFlasherEntity(JSContext *context, JSObject *jsobj, OOFlasherEntity **outEntity) { OOJS_PROFILE_ENTER BOOL result; Entity *entity = nil; if (outEntity == NULL) return NO; *outEntity = nil; result = OOJSEntityGetEntity(context, jsobj, &entity); if (!result) return NO; if (![entity isKindOfClass:[OOFlasherEntity class]]) return NO; *outEntity = (OOFlasherEntity *)entity; return YES; OOJS_PROFILE_EXIT } @implementation OOFlasherEntity (OOJavaScriptExtensions) - (void)getJSClass:(JSClass **)outClass andPrototype:(JSObject **)outPrototype { *outClass = &sFlasherClass; *outPrototype = sFlasherPrototype; } - (NSString *) oo_jsClassName { return @"Flasher"; } - (BOOL) isVisibleToScripts { return YES; } @end static JSBool FlasherGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) OOFlasherEntity *entity = nil; id result = nil; if (!JSFlasherGetFlasherEntity(context, this, &entity)) return NO; if (entity == nil) { *value = JSVAL_VOID; return YES; } switch (JSID_TO_INT(propID)) { case kFlasher_active: *value = OOJSValueFromBOOL([entity isActive]); return YES; case kFlasher_color: result = [[entity color] normalizedArray]; break; case kFlasher_frequency: return JS_NewNumberValue(context, [entity frequency], value); case kFlasher_fraction: return JS_NewNumberValue(context, [entity fraction], value); case kFlasher_phase: return JS_NewNumberValue(context, [entity phase], value); case kFlasher_size: return JS_NewNumberValue(context, [entity diameter], value); default: OOJSReportBadPropertySelector(context, this, propID, sFlasherProperties); return NO; } *value = OOJSValueFromNativeObject(context, result); return YES; OOJS_NATIVE_EXIT } static JSBool FlasherSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) OOFlasherEntity *entity = nil; jsdouble fValue; JSBool bValue; OOColor *colorForScript = nil; if (!JSFlasherGetFlasherEntity(context, this, &entity)) return NO; if (entity == nil) return YES; switch (JSID_TO_INT(propID)) { case kFlasher_active: if (JS_ValueToBoolean(context, *value, &bValue)) { [entity setActive:bValue]; return YES; } break; case kFlasher_color: colorForScript = [OOColor colorWithDescription:OOJSNativeObjectFromJSValue(context, *value)]; if (colorForScript != nil || JSVAL_IS_NULL(*value)) { [entity setColor:colorForScript]; return YES; } break; case kFlasher_frequency: if (JS_ValueToNumber(context, *value, &fValue)) { if (fValue >= 0.0) { [entity setFrequency:fValue]; return YES; } } break; case kFlasher_fraction: if (JS_ValueToNumber(context, *value, &fValue)) { if (fValue > 0.0 && fValue <= 1.0) { [entity setFraction:fValue]; return YES; } } break; case kFlasher_phase: if (JS_ValueToNumber(context, *value, &fValue)) { [entity setPhase:fValue]; return YES; } break; case kFlasher_size: if (JS_ValueToNumber(context, *value, &fValue)) { if (fValue > 0.0) { [entity setDiameter:fValue]; return YES; } } break; default: OOJSReportBadPropertySelector(context, this, propID, sFlasherProperties); return NO; } OOJSReportBadPropertyValue(context, this, propID, sFlasherProperties, *value); return NO; OOJS_NATIVE_EXIT } // *** Methods *** #define GET_THIS_FLASHER(THISENT) do { \ if (EXPECT_NOT(!JSFlasherGetFlasherEntity(context, OOJS_THIS, &THISENT))) return NO; /* Exception */ \ if (OOIsStaleEntity(THISENT)) OOJS_RETURN_VOID; \ } while (0) static JSBool FlasherRemove(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOFlasherEntity *thisEnt = nil; GET_THIS_FLASHER(thisEnt); Entity *parent = [thisEnt owner]; if ([parent isShip]) { [(ShipEntity *)parent removeFlasher:thisEnt]; } else { [(OOVisualEffectEntity *)parent removeSubEntity:thisEnt]; } OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } oolite-1.82/src/Core/Scripting/OOJSFont.h000066400000000000000000000025271256642440500201500ustar00rootroot00000000000000/* OOJSFont.h JavaScript interface for introspecting fonts. Well, the font. Designed so that expanding to multiple font support would be reasonably elegant from the JS side of the fence. Copyright (C) 2011-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import #include void InitOOJSFont(JSContext *context, JSObject *global); oolite-1.82/src/Core/Scripting/OOJSFont.m000066400000000000000000000036741256642440500201610ustar00rootroot00000000000000/* OOJSFont.m Copyright (C) 2011-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOJSFont.h" #import "OOJavaScriptEngine.h" #import "HeadUpDisplay.h" static JSBool FontMeasureString(JSContext *context, uintN argc, jsval *vp); // MARK: Public void InitOOJSFont(JSContext *context, JSObject *global) { JSObject *fontObject = JS_DefineObject(context, global, "defaultFont", NULL, NULL, OOJS_PROP_READONLY); JS_DefineFunction(context, fontObject, "measureString", FontMeasureString, 1, OOJS_METHOD_READONLY); } // MARK: Methods static JSBool FontMeasureString(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) if (EXPECT_NOT(argc < 1) || JSVAL_IS_VOID(OOJS_ARGV[0])) { jsval undefined = JSVAL_VOID; OOJSReportBadArguments(context, nil, @"defaultFont.measureString", MIN(argc, 1U), &undefined, nil, @"string"); return NO; } OOJS_RETURN_DOUBLE(OOStringWidthInEm(OOStringFromJSValue(context, OOJS_ARGV[0]))); OOJS_NATIVE_EXIT } oolite-1.82/src/Core/Scripting/OOJSFrameCallbacks.h000066400000000000000000000025021256642440500220650ustar00rootroot00000000000000/* OOJSFrameCallbacks.h Support for JavaScript callbacks to be invoked on every frame. Copyright (C) 2011-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOJavaScriptEngine.h" void InitOOJSFrameCallbacks(JSContext *context, JSObject *global); void OOJSFrameCallbacksInvoke(OOTimeDelta delta); void OOJSFrameCallbacksRemoveAll(void); oolite-1.82/src/Core/Scripting/OOJSFrameCallbacks.m000066400000000000000000000332631256642440500221020ustar00rootroot00000000000000/* OOJSFrameCallbacks.m Copyright (C) 2011-2013 Jens Ayton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import "OOJSFrameCallbacks.h" #import "OOJSEngineTimeManagement.h" #import "OOCollectionExtractors.h" /* By default, tracking IDs are scrambled to discourage people from trying to be clever or making assumptions about them. If DEBUG_FCB_SIMPLE_TRACKING_IDS is non-zero, tracking IDs starting from 1 and rising monotonously are used instead. Additionally, the next ID is reset to 1 when all frame callbacks are removed. */ #ifndef DEBUG_FCB_SIMPLE_TRACKING_IDS #define DEBUG_FCB_SIMPLE_TRACKING_IDS 0 #endif #ifndef DEBUG_FCB_VERBOSE_LOGGING #define DEBUG_FCB_VERBOSE_LOGGING 0 #endif #if defined (NDEBUG) && DEBUG_FCB_SIMPLE_TRACKING_IDS #error Deployment builds may not be built with DEBUG_FCB_SIMPLE_TRACKING_IDS. #endif #if DEBUG_FCB_VERBOSE_LOGGING #define FCBLog OOLog #define FCBLogIndentIf OOLogIndentIf #define FCBLogOutdentIf OOLogOutdentIf #else #define FCBLog(...) do {} while (0) #define FCBLogIndentIf(key) do {} while (0) #define FCBLogOutdentIf(key) do {} while (0) #endif enum { kMinCount = 16, #if DEBUG_FCB_SIMPLE_TRACKING_IDS kIDScrambleMask = 0, kIDIncrement = 1 #else kIDScrambleMask = 0x2315EB16, // Just a random number. kIDIncrement = 992699 // A large prime number, to produce a non-obvious sequence which still uses all 2^32 values. #endif }; typedef struct { jsval callback; uint32 trackingID; uint32 _padding; } CallbackEntry; static CallbackEntry *sCallbacks; static NSUInteger sCount; // Number of slots in use. static NSUInteger sSpace; // Number of slots allocated. static NSUInteger sHighWaterMark; // Number of slots which are GC roots. static NSMutableArray *sDeferredOps; // Deferred adds/removes while running. static uint32 sNextID; static BOOL sRunning; // Methods static JSBool GlobalAddFrameCallback(JSContext *context, uintN argc, jsval *vp); static JSBool GlobalRemoveFrameCallback(JSContext *context, uintN argc, jsval *vp); static JSBool GlobalIsValidFrameCallback(JSContext *context, uintN argc, jsval *vp); // Internals static BOOL AddCallback(JSContext *context, jsval callback, uint32 trackingID, NSString **errorString); static BOOL GrowCallbackList(JSContext *context, NSString **errorString); static BOOL GetIndexForTrackingID(uint32 trackingID, NSUInteger *outIndex); static BOOL RemoveCallbackWithTrackingID(JSContext *context, uint32 trackingID); static void RemoveCallbackAtIndex(JSContext *context, NSUInteger index); static void QueueDeferredOperation(NSString *opType, uint32 trackingID, OOJSValue *value); static void RunDeferredOperations(JSContext *context); // MARK: Public void InitOOJSFrameCallbacks(JSContext *context, JSObject *global) { JS_DefineFunction(context, global, "addFrameCallback", GlobalAddFrameCallback, 1, OOJS_METHOD_READONLY); JS_DefineFunction(context, global, "removeFrameCallback", GlobalRemoveFrameCallback, 1, OOJS_METHOD_READONLY); JS_DefineFunction(context, global, "isValidFrameCallback", GlobalIsValidFrameCallback, 1, OOJS_METHOD_READONLY); #if DEBUG_FCB_SIMPLE_TRACKING_IDS sNextID = 1; #else // Set randomish initial ID to catch bad habits. sNextID = [[NSDate date] timeIntervalSinceReferenceDate]; #endif } void OOJSFrameCallbacksInvoke(OOTimeDelta inDeltaT) { NSCAssert1(!sRunning, @"%s cannot be called while frame callbacks are running.", __PRETTY_FUNCTION__); if (sCount != 0) { const OOTimeDelta delta = inDeltaT * [UNIVERSE timeAccelerationFactor]; JSContext *context = OOJSAcquireContext(); jsval deltaVal, result; NSUInteger i; if (EXPECT(JS_NewNumberValue(context, delta, &deltaVal))) { // Block mutations. sRunning = YES; /* The watchdog timer only fires once per second in deployment builds, but in testrelease builds at least we can keep them on a short leash. */ OOJSStartTimeLimiterWithTimeLimit(0.1); for (i = 0; i < sCount; i++) { // TODO: remove out of scope callbacks - post MNSR! JS_CallFunctionValue(context, NULL, sCallbacks[i].callback, 1, &deltaVal, &result); JS_ReportPendingException(context); } OOJSStopTimeLimiter(); sRunning = NO; if (EXPECT_NOT(sDeferredOps != NULL)) { RunDeferredOperations(context); DESTROY(sDeferredOps); } } OOJSRelinquishContext(context); } } void OOJSFrameCallbacksRemoveAll(void) { NSCAssert1(!sRunning, @"%s cannot be called while frame callbacks are running.", __PRETTY_FUNCTION__); if (sCount != 0) { JSContext *context = OOJSAcquireContext(); while (sCount != 0) RemoveCallbackAtIndex(context, sCount - 1); OOJSRelinquishContext(context); } } // MARK: Methods // addFrameCallback(callback : Function) : Number static JSBool GlobalAddFrameCallback(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) // Get callback argument and verify that it's a function. jsval callback = OOJS_ARGV[0]; if (EXPECT_NOT(argc < 1 || !OOJSValueIsFunction(context, callback))) { OOJSReportBadArguments(context, nil, @"addFrameCallback", MIN(argc, 1U), OOJS_ARGV, nil, @"function"); return NO; } // Assign a tracking ID. uint32 trackingID = sNextID ^ kIDScrambleMask; sNextID += kIDIncrement; if (EXPECT(!sRunning)) { // Add to list immediately. NSString *errorString = nil; if (EXPECT_NOT(!AddCallback(context, callback, trackingID, &errorString))) { OOJSReportError(context, @"%@", errorString); return NO; } } else { // Defer mutations during callback invocation. FCBLog(@"script.frameCallback.debug.add.deferred", @"Deferring addition of frame callback with tracking ID %u.", trackingID); QueueDeferredOperation(@"add", trackingID, [OOJSValue valueWithJSValue:callback inContext:context]); } OOJS_RETURN_INT(trackingID); OOJS_NATIVE_EXIT } // removeFrameCallback(trackingID : Number) static JSBool GlobalRemoveFrameCallback(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) // Get tracking ID argument. uint32 trackingID; if (EXPECT_NOT(argc < 1 || !JS_ValueToECMAUint32(context, OOJS_ARGV[0], &trackingID))) { OOJSReportBadArguments(context, nil, @"removeFrameCallback", MIN(argc, 1U), OOJS_ARGV, nil, @"frame callback tracking ID"); return NO; } if (EXPECT(!sRunning)) { // Remove it. if (EXPECT_NOT(!RemoveCallbackWithTrackingID(context, trackingID))) { OOJSReportWarning(context, @"removeFrameCallback(): invalid tracking ID."); } } else { // Defer mutations during callback invocation. FCBLog(@"script.frameCallback.debug.remove.deferred", @"Deferring removal of frame callback with tracking ID %u.", trackingID); QueueDeferredOperation(@"remove", trackingID, nil); } OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // isValidFrameCallback(trackingID : Number) static JSBool GlobalIsValidFrameCallback(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) if (EXPECT_NOT(argc < 1)) { OOJSReportBadArguments(context, nil, @"isValidFrameCallback", 0, OOJS_ARGV, nil, @"frame callback tracking ID"); return NO; } // Get tracking ID argument. uint32 trackingID; if (EXPECT_NOT(!JS_ValueToECMAUint32(context, OOJS_ARGV[0], &trackingID))) { OOJS_RETURN_BOOL(NO); } NSUInteger index; OOJS_RETURN_BOOL(GetIndexForTrackingID(trackingID, &index)); OOJS_NATIVE_EXIT } // MARK: Internals static BOOL AddCallback(JSContext *context, jsval callback, uint32 trackingID, NSString **errorString) { NSCParameterAssert(context != NULL && JS_IsInRequest(context)); NSCParameterAssert(errorString != NULL); NSCAssert1(!sRunning, @"%s cannot be called while frame callbacks are running.", __PRETTY_FUNCTION__); if (EXPECT_NOT(sCount == sSpace)) { if (!GrowCallbackList(context, errorString)) return NO; } FCBLog(@"script.frameCallback.debug.add", @"Adding frame callback with tracking ID %u.", trackingID); sCallbacks[sCount].callback = callback; if (sCount >= sHighWaterMark) { // If we haven't used this slot before, root it. if (EXPECT_NOT(!OOJSAddGCValueRoot(context, &sCallbacks[sCount].callback, "frame callback"))) { *errorString = @"Failed to add GC root for frame callback."; return NO; } sHighWaterMark = sCount + 1; } sCallbacks[sCount].trackingID = trackingID; sCount++; return YES; } static BOOL GrowCallbackList(JSContext *context, NSString **errorString) { NSCParameterAssert(context != NULL && JS_IsInRequest(context)); NSCParameterAssert(errorString != NULL); NSUInteger newSpace = MAX(sSpace * 2, (NSUInteger)kMinCount); CallbackEntry *newCallbacks = calloc(sizeof (CallbackEntry), newSpace); if (newCallbacks == NULL) return NO; CallbackEntry *oldCallbacks = sCallbacks; // Root and copy occupied slots. NSUInteger newHighWaterMark = sCount; NSUInteger i; for (i = 0; i < newHighWaterMark; i++) { if (EXPECT_NOT(!OOJSAddGCValueRoot(context, &newCallbacks[i].callback, "frame callback"))) { // If we can't root them all, we fail; unroot all entries to date, free the buffer and return NO. NSUInteger j; for (j = 0; j < i; j++) { JS_RemoveValueRoot(context, &newCallbacks[j].callback); } free(newCallbacks); *errorString = @"Failed to add GC root for frame callback."; return NO; } newCallbacks[i] = oldCallbacks[i]; } // Unroot old array's slots. for (i = 0; i < sHighWaterMark; i++) { JS_RemoveValueRoot(context, &oldCallbacks[i].callback); } // We only rooted the occupied slots, so reset high water mark. sHighWaterMark = newHighWaterMark; // Replace array. sCallbacks = newCallbacks; free(oldCallbacks); sSpace = newSpace; return YES; } static BOOL GetIndexForTrackingID(uint32 trackingID, NSUInteger *outIndex) { NSCParameterAssert(outIndex != NULL); /* It is assumed that few frame callbacks will be active at once, so a linear search is reasonable. If they become unexpectedly popular, we can switch to a sorted list or a separate lookup table without changing the API. */ NSUInteger i; for (i = 0; i < sCount; i++) { if (sCallbacks[i].trackingID == trackingID) { *outIndex = i; return YES; } } return NO; } static BOOL RemoveCallbackWithTrackingID(JSContext *context, uint32 trackingID) { NSCParameterAssert(context != NULL && JS_IsInRequest(context)); NSCAssert1(!sRunning, @"%s cannot be called while frame callbacks are running.", __PRETTY_FUNCTION__); NSUInteger index = 0; if (GetIndexForTrackingID(trackingID, &index)) { RemoveCallbackAtIndex(context, index); return YES; } return NO; } static void RemoveCallbackAtIndex(JSContext *context, NSUInteger index) { NSCParameterAssert(context != NULL && JS_IsInRequest(context)); NSCParameterAssert(index < sCount && sCallbacks != NULL); NSCAssert1(!sRunning, @"%s cannot be called while frame callbacks are running.", __PRETTY_FUNCTION__); FCBLog(@"script.frameCallback.debug.remove", @"Removing frame callback with tracking ID %u.", sCallbacks[index].trackingID); // Overwrite entry to be removed with last entry, and decrement count. sCount--; sCallbacks[index] = sCallbacks[sCount]; sCallbacks[sCount].callback = JSVAL_NULL; #if DEBUG_FCB_SIMPLE_TRACKING_IDS if (sCount == 0) { OOLog(@"script.frameCallback.debug.reset", @"All frame callbacks removed, resetting next ID to 1."); sNextID = 1; } #endif } static void QueueDeferredOperation(NSString *opType, uint32 trackingID, OOJSValue *value) { NSCAssert1(sRunning, @"%s can only be called while frame callbacks are running.", __PRETTY_FUNCTION__); if (sDeferredOps == nil) sDeferredOps = [[NSMutableArray alloc] init]; [sDeferredOps addObject:[NSDictionary dictionaryWithObjectsAndKeys: opType, @"operation", [NSNumber numberWithInt:trackingID], @"trackingID", value, @"value", nil]]; } static void RunDeferredOperations(JSContext *context) { NSDictionary *operation = nil; NSEnumerator *operationEnum = nil; FCBLog(@"script.frameCallback.debug.run-deferred", @"Running %lu deferred frame callback operations.", (long)[sDeferredOps count]); FCBLogIndentIf(@"script.frameCallback.debug.run-deferred"); for (operationEnum = [sDeferredOps objectEnumerator]; (operation = [operationEnum nextObject]); ) { NSString *opType = [operation objectForKey:@"operation"]; uint32 trackingID = [operation oo_intForKey:@"trackingID"]; if ([opType isEqualToString:@"add"]) { OOJSValue *callbackObj = [operation objectForKey:@"value"]; NSString *errorString = nil; if (!AddCallback(context, OOJSValueFromNativeObject(context, callbackObj), trackingID, &errorString)) { OOLogWARN(@"script.frameCallback.deferredAdd.failed", @"Deferred frame callback insertion failed: %@", errorString); } } else if ([opType isEqualToString:@"remove"]) { RemoveCallbackWithTrackingID(context, trackingID); } } FCBLogOutdentIf(@"script.frameCallback.debug.run-deferred"); } oolite-1.82/src/Core/Scripting/OOJSFunction.h000066400000000000000000000041221256642440500210200ustar00rootroot00000000000000/* OOJSFunction.h Object encapsulating a runnable JavaScript function. JavaScript support for Oolite Copyright (C) 2007-2013 David Taylor and Jens Ayton. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #include @interface OOJSFunction: NSObject { @private JSFunction *_function; NSString *_name; } - (id) initWithFunction:(JSFunction *)function context:(JSContext *)context; - (id) initWithName:(NSString *)name scope:(JSObject *)scope // may be NULL, in which case global object is used. code:(NSString *)code // full JS code for function, including function declaration. argumentCount:(NSUInteger)argCount argumentNames:(const char **)argNames fileName:(NSString *)fileName lineNumber:(NSUInteger)lineNumber context:(JSContext *)context; // may be NULL. If not null, must be in a request. - (NSString *) name; - (JSFunction *) function; - (jsval) functionValue; // Raw evaluation. Context may not be NULL and must be in a request. - (BOOL) evaluateWithContext:(JSContext *)context scope:(JSObject *)jsThis argc:(uintN)argc argv:(jsval *)argv result:(jsval *)result; // Object-wrapper evaluation. - (id) evaluateWithContext:(JSContext *)context scope:(id)jsThis arguments:(NSArray *)arguments; // As above, but converts result to a boolean. - (BOOL) evaluatePredicateWithContext:(JSContext *)context scope:(id)jsThis arguments:(NSArray *)arguments; @end oolite-1.82/src/Core/Scripting/OOJSFunction.m000066400000000000000000000131621256642440500210310ustar00rootroot00000000000000/* OOJSFunction.m JavaScript support for Oolite Copyright (C) 2007-2013 David Taylor and Jens Ayton. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSFunction.h" #import "OOJSScript.h" #import "OOJSEngineTimeManagement.h" @implementation OOJSFunction - (id) initWithFunction:(JSFunction *)function context:(JSContext *)context { NSParameterAssert(context != NULL); if (function == NULL) { [self release]; return nil; } if ((self = [super init])) { _function = function; OOJSAddGCObjectRoot(context, (JSObject **)&_function, "OOJSFunction._function"); _name = [OOStringFromJSString(context, JS_GetFunctionId(function)) retain]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deleteJSValue) name:kOOJavaScriptEngineWillResetNotification object:[OOJavaScriptEngine sharedEngine]]; } return self; } - (id) initWithName:(NSString *)name scope:(JSObject *)scope code:(NSString *)code argumentCount:(NSUInteger)argCount argumentNames:(const char **)argNames fileName:(NSString *)fileName lineNumber:(NSUInteger)lineNumber context:(JSContext *)context { BOOL OK = YES; BOOL releaseContext = NO; jschar *buffer = NULL; size_t length = 0; JSFunction *function; if (context == NULL) { context = OOJSAcquireContext(); releaseContext = YES; } if (scope == NULL) scope = [[OOJavaScriptEngine sharedEngine] globalObject]; if (code == nil || (argCount > 0 && argNames == NULL)) OK = NO; if (OK) { // jschar and unichar are both defined to be 16-bit elements. assert(sizeof(jschar) == sizeof(unichar)); length = [code length]; buffer = malloc(sizeof(jschar) * length); if (buffer == NULL) OK = NO; } if (OK) { assert(argCount < UINT32_MAX); [code getCharacters:buffer]; function = JS_CompileUCFunction(context, scope, [name UTF8String], (uint32_t)argCount, argNames, buffer, length, [fileName UTF8String], (uint32_t)lineNumber); if (function == NULL) OK = NO; free(buffer); } if (OK) { self = [self initWithFunction:function context:context]; } else { DESTROY(self); } if (releaseContext) OOJSRelinquishContext(context); return self; } - (void) deleteJSValue { if (_function != NULL) { JSContext *context = OOJSAcquireContext(); JS_RemoveObjectRoot(context, (JSObject **)&_function); OOJSRelinquishContext(context); _function = NULL; [[NSNotificationCenter defaultCenter] removeObserver:self name:kOOJavaScriptEngineWillResetNotification object:[OOJavaScriptEngine sharedEngine]]; } } - (void) dealloc { [self deleteJSValue]; DESTROY(_name); [super dealloc]; } - (NSString *) descriptionComponents { NSString *name = [self name]; if (name == nil) name = @""; return [NSString stringWithFormat:@"%@()", name]; } - (NSString *) name { return _name; } - (JSFunction *) function { return _function; } - (jsval) functionValue { if (EXPECT(_function != NULL)) { return OBJECT_TO_JSVAL(JS_GetFunctionObject(_function)); } else { return JSVAL_NULL; } } - (BOOL) evaluateWithContext:(JSContext *)context scope:(JSObject *)jsThis argc:(uintN)argc argv:(jsval *)argv result:(jsval *)result { [OOJSScript pushScript:nil]; OOJSStartTimeLimiter(); BOOL OK = JS_CallFunction(context, jsThis, _function, argc, argv, result); OOJSStopTimeLimiter(); [OOJSScript popScript:nil]; return OK; } // Semi-raw evaluation shared by convenience methods below. - (BOOL) evaluateWithContext:(JSContext *)context scope:(id)jsThis arguments:(NSArray *)arguments result:(jsval *)result { NSUInteger i, argc = [arguments count]; assert(argc < UINT32_MAX); jsval argv[argc]; for (i = 0; i < argc; i++) { argv[i] = [[arguments objectAtIndex:i] oo_jsValueInContext:context]; OOJSAddGCValueRoot(context, &argv[i], "OOJSFunction argv"); } JSObject *scopeObj = NULL; BOOL OK = YES; if (jsThis != nil) OK = JS_ValueToObject(context, [jsThis oo_jsValueInContext:context], &scopeObj); if (OK) OK = [self evaluateWithContext:context scope:scopeObj argc:(uint32_t)argc argv:argv result:result]; for (i = 0; i < argc; i++) { JS_RemoveValueRoot(context, &argv[i]); } return OK; } - (id) evaluateWithContext:(JSContext *)context scope:(id)jsThis arguments:(NSArray *)arguments { jsval result; BOOL OK = [self evaluateWithContext:context scope:jsThis arguments:arguments result:&result]; if (!OK) return nil; return OOJSNativeObjectFromJSValue(context, result); } - (BOOL) evaluatePredicateWithContext:(JSContext *)context scope:(id)jsThis arguments:(NSArray *)arguments { jsval result; BOOL OK = [self evaluateWithContext:context scope:jsThis arguments:arguments result:&result]; JSBool retval = NO; if (OK) OK = JS_ValueToBoolean(context, result, &retval); return OK && retval; } @end oolite-1.82/src/Core/Scripting/OOJSGlobal.h000066400000000000000000000017221256642440500204360ustar00rootroot00000000000000/* OOJSGlobal.h JavaScript global object. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #include void CreateOOJSGlobal(JSContext *context, JSObject **outGlobal); void SetUpOOJSGlobal(JSContext *context, JSObject *global); oolite-1.82/src/Core/Scripting/OOJSGlobal.m000066400000000000000000000344721256642440500204530ustar00rootroot00000000000000/* OOJSGlobal.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSGlobal.h" #import "OOJavaScriptEngine.h" #import "OOJSPlayer.h" #import "PlayerEntityScriptMethods.h" #import "OOStringExpander.h" #import "OOConstToString.h" #import "OOConstToJSString.h" #import "OOCollectionExtractors.h" #import "OOTexture.h" #import "GuiDisplayGen.h" #import "MyOpenGLView.h" #import "ResourceManager.h" #import "OOSystemDescriptionManager.h" #import "NSFileManagerOOExtensions.h" #if OOJSENGINE_MONITOR_SUPPORT @interface OOJavaScriptEngine (OOMonitorSupportInternal) - (void)sendMonitorLogMessage:(NSString *)message withMessageClass:(NSString *)messageClass inContext:(JSContext *)context; @end #endif static NSString * const kOOLogDebugMessage = @"script.debug.message"; static JSBool GlobalGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); #ifndef NDEBUG static JSBool GlobalSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); #endif static JSBool GlobalLog(JSContext *context, uintN argc, jsval *vp); static JSBool GlobalExpandDescription(JSContext *context, uintN argc, jsval *vp); static JSBool GlobalExpandMissionText(JSContext *context, uintN argc, jsval *vp); static JSBool GlobalDisplayNameForCommodity(JSContext *context, uintN argc, jsval *vp); static JSBool GlobalRandomName(JSContext *context, uintN argc, jsval *vp); static JSBool GlobalRandomInhabitantsDescription(JSContext *context, uintN argc, jsval *vp); static JSBool GlobalSetScreenBackground(JSContext *context, uintN argc, jsval *vp); static JSBool GlobalSetScreenOverlay(JSContext *context, uintN argc, jsval *vp); static JSBool GlobalAutoAIForRole(JSContext *context, uintN argc, jsval *vp); #ifndef NDEBUG static JSBool GlobalTakeSnapShot(JSContext *context, uintN argc, jsval *vp); #endif static JSClass sGlobalClass = { "Global", JSCLASS_GLOBAL_FLAGS, JS_PropertyStub, JS_PropertyStub, GlobalGetProperty, #ifndef NDEBUG GlobalSetProperty, #else // No writeable properties in non-debug builds JS_StrictPropertyStub, #endif JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub }; enum { // Property IDs kGlobal_galaxyNumber, // galaxy number, integer, read-only kGlobal_global, // global.global.global.global, integer, read-only kGlobal_guiScreen, // current GUI screen, string, read-only #ifndef NDEBUG kGlobal_timeAccelerationFactor // time acceleration, float, read/write #endif }; static JSPropertySpec sGlobalProperties[] = { // JS name ID flags { "galaxyNumber", kGlobal_galaxyNumber, OOJS_PROP_READONLY_CB }, { "guiScreen", kGlobal_guiScreen, OOJS_PROP_READONLY_CB }, #ifndef NDEBUG { "timeAccelerationFactor", kGlobal_timeAccelerationFactor, OOJS_PROP_READWRITE_CB }, #endif { 0 } }; static JSFunctionSpec sGlobalMethods[] = { // JS name Function min args { "log", GlobalLog, 1 }, { "autoAIForRole", GlobalAutoAIForRole, 1 }, { "expandDescription", GlobalExpandDescription, 1 }, { "expandMissionText", GlobalExpandMissionText, 1 }, { "displayNameForCommodity", GlobalDisplayNameForCommodity, 1 }, { "randomName", GlobalRandomName, 0 }, { "randomInhabitantsDescription", GlobalRandomInhabitantsDescription, 1 }, { "setScreenBackground", GlobalSetScreenBackground, 1 }, { "setScreenOverlay", GlobalSetScreenOverlay, 1 }, #ifndef NDEBUG { "takeSnapShot", GlobalTakeSnapShot, 1 }, #endif { 0 } }; void CreateOOJSGlobal(JSContext *context, JSObject **outGlobal) { assert(outGlobal != NULL); *outGlobal = JS_NewCompartmentAndGlobalObject(context, &sGlobalClass, NULL); JS_SetGlobalObject(context, *outGlobal); JS_DefineProperty(context, *outGlobal, "global", OBJECT_TO_JSVAL(*outGlobal), NULL, NULL, OOJS_PROP_READONLY); } void SetUpOOJSGlobal(JSContext *context, JSObject *global) { JS_DefineProperties(context, global, sGlobalProperties); JS_DefineFunctions(context, global, sGlobalMethods); } static JSBool GlobalGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); switch (JSID_TO_INT(propID)) { case kGlobal_galaxyNumber: *value = INT_TO_JSVAL([player currentGalaxyID]); return YES; case kGlobal_guiScreen: *value = OOJSValueFromGUIScreenID(context, [player guiScreen]); return YES; #ifndef NDEBUG case kGlobal_timeAccelerationFactor: return JS_NewNumberValue(context, [UNIVERSE timeAccelerationFactor], value); #endif default: OOJSReportBadPropertySelector(context, this, propID, sGlobalProperties); return NO; } OOJS_NATIVE_EXIT } #ifndef NDEBUG static JSBool GlobalSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) jsdouble fValue; switch (JSID_TO_INT(propID)) { case kGlobal_timeAccelerationFactor: if (JS_ValueToNumber(context, *value, &fValue)) { [UNIVERSE setTimeAccelerationFactor:fValue]; return YES; } break; default: OOJSReportBadPropertySelector(context, this, propID, sGlobalProperties); } OOJSReportBadPropertyValue(context, this, propID, sGlobalProperties, *value); return NO; OOJS_NATIVE_EXIT } #endif // *** Methods *** // log([messageClass : String,] message : string, ...) static JSBool GlobalLog(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *message = nil; NSString *messageClass = nil; if (EXPECT_NOT(argc < 1)) { OOJS_RETURN_VOID; } if (argc < 2) { messageClass = kOOLogDebugMessage; message = OOStringFromJSValue(context, OOJS_ARGV[0]); } else { messageClass = OOStringFromJSValueEvenIfNull(context, OOJS_ARGV[0]); if (!OOLogWillDisplayMessagesInClass(messageClass)) { // Do nothing (and short-circuit) if message class is filtered out. OOJS_RETURN_VOID; } message = [NSString concatenationOfStringsFromJavaScriptValues:OOJS_ARGV + 1 count:argc - 1 separator:@", " inContext:context]; } OOJS_BEGIN_FULL_NATIVE(context) OOLog(messageClass, @"%@", message); #if OOJSENGINE_MONITOR_SUPPORT [[OOJavaScriptEngine sharedEngine] sendMonitorLogMessage:message withMessageClass:nil inContext:context]; #endif OOJS_END_FULL_NATIVE OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // expandDescription(description : String [, overrides : object (dictionary)]) : String static JSBool GlobalExpandDescription(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *string = nil; NSDictionary *overrides = nil; if (argc > 0) string = OOStringFromJSValue(context, OOJS_ARGV[0]); if (string == nil) { OOJSReportBadArguments(context, nil, @"expandDescription", MIN(argc, 1U), OOJS_ARGV, nil, @"string"); return NO; } if (argc > 1) { overrides = OOJSDictionaryFromStringTable(context, OOJS_ARGV[1]); } OOJS_BEGIN_FULL_NATIVE(context) string = OOExpandDescriptionString(kNilRandomSeed, string, overrides, nil, nil, kOOExpandForJavaScript | kOOExpandGoodRNG); OOJS_END_FULL_NATIVE OOJS_RETURN_OBJECT(string); OOJS_NATIVE_EXIT } // expandMissionText(textKey : String [, overrides : object (dictionary)]) : String static JSBool GlobalExpandMissionText(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *string = nil; NSDictionary *overrides = nil; if (argc > 0) string = OOStringFromJSValue(context, OOJS_ARGV[0]); if (string == nil) { OOJSReportBadArguments(context, nil, @"expandMissionText", MIN(argc, 1U), OOJS_ARGV, nil, @"string"); return NO; } if (argc > 1) { overrides = OOJSDictionaryFromStringTable(context, OOJS_ARGV[1]); } string = [[UNIVERSE missiontext] oo_stringForKey:string]; string = OOExpandDescriptionString(kNilRandomSeed, string, overrides, nil, nil, kOOExpandForJavaScript | kOOExpandBackslashN | kOOExpandGoodRNG); OOJS_RETURN_OBJECT(string); OOJS_NATIVE_EXIT } // displayNameForCommodity(commodityName : String) : String static JSBool GlobalDisplayNameForCommodity(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *string = nil; if (argc > 0) string = OOStringFromJSValue(context,OOJS_ARGV[0]); if (string == nil) { OOJSReportBadArguments(context, nil, @"displayNameForCommodity", MIN(argc, 1U), OOJS_ARGV, nil, @"string"); return NO; } OOJS_RETURN_OBJECT(CommodityDisplayNameForSymbolicName(string)); OOJS_NATIVE_EXIT } // randomName() : String static JSBool GlobalRandomName(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) /* Temporarily set the system generation seed to a "really random" seed, so randomName() isn't repeatable. */ RNG_Seed savedSeed = currentRandomSeed(); setRandomSeed((RNG_Seed){ Ranrot(), Ranrot(), Ranrot(), Ranrot() }); NSString *result = OOExpand(@"%N"); // Restore seed. setRandomSeed(savedSeed); OOJS_RETURN_OBJECT(result); OOJS_NATIVE_EXIT } // randomInhabitantsDescription() : String static JSBool GlobalRandomInhabitantsDescription(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *string = nil; Random_Seed aSeed; JSBool isPlural = YES; if (argc > 0 && !JS_ValueToBoolean(context, OOJS_ARGV[0], &isPlural)) { OOJSReportBadArguments(context, nil, @"displayNameForCommodity", 1, OOJS_ARGV, nil, @"boolean"); return NO; } make_pseudo_random_seed(&aSeed); string = [UNIVERSE getSystemInhabitants:Ranrot()%OO_SYSTEMS_PER_GALAXY plural:isPlural]; OOJS_RETURN_OBJECT(string); OOJS_NATIVE_EXIT } // setScreenBackground(descriptor : guiTextureDescriptor) : Boolean static JSBool GlobalSetScreenBackground(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) BOOL result = NO; jsval value = (argc > 0) ? OOJS_ARGV[0] : JSVAL_NULL; if (EXPECT_NOT(argc == 0)) { OOJSReportWarning(context, @"Usage error: %@() called with no arguments. Treating as %@(null). This call may fail in a future version of Oolite.", @"setScreenBackground", @"setScreenBackground"); } else if (EXPECT_NOT(JSVAL_IS_VOID(value))) { OOJSReportBadArguments(context, nil, @"setScreenBackground", 1, &value, nil, @"GUI texture descriptor"); return NO; } if ([UNIVERSE viewDirection] == VIEW_GUI_DISPLAY) { GuiDisplayGen *gui = [UNIVERSE gui]; NSDictionary *descriptor = [gui textureDescriptorFromJSValue:value inContext:context callerDescription:@"setScreenBackground()"]; result = [gui setBackgroundTextureDescriptor:descriptor]; // add some permanence to the override if we're in the equip ship screen if (result && [PLAYER guiScreen] == GUI_SCREEN_EQUIP_SHIP) [PLAYER setEquipScreenBackgroundDescriptor:descriptor]; } OOJS_RETURN_BOOL(result); OOJS_NATIVE_EXIT } // setScreenOverlay(descriptor : guiTextureDescriptor) : Boolean static JSBool GlobalSetScreenOverlay(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) BOOL result = NO; jsval value = (argc > 0) ? OOJS_ARGV[0] : JSVAL_NULL; if (EXPECT_NOT(argc == 0)) { OOJSReportWarning(context, @"Usage error: %@() called with no arguments. Treating as %@(null). This call may fail in a future version of Oolite.", @"setScreenOverlay", @"setScreenOverlay"); } else if (EXPECT_NOT(JSVAL_IS_VOID(value))) { OOJSReportBadArguments(context, nil, @"setScreenOverlay", 1, &value, nil, @"GUI texture descriptor"); return NO; } if ([UNIVERSE viewDirection] == VIEW_GUI_DISPLAY) { GuiDisplayGen *gui = [UNIVERSE gui]; NSDictionary *descriptor = [gui textureDescriptorFromJSValue:value inContext:context callerDescription:@"setScreenOverlay()"]; result = [gui setForegroundTextureDescriptor:descriptor]; } OOJS_RETURN_BOOL(result); OOJS_NATIVE_EXIT } #ifndef NDEBUG // takeSnapShot([name : alphanumeric String]) : Boolean static JSBool GlobalTakeSnapShot(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *value = nil; NSMutableCharacterSet *allowedChars = (NSMutableCharacterSet *)[NSMutableCharacterSet alphanumericCharacterSet]; BOOL result = NO; [allowedChars addCharactersInString:@"_-"]; if (argc > 0) { value = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(value == nil || [value rangeOfCharacterFromSet:[allowedChars invertedSet]].location != NSNotFound)) { OOJSReportBadArguments(context, nil, @"takeSnapShot", argc, OOJS_ARGV, nil, @"alphanumeric string"); return NO; } } NSString *playerFileDirectory = [[NSFileManager defaultManager] defaultCommanderPath]; NSDictionary *attr = [[NSFileManager defaultManager] oo_fileSystemAttributesAtPath:playerFileDirectory]; if (attr != nil) { double freeSpace = [attr oo_doubleForKey:NSFileSystemFreeSize]; if (freeSpace < 1073741824) // less than 1 GB free on disk? { OOJSReportWarning(context, @"takeSnapShot: function disabled when free disk space is less than 1GB."); OOJS_RETURN_BOOL(NO); } } OOJS_BEGIN_FULL_NATIVE(context) result = [[UNIVERSE gameView] snapShot:value]; OOJS_END_FULL_NATIVE OOJS_RETURN_BOOL(result); OOJS_NATIVE_EXIT } #endif // autoAIForRole(role : String) : String static JSBool GlobalAutoAIForRole(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *string = nil; if (argc > 0) string = OOStringFromJSValue(context,OOJS_ARGV[0]); if (string == nil) { OOJSReportBadArguments(context, nil, @"autoAIForRole", MIN(argc, 1U), OOJS_ARGV, nil, @"string"); return NO; } NSDictionary *autoAIMap = [ResourceManager dictionaryFromFilesNamed:@"autoAImap.plist" inFolder:@"Config" andMerge:YES]; NSString *autoAI = [autoAIMap oo_stringForKey:string]; OOJS_RETURN_OBJECT(autoAI); OOJS_NATIVE_EXIT } oolite-1.82/src/Core/Scripting/OOJSInterfaceDefinition.h000066400000000000000000000027101256642440500231450ustar00rootroot00000000000000/* OOJSInterfaceDefinition.h Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSScript.h" #include @interface OOJSInterfaceDefinition: OOWeakRefObject { @private jsval _callback; JSObject *_callbackThis; OOJSScript *_owningScript; NSString *_title; NSString *_summary; NSString *_category; } - (NSString *)title; - (void)setTitle:(NSString *)title; - (NSString *)category; - (void)setCategory:(NSString *)category; - (NSString *)summary; - (void)setSummary:(NSString *)summary; - (jsval)callback; - (void)setCallback:(jsval)callback; - (JSObject *)callbackThis; - (void)setCallbackThis:(JSObject *)callbackthis; - (void)runCallback:(NSString *)key; - (NSComparisonResult)interfaceCompare:(OOJSInterfaceDefinition *)other; @end oolite-1.82/src/Core/Scripting/OOJSInterfaceDefinition.m000066400000000000000000000072341256642440500231600ustar00rootroot00000000000000/* OOJSInterfaceDefinition.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSInterfaceDefinition.h" #import "OOJavaScriptEngine.h" @implementation OOJSInterfaceDefinition - (id) init { _callback = JSVAL_VOID; _callbackThis = NULL; _owningScript = [[OOJSScript currentlyRunningScript] weakRetain]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deleteJSPointers) name:kOOJavaScriptEngineWillResetNotification object:[OOJavaScriptEngine sharedEngine]]; return self; } - (void) deleteJSPointers { JSContext *context = OOJSAcquireContext(); _callback = JSVAL_VOID; _callbackThis = NULL; JS_RemoveValueRoot(context, &_callback); JS_RemoveObjectRoot(context, &_callbackThis); OOJSRelinquishContext(context); [[NSNotificationCenter defaultCenter] removeObserver:self name:kOOJavaScriptEngineWillResetNotification object:[OOJavaScriptEngine sharedEngine]]; } - (void) dealloc { [_owningScript release]; [self deleteJSPointers]; [super dealloc]; } - (NSString *)title { return _title; } - (void)setTitle:(NSString *)title { [_title autorelease]; _title = [title retain]; } - (NSString *)category { return _category; } - (void)setCategory:(NSString *)category { [_category autorelease]; _category = [category retain]; } - (NSString *)summary { return _summary; } - (void)setSummary:(NSString *)summary { [_summary autorelease]; _summary = [summary retain]; } - (jsval)callback { return _callback; } - (void)setCallback:(jsval)callback { JSContext *context = OOJSAcquireContext(); JS_RemoveValueRoot(context, &_callback); _callback = callback; OOJSAddGCValueRoot(context, &_callback, "OOJSInterfaceDefinition callback function"); OOJSRelinquishContext(context); } - (JSObject *)callbackThis { return _callbackThis; } - (void)setCallbackThis:(JSObject *)callbackThis { JSContext *context = OOJSAcquireContext(); JS_RemoveObjectRoot(context, &_callbackThis); _callbackThis = callbackThis; OOJSAddGCObjectRoot(context, &_callbackThis, "OOJSInterfaceDefinition callback this"); OOJSRelinquishContext(context); } - (void)runCallback:(NSString *)key { OOJavaScriptEngine *engine = [OOJavaScriptEngine sharedEngine]; JSContext *context = OOJSAcquireContext(); jsval rval = JSVAL_VOID; jsval cKey = OOJSValueFromNativeObject(context, key); OOJSScript *owner = [_owningScript retain]; // local copy needed [OOJSScript pushScript:owner]; [engine callJSFunction:_callback forObject:_callbackThis argc:1 argv:&cKey result:&rval]; [OOJSScript popScript:owner]; [owner release]; OOJSRelinquishContext(context); } - (NSComparisonResult)interfaceCompare:(OOJSInterfaceDefinition *)other { NSComparisonResult byCategory = [_category caseInsensitiveCompare:[other category]]; if (byCategory == NSOrderedSame) { return [_title caseInsensitiveCompare:[other title]]; } else { return byCategory; } } @end oolite-1.82/src/Core/Scripting/OOJSManifest.h000066400000000000000000000017201256642440500210020ustar00rootroot00000000000000/* OOJSManifest.h JavaScript object representing system info overrides. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #include void InitOOJSManifest(JSContext *context, JSObject *global); oolite-1.82/src/Core/Scripting/OOJSManifest.m000066400000000000000000000203521256642440500210110ustar00rootroot00000000000000/* OOJSManifest.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSManifest.h" #import "OOJavaScriptEngine.h" #import "PlayerEntity.h" #import "PlayerEntityScriptMethods.h" #import "PlayerEntityContracts.h" #import "Universe.h" #import "OOJSPlayer.h" #import "OOJSPlayerShip.h" #import "OOIsNumberLiteral.h" static JSObject *sManifestPrototype; static JSObject *sManifestObject; static JSBool ManifestComment(JSContext *context, uintN argc, jsval *vp); static JSBool ManifestSetComment(JSContext *context, uintN argc, jsval *vp); static JSBool ManifestShortComment(JSContext *context, uintN argc, jsval *vp); static JSBool ManifestSetShortComment(JSContext *context, uintN argc, jsval *vp); static JSBool ManifestDeleteProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool ManifestGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool ManifestSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); static JSClass sManifestClass = { "Manifest", JSCLASS_HAS_PRIVATE, JS_PropertyStub, ManifestDeleteProperty, ManifestGetProperty, ManifestSetProperty, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, OOJSObjectWrapperFinalize, JSCLASS_NO_OPTIONAL_MEMBERS }; enum { kManifest_list // manifest list, array of commodities: name, unit, quantity, displayName - read-only }; static JSPropertySpec sManifestProperties[] = { // JS name ID flags { "list", kManifest_list, OOJS_PROP_READONLY_CB }, { 0 } }; static JSFunctionSpec sManifestMethods[] = { // JS name Function min args { "shortComment", ManifestShortComment, 1 }, { "setShortComment", ManifestSetShortComment, 2 }, { "comment", ManifestComment, 1 }, { "setComment", ManifestSetComment, 2 }, { 0 } }; // Helper class wrapped by JS Manifest objects @interface OOManifest: NSObject @end @implementation OOManifest - (void) dealloc { [super dealloc]; } - (NSString *) oo_jsClassName { return @"Manifest"; } - (jsval) oo_jsValueInContext:(JSContext *)context { JSObject *jsSelf = NULL; jsval result = JSVAL_NULL; jsSelf = JS_NewObject(context, &sManifestClass, sManifestPrototype, NULL); if (jsSelf != NULL) { if (!JS_SetPrivate(context, jsSelf, [self retain])) jsSelf = NULL; } if (jsSelf != NULL) result = OBJECT_TO_JSVAL(jsSelf); return result; } @end void InitOOJSManifest(JSContext *context, JSObject *global) { sManifestPrototype = JS_InitClass(context, global, NULL, &sManifestClass, OOJSUnconstructableConstruct, 0, sManifestProperties, sManifestMethods, NULL, NULL); OOJSRegisterObjectConverter(&sManifestClass, OOJSBasicPrivateObjectConverter); // Create manifest object as a property of the player.ship object. sManifestObject = JS_DefineObject(context, JSPlayerShipObject(), "manifest", &sManifestClass, sManifestPrototype, OOJS_PROP_READONLY); JS_SetPrivate(context, sManifestObject, NULL); // Also define manifest object as a property of the global object. // Wait, what? Why? Oh well, too late now. Deprecate for EMMSTRAN? -- Ahruman 2011-02-10 JS_DefineObject(context, global, "manifest", &sManifestClass, sManifestPrototype, OOJS_PROP_READONLY); } static JSBool ManifestDeleteProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { jsval v = JSVAL_VOID; return ManifestSetProperty(context, this, propID, NO, &v); } static JSBool ManifestGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { OOJS_NATIVE_ENTER(context) id result = nil; PlayerEntity *entity = OOPlayerForScripting(); if (JSID_IS_INT(propID)) { switch (JSID_TO_INT(propID)) { case kManifest_list: result = [entity cargoListForScripting]; break; default: OOJSReportBadPropertySelector(context, this, propID, sManifestProperties); return NO; } } else if (JSID_IS_STRING(propID)) { /* 'list' property is hard-coded * others map to the commodity keys in trade-goods.plist * compatible-ish with 1.80 and earlier except that * alienItems and similar aliases don't work */ NSString *key = OOStringFromJSString(context, JSID_TO_STRING(propID)); if ([[UNIVERSE commodities] goodDefined:key]) { *value = INT_TO_JSVAL([entity cargoQuantityForType:key]); return YES; } else { return YES; } } *value = OOJSValueFromNativeObject(context, result); return YES; OOJS_NATIVE_EXIT } static JSBool ManifestSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { OOJS_NATIVE_ENTER(context) PlayerEntity *entity = OOPlayerForScripting(); int32 iValue; if (JSID_IS_STRING(propID)) { NSString *key = OOStringFromJSString(context, JSID_TO_STRING(propID)); OOMassUnit unit = [[UNIVERSE commodityMarket] massUnitForGood:key]; // we can always change gold, platinum & gem-stones quantities, even with special cargo if (unit == UNITS_TONS && [entity specialCargo]) { OOJSReportWarning(context, @"PlayerShip.manifest['foo'] - cannot modify cargo tonnage when Special Cargo is in use."); return YES; } if (JS_ValueToInt32(context, *value, &iValue)) { if (iValue < 0) iValue = 0; [entity setCargoQuantityForType:key amount:iValue]; } else { OOJSReportBadPropertyValue(context, this, propID, sManifestProperties, *value); } } return YES; OOJS_NATIVE_EXIT } static JSBool ManifestComment(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOCommodityType good = nil; NSString * information = nil; if (argc > 0) { good = OOStringFromJSValue(context, OOJS_ARGV[0]); } if (good == nil) { OOJSReportBadArguments(context, @"Manifest", @"comment", MIN(argc, 1U), OOJS_ARGV, nil, @"good"); return NO; } information = [[PLAYER shipCommodityData] commentForGood:good]; OOJS_RETURN_OBJECT(information); OOJS_NATIVE_EXIT } static JSBool ManifestSetComment(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) BOOL OK; OOCommodityType good = nil; NSString * information = nil; if (argc > 1) { good = OOStringFromJSValue(context, OOJS_ARGV[0]); information = OOStringFromJSValue(context, OOJS_ARGV[1]); } if (good == nil || information == nil) { OOJSReportBadArguments(context, @"Manifest", @"setComment", MIN(argc, 2U), OOJS_ARGV, nil, @"good and information text"); return NO; } OK = [[PLAYER shipCommodityData] setComment:information forGood:good]; OOJS_RETURN_BOOL(OK); OOJS_NATIVE_EXIT } static JSBool ManifestShortComment(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOCommodityType good = nil; NSString * information = nil; if (argc > 0) { good = OOStringFromJSValue(context, OOJS_ARGV[0]); } if (good == nil) { OOJSReportBadArguments(context, @"Manifest", @"shortComment", MIN(argc, 1U), OOJS_ARGV, nil, @"good"); return NO; } information = [[PLAYER shipCommodityData] shortCommentForGood:good]; OOJS_RETURN_OBJECT(information); OOJS_NATIVE_EXIT } static JSBool ManifestSetShortComment(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) BOOL OK; OOCommodityType good = nil; NSString * information = nil; if (argc > 1) { good = OOStringFromJSValue(context, OOJS_ARGV[0]); information = OOStringFromJSValue(context, OOJS_ARGV[1]); } if (good == nil || information == nil) { OOJSReportBadArguments(context, @"Manifest", @"setShortComment", MIN(argc, 2U), OOJS_ARGV, nil, @"good and information text"); return NO; } OK = [[PLAYER shipCommodityData] setShortComment:information forGood:good]; OOJS_RETURN_BOOL(OK); OOJS_NATIVE_EXIT } oolite-1.82/src/Core/Scripting/OOJSMission.h000066400000000000000000000016761256642440500206670ustar00rootroot00000000000000/* OOJSMission.h JavaScript mission screen interface object. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #include void InitOOJSMission(JSContext *context, JSObject *global); void MissionRunCallback(); oolite-1.82/src/Core/Scripting/OOJSMission.m000066400000000000000000000474141256642440500206740ustar00rootroot00000000000000/* OOJSMission.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSMission.h" #import "OOJavaScriptEngine.h" #import "OOJSScript.h" #import "OOConstToJSString.h" #import "OOJSPlayer.h" #import "PlayerEntityScriptMethods.h" #import "OOStringExpander.h" #import "OOCollectionExtractors.h" #import "OOMusicController.h" #import "GuiDisplayGen.h" #import "OODebugStandards.h" static JSBool MissionGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool MissionSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); static JSBool MissionMarkSystem(JSContext *context, uintN argc, jsval *vp); static JSBool MissionUnmarkSystem(JSContext *context, uintN argc, jsval *vp); static JSBool MissionAddMessageText(JSContext *context, uintN argc, jsval *vp); static JSBool MissionSetInstructions(JSContext *context, uintN argc, jsval *vp); static JSBool MissionSetInstructionsKey(JSContext *context, uintN argc, jsval *vp); static JSBool MissionRunScreen(JSContext *context, uintN argc, jsval *vp); static JSBool MissionRunShipLibrary(JSContext *context, uintN argc, jsval *vp); static JSBool MissionSetInstructionsInternal(JSContext *context, uintN argc, jsval *vp, BOOL isKey); // Mission screen callback varibables static jsval sCallbackFunction; static jsval sCallbackThis; static OOJSScript *sCallbackScript = nil; static JSObject *sMissionObject; static JSClass sMissionClass = { "Mission", 0, JS_PropertyStub, JS_PropertyStub, MissionGetProperty, MissionSetProperty, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub }; enum { kMission_markedSystems, kMission_screenID, kMission_exitScreen }; static JSPropertySpec sMissionProperties[] = { // JS name ID flags { "markedSystems", kMission_markedSystems, OOJS_PROP_READONLY_CB }, { "screenID", kMission_screenID, OOJS_PROP_READONLY_CB }, { "exitScreen", kMission_exitScreen, OOJS_PROP_READWRITE_CB }, { 0 } }; static JSFunctionSpec sMissionMethods[] = { // JS name Function min args { "addMessageText", MissionAddMessageText, 1 }, { "markSystem", MissionMarkSystem, 1 }, { "runScreen", MissionRunScreen, 1 }, // the callback function is optional! { "setInstructions", MissionSetInstructions, 1 }, { "setInstructionsKey", MissionSetInstructionsKey, 1 }, { "unmarkSystem", MissionUnmarkSystem, 1 }, { "runShipLibrary", MissionRunShipLibrary, 0 }, { 0 } }; void InitOOJSMission(JSContext *context, JSObject *global) { sCallbackFunction = JSVAL_NULL; sCallbackThis = JSVAL_NULL; JSObject *missionPrototype = JS_InitClass(context, global, NULL, &sMissionClass, OOJSUnconstructableConstruct, 0, sMissionProperties, sMissionMethods, NULL, NULL); sMissionObject = JS_DefineObject(context, global, "mission", &sMissionClass, missionPrototype, OOJS_PROP_READONLY); // Ensure JS objects are rooted. OOJSAddGCValueRoot(context, &sCallbackFunction, "Pending mission callback function"); OOJSAddGCValueRoot(context, &sCallbackThis, "Pending mission callback this"); } void MissionRunCallback() { // don't do anything if we don't have a function. if (JSVAL_IS_NULL(sCallbackFunction) || JSVAL_IS_VOID(sCallbackFunction)) return; jsval argval = JSVAL_VOID; jsval rval = JSVAL_VOID; PlayerEntity *player = OOPlayerForScripting(); OOJavaScriptEngine *engine = [OOJavaScriptEngine sharedEngine]; JSContext *context = OOJSAcquireContext(); /* Create temporarily-rooted local copies of sCallbackFunction and sCallbackThis, then clear the statics. This must be done in advance since the callback might call runScreen() and clobber the statics. */ jsval cbFunction = JSVAL_VOID; JSObject *cbThis = NULL; OOJSScript *cbScript = sCallbackScript; OOJSAddGCValueRoot(context, &cbFunction, "Mission callback function"); OOJSAddGCObjectRoot(context, &cbThis, "Mission callback this"); cbFunction = sCallbackFunction; cbScript = sCallbackScript; JS_ValueToObject(context, sCallbackThis, &cbThis); sCallbackScript = nil; sCallbackFunction = JSVAL_NULL; sCallbackThis = JSVAL_NULL; argval = OOJSValueFromNativeObject(context, [player missionChoice_string]); // now reset the mission choice silently, before calling the callback script. [player setMissionChoice:nil withEvent:NO]; // Call the callback. @try { [OOJSScript pushScript:cbScript]; [engine callJSFunction:cbFunction forObject:cbThis argc:1 argv:&argval result:&rval]; } @catch (NSException *exception) { // Squash any exception, allow cleanup to happen and so forth. OOLog(kOOLogException, @"Ignoring exception %@:%@ during handling of mission screen completion callback.", [exception name], [exception reason]); } [OOJSScript popScript:cbScript]; // Manage that memory. [cbScript release]; JS_RemoveValueRoot(context, &cbFunction); JS_RemoveObjectRoot(context, &cbThis); OOJSRelinquishContext(context); } static JSBool MissionGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) id result = nil; PlayerEntity *player = OOPlayerForScripting(); switch (JSID_TO_INT(propID)) { case kMission_markedSystems: result = [player getMissionDestinations]; if (result == nil) result = [NSDictionary dictionary]; result = [result allValues]; break; case kMission_screenID: result = [player missionScreenID]; break; case kMission_exitScreen: *value = OOJSValueFromGUIScreenID(context, [player missionExitScreen]); return YES; default: OOJSReportBadPropertySelector(context, this, propID, sMissionProperties); return NO; } *value = OOJSValueFromNativeObject(context, result); return YES; OOJS_NATIVE_EXIT } static JSBool MissionSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) OOGUIScreenID exitScreen; PlayerEntity *player = OOPlayerForScripting(); switch (JSID_TO_INT(propID)) { case kMission_exitScreen: exitScreen = OOGUIScreenIDFromJSValue(context, *value); [player setMissionExitScreen:exitScreen]; return YES; default: OOJSReportBadPropertySelector(context, this, propID, sMissionProperties); } OOJSReportBadPropertyValue(context, this, propID, sMissionProperties, *value); return NO; OOJS_NATIVE_EXIT } // *** Methods *** // markSystem(integer+) static JSBool MissionMarkSystem(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); unsigned i; int dest; // two pass. Once to validate, once to apply if they validate for (i=0;i= 0) { [player addMissionDestinationMarker:marker]; } } } OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // unmarkSystem(integer+) static JSBool MissionUnmarkSystem(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); unsigned i; int dest; // two pass. Once to validate, once to apply if they validate for (i=0;i= 0) { if (![player removeMissionDestinationMarker:marker]) { result = NO; } } } } OOJS_RETURN_BOOL(result); OOJS_NATIVE_EXIT } // addMessageText(text : String) static JSBool MissionAddMessageText(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); NSString *text = nil; if (EXPECT_NOT(argc == 0)) { OOJS_RETURN_VOID; } // Found "FIXME: warning if no mission screen running.",,, // However: used routinely by the Constrictor mission in F7, without mission screens. text = OOStringFromJSValue(context, OOJS_ARGV[0]); [player addLiteralMissionText:text]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // setInstructionsKey(instructionsKey: String [, missionKey : String]) static JSBool MissionSetInstructionsKey(JSContext *context, uintN argc, jsval *vp) { return MissionSetInstructionsInternal(context, argc, vp, YES); } // setInstructions(instructions: String [, missionKey : String]) static JSBool MissionSetInstructions(JSContext *context, uintN argc, jsval *vp) { return MissionSetInstructionsInternal(context, argc, vp, NO); } static JSBool MissionSetInstructionsInternal(JSContext *context, uintN argc, jsval *vp, BOOL isKey) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); NSString *text = nil; NSArray *texts = nil; NSString *missionKey = nil; if (EXPECT_NOT(argc == 0)) { OOJSReportWarning(context, @"Usage error: mission.%@() called with no arguments. Treating as Mission.%@(null). This call may fail in a future version of Oolite.", isKey ? @"setInstructionsKey" : @"setInstructions", isKey ? @"setInstructionsKey" : @"setInstructions"); } else if (EXPECT_NOT(JSVAL_IS_VOID(OOJS_ARGV[0]))) { OOJSReportBadArguments(context, @"Mission", isKey ? @"setInstructionsKey" : @"setInstructions", 1, OOJS_ARGV, NULL, @"string or null"); return NO; } else if (!JSVAL_IS_NULL(OOJS_ARGV[0]) && JSVAL_IS_OBJECT(OOJS_ARGV[0])) { texts = OOJSNativeObjectFromJSValue(context, OOJS_ARGV[0]); } else { text = OOStringFromJSValue(context, OOJS_ARGV[0]); } if (argc > 1) { missionKey = OOStringFromJSValueEvenIfNull(context, OOJS_ARGV[1]); } else { missionKey = [[OOJSScript currentlyRunningScript] name]; } if (text != nil) { if (isKey) { [player setMissionDescription:text forMission:missionKey]; } else { [player setMissionInstructions:text forMission:missionKey]; } } else if (texts != nil && !isKey) { [player setMissionInstructionsList:texts forMission:missionKey]; } else { [player clearMissionDescriptionForMission:missionKey]; } OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } static NSDictionary *GetParameterDictionary(JSContext *context, JSObject *object, const char *key) { jsval value = JSVAL_NULL; if (JS_GetProperty(context, object, key, &value)) { if (JSVAL_IS_OBJECT(value)) { return OOJSNativeObjectFromJSObject(context, JSVAL_TO_OBJECT(value)); } } return nil; } static NSString *GetParameterString(JSContext *context, JSObject *object, const char *key) { jsval value = JSVAL_NULL; if (JS_GetProperty(context, object, key, &value)) { return OOStringFromJSValue(context, value); } return nil; } static NSDictionary *GetParameterImageDescriptor(JSContext *context, JSObject *object, const char *key) { jsval value = JSVAL_NULL; if (JS_GetProperty(context, object, key, &value)) { return [[UNIVERSE gui] textureDescriptorFromJSValue:value inContext:context callerDescription:@"mission.runScreen()"]; } else { return nil; } } // runScreen(params: dict, callBack:function) - if the callback function is null, emulate the old style runMissionScreen static JSBool MissionRunScreen(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); jsval function = JSVAL_NULL; jsval value = JSVAL_NULL; JSObject *params = NULL; // No mission screens during intro. if ([player status] == STATUS_START_GAME) { // (though no JS should be loaded at this stage, so this // check may be obsolete - CIM) OOJS_RETURN_BOOL(NO); } // Validate arguments. if (argc < 1 || !JS_ValueToObject(context, OOJS_ARGV[0], ¶ms)) { OOJSReportBadArguments(context, @"mission", @"runScreen", MIN(argc, 1U), &OOJS_ARGV[0], nil, @"parameter object"); return NO; } if (argc > 1) function = OOJS_ARGV[1]; if (!JSVAL_IS_NULL(function) && !OOJSValueIsFunction(context, function)) { OOJSReportBadArguments(context, @"mission", @"runScreen", 1, &OOJS_ARGV[1], nil, @"function"); return NO; } // Not OOJS_BEGIN_FULL_NATIVE() - we use JSAPI while paused. OOJSPauseTimeLimiter(); if (!JSVAL_IS_NULL(function)) { /* CIM 30/12/12: This following line causes problems in certain * cases, but has to be kept for backward * compatibility. Documenting the third argument of * mission.runScreen will at least help people get around it in * multi-world-script mission screens. (Though, since no-one has * complained yet, perhaps I'm the only one who uses them?) */ sCallbackScript = [[[OOJSScript currentlyRunningScript] weakRefUnderlyingObject] retain]; if (argc > 2) { sCallbackThis = OOJS_ARGV[2]; } else { sCallbackThis = OOJSValueFromNativeObject(context, sCallbackScript); } } // Apply settings. if (JS_GetProperty(context, params, "title", &value) && !JSVAL_IS_VOID(value)) { [player setMissionTitle:OOStringFromJSValue(context, value)]; } else { NSString *titleKey = GetParameterString(context, params, "titleKey"); if (titleKey != nil) { NSString *message = [[UNIVERSE missiontext] oo_stringForKey:titleKey]; if (message != nil) { [player setMissionTitle:OOExpand(message)]; } else { OOJSReportWarning(context, @"Mission.runScreen: titleKey '%@' has no entry in missiontext.plist.", titleKey); } } } [[OOMusicController sharedController] setMissionMusic:GetParameterString(context, params, "music")]; [player setMissionOverlayDescriptor:GetParameterImageDescriptor(context, params, "overlay")]; [player setMissionBackgroundDescriptor:GetParameterImageDescriptor(context, params, "background")]; [player setMissionBackgroundSpecial:GetParameterString(context, params, "backgroundSpecial")]; [UNIVERSE removeDemoShips]; // remove any demoship or miniature planet that may be remaining from previous screens if ([player status] == STATUS_IN_FLIGHT) { OOStandardsError(@"Mission screens should not be used while in flight"); if (OOEnforceStandards()) { return NO; } } ShipEntity *demoShip = nil; if (JS_GetProperty(context, params, "model", &value) && !JSVAL_IS_VOID(value)) { if ([player status] == STATUS_IN_FLIGHT && JSVAL_IS_STRING(value)) { OOJSReportWarning(context, @"Mission.runScreen: model cannot be displayed while in flight."); } else { NSString *role = OOStringFromJSValue(context, value); JSBool spinning = YES; if (JS_GetProperty(context, params, "spinModel", &value) && !JSVAL_IS_VOID(value)) { JS_ValueToBoolean(context, value, &spinning); } // [player showShipModel:OOStringFromJSValue(context, value)]; demoShip = [UNIVERSE makeDemoShipWithRole:role spinning:spinning]; } } if (demoShip != nil) { if (JS_GetProperty(context, params, "modelPersonality", &value) && !JSVAL_IS_VOID(value)) { int personality = 0; JS_ValueToInt32(context,value,&personality); [demoShip setEntityPersonalityInt:personality]; } jsval demoShipVal = [demoShip oo_jsValueInContext:context]; JS_SetProperty(context, sMissionObject, "displayModel", &demoShipVal); } else { JS_DeleteProperty(context, sMissionObject, "displayModel"); } JSBool allowInterrupt = NO; if (JS_GetProperty(context, params, "allowInterrupt", &value) && !JSVAL_IS_VOID(value)) { JS_ValueToBoolean(context, value, &allowInterrupt); } if (JS_GetProperty(context, params, "exitScreen", &value) && !JSVAL_IS_VOID(value)) { [player setMissionExitScreen:OOGUIScreenIDFromJSValue(context, value)]; } else { [player setMissionExitScreen:GUI_SCREEN_STATUS]; } if (JS_GetProperty(context, params, "screenID", &value) && !JSVAL_IS_VOID(value)) { [player setMissionScreenID:OOStringFromJSValue(context, value)]; } else { [player clearMissionScreenID]; } JSBool textEntry = NO; if (JS_GetProperty(context, params, "textEntry", &value) && !JSVAL_IS_VOID(value)) { JS_ValueToBoolean(context, value, &textEntry); } if (textEntry) { [player setMissionChoiceByTextEntry:YES]; } else { [player setMissionChoiceByTextEntry:NO]; } // Start the mission screen. sCallbackFunction = function; [player setGuiToMissionScreenWithCallback:!JSVAL_IS_NULL(sCallbackFunction)]; // Apply more settings. (These must be done after starting the screen for legacy reasons.) if (allowInterrupt) { [player allowMissionInterrupt]; } NSString *message = GetParameterString(context, params, "message"); if (message != nil) { [player addLiteralMissionText:message]; } else { NSString *messageKey = GetParameterString(context, params, "messageKey"); if (messageKey != nil) [player addMissionText:messageKey]; } if (!textEntry) { NSDictionary *choices = GetParameterDictionary(context, params, "choices"); if (choices == nil) { [player setMissionChoices:GetParameterString(context, params, "choicesKey")]; } else { [player setMissionChoicesDictionary:choices]; } } NSString *firstKey = GetParameterString(context, params, "initialChoicesKey"); if (firstKey != nil) { OOGUIRow row = [[UNIVERSE gui] rowForKey:firstKey]; if (row != -1) { [[UNIVERSE gui] setSelectedRow:row]; } } // now clean up! [player setMissionOverlayDescriptor:nil]; [player setMissionBackgroundDescriptor:nil]; [player setMissionTitle:nil]; [player setMissionMusic:nil]; OOJSResumeTimeLimiter(); OOJS_RETURN_BOOL(YES); OOJS_NATIVE_EXIT } static JSBool MissionRunShipLibrary(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); BOOL OK = YES; if ([player status] != STATUS_DOCKED) { OOJSReportWarning(context, @"Mission.runShipLibrary: must be docked."); OK = NO; } else { [PLAYER setGuiToIntroFirstGo:NO]; } OOJS_RETURN_BOOL(OK); OOJS_NATIVE_EXIT } oolite-1.82/src/Core/Scripting/OOJSMissionVariables.h000066400000000000000000000016571256642440500225170ustar00rootroot00000000000000/* OOJSMissionVariables.h JavaScript mission variables object. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #include void InitOOJSMissionVariables(JSContext *context, JSObject *global); oolite-1.82/src/Core/Scripting/OOJSMissionVariables.m000066400000000000000000000134701256642440500225200ustar00rootroot00000000000000/* OOJSMissionVariables.h JavaScript mission variables object. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSMissionVariables.h" #import "OOJavaScriptEngine.h" #import "OOIsNumberLiteral.h" #import "OOJSPlayer.h" static NSString *KeyForPropertyID(JSContext *context, jsid propID) { NSCParameterAssert(JSID_IS_STRING(propID)); NSString *key = OOStringFromJSString(context, JSID_TO_STRING(propID)); if ([key hasPrefix:@"_"]) return nil; return [@"mission_" stringByAppendingString:key]; } static JSBool MissionVariablesDeleteProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool MissionVariablesGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool MissionVariablesSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); static JSBool MissionVariablesEnumerate(JSContext *context, JSObject *object, JSIterateOp enumOp, jsval *state, jsid *idp); #ifndef NDEBUG static id MissionVariablesConverter(JSContext *context, JSObject *object); #endif static JSClass sMissionVariablesClass = { "MissionVariables", JSCLASS_NEW_ENUMERATE, JS_PropertyStub, MissionVariablesDeleteProperty, MissionVariablesGetProperty, MissionVariablesSetProperty, (JSEnumerateOp)MissionVariablesEnumerate, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub }; void InitOOJSMissionVariables(JSContext *context, JSObject *global) { JS_DefineObject(context, global, "missionVariables", &sMissionVariablesClass, NULL, OOJS_PROP_READONLY); #ifndef NDEBUG // Allow callObjC() on missionVariables to call methods on the mission variables dictionary. OOJSRegisterObjectConverter(&sMissionVariablesClass, MissionVariablesConverter); #endif } #ifndef NDEBUG static id MissionVariablesConverter(JSContext *context, JSObject *object) { return [PLAYER missionVariables]; } #endif static JSBool MissionVariablesDeleteProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); if (JSID_IS_STRING(propID)) { NSString *key = KeyForPropertyID(context, propID); [player setMissionVariable:nil forKey:key]; } return YES; OOJS_NATIVE_EXIT } static JSBool MissionVariablesGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); if (JSID_IS_STRING(propID)) { NSString *key = KeyForPropertyID(context, propID); if (key == nil) return YES; id mvar = [player missionVariableForKey:key]; if ([mvar isKindOfClass:[NSString class]]) // Currently there should only be strings, but we may want to change this. { if (OOIsNumberLiteral(mvar, YES)) { return JS_NewNumberValue(context, [mvar doubleValue], value); } } *value = OOJSValueFromNativeObject(context, mvar); } return YES; OOJS_NATIVE_EXIT } static JSBool MissionVariablesSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); if (JSID_IS_STRING(propID)) { NSString *key = KeyForPropertyID(context, propID); if (key == nil) { OOJSReportError(context, @"Invalid mission variable name \"%@\".", [OOStringFromJSID(propID) escapedForJavaScriptLiteral]); return NO; } NSString *objValue = OOStringFromJSValue(context, *value); if ([objValue isKindOfClass:[NSNull class]]) objValue = nil; [player setMissionVariable:objValue forKey:key]; } return YES; OOJS_NATIVE_EXIT } static JSBool MissionVariablesEnumerate(JSContext *context, JSObject *object, JSIterateOp enumOp, jsval *state, jsid *idp) { OOJS_NATIVE_ENTER(context) NSEnumerator *enumerator = nil; switch (enumOp) { case JSENUMERATE_INIT: case JSENUMERATE_INIT_ALL: // For ES5 Object.getOwnPropertyNames(). Since we have no non-enumerable properties, this is the same as _INIT. { // -allKeys implicitly makes a copy, which is good since the enumerating code might mutate. NSArray *mvars = [[PLAYER missionVariables] allKeys]; enumerator = [[mvars objectEnumerator] retain]; *state = PRIVATE_TO_JSVAL(enumerator); NSUInteger count = [mvars count]; assert(count <= INT32_MAX); if (idp != NULL) *idp = INT_TO_JSID((uint32_t)count); return YES; } case JSENUMERATE_NEXT: { enumerator = JSVAL_TO_PRIVATE(*state); for (;;) { NSString *next = [enumerator nextObject]; if (next == nil) break; if (![next hasPrefix:@"mission_"]) continue; // Skip mission instructions, which aren't visible through missionVariables. next = [next substringFromIndex:8]; // Cut off "mission_". jsval val = [next oo_jsValueInContext:context]; return JS_ValueToId(context, val, idp); } // If we got here, we've hit the end of the enumerator. *state = JSVAL_NULL; // Fall through. } case JSENUMERATE_DESTROY: { if (enumerator == nil && JSVAL_IS_DOUBLE(*state)) { enumerator = JSVAL_TO_PRIVATE(*state); } [enumerator release]; if (idp != NULL) *idp = JSID_VOID; return YES; } } OOJS_NATIVE_EXIT } oolite-1.82/src/Core/Scripting/OOJSOolite.h000066400000000000000000000016661256642440500205000ustar00rootroot00000000000000/* OOJSOolite.h JavaScript proxy for Oolite (for version checking and similar). Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #include void InitOOJSOolite(JSContext *context, JSObject *global); oolite-1.82/src/Core/Scripting/OOJSOolite.m000066400000000000000000000117401256642440500204770ustar00rootroot00000000000000/* OOJSOolite.h JavaScript proxy for Oolite (for version checking and similar). Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSOolite.h" #import "OOJavaScriptEngine.h" #import "OOStringParsing.h" #import "OOJSPlayer.h" static JSBool OoliteGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static NSString *VersionString(void); static NSArray *VersionComponents(void); static JSBool OoliteCompareVersion(JSContext *context, uintN argc, jsval *vp); static JSClass sOoliteClass = { "Oolite", 0, JS_PropertyStub, JS_PropertyStub, OoliteGetProperty, JS_StrictPropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub }; enum { // Property IDs kOolite_version, // version number components, array, read-only kOolite_versionString, // version number as string, string, read-only kOolite_jsVersion, // JavaScript version, integer, read-only kOolite_jsVersionString, // JavaScript version as string, string, read-only kOolite_gameSettings, // Various game settings, object, read-only }; static JSPropertySpec sOoliteProperties[] = { // JS name ID flags { "gameSettings", kOolite_gameSettings, OOJS_PROP_READONLY_CB }, { "jsVersion", kOolite_jsVersion, OOJS_PROP_READONLY_CB }, { "jsVersionString", kOolite_jsVersionString, OOJS_PROP_READONLY_CB }, { "version", kOolite_version, OOJS_PROP_READONLY_CB }, { "versionString", kOolite_versionString, OOJS_PROP_READONLY_CB }, { 0 } }; static JSFunctionSpec sOoliteMethods[] = { // JS name Function min args { "compareVersion", OoliteCompareVersion, 1 }, { 0 } }; void InitOOJSOolite(JSContext *context, JSObject *global) { JSObject *oolitePrototype = JS_InitClass(context, global, NULL, &sOoliteClass, OOJSUnconstructableConstruct, 0, sOoliteProperties, sOoliteMethods, NULL, NULL); JS_DefineObject(context, global, "oolite", &sOoliteClass, oolitePrototype, OOJS_PROP_READONLY); } static JSBool OoliteGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) id result = nil; switch (JSID_TO_INT(propID)) { case kOolite_version: result = VersionComponents(); break; case kOolite_versionString: result = VersionString(); break; case kOolite_jsVersion: *value = INT_TO_JSVAL(JS_GetVersion(context)); return YES; case kOolite_jsVersionString: *value = STRING_TO_JSVAL(JS_NewStringCopyZ(context, JS_VersionToString(JS_GetVersion(context)))); return YES; case kOolite_gameSettings: result = [UNIVERSE gameSettings]; break; default: OOJSReportBadPropertySelector(context, this, propID, sOoliteProperties); return NO; } *value = OOJSValueFromNativeObject(context, result); return YES; OOJS_NATIVE_EXIT } static NSString *VersionString(void) { return [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]; } static NSArray *VersionComponents(void) { return ComponentsFromVersionString(VersionString()); } /* oolite.compareVersion(versionSpec) : Number returns -1 if the current version of Oolite is less than versionSpec, 0 if they are equal, and 1 if the current version is newer. versionSpec may be a string or an array. Example: if (0 < oolite.compareVersion("1.70")) log("Old version of Oolite!") else this.doStuffThatRequires170() */ static JSBool OoliteCompareVersion(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) id components = nil; NSEnumerator *componentEnum = nil; id component = nil; if (argc == 0) OOJS_RETURN_VOID; // Backwards-compatibility: be overly lenient. components = OOJSNativeObjectFromJSValue(context, OOJS_ARGV[0]); if ([components isKindOfClass:[NSArray class]]) { // Require each element to be a number for (componentEnum = [components objectEnumerator]; (component = [componentEnum nextObject]); ) { if (![component isKindOfClass:[NSNumber class]]) { components = nil; break; } } } else if ([components isKindOfClass:[NSString class]]) { components = ComponentsFromVersionString(components); } else components = nil; if (components != nil) { OOJS_RETURN_INT((int32_t)CompareVersions(components, VersionComponents())); } else { OOJS_RETURN_VOID; } OOJS_NATIVE_EXIT } oolite-1.82/src/Core/Scripting/OOJSPlanet.h000066400000000000000000000016631256642440500204650ustar00rootroot00000000000000/* OOJSPlanet.h JavaScript proxy for PlanetEntities. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #include @class OOPlanetEntity; void InitOOJSPlanet(JSContext *context, JSObject *global); oolite-1.82/src/Core/Scripting/OOJSPlanet.m000066400000000000000000000144161256642440500204720ustar00rootroot00000000000000/* OOJSPlanet.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSPlanet.h" #import "OOJSEntity.h" #import "OOJavaScriptEngine.h" #import "OOJSQuaternion.h" #import "OOPlanetEntity.h" static JSObject *sPlanetPrototype; static JSBool PlanetGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool PlanetSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); static JSClass sPlanetClass = { "Planet", JSCLASS_HAS_PRIVATE, JS_PropertyStub, // addProperty JS_PropertyStub, // delProperty PlanetGetProperty, // getProperty PlanetSetProperty, // setProperty JS_EnumerateStub, // enumerate JS_ResolveStub, // resolve JS_ConvertStub, // convert OOJSObjectWrapperFinalize,// finalize JSCLASS_NO_OPTIONAL_MEMBERS }; enum { // Property IDs kPlanet_isMainPlanet, // Is [UNIVERSE planet], boolean, read-only kPlanet_hasAtmosphere, kPlanet_name, // Name of planet, string, read/write kPlanet_radius, // Radius of planet in metres, read-only kPlanet_texture, // Planet texture read / write kPlanet_orientation, // orientation, quaternion, read/write kPlanet_rotationalVelocity, // read/write }; static JSPropertySpec sPlanetProperties[] = { // JS name ID flags { "hasAtmosphere", kPlanet_hasAtmosphere, OOJS_PROP_READONLY_CB }, { "isMainPlanet", kPlanet_isMainPlanet, OOJS_PROP_READONLY_CB }, { "name", kPlanet_name, OOJS_PROP_READWRITE_CB }, { "radius", kPlanet_radius, OOJS_PROP_READONLY_CB }, { "rotationalVelocity", kPlanet_rotationalVelocity, OOJS_PROP_READWRITE_CB }, { "texture", kPlanet_texture, OOJS_PROP_READWRITE_CB }, { "orientation", kPlanet_orientation, OOJS_PROP_READWRITE_CB }, // Not documented since it's inherited from Entity { 0 } }; DEFINE_JS_OBJECT_GETTER(JSPlanetGetPlanetEntity, &sPlanetClass, sPlanetPrototype, OOPlanetEntity) void InitOOJSPlanet(JSContext *context, JSObject *global) { sPlanetPrototype = JS_InitClass(context, global, JSEntityPrototype(), &sPlanetClass, OOJSUnconstructableConstruct, 0, sPlanetProperties, NULL, NULL, NULL); OOJSRegisterObjectConverter(&sPlanetClass, OOJSBasicPrivateObjectConverter); OOJSRegisterSubclass(&sPlanetClass, JSEntityClass()); } @implementation OOPlanetEntity (OOJavaScriptExtensions) - (BOOL) isVisibleToScripts { OOStellarBodyType type = [self planetType]; return type == STELLAR_TYPE_NORMAL_PLANET || type == STELLAR_TYPE_MOON; } - (void)getJSClass:(JSClass **)outClass andPrototype:(JSObject **)outPrototype { *outClass = &sPlanetClass; *outPrototype = sPlanetPrototype; } - (NSString *) oo_jsClassName { switch ([self planetType]) { case STELLAR_TYPE_NORMAL_PLANET: return @"Planet"; case STELLAR_TYPE_MOON: return @"Moon"; default: return @"Unknown"; } } @end static JSBool PlanetGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) OOPlanetEntity *planet = nil; if (!JSPlanetGetPlanetEntity(context, this, &planet)) return NO; switch (JSID_TO_INT(propID)) { case kPlanet_isMainPlanet: *value = OOJSValueFromBOOL(planet == (id)[UNIVERSE planet]); return YES; case kPlanet_radius: return JS_NewNumberValue(context, [planet radius], value); case kPlanet_hasAtmosphere: *value = OOJSValueFromBOOL([planet hasAtmosphere]); return YES; case kPlanet_texture: *value = OOJSValueFromNativeObject(context, [planet textureFileName]); return YES; case kPlanet_name: *value = OOJSValueFromNativeObject(context, [planet name]); return YES; case kPlanet_orientation: return QuaternionToJSValue(context, [planet normalOrientation], value); case kPlanet_rotationalVelocity: return JS_NewNumberValue(context, [planet rotationalVelocity], value); default: OOJSReportBadPropertySelector(context, this, propID, sPlanetProperties); return NO; } OOJS_NATIVE_EXIT } static JSBool PlanetSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) OOPlanetEntity *planet = nil; NSString *sValue = nil; Quaternion qValue; jsdouble dValue; if (!JSPlanetGetPlanetEntity(context, this, &planet)) return NO; switch (JSID_TO_INT(propID)) { case kPlanet_name: sValue = OOStringFromJSValue(context, *value); [planet setName:sValue]; return YES; case kPlanet_texture: { BOOL OK = NO; sValue = OOStringFromJSValue(context, *value); OOJSPauseTimeLimiter(); if ([planet isKindOfClass:[OOPlanetEntity class]]) { if (sValue == nil) { OOJSReportWarning(context, @"Expected texture string. Value not set."); } else { OK = YES; } } if (OK) { OK = [planet setUpPlanetFromTexture:sValue]; if (!OK) OOJSReportWarning(context, @"Cannot find texture \"%@\". Value not set.", sValue); } OOJSResumeTimeLimiter(); return YES; // Even if !OK, no exception was raised. } case kPlanet_orientation: if (JSValueToQuaternion(context, *value, &qValue)) { quaternion_normalize(&qValue); [planet setOrientation:qValue]; return YES; } break; case kPlanet_rotationalVelocity: if (JS_ValueToNumber(context, *value, &dValue)) { [planet setRotationalVelocity:dValue]; return YES; } break; default: OOJSReportBadPropertySelector(context, this, propID, sPlanetProperties); return NO; } OOJSReportBadPropertyValue(context, this, propID, sPlanetProperties, *value); return NO; OOJS_NATIVE_EXIT } oolite-1.82/src/Core/Scripting/OOJSPlayer.h000066400000000000000000000025601256642440500204730ustar00rootroot00000000000000/* OOJSPlayer.h JavaScript proxy for the player. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #include @class PlayerEntity; void InitOOJSPlayer(JSContext *context, JSObject *global); JSClass *JSPlayerClass(void); JSObject *JSPlayerPrototype(void); JSObject *JSPlayerObject(void); /* All JS functions which talk to the player entity should call OOOPlayerForScripting() to ensure that the script target (for the legacy system) is set correctly. Additionally, all such functions should _always_ call OOPlayerForScripting(), even if they end up not using it, to ensure consistent state. */ PlayerEntity *OOPlayerForScripting(void); oolite-1.82/src/Core/Scripting/OOJSPlayer.m000066400000000000000000000501761256642440500205060ustar00rootroot00000000000000/* OOJSPlayer.h Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSPlayer.h" #import "OOJSEntity.h" #import "OOJSShip.h" #import "OOJSVector.h" #import "OOJavaScriptEngine.h" #import "EntityOOJavaScriptExtensions.h" #import "PlayerEntity.h" #import "PlayerEntityContracts.h" #import "PlayerEntityScriptMethods.h" #import "PlayerEntityLegacyScriptEngine.h" #import "OOConstToString.h" #import "OOFunctionAttributes.h" #import "OOCollectionExtractors.h" #import "OOStringParsing.h" static JSObject *sPlayerPrototype; static JSObject *sPlayerObject; static JSBool PlayerGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool PlayerSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); static JSBool PlayerCommsMessage(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerConsoleMessage(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerEndScenario(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerIncreaseContractReputation(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerDecreaseContractReputation(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerIncreasePassengerReputation(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerDecreasePassengerReputation(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerIncreaseParcelReputation(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerDecreaseParcelReputation(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerAddMessageToArrivalReport(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerReplaceShip(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerSetEscapePodDestination(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerSetPlayerRole(JSContext *context, uintN argc, jsval *vp); static JSClass sPlayerClass = { "Player", JSCLASS_HAS_PRIVATE, JS_PropertyStub, // addProperty JS_PropertyStub, // delProperty PlayerGetProperty, // getProperty PlayerSetProperty, // setProperty JS_EnumerateStub, // enumerate JS_ResolveStub, // resolve JS_ConvertStub, // convert OOJSObjectWrapperFinalize,// finalize JSCLASS_NO_OPTIONAL_MEMBERS }; enum { // Property IDs kPlayer_alertAltitude, // low altitude alert flag, boolean, read-only kPlayer_alertCondition, // alert level, integer, read-only kPlayer_alertEnergy, // low energy alert flag, boolean, read-only kPlayer_alertHostiles, // hostiles present alert flag, boolean, read-only kPlayer_alertMassLocked, // mass lock alert flag, boolean, read-only kPlayer_alertTemperature, // cabin temperature alert flag, boolean, read-only kPlayer_bounty, // bounty, unsigned int, read/write kPlayer_contractReputation, // reputation for cargo contracts, integer, read only kPlayer_contractReputationPrecise, // reputation for cargo contracts, float, read only kPlayer_credits, // credit balance, float, read/write kPlayer_dockingClearanceStatus, // docking clearance status, string, read only kPlayer_legalStatus, // legalStatus, string, read-only kPlayer_name, // Player name, string, read/write kPlayer_parcelReputation, // reputation for parcel contracts, integer, read-only kPlayer_parcelReputationPrecise, // reputation for parcel contracts, float, read-only kPlayer_passengerReputation, // reputation for passenger contracts, integer, read-only kPlayer_passengerReputationPrecise, // reputation for passenger contracts, float, read-only kPlayer_rank, // rank, string, read-only kPlayer_roleWeights, // role weights, array, read-only kPlayer_score, // kill count, integer, read/write kPlayer_trumbleCount, // number of trumbles, integer, read-only }; static JSPropertySpec sPlayerProperties[] = { // JS name ID flags { "alertAltitude", kPlayer_alertAltitude, OOJS_PROP_READONLY_CB }, { "alertCondition", kPlayer_alertCondition, OOJS_PROP_READONLY_CB }, { "alertEnergy", kPlayer_alertEnergy, OOJS_PROP_READONLY_CB }, { "alertHostiles", kPlayer_alertHostiles, OOJS_PROP_READONLY_CB }, { "alertMassLocked", kPlayer_alertMassLocked, OOJS_PROP_READONLY_CB }, { "alertTemperature", kPlayer_alertTemperature, OOJS_PROP_READONLY_CB }, { "bounty", kPlayer_bounty, OOJS_PROP_READWRITE_CB }, { "contractReputation", kPlayer_contractReputation, OOJS_PROP_READONLY_CB }, { "contractReputationPrecise", kPlayer_contractReputationPrecise, OOJS_PROP_READONLY_CB }, { "credits", kPlayer_credits, OOJS_PROP_READWRITE_CB }, { "dockingClearanceStatus", kPlayer_dockingClearanceStatus, OOJS_PROP_READONLY_CB }, { "legalStatus", kPlayer_legalStatus, OOJS_PROP_READONLY_CB }, { "name", kPlayer_name, OOJS_PROP_READWRITE_CB }, { "parcelReputation", kPlayer_parcelReputation, OOJS_PROP_READONLY_CB }, { "parcelReputationPrecise", kPlayer_parcelReputationPrecise, OOJS_PROP_READONLY_CB }, { "passengerReputation", kPlayer_passengerReputation, OOJS_PROP_READONLY_CB }, { "passengerReputationPrecise", kPlayer_passengerReputationPrecise, OOJS_PROP_READONLY_CB }, { "rank", kPlayer_rank, OOJS_PROP_READONLY_CB }, { "roleWeights", kPlayer_roleWeights, OOJS_PROP_READONLY_CB }, { "score", kPlayer_score, OOJS_PROP_READWRITE_CB }, { "trumbleCount", kPlayer_trumbleCount, OOJS_PROP_READONLY_CB }, { 0 } }; static JSFunctionSpec sPlayerMethods[] = { // JS name Function min args { "addMessageToArrivalReport", PlayerAddMessageToArrivalReport, 1 }, { "commsMessage", PlayerCommsMessage, 1 }, { "consoleMessage", PlayerConsoleMessage, 1 }, { "decreaseContractReputation", PlayerDecreaseContractReputation, 0 }, { "decreaseParcelReputation", PlayerDecreaseParcelReputation, 0 }, { "decreasePassengerReputation", PlayerDecreasePassengerReputation, 0 }, { "endScenario", PlayerEndScenario, 1 }, { "increaseContractReputation", PlayerIncreaseContractReputation, 0 }, { "increaseParcelReputation", PlayerIncreaseParcelReputation, 0 }, { "increasePassengerReputation", PlayerIncreasePassengerReputation, 0 }, { "replaceShip", PlayerReplaceShip, 1 }, { "setEscapePodDestination", PlayerSetEscapePodDestination, 1 }, // null destination must be set explicitly { "setPlayerRole", PlayerSetPlayerRole, 1 }, { 0 } }; void InitOOJSPlayer(JSContext *context, JSObject *global) { sPlayerPrototype = JS_InitClass(context, global, NULL, &sPlayerClass, OOJSUnconstructableConstruct, 0, sPlayerProperties, sPlayerMethods, NULL, NULL); OOJSRegisterObjectConverter(&sPlayerClass, OOJSBasicPrivateObjectConverter); // Create player object as a property of the global object. sPlayerObject = JS_DefineObject(context, global, "player", &sPlayerClass, sPlayerPrototype, OOJS_PROP_READONLY); } JSClass *JSPlayerClass(void) { return &sPlayerClass; } JSObject *JSPlayerPrototype(void) { return sPlayerPrototype; } JSObject *JSPlayerObject(void) { return sPlayerObject; } PlayerEntity *OOPlayerForScripting(void) { PlayerEntity *player = PLAYER; [player setScriptTarget:player]; return player; } static JSBool PlayerGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) id result = nil; PlayerEntity *player = OOPlayerForScripting(); switch (JSID_TO_INT(propID)) { case kPlayer_name: result = [player commanderName]; break; case kPlayer_score: *value = INT_TO_JSVAL([player score]); return YES; case kPlayer_credits: return JS_NewNumberValue(context, [player creditBalance], value); case kPlayer_rank: *value = OOJSValueFromNativeObject(context, OODisplayRatingStringFromKillCount([player score])); return YES; case kPlayer_legalStatus: *value = OOJSValueFromNativeObject(context, OODisplayStringFromLegalStatus([player legalStatus])); return YES; case kPlayer_alertCondition: *value = INT_TO_JSVAL([player alertCondition]); return YES; case kPlayer_alertTemperature: *value = OOJSValueFromBOOL([player alertFlags] & ALERT_FLAG_TEMP); return YES; case kPlayer_alertMassLocked: *value = OOJSValueFromBOOL([player alertFlags] & ALERT_FLAG_MASS_LOCK); return YES; case kPlayer_alertAltitude: *value = OOJSValueFromBOOL([player alertFlags] & ALERT_FLAG_ALT); return YES; case kPlayer_alertEnergy: *value = OOJSValueFromBOOL([player alertFlags] & ALERT_FLAG_ENERGY); return YES; case kPlayer_alertHostiles: *value = OOJSValueFromBOOL([player alertFlags] & ALERT_FLAG_HOSTILES); return YES; case kPlayer_trumbleCount: return JS_NewNumberValue(context, [player trumbleCount], value); /* For compatibility with previous versions, these are still on * a -7 to +7 scale */ case kPlayer_contractReputation: return JS_NewNumberValue(context, (int)(((float)[player contractReputation])/10.0), value); case kPlayer_passengerReputation: return JS_NewNumberValue(context, (int)(((float)[player passengerReputation])/10.0), value); case kPlayer_parcelReputation: return JS_NewNumberValue(context, (int)(((float)[player parcelReputation])/10.0), value); /* Full-precision reputations */ case kPlayer_contractReputationPrecise: return JS_NewNumberValue(context, ((float)[player contractReputation])/10.0, value); case kPlayer_passengerReputationPrecise: return JS_NewNumberValue(context, ((float)[player passengerReputation])/10.0, value); case kPlayer_parcelReputationPrecise: return JS_NewNumberValue(context, ((float)[player parcelReputation])/10.0, value); case kPlayer_dockingClearanceStatus: // EMMSTRAN: OOConstToJSString-ify this. *value = OOJSValueFromNativeObject(context, DockingClearanceStatusToString([player getDockingClearanceStatus])); return YES; case kPlayer_bounty: *value = INT_TO_JSVAL([player legalStatus]); return YES; case kPlayer_roleWeights: result = [player roleWeights]; break; default: OOJSReportBadPropertySelector(context, this, propID, sPlayerProperties); return NO; } *value = OOJSValueFromNativeObject(context, result); return YES; OOJS_NATIVE_EXIT } static JSBool PlayerSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); jsdouble fValue; int32 iValue; NSString *sValue; switch (JSID_TO_INT(propID)) { case kPlayer_name: sValue = OOStringFromJSValue(context,*value); if (sValue != nil) { [player setCommanderName:sValue]; return YES; } break; case kPlayer_score: if (JS_ValueToInt32(context, *value, &iValue)) { iValue = MAX(iValue, 0); [player setScore:iValue]; return YES; } break; case kPlayer_credits: if (JS_ValueToNumber(context, *value, &fValue)) { [player setCreditBalance:fValue]; return YES; } break; case kPlayer_bounty: if (JS_ValueToInt32(context, *value, &iValue)) { if (iValue < 0) iValue = 0; [player setBounty:iValue withReason:kOOLegalStatusReasonByScript]; return YES; } break; default: OOJSReportBadPropertySelector(context, this, propID, sPlayerProperties); return NO; } OOJSReportBadPropertyValue(context, this, propID, sPlayerProperties, *value); return NO; OOJS_NATIVE_EXIT } // *** Methods *** // commsMessage(message : String [, duration : Number]) static JSBool PlayerCommsMessage(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *message = nil; double time = 4.5; BOOL gotTime = YES; if (argc > 0) message = OOStringFromJSValue(context, OOJS_ARGV[0]); if (argc > 1) gotTime = JS_ValueToNumber(context, OOJS_ARGV[1], &time); if (message == nil || !gotTime) { OOJSReportBadArguments(context, @"Player", @"commsMessage", argc, OOJS_ARGV, nil, @"message and optional duration"); return NO; } [UNIVERSE addCommsMessage:message forCount:time]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // consoleMessage(message : String [, duration : Number]) static JSBool PlayerConsoleMessage(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *message = nil; double time = 3.0; BOOL gotTime = YES; if (argc > 0) message = OOStringFromJSValue(context, OOJS_ARGV[0]); if (argc > 1) gotTime = JS_ValueToNumber(context, OOJS_ARGV[1], &time); if (message == nil || !gotTime) { OOJSReportBadArguments(context, @"Player", @"consoleMessage", argc, OOJS_ARGV, nil, @"message and optional duration"); return NO; } [UNIVERSE addMessage:message forCount:time]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // endScenario(scenario : String) static JSBool PlayerEndScenario(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *scenario = nil; if (argc > 0) scenario = OOStringFromJSValue(context, OOJS_ARGV[0]); if (scenario == nil) { OOJSReportBadArguments(context, @"Player", @"endScenario", argc, OOJS_ARGV, nil, @"scenario key"); return NO; } OOJS_RETURN_BOOL([PLAYER endScenario:scenario]); OOJS_NATIVE_EXIT } // increaseContractReputation() static JSBool PlayerIncreaseContractReputation(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) [OOPlayerForScripting() increaseContractReputation:1]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // decreaseContractReputation() static JSBool PlayerDecreaseContractReputation(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) [OOPlayerForScripting() decreaseContractReputation:1]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // increaseParcelReputation() static JSBool PlayerIncreaseParcelReputation(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) [OOPlayerForScripting() increaseParcelReputation:1]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // decreaseParcelReputation() static JSBool PlayerDecreaseParcelReputation(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) [OOPlayerForScripting() decreaseParcelReputation:1]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // increasePassengerReputation() static JSBool PlayerIncreasePassengerReputation(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) [OOPlayerForScripting() increasePassengerReputation:1]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // decreasePassengerReputation() static JSBool PlayerDecreasePassengerReputation(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) [OOPlayerForScripting() decreasePassengerReputation:1]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // addMessageToArrivalReport(message : String) static JSBool PlayerAddMessageToArrivalReport(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *report = nil; PlayerEntity *player = OOPlayerForScripting(); if (argc > 0) report = OOStringFromJSValue(context, OOJS_ARGV[0]); if (report == nil) { OOJSReportBadArguments(context, @"Player", @"addMessageToArrivalReport", MIN(argc, 1U), OOJS_ARGV, nil, @"string (arrival message)"); return NO; } [player addMessageToReport:report]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // replaceShip (shipyard-key : String) static JSBool PlayerReplaceShip(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *shipKey = nil; PlayerEntity *player = OOPlayerForScripting(); BOOL success = NO; int personality = 0; if (argc > 0) shipKey = OOStringFromJSValue(context, OOJS_ARGV[0]); if (shipKey == nil) { OOJSReportBadArguments(context, @"Player", @"replaceShip", MIN(argc, 1U), OOJS_ARGV, nil, @"string (shipyard key)"); return NO; } if (EXPECT_NOT(!([player status] == STATUS_DOCKED))) { OOJSReportError(context, @"Player.replaceShip() only works while the player is docked."); return NO; } success = [player buyNamedShip:shipKey]; if (argc > 1) { JS_ValueToInt32(context,OOJS_ARGV[1],&personality); if (personality >= 0 && (uint16_t)personality < ENTITY_PERSONALITY_MAX) { [player setEntityPersonalityInt:(uint16_t)personality]; } } if (success) { // slightly misnamed world event now [player doScriptEvent:OOJSID("playerBoughtNewShip") withArgument:player andArgument:[NSNumber numberWithInt:0]]; } OOJS_RETURN_BOOL(success); OOJS_NATIVE_EXIT } // setEscapePodDestination(Entity | 'NEARBY_SYSTEM') static JSBool PlayerSetEscapePodDestination(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) if (EXPECT_NOT(!OOIsPlayerStale())) { OOJSReportError(context, @"Player.setEscapePodDestination() only works while the escape pod is in flight."); return NO; } BOOL OK = NO; id destValue = nil; PlayerEntity *player = OOPlayerForScripting(); if (argc == 1) { destValue = OOJSNativeObjectFromJSValue(context, OOJS_ARGV[0]); if (destValue == nil) { [player setDockTarget:NULL]; OK = YES; } else if ([destValue isKindOfClass:[ShipEntity class]] && [destValue isStation]) { [player setDockTarget:destValue]; OK = YES; } else if ([destValue isKindOfClass:[NSString class]]) { if ([destValue isEqualToString:@"NEARBY_SYSTEM"]) { // find the nearest system with a main station, or die in the attempt! [player setDockTarget:NULL]; double rescueRange = MAX_JUMP_RANGE; // reach at least 1 other system! if ([UNIVERSE inInterstellarSpace]) { // Set 3.5 ly as the limit, enough to reach at least 2 systems! rescueRange = MAX_JUMP_RANGE / 2.0; } NSMutableArray *sDests = [UNIVERSE nearbyDestinationsWithinRange:rescueRange]; NSUInteger i = 0, nDests = [sDests count]; if (nDests > 0) for (i = --nDests; i > 0; i--) { if ([[sDests oo_dictionaryAtIndex:i] oo_boolForKey:@"nova"]) { [sDests removeObjectAtIndex:i]; } } // i is back to 0, nDests could have changed... nDests = [sDests count]; if (nDests > 0) // we have a system with a main station! { if (nDests > 1) i = ranrot_rand() % nDests; // any nearby system will do. NSDictionary *dest = [sDests objectAtIndex:i]; // add more time until rescue, with overheads for entering witchspace in case of overlapping systems. double dist = [dest oo_doubleForKey:@"distance"]; [player addToAdjustTime:(.2 + dist * dist) * 3600.0 + 5400.0 * (ranrot_rand() & 127)]; // at the end of the docking sequence we'll check if the target system is the same as the system we're in... [player setTargetSystemID:i]; } OK = YES; } } else { JSBool bValue; if (JS_ValueToBoolean(context, OOJS_ARGV[0], &bValue) && bValue == NO) { [player setDockTarget:NULL]; OK = YES; } } } if (OK == NO) { OOJSReportBadArguments(context, @"Player", @"setEscapePodDestination", argc, OOJS_ARGV, nil, @"a valid station, null, or 'NEARBY_SYSTEM'"); } return OK; OOJS_NATIVE_EXIT } // setPlayerRole (role-key : String [, index : Number]) static JSBool PlayerSetPlayerRole(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *role = nil; PlayerEntity *player = OOPlayerForScripting(); uint32 index = 0; if (argc > 0) role = OOStringFromJSValue(context, OOJS_ARGV[0]); if (role == nil) { OOJSReportBadArguments(context, @"Player", @"setPlayerRole", MIN(argc, 1U), OOJS_ARGV, nil, @"string (role) [, number (index)]"); return NO; } if (argc > 1) { if (JS_ValueToECMAUint32(context,OOJS_ARGV[1],&index)) { [player addRoleToPlayer:role inSlot:index]; return YES; } } [player addRoleToPlayer:role]; return YES; OOJS_NATIVE_EXIT } oolite-1.82/src/Core/Scripting/OOJSPlayerShip.h000066400000000000000000000026161256642440500213210ustar00rootroot00000000000000/* OOJSPlayerShip.h JavaScript proxy for the player's ship. While the player and player's ship are not differentiated in Oolite, such a separation makes more sense conceptually and design-wise, and we might want to make it that way in the future. The scripting interface anticipates this by using two separate objects for the player and player's ship. The -javaScriptValue of the PlayerEntity is the player's ship. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #include @class PlayerEntity; void InitOOJSPlayerShip(JSContext *context, JSObject *global); JSClass *JSPlayerShipClass(void); JSObject *JSPlayerShipPrototype(void); JSObject *JSPlayerShipObject(void); oolite-1.82/src/Core/Scripting/OOJSPlayerShip.m000066400000000000000000001357601256642440500213350ustar00rootroot00000000000000/* OOJSPlayerShip.h Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCollectionExtractors.h" #import "OOJSPlayer.h" #import "OOJSEntity.h" #import "OOJSShip.h" #import "OOJSVector.h" #import "OOJSQuaternion.h" #import "OOJavaScriptEngine.h" #import "EntityOOJavaScriptExtensions.h" #import "OOSystemDescriptionManager.h" #import "PlayerEntity.h" #import "PlayerEntityControls.h" #import "PlayerEntityContracts.h" #import "PlayerEntityScriptMethods.h" #import "PlayerEntityLegacyScriptEngine.h" #import "HeadUpDisplay.h" #import "StationEntity.h" #import "OOConstToJSString.h" #import "OOConstToString.h" #import "OOFunctionAttributes.h" #import "OOEquipmentType.h" #import "OOJSEquipmentInfo.h" static JSObject *sPlayerShipPrototype; static JSObject *sPlayerShipObject; static JSBool PlayerShipGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool PlayerShipSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); static JSBool PlayerShipLaunch(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerShipRemoveAllCargo(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerShipUseSpecialCargo(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerShipEngageAutopilotToStation(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerShipDisengageAutopilot(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerShipAwardEquipmentToCurrentPylon(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerShipAddPassenger(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerShipRemovePassenger(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerShipAddParcel(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerShipRemoveParcel(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerShipAwardContract(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerShipRemoveContract(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerShipSetCustomView(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerShipResetCustomView(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerShipResetScannerZoom(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerShipTakeInternalDamage(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerShipBeginHyperspaceCountdown(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerShipCancelHyperspaceCountdown(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerShipSetMultiFunctionDisplay(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerShipSetMultiFunctionText(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerShipHideHUDSelector(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerShipShowHUDSelector(JSContext *context, uintN argc, jsval *vp); static JSBool PlayerShipSetCustomHUDDial(JSContext *context, uintN argc, jsval *vp); static BOOL ValidateContracts(JSContext *context, uintN argc, jsval *vp, BOOL isCargo, OOSystemID *start, OOSystemID *destination, double *eta, double *fee, double *premium, NSString *functionName, unsigned *risk); static JSClass sPlayerShipClass = { "PlayerShip", JSCLASS_HAS_PRIVATE, JS_PropertyStub, // addProperty JS_PropertyStub, // delProperty PlayerShipGetProperty, // getProperty PlayerShipSetProperty, // setProperty JS_EnumerateStub, // enumerate JS_ResolveStub, // resolve JS_ConvertStub, // convert OOJSObjectWrapperFinalize,// finalize JSCLASS_NO_OPTIONAL_MEMBERS }; enum { // Property IDs kPlayerShip_aftShield, // aft shield charge level, nonnegative float, read/write kPlayerShip_aftShieldRechargeRate, // aft shield recharge rate, positive float, read-only kPlayerShip_compassMode, // compass mode, string, read-only kPlayerShip_compassTarget, // object targeted by the compass, entity, read-only kPlayerShip_currentWeapon, // shortcut property to _aftWeapon, etc. overrides kShip generic version kPlayerShip_crosshairs, // custom plist file defining crosshairs kPlayerShip_cursorCoordinates, // cursor coordinates (unscaled), Vector3D, read only kPlayerShip_cursorCoordinatesInLY, // cursor coordinates (in LY), Vector3D, read only kPlayerShip_docked, // docked, boolean, read-only kPlayerShip_dockedStation, // docked station, entity, read-only kPlayerShip_fastEquipmentA, // fast equipment A, string, read/write kPlayerShip_fastEquipmentB, // fast equipment B, string, read/write kPlayerShip_forwardShield, // forward shield charge level, nonnegative float, read/write kPlayerShip_forwardShieldRechargeRate, // forward shield recharge rate, positive float, read-only kPlayerShip_fuelLeakRate, // fuel leak rate, float, read/write kPlayerShip_galacticHyperspaceBehaviour, // can be standard, all systems reachable or fixed coordinates, integer, read-only kPlayerShip_galacticHyperspaceFixedCoords, // used when fixed coords behaviour is selected, Vector3D, read/write kPlayerShip_galacticHyperspaceFixedCoordsInLY, // used when fixed coords behaviour is selected, Vector3D, read/write kPlayerShip_galaxyCoordinates, // galaxy coordinates (unscaled), Vector3D, read only kPlayerShip_galaxyCoordinatesInLY, // galaxy coordinates (in LY), Vector3D, read only kPlayerShip_hud, // hud name identifier, string, read/write kPlayerShip_hudHidden, // hud visibility, boolean, read/write kPlayerShip_injectorsEngaged, // injectors in use, boolean, read-only kPlayerShip_maxAftShield, // maximum aft shield charge level, positive float, read-only kPlayerShip_maxForwardShield, // maximum forward shield charge level, positive float, read-only kPlayerShip_multiFunctionDisplays, // mfd count, positive int, read-only kPlayerShip_multiFunctionDisplayList, // active mfd list, array, read-only kPlayerShip_missilesOnline, // bool (false for ident mode, true for missile mode) kPlayerShip_pitch, // pitch (overrules Ship) kPlayerShip_price, // idealised trade-in value decicredits, positive int, read-only kPlayerShip_renovationCost, // int read-only current renovation cost kPlayerShip_renovationMultiplier, // float read-only multiplier for renovation costs kPlayerShip_reticleTargetSensitive, // target box changes color when primary target in crosshairs, boolean, read/write kPlayerShip_roll, // roll (overrules Ship) kPlayerShip_routeMode, // ANA mode kPlayerShip_scannerNonLinear, // non linear scanner setting, boolean, read/write kPlayerShip_scannerUltraZoom, // scanner zoom in powers of 2, boolean, read/write kPlayerShip_scoopOverride, // Scooping kPlayerShip_serviceLevel, // servicing level, positive int 75-100, read-only kPlayerShip_specialCargo, // special cargo, string, read-only kPlayerShip_targetSystem, // target system id, int, read-write kPlayerShip_torusEngaged, // torus in use, boolean, read-only kPlayerShip_viewDirection, // view direction identifier, string, read-only kPlayerShip_viewPositionAft, // view position offset, vector, read-only kPlayerShip_viewPositionForward, // view position offset, vector, read-only kPlayerShip_viewPositionPort, // view position offset, vector, read-only kPlayerShip_viewPositionStarboard, // view position offset, vector, read-only kPlayerShip_weaponsOnline, // weapons online status, boolean, read-only kPlayerShip_yaw, // yaw (overrules Ship) }; static JSPropertySpec sPlayerShipProperties[] = { // JS name ID flags { "aftShield", kPlayerShip_aftShield, OOJS_PROP_READWRITE_CB }, { "aftShieldRechargeRate", kPlayerShip_aftShieldRechargeRate, OOJS_PROP_READWRITE_CB }, { "compassMode", kPlayerShip_compassMode, OOJS_PROP_READONLY_CB }, { "compassTarget", kPlayerShip_compassTarget, OOJS_PROP_READONLY_CB }, { "currentWeapon", kPlayerShip_currentWeapon, OOJS_PROP_READWRITE_CB }, { "crosshairs", kPlayerShip_crosshairs, OOJS_PROP_READWRITE_CB }, { "cursorCoordinates", kPlayerShip_cursorCoordinates, OOJS_PROP_READONLY_CB }, { "cursorCoordinatesInLY", kPlayerShip_cursorCoordinatesInLY, OOJS_PROP_READONLY_CB }, { "docked", kPlayerShip_docked, OOJS_PROP_READONLY_CB }, { "dockedStation", kPlayerShip_dockedStation, OOJS_PROP_READONLY_CB }, { "fastEquipmentA", kPlayerShip_fastEquipmentA, OOJS_PROP_READWRITE_CB }, { "fastEquipmentB", kPlayerShip_fastEquipmentB, OOJS_PROP_READWRITE_CB }, { "forwardShield", kPlayerShip_forwardShield, OOJS_PROP_READWRITE_CB }, { "forwardShieldRechargeRate", kPlayerShip_forwardShieldRechargeRate, OOJS_PROP_READWRITE_CB }, { "fuelLeakRate", kPlayerShip_fuelLeakRate, OOJS_PROP_READWRITE_CB }, { "galacticHyperspaceBehaviour", kPlayerShip_galacticHyperspaceBehaviour, OOJS_PROP_READWRITE_CB }, { "galacticHyperspaceFixedCoords", kPlayerShip_galacticHyperspaceFixedCoords, OOJS_PROP_READWRITE_CB }, { "galacticHyperspaceFixedCoordsInLY", kPlayerShip_galacticHyperspaceFixedCoordsInLY, OOJS_PROP_READWRITE_CB }, { "galaxyCoordinates", kPlayerShip_galaxyCoordinates, OOJS_PROP_READONLY_CB }, { "galaxyCoordinatesInLY", kPlayerShip_galaxyCoordinatesInLY, OOJS_PROP_READONLY_CB }, { "hud", kPlayerShip_hud, OOJS_PROP_READWRITE_CB }, { "hudHidden", kPlayerShip_hudHidden, OOJS_PROP_READWRITE_CB }, { "injectorsEngaged", kPlayerShip_injectorsEngaged, OOJS_PROP_READONLY_CB }, // manifest defined in OOJSManifest.m { "maxAftShield", kPlayerShip_maxAftShield, OOJS_PROP_READWRITE_CB }, { "maxForwardShield", kPlayerShip_maxForwardShield, OOJS_PROP_READWRITE_CB }, { "missilesOnline", kPlayerShip_missilesOnline, OOJS_PROP_READONLY_CB }, { "multiFunctionDisplays", kPlayerShip_multiFunctionDisplays, OOJS_PROP_READONLY_CB }, { "multiFunctionDisplayList", kPlayerShip_multiFunctionDisplayList, OOJS_PROP_READONLY_CB }, { "price", kPlayerShip_price, OOJS_PROP_READONLY_CB }, { "pitch", kPlayerShip_pitch, OOJS_PROP_READONLY_CB }, { "renovationCost", kPlayerShip_renovationCost, OOJS_PROP_READONLY_CB }, { "renovationMultiplier", kPlayerShip_renovationMultiplier, OOJS_PROP_READONLY_CB }, { "reticleTargetSensitive", kPlayerShip_reticleTargetSensitive, OOJS_PROP_READWRITE_CB }, { "roll", kPlayerShip_roll, OOJS_PROP_READONLY_CB }, { "routeMode", kPlayerShip_routeMode, OOJS_PROP_READONLY_CB }, { "scannerNonLinear", kPlayerShip_scannerNonLinear, OOJS_PROP_READWRITE_CB }, { "scannerUltraZoom", kPlayerShip_scannerUltraZoom, OOJS_PROP_READWRITE_CB }, { "scoopOverride", kPlayerShip_scoopOverride, OOJS_PROP_READWRITE_CB }, { "serviceLevel", kPlayerShip_serviceLevel, OOJS_PROP_READWRITE_CB }, { "specialCargo", kPlayerShip_specialCargo, OOJS_PROP_READONLY_CB }, { "targetSystem", kPlayerShip_targetSystem, OOJS_PROP_READWRITE_CB }, { "torusEngaged", kPlayerShip_torusEngaged, OOJS_PROP_READONLY_CB }, { "viewDirection", kPlayerShip_viewDirection, OOJS_PROP_READONLY_CB }, { "viewPositionAft", kPlayerShip_viewPositionAft, OOJS_PROP_READONLY_CB }, { "viewPositionForward", kPlayerShip_viewPositionForward, OOJS_PROP_READONLY_CB }, { "viewPositionPort", kPlayerShip_viewPositionPort, OOJS_PROP_READONLY_CB }, { "viewPositionStarboard", kPlayerShip_viewPositionStarboard, OOJS_PROP_READONLY_CB }, { "weaponsOnline", kPlayerShip_weaponsOnline, OOJS_PROP_READONLY_CB }, { "yaw", kPlayerShip_yaw, OOJS_PROP_READONLY_CB }, { 0 } }; static JSFunctionSpec sPlayerShipMethods[] = { // JS name Function min args { "addParcel", PlayerShipAddParcel, 0 }, { "addPassenger", PlayerShipAddPassenger, 0 }, { "awardContract", PlayerShipAwardContract, 0 }, { "awardEquipmentToCurrentPylon", PlayerShipAwardEquipmentToCurrentPylon, 1 }, { "beginHyperspaceCountdown", PlayerShipBeginHyperspaceCountdown, 0 }, { "cancelHyperspaceCountdown", PlayerShipCancelHyperspaceCountdown, 0 }, { "disengageAutopilot", PlayerShipDisengageAutopilot, 0 }, { "engageAutopilotToStation", PlayerShipEngageAutopilotToStation, 1 }, { "hideHUDSelector", PlayerShipHideHUDSelector, 1 }, { "launch", PlayerShipLaunch, 0 }, { "removeAllCargo", PlayerShipRemoveAllCargo, 0 }, { "removeContract", PlayerShipRemoveContract, 2 }, { "removeParcel", PlayerShipRemoveParcel, 1 }, { "removePassenger", PlayerShipRemovePassenger, 1 }, { "resetCustomView", PlayerShipResetCustomView, 0 }, { "resetScannerZoom", PlayerShipResetScannerZoom, 0 }, { "setCustomView", PlayerShipSetCustomView, 2 }, { "setCustomHUDDial", PlayerShipSetCustomHUDDial, 2 }, { "setMultiFunctionDisplay", PlayerShipSetMultiFunctionDisplay, 1 }, { "setMultiFunctionText", PlayerShipSetMultiFunctionText, 1 }, { "showHUDSelector", PlayerShipShowHUDSelector, 1 }, { "takeInternalDamage", PlayerShipTakeInternalDamage, 0 }, { "useSpecialCargo", PlayerShipUseSpecialCargo, 1 }, { 0 } }; void InitOOJSPlayerShip(JSContext *context, JSObject *global) { sPlayerShipPrototype = JS_InitClass(context, global, JSShipPrototype(), &sPlayerShipClass, OOJSUnconstructableConstruct, 0, sPlayerShipProperties, sPlayerShipMethods, NULL, NULL); OOJSRegisterObjectConverter(&sPlayerShipClass, OOJSBasicPrivateObjectConverter); OOJSRegisterSubclass(&sPlayerShipClass, JSShipClass()); PlayerEntity *player = [PlayerEntity sharedPlayer]; // NOTE: at time of writing, this creates the player entity. Don't use PLAYER here. // Create ship object as a property of the player object. sPlayerShipObject = JS_DefineObject(context, JSPlayerObject(), "ship", &sPlayerShipClass, sPlayerShipPrototype, OOJS_PROP_READONLY); JS_SetPrivate(context, sPlayerShipObject, OOConsumeReference([player weakRetain])); [player setJSSelf:sPlayerShipObject context:context]; // Analyzer: object leaked. [Expected, object is retained by JS object.] } JSClass *JSPlayerShipClass(void) { return &sPlayerShipClass; } JSObject *JSPlayerShipPrototype(void) { return sPlayerShipPrototype; } JSObject *JSPlayerShipObject(void) { return sPlayerShipObject; } @implementation PlayerEntity (OOJavaScriptExtensions) - (NSString *) oo_jsClassName { return @"PlayerShip"; } - (void) setJSSelf:(JSObject *)val context:(JSContext *)context { _jsSelf = val; OOJSAddGCObjectRoot(context, &_jsSelf, "Player jsSelf"); [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(javaScriptEngineWillReset:) name:kOOJavaScriptEngineWillResetNotification object:[OOJavaScriptEngine sharedEngine]]; } - (void) javaScriptEngineWillReset:(NSNotification *)notification { [[NSNotificationCenter defaultCenter] removeObserver:self name:kOOJavaScriptEngineWillResetNotification object:[OOJavaScriptEngine sharedEngine]]; if (_jsSelf != NULL) { JSContext *context = OOJSAcquireContext(); JS_RemoveObjectRoot(context, &_jsSelf); _jsSelf = NULL; OOJSRelinquishContext(context); } } @end static JSBool PlayerShipGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) if (EXPECT_NOT(OOIsPlayerStale() || this == sPlayerShipPrototype)) { *value = JSVAL_VOID; return YES; } id result = nil; PlayerEntity *player = OOPlayerForScripting(); switch (JSID_TO_INT(propID)) { case kPlayerShip_fuelLeakRate: return JS_NewNumberValue(context, [player fuelLeakRate], value); case kPlayerShip_docked: *value = OOJSValueFromBOOL([player isDocked]); return YES; case kPlayerShip_dockedStation: result = [player dockedStation]; break; case kPlayerShip_specialCargo: result = [player specialCargo]; break; case kPlayerShip_reticleTargetSensitive: *value = OOJSValueFromBOOL([[player hud] reticleTargetSensitive]); return YES; case kPlayerShip_galacticHyperspaceBehaviour: *value = OOJSValueFromGalacticHyperspaceBehaviour(context, [player galacticHyperspaceBehaviour]); return YES; case kPlayerShip_galacticHyperspaceFixedCoords: return NSPointToVectorJSValue(context, [player galacticHyperspaceFixedCoords], value); case kPlayerShip_galacticHyperspaceFixedCoordsInLY: return VectorToJSValue(context, OOGalacticCoordinatesFromInternal([player galacticHyperspaceFixedCoords]), value); case kPlayerShip_fastEquipmentA: result = [player fastEquipmentA]; break; case kPlayerShip_fastEquipmentB: result = [player fastEquipmentB]; break; case kPlayerShip_forwardShield: return JS_NewNumberValue(context, [player forwardShieldLevel], value); case kPlayerShip_aftShield: return JS_NewNumberValue(context, [player aftShieldLevel], value); case kPlayerShip_maxForwardShield: return JS_NewNumberValue(context, [player maxForwardShieldLevel], value); case kPlayerShip_maxAftShield: return JS_NewNumberValue(context, [player maxAftShieldLevel], value); case kPlayerShip_forwardShieldRechargeRate: // No distinction made internally return JS_NewNumberValue(context, [player forwardShieldRechargeRate], value); case kPlayerShip_aftShieldRechargeRate: // No distinction made internally return JS_NewNumberValue(context, [player aftShieldRechargeRate], value); case kPlayerShip_multiFunctionDisplays: return JS_NewNumberValue(context, [[player hud] mfdCount], value); case kPlayerShip_multiFunctionDisplayList: result = [player multiFunctionDisplayList]; break; case kPlayerShip_missilesOnline: *value = OOJSValueFromBOOL(![player dialIdentEngaged]); return YES; case kPlayerShip_galaxyCoordinates: return NSPointToVectorJSValue(context, [player galaxy_coordinates], value); case kPlayerShip_galaxyCoordinatesInLY: return VectorToJSValue(context, OOGalacticCoordinatesFromInternal([player galaxy_coordinates]), value); case kPlayerShip_cursorCoordinates: return NSPointToVectorJSValue(context, [player cursor_coordinates], value); case kPlayerShip_cursorCoordinatesInLY: return VectorToJSValue(context, OOGalacticCoordinatesFromInternal([player cursor_coordinates]), value); case kPlayerShip_targetSystem: *value = INT_TO_JSVAL([player targetSystemID]); return YES; case kPlayerShip_routeMode: { OORouteType route = [player ANAMode]; switch (route) { case OPTIMIZED_BY_TIME: result = @"OPTIMIZED_BY_TIME"; break; case OPTIMIZED_BY_JUMPS: result = @"OPTIMIZED_BY_JUMPS"; break; case OPTIMIZED_BY_NONE: result = @"OPTIMIZED_BY_NONE"; break; } break; } case kPlayerShip_scannerNonLinear: *value = OOJSValueFromBOOL([[player hud] nonlinearScanner]); return YES; case kPlayerShip_scannerUltraZoom: *value = OOJSValueFromBOOL([[player hud] scannerUltraZoom]); return YES; case kPlayerShip_scoopOverride: *value = OOJSValueFromBOOL([player scoopOverride]); return YES; case kPlayerShip_injectorsEngaged: *value = OOJSValueFromBOOL([player injectorsEngaged]); return YES; case kPlayerShip_torusEngaged: *value = OOJSValueFromBOOL([player hyperspeedEngaged]); return YES; case kPlayerShip_compassTarget: result = [player compassTarget]; break; case kPlayerShip_compassMode: *value = OOJSValueFromCompassMode(context, [player compassMode]); return YES; case kPlayerShip_hud: result = [[player hud] hudName]; break; case kPlayerShip_crosshairs: result = [[player hud] crosshairDefinition]; break; case kPlayerShip_hudHidden: *value = OOJSValueFromBOOL([[player hud] isHidden]); return YES; case kPlayerShip_weaponsOnline: *value = OOJSValueFromBOOL([player weaponsOnline]); return YES; case kPlayerShip_viewDirection: *value = OOJSValueFromViewID(context, [UNIVERSE viewDirection]); return YES; case kPlayerShip_viewPositionAft: return VectorToJSValue(context, [player viewpointOffsetAft], value); case kPlayerShip_viewPositionForward: return VectorToJSValue(context, [player viewpointOffsetForward], value); case kPlayerShip_viewPositionPort: return VectorToJSValue(context, [player viewpointOffsetPort], value); case kPlayerShip_viewPositionStarboard: return VectorToJSValue(context, [player viewpointOffsetStarboard], value); case kPlayerShip_currentWeapon: result = [player weaponTypeForFacing:[player currentWeaponFacing] strict:NO]; break; case kPlayerShip_price: return JS_NewNumberValue(context, [UNIVERSE tradeInValueForCommanderDictionary:[player commanderDataDictionary]], value); case kPlayerShip_serviceLevel: return JS_NewNumberValue(context, [player tradeInFactor], value); case kPlayerShip_renovationCost: return JS_NewNumberValue(context, [player renovationCosts], value); case kPlayerShip_renovationMultiplier: return JS_NewNumberValue(context, [player renovationFactor], value); // make roll, pitch, yaw reported to JS use same +/- convention as // for NPC ships case kPlayerShip_pitch: return JS_NewNumberValue(context, -[player flightPitch], value); case kPlayerShip_roll: return JS_NewNumberValue(context, -[player flightRoll], value); case kPlayerShip_yaw: return JS_NewNumberValue(context, -[player flightYaw], value); default: OOJSReportBadPropertySelector(context, this, propID, sPlayerShipProperties); } *value = OOJSValueFromNativeObject(context, result); return YES; OOJS_NATIVE_EXIT } static JSBool PlayerShipSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) if (EXPECT_NOT(OOIsPlayerStale())) return YES; PlayerEntity *player = OOPlayerForScripting(); jsdouble fValue; JSBool bValue; int32 iValue; NSString *sValue = nil; OOGalacticHyperspaceBehaviour ghBehaviour; Vector vValue; switch (JSID_TO_INT(propID)) { case kPlayerShip_fuelLeakRate: if (JS_ValueToNumber(context, *value, &fValue)) { [player setFuelLeakRate:fValue]; return YES; } break; case kPlayerShip_reticleTargetSensitive: if (JS_ValueToBoolean(context, *value, &bValue)) { [[player hud] setReticleTargetSensitive:bValue]; return YES; } break; case kPlayerShip_galacticHyperspaceBehaviour: ghBehaviour = OOGalacticHyperspaceBehaviourFromJSValue(context, *value); if (ghBehaviour != GALACTIC_HYPERSPACE_BEHAVIOUR_UNKNOWN) { [player setGalacticHyperspaceBehaviour:ghBehaviour]; return YES; } break; case kPlayerShip_galacticHyperspaceFixedCoords: if (JSValueToVector(context, *value, &vValue)) { NSPoint coords = { vValue.x, vValue.y }; [player setGalacticHyperspaceFixedCoords:coords]; return YES; } break; case kPlayerShip_galacticHyperspaceFixedCoordsInLY: if (JSValueToVector(context, *value, &vValue)) { NSPoint coords = OOInternalCoordinatesFromGalactic(vValue); [player setGalacticHyperspaceFixedCoords:coords]; return YES; } break; case kPlayerShip_fastEquipmentA: sValue = OOStringFromJSValue(context, *value); if (sValue != nil) { [player setFastEquipmentA:sValue]; return YES; } break; case kPlayerShip_fastEquipmentB: sValue = OOStringFromJSValue(context, *value); if (sValue != nil) { [player setFastEquipmentB:sValue]; return YES; } break; case kPlayerShip_forwardShield: if (JS_ValueToNumber(context, *value, &fValue)) { [player setForwardShieldLevel:fValue]; return YES; } break; case kPlayerShip_aftShield: if (JS_ValueToNumber(context, *value, &fValue)) { [player setAftShieldLevel:fValue]; return YES; } break; case kPlayerShip_maxForwardShield: if (JS_ValueToNumber(context, *value, &fValue)) { [player setMaxForwardShieldLevel:fValue]; return YES; } break; case kPlayerShip_maxAftShield: if (JS_ValueToNumber(context, *value, &fValue)) { [player setMaxAftShieldLevel:fValue]; return YES; } break; case kPlayerShip_forwardShieldRechargeRate: if (JS_ValueToNumber(context, *value, &fValue)) { [player setForwardShieldRechargeRate:fValue]; return YES; } break; case kPlayerShip_aftShieldRechargeRate: if (JS_ValueToNumber(context, *value, &fValue)) { [player setAftShieldRechargeRate:fValue]; return YES; } break; case kPlayerShip_scannerNonLinear: if (JS_ValueToBoolean(context, *value, &bValue)) { [[player hud] setNonlinearScanner:bValue]; return YES; } break; case kPlayerShip_scannerUltraZoom: if (JS_ValueToBoolean(context, *value, &bValue)) { [[player hud] setScannerUltraZoom:bValue]; return YES; } break; case kPlayerShip_scoopOverride: if (JS_ValueToBoolean(context, *value, &bValue)) { [player setScoopOverride:bValue]; return YES; } break; case kPlayerShip_hud: sValue = OOStringFromJSValue(context, *value); if (sValue != nil) { [player switchHudTo:sValue]; // EMMSTRAN: logged error should be a JS warning. return YES; } else { [player resetHud]; return YES; } break; case kPlayerShip_crosshairs: sValue = OOStringFromJSValue(context, *value); if (sValue == nil) { // reset HUD back to its plist settings NSString *hud = [[[player hud] hudName] retain]; [player switchHudTo:hud]; [hud release]; return YES; } else { if (![[player hud] setCrosshairDefinition:sValue]) { OOJSReportWarning(context, @"Crosshair definition file %@ not found or invalid", sValue); } return YES; } break; case kPlayerShip_hudHidden: if (JS_ValueToBoolean(context, *value, &bValue)) { [[player hud] setHidden:bValue]; return YES; } break; case kPlayerShip_serviceLevel: if (JS_ValueToNumber(context, *value, &fValue)) { int newLevel = (int)fValue; [player adjustTradeInFactorBy:(newLevel-[player tradeInFactor])]; return YES; } case kPlayerShip_currentWeapon: { BOOL exists = NO; sValue = JSValueToEquipmentKeyRelaxed(context, *value, &exists); if (!exists || sValue == nil) { sValue = @"EQ_WEAPON_NONE"; } [player setWeaponMount:[player currentWeaponFacing] toWeapon:sValue]; return YES; } case kPlayerShip_targetSystem: /* This first check is essential: if removed, it would be * possible to make jumps of arbitrary length - CIM */ if ([player status] != STATUS_ENTERING_WITCHSPACE) { /* These checks though similar are less important. The * consequences of allowing jump destination to be set in * flight are not as severe and do not allow the 7LY limit to * be broken. Nevertheless, it is not allowed. - CIM */ // (except when compiled in debugging mode) #ifndef OO_DUMP_PLANETINFO if (EXPECT_NOT([player status] != STATUS_DOCKED && [player status] != STATUS_LAUNCHING)) { OOJSReportError(context, @"player.ship.targetSystem is read-only unless called when docked."); return NO; } #endif if (JS_ValueToInt32(context, *value, &iValue)) { if (iValue >= 0 && iValue < OO_SYSTEMS_PER_GALAXY) { [player setTargetSystemID:iValue]; return YES; } else { return NO; } } } else { OOJSReportError(context, @"player.ship.targetSystem is read-only unless called when docked."); return NO; } default: OOJSReportBadPropertySelector(context, this, propID, sPlayerShipProperties); return NO; } OOJSReportBadPropertyValue(context, this, propID, sPlayerShipProperties, *value); return NO; OOJS_NATIVE_EXIT } // *** Methods *** // launch() static JSBool PlayerShipLaunch(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) if (EXPECT_NOT(OOIsPlayerStale())) OOJS_RETURN_VOID; [OOPlayerForScripting() launchFromStation]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // removeAllCargo() static JSBool PlayerShipRemoveAllCargo(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) if (EXPECT_NOT(OOIsPlayerStale())) OOJS_RETURN_VOID; PlayerEntity *player = OOPlayerForScripting(); if ([player isDocked]) { [player removeAllCargo]; OOJS_RETURN_VOID; } else { OOJSReportError(context, @"PlayerShip.removeAllCargo only works when docked."); return NO; } OOJS_NATIVE_EXIT } // useSpecialCargo(name : String) static JSBool PlayerShipUseSpecialCargo(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) if (EXPECT_NOT(OOIsPlayerStale())) OOJS_RETURN_VOID; PlayerEntity *player = OOPlayerForScripting(); NSString *name = nil; if (argc > 0) name = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(name == nil)) { OOJSReportBadArguments(context, @"PlayerShip", @"useSpecialCargo", MIN(argc, 1U), OOJS_ARGV, nil, @"string (special cargo description)"); return NO; } [player useSpecialCargo:OOStringFromJSValue(context, OOJS_ARGV[0])]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // engageAutopilotToStation(stationForDocking : Station) : Boolean static JSBool PlayerShipEngageAutopilotToStation(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) if (EXPECT_NOT(OOIsPlayerStale())) OOJS_RETURN_VOID; PlayerEntity *player = OOPlayerForScripting(); StationEntity *stationForDocking = nil; if (argc > 0) stationForDocking = OOJSNativeObjectOfClassFromJSValue(context, OOJS_ARGV[0], [StationEntity class]); if (stationForDocking == nil) { OOJSReportBadArguments(context, @"PlayerShip", @"engageAutopilot", MIN(argc, 1U), OOJS_ARGV, nil, @"station"); return NO; } OOJS_RETURN_BOOL([player engageAutopilotToStation:stationForDocking]); OOJS_NATIVE_EXIT } // disengageAutopilot() static JSBool PlayerShipDisengageAutopilot(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) if (EXPECT_NOT(OOIsPlayerStale())) OOJS_RETURN_VOID; [OOPlayerForScripting() disengageAutopilot]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // awardEquipmentToCurrentPylon(externalTank: equipmentInfoExpression) : Boolean static JSBool PlayerShipAwardEquipmentToCurrentPylon(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) if (EXPECT_NOT(OOIsPlayerStale())) OOJS_RETURN_VOID; PlayerEntity *player = OOPlayerForScripting(); NSString *key = nil; OOEquipmentType *eqType = nil; if (argc > 0) key = JSValueToEquipmentKey(context, OOJS_ARGV[0]); if (key != nil) eqType = [OOEquipmentType equipmentTypeWithIdentifier:key]; if (EXPECT_NOT(![eqType isMissileOrMine])) { OOJSReportBadArguments(context, @"PlayerShip", @"awardEquipmentToCurrentPylon", MIN(argc, 1U), OOJS_ARGV, nil, @"equipment type (external store)"); return NO; } OOJS_RETURN_BOOL([player assignToActivePylon:key]); OOJS_NATIVE_EXIT } // addPassenger(name: string, start: int, destination: int, ETA: double, fee: double) : Boolean static JSBool PlayerShipAddPassenger(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); NSString *name = nil; OOSystemID start = 0, destination = 0; jsdouble eta = 0.0, fee = 0.0, advance = 0.0; unsigned risk = 0; if (argc < 5) { OOJSReportBadArguments(context, @"PlayerShip", @"addPassenger", argc, OOJS_ARGV, nil, @"name, start, destination, ETA, fee"); return NO; } name = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(name == nil)) { OOJSReportBadArguments(context, @"PlayerShip", @"addPassenger", 1, &OOJS_ARGV[0], nil, @"string"); return NO; } if (!ValidateContracts(context, argc, vp, NO, &start, &destination, &eta, &fee, &advance, @"addPassenger", &risk)) return NO; // always go through validate contracts (passenger) // Ensure there's space. if ([player passengerCount] >= [player passengerCapacity]) OOJS_RETURN_BOOL(NO); BOOL OK = [player addPassenger:name start:start destination:destination eta:eta fee:fee advance:advance risk:risk]; OOJS_RETURN_BOOL(OK); OOJS_NATIVE_EXIT } // removePassenger(name :string) static JSBool PlayerShipRemovePassenger(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); NSString *name = nil; BOOL OK = YES; if (argc > 0) name = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(name == nil)) { OOJSReportBadArguments(context, @"PlayerShip", @"removePassenger", MIN(argc, 1U), OOJS_ARGV, nil, @"string"); return NO; } OK = [player passengerCount] > 0 && [name length] > 0; if (OK) OK = [player removePassenger:name]; OOJS_RETURN_BOOL(OK); OOJS_NATIVE_EXIT } // addParcel(description: string, start: int, destination: int, ETA: double, fee: double) : Boolean static JSBool PlayerShipAddParcel(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); NSString *name = nil; OOSystemID start = 0, destination = 0; jsdouble eta = 0.0, fee = 0.0, premium = 0.0; unsigned risk = 0; if (argc < 5) { OOJSReportBadArguments(context, @"PlayerShip", @"addParcel", argc, OOJS_ARGV, nil, @"name, start, destination, ETA, fee"); return NO; } name = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(name == nil)) { OOJSReportBadArguments(context, @"PlayerShip", @"addParcel", 1, &OOJS_ARGV[0], nil, @"string"); return NO; } if (!ValidateContracts(context, argc, vp, NO, &start, &destination, &eta, &fee, &premium, @"addParcel", &risk)) return NO; // always go through validate contracts (passenger/parcel mode) // Ensure there's space. BOOL OK = [player addParcel:name start:start destination:destination eta:eta fee:fee premium:premium risk:risk]; OOJS_RETURN_BOOL(OK); OOJS_NATIVE_EXIT } // removeParcel(description :string) static JSBool PlayerShipRemoveParcel(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); NSString *name = nil; BOOL OK = YES; if (argc > 0) name = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(name == nil)) { OOJSReportBadArguments(context, @"PlayerShip", @"removeParcel", MIN(argc, 1U), OOJS_ARGV, nil, @"string"); return NO; } OK = [player parcelCount] > 0 && [name length] > 0; if (OK) OK = [player removeParcel:name]; OOJS_RETURN_BOOL(OK); OOJS_NATIVE_EXIT } // awardContract(quantity: int, commodity: string, start: int, destination: int, eta: double, fee: double) : Boolean static JSBool PlayerShipAwardContract(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); NSString *key = nil; int32 qty = 0; OOSystemID start = 0, destination = 0; jsdouble eta = 0.0, fee = 0.0, premium = 0.0; if (argc < 6) { OOJSReportBadArguments(context, @"PlayerShip", @"awardContract", argc, OOJS_ARGV, nil, @"quantity, commodity, start, destination, ETA, fee"); return NO; } if (!JS_ValueToInt32(context, OOJS_ARGV[0], &qty)) { OOJSReportBadArguments(context, @"PlayerShip", @"awardContract", 1, &OOJS_ARGV[0], nil, @"positive integer (cargo quantity)"); return NO; } key = OOStringFromJSValue(context, OOJS_ARGV[1]); if (EXPECT_NOT(key == nil)) { OOJSReportBadArguments(context, @"PlayerShip", @"awardContract", 1, &OOJS_ARGV[1], nil, @"string (commodity identifier)"); return NO; } if (!ValidateContracts(context, argc, vp, YES, &start, &destination, &eta, &fee, &premium, @"awardContract", NULL)) return NO; // always go through validate contracts (cargo) BOOL OK = [player awardContract:qty commodity:key start:start destination:destination eta:eta fee:fee premium:premium]; OOJS_RETURN_BOOL(OK); OOJS_NATIVE_EXIT } // removeContract(commodity: string, destination: int) static JSBool PlayerShipRemoveContract(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); NSString *key = nil; int32 dest = 0; if (argc < 2) { OOJSReportBadArguments(context, @"PlayerShip", @"removeContract", argc, OOJS_ARGV, nil, @"commodity, destination"); return NO; } key = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(key == nil)) { OOJSReportBadArguments(context, @"PlayerShip", @"removeContract", 1, &OOJS_ARGV[0], nil, @"string (commodity identifier)"); return NO; } if (!JS_ValueToInt32(context, OOJS_ARGV[1], &dest) || dest < 0 || dest > 255) { OOJSReportBadArguments(context, @"PlayerShip", @"removeContract", 1, &OOJS_ARGV[1], nil, @"system ID"); return NO; } BOOL OK = [player removeContract:key destination:(unsigned)dest]; OOJS_RETURN_BOOL(OK); OOJS_NATIVE_EXIT } // setCustomView(position:vector, orientation:quaternion [, weapon:string]) static JSBool PlayerShipSetCustomView(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); if (argc < 2) { OOJSReportBadArguments(context, @"PlayerShip", @"setCustomView", argc, OOJS_ARGV, nil, @"position, orientiation, [weapon]"); return NO; } // must be in custom view if ([UNIVERSE viewDirection] != VIEW_CUSTOM) { OOJSReportError(context, @"PlayerShip.setCustomView only works when custom view is active."); return NO; } NSMutableDictionary *viewData = [NSMutableDictionary dictionaryWithCapacity:3]; Vector position = kZeroVector; BOOL gotpos = JSValueToVector(context, OOJS_ARGV[0], &position); if (!gotpos) { OOJSReportBadArguments(context, @"PlayerShip", @"setCustomView", argc, OOJS_ARGV, nil, @"position, orientiation, [weapon]"); return NO; } NSString *positionstr = [[NSString alloc] initWithFormat:@"%f %f %f",position.x,position.y,position.z]; Quaternion orientation = kIdentityQuaternion; BOOL gotquat = JSValueToQuaternion(context, OOJS_ARGV[1], &orientation); if (!gotquat) { OOJSReportBadArguments(context, @"PlayerShip", @"setCustomView", argc, OOJS_ARGV, nil, @"position, orientiation, [weapon]"); return NO; } NSString *orientationstr = [[NSString alloc] initWithFormat:@"%f %f %f %f",orientation.w,orientation.x,orientation.y,orientation.z]; [viewData setObject:positionstr forKey:@"view_position"]; [viewData setObject:orientationstr forKey:@"view_orientation"]; if (argc > 2) { NSString* facing = OOStringFromJSValue(context,OOJS_ARGV[2]); [viewData setObject:facing forKey:@"weapon_facing"]; } [player setCustomViewDataFromDictionary:viewData withScaling:NO]; [player noteSwitchToView:VIEW_CUSTOM fromView:VIEW_CUSTOM]; [positionstr release]; [orientationstr release]; OOJS_RETURN_BOOL(YES); OOJS_NATIVE_EXIT } // resetCustomView() static JSBool PlayerShipResetCustomView(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); // must be in custom view if ([UNIVERSE viewDirection] != VIEW_CUSTOM) { OOJSReportError(context, @"PlayerShip.setCustomView only works when custom view is active."); return NO; } [player resetCustomView]; [player noteSwitchToView:VIEW_CUSTOM fromView:VIEW_CUSTOM]; OOJS_RETURN_BOOL(YES); OOJS_NATIVE_EXIT } // resetScannerZoom() static JSBool PlayerShipResetScannerZoom(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); [player resetScannerZoom]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // takeInternalDamage() static JSBool PlayerShipTakeInternalDamage(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); BOOL took = [player takeInternalDamage]; OOJS_RETURN_BOOL(took); OOJS_NATIVE_EXIT } // beginHyperspaceCountdown([int: spin_time]) static JSBool PlayerShipBeginHyperspaceCountdown(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); int32 spin_time; int32 witchspaceSpinUpTime = 0; BOOL begun = NO; if (argc < 1) { witchspaceSpinUpTime = 0; #ifdef OO_DUMP_PLANETINFO // quick intersystem jumps for debugging witchspaceSpinUpTime = 1; #endif } else { if (!JS_ValueToInt32(context, OOJS_ARGV[0], &spin_time) || spin_time < 5 || spin_time > 60) { OOJSReportBadArguments(context, @"PlayerShip", @"beginHyperspaceCountdown", 1, &OOJS_ARGV[0], nil, @"between 5 and 60 seconds"); return NO; } if (spin_time < 5) { witchspaceSpinUpTime = 5; } else { witchspaceSpinUpTime = spin_time; } } if ([player hasHyperspaceMotor] && [player status] == STATUS_IN_FLIGHT && [player witchJumpChecklist:false]) { [player beginWitchspaceCountdown:witchspaceSpinUpTime]; begun = YES; } OOJS_RETURN_BOOL(begun); OOJS_NATIVE_EXIT } // cancelHyperspaceCountdown() static JSBool PlayerShipCancelHyperspaceCountdown(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); BOOL cancelled = NO; if ([player hasHyperspaceMotor] && [player status] == STATUS_WITCHSPACE_COUNTDOWN) { [player cancelWitchspaceCountdown]; [player setJumpType:false]; cancelled = YES; } OOJS_RETURN_BOOL(cancelled); OOJS_NATIVE_EXIT } // setMultiFunctionDisplay(index,key) static JSBool PlayerShipSetMultiFunctionDisplay(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *key = nil; uint32 index = 0; PlayerEntity *player = OOPlayerForScripting(); BOOL OK = YES; if (argc > 0) { if (!JS_ValueToECMAUint32(context,OOJS_ARGV[0],&index)) { OOJSReportBadArguments(context, @"PlayerShip", @"setMultiFunctionDisplay", MIN(argc, 1U), OOJS_ARGV, nil, @"number (index) [, string (key)]"); return NO; } } if (argc > 1) { key = OOStringFromJSValue(context, OOJS_ARGV[1]); } OK = [player setMultiFunctionDisplay:index toKey:key]; OOJS_RETURN_BOOL(OK); OOJS_NATIVE_EXIT } // setMultiFunctionText(key,value) static JSBool PlayerShipSetMultiFunctionText(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *key = nil; NSString *value = nil; PlayerEntity *player = OOPlayerForScripting(); JSBool reflow = NO; if (argc > 0) { key = OOStringFromJSValue(context, OOJS_ARGV[0]); } if (key == nil) { OOJSReportBadArguments(context, @"PlayerShip", @"setMultiFunctionText", MIN(argc, 1U), OOJS_ARGV, nil, @"string (key) [, string (text)]"); return NO; } if (argc > 1) { value = OOStringFromJSValue(context, OOJS_ARGV[1]); } if (argc > 2 && EXPECT_NOT(!JS_ValueToBoolean(context, OOJS_ARGV[2], &reflow))) { OOJSReportBadArguments(context, @"setMultiFunctionText", @"reflow", argc, OOJS_ARGV, nil, @"boolean"); return NO; } if (!reflow) { [player setMultiFunctionText:value forKey:key]; } else { GuiDisplayGen *gui = [UNIVERSE gui]; NSString *formatted = [gui reflowTextForMFD:value]; [player setMultiFunctionText:formatted forKey:key]; } OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // setCustomHUDDial(key,value) static JSBool PlayerShipSetCustomHUDDial(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *key = nil; id value = nil; PlayerEntity *player = OOPlayerForScripting(); if (argc > 0) { key = OOStringFromJSValue(context, OOJS_ARGV[0]); } if (key == nil) { OOJSReportBadArguments(context, @"PlayerShip", @"setCustomHUDDial", MIN(argc, 1U), OOJS_ARGV, nil, @"string (key), value]"); return NO; } if (argc > 1) { value = OOJSNativeObjectFromJSValue(context, OOJS_ARGV[1]); } else { value = @""; } [player setDialCustom:value forKey:key]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } static JSBool PlayerShipHideHUDSelector(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *key = nil; PlayerEntity *player = OOPlayerForScripting(); if (argc > 0) { key = OOStringFromJSValue(context, OOJS_ARGV[0]); } if (key == nil) { OOJSReportBadArguments(context, @"PlayerShip", @"hideHUDSelector", MIN(argc, 1U), OOJS_ARGV, nil, @"string (selector)"); return NO; } [[player hud] setHiddenSelector:key hidden:YES]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } static JSBool PlayerShipShowHUDSelector(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *key = nil; PlayerEntity *player = OOPlayerForScripting(); if (argc > 0) { key = OOStringFromJSValue(context, OOJS_ARGV[0]); } if (key == nil) { OOJSReportBadArguments(context, @"PlayerShip", @"hideHUDSelector", MIN(argc, 1U), OOJS_ARGV, nil, @"string (selector)"); return NO; } [[player hud] setHiddenSelector:key hidden:NO]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } static BOOL ValidateContracts(JSContext *context, uintN argc, jsval *vp, BOOL isCargo, OOSystemID *start, OOSystemID *destination, double *eta, double *fee, double *premium, NSString *functionName, unsigned *risk) { OOJS_PROFILE_ENTER NSCParameterAssert(context != NULL && vp != NULL && start != NULL && destination != NULL && eta != NULL && fee != NULL); unsigned uValue, offset = isCargo ? 2 : 1; jsdouble fValue; int32 iValue; if (!JS_ValueToInt32(context, OOJS_ARGV[offset + 0], &iValue) || iValue < 0 || iValue > kOOMaximumSystemID) { OOJSReportBadArguments(context, @"PlayerShip", functionName, 1, &OOJS_ARGV[offset + 0], nil, @"system ID"); return NO; } *start = iValue; if (!JS_ValueToInt32(context, OOJS_ARGV[offset + 1], &iValue) || iValue < 0 || iValue > kOOMaximumSystemID) { OOJSReportBadArguments(context, @"PlayerShip", functionName, 1, &OOJS_ARGV[offset + 1], nil, @"system ID"); return NO; } *destination = iValue; if (!JS_ValueToNumber(context, OOJS_ARGV[offset + 2], &fValue) || !isfinite(fValue) || fValue <= [PLAYER clockTime]) { OOJSReportBadArguments(context, @"PlayerShip", functionName, 1, &OOJS_ARGV[offset + 2], nil, @"number (future time)"); return NO; } *eta = fValue; if (!JS_ValueToNumber(context, OOJS_ARGV[offset + 3], &fValue) || !isfinite(fValue) || fValue < 0.0) { OOJSReportBadArguments(context, @"PlayerShip", functionName, 1, &OOJS_ARGV[offset + 3], nil, @"number (credits quantity)"); return NO; } *fee = fValue; if (argc > offset+4 && JS_ValueToNumber(context, OOJS_ARGV[offset + 4], &fValue) && isfinite(fValue) && fValue >= 0.0) { *premium = fValue; } if (!isCargo) { if (argc > offset+5 && JS_ValueToECMAUint32(context, OOJS_ARGV[offset + 5], &uValue) && isfinite(uValue)) { *risk = uValue; } } return YES; OOJS_PROFILE_EXIT } oolite-1.82/src/Core/Scripting/OOJSPopulatorDefinition.h000066400000000000000000000022261256642440500232340ustar00rootroot00000000000000/* OOJSPopulatorDefinition.h Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSScript.h" #include #import "OOMaths.h" @interface OOJSPopulatorDefinition: OOWeakRefObject { @private jsval _callback; JSObject *_callbackThis; OOJSScript *_owningScript; } - (jsval)callback; - (void)setCallback:(jsval)callback; - (JSObject *)callbackThis; - (void)setCallbackThis:(JSObject *)callbackthis; - (void)runCallback:(HPVector)location; @end oolite-1.82/src/Core/Scripting/OOJSPopulatorDefinition.m000066400000000000000000000057651256642440500232540ustar00rootroot00000000000000/* OOJSPopulatorDefinition.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSPopulatorDefinition.h" #import "OOJavaScriptEngine.h" #import "OOMaths.h" #import "OOJSVector.h" @implementation OOJSPopulatorDefinition - (id) init { _callback = JSVAL_VOID; _callbackThis = NULL; _owningScript = [[OOJSScript currentlyRunningScript] weakRetain]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deleteJSPointers) name:kOOJavaScriptEngineWillResetNotification object:[OOJavaScriptEngine sharedEngine]]; return self; } - (void) deleteJSPointers { JSContext *context = OOJSAcquireContext(); _callback = JSVAL_VOID; _callbackThis = NULL; JS_RemoveValueRoot(context, &_callback); JS_RemoveObjectRoot(context, &_callbackThis); OOJSRelinquishContext(context); [[NSNotificationCenter defaultCenter] removeObserver:self name:kOOJavaScriptEngineWillResetNotification object:[OOJavaScriptEngine sharedEngine]]; } - (void) dealloc { [_owningScript release]; [self deleteJSPointers]; [super dealloc]; } - (jsval)callback { return _callback; } - (void)setCallback:(jsval)callback { JSContext *context = OOJSAcquireContext(); JS_RemoveValueRoot(context, &_callback); _callback = callback; OOJSAddGCValueRoot(context, &_callback, "OOJSPopulatorDefinition callback function"); OOJSRelinquishContext(context); } - (JSObject *)callbackThis { return _callbackThis; } - (void)setCallbackThis:(JSObject *)callbackThis { JSContext *context = OOJSAcquireContext(); JS_RemoveObjectRoot(context, &_callbackThis); _callbackThis = callbackThis; OOJSAddGCObjectRoot(context, &_callbackThis, "OOJSPopulatorDefinition callback this"); OOJSRelinquishContext(context); } - (void)runCallback:(HPVector)location { OOJavaScriptEngine *engine = [OOJavaScriptEngine sharedEngine]; JSContext *context = OOJSAcquireContext(); jsval loc, rval = JSVAL_VOID; VectorToJSValue(context, HPVectorToVector(location), &loc); OOJSScript *owner = [_owningScript retain]; // local copy needed [OOJSScript pushScript:owner]; [engine callJSFunction:_callback forObject:_callbackThis argc:1 argv:&loc result:&rval]; [OOJSScript popScript:owner]; [owner release]; OOJSRelinquishContext(context); } @end oolite-1.82/src/Core/Scripting/OOJSPropID.h000066400000000000000000000026661256642440500204030ustar00rootroot00000000000000/* OOJSPropID.h JavaScript support for Oolite Copyright (C) 2007-2013 David Taylor and Jens Ayton. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #import "OOFunctionAttributes.h" /* OOJSID(const char * [literal]) Macro to create a string-based jsid. The string is interned and converted into a string by a helper the first time the macro is hit, then cached. */ #ifdef JS_USE_JSVAL_JSID_STRUCT_TYPES #define OOJSID(str) ({ static jsid idCache; static JSBool inited; if (EXPECT_NOT(!inited)) { OOJSInitJSIDCachePRIVATE(""str, &idCache); inited = JS_TRUE; } idCache; }) #else #define OOJSID(str) ({ static jsid idCache = JSID_VOID; if (EXPECT_NOT(idCache == JSID_VOID)) OOJSInitJSIDCachePRIVATE(""str, &idCache); idCache; }) #endif void OOJSInitJSIDCachePRIVATE(const char *name, jsid *idCache); oolite-1.82/src/Core/Scripting/OOJSQuaternion.h000066400000000000000000000053231256642440500213640ustar00rootroot00000000000000/* OOJSQuaternion.h JavaScript proxy for quaternions. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #include #import "OOMaths.h" void InitOOJSQuaternion(JSContext *context, JSObject *global); JSObject *JSQuaternionWithQuaternion(JSContext *context, Quaternion quaternion) NONNULL_FUNC; BOOL QuaternionToJSValue(JSContext *context, Quaternion quaternion, jsval *outValue) NONNULL_FUNC; BOOL JSValueToQuaternion(JSContext *context, jsval value, Quaternion *outQuaternion) NONNULL_FUNC; /* Given a JS Quaternion object, get the corresponding Vector struct. Given a JS Entity, get its orientation. Given a JS Array with exactly four elements, all of them numbers, treat them as [w, x, y, z] components. For anything else, return NO. (Other implicit conversions may be added in future.) */ BOOL JSObjectGetQuaternion(JSContext *context, JSObject *quaternionObj, Quaternion *outQuaternion) GCC_ATTR((nonnull (1, 3))); // Set the value of a JS quaternion object. BOOL JSQuaternionSetQuaternion(JSContext *context, JSObject *quaternionObj, Quaternion quaternion) GCC_ATTR((nonnull (1))); /* QuaternionFromArgumentList() Construct a quaternion from an argument list which is either a (JS) quaternion, a (JS) entity, four numbers or a JS array of four numbers. The optional outConsumed argument can be used to find out how many parameters were used (currently, this will be 0 on failure, otherwise 1 or 4). On failure, it will return NO and raise an error. If the caller is a JS callback, it must return NO to signal an error. */ BOOL QuaternionFromArgumentList(JSContext *context, NSString *scriptClass, NSString *function, uintN argc, jsval *argv, Quaternion *outQuaternion, uintN *outConsumed) GCC_ATTR((nonnull (1, 5, 6))); /* QuaternionFromArgumentList() Like VectorFromArgumentList(), but does not report an error on failure. */ BOOL QuaternionFromArgumentListNoError(JSContext *context, uintN argc, jsval *argv, Quaternion *outVector, uintN *outConsumed) GCC_ATTR((nonnull (1, 3, 4))); oolite-1.82/src/Core/Scripting/OOJSQuaternion.m000066400000000000000000000546411256642440500214000ustar00rootroot00000000000000/* OOJSQuaternion.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSQuaternion.h" #import "OOJavaScriptEngine.h" #if OOLITE_GNUSTEP #import #else #import #endif #import "OOConstToString.h" #import "OOJSEntity.h" #import "OOJSVector.h" static JSObject *sQuaternionPrototype; static BOOL GetThisQuaternion(JSContext *context, JSObject *quaternionObj, Quaternion *outQuaternion, NSString *method) NONNULL_FUNC; static JSBool QuaternionGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool QuaternionSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); static void QuaternionFinalize(JSContext *context, JSObject *this); static JSBool QuaternionConstruct(JSContext *context, uintN argc, jsval *vp); // Methods static JSBool QuaternionToString(JSContext *context, uintN argc, jsval *vp); static JSBool QuaternionToSource(JSContext *context, uintN argc, jsval *vp); static JSBool QuaternionMultiply(JSContext *context, uintN argc, jsval *vp); static JSBool QuaternionDot(JSContext *context, uintN argc, jsval *vp); static JSBool QuaternionRotate(JSContext *context, uintN argc, jsval *vp); static JSBool QuaternionRotateX(JSContext *context, uintN argc, jsval *vp); static JSBool QuaternionRotateY(JSContext *context, uintN argc, jsval *vp); static JSBool QuaternionRotateZ(JSContext *context, uintN argc, jsval *vp); static JSBool QuaternionNormalize(JSContext *context, uintN argc, jsval *vp); static JSBool QuaternionConjugate(JSContext *context, uintN argc, jsval *vp); static JSBool QuaternionVectorForward(JSContext *context, uintN argc, jsval *vp); static JSBool QuaternionVectorUp(JSContext *context, uintN argc, jsval *vp); static JSBool QuaternionVectorRight(JSContext *context, uintN argc, jsval *vp); static JSBool QuaternionToArray(JSContext *context, uintN argc, jsval *vp); // Static methods static JSBool QuaternionStaticRandom(JSContext *context, uintN argc, jsval *vp); static JSClass sQuaternionClass = { "Quaternion", JSCLASS_HAS_PRIVATE, JS_PropertyStub, // addProperty JS_PropertyStub, // delProperty QuaternionGetProperty, // getProperty QuaternionSetProperty, // setProperty JS_EnumerateStub, // enumerate JS_ResolveStub, // resolve JS_ConvertStub, // convert QuaternionFinalize, // finalize JSCLASS_NO_OPTIONAL_MEMBERS }; enum { // Property IDs kQuaternion_w, kQuaternion_x, kQuaternion_y, kQuaternion_z }; static JSPropertySpec sQuaternionProperties[] = { // JS name ID flags { "w", kQuaternion_w, OOJS_PROP_READWRITE_CB }, { "x", kQuaternion_x, OOJS_PROP_READWRITE_CB }, { "y", kQuaternion_y, OOJS_PROP_READWRITE_CB }, { "z", kQuaternion_z, OOJS_PROP_READWRITE_CB }, { 0 } }; static JSFunctionSpec sQuaternionMethods[] = { // JS name Function min args { "toString", QuaternionToString, 0, }, { "toSource", QuaternionToSource, 0, }, { "dot", QuaternionDot, 1, }, { "multiply", QuaternionMultiply, 1, }, { "normalize", QuaternionNormalize, 0, }, { "conjugate", QuaternionConjugate, 0, }, { "rotate", QuaternionRotate, 2, }, { "rotateX", QuaternionRotateX, 1, }, { "rotateY", QuaternionRotateY, 1, }, { "rotateZ", QuaternionRotateZ, 1, }, { "toArray", QuaternionToArray, 0, }, { "vectorForward", QuaternionVectorForward, 0, }, { "vectorRight", QuaternionVectorRight, 0, }, { "vectorUp", QuaternionVectorUp, 0, }, { 0 } }; static JSFunctionSpec sQuaternionStaticMethods[] = { // JS name Function min args { "random", QuaternionStaticRandom, 0, }, { 0 } }; // *** Public *** void InitOOJSQuaternion(JSContext *context, JSObject *global) { sQuaternionPrototype = JS_InitClass(context, global, NULL, &sQuaternionClass, QuaternionConstruct, 4, sQuaternionProperties, sQuaternionMethods, NULL, sQuaternionStaticMethods); } JSObject *JSQuaternionWithQuaternion(JSContext *context, Quaternion quaternion) { OOJS_PROFILE_ENTER JSObject *result = NULL; Quaternion *private = NULL; private = malloc(sizeof *private); if (EXPECT_NOT(private == NULL)) return NULL; *private = quaternion; result = JS_NewObject(context, &sQuaternionClass, sQuaternionPrototype, NULL); if (result != NULL) { if (!JS_SetPrivate(context, result, private)) result = NULL; } if (EXPECT_NOT(result == NULL)) free(private); return result; OOJS_PROFILE_EXIT } BOOL QuaternionToJSValue(JSContext *context, Quaternion quaternion, jsval *outValue) { OOJS_PROFILE_ENTER JSObject *object = NULL; assert(outValue != NULL); object = JSQuaternionWithQuaternion(context, quaternion); if (EXPECT_NOT(object == NULL)) return NO; *outValue = OBJECT_TO_JSVAL(object); return YES; OOJS_PROFILE_EXIT } BOOL JSValueToQuaternion(JSContext *context, jsval value, Quaternion *outQuaternion) { if (EXPECT_NOT(!JSVAL_IS_OBJECT(value))) return NO; return JSObjectGetQuaternion(context, JSVAL_TO_OBJECT(value), outQuaternion); } #if OO_DEBUG typedef struct { NSUInteger quatCount; NSUInteger entityCount; NSUInteger arrayCount; NSUInteger protoCount; NSUInteger nullCount; NSUInteger failCount; } QuaternionStatistics; static QuaternionStatistics sQuaternionConversionStats; @implementation PlayerEntity (JSQuaternionStatistics) // :setM quatStats PS.callObjC("reportJSQuaternionStatistics") // :quatStats - (NSString *) reportJSQuaternionStatistics { QuaternionStatistics *stats = &sQuaternionConversionStats; NSUInteger sum = stats->quatCount + stats->entityCount + stats->arrayCount + stats->protoCount; double convFac = 100.0 / sum; return [NSString stringWithFormat: @"quaternion-to-quaternion conversions: %lu (%g %%)\n" " entity-to-quaternion conversions: %lu (%g %%)\n" " array-to-quaternion conversions: %lu (%g %%)\n" " prototype-to-zero conversions: %lu (%g %%)\n" " null conversions: %lu (%g %%)\n" " failed conversions: %lu (%g %%)\n" " total: %lu", (long)stats->quatCount, stats->quatCount * convFac, (long)stats->entityCount, stats->entityCount * convFac, (long)stats->arrayCount, stats->arrayCount * convFac, (long)stats->protoCount, stats->protoCount * convFac, (long)stats->nullCount, stats->nullCount * convFac, (long)stats->failCount, stats->failCount * convFac, (long)sum]; } - (void) clearJSQuaternionStatistics { memset(&sQuaternionConversionStats, 0, sizeof sQuaternionConversionStats); } @end #define COUNT(FIELD) do { sQuaternionConversionStats.FIELD++; } while (0) #else #define COUNT(FIELD) do {} while (0) #endif BOOL JSObjectGetQuaternion(JSContext *context, JSObject *quaternionObj, Quaternion *outQuaternion) { OOJS_PROFILE_ENTER assert(outQuaternion != NULL); Quaternion *private = NULL; jsuint arrayLength; jsval arrayW, arrayX, arrayY, arrayZ; jsdouble dVal; // quaternionObj can legitimately be NULL, e.g. when JS_NULL is converted to a JSObject *. if (EXPECT_NOT(quaternionObj == NULL)) { COUNT(nullCount); return NO; } // If this is a (JS) Quaternion... private = JS_GetInstancePrivate(context, quaternionObj, &sQuaternionClass, NULL); if (EXPECT(private != NULL)) { COUNT(quatCount); *outQuaternion = *private; return YES; } // If it's an array... if (EXPECT(JS_IsArrayObject(context, quaternionObj))) { // ...and it has exactly four elements... if (JS_GetArrayLength(context, quaternionObj, &arrayLength) && arrayLength == 4) { if (JS_LookupElement(context, quaternionObj, 0, &arrayW) && JS_LookupElement(context, quaternionObj, 1, &arrayX) && JS_LookupElement(context, quaternionObj, 2, &arrayY) && JS_LookupElement(context, quaternionObj, 3, &arrayZ)) { // ...se the four numbers as [w, x, y, z] if (!JS_ValueToNumber(context, arrayW, &dVal)) return NO; outQuaternion->w = dVal; if (!JS_ValueToNumber(context, arrayX, &dVal)) return NO; outQuaternion->x = dVal; if (!JS_ValueToNumber(context, arrayY, &dVal)) return NO; outQuaternion->y = dVal; if (!JS_ValueToNumber(context, arrayZ, &dVal)) return NO; outQuaternion->z = dVal; COUNT(arrayCount); return YES; } } } // If it's an entity, use its orientation. if (OOJSIsMemberOfSubclass(context, quaternionObj, JSEntityClass())) { COUNT(entityCount); Entity *entity = JS_GetPrivate(context, quaternionObj); *outQuaternion = [entity orientation]; return YES; } /* If it's actually a Quaternion but with no private field (this happens for Quaternion.prototype)... NOTE: it would be prettier to do this at the top when we handle normal Quaternions, but it's a rare case which should be kept off the fast path. */ if (JS_InstanceOf(context, quaternionObj, &sQuaternionClass, NULL)) { COUNT(protoCount); *outQuaternion = kZeroQuaternion; return YES; } COUNT(failCount); return NO; OOJS_PROFILE_EXIT } static BOOL GetThisQuaternion(JSContext *context, JSObject *quaternionObj, Quaternion *outQuaternion, NSString *method) { if (EXPECT(JSObjectGetQuaternion(context, quaternionObj, outQuaternion))) return YES; jsval arg = OBJECT_TO_JSVAL(quaternionObj); OOJSReportBadArguments(context, @"Quaternion", method, 1, &arg, @"Invalid target object", @"Quaternion"); return NO; } BOOL JSQuaternionSetQuaternion(JSContext *context, JSObject *quaternionObj, Quaternion quaternion) { OOJS_PROFILE_ENTER Quaternion *private = NULL; assert(quaternionObj != NULL); private = JS_GetInstancePrivate(context, quaternionObj, &sQuaternionClass, NULL); if (private != NULL) // If this is a (JS) Quaternion... { *private = quaternion; return YES; } if (JS_InstanceOf(context, quaternionObj, &sQuaternionClass, NULL)) { // Silently fail for the prototype. return YES; } return NO; OOJS_PROFILE_EXIT } static BOOL QuaternionFromArgumentListNoErrorInternal(JSContext *context, uintN argc, jsval *argv, Quaternion *outQuaternion, uintN *outConsumed, BOOL permitNumberList) { OOJS_PROFILE_ENTER double w, x, y, z; if (EXPECT_NOT(argc == 0)) return NO; assert(argv != NULL && outQuaternion != NULL); if (outConsumed != NULL) *outConsumed = 0; // Is first object a quaternion or entity? if (JSVAL_IS_OBJECT(argv[0])) { if (JSObjectGetQuaternion(context, JSVAL_TO_OBJECT(argv[0]), outQuaternion)) { if (outConsumed != NULL) *outConsumed = 1; return YES; } } if (!permitNumberList) return NO; // As a special case for QuaternionConstruct(), look for four numbers. if (EXPECT_NOT(argc < 4)) return NO; // Given a string, JS_ValueToNumber() returns YES but provides a NaN number. if (EXPECT_NOT(!JS_ValueToNumber(context, argv[0], &w) || isnan(w))) return NO; if (EXPECT_NOT(!JS_ValueToNumber(context, argv[1], &x) || isnan(x))) return NO; if (EXPECT_NOT(!JS_ValueToNumber(context, argv[2], &y) || isnan(y))) return NO; if (EXPECT_NOT(!JS_ValueToNumber(context, argv[3], &z) || isnan(z))) return NO; // We got our four numbers. *outQuaternion = make_quaternion(w, x, y, z); if (outConsumed != NULL) *outConsumed = 4; return YES; OOJS_PROFILE_EXIT } // EMMSTRAN: remove outConsumed, since it can only be 1 except in failure (constructor is an exception, but it uses QuaternionFromArgumentListNoErrorInternal() directly). BOOL QuaternionFromArgumentList(JSContext *context, NSString *scriptClass, NSString *function, uintN argc, jsval *argv, Quaternion *outQuaternion, uintN *outConsumed) { if (QuaternionFromArgumentListNoErrorInternal(context, argc, argv, outQuaternion, outConsumed, NO)) return YES; else { OOJSReportBadArguments(context, scriptClass, function, argc, argv, @"Could not construct quaternion from parameters", @"Quaternion, Entity or four numbers"); return NO; } } BOOL QuaternionFromArgumentListNoError(JSContext *context, uintN argc, jsval *argv, Quaternion *outQuaternion, uintN *outConsumed) { return QuaternionFromArgumentListNoErrorInternal(context, argc, argv, outQuaternion, outConsumed, NO); } // *** Implementation stuff *** static JSBool QuaternionGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_PROFILE_ENTER Quaternion quaternion; GLfloat fValue; if (EXPECT_NOT(!JSObjectGetQuaternion(context, this, &quaternion))) return NO; switch (JSID_TO_INT(propID)) { case kQuaternion_w: fValue = quaternion.w; break; case kQuaternion_x: fValue = quaternion.x; break; case kQuaternion_y: fValue = quaternion.y; break; case kQuaternion_z: fValue = quaternion.z; break; default: OOJSReportBadPropertySelector(context, this, propID, sQuaternionProperties); return NO; } return JS_NewNumberValue(context, fValue, value); OOJS_PROFILE_EXIT } static JSBool QuaternionSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_PROFILE_ENTER Quaternion quaternion; jsdouble dval; if (EXPECT_NOT(!JSObjectGetQuaternion(context, this, &quaternion))) return NO; if (EXPECT_NOT(!JS_ValueToNumber(context, *value, &dval))) { OOJSReportBadPropertyValue(context, this, propID, sQuaternionProperties, *value); return NO; } switch (JSID_TO_INT(propID)) { case kQuaternion_w: quaternion.w = dval; break; case kQuaternion_x: quaternion.x = dval; break; case kQuaternion_y: quaternion.y = dval; break; case kQuaternion_z: quaternion.z = dval; break; default: OOJSReportBadPropertySelector(context, this, propID, sQuaternionProperties); return NO; } return JSQuaternionSetQuaternion(context, this, quaternion); OOJS_PROFILE_EXIT } static void QuaternionFinalize(JSContext *context, JSObject *this) { Quaternion *private = NULL; private = JS_GetInstancePrivate(context, this, &sQuaternionClass, NULL); if (private != NULL) { free(private); } } static JSBool QuaternionConstruct(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER Quaternion quaternion = kIdentityQuaternion; Quaternion *private = NULL; JSObject *this = NULL; private = malloc(sizeof *private); if (EXPECT_NOT(private == NULL)) return NO; this = JS_NewObject(context, &sQuaternionClass, NULL, NULL); if (EXPECT_NOT(this == NULL)) return NO; if (argc != 0) { if (EXPECT_NOT(!QuaternionFromArgumentListNoErrorInternal(context, argc, OOJS_ARGV, &quaternion, NULL, YES))) { free(private); OOJSReportBadArguments(context, NULL, NULL, argc, OOJS_ARGV, @"Could not construct quaternion from parameters", @"Quaternion, Entity or array of four numbers"); return NO; } } *private = quaternion; if (!JS_SetPrivate(context, this, private)) { free(private); return NO; } OOJS_RETURN_JSOBJECT(this); OOJS_PROFILE_EXIT } // *** Methods *** // toString() : String static JSBool QuaternionToString(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) Quaternion thisq; if (EXPECT_NOT(!GetThisQuaternion(context, OOJS_THIS, &thisq, @"toString"))) return NO; OOJS_RETURN_OBJECT(QuaternionDescription(thisq)); OOJS_NATIVE_EXIT } // toSource() : String static JSBool QuaternionToSource(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) Quaternion thisq; if (EXPECT_NOT(!GetThisQuaternion(context, OOJS_THIS, &thisq, @"toSource"))) return NO; NSString *str = [NSString stringWithFormat:@"Quaternion(%g, %g, %g, %g)", thisq.w, thisq.x, thisq.y, thisq.z]; OOJS_RETURN_OBJECT(str); OOJS_NATIVE_EXIT } // multiply(q : quaternionExpression) : Quaternion static JSBool QuaternionMultiply(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER Quaternion thisq, thatq, result; if (EXPECT_NOT(!GetThisQuaternion(context, OOJS_THIS, &thisq, @"multiply"))) return NO; if (EXPECT_NOT(!QuaternionFromArgumentList(context, @"Quaternion", @"multiply", argc, OOJS_ARGV, &thatq, NULL))) return NO; result = quaternion_multiply(thisq, thatq); OOJS_RETURN_QUATERNION(result); OOJS_PROFILE_EXIT } // dot(q : quaternionExpression) : Number static JSBool QuaternionDot(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER Quaternion thisq, thatq; double result; if (EXPECT_NOT(!GetThisQuaternion(context, OOJS_THIS, &thisq, @"dot"))) return NO; if (EXPECT_NOT(!QuaternionFromArgumentList(context, @"Quaternion", @"dot", argc, OOJS_ARGV, &thatq, NULL))) return NO; result = quaternion_dot_product(thisq, thatq); OOJS_RETURN_DOUBLE(result); OOJS_PROFILE_EXIT } // rotate(axis : vectorExpression, angle : Number) : Quaternion static JSBool QuaternionRotate(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER Quaternion thisq; HPVector axis; double angle; uintN consumed; jsval *argv = OOJS_ARGV; if (EXPECT_NOT(!GetThisQuaternion(context, OOJS_THIS, &thisq, @"rotate"))) return NO; if (EXPECT_NOT(!VectorFromArgumentList(context, @"Quaternion", @"rotate", argc, argv, &axis, &consumed))) return NO; argv += consumed; argc -= consumed; if (argc > 0) { if (EXPECT_NOT(!OOJSArgumentListGetNumber(context, @"Quaternion", @"rotate", argc, argv, &angle, NULL))) return NO; quaternion_rotate_about_axis(&thisq, HPVectorToVector(axis), angle); } // Else no angle specified, so don't rotate and pass value through unchanged. OOJS_RETURN_QUATERNION(thisq); OOJS_PROFILE_EXIT } // rotateX(angle : Number) : Quaternion static JSBool QuaternionRotateX(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER Quaternion quat; double angle; if (EXPECT_NOT(!GetThisQuaternion(context, OOJS_THIS, &quat, @"rotateX"))) return NO; if (EXPECT_NOT(!OOJSArgumentListGetNumber(context, @"Quaternion", @"rotateX", argc, OOJS_ARGV, &angle, NULL))) return NO; quaternion_rotate_about_x(&quat, angle); OOJS_RETURN_QUATERNION(quat); OOJS_PROFILE_EXIT } // rotateY(angle : Number) : Quaternion static JSBool QuaternionRotateY(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER Quaternion quat; double angle; if (EXPECT_NOT(!GetThisQuaternion(context, OOJS_THIS, &quat, @"rotateY"))) return NO; if (EXPECT_NOT(!OOJSArgumentListGetNumber(context, @"Quaternion", @"rotateY", argc, OOJS_ARGV, &angle, NULL))) return NO; quaternion_rotate_about_y(&quat, angle); OOJS_RETURN_QUATERNION(quat); OOJS_PROFILE_EXIT } // rotateZ(angle : Number) : Quaternion static JSBool QuaternionRotateZ(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER Quaternion quat; double angle; if (EXPECT_NOT(!GetThisQuaternion(context, OOJS_THIS, &quat, @"rotateZ"))) return NO; if (EXPECT_NOT(!OOJSArgumentListGetNumber(context, @"Quaternion", @"rotateZ", argc, OOJS_ARGV, &angle, NULL))) return NO; quaternion_rotate_about_z(&quat, angle); OOJS_RETURN_QUATERNION(quat); OOJS_PROFILE_EXIT } // normalize() : Quaternion static JSBool QuaternionNormalize(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER Quaternion quat; if (EXPECT_NOT(!GetThisQuaternion(context, OOJS_THIS, &quat, @"normalize"))) return NO; quaternion_normalize(&quat); OOJS_RETURN_QUATERNION(quat); OOJS_PROFILE_EXIT } // conjugate() : Quaternion static JSBool QuaternionConjugate(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER Quaternion quat, result; if (EXPECT_NOT(!GetThisQuaternion(context, OOJS_THIS, &quat, @"conjugate"))) return NO; result = quaternion_conjugate(quat); OOJS_RETURN_QUATERNION(result); OOJS_PROFILE_EXIT } // vectorForward() : Vector static JSBool QuaternionVectorForward(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER Quaternion thisq; Vector result; if (EXPECT_NOT(!GetThisQuaternion(context, OOJS_THIS, &thisq, @"vectorForward"))) return NO; result = vector_forward_from_quaternion(thisq); OOJS_RETURN_VECTOR(result); OOJS_PROFILE_EXIT } // vectorUp() : Vector static JSBool QuaternionVectorUp(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER Quaternion thisq; Vector result; if (EXPECT_NOT(!GetThisQuaternion(context, OOJS_THIS, &thisq, @"vectorUp"))) return NO; result = vector_up_from_quaternion(thisq); OOJS_RETURN_VECTOR(result); OOJS_PROFILE_EXIT } // vectorRight() : Vector static JSBool QuaternionVectorRight(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER Quaternion thisq; Vector result; if (EXPECT_NOT(!GetThisQuaternion(context, OOJS_THIS, &thisq, @"vectorRight"))) return NO; result = vector_right_from_quaternion(thisq); OOJS_RETURN_VECTOR(result); OOJS_PROFILE_EXIT } // toArray() : Array static JSBool QuaternionToArray(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER Quaternion thisq; JSObject *result = NULL; BOOL OK = YES; jsval nVal; if (EXPECT_NOT(!GetThisQuaternion(context, OOJS_THIS, &thisq, @"toArray"))) return NO; result = JS_NewArrayObject(context, 0, NULL); if (result != NULL) { // We do this at the top because *outResult is a GC root. OOJS_SET_RVAL(OBJECT_TO_JSVAL(result)); if (JS_NewNumberValue(context, thisq.w, &nVal)) JS_SetElement(context, result, 0, &nVal); else OK = NO; if (JS_NewNumberValue(context, thisq.x, &nVal)) JS_SetElement(context, result, 1, &nVal); else OK = NO; if (JS_NewNumberValue(context, thisq.y, &nVal)) JS_SetElement(context, result, 2, &nVal); else OK = NO; if (JS_NewNumberValue(context, thisq.z, &nVal)) JS_SetElement(context, result, 3, &nVal); else OK = NO; } if (!OK) OOJS_SET_RVAL(JSVAL_VOID); return YES; OOJS_PROFILE_EXIT } // *** Static methods *** // random() : Quaternion static JSBool QuaternionStaticRandom(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER OOJS_RETURN_QUATERNION(OORandomQuaternion()); OOJS_PROFILE_EXIT } oolite-1.82/src/Core/Scripting/OOJSScript.h000066400000000000000000000053261256642440500205060ustar00rootroot00000000000000/* OOJSScript.h JavaScript support for Oolite Copyright (C) 2007-2013 David Taylor and Jens Ayton. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOScript.h" #import "OOJavaScriptEngine.h" static NSString * const kLocalManifestProperty = @"oolite_manifest_identifier"; @interface OOJSScript: OOScript { @private JSObject *_jsSelf; NSString *name; NSString *description; NSString *version; NSString *filePath; OOWeakReference *weakSelf; } + (id) scriptWithPath:(NSString *)path properties:(NSDictionary *)properties; - (id) initWithPath:(NSString *)path properties:(NSDictionary *)properties; + (OOJSScript *) currentlyRunningScript; + (NSArray *) scriptStack; /* External manipulation of acrtive script stack. Used, for instance, by timers. Failing to balance these will crash! Passing a nil script is valid for cases where JS is used which is not attached to a specific script. */ + (void) pushScript:(OOJSScript *)script; + (void) popScript:(OOJSScript *)script; /* Call a method. Requires a request on context. outResult may be NULL. */ - (BOOL) callMethod:(jsid)methodID inContext:(JSContext *)context withArguments:(jsval *)argv count:(intN)argc result:(jsval *)outResult; - (id) propertyWithID:(jsid)propID inContext:(JSContext *)context; // Set a property which can be modified or deleted by the script. - (BOOL) setProperty:(id)value withID:(jsid)propID inContext:(JSContext *)context; // Set a special property which cannot be modified or deleted by the script. - (BOOL) defineProperty:(id)value withID:(jsid)propID inContext:(JSContext *)context; - (id) propertyNamed:(NSString *)name; - (BOOL) setProperty:(id)value named:(NSString *)name; - (BOOL) defineProperty:(id)value named:(NSString *)name; @end @interface OOScript (JavaScriptEvents) // For simplicity, calling methods on non-JS scripts works but does nothing. - (BOOL) callMethod:(jsid)methodID inContext:(JSContext *)context withArguments:(jsval *)argv count:(intN)argc result:(jsval *)outResult; @end void InitOOJSScript(JSContext *context, JSObject *global); oolite-1.82/src/Core/Scripting/OOJSScript.m000066400000000000000000000524351256642440500205160ustar00rootroot00000000000000/* OOJSScript.m JavaScript support for Oolite Copyright (C) 2007-2013 David Taylor and Jens Ayton. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef OO_CACHE_JS_SCRIPTS #define OO_CACHE_JS_SCRIPTS 1 #endif #import "OOJSScript.h" #import "OOJavaScriptEngine.h" #import "OOJSEngineTimeManagement.h" #import "OOLogging.h" #import "OOConstToString.h" #import "Entity.h" #import "NSStringOOExtensions.h" #import "EntityOOJavaScriptExtensions.h" #import "OOConstToJSString.h" #import "OOManifestProperties.h" #import "OOCollectionExtractors.h" #import "OOPListParsing.h" #import "OODebugStandards.h" #if OO_CACHE_JS_SCRIPTS #include #import "OOCacheManager.h" #endif typedef struct RunningStack RunningStack; struct RunningStack { RunningStack *back; OOJSScript *current; }; static JSObject *sScriptPrototype; static RunningStack *sRunningStack = NULL; static void AddStackToArrayReversed(NSMutableArray *array, RunningStack *stack); static JSScript *LoadScriptWithName(JSContext *context, NSString *path, JSObject *object, JSObject **outScriptObject, NSString **outErrorMessage); #if OO_CACHE_JS_SCRIPTS static NSData *CompiledScriptData(JSContext *context, JSScript *script); static JSScript *ScriptWithCompiledData(JSContext *context, NSData *data); #endif static NSString *StrippedName(NSString *string); static JSBool ScriptAddProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSClass sScriptClass = { "Script", JSCLASS_HAS_PRIVATE, ScriptAddProperty, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, OOJSObjectWrapperFinalize }; static JSFunctionSpec sScriptMethods[] = { // JS name Function min args { "toString", OOJSObjectWrapperToString, 0, }, { 0 } }; @interface OOJSScript (OOPrivate) - (NSString *)scriptNameFromPath:(NSString *)path; - (NSDictionary *)defaultPropertiesFromPath:(NSString *)path; @end @implementation OOJSScript + (id) scriptWithPath:(NSString *)path properties:(NSDictionary *)properties { return [[[self alloc] initWithPath:path properties:properties] autorelease]; } - (id) initWithPath:(NSString *)path properties:(NSDictionary *)properties { JSContext *context = NULL; NSString *problem = nil; // Acts as error flag. JSScript *script = NULL; JSObject *scriptObject = NULL; jsval returnValue = JSVAL_VOID; NSEnumerator *keyEnum = nil; NSString *key = nil; id property = nil; self = [super init]; if (self == nil) problem = @"allocation failure"; else { context = OOJSAcquireContext(); if (JS_IsExceptionPending(context)) { JS_ClearPendingException(context); OOLogERR(@"script.javaScript.load.waitingException", @"Prior to loading script %@, there was a pending JavaScript exception, which has been cleared. This is an internal error, please report it.", path); } // Set up JS object if (!problem) { _jsSelf = JS_NewObject(context, &sScriptClass, sScriptPrototype, NULL); if (_jsSelf == NULL) problem = @"allocation failure"; } if (!problem && !OOJSAddGCObjectRoot(context, &_jsSelf, "Script object")) { problem = @"could not add JavaScript root object"; } if (!problem && !OOJSAddGCObjectRoot(context, &scriptObject, "Script GC holder")) { problem = @"could not add JavaScript root object"; } if (!problem) { if (!JS_SetPrivate(context, _jsSelf, OOConsumeReference([self weakRetain]))) { problem = @"could not set private backreference"; } } // Push self on stack of running scripts. RunningStack stackElement = { .back = sRunningStack, .current = self }; sRunningStack = &stackElement; filePath = [path retain]; if (!problem) { OOLog(@"script.javaScript.willLoad", @"About to load JavaScript %@", path); script = LoadScriptWithName(context, path, _jsSelf, &scriptObject, &problem); } OOLogIndentIf(@"script.javaScript.willLoad"); // Set default properties from manifest.plist NSDictionary *defaultProperties = [self defaultPropertiesFromPath:path]; for (keyEnum = [defaultProperties keyEnumerator]; (key = [keyEnum nextObject]); ) { if ([key isKindOfClass:[NSString class]]) { property = [defaultProperties objectForKey:key]; if ([key isEqualToString:kLocalManifestProperty]) { // this must not be editable [self defineProperty:property named:key]; } else { // can be overwritten by script itself [self setProperty:property named:key]; } } } // Set properties. (read-only) if (!problem && properties != nil) { for (keyEnum = [properties keyEnumerator]; (key = [keyEnum nextObject]); ) { if ([key isKindOfClass:[NSString class]]) { property = [properties objectForKey:key]; [self defineProperty:property named:key]; } } } /* Set initial name (in case of script error during initial run). The "name" ivar is not set here, so the property can be fetched from JS if we fail during setup. However, the "name" ivar is set later so that the script object can't be renamed after the initial run. This could probably also be achieved by fiddling with JS property attributes. */ jsid nameID = OOJSID("name"); [self setProperty:[self scriptNameFromPath:path] withID:nameID inContext:context]; // Run the script (allowing it to set up the properties we need, as well as setting up those event handlers) if (!problem) { OOJSStartTimeLimiterWithTimeLimit(kOOJSLongTimeLimit); if (!JS_ExecuteScript(context, _jsSelf, script, &returnValue)) { problem = @"could not run script"; } OOJSStopTimeLimiter(); // We don't need the script any more - the event handlers hang around as long as the JS object exists. JS_DestroyScript(context, script); } JS_RemoveObjectRoot(context, &scriptObject); sRunningStack = stackElement.back; if (!problem) { // Get display attributes from script DESTROY(name); name = [StrippedName([[self propertyWithID:nameID inContext:context] description]) copy]; if (name == nil) { name = [[self scriptNameFromPath:path] retain]; [self setProperty:name withID:nameID inContext:context]; } version = [[[self propertyWithID:OOJSID("version") inContext:context] description] copy]; description = [[[self propertyWithID:OOJSID("description") inContext:context] description] copy]; OOLog(@"script.javaScript.load.success", @"Loaded JavaScript: %@ -- %@", [self displayName], description ? description : (NSString *)@"(no description)"); } OOLogOutdentIf(@"script.javaScript.willLoad"); DESTROY(filePath); // Only used for error reporting during startup. } if (problem) { OOLog(@"script.javaScript.load.failed", @"***** Error loading JavaScript script %@ -- %@", path, problem); JS_ReportPendingException(context); DESTROY(self); } OOJSRelinquishContext(context); if (self != nil) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(javaScriptEngineWillReset:) name:kOOJavaScriptEngineWillResetNotification object:[OOJavaScriptEngine sharedEngine]]; } return self; } - (void) dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self name:kOOJavaScriptEngineWillResetNotification object:[OOJavaScriptEngine sharedEngine]]; DESTROY(name); DESTROY(description); DESTROY(version); DESTROY(filePath); if (_jsSelf != NULL) { JSContext *context = OOJSAcquireContext(); OOJSObjectWrapperFinalize(context, _jsSelf); // Release weakref to self JS_RemoveObjectRoot(context, &_jsSelf); // Unroot jsSelf OOJSRelinquishContext(context); } [weakSelf weakRefDrop]; [super dealloc]; } - (NSString *) oo_jsClassName { return @"Script"; } - (NSString *)descriptionComponents { if (_jsSelf != NULL) return [super descriptionComponents]; else return @"invalid script"; } - (void) javaScriptEngineWillReset:(NSNotification *)notification { // All scripts become invalid when the JS engine resets. if (_jsSelf != NULL) { _jsSelf = NULL; JSContext *context = OOJSAcquireContext(); JS_RemoveObjectRoot(context, &_jsSelf); OOJSRelinquishContext(context); } } + (OOJSScript *) currentlyRunningScript { if (sRunningStack == NULL) return NULL; return sRunningStack->current; } + (NSArray *) scriptStack { NSMutableArray *result = nil; result = [NSMutableArray array]; AddStackToArrayReversed(result, sRunningStack); return result; } - (id) weakRetain { if (weakSelf == nil) weakSelf = [OOWeakReference weakRefWithObject:self]; return [weakSelf retain]; } - (void) weakRefDied:(OOWeakReference *)weakRef { if (weakRef == weakSelf) weakSelf = nil; } - (NSString *) name { if (name == nil) name = [[self propertyNamed:@"name"] copy]; if (name == nil) return [self scriptNameFromPath:filePath]; // Special case for parse errors during load. return name; } - (NSString *) scriptDescription { return description; } - (NSString *) version { return version; } - (void)runWithTarget:(Entity *)target { } - (BOOL) callMethod:(jsid)methodID inContext:(JSContext *)context withArguments:(jsval *)argv count:(intN)argc result:(jsval *)outResult { NSParameterAssert(name != NULL && (argv != NULL || argc == 0) && context != NULL && JS_IsInRequest(context)); if (_jsSelf == NULL) return NO; JSObject *root = NULL; BOOL OK = NO; jsval method; jsval ignoredResult = JSVAL_VOID; if (outResult == NULL) outResult = &ignoredResult; OOJSAddGCObjectRoot(context, &root, "OOJSScript method root"); if (EXPECT(JS_GetMethodById(context, _jsSelf, methodID, &root, &method) && !JSVAL_IS_VOID(method))) { #ifndef NDEBUG if (JS_IsExceptionPending(context)) { OOLog(@"script.internalBug", @"Exception pending on context before calling method in %s, clearing. This is an internal error, please report it.", __PRETTY_FUNCTION__); JS_ClearPendingException(context); } OOLog(@"script.javaScript.call", @"Calling [%@].%@()", [self name], OOStringFromJSID(methodID)); OOLogIndentIf(@"script.javaScript.call"); #endif // Push self on stack of running scripts. RunningStack stackElement = { .back = sRunningStack, .current = self }; sRunningStack = &stackElement; // Call the method. OOJSStartTimeLimiter(); OK = JS_CallFunctionValue(context, _jsSelf, method, argc, argv, outResult); OOJSStopTimeLimiter(); if (JS_IsExceptionPending(context)) { JS_ReportPendingException(context); OK = NO; } // Pop running scripts stack sRunningStack = stackElement.back; #ifndef NDEBUG OOLogOutdentIf(@"script.javaScript.call"); #endif } JS_RemoveObjectRoot(context, &root); return OK; } - (id) propertyWithID:(jsid)propID inContext:(JSContext *)context { NSParameterAssert(context != NULL && JS_IsInRequest(context)); if (_jsSelf == NULL) return nil; jsval jsValue = JSVAL_VOID; if (JS_GetPropertyById(context, _jsSelf, propID, &jsValue)) { return OOJSNativeObjectFromJSValue(context, jsValue); } return nil; } - (BOOL) setProperty:(id)value withID:(jsid)propID inContext:(JSContext *)context { NSParameterAssert(context != NULL && JS_IsInRequest(context)); if (_jsSelf == NULL) return NO; jsval jsValue = OOJSValueFromNativeObject(context, value); return JS_SetPropertyById(context, _jsSelf, propID, &jsValue); } - (BOOL) defineProperty:(id)value withID:(jsid)propID inContext:(JSContext *)context { NSParameterAssert(context != NULL && JS_IsInRequest(context)); if (_jsSelf == NULL) return NO; jsval jsValue = OOJSValueFromNativeObject(context, value); return JS_DefinePropertyById(context, _jsSelf, propID, jsValue, NULL, NULL, OOJS_PROP_READONLY); } - (id) propertyNamed:(NSString *)propName { if (propName == nil) return nil; if (_jsSelf == NULL) return nil; JSContext *context = OOJSAcquireContext(); id result = [self propertyWithID:OOJSIDFromString(propName) inContext:context]; OOJSRelinquishContext(context); return result; } - (BOOL) setProperty:(id)value named:(NSString *)propName { if (value == nil || propName == nil) return NO; if (_jsSelf == NULL) return NO; JSContext *context = OOJSAcquireContext(); BOOL result = [self setProperty:value withID:OOJSIDFromString(propName) inContext:context]; OOJSRelinquishContext(context); return result; } - (BOOL) defineProperty:(id)value named:(NSString *)propName { if (value == nil || propName == nil) return NO; if (_jsSelf == NULL) return NO; JSContext *context = OOJSAcquireContext(); BOOL result = [self defineProperty:value withID:OOJSIDFromString(propName) inContext:context]; OOJSRelinquishContext(context); return result; } - (jsval)oo_jsValueInContext:(JSContext *)context { if (_jsSelf == NULL) return JSVAL_VOID; return OBJECT_TO_JSVAL(_jsSelf); } + (void)pushScript:(OOJSScript *)script { RunningStack *element = NULL; element = malloc(sizeof *element); if (element == NULL) exit(EXIT_FAILURE); element->back = sRunningStack; element->current = script; sRunningStack = element; } + (void)popScript:(OOJSScript *)script { RunningStack *element = NULL; assert(sRunningStack->current == script); element = sRunningStack; sRunningStack = sRunningStack->back; free(element); } @end @implementation OOJSScript (OOPrivate) /* Generate default name for script which doesn't set its name property when first run. The generated name is .anon-script, where is selected as follows: * If path is nil (futureproofing), use the address of the script object. * If the file's name is something other than script.*, use the file name. * If the containing directory is something other than Config, use the containing directory's name. * Otherwise, use the containing directory's parent (which will generally be an OXP root directory). * If either of the two previous steps results in an empty string, fall back on the full path. */ - (NSString *)scriptNameFromPath:(NSString *)path { NSString *lastComponent = nil; NSString *truncatedPath = nil; NSString *theName = nil; if (path == nil) theName = [NSString stringWithFormat:@"%p", self]; else { lastComponent = [path lastPathComponent]; if (![lastComponent hasPrefix:@"script."]) theName = lastComponent; else { truncatedPath = [path stringByDeletingLastPathComponent]; if (NSOrderedSame == [[truncatedPath lastPathComponent] caseInsensitiveCompare:@"Config"]) { truncatedPath = [truncatedPath stringByDeletingLastPathComponent]; } if (NSOrderedSame == [[truncatedPath pathExtension] caseInsensitiveCompare:@"oxp"]) { truncatedPath = [truncatedPath stringByDeletingPathExtension]; } lastComponent = [truncatedPath lastPathComponent]; theName = lastComponent; } } if (0 == [theName length]) theName = path; return StrippedName([theName stringByAppendingString:@".anon-script"]); } - (NSDictionary *) defaultPropertiesFromPath:(NSString *)path { // remove file name, remove OXP subfolder, add manifest.plist NSString *manifestPath = [[[path stringByDeletingLastPathComponent] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"manifest.plist"]; NSDictionary *manifest = OODictionaryFromFile(manifestPath); NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithCapacity:3]; /* __oolite.tmp.* is allocated for OXPs without manifests. Its * values are meaningless and shouldn't be used here */ if (manifest != nil && ![[manifest oo_stringForKey:kOOManifestIdentifier] hasPrefix:@"__oolite.tmp."]) { if ([manifest objectForKey:kOOManifestVersion] != nil) { [properties setObject:[manifest oo_stringForKey:kOOManifestVersion] forKey:@"version"]; } if ([manifest objectForKey:kOOManifestIdentifier] != nil) { // used for system info [properties setObject:[manifest oo_stringForKey:kOOManifestIdentifier] forKey:kLocalManifestProperty]; } if ([manifest objectForKey:kOOManifestAuthor] != nil) { [properties setObject:[manifest oo_stringForKey:kOOManifestAuthor] forKey:@"author"]; } if ([manifest objectForKey:kOOManifestLicense] != nil) { [properties setObject:[manifest oo_stringForKey:kOOManifestLicense] forKey:@"license"]; } } return properties; } @end @implementation OOScript (JavaScriptEvents) - (BOOL) callMethod:(jsid)methodID inContext:(JSContext *)context withArguments:(jsval *)argv count:(intN)argc result:(jsval *)outResult { return NO; } @end void InitOOJSScript(JSContext *context, JSObject *global) { sScriptPrototype = JS_InitClass(context, global, NULL, &sScriptClass, OOJSUnconstructableConstruct, 0, NULL, sScriptMethods, NULL, NULL); OOJSRegisterObjectConverter(&sScriptClass, OOJSBasicPrivateObjectConverter); } static JSBool ScriptAddProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { // Complain about attempts to set the property tickle. if (JSID_IS_STRING(propID)) { JSString *propName = JSID_TO_STRING(propID); JSBool match; if (JS_StringEqualsAscii(context, propName, "tickle", &match) && match) { OOJSScript *thisScript = OOJSNativeObjectOfClassFromJSObject(context, this, [OOJSScript class]); OOJSReportWarning(context, @"Script %@ appears to use the tickle() event handler, which is no longer supported.", [thisScript name]); } } return YES; } static void AddStackToArrayReversed(NSMutableArray *array, RunningStack *stack) { if (stack != NULL) { AddStackToArrayReversed(array, stack->back); [array addObject:stack->current]; } } static JSScript *LoadScriptWithName(JSContext *context, NSString *path, JSObject *object, JSObject **outScriptObject, NSString **outErrorMessage) { #if OO_CACHE_JS_SCRIPTS OOCacheManager *cache = nil; #endif NSString *fileContents = nil; NSData *data = nil; JSScript *script = NULL; NSCParameterAssert(outScriptObject != NULL && outErrorMessage != NULL); *outErrorMessage = nil; #if OO_CACHE_JS_SCRIPTS // Look for cached compiled script cache = [OOCacheManager sharedCache]; data = [cache objectForKey:path inCache:@"compiled JavaScript scripts"]; if (data != nil) { script = ScriptWithCompiledData(context, data); } #endif if (script == NULL) { fileContents = [NSString stringWithContentsOfUnicodeFile:path]; if (fileContents != nil) { #ifndef NDEBUG /* FIXME: this isn't strictly the right test, since strict * mode can be enabled with this string within a function * definition, but it seems unlikely anyone is actually doing * that here. */ if ([fileContents rangeOfString:@"\"use strict\";"].location == NSNotFound && [fileContents rangeOfString:@"'use strict';"].location == NSNotFound) { OOStandardsDeprecated([NSString stringWithFormat:@"Script %@ does not \"use strict\";",path]); if (OOEnforceStandards()) { // prepend it anyway // TODO: some time after 1.82, make this required fileContents = [@"\"use strict\";\n" stringByAppendingString:fileContents]; } } #endif data = [fileContents utf16DataWithBOM:NO]; } if (data == nil) *outErrorMessage = @"could not load file"; else { script = JS_CompileUCScript(context, object, [data bytes], [data length] / sizeof(unichar), [path UTF8String], 1); if (script != NULL) *outScriptObject = JS_NewScriptObject(context, script); else *outErrorMessage = @"compilation failed"; } #if OO_CACHE_JS_SCRIPTS if (script != NULL) { // Write compiled script to cache data = CompiledScriptData(context, script); [cache setObject:data forKey:path inCache:@"compiled JavaScript scripts"]; } #endif } return script; } #if OO_CACHE_JS_SCRIPTS static NSData *CompiledScriptData(JSContext *context, JSScript *script) { JSXDRState *xdr = NULL; NSData *result = nil; uint32 length; void *bytes = NULL; xdr = JS_XDRNewMem(context, JSXDR_ENCODE); if (xdr != NULL) { if (JS_XDRScript(xdr, &script)) { bytes = JS_XDRMemGetData(xdr, &length); if (bytes != NULL) { result = [NSData dataWithBytes:bytes length:length]; } } JS_XDRDestroy(xdr); } return result; } static JSScript *ScriptWithCompiledData(JSContext *context, NSData *data) { JSXDRState *xdr = NULL; JSScript *result = NULL; if (data == nil) return NULL; xdr = JS_XDRNewMem(context, JSXDR_DECODE); if (xdr != NULL) { NSUInteger length = [data length]; if (EXPECT_NOT(length > UINT32_MAX)) return NULL; JS_XDRMemSetData(xdr, (void *)[data bytes], (uint32_t)length); if (!JS_XDRScript(xdr, &result)) result = NULL; JS_XDRMemSetData(xdr, NULL, 0); // Don't let it be freed by XDRDestroy JS_XDRDestroy(xdr); } return result; } #endif static NSString *StrippedName(NSString *string) { static NSCharacterSet *invalidSet = nil; if (invalidSet == nil) invalidSet = [[NSCharacterSet characterSetWithCharactersInString:@"_ \t\n\r\v"] retain]; return [string stringByTrimmingCharactersInSet:invalidSet]; } oolite-1.82/src/Core/Scripting/OOJSShip.h000066400000000000000000000017461256642440500201470ustar00rootroot00000000000000/* OOJSShip.h JavaScript proxy for ShipEntities. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #include @class ShipEntity; void InitOOJSShip(JSContext *context, JSObject *global); JSClass *JSShipClass(void); JSObject *JSShipPrototype(void); oolite-1.82/src/Core/Scripting/OOJSShip.m000066400000000000000000003612231256642440500201530ustar00rootroot00000000000000/* OOJSShip.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSShip.h" #import "OOJSEntity.h" #import "OOJSWormhole.h" #import "OOJSVector.h" #import "OOJSQuaternion.h" #import "OOJSEquipmentInfo.h" #import "OOJavaScriptEngine.h" #import "ShipEntity.h" #import "ShipEntityAI.h" #import "ShipEntityScriptMethods.h" #import "StationEntity.h" #import "WormholeEntity.h" #import "AI.h" #import "OOStringParsing.h" #import "EntityOOJavaScriptExtensions.h" #import "OORoleSet.h" #import "OOJSPlayer.h" #import "PlayerEntity.h" #import "PlayerEntityScriptMethods.h" #import "OOShipGroup.h" #import "OOShipRegistry.h" #import "OOEquipmentType.h" #import "ResourceManager.h" #import "OOCollectionExtractors.h" #import "OOMesh.h" #import "OOConstToString.h" #import "OOEntityFilterPredicate.h" #import "OOCharacter.h" static JSObject *sShipPrototype; static JSBool ShipGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool ShipSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); static JSBool ShipSetScript(JSContext *context, uintN argc, jsval *vp); static JSBool ShipSetAI(JSContext *context, uintN argc, jsval *vp); static JSBool ShipSwitchAI(JSContext *context, uintN argc, jsval *vp); static JSBool ShipExitAI(JSContext *context, uintN argc, jsval *vp); static JSBool ShipReactToAIMessage(JSContext *context, uintN argc, jsval *vp); static JSBool ShipSendAIMessage(JSContext *context, uintN argc, jsval *vp); static JSBool ShipDeployEscorts(JSContext *context, uintN argc, jsval *vp); static JSBool ShipDockEscorts(JSContext *context, uintN argc, jsval *vp); static JSBool ShipHasRole(JSContext *context, uintN argc, jsval *vp); static JSBool ShipEjectItem(JSContext *context, uintN argc, jsval *vp); static JSBool ShipEjectSpecificItem(JSContext *context, uintN argc, jsval *vp); static JSBool ShipDumpCargo(JSContext *context, uintN argc, jsval *vp); static JSBool ShipSpawn(JSContext *context, uintN argc, jsval *vp); static JSBool ShipDealEnergyDamage(JSContext *context, uintN argc, jsval *vp); static JSBool ShipExplode(JSContext *context, uintN argc, jsval *vp); static JSBool ShipRemove(JSContext *context, uintN argc, jsval *vp); static JSBool ShipRunLegacyScriptActions(JSContext *context, uintN argc, jsval *vp); static JSBool ShipCommsMessage(JSContext *context, uintN argc, jsval *vp); static JSBool ShipFireECM(JSContext *context, uintN argc, jsval *vp); static JSBool ShipAbandonShip(JSContext *context, uintN argc, jsval *vp); static JSBool ShipCanAwardEquipment(JSContext *context, uintN argc, jsval *vp); static JSBool ShipAwardEquipment(JSContext *context, uintN argc, jsval *vp); static JSBool ShipAdjustCargo(JSContext *context, uintN argc, jsval *vp); static JSBool ShipRequestHelpFromGroup(JSContext *context, uintN argc, jsval *vp); static JSBool ShipPatrolReportIn(JSContext *context, uintN argc, jsval *vp); static JSBool ShipRemoveEquipment(JSContext *context, uintN argc, jsval *vp); static JSBool ShipRestoreSubEntities(JSContext *context, uintN argc, jsval *vp); static JSBool ShipHasEquipmentProviding(JSContext *context, uintN argc, jsval *vp); static JSBool ShipEquipmentStatus(JSContext *context, uintN argc, jsval *vp); static JSBool ShipSetEquipmentStatus(JSContext *context, uintN argc, jsval *vp); static JSBool ShipSelectNewMissile(JSContext *context, uintN argc, jsval *vp); static JSBool ShipFireMissile(JSContext *context, uintN argc, jsval *vp); static JSBool ShipFindNearestStation(JSContext *context, uintN argc, jsval *vp); static JSBool ShipSetBounty(JSContext *context, uintN argc, jsval *vp); static JSBool ShipSetCargo(JSContext *context, uintN argc, jsval *vp); static JSBool ShipSetMaterials(JSContext *context, uintN argc, jsval *vp); static JSBool ShipSetShaders(JSContext *context, uintN argc, jsval *vp); static JSBool ShipExitSystem(JSContext *context, uintN argc, jsval *vp); static JSBool ShipUpdateEscortFormation(JSContext *context, uintN argc, jsval *vp); static JSBool ShipClearDefenseTargets(JSContext *context, uintN argc, jsval *vp); static JSBool ShipAddDefenseTarget(JSContext *context, uintN argc, jsval *vp); static JSBool ShipRemoveDefenseTarget(JSContext *context, uintN argc, jsval *vp); static JSBool ShipAddCollisionException(JSContext *context, uintN argc, jsval *vp); static JSBool ShipRemoveCollisionException(JSContext *context, uintN argc, jsval *vp); static JSBool ShipGetMaterials(JSContext *context, uintN argc, jsval *vp); static JSBool ShipGetShaders(JSContext *context, uintN argc, jsval *vp); static JSBool ShipBecomeCascadeExplosion(JSContext *context, uintN argc, jsval *vp); static JSBool ShipBroadcastCascadeImminent(JSContext *context, uintN argc, jsval *vp); static JSBool ShipBroadcastDistressMessage(JSContext *context, uintN argc, jsval *vp); static JSBool ShipOfferToEscort(JSContext *context, uintN argc, jsval *vp); static JSBool ShipMarkTargetForFines(JSContext *context, uintN argc, jsval *vp); static JSBool ShipEnterWormhole(JSContext *context, uintN argc, jsval *vp); static JSBool ShipNotifyGroupOfWormhole(JSContext *context, uintN argc, jsval *vp); static JSBool ShipThrowSpark(JSContext *context, uintN argc, jsval *vp); static JSBool ShipPerformAttack(JSContext *context, uintN argc, jsval *vp); static JSBool ShipPerformCollect(JSContext *context, uintN argc, jsval *vp); static JSBool ShipPerformEscort(JSContext *context, uintN argc, jsval *vp); static JSBool ShipPerformFaceDestination(JSContext *context, uintN argc, jsval *vp); static JSBool ShipPerformFlee(JSContext *context, uintN argc, jsval *vp); static JSBool ShipPerformFlyToRangeFromDestination(JSContext *context, uintN argc, jsval *vp); static JSBool ShipPerformHold(JSContext *context, uintN argc, jsval *vp); static JSBool ShipPerformIdle(JSContext *context, uintN argc, jsval *vp); static JSBool ShipPerformIntercept(JSContext *context, uintN argc, jsval *vp); static JSBool ShipPerformLandOnPlanet(JSContext *context, uintN argc, jsval *vp); static JSBool ShipPerformMining(JSContext *context, uintN argc, jsval *vp); static JSBool ShipPerformScriptedAI(JSContext *context, uintN argc, jsval *vp); static JSBool ShipPerformScriptedAttackAI(JSContext *context, uintN argc, jsval *vp); static JSBool ShipPerformStop(JSContext *context, uintN argc, jsval *vp); static JSBool ShipPerformTumble(JSContext *context, uintN argc, jsval *vp); static JSBool ShipRequestDockingInstructions(JSContext *context, uintN argc, jsval *vp); static JSBool ShipRecallDockingInstructions(JSContext *context, uintN argc, jsval *vp); static JSBool ShipCheckCourseToDestination(JSContext *context, uintN argc, jsval *vp); static JSBool ShipGetSafeCourseToDestination(JSContext *context, uintN argc, jsval *vp); static JSBool ShipCheckScanner(JSContext *context, uintN argc, jsval *vp); static JSBool ShipThreatAssessment(JSContext *context, uintN argc, jsval *vp); static JSBool ShipDamageAssessment(JSContext *context, uintN argc, jsval *vp); static double ShipThreatAssessmentWeapon(OOWeaponType wt); static JSBool ShipSetCargoType(JSContext *context, uintN argc, jsval *vp); static JSBool ShipSetCrew(JSContext *context, uintN argc, jsval *vp); static BOOL RemoveOrExplodeShip(JSContext *context, uintN argc, jsval *vp, BOOL explode); static JSBool ShipSetMaterialsInternal(JSContext *context, uintN argc, jsval *vp, ShipEntity *thisEnt, BOOL fromShaders); static JSBool ShipStaticKeysForRole(JSContext *context, uintN argc, jsval *vp); static JSBool ShipStaticKeys(JSContext *context, uintN argc, jsval *vp); static JSBool ShipStaticRoles(JSContext *context, uintN argc, jsval *vp); static JSBool ShipStaticRoleIsInCategory(JSContext *context, uintN argc, jsval *vp); static JSBool ShipStaticShipDataForKey(JSContext *context, uintN argc, jsval *vp); static JSClass sShipClass = { "Ship", JSCLASS_HAS_PRIVATE, JS_PropertyStub, // addProperty JS_PropertyStub, // delProperty ShipGetProperty, // getProperty ShipSetProperty, // setProperty JS_EnumerateStub, // enumerate JS_ResolveStub, // resolve JS_ConvertStub, // convert OOJSObjectWrapperFinalize,// finalize JSCLASS_NO_OPTIONAL_MEMBERS }; /* It turns out that the value in SpiderMonkey used to identify these * enums is an 8-bit signed int: * developer.mozilla.org/en/docs/SpiderMonkey/JSAPI_Reference/JSPropertySpec * which puts a limit of 256 properties on the ship object. Moved the * enum to start at -128, so we can use the full 256 rather than just * 128 of them. I don't think any of our other classes are getting * close to the limit yet. * - CIM 29/9/2013 */ enum { // Property IDs kShip_accuracy = -128, // the ship's accuracy, float, read/write kShip_aftWeapon, // the ship's aft weapon, equipmentType, read/write kShip_AI, // AI state machine name, string, read-only kShip_AIScript, // AI script, Script, read-only kShip_AIScriptWakeTime, // next wakeup time, integer, read/write kShip_AIState, // AI state machine state, string, read/write kShip_AIFoundTarget, // AI "found target", entity, read/write kShip_AIPrimaryAggressor, // AI "primary aggressor", entity, read/write kShip_alertCondition, // number 0-3, read-only, combat alert level kShip_autoAI, // bool, read-only, auto_ai from shipdata kShip_autoWeapons, // bool, read-only, auto_weapons from shipdata kShip_beaconCode, // beacon code, string, read/write kShip_beaconLabel, // beacon label, string, read/write kShip_boundingBox, // boundingBox, vector, read-only kShip_bounty, // bounty, unsigned int, read/write kShip_cargoList, // cargo on board, array of objects, read-only kShip_cargoSpaceAvailable, // free cargo space, integer, read-only kShip_cargoSpaceCapacity, // maximum cargo, integer, read/write kShip_cargoSpaceUsed, // cargo on board, integer, read-only kShip_collisionExceptions, // collision exception list, array, read-only kShip_contracts, // cargo contracts contracts, array - strings & whatnot, read only kShip_commodity, // commodity of a ship, read only kShip_commodityAmount, // commodityAmount of a ship, read only kShip_cloakAutomatic, // should cloack start by itself or by script, read/write kShip_crew, // crew, list, read only kShip_cruiseSpeed, // desired cruising speed, number, read only kShip_currentWeapon, // the ship's active weapon, equipmentType, read/write kShip_dataKey, // string, read-only, shipdata.plist key kShip_defenseTargets, // array, read-only, defense targets kShip_desiredRange, // desired Range, double, read/write kShip_desiredSpeed, // AI desired flight speed, double, read/write kShip_destination, // flight destination, Vector, read/write kShip_destinationSystem, // destination system, number, read/write kShip_displayName, // name displayed on screen, string, read/write kShip_dockingInstructions, // name displayed on screen, string, read/write kShip_energyRechargeRate, // energy recharge rate, float, read-only kShip_entityPersonality, // per-ship random number, int, read-only kShip_equipment, // the ship's equipment, array of EquipmentInfo, read only kShip_escortGroup, // group, ShipGroup, read-only kShip_escorts, // deployed escorts, array of Ship, read-only kShip_exhaustEmissiveColor, // exhaust emissive color, array, read/write kShip_exhausts, // exhausts, array, read-only kShip_extraCargo, // cargo space increase granted by large cargo bay, int, read-only kShip_flashers, // flashers, array, read-only kShip_forwardWeapon, // the ship's forward weapon, equipmentType, read/write kShip_fuel, // fuel, float, read/write kShip_fuelChargeRate, // fuel scoop rate & charge multiplier, float, read-only kShip_group, // group, ShipGroup, read/write kShip_hasHostileTarget, // has hostile target, boolean, read-only kShip_hasHyperspaceMotor, // has hyperspace motor, boolean, read-only kShip_hasSuspendedAI, // AI has suspended states, boolean, read-only kShip_heading, // forwardVector of a ship, read-only kShip_heatInsulation, // hull heat insulation, double, read/write kShip_homeSystem, // home system, number, read/write kShip_hyperspaceSpinTime, // hyperspace spin time, float, read/write kShip_injectorBurnRate, // injector burn rate, number, read/write dLY/s kShip_injectorSpeedFactor, // injector speed factor, number, read/write kShip_isBeacon, // is beacon, boolean, read-only kShip_isBoulder, // is a boulder (generates splinters), boolean, read/write kShip_isCargo, // contains cargo, boolean, read-only kShip_isCloaked, // cloaked, boolean, read/write (if cloaking device installed) kShip_isDerelict, // is an abandoned ship, boolean, read-only kShip_isFrangible, // frangible, boolean, read-only kShip_isFleeing, // is fleeing, boolean, read-only kShip_isJamming, // jamming scanners, boolean, read/write (if jammer installed) kShip_isMinable, // is a sensible target for mining, boolean, read-only kShip_isMine, // is mine, boolean, read-only kShip_isMissile, // is missile, boolean, read-only kShip_isPiloted, // is piloted, boolean, read-only (includes stations) kShip_isPirate, // is pirate, boolean, read-only kShip_isPirateVictim, // is pirate victim, boolean, read-only kShip_isPolice, // is police, boolean, read-only kShip_isRock, // is a rock (hermits included), boolean, read-only kShip_isThargoid, // is thargoid, boolean, read-only kShip_isTurret, // is turret, boolean, read-only kShip_isTrader, // is trader, boolean, read-only kShip_isWeapon, // is missile or mine, boolean, read-only kShip_laserHeatLevel, // active laser temperature, float, read-only kShip_laserHeatLevelAft, // aft laser temperature, float, read-only kShip_laserHeatLevelForward, // fore laser temperature, float, read-only kShip_laserHeatLevelPort, // port laser temperature, float, read-only kShip_laserHeatLevelStarboard, // starboard laser temperature, float, read-only kShip_lightsActive, // flasher/shader light flag, boolean, read/write kShip_markedForFines, // has been marked for fines kShip_maxEscorts, // maximum escort count, int, read/write kShip_maxPitch, // maximum flight pitch, double, read-only kShip_maxSpeed, // maximum flight speed, double, read-only kShip_maxRoll, // maximum flight roll, double, read-only kShip_maxYaw, // maximum flight yaw, double, read-only kShip_maxThrust, // maximum thrust, double, read-only kShip_missileCapacity, // max missiles capacity, integer, read-only kShip_missileLoadTime, // missile load time, double, read/write kShip_missiles, // the ship's missiles / external storage, array of equipmentTypes, read only kShip_name, // name, string, read-only kShip_parcelCount, // number of parcels on ship, integer, read-only kShip_parcels, // parcel contracts, array - strings & whatnot, read only kShip_passengerCapacity, // amount of passenger space on ship, integer, read-only kShip_passengerCount, // number of passengers on ship, integer, read-only kShip_passengers, // passengers contracts, array - strings & whatnot, read only kShip_pitch, // pitch level, float, read-only kShip_portWeapon, // the ship's port weapon, equipmentType, read/write kShip_potentialCollider, // "proximity alert" ship, Entity, read-only kShip_primaryRole, // Primary role, string, read/write kShip_reactionTime, // AI reaction time, read/write kShip_reportAIMessages, // report AI messages, boolean, read/write kShip_roleWeights, // roles and weights, dictionary, read-only kShip_roles, // roles, array, read-only kShip_roll, // roll level, float, read-only kShip_savedCoordinates, // coordinates in system space for AI use, Vector, read/write kShip_scanDescription, // STE scan class label, string, read/write kShip_scannerDisplayColor1, // color of lollipop shown on scanner, array, read/write kShip_scannerDisplayColor2, // color of lollipop shown on scanner when flashing, array, read/write kShip_scannerHostileDisplayColor1, // color of lollipop shown on scanner, array, read/write kShip_scannerHostileDisplayColor2, // color of lollipop shown on scanner when flashing, array, read/write kShip_scannerRange, // scanner range, double, read-only kShip_script, // script, Script, read-only kShip_scriptedMisjump, // next jump will miss if set to true, boolean, read/write kShip_scriptedMisjumpRange, // 0..1 range of next misjump, float, read/write kShip_scriptInfo, // arbitrary data for scripts, dictionary, read-only kShip_shipClassName, // ship type name, string, read/write kShip_shipUniqueName, // uniqish name, string, read/write kShip_speed, // current flight speed, double, read-only kShip_starboardWeapon, // the ship's starboard weapon, equipmentType, read/write kShip_subEntities, // subentities, array of Ship, read-only kShip_subEntityCapacity, // max subentities for this ship, int, read-only kShip_subEntityRotation, // subentity rotation velocity, quaternion, read/write kShip_sunGlareFilter, // sun glare filter multiplier, float, read/write kShip_target, // target, Ship, read/write kShip_temperature, // hull temperature, double, read/write kShip_thrust, // the ship's thrust, double, read/write kShip_thrustVector, // thrust-related component of velocity, vector, read-only kShip_trackCloseContacts, // generate close contact events, boolean, read/write kShip_vectorForward, // forwardVector of a ship, read-only kShip_vectorRight, // rightVector of a ship, read-only kShip_vectorUp, // upVector of a ship, read-only kShip_velocity, // velocity, vector, read/write kShip_weaponFacings, // available facings, int, read-only kShip_weaponPositionAft, // weapon offset, vector, read-only kShip_weaponPositionForward, // weapon offset, vector, read-only kShip_weaponPositionPort, // weapon offset, vector, read-only kShip_weaponPositionStarboard, // weapon offset, vector, read-only kShip_weaponRange, // weapon range, double, read-only kShip_withinStationAegis, // within main station aegis, boolean, read/write kShip_yaw, // yaw level, float, read-only }; static JSPropertySpec sShipProperties[] = { // JS name ID flags { "accuracy", kShip_accuracy, OOJS_PROP_READWRITE_CB }, { "aftWeapon", kShip_aftWeapon, OOJS_PROP_READWRITE_CB }, { "AI", kShip_AI, OOJS_PROP_READONLY_CB }, { "AIScript", kShip_AIScript, OOJS_PROP_READONLY_CB }, { "AIScriptWakeTime", kShip_AIScriptWakeTime, OOJS_PROP_READWRITE_CB }, { "AIState", kShip_AIState, OOJS_PROP_READWRITE_CB }, { "AIFoundTarget", kShip_AIFoundTarget, OOJS_PROP_READWRITE_CB }, { "AIPrimaryAggressor", kShip_AIPrimaryAggressor, OOJS_PROP_READWRITE_CB }, { "alertCondition", kShip_alertCondition, OOJS_PROP_READONLY_CB }, { "autoAI", kShip_autoAI, OOJS_PROP_READONLY_CB }, { "autoWeapons", kShip_autoWeapons, OOJS_PROP_READONLY_CB }, { "beaconCode", kShip_beaconCode, OOJS_PROP_READWRITE_CB }, { "beaconLabel", kShip_beaconLabel, OOJS_PROP_READWRITE_CB }, { "boundingBox", kShip_boundingBox, OOJS_PROP_READONLY_CB }, { "bounty", kShip_bounty, OOJS_PROP_READWRITE_CB }, { "cargoList", kShip_cargoList, OOJS_PROP_READONLY_CB }, { "cargoSpaceUsed", kShip_cargoSpaceUsed, OOJS_PROP_READONLY_CB }, { "cargoSpaceCapacity", kShip_cargoSpaceCapacity, OOJS_PROP_READWRITE_CB }, { "cargoSpaceAvailable", kShip_cargoSpaceAvailable, OOJS_PROP_READONLY_CB }, { "collisionExceptions", kShip_collisionExceptions, OOJS_PROP_READONLY_CB }, { "commodity", kShip_commodity, OOJS_PROP_READONLY_CB }, { "commodityAmount", kShip_commodityAmount, OOJS_PROP_READONLY_CB }, // contracts instead of cargo to distinguish them from the manifest { "contracts", kShip_contracts, OOJS_PROP_READONLY_CB }, { "cloakAutomatic", kShip_cloakAutomatic, OOJS_PROP_READWRITE_CB}, { "crew", kShip_crew, OOJS_PROP_READONLY_CB }, { "cruiseSpeed", kShip_cruiseSpeed, OOJS_PROP_READONLY_CB }, { "currentWeapon", kShip_currentWeapon, OOJS_PROP_READWRITE_CB }, { "dataKey", kShip_dataKey, OOJS_PROP_READONLY_CB }, { "defenseTargets", kShip_defenseTargets, OOJS_PROP_READONLY_CB }, { "desiredRange", kShip_desiredRange, OOJS_PROP_READWRITE_CB }, { "desiredSpeed", kShip_desiredSpeed, OOJS_PROP_READWRITE_CB }, { "destination", kShip_destination, OOJS_PROP_READWRITE_CB }, { "destinationSystem", kShip_destinationSystem, OOJS_PROP_READWRITE_CB }, { "displayName", kShip_displayName, OOJS_PROP_READWRITE_CB }, { "dockingInstructions", kShip_dockingInstructions, OOJS_PROP_READONLY_CB }, { "energyRechargeRate", kShip_energyRechargeRate, OOJS_PROP_READWRITE_CB }, { "entityPersonality", kShip_entityPersonality, OOJS_PROP_READWRITE_CB }, { "equipment", kShip_equipment, OOJS_PROP_READONLY_CB }, { "escorts", kShip_escorts, OOJS_PROP_READONLY_CB }, { "escortGroup", kShip_escortGroup, OOJS_PROP_READONLY_CB }, { "exhaustEmissiveColor", kShip_exhaustEmissiveColor, OOJS_PROP_READWRITE_CB }, { "exhausts", kShip_exhausts, OOJS_PROP_READONLY_CB }, { "extraCargo", kShip_extraCargo, OOJS_PROP_READONLY_CB }, { "flashers", kShip_flashers, OOJS_PROP_READONLY_CB }, { "forwardWeapon", kShip_forwardWeapon, OOJS_PROP_READWRITE_CB }, { "fuel", kShip_fuel, OOJS_PROP_READWRITE_CB }, { "fuelChargeRate", kShip_fuelChargeRate, OOJS_PROP_READONLY_CB }, { "group", kShip_group, OOJS_PROP_READWRITE_CB }, { "hasHostileTarget", kShip_hasHostileTarget, OOJS_PROP_READONLY_CB }, { "hasHyperspaceMotor", kShip_hasHyperspaceMotor, OOJS_PROP_READONLY_CB }, { "hasSuspendedAI", kShip_hasSuspendedAI, OOJS_PROP_READONLY_CB }, { "heatInsulation", kShip_heatInsulation, OOJS_PROP_READWRITE_CB }, { "heading", kShip_heading, OOJS_PROP_READONLY_CB }, { "homeSystem", kShip_homeSystem, OOJS_PROP_READWRITE_CB }, { "hyperspaceSpinTime", kShip_hyperspaceSpinTime, OOJS_PROP_READWRITE_CB }, { "injectorBurnRate", kShip_injectorBurnRate, OOJS_PROP_READWRITE_CB }, { "injectorSpeedFactor", kShip_injectorSpeedFactor, OOJS_PROP_READWRITE_CB }, { "homeSystem", kShip_homeSystem, OOJS_PROP_READWRITE_CB }, { "isBeacon", kShip_isBeacon, OOJS_PROP_READONLY_CB }, { "isCloaked", kShip_isCloaked, OOJS_PROP_READWRITE_CB }, { "isCargo", kShip_isCargo, OOJS_PROP_READONLY_CB }, { "isDerelict", kShip_isDerelict, OOJS_PROP_READONLY_CB }, { "isFrangible", kShip_isFrangible, OOJS_PROP_READONLY_CB }, { "isFleeing", kShip_isFleeing, OOJS_PROP_READONLY_CB }, { "isJamming", kShip_isJamming, OOJS_PROP_READONLY_CB }, { "isMinable", kShip_isMinable, OOJS_PROP_READONLY_CB }, { "isMine", kShip_isMine, OOJS_PROP_READONLY_CB }, { "isMissile", kShip_isMissile, OOJS_PROP_READONLY_CB }, { "isPiloted", kShip_isPiloted, OOJS_PROP_READONLY_CB }, { "isPirate", kShip_isPirate, OOJS_PROP_READONLY_CB }, { "isPirateVictim", kShip_isPirateVictim, OOJS_PROP_READONLY_CB }, { "isPolice", kShip_isPolice, OOJS_PROP_READONLY_CB }, { "isRock", kShip_isRock, OOJS_PROP_READONLY_CB }, { "isBoulder", kShip_isBoulder, OOJS_PROP_READWRITE_CB }, { "isThargoid", kShip_isThargoid, OOJS_PROP_READONLY_CB }, { "isTurret", kShip_isTurret, OOJS_PROP_READONLY_CB }, { "isTrader", kShip_isTrader, OOJS_PROP_READONLY_CB }, { "isWeapon", kShip_isWeapon, OOJS_PROP_READONLY_CB }, { "laserHeatLevel", kShip_laserHeatLevel, OOJS_PROP_READONLY_CB }, { "laserHeatLevelAft", kShip_laserHeatLevelAft, OOJS_PROP_READONLY_CB }, { "laserHeatLevelForward", kShip_laserHeatLevelForward, OOJS_PROP_READONLY_CB }, { "laserHeatLevelPort", kShip_laserHeatLevelPort, OOJS_PROP_READONLY_CB }, { "laserHeatLevelStarboard", kShip_laserHeatLevelStarboard, OOJS_PROP_READONLY_CB }, { "lightsActive", kShip_lightsActive, OOJS_PROP_READWRITE_CB }, { "markedForFines", kShip_markedForFines, OOJS_PROP_READONLY_CB }, { "maxEscorts", kShip_maxEscorts, OOJS_PROP_READWRITE_CB }, { "maxPitch", kShip_maxPitch, OOJS_PROP_READWRITE_CB }, { "maxSpeed", kShip_maxSpeed, OOJS_PROP_READWRITE_CB }, { "maxRoll", kShip_maxRoll, OOJS_PROP_READWRITE_CB }, { "maxYaw", kShip_maxYaw, OOJS_PROP_READWRITE_CB }, { "maxThrust", kShip_maxThrust, OOJS_PROP_READWRITE_CB }, { "missileCapacity", kShip_missileCapacity, OOJS_PROP_READONLY_CB }, { "missileLoadTime", kShip_missileLoadTime, OOJS_PROP_READWRITE_CB }, { "missiles", kShip_missiles, OOJS_PROP_READONLY_CB }, { "name", kShip_name, OOJS_PROP_READWRITE_CB }, { "parcelCount", kShip_parcelCount, OOJS_PROP_READONLY_CB }, { "parcels", kShip_parcels, OOJS_PROP_READONLY_CB }, { "passengerCount", kShip_passengerCount, OOJS_PROP_READONLY_CB }, { "passengerCapacity", kShip_passengerCapacity, OOJS_PROP_READONLY_CB }, { "passengers", kShip_passengers, OOJS_PROP_READONLY_CB }, { "pitch", kShip_pitch, OOJS_PROP_READONLY_CB }, { "portWeapon", kShip_portWeapon, OOJS_PROP_READWRITE_CB }, { "potentialCollider", kShip_potentialCollider, OOJS_PROP_READONLY_CB }, { "primaryRole", kShip_primaryRole, OOJS_PROP_READWRITE_CB }, { "reactionTime", kShip_reactionTime, OOJS_PROP_READWRITE_CB }, { "reportAIMessages", kShip_reportAIMessages, OOJS_PROP_READWRITE_CB }, { "roleWeights", kShip_roleWeights, OOJS_PROP_READONLY_CB }, { "roles", kShip_roles, OOJS_PROP_READONLY_CB }, { "roll", kShip_roll, OOJS_PROP_READONLY_CB }, { "savedCoordinates", kShip_savedCoordinates, OOJS_PROP_READWRITE_CB }, { "scanDescription", kShip_scanDescription, OOJS_PROP_READWRITE_CB }, { "scannerDisplayColor1", kShip_scannerDisplayColor1, OOJS_PROP_READWRITE_CB }, { "scannerDisplayColor2", kShip_scannerDisplayColor2, OOJS_PROP_READWRITE_CB }, { "scannerHostileDisplayColor1", kShip_scannerHostileDisplayColor1, OOJS_PROP_READWRITE_CB }, { "scannerHostileDisplayColor2", kShip_scannerHostileDisplayColor2, OOJS_PROP_READWRITE_CB }, { "scannerRange", kShip_scannerRange, OOJS_PROP_READONLY_CB }, { "script", kShip_script, OOJS_PROP_READONLY_CB }, { "scriptedMisjump", kShip_scriptedMisjump, OOJS_PROP_READWRITE_CB }, { "scriptedMisjumpRange", kShip_scriptedMisjumpRange, OOJS_PROP_READWRITE_CB }, { "scriptInfo", kShip_scriptInfo, OOJS_PROP_READONLY_CB }, { "shipClassName", kShip_shipClassName, OOJS_PROP_READWRITE_CB }, { "shipUniqueName", kShip_shipUniqueName, OOJS_PROP_READWRITE_CB }, { "speed", kShip_speed, OOJS_PROP_READONLY_CB }, { "starboardWeapon", kShip_starboardWeapon, OOJS_PROP_READWRITE_CB }, { "subEntities", kShip_subEntities, OOJS_PROP_READONLY_CB }, { "subEntityCapacity", kShip_subEntityCapacity, OOJS_PROP_READONLY_CB }, { "subEntityRotation", kShip_subEntityRotation, OOJS_PROP_READWRITE_CB }, { "sunGlareFilter", kShip_sunGlareFilter, OOJS_PROP_READWRITE_CB }, { "target", kShip_target, OOJS_PROP_READWRITE_CB }, { "temperature", kShip_temperature, OOJS_PROP_READWRITE_CB }, { "thrust", kShip_thrust, OOJS_PROP_READWRITE_CB }, { "thrustVector", kShip_thrustVector, OOJS_PROP_READONLY_CB }, { "trackCloseContacts", kShip_trackCloseContacts, OOJS_PROP_READWRITE_CB }, { "vectorForward", kShip_vectorForward, OOJS_PROP_READONLY_CB }, { "vectorRight", kShip_vectorRight, OOJS_PROP_READONLY_CB }, { "vectorUp", kShip_vectorUp, OOJS_PROP_READONLY_CB }, { "velocity", kShip_velocity, OOJS_PROP_READWRITE_CB }, { "weaponFacings", kShip_weaponFacings, OOJS_PROP_READONLY_CB }, { "weaponPositionAft", kShip_weaponPositionAft, OOJS_PROP_READONLY_CB }, { "weaponPositionForward", kShip_weaponPositionForward, OOJS_PROP_READONLY_CB }, { "weaponPositionPort", kShip_weaponPositionPort, OOJS_PROP_READONLY_CB }, { "weaponPositionStarboard", kShip_weaponPositionStarboard, OOJS_PROP_READONLY_CB }, { "weaponRange", kShip_weaponRange, OOJS_PROP_READONLY_CB }, { "withinStationAegis", kShip_withinStationAegis, OOJS_PROP_READONLY_CB }, { "yaw", kShip_yaw, OOJS_PROP_READONLY_CB }, { 0 } }; static JSFunctionSpec sShipMethods[] = { // JS name Function min args { "abandonShip", ShipAbandonShip, 0 }, { "addCollisionException", ShipAddCollisionException, 1 }, { "addDefenseTarget", ShipAddDefenseTarget, 1 }, { "adjustCargo", ShipAdjustCargo, 2 }, { "awardEquipment", ShipAwardEquipment, 1 }, { "becomeCascadeExplosion", ShipBecomeCascadeExplosion, 0 }, { "broadcastCascadeImminent", ShipBroadcastCascadeImminent, 0 }, { "broadcastDistressMessage", ShipBroadcastDistressMessage, 0 }, { "canAwardEquipment", ShipCanAwardEquipment, 1 }, { "checkCourseToDestination", ShipCheckCourseToDestination, 0 }, { "checkScanner", ShipCheckScanner, 0 }, { "clearDefenseTargets", ShipClearDefenseTargets, 0 }, { "commsMessage", ShipCommsMessage, 1 }, { "damageAssessment", ShipDamageAssessment, 0 }, { "dealEnergyDamage", ShipDealEnergyDamage, 2 }, { "deployEscorts", ShipDeployEscorts, 0 }, { "dockEscorts", ShipDockEscorts, 0 }, { "dumpCargo", ShipDumpCargo, 0 }, { "ejectItem", ShipEjectItem, 1 }, { "ejectSpecificItem", ShipEjectSpecificItem, 1 }, { "enterWormhole", ShipEnterWormhole, 0 }, { "equipmentStatus", ShipEquipmentStatus, 1 }, { "exitAI", ShipExitAI, 0 }, { "exitSystem", ShipExitSystem, 0 }, { "explode", ShipExplode, 0 }, { "fireECM", ShipFireECM, 0 }, { "fireMissile", ShipFireMissile, 0 }, { "findNearestStation", ShipFindNearestStation, 0 }, { "getMaterials", ShipGetMaterials, 0 }, { "getSafeCourseToDestination", ShipGetSafeCourseToDestination, 0 }, { "getShaders", ShipGetShaders, 0 }, { "hasEquipmentProviding", ShipHasEquipmentProviding, 1 }, { "hasRole", ShipHasRole, 1 }, { "markTargetForFines", ShipMarkTargetForFines, 0 }, { "notifyGroupOfWormhole", ShipNotifyGroupOfWormhole, 0 }, { "offerToEscort", ShipOfferToEscort, 1 }, { "patrolReportIn", ShipPatrolReportIn, 1}, { "performAttack", ShipPerformAttack, 0 }, { "performCollect", ShipPerformCollect, 0 }, { "performEscort", ShipPerformEscort, 0 }, { "performFaceDestination", ShipPerformFaceDestination, 0 }, { "performFlee", ShipPerformFlee, 0 }, { "performFlyToRangeFromDestination", ShipPerformFlyToRangeFromDestination, 0 }, { "performHold", ShipPerformHold, 0 }, { "performIdle", ShipPerformIdle, 0 }, { "performIntercept", ShipPerformIntercept, 0 }, { "performLandOnPlanet", ShipPerformLandOnPlanet, 0 }, { "performMining", ShipPerformMining, 0 }, { "performScriptedAI", ShipPerformScriptedAI, 0 }, { "performScriptedAttackAI", ShipPerformScriptedAttackAI, 0 }, { "performStop", ShipPerformStop, 0 }, { "performTumble", ShipPerformTumble, 0 }, { "reactToAIMessage", ShipReactToAIMessage, 1 }, { "remove", ShipRemove, 0 }, { "removeCollisionException", ShipRemoveCollisionException, 1 }, { "removeDefenseTarget", ShipRemoveDefenseTarget, 1 }, { "removeEquipment", ShipRemoveEquipment, 1 }, { "requestHelpFromGroup", ShipRequestHelpFromGroup, 0}, { "requestDockingInstructions", ShipRequestDockingInstructions, 0}, { "recallDockingInstructions", ShipRecallDockingInstructions, 0}, { "restoreSubEntities", ShipRestoreSubEntities, 0 }, { "__runLegacyScriptActions", ShipRunLegacyScriptActions, 2 }, // Deliberately not documented { "selectNewMissile", ShipSelectNewMissile, 0 }, { "sendAIMessage", ShipSendAIMessage, 1 }, { "setAI", ShipSetAI, 1 }, { "setBounty", ShipSetBounty, 2 }, { "setCargo", ShipSetCargo, 1 }, { "setCargoType", ShipSetCargoType, 1 }, { "setCrew", ShipSetCrew, 1 }, { "setEquipmentStatus", ShipSetEquipmentStatus, 2 }, { "setMaterials", ShipSetMaterials, 1 }, { "setScript", ShipSetScript, 1 }, { "setShaders", ShipSetShaders, 2 }, { "spawn", ShipSpawn, 1 }, // spawnOne() is defined in the prefix script. { "switchAI", ShipSwitchAI, 1 }, { "threatAssessment", ShipThreatAssessment, 1 }, { "throwSpark", ShipThrowSpark, 0 }, { "updateEscortFormation", ShipUpdateEscortFormation, 0 }, { 0 } }; static JSFunctionSpec sShipStaticMethods[] = { // JS name Function min args { "keys", ShipStaticKeys, 0 }, { "keysForRole", ShipStaticKeysForRole, 1 }, { "roleIsInCategory", ShipStaticRoleIsInCategory, 2 }, { "roles", ShipStaticRoles, 0 }, { "shipDataForKey", ShipStaticShipDataForKey, 1 }, { 0 } }; DEFINE_JS_OBJECT_GETTER(JSShipGetShipEntity, &sShipClass, sShipPrototype, ShipEntity) void InitOOJSShip(JSContext *context, JSObject *global) { sShipPrototype = JS_InitClass(context, global, JSEntityPrototype(), &sShipClass, OOJSUnconstructableConstruct, 0, sShipProperties, sShipMethods, NULL, sShipStaticMethods); OOJSRegisterObjectConverter(&sShipClass, OOJSBasicPrivateObjectConverter); OOJSRegisterSubclass(&sShipClass, JSEntityClass()); } JSClass *JSShipClass(void) { return &sShipClass; } JSObject *JSShipPrototype(void) { return sShipPrototype; } static JSBool ShipGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) ShipEntity *entity = nil; id result = nil; if (EXPECT_NOT(!JSShipGetShipEntity(context, this, &entity))) return NO; if (OOIsStaleEntity(entity)) { *value = JSVAL_VOID; return YES; } switch (JSID_TO_INT(propID)) { case kShip_name: result = [entity name]; break; case kShip_displayName: result = [entity displayName]; break; case kShip_shipUniqueName: result = [entity shipUniqueName]; break; case kShip_shipClassName: result = [entity shipClassName]; break; case kShip_scanDescription: result = [entity scanDescriptionForScripting]; break; case kShip_roles: result = [[entity roleSet] sortedRoles]; break; case kShip_roleWeights: result = [[entity roleSet] rolesAndProbabilities]; break; case kShip_primaryRole: result = [entity primaryRole]; break; case kShip_AI: result = [[entity getAI] name]; break; case kShip_AIState: result = [[entity getAI] state]; break; case kShip_AIFoundTarget: result = [entity foundTarget]; break; case kShip_AIPrimaryAggressor: result = [entity primaryAggressor]; break; case kShip_alertCondition: return JS_NewNumberValue(context, [entity realAlertCondition], value); case kShip_autoAI: *value = OOJSValueFromBOOL([entity hasAutoAI]); return YES; case kShip_autoWeapons: *value = OOJSValueFromBOOL([entity hasAutoWeapons]); return YES; case kShip_accuracy: return JS_NewNumberValue(context, [entity accuracy], value); case kShip_fuel: return JS_NewNumberValue(context, [entity fuel] * 0.1, value); case kShip_fuelChargeRate: return JS_NewNumberValue(context, [entity fuelChargeRate], value); case kShip_bounty: return JS_NewNumberValue(context, [entity bounty], value); return YES; case kShip_subEntities: result = [entity subEntitiesForScript]; break; case kShip_exhausts: result = [[entity exhaustEnumerator] allObjects]; break; case kShip_flashers: result = [[entity flasherEnumerator] allObjects]; break; case kShip_subEntityCapacity: return JS_NewNumberValue(context, [entity maxShipSubEntities], value); return YES; case kShip_subEntityRotation: return QuaternionToJSValue(context, [entity subEntityRotationalVelocity], value); case kShip_hasSuspendedAI: *value = OOJSValueFromBOOL([[entity getAI] hasSuspendedStateMachines]); return YES; case kShip_target: result = [entity primaryTarget]; break; case kShip_defenseTargets: { [entity validateDefenseTargets]; result = [NSMutableArray arrayWithCapacity:[entity defenseTargetCount]]; NSEnumerator *defTargets = [entity defenseTargetEnumerator]; Entity *target = nil; while ((target = [[defTargets nextObject] weakRefUnderlyingObject])) { [result addObject:target]; } break; } case kShip_crew: result = [entity crewForScripting]; break; case kShip_escorts: result = [[entity escortGroup] memberArrayExcludingLeader]; break; case kShip_group: result = [entity group]; break; case kShip_escortGroup: result = [entity escortGroup]; break; case kShip_temperature: return JS_NewNumberValue(context, [entity temperature] / SHIP_MAX_CABIN_TEMP, value); case kShip_heatInsulation: return JS_NewNumberValue(context, [entity heatInsulation], value); case kShip_heading: return VectorToJSValue(context, [entity forwardVector], value); case kShip_energyRechargeRate: return JS_NewNumberValue(context, [entity energyRechargeRate], value); case kShip_entityPersonality: *value = INT_TO_JSVAL([entity entityPersonalityInt]); return YES; case kShip_isBeacon: *value = OOJSValueFromBOOL([entity isBeacon]); return YES; case kShip_beaconCode: result = [entity beaconCode]; break; case kShip_beaconLabel: result = [entity beaconLabel]; break; case kShip_isFrangible: *value = OOJSValueFromBOOL([entity isFrangible]); return YES; case kShip_isCloaked: *value = OOJSValueFromBOOL([entity isCloaked]); return YES; case kShip_cloakAutomatic: *value = OOJSValueFromBOOL([entity hasAutoCloak]); return YES; case kShip_isJamming: *value = OOJSValueFromBOOL([entity isJammingScanning]); return YES; case kShip_potentialCollider: result = [entity proximityAlert]; break; case kShip_hasHostileTarget: *value = OOJSValueFromBOOL([entity hasHostileTarget]); return YES; case kShip_hasHyperspaceMotor: *value = OOJSValueFromBOOL([entity hasHyperspaceMotor]); return YES; case kShip_hyperspaceSpinTime: return JS_NewNumberValue(context, [entity hyperspaceSpinTime], value); case kShip_weaponRange: return JS_NewNumberValue(context, [entity weaponRange], value); case kShip_weaponFacings: if ([entity isPlayer]) { PlayerEntity *pent = (PlayerEntity*)entity; return JS_NewNumberValue(context, [pent availableFacings], value); } return JS_NewNumberValue(context, [entity weaponFacings], value); case kShip_weaponPositionAft: return VectorToJSValue(context, [entity aftWeaponOffset], value); case kShip_weaponPositionForward: return VectorToJSValue(context, [entity forwardWeaponOffset], value); case kShip_weaponPositionPort: return VectorToJSValue(context, [entity portWeaponOffset], value); case kShip_weaponPositionStarboard: return VectorToJSValue(context, [entity starboardWeaponOffset], value); case kShip_scannerRange: return JS_NewNumberValue(context, [entity scannerRange], value); case kShip_reactionTime: return JS_NewNumberValue(context, [entity reactionTime], value); case kShip_reportAIMessages: *value = OOJSValueFromBOOL([entity reportAIMessages]); return YES; case kShip_withinStationAegis: *value = OOJSValueFromBOOL([entity withinStationAegis]); return YES; case kShip_cargoSpaceCapacity: *value = INT_TO_JSVAL([entity maxAvailableCargoSpace]); return YES; case kShip_cargoSpaceUsed: *value = INT_TO_JSVAL([entity maxAvailableCargoSpace] - [entity availableCargoSpace]); return YES; case kShip_cargoSpaceAvailable: *value = INT_TO_JSVAL([entity availableCargoSpace]); return YES; case kShip_cargoList: result = [entity cargoListForScripting]; break; case kShip_extraCargo: return JS_NewNumberValue(context, [entity extraCargo], value); return YES; case kShip_commodity: if ([entity commodityAmount] > 0) { result = [entity commodityType]; } break; case kShip_commodityAmount: *value = INT_TO_JSVAL([entity commodityAmount]); return YES; case kShip_collisionExceptions: result = [entity collisionExceptions]; break; case kShip_speed: return JS_NewNumberValue(context, [entity flightSpeed], value); case kShip_cruiseSpeed: return JS_NewNumberValue(context, [entity cruiseSpeed], value); case kShip_dataKey: result = [entity shipDataKey]; break; case kShip_desiredRange: return JS_NewNumberValue(context, [entity desiredRange], value); case kShip_desiredSpeed: return JS_NewNumberValue(context, [entity desiredSpeed], value); case kShip_destination: return HPVectorToJSValue(context, [entity destination], value); case kShip_markedForFines: *value = OOJSValueFromBOOL([entity markedForFines]); return YES; case kShip_maxEscorts: return JS_NewNumberValue(context, [entity maxEscortCount], value); case kShip_maxPitch: return JS_NewNumberValue(context, [entity maxFlightPitch], value); case kShip_maxSpeed: return JS_NewNumberValue(context, [entity maxFlightSpeed], value); case kShip_maxRoll: return JS_NewNumberValue(context, [entity maxFlightRoll], value); case kShip_maxYaw: return JS_NewNumberValue(context, [entity maxFlightYaw], value); case kShip_injectorBurnRate: return JS_NewNumberValue(context, [entity afterburnerRate], value); case kShip_injectorSpeedFactor: return JS_NewNumberValue(context, [entity afterburnerFactor], value); case kShip_script: result = [entity shipScript]; break; case kShip_AIScript: result = [entity shipAIScript]; break; case kShip_AIScriptWakeTime: return JS_NewNumberValue(context, [entity shipAIScriptWakeTime], value); break; case kShip_destinationSystem: return JS_NewNumberValue(context, [entity destinationSystem], value); break; case kShip_homeSystem: return JS_NewNumberValue(context, [entity homeSystem], value); break; case kShip_isPirate: *value = OOJSValueFromBOOL([entity isPirate]); return YES; case kShip_isPolice: *value = OOJSValueFromBOOL([entity isPolice]); return YES; case kShip_isThargoid: *value = OOJSValueFromBOOL([entity isThargoid]); return YES; case kShip_isTurret: *value = OOJSValueFromBOOL([entity isTurret]); return YES; case kShip_isTrader: *value = OOJSValueFromBOOL([entity isTrader]); return YES; case kShip_isPirateVictim: *value = OOJSValueFromBOOL([entity isPirateVictim]); return YES; case kShip_isMissile: *value = OOJSValueFromBOOL([entity isMissile]); return YES; case kShip_isMine: *value = OOJSValueFromBOOL([entity isMine]); return YES; case kShip_isWeapon: *value = OOJSValueFromBOOL([entity isWeapon]); return YES; case kShip_isRock: *value = OOJSValueFromBOOL([entity scanClass] == CLASS_ROCK); // hermits and asteroids! return YES; case kShip_isMinable: *value = OOJSValueFromBOOL([entity isMinable]); return YES; case kShip_isBoulder: *value = OOJSValueFromBOOL([entity isBoulder]); return YES; case kShip_isFleeing: if ([entity isPlayer]) { *value = OOJSValueFromBOOL([(PlayerEntity*)entity fleeingStatus] >= PLAYER_FLEEING_CARGO); } else { *value = OOJSValueFromBOOL([entity behaviour] == BEHAVIOUR_FLEE_TARGET || [entity behaviour] == BEHAVIOUR_FLEE_EVASIVE_ACTION); } return YES; case kShip_isCargo: *value = OOJSValueFromBOOL([entity scanClass] == CLASS_CARGO && [entity commodityAmount] > 0); return YES; case kShip_isDerelict: *value = OOJSValueFromBOOL([entity isHulk]); return YES; case kShip_isPiloted: *value = OOJSValueFromBOOL([entity isPlayer] || [[entity crew] count] > 0); return YES; case kShip_scriptedMisjump: *value = OOJSValueFromBOOL([entity scriptedMisjump]); return YES; case kShip_scriptedMisjumpRange: return JS_NewNumberValue(context, [entity scriptedMisjumpRange], value); case kShip_scriptInfo: result = [entity scriptInfo]; if (result == nil) result = [NSDictionary dictionary]; // empty rather than null break; case kShip_sunGlareFilter: return JS_NewNumberValue(context, [entity sunGlareFilter], value); case kShip_trackCloseContacts: *value = OOJSValueFromBOOL([entity trackCloseContacts]); return YES; case kShip_passengerCount: return JS_NewNumberValue(context, [entity passengerCount], value); case kShip_parcelCount: return JS_NewNumberValue(context, [entity parcelCount], value); case kShip_passengerCapacity: return JS_NewNumberValue(context, [entity passengerCapacity], value); case kShip_missileCapacity: return JS_NewNumberValue(context, [entity missileCapacity], value); case kShip_missileLoadTime: return JS_NewNumberValue(context, [entity missileLoadTime], value); case kShip_savedCoordinates: return HPVectorToJSValue(context,[entity coordinates], value); case kShip_equipment: result = [entity equipmentListForScripting]; break; case kShip_currentWeapon: result = [entity weaponTypeForFacing:[entity currentWeaponFacing] strict:YES]; break; case kShip_forwardWeapon: result = [entity weaponTypeForFacing:WEAPON_FACING_FORWARD strict:YES]; break; case kShip_aftWeapon: result = [entity weaponTypeForFacing:WEAPON_FACING_AFT strict:YES]; break; case kShip_portWeapon: result = [entity weaponTypeForFacing:WEAPON_FACING_PORT strict:YES]; break; case kShip_starboardWeapon: result = [entity weaponTypeForFacing:WEAPON_FACING_STARBOARD strict:YES]; break; case kShip_laserHeatLevel: return JS_NewNumberValue(context, [entity laserHeatLevel], value); case kShip_laserHeatLevelAft: return JS_NewNumberValue(context, [entity laserHeatLevelAft], value); case kShip_laserHeatLevelForward: return JS_NewNumberValue(context, [entity laserHeatLevelForward], value); case kShip_laserHeatLevelPort: return JS_NewNumberValue(context, [entity laserHeatLevelPort], value); case kShip_laserHeatLevelStarboard: return JS_NewNumberValue(context, [entity laserHeatLevelStarboard], value); case kShip_missiles: result = [entity missilesList]; break; case kShip_passengers: result = [entity passengerListForScripting]; break; case kShip_parcels: result = [entity parcelListForScripting]; break; case kShip_contracts: result = [entity contractListForScripting]; break; case kShip_dockingInstructions: result = [entity dockingInstructions]; break; case kShip_scannerDisplayColor1: result = [[entity scannerDisplayColor1] normalizedArray]; break; case kShip_scannerDisplayColor2: result = [[entity scannerDisplayColor2] normalizedArray]; break; case kShip_scannerHostileDisplayColor1: result = [[entity scannerDisplayColorHostile1] normalizedArray]; break; case kShip_scannerHostileDisplayColor2: result = [[entity scannerDisplayColorHostile2] normalizedArray]; break; case kShip_exhaustEmissiveColor: result = [[entity exhaustEmissiveColor] normalizedArray]; break; case kShip_maxThrust: return JS_NewNumberValue(context, [entity maxThrust], value); case kShip_thrust: return JS_NewNumberValue(context, [entity thrust], value); case kShip_lightsActive: *value = OOJSValueFromBOOL([entity lightsActive]); return YES; case kShip_vectorRight: return VectorToJSValue(context, [entity rightVector], value); case kShip_vectorForward: return VectorToJSValue(context, [entity forwardVector], value); case kShip_vectorUp: return VectorToJSValue(context, [entity upVector], value); case kShip_velocity: return VectorToJSValue(context, [entity velocity], value); case kShip_thrustVector: return VectorToJSValue(context, [entity thrustVector], value); case kShip_pitch: return JS_NewNumberValue(context, [entity flightPitch], value); case kShip_roll: return JS_NewNumberValue(context, [entity flightRoll], value); case kShip_yaw: return JS_NewNumberValue(context, [entity flightYaw], value); case kShip_boundingBox: { Vector bbvect; BoundingBox box; if ([entity isSubEntity]) { box = [entity boundingBox]; } else { box = [entity totalBoundingBox]; } bounding_box_get_dimensions(box,&bbvect.x,&bbvect.y,&bbvect.z); return VectorToJSValue(context, bbvect, value); } default: OOJSReportBadPropertySelector(context, this, propID, sShipProperties); return NO; } *value = OOJSValueFromNativeObject(context, result); return YES; OOJS_NATIVE_EXIT } static JSBool ShipSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) ShipEntity *entity = nil; ShipEntity *target = nil; NSString *sValue = nil; jsdouble fValue; int32 iValue; JSBool bValue; Vector vValue; Quaternion qValue; HPVector hpvValue; OOShipGroup *group = nil; OOColor *colorForScript = nil; BOOL exists; if (EXPECT_NOT(!JSShipGetShipEntity(context, this, &entity))) return NO; if (OOIsStaleEntity(entity)) return YES; NSCAssert(![entity isTemplateCargoPod], @"-OOJSShip: a template cargo pod has become accessible to Javascript"); switch (JSID_TO_INT(propID)) { case kShip_name: if (EXPECT_NOT([entity isPlayer])) goto playerReadOnly; sValue = OOStringFromJSValue(context,*value); if (sValue != nil) { [entity setName:sValue]; return YES; } break; case kShip_displayName: if (EXPECT_NOT([entity isPlayer])) goto playerReadOnly; sValue = OOStringFromJSValue(context,*value); if (sValue != nil) { [entity setDisplayName:sValue]; return YES; } break; case kShip_shipUniqueName: sValue = OOStringFromJSValue(context,*value); if (sValue != nil) { [entity setShipUniqueName:sValue]; return YES; } break; case kShip_shipClassName: sValue = OOStringFromJSValue(context,*value); if (sValue != nil) { [entity setShipClassName:sValue]; return YES; } break; case kShip_scanDescription: sValue = OOStringFromJSValue(context,*value); // can set to nil [entity setScanDescription:sValue]; return YES; case kShip_primaryRole: if (EXPECT_NOT([entity isPlayer])) goto playerReadOnly; sValue = OOStringFromJSValue(context,*value); if (sValue != nil) { [entity setPrimaryRole:sValue]; return YES; } break; case kShip_AIState: if (EXPECT_NOT([entity isPlayer])) goto playerReadOnly; sValue = OOStringFromJSValue(context,*value); if (sValue != nil) { [[entity getAI] setState:sValue]; return YES; } break; case kShip_beaconCode: if (EXPECT_NOT([entity isPlayer])) goto playerReadOnly; sValue = OOStringFromJSValue(context,*value); if ([sValue length] == 0) { if ([entity isBeacon]) { [UNIVERSE clearBeacon:entity]; if ([PLAYER nextBeacon] == entity) { [PLAYER setCompassMode:COMPASS_MODE_PLANET]; } } } else { if ([entity isBeacon]) { [entity setBeaconCode:sValue]; } else // Universe needs to update beacon lists in this case only { [entity setBeaconCode:sValue]; [UNIVERSE setNextBeacon:entity]; } } return YES; break; case kShip_beaconLabel: if (EXPECT_NOT([entity isPlayer])) goto playerReadOnly; sValue = OOStringFromJSValue(context,*value); if (sValue != nil) { [entity setBeaconLabel:sValue]; return YES; } break; case kShip_accuracy: if (EXPECT_NOT([entity isPlayer])) goto playerReadOnly; if (JS_ValueToNumber(context, *value, &fValue)) { [entity setAccuracy:fValue]; return YES; } break; case kShip_fuel: if (JS_ValueToNumber(context, *value, &fValue)) { fValue = OOClamp_0_max_d(fValue, MAX_JUMP_RANGE); [entity setFuel:lround(fValue * 10.0)]; return YES; } break; case kShip_entityPersonality: if (JS_ValueToInt32(context, *value, &iValue)) { if (iValue < 0 || iValue > ENTITY_PERSONALITY_MAX) { OOJSReportError(context, @"ship.%@ must be >= 0 and <= %u.", OOStringFromJSPropertyIDAndSpec(context, propID, sShipProperties),ENTITY_PERSONALITY_MAX); return NO; } [entity setEntityPersonalityInt: iValue]; return YES; } case kShip_hyperspaceSpinTime: if (JS_ValueToNumber(context, *value, &fValue)) { [entity setHyperspaceSpinTime:fValue]; return YES; } break; case kShip_bounty: if (JS_ValueToInt32(context, *value, &iValue)) { if (iValue < 0) iValue = 0; [entity setBounty:iValue withReason:kOOLegalStatusReasonByScript]; return YES; } break; case kShip_cargoSpaceCapacity: if (JS_ValueToInt32(context, *value, &iValue)) { if (iValue < 0) iValue = 0; if ((OOCargoQuantity)iValue < [entity maxAvailableCargoSpace] - [entity availableCargoSpace]) { iValue = [entity maxAvailableCargoSpace] - [entity availableCargoSpace]; } [entity setMaxAvailableCargoSpace:iValue]; return YES; } break; case kShip_destinationSystem: if (JS_ValueToInt32(context, *value, &iValue)) { if (iValue < 0) iValue = 0; [entity setDestinationSystem:iValue]; return YES; } break; case kShip_homeSystem: if (JS_ValueToInt32(context, *value, &iValue)) { if (iValue < 0) iValue = 0; [entity setHomeSystem:iValue]; return YES; } break; case kShip_target: if (JSVAL_IS_NULL(*value)) { [entity setTargetForScript:nil]; return YES; } else if (JSValueToEntity(context, *value, &target) && [target isKindOfClass:[ShipEntity class]]) { [entity setTargetForScript:target]; return YES; } break; case kShip_AIFoundTarget: if (EXPECT_NOT([entity isPlayer])) goto playerReadOnly; if (JSVAL_IS_NULL(*value)) { [entity setFoundTarget:nil]; return YES; } else if (JSValueToEntity(context, *value, &target) && [target isKindOfClass:[ShipEntity class]]) { [entity setFoundTarget:target]; return YES; } break; case kShip_AIPrimaryAggressor: if (EXPECT_NOT([entity isPlayer])) goto playerReadOnly; if (JSVAL_IS_NULL(*value)) { [entity setPrimaryAggressor:nil]; return YES; } else if (JSValueToEntity(context, *value, &target) && [target isKindOfClass:[ShipEntity class]]) { [entity setPrimaryAggressor:target]; return YES; } break; case kShip_group: group = OOJSNativeObjectOfClassFromJSValue(context, *value, [OOShipGroup class]); if (group != nil || JSVAL_IS_NULL(*value)) { [entity setGroup:group]; return YES; } break; case kShip_AIScriptWakeTime: if (JS_ValueToNumber(context, *value, &fValue)) { [entity setAIScriptWakeTime:fValue]; return YES; } break; case kShip_temperature: if (JS_ValueToNumber(context, *value, &fValue)) { fValue = fmax(fValue, 0.0); [entity setTemperature:fValue * SHIP_MAX_CABIN_TEMP]; return YES; } break; case kShip_heatInsulation: if (JS_ValueToNumber(context, *value, &fValue)) { fValue = fmax(fValue, 0.125); [entity setHeatInsulation:fValue]; return YES; } break; case kShip_isCloaked: if (JS_ValueToBoolean(context, *value, &bValue)) { [entity setCloaked:bValue]; return YES; } break; case kShip_cloakAutomatic: if (JS_ValueToBoolean(context, *value, &bValue)) { [entity setAutoCloak:bValue]; return YES; } break; case kShip_missileLoadTime: if (JS_ValueToNumber(context, *value, &fValue)) { [entity setMissileLoadTime:fmax(0.0, fValue)]; return YES; } break; case kShip_reactionTime: if (EXPECT_NOT([entity isPlayer])) goto playerReadOnly; if (JS_ValueToNumber(context, *value, &fValue)) { [entity setReactionTime:fValue]; return YES; } break; case kShip_reportAIMessages: if (JS_ValueToBoolean(context, *value, &bValue)) { [entity setReportAIMessages:bValue]; return YES; } break; case kShip_trackCloseContacts: if (JS_ValueToBoolean(context, *value, &bValue)) { [entity setTrackCloseContacts:bValue]; return YES; } break; case kShip_isBoulder: if (EXPECT_NOT([entity isPlayer])) goto playerReadOnly; if (JS_ValueToBoolean(context, *value, &bValue)) { [entity setIsBoulder:bValue]; return YES; } break; case kShip_destination: if (EXPECT_NOT([entity isPlayer])) goto playerReadOnly; if (JSValueToHPVector(context, *value, &hpvValue)) { // use setEscortDestination rather than setDestination as // scripted amendments shouldn't necessarily reset frustration [entity setEscortDestination:hpvValue]; return YES; } break; case kShip_desiredSpeed: if (EXPECT_NOT([entity isPlayer])) goto playerReadOnly; if (JS_ValueToNumber(context, *value, &fValue)) { [entity setDesiredSpeed:fmax(fValue, 0.0)]; return YES; } break; case kShip_desiredRange: if (EXPECT_NOT([entity isPlayer])) goto playerReadOnly; if (JS_ValueToNumber(context, *value, &fValue)) { [entity setDesiredRange:fmax(fValue, 0.0)]; return YES; } break; case kShip_savedCoordinates: if (JSValueToHPVector(context, *value, &hpvValue)) { [entity setCoordinate:hpvValue]; return YES; } break; case kShip_scannerDisplayColor1: colorForScript = [OOColor colorWithDescription:OOJSNativeObjectFromJSValue(context, *value)]; if (colorForScript != nil || JSVAL_IS_NULL(*value)) { [entity setScannerDisplayColor1:colorForScript]; return YES; } break; case kShip_scannerDisplayColor2: colorForScript = [OOColor colorWithDescription:OOJSNativeObjectFromJSValue(context, *value)]; if (colorForScript != nil || JSVAL_IS_NULL(*value)) { [entity setScannerDisplayColor2:colorForScript]; return YES; } break; case kShip_scannerHostileDisplayColor1: colorForScript = [OOColor colorWithDescription:OOJSNativeObjectFromJSValue(context, *value)]; if (colorForScript != nil || JSVAL_IS_NULL(*value)) { [entity setScannerDisplayColorHostile1:colorForScript]; return YES; } break; case kShip_scannerHostileDisplayColor2: colorForScript = [OOColor colorWithDescription:OOJSNativeObjectFromJSValue(context, *value)]; if (colorForScript != nil || JSVAL_IS_NULL(*value)) { [entity setScannerDisplayColorHostile2:colorForScript]; return YES; } break; case kShip_exhaustEmissiveColor: colorForScript = [OOColor colorWithDescription:OOJSNativeObjectFromJSValue(context, *value)]; if (colorForScript != nil || JSVAL_IS_NULL(*value)) { [entity setExhaustEmissiveColor:colorForScript]; return YES; } break; case kShip_scriptedMisjump: if (JS_ValueToBoolean(context, *value, &bValue)) { [entity setScriptedMisjump:bValue]; return YES; } break; case kShip_scriptedMisjumpRange: if (JS_ValueToNumber(context, *value, &fValue)) { if (fValue > 0.0 && fValue < 1.0) { [entity setScriptedMisjumpRange:fValue]; return YES; } else { OOJSReportError(context, @"ship.%@ must be > 0.0 and < 1.0.", OOStringFromJSPropertyIDAndSpec(context, propID, sShipProperties)); return NO; } } break; case kShip_subEntityRotation: if (JSValueToQuaternion(context, *value, &qValue)) { [entity setSubEntityRotationalVelocity:qValue]; return YES; } break; case kShip_sunGlareFilter: if (JS_ValueToNumber(context, *value, &fValue)) { if (fValue >= 0.0f && fValue <= 1.0f) { [entity setSunGlareFilter:fValue]; return YES; } else { OOJSReportError(context, @"ship.%@ must be > 0.0 and < 1.0.", OOStringFromJSPropertyIDAndSpec(context, propID, sShipProperties)); return NO; } } break; case kShip_thrust: // if (EXPECT_NOT([entity isPlayer])) goto playerReadOnly; if (JS_ValueToNumber(context, *value, &fValue)) { [entity setThrust:OOClamp_0_max_f(fValue, [entity maxThrust])]; return YES; } break; case kShip_maxPitch: if (JS_ValueToNumber(context, *value, &fValue)) { if (fValue < 0) { OOJSReportError(context, @"ship.maxPitch cannot be negative."); return NO; } [entity setMaxFlightPitch:fValue]; return YES; } break; case kShip_maxSpeed: if (JS_ValueToNumber(context, *value, &fValue)) { if (fValue < 0) { OOJSReportError(context, @"ship.maxSpeed cannot be negative."); return NO; } [entity setMaxFlightSpeed:fValue]; return YES; } break; case kShip_maxRoll: if (JS_ValueToNumber(context, *value, &fValue)) { if (fValue < 0) { OOJSReportError(context, @"ship.maxRoll cannot be negative."); return NO; } [entity setMaxFlightRoll:fValue]; return YES; } break; case kShip_maxYaw: if (JS_ValueToNumber(context, *value, &fValue)) { if (fValue < 0) { OOJSReportError(context, @"ship.maxYaw cannot be negative."); return NO; } [entity setMaxFlightYaw:fValue]; return YES; } break; case kShip_injectorBurnRate: if (JS_ValueToNumber(context, *value, &fValue)) { if (fValue < 0) { OOJSReportError(context, @"ship.injectorBurnRate cannot be negative."); return NO; } [entity setAfterburnerRate:fValue]; return YES; } break; case kShip_injectorSpeedFactor: if (JS_ValueToNumber(context, *value, &fValue)) { if (fValue < 1) { OOJSReportError(context, @"ship.injectorSpeedFactor cannot be less than 1.0."); return NO; } #if OO_VARIABLE_TORUS_SPEED else if (fValue > MIN_HYPERSPEED_FACTOR) { OOJSReportError(context, @"ship.injectorSpeedFactor cannot be higher than minimum torus speed factor (%f).",MIN_HYPERSPEED_FACTOR); #else else if (fValue > HYPERSPEED_FACTOR) { OOJSReportError(context, @"ship.injectorSpeedFactor cannot be higher than torus speed factor (%f).",HYPERSPEED_FACTOR); #endif return NO; } [entity setAfterburnerFactor:fValue]; return YES; } break; case kShip_maxThrust: if (JS_ValueToNumber(context, *value, &fValue)) { if (fValue < 0) { OOJSReportError(context, @"ship.maxThrust cannot be negative."); return NO; } [entity setMaxThrust:fValue]; return YES; } break; case kShip_energyRechargeRate: if (JS_ValueToNumber(context, *value, &fValue)) { if (fValue < 0) { OOJSReportError(context, @"ship.energyRechargeRate cannot be negative."); return NO; } [entity setEnergyRechargeRate:fValue]; return YES; } break; case kShip_lightsActive: if (JS_ValueToBoolean(context, *value, &bValue)) { if (bValue) [entity switchLightsOn]; else [entity switchLightsOff]; return YES; } break; case kShip_velocity: if (JSValueToVector(context, *value, &vValue)) { [entity setTotalVelocity:vValue]; return YES; } break; case kShip_portWeapon: case kShip_starboardWeapon: case kShip_aftWeapon: case kShip_forwardWeapon: case kShip_currentWeapon: sValue = JSValueToEquipmentKeyRelaxed(context, *value, &exists); if (sValue == nil) { sValue = @"EQ_WEAPON_NONE"; } OOWeaponFacing facing = WEAPON_FACING_FORWARD; switch (JSID_TO_INT(propID)) { case kShip_aftWeapon: facing = WEAPON_FACING_AFT; break; case kShip_forwardWeapon: facing = WEAPON_FACING_FORWARD; break; case kShip_portWeapon: facing = WEAPON_FACING_PORT; break; case kShip_starboardWeapon: facing = WEAPON_FACING_STARBOARD; break; case kShip_currentWeapon: facing = [entity currentWeaponFacing]; break; } [entity setWeaponMount:facing toWeapon:sValue]; return YES; case kShip_maxEscorts: if (EXPECT_NOT([entity isPlayer])) goto playerReadOnly; if (JS_ValueToInt32(context, *value, &iValue)) { if ((NSInteger)iValue < (NSInteger)[[entity escortGroup] count] - 1) { OOJSReportError(context, @"ship.%@ must be >= current escort numbers.", OOStringFromJSPropertyIDAndSpec(context, propID, sShipProperties)); return NO; } if (iValue > MAX_ESCORTS) { OOJSReportError(context, @"ship.%@ must be <= %d.", OOStringFromJSPropertyIDAndSpec(context, propID, sShipProperties),MAX_ESCORTS); return NO; } [entity setMaxEscortCount:iValue]; return YES; } break; default: OOJSReportBadPropertySelector(context, this, propID, sShipProperties); return NO; } OOJSReportBadPropertyValue(context, this, propID, sShipProperties, *value); return NO; playerReadOnly: OOJSReportError(context, @"player.ship.%@ is read-only.", OOStringFromJSPropertyIDAndSpec(context, propID, sShipProperties)); return NO; // Not used (yet) /* npcReadOnly: OOJSReportError(context, @"npc.ship.%@ is read-only.", OOStringFromJSPropertyIDAndSpec(context, propID, sShipProperties)); return NO; */ OOJS_NATIVE_EXIT } // *** Methods *** #define GET_THIS_SHIP(THISENT) do { \ if (EXPECT_NOT(!JSShipGetShipEntity(context, OOJS_THIS, &THISENT))) return NO; /* Exception */ \ if (OOIsStaleEntity(THISENT)) OOJS_RETURN_VOID; \ } while (0) // setScript(scriptName : String) static JSBool ShipSetScript(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; NSString *name = nil; GET_THIS_SHIP(thisEnt); if (argc > 0) name = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(name == nil)) { OOJSReportBadArguments(context, @"Ship", @"setScript", MIN(argc, 1U), OOJS_ARGV, nil, @"string (script name)"); return NO; } if (EXPECT_NOT([thisEnt isPlayer])) { OOJSReportErrorForCaller(context, @"Ship", @"setScript", @"Not valid for player ship."); return NO; } [thisEnt setShipScript:name]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // setAI(aiName : String) static JSBool ShipSetAI(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; NSString *name = nil; GET_THIS_SHIP(thisEnt); if (argc > 0) name = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(name == nil)) { OOJSReportBadArguments(context, @"Ship", @"setAI", MIN(argc, 1U), OOJS_ARGV, nil, @"string (AI name)"); return NO; } if (EXPECT_NOT([thisEnt isPlayer])) { OOJSReportErrorForCaller(context, @"Ship", @"setAI", @"Not valid for player ship."); return NO; } [thisEnt setAITo:name]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // switchAI(aiName : String) static JSBool ShipSwitchAI(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; NSString *name = nil; GET_THIS_SHIP(thisEnt); if (argc > 0) name = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(name == nil)) { OOJSReportBadArguments(context, @"Ship", @"switchAI", MIN(argc, 1U), OOJS_ARGV, nil, @"string (AI name)"); return NO; } if (EXPECT_NOT([thisEnt isPlayer])) { OOJSReportErrorForCaller(context, @"Ship", @"switchAI", @"Not valid for player ship."); return NO; } [thisEnt switchAITo:name]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // exitAI() static JSBool ShipExitAI(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; AI *thisAI = nil; NSString *message = nil; GET_THIS_SHIP(thisEnt); if (EXPECT_NOT([thisEnt isPlayer])) { OOJSReportErrorForCaller(context, @"Ship", @"exitAI", @"Not valid for player ship."); return NO; } thisAI = [thisEnt getAI]; if ([thisAI hasSuspendedStateMachines]) { if (argc > 0) { message = OOStringFromJSValue(context, OOJS_ARGV[0]); } // Else AI will default to RESTARTED. [thisAI exitStateMachineWithMessage:message]; } else { OOJSReportWarningForCaller(context, @"Ship", @"exitAI()", @"Cannot exit current AI state machine because there are no suspended state machines."); } OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // reactToAIMessage(message : String) static JSBool ShipReactToAIMessage(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; NSString *message = nil; GET_THIS_SHIP(thisEnt); if (argc > 0) message = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(message == nil)) { OOJSReportBadArguments(context, @"Ship", @"reactToAIMessage", MIN(argc, 1U), OOJS_ARGV, nil, @"string"); return NO; } if (EXPECT_NOT([thisEnt isPlayer])) { OOJSReportErrorForCaller(context, @"Ship", @"reactToAIMessage", @"Not valid for player ship."); return NO; } [thisEnt reactToAIMessage:message context:@"JavaScript reactToAIMessage()"]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // sendAIMessage(message : String) static JSBool ShipSendAIMessage(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; NSString *message = nil; GET_THIS_SHIP(thisEnt); if (argc > 0) message = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(message == nil)) { OOJSReportBadArguments(context, @"Ship", @"sendAIMessage", MIN(argc, 1U), OOJS_ARGV, nil, @"string"); return NO; } if (EXPECT_NOT([thisEnt isPlayer])) { OOJSReportErrorForCaller(context, @"Ship", @"sendAIMessage", @"Not valid for player ship."); return NO; } [thisEnt sendAIMessage:message]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // deployEscorts() static JSBool ShipDeployEscorts(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt deployEscorts]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // dockEscorts() static JSBool ShipDockEscorts(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt dockEscorts]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // hasEquipmentProviding(equipment : String) : Boolean static JSBool ShipHasEquipmentProviding(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; NSString *equipment = nil; GET_THIS_SHIP(thisEnt); if (argc > 0) equipment = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(equipment == nil)) { OOJSReportBadArguments(context, @"Ship", @"hasEquipmentProviding", MIN(argc, 1U), OOJS_ARGV, nil, @"string (equipment)"); return NO; } OOJS_RETURN_BOOL([thisEnt hasEquipmentItemProviding:equipment]); OOJS_NATIVE_EXIT } // hasRole(role : String) : Boolean static JSBool ShipHasRole(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; NSString *role = nil; GET_THIS_SHIP(thisEnt); if (argc > 0) role = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(role == nil)) { OOJSReportBadArguments(context, @"Ship", @"hasRole", MIN(argc, 1U), OOJS_ARGV, nil, @"string (role)"); return NO; } OOJS_RETURN_BOOL([thisEnt hasRole:role]); OOJS_NATIVE_EXIT } // ejectItem(role : String) : Ship static JSBool ShipEjectItem(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; NSString *role = nil; GET_THIS_SHIP(thisEnt); if (argc > 0) role = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(role == nil)) { OOJSReportBadArguments(context, @"Ship", @"ejectItem", MIN(argc, 1U), OOJS_ARGV, nil, @"string (role)"); return NO; } OOJS_RETURN_OBJECT([thisEnt ejectShipOfRole:role]); OOJS_NATIVE_EXIT } // ejectSpecificItem(itemKey : String) : Ship static JSBool ShipEjectSpecificItem(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; NSString *itemKey = nil; GET_THIS_SHIP(thisEnt); if (argc > 0) itemKey = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(itemKey == nil)) { OOJSReportBadArguments(context, @"Ship", @"ejectSpecificItem", MIN(argc, 1U), OOJS_ARGV, nil, @"string (ship key)"); return NO; } OOJS_RETURN_OBJECT([thisEnt ejectShipOfType:itemKey]); OOJS_NATIVE_EXIT } // dumpCargo() : Ship static JSBool ShipDumpCargo(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; OOCommodityType pref = nil; GET_THIS_SHIP(thisEnt); if (EXPECT_NOT([thisEnt isPlayer] && [(PlayerEntity *)thisEnt isDocked])) { OOJSReportWarningForCaller(context, @"PlayerShip", @"dumpCargo", @"Can't dump cargo while docked, ignoring."); OOJS_RETURN_NULL; } if (argc > 1) { pref = OOStringFromJSValue(context, OOJS_ARGV[1]); } // NPCs can queue multiple items to dump if (!EXPECT_NOT([thisEnt isPlayer])) { int32 i, count = 1; BOOL gotCount = YES; if (argc > 0) gotCount = JS_ValueToInt32(context, OOJS_ARGV[0], &count); if (EXPECT_NOT(!gotCount || count < 1 || count > 64)) { OOJSReportBadArguments(context, @"Ship", @"dumpCargo", MIN(argc, 1U), OOJS_ARGV, nil, @"optional quantity (1 to 64), optional preferred commodity"); return NO; } for (i = 1; i < count; i++) { [thisEnt performSelector:@selector(dumpCargo) withObject:nil afterDelay:0.75 * i]; // drop 3 canisters per 2 seconds } } OOJS_RETURN_OBJECT([thisEnt dumpCargoItem:pref]); OOJS_NATIVE_EXIT } // spawn(role : String [, number : count]) : Array static JSBool ShipSpawn(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; NSString *role = nil; int32 count = 1; BOOL gotCount = YES; NSArray *result = nil; GET_THIS_SHIP(thisEnt); if (argc > 0) role = OOStringFromJSValue(context, OOJS_ARGV[0]); if (argc > 1) gotCount = JS_ValueToInt32(context, OOJS_ARGV[1], &count); if (EXPECT_NOT(role == nil || !gotCount || count < 1 || count > 64)) { OOJSReportBadArguments(context, @"Ship", @"spawn", MIN(argc, 1U), OOJS_ARGV, nil, @"role and optional quantity (1 to 64)"); return NO; } OOJS_BEGIN_FULL_NATIVE(context) result = [thisEnt spawnShipsWithRole:role count:count]; OOJS_END_FULL_NATIVE OOJS_RETURN_OBJECT(result); OOJS_NATIVE_EXIT } // dealEnergyDamage(). Replaces AI's dealEnergyDamageWithinDesiredRange static JSBool ShipDealEnergyDamage(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; jsdouble baseDamage; jsdouble range; jsdouble velocityBias = 0.0; BOOL gotDamage; BOOL gotRange; BOOL gotVBias; GET_THIS_SHIP(thisEnt); if (argc < 2) { OOJSReportBadArguments(context, @"Ship", @"dealEnergyDamage", argc, OOJS_ARGV, nil, @"damage and range needed"); return NO; } gotDamage = JS_ValueToNumber(context, OOJS_ARGV[0], &baseDamage); if (EXPECT_NOT(!gotDamage || baseDamage < 0)) { OOJSReportBadArguments(context, @"Ship", @"dealEnergyDamage", argc, OOJS_ARGV, nil, @"damage must be positive"); return NO; } gotRange = JS_ValueToNumber(context, OOJS_ARGV[1], &range); if (EXPECT_NOT(!gotRange || range < 0)) { OOJSReportBadArguments(context, @"Ship", @"dealEnergyDamage", argc, OOJS_ARGV, nil, @"range must be positive"); return NO; } if (argc >= 3) { gotVBias = JS_ValueToNumber(context, OOJS_ARGV[2], &velocityBias); if (!gotVBias) { OOJSReportBadArguments(context, @"Ship", @"dealEnergyDamage", argc, OOJS_ARGV, nil, @"velocity bias must be a number"); return NO; } } [thisEnt dealEnergyDamage:(GLfloat)baseDamage atRange:(GLfloat)range withBias:(GLfloat)velocityBias]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // explode() static JSBool ShipExplode(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) return RemoveOrExplodeShip(context, argc, vp, YES); OOJS_NATIVE_EXIT } // remove([suppressDeathEvent : Boolean = false]) static JSBool ShipRemove(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; JSBool suppressDeathEvent = NO; GET_THIS_SHIP(thisEnt); if ([thisEnt isPlayer]) { OOJSReportError(context, @"Cannot remove() player's ship."); return NO; } if (argc > 0 && EXPECT_NOT(!JS_ValueToBoolean(context, OOJS_ARGV[0], &suppressDeathEvent))) { OOJSReportBadArguments(context, @"Ship", @"remove", argc, OOJS_ARGV, nil, @"boolean"); return NO; } [thisEnt doScriptEvent:OOJSID("shipRemoved") withArgument:[NSNumber numberWithBool:suppressDeathEvent]]; if (suppressDeathEvent) { [thisEnt removeScript]; } return RemoveOrExplodeShip(context, argc, vp, NO); OOJS_NATIVE_EXIT } // __runLegacyScriptActions(target : Ship, actions : Array) static JSBool ShipRunLegacyScriptActions(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; PlayerEntity *player = nil; ShipEntity *target = nil; NSArray *actions = nil; player = OOPlayerForScripting(); GET_THIS_SHIP(thisEnt); if (argc > 1) actions = OOJSNativeObjectFromJSValue(context, OOJS_ARGV[1]); if (EXPECT_NOT(argc != 2 || !JSVAL_IS_OBJECT(OOJS_ARGV[0]) || !JSShipGetShipEntity(context, JSVAL_TO_OBJECT(OOJS_ARGV[0]), &target) || ![actions isKindOfClass:[NSArray class]])) { OOJSReportBadArguments(context, @"Ship", @"__runLegacyScriptActions", argc, OOJS_ARGV, nil, @"target and array of actions"); return NO; } if (target != nil) // Not stale reference { [player setScriptTarget:thisEnt]; [player runUnsanitizedScriptActions:actions allowingAIMethods:YES withContextName:[NSString stringWithFormat:@"", [thisEnt name]] forTarget:target]; } OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // commsMessage(message : String[,target : Ship]) static JSBool ShipCommsMessage(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; NSString *message = nil; ShipEntity *target = nil; GET_THIS_SHIP(thisEnt); if (argc > 0) message = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(message == nil || (argc > 1 && (JSVAL_IS_NULL(OOJS_ARGV[1]) || !JSVAL_IS_OBJECT(OOJS_ARGV[1]) || !JSShipGetShipEntity(context, JSVAL_TO_OBJECT(OOJS_ARGV[1]), &target))))) { OOJSReportBadArguments(context, @"Ship", @"commsMessage", MIN(argc, 1U), OOJS_ARGV, nil, @"message and optional target"); return NO; } if (argc < 2) { [thisEnt commsMessage:message withUnpilotedOverride:YES]; // generic broadcast } else if (target != nil) // Not stale reference { [thisEnt sendMessage:message toShip:target withUnpilotedOverride:YES]; // ship-to-ship narrowcast } OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // fireECM() static JSBool ShipFireECM(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; BOOL OK; GET_THIS_SHIP(thisEnt); OK = [thisEnt fireECM]; if (!OK) { OOJSReportWarning(context, @"Ship %@ was requested to fire ECM burst but does not carry ECM equipment.", [thisEnt oo_jsDescription]); } OOJS_RETURN_BOOL(OK); OOJS_NATIVE_EXIT } // abandonShip() static JSBool ShipAbandonShip(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); OOJS_RETURN_BOOL([thisEnt hasEscapePod] && [thisEnt abandonShip]); OOJS_NATIVE_EXIT } // canAwardEquipment(type : equipmentInfoExpression) static JSBool ShipCanAwardEquipment(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; NSString *key = nil; BOOL result; BOOL exists; GET_THIS_SHIP(thisEnt); if (argc > 0) key = JSValueToEquipmentKeyRelaxed(context, OOJS_ARGV[0], &exists); if (EXPECT_NOT(key == nil)) { OOJSReportBadArguments(context, @"Ship", @"canAwardEquipment", MIN(argc, 1U), OOJS_ARGV, nil, @"equipment type"); return NO; } if (exists) { result = YES; // can't add fuel as equipment. if ([key isEqualToString:@"EQ_FUEL"]) result = NO; if (result) result = [thisEnt canAddEquipment:key inContext:@"scripted"]; } else { // Unknown type. result = NO; } OOJS_RETURN_BOOL(result); OOJS_NATIVE_EXIT } // awardEquipment(type : equipmentInfoExpression) : Boolean static JSBool ShipAwardEquipment(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; OOEquipmentType *eqType = nil; NSString *identifier = nil; BOOL OK = YES; BOOL berth; BOOL isRepair = NO; GET_THIS_SHIP(thisEnt); if (argc > 0) eqType = JSValueToEquipmentType(context, OOJS_ARGV[0]); if (EXPECT_NOT(eqType == nil)) { OOJSReportBadArguments(context, @"Ship", @"awardEquipment", MIN(argc, 1U), OOJS_ARGV, nil, @"equipment type"); return NO; } // Check that equipment is permitted. identifier = [eqType identifier]; berth = [identifier isEqualToString:@"EQ_PASSENGER_BERTH"]; if (berth) { OK = [thisEnt availableCargoSpace] >= [eqType requiredCargoSpace]; } else if ([identifier isEqualToString:@"EQ_FUEL"]) { OK = NO; } else { OK = [eqType canCarryMultiple] || ![thisEnt hasEquipmentItem:identifier]; } if (OK) { if ([thisEnt isPlayer]) { PlayerEntity *player = (PlayerEntity *)thisEnt; if ([identifier isEqualToString:@"EQ_MISSILE_REMOVAL"]) { [player removeMissiles]; } else if ([eqType isMissileOrMine]) { OK = [player mountMissileWithRole:identifier]; } else if (berth) { OK = [player changePassengerBerths: +1]; } else if ([identifier isEqualToString:@"EQ_PASSENGER_BERTH_REMOVAL"]) { OK = [player changePassengerBerths: -1]; } else { isRepair = [thisEnt hasEquipmentItem:[identifier stringByAppendingString:@"_DAMAGED"]]; OK = [player addEquipmentItem:identifier withValidation:YES inContext:@"scripted"]; if (OK && isRepair) { [player doScriptEvent:OOJSID("equipmentRepaired") withArgument:identifier]; } } } else { if ([identifier isEqualToString:@"EQ_MISSILE_REMOVAL"]) { [thisEnt removeMissiles]; } // no passenger handling for NPCs. EQ_CARGO_BAY is dealt with inside addEquipmentItem else if (!berth && ![identifier isEqualToString:@"EQ_PASSENGER_BERTH_REMOVAL"]) { OK = [thisEnt addEquipmentItem:identifier withValidation:YES inContext:@"scripted"]; } else { OK = NO; } } } OOJS_RETURN_BOOL(OK); OOJS_NATIVE_EXIT } // removeEquipment(type : equipmentInfoExpression) static JSBool ShipRemoveEquipment(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; NSString *key = nil; BOOL OK = YES; GET_THIS_SHIP(thisEnt); if (argc > 0) key = JSValueToEquipmentKey(context, OOJS_ARGV[0]); if (EXPECT_NOT(key == nil)) { OOJSReportBadArguments(context, @"Ship", @"removeEquipment", MIN(argc, 1U), OOJS_ARGV, nil, @"equipment type"); return NO; } // berths are not in hasOneEquipmentItem OK = [thisEnt hasOneEquipmentItem:key includeMissiles:YES whileLoading:NO] || ([key isEqualToString:@"EQ_PASSENGER_BERTH"] && [thisEnt passengerCapacity] > 0); if (!OK) { // Allow removal of damaged equipment. key = [key stringByAppendingString:@"_DAMAGED"]; OK = [thisEnt hasOneEquipmentItem:key includeMissiles:NO whileLoading:NO]; } if (OK) { //exceptions if ([key isEqualToString:@"EQ_PASSENGER_BERTH"] || [key isEqualToString:@"EQ_CARGO_BAY"]) { if ([key isEqualToString:@"EQ_PASSENGER_BERTH"]) { if ([thisEnt passengerCapacity] > [thisEnt passengerCount]) { // must be the player's ship! if ([thisEnt isPlayer]) [(PlayerEntity*)thisEnt changePassengerBerths:-1]; } else OK = NO; } else // EQ_CARGO_BAY { // player cargo bay removal handled in script if ([thisEnt isPlayer] || [thisEnt extraCargo] <= [thisEnt availableCargoSpace]) { [thisEnt removeEquipmentItem:key]; } else OK = NO; } } else [thisEnt removeEquipmentItem:key]; } OOJS_RETURN_BOOL(OK); OOJS_NATIVE_EXIT } // restoreSubEntities(): boolean static JSBool ShipRestoreSubEntities(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; NSUInteger numSubEntitiesRestored = 0U; GET_THIS_SHIP(thisEnt); NSUInteger subCount = [[thisEnt subEntitiesForScript] count]; [thisEnt clearSubEntities]; [thisEnt setUpSubEntities]; if ([[thisEnt subEntitiesForScript] count] - subCount > 0) numSubEntitiesRestored = [[thisEnt subEntitiesForScript] count] - subCount; // for each subentity restored, slightly increase the trade-in factor if ([thisEnt isPlayer]) { int tradeInFactorChange = (int)MAX(PLAYER_SHIP_SUBENTITY_TRADE_IN_VALUE * numSubEntitiesRestored, 25U); [(PlayerEntity *)thisEnt adjustTradeInFactorBy:tradeInFactorChange]; } OOJS_RETURN_BOOL(numSubEntitiesRestored > 0); OOJS_NATIVE_EXIT } // setEquipmentStatus(type : equipmentInfoExpression, status : String): boolean static JSBool ShipSetEquipmentStatus(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) // equipment status accepted: @"EQUIPMENT_OK", @"EQUIPMENT_DAMAGED" ShipEntity *thisEnt = nil; OOEquipmentType *eqType = nil; NSString *key = nil; NSString *damagedKey = nil; NSString *status = nil; BOOL hasOK = NO, hasDamaged = NO; GET_THIS_SHIP(thisEnt); if (argc < 2) { OOJSReportBadArguments(context, @"Ship", @"setEquipmentStatus", argc, OOJS_ARGV, nil, @"equipment type and status"); return NO; } eqType = JSValueToEquipmentType(context, OOJS_ARGV[0]); if (EXPECT_NOT(eqType == nil)) { OOJSReportBadArguments(context, @"Ship", @"setEquipmentStatus", 1, &OOJS_ARGV[0], nil, @"equipment type"); return NO; } status = OOStringFromJSValue(context, OOJS_ARGV[1]); if (EXPECT_NOT(status == nil)) { OOJSReportBadArguments(context, @"Ship", @"setEquipmentStatus", 1, &OOJS_ARGV[1], nil, @"equipment status"); return NO; } // EMMSTRAN: use interned strings. if (![status isEqualToString:@"EQUIPMENT_OK"] && ![status isEqualToString:@"EQUIPMENT_DAMAGED"]) { OOJSReportErrorForCaller(context, @"Ship", @"setEquipmentStatus", @"Second parameter for setEquipmentStatus must be either \"EQUIPMENT_OK\" or \"EQUIPMENT_DAMAGED\"."); return NO; } key = [eqType identifier]; hasOK = [thisEnt hasEquipmentItem:key]; BOOL setOK = [status isEqualToString:@"EQUIPMENT_OK"]; BOOL setDamaged = [status isEqualToString:@"EQUIPMENT_DAMAGED"]; if ([eqType canBeDamaged]) { damagedKey = [key stringByAppendingString:@"_DAMAGED"]; hasDamaged = [thisEnt hasEquipmentItem:damagedKey]; if ((setOK && hasDamaged) || (setDamaged && hasOK)) { // the implementation is identical between player and ship. [thisEnt removeEquipmentItem:(setDamaged ? key : damagedKey)]; if ([thisEnt isPlayer]) { // these player methods are different to the ship ones. [(PlayerEntity*)thisEnt addEquipmentItem:(setDamaged ? damagedKey : key) withValidation:NO inContext:@"scripted"]; if (setDamaged) { [(PlayerEntity*)thisEnt doScriptEvent:OOJSID("equipmentDamaged") withArgument:key]; } else if (setOK) { [(PlayerEntity*)thisEnt doScriptEvent:OOJSID("equipmentRepaired") withArgument:key]; } // if player's Docking Computers are set to EQUIPMENT_DAMAGED while on, stop them // this is now done in a different method // if (hasOK && [key isEqualToString:@"EQ_DOCK_COMP"]) [(PlayerEntity*)thisEnt disengageAutopilot]; } else { [thisEnt addEquipmentItem:(setDamaged ? damagedKey : key) withValidation:NO inContext:@"scripted"]; if (hasOK) [thisEnt doScriptEvent:OOJSID("equipmentDamaged") withArgument:key]; } } } else { if (hasOK && ![status isEqualToString:@"EQUIPMENT_OK"]) { OOJSReportWarningForCaller(context, @"Ship", @"setEquipmentStatus", @"Equipment %@ cannot be damaged.", key); hasOK = NO; } } OOJS_RETURN_BOOL(hasOK || hasDamaged); OOJS_NATIVE_EXIT } // equipmentStatus(type : equipmentInfoExpression) : String static JSBool ShipEquipmentStatus(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) /* Interned string constants. Interned strings are guaranteed to survive for the lifetime of the JS runtime, which lasts as long as Oolite is running. */ static jsval strOK, strDamaged, strUnavailable, strUnknown; static BOOL inited = NO; if (EXPECT_NOT(!inited)) { inited = YES; strOK = STRING_TO_JSVAL(JS_InternString(context, "EQUIPMENT_OK")); strDamaged = STRING_TO_JSVAL(JS_InternString(context, "EQUIPMENT_DAMAGED")); strUnavailable = STRING_TO_JSVAL(JS_InternString(context, "EQUIPMENT_UNAVAILABLE")); strUnknown = STRING_TO_JSVAL(JS_InternString(context, "EQUIPMENT_UNKNOWN")); } ShipEntity *thisEnt = nil; NSString *key = nil; JSBool asDict = NO; NSDictionary *dict = nil; GET_THIS_SHIP(thisEnt); if (argc > 0) key = JSValueToEquipmentKey(context, OOJS_ARGV[0]); if (argc > 1) JS_ValueToBoolean(context, OOJS_ARGV[1], &asDict); if (EXPECT_NOT(key == nil)) { if (argc > 0 && JSVAL_IS_STRING(OOJS_ARGV[0])) { if (asDict) { dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:1],@"EQUIPMENT_UNKNOWN",nil]; OOJS_RETURN_OBJECT(dict); } else { OOJS_RETURN(strUnknown); } } OOJSReportBadArguments(context, @"Ship", @"equipmentStatus", MIN(argc, 1U), &OOJS_ARGV[0], nil, @"equipment type"); return NO; } if (asDict) { dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:[thisEnt countEquipmentItem:key]],@"EQUIPMENT_OK",[NSNumber numberWithInt:[thisEnt countEquipmentItem:[key stringByAppendingString:@"_DAMAGED"]]],@"EQUIPMENT_DAMAGED",nil]; OOJS_RETURN_OBJECT(dict); } else { if ([thisEnt hasEquipmentItem:key includeWeapons:YES whileLoading:NO]) OOJS_RETURN(strOK); else if ([thisEnt hasEquipmentItem:[key stringByAppendingString:@"_DAMAGED"]]) OOJS_RETURN(strDamaged); OOJS_RETURN(strUnavailable); } OOJS_NATIVE_EXIT } // selectNewMissile() static JSBool ShipSelectNewMissile(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); NSString *result = [[thisEnt selectMissile] identifier]; // if there's a badly defined missile, selectMissile may return nil if (result == nil) result = @"EQ_MISSILE"; OOJS_RETURN_OBJECT(result); OOJS_NATIVE_EXIT } // fireMissile() static JSBool ShipFireMissile(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; id result = nil; GET_THIS_SHIP(thisEnt); if (argc > 0) result = [thisEnt fireMissileWithIdentifier:OOStringFromJSValue(context, OOJS_ARGV[0]) andTarget:[thisEnt primaryTarget]]; else result = [thisEnt fireMissile]; OOJS_RETURN_OBJECT(result); OOJS_NATIVE_EXIT } // findNearestStation static JSBool ShipFindNearestStation(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; StationEntity *result = nil; GET_THIS_SHIP(thisEnt); double sdist, distance = 1E32; NSEnumerator *statEnum = [[UNIVERSE stations] objectEnumerator]; StationEntity *se = nil; while ((se = [statEnum nextObject])) { sdist = HPdistance2([thisEnt position],[se position]); if (sdist < distance) { distance = sdist; result = se; } } OOJS_RETURN_OBJECT(result); OOJS_NATIVE_EXIT } // setBounty(amount, reason) static JSBool ShipSetBounty(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; NSString *reason = nil; int32 newbounty = 0; BOOL gotBounty = YES; GET_THIS_SHIP(thisEnt); if (argc > 0) gotBounty = JS_ValueToInt32(context, OOJS_ARGV[0], &newbounty); if (argc > 1) reason = OOStringFromJSValue(context, OOJS_ARGV[1]); if (EXPECT_NOT(reason == nil || !gotBounty || newbounty < 0)) { OOJSReportBadArguments(context, @"Ship", @"setBounty", argc, OOJS_ARGV, nil, @"new bounty and reason"); return NO; } [thisEnt setBounty:(OOCreditsQuantity)newbounty withReasonAsString:reason]; return YES; OOJS_NATIVE_EXIT } // setCargo(cargoType : String [, number : count]) static JSBool ShipSetCargo(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; OOCommodityType commodity = nil; int32 count = 1; BOOL gotCount = YES; GET_THIS_SHIP(thisEnt); if (argc > 0) commodity = OOStringFromJSValue(context, OOJS_ARGV[0]); if (argc > 1) gotCount = JS_ValueToInt32(context, OOJS_ARGV[1], &count); if (EXPECT_NOT(commodity == nil || !gotCount || count < 1)) { OOJSReportBadArguments(context, @"Ship", @"setCargo", argc, OOJS_ARGV, nil, @"cargo name and optional positive quantity"); return NO; } if ([[UNIVERSE commodities] goodDefined:commodity]) { [thisEnt setCommodityForPod:commodity andAmount:count]; } OOJS_RETURN_BOOL([[UNIVERSE commodities] goodDefined:commodity]); OOJS_NATIVE_EXIT } // setCrew(crewDefinition : Object) static JSBool ShipSetCrew(JSContext *context, uintN argc, jsval *vp) { /* TODO: ships can in theory have multiple crew, so this could * allow that to be set. Probably not necessary for now. */ OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; NSDictionary *crewDefinition = nil; JSObject *params = NULL; GET_THIS_SHIP(thisEnt); if (argc < 1 || (!JSVAL_IS_NULL(OOJS_ARGV[0]) && !JS_ValueToObject(context, OOJS_ARGV[0], ¶ms))) { OOJSReportBadArguments(context, @"Ship", @"setCrew", MIN(argc, 1U), OOJS_ARGV, NULL, @"definition"); return NO; } BOOL success = YES; if (![thisEnt isExplicitlyUnpiloted]) { if (JSVAL_IS_NULL(OOJS_ARGV[0])) { [thisEnt setCrew:nil]; } else { crewDefinition = OOJSNativeObjectFromJSObject(context, JSVAL_TO_OBJECT(OOJS_ARGV[0])); OOCharacter *crew = [OOCharacter characterWithDictionary:crewDefinition]; [thisEnt setCrew:[NSArray arrayWithObject:crew]]; } } else { success = NO; } OOJS_RETURN_BOOL(success); OOJS_NATIVE_EXIT } // setCargoType(cargoType : String) static JSBool ShipSetCargoType(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; NSString *cargoType = nil; GET_THIS_SHIP(thisEnt); if (argc > 0) cargoType = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(cargoType == nil)) { OOJSReportBadArguments(context, @"Ship", @"setCargoType", argc, OOJS_ARGV, nil, @"cargo type name"); return NO; } if ([thisEnt cargoType] != CARGO_NOT_CARGO) { OOJSReportBadArguments(context, @"Ship", @"setCargoType", argc, OOJS_ARGV, nil, [NSString stringWithFormat:@"Can only be used on cargo pod carriers, not cargo pods (%@)",[thisEnt shipDataKey]]); return NO; } BOOL ok = YES; if ([cargoType isEqualToString:@"SCARCE_GOODS"]) { [thisEnt setCargoFlag:CARGO_FLAG_FULL_SCARCE]; } else if ([cargoType isEqualToString:@"PLENTIFUL_GOODS"]) { [thisEnt setCargoFlag:CARGO_FLAG_FULL_PLENTIFUL]; } else if ([cargoType isEqualToString:@"MEDICAL_GOODS"]) { [thisEnt setCargoFlag:CARGO_FLAG_FULL_MEDICAL]; } else if ([cargoType isEqualToString:@"ILLEGAL_GOODS"]) { [thisEnt setCargoFlag:CARGO_FLAG_FULL_CONTRABAND]; } else if ([cargoType isEqualToString:@"PIRATE_GOODS"]) { [thisEnt setCargoFlag:CARGO_FLAG_PIRATE]; } else { ok = NO; } OOJS_RETURN_BOOL(ok); OOJS_NATIVE_EXIT } // setMaterials(params: dict, [shaders: dict]) // sets materials dictionary. Optional parameter sets the shaders dictionary too. static JSBool ShipSetMaterials(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; if (argc < 1) { OOJSReportBadArguments(context, @"Ship", @"setMaterials", 0, OOJS_ARGV, nil, @"parameter object"); return NO; } GET_THIS_SHIP(thisEnt); return ShipSetMaterialsInternal(context, argc, vp, thisEnt, NO); OOJS_NATIVE_EXIT } // setShaders(params: dict) static JSBool ShipSetShaders(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); if (argc < 1) { OOJSReportBadArguments(context, @"Ship", @"setShaders", 0, OOJS_ARGV, nil, @"parameter object"); return NO; } if (JSVAL_IS_NULL(OOJS_ARGV[0]) || (!JSVAL_IS_NULL(OOJS_ARGV[0]) && !JSVAL_IS_OBJECT(OOJS_ARGV[0]))) { // EMMSTRAN: JS_ValueToObject() and normal error handling here. OOJSReportWarning(context, @"Ship.%@: expected %@ instead of '%@'.", @"setShaders", @"object", OOStringFromJSValueEvenIfNull(context, OOJS_ARGV[0])); OOJS_RETURN_BOOL(NO); } OOJS_ARGV[1] = OOJS_ARGV[0]; return ShipSetMaterialsInternal(context, argc, vp, thisEnt, YES); OOJS_NATIVE_EXIT } static JSBool ShipSetMaterialsInternal(JSContext *context, uintN argc, jsval *vp, ShipEntity *thisEnt, BOOL fromShaders) { OOJS_PROFILE_ENTER JSObject *params = NULL; NSDictionary *materials; NSDictionary *shaders; BOOL withShaders = NO; BOOL success = NO; GET_THIS_SHIP(thisEnt); if (JSVAL_IS_NULL(OOJS_ARGV[0]) || (!JSVAL_IS_NULL(OOJS_ARGV[0]) && !JSVAL_IS_OBJECT(OOJS_ARGV[0]))) { OOJSReportWarning(context, @"Ship.%@: expected %@ instead of '%@'.", @"setMaterials", @"object", OOStringFromJSValueEvenIfNull(context, OOJS_ARGV[0])); OOJS_RETURN_BOOL(NO); } if (argc > 1) { withShaders = YES; if (JSVAL_IS_NULL(OOJS_ARGV[1]) || (!JSVAL_IS_NULL(OOJS_ARGV[1]) && !JSVAL_IS_OBJECT(OOJS_ARGV[1]))) { OOJSReportWarning(context, @"Ship.%@: expected %@ instead of '%@'.", @"setMaterials", @"object as second parameter", OOStringFromJSValueEvenIfNull(context, OOJS_ARGV[1])); withShaders = NO; } } if (fromShaders) { materials = [[thisEnt mesh] materials]; params = JSVAL_TO_OBJECT(OOJS_ARGV[0]); shaders = OOJSNativeObjectFromJSObject(context, params); } else { params = JSVAL_TO_OBJECT(OOJS_ARGV[0]); materials = OOJSNativeObjectFromJSObject(context, params); if (withShaders) { params = JSVAL_TO_OBJECT(OOJS_ARGV[1]); shaders = OOJSNativeObjectFromJSObject(context, params); } else { shaders = [[thisEnt mesh] shaders]; } } OOJS_BEGIN_FULL_NATIVE(context) NSDictionary *shipDict = [thisEnt shipInfoDictionary]; // First we test to see if we can create the mesh. OOMesh *mesh = [OOMesh meshWithName:[shipDict oo_stringForKey:@"model"] cacheKey:nil materialDictionary:materials shadersDictionary:shaders smooth:[shipDict oo_boolForKey:@"smooth" defaultValue:NO] shaderMacros:[[ResourceManager materialDefaults] oo_dictionaryForKey:@"ship-prefix-macros"] shaderBindingTarget:thisEnt]; if (mesh != nil) { [thisEnt setMesh:mesh]; success = YES; } OOJS_END_FULL_NATIVE OOJS_RETURN_BOOL(success); OOJS_PROFILE_EXIT } // exitSystem([int systemID]) static JSBool ShipExitSystem(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) ShipEntity *thisEnt = nil; int32 systemID = -1; BOOL OK = NO; GET_THIS_SHIP(thisEnt); if (EXPECT_NOT([thisEnt isPlayer])) { OOJSReportErrorForCaller(context, @"Ship", @"exitSystem", @"Not valid for player ship."); return NO; } if (argc > 0) { if (!JS_ValueToInt32(context, OOJS_ARGV[0], &systemID) || systemID < 0 || 255 < systemID) { OOJSReportBadArguments(context, @"Ship", @"exitSystem", MIN(argc, 1U), OOJS_ARGV, nil, @"system ID"); return NO; } } OK = [thisEnt performHyperSpaceToSpecificSystem:systemID]; // -1 == random destination system OOJS_RETURN_BOOL(OK); OOJS_NATIVE_EXIT } static JSBool ShipUpdateEscortFormation(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt updateEscortFormation]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static BOOL RemoveOrExplodeShip(JSContext *context, uintN argc, jsval *vp, BOOL explode) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); if (EXPECT_NOT([thisEnt isPlayer])) { NSCAssert(explode, @"RemoveOrExplodeShip(): shouldn't be called for player with !explode."); // player.ship.remove() is blocked by caller. PlayerEntity *player = (PlayerEntity *)thisEnt; if ([player isDocked]) { OOJSReportError(context, @"Cannot explode() player's ship while docked."); return NO; } } if (thisEnt == (ShipEntity *)[UNIVERSE station]) { // Allow exploding of main station (e.g. nova mission) [UNIVERSE unMagicMainStation]; } if (EXPECT_NOT([thisEnt status] == STATUS_DOCKED)) { /* If it's in the launch queue and hasn't yet been added to * the universe, set the status to DEAD (so it gets removed * from the launch queue) */ [thisEnt setStatus:STATUS_DEAD]; /* No shipDied event occurs in this place - it never really * existed. This case is just to prevent an error from the * usual code branch - removing ships while they're still * docked is not recommended. */ } else { [thisEnt setSuppressExplosion:!explode]; [thisEnt setEnergy:1]; [thisEnt takeEnergyDamage:500000000.0 from:nil becauseOf:nil]; } OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipClearDefenseTargets(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt removeAllDefenseTargets]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipAddDefenseTarget(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; ShipEntity *target = nil; GET_THIS_SHIP(thisEnt); if (EXPECT_NOT(argc == 0 || (argc > 0 && (JSVAL_IS_NULL(OOJS_ARGV[0]) || !JSVAL_IS_OBJECT(OOJS_ARGV[0]) || !JSShipGetShipEntity(context, JSVAL_TO_OBJECT(OOJS_ARGV[0]), &target))))) { OOJSReportBadArguments(context, @"Ship", @"addDefenseTarget", 1U, OOJS_ARGV, nil, @"target"); return NO; } [thisEnt addDefenseTarget:target]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipRemoveDefenseTarget(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; ShipEntity *target = nil; GET_THIS_SHIP(thisEnt); if (EXPECT_NOT(argc == 0 || (argc > 0 && (JSVAL_IS_NULL(OOJS_ARGV[0]) || !JSVAL_IS_OBJECT(OOJS_ARGV[0]) || !JSShipGetShipEntity(context, JSVAL_TO_OBJECT(OOJS_ARGV[0]), &target))))) { OOJSReportBadArguments(context, @"Ship", @"removeDefenseTarget", 1U, OOJS_ARGV, nil, @"target"); return NO; } [thisEnt removeDefenseTarget:target]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipAddCollisionException(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; ShipEntity *target = nil; GET_THIS_SHIP(thisEnt); if (EXPECT_NOT(argc == 0 || (argc > 0 && (JSVAL_IS_NULL(OOJS_ARGV[0]) || !JSVAL_IS_OBJECT(OOJS_ARGV[0]) || !JSShipGetShipEntity(context, JSVAL_TO_OBJECT(OOJS_ARGV[0]), &target))))) { OOJSReportBadArguments(context, @"Ship", @"addCollisionException", 1U, OOJS_ARGV, nil, @"other ship"); return NO; } // have to do it both ways because it's not defined which order // the collisions get tested in. More efficient to add both ways // than to test both ways [thisEnt addCollisionException:target]; [target addCollisionException:thisEnt]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipRemoveCollisionException(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; ShipEntity *target = nil; GET_THIS_SHIP(thisEnt); if (EXPECT_NOT(argc == 0 || (argc > 0 && (JSVAL_IS_NULL(OOJS_ARGV[0]) || !JSVAL_IS_OBJECT(OOJS_ARGV[0]) || !JSShipGetShipEntity(context, JSVAL_TO_OBJECT(OOJS_ARGV[0]), &target))))) { OOJSReportBadArguments(context, @"Ship", @"removeCollisionException", 1U, OOJS_ARGV, nil, @"other ship"); return NO; } // doesn't need a check to see if it was already gone [thisEnt removeCollisionException:target]; [target removeCollisionException:thisEnt]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } //getMaterials() static JSBool ShipGetMaterials(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; NSObject *result = nil; GET_THIS_SHIP(thisEnt); result = [[thisEnt mesh] materials]; if (result == nil) result = [NSDictionary dictionary]; OOJS_RETURN_OBJECT(result); OOJS_PROFILE_EXIT } //getShaders() static JSBool ShipGetShaders(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; NSObject *result = nil; GET_THIS_SHIP(thisEnt); result = [[thisEnt mesh] shaders]; if (result == nil) result = [NSDictionary dictionary]; OOJS_RETURN_OBJECT(result); OOJS_PROFILE_EXIT } static JSBool ShipBroadcastCascadeImminent(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt broadcastEnergyBlastImminent]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipBecomeCascadeExplosion(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt becomeEnergyBlast]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipOfferToEscort(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; ShipEntity *mother = nil; GET_THIS_SHIP(thisEnt); if (EXPECT_NOT(argc == 0 || (argc > 0 && (JSVAL_IS_NULL(OOJS_ARGV[0]) || !JSVAL_IS_OBJECT(OOJS_ARGV[0]) || !JSShipGetShipEntity(context, JSVAL_TO_OBJECT(OOJS_ARGV[0]), &mother))))) { OOJSReportBadArguments(context, @"Ship", @"offerToEscort", 1U, OOJS_ARGV, nil, @"target"); return NO; } BOOL result = [thisEnt suggestEscortTo:mother]; OOJS_RETURN_BOOL(result); OOJS_PROFILE_EXIT } static JSBool ShipRequestHelpFromGroup(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt groupAttackTarget]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipPatrolReportIn(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; ShipEntity *target = nil; GET_THIS_SHIP(thisEnt); if (EXPECT_NOT(argc == 0 || (argc > 0 && (JSVAL_IS_NULL(OOJS_ARGV[0]) || !JSVAL_IS_OBJECT(OOJS_ARGV[0]) || !JSShipGetShipEntity(context, JSVAL_TO_OBJECT(OOJS_ARGV[0]), &target))))) { OOJSReportBadArguments(context, @"Ship", @"addDefenseTarget", 1U, OOJS_ARGV, nil, @"target"); return NO; } if ([target isStation]) { StationEntity *station = (StationEntity*)target; [station acceptPatrolReportFrom:thisEnt]; } OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipMarkTargetForFines(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); ShipEntity *ship = [thisEnt primaryTarget]; BOOL ok = NO; if ((ship != nil) && ([ship status] != STATUS_DEAD) && ([ship status] != STATUS_DOCKED)) { ok = [ship markForFines]; } OOJS_RETURN_BOOL(ok); OOJS_PROFILE_EXIT } static JSBool ShipEnterWormhole(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; Entity *hole = nil; if ([PLAYER status] != STATUS_ENTERING_WITCHSPACE) { OOJSReportError(context, @"Cannot use this function while player's ship not entering witchspace."); return NO; } GET_THIS_SHIP(thisEnt); if (EXPECT_NOT(argc == 0 || (argc > 0 && !JSVAL_IS_NULL(OOJS_ARGV[0]) && (!JSVAL_IS_OBJECT(OOJS_ARGV[0]) || !OOJSEntityGetEntity(context, JSVAL_TO_OBJECT(OOJS_ARGV[0]), &hole))))) { [thisEnt enterPlayerWormhole]; } else { if (![hole isWormhole]) { OOJSReportBadArguments(context, @"Ship", @"enterWormhole", 1U, OOJS_ARGV, nil, @"[wormhole]"); return NO; } [thisEnt enterWormhole:(WormholeEntity*)hole]; } OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipNotifyGroupOfWormhole(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt wormholeEntireGroup]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipThrowSpark(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt setThrowSparks:YES]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipPerformAttack(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt performAttack]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipPerformCollect(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt performCollect]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipPerformEscort(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt performEscort]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipPerformFaceDestination(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt performFaceDestination]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipPerformFlee(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt performFlee]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipPerformFlyToRangeFromDestination(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt performFlyToRangeFromDestination]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipPerformHold(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt performHold]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipPerformIdle(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt performIdle]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipPerformIntercept(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt performIntercept]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipPerformLandOnPlanet(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt performLandOnPlanet]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipPerformMining(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt performMining]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipPerformScriptedAI(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt performScriptedAI]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipPerformScriptedAttackAI(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt performScriptedAttackAI]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipPerformStop(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt performStop]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipPerformTumble(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt performTumble]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipRequestDockingInstructions(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt requestDockingCoordinates]; NSDictionary *dockingInstructions = [thisEnt dockingInstructions]; if (dockingInstructions != nil) { OOJS_RETURN_OBJECT(dockingInstructions); } else { OOJS_RETURN_NULL; } OOJS_PROFILE_EXIT } static JSBool ShipRecallDockingInstructions(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt recallDockingInstructions]; NSDictionary *dockingInstructions = [thisEnt dockingInstructions]; if (dockingInstructions != nil) { OOJS_RETURN_OBJECT(dockingInstructions); } else { OOJS_RETURN_NULL; } OOJS_PROFILE_EXIT } static JSBool ShipBroadcastDistressMessage(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); [thisEnt broadcastDistressMessageWithDumping:NO]; OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } static JSBool ShipCheckCourseToDestination(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); Entity *hazard = [UNIVERSE hazardOnRouteFromEntity:thisEnt toDistance:[thisEnt desiredRange] fromPoint:[thisEnt destination]]; OOJS_RETURN_OBJECT(hazard); OOJS_PROFILE_EXIT } static JSBool ShipGetSafeCourseToDestination(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; GET_THIS_SHIP(thisEnt); HPVector waypoint = [UNIVERSE getSafeVectorFromEntity:thisEnt toDistance:[thisEnt desiredRange] fromPoint:[thisEnt destination]]; OOJS_RETURN_HPVECTOR(waypoint); OOJS_PROFILE_EXIT } static JSBool ShipCheckScanner(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; JSBool onlyCheckPowered = NO; GET_THIS_SHIP(thisEnt); if (argc > 0 && EXPECT_NOT(!JS_ValueToBoolean(context, OOJS_ARGV[0], &onlyCheckPowered))) { OOJSReportBadArguments(context, @"Ship", @"checkScanner", argc, OOJS_ARGV, nil, @"boolean"); return NO; } if (onlyCheckPowered) { [thisEnt checkScannerIgnoringUnpowered]; } else { [thisEnt checkScanner]; } ShipEntity **scannedShips = [thisEnt scannedShips]; unsigned num = [thisEnt numberOfScannedShips]; NSMutableArray *scanResult = [NSMutableArray array]; for (unsigned i = 0; i < num ; i++) { [scanResult addObject:scannedShips[i]]; } OOJS_RETURN_OBJECT(scanResult); OOJS_PROFILE_EXIT } static JSBool ShipAdjustCargo(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; NSString *commodity = @""; int32 adjustment = 0; GET_THIS_SHIP(thisEnt); if (argc < 2) { OOJSReportBadArguments(context, @"Ship", @"adjustCargo", argc, OOJS_ARGV, nil, @"commodity, amount"); return NO; } commodity = OOStringFromJSValue(context, OOJS_ARGV[0]); if (!JS_ValueToInt32(context, OOJS_ARGV[1], &adjustment)) { OOJSReportBadArguments(context, @"Ship", @"adjustCargo", argc, OOJS_ARGV, nil, @"commodity, amount"); return NO; } if ([thisEnt cargoType] != CARGO_NOT_CARGO || [thisEnt isPlayer]) { OOJSReportError(context, @"ship.adjustCargo may only be used on NPC cargo carriers"); return NO; } BOOL ok = YES; if (adjustment > 0) { NSArray *cargo = [UNIVERSE getContainersOfCommodity:commodity :adjustment]; // non-reified templates ok = [thisEnt addCargo:cargo]; } else if (adjustment < 0) { OOCargoQuantity r = (OOCargoQuantity)(-adjustment); ok = [thisEnt removeCargo:commodity amount:r]; } OOJS_RETURN_BOOL(ok); OOJS_PROFILE_EXIT } /* 0 = no significant damage or consumable loss, higher numbers mean some */ static JSBool ShipDamageAssessment(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; NSUInteger assessment = 0; GET_THIS_SHIP(thisEnt); // if could have missiles but doesn't, consumables low if ([thisEnt missileCapacity] > 0 && [[thisEnt missilesList] count] == 0) { assessment++; } // if has injectors but fuel is low, consumables low // if no injectors, not a problem if ([thisEnt hasFuelInjection] && [thisEnt fuel] < 35) { assessment++; } /* TODO: when NPC equipment can be damaged in combat, assess this * here */ OOJS_RETURN_INT(assessment); OOJS_PROFILE_EXIT } static JSBool ShipThreatAssessment(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER ShipEntity *thisEnt = nil; JSBool fullCheck = NO; GET_THIS_SHIP(thisEnt); if (argc > 0 && EXPECT_NOT(!JS_ValueToBoolean(context, OOJS_ARGV[0], &fullCheck))) { OOJSReportBadArguments(context, @"Ship", @"threatAssessment", argc, OOJS_ARGV, nil, @"boolean"); return NO; } // start with 2.5 per ship double assessment = 2.5; // +/- 0.1 for speed, larger subtraction for very slow ships GLfloat maxspeed = [thisEnt maxFlightSpeed]; assessment += (maxspeed-300)/1000; if (maxspeed < 200) { assessment += (maxspeed-200)/500; } /* FIXME: at the moment this means NPCs can detect other NPCs shield * boosters, since they're implemented as extra energy */ assessment += ([thisEnt maxEnergy]-200)/1000; // add on some for missiles. Mostly ignore 3rd and subsequent // missiles: either they can be ECMd or the first two are already // too many. if ([thisEnt missileCapacity] > 2) { assessment += 0.5; } else { assessment += ((double)[thisEnt missileCapacity])/5.0; } /* Turret count is public knowledge */ NSEnumerator *subEnum = [thisEnt shipSubEntityEnumerator]; ShipEntity *se = nil; while ((se = [subEnum nextObject])) { if ([se isTurret]) { /* TODO: consider making ship combat behaviour try to * stay at long range from enemies with turrets. Then * we could perhaps reduce this bonus a bit. */ assessment += 1; } } if (fullCheck) { // consider pilot skill if ([thisEnt isPlayer]) { double score = (double)[PLAYER score]; if (score > 6400) { score = 6400; } assessment += pow(score,0.33)/10; // 0 - 1.8 } else { assessment += [thisEnt accuracy]/5; } // check lasers OOWeaponType wt = [thisEnt weaponTypeIDForFacing:WEAPON_FACING_FORWARD strict:NO]; assessment += ShipThreatAssessmentWeapon(wt); if (isWeaponNone(wt)) { assessment -= 1.5; // further penalty for ships with no forward laser } wt = [thisEnt weaponTypeIDForFacing:WEAPON_FACING_AFT strict:NO]; if (!isWeaponNone(wt)) { assessment += 1 + ShipThreatAssessmentWeapon(wt); } // port and starboard weapons less important wt = [thisEnt weaponTypeIDForFacing:WEAPON_FACING_PORT strict:NO]; if (!isWeaponNone(wt)) { assessment += 0.2 + ShipThreatAssessmentWeapon(wt)/5.0; } wt = [thisEnt weaponTypeIDForFacing:WEAPON_FACING_STARBOARD strict:NO]; if (!isWeaponNone(wt)) { assessment += 0.2 + ShipThreatAssessmentWeapon(wt)/5.0; } // combat-related secondary equipment if ([thisEnt hasECM]) { assessment += 0.5; } if ([thisEnt hasFuelInjection]) { assessment += 0.5; } } else { // consider thargoids dangerous if ([thisEnt isThargoid]) { assessment *= 1.5; if ([thisEnt hasRole:@"thargoid-mothership"]) { assessment += 5; } } else { // consider that armed ships might have a trick or two if ([thisEnt weaponFacings] == 1) { assessment += 0.25; } else { // and more than one trick if they can mount multiple lasers assessment += 0.5; } } } // mostly ignore fleeing ships as threats if ([thisEnt behaviour] == BEHAVIOUR_FLEE_TARGET || [thisEnt behaviour] == BEHAVIOUR_FLEE_EVASIVE_ACTION) { assessment *= 0.2; } else if ([thisEnt isPlayer] && [(PlayerEntity*)thisEnt fleeingStatus] >= PLAYER_FLEEING_CARGO) { assessment *= 0.2; } // don't go too low. if (assessment < 0.1) { assessment = 0.1; } OOJS_RETURN_DOUBLE(assessment); OOJS_PROFILE_EXIT } static double ShipThreatAssessmentWeapon(OOWeaponType wt) { if (wt == nil) { return -1.0; } return [wt weaponThreatAssessment]; } /** Static methods */ static JSBool ShipStaticKeys(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context); OOShipRegistry *registry = [OOShipRegistry sharedRegistry]; NSArray *keys = [registry shipKeys]; OOJS_RETURN_OBJECT(keys); OOJS_NATIVE_EXIT } static JSBool ShipStaticKeysForRole(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context); OOShipRegistry *registry = [OOShipRegistry sharedRegistry]; if (argc > 0) { NSString *role = OOStringFromJSValue(context, OOJS_ARGV[0]); NSArray *keys = [registry shipKeysWithRole:role]; OOJS_RETURN_OBJECT(keys); } else { OOJSReportBadArguments(context, @"Ship", @"shipKeysForRole", MIN(argc, 1U), OOJS_ARGV, nil, @"ship role"); return NO; } OOJS_NATIVE_EXIT } static JSBool ShipStaticRoleIsInCategory(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context); if (argc > 1) { NSString *role = OOStringFromJSValue(context, OOJS_ARGV[0]); NSString *category = OOStringFromJSValue(context, OOJS_ARGV[1]); OOJS_RETURN_BOOL([UNIVERSE role:role isInCategory:category]); } else { OOJSReportBadArguments(context, @"Ship", @"roleIsInCategory", MIN(argc, 2U), OOJS_ARGV, nil, @"role, category"); return NO; } OOJS_NATIVE_EXIT } static JSBool ShipStaticRoles(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context); OOShipRegistry *registry = [OOShipRegistry sharedRegistry]; NSArray *keys = [registry shipRoles]; OOJS_RETURN_OBJECT(keys); OOJS_NATIVE_EXIT } static JSBool ShipStaticShipDataForKey(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context); OOShipRegistry *registry = [OOShipRegistry sharedRegistry]; if (argc > 0) { NSString *key = OOStringFromJSValue(context, OOJS_ARGV[0]); NSDictionary *keys = [registry shipInfoForKey:key]; OOJS_RETURN_OBJECT(keys); } else { OOJSReportBadArguments(context, @"Ship", @"shipDataForKey", MIN(argc, 1U), OOJS_ARGV, nil, @"ship role"); return NO; } OOJS_NATIVE_EXIT } oolite-1.82/src/Core/Scripting/OOJSShipGroup.h000066400000000000000000000016051256642440500211560ustar00rootroot00000000000000/* OOJSShipGroup.h JavaScript wrapper for ship group objects. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include void InitOOJSShipGroup(JSContext *context, JSObject *global); oolite-1.82/src/Core/Scripting/OOJSShipGroup.m000066400000000000000000000235151256642440500211670ustar00rootroot00000000000000/* OOShipGroup.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOShipGroup.h" #import "OOJavaScriptEngine.h" #import "OOShipGroup.h" #import "Universe.h" static JSObject *sShipGroupPrototype; static JSBool ShipGroupGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool ShipGroupSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); static JSBool ShipGroupConstruct(JSContext *context, uintN argc, jsval *vp); // Methods static JSBool ShipGroupAddShip(JSContext *context, uintN argc, jsval *vp); static JSBool ShipGroupRemoveShip(JSContext *context, uintN argc, jsval *vp); static JSBool ShipGroupContainsShip(JSContext *context, uintN argc, jsval *vp); static JSClass sShipGroupClass = { "ShipGroup", JSCLASS_HAS_PRIVATE, JS_PropertyStub, // addProperty JS_PropertyStub, // delProperty ShipGroupGetProperty, // getProperty ShipGroupSetProperty, // setProperty JS_EnumerateStub, // enumerate JS_ResolveStub, // resolve JS_ConvertStub, // convert OOJSObjectWrapperFinalize,// finalize JSCLASS_NO_OPTIONAL_MEMBERS }; enum { // Property IDs kShipGroup_ships, // array of ships, double, read-only kShipGroup_leader, // leader, Ship, read/write kShipGroup_name, // name, string, read/write kShipGroup_count, // number of ships, integer, read-only }; static JSPropertySpec sShipGroupProperties[] = { // JS name ID flags { "count", kShipGroup_count, OOJS_PROP_READONLY_CB }, { "leader", kShipGroup_leader, OOJS_PROP_READWRITE_CB }, { "name", kShipGroup_name, OOJS_PROP_READWRITE_CB }, { "ships", kShipGroup_ships, OOJS_PROP_READONLY_CB }, { 0 } }; static JSFunctionSpec sShipGroupMethods[] = { // JS name Function min args { "toString", OOJSObjectWrapperToString, 0 }, { "addShip", ShipGroupAddShip, 1 }, { "containsShip", ShipGroupContainsShip, 1 }, { "removeShip", ShipGroupRemoveShip, 1 }, { 0 } }; DEFINE_JS_OBJECT_GETTER(JSShipGroupGetShipGroup, &sShipGroupClass, sShipGroupPrototype, OOShipGroup); void InitOOJSShipGroup(JSContext *context, JSObject *global) { sShipGroupPrototype = JS_InitClass(context, global, NULL, &sShipGroupClass, ShipGroupConstruct, 0, sShipGroupProperties, sShipGroupMethods, NULL, NULL); OOJSRegisterObjectConverter(&sShipGroupClass, OOJSBasicPrivateObjectConverter); } static JSBool ShipGroupGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) OOShipGroup *group = nil; id result = nil; if (EXPECT_NOT(!JSShipGroupGetShipGroup(context, this, &group))) return NO; switch (JSID_TO_INT(propID)) { case kShipGroup_ships: result = [group memberArray]; if (result == nil) result = [NSArray array]; break; case kShipGroup_leader: result = [group leader]; break; case kShipGroup_name: result = [group name]; if (result == nil) result = [NSNull null]; break; case kShipGroup_count: return JS_NewNumberValue(context, [group count], value); default: OOJSReportBadPropertySelector(context, this, propID, sShipGroupProperties); return NO; } *value = OOJSValueFromNativeObject(context, result); return YES; OOJS_NATIVE_EXIT } static JSBool ShipGroupSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) OOShipGroup *group = nil; ShipEntity *shipValue = nil; if (EXPECT_NOT(!JSShipGroupGetShipGroup(context, this, &group))) return NO; switch (JSID_TO_INT(propID)) { case kShipGroup_leader: shipValue = OOJSNativeObjectOfClassFromJSValue(context, *value, [ShipEntity class]); if (shipValue != nil || JSVAL_IS_NULL(*value)) { [group setLeader:shipValue]; return YES; } break; case kShipGroup_name: [group setName:OOStringFromJSValueEvenIfNull(context, *value)]; return YES; break; default: OOJSReportBadPropertySelector(context, this, propID, sShipGroupProperties); return NO; } OOJSReportBadPropertyValue(context, this, propID, sShipGroupProperties, *value); return NO; OOJS_NATIVE_EXIT } // new ShipGroup([name : String [, leader : Ship]]) : ShipGroup static JSBool ShipGroupConstruct(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) if (EXPECT_NOT(!JS_IsConstructing(context, vp))) { OOJSReportError(context, @"ShipGroup() cannot be called as a function, it must be used as a constructor (as in new ShipGroup(...))."); return NO; } NSString *name = nil; ShipEntity *leader = nil; if (argc >= 1) { if (!JSVAL_IS_STRING(OOJS_ARGV[0])) { OOJSReportBadArguments(context, nil, @"ShipGroup()", 1, OOJS_ARGV, @"Could not create ShipGroup", @"group name"); return NO; } name = OOStringFromJSValue(context, OOJS_ARGV[0]); } if (argc >= 2) { leader = OOJSNativeObjectOfClassFromJSValue(context, OOJS_ARGV[1], [ShipEntity class]); if (leader == nil && !JSVAL_IS_NULL(OOJS_ARGV[1])) { OOJSReportBadArguments(context, nil, @"ShipGroup()", 1, OOJS_ARGV + 1, @"Could not create ShipGroup", @"ship"); return NO; } } OOJS_RETURN_OBJECT([OOShipGroup groupWithName:name leader:leader]); OOJS_NATIVE_EXIT } @implementation OOShipGroup (OOJavaScriptExtensions) - (jsval) oo_jsValueInContext:(JSContext *)context { jsval result = JSVAL_NULL; if (_jsSelf == NULL) { _jsSelf = JS_NewObject(context, &sShipGroupClass, sShipGroupPrototype, NULL); if (_jsSelf != NULL) { if (!JS_SetPrivate(context, _jsSelf, [self retain])) _jsSelf = NULL; } } if (_jsSelf != NULL) result = OBJECT_TO_JSVAL(_jsSelf); return result; } - (void) oo_clearJSSelf:(JSObject *)selfVal { if (_jsSelf == selfVal) _jsSelf = NULL; } @end // *** Methods *** // addShip(ship : Ship) static JSBool ShipGroupAddShip(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOShipGroup *thisGroup = nil; ShipEntity *ship = nil; BOOL OK = YES; if (EXPECT_NOT(!JSShipGroupGetShipGroup(context, OOJS_THIS, &thisGroup))) return NO; if (argc > 0) ship = OOJSNativeObjectOfClassFromJSValue(context, OOJS_ARGV[0], [ShipEntity class]); if (ship == nil) { if (argc > 0 && JSVAL_IS_NULL(OOJS_ARGV[0])) OOJS_RETURN_VOID; // OK, do nothing for null ship. OOJSReportBadArguments(context, @"ShipGroup", @"addShip", MIN(argc, 1U), OOJS_ARGV, nil, @"ship"); return NO; } if ([thisGroup containsShip:ship]) { // nothing to do... OOJS_RETURN_VOID; } else { ShipEntity *thisGroupLeader = [thisGroup leader]; if ([thisGroupLeader escortGroup] == thisGroup) // escort group! { if ([thisGroup count] > 1) // already with some escorts { OOShipGroup *thatGroup = [ship group]; if ([thatGroup count] > 1 && [[thatGroup leader] escortGroup] == thatGroup) // new escort already escorting! { OOJSReportWarningForCaller(context, @"ShipGroup", @"addShip", @"Ship %@ cannot be assigned to two escort groups, ignoring.", ship); OK = NO; } else { OK = [thisGroupLeader acceptAsEscort:ship]; } } else // [thisGroup count] == 1, default unescorted ship? { if ([thisGroupLeader escortGroup] == [thisGroupLeader group]) { // Default unescorted, unescortable, ship. Create new group and use that instead. [thisGroupLeader setGroup:[[OOShipGroup alloc] initWithName:@"ship group"]]; thisGroup = [thisGroupLeader group]; } else { // Unescorted ship with custom group. See if it accepts escorts. OK = [thisGroupLeader acceptAsEscort:ship]; } } } if (OK) { OOJS_RETURN_BOOL([thisGroup addShip:ship]); // if ship is there already, noop & YES } else OOJS_RETURN_BOOL(NO); } OOJS_NATIVE_EXIT } // removeShip(ship : Ship) static JSBool ShipGroupRemoveShip(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOShipGroup *thisGroup = nil; ShipEntity *ship = nil; if (EXPECT_NOT(!JSShipGroupGetShipGroup(context, OOJS_THIS, &thisGroup))) return NO; if (argc > 0) ship = OOJSNativeObjectOfClassFromJSValue(context, OOJS_ARGV[0], [ShipEntity class]); if (ship == nil) { if (argc > 0 && JSVAL_IS_NULL(OOJS_ARGV[0])) OOJS_RETURN_VOID; // OK, do nothing for null ship. OOJSReportBadArguments(context, @"ShipGroup", @"removeShip", MIN(argc, 1U), OOJS_ARGV, nil, @"ship"); return NO; } OOJS_RETURN_BOOL([thisGroup removeShip:ship]); OOJS_NATIVE_EXIT } // containsShip(ship : Ship) : Boolean static JSBool ShipGroupContainsShip(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOShipGroup *thisGroup = nil; ShipEntity *ship = nil; if (EXPECT_NOT(!JSShipGroupGetShipGroup(context, OOJS_THIS, &thisGroup))) return NO; if (argc > 0) ship = OOJSNativeObjectOfClassFromJSValue(context, OOJS_ARGV[0], [ShipEntity class]); if (ship == nil) { if (argc > 0 && JSVAL_IS_NULL(OOJS_ARGV[0])) OOJS_RETURN_BOOL(NO); // OK, return false for null ship. OOJSReportBadArguments(context, @"ShipGroup", @"containsShip", MIN(argc, 1U), OOJS_ARGV, nil, @"ship"); return NO; } OOJS_RETURN_BOOL([thisGroup containsShip:ship]); OOJS_NATIVE_EXIT } oolite-1.82/src/Core/Scripting/OOJSSound.h000066400000000000000000000021431256642440500203240ustar00rootroot00000000000000/* OOJSSound.h JavaScript sound object. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #include @class OOSound; void InitOOJSSound(JSContext *context, JSObject *global); /* SoundFromJSValue() Convert a JS value to a sound. The value may be either a Sound object or a string specifying a sound name. */ OOSound *SoundFromJSValue(JSContext *context, jsval value); oolite-1.82/src/Core/Scripting/OOJSSound.m000066400000000000000000000146361256642440500203430ustar00rootroot00000000000000/* OOJSSound.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSSound.h" #import "OOJavaScriptEngine.h" #import "OOSound.h" #import "OOMusicController.h" #import "ResourceManager.h" #import "Universe.h" static JSObject *sSoundPrototype; static OOSound *GetNamedSound(NSString *name); static JSBool SoundGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); // Static methods static JSBool SoundStaticLoad(JSContext *context, uintN argc, jsval *vp); static JSBool SoundStaticPlayMusic(JSContext *context, uintN argc, jsval *vp); static JSBool SoundStaticStopMusic(JSContext *context, uintN argc, jsval *vp); static JSClass sSoundClass = { "Sound", JSCLASS_HAS_PRIVATE, JS_PropertyStub, // addProperty JS_PropertyStub, // delProperty SoundGetProperty, // getProperty JS_StrictPropertyStub, // setProperty JS_EnumerateStub, // enumerate JS_ResolveStub, // resolve JS_ConvertStub, // convert OOJSObjectWrapperFinalize, // finalize JSCLASS_NO_OPTIONAL_MEMBERS }; enum { // Property IDs kSound_name }; static JSPropertySpec sSoundProperties[] = { // JS name ID flags { "name", kSound_name, OOJS_PROP_READONLY_CB }, { 0 } }; static JSFunctionSpec sSoundMethods[] = { // JS name Function min args { "toString", OOJSObjectWrapperToString, 0, }, { 0 } }; static JSFunctionSpec sSoundStaticMethods[] = { // JS name Function min args { "load", SoundStaticLoad, 1, }, { "playMusic", SoundStaticPlayMusic, 1, }, { "stopMusic", SoundStaticStopMusic, 0, }, { 0 } }; DEFINE_JS_OBJECT_GETTER(JSSoundGetSound, &sSoundClass, sSoundPrototype, OOSound) // *** Public *** void InitOOJSSound(JSContext *context, JSObject *global) { sSoundPrototype = JS_InitClass(context, global, NULL, &sSoundClass, OOJSUnconstructableConstruct, 0, sSoundProperties, sSoundMethods, NULL, sSoundStaticMethods); OOJSRegisterObjectConverter(&sSoundClass, OOJSBasicPrivateObjectConverter); } OOSound *SoundFromJSValue(JSContext *context, jsval value) { OOJS_PROFILE_ENTER OOJSPauseTimeLimiter(); if ([PLAYER status] != STATUS_START_GAME && JSVAL_IS_STRING(value)) { return GetNamedSound(OOStringFromJSValue(context, value)); } else { return OOJSNativeObjectOfClassFromJSValue(context, value, [OOSound class]); } OOJSResumeTimeLimiter(); OOJS_PROFILE_EXIT } // *** Implementation stuff *** static JSBool SoundGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) OOSound *sound = nil; if (EXPECT_NOT(!JSSoundGetSound(context, this, &sound))) return NO; switch (JSID_TO_INT(propID)) { case kSound_name: *value = OOJSValueFromNativeObject(context, [sound name]); return YES; default: OOJSReportBadPropertySelector(context, this, propID, sSoundProperties); return NO; } OOJS_NATIVE_EXIT } static OOSound *GetNamedSound(NSString *name) { OOSound *sound = nil; if ([name hasPrefix:@"["] && [name hasSuffix:@"]"]) { sound = [OOSound soundWithCustomSoundKey:name]; } else { sound = [ResourceManager ooSoundNamed:name inFolder:@"Sounds"]; } return sound; } // *** Static methods *** // load(name : String) : Sound static JSBool SoundStaticLoad(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *name = nil; OOSound *sound = nil; if (argc > 0) name = OOStringFromJSValue(context, OOJS_ARGV[0]); if (name == nil) { OOJSReportBadArguments(context, @"Sound", @"load", MIN(argc, 1U), OOJS_ARGV, nil, @"string"); return NO; } OOJS_BEGIN_FULL_NATIVE(context) sound = GetNamedSound(name); OOJS_END_FULL_NATIVE OOJS_RETURN_OBJECT(sound); OOJS_NATIVE_EXIT } // playMusic(name : String [, loop : Boolean]) static JSBool SoundStaticPlayMusic(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *name = nil; JSBool loop = NO; if (argc > 0) name = OOStringFromJSValue(context, OOJS_ARGV[0]); if (name == nil) { OOJSReportBadArguments(context, @"Sound", @"playMusic", MIN(argc, 1U), OOJS_ARGV, nil, @"string"); return NO; } if (argc > 1) { if (!JS_ValueToBoolean(context, OOJS_ARGV[1], &loop)) { OOJSReportBadArguments(context, @"Sound", @"playMusic", 1, OOJS_ARGV + 1, nil, @"boolean"); return NO; } } OOJS_BEGIN_FULL_NATIVE(context) [[OOMusicController sharedController] playMusicNamed:name loop:loop]; OOJS_END_FULL_NATIVE OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // Sound.stopMusic([name : String]) static JSBool SoundStaticStopMusic(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *name = nil; if (argc > 0) { name = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(name == nil)) { OOJSReportBadArguments(context, @"Sound", @"stopMusic", argc, OOJS_ARGV, nil, @"string or no argument"); return NO; } } OOJS_BEGIN_FULL_NATIVE(context) OOMusicController *controller = [OOMusicController sharedController]; if (name == nil || [name isEqualToString:[controller playingMusic]]) { [[OOMusicController sharedController] stop]; } OOJS_END_FULL_NATIVE OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } @implementation OOSound (OOJavaScriptExtentions) - (jsval) oo_jsValueInContext:(JSContext *)context { JSObject *jsSelf = NULL; jsval result = JSVAL_NULL; jsSelf = JS_NewObject(context, &sSoundClass, sSoundPrototype, NULL); if (jsSelf != NULL) { if (!JS_SetPrivate(context, jsSelf, [self retain])) jsSelf = NULL; } if (jsSelf != NULL) result = OBJECT_TO_JSVAL(jsSelf); return result; } - (NSString *) oo_jsDescription { return [NSString stringWithFormat:@"[Sound \"%@\"]", [self name]]; } - (NSString *) oo_jsClassName { return @"Sound"; } @end oolite-1.82/src/Core/Scripting/OOJSSoundSource.h000066400000000000000000000016651256642440500215150ustar00rootroot00000000000000/* OOJSSoundSource.h JavaScript sound source object. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #include @class OOSoundSource; void InitOOJSSoundSource(JSContext *context, JSObject *global); oolite-1.82/src/Core/Scripting/OOJSSoundSource.m000066400000000000000000000207011256642440500215120ustar00rootroot00000000000000/* OOJSSoundSource.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSSound.h" #import "OOJSVector.h" #import "OOJavaScriptEngine.h" #import "OOSound.h" #import "ResourceManager.h" static JSObject *sSoundSourcePrototype; static JSBool SoundSourceGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool SoundSourceSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); static JSBool SoundSourceConstruct(JSContext *context, uintN argc, jsval *vp); // Methods static JSBool SoundSourcePlay(JSContext *context, uintN argc, jsval *vp); static JSBool SoundSourceStop(JSContext *context, uintN argc, jsval *vp); static JSBool SoundSourcePlayOrRepeat(JSContext *context, uintN argc, jsval *vp); static JSClass sSoundSourceClass = { "SoundSource", JSCLASS_HAS_PRIVATE, JS_PropertyStub, // addProperty JS_PropertyStub, // delProperty SoundSourceGetProperty, // getProperty SoundSourceSetProperty, // setProperty JS_EnumerateStub, // enumerate JS_ResolveStub, // resolve JS_ConvertStub, // convert OOJSObjectWrapperFinalize, // finalize JSCLASS_NO_OPTIONAL_MEMBERS }; enum { // Property IDs kSoundSource_sound, kSoundSource_isPlaying, kSoundSource_loop, kSoundSource_position, kSoundSource_positional, kSoundSource_repeatCount, kSoundSource_volume }; static JSPropertySpec sSoundSourceProperties[] = { // JS name ID flags { "isPlaying", kSoundSource_isPlaying, OOJS_PROP_READONLY_CB }, { "loop", kSoundSource_loop, OOJS_PROP_READWRITE_CB }, { "position", kSoundSource_position, OOJS_PROP_READWRITE_CB }, { "positional", kSoundSource_positional, OOJS_PROP_READWRITE_CB }, { "repeatCount", kSoundSource_repeatCount, OOJS_PROP_READWRITE_CB }, { "sound", kSoundSource_sound, OOJS_PROP_READWRITE_CB }, { "volume", kSoundSource_volume, OOJS_PROP_READWRITE_CB }, { 0 } }; static JSFunctionSpec sSoundSourceMethods[] = { // JS name Function min args { "toString", OOJSObjectWrapperToString, 0, }, { "play", SoundSourcePlay, 0, }, { "playOrRepeat", SoundSourcePlayOrRepeat, 0, }, // playSound is defined in oolite-global-prefix.js. { "stop", SoundSourceStop, 0, }, { 0 } }; DEFINE_JS_OBJECT_GETTER(JSSoundSourceGetSoundSource, &sSoundSourceClass, sSoundSourcePrototype, OOSoundSource) // *** Public *** void InitOOJSSoundSource(JSContext *context, JSObject *global) { sSoundSourcePrototype = JS_InitClass(context, global, NULL, &sSoundSourceClass, SoundSourceConstruct, 0, sSoundSourceProperties, sSoundSourceMethods, NULL, NULL); OOJSRegisterObjectConverter(&sSoundSourceClass, OOJSBasicPrivateObjectConverter); } static JSBool SoundSourceConstruct(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) if (EXPECT_NOT(!JS_IsConstructing(context, vp))) { OOJSReportError(context, @"SoundSource() cannot be called as a function, it must be used as a constructor (as in new SoundSource())."); return NO; } OOJS_RETURN_OBJECT([[[OOSoundSource alloc] init] autorelease]); OOJS_NATIVE_EXIT } // *** Implementation stuff *** static JSBool SoundSourceGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) OOSoundSource *soundSource = nil; if (!JSSoundSourceGetSoundSource(context, this, &soundSource)) return NO; switch (JSID_TO_INT(propID)) { case kSoundSource_sound: *value = OOJSValueFromNativeObject(context, [soundSource sound]); return YES; case kSoundSource_isPlaying: *value = OOJSValueFromBOOL([soundSource isPlaying]); return YES; case kSoundSource_loop: *value = OOJSValueFromBOOL([soundSource loop]); return YES; case kSoundSource_repeatCount: *value = INT_TO_JSVAL([soundSource repeatCount]); return YES; case kSoundSource_position: return VectorToJSValue(context, [soundSource position], value); case kSoundSource_positional: *value = OOJSValueFromBOOL([soundSource positional]); return YES; case kSoundSource_volume: return JS_NewNumberValue(context, [soundSource gain], value); default: OOJSReportBadPropertySelector(context, this, propID, sSoundSourceProperties); return NO; } OOJS_NATIVE_EXIT } static JSBool SoundSourceSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) OOSoundSource *soundSource = nil; int32 iValue; JSBool bValue; Vector vValue; double fValue; if (!JSSoundSourceGetSoundSource(context, this, &soundSource)) return NO; switch (JSID_TO_INT(propID)) { case kSoundSource_sound: [soundSource setSound:SoundFromJSValue(context, *value)]; return YES; break; case kSoundSource_loop: if (JS_ValueToBoolean(context, *value, &bValue)) { [soundSource setLoop:bValue]; return YES; } break; case kSoundSource_repeatCount: if (JS_ValueToInt32(context, *value, &iValue)) { if (iValue > 100) iValue = 100; if (100 < 1) iValue = 1; [soundSource setRepeatCount:iValue]; return YES; } break; case kSoundSource_position: if (JSValueToVector(context, *value, &vValue)) { [soundSource setPosition:vValue]; return YES; } break; case kSoundSource_positional: if (JS_ValueToBoolean(context, *value, &bValue)) { [soundSource setPositional:bValue]; return YES; } break; case kSoundSource_volume: if (JS_ValueToNumber(context, *value, &fValue)) { fValue = OOClamp_0_max_d(fValue, 1); [soundSource setGain:fValue]; return YES; } break; default: OOJSReportBadPropertySelector(context, this, propID, sSoundSourceProperties); return NO; } OOJSReportBadPropertyValue(context, this, propID, sSoundSourceProperties, *value); return NO; OOJS_NATIVE_EXIT } // *** Methods *** // play([count : Number]) static JSBool SoundSourcePlay(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOSoundSource *thisv = nil; int32 count = 0; if (EXPECT_NOT(!JSSoundSourceGetSoundSource(context, OOJS_THIS, &thisv))) return NO; if (argc > 0 && !JSVAL_IS_VOID(OOJS_ARGV[0]) && !JS_ValueToInt32(context, OOJS_ARGV[0], &count)) { OOJSReportBadArguments(context, @"SoundSource", @"play", 1, OOJS_ARGV, nil, @"integer count or no argument"); return NO; } if (count > 0) { if (count > 100) count = 100; [thisv setRepeatCount:count]; } OOJS_BEGIN_FULL_NATIVE(context) [thisv play]; OOJS_END_FULL_NATIVE OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // stop() static JSBool SoundSourceStop(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOSoundSource *thisv = nil; if (EXPECT_NOT(!JSSoundSourceGetSoundSource(context, OOJS_THIS, &thisv))) return NO; OOJS_BEGIN_FULL_NATIVE(context) [thisv stop]; OOJS_END_FULL_NATIVE OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // playOrRepeat() static JSBool SoundSourcePlayOrRepeat(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOSoundSource *thisv = nil; if (EXPECT_NOT(!JSSoundSourceGetSoundSource(context, OOJS_THIS, &thisv))) return NO; OOJS_BEGIN_FULL_NATIVE(context) [thisv playOrRepeat]; OOJS_END_FULL_NATIVE OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } @implementation OOSoundSource (OOJavaScriptExtentions) - (jsval) oo_jsValueInContext:(JSContext *)context { JSObject *jsSelf = NULL; jsval result = JSVAL_NULL; jsSelf = JS_NewObject(context, &sSoundSourceClass, sSoundSourcePrototype, NULL); if (jsSelf != NULL) { if (!JS_SetPrivate(context, jsSelf, [self retain])) jsSelf = NULL; } if (jsSelf != NULL) result = OBJECT_TO_JSVAL(jsSelf); return result; } - (NSString *) oo_jsClassName { return @"SoundSource"; } @end oolite-1.82/src/Core/Scripting/OOJSSpecialFunctions.h000066400000000000000000000024701256642440500225100ustar00rootroot00000000000000/* OOJSSpecialFunctions.h Special functions for certain scripts, currently the global prefix script and the debug console script. Note that it's possible for other scripts to get at the "special" object through the debug console object (debugConsole.script.special). If putting actually dangerous functions in here, it'd be a good idea to learn to use SpiderMonkey's security architecture (JSPrincipals and such). Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJavaScriptEngine.h" void InitOOJSSpecialFunctions(JSContext *context, JSObject *global); OOJSValue *JSSpecialFunctionsObjectWrapper(JSContext *context); oolite-1.82/src/Core/Scripting/OOJSSpecialFunctions.m000066400000000000000000000055041256642440500225160ustar00rootroot00000000000000/* OOJSSpecialFunctions.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #import "OOJSSpecialFunctions.h" static JSBool SpecialJSWarning(JSContext *context, uintN argc, jsval *vp); #ifndef NDEBUG static JSBool SpecialMarkConsoleEntryPoint(JSContext *context, uintN argc, jsval *vp); #endif static JSFunctionSpec sSpecialFunctionsMethods[] = { // JS name Function min args { "jsWarning", SpecialJSWarning, 1 }, #ifndef NDEBUG { "markConsoleEntryPoint", SpecialMarkConsoleEntryPoint, 0 }, #endif { 0 } }; void InitOOJSSpecialFunctions(JSContext *context, JSObject *global) { } OOJSValue *JSSpecialFunctionsObjectWrapper(JSContext *context) { /* Special object is created on the fly so it can be GCed (the debug console script keeps a reference to its copy, but the prefix script doesn't) and so we don't need to clean up a root on JS engine reset. -- Ahruman 2011-03-30 */ JSObject *special = NULL; OOJSAddGCObjectRoot(context, &special, "OOJSSpecialFunctions"); special = JS_NewObject(context, NULL, NULL, NULL); JS_DefineFunctions(context, special, sSpecialFunctionsMethods); JS_FreezeObject(context, special); OOJSValue *result = [OOJSValue valueWithJSObject:special inContext:context]; JS_RemoveObjectRoot(context, &special); return result; } static JSBool SpecialJSWarning(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER // These functions are exception-safe if (EXPECT_NOT(argc < 1)) { OOJSReportBadArguments(context, @"special", @"jsWarning", argc, OOJS_ARGV, nil, @"string"); return NO; } OOJSSetWarningOrErrorStackSkip(1); OOJSReportWarning(context, @"%@", OOStringFromJSValue(context, OOJS_ARGV[0])); OOJSSetWarningOrErrorStackSkip(0); OOJS_RETURN_VOID; OOJS_PROFILE_EXIT } #ifndef NDEBUG static JSBool SpecialMarkConsoleEntryPoint(JSContext *context, uintN argc, jsval *vp) { // First stack frame will be in eval() in console.script.evaluate(), unless someone is playing silly buggers. JSStackFrame *frame = NULL; if (JS_FrameIterator(context, &frame) != NULL) { OOJSMarkConsoleEvalLocation(context, frame); } OOJS_RETURN_VOID; } #endif oolite-1.82/src/Core/Scripting/OOJSStation.h000066400000000000000000000016641256642440500206640ustar00rootroot00000000000000/* OOJSStation.h JavaScript proxy for StationEntities. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #include @class StationEntity; void InitOOJSStation(JSContext *context, JSObject *global); oolite-1.82/src/Core/Scripting/OOJSStation.m000066400000000000000000000607111256642440500206670ustar00rootroot00000000000000/* OOJSStation.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSStation.h" #import "OOJSEntity.h" #import "OOJSShip.h" #import "OOJSPlayer.h" #import "OOJavaScriptEngine.h" #import "OOJSInterfaceDefinition.h" #import "OOConstToString.h" #import "StationEntity.h" #import "GameController.h" static JSObject *sStationPrototype; static BOOL JSStationGetStationEntity(JSContext *context, JSObject *stationObj, StationEntity **outEntity); static JSBool StationGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool StationSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); static JSBool StationAbortAllDockings(JSContext *context, uintN argc, jsval *vp); static JSBool StationAbortDockingForShip(JSContext *context, uintN argc, jsval *vp); static JSBool StationDockPlayer(JSContext *context, uintN argc, jsval *vp); static JSBool StationLaunchShipWithRole(JSContext *context, uintN argc, jsval *vp); static JSBool StationLaunchDefenseShip(JSContext *context, uintN argc, jsval *vp); static JSBool StationLaunchEscort(JSContext *context, uintN argc, jsval *vp); static JSBool StationLaunchScavenger(JSContext *context, uintN argc, jsval *vp); static JSBool StationLaunchMiner(JSContext *context, uintN argc, jsval *vp); static JSBool StationLaunchPirateShip(JSContext *context, uintN argc, jsval *vp); static JSBool StationLaunchShuttle(JSContext *context, uintN argc, jsval *vp); static JSBool StationLaunchPatrol(JSContext *context, uintN argc, jsval *vp); static JSBool StationLaunchPolice(JSContext *context, uintN argc, jsval *vp); static JSBool StationSetInterface(JSContext *context, uintN argc, jsval *vp); static JSBool StationSetMarketPrice(JSContext *context, uintN argc, jsval *vp); static JSBool StationSetMarketQuantity(JSContext *context, uintN argc, jsval *vp); static JSClass sStationClass = { "Station", JSCLASS_HAS_PRIVATE, JS_PropertyStub, // addProperty JS_PropertyStub, // delProperty StationGetProperty, // getProperty StationSetProperty, // setProperty JS_EnumerateStub, // enumerate JS_ResolveStub, // resolve JS_ConvertStub, // convert OOJSObjectWrapperFinalize,// finalize JSCLASS_NO_OPTIONAL_MEMBERS }; enum { // Property IDs kStation_alertCondition, kStation_allegiance, kStation_allowsAutoDocking, kStation_allowsFastDocking, kStation_breakPattern, kStation_dockedContractors, // miners and scavengers. kStation_dockedDefenders, kStation_dockedPolice, kStation_equipmentPriceFactor, kStation_equivalentTechLevel, kStation_hasNPCTraffic, kStation_hasShipyard, kStation_isMainStation, // Is [UNIVERSE station], boolean, read-only kStation_market, kStation_requiresDockingClearance, kStation_roll, kStation_suppressArrivalReports, }; static JSPropertySpec sStationProperties[] = { // JS name ID flags { "alertCondition", kStation_alertCondition, OOJS_PROP_READWRITE_CB }, { "allegiance", kStation_allegiance, OOJS_PROP_READWRITE_CB }, { "allowsAutoDocking", kStation_allowsAutoDocking, OOJS_PROP_READWRITE_CB }, { "allowsFastDocking", kStation_allowsFastDocking, OOJS_PROP_READWRITE_CB }, { "breakPattern", kStation_breakPattern, OOJS_PROP_READWRITE_CB }, { "dockedContractors", kStation_dockedContractors, OOJS_PROP_READONLY_CB }, { "dockedDefenders", kStation_dockedDefenders, OOJS_PROP_READONLY_CB }, { "dockedPolice", kStation_dockedPolice, OOJS_PROP_READONLY_CB }, { "equipmentPriceFactor", kStation_equipmentPriceFactor, OOJS_PROP_READONLY_CB }, { "equivalentTechLevel", kStation_equivalentTechLevel, OOJS_PROP_READONLY_CB }, { "hasNPCTraffic", kStation_hasNPCTraffic, OOJS_PROP_READWRITE_CB }, { "hasShipyard", kStation_hasShipyard, OOJS_PROP_READONLY_CB }, { "isMainStation", kStation_isMainStation, OOJS_PROP_READONLY_CB }, { "market", kStation_market, OOJS_PROP_READONLY_CB }, { "requiresDockingClearance", kStation_requiresDockingClearance, OOJS_PROP_READWRITE_CB }, { "roll", kStation_roll, OOJS_PROP_READWRITE_CB }, { "suppressArrivalReports", kStation_suppressArrivalReports, OOJS_PROP_READWRITE_CB }, { 0 } }; static JSFunctionSpec sStationMethods[] = { // JS name Function min args { "abortAllDockings", StationAbortAllDockings, 0 }, { "abortDockingForShip", StationAbortDockingForShip, 1 }, { "dockPlayer", StationDockPlayer, 0 }, { "launchDefenseShip", StationLaunchDefenseShip, 0 }, { "launchEscort", StationLaunchEscort, 0 }, { "launchMiner", StationLaunchMiner, 0 }, { "launchPatrol", StationLaunchPatrol, 0 }, { "launchPirateShip", StationLaunchPirateShip, 0 }, { "launchPolice", StationLaunchPolice, 0 }, { "launchScavenger", StationLaunchScavenger, 0 }, { "launchShipWithRole", StationLaunchShipWithRole, 1 }, { "launchShuttle", StationLaunchShuttle, 0 }, { "setInterface", StationSetInterface, 0 }, { "setMarketPrice", StationSetMarketPrice, 2 }, { "setMarketQuantity", StationSetMarketQuantity, 2 }, { 0 } }; void InitOOJSStation(JSContext *context, JSObject *global) { sStationPrototype = JS_InitClass(context, global, JSShipPrototype(), &sStationClass, OOJSUnconstructableConstruct, 0, sStationProperties, sStationMethods, NULL, NULL); OOJSRegisterObjectConverter(&sStationClass, OOJSBasicPrivateObjectConverter); OOJSRegisterSubclass(&sStationClass, JSShipClass()); } static BOOL JSStationGetStationEntity(JSContext *context, JSObject *stationObj, StationEntity **outEntity) { OOJS_PROFILE_ENTER BOOL result; Entity *entity = nil; if (outEntity == NULL) return NO; *outEntity = nil; result = OOJSEntityGetEntity(context, stationObj, &entity); if (!result) return NO; if (![entity isKindOfClass:[StationEntity class]]) return NO; *outEntity = (StationEntity *)entity; return YES; OOJS_PROFILE_EXIT } static BOOL JSStationGetShipEntity(JSContext *context, JSObject *shipObj, ShipEntity **outEntity) { OOJS_PROFILE_ENTER BOOL result; Entity *entity = nil; if (outEntity == NULL) return NO; *outEntity = nil; result = OOJSEntityGetEntity(context, shipObj, &entity); if (!result) return NO; if (![entity isKindOfClass:[ShipEntity class]]) return NO; *outEntity = (ShipEntity *)entity; return YES; OOJS_PROFILE_EXIT } @implementation StationEntity (OOJavaScriptExtensions) - (void)getJSClass:(JSClass **)outClass andPrototype:(JSObject **)outPrototype { *outClass = &sStationClass; *outPrototype = sStationPrototype; } - (NSString *) oo_jsClassName { return @"Station"; } @end static JSBool StationGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) StationEntity *entity = nil; if (!JSStationGetStationEntity(context, this, &entity)) return NO; if (entity == nil) { *value = JSVAL_VOID; return YES; } switch (JSID_TO_INT(propID)) { case kStation_isMainStation: *value = OOJSValueFromBOOL(entity == [UNIVERSE station]); return YES; case kStation_hasNPCTraffic: *value = OOJSValueFromBOOL([entity hasNPCTraffic]); return YES; case kStation_hasShipyard: *value = OOJSValueFromBOOL([entity hasShipyard]); return YES; case kStation_alertCondition: *value = INT_TO_JSVAL([entity alertLevel]); return YES; case kStation_allegiance: { NSString *result = [entity allegiance]; *value = OOJSValueFromNativeObject(context, result); return YES; } case kStation_requiresDockingClearance: *value = OOJSValueFromBOOL([entity requiresDockingClearance]); return YES; case kStation_roll: // same as in ship definition, but this time read/write below return JS_NewNumberValue(context, [entity flightRoll], value); case kStation_allowsFastDocking: *value = OOJSValueFromBOOL([entity allowsFastDocking]); return YES; case kStation_allowsAutoDocking: *value = OOJSValueFromBOOL([entity allowsAutoDocking]); return YES; case kStation_dockedContractors: *value = INT_TO_JSVAL([entity countOfDockedContractors]); return YES; case kStation_dockedPolice: *value = INT_TO_JSVAL([entity countOfDockedPolice]); return YES; case kStation_dockedDefenders: *value = INT_TO_JSVAL([entity countOfDockedDefenders]); return YES; case kStation_equivalentTechLevel: *value = INT_TO_JSVAL((int32_t)[entity equivalentTechLevel]); return YES; case kStation_equipmentPriceFactor: return JS_NewNumberValue(context, [entity equipmentPriceFactor], value); case kStation_suppressArrivalReports: *value = OOJSValueFromBOOL([entity suppressArrivalReports]); return YES; case kStation_breakPattern: *value = OOJSValueFromBOOL([entity hasBreakPattern]); return YES; case kStation_market: { NSDictionary *market = [entity localMarketForScripting]; *value = OOJSValueFromNativeObject(context, market); return YES; } default: OOJSReportBadPropertySelector(context, this, propID, sStationProperties); return NO; } OOJS_NATIVE_EXIT } static JSBool StationSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) StationEntity *entity = nil; JSBool bValue; int32 iValue; jsdouble fValue; NSString *sValue = nil; if (!JSStationGetStationEntity(context, this, &entity)) return NO; if (entity == nil) return YES; switch (JSID_TO_INT(propID)) { case kStation_hasNPCTraffic: if (JS_ValueToBoolean(context, *value, &bValue)) { [entity setHasNPCTraffic:bValue]; return YES; } break; case kStation_alertCondition: if (JS_ValueToInt32(context, *value, &iValue)) { [entity setAlertLevel:iValue signallingScript:NO]; // Performs range checking return YES; } break; case kStation_allegiance: sValue = OOStringFromJSValue(context,*value); if (sValue != nil) { [entity setAllegiance:sValue]; return YES; } break; case kStation_requiresDockingClearance: if (JS_ValueToBoolean(context, *value, &bValue)) { [entity setRequiresDockingClearance:bValue]; return YES; } break; case kStation_roll: if (JS_ValueToNumber(context, *value, &fValue)) { /* if (fValue < -2.0) fValue = -2.0; if (fValue > 2.0) fValue = 2.0; // clamping to -2.0...2.0 gives us M_PI actual maximum rotation [entity setRoll:fValue]; */ // use setRawRoll to make the units here equal to those in kShip_roll if (fValue < -M_PI) fValue = -M_PI; else if (fValue > M_PI) fValue = M_PI; [entity setRawRoll:fValue]; return YES; } break; case kStation_allowsFastDocking: if (JS_ValueToBoolean(context, *value, &bValue)) { [entity setAllowsFastDocking:bValue]; return YES; } break; case kStation_allowsAutoDocking: if (JS_ValueToBoolean(context, *value, &bValue)) { [entity setAllowsAutoDocking:bValue]; return YES; } break; case kStation_suppressArrivalReports: if (JS_ValueToBoolean(context, *value, &bValue)) { [entity setSuppressArrivalReports:bValue]; return YES; } break; case kStation_breakPattern: if (JS_ValueToBoolean(context, *value, &bValue)) { [entity setHasBreakPattern:bValue]; return YES; } break; default: OOJSReportBadPropertySelector(context, this, propID, sStationProperties); return NO; } OOJSReportBadPropertyValue(context, this, propID, sStationProperties, *value); return NO; OOJS_NATIVE_EXIT } // *** Methods *** static JSBool StationAbortAllDockings(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) StationEntity *station = nil; JSStationGetStationEntity(context, OOJS_THIS, &station); [station abortAllDockings]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } static JSBool StationAbortDockingForShip(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) StationEntity *station = nil; JSStationGetStationEntity(context, OOJS_THIS, &station); if (argc == 0) { OOJSReportBadArguments(context, @"Station", @"abortDockingForShip", MIN(argc, 1U), OOJS_ARGV, nil, @"ship in docking queue"); return NO; } ShipEntity *ship = nil; JSStationGetShipEntity(context, JSVAL_TO_OBJECT(OOJS_ARGV[0]), &ship); if (ship != nil) { [station abortDockingForShip:ship]; } OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // dockPlayer() // Proposed and written by Frame 20090729 static JSBool StationDockPlayer(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); GameController *gameController = [UNIVERSE gameController]; if (EXPECT_NOT([gameController isGamePaused])) { /* Station.dockPlayer() was executed while the game was in pause. Do we want to return an error or just unpause and continue? I think unpausing is the sensible thing to do here - Nikos 20110208 */ [gameController setGamePaused:NO]; } if (EXPECT(![player isDocked])) { StationEntity *stationForDockingPlayer = nil; JSStationGetStationEntity(context, OOJS_THIS, &stationForDockingPlayer); [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_GRANTED]; [player safeAllMissiles]; [UNIVERSE setViewDirection:VIEW_FORWARD]; [player enterDock:stationForDockingPlayer]; } OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // launchShipWithRole(role : String [, abortAllDockings : boolean]) : shipEntity static JSBool StationLaunchShipWithRole(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *shipRole = nil; StationEntity *station = nil; ShipEntity *result = nil; JSBool abortAllDockings = NO; if (!JSStationGetStationEntity(context, OOJS_THIS, &station)) OOJS_RETURN_VOID; // stale reference, no-op if (argc > 0) shipRole = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(shipRole == nil)) { OOJSReportBadArguments(context, @"Station", @"launchShipWithRole", MIN(argc, 1U), OOJS_ARGV, nil, @"string (role)"); return NO; } if (argc > 1) JS_ValueToBoolean(context, OOJS_ARGV[1], &abortAllDockings); OOJS_BEGIN_FULL_NATIVE(context) result = [station launchIndependentShip:shipRole]; if (abortAllDockings) [station abortAllDockings]; OOJS_END_FULL_NATIVE OOJS_RETURN_OBJECT(result); OOJS_NATIVE_EXIT } static JSBool StationLaunchDefenseShip(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) StationEntity *station = nil; if (!JSStationGetStationEntity(context, OOJS_THIS, &station)) OOJS_RETURN_VOID; // stale reference, no-op ShipEntity *launched = nil; OOJS_BEGIN_FULL_NATIVE(context) launched = [station launchDefenseShip]; OOJS_END_FULL_NATIVE OOJS_RETURN_OBJECT(launched); OOJS_NATIVE_EXIT } static JSBool StationLaunchEscort(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) StationEntity *station = nil; if (!JSStationGetStationEntity(context, OOJS_THIS, &station)) OOJS_RETURN_VOID; // stale reference, no-op ShipEntity *launched = nil; OOJS_BEGIN_FULL_NATIVE(context) launched = [station launchEscort]; OOJS_END_FULL_NATIVE OOJS_RETURN_OBJECT(launched); OOJS_NATIVE_EXIT } static JSBool StationLaunchScavenger(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) StationEntity *station = nil; if (!JSStationGetStationEntity(context, OOJS_THIS, &station)) OOJS_RETURN_VOID; // stale reference, no-op ShipEntity *launched = nil; OOJS_BEGIN_FULL_NATIVE(context) launched = [station launchScavenger]; OOJS_END_FULL_NATIVE OOJS_RETURN_OBJECT(launched); OOJS_NATIVE_EXIT } static JSBool StationLaunchMiner(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) StationEntity *station = nil; if (!JSStationGetStationEntity(context, OOJS_THIS, &station)) OOJS_RETURN_VOID; // stale reference, no-op ShipEntity *launched = nil; OOJS_BEGIN_FULL_NATIVE(context) launched = [station launchMiner]; OOJS_END_FULL_NATIVE OOJS_RETURN_OBJECT(launched); OOJS_NATIVE_EXIT } static JSBool StationLaunchPirateShip(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) StationEntity *station = nil; if (!JSStationGetStationEntity(context, OOJS_THIS, &station)) OOJS_RETURN_VOID; // stale reference, no-op ShipEntity *launched = nil; OOJS_BEGIN_FULL_NATIVE(context) launched = [station launchPirateShip]; OOJS_END_FULL_NATIVE OOJS_RETURN_OBJECT(launched); OOJS_NATIVE_EXIT } static JSBool StationLaunchShuttle(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) StationEntity *station = nil; if (!JSStationGetStationEntity(context, OOJS_THIS, &station)) OOJS_RETURN_VOID; // stale reference, no-op ShipEntity *launched = nil; OOJS_BEGIN_FULL_NATIVE(context) launched = [station launchShuttle]; OOJS_END_FULL_NATIVE OOJS_RETURN_OBJECT(launched); OOJS_NATIVE_EXIT } static JSBool StationLaunchPatrol(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) StationEntity *station = nil; if (!JSStationGetStationEntity(context, OOJS_THIS, &station)) OOJS_RETURN_VOID; // stale reference, no-op ShipEntity *launched = nil; OOJS_BEGIN_FULL_NATIVE(context) launched = [station launchPatrol]; OOJS_END_FULL_NATIVE OOJS_RETURN_OBJECT(launched); OOJS_NATIVE_EXIT } static JSBool StationLaunchPolice(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) StationEntity *station = nil; if (!JSStationGetStationEntity(context, OOJS_THIS, &station)) OOJS_RETURN_VOID; // stale reference, no-op NSArray *launched = nil; OOJS_BEGIN_FULL_NATIVE(context) launched = [station launchPolice]; OOJS_END_FULL_NATIVE OOJS_RETURN_OBJECT(launched); OOJS_NATIVE_EXIT } static JSBool StationSetInterface(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) StationEntity *station = nil; if (!JSStationGetStationEntity(context, OOJS_THIS, &station)) OOJS_RETURN_VOID; // stale reference, no-op if (argc < 1) { OOJSReportBadArguments(context, @"Station", @"setInterface", MIN(argc, 1U), OOJS_ARGV, NULL, @"key [, definition]"); return NO; } NSString *key = OOStringFromJSValue(context, OOJS_ARGV[0]); if (argc < 2 || JSVAL_IS_NULL(OOJS_ARGV[1])) { [station setInterfaceDefinition:nil forKey:key]; OOJS_RETURN_VOID; } jsval value = JSVAL_NULL; jsval callback = JSVAL_NULL; JSObject *callbackThis = NULL; JSObject *params = NULL; NSString *title = nil; NSString *summary = nil; NSString *category = nil; if (!JS_ValueToObject(context, OOJS_ARGV[1], ¶ms)) { OOJSReportBadArguments(context, @"Station", @"setInterface", MIN(argc, 1U), OOJS_ARGV, NULL, @"key [, definition]"); return NO; } // get and validate title if (JS_GetProperty(context, params, "title", &value) == JS_FALSE || JSVAL_IS_VOID(value)) { OOJSReportBadArguments(context, @"Station", @"setInterface", MIN(argc, 1U), OOJS_ARGV, NULL, @"key [, definition]; if definition is set, it must have a 'title' property."); return NO; } title = OOStringFromJSValue(context, value); if (title == nil || [title length] == 0) { OOJSReportBadArguments(context, @"Station", @"setInterface", MIN(argc, 1U), OOJS_ARGV, NULL, @"key [, definition]; if definition is set, 'title' property must be a non-empty string."); return NO; } // get category with default if (JS_GetProperty(context, params, "category", &value) == JS_FALSE || JSVAL_IS_VOID(value)) { category = [NSString stringWithString:DESC(@"interfaces-category-uncategorised")]; } else { category = OOStringFromJSValue(context, value); if (category == nil || [category length] == 0) { category = [NSString stringWithString:DESC(@"interfaces-category-uncategorised")]; } } // get and validate summary if (JS_GetProperty(context, params, "summary", &value) == JS_FALSE || JSVAL_IS_VOID(value)) { OOJSReportBadArguments(context, @"Station", @"setInterface", MIN(argc, 1U), OOJS_ARGV, NULL, @"key [, definition]; if definition is set, it must have a 'summary' property."); return NO; } summary = OOStringFromJSValue(context, value); if (summary == nil || [summary length] == 0) { OOJSReportBadArguments(context, @"Station", @"setInterface", MIN(argc, 1U), OOJS_ARGV, NULL, @"key [, definition]; if definition is set, 'summary' property must be a non-empty string."); return NO; } // get and validate callback if (JS_GetProperty(context, params, "callback", &callback) == JS_FALSE || JSVAL_IS_VOID(callback)) { OOJSReportBadArguments(context, @"Station", @"setInterface", MIN(argc, 1U), OOJS_ARGV, NULL, @"key [, definition]; if definition is set, it must have a 'callback' property."); return NO; } if (!OOJSValueIsFunction(context,callback)) { OOJSReportBadArguments(context, @"Station", @"setInterface", MIN(argc, 1U), OOJS_ARGV, NULL, @"key [, definition]; 'callback' property must be a function."); return NO; } OOJSInterfaceDefinition* definition = [[OOJSInterfaceDefinition alloc] init]; [definition setTitle:title]; [definition setCategory:category]; [definition setSummary:summary]; [definition setCallback:callback]; // get callback 'this' if (JS_GetProperty(context, params, "cbThis", &value) == JS_TRUE && !JSVAL_IS_VOID(value)) { JS_ValueToObject(context, value, &callbackThis); [definition setCallbackThis:callbackThis]; // can do .bind(this) for callback instead } [station setInterfaceDefinition:definition forKey:key]; [definition release]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } static JSBool StationSetMarketPrice(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) StationEntity *station = nil; if (!JSStationGetStationEntity(context, OOJS_THIS, &station)) OOJS_RETURN_VOID; // stale reference, no-op if (argc < 2) { OOJSReportBadArguments(context, @"Station", @"setMarketPrice", MIN(argc, 2U), OOJS_ARGV, NULL, @"commodity, credits"); return NO; } OOCommodityType commodity = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(![[UNIVERSE commodities] goodDefined:commodity])) { OOJSReportBadArguments(context, @"Station", @"setMarketPrice", MIN(argc, 2U), OOJS_ARGV, NULL, @"Unrecognised commodity type"); return NO; } int32 price; BOOL gotPrice = JS_ValueToInt32(context, OOJS_ARGV[1], &price); if (EXPECT_NOT(!gotPrice || price < 0)) { OOJSReportBadArguments(context, @"Station", @"setMarketPrice", MIN(argc, 2U), OOJS_ARGV, NULL, @"Price must be at least 0 decicredits"); return NO; } [station setPrice:(NSUInteger)price forCommodity:commodity]; if (station == [PLAYER dockedStation] && [PLAYER guiScreen] == GUI_SCREEN_MARKET) { [PLAYER setGuiToMarketScreen]; // refresh screen } OOJS_RETURN_BOOL(YES); OOJS_NATIVE_EXIT } static JSBool StationSetMarketQuantity(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) StationEntity *station = nil; if (!JSStationGetStationEntity(context, OOJS_THIS, &station)) OOJS_RETURN_VOID; // stale reference, no-op if (argc < 2) { OOJSReportBadArguments(context, @"Station", @"setMarketQuantity", MIN(argc, 2U), OOJS_ARGV, NULL, @"commodity, units"); return NO; } OOCommodityType commodity = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(![[UNIVERSE commodities] goodDefined:commodity])) { OOJSReportBadArguments(context, @"Station", @"setMarketQuantity", MIN(argc, 2U), OOJS_ARGV, NULL, @"Unrecognised commodity type"); return NO; } int32 quantity; BOOL gotQuantity = JS_ValueToInt32(context, OOJS_ARGV[1], &quantity); if (EXPECT_NOT(!gotQuantity || quantity < 0 || (OOCargoQuantity)quantity > [[station localMarket] capacityForGood:commodity])) { OOJSReportBadArguments(context, @"Station", @"setMarketQuantity", MIN(argc, 2U), OOJS_ARGV, NULL, @"Quantity must be between 0 and the station market capacity"); return NO; } [station setQuantity:(NSUInteger)quantity forCommodity:commodity]; if (station == [PLAYER dockedStation] && [PLAYER guiScreen] == GUI_SCREEN_MARKET) { [PLAYER setGuiToMarketScreen]; // refresh screen } OOJS_RETURN_BOOL(YES); OOJS_NATIVE_EXIT } oolite-1.82/src/Core/Scripting/OOJSSun.h000066400000000000000000000016131256642440500200020ustar00rootroot00000000000000/* OOJSSun.h JavaScript proxy for suns. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #include void InitOOJSSun(JSContext *context, JSObject *global); oolite-1.82/src/Core/Scripting/OOJSSun.m000066400000000000000000000110321256642440500200030ustar00rootroot00000000000000/* OOJSSun.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSSun.h" #import "OOJSEntity.h" #import "OOJavaScriptEngine.h" #import "OOSunEntity.h" static JSObject *sSunPrototype; static JSBool SunGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool SunGoNova(JSContext *context, uintN argc, jsval *vp); static JSBool SunCancelNova(JSContext *context, uintN argc, jsval *vp); static JSClass sSunClass = { "Sun", JSCLASS_HAS_PRIVATE, JS_PropertyStub, // addProperty JS_PropertyStub, // delProperty SunGetProperty, // getProperty JS_StrictPropertyStub, // setProperty JS_EnumerateStub, // enumerate JS_ResolveStub, // resolve JS_ConvertStub, // convert OOJSObjectWrapperFinalize,// finalize JSCLASS_NO_OPTIONAL_MEMBERS }; enum { // Property IDs kSun_radius, // Radius of sun in metres, number, read-only kSun_hasGoneNova, // Has sun gone nova, boolean, read-only kSun_isGoingNova, // Will sun go nova, boolean, read-only kSun_name // Name of sun, string, read-only (writable via systeminfo) }; static JSPropertySpec sSunProperties[] = { // JS name ID flags { "hasGoneNova", kSun_hasGoneNova, OOJS_PROP_READONLY_CB }, { "isGoingNova", kSun_isGoingNova, OOJS_PROP_READONLY_CB }, { "name", kSun_name, OOJS_PROP_READONLY_CB }, { "radius", kSun_radius, OOJS_PROP_READONLY_CB }, { 0 } }; static JSFunctionSpec sSunMethods[] = { // JS name Function min args { "cancelNova", SunCancelNova, 0 }, { "goNova", SunGoNova, 1 }, { 0 } }; DEFINE_JS_OBJECT_GETTER(JSSunGetSunEntity, &sSunClass, sSunPrototype, OOSunEntity) void InitOOJSSun(JSContext *context, JSObject *global) { sSunPrototype = JS_InitClass(context, global, JSEntityPrototype(), &sSunClass, OOJSUnconstructableConstruct, 0, sSunProperties, sSunMethods, NULL, NULL); OOJSRegisterObjectConverter(&sSunClass, OOJSBasicPrivateObjectConverter); OOJSRegisterSubclass(&sSunClass, JSEntityClass()); } @implementation OOSunEntity (OOJavaScriptExtensions) - (BOOL) isVisibleToScripts { return YES; } - (void)getJSClass:(JSClass **)outClass andPrototype:(JSObject **)outPrototype { *outClass = &sSunClass; *outPrototype = sSunPrototype; } - (NSString *) oo_jsClassName { return @"Sun"; } @end static JSBool SunGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) OOSunEntity *sun = nil; if (EXPECT_NOT(!JSSunGetSunEntity(context, this, &sun))) return NO; switch (JSID_TO_INT(propID)) { case kSun_radius: return JS_NewNumberValue(context, [sun radius], value); case kSun_name: *value = OOJSValueFromNativeObject(context, [sun name]); return YES; case kSun_hasGoneNova: *value = OOJSValueFromBOOL([sun goneNova]); return YES; case kSun_isGoingNova: *value = OOJSValueFromBOOL([sun willGoNova] && ![sun goneNova]); return YES; default: OOJSReportBadPropertySelector(context, this, propID, sSunProperties); return NO; } OOJS_NATIVE_EXIT } // *** Methods *** // goNova([delay : Number]) static JSBool SunGoNova(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOSunEntity *sun = nil; jsdouble delay = 0; if (EXPECT_NOT(!JSSunGetSunEntity(context, OOJS_THIS, &sun))) return NO; if (argc > 0 && EXPECT_NOT(!JS_ValueToNumber(context, OOJS_ARGV[0], &delay))) return NO; [sun setGoingNova:YES inTime:delay]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // cancelNova() static JSBool SunCancelNova(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOSunEntity *sun = nil; if (EXPECT_NOT(!JSSunGetSunEntity(context, OOJS_THIS, &sun))) return NO; if ([sun willGoNova] && ![sun goneNova]) { [sun setGoingNova:NO inTime:0]; } OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } oolite-1.82/src/Core/Scripting/OOJSSystem.h000066400000000000000000000016371256642440500205270ustar00rootroot00000000000000/* OOJSSystem.h JavaScript proxy for the current system. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #include void InitOOJSSystem(JSContext *context, JSObject *global); oolite-1.82/src/Core/Scripting/OOJSSystem.m000066400000000000000000001446731256642440500205440ustar00rootroot00000000000000/* OOJSSystem.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSSystem.h" #import "OOJavaScriptEngine.h" #import "OOJSVector.h" #import "OOJSQuaternion.h" #import "OOJSEntity.h" #import "OOJSPlayer.h" #import "Universe.h" #import "OOPlanetEntity.h" #import "PlayerEntityScriptMethods.h" #import "OOJSSystemInfo.h" #import "OOCollectionExtractors.h" #import "OOConstToString.h" #import "OOConstToJSString.h" #import "OOEntityFilterPredicate.h" #import "OOFilteringEnumerator.h" #import "OOJSPopulatorDefinition.h" #import "OODebugStandards.h" static JSObject *sSystemPrototype; // Support functions for entity search methods. static BOOL GetRelativeToAndRange(JSContext *context, NSString *methodName, uintN *ioArgc, jsval **ioArgv, Entity **outRelativeTo, double *outRange); static NSArray *FindJSVisibleEntities(EntityFilterPredicate predicate, void *parameter, Entity *relativeTo, double range); static NSArray *FindShips(EntityFilterPredicate predicate, void *parameter, Entity *relativeTo, double range); static NSComparisonResult CompareEntitiesByDistance(id a, id b, void *relativeTo); static JSBool SystemAddShipsOrGroup(JSContext *context, uintN argc, jsval *vp, BOOL isGroup); static JSBool SystemAddShipsOrGroupToRoute(JSContext *context, uintN argc, jsval *vp, BOOL isGroup); static JSBool SystemGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool SystemSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); static JSBool SystemToString(JSContext *context, uintN argc, jsval *vp); static JSBool SystemAddPlanet(JSContext *context, uintN argc, jsval *vp); static JSBool SystemAddMoon(JSContext *context, uintN argc, jsval *vp); static JSBool SystemSendAllShipsAway(JSContext *context, uintN argc, jsval *vp); static JSBool SystemCountShipsWithPrimaryRole(JSContext *context, uintN argc, jsval *vp); static JSBool SystemCountShipsWithRole(JSContext *context, uintN argc, jsval *vp); static JSBool SystemCountEntitiesWithScanClass(JSContext *context, uintN argc, jsval *vp); static JSBool SystemShipsWithPrimaryRole(JSContext *context, uintN argc, jsval *vp); static JSBool SystemShipsWithRole(JSContext *context, uintN argc, jsval *vp); static JSBool SystemEntitiesWithScanClass(JSContext *context, uintN argc, jsval *vp); static JSBool SystemFilteredEntities(JSContext *context, uintN argc, jsval *vp); static JSBool SystemLocationFromCode(JSContext *context, uintN argc, jsval *vp); static JSBool SystemAddShips(JSContext *context, uintN argc, jsval *vp); static JSBool SystemAddGroup(JSContext *context, uintN argc, jsval *vp); static JSBool SystemAddShipsToRoute(JSContext *context, uintN argc, jsval *vp); static JSBool SystemAddGroupToRoute(JSContext *context, uintN argc, jsval *vp); static JSBool SystemAddVisualEffect(JSContext *context, uintN argc, jsval *vp); static JSBool SystemSetPopulator(JSContext *context, uintN argc, jsval *vp); static JSBool SystemSetWaypoint(JSContext *context, uintN argc, jsval *vp); static JSBool SystemLegacyAddShips(JSContext *context, uintN argc, jsval *vp); static JSBool SystemLegacyAddSystemShips(JSContext *context, uintN argc, jsval *vp); static JSBool SystemLegacyAddShipsAt(JSContext *context, uintN argc, jsval *vp); static JSBool SystemLegacyAddShipsAtPrecisely(JSContext *context, uintN argc, jsval *vp); static JSBool SystemLegacyAddShipsWithinRadius(JSContext *context, uintN argc, jsval *vp); static JSBool SystemLegacySpawnShip(JSContext *context, uintN argc, jsval *vp); static JSBool SystemStaticSystemNameForID(JSContext *context, uintN argc, jsval *vp); static JSBool SystemStaticSystemIDForName(JSContext *context, uintN argc, jsval *vp); static JSBool SystemStaticInfoForSystem(JSContext *context, uintN argc, jsval *vp); static JSClass sSystemClass = { "System", 0, JS_PropertyStub, JS_PropertyStub, SystemGetProperty, SystemSetProperty, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub }; enum { // Property IDs kSystem_allDemoShips, // demo ships, array of Ship, read-only kSystem_allShips, // ships in system, array of Ship, read-only kSystem_allVisualEffects, // VEs in system, array of VEs, read-only kSystem_ambientLevel, // ambient light level, float, read/write kSystem_breakPattern, // witchspace break pattern shown kSystem_description, // description, string, read/write kSystem_economy, // economy ID, integer, read/write kSystem_economyDescription, // economy ID description, string, read-only kSystem_government, // government ID, integer, read/write kSystem_governmentDescription, // government ID description, string, read-only kSystem_ID, // planet number, integer, read-only kSystem_info, // system info dictionary, SystemInfo, read/write kSystem_inhabitantsDescription, // description of inhabitant species, string, read/write kSystem_isInterstellarSpace, // is interstellar space, boolean, read-only kSystem_mainPlanet, // system's main planet, Planet, read-only kSystem_mainStation, // system's main station, Station, read-only kSystem_name, // name, string, read/write kSystem_planets, // planets in system, array of Planet, read-only kSystem_population, // population, integer, read/write kSystem_populatorSettings, // populator settings, dictionary, read-only kSystem_productivity, // productivity, integer, read/write kSystem_pseudoRandom100, // constant-per-system pseudorandom number in [0..100), integer, read-only kSystem_pseudoRandom256, // constant-per-system pseudorandom number in [0..256), integer, read-only kSystem_pseudoRandomNumber, // constant-per-system pseudorandom number in [0..1), double, read-only kSystem_sun, // system's sun, Planet, read-only kSystem_stations, // list of dockable entities, read-only kSystem_techLevel, // tech level ID, integer, read/write kSystem_waypoints, // dictionary of current player waypoints, read-only kSystem_wormholes, // list of active entry wormholes, read-only }; static JSPropertySpec sSystemProperties[] = { // JS name ID flags { "allDemoShips", kSystem_allDemoShips, OOJS_PROP_READONLY_CB }, { "allShips", kSystem_allShips, OOJS_PROP_READONLY_CB }, { "allVisualEffects", kSystem_allVisualEffects, OOJS_PROP_READONLY_CB }, { "ambientLevel", kSystem_ambientLevel, OOJS_PROP_READWRITE_CB }, { "breakPattern", kSystem_breakPattern, OOJS_PROP_READWRITE_CB }, { "description", kSystem_description, OOJS_PROP_READWRITE_CB }, { "economy", kSystem_economy, OOJS_PROP_READWRITE_CB }, { "economyDescription", kSystem_economyDescription, OOJS_PROP_READONLY_CB }, { "government", kSystem_government, OOJS_PROP_READWRITE_CB }, { "governmentDescription", kSystem_governmentDescription, OOJS_PROP_READONLY_CB }, { "ID", kSystem_ID, OOJS_PROP_READONLY_CB }, { "info", kSystem_info, OOJS_PROP_READONLY_CB }, { "inhabitantsDescription", kSystem_inhabitantsDescription, OOJS_PROP_READWRITE_CB }, { "isInterstellarSpace", kSystem_isInterstellarSpace, OOJS_PROP_READONLY_CB}, { "mainPlanet", kSystem_mainPlanet, OOJS_PROP_READONLY_CB }, { "mainStation", kSystem_mainStation, OOJS_PROP_READONLY_CB }, { "name", kSystem_name, OOJS_PROP_READWRITE_CB }, { "planets", kSystem_planets, OOJS_PROP_READONLY_CB }, { "population", kSystem_population, OOJS_PROP_READWRITE_CB }, { "populatorSettings", kSystem_populatorSettings, OOJS_PROP_READONLY_CB }, { "productivity", kSystem_productivity, OOJS_PROP_READWRITE_CB }, { "pseudoRandom100", kSystem_pseudoRandom100, OOJS_PROP_READONLY_CB }, { "pseudoRandom256", kSystem_pseudoRandom256, OOJS_PROP_READONLY_CB }, { "pseudoRandomNumber", kSystem_pseudoRandomNumber, OOJS_PROP_READONLY_CB }, { "stations", kSystem_stations, OOJS_PROP_READONLY_CB }, { "sun", kSystem_sun, OOJS_PROP_READONLY_CB }, { "techLevel", kSystem_techLevel, OOJS_PROP_READWRITE_CB }, { "waypoints", kSystem_waypoints, OOJS_PROP_READONLY_CB }, { "wormholes", kSystem_wormholes, OOJS_PROP_READONLY_CB }, { 0 } }; static JSFunctionSpec sSystemMethods[] = { // JS name Function min args { "toString", SystemToString, 0 }, { "addGroup", SystemAddGroup, 3 }, { "addGroupToRoute", SystemAddGroupToRoute, 2 }, { "addMoon", SystemAddMoon, 1 }, { "addPlanet", SystemAddPlanet, 1 }, { "addShips", SystemAddShips, 3 }, { "addShipsToRoute", SystemAddShipsToRoute, 2 }, { "addVisualEffect", SystemAddVisualEffect, 2 }, { "countEntitiesWithScanClass", SystemCountEntitiesWithScanClass, 1 }, { "countShipsWithPrimaryRole", SystemCountShipsWithPrimaryRole, 1 }, { "countShipsWithRole", SystemCountShipsWithRole, 1 }, { "entitiesWithScanClass", SystemEntitiesWithScanClass, 1 }, { "filteredEntities", SystemFilteredEntities, 2 }, { "locationFromCode", SystemLocationFromCode, 1 }, // scrambledPseudoRandomNumber is implemented in oolite-global-prefix.js { "sendAllShipsAway", SystemSendAllShipsAway, 1 }, { "setPopulator", SystemSetPopulator, 2 }, { "setWaypoint", SystemSetWaypoint, 4 }, { "shipsWithPrimaryRole", SystemShipsWithPrimaryRole, 1 }, { "shipsWithRole", SystemShipsWithRole, 1 }, { "legacy_addShips", SystemLegacyAddShips, 2 }, { "legacy_addSystemShips", SystemLegacyAddSystemShips, 3 }, { "legacy_addShipsAt", SystemLegacyAddShipsAt, 6 }, { "legacy_addShipsAtPrecisely", SystemLegacyAddShipsAtPrecisely, 6 }, { "legacy_addShipsWithinRadius", SystemLegacyAddShipsWithinRadius, 7 }, { "legacy_spawnShip", SystemLegacySpawnShip, 1 }, { 0 } }; static JSFunctionSpec sSystemStaticMethods[] = { { "infoForSystem", SystemStaticInfoForSystem, 2 }, { "systemIDForName", SystemStaticSystemIDForName, 1 }, { "systemNameForID", SystemStaticSystemNameForID, 1 }, { 0 } }; void InitOOJSSystem(JSContext *context, JSObject *global) { sSystemPrototype = JS_InitClass(context, global, NULL, &sSystemClass, OOJSUnconstructableConstruct, 0, sSystemProperties, sSystemMethods, NULL, sSystemStaticMethods); // Create system object as a property of the global object. JS_DefineObject(context, global, "system", &sSystemClass, sSystemPrototype, OOJS_PROP_READONLY); } static JSBool SystemGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) id result = nil; PlayerEntity *player = nil; NSDictionary *systemData = nil; BOOL handled = NO; player = OOPlayerForScripting(); // Handle cases which don't require systemData. switch (JSID_TO_INT(propID)) { case kSystem_ID: *value = INT_TO_JSVAL([player currentSystemID]); return YES; case kSystem_isInterstellarSpace: *value = OOJSValueFromBOOL([UNIVERSE inInterstellarSpace]); return YES; case kSystem_mainStation: result = [UNIVERSE station]; handled = YES; break; case kSystem_mainPlanet: result = [UNIVERSE planet]; handled = YES; break; case kSystem_sun: result = [UNIVERSE sun]; handled = YES; break; case kSystem_planets: result = [[[UNIVERSE planets] objectEnumeratorFilteredWithSelector:@selector(isVisibleToScripts)] allObjects]; handled = YES; break; case kSystem_stations: result = [UNIVERSE stations]; handled = YES; break; case kSystem_waypoints: result = [UNIVERSE currentWaypoints]; handled = YES; break; case kSystem_wormholes: result = [UNIVERSE wormholes]; handled = YES; break; case kSystem_allShips: OOJS_BEGIN_FULL_NATIVE(context) result = [UNIVERSE findShipsMatchingPredicate:JSEntityIsJavaScriptSearchablePredicate parameter:NULL inRange:-1 ofEntity:nil]; OOJS_END_FULL_NATIVE handled = YES; break; case kSystem_allDemoShips: OOJS_BEGIN_FULL_NATIVE(context) result = [UNIVERSE findShipsMatchingPredicate:JSEntityIsDemoShipPredicate parameter:NULL inRange:-1 ofEntity:nil]; OOJS_END_FULL_NATIVE handled = YES; break; case kSystem_allVisualEffects: OOJS_BEGIN_FULL_NATIVE(context) result = [UNIVERSE findVisualEffectsMatchingPredicate:JSEntityIsJavaScriptSearchablePredicate parameter:NULL inRange:-1 ofEntity:nil]; OOJS_END_FULL_NATIVE handled = YES; break; case kSystem_ambientLevel: return JS_NewNumberValue(context, [UNIVERSE ambientLightLevel], value); case kSystem_info: *value = GetJSSystemInfoForSystem(context, [player currentGalaxyID], [player currentSystemID]); return YES; case kSystem_pseudoRandomNumber: return JS_NewNumberValue(context, [player systemPseudoRandomFloat], value); case kSystem_pseudoRandom100: *value = INT_TO_JSVAL([player systemPseudoRandom100]); return YES; case kSystem_pseudoRandom256: *value = INT_TO_JSVAL([player systemPseudoRandom256]); return YES; case kSystem_breakPattern: *value = OOJSValueFromBOOL([UNIVERSE witchspaceBreakPattern]); return YES; case kSystem_populatorSettings: *value = OOJSValueFromNativeObject(context, [UNIVERSE getPopulatorSettings]); return YES; } if (!handled) { // Handle cases which do require systemData. if (EXPECT (![UNIVERSE inInterstellarSpace])) { systemData = [UNIVERSE currentSystemData]; switch (JSID_TO_INT(propID)) { case kSystem_name: result = [systemData objectForKey:KEY_NAME]; break; case kSystem_description: result = [systemData objectForKey:KEY_DESCRIPTION]; break; case kSystem_inhabitantsDescription: result = [systemData objectForKey:KEY_INHABITANTS]; break; case kSystem_government: *value = INT_TO_JSVAL([systemData oo_intForKey:KEY_GOVERNMENT]); return YES; case kSystem_governmentDescription: result = OODisplayStringFromGovernmentID([systemData oo_intForKey:KEY_GOVERNMENT]); if (result == nil) result = DESC(@"not-applicable"); break; case kSystem_economy: *value = INT_TO_JSVAL([systemData oo_intForKey:KEY_ECONOMY]); return YES; case kSystem_economyDescription: result = OODisplayStringFromEconomyID([systemData oo_intForKey:KEY_ECONOMY]); if (result == nil) result = DESC(@"not-applicable"); break; case kSystem_techLevel: *value = INT_TO_JSVAL([systemData oo_intForKey:KEY_TECHLEVEL]); return YES; case kSystem_population: *value = INT_TO_JSVAL([systemData oo_intForKey:KEY_POPULATION]); return YES; case kSystem_productivity: *value = INT_TO_JSVAL([systemData oo_intForKey:KEY_PRODUCTIVITY]); return YES; default: OOJSReportBadPropertySelector(context, this, propID, sSystemProperties); return NO; } } else { // if in interstellar space, systemData values are null & void! switch (JSID_TO_INT(propID)) { case kSystem_name: result = DESC(@"interstellar-space"); break; case kSystem_description: result = @""; break; case kSystem_inhabitantsDescription: result = DESC(@"not-applicable"); break; case kSystem_government: *value = INT_TO_JSVAL(-1); return YES; case kSystem_governmentDescription: result = DESC(@"not-applicable"); break; case kSystem_economy: *value = INT_TO_JSVAL(-1); return YES; case kSystem_economyDescription: result = DESC(@"not-applicable"); break; case kSystem_techLevel: *value = INT_TO_JSVAL(-1); return YES; case kSystem_population: *value = INT_TO_JSVAL(0); return YES; case kSystem_productivity: *value = INT_TO_JSVAL(0); return YES; default: OOJSReportBadPropertySelector(context, this, propID, sSystemProperties); return NO; } } } *value = OOJSValueFromNativeObject(context, result); return YES; OOJS_NATIVE_EXIT } static JSBool SystemSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) PlayerEntity *player = nil; OOGalaxyID galaxy; OOSystemID system; NSString *stringValue = nil; NSString *manifest = nil; jsdouble fValue; int32 iValue; JSBool bValue; player = OOPlayerForScripting(); galaxy = [player currentGalaxyID]; system = [player currentSystemID]; switch (JSID_TO_INT(propID)) { case kSystem_ambientLevel: if (JS_ValueToNumber(context, *value, &fValue)) { [UNIVERSE setAmbientLightLevel:fValue]; [UNIVERSE setLighting]; return YES; } break; case kSystem_breakPattern: if (JS_ValueToBoolean(context, *value, &bValue)) { [UNIVERSE setWitchspaceBreakPattern:bValue]; return YES; } break; default: {}// do nothing yet } if (system == -1) return YES; // Can't change anything else in interstellar space. manifest = [[OOJSScript currentlyRunningScript] propertyNamed:kLocalManifestProperty]; switch (JSID_TO_INT(propID)) { case kSystem_name: stringValue = OOStringFromJSValue(context, *value); if (stringValue != nil) { [UNIVERSE setSystemDataForGalaxy:galaxy planet:system key:KEY_NAME value:stringValue fromManifest:manifest forLayer:OO_LAYER_OXP_DYNAMIC]; return YES; } break; case kSystem_description: stringValue = OOStringFromJSValue(context, *value); if (stringValue != nil) { [UNIVERSE setSystemDataForGalaxy:galaxy planet:system key:KEY_DESCRIPTION value:stringValue fromManifest:manifest forLayer:OO_LAYER_OXP_DYNAMIC]; return YES; } break; case kSystem_inhabitantsDescription: stringValue = OOStringFromJSValue(context, *value); if (stringValue != nil) { [UNIVERSE setSystemDataForGalaxy:galaxy planet:system key:KEY_INHABITANTS value:stringValue fromManifest:manifest forLayer:OO_LAYER_OXP_DYNAMIC]; return YES; } break; case kSystem_government: if (JS_ValueToInt32(context, *value, &iValue)) { if (iValue < 0) iValue = 0; if (7 < iValue) iValue = 7; [UNIVERSE setSystemDataForGalaxy:galaxy planet:system key:KEY_GOVERNMENT value:[NSNumber numberWithInt:iValue] fromManifest:manifest forLayer:OO_LAYER_OXP_DYNAMIC]; return YES; } break; case kSystem_economy: if (JS_ValueToInt32(context, *value, &iValue)) { if (iValue < 0) iValue = 0; if (7 < iValue) iValue = 7; [UNIVERSE setSystemDataForGalaxy:galaxy planet:system key:KEY_ECONOMY value:[NSNumber numberWithInt:iValue] fromManifest:manifest forLayer:OO_LAYER_OXP_DYNAMIC]; return YES; } break; case kSystem_techLevel: if (JS_ValueToInt32(context, *value, &iValue)) { if (iValue < 0) iValue = 0; if (15 < iValue) iValue = 15; [UNIVERSE setSystemDataForGalaxy:galaxy planet:system key:KEY_TECHLEVEL value:[NSNumber numberWithInt:iValue] fromManifest:manifest forLayer:OO_LAYER_OXP_DYNAMIC]; return YES; } break; case kSystem_population: if (JS_ValueToInt32(context, *value, &iValue)) { [UNIVERSE setSystemDataForGalaxy:galaxy planet:system key:KEY_POPULATION value:[NSNumber numberWithInt:iValue] fromManifest:manifest forLayer:OO_LAYER_OXP_DYNAMIC]; return YES; } break; case kSystem_productivity: if (JS_ValueToInt32(context, *value, &iValue)) { [UNIVERSE setSystemDataForGalaxy:galaxy planet:system key:KEY_PRODUCTIVITY value:[NSNumber numberWithInt:iValue] fromManifest:manifest forLayer:OO_LAYER_OXP_DYNAMIC]; return YES; } break; default: OOJSReportBadPropertySelector(context, this, propID, sSystemProperties); return NO; } OOJSReportBadPropertyValue(context, this, propID, sSystemProperties, *value); return NO; OOJS_NATIVE_EXIT } // *** Methods *** // toString() : String static JSBool SystemToString(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); NSString *systemDesc = nil; systemDesc = [NSString stringWithFormat:@"[System %u:%u \"%@\"]", [player currentGalaxyID], [player currentSystemID], [[UNIVERSE currentSystemData] objectForKey:KEY_NAME]]; OOJS_RETURN_OBJECT(systemDesc); OOJS_NATIVE_EXIT } // addPlanet(key : String) : Planet static JSBool SystemAddPlanet(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); NSString *key = nil; OOPlanetEntity *planet = nil; if (argc > 0) key = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(key == nil)) { OOJSReportBadArguments(context, @"System", @"addPlanet", MIN(argc, 1U), OOJS_ARGV, nil, @"string (planet key)"); return NO; } OOJS_BEGIN_FULL_NATIVE(context) planet = [player addPlanet:key]; OOJS_END_FULL_NATIVE OOJS_RETURN_OBJECT(planet); OOJS_NATIVE_EXIT } // addMoon(key : String) : Planet static JSBool SystemAddMoon(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); NSString *key = nil; OOPlanetEntity *planet = nil; if (argc > 0) key = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(key == nil)) { OOJSReportBadArguments(context, @"System", @"addMoon", MIN(argc, 1U), OOJS_ARGV, nil, @"string (planet key)"); return NO; } OOJS_BEGIN_FULL_NATIVE(context) planet = [player addMoon:key]; OOJS_END_FULL_NATIVE OOJS_RETURN_OBJECT(planet); OOJS_NATIVE_EXIT } // sendAllShipsAway() static JSBool SystemSendAllShipsAway(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); [player sendAllShipsAway]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // countShipsWithPrimaryRole(role : String [, relativeTo : Entity [, range : Number]]) : Number static JSBool SystemCountShipsWithPrimaryRole(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *role = nil; Entity *relativeTo = nil; double range = -1; unsigned result; if (argc > 0) role = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(role == nil)) { OOJSReportBadArguments(context, @"System", @"countShipsWithPrimaryRole", MIN(argc, 1U), OOJS_ARGV, nil, @"string (role)"); return NO; } // Get optional arguments argc -= 1; jsval *argv = OOJS_ARGV + 1; if (EXPECT_NOT(!GetRelativeToAndRange(context, @"countShipsWithPrimaryRole", &argc, &argv, &relativeTo, &range))) return NO; OOJS_BEGIN_FULL_NATIVE(context) result = [UNIVERSE countShipsWithPrimaryRole:role inRange:range ofEntity:relativeTo]; OOJS_END_FULL_NATIVE OOJS_RETURN_INT(result); OOJS_NATIVE_EXIT } // countShipsWithRole(role : String [, relativeTo : Entity [, range : Number]]) : Number static JSBool SystemCountShipsWithRole(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *role = nil; Entity *relativeTo = nil; double range = -1; unsigned result; if (argc > 0) role = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(role == nil)) { OOJSReportBadArguments(context, @"System", @"countShipsWithRole", MIN(argc, 1U), OOJS_ARGV, nil, @"string (role)"); return NO; } // Get optional arguments argc -= 1; jsval *argv = OOJS_ARGV + 1; if (EXPECT_NOT(!GetRelativeToAndRange(context, @"countShipsWithRole", &argc, &argv, &relativeTo, &range))) return NO; OOJS_BEGIN_FULL_NATIVE(context) result = [UNIVERSE countShipsWithRole:role inRange:range ofEntity:relativeTo]; OOJS_END_FULL_NATIVE OOJS_RETURN_INT(result); OOJS_NATIVE_EXIT } // shipsWithPrimaryRole(role : String [, relativeTo : Entity [, range : Number]]) : Array (Entity) static JSBool SystemShipsWithPrimaryRole(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *role = nil; Entity *relativeTo = nil; double range = -1; NSArray *result = nil; if (argc > 0) role = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(role == nil)) { OOJSReportBadArguments(context, @"System", @"countShipsWithRole", MIN(argc, 1U), OOJS_ARGV, nil, @"string (role)"); return NO; } // Get optional arguments argc -= 1; jsval *argv = OOJS_ARGV + 1; if (EXPECT_NOT(!GetRelativeToAndRange(context, @"shipsWithPrimaryRole", &argc, &argv, &relativeTo, &range))) return NO; // Search for entities OOJS_BEGIN_FULL_NATIVE(context) result = FindShips(HasPrimaryRolePredicate, role, relativeTo, range); OOJS_END_FULL_NATIVE OOJS_RETURN_OBJECT(result); OOJS_NATIVE_EXIT } // shipsWithRole(role : String [, relativeTo : Entity [, range : Number]]) : Array (Entity) static JSBool SystemShipsWithRole(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *role = nil; Entity *relativeTo = nil; double range = -1; NSArray *result = nil; if (argc > 0) role = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(role == nil)) { OOJSReportBadArguments(context, @"System", @"shipsWithRole", MIN(argc, 1U), OOJS_ARGV, nil, @"string (role)"); return NO; } // Get optional arguments argc -= 1; jsval *subargv = OOJS_ARGV + 1; if (EXPECT_NOT(!GetRelativeToAndRange(context, @"shipsWithRole", &argc, &subargv, &relativeTo, &range))) return NO; // Search for entities OOJS_BEGIN_FULL_NATIVE(context) result = FindShips(HasRolePredicate, role, relativeTo, range); OOJS_END_FULL_NATIVE OOJS_RETURN_OBJECT(result); OOJS_NATIVE_EXIT } // countEntitiesWithScanClass(scanClass : String [, relativeTo : Entity [, range : Number]]) : Number static JSBool SystemCountEntitiesWithScanClass(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOScanClass scanClass = CLASS_NOT_SET; Entity *relativeTo = nil; double range = -1; unsigned result; if (argc > 0) scanClass = OOScanClassFromJSValue(context, OOJS_ARGV[0]); if (scanClass == CLASS_NOT_SET) { OOJSReportBadArguments(context, @"System", @"countEntitiesWithScanClass", MIN(argc, 1U), OOJS_ARGV, nil, @"string (scan class)"); return NO; } // Get optional arguments argc -= 1; jsval *argv = OOJS_ARGV + 1; if (EXPECT_NOT(!GetRelativeToAndRange(context, @"countEntitiesWithScanClass", &argc, &argv, &relativeTo, &range))) return NO; OOJS_BEGIN_FULL_NATIVE(context) result = [UNIVERSE countShipsWithScanClass:scanClass inRange:range ofEntity:relativeTo]; OOJS_END_FULL_NATIVE OOJS_RETURN_INT(result); OOJS_NATIVE_EXIT } // entitiesWithScanClass(scanClass : String [, relativeTo : Entity [, range : Number]]) : Array (Entity) static JSBool SystemEntitiesWithScanClass(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOScanClass scanClass = CLASS_NOT_SET; Entity *relativeTo = nil; double range = -1; NSArray *result = nil; if (argc > 0) scanClass = OOScanClassFromJSValue(context, OOJS_ARGV[0]); if (scanClass == CLASS_NOT_SET) { OOJSReportBadArguments(context, @"System", @"countEntitiesWithScanClass", MIN(argc, 1U), OOJS_ARGV, nil, @"string (scan class)"); return NO; } // Get optional arguments argc -= 1; jsval *argv = OOJS_ARGV + 1; if (EXPECT_NOT(!GetRelativeToAndRange(context, @"entitiesWithScanClass", &argc, &argv, &relativeTo, &range))) return NO; // Search for entities OOJS_BEGIN_FULL_NATIVE(context) result = FindJSVisibleEntities(HasScanClassPredicate, [NSNumber numberWithInt:scanClass], relativeTo, range); OOJS_END_FULL_NATIVE OOJS_RETURN_OBJECT(result); OOJS_NATIVE_EXIT } // filteredEntities(this : Object, predicate : Function [, relativeTo : Entity [, range : Number]]) : Array (Entity) static JSBool SystemFilteredEntities(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) JSObject *jsThis = NULL; jsval predicate; Entity *relativeTo = nil; double range = -1; NSArray *result = nil; // Get this and predicate arguments if (argc < 2 || !OOJSValueIsFunction(context, OOJS_ARGV[1]) || !JS_ValueToObject(context, OOJS_ARGV[0], &jsThis)) { OOJSReportBadArguments(context, @"System", @"filteredEntities", argc, OOJS_ARGV, nil, @"this, predicate function, and optional reference entity and range"); return NO; } predicate = OOJS_ARGV[1]; // Get optional arguments argc -= 2; jsval *argv = OOJS_ARGV + 2; if (EXPECT_NOT(!GetRelativeToAndRange(context, @"filteredEntities", &argc, &argv, &relativeTo, &range))) return NO; // Search for entities JSFunctionPredicateParameter param = { context, predicate, jsThis, NO }; OOJSPauseTimeLimiter(); result = FindJSVisibleEntities(JSFunctionPredicate, ¶m, relativeTo, range); OOJSResumeTimeLimiter(); if (EXPECT_NOT(param.errorFlag)) return NO; OOJS_RETURN_OBJECT(result); OOJS_NATIVE_EXIT } // locationFromCode(populator_named_region : String) static JSBool SystemLocationFromCode(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *code = nil; if (argc > 0) { code = OOStringFromJSValue(context, OOJS_ARGV[0]); } if (EXPECT_NOT(code == nil)) { OOJSReportBadArguments(context, @"System", @"locationFromCode", argc, OOJS_ARGV, nil, @"location code"); return NO; } OOSunEntity *sun = [UNIVERSE sun]; OOPlanetEntity *planet = [UNIVERSE planet]; HPVector position = kZeroHPVector; if (sun == nil || planet == nil) { position = [UNIVERSE locationByCode:@"WITCHPOINT" withSun:nil andPlanet:nil]; } else { position = [UNIVERSE locationByCode:code withSun:sun andPlanet:planet]; } OOJS_RETURN_HPVECTOR(position); OOJS_NATIVE_EXIT } // addShips(role : String, count : Number [, position: Vector [, radius: Number]]) : Array static JSBool SystemAddShips(JSContext *context, uintN argc, jsval *vp) { return SystemAddShipsOrGroup(context, argc, vp, NO); } // addGroup(role : String, count : Number [, position: Vector [, radius: Number]]) : Array static JSBool SystemAddGroup(JSContext *context, uintN argc, jsval *vp) { return SystemAddShipsOrGroup(context, argc, vp, YES); } // addShipsToRoute(role : String, count : Number [, position: Number [, route: String]]) static JSBool SystemAddShipsToRoute(JSContext *context, uintN argc, jsval *vp) { return SystemAddShipsOrGroupToRoute(context, argc, vp, NO); } // addGroupToRoute(role : String, count : Number, position: Number[, route: String]) static JSBool SystemAddGroupToRoute(JSContext *context, uintN argc, jsval *vp) { return SystemAddShipsOrGroupToRoute(context, argc, vp, YES); } // legacy_addShips(role : String, count : Number) static JSBool SystemLegacyAddShips(JSContext *context, uintN argc, jsval *vp) { OOStandardsDeprecated(@"system.legacy_addShips() is deprecated"); OOJS_NATIVE_ENTER(context) NSString *role = nil; int32 count; if (argc > 0) role = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(role == nil || !JS_ValueToInt32(context, OOJS_ARGV[1], &count) || argc < 2 || count < 1 || 64 < count)) { OOJSReportBadArguments(context, @"System", @"legacy_addShips", argc, OOJS_ARGV, nil, @"role and positive count no greater than 64"); return NO; } OOJS_BEGIN_FULL_NATIVE(context) while (count--) [UNIVERSE witchspaceShipWithPrimaryRole:role]; OOJS_END_FULL_NATIVE OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // legacy_addSystemShips(role : String, count : Number, location : Number) static JSBool SystemLegacyAddSystemShips(JSContext *context, uintN argc, jsval *vp) { OOStandardsDeprecated(@"system.legacy_addSystemShips() is deprecated"); OOJS_NATIVE_ENTER(context) jsdouble position; NSString *role = nil; int32 count; if (argc > 0) role = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(role == nil || !JS_ValueToInt32(context, OOJS_ARGV[1], &count) || count < 1 || 64 < count || argc < 3 || !JS_ValueToNumber(context, OOJS_ARGV[2], &position))) { OOJSReportBadArguments(context, @"System", @"legacy_addSystemShips", argc, OOJS_ARGV, nil, @"role, positive count no greater than 64, and position along route"); return NO; } OOJS_BEGIN_FULL_NATIVE(context) while (count--) [UNIVERSE addShipWithRole:role nearRouteOneAt:position]; OOJS_END_FULL_NATIVE OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // legacy_addShipsAt(role : String, count : Number, coordScheme : String, coords : vectorExpression) static JSBool SystemLegacyAddShipsAt(JSContext *context, uintN argc, jsval *vp) { OOStandardsDeprecated(@"system.legacy_addShipsAt() is deprecated"); OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); HPVector where; NSString *role = nil; int32 count; NSString *coordScheme = nil; NSString *arg = nil; if (argc > 0) role = OOStringFromJSValue(context, OOJS_ARGV[0]); coordScheme = OOStringFromJSValue(context, OOJS_ARGV[2]); if (EXPECT_NOT(role == nil || !JS_ValueToInt32(context, OOJS_ARGV[1], &count) || count < 1 || 64 < count || coordScheme == nil || argc < 4 || !VectorFromArgumentListNoError(context, argc - 3, OOJS_ARGV + 3, &where, NULL))) { OOJSReportBadArguments(context, @"System", @"legacy_addShipsAt", argc, OOJS_ARGV, nil, @"role, positive count no greater than 64, coordinate scheme and coordinates"); return NO; } OOJS_BEGIN_FULL_NATIVE(context) arg = [NSString stringWithFormat:@"%@ %d %@ %f %f %f", role, count, coordScheme, where.x, where.y, where.z]; [player addShipsAt:arg]; OOJS_END_FULL_NATIVE OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // legacy_addShipsAtPrecisely(role : String, count : Number, coordScheme : String, coords : vectorExpression) static JSBool SystemLegacyAddShipsAtPrecisely(JSContext *context, uintN argc, jsval *vp) { OOStandardsDeprecated(@"system.legacy_addShipsAtPrecisely() is deprecated"); OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); HPVector where; NSString *role = nil; int32 count; NSString *coordScheme = nil; NSString *arg = nil; if (argc > 0) role = OOStringFromJSValue(context, OOJS_ARGV[0]); coordScheme = OOStringFromJSValue(context, OOJS_ARGV[2]); if (EXPECT_NOT(role == nil || !JS_ValueToInt32(context, OOJS_ARGV[1], &count) || count < 1 || 64 < count || coordScheme == nil || argc < 4 || !VectorFromArgumentListNoError(context, argc - 3, OOJS_ARGV + 3, &where, NULL))) { OOJSReportBadArguments(context, @"System", @"legacy_addShipsAtPrecisely", argc, OOJS_ARGV, nil, @"role, positive count no greater than 64, coordinate scheme and coordinates"); return NO; } OOJS_BEGIN_FULL_NATIVE(context) arg = [NSString stringWithFormat:@"%@ %d %@ %f %f %f", role, count, coordScheme, where.x, where.y, where.z]; [player addShipsAtPrecisely:arg]; OOJS_END_FULL_NATIVE OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // legacy_addShipsWithinRadius(role : String, count : Number, coordScheme : String, coords : vectorExpression, radius : Number) static JSBool SystemLegacyAddShipsWithinRadius(JSContext *context, uintN argc, jsval *vp) { OOStandardsDeprecated(@"system.legacy_addShipsWithinRadius() is deprecated"); OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); HPVector where; jsdouble radius; NSString *role = nil; int32 count; NSString *coordScheme = nil; NSString *arg = nil; uintN consumed = 0; if (argc > 0) role = OOStringFromJSValue(context, OOJS_ARGV[0]); if (argc > 2) coordScheme = OOStringFromJSValue(context, OOJS_ARGV[2]); if (EXPECT_NOT(role == nil || !JS_ValueToInt32(context, OOJS_ARGV[1], &count) || count < 1 || 64 < count || coordScheme == nil || argc < 5 || !VectorFromArgumentListNoError(context, argc - 3, OOJS_ARGV + 3, &where, &consumed) || !JS_ValueToNumber(context, OOJS_ARGV[3 + consumed], &radius))) { OOJSReportBadArguments(context, @"System", @"legacy_addShipWithinRadius", argc, OOJS_ARGV, nil, @"role, positive count no greater than 64, coordinate scheme, coordinates and radius"); return NO; } OOJS_BEGIN_FULL_NATIVE(context) arg = [NSString stringWithFormat:@"%@ %d %@ %f %f %f %f", role, count, coordScheme, where.x, where.y, where.z, radius]; [player addShipsWithinRadius:arg]; OOJS_END_FULL_NATIVE OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // legacy_spawnShip(key : string) static JSBool SystemLegacySpawnShip(JSContext *context, uintN argc, jsval *vp) { OOStandardsDeprecated(@"system.legacy_spawnShip() is deprecated"); OOJS_NATIVE_ENTER(context) NSString *key = nil; OOPlayerForScripting(); // For backwards-compatibility if (argc > 0) key = OOStringFromJSValue(context, OOJS_ARGV[0]); if (key == nil) { OOJSReportBadArguments(context, @"System", @"legacy_spawnShip", MIN(argc, 1U), OOJS_ARGV, nil, @"string (ship key)"); return NO; } OOJS_BEGIN_FULL_NATIVE(context) [UNIVERSE spawnShip:key]; OOJS_END_FULL_NATIVE OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // *** Static methods *** // systemNameForID(ID : Number) : String static JSBool SystemStaticSystemNameForID(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) int32 systemID; if (argc < 1 || !JS_ValueToInt32(context, OOJS_ARGV[0], &systemID) || systemID < -1 || kOOMaximumSystemID < systemID) // -1 interstellar space! { OOJSReportBadArguments(context, @"System", @"systemNameForID", MIN(argc, 1U), OOJS_ARGV, nil, @"system ID"); return NO; } if (systemID == -1) OOJS_RETURN_OBJECT(DESC(@"interstellar-space")); else OOJS_RETURN_OBJECT([UNIVERSE getSystemName:systemID]); OOJS_NATIVE_EXIT } // systemIDForName(name : String) : Number static JSBool SystemStaticSystemIDForName(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *name = nil; unsigned result; if (argc > 0) name = OOStringFromJSValue(context, OOJS_ARGV[0]); if (name == nil) { OOJSReportBadArguments(context, @"System", @"systemIDForName", MIN(argc, 1U), OOJS_ARGV, nil, @"string"); return NO; } OOJS_BEGIN_FULL_NATIVE(context) result = [UNIVERSE findSystemFromName:name]; OOJS_END_FULL_NATIVE OOJS_RETURN_INT(result); OOJS_NATIVE_EXIT } // infoForSystem(galaxyID : Number, systemID : Number) : SystemInfo static JSBool SystemStaticInfoForSystem(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) int32 galaxyID; int32 systemID; if (argc < 2 || !JS_ValueToInt32(context, OOJS_ARGV[0], &galaxyID) || !JS_ValueToInt32(context, OOJS_ARGV[1], &systemID)) { OOJSReportBadArguments(context, @"System", @"infoForSystem", argc, OOJS_ARGV, nil, @"galaxy ID and system ID"); return NO; } if (galaxyID < 0 || galaxyID > kOOMaximumGalaxyID) { OOJSReportBadArguments(context, @"System", @"infoForSystem", 1, OOJS_ARGV, @"Invalid galaxy ID", [NSString stringWithFormat:@"number in the range 0 to %u", kOOMaximumGalaxyID]); return NO; } if (systemID < kOOMinimumSystemID || systemID > kOOMaximumSystemID) { OOJSReportBadArguments(context, @"System", @"infoForSystem", 1, OOJS_ARGV + 1, @"Invalid system ID", [NSString stringWithFormat:@"number in the range %i to %i", kOOMinimumSystemID, kOOMaximumSystemID]); return NO; } OOJS_RETURN(GetJSSystemInfoForSystem(context, galaxyID, systemID)); OOJS_NATIVE_EXIT } static JSBool SystemAddVisualEffect(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *key = nil; HPVector where; uintN consumed = 0; if (argc > 0) key = OOStringFromJSValue(context, OOJS_ARGV[0]); if (key == nil) { OOJSReportBadArguments(context, @"System", @"addVisualEffect", MIN(argc, 1U), &OOJS_ARGV[0], nil, @"string (key)"); return NO; } if (!VectorFromArgumentListNoError(context, argc - 1, OOJS_ARGV + 1, &where, &consumed)) { OOJSReportBadArguments(context, @"System", @"addVisualEffect", MIN(argc - 1, 1U), &OOJS_ARGV[1], nil, @"vector"); return NO; } OOVisualEffectEntity *result = nil; OOJS_BEGIN_FULL_NATIVE(context) result = [UNIVERSE addVisualEffectAt:where withKey:key]; OOJS_END_FULL_NATIVE OOJS_RETURN_OBJECT(result); OOJS_NATIVE_EXIT } static JSBool SystemSetPopulator(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *key; NSMutableDictionary *settings; JSObject *params = NULL; if (argc < 1) { OOJSReportBadArguments(context, @"System", @"setPopulator", MIN(argc, 0U), &OOJS_ARGV[0], nil, @"string (key), object (settings)"); return NO; } key = OOStringFromJSValue(context, OOJS_ARGV[0]); if (key == nil) { OOJSReportBadArguments(context, @"System", @"setPopulator", MIN(argc, 0U), &OOJS_ARGV[0], nil, @"key, settings"); return NO; } if (argc < 2 || JSVAL_IS_NULL(OOJS_ARGV[1])) { // clearing [UNIVERSE setPopulatorSetting:key to:nil]; } else { // adding if (!JS_ValueToObject(context, OOJS_ARGV[1], ¶ms)) { OOJSReportBadArguments(context, @"System", @"setPopulator", MIN(argc, 1U), OOJS_ARGV, NULL, @"key, settings"); return NO; } jsval callback = JSVAL_NULL; if (JS_GetProperty(context, params, "callback", &callback) == JS_FALSE || JSVAL_IS_VOID(callback)) { OOJSReportBadArguments(context, @"System", @"setPopulator", MIN(argc, 1U), OOJS_ARGV, NULL, @"settings must have a 'callback' property."); return NO; } OOJSPopulatorDefinition *populator = [[OOJSPopulatorDefinition alloc] init]; [populator setCallback:callback]; settings = OOJSNativeObjectFromJSObject(context, JSVAL_TO_OBJECT(OOJS_ARGV[1])); [settings setObject:populator forKey:@"callbackObj"]; jsval coords = JSVAL_NULL; if (JS_GetProperty(context, params, "coordinates", &coords) != JS_FALSE && !JSVAL_IS_VOID(coords)) { Vector coordinates = kZeroVector; if (JSValueToVector(context, coords, &coordinates)) { // convert vector in NS-storable form [settings setObject:[NSArray arrayWithObjects:[NSNumber numberWithFloat:coordinates.x],[NSNumber numberWithFloat:coordinates.y],[NSNumber numberWithFloat:coordinates.z],nil] forKey:@"coordinates"]; } } [populator release]; [UNIVERSE setPopulatorSetting:key to:settings]; } OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } static JSBool SystemSetWaypoint(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *key; NSMutableDictionary *settings; HPVector position; Quaternion orientation; if (argc < 1) { OOJSReportBadArguments(context, @"System", @"setWaypoint", MIN(argc, 0U), &OOJS_ARGV[0], nil, @"key, position, orientation, definition"); return NO; } key = OOStringFromJSValue(context, OOJS_ARGV[0]); if (key == nil) { OOJSReportBadArguments(context, @"System", @"setWaypoint", MIN(argc, 0U), &OOJS_ARGV[0], nil, @"key, position, orientation, definition"); return NO; } if (argc < 4 || JSVAL_IS_NULL(OOJS_ARGV[3])) { // clearing [UNIVERSE defineWaypoint:nil forKey:key]; } else { // adding if (!JSValueToHPVector(context, OOJS_ARGV[1], &position)) { OOJSReportBadArguments(context, @"System", @"setWaypoint", MIN(argc, 2U), OOJS_ARGV, NULL, @"key, position, orientation, definition"); return NO; } if (!JSValueToQuaternion(context, OOJS_ARGV[2], &orientation)) { OOJSReportBadArguments(context, @"System", @"setWaypoint", MIN(argc, 3U), OOJS_ARGV, NULL, @"key, position, orientation, definition"); return NO; } if (!JSVAL_IS_OBJECT(OOJS_ARGV[3]) || JSVAL_IS_NULL(OOJS_ARGV[3])) { OOJSReportBadArguments(context, @"System", @"setWaypoint", MIN(argc, 4U), OOJS_ARGV, NULL, @"key, position, orientation, definition"); return NO; } settings = [[OOJSNativeObjectFromJSObject(context, JSVAL_TO_OBJECT(OOJS_ARGV[3])) mutableCopy] autorelease]; [settings setObject:[NSArray arrayWithObjects:[NSNumber numberWithDouble:position.x],[NSNumber numberWithDouble:position.y],[NSNumber numberWithDouble:position.z],nil] forKey:@"position"]; [settings setObject:[NSArray arrayWithObjects:[NSNumber numberWithDouble:orientation.w],[NSNumber numberWithDouble:orientation.x],[NSNumber numberWithDouble:orientation.y],[NSNumber numberWithDouble:orientation.z],nil] forKey:@"orientation"]; [UNIVERSE defineWaypoint:settings forKey:key]; } OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // *** Helper functions *** // Shared implementation of addShips() and addGroup(). static JSBool SystemAddShipsOrGroup(JSContext *context, uintN argc, jsval *vp, BOOL isGroup) { OOJS_NATIVE_ENTER(context) NSString *role = nil; int32 count = 0; uintN consumed = 0; HPVector where; double radius = NSNotFound; // a negative value means id result = nil; NSString *func = isGroup ? @"addGroup" : @"addShips"; if (argc > 0) role = OOStringFromJSValue(context, OOJS_ARGV[0]); if (role == nil) { OOJSReportBadArguments(context, @"System", func, MIN(argc, 1U), &OOJS_ARGV[0], nil, @"string (role)"); return NO; } if (argc < 2 || !JS_ValueToInt32(context, OOJS_ARGV[1], &count) || count < 1 || 64 < count) { OOJSReportBadArguments(context, @"System", func, MIN(argc - 1, 1U), &OOJS_ARGV[1], nil, @"number (positive count no greater than 64)"); return NO; } if (argc < 3) { where = [UNIVERSE getWitchspaceExitPosition]; radius = SCANNER_MAX_RANGE; } else { if (!VectorFromArgumentListNoError(context, argc - 2, OOJS_ARGV + 2, &where, &consumed)) { OOJSReportBadArguments(context, @"System", func, MIN(argc - 2, 1U), &OOJS_ARGV[2], nil, @"vector"); return NO; } if (argc > 2 + consumed) { if (!JS_ValueToNumber(context, OOJS_ARGV[2 + consumed], &radius)) { OOJSReportBadArguments(context, @"System", func, MIN(argc - 2 - consumed, 1U), &OOJS_ARGV[2 + consumed], nil, @"number (radius)"); return NO; } } } OOJS_BEGIN_FULL_NATIVE(context) // Note: the use of witchspace-in effects (as in legacy_addShips) depends on proximity to the witchpoint. result = [UNIVERSE addShipsAt:where withRole:role quantity:count withinRadius:radius asGroup:isGroup]; if (isGroup) { NSArray *array = result; if ([array count] > 0) result = [(ShipEntity *)[array objectAtIndex:0] group]; else result = nil; } OOJS_END_FULL_NATIVE OOJS_RETURN_OBJECT(result); OOJS_NATIVE_EXIT } static JSBool SystemAddShipsOrGroupToRoute(JSContext *context, uintN argc, jsval *vp, BOOL isGroup) { OOJS_NATIVE_ENTER(context) NSString *role = nil; NSString *route = @"st"; // default route witchpoint -> station. ("st" itself is not selectable by script) static NSSet *validRoutes = nil; int32 count = 0; double where = NSNotFound; // a negative value means random positioning! id result = nil; NSString *func = isGroup ? @"addGroup" : @"addShips"; if (argc > 0) role = OOStringFromJSValue(context, OOJS_ARGV[0]); if (role == nil) { OOJSReportBadArguments(context, @"System", func, MIN(argc, 1U), &OOJS_ARGV[0], nil, @"string (role)"); return NO; } if (argc < 2 || !JS_ValueToInt32(context, OOJS_ARGV[1], &count) || count < 1 || 64 < count) { OOJSReportBadArguments(context, @"System", func, MIN(argc - 1, 1U), &OOJS_ARGV[1], nil, @"number (positive count no greater than 64)"); return NO; } if (argc > 2) { if (!JS_ValueToNumber(context, OOJS_ARGV[2], &where) || !isfinite(where) || where < 0.0f || where > 1.0f) { OOJSReportBadArguments(context, @"System", func, MIN(argc - 2, 1U), &OOJS_ARGV[2], nil, @"number (position along route)"); return NO; } if (argc > 3) { route = [OOStringFromJSValue(context, OOJS_ARGV[3]) lowercaseString]; if (validRoutes == nil) { validRoutes = [[NSSet alloc] initWithObjects:@"wp", @"pw", @"ws", @"sw", @"sp", @"ps", nil]; } if (route == nil || ![validRoutes containsObject:route]) { OOJSReportBadArguments(context, @"System", func, MIN(argc - 3, 1U), &OOJS_ARGV[3], nil, @"string (route specifier)"); return NO; } } } OOJS_BEGIN_FULL_NATIVE(context) // Note: the use of witchspace-in effects (as in legacy_addShips) depends on proximity to the witchpoint. result = [UNIVERSE addShipsToRoute:route withRole:role quantity:count routeFraction:where asGroup:isGroup]; if (isGroup) { NSArray *array = result; if ([array count] > 0) result = [(ShipEntity *)[array objectAtIndex:0] group]; else result = nil; } OOJS_END_FULL_NATIVE OOJS_RETURN_OBJECT(result); OOJS_NATIVE_EXIT } static BOOL GetRelativeToAndRange(JSContext *context, NSString *methodName, uintN *ioArgc, jsval **ioArgv, Entity **outRelativeTo, double *outRange) { OOJS_PROFILE_ENTER // No NULL arguments accepted. assert(ioArgc && ioArgv && outRelativeTo && outRange); // Get optional argument relativeTo : Entity if (*ioArgc != 0) { if (EXPECT_NOT(JSVAL_IS_NULL(**ioArgv) || !JSValueToEntity(context, **ioArgv, outRelativeTo))) { OOJSReportBadArguments(context, @"System", methodName, 1, *ioArgv, nil, @"entity"); return NO; } (*ioArgv)++; (*ioArgc)--; } // Get optional argument range : Number if (*ioArgc != 0) { if (!EXPECT_NOT(JS_ValueToNumber(context, **ioArgv, outRange))) { OOJSReportBadArguments(context, @"System", methodName, 1, *ioArgv, nil, @"number"); return NO; } (*ioArgv)++; (*ioArgc)--; } return YES; OOJS_PROFILE_EXIT } static NSArray *FindJSVisibleEntities(EntityFilterPredicate predicate, void *parameter, Entity *relativeTo, double range) { OOJS_PROFILE_ENTER NSMutableArray *result = nil; BinaryOperationPredicateParameter param = { JSEntityIsJavaScriptSearchablePredicate, NULL, predicate, parameter }; result = [UNIVERSE findEntitiesMatchingPredicate:ANDPredicate parameter:¶m inRange:range ofEntity:relativeTo]; if (result != nil && relativeTo != nil && ![relativeTo isPlayer]) { [result sortUsingFunction:CompareEntitiesByDistance context:relativeTo]; } if (result == nil) return [NSArray array]; return result; OOJS_PROFILE_EXIT } static NSArray *FindShips(EntityFilterPredicate predicate, void *parameter, Entity *relativeTo, double range) { OOJS_PROFILE_ENTER BinaryOperationPredicateParameter param = { IsShipPredicate, NULL, predicate, parameter }; return FindJSVisibleEntities(ANDPredicate, ¶m, relativeTo, range); OOJS_PROFILE_EXIT } static NSComparisonResult CompareEntitiesByDistance(id a, id b, void *relativeTo) { OOJS_PROFILE_ENTER Entity *ea = a, *eb = b, *r = (id)relativeTo; float d1, d2; d1 = HPdistance2(ea->position, r->position); d2 = HPdistance2(eb->position, r->position); if (d1 < d2) return NSOrderedAscending; else if (d1 > d2) return NSOrderedDescending; else return NSOrderedSame; OOJS_PROFILE_EXIT } oolite-1.82/src/Core/Scripting/OOJSSystemInfo.h000066400000000000000000000021531256642440500213350ustar00rootroot00000000000000/* OOJSSystemInfo.h JavaScript object representing system info overrides. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #include #import "OOTypes.h" void InitOOJSSystemInfo(JSContext *context, JSObject *global); // Returns JSVAL_NULL on failure (with a JS warning, but no exception). jsval GetJSSystemInfoForSystem(JSContext *context, OOGalaxyID galaxy, OOSystemID system); oolite-1.82/src/Core/Scripting/OOJSSystemInfo.m000066400000000000000000000552211256642440500213460ustar00rootroot00000000000000/* OOJSSystemInfo.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSSystemInfo.h" #import "OOJavaScriptEngine.h" #import "PlayerEntityScriptMethods.h" #import "Universe.h" #import "OOJSVector.h" #import "OOIsNumberLiteral.h" #import "OOConstToString.h" #import "OOSystemDescriptionManager.h" #import "OOJSScript.h" static JSObject *sSystemInfoPrototype; static JSObject *sCachedSystemInfo; static OOGalaxyID sCachedGalaxy; static OOSystemID sCachedSystem; static JSBool SystemInfoDeleteProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool SystemInfoGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool SystemInfoSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); static void SystemInfoFinalize(JSContext *context, JSObject *this); static JSBool SystemInfoEnumerate(JSContext *context, JSObject *this, JSIterateOp enumOp, jsval *state, jsid *idp); static JSBool SystemInfoDistanceToSystem(JSContext *context, uintN argc, jsval *vp); static JSBool SystemInfoRouteToSystem(JSContext *context, uintN argc, jsval *vp); static JSBool SystemInfoSamplePrice(JSContext *context, uintN argc, jsval *vp); static JSBool SystemInfoSetPropertyMethod(JSContext *context, uintN argc, jsval *vp); static JSBool SystemInfoStaticSetInterstellarProperty(JSContext *context, uintN argc, jsval *vp); static JSBool SystemInfoStaticFilteredSystems(JSContext *context, uintN argc, jsval *vp); static JSClass sSystemInfoClass = { "SystemInfo", JSCLASS_HAS_PRIVATE | JSCLASS_NEW_ENUMERATE, JS_PropertyStub, SystemInfoDeleteProperty, SystemInfoGetProperty, SystemInfoSetProperty, (JSEnumerateOp)SystemInfoEnumerate, JS_ResolveStub, JS_ConvertStub, SystemInfoFinalize, JSCLASS_NO_OPTIONAL_MEMBERS }; enum { // Property IDs kSystemInfo_coordinates, // system coordinates (in LY), Vector3D (with z = 0), read-only kSystemInfo_internalCoordinates, // system coordinates (unscaled), Vector3D (with z = 0), read-only kSystemInfo_galaxyID, // galaxy number, integer, read-only kSystemInfo_systemID // system number, integer, read-only }; static JSPropertySpec sSystemInfoProperties[] = { // JS name ID flags { "coordinates", kSystemInfo_coordinates, OOJS_PROP_READONLY_CB }, { "internalCoordinates", kSystemInfo_internalCoordinates, OOJS_PROP_READONLY_CB }, { "galaxyID", kSystemInfo_galaxyID, OOJS_PROP_READONLY_CB }, { "systemID", kSystemInfo_systemID, OOJS_PROP_READONLY_CB }, { 0 } }; static JSFunctionSpec sSystemInfoMethods[] = { // JS name Function min args { "toString", OOJSObjectWrapperToString, 0 }, { "distanceToSystem", SystemInfoDistanceToSystem, 1 }, { "routeToSystem", SystemInfoRouteToSystem, 1 }, { "samplePrice", SystemInfoSamplePrice, 1 }, { "setProperty", SystemInfoSetPropertyMethod, 3 }, { 0 } }; static JSFunctionSpec sSystemInfoStaticMethods[] = { // JS name Function min args { "filteredSystems", SystemInfoStaticFilteredSystems, 2 }, { "setInterstellarProperty",SystemInfoStaticSetInterstellarProperty, 4 }, { 0 } }; // Helper class wrapped by JS SystemInfo objects @interface OOSystemInfo: NSObject { @private OOGalaxyID _galaxy; OOSystemID _system; NSString *_planetKey; } - (id) initWithGalaxy:(OOGalaxyID)galaxy system:(OOSystemID)system; - (id) valueForKey:(NSString *)key; - (void) setValue:(id)value forKey:(NSString *)key; - (NSArray *) allKeys; - (OOGalaxyID) galaxy; - (OOSystemID) system; //- (Random_Seed) systemSeed; @end DEFINE_JS_OBJECT_GETTER(JSSystemInfoGetSystemInfo, &sSystemInfoClass, sSystemInfoPrototype, OOSystemInfo); @implementation OOSystemInfo - (id) init { [self release]; return nil; } - (id) initWithGalaxy:(OOGalaxyID)galaxy system:(OOSystemID)system { if (galaxy > kOOMaximumGalaxyID || system > kOOMaximumSystemID || system < kOOMinimumSystemID) { [self release]; return nil; } if ((self = [super init])) { _galaxy = galaxy; _system = system; _planetKey = [[NSString stringWithFormat:@"%u %i", galaxy, system] retain]; } return self; } - (void) dealloc { [_planetKey release]; [super dealloc]; } - (NSString *) descriptionComponents { return [NSString stringWithFormat:@"galaxy %u, system %i", _galaxy, _system]; } - (NSString *) shortDescriptionComponents { return _planetKey; } - (NSString *) oo_jsClassName { return @"SystemInfo"; } - (BOOL) isEqual:(id)other { return other == self || ([other isKindOfClass:[OOSystemInfo class]] && [other galaxy] == _galaxy && [other system] == _system); } - (NSUInteger) hash { NSUInteger hash = _galaxy; hash <<= 16; hash |= (uint16_t)_system; return hash; } - (id) valueForKey:(NSString *)key { if ([UNIVERSE inInterstellarSpace] && _system == -1) { return [[UNIVERSE currentSystemData] objectForKey:key]; } return [UNIVERSE systemDataForGalaxy:_galaxy planet:_system key:key]; } - (void) setValue:(id)value forKey:(NSString *)key { NSString *manifest = [[OOJSScript currentlyRunningScript] propertyNamed:kLocalManifestProperty]; [UNIVERSE setSystemDataForGalaxy:_galaxy planet:_system key:key value:value fromManifest:manifest forLayer:OO_LAYER_OXP_DYNAMIC]; } - (NSArray *) allKeys { if ([UNIVERSE inInterstellarSpace] && _system == -1) { return [[UNIVERSE currentSystemData] allKeys]; } return [UNIVERSE systemDataKeysForGalaxy:_galaxy planet:_system]; } - (OOGalaxyID) galaxy { return _galaxy; } - (OOSystemID) system { return _system; } /*- (Random_Seed) systemSeed { NSAssert([PLAYER currentGalaxyID] == _galaxy, @"Attempt to use -[OOSystemInfo systemSeed] from a different galaxy."); return [UNIVERSE systemSeedForSystemNumber:_system]; }*/ - (NSPoint) coordinates { if ([UNIVERSE inInterstellarSpace] && _system == -1) { return [PLAYER galaxy_coordinates]; } return [UNIVERSE coordinatesForSystem:_system]; } - (jsval) oo_jsValueInContext:(JSContext *)context { JSObject *jsSelf = NULL; jsval result = JSVAL_NULL; jsSelf = JS_NewObject(context, &sSystemInfoClass, sSystemInfoPrototype, NULL); if (jsSelf != NULL) { if (!JS_SetPrivate(context, jsSelf, [self retain])) jsSelf = NULL; } if (jsSelf != NULL) result = OBJECT_TO_JSVAL(jsSelf); return result; } @end void InitOOJSSystemInfo(JSContext *context, JSObject *global) { sSystemInfoPrototype = JS_InitClass(context, global, NULL, &sSystemInfoClass, OOJSUnconstructableConstruct, 0, sSystemInfoProperties, sSystemInfoMethods, NULL, sSystemInfoStaticMethods); OOJSRegisterObjectConverter(&sSystemInfoClass, OOJSBasicPrivateObjectConverter); } jsval GetJSSystemInfoForSystem(JSContext *context, OOGalaxyID galaxy, OOSystemID system) { OOJS_PROFILE_ENTER // Use cached object if possible. if (sCachedSystemInfo != NULL && sCachedGalaxy == galaxy && sCachedSystem == system) { return OBJECT_TO_JSVAL(sCachedSystemInfo); } // If not, create a new one. OOSystemInfo *info = nil; jsval result; OOJS_BEGIN_FULL_NATIVE(context) info = [[[OOSystemInfo alloc] initWithGalaxy:galaxy system:system] autorelease]; OOJS_END_FULL_NATIVE if (EXPECT_NOT(info == nil)) { OOJSReportWarning(context, @"Could not create system info object for galaxy %u, system %i.", galaxy, system); } result = OOJSValueFromNativeObject(context, info); // Cache is not a root; we clear it in finalize if necessary. sCachedSystemInfo = JSVAL_TO_OBJECT(result); sCachedGalaxy = galaxy; sCachedSystem = system; return result; OOJS_PROFILE_EXIT_JSVAL } static void SystemInfoFinalize(JSContext *context, JSObject *this) { OOJS_PROFILE_ENTER [(id)JS_GetPrivate(context, this) release]; JS_SetPrivate(context, this, nil); // Clear now-stale cache entry if appropriate. if (sCachedSystemInfo == this) sCachedSystemInfo = NULL; OOJS_PROFILE_EXIT_VOID } static JSBool SystemInfoEnumerate(JSContext *context, JSObject *this, JSIterateOp enumOp, jsval *state, jsid *idp) { OOJS_NATIVE_ENTER(context) NSEnumerator *enumerator = nil; switch (enumOp) { case JSENUMERATE_INIT: case JSENUMERATE_INIT_ALL: // For ES5 Object.getOwnPropertyNames(). Since we have no non-enumerable properties, this is the same as _INIT. { OOSystemInfo *info = JS_GetPrivate(context, this); NSArray *keys = [info allKeys]; enumerator = [[keys objectEnumerator] retain]; *state = PRIVATE_TO_JSVAL(enumerator); NSUInteger count = [keys count]; assert(count <= INT32_MAX); if (idp != NULL) *idp = INT_TO_JSID((int32_t)count); return YES; } case JSENUMERATE_NEXT: { enumerator = JSVAL_TO_PRIVATE(*state); NSString *next = [enumerator nextObject]; if (next != nil) { jsval val = [next oo_jsValueInContext:context]; return JS_ValueToId(context, val, idp); } // else: *state = JSVAL_NULL; // Fall through. } case JSENUMERATE_DESTROY: { if (enumerator == nil && JSVAL_IS_DOUBLE(*state)) { enumerator = JSVAL_TO_PRIVATE(*state); } [enumerator release]; if (idp != NULL) *idp = JSID_VOID; return YES; } } OOJS_NATIVE_EXIT } static JSBool SystemInfoDeleteProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { OOJS_PROFILE_ENTER // Any exception will be converted in SystemInfoSetProperty() jsval v = JSVAL_VOID; return SystemInfoSetProperty(context, this, propID, NO, &v); OOJS_PROFILE_EXIT } static JSBool SystemInfoGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { OOJS_NATIVE_ENTER(context) if (this == sSystemInfoPrototype) { // Let SpiderMonkey handle access to the prototype object (where info will be nil). return YES; } OOSystemInfo *info = OOJSNativeObjectOfClassFromJSObject(context, this, [OOSystemInfo class]); // What if we're trying to access a saved witchspace systemInfo object? BOOL savedInterstellarInfo = ![UNIVERSE inInterstellarSpace] && [info system] == -1; BOOL sameGalaxy = [PLAYER currentGalaxyID] == [info galaxy]; if (JSID_IS_INT(propID)) { switch (JSID_TO_INT(propID)) { case kSystemInfo_coordinates: if (sameGalaxy && !savedInterstellarInfo) { return VectorToJSValue(context, OOGalacticCoordinatesFromInternal([info coordinates]), value); } else { OOJSReportError(context, @"Cannot read systemInfo values for %@.", savedInterstellarInfo ? @"invalid interstellar space reference" : @"other galaxies"); return NO; } break; case kSystemInfo_internalCoordinates: if (sameGalaxy && !savedInterstellarInfo) { return NSPointToVectorJSValue(context, [info coordinates], value); } else { OOJSReportError(context, @"Cannot read systemInfo values for %@.", savedInterstellarInfo ? @"invalid interstellar space reference" : @"other galaxies"); return NO; } break; case kSystemInfo_galaxyID: *value = INT_TO_JSVAL([info galaxy]); return YES; case kSystemInfo_systemID: *value = INT_TO_JSVAL([info system]); return YES; default: OOJSReportBadPropertySelector(context, this, propID, sSystemInfoProperties); return NO; } } else if (JSID_IS_STRING(propID)) { NSString *key = OOStringFromJSString(context, JSID_TO_STRING(propID)); OOSystemDescriptionManager *systemManager = [UNIVERSE systemManager]; id propValue = nil; // interstellar space needs more work at this stage if ([info system] != -1) { propValue = [systemManager getProperty:key forSystem:[info system] inGalaxy:[info galaxy]]; } else { propValue = [info valueForKey:key]; } if (propValue != nil) { if ([propValue isKindOfClass:[NSNumber class]] || OOIsNumberLiteral(propValue, YES)) { BOOL OK = JS_NewNumberValue(context, [propValue doubleValue], value); if (!OK) { *value = JSVAL_VOID; return NO; } } else { *value = [propValue oo_jsValueInContext:context]; } } } return YES; OOJS_NATIVE_EXIT } static JSBool SystemInfoSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { if (EXPECT_NOT(this == sSystemInfoPrototype)) { // Let SpiderMonkey handle access to the prototype object (where info will be nil). return YES; } OOJS_NATIVE_ENTER(context); if (JSID_IS_STRING(propID)) { NSString *key = OOStringFromJSString(context, JSID_TO_STRING(propID)); OOSystemInfo *info = OOJSNativeObjectOfClassFromJSObject(context, this, [OOSystemInfo class]); [info setValue:OOStringFromJSValue(context, *value) forKey:key]; } return YES; OOJS_NATIVE_EXIT } // distanceToSystem(sys : SystemInfo) : Number static JSBool SystemInfoDistanceToSystem(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOSystemInfo *thisInfo = nil; JSObject *otherObj = NULL; OOSystemInfo *otherInfo = nil; if (!JSSystemInfoGetSystemInfo(context, OOJS_THIS, &thisInfo)) return NO; if (argc < 1 || !JS_ValueToObject(context, OOJS_ARGV[0], &otherObj) || !JSSystemInfoGetSystemInfo(context, otherObj, &otherInfo)) { OOJSReportBadArguments(context, @"SystemInfo", @"distanceToSystem", MIN(argc, 1U), OOJS_ARGV, nil, @"system info"); return NO; } BOOL sameGalaxy = ([thisInfo galaxy] == [otherInfo galaxy]); if (!sameGalaxy) { OOJSReportErrorForCaller(context, @"SystemInfo", @"distanceToSystem", @"Cannot calculate distance for systems in other galaxies."); return NO; } NSPoint thisCoord = [thisInfo coordinates]; NSPoint otherCoord = [otherInfo coordinates]; OOJS_RETURN_DOUBLE(distanceBetweenPlanetPositions(thisCoord.x, thisCoord.y, otherCoord.x, otherCoord.y)); OOJS_NATIVE_EXIT } // routeToSystem(sys : SystemInfo [, optimizedBy : String]) : Object static JSBool SystemInfoRouteToSystem(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOSystemInfo *thisInfo = nil; JSObject *otherObj = NULL; OOSystemInfo *otherInfo = nil; NSDictionary *result = nil; OORouteType routeType = OPTIMIZED_BY_JUMPS; if (!JSSystemInfoGetSystemInfo(context, OOJS_THIS, &thisInfo)) return NO; if (argc < 1 || !JS_ValueToObject(context, OOJS_ARGV[0], &otherObj) || !JSSystemInfoGetSystemInfo(context, otherObj, &otherInfo)) { OOJSReportBadArguments(context, @"SystemInfo", @"routeToSystem", MIN(argc, 1U), OOJS_ARGV, nil, @"system info"); return NO; } BOOL sameGalaxy = ([thisInfo galaxy] == [otherInfo galaxy]); if (!sameGalaxy) { OOJSReportErrorForCaller(context, @"SystemInfo", @"routeToSystem", @"Cannot calculate route for destinations in other galaxies."); return NO; } if (argc >= 2) { routeType = StringToRouteType(OOStringFromJSValue(context, OOJS_ARGV[1])); } OOJS_BEGIN_FULL_NATIVE(context) result = [UNIVERSE routeFromSystem:[thisInfo system] toSystem:[otherInfo system] optimizedBy:routeType]; OOJS_END_FULL_NATIVE OOJS_RETURN_OBJECT(result); OOJS_NATIVE_EXIT } // samplePrice(commodity) static JSBool SystemInfoSamplePrice(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOSystemInfo *thisInfo = nil; if (!JSSystemInfoGetSystemInfo(context, OOJS_THIS, &thisInfo)) return NO; OOCommodityType commodity = OOStringFromJSValue(context, OOJS_ARGV[0]); if (EXPECT_NOT(![[UNIVERSE commodities] goodDefined:commodity])) { OOJSReportBadArguments(context, @"SystemInfo", @"samplePrice", MIN(argc, 1U), OOJS_ARGV, NULL, @"Unrecognised commodity type"); return NO; } BOOL sameGalaxy = ([thisInfo galaxy] == [PLAYER galaxyNumber]); if (!sameGalaxy) { OOJSReportErrorForCaller(context, @"SystemInfo", @"routeToSystem", @"Cannot calculate sample price for destinations in other galaxies."); return NO; } OOCreditsQuantity price = [[UNIVERSE commodities] samplePriceForCommodity:commodity inEconomy:[[thisInfo valueForKey:@"economy"] intValue] withScript:[thisInfo valueForKey:@"commodity_script"] inSystem:[thisInfo system]]; OOJS_RETURN_INT(price); OOJS_NATIVE_EXIT } static JSBool SystemInfoSetPropertyMethod(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOSystemInfo *thisInfo = nil; if (!JSSystemInfoGetSystemInfo(context, OOJS_THIS, &thisInfo)) return NO; NSString *property = nil; id value = nil; NSString *manifest = nil; int32 iValue; if (argc < 3) { OOJSReportBadArguments(context, @"SystemInfo", @"setProperty", MIN(argc, 3U), OOJS_ARGV, NULL, @"setProperty(layer, property, value [,manifest])"); return NO; } if (!JS_ValueToInt32(context, OOJS_ARGV[0], &iValue)) { OOJSReportBadArguments(context, @"SystemInfo", @"setProperty", MIN(argc, 3U), OOJS_ARGV, NULL, @"setProperty(layer, property, value [,manifest])"); return NO; } if (iValue < 0 || iValue >= OO_SYSTEM_LAYERS) { OOJSReportBadArguments(context, @"SystemInfo", @"setProperty", MIN(argc, 3U), OOJS_ARGV, NULL, @"layer must be 0, 1, 2 or 3"); return NO; } OOSystemLayer layer = (OOSystemLayer)iValue; property = OOStringFromJSValue(context, OOJS_ARGV[1]); if (!JSVAL_IS_NULL(OOJS_ARGV[2])) { value = OOJSNativeObjectFromJSValue(context, OOJS_ARGV[2]); } if (argc >= 4) { manifest = OOStringFromJSValue(context, OOJS_ARGV[3]); } else { manifest = [[OOJSScript currentlyRunningScript] propertyNamed:kLocalManifestProperty]; } [UNIVERSE setSystemDataForGalaxy:[thisInfo galaxy] planet:[thisInfo system] key:property value:value fromManifest:manifest forLayer:layer]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } // filteredSystems(this : Object, predicate : Function) : Array static JSBool SystemInfoStaticFilteredSystems(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) JSObject *jsThis = NULL; // Get this and predicate arguments if (argc < 2 || !OOJSValueIsFunction(context, OOJS_ARGV[1]) || !JS_ValueToObject(context, OOJS_ARGV[0], &jsThis)) { OOJSReportBadArguments(context, @"SystemInfo", @"filteredSystems", argc, OOJS_ARGV, nil, @"this and predicate function"); return NO; } jsval predicate = OOJS_ARGV[1]; NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSMutableArray *result = [NSMutableArray arrayWithCapacity:256]; // Not OOJS_BEGIN_FULL_NATIVE() - we use JSAPI while paused. OOJSPauseTimeLimiter(); // Iterate over systems. BOOL OK = result != nil; OOGalaxyID galaxy = [PLAYER currentGalaxyID]; OOSystemID system; for (system = 0; system <= kOOMaximumSystemID; system++) { // NOTE: this deliberately bypasses the cache, since iteration is inherently unfriendly to a single-item cache. OOSystemInfo *info = [[[OOSystemInfo alloc] initWithGalaxy:galaxy system:system] autorelease]; jsval args[1] = { OOJSValueFromNativeObject(context, info) }; jsval rval = JSVAL_VOID; OOJSResumeTimeLimiter(); OK = JS_CallFunctionValue(context, jsThis, predicate, 1, args, &rval); OOJSPauseTimeLimiter(); if (OK) { if (JS_IsExceptionPending(context)) { JS_ReportPendingException(context); OK = NO; } } if (OK) { JSBool boolVal; if (JS_ValueToBoolean(context, rval, &boolVal) && boolVal) { [result addObject:info]; } } if (!OK) break; } if (OK) { OOJS_SET_RVAL([result oo_jsValueInContext:context]); } else { OOJS_SET_RVAL(JSVAL_VOID); } [pool release]; OOJSResumeTimeLimiter(); return OK; OOJS_NATIVE_EXIT } static JSBool SystemInfoStaticSetInterstellarProperty(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) NSString *property = nil; id value = nil; NSString *manifest = nil; int32 iValue; OOGalaxyID g; OOSystemID s1,s2; if (argc < 6) { OOJSReportBadArguments(context, @"SystemInfo", @"setInterstellarProperty", MIN(argc, 3U), OOJS_ARGV, NULL, @"setProperty(galaxy, fromsystem, tosystem, layer, property, value [,manifest])"); return NO; } if (!JS_ValueToInt32(context, OOJS_ARGV[0], &iValue)) { OOJSReportBadArguments(context, @"SystemInfo", @"setInterstellarProperty", MIN(argc, 3U), OOJS_ARGV, NULL, @"setProperty(galaxy, fromsystem, tosystem, layer, property, value [,manifest])"); return NO; } if (iValue < 0 || iValue > kOOMaximumGalaxyID) { OOJSReportBadArguments(context, @"SystemInfo", @"setInterstellarProperty", MIN(argc, 3U), OOJS_ARGV, NULL, @"galaxy out of range"); return NO; } else { g = (OOGalaxyID)iValue; } if (!JS_ValueToInt32(context, OOJS_ARGV[1], &iValue)) { OOJSReportBadArguments(context, @"SystemInfo", @"setInterstellarProperty", MIN(argc, 3U), OOJS_ARGV, NULL, @"setProperty(galaxy, fromsystem, tosystem, layer, property, value [,manifest])"); return NO; } if (iValue < 0 || iValue > kOOMaximumSystemID) { OOJSReportBadArguments(context, @"SystemInfo", @"setInterstellarProperty", MIN(argc, 3U), OOJS_ARGV, NULL, @"fromsystem out of range"); return NO; } else { s1 = (OOSystemID)iValue; } if (!JS_ValueToInt32(context, OOJS_ARGV[2], &iValue)) { OOJSReportBadArguments(context, @"SystemInfo", @"setInterstellarProperty", MIN(argc, 3U), OOJS_ARGV, NULL, @"setProperty(galaxy, fromsystem, tosystem, layer, property, value [,manifest])"); return NO; } if (iValue < 0 || iValue > kOOMaximumSystemID) { OOJSReportBadArguments(context, @"SystemInfo", @"setInterstellarProperty", MIN(argc, 3U), OOJS_ARGV, NULL, @"tosystem out of range"); return NO; } else { s2 = (OOSystemID)iValue; } if (!JS_ValueToInt32(context, OOJS_ARGV[3], &iValue)) { OOJSReportBadArguments(context, @"SystemInfo", @"setInterstellarProperty", MIN(argc, 3U), OOJS_ARGV, NULL, @"setProperty(galaxy, fromsystem, tosystem, layer, property, value [,manifest])"); return NO; } if (iValue < 0 || iValue >= OO_SYSTEM_LAYERS) { OOJSReportBadArguments(context, @"SystemInfo", @"setInterstellarProperty", MIN(argc, 3U), OOJS_ARGV, NULL, @"layer must be 0, 1, 2 or 3"); return NO; } OOSystemLayer layer = (OOSystemLayer)iValue; property = OOStringFromJSValue(context, OOJS_ARGV[4]); if (!JSVAL_IS_NULL(OOJS_ARGV[5])) { value = OOJSNativeObjectFromJSValue(context, OOJS_ARGV[5]); } if (argc >= 7) { manifest = OOStringFromJSValue(context, OOJS_ARGV[6]); } else { manifest = [[OOJSScript currentlyRunningScript] propertyNamed:kLocalManifestProperty]; } NSString *key = [NSString stringWithFormat:@"interstellar: %u %u %u",g,s1,s2]; [[UNIVERSE systemManager] setProperty:property forSystemKey:key andLayer:layer toValue:value fromManifest:manifest]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } oolite-1.82/src/Core/Scripting/OOJSTimer.h000066400000000000000000000022331256642440500203140ustar00rootroot00000000000000/* OOJSTimer.h JavaScript timer class. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOScriptTimer.h" #import "OOJSScript.h" #include @interface OOJSTimer: OOScriptTimer { @private jsval _function; JSObject *_jsThis; // The object that is 'this' in the function call. OOJSScript *_owningScript; JSObject *_jsSelf; // The JS Timer object proxy for this OOJSTimer. } @end void InitOOJSTimer(JSContext *context, JSObject *global); oolite-1.82/src/Core/Scripting/OOJSTimer.m000066400000000000000000000257251256642440500203340ustar00rootroot00000000000000/* OOJSTimer.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSTimer.h" #import "OOJavaScriptEngine.h" #import "Universe.h" // Minimum allowable interval for repeating timers. #define kMinInterval 0.25 static JSObject *sTimerPrototype; static JSClass sTimerClass; @interface OOJSTimer (Private) - (id) initWithDelay:(OOTimeAbsolute)delay interval:(OOTimeDelta)interval context:(JSContext *)context function:(jsval)function this:(JSObject *)jsThis; @end @implementation OOJSTimer - (id) initWithDelay:(OOTimeAbsolute)delay interval:(OOTimeDelta)interval context:(JSContext *)context function:(jsval)function this:(JSObject *)jsThis { self = [super initWithNextTime:[UNIVERSE getTime] + delay interval:interval]; if (self != nil) { NSAssert(OOJSValueIsFunction(context, function), @"Attempt to init OOJSTimer with a function that isn't."); _jsThis = jsThis; OOJSAddGCObjectRoot(context, &_jsThis, "OOJSTimer this"); _function = function; OOJSAddGCValueRoot(context, &_function, "OOJSTimer function"); _jsSelf = JS_NewObject(context, &sTimerClass, sTimerPrototype, NULL); if (_jsSelf != NULL) { if (!JS_SetPrivate(context, _jsSelf, [self retain])) _jsSelf = NULL; } if (_jsSelf == NULL) { [self release]; return nil; } _owningScript = [[OOJSScript currentlyRunningScript] weakRetain]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deleteJSPointers) name:kOOJavaScriptEngineWillResetNotification object:[OOJavaScriptEngine sharedEngine]]; } return self; } - (void) deleteJSPointers { [self unscheduleTimer]; if (_jsThis != NULL) { _jsThis = NULL; _function = JSVAL_VOID; JSContext *context = OOJSAcquireContext(); JS_RemoveObjectRoot(context, &_jsThis); JS_RemoveValueRoot(context, &_function); OOJSRelinquishContext(context); [[NSNotificationCenter defaultCenter] removeObserver:self name:kOOJavaScriptEngineWillResetNotification object:[OOJavaScriptEngine sharedEngine]]; } } - (void) dealloc { [_owningScript release]; [self deleteJSPointers]; [super dealloc]; } - (NSString *) descriptionComponents { NSString *funcName = nil; JSContext *context = NULL; if (JSVAL_IS_VOID(_function) || JSVAL_IS_NULL(_function)) { return @"invalid"; } context = OOJSAcquireContext(); funcName = OOStringFromJSString(context, JS_GetFunctionId(JS_ValueToFunction(context, _function))); OOJSRelinquishContext(context); if (funcName == nil) { funcName = @"anonymous"; } return [NSString stringWithFormat:@"%@, function: %@", [super descriptionComponents], funcName]; } - (NSString *) oo_jsClassName { return @"Timer"; } - (void) timerFired { jsval rval = JSVAL_VOID; NSString *description = nil; OOJavaScriptEngine *engine = [OOJavaScriptEngine sharedEngine]; JSContext *context = OOJSAcquireContext(); // stop and remove the timer if _jsThis (the first parameter in the constructor) dies. id object = OOJSNativeObjectFromJSObject(context, _jsThis); if (object != nil) { description = [object oo_jsDescription]; if (description == nil) description = [object description]; } if (description == nil) { [self unscheduleTimer]; OOJSRelinquishContext(context); return; } [OOJSScript pushScript:_owningScript]; [engine callJSFunction:_function forObject:_jsThis argc:0 argv:NULL result:&rval]; [OOJSScript popScript:_owningScript]; OOJSRelinquishContext(context); } - (jsval) oo_jsValueInContext:(JSContext *)context { return OBJECT_TO_JSVAL(_jsSelf); } @end static JSBool TimerGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool TimerSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); static void TimerFinalize(JSContext *context, JSObject *this); static JSBool TimerConstruct(JSContext *context, uintN argc, jsval *vp); // Methods static JSBool TimerStart(JSContext *context, uintN argc, jsval *vp); static JSBool TimerStop(JSContext *context, uintN argc, jsval *vp); static JSClass sTimerClass = { "Timer", JSCLASS_HAS_PRIVATE, JS_PropertyStub, // addProperty JS_PropertyStub, // delProperty TimerGetProperty, // getProperty TimerSetProperty, // setProperty JS_EnumerateStub, // enumerate JS_ResolveStub, // resolve JS_ConvertStub, // convert TimerFinalize, // finalize JSCLASS_NO_OPTIONAL_MEMBERS }; enum { // Property IDs kTimer_nextTime, // next fire time, double, read/write kTimer_interval, // interval, double, read/write kTimer_isRunning // is scheduled, boolean, read-only }; static JSPropertySpec sTimerProperties[] = { // JS name ID flags { "interval", kTimer_interval, OOJS_PROP_READWRITE_CB }, { "isRunning", kTimer_isRunning, OOJS_PROP_READONLY_CB }, { "nextTime", kTimer_nextTime, OOJS_PROP_READWRITE_CB }, { 0 } }; static JSFunctionSpec sTimerMethods[] = { // JS name Function min args { "toString", OOJSObjectWrapperToString, 0 }, { "start", TimerStart, 0 }, { "stop", TimerStop, 0 }, { 0 } }; DEFINE_JS_OBJECT_GETTER(JSTimerGetTimer, &sTimerClass, sTimerPrototype, OOJSTimer); void InitOOJSTimer(JSContext *context, JSObject *global) { sTimerPrototype = JS_InitClass(context, global, NULL, &sTimerClass, TimerConstruct, 0, sTimerProperties, sTimerMethods, NULL, NULL); OOJSRegisterObjectConverter(&sTimerClass, OOJSBasicPrivateObjectConverter); } static JSBool TimerGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) OOJSTimer *timer = nil; if (EXPECT_NOT(!JSTimerGetTimer(context, this, &timer))) return NO; switch (JSID_TO_INT(propID)) { case kTimer_nextTime: return JS_NewNumberValue(context, [timer nextTime], value); case kTimer_interval: return JS_NewNumberValue(context, [timer interval], value); case kTimer_isRunning: *value = OOJSValueFromBOOL([timer isScheduled]); return YES; default: OOJSReportBadPropertySelector(context, this, propID, sTimerProperties); return NO; } OOJS_NATIVE_EXIT } static JSBool TimerSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) OOJSTimer *timer = nil; double fValue; if (EXPECT_NOT(!JSTimerGetTimer(context, this, &timer))) return NO; switch (JSID_TO_INT(propID)) { case kTimer_nextTime: if (JS_ValueToNumber(context, *value, &fValue)) { if (![timer setNextTime:fValue]) { OOJSReportWarning(context, @"Ignoring attempt to change next fire time for running timer %@.", timer); } return YES; } break; case kTimer_interval: if (JS_ValueToNumber(context, *value, &fValue)) { [timer setInterval:fValue]; return YES; } break; default: OOJSReportBadPropertySelector(context, this, propID, sTimerProperties); return NO; } OOJSReportBadPropertyValue(context, this, propID, sTimerProperties, *value); return NO; OOJS_NATIVE_EXIT } static void TimerFinalize(JSContext *context, JSObject *this) { OOJS_PROFILE_ENTER // Can't use JSTimerGetTimer() here - potential chicken-and-egg problem manifesting as a crash. OOJSTimer *timer = (OOJSTimer *)JS_GetPrivate(context, this); if (timer != nil) { if ([timer isScheduled]) { OOLogWARN(@"script.javaScript.unrootedTimer", @"Timer %@ is being garbage-collected while still running. You must keep a reference to all running timers, or they will stop unpredictably!", timer); } [timer release]; JS_SetPrivate(context, this, NULL); } OOJS_PROFILE_EXIT_VOID } // new Timer(this : Object, function : Function, delay : Number [, interval : Number]) : Timer static JSBool TimerConstruct(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) jsval function = JSVAL_VOID; double delay; double interval = -1.0; OOJSTimer *timer = nil; JSObject *callbackThis = NULL; if (EXPECT_NOT(!JS_IsConstructing(context, vp))) { OOJSReportError(context, @"Timer() cannot be called as a function, it must be used as a constructor (as in new Timer(...))."); return NO; } if (argc < 3) { OOJSReportBadArguments(context, nil, @"Timer", argc, OOJS_ARGV, @"Invalid arguments in constructor", @"(object, function, number [, number])"); return NO; } if (!JSVAL_IS_NULL(OOJS_ARGV[0]) && !JSVAL_IS_VOID(OOJS_ARGV[0])) { if (!JS_ValueToObject(context, OOJS_ARGV[0], &callbackThis)) { OOJSReportBadArguments(context, nil, @"Timer", 1, OOJS_ARGV, @"Invalid argument in constructor", @"object"); return NO; } } function = OOJS_ARGV[1]; if (JS_ValueToFunction(context, function) == NULL) { OOJSReportBadArguments(context, nil, @"Timer", 1, OOJS_ARGV + 1, @"Invalid argument in constructor", @"function"); return NO; } if (!JS_ValueToNumber(context, OOJS_ARGV[2], &delay) || isnan(delay)) { OOJSReportBadArguments(context, nil, @"Timer", 1, OOJS_ARGV + 2, @"Invalid argument in constructor", @"number"); return NO; } // Fourth argument is optional. if (3 < argc && !JS_ValueToNumber(context, OOJS_ARGV[3], &interval)) interval = -1; // Ensure interval is not too small. if (0.0 < interval && interval < kMinInterval) interval = kMinInterval; timer = [[OOJSTimer alloc] initWithDelay:delay interval:interval context:context function:function this:callbackThis]; if (EXPECT_NOT(!timer)) return NO; if (delay >= 0) // Leave in stopped state if delay is negative { [timer scheduleTimer]; } [timer autorelease]; OOJS_RETURN_OBJECT(timer); OOJS_NATIVE_EXIT } // *** Methods *** // start() : Boolean static JSBool TimerStart(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOJSTimer *thisTimer = nil; if (EXPECT_NOT(!JSTimerGetTimer(context, OOJS_THIS, &thisTimer))) return NO; OOJS_RETURN_BOOL([thisTimer scheduleTimer]); OOJS_NATIVE_EXIT } // stop() static JSBool TimerStop(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOJSTimer *thisTimer = nil; if (EXPECT_NOT(!JSTimerGetTimer(context, OOJS_THIS, &thisTimer))) return NO; [thisTimer unscheduleTimer]; OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } oolite-1.82/src/Core/Scripting/OOJSVector.h000066400000000000000000000061741256642440500205060ustar00rootroot00000000000000/* OOJSVector.h JavaScript proxy for vectors. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #include #import "OOMaths.h" void InitOOJSVector(JSContext *context, JSObject *global); JSObject *JSVectorWithVector(JSContext *context, Vector vector) NONNULL_FUNC; JSObject *JSVectorWithHPVector(JSContext *context, HPVector vector) NONNULL_FUNC; BOOL VectorToJSValue(JSContext *context, Vector vector, jsval *outValue) NONNULL_FUNC; BOOL HPVectorToJSValue(JSContext *context, HPVector vector, jsval *outValue) NONNULL_FUNC; BOOL NSPointToVectorJSValue(JSContext *context, NSPoint point, jsval *outValue) NONNULL_FUNC; BOOL JSValueToVector(JSContext *context, jsval value, Vector *outVector) NONNULL_FUNC; BOOL JSValueToHPVector(JSContext *context, jsval value, HPVector *outVector) NONNULL_FUNC; /* Given a JS Vector object, get the corresponding Vector struct. Given a JS Entity, get its position. Given a JS Array with exactly three elements, all of them numbers, treat them as [x, y, z] components. For anything else, return NO. (Other implicit conversions may be added in future.) */ BOOL JSObjectGetVector(JSContext *context, JSObject *vectorObj, HPVector *outVector) GCC_ATTR((nonnull (1, 3))); // Set the value of a JS vector object. BOOL JSVectorSetVector(JSContext *context, JSObject *vectorObj, Vector vector) GCC_ATTR((nonnull (1))); BOOL JSVectorSetHPVector(JSContext *context, JSObject *vectorObj, HPVector vector) GCC_ATTR((nonnull (1))); /* VectorFromArgumentList() Construct a vector from an argument list which is either a (JS) vector, a (JS) entity, or an array of three numbers. The optional outConsumed argument can be used to find out how many parameters were used (currently, this will be 0 on failure, otherwise 1). On failure, it will return NO and raise an error. If the caller is a JS callback, it must return NO to signal an error. DEPRECATED in favour of JSObjectGetVector(), since the list-of-number form is no longer used. */ BOOL VectorFromArgumentList(JSContext *context, NSString *scriptClass, NSString *function, uintN argc, jsval *argv, HPVector *outVector, uintN *outConsumed) GCC_ATTR((nonnull (1, 5, 6))); /* VectorFromArgumentListNoError() Like VectorFromArgumentList(), but does not report an error on failure. */ BOOL VectorFromArgumentListNoError(JSContext *context, uintN argc, jsval *argv, HPVector *outVector, uintN *outConsumed) GCC_ATTR((nonnull (1, 3, 4))); oolite-1.82/src/Core/Scripting/OOJSVector.m000066400000000000000000000735771256642440500205260ustar00rootroot00000000000000/* OOJSVector.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSVector.h" #import "OOJavaScriptEngine.h" #if OOLITE_GNUSTEP #import #else #import #endif #import "OOConstToString.h" #import "OOJSEntity.h" #import "OOJSQuaternion.h" static JSObject *sVectorPrototype; static BOOL GetThisVector(JSContext *context, JSObject *vectorObj, HPVector *outVector, NSString *method) NONNULL_FUNC; static JSBool VectorGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool VectorSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); static void VectorFinalize(JSContext *context, JSObject *this); static JSBool VectorConstruct(JSContext *context, uintN argc, jsval *vp); // Methods static JSBool VectorToString(JSContext *context, uintN argc, jsval *vp); static JSBool VectorToSource(JSContext *context, uintN argc, jsval *vp); static JSBool VectorAdd(JSContext *context, uintN argc, jsval *vp); static JSBool VectorSubtract(JSContext *context, uintN argc, jsval *vp); static JSBool VectorDistanceTo(JSContext *context, uintN argc, jsval *vp); static JSBool VectorSquaredDistanceTo(JSContext *context, uintN argc, jsval *vp); static JSBool VectorMultiply(JSContext *context, uintN argc, jsval *vp); static JSBool VectorDot(JSContext *context, uintN argc, jsval *vp); static JSBool VectorAngleTo(JSContext *context, uintN argc, jsval *vp); static JSBool VectorFromCoordinateSystem(JSContext *context, uintN argc, jsval *vp); static JSBool VectorToCoordinateSystem(JSContext *context, uintN argc, jsval *vp); static JSBool VectorCross(JSContext *context, uintN argc, jsval *vp); static JSBool VectorTripleProduct(JSContext *context, uintN argc, jsval *vp); static JSBool VectorDirection(JSContext *context, uintN argc, jsval *vp); static JSBool VectorMagnitude(JSContext *context, uintN argc, jsval *vp); static JSBool VectorSquaredMagnitude(JSContext *context, uintN argc, jsval *vp); static JSBool VectorRotationTo(JSContext *context, uintN argc, jsval *vp); static JSBool VectorRotateBy(JSContext *context, uintN argc, jsval *vp); static JSBool VectorToArray(JSContext *context, uintN argc, jsval *vp); // Static methods static JSBool VectorStaticInterpolate(JSContext *context, uintN argc, jsval *vp); static JSBool VectorStaticRandom(JSContext *context, uintN argc, jsval *vp); static JSBool VectorStaticRandomDirection(JSContext *context, uintN argc, jsval *vp); static JSBool VectorStaticRandomDirectionAndLength(JSContext *context, uintN argc, jsval *vp); static JSClass sVectorClass = { "Vector3D", JSCLASS_HAS_PRIVATE, JS_PropertyStub, // addProperty JS_PropertyStub, // delProperty VectorGetProperty, // getProperty VectorSetProperty, // setProperty JS_EnumerateStub, // enumerate JS_ResolveStub, // resolve JS_ConvertStub, // convert VectorFinalize, // finalize JSCLASS_NO_OPTIONAL_MEMBERS }; enum { // Property IDs kVector_x, kVector_y, kVector_z }; static JSPropertySpec sVectorProperties[] = { // JS name ID flags { "x", kVector_x, OOJS_PROP_READWRITE_CB }, { "y", kVector_y, OOJS_PROP_READWRITE_CB }, { "z", kVector_z, OOJS_PROP_READWRITE_CB }, { 0 } }; static JSFunctionSpec sVectorMethods[] = { // JS name Function min args { "toSource", VectorToSource, 0, }, { "toString", VectorToString, 0, }, { "add", VectorAdd, 1, }, { "angleTo", VectorAngleTo, 1, }, { "cross", VectorCross, 1, }, { "direction", VectorDirection, 0, }, { "distanceTo", VectorDistanceTo, 1, }, { "dot", VectorDot, 1, }, { "fromCoordinateSystem", VectorFromCoordinateSystem, 1, }, { "magnitude", VectorMagnitude, 0, }, { "multiply", VectorMultiply, 1, }, { "rotateBy", VectorRotateBy, 1, }, { "rotationTo", VectorRotationTo, 1, }, { "squaredDistanceTo", VectorSquaredDistanceTo, 1, }, { "squaredMagnitude", VectorSquaredMagnitude, 0, }, { "subtract", VectorSubtract, 1, }, { "toArray", VectorToArray, 0, }, { "toCoordinateSystem", VectorToCoordinateSystem, 1, }, { "tripleProduct", VectorTripleProduct, 2, }, { 0 } }; static JSFunctionSpec sVectorStaticMethods[] = { // JS name Function min args { "interpolate", VectorStaticInterpolate, 3, }, { "random", VectorStaticRandom, 0, }, { "randomDirection", VectorStaticRandomDirection, 0, }, { "randomDirectionAndLength", VectorStaticRandomDirectionAndLength, 0, }, { 0 } }; // *** Public *** void InitOOJSVector(JSContext *context, JSObject *global) { sVectorPrototype = JS_InitClass(context, global, NULL, &sVectorClass, VectorConstruct, 0, sVectorProperties, sVectorMethods, NULL, sVectorStaticMethods); } JSObject *JSVectorWithVector(JSContext *context, Vector vector) { OOJS_PROFILE_ENTER JSObject *result = NULL; HPVector *private = NULL; private = malloc(sizeof *private); if (EXPECT_NOT(private == NULL)) return NULL; *private = vectorToHPVector(vector); result = JS_NewObject(context, &sVectorClass, sVectorPrototype, NULL); if (result != NULL) { if (EXPECT_NOT(!JS_SetPrivate(context, result, private))) result = NULL; } if (EXPECT_NOT(result == NULL)) free(private); return result; OOJS_PROFILE_EXIT } BOOL VectorToJSValue(JSContext *context, Vector vector, jsval *outValue) { OOJS_PROFILE_ENTER JSObject *object = NULL; assert(outValue != NULL); object = JSVectorWithVector(context, vector); if (EXPECT_NOT(object == NULL)) return NO; *outValue = OBJECT_TO_JSVAL(object); return YES; OOJS_PROFILE_EXIT } JSObject *JSVectorWithHPVector(JSContext *context, HPVector vector) { OOJS_PROFILE_ENTER JSObject *result = NULL; HPVector *private = NULL; private = malloc(sizeof *private); if (EXPECT_NOT(private == NULL)) return NULL; *private = vector; result = JS_NewObject(context, &sVectorClass, sVectorPrototype, NULL); if (result != NULL) { if (EXPECT_NOT(!JS_SetPrivate(context, result, private))) result = NULL; } if (EXPECT_NOT(result == NULL)) free(private); return result; OOJS_PROFILE_EXIT } BOOL HPVectorToJSValue(JSContext *context, HPVector vector, jsval *outValue) { OOJS_PROFILE_ENTER JSObject *object = NULL; assert(outValue != NULL); object = JSVectorWithHPVector(context, vector); if (EXPECT_NOT(object == NULL)) return NO; *outValue = OBJECT_TO_JSVAL(object); return YES; OOJS_PROFILE_EXIT } BOOL NSPointToVectorJSValue(JSContext *context, NSPoint point, jsval *outValue) { return VectorToJSValue(context, make_vector(point.x, point.y, 0), outValue); } BOOL JSValueToHPVector(JSContext *context, jsval value, HPVector *outVector) { if (EXPECT_NOT(!JSVAL_IS_OBJECT(value))) return NO; return JSObjectGetVector(context, JSVAL_TO_OBJECT(value), outVector); } BOOL JSValueToVector(JSContext *context, jsval value, Vector *outVector) { if (EXPECT_NOT(!JSVAL_IS_OBJECT(value))) return NO; HPVector tmp = kZeroHPVector; BOOL result = JSObjectGetVector(context, JSVAL_TO_OBJECT(value), &tmp); *outVector = HPVectorToVector(tmp); return result; } #if OO_DEBUG typedef struct { NSUInteger vectorCount; NSUInteger entityCount; NSUInteger arrayCount; NSUInteger protoCount; NSUInteger nullCount; NSUInteger failCount; } VectorStatistics; static VectorStatistics sVectorConversionStats; @implementation PlayerEntity (JSVectorStatistics) // :setM vectorStats PS.callObjC("reportJSVectorStatistics") // :vectorStats - (NSString *) reportJSVectorStatistics { VectorStatistics *stats = &sVectorConversionStats; NSUInteger sum = stats->vectorCount + stats->entityCount + stats->arrayCount + stats->protoCount; double convFac = 100.0 / sum; if (sum == 0) convFac = 0; return [NSString stringWithFormat: @" vector-to-vector conversions: %lu (%g %%)\n" " entity-to-vector conversions: %lu (%g %%)\n" " array-to-vector conversions: %lu (%g %%)\n" "prototype-to-zero conversions: %lu (%g %%)\n" " null conversions: %lu (%g %%)\n" " failed conversions: %lu (%g %%)\n" " total: %lu", (long)stats->vectorCount, stats->vectorCount * convFac, (long)stats->entityCount, stats->entityCount * convFac, (long)stats->arrayCount, stats->arrayCount * convFac, (long)stats->protoCount, stats->protoCount * convFac, (long)stats->nullCount, stats->nullCount * convFac, (long)stats->failCount, stats->failCount * convFac, (long)sum]; } - (void) clearJSVectorStatistics { memset(&sVectorConversionStats, 0, sizeof sVectorConversionStats); } @end #define COUNT(FIELD) do { sVectorConversionStats.FIELD++; } while (0) #else #define COUNT(FIELD) do {} while (0) #endif BOOL JSObjectGetVector(JSContext *context, JSObject *vectorObj, HPVector *outVector) { OOJS_PROFILE_ENTER assert(outVector != NULL); HPVector *private = NULL; jsuint arrayLength; jsval arrayX, arrayY, arrayZ; jsdouble x, y, z; // vectorObj can legitimately be NULL, e.g. when JS_NULL is converted to a JSObject *. if (EXPECT_NOT(vectorObj == NULL)) { COUNT(nullCount); return NO; } // If this is a (JS) Vector... private = JS_GetInstancePrivate(context, vectorObj, &sVectorClass, NULL); if (EXPECT(private != NULL)) { COUNT(vectorCount); *outVector = *private; return YES; } // If it's an array... if (EXPECT(JS_IsArrayObject(context, vectorObj))) { // ...and it has exactly three elements... if (JS_GetArrayLength(context, vectorObj, &arrayLength) && arrayLength == 3) { if (JS_LookupElement(context, vectorObj, 0, &arrayX) && JS_LookupElement(context, vectorObj, 1, &arrayY) && JS_LookupElement(context, vectorObj, 2, &arrayZ)) { // ...use the three numbers as [x, y, z] if (JS_ValueToNumber(context, arrayX, &x) && JS_ValueToNumber(context, arrayY, &y) && JS_ValueToNumber(context, arrayZ, &z)) { COUNT(arrayCount); *outVector = make_HPvector(x, y, z); return YES; } } } } // If it's an entity, use its position. if (OOJSIsMemberOfSubclass(context, vectorObj, JSEntityClass())) { COUNT(entityCount); Entity *entity = [(id)JS_GetPrivate(context, vectorObj) weakRefUnderlyingObject]; *outVector = [entity position]; return YES; } /* If it's actually a Vector3D but with no private field (this happens for Vector3D.prototype)... NOTE: it would be prettier to do this at the top when we handle normal Vector3Ds, but it's a rare case which should be kept off the fast path. */ if (JS_InstanceOf(context, vectorObj, &sVectorClass, NULL)) { COUNT(protoCount); *outVector = kZeroHPVector; return YES; } COUNT(failCount); return NO; OOJS_PROFILE_EXIT } static BOOL GetThisVector(JSContext *context, JSObject *vectorObj, HPVector *outVector, NSString *method) { if (EXPECT(JSObjectGetVector(context, vectorObj, outVector))) return YES; jsval arg = OBJECT_TO_JSVAL(vectorObj); OOJSReportBadArguments(context, @"Vector3D", method, 1, &arg, @"Invalid target object", @"Vector3D"); return NO; } BOOL JSVectorSetVector(JSContext *context, JSObject *vectorObj, Vector vector) { return JSVectorSetHPVector(context,vectorObj,vectorToHPVector(vector)); } BOOL JSVectorSetHPVector(JSContext *context, JSObject *vectorObj, HPVector vector) { OOJS_PROFILE_ENTER HPVector *private = NULL; if (EXPECT_NOT(vectorObj == NULL)) return NO; private = JS_GetInstancePrivate(context, vectorObj, &sVectorClass, NULL); if (private != NULL) // If this is a (JS) Vector... { *private = vector; return YES; } if (JS_InstanceOf(context, vectorObj, &sVectorClass, NULL)) { // Silently fail for the prototype. return YES; } return NO; OOJS_PROFILE_EXIT } static BOOL VectorFromArgumentListNoErrorInternal(JSContext *context, uintN argc, jsval *argv, HPVector *outVector, uintN *outConsumed, BOOL permitNumberList) { OOJS_PROFILE_ENTER double x, y, z; if (EXPECT_NOT(argc == 0)) return NO; assert(argv != NULL && outVector != NULL); if (outConsumed != NULL) *outConsumed = 0; // Is first object a vector, array or entity? if (JSVAL_IS_OBJECT(argv[0])) { if (JSObjectGetVector(context, JSVAL_TO_OBJECT(argv[0]), outVector)) { if (outConsumed != NULL) *outConsumed = 1; return YES; } } if (!permitNumberList) return NO; // As a special case for VectorConstruct(), look for three numbers. if (argc < 3) return NO; // Given a string, JS_ValueToNumber() returns YES but provides a NaN number. if (EXPECT_NOT(!JS_ValueToNumber(context, argv[0], &x) || isnan(x))) return NO; if (EXPECT_NOT(!JS_ValueToNumber(context, argv[1], &y) || isnan(y))) return NO; if (EXPECT_NOT(!JS_ValueToNumber(context, argv[2], &z) || isnan(z))) return NO; // We got our three numbers. *outVector = make_HPvector(x, y, z); if (outConsumed != NULL) *outConsumed = 3; return YES; OOJS_PROFILE_EXIT } // EMMSTRAN: remove outConsumed, since it can only be 1 except in failure (constructor is an exception, but it uses VectorFromArgumentListNoErrorInternal() directly). BOOL VectorFromArgumentList(JSContext *context, NSString *scriptClass, NSString *function, uintN argc, jsval *argv, HPVector *outVector, uintN *outConsumed) { if (VectorFromArgumentListNoErrorInternal(context, argc, argv, outVector, outConsumed, NO)) return YES; else { OOJSReportBadArguments(context, scriptClass, function, argc, argv, @"Could not construct vector from parameters", @"Vector, Entity or array of three numbers"); return NO; } } BOOL VectorFromArgumentListNoError(JSContext *context, uintN argc, jsval *argv, HPVector *outVector, uintN *outConsumed) { return VectorFromArgumentListNoErrorInternal(context, argc, argv, outVector, outConsumed, NO); } // *** Implementation stuff *** static JSBool VectorGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_PROFILE_ENTER HPVector vector; OOHPScalar fValue; if (EXPECT_NOT(!JSObjectGetVector(context, this, &vector))) return NO; switch (JSID_TO_INT(propID)) { case kVector_x: fValue = vector.x; break; case kVector_y: fValue = vector.y; break; case kVector_z: fValue = vector.z; break; default: OOJSReportBadPropertySelector(context, this, propID, sVectorProperties); return NO; } return JS_NewNumberValue(context, fValue, value); OOJS_PROFILE_EXIT } static JSBool VectorSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_PROFILE_ENTER HPVector vector; jsdouble dval; if (EXPECT_NOT(!JSObjectGetVector(context, this, &vector))) return NO; if (EXPECT_NOT(!JS_ValueToNumber(context, *value, &dval))) { OOJSReportBadPropertyValue(context, this, propID, sVectorProperties, *value); return NO; } switch (JSID_TO_INT(propID)) { case kVector_x: vector.x = dval; break; case kVector_y: vector.y = dval; break; case kVector_z: vector.z = dval; break; default: OOJSReportBadPropertySelector(context, this, propID, sVectorProperties); return NO; } return JSVectorSetHPVector(context, this, vector); OOJS_PROFILE_EXIT } static void VectorFinalize(JSContext *context, JSObject *this) { OOJS_PROFILE_ENTER Vector *private = NULL; private = JS_GetInstancePrivate(context, this, &sVectorClass, NULL); if (private != NULL) { free(private); } OOJS_PROFILE_EXIT_VOID } static JSBool VectorConstruct(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER HPVector vector = kZeroHPVector; HPVector *private = NULL; JSObject *this = NULL; private = malloc(sizeof *private); if (EXPECT_NOT(private == NULL)) return NO; this = JS_NewObject(context, &sVectorClass, NULL, NULL); if (EXPECT_NOT(this == NULL)) return NO; if (argc != 0) { if (EXPECT_NOT(!VectorFromArgumentListNoErrorInternal(context, argc, OOJS_ARGV, &vector, NULL, YES))) { free(private); OOJSReportBadArguments(context, NULL, NULL, argc, OOJS_ARGV, @"Could not construct vector from parameters", @"Vector, Entity or array of three numbers"); return NO; } } *private = vector; if (EXPECT_NOT(!JS_SetPrivate(context, this, private))) { free(private); return NO; } OOJS_RETURN_JSOBJECT(this); OOJS_PROFILE_EXIT } // *** Methods *** // toString() : String static JSBool VectorToString(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) HPVector thisv; if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"toString"))) return NO; OOJS_RETURN_OBJECT(HPVectorDescription(thisv)); OOJS_NATIVE_EXIT } // toSource() : String static JSBool VectorToSource(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) HPVector thisv; if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"toSource"))) return NO; NSString *str = [NSString stringWithFormat:@"Vector3D(%g, %g, %g)", thisv.x, thisv.y, thisv.z]; OOJS_RETURN_OBJECT(str); OOJS_NATIVE_EXIT } // add(v : vectorExpression) : Vector3D static JSBool VectorAdd(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER HPVector thisv, thatv, result; if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"add"))) return NO; if (EXPECT_NOT(!VectorFromArgumentList(context, @"Vector3D", @"add", argc, OOJS_ARGV, &thatv, NULL))) return NO; result = HPvector_add(thisv, thatv); OOJS_RETURN_HPVECTOR(result); OOJS_PROFILE_EXIT } // subtract(v : vectorExpression) : Vector3D static JSBool VectorSubtract(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER HPVector thisv, thatv, result; if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"subtract"))) return NO; if (EXPECT_NOT(!VectorFromArgumentList(context, @"Vector3D", @"subtract", argc, OOJS_ARGV, &thatv, NULL))) return NO; result = HPvector_subtract(thisv, thatv); OOJS_RETURN_HPVECTOR(result); OOJS_PROFILE_EXIT } // distanceTo(v : vectorExpression) : Number static JSBool VectorDistanceTo(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER HPVector thisv, thatv; GLfloat result; if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"distanceTo"))) return NO; if (EXPECT_NOT(!VectorFromArgumentList(context, @"Vector3D", @"distanceTo", argc, OOJS_ARGV, &thatv, NULL))) return NO; result = HPdistance(thisv, thatv); OOJS_RETURN_DOUBLE(result); OOJS_PROFILE_EXIT } // squaredDistanceTo(v : vectorExpression) : Number static JSBool VectorSquaredDistanceTo(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER HPVector thisv, thatv; GLfloat result; if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"squaredDistanceTo"))) return NO; if (EXPECT_NOT(!VectorFromArgumentList(context, @"Vector3D", @"squaredDistanceTo", argc, OOJS_ARGV, &thatv, NULL))) return NO; result = HPdistance2(thisv, thatv); OOJS_RETURN_DOUBLE(result); OOJS_PROFILE_EXIT } // multiply(n : Number) : Vector3D static JSBool VectorMultiply(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER HPVector thisv, result; double scalar; if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"multiply"))) return NO; if (EXPECT_NOT(!OOJSArgumentListGetNumber(context, @"Vector3D", @"multiply", argc, OOJS_ARGV, &scalar, NULL))) return NO; result = HPvector_multiply_scalar(thisv, scalar); OOJS_RETURN_HPVECTOR(result); OOJS_PROFILE_EXIT } // dot(v : vectorExpression) : Number static JSBool VectorDot(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER HPVector thisv, thatv; GLfloat result; if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"dot"))) return NO; if (EXPECT_NOT(!VectorFromArgumentList(context, @"Vector3D", @"dot", argc, OOJS_ARGV, &thatv, NULL))) return NO; result = HPdot_product(thisv, thatv); OOJS_RETURN_DOUBLE(result); OOJS_PROFILE_EXIT } // angleTo(v : vectorExpression) : Number static JSBool VectorAngleTo(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER HPVector thisv, thatv; GLfloat result; if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"angleTo"))) return NO; if (EXPECT_NOT(!VectorFromArgumentList(context, @"Vector3D", @"angleTo", argc, OOJS_ARGV, &thatv, NULL))) return NO; result = HPdot_product(HPvector_normal(thisv), HPvector_normal(thatv)); if (result > 1.0f) result = 1.0f; if (result < -1.0f) result = -1.0f; // for identical vectors the dot_product sometimes returnes a value > 1.0 because of rounding errors, resulting // in an undefined result for the acos. result = acos(result); OOJS_RETURN_DOUBLE(result); OOJS_PROFILE_EXIT } // cross(v : vectorExpression) : Vector3D static JSBool VectorCross(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER HPVector thisv, thatv, result; if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"cross"))) return NO; if (EXPECT_NOT(!VectorFromArgumentList(context, @"Vector3D", @"cross", argc, OOJS_ARGV, &thatv, NULL))) return NO; result = HPtrue_cross_product(thisv, thatv); OOJS_RETURN_HPVECTOR(result); OOJS_PROFILE_EXIT } // tripleProduct(v : vectorExpression, u : vectorExpression) : Number static JSBool VectorTripleProduct(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER HPVector thisv, thatv, theotherv; GLfloat result; uintN consumed; jsval *argv = OOJS_ARGV; if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"tripleProduct"))) return NO; if (EXPECT_NOT(!VectorFromArgumentList(context, @"Vector3D", @"tripleProduct", argc, argv, &thatv, &consumed))) return NO; argc -= consumed; argv += consumed; if (EXPECT_NOT(!VectorFromArgumentList(context, @"Vector3D", @"tripleProduct", argc, argv, &theotherv, NULL))) return NO; result = HPtriple_product(thisv, thatv, theotherv); OOJS_RETURN_DOUBLE(result); OOJS_PROFILE_EXIT } // direction() : Vector3D static JSBool VectorDirection(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER HPVector thisv, result; if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"direction"))) return NO; result = HPvector_normal(thisv); OOJS_RETURN_HPVECTOR(result); OOJS_PROFILE_EXIT } // magnitude() : Number static JSBool VectorMagnitude(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER HPVector thisv; GLfloat result; if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"magnitude"))) return NO; result = HPmagnitude(thisv); OOJS_RETURN_DOUBLE(result); OOJS_PROFILE_EXIT } // squaredMagnitude() : Number static JSBool VectorSquaredMagnitude(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER HPVector thisv; GLfloat result; if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"squaredMagnitude"))) return NO; result = HPmagnitude2(thisv); OOJS_RETURN_DOUBLE(result); OOJS_PROFILE_EXIT } // rotationTo(v : vectorExpression [, limit : Number]) : Quaternion static JSBool VectorRotationTo(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER HPVector thisv, thatv; double limit; BOOL gotLimit; Quaternion result; uintN consumed; jsval *argv = OOJS_ARGV; if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"rotationTo"))) return NO; if (EXPECT_NOT(!VectorFromArgumentList(context, @"Vector3D", @"rotationTo", argc, OOJS_ARGV, &thatv, &consumed))) return NO; argc -= consumed; argv += consumed; if (argc != 0) // limit parameter is optional. { if (EXPECT_NOT(!OOJSArgumentListGetNumber(context, @"Vector3D", @"rotationTo", argc, argv, &limit, NULL))) return NO; gotLimit = YES; } else gotLimit = NO; if (gotLimit) result = quaternion_limited_rotation_between(HPVectorToVector(thisv), HPVectorToVector(thatv), limit); else result = quaternion_rotation_between(HPVectorToVector(thisv), HPVectorToVector(thatv)); OOJS_RETURN_QUATERNION(result); OOJS_PROFILE_EXIT } // rotateBy(q : quaternionExpression) : Vector3D static JSBool VectorRotateBy(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER HPVector thisv, result; Quaternion q; if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"rotateBy"))) return NO; if (EXPECT_NOT(!QuaternionFromArgumentList(context, @"Vector3D", @"rotateBy", argc, OOJS_ARGV, &q, NULL))) return NO; result = quaternion_rotate_HPvector(q, thisv); OOJS_RETURN_HPVECTOR(result); OOJS_PROFILE_EXIT } // toArray() : Array static JSBool VectorToArray(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER HPVector thisv; JSObject *result = NULL; jsval nVal; if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"toArray"))) return NO; result = JS_NewArrayObject(context, 0, NULL); if (result != NULL) { // We do this at the top because the return value slot is a GC root. OOJS_SET_RVAL(OBJECT_TO_JSVAL(result)); if (JS_NewNumberValue(context, thisv.x, &nVal) && JS_SetElement(context, result, 0, &nVal) && JS_NewNumberValue(context, thisv.y, &nVal) && JS_SetElement(context, result, 1, &nVal) && JS_NewNumberValue(context, thisv.z, &nVal) && JS_SetElement(context, result, 2, &nVal)) { return YES; } // If we get here, the conversion and stuffing in the previous condition failed. OOJS_SET_RVAL(JSVAL_VOID); } return YES; OOJS_PROFILE_EXIT } // toCoordinateSystem(coordScheme : String) static JSBool VectorToCoordinateSystem(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) HPVector thisv; NSString *coordScheme = nil; HPVector result; if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"toCoordinateSystem"))) return NO; if (EXPECT_NOT(argc < 1 || (coordScheme = OOStringFromJSValue(context, OOJS_ARGV[0])) == nil)) { OOJSReportBadArguments(context, @"Vector3D", @"toCoordinateSystem", MIN(argc, 1U), OOJS_ARGV, nil, @"coordinate system"); return NO; } OOJS_BEGIN_FULL_NATIVE(context) result = [UNIVERSE legacyPositionFrom:thisv asCoordinateSystem:coordScheme]; OOJS_END_FULL_NATIVE OOJS_RETURN_HPVECTOR(result); OOJS_NATIVE_EXIT } // fromCoordinateSystem(coordScheme : String) static JSBool VectorFromCoordinateSystem(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) HPVector thisv; NSString *coordScheme = nil; HPVector result; if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"fromCoordinateSystem"))) return NO; if (EXPECT_NOT(argc < 1 || (coordScheme = OOStringFromJSValue(context, OOJS_ARGV[0])) == nil)) { OOJSReportBadArguments(context, @"Vector3D", @"fromCoordinateSystem", MIN(argc, 1U), OOJS_ARGV, nil, @"coordinate system"); return NO; } OOJS_BEGIN_FULL_NATIVE(context) NSString *arg = [NSString stringWithFormat:@"%@ %f %f %f", coordScheme, thisv.x, thisv.y, thisv.z]; result = [UNIVERSE coordinatesFromCoordinateSystemString:arg]; OOJS_END_FULL_NATIVE OOJS_RETURN_HPVECTOR(result); OOJS_NATIVE_EXIT } // *** Static methods *** // interpolate(v : Vector3D, u : Vector3D, alpha : Number) : Vector3D static JSBool VectorStaticInterpolate(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER HPVector av, bv; double interp; HPVector result; uintN consumed; uintN inArgc = argc; jsval *argv = OOJS_ARGV; jsval *inArgv = argv; if (EXPECT_NOT(argc < 3)) goto INSUFFICIENT_ARGUMENTS; if (EXPECT_NOT(!VectorFromArgumentList(context, @"Vector3D", @"interpolate", argc, argv, &av, &consumed))) return NO; argc -= consumed; argv += consumed; if (EXPECT_NOT(argc < 2)) goto INSUFFICIENT_ARGUMENTS; if (EXPECT_NOT(!VectorFromArgumentList(context, @"Vector3D", @"interpolate", argc, argv, &bv, &consumed))) return NO; argc -= consumed; argv += consumed; if (EXPECT_NOT(argc < 1)) goto INSUFFICIENT_ARGUMENTS; if (EXPECT_NOT(!OOJSArgumentListGetNumber(context, @"Vector3D", @"interpolate", argc, argv, &interp, NULL))) return NO; result = OOHPVectorInterpolate(av, bv, interp); OOJS_RETURN_HPVECTOR(result); INSUFFICIENT_ARGUMENTS: OOJSReportBadArguments(context, @"Vector3D", @"interpolate", inArgc, inArgv, @"Insufficient parameters", @"vector expression, vector expression and number"); return NO; OOJS_PROFILE_EXIT } // random([maxLength : Number]) : Vector3D static JSBool VectorStaticRandom(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER double maxLength; if (argc == 0 || !OOJSArgumentListGetNumberNoError(context, argc, OOJS_ARGV, &maxLength, NULL)) maxLength = 1.0; OOJS_RETURN_HPVECTOR(OOHPVectorRandomSpatial(maxLength)); OOJS_PROFILE_EXIT } // randomDirection([scale : Number]) : Vector3D static JSBool VectorStaticRandomDirection(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER double scale; if (argc == 0 || !OOJSArgumentListGetNumberNoError(context, argc, OOJS_ARGV, &scale, NULL)) scale = 1.0; OOJS_RETURN_HPVECTOR(HPvector_multiply_scalar(OORandomUnitHPVector(), scale)); OOJS_PROFILE_EXIT } // randomDirectionAndLength([maxLength : Number]) : Vector3D static JSBool VectorStaticRandomDirectionAndLength(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER double maxLength; if (argc == 0 || !OOJSArgumentListGetNumberNoError(context, argc, OOJS_ARGV, &maxLength, NULL)) maxLength = 1.0; OOJS_RETURN_HPVECTOR(OOHPVectorRandomRadial(maxLength)); OOJS_PROFILE_EXIT } oolite-1.82/src/Core/Scripting/OOJSVisualEffect.h000066400000000000000000000022751256642440500216220ustar00rootroot00000000000000/* OOJSVisualEffect.h JavaScript proxy for OOVisualEffectEntities. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #include @class OOVisualEffectEntity; void InitOOJSVisualEffect(JSContext *context, JSObject *global); @interface OOVisualEffectEntity (OOJavaScriptExtensions) - (void)getJSClass:(JSClass **)outClass andPrototype:(JSObject **)outPrototype; - (NSString *) oo_jsClassName; - (BOOL) isVisibleToScripts; - (NSArray *) subEntitiesForScript; @end oolite-1.82/src/Core/Scripting/OOJSVisualEffect.m000066400000000000000000000463731256642440500216360ustar00rootroot00000000000000/* OOJSVisualEffect.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOVisualEffectEntity.h" #import "OOJSVisualEffect.h" #import "OOJSEntity.h" #import "OOJSVector.h" #import "OOJavaScriptEngine.h" #import "OOMesh.h" #import "OOCollectionExtractors.h" #import "ResourceManager.h" #import "EntityOOJavaScriptExtensions.h" static JSObject *sVisualEffectPrototype; static BOOL JSVisualEffectGetVisualEffectEntity(JSContext *context, JSObject *stationObj, OOVisualEffectEntity **outEntity); static JSBool VisualEffectGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool VisualEffectSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); static JSBool VisualEffectRemove(JSContext *context, uintN argc, jsval *vp); static JSBool VisualEffectGetShaders(JSContext *context, uintN argc, jsval *vp); static JSBool VisualEffectSetShaders(JSContext *context, uintN argc, jsval *vp); static JSBool VisualEffectGetMaterials(JSContext *context, uintN argc, jsval *vp); static JSBool VisualEffectSetMaterials(JSContext *context, uintN argc, jsval *vp); static JSBool VisualEffectScale(JSContext *context, uintN argc, jsval *vp); static JSBool VisualEffectRestoreSubEntities(JSContext *context, uintN argc, jsval *vp); static JSBool VisualEffectSetMaterialsInternal(JSContext *context, uintN argc, jsval *vp, OOVisualEffectEntity *thisEnt, BOOL fromShaders); static JSClass sVisualEffectClass = { "VisualEffect", JSCLASS_HAS_PRIVATE, JS_PropertyStub, // addProperty JS_PropertyStub, // delProperty VisualEffectGetProperty, // getProperty VisualEffectSetProperty, // setProperty JS_EnumerateStub, // enumerate JS_ResolveStub, // resolve JS_ConvertStub, // convert OOJSObjectWrapperFinalize,// finalize JSCLASS_NO_OPTIONAL_MEMBERS }; enum { // Property IDs kVisualEffect_beaconCode, kVisualEffect_beaconLabel, kVisualEffect_dataKey, kVisualEffect_hullHeatLevel, kVisualEffect_isBreakPattern, kVisualEffect_scaleX, kVisualEffect_scaleY, kVisualEffect_scaleZ, kVisualEffect_scannerDisplayColor1, kVisualEffect_scannerDisplayColor2, kVisualEffect_script, kVisualEffect_scriptInfo, kVisualEffect_shaderFloat1, kVisualEffect_shaderFloat2, kVisualEffect_shaderInt1, kVisualEffect_shaderInt2, kVisualEffect_shaderVector1, kVisualEffect_shaderVector2, kVisualEffect_subEntities, kVisualEffect_vectorForward, kVisualEffect_vectorRight, kVisualEffect_vectorUp }; static JSPropertySpec sVisualEffectProperties[] = { // JS name ID flags { "beaconCode", kVisualEffect_beaconCode, OOJS_PROP_READWRITE_CB }, { "beaconLabel", kVisualEffect_beaconLabel, OOJS_PROP_READWRITE_CB }, { "dataKey", kVisualEffect_dataKey, OOJS_PROP_READONLY_CB }, { "isBreakPattern", kVisualEffect_isBreakPattern, OOJS_PROP_READWRITE_CB }, { "scaleX", kVisualEffect_scaleX, OOJS_PROP_READWRITE_CB }, { "scaleY", kVisualEffect_scaleY, OOJS_PROP_READWRITE_CB }, { "scaleZ", kVisualEffect_scaleZ, OOJS_PROP_READWRITE_CB }, { "scannerDisplayColor1", kVisualEffect_scannerDisplayColor1, OOJS_PROP_READWRITE_CB }, { "scannerDisplayColor2", kVisualEffect_scannerDisplayColor2, OOJS_PROP_READWRITE_CB }, { "hullHeatLevel", kVisualEffect_hullHeatLevel, OOJS_PROP_READWRITE_CB }, { "script", kVisualEffect_script, OOJS_PROP_READONLY_CB }, { "scriptInfo", kVisualEffect_scriptInfo, OOJS_PROP_READONLY_CB }, { "shaderFloat1", kVisualEffect_shaderFloat1, OOJS_PROP_READWRITE_CB }, { "shaderFloat2", kVisualEffect_shaderFloat2, OOJS_PROP_READWRITE_CB }, { "shaderInt1", kVisualEffect_shaderInt1, OOJS_PROP_READWRITE_CB }, { "shaderInt2", kVisualEffect_shaderInt2, OOJS_PROP_READWRITE_CB }, { "shaderVector1", kVisualEffect_shaderVector1, OOJS_PROP_READWRITE_CB }, { "shaderVector2", kVisualEffect_shaderVector2, OOJS_PROP_READWRITE_CB }, { "subEntities", kVisualEffect_subEntities, OOJS_PROP_READONLY_CB }, { "vectorForward", kVisualEffect_vectorForward, OOJS_PROP_READONLY_CB }, { "vectorRight", kVisualEffect_vectorRight, OOJS_PROP_READONLY_CB }, { "vectorUp", kVisualEffect_vectorUp, OOJS_PROP_READONLY_CB }, { 0 } }; static JSFunctionSpec sVisualEffectMethods[] = { // JS name Function min args { "getMaterials", VisualEffectGetMaterials, 0 }, { "getShaders", VisualEffectGetShaders, 0 }, { "remove", VisualEffectRemove, 0 }, { "restoreSubEntities", VisualEffectRestoreSubEntities, 0 }, { "scale", VisualEffectScale, 1 }, { "setMaterials", VisualEffectSetMaterials, 1 }, { "setShaders", VisualEffectSetShaders, 2 }, { 0 } }; void InitOOJSVisualEffect(JSContext *context, JSObject *global) { sVisualEffectPrototype = JS_InitClass(context, global, JSEntityPrototype(), &sVisualEffectClass, OOJSUnconstructableConstruct, 0, sVisualEffectProperties, sVisualEffectMethods, NULL, NULL); OOJSRegisterObjectConverter(&sVisualEffectClass, OOJSBasicPrivateObjectConverter); OOJSRegisterSubclass(&sVisualEffectClass, JSEntityClass()); } static BOOL JSVisualEffectGetVisualEffectEntity(JSContext *context, JSObject *visualEffectObj, OOVisualEffectEntity **outEntity) { OOJS_PROFILE_ENTER BOOL result; Entity *entity = nil; if (outEntity == NULL) return NO; *outEntity = nil; result = OOJSEntityGetEntity(context, visualEffectObj, &entity); if (!result) return NO; if (![entity isKindOfClass:[OOVisualEffectEntity class]]) return NO; *outEntity = (OOVisualEffectEntity *)entity; return YES; OOJS_PROFILE_EXIT } @implementation OOVisualEffectEntity (OOJavaScriptExtensions) - (void)getJSClass:(JSClass **)outClass andPrototype:(JSObject **)outPrototype { *outClass = &sVisualEffectClass; *outPrototype = sVisualEffectPrototype; } - (NSString *) oo_jsClassName { return @"VisualEffect"; } - (BOOL) isVisibleToScripts { return YES; } - (NSArray *) subEntitiesForScript { return [[self visualEffectSubEntityEnumerator] allObjects]; } @end static JSBool VisualEffectGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) OOVisualEffectEntity *entity = nil; id result = nil; if (!JSVisualEffectGetVisualEffectEntity(context, this, &entity)) return NO; if (entity == nil) { *value = JSVAL_VOID; return YES; } switch (JSID_TO_INT(propID)) { case kVisualEffect_beaconCode: result = [entity beaconCode]; break; case kVisualEffect_beaconLabel: result = [entity beaconLabel]; break; case kVisualEffect_dataKey: result = [entity effectKey]; break; case kVisualEffect_isBreakPattern: *value = OOJSValueFromBOOL([entity isBreakPattern]); return YES; case kVisualEffect_vectorRight: return VectorToJSValue(context, [entity rightVector], value); case kVisualEffect_vectorForward: return VectorToJSValue(context, [entity forwardVector], value); case kVisualEffect_vectorUp: return VectorToJSValue(context, [entity upVector], value); case kVisualEffect_scaleX: return JS_NewNumberValue(context, [entity scaleX], value); case kVisualEffect_scaleY: return JS_NewNumberValue(context, [entity scaleY], value); case kVisualEffect_scaleZ: return JS_NewNumberValue(context, [entity scaleZ], value); case kVisualEffect_scannerDisplayColor1: result = [[entity scannerDisplayColor1] normalizedArray]; break; case kVisualEffect_scannerDisplayColor2: result = [[entity scannerDisplayColor2] normalizedArray]; break; case kVisualEffect_hullHeatLevel: return JS_NewNumberValue(context, [entity hullHeatLevel], value); case kVisualEffect_shaderFloat1: return JS_NewNumberValue(context, [entity shaderFloat1], value); case kVisualEffect_shaderFloat2: return JS_NewNumberValue(context, [entity shaderFloat2], value); case kVisualEffect_shaderInt1: *value = INT_TO_JSVAL([entity shaderInt1]); return YES; case kVisualEffect_shaderInt2: *value = INT_TO_JSVAL([entity shaderInt2]); return YES; case kVisualEffect_shaderVector1: return VectorToJSValue(context, [entity shaderVector1], value); case kVisualEffect_shaderVector2: return VectorToJSValue(context, [entity shaderVector2], value); case kVisualEffect_subEntities: result = [entity subEntitiesForScript]; break; case kVisualEffect_script: result = [entity script]; break; case kVisualEffect_scriptInfo: result = [entity scriptInfo]; if (result == nil) result = [NSDictionary dictionary]; // empty rather than null break; default: OOJSReportBadPropertySelector(context, this, propID, sVisualEffectProperties); return NO; } *value = OOJSValueFromNativeObject(context, result); return YES; OOJS_NATIVE_EXIT } static JSBool VisualEffectSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) OOVisualEffectEntity *entity = nil; JSBool bValue; OOColor *colorForScript; int32 iValue; jsdouble fValue; Vector vValue; NSString *sValue = nil; if (!JSVisualEffectGetVisualEffectEntity(context, this, &entity)) return NO; if (entity == nil) return YES; switch (JSID_TO_INT(propID)) { case kVisualEffect_beaconCode: sValue = OOStringFromJSValue(context,*value); if (sValue == nil || [sValue length] == 0) { if ([entity isBeacon]) { [UNIVERSE clearBeacon:entity]; if ([PLAYER nextBeacon] == entity) { [PLAYER setCompassMode:COMPASS_MODE_PLANET]; } } } else { if ([entity isBeacon]) { [entity setBeaconCode:sValue]; } else // Universe needs to update beacon lists in this case only { [entity setBeaconCode:sValue]; [UNIVERSE setNextBeacon:entity]; } } return YES; break; case kVisualEffect_beaconLabel: sValue = OOStringFromJSValue(context,*value); if (sValue != nil) { [entity setBeaconLabel:sValue]; return YES; } break; case kVisualEffect_isBreakPattern: if (JS_ValueToBoolean(context, *value, &bValue)) { [entity setIsBreakPattern:bValue]; return YES; } break; case kVisualEffect_scannerDisplayColor1: colorForScript = [OOColor colorWithDescription:OOJSNativeObjectFromJSValue(context, *value)]; if (colorForScript != nil || JSVAL_IS_NULL(*value)) { [entity setScannerDisplayColor1:colorForScript]; return YES; } break; case kVisualEffect_scannerDisplayColor2: colorForScript = [OOColor colorWithDescription:OOJSNativeObjectFromJSValue(context, *value)]; if (colorForScript != nil || JSVAL_IS_NULL(*value)) { [entity setScannerDisplayColor2:colorForScript]; return YES; } break; case kVisualEffect_scaleX: if (JS_ValueToNumber(context, *value, &fValue)) { if (fValue > 0.0) { [entity setScaleX:fValue]; return YES; } } break; case kVisualEffect_scaleY: if (JS_ValueToNumber(context, *value, &fValue)) { if (fValue > 0.0) { [entity setScaleY:fValue]; return YES; } } break; case kVisualEffect_scaleZ: if (JS_ValueToNumber(context, *value, &fValue)) { if (fValue > 0.0) { [entity setScaleZ:fValue]; return YES; } } break; case kVisualEffect_hullHeatLevel: if (JS_ValueToNumber(context, *value, &fValue)) { [entity setHullHeatLevel:fValue]; return YES; } break; case kVisualEffect_shaderFloat1: if (JS_ValueToNumber(context, *value, &fValue)) { [entity setShaderFloat1:fValue]; return YES; } break; case kVisualEffect_shaderFloat2: if (JS_ValueToNumber(context, *value, &fValue)) { [entity setShaderFloat2:fValue]; return YES; } break; case kVisualEffect_shaderInt1: if (JS_ValueToInt32(context, *value, &iValue)) { [entity setShaderInt1:iValue]; return YES; } break; case kVisualEffect_shaderInt2: if (JS_ValueToInt32(context, *value, &iValue)) { [entity setShaderInt2:iValue]; return YES; } break; case kVisualEffect_shaderVector1: if (JSValueToVector(context, *value, &vValue)) { [entity setShaderVector1:vValue]; return YES; } break; case kVisualEffect_shaderVector2: if (JSValueToVector(context, *value, &vValue)) { [entity setShaderVector2:vValue]; return YES; } break; default: OOJSReportBadPropertySelector(context, this, propID, sVisualEffectProperties); return NO; } OOJSReportBadPropertyValue(context, this, propID, sVisualEffectProperties, *value); return NO; OOJS_NATIVE_EXIT } // *** Methods *** #define GET_THIS_EFFECT(THISENT) do { \ if (EXPECT_NOT(!JSVisualEffectGetVisualEffectEntity(context, OOJS_THIS, &THISENT))) return NO; /* Exception */ \ if (OOIsStaleEntity(THISENT)) OOJS_RETURN_VOID; \ } while (0) static JSBool VisualEffectRemove(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOVisualEffectEntity *thisEnt = nil; GET_THIS_EFFECT(thisEnt); if ([thisEnt isSubEntity]) { OOVisualEffectEntity *parent = [thisEnt owner]; [parent removeSubEntity:thisEnt]; } else { [thisEnt remove]; } OOJS_RETURN_VOID; OOJS_NATIVE_EXIT } //getMaterials() static JSBool VisualEffectGetMaterials(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER NSObject *result = nil; OOVisualEffectEntity *thisEnt = nil; GET_THIS_EFFECT(thisEnt); result = [[thisEnt mesh] materials]; if (result == nil) result = [NSDictionary dictionary]; OOJS_RETURN_OBJECT(result); OOJS_PROFILE_EXIT } //getShaders() static JSBool VisualEffectGetShaders(JSContext *context, uintN argc, jsval *vp) { OOJS_PROFILE_ENTER NSObject *result = nil; OOVisualEffectEntity *thisEnt = nil; GET_THIS_EFFECT(thisEnt); result = [[thisEnt mesh] shaders]; if (result == nil) result = [NSDictionary dictionary]; OOJS_RETURN_OBJECT(result); OOJS_PROFILE_EXIT } // setMaterials(params: dict, [shaders: dict]) // sets materials dictionary. Optional parameter sets the shaders dictionary too. static JSBool VisualEffectSetMaterials(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOVisualEffectEntity *thisEnt = nil; if (argc < 1) { OOJSReportBadArguments(context, @"VisualEffect", @"setMaterials", 0, OOJS_ARGV, nil, @"parameter object"); return NO; } GET_THIS_EFFECT(thisEnt); return VisualEffectSetMaterialsInternal(context, argc, vp, thisEnt, NO); OOJS_NATIVE_EXIT } // setShaders(params: dict) static JSBool VisualEffectSetShaders(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOVisualEffectEntity *thisEnt = nil; GET_THIS_EFFECT(thisEnt); if (argc < 1) { OOJSReportBadArguments(context, @"VisualEffect", @"setShaders", 0, OOJS_ARGV, nil, @"parameter object"); return NO; } if (JSVAL_IS_NULL(OOJS_ARGV[0]) || (!JSVAL_IS_NULL(OOJS_ARGV[0]) && !JSVAL_IS_OBJECT(OOJS_ARGV[0]))) { // EMMSTRAN: JS_ValueToObject() and normal error handling here. OOJSReportWarning(context, @"VisualEffect.%@: expected %@ instead of '%@'.", @"setShaders", @"object", OOStringFromJSValueEvenIfNull(context, OOJS_ARGV[0])); OOJS_RETURN_BOOL(NO); } OOJS_ARGV[1] = OOJS_ARGV[0]; return VisualEffectSetMaterialsInternal(context, argc, vp, thisEnt, YES); OOJS_NATIVE_EXIT } /* ** helper functions ** */ static JSBool VisualEffectSetMaterialsInternal(JSContext *context, uintN argc, jsval *vp, OOVisualEffectEntity *thisEnt, BOOL fromShaders) { OOJS_PROFILE_ENTER JSObject *params = NULL; NSDictionary *materials; NSDictionary *shaders; BOOL withShaders = NO; BOOL success = NO; GET_THIS_EFFECT(thisEnt); if (JSVAL_IS_NULL(OOJS_ARGV[0]) || (!JSVAL_IS_NULL(OOJS_ARGV[0]) && !JSVAL_IS_OBJECT(OOJS_ARGV[0]))) { OOJSReportWarning(context, @"VisualEffect.%@: expected %@ instead of '%@'.", @"setMaterials", @"object", OOStringFromJSValueEvenIfNull(context, OOJS_ARGV[0])); OOJS_RETURN_BOOL(NO); } if (argc > 1) { withShaders = YES; if (JSVAL_IS_NULL(OOJS_ARGV[1]) || (!JSVAL_IS_NULL(OOJS_ARGV[1]) && !JSVAL_IS_OBJECT(OOJS_ARGV[1]))) { OOJSReportWarning(context, @"VisualEffect.%@: expected %@ instead of '%@'.", @"setMaterials", @"object as second parameter", OOStringFromJSValueEvenIfNull(context, OOJS_ARGV[1])); withShaders = NO; } } if (fromShaders) { materials = [[thisEnt mesh] materials]; params = JSVAL_TO_OBJECT(OOJS_ARGV[0]); shaders = OOJSNativeObjectFromJSObject(context, params); } else { params = JSVAL_TO_OBJECT(OOJS_ARGV[0]); materials = OOJSNativeObjectFromJSObject(context, params); if (withShaders) { params = JSVAL_TO_OBJECT(OOJS_ARGV[1]); shaders = OOJSNativeObjectFromJSObject(context, params); } else { shaders = [[thisEnt mesh] shaders]; } } OOJS_BEGIN_FULL_NATIVE(context) NSDictionary *effectDict = [thisEnt effectInfoDictionary]; // First we test to see if we can create the mesh. OOMesh *mesh = [OOMesh meshWithName:[effectDict oo_stringForKey:@"model"] cacheKey:nil materialDictionary:materials shadersDictionary:shaders smooth:[effectDict oo_boolForKey:@"smooth" defaultValue:NO] shaderMacros:[[ResourceManager materialDefaults] oo_dictionaryForKey:@"ship-prefix-macros"] shaderBindingTarget:thisEnt]; if (mesh != nil) { [thisEnt setMesh:mesh]; success = YES; } OOJS_END_FULL_NATIVE OOJS_RETURN_BOOL(success); OOJS_PROFILE_EXIT } static JSBool VisualEffectScale(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOVisualEffectEntity *thisEnt = nil; GET_THIS_EFFECT(thisEnt); jsdouble scale; BOOL gotScale; if (argc < 1) { OOJSReportBadArguments(context, @"VisualEffect", @"scale", argc, OOJS_ARGV, nil, @"scale factor needed"); return NO; } gotScale = JS_ValueToNumber(context, OOJS_ARGV[0], &scale); if (EXPECT_NOT(scale <= 0.0 || !gotScale)) { OOJSReportBadArguments(context, @"VisualEffect", @"scale", argc, OOJS_ARGV, nil, @"scale factor must be positive"); return NO; } // set all three scales [thisEnt setScaleX:scale]; [thisEnt setScaleY:scale]; [thisEnt setScaleZ:scale]; return YES; OOJS_NATIVE_EXIT } // restoreSubEntities(): boolean static JSBool VisualEffectRestoreSubEntities(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) OOVisualEffectEntity *thisEnt = nil; NSUInteger numSubEntitiesRestored = 0U; GET_THIS_EFFECT(thisEnt); NSUInteger subCount = [[thisEnt subEntitiesForScript] count]; [thisEnt clearSubEntities]; [thisEnt setUpSubEntities]; if ([[thisEnt subEntitiesForScript] count] - subCount > 0) numSubEntitiesRestored = [[thisEnt subEntitiesForScript] count] - subCount; OOJS_RETURN_BOOL(numSubEntitiesRestored > 0); OOJS_NATIVE_EXIT } oolite-1.82/src/Core/Scripting/OOJSWaypoint.h000066400000000000000000000022061256642440500210460ustar00rootroot00000000000000/* OOJSWaypoint.h JavaScript proxy for OOWaypointEntities. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #include @class OOWaypointEntity; void InitOOJSWaypoint(JSContext *context, JSObject *global); @interface OOWaypointEntity (OOJavaScriptExtensions) - (void)getJSClass:(JSClass **)outClass andPrototype:(JSObject **)outPrototype; - (NSString *) oo_jsClassName; - (BOOL) isVisibleToScripts; @end oolite-1.82/src/Core/Scripting/OOJSWaypoint.m000066400000000000000000000143721256642440500210620ustar00rootroot00000000000000/* OOJSWaypoint.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOWaypointEntity.h" #import "OOJSWaypoint.h" #import "OOJSEntity.h" #import "OOJSVector.h" #import "OOJSQuaternion.h" #import "OOJavaScriptEngine.h" #import "OOCollectionExtractors.h" #import "EntityOOJavaScriptExtensions.h" static JSObject *sWaypointPrototype; static BOOL JSWaypointGetWaypointEntity(JSContext *context, JSObject *stationObj, OOWaypointEntity **outEntity); static JSBool WaypointGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool WaypointSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); static JSClass sWaypointClass = { "Waypoint", JSCLASS_HAS_PRIVATE, JS_PropertyStub, // addProperty JS_PropertyStub, // delProperty WaypointGetProperty, // getProperty WaypointSetProperty, // setProperty JS_EnumerateStub, // enumerate JS_ResolveStub, // resolve JS_ConvertStub, // convert OOJSObjectWrapperFinalize,// finalize JSCLASS_NO_OPTIONAL_MEMBERS }; enum { // Property IDs kWaypoint_beaconCode, kWaypoint_beaconLabel, kWaypoint_orientation, // overrides entity as waypoints can be unoriented kWaypoint_size }; static JSPropertySpec sWaypointProperties[] = { // JS name ID flags { "beaconCode", kWaypoint_beaconCode, OOJS_PROP_READWRITE_CB }, { "beaconLabel", kWaypoint_beaconLabel, OOJS_PROP_READWRITE_CB }, { "orientation", kWaypoint_orientation, OOJS_PROP_READWRITE_CB }, { "size", kWaypoint_size, OOJS_PROP_READWRITE_CB }, { 0 } }; static JSFunctionSpec sWaypointMethods[] = { // JS name Function min args // { "", WaypointDoStuff, 0 }, { 0 } }; void InitOOJSWaypoint(JSContext *context, JSObject *global) { sWaypointPrototype = JS_InitClass(context, global, JSEntityPrototype(), &sWaypointClass, OOJSUnconstructableConstruct, 0, sWaypointProperties, sWaypointMethods, NULL, NULL); OOJSRegisterObjectConverter(&sWaypointClass, OOJSBasicPrivateObjectConverter); OOJSRegisterSubclass(&sWaypointClass, JSEntityClass()); } static BOOL JSWaypointGetWaypointEntity(JSContext *context, JSObject *wormholeObj, OOWaypointEntity **outEntity) { OOJS_PROFILE_ENTER BOOL result; Entity *entity = nil; if (outEntity == NULL) return NO; *outEntity = nil; result = OOJSEntityGetEntity(context, wormholeObj, &entity); if (!result) return NO; if (![entity isKindOfClass:[OOWaypointEntity class]]) return NO; *outEntity = (OOWaypointEntity *)entity; return YES; OOJS_PROFILE_EXIT } @implementation OOWaypointEntity (OOJavaScriptExtensions) - (void)getJSClass:(JSClass **)outClass andPrototype:(JSObject **)outPrototype { *outClass = &sWaypointClass; *outPrototype = sWaypointPrototype; } - (NSString *) oo_jsClassName { return @"Waypoint"; } - (BOOL) isVisibleToScripts { return YES; } @end static JSBool WaypointGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) OOWaypointEntity *entity = nil; id result = nil; Quaternion q = kIdentityQuaternion; if (!JSWaypointGetWaypointEntity(context, this, &entity)) return NO; if (entity == nil) { *value = JSVAL_VOID; return YES; } switch (JSID_TO_INT(propID)) { case kWaypoint_beaconCode: result = [entity beaconCode]; break; case kWaypoint_beaconLabel: result = [entity beaconLabel]; break; case kWaypoint_orientation: q = [entity orientation]; if (![entity oriented]) { q = kZeroQuaternion; } return QuaternionToJSValue(context, q, value); case kWaypoint_size: return JS_NewNumberValue(context, [entity size], value); default: OOJSReportBadPropertySelector(context, this, propID, sWaypointProperties); return NO; } *value = OOJSValueFromNativeObject(context, result); return YES; OOJS_NATIVE_EXIT } static JSBool WaypointSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) OOWaypointEntity *entity = nil; jsdouble fValue; NSString *sValue = nil; Quaternion qValue; if (!JSWaypointGetWaypointEntity(context, this, &entity)) return NO; if (entity == nil) return YES; switch (JSID_TO_INT(propID)) { case kWaypoint_beaconCode: sValue = OOStringFromJSValue(context,*value); if (sValue == nil || [sValue length] == 0) { if ([entity isBeacon]) { [UNIVERSE clearBeacon:entity]; if ([PLAYER nextBeacon] == entity) { [PLAYER setCompassMode:COMPASS_MODE_PLANET]; } } } else { if ([entity isBeacon]) { [entity setBeaconCode:sValue]; } else // Universe needs to update beacon lists in this case only { [entity setBeaconCode:sValue]; [UNIVERSE setNextBeacon:entity]; } } return YES; break; case kWaypoint_beaconLabel: sValue = OOStringFromJSValue(context,*value); if (sValue != nil) { [entity setBeaconLabel:sValue]; return YES; } break; case kWaypoint_orientation: if (JSValueToQuaternion(context, *value, &qValue)) { [entity setNormalOrientation:qValue]; return YES; } break; case kWaypoint_size: if (JS_ValueToNumber(context, *value, &fValue)) { if (fValue > 0.0) { [entity setSize:fValue]; return YES; } } break; default: OOJSReportBadPropertySelector(context, this, propID, sWaypointProperties); return NO; } OOJSReportBadPropertyValue(context, this, propID, sWaypointProperties, *value); return NO; OOJS_NATIVE_EXIT } // *** Methods *** oolite-1.82/src/Core/Scripting/OOJSWorldScripts.h000066400000000000000000000017721256642440500217020ustar00rootroot00000000000000/* OOJSWorldScripts.h JavaScript world scripts object. This is a global object that allows scripts to look up and iterate over world scripts. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #include void InitOOJSWorldScripts(JSContext *context, JSObject *global); oolite-1.82/src/Core/Scripting/OOJSWorldScripts.m000066400000000000000000000060751256642440500217100ustar00rootroot00000000000000/* OOJSWorldScripts.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOJSWorldScripts.h" #import "OOJavaScriptEngine.h" #import "PlayerEntity.h" #import "OOJSPlayer.h" static JSBool WorldScriptsGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool WorldScriptsEnumerate(JSContext *cx, JSObject *obj); static JSClass sWorldScriptsClass = { "WorldScripts", 0, JS_PropertyStub, JS_PropertyStub, WorldScriptsGetProperty, JS_StrictPropertyStub, WorldScriptsEnumerate, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub }; void InitOOJSWorldScripts(JSContext *context, JSObject *global) { JS_DefineObject(context, global, "worldScripts", &sWorldScriptsClass, NULL, OOJS_PROP_READONLY); } static JSBool WorldScriptsGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { OOJS_NATIVE_ENTER(context) PlayerEntity *player = OOPlayerForScripting(); NSString *scriptName = nil; id script = nil; if (!JSID_IS_STRING(propID)) return YES; scriptName = OOStringFromJSString(context, JSID_TO_STRING(propID)); if (scriptName != nil) { script = [[player worldScriptsByName] objectForKey:scriptName]; if (script != nil) { /* If script is an OOJSScript, this should return a JS Script object. For other OOScript subclasses, it will return JSVAL_NULL. If no script exists, the value will be JSVAL_VOID. */ *value = [script oo_jsValueInContext:context]; } else { *value = JSVAL_VOID; } } return YES; OOJS_NATIVE_EXIT } static JSBool WorldScriptsEnumerate(JSContext *context, JSObject *object) { OOJS_NATIVE_ENTER(context) /* In order to support enumeration of world scripts (e.g., for (name in worldScripts) { ... }), define each property on demand. Since world scripts cannot be deleted, we don't need to worry about that case (as in OOJSMissionVariables). Since WorldScriptsGetProperty() will be called for each access anyway, we define the value as null here. */ NSArray *names = nil; NSEnumerator *nameEnum = nil; NSString *name = nil; names = [OOPlayerForScripting() worldScriptNames]; for (nameEnum = [names objectEnumerator]; (name = [nameEnum nextObject]); ) { if (!JS_DefineProperty(context, object, [name UTF8String], JSVAL_NULL, WorldScriptsGetProperty, NULL, OOJS_PROP_READONLY_CB)) return NO; } return YES; OOJS_NATIVE_EXIT } oolite-1.82/src/Core/Scripting/OOJSWormhole.h000066400000000000000000000021771256642440500210370ustar00rootroot00000000000000/* OOJSWormhole.h JavaScript proxy for WormholeEntities. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #include @class WormholeEntity; void InitOOJSWormhole(JSContext *context, JSObject *global); @interface WormholeEntity (OOJavaScriptExtensions) - (void)getJSClass:(JSClass **)outClass andPrototype:(JSObject **)outPrototype; - (NSString *) oo_jsClassName; - (BOOL) isVisibleToScripts; @end oolite-1.82/src/Core/Scripting/OOJSWormhole.m000066400000000000000000000117611256642440500210430ustar00rootroot00000000000000/* OOJSWormhole.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "WormholeEntity.h" #import "OOJSWormhole.h" #import "OOJSEntity.h" #import "OOJSVector.h" #import "OOJavaScriptEngine.h" #import "OOCollectionExtractors.h" #import "EntityOOJavaScriptExtensions.h" static JSObject *sWormholePrototype; static BOOL JSWormholeGetWormholeEntity(JSContext *context, JSObject *stationObj, WormholeEntity **outEntity); static JSBool WormholeGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value); static JSBool WormholeSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value); static JSClass sWormholeClass = { "Wormhole", JSCLASS_HAS_PRIVATE, JS_PropertyStub, // addProperty JS_PropertyStub, // delProperty WormholeGetProperty, // getProperty WormholeSetProperty, // setProperty JS_EnumerateStub, // enumerate JS_ResolveStub, // resolve JS_ConvertStub, // convert OOJSObjectWrapperFinalize,// finalize JSCLASS_NO_OPTIONAL_MEMBERS }; enum { // Property IDs kWormhole_arrivalTime, kWormhole_destination, kWormhole_expiryTime, kWormhole_origin }; static JSPropertySpec sWormholeProperties[] = { // JS name ID flags { "arrivalTime", kWormhole_arrivalTime, OOJS_PROP_READONLY_CB }, { "destination", kWormhole_destination, OOJS_PROP_READONLY_CB }, { "expiryTime", kWormhole_expiryTime, OOJS_PROP_READONLY_CB }, { "origin", kWormhole_origin, OOJS_PROP_READONLY_CB }, { 0 } }; static JSFunctionSpec sWormholeMethods[] = { // JS name Function min args // { "", WormholeDoStuff, 0 }, { 0 } }; void InitOOJSWormhole(JSContext *context, JSObject *global) { sWormholePrototype = JS_InitClass(context, global, JSEntityPrototype(), &sWormholeClass, OOJSUnconstructableConstruct, 0, sWormholeProperties, sWormholeMethods, NULL, NULL); OOJSRegisterObjectConverter(&sWormholeClass, OOJSBasicPrivateObjectConverter); OOJSRegisterSubclass(&sWormholeClass, JSEntityClass()); } static BOOL JSWormholeGetWormholeEntity(JSContext *context, JSObject *wormholeObj, WormholeEntity **outEntity) { OOJS_PROFILE_ENTER BOOL result; Entity *entity = nil; if (outEntity == NULL) return NO; *outEntity = nil; result = OOJSEntityGetEntity(context, wormholeObj, &entity); if (!result) return NO; if (![entity isKindOfClass:[WormholeEntity class]]) return NO; *outEntity = (WormholeEntity *)entity; return YES; OOJS_PROFILE_EXIT } @implementation WormholeEntity (OOJavaScriptExtensions) - (void)getJSClass:(JSClass **)outClass andPrototype:(JSObject **)outPrototype { *outClass = &sWormholeClass; *outPrototype = sWormholePrototype; } - (NSString *) oo_jsClassName { return @"Wormhole"; } - (BOOL) isVisibleToScripts { return YES; } @end static JSBool WormholeGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) WormholeEntity *entity = nil; id result = nil; if (!JSWormholeGetWormholeEntity(context, this, &entity)) return NO; if (entity == nil) { *value = JSVAL_VOID; return YES; } switch (JSID_TO_INT(propID)) { case kWormhole_arrivalTime: return JS_NewNumberValue(context, [entity arrivalTime], value); case kWormhole_destination: return JS_NewNumberValue(context, [entity destination], value); case kWormhole_expiryTime: return JS_NewNumberValue(context, [entity expiryTime], value); case kWormhole_origin: return JS_NewNumberValue(context, [entity origin], value); default: OOJSReportBadPropertySelector(context, this, propID, sWormholeProperties); return NO; } *value = OOJSValueFromNativeObject(context, result); return YES; OOJS_NATIVE_EXIT } static JSBool WormholeSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value) { if (!JSID_IS_INT(propID)) return YES; OOJS_NATIVE_ENTER(context) WormholeEntity *entity = nil; if (!JSWormholeGetWormholeEntity(context, this, &entity)) return NO; if (entity == nil) return YES; switch (JSID_TO_INT(propID)) { default: OOJSReportBadPropertySelector(context, this, propID, sWormholeProperties); return NO; } OOJSReportBadPropertyValue(context, this, propID, sWormholeProperties, *value); return NO; OOJS_NATIVE_EXIT } // *** Methods *** oolite-1.82/src/Core/Scripting/OOJavaScriptEngine.h000066400000000000000000000563461256642440500222110ustar00rootroot00000000000000/* OOJavaScriptEngine.h JavaScript support for Oolite Copyright (C) 2007-2013 David Taylor and Jens Ayton. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #import "Universe.h" #import "PlayerEntity.h" #import "PlayerEntityLegacyScriptEngine.h" #include #define OOJSENGINE_MONITOR_SUPPORT (!defined(NDEBUG)) #import "OOJSPropID.h" @protocol OOJavaScriptEngineMonitor; @interface OOJavaScriptEngine: NSObject { @private JSRuntime *_runtime; JSObject *_globalObject; BOOL _showErrorLocations; JSClass *_objectClass; JSClass *_stringClass; JSClass *_arrayClass; JSClass *_numberClass; JSClass *_booleanClass; #ifndef NDEBUG BOOL _dumpStackForErrors; BOOL _dumpStackForWarnings; #endif #if OOJSENGINE_MONITOR_SUPPORT id _monitor; #endif } + (OOJavaScriptEngine *) sharedEngine; - (JSObject *) globalObject; - (void) runMissionCallback; /* Tear down context and global object and rebuild them from scratch. This invalidates -globalObject and the main thread context. */ - (BOOL) reset; // Call a JS function, setting up new contexts as necessary. Caller is responsible for ensuring the jsval passed really is a function. - (BOOL) callJSFunction:(jsval)function forObject:(JSObject *)jsThis argc:(uintN)argc argv:(jsval *)argv result:(jsval *)outResult; - (void) removeGCObjectRoot:(JSObject **)rootPtr; - (void) removeGCValueRoot:(jsval *)rootPtr; - (void) garbageCollectionOpportunity:(BOOL)force; - (BOOL) showErrorLocations; - (void) setShowErrorLocations:(BOOL)value; - (JSClass *) objectClass; - (JSClass *) stringClass; - (JSClass *) arrayClass; - (JSClass *) numberClass; - (JSClass *) booleanClass; #ifndef NDEBUG - (BOOL) dumpStackForErrors; - (void) setDumpStackForErrors:(BOOL)value; - (BOOL) dumpStackForWarnings; - (void) setDumpStackForWarnings:(BOOL)value; // Install handler for JS "debugger" statment. - (void) enableDebuggerStatement; #endif @end #if !JS_THREADSAFE #define JS_IsInRequest(context) (((void)(context)), YES) #define JS_BeginRequest(context) do {} while (0) #define JS_EndRequest(context) do {} while (0) #endif // Get the main thread's JS context, and begin a request on it. OOINLINE JSContext *OOJSAcquireContext(void) { extern JSContext *gOOJSMainThreadContext; NSCAssert(gOOJSMainThreadContext != NULL, @"Attempt to use JavaScript context before JavaScript engine is initialized."); JS_BeginRequest(gOOJSMainThreadContext); return gOOJSMainThreadContext; } // End a request on the main thread's context. OOINLINE void OOJSRelinquishContext(JSContext *context) { #ifndef NDEBUG extern JSContext *gOOJSMainThreadContext; NSCParameterAssert(context == gOOJSMainThreadContext && JS_IsInRequest(context)); #endif JS_EndRequest(context); } // Notifications sent when JavaScript engine is reset. extern NSString * const kOOJavaScriptEngineWillResetNotification; extern NSString * const kOOJavaScriptEngineDidResetNotification; /* Error and warning reporters. Note that after reporting an error in a JavaScript callback, the caller must return NO to signal an error. */ void OOJSReportError(JSContext *context, NSString *format, ...); void OOJSReportErrorWithArguments(JSContext *context, NSString *format, va_list args); void OOJSReportErrorForCaller(JSContext *context, NSString *scriptClass, NSString *function, NSString *format, ...); void OOJSReportWarning(JSContext *context, NSString *format, ...); void OOJSReportWarningWithArguments(JSContext *context, NSString *format, va_list args); void OOJSReportWarningForCaller(JSContext *context, NSString *scriptClass, NSString *function, NSString *format, ...); void OOJSReportBadPropertySelector(JSContext *context, JSObject *thisObj, jsid propID, JSPropertySpec *propertySpec); void OOJSReportBadPropertyValue(JSContext *context, JSObject *thisObj, jsid propID, JSPropertySpec *propertySpec, jsval value); void OOJSReportBadArguments(JSContext *context, NSString *scriptClass, NSString *function, uintN argc, jsval *argv, NSString *message, NSString *expectedArgsDescription); /* OOJSSetWarningOrErrorStackSkip() Indicate that the direct call site is not relevant for error handler. Currently, if non-zero, no call site information is provided. Ideally, we'd stack crawl instead. */ void OOJSSetWarningOrErrorStackSkip(unsigned skip); /* OOJSArgumentListGetNumber() Get a single number from an argument list. The optional outConsumed argument can be used to find out how many parameters were used (currently, this will be 0 on failure, otherwise 1). On failure, it will return NO and raise an error. If the caller is a JS callback, it must return NO to signal an error. */ BOOL OOJSArgumentListGetNumber(JSContext *context, NSString *scriptClass, NSString *function, uintN argc, jsval *argv, double *outNumber, uintN *outConsumed); /* OOJSArgumentListGetNumberNoError() Like OOJSArgumentListGetNumber(), but does not report an error on failure. */ BOOL OOJSArgumentListGetNumberNoError(JSContext *context, uintN argc, jsval *argv, double *outNumber, uintN *outConsumed); // Typed as int rather than BOOL to work with more general expressions such as bitfield tests. OOINLINE jsval OOJSValueFromBOOL(int b) INLINE_CONST_FUNC; OOINLINE jsval OOJSValueFromBOOL(int b) { return BOOLEAN_TO_JSVAL(b != NO); } @interface NSObject (OOJavaScript) /* -oo_jsValueInContext: Return the JavaScript value representation of an object. The default implementation returns JSVAL_VOID. SAFETY NOTE: if this message is sent to nil, the return value depends on the platform and whether JS_USE_JSVAL_JSID_STRUCT_TYPES is set. If the receiver may be nil, use OOJSValueFromNativeObject() instead. One case where it is safe to use oo_jsValueInContext: is with objects retrieved from Foundation collections, as they can never be nil. Requires a request on context. */ - (jsval) oo_jsValueInContext:(JSContext *)context; /* -oo_jsDescription -oo_jsDescriptionWithClassName: -oo_jsClassName See comments for -descriptionComponents in OOCocoa.h. */ - (NSString *) oo_jsDescription; - (NSString *) oo_jsDescriptionWithClassName:(NSString *)className; - (NSString *) oo_jsClassName; /* oo_clearJSSelf: This is called by OOJSObjectWrapperFinalize() when a JS object wrapper is collected. The default implementation does nothing. */ - (void) oo_clearJSSelf:(JSObject *)selfVal; @end /* OOJSValueFromNativeObject() Return a JavaScript value representation of an object, or null if passed nil. Requires a request on context. */ OOINLINE jsval OOJSValueFromNativeObject(JSContext *context, id object) { if (object != nil) return [object oo_jsValueInContext:context]; return JSVAL_NULL; } /* OOJSObjectFromNativeObject() Return a JavaScript object representation of an object, or null if passed nil. The value is boxed if necessary. Requires a request on context. */ JSObject *OOJSObjectFromNativeObject(JSContext *context, id object); /* OOJSValue: an object whose purpose in life is to hold a JavaScript value. This is somewhat useful for putting JavaScript objects in ObjC collections, for instance to pass as properties to script loaders. The value is GC rooted for the lifetime of the OOJSValue. All methods take a context parameter, which must either be nil or a context in a request. */ @interface OOJSValue: NSObject { jsval _val; } + (id) valueWithJSValue:(jsval)value inContext:(JSContext *)context; + (id) valueWithJSObject:(JSObject *)object inContext:(JSContext *)context; - (id) initWithJSValue:(jsval)value inContext:(JSContext *)context; - (id) initWithJSObject:(JSObject *)object inContext:(JSContext *)context; @end /**** String utilities ****/ /* OOJSSTR(const char * [literal]) Create and cache a jsval referring to an interned string literal. */ #define OOJSSTR(str) ({ static jsval strCache; static BOOL inited; if (EXPECT_NOT(!inited)) OOJSStrLiteralCachePRIVATE(""str, &strCache, &inited); strCache; }) void OOJSStrLiteralCachePRIVATE(const char *string, jsval *strCache, BOOL *inited); // Convert a JSString to an NSString. NSString *OOStringFromJSString(JSContext *context, JSString *string); /* Convert an arbitrary JS object to an NSString, calling JS_ValueToString. OOStringFromJSValue() returns nil if value is null or undefined, OOStringFromJSValueEvenIfNull() returns "null" or "undefined". */ NSString *OOStringFromJSValue(JSContext *context, jsval value); NSString *OOStringFromJSValueEvenIfNull(JSContext *context, jsval value); /* OOStringFromJSPropertyIDAndSpec(context, propID, propertySpec) Returns the name of a property given either a name or a tinyid. (Intended for error reporting inside JSPropertyOps.) */ NSString *OOStringFromJSPropertyIDAndSpec(JSContext *context, jsid propID, JSPropertySpec *propertySpec); /* Describe a value for debugging or error reporting. Strings are quoted, escaped and limited in length. Functions are described as "function foo" (or just "function" if they're anonymous). Up to four elements of arrays are included, followed by total count of there are more than four. If abbreviateObjects, the description "[object Object]" is replaced with "{...}", which may or may not be clearer depending on context. */ NSString *OOJSDescribeValue(JSContext *context, jsval value, BOOL abbreviateObjects); // Convert a jsid to an NSString. NSString *OOStringFromJSID(jsid propID); // Convert an NSString to a jsid. jsid OOJSIDFromString(NSString *string); @interface NSString (OOJavaScriptExtensions) // For diagnostic messages; produces things like @"(42, true, "a string", an object description)". + (NSString *) stringWithJavaScriptParameters:(jsval *)params count:(uintN)count inContext:(JSContext *)context; // Concatenate sequence of arbitrary JS objects into string. + (NSString *) concatenationOfStringsFromJavaScriptValues:(jsval *)values count:(size_t)count separator:(NSString *)separator inContext:(JSContext *)context; // Add escape codes for string so that it's a valid JavaScript literal (if you put "" or '' around it). - (NSString *) escapedForJavaScriptLiteral; @end // OOEntityFilterPredicate wrapping a JavaScript function. typedef struct { JSContext *context; jsval function; // Caller is responsible for ensuring this is a function object (using OOJSValueIsFunction()). JSObject *jsThis; BOOL errorFlag; // Set if a JS exception occurs. The // exception will have been reported. // This also supresses further filtering. } JSFunctionPredicateParameter; BOOL JSFunctionPredicate(Entity *entity, void *parameter); // YES for ships and (normal) planets. Parameter: ignored. BOOL JSEntityIsJavaScriptVisiblePredicate(Entity *entity, void *parameter); // YES for ships other than sub-entities and menu-display ships, and planets other than atmospheres and menu miniatures. Parameter: ignored. BOOL JSEntityIsJavaScriptSearchablePredicate(Entity *entity, void *parameter); // YES for menu-display ships. Parameter: ignored BOOL JSEntityIsDemoShipPredicate(Entity *entity, void *parameter); // These require a request on context. id OOJSNativeObjectFromJSValue(JSContext *context, jsval value); id OOJSNativeObjectFromJSObject(JSContext *context, JSObject *object); id OOJSNativeObjectOfClassFromJSValue(JSContext *context, jsval value, Class requiredClass); id OOJSNativeObjectOfClassFromJSObject(JSContext *context, JSObject *object, Class requiredClass); OOINLINE JSClass *OOJSGetClass(JSContext *cx, JSObject *obj) ALWAYS_INLINE_FUNC; OOINLINE JSClass *OOJSGetClass(JSContext *cx, JSObject *obj) { #if JS_THREADSAFE return JS_GetClass(cx, obj); #else return JS_GetClass(obj); #endif } /* OOJSValueIsFunction(context, value) Test whether a jsval is a function object. The main tripping point here is that JSVAL_IS_OBJECT() is true for JSVAL_NULL, but JS_ObjectIsFunction() crashes if passed null. */ OOINLINE BOOL OOJSValueIsFunction(JSContext *context, jsval value) { return JSVAL_IS_OBJECT(value) && !JSVAL_IS_NULL(value) && JS_ObjectIsFunction(context, JSVAL_TO_OBJECT(value)); } /* OOJSValueIsArray(context, value) Test whether a jsval is an array object. The main tripping point here is that JSVAL_IS_OBJECT() is true for JSVAL_NULL, but JS_IsArrayObject() crashes if passed null. Also, it should be called JS_ObjectIsArray() for consistency. */ OOINLINE BOOL OOJSValueIsArray(JSContext *context, jsval value) { return JSVAL_IS_OBJECT(value) && !JSVAL_IS_NULL(value) && JS_IsArrayObject(context, JSVAL_TO_OBJECT(value)); } /* OOJSDictionaryFromJSValue(context, value) OOJSDictionaryFromJSObject(context, object) Converts a JavaScript value to a dictionary by calling OOJSNativeObjectFromJSValue() on each of its values. Only enumerable own (i.e., not inherited) properties with string keys are included. Requires a request on context. */ NSDictionary *OOJSDictionaryFromJSValue(JSContext *context, jsval value); NSDictionary *OOJSDictionaryFromJSObject(JSContext *context, JSObject *object); /* OOJSDictionaryFromStringTable(context, value) Treat an arbitrary JavaScript object as a dictionary mapping strings to strings, and convert to a corresponding NSDictionary. The values are converted to strings using JS_ValueToString(). Only enumerable own (i.e., not inherited) properties with string keys are included. Requires a request on context. */ NSDictionary *OOJSDictionaryFromStringTable(JSContext *context, jsval value); /* DEFINE_JS_OBJECT_GETTER() Defines a helper to extract Objective-C objects from the private field of JS objects, with runtime type checking. The generated accessor requires a request on context. Weakrefs are automatically unpacked. Types which extend other types, such as entity subtypes, must register their relationships with OOJSRegisterSubclass() below. The signature of the generator is: BOOL (JSContext *context, JSObject *inObject, ** outObject) If it returns NO, inObject is of the wrong class and an error has been raised. Otherwise, outObject is either a native object of the specified class (or a subclass) or nil. */ #ifndef NDEBUG #define DEFINE_JS_OBJECT_GETTER(NAME, JSCLASS, JSPROTO, OBJCCLASSNAME) \ static BOOL NAME(JSContext *context, JSObject *inObject, OBJCCLASSNAME **outObject) GCC_ATTR((unused)); \ static BOOL NAME(JSContext *context, JSObject *inObject, OBJCCLASSNAME **outObject) \ { \ NSCParameterAssert(outObject != NULL); \ static Class cls = Nil; \ if (EXPECT_NOT(cls == Nil)) cls = [OBJCCLASSNAME class]; \ return OOJSObjectGetterImplPRIVATE(context, inObject, JSCLASS, cls, #NAME, (id *)outObject); \ } #else #define DEFINE_JS_OBJECT_GETTER(NAME, JSCLASS, JSPROTO, OBJCCLASSNAME) \ OOINLINE BOOL NAME(JSContext *context, JSObject *inObject, OBJCCLASSNAME **outObject) \ { \ return OOJSObjectGetterImplPRIVATE(context, inObject, JSCLASS, (id *)outObject); \ } #endif // For DEFINE_JS_OBJECT_GETTER()'s use. #ifndef NDEBUG BOOL OOJSObjectGetterImplPRIVATE(JSContext *context, JSObject *object, JSClass *requiredJSClass, Class requiredObjCClass, const char *name, id *outObject); #else BOOL OOJSObjectGetterImplPRIVATE(JSContext *context, JSObject *object, JSClass *requiredJSClass, id *outObject); #endif /* Subclass relationships. JSAPI doesn't have a concept of subclassing, as JavaScript doesn't have a concept of classes, but Oolite reflects part of its class hierarchy as related JSClasses whose prototypes inherit each other. For instance, JS Entity methods work on JS Ships. In order for this to work, OOJSEntityGetEntity() must be able to know that Ship is a subclass of Entity. This is done using OOJSIsSubclass(). void OOJSRegisterSubclass(JSClass *subclass, JSClass *superclass) Register subclass as a subclass of superclass. Subclass must not previously have been registered as a subclass of any class (i.e., single inheritance is required). BOOL OOJSIsSubclass(JSClass *putativeSubclass, JSClass *superclass) Test whether putativeSubclass is a equal to superclass or a registered subclass of superclass, recursively. */ void OOJSRegisterSubclass(JSClass *subclass, JSClass *superclass); BOOL OOJSIsSubclass(JSClass *putativeSubclass, JSClass *superclass); OOINLINE BOOL OOJSIsMemberOfSubclass(JSContext *context, JSObject *object, JSClass *superclass) { return OOJSIsSubclass(OOJSGetClass(context, object), superclass); } /* Support for OOJSNativeObjectFromJSValue() family OOJSClassConverterCallback specifies the prototype for a callback function which converts a JavaScript object to an Objective-C object. OOJSBasicPrivateObjectConverter() is a OOJSClassConverterCallback which returns the JS object's private storage value. It automatically unpacks OOWeakReferences if relevant. OOJSRegisterObjectConverter() registers a callback for a specific JS class. It is not automatically propagated to subclasses. */ typedef id (*OOJSClassConverterCallback)(JSContext *context, JSObject *object); id OOJSBasicPrivateObjectConverter(JSContext *context, JSObject *object); void OOJSRegisterObjectConverter(JSClass *theClass, OOJSClassConverterCallback converter); /* JS root handling The name parameter to JS_AddNamed*Root is assigned with no overhead, not copied, but the strings serve no purpose in a release build so we may as well strip them out. In debug builds, this will deliberately cause an error if name is not a string literal. */ #ifdef NDEBUG #define OOJSAddGCValueRoot(context, root, name) JS_AddValueRoot((context), (root)) #define OOJSAddGCStringRoot(context, root, name) JS_AddStringRoot((context), (root)) #define OOJSAddGCObjectRoot(context, root, name) JS_AddObjectRoot((context), (root)) #define OOJSAddGCThingRoot(context, root, name) JS_AddGCThingRoot((context), (root)) #else #define OOJSAddGCValueRoot(context, root, name) JS_AddNamedValueRoot((context), (root), "" name) #define OOJSAddGCStringRoot(context, root, name) JS_AddNamedStringRoot((context), (root), "" name) #define OOJSAddGCObjectRoot(context, root, name) JS_AddNamedObjectRoot((context), (root), "" name) #define OOJSAddGCThingRoot(context, root, name) JS_AddNamedGCThingRoot((context), (root), "" name) #endif #if OOJSENGINE_MONITOR_SUPPORT /* Protocol for debugging "monitor" object. The monitor is an object -- in Oolite, or via Distributed Objects -- which is provided with debugging information by the OOJavaScriptEngine. */ @protocol OOJavaScriptEngineMonitor // Sent for JS errors or warnings. - (oneway void)jsEngine:(in byref OOJavaScriptEngine *)engine context:(in JSContext *)context error:(in JSErrorReport *)errorReport stackSkip:(in unsigned)stackSkip showingLocation:(in BOOL)showLocation withMessage:(in NSString *)message; // Sent for JS log messages. Note: messageClass will be nil if Log() is used rather than LogWithClass(). - (oneway void)jsEngine:(in byref OOJavaScriptEngine *)engine context:(in JSContext *)context logMessage:(in NSString *)message ofClass:(in NSString *)messageClass; @end @interface OOJavaScriptEngine (OOMonitorSupport) - (void)setMonitor:(id)monitor; @end #endif #import "OOJSEngineNativeWrappers.h" /* See comments on time limiter in OOJSEngineTimeManagement.h. */ void OOJSPauseTimeLimiter(void); void OOJSResumeTimeLimiter(void); /* OOJSDumpStack() Write JavaScript stack to log. OOJSDescribeLocation() Get script and line number for a stack frame. OOJSMarkConsoleEvalLocation() Specify that a given stack frame identifies eval()ed code from the debug console, so that matching locations can be described specially by OOJSDescribeLocation(). */ #ifndef NDEBUG void OOJSDumpStack(JSContext *context); NSString *OOJSDescribeLocation(JSContext *context, JSStackFrame *stackFrame); void OOJSMarkConsoleEvalLocation(JSContext *context, JSStackFrame *stackFrame); #else #define OOJSDumpStack(cx) do {} while (0) #define OOJSDescribeLocation(cx, frame) do {} while (0) #define OOJSMarkConsoleEvalLocation(cx, frame) do {} while (0) #endif /***** Reusable JS callbacks ****/ /* OOJSUnconstructableConstruct Constructor callback for pseudo-classes which can't be constructed. */ JSBool OOJSUnconstructableConstruct(JSContext *context, uintN argc, jsval *vp); /* OOJSObjectWrapperFinalize Finalizer for JS classes whose private storage is a retained object reference (generally an OOWeakReference, but doesn't have to be). */ void OOJSObjectWrapperFinalize(JSContext *context, JSObject *this); /* OOJSObjectWrapperToString Implementation of toString() for JS classes whose private storage is an Objective-C object reference (generally an OOWeakReference). Calls -oo_jsDescription and, if that fails, -description. */ JSBool OOJSObjectWrapperToString(JSContext *context, uintN argc, jsval *vp); /***** Appropriate flags for host-defined read/write and read-only properties *****/ // Slot-based (defined with JS_Define{Property/Object/Function}() and no callbacks) #define OOJS_PROP_READWRITE (JSPROP_PERMANENT | JSPROP_ENUMERATE) #define OOJS_PROP_READONLY (JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY) // Non-enumerable properties #define OOJS_PROP_HIDDEN_READWRITE (JSPROP_PERMANENT) #define OOJS_PROP_HIDDEN_READONLY (JSPROP_PERMANENT | JSPROP_READONLY) // Methods should be non-enumerable #define OOJS_METHOD_READONLY OOJS_PROP_HIDDEN_READONLY // Callback-based (includes all properties specified in JSPropertySpecs) #define OOJS_PROP_READWRITE_CB (OOJS_PROP_READWRITE | JSPROP_SHARED) #define OOJS_PROP_READONLY_CB (OOJS_PROP_READONLY | JSPROP_SHARED) #define OOJS_PROP_HIDDEN_READWRITE_CB (OOJS_PROP_HIDDEN_READWRITE | JSPROP_SHARED) #define OOJS_PROP_HIDDEN_READONLY_CB (OOJS_PROP_HIDDEN_READONLY | JSPROP_SHARED) /***** Helpers for native callbacks. *****/ #define OOJS_THIS JS_THIS_OBJECT(context, vp) #define OOJS_ARGV JS_ARGV(context, vp) #define OOJS_RVAL JS_RVAL(context, vp) #define OOJS_SET_RVAL(v) JS_SET_RVAL(context, vp, v) #define OOJS_RETURN(v) do { OOJS_SET_RVAL(v); return YES; } while (0) #define OOJS_RETURN_JSOBJECT(o) OOJS_RETURN(OBJECT_TO_JSVAL(o)) #define OOJS_RETURN_VOID OOJS_RETURN(JSVAL_VOID) #define OOJS_RETURN_NULL OOJS_RETURN(JSVAL_NULL) #define OOJS_RETURN_BOOL(v) OOJS_RETURN(OOJSValueFromBOOL(v)) #define OOJS_RETURN_INT(v) OOJS_RETURN(INT_TO_JSVAL(v)) #define OOJS_RETURN_OBJECT(o) OOJS_RETURN(OOJSValueFromNativeObject(context, o)) #define OOJS_RETURN_WITH_HELPER(helper, value) \ do { \ jsval jsresult; \ BOOL OK = helper(context, value, &jsresult); \ JS_SET_RVAL(context, vp, jsresult); return OK; \ } while (0) #define OOJS_RETURN_VECTOR(value) OOJS_RETURN_WITH_HELPER(VectorToJSValue, value) #define OOJS_RETURN_HPVECTOR(value) OOJS_RETURN_WITH_HELPER(HPVectorToJSValue, value) #define OOJS_RETURN_QUATERNION(value) OOJS_RETURN_WITH_HELPER(QuaternionToJSValue, value) #define OOJS_RETURN_DOUBLE(value) OOJS_RETURN_WITH_HELPER(JS_NewNumberValue, value) oolite-1.82/src/Core/Scripting/OOJavaScriptEngine.m000066400000000000000000001727451256642440500222200ustar00rootroot00000000000000/* OOJavaScriptEngine.m JavaScript support for Oolite Copyright (C) 2007-2013 David Taylor and Jens Ayton. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #import "OOJavaScriptEngine.h" #import "OOJSEngineTimeManagement.h" #import "OOJSScript.h" #import "OOCollectionExtractors.h" #import "Universe.h" #import "OOPlanetEntity.h" #import "NSStringOOExtensions.h" #import "OOWeakReference.h" #import "EntityOOJavaScriptExtensions.h" #import "ResourceManager.h" #import "NSNumberOOExtensions.h" #import "OOConstToJSString.h" #import "OOVisualEffectEntity.h" #import "OOWaypointEntity.h" #import "OOJSGlobal.h" #import "OOJSMissionVariables.h" #import "OOJSMission.h" #import "OOJSVector.h" #import "OOJSQuaternion.h" #import "OOJSEntity.h" #import "OOJSShip.h" #import "OOJSStation.h" #import "OOJSDock.h" #import "OOJSVisualEffect.h" #import "OOJSExhaustPlume.h" #import "OOJSFlasher.h" #import "OOJSWormhole.h" #import "OOJSWaypoint.h" #import "OOJSPlayer.h" #import "OOJSPlayerShip.h" #import "OOJSManifest.h" #import "OOJSPlanet.h" #import "OOJSSystem.h" #import "OOJSOolite.h" #import "OOJSTimer.h" #import "OOJSClock.h" #import "OOJSSun.h" #import "OOJSWorldScripts.h" #import "OOJSSound.h" #import "OOJSSoundSource.h" #import "OOJSSpecialFunctions.h" #import "OOJSSystemInfo.h" #import "OOJSEquipmentInfo.h" #import "OOJSShipGroup.h" #import "OOJSFrameCallbacks.h" #import "OOJSFont.h" #import "OOProfilingStopwatch.h" #import "OOLoggingExtended.h" #include #define OOJSENGINE_JSVERSION JSVERSION_ECMA_5 #ifdef DEBUG #define JIT_OPTIONS 0 #else #define JIT_OPTIONS JSOPTION_JIT | JSOPTION_METHODJIT | JSOPTION_PROFILING #endif #define OOJSENGINE_CONTEXT_OPTIONS JSOPTION_VAROBJFIX | JSOPTION_RELIMIT | JSOPTION_ANONFUNFIX | JIT_OPTIONS #define OOJS_STACK_SIZE 8192 static OOJavaScriptEngine *sSharedEngine = nil; static unsigned sErrorHandlerStackSkip = 0; JSContext *gOOJSMainThreadContext = NULL; NSString * const kOOJavaScriptEngineWillResetNotification = @"org.aegidian.oolite OOJavaScriptEngine will reset"; NSString * const kOOJavaScriptEngineDidResetNotification = @"org.aegidian.oolite OOJavaScriptEngine did reset"; #if OOJSENGINE_MONITOR_SUPPORT @interface OOJavaScriptEngine (OOMonitorSupportInternal) - (void)sendMonitorError:(JSErrorReport *)errorReport withMessage:(NSString *)message inContext:(JSContext *)context; - (void)sendMonitorLogMessage:(NSString *)message withMessageClass:(NSString *)messageClass inContext:(JSContext *)context; @end #endif @interface OOJavaScriptEngine (Private) - (BOOL) lookUpStandardClassPointers; - (void) registerStandardObjectConverters; - (void) createMainThreadContext; - (void) destroyMainThreadContext; @end static void ReportJSError(JSContext *context, const char *message, JSErrorReport *report); static id JSArrayConverter(JSContext *context, JSObject *object); static id JSStringConverter(JSContext *context, JSObject *object); static id JSNumberConverter(JSContext *context, JSObject *object); static id JSBooleanConverter(JSContext *context, JSObject *object); static void UnregisterObjectConverters(void); static void UnregisterSubclasses(void); static void ReportJSError(JSContext *context, const char *message, JSErrorReport *report) { NSString *severity = @"error"; NSString *messageText = nil; NSString *lineBuf = nil; NSString *messageClass = nil; NSString *highlight = @"*****"; NSString *activeScript = nil; OOJavaScriptEngine *jsEng = [OOJavaScriptEngine sharedEngine]; BOOL showLocation = [jsEng showErrorLocations]; // Not OOJS_BEGIN_FULL_NATIVE() - we use JSAPI while paused. OOJSPauseTimeLimiter(); jschar empty[1] = { 0 }; JSErrorReport blankReport = { .filename = "", .linebuf = "", .uclinebuf = empty, .uctokenptr = empty, .ucmessage = empty }; if (EXPECT_NOT(report == NULL)) report = &blankReport; if (EXPECT_NOT(message == NULL || *message == '\0')) message = ""; // Type of problem: error, warning or exception? (Strict flag wilfully ignored.) if (report->flags & JSREPORT_EXCEPTION) severity = @"exception"; else if (report->flags & JSREPORT_WARNING) { severity = @"warning"; highlight = @"-----"; } // The error message itself messageText = [NSString stringWithUTF8String:message]; // Get offending line, if present, and trim trailing line breaks lineBuf = [NSString stringWithUTF16String:report->uclinebuf]; while ([lineBuf hasSuffix:@"\n"] || [lineBuf hasSuffix:@"\r"]) lineBuf = [lineBuf substringToIndex:[lineBuf length] - 1]; // Get string for error number, for useful log message classes NSDictionary *errorNames = [ResourceManager dictionaryFromFilesNamed:@"javascript-errors.plist" inFolder:@"Config" andMerge:YES]; NSString *errorNumberStr = [NSString stringWithFormat:@"%u", report->errorNumber]; NSString *errorName = [errorNames oo_stringForKey:errorNumberStr]; if (errorName == nil) errorName = errorNumberStr; // Log message class messageClass = [NSString stringWithFormat:@"script.javaScript.%@.%@", severity, errorName]; // Skip the rest if this is a warning being ignored. if ((report->flags & JSREPORT_WARNING) == 0 || OOLogWillDisplayMessagesInClass(messageClass)) { // First line: problem description // avoid windows DEP exceptions! OOJSScript *thisScript = [[OOJSScript currentlyRunningScript] weakRetain]; activeScript = [[thisScript weakRefUnderlyingObject] displayName]; [thisScript release]; if (activeScript == nil) activeScript = @""; OOLog(messageClass, @"%@ JavaScript %@ (%@): %@", highlight, severity, activeScript, messageText); if (showLocation && sErrorHandlerStackSkip == 0 && report->filename != NULL) { // Second line: where error occured, and line if provided. (The line is only provided for compile-time errors, not run-time errors.) if ([lineBuf length] != 0) { OOLog(messageClass, @" %s, line %d: %@", report->filename, report->lineno, lineBuf); } else { OOLog(messageClass, @" %s, line %d.", report->filename, report->lineno); } } #ifndef NDEBUG BOOL dump; if (report->flags & JSREPORT_WARNING) dump = [jsEng dumpStackForWarnings]; else dump = [jsEng dumpStackForErrors]; if (dump) OOJSDumpStack(context); #endif #if OOJSENGINE_MONITOR_SUPPORT JSExceptionState *exState = JS_SaveExceptionState(context); [[OOJavaScriptEngine sharedEngine] sendMonitorError:report withMessage:messageText inContext:context]; JS_RestoreExceptionState(context, exState); #endif } OOJSResumeTimeLimiter(); } //=========================================================================== // JavaScript engine initialisation and shutdown //=========================================================================== @implementation OOJavaScriptEngine + (OOJavaScriptEngine *) sharedEngine { if (sSharedEngine == nil) sSharedEngine = [[self alloc] init]; return sSharedEngine; } - (void) runMissionCallback { MissionRunCallback(); } - (id) init { NSAssert(sSharedEngine == nil, @"Attempt to create multiple OOJavaScriptEngines."); if (!(self = [super init])) return nil; sSharedEngine = self; JS_SetCStringsAreUTF8(); #ifndef NDEBUG /* Set stack trace preferences from preferences. These will be overriden by the debug OXP script if installed, but being able to enable traces without setting up the debug console could be useful for debugging users' problems. */ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; [self setDumpStackForErrors:[defaults boolForKey:@"dump-stack-for-errors"]]; [self setDumpStackForWarnings:[defaults boolForKey:@"dump-stack-for-warnings"]]; #endif assert(sizeof(jschar) == sizeof(unichar)); // initialize the JS run time, and return result in runtime. _runtime = JS_NewRuntime(32L * 1024L * 1024L); // if runtime creation failed, end the program here. if (_runtime == NULL) { OOLog(@"script.javaScript.init.error", @"***** FATAL ERROR: failed to create JavaScript runtime."); exit(1); } // OOJSTimeManagementInit() must be called before any context is created! OOJSTimeManagementInit(self, _runtime); [self createMainThreadContext]; return self; } - (void) createMainThreadContext { NSAssert(gOOJSMainThreadContext == NULL, @"-[OOJavaScriptEngine createMainThreadContext] called while the main thread context exists."); // create a context and associate it with the JS runtime. gOOJSMainThreadContext = JS_NewContext(_runtime, OOJS_STACK_SIZE); // if context creation failed, end the program here. if (gOOJSMainThreadContext == NULL) { OOLog(@"script.javaScript.init.error", @"***** FATAL ERROR: failed to create JavaScript context."); exit(1); } JS_BeginRequest(gOOJSMainThreadContext); JS_SetOptions(gOOJSMainThreadContext, OOJSENGINE_CONTEXT_OPTIONS); JS_SetVersion(gOOJSMainThreadContext, OOJSENGINE_JSVERSION); #if JS_GC_ZEAL uint8_t gcZeal = [[NSUserDefaults standardUserDefaults] oo_unsignedCharForKey:@"js-gc-zeal"]; if (gcZeal > 0) { // Useful js-gc-zeal values are 0 (off), 1 and 2. OOLog(@"script.javaScript.debug.gcZeal", @"Setting JavaScript garbage collector zeal to %u.", gcZeal); JS_SetGCZeal(gOOJSMainThreadContext, gcZeal); } #endif JS_SetErrorReporter(gOOJSMainThreadContext, ReportJSError); // Create the global object. CreateOOJSGlobal(gOOJSMainThreadContext, &_globalObject); // Initialize the built-in JS objects and the global object. JS_InitStandardClasses(gOOJSMainThreadContext, _globalObject); if (![self lookUpStandardClassPointers]) { OOLog(@"script.javaScript.init.error", @"***** FATAL ERROR: failed to look up standard JavaScript classes."); exit(1); } [self registerStandardObjectConverters]; SetUpOOJSGlobal(gOOJSMainThreadContext, _globalObject); OOConstToJSStringInit(gOOJSMainThreadContext); // Initialize Oolite classes. InitOOJSMissionVariables(gOOJSMainThreadContext, _globalObject); InitOOJSMission(gOOJSMainThreadContext, _globalObject); InitOOJSOolite(gOOJSMainThreadContext, _globalObject); InitOOJSVector(gOOJSMainThreadContext, _globalObject); InitOOJSQuaternion(gOOJSMainThreadContext, _globalObject); InitOOJSSystem(gOOJSMainThreadContext, _globalObject); InitOOJSEntity(gOOJSMainThreadContext, _globalObject); InitOOJSShip(gOOJSMainThreadContext, _globalObject); InitOOJSStation(gOOJSMainThreadContext, _globalObject); InitOOJSDock(gOOJSMainThreadContext, _globalObject); InitOOJSVisualEffect(gOOJSMainThreadContext, _globalObject); InitOOJSExhaustPlume(gOOJSMainThreadContext, _globalObject); InitOOJSFlasher(gOOJSMainThreadContext, _globalObject); InitOOJSWormhole(gOOJSMainThreadContext, _globalObject); InitOOJSWaypoint(gOOJSMainThreadContext, _globalObject); InitOOJSPlayer(gOOJSMainThreadContext, _globalObject); InitOOJSPlayerShip(gOOJSMainThreadContext, _globalObject); InitOOJSManifest(gOOJSMainThreadContext, _globalObject); InitOOJSSun(gOOJSMainThreadContext, _globalObject); InitOOJSPlanet(gOOJSMainThreadContext, _globalObject); InitOOJSScript(gOOJSMainThreadContext, _globalObject); InitOOJSTimer(gOOJSMainThreadContext, _globalObject); InitOOJSClock(gOOJSMainThreadContext, _globalObject); InitOOJSWorldScripts(gOOJSMainThreadContext, _globalObject); InitOOJSSound(gOOJSMainThreadContext, _globalObject); InitOOJSSoundSource(gOOJSMainThreadContext, _globalObject); InitOOJSSpecialFunctions(gOOJSMainThreadContext, _globalObject); InitOOJSSystemInfo(gOOJSMainThreadContext, _globalObject); InitOOJSEquipmentInfo(gOOJSMainThreadContext, _globalObject); InitOOJSShipGroup(gOOJSMainThreadContext, _globalObject); InitOOJSFrameCallbacks(gOOJSMainThreadContext, _globalObject); InitOOJSFont(gOOJSMainThreadContext, _globalObject); // Run prefix scripts. [OOJSScript jsScriptFromFileNamed:@"oolite-global-prefix.js" properties:[NSDictionary dictionaryWithObject:JSSpecialFunctionsObjectWrapper(gOOJSMainThreadContext) forKey:@"special"]]; JS_EndRequest(gOOJSMainThreadContext); OOLog(@"script.javaScript.init.success", @"Set up JavaScript context."); } - (void) destroyMainThreadContext { if (gOOJSMainThreadContext != NULL) { JSContext *context = OOJSAcquireContext(); JS_ClearScope(gOOJSMainThreadContext, _globalObject); _globalObject = NULL; _objectClass = NULL; _stringClass = NULL; _arrayClass = NULL; _numberClass = NULL; _booleanClass = NULL; UnregisterObjectConverters(); UnregisterSubclasses(); OOConstToJSStringDestroy(); OOJSRelinquishContext(context); _globalObject = NULL; JS_DestroyContext(gOOJSMainThreadContext); // Forces unconditional GC. gOOJSMainThreadContext = NULL; } } - (BOOL) reset { NSAssert(gOOJSMainThreadContext != NULL, @"JavaScript engine not active. Can't reset."); OOJSFrameCallbacksRemoveAll(); # if 0 // deferred JS reset - test harness. static int counter = 3; // loading a savegame with different strict mode calls js reset twice if (counter-- == 0) { counter = 3; OOLog(@"script.javascript.init.error", @"JavaScript processes still pending. Can't reset JavaScript engine."); return NO; } else { OOLog(@"script.javascript.init", @"JavaScript reset successful."); } #endif #if JS_THREADSAFE //NSAssert(!JS_IsInRequest(gOOJSMainThreadContext), @"JavaScript processes still pending. Can't reset JavaScript engine."); if (JS_IsInRequest(gOOJSMainThreadContext)) { // some threads are still pending, this should mean timers are still being removed. OOLog(@"script.javascript.init.error", @"JavaScript processes still pending. Can't reset JavaScript engine."); return NO; } else { OOLog(@"script.javascript.init", @"JavaScript reset successful."); } #endif JSContext *context = OOJSAcquireContext(); [[NSNotificationCenter defaultCenter] postNotificationName:kOOJavaScriptEngineWillResetNotification object:self]; OOJSRelinquishContext(context); [self destroyMainThreadContext]; [self createMainThreadContext]; context = OOJSAcquireContext(); [[NSNotificationCenter defaultCenter] postNotificationName:kOOJavaScriptEngineDidResetNotification object:self]; OOJSRelinquishContext(context); [self garbageCollectionOpportunity:YES]; return YES; } - (void) dealloc { sSharedEngine = nil; OOJSFrameCallbacksRemoveAll(); [self destroyMainThreadContext]; JS_DestroyRuntime(_runtime); [super dealloc]; } - (JSObject *) globalObject { return _globalObject; } - (BOOL) callJSFunction:(jsval)function forObject:(JSObject *)jsThis argc:(uintN)argc argv:(jsval *)argv result:(jsval *)outResult { JSContext *context = NULL; BOOL result; NSParameterAssert(OOJSValueIsFunction(context, function)); context = OOJSAcquireContext(); OOJSStartTimeLimiter(); result = JS_CallFunctionValue(context, jsThis, function, argc, argv, outResult); OOJSStopTimeLimiter(); JS_ReportPendingException(context); OOJSRelinquishContext(context); return result; } - (void) removeGCObjectRoot:(JSObject **)rootPtr { JSContext *context = OOJSAcquireContext(); JS_RemoveObjectRoot(context, rootPtr); OOJSRelinquishContext(context); } - (void) removeGCValueRoot:(jsval *)rootPtr { JSContext *context = OOJSAcquireContext(); JS_RemoveValueRoot(context, rootPtr); OOJSRelinquishContext(context); } - (void) garbageCollectionOpportunity:(BOOL)force { JSContext *context = OOJSAcquireContext(); if (force) { JS_GC(context); } else { JS_MaybeGC(context); } OOJSRelinquishContext(context); } - (BOOL) showErrorLocations { return _showErrorLocations; } - (void) setShowErrorLocations:(BOOL)value { _showErrorLocations = !!value; } - (JSClass *) objectClass { return _objectClass; } - (JSClass *) stringClass { return _stringClass; } - (JSClass *) arrayClass { return _arrayClass; } - (JSClass *) numberClass { return _numberClass; } - (JSClass *) booleanClass { return _booleanClass; } - (BOOL) lookUpStandardClassPointers { JSObject *templateObject = NULL; templateObject = JS_NewObject(gOOJSMainThreadContext, NULL, NULL, NULL); if (EXPECT_NOT(templateObject == NULL)) return NO; _objectClass = OOJSGetClass(gOOJSMainThreadContext, templateObject); if (EXPECT_NOT(!JS_ValueToObject(gOOJSMainThreadContext, JS_GetEmptyStringValue(gOOJSMainThreadContext), &templateObject))) return NO; _stringClass = OOJSGetClass(gOOJSMainThreadContext, templateObject); templateObject = JS_NewArrayObject(gOOJSMainThreadContext, 0, NULL); if (EXPECT_NOT(templateObject == NULL)) return NO; _arrayClass = OOJSGetClass(gOOJSMainThreadContext, templateObject); if (EXPECT_NOT(!JS_ValueToObject(gOOJSMainThreadContext, INT_TO_JSVAL(0), &templateObject))) return NO; _numberClass = OOJSGetClass(gOOJSMainThreadContext, templateObject); if (EXPECT_NOT(!JS_ValueToObject(gOOJSMainThreadContext, JSVAL_FALSE, &templateObject))) return NO; _booleanClass = OOJSGetClass(gOOJSMainThreadContext, templateObject); return YES; } - (void) registerStandardObjectConverters { OOJSRegisterObjectConverter([self objectClass], (OOJSClassConverterCallback)OOJSDictionaryFromJSObject); OOJSRegisterObjectConverter([self stringClass], JSStringConverter); OOJSRegisterObjectConverter([self arrayClass], JSArrayConverter); OOJSRegisterObjectConverter([self numberClass], JSNumberConverter); OOJSRegisterObjectConverter([self booleanClass], JSBooleanConverter); } #ifndef NDEBUG static JSTrapStatus DebuggerHook(JSContext *context, JSScript *script, jsbytecode *pc, jsval *rval, void *closure) { OOJSPauseTimeLimiter(); OOLog(@"script.javaScript.debugger", @"debugger invoked during %@:", [[OOJSScript currentlyRunningScript] displayName]); OOJSDumpStack(context); OOJSResumeTimeLimiter(); return JSTRAP_CONTINUE; } - (BOOL) dumpStackForErrors { return _dumpStackForErrors; } - (void) setDumpStackForErrors:(BOOL)value { _dumpStackForErrors = !!value; } - (BOOL) dumpStackForWarnings { return _dumpStackForWarnings; } - (void) setDumpStackForWarnings:(BOOL)value { _dumpStackForWarnings = !!value; } - (void) enableDebuggerStatement { JS_SetDebuggerHandler(_runtime, DebuggerHook, self); } #endif @end #if OOJSENGINE_MONITOR_SUPPORT @implementation OOJavaScriptEngine (OOMonitorSupport) - (void) setMonitor:(id)inMonitor { [_monitor autorelease]; _monitor = [inMonitor retain]; } @end @implementation OOJavaScriptEngine (OOMonitorSupportInternal) - (void) sendMonitorError:(JSErrorReport *)errorReport withMessage:(NSString *)message inContext:(JSContext *)theContext { if ([_monitor respondsToSelector:@selector(jsEngine:context:error:stackSkip:showingLocation:withMessage:)]) { [_monitor jsEngine:self context:theContext error:errorReport stackSkip:sErrorHandlerStackSkip showingLocation:[self showErrorLocations] withMessage:message]; } } - (void) sendMonitorLogMessage:(NSString *)message withMessageClass:(NSString *)messageClass inContext:(JSContext *)theContext { if ([_monitor respondsToSelector:@selector(jsEngine:context:logMessage:ofClass:)]) { [_monitor jsEngine:self context:theContext logMessage:message ofClass:messageClass]; } } @end #endif #ifndef NDEBUG static void DumpVariable(JSContext *context, JSPropertyDesc *prop) { NSString *name = OOStringFromJSValueEvenIfNull(context, prop->id); NSString *value = OOJSDescribeValue(context, prop->value, YES); enum { kInterestingFlags = ~(JSPD_ENUMERATE | JSPD_PERMANENT | JSPD_VARIABLE | JSPD_ARGUMENT) }; NSString *flagStr = @""; if ((prop->flags & kInterestingFlags) != 0) { NSMutableArray *flags = [NSMutableArray array]; if (prop->flags & JSPD_READONLY) [flags addObject:@"read-only"]; if (prop->flags & JSPD_ALIAS) [flags addObject:[NSString stringWithFormat:@"alias (%@)", OOJSDescribeValue(context, prop->alias, YES)]]; if (prop->flags & JSPD_EXCEPTION) [flags addObject:@"exception"]; if (prop->flags & JSPD_ERROR) [flags addObject:@"error"]; flagStr = [NSString stringWithFormat:@" [%@]", [flags componentsJoinedByString:@", "]]; } OOLog(@"script.javaScript.stackTrace", @" %@: %@%@", name, value, flagStr); } void OOJSDumpStack(JSContext *context) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; @try { JSStackFrame *frame = NULL; unsigned idx = 0; unsigned skip = sErrorHandlerStackSkip; while (JS_FrameIterator(context, &frame) != NULL) { JSScript *script = JS_GetFrameScript(context, frame); NSString *desc = nil; JSPropertyDescArray properties = { 0 , NULL }; BOOL gotProperties = NO; idx++; if (!JS_IsScriptFrame(context, frame)) { continue; } if (skip != 0) { skip--; continue; } if (script != NULL) { NSString *location = OOJSDescribeLocation(context, frame); JSObject *scope = JS_GetFrameScopeChain(context, frame); if (scope != NULL) gotProperties = JS_GetPropertyDescArray(context, scope, &properties); NSString *funcDesc = nil; JSFunction *function = JS_GetFrameFunction(context, frame); if (function != NULL) { JSString *funcName = JS_GetFunctionId(function); if (funcName != NULL) { funcDesc = OOStringFromJSString(context, funcName); if (!JS_IsConstructorFrame(context, frame)) { funcDesc = [funcDesc stringByAppendingString:@"()"]; } else { funcDesc = [NSString stringWithFormat:@"new %@()", funcDesc]; } } else { funcDesc = @""; } } else { funcDesc = @""; } desc = [NSString stringWithFormat:@"(%@) %@", location, funcDesc]; } else if (JS_IsDebuggerFrame(context, frame)) { desc = @""; } else { desc = @""; } OOLog(@"script.javaScript.stackTrace", @"%2u %@", idx - 1, desc); if (gotProperties) { jsval this; if (JS_GetFrameThis(context, frame, &this)) { static BOOL haveThis = NO; static jsval thisAtom; if (EXPECT_NOT(!haveThis)) { thisAtom = STRING_TO_JSVAL(JS_InternString(context, "this")); haveThis = YES; } JSPropertyDesc thisDesc = { .id = thisAtom, .value = this }; DumpVariable(context, &thisDesc); } // Dump arguments. unsigned i; for (i = 0; i < properties.length; i++) { JSPropertyDesc *prop = &properties.array[i]; if (prop->flags & JSPD_ARGUMENT) DumpVariable(context, prop); } // Dump locals. for (i = 0; i < properties.length; i++) { JSPropertyDesc *prop = &properties.array[i]; if (prop->flags & JSPD_VARIABLE) DumpVariable(context, prop); } // Dump anything else. for (i = 0; i < properties.length; i++) { JSPropertyDesc *prop = &properties.array[i]; if (!(prop->flags & (JSPD_ARGUMENT | JSPD_VARIABLE))) DumpVariable(context, prop); } JS_PutPropertyDescArray(context, &properties); } } } @catch (NSException *exception) { OOLog(kOOLogException, @"Exception during JavaScript stack trace: %@:%@", [exception name], [exception reason]); } [pool release]; } static const char *sConsoleScriptName; // Lifetime is lifetime of script object, which is forever. static NSUInteger sConsoleEvalLineNo; static void GetLocationNameAndLine(JSContext *context, JSStackFrame *stackFrame, const char **name, NSUInteger *line) { NSCParameterAssert(context != NULL && stackFrame != NULL && name != NULL && line != NULL); *name = NULL; *line = 0; JSScript *script = JS_GetFrameScript(context, stackFrame); if (script != NULL) { *name = JS_GetScriptFilename(context, script); if (name != NULL) { jsbytecode *PC = JS_GetFramePC(context, stackFrame); *line = JS_PCToLineNumber(context, script, PC); } } else if (JS_IsDebuggerFrame(context, stackFrame)) { *name = ""; } } NSString *OOJSDescribeLocation(JSContext *context, JSStackFrame *stackFrame) { NSCParameterAssert(context != NULL && stackFrame != NULL); const char *fileName; NSUInteger lineNo; GetLocationNameAndLine(context, stackFrame, &fileName, &lineNo); if (fileName == NULL) return nil; // If this stops working, we probably need to switch to strcmp(). if (fileName == sConsoleScriptName && lineNo >= sConsoleEvalLineNo) return @""; // Objectify it. NSString *fileNameObj = [NSString stringWithUTF8String:fileName]; if (fileNameObj == nil) fileNameObj = [NSString stringWithCString:fileName encoding:NSISOLatin1StringEncoding]; if (fileNameObj == nil) return nil; NSString *shortFileName = [fileNameObj lastPathComponent]; if (![[shortFileName lowercaseString] isEqualToString:@"script.js"]) fileNameObj = shortFileName; return [NSString stringWithFormat:@"%@:%lu", fileNameObj, lineNo]; } void OOJSMarkConsoleEvalLocation(JSContext *context, JSStackFrame *stackFrame) { GetLocationNameAndLine(context, stackFrame, &sConsoleScriptName, &sConsoleEvalLineNo); } #endif void OOJSInitJSIDCachePRIVATE(const char *name, jsid *idCache) { NSCParameterAssert(name != NULL && name[0] != '\0' && idCache != NULL); JSContext *context = OOJSAcquireContext(); JSString *string = JS_InternString(context, name); if (EXPECT_NOT(string == NULL)) { [NSException raise:NSGenericException format:@"Failed to initialize JS ID cache for \"%s\".", name]; } *idCache = INTERNED_STRING_TO_JSID(string); OOJSRelinquishContext(context); } jsid OOJSIDFromString(NSString *string) { if (EXPECT_NOT(string == nil)) return JSID_VOID; JSContext *context = OOJSAcquireContext(); enum { kStackBufSize = 1024 }; unichar stackBuf[kStackBufSize]; unichar *buffer; size_t length = [string length]; if (length < kStackBufSize) { buffer = stackBuf; } else { buffer = malloc(sizeof (unichar) * length); if (EXPECT_NOT(buffer == NULL)) return JSID_VOID; } [string getCharacters:buffer]; JSString *jsString = JS_InternUCStringN(context, buffer, length); if (EXPECT_NOT(buffer != stackBuf)) free(buffer); OOJSRelinquishContext(context); if (EXPECT(jsString != NULL)) return INTERNED_STRING_TO_JSID(jsString); else return JSID_VOID; } NSString *OOStringFromJSID(jsid propID) { JSContext *context = OOJSAcquireContext(); jsval value; NSString *result = nil; if (JS_IdToValue(context, propID, &value)) { result = OOStringFromJSString(context, JS_ValueToString(context, value)); } OOJSRelinquishContext(context); return result; } static NSString *CallerPrefix(NSString *scriptClass, NSString *function) { if (function == nil) return @""; if (scriptClass == nil) return [function stringByAppendingString:@": "]; return [NSString stringWithFormat:@"%@.%@: ", scriptClass, function]; } void OOJSReportError(JSContext *context, NSString *format, ...) { va_list args; va_start(args, format); OOJSReportErrorWithArguments(context, format, args); va_end(args); } void OOJSReportErrorForCaller(JSContext *context, NSString *scriptClass, NSString *function, NSString *format, ...) { va_list args; NSString *msg = nil; @try { va_start(args, format); msg = [[NSString alloc] initWithFormat:format arguments:args]; va_end(args); OOJSReportError(context, @"%@%@", CallerPrefix(scriptClass, function), msg); } @catch (id exception) { // Squash any secondary errors during error handling. } [msg release]; } void OOJSReportErrorWithArguments(JSContext *context, NSString *format, va_list args) { NSString *msg = nil; NSCParameterAssert(JS_IsInRequest(context)); @try { msg = [[NSString alloc] initWithFormat:format arguments:args]; JS_ReportError(context, "%s", [msg UTF8String]); } @catch (id exception) { // Squash any secondary errors during error handling. } [msg release]; } void OOJSReportWrappedException(JSContext *context, id exception) { if (!JS_IsExceptionPending(context)) { if ([exception isKindOfClass:[NSException class]]) OOJSReportError(context, @"Native exception: %@", [exception reason]); else OOJSReportError(context, @"Unidentified native exception"); } // Else, let the pending exception propagate. } #ifndef NDEBUG void OOJSUnreachable(const char *function, const char *file, unsigned line) { OOLog(@"fatal.unreachable", @"Supposedly unreachable statement reached in %s (%@:%u) -- terminating.", function, OOLogAbbreviatedFileName(file), line); abort(); } #endif void OOJSReportWarning(JSContext *context, NSString *format, ...) { va_list args; va_start(args, format); OOJSReportWarningWithArguments(context, format, args); va_end(args); } void OOJSReportWarningForCaller(JSContext *context, NSString *scriptClass, NSString *function, NSString *format, ...) { va_list args; NSString *msg = nil; @try { va_start(args, format); msg = [[NSString alloc] initWithFormat:format arguments:args]; va_end(args); OOJSReportWarning(context, @"%@%@", CallerPrefix(scriptClass, function), msg); } @catch (id exception) { // Squash any secondary errors during error handling. } [msg release]; } void OOJSReportWarningWithArguments(JSContext *context, NSString *format, va_list args) { NSString *msg = nil; @try { msg = [[NSString alloc] initWithFormat:format arguments:args]; JS_ReportWarning(context, "%s", [msg UTF8String]); } @catch (id exception) { // Squash any secondary errors during error handling. } [msg release]; } void OOJSReportBadPropertySelector(JSContext *context, JSObject *thisObj, jsid propID, JSPropertySpec *propertySpec) { NSString *propName = OOStringFromJSPropertyIDAndSpec(context, propID, propertySpec); const char *className = OOJSGetClass(context, thisObj)->name; OOJSReportError(context, @"Invalid property identifier %@ for instance of %s.", propName, className); } void OOJSReportBadPropertyValue(JSContext *context, JSObject *thisObj, jsid propID, JSPropertySpec *propertySpec, jsval value) { NSString *propName = OOStringFromJSPropertyIDAndSpec(context, propID, propertySpec); const char *className = OOJSGetClass(context, thisObj)->name; NSString *valueDesc = OOJSDescribeValue(context, value, YES); OOJSReportError(context, @"Cannot set property %@ of instance of %s to invalid value %@.", propName, className, valueDesc); } void OOJSReportBadArguments(JSContext *context, NSString *scriptClass, NSString *function, uintN argc, jsval *argv, NSString *message, NSString *expectedArgsDescription) { @try { if (message == nil) message = @"Invalid arguments"; message = [NSString stringWithFormat:@"%@ %@", message, [NSString stringWithJavaScriptParameters:argv count:argc inContext:context]]; if (expectedArgsDescription != nil) message = [NSString stringWithFormat:@"%@ -- expected %@", message, expectedArgsDescription]; OOJSReportErrorForCaller(context, scriptClass, function, @"%@.", message); } @catch (id exception) { // Squash any secondary errors during error handling. } } void OOJSSetWarningOrErrorStackSkip(unsigned skip) { sErrorHandlerStackSkip = skip; } BOOL OOJSArgumentListGetNumber(JSContext *context, NSString *scriptClass, NSString *function, uintN argc, jsval *argv, double *outNumber, uintN *outConsumed) { if (OOJSArgumentListGetNumberNoError(context, argc, argv, outNumber, outConsumed)) { return YES; } else { OOJSReportBadArguments(context, scriptClass, function, argc, argv, @"Expected number, got", NULL); return NO; } } BOOL OOJSArgumentListGetNumberNoError(JSContext *context, uintN argc, jsval *argv, double *outNumber, uintN *outConsumed) { OOJS_PROFILE_ENTER double value; NSCParameterAssert(context != NULL && (argv != NULL || argc == 0) && outNumber != NULL); // Get value, if possible. if (EXPECT_NOT(!JS_ValueToNumber(context, argv[0], &value) || isnan(value))) { if (outConsumed != NULL) *outConsumed = 0; return NO; } // Success. *outNumber = value; if (outConsumed != NULL) *outConsumed = 1; return YES; OOJS_PROFILE_EXIT } static JSObject *JSArrayFromNSArray(JSContext *context, NSArray *array) { OOJS_PROFILE_ENTER JSObject *result = NULL; if (array == nil) return NULL; @try { NSUInteger fullCount = [array count]; if (EXPECT_NOT(fullCount > INT32_MAX)) { return NULL; } uint32_t i, count = (int32_t)fullCount; result = JS_NewArrayObject(context, 0, NULL); if (result != NULL) { for (i = 0; i != count; ++i) { jsval value = [[array objectAtIndex:i] oo_jsValueInContext:context]; BOOL OK = JS_SetElement(context, result, i, &value); if (EXPECT_NOT(!OK)) { result = NULL; break; } } } } @catch (id ex) { result = NULL; } return (JSObject *)result; OOJS_PROFILE_EXIT } static BOOL JSNewNSArrayValue(JSContext *context, NSArray *array, jsval *value) { OOJS_PROFILE_ENTER JSObject *object = NULL; BOOL OK = YES; if (value == NULL) return NO; // NOTE: should be called within a local root scope or have *value be a set root for GC reasons. if (!JS_EnterLocalRootScope(context)) return NO; object = JSArrayFromNSArray(context, array); if (object == NULL) { *value = JSVAL_VOID; OK = NO; } else { *value = OBJECT_TO_JSVAL(object); } JS_LeaveLocalRootScopeWithResult(context, *value); return OK; OOJS_PROFILE_EXIT } /* Convert an NSDictionary to a JavaScript Object. Only properties whose keys are either strings or non-negative NSNumbers, and whose values have a non-void JS representation, are converted. */ static JSObject *JSObjectFromNSDictionary(JSContext *context, NSDictionary *dictionary) { OOJS_PROFILE_ENTER JSObject *result = NULL; BOOL OK = YES; NSEnumerator *keyEnum = nil; id key = nil; jsval value; jsint index; if (dictionary == nil) return NULL; @try { result = JS_NewObject(context, NULL, NULL, NULL); // create object of class Object if (result != NULL) { for (keyEnum = [dictionary keyEnumerator]; (key = [keyEnum nextObject]); ) { if ([key isKindOfClass:[NSString class]] && [key length] != 0) { #ifndef __GNUC__ value = [[dictionary objectForKey:key] oo_jsValueInContext:context]; #else #if __GNUC__ > 4 || __GNUC_MINOR__ > 6 value = [[dictionary objectForKey:key] oo_jsValueInContext:context]; #else // GCC before 4.7 seems to have problems with this // bit if the object is a weakref, causing crashes // in docking code. id tmp = [dictionary objectForKey:key]; if ([tmp respondsToSelector:@selector(weakRefUnderlyingObject)]) { tmp = [tmp weakRefUnderlyingObject]; } value = [tmp oo_jsValueInContext:context]; #endif #endif if (!JSVAL_IS_VOID(value)) { OK = JS_SetPropertyById(context, result, OOJSIDFromString(key), &value); if (EXPECT_NOT(!OK)) break; } } else if ([key isKindOfClass:[NSNumber class]]) { index = [key intValue]; if (0 < index) { value = [[dictionary objectForKey:key] oo_jsValueInContext:context]; if (!JSVAL_IS_VOID(value)) { OK = JS_SetElement(context, (JSObject *)result, index, &value); if (EXPECT_NOT(!OK)) break; } } } if (EXPECT_NOT(!OK)) break; } } } @catch (id exception) { OK = NO; } if (EXPECT_NOT(!OK)) { result = NULL; } return (JSObject *)result; OOJS_PROFILE_EXIT } static BOOL JSNewNSDictionaryValue(JSContext *context, NSDictionary *dictionary, jsval *value) { OOJS_PROFILE_ENTER JSObject *object = NULL; BOOL OK = YES; if (value == NULL) return NO; // NOTE: should be called within a local root scope or have *value be a set root for GC reasons. if (!JS_EnterLocalRootScope(context)) return NO; object = JSObjectFromNSDictionary(context, dictionary); if (object == NULL) { *value = JSVAL_VOID; OK = NO; } else { *value = OBJECT_TO_JSVAL(object); } JS_LeaveLocalRootScopeWithResult(context, *value); return OK; OOJS_PROFILE_EXIT } @implementation NSObject (OOJavaScriptConversion) - (jsval) oo_jsValueInContext:(JSContext *)context { return JSVAL_VOID; } - (NSString *) oo_jsClassName { return nil; } - (NSString *) oo_jsDescription { return [self oo_jsDescriptionWithClassName:[self oo_jsClassName]]; } - (NSString *) oo_jsDescriptionWithClassName:(NSString *)className { OOJS_PROFILE_ENTER NSString *components = nil; NSString *description = nil; components = [self descriptionComponents]; if (className == nil) className = [[self class] description]; if (components != nil) { description = [NSString stringWithFormat:@"[%@ %@]", className, components]; } else { description = [NSString stringWithFormat:@"[object %@]", className]; } return description; OOJS_PROFILE_EXIT } - (void) oo_clearJSSelf:(JSObject *)selfVal { } @end JSObject *OOJSObjectFromNativeObject(JSContext *context, id object) { jsval value = OOJSValueFromNativeObject(context, object); JSObject *result = NULL; if (JS_ValueToObject(context, value, &result)) return result; return NULL; } @implementation OOJSValue + (id) valueWithJSValue:(jsval)value inContext:(JSContext *)context { OOJS_PROFILE_ENTER return [[[self alloc] initWithJSValue:value inContext:context] autorelease]; OOJS_PROFILE_EXIT } + (id) valueWithJSObject:(JSObject *)object inContext:(JSContext *)context { OOJS_PROFILE_ENTER return [[[self alloc] initWithJSObject:object inContext:context] autorelease]; OOJS_PROFILE_EXIT } - (id) initWithJSValue:(jsval)value inContext:(JSContext *)context { OOJS_PROFILE_ENTER self = [super init]; if (self != nil) { BOOL tempCtxt = NO; if (context == NULL) { context = OOJSAcquireContext(); tempCtxt = YES; } _val = value; if (!JSVAL_IS_VOID(_val)) { JS_AddNamedValueRoot(context, &_val, "OOJSValue"); [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deleteJSValue) name:kOOJavaScriptEngineWillResetNotification object:[OOJavaScriptEngine sharedEngine]]; } if (tempCtxt) OOJSRelinquishContext(context); } return self; OOJS_PROFILE_EXIT } - (id) initWithJSObject:(JSObject *)object inContext:(JSContext *)context { return [self initWithJSValue:OBJECT_TO_JSVAL(object) inContext:context]; } - (void) deleteJSValue { if (!JSVAL_IS_VOID(_val)) { JSContext *context = OOJSAcquireContext(); JS_RemoveValueRoot(context, &_val); OOJSRelinquishContext(context); _val = JSVAL_VOID; [[NSNotificationCenter defaultCenter] removeObserver:self name:kOOJavaScriptEngineWillResetNotification object:[OOJavaScriptEngine sharedEngine]]; } } - (void) dealloc { [self deleteJSValue]; [super dealloc]; } - (jsval) oo_jsValueInContext:(JSContext *)context { return _val; } @end void OOJSStrLiteralCachePRIVATE(const char *string, jsval *strCache, BOOL *inited) { NSCParameterAssert(string != NULL && strCache != NULL && inited != NULL && !*inited); JSContext *context = OOJSAcquireContext(); JSString *jsString = JS_InternString(context, string); if (EXPECT_NOT(string == NULL)) { [NSException raise:NSGenericException format:@"Failed to initialize JavaScript string literal cache for \"%@\".", [[NSString stringWithUTF8String:string] escapedForJavaScriptLiteral]]; } *strCache = STRING_TO_JSVAL(jsString); *inited = YES; OOJSRelinquishContext(context); } NSString *OOStringFromJSString(JSContext *context, JSString *string) { OOJS_PROFILE_ENTER if (EXPECT_NOT(string == NULL)) return nil; size_t length; const jschar *chars = JS_GetStringCharsAndLength(context, string, &length); if (EXPECT(chars != NULL)) { return [NSString stringWithCharacters:chars length:length]; } else { return nil; } OOJS_PROFILE_EXIT } NSString *OOStringFromJSValueEvenIfNull(JSContext *context, jsval value) { OOJS_PROFILE_ENTER NSCParameterAssert(context != NULL && JS_IsInRequest(context)); JSString *string = JS_ValueToString(context, value); // Calls the value's toString method if needed. return OOStringFromJSString(context, string); OOJS_PROFILE_EXIT } NSString *OOStringFromJSValue(JSContext *context, jsval value) { OOJS_PROFILE_ENTER if (EXPECT(!JSVAL_IS_NULL(value) && !JSVAL_IS_VOID(value))) { return OOStringFromJSValueEvenIfNull(context, value); } return nil; OOJS_PROFILE_EXIT } NSString *OOStringFromJSPropertyIDAndSpec(JSContext *context, jsid propID, JSPropertySpec *propertySpec) { if (JSID_IS_STRING(propID)) { return OOStringFromJSString(context, JSID_TO_STRING(propID)); } else if (JSID_IS_INT(propID) && propertySpec != NULL) { int tinyid = JSID_TO_INT(propID); while (propertySpec->name != NULL) { if (propertySpec->tinyid == tinyid) return [NSString stringWithUTF8String:propertySpec->name]; propertySpec++; } } jsval value; if (!JS_IdToValue(context, propID, &value)) return @"unknown"; return OOStringFromJSString(context, JS_ValueToString(context, value)); } static NSString *DescribeValue(JSContext *context, jsval value, BOOL abbreviateObjects, BOOL recursing) { OOJS_PROFILE_ENTER NSCParameterAssert(context != NULL && JS_IsInRequest(context)); if (OOJSValueIsFunction(context, value)) { JSString *name = JS_GetFunctionId(JS_ValueToFunction(context, value)); if (name != NULL) return [NSString stringWithFormat:@"function %@", OOStringFromJSString(context, name)]; else return @"function"; } NSString *result = nil; JSClass *class = NULL; OOJavaScriptEngine *jsEng = [OOJavaScriptEngine sharedEngine]; if (JSVAL_IS_OBJECT(value) && !JSVAL_IS_NULL(value)) { class = OOJSGetClass(context, JSVAL_TO_OBJECT(value)); } // Convert String objects to strings. if (class == [jsEng stringClass]) { value = STRING_TO_JSVAL(JS_ValueToString(context, value)); } if (JSVAL_IS_STRING(value)) { enum { kMaxLength = 200 }; JSString *string = JSVAL_TO_STRING(value); size_t length; const jschar *chars = JS_GetStringCharsAndLength(context, string, &length); result = [NSString stringWithCharacters:chars length:MIN(length, (size_t)kMaxLength)]; result = [NSString stringWithFormat:@"\"%@%@\"", [result escapedForJavaScriptLiteral], (length > kMaxLength) ? @"..." : @""]; } else if (class == [jsEng arrayClass]) { // Descibe up to four elements of an array. jsuint count; JSObject *obj = JSVAL_TO_OBJECT(value); if (JS_GetArrayLength(context, obj, &count)) { if (!recursing) { NSMutableString *arrayDesc = [NSMutableString stringWithString:@"["]; jsuint i, effectiveCount = MIN(count, (jsuint)4); for (i = 0; i < effectiveCount; i++) { jsval item; NSString *itemDesc = @"?"; if (JS_GetElement(context, obj, i, &item)) { itemDesc = DescribeValue(context, item, YES /* always abbreviate objects in arrays */, YES); } if (i != 0) [arrayDesc appendString:@", "]; [arrayDesc appendString:itemDesc]; } if (effectiveCount != count) { [arrayDesc appendFormat:@", ... <%u items total>]", count]; } else { [arrayDesc appendString:@"]"]; } result = arrayDesc; } else { result = [NSString stringWithFormat:@"[<%u items>]", count]; } } else { result = @"[...]"; } } if (result == nil) { result = OOStringFromJSValueEvenIfNull(context, value); if (abbreviateObjects && class == [jsEng objectClass] && [result isEqualToString:@"[object Object]"]) { result = @"{...}"; } if (result == nil) result = @"?"; } return result; OOJS_PROFILE_EXIT } NSString *OOJSDescribeValue(JSContext *context, jsval value, BOOL abbreviateObjects) { return DescribeValue(context, value, abbreviateObjects, NO); } @implementation NSString (OOJavaScriptExtensions) + (NSString *) stringWithJavaScriptParameters:(jsval *)params count:(uintN)count inContext:(JSContext *)context { OOJS_PROFILE_ENTER if (params == NULL && count != 0) return nil; uintN i; NSMutableString *result = [NSMutableString stringWithString:@"("]; for (i = 0; i < count; ++i) { if (i != 0) [result appendString:@", "]; [result appendString:OOJSDescribeValue(context, params[i], NO)]; } [result appendString:@")"]; return result; OOJS_PROFILE_EXIT } - (jsval) oo_jsValueInContext:(JSContext *)context { OOJS_PROFILE_ENTER size_t length = [self length]; unichar *buffer = NULL; JSString *string = NULL; if (length == 0) { jsval result = JS_GetEmptyStringValue(context); return result; } else { buffer = malloc(length * sizeof *buffer); if (buffer == NULL) return JSVAL_VOID; [self getCharacters:buffer]; string = JS_NewUCStringCopyN(context, buffer, length); free(buffer); return STRING_TO_JSVAL(string); } OOJS_PROFILE_EXIT_JSVAL } + (NSString *) concatenationOfStringsFromJavaScriptValues:(jsval *)values count:(size_t)count separator:(NSString *)separator inContext:(JSContext *)context { OOJS_PROFILE_ENTER size_t i; NSMutableString *result = nil; NSString *element = nil; if (count < 1) return nil; if (values == NULL) return NULL; for (i = 0; i != count; ++i) { element = OOStringFromJSValueEvenIfNull(context, values[i]); if (result == nil) result = [[element mutableCopy] autorelease]; else { if (separator != nil) [result appendString:separator]; [result appendString:element]; } } return result; OOJS_PROFILE_EXIT } - (NSString *)escapedForJavaScriptLiteral { OOJS_PROFILE_ENTER NSMutableString *result = nil; NSUInteger i, length; unichar c; NSAutoreleasePool *pool = nil; length = [self length]; result = [NSMutableString stringWithCapacity:length]; // Not hugely efficient. pool = [[NSAutoreleasePool alloc] init]; for (i = 0; i != length; ++i) { c = [self characterAtIndex:i]; switch (c) { case '\\': [result appendString:@"\\\\"]; break; case '\b': [result appendString:@"\\b"]; break; case '\f': [result appendString:@"\\f"]; break; case '\n': [result appendString:@"\\n"]; break; case '\r': [result appendString:@"\\r"]; break; case '\t': [result appendString:@"\\t"]; break; case '\v': [result appendString:@"\\v"]; break; case '\'': [result appendString:@"\\\'"]; break; case '\"': [result appendString:@"\\\""]; break; default: [result appendString:[NSString stringWithCharacters:&c length:1]]; } } [pool release]; return result; OOJS_PROFILE_EXIT } - (NSString *) oo_jsClassName { return @"String"; } @end @implementation NSArray (OOJavaScriptConversion) - (jsval)oo_jsValueInContext:(JSContext *)context { jsval value = JSVAL_VOID; JSNewNSArrayValue(context, self, &value); return value; } @end @implementation NSDictionary (OOJavaScriptConversion) - (jsval)oo_jsValueInContext:(JSContext *)context { jsval value = JSVAL_VOID; JSNewNSDictionaryValue(context, self, &value); return value; } @end @implementation NSNumber (OOJavaScriptConversion) - (jsval)oo_jsValueInContext:(JSContext *)context { OOJS_PROFILE_ENTER jsval result; BOOL isFloat = NO; long long longLongValue; isFloat = [self oo_isFloatingPointNumber]; if (!isFloat) { longLongValue = [self longLongValue]; if (longLongValue < (long long)JSVAL_INT_MIN || (long long)JSVAL_INT_MAX < longLongValue) { // values outside JSVAL_INT range are returned as doubles. isFloat = YES; } } if (isFloat) { if (!JS_NewNumberValue(context, [self doubleValue], &result)) result = JSVAL_VOID; } else { result = INT_TO_JSVAL((int32_t)longLongValue); } return result; OOJS_PROFILE_EXIT_JSVAL } - (NSString *) oo_jsClassName { return @"Number"; } @end @implementation NSNull (OOJavaScriptConversion) - (jsval)oo_jsValueInContext:(JSContext *)context { return JSVAL_NULL; } @end JSBool OOJSUnconstructableConstruct(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) JSFunction *function = JS_ValueToFunction(context, JS_CALLEE(context, vp)); NSString *name = OOStringFromJSString(context, JS_GetFunctionId(function)); OOJSReportError(context, @"%@ cannot be used as a constructor.", name); return NO; OOJS_NATIVE_EXIT } void OOJSObjectWrapperFinalize(JSContext *context, JSObject *this) { OOJS_PROFILE_ENTER id object = JS_GetPrivate(context, this); if (object != nil) { [[object weakRefUnderlyingObject] oo_clearJSSelf:this]; [object release]; JS_SetPrivate(context, this, nil); } OOJS_PROFILE_EXIT_VOID } JSBool OOJSObjectWrapperToString(JSContext *context, uintN argc, jsval *vp) { OOJS_NATIVE_ENTER(context) id object = nil; NSString *description = nil; JSClass *jsClass = NULL; object = OOJSNativeObjectFromJSObject(context, OOJS_THIS); if (object != nil) { description = [object oo_jsDescription]; if (description == nil) description = [object description]; } if (description == nil) { jsClass = OOJSGetClass(context, OOJS_THIS); if (jsClass != NULL) { description = [NSString stringWithFormat:@"[object %@]", [NSString stringWithUTF8String:jsClass->name]]; } } if (description == nil) description = @"[object]"; OOJS_RETURN_OBJECT(description); OOJS_NATIVE_EXIT } BOOL JSFunctionPredicate(Entity *entity, void *parameter) { OOJS_PROFILE_ENTER JSFunctionPredicateParameter *param = parameter; jsval args[1]; jsval rval = JSVAL_VOID; JSBool result = NO; NSCParameterAssert(entity != nil && param != NULL); NSCParameterAssert(param->context != NULL && JS_IsInRequest(param->context)); NSCParameterAssert(OOJSValueIsFunction(param->context, param->function)); if (EXPECT_NOT(param->errorFlag)) return NO; args[0] = [entity oo_jsValueInContext:param->context]; // entity is required to be non-nil (asserted above), so oo_jsValueInContext: is safe. OOJSStartTimeLimiter(); OOJSResumeTimeLimiter(); BOOL success = JS_CallFunctionValue(param->context, param->jsThis, param->function, 1, args, &rval); OOJSPauseTimeLimiter(); OOJSStopTimeLimiter(); if (success) { if (!JS_ValueToBoolean(param->context, rval, &result)) result = NO; if (JS_IsExceptionPending(param->context)) { JS_ReportPendingException(param->context); param->errorFlag = YES; } } else { param->errorFlag = YES; } return result; OOJS_PROFILE_EXIT } BOOL JSEntityIsJavaScriptVisiblePredicate(Entity *entity, void *parameter) { OOJS_PROFILE_ENTER return [entity isVisibleToScripts]; OOJS_PROFILE_EXIT } BOOL JSEntityIsJavaScriptSearchablePredicate(Entity *entity, void *parameter) { OOJS_PROFILE_ENTER if (![entity isVisibleToScripts]) return NO; if ([entity isShip]) { if ([entity isSubEntity]) return NO; if ([entity status] == STATUS_COCKPIT_DISPLAY) return NO; // Demo ship return YES; } else if ([entity isPlanet]) { switch ([(OOPlanetEntity *)entity planetType]) { case STELLAR_TYPE_MOON: case STELLAR_TYPE_NORMAL_PLANET: case STELLAR_TYPE_SUN: return YES; #if !NEW_PLANETS case STELLAR_TYPE_ATMOSPHERE: #endif case STELLAR_TYPE_MINIATURE: return NO; } } return YES; // would happen if we added a new script-visible class OOJS_PROFILE_EXIT } BOOL JSEntityIsDemoShipPredicate(Entity *entity, void *parameter) { return ([entity isVisibleToScripts] && [entity isShip] && [entity status] == STATUS_COCKPIT_DISPLAY && ![entity isSubEntity]); } static NSMapTable *sRegisteredSubClasses; void OOJSRegisterSubclass(JSClass *subclass, JSClass *superclass) { NSCParameterAssert(subclass != NULL && superclass != NULL); if (sRegisteredSubClasses == NULL) { sRegisteredSubClasses = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); } NSCAssert(NSMapGet(sRegisteredSubClasses, subclass) == NULL, @"A JS class cannot be registered as a subclass of multiple classes."); NSMapInsertKnownAbsent(sRegisteredSubClasses, subclass, superclass); } static void UnregisterSubclasses(void) { NSFreeMapTable(sRegisteredSubClasses); sRegisteredSubClasses = NULL; } BOOL OOJSIsSubclass(JSClass *putativeSubclass, JSClass *superclass) { NSCParameterAssert(putativeSubclass != NULL && superclass != NULL); NSCAssert(sRegisteredSubClasses != NULL, @"OOJSIsSubclass() called before any subclasses registered (disallowed for hot path efficiency)."); do { if (putativeSubclass == superclass) return YES; putativeSubclass = NSMapGet(sRegisteredSubClasses, putativeSubclass); } while (putativeSubclass != NULL); return NO; } BOOL OOJSObjectGetterImplPRIVATE(JSContext *context, JSObject *object, JSClass *requiredJSClass, #ifndef NDEBUG Class requiredObjCClass, const char *name, #endif id *outObject) { #ifndef NDEBUG OOJS_PROFILE_ENTER_NAMED(name) NSCParameterAssert(requiredObjCClass != Nil); NSCParameterAssert(context != NULL && object != NULL && requiredJSClass != NULL && outObject != NULL); #else OOJS_PROFILE_ENTER #endif /* Ensure it's a valid type of JS object. This is absolutely necessary, because if we don't check it we'll crash trying to get the private field of something that isn't an ObjC object wrapper - for example, Ship.setAI.call(new Vector3D, "") is valid JavaScript. Alternatively, we could abuse JSCLASS_PRIVATE_IS_NSISUPPORTS as a flag for ObjC object wrappers (SpiderMonkey only uses it internally in a debug function we don't use), but we'd still need to do an Objective-C class test, and I don't think that's any faster. TODO: profile. */ JSClass *actualClass = OOJSGetClass(context, object); if (EXPECT_NOT(!OOJSIsSubclass(actualClass, requiredJSClass))) { OOJSReportError(context, @"Native method expected %s, got %@.", requiredJSClass->name, OOStringFromJSValue(context, OBJECT_TO_JSVAL(object))); return NO; } NSCAssert(actualClass->flags & JSCLASS_HAS_PRIVATE, @"Native object accessor requires JS class with private storage."); // Get the underlying object. *outObject = [(id)JS_GetPrivate(context, object) weakRefUnderlyingObject]; #ifndef NDEBUG // Double-check that the underlying object is of the expected ObjC class. if (EXPECT_NOT(*outObject != nil && ![*outObject isKindOfClass:requiredObjCClass])) { OOJSReportError(context, @"Native method expected %@ from %s and got correct JS type but incorrect native object %@", requiredObjCClass, requiredJSClass->name, *outObject); return NO; } #endif return YES; OOJS_PROFILE_EXIT } NSDictionary *OOJSDictionaryFromJSValue(JSContext *context, jsval value) { OOJS_PROFILE_ENTER JSObject *object = NULL; if (EXPECT_NOT(!JS_ValueToObject(context, value, &object) || object == NULL)) { return nil; } return OOJSDictionaryFromJSObject(context, object); OOJS_PROFILE_EXIT } NSDictionary *OOJSDictionaryFromJSObject(JSContext *context, JSObject *object) { OOJS_PROFILE_ENTER JSIdArray *ids = NULL; jsint i; NSMutableDictionary *result = nil; jsval value = JSVAL_VOID; id objKey = nil; id objValue = nil; ids = JS_Enumerate(context, object); if (EXPECT_NOT(ids == NULL)) { return nil; } result = [NSMutableDictionary dictionaryWithCapacity:ids->length]; for (i = 0; i != ids->length; ++i) { jsid thisID = ids->vector[i]; if (JSID_IS_STRING(thisID)) { objKey = OOStringFromJSString(context, JSID_TO_STRING(thisID)); } else if (JSID_IS_INT(thisID)) { /* this causes problems with native functions which expect string keys * e.g. in mission.runScreen with the 'choices' parameter * should this instead be making the objKey a string? * is there anything that relies on the current behaviour? * - CIM 15/2/13 */ objKey = [NSNumber numberWithInt:JSID_TO_INT(thisID)]; } else { objKey = nil; } value = JSVAL_VOID; if (objKey != nil && !JS_LookupPropertyById(context, object, thisID, &value)) value = JSVAL_VOID; if (objKey != nil && !JSVAL_IS_VOID(value)) { objValue = OOJSNativeObjectFromJSValue(context, value); if (objValue != nil) { [result setObject:objValue forKey:objKey]; } } } JS_DestroyIdArray(context, ids); return result; OOJS_PROFILE_EXIT } NSDictionary *OOJSDictionaryFromStringTable(JSContext *context, jsval tableValue) { OOJS_PROFILE_ENTER JSObject *tableObject = NULL; JSIdArray *ids; jsint i; NSMutableDictionary *result = nil; jsval value = JSVAL_VOID; id objKey = nil; id objValue = nil; if (EXPECT_NOT(JSVAL_IS_NULL(tableValue) || !JS_ValueToObject(context, tableValue, &tableObject))) { return nil; } ids = JS_Enumerate(context, tableObject); if (EXPECT_NOT(ids == NULL)) { return nil; } result = [NSMutableDictionary dictionaryWithCapacity:ids->length]; for (i = 0; i != ids->length; ++i) { jsid thisID = ids->vector[i]; if (JSID_IS_STRING(thisID)) { objKey = OOStringFromJSString(context, JSID_TO_STRING(thisID)); } else { objKey = nil; } value = JSVAL_VOID; if (objKey != nil && !JS_LookupPropertyById(context, tableObject, thisID, &value)) value = JSVAL_VOID; if (objKey != nil && !JSVAL_IS_VOID(value)) { objValue = OOStringFromJSValueEvenIfNull(context, value); if (objValue != nil) { [result setObject:objValue forKey:objKey]; } } } JS_DestroyIdArray(context, ids); return result; OOJS_PROFILE_EXIT } static NSMutableDictionary *sObjectConverters; id OOJSNativeObjectFromJSValue(JSContext *context, jsval value) { OOJS_PROFILE_ENTER if (JSVAL_IS_NULL(value) || JSVAL_IS_VOID(value)) return nil; if (JSVAL_IS_INT(value)) { return [NSNumber numberWithInt:JSVAL_TO_INT(value)]; } if (JSVAL_IS_DOUBLE(value)) { return [NSNumber numberWithDouble:JSVAL_TO_DOUBLE(value)]; } if (JSVAL_IS_BOOLEAN(value)) { return [NSNumber numberWithBool:JSVAL_TO_BOOLEAN(value)]; } if (JSVAL_IS_STRING(value)) { return OOStringFromJSValue(context, value); } if (JSVAL_IS_OBJECT(value)) { return OOJSNativeObjectFromJSObject(context, JSVAL_TO_OBJECT(value)); } return nil; OOJS_PROFILE_EXIT } id OOJSNativeObjectFromJSObject(JSContext *context, JSObject *tableObject) { OOJS_PROFILE_ENTER NSValue *wrappedClass = nil; NSValue *wrappedConverter = nil; OOJSClassConverterCallback converter = NULL; JSClass *class = NULL; if (tableObject == NULL) return nil; class = OOJSGetClass(context, tableObject); wrappedClass = [NSValue valueWithPointer:class]; if (wrappedClass != nil) wrappedConverter = [sObjectConverters objectForKey:wrappedClass]; if (wrappedConverter != nil) { converter = [wrappedConverter pointerValue]; return converter(context, tableObject); } return nil; OOJS_PROFILE_EXIT } id OOJSNativeObjectOfClassFromJSValue(JSContext *context, jsval value, Class requiredClass) { id result = OOJSNativeObjectFromJSValue(context, value); if (![result isKindOfClass:requiredClass]) result = nil; return result; } id OOJSNativeObjectOfClassFromJSObject(JSContext *context, JSObject *object, Class requiredClass) { id result = OOJSNativeObjectFromJSObject(context, object); if (![result isKindOfClass:requiredClass]) result = nil; return result; } id OOJSBasicPrivateObjectConverter(JSContext *context, JSObject *object) { id result; /* This will do the right thing - for non-OOWeakReferences, weakRefUnderlyingObject returns the object itself. For nil, of course, it returns nil. */ result = JS_GetPrivate(context, object); return [result weakRefUnderlyingObject]; } void OOJSRegisterObjectConverter(JSClass *theClass, OOJSClassConverterCallback converter) { NSValue *wrappedClass = nil; NSValue *wrappedConverter = nil; if (theClass == NULL) return; if (sObjectConverters == nil) sObjectConverters = [[NSMutableDictionary alloc] init]; wrappedClass = [NSValue valueWithPointer:theClass]; if (converter != NULL) { wrappedConverter = [NSValue valueWithPointer:converter]; [sObjectConverters setObject:wrappedConverter forKey:wrappedClass]; } else { [sObjectConverters removeObjectForKey:wrappedClass]; } } static void UnregisterObjectConverters(void) { DESTROY(sObjectConverters); } static id JSArrayConverter(JSContext *context, JSObject *array) { jsuint i, count; id *values = NULL; jsval value = JSVAL_VOID; id object = nil; NSArray *result = nil; // Convert a JS array to an NSArray by calling OOJSNativeObjectFromJSValue() on all its elements. if (!JS_IsArrayObject(context, array)) return nil; if (!JS_GetArrayLength(context, array, &count)) return nil; if (count == 0) return [NSArray array]; values = calloc(count, sizeof *values); if (values == NULL) return nil; for (i = 0; i != count; ++i) { value = JSVAL_VOID; if (!JS_GetElement(context, array, i, &value)) value = JSVAL_VOID; object = OOJSNativeObjectFromJSValue(context, value); if (object == nil) object = [NSNull null]; values[i] = object; } result = [NSArray arrayWithObjects:values count:count]; free(values); return result; } static id JSStringConverter(JSContext *context, JSObject *object) { return OOStringFromJSValue(context, OBJECT_TO_JSVAL(object)); } static id JSNumberConverter(JSContext *context, JSObject *object) { jsdouble value; if (JS_ValueToNumber(context, OBJECT_TO_JSVAL(object), &value)) { return [NSNumber numberWithDouble:value]; } return nil; } static id JSBooleanConverter(JSContext *context, JSObject *object) { /* Fun With JavaScript: Boolean(false) is a truthy value, since it's a non-null object. JS_ValueToBoolean() therefore reports true. However, Boolean objects are transformed to numbers sanely, so this works. */ jsdouble value; if (JS_ValueToNumber(context, OBJECT_TO_JSVAL(object), &value)) { return [NSNumber numberWithBool:(value != 0)]; } return nil; } oolite-1.82/src/Core/Scripting/OOLegacyScriptWhitelist.h000066400000000000000000000107621256642440500232730ustar00rootroot00000000000000/* OOLegacyScriptWhitelist.h Functions to apply method whitelist and basic tokenization to legacy scripts. A sanitized script is an array of zero or more sanitized statements. A sanitized statement is an array whose first element is a boolean indicating whether it's a conditional statement (true) or an action statement (false). A conditional statement has three additional elements. The first is an array of sanitized conditions (see below). The second is a sanitized script to execute if the condition evaluates to true. The third is a sanitized script to execute if the condition evaluates to false. An action statement has one or two additional elements, both strings. The first is a selector. If the selector ends with a colon (i.e., takes an argument), the second is the argument. A sanitized condition is an array of the form: (opType, rawString, selector, comparisonType, operandArray). opType and comparisonType are NSNumbers containing OOOperationType and OOComparisonType enumerators, respectively. rawString is the original textual representation of the condition for display purposes. selector is a string, either a method selector or a mission/local variable name. operandArray is an array of operands. Each operand is itself an array of two items: a boolean indicating whether it's a method selector (true) or a string (false), and a string. The special opType OP_FALSE doesn't require any other elements in the array. All other valid opTypes require the array to have five elements. A complete example: given the following script (the Cloaking Device mission script from Oolite 1.65): ( { conditions = ( "galaxy_number equal 4", "status_string equal STATUS_EXITING_WITCHSPACE", "mission_cloak undefined" ); do = ( { conditions = ("mission_cloakcounter undefined"); do = ("set: mission_cloakcounter 0"); }, "increment: mission_cloakcounter", "checkForShips: asp-cloaked", { conditions = ("shipsFound_number equal 0", "mission_cloakcounter greaterthan 6"); do = ("addShips: asp-cloaked 1", "addShips: asp-pirate 2"); } ); } ) the sanitized form (with rawString values replaced with "..." for simplicity) is: ( ( true, // This is a conditonal statement ( // conditions (OP_NUMBER, "...", "galaxy_number", COMPARISON_EQUAL, ((false, "4"))), (OP_STRING, "...", "status_string", COMPARISON_EQUAL, ((false, "STATUS_EXITING_WITCHSPACE"))), (OP_MISSION_VAR, "...", "mission_cloak", COMPARISON_UNDEFINED, ()) ), ( // do ( true, ( (OP_MISSION_VAR, "...", "mission_cloakcounter", COMPARISON_UNDEFINED, ()) ), ( (false, "set:", "mission_cloakcounter 0") ), () ), (false, "increment:", "mission_cloakcounter"), (false, "checkForShips:", "asp-cloaked"), (true, ( (OP_NUMBER, "...", "shipsFound_number", COMPARISON_EQUAL, ((false, "0"))), (OP_MISSION_VAR, "...", "mission_cloakcounter, COMPARISON_GREATERTHAN, ((false, "6"))), ), ( (false, "addShips:", "asp-cloaked 1"), (false, "addShips:", "asp-pirate 2"), ), () ) ), () // else ) ) Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" // context is used for error messages. NSArray *OOSanitizeLegacyScript(NSArray *script, NSString *context, BOOL allowAIMethods); NSArray *OOSanitizeLegacyScriptConditions(NSArray *conditions, NSString *context); /* Quick test of whether a conditions array is sanitized. It is assumed that this will only be passed fully-sanitized or fully-unsanitized conditions arrays, so the test doesn't need to be exhaustive. Note that OOLegacyConditionsAreSanitized() is *not* called by OOSanitizeLegacyScript(), so that it is not possible to sneak an unwhitelisted "pre-compiled" condition past it. */ BOOL OOLegacyConditionsAreSanitized(NSArray *conditions); oolite-1.82/src/Core/Scripting/OOLegacyScriptWhitelist.m000066400000000000000000000427371256642440500233070ustar00rootroot00000000000000/* OOLegacyScriptWhitelist.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOLegacyScriptWhitelist.h" #import "OOStringParsing.h" #import "ResourceManager.h" #import "OOCollectionExtractors.h" #import "PlayerEntityLegacyScriptEngine.h" #import "NSDictionaryOOExtensions.h" #import "OODeepCopy.h" #define INCLUDE_RAW_STRING !defined(NDEBUG) // If nonzero, raw condition strings are included; if zero, a placeholder is used. typedef struct SanStackElement SanStackElement; struct SanStackElement { SanStackElement *back; NSString *key; // Dictionary key; nil for arrays. NSUInteger index; // Array index if key is nil. }; static NSArray *OOSanitizeLegacyScriptInternal(NSArray *script, SanStackElement *stack, BOOL allowAIMethods); static NSArray *OOSanitizeLegacyScriptConditionsInternal(NSArray *conditions, SanStackElement *stack); static NSArray *SanitizeCondition(NSString *condition, SanStackElement *stack); static NSArray *SanitizeConditionalStatement(NSDictionary *statement, SanStackElement *stack, BOOL allowAIMethods); static NSArray *SanitizeActionStatement(NSString *statement, SanStackElement *stack, BOOL allowAIMethods); static OOOperationType ClassifyLHSConditionSelector(NSString *selectorString, NSString **outSanitizedMethod, SanStackElement *stack); static NSString *SanitizeQueryMethod(NSString *selectorString); // Checks aliases and whitelist, returns nil if whitelist fails. static NSString *SanitizeActionMethod(NSString *selectorString, BOOL allowAIMethods); // Checks aliases and whitelist, returns nil if whitelist fails. static NSArray *AlwaysFalseConditions(void); static BOOL IsAlwaysFalseConditions(NSArray *conditions); static NSString *StringFromStack(SanStackElement *topOfStack); NSArray *OOSanitizeLegacyScript(NSArray *script, NSString *context, BOOL allowAIMethods) { SanStackElement stackRoot = { NULL, context, 0 }; NSArray *result = OOSanitizeLegacyScriptInternal(script, &stackRoot, allowAIMethods); return [OODeepCopy(result) autorelease]; } static NSArray *OOSanitizeLegacyScriptInternal(NSArray *script, SanStackElement *stack, BOOL allowAIMethods) { NSAutoreleasePool *pool = nil; NSMutableArray *result = nil; NSEnumerator *statementEnum = nil; id statement = nil; NSUInteger index = 0; pool = [[NSAutoreleasePool alloc] init]; result = [NSMutableArray arrayWithCapacity:[script count]]; for (statementEnum = [script objectEnumerator]; (statement = [statementEnum nextObject]); ) { SanStackElement subStack = { stack, nil, index++ }; if ([statement isKindOfClass:[NSDictionary class]]) { statement = SanitizeConditionalStatement(statement, &subStack, allowAIMethods); } else if ([statement isKindOfClass:[NSString class]]) { statement = SanitizeActionStatement(statement, &subStack, allowAIMethods); } else { OOLog(@"script.syntax.statement.invalidType", @"***** SCRIPT ERROR: in %@, statement is of invalid type - expected string or dictionary, got %@.", StringFromStack(stack), [statement class]); statement = nil; } if (statement != nil) { [result addObject:statement]; } } [result retain]; [pool release]; return [result autorelease]; } NSArray *OOSanitizeLegacyScriptConditions(NSArray *conditions, NSString *context) { if (context == nil) context = @""; SanStackElement stackRoot = { NULL, context, 0 }; NSArray *result = OOSanitizeLegacyScriptConditionsInternal(conditions, &stackRoot); return [OODeepCopy(result) autorelease]; } static NSArray *OOSanitizeLegacyScriptConditionsInternal(NSArray *conditions, SanStackElement *stack) { NSEnumerator *conditionEnum = nil; NSString *condition = nil; NSMutableArray *result = nil; NSArray *tokens = nil; BOOL OK = YES; NSUInteger index = 0; if (OOLegacyConditionsAreSanitized(conditions) || conditions == nil) return conditions; result = [NSMutableArray arrayWithCapacity:[conditions count]]; for (conditionEnum = [conditions objectEnumerator]; (condition = [conditionEnum nextObject]); ) { SanStackElement subStack = { stack, nil, index++ }; if (![condition isKindOfClass:[NSString class]]) { OOLog(@"script.syntax.condition.notString", @"***** SCRIPT ERROR: in %@, bad condition - expected string, got %@; ignoring.", StringFromStack(stack), [condition class]); OK = NO; break; } tokens = SanitizeCondition(condition, &subStack); if (tokens != nil) { [result addObject:tokens]; } else { OK = NO; break; } } if (OK) return result; else return AlwaysFalseConditions(); } BOOL OOLegacyConditionsAreSanitized(NSArray *conditions) { if ([conditions count] == 0) return YES; // Empty array is safe. return [[conditions objectAtIndex:0] isKindOfClass:[NSArray class]]; } static NSArray *SanitizeCondition(NSString *condition, SanStackElement *stack) { NSArray *tokens = nil; NSUInteger i, tokenCount; OOOperationType opType; NSString *selectorString = nil; NSString *sanitizedSelectorString = nil; NSString *comparatorString = nil; OOComparisonType comparatorValue; NSMutableArray *rhs = nil; NSString *rhsItem = nil; NSString *rhsSelector = nil; NSArray *sanitizedRHSItem = nil; NSString *stringSegment = nil; tokens = ScanTokensFromString(condition); tokenCount = [tokens count]; if (tokenCount < 1) { OOLog(@"script.debug.syntax.scriptCondition.noneSpecified", @"***** SCRIPT ERROR: in %@, empty script condition.", StringFromStack(stack)); return NO; } // Parse left-hand side. selectorString = [tokens oo_stringAtIndex:0]; opType = ClassifyLHSConditionSelector(selectorString, &sanitizedSelectorString, stack); if (opType >= OP_INVALID) { OOLog(@"script.unpermittedMethod", @"***** SCRIPT ERROR: in %@ (\"%@\"), method \"%@\" not allowed.", StringFromStack(stack), condition, selectorString); return NO; } // Parse operator. if (tokenCount > 1) { comparatorString = [tokens oo_stringAtIndex:1]; if ([comparatorString isEqualToString:@"equal"]) comparatorValue = COMPARISON_EQUAL; else if ([comparatorString isEqualToString:@"notequal"]) comparatorValue = COMPARISON_NOTEQUAL; else if ([comparatorString isEqualToString:@"lessthan"]) comparatorValue = COMPARISON_LESSTHAN; else if ([comparatorString isEqualToString:@"greaterthan"]) comparatorValue = COMPARISON_GREATERTHAN; else if ([comparatorString isEqualToString:@"morethan"]) comparatorValue = COMPARISON_GREATERTHAN; else if ([comparatorString isEqualToString:@"oneof"]) comparatorValue = COMPARISON_ONEOF; else if ([comparatorString isEqualToString:@"undefined"]) comparatorValue = COMPARISON_UNDEFINED; else { OOLog(@"script.debug.syntax.badComparison", @"***** SCRIPT ERROR: in %@ (\"%@\"), unknown comparison operator \"%@\", will return NO.", StringFromStack(stack), condition, comparatorString); return NO; } } else { /* In the direct interpreter, having no operator resulted in an implicit COMPARISON_NO operator, which always evaluated to false. Returning NO here causes AlwaysFalseConditions() to be used, which has the same effect. */ OOLog(@"script.debug.syntax.noOperator", @"----- WARNING: SCRIPT in %@ -- No operator in expression \"%@\", will always evaluate as false.", StringFromStack(stack), condition); return NO; } // Check for invalid opType/comparator combinations. if (opType == OP_NUMBER && comparatorValue == COMPARISON_UNDEFINED) { OOLog(@"script.debug.syntax.invalidOperator", @"***** SCRIPT ERROR: in %@ (\"%@\"), comparison operator \"%@\" is not valid for %@.", StringFromStack(stack), condition, @"undefined", @"numbers"); return NO; } else if (opType == OP_BOOL) { switch (comparatorValue) { // Valid comparators case COMPARISON_EQUAL: case COMPARISON_NOTEQUAL: break; default: OOLog(@"script.debug.syntax.invalidOperator", @"***** SCRIPT ERROR: in %@ (\"%@\"), comparison operator \"%@\" is not valid for %@.", StringFromStack(stack), condition, OOComparisonTypeToString(comparatorValue), @"booleans"); return NO; } } /* Parse right-hand side. Each token is converted to an array of the token and a boolean indicating whether it's a selector. This also coalesces non-selector tokens, i.e. whitespace-separated string segments. */ if (tokenCount > 2) { rhs = [NSMutableArray arrayWithCapacity:tokenCount - 2]; for (i = 2; i < tokenCount; i++) { rhsItem = [tokens oo_stringAtIndex:i]; rhsSelector = SanitizeQueryMethod(rhsItem); if (rhsSelector != nil) { // Method if (stringSegment != nil) { // Add stringSegment as a literal token. sanitizedRHSItem = [NSArray arrayWithObjects:[NSNumber numberWithBool:NO], stringSegment, nil]; [rhs addObject:sanitizedRHSItem]; stringSegment = nil; } sanitizedRHSItem = [NSArray arrayWithObjects:[NSNumber numberWithBool:YES], rhsSelector, nil]; [rhs addObject:sanitizedRHSItem]; } else { // String; append to stringSegment if (stringSegment == nil) stringSegment = rhsItem; else stringSegment = [NSString stringWithFormat:@"%@ %@", stringSegment, rhsItem]; } } if (stringSegment != nil) { sanitizedRHSItem = [NSArray arrayWithObjects:[NSNumber numberWithBool:NO], stringSegment, nil]; [rhs addObject:sanitizedRHSItem]; } } else { rhs = [NSMutableArray array]; } NSString *rawString = nil; #if INCLUDE_RAW_STRING rawString = condition; #else rawString = @""; #endif return [NSArray arrayWithObjects: [NSNumber numberWithUnsignedInt:opType], rawString, sanitizedSelectorString, [NSNumber numberWithUnsignedInt:comparatorValue], rhs, nil]; } static NSArray *SanitizeConditionalStatement(NSDictionary *statement, SanStackElement *stack, BOOL allowAIMethods) { NSArray *conditions = nil; NSArray *doActions = nil; NSArray *elseActions = nil; conditions = [statement oo_arrayForKey:@"conditions"]; if (conditions == nil) { OOLog(@"script.syntax.noConditions", @"***** SCRIPT ERROR: in %@, conditions array contains no \"conditions\" entry, ignoring.", StringFromStack(stack)); return nil; } // Sanitize conditions. SanStackElement subStack = { stack, @"conditions", 0 }; conditions = OOSanitizeLegacyScriptConditionsInternal(conditions, &subStack); if (conditions == nil) { return nil; } // Sanitize do and else. if (!IsAlwaysFalseConditions(conditions)) doActions = [statement oo_arrayForKey:@"do"]; if (doActions != nil) { subStack.key = @"do"; doActions = OOSanitizeLegacyScriptInternal(doActions, &subStack, allowAIMethods); } elseActions = [statement oo_arrayForKey:@"else"]; if (elseActions != nil) { subStack.key = @"else"; elseActions = OOSanitizeLegacyScriptInternal(elseActions, &subStack, allowAIMethods); } // If neither does anything, the statment has no effect. if ([doActions count] == 0 && [elseActions count] == 0) { return nil; } if (doActions == nil) doActions = [NSArray array]; if (elseActions == nil) elseActions = [NSArray array]; return [NSArray arrayWithObjects:[NSNumber numberWithBool:YES], conditions, doActions, elseActions, nil]; } static NSArray *SanitizeActionStatement(NSString *statement, SanStackElement *stack, BOOL allowAIMethods) { NSMutableArray *tokens = nil; NSUInteger tokenCount; NSString *rawSelectorString = nil; NSString *selectorString = nil; NSString *argument = nil; tokens = ScanTokensFromString(statement); tokenCount = [tokens count]; if (tokenCount == 0) return nil; rawSelectorString = [tokens objectAtIndex:0]; selectorString = SanitizeActionMethod(rawSelectorString, allowAIMethods); if (selectorString == nil) { OOLog(@"script.unpermittedMethod", @"***** SCRIPT ERROR: in %@ (\"%@\"), method \"%@\" not allowed.", StringFromStack(stack), statement, rawSelectorString); return nil; } if ([selectorString isEqualToString:@"doNothing"]) { return nil; } if ([selectorString hasSuffix:@":"]) { // Expects an argument if (tokenCount == 2) { argument = [tokens objectAtIndex:1]; } else { [tokens removeObjectAtIndex:0]; argument = [tokens componentsJoinedByString:@" "]; } argument = [argument stringByReplacingOccurrencesOfString:@"[credits_number]" withString:@"[_oo_legacy_credits_number]"]; } return [NSArray arrayWithObjects:[NSNumber numberWithBool:NO], selectorString, argument, nil]; } static OOOperationType ClassifyLHSConditionSelector(NSString *selectorString, NSString **outSanitizedSelector, SanStackElement *stack) { assert(outSanitizedSelector != NULL); *outSanitizedSelector = selectorString; // Allow arbitrary mission_foo or local_foo pseudo-selectors. if ([selectorString hasPrefix:@"mission_"]) return OP_MISSION_VAR; if ([selectorString hasPrefix:@"local_"]) return OP_LOCAL_VAR; // If it's a real method, check against whitelist. *outSanitizedSelector = SanitizeQueryMethod(selectorString); if (*outSanitizedSelector == nil) { return OP_INVALID; } // If it's a real method, and in the whitelist, classify by suffix. if ([selectorString hasSuffix:@"_string"]) return OP_STRING; if ([selectorString hasSuffix:@"_number"]) return OP_NUMBER; if ([selectorString hasSuffix:@"_bool"]) return OP_BOOL; // If we got here, something's wrong. OOLog(@"script.sanitize.unclassifiedSelector", @"***** ERROR: Whitelisted query method \"%@\" has no type suffix, treating as invalid.", selectorString); return OP_INVALID; } static NSString *SanitizeQueryMethod(NSString *selectorString) { static NSSet *whitelist = nil; static NSDictionary *aliases = nil; NSString *aliasedSelector = nil; if (whitelist == nil) { whitelist = [[NSSet alloc] initWithArray:[[ResourceManager whitelistDictionary] oo_arrayForKey:@"query_methods"]]; aliases = [[[ResourceManager whitelistDictionary] oo_dictionaryForKey:@"query_method_aliases"] retain]; } aliasedSelector = [aliases oo_stringForKey:selectorString]; if (aliasedSelector != nil) selectorString = aliasedSelector; if (![whitelist containsObject:selectorString]) selectorString = nil; return selectorString; } static NSString *SanitizeActionMethod(NSString *selectorString, BOOL allowAIMethods) { static NSSet *whitelist = nil; static NSSet *whitelistWithAI = nil; static NSDictionary *aliases = nil; static NSDictionary *aliasesWithAI = nil; NSString *aliasedSelector = nil; if (whitelist == nil) { NSArray *actionMethods = nil; NSArray *aiMethods = nil; NSArray *aiAndActionMethods = nil; actionMethods = [[ResourceManager whitelistDictionary] oo_arrayForKey:@"action_methods"]; aiMethods = [[ResourceManager whitelistDictionary] oo_arrayForKey:@"ai_methods"]; aiAndActionMethods = [[ResourceManager whitelistDictionary] oo_arrayForKey:@"ai_and_action_methods"]; if (actionMethods == nil) actionMethods = [NSArray array]; if (aiMethods == nil) aiMethods = [NSArray array]; if (aiAndActionMethods != nil) actionMethods = [actionMethods arrayByAddingObjectsFromArray:aiAndActionMethods]; whitelist = [[NSSet alloc] initWithArray:actionMethods]; whitelistWithAI = [[NSSet alloc] initWithArray:[aiMethods arrayByAddingObjectsFromArray:actionMethods]]; aliases = [[[ResourceManager whitelistDictionary] oo_dictionaryForKey:@"action_method_aliases"] retain]; aliasesWithAI = [[ResourceManager whitelistDictionary] oo_dictionaryForKey:@"ai_method_aliases"]; if (aliasesWithAI != nil) { aliasesWithAI = [[aliasesWithAI dictionaryByAddingEntriesFromDictionary:aliases] copy]; } else { aliasesWithAI = [aliases copy]; } } aliasedSelector = [(allowAIMethods ? aliasesWithAI : aliases) oo_stringForKey:selectorString]; if (aliasedSelector != nil) selectorString = aliasedSelector; if (![(allowAIMethods ? whitelistWithAI : whitelist) containsObject:selectorString]) selectorString = nil; return selectorString; } // Return a conditions array that always evaluates as false. static NSArray *AlwaysFalseConditions(void) { static NSArray *alwaysFalse = nil; if (alwaysFalse != nil) { alwaysFalse = [NSArray arrayWithObject:[NSArray arrayWithObject:[NSNumber numberWithUnsignedInt:OP_FALSE]]]; [alwaysFalse retain]; } return alwaysFalse; } static BOOL IsAlwaysFalseConditions(NSArray *conditions) { return [[conditions oo_arrayAtIndex:0] oo_unsignedIntAtIndex:0] == OP_FALSE; } static NSMutableString *StringFromStackInternal(SanStackElement *topOfStack) { if (topOfStack == NULL) return nil; NSMutableString *base = StringFromStackInternal(topOfStack->back); if (base == nil) base = [NSMutableString string]; NSString *string = topOfStack->key; if (string == nil) string = [NSString stringWithFormat:@"%lu", (unsigned long)topOfStack->index]; if ([base length] > 0) [base appendString:@"."]; [base appendString:string]; return base; } static NSString *StringFromStack(SanStackElement *topOfStack) { return StringFromStackInternal(topOfStack); } oolite-1.82/src/Core/Scripting/OOPListScript.h000066400000000000000000000023101256642440500212130ustar00rootroot00000000000000/* OOPListScript.h Property list-based script. I started off reimplementing plist scripting here, in order to remove one of PlayerEntity's many overloaded functions. The scale of the task was such that I've stepped back, and this simply wraps the old plist scripting in PlayerEntity. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOScript.h" @interface OOPListScript: OOScript { @private NSArray *_script; NSDictionary *_metadata; } + (NSArray *)scriptsInPListFile:(NSString *)filePath; @end oolite-1.82/src/Core/Scripting/OOPListScript.m000066400000000000000000000130511256642440500212240ustar00rootroot00000000000000/* OOPListScript.h Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOPListScript.h" #import "OOPListParsing.h" #import "PlayerEntityLegacyScriptEngine.h" #import "OOLegacyScriptWhitelist.h" #import "OOCacheManager.h" #import "OOCollectionExtractors.h" static NSString * const kMDKeyName = @"name"; static NSString * const kMDKeyDescription = @"description"; static NSString * const kMDKeyVersion = @"version"; static NSString * const kKeyMetadata = @"!metadata!"; static NSString * const kKeyScript = @"script"; static NSString * const kCacheName = @"sanitized legacy scripts"; @interface OOPListScript (SetUp) + (NSArray *)scriptsFromDictionaryOfScripts:(NSDictionary *)dictionary filePath:(NSString *)filePath; + (NSArray *) loadCachedScripts:(NSDictionary *)cachedScripts; - (id)initWithName:(NSString *)name scriptArray:(NSArray *)script metadata:(NSDictionary *)metadata; @end @implementation OOPListScript + (NSArray *)scriptsInPListFile:(NSString *)filePath { NSDictionary *cachedScripts = [[OOCacheManager sharedCache] objectForKey:filePath inCache:kCacheName]; if (cachedScripts != nil) { return [self loadCachedScripts:cachedScripts]; } else { NSDictionary *dict = OODictionaryFromFile(filePath); if (dict == nil) return nil; return [self scriptsFromDictionaryOfScripts:dict filePath:filePath]; } } - (void)dealloc { [_script release]; [_metadata release]; [super dealloc]; } - (NSString *)name { return [_metadata objectForKey:kMDKeyName]; } - (NSString *)scriptDescription { return [_metadata objectForKey:kMDKeyDescription]; } - (NSString *)version { return [_metadata objectForKey:kMDKeyVersion]; } - (BOOL) requiresTickle { return YES; } - (void)runWithTarget:(Entity *)target { if (target != nil && ![target isKindOfClass:[ShipEntity class]]) { OOLog(@"script.legacy.run.badTarget", @"Expected ShipEntity or nil for target, got %@.", [target class]); return; } OOLog(@"script.legacy.run", @"Running script %@", [self displayName]); OOLogIndentIf(@"script.legacy.run"); [PLAYER runScriptActions:_script withContextName:[self name] forTarget:(ShipEntity *)target]; OOLogOutdentIf(@"script.legacy.run"); } @end @implementation OOPListScript (SetUp) + (NSArray *)scriptsFromDictionaryOfScripts:(NSDictionary *)dictionary filePath:(NSString *)filePath { NSMutableArray *result = nil; NSEnumerator *keyEnum = nil; NSString *key = nil; NSArray *scriptArray = nil; NSDictionary *metadata = nil; NSMutableDictionary *cachedScripts = nil; OOPListScript *script = nil; NSUInteger count = [dictionary count]; result = [NSMutableArray arrayWithCapacity:count]; cachedScripts = [NSMutableDictionary dictionaryWithCapacity:count]; metadata = [dictionary objectForKey:kKeyMetadata]; if (![metadata isKindOfClass:[NSDictionary class]]) metadata = nil; for (keyEnum = [dictionary keyEnumerator]; (key = [keyEnum nextObject]); ) { scriptArray = [dictionary objectForKey:key]; if ([key isKindOfClass:[NSString class]] && [scriptArray isKindOfClass:[NSArray class]] && ![key isEqual:kKeyMetadata]) { scriptArray = OOSanitizeLegacyScript(scriptArray, key, NO); if (scriptArray != nil) { script = [[self alloc] initWithName:key scriptArray:scriptArray metadata:metadata]; if (script != nil) { [result addObject:script]; [cachedScripts setObject:[NSDictionary dictionaryWithObjectsAndKeys:scriptArray, kKeyScript, metadata, kKeyMetadata, nil] forKey:key]; [script release]; } } } } [[OOCacheManager sharedCache] setObject:cachedScripts forKey:filePath inCache:kCacheName]; return [[result copy] autorelease]; } + (NSArray *) loadCachedScripts:(NSDictionary *)cachedScripts { NSEnumerator *keyEnum = nil; NSString *key = nil; NSMutableArray *result = [NSMutableArray arrayWithCapacity:[cachedScripts count]]; for (keyEnum = [cachedScripts keyEnumerator]; (key = [keyEnum nextObject]); ) { NSDictionary *cacheValue = [cachedScripts oo_dictionaryForKey:key]; NSArray *scriptArray = [cacheValue oo_arrayForKey:kKeyScript]; NSDictionary *metadata = [cacheValue oo_dictionaryForKey:kKeyMetadata]; OOPListScript *script = [[self alloc] initWithName:key scriptArray:scriptArray metadata:metadata]; if (script != nil) { [result addObject:script]; [script release]; } } return [[result copy] autorelease]; } - (id)initWithName:(NSString *)name scriptArray:(NSArray *)script metadata:(NSDictionary *)metadata { self = [super init]; if (self != nil) { _script = [script retain]; if (name != nil) { if (metadata == nil) metadata = [NSDictionary dictionaryWithObject:name forKey:kMDKeyName]; else { NSMutableDictionary *mutableMetadata = [[metadata mutableCopy] autorelease]; [mutableMetadata setObject:name forKey:kMDKeyName]; metadata = mutableMetadata; } } _metadata = [metadata copy]; } return self; } @end oolite-1.82/src/Core/Scripting/OOScript.h000066400000000000000000000043051256642440500202450ustar00rootroot00000000000000/* OOScript.h Abstract base class for scripts. Currently, Oolite supports two types of script: the original property list scripts and JavaScript scripts. OOS, a format that translated into plist scripts, was supported until 1.69.1, but never used. OOScript unifies the interfaces to the script types and abstracts loading. Additionally, it falls back to a more "primitive" script if loading of one type fails; specifically, the order of precedence is: script.js (JavaScript) // script.oos (OOS) script.plist (property list) Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import @class Entity; @interface OOScript: NSObject /* Looks for path/world-scripts.plist, path/script.js, then path/script.plist. May return zero or more scripts. */ + (NSArray *)worldScriptsAtPath:(NSString *)path; // Load named scripts from Scripts folders. + (NSArray *)scriptsFromFileNamed:(NSString *)fileName; + (NSArray *)scriptsFromList:(NSArray *)fileNames; + (NSArray *)scriptsFromFileAtPath:(NSString *)filePath; // Load a single JavaScript script. + (id)jsScriptFromFileNamed:(NSString *)fileName properties:(NSDictionary *)properties; // As above, but load from the "AIs" directory + (id)jsAIScriptFromFileNamed:(NSString *)fileName properties:(NSDictionary *)properties; - (NSString *)name; - (NSString *)scriptDescription; - (NSString *)version; - (NSString *)displayName; // "name version" if version is defined, otherwise just "name". - (BOOL) requiresTickle; - (void)runWithTarget:(Entity *)target; @end oolite-1.82/src/Core/Scripting/OOScript.m000066400000000000000000000200431256642440500202470ustar00rootroot00000000000000/* OOScript.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOScript.h" #import "OOJSScript.h" #import "OOPListScript.h" #import "OOLogging.h" #import "Universe.h" #import "OOJavaScriptEngine.h" #import "OOPListParsing.h" #import "ResourceManager.h" #import "OODebugStandards.h" static NSString * const kOOLogLoadScriptJavaScript = @"script.load.javaScript"; static NSString * const kOOLogLoadScriptPList = @"script.load.pList"; static NSString * const kOOLogLoadScriptOK = @"script.load.parseOK"; static NSString * const kOOLogLoadScriptParseError = @"script.load.parseError"; static NSString * const kOOLogLoadScriptNone = @"script.load.none"; @implementation OOScript + (NSArray *)worldScriptsAtPath:(NSString *)path { NSFileManager *fmgr = nil; NSString *filePath = nil; NSArray *names = nil; NSArray *result = nil; id script = nil; BOOL foundScript = NO; fmgr = [NSFileManager defaultManager]; // First, look for world-scripts.plist. filePath = [path stringByAppendingPathComponent:@"world-scripts.plist"]; if (filePath != nil) { names = OOArrayFromFile(filePath); if (names != nil) { foundScript = YES; result = [self scriptsFromList:names]; } } // Second, try to load a JavaScript. if (result == nil) { filePath = [path stringByAppendingPathComponent:@"script.js"]; if ([fmgr oo_oxzFileExistsAtPath:filePath]) foundScript = YES; else { filePath = [path stringByAppendingPathComponent:@"script.es"]; if ([fmgr oo_oxzFileExistsAtPath:filePath]) foundScript = YES; } if (foundScript) { OOLog(kOOLogLoadScriptJavaScript, @"Trying to load JavaScript script %@", filePath); OOLogIndentIf(kOOLogLoadScriptJavaScript); script = [OOJSScript scriptWithPath:filePath properties:nil]; if (script != nil) { result = [NSArray arrayWithObject:script]; OOLog(kOOLogLoadScriptOK, @"Successfully loaded JavaScript script %@", filePath); } else OOLogERR(kOOLogLoadScriptParseError, @"Failed to load JavaScript script %@", filePath); OOLogOutdentIf(kOOLogLoadScriptJavaScript); } } // Third, try to load a plist script. if (result == nil) { filePath = [path stringByAppendingPathComponent:@"script.plist"]; if ([fmgr oo_oxzFileExistsAtPath:filePath]) { OOStandardsDeprecated([NSString stringWithFormat:@"Legacy script %@ is deprecated",filePath]); if (!OOEnforceStandards()) { foundScript = YES; OOLog(kOOLogLoadScriptPList, @"Trying to load property list script %@", filePath); OOLogIndentIf(kOOLogLoadScriptPList); result = [OOPListScript scriptsInPListFile:filePath]; if (result != nil) OOLog(kOOLogLoadScriptOK, @"Successfully loaded property list script %@", filePath); else OOLogERR(kOOLogLoadScriptParseError, @"Failed to load property list script %@", filePath); OOLogOutdentIf(kOOLogLoadScriptPList); } } } if (result == nil && foundScript) { OOLog(kOOLogLoadScriptNone, @"No script could be loaded from %@", path); } return result; } + (NSArray *)scriptsFromFileNamed:(NSString *)fileName { NSArray *result = nil; NSString *path = [ResourceManager pathForFileNamed:fileName inFolder:@"Scripts"]; if (path != nil) { result = [self scriptsFromFileAtPath:path]; } if (result == nil) { OOLogERR(@"script.load.notFound", @"Could not find script file %@.", fileName); } return result; } + (NSArray *)scriptsFromList:(NSArray *)fileNames { NSEnumerator *nameEnum = nil; NSString *name = nil; NSMutableArray *result = nil; NSArray *scripts = nil; result = [NSMutableArray arrayWithCapacity:[fileNames count]]; for (nameEnum = [fileNames objectEnumerator]; (name = [nameEnum nextObject]); ) { scripts = [self scriptsFromFileNamed:name]; if (scripts != nil) [result addObjectsFromArray:scripts]; } return result; } + (NSArray *)scriptsFromFileAtPath:(NSString *)filePath { // oo_oxzFile always returns false for directories if (![[NSFileManager defaultManager] oo_oxzFileExistsAtPath:filePath]) return nil; NSString *extension = [[filePath pathExtension] lowercaseString]; if ([extension isEqualToString:@"js"] || [extension isEqualToString:@"es"]) { NSArray *result = nil; OOScript *script = [OOJSScript scriptWithPath:filePath properties:nil]; if (script != nil) result = [NSArray arrayWithObject:script]; return result; } else if ([extension isEqualToString:@"plist"]) { OOStandardsDeprecated([NSString stringWithFormat:@"Legacy script %@ is deprecated",filePath]); if (OOEnforceStandards()) { return nil; } return [OOPListScript scriptsInPListFile:filePath]; } OOLogERR(@"script.load.badName", @"Don't know how to load a script from %@.", filePath); return nil; } + (id)jsScriptFromFileNamed:(NSString *)fileName properties:(NSDictionary *)properties { NSString *extension = nil; NSString *path = nil; if ([fileName length] == 0) return nil; extension = [[fileName pathExtension] lowercaseString]; if ([extension isEqualToString:@"js"] || [extension isEqualToString:@"es"]) { path = [ResourceManager pathForFileNamed:fileName inFolder:@"Scripts"]; if (path == nil) { OOLogERR(@"script.load.notFound", @"Could not find script file %@.", fileName); return nil; } return [OOJSScript scriptWithPath:path properties:properties]; } else if ([extension isEqualToString:@"plist"]) { OOLogERR(@"script.load.badName", @"Can't load script named %@ - legacy scripts are not supported in this context.", fileName); return nil; } OOLogERR(@"script.load.badName", @"Don't know how to load a script from %@.", fileName); return nil; } + (id)jsAIScriptFromFileNamed:(NSString *)fileName properties:(NSDictionary *)properties { NSString *extension = nil; NSString *path = nil; if ([fileName length] == 0) return nil; extension = [[fileName pathExtension] lowercaseString]; if ([extension isEqualToString:@"js"] || [extension isEqualToString:@"es"]) { path = [ResourceManager pathForFileNamed:fileName inFolder:@"AIs"]; if (path == nil) { OOLogERR(@"script.load.notFound", @"Could not find script file %@.", fileName); return nil; } return [OOJSScript scriptWithPath:path properties:properties]; } else if ([extension isEqualToString:@"plist"]) { OOLogERR(@"script.load.badName", @"Can't load script named %@ - legacy scripts are not supported in this context.", fileName); return nil; } OOLogERR(@"script.load.badName", @"Don't know how to load a script from %@.", fileName); return nil; } - (NSString *)descriptionComponents { return [NSString stringWithFormat:@"\"%@\" version %@", [self name], [self version]]; } - (NSString *)name { OOLogERR(kOOLogSubclassResponsibility, @"OOScript should not be used directly!"); return nil; } - (NSString *)scriptDescription { OOLogERR(kOOLogSubclassResponsibility, @"OOScript should not be used directly!"); return nil; } - (NSString *)version { OOLogERR(kOOLogSubclassResponsibility, @"OOScript should not be used directly!"); return nil; } - (NSString *)displayName { NSString *name = [self name]; NSString *version = [self version]; if (version != nil) return [NSString stringWithFormat:@"%@ %@", name, version]; else if (name != nil) return [NSString stringWithFormat:@"%@", name]; else return nil; } - (BOOL) requiresTickle { return NO; } - (void)runWithTarget:(Entity *)target { OOLogERR(kOOLogSubclassResponsibility, @"OOScript should not be used directly!"); } @end oolite-1.82/src/Core/Scripting/OOScriptTimer.h000066400000000000000000000041061256642440500212450ustar00rootroot00000000000000/* OOScriptTimer.h Abstract base class for script timers. An OOScriptTimer does nothing when it fires; subclasses should override the -timerFired method. Timers are immutable. They are retained by the timer subsystem while scheduled. A timer with a negative interval will only fire once. A negative nexttime when inited will cause the timer to fire after the specified interval. A persistent timer will remain if the player dies and respawns; non-persistent timers will be removed. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #import "OOTypes.h" @interface OOScriptTimer: NSObject { @private OOTimeAbsolute _nextTime; OOTimeDelta _interval; BOOL _isScheduled; BOOL _hasBeenRun; // Needed for one-shot timers. } - (id) initWithNextTime:(OOTimeAbsolute)nextTime interval:(OOTimeDelta)interval; // Sets nextTime to current time + delay. - (id) initOneShotTimerWithDelay:(OOTimeDelta)delay; - (OOTimeAbsolute)nextTime; - (BOOL)setNextTime:(OOTimeAbsolute)nextTime; // Only works when timer is not scheduled. - (OOTimeDelta)interval; - (void)setInterval:(OOTimeDelta)interval; // Subclass responsibility: - (void) timerFired; - (BOOL) scheduleTimer; - (void) unscheduleTimer; - (BOOL) isScheduled; + (void) updateTimers; + (void) noteGameReset; - (BOOL) isValidForScheduling; - (NSComparisonResult) compareByNextFireTime:(OOScriptTimer *)other; @end oolite-1.82/src/Core/Scripting/OOScriptTimer.m000066400000000000000000000123331256642440500212530ustar00rootroot00000000000000/* OOScriptTimer.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOScriptTimer.h" #import "Universe.h" #import "OOLogging.h" #import "OOPriorityQueue.h" static OOPriorityQueue *sTimers; // During an update, new timers must be deferred to avoid an infinite loop. static BOOL sUpdating; static NSMutableArray *sDeferredTimers; @implementation OOScriptTimer - (id) initWithNextTime:(OOTimeAbsolute)nextTime interval:(OOTimeDelta)interval { OOTimeAbsolute now; if ((self = [super init])) { if (interval <= 0.0) interval = -1.0; now = [UNIVERSE getTime]; if (nextTime < 0.0) nextTime = now + interval; if (nextTime < now && interval < 0) { // Negative or old nextTime and negative interval = meaningless. [self release]; self = nil; } else { _nextTime = nextTime; _interval = interval; _hasBeenRun = NO; } } return self; } // Sets nextTime to current time + delay. - (id) initOneShotTimerWithDelay:(OOTimeDelta)delay { return [self initWithNextTime:[UNIVERSE getTime] + delay interval:-1.0]; } - (void) dealloc { if (_isScheduled) [self unscheduleTimer]; [super dealloc]; } - (NSString *) descriptionComponents { NSString *intervalDesc = nil; if (_interval <= 0.0) intervalDesc = @"one-shot"; else intervalDesc = [NSString stringWithFormat:@"interval: %g", _interval]; return [NSString stringWithFormat:@"nextTime: %g, %@, %srunning", _nextTime, intervalDesc, _isScheduled ? "" : "not "]; } - (OOTimeAbsolute)nextTime { return _nextTime; } - (BOOL)setNextTime:(OOTimeAbsolute)nextTime { if (_isScheduled) return NO; _nextTime = nextTime; return YES; } - (OOTimeDelta)interval { return _interval; } - (void)setInterval:(OOTimeDelta)interval { if (interval <= 0.0) interval = -1.0; _interval = interval; } - (void) timerFired { OOLogGenericSubclassResponsibility(); } - (BOOL) scheduleTimer { if (_isScheduled) return YES; if (![self isValidForScheduling]) return NO; if (EXPECT(!sUpdating)) { if (EXPECT_NOT(sTimers == nil)) sTimers = [[OOPriorityQueue alloc] initWithComparator:@selector(compareByNextFireTime:)]; [sTimers addObject:self]; } else { if (sDeferredTimers == nil) sDeferredTimers = [[NSMutableArray alloc] init]; [sDeferredTimers addObject:self]; } _isScheduled = YES; return YES; } - (void) unscheduleTimer { [sTimers removeExactObject:self]; _isScheduled = NO; _hasBeenRun = NO; } - (BOOL) isScheduled { return _isScheduled; } + (void) updateTimers { OOScriptTimer *timer = nil; OOTimeAbsolute now; sUpdating = YES; now = [UNIVERSE getTime]; for (;;) { timer = [sTimers peekAtNextObject]; if (timer == nil || now < [timer nextTime]) break; [sTimers removeNextObject]; // Must fire before rescheduling so that the timer callback can stop itself. -- Ahruman 2011-01-01 [timer timerFired]; timer->_hasBeenRun = YES; if (timer->_isScheduled) { timer->_isScheduled = NO; [timer scheduleTimer]; } } if (sDeferredTimers != nil) { [sTimers addObjects:sDeferredTimers]; DESTROY(sDeferredTimers); } sUpdating = NO; } + (void) noteGameReset { NSArray *timers = nil; NSEnumerator *timerEnum = nil; OOScriptTimer *timer = nil; // Intermediate array is required so we don't get stuck in an endless loop over reinserted timers. Note that -sortedObjects also clears the queue! timers = [sTimers sortedObjects]; for (timerEnum = [timers objectEnumerator]; (timer = [timerEnum nextObject]); ) { timer->_isScheduled = NO; } } - (BOOL) isValidForScheduling { OOTimeAbsolute now; double scaled; now = [UNIVERSE getTime]; if (_nextTime <= now) { if (_interval <= 0.0 && _hasBeenRun) return NO; // One-shot timer which has expired // Move _nextTime to the closest future time that's a multiple of _interval scaled = (now - _nextTime) / _interval; scaled = ceil(scaled); _nextTime += scaled * _interval; if (_nextTime <= now && _hasBeenRun) { // Should only happen if _nextTime is exactly equal to now after previous stuff _nextTime += _interval; } } return YES; } - (NSComparisonResult) compareByNextFireTime:(OOScriptTimer *)other { OOTimeAbsolute otherTime = -INFINITY; @try { if (other != nil) otherTime = [other nextTime]; } @catch (NSException *exception) { OOLog(kOOLogException, @"\n\n***** Ignoring Timer Exception: %@ : %@ *****\n\n",[exception name], [exception reason]); } if (_nextTime < otherTime) return NSOrderedAscending; else if (_nextTime > otherTime) return NSOrderedDescending; else return NSOrderedSame; } @end oolite-1.82/src/Core/Tables/000077500000000000000000000000001256642440500156405ustar00rootroot00000000000000oolite-1.82/src/Core/Tables/OOBehaviour.tbl000066400000000000000000000032701256642440500205270ustar00rootroot00000000000000ENTRY(BEHAVIOUR_IDLE, 0U) ENTRY(BEHAVIOUR_TRACK_TARGET, 1) ENTRY(BEHAVIOUR_TUMBLE, 4) ENTRY(BEHAVIOUR_STOP_STILL, 5) ENTRY(BEHAVIOUR_STATION_KEEPING, 10) ENTRY(BEHAVIOUR_ATTACK_TARGET, 101) ENTRY(BEHAVIOUR_ATTACK_FLY_TO_TARGET, 102) ENTRY(BEHAVIOUR_ATTACK_FLY_FROM_TARGET, 103) ENTRY(BEHAVIOUR_RUNNING_DEFENSE, 104) // fleeing ENTRY(BEHAVIOUR_FLEE_TARGET, 105) // advanced combat... ENTRY(BEHAVIOUR_ATTACK_FLY_TO_TARGET_SIX, 106) ENTRY(BEHAVIOUR_ATTACK_MINING_TARGET, 107) ENTRY(BEHAVIOUR_EVASIVE_ACTION, 108) ENTRY(BEHAVIOUR_FLEE_EVASIVE_ACTION, 109) ENTRY(BEHAVIOUR_ATTACK_BREAK_OFF_TARGET, 110) ENTRY(BEHAVIOUR_ATTACK_SLOW_DOGFIGHT, 111) // further advanced combat... ENTRY(BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE,112) ENTRY(BEHAVIOUR_ATTACK_SNIPER, 113) // side weapon combat... ENTRY(BEHAVIOUR_ATTACK_BROADSIDE, 115) ENTRY(BEHAVIOUR_ATTACK_BROADSIDE_LEFT, 116) ENTRY(BEHAVIOUR_ATTACK_BROADSIDE_RIGHT, 117) ENTRY(BEHAVIOUR_CLOSE_TO_BROADSIDE_RANGE, 118) ENTRY(BEHAVIOUR_CLOSE_WITH_TARGET, 125) ENTRY(BEHAVIOUR_AVOID_COLLISION, 130) ENTRY(BEHAVIOUR_TRACK_AS_TURRET, 150) ENTRY(BEHAVIOUR_FLY_RANGE_FROM_DESTINATION, 200) ENTRY(BEHAVIOUR_FLY_TO_DESTINATION, 201) ENTRY(BEHAVIOUR_FLY_FROM_DESTINATION, 202) ENTRY(BEHAVIOUR_FACE_DESTINATION, 203) ENTRY(BEHAVIOUR_FLY_THRU_NAVPOINTS, 210) ENTRY(BEHAVIOUR_LAND_ON_PLANET, 250) ENTRY(BEHAVIOUR_COLLECT_TARGET, 300) ENTRY(BEHAVIOUR_INTERCEPT_TARGET, 350) ENTRY(BEHAVIOUR_FORMATION_FORM_UP, 501) ENTRY(BEHAVIOUR_FORMATION_BREAK, 502) ENTRY(BEHAVIOUR_ENERGY_BOMB_COUNTDOWN, 601) ENTRY(BEHAVIOUR_TRACTORED, 701) ENTRY(BEHAVIOUR_SCRIPTED_AI, 801) ENTRY(BEHAVIOUR_SCRIPTED_ATTACK_AI, 811) oolite-1.82/src/Core/Tables/OOCompassMode.tbl000066400000000000000000000004251256642440500210140ustar00rootroot00000000000000// NOTE: numerical values are available to shaders. ENTRY(COMPASS_MODE_INACTIVE, -1) ENTRY(COMPASS_MODE_BASIC, 0) ENTRY(COMPASS_MODE_PLANET, 1) ENTRY(COMPASS_MODE_STATION, 3) ENTRY(COMPASS_MODE_SUN, 4) ENTRY(COMPASS_MODE_TARGET, 5) ENTRY(COMPASS_MODE_BEACONS, 6) oolite-1.82/src/Core/Tables/OOEntityStatus.tbl000066400000000000000000000012121256642440500212550ustar00rootroot00000000000000ENTRY(STATUS_START_GAME, -10) ENTRY(STATUS_RESTART_GAME, -2) ENTRY(STATUS_DEAD, -1) ENTRY(STATUS_INACTIVE, 0) ENTRY(STATUS_TEST, 1) ENTRY(STATUS_COCKPIT_DISPLAY, 2) ENTRY(STATUS_ACTIVE, 5) ENTRY(STATUS_EFFECT, 10) ENTRY(STATUS_IN_FLIGHT, 100) ENTRY(STATUS_DOCKED, 200) ENTRY(STATUS_AUTOPILOT_ENGAGED, 300) ENTRY(STATUS_DOCKING, 401) ENTRY(STATUS_LAUNCHING, 402) ENTRY(STATUS_WITCHSPACE_COUNTDOWN, 410) ENTRY(STATUS_ENTERING_WITCHSPACE, 411) ENTRY(STATUS_EXITING_WITCHSPACE, 412) ENTRY(STATUS_ESCAPE_SEQUENCE, 500) ENTRY(STATUS_IN_HOLD, 600) ENTRY(STATUS_BEING_SCOOPED, 700) ENTRY(STATUS_HANDLING_ERROR, 999) oolite-1.82/src/Core/Tables/OOGUIScreenID.tbl000066400000000000000000000015521256642440500206050ustar00rootroot00000000000000ENTRY(GUI_SCREEN_MAIN, -1) ENTRY(GUI_SCREEN_INTRO1, -1) ENTRY(GUI_SCREEN_SHIPLIBRARY, -1) ENTRY(GUI_SCREEN_KEYBOARD, -1) ENTRY(GUI_SCREEN_NEWGAME, -1) ENTRY(GUI_SCREEN_OXZMANAGER, -1) ENTRY(GUI_SCREEN_STATUS, -1) ENTRY(GUI_SCREEN_MANIFEST, -1) ENTRY(GUI_SCREEN_EQUIP_SHIP, -1) ENTRY(GUI_SCREEN_SHIPYARD, -1) ENTRY(GUI_SCREEN_LONG_RANGE_CHART, -1) ENTRY(GUI_SCREEN_SHORT_RANGE_CHART, -1) ENTRY(GUI_SCREEN_SYSTEM_DATA, -1) ENTRY(GUI_SCREEN_MARKET, -1) ENTRY(GUI_SCREEN_MARKETINFO, -1) // ENTRY(GUI_SCREEN_CONTRACTS, -1) ENTRY(GUI_SCREEN_OPTIONS, -1) ENTRY(GUI_SCREEN_GAMEOPTIONS, -1) ENTRY(GUI_SCREEN_LOAD, -1) ENTRY(GUI_SCREEN_SAVE, -1) ENTRY(GUI_SCREEN_SAVE_OVERWRITE, -1) ENTRY(GUI_SCREEN_STICKMAPPER, -1) ENTRY(GUI_SCREEN_STICKPROFILE, -1) ENTRY(GUI_SCREEN_MISSION, -1) ENTRY(GUI_SCREEN_REPORT, -1) ENTRY(GUI_SCREEN_INTERFACES, -1) oolite-1.82/src/Core/Tables/OOGalacticHyperspaceBehaviour.tbl000066400000000000000000000003411256642440500241770ustar00rootroot00000000000000GALACTIC_HYPERSPACE_ENTRY(BEHAVIOUR_STANDARD, 0) GALACTIC_HYPERSPACE_ENTRY(BEHAVIOUR_ALL_SYSTEMS_REACHABLE, 1) GALACTIC_HYPERSPACE_ENTRY(BEHAVIOUR_FIXED_COORDINATES, 2) GALACTIC_HYPERSPACE_ENTRY(BEHAVIOUR_UNKNOWN, -1) oolite-1.82/src/Core/Tables/OOLegalStatusReason.tbl000066400000000000000000000024141256642440500222020ustar00rootroot00000000000000DIFF_STRING_ENTRY(kOOLegalStatusReasonUnknown, "unknown") DIFF_STRING_ENTRY(kOOLegalStatusReasonSetup, "setup actions") DIFF_STRING_ENTRY(kOOLegalStatusReasonByScript, "scripted") DIFF_STRING_ENTRY(kOOLegalStatusReasonAttackedPolice, "attacked police") DIFF_STRING_ENTRY(kOOLegalStatusReasonAttackedMainStation, "attacked main station") DIFF_STRING_ENTRY(kOOLegalStatusReasonAttackedInnocent, "attacked innocent") DIFF_STRING_ENTRY(kOOLegalStatusReasonSeenByPolice, "seen by police") DIFF_STRING_ENTRY(kOOLegalStatusReasonDistressCall, "distress call") DIFF_STRING_ENTRY(kOOLegalStatusReasonIllegalExports, "illegal exports") DIFF_STRING_ENTRY(kOOLegalStatusReasonIllegalImports, "illegal imports") DIFF_STRING_ENTRY(kOOLegalStatusReasonAssistingOffender, "assisting offenders") DIFF_STRING_ENTRY(kOOLegalStatusReasonNewGalaxy, "new galaxy") DIFF_STRING_ENTRY(kOOLegalStatusReasonNewSystem, "new system") DIFF_STRING_ENTRY(kOOLegalStatusReasonPaidFine, "paid fine") DIFF_STRING_ENTRY(kOOLegalStatusReasonEscapePod, "escape pod") DIFF_STRING_ENTRY(kOOLegalStatusReasonAssistingPolice, "assisting police") DIFF_STRING_ENTRY(kOOLegalStatusReasonPoliceAreClean, "police are clean") // DIFF_STRING_ENTRY(kOOLegalStatusReasonThereCanBeOnlyOne, "there can be only one") oolite-1.82/src/Core/Tables/OOOpenGLStates.tbl000066400000000000000000000043001256642440500211060ustar00rootroot00000000000000/* State items and their abstract types for the OpenGL state setting/ verification system. This is included several times in OOOpenGL.m. Field names correspond to OpenGL enumerants without the GL_ prefix. ITEM_STATEFLAG(name) defines a field whose type is StateFlag and which is read using glIsEnabled() and written using glEnable()/glDisable(). StateFlag can be false, true, or kStateMaybe, in which case it is ignored by the verifier. ITEM_CLIENTSTATEFLAG(name) is similar to ITEM_STATEFLAG, but values are written using glEnableClientState()/glDisableClientState() and the Maybe state is not supported. ITEM_SPECIAL(name, type, displayFunc) defines a field of the specified type which is read and written by calling functions GetState_ and SetState_. is the name of a function that converts the value to an NSString. ITEM_INT(name) is like ITEM_SPECIAL(name, GLint, OOGLEnumToString) except that values are read using glGetIntegerv(). */ ITEM_STATEFLAG(LIGHTING); ITEM_STATEFLAG(LIGHT0); ITEM_STATEFLAG(LIGHT1); ITEM_STATEFLAG(LIGHT2); ITEM_STATEFLAG(LIGHT3); ITEM_STATEFLAG(LIGHT4); ITEM_STATEFLAG(LIGHT5); ITEM_STATEFLAG(LIGHT6); ITEM_STATEFLAG(LIGHT7); ITEM_STATEFLAG(TEXTURE_2D); ITEM_STATEFLAG(COLOR_MATERIAL); ITEM_STATEFLAG(BLEND); ITEM_STATEFLAG(FOG); ITEM_STATEFLAG(DEPTH_TEST); ITEM_STATEFLAG(NORMALIZE); ITEM_STATEFLAG(RESCALE_NORMAL); ITEM_CLIENTSTATEFLAG(VERTEX_ARRAY); ITEM_CLIENTSTATEFLAG(NORMAL_ARRAY); ITEM_CLIENTSTATEFLAG(COLOR_ARRAY); ITEM_CLIENTSTATEFLAG(INDEX_ARRAY); ITEM_CLIENTSTATEFLAG(TEXTURE_COORD_ARRAY); ITEM_CLIENTSTATEFLAG(EDGE_FLAG_ARRAY); // Smoothing flags are not included. They're set once at startup and never // disabled, except that they're not enabled if gpu-settings.plist says not to. //ITEM_SPECIAL(POINT_SMOOTH); //ITEM_SPECIAL(LINE_SMOOTH); ITEM_SPECIAL(DEPTH_WRITEMASK, bool, OOGLFlagToString); ITEM_STATEFLAG(CULL_FACE); ITEM_INT(SHADE_MODEL); ITEM_SPECIAL(TEXTURE_ENV_MODE, GLenum, OOGLEnumToString); ITEM_SPECIAL(ACTIVE_TEXTURE, GLenum, OOGLEnumToString); ITEM_SPECIAL(CLIENT_ACTIVE_TEXTURE, GLenum, OOGLEnumToString); ITEM_INT(CULL_FACE_MODE); ITEM_INT(FRONT_FACE); // Extra special handling: blend mode (two getters but one setter). oolite-1.82/src/Core/Tables/OOScanClass.tbl000066400000000000000000000006741256642440500204620ustar00rootroot00000000000000ENTRY(CLASS_NOT_SET, -1) ENTRY(CLASS_NO_DRAW, 0) ENTRY(CLASS_NEUTRAL, 1) ENTRY(CLASS_STATION, 3) ENTRY(CLASS_TARGET, 4) ENTRY(CLASS_CARGO, 5) ENTRY(CLASS_MISSILE, 6) ENTRY(CLASS_ROCK, 7) ENTRY(CLASS_MINE, 8) ENTRY(CLASS_THARGOID, 9) ENTRY(CLASS_BUOY, 10) ENTRY(CLASS_VISUAL_EFFECT, 11) ENTRY(CLASS_WORMHOLE, 444) ENTRY(CLASS_PLAYER, 100) ENTRY(CLASS_POLICE, 999) ENTRY(CLASS_MILITARY, 333) oolite-1.82/src/Core/Tables/OOShipDamageType.tbl000066400000000000000000000006221256642440500214450ustar00rootroot00000000000000DIFF_STRING_ENTRY(kOODamageTypeScrape, "scrape damage") DIFF_STRING_ENTRY(kOODamageTypeHeat, "heat damage") DIFF_STRING_ENTRY(kOODamageTypeEnergy, "energy damage") DIFF_STRING_ENTRY(kOODamageTypeCascadeWeapon, "cascade weapon") DIFF_STRING_ENTRY(kOODamageTypeHitAPlanet, "hit a planet") DIFF_STRING_ENTRY(kOODamageTypeHitASun, "hit a sun") DIFF_STRING_ENTRY(kOODamageTypeRemoved, "removed") oolite-1.82/src/Core/Tables/OOViewID.tbl000066400000000000000000000003311256642440500177250ustar00rootroot00000000000000ENTRY(VIEW_FORWARD, 0) ENTRY(VIEW_AFT, 1) ENTRY(VIEW_PORT, 2) ENTRY(VIEW_STARBOARD, 3) ENTRY(VIEW_CUSTOM, 7) ENTRY(VIEW_NONE, -1) ENTRY(VIEW_GUI_DISPLAY, 10) ENTRY(VIEW_BREAK_PATTERN, 20) oolite-1.82/src/Core/TextureStore.h000066400000000000000000000031021256642440500172500ustar00rootroot00000000000000/* TextureStore.h Singleton responsible for loading, binding and caching textures. Legacy class, used only by PlanetEntity. Use OOTexture or OOMaterial for any new development. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // TextureStore is only used for old planets. #import "OOStellarBody.h" #if !NEW_PLANETS #import "OOCocoa.h" #import "OOOpenGL.h" @class OOColor; @interface TextureStore: NSObject // routines to create textures... + (BOOL) getPlanetTextureNameFor:(NSDictionary *)planetInfo intoData:(unsigned char **)textureData width:(GLuint *)textureWidth height:(GLuint *)textureHeight; + (BOOL) getCloudTextureNameFor:(OOColor *)color :(GLfloat)impress :(GLfloat)bias intoData:(unsigned char **)textureData width:(GLuint *)textureWidth height:(GLuint *)textureHeight; @end void fillRanNoiseBuffer(); #endif // !NEW_PLANETS oolite-1.82/src/Core/TextureStore.m000066400000000000000000000237751256642440500172770ustar00rootroot00000000000000/* TextureStore.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "TextureStore.h" #if !NEW_PLANETS #import "OOMaths.h" #ifndef NDEBUG #import "Universe.h" #import "MyOpenGLView.h" #else #import "OOColor.h" #endif #import "OOCollectionExtractors.h" #define DEBUG_DUMP ( 0 && !defined(NDEBUG)) static NSString * const kOOLogPlanetTextureGen = @"texture.planet.generate"; #import "OOTextureGenerator.h" // For FloatRGB static FloatRGB FloatRGBFromDictColor(NSDictionary *dictionary, NSString *key) { OOColor *color = [dictionary objectForKey:key]; if (color == nil) { // could not get a color from the dicitionary, return white color instead of hitting the assert below color = [OOColor colorWithDescription:@"whiteColor"]; OOLog(@"textureStore.FloatRGBFromDictColor.nilColor", @"Expected color for key \"%@\" in dictionary %@, got nil. Setting color to %@", key, dictionary, [color rgbaDescription]); } NSCAssert1([color isKindOfClass:[OOColor class]], @"Expected OOColor, got %@", [color class]); return (FloatRGB){ [color redComponent], [color greenComponent], [color blueComponent] }; } static FloatRGB Blend(float fraction, FloatRGB a, FloatRGB b) { return (FloatRGB) { OOLerp(a.r, b.r, fraction), OOLerp(a.g, b.g, fraction), OOLerp(a.b, b.b, fraction) }; } static FloatRGB PlanetTextureColor(float q, float impress, float bias, FloatRGB seaColor, FloatRGB paleSeaColor, FloatRGB landColor, FloatRGB paleLandColor) { const FloatRGB kWhite = { 1.0, 1.0, 1.0 }; float maxq = impress + bias; float hi = 0.66667 * maxq; float oh = 1.0 / hi; float ih = 1.0 / (1.0 - hi); if (q <= 0.0) { return seaColor; } if (q > 1.0) { return (FloatRGB){ 1.0f, 1.0f, 1.0f }; } if (q < 0.01) { return Blend(q * 100.0f, paleSeaColor, landColor); } if (q > hi) { return Blend((q - hi) * ih, paleLandColor, kWhite); // snow capped peaks } return Blend((hi - q) * oh, paleLandColor, landColor); } static void fillSquareImageDataWithCloudTexture(unsigned char * imageBuffer, int width, OOColor* cloudcolor, float impress, float bias); static void fillSquareImageWithPlanetTex(unsigned char * imageBuffer, int width, float impress, float bias, FloatRGB seaColor, FloatRGB paleSeaColor, FloatRGB landColor, FloatRGB paleLandColor); @implementation TextureStore #define PROC_TEXTURE_SIZE 512 + (BOOL) getPlanetTextureNameFor:(NSDictionary *)planetInfo intoData:(unsigned char **)textureData width:(GLuint *)textureWidth height:(GLuint *)textureHeight { int texture_h = PROC_TEXTURE_SIZE; int texture_w = PROC_TEXTURE_SIZE; int tex_bytes = texture_w * texture_h * 4; NSParameterAssert(textureData != NULL && textureWidth != NULL && textureHeight != NULL); unsigned char *imageBuffer = malloc(tex_bytes); if (imageBuffer == NULL) return NO; *textureData = imageBuffer; *textureWidth = texture_w; *textureHeight = texture_h; float land_fraction = [[planetInfo objectForKey:@"land_fraction"] floatValue]; float sea_bias = land_fraction - 1.0; OOLog(kOOLogPlanetTextureGen, @"genning texture for land_fraction %.5f", land_fraction); FloatRGB land_color = FloatRGBFromDictColor(planetInfo, @"land_color"); FloatRGB sea_color = FloatRGBFromDictColor(planetInfo, @"sea_color"); FloatRGB polar_land_color = FloatRGBFromDictColor(planetInfo, @"polar_land_color"); FloatRGB polar_sea_color = FloatRGBFromDictColor(planetInfo, @"polar_sea_color"); // Pale sea colour gives a better transition between land and sea., Backported from the new planets code. FloatRGB pale_sea_color = Blend(0.45, polar_sea_color, Blend(0.7, sea_color, land_color)); fillSquareImageWithPlanetTex(imageBuffer, texture_w, 1.0, sea_bias, sea_color, pale_sea_color, land_color, polar_land_color); return YES; } + (BOOL) getCloudTextureNameFor:(OOColor*)color :(GLfloat)impress :(GLfloat)bias intoData:(unsigned char **)textureData width:(GLuint *)textureWidth height:(GLuint *)textureHeight { int texture_h = PROC_TEXTURE_SIZE; int texture_w = PROC_TEXTURE_SIZE; int tex_bytes; tex_bytes = texture_w * texture_h * 4; NSParameterAssert(textureData != NULL && textureWidth != NULL && textureHeight != NULL); unsigned char *imageBuffer = malloc(tex_bytes); if (imageBuffer == NULL) return NO; *textureData = imageBuffer; *textureWidth = texture_w; *textureHeight = texture_h; fillSquareImageDataWithCloudTexture( imageBuffer, texture_w, color, impress, bias); return YES; } @end static RANROTSeed sNoiseSeed; static float ranNoiseBuffer[128 * 128]; void fillRanNoiseBuffer() { sNoiseSeed = RANROTGetFullSeed(); int i; for (i = 0; i < 16384; i++) ranNoiseBuffer[i] = randf(); } static void addNoise(float * buffer, int p, int n, float scale) { int x, y; float r = (float)p / (float)n; for (y = 0; y < p; y++) for (x = 0; x < p; x++) { int ix = floor( (float)x / r); int jx = (ix + 1) % n; int iy = floor( (float)y / r); int jy = (iy + 1) % n; float qx = x / r - ix; float qy = y / r - iy; ix &= 127; iy &= 127; jx &= 127; jy &= 127; float rix = OOLerp(ranNoiseBuffer[iy * 128 + ix], ranNoiseBuffer[iy * 128 + jx], qx); float rjx = OOLerp(ranNoiseBuffer[jy * 128 + ix], ranNoiseBuffer[jy * 128 + jx], qx); float rfinal = scale * OOLerp(rix, rjx, qy); buffer[y * p + x] += rfinal; } } static float q_factor(float* accbuffer, int x, int y, int width, BOOL polar_y_smooth, float polar_y_value, BOOL polar_x_smooth, float polar_x_value, float impress, float bias) { while ( x < 0 ) x+= width; while ( y < 0 ) y+= width; while ( x >= width ) x-= width; while ( y >= width ) y-= width; float q = accbuffer[ y * width + x]; // 0.0 -> 1.0 q *= impress; // impress q += bias; // + bias float polar_y = (2.0f * y - width) / (float) width; float polar_x = (2.0f * x - width) / (float) width; polar_x *= polar_x; polar_y *= polar_y; if (polar_x_smooth) q = q * (1.0 - polar_x) + polar_x * polar_x_value; if (polar_y_smooth) q = q * (1.0 - polar_y) + polar_y * polar_y_value; if (q > 1.0) q = 1.0; if (q < 0.0) q = 0.0; return q; } static void fillSquareImageDataWithCloudTexture(unsigned char * imageBuffer, int width, OOColor* cloudcolor, float impress, float bias) { NSCParameterAssert(width > 0); float accbuffer[width * width]; memset(accbuffer, 0, sizeof accbuffer); int x, y; GLfloat rgba[4]; rgba[0] = [cloudcolor redComponent]; rgba[1] = [cloudcolor greenComponent]; rgba[2] = [cloudcolor blueComponent]; rgba[3] = [cloudcolor alphaComponent]; int octave = 8; float scale = 0.5; while (octave < width) { addNoise(accbuffer, width, octave, scale); octave *= 2; scale *= 0.5; } float pole_value = (impress * accbuffer[0] - bias < 0.0)? 0.0: 1.0; for (y = 0; y < width; y++) for (x = 0; x < width; x++) { float q = q_factor(accbuffer, x, y, width, YES, pole_value, NO, 0.0, impress, bias); imageBuffer[0 + 4 * (y * width + x) ] = 255 * rgba[0]; imageBuffer[1 + 4 * (y * width + x) ] = 255 * rgba[1]; imageBuffer[2 + 4 * (y * width + x) ] = 255 * rgba[2]; imageBuffer[3 + 4 * (y * width + x) ] = 255 * rgba[3] * q; } #if DEBUG_DUMP NSString *name = [NSString stringWithFormat:@"atmosphere-%u-%u-old", sNoiseSeed.high, sNoiseSeed.low]; OOLog(@"planetTex.dump", [NSString stringWithFormat:@"Saving generated texture to file %@.", name]); [[UNIVERSE gameView] dumpRGBAToFileNamed:name bytes:imageBuffer width:width height:width rowBytes:width * 4]; #endif } static void fillSquareImageWithPlanetTex(unsigned char * imageBuffer, int width, float impress, float bias, FloatRGB seaColor, FloatRGB paleSeaColor, FloatRGB landColor, FloatRGB paleLandColor) { float accbuffer[width * width]; memset(accbuffer, 0, sizeof accbuffer); int octave = 8; float scale = 0.5; while (octave < width) { addNoise(accbuffer, width, octave, scale); octave *= 2; scale *= 0.5; } float pole_value = (impress + bias > 0.5)? 0.5 * (impress + bias) : 0.0; int x, y; for (y = 0; y < width; y++) for (x = 0; x < width; x++) { float q = q_factor(accbuffer, x, y, width, YES, pole_value, NO, 0.0, impress, bias); float yN = q_factor(accbuffer, x, y - 1, width, YES, pole_value, NO, 0.0, impress, bias); float yS = q_factor(accbuffer, x, y + 1, width, YES, pole_value, NO, 0.0, impress, bias); float yW = q_factor(accbuffer, x - 1, y, width, YES, pole_value, NO, 0.0, impress, bias); float yE = q_factor(accbuffer, x + 1, y, width, YES, pole_value, NO, 0.0, impress, bias); Vector norm = make_vector( 24.0 * (yW - yE), 24.0 * (yS - yN), 2.0); norm = vector_normal(norm); GLfloat shade = pow(norm.z, 3.2); FloatRGB color = PlanetTextureColor(q, impress, bias, seaColor, paleSeaColor, landColor, paleLandColor); color.r *= shade; color.g *= shade; color.b *= shade; imageBuffer[0 + 4 * (y * width + x)] = 255 * color.r; imageBuffer[1 + 4 * (y * width + x)] = 255 * color.g; imageBuffer[2 + 4 * (y * width + x)] = 255 * color.b; imageBuffer[3 + 4 * (y * width + x)] = 255; } #if DEBUG_DUMP OOLog(@"planetTex.dump", [NSString stringWithFormat:@"Saving generated texture to file planet-%u-%u-old.", sNoiseSeed.high, sNoiseSeed.low]); [[UNIVERSE gameView] dumpRGBAToFileNamed:[NSString stringWithFormat:@"planet-%u-%u-old", sNoiseSeed.high, sNoiseSeed.low] bytes:imageBuffer width:width height:width rowBytes:width * 4]; #endif } #endif // !NEW_PLANETS oolite-1.82/src/Core/Universe.h000066400000000000000000000731021256642440500164020ustar00rootroot00000000000000/* Universe.h Manages a lot of stuff that isn't managed somewhere else. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #import "OOOpenGL.h" #import "legacy_random.h" #import "OOMaths.h" #import "OOColor.h" #import "OOWeakReference.h" #import "OOTypes.h" #import "OOSound.h" #import "OOJSPropID.h" #import "OOStellarBody.h" #import "OOEntityWithDrawable.h" #import "OOCommodities.h" #import "OOSystemDescriptionManager.h" #if OOLITE_ESPEAK #include #endif @class GameController, CollisionRegion, MyOpenGLView, GuiDisplayGen, Entity, ShipEntity, StationEntity, OOPlanetEntity, OOSunEntity, OOVisualEffectEntity, PlayerEntity, OORoleSet, WormholeEntity, DockEntity, OOJSScript, OOWaypointEntity, OOSystemDescriptionManager; typedef BOOL (*EntityFilterPredicate)(Entity *entity, void *parameter); #ifndef OO_SCANCLASS_TYPE #define OO_SCANCLASS_TYPE typedef enum OOScanClass OOScanClass; #endif #define CROSSHAIR_SIZE 32.0 enum { MARKET_NAME = 0, MARKET_QUANTITY = 1, MARKET_PRICE = 2, MARKET_BASE_PRICE = 3, MARKET_ECO_ADJUST_PRICE = 4, MARKET_ECO_ADJUST_QUANTITY = 5, MARKET_BASE_QUANTITY = 6, MARKET_MASK_PRICE = 7, MARKET_MASK_QUANTITY = 8, MARKET_UNITS = 9 }; enum { EQUIPMENT_TECH_LEVEL_INDEX = 0, EQUIPMENT_PRICE_INDEX = 1, EQUIPMENT_SHORT_DESC_INDEX = 2, EQUIPMENT_KEY_INDEX = 3, EQUIPMENT_LONG_DESC_INDEX = 4, EQUIPMENT_EXTRA_INFO_INDEX = 5 }; #define SHADERS_MIN SHADERS_OFF #define MAX_MESSAGES 5 #define PROXIMITY_WARN_DISTANCE 4 // Eric 2010-10-17: old value was 20.0 #define PROXIMITY_WARN_DISTANCE2 (PROXIMITY_WARN_DISTANCE * PROXIMITY_WARN_DISTANCE) #define PROXIMITY_AVOID_DISTANCE_FACTOR 10.0 #define SAFE_ADDITION_FACTOR2 800 // Eric 2010-10-17: used to be "2 * PROXIMITY_WARN_DISTANCE2" #define SUN_SKIM_RADIUS_FACTOR 1.15470053838 // 2 sqrt(3) / 3. Why? I have no idea. -- Ahruman 2009-10-04 #define SUN_SPARKS_RADIUS_FACTOR 2.0 #define KEY_TECHLEVEL @"techlevel" #define KEY_ECONOMY @"economy" #define KEY_ECONOMY_DESC @"economy_description" #define KEY_GOVERNMENT @"government" #define KEY_GOVERNMENT_DESC @"government_description" #define KEY_POPULATION @"population" #define KEY_POPULATION_DESC @"population_description" #define KEY_PRODUCTIVITY @"productivity" #define KEY_RADIUS @"radius" #define KEY_NAME @"name" #define KEY_INHABITANT @"inhabitant" #define KEY_INHABITANTS @"inhabitants" #define KEY_DESCRIPTION @"description" #define KEY_SHORT_DESCRIPTION @"short_description" #define KEY_PLANETNAME @"planet_name" #define KEY_SUNNAME @"sun_name" #define KEY_CHANCE @"chance" #define KEY_PRICE @"price" #define KEY_OPTIONAL_EQUIPMENT @"optional_equipment" #define KEY_STANDARD_EQUIPMENT @"standard_equipment" #define KEY_EQUIPMENT_MISSILES @"missiles" #define KEY_EQUIPMENT_FORWARD_WEAPON @"forward_weapon_type" #define KEY_EQUIPMENT_AFT_WEAPON @"aft_weapon_type" #define KEY_EQUIPMENT_PORT_WEAPON @"port_weapon_type" #define KEY_EQUIPMENT_STARBOARD_WEAPON @"starboard_weapon_type" #define KEY_EQUIPMENT_EXTRAS @"extras" #define KEY_WEAPON_FACINGS @"weapon_facings" #define KEY_RENOVATION_MULTIPLIER @"renovation_multiplier" #define SHIPYARD_KEY_ID @"id" #define SHIPYARD_KEY_SHIPDATA_KEY @"shipdata_key" #define SHIPYARD_KEY_SHIP @"ship" #define SHIPYARD_KEY_PRICE @"price" #define SHIPYARD_KEY_PERSONALITY @"personality" // default passenger berth required space #define PASSENGER_BERTH_SPACE 5 #define PLANETINFO_UNIVERSAL_KEY @"universal" #define PLANETINFO_INTERSTELLAR_KEY @"interstellar space" #define OOLITE_EXCEPTION_LOOPING @"OoliteLoopingException" #define OOLITE_EXCEPTION_DATA_NOT_FOUND @"OoliteDataNotFoundException" #define OOLITE_EXCEPTION_FATAL @"OoliteFatalException" // the distance the sky backdrop is from the camera // though it appears at infinity #define BILLBOARD_DEPTH 75000.0 #define TIME_ACCELERATION_FACTOR_MIN 0.0625f #define TIME_ACCELERATION_FACTOR_DEFAULT 1.0f #define TIME_ACCELERATION_FACTOR_MAX 16.0f #define DEMO_LIGHT_POSITION 5000.0f, 25000.0f, -10000.0f #define MIN_DISTANCE_TO_BUOY 750.0f // don't add ships within this distance #define MIN_DISTANCE_TO_BUOY2 (MIN_DISTANCE_TO_BUOY * MIN_DISTANCE_TO_BUOY) // if this is changed, also change oolite-populator.js // once this number has been in a stable release, cannot easily be changed #define SYSTEM_REPOPULATION_INTERVAL 20.0f; #ifndef OO_LOCALIZATION_TOOLS #define OO_LOCALIZATION_TOOLS 1 #endif #ifndef MASS_DEPENDENT_FUEL_PRICES #define MASS_DEPENDENT_FUEL_PRICES 1 #endif @interface Universe: OOWeakRefObject { @public // use a sorted list for drawing and other activities Entity *sortedEntities[UNIVERSE_MAX_ENTITIES + 1]; // One extra for padding; see -doRemoveEntity:. unsigned n_entities; int cursor_row; // collision optimisation sorted lists Entity *x_list_start, *y_list_start, *z_list_start; GLfloat stars_ambient[4]; @private NSUInteger _sessionID; // colors GLfloat sun_diffuse[4]; GLfloat sun_specular[4]; OOViewID viewDirection; OOMatrix viewMatrix; GLfloat airResistanceFactor; MyOpenGLView *gameView; int next_universal_id; Entity *entity_for_uid[MAX_ENTITY_UID]; NSMutableArray *entities; OOWeakReference *_firstBeacon, *_lastBeacon; NSMutableDictionary *waypoints; GLfloat skyClearColor[4]; NSString *currentMessage; OOTimeAbsolute messageRepeatTime; OOTimeAbsolute countdown_messageRepeatTime; // Getafix(4/Aug/2010) - Quickfix countdown messages colliding with weapon overheat messages. // For proper handling of message dispatching, code refactoring is needed. GuiDisplayGen *gui; GuiDisplayGen *message_gui; GuiDisplayGen *comm_log_gui; BOOL displayGUI; BOOL wasDisplayGUI; BOOL autoSaveNow; BOOL autoSave; BOOL wireframeGraphics; OOGraphicsDetail detailLevel; // Above entry replaces these two // BOOL reducedDetail; // OOShaderSetting shaderEffectsLevel; BOOL displayFPS; OOTimeAbsolute universal_time; OOTimeDelta time_delta; OOTimeAbsolute demo_stage_time; OOTimeAbsolute demo_start_time; GLfloat demo_start_z; int demo_stage; NSUInteger demo_ship_index; NSUInteger demo_ship_subindex; NSArray *demo_ships; GLfloat main_light_position[4]; BOOL dumpCollisionInfo; OOCommodities *commodities; OOCommodityMarket *commodityMarket; NSDictionary *_descriptions; // holds descriptive text for lots of stuff, loaded at initialisation NSDictionary *customSounds; // holds descriptive audio for lots of stuff, loaded at initialisation NSDictionary *characters; // holds descriptons of characters NSArray *_scenarios; // game start scenarios NSDictionary *globalSettings; // miscellaneous global game settings OOSystemDescriptionManager *systemManager; // planetinfo data manager NSDictionary *missiontext; // holds descriptive text for missions, loaded at initialisation NSArray *equipmentData; // holds data on available equipment, loaded at initialisation // NSSet *pirateVictimRoles; // Roles listed in pirateVictimRoles.plist. NSDictionary *roleCategories; // Categories for roles from role-categories.plist, extending the old pirate-victim-roles.plist NSDictionary *autoAIMap; // Default AIs for roles from autoAImap.plist. NSDictionary *screenBackgrounds; // holds filenames for various screens backgrounds, loaded at initialisation NSDictionary *explosionSettings; // explosion settings from explosions.plist NSDictionary *cargoPods; // template cargo pods OOGalaxyID galaxyID; OOSystemID systemID; OOSystemID targetSystemID; NSString *system_names[256]; // hold pregenerated universe info BOOL system_found[256]; // holds matches for input strings int breakPatternCounter; ShipEntity *demo_ship; StationEntity *cachedStation; OOPlanetEntity *cachedPlanet; OOSunEntity *cachedSun; NSMutableArray *allPlanets; NSMutableSet *allStations; float ambientLightLevel; NSMutableDictionary *populatorSettings; OOTimeDelta next_repopulation; NSString *system_repopulator; BOOL deterministic_population; NSArray *closeSystems; NSString *useAddOns; BOOL no_update; #ifndef NDEBUG double timeAccelerationFactor; #endif NSMutableArray *activeWormholes; NSMutableArray *characterPool; CollisionRegion *universeRegion; // check and maintain linked lists occasionally BOOL doLinkedListMaintenanceThisUpdate; NSMutableSet *entitiesDeadThisUpdate; int framesDoneThisUpdate; #if OOLITE_SPEECH_SYNTH #if OOLITE_MAC_OS_X NSSpeechSynthesizer *speechSynthesizer; #elif OOLITE_ESPEAK const espeak_VOICE **espeak_voices; unsigned int espeak_voice_count; #endif NSArray *speechArray; #endif #if NEW_PLANETS NSMutableArray *_preloadingPlanetMaterials; #endif BOOL doProcedurallyTexturedPlanets; GLfloat frustum[6][4]; NSMutableDictionary *conditionScripts; BOOL _pauseMessage; BOOL _autoCommLog; BOOL _permanentCommLog; BOOL _permanentMessageLog; BOOL _witchspaceBreakPattern; BOOL _dockingClearanceProtocolActive; BOOL _doingStartUp; } - (id)initWithGameView:(MyOpenGLView *)gameView; // SessionID: a value that's incremented when the game is reset. - (NSUInteger) sessionID; - (BOOL) doProcedurallyTexturedPlanets; - (void) setDoProcedurallyTexturedPlanets:(BOOL) value; - (NSString *) useAddOns; - (BOOL) setUseAddOns:(NSString *)newUse fromSaveGame: (BOOL)saveGame; - (BOOL) setUseAddOns:(NSString *) newUse fromSaveGame:(BOOL) saveGame forceReinit:(BOOL)force; - (void) setUpSettings; - (BOOL) reinitAndShowDemo:(BOOL)showDemo; - (BOOL) doingStartUp; // True during initial game startup (not reset). - (NSUInteger) entityCount; #ifndef NDEBUG - (void) debugDumpEntities; - (NSArray *) entityList; #endif - (void) pauseGame; - (void) carryPlayerOn:(StationEntity*)carrier inWormhole:(WormholeEntity*)wormhole; - (void) setUpUniverseFromStation; - (void) setUpUniverseFromWitchspace; - (void) setUpUniverseFromMisjump; - (void) setUpWitchspace; - (void) setUpWitchspaceBetweenSystem:(OOSystemID)s1 andSystem:(OOSystemID)s2; - (void) setUpSpace; - (void) populateNormalSpace; - (void) clearSystemPopulator; - (BOOL) deterministicPopulation; - (void) populateSystemFromDictionariesWithSun:(OOSunEntity *)sun andPlanet:(OOPlanetEntity *)planet; - (NSDictionary *) getPopulatorSettings; - (void) setPopulatorSetting:(NSString *)key to:(NSDictionary *)setting; - (HPVector) locationByCode:(NSString *)code withSun:(OOSunEntity *)sun andPlanet:(OOPlanetEntity *)planet; - (void) setAmbientLightLevel:(float)newValue; - (float) ambientLightLevel; - (void) setLighting; - (void) forceLightSwitch; - (void) setMainLightPosition: (Vector) sunPos; - (OOPlanetEntity *) setUpPlanet; - (void) makeSunSkimmer:(ShipEntity *) ship andSetAI:(BOOL)setAI; - (void) addShipWithRole:(NSString *) desc nearRouteOneAt:(double) route_fraction; - (HPVector) coordinatesForPosition:(HPVector) pos withCoordinateSystem:(NSString *) system returningScalar:(GLfloat*) my_scalar; - (NSString *) expressPosition:(HPVector) pos inCoordinateSystem:(NSString *) system; - (HPVector) legacyPositionFrom:(HPVector) pos asCoordinateSystem:(NSString *) system; - (HPVector) coordinatesFromCoordinateSystemString:(NSString *) system_x_y_z; - (BOOL) addShipWithRole:(NSString *) desc nearPosition:(HPVector) pos withCoordinateSystem:(NSString *) system; - (BOOL) addShips:(int) howMany withRole:(NSString *) desc atPosition:(HPVector) pos withCoordinateSystem:(NSString *) system; - (BOOL) addShips:(int) howMany withRole:(NSString *) desc nearPosition:(HPVector) pos withCoordinateSystem:(NSString *) system; - (BOOL) addShips:(int) howMany withRole:(NSString *) desc nearPosition:(HPVector) pos withCoordinateSystem:(NSString *) system withinRadius:(GLfloat) radius; - (BOOL) addShips:(int) howMany withRole:(NSString *) desc intoBoundingBox:(BoundingBox) bbox; - (BOOL) spawnShip:(NSString *) shipdesc; - (void) witchspaceShipWithPrimaryRole:(NSString *)role; - (ShipEntity *) spawnShipWithRole:(NSString *) desc near:(Entity *) entity; - (OOVisualEffectEntity *) addVisualEffectAt:(HPVector)pos withKey:(NSString *)key; - (ShipEntity *) addShipAt:(HPVector)pos withRole:(NSString *)role withinRadius:(GLfloat)radius; - (NSArray *) addShipsAt:(HPVector)pos withRole:(NSString *)role quantity:(unsigned)count withinRadius:(GLfloat)radius asGroup:(BOOL)isGroup; - (NSArray *) addShipsToRoute:(NSString *)route withRole:(NSString *)role quantity:(unsigned)count routeFraction:(double)routeFraction asGroup:(BOOL)isGroup; - (BOOL) roleIsPirateVictim:(NSString *)role; - (BOOL) role:(NSString *)role isInCategory:(NSString *)category; - (void) forceWitchspaceEntries; - (void) addWitchspaceJumpEffectForShip:(ShipEntity *)ship; - (GLfloat) safeWitchspaceExitDistance; - (void) setUpBreakPattern:(HPVector)pos orientation:(Quaternion)q forDocking:(BOOL)forDocking; - (BOOL) witchspaceBreakPattern; - (void) setWitchspaceBreakPattern:(BOOL)newValue; - (BOOL) dockingClearanceProtocolActive; - (void) setDockingClearanceProtocolActive:(BOOL)newValue; - (void) handleGameOver; - (void) setupIntroFirstGo:(BOOL)justCobra; - (void) selectIntro2Previous; - (void) selectIntro2Next; - (void) selectIntro2PreviousCategory; - (void) selectIntro2NextCategory; - (StationEntity *) station; - (OOPlanetEntity *) planet; - (OOSunEntity *) sun; - (NSArray *) planets; // Note: does not include sun. - (NSArray *) stations; // includes main station - (NSArray *) wormholes; - (StationEntity *) stationWithRole:(NSString *)role andPosition:(HPVector)position; // Turn main station into just another station, for blowUpStation. - (void) unMagicMainStation; // find a valid station in interstellar space - (StationEntity *) stationFriendlyTo:(ShipEntity *) ship; - (void) resetBeacons; - (Entity *) firstBeacon; - (Entity *) lastBeacon; - (void) setNextBeacon:(Entity *) beaconShip; - (void) clearBeacon:(Entity *) beaconShip; - (NSDictionary *) currentWaypoints; - (void) defineWaypoint:(NSDictionary *)definition forKey:(NSString *)key; - (GLfloat *) skyClearColor; // Note: the alpha value is also air resistance! - (void) setSkyColorRed:(GLfloat)red green:(GLfloat)green blue:(GLfloat)blue alpha:(GLfloat)alpha; - (BOOL) breakPatternOver; - (BOOL) breakPatternHide; - (NSString *) randomShipKeyForRoleRespectingConditions:(NSString *)role; - (ShipEntity *) newShipWithRole:(NSString *)role OO_RETURNS_RETAINED; // Selects ship using role weights, applies auto_ai, respects conditions - (ShipEntity *) newShipWithName:(NSString *)shipKey OO_RETURNS_RETAINED; // Does not apply auto_ai or respect conditions - (ShipEntity *) newSubentityWithName:(NSString *)shipKey andScaleFactor:(float)scale OO_RETURNS_RETAINED; // Does not apply auto_ai or respect conditions - (OOVisualEffectEntity *) newVisualEffectWithName:(NSString *)effectKey OO_RETURNS_RETAINED; - (DockEntity *) newDockWithName:(NSString *)shipKey andScaleFactor:(float)scale OO_RETURNS_RETAINED; // Does not apply auto_ai or respect conditions - (ShipEntity *) newShipWithName:(NSString *)shipKey usePlayerProxy:(BOOL)usePlayerProxy OO_RETURNS_RETAINED; // If usePlayerProxy, non-carriers are instantiated as ProxyPlayerEntity. - (ShipEntity *) newShipWithName:(NSString *)shipKey usePlayerProxy:(BOOL)usePlayerProxy isSubentity:(BOOL)isSubentity OO_RETURNS_RETAINED; - (ShipEntity *) newShipWithName:(NSString *)shipKey usePlayerProxy:(BOOL)usePlayerProxy isSubentity:(BOOL)isSubentity andScaleFactor:(float)scale OO_RETURNS_RETAINED; - (Class) shipClassForShipDictionary:(NSDictionary *)dict; - (NSString *)defaultAIForRole:(NSString *)role; // autoAImap.plist lookup - (OOCargoQuantity) maxCargoForShip:(NSString *) desc; - (OOCreditsQuantity) getEquipmentPriceForKey:(NSString *) eq_key; - (OOCommodities *) commodities; - (ShipEntity *) reifyCargoPod:(ShipEntity *)cargoObj; - (ShipEntity *) cargoPodFromTemplate:(ShipEntity *)cargoObj; - (NSArray *) getContainersOfGoods:(OOCargoQuantity)how_many scarce:(BOOL)scarce legal:(BOOL)legal; - (NSArray *) getContainersOfCommodity:(OOCommodityType) commodity_name :(OOCargoQuantity) how_many; - (void) fillCargopodWithRandomCargo:(ShipEntity *)cargopod; - (NSString *) getRandomCommodity; - (OOCargoQuantity) getRandomAmountOfCommodity:(OOCommodityType) co_type; - (NSDictionary *) commodityDataForType:(OOCommodityType)type; - (NSString *) displayNameForCommodity:(OOCommodityType)co_type; - (NSString *) describeCommodity:(OOCommodityType)co_type amount:(OOCargoQuantity) co_amount; - (void) setGameView:(MyOpenGLView *)view; - (MyOpenGLView *) gameView; - (GameController *) gameController; - (NSDictionary *) gameSettings; - (void) useGUILightSource:(BOOL)GUILight; - (void) drawUniverse; - (void) defineFrustum; - (BOOL) viewFrustumIntersectsSphereAt:(Vector)position withRadius:(GLfloat)radius; - (void) drawMessage; - (void) drawWatermarkString:(NSString *)watermarkString; // Used to draw subentities. Should be getting this from camera. - (OOMatrix) viewMatrix; - (id) entityForUniversalID:(OOUniversalID)u_id; - (BOOL) addEntity:(Entity *) entity; - (BOOL) removeEntity:(Entity *) entity; - (void) ensureEntityReallyRemoved:(Entity *)entity; - (void) removeAllEntitiesExceptPlayer; - (void) removeDemoShips; - (ShipEntity *) makeDemoShipWithRole:(NSString *)role spinning:(BOOL)spinning; - (BOOL) isVectorClearFromEntity:(Entity *) e1 toDistance:(double)dist fromPoint:(HPVector) p2; - (Entity*) hazardOnRouteFromEntity:(Entity *) e1 toDistance:(double)dist fromPoint:(HPVector) p2; - (HPVector) getSafeVectorFromEntity:(Entity *) e1 toDistance:(double)dist fromPoint:(HPVector) p2; - (ShipEntity *) addWreckageFrom:(ShipEntity *)ship withRole:(NSString *)wreckRole at:(HPVector)rpos scale:(GLfloat)scale lifetime:(GLfloat)lifetime; - (void) addLaserHitEffectsAt:(HPVector)pos against:(ShipEntity *)target damage:(float)damage color:(OOColor *)color; - (ShipEntity *) firstShipHitByLaserFromShip:(ShipEntity *)srcEntity inDirection:(OOWeaponFacing)direction offset:(Vector)offset gettingRangeFound:(GLfloat*)range_ptr; - (Entity *) firstEntityTargetedByPlayer; - (Entity *) firstEntityTargetedByPlayerPrecisely; - (NSArray *) entitiesWithinRange:(double)range ofEntity:(Entity *)entity; - (unsigned) countShipsWithRole:(NSString *)role inRange:(double)range ofEntity:(Entity *)entity; - (unsigned) countShipsWithRole:(NSString *)role; - (unsigned) countShipsWithPrimaryRole:(NSString *)role inRange:(double)range ofEntity:(Entity *)entity; - (unsigned) countShipsWithPrimaryRole:(NSString *)role; - (unsigned) countShipsWithScanClass:(OOScanClass)scanClass inRange:(double)range ofEntity:(Entity *)entity; - (void) sendShipsWithPrimaryRole:(NSString *)role messageToAI:(NSString *)message; // General count/search methods. Pass range of -1 and entity of nil to search all of system. - (unsigned) countEntitiesMatchingPredicate:(EntityFilterPredicate)predicate parameter:(void *)parameter inRange:(double)range ofEntity:(Entity *)entity; - (unsigned) countShipsMatchingPredicate:(EntityFilterPredicate)predicate parameter:(void *)parameter inRange:(double)range ofEntity:(Entity *)entity; - (NSMutableArray *) findEntitiesMatchingPredicate:(EntityFilterPredicate)predicate parameter:(void *)parameter inRange:(double)range ofEntity:(Entity *)entity; - (id) findOneEntityMatchingPredicate:(EntityFilterPredicate)predicate parameter:(void *)parameter; - (NSMutableArray *) findShipsMatchingPredicate:(EntityFilterPredicate)predicate parameter:(void *)parameter inRange:(double)range ofEntity:(Entity *)entity; - (NSMutableArray *) findVisualEffectsMatchingPredicate:(EntityFilterPredicate)predicate parameter:(void *)parameter inRange:(double)range ofEntity:(Entity *)entity; - (id) nearestEntityMatchingPredicate:(EntityFilterPredicate)predicate parameter:(void *)parameter relativeToEntity:(Entity *)entity; - (id) nearestShipMatchingPredicate:(EntityFilterPredicate)predicate parameter:(void *)parameter relativeToEntity:(Entity *)entity; - (OOTimeAbsolute) getTime; - (OOTimeDelta) getTimeDelta; - (void) findCollisionsAndShadows; - (NSString*) collisionDescription; - (void) dumpCollisions; - (OOViewID) viewDirection; - (void) setViewDirection:(OOViewID)vd; - (void) enterGUIViewModeWithMouseInteraction:(BOOL)mouseInteraction; // Use instead of setViewDirection:VIEW_GUI_DISPLAY - (NSString *) soundNameForCustomSoundKey:(NSString *)key; - (NSDictionary *) screenTextureDescriptorForKey:(NSString *)key; - (void) clearPreviousMessage; - (void) setMessageGuiBackgroundColor:(OOColor *) some_color; - (void) displayMessage:(NSString *) text forCount:(OOTimeDelta) count; - (void) displayCountdownMessage:(NSString *) text forCount:(OOTimeDelta) count; - (void) addDelayedMessage:(NSString *) text forCount:(OOTimeDelta) count afterDelay:(OOTimeDelta) delay; - (void) addDelayedMessage:(NSDictionary *) textdict; - (void) addMessage:(NSString *) text forCount:(OOTimeDelta) count; - (void) addMessage:(NSString *) text forCount:(OOTimeDelta) count forceDisplay:(BOOL) forceDisplay; - (void) addCommsMessage:(NSString *) text forCount:(OOTimeDelta) count; - (void) addCommsMessage:(NSString *) text forCount:(OOTimeDelta) count andShowComms:(BOOL)showComms logOnly:(BOOL)logOnly; - (void) showCommsLog:(OOTimeDelta) how_long; - (void) update:(OOTimeDelta)delta_t; // Time Acelleration Factor. In deployment builds, this is always 1.0 and -setTimeAccelerationFactor: does nothing. - (double) timeAccelerationFactor; - (void) setTimeAccelerationFactor:(double)newTimeAccelerationFactor; - (void) filterSortedLists; /////////////////////////////////////// - (void) setGalaxyTo:(OOGalaxyID) g; - (void) setGalaxyTo:(OOGalaxyID) g andReinit:(BOOL) forced; - (void) setSystemTo:(OOSystemID) s; - (OOSystemID) currentSystemID; - (NSDictionary *) descriptions; - (NSDictionary *) characters; - (NSDictionary *) missiontext; - (NSArray *) scenarios; - (NSDictionary *) explosionSetting:(NSString *)explosion; - (OOSystemDescriptionManager *) systemManager; - (NSString *)descriptionForKey:(NSString *)key; // String, or random item from array - (NSString *)descriptionForArrayKey:(NSString *)key index:(unsigned)index; // Indexed item from array - (BOOL) descriptionBooleanForKey:(NSString *)key; // Boolean from descriptions.plist, for configuration. - (NSString *) keyForPlanetOverridesForSystem:(OOSystemID) s inGalaxy:(OOGalaxyID) g; - (NSString *) keyForInterstellarOverridesForSystems:(OOSystemID) s1 :(OOSystemID) s2 inGalaxy:(OOGalaxyID) g; - (NSDictionary *) generateSystemData:(OOSystemID) s; - (NSDictionary *) generateSystemData:(OOSystemID) s useCache:(BOOL) useCache; - (NSDictionary *) currentSystemData; // Same as generateSystemData:systemSeed unless in interstellar space. - (BOOL) inInterstellarSpace; - (void) setSystemDataKey:(NSString*) key value:(NSObject*) object fromManifest:(NSString *)manifest; - (void) setSystemDataForGalaxy:(OOGalaxyID) gnum planet:(OOSystemID) pnum key:(NSString *)key value:(id)object fromManifest:(NSString *)manifest forLayer:(OOSystemLayer)layer; - (id) systemDataForGalaxy:(OOGalaxyID) gnum planet:(OOSystemID) pnum key:(NSString *)key; - (NSArray *) systemDataKeysForGalaxy:(OOGalaxyID)gnum planet:(OOSystemID)pnum; - (NSString *) getSystemName:(OOSystemID) sys; - (OOGovernmentID) getSystemGovernment:(OOSystemID) sys; - (NSString *) getSystemInhabitants:(OOSystemID) sys; - (NSString *) getSystemInhabitants:(OOSystemID) sys plural:(BOOL)plural; - (NSPoint) coordinatesForSystem:(OOSystemID)s; - (OOSystemID) findSystemAtCoords:(NSPoint) coords withGalaxy:(OOGalaxyID) gal; - (OOSystemID) findSystemFromName:(NSString *) sysName; /** * Finds systems within range. If range is greater than 7.0LY then only look within 7.0LY. */ - (NSMutableArray *) nearbyDestinationsWithinRange:(double) range; - (OOSystemID) findNeighbouringSystemToCoords:(NSPoint) coords withGalaxy:(OOGalaxyID) gal; - (OOSystemID) findConnectedSystemAtCoords:(NSPoint) coords withGalaxy:(OOGalaxyID) gal; - (OOSystemID) findSystemNumberAtCoords:(NSPoint) coords withGalaxy:(OOGalaxyID) gal; - (NSPoint) findSystemCoordinatesWithPrefix:(NSString *) p_fix; - (NSPoint) findSystemCoordinatesWithPrefix:(NSString *) p_fix exactMatch:(BOOL) exactMatch; - (BOOL*) systemsFound; - (NSString*) systemNameIndex:(OOSystemID) index; - (NSDictionary *) routeFromSystem:(OOSystemID) start toSystem:(OOSystemID) goal optimizedBy:(OORouteType) optimizeBy; - (NSArray *) neighboursToSystem:(OOSystemID) system_number; - (void) preloadPlanetTexturesForSystem:(OOSystemID)system; - (void) preloadSounds; - (NSDictionary *) globalSettings; - (NSArray *) equipmentData; - (OOCommodityMarket *) commodityMarket; - (NSString *) timeDescription:(OOTimeDelta) interval; - (NSString *) shortTimeDescription:(OOTimeDelta) interval; - (void) loadStationMarkets:(NSArray *)marketData; - (NSArray *) getStationMarkets; - (NSArray *) shipsForSaleForSystem:(OOSystemID) s withTL:(OOTechLevelID) specialTL atTime:(OOTimeAbsolute) current_time; /* Calculate base cost, before depreciation */ - (OOCreditsQuantity) tradeInValueForCommanderDictionary:(NSDictionary*) cmdr_dict; - (NSString*) brochureDescriptionWithDictionary:(NSDictionary*) dict standardEquipment:(NSArray*) extras optionalEquipment:(NSArray*) options; - (HPVector) getWitchspaceExitPosition; - (Quaternion) getWitchspaceExitRotation; - (HPVector) getSunSkimStartPositionForShip:(ShipEntity*) ship; - (HPVector) getSunSkimEndPositionForShip:(ShipEntity*) ship; - (NSArray*) listBeaconsWithCode:(NSString*) code; - (void) allShipsDoScriptEvent:(jsid)event andReactToAIMessage:(NSString *)message; /////////////////////////////////////// - (void) clearGUIs; - (GuiDisplayGen *) gui; - (GuiDisplayGen *) commLogGUI; - (GuiDisplayGen *) messageGUI; - (void) resetCommsLogColor; - (void) setDisplayText:(BOOL) value; - (BOOL) displayGUI; - (void) setDisplayFPS:(BOOL) value; - (BOOL) displayFPS; - (void) setAutoSave:(BOOL) value; - (BOOL) autoSave; - (void) setWireframeGraphics:(BOOL) value; - (BOOL) wireframeGraphics; - (BOOL) reducedDetail; - (void) setDetailLevel:(OOGraphicsDetail)value; - (OOGraphicsDetail) detailLevel; - (BOOL) useShaders; - (void) handleOoliteException:(NSException *)ooliteException; - (GLfloat)airResistanceFactor; // speech routines // - (void) startSpeakingString:(NSString *) text; // - (void) stopSpeaking; // - (BOOL) isSpeaking; // #if OOLITE_ESPEAK - (NSString *) voiceName:(unsigned int) index; - (unsigned int) voiceNumber:(NSString *) name; - (unsigned int) nextVoice:(unsigned int) index; - (unsigned int) prevVoice:(unsigned int) index; - (unsigned int) setVoice:(unsigned int) index withGenderM:(BOOL) isMale; #endif // //// //autosave - (void) setAutoSaveNow:(BOOL) value; - (BOOL) autoSaveNow; - (int) framesDoneThisUpdate; - (void) resetFramesDoneThisUpdate; // True if textual pause message (as opposed to overlay) is being shown. - (BOOL) pauseMessageVisible; - (void) setPauseMessageVisible:(BOOL)value; - (BOOL) permanentCommLog; - (void) setPermanentCommLog:(BOOL)value; - (void) setAutoCommLog:(BOOL)value; - (BOOL) permanentMessageLog; - (void) setPermanentMessageLog:(BOOL)value; - (BOOL) blockJSPlayerShipProps; - (void) setBlockJSPlayerShipProps:(BOOL)value; - (void) loadConditionScripts; - (void) addConditionScripts:(NSEnumerator *)scripts; - (OOJSScript *) getConditionScript:(NSString *)scriptname; @end /* Use UNIVERSE to refer to the global universe object. The purpose of this is that it makes UNIVERSE essentially a read-only global with zero overhead. */ OOINLINE Universe *OOGetUniverse(void) INLINE_CONST_FUNC; OOINLINE Universe *OOGetUniverse(void) { extern Universe *gSharedUniverse; return gSharedUniverse; } #define UNIVERSE OOGetUniverse() // Only for use with string literals, and only for looking up strings. // DESC() is deprecated in favour of OOExpandKey() except in known performance- // critical contexts. #define DESC(key) (OOLookUpDescriptionPRIV(key "")) #define DESC_PLURAL(key,count) (OOLookUpPluralDescriptionPRIV(key "", count)) // Not for direct use. NSComparisonResult populatorPrioritySort(id a, id b, void *context); NSComparisonResult equipmentSort(id a, id b, void *context); NSString *OOLookUpDescriptionPRIV(NSString *key); NSString *OOLookUpPluralDescriptionPRIV(NSString *key, NSInteger count); @interface OOSound (OOCustomSounds) + (id) soundWithCustomSoundKey:(NSString *)key; - (id) initWithCustomSoundKey:(NSString *)key; @end @interface OOSoundSource (OOCustomSounds) + (id) sourceWithCustomSoundKey:(NSString *)key; - (id) initWithCustomSoundKey:(NSString *)key; - (void) playCustomSoundWithKey:(NSString *)key; @end NSString *OODisplayStringFromGovernmentID(OOGovernmentID government); NSString *OODisplayStringFromEconomyID(OOEconomyID economy); oolite-1.82/src/Core/Universe.m000066400000000000000000011201211256642440500164020ustar00rootroot00000000000000/* Universe.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOOpenGL.h" #import "Universe.h" #import "MyOpenGLView.h" #import "GameController.h" #import "ResourceManager.h" #import "AI.h" #import "GuiDisplayGen.h" #import "HeadUpDisplay.h" #import "OOSound.h" #import "OOColor.h" #import "OOCacheManager.h" #import "OOStringExpander.h" #import "OOStringParsing.h" #import "OOCollectionExtractors.h" #import "OOConstToString.h" #import "OOOpenGLExtensionManager.h" #import "OOOpenGLMatrixManager.h" #import "OOCPUInfo.h" #import "OOMaterial.h" #import "OOTexture.h" #import "OORoleSet.h" #import "OOShipGroup.h" #import "OODebugSupport.h" #import "Octree.h" #import "CollisionRegion.h" #import "OOGraphicsResetManager.h" #import "OODebugSupport.h" #import "OOEntityFilterPredicate.h" #import "OOCharacter.h" #import "OOShipRegistry.h" #import "OOProbabilitySet.h" #import "OOEquipmentType.h" #import "OOShipLibraryDescriptions.h" #import "PlayerEntity.h" #import "PlayerEntityContracts.h" #import "PlayerEntityScriptMethods.h" #import "StationEntity.h" #import "DockEntity.h" #import "SkyEntity.h" #import "DustEntity.h" #import "OOPlanetEntity.h" #import "OOVisualEffectEntity.h" #import "OOWaypointEntity.h" #import "OOSunEntity.h" #import "WormholeEntity.h" #import "OOBreakPatternEntity.h" #import "ShipEntityAI.h" #import "ProxyPlayerEntity.h" #import "OORingEffectEntity.h" #import "OOLightParticleEntity.h" #import "OOFlashEffectEntity.h" #import "OOExplosionCloudEntity.h" #import "OOSystemDescriptionManager.h" #import "OOMusicController.h" #import "OOAsyncWorkManager.h" #import "OODebugFlags.h" #import "OODebugStandards.h" #import "OOLoggingExtended.h" #import "OOJSEngineTimeManagement.h" #import "OOJoystickManager.h" #import "OOScriptTimer.h" #import "OOJSScript.h" #import "OOJSFrameCallbacks.h" #import "OOJSPopulatorDefinition.h" #if OO_LOCALIZATION_TOOLS #import "OOConvertSystemDescriptions.h" #endif #if OOLITE_ESPEAK #include #endif enum { DEMO_FLY_IN = 101, DEMO_SHOW_THING, DEMO_FLY_OUT }; #define DEMO2_VANISHING_DISTANCE 650.0 #define DEMO2_FLY_IN_STAGE_TIME 0.4 #define MAX_NUMBER_OF_ENTITIES 200 #define STANDARD_STATION_ROLL 0.4 // currently twice scanner radius #define LANE_WIDTH 51200.0 static NSString * const kOOLogUniversePopulateError = @"universe.populate.error"; static NSString * const kOOLogUniversePopulateWitchspace = @"universe.populate.witchspace"; static NSString * const kOOLogEntityVerificationError = @"entity.linkedList.verify.error"; static NSString * const kOOLogEntityVerificationRebuild = @"entity.linkedList.verify.rebuild"; Universe *gSharedUniverse = nil; extern Entity *gOOJSPlayerIfStale; Entity *gOOJSPlayerIfStale = nil; static BOOL MaintainLinkedLists(Universe* uni); OOINLINE BOOL EntityInRange(HPVector p1, Entity *e2, float range); static OOComparisonResult compareName(id dict1, id dict2, void * context); static OOComparisonResult comparePrice(id dict1, id dict2, void * context); /* TODO: route calculation is really slow - find a way to safely enable this */ #undef CACHE_ROUTE_FROM_SYSTEM_RESULTS @interface RouteElement: NSObject { @private OOSystemID _location, _parent; double _cost, _distance, _time; } + (instancetype) elementWithLocation:(OOSystemID) location parent:(OOSystemID)parent cost:(double) cost distance:(double) distance time:(double) time; - (OOSystemID) parent; - (OOSystemID) location; - (double) cost; - (double) distance; - (double) time; @end @implementation RouteElement + (instancetype) elementWithLocation:(OOSystemID) location parent:(OOSystemID) parent cost:(double) cost distance:(double) distance time:(double) time { RouteElement *r = [[RouteElement alloc] init]; r->_location = location; r->_parent = parent; r->_cost = cost; r->_distance = distance; r->_time = time; return [r autorelease]; } - (OOSystemID) parent { return _parent; } - (OOSystemID) location { return _location; } - (double) cost { return _cost; } - (double) distance { return _distance; } - (double) time { return _time; } @end @interface Universe (OOPrivate) - (BOOL) doRemoveEntity:(Entity *)entity; - (void) setUpCargoPods; - (void) setUpInitialUniverse; - (HPVector) fractionalPositionFrom:(HPVector)point0 to:(HPVector)point1 withFraction:(double)routeFraction; - (void) populateSpaceFromActiveWormholes; - (NSString *)chooseStringForKey:(NSString *)key inDictionary:(NSDictionary *)dictionary; #if OO_LOCALIZATION_TOOLS #if DEBUG_GRAPHVIZ - (void) dumpDebugGraphViz; - (void) dumpSystemDescriptionGraphViz; #endif - (void) addNumericRefsInString:(NSString *)string toGraphViz:(NSMutableString *)graphViz fromNode:(NSString *)fromNode nodeCount:(NSUInteger)nodeCount; - (void) runLocalizationTools; #endif #if NEW_PLANETS - (void) prunePreloadingPlanetMaterials; #endif // Set shader effects level without logging or triggering a reset -- should only be used directly during startup. - (void) setShaderEffectsLevelDirectly:(OOShaderSetting)value; - (void) setFirstBeacon:(Entity *)beacon; - (void) setLastBeacon:(Entity *)beacon; - (void) verifyDescriptions; - (void) loadDescriptions; - (void) loadScenarios; - (void) verifyEntitySessionIDs; - (float) randomDistanceWithinScanner; - (Vector) randomPlaceWithinScannerFrom:(Vector)pos alongRoute:(Vector)route withOffset:(double)offset; - (void) setDetailLevelDirectly:(OOGraphicsDetail)value; - (NSDictionary *)demoShipData; - (void) setLibraryTextForDemoShip; @end @implementation Universe // Flags needed when JS reset fails. static int JSResetFlags = 0; // track the position and status of the lights static BOOL object_light_on = NO; static BOOL demo_light_on = NO; static GLfloat sun_off[4] = {0.0, 0.0, 0.0, 1.0}; static GLfloat demo_light_position[4] = { DEMO_LIGHT_POSITION, 1.0 }; #define DOCKED_AMBIENT_LEVEL 0.2f // Was 0.05, 'temporarily' set to 0.2. #define DOCKED_ILLUM_LEVEL 0.7f static GLfloat docked_light_ambient[4] = { DOCKED_AMBIENT_LEVEL, DOCKED_AMBIENT_LEVEL, DOCKED_AMBIENT_LEVEL, 1.0f }; static GLfloat docked_light_diffuse[4] = { DOCKED_ILLUM_LEVEL, DOCKED_ILLUM_LEVEL, DOCKED_ILLUM_LEVEL, 1.0f }; // white static GLfloat docked_light_specular[4] = { DOCKED_ILLUM_LEVEL, DOCKED_ILLUM_LEVEL, DOCKED_ILLUM_LEVEL * 0.75f, (GLfloat) 1.0f }; // yellow-white // 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. // TODO: considering the size of the sun and the number of background stars might be worthwhile. -- Ahruman 20080322 #define SUN_AMBIENT_INFLUENCE 0.75 - (id) initWithGameView:(MyOpenGLView *)inGameView { OOProfilerStartMarker(@"startup"); if (gSharedUniverse != nil) { [self release]; [NSException raise:NSInternalInconsistencyException format:@"%s: expected only one Universe to exist at a time.", __PRETTY_FUNCTION__]; } OO_DEBUG_PROGRESS(@"Universe initWithGameView:"); self = [super init]; if (self == nil) return nil; _doingStartUp = YES; OOInitReallyRandom([NSDate timeIntervalSinceReferenceDate] * 1e9); NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; // prefs value no longer used - per save game but startup needs to // be non-strict useAddOns = [[NSString alloc] initWithString:SCENARIO_OXP_DEFINITION_ALL]; [self setGameView:inGameView]; gSharedUniverse = self; allPlanets = [[NSMutableArray alloc] init]; allStations = [[NSMutableSet alloc] init]; OOCPUInfoInit(); [OOJoystickManager sharedStickHandler]; // init OpenGL extension manager (must be done before any other threads might use it) [OOOpenGLExtensionManager sharedManager]; [self setDetailLevelDirectly:[prefs oo_intForKey:@"detailLevel" defaultValue:[[OOOpenGLExtensionManager sharedManager] defaultDetailLevel]]]; [OOMaterial setUp]; // Preload cache [OOCacheManager sharedCache]; #if OOLITE_SPEECH_SYNTH OOLog(@"speech.synthesis", @"Spoken messages are %@.", ([prefs oo_boolForKey:@"speech_on" defaultValue:NO] ? @"on" :@"off")); #endif // init the Resource Manager [ResourceManager setUseAddOns:useAddOns]; // also logs the paths if changed // Set up the internal game strings [self loadDescriptions]; // DESC expansion is now possible! // load starting saves [self loadScenarios]; autoSave = [prefs oo_boolForKey:@"autosave" defaultValue:NO]; wireframeGraphics = [prefs oo_boolForKey:@"wireframe-graphics" defaultValue:NO]; doProcedurallyTexturedPlanets = [prefs oo_boolForKey:@"procedurally-textured-planets" defaultValue:YES]; [inGameView setGammaValue:[prefs oo_floatForKey:@"gamma-value" defaultValue:1.0f]]; [inGameView setFov:OOClamp_0_max_f([prefs oo_floatForKey:@"fov-value" defaultValue:57.2f], MAX_FOV_DEG) fromFraction:NO]; if ([inGameView fov:NO] < MIN_FOV_DEG) [inGameView setFov:MIN_FOV_DEG fromFraction:NO]; // Set up speech synthesizer. #if OOLITE_SPEECH_SYNTH #if OOLITE_MAC_OS_X dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ /* NSSpeechSynthesizer can take over a second on an SSD and several seconds on an HDD for a cold start, and a third of a second upward for a warm start. There are no particular thread safety consider- ations documented for NSSpeechSynthesizer, so I'm assuming the default one-thread-at-a-time access rule applies. -- Ahruman 2012-09-13 */ OOLog(@"speech.setup.begin", @"Starting to set up speech synthesizer."); NSSpeechSynthesizer *synth = [[NSSpeechSynthesizer alloc] init]; OOLog(@"speech.setup.end", @"Finished setting up speech synthesizer."); speechSynthesizer = synth; }); #elif OOLITE_ESPEAK int volume = [OOSound masterVolume] * 100; espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 100, NULL, 0); espeak_SetParameter(espeakPUNCTUATION, espeakPUNCT_NONE, 0); espeak_SetParameter(espeakVOLUME, volume, 0); espeak_voices = espeak_ListVoices(NULL); for (espeak_voice_count = 0; espeak_voices[espeak_voice_count]; ++espeak_voice_count) /**/; #endif #endif [[GameController sharedController] logProgress:DESC(@"loading-ships")]; // Load ship data [OOShipRegistry sharedRegistry]; entities = [[NSMutableArray arrayWithCapacity:MAX_NUMBER_OF_ENTITIES] retain]; [[GameController sharedController] logProgress:OOExpandKeyRandomized(@"loading-miscellany")]; // this MUST have the default no. of rows else the GUI_ROW macros in PlayerEntity.h need modification gui = [[GuiDisplayGen alloc] init]; // alloc retains comm_log_gui = [[GuiDisplayGen alloc] init]; // alloc retains missiontext = [[ResourceManager dictionaryFromFilesNamed:@"missiontext.plist" inFolder:@"Config" andMerge:YES] retain]; waypoints = [[NSMutableDictionary alloc] init]; [self setUpSettings]; // can't do this here as it might lock an OXZ open // [self preloadSounds]; // Must be after setUpSettings. // Preload particle effect textures: [OOLightParticleEntity setUpTexture]; [OOFlashEffectEntity setUpTexture]; // set up cargopod templates [self setUpCargoPods]; PlayerEntity *player = [PlayerEntity sharedPlayer]; [player deferredInit]; [self addEntity:player]; [player setStatus:STATUS_START_GAME]; [player setShowDemoShips: YES]; [self setUpInitialUniverse]; universeRegion = [[CollisionRegion alloc] initAsUniverse]; entitiesDeadThisUpdate = [[NSMutableSet alloc] init]; framesDoneThisUpdate = 0; [[GameController sharedController] logProgress:DESC(@"initializing-debug-support")]; OOInitDebugSupport(); [[GameController sharedController] logProgress:DESC(@"running-scripts")]; [player completeSetUp]; [[GameController sharedController] logProgress:DESC(@"populating-space")]; [self populateNormalSpace]; [[GameController sharedController] logProgress:OOExpandKeyRandomized(@"loading-miscellany")]; #if OO_LOCALIZATION_TOOLS [self runLocalizationTools]; #if DEBUG_GRAPHVIZ [self dumpDebugGraphViz]; #endif #endif [player startUpComplete]; _doingStartUp = NO; OOProfilerEndMarker(@"startup"); return self; } - (void) dealloc { gSharedUniverse = nil; [currentMessage release]; [gui release]; [message_gui release]; [comm_log_gui release]; [entities release]; [commodities release]; [_descriptions release]; [characters release]; [customSounds release]; [globalSettings release]; [systemManager release]; [missiontext release]; [equipmentData release]; [demo_ships release]; [autoAIMap release]; [screenBackgrounds release]; [gameView release]; [populatorSettings release]; [system_repopulator release]; [allPlanets release]; [allStations release]; [explosionSettings release]; [activeWormholes release]; [characterPool release]; [universeRegion release]; [cargoPods release]; DESTROY(_firstBeacon); DESTROY(_lastBeacon); DESTROY(waypoints); unsigned i; for (i = 0; i < 256; i++) [system_names[i] release]; [entitiesDeadThisUpdate release]; [[OOCacheManager sharedCache] flush]; #if OOLITE_SPEECH_SYNTH [speechArray release]; #if OOLITE_MAC_OS_X [speechSynthesizer release]; #elif OOLITE_ESPEAK espeak_Cancel(); #endif #endif [conditionScripts release]; [super dealloc]; } - (NSUInteger) sessionID { return _sessionID; } - (BOOL) doingStartUp { return _doingStartUp; } - (BOOL) doProcedurallyTexturedPlanets { return doProcedurallyTexturedPlanets; } - (void) setDoProcedurallyTexturedPlanets:(BOOL) value { doProcedurallyTexturedPlanets = !!value; // ensure yes or no [[NSUserDefaults standardUserDefaults] setBool:doProcedurallyTexturedPlanets forKey:@"procedurally-textured-planets"]; } - (NSString *) useAddOns { return useAddOns; } - (BOOL) setUseAddOns:(NSString *) newUse fromSaveGame:(BOOL) saveGame { return [self setUseAddOns:newUse fromSaveGame:saveGame forceReinit:NO]; } - (BOOL) setUseAddOns:(NSString *) newUse fromSaveGame:(BOOL) saveGame forceReinit:(BOOL)force { if (!force && [newUse isEqualToString:useAddOns]) { return YES; } DESTROY(useAddOns); useAddOns = [newUse retain]; return [self reinitAndShowDemo:!saveGame]; } - (NSUInteger) entityCount { return [entities count]; } #ifndef NDEBUG - (void) debugDumpEntities { int i; int show_count = n_entities; if (!OOLogWillDisplayMessagesInClass(@"universe.objectDump")) return; OOLog(@"universe.objectDump", @"DEBUG: Entity Dump - [entities count] = %lu,\tn_entities = %u", [entities count], n_entities); OOLogIndent(); for (i = 0; i < show_count; i++) { OOLog(@"universe.objectDump", @"Ent:%4u %@", i, [sortedEntities[i] descriptionForObjDump]); } OOLogOutdent(); if ([entities count] != n_entities) { OOLog(@"universe.objectDump", @"entities = %@", [entities description]); } } - (NSArray *) entityList { return [NSArray arrayWithArray:entities]; } #endif - (void) pauseGame { // deal with the machine going to sleep, or player pressing 'p'. PlayerEntity *player = PLAYER; [self setPauseMessageVisible:NO]; NSString *pauseKey = [PLAYER keyBindingDescription:@"key_pausebutton"]; if ([player status] == STATUS_DOCKED) { if ([gui setForegroundTextureKey:@"paused_docked_overlay"]) { [gui drawGUI:1.0 drawCursor:NO]; } else { [self setPauseMessageVisible:YES]; [self addMessage:OOExpandKey(@"game-paused-docked", pauseKey) forCount:1.0]; } } else { if ([player guiScreen] != GUI_SCREEN_MAIN && [gui setForegroundTextureKey:@"paused_overlay"]) { [gui drawGUI:1.0 drawCursor:NO]; } else { [self setPauseMessageVisible:YES]; [self addMessage:OOExpandKey(@"game-paused", pauseKey) forCount:1.0]; } } [[self gameController] setGamePaused:YES]; } - (void) carryPlayerOn:(StationEntity*)carrier inWormhole:(WormholeEntity*)wormhole { PlayerEntity *player = PLAYER; OOSystemID dest = [wormhole destination]; [player setWormhole:wormhole]; [player addScannedWormhole:wormhole]; ShipScriptEventNoCx(player, "shipWillEnterWitchspace", OOJSSTR("carried")); [self allShipsDoScriptEvent:OOJSID("playerWillEnterWitchspace") andReactToAIMessage:@"PLAYER WITCHSPACE"]; [player setRandom_factor:(ranrot_rand() & 255)]; // random factor for market values is reset // misjump on wormhole sets correct travel time if needed [player addToAdjustTime:[wormhole travelTime]]; // clear old entities [self removeAllEntitiesExceptPlayer]; // should we add wear-and-tear to the player ship if they're not doing // the jump themselves? Left out for now. - CIM if (![wormhole withMisjump]) { [player setSystemID:dest]; [self setSystemTo: dest]; [self setUpSpace]; [self populateNormalSpace]; [player setBounty:([player legalStatus]/2) withReason:kOOLegalStatusReasonNewSystem]; if ([player random_factor] < 8) [player erodeReputation]; // every 32 systems or so, dro } else { [player setGalaxyCoordinates:[wormhole destinationCoordinates]]; [self setUpWitchspaceBetweenSystem:[wormhole origin] andSystem:[wormhole destination]]; if (randf() < 0.1) [player erodeReputation]; // once every 10 misjumps - should be much rarer than successful jumps! } // which will kick the ship out of the wormhole with the // player still aboard [wormhole disgorgeShips]; //reset atmospherics in case carrier was in atmosphere [UNIVERSE setSkyColorRed:0.0f // back to black green:0.0f blue:0.0f alpha:0.0f]; [self setWitchspaceBreakPattern:YES]; [player doScriptEvent:OOJSID("shipWillExitWitchspace")]; [player doScriptEvent:OOJSID("shipExitedWitchspace")]; [player setWormhole:nil]; } - (void) setUpUniverseFromStation { if (![self sun]) { // we're in witchspace... PlayerEntity *player = PLAYER; StationEntity *dockedStation = [player dockedStation]; NSPoint coords = [player galaxy_coordinates]; // check the nearest system OOSystemID sys = [self findSystemAtCoords:coords withGalaxy:[player galaxyNumber]]; BOOL interstel =[dockedStation interstellarUndockingAllowed];// && (s_seed.d != coords.x || s_seed.b != coords.y); - Nikos 20110623: Do we really need the commented out check? // remove everything except the player and the docked station if (dockedStation && !interstel) { // jump to the nearest system [player setSystemID:sys]; closeSystems = nil; [self setSystemTo: sys]; int index = 0; while ([entities count] > 2) { Entity *ent = [entities objectAtIndex:index]; if ((ent != player)&&(ent != dockedStation)) { if (ent->isStation) // clear out queues [(StationEntity *)ent clear]; [self removeEntity:ent]; } else { index++; // leave that one alone } } } else { if (dockedStation == nil) [self removeAllEntitiesExceptPlayer]; // get rid of witchspace sky etc. if still extant } if (!dockedStation || !interstel) { [self setUpSpace]; // launching from station that jumped from interstellar space to normal space. [self populateNormalSpace]; if (dockedStation) { if ([dockedStation maxFlightSpeed] > 0) // we are a carrier: exit near the WitchspaceExitPosition { float d1 = [self randomDistanceWithinScanner]; HPVector pos = [UNIVERSE getWitchspaceExitPosition]; // no need to reset the PRNG Quaternion q1; quaternion_set_random(&q1); if (abs((int)d1) < 2750) { d1 += ((d1 > 0.0)? 2750.0f: -2750.0f); // no closer than 2750m. Carriers are bigger than player ships. } Vector v1 = vector_forward_from_quaternion(q1); pos.x += v1.x * d1; // randomise exit position pos.y += v1.y * d1; pos.z += v1.z * d1; [dockedStation setPosition: pos]; } [self setWitchspaceBreakPattern:YES]; [player doScriptEvent:OOJSID("shipWillExitWitchspace")]; [player doScriptEvent:OOJSID("shipExitedWitchspace")]; } } } if(!autoSaveNow) [self setViewDirection:VIEW_FORWARD]; displayGUI = NO; //reset atmospherics in case we ejected while we were in the atmophere [UNIVERSE setSkyColorRed:0.0f // back to black green:0.0f blue:0.0f alpha:0.0f]; } - (void) setUpUniverseFromWitchspace { PlayerEntity *player; // // check the player is still around! // if ([entities count] == 0) { /*- the player ship -*/ player = [[PlayerEntity alloc] init]; // alloc retains! [self addEntity:player]; /*--*/ } else { player = [PLAYER retain]; // retained here } [self setUpSpace]; [self populateNormalSpace]; [player leaveWitchspace]; [player release]; // released here [self setViewDirection:VIEW_FORWARD]; [comm_log_gui printLongText:[NSString stringWithFormat:@"%@ %@", [self getSystemName:systemID], [player dial_clock_adjusted]] align:GUI_ALIGN_CENTER color:[OOColor whiteColor] fadeTime:0 key:nil addToArray:[player commLog]]; displayGUI = NO; } - (void) setUpUniverseFromMisjump { PlayerEntity *player; // // check the player is still around! // if ([entities count] == 0) { /*- the player ship -*/ player = [[PlayerEntity alloc] init]; // alloc retains! [self addEntity:player]; /*--*/ } else { player = [PLAYER retain]; // retained here } [self setUpWitchspace]; [player leaveWitchspace]; [player release]; // released here [self setViewDirection:VIEW_FORWARD]; displayGUI = NO; } - (void) setUpWitchspace { [self setUpWitchspaceBetweenSystem:[PLAYER systemID] andSystem:[PLAYER nextHopTargetSystemID]]; } - (void) setUpWitchspaceBetweenSystem:(OOSystemID)s1 andSystem:(OOSystemID)s2 { // new system is hyper-centric : witchspace exit point is origin Entity *thing; PlayerEntity* player = PLAYER; Quaternion randomQ; NSString* override_key = [self keyForInterstellarOverridesForSystems:s1 :s2 inGalaxy:galaxyID]; NSDictionary *systeminfo = [systemManager getPropertiesForSystemKey:override_key]; [universeRegion clearSubregions]; // fixed entities (part of the graphics system really) come first... /*- the sky backdrop -*/ OOColor *col1 = [OOColor colorWithRed:0.0 green:1.0 blue:0.5 alpha:1.0]; OOColor *col2 = [OOColor colorWithRed:0.0 green:1.0 blue:0.0 alpha:1.0]; thing = [[SkyEntity alloc] initWithColors:col1:col2 andSystemInfo: systeminfo]; // alloc retains! [thing setScanClass: CLASS_NO_DRAW]; quaternion_set_random(&randomQ); [thing setOrientation:randomQ]; [self addEntity:thing]; [thing release]; /*- the dust particle system -*/ thing = [[DustEntity alloc] init]; [thing setScanClass: CLASS_NO_DRAW]; [self addEntity:thing]; [thing release]; ambientLightLevel = [systeminfo oo_floatForKey:@"ambient_level" defaultValue:1.0]; [self setLighting]; // also sets initial lights positions. OOLog(kOOLogUniversePopulateWitchspace, @"Populating witchspace ..."); OOLogIndentIf(kOOLogUniversePopulateWitchspace); [self clearSystemPopulator]; NSString *populator = [systeminfo oo_stringForKey:@"populator" defaultValue:@"interstellarSpaceWillPopulate"]; [system_repopulator release]; system_repopulator = [[systeminfo oo_stringForKey:@"repopulator" defaultValue:@"interstellarSpaceWillRepopulate"] retain]; JSContext *context = OOJSAcquireContext(); [PLAYER doWorldScriptEvent:OOJSIDFromString(populator) inContext:context withArguments:NULL count:0 timeLimit:kOOJSLongTimeLimit]; OOJSRelinquishContext(context); [self populateSystemFromDictionariesWithSun:nil andPlanet:nil]; // systeminfo might have a 'script_actions' resource we want to activate now... NSArray *script_actions = [systeminfo oo_arrayForKey:@"script_actions"]; if (script_actions != nil) { OOStandardsDeprecated([NSString stringWithFormat:@"The script_actions system info key is deprecated for %@.",override_key]); if (!OOEnforceStandards()) { [player runUnsanitizedScriptActions:script_actions allowingAIMethods:NO withContextName:@"" forTarget:nil]; } } next_repopulation = randf() * SYSTEM_REPOPULATION_INTERVAL; OOLogOutdentIf(kOOLogUniversePopulateWitchspace); } - (OOPlanetEntity *) setUpPlanet { // set the system seed for random number generation Random_Seed systemSeed = [systemManager getRandomSeedForCurrentSystem]; seed_for_planet_description(systemSeed); NSMutableDictionary *planetDict = [NSMutableDictionary dictionaryWithDictionary:[systemManager getPropertiesForCurrentSystem]]; [planetDict oo_setBool:YES forKey:@"mainForLocalSystem"]; OOPlanetEntity *a_planet = [[OOPlanetEntity alloc] initFromDictionary:planetDict withAtmosphere:[planetDict oo_boolForKey:@"has_atmosphere" defaultValue:YES] andSeed:systemSeed forSystem:systemID]; double planet_zpos = [planetDict oo_floatForKey:@"planet_distance" defaultValue:500000]; planet_zpos *= [planetDict oo_floatForKey:@"planet_distance_multiplier" defaultValue:1.0]; #ifdef OO_DUMP_PLANETINFO OOLog(@"planetinfo.record",@"planet zpos = %f",planet_zpos); #endif [a_planet setPosition:(HPVector){ 0, 0, planet_zpos }]; [a_planet setEnergy:1000000.0]; if ([allPlanets count]>0) // F7 sets [UNIVERSE planet], which can lead to some trouble! TODO: track down where exactly that happens! { OOPlanetEntity *tmp=[allPlanets objectAtIndex:0]; [self addEntity:a_planet]; [allPlanets removeObject:a_planet]; cachedPlanet=a_planet; [allPlanets replaceObjectAtIndex:0 withObject:a_planet]; [self removeEntity:(Entity *)tmp]; } else { [self addEntity:a_planet]; } return [a_planet autorelease]; } /* At any time other than game start, any call to this must be followed * by [self populateNormalSpace]. However, at game start, they need to be * separated to allow Javascript startUp routines to be run in-between */ - (void) setUpSpace { Entity *thing; // ShipEntity *nav_buoy; StationEntity *a_station; OOSunEntity *a_sun; OOPlanetEntity *a_planet; HPVector stationPos; Vector vf; id dict_object; NSDictionary *systeminfo = [systemManager getPropertiesForCurrentSystem]; unsigned techlevel = [systeminfo oo_unsignedIntForKey:KEY_TECHLEVEL]; NSString *stationDesc = nil, *defaultStationDesc = nil; OOColor *bgcolor; OOColor *pale_bgcolor; BOOL sunGoneNova; Random_Seed systemSeed = [systemManager getRandomSeedForCurrentSystem]; [[GameController sharedController] logProgress:DESC(@"populating-space")]; sunGoneNova = [systeminfo oo_boolForKey:@"sun_gone_nova" defaultValue:NO]; OO_DEBUG_PUSH_PROGRESS(@"setUpSpace - clearSubRegions, sky, dust"); [universeRegion clearSubregions]; // fixed entities (part of the graphics system really) come first... [self setSkyColorRed:0.0f green:0.0f blue:0.0f alpha:0.0f]; #ifdef OO_DUMP_PLANETINFO OOLog(@"planetinfo.record",@"seed = %d %d %d %d",system_seed.c,system_seed.d,system_seed.e,system_seed.f); OOLog(@"planetinfo.record",@"coordinates = %d %d",system_seed.d,system_seed.b); #define SPROP(PROP) OOLog(@"planetinfo.record",@#PROP " = \"%@\";",[systeminfo oo_stringForKey:@"" #PROP]); #define IPROP(PROP) OOLog(@"planetinfo.record",@#PROP " = %d;",[systeminfo oo_intForKey:@#PROP]); #define FPROP(PROP) OOLog(@"planetinfo.record",@#PROP " = %f;",[systeminfo oo_floatForKey:@"" #PROP]); IPROP(government); IPROP(economy); IPROP(techlevel); IPROP(population); IPROP(productivity); SPROP(name); SPROP(inhabitant); SPROP(inhabitants); SPROP(description); #endif // set the system seed for random number generation seed_for_planet_description(systemSeed); /*- the sky backdrop -*/ // colors... float h1 = randf(); float h2 = h1 + 1.0 / (1.0 + (Ranrot() % 5)); while (h2 > 1.0) h2 -= 1.0; OOColor *col1 = [OOColor colorWithHue:h1 saturation:randf() brightness:0.5 + randf()/2.0 alpha:1.0]; OOColor *col2 = [OOColor colorWithHue:h2 saturation:0.5 + randf()/2.0 brightness:0.5 + randf()/2.0 alpha:1.0]; thing = [[SkyEntity alloc] initWithColors:col1:col2 andSystemInfo: systeminfo]; // alloc retains! [thing setScanClass: CLASS_NO_DRAW]; [self addEntity:thing]; // bgcolor = [(SkyEntity *)thing skyColor]; // h1 = randf()/3.0; if (h1 > 0.17) { h1 += 0.33; } ambientLightLevel = [systeminfo oo_floatForKey:@"ambient_level" defaultValue:1.0]; // pick a main sequence colour dict_object=[systeminfo objectForKey:@"sun_color"]; if (dict_object!=nil) { bgcolor = [OOColor colorWithDescription:dict_object]; } else { bgcolor = [OOColor colorWithHue:h1 saturation:0.75*randf() brightness:0.65+randf()/5.0 alpha:1.0]; } pale_bgcolor = [bgcolor blendedColorWithFraction:0.5 ofColor:[OOColor whiteColor]]; [thing release]; /*--*/ /*- the dust particle system -*/ thing = [[DustEntity alloc] init]; // alloc retains! [thing setScanClass: CLASS_NO_DRAW]; [self addEntity:thing]; [(DustEntity *)thing setDustColor:pale_bgcolor]; [thing release]; /*--*/ float defaultSunFlare = randf()*0.1; float defaultSunHues = 0.5+randf()*0.5; OO_DEBUG_POP_PROGRESS(); // actual entities next... OO_DEBUG_PUSH_PROGRESS(@"setUpSpace - planet"); a_planet=[self setUpPlanet]; // resets RNG when called double planet_radius = [a_planet radius]; OO_DEBUG_POP_PROGRESS(); // set the system seed for random number generation seed_for_planet_description(systemSeed); OO_DEBUG_PUSH_PROGRESS(@"setUpSpace - sun"); /*- space sun -*/ double sun_radius; double sun_distance; double sunDistanceModifier; double safeDistance; HPVector sunPos; sunDistanceModifier = [systeminfo oo_nonNegativeDoubleForKey:@"sun_distance_modifier" defaultValue:0.0]; if (sunDistanceModifier < 6.0) // <6 isn't valid { sun_distance = [systeminfo oo_nonNegativeDoubleForKey:@"sun_distance" defaultValue:(planet_radius*20)]; // note, old property was _modifier, new property is _multiplier sun_distance *= [systeminfo oo_nonNegativeDoubleForKey:@"sun_distance_multiplier" defaultValue:1]; } else { sun_distance = planet_radius * sunDistanceModifier; } sun_radius = [systeminfo oo_nonNegativeDoubleForKey:@"sun_radius" defaultValue:2.5 * planet_radius]; // clamp the sun radius if ((sun_radius < 1000.0) || (sun_radius > sun_distance / 2 && !sunGoneNova)) { OOLogWARN(@"universe.setup.badSun",@"Sun radius of %f is not valid for this system",sun_radius); sun_radius = sun_radius < 1000.0 ? 1000.0 : (sun_distance / 2); } #ifdef OO_DUMP_PLANETINFO OOLog(@"planetinfo.record",@"sun_radius = %f",sun_radius); #endif safeDistance=36 * sun_radius * sun_radius; // 6 times the sun radius // here we need to check if the sun collides with (or is too close to) the witchpoint // otherwise at (for example) Maregais in Galaxy 1 we go BANG! HPVector sun_dir = [systeminfo oo_hpvectorForKey:@"sun_vector"]; sun_distance /= 2.0; do { sun_distance *= 2.0; sunPos = HPvector_subtract([a_planet position], HPvector_multiply_scalar(sun_dir,sun_distance)); // if not in the safe distance, multiply by two and try again } while (HPmagnitude2(sunPos) < safeDistance); // set planetary axial tilt to 0 degrees // TODO: allow this to vary [a_planet setOrientation:quaternion_rotation_betweenHP(sun_dir,make_HPvector(1.0,0.0,0.0))]; #ifdef OO_DUMP_PLANETINFO OOLog(@"planetinfo.record",@"sun_vector = %.3f %.3f %.3f",vf.x,vf.y,vf.z); OOLog(@"planetinfo.record",@"sun_distance = %.0f",sun_distance); #endif NSMutableDictionary *sun_dict = [NSMutableDictionary dictionaryWithCapacity:5]; [sun_dict setObject:[NSNumber numberWithDouble:sun_radius] forKey:@"sun_radius"]; dict_object=[systeminfo objectForKey: @"corona_shimmer"]; if (dict_object!=nil) [sun_dict setObject:dict_object forKey:@"corona_shimmer"]; dict_object=[systeminfo objectForKey: @"corona_hues"]; if (dict_object!=nil) { [sun_dict setObject:dict_object forKey:@"corona_hues"]; } else { [sun_dict setObject:[NSNumber numberWithFloat:defaultSunHues] forKey:@"corona_hues"]; } dict_object=[systeminfo objectForKey: @"corona_flare"]; if (dict_object!=nil) { [sun_dict setObject:dict_object forKey:@"corona_flare"]; } else { [sun_dict setObject:[NSNumber numberWithFloat:defaultSunFlare] forKey:@"corona_flare"]; } dict_object=[systeminfo objectForKey:KEY_SUNNAME]; if (dict_object!=nil) { [sun_dict setObject:dict_object forKey:KEY_SUNNAME]; } #ifdef OO_DUMP_PLANETINFO OOLog(@"planetinfo.record",@"corona_flare = %f",[sun_dict oo_floatForKey:@"corona_flare"]); OOLog(@"planetinfo.record",@"corona_hues = %f",[sun_dict oo_floatForKey:@"corona_hues"]); OOLog(@"planetinfo.record",@"sun_color = %@",[bgcolor descriptionComponents]); #endif a_sun = [[OOSunEntity alloc] initSunWithColor:bgcolor andDictionary:sun_dict]; // alloc retains! [a_sun setStatus:STATUS_ACTIVE]; [a_sun setPosition:sunPos]; // sets also light origin [a_sun setEnergy:1000000.0]; [self addEntity:a_sun]; if (sunGoneNova) { [a_sun setRadius: sun_radius andCorona:0.3]; [a_sun setThrowSparks:YES]; [a_sun setVelocity: kZeroVector]; } // set the lighting only after we know which sun we have. [self setLighting]; OO_DEBUG_POP_PROGRESS(); OO_DEBUG_PUSH_PROGRESS(@"setUpSpace - main station"); /*- space station -*/ stationPos = [a_planet position]; vf = [systeminfo oo_vectorForKey:@"station_vector"]; #ifdef OO_DUMP_PLANETINFO OOLog(@"planetinfo.record",@"station_vector = %.3f %.3f %.3f",vf.x,vf.y,vf.z); #endif stationPos = HPvector_subtract(stationPos, vectorToHPVector(vector_multiply_scalar(vf, 2.0 * planet_radius))); //// possibly systeminfo has an override for the station stationDesc = [systeminfo oo_stringForKey:@"station" defaultValue:@"coriolis"]; #ifdef OO_DUMP_PLANETINFO OOLog(@"planetinfo.record",@"station = %@",stationDesc); #endif a_station = (StationEntity *)[self newShipWithRole:stationDesc]; // retain count = 1 /* Sanity check: ensure that only stations are generated here. This is an attempt to fix exceptions of the form: NSInvalidArgumentException : *** -[ShipEntity setPlanet:]: selector not recognized [self = 0x19b7e000] ***** which I presume to be originating here since all other uses of setPlanet: are guarded by isStation checks. This error could happen if a ship that is not a station has a station role, or equivalently if an OXP sets a system's station role to a role used by non-stations. -- Ahruman 20080303 */ if (![a_station isStation] || ![a_station validForAddToUniverse]) { if (a_station == nil) { // Should have had a more specific error already, just specify context OOLog(@"universe.setup.badStation", @"Failed to set up a ship for role \"%@\" as system station, trying again with \"%@\".", stationDesc, defaultStationDesc); } else { 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); } [a_station release]; stationDesc = defaultStationDesc; a_station = (StationEntity *)[self newShipWithRole:stationDesc]; // retain count = 1 if (![a_station isStation] || ![a_station validForAddToUniverse]) { if (a_station == nil) { 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); } else { 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); } [a_station release]; a_station = (StationEntity *)[self newShipWithName:@"coriolis-station"]; if (![a_station isStation] || ![a_station validForAddToUniverse]) { OOLog(@"universe.setup.badStation", @"Could not create built-in Coriolis station! Generating a stationless system."); DESTROY(a_station); } } } if (a_station != nil) { [a_station setOrientation:quaternion_rotation_between(vf,make_vector(0.0,0.0,1.0))]; [a_station setPosition: stationPos]; [a_station setPitch: 0.0]; [a_station setScanClass: CLASS_STATION]; //[a_station setPlanet:[self planet]]; // done inside addEntity. [a_station setEquivalentTechLevel:techlevel]; [self addEntity:a_station]; // STATUS_IN_FLIGHT, AI state GLOBAL [a_station setStatus:STATUS_ACTIVE]; // For backward compatibility. Might not be needed. [a_station setAllowsFastDocking:true]; // Main stations always allow fast docking. [a_station setAllegiance:@"galcop"]; // Main station is galcop controlled } OO_DEBUG_POP_PROGRESS(); cachedSun = a_sun; cachedPlanet = a_planet; cachedStation = a_station; closeSystems = nil; OO_DEBUG_POP_PROGRESS(); OO_DEBUG_PUSH_PROGRESS(@"setUpSpace - populate from wormholes"); [self populateSpaceFromActiveWormholes]; OO_DEBUG_POP_PROGRESS(); [a_sun release]; [a_station release]; } - (void) populateNormalSpace { NSDictionary *systeminfo = [systemManager getPropertiesForCurrentSystem]; BOOL sunGoneNova = [systeminfo oo_boolForKey:@"sun_gone_nova"]; // check for nova if (sunGoneNova) { OO_DEBUG_PUSH_PROGRESS(@"setUpSpace - post-nova"); HPVector v0 = make_HPvector(0,0,34567.89); double min_safe_dist2 = 6000000.0 * 6000000.0; HPVector sunPos = [cachedSun position]; while (HPmagnitude2(cachedSun->position) < min_safe_dist2) // back off the planetary bodies { v0.z *= 2.0; sunPos = HPvector_add(sunPos, v0); [cachedSun setPosition:sunPos]; // also sets light origin } [self removeEntity:cachedPlanet]; // and Poof! it's gone cachedPlanet = nil; [self removeEntity:cachedStation]; // also remove main station cachedStation = nil; } OO_DEBUG_PUSH_PROGRESS(@"setUpSpace - populate from hyperpoint"); // [self populateSpaceFromHyperPoint:witchPos toPlanetPosition: a_planet->position andSunPosition: a_sun->position]; [self clearSystemPopulator]; if ([PLAYER status] != STATUS_START_GAME) { NSString *populator = [systeminfo oo_stringForKey:@"populator" defaultValue:(sunGoneNova)?@"novaSystemWillPopulate":@"systemWillPopulate"]; [system_repopulator release]; system_repopulator = [[systeminfo oo_stringForKey:@"repopulator" defaultValue:(sunGoneNova)?@"novaSystemWillRepopulate":@"systemWillRepopulate"] retain]; JSContext *context = OOJSAcquireContext(); [PLAYER doWorldScriptEvent:OOJSIDFromString(populator) inContext:context withArguments:NULL count:0 timeLimit:kOOJSLongTimeLimit]; OOJSRelinquishContext(context); [self populateSystemFromDictionariesWithSun:cachedSun andPlanet:cachedPlanet]; } OO_DEBUG_POP_PROGRESS(); // systeminfo might have a 'script_actions' resource we want to activate now... NSArray *script_actions = [systeminfo oo_arrayForKey:@"script_actions"]; if (script_actions != nil) { OOStandardsDeprecated([NSString stringWithFormat:@"The script_actions system info key is deprecated for %@.",[self getSystemName:systemID]]); if (!OOEnforceStandards()) { OO_DEBUG_PUSH_PROGRESS(@"setUpSpace - legacy script_actions"); [PLAYER runUnsanitizedScriptActions:script_actions allowingAIMethods:NO withContextName:@"" forTarget:nil]; OO_DEBUG_POP_PROGRESS(); } } next_repopulation = randf() * SYSTEM_REPOPULATION_INTERVAL; } - (void) clearSystemPopulator { [populatorSettings release]; populatorSettings = [[NSMutableDictionary alloc] initWithCapacity:128]; } - (NSDictionary *) getPopulatorSettings { return populatorSettings; } - (void) setPopulatorSetting:(NSString *)key to:(NSDictionary *)setting { if (setting == nil) { [populatorSettings removeObjectForKey:key]; } else { [populatorSettings setObject:setting forKey:key]; } } - (BOOL) deterministicPopulation { return deterministic_population; } - (void) populateSystemFromDictionariesWithSun:(OOSunEntity *)sun andPlanet:(OOPlanetEntity *)planet { Random_Seed systemSeed = [systemManager getRandomSeedForCurrentSystem]; NSArray *blocks = [populatorSettings allValues]; NSEnumerator *enumerator = [[blocks sortedArrayUsingFunction:populatorPrioritySort context:nil] objectEnumerator]; NSDictionary *populator = nil; HPVector location = kZeroHPVector; uint32_t i, locationSeed, groupCount, rndvalue; RANROTSeed rndcache = RANROTGetFullSeed(); RANROTSeed rndlocal = RANROTGetFullSeed(); NSString *locationCode = nil; OOJSPopulatorDefinition *pdef = nil; while ((populator = [enumerator nextObject])) { deterministic_population = [populator oo_boolForKey:@"deterministic" defaultValue:NO]; if (EXPECT_NOT(sun == nil || planet == nil)) { // needs to be a non-nova system, and not interstellar space deterministic_population = NO; } locationSeed = [populator oo_unsignedIntForKey:@"locationSeed" defaultValue:0]; groupCount = [populator oo_unsignedIntForKey:@"groupCount" defaultValue:1]; for (i = 0; i < groupCount; i++) { locationCode = [populator oo_stringForKey:@"location" defaultValue:@"COORDINATES"]; if ([locationCode isEqualToString:@"COORDINATES"]) { location = [populator oo_hpvectorForKey:@"coordinates" defaultValue:kZeroHPVector]; } else { if (locationSeed != 0) { rndcache = RANROTGetFullSeed(); // different place for each system rndlocal = RanrotSeedFromRandomSeed(systemSeed); rndvalue = RanrotWithSeed(&rndlocal); // ...for location seed rndlocal = MakeRanrotSeed(rndvalue+locationSeed); rndvalue = RanrotWithSeed(&rndlocal); // ...for iteration (63647 is nothing special, just a largish prime) RANROTSetFullSeed(MakeRanrotSeed(rndvalue+(i*63647))); } else { // not fixed coordinates and not seeded RNG; can't // be deterministic deterministic_population = NO; } if (sun == nil || planet == nil) { // all interstellar space and nova locations equal to WITCHPOINT location = [self locationByCode:@"WITCHPOINT" withSun:nil andPlanet:nil]; } else { location = [self locationByCode:locationCode withSun:sun andPlanet:planet]; } if(locationSeed != 0) { // go back to the main random sequence RANROTSetFullSeed(rndcache); } } // location now contains a Vector coordinate, one way or another pdef = [populator objectForKey:@"callbackObj"]; [pdef runCallback:location]; } } // nothing is deterministic once the populator is done deterministic_population = NO; } /* Generates a position within one of the named regions: * * WITCHPOINT: within scanner of witchpoint * LANE_*: within two scanner of lane, not too near each end * STATION_AEGIS: within two scanner of main station, not in planet * *_ORBIT_*: around the object, in a shell relative to object radius * TRIANGLE: somewhere in the triangle defined by W, P, S * INNER_SYSTEM: closer to the sun than the planet is * OUTER_SYSTEM: further from the sun than the planet is * *_OFFPLANE: like the above, but not on the orbital plane * * Can be called with nil sun or planet, but if so the calling function * must make sure the location code is WITCHPOINT. */ - (HPVector) locationByCode:(NSString *)code withSun:(OOSunEntity *)sun andPlanet:(OOPlanetEntity *)planet { HPVector result = kZeroHPVector; if ([code isEqualToString:@"WITCHPOINT"] || sun == nil || planet == nil || [sun goneNova]) { result = OOHPVectorRandomSpatial(SCANNER_MAX_RANGE); } // past this point, can assume non-nil sun, planet else { if ([code isEqualToString:@"LANE_WPS"]) { // pick position on one of the lanes, weighted by lane length double l1 = HPmagnitude([planet position]); double l2 = HPmagnitude(HPvector_subtract([sun position],[planet position])); double l3 = HPmagnitude([sun position]); double total = l1+l2+l3; float choice = randf(); if (choice < l1/total) { return [self locationByCode:@"LANE_WP" withSun:sun andPlanet:planet]; } else if (choice < (l1+l2)/total) { return [self locationByCode:@"LANE_PS" withSun:sun andPlanet:planet]; } else { return [self locationByCode:@"LANE_WS" withSun:sun andPlanet:planet]; } } else if ([code isEqualToString:@"LANE_WP"]) { result = OORandomPositionInCylinder(kZeroHPVector,SCANNER_MAX_RANGE,[planet position],[planet radius]*3,LANE_WIDTH); } else if ([code isEqualToString:@"LANE_WS"]) { result = OORandomPositionInCylinder(kZeroHPVector,SCANNER_MAX_RANGE,[sun position],[sun radius]*3,LANE_WIDTH); } else if ([code isEqualToString:@"LANE_PS"]) { result = OORandomPositionInCylinder([planet position],[planet radius]*3,[sun position],[sun radius]*3,LANE_WIDTH); } else if ([code isEqualToString:@"STATION_AEGIS"]) { do { result = OORandomPositionInShell([[self station] position],[[self station] collisionRadius]*1.2,SCANNER_MAX_RANGE*2.0); } while(HPdistance2(result,[planet position])<[planet radius]*[planet radius]*1.5); // loop to make sure not generated too close to the planet's surface } else if ([code isEqualToString:@"PLANET_ORBIT_LOW"]) { result = OORandomPositionInShell([planet position],[planet radius]*1.1,[planet radius]*2.0); } else if ([code isEqualToString:@"PLANET_ORBIT"]) { result = OORandomPositionInShell([planet position],[planet radius]*2.0,[planet radius]*4.0); } else if ([code isEqualToString:@"PLANET_ORBIT_HIGH"]) { result = OORandomPositionInShell([planet position],[planet radius]*4.0,[planet radius]*8.0); } else if ([code isEqualToString:@"STAR_ORBIT_LOW"]) { result = OORandomPositionInShell([sun position],[sun radius]*1.1,[sun radius]*2.0); } else if ([code isEqualToString:@"STAR_ORBIT"]) { result = OORandomPositionInShell([sun position],[sun radius]*2.0,[sun radius]*4.0); } else if ([code isEqualToString:@"STAR_ORBIT_HIGH"]) { result = OORandomPositionInShell([sun position],[sun radius]*4.0,[sun radius]*8.0); } else if ([code isEqualToString:@"TRIANGLE"]) { do { // pick random point in triangle by algorithm at // http://adamswaab.wordpress.com/2009/12/11/random-point-in-a-triangle-barycentric-coordinates/ // simplified by using the origin as A OOScalar r = randf(); OOScalar s = randf(); if (r+s >= 1) { r = 1-r; s = 1-s; } result = HPvector_add(HPvector_multiply_scalar([planet position],r),HPvector_multiply_scalar([sun position],s)); } // make sure at least 3 radii from vertices 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); } else if ([code isEqualToString:@"INNER_SYSTEM"]) { do { result = OORandomPositionInShell([sun position],[sun radius]*3.0,HPdistance([sun position],[planet position])); result = OOProjectHPVectorToPlane(result,kZeroHPVector,HPcross_product([sun position],[planet position])); result = HPvector_add(result,OOHPVectorRandomSpatial([planet radius])); // projection to plane could bring back too close to sun } while (HPdistance2(result,[sun position]) < [sun radius]*[sun radius]*9.0); } else if ([code isEqualToString:@"INNER_SYSTEM_OFFPLANE"]) { result = OORandomPositionInShell([sun position],[sun radius]*3.0,HPdistance([sun position],[planet position])); } else if ([code isEqualToString:@"OUTER_SYSTEM"]) { result = OORandomPositionInShell([sun position],HPdistance([sun position],[planet position]),HPdistance([sun position],[planet position])*10.0); // no more than 10 AU out result = OOProjectHPVectorToPlane(result,kZeroHPVector,HPcross_product([sun position],[planet position])); result = HPvector_add(result,OOHPVectorRandomSpatial(0.01*HPdistance(result,[sun position]))); // within 1% of plane } else if ([code isEqualToString:@"OUTER_SYSTEM_OFFPLANE"]) { result = OORandomPositionInShell([sun position],HPdistance([sun position],[planet position]),HPdistance([sun position],[planet position])*10.0); // no more than 10 AU out } else { OOLog(kOOLogUniversePopulateError,@"Named populator region %@ is not implemented, falling back to WITCHPOINT",code); result = OOHPVectorRandomSpatial(SCANNER_MAX_RANGE); } } return result; } - (void) setAmbientLightLevel:(float)newValue { NSAssert(UNIVERSE != nil, @"Attempt to set ambient light level with a non yet existent universe."); ambientLightLevel = OOClamp_0_max_f(newValue, 10.0f); return; } - (float) ambientLightLevel { return ambientLightLevel; } - (void) setLighting { /* GL_LIGHT1 is the sun and is active while a sun exists in space where there is no sun (witch/interstellar space) this is placed at the origin Shaders: this light is also used inside the station and needs to have its position reset relative to the player whenever demo ships or background scenes are to be shown -- 20100111 GL_LIGHT0 is the light for inside the station and needs to have its position reset relative to the player whenever demo ships or background scenes are to be shown Shaders: this light is not used. -- 20100111 */ OOSunEntity *the_sun = [self sun]; SkyEntity *the_sky = nil; GLfloat sun_pos[] = {0.0, 0.0, 0.0, 1.0}; // equivalent to kZeroVector - for interstellar space. GLfloat sun_ambient[] = {0.0, 0.0, 0.0, 1.0}; // overridden later in code int i; for (i = n_entities - 1; i > 0; i--) if ((sortedEntities[i]) && ([sortedEntities[i] isKindOfClass:[SkyEntity class]])) the_sky = (SkyEntity*)sortedEntities[i]; if (the_sun) { [the_sun getDiffuseComponents:sun_diffuse]; [the_sun getSpecularComponents:sun_specular]; OOGL(glLightfv(GL_LIGHT1, GL_AMBIENT, sun_ambient)); OOGL(glLightfv(GL_LIGHT1, GL_DIFFUSE, sun_diffuse)); OOGL(glLightfv(GL_LIGHT1, GL_SPECULAR, sun_specular)); sun_pos[0] = the_sun->position.x; sun_pos[1] = the_sun->position.y; sun_pos[2] = the_sun->position.z; } else { // witchspace stars_ambient[0] = 0.05; stars_ambient[1] = 0.20; stars_ambient[2] = 0.05; stars_ambient[3] = 1.0; sun_diffuse[0] = 0.85; sun_diffuse[1] = 1.0; sun_diffuse[2] = 0.85; sun_diffuse[3] = 1.0; sun_specular[0] = 0.95; sun_specular[1] = 1.0; sun_specular[2] = 0.95; sun_specular[3] = 1.0; OOGL(glLightfv(GL_LIGHT1, GL_AMBIENT, sun_ambient)); OOGL(glLightfv(GL_LIGHT1, GL_DIFFUSE, sun_diffuse)); OOGL(glLightfv(GL_LIGHT1, GL_SPECULAR, sun_specular)); } OOGL(glLightfv(GL_LIGHT1, GL_POSITION, sun_pos)); if (the_sky) { // ambient lighting! GLfloat r,g,b,a; [[the_sky skyColor] getRed:&r green:&g blue:&b alpha:&a]; r = r * (1.0 - SUN_AMBIENT_INFLUENCE) + sun_diffuse[0] * SUN_AMBIENT_INFLUENCE; g = g * (1.0 - SUN_AMBIENT_INFLUENCE) + sun_diffuse[1] * SUN_AMBIENT_INFLUENCE; b = b * (1.0 - SUN_AMBIENT_INFLUENCE) + sun_diffuse[2] * SUN_AMBIENT_INFLUENCE; GLfloat ambient_level = [self ambientLightLevel]; stars_ambient[0] = ambient_level * 0.0625 * (1.0 + r) * (1.0 + r); stars_ambient[1] = ambient_level * 0.0625 * (1.0 + g) * (1.0 + g); stars_ambient[2] = ambient_level * 0.0625 * (1.0 + b) * (1.0 + b); stars_ambient[3] = 1.0; } // light for demo ships display.. OOGL(glLightfv(GL_LIGHT0, GL_AMBIENT, docked_light_ambient)); OOGL(glLightfv(GL_LIGHT0, GL_DIFFUSE, docked_light_diffuse)); OOGL(glLightfv(GL_LIGHT0, GL_SPECULAR, docked_light_specular)); OOGL(glLightfv(GL_LIGHT0, GL_POSITION, demo_light_position)); OOGL(glLightModelfv(GL_LIGHT_MODEL_AMBIENT, stars_ambient)); } // Call this method to avoid lighting glich after windowed/fullscreen transition on macs. - (void) forceLightSwitch { demo_light_on = !demo_light_on; } - (void) setMainLightPosition: (Vector) sunPos { main_light_position[0] = sunPos.x; main_light_position[1] = sunPos.y; main_light_position[2] = sunPos.z; main_light_position[3] = 1.0; } - (ShipEntity *) addShipWithRole:(NSString *)desc launchPos:(HPVector)launchPos rfactor:(GLfloat)rfactor { if (rfactor != 0.0) { // Calculate the position as soon as possible, to minimise 'lollipop flash' launchPos.x += 2 * rfactor * (randf() - 0.5); launchPos.y += 2 * rfactor * (randf() - 0.5); launchPos.z += 2 * rfactor * (randf() - 0.5); } ShipEntity *ship = [self newShipWithRole:desc]; // retain count = 1 if (ship) { [ship setPosition:launchPos]; // minimise 'lollipop flash' // Deal with scripted cargopods and ensure they are filled with something. if ([ship hasRole:@"cargopod"]) [self fillCargopodWithRandomCargo:ship]; // Ensure piloted ships have pilots. if (![ship crew] && ![ship isUnpiloted]) [ship setCrew:[NSArray arrayWithObject: [OOCharacter randomCharacterWithRole:desc andOriginalSystem:Ranrot() & 255]]]; if ([ship scanClass] == CLASS_NOT_SET) { [ship setScanClass: CLASS_NEUTRAL]; } [self addEntity:ship]; // STATUS_IN_FLIGHT, AI state GLOBAL [ship release]; return ship; } return nil; } - (void) addShipWithRole:(NSString *) desc nearRouteOneAt:(double) route_fraction { // adds a ship within scanner range of a point on route 1 Entity *theStation = [self station]; if (!theStation) { return; } HPVector launchPos = OOHPVectorInterpolate([self getWitchspaceExitPosition], [theStation position], route_fraction); [self addShipWithRole:desc launchPos:launchPos rfactor:SCANNER_MAX_RANGE]; } - (HPVector) coordinatesForPosition:(HPVector) pos withCoordinateSystem:(NSString *) system returningScalar:(GLfloat*) my_scalar { /* the point is described using a system selected by a string consisting of a three letter code. The first letter indicates the feature that is the origin of the coordinate system. w => witchpoint s => sun p => planet The next letter indicates the feature on the 'z' axis of the coordinate system. w => witchpoint s => sun p => planet Then the 'y' axis of the system is normal to the plane formed by the planet, sun and witchpoint. And the 'x' axis of the system is normal to the y and z axes. So: ps: z axis = (planet -> sun) y axis = normal to (planet - sun - witchpoint) x axis = normal to y and z axes pw: z axis = (planet -> witchpoint) y axis = normal to (planet - witchpoint - sun) x axis = normal to y and z axes sp: z axis = (sun -> planet) y axis = normal to (sun - planet - witchpoint) x axis = normal to y and z axes sw: z axis = (sun -> witchpoint) y axis = normal to (sun - witchpoint - planet) x axis = normal to y and z axes wp: z axis = (witchpoint -> planet) y axis = normal to (witchpoint - planet - sun) x axis = normal to y and z axes ws: z axis = (witchpoint -> sun) y axis = normal to (witchpoint - sun - planet) x axis = normal to y and z axes The third letter denotes the units used: m: meters p: planetary radii s: solar radii u: distance between first two features indicated (eg. spu means that u = distance from sun to the planet) in interstellar space (== no sun) coordinates are absolute irrespective of the system used. [1.71] The position code "abs" can also be used for absolute coordinates. */ NSString* l_sys = [system lowercaseString]; if ([l_sys length] != 3) return kZeroHPVector; OOPlanetEntity* the_planet = [self planet]; OOSunEntity* the_sun = [self sun]; if (the_planet == nil || the_sun == nil || [l_sys isEqualToString:@"abs"]) { if (my_scalar) *my_scalar = 1.0; return pos; } HPVector w_pos = [self getWitchspaceExitPosition]; // don't reset PRNG HPVector p_pos = the_planet->position; HPVector s_pos = the_sun->position; const char* c_sys = [l_sys UTF8String]; HPVector p0, p1, p2; switch (c_sys[0]) { case 'w': p0 = w_pos; switch (c_sys[1]) { case 'p': p1 = p_pos; p2 = s_pos; break; case 's': p1 = s_pos; p2 = p_pos; break; default: return kZeroHPVector; } break; case 'p': p0 = p_pos; switch (c_sys[1]) { case 'w': p1 = w_pos; p2 = s_pos; break; case 's': p1 = s_pos; p2 = w_pos; break; default: return kZeroHPVector; } break; case 's': p0 = s_pos; switch (c_sys[1]) { case 'w': p1 = w_pos; p2 = p_pos; break; case 'p': p1 = p_pos; p2 = w_pos; break; default: return kZeroHPVector; } break; default: return kZeroHPVector; } HPVector k = HPvector_normal_or_zbasis(HPvector_subtract(p1, p0)); // 'forward' HPVector v = HPvector_normal_or_xbasis(HPvector_subtract(p2, p0)); // temporary vector in plane of 'forward' and 'right' HPVector j = HPcross_product(k, v); // 'up' HPVector i = HPcross_product(j, k); // 'right' GLfloat scale = 1.0; switch (c_sys[2]) { case 'p': scale = [the_planet radius]; break; case 's': scale = [the_sun radius]; break; case 'u': scale = HPmagnitude(HPvector_subtract(p1, p0)); break; case 'm': scale = 1.0f; break; default: return kZeroHPVector; } if (my_scalar) *my_scalar = scale; // result = p0 + ijk HPVector result = p0; // origin result.x += scale * (pos.x * i.x + pos.y * j.x + pos.z * k.x); result.y += scale * (pos.x * i.y + pos.y * j.y + pos.z * k.y); result.z += scale * (pos.x * i.z + pos.y * j.z + pos.z * k.z); return result; } - (NSString *) expressPosition:(HPVector) pos inCoordinateSystem:(NSString *) system { HPVector result = [self legacyPositionFrom:pos asCoordinateSystem:system]; return [NSString stringWithFormat:@"%@ %.2f %.2f %.2f", system, result.x, result.y, result.z]; } - (HPVector) legacyPositionFrom:(HPVector) pos asCoordinateSystem:(NSString *) system { NSString* l_sys = [system lowercaseString]; if ([l_sys length] != 3) return kZeroHPVector; OOPlanetEntity* the_planet = [self planet]; OOSunEntity* the_sun = [self sun]; if (the_planet == nil || the_sun == nil || [l_sys isEqualToString:@"abs"]) { return pos; } HPVector w_pos = [self getWitchspaceExitPosition]; // don't reset PRNG HPVector p_pos = the_planet->position; HPVector s_pos = the_sun->position; const char* c_sys = [l_sys UTF8String]; HPVector p0, p1, p2; switch (c_sys[0]) { case 'w': p0 = w_pos; switch (c_sys[1]) { case 'p': p1 = p_pos; p2 = s_pos; break; case 's': p1 = s_pos; p2 = p_pos; break; default: return kZeroHPVector; } break; case 'p': p0 = p_pos; switch (c_sys[1]) { case 'w': p1 = w_pos; p2 = s_pos; break; case 's': p1 = s_pos; p2 = w_pos; break; default: return kZeroHPVector; } break; case 's': p0 = s_pos; switch (c_sys[1]) { case 'w': p1 = w_pos; p2 = p_pos; break; case 'p': p1 = p_pos; p2 = w_pos; break; default: return kZeroHPVector; } break; default: return kZeroHPVector; } HPVector k = HPvector_normal_or_zbasis(HPvector_subtract(p1, p0)); // 'z' axis in m HPVector v = HPvector_normal_or_xbasis(HPvector_subtract(p2, p0)); // temporary vector in plane of 'forward' and 'right' HPVector j = HPcross_product(k, v); // 'y' axis in m HPVector i = HPcross_product(j, k); // 'x' axis in m GLfloat scale = 1.0; switch (c_sys[2]) { case 'p': { scale = 1.0f / [the_planet radius]; break; } case 's': { scale = 1.0f / [the_sun radius]; break; } case 'u': scale = 1.0f / HPdistance(p1, p0); break; case 'm': scale = 1.0f; break; default: return kZeroHPVector; } // result = p0 + ijk HPVector r_pos = HPvector_subtract(pos, p0); HPVector result = make_HPvector(scale * (r_pos.x * i.x + r_pos.y * i.y + r_pos.z * i.z), scale * (r_pos.x * j.x + r_pos.y * j.y + r_pos.z * j.z), scale * (r_pos.x * k.x + r_pos.y * k.y + r_pos.z * k.z) ); // scale * dot_products return result; } - (HPVector) coordinatesFromCoordinateSystemString:(NSString *) system_x_y_z { NSArray* tokens = ScanTokensFromString(system_x_y_z); if ([tokens count] != 4) { // Not necessarily an error. return make_HPvector(0,0,0); } GLfloat dummy; return [self coordinatesForPosition:make_HPvector([tokens oo_floatAtIndex:1], [tokens oo_floatAtIndex:2], [tokens oo_floatAtIndex:3]) withCoordinateSystem:[tokens oo_stringAtIndex:0] returningScalar:&dummy]; } - (BOOL) addShipWithRole:(NSString *) desc nearPosition:(HPVector) pos withCoordinateSystem:(NSString *) system { // initial position GLfloat scalar = 1.0; HPVector launchPos = [self coordinatesForPosition:pos withCoordinateSystem:system returningScalar:&scalar]; // randomise GLfloat rfactor = scalar; if (rfactor > SCANNER_MAX_RANGE) rfactor = SCANNER_MAX_RANGE; if (rfactor < 1000) rfactor = 1000; return ([self addShipWithRole:desc launchPos:launchPos rfactor:rfactor] != nil); } - (BOOL) addShips:(int) howMany withRole:(NSString *) desc atPosition:(HPVector) pos withCoordinateSystem:(NSString *) system { // initial bounding box GLfloat scalar = 1.0; HPVector launchPos = [self coordinatesForPosition:pos withCoordinateSystem:system returningScalar:&scalar]; GLfloat distance_from_center = 0.0; HPVector v_from_center, ship_pos; HPVector ship_positions[howMany]; int i = 0; int scale_up_after = 0; int current_shell = 0; GLfloat walk_factor = 2.0; while (i < howMany) { ShipEntity *ship = [self addShipWithRole:desc launchPos:launchPos rfactor:0.0]; if (ship == nil) return NO; OOScanClass scanClass = [ship scanClass]; [ship setScanClass:CLASS_NO_DRAW]; // avoid lollipop flash GLfloat safe_distance2 = ship->collision_radius * ship->collision_radius * SAFE_ADDITION_FACTOR2; BOOL safe; int limit_count = 8; v_from_center = kZeroHPVector; do { do { v_from_center.x += walk_factor * (randf() - 0.5); v_from_center.y += walk_factor * (randf() - 0.5); v_from_center.z += walk_factor * (randf() - 0.5); // drunkards walk } while ((v_from_center.x == 0.0)&&(v_from_center.y == 0.0)&&(v_from_center.z == 0.0)); v_from_center = HPvector_normal(v_from_center); // guaranteed non-zero ship_pos = make_HPvector( launchPos.x + distance_from_center * v_from_center.x, launchPos.y + distance_from_center * v_from_center.y, launchPos.z + distance_from_center * v_from_center.z); // check this position against previous ship positions in this shell safe = YES; int j = i - 1; while (safe && (j >= current_shell)) { safe = (safe && (HPdistance2(ship_pos, ship_positions[j]) > safe_distance2)); j--; } if (!safe) { limit_count--; if (!limit_count) // give up and expand the shell { limit_count = 8; distance_from_center += sqrt(safe_distance2); // expand to the next distance } } } while (!safe); [ship setPosition:ship_pos]; [ship setScanClass:scanClass == CLASS_NOT_SET ? CLASS_NEUTRAL : scanClass]; Quaternion qr; quaternion_set_random(&qr); [ship setOrientation:qr]; // [self addEntity:ship]; // STATUS_IN_FLIGHT, AI state GLOBAL ship_positions[i] = ship_pos; i++; if (i > scale_up_after) { current_shell = i; scale_up_after += 1 + 2 * i; distance_from_center += sqrt(safe_distance2); // fill the next shell } } return YES; } - (BOOL) addShips:(int) howMany withRole:(NSString *) desc nearPosition:(HPVector) pos withCoordinateSystem:(NSString *) system { // initial bounding box GLfloat scalar = 1.0; HPVector launchPos = [self coordinatesForPosition:pos withCoordinateSystem:system returningScalar:&scalar]; GLfloat rfactor = scalar; if (rfactor > SCANNER_MAX_RANGE) rfactor = SCANNER_MAX_RANGE; if (rfactor < 1000) rfactor = 1000; BoundingBox launch_bbox; bounding_box_reset_to_vector(&launch_bbox, make_vector(launchPos.x - rfactor, launchPos.y - rfactor, launchPos.z - rfactor)); bounding_box_add_xyz(&launch_bbox, launchPos.x + rfactor, launchPos.y + rfactor, launchPos.z + rfactor); return [self addShips: howMany withRole: desc intoBoundingBox: launch_bbox]; } - (BOOL) addShips:(int) howMany withRole:(NSString *) desc nearPosition:(HPVector) pos withCoordinateSystem:(NSString *) system withinRadius:(GLfloat) radius { // initial bounding box GLfloat scalar = 1.0; HPVector launchPos = [self coordinatesForPosition:pos withCoordinateSystem:system returningScalar:&scalar]; GLfloat rfactor = radius; if (rfactor < 1000) rfactor = 1000; BoundingBox launch_bbox; bounding_box_reset_to_vector(&launch_bbox, make_vector(launchPos.x - rfactor, launchPos.y - rfactor, launchPos.z - rfactor)); bounding_box_add_xyz(&launch_bbox, launchPos.x + rfactor, launchPos.y + rfactor, launchPos.z + rfactor); return [self addShips: howMany withRole: desc intoBoundingBox: launch_bbox]; } - (BOOL) addShips:(int) howMany withRole:(NSString *) desc intoBoundingBox:(BoundingBox) bbox { if (howMany < 1) return YES; if (howMany > 1) { // divide the number of ships in two int h0 = howMany / 2; int h1 = howMany - h0; // split the bounding box into two along its longest dimension GLfloat lx = bbox.max.x - bbox.min.x; GLfloat ly = bbox.max.y - bbox.min.y; GLfloat lz = bbox.max.z - bbox.min.z; BoundingBox bbox0 = bbox; BoundingBox bbox1 = bbox; if ((lx > lz)&&(lx > ly)) // longest dimension is x { bbox0.min.x += 0.5 * lx; bbox1.max.x -= 0.5 * lx; } else { if (ly > lz) // longest dimension is y { bbox0.min.y += 0.5 * ly; bbox1.max.y -= 0.5 * ly; } else // longest dimension is z { bbox0.min.z += 0.5 * lz; bbox1.max.z -= 0.5 * lz; } } // place half the ships into each bounding box return ([self addShips: h0 withRole: desc intoBoundingBox: bbox0] && [self addShips: h1 withRole: desc intoBoundingBox: bbox1]); } // randomise within the bounding box (biased towards the center of the box) HPVector pos = make_HPvector(bbox.min.x, bbox.min.y, bbox.min.z); pos.x += 0.5 * (randf() + randf()) * (bbox.max.x - bbox.min.x); pos.y += 0.5 * (randf() + randf()) * (bbox.max.y - bbox.min.y); pos.z += 0.5 * (randf() + randf()) * (bbox.max.z - bbox.min.z); return ([self addShipWithRole:desc launchPos:pos rfactor:0.0] != nil); } - (BOOL) spawnShip:(NSString *) shipdesc { // no need to do any more than log - enforcing modes wouldn't even have // loaded the legacy script OOStandardsDeprecated([NSString stringWithFormat:@"'spawn' via legacy script is deprecated as a way of adding ships for %@",shipdesc]); ShipEntity *ship; NSDictionary *shipdict = nil; shipdict = [[OOShipRegistry sharedRegistry] shipInfoForKey:shipdesc]; if (shipdict == nil) return NO; ship = [self newShipWithName:shipdesc]; // retain count is 1 if (ship == nil) return NO; // set any spawning characteristics NSDictionary *spawndict = [shipdict oo_dictionaryForKey:@"spawn"]; HPVector pos, rpos, spos; NSString *positionString = nil; // position positionString = [spawndict oo_stringForKey:@"position"]; if (positionString != nil) { if([positionString hasPrefix:@"abs "] && ([self planet] != nil || [self sun] !=nil)) { 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); } pos = [self coordinatesFromCoordinateSystemString:positionString]; } else { // without position defined, the ship will be added on top of the witchpoint buoy. pos = OOHPVectorRandomRadial(SCANNER_MAX_RANGE); OOLogERR(@"universe.spawnShip.error", @"***** ERROR: failed to find a spawn position for ship %@.", shipdesc); } [ship setPosition:pos]; // facing_position positionString = [spawndict oo_stringForKey:@"facing_position"]; if (positionString != nil) { if([positionString hasPrefix:@"abs "] && ([self planet] != nil || [self sun] !=nil)) { 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); } spos = [ship position]; Quaternion q1; rpos = [self coordinatesFromCoordinateSystemString:positionString]; rpos = HPvector_subtract(rpos, spos); // position relative to ship if (!HPvector_equal(rpos, kZeroHPVector)) { rpos = HPvector_normal(rpos); if (!HPvector_equal(rpos, HPvector_flip(kBasisZHPVector))) { q1 = quaternion_rotation_between(HPVectorToVector(rpos), kBasisZVector); } else { // for the inverse of the kBasisZVector the rotation is undefined, so we select one. q1 = make_quaternion(0,1,0,0); } [ship setOrientation:q1]; } } [self addEntity:ship]; // STATUS_IN_FLIGHT, AI state GLOBAL [ship release]; return YES; } - (void) witchspaceShipWithPrimaryRole:(NSString *)role { // adds a ship exiting witchspace (corollary of when ships leave the system) ShipEntity *ship = nil; NSDictionary *systeminfo = nil; OOGovernmentID government; systeminfo = [self currentSystemData]; government = [systeminfo oo_unsignedCharForKey:KEY_GOVERNMENT]; ship = [self newShipWithRole:role]; // retain count = 1 // Deal with scripted cargopods and ensure they are filled with something. if (ship && [ship hasRole:@"cargopod"]) { [self fillCargopodWithRandomCargo:ship]; } if (ship) { if (([ship scanClass] == CLASS_NO_DRAW)||([ship scanClass] == CLASS_NOT_SET)) [ship setScanClass: CLASS_NEUTRAL]; if ([role isEqual:@"trader"]) { [ship setCargoFlag: CARGO_FLAG_FULL_SCARCE]; if ([ship hasRole:@"sunskim-trader"] && randf() < 0.25) // select 1/4 of the traders suitable for sunskimming. { [ship setCargoFlag: CARGO_FLAG_FULL_PLENTIFUL]; [self makeSunSkimmer:ship andSetAI:YES]; } else { [ship switchAITo:@"oolite-traderAI.js"]; } if (([ship pendingEscortCount] > 0)&&((Ranrot() % 7) < government)) // remove escorts if we feel safe { int nx = [ship pendingEscortCount] - 2 * (1 + (Ranrot() & 3)); // remove 2,4,6, or 8 escorts [ship setPendingEscortCount:(nx > 0) ? nx : 0]; } } if ([role isEqual:@"pirate"]) { [ship setCargoFlag: CARGO_FLAG_PIRATE]; [ship setBounty: (Ranrot() & 7) + (Ranrot() & 7) + ((randf() < 0.05)? 63 : 23) withReason:kOOLegalStatusReasonSetup]; // they already have a price on their heads } if ([ship crew] == nil && ![ship isUnpiloted]) [ship setCrew:[NSArray arrayWithObject: [OOCharacter randomCharacterWithRole:role andOriginalSystem: Ranrot() & 255]]]; // The following is set inside leaveWitchspace: AI state GLOBAL, STATUS_EXITING_WITCHSPACE, ai message: EXITED_WITCHSPACE, then STATUS_IN_FLIGHT [ship leaveWitchspace]; [ship release]; } } // adds a ship within the collision radius of the other entity - (ShipEntity *) spawnShipWithRole:(NSString *) desc near:(Entity *) entity { if (entity == nil) return nil; ShipEntity *ship = nil; HPVector spawn_pos; Quaternion spawn_q; GLfloat offset = (randf() + randf()) * entity->collision_radius; quaternion_set_random(&spawn_q); spawn_pos = HPvector_add([entity position], vectorToHPVector(vector_multiply_scalar(vector_forward_from_quaternion(spawn_q), offset))); ship = [self addShipWithRole:desc launchPos:spawn_pos rfactor:0.0]; [ship setOrientation:spawn_q]; return ship; } - (OOVisualEffectEntity *) addVisualEffectAt:(HPVector)pos withKey:(NSString *)key { OOJS_PROFILE_ENTER // minimise the time between creating ship & assigning position. OOVisualEffectEntity *vis = [self newVisualEffectWithName:key]; // is retained BOOL success = NO; if (vis != nil) { [vis setPosition:pos]; [vis setOrientation:OORandomQuaternion()]; success = [self addEntity:vis]; // retained globally now [vis release]; } return success ? vis : (OOVisualEffectEntity *)nil; OOJS_PROFILE_EXIT } - (ShipEntity *) addShipAt:(HPVector)pos withRole:(NSString *)role withinRadius:(GLfloat)radius { OOJS_PROFILE_ENTER // minimise the time between creating ship & assigning position. if (radius == NSNotFound) { GLfloat scalar = 1.0; [self coordinatesForPosition:pos withCoordinateSystem:@"abs" returningScalar:&scalar]; // randomise GLfloat rfactor = scalar; if (rfactor > SCANNER_MAX_RANGE) rfactor = SCANNER_MAX_RANGE; if (rfactor < 1000) rfactor = 1000; pos.x += rfactor*(randf() - randf()); pos.y += rfactor*(randf() - randf()); pos.z += rfactor*(randf() - randf()); } else { pos = HPvector_add(pos, OOHPVectorRandomSpatial(radius)); } ShipEntity *ship = [self newShipWithRole:role]; // is retained BOOL success = NO; if (ship != nil) { [ship setPosition:pos]; if ([ship hasRole:@"cargopod"]) [self fillCargopodWithRandomCargo:ship]; OOScanClass scanClass = [ship scanClass]; if (scanClass == CLASS_NOT_SET) { scanClass = CLASS_NEUTRAL; [ship setScanClass:scanClass]; } if ([ship crew] == nil && ![ship isUnpiloted]) { [ship setCrew:[NSArray arrayWithObject: [OOCharacter randomCharacterWithRole:role andOriginalSystem:Ranrot() & 255]]]; } [ship setOrientation:OORandomQuaternion()]; BOOL trader = [role isEqualToString:@"trader"]; if (trader) { // half of traders created anywhere will now have cargo. if (randf() > 0.5f) { [ship setCargoFlag:(randf() < 0.66f ? CARGO_FLAG_FULL_PLENTIFUL : CARGO_FLAG_FULL_SCARCE)]; // most of them will carry the cargo produced in-system. } uint8_t pendingEscortCount = [ship pendingEscortCount]; if (pendingEscortCount > 0) { OOGovernmentID government = [[self currentSystemData] oo_unsignedCharForKey:KEY_GOVERNMENT]; if ((Ranrot() % 7) < government) // remove escorts if we feel safe { int nx = pendingEscortCount - 2 * (1 + (Ranrot() & 3)); // remove 2,4,6, or 8 escorts [ship setPendingEscortCount:(nx > 0) ? nx : 0]; } } } if (HPdistance([self getWitchspaceExitPosition], pos) > SCANNER_MAX_RANGE) { // nothing extra to do success = [self addEntity:ship]; // STATUS_IN_FLIGHT, AI state GLOBAL - ship is retained globally } else // witchspace incoming traders & pirates need extra settings. { if (trader) { [ship setCargoFlag:CARGO_FLAG_FULL_SCARCE]; if ([ship hasRole:@"sunskim-trader"] && randf() < 0.25) { [ship setCargoFlag:CARGO_FLAG_FULL_PLENTIFUL]; [self makeSunSkimmer:ship andSetAI:YES]; } else { [ship switchAITo:@"oolite-traderAI.js"]; } } else if ([role isEqual:@"pirate"]) { [ship setBounty:(Ranrot() & 7) + (Ranrot() & 7) + ((randf() < 0.05)? 63 : 23) withReason:kOOLegalStatusReasonSetup]; // they already have a price on their heads } // Status changes inside the following call: AI state GLOBAL, then STATUS_EXITING_WITCHSPACE, // with the EXITED_WITCHSPACE message sent to the AI. At last we set STATUS_IN_FLIGHT. // Includes addEntity, so ship is retained globally. success = [ship witchspaceLeavingEffects]; } [ship release]; } return success ? ship : (ShipEntity *)nil; OOJS_PROFILE_EXIT } - (NSArray *) addShipsAt:(HPVector)pos withRole:(NSString *)role quantity:(unsigned)count withinRadius:(GLfloat)radius asGroup:(BOOL)isGroup { OOJS_PROFILE_ENTER NSMutableArray *ships = [NSMutableArray arrayWithCapacity:count]; ShipEntity *ship = nil; OOShipGroup *group = nil; if (isGroup) { group = [OOShipGroup groupWithName:[NSString stringWithFormat:@"%@ group", role]]; } while (count--) { ship = [self addShipAt:pos withRole:role withinRadius:radius]; if (ship != nil) { // TODO: avoid collisions!!! if (isGroup) [ship setGroup:group]; [ships addObject:ship]; } } if ([ships count] == 0) return nil; return [[ships copy] autorelease]; OOJS_PROFILE_EXIT } - (NSArray *) addShipsToRoute:(NSString *)route withRole:(NSString *)role quantity:(unsigned)count routeFraction:(double)routeFraction asGroup:(BOOL)isGroup { NSMutableArray *ships = [NSMutableArray arrayWithCapacity:count]; ShipEntity *ship = nil; Entity *entity = nil; HPVector pos = kZeroHPVector, direction = kZeroHPVector, point0 = kZeroHPVector, point1 = kZeroHPVector; double radius = 0; if ([route isEqualToString:@"pw"] || [route isEqualToString:@"sw"] || [route isEqualToString:@"ps"]) { routeFraction = 1.0f - routeFraction; } // which route is it? if ([route isEqualTo:@"wp"] || [route isEqualTo:@"pw"]) { point0 = [self getWitchspaceExitPosition]; entity = [self planet]; if (entity == nil) return nil; point1 = [entity position]; radius = [entity radius]; } else if ([route isEqualTo:@"ws"] || [route isEqualTo:@"sw"]) { point0 = [self getWitchspaceExitPosition]; entity = [self sun]; if (entity == nil) return nil; point1 = [entity position]; radius = [entity radius]; } else if ([route isEqualTo:@"sp"] || [route isEqualTo:@"ps"]) { entity = [self sun]; if (entity == nil) return nil; point0 = [entity position]; double radius0 = [entity radius]; entity = [self planet]; if (entity == nil) return nil; point1 = [entity position]; radius = [entity radius]; // shorten the route by scanner range & sun radius, otherwise ships could be created inside it. direction = HPvector_normal(HPvector_subtract(point0, point1)); point0 = HPvector_subtract(point0, HPvector_multiply_scalar(direction, radius0 + SCANNER_MAX_RANGE * 1.1f)); } else if ([route isEqualTo:@"st"]) { point0 = [self getWitchspaceExitPosition]; if ([self station] == nil) return nil; point1 = [[self station] position]; radius = [[self station] collisionRadius]; } else return nil; // no route specifier? We shouldn't be here! // shorten the route by scanner range & radius, otherwise ships could be created inside the route destination. direction = HPvector_normal(HPvector_subtract(point1, point0)); point1 = HPvector_subtract(point1, HPvector_multiply_scalar(direction, radius + SCANNER_MAX_RANGE * 1.1f)); pos = [self fractionalPositionFrom:point0 to:point1 withFraction:routeFraction]; if(isGroup) { return [self addShipsAt:pos withRole:role quantity:count withinRadius:(SCANNER_MAX_RANGE / 10.0f) asGroup:YES]; } else { while (count--) { ship = [self addShipAt:pos withRole:role withinRadius:0]; // no radius because pos is already randomised with SCANNER_MAX_RANGE. if (ship != nil) [ships addObject:ship]; if (count > 0) pos = [self fractionalPositionFrom:point0 to:point1 withFraction:routeFraction]; } if ([ships count] == 0) return nil; } return [[ships copy] autorelease]; } - (BOOL) roleIsPirateVictim:(NSString *)role { return [self role:role isInCategory:@"oolite-pirate-victim"]; } - (BOOL) role:(NSString *)role isInCategory:(NSString *)category { NSSet *categoryInfo = [roleCategories objectForKey:category]; if (categoryInfo == nil) { return NO; } return [categoryInfo containsObject:role]; } // used to avoid having lost escorts when player advances clock while docked - (void) forceWitchspaceEntries { unsigned i; for (i = 0; i < n_entities; i++) { if (sortedEntities[i]->isShip) { ShipEntity *my_ship = (ShipEntity*)sortedEntities[i]; Entity* my_target = [my_ship primaryTarget]; if ([my_target isWormhole]) { [my_ship enterTargetWormhole]; } else if ([[[my_ship getAI] state] isEqualToString:@"ENTER_WORMHOLE"]) { [my_ship enterTargetWormhole]; } } } } - (void) addWitchspaceJumpEffectForShip:(ShipEntity *)ship { // don't add rings when system is being populated if ([PLAYER status] != STATUS_ENTERING_WITCHSPACE && [PLAYER status] != STATUS_EXITING_WITCHSPACE) { [self addEntity:[OORingEffectEntity ringFromEntity:ship]]; [self addEntity:[OORingEffectEntity shrinkingRingFromEntity:ship]]; } } - (GLfloat) safeWitchspaceExitDistance { for (unsigned i = 0; i < n_entities; i++) { Entity *e2 = sortedEntities[i]; if ([e2 isShip] && [(ShipEntity*)e2 hasPrimaryRole:@"buoy-witchpoint"]) { return [(ShipEntity*)e2 collisionRadius] + MIN_DISTANCE_TO_BUOY; } } return MIN_DISTANCE_TO_BUOY; } - (void) setUpBreakPattern:(HPVector) pos orientation:(Quaternion) q forDocking:(BOOL) forDocking { int i; OOBreakPatternEntity *ring = nil; id colorDesc = nil; OOColor *color = nil; [self setViewDirection:VIEW_FORWARD]; q.w = -q.w; // reverse the quaternion because this is from the player's viewpoint Vector v = vector_forward_from_quaternion(q); Vector vel = vector_multiply_scalar(v, -BREAK_PATTERN_RING_SPEED); // hyperspace colours OOColor *col1 = [OOColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.5]; //standard tunnel colour OOColor *col2 = [OOColor colorWithRed:0.0 green:0.0 blue:1.0 alpha:0.25]; //standard tunnel colour colorDesc = [[self globalSettings] objectForKey:@"hyperspace_tunnel_color_1"]; if (colorDesc != nil) { color = [OOColor colorWithDescription:colorDesc]; if (color != nil) col1 = color; else OOLogWARN(@"hyperspaceTunnel.fromDict", @"could not interpret \"%@\" as a colour.", colorDesc); } colorDesc = [[self globalSettings] objectForKey:@"hyperspace_tunnel_color_2"]; if (colorDesc != nil) { color = [OOColor colorWithDescription:colorDesc]; if (color != nil) col2 = color; else OOLogWARN(@"hyperspaceTunnel.fromDict", @"could not interpret \"%@\" as a colour.", colorDesc); } unsigned sides = kOOBreakPatternMaxSides; GLfloat startAngle = 0; GLfloat aspectRatio = 1; if (forDocking) { NSDictionary *info = [[PLAYER dockedStation] shipInfoDictionary]; sides = [info oo_unsignedIntForKey:@"tunnel_corners" defaultValue:4]; startAngle = [info oo_floatForKey:@"tunnel_start_angle" defaultValue:45.0f]; aspectRatio = [info oo_floatForKey:@"tunnel_aspect_ratio" defaultValue:2.67f]; } for (i = 1; i < 11; i++) { ring = [OOBreakPatternEntity breakPatternWithPolygonSides:sides startAngle:startAngle aspectRatio:aspectRatio]; if (!forDocking) { [ring setInnerColor:col1 outerColor:col2]; } Vector offset = vector_multiply_scalar(v, i * BREAK_PATTERN_RING_SPACING); [ring setPosition:HPvector_add(pos, vectorToHPVector(offset))]; // ahead of the player [ring setOrientation:q]; [ring setVelocity:vel]; [ring setLifetime:i * BREAK_PATTERN_RING_SPACING]; // FIXME: better would be to have break pattern timing not depend on // these ring objects existing in the first place. - CIM if (forDocking && ![[PLAYER dockedStation] hasBreakPattern]) { ring->isImmuneToBreakPatternHide = NO; } else if (!forDocking && ![self witchspaceBreakPattern]) { ring->isImmuneToBreakPatternHide = NO; } [self addEntity:ring]; breakPatternCounter++; } } - (BOOL) witchspaceBreakPattern { return _witchspaceBreakPattern; } - (void) setWitchspaceBreakPattern:(BOOL)newValue { _witchspaceBreakPattern = !!newValue; } - (BOOL) dockingClearanceProtocolActive { return _dockingClearanceProtocolActive; } - (void) setDockingClearanceProtocolActive:(BOOL)newValue { OOShipRegistry *registry = [OOShipRegistry sharedRegistry]; NSEnumerator *statEnum = [allStations objectEnumerator]; StationEntity *station = nil; /* CIM: picking a random ship type which can take the same primary * role as the station to determine whether it has no set docking * clearance requirements seems unlikely to work entirely * correctly. To be fixed. */ while ((station = [statEnum nextObject])) { NSString *stationKey = [registry randomShipKeyForRole:[station primaryRole]]; if (![[[registry shipInfoForKey:stationKey] allKeys] containsObject:@"requires_docking_clearance"]) { [station setRequiresDockingClearance:!!newValue]; } } _dockingClearanceProtocolActive = !!newValue; } - (void) handleGameOver { if ([[self gameController] playerFileToLoad]) { [[self gameController] loadPlayerIfRequired]; } else { [self setUseAddOns:SCENARIO_OXP_DEFINITION_ALL fromSaveGame:NO forceReinit:YES]; // calls reinitAndShowDemo } } - (void) setupIntroFirstGo:(BOOL)justCobra { PlayerEntity *player = PLAYER; ShipEntity *ship = nil; Quaternion q2 = { 0.0f, 0.0f, 1.0f, 0.0f }; // w,x,y,z // in status demo draw ships and display text if (!justCobra) { DESTROY(demo_ships); demo_ships = [[[OOShipRegistry sharedRegistry] demoShipKeys] retain]; // always, even if it's the cobra, because it's repositioned [self removeDemoShips]; } if (justCobra) { [player setStatus: STATUS_START_GAME]; } [player setShowDemoShips: YES]; displayGUI = YES; if (justCobra) { /*- cobra - intro1 -*/ ship = [self newShipWithName:PLAYER_SHIP_DESC usePlayerProxy:YES]; } else { /*- demo ships - intro2 -*/ demo_ship_index = 0; demo_ship_subindex = 0; /* Try to set the initial list position to Cobra III if * available, and at least the Ships category. */ NSArray *subList = nil; foreach (subList, demo_ships) { if ([[[subList oo_dictionaryAtIndex:0] oo_stringForKey:kOODemoShipClass] isEqualToString:@"ship"]) { demo_ship_index = [demo_ships indexOfObject:subList]; NSDictionary *shipEntry = nil; foreach (shipEntry, subList) { if ([[shipEntry oo_stringForKey:kOODemoShipKey] isEqualToString:@"cobra3-trader"]) { demo_ship_subindex = [subList indexOfObject:shipEntry]; break; } } break; } } if (!demo_ship) ship = [self newShipWithName:[[[demo_ships oo_arrayAtIndex:demo_ship_index] oo_dictionaryAtIndex:demo_ship_subindex] oo_stringForKey:kOODemoShipKey] usePlayerProxy:NO]; // stop consistency problems on the ship library screen [ship removeEquipmentItem:@"EQ_SHIELD_BOOSTER"]; [ship removeEquipmentItem:@"EQ_SHIELD_ENHANCER"]; } if (ship) { [ship setOrientation:q2]; if (!justCobra) { [ship setPositionX:0.0f y:0.0f z:DEMO2_VANISHING_DISTANCE * ship->collision_radius * 0.01]; [ship setDestination: ship->position]; // ideal position } else { // main screen Cobra is closer [ship setPositionX:0.0f y:0.0f z:3.6 * ship->collision_radius]; } [ship setScanClass: CLASS_NO_DRAW]; if (justCobra) { [ship setRoll:M_PI/7.5]; [ship setPitch:M_PI/15.0]; } else { [ship setRoll:M_PI/10.0]; [ship setPitch:M_PI/20.0]; } [ship switchAITo:@"nullAI.plist"]; if([ship pendingEscortCount] > 0) [ship setPendingEscortCount:0]; [self addEntity:ship]; // STATUS_IN_FLIGHT, AI state GLOBAL // now override status [ship setStatus:STATUS_COCKPIT_DISPLAY]; demo_ship = ship; [ship release]; } if (!justCobra) { // [gui setText:[demo_ship displayName] forRow:19 align:GUI_ALIGN_CENTER]; [self setLibraryTextForDemoShip]; } [self enterGUIViewModeWithMouseInteraction:NO]; if (!justCobra) { demo_stage = DEMO_SHOW_THING; demo_stage_time = universal_time + 300.0; } } - (NSDictionary *)demoShipData { return [[demo_ships oo_arrayAtIndex:demo_ship_index] oo_dictionaryAtIndex:demo_ship_subindex]; } - (void) setLibraryTextForDemoShip { OOGUITabSettings tab_stops; tab_stops[0] = 0; tab_stops[1] = 170; tab_stops[2] = 340; [gui setTabStops:tab_stops]; /* [gui setText:[demo_ship displayName] forRow:19 align:GUI_ALIGN_CENTER]; [gui setColor:[OOColor whiteColor] forRow:19]; */ NSDictionary *librarySettings = [self demoShipData]; OOGUIRow descRow = 7; NSString *field1 = nil; NSString *field2 = nil; NSString *field3 = nil; NSString *override = nil; // clear rows for (NSUInteger i=1;i<=26;i++) { [gui setText:@"" forRow:i]; } /* Row 1: ScanClass, Name, Summary */ override = [librarySettings oo_stringForKey:kOODemoShipClass defaultValue:@"ship"]; field1 = OOShipLibraryCategorySingular(override); field2 = [demo_ship shipClassName]; override = [librarySettings oo_stringForKey:kOODemoShipSummary defaultValue:nil]; if (override != nil) { field3 = OOExpand(override); } else { field3 = @""; } [gui setArray:[NSArray arrayWithObjects:field1,field2,field3,nil] forRow:1]; [gui setColor:[OOColor greenColor] forRow:1]; // ship_data defaults to true for "ship" class, false for everything else if (![librarySettings oo_boolForKey:kOODemoShipShipData defaultValue:[[librarySettings oo_stringForKey:kOODemoShipClass defaultValue:@"ship"] isEqualToString:@"ship"]]) { descRow = 3; } else { /* Row 2: Speed, Turn Rate, Cargo */ override = [librarySettings oo_stringForKey:kOODemoShipSpeed defaultValue:nil]; if (override != nil) { if ([override length] == 0) { field1 = @""; } else { field1 = [NSString stringWithFormat:DESC(@"oolite-ship-library-speed-custom"),OOExpand(override)]; } } else { field1 = OOShipLibrarySpeed(demo_ship); } override = [librarySettings oo_stringForKey:kOODemoShipTurnRate defaultValue:nil]; if (override != nil) { if ([override length] == 0) { field2 = @""; } else { field2 = [NSString stringWithFormat:DESC(@"oolite-ship-library-turn-custom"),OOExpand(override)]; } } else { field2 = OOShipLibraryTurnRate(demo_ship); } override = [librarySettings oo_stringForKey:kOODemoShipCargo defaultValue:nil]; if (override != nil) { if ([override length] == 0) { field3 = @""; } else { field3 = [NSString stringWithFormat:DESC(@"oolite-ship-library-cargo-custom"),OOExpand(override)]; } } else { field3 = OOShipLibraryCargo(demo_ship); } [gui setArray:[NSArray arrayWithObjects:field1,field2,field3,nil] forRow:3]; /* Row 3: recharge rate, energy banks, witchspace */ override = [librarySettings oo_stringForKey:kOODemoShipGenerator defaultValue:nil]; if (override != nil) { if ([override length] == 0) { field1 = @""; } else { field1 = [NSString stringWithFormat:DESC(@"oolite-ship-library-generator-custom"),OOExpand(override)]; } } else { field1 = OOShipLibraryGenerator(demo_ship); } override = [librarySettings oo_stringForKey:kOODemoShipShields defaultValue:nil]; if (override != nil) { if ([override length] == 0) { field2 = @""; } else { field2 = [NSString stringWithFormat:DESC(@"oolite-ship-library-shields-custom"),OOExpand(override)]; } } else { field2 = OOShipLibraryShields(demo_ship); } override = [librarySettings oo_stringForKey:kOODemoShipWitchspace defaultValue:nil]; if (override != nil) { if ([override length] == 0) { field3 = @""; } else { field3 = [NSString stringWithFormat:DESC(@"oolite-ship-library-witchspace-custom"),OOExpand(override)]; } } else { field3 = OOShipLibraryWitchspace(demo_ship); } [gui setArray:[NSArray arrayWithObjects:field1,field2,field3,nil] forRow:4]; /* Row 4: weapons, size */ override = [librarySettings oo_stringForKey:kOODemoShipWeapons defaultValue:nil]; if (override != nil) { if ([override length] == 0) { field1 = @""; } else { field1 = [NSString stringWithFormat:DESC(@"oolite-ship-library-weapons-custom"),OOExpand(override)]; } } else { field1 = OOShipLibraryWeapons(demo_ship); } field2 = @""; override = [librarySettings oo_stringForKey:kOODemoShipSize defaultValue:nil]; if (override != nil) { if ([override length] == 0) { field3 = @""; } else { field3 = [NSString stringWithFormat:DESC(@"oolite-ship-library-size-custom"),OOExpand(override)]; } } else { field3 = OOShipLibrarySize(demo_ship); } [gui setArray:[NSArray arrayWithObjects:field1,field2,field3,nil] forRow:5]; } override = [librarySettings oo_stringForKey:kOODemoShipDescription defaultValue:nil]; if (override != nil) { [gui addLongText:OOExpand(override) startingAtRow:descRow align:GUI_ALIGN_LEFT]; } // line 19: ship categories field1 = [NSString stringWithFormat:@"<-- %@",OOShipLibraryCategoryPlural([[[demo_ships objectAtIndex:((demo_ship_index+[demo_ships count]-1)%[demo_ships count])] objectAtIndex:0] oo_stringForKey:kOODemoShipClass])]; field2 = OOShipLibraryCategoryPlural([[[demo_ships objectAtIndex:demo_ship_index] objectAtIndex:0] oo_stringForKey:kOODemoShipClass]); field3 = [NSString stringWithFormat:@"%@ -->",OOShipLibraryCategoryPlural([[[demo_ships objectAtIndex:((demo_ship_index+1)%[demo_ships count])] objectAtIndex:0] oo_stringForKey:kOODemoShipClass])]; [gui setArray:[NSArray arrayWithObjects:field1,field2,field3,nil] forRow:19]; [gui setColor:[OOColor greenColor] forRow:19]; // lines 21-25: ship names NSArray *subList = [demo_ships objectAtIndex:demo_ship_index]; NSUInteger i,start = demo_ship_subindex - (demo_ship_subindex%5); NSUInteger end = start + 4; if (end >= [subList count]) { end = [subList count] - 1; } OOGUIRow row = 21; field1 = @""; field3 = @""; for (i = start ; i <= end ; i++) { field2 = [[subList objectAtIndex:i] oo_stringForKey:kOODemoShipName]; [gui setArray:[NSArray arrayWithObjects:field1,field2,field3,nil] forRow:row]; if (i == demo_ship_subindex) { [gui setColor:[OOColor yellowColor] forRow:row]; } else { [gui setColor:[OOColor whiteColor] forRow:row]; } row++; } field2 = @"..."; if (start > 0) { [gui setArray:[NSArray arrayWithObjects:field1,field2,field3,nil] forRow:20]; [gui setColor:[OOColor whiteColor] forRow:20]; } if (end < [subList count]-1) { [gui setArray:[NSArray arrayWithObjects:field1,field2,field3,nil] forRow:26]; [gui setColor:[OOColor whiteColor] forRow:26]; } } - (void) selectIntro2Previous { demo_stage = DEMO_SHOW_THING; NSUInteger subcount = [[demo_ships objectAtIndex:demo_ship_index] count]; demo_ship_subindex = (demo_ship_subindex + subcount - 2) % subcount; demo_stage_time = universal_time - 1.0; // force change } - (void) selectIntro2PreviousCategory { demo_stage = DEMO_SHOW_THING; demo_ship_index = (demo_ship_index + [demo_ships count] - 1) % [demo_ships count]; demo_ship_subindex = [[demo_ships objectAtIndex:demo_ship_index] count] - 1; demo_stage_time = universal_time - 1.0; // force change } - (void) selectIntro2NextCategory { demo_stage = DEMO_SHOW_THING; demo_ship_index = (demo_ship_index + 1) % [demo_ships count]; demo_ship_subindex = [[demo_ships objectAtIndex:demo_ship_index] count] - 1; demo_stage_time = universal_time - 1.0; // force change } - (void) selectIntro2Next { demo_stage = DEMO_SHOW_THING; demo_stage_time = universal_time - 1.0; // force change } static BOOL IsCandidateMainStationPredicate(Entity *entity, void *parameter) { return [entity isStation] && !entity->isExplicitlyNotMainStation; } static BOOL IsFriendlyStationPredicate(Entity *entity, void *parameter) { return [entity isStation] && ![(ShipEntity *)entity isHostileTo:parameter]; } - (StationEntity *) station { if (cachedSun != nil && cachedStation == nil) { cachedStation = [self findOneEntityMatchingPredicate:IsCandidateMainStationPredicate parameter:nil]; } return cachedStation; } - (StationEntity *) stationWithRole:(NSString *)role andPosition:(HPVector)position { if ([role isEqualToString:@""]) { return nil; } float range = 1000000; // allow a little variation in position NSArray *stations = [self stations]; StationEntity *station = nil; foreach (station, stations) { if (HPdistance2(position,[station position]) < range) { if ([[station primaryRole] isEqualToString:role]) { return station; } } } return nil; } - (StationEntity *) stationFriendlyTo:(ShipEntity *) ship { // In interstellar space we select a random friendly carrier as mainStation. // No caching: friendly status can change! return [self findOneEntityMatchingPredicate:IsFriendlyStationPredicate parameter:ship]; } - (OOPlanetEntity *) planet { if (cachedPlanet == nil && [allPlanets count] > 0) { cachedPlanet = [allPlanets objectAtIndex:0]; } return cachedPlanet; } - (OOSunEntity *) sun { if (cachedSun == nil) { cachedSun = [self findOneEntityMatchingPredicate:IsSunPredicate parameter:nil]; } return cachedSun; } - (NSArray *) planets { return allPlanets; } - (NSArray *) stations { return [allStations allObjects]; } - (NSArray *) wormholes { return activeWormholes; } - (void) unMagicMainStation { /* During the demo screens, the player must remain docked in order for the UI to work. This means either enforcing invulnerability or launching the player when the station is destroyed even if on the "new game Y/N" screen. The latter is a) weirder and b) harder. If your OXP relies on being able to destroy the main station before the game has even started, your OXP sucks. */ OOEntityStatus playerStatus = [PLAYER status]; if (playerStatus == STATUS_START_GAME) return; StationEntity *theStation = [self station]; if (theStation != nil) theStation->isExplicitlyNotMainStation = YES; cachedStation = nil; } - (void) resetBeacons { Entity *beaconShip = [self firstBeacon], *next = nil; while (beaconShip) { next = [beaconShip nextBeacon]; [beaconShip setPrevBeacon:nil]; [beaconShip setNextBeacon:nil]; beaconShip = next; } [self setFirstBeacon:nil]; [self setLastBeacon:nil]; } - (Entity *) firstBeacon { return [_firstBeacon weakRefUnderlyingObject]; } - (void) setFirstBeacon:(Entity *)beacon { if (beacon != [self firstBeacon]) { [beacon setPrevBeacon:nil]; [beacon setNextBeacon:[self firstBeacon]]; [[self firstBeacon] setPrevBeacon:beacon]; [_firstBeacon release]; _firstBeacon = [beacon weakRetain]; } } - (Entity *) lastBeacon { return [_lastBeacon weakRefUnderlyingObject]; } - (void) setLastBeacon:(Entity *)beacon { if (beacon != [self lastBeacon]) { [beacon setNextBeacon:nil]; [beacon setPrevBeacon:[self lastBeacon]]; [[self lastBeacon] setNextBeacon:beacon]; [_lastBeacon release]; _lastBeacon = [beacon weakRetain]; } } - (void) setNextBeacon:(Entity *) beaconShip { if ([beaconShip isBeacon]) { [self setLastBeacon:beaconShip]; if ([self firstBeacon] == nil) [self setFirstBeacon:beaconShip]; } else { OOLog(@"universe.beacon.error", @"***** ERROR: Universe setNextBeacon '%@'. The ship has no beacon code set.", beaconShip); } } - (void) clearBeacon:(Entity *) beaconShip { Entity *tmp = nil; if ([beaconShip isBeacon]) { if ([self firstBeacon] == beaconShip) { tmp = [[beaconShip nextBeacon] nextBeacon]; [self setFirstBeacon:[beaconShip nextBeacon]]; [[beaconShip prevBeacon] setNextBeacon:tmp]; } else if ([self lastBeacon] == beaconShip) { tmp = [[beaconShip prevBeacon] prevBeacon]; [self setLastBeacon:[beaconShip prevBeacon]]; [[beaconShip nextBeacon] setPrevBeacon:tmp]; } else { [[beaconShip nextBeacon] setPrevBeacon:[beaconShip prevBeacon]]; [[beaconShip prevBeacon] setNextBeacon:[beaconShip nextBeacon]]; } [beaconShip setBeaconCode:nil]; } } - (NSDictionary *) currentWaypoints { return waypoints; } - (void) defineWaypoint:(NSDictionary *)definition forKey:(NSString *)key { OOWaypointEntity *waypoint = nil; BOOL preserveCompass = NO; waypoint = [waypoints objectForKey:key]; if (waypoint != nil) { if ([PLAYER compassTarget] == waypoint) { preserveCompass = YES; } [self removeEntity:waypoint]; [waypoints removeObjectForKey:key]; } if (definition != nil) { waypoint = [OOWaypointEntity waypointWithDictionary:definition]; if (waypoint != nil) { [self addEntity:waypoint]; [waypoints setObject:waypoint forKey:key]; if (preserveCompass) { [PLAYER setCompassTarget:waypoint]; [PLAYER setNextBeacon:waypoint]; } } } } - (GLfloat *) skyClearColor { return skyClearColor; } - (void) setSkyColorRed:(GLfloat)red green:(GLfloat)green blue:(GLfloat)blue alpha:(GLfloat)alpha { skyClearColor[0] = red; skyClearColor[1] = green; skyClearColor[2] = blue; skyClearColor[3] = alpha; airResistanceFactor = alpha; } - (BOOL) breakPatternOver { return (breakPatternCounter == 0); } - (BOOL) breakPatternHide { Entity* player = PLAYER; return ((breakPatternCounter > 5)||(!player)||([player status] == STATUS_DOCKING)); } #define PROFILE_SHIP_SELECTION 0 - (BOOL) canInstantiateShip:(NSString *)shipKey { NSDictionary *shipInfo = nil; NSArray *conditions = nil; NSString *condition_script = nil; shipInfo = [[OOShipRegistry sharedRegistry] shipInfoForKey:shipKey]; condition_script = [shipInfo oo_stringForKey:@"condition_script"]; if (condition_script != nil) { OOJSScript *condScript = [self getConditionScript:condition_script]; if (condScript != nil) // should always be non-nil, but just in case { JSContext *context = OOJSAcquireContext(); BOOL OK; JSBool allow_instantiation; jsval result; jsval args[] = { OOJSValueFromNativeObject(context, shipKey) }; OK = [condScript callMethod:OOJSID("allowSpawnShip") inContext:context withArguments:args count:sizeof args / sizeof *args result:&result]; if (OK) OK = JS_ValueToBoolean(context, result, &allow_instantiation); OOJSRelinquishContext(context); if (OK && !allow_instantiation) { /* if the script exists, the function exists, the function * returns a bool, and that bool is false, block * instantiation. Otherwise allow it as default */ return NO; } } } conditions = [shipInfo oo_arrayForKey:@"conditions"]; if (conditions == nil) return YES; // Check conditions return [PLAYER scriptTestConditions:conditions]; } - (NSString *) randomShipKeyForRoleRespectingConditions:(NSString *)role { OOJS_PROFILE_ENTER OOShipRegistry *registry = [OOShipRegistry sharedRegistry]; NSString *shipKey = nil; OOMutableProbabilitySet *pset = nil; #if PROFILE_SHIP_SELECTION static unsigned long profTotal = 0, profSlowPath = 0; ++profTotal; #endif // Select a ship, check conditions and return it if possible. shipKey = [registry randomShipKeyForRole:role]; if ([self canInstantiateShip:shipKey]) return shipKey; /* If we got here, condition check failed. We now need to keep trying until we either find an acceptable ship or run out of candidates. This is special-cased because it has more overhead than the more common conditionless lookup. */ #if PROFILE_SHIP_SELECTION ++profSlowPath; if ((profSlowPath % 10) == 0) // Only print every tenth slow path, to reduce spamminess. { 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); } #endif pset = [[[registry probabilitySetForRole:role] mutableCopy] autorelease]; while ([pset count] > 0) { // Select a ship, check conditions and return it if possible. shipKey = [pset randomObject]; if ([self canInstantiateShip:shipKey]) return shipKey; // Condition failed -> remove ship from consideration. [pset removeObject:shipKey]; } // If we got here, some ships existed but all failed conditions test. return nil; OOJS_PROFILE_EXIT } - (ShipEntity *) newShipWithRole:(NSString *)role { OOJS_PROFILE_ENTER ShipEntity *ship = nil; NSString *shipKey = nil; NSDictionary *shipInfo = nil; NSString *autoAI = nil; shipKey = [self randomShipKeyForRoleRespectingConditions:role]; if (shipKey != nil) { ship = [self newShipWithName:shipKey]; if (ship != nil) { [ship setPrimaryRole:role]; shipInfo = [[OOShipRegistry sharedRegistry] shipInfoForKey:shipKey]; if ([shipInfo oo_fuzzyBooleanForKey:@"auto_ai" defaultValue:YES]) { // Set AI based on role autoAI = [self defaultAIForRole:role]; if (autoAI != nil) { [ship setAITo:autoAI]; // Nikos 20090604 // Pirate, trader or police with auto_ai? Follow populator rules for them. if ([role isEqualToString:@"pirate"]) [ship setBounty:20 + randf() * 50 withReason:kOOLegalStatusReasonSetup]; if ([role isEqualToString:@"trader"]) [ship setBounty:0 withReason:kOOLegalStatusReasonSetup]; if ([role isEqualToString:@"police"]) [ship setScanClass:CLASS_POLICE]; if ([role isEqualToString:@"interceptor"]) { [ship setScanClass: CLASS_POLICE]; [ship setPrimaryRole:@"police"]; // to make sure interceptors get the correct pilot later on. } } if ([role isEqualToString:@"thargoid"]) [ship setScanClass: CLASS_THARGOID]; // thargoids are not on the autoAIMap } } } return ship; OOJS_PROFILE_EXIT } - (OOVisualEffectEntity *) newVisualEffectWithName:(NSString *)effectKey { OOJS_PROFILE_ENTER NSDictionary *effectDict = nil; OOVisualEffectEntity *effect = nil; effectDict = [[OOShipRegistry sharedRegistry] effectInfoForKey:effectKey]; if (effectDict == nil) return nil; @try { effect = [[OOVisualEffectEntity alloc] initWithKey:effectKey definition:effectDict]; } @catch (NSException *exception) { if ([[exception name] isEqual:OOLITE_EXCEPTION_DATA_NOT_FOUND]) { OOLog(kOOLogException, @"***** Oolite Exception : '%@' in [Universe newVisualEffectWithName: %@ ] *****", [exception reason], effectKey); } else @throw exception; } return effect; OOJS_PROFILE_EXIT } - (ShipEntity *) newSubentityWithName:(NSString *)shipKey andScaleFactor:(float)scale { return [self newShipWithName:shipKey usePlayerProxy:NO isSubentity:YES andScaleFactor:scale]; } - (ShipEntity *) newShipWithName:(NSString *)shipKey usePlayerProxy:(BOOL)usePlayerProxy { return [self newShipWithName:shipKey usePlayerProxy:usePlayerProxy isSubentity:NO]; } - (ShipEntity *) newShipWithName:(NSString *)shipKey usePlayerProxy:(BOOL)usePlayerProxy isSubentity:(BOOL)isSubentity { return [self newShipWithName:shipKey usePlayerProxy:usePlayerProxy isSubentity:isSubentity andScaleFactor:1.0f]; } - (ShipEntity *) newShipWithName:(NSString *)shipKey usePlayerProxy:(BOOL)usePlayerProxy isSubentity:(BOOL)isSubentity andScaleFactor:(float)scale { OOJS_PROFILE_ENTER NSDictionary *shipDict = nil; ShipEntity *ship = nil; shipDict = [[OOShipRegistry sharedRegistry] shipInfoForKey:shipKey]; if (shipDict == nil) return nil; volatile Class shipClass = nil; if (isSubentity) { shipClass = [ShipEntity class]; } else { shipClass = [self shipClassForShipDictionary:shipDict]; if (usePlayerProxy && shipClass == [ShipEntity class]) { shipClass = [ProxyPlayerEntity class]; } } @try { if (scale != 1.0f) { NSMutableDictionary *mShipDict = [shipDict mutableCopy]; [mShipDict setObject:[NSNumber numberWithFloat:scale] forKey:@"model_scale_factor"]; shipDict = [NSDictionary dictionaryWithDictionary:mShipDict]; [mShipDict release]; } ship = [[shipClass alloc] initWithKey:shipKey definition:shipDict]; } @catch (NSException *exception) { if ([[exception name] isEqual:OOLITE_EXCEPTION_DATA_NOT_FOUND]) { OOLog(kOOLogException, @"***** Oolite Exception : '%@' in [Universe newShipWithName: %@ ] *****", [exception reason], shipKey); } else @throw exception; } // Set primary role to same as ship name, if ship name is also a role. // Otherwise, if caller doesn't set a role, one will be selected randomly. if ([ship hasRole:shipKey]) [ship setPrimaryRole:shipKey]; return ship; OOJS_PROFILE_EXIT } - (DockEntity *) newDockWithName:(NSString *)shipDataKey andScaleFactor:(float)scale { OOJS_PROFILE_ENTER NSDictionary *shipDict = nil; DockEntity *dock = nil; shipDict = [[OOShipRegistry sharedRegistry] shipInfoForKey:shipDataKey]; if (shipDict == nil) return nil; @try { if (scale != 1.0f) { NSMutableDictionary *mShipDict = [shipDict mutableCopy]; [mShipDict setObject:[NSNumber numberWithFloat:scale] forKey:@"model_scale_factor"]; shipDict = [NSDictionary dictionaryWithDictionary:mShipDict]; [mShipDict release]; } dock = [[DockEntity alloc] initWithKey:shipDataKey definition:shipDict]; } @catch (NSException *exception) { if ([[exception name] isEqual:OOLITE_EXCEPTION_DATA_NOT_FOUND]) { OOLog(kOOLogException, @"***** Oolite Exception : '%@' in [Universe newDockWithName: %@ ] *****", [exception reason], shipDataKey); } else @throw exception; } // Set primary role to same as name, if ship name is also a role. // Otherwise, if caller doesn't set a role, one will be selected randomly. if ([dock hasRole:shipDataKey]) [dock setPrimaryRole:shipDataKey]; return dock; OOJS_PROFILE_EXIT } - (ShipEntity *) newShipWithName:(NSString *)shipKey { return [self newShipWithName:shipKey usePlayerProxy:NO]; } - (Class) shipClassForShipDictionary:(NSDictionary *)dict { OOJS_PROFILE_ENTER if (dict == nil) return Nil; BOOL isStation = NO; NSString *shipRoles = [dict oo_stringForKey:@"roles"]; if (shipRoles != nil) { isStation = [shipRoles rangeOfString:@"station"].location != NSNotFound || [shipRoles rangeOfString:@"carrier"].location != NSNotFound; } // Note priority here: is_carrier overrides isCarrier which overrides roles. isStation = [dict oo_boolForKey:@"isCarrier" defaultValue:isStation]; isStation = [dict oo_boolForKey:@"is_carrier" defaultValue:isStation]; return isStation ? [StationEntity class] : [ShipEntity class]; OOJS_PROFILE_EXIT } - (NSString *)defaultAIForRole:(NSString *)role { return [autoAIMap oo_stringForKey:role]; } - (OOCargoQuantity) maxCargoForShip:(NSString *) desc { return [[[OOShipRegistry sharedRegistry] shipInfoForKey:desc] oo_unsignedIntForKey:@"max_cargo" defaultValue:0]; } /* * Price for an item expressed in 10ths of credits (divide by 10 to get credits) */ - (OOCreditsQuantity) getEquipmentPriceForKey:(NSString *)eq_key { NSArray *itemData; foreach (itemData, equipmentData) { NSString *itemType = [itemData oo_stringAtIndex:EQUIPMENT_KEY_INDEX]; if ([itemType isEqual:eq_key]) { return [itemData oo_unsignedLongLongAtIndex:EQUIPMENT_PRICE_INDEX]; } } return 0; } - (OOCommodities *) commodities { return commodities; } /* Converts template cargo pods to real ones */ - (ShipEntity *) reifyCargoPod:(ShipEntity *)cargoObj { if ([cargoObj isTemplateCargoPod]) { return [UNIVERSE cargoPodFromTemplate:cargoObj]; } else { return cargoObj; } } - (ShipEntity *) cargoPodFromTemplate:(ShipEntity *)cargoObj { ShipEntity *container = nil; // this is a template container, so we need to make a real one OOCommodityType co_type = [cargoObj commodityType]; OOCargoQuantity co_amount = [UNIVERSE getRandomAmountOfCommodity:co_type]; if (randf() < 0.5) // stops OXP monopolising pods for commodities { container = [UNIVERSE newShipWithRole:co_type]; // newShipWithRole returns retained object } if (container == nil) { container = [UNIVERSE newShipWithRole:@"cargopod"]; } [container setCommodity:co_type andAmount:co_amount]; return [container autorelease]; } - (NSArray *) getContainersOfGoods:(OOCargoQuantity)how_many scarce:(BOOL)scarce legal:(BOOL)legal { /* build list of goods allocating 0..100 for each based on how much of each quantity there is. Use a ratio of n x 100/64 for plentiful goods; reverse the probabilities for scarce goods. */ NSMutableArray *accumulator = [NSMutableArray arrayWithCapacity:how_many]; NSUInteger i=0, commodityCount = [commodityMarket count]; OOCargoQuantity quantities[commodityCount]; OOCargoQuantity total_quantity = 0; NSArray *goodsKeys = [commodityMarket goods]; NSString *goodsKey = nil; foreach (goodsKey, goodsKeys) { OOCargoQuantity q = [commodityMarket quantityForGood:goodsKey]; if (scarce) { if (q < 64) q = 64 - q; else q = 0; } // legal YES restricts (almost) only to legal goods // legal NO allows illegal goods, but not necessarily a full hold if (legal && [commodityMarket exportLegalityForGood:goodsKey] > 0) { q &= 1; // keep a very small chance, sometimes } if (q > 64) q = 64; q *= 100; q/= 64; quantities[i++] = q; total_quantity += q; } // quantities is now used to determine which good get into the containers for (i = 0; i < how_many; i++) { NSUInteger co_type = 0; int qr=0; if(total_quantity) { qr = 1+(Ranrot() % total_quantity); co_type = 0; while (qr > 0) { NSAssert((NSUInteger)co_type < commodityCount, @"Commodity type index out of range."); qr -= quantities[co_type++]; } co_type--; } ShipEntity *container = [cargoPods objectForKey:[goodsKeys oo_stringAtIndex:co_type]]; if (container != nil) { [accumulator addObject:container]; } else { OOLog(@"universe.createContainer.failed", @"***** ERROR: failed to find a container to fill with %@ (%ld).", [goodsKeys oo_stringAtIndex:co_type], co_type); } } return [NSArray arrayWithArray:accumulator]; } - (NSArray *) getContainersOfCommodity:(OOCommodityType)commodity_name :(OOCargoQuantity)how_much { NSMutableArray *accumulator = [NSMutableArray arrayWithCapacity:how_much]; if (![commodities goodDefined:commodity_name]) { return [NSArray array]; // empty array } ShipEntity *container = [cargoPods objectForKey:commodity_name]; while (how_much > 0) { if (container) { [accumulator addObject:container]; } else { OOLog(@"universe.createContainer.failed", @"***** ERROR: failed to find a container to fill with %@", commodity_name); } how_much--; } return [NSArray arrayWithArray:accumulator]; } - (void) fillCargopodWithRandomCargo:(ShipEntity *)cargopod { if (cargopod == nil || ![cargopod hasRole:@"cargopod"] || [cargopod cargoType] == CARGO_SCRIPTED_ITEM) return; if ([cargopod commodityType] == nil || ![cargopod commodityAmount]) { NSString *aCommodity = [self getRandomCommodity]; OOCargoQuantity aQuantity = [self getRandomAmountOfCommodity:aCommodity]; [cargopod setCommodity:aCommodity andAmount:aQuantity]; } } - (NSString *) getRandomCommodity { return [commodities getRandomCommodity]; } - (OOCargoQuantity) getRandomAmountOfCommodity:(OOCommodityType)co_type { OOMassUnit units; if (co_type == nil) { return 0; } units = [commodities massUnitForGood:co_type]; switch (units) { case 0 : // TONNES return 1; case 1 : // KILOGRAMS return 1 + (Ranrot() % 6) + (Ranrot() % 6) + (Ranrot() % 6); case 2 : // GRAMS return 4 + (Ranrot() % 16) + (Ranrot() % 11) + (Ranrot() % 6); } OOLog(@"universe.commodityAmount.warning",@"Commodity %@ has an unrecognised mass unit, assuming tonnes",co_type); return 1; } - (NSDictionary *)commodityDataForType:(OOCommodityType)type { return [commodityMarket definitionForGood:type]; } - (NSString *) displayNameForCommodity:(OOCommodityType)co_type { return [commodityMarket nameForGood:co_type]; } - (NSString *) describeCommodity:(OOCommodityType)co_type amount:(OOCargoQuantity)co_amount { int units; NSString *unitDesc = nil, *typeDesc = nil; NSDictionary *commodity = [self commodityDataForType:co_type]; if (commodity == nil) return @""; units = [commodityMarket massUnitForGood:co_type]; if (co_amount == 1) { switch (units) { case UNITS_KILOGRAMS : // KILOGRAM unitDesc = DESC(@"cargo-kilogram"); break; case UNITS_GRAMS : // GRAM unitDesc = DESC(@"cargo-gram"); break; case UNITS_TONS : // TONNE default : unitDesc = DESC(@"cargo-ton"); break; } } else { switch (units) { case UNITS_KILOGRAMS : // KILOGRAMS unitDesc = DESC(@"cargo-kilograms"); break; case UNITS_GRAMS : // GRAMS unitDesc = DESC(@"cargo-grams"); break; case UNITS_TONS : // TONNES default : unitDesc = DESC(@"cargo-tons"); break; } } typeDesc = [commodityMarket nameForGood:co_type]; return [NSString stringWithFormat:@"%d %@ %@",co_amount, unitDesc, typeDesc]; } //////////////////////////////////////////////////// - (void) setGameView:(MyOpenGLView *)view { [gameView release]; gameView = [view retain]; } - (MyOpenGLView *) gameView { return gameView; } - (GameController *) gameController { return [[self gameView] gameController]; } - (NSDictionary *) gameSettings { #if OOLITE_SDL NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:11]; #else NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:10]; #endif [result oo_setInteger:[PLAYER isSpeechOn] forKey:@"speechOn"]; [result oo_setBool:autoSave forKey:@"autosave"]; [result oo_setBool:wireframeGraphics forKey:@"wireframeGraphics"]; [result oo_setBool:doProcedurallyTexturedPlanets forKey:@"procedurallyTexturedPlanets"]; #if OOLITE_SDL [result oo_setFloat:[gameView gammaValue] forKey:@"gammaValue"]; #endif [result oo_setFloat:[gameView fov:NO] forKey:@"fovValue"]; [result setObject:OOStringFromGraphicsDetail([self detailLevel]) forKey:@"detailLevel"]; NSString *desc = @"UNDEFINED"; switch ([[OOMusicController sharedController] mode]) { case kOOMusicOff: desc = @"MUSIC_OFF"; break; case kOOMusicOn: desc = @"MUSIC_ON"; break; case kOOMusicITunes: desc = @"MUSIC_ITUNES"; break; } [result setObject:desc forKey:@"musicMode"]; NSDictionary *gameWindow = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithFloat:[gameView viewSize].width], @"width", [NSNumber numberWithFloat:[gameView viewSize].height], @"height", [NSNumber numberWithBool:[[self gameController] inFullScreenMode]], @"fullScreen", nil]; [result setObject:gameWindow forKey:@"gameWindow"]; [result setObject:[PLAYER keyConfig] forKey:@"keyConfig"]; return [[result copy] autorelease]; } - (void) useGUILightSource:(BOOL)GUILight { if (GUILight != demo_light_on) { if (![self useShaders]) { if (GUILight) { OOGL(glEnable(GL_LIGHT0)); OOGL(glDisable(GL_LIGHT1)); } else { OOGL(glEnable(GL_LIGHT1)); OOGL(glDisable(GL_LIGHT0)); } } // There should be nothing to do for shaders, they use the same (always on) light source // both in flight & in gui mode. According to the standard, shaders should treat lights as // always enabled. At least one non-standard shader implementation (windows' X3100 Intel // core with GM965 chipset and version 6.14.10.4990 driver) does _not_ use glDisabled lights, // making the following line necessary. else OOGL(glEnable(GL_LIGHT1)); // make sure we have a light, even with shaders (!) demo_light_on = GUILight; } } - (void) lightForEntity:(BOOL)isLit { if (isLit != object_light_on) { if ([self useShaders]) { if (isLit) { OOGL(glLightfv(GL_LIGHT1, GL_DIFFUSE, sun_diffuse)); OOGL(glLightfv(GL_LIGHT1, GL_SPECULAR, sun_specular)); } else { OOGL(glLightfv(GL_LIGHT1, GL_DIFFUSE, sun_off)); OOGL(glLightfv(GL_LIGHT1, GL_SPECULAR, sun_off)); } } else { if (!demo_light_on) { if (isLit) OOGL(glEnable(GL_LIGHT1)); else OOGL(glDisable(GL_LIGHT1)); } else { // If we're in demo/GUI mode we should always have a lit object. OOGL(glEnable(GL_LIGHT0)); // Redundant, see above. //if (isLit) OOGL(glEnable(GL_LIGHT0)); //else OOGL(glDisable(GL_LIGHT0)); } } object_light_on = isLit; } } // global rotation matrix definitions static const OOMatrix fwd_matrix = {{ { 1.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 1.0f } }}; static const OOMatrix aft_matrix = {{ {-1.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, -1.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 1.0f } }}; static const OOMatrix port_matrix = {{ { 0.0f, 0.0f, -1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 1.0f } }}; static const OOMatrix starboard_matrix = {{ { 0.0f, 0.0f, 1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f, 0.0f }, {-1.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 1.0f } }}; - (void) getActiveViewMatrix:(OOMatrix *)outMatrix forwardVector:(Vector *)outForward upVector:(Vector *)outUp { assert(outMatrix != NULL && outForward != NULL && outUp != NULL); PlayerEntity *player = nil; switch (viewDirection) { case VIEW_AFT: *outMatrix = aft_matrix; *outForward = vector_flip(kBasisZVector); *outUp = kBasisYVector; return; case VIEW_PORT: *outMatrix = port_matrix; *outForward = vector_flip(kBasisXVector); *outUp = kBasisYVector; return; case VIEW_STARBOARD: *outMatrix = starboard_matrix; *outForward = kBasisXVector; *outUp = kBasisYVector; return; case VIEW_CUSTOM: player = PLAYER; *outMatrix = [player customViewMatrix]; *outForward = [player customViewForwardVector]; *outUp = [player customViewUpVector]; return; case VIEW_FORWARD: case VIEW_NONE: case VIEW_GUI_DISPLAY: case VIEW_BREAK_PATTERN: ; } *outMatrix = fwd_matrix; *outForward = kBasisZVector; *outUp = kBasisYVector; } - (OOMatrix) activeViewMatrix { OOMatrix m; Vector f, u; [self getActiveViewMatrix:&m forwardVector:&f upVector:&u]; return m; } /* Code adapted from http://www.crownandcutlass.com/features/technicaldetails/frustum.html * Original license is: "This page and its contents are Copyright 2000 by Mark Morley * Unless otherwise noted, you may use any and all code examples provided herein in any way you want." */ - (void) defineFrustum { OOMatrix clip; GLfloat rt; clip = OOGLGetModelViewProjection(); /* Extract the numbers for the RIGHT plane */ frustum[0][0] = clip.m[0][3] - clip.m[0][0]; frustum[0][1] = clip.m[1][3] - clip.m[1][0]; frustum[0][2] = clip.m[2][3] - clip.m[2][0]; frustum[0][3] = clip.m[3][3] - clip.m[3][0]; /* Normalize the result */ rt = 1.0f / sqrt(frustum[0][0] * frustum[0][0] + frustum[0][1] * frustum[0][1] + frustum[0][2] * frustum[0][2]); frustum[0][0] *= rt; frustum[0][1] *= rt; frustum[0][2] *= rt; frustum[0][3] *= rt; /* Extract the numbers for the LEFT plane */ frustum[1][0] = clip.m[0][3] + clip.m[0][0]; frustum[1][1] = clip.m[1][3] + clip.m[1][0]; frustum[1][2] = clip.m[2][3] + clip.m[2][0]; frustum[1][3] = clip.m[3][3] + clip.m[3][0]; /* Normalize the result */ rt = 1.0f / sqrt(frustum[1][0] * frustum[1][0] + frustum[1][1] * frustum[1][1] + frustum[1][2] * frustum[1][2]); frustum[1][0] *= rt; frustum[1][1] *= rt; frustum[1][2] *= rt; frustum[1][3] *= rt; /* Extract the BOTTOM plane */ frustum[2][0] = clip.m[0][3] + clip.m[0][1]; frustum[2][1] = clip.m[1][3] + clip.m[1][1]; frustum[2][2] = clip.m[2][3] + clip.m[2][1]; frustum[2][3] = clip.m[3][3] + clip.m[3][1]; /* Normalize the result */ rt = 1.0 / sqrt(frustum[2][0] * frustum[2][0] + frustum[2][1] * frustum[2][1] + frustum[2][2] * frustum[2][2]); frustum[2][0] *= rt; frustum[2][1] *= rt; frustum[2][2] *= rt; frustum[2][3] *= rt; /* Extract the TOP plane */ frustum[3][0] = clip.m[0][3] - clip.m[0][1]; frustum[3][1] = clip.m[1][3] - clip.m[1][1]; frustum[3][2] = clip.m[2][3] - clip.m[2][1]; frustum[3][3] = clip.m[3][3] - clip.m[3][1]; /* Normalize the result */ rt = 1.0 / sqrt(frustum[3][0] * frustum[3][0] + frustum[3][1] * frustum[3][1] + frustum[3][2] * frustum[3][2]); frustum[3][0] *= rt; frustum[3][1] *= rt; frustum[3][2] *= rt; frustum[3][3] *= rt; /* Extract the FAR plane */ frustum[4][0] = clip.m[0][3] - clip.m[0][2]; frustum[4][1] = clip.m[1][3] - clip.m[1][2]; frustum[4][2] = clip.m[2][3] - clip.m[2][2]; frustum[4][3] = clip.m[3][3] - clip.m[3][2]; /* Normalize the result */ rt = sqrt(frustum[4][0] * frustum[4][0] + frustum[4][1] * frustum[4][1] + frustum[4][2] * frustum[4][2]); frustum[4][0] *= rt; frustum[4][1] *= rt; frustum[4][2] *= rt; frustum[4][3] *= rt; /* Extract the NEAR plane */ frustum[5][0] = clip.m[0][3] + clip.m[0][2]; frustum[5][1] = clip.m[1][3] + clip.m[1][2]; frustum[5][2] = clip.m[2][3] + clip.m[2][2]; frustum[5][3] = clip.m[3][3] + clip.m[3][2]; /* Normalize the result */ rt = sqrt(frustum[5][0] * frustum[5][0] + frustum[5][1] * frustum[5][1] + frustum[5][2] * frustum[5][2]); frustum[5][0] *= rt; frustum[5][1] *= rt; frustum[5][2] *= rt; frustum[5][3] *= rt; } - (BOOL) viewFrustumIntersectsSphereAt:(Vector)position withRadius:(GLfloat)radius { // position is the relative position between the camera and the object int p; for (p = 0; p < 6; p++) { if (frustum[p][0] * position.x + frustum[p][1] * position.y + frustum[p][2] * position.z + frustum[p][3] <= -radius) { return NO; } } return YES; } - (void) drawUniverse { OOLog(@"universe.profile.draw",@"Begin draw"); if (!no_update) { @try { no_update = YES; // block other attempts to draw int i, v_status, vdist; Vector view_dir, view_up; OOMatrix view_matrix; int ent_count = n_entities; Entity *my_entities[ent_count]; int draw_count = 0; PlayerEntity *player = PLAYER; Entity *drawthing = nil; BOOL demoShipMode = [player showDemoShips]; NSSize viewSize = [gameView viewSize]; float aspect = viewSize.height/viewSize.width; if (!displayGUI && wasDisplayGUI) { // reset light1 position for the shaders if (cachedSun) [UNIVERSE setMainLightPosition:HPVectorToVector([cachedSun position])]; // the main light is the sun. else [UNIVERSE setMainLightPosition:kZeroVector]; } wasDisplayGUI = displayGUI; // use a non-mutable copy so this can't be changed under us. for (i = 0; i < ent_count; i++) { /* BUG: this list is ordered nearest to furthest from * the player, and we just assume that the camera is * on/near the player. So long as everything uses * depth tests, we'll get away with it; it'll just * occasionally be inefficient. - CIM */ Entity *e = sortedEntities[i]; // ordered NEAREST -> FURTHEST AWAY if ([e isVisible]) { my_entities[draw_count++] = [[e retain] autorelease]; } } v_status = [player status]; OOCheckOpenGLErrors(@"Universe before doing anything"); OOSetOpenGLState(OPENGL_STATE_OPAQUE); // FIXME: should be redundant. OOGL(glClear(GL_COLOR_BUFFER_BIT)); if (!displayGUI) { OOGL(glClearColor(skyClearColor[0], skyClearColor[1], skyClearColor[2], skyClearColor[3])); } else { OOGL(glClearColor(0.0, 0.0, 0.0, 0.0)); // If set, display background GUI image. Must be done before enabling lights to avoid dim backgrounds OOGLResetProjection(); OOGLFrustum(-0.5, 0.5, -aspect*0.5, aspect*0.5, 1.0, MAX_CLEAR_DEPTH); [gui drawGUIBackground]; } BOOL fogging, bpHide = [self breakPatternHide]; for (vdist=0;vdist<=1;vdist++) { float nearPlane = vdist ? 1.0 : INTERMEDIATE_CLEAR_DEPTH; float farPlane = vdist ? INTERMEDIATE_CLEAR_DEPTH : MAX_CLEAR_DEPTH; float ratio = (displayGUI ? 0.5 : [gameView fov:YES]) * nearPlane; // 0.5 is field of view ratio for GUIs OOGLResetProjection(); if ((displayGUI && 4*aspect >= 3) || (!displayGUI && 4*aspect <= 3)) { OOGLFrustum(-ratio, ratio, -aspect*ratio, aspect*ratio, nearPlane, farPlane); } else { OOGLFrustum(-3*ratio/aspect/4, 3*ratio/aspect/4, -3*ratio/4, 3*ratio/4, nearPlane, farPlane); } [self getActiveViewMatrix:&view_matrix forwardVector:&view_dir upVector:&view_up]; OOGLResetModelView(); // reset matrix OOGLLookAt(kZeroVector, kBasisZVector, kBasisYVector); // HACK BUSTED OOGLMultModelView(OOMatrixForScale(-1.0,1.0,1.0)); // flip left and right OOGLPushModelView(); // save this flat viewpoint /* OpenGL viewpoints: * * Oolite used to transform the viewpoint by the inverse of the * view position, and then transform the objects by the inverse * of their position, to get the correct view. However, as * OpenGL only uses single-precision floats, this causes * noticeable display inaccuracies relatively close to the * origin. * * Instead, we now calculate the difference between the view * position and the object using high-precision vectors, convert * the difference to a low-precision vector (since if you can * see it, it's close enough for the loss of precision not to * matter) and use that relative vector for the OpenGL transform * * Objects which reset the view matrix in their display need to be * handled a little more carefully than before. */ OOSetOpenGLState(OPENGL_STATE_OPAQUE); // clearing the depth buffer waits until we've set // STATE_OPAQUE so that depth writes are definitely // available. OOGL(glClear(GL_DEPTH_BUFFER_BIT)); // Set up view transformation matrix OOMatrix flipMatrix = kIdentityMatrix; flipMatrix.m[2][2] = -1; view_matrix = OOMatrixMultiply(view_matrix, flipMatrix); Vector viewOffset = [player viewpointOffset]; OOGLLookAt(view_dir, kZeroVector, view_up); if (EXPECT(!displayGUI || demoShipMode)) { if (EXPECT(!demoShipMode)) // we're in flight { // rotate the view OOGLMultModelView([player rotationMatrix]); // translate the view // HPVect: camera-relative position OOGL(glLightModelfv(GL_LIGHT_MODEL_AMBIENT, stars_ambient)); // main light position, no shaders, in-flight / shaders, in-flight and docked. if (cachedSun) { [self setMainLightPosition:[cachedSun cameraRelativePosition]]; } OOGL(glLightfv(GL_LIGHT1, GL_POSITION, main_light_position)); } else { OOGL(glLightModelfv(GL_LIGHT_MODEL_AMBIENT, docked_light_ambient)); // main_light_position no shaders, docked/GUI. OOGL(glLightfv(GL_LIGHT0, GL_POSITION, main_light_position)); // main light position, no shaders, in-flight / shaders, in-flight and docked. OOGL(glLightfv(GL_LIGHT1, GL_POSITION, main_light_position)); } OOGL([self useGUILightSource:demoShipMode]); // HACK: store view matrix for absolute drawing of active subentities (i.e., turrets, flashers). viewMatrix = OOGLGetModelView(); int furthest = draw_count - 1; int nearest = 0; BOOL inAtmosphere = airResistanceFactor > 0.01; GLfloat fogFactor = 0.5 / airResistanceFactor; double fog_scale, half_scale; GLfloat flat_ambdiff[4] = {1.0, 1.0, 1.0, 1.0}; // for alpha GLfloat mat_no[4] = {0.0, 0.0, 0.0, 1.0}; // nothing OOGL(glHint(GL_FOG_HINT, [self reducedDetail] ? GL_FASTEST : GL_NICEST)); [self defineFrustum]; // camera is set up for this frame OOVerifyOpenGLState(); OOCheckOpenGLErrors(@"Universe after setting up for opaque pass"); OOLog(@"universe.profile.draw",@"Begin opaque pass"); // DRAW ALL THE OPAQUE ENTITIES for (i = furthest; i >= nearest; i--) { drawthing = my_entities[i]; OOEntityStatus d_status = [drawthing status]; if (bpHide && !drawthing->isImmuneToBreakPatternHide) continue; if (vdist == 1 && [drawthing cameraRangeFront] > farPlane*1.5) continue; if (vdist == 0 && [drawthing cameraRangeBack] < nearPlane) continue; // if (vdist == 1 && [drawthing isPlanet]) continue; if (!((d_status == STATUS_COCKPIT_DISPLAY) ^ demoShipMode)) // either demo ship mode or in flight { // reset material properties // FIXME: should be part of SetState OOGL(glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, flat_ambdiff)); OOGL(glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, mat_no)); OOGLPushModelView(); if (EXPECT(drawthing != player)) { //translate the object // HPVect: camera relative [drawthing updateCameraRelativePosition]; OOGLTranslateModelView([drawthing cameraRelativePosition]); //rotate the object OOGLMultModelView([drawthing drawRotationMatrix]); } else { // Load transformation matrix OOGLLoadModelView(view_matrix); //translate the object from the viewpoint OOGLTranslateModelView(vector_flip(viewOffset)); } // atmospheric fog fogging = (inAtmosphere && ![drawthing isStellarObject]); if (fogging) { fog_scale = BILLBOARD_DEPTH * fogFactor; half_scale = fog_scale * 0.50; OOGL(glEnable(GL_FOG)); OOGL(glFogi(GL_FOG_MODE, GL_LINEAR)); OOGL(glFogfv(GL_FOG_COLOR, skyClearColor)); OOGL(glFogf(GL_FOG_START, half_scale)); OOGL(glFogf(GL_FOG_END, fog_scale)); } [self lightForEntity:demoShipMode || drawthing->isSunlit]; // draw the thing [drawthing drawImmediate:false translucent:false]; OOGLPopModelView(); // atmospheric fog if (fogging) { OOGL(glDisable(GL_FOG)); } } if (!((d_status == STATUS_COCKPIT_DISPLAY) ^ demoShipMode)) // either in flight or in demo ship mode { OOGLPushModelView(); if (EXPECT(drawthing != player)) { //translate the object // HPVect: camera relative positions [drawthing updateCameraRelativePosition]; OOGLTranslateModelView([drawthing cameraRelativePosition]); //rotate the object OOGLMultModelView([drawthing drawRotationMatrix]); } else { // Load transformation matrix OOGLLoadModelView(view_matrix); //translate the object from the viewpoint OOGLTranslateModelView(vector_flip(viewOffset)); } // experimental - atmospheric fog fogging = (inAtmosphere && ![drawthing isStellarObject]); if (fogging) { fog_scale = BILLBOARD_DEPTH * fogFactor; half_scale = fog_scale * 0.50; OOGL(glEnable(GL_FOG)); OOGL(glFogi(GL_FOG_MODE, GL_LINEAR)); OOGL(glFogfv(GL_FOG_COLOR, skyClearColor)); OOGL(glFogf(GL_FOG_START, half_scale)); OOGL(glFogf(GL_FOG_END, fog_scale)); } // draw the thing [drawthing drawImmediate:false translucent:true]; // atmospheric fog if (fogging) { OOGL(glDisable(GL_FOG)); } OOGLPopModelView(); } } } OOGLPopModelView(); } /* Reset for HUD drawing */ OOGLResetProjection(); OOGLFrustum(-0.5, 0.5, -aspect*0.5, aspect*0.5, 1.0, MAX_CLEAR_DEPTH); OOCheckOpenGLErrors(@"Universe after drawing entities"); OOLog(@"universe.profile.draw",@"Begin HUD"); OOSetOpenGLState(OPENGL_STATE_OVERLAY); // FIXME: should be redundant. if (EXPECT(!displayGUI || demoShipMode)) { if (!bpHide && cachedSun) { [cachedSun drawDirectVisionSunGlare]; [cachedSun drawStarGlare]; } } GLfloat lineWidth = [gameView viewSize].width / 1024.0; // restore line size if (lineWidth < 1.0) lineWidth = 1.0; if (lineWidth > 1.5) lineWidth = 1.5; // don't overscale; think of ultra-wide screen setups OOGL(GLScaledLineWidth(lineWidth)); [self drawMessage]; HeadUpDisplay *theHUD = [player hud]; // If the HUD has a non-nil deferred name string, it means that a HUD switch was requested while it was being rendered. // If so, execute the deferred HUD switch now - Nikos 20110628 if ([theHUD deferredHudName] != nil) { NSString *deferredName = [[theHUD deferredHudName] retain]; [player switchHudTo:deferredName]; [deferredName release]; theHUD = [player hud]; // HUD has been changed, so point to its new address } // Hiding HUD: has been a regular - non-debug - feature as of r2749, about 2 yrs ago! --Kaks 2011.10.14 static float sPrevHudAlpha = -1.0f; if ([theHUD isHidden]) { if (sPrevHudAlpha < 0.0f) { sPrevHudAlpha = [theHUD overallAlpha]; } [theHUD setOverallAlpha:0.0f]; } else if (sPrevHudAlpha >= 0.0f) { [theHUD setOverallAlpha:sPrevHudAlpha]; sPrevHudAlpha = -1.0f; } switch (v_status) { case STATUS_DEAD: case STATUS_ESCAPE_SEQUENCE: case STATUS_START_GAME: // no HUD rendering in these modes break; default: switch ([player guiScreen]) { case GUI_SCREEN_KEYBOARD: // no HUD rendering on this screen break; default: [theHUD setLineWidth:lineWidth]; [theHUD renderHUD]; } } #if (defined (SNAPSHOT_BUILD) && defined (OOLITE_SNAPSHOT_VERSION)) [self drawWatermarkString:@"Development version " @OOLITE_SNAPSHOT_VERSION]; #endif OOCheckOpenGLErrors(@"Universe after drawing HUD"); OOGL(glFlush()); // don't wait around for drawing to complete no_update = NO; // allow other attempts to draw // frame complete, when it is time to update the fps_counter, updateClocks:delta_t // in PlayerEntity.m will take care of resetting the processed frames number to 0. if (![[self gameController] isGamePaused]) { framesDoneThisUpdate++; } } @catch (NSException *exception) { no_update = NO; // make sure we don't get stuck in all subsequent frames. if ([[exception name] hasPrefix:@"Oolite"]) { [self handleOoliteException:exception]; } else { OOLog(kOOLogException, @"***** Exception: %@ : %@ *****",[exception name], [exception reason]); @throw exception; } } } OOLog(@"universe.profile.draw",@"End drawing"); } - (int) framesDoneThisUpdate { return framesDoneThisUpdate; } - (void) resetFramesDoneThisUpdate { framesDoneThisUpdate = 0; } - (OOMatrix) viewMatrix { return viewMatrix; } - (void) drawMessage { OOSetOpenGLState(OPENGL_STATE_OVERLAY); OOGL(glDisable(GL_TEXTURE_2D)); // for background sheets float overallAlpha = [[PLAYER hud] overallAlpha]; if (displayGUI) { if ([[self gameController] mouseInteractionMode] == MOUSE_MODE_UI_SCREEN_WITH_INTERACTION) { cursor_row = [gui drawGUI:1.0 drawCursor:YES]; } else { [gui drawGUI:1.0 drawCursor:NO]; } } [message_gui drawGUI:[message_gui alpha] * overallAlpha drawCursor:NO]; [comm_log_gui drawGUI:[comm_log_gui alpha] * overallAlpha drawCursor:NO]; OOVerifyOpenGLState(); } - (void) drawWatermarkString:(NSString *) watermarkString { NSSize watermarkStringSize = OORectFromString(watermarkString, 0.0f, 0.0f, NSMakeSize(10, 10)).size; OOGL(glColor4f(0.0, 1.0, 0.0, 1.0)); // position the watermark string on the top right hand corner of the game window and right-align it OODrawString(watermarkString, MAIN_GUI_PIXEL_WIDTH / 2 - watermarkStringSize.width + 80, MAIN_GUI_PIXEL_HEIGHT / 2 - watermarkStringSize.height, [gameView display_z], NSMakeSize(10,10)); } - (id)entityForUniversalID:(OOUniversalID)u_id { if (u_id == 100) return PLAYER; // the player if (MAX_ENTITY_UID < u_id) { OOLog(@"universe.badUID", @"Attempt to retrieve entity for out-of-range UID %u. (This is an internal programming error, please report it.)", u_id); return nil; } if ((u_id == NO_TARGET)||(!entity_for_uid[u_id])) return nil; Entity *ent = entity_for_uid[u_id]; if ([ent isEffect]) // effects SHOULD NOT HAVE U_IDs! { return nil; } if ([ent status] == STATUS_DEAD || [ent status] == STATUS_DOCKED) { return nil; } return ent; } static BOOL MaintainLinkedLists(Universe *uni) { NSCParameterAssert(uni != NULL); BOOL result = YES; // DEBUG check for loops and short lists if (uni->n_entities > 0) { int n; Entity *checkEnt, *last; last = nil; n = uni->n_entities; checkEnt = uni->x_list_start; while ((n--)&&(checkEnt)) { last = checkEnt; checkEnt = checkEnt->x_next; } if ((checkEnt)||(n > 0)) { OOExtraLog(kOOLogEntityVerificationError, @"Broken x_next %@ list (%d) ***", uni->x_list_start, n); result = NO; } n = uni->n_entities; checkEnt = last; while ((n--)&&(checkEnt)) checkEnt = checkEnt->x_previous; if ((checkEnt)||(n > 0)) { OOExtraLog(kOOLogEntityVerificationError, @"Broken x_previous %@ list (%d) ***", uni->x_list_start, n); if (result) { OOExtraLog(kOOLogEntityVerificationRebuild, @"REBUILDING x_previous list from x_next list"); checkEnt = uni->x_list_start; checkEnt->x_previous = nil; while (checkEnt->x_next) { last = checkEnt; checkEnt = checkEnt->x_next; checkEnt->x_previous = last; } } } n = uni->n_entities; checkEnt = uni->y_list_start; while ((n--)&&(checkEnt)) { last = checkEnt; checkEnt = checkEnt->y_next; } if ((checkEnt)||(n > 0)) { OOExtraLog(kOOLogEntityVerificationError, @"Broken *** broken y_next %@ list (%d) ***", uni->y_list_start, n); result = NO; } n = uni->n_entities; checkEnt = last; while ((n--)&&(checkEnt)) checkEnt = checkEnt->y_previous; if ((checkEnt)||(n > 0)) { OOExtraLog(kOOLogEntityVerificationError, @"Broken y_previous %@ list (%d) ***", uni->y_list_start, n); if (result) { OOExtraLog(kOOLogEntityVerificationRebuild, @"REBUILDING y_previous list from y_next list"); checkEnt = uni->y_list_start; checkEnt->y_previous = nil; while (checkEnt->y_next) { last = checkEnt; checkEnt = checkEnt->y_next; checkEnt->y_previous = last; } } } n = uni->n_entities; checkEnt = uni->z_list_start; while ((n--)&&(checkEnt)) { last = checkEnt; checkEnt = checkEnt->z_next; } if ((checkEnt)||(n > 0)) { OOExtraLog(kOOLogEntityVerificationError, @"Broken z_next %@ list (%d) ***", uni->z_list_start, n); result = NO; } n = uni->n_entities; checkEnt = last; while ((n--)&&(checkEnt)) checkEnt = checkEnt->z_previous; if ((checkEnt)||(n > 0)) { OOExtraLog(kOOLogEntityVerificationError, @"Broken z_previous %@ list (%d) ***", uni->z_list_start, n); if (result) { OOExtraLog(kOOLogEntityVerificationRebuild, @"REBUILDING z_previous list from z_next list"); checkEnt = uni->z_list_start; NSCAssert(checkEnt != nil, @"Expected z-list to be non-empty."); // Previously an implicit assumption. -- Ahruman 2011-01-25 checkEnt->z_previous = nil; while (checkEnt->z_next) { last = checkEnt; checkEnt = checkEnt->z_next; checkEnt->z_previous = last; } } } } if (!result) { OOExtraLog(kOOLogEntityVerificationRebuild, @"Rebuilding all linked lists from scratch"); NSArray *allEntities = uni->entities; uni->x_list_start = nil; uni->y_list_start = nil; uni->z_list_start = nil; Entity *ent = nil; foreach (ent, allEntities) { ent->x_next = nil; ent->x_previous = nil; ent->y_next = nil; ent->y_previous = nil; ent->z_next = nil; ent->z_previous = nil; [ent addToLinkedLists]; } } return result; } - (BOOL) addEntity:(Entity *) entity { if (entity) { ShipEntity *se = nil; OOVisualEffectEntity *ve = nil; OOWaypointEntity *wp = nil; if (![entity validForAddToUniverse]) return NO; // don't add things twice! if ([entities containsObject:entity]) return YES; if (n_entities >= UNIVERSE_MAX_ENTITIES - 1) { // throw an exception here... OOLog(@"universe.addEntity.failed", @"***** Universe cannot addEntity:%@ -- Universe is full (%d entities out of %d)", entity, n_entities, UNIVERSE_MAX_ENTITIES); #ifndef NDEBUG if (OOLogWillDisplayMessagesInClass(@"universe.maxEntitiesDump")) [self debugDumpEntities]; #endif return NO; } if (![entity isEffect]) { unsigned limiter = UNIVERSE_MAX_ENTITIES; while (entity_for_uid[next_universal_id] != nil) // skip allocated numbers { next_universal_id++; // increment keeps idkeys unique if (next_universal_id >= MAX_ENTITY_UID) { next_universal_id = MIN_ENTITY_UID; } if (limiter-- == 0) { // Every slot has been tried! This should not happen due to previous test, but there was a problem here in 1.70. OOLog(@"universe.addEntity.failed", @"***** Universe cannot addEntity:%@ -- Could not find free slot for entity.", entity); return NO; } } [entity setUniversalID:next_universal_id]; entity_for_uid[next_universal_id] = entity; if ([entity isShip]) { se = (ShipEntity *)entity; if ([se isBeacon]) { [self setNextBeacon:se]; } if ([se isStation]) { // check if it is a proper rotating station (ie. roles contains the word "station") if ([(StationEntity*)se isRotatingStation]) { double stationRoll = 0.0; // check for station_roll override id definedRoll = [[se shipInfoDictionary] objectForKey:@"station_roll"]; if (definedRoll != nil) { stationRoll = OODoubleFromObject(definedRoll, stationRoll); } else { stationRoll = [[self currentSystemData] oo_doubleForKey:@"station_roll" defaultValue:STANDARD_STATION_ROLL]; } [se setRoll: stationRoll]; } else { [se setRoll: 0.0]; } [(StationEntity *)se setPlanet:[self planet]]; if ([se maxFlightSpeed] > 0) se->isExplicitlyNotMainStation = YES; // we never want carriers to become main stations. } // stations used to have STATUS_ACTIVE, they're all STATUS_IN_FLIGHT now. if ([se status] != STATUS_COCKPIT_DISPLAY) { [se setStatus:STATUS_IN_FLIGHT]; } } } else { [entity setUniversalID:NO_TARGET]; if ([entity isVisualEffect]) { ve = (OOVisualEffectEntity *)entity; if ([ve isBeacon]) { [self setNextBeacon:ve]; } } else if ([entity isWaypoint]) { wp = (OOWaypointEntity *)entity; if ([wp isBeacon]) { [self setNextBeacon:wp]; } } } // lighting considerations entity->isSunlit = YES; entity->shadingEntityID = NO_TARGET; // add it to the universe [entities addObject:entity]; [entity wasAddedToUniverse]; // maintain sorted list (and for the scanner relative position) HPVector entity_pos = entity->position; HPVector delta = HPvector_between(entity_pos, PLAYER->position); double z_distance = HPmagnitude2(delta); entity->zero_distance = z_distance; unsigned index = n_entities; sortedEntities[index] = entity; entity->zero_index = index; while ((index > 0)&&(z_distance < sortedEntities[index - 1]->zero_distance)) // bubble into place { sortedEntities[index] = sortedEntities[index - 1]; sortedEntities[index]->zero_index = index; index--; sortedEntities[index] = entity; entity->zero_index = index; } // increase n_entities... n_entities++; // add entity to linked lists [entity addToLinkedLists]; // position and universe have been set - so we can do this if ([entity canCollide]) // filter only collidables disappearing { doLinkedListMaintenanceThisUpdate = YES; } if ([entity isWormhole]) { [activeWormholes addObject:entity]; } else if ([entity isPlanet]) { [allPlanets addObject:entity]; } else if ([entity isShip]) { [[se getAI] setOwner:se]; [[se getAI] setState:@"GLOBAL"]; if ([entity isStation]) { [allStations addObject:entity]; } } return YES; } return NO; } - (BOOL) removeEntity:(Entity *) entity { if (entity != nil && ![entity isPlayer]) { /* Ensure entity won't actually be dealloced until the end of this update (or the next update if none is in progress), because there may be things pointing to it but not retaining it. */ [entitiesDeadThisUpdate addObject:entity]; if ([entity isStation]) { [allStations removeObject:entity]; if ([PLAYER getTargetDockStation] == entity) { [PLAYER setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE]; } } return [self doRemoveEntity:entity]; } return NO; } - (void) ensureEntityReallyRemoved:(Entity *)entity { if ([entity universalID] != NO_TARGET) { OOLog(@"universe.unremovedEntity", @"Entity %@ dealloced without being removed from universe! (This is an internal programming error, please report it.)", entity); [self doRemoveEntity:entity]; } } - (void) removeAllEntitiesExceptPlayer { BOOL updating = no_update; no_update = YES; // no drawing while we do this! #ifndef NDEBUG Entity* p0 = [entities objectAtIndex:0]; if (!(p0->isPlayer)) { OOLog(kOOLogInconsistentState, @"***** First entity is not the player in Universe.removeAllEntitiesExceptPlayer - exiting."); exit(EXIT_FAILURE); } #endif // preserve wormholes NSMutableArray *savedWormholes = [activeWormholes mutableCopy]; while ([entities count] > 1) { Entity* ent = [entities objectAtIndex:1]; if (ent->isStation) // clear out queues [(StationEntity *)ent clear]; [self removeEntity:ent]; } [activeWormholes release]; activeWormholes = savedWormholes; // will be cleared out by populateSpaceFromActiveWormholes // maintain sorted list n_entities = 1; cachedSun = nil; cachedPlanet = nil; cachedStation = nil; [closeSystems release]; closeSystems = nil; [self resetBeacons]; [waypoints removeAllObjects]; no_update = updating; // restore drawing } - (void) removeDemoShips { int i; int ent_count = n_entities; if (ent_count > 0) { Entity* ent; for (i = 0; i < ent_count; i++) { ent = sortedEntities[i]; if ([ent status] == STATUS_COCKPIT_DISPLAY && ![ent isPlayer]) { [self removeEntity:ent]; } } } demo_ship = nil; } - (ShipEntity *) makeDemoShipWithRole:(NSString *)role spinning:(BOOL)spinning { if ([PLAYER dockedStation] == nil) return nil; [self removeDemoShips]; // get rid of any pre-existing models on display [PLAYER setShowDemoShips: YES]; Quaternion q2 = { (GLfloat)M_SQRT1_2, (GLfloat)M_SQRT1_2, (GLfloat)0.0, (GLfloat)0.0 }; ShipEntity *ship = [self newShipWithRole:role]; // retain count = 1 if (ship) { double cr = [ship collisionRadius]; [ship setOrientation:q2]; [ship setPositionX:0.0f y:0.0f z:3.6f * cr]; [ship setScanClass:CLASS_NO_DRAW]; [ship switchAITo:@"nullAI.plist"]; [ship setPendingEscortCount:0]; [UNIVERSE addEntity:ship]; // STATUS_IN_FLIGHT, AI state GLOBAL if (spinning) { [ship setRoll:M_PI/5.0]; // roll must be set after addEntity or stations will not roll in demo. [ship setPitch:M_PI/10.0]; } [ship setStatus:STATUS_COCKPIT_DISPLAY]; // stop problems on the ship library screen // demo ships shouldn't have this equipment [ship removeEquipmentItem:@"EQ_SHIELD_BOOSTER"]; [ship removeEquipmentItem:@"EQ_SHIELD_ENHANCER"]; } return [ship autorelease]; } - (BOOL) isVectorClearFromEntity:(Entity *) e1 toDistance:(double)dist fromPoint:(HPVector) p2 { if (!e1) return NO; HPVector f1; HPVector p1 = e1->position; HPVector v1 = p2; v1.x -= p1.x; v1.y -= p1.y; v1.z -= p1.z; // vector from entity to p2 double nearest = sqrt(v1.x*v1.x + v1.y*v1.y + v1.z*v1.z) - dist; // length of vector if (nearest < 0.0) return YES; // within range already! int i; int ent_count = n_entities; Entity* my_entities[ent_count]; for (i = 0; i < ent_count; i++) my_entities[i] = [sortedEntities[i] retain]; // retained if (v1.x || v1.y || v1.z) f1 = HPvector_normal(v1); // unit vector in direction of p2 from p1 else f1 = make_HPvector(0, 0, 1); for (i = 0; i < ent_count ; i++) { Entity *e2 = my_entities[i]; if ((e2 != e1)&&([e2 canCollide])) { HPVector epos = e2->position; epos.x -= p1.x; epos.y -= p1.y; epos.z -= p1.z; // epos now holds vector from p1 to this entities position double d_forward = HPdot_product(epos,f1); // distance along f1 which is nearest to e2's position if ((d_forward > 0)&&(d_forward < nearest)) { double cr = 1.10 * (e2->collision_radius + e1->collision_radius); // 10% safety margin HPVector p0 = e1->position; p0.x += d_forward * f1.x; p0.y += d_forward * f1.y; p0.z += d_forward * f1.z; // p0 holds nearest point on current course to center of incident object HPVector epos = e2->position; p0.x -= epos.x; p0.y -= epos.y; p0.z -= epos.z; // compare with center of incident object double dist2 = p0.x * p0.x + p0.y * p0.y + p0.z * p0.z; if (dist2 < cr*cr) { for (i = 0; i < ent_count; i++) [my_entities[i] release]; // released return NO; } } } } for (i = 0; i < ent_count; i++) [my_entities[i] release]; // released return YES; } - (Entity*) hazardOnRouteFromEntity:(Entity *) e1 toDistance:(double)dist fromPoint:(HPVector) p2 { if (!e1) return nil; HPVector f1; HPVector p1 = e1->position; HPVector v1 = p2; v1.x -= p1.x; v1.y -= p1.y; v1.z -= p1.z; // vector from entity to p2 double nearest = HPmagnitude(v1) - dist; // length of vector if (nearest < 0.0) return nil; // within range already! Entity* result = nil; int i; int ent_count = n_entities; Entity* my_entities[ent_count]; for (i = 0; i < ent_count; i++) my_entities[i] = [sortedEntities[i] retain]; // retained if (v1.x || v1.y || v1.z) f1 = HPvector_normal(v1); // unit vector in direction of p2 from p1 else f1 = make_HPvector(0, 0, 1); for (i = 0; (i < ent_count) && (!result) ; i++) { Entity *e2 = my_entities[i]; if ((e2 != e1)&&([e2 canCollide])) { HPVector epos = e2->position; epos.x -= p1.x; epos.y -= p1.y; epos.z -= p1.z; // epos now holds vector from p1 to this entities position double d_forward = HPdot_product(epos,f1); // distance along f1 which is nearest to e2's position if ((d_forward > 0)&&(d_forward < nearest)) { double cr = 1.10 * (e2->collision_radius + e1->collision_radius); // 10% safety margin HPVector p0 = e1->position; p0.x += d_forward * f1.x; p0.y += d_forward * f1.y; p0.z += d_forward * f1.z; // p0 holds nearest point on current course to center of incident object HPVector epos = e2->position; p0.x -= epos.x; p0.y -= epos.y; p0.z -= epos.z; // compare with center of incident object double dist2 = HPmagnitude2(p0); if (dist2 < cr*cr) result = e2; } } } for (i = 0; i < ent_count; i++) [my_entities[i] release]; // released return result; } - (HPVector) getSafeVectorFromEntity:(Entity *) e1 toDistance:(double)dist fromPoint:(HPVector) p2 { // heuristic three if (!e1) { OOLog(kOOLogParameterError, @"***** No entity set in Universe getSafeVectorFromEntity:toDistance:fromPoint:"); return kZeroHPVector; } HPVector f1; HPVector result = p2; int i; int ent_count = n_entities; Entity* my_entities[ent_count]; for (i = 0; i < ent_count; i++) my_entities[i] = [sortedEntities[i] retain]; // retained HPVector p1 = e1->position; HPVector v1 = p2; v1.x -= p1.x; v1.y -= p1.y; v1.z -= p1.z; // vector from entity to p2 double nearest = sqrt(v1.x*v1.x + v1.y*v1.y + v1.z*v1.z) - dist; // length of vector if (v1.x || v1.y || v1.z) f1 = HPvector_normal(v1); // unit vector in direction of p2 from p1 else f1 = make_HPvector(0, 0, 1); for (i = 0; i < ent_count; i++) { Entity *e2 = my_entities[i]; if ((e2 != e1)&&([e2 canCollide])) { HPVector epos = e2->position; epos.x -= p1.x; epos.y -= p1.y; epos.z -= p1.z; double d_forward = HPdot_product(epos,f1); if ((d_forward > 0)&&(d_forward < nearest)) { double cr = 1.20 * (e2->collision_radius + e1->collision_radius); // 20% safety margin HPVector p0 = e1->position; p0.x += d_forward * f1.x; p0.y += d_forward * f1.y; p0.z += d_forward * f1.z; // p0 holds nearest point on current course to center of incident object HPVector epos = e2->position; p0.x -= epos.x; p0.y -= epos.y; p0.z -= epos.z; // compare with center of incident object double dist2 = p0.x * p0.x + p0.y * p0.y + p0.z * p0.z; if (dist2 < cr*cr) { result = e2->position; // center of incident object nearest = d_forward; if (dist2 == 0.0) { // ie. we're on a line through the object's center ! // jitter the position somewhat! result.x += ((int)(Ranrot() % 1024) - 512)/512.0; // -1.0 .. +1.0 result.y += ((int)(Ranrot() % 1024) - 512)/512.0; // -1.0 .. +1.0 result.z += ((int)(Ranrot() % 1024) - 512)/512.0; // -1.0 .. +1.0 } HPVector nearest_point = p1; nearest_point.x += d_forward * f1.x; nearest_point.y += d_forward * f1.y; nearest_point.z += d_forward * f1.z; // nearest point now holds nearest point on line to center of incident object HPVector outward = nearest_point; outward.x -= result.x; outward.y -= result.y; outward.z -= result.z; if (outward.x||outward.y||outward.z) outward = HPvector_normal(outward); else outward.y = 1.0; // outward holds unit vector through the nearest point on the line from the center of incident object HPVector backward = p1; backward.x -= result.x; backward.y -= result.y; backward.z -= result.z; if (backward.x||backward.y||backward.z) backward = HPvector_normal(backward); else backward.z = -1.0; // backward holds unit vector from center of the incident object to the center of the ship HPVector dd = result; dd.x -= p1.x; dd.y -= p1.y; dd.z -= p1.z; double current_distance = HPmagnitude(dd); // sanity check current_distance if (current_distance < cr * 1.25) // 25% safety margin current_distance = cr * 1.25; if (current_distance > cr * 5.0) // up to 2 diameters away current_distance = cr * 5.0; // choose a point that's three parts backward and one part outward result.x += 0.25 * (outward.x * current_distance) + 0.75 * (backward.x * current_distance); // push 'out' by this amount result.y += 0.25 * (outward.y * current_distance) + 0.75 * (backward.y * current_distance); result.z += 0.25 * (outward.z * current_distance) + 0.75 * (backward.z * current_distance); } } } } for (i = 0; i < ent_count; i++) [my_entities[i] release]; // released return result; } - (ShipEntity*) addWreckageFrom:(ShipEntity *)ship withRole:(NSString *)wreckRole at:(HPVector)rpos scale:(GLfloat)scale lifetime:(GLfloat)lifetime { ShipEntity* wreck = [UNIVERSE newShipWithRole:wreckRole]; // retain count = 1 Quaternion q; if (wreck) { GLfloat expected_mass = 0.1f * [ship mass] * (0.75 + 0.5 * randf()); GLfloat wreck_mass = [wreck mass]; GLfloat scale_factor = powf(expected_mass / wreck_mass, 0.33333333f) * scale; // cube root of volume ratio [wreck rescaleBy: scale_factor]; [wreck setPosition:rpos]; [wreck setVelocity:[ship velocity]]; quaternion_set_random(&q); [wreck setOrientation:q]; [wreck setTemperature: 1000.0]; // take 1000e heat damage per second [wreck setHeatInsulation: 1.0e7]; // very large! so it won't cool down [wreck setEnergy: lifetime]; [wreck setIsWreckage:YES]; [UNIVERSE addEntity:wreck]; // STATUS_IN_FLIGHT, AI state GLOBAL [wreck performTumble]; // [wreck rescaleBy: 1.0/scale_factor]; [wreck release]; } return wreck; } - (void) addLaserHitEffectsAt:(HPVector)pos against:(ShipEntity *)target damage:(float)damage color:(OOColor *)color { // low energy, start getting small surface explosions if ([target showDamage] && [target energy] < [target maxEnergy]/2) { NSString *key = (randf() < 0.5) ? @"oolite-hull-spark" : @"oolite-hull-spark-b"; NSDictionary *settings = [UNIVERSE explosionSetting:key]; OOExplosionCloudEntity* burst = [OOExplosionCloudEntity explosionCloudFromEntity:target withSettings:settings]; [burst setPosition:pos]; [self addEntity: burst]; if ([target energy] * randf() < damage) { ShipEntity *wreck = [self addWreckageFrom:target withRole:@"oolite-wreckage-chunk" at:pos scale:0.05 lifetime:(125.0+(randf()*200.0))]; if (wreck) { Vector direction = HPVectorToVector(HPvector_normal(HPvector_subtract(pos,[target position]))); [wreck setVelocity:vector_add([wreck velocity],vector_multiply_scalar(direction,10+20*randf()))]; } } } else { [self addEntity:[OOFlashEffectEntity laserFlashWithPosition:pos velocity:[target velocity] color:color]]; } } - (ShipEntity *) firstShipHitByLaserFromShip:(ShipEntity *)srcEntity inDirection:(OOWeaponFacing)direction offset:(Vector)offset gettingRangeFound:(GLfloat *)range_ptr { if (srcEntity == nil) return nil; ShipEntity *hit_entity = nil; ShipEntity *hit_subentity = nil; HPVector p0 = [srcEntity position]; Quaternion q1 = [srcEntity normalOrientation]; ShipEntity *parent = [srcEntity parentEntity]; if (parent) { // we're a subentity! BoundingBox bbox = [srcEntity boundingBox]; HPVector midfrontplane = make_HPvector(0.5 * (bbox.max.x + bbox.min.x), 0.5 * (bbox.max.y + bbox.min.y), bbox.max.z); p0 = [srcEntity absolutePositionForSubentityOffset:midfrontplane]; q1 = [parent orientation]; if ([parent isPlayer]) q1.w = -q1.w; } double nearest = [srcEntity weaponRange]; int i; int ent_count = n_entities; int ship_count = 0; ShipEntity *my_entities[ent_count]; for (i = 0; i < ent_count; i++) { Entity* ent = sortedEntities[i]; if (ent != srcEntity && ent != parent && [ent isShip] && [ent canCollide]) { my_entities[ship_count++] = [(ShipEntity *)ent retain]; } } Vector u1, f1, r1; basis_vectors_from_quaternion(q1, &r1, &u1, &f1); p0 = HPvector_add(p0, vectorToHPVector(OOVectorMultiplyMatrix(offset, OOMatrixFromBasisVectors(r1, u1, f1)))); switch (direction) { case WEAPON_FACING_FORWARD: case WEAPON_FACING_NONE: break; case WEAPON_FACING_AFT: quaternion_rotate_about_axis(&q1, u1, M_PI); break; case WEAPON_FACING_PORT: quaternion_rotate_about_axis(&q1, u1, M_PI/2.0); break; case WEAPON_FACING_STARBOARD: quaternion_rotate_about_axis(&q1, u1, -M_PI/2.0); break; } basis_vectors_from_quaternion(q1, &r1, NULL, &f1); HPVector p1 = HPvector_add(p0, vectorToHPVector(vector_multiply_scalar(f1, nearest))); //endpoint for (i = 0; i < ship_count; i++) { ShipEntity *e2 = my_entities[i]; // check outermost bounding sphere GLfloat cr = e2->collision_radius; Vector rpos = HPVectorToVector(HPvector_subtract(e2->position, p0)); Vector v_off = make_vector(dot_product(rpos, r1), dot_product(rpos, u1), dot_product(rpos, f1)); if (v_off.z > 0.0 && v_off.z < nearest + cr && // ahead AND within range v_off.x < cr && v_off.x > -cr && v_off.y < cr && v_off.y > -cr && // AND not off to one side or another v_off.x * v_off.x + v_off.y * v_off.y < cr * cr) // AND not off to both sides { ShipEntity *entHit = nil; GLfloat hit = [(ShipEntity *)e2 doesHitLine:p0 :p1 :&entHit]; // octree detection if (hit > 0.0 && hit < nearest) { if ([entHit isSubEntity]) { hit_subentity = entHit; } hit_entity = e2; nearest = hit; p1 = HPvector_add(p0, vectorToHPVector(vector_multiply_scalar(f1, nearest))); } } } if (hit_entity) { // I think the above code does not guarantee that the closest hit_subentity belongs to the closest hit_entity. if (hit_subentity && [hit_subentity owner] == hit_entity) [hit_entity setSubEntityTakingDamage:hit_subentity]; if (range_ptr != NULL) { *range_ptr = nearest; } } for (i = 0; i < ship_count; i++) [my_entities[i] release]; // released return hit_entity; } - (Entity *) firstEntityTargetedByPlayer { PlayerEntity *player = PLAYER; Entity *hit_entity = nil; OOScalar nearest2 = SCANNER_MAX_RANGE - 100; // 100m shorter than range at which target is lost nearest2 *= nearest2; int i; int ent_count = n_entities; int ship_count = 0; Entity *my_entities[ent_count]; for (i = 0; i < ent_count; i++) { if (([sortedEntities[i] isShip] && ![sortedEntities[i] isPlayer]) || [sortedEntities[i] isWormhole]) { my_entities[ship_count++] = [sortedEntities[i] retain]; } } Quaternion q1 = [player normalOrientation]; Vector u1, f1, r1; basis_vectors_from_quaternion(q1, &r1, &u1, &f1); Vector offset = [player weaponViewOffset]; HPVector p1 = HPvector_add([player position], vectorToHPVector(OOVectorMultiplyMatrix(offset, OOMatrixFromBasisVectors(r1, u1, f1)))); // Note: deliberately tied to view direction, not weapon facing. All custom views count as forward for targeting. switch (viewDirection) { case VIEW_AFT : quaternion_rotate_about_axis(&q1, u1, M_PI); break; case VIEW_PORT : quaternion_rotate_about_axis(&q1, u1, 0.5 * M_PI); break; case VIEW_STARBOARD : quaternion_rotate_about_axis(&q1, u1, -0.5 * M_PI); break; default: break; } basis_vectors_from_quaternion(q1, &r1, NULL, &f1); for (i = 0; i < ship_count; i++) { Entity *e2 = my_entities[i]; if ([e2 canCollide] && [e2 scanClass] != CLASS_NO_DRAW) { Vector rp = HPVectorToVector(HPvector_subtract([e2 position], p1)); OOScalar dist2 = magnitude2(rp); if (dist2 < nearest2) { OOScalar df = dot_product(f1, rp); if (df > 0.0 && df * df < nearest2) { OOScalar du = dot_product(u1, rp); OOScalar dr = dot_product(r1, rp); OOScalar cr = [e2 collisionRadius]; if (du * du + dr * dr < cr * cr) { hit_entity = e2; nearest2 = dist2; } } } } } // check for MASC'M if (hit_entity != nil && [hit_entity isShip]) { ShipEntity* ship = (ShipEntity*)hit_entity; if ([ship isJammingScanning] && ![player hasMilitaryScannerFilter]) { hit_entity = nil; } } for (i = 0; i < ship_count; i++) { [my_entities[i] release]; } return hit_entity; } - (Entity *) firstEntityTargetedByPlayerPrecisely { OOWeaponFacing targetFacing; Vector laserPortOffset = kZeroVector; PlayerEntity *player = PLAYER; switch (viewDirection) { case VIEW_FORWARD: targetFacing = WEAPON_FACING_FORWARD; laserPortOffset = [player forwardWeaponOffset]; break; case VIEW_AFT: targetFacing = WEAPON_FACING_AFT; laserPortOffset = [player aftWeaponOffset]; break; case VIEW_PORT: targetFacing = WEAPON_FACING_PORT; laserPortOffset = [player portWeaponOffset]; break; case VIEW_STARBOARD: targetFacing = WEAPON_FACING_STARBOARD; laserPortOffset = [player starboardWeaponOffset]; break; default: // Match behaviour of -firstEntityTargetedByPlayer. targetFacing = WEAPON_FACING_FORWARD; laserPortOffset = [player forwardWeaponOffset]; } return [self firstShipHitByLaserFromShip:PLAYER inDirection:targetFacing offset:laserPortOffset gettingRangeFound:NULL]; } - (NSArray *) entitiesWithinRange:(double)range ofEntity:(Entity *)entity { if (entity == nil) return nil; return [self findShipsMatchingPredicate:YESPredicate parameter:NULL inRange:range ofEntity:entity]; } - (unsigned) countShipsWithRole:(NSString *)role inRange:(double)range ofEntity:(Entity *)entity { return [self countShipsMatchingPredicate:HasRolePredicate parameter:role inRange:range ofEntity:entity]; } - (unsigned) countShipsWithRole:(NSString *)role { return [self countShipsWithRole:role inRange:-1 ofEntity:nil]; } - (unsigned) countShipsWithPrimaryRole:(NSString *)role inRange:(double)range ofEntity:(Entity *)entity { return [self countShipsMatchingPredicate:HasPrimaryRolePredicate parameter:role inRange:range ofEntity:entity]; } - (unsigned) countShipsWithScanClass:(OOScanClass)scanClass inRange:(double)range ofEntity:(Entity *)entity { return [self countShipsMatchingPredicate:HasScanClassPredicate parameter:[NSNumber numberWithInt:scanClass] inRange:range ofEntity:entity]; } - (unsigned) countShipsWithPrimaryRole:(NSString *)role { return [self countShipsWithPrimaryRole:role inRange:-1 ofEntity:nil]; } - (void) sendShipsWithPrimaryRole:(NSString *)role messageToAI:(NSString *)ms { NSArray *targets = nil; targets = [self findShipsMatchingPredicate:HasPrimaryRolePredicate parameter:role inRange:-1 ofEntity:nil]; [targets makeObjectsPerformSelector:@selector(reactToMessage:) withObject:ms]; } - (unsigned) countEntitiesMatchingPredicate:(EntityFilterPredicate)predicate parameter:(void *)parameter inRange:(double)range ofEntity:(Entity *)e1 { unsigned i, found = 0; HPVector p1; double distance, cr; if (predicate == NULL) predicate = YESPredicate; if (e1 != nil) p1 = e1->position; else p1 = kZeroHPVector; for (i = 0; i < n_entities; i++) { Entity *e2 = sortedEntities[i]; if (e2 != e1 && predicate(e2, parameter)) { if (range < 0) distance = -1; // Negative range means infinity else { cr = range + e2->collision_radius; distance = HPdistance2(e2->position, p1) - cr * cr; } if (distance < 0) { found++; } } } return found; } - (unsigned) countShipsMatchingPredicate:(EntityFilterPredicate)predicate parameter:(void *)parameter inRange:(double)range ofEntity:(Entity *)entity { if (predicate != NULL) { BinaryOperationPredicateParameter param = { IsShipPredicate, NULL, predicate, parameter }; return [self countEntitiesMatchingPredicate:ANDPredicate parameter:¶m inRange:range ofEntity:entity]; } else { return [self countEntitiesMatchingPredicate:IsShipPredicate parameter:NULL inRange:range ofEntity:entity]; } } OOINLINE BOOL EntityInRange(HPVector p1, Entity *e2, float range) { if (range < 0) return YES; float cr = range + e2->collision_radius; return HPdistance2(e2->position,p1) < cr * cr; } // NOTE: OOJSSystem relies on this returning entities in distance-from-player order. // This can be easily changed by removing the [reference isPlayer] conditions in FindJSVisibleEntities(). - (NSMutableArray *) findEntitiesMatchingPredicate:(EntityFilterPredicate)predicate parameter:(void *)parameter inRange:(double)range ofEntity:(Entity *)e1 { OOJS_PROFILE_ENTER unsigned i; HPVector p1; NSMutableArray *result = nil; OOJSPauseTimeLimiter(); if (predicate == NULL) predicate = YESPredicate; result = [NSMutableArray arrayWithCapacity:n_entities]; if (e1 != nil) p1 = [e1 position]; else p1 = kZeroHPVector; for (i = 0; i < n_entities; i++) { Entity *e2 = sortedEntities[i]; if (e1 != e2 && EntityInRange(p1, e2, range) && predicate(e2, parameter)) { [result addObject:e2]; } } OOJSResumeTimeLimiter(); return result; OOJS_PROFILE_EXIT } - (id) findOneEntityMatchingPredicate:(EntityFilterPredicate)predicate parameter:(void *)parameter { unsigned i; Entity *candidate = nil; OOJSPauseTimeLimiter(); if (predicate == NULL) predicate = YESPredicate; for (i = 0; i < n_entities; i++) { candidate = sortedEntities[i]; if (predicate(candidate, parameter)) return candidate; } OOJSResumeTimeLimiter(); return nil; } - (NSMutableArray *) findShipsMatchingPredicate:(EntityFilterPredicate)predicate parameter:(void *)parameter inRange:(double)range ofEntity:(Entity *)entity { if (predicate != NULL) { BinaryOperationPredicateParameter param = { IsShipPredicate, NULL, predicate, parameter }; return [self findEntitiesMatchingPredicate:ANDPredicate parameter:¶m inRange:range ofEntity:entity]; } else { return [self findEntitiesMatchingPredicate:IsShipPredicate parameter:NULL inRange:range ofEntity:entity]; } } - (NSMutableArray *) findVisualEffectsMatchingPredicate:(EntityFilterPredicate)predicate parameter:(void *)parameter inRange:(double)range ofEntity:(Entity *)entity { if (predicate != NULL) { BinaryOperationPredicateParameter param = { IsVisualEffectPredicate, NULL, predicate, parameter }; return [self findEntitiesMatchingPredicate:ANDPredicate parameter:¶m inRange:range ofEntity:entity]; } else { return [self findEntitiesMatchingPredicate:IsVisualEffectPredicate parameter:NULL inRange:range ofEntity:entity]; } } - (id) nearestEntityMatchingPredicate:(EntityFilterPredicate)predicate parameter:(void *)parameter relativeToEntity:(Entity *)entity { unsigned i; HPVector p1; float rangeSq = INFINITY; id result = nil; if (predicate == NULL) predicate = YESPredicate; if (entity != nil) p1 = [entity position]; else p1 = kZeroHPVector; for (i = 0; i < n_entities; i++) { Entity *e2 = sortedEntities[i]; float distanceToReferenceEntitySquared = (float)HPdistance2(p1, [e2 position]); if (entity != e2 && distanceToReferenceEntitySquared < rangeSq && predicate(e2, parameter)) { result = e2; rangeSq = distanceToReferenceEntitySquared; } } return [[result retain] autorelease]; } - (id) nearestShipMatchingPredicate:(EntityFilterPredicate)predicate parameter:(void *)parameter relativeToEntity:(Entity *)entity { if (predicate != NULL) { BinaryOperationPredicateParameter param = { IsShipPredicate, NULL, predicate, parameter }; return [self nearestEntityMatchingPredicate:ANDPredicate parameter:¶m relativeToEntity:entity]; } else { return [self nearestEntityMatchingPredicate:IsShipPredicate parameter:NULL relativeToEntity:entity]; } } - (OOTimeAbsolute) getTime { return universal_time; } - (OOTimeDelta) getTimeDelta { return time_delta; } - (void) findCollisionsAndShadows { unsigned i; [universeRegion clearEntityList]; for (i = 0; i < n_entities; i++) { [universeRegion checkEntity:sortedEntities[i]]; // sorts out which region it's in } if (![[self gameController] isGamePaused]) { [universeRegion findCollisions]; } // do check for entities that can't see the sun! [universeRegion findShadowedEntities]; } - (NSString*) collisionDescription { if (universeRegion != nil) return [universeRegion collisionDescription]; else return @"-"; } - (void) dumpCollisions { dumpCollisionInfo = YES; } - (OOViewID) viewDirection { return viewDirection; } - (void) setViewDirection:(OOViewID) vd { NSString *ms = nil; BOOL guiSelected = NO; if ((viewDirection == vd) && (vd != VIEW_CUSTOM) && (!displayGUI)) return; switch (vd) { case VIEW_FORWARD: ms = DESC(@"forward-view-string"); break; case VIEW_AFT: ms = DESC(@"aft-view-string"); break; case VIEW_PORT: ms = DESC(@"port-view-string"); break; case VIEW_STARBOARD: ms = DESC(@"starboard-view-string"); break; case VIEW_CUSTOM: ms = [PLAYER customViewDescription]; break; case VIEW_GUI_DISPLAY: [self setDisplayText:YES]; [self setMainLightPosition:(Vector){ DEMO_LIGHT_POSITION }]; guiSelected = YES; break; default: guiSelected = YES; break; } if (guiSelected) { [[self gameController] setMouseInteractionModeForUIWithMouseInteraction:NO]; } else { displayGUI = NO; // switch off any text displays [[self gameController] setMouseInteractionModeForFlight]; } if (viewDirection != vd || viewDirection == VIEW_CUSTOM) { #if (ALLOW_CUSTOM_VIEWS_WHILE_PAUSED) BOOL gamePaused = [[self gameController] isGamePaused]; #else BOOL gamePaused = NO; #endif // view notifications for when the player switches to/from gui! //if (EXPECT(viewDirection == VIEW_GUI_DISPLAY || vd == VIEW_GUI_DISPLAY )) [PLAYER noteViewDidChangeFrom:viewDirection toView:vd]; viewDirection = vd; if (ms && !gamePaused) { [self addMessage:ms forCount:3]; } else if (gamePaused) { [message_gui clear]; } } } - (void) enterGUIViewModeWithMouseInteraction:(BOOL)mouseInteraction { [self setViewDirection:VIEW_GUI_DISPLAY]; [[self gameController] setMouseInteractionModeForUIWithMouseInteraction:mouseInteraction]; } - (NSString *) soundNameForCustomSoundKey:(NSString *)key { NSString *result = nil; NSMutableSet *seen = nil; id object = [customSounds objectForKey:key]; if ([object isKindOfClass:[NSArray class]] && [object count] > 0) { key = [object oo_stringAtIndex:Ranrot() % [object count]]; } else { object=nil; } result = [[OOCacheManager sharedCache] objectForKey:key inCache:@"resolved custom sounds"]; if (result == nil) { // Resolve sound, allowing indirection within customsounds.plist seen = [NSMutableSet set]; result = key; if (object == nil || ([result hasPrefix:@"["] && [result hasSuffix:@"]"])) { for (;;) { [seen addObject:result]; object = [customSounds objectForKey:result]; if( [object isKindOfClass:[NSArray class]] && [object count] > 0) { result = [object oo_stringAtIndex:Ranrot() % [object count]]; if ([key hasPrefix:@"["] && [key hasSuffix:@"]"]) key=result; } else { if ([object isKindOfClass:[NSString class]]) result = object; else result = nil; } if (result == nil || ![result hasPrefix:@"["] || ![result hasSuffix:@"]"]) break; if ([seen containsObject:result]) { OOLogERR(@"sound.customSounds.recursion", @"recursion in customsounds.plist for '%@' (at '%@'), no sound will be played.", key, result); result = nil; break; } } } if (result == nil) result = @"__oolite-no-sound"; [[OOCacheManager sharedCache] setObject:result forKey:key inCache:@"resolved custom sounds"]; } if ([result isEqualToString:@"__oolite-no-sound"]) { OOLog(@"sound.customSounds", @"Could not resolve sound name in customsounds.plist for '%@', no sound will be played.", key); result = nil; } return result; } - (NSDictionary *) screenTextureDescriptorForKey:(NSString *)key { id value = [screenBackgrounds objectForKey:key]; while ([value isKindOfClass:[NSArray class]]) value = [value objectAtIndex:Ranrot() % [value count]]; if ([value isKindOfClass:[NSString class]]) value = [NSDictionary dictionaryWithObject:value forKey:@"name"]; else if (![value isKindOfClass:[NSDictionary class]]) value = nil; // Start loading the texture, and return nil if it doesn't exist. if (![[self gui] preloadGUITexture:value]) value = nil; return value; } - (void) clearPreviousMessage { if (currentMessage) [currentMessage release]; currentMessage = nil; } - (void) setMessageGuiBackgroundColor:(OOColor *)some_color { [message_gui setBackgroundColor:some_color]; } - (void) displayMessage:(NSString *) text forCount:(OOTimeDelta)count { if (![currentMessage isEqual:text] || universal_time >= messageRepeatTime) { if (currentMessage) [currentMessage release]; currentMessage = [text retain]; messageRepeatTime=universal_time + 6.0; [message_gui printLongText:text align:GUI_ALIGN_CENTER color:[OOColor yellowColor] fadeTime:count key:nil addToArray:nil]; } } - (void) displayCountdownMessage:(NSString *) text forCount:(OOTimeDelta)count { if (![currentMessage isEqual:text] && universal_time >= countdown_messageRepeatTime) { if (currentMessage) [currentMessage release]; currentMessage = [text retain]; countdown_messageRepeatTime=universal_time + count; [message_gui printLineNoScroll:text align:GUI_ALIGN_CENTER color:[OOColor yellowColor] fadeTime:count key:nil addToArray:nil]; } } - (void) addDelayedMessage:(NSString *)text forCount:(OOTimeDelta)count afterDelay:(double)delay { NSMutableDictionary *msgDict = [NSMutableDictionary dictionaryWithCapacity:2]; [msgDict setObject:text forKey:@"message"]; [msgDict setObject:[NSNumber numberWithDouble:count] forKey:@"duration"]; [self performSelector:@selector(addDelayedMessage:) withObject:msgDict afterDelay:delay]; } - (void) addDelayedMessage:(NSDictionary *) textdict { NSString *msg = nil; OOTimeDelta msg_duration; msg = [textdict oo_stringForKey:@"message"]; if (msg == nil) return; msg_duration = [textdict oo_nonNegativeDoubleForKey:@"duration" defaultValue:3.0]; [self addMessage:msg forCount:msg_duration]; } - (void) addMessage:(NSString *)text forCount:(OOTimeDelta)count { [self addMessage:text forCount:count forceDisplay:NO]; } - (void) speakWithSubstitutions:(NSString *)text { #if OOLITE_SPEECH_SYNTH //speech synthesis PlayerEntity* player = PLAYER; if ([player isSpeechOn] > OOSPEECHSETTINGS_OFF) { NSString *systemSaid = nil; NSString *h_systemSaid = nil; NSString *systemName = [self getSystemName:systemID]; systemSaid = systemName; NSString *h_systemName = [self getSystemName:[player targetSystemID]]; h_systemSaid = h_systemName; NSString *spokenText = text; if (speechArray != nil) { NSEnumerator *speechEnumerator = nil; NSArray *thePair = nil; for (speechEnumerator = [speechArray objectEnumerator]; (thePair = [speechEnumerator nextObject]); ) { NSString *original_phrase = [thePair oo_stringAtIndex:0]; NSUInteger replacementIndex; #if OOLITE_MAC_OS_X replacementIndex = 1; #elif OOLITE_ESPEAK replacementIndex = [thePair count] > 2 ? 2 : 1; #endif NSString *replacement_phrase = [thePair oo_stringAtIndex:replacementIndex]; if (![replacement_phrase isEqualToString:@"_"]) { spokenText = [spokenText stringByReplacingOccurrencesOfString:original_phrase withString:replacement_phrase]; } } spokenText = [spokenText stringByReplacingOccurrencesOfString:systemName withString:systemSaid]; spokenText = [spokenText stringByReplacingOccurrencesOfString:h_systemName withString:h_systemSaid]; } [self stopSpeaking]; [self startSpeakingString:spokenText]; } #endif // OOLITE_SPEECH_SYNTH } - (void) addMessage:(NSString *) text forCount:(OOTimeDelta) count forceDisplay:(BOOL) forceDisplay { if (![currentMessage isEqual:text] || forceDisplay || universal_time >= messageRepeatTime) { if ([PLAYER isSpeechOn] == OOSPEECHSETTINGS_ALL) { [self speakWithSubstitutions:text]; } [message_gui printLongText:text align:GUI_ALIGN_CENTER color:[OOColor yellowColor] fadeTime:([self permanentMessageLog]?0.0:count) key:nil addToArray:nil]; [currentMessage release]; currentMessage = [text retain]; messageRepeatTime=universal_time + 6.0; } } - (void) addCommsMessage:(NSString *)text forCount:(OOTimeDelta)count { [self addCommsMessage:text forCount:count andShowComms:_autoCommLog logOnly:NO]; } - (void) addCommsMessage:(NSString *)text forCount:(OOTimeDelta)count andShowComms:(BOOL)showComms logOnly:(BOOL)logOnly { if ([PLAYER showDemoShips]) return; if (![currentMessage isEqualToString:text] || universal_time >= messageRepeatTime) { PlayerEntity* player = PLAYER; if (!logOnly) { if ([player isSpeechOn] >= OOSPEECHSETTINGS_COMMS) { // EMMSTRAN: should say "Incoming message from ..." when prefixed with sender name. NSString *format = OOExpandKey(@"speech-synthesis-incoming-message-@"); [self speakWithSubstitutions:[NSString stringWithFormat:format, text]]; } [message_gui printLongText:text align:GUI_ALIGN_CENTER color:[OOColor greenColor] fadeTime:([self permanentMessageLog]?0.0:count) key:nil addToArray:nil]; [currentMessage release]; currentMessage = [text retain]; messageRepeatTime=universal_time + 6.0; } [comm_log_gui printLongText:text align:GUI_ALIGN_LEFT color:nil fadeTime:0.0 key:nil addToArray:[player commLog]]; if (showComms) [self showCommsLog:6.0]; } } - (void) showCommsLog:(OOTimeDelta)how_long { [comm_log_gui setAlpha:1.0]; if (![self permanentCommLog]) [comm_log_gui fadeOutFromTime:[self getTime] overDuration:how_long]; } - (void) repopulateSystem { if (EXPECT_NOT([PLAYER status] == STATUS_START_GAME)) { return; // no need to be adding ships as this is not a "real" game } JSContext *context = OOJSAcquireContext(); [PLAYER doWorldScriptEvent:OOJSIDFromString(system_repopulator) inContext:context withArguments:NULL count:0 timeLimit:kOOJSLongTimeLimit]; OOJSRelinquishContext(context); next_repopulation = SYSTEM_REPOPULATION_INTERVAL; } - (void) update:(OOTimeDelta)inDeltaT { volatile OOTimeDelta delta_t = inDeltaT * [self timeAccelerationFactor]; NSUInteger sessionID = _sessionID; OOLog(@"universe.profile.update",@"Begin update"); if (EXPECT(!no_update)) { next_repopulation -= delta_t; if (next_repopulation < 0) { [self repopulateSystem]; } unsigned i, ent_count = n_entities; Entity *my_entities[ent_count]; [self verifyEntitySessionIDs]; // use a retained copy so this can't be changed under us. for (i = 0; i < ent_count; i++) { my_entities[i] = [sortedEntities[i] retain]; // explicitly retain each one } NSString * volatile update_stage = @"initialisation"; #ifndef NDEBUG id volatile update_stage_param = nil; #endif @try { PlayerEntity *player = PLAYER; skyClearColor[0] = 0.0; skyClearColor[1] = 0.0; skyClearColor[2] = 0.0; skyClearColor[3] = 0.0; time_delta = delta_t; universal_time += delta_t; if (EXPECT_NOT([player showDemoShips] && [player guiScreen] == GUI_SCREEN_SHIPLIBRARY)) { update_stage = @"demo management"; if (universal_time >= demo_stage_time) { if (ent_count > 1) { Vector vel; Quaternion q2 = kIdentityQuaternion; quaternion_rotate_about_y(&q2,M_PI); switch (demo_stage) { case DEMO_FLY_IN: [demo_ship setPosition:[demo_ship destination]]; // ideal position demo_stage = DEMO_SHOW_THING; demo_stage_time = universal_time + 300.0; break; case DEMO_SHOW_THING: vel = make_vector(0, 0, DEMO2_VANISHING_DISTANCE * demo_ship->collision_radius * 6.0); [demo_ship setVelocity:vel]; demo_stage = DEMO_FLY_OUT; demo_stage_time = universal_time + 0.25; break; case DEMO_FLY_OUT: // change the demo_ship here [self removeEntity:demo_ship]; demo_ship = nil; /* NSString *shipDesc = nil; NSString *shipName = nil; NSDictionary *shipDict = nil; */ demo_ship_subindex = (demo_ship_subindex + 1) % [[demo_ships objectAtIndex:demo_ship_index] count]; demo_ship = [self newShipWithName:[[self demoShipData] oo_stringForKey:kOODemoShipKey] usePlayerProxy:NO]; if (demo_ship != nil) { [demo_ship removeEquipmentItem:@"EQ_SHIELD_BOOSTER"]; [demo_ship removeEquipmentItem:@"EQ_SHIELD_ENHANCER"]; [demo_ship switchAITo:@"nullAI.plist"]; [demo_ship setOrientation:q2]; [demo_ship setScanClass: CLASS_NO_DRAW]; [demo_ship setStatus: STATUS_COCKPIT_DISPLAY]; // prevents it getting escorts on addition if ([self addEntity:demo_ship]) { [demo_ship release]; // We now own a reference through the entity list. [demo_ship setStatus:STATUS_COCKPIT_DISPLAY]; demo_start_z=DEMO2_VANISHING_DISTANCE * demo_ship->collision_radius; [demo_ship setPositionX:0.0f y:0.0f z:demo_start_z]; [demo_ship setDestination: make_HPvector(0.0f, 0.0f, demo_start_z * 0.01f)]; // ideal position [demo_ship setVelocity:kZeroVector]; [demo_ship setScanClass: CLASS_NO_DRAW]; [demo_ship setRoll:M_PI/10.0]; [demo_ship setPitch:M_PI/20.0]; // [gui setText:shipName != nil ? shipName : [demo_ship displayName] forRow:19 align:GUI_ALIGN_CENTER]; [self setLibraryTextForDemoShip]; demo_stage = DEMO_FLY_IN; demo_start_time=universal_time; demo_stage_time = demo_start_time + DEMO2_FLY_IN_STAGE_TIME; } else { demo_ship = nil; } } break; } } } else if (demo_stage == DEMO_FLY_IN) { GLfloat delta = (universal_time - demo_start_time) / DEMO2_FLY_IN_STAGE_TIME; [demo_ship setPositionX:0.0f y:[demo_ship destination].y * delta z:demo_start_z + ([demo_ship destination].z - demo_start_z) * delta ]; } } update_stage = @"update:entity"; NSMutableSet *zombies = nil; OOLog(@"universe.profile.update", @"%@", update_stage); for (i = 0; i < ent_count; i++) { Entity *thing = my_entities[i]; #ifndef NDEBUG update_stage_param = thing; update_stage = @"update:entity [%@]"; #endif // Game Over code depends on regular delta_t updates to the dead player entity. Ignore the player entity, even when dead. if (EXPECT_NOT([thing status] == STATUS_DEAD && ![entitiesDeadThisUpdate containsObject:thing] && ![thing isPlayer])) { if (zombies == nil) zombies = [NSMutableSet set]; [zombies addObject:thing]; continue; } [thing update:delta_t]; if (EXPECT_NOT(sessionID != _sessionID)) { // Game was reset (in player update); end this update: cycle. break; } #ifndef NDEBUG update_stage = @"update:list maintenance [%@]"; #endif // maintain distance-from-player list GLfloat z_distance = thing->zero_distance; int index = thing->zero_index; while (index > 0 && z_distance < sortedEntities[index - 1]->zero_distance) { sortedEntities[index] = sortedEntities[index - 1]; // bubble up the list, usually by just one position sortedEntities[index - 1] = thing; thing->zero_index = index - 1; sortedEntities[index]->zero_index = index; index--; } // update deterministic AI if ([thing isShip]) { #ifndef NDEBUG update_stage = @"update:think [%@]"; #endif AI* theShipsAI = [(ShipEntity *)thing getAI]; if (theShipsAI) { double thinkTime = [theShipsAI nextThinkTime]; if ((universal_time > thinkTime)||(thinkTime == 0.0)) { [theShipsAI setNextThinkTime:universal_time + [theShipsAI thinkTimeInterval]]; [theShipsAI think]; } } } } #ifndef NDEBUG update_stage_param = nil; #endif if (zombies != nil) { update_stage = @"shootin' zombies"; NSEnumerator *zombieEnum = nil; Entity *zombie = nil; for (zombieEnum = [zombies objectEnumerator]; (zombie = [zombieEnum nextObject]); ) { OOLogERR(@"universe.zombie", @"Found dead entity %@ in active entity list, removing. This is an internal error, please report it.", zombie); [self removeEntity:zombie]; } } // Maintain x/y/z order lists update_stage = @"updating linked lists"; OOLog(@"universe.profile.update", @"%@", update_stage); for (i = 0; i < ent_count; i++) { [my_entities[i] updateLinkedLists]; } // detect collisions and light ships that can see the sun update_stage = @"collision and shadow detection"; OOLog(@"universe.profile.update", @"%@", update_stage); [self filterSortedLists]; [self findCollisionsAndShadows]; // do any required check and maintenance of linked lists if (doLinkedListMaintenanceThisUpdate) { MaintainLinkedLists(self); doLinkedListMaintenanceThisUpdate = NO; } } @catch (NSException *exception) { if ([[exception name] hasPrefix:@"Oolite"]) { [self handleOoliteException:exception]; } else { #ifndef NDEBUG if (update_stage_param != nil) update_stage = [NSString stringWithFormat:update_stage, update_stage_param]; #endif OOLog(kOOLogException, @"***** Exception during [%@] in [Universe update:] : %@ : %@ *****", update_stage, [exception name], [exception reason]); @throw exception; } } // dispose of the non-mutable copy and everything it references neatly update_stage = @"clean up"; OOLog(@"universe.profile.update", @"%@", update_stage); for (i = 0; i < ent_count; i++) { [my_entities[i] release]; // explicitly release each one } /* Garbage collection is going to result in a significant * pause when it happens. Doing it here is better than doing * it in the middle of the update when it might slow a * function into the timelimiter through no fault of its * own. JS_MaybeGC will only run a GC when it's * necessary. Merely checking is not significant in terms of * time. - CIM: 4/8/2013 */ update_stage = @"JS Garbage Collection"; OOLog(@"universe.profile.update", @"%@", update_stage); #ifndef NDEBUG JSContext *context = OOJSAcquireContext(); uint32 gcbytes1 = JS_GetGCParameter(JS_GetRuntime(context),JSGC_BYTES); OOJSRelinquishContext(context); #endif [[OOJavaScriptEngine sharedEngine] garbageCollectionOpportunity:NO]; #ifndef NDEBUG context = OOJSAcquireContext(); uint32 gcbytes2 = JS_GetGCParameter(JS_GetRuntime(context),JSGC_BYTES); OOJSRelinquishContext(context); if (gcbytes2 < gcbytes1) { OOLog(@"universe.profile.jsgc",@"Unplanned JS Garbage Collection from %d to %d",gcbytes1,gcbytes2); } #endif } else { // always perform player's dead updates: allows deferred JS resets. if ([PLAYER status] == STATUS_DEAD) [PLAYER update:delta_t]; } [entitiesDeadThisUpdate autorelease]; entitiesDeadThisUpdate = nil; entitiesDeadThisUpdate = [[NSMutableSet alloc] initWithCapacity:n_entities]; #if NEW_PLANETS [self prunePreloadingPlanetMaterials]; #endif OOLog(@"universe.profile.update",@"Update complete"); } #ifndef NDEBUG - (double) timeAccelerationFactor { return timeAccelerationFactor; } - (void) setTimeAccelerationFactor:(double)newTimeAccelerationFactor { if (newTimeAccelerationFactor < TIME_ACCELERATION_FACTOR_MIN || newTimeAccelerationFactor > TIME_ACCELERATION_FACTOR_MAX) { newTimeAccelerationFactor = TIME_ACCELERATION_FACTOR_DEFAULT; } timeAccelerationFactor = newTimeAccelerationFactor; } #else - (double) timeAccelerationFactor { return 1.0; } - (void) setTimeAccelerationFactor:(double)newTimeAccelerationFactor { } #endif - (void) filterSortedLists { /* Eric, 17-10-2010: raised the area to be not filtered out, from the combined collision size to 2x this size. This allows this filtered list to be used also for proximity_alert and not only for collisions. Before the proximity_alert could only trigger when already very near a collision. To late for ships to react. This does raise the number of entities in the collision chain with as result that the number of pairs to compair becomes significant larger. However, almost all of these extra pairs are dealt with by a simple distance check. I currently see no noticeable negative effect while playing, but this change might still give some trouble I missed. */ Entity *e0, *next, *prev; OOHPScalar start, finish, next_start, next_finish, prev_start, prev_finish; // using the z_list - set or clear collisionTestFilter and clear collision_chain e0 = z_list_start; while (e0) { e0->collisionTestFilter = [e0 canCollide]?0:3; e0->collision_chain = nil; e0 = e0->z_next; } // done. /* We need to check the lists in both ascending and descending order * to catch some cases with interposition of entities. We set cTF = * 1 on the way up, and |= 2 on the way down. Therefore it's only 3 * at the end of the list if it was caught both ways on the same * list. - CIM: 7/11/2012 */ // start with the z_list e0 = z_list_start; while (e0) { // here we are either at the start of the list or just past a gap start = e0->position.z - 2.0f * e0->collision_radius; finish = start + 4.0f * e0->collision_radius; next = e0->z_next; while ((next)&&(next->collisionTestFilter == 3)) // next has been eliminated from the list of possible colliders - so skip it next = next->z_next; if (next) { next_start = next->position.z - 2.0f * next->collision_radius; if (next_start < finish) { // e0 and next overlap while ((next)&&(next_start < finish)) { // skip forward to the next gap or the end of the list next_finish = next_start + 4.0f * next->collision_radius; if (next_finish > finish) finish = next_finish; e0 = next; next = e0->z_next; while ((next)&&(next->collisionTestFilter==3)) // next has been eliminated - so skip it next = next->z_next; if (next) next_start = next->position.z - 2.0f * next->collision_radius; } // now either (next == nil) or (next_start >= finish)-which would imply a gap! } else { // e0 is a singleton e0->collisionTestFilter = 1; } } else // (next == nil) { // at the end of the list so e0 is a singleton e0->collisionTestFilter = 1; } e0 = next; } // list filtered upwards, now filter downwards // e0 currently = end of z list while (e0) { // here we are either at the start of the list or just past a gap start = e0->position.z + 2.0f * e0->collision_radius; finish = start - 4.0f * e0->collision_radius; prev = e0->z_previous; while ((prev)&&(prev->collisionTestFilter == 3)) // next has been eliminated from the list of possible colliders - so skip it prev = prev->z_previous; if (prev) { prev_start = prev->position.z + 2.0f * prev->collision_radius; if (prev_start > finish) { // e0 and next overlap while ((prev)&&(prev_start > finish)) { // skip forward to the next gap or the end of the list prev_finish = prev_start - 4.0f * prev->collision_radius; if (prev_finish < finish) finish = prev_finish; e0 = prev; prev = e0->z_previous; while ((prev)&&(prev->collisionTestFilter==3)) // next has been eliminated - so skip it prev = prev->z_previous; if (prev) prev_start = prev->position.z + 2.0f * prev->collision_radius; } // now either (prev == nil) or (prev_start <= finish)-which would imply a gap! } else { // e0 is a singleton e0->collisionTestFilter |= 2; } } else // (prev == nil) { // at the end of the list so e0 is a singleton e0->collisionTestFilter |= 2; } e0 = prev; } // done! list filtered // then with the y_list, z_list singletons now create more gaps.. e0 = y_list_start; while (e0) { // here we are either at the start of the list or just past a gap start = e0->position.y - 2.0f * e0->collision_radius; finish = start + 4.0f * e0->collision_radius; next = e0->y_next; while ((next)&&(next->collisionTestFilter==3)) // next has been eliminated from the list of possible colliders - so skip it next = next->y_next; if (next) { next_start = next->position.y - 2.0f * next->collision_radius; if (next_start < finish) { // e0 and next overlap while ((next)&&(next_start < finish)) { // skip forward to the next gap or the end of the list next_finish = next_start + 4.0f * next->collision_radius; if (next_finish > finish) finish = next_finish; e0 = next; next = e0->y_next; while ((next)&&(next->collisionTestFilter==3)) // next has been eliminated - so skip it next = next->y_next; if (next) next_start = next->position.y - 2.0f * next->collision_radius; } // now either (next == nil) or (next_start >= finish)-which would imply a gap! } else { // e0 is a singleton e0->collisionTestFilter = 1; } } else // (next == nil) { // at the end of the list so e0 is a singleton e0->collisionTestFilter = 1; } e0 = next; } // list filtered upwards, now filter downwards // e0 currently = end of y list while (e0) { // here we are either at the start of the list or just past a gap start = e0->position.y + 2.0f * e0->collision_radius; finish = start - 4.0f * e0->collision_radius; prev = e0->y_previous; while ((prev)&&(prev->collisionTestFilter == 3)) // next has been eliminated from the list of possible colliders - so skip it prev = prev->y_previous; if (prev) { prev_start = prev->position.y + 2.0f * prev->collision_radius; if (prev_start > finish) { // e0 and next overlap while ((prev)&&(prev_start > finish)) { // skip forward to the next gap or the end of the list prev_finish = prev_start - 4.0f * prev->collision_radius; if (prev_finish < finish) finish = prev_finish; e0 = prev; prev = e0->y_previous; while ((prev)&&(prev->collisionTestFilter==3)) // next has been eliminated - so skip it prev = prev->y_previous; if (prev) prev_start = prev->position.y + 2.0f * prev->collision_radius; } // now either (prev == nil) or (prev_start <= finish)-which would imply a gap! } else { // e0 is a singleton e0->collisionTestFilter |= 2; } } else // (prev == nil) { // at the end of the list so e0 is a singleton e0->collisionTestFilter |= 2; } e0 = prev; } // done! list filtered // finish with the x_list e0 = x_list_start; while (e0) { // here we are either at the start of the list or just past a gap start = e0->position.x - 2.0f * e0->collision_radius; finish = start + 4.0f * e0->collision_radius; next = e0->x_next; while ((next)&&(next->collisionTestFilter==3)) // next has been eliminated from the list of possible colliders - so skip it next = next->x_next; if (next) { next_start = next->position.x - 2.0f * next->collision_radius; if (next_start < finish) { // e0 and next overlap while ((next)&&(next_start < finish)) { // skip forward to the next gap or the end of the list next_finish = next_start + 4.0f * next->collision_radius; if (next_finish > finish) finish = next_finish; e0 = next; next = e0->x_next; while ((next)&&(next->collisionTestFilter==3)) // next has been eliminated - so skip it next = next->x_next; if (next) next_start = next->position.x - 2.0f * next->collision_radius; } // now either (next == nil) or (next_start >= finish)-which would imply a gap! } else { // e0 is a singleton e0->collisionTestFilter = 1; } } else // (next == nil) { // at the end of the list so e0 is a singleton e0->collisionTestFilter = 1; } e0 = next; } // list filtered upwards, now filter downwards // e0 currently = end of x list while (e0) { // here we are either at the start of the list or just past a gap start = e0->position.x + 2.0f * e0->collision_radius; finish = start - 4.0f * e0->collision_radius; prev = e0->x_previous; while ((prev)&&(prev->collisionTestFilter == 3)) // next has been eliminated from the list of possible colliders - so skip it prev = prev->x_previous; if (prev) { prev_start = prev->position.x + 2.0f * prev->collision_radius; if (prev_start > finish) { // e0 and next overlap while ((prev)&&(prev_start > finish)) { // skip forward to the next gap or the end of the list prev_finish = prev_start - 4.0f * prev->collision_radius; if (prev_finish < finish) finish = prev_finish; e0 = prev; prev = e0->x_previous; while ((prev)&&(prev->collisionTestFilter==3)) // next has been eliminated - so skip it prev = prev->x_previous; if (prev) prev_start = prev->position.x + 2.0f * prev->collision_radius; } // now either (prev == nil) or (prev_start <= finish)-which would imply a gap! } else { // e0 is a singleton e0->collisionTestFilter |= 2; } } else // (prev == nil) { // at the end of the list so e0 is a singleton e0->collisionTestFilter |= 2; } e0 = prev; } // done! list filtered // repeat the y_list - so gaps from the x_list influence singletons e0 = y_list_start; while (e0) { // here we are either at the start of the list or just past a gap start = e0->position.y - 2.0f * e0->collision_radius; finish = start + 4.0f * e0->collision_radius; next = e0->y_next; while ((next)&&(next->collisionTestFilter==3)) // next has been eliminated from the list of possible colliders - so skip it next = next->y_next; if (next) { next_start = next->position.y - 2.0f * next->collision_radius; if (next_start < finish) { // e0 and next overlap while ((next)&&(next_start < finish)) { // skip forward to the next gap or the end of the list next_finish = next_start + 4.0f * next->collision_radius; if (next_finish > finish) finish = next_finish; e0 = next; next = e0->y_next; while ((next)&&(next->collisionTestFilter==3)) // next has been eliminated - so skip it next = next->y_next; if (next) next_start = next->position.y - 2.0f * next->collision_radius; } // now either (next == nil) or (next_start >= finish)-which would imply a gap! } else { // e0 is a singleton e0->collisionTestFilter = 1; } } else // (next == nil) { // at the end of the list so e0 is a singleton e0->collisionTestFilter = 1; } e0 = next; } // e0 currently = end of y list while (e0) { // here we are either at the start of the list or just past a gap start = e0->position.y + 2.0f * e0->collision_radius; finish = start - 4.0f * e0->collision_radius; prev = e0->y_previous; while ((prev)&&(prev->collisionTestFilter == 3)) // next has been eliminated from the list of possible colliders - so skip it prev = prev->y_previous; if (prev) { prev_start = prev->position.y + 2.0f * prev->collision_radius; if (prev_start > finish) { // e0 and next overlap while ((prev)&&(prev_start > finish)) { // skip forward to the next gap or the end of the list prev_finish = prev_start - 4.0f * prev->collision_radius; if (prev_finish < finish) finish = prev_finish; e0 = prev; prev = e0->y_previous; while ((prev)&&(prev->collisionTestFilter==3)) // next has been eliminated - so skip it prev = prev->y_previous; if (prev) prev_start = prev->position.y + 2.0f * prev->collision_radius; } // now either (prev == nil) or (prev_start <= finish)-which would imply a gap! } else { // e0 is a singleton e0->collisionTestFilter |= 2; } } else // (prev == nil) { // at the end of the list so e0 is a singleton e0->collisionTestFilter |= 2; } e0 = prev; } // done! list filtered // finally, repeat the z_list - this time building collision chains... e0 = z_list_start; while (e0) { // here we are either at the start of the list or just past a gap start = e0->position.z - 2.0f * e0->collision_radius; finish = start + 4.0f * e0->collision_radius; next = e0->z_next; while ((next)&&(next->collisionTestFilter==3)) // next has been eliminated from the list of possible colliders - so skip it next = next->z_next; if (next) { next_start = next->position.z - 2.0f * next->collision_radius; if (next_start < finish) { // e0 and next overlap while ((next)&&(next_start < finish)) { // chain e0 to next in collision e0->collision_chain = next; // skip forward to the next gap or the end of the list next_finish = next_start + 4.0f * next->collision_radius; if (next_finish > finish) finish = next_finish; e0 = next; next = e0->z_next; while ((next)&&(next->collisionTestFilter==3)) // next has been eliminated - so skip it next = next->z_next; if (next) next_start = next->position.z - 2.0f * next->collision_radius; } // now either (next == nil) or (next_start >= finish)-which would imply a gap! e0->collision_chain = nil; // end the collision chain } else { // e0 is a singleton e0->collisionTestFilter = 1; } } else // (next == nil) { // at the end of the list so e0 is a singleton e0->collisionTestFilter = 1; } e0 = next; } // e0 currently = end of z list while (e0) { // here we are either at the start of the list or just past a gap start = e0->position.z + 2.0f * e0->collision_radius; finish = start - 4.0f * e0->collision_radius; prev = e0->z_previous; while ((prev)&&(prev->collisionTestFilter == 3)) // next has been eliminated from the list of possible colliders - so skip it prev = prev->z_previous; if (prev) { prev_start = prev->position.z + 2.0f * prev->collision_radius; if (prev_start > finish) { // e0 and next overlap while ((prev)&&(prev_start > finish)) { // e0 probably already in collision chain at this point, but if it // isn't we have to insert it if (prev->collision_chain != e0) { if (prev->collision_chain == nil) { // easy, just add it onto the start of the chain prev->collision_chain = e0; } else { /* not nil and not e0 shouldn't be possible, I think. * if it is, that implies that e0->collision_chain is nil, though * so: */ if (e0->collision_chain == nil) { e0->collision_chain = prev->collision_chain; prev->collision_chain = e0; } else { /* This shouldn't happen... If it does, we accept * missing collision checks and move on */ OOLog(@"general.error.inconsistentState",@"Unexpected state in collision chain builder prev=%@, prev->c=%@, e0=%@, e0->c=%@",prev,prev->collision_chain,e0,e0->collision_chain); } } } // skip forward to the next gap or the end of the list prev_finish = prev_start - 4.0f * prev->collision_radius; if (prev_finish < finish) finish = prev_finish; e0 = prev; prev = e0->z_previous; while ((prev)&&(prev->collisionTestFilter==3)) // next has been eliminated - so skip it prev = prev->z_previous; if (prev) prev_start = prev->position.z + 2.0f * prev->collision_radius; } // now either (prev == nil) or (prev_start <= finish)-which would imply a gap! // all the collision chains are already terminated somewhere // at this point so no need to set e0->collision_chain = nil } else { // e0 is a singleton e0->collisionTestFilter |= 2; } } else // (prev == nil) { // at the end of the list so e0 is a singleton e0->collisionTestFilter |= 2; } e0 = prev; } // done! list filtered } - (void) setGalaxyTo:(OOGalaxyID) g { [self setGalaxyTo:g andReinit:NO]; } - (void) setGalaxyTo:(OOGalaxyID) g andReinit:(BOOL) forced { int i; NSAutoreleasePool *pool = nil; if (galaxyID != g || forced) { galaxyID = g; // systems pool = [[NSAutoreleasePool alloc] init]; for (i = 0; i < 256; i++) { if (system_names[i]) { [system_names[i] release]; } system_names[i] = [[systemManager getProperty:@"name" forSystem:i inGalaxy:g] retain]; } [pool release]; } } - (void) setSystemTo:(OOSystemID) s { NSDictionary *systemData; PlayerEntity *player = PLAYER; OOEconomyID economy; NSString *scriptName; [self setGalaxyTo: [player galaxyNumber]]; systemID = s; targetSystemID = s; systemData = [self generateSystemData:targetSystemID]; economy = [systemData oo_unsignedCharForKey:KEY_ECONOMY]; scriptName = [systemData oo_stringForKey:@"market_script" defaultValue:nil]; DESTROY(commodityMarket); commodityMarket = [[commodities generateMarketForSystemWithEconomy:economy andScript:scriptName] retain]; } - (OOSystemID) currentSystemID { return systemID; } - (NSDictionary *) descriptions { if (_descriptions == nil) { // Load internal descriptions.plist for use in early init, OXP verifier etc. // It will be replaced by merged version later if running the game normally. _descriptions = [NSDictionary dictionaryWithContentsOfFile:[[[ResourceManager builtInPath] stringByAppendingPathComponent:@"Config"] stringByAppendingPathComponent:@"descriptions.plist"]]; [self verifyDescriptions]; } return _descriptions; } static void VerifyDesc(NSString *key, id desc); static void VerifyDescString(NSString *key, NSString *desc) { if ([desc rangeOfString:@"%n"].location != NSNotFound) { OOLog(@"descriptions.verify.percentN", @"***** FATAL: descriptions.plist entry \"%@\" contains the dangerous control sequence %%n.", key); exit(EXIT_FAILURE); } } static void VerifyDescArray(NSString *key, NSArray *desc) { id subDesc = nil; foreach (subDesc, desc) { VerifyDesc(key, subDesc); } } static void VerifyDesc(NSString *key, id desc) { if ([desc isKindOfClass:[NSString class]]) { VerifyDescString(key, desc); } else if ([desc isKindOfClass:[NSArray class]]) { VerifyDescArray(key, desc); } else if ([desc isKindOfClass:[NSNumber class]]) { // No verification needed. } else { OOLogERR(@"descriptions.verify.badType", @"***** FATAL: descriptions.plist entry for \"%@\" is neither a string nor an array.", key); exit(EXIT_FAILURE); } } - (void) verifyDescriptions { /* Ensure that no descriptions.plist entries contain the %n format code, which can be used to smash the stack and potentially call arbitrary functions. %n is deliberately not supported in Foundation/CoreFoundation under Mac OS X, but unfortunately GNUstep implements it. -- Ahruman 2011-05-05 */ NSDictionary *descriptions = [self descriptions]; NSString *key = nil; foreachkey (key, descriptions) { VerifyDesc(key, [descriptions objectForKey:key]); } } - (void) loadDescriptions { [_descriptions autorelease]; _descriptions = [[ResourceManager dictionaryFromFilesNamed:@"descriptions.plist" inFolder:@"Config" andMerge:YES] retain]; [self verifyDescriptions]; } - (NSDictionary *) explosionSetting:(NSString *)explosion { return [explosionSettings oo_dictionaryForKey:explosion defaultValue:nil]; } - (NSArray *) scenarios { return _scenarios; } - (void) loadScenarios { [_scenarios autorelease]; _scenarios = [[ResourceManager arrayFromFilesNamed:@"scenarios.plist" inFolder:@"Config" andMerge:YES] retain]; [self verifyDescriptions]; } - (NSDictionary *) characters { return characters; } - (NSDictionary *) missiontext { return missiontext; } - (NSString *)descriptionForKey:(NSString *)key { return [self chooseStringForKey:key inDictionary:[self descriptions]]; } - (NSString *)descriptionForArrayKey:(NSString *)key index:(unsigned)index { NSArray *array = [[self descriptions] oo_arrayForKey:key]; if ([array count] <= index) return nil; // Catches nil array return [array objectAtIndex:index]; } - (BOOL) descriptionBooleanForKey:(NSString *)key { return [[self descriptions] oo_boolForKey:key]; } - (OOSystemDescriptionManager *) systemManager { return systemManager; } - (NSString *) keyForPlanetOverridesForSystem:(OOSystemID) s inGalaxy:(OOGalaxyID) g { return [NSString stringWithFormat:@"%d %d", g, s]; } - (NSString *) keyForInterstellarOverridesForSystems:(OOSystemID) s1 :(OOSystemID) s2 inGalaxy:(OOGalaxyID) g { return [NSString stringWithFormat:@"interstellar: %d %d %d", g, s1, s2]; } - (NSDictionary *) generateSystemData:(OOSystemID) s { return [self generateSystemData:s useCache:YES]; } // cache isn't handled this way any more - (NSDictionary *) generateSystemData:(OOSystemID) s useCache:(BOOL) useCache { OOJS_PROFILE_ENTER // TODO: At the moment this method is only called for systems in the // same galaxy. At some point probably needs generalising to have a // galaxynumber parameter. NSString *systemKey = [NSString stringWithFormat:@"%u %u",[PLAYER galaxyNumber],s]; return [systemManager getPropertiesForSystemKey:systemKey]; OOJS_PROFILE_EXIT } - (NSDictionary *) currentSystemData { OOJS_PROFILE_ENTER if (![self inInterstellarSpace]) { return [self generateSystemData:systemID]; } else { static NSDictionary *interstellarDict = nil; if (interstellarDict == nil) { NSString *interstellarName = DESC(@"interstellar-space"); NSString *notApplicable = DESC(@"not-applicable"); NSNumber *minusOne = [NSNumber numberWithInt:-1]; NSNumber *zero = [NSNumber numberWithInt:0]; interstellarDict = [[NSDictionary alloc] initWithObjectsAndKeys: interstellarName, KEY_NAME, minusOne, KEY_GOVERNMENT, minusOne, KEY_ECONOMY, minusOne, KEY_TECHLEVEL, zero, KEY_POPULATION, zero, KEY_PRODUCTIVITY, zero, KEY_RADIUS, notApplicable, KEY_INHABITANTS, notApplicable, KEY_DESCRIPTION, nil]; } return interstellarDict; } OOJS_PROFILE_EXIT } - (BOOL) inInterstellarSpace { return [self sun] == nil; } // layer 2 // used by legacy script engine and sun going nova - (void) setSystemDataKey:(NSString *)key value:(NSObject *)object fromManifest:(NSString *)manifest { [self setSystemDataForGalaxy:galaxyID planet:systemID key:key value:object fromManifest:manifest forLayer:OO_LAYER_OXP_DYNAMIC]; } - (void) setSystemDataForGalaxy:(OOGalaxyID)gnum planet:(OOSystemID)pnum key:(NSString *)key value:(id)object fromManifest:(NSString *)manifest forLayer:(OOSystemLayer)layer { static BOOL sysdataLocked = NO; if (sysdataLocked) { OOLogERR(@"script.error", @"System properties cannot be set during 'systemInformationChanged' events to avoid infinite loops."); return; } // trying to set unsettable properties? if ([key isEqualToString:KEY_RADIUS]) // buggy if we allow this key to be set { OOLogERR(@"script.error", @"System property '%@' cannot be set.",key); return; } if ([key isEqualToString:@"coordinates"]) // setting this in game would be very confusing { OOLogERR(@"script.error", @"System property '%@' cannot be set.",key); return; } NSString *overrideKey = [NSString stringWithFormat:@"%u %u", gnum, pnum]; BOOL sameGalaxy = (gnum == [PLAYER currentGalaxyID]); BOOL sameSystem = (sameGalaxy && pnum == [self currentSystemID]); NSDictionary *sysInfo = nil; // short range map fix [gui refreshStarChart]; // long range map fixes if ([key isEqualToString:KEY_NAME]) { object=(id)[[(NSString *)object lowercaseString] capitalizedString]; if(sameGalaxy) { if (system_names[pnum]) [system_names[pnum] release]; system_names[pnum] = [(NSString *)object retain]; } } else if ([key isEqualToString:@"sun_radius"]) { if ([object doubleValue] < 1000.0 || [object doubleValue] > 10000000.0 ) { object = ([object doubleValue] < 1000.0 ? (id)@"1000.0" : (id)@"10000000.0"); // works! } } else if ([key hasPrefix:@"corona_"]) { object = (id)[NSString stringWithFormat:@"%f",OOClamp_0_1_f([object floatValue])]; } [systemManager setProperty:key forSystemKey:overrideKey andLayer:layer toValue:object fromManifest:manifest]; // Apply changes that can be effective immediately, issue warning if they can't be changed just now if (sameSystem) { sysInfo = [systemManager getPropertiesForCurrentSystem]; OOSunEntity* the_sun = [self sun]; /* KEY_ECONOMY used to be here, but resetting the main station * market while the player is in the system is likely to cause * more trouble than it's worth. Let them leave and come back * - CIM */ if ([key isEqualToString:KEY_TECHLEVEL]) { if([self station]){ [[self station] setEquivalentTechLevel:[object intValue]]; [[self station] setLocalShipyard:[self shipsForSaleForSystem:systemID withTL:[object intValue] atTime:[PLAYER clockTime]]]; } } else if ([key isEqualToString:@"sun_color"] || [key isEqualToString:@"star_count_multiplier"] || [key isEqualToString:@"nebula_count_multiplier"] || [key hasPrefix:@"sky_"]) { SkyEntity *the_sky = nil; int i; for (i = n_entities - 1; i > 0; i--) if ((sortedEntities[i]) && ([sortedEntities[i] isKindOfClass:[SkyEntity class]])) the_sky = (SkyEntity*)sortedEntities[i]; if (the_sky != nil) { [the_sky changeProperty:key withDictionary:sysInfo]; if ([key isEqualToString:@"sun_color"]) { OOColor *color=[[the_sky skyColor] blendedColorWithFraction:0.5 ofColor:[OOColor whiteColor]]; if (the_sun != nil) { [the_sun setSunColor:color]; [the_sun getDiffuseComponents:sun_diffuse]; [the_sun getSpecularComponents:sun_specular]; } for (i = n_entities - 1; i > 0; i--) if ((sortedEntities[i]) && ([sortedEntities[i] isKindOfClass:[DustEntity class]])) [(DustEntity*)sortedEntities[i] setDustColor:color]; } } } else if (the_sun != nil && ([key hasPrefix:@"sun_"] || [key hasPrefix:@"corona_"])) { [the_sun changeSunProperty:key withDictionary:sysInfo]; } else if ([key isEqualToString:@"texture"]) { [[self planet] setUpPlanetFromTexture:(NSString *)object]; } else if ([key isEqualToString:@"texture_hsb_color"]) { [[self planet] setUpPlanetFromTexture: [[self planet] textureFileName]]; } } sysdataLocked = YES; [PLAYER doScriptEvent:OOJSID("systemInformationChanged") withArguments:[NSArray arrayWithObjects:[NSNumber numberWithInt:gnum],[NSNumber numberWithInt:pnum],key,object,nil]]; sysdataLocked = NO; } - (NSDictionary *) generateSystemDataForGalaxy:(OOGalaxyID)gnum planet:(OOSystemID)pnum { NSString *systemKey = [self keyForPlanetOverridesForSystem:pnum inGalaxy:gnum]; return [systemManager getPropertiesForSystemKey:systemKey]; } - (NSArray *) systemDataKeysForGalaxy:(OOGalaxyID)gnum planet:(OOSystemID)pnum { return [[self generateSystemDataForGalaxy:gnum planet:pnum] allKeys]; } /* Only called from OOJSSystemInfo. */ - (id) systemDataForGalaxy:(OOGalaxyID)gnum planet:(OOSystemID)pnum key:(NSString *)key { return [systemManager getProperty:key forSystem:pnum inGalaxy:gnum]; } - (NSString *) getSystemName:(OOSystemID) sys { return [systemManager getProperty:@"name" forSystem:sys inGalaxy:galaxyID]; } - (OOGovernmentID) getSystemGovernment:(OOSystemID) sys { return [[systemManager getProperty:@"government" forSystem:sys inGalaxy:galaxyID] unsignedCharValue]; } - (NSString *) getSystemInhabitants:(OOSystemID) sys { return [self getSystemInhabitants:sys plural:YES]; } - (NSString *) getSystemInhabitants:(OOSystemID) sys plural:(BOOL)plural { NSString *ret = nil; if (!plural) { ret = [systemManager getProperty:KEY_INHABITANT forSystem:sys inGalaxy:galaxyID]; } if (ret != nil) // the singular form might be absent. { return ret; } else { return [systemManager getProperty:KEY_INHABITANT forSystem:sys inGalaxy:galaxyID]; } } - (NSPoint) coordinatesForSystem:(OOSystemID)s { return [systemManager getCoordinatesForSystem:s inGalaxy:galaxyID]; } - (OOSystemID) findSystemFromName:(NSString *) sysName { if (sysName == nil) return -1; // no match found! NSString *system_name = nil; NSString *match = [sysName lowercaseString]; int i; for (i = 0; i < 256; i++) { system_name = [system_names[i] lowercaseString]; if ([system_name isEqualToString:match]) { return i; } } return -1; // no match found! } - (OOSystemID) findSystemAtCoords:(NSPoint) coords withGalaxy:(OOGalaxyID) g { return [self findSystemNumberAtCoords:coords withGalaxy:g]; } - (NSMutableArray *) nearbyDestinationsWithinRange:(double)range { NSMutableArray *result = [NSMutableArray arrayWithCapacity:16]; range = OOClamp_0_max_d(range, MAX_JUMP_RANGE); // limit to systems within 7LY NSPoint here = [PLAYER galaxy_coordinates]; for (unsigned short i = 0; i < 256; i++) { NSPoint there = [self coordinatesForSystem:i]; double dist = distanceBetweenPlanetPositions(here.x, here.y, there.x, there.y); if (dist <= range && (i != systemID || [self inInterstellarSpace])) // if we are in interstellar space, it's OK to include the system we (mis)jumped from { [result addObject: [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithDouble:dist], @"distance", [NSNumber numberWithInt:i], @"sysID", [[self generateSystemData:i] oo_stringForKey:@"sun_gone_nova" defaultValue:@"0"], @"nova", nil]]; } } return result; } - (OOSystemID) findNeighbouringSystemToCoords:(NSPoint) coords withGalaxy:(OOGalaxyID) g { double distance; int n,i,j; double min_dist = 10000.0; // make list of connected systems BOOL connected[256]; for (i = 0; i < 256; i++) connected[i] = NO; connected[0] = YES; // system zero is always connected (true for galaxies 0..7) for (n = 0; n < 3; n++) //repeat three times for surety { for (i = 0; i < 256; i++) // flood fill out from system zero { NSPoint ipos = [systemManager getCoordinatesForSystem:i inGalaxy:g]; for (j = i+1; j < 256; j++) { NSPoint jpos = [systemManager getCoordinatesForSystem:j inGalaxy:g]; double dist = distanceBetweenPlanetPositions(ipos.x,ipos.y,jpos.x,jpos.y); if (dist <= MAX_JUMP_RANGE) { connected[j] |= connected[i]; connected[i] |= connected[j]; } } } } OOSystemID system = 0; for (i = 0; i < 256; i++) { NSPoint ipos = [systemManager getCoordinatesForSystem:i inGalaxy:g]; distance = distanceBetweenPlanetPositions((int)coords.x, (int)coords.y, ipos.x, ipos.y); if ((connected[i])&&(distance < min_dist)&&(distance != 0.0)) { min_dist = distance; system = i; } } return system; } /* This differs from the above function in that it can return a system * exactly at the specified coordinates */ - (OOSystemID) findConnectedSystemAtCoords:(NSPoint) coords withGalaxy:(OOGalaxyID) g { double distance; int n,i,j; double min_dist = 10000.0; // make list of connected systems BOOL connected[256]; for (i = 0; i < 256; i++) connected[i] = NO; connected[0] = YES; // system zero is always connected (true for galaxies 0..7) for (n = 0; n < 3; n++) //repeat three times for surety { for (i = 0; i < 256; i++) // flood fill out from system zero { NSPoint ipos = [systemManager getCoordinatesForSystem:i inGalaxy:g]; for (j = i+1; j < 256; j++) { NSPoint jpos = [systemManager getCoordinatesForSystem:j inGalaxy:g]; double dist = distanceBetweenPlanetPositions(ipos.x,ipos.y,jpos.x,jpos.y); if (dist <= MAX_JUMP_RANGE) { connected[j] |= connected[i]; connected[i] |= connected[j]; } } } } OOSystemID system = 0; for (i = 0; i < 256; i++) { NSPoint ipos = [systemManager getCoordinatesForSystem:i inGalaxy:g]; distance = distanceBetweenPlanetPositions((int)coords.x, (int)coords.y, ipos.x, ipos.y); if ((connected[i])&&(distance < min_dist)) { min_dist = distance; system = i; } } return system; } - (OOSystemID) findSystemNumberAtCoords:(NSPoint) coords withGalaxy:(OOGalaxyID)g { /* NOTE: this previously used NSNotFound as the default value, but returned an int, which would truncate on 64-bit systems. I assume no-one was using it in a context where the default value was returned. -- Ahruman 2012-08-25 */ OOSystemID system = kOOMinimumSystemID; unsigned distance, dx, dy; OOSystemID i; unsigned min_dist = 10000; for (i = 0; i < 256; i++) { NSPoint ipos = [systemManager getCoordinatesForSystem:i inGalaxy:g]; dx = ABS(coords.x - ipos.x); dy = ABS(coords.y - ipos.y); if (dx > dy) distance = (dx + dx + dy) / 2; else distance = (dx + dy + dy) / 2; if (distance < min_dist) { min_dist = distance; system = i; } // with coincident systems choose only if ABOVE if ((distance == min_dist)&&(coords.y > ipos.y)) { system = i; } // or if EQUAL but already selected else if ((distance == min_dist)&&(coords.y == ipos.y)&&(i==[PLAYER targetSystemID])) { system = i; } } return system; } - (NSPoint) findSystemCoordinatesWithPrefix:(NSString *) p_fix { return [self findSystemCoordinatesWithPrefix:p_fix exactMatch:NO]; } - (NSPoint) findSystemCoordinatesWithPrefix:(NSString *) p_fix exactMatch:(BOOL) exactMatch { NSString *system_name = nil; NSPoint system_coords = NSMakePoint(-1.0,-1.0); int i; int result = -1; for (i = 0; i < 256; i++) { system_found[i] = NO; system_name = [system_names[i] lowercaseString]; if ((exactMatch && [system_name isEqualToString:p_fix]) || (!exactMatch && [system_name hasPrefix:p_fix])) { system_found[i] = YES; if (result < 0) { system_coords = [systemManager getCoordinatesForSystem:i inGalaxy:galaxyID]; result = i; } } } return system_coords; } - (BOOL*) systemsFound { return (BOOL*)system_found; } - (NSString*)systemNameIndex:(OOSystemID)index { return system_names[index & 255]; } - (NSDictionary *) routeFromSystem:(OOSystemID) start toSystem:(OOSystemID) goal optimizedBy:(OORouteType) optimizeBy { /* time_cost = distance * distance jump_cost = jumps * max_total_distance + distance = max_total_tistance + distance max_total_distance is 7 * 256 max_time_cost = max_planets * max_time_cost = 256 * (7 * 7) max_jump_cost = max_planets * max_jump_cost = 256 * (7 * 256 + 7) */ // no interstellar space for start and/or goal please if (start == -1 || goal == -1) return nil; #ifdef CACHE_ROUTE_FROM_SYSTEM_RESULTS static NSDictionary *c_route = nil; static OOSystemID c_start, c_goal; static OORouteType c_optimizeBy; if (c_route != nil && c_start == start && c_goal == goal && c_optimizeBy == optimizeBy) { return c_route; } #endif unsigned i, j; if (start > 255 || goal > 255) return nil; NSArray *neighbours[256]; for (i = 0; i < 256; i++) { neighbours[i] = [self neighboursToSystem:i]; } RouteElement *cheapest[256] = {0}; double maxCost = optimizeBy == OPTIMIZED_BY_TIME ? 256 * (7 * 7) : 256 * (7 * 256 + 7); NSMutableArray *curr = [NSMutableArray arrayWithCapacity:256]; [curr addObject:cheapest[start] = [RouteElement elementWithLocation:start parent:-1 cost:0 distance:0 time:0]]; NSMutableArray *next = [NSMutableArray arrayWithCapacity:256]; while ([curr count] != 0) { for (i = 0; i < [curr count]; i++) { RouteElement *elemI = [curr objectAtIndex:i]; NSArray *ns = neighbours[[elemI location]]; for (j = 0; j < [ns count]; j++) { RouteElement *ce = cheapest[[elemI location]]; OOSystemID n = [ns oo_intAtIndex:j]; OOSystemID c = [ce location]; NSPoint cpos = [systemManager getCoordinatesForSystem:c inGalaxy:galaxyID]; NSPoint npos = [systemManager getCoordinatesForSystem:n inGalaxy:galaxyID]; double lastDistance = distanceBetweenPlanetPositions(npos.x,npos.y,cpos.x,cpos.y); double lastTime = lastDistance * lastDistance; double distance = [ce distance] + lastDistance; double time = [ce time] + lastTime; double cost = [ce cost] + (optimizeBy == OPTIMIZED_BY_TIME ? lastTime : 7 * 256 + lastDistance); if (cost < maxCost && (cheapest[n] == nil || [cheapest[n] cost] > cost)) { RouteElement *e = [RouteElement elementWithLocation:n parent:c cost:cost distance:distance time:time]; cheapest[n] = e; [next addObject:e]; if (n == goal && cost < maxCost) maxCost = cost; } } } [curr setArray:next]; [next removeAllObjects]; } if (!cheapest[goal]) return nil; NSMutableArray *route = [NSMutableArray arrayWithCapacity:256]; RouteElement *e = cheapest[goal]; for (;;) { [route insertObject:[NSNumber numberWithInt:[e location]] atIndex:0]; if ([e parent] == -1) break; e = cheapest[[e parent]]; } #ifdef CACHE_ROUTE_FROM_SYSTEM_RESULTS c_start = start; c_goal = goal; c_optimizeBy = optimizeBy; [c_route release]; c_route = [[NSDictionary alloc] initWithObjectsAndKeys: route, @"route", [NSNumber numberWithDouble:[cheapest[goal] distance]], @"distance", nil]; return c_route; #else return [NSDictionary dictionaryWithObjectsAndKeys: route, @"route", [NSNumber numberWithDouble:[cheapest[goal] distance]], @"distance", [NSNumber numberWithDouble:[cheapest[goal] time]], @"time", nil]; #endif } - (NSArray *) neighboursToSystem: (OOSystemID) s { if (s == systemID && closeSystems != nil) { return closeSystems; } NSArray *neighbours = [systemManager getNeighbourIDsForSystem:s inGalaxy:galaxyID]; if (s == systemID) { [closeSystems release]; closeSystems = [neighbours copy]; return closeSystems; } return neighbours; } /* Planet texture preloading. In order to hide the cost of synthesizing textures, we want to start rendering them asynchronously as soon as there's a hint they may be needed soon: when a system is selected on one of the charts, and when beginning a jump. However, it would be a Bad Idea™ to allow an arbitrary number of planets to be queued, since you can click on lots of systems quite quickly on the long-range chart. To rate-limit this, we track the materials that are being preloaded and only queue the ones for a new system if there are no more than two in the queue. (Currently, each system will have at most two materials, the main planet and the main planet's atmosphere, but it may be worth adding the ability to declare planets in planetinfo.plist instead of using scripts so that they can also benefit from preloading.) The preloading materials list is pruned before preloading, and also once per frame so textures can fall out of the regular cache. -- Ahruman 2009-12-19 DISABLED due to crashes on some Windows systems. Textures generated here remain in the sRecentTextures cache when released, suggesting a retain imbalance somewhere. Cannot reproduce under Mac OS X. Needs further analysis before reenabling. http://www.aegidian.org/bb/viewtopic.php?f=3&t=12109 -- Ahruman 2012-06-29 */ - (void) preloadPlanetTexturesForSystem:(OOSystemID)s { // #if NEW_PLANETS #if 0 [self prunePreloadingPlanetMaterials]; if ([_preloadingPlanetMaterials count] < 3) { if (_preloadingPlanetMaterials == nil) _preloadingPlanetMaterials = [[NSMutableArray alloc] initWithCapacity:4]; OOPlanetEntity *planet = [[OOPlanetEntity alloc] initAsMainPlanetForSystem:s]; OOMaterial *surface = [planet material]; // can be nil if texture mis-defined if (surface != nil) { // if it's already loaded, no need to continue if (![surface isFinishedLoading]) { [_preloadingPlanetMaterials addObject:surface]; // In some instances (retextured planets atm), the main planet might not have an atmosphere defined. // Trying to add nil to _preloadingPlanetMaterials will prematurely terminate the calling function.(!) --Kaks 20100107 OOMaterial *atmo = [planet atmosphereMaterial]; if (atmo != nil) [_preloadingPlanetMaterials addObject:atmo]; } } [planet release]; } #endif } - (NSDictionary *) globalSettings { return globalSettings; } - (NSArray *) equipmentData { return equipmentData; } - (OOCommodityMarket *) commodityMarket { return commodityMarket; } - (NSString *) timeDescription:(double) interval { double r_time = interval; NSString* result = @""; if (r_time > 86400) { int days = floor(r_time / 86400); r_time -= 86400 * days; result = [NSString stringWithFormat:@"%@ %d day%@", result, days, (days > 1) ? @"s" : @""]; } if (r_time > 3600) { int hours = floor(r_time / 3600); r_time -= 3600 * hours; result = [NSString stringWithFormat:@"%@ %d hour%@", result, hours, (hours > 1) ? @"s" : @""]; } if (r_time > 60) { int mins = floor(r_time / 60); r_time -= 60 * mins; result = [NSString stringWithFormat:@"%@ %d minute%@", result, mins, (mins > 1) ? @"s" : @""]; } if (r_time > 0) { int secs = floor(r_time); result = [NSString stringWithFormat:@"%@ %d second%@", result, secs, (secs > 1) ? @"s" : @""]; } return [result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; } - (NSString *) shortTimeDescription:(double) interval { double r_time = interval; NSString* result = @""; int parts = 0; if (interval <= 0.0) return DESC(@"contracts-no-time"); if (r_time > 86400) { int days = floor(r_time / 86400); r_time -= 86400 * days; result = [NSString stringWithFormat:@"%@ %d %@", result, days, DESC_PLURAL(@"contracts-day-word", days)]; parts++; } if (r_time > 3600) { int hours = floor(r_time / 3600); r_time -= 3600 * hours; result = [NSString stringWithFormat:@"%@ %d %@", result, hours, DESC_PLURAL(@"contracts-hour-word", hours)]; parts++; } if (parts < 2 && r_time > 60) { int mins = floor(r_time / 60); r_time -= 60 * mins; result = [NSString stringWithFormat:@"%@ %d %@", result, mins, DESC_PLURAL(@"contracts-minute-word", mins)]; parts++; } if (parts < 2 && r_time > 0) { int secs = floor(r_time); result = [NSString stringWithFormat:@"%@ %d %@", result, secs, DESC_PLURAL(@"contracts-second-word", secs)]; } return [result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; } - (void) makeSunSkimmer:(ShipEntity *) ship andSetAI:(BOOL)setAI { if (setAI) [ship switchAITo:@"oolite-traderAI.js"]; // perfectly acceptable for both route 2 & 3 [ship setFuel:(Ranrot()&31)]; // slow ships need extra insulation or they will burn up when sunskimming. (Tested at biggest sun in G3: Aenqute) float minInsulation = 1000 / [ship maxFlightSpeed] + 1; if ([ship heatInsulation] < minInsulation) [ship setHeatInsulation:minInsulation]; } - (Random_Seed) marketSeed { Random_Seed ret = [systemManager getRandomSeedForCurrentSystem]; // adjust basic seed by market random factor // which for (very bad) historical reasons is 0x80 ret.f ^= 0x80; // XOR back to front ret.e ^= ret.f; // XOR ret.d ^= ret.e; // XOR ret.c ^= ret.d; // XOR ret.b ^= ret.c; // XOR ret.a ^= ret.b; // XOR return ret; } - (void) loadStationMarkets:(NSArray *)marketData { if (marketData == nil) { return; } NSArray *stations = [self stations]; StationEntity *station = nil; NSDictionary *savedMarket = nil; foreach (savedMarket, marketData) { HPVector pos = [savedMarket oo_hpvectorForKey:@"position"]; foreach (station, stations) { // must be deterministic and secondary if ([station allowsSaving] && station != [UNIVERSE station]) { // allow a km of drift just in case if (HPdistance2(pos,[station position]) < 1000000) { [station setLocalMarket:[savedMarket oo_arrayForKey:@"market"]]; break; } } } } } - (NSArray *) getStationMarkets { NSMutableArray *markets = [[NSMutableArray alloc] init]; NSArray *stations = [self stations]; StationEntity *station = nil; NSMutableDictionary *savedMarket = nil; OOCommodityMarket *stationMarket = nil; foreach (station, stations) { // must be deterministic and secondary if ([station allowsSaving] && station != [UNIVERSE station]) { stationMarket = [station localMarket]; if (stationMarket != nil) { savedMarket = [NSMutableDictionary dictionaryWithCapacity:2]; [savedMarket setObject:[stationMarket saveStationAmounts] forKey:@"market"]; [savedMarket setObject:ArrayFromHPVector([station position]) forKey:@"position"]; [markets addObject:savedMarket]; } } } return [markets autorelease]; } - (NSArray *) shipsForSaleForSystem:(OOSystemID)s withTL:(OOTechLevelID)specialTL atTime:(OOTimeAbsolute)current_time { RANROTSeed saved_seed = RANROTGetFullSeed(); Random_Seed ship_seed = [self marketSeed]; NSMutableDictionary *resultDictionary = [NSMutableDictionary dictionary]; float tech_price_boost = (ship_seed.a + ship_seed.b) / 256.0; unsigned i; PlayerEntity *player = PLAYER; OOShipRegistry *registry = [OOShipRegistry sharedRegistry]; RANROTSeed personalitySeed = RanrotSeedFromRandomSeed(ship_seed); for (i = 0; i < 256; i++) { long long reference_time = 0x1000000 * floor(current_time / 0x1000000); long long c_time = ship_seed.a * 0x10000 + ship_seed.b * 0x100 + ship_seed.c; double ship_sold_time = reference_time + c_time; if (ship_sold_time < 0) ship_sold_time += 0x1000000; // wraparound double days_until_sale = (ship_sold_time - current_time) / 86400.0; NSMutableArray *keysForShips = [NSMutableArray arrayWithArray:[registry playerShipKeys]]; unsigned si; for (si = 0; si < [keysForShips count]; si++) { //eliminate any ships that fail a 'conditions test' NSString *key = [keysForShips oo_stringAtIndex:si]; NSDictionary *dict = [registry shipyardInfoForKey:key]; NSArray *conditions = [dict oo_arrayForKey:@"conditions"]; if (![player scriptTestConditions:conditions]) { [keysForShips removeObjectAtIndex:si--]; } NSString *condition_script = [dict oo_stringForKey:@"condition_script"]; if (condition_script != nil) { OOJSScript *condScript = [self getConditionScript:condition_script]; if (condScript != nil) // should always be non-nil, but just in case { JSContext *context = OOJSAcquireContext(); BOOL OK; JSBool allow_purchase; jsval result; jsval args[] = { OOJSValueFromNativeObject(context, key) }; OK = [condScript callMethod:OOJSID("allowOfferShip") inContext:context withArguments:args count:sizeof args / sizeof *args result:&result]; if (OK) OK = JS_ValueToBoolean(context, result, &allow_purchase); OOJSRelinquishContext(context); if (OK && !allow_purchase) { /* if the script exists, the function exists, the function * returns a bool, and that bool is false, block * purchase. Otherwise allow it as default */ [keysForShips removeObjectAtIndex:si--]; } } } } NSDictionary *systemInfo = [self generateSystemData:s]; OOTechLevelID techlevel; if (specialTL != NSNotFound) { //if we are passed a tech level use that techlevel = specialTL; } else { //otherwise use default for system techlevel = [systemInfo oo_unsignedIntForKey:KEY_TECHLEVEL]; } unsigned ship_index = (ship_seed.d * 0x100 + ship_seed.e) % [keysForShips count]; NSString *ship_key = [keysForShips oo_stringAtIndex:ship_index]; NSDictionary *ship_info = [registry shipyardInfoForKey:ship_key]; OOTechLevelID ship_techlevel = [ship_info oo_intForKey:KEY_TECHLEVEL]; double chance = 1.0 - pow(1.0 - [ship_info oo_doubleForKey:KEY_CHANCE], MAX((OOTechLevelID)1, techlevel - ship_techlevel)); // seed random number generator int superRand1 = ship_seed.a * 0x10000 + ship_seed.c * 0x100 + ship_seed.e; uint32_t superRand2 = ship_seed.b * 0x10000 + ship_seed.d * 0x100 + ship_seed.f; ranrot_srand(superRand2); NSDictionary* shipBaseDict = [[OOShipRegistry sharedRegistry] shipInfoForKey:ship_key]; if ((days_until_sale > 0.0) && (days_until_sale < 30.0) && (ship_techlevel <= techlevel) && (randf() < chance) && (shipBaseDict != nil)) { NSMutableDictionary* shipDict = [NSMutableDictionary dictionaryWithDictionary:shipBaseDict]; NSMutableString* shortShipDescription = [NSMutableString stringWithCapacity:256]; NSString *shipName = [shipDict oo_stringForKey:@"display_name" defaultValue:[shipDict oo_stringForKey:KEY_NAME]]; OOCreditsQuantity price = [ship_info oo_unsignedIntForKey:KEY_PRICE]; OOCreditsQuantity base_price = price; NSMutableArray* extras = [NSMutableArray arrayWithArray:[[ship_info oo_dictionaryForKey:KEY_STANDARD_EQUIPMENT] oo_arrayForKey:KEY_EQUIPMENT_EXTRAS]]; NSString* fwdWeaponString = [[ship_info oo_dictionaryForKey:KEY_STANDARD_EQUIPMENT] oo_stringForKey:KEY_EQUIPMENT_FORWARD_WEAPON]; NSString* aftWeaponString = [[ship_info oo_dictionaryForKey:KEY_STANDARD_EQUIPMENT] oo_stringForKey:KEY_EQUIPMENT_AFT_WEAPON]; NSMutableArray* options = [NSMutableArray arrayWithArray:[ship_info oo_arrayForKey:KEY_OPTIONAL_EQUIPMENT]]; OOCargoQuantity maxCargo = [shipDict oo_unsignedIntForKey:@"max_cargo"]; // more info for potential purchasers - how to reveal this I'm not yet sure... //NSString* brochure_desc = [self brochureDescriptionWithDictionary: ship_dict standardEquipment: extras optionalEquipment: options]; //NSLog(@"%@ Brochure description : \"%@\"", [ship_dict objectForKey:KEY_NAME], brochure_desc); [shortShipDescription appendFormat:@"%@:", shipName]; OOWeaponFacingSet availableFacings = [ship_info oo_unsignedIntForKey:KEY_WEAPON_FACINGS defaultValue:VALID_WEAPON_FACINGS] & VALID_WEAPON_FACINGS; OOWeaponType fwdWeapon = OOWeaponTypeFromEquipmentIdentifierSloppy(fwdWeaponString); OOWeaponType aftWeapon = OOWeaponTypeFromEquipmentIdentifierSloppy(aftWeaponString); //port and starboard weapons are not modified in the shipyard int passengerBerthCount = 0; BOOL customised = NO; BOOL weaponCustomized = NO; NSString *fwdWeaponDesc = nil; NSString *shortExtrasKey = @"shipyard-first-extra"; // for testing condition scripts ShipEntity *testship = [[ProxyPlayerEntity alloc] initWithKey:ship_key definition:shipDict]; // customise the ship (if chance = 1, then ship will get all possible add ons) while ((randf() < chance) && ([options count])) { chance *= chance; //decrease the chance of a further customisation (unless it is 1, which might be a bug) int optionIndex = Ranrot() % [options count]; NSString *equipmentKey = [options oo_stringAtIndex:optionIndex]; OOEquipmentType *item = [OOEquipmentType equipmentTypeWithIdentifier:equipmentKey]; if (item != nil) { OOTechLevelID eqTechLevel = [item techLevel]; OOCreditsQuantity eqPrice = [item price] / 10; // all amounts are x/10 due to being represented in tenths of credits. NSString *eqShortDesc = [item name]; if ([item techLevel] > techlevel) { // Cap maximum tech level. eqTechLevel = MIN(eqTechLevel, 15U); // Higher tech items are rarer! if (randf() * (eqTechLevel - techlevel) < 1.0) { // All included equip has a 10% discount. eqPrice *= (tech_price_boost + eqTechLevel - techlevel) * 90 / 100; } else break; // Bar this upgrade. } if ([item incompatibleEquipment] != nil && extras != nil) { NSEnumerator *keyEnum = nil; id key = nil; BOOL incompatible = NO; for (keyEnum = [[item incompatibleEquipment] objectEnumerator]; (key = [keyEnum nextObject]); ) { if ([extras containsObject:key]) { [options removeObject:equipmentKey]; incompatible = YES; break; } } if (incompatible) break; // make sure the incompatible equipment is not choosen later on. for (keyEnum = [[item incompatibleEquipment] objectEnumerator]; (key = [keyEnum nextObject]); ) { if ([options containsObject:key]) { [options removeObject:key]; } } } /* Check condition scripts */ NSString *condition_script = [item conditionScript]; if (condition_script != nil) { OOJSScript *condScript = [self getConditionScript:condition_script]; if (condScript != nil) // should always be non-nil, but just in case { JSContext *JScontext = OOJSAcquireContext(); BOOL OK; JSBool allow_addition; jsval result; jsval args[] = { OOJSValueFromNativeObject(JScontext, equipmentKey) , OOJSValueFromNativeObject(JScontext, testship) , OOJSValueFromNativeObject(JScontext, @"newShip")}; OK = [condScript callMethod:OOJSID("allowAwardEquipment") inContext:JScontext withArguments:args count:sizeof args / sizeof *args result:&result]; if (OK) OK = JS_ValueToBoolean(JScontext, result, &allow_addition); OOJSRelinquishContext(JScontext); if (OK && !allow_addition) { /* if the script exists, the function exists, the function * returns a bool, and that bool is false, block * addition. Otherwise allow it as default */ break; } } } if ([item requiresEquipment] != nil && extras != nil) { NSEnumerator *keyEnum = nil; id key = nil; BOOL missing = NO; for (keyEnum = [[item requiresEquipment] objectEnumerator]; (key = [keyEnum nextObject]); ) { if (![extras containsObject:key]) { missing = YES; } } if (missing) break; } if ([item requiresAnyEquipment] != nil && extras != nil) { NSEnumerator *keyEnum = nil; id key = nil; BOOL missing = YES; for (keyEnum = [[item requiresAnyEquipment] objectEnumerator]; (key = [keyEnum nextObject]); ) { if ([extras containsObject:key]) { missing = NO; } } if (missing) break; } // Special case, NEU has to be compatible with EEU inside equipment.plist // but we can only have either one or the other on board. if ([equipmentKey isEqualTo:@"EQ_NAVAL_ENERGY_UNIT"]) { if ([extras containsObject:@"EQ_ENERGY_UNIT"]) { [options removeObject:equipmentKey]; break; } } if ([equipmentKey hasPrefix:@"EQ_WEAPON"]) { OOWeaponType new_weapon = OOWeaponTypeFromEquipmentIdentifierSloppy(equipmentKey); //fit best weapon forward if (availableFacings & WEAPON_FACING_FORWARD && [new_weapon weaponThreatAssessment] > [fwdWeapon weaponThreatAssessment]) { //again remember to divide price by 10 to get credits from tenths of credit price -= [self getEquipmentPriceForKey:fwdWeaponString] * 90 / 1000; // 90% credits price += eqPrice; fwdWeaponString = equipmentKey; fwdWeapon = new_weapon; [shipDict setObject:fwdWeaponString forKey:KEY_EQUIPMENT_FORWARD_WEAPON]; weaponCustomized = YES; fwdWeaponDesc = eqShortDesc; } else { //if less good than current forward, try fitting is to rear if (availableFacings & WEAPON_FACING_AFT && (isWeaponNone(aftWeapon) || [new_weapon weaponThreatAssessment] > [aftWeapon weaponThreatAssessment])) { price -= [self getEquipmentPriceForKey:aftWeaponString] * 90 / 1000; // 90% credits price += eqPrice; aftWeaponString = equipmentKey; aftWeapon = new_weapon; [shipDict setObject:aftWeaponString forKey:KEY_EQUIPMENT_AFT_WEAPON]; } else { [options removeObject:equipmentKey]; //dont try again } } } else { if ([equipmentKey isEqualToString:@"EQ_PASSENGER_BERTH"]) { if ((maxCargo >= PASSENGER_BERTH_SPACE) && (randf() < chance)) { maxCargo -= PASSENGER_BERTH_SPACE; price += eqPrice; [extras addObject:equipmentKey]; passengerBerthCount++; customised = YES; } else { // remove the option if there's no space left [options removeObject:equipmentKey]; } } else { price += eqPrice; [extras addObject:equipmentKey]; if ([item isVisible]) { NSString *item = eqShortDesc; [shortShipDescription appendString:OOExpandKey(shortExtrasKey, item)]; shortExtrasKey = @"shipyard-additional-extra"; } customised = YES; [options removeObject:equipmentKey]; //dont add twice } } } else { [options removeObject:equipmentKey]; } } // end adding optional equipment [testship release]; // i18n: Some languages require that no conversion to lower case string takes place. BOOL lowercaseIgnore = [[self descriptions] oo_boolForKey:@"lowercase_ignore"]; if (passengerBerthCount) { NSString* npb = (passengerBerthCount > 1)? [NSString stringWithFormat:@"%d ", passengerBerthCount] : (id)@""; NSString* ppb = DESC_PLURAL(@"passenger-berth", passengerBerthCount); NSString* extraPassengerBerthsDescription = [NSString stringWithFormat:DESC(@"extra-@-@-(passenger-berths)"), npb, ppb]; NSString *item = extraPassengerBerthsDescription; [shortShipDescription appendString:OOExpandKey(shortExtrasKey, item)]; shortExtrasKey = @"shipyard-additional-extra"; } if (!customised) { [shortShipDescription appendString:OOExpandKey(@"shipyard-standard-customer-model")]; } if (weaponCustomized) { NSString *weapon = (lowercaseIgnore ? fwdWeaponDesc : [fwdWeaponDesc lowercaseString]); [shortShipDescription appendString:OOExpandKey(@"shipyard-forward-weapon-upgraded", weapon)]; } if (price > base_price) { price = base_price + cunningFee(price - base_price, 0.05); } [shortShipDescription appendString:OOExpandKey(@"shipyard-price", price)]; NSString *shipID = [NSString stringWithFormat:@"%06x-%06x", superRand1, superRand2]; uint16_t personality = RanrotWithSeed(&personalitySeed) & ENTITY_PERSONALITY_MAX; NSDictionary *ship_info_dictionary = [NSDictionary dictionaryWithObjectsAndKeys: shipID, SHIPYARD_KEY_ID, ship_key, SHIPYARD_KEY_SHIPDATA_KEY, shipDict, SHIPYARD_KEY_SHIP, shortShipDescription, KEY_SHORT_DESCRIPTION, [NSNumber numberWithUnsignedLongLong:price], SHIPYARD_KEY_PRICE, extras, KEY_EQUIPMENT_EXTRAS, [NSNumber numberWithUnsignedShort:personality], SHIPYARD_KEY_PERSONALITY, NULL]; [resultDictionary setObject:ship_info_dictionary forKey:shipID]; // should order them fairly randomly } // next contract rotate_seed(&ship_seed); rotate_seed(&ship_seed); rotate_seed(&ship_seed); rotate_seed(&ship_seed); } NSMutableArray *resultArray = [[[resultDictionary allValues] mutableCopy] autorelease]; [resultArray sortUsingFunction:compareName context:NULL]; // remove identically priced ships of the same name i = 1; while (i < [resultArray count]) { if (compareName([resultArray objectAtIndex:i - 1], [resultArray objectAtIndex:i], nil) == NSOrderedSame ) { [resultArray removeObjectAtIndex: i]; } else { i++; } } RANROTSetFullSeed(saved_seed); return [NSArray arrayWithArray:resultArray]; } static OOComparisonResult compareName(id dict1, id dict2, void *context) { NSDictionary *ship1 = [(NSDictionary *)dict1 oo_dictionaryForKey:SHIPYARD_KEY_SHIP]; NSDictionary *ship2 = [(NSDictionary *)dict2 oo_dictionaryForKey:SHIPYARD_KEY_SHIP]; NSString *name1 = [ship1 oo_stringForKey:KEY_NAME]; NSString *name2 = [ship2 oo_stringForKey:KEY_NAME]; NSComparisonResult result = [[name1 lowercaseString] compare:[name2 lowercaseString]]; if (result != NSOrderedSame) return result; else return comparePrice(dict1, dict2, context); } static OOComparisonResult comparePrice(id dict1, id dict2, void *context) { NSNumber *price1 = [(NSDictionary *)dict1 objectForKey:SHIPYARD_KEY_PRICE]; NSNumber *price2 = [(NSDictionary *)dict2 objectForKey:SHIPYARD_KEY_PRICE]; return [price1 compare:price2]; } - (OOCreditsQuantity) tradeInValueForCommanderDictionary:(NSDictionary *)dict { // get basic information about the craft OOCreditsQuantity base_price = 0ULL; NSString *ship_desc = [dict oo_stringForKey:@"ship_desc"]; NSDictionary *shipyard_info = [[OOShipRegistry sharedRegistry] shipyardInfoForKey:ship_desc]; // This checks a rare, but possible case. If the ship for which we are trying to calculate a trade in value // does not have a shipyard dictionary entry, report it and set its base price to 0 -- Nikos 20090613. if (shipyard_info == nil) { OOLogERR(@"universe.tradeInValueForCommanderDictionary.valueCalculationError", @"Shipyard dictionary entry for ship %@ required for trade in value calculation, but does not exist. Setting ship value to 0.", ship_desc); } else { base_price = [shipyard_info oo_unsignedLongLongForKey:SHIPYARD_KEY_PRICE defaultValue:0ULL]; } if(base_price == 0ULL) return base_price; OOCreditsQuantity scrap_value = 351; // translates to 250 cr. OOWeaponType ship_fwd_weapon = [OOEquipmentType equipmentTypeWithIdentifier:[dict oo_stringForKey:@"forward_weapon"]]; OOWeaponType ship_aft_weapon = [OOEquipmentType equipmentTypeWithIdentifier:[dict oo_stringForKey:@"aft_weapon"]]; OOWeaponType ship_port_weapon = [OOEquipmentType equipmentTypeWithIdentifier:[dict oo_stringForKey:@"port_weapon"]]; OOWeaponType ship_starboard_weapon = [OOEquipmentType equipmentTypeWithIdentifier:[dict oo_stringForKey:@"starboard_weapon"]]; unsigned ship_missiles = [dict oo_unsignedIntForKey:@"missiles"]; unsigned ship_max_passengers = [dict oo_unsignedIntForKey:@"max_passengers"]; NSMutableArray *ship_extra_equipment = [NSMutableArray arrayWithArray:[[dict oo_dictionaryForKey:@"extra_equipment"] allKeys]]; NSDictionary *basic_info = [shipyard_info oo_dictionaryForKey:KEY_STANDARD_EQUIPMENT]; unsigned base_missiles = [basic_info oo_unsignedIntForKey:KEY_EQUIPMENT_MISSILES]; OOCreditsQuantity base_missiles_value = base_missiles * [UNIVERSE getEquipmentPriceForKey:@"EQ_MISSILE"] / 10; NSString *base_weapon_key = [basic_info oo_stringForKey:KEY_EQUIPMENT_FORWARD_WEAPON]; OOCreditsQuantity base_weapons_value = [UNIVERSE getEquipmentPriceForKey:base_weapon_key] / 10; NSMutableArray *base_extra_equipment = [NSMutableArray arrayWithArray:[basic_info oo_arrayForKey:KEY_EQUIPMENT_EXTRAS]]; NSString *weapon_key = nil; // was aft_weapon defined as standard equipment ? base_weapon_key = [basic_info oo_stringForKey:KEY_EQUIPMENT_AFT_WEAPON defaultValue:nil]; if (base_weapon_key != nil) base_weapons_value += [UNIVERSE getEquipmentPriceForKey:base_weapon_key] / 10; OOCreditsQuantity ship_main_weapons_value = 0; OOCreditsQuantity ship_other_weapons_value = 0; OOCreditsQuantity ship_missiles_value = 0; // calculate the actual value for the missiles present on board. NSArray *missileRoles = [dict oo_arrayForKey:@"missile_roles"]; if (missileRoles != nil) { unsigned i; for (i = 0; i < ship_missiles; i++) { NSString *missile_desc = [missileRoles oo_stringAtIndex:i]; if (missile_desc != nil && ![missile_desc isEqualToString:@"NONE"]) { ship_missiles_value += [UNIVERSE getEquipmentPriceForKey:missile_desc] / 10; } } } else ship_missiles_value = ship_missiles * [UNIVERSE getEquipmentPriceForKey:@"EQ_MISSILE"] / 10; // needs to be a signed value, we can then subtract from the base price, if less than standard equipment. long long extra_equipment_value = ship_max_passengers * [UNIVERSE getEquipmentPriceForKey:@"EQ_PASSENGER_BERTH"]/10; // add on missile values extra_equipment_value += ship_missiles_value - base_missiles_value; // work out weapon values if (ship_fwd_weapon) { weapon_key = OOEquipmentIdentifierFromWeaponType(ship_fwd_weapon); ship_main_weapons_value = [UNIVERSE getEquipmentPriceForKey:weapon_key] / 10; } if (ship_aft_weapon) { weapon_key = OOEquipmentIdentifierFromWeaponType(ship_aft_weapon); if (base_weapon_key != nil) // aft weapon was defined as a base weapon { ship_main_weapons_value += [UNIVERSE getEquipmentPriceForKey:weapon_key] / 10; //take weapon downgrades into account } else { ship_other_weapons_value += [UNIVERSE getEquipmentPriceForKey:weapon_key] / 10; } } if (ship_port_weapon) { weapon_key = OOEquipmentIdentifierFromWeaponType(ship_port_weapon); ship_other_weapons_value += [UNIVERSE getEquipmentPriceForKey:weapon_key] / 10; } if (ship_starboard_weapon) { weapon_key = OOEquipmentIdentifierFromWeaponType(ship_starboard_weapon); ship_other_weapons_value += [UNIVERSE getEquipmentPriceForKey:weapon_key] / 10; } // add on extra weapons, take away the value of the base weapons extra_equipment_value += ship_other_weapons_value; extra_equipment_value += ship_main_weapons_value - base_weapons_value; NSInteger i; NSString *eq_key = nil; // shipyard.plist settings might have duplicate keys. // cull possible duplicates from inside base equipment for (i = [base_extra_equipment count]-1; i > 0;i--) { eq_key = [base_extra_equipment oo_stringAtIndex:i]; if ([base_extra_equipment indexOfObject:eq_key inRange:NSMakeRange(0, i-1)] != NSNotFound) [base_extra_equipment removeObjectAtIndex:i]; } // do we at least have the same equipment as a standard ship? for (i = [base_extra_equipment count]-1; i >= 0; i--) { eq_key = [base_extra_equipment oo_stringAtIndex:i]; if ([ship_extra_equipment containsObject:eq_key]) [ship_extra_equipment removeObject:eq_key]; else // if the ship has less equipment than standard, deduct the missing equipent's price extra_equipment_value -= ([UNIVERSE getEquipmentPriceForKey:eq_key] / 10); } // remove portable equipment from the totals OOEquipmentType *item = nil; for (i = [ship_extra_equipment count]-1; i >= 0; i--) { eq_key = [ship_extra_equipment oo_stringAtIndex:i]; item = [OOEquipmentType equipmentTypeWithIdentifier:eq_key]; if ([item isPortableBetweenShips]) [ship_extra_equipment removeObjectAtIndex:i]; } // add up what we've got left. for (i = [ship_extra_equipment count]-1; i >= 0; i--) extra_equipment_value += ([UNIVERSE getEquipmentPriceForKey:[ship_extra_equipment oo_stringAtIndex:i]] / 10); // 10% discount for second hand value, steeper reduction if worse than standard. extra_equipment_value *= extra_equipment_value < 0 ? 1.4 : 0.9; // we'll return at least the scrap value // TODO: calculate scrap value based on the size of the ship. if ((long long)scrap_value > (long long)base_price + extra_equipment_value) return scrap_value; return base_price + extra_equipment_value; } - (NSString *) brochureDescriptionWithDictionary:(NSDictionary *)dict standardEquipment:(NSArray *)extras optionalEquipment:(NSArray *)options { NSMutableArray *mut_extras = [NSMutableArray arrayWithArray:extras]; NSString *allOptions = [options componentsJoinedByString:@" "]; NSMutableString *desc = [NSMutableString stringWithFormat:@"The %@.", [dict oo_stringForKey: KEY_NAME]]; // cargo capacity and expansion OOCargoQuantity max_cargo = [dict oo_unsignedIntForKey:@"max_cargo"]; if (max_cargo) { OOCargoQuantity extra_cargo = [dict oo_unsignedIntForKey:@"extra_cargo" defaultValue:15]; [desc appendFormat:@" Cargo capacity %dt", max_cargo]; BOOL canExpand = ([allOptions rangeOfString:@"EQ_CARGO_BAY"].location != NSNotFound); if (canExpand) [desc appendFormat:@" (expandable to %dt at most starports)", max_cargo + extra_cargo]; [desc appendString:@"."]; } // speed float top_speed = [dict oo_intForKey:@"max_flight_speed"]; [desc appendFormat:@" Top speed %.3fLS.", 0.001 * top_speed]; // passenger berths if ([mut_extras count]) { unsigned n_berths = 0; unsigned i; for (i = 0; i < [mut_extras count]; i++) { NSString* item_key = [mut_extras oo_stringAtIndex:i]; if ([item_key isEqual:@"EQ_PASSENGER_BERTH"]) { n_berths++; [mut_extras removeObjectAtIndex:i--]; } } if (n_berths) { if (n_berths == 1) [desc appendString:@" Includes luxury accomodation for a single passenger."]; else [desc appendFormat:@" Includes luxury accomodation for %d passengers.", n_berths]; } } // standard fittings if ([mut_extras count]) { [desc appendString:@"\nComes with"]; unsigned i, j; for (i = 0; i < [mut_extras count]; i++) { NSString* item_key = [mut_extras oo_stringAtIndex:i]; NSString* item_desc = nil; for (j = 0; ((j < [equipmentData count])&&(!item_desc)) ; j++) { NSString *eq_type = [[equipmentData oo_arrayAtIndex:j] oo_stringAtIndex:EQUIPMENT_KEY_INDEX]; if ([eq_type isEqual:item_key]) item_desc = [[equipmentData oo_arrayAtIndex:j] oo_stringAtIndex:EQUIPMENT_SHORT_DESC_INDEX]; } if (item_desc) { switch ([mut_extras count] - i) { case 1: [desc appendFormat:@" %@ fitted as standard.", item_desc]; break; case 2: [desc appendFormat:@" %@ and", item_desc]; break; default: [desc appendFormat:@" %@,", item_desc]; break; } } } } // optional fittings if ([options count]) { [desc appendString:@"\nCan additionally be outfitted with"]; unsigned i, j; for (i = 0; i < [options count]; i++) { NSString* item_key = [options oo_stringAtIndex:i]; NSString* item_desc = nil; for (j = 0; ((j < [equipmentData count])&&(!item_desc)) ; j++) { NSString *eq_type = [[equipmentData oo_arrayAtIndex:j] oo_stringAtIndex:EQUIPMENT_KEY_INDEX]; if ([eq_type isEqual:item_key]) item_desc = [[equipmentData oo_arrayAtIndex:j] oo_stringAtIndex:EQUIPMENT_SHORT_DESC_INDEX]; } if (item_desc) { switch ([options count] - i) { case 1: [desc appendFormat:@" %@ at suitably equipped starports.", item_desc]; break; case 2: [desc appendFormat:@" %@ and/or", item_desc]; break; default: [desc appendFormat:@" %@,", item_desc]; break; } } } } return desc; } - (HPVector) getWitchspaceExitPosition { return kZeroHPVector; } - (Quaternion) getWitchspaceExitRotation { // this should be fairly close to {0,0,0,1} Quaternion q_result; // CIM: seems to be no reason why this should be a per-system constant // - trying it without resetting the RNG for now // seed_RNG_only_for_planet_description(system_seed); q_result.x = (gen_rnd_number() - 128)/1024.0; q_result.y = (gen_rnd_number() - 128)/1024.0; q_result.z = (gen_rnd_number() - 128)/1024.0; q_result.w = 1.0; quaternion_normalize(&q_result); return q_result; } // FIXME: should use vector functions - (HPVector) getSunSkimStartPositionForShip:(ShipEntity*) ship { if (!ship) { OOLog(kOOLogParameterError, @"***** No ship set in Universe getSunSkimStartPositionForShip:"); return kZeroHPVector; } OOSunEntity* the_sun = [self sun]; // get vector from sun position to ship if (!the_sun) { OOLog(kOOLogInconsistentState, @"***** No sun set in Universe getSunSkimStartPositionForShip:"); return kZeroHPVector; } HPVector v0 = the_sun->position; HPVector v1 = ship->position; v1.x -= v0.x; v1.y -= v0.y; v1.z -= v0.z; // vector from sun to ship if (v1.x||v1.y||v1.z) v1 = HPvector_normal(v1); else v1.z = 1.0; double radius = SUN_SKIM_RADIUS_FACTOR * the_sun->collision_radius - 250.0; // 250 m inside the skim radius v1.x *= radius; v1.y *= radius; v1.z *= radius; v1.x += v0.x; v1.y += v0.y; v1.z += v0.z; return v1; } // FIXME: should use vector functions - (HPVector) getSunSkimEndPositionForShip:(ShipEntity*) ship { OOSunEntity* the_sun = [self sun]; if (!ship) { OOLog(kOOLogParameterError, @"***** No ship set in Universe getSunSkimEndPositionForShip:"); return kZeroHPVector; } // get vector from sun position to ship if (!the_sun) { OOLog(kOOLogInconsistentState, @"***** No sun set in Universe getSunSkimEndPositionForShip:"); return kZeroHPVector; } HPVector v0 = the_sun->position; HPVector v1 = ship->position; v1.x -= v0.x; v1.y -= v0.y; v1.z -= v0.z; if (v1.x||v1.y||v1.z) v1 = HPvector_normal(v1); else v1.z = 1.0; HPVector v2 = make_HPvector(randf()-0.5, randf()-0.5, randf()-0.5); // random vector if (v2.x||v2.y||v2.z) v2 = HPvector_normal(v2); else v2.x = 1.0; HPVector v3 = HPcross_product(v1, v2); // random vector at 90 degrees to v1 and v2 (random Vector) if (v3.x||v3.y||v3.z) v3 = HPvector_normal(v3); else v3.y = 1.0; double radius = SUN_SKIM_RADIUS_FACTOR * the_sun->collision_radius - 250.0; // 250 m inside the skim radius v1.x *= radius; v1.y *= radius; v1.z *= radius; v1.x += v0.x; v1.y += v0.y; v1.z += v0.z; v1.x += 15000 * v3.x; v1.y += 15000 * v3.y; v1.z += 15000 * v3.z; // point 15000m at a tangent to sun from v1 v1.x -= v0.x; v1.y -= v0.y; v1.z -= v0.z; if (v1.x||v1.y||v1.z) v1 = HPvector_normal(v1); else v1.z = 1.0; v1.x *= radius; v1.y *= radius; v1.z *= radius; v1.x += v0.x; v1.y += v0.y; v1.z += v0.z; return v1; } - (NSArray *) listBeaconsWithCode:(NSString *)code { NSMutableArray *result = [NSMutableArray array]; Entity *beacon = [self firstBeacon]; while (beacon != nil) { NSString *beaconCode = [beacon beaconCode]; if ([beaconCode rangeOfString:code options: NSCaseInsensitiveSearch].location != NSNotFound) { [result addObject:beacon]; } beacon = [beacon nextBeacon]; } return [result sortedArrayUsingSelector:@selector(compareBeaconCodeWith:)]; } - (void) allShipsDoScriptEvent:(jsid)event andReactToAIMessage:(NSString *)message { int i; int ent_count = n_entities; int ship_count = 0; ShipEntity* my_ships[ent_count]; for (i = 0; i < ent_count; i++) { if (sortedEntities[i]->isShip) { my_ships[ship_count++] = [(ShipEntity *)sortedEntities[i] retain]; // retained } } for (i = 0; i < ship_count; i++) { ShipEntity* se = my_ships[i]; [se doScriptEvent:event]; if (message != nil) [[se getAI] reactToMessage:message context:@"global message"]; [se release]; // released } } /////////////////////////////////////// - (GuiDisplayGen *) gui { return gui; } - (GuiDisplayGen *) commLogGUI { return comm_log_gui; } - (GuiDisplayGen *) messageGUI { return message_gui; } - (void) clearGUIs { [gui clear]; [message_gui clear]; [comm_log_gui clear]; [comm_log_gui printLongText:DESC(@"communications-log-string") align:GUI_ALIGN_CENTER color:[OOColor yellowColor] fadeTime:0 key:nil addToArray:nil]; } - (void) resetCommsLogColor { [comm_log_gui setTextColor:[OOColor whiteColor]]; } - (void) setDisplayText:(BOOL) value { displayGUI = !!value; } - (BOOL) displayGUI { return displayGUI; } - (void) setDisplayFPS:(BOOL) value { displayFPS = !!value; } - (BOOL) displayFPS { return displayFPS; } - (void) setAutoSave:(BOOL) value { autoSave = !!value; [[NSUserDefaults standardUserDefaults] setBool:autoSave forKey:@"autosave"]; } - (BOOL) autoSave { return autoSave; } - (void) setAutoSaveNow:(BOOL) value { autoSaveNow = !!value; } - (BOOL) autoSaveNow { return autoSaveNow; } - (void) setWireframeGraphics:(BOOL) value { wireframeGraphics = !!value; [[NSUserDefaults standardUserDefaults] setBool:wireframeGraphics forKey:@"wireframe-graphics"]; } - (BOOL) wireframeGraphics { return wireframeGraphics; } - (BOOL) reducedDetail { return detailLevel == DETAIL_LEVEL_MINIMUM; } /* Only to be called directly at initialisation */ - (void) setDetailLevelDirectly:(OOGraphicsDetail)value { if (value >= DETAIL_LEVEL_MAXIMUM) { value = DETAIL_LEVEL_MAXIMUM; } else if (value <= DETAIL_LEVEL_MINIMUM) { value = DETAIL_LEVEL_MINIMUM; } if (![[OOOpenGLExtensionManager sharedManager] shadersSupported]) { value = DETAIL_LEVEL_MINIMUM; } detailLevel = value; } - (void) setDetailLevel:(OOGraphicsDetail)value { OOGraphicsDetail old = detailLevel; [self setDetailLevelDirectly:value]; [[NSUserDefaults standardUserDefaults] setInteger:detailLevel forKey:@"detailLevel"]; // if changed then reset graphics state // (some items now require this even if shader on/off mode unchanged) if (old != detailLevel) { OOLog(@"rendering.detail-level", @"Detail level set to %@.", OOStringFromGraphicsDetail(detailLevel)); [[OOGraphicsResetManager sharedManager] resetGraphicsState]; } } - (OOGraphicsDetail) detailLevel { return detailLevel; } - (BOOL) useShaders { return detailLevel >= DETAIL_LEVEL_SHADERS; } - (void) handleOoliteException:(NSException *)exception { if (exception != nil) { if ([[exception name] isEqual:OOLITE_EXCEPTION_FATAL]) { PlayerEntity *player = PLAYER; [player setStatus:STATUS_HANDLING_ERROR]; OOLog(kOOLogException, @"***** Handling Fatal : %@ : %@ *****",[exception name], [exception reason]); NSString* exception_msg = [NSString stringWithFormat:@"Exception : %@ : %@ Please take a screenshot and/or press esc or Q to quit.", [exception name], [exception reason]]; [self addMessage:exception_msg forCount:30.0]; [[self gameController] setGamePaused:YES]; } else { OOLog(kOOLogException, @"***** Handling Non-fatal : %@ : %@ *****",[exception name], [exception reason]); } } } - (GLfloat)airResistanceFactor { return airResistanceFactor; } // speech routines #if OOLITE_MAC_OS_X - (void) startSpeakingString:(NSString *) text { [speechSynthesizer startSpeakingString:[NSString stringWithFormat:@"[[volm %.3f]]%@", 0.3333333f * [OOSound masterVolume], text]]; } - (void) stopSpeaking { if ([speechSynthesizer respondsToSelector:@selector(stopSpeakingAtBoundary:)]) { [speechSynthesizer stopSpeakingAtBoundary:NSSpeechWordBoundary]; } else { [speechSynthesizer stopSpeaking]; } } - (BOOL) isSpeaking { return [speechSynthesizer isSpeaking]; } #elif OOLITE_ESPEAK - (void) startSpeakingString:(NSString *) text { NSData *utf8 = [text dataUsingEncoding:NSUTF8StringEncoding]; if (utf8 != nil) // we have a valid UTF-8 string { const char *stringToSay = [text UTF8String]; espeak_Synth(stringToSay, strlen(stringToSay) + 1 /* inc. NULL */, 0, POS_CHARACTER, 0, espeakCHARS_UTF8 | espeakPHONEMES | espeakENDPAUSE, NULL, NULL); } } - (void) stopSpeaking { espeak_Cancel(); } - (BOOL) isSpeaking { return espeak_IsPlaying(); } - (NSString *) voiceName:(unsigned int) index { if (index >= espeak_voice_count) return @"-"; return [NSString stringWithCString: espeak_voices[index]->name]; } - (unsigned int) voiceNumber:(NSString *) name { if (name == nil) return UINT_MAX; const char *const label = [name UTF8String]; if (!label) return UINT_MAX; unsigned int index = -1; while (espeak_voices[++index] && strcmp (espeak_voices[index]->name, label)) /**/; return (index < espeak_voice_count) ? index : UINT_MAX; } - (unsigned int) nextVoice:(unsigned int) index { if (++index >= espeak_voice_count) index = 0; return index; } - (unsigned int) prevVoice:(unsigned int) index { if (--index >= espeak_voice_count) index = espeak_voice_count - 1; return index; } - (unsigned int) setVoice:(unsigned int) index withGenderM:(BOOL) isMale { if (index == UINT_MAX) index = [self voiceNumber:DESC(@"espeak-default-voice")]; if (index < espeak_voice_count) { espeak_VOICE voice = { espeak_voices[index]->name, NULL, NULL, isMale ? 1 : 2 }; espeak_SetVoiceByProperties (&voice); } return index; } #else - (void) startSpeakingString:(NSString *) text {} - (void) stopSpeaking {} - (BOOL) isSpeaking { return NO; } #endif - (BOOL) pauseMessageVisible { return _pauseMessage; } - (void) setPauseMessageVisible:(BOOL)value { _pauseMessage = value; } - (BOOL) permanentMessageLog { return _permanentMessageLog; } - (void) setPermanentMessageLog:(BOOL)value { _permanentMessageLog = value; } - (BOOL) permanentCommLog { return _permanentCommLog; } - (void) setPermanentCommLog:(BOOL)value { _permanentCommLog = value; } - (void) setAutoCommLog:(BOOL)value { _autoCommLog = value; } - (BOOL) blockJSPlayerShipProps { return gOOJSPlayerIfStale != nil; } - (void) setBlockJSPlayerShipProps:(BOOL)value { if (value) { gOOJSPlayerIfStale = PLAYER; } else { gOOJSPlayerIfStale = nil; } } - (void) setUpSettings { [self resetBeacons]; next_universal_id = 100; // start arbitrarily above zero memset(entity_for_uid, 0, sizeof entity_for_uid); [self setMainLightPosition:kZeroVector]; [gui autorelease]; gui = [[GuiDisplayGen alloc] init]; [gui setTextColor:[OOColor colorWithDescription:[[gui userSettings] objectForKey:kGuiDefaultTextColor]]]; // message_gui and comm_log_gui defaults are set up inside [hud resetGuis:] ( via [player deferredInit], called from the code that calls this method). [message_gui autorelease]; message_gui = [[GuiDisplayGen alloc] initWithPixelSize:NSMakeSize(480, 160) columns:1 rows:9 rowHeight:19 rowStart:20 title:nil]; [comm_log_gui autorelease]; comm_log_gui = [[GuiDisplayGen alloc] initWithPixelSize:NSMakeSize(360, 120) columns:1 rows:10 rowHeight:12 rowStart:12 title:nil]; // time_delta = 0.0; #ifndef NDEBUG [self setTimeAccelerationFactor:TIME_ACCELERATION_FACTOR_DEFAULT]; #endif universal_time = 0.0; messageRepeatTime = 0.0; countdown_messageRepeatTime = 0.0; #if OOLITE_SPEECH_SYNTH [speechArray autorelease]; speechArray = [[ResourceManager arrayFromFilesNamed:@"speech_pronunciation_guide.plist" inFolder:@"Config" andMerge:YES] retain]; #endif [commodities autorelease]; commodities = [[OOCommodities alloc] init]; [self loadDescriptions]; [characters autorelease]; characters = [[ResourceManager dictionaryFromFilesNamed:@"characters.plist" inFolder:@"Config" andMerge:YES] retain]; [customSounds autorelease]; customSounds = [[ResourceManager dictionaryFromFilesNamed:@"customsounds.plist" inFolder:@"Config" andMerge:YES] retain]; [globalSettings autorelease]; globalSettings = [[ResourceManager dictionaryFromFilesNamed:@"global-settings.plist" inFolder:@"Config" mergeMode:MERGE_SMART cache:YES] retain]; [systemManager autorelease]; systemManager = [[ResourceManager systemDescriptionManager] retain]; [screenBackgrounds autorelease]; screenBackgrounds = [[ResourceManager dictionaryFromFilesNamed:@"screenbackgrounds.plist" inFolder:@"Config" andMerge:YES] retain]; // role-categories.plist and pirate-victim-roles.plist [roleCategories autorelease]; roleCategories = [[ResourceManager roleCategoriesDictionary] retain]; [autoAIMap autorelease]; autoAIMap = [[ResourceManager dictionaryFromFilesNamed:@"autoAImap.plist" inFolder:@"Config" andMerge:YES] retain]; [equipmentData autorelease]; NSArray *equipmentTemp = [ResourceManager arrayFromFilesNamed:@"equipment.plist" inFolder:@"Config" andMerge:YES]; equipmentData = [[equipmentTemp sortedArrayUsingFunction:equipmentSort context:NULL] retain]; [OOEquipmentType loadEquipment]; [explosionSettings autorelease]; explosionSettings = [[ResourceManager dictionaryFromFilesNamed:@"explosions.plist" inFolder:@"Config" andMerge:YES] retain]; } - (void) setUpCargoPods { NSMutableDictionary *tmp = [[NSMutableDictionary alloc] initWithCapacity:[commodities count]]; OOCommodityType type = nil; foreach (type, [commodities goods]) { ShipEntity *container = [self newShipWithRole:@"oolite-template-cargopod"]; [container setScanClass:CLASS_CARGO]; [container setCommodity:type andAmount:1]; [tmp setObject:container forKey:type]; [container release]; } [cargoPods release]; cargoPods = [[NSDictionary alloc] initWithDictionary:tmp]; [tmp release]; } - (void) verifyEntitySessionIDs { #ifndef NDEBUG NSMutableArray *badEntities = nil; Entity *entity = nil; unsigned i; for (i = 0; i < n_entities; i++) { entity = sortedEntities[i]; if ([entity sessionID] != _sessionID) { OOLogERR(@"universe.sessionIDs.verify.failed", @"Invalid entity %@ (came from session %lu, current session is %lu).", [entity shortDescription], [entity sessionID], _sessionID); if (badEntities == nil) badEntities = [NSMutableArray array]; [badEntities addObject:entity]; } } foreach (entity, badEntities) { [self removeEntity:entity]; } #endif } // FIXME: needs less redundancy? - (BOOL) reinitAndShowDemo:(BOOL) showDemo { no_update = YES; PlayerEntity* player = PLAYER; assert(player != nil); if (JSResetFlags != 0) // JS reset failed, remember previous settings { showDemo = (JSResetFlags & 2) > 0; // binary 10, a.k.a. 1 << 1 } else { JSResetFlags = (showDemo << 1); } [self removeAllEntitiesExceptPlayer]; [OOTexture clearCache]; _sessionID++; // Must be after removing old entities and before adding new ones. [ResourceManager setUseAddOns:useAddOns]; // also logs the paths //[ResourceManager loadScripts]; // initialised inside [player setUp]! // NOTE: Anything in the sharedCache is now trashed and must be // reloaded. Ideally anything using the sharedCache should // be aware of cache flushes so it can automatically // reinitialize itself - mwerle 20081107. [OOShipRegistry reload]; [[self gameController] setGamePaused:NO]; [[self gameController] setMouseInteractionModeForUIWithMouseInteraction:NO]; [PLAYER setSpeed:0.0]; [self loadDescriptions]; [self loadScenarios]; [missiontext autorelease]; missiontext = [[ResourceManager dictionaryFromFilesNamed:@"missiontext.plist" inFolder:@"Config" andMerge:YES] retain]; if(showDemo) { [demo_ships release]; demo_ships = [[[OOShipRegistry sharedRegistry] demoShipKeys] retain]; demo_ship_index = 0; demo_ship_subindex = 0; } breakPatternCounter = 0; cachedSun = nil; cachedPlanet = nil; cachedStation = nil; [self setUpSettings]; // reset these in case OXP set has changed // set up cargopod templates [self setUpCargoPods]; if (![player setUpAndConfirmOK:YES]) { // reinitAndShowDemo rescheduled inside setUpAndConfirmOK... return NO; // Abort! } // we can forget the previous settings now. JSResetFlags = 0; [self addEntity:player]; demo_ship = nil; [[self gameController] setPlayerFileToLoad:nil]; // reset Quicksave [self setUpInitialUniverse]; autoSaveNow = NO; // don't autosave immediately after restarting a game [[self station] initialiseLocalMarket]; if(showDemo) { [player setStatus:STATUS_START_GAME]; } else { [player setDockedAtMainStation]; } [player completeSetUp]; if(showDemo) { [player setGuiToIntroFirstGo:YES]; } else { // no need to do these if showing the demo as the only way out // now is to load a game [self populateNormalSpace]; [player startUpComplete]; } if(!showDemo) { [player setGuiToStatusScreen]; [player doWorldEventUntilMissionScreen:OOJSID("missionScreenOpportunity")]; } [self verifyEntitySessionIDs]; no_update = NO; return YES; } - (void) setUpInitialUniverse { PlayerEntity* player = PLAYER; OO_DEBUG_PUSH_PROGRESS(@"Wormhole and character reset"); if (activeWormholes) [activeWormholes autorelease]; activeWormholes = [[NSMutableArray arrayWithCapacity:16] retain]; if (characterPool) [characterPool autorelease]; characterPool = [[NSMutableArray arrayWithCapacity:256] retain]; OO_DEBUG_POP_PROGRESS(); OO_DEBUG_PUSH_PROGRESS(@"Galaxy reset"); [self setGalaxyTo: [player galaxyNumber] andReinit:YES]; systemID = [player systemID]; OO_DEBUG_POP_PROGRESS(); OO_DEBUG_PUSH_PROGRESS(@"Player init: setUpShipFromDictionary"); [player setUpShipFromDictionary:[[OOShipRegistry sharedRegistry] shipInfoForKey:[player shipDataKey]]]; // the standard cobra at this point [player baseMass]; // bootstrap the base mass used in all fuel charge calculations. OO_DEBUG_POP_PROGRESS(); // Player init above finishes initialising all standard player ship properties. Now that the base mass is set, we can run setUpSpace! [self setUpSpace]; [self setDockingClearanceProtocolActive: [[self currentSystemData] oo_boolForKey:@"stations_require_docking_clearance" defaultValue:YES]]; [self enterGUIViewModeWithMouseInteraction:NO]; [player setPosition:[[self station] position]]; [player setOrientation:kIdentityQuaternion]; } - (float) randomDistanceWithinScanner { return SCANNER_MAX_RANGE * ((Ranrot() & 255) / 256.0 - 0.5); } - (Vector) randomPlaceWithinScannerFrom:(Vector)pos alongRoute:(Vector)route withOffset:(double)offset { pos.x += offset * route.x + [self randomDistanceWithinScanner]; pos.y += offset * route.y + [self randomDistanceWithinScanner]; pos.z += offset * route.z + [self randomDistanceWithinScanner]; return pos; } - (HPVector) fractionalPositionFrom:(HPVector)point0 to:(HPVector)point1 withFraction:(double)routeFraction { if (routeFraction == NSNotFound) routeFraction = randf(); point1 = OOHPVectorInterpolate(point0, point1, routeFraction); point1.x += 2 * SCANNER_MAX_RANGE * (randf() - 0.5); point1.y += 2 * SCANNER_MAX_RANGE * (randf() - 0.5); point1.z += 2 * SCANNER_MAX_RANGE * (randf() - 0.5); return point1; } - (BOOL)doRemoveEntity:(Entity *)entity { // remove reference to entity in linked lists if ([entity canCollide]) // filter only collidables disappearing { doLinkedListMaintenanceThisUpdate = YES; } [entity removeFromLinkedLists]; // moved forward ^^ // remove from the reference dictionary int old_id = [entity universalID]; entity_for_uid[old_id] = nil; [entity setUniversalID:NO_TARGET]; [entity wasRemovedFromUniverse]; // maintain sorted lists int index = entity->zero_index; int n = 1; if (index >= 0) { if (sortedEntities[index] != entity) { OOLog(kOOLogInconsistentState, @"DEBUG: Universe removeEntity:%@ ENTITY IS NOT IN THE RIGHT PLACE IN THE ZERO_DISTANCE SORTED LIST -- FIXING...", entity); unsigned i; index = -1; for (i = 0; (i < n_entities)&&(index == -1); i++) if (sortedEntities[i] == entity) index = i; if (index == -1) OOLog(kOOLogInconsistentState, @"DEBUG: Universe removeEntity:%@ ENTITY IS NOT IN THE ZERO_DISTANCE SORTED LIST -- CONTINUING...", entity); } if (index != -1) { while ((unsigned)index < n_entities) { while (((unsigned)index + n < n_entities)&&(sortedEntities[index + n] == entity)) { n++; // ie there's a duplicate entry for this entity } /* BUG: when n_entities == UNIVERSE_MAX_ENTITIES, this read off the end of the array and copied (Entity *)n_entities = 0x800 into the list. The subsequent update of zero_index derferenced 0x800 and crashed. FIX: add an extra unused slot to sortedEntities, which is always nil. EFFICIENCY CONCERNS: this could have been an alignment issue since UNIVERSE_MAX_ENTITIES == 2048, but it isn't really. sortedEntities is part of the object, not malloced, it isn't aligned, and the end of it is only live in degenerate cases. -- Ahruman 2012-07-11 */ sortedEntities[index] = sortedEntities[index + n]; // copy entity[index + n] -> entity[index] (preserves sort order) if (sortedEntities[index]) { sortedEntities[index]->zero_index = index; // give it its correct position } index++; } if (n > 1) OOLog(kOOLogInconsistentState, @"DEBUG: Universe removeEntity: REMOVED %d EXTRA COPIES OF %@ FROM THE ZERO_DISTANCE SORTED LIST", n - 1, entity); while (n--) { n_entities--; sortedEntities[n_entities] = nil; } } entity->zero_index = -1; // it's GONE! } // remove from the definitive list if ([entities containsObject:entity]) { // FIXME: better approach needed for core break patterns - CIM if ([entity isBreakPattern] && ![entity isVisualEffect]) { breakPatternCounter--; } if ([entity isShip]) { ShipEntity *se = (ShipEntity*)entity; [self clearBeacon:se]; } if ([entity isWaypoint]) { OOWaypointEntity *wp = (OOWaypointEntity*)entity; [self clearBeacon:wp]; } if ([entity isVisualEffect]) { OOVisualEffectEntity *ve = (OOVisualEffectEntity*)entity; [self clearBeacon:ve]; } if ([entity isWormhole]) { [activeWormholes removeObject:entity]; } else if ([entity isPlanet]) { [allPlanets removeObject:entity]; } [entities removeObject:entity]; return YES; } return NO; } static void PreloadOneSound(NSString *soundName) { if (![soundName hasPrefix:@"["] && ![soundName hasSuffix:@"]"]) { [ResourceManager ooSoundNamed:soundName inFolder:@"Sounds"]; } } - (void) preloadSounds { // Preload sounds to avoid loading stutter. NSString *key = nil; foreachkey (key, customSounds) { id object = [customSounds objectForKey:key]; if([object isKindOfClass:[NSString class]]) { PreloadOneSound(object); } else if([object isKindOfClass:[NSArray class]] && [object count] > 0) { NSString *soundName = nil; foreach (soundName, object) { if ([soundName isKindOfClass:[NSString class]]) { PreloadOneSound(soundName); } } } } // Afterburner sound doesn't go through customsounds.plist. PreloadOneSound(@"afterburner1.ogg"); } - (void) populateSpaceFromActiveWormholes { NSAutoreleasePool *pool = nil; while ([activeWormholes count]) { pool = [[NSAutoreleasePool alloc] init]; @try { WormholeEntity* whole = [activeWormholes objectAtIndex:0]; // If the wormhole has been scanned by the player then the // PlayerEntity will take care of it if (![whole isScanned] && NSEqualPoints([PLAYER galaxy_coordinates], [whole destinationCoordinates]) ) { // this is a wormhole to this system [whole disgorgeShips]; } [activeWormholes removeObjectAtIndex:0]; // empty it out } @catch (NSException *exception) { OOLog(kOOLogException, @"Squashing exception during wormhole unpickling (%@: %@).", [exception name], [exception reason]); } [pool release]; } } - (NSString *)chooseStringForKey:(NSString *)key inDictionary:(NSDictionary *)dictionary { id object = [dictionary objectForKey:key]; if ([object isKindOfClass:[NSString class]]) return object; else if ([object isKindOfClass:[NSArray class]] && [object count] > 0) return [object oo_stringAtIndex:Ranrot() % [object count]]; return nil; } #if OO_LOCALIZATION_TOOLS #if DEBUG_GRAPHVIZ - (void) dumpDebugGraphViz { if ([[NSUserDefaults standardUserDefaults] boolForKey:@"universe-dump-debug-graphviz"]) { [self dumpSystemDescriptionGraphViz]; } } - (void) dumpSystemDescriptionGraphViz { NSMutableString *graphViz = nil; NSArray *systemDescriptions = nil; NSArray *thisDesc = nil; NSUInteger i, count, j, subCount; NSString *descLine = nil; NSArray *curses = nil; NSString *label = nil; NSDictionary *keyMap = nil; keyMap = [ResourceManager dictionaryFromFilesNamed:@"sysdesc_key_table.plist" inFolder:@"Config" andMerge:NO]; graphViz = [NSMutableString stringWithString: @"// System description grammar:\n\n" "digraph system_descriptions\n" "{\n" "\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" "\tedge [arrowhead=dot]\n" "\tnode [shape=none height=0.2 width=3 fontname=Helvetica]\n\t\n"]; systemDescriptions = [[self descriptions] oo_arrayForKey:@"system_description"]; count = [systemDescriptions count]; // Add system-description-string as special node (it's the one thing that ties [14] to everything else). descLine = DESC(@"system-description-string"); label = OOStringifySystemDescriptionLine(descLine, keyMap, NO); [graphViz appendFormat:@"\tsystem_description_string [label=\"%@\" shape=ellipse]\n", EscapedGraphVizString(label)]; [self addNumericRefsInString:descLine toGraphViz:graphViz fromNode:@"system_description_string" nodeCount:count]; [graphViz appendString:@"\t\n"]; // Add special nodes for formatting codes [graphViz appendString: @"\tpercent_I [label=\"%I\\nInhabitants\" shape=diamond]\n" "\tpercent_H [label=\"%H\\nSystem name\" shape=diamond]\n" "\tpercent_RN [label=\"%R/%N\\nRandom name\" shape=diamond]\n" "\tpercent_J [label=\"%J\\nNumbered system name\" shape=diamond]\n\t\n"]; // Toss in the Thargoid curses, too [graphViz appendString:@"\tsubgraph cluster_thargoid_curses\n\t{\n\t\tlabel = \"Thargoid curses\"\n"]; curses = [[self descriptions] oo_arrayForKey:@"thargoid_curses"]; subCount = [curses count]; for (j = 0; j < subCount; ++j) { label = OOStringifySystemDescriptionLine([curses oo_stringAtIndex:j], keyMap, NO); [graphViz appendFormat:@"\t\tthargoid_curse_%lu [label=\"%@\"]\n", j, EscapedGraphVizString(label)]; } [graphViz appendString:@"\t}\n"]; for (j = 0; j < subCount; ++j) { [self addNumericRefsInString:[curses oo_stringAtIndex:j] toGraphViz:graphViz fromNode:[NSString stringWithFormat:@"thargoid_curse_%lu", j] nodeCount:count]; } [graphViz appendString:@"\t\n"]; // The main show: the bits of systemDescriptions itself. // Define the nodes for (i = 0; i < count; ++i) { // Build label, using sysdesc_key_table.plist if available label = [keyMap objectForKey:[NSString stringWithFormat:@"%lu", i]]; if (label == nil) label = [NSString stringWithFormat:@"[%lu]", i]; else label = [NSString stringWithFormat:@"[%lu] (%@)", i, label]; [graphViz appendFormat:@"\tsubgraph cluster_%lu\n\t{\n\t\tlabel=\"%@\"\n", i, EscapedGraphVizString(label)]; thisDesc = [systemDescriptions oo_arrayAtIndex:i]; subCount = [thisDesc count]; for (j = 0; j < subCount; ++j) { label = OOStringifySystemDescriptionLine([thisDesc oo_stringAtIndex:j], keyMap, NO); [graphViz appendFormat:@"\t\tn%lu_%lu [label=\"\\\"%@\\\"\"]\n", i, j, EscapedGraphVizString(label)]; } [graphViz appendString:@"\t}\n"]; } [graphViz appendString:@"\t\n"]; // Define the edges for (i = 0; i != count; ++i) { thisDesc = [systemDescriptions oo_arrayAtIndex:i]; subCount = [thisDesc count]; for (j = 0; j != subCount; ++j) { descLine = [thisDesc oo_stringAtIndex:j]; [self addNumericRefsInString:descLine toGraphViz:graphViz fromNode:[NSString stringWithFormat:@"n%lu_%lu", i, j] nodeCount:count]; } } // Write file [graphViz appendString:@"\t}\n"]; [ResourceManager writeDiagnosticData:[graphViz dataUsingEncoding:NSUTF8StringEncoding] toFileNamed:@"SystemDescription.dot"]; } #endif // DEBUG_GRAPHVIZ - (void) addNumericRefsInString:(NSString *)string toGraphViz:(NSMutableString *)graphViz fromNode:(NSString *)fromNode nodeCount:(NSUInteger)nodeCount { NSString *index = nil; NSInteger start, end; NSRange remaining, subRange; unsigned i; remaining = NSMakeRange(0, [string length]); for (;;) { subRange = [string rangeOfString:@"[" options:NSLiteralSearch range:remaining]; if (subRange.location == NSNotFound) break; start = subRange.location + subRange.length; remaining.length -= start - remaining.location; remaining.location = start; subRange = [string rangeOfString:@"]" options:NSLiteralSearch range:remaining]; if (subRange.location == NSNotFound) break; end = subRange.location; remaining.length -= end - remaining.location; remaining.location = end; index = [string substringWithRange:NSMakeRange(start, end - start)]; i = [index intValue]; // Each node gets a colour for its incoming edges. The multiplication and mod shuffle them to avoid adjacent nodes having similar colours. [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]; } if ([string rangeOfString:@"%I"].location != NSNotFound) { [graphViz appendFormat:@"\t%@ -> percent_I [color=\"0,0,0.25\"]\n", fromNode]; } if ([string rangeOfString:@"%H"].location != NSNotFound) { [graphViz appendFormat:@"\t%@ -> percent_H [color=\"0,0,0.45\"]\n", fromNode]; } if ([string rangeOfString:@"%R"].location != NSNotFound || [string rangeOfString:@"%N"].location != NSNotFound) { [graphViz appendFormat:@"\t%@ -> percent_RN [color=\"0,0,0.65\"]\n", fromNode]; } // TODO: test graphViz output for @"%Jxxx" if ([string rangeOfString:@"%J"].location != NSNotFound) { [graphViz appendFormat:@"\t%@ -> percent_J [color=\"0,0,0.75\"]\n", fromNode]; } } - (void) runLocalizationTools { // Handle command line options to transform system_description array for easier localization NSArray *arguments = nil; NSEnumerator *argEnum = nil; NSString *arg = nil; BOOL compileSysDesc = NO, exportSysDesc = NO, xml = NO; arguments = [[NSProcessInfo processInfo] arguments]; for (argEnum = [arguments objectEnumerator]; (arg = [argEnum nextObject]); ) { if ([arg isEqual:@"--compile-sysdesc"]) compileSysDesc = YES; else if ([arg isEqual:@"--export-sysdesc"]) exportSysDesc = YES; else if ([arg isEqual:@"--xml"]) xml = YES; else if ([arg isEqual:@"--openstep"]) xml = NO; } if (compileSysDesc) CompileSystemDescriptions(xml); if (exportSysDesc) ExportSystemDescriptions(xml); } #endif #if NEW_PLANETS // See notes at preloadPlanetTexturesForSystem:. - (void) prunePreloadingPlanetMaterials { [[OOAsyncWorkManager sharedAsyncWorkManager] completePendingTasks]; NSUInteger i = [_preloadingPlanetMaterials count]; while (i--) { if ([[_preloadingPlanetMaterials objectAtIndex:i] isFinishedLoading]) { [_preloadingPlanetMaterials removeObjectAtIndex:i]; } } } #endif - (void) loadConditionScripts { [conditionScripts autorelease]; conditionScripts = [[NSMutableDictionary alloc] init]; // get list of names from cache manager [self addConditionScripts:[[[OOCacheManager sharedCache] objectForKey:@"equipment conditions" inCache:@"condition scripts"] objectEnumerator]]; [self addConditionScripts:[[[OOCacheManager sharedCache] objectForKey:@"ship conditions" inCache:@"condition scripts"] objectEnumerator]]; [self addConditionScripts:[[[OOCacheManager sharedCache] objectForKey:@"demoship conditions" inCache:@"condition scripts"] objectEnumerator]]; } - (void) addConditionScripts:(NSEnumerator *)scripts { NSString *scriptname = nil; while ((scriptname = [scripts nextObject])) { if ([conditionScripts objectForKey:scriptname] == nil) { OOJSScript *script = [OOScript jsScriptFromFileNamed:scriptname properties:nil]; if (script != nil) { [conditionScripts setObject:script forKey:scriptname]; } } } } - (OOJSScript*) getConditionScript:(NSString *)scriptname { return [conditionScripts objectForKey:scriptname]; } @end @implementation OOSound (OOCustomSounds) + (id) soundWithCustomSoundKey:(NSString *)key { NSString *fileName = [UNIVERSE soundNameForCustomSoundKey:key]; if (fileName == nil) return nil; return [ResourceManager ooSoundNamed:fileName inFolder:@"Sounds"]; } - (id) initWithCustomSoundKey:(NSString *)key { [self release]; return [[OOSound soundWithCustomSoundKey:key] retain]; } @end @implementation OOSoundSource (OOCustomSounds) + (id) sourceWithCustomSoundKey:(NSString *)key { return [[[self alloc] initWithCustomSoundKey:key] autorelease]; } - (id) initWithCustomSoundKey:(NSString *)key { OOSound *theSound = [OOSound soundWithCustomSoundKey:key]; if (theSound != nil) { self = [self initWithSound:theSound]; } else { [self release]; self = nil; } return self; } - (void) playCustomSoundWithKey:(NSString *)key { OOSound *theSound = [OOSound soundWithCustomSoundKey:key]; if (theSound != nil) [self playSound:theSound]; } @end NSComparisonResult populatorPrioritySort(id a, id b, void *context) { NSDictionary *one = (NSDictionary *)a; NSDictionary *two = (NSDictionary *)b; int pri_one = [one oo_intForKey:@"priority" defaultValue:100]; int pri_two = [two oo_intForKey:@"priority" defaultValue:100]; if (pri_one < pri_two) return NSOrderedAscending; if (pri_one > pri_two) return NSOrderedDescending; return NSOrderedSame; } NSComparisonResult equipmentSort(id a, id b, void *context) { NSArray *one = (NSArray *)a; NSArray *two = (NSArray *)b; /* Sort by explicit sort_order, then tech level, then price */ OOCreditsQuantity comp1 = [[one oo_dictionaryAtIndex:EQUIPMENT_EXTRA_INFO_INDEX] oo_unsignedLongLongForKey:@"sort_order" defaultValue:1000]; OOCreditsQuantity comp2 = [[two oo_dictionaryAtIndex:EQUIPMENT_EXTRA_INFO_INDEX] oo_unsignedLongLongForKey:@"sort_order" defaultValue:1000]; if (comp1 < comp2) return NSOrderedAscending; if (comp1 > comp2) return NSOrderedDescending; comp1 = [one oo_unsignedLongLongAtIndex:EQUIPMENT_TECH_LEVEL_INDEX]; comp2 = [two oo_unsignedLongLongAtIndex:EQUIPMENT_TECH_LEVEL_INDEX]; if (comp1 < comp2) return NSOrderedAscending; if (comp1 > comp2) return NSOrderedDescending; comp1 = [one oo_unsignedLongLongAtIndex:EQUIPMENT_PRICE_INDEX]; comp2 = [two oo_unsignedLongLongAtIndex:EQUIPMENT_PRICE_INDEX]; if (comp1 < comp2) return NSOrderedAscending; if (comp1 > comp2) return NSOrderedDescending; return NSOrderedSame; } NSString *OOLookUpDescriptionPRIV(NSString *key) { NSString *result = [UNIVERSE descriptionForKey:key]; if (result == nil) result = key; return result; } // There's a hint of gettext about this... NSString *OOLookUpPluralDescriptionPRIV(NSString *key, NSInteger count) { NSArray *conditions = [[UNIVERSE descriptions] oo_arrayForKey:@"plural-rules"]; // are we using an older descriptions.plist (1.72.x) ? NSString *tmp = [UNIVERSE descriptionForKey:key]; if (tmp != nil) { static NSMutableSet *warned = nil; if (![warned containsObject:tmp]) { OOLogWARN(@"localization.plurals", @"'%@' found in descriptions.plist, should be '%@%%0'. Localization data needs updating.",key,key); if (warned == nil) warned = [[NSMutableSet alloc] init]; [warned addObject:tmp]; } } if (conditions == nil) { if (tmp == nil) // this should mean that descriptions.plist is from 1.73 or above. return OOLookUpDescriptionPRIV([NSString stringWithFormat:@"%@%%%d", key, count != 1]); // still using an older descriptions.plist return tmp; } int unsigned i; long int index; for (index = i = 0; i < [conditions count]; ++index, ++i) { const char *cond = [[conditions oo_stringAtIndex:i] UTF8String]; if (!cond) break; long int input = count; BOOL flag = NO; // we XOR test results with this while (isspace (*cond)) ++cond; for (;;) { while (isspace (*cond)) ++cond; char command = *cond++; switch (command) { case 0: goto passed; // end of string case '~': flag = !flag; continue; } long int param = strtol (cond, (char **)&cond, 10); switch (command) { case '#': index = param; continue; case '%': if (param < 2) break; // ouch - fail this! input %= param; continue; case '=': if (flag ^ (input == param)) continue; break; case '!': if (flag ^ (input != param)) continue; break; case '<': if (flag ^ (input < param)) continue; break; case '>': if (flag ^ (input > param)) continue; break; } // if we arrive here, we have an unknown test or a test has failed break; } } passed: return OOLookUpDescriptionPRIV([NSString stringWithFormat:@"%@%%%ld", key, index]); } oolite-1.82/src/Core/dummy.cpp000066400000000000000000000001531256642440500162640ustar00rootroot00000000000000/* This file exists to make the build system Do the Right Thing to link C++ code in static libraries. */ oolite-1.82/src/Core/legacy_random.c000066400000000000000000000147741256642440500174130ustar00rootroot00000000000000/* legacy_random.c Oolite Copyright (C) 2004-2008 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "legacy_random.h" const Random_Seed kNilRandomSeed = {0}; static RNG_Seed rnd_seed; // TODO: Why is this based on a static? Should change to OOMungeCheckSum(&checkSum, value); static int32_t checksum; void clear_checksum() { checksum = 0; } int16_t munge_checksum(long long value_) { uint32_t value = (uint32_t)value_; int32_t mult1 = (value & 15) + 8; checksum += value; checksum *= mult1; checksum += mult1; checksum &= 0xffff; return checksum; } // cunning price rounding routine: // double cunningFee(double value, double precision) { double fee = value; double superfee = 100000.0; double max = 1 + precision; double min = 1 - precision; unsigned long long rounded_fee = superfee * floor(0.5 + fee / superfee); if (rounded_fee == 0) rounded_fee = 1; double ratio = fee / (double)rounded_fee; while ((ratio < min || ratio > max) && superfee > 1) { rounded_fee = superfee * floor(0.5 + fee / superfee); if (rounded_fee == 0) rounded_fee = 1; ratio = fee / (double)rounded_fee; superfee /= 10.0; } if (ratio > min && ratio < max) fee = rounded_fee; return fee; } // an implementation of RANROT // pseudo random number generator // static RANROTSeed sRANROT; unsigned Ranrot(void) { sRANROT.high = (sRANROT.high << 16) + (sRANROT.high >> 16); sRANROT.high += sRANROT.low; sRANROT.low += sRANROT.high; return sRANROT.high & 0x7FFFFFFF; } unsigned RanrotWithSeed(RANROTSeed *ioSeed) { assert(ioSeed != NULL); ioSeed->high = (ioSeed->high << 16) + (ioSeed->high >> 16); ioSeed->high += ioSeed->low; ioSeed->low += ioSeed->high; return ioSeed->high & 0x7FFFFFFF; } RANROTSeed MakeRanrotSeed(uint32_t seed) { RANROTSeed result = { .low = seed, .high = ~seed }; // Mix it up a bit. RanrotWithSeed(&result); RanrotWithSeed(&result); RanrotWithSeed(&result); return result; } RANROTSeed RanrotSeedFromRNGSeed(RNG_Seed seed) { return MakeRanrotSeed(seed.a * 0x1000000 + seed.b * 0x10000 + seed.c * 0x100 + seed.d); } RANROTSeed RanrotSeedFromRandomSeed(Random_Seed seed) { // Same pattern as seed_for_planet_description(). RNG_Seed s = { .a = seed.c, .b = seed.d, .c = seed.e, .d = seed.f }; return RanrotSeedFromRNGSeed(s); } void ranrot_srand(uint32_t seed) { sRANROT = MakeRanrotSeed(seed); } float randf (void) { return (Ranrot() & 0xffff) * (1.0f / 65536.0f); } float randfWithSeed(RANROTSeed *ioSeed) { return (RanrotWithSeed(ioSeed) & 0xffff) * (1.0f / 65536.0f); } float bellf (int n) { int i = n; float total = 0; if (EXPECT_NOT(i <= 0)) { printf("***** ERROR - attempt to generate bellf(%d)\n", n); return 0.0f; // catch possible div-by-zero problem } while (i-- > 0) total += (Ranrot() & 1023); return total / (1024.0f * n); } RANROTSeed RANROTGetFullSeed(void) { return sRANROT; } void RANROTSetFullSeed(RANROTSeed seed) { sRANROT = seed; } void seed_RNG_only_for_planet_description (Random_Seed s_seed) { rnd_seed.a = s_seed.c; rnd_seed.b = s_seed.d; rnd_seed.c = s_seed.e; rnd_seed.d = s_seed.f; } void seed_for_planet_description (Random_Seed s_seed) { seed_RNG_only_for_planet_description(s_seed); sRANROT = RanrotSeedFromRNGSeed(rnd_seed); } RNG_Seed currentRandomSeed (void) { return rnd_seed; } void setRandomSeed (RNG_Seed a_seed) { rnd_seed = a_seed; } int gen_rnd_number (void) { int a,x; x = (rnd_seed.a * 2) & 0xFF; a = x + rnd_seed.c; if (rnd_seed.a > 127) a++; rnd_seed.a = a & 0xFF; rnd_seed.c = x; a = a / 256; /* a = any carry left from above */ x = rnd_seed.b; a = (a + x + rnd_seed.d) & 0xFF; rnd_seed.b = a; rnd_seed.d = x; return a; } static bool sReallyRandomInited = false; static RANROTSeed sReallyRandomSeed; uint32_t OOReallyRandom(void) { assert(sReallyRandomInited); return RanrotWithSeed(&sReallyRandomSeed); } void OOInitReallyRandom(uint64_t seed) { assert(!sReallyRandomInited); seed ^= 0xA471D52AEF3B6322ULL; sReallyRandomSeed.high = (seed >> 32) & 0xFFFFFFFF; sReallyRandomSeed.low = seed & 0xFFFFFFFF; sReallyRandomInited = true; OOReallyRandom(); } void OOSetReallyRandomRANROTSeed(void) { assert(sReallyRandomInited); sRANROT = sReallyRandomSeed; OOReallyRandom(); // Don't go reusing it. } void OOSetReallyRandomRndSeed(void) { uint32_t val = OOReallyRandom(); rnd_seed.a = (val >> 24) & 0xFF; rnd_seed.b = (val >> 16) & 0xFF; rnd_seed.c = (val >> 8) & 0xFF; rnd_seed.d = val & 0xFF; } void OOSetReallyRandomRANROTAndRndSeeds(void) { OOSetReallyRandomRANROTSeed(); OOSetReallyRandomRndSeed(); } OORandomState OOSaveRandomState(void) { return (OORandomState) { .ranrot = sRANROT, .rnd = rnd_seed }; } void OORestoreRandomState(OORandomState state) { sRANROT = state.ranrot; rnd_seed = state.rnd; } void make_pseudo_random_seed (Random_Seed *seed_ptr) { seed_ptr->a = gen_rnd_number(); seed_ptr->b = gen_rnd_number(); seed_ptr->c = gen_rnd_number(); seed_ptr->d = gen_rnd_number(); seed_ptr->e = gen_rnd_number(); seed_ptr->f = gen_rnd_number(); } void rotate_seed (Random_Seed *seed_ptr) { uint_fast16_t x; uint_fast16_t y; /* Note: this is equivalent to adding three (little-endian) 16-bit values together, rotating the three numbers and replacing one of them with the sum. The byte-oriented approach is presumably because it was reverse-engineered from eight-bit machine code. Switching to a plain sixteen-bit representation is more trouble than it's worth since so much code uses byte values from the seed struct directly. */ x = seed_ptr->a + seed_ptr->c + seed_ptr->e; y = seed_ptr->b + seed_ptr->d + seed_ptr->f; seed_ptr->a = seed_ptr->c; seed_ptr->b = seed_ptr->d; seed_ptr->c = seed_ptr->e; seed_ptr->d = seed_ptr->f; seed_ptr->e = x; seed_ptr->f = y + (x >> 8); } oolite-1.82/src/Core/legacy_random.h000066400000000000000000000120221256642440500174000ustar00rootroot00000000000000/* legacy_random.h Pseudo-random number generator designed to produce identical results to that used in BBC Elite (for dynamic world generation), and related functions. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef LEGACY_RANDOM_H #define LEGACY_RANDOM_H #include "OOFunctionAttributes.h" #include #include typedef struct Random_Seed { uint8_t a, /* 6c */ b, /* 6d */ c, /* 6e */ d, /* 6f */ e, /* 70 */ f; /* 71 */ } Random_Seed; typedef struct RNG_Seed { int32_t a, b, c, d; } RNG_Seed; typedef struct RANROTSeed { uint32_t high, low; } RANROTSeed; extern const Random_Seed kNilRandomSeed; // checksum stuff void clear_checksum(); int16_t munge_checksum(long long value); // cunning price rounding routine: double cunningFee(double value, double precision); // precision is the fraction below which numbers become insignificant. // an implementation of RANROT // pseudo random number generator void ranrot_srand(uint32_t seed); unsigned Ranrot(void); #define ranrot_rand() ((int)Ranrot()) // Some uses perform arithmetic that does weird things if result is unsigned -- DustEntity.m, for instance. float randf(void); float bellf(int n); RANROTSeed RANROTGetFullSeed(void); void RANROTSetFullSeed(RANROTSeed seed); RANROTSeed MakeRanrotSeed(uint32_t seed); RANROTSeed RanrotSeedFromRNGSeed(RNG_Seed seed); RANROTSeed RanrotSeedFromRandomSeed(Random_Seed seed); unsigned RanrotWithSeed(RANROTSeed *ioSeed); float randfWithSeed(RANROTSeed *ioSeed); OOINLINE double distanceBetweenPlanetPositions(int x1, int y1, int x2, int y2) INLINE_CONST_FUNC; OOINLINE double accurateDistanceBetweenPlanetPositions(int x1, int y1, int x2, int y2) INLINE_CONST_FUNC; void seed_for_planet_description(Random_Seed s_seed); void seed_RNG_only_for_planet_description(Random_Seed s_seed); RNG_Seed currentRandomSeed(void); void setRandomSeed(RNG_Seed a_seed); // Range: 0..255 int gen_rnd_number (void); void make_pseudo_random_seed (Random_Seed *seed_ptr); OOINLINE int is_nil_seed(Random_Seed a_seed) INLINE_CONST_FUNC; void rotate_seed (Random_Seed *seed_ptr); OOINLINE int rotate_byte_left (int x) INLINE_CONST_FUNC; OOINLINE int equal_seeds(Random_Seed seed1, Random_Seed seed2) INLINE_CONST_FUNC; /* The "really really random" PRNG. This is a separate RANROT seed that is seeded once at startup and never reset under any circumstances. It can also be used to seed get_rnd_number and the main RANROT seed. If doing this, save and restore the seeds using the functions above ore OOSaveRandomState() and OORestoreRandomState(). Since these use a global seed, they may only be used from the main thread. */ uint32_t OOReallyRandom(void); void OOInitReallyRandom(uint64_t seed); void OOSetReallyRandomRANROTSeed(void); void OOSetReallyRandomRndSeed(void); void OOSetReallyRandomRANROTAndRndSeeds(void); /* OOSaveRandomState()/OORestoreRandomState(): save and restore both the main RANROT seed and the rnd seed in one shot. */ typedef struct { RANROTSeed ranrot; RNG_Seed rnd; } OORandomState; OORandomState OOSaveRandomState(void); void OORestoreRandomState(OORandomState state); /*** Only inline definitions beyond this point ***/ OOINLINE int equal_seeds(Random_Seed seed1, Random_Seed seed2) { return ((seed1.a == seed2.a)&&(seed1.b == seed2.b)&&(seed1.c == seed2.c)&&(seed1.d == seed2.d)&&(seed1.e == seed2.e)&&(seed1.f == seed2.f)); } OOINLINE int is_nil_seed(Random_Seed a_seed) { return equal_seeds(a_seed, kNilRandomSeed); } OOINLINE int rotate_byte_left(int x) { return ((x << 1) | (x >> 7)) & 255; } // a method used to determine interplanetary distances, // if accurate, it has to scale distance down by a factor of 7.15:7.0 // to allow routes navigable in the original! OOINLINE double distanceBetweenPlanetPositions(int x1, int y1, int x2, int y2) { int dx = x1 - x2; int dy = (y1 - y2)/2; int dist = sqrt(dx*dx + dy*dy); // N.b. Rounding error due to truncation is desired. return 0.4 * dist; } OOINLINE double accurateDistanceBetweenPlanetPositions(int x1, int y1, int x2, int y2) { double dx = x1 - x2; double dy = (y1 - y2) / 2.0; double dist = hypot(dx, dy); return 0.4 * dist; } OOINLINE double travelTimeBetweenPlanetPositions(int x1, int y1, int x2, int y2) { double distance = distanceBetweenPlanetPositions(x1, y1, x2, y2); return distance * distance; } #endif oolite-1.82/src/SDL/000077500000000000000000000000001256642440500141605ustar00rootroot00000000000000oolite-1.82/src/SDL/Comparison.h000066400000000000000000000075741256642440500164600ustar00rootroot00000000000000// // $Id: Comparison.h,v 1.3 2004/12/12 20:17:24 will_mason Exp $ // // vi: set ft=objc: /* * ObjectiveLib - a library of containers and algorithms for Objective-C * * Copyright (c) 2004 * Will Mason * * Portions: * * Copyright (c) 1994 * Hewlett-Packard Company * * Copyright (c) 1996,1997 * Silicon Graphics Computer Systems, Inc. * * Copyright (c) 1997 * Moscow Center for SPARC Technology * * Copyright (c) 1999 * Boris Fomitchev * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * You may contact the author at will_mason@users.sourceforge.net. */ #if defined(GNUSTEP) #if !defined(__COMPARISON_OL_GUARD) #define __COMPARISON_OL_GUARD #include /** * @category NSObject(OLComparisonMethods) Comparison.h Objectivelib/Comparison.h * * Comparison methods used in @ref Functors "function objects". These comparison * methods are only required to be included when GNUstep is the platform, as * Cocoa already defines them. Under Cocoa they are declared in the * intuitively-named file @c NSScriptWhoseTests.h. All of these methods send * the message @c compare: to the receiving object. * * @pre The receiving object must implement the method @c compare:. */ @interface NSObject (OLComparisonMethods) /** * Return whether another object is equal to this one. This message returns YES if * and only if the message @c compare: returns @c NSOrderedSame. * * @param object the object to which to compare this one * @return YES if @a object is equal to this one, NO otherwise */ - (BOOL) isEqualTo: (id)object; /** * Return whether this object is greater than another one. This message returns * YES if and only if @c compare: returns @c NSOrderedDescending. * * @param object the object to which to compare this one * @return YES if this object is greater than @a object, NO otherwise */ - (BOOL) isGreaterThan: (id)object; /** * Return whether this object is greater than or equal to another one. This message returns * YES if and only if @c compare: does not return @c NSOrderedAscending. * * @param object the object to which to compare this one * @return YES if this object is greater than or equal to @a object, NO otherwise */ - (BOOL) isGreaterThanOrEqualTo: (id)object; /** * Return whether this object is less than another one. This message returns * YES if and only if @c compare: returns @c NSOrderedAscending. * * @param object the object to which to compare this one * @return YES if this object is less than @a object, NO otherwise */ - (BOOL) isLessThan: (id)object; /** * Return whether this object is less than or equal to another one. This message returns * YES if and only if @c compare: does not return @c NSOrderedDescending. * * @param object the object to which to compare this one * @return YES if this object is less than or equal to @a object, NO otherwise */ - (BOOL) isLessThanOrEqualTo: (id)object; /** * Return whether another object is not equal to this one. This message returns YES if * and only if the message @c compare: does not return @c NSOrderedSame. * * @param object the object to which to compare this one * @return YES if @a object is not equal to this one, NO otherwise */ - (BOOL) isNotEqualTo: (id)object; @end #endif #endif oolite-1.82/src/SDL/Comparison.m000066400000000000000000000042441256642440500164540ustar00rootroot00000000000000// // $Id: Comparison.m,v 1.3 2004/11/29 23:35:50 will_mason Exp $ // // vi: set ft=objc: /* * ObjectiveLib - a library of containers and algorithms for Objective-C * * Copyright (c) 2004 * Will Mason * * Portions: * * Copyright (c) 1994 * Hewlett-Packard Company * * Copyright (c) 1996,1997 * Silicon Graphics Computer Systems, Inc. * * Copyright (c) 1997 * Moscow Center for SPARC Technology * * Copyright (c) 1999 * Boris Fomitchev * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * You may contact the author at will_mason@users.sourceforge.net. */ #if defined(GNUSTEP) #include "Comparison.h" #include @implementation NSObject (OLComparison) - (BOOL) isEqualTo: (id)object { return (object != nil && [self compare: object] == NSOrderedSame) ? YES : NO; } - (BOOL) isGreaterThan: (id)object { return (object != nil && [self compare: object] == NSOrderedDescending) ? YES : NO; } - (BOOL) isGreaterThanOrEqualTo: (id)object { return (object != nil && [self compare: object] != NSOrderedAscending) ? YES : NO; } - (BOOL) isLessThan: (id)object { return (object != nil && [self compare: object] == NSOrderedAscending) ? YES : NO; } - (BOOL) isLessThanOrEqualTo: (id)object { return (object != nil && [self compare: object] != NSOrderedDescending) ? YES : NO; } - (BOOL) isNotEqualTo: (id)object { return (object != nil && [self compare: object] != NSOrderedSame) ? YES : NO; } @end #endif oolite-1.82/src/SDL/GameController+SDLFullScreen.m000066400000000000000000000076251256642440500216660ustar00rootroot00000000000000/* GameController+SDLFullScreen.m Full-screen rendering support for SDL targets. Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "GameController.h" #if OOLITE_SDL #import "MyOpenGLView.h" #import "Universe.h" #import "OOFullScreenController.h" @implementation GameController (FullScreen) - (void) setUpDisplayModes { NSArray *modes = [gameView getScreenSizeArray]; NSDictionary *mode = nil; unsigned int modeIndex, modeCount; unsigned int modeWidth, modeHeight; displayModes = [[NSMutableArray alloc] init]; modeCount = [modes count]; for (modeIndex = 0; modeIndex < modeCount; modeIndex++) { mode = [modes objectAtIndex: modeIndex]; modeWidth = [[mode objectForKey: kOODisplayWidth] intValue]; modeHeight = [[mode objectForKey: kOODisplayHeight] intValue]; if (modeWidth < DISPLAY_MIN_WIDTH || modeWidth > DISPLAY_MAX_WIDTH || modeHeight < DISPLAY_MIN_HEIGHT || modeHeight > DISPLAY_MAX_HEIGHT) continue; [displayModes addObject: mode]; } NSSize fsmSize = [gameView currentScreenSize]; width = fsmSize.width; height = fsmSize.height; } - (void) setFullScreenMode:(BOOL)fsm { fullscreen = fsm; } - (void) exitFullScreenMode { [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"fullscreen"]; stayInFullScreenMode = NO; } - (BOOL) inFullScreenMode { return [gameView inFullScreenMode]; } - (BOOL) setDisplayWidth:(unsigned int) d_width Height:(unsigned int) d_height Refresh:(unsigned int) d_refresh { NSDictionary *d_mode = [self findDisplayModeForWidth: d_width Height: d_height Refresh: d_refresh]; if (d_mode) { width = d_width; height = d_height; refresh = d_refresh; fullscreenDisplayMode = d_mode; NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; [userDefaults setInteger:width forKey:@"display_width"]; [userDefaults setInteger:height forKey:@"display_height"]; [userDefaults setInteger:refresh forKey:@"display_refresh"]; // Manual synchronization is required for SDL And doesn't hurt much for OS X. [userDefaults synchronize]; return YES; } return NO; } - (NSDictionary *) findDisplayModeForWidth:(unsigned int) d_width Height:(unsigned int) d_height Refresh:(unsigned int) d_refresh { int i, modeCount; NSDictionary *mode; unsigned int modeWidth, modeHeight, modeRefresh; modeCount = [displayModes count]; for (i = 0; i < modeCount; i++) { mode = [displayModes objectAtIndex: i]; modeWidth = [[mode objectForKey:kOODisplayWidth] intValue]; modeHeight = [[mode objectForKey:kOODisplayHeight] intValue]; modeRefresh = [[mode objectForKey:kOODisplayRefreshRate] intValue]; if ((modeWidth == d_width)&&(modeHeight == d_height)&&(modeRefresh == d_refresh)) { return mode; } } return nil; } - (NSArray *) displayModes { return [NSArray arrayWithArray:displayModes]; } - (NSUInteger) indexOfCurrentDisplayMode { NSDictionary *mode; mode = [self findDisplayModeForWidth: width Height: height Refresh: refresh]; if (mode == nil) return NSNotFound; else return [displayModes indexOfObject:mode]; return NSNotFound; } - (void) pauseFullScreenModeToPerform:(SEL) selector onTarget:(id) target { pauseSelector = selector; pauseTarget = target; stayInFullScreenMode = NO; } @end #endif oolite-1.82/src/SDL/MyOpenGLView.h000066400000000000000000000171321256642440500166220ustar00rootroot00000000000000/* MyOpenGLView.h Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOCocoa.h" #import "OOOpenGL.h" #import "OOMouseInteractionMode.h" #import "OOOpenGLMatrixManager.h" #include #define MIN_FOV_DEG 30.0f #define MAX_FOV_DEG 80.0f #define MIN_FOV (tan((MIN_FOV_DEG / 2) * M_PI / 180.0f)) #define MAX_FOV (tan((MAX_FOV_DEG / 2) * M_PI / 180.0f)) #define MOUSEVIRTUALSTICKSENSITIVITYFACTOR 0.95f #define MOUSEX_MAXIMUM 0.6 #define MOUSEY_MAXIMUM 0.6 #define MAX_CLEAR_DEPTH 10000000000.0 // 10 000 000 km. #define INTERMEDIATE_CLEAR_DEPTH 100.0 // 100 m. #define NUM_KEYS 320 #define MOUSE_DOUBLE_CLICK_INTERVAL 0.40 #define OOMOUSEWHEEL_EVENTS_DELAY_INTERVAL 0.05 #define SNAPSHOTS_PNG_FORMAT 1 @class Entity, GameController, OpenGLSprite; enum GameViewKeys { gvFunctionKey1 = 241, gvFunctionKey2, gvFunctionKey3, gvFunctionKey4, gvFunctionKey5, gvFunctionKey6, gvFunctionKey7, gvFunctionKey8, gvFunctionKey9, gvFunctionKey10, gvFunctionKey11, gvArrowKeyRight, gvArrowKeyLeft, gvArrowKeyDown, gvArrowKeyUp, // 255 gvMouseLeftButton = 301, gvMouseDoubleClick, gvHomeKey, gvEndKey, gvInsertKey, gvDeleteKey, gvPageUpKey, gvPageDownKey, // 308 gvNumberKey0 = 48, gvNumberKey1, gvNumberKey2, gvNumberKey3, gvNumberKey4, gvNumberKey5, gvNumberKey6, gvNumberKey7, gvNumberKey8, gvNumberKey9, //57 gvNumberPadKey0 = 310, gvNumberPadKey1, gvNumberPadKey2, gvNumberPadKey3, gvNumberPadKey4, gvNumberPadKey5, gvNumberPadKey6, gvNumberPadKey7, gvNumberPadKey8, gvNumberPadKey9 //319 }; enum MouseWheelStatus { gvMouseWheelDown = -1, gvMouseWheelNeutral, gvMouseWheelUp }; enum StringInput { gvStringInputNo = 0, gvStringInputAlpha = 1, gvStringInputLoadSave = 2, gvStringInputAll = 3 }; enum KeyboardType { gvKeyboardAuto, gvKeyboardUS, gvKeyboardUK }; extern int debug; @interface MyOpenGLView : NSObject { GameController *gameController; BOOL keys[NUM_KEYS]; BOOL supressKeys; // DJS BOOL opt, ctrl, command, shift; BOOL allowingStringInput; BOOL isAlphabetKeyDown; int keycodetrans[255]; BOOL m_glContextInitialized; NSPoint mouseDragStartPoint; BOOL mouseWarped; NSTimeInterval timeIntervalAtLastClick; NSTimeInterval timeSinceLastMouseWheel; BOOL doubleClick; NSMutableString *typedString; NSPoint virtualJoystickPosition; NSSize viewSize; GLfloat display_z; GLfloat x_offset, y_offset; double squareX,squareY; NSRect bounds; float _gamma; float _fov; // Full screen sizes NSMutableArray *screenSizes; int currentSize; //we need an int! BOOL fullScreen; // Windowed mode NSSize currentWindowSize; SDL_Surface *surface; BOOL showSplashScreen; #if OOLITE_WINDOWS BOOL wasFullScreen; BOOL updateContext; BOOL saveSize; unsigned keyboardMap; HWND SDL_Window; MONITORINFOEX monitorInfo; RECT lastGoodRect; #endif BOOL grabMouseStatus; NSSize firstScreen; OOOpenGLMatrixManager *matrixManager; // Mouse mode indicator (for mouse movement model) BOOL mouseInDeltaMode; int _mouseWheelState; } - (void) initSplashScreen; - (void) endSplashScreen; - (void) autoShowMouse; - (void) setStringInput: (enum StringInput) value; - (void) allowStringInput: (BOOL) value; - (enum StringInput) allowingStringInput; - (NSString *) typedString; - (void) resetTypedString; - (void) setTypedString:(NSString*) value; - (NSSize) viewSize; - (GLfloat) display_z; - (GLfloat) x_offset; - (GLfloat) y_offset; - (GameController *) gameController; - (void) setGameController:(GameController *) controller; - (void) noteMouseInteractionModeChangedFrom:(OOMouseInteractionMode)oldMode to:(OOMouseInteractionMode)newMode; - (void) initialiseGLWithSize:(NSSize) v_size; - (void) initialiseGLWithSize:(NSSize) v_size useVideoMode:(BOOL) v_mode; - (BOOL) isRunningOnPrimaryDisplayDevice; #if OOLITE_WINDOWS - (BOOL) getCurrentMonitorInfo:(MONITORINFOEX *)mInfo; - (MONITORINFOEX) currentMonitorInfo; #endif - (void) grabMouseInsideGameWindow:(BOOL) value; - (void) drawRect:(NSRect)rect; - (void) updateScreen; - (void) updateScreenWithVideoMode:(BOOL) v_mode; - (void) display; - (BOOL) snapShot:(NSString *)filename; #if SNAPSHOTS_PNG_FORMAT - (BOOL) pngSaveSurface:(NSString *)fileName withSurface:(SDL_Surface *)surf; #endif - (NSRect) bounds; + (NSMutableDictionary *) getNativeSize; - (void) setFullScreenMode:(BOOL)fsm; - (BOOL) inFullScreenMode; - (void) toggleScreenMode; - (void) setDisplayMode:(int)mode fullScreen:(BOOL)fsm; - (int) indexOfCurrentSize; - (void) setScreenSize: (int)sizeIndex; - (NSMutableArray *)getScreenSizeArray; - (void) populateFullScreenModelist; - (NSSize) modeAsSize: (int)sizeIndex; - (void) saveWindowSize: (NSSize) windowSize; - (NSSize) loadWindowSize; - (int) loadFullscreenSettings; - (int) findDisplayModeForWidth: (unsigned int) d_width Height:(unsigned int) d_height Refresh: (unsigned int)d_refresh; - (NSSize) currentScreenSize; - (void) pollControls; - (void) setVirtualJoystick:(double) vmx :(double) vmy; - (NSPoint) virtualJoystickPosition; - (void) clearKeys; - (void) clearMouse; - (void) clearKey: (int)theKey; - (void) resetMouse; - (BOOL) isAlphabetKeyDown; - (void) supressKeysUntilKeyUp; // DJS - (BOOL) isDown: (int) key; - (BOOL) isOptDown; - (BOOL) isCtrlDown; - (BOOL) isCommandDown; - (BOOL) isShiftDown; - (int) numKeys; - (int) mouseWheelState; // Command-key combinations need special handling. SDL stubs for these mac functions. - (BOOL) isCommandQDown; - (BOOL) isCommandFDown; - (void) clearCommandF; - (void) setKeyboardTo: (NSString *) value; - (void) setMouseInDeltaMode: (BOOL) inDelta; - (void) setGammaValue: (float) value; - (float) gammaValue; - (void) setFov:(float)value fromFraction:(BOOL)fromFraction; - (float) fov:(BOOL)inFraction; // Check current state of shift key rather than relying on last event. + (BOOL)pollShiftKey; - (OOOpenGLMatrixManager *) getOpenGLMatrixManager; #ifndef NDEBUG // General image-dumping method. - (void) dumpRGBAToFileNamed:(NSString *)name bytes:(uint8_t *)bytes width:(NSUInteger)width height:(NSUInteger)height rowBytes:(NSUInteger)rowBytes; - (void) dumpRGBToFileNamed:(NSString *)name bytes:(uint8_t *)bytes width:(NSUInteger)width height:(NSUInteger)height rowBytes:(NSUInteger)rowBytes; - (void) dumpGrayToFileNamed:(NSString *)name bytes:(uint8_t *)bytes width:(NSUInteger)width height:(NSUInteger)height rowBytes:(NSUInteger)rowBytes; - (void) dumpGrayAlphaToFileNamed:(NSString *)name bytes:(uint8_t *)bytes width:(NSUInteger)width height:(NSUInteger)height rowBytes:(NSUInteger)rowBytes; - (void) dumpRGBAToRGBFileNamed:(NSString *)rgbName andGrayFileNamed:(NSString *)grayName bytes:(uint8_t *)bytes width:(NSUInteger)width height:(NSUInteger)height rowBytes:(NSUInteger)rowBytes; #endif @end oolite-1.82/src/SDL/MyOpenGLView.m000066400000000000000000002161551256642440500166350ustar00rootroot00000000000000/* MyOpenGLView.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "png.h" #import "MyOpenGLView.h" #import "GameController.h" #import "Universe.h" #import "OOSDLJoystickManager.h" #import "SDL_syswm.h" #import "OOSound.h" #import "NSFileManagerOOExtensions.h" // to find savedir #import "PlayerEntity.h" #import "GuiDisplayGen.h" #import "PlanetEntity.h" #import "OOGraphicsResetManager.h" #import "OOCollectionExtractors.h" // for splash screen settings #import "OOFullScreenController.h" #define kOOLogUnconvertedNSLog @"unclassified.MyOpenGLView" #include @interface MyOpenGLView (OOPrivate) - (void) handleStringInput: (SDL_KeyboardEvent *) kbd_event; // DJS @end @implementation MyOpenGLView + (NSMutableDictionary *) getNativeSize { NSMutableDictionary *mode=[[NSMutableDictionary alloc] init]; int nativeDisplayWidth = 1024; int nativeDisplayHeight = 768; #if OOLITE_LINUX SDL_SysWMinfo dpyInfo; SDL_VERSION(&dpyInfo.version); if(SDL_GetWMInfo(&dpyInfo)) { nativeDisplayWidth = DisplayWidth(dpyInfo.info.x11.display, 0); nativeDisplayHeight = DisplayHeight(dpyInfo.info.x11.display, 0); OOLog(@"display.mode.list.native", @"X11 native resolution detected: %d x %d", nativeDisplayWidth, nativeDisplayHeight); } else { OOLog(@"display.mode.list.native.failed", @"SDL_GetWMInfo failed, defaulting to 1024x768 for native size"); } #elif OOLITE_WINDOWS nativeDisplayWidth = GetSystemMetrics(SM_CXSCREEN); nativeDisplayHeight = GetSystemMetrics(SM_CYSCREEN); OOLog(@"display.mode.list.native", @"Windows native resolution detected: %d x %d", nativeDisplayWidth, nativeDisplayHeight); #else OOLog(@"display.mode.list.native.unknown", @"Unknown architecture, defaulting to 1024x768"); #endif [mode setValue: [NSNumber numberWithInt: nativeDisplayWidth] forKey:kOODisplayWidth]; [mode setValue: [NSNumber numberWithInt: nativeDisplayHeight] forKey: kOODisplayHeight]; [mode setValue: [NSNumber numberWithInt: 0] forKey: kOODisplayRefreshRate]; return [mode autorelease]; } - (void) createSurface { // Changing these flags can trigger texture bugs. const int videoModeFlags = SDL_HWSURFACE | SDL_OPENGL | SDL_RESIZABLE; if (showSplashScreen) { #if OOLITE_WINDOWS // Pre setVideoMode adjustments. NSSize tmp = currentWindowSize; ShowWindow(SDL_Window,SW_SHOWMINIMIZED); updateContext = NO; //don't update the (splash screen) window yet! MoveWindow(SDL_Window,GetSystemMetrics(SM_CXSCREEN)/2,GetSystemMetrics(SM_CYSCREEN)/2,1,1,TRUE); // centre the splash screen // Initialise the SDL surface. (need custom SDL.dll) surface = SDL_SetVideoMode(firstScreen.width, firstScreen.height, 32, videoModeFlags); // Post setVideoMode adjustments. currentWindowSize=tmp; #else // Changing the flags can trigger texture bugs. surface = SDL_SetVideoMode(8, 8, 32, videoModeFlags); if (!surface) { return; } #endif } else { #if OOLITE_WINDOWS updateContext = YES; #endif surface = SDL_SetVideoMode(firstScreen.width, firstScreen.height, 32, videoModeFlags); if (!surface) { return; } // blank the surface / go to fullscreen [self initialiseGLWithSize: firstScreen]; } _gamma = 1.0f; if (SDL_SetGamma(_gamma, _gamma, _gamma) < 0 ) { char * errStr = SDL_GetError(); OOLogWARN(@"gamma.set.failed", @"Could not set gamma: %s", errStr); // CIM: this doesn't seem to necessarily be fatal. Gamma settings // mostly work on mine despite this function failing. // exit(1); } SDL_EnableUNICODE(1); } - (id) init { self = [super init]; Uint32 colorkey; SDL_Surface *icon=NULL; NSString *imagesDir; // SDL splash screen settings NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; showSplashScreen = [prefs oo_boolForKey:@"splash-screen" defaultValue:YES]; BOOL vSyncPreference = [prefs oo_boolForKey:@"v-sync" defaultValue:YES]; int vSyncValue; NSArray *arguments = nil; NSEnumerator *argEnum = nil; NSString *arg = nil; BOOL noSplashArgFound = NO; arguments = [[NSProcessInfo processInfo] arguments]; // scan for splash screen overrides: -nosplash || --nosplash , -splash || --splash // scan for V-sync disabling overrides: -novsync || --novsync for (argEnum = [arguments objectEnumerator]; (arg = [argEnum nextObject]); ) { if ([arg isEqual:@"-nosplash"] || [arg isEqual:@"--nosplash"]) { showSplashScreen = NO; noSplashArgFound = YES; // -nosplash always trumps -splash } else if (([arg isEqual:@"-splash"] || [arg isEqual:@"--splash"]) && !noSplashArgFound) { showSplashScreen = YES; } // if V-sync is disabled at the command line, override the defaults file if ([arg isEqual:@"-novsync"] || [arg isEqual:@"--novsync"]) vSyncPreference = NO; } matrixManager = [[OOOpenGLMatrixManager alloc] init]; // TODO: This code up to and including stickHandler really ought // not to be in this class. OOLog(@"sdl.init", @"initialising SDL"); if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK) < 0) { OOLog(@"sdl.init.failed", @"Unable to init SDL: %s\n", SDL_GetError()); [self dealloc]; return nil; } SDL_putenv ("SDL_VIDEO_WINDOW_POS=center"); [OOJoystickManager setStickHandlerClass:[OOSDLJoystickManager class]]; // end TODO [OOSound setUp]; // Generate the window caption, containing the version number and the date the executable was compiled. static char windowCaption[128]; NSString *versionString = [NSString stringWithFormat:@"Oolite v%@", [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]]; strcpy (windowCaption, [versionString UTF8String]); strcat (windowCaption, " - "__DATE__); SDL_WM_SetCaption (windowCaption, "Oolite"); // Set window title. #if OOLITE_WINDOWS // needed for enabling system window manager events, which is needed for handling window movement messages SDL_EventState (SDL_SYSWMEVENT, SDL_ENABLE); //capture the window handle for later static SDL_SysWMinfo wInfo; SDL_VERSION(&wInfo.version); SDL_GetWMInfo(&wInfo); SDL_Window = wInfo.window; // This must be inited after SDL_Window has been set - we need the main window handle in order to get monitor info if (![self getCurrentMonitorInfo:&monitorInfo]) { OOLogWARN(@"display.initGL.monitorInfoWarning", @"Could not get current monitor information."); } #endif grabMouseStatus = NO; imagesDir = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Images"]; icon = SDL_LoadBMP([[imagesDir stringByAppendingPathComponent:@"WMicon.bmp"] UTF8String]); if (icon != NULL) { colorkey = SDL_MapRGB(icon->format, 128, 0, 128); SDL_SetColorKey(icon, SDL_SRCCOLORKEY, colorkey); SDL_WM_SetIcon(icon, NULL); } SDL_FreeSurface(icon); SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 32); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); // V-sync settings SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, vSyncPreference); // V-sync on by default. OOLog(@"display.initGL", @"V-Sync %@requested.", vSyncPreference ? @"" : @"not "); // Verify V-sync successfully set - report it if not if (vSyncPreference && SDL_GL_GetAttribute(SDL_GL_SWAP_CONTROL, &vSyncValue) == -1) { OOLogWARN(@"display.initGL", @"Could not enable V-Sync. Please check that your graphics driver supports the %@_swap_control extension.", OOLITE_WINDOWS ? @"WGL_EXT" : @"[GLX_SGI/GLX_MESA]"); } /* Multisampling significantly improves graphics quality with * basically no extra programming effort on our part, especially * for curved surfaces like the planet, but is also expensive - in * the worst case the entire scene must be rendered four * times. For now it can be a hidden setting. If early testing * doesn't give any problems (other than speed on low-end graphics * cards) a game options entry might be useful. - CIM, 24 Aug 2013*/ if ([prefs oo_boolForKey:@"anti-aliasing" defaultValue:NO]) { SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); } OOLog(@"display.mode.list", @"CREATING MODE LIST"); [self populateFullScreenModelist]; currentSize = 0; // Find what the full screen and windowed settings are. [self loadFullscreenSettings]; [self loadWindowSize]; // Set up the drawing surface's dimensions. firstScreen= (fullScreen) ? [self modeAsSize: currentSize] : currentWindowSize; viewSize = firstScreen; // viewSize must be set prior to splash screen initialization OOLog(@"display.initGL",@"Trying 32-bit depth buffer"); [self createSurface]; if (surface == NULL) { // Retry with a 24-bit depth buffer OOLog(@"display.initGL",@"Trying 24-bit depth buffer"); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); [self createSurface]; if (surface == NULL) { // Still not working? One last go... // Retry, allowing 16-bit contexts. OOLog(@"display.initGL",@"Trying 16-bit depth buffer"); SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); // and if it's this bad, forget even trying to multisample! SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); [self createSurface]; if (surface == NULL) { char * errStr = SDL_GetError(); OOLogERR(@"display.mode.error", @"Could not create display surface: %s", errStr); #if OOLITE_WINDOWS if (showSplashScreen) { [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"splash-screen"]; OOLogWARN(@"display.mode.conflict",@"Possible incompatibility between the splash screen and video drivers detected."); OOLogWARN(@"display.mode.conflict",@"Oolite will start without showing the splash screen from now on. Override with 'oolite.exe -splash'"); } #endif exit(1); } } } bounds.size.width = surface->w; bounds.size.height = surface->h; [self autoShowMouse]; virtualJoystickPosition = NSMakePoint(0.0,0.0); mouseWarped = NO; typedString = [[NSMutableString alloc] initWithString:@""]; allowingStringInput = gvStringInputNo; isAlphabetKeyDown = NO; timeIntervalAtLastClick = timeSinceLastMouseWheel = [NSDate timeIntervalSinceReferenceDate]; _mouseWheelState = gvMouseWheelNeutral; m_glContextInitialized = NO; return self; } - (void) endSplashScreen { if (!showSplashScreen) return; #if OOLITE_WINDOWS wasFullScreen = !fullScreen; updateContext = YES; ShowWindow(SDL_Window,SW_RESTORE); [self initialiseGLWithSize: firstScreen]; #else int videoModeFlags = SDL_HWSURFACE | SDL_OPENGL; videoModeFlags |= (fullScreen) ? SDL_FULLSCREEN : SDL_RESIZABLE; surface = SDL_SetVideoMode(firstScreen.width, firstScreen.height, 32, videoModeFlags); if (!surface && fullScreen == YES) { [self setFullScreenMode: NO]; videoModeFlags &= ~SDL_FULLSCREEN; videoModeFlags |= SDL_RESIZABLE; surface = SDL_SetVideoMode(currentWindowSize.width, currentWindowSize.height, 32, videoModeFlags); } SDL_putenv ("SDL_VIDEO_WINDOW_POS=none"); //stop linux from auto centering on resize /* MKW 2011.11.11 * Eat all SDL events to gobble up any resize events while the * splash-screen was visible. They affected the main window after 1.74. * TODO: should really process SDL events while showing the splash-screen int numEvents = 0; */ SDL_Event dummyEvent; while (SDL_PollEvent(&dummyEvent)) { /* Do nothing; the below is for development info numEvents++; OOLog(@"display.splash", @"Suppressed splash-screen event %d: %d ", numEvents, dummyEvent.type); */ } #endif [self updateScreen]; [self autoShowMouse]; } - (void) dealloc { if (typedString) [typedString release]; if (screenSizes) [screenSizes release]; if (surface != 0) { SDL_FreeSurface(surface); surface = 0; } SDL_Quit(); if (matrixManager) { [matrixManager release]; } [super dealloc]; } - (void) autoShowMouse { //don't touch the 'please wait...' cursor. if (fullScreen) { if (SDL_ShowCursor(SDL_QUERY) == SDL_ENABLE) SDL_ShowCursor(SDL_DISABLE); } else { if (SDL_ShowCursor(SDL_QUERY) == SDL_DISABLE) SDL_ShowCursor(SDL_ENABLE); } } - (void) setStringInput: (enum StringInput) value { allowingStringInput = value; } - (void) allowStringInput: (BOOL) value { if (value) allowingStringInput = gvStringInputAlpha; else allowingStringInput = gvStringInputNo; } -(enum StringInput) allowingStringInput { return allowingStringInput; } - (NSString *) typedString { return typedString; } - (void) resetTypedString { [typedString setString:@""]; } - (void) setTypedString:(NSString*) value { [typedString setString:value]; } - (NSRect) bounds { return bounds; } - (NSSize) viewSize { return viewSize; } - (GLfloat) display_z { return display_z; } - (GLfloat) x_offset { return x_offset; } - (GLfloat) y_offset { return y_offset; } - (GameController *) gameController { return gameController; } - (void) setGameController:(GameController *) controller { gameController = controller; } - (void) noteMouseInteractionModeChangedFrom:(OOMouseInteractionMode)oldMode to:(OOMouseInteractionMode)newMode { [self autoShowMouse]; [self setMouseInDeltaMode:OOMouseInteractionModeIsFlightMode(newMode)]; } - (BOOL) inFullScreenMode { return fullScreen; } #ifdef GNUSTEP - (void) setFullScreenMode:(BOOL)fsm { fullScreen = fsm; // Save the settings for later. [[NSUserDefaults standardUserDefaults] setBool: fullScreen forKey:@"fullscreen"]; [[NSUserDefaults standardUserDefaults] synchronize]; } - (void) toggleScreenMode { [self setFullScreenMode: !fullScreen]; #if OOLITE_WINDOWS [self getCurrentMonitorInfo:&monitorInfo]; #endif if(fullScreen) { #if OOLITE_WINDOWS if(![self isRunningOnPrimaryDisplayDevice]) { [self initialiseGLWithSize:NSMakeSize(monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left, monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top)]; } else [self initialiseGLWithSize:[self modeAsSize: currentSize]]; #else [self initialiseGLWithSize:[self modeAsSize: currentSize]]; #endif } else [self initialiseGLWithSize: currentWindowSize]; } - (void) setDisplayMode:(int)mode fullScreen:(BOOL)fsm { [self setFullScreenMode: fsm]; currentSize=mode; if(fullScreen) [self initialiseGLWithSize: [self modeAsSize: mode]]; } - (int) indexOfCurrentSize { return currentSize; } - (void) setScreenSize: (int)sizeIndex { currentSize=sizeIndex; if(fullScreen) [self initialiseGLWithSize: [self modeAsSize: currentSize]]; } - (NSMutableArray *)getScreenSizeArray { return screenSizes; } - (NSSize) modeAsSize:(int)sizeIndex { NSDictionary *mode=[screenSizes objectAtIndex: sizeIndex]; return NSMakeSize([[mode objectForKey: kOODisplayWidth] intValue], [[mode objectForKey: kOODisplayHeight] intValue]); } #endif - (void) display { [self updateScreen]; } - (void) updateScreen { [self drawRect: NSMakeRect(0, 0, viewSize.width, viewSize.height)]; } - (void) drawRect:(NSRect)rect { [self updateScreenWithVideoMode:YES]; } - (void) updateScreenWithVideoMode:(BOOL) v_mode { if ((viewSize.width != surface->w)||(viewSize.height != surface->h)) // resized { #if OOLITE_LINUX m_glContextInitialized = NO; //probably not needed #endif viewSize.width = surface->w; viewSize.height = surface->h; } if (m_glContextInitialized == NO) { [self initialiseGLWithSize:viewSize useVideoMode:v_mode]; } if (surface == 0) return; // do all the drawing! // if (UNIVERSE) [UNIVERSE drawUniverse]; else { // not set up yet, draw a black screen glClearColor( 0.0, 0.0, 0.0, 0.0); glClear( GL_COLOR_BUFFER_BIT); } SDL_GL_SwapBuffers(); } - (void) initSplashScreen { if (!showSplashScreen) return; //too early for OOTexture! SDL_Surface *image=NULL; SDL_Rect dest; NSString *imagesDir = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Images"]; image = SDL_LoadBMP([[imagesDir stringByAppendingPathComponent:@"splash.bmp"] UTF8String]); if (image == NULL) { SDL_FreeSurface(image); OOLogWARN(@"sdl.gameStart", @"image 'splash.bmp' not found!"); [self endSplashScreen]; return; } dest.x = 0; dest.y = 0; dest.w = image->w; dest.h = image->h; #if OOLITE_WINDOWS dest.x = (GetSystemMetrics(SM_CXSCREEN)- dest.w)/2; dest.y = (GetSystemMetrics(SM_CYSCREEN)-dest.h)/2; SetWindowLong(SDL_Window,GWL_STYLE,GetWindowLong(SDL_Window,GWL_STYLE) & ~WS_CAPTION & ~WS_THICKFRAME); ShowWindow(SDL_Window,SW_RESTORE); MoveWindow(SDL_Window,dest.x,dest.y,dest.w,dest.h,TRUE); #else /* MKW 2011.11.11 * According to Marc using the NOFRAME flag causes trouble under Ubuntu 8.04. * * The current Ubuntu LTS is 10.04, which doesn't seem to have that problem. * 12.04 LTS is going to be released soon, also without apparent problems. * Changed to SDL_NOFRAME, throwing caution to the wind - Kaks 2012.03.23 * Took SDL_NOFRAME out, since it still causes strange problems here - cim 2012.04.09 */ surface = SDL_SetVideoMode(dest.w, dest.h, 32, SDL_HWSURFACE | SDL_OPENGL); #endif OOSetOpenGLState(OPENGL_STATE_OVERLAY); glViewport( 0, 0, dest.w, dest.h); glEnable( GL_TEXTURE_2D ); glClearColor( 0.0f, 0.0f, 0.0f, 0.0f ); glClear( GL_COLOR_BUFFER_BIT ); [matrixManager resetProjection]; [matrixManager orthoLeft: 0.0f right: dest.w bottom: dest.h top: 0.0 near: -1.0 far: 1.0]; [matrixManager syncProjection]; [matrixManager resetModelView]; [matrixManager syncModelView]; GLuint texture; GLenum texture_format; GLint nOfColors; // get the number of channels in the SDL image nOfColors = image->format->BytesPerPixel; if (nOfColors == 4) // contains an alpha channel { if (image->format->Rmask == 0x000000ff) texture_format = GL_RGBA; else texture_format = GL_BGRA; } else if (nOfColors == 3) // no alpha channel { if (image->format->Rmask == 0x000000ff) texture_format = GL_RGB; else texture_format = GL_BGR; } else { SDL_FreeSurface(image); OOLog(@"Sdl.GameStart", @"----- Encoding error within image 'splash.bmp'"); [self endSplashScreen]; return; } glGenTextures( 1, &texture ); glBindTexture( GL_TEXTURE_2D, texture ); // Set the texture's stretching properties glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); // Set the texture image data with the information from SDL_Surface glTexImage2D( GL_TEXTURE_2D, 0, nOfColors, image->w, image->h, 0, texture_format, GL_UNSIGNED_BYTE, image->pixels ); glBindTexture( GL_TEXTURE_2D, texture ); glBegin( GL_QUADS ); glTexCoord2i( 0, 0 ); glVertex2i( 0, 0 ); glTexCoord2i( 1, 0 ); glVertex2i( dest.w, 0 ); glTexCoord2i( 1, 1 ); glVertex2i( dest.w, dest.h ); glTexCoord2i( 0, 1 ); glVertex2i( 0, dest.h ); glEnd(); SDL_GL_SwapBuffers(); [matrixManager resetModelView]; [matrixManager syncModelView]; if ( image ) { SDL_FreeSurface( image ); } glDeleteTextures(1, &texture); glDisable( GL_TEXTURE_2D ); OOVerifyOpenGLState(); } #if OOLITE_WINDOWS - (MONITORINFOEX) currentMonitorInfo { return monitorInfo; } - (BOOL) getCurrentMonitorInfo:(MONITORINFOEX *)mInfo { HMONITOR hMon = MonitorFromWindow(SDL_Window, MONITOR_DEFAULTTOPRIMARY); ZeroMemory(mInfo, sizeof(MONITORINFOEX)); mInfo->cbSize = sizeof(MONITORINFOEX); if (GetMonitorInfo (hMon, (LPMONITORINFO)mInfo)) { return YES; } return NO; } - (BOOL) isRunningOnPrimaryDisplayDevice { BOOL result = YES; [self getCurrentMonitorInfo:&monitorInfo]; if (!(monitorInfo.dwFlags & MONITORINFOF_PRIMARY)) { result = NO; } return result; } - (void) grabMouseInsideGameWindow:(BOOL) value { if(value) { RECT gameWindowRect; GetWindowRect(SDL_Window, &gameWindowRect); ClipCursor(&gameWindowRect); } else { ClipCursor(NULL); } grabMouseStatus = !!value; } #else // Linus stub methods // for Linux we assume we are always on the primary monitor for now - (BOOL) isRunningOnPrimaryDisplayDevice { return YES; } - (void) grabMouseInsideGameWindow:(BOOL) value { // do nothing } #endif //OOLITE_WINDOWS - (void) initialiseGLWithSize:(NSSize) v_size { [self initialiseGLWithSize:v_size useVideoMode:YES]; } - (void) initialiseGLWithSize:(NSSize) v_size useVideoMode:(BOOL) v_mode { #if OOLITE_LINUX NSSize oldViewSize = viewSize; #endif viewSize = v_size; OOLog(@"display.initGL", @"Requested a new surface of %d x %d, %@.", (int)viewSize.width, (int)viewSize.height,(fullScreen ? @"fullscreen" : @"windowed")); SDL_GL_SwapBuffers(); // clear the buffer before resize #if OOLITE_WINDOWS if (!updateContext) return; DEVMODE settings; settings.dmSize = sizeof(DEVMODE); settings.dmDriverExtra = 0; EnumDisplaySettings(0, ENUM_CURRENT_SETTINGS, &settings); static BOOL lastWindowPlacementMaximized = NO; WINDOWPLACEMENT windowPlacement; windowPlacement.length = sizeof(WINDOWPLACEMENT); if (fullScreen && GetWindowPlacement(SDL_Window, &windowPlacement) && (windowPlacement.showCmd == SW_SHOWMAXIMIZED)) { if (!wasFullScreen) { lastWindowPlacementMaximized = YES; } } // are we attempting to go to a different screen resolution? Note: this also takes care of secondary monitor situations because // EnumDisplaySettings was called with zero as first parameter, hence it yields settings for the display device the main application // thread is running on (i.e. primary). Since we only uae native resolution for full screen on secondaty moniors, changingResolution // is expected to always be false for non-primary display devices - Nikos 20150310 BOOL changingResolution = (fullScreen && settings.dmPelsWidth != viewSize.width && settings.dmPelsHeight != viewSize.height) || (wasFullScreen && settings.dmPelsWidth != [[[screenSizes objectAtIndex:0] objectForKey: kOODisplayWidth] intValue] && settings.dmPelsHeight != [[[screenSizes objectAtIndex:0] objectForKey: kOODisplayWidth] intValue]); RECT wDC; if (fullScreen) { /*NOTE: If we ever decide to change the default behaviour of launching always on primary monitor to launching on the monitor the program was started on, all that needs to be done is comment out the line below, as well as the identical one in the else branch further down. Nikos 20141222 */ [self getCurrentMonitorInfo: &monitorInfo]; settings.dmPelsWidth = viewSize.width; settings.dmPelsHeight = viewSize.height; settings.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT; // just before going fullscreen, save the location of the current window. It // may be needed in case of potential attempts to move our fullscreen window // in a maximized state (yes, in Windows this is entirely possible). if(lastWindowPlacementMaximized) { CopyRect(&lastGoodRect, &windowPlacement.rcNormalPosition); } else GetWindowRect(SDL_Window, &lastGoodRect); // ok, can go fullscreen now if(!wasFullScreen) { SetWindowLong(SDL_Window,GWL_STYLE,GetWindowLong(SDL_Window,GWL_STYLE) & ~WS_CAPTION & ~WS_THICKFRAME); } SetForegroundWindow(SDL_Window); if (changingResolution) { if (ChangeDisplaySettingsEx(monitorInfo.szDevice, &settings, NULL, CDS_FULLSCREEN, NULL) != DISP_CHANGE_SUCCESSFUL) { m_glContextInitialized = YES; OOLogERR(@"displayMode.change.error", @"Could not switch to requested display mode."); return; } } MoveWindow(SDL_Window, monitorInfo.rcMonitor.left, monitorInfo.rcMonitor.top, viewSize.width, viewSize.height, TRUE); } else if ( wasFullScreen ) { // stop saveWindowSize from reacting to caption & frame saveSize=NO; if (changingResolution) ChangeDisplaySettingsEx(NULL, NULL, NULL, 0, NULL); /*NOTE: If we ever decide to change the default behaviour of launching always on primary monitor to launching on the monitor the program was started on, we need to comment out the line below. For now, this line is needed for correct positioning of our window in case we return from a non-native resolution fullscreen and has to come after the display settings have been reverted. Nikos 20141222 */ [self getCurrentMonitorInfo: &monitorInfo]; SetWindowLong(SDL_Window,GWL_STYLE,GetWindowLong(SDL_Window,GWL_STYLE) | WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX ); if (!lastWindowPlacementMaximized) { windowPlacement.showCmd = SW_SHOWNORMAL; SetWindowPlacement(SDL_Window, &windowPlacement); } MoveWindow(SDL_Window, (monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left - (int)viewSize.width)/2 + monitorInfo.rcMonitor.left, (monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top - (int)viewSize.height)/2 + monitorInfo.rcMonitor.top - (lastWindowPlacementMaximized ? 32 : 16), (int)viewSize.width,(int)viewSize.height,TRUE); lastWindowPlacementMaximized = NO; ShowWindow(SDL_Window,SW_SHOW); } GetClientRect(SDL_Window, &wDC); if (!fullScreen && (bounds.size.width != wDC.right - wDC.left || bounds.size.height != wDC.bottom - wDC.top)) { RECT wDCtemp; // MoveWindow is used below to resize the game window when required. The resized window it // creates includes the client plus the non-client area. This means that although dimensions // capable of containing our wanted client area size are requested, the actual window generated has a // slightly smaller client area than intended. We fix this by calculating nonClientAreaCorrection // and adding it to the needed size when necessary (i.e. after splash screen or when switching from // full screen to window) - Nikos 20091024 NSSize nonClientAreaCorrection = NSMakeSize(0,0); GetWindowRect(SDL_Window, &wDC); if (wasFullScreen) // this is true when switching from full screen or when starting in windowed mode after the splash screen has ended { wDCtemp.top = wDC.top; wDCtemp.bottom = wDC.bottom; wDCtemp.left = wDC.left; wDCtemp.right = wDC.right; AdjustWindowRect(&wDCtemp, WS_CAPTION | WS_THICKFRAME, FALSE); nonClientAreaCorrection.width = fabs((wDCtemp.right - wDCtemp.left) - (wDC.right - wDC.left)); nonClientAreaCorrection.height = fabs((wDCtemp.bottom - wDCtemp.top) - (wDC.bottom - wDC.top)); } viewSize.width = wDC.right - wDC.left; viewSize.height = wDC.bottom - wDC.top; MoveWindow(SDL_Window,wDC.left,wDC.top,viewSize.width + nonClientAreaCorrection.width,viewSize.height + nonClientAreaCorrection.height,TRUE); GetClientRect(SDL_Window, &wDC); } // Reset bounds and viewSize to current values bounds.size.width = viewSize.width = wDC.right - wDC.left; bounds.size.height = viewSize.height = wDC.bottom - wDC.top; if (fullScreen) // bounds on fullscreen coincide with client area, since we are borderless { bounds.origin.x = monitorInfo.rcMonitor.left; bounds.origin.y = monitorInfo.rcMonitor.top; } wasFullScreen=fullScreen; #else //OOLITE_LINUX int videoModeFlags = SDL_HWSURFACE | SDL_OPENGL; if (v_mode == NO) videoModeFlags |= SDL_NOFRAME; if (fullScreen == YES) { videoModeFlags |= SDL_FULLSCREEN; } else { videoModeFlags |= SDL_RESIZABLE; } surface = SDL_SetVideoMode((int)viewSize.width, (int)viewSize.height, 32, videoModeFlags); if (!surface && fullScreen == YES) { [self setFullScreenMode: NO]; viewSize = oldViewSize; videoModeFlags &= ~SDL_FULLSCREEN; videoModeFlags |= SDL_RESIZABLE; surface = SDL_SetVideoMode((int)viewSize.width, (int)viewSize.height, 32, videoModeFlags); } if (!surface) { // we should always have a valid surface, but in case we don't OOLogERR(@"display.mode.error",@"Unable to change display mode: %s",SDL_GetError()); exit(1); } bounds.size.width = surface->w; bounds.size.height = surface->h; #endif OOLog(@"display.initGL", @"Created a new surface of %d x %d, %@.", (int)viewSize.width, (int)viewSize.height,(fullScreen ? @"fullscreen" : @"windowed")); if (viewSize.width/viewSize.height > 4.0/3.0) { display_z = 480.0 * bounds.size.width/bounds.size.height; x_offset = 240.0 * bounds.size.width/bounds.size.height; y_offset = 240.0; } else { display_z = 640.0; x_offset = 320.0; y_offset = 320.0 * bounds.size.height/bounds.size.width; } if (surface != 0) SDL_FreeSurface(surface); [self autoShowMouse]; [[self gameController] setUpBasicOpenGLStateWithSize:viewSize]; SDL_GL_SwapBuffers(); squareX = 0.0f; m_glContextInitialized = YES; } - (BOOL) snapShot:(NSString *)filename { BOOL snapShotOK = YES; SDL_Surface* tmpSurface; // backup the previous directory NSString* originalDirectory = [[NSFileManager defaultManager] currentDirectoryPath]; // use the snapshots directory [[NSFileManager defaultManager] chdirToSnapshotPath]; BOOL withFilename = (filename != nil); static unsigned imageNo = 0; unsigned tmpImageNo = 0; NSString *pathToPic = nil; NSString *baseName = @"oolite"; #if SNAPSHOTS_PNG_FORMAT NSString *extension = @".png"; #else NSString *extension = @".bmp"; #endif if (withFilename) { baseName = filename; pathToPic = [filename stringByAppendingString:extension]; } else { tmpImageNo = imageNo; } if (withFilename && [[NSFileManager defaultManager] fileExistsAtPath:pathToPic]) { OOLog(@"screenshot.filenameExists", @"Snapshot \"%@%@\" already exists - adding numerical sequence.", pathToPic, extension); pathToPic = nil; } if (pathToPic == nil) { do { tmpImageNo++; pathToPic = [NSString stringWithFormat:@"%@-%03d%@", baseName, tmpImageNo, extension]; } while ([[NSFileManager defaultManager] fileExistsAtPath:pathToPic]); } if (!withFilename) { imageNo = tmpImageNo; } OOLog(@"screenshot", @"Saved screen shot \"%@\" (%u x %u pixels).", pathToPic, surface->w, surface->h); int pitch = surface->w * 3; unsigned char *pixls = malloc(pitch * surface->h); int y; int off; if (surface->w % 4) glPixelStorei(GL_PACK_ALIGNMENT,1); else glPixelStorei(GL_PACK_ALIGNMENT,4); for (y=surface->h-1, off=0; y>=0; y--, off+=pitch) { glReadPixels(0, y, surface->w, 1, GL_RGB, GL_UNSIGNED_BYTE, pixls + off); } tmpSurface=SDL_CreateRGBSurfaceFrom(pixls,surface->w,surface->h,24,surface->w*3,0xFF,0xFF00,0xFF0000,0x0); #if SNAPSHOTS_PNG_FORMAT if(![self pngSaveSurface:pathToPic withSurface:tmpSurface]) { OOLog(@"screenshotPNG", @"Failed to save %@", pathToPic); snapShotOK = NO; } #else if (SDL_SaveBMP(tmpSurface, [pathToPic UTF8String]) == -1) { OOLog(@"screenshotBMP", @"Failed to save %@", pathToPic); snapShotOK = NO; } #endif SDL_FreeSurface(tmpSurface); free(pixls); // return to the previous directory [[NSFileManager defaultManager] changeCurrentDirectoryPath:originalDirectory]; return snapShotOK; } #if SNAPSHOTS_PNG_FORMAT // This method is heavily based on 'Mars, Land of No Mercy' SDL examples, by Angelo "Encelo" Theodorou, see http://encelo.netsons.org/programming/sdl - (BOOL) pngSaveSurface:(NSString *)fileName withSurface:(SDL_Surface *)surf { FILE *fp; png_structp pngPtr; png_infop infoPtr; int i, colorType; png_bytep *rowPointers; fp = fopen([fileName UTF8String], "wb"); if (fp == NULL) { OOLog(@"pngSaveSurface.fileCreate.failed", @"Failed to create output screenshot file %@", fileName); return NO; } // initialize png structures (no callbacks) pngPtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (pngPtr == NULL) { return NO; } infoPtr = png_create_info_struct(pngPtr); if (infoPtr == NULL) { png_destroy_write_struct(&pngPtr, (png_infopp)NULL); OOLog(@"pngSaveSurface.info_struct.failed", @"png_create_info_struct error"); exit(-1); } if (setjmp(png_jmpbuf(pngPtr))) { png_destroy_write_struct(&pngPtr, &infoPtr); fclose(fp); exit(-1); } png_init_io(pngPtr, fp); colorType = PNG_COLOR_MASK_COLOR; /* grayscale not supported */ if (surf->format->palette) { colorType |= PNG_COLOR_MASK_PALETTE; } else if (surf->format->Amask) { colorType |= PNG_COLOR_MASK_ALPHA; } png_set_IHDR(pngPtr, infoPtr, surf->w, surf->h, 8, colorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); // write the image png_write_info(pngPtr, infoPtr); png_set_packing(pngPtr); rowPointers = (png_bytep*) malloc(sizeof(png_bytep)*surf->h); for (i = 0; i < surf->h; i++) { rowPointers[i] = (png_bytep)(Uint8 *)surf->pixels + i*surf->pitch; } png_write_image(pngPtr, rowPointers); png_write_end(pngPtr, infoPtr); free(rowPointers); png_destroy_write_struct(&pngPtr, &infoPtr); fclose(fp); return YES; } #endif // SNAPSHOTS_PNG_FORMAT /* Turn the Cocoa ArrowKeys into our arrow key constants. */ - (int) translateKeyCode: (int) input { int key = input; switch ( input ) { case NSUpArrowFunctionKey: key = gvArrowKeyUp; break; case NSDownArrowFunctionKey: key = gvArrowKeyDown; break; case NSLeftArrowFunctionKey: key = gvArrowKeyLeft; break; case NSRightArrowFunctionKey: key = gvArrowKeyRight; break; case NSF1FunctionKey: key = gvFunctionKey1; break; case NSF2FunctionKey: key = gvFunctionKey2; break; case NSF3FunctionKey: key = gvFunctionKey3; break; case NSF4FunctionKey: key = gvFunctionKey4; break; case NSF5FunctionKey: key = gvFunctionKey5; break; case NSF6FunctionKey: key = gvFunctionKey6; break; case NSF7FunctionKey: key = gvFunctionKey7; break; case NSF8FunctionKey: key = gvFunctionKey8; break; case NSF9FunctionKey: key = gvFunctionKey9; break; case NSF10FunctionKey: key = gvFunctionKey10; break; case NSF11FunctionKey: key = gvFunctionKey11; break; case NSHomeFunctionKey: key = gvHomeKey; break; default: break; } return key; } - (void) setVirtualJoystick:(double) vmx :(double) vmy { virtualJoystickPosition.x = vmx; virtualJoystickPosition.y = vmy; } - (NSPoint) virtualJoystickPosition { return virtualJoystickPosition; } ///////////////////////////////////////////////////////////// - (void) clearKeys { int i; for (i = 0; i < [self numKeys]; i++) keys[i] = NO; } - (void) clearMouse { keys[gvMouseDoubleClick] = NO; keys[gvMouseLeftButton] = NO; doubleClick = NO; } - (void) clearKey: (int)theKey { if (theKey >= 0 && theKey < [self numKeys]) { keys[theKey] = NO; } } - (void) resetMouse { [self setVirtualJoystick:0.0 :0.0]; if ([[PlayerEntity sharedPlayer] isMouseControlOn]) { SDL_WarpMouse([self viewSize].width / 2, [self viewSize].height / 2); mouseWarped = YES; } } - (BOOL) isAlphabetKeyDown { return isAlphabetKeyDown = NO;; } // DJS: When entering submenus in the gui, it is not helpful if the // key down that brought you into the submenu is still registered // as down when we're in. This makes isDown return NO until a key up // event has been received from SDL. - (void) supressKeysUntilKeyUp { if (keys[gvMouseDoubleClick] == NO) { supressKeys = YES; [self clearKeys]; } else { [self clearMouse]; } } - (BOOL) isDown: (int) key { if ( supressKeys ) return NO; if ( key < 0 ) return NO; if ( key >= [self numKeys] ) return NO; return keys[key]; } - (BOOL) isOptDown { return opt; } - (BOOL) isCtrlDown { return ctrl; } - (BOOL) isCommandDown { return command; } - (BOOL) isShiftDown { return shift; } - (int) numKeys { return NUM_KEYS; } - (int) mouseWheelState { return _mouseWheelState; } - (BOOL) isCommandQDown { return NO; } - (BOOL) isCommandFDown { return NO; } - (void) clearCommandF { // SDL stub for the mac function. } - (void) setKeyboardTo: (NSString *) value { #if OOLITE_WINDOWS keyboardMap=gvKeyboardAuto; if ([value isEqual: @"UK"]) { keyboardMap=gvKeyboardUK; } else if ([value isEqual: @"US"]) { keyboardMap=gvKeyboardUS; } #endif } - (void)pollControls { SDL_Event event; SDL_KeyboardEvent *kbd_event; SDL_MouseButtonEvent *mbtn_event; SDL_MouseMotionEvent *mmove_event; int mxdelta, mydelta; float mouseVirtualStickSensitivityX = viewSize.width * MOUSEVIRTUALSTICKSENSITIVITYFACTOR; float mouseVirtualStickSensitivityY = viewSize.height * MOUSEVIRTUALSTICKSENSITIVITYFACTOR; NSTimeInterval timeNow = [NSDate timeIntervalSinceReferenceDate]; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_JOYAXISMOTION: case SDL_JOYBUTTONUP: case SDL_JOYBUTTONDOWN: case SDL_JOYHATMOTION: [(OOSDLJoystickManager*)[OOJoystickManager sharedStickHandler] handleSDLEvent: &event]; break; case SDL_MOUSEBUTTONDOWN: mbtn_event = (SDL_MouseButtonEvent*)&event; switch(mbtn_event->button) { case SDL_BUTTON_LEFT: keys[gvMouseLeftButton] = YES; break; case SDL_BUTTON_RIGHT: // Cocoa version does this in the GameController /* The mouseWarped variable is quite important as far as mouse control is concerned. When we reset the virtual joystick (mouse) coordinates, we need to send a WarpMouse call because we must recenter the pointer physically on screen. This goes together with a mouse motion event, so we use mouseWarped to simply ignore handling of motion events in this case. - Nikos 20110721 */ [self resetMouse]; // Will set mouseWarped to YES break; // mousewheel stuff case SDL_BUTTON_WHEELUP: _mouseWheelState = gvMouseWheelUp; break; case SDL_BUTTON_WHEELDOWN: _mouseWheelState = gvMouseWheelDown; break; } break; case SDL_MOUSEBUTTONUP: mbtn_event = (SDL_MouseButtonEvent*)&event; NSTimeInterval timeBetweenClicks = timeNow - timeIntervalAtLastClick; timeIntervalAtLastClick += timeBetweenClicks; if (mbtn_event->button == SDL_BUTTON_LEFT) { if (!doubleClick) { doubleClick = (timeBetweenClicks < MOUSE_DOUBLE_CLICK_INTERVAL); // One fifth of a second keys[gvMouseDoubleClick] = doubleClick; } keys[gvMouseLeftButton] = NO; } /* Mousewheel handling - just note time since last use here and mark as inactive, if needed, at the end of this method. Note that the mousewheel button up event is kind of special, as in, it is sent at the same time as its corresponding mousewheel button down one - Nikos 20140809 */ if (mbtn_event->button == SDL_BUTTON_WHEELUP || mbtn_event->button == SDL_BUTTON_WHEELDOWN) { NSTimeInterval timeBetweenMouseWheels = timeNow - timeSinceLastMouseWheel; timeSinceLastMouseWheel += timeBetweenMouseWheels; } break; case SDL_MOUSEMOTION: { // Delta mode is set when the game is in 'flight' mode. // In this mode, the mouse movement delta is used rather // than absolute position. This is because if the user // clicks the right button to recentre the virtual joystick, // if we are using absolute joystick positioning, as soon // as the player touches the mouse again, the virtual joystick // will snap back to the absolute position (which can be // annoyingly fatal in battle). if(mouseInDeltaMode) { // possible TODO - make virtual stick sensitivity configurable SDL_GetRelativeMouseState(&mxdelta, &mydelta); double mxd=(double)mxdelta / mouseVirtualStickSensitivityX; double myd=(double)mydelta / mouseVirtualStickSensitivityY; if (!mouseWarped) // Standard event, update coordinates { virtualJoystickPosition.x += mxd; virtualJoystickPosition.y += myd; // if we excceed the limits, revert changes if(fabs(virtualJoystickPosition.x) > MOUSEX_MAXIMUM) { virtualJoystickPosition.x -= mxd; } if(fabs(virtualJoystickPosition.y) > MOUSEY_MAXIMUM) { virtualJoystickPosition.y -= myd; } } else { // Motion event generated by WarpMouse is ignored and // we reset mouseWarped for the next time. mouseWarped = NO; } } else { // Windowed mode. Use the absolute position so the // Oolite mouse pointer appears under the X Window System // mouse pointer. mmove_event = (SDL_MouseMotionEvent*)&event; int w=bounds.size.width; int h=bounds.size.height; if (!mouseWarped) // standard event, handle it { double mx = mmove_event->x - w/2.0; double my = mmove_event->y - h/2.0; if (display_z > 640.0) { mx /= w * MAIN_GUI_PIXEL_WIDTH / display_z; my /= h; } else { mx /= MAIN_GUI_PIXEL_WIDTH * w / 640.0; my /= MAIN_GUI_PIXEL_HEIGHT * w / 640.0; } [self setVirtualJoystick:mx :my]; } else { // event coming from WarpMouse ignored, get ready for the next mouseWarped = NO; } } break; } case SDL_KEYDOWN: kbd_event = (SDL_KeyboardEvent*)&event; if(allowingStringInput) { [self handleStringInput: kbd_event]; } // Macro KEYCODE_DOWN_EITHER. Detect the keypress state (with shift or without) and assign appropriate values to the // keys array. This way Oolite can use more keys, since now key '3', for example is a different keypress to '#'. #define KEYCODE_DOWN_EITHER(a,b) do { \ if (shift) { keys[a] = YES; keys[b] = NO; } else { keys[a] = NO; keys[b] = YES; } \ } while (0) #if OOLITE_WINDOWS /* Windows locale patch - Enable backslash in win/UK */ if (EXPECT_NOT(kbd_event->keysym.scancode==86 && (keyboardMap==gvKeyboardAuto || keyboardMap==gvKeyboardUK))) { //non-US scancode. If in autodetect, we'll assume UK keyboard. KEYCODE_DOWN_EITHER (124, 92); // | or \. } else switch (kbd_event->keysym.sym) { case SDLK_BACKSLASH: if (keyboardMap==gvKeyboardUK ) { KEYCODE_DOWN_EITHER (126, 35); // ~ or # } else if (keyboardMap==gvKeyboardAuto || keyboardMap==gvKeyboardUS) { KEYCODE_DOWN_EITHER (124, 92); // | or \. } break; #else switch (kbd_event->keysym.sym) { case SDLK_BACKSLASH: KEYCODE_DOWN_EITHER (124, 92); break; // | or \. #endif case SDLK_1: KEYCODE_DOWN_EITHER (33, gvNumberKey1); break; // ! or 1 #if OOLITE_WINDOWS /* Windows locale patch - fix shift-2 & shift-3 */ case SDLK_2: if (keyboardMap==gvKeyboardUK) { KEYCODE_DOWN_EITHER (34, gvNumberKey2); // " or 2 } else { KEYCODE_DOWN_EITHER (64, gvNumberKey2); // @ or 2 } break; case SDLK_3: if (keyboardMap==gvKeyboardUK) { KEYCODE_DOWN_EITHER (156, gvNumberKey3); // � or 3 } else { KEYCODE_DOWN_EITHER (35, gvNumberKey3); // # or 3 } break; #else case SDLK_2: KEYCODE_DOWN_EITHER (64, gvNumberKey2); break; // @ or 2 case SDLK_3: KEYCODE_DOWN_EITHER (35, gvNumberKey3); break; // # or 3 #endif case SDLK_4: KEYCODE_DOWN_EITHER (36, gvNumberKey4); break; // $ or 4 case SDLK_5: KEYCODE_DOWN_EITHER (37, gvNumberKey5); break; // % or 5 case SDLK_6: KEYCODE_DOWN_EITHER (94, gvNumberKey6); break; // ^ or 6 case SDLK_7: KEYCODE_DOWN_EITHER (38, gvNumberKey7); break; // & or 7 case SDLK_8: KEYCODE_DOWN_EITHER (42, gvNumberKey8); break; // * or 8 case SDLK_9: KEYCODE_DOWN_EITHER (40, gvNumberKey9); break; // ( or 9 case SDLK_0: KEYCODE_DOWN_EITHER (41, gvNumberKey0); break; // ) or 0 case SDLK_MINUS: KEYCODE_DOWN_EITHER (95, 45); break; // _ or - case SDLK_COMMA: KEYCODE_DOWN_EITHER (60, 44); break; // < or , case SDLK_EQUALS: KEYCODE_DOWN_EITHER (43, 61); break; // + or = case SDLK_PERIOD: KEYCODE_DOWN_EITHER (62, 46); break; // > or . case SDLK_SLASH: KEYCODE_DOWN_EITHER (63, 47); break; // ? or / case SDLK_a: KEYCODE_DOWN_EITHER (65, 97); break; // A or a case SDLK_b: KEYCODE_DOWN_EITHER (66, 98); break; // B or b case SDLK_c: KEYCODE_DOWN_EITHER (67, 99); break; // C or c case SDLK_d: KEYCODE_DOWN_EITHER (68, 100); break; // D or d case SDLK_e: KEYCODE_DOWN_EITHER (69, 101); break; // E or e case SDLK_f: KEYCODE_DOWN_EITHER (70, 102); break; // F or f case SDLK_g: KEYCODE_DOWN_EITHER (71, 103); break; // G or g case SDLK_h: KEYCODE_DOWN_EITHER (72, 104); break; // H or h case SDLK_i: KEYCODE_DOWN_EITHER (73, 105); break; // I or i case SDLK_j: KEYCODE_DOWN_EITHER (74, 106); break; // J or j case SDLK_k: KEYCODE_DOWN_EITHER (75, 107); break; // K or k case SDLK_l: KEYCODE_DOWN_EITHER (76, 108); break; // L or l case SDLK_m: KEYCODE_DOWN_EITHER (77, 109); break; // M or m case SDLK_n: KEYCODE_DOWN_EITHER (78, 110); break; // N or n case SDLK_o: KEYCODE_DOWN_EITHER (79, 111); break; // O or o case SDLK_p: KEYCODE_DOWN_EITHER (80, 112); break; // P or p case SDLK_q: KEYCODE_DOWN_EITHER (81, 113); break; // Q or q case SDLK_r: KEYCODE_DOWN_EITHER (82, 114); break; // R or r case SDLK_s: KEYCODE_DOWN_EITHER (83, 115); break; // S or s case SDLK_t: KEYCODE_DOWN_EITHER (84, 116); break; // T or t case SDLK_u: KEYCODE_DOWN_EITHER (85, 117); break; // U or u case SDLK_v: KEYCODE_DOWN_EITHER (86, 118); break; // V or v case SDLK_w: KEYCODE_DOWN_EITHER (87, 119); break; // W or w case SDLK_x: KEYCODE_DOWN_EITHER (88, 120); break; // X or x case SDLK_y: KEYCODE_DOWN_EITHER (89, 121); break; // Y or y case SDLK_z: KEYCODE_DOWN_EITHER (90, 122); break; // Z or z case SDLK_SEMICOLON: KEYCODE_DOWN_EITHER(58, 59); break; // : or ; //SDLK_BACKQUOTE and SDLK_HASH are special cases. No SDLK_ with code 126 exists. case SDLK_HASH: if (!shift) keys[126] = YES; break; // ~ (really #) case SDLK_BACKQUOTE: if (!shift) keys[96] = YES; break; // ` case SDLK_QUOTE: keys[39] = YES; break; // ' case SDLK_LEFTBRACKET: keys[91] = YES; break; // [ case SDLK_RIGHTBRACKET: keys[93] = YES; break; // ] case SDLK_HOME: keys[gvHomeKey] = YES; break; case SDLK_END: keys[gvEndKey] = YES; break; case SDLK_INSERT: keys[gvInsertKey] = YES; break; case SDLK_PAGEUP: keys[gvPageUpKey] = YES; break; case SDLK_PAGEDOWN: keys[gvPageDownKey] = YES; break; case SDLK_SPACE: keys[32] = YES; break; case SDLK_RETURN: keys[13] = YES; break; case SDLK_TAB: keys[9] = YES; break; case SDLK_UP: keys[gvArrowKeyUp] = YES; break; case SDLK_DOWN: keys[gvArrowKeyDown] = YES; break; case SDLK_LEFT: keys[gvArrowKeyLeft] = YES; break; case SDLK_RIGHT: keys[gvArrowKeyRight] = YES; break; case SDLK_KP_MINUS: keys[45] = YES; break; // numeric keypad - key case SDLK_KP_PLUS: keys[43] = YES; break; // numeric keypad + key case SDLK_KP_ENTER: keys[13] = YES; break; case SDLK_KP_MULTIPLY: keys[42] = YES; break; // * case SDLK_KP1: keys[gvNumberPadKey1] = YES; break; case SDLK_KP2: keys[gvNumberPadKey2] = YES; break; case SDLK_KP3: keys[gvNumberPadKey3] = YES; break; case SDLK_KP4: keys[gvNumberPadKey4] = YES; break; case SDLK_KP5: keys[gvNumberPadKey5] = YES; break; case SDLK_KP6: keys[gvNumberPadKey6] = YES; break; case SDLK_KP7: keys[gvNumberPadKey7] = YES; break; case SDLK_KP8: keys[gvNumberPadKey8] = YES; break; case SDLK_KP9: keys[gvNumberPadKey9] = YES; break; case SDLK_KP0: keys[gvNumberPadKey0] = YES; break; case SDLK_F1: keys[gvFunctionKey1] = YES; break; case SDLK_F2: keys[gvFunctionKey2] = YES; break; case SDLK_F3: keys[gvFunctionKey3] = YES; break; case SDLK_F4: keys[gvFunctionKey4] = YES; break; case SDLK_F5: keys[gvFunctionKey5] = YES; break; case SDLK_F6: keys[gvFunctionKey6] = YES; break; case SDLK_F7: keys[gvFunctionKey7] = YES; break; case SDLK_F8: keys[gvFunctionKey8] = YES; break; case SDLK_F9: keys[gvFunctionKey9] = YES; break; case SDLK_F10: keys[gvFunctionKey10] = YES; break; case SDLK_BACKSPACE: case SDLK_DELETE: keys[gvDeleteKey] = YES; break; case SDLK_LSHIFT: case SDLK_RSHIFT: shift = YES; break; case SDLK_LCTRL: case SDLK_RCTRL: ctrl = YES; break; case SDLK_F12: [self toggleScreenMode]; // normally we would want to do a gui screen resize update here, but // toggling full screen mode executes an SDL_VIDEORESIZE event, which // takes care of this for us - Nikos 20140129 break; case SDLK_ESCAPE: if (shift) { SDL_FreeSurface(surface); [gameController exitAppWithContext:@"Shift-escape pressed"]; } else keys[27] = YES; break; default: // Numerous cases not handled. //OOLog(@"keys.test", @"Keydown scancode: %d", kbd_event->keysym.scancode); ; } break; case SDL_KEYUP: supressKeys = NO; // DJS kbd_event = (SDL_KeyboardEvent*)&event; #define KEYCODE_UP_BOTH(a,b) do { \ keys[a] = NO; keys[b] = NO; \ } while (0) #if OOLITE_WINDOWS /* Windows locale patch - Enable backslash in win/UK */ if (EXPECT_NOT(kbd_event->keysym.scancode==86 && (keyboardMap==gvKeyboardAuto || keyboardMap==gvKeyboardUK))) { //non-US scancode. If in autodetect, we'll assume UK keyboard. KEYCODE_UP_BOTH (124, 92); // | or \. } else switch (kbd_event->keysym.sym) { case SDLK_BACKSLASH: if (keyboardMap==gvKeyboardUK ) { KEYCODE_UP_BOTH (126, 35); // ~ or # } else if (keyboardMap==gvKeyboardAuto || keyboardMap==gvKeyboardUS) { KEYCODE_UP_BOTH (124, 92); // | or \. } break; #else switch (kbd_event->keysym.sym) { case SDLK_BACKSLASH: KEYCODE_UP_BOTH (124, 92); break; // | or \. #endif case SDLK_1: KEYCODE_UP_BOTH (33, gvNumberKey1); break; // ! and 1 #if OOLITE_WINDOWS /* Windows locale patch - fix shift-2 & shift-3 */ case SDLK_2: if (keyboardMap==gvKeyboardUK) { KEYCODE_UP_BOTH (34, gvNumberKey2); // " or 2 } else { KEYCODE_UP_BOTH (64, gvNumberKey2); // @ or 2 } break; case SDLK_3: if (keyboardMap==gvKeyboardUK) { KEYCODE_UP_BOTH (156, gvNumberKey3); // � or 3 } else { KEYCODE_UP_BOTH (35, gvNumberKey3); // # or 3 } break; #else case SDLK_2: KEYCODE_UP_BOTH (64, gvNumberKey2); break; // @ or 2 case SDLK_3: KEYCODE_UP_BOTH (35, gvNumberKey3); break; // # or 3 #endif case SDLK_4: KEYCODE_UP_BOTH (36, gvNumberKey4); break; // $ or 4 case SDLK_5: KEYCODE_UP_BOTH (37, gvNumberKey5); break; // % or 5 case SDLK_6: KEYCODE_UP_BOTH (94, gvNumberKey6); break; // ^ or 6 case SDLK_7: KEYCODE_UP_BOTH (38, gvNumberKey7); break; // & or 7 case SDLK_8: KEYCODE_UP_BOTH (42, gvNumberKey8); break; // * or 8 case SDLK_9: KEYCODE_UP_BOTH (40, gvNumberKey9); break; // ( or 9 case SDLK_0: KEYCODE_UP_BOTH (41, gvNumberKey0); break; // ) or 0 case SDLK_MINUS: KEYCODE_UP_BOTH (95, 45); break; // _ and - case SDLK_COMMA: KEYCODE_UP_BOTH (60, 44); break; // < and , case SDLK_EQUALS: KEYCODE_UP_BOTH (43, 61); break; // + and = case SDLK_PERIOD: KEYCODE_UP_BOTH (62, 46); break; // > and . case SDLK_SLASH: KEYCODE_UP_BOTH (63, 47); break; // ? and / case SDLK_a: KEYCODE_UP_BOTH (65, 97); break; // A and a case SDLK_b: KEYCODE_UP_BOTH (66, 98); break; // B and b case SDLK_c: KEYCODE_UP_BOTH (67, 99); break; // C and c case SDLK_d: KEYCODE_UP_BOTH (68, 100); break; // D and d case SDLK_e: KEYCODE_UP_BOTH (69, 101); break; // E and e case SDLK_f: KEYCODE_UP_BOTH (70, 102); break; // F and f case SDLK_g: KEYCODE_UP_BOTH (71, 103); break; // G and g case SDLK_h: KEYCODE_UP_BOTH (72, 104); break; // H and h case SDLK_i: KEYCODE_UP_BOTH (73, 105); break; // I and i case SDLK_j: KEYCODE_UP_BOTH (74, 106); break; // J and j case SDLK_k: KEYCODE_UP_BOTH (75, 107); break; // K and k case SDLK_l: KEYCODE_UP_BOTH (76, 108); break; // L and l case SDLK_m: KEYCODE_UP_BOTH (77, 109); break; // M and m case SDLK_n: KEYCODE_UP_BOTH (78, 110); break; // N and n case SDLK_o: KEYCODE_UP_BOTH (79, 111); break; // O and o case SDLK_p: KEYCODE_UP_BOTH (80, 112); break; // P and p case SDLK_q: KEYCODE_UP_BOTH (81, 113); break; // Q and q case SDLK_r: KEYCODE_UP_BOTH (82, 114); break; // R and r case SDLK_s: KEYCODE_UP_BOTH (83, 115); break; // S and s case SDLK_t: KEYCODE_UP_BOTH (84, 116); break; // T and t case SDLK_u: KEYCODE_UP_BOTH (85, 117); break; // U and u case SDLK_v: KEYCODE_UP_BOTH (86, 118); break; // V and v case SDLK_w: KEYCODE_UP_BOTH (87, 119); break; // W and w case SDLK_x: KEYCODE_UP_BOTH (88, 120); break; // X and x case SDLK_y: KEYCODE_UP_BOTH (89, 121); break; // Y and y case SDLK_z: KEYCODE_UP_BOTH (90, 122); break; // Z and z case SDLK_SEMICOLON: KEYCODE_UP_BOTH(58, 59); break; // : and ; //SDLK_BACKQUOTE and SDLK_HASH are special cases. No SDLK_ with code 126 exists. case SDLK_HASH: if (!shift) keys[126] = NO; break; // ~ (really #) case SDLK_BACKQUOTE: keys[96] = NO; break; // ` case SDLK_QUOTE: keys[39] = NO; break; // ' case SDLK_LEFTBRACKET: keys[91] = NO; break; // [ case SDLK_RIGHTBRACKET: keys[93] = NO; break; // ] case SDLK_HOME: keys[gvHomeKey] = NO; break; case SDLK_END: keys[gvEndKey] = NO; break; case SDLK_INSERT: keys[gvInsertKey] = NO; break; case SDLK_PAGEUP: keys[gvPageUpKey] = NO; break; case SDLK_PAGEDOWN: keys[gvPageDownKey] = NO; break; case SDLK_SPACE: keys[32] = NO; break; case SDLK_RETURN: keys[13] = NO; break; case SDLK_TAB: keys[9] = NO; break; case SDLK_UP: keys[gvArrowKeyUp] = NO; break; case SDLK_DOWN: keys[gvArrowKeyDown] = NO; break; case SDLK_LEFT: keys[gvArrowKeyLeft] = NO; break; case SDLK_RIGHT: keys[gvArrowKeyRight] = NO; break; case SDLK_KP_MINUS: keys[45] = NO; break; // numeric keypad - key case SDLK_KP_PLUS: keys[43] = NO; break; // numeric keypad + key case SDLK_KP_ENTER: keys[13] = NO; break; case SDLK_KP_MULTIPLY: keys[42] = NO; break; // * case SDLK_KP1: keys[gvNumberPadKey1] = NO; break; case SDLK_KP2: keys[gvNumberPadKey2] = NO; break; case SDLK_KP3: keys[gvNumberPadKey3] = NO; break; case SDLK_KP4: keys[gvNumberPadKey4] = NO; break; case SDLK_KP5: keys[gvNumberPadKey5] = NO; break; case SDLK_KP6: keys[gvNumberPadKey6] = NO; break; case SDLK_KP7: keys[gvNumberPadKey7] = NO; break; case SDLK_KP8: keys[gvNumberPadKey8] = NO; break; case SDLK_KP9: keys[gvNumberPadKey9] = NO; break; case SDLK_KP0: keys[gvNumberPadKey0] = NO; break; case SDLK_F1: keys[gvFunctionKey1] = NO; break; case SDLK_F2: keys[gvFunctionKey2] = NO; break; case SDLK_F3: keys[gvFunctionKey3] = NO; break; case SDLK_F4: keys[gvFunctionKey4] = NO; break; case SDLK_F5: keys[gvFunctionKey5] = NO; break; case SDLK_F6: keys[gvFunctionKey6] = NO; break; case SDLK_F7: keys[gvFunctionKey7] = NO; break; case SDLK_F8: keys[gvFunctionKey8] = NO; break; case SDLK_F9: keys[gvFunctionKey9] = NO; break; case SDLK_F10: keys[gvFunctionKey10] = NO; break; case SDLK_BACKSPACE: case SDLK_DELETE: keys[gvDeleteKey] = NO; break; case SDLK_LSHIFT: case SDLK_RSHIFT: shift = NO; break; case SDLK_LCTRL: case SDLK_RCTRL: ctrl = NO; break; case SDLK_ESCAPE: keys[27] = NO; break; default: // Numerous cases not handled. //OOLog(@"keys.test", @"Keyup scancode: %d", kbd_event->keysym.scancode); ; } break; case SDL_VIDEORESIZE: { SDL_ResizeEvent *rsevt=(SDL_ResizeEvent *)&event; NSSize newSize=NSMakeSize(rsevt->w, rsevt->h); #if OOLITE_WINDOWS if (!fullScreen && updateContext) { if (saveSize == NO) { // event triggered by caption & frame // next event will be a real resize. saveSize = YES; } else { [self initialiseGLWithSize: newSize]; [self saveWindowSize: newSize]; } } #else [self initialiseGLWithSize: newSize]; [self saveWindowSize: newSize]; #endif // certain gui screens will require an immediate redraw after // a resize event - Nikos 20140129 if ([PlayerEntity sharedPlayer]) { [[PlayerEntity sharedPlayer] doGuiScreenResizeUpdates]; } break; } #if OOLITE_WINDOWS // need to track this because the user may move the game window // to a secondary monitor, in which case we must potentially // refresh the information displayed (e.g. Game Options screen) // Nikos - 20140920 case SDL_SYSWMEVENT: { switch (event.syswm.msg->msg) { case WM_WINDOWPOSCHANGING: /* if we are in fullscreen mode we normally don't worry about having the window moved. However, when using multiple monitors, one can use hotkey combinations to make the window "jump" from one monitor to the next. We don't want this to happen, so if we detect that our (fullscreen) window has moved, we immediately bring it back to its original position. Nikos - 20140922 */ if (fullScreen) { RECT rDC; /* attempting to move our fullscreen window while in maximized state can freak Windows out and the window may not return to its original position properly. Solution: if such a move takes place, first change the window placement to normal, move it normally, then restore its placement to maximized again. Additionally, the last good known window position seems to be lost in such a case. While at it, update also the coordinates of the non-maximized window so that it can return to its original position - this is why we need lastGoodRect. */ WINDOWPLACEMENT wp; wp.length = sizeof(WINDOWPLACEMENT); GetWindowPlacement(SDL_Window, &wp); GetWindowRect(SDL_Window, &rDC); if (rDC.left != monitorInfo.rcMonitor.left || rDC.top != monitorInfo.rcMonitor.top) { BOOL fullScreenMaximized = NO; if (wp.showCmd == SW_SHOWMAXIMIZED && !fullScreenMaximized) { fullScreenMaximized = YES; wp.showCmd = SW_SHOWNORMAL; SetWindowPlacement(SDL_Window, &wp); } MoveWindow(SDL_Window, monitorInfo.rcMonitor.left, monitorInfo.rcMonitor.top, viewSize.width, viewSize.height, TRUE); if (fullScreenMaximized) { GetWindowPlacement(SDL_Window, &wp); wp.showCmd = SW_SHOWMAXIMIZED; CopyRect(&wp.rcNormalPosition, &lastGoodRect); SetWindowPlacement(SDL_Window, &wp); } } else if (wp.showCmd == SW_SHOWMAXIMIZED) { CopyRect(&wp.rcNormalPosition, &lastGoodRect); SetWindowPlacement(SDL_Window, &wp); } } // it is important that this gets done after we've dealt with possible fullscreen movements, // because -doGuiScreenResizeUpdates does itself an update on current monitor if ([PlayerEntity sharedPlayer]) { [[PlayerEntity sharedPlayer] doGuiScreenResizeUpdates]; } /* deliberately no break statement here - moving or resizing the window changes its bounds rectangle. Therefore we must check whether to clip the mouse or not inside the newly updated rectangle, so just let it fall through */ case WM_ACTIVATEAPP: if(grabMouseStatus) [self grabMouseInsideGameWindow:YES]; break; default: ; } break; } #endif // caused by INTR or someone hitting close case SDL_QUIT: { SDL_FreeSurface(surface); [gameController exitAppWithContext:@"SDL_QUIT event received"]; } } } // check if enough time has passed since last use of the mousewheel and act // if needed if (timeNow >= timeSinceLastMouseWheel + OOMOUSEWHEEL_EVENTS_DELAY_INTERVAL) { _mouseWheelState = gvMouseWheelNeutral; } } // DJS: String input handler. Since for SDL versions we're also handling // freeform typing this has necessarily got more complex than the non-SDL // versions. - (void) handleStringInput: (SDL_KeyboardEvent *) kbd_event; { SDLKey key=kbd_event->keysym.sym; // Del, Backspace if((key == SDLK_BACKSPACE || key == SDLK_DELETE) && [typedString length] > 0) { // delete [typedString deleteCharactersInRange:NSMakeRange([typedString length]-1, 1)]; } isAlphabetKeyDown=NO; // TODO: a more flexible mechanism for max. string length ? if([typedString length] < 40) { if (allowingStringInput == gvStringInputAlpha) { // inputAlpha - limited input for planet find screen if(key >= SDLK_a && key <= SDLK_z) { isAlphabetKeyDown=YES; [typedString appendFormat:@"%c", key]; // if in inputAlpha, keep in lower case. } } else { Uint16 unicode = kbd_event->keysym.unicode; // printable range if (unicode >= 32 && unicode <= 126) { if ((char)unicode != '/' || allowingStringInput == gvStringInputAll) { isAlphabetKeyDown=YES; [typedString appendFormat:@"%c", unicode]; } } } } } // Full screen mode enumerator. - (void) populateFullScreenModelist { int i; SDL_Rect **modes; NSMutableDictionary *mode; screenSizes=[[NSMutableArray alloc] init]; // The default resolution (slot 0) is the resolution we are // already in since this is guaranteed to work. mode=[MyOpenGLView getNativeSize]; [screenSizes addObject: mode]; modes=SDL_ListModes(NULL, SDL_FULLSCREEN|SDL_HWSURFACE); if(modes == (SDL_Rect **)NULL) { OOLog(@"display.mode.list.none", @"SDL didn't return any screen modes"); return; } if(modes == (SDL_Rect **)-1) { OOLog(@"display.mode.list.none", @"SDL claims 'all resolutions available' which is unhelpful in the extreme"); return; } int lastw=[[mode objectForKey: kOODisplayWidth] intValue]; int lasth=[[mode objectForKey: kOODisplayHeight] intValue]; for(i=0; modes[i]; i++) { // SDL_ListModes often lists a mode several times, // presumably because each mode has several refresh rates. // But the modes pointer is an SDL_Rect which can't represent // refresh rates. WHY!? if(modes[i]->w != lastw || modes[i]->h != lasth) { // new resolution, save it mode=[NSMutableDictionary dictionary]; [mode setValue: [NSNumber numberWithInt: (int)modes[i]->w] forKey: kOODisplayWidth]; [mode setValue: [NSNumber numberWithInt: (int)modes[i]->h] forKey: kOODisplayHeight]; [mode setValue: [NSNumber numberWithInt: 0] forKey: kOODisplayRefreshRate]; [screenSizes addObject: mode]; OOLog(@"display.mode.list", @"Added res %d x %d", modes[i]->w, modes[i]->h); lastw=modes[i]->w; lasth=modes[i]->h; } } } // Save and restore window sizes to/from defaults. - (void) saveWindowSize: (NSSize) windowSize { NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults]; [defaults setInteger: (int)windowSize.width forKey: @"window_width"]; [defaults setInteger: (int)windowSize.height forKey: @"window_height"]; currentWindowSize=windowSize; } - (NSSize) loadWindowSize { NSSize windowSize; NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults]; if([defaults objectForKey:@"window_width"] && [defaults objectForKey:@"window_height"]) { windowSize=NSMakeSize([defaults integerForKey: @"window_width"], [defaults integerForKey: @"window_height"]); } else { windowSize=NSMakeSize(800, 600); } currentWindowSize=windowSize; return windowSize; } - (int) loadFullscreenSettings { currentSize=0; int width=0, height=0, refresh=0; unsigned i; NSArray* cmdline_arguments = [[NSProcessInfo processInfo] arguments]; NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; if ([userDefaults objectForKey:@"display_width"]) width = [userDefaults integerForKey:@"display_width"]; if ([userDefaults objectForKey:@"display_height"]) height = [userDefaults integerForKey:@"display_height"]; if ([userDefaults objectForKey:@"display_refresh"]) refresh = [userDefaults integerForKey:@"display_refresh"]; if([userDefaults objectForKey:@"fullscreen"]) fullScreen=[userDefaults boolForKey:@"fullscreen"]; // Check if -fullscreen has been passed on the command line. If yes, set it regardless of // what is set by .GNUstepDefaults. for (i = 0; i < [cmdline_arguments count]; i++) { if ([[cmdline_arguments objectAtIndex:i] isEqual:@"-fullscreen"]) fullScreen = YES; } if(width && height) { currentSize=[self findDisplayModeForWidth: width Height: height Refresh: refresh]; return currentSize; } return currentSize; } - (int) findDisplayModeForWidth:(unsigned int) d_width Height:(unsigned int) d_height Refresh:(unsigned int) d_refresh { int i, modeCount; NSDictionary *mode; unsigned int modeWidth, modeHeight, modeRefresh; modeCount = [screenSizes count]; for (i = 0; i < modeCount; i++) { mode = [screenSizes objectAtIndex: i]; modeWidth = [[mode objectForKey: kOODisplayWidth] intValue]; modeHeight = [[mode objectForKey: kOODisplayHeight] intValue]; modeRefresh = [[mode objectForKey: kOODisplayRefreshRate] intValue]; if ((modeWidth == d_width)&&(modeHeight == d_height)&&(modeRefresh == d_refresh)) { OOLog(@"display.mode.found", @"Found mode %@", mode); return i; } } OOLog(@"display.mode.found.failed", @"Failed to find mode: width=%d height=%d refresh=%d", d_width, d_height, d_refresh); OOLog(@"display.mode.found.failed.list", @"Contents of list: %@", screenSizes); return 0; } - (NSSize) currentScreenSize { NSDictionary *mode=[screenSizes objectAtIndex: currentSize]; if(mode) { return NSMakeSize([[mode objectForKey: kOODisplayWidth] intValue], [[mode objectForKey: kOODisplayHeight] intValue]); } OOLog(@"display.mode.unknown", @"Screen size unknown!"); return NSMakeSize(800, 600); } - (void) setMouseInDeltaMode: (BOOL) inDelta { mouseInDeltaMode=inDelta; } - (void) setGammaValue: (float) value { if (value < 0.2f) value = 0.2f; if (value > 4.0f) value = 4.0f; _gamma = value; SDL_SetGamma(_gamma, _gamma, _gamma); [[NSUserDefaults standardUserDefaults] setFloat:_gamma forKey:@"gamma-value"]; } - (float) gammaValue { return _gamma; } - (void) setFov:(float)value fromFraction:(BOOL)fromFraction { _fov = fromFraction ? value : tan((value / 2) * M_PI / 180); } - (float) fov:(BOOL)inFraction { return inFraction ? _fov : 2 * atan(_fov) * 180 / M_PI; } - (OOOpenGLMatrixManager *) getOpenGLMatrixManager { return matrixManager; } + (BOOL)pollShiftKey { #if OOLITE_WINDOWS // SDL_GetModState() does not seem to do exactly what is intended under Windows. For this reason, // the GetKeyState Windows API call is used to detect the Shift keypress. -- Nikos. return 0 != (GetKeyState(VK_SHIFT) & 0x100); #else return 0 != (SDL_GetModState() & (KMOD_LSHIFT | KMOD_RSHIFT)); #endif } #ifndef NDEBUG - (void) dumpRGBAToFileNamed:(NSString *)name bytes:(uint8_t *)bytes width:(NSUInteger)width height:(NSUInteger)height rowBytes:(NSUInteger)rowBytes { if (name == nil || bytes == NULL || width == 0 || height == 0 || rowBytes < width * 4) return; // use the snapshots directory NSString *dumpFile = [[NSHomeDirectory() stringByAppendingPathComponent:@SAVEDIR] stringByAppendingPathComponent:@SNAPSHOTDIR]; dumpFile = [dumpFile stringByAppendingPathComponent: [NSString stringWithFormat:@"%@.bmp", name]]; // convert transparency to black before saving to bmp SDL_Surface* tmpSurface = SDL_CreateRGBSurfaceFrom(bytes, width, height, 32, rowBytes, 0xFF, 0xFF00, 0xFF0000, 0xFF000000); SDL_SaveBMP(tmpSurface, [dumpFile UTF8String]); SDL_FreeSurface(tmpSurface); } - (void) dumpRGBToFileNamed:(NSString *)name bytes:(uint8_t *)bytes width:(NSUInteger)width height:(NSUInteger)height rowBytes:(NSUInteger)rowBytes { if (name == nil || bytes == NULL || width == 0 || height == 0 || rowBytes < width * 3) return; // use the snapshots directory NSString *dumpFile = [[NSHomeDirectory() stringByAppendingPathComponent:@SAVEDIR] stringByAppendingPathComponent:@SNAPSHOTDIR]; dumpFile = [dumpFile stringByAppendingPathComponent: [NSString stringWithFormat:@"%@.bmp", name]]; SDL_Surface* tmpSurface = SDL_CreateRGBSurfaceFrom(bytes, width, height, 24, rowBytes, 0xFF, 0xFF00, 0xFF0000, 0x0); SDL_SaveBMP(tmpSurface, [dumpFile UTF8String]); SDL_FreeSurface(tmpSurface); } - (void) dumpGrayToFileNamed:(NSString *)name bytes:(uint8_t *)bytes width:(NSUInteger)width height:(NSUInteger)height rowBytes:(NSUInteger)rowBytes { if (name == nil || bytes == NULL || width == 0 || height == 0 || rowBytes < width) return; // use the snapshots directory NSString *dumpFile = [[NSHomeDirectory() stringByAppendingPathComponent:@SAVEDIR] stringByAppendingPathComponent:@SNAPSHOTDIR]; dumpFile = [dumpFile stringByAppendingPathComponent: [NSString stringWithFormat:@"%@.bmp", name]]; SDL_Surface* tmpSurface = SDL_CreateRGBSurfaceFrom(bytes, width, height, 8, rowBytes, 0xFF, 0xFF, 0xFF, 0x0); SDL_SaveBMP(tmpSurface, [dumpFile UTF8String]); SDL_FreeSurface(tmpSurface); } - (void) dumpGrayAlphaToFileNamed:(NSString *)name bytes:(uint8_t *)bytes width:(NSUInteger)width height:(NSUInteger)height rowBytes:(NSUInteger)rowBytes { if (name == nil || bytes == NULL || width == 0 || height == 0 || rowBytes < width * 2) return; // use the snapshots directory NSString *dumpFile = [[NSHomeDirectory() stringByAppendingPathComponent:@SAVEDIR] stringByAppendingPathComponent:@SNAPSHOTDIR]; dumpFile = [dumpFile stringByAppendingPathComponent: [NSString stringWithFormat:@"%@.bmp", name]]; SDL_Surface* tmpSurface = SDL_CreateRGBSurfaceFrom(bytes, width, height, 16, rowBytes, 0xFF, 0xFF, 0xFF, 0xFF); SDL_SaveBMP(tmpSurface, [dumpFile UTF8String]); SDL_FreeSurface(tmpSurface); } - (void) dumpRGBAToRGBFileNamed:(NSString *)rgbName andGrayFileNamed:(NSString *)grayName bytes:(uint8_t *)bytes width:(NSUInteger)width height:(NSUInteger)height rowBytes:(NSUInteger)rowBytes { if ((rgbName == nil && grayName == nil) || bytes == NULL || width == 0 || height == 0 || rowBytes < width * 4) return; uint8_t *rgbBytes, *rgbPx, *grayBytes, *grayPx, *srcPx; NSUInteger x, y; BOOL trivalAlpha = YES; rgbPx = rgbBytes = malloc(width * height * 3); if (rgbBytes == NULL) return; grayPx = grayBytes = malloc(width * height); if (grayBytes == NULL) { free(rgbBytes); return; } for (y = 0; y < height; y++) { srcPx = bytes + rowBytes * y; for (x = 0; x < width; x++) { *rgbPx++ = *srcPx++; *rgbPx++ = *srcPx++; *rgbPx++ = *srcPx++; trivalAlpha = trivalAlpha && ((*srcPx == 0xFF) || (*srcPx == 0x00)); // Look for any "interesting" pixels in alpha. *grayPx++ = *srcPx++; } } [self dumpRGBToFileNamed:rgbName bytes:rgbBytes width:width height:height rowBytes:width * 3]; free(rgbBytes); if (!trivalAlpha) { [self dumpGrayToFileNamed:grayName bytes:grayBytes width:width height:height rowBytes:width]; } free(grayBytes); } #endif @end oolite-1.82/src/SDL/OOSDLJoystickManager.h000066400000000000000000000032621256642440500202270ustar00rootroot00000000000000/* OOSDLJoystickManager.h By Dylan Smith JoystickHandler handles joystick events from SDL, and translates them into the appropriate action via a lookup table. The lookup table is stored as a simple array rather than an ObjC dictionary since this will be examined fairly often (once per frame during gameplay). Conversion methods are provided to convert between the internal representation and an NSDictionary (for loading/saving user defaults and for use in areas where portability/ease of coding are more important than performance such as the GUI) Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #import #import "OOJoystickManager.h" @interface OOSDLJoystickManager: OOJoystickManager { @private SDL_Joystick *stick[MAX_STICKS]; NSUInteger stickCount; } - (id) init; - (BOOL) handleSDLEvent: (SDL_Event *)evt; - (NSString *) nameOfJoystick:(NSUInteger)stickNumber; - (int16_t) getAxisWithStick:(NSUInteger) stickNum axis:(NSUInteger) axisNum ; @end oolite-1.82/src/SDL/OOSDLJoystickManager.m000066400000000000000000000051541256642440500202360ustar00rootroot00000000000000/* OOSDLJoystickManager.m By Dylan Smith Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import "OOSDLJoystickManager.h" #import "OOLogging.h" #define kOOLogUnconvertedNSLog @"unclassified.OOSDLJoystickManager" @implementation OOSDLJoystickManager - (id) init { int i; // Find and open the sticks. Make sure that we don't fail if more joysticks than MAX_STICKS are detected. stickCount = SDL_NumJoysticks(); OOLog(@"joystick.init", @"Number of joysticks detected: %ld", (long)stickCount); if (stickCount > MAX_STICKS) { stickCount = MAX_STICKS; OOLog(@"joystick.init", @"Number of joysticks detected exceeds maximum number of joysticks allowed. Setting number of active joysticks to %d.", MAX_STICKS); } if(stickCount) { for(i = 0; i < stickCount; i++) { // it's doubtful MAX_STICKS will ever get exceeded, but // we need to be defensive. if(i > MAX_STICKS) break; stick[i]=SDL_JoystickOpen(i); if(!stick[i]) { OOLog(@"joystick.init", @"Failed to open joystick #%d", i); } } SDL_JoystickEventState(SDL_ENABLE); } return [super init]; } - (BOOL) handleSDLEvent: (SDL_Event *)evt { BOOL rc=NO; switch(evt->type) { case SDL_JOYAXISMOTION: [self decodeAxisEvent: (JoyAxisEvent *)evt]; rc=YES; break; case SDL_JOYBUTTONDOWN: case SDL_JOYBUTTONUP: [self decodeButtonEvent: (JoyButtonEvent *)evt]; rc=YES; break; case SDL_JOYHATMOTION: [self decodeHatEvent: (JoyHatEvent *)evt]; rc=YES; break; default: OOLog(@"handleSDLEvent.unknownEvent", @"JoystickHandler was sent an event it doesn't know"); } return rc; } // Overrides - (NSUInteger) joystickCount { return stickCount; } - (NSString *) nameOfJoystick:(NSUInteger)stickNumber { return [NSString stringWithUTF8String:SDL_JoystickName((int)stickNumber)]; } - (int16_t) getAxisWithStick:(NSUInteger) stickNum axis:(NSUInteger) axisNum { return SDL_JoystickGetAxis(stick[stickNum], axisNum); } @end oolite-1.82/src/SDL/main.m000066400000000000000000000071451256642440500152710ustar00rootroot00000000000000/* main.m Oolite Copyright (C) 2004-2013 Giles C Williams and contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifdef GNUSTEP #import #import #import "GameController.h" #import "OOLoggingExtended.h" #if OOLITE_WINDOWS #include #include #endif GameController* controller; #endif #ifndef NDEBUG uint32_t gDebugFlags = 0; #endif #ifdef OOLITE_SDL_MAC #define main SDL_main #endif int main(int argc, char *argv[]) { #ifdef GNUSTEP int i; #if OOLITE_WINDOWS // Detect current working directory and set up GNUstep environment variables #define MAX_PATH_LEN 256 char currentWorkingDir[MAX_PATH_LEN]; char envVarString[2 * MAX_PATH_LEN]; GetCurrentDirectory(MAX_PATH_LEN - 1, currentWorkingDir); #define SETENVVAR(var, value) do {\ sprintf(envVarString, "%s=%s", (var), (value));\ SDL_putenv (envVarString);\ } while (0); SETENVVAR("GNUSTEP_PATH_HANDLING", "windows"); SETENVVAR("GNUSTEP_SYSTEM_ROOT", currentWorkingDir); SETENVVAR("GNUSTEP_LOCAL_ROOT", currentWorkingDir); SETENVVAR("GNUSTEP_NETWORK_ROOT", currentWorkingDir); SETENVVAR("GNUSTEP_USERS_ROOT", currentWorkingDir); SETENVVAR("HOMEPATH", currentWorkingDir); /* Windows amibtiously starts apps with the C library locale set to the system locale rather than the "C" locale as per spec. Fixing here so numbers don't behave strangely. */ setlocale(LC_ALL, "C"); #endif // Need this because we're not using the default run loop's autorelease // pool. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; OOLoggingInit(); NS_DURING // dajt: allocate and set the NSApplication delegate manually because not // using NIB to do this controller = [[GameController alloc] init]; // Release anything allocated during the controller initialisation that // is no longer required. for (i = 1; i < argc; i++) { // The commented out lines below do not seem to do anything, at least on Windows. // The -fullscreen argument processing has been implemented in the loadFullscreenSettings // method, inside src/SDL/MyOpenGLView.m. /* -------- Begin commented out section -------- if (strcmp("-fullscreen", argv[i]) == 0) [controller setFullScreenMode: YES]; --------- End commented out section -------- */ if (strcmp("-load", argv[i]) == 0) { i++; if (i < argc) [controller setPlayerFileToLoad: [NSString stringWithCString: argv[i]]]; } } DESTROY(pool); // Call applicationDidFinishLaunching because NSApp is not running in // GNUstep port. [controller applicationDidFinishLaunching: nil]; NS_HANDLER OOLogERR(kOOLogException, @"Root exception handler hit - terminating. This is an internal error, please report it. Exception name: %@, reason: %@", [localException name], [localException reason]); return EXIT_FAILURE; NS_ENDHANDLER #endif // never reached return 0; } #if OOLITE_SDL_MAC @implementation NSWindow (SDLBugWorkaround) - (void)release {} @end #endif oolite-1.82/tools/000077500000000000000000000000001256642440500141075ustar00rootroot00000000000000oolite-1.82/tools/BBC keys/000077500000000000000000000000001256642440500154315ustar00rootroot00000000000000oolite-1.82/tools/BBC keys/keyconfig.plist000066400000000000000000000031001256642440500204560ustar00rootroot00000000000000{ key_roll_left = ","; key_roll_right = "."; key_pitch_forward = "s"; key_pitch_back = "x"; key_yaw_left = 253; // left arrow key_yaw_right = 252; // right arrow key_increase_speed = " "; key_decrease_speed = "/"; key_inject_fuel = "i"; key_fire_lasers = "a"; key_weapons_online_toggle = "_"; key_launch_missile = "m"; key_next_missile = "y"; key_ecm = "e"; key_prime_equipment = "N"; key_activate_equipment = "n"; key_mode_equipment = "b"; key_target_incoming_missile = "T"; key_target_missile = "t"; key_untarget_missile = "u"; key_ident_system = "r"; key_scanner_zoom = "z"; key_scanner_unzoom = "Z"; key_launch_escapepod = 27; // escape key_energy_bomb = "\t"; // tab key_galactic_hyperspace = "g"; key_hyperspace = "h"; key_jumpdrive = "j"; key_dump_cargo = "d"; key_rotate_cargo = "R"; key_autopilot = "c"; key_autodock = "C"; key_docking_clearance_request = "L"; key_snapshot = "*"; key_docking_music = "s"; key_advanced_nav_array = "^"; key_map_home = 302; // Home key_map_info = "i"; key_pausebutton = "p"; key_show_fps = "F"; key_mouse_control = "M"; key_hud_toggle = "o"; key_comms_log = "`"; key_prev_compass_mode = "|"; key_next_compass_mode = "\\"; key_cloaking_device = "0"; key_contract_info = "?"; key_next_target = "+"; key_previous_target = "-"; key_custom_view = "v"; key_dump_target_state = "H"; } oolite-1.82/tools/JSErrorMunger/000077500000000000000000000000001256642440500166135ustar00rootroot00000000000000oolite-1.82/tools/JSErrorMunger/JSErrorMunger.m000066400000000000000000000057761256642440500215140ustar00rootroot00000000000000/* Tool to convert SpiderMonkey error code table (in js.msg) into a plist for our error reporter. Requires some pre-massaging of js.msg to turn it into an ASCII plist array of arrays. */ #import void Handle1Spec(NSArray *spec); NSString *ConvertName(NSString *name); int main (int argc, const char * argv[]) { [NSAutoreleasePool new]; if (argc < 2) { fprintf(stderr, "Specify path to js.msg.\n"); return EXIT_FAILURE; } NSString *jsmsg = [NSString stringWithContentsOfFile:[NSString stringWithUTF8String:argv[1]]]; if (jsmsg == nil) { fprintf(stderr, "Can't find %s.\n", argv[1]); return EXIT_FAILURE; } // Hit it with a hammer until it looks like an OpenStep plist array of arrays NSArray *components = [jsmsg componentsSeparatedByString:@")\n"]; jsmsg = [components componentsJoinedByString:@"),\n"]; jsmsg = [[@"(\n" stringByAppendingString:jsmsg] stringByAppendingString:@"())\n"]; jsmsg = [[jsmsg componentsSeparatedByString:@"MSG_DEF"] componentsJoinedByString:@""]; NSString *errorString = nil; NSArray *errors = [NSPropertyListSerialization propertyListFromData:[jsmsg dataUsingEncoding:NSASCIIStringEncoding] mutabilityOption:NSPropertyListImmutable format:NULL errorDescription:&errorString]; if (errors == nil) { fprintf(stderr, "Parse error: %s\n\n--\n%s\n", [errorString UTF8String], [jsmsg UTF8String]); return EXIT_FAILURE; } printf("// Identifiers for JavaScript error and warning codes\n// Automatically generated by JSErrorMunger.m\n\n{\n"); for (NSArray *spec in errors) { Handle1Spec(spec); } printf("}\n"); return EXIT_SUCCESS; } void Handle1Spec(NSArray *spec) { if (spec.count < 2) return; NSString *name = [spec objectAtIndex:0]; NSNumber *index = [spec objectAtIndex:1]; name = ConvertName(name); printf("\t\"%u\" = \"%s\";\n", [index intValue], [name UTF8String]); } NSString *ConvertName(NSString *name) { if ([name isEqualToString:@"JSMSG_USER_DEFINED_ERROR"]) { // Don't want "Error" here because it's also used for warnings. return @"ooliteDefined"; } NSArray *components = [[name substringFromIndex:6] componentsSeparatedByString:@"_"]; NSMutableString *result = [NSMutableString string]; NSDictionary *specialCases = [NSDictionary dictionaryWithObjectsAndKeys: @"XML", @"XML", @"XMLList", @"XMLLIST", @"XDR", @"XDR", @"RHS", @"RHS", @"LHS", @"LHS", @"ID", @"ID", @"URI", @"URI", @"URL", @"URL", @"newRegExp", @"NEWREGEXP", @"NCR", @"NCR", @"UTF8", @"UTF8", @"NS", @"NS", @"oolite", @"USER", @"Assignment", @"ASS", nil]; for (NSString *comp in components) { NSString *converted = [specialCases objectForKey:comp]; if (converted == nil) { if (result.length == 0) converted = [comp lowercaseString]; else converted = [comp capitalizedString]; } [result appendString:converted]; } return result; } oolite-1.82/tools/UpdateBerlios.pm000066400000000000000000000147211256642440500172140ustar00rootroot00000000000000package UpdateBerlios; use LWP::UserAgent; use HTTP::Request; use HTTP::Request::Form; use HTTP::Cookies; use HTML::TreeBuilder; use Data::Dumper; use strict; # Configuration. my $LOGINACTION="https://developer.berlios.de/account/login.php"; my $EDITRELEASE="https://developer.berlios.de/project/admin/editreleases.php"; my %TYPE= ('deb' => '1000', 'rpm' => '2000', 'zip' => '3000', 'bz2' => '3001', 'gz' => '3002', 'exe' => '4000', 'srczip' => '5000', 'srcbz2' => '5001', 'srcgz' => '5002', 'srcrpm' => '5100', 'srcother' => '5900', 'jpg' => '8000', 'txt' => '8001', 'html' => '8002', 'pdf' => '8003', 'other' => '9999'); my %ARCH= ('x86' => '1000', 'ia64' => '6000', 'alpha' => '7000', 'any' => '8000', 'ppc' => '2000', 'mips' => '3000', 'sparc' => '4000', 'sparc64' => '5000', 'other' => '9999', 'x86_64' => '9000'); sub new { my ($class)=@_; my $self= { projurl => undef }; bless $self, $class; return $self; } sub connect { my ($self, $user, $passwd)=@_; chomp $user; chomp $passwd; $self->{ua}=new LWP::UserAgent; $self->{ua}->agent("Oolite-Updater/1.0"); $self->{ua}->cookie_jar( {} ); my $forms=$self->getForms($LOGINACTION); my $login=new HTTP::Request::Form($forms->[2], $LOGINACTION); $login->field('form_loginname', $user); $login->field('form_pw', $passwd); $login->field('stay_in_ssl', 1); my $res=$self->{ua}->request($login->press('login')); # test for session cookie to see if login worked if($self->{ua}->cookie_jar->as_string =~ /session_hash/) { print("Logged into BerliOS as $user\n"); return 1; } print("Login failed"); exit(255); } sub deleteFiles { my ($self, $url)=@_; # The general idea here is to keep hitting Delete until there # are no more delete forms left (all files are gone). while(1) { my $forms=$self->getForms($url); # iterate through the list of forms to find one that lets # us delete. my $candidate=undef; my $delfound=0; foreach my $form (@$forms) { $candidate=new HTTP::Request::Form ($form, $EDITRELEASE); if($candidate->field("step3") eq "Delete File") { $delfound=1; last; } } if(!$delfound) { # Nothing more last; } # the html doesn't seem to parse properly so for deleting we have # to do this by hand!! my $reqstr="group_id=" . $candidate->field('group_id'); $reqstr.="&release_id=" . $candidate->field('release_id'); $reqstr.="&package_id=" . $candidate->field('package_id'); $reqstr.="&file_id=" . $candidate->field('file_id'); $reqstr.="&im_sure=1"; $reqstr.="&step3=Delete File"; my $req=HTTP::Request->new(POST => $url); $req->content_type('application/x-www-form-urlencoded'); $req->content($reqstr); my $res=$self->{ua}->request($req); if($res->is_success) { print("Deleted file id " . $candidate->field('file_id') . "\n"); } else { die("Delete failed!"); } } } # call this as thing->addFiles("http://...", file1, file2, ...); sub addFiles { my $self=shift; my $url=shift; my $forms=$self->getForms($url); my $fileform=new HTTP::Request::Form($forms->[2], $EDITRELEASE); if($fileform->field('step2') ne "1") { die("Unexpected value for step2: " . $fileform->field('step2')); } # the form module can't parse this form (probably because of the # repeated field names) so we do it by hand. my @params; while(my $filename=shift()) { push @params, "file_list[]=$filename"; } my $reqstr=join("&", @params); $reqstr.="&group_id=" . $fileform->field('group_id'); $reqstr.="&package_id=" . $fileform->field('package_id'); $reqstr.="&release_id=" . $fileform->field('release_id'); $reqstr.="&step2=1"; my $req=HTTP::Request->new(POST => $url); $req->content_type('application/x-www-form-urlencoded'); $req->content($reqstr); my $res=$self->{ua}->request($req); if($res->is_success) { if(! grep /File(s) Added/, $res->content) { return undef; } } else { die("Request failed"); } return ($fileform->field('group_id'), $fileform->field('package_id'), $fileform->field('release_id')); } sub setFileArchitectures { my ($self, $url, $arch, $type)=@_; # make sure arch/type can be converted my $arch=$ARCH{$arch}; my $type=$TYPE{$type}; if(!defined($arch) || !defined($type)) { print("Arch/type not found (arch was '$arch', type was '$type')\n"); exit; } my $forms=$self->getForms($url); my $candidate=undef; my %fileIdList; # all we're doing is getting a list of all file_ids and changing # them en-masse. foreach my $form (@$forms) { $candidate=new HTTP::Request::Form ($form, $EDITRELEASE); if(length($candidate->field('file_id')) && $candidate->field('step3') eq "1") { $fileIdList{$candidate->field('file_id')}= "group_id=" . $candidate->field('group_id') . "&release_id=" . $candidate->field('release_id') . "&package_id=" . $candidate->field('package_id') . "&file_id=" . $candidate->field('file_id') . "&step3=1&processor_id=$arch&type_id=$type" . "&new_release_id=" . $candidate->field('release_id') . "&release_time=" . `date +"%Y-%m-%d"`; } } foreach my $fileId (keys %fileIdList) { print("Updating file_id $fileId...\n"); my $req=HTTP::Request->new(POST => $url); $req->content_type('application/x-www-form-urlencoded'); $req->content($fileIdList{$fileId}); my $res=$self->{ua}->request($req); if($res->is_success) { if(! grep /File Updated/, $res->content) { print("Warning: possibly did not update file_id $fileId\n"); } } else { die("Request failed"); } } } sub getForms { my ($self, $url)=@_; my $req=new HTTP::Request(GET => $url); my $res=$self->{ua}->request($req); my $tree=new HTML::TreeBuilder; $tree->parse($res->content); $tree->eof; # enumerate forms my @forms=$tree->find_by_tag_name('FORM') or die("No forms found at $url"); return \@forms; } oolite-1.82/tools/distancemap/000077500000000000000000000000001256642440500163775ustar00rootroot00000000000000oolite-1.82/tools/distancemap/GrayMap.c000066400000000000000000000133731256642440500201120ustar00rootroot00000000000000/* GrayMap.c Simple library for dealing with 8-bit greyscale images. This file is hereby placed in the public domain. */ #import #import #include "GrayMap.h" #import "png.h" static png_bytepp MakeGrayMapRowPointers(GrayMap *grayMap); // Free with free() GrayMap *ReadGrayMap(const char *path) { FILE *file = NULL; GrayMap *result = NULL; file = fopen(path, "rb"); if (file == NULL) { fprintf(stderr, "Could not open %s.\n", path); return NULL; } result = ReadGrayMapFile(file, path); fclose(file); return result; } GrayMap *NewGrayMap(uint32_t width, uint32_t height, uint32_t rowBytes) { GrayMap *result = NULL; if (rowBytes == 0) rowBytes = width; else assert(rowBytes >= width); result = malloc(sizeof *result); if (result != NULL) { result->pixels = malloc(rowBytes * height); if (result->pixels == NULL) { free(result); result = NULL; } else { result->width = width; result->height = height; result->rowBytes = rowBytes; } } return result; } void DisposeGrayMap(GrayMap *grayMap) { if (grayMap != NULL) { free(grayMap->pixels); free(grayMap); } } static png_bytepp MakeGrayMapRowPointers(GrayMap *grayMap) { if (grayMap == NULL) return NULL; png_bytepp rows = malloc(sizeof (png_bytep) * grayMap->rowBytes); if (rows != NULL) { for (uint32_t i = 0; i != grayMap->height; ++i) { rows[i] = ((png_bytep)grayMap->pixels) + i * grayMap->rowBytes; } } return rows; } GrayMap *ReadGrayMapFile(FILE *file, const char *name) { GrayMap *result = NULL; struct png_struct_def *png = NULL; struct png_info_struct *pngInfo = NULL; struct png_info_struct *pngEndInfo = NULL; png_bytepp rows = NULL; png_uint_32 pngWidth, pngHeight; int depth, colorType; if (file == NULL) return NULL; // Set up PNG decoding. png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (png == NULL) { fprintf(stderr, "Error preparing to read %s.\n", name); return NULL; } pngInfo = png_create_info_struct(png); if (pngInfo == NULL) { fprintf(stderr, "Error preparing to read %s.\n", name); return NULL; } pngEndInfo = png_create_info_struct(png); if (pngInfo == NULL) { fprintf(stderr, "Error preparing to read %s.\n", name); return NULL; } if (setjmp(png_jmpbuf(png))) { // libpng will jump here on error. free(rows); return NULL; } png_init_io(png, file); png_read_info(png, pngInfo); if (!png_get_IHDR(png, pngInfo, &pngWidth, &pngHeight, &depth, &colorType, NULL, NULL, NULL)) { fprintf(stderr, "Failed to get metadata from PNG %s", name); return NULL; } // Set to transform to 8-bit greyscale without alpha. if (colorType & PNG_COLOR_MASK_ALPHA) { png_set_strip_alpha(png); } if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) { png_set_strip_16(png); png_set_expand(png); } else if (colorType == PNG_COLOR_TYPE_PALETTE || colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_RGB_ALPHA) { png_set_rgb_to_gray(png, 3, -1, -1); } else { fprintf(stderr, "Unknown colour type (0x%.X) in %s.\n", colorType, name); return NULL; } png_read_update_info(png, pngInfo); result = NewGrayMap(pngWidth, pngHeight, png_get_rowbytes(png, pngInfo)); if (result == NULL) { fprintf(stderr, "Could not allocate memory for source image.\n"); return NULL; } // Create array of row pointers. rows = MakeGrayMapRowPointers(result); if (rows == NULL) { fprintf(stderr, "Could not allocate memory for source image.\n"); DisposeGrayMap(result); return NULL; } // Read. png_read_image(png, rows); png_read_end(png, pngEndInfo); free(rows); png_destroy_read_struct(&png, &pngInfo, &pngEndInfo); return result; } bool WriteGrayMap(const char *path, GrayMap *grayMap) { FILE *file = NULL; bool result = NULL; file = fopen(path, "wb"); if (file == NULL) { fprintf(stderr, "Could not open %s.\n", path); return false; } result = WriteGrayMapFile(file, path, grayMap); fclose(file); return result; } bool WriteGrayMapFile(FILE *file, const char *name, GrayMap *grayMap) { struct png_struct_def *png = NULL; struct png_info_struct *pngInfo = NULL; png_bytepp rows = NULL; if (grayMap == NULL || file == NULL) return false; // Set up PNG encoding. png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (png == NULL) { fprintf(stderr, "Error preparing to write %s.\n", name); return false; } pngInfo = png_create_info_struct(png); if (pngInfo == NULL) { fprintf(stderr, "Error preparing to write %s.\n", name); return false; } if (setjmp(png_jmpbuf(png))) { // libpng will jump here on error. free(rows); return false; } // Create array of row pointers. rows = MakeGrayMapRowPointers(grayMap); if (rows == NULL) { fprintf(stderr, "Could not allocate memory to write image.\n"); return NULL; } // Write. png_init_io(png, file); png_set_IHDR(png, pngInfo, grayMap->width, grayMap->height, 8, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); png_write_info(png, pngInfo); png_write_image(png, rows); png_write_end(png, NULL); free(rows); return true; } uint8_t GrayMapGet(GrayMap *grayMap, int32_t x, int32_t y) { // Out-of-range values or NULL grayMap result in 0. if (grayMap == NULL || x < 0 || y < 0 || grayMap->width <= x || grayMap->height <= y) return 0; return grayMap->pixels[y * grayMap->rowBytes + x]; } void GrayMapSet(GrayMap *grayMap, int32_t x, int32_t y, uint8_t value) { // Out-if-range values or NULL grayMap are ignored. if (grayMap == NULL || x < 0 || y < 0 || grayMap->width <= x || grayMap->height <= y) return; grayMap->pixels[y * grayMap->rowBytes + x] = value; } oolite-1.82/tools/distancemap/GrayMap.h000066400000000000000000000014421256642440500201110ustar00rootroot00000000000000/* GrayMap.h Simple library for dealing with 8-bit greyscale images. This file is hereby placed in the public domain. */ #import #import #import typedef struct { uint8_t *pixels; uint32_t width; uint32_t height; uint32_t rowBytes; } GrayMap; GrayMap *NewGrayMap(uint32_t width, uint32_t height, uint32_t rowBytes); void DisposeGrayMap(GrayMap *grayMap); GrayMap *ReadGrayMap(const char *path); GrayMap *ReadGrayMapFile(FILE *file, const char *name); bool WriteGrayMap(const char *path, GrayMap *grayMap); bool WriteGrayMapFile(FILE *file, const char *name, GrayMap *grayMap); uint8_t GrayMapGet(GrayMap *grayMap, int32_t x, int32_t y); // Out-of-bounds values return 0. void GrayMapSet(GrayMap *grayMap, int32_t x, int32_t y, uint8_t value); oolite-1.82/tools/distancemap/Q.png000066400000000000000000000045451256642440500173150ustar00rootroot00000000000000PNG  IHDRWf. ,IDATxMFQxxKobi,K`Hu7].O_{ΰ|3Rq{_<e} }8%܄]`..]y Yf?%\uV{<p݂@{(/ xeQ(plBF\,F\."hWn%S+%Sk%Rյw-ԏ&*,j]{Z[/AVٺWa3կ K6x`W.ţx"/ti6K e)q;6薮Rڠ.Ʃ ge\0/Zqi|*'MQRgգ%:>\[ЇZe>k/9ò]Uj ( X_,^dIFɃ oq;uE^AmyVAx|:prxlnDI6j6$*EDfg*Ea˓6 jPU) 2 !dZe6]ٰEP_YF!cR2MUղJS}w,6 :d)dYl:lZF7 iF-at(PemT4GE idpF0Y5iN#p8N#p8,>\DpV4Is-05͕5i5H4HIc"_h}]dK.ڛ;{,;+\Ma ' )KG Xkؕ&v[:' \(7(@yQLv t=<5j6 n@9C pE`:R"7' BHhHgx J%> \?/@   @?KA }oF $p9toh Ion#@]O|' @S~>R`tYxJ,inv!LU~!R@ox|w?K`GRa?K`Ͼ\%,OP"IR.phW/p 7_^"=bF+ ܣ[Vs}G"O9#\#\;@W*؞G \v &}x[ЧT]V>|,ޞT /W⣦+m^CN-]8Bh`҇ b?m0.TC, ^mYG+>M0i_u> v~{&뉳 ]]beji>m;%YOn ףrTFqtˁz?8$ \pgq/5bAM=XpH.l] 8pؙ{ވoL'H݈/e{= kU:4-a8dgwꏙxhw& w14qo[ h=]cxE߈5[fݽSx#FltE /ktE.1nEp."裋4Ӹ.݈Mx#iE]}t<S=91:j #import #import #import #import #import "GrayMap.h" enum { kDownscale = 16, kHalfDownscale = kDownscale / 2, kThreshold = 128 }; static void PerformDistanceMapping(GrayMap *source, GrayMap *dmap, GrayMap *amap); static void DistanceMapOnePixel(GrayMap *source, GrayMap *dmap, GrayMap *amap, uint32_t x, uint32_t y); static bool wrap = false; static inline uint32_t RoundSize(uint32_t size) { return (size + kDownscale - 1) / kDownscale; } int main (int argc, char * argv[]) { GrayMap *source = NULL; GrayMap *dmap = NULL; GrayMap *amap = NULL; bool printUsage = false; bool angleMap = false; // Get options for (;;) { int option = getopt(argc, argv, "wa"); if (option == -1) break; switch (option) { case 'w': wrap = true; break; case 'a': angleMap = true; break; default: printUsage = true; } } if (argc <= optind) printUsage = true; if (printUsage) { fprintf(stderr, "Usage: %s [-w] \n", argv[0]); return EXIT_FAILURE; } source = ReadGrayMap(argv[optind]); if (source == NULL) return EXIT_FAILURE; dmap = NewGrayMap(RoundSize(source->width), RoundSize(source->height), 0); if (angleMap) amap = NewGrayMap(RoundSize(source->width), RoundSize(source->height), 0); if (dmap == NULL || (angleMap && amap == NULL)) { fprintf(stderr, "Could not allocate memory for output image.\n"); return EXIT_FAILURE; } PerformDistanceMapping(source, dmap, amap); WriteGrayMap("distance_map.png", dmap); if (angleMap) WriteGrayMap("angle_map.png", amap); return 0; } static void PerformDistanceMapping(GrayMap *source, GrayMap *dmap, GrayMap *amap) { uint32_t width, height, x, y; assert(source && dmap); width = dmap->width; height = dmap->height; for (y = 0; y != height; ++y) { for (x = 0; x != width; ++x) { DistanceMapOnePixel(source, dmap, amap, x, y); } putchar('.'); fflush(stdout); } } static bool ReadPx(GrayMap *source, uint32_t x, uint32_t y, int16_t dx, int16_t dy); static inline int32_t Max(int32_t a, int32_t b) { return (a > b) ? a : b; } static inline int32_t Min(int32_t a, int32_t b) { return (a < b) ? a : b; } static inline int32_t Abs(int32_t a) { return (a >= 0) ? a : -a; } static void DistanceMapOnePixel(GrayMap *source, GrayMap *dmap, GrayMap *amap, uint32_t x, uint32_t y) { int32_t dx, dy; bool target; uint32_t distanceSq, bestDistanceSq = UINT32_MAX; uint32_t bestDistance; float bestAngle; uint32_t currDistance, maxDistance = Max(source->width, source->height); int8_t ddx = 1, ddy = 0, ddt; uint8_t countdown = 3; uint32_t i, length = 2; int32_t bestDx = 1, bestDy = 1; dx = 0; dy = -1; if (amap == NULL) maxDistance = Min(maxDistance, 128); target = !ReadPx(source, x, y, 0, 0); for (;;) { // Spiral outwards. do { for (i = 0; i < length; i++) { if (ReadPx(source, x, y, dx, dy) == target) { distanceSq = dx * dx + dy * dy; if (distanceSq < bestDistanceSq) { bestDistanceSq = distanceSq; bestDx = dx; bestDy = dy; } } dx += ddx; dy += ddy; } // Turn a corner. ddt = ddx; ddx = -ddy; ddy = ddt; } while (--countdown); currDistance = Max(Abs(dx), Abs(dy)); if ((currDistance * currDistance) > bestDistanceSq || currDistance > maxDistance) break; countdown = 2; length++; } bestDistance = sqrt(bestDistanceSq); if (target) { if (bestDistance > 128) bestDistance = 0; else bestDistance = 128 - bestDistance; bestDx = -bestDx; bestDy = -bestDy; } else { bestDistance = 127 + bestDistance; if (bestDistance > 255) bestDistance = 255; } GrayMapSet(dmap, x, y, bestDistance); if (amap != NULL) { bestAngle = atan2(bestDx, bestDy); bestAngle = (bestAngle + M_PI) * 127.5 / M_PI; // Convert from +/-pi to 0..255 GrayMapSet(amap, x, y, bestAngle); } } static bool ReadPx(GrayMap *source, uint32_t x, uint32_t y, int16_t dx, int16_t dy) { int32_t ax = x * kDownscale + dx - kHalfDownscale; int32_t ay = y * kDownscale + dy - kHalfDownscale; if (wrap) { ax = ax % source->width; if (ax < 0) ax += source->width; ay = ay % source->height; if (ay < 0) ay += source->height; } return GrayMapGet(source, ax, ay) >= kThreshold; } oolite-1.82/tools/distancemap/distancemap_Prefix.pch000066400000000000000000000002441256642440500227000ustar00rootroot00000000000000// // Prefix header for all source files of the 'distancemap' target in the 'distancemap' project. // #ifdef __OBJC__ #import #endif oolite-1.82/tools/distancemap/dmap-shader/000077500000000000000000000000001256642440500205645ustar00rootroot00000000000000oolite-1.82/tools/distancemap/dmap-shader/Distance map.sbproj000066400000000000000000004400001256642440500242730ustar00rootroot00000000000000SQLite format 3@ "$#  4x4{9E!ITeapotWirebuiltin://localhost/TeapotWire;?Torusbuiltin://localhost/Torus<?Planebuiltin://localhost/PlaneAESquigglebuiltin://localhost/Squiggle=ASpherebuiltin://localhost/SphereG#KTeapotPointbuiltin://localhost/TeapotPoint=ATeapotbuiltin://localhost/Teapot   'default state  v  file://localhost/Users/jayton/Programming/Projects/Oolite-proj/svn-repo/trunk/tools/distancemap/build/Release/distance_map.pngfile://localhost/Users/jayton/Programming/Projects/Oolite-proj/svn-repo/trunk/tools/distancemap/build/Release/angle_map.png    1GL_FRAGMENT_SHADER-GL_VERTEX_SHADER   %Untitled - 1  gM3 ) kSBTextureView ' kSBRenderView ' kSBSymbolView)kSBProgramView) kSBTextureView' kSBRenderView)kSBProgramView' kSBSymbolView' kSBRenderView)kSBProgramView) kSBTextureView           5 5 Cs 0uniform sampler2D tex; const float kThreshold = 0.5; const float kInnerThreshold = kThreshold + 0.02; const float kOuterThreshold = kThreshold - 0.2; const float kFallbackAAFactor = 0.03; const vec4 kBlack = vec4(0.0, 0.0, 0.0, 1.0); const vec4 kWhite = vec4(1.0); const vec4 kRed = vec4(1.0, 0.0, 0.0, 1.0); const vec4 kBlue = vec4(0.0, 0.0, 1.0, 1.0); float AntiAliasFactor() { return length(fwidth(gl_TexCoord[0].xy)) * 2.0; } float DistanceMap(sampler2D texture, vec2 texCoords, float threshold) { float dmap = texture2D(tex, texCoords).r; // Fake anti-aliasing with a hermite blur. // The fwidth() term lets us scale this appropriately for the screen. vec2 fw = fwidth(texCoords); float aaFactor = (fw.x + fw.y) * 5.0; // If fwidth() doesn't provide useful data, use a fixed blur instead. // Setting kFallbackAAFactor to zero gives you aliased output in the fallback case. aaFactor = (aaFactor == 0.0) ? kFallbackAAFactor : aaFactor; return smoothstep(threshold - aaFactor, threshold + aaFactor, dmap); } void main() { #if 1 float inner = DistanceMap(tex, gl_TexCoord[0].xy, kInnerThreshold); float outer = DistanceMap(tex, gl_TexCoord[0].xy, kOuterThreshold); vec4 decalColor = mix(kBlack, kRed, inner); decalColor = mix(kWhite, decalColor, outer); #else float mask = DistanceMap(tex, gl_TexCoord[0].xy, kThreshold); vec4 decalColor = mix(kWhite, kBlack, mask); #endif gl_FragColor = decalColor; } dmap.fsfile://localhost/Users/jayton/Programming/Projects/Oolite-proj/svn-repo/trunk/tools/distancemap/dmap-shader/dmap.fsEqs 0uniform sampler2D tex; const float kThreshold = 0.5; const float kInnerThreshold = kThreshold + 0.01; const float kOuterThreshold = kThreshold - 0.1; const float kFallbackAAFactor = 0.03; const float kAABlurFactor = 1.5; const vec4 kBlack = vec4(0.0, 0.0, 0.0, 1.0); const vec4 kWhite = vec4(1.0); const vec4 kRed = vec4(1.0, 0.0, 0.0, 1.0); const vec4 kBlue = vec4(0.0, 0.0, 1.0, 1.0); float AntiAliasFactor() { return length(fwidth(gl_TexCoord[0].xy)) * 2.0; } float DistanceMap(sampler2D texture, vec2 texCoords, float threshold) { float dmap = texture2D(tex, texCoords).r; // Fake anti-aliasing with a hermite blur. // The fwidth() term lets us scale this appropriately for the screen. vec2 fw = fwidth(texCoords); float aaFactor = (fw.x + fw.y) * kAABlurFactor; // If fwidth() doesn't provide useful data, use a fixed blur instead. // Setting kFallbackAAFactor to zero gives you aliased output in the fallback case. aaFactor = (aaFactor == 0.0) ? kFallbackAAFactor : aaFactor; return smoothstep(threshold - aaFactor, threshold + aaFactor, dmap); } void main() { float inner = DistanceMap(tex, gl_TexCoord[0].xy, kInnerThreshold); float outer = DistanceMap(tex, gl_TexCoord[0].xy, kOuterThreshold); vec4 color = mix(kBlack, kRed, inner); color = mix(kWhite, color, outer); gl_FragColor = color; } dmap.fsfile://localhost/Users/jayton/Programming/Projects/Oolite-proj/svn-repo/trunk/tools/distancemap/dmap-shader/dmap.fsgs 1void main() { gl_FrontColor = gl_Color; gl_TexCoord[0] = gl_MultiTexCoord0; gl_Position = ftransform(); } dmap.vsfile://localhost/Users/jayton/Programming/Projects/Oolite-proj/svn-repo/trunk/tools/distancemap/dmap-shader/dmap.vs  5 ?zG{?əfffhə  ?zG{   *+default program     $')& ))@@$')& ))@@  & ) deltaThreshold  ^tex  mV?'UniformTexture Target +SymbolComponent Source +SBTextureModule )SBSymbolModule )SBRenderModule+SBProgramModuleSBModule Project'PipelineStage%ParameterARB ImageGLStateGeometry  RU^D83DF01A-96FE-401D-876A-09B63F7C6E37bplist00 _NSStoreModelVersionIdentifiers[NSStoreType_NSPersistenceFrameworkVersion_ NSStoreModelVersionHashesVersion_NSStoreModelVersionHashesVSQLite  !"#$%&'()*^SBSymbolModuleVSource^SBRenderModuleWUniformWProjectWGLStateVTargetUImage_SymbolComponentWTexture\ParameterARB]PipelineStage_SBProgramModuleXGeometryXSBModule_SBTextureModuleO dLۮS,/ .=k~O xfns4e3 7sEEODx O _.DG1э0 _XI>ɕO ?JZhj0O yݚO Y.!GVC0:O 3+ ŦYuPxdFmDbQO - 1E/ƶΓ`"2O oMd*'%SMKR@FJB`O E9wԀSlF\fIs^+M yO 5,.~IWQc4<&@O /%+ oXtx{Eb}jCO h \iDZhd7`M‚5;>=O F2A ##OTaɊ2`{O lB :-lF/K&Z{]O 'X͸֖ff5>FK )_'4@` *2?M_hq2Ux'Jm+     =F gtableZGEOMETRYZGEOMETRYCREATE TABLE ZGEOMETRY ( Z_ENT INTEGER, Z_PK INTEGER PRIMARY KEY, Z_OPT INTEGER, ZMULTITEXACTIVEFLAGS INTEGER, ZTYPE INTEGER, ZRENDERINSCENE INTEGER, ZRENDERTARGET INTEGER, ZPROJECT INTEGER, ZCOLORB FLOAT, ZCOLORR FLOAT, ZCOLORG FLOAT, ZRENDERSELECTOR VARCHAR, ZSETUPRENDERSELECTOR VARCHAR, ZNAME VARCHAR, ZENDRENDERSELECTOR VARCHAR, ZURL VARCHAR, ZMODELDATA BLOB )zGindexZGEOMETRY_ZRENDERTARGET_INDEXZGEOMETRYCREATE INDEX ZGEOMETRY_ZRENDERTARGET_INDEX ON ZGEOMETRY (ZRENDERTARGET)k=indexZGEOMETRY_ZPROJECT_INDEXZGEOMETRYCREATE INDEX ZGEOMETRY_ZPROJECT_INDEX ON ZGEOMETRY (ZPROJECT) tableZGLSTATEZGLSTATECREATE TABLE ZGLSTATE ( Z_ENT INTEGER, Z_PK INTEGER PRIMARY KEY, Z_OPT INTEGER, ZTYPE INTEGER, ZPROJECT INTEGER, ZNAME VARCHAR )g;indexZGLSTATE_ZPROJECT_INDEXZGLSTATECREATE INDEX ZGLSTATE_ZPROJECT_INDEX ON ZGLSTATE (ZPROJECT) tableZIMAGEZIMAGECREATE TABLE ZIMAGE ( Z_ENT INTEGER, Z_PK INTEGER PRIMARY KEY, Z_OPT INTEGER, ZNDX INTEGER, ZTEXTUREUNIT INTEGER, ZURL VARCHAR )k? indexZIMAGE_ZTEXTUREUNIT_INDEXZIMAGE CREATE INDEX ZIMAGE_ZTEXTUREUNIT_INDEX ON ZIMAGE (ZTEXTUREUNIT)d''tableZPARAMETERARBZPARAMETERARB CREATE TABLE ZPARAMETERARB ( ZPIPELINESTAGE INTEGER, Z_PK INTEGER PRIMARY KEY, Z_OPT INTEGER, ZISACTIVE INTEGER, ZPRIMITIVETYPE INTEGER, ZLOCATION INTEGER, ZISLOCAL INTEGER, Z_ENT INTEGER ) Q'/indexZPARAMETERARB_ZPIPELINESTAGE_INDEXZPARAMETERARB CREATE INDEX ZPARAMETERARB_ZPIPELINESTAGE_INDEX ON ZPARAMETERARB (ZPIPELINESTAGE)5 ))%tableZPIPELINESTAGEZPIPELINESTAGE CREATE TABLE ZPIPELINESTAGE ( Z_ENT INTEGER, Z_PK INTEGER PRIMARY KEY, Z_OPT INTEGER, ZSOURCEFILE INTEGER, ZTARGET INTEGER, ZSTAGE VARCHAR ) M)'indexZPIPELINESTAGE_ZSOURCEFILE_INDEXZPIPELINESTAGE CREATE INDEX ZPIPELINESTAGE_ZSOURCEFILE_INDEX ON ZPIPELINESTAGE (ZSOURCEFILE)| E)indexZPIPELINESTAGE_ZTARGET_INDEXZPIPELINESTAGECREATE INDEX ZPIPELINESTAGE_ZTARGET_INDEX ON ZPIPELINESTAGE (ZTARGET)7 AtableZPROJECTZPROJECTCREATE TABLE ZPROJECT ( Z_PK INTEGER PRIMARY KEY, Z_OPT INTEGER, Z_ENT INTEGER, ZUSESHADERS INTEGER, ZTYPE INTEGER, ZACTIVETARGET INTEGER, ZNAME VARCHAR ) 8Z  Z >r!B8{ !!AtableZ_METADATAZ_METADATA"CREATE TABLE Z_METADATA (Z_VERSION INTEGER PRIMARY KEY, Z_UUID VARCHAR(255), Z_PLIST BLOB) %%WtableZ_PRIMARYKEYZ_PRIMARYKEY!CREATE TABLE Z_PRIMARYKEY (Z_ENT INTEGER PRIMARY KEY, Z_NAME VARCHAR, Z_SUPER INTEGER, Z_MAX INTEGER)c9indexZUNIFORM_ZTARGET_INDEXZUNIFORM CREATE INDEX ZUNIFORM_ZTARGET_INDEX ON ZUNIFORM (ZTARGET)}tableZUNIFORMZUNIFORMCREATE TABLE ZUNIFORM ( Z_ENT INTEGER, Z_PK INTEGER PRIMARY KEY, Z_OPT INTEGER, ZLOCATION INTEGER, ZCFTYPE INTEGER, ZISEDITABLE INTEGER, ZTARGET INTEGER, ZISMATRIX INTEGER, ZPRIMITIVETYPE INTEGER, ZTYPE INTEGER, ZVECTORSIZE INTEGER, ZNAME VARCHAR )`7{indexZTEXTURE_ZSTATE_INDEXZTEXTURECREATE INDEX ZTEXTURE_ZSTATE_INDEX ON ZTEXTURE (ZSTATE)NotableZTEXTUREZTEXTURECREATE TABLE ZTEXTURE ( ZSTATE INTEGER, Z_PK INTEGER PRIMARY KEY, Z_OPT INTEGER, ZMINFILTER INTEGER, ZTWRAP INTEGER, ZMAGFILTER INTEGER, ZTEXUNIT INTEGER, ZTEXTURETARGET INTEGER, Z_ENT INTEGER, ZSWRAP INTEGER, ZACTIVEFLAG INTEGER, ZRWRAP INTEGER, ZIMAGEWIDTH FLOAT, ZIMAGEHEIGHT FLOAT, ZIMAGEDEPTH FLOAT )rCindexZTARGET_ZRENDERTARGET_INDEXZTARGETCREATE INDEX ZTARGET_ZRENDERTARGET_INDEX ON ZTARGET (ZRENDERTARGET)rCindexZTARGET_ZACTIVETARGET_INDEXZTARGETCREATE INDEX ZTARGET_ZACTIVETARGET_INDEX ON ZTARGET (ZACTIVETARGET)vEindexZPROJECT_ZACTIVETARGET_INDEXZPROJECTCREATE INDEX ZPROJECT_ZACTIVETARGET_INDEX ON ZPROJECT (ZACTIVETARGET)G]tableZSBMODULEZSBMODULECREATE TABLE ZSBMODULE ( Z_PK INTEGER PRIMARY KEY, Z_OPT INTEGER, Z_ENT INTEGER, ZIDENTIFIER3 VARCHAR, ZIDENTIFIER VARCHAR, ZIDENTIFIER1 VARCHAR, ZIDENTIFIER2 VARCHAR )a7{indexZSBMODULE_Z_ENT_INDEXZSBMODULECREATE INDEX ZSBMODULE_Z_ENT_INDEX ON ZSBMODULE (Z_ENT) tableZSOURCEZSOURCECREATE TABLE ZSOURCE ( ZPROJECT INTEGER, Z_PK INTEGER PRIMARY KEY, Z_OPT INTEGER, ZOPTIMIZATIONLEVEL INTEGER, ZAUTOCOMPILE INTEGER, Z_ENT INTEGER, ZLANGUAGETYPE INTEGER, ZHASMAIN INTEGER, ZTYPE INTEGER, ZSOURCESTRING VARCHAR, ZNAME VARCHAR, ZURL VARCHAR )b9indexZSOURCE_ZPROJECT_INDEXZSOURCECREATE INDEX ZSOURCE_ZPROJECT_INDEX ON ZSOURCE (ZPROJECT)>--/tableZSYMBOLCOMPONENTZSYMBOLCOMPONENTCREATE TABLE ZSYMBOLCOMPONENT ( ZSYMBOL INTEGER, Z_PK INTEGER PRIMARY KEY, Z_OPT INTEGER, ZINTVAL INTEGER, ZANIMATE INTEGER, ZCOMPONENTNUM INTEGER, ZPARAMETERARB INTEGER, Z_ENT INTEGER, ZNUMTICKS INTEGER, ZSTEPSIZE FLOAT, ZMAXVALUE FLOAT, ZFLOATVAL FLOAT, ZMINVALUE FLOAT )U-7indexZSYMBOLCOMPONENT_ZPARAMETERARB_INDEXZSYMBOLCOMPONENTCREATE INDEX ZSYMBOLCOMPONENT_ZPARAMETERARB_INDEX ON ZSYMBOLCOMPONENT (ZPARAMETERARB)I-indexZSYMBOLCOMPONENT_ZSYMBOL_INDEXZSYMBOLCOMPONENTCREATE INDEX ZSYMBOLCOMPONENT_ZSYMBOL_INDEX ON ZSYMBOLCOMPONENT (ZSYMBOL)Z tableZTARGETZTARGETCREATE TABLE ZTARGET ( ZRENDERTARGET INTEGER, Z_PK INTEGER PRIMARY KEY, Z_OPT INTEGER, ZINPUTGEOMETRYTYPE INTEGER, ZTYPE INTEGER, ZGEOMVERTICESOUT INTEGER, ZOUTPUTGEOMETRYTYPE INTEGER, ZOPTIMIZATIONLEVEL INTEGER, Z_ENT INTEGER, ZAUTOLINK INTEGER, ZPROJECT INTEGER, ZSTATE INTEGER, ZACTIVETARGET INTEGER, ZNAME VARCHAR )b9indexZTARGET_ZPROJECT_INDEXZTARGETCREATE INDEX ZTARGET_ZPROJECT_INDEX ON ZTARGET (ZPROJECT)\5windexZTARGET_ZSTATE_INDEXZTARGETCREATE INDEX ZTARGET_ZSTATE_INDEX ON ZTARGET (ZSTATE)oolite-1.82/tools/distancemap/dmap-shader/dmap.fs000066400000000000000000000025551256642440500220460ustar00rootroot00000000000000uniform sampler2D directionMap; uniform sampler2D angleMap; const float kThreshold = 0.5; const float kInnerThreshold = kThreshold + 0.01; const float kOuterThreshold = kThreshold - 0.1; const float kFallbackAAFactor = 0.03; const float kAABlurFactor = 1.5; const vec4 kBlack = vec4(0.0, 0.0, 0.0, 1.0); const vec4 kWhite = vec4(1.0); const vec4 kRed = vec4(1.0, 0.0, 0.0, 1.0); const vec4 kBlue = vec4(0.0, 0.0, 1.0, 1.0); float AntiAliasFactor() { return length(fwidth(gl_TexCoord[0].xy)) * 2.0; } float DistanceMap(sampler2D texture, vec2 texCoords, float threshold) { float dmap = texture2D(texture, texCoords).r; // Fake anti-aliasing with a hermite blur. // The fwidth() term lets us scale this appropriately for the screen. vec2 fw = fwidth(texCoords); float aaFactor = (fw.x + fw.y) * kAABlurFactor; // If fwidth() doesn't provide useful data, use a fixed blur instead. // Setting kFallbackAAFactor to zero gives you aliased output in the fallback case. aaFactor = (aaFactor == 0.0) ? kFallbackAAFactor : aaFactor; return smoothstep(threshold - aaFactor, threshold + aaFactor, dmap); } void main() { float inner = DistanceMap(directionMap, gl_TexCoord[0].xy, kInnerThreshold); float outer = DistanceMap(directionMap, gl_TexCoord[0].xy, kOuterThreshold); vec4 color = mix(kBlack, kRed, inner); color = mix(kWhite, color, outer); gl_FragColor = color; } oolite-1.82/tools/distancemap/dmap-shader/dmap.vs000066400000000000000000000001551256642440500220600ustar00rootroot00000000000000void main() { gl_FrontColor = gl_Color; gl_TexCoord[0] = gl_MultiTexCoord0; gl_Position = ftransform(); } oolite-1.82/tools/distancemap/pngusr.h000066400000000000000000000020121256642440500200610ustar00rootroot00000000000000/* pngusr.h: customise libpng build */ /* We only want to read PNGs */ #define PNG_NO_WRITE_SUPPORTED /* No textures embedded in MNGs for us. */ #define PNG_NO_MNG_FEATURES /* Read transformations we don't use */ #define PNG_NO_READ_STRIP_ALPHA #define PNG_NO_READ_SWAP #define PNG_NO_READ_PACKSWAP #define PNG_NO_READ_DITHER #define PNG_NO_READ_GAMMA #define PNG_NO_READ_INVERT_ALPHA #define PNG_NO_READ_USER_TRANSFORM #define PNG_NO_READ_BACKGROUND #define PNG_NO_READ_SHIFT #define PNG_NO_PROGRESSIVE_READ /* Real men access the info struct directly */ #define PNG_NO_EASY_ACCESS /* Let libpng do its own malloc()ing */ #define PNG_NO_USER_MEM /* Static size limit */ #define PNG_NO_SET_USER_LIMITS #define PNG_USER_WIDTH_MAX (65536L) #define PNG_USER_HEIGHT_MAX PNG_USER_WIDTH_MAX /* We don't want any ancillary chunks. */ #define PNG_NO_READ_TEXT #define PNG_READ_ANCILLARY_CHUNKS_NOT_SUPPORTED /* tRNS chunk support has a vulnerability prior to libpng 1.2.18, and we don't need it anyway. */ #define PNG_NO_READ_tRNS oolite-1.82/tools/fonttexgen/000077500000000000000000000000001256642440500162705ustar00rootroot00000000000000oolite-1.82/tools/fonttexgen/English.lproj/000077500000000000000000000000001256642440500210065ustar00rootroot00000000000000oolite-1.82/tools/fonttexgen/English.lproj/InfoPlist.strings000066400000000000000000000002741256642440500243330ustar00rootroot00000000000000/* Localized versions of Info.plist keys */ NSHumanReadableCopyright = " Jens Ayton, 2008";oolite-1.82/tools/fonttexgen/English.lproj/MainMenu.nib/000077500000000000000000000000001256642440500232665ustar00rootroot00000000000000oolite-1.82/tools/fonttexgen/English.lproj/MainMenu.nib/designable.nib000066400000000000000000004057021256642440500260650ustar00rootroot00000000000000 1050 11B26 851 1138 566.00 com.apple.InterfaceBuilder.CocoaPlugin 851 YES YES com.apple.InterfaceBuilder.CocoaPlugin PluginDependencyRecalculationVersion YES NSApplication FirstResponder NSApplication AMainMenu YES Font Texture Generator 1048576 2147483647 NSImage NSMenuCheckmark NSImage NSMenuMixedState submenuAction: Font Texture Generator YES About Font Texture Generator 2147483647 YES YES 1048576 2147483647 Services 1048576 2147483647 submenuAction: Services YES _NSServicesMenu YES YES 1048576 2147483647 Hide Font Texture Generator h 1048576 2147483647 Hide Others h 1572864 2147483647 Show All 1048576 2147483647 YES YES 1048576 2147483647 Quit Font Texture Generator q 1048576 2147483647 _NSAppleMenu File 1048576 2147483647 submenuAction: File YES Save As… s 1048840 2147483647 Edit 1048576 2147483647 submenuAction: Edit YES Undo z 1048576 2147483647 Redo Z 1179648 2147483647 YES YES 1048576 2147483647 Cut x 1048576 2147483647 Copy c 1048576 2147483647 Paste v 1048576 2147483647 Delete 1048576 2147483647 Select All a 1048576 2147483647 YES YES 1048576 2147483647 Find 1048576 2147483647 submenuAction: Find YES Find… f 1048576 2147483647 1 Find Next g 1048576 2147483647 2 Find Previous G 1179648 2147483647 3 Use Selection for Find e 1048576 2147483647 7 Jump to Selection j 1048576 2147483647 Spelling and Grammar 1048576 2147483647 submenuAction: Spelling and Grammar YES Show Spelling… : 1048576 2147483647 Check Spelling ; 1048576 2147483647 Check Spelling While Typing 1048576 2147483647 Check Grammar With Spelling 1048576 2147483647 Substitutions 1048576 2147483647 submenuAction: Substitutions YES Smart Copy/Paste f 1048576 2147483647 1 Smart Quotes g 1048576 2147483647 2 Smart Links G 1179648 2147483647 3 Speech 1048576 2147483647 submenuAction: Speech YES Start Speaking 1048576 2147483647 Stop Speaking 1048576 2147483647 _NSMainMenu 15 2 {{35, 72}, {1210, 1064}} 1946157056 Window NSWindow {3.4028235e+38, 3.4028235e+38} {314, 193} 274 YES 274 {{20, 20}, {1024, 1024}} FontTextureView 265 {{1123, 1022}, {46, 22}} YES -1804468671 272630784 LucidaGrande 13 1040 YES 6 System textBackgroundColor 3 MQA 6 System textColor 3 MAA 265 {{1061, 1024}, {57, 17}} YES 67239488 272630784 X offset: 6 System controlColor 3 MC42NjY2NjY2ODY1AA 6 System controlTextColor 265 {{1174, 1018}, {19, 27}} YES 917024 0 -512 512 1 YES 265 {{1174, 987}, {19, 27}} YES 917024 0 -512 512 1 YES 265 {{1061, 993}, {57, 17}} YES 67239488 272630784 Y offset: 265 {{1123, 991}, {46, 22}} YES -1804468671 272630784 YES 265 {{1058, 893}, {96, 32}} YES 67239424 134217728 Save -2038284033 129 200 25 265 {{1062, 967}, {110, 18}} YES -2080244224 0 Checkerboard 1211912703 2 NSImage NSSwitch NSSwitch 200 25 265 {{1061, 937}, {132, 26}} YES -2076049856 2048 109199615 1 400 75 Latin-1 1048576 2147483647 1 _popUpItemAction: 12 YES OtherViews YES Latin-2 1048576 2147483647 _popUpItemAction: 15 Cyrillic 1048576 2147483647 _popUpItemAction: 11 Greek 1048576 2147483647 _popUpItemAction: 13 Turkish 1048576 2147483647 _popUpItemAction: 14 1 YES YES 2 {1210, 1064} {{0, 0}, {1920, 1178}} {314, 215} {3.4028235e+38, 3.4028235e+38} YES YES YES orderFrontStandardAboutPanel: 142 toggleContinuousSpellChecking: 222 undo: 223 copy: 224 checkSpelling: 225 paste: 226 stopSpeaking: 227 cut: 228 showGuessPanel: 230 redo: 231 selectAll: 232 startSpeaking: 233 delete: 235 performFindPanelAction: 241 centerSelectionInVisibleArea: 245 toggleGrammarChecking: 347 toggleSmartInsertDelete: 355 toggleAutomaticQuoteSubstitution: 356 toggleAutomaticLinkDetection: 357 hide: 367 hideOtherApplications: 368 terminate: 369 unhideAllApplications: 370 value: values.offsetX value: values.offsetX value values.offsetX 2 392 value: values.offsetX value: values.offsetX value values.offsetX 2 394 value: values.offsetY value: values.offsetY value values.offsetY 2 402 value: values.offsetY value: values.offsetY value values.offsetY 2 404 saveImage: 415 value: values.alternatingColors value: values.alternatingColors value values.alternatingColors 2 419 saveImage: 420 takeEncodingFromTag: 431 encodingPopUp 432 YES 0 YES -2 File's Owner -1 First Responder -3 Application 29 YES MainMenu 56 YES 217 YES 83 YES 81 YES 80 8 205 YES 202 198 207 214 199 203 197 206 215 218 YES 216 YES 200 YES 219 201 204 220 YES 213 210 221 208 209 57 YES 58 134 150 136 1111 144 143 131 YES 149 145 130 211 YES 212 YES 195 196 346 348 YES 349 YES 350 351 354 371 YES 372 YES 375 376 YES 377 378 YES 379 380 YES 381 390 395 YES 396 YES 397 YES 398 399 400 413 YES 414 416 YES 417 423 YES 424 YES 425 YES 426 427 428 429 430 YES YES -3.IBPluginDependency 130.IBPluginDependency 130.ImportedFromIB2 130.editorWindowContentRectSynchronizationRect 131.IBPluginDependency 131.ImportedFromIB2 134.IBPluginDependency 134.ImportedFromIB2 136.IBPluginDependency 136.ImportedFromIB2 143.IBPluginDependency 143.ImportedFromIB2 144.IBPluginDependency 144.ImportedFromIB2 145.IBPluginDependency 145.ImportedFromIB2 149.IBPluginDependency 149.ImportedFromIB2 150.IBPluginDependency 150.ImportedFromIB2 195.IBPluginDependency 195.ImportedFromIB2 196.IBPluginDependency 196.ImportedFromIB2 197.IBPluginDependency 197.ImportedFromIB2 198.IBPluginDependency 198.ImportedFromIB2 199.IBPluginDependency 199.ImportedFromIB2 200.IBPluginDependency 200.ImportedFromIB2 200.editorWindowContentRectSynchronizationRect 201.IBPluginDependency 201.ImportedFromIB2 202.IBPluginDependency 202.ImportedFromIB2 203.IBPluginDependency 203.ImportedFromIB2 204.IBPluginDependency 204.ImportedFromIB2 205.IBPluginDependency 205.ImportedFromIB2 205.editorWindowContentRectSynchronizationRect 206.IBPluginDependency 206.ImportedFromIB2 207.IBPluginDependency 207.ImportedFromIB2 208.IBPluginDependency 208.ImportedFromIB2 209.IBPluginDependency 209.ImportedFromIB2 210.IBPluginDependency 210.ImportedFromIB2 211.IBPluginDependency 211.ImportedFromIB2 212.IBPluginDependency 212.ImportedFromIB2 212.editorWindowContentRectSynchronizationRect 213.IBPluginDependency 213.ImportedFromIB2 214.IBPluginDependency 214.ImportedFromIB2 215.IBPluginDependency 215.ImportedFromIB2 216.IBPluginDependency 216.ImportedFromIB2 217.IBPluginDependency 217.ImportedFromIB2 218.IBPluginDependency 218.ImportedFromIB2 219.IBPluginDependency 219.ImportedFromIB2 220.IBPluginDependency 220.ImportedFromIB2 220.editorWindowContentRectSynchronizationRect 221.IBPluginDependency 221.ImportedFromIB2 29.IBPluginDependency 29.ImportedFromIB2 29.WindowOrigin 29.editorWindowContentRectSynchronizationRect 346.IBPluginDependency 346.ImportedFromIB2 348.IBPluginDependency 348.ImportedFromIB2 349.IBPluginDependency 349.ImportedFromIB2 349.editorWindowContentRectSynchronizationRect 350.IBPluginDependency 350.ImportedFromIB2 351.IBPluginDependency 351.ImportedFromIB2 354.IBPluginDependency 354.ImportedFromIB2 371.IBEditorWindowLastContentRect 371.IBPluginDependency 371.IBWindowTemplateEditedContentRect 371.NSWindowTemplate.visibleAtLaunch 371.editorWindowContentRectSynchronizationRect 371.windowTemplate.hasMinSize 371.windowTemplate.minSize 372.IBPluginDependency 375.IBPluginDependency 375.IBViewBoundsToFrameTransform 376.IBPluginDependency 376.IBViewBoundsToFrameTransform 377.IBPluginDependency 378.IBPluginDependency 378.IBViewBoundsToFrameTransform 379.IBPluginDependency 380.IBPluginDependency 380.IBViewBoundsToFrameTransform 381.IBPluginDependency 390.IBPluginDependency 395.IBPluginDependency 395.IBViewBoundsToFrameTransform 396.IBPluginDependency 396.IBViewBoundsToFrameTransform 397.IBPluginDependency 397.IBViewBoundsToFrameTransform 398.IBPluginDependency 399.IBPluginDependency 400.IBPluginDependency 413.IBPluginDependency 413.IBViewBoundsToFrameTransform 414.IBPluginDependency 416.IBPluginDependency 416.IBViewBoundsToFrameTransform 417.IBPluginDependency 423.IBPluginDependency 423.IBViewBoundsToFrameTransform 424.IBPluginDependency 425.IBPluginDependency 425.editorWindowContentRectSynchronizationRect 426.IBPluginDependency 427.IBPluginDependency 428.IBPluginDependency 429.IBPluginDependency 430.IBPluginDependency 56.IBPluginDependency 56.ImportedFromIB2 57.IBPluginDependency 57.ImportedFromIB2 57.editorWindowContentRectSynchronizationRect 58.IBPluginDependency 58.ImportedFromIB2 80.IBPluginDependency 80.ImportedFromIB2 81.IBPluginDependency 81.ImportedFromIB2 81.editorWindowContentRectSynchronizationRect 83.IBPluginDependency 83.ImportedFromIB2 YES com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{436, 809}, {64, 6}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{608, 612}, {275, 83}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{187, 434}, {243, 243}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{608, 612}, {167, 43}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{608, 612}, {241, 103}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {74, 862} {{593, 1006}, {297, 20}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{608, 612}, {215, 63}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{0, 81}, {1210, 1064}} com.apple.InterfaceBuilder.CocoaPlugin {{0, 81}, {1210, 1064}} {{35, 47}, {1210, 1089}} {314, 193} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin AUGgAABBoAAAA com.apple.InterfaceBuilder.CocoaPlugin P4AAAL+AAABEjGAAxIJAAA com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin P4AAAL+AAABEhKAAxIHgAA com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin AUSSwABEfoAAA com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin AUSSwABEdsAAA com.apple.InterfaceBuilder.CocoaPlugin P4AAAL+AAABEhKAAxHwAAA com.apple.InterfaceBuilder.CocoaPlugin P4AAAL+AAABEjGAAxHzAAA com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin P4AAAL+AAABEhEAAxGbAAA com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin P4AAAL+AAABEhMAAxHXAAA com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin P4AAAL+AAABEhKAAxHBAAA com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{1085, 907}, {132, 103}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{605, 853}, {298, 153}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{737, 983}, {142, 23}} com.apple.InterfaceBuilder.CocoaPlugin YES YES YES YES 432 YES FontTextureView NSView YES YES saveImage: takeEncodingFromTag: YES id id YES YES saveImage: takeEncodingFromTag: YES saveImage: id takeEncodingFromTag: id encodingPopUp NSPopUpButton encodingPopUp encodingPopUp NSPopUpButton IBProjectSource FontTextureView.h YES NSActionCell NSCell IBFrameworkSource AppKit.framework/Headers/NSActionCell.h NSApplication NSResponder IBFrameworkSource AppKit.framework/Headers/NSApplication.h NSApplication IBFrameworkSource AppKit.framework/Headers/NSApplicationScripting.h NSApplication IBFrameworkSource AppKit.framework/Headers/NSColorPanel.h NSApplication IBFrameworkSource AppKit.framework/Headers/NSHelpManager.h NSApplication IBFrameworkSource AppKit.framework/Headers/NSPageLayout.h NSApplication IBFrameworkSource AppKit.framework/Headers/NSUserInterfaceItemSearching.h NSApplication IBFrameworkSource AppKit.framework/Headers/NSWindowRestoration.h NSBrowser NSControl IBFrameworkSource AppKit.framework/Headers/NSBrowser.h NSButton NSControl IBFrameworkSource AppKit.framework/Headers/NSButton.h NSButtonCell NSActionCell IBFrameworkSource AppKit.framework/Headers/NSButtonCell.h NSCell NSObject IBFrameworkSource AppKit.framework/Headers/NSCell.h NSControl NSView IBFrameworkSource AppKit.framework/Headers/NSControl.h NSControl IBFrameworkSource AppKit.framework/Headers/NSLayoutConstraint.h NSController NSObject IBFrameworkSource AppKit.framework/Headers/NSController.h NSFormatter NSObject IBFrameworkSource Foundation.framework/Headers/NSFormatter.h NSMatrix NSControl IBFrameworkSource AppKit.framework/Headers/NSMatrix.h NSMenu NSObject IBFrameworkSource AppKit.framework/Headers/NSMenu.h NSMenuItem NSObject IBFrameworkSource AppKit.framework/Headers/NSMenuItem.h NSMenuItemCell NSButtonCell IBFrameworkSource AppKit.framework/Headers/NSMenuItemCell.h NSMovieView NSView IBFrameworkSource AppKit.framework/Headers/NSMovieView.h NSObject IBFrameworkSource AppKit.framework/Headers/NSAccessibility.h NSObject NSObject NSObject NSObject NSObject IBFrameworkSource AppKit.framework/Headers/NSDictionaryController.h NSObject IBFrameworkSource AppKit.framework/Headers/NSDragging.h NSObject IBFrameworkSource AppKit.framework/Headers/NSFontManager.h NSObject IBFrameworkSource AppKit.framework/Headers/NSFontPanel.h NSObject IBFrameworkSource AppKit.framework/Headers/NSKeyValueBinding.h NSObject NSObject IBFrameworkSource AppKit.framework/Headers/NSNibLoading.h NSObject IBFrameworkSource AppKit.framework/Headers/NSPasteboard.h NSObject IBFrameworkSource AppKit.framework/Headers/NSSavePanel.h NSObject IBFrameworkSource AppKit.framework/Headers/NSTableView.h NSObject IBFrameworkSource AppKit.framework/Headers/NSToolbarItem.h NSObject IBFrameworkSource AppKit.framework/Headers/NSView.h NSObject IBFrameworkSource Foundation.framework/Headers/NSArchiver.h NSObject IBFrameworkSource Foundation.framework/Headers/NSClassDescription.h NSObject IBFrameworkSource Foundation.framework/Headers/NSError.h NSObject IBFrameworkSource Foundation.framework/Headers/NSFileManager.h NSObject IBFrameworkSource Foundation.framework/Headers/NSKeyValueCoding.h NSObject IBFrameworkSource Foundation.framework/Headers/NSKeyValueObserving.h NSObject IBFrameworkSource Foundation.framework/Headers/NSKeyedArchiver.h NSObject IBFrameworkSource Foundation.framework/Headers/NSObject.h NSObject IBFrameworkSource Foundation.framework/Headers/NSObjectScripting.h NSObject IBFrameworkSource Foundation.framework/Headers/NSPortCoder.h NSObject IBFrameworkSource Foundation.framework/Headers/NSRunLoop.h NSObject IBFrameworkSource Foundation.framework/Headers/NSScriptClassDescription.h NSObject IBFrameworkSource Foundation.framework/Headers/NSScriptKeyValueCoding.h NSObject IBFrameworkSource Foundation.framework/Headers/NSScriptObjectSpecifiers.h NSObject IBFrameworkSource Foundation.framework/Headers/NSScriptWhoseTests.h NSObject IBFrameworkSource Foundation.framework/Headers/NSThread.h NSObject IBFrameworkSource Foundation.framework/Headers/NSURL.h NSObject IBFrameworkSource QuartzCore.framework/Headers/CAAnimation.h NSObject IBFrameworkSource QuartzCore.framework/Headers/CALayer.h NSObject IBFrameworkSource QuartzCore.framework/Headers/CIImageProvider.h NSPopUpButton NSButton IBFrameworkSource AppKit.framework/Headers/NSPopUpButton.h NSPopUpButtonCell NSMenuItemCell IBFrameworkSource AppKit.framework/Headers/NSPopUpButtonCell.h NSResponder IBFrameworkSource AppKit.framework/Headers/NSInterfaceStyle.h NSResponder NSObject IBFrameworkSource AppKit.framework/Headers/NSResponder.h NSResponder NSStepper NSControl IBFrameworkSource AppKit.framework/Headers/NSStepper.h NSStepperCell NSActionCell IBFrameworkSource AppKit.framework/Headers/NSStepperCell.h NSTableView NSControl NSText NSView IBFrameworkSource AppKit.framework/Headers/NSText.h NSTextField NSControl IBFrameworkSource AppKit.framework/Headers/NSTextField.h NSTextFieldCell NSActionCell IBFrameworkSource AppKit.framework/Headers/NSTextFieldCell.h NSUserDefaultsController NSController IBFrameworkSource AppKit.framework/Headers/NSUserDefaultsController.h NSView IBFrameworkSource AppKit.framework/Headers/NSClipView.h NSView NSView NSView IBFrameworkSource AppKit.framework/Headers/NSOpenGLView.h NSView IBFrameworkSource AppKit.framework/Headers/NSRulerView.h NSView NSResponder NSWindow IBFrameworkSource AppKit.framework/Headers/NSDrawer.h NSWindow NSWindow NSResponder IBFrameworkSource AppKit.framework/Headers/NSWindow.h NSWindow NSWindow IBFrameworkSource AppKit.framework/Headers/NSWindowScripting.h 0 IBCocoaFramework com.apple.InterfaceBuilder.CocoaPlugin.macosx com.apple.InterfaceBuilder.CocoaPlugin.macosx com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 YES ../fonttexgen.xcodeproj 3 YES YES NSMenuCheckmark NSMenuMixedState NSSwitch YES {9, 8} {7, 2} {15, 15} oolite-1.82/tools/fonttexgen/English.lproj/MainMenu.nib/keyedobjects.nib000066400000000000000000000420111256642440500264310ustar00rootroot00000000000000bplist00rsT$topX$objectsX$versionY$archiver]IB.objectdata !%&,1QRSTUV dqz{|  (12;<EFPYZmnsv !-./36<? @BCDEHLow!"#$(12348ABCGPQRV_cdefjstuvz     !(-.567<EIJKPQVW[!,-.>IT_`airs|}      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghiloU$null  _NSAccessibilityOidsValues_NSAccessibilityConnectors_NSObjectsValues]NSConnections_NSAccessibilityOidsKeysVNSRoot]NSObjectsKeys_NSVisibleWindowsZNSOidsKeys\NSOidsValuesV$class7s89"#$[NSClassName]NSApplication'()*X$classesZ$classname*+^NSCustomObjectXNSObject-.0ZNS.objects/r23456789:;<=>?@ABCDEFGHIJKLMNHP_NSWindowStyleMask_NSWindowBacking_NSWindowContentMinSizeYNSMinSize]NSWindowTitle]NSWindowClass\NSWindowRect_NSUserInterfaceItemIdentifier_NSWindowContentMaxSize\NSScreenRect\NSWindowViewYNSMaxSizeYNSWTFlags[NSViewClass_NSWindowIsRestorable o  n ptq _{{35, 72}, {1210, 1064}}VWindowXNSWindow_{3.4028235e+38, 3.4028235e+38}Z{314, 193}WXYZ[\]^_`ab^ZNSSubviews_NSNextResponder[NSFrameSize[NSSuperviewXNSvFlagsXNSWindow klm -epfghijklmno$-259_{{1058, 893}, {96, 32}}[\]^_`cmefghijl_NSKeyEquivalent^NSButtonFlags2_NSPeriodicInterval]NSButtonFlags_NSPeriodicDelay_NSAlternateContents?<@@TSave'(oppqr+\NSButtonCell\NSActionCellVNSCell'(tuu+XNSButtonXZr[\KKyP{V^~ C  A D_{{1062, 967}, {110, 18}}[\]^_`nBh _NSAlternateImage]NSNormalImageJFEBH>u+]NSPopUpButton\{1210, 1064}'(A+_{{0, 0}, {1920, 1178}}Z{314, 215}_{3.4028235e+38, 3.4028235e+38}'(FGG+_NSWindowTemplate'(IJJK+\NSMutableSetUNSSet-Mp NOPQRSTUVWXYZ[\]^_`abcdefghijklmtzĀɀ΀Ӏ؀܀gpqrtuv]NSDestinationWNSLabelXNSSourcexyuxyz׀vwSUXh _About Font Texture Generator_orderFrontStandardAboutPanel:'(+_NSNibControlConnector^NSNibConnectorqru~y{׀|}SUX*h,_Check Spelling While Typing_toggleContinuousSpellChecking:qruy׀SUXhTUndoQzUundo:qruy׀SUXTCopyQcUcopy:qruĀy׀|SUX^Check SpellingQ;^checkSpelling:qruԀy׀SUXUPasteQvVpaste:qru䀘y׀SUX2h4]Stop Speaking]stopSpeaking:qruy׀SUXSCutQxTcut:qruy   ׀|SUXnShow Spelling &Q:_showGuessPanel:qruy׀SUXTRedoQZUredo:qr%u'y*+,.׀SUXZSelect AllQaZselectAll:qr5u7y:;<׀SUX^Start Speaking^startSpeaking:qrDuFyIJK׀SUXVDeleteWdelete:qrSuUyWXYZ\SUX`bh!eFind &Qf_performFindPanelAction:qrguiyWlmnp׀SUX_Jump to SelectionQj_centerSelectionInVisibleArea:qrwuyÀy|}~׀|€SUX_Check Grammar With Spelling_toggleGrammarChecking:qruȀy\ǀSUX.h0_Smart Copy/Paste_toggleSmartInsertDelete:qrùyBˀS̀UX\Smart QuotesQg_!toggleAutomaticQuoteSubstitution:qruҀyЀSрUX[Smart LinksQG_toggleAutomaticLinkDetection:qru׀yx׀vՀSրUX_Hide Font Texture GeneratorQhUhide:qruˀۀyx׀vڀSրUX[Hide Others_hideOtherApplications:qruڀyx׀vހS߀UX_Quit Font Texture GeneratorQqZterminate:qruyx׀vSUXXShow All_unhideAllApplications:pqrBg_NSNibBindingConnectorVersionYNSKeyPathYNSBindingP_NSSharedInstance '(+_NSUserDefaultsController\NSController_NSUserDefaultsController_value: values.offsetXUvalue^values.offsetX'( +_NSNibBindingConnector^NSNibConnector_NSNibBindingConnectorpqrBi-pqrBl9_value: values.offsetY^values.offsetYpqrBj2pqrf*umy?@B׀SUXFHhhSave As &QspqrfMuoyM_takeEncodingFromTag:pqroSTfM]encodingPopUp'(XYYZ+_NSNibOutletConnector^NSNibConnector-\N]^_xv;=defijl7'Fy{Ui~Wy/KfghijklD0mXn~ovu Ԁ݁ ف―|"1$){&-ƀŀʀπ $&-/259;74<>BDMOQP[^ad6"#]NSApplication5hYAMainMenu-p_dg^xYNSSubmenuvSUX_Font Texture Generator^submenuAction:-pvifjlڀu  Ԁـ݀gxPP\NSIsDisabled]NSIsSeparatorvS UX xev SUX XServices^submenuAction: h-pg__NSServicesMenuxPPvS UX xPPvS UX \_NSAppleMenu^=FSUXTFile^submenuAction:-p;g^#$+SUXTEdit^submenuAction:-/pyF'~)-1g@APPS UX KLPPS UX VWW`^SUX TFind^submenuAction:-bpU{i"$&gWklmB#S̀UXYFind NextWuvw%SрUX]Find PreviousW'S(UX_Use Selection for FindQe|*SUX+_Spelling and Grammar^submenuAction:-py{gƁ.SUX/]Substitutions^submenuAction:-pŀʀπg2SUX3VSpeech^submenuAction:-p7䀮g[_NSMainMenu'(22+-N^_x=d^fxxxxxxxxWWW~W^W/KKgKhKiKKKlkjKmKnKov vvvvvvvv)||1||-ƀƀƀ  $ - 952 < B MOQQQQQ6-o]^_xv;=defNijl7'Fy{Ui~WOPQRSTUVWXYZ[\y]^_`abcd/KfghiefjklD0ghmXin~jkolmvu Ԁ݀t ف―|"1$){&z-ƀŀʀπĀɀ΀Ӏ؀܀ $&-/259;74<>BDMOQP[^ad6-o:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~689:PQSZ[\]^_bcdeopqrstwxyz{|}-jpg-m6'(pqq+^NSIBObjectData_NSKeyedArchiver'0:?MO(6PWex *-/1t,9CMYprtvxz|~#/;DMOQSUXZ\ez|~#DNUWY[\_ace        " + 0 1 B I P Y b d f i v       ( = ? A C E O \ _ a j s  0 2 4 6 8 : < A F P e g i k m z  4 A L W c d f o q z        ! # % ? ` a c l n w | 24689<>@B\*9N\n!#%&)+-/J'0<>@IRWm +HSez|~$17@BGLNPRTVXZ\iuwy{ "$&SUZ_acegikmow %*,.02468:@T .;BKXfs| XZ\^`bdfhjlnprtvxz|~;[dk0=?ACdfkprtvxz').3579;=LN]jlnp #1>@BDeglqsuwy{57<ACEGIKPRXegik"/135VX]bdfhjls{9;@EGIKMOce>@EJLNPRTadfi|  % ' ) + P R W \ ^ ` b d f r t !!!!!!8!:!?!D!F!H!J!L!N!Z!s!!!!!!!!!!!!!!!!!!!!"""#"("*","."0"2";"T"q""""""""""""""""##"#:#@#O#X#_#w################$$$4$6$8$:$<$>$@$Q$S$U$W$Y$d$$$$$$$$$$$$$$%%% %%%%%%%'%*%,%/%@%B%S%U%W%Y%[%r%%%%%%%%%%%&w&z&}&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&'''''' ' '''''''''' '"'$'&')'2'4'7'E'V'Y'\'^'a'k't'{'~'''''''''''''''''((((("(%('()(+(.(0(2([(h(v(x(}(((((((((((((((((((((())))))))&)O)Q)V)[)])_)`)b)d)f)g)))))))))))))))))))))))*******H*K*P*U*W*Z*\*^*`*b*e*j*y**************************++)+++0+5+7+9+:+<+>+@+A+j+l+q+v+x+{+}+++++++++++++++++++++++,,(,*,/,4,7,9,;,=,?,M,r,t,y,~,,,,,,,,,,,,,,,,,,,----$-&-(-*-,-.-W-Y-^-c-e-h-j-l-n-p-s--------------------.. .....#.,.1.:.................////// / ///////// /"/$/'/)/+/-///2/4/6/9/;/=/?/B/D/F/H/J/L/N/P/R/T/V/X/Z/\/^/`/b/d/f/h/j/l/n/p/r/t/v/x/z/|/~///0m0p0s0v0x0z0|0~00000000000000000000000000000000000000000000000000000000111111 1 1111111111 1"1$1&1(1*1,1.10121416181:1<1>1@1B1D1F1H1J1L1N1P1R1T1V1X1Z1\1^1a1j2K2N2Q2T2W2Z2]2`2c2f2i2l2o2r2u2x2{2~22222222222222222222222222222222222222222223333 3333333 3#3&3)3,3/3235383;3>3A3D3G3J3M3P3S3V3Y3\3_3b3e3h3k3n3q3t3w3z3}33333333333333333333333333333333333333333333333333333333444444 4 444444444!4$4'4*4-404346494<4?4B4E4H4K4N4Q4T4W4Z4]4`4c4f4i4l4o4r4u4x4{4~4444444444444444444444444444t5oolite-1.82/tools/fonttexgen/FontTextureView.h000066400000000000000000000011161256642440500215620ustar00rootroot00000000000000// // FontTextureView.h // fonttexgen // // Created by Jens Ayton on 2008-01-27. // Copyright 2008 Jens Ayton. All rights reserved. // #import @interface FontTextureView: NSView { int _offsetX, _offsetY; BOOL _alternatingColors; NSImage *_topRows; NSArray *_widths; NSDictionary *_template; NSStringEncoding _encoding; IBOutlet NSPopUpButton *encodingPopUp; } @property (nonatomic) int offsetX, offsetY; @property (nonatomic) BOOL alternatingColors; - (IBAction) saveImage:(id)sender; - (IBAction) takeEncodingFromTag:(id)sender; @end oolite-1.82/tools/fonttexgen/FontTextureView.m000066400000000000000000000147231256642440500215770ustar00rootroot00000000000000// // FontTextureView.m // fonttexgen // // Created by Jens Ayton on 2008-01-27. // Copyright 2008 Jens Ayton. All rights reserved. // // Hackerific: it's a view that thinks it's a controller. #import "FontTextureView.h" #import #import "OOEncodingConverter.h" @interface NSImage () @property (setter=setFlipped:, getter=isFlipped) BOOL flipped; @end static inline NSPoint ScalePoint(NSPoint point, NSPoint scale) { point.x *= scale.x; point.y *= scale.y; return point; } static inline NSSize ScaleSize(NSSize size, NSPoint scale) { size.width *= scale.x; size.height *= scale.y; return size; } static inline NSRect ScaleRect(NSRect rect, NSPoint scale) { rect.origin = ScalePoint(rect.origin, scale); rect.size = ScaleSize(rect.size, scale); return rect; } @interface NSString (StringWithCharacter) + (NSString *) stringWithCharacter:(unichar)value; @end @implementation FontTextureView @synthesize offsetX = _offsetX, offsetY = _offsetY; @synthesize alternatingColors = _alternatingColors; + (void) initialize { [self exposeBinding:@"offsetX"]; [self exposeBinding:@"offsetY"]; [self exposeBinding:@"alternatingColors"]; [[NSUserDefaults standardUserDefaults] registerDefaults: [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:4], @"offsetX", [NSNumber numberWithInt:NSWindowsCP1252StringEncoding], @"encoding", nil]]; } - (void) setOffsetX:(int)x { _offsetX = x; [self setNeedsDisplay:YES]; } - (void) setOffsetY:(int)y { _offsetY = y; [self setNeedsDisplay:YES]; } - (void) setAlternatingColors:(BOOL)flag { _alternatingColors = flag; [self setNeedsDisplay:YES]; } - (id)initWithFrame:(NSRect)frame { self = [super initWithFrame:frame]; if (self) { [self bind:@"offsetX" toObject:[NSUserDefaultsController sharedUserDefaultsController] withKeyPath:@"values.offsetX" options:nil]; [self bind:@"offsetY" toObject:[NSUserDefaultsController sharedUserDefaultsController] withKeyPath:@"values.offsetY" options:nil]; [self bind:@"alternatingColors" toObject:[NSUserDefaultsController sharedUserDefaultsController] withKeyPath:@"values.alternatingColors" options:nil]; _encoding = [[NSUserDefaults standardUserDefaults] integerForKey:@"encoding"]; [encodingPopUp selectItemWithTag:_encoding]; _topRows = [NSImage imageNamed:@"toprows"]; _topRows.flipped = YES; _template = [[NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"template" ofType:@"plist"]] copy]; } return self; } - (BOOL) isFlipped { return YES; } - (void)drawRect:(NSRect)rect { // Disable subpixel anti-aliasing. CGContextRef cgCtxt = [[NSGraphicsContext currentContext] graphicsPort]; CGContextSaveGState(cgCtxt); CGContextSetShouldSmoothFonts(cgCtxt, NO); [[NSColor blackColor] set]; [NSBezierPath fillRect:rect]; // Originally hard-coded at 512x512 pixels, scale is used to transform magic numbers as appropriate. NSPoint scale = { self.bounds.size.width / 512, self.bounds.size.height / 512 }; NSFont *font = [NSFont fontWithName:@"Helvetica Bold" size:25.0 * scale.y]; if (font == nil) NSLog(@"Failed to find font!"); NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, [NSColor whiteColor], NSForegroundColorAttributeName, nil]; NSMutableArray *widths = [[_template objectForKey:@"widths"] mutableCopy]; unsigned x, y; for (y = 0; y < 16; ++y) { for (x = 0; x < 16; ++x) { if (_alternatingColors && ((x % 2) == (y % 2))) { NSRect frame = {{ x * 32.0 * scale.x, y * 32.0 * scale.y }, { 32.0 * scale.x, 32.0 * scale.y }}; [[NSColor darkGrayColor] set]; [NSBezierPath fillRect:frame]; } uint8_t value = y * 16 + x; if (y >= 2 || value == '\t' || value == 0x08 || value == 0x18) { // if (value == 0x7F) value = '?'; // Substitution glyph for unknown characters -- not used NSString *string = [[NSString alloc] initWithBytes:&value length:1 encoding:_encoding]; if (value == 0x08) string = [NSString stringWithCharacter:0x2605]; // Black Star if (value == 0x18) string = [NSString stringWithCharacter:0x2606]; // White Star // Replace Euro sign with Cruzeiro sign. if ([string characterAtIndex:0] == 0x20AC) string = [NSString stringWithCharacter:0x20A2]; // Replace tab with space. if ([string characterAtIndex:0] == '\t') string = [NSString stringWithCharacter:' ']; NSPoint point = NSMakePoint((x * 32.0 + self.offsetX) * scale.x, (y * 32.0 + self.offsetY) * scale.y); [string drawAtPoint:point withAttributes:attrs]; NSNumber *width = [NSNumber numberWithFloat:[string sizeWithAttributes:attrs].width / (4.0 * scale.x)]; if (value < 32) { [widths replaceObjectAtIndex:value withObject:width]; } else { [widths addObject:width]; } } } } [NSGraphicsContext currentContext].imageInterpolation = NSImageInterpolationHigh; NSRect srcRect = {{0, 0}, _topRows.size}; NSRect dstRect = {{0, 0}, {32.0 * scale.x * 8.0, 32.0 * scale.y * 2.0}}; [_topRows drawInRect:dstRect fromRect:srcRect operation:NSCompositePlusLighter fraction:1.0]; _widths = [widths copy]; CGContextRestoreGState(cgCtxt); } - (IBAction) saveImage:(id)sender { NSSavePanel *savePanel = [NSSavePanel savePanel]; [savePanel setAllowedFileTypes:[NSArray arrayWithObject:(NSString *)kUTTypeTIFF]]; [savePanel setCanSelectHiddenExtension:YES]; [savePanel setExtensionHidden:NO]; [savePanel setNameFieldStringValue:@"oolite-font.tiff"]; if ([savePanel runModal] == NSOKButton) { NSBitmapImageRep *rep = [self bitmapImageRepForCachingDisplayInRect:self.bounds]; [self cacheDisplayInRect:self.bounds toBitmapImageRep:rep]; NSString *path = [[savePanel URL] path]; [[rep TIFFRepresentation] writeToFile:path atomically:YES]; NSMutableDictionary *plist = [_template mutableCopy]; [plist setObject:_widths forKey:@"widths"]; [plist setObject:StringFromEncoding(_encoding) forKey:@"encoding"]; path = [[path stringByDeletingPathExtension] stringByAppendingPathExtension:@"plist"]; [plist writeToFile:path atomically:YES]; } } - (IBAction) takeEncodingFromTag:(id)sender { _encoding = [[sender selectedItem] tag]; [[NSUserDefaults standardUserDefaults] setInteger:_encoding forKey:@"encoding"]; [self setNeedsDisplay:YES]; } @end @implementation NSString (StringWithCharacter) + (NSString *) stringWithCharacter:(unichar)value { return [NSString stringWithCharacters:&value length:1]; } @end oolite-1.82/tools/fonttexgen/Info.plist000066400000000000000000000015131256642440500202400ustar00rootroot00000000000000 CFBundleDevelopmentRegion English CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIconFile CFBundleIdentifier org.aegidian.oolite.fonttexgen CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleSignature ???? CFBundleVersion 1.0 NSMainNibFile MainMenu NSPrincipalClass NSApplication oolite-1.82/tools/fonttexgen/fonttexgen_Prefix.pch000066400000000000000000000002271256642440500224630ustar00rootroot00000000000000// // Prefix header for all source files of the 'fonttexgen' target in the 'fonttexgen' project // #ifdef __OBJC__ #import #endif oolite-1.82/tools/fonttexgen/main.m000066400000000000000000000010121256642440500173640ustar00rootroot00000000000000// // main.m // fonttexgen // // Created by Jens Ayton on 2008-01-27. // Copyright Jens Ayton 2008. All rights reserved. // #import int main(int argc, char *argv[]) { @try { [[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithObject:[NSNumber numberWithInteger:1] forKey:@"AppleFontSmoothing"]]; return NSApplicationMain(argc, (const char **) argv); } @catch (id e) { NSLog(@"*** Root exception handler: %@: %@", [e name], [e reason]); } return -1; } oolite-1.82/tools/fonttexgen/oolite-gov-symbols.pdf000066400000000000000000001047001256642440500225370ustar00rootroot00000000000000%PDF-1.3 % 4 0 obj << /Length 5 0 R /Filter /FlateDecode >> stream xK$9]羊F3ǔp)dR}sUsj9(.W~:]u|o:|7u>_+_?p?8Oat:O?p:8ϗ*^~\˦j|L4pxY;/?vsX U|z-FǏ@!*6?_=N7Ȝ_wF>~xm;;<Jzyx9?E=S{?]SNn?쇶Rvt8c؁,Q/`+m5UBz?*e;c:c zoG` 9o^U0%X%Sή_STo[*D,`w=b<\FTeEg:#z]CMb11\?g5D3Unۮ=(NN2GZ팆2\NÿQ.]\N.1.Wʿ0ʳۃ{J!WXngV9эz[;~k lۿ7Z3ESMY. wSKe8{  Ϳ3xvk 8U^B%^ (D/-ls|qW+cw|_ӝ_ƿߴ~UԮQ]dpU׎!n$Ič3/vk_?m-]o2m_t帺ÉźϚ(Gez?nn*s:vTa#b!*ŕ`xP WDc[Y,*π稼} tӝ2N{F+|1fg-v Ca! 4KF7F tSQH=;)eQ,7\C5n30"vEЕ+>\ۅ"4  f^VeD%dma^y̝Fa[;BO.Txt chTa!t=a(i w 2" !# HF2{*(ie/.@gc dg!;1/仚jkOzq ;E]0HR#{[@8uyiYiXH7UsO{ƍ͗&NڪaΚk GE@J==A W4ANw$'^p@%UaxSL2S0VçrqT" $ t<)䢦dR^,,1c~mRl J^Y^b'8ݢ65=oP̖'ta 5A PS-qN @*(wPS\X`EMGIMA椠j:J,9u[˰&c~+h +_U4+~:Cpi)JMbjQBh1nI*$GRhq YX,ElS!Z¢ƘESIMCIhjӵ&MZn{%AQᢩ)f徭V:*K9hja=С,[o9^Ph Gsbq5v艵 l՚T;-:,X{i_@įEUsD"rL*[eYе]ݕsIWW VHOAIno? &q㤕l%]Pt5 }175*`tu o7[N'a<@L8dy͝ Ĝtu7kM:4ϐ/lLwcKg~Fx\X>áNhMX': kQ6@5dp}8,!Y(IU8Š>#dQָL c!`UKh-*yrWleZiF2C &jpƮ 3v0C7ٴѫCQXuDRK՟| Ipj:rZ̔+f4N^AQc2)e1O9ڜb_$#5R&%FR76D5B[}DgMY9^<5G]V(.eWs3 .s:ƝIF匭+9yf2-R{,m2cXء]%8x){i\WbYp~p$Ǵ@Je7(` rb-k cG,1!' ]JወJ'hA1Kfp7_<ҩdw4jcnt-Er;'g33Yn>E&1c/-! d\>陈ɊGÚZ5B(tLH@ 궠s!Dkf%|phzf OG#@uc Zͤ< )Ղ/0ԯ~?EyYװ*76Ns8)NYW@|Pج \Xm{ /z.掌e rhJVn6ō,촇 é.N\?Z?`̂`q~I$ԟxr`xLĢ*"j -P!}bk">Y^D](A0Y3 7Aѣ VXgamW0JfHe3<(6522#ʘ+D4VrG1 q Eh%_5j/}oAT?#h 2jVm@ o$i@ĭ{ $Vں'IZ$Ykx26 E|p9s'xS%._E8Mۂ Bac R@`rƵ5] maUvg"^w׏Pago{p&LGL9 yv`ѵpX72_ n(ʂx^* #R(5h2:mSu.9' YA o14q \w0uA4dB47nEOd9Gi0Z! %vU65ƫ'Xpa!M1WQ`䌧ʖ50nF|TFQRMKM@Ǚϯ7| hR^m`Nqȣ%vS 4t2a♜J.jͼ{vf NY 2!Vӻ$\Z$=g$9@+NJï cSq EO% rFsN^!*n % 6,NK.}d5^ AvE!fO36t#4huX/@YbB'#yte(>ci'Gdz:3l)1JqwmKwh3ơS&A6`9@ ofmb(_ Q6 /VmcWFa&]fOEX`DQoA,*XlPXUݝQ;B}Ү[+R@^$[+:H\X w#_8jcl}akc b0 ծä2$~a A}lfP;b/8yWIB@+FWD5Vel=!p$fNtA6B "# }*su]m.d${ҋrTB{Qo[6KFV.[r'Φ74hxe"k҄-9j?\o[Ε9Z`V} %3vbrJ?)dnR uԇ h[n hҵ 'a8 Id%BDҿX%J&VYػ%Ck dYva>\ΰfeJй.q걒Tmvx5TrC[VxMkuŚLu5\lfG c&C4UvÔX31O̳$a ;J(:DuObL뼧BN tk .ܥ~{ };*RLõ3fUBl8ZeBF?BǢ%(ECg2 BM2;GIB' ]%U.} Ҍ  u_oP$L!jǎy*l:7+"f~f@]? !efKhlf,EE=gIBJnj]B]rA$lf,հQ$.؞{~o s9R%3E+(aa2.EZ['ܶ4n,(6`,p:=u$ϲk/*: /M=JF?H"ѹuu.L9YX޺u aQ8 Я_D`RDPѯ@ <1MVoD1ȓ4 an6 H^˜n3NKЋ8m3ޥG:eŸhG=M(% ͆P׎>!hm Pd0n0|Tib4Jt!J>vQt$xsmy-ė2nfl<—Xv^Q>c$y]a&~C KHѿ_"I&$>ϳgn*AX&Or4'īu.Oz xϺ[\> %cQ7PǭI!^>YUN:ǍF'́e& kf*W##+׍dS!z)Z? o;k2p9Up QCJ&.{WK'oP(P_YخrhufsbD>S=@fo4mCڑ:`$RX@M~= 5iDzCez-dKIj;2RLucV,q^NfYPw_'F%:a@3PѰ?T@ &}j 3 79{?UDCfQpu8ZAsk">Z%i·û|cb,C5oDS1QX pW~OƃXfz͒(Eu;p) czF L& <xHX?1~ E#U67d\~Ձㆁ^v '_AivmEkx69}SP7f-dz|DUB(K^d:U%m @l2:z+̹]ENL=wGݻ)2)3Ke8P_@R7}UڇML2Dc|p>a0E_N @JdɧeZi6$1BUѮx48hdu()a!=J[翃,6yYd{I)nkh=0-ĥ!G'jua=3'wPyl/4.3 P+^’i{ƚifmFK~ފ&G$LEZIZ<$.ÈK7ziYoV!/WZE~ #5bGOG\=٠E(a~V>q+򚅎T {evhDBO@T4wI#Kv0izMRWّ⯳dT_!$bfhk IW M~NΤ;K6 {g![Z#c;ōZbcgښ׼eRԑ$P !nQ%X(Mb/ uGa^qdrX&@igEl}CT8l632,[ta\ ?.b =Ea=hP Vl!ӕ`Vmm& 7v m/P-mS oӜxE"R]gRXE.?RaYQb1ďh 8wbJ9v1&r~ b9BscL)v{c,v'rtQ iLE+ XZ~Fo;&u)h3A,Ht-DY}J5Ęi >&:Clykk,eAxTKE w 5fF]G-Lᮄ-}k]C!CI87;,!9<2[֯Zh$ǯ-y%:~{FD+ KIȫ&7yYuYuh" y n[D \<Rb<_ J!nUd'y89܍ y pI:*Ye].W+rqp;^o\4p~@ޮM{o{\3 pHR=sΥF۾n3I-G~kXR:o 9 9?DŽvg*O8 IfV)m \8 v_E2p "R1,p\nOֵDe7 3gHDTA򑮄Di#4\08U&F'\\ރ# sllꅤE] yw.sBfQ.Z J! O bQR &OĉXg%XbPwmDRD!k\E %PB[#WG0{HH雍aCgXu#SIJ AgƳJl93*auyPGx듁QrNeÊDN2AZ=Jh$gS}ˤ/S6߀ڤV@~~Nmc_!o]F<)KD+B~7g7E>H \2lO¨f c響{߂$>> n).=V;sbSĬ$*>n'ag(bcc8z"|OA0^XP}5~W䎀Bq[b}7h4>>`Df>J|s]<kHc0af~Jda%{UR5w[Tc$yW OLv-P @ &+ a,R<:-$SȁL!!s( $ jwIs0:)5hYv{ԓ#D)(2QH0'4l/2클V:-ַN)>V7vCyx:sidwjH}pom[WG_΍i5lm7싸a 2r'W%C 51P;PߺMD|des4Ё "'IZc X9[ȃ։V3mL; s]Iziv?4Z4$aBSҘ|N06x^B:%?:T tTa?Z U Aӊ\ܻ柳{%tL1cSA)ar!ILs"[S%h,p@4;}{mSrBZUV7BX bt2GK=pwX`FsIBf{SBՆԛ3vKvSU-JYwx1 OrœݝWJww YqOuOI!'@#HxI%?tO6~B oZZUL,}_lR6P%y rzn󀃶X| ldt<#sv>@96mh}#(V]Ad=\.0 gk<(儆zm>+=6fYQxbv$(3ŎLteXQSՏoSqQx^{1X .[Q܁m]{T {$a@3O5{*8I|}Vyj--AJXOIbzg ź0EU2zZ0}aE]qQ/xJR0[̰Lր }SZ3e"%_ D@RDޔ`#[]K?r z VоFhoz]dE0߂'E *a}I771Xv盦>if~|jgQ{X: ]{]Qg^] mT *W ZlNQ0hguᆫve 5qM1j1%/ѣ@9vI&a|*R AAB!9oCcFh婤5uR 45gE4cI_"iz|݃JR*久йe0B&*0}Pj0✺$\}"Ew5 \0-<$9N (7F7"C*/gzRAVCgJ'f ZBnÏ )#V '<QR3gn OV &p 5pPxY2?iEBY )ɿ4FJYIW?"2dFN<س*{(͊pBxft,y=,i!YgY7bfѐ ,fԪzpr8y6\U4ZI)EjنL{Nȗ(/ )ˆT;UJYUʫd*('[)=&֭:0KiKQϜ;W RpMl&h uCyP&+CZ ?ġ[B)9#6vAV[E+*$x2iUIg /Ps '8Ud,zdWBmIwhl&qVz]JK##ג70k,L8cN`Լ+Xe4}IHJH10$[X)"4.%AȊhiJkVeeKUT;+B-r/I y#wKz{B{ }6nda!ƙ?-ߨK%/Y4Gg) JxXDɕ}UB׼?U(y=ƨV=n5 WWm [D{dY( VyR%lFˎ"yH&kWwO m! ų2E6qP@FD-ZUW[*6@@*&Fmd9:$ic󾩶l71>biʆ+,6ul:S[)r(- Nzԩ(i71f4<,|]1׋ɹ'"(mib+iIM"c'D'!~ &[à {(&/>ֵ%6 J\qxHF\qTxwJ1BAS8ٻ ،2JfzJ4: <ç [CƔR@Z^[9NH)7ޡ;;NpBr<mtIϙ ]=_H5#TFխ4&w;jVEc-U/ɸ7 `"ExlE.{XvB cBG0k2z1=wd#oyn*DT2Ysl_Ly+p5 ɼ+8;Hg@8{ prZ,"-I5I"x"`LY;Cc7q=9b`,RI^aKXG,fM471Z0;sudzl` }eh,og0\{(hGhg&hI~ I&l'fHI&OI4j_hvjC\e(XrgYCͣq4: DBu(l^"pQOZ@+_@92<"Oln SB_Ii4Y9 ғ9bњŽdˈb_8uVhͻ ^AYBqkEfb^LV5a,2OeթU;LdAKIyd~}GG,#)38aۃgLxV4(Ɂ^~ ,{Ԝ:PmI Z`w065+;Rhi6J$C9*zNOe8Z.SP%^+kȠ-C/;)ϩq "q~$El,=ΉU 2KгVEiQƙT2v9Wf&=s0R' ԉ&LjghTE&c, NP2jL3 vv)4ՈFd- aQ8&렗M%;iaS$/Y# bYKƲ,O-Ků&IBlCD"5#1ՁǖXJomYZg_? cV>[ǁYFޟK*YU_u`Ny#j`44K4(F5Ӝ,jK6j-#ͦrk.ٽc;olf5`c $200[ ˬ/x,6j] h1]JW5*!FK۫g"զwG{~ {d˼lBk97͝frE. 䨀 GT? ʉAX4(Hfe`uҌ6f j>jd 먌 aA0 OImPƋ,¢)-KV:` ^Y^ݘUݐEXƸ5YF8EP )/"I z8fx;oY-)n.dD m+r)$Yp Js z&^Z ‰d6Dfs*sEufkU4j}ox3tÏaȴ8RC @ (,ctmvZ- FEH.(16U?A1}q:dU$<kgdΣ+"l@*i"3GmP`kY}8M4Xwxl-jңE(RBD x)|Ӹ]txÌ( (,xqS {#AsQO :ãT4(W-{[ZFzԚh9DK6ָ΃Pt&aV4`p@X1:*~O F%}Zh/M˛kM eD*ָʽG|BC|-3Y@b5; r;_55i eϖU2{ZSB-c31YRN9.; CVA+U,HF-VsߑO)pB1JXTjjK]szZB-Lz_%O↊D#"g~'y MF1壄ת^o(I1uQHO->DZs1[%S䤿nrlɍV9/5fз/1Ī:K6'M-gEUoJwK'TaXQ5'V.y%(h65__pd4 "닀ܭ)w8պ\\n&ٱ 9>C;iJɃ^V]P*{o0]7LT+[J$"6ӅuԶ?D%kiwwF=d2#i;ä3!Ȼ'=f$q;PXq&x !̈́-@U']3;;L'U۔7LYBbJW"P핟 E y@:И(΍") p*;`T8JB3'1kRLưW# qT"k,Q!/S(qKrֺLGhh:Jjh1i3 a[ q{4Eu-aNMx^~%Xs Xc+e#'WL++( "Fӳۃŧ6eԴUJsdhJ^Fֆ  -[٬9wٹ>b*V4"E ߅$FZZf #8wT5 LZJ6rv`9%md&d41d[SNeHn2TXY9'C>d0%T>](9}6^t)d)1A`Ysv+Ab({(o`锥V7^t#'OFfY>O (pYpW1Uzimd2xؘ۷45XWa4֭%RIRKUc } 7bsr `Z""D8o4ba3 aJmzFäA'\gp0Hvmo§g\xڧ [:(-YlF9#Gd#xwWCxzq-2{akgfoQYwꌖ3{UfĎ2A$Jć7m'`72,,LQMS tv < ˋ1^^'kә;u:yxݲ H hx;$Q9k"AF]9@m< %(<;[C^3D.p4\<3a1zI |!ҫ|'ۣy,b1Z)c遗kpiZwZF2 ϘP(a֭g#X|pHXxK //cH5`W@1W-ޝ͓$^' }Ѐ0K^ZTb z|DmZ/%ސ0iđbWr$Zz]ǓPg:LT.X[ թGnY|՚%Y:Mg&bdff4wQ ! `Mu麼%ZV%뚕G(Xɵ^5J=ц}IڂR%ŋ1)@PhQ0lrRUG=F`πdX: iǨȜTSJ9U%nbMQ6 bZbMW2Hn\=%&V]QճX7K]:v?Cb#h0ASoRo=-, pH==Iwubmć^R`9[R.iur6y+ m) $RnQ`l?(SIQB\ MQ;ya_h|h>گS?1zmPvP&m^ͽ7&Ȝ3+O$/Jc* m\ur904m䚷wSbT9X.`[źc KdNl!$QWJJtAi T"ĚuX2aXԑ3{ 91R_6}"XM++I'sfju}2ӵ#;lI] H3Ei,qƯ\\K7[*OBy5puRswGx!=, ,s2W.@>y.!ԣ!#zigl N|N\F>ԌkLw{iͼHV 1oK`eS/ܕ tϠPE`!p'hPx.qs -Kὶ v-3%צYӟܦ-s Mlm%CxSZu&%6礸P:Cr,QME}P'$*|f0AY‹x Sb 14{Ӽ)V %B4ljXK)1ulj5a68 gʻ]$ALz?,$e1pR,fpmm O^$%jMh(ZvcLC+fh-V?]"vRTQW E=,Pvmlz:jy7+^}keFY/*)ADnQ$IL .*"UCUqm*1gd_ :2g: ՚ 'E#0{ilI'x6 m9Jg/m{fcoضt JX{X oY+ըŊ$klH_mEw9q4ear Sӽ 0L3Pq nA. AJyyGSe\M1ieOm3dňCtlDg#hzxd4~'J㌢,L\Y%02]#qzmϒ߆Rm ; RGRoKJ1mwA}CGȈC"^,a}yˬK ^a< D "*oU x%Ն"m]ԅ?%f Pc.Ku/*^E կ̃oO5vo,Bpo3qf08nJ6\AzcvۘJ?2RmږzD}ǽf-É.{@\4c;ĺv (SVtSB-}+9#mϒYN)(T;MLc>ߔ Hed/*0K 0JV4DHEPQR*`K:CMI*L\e͓-7g`+:4lSu{DI?sdBKg>]vjyQ|B С@ρ?+%PG?D۟zåMV?P֬q>+)ce}?9W߳JśA ?;4 ̒7m=JKae|Uqk9ы:Nڂ!,Yr>%vXE֕z:(%/8Ԝ$,>;v$ C9 la*W 4WeX4I_}qO $Ihiu[u>L,GXyTIⲛh8LlZmD4N5}_);64~-F:W [eS*+jY6¸=zT?P1M~V_+xuf6~ٿ˪[K-`zVnz&O1k36>wBQ Bb1s1zԕ9CiS<(- ft>Cmdmޯ":,27mf>9Պjzewsϓg޹/ɡY@;{*͖g콫anKF;9L|:a F>Gc {o󟇯症-zp']ZN0}Dv~J3\Z@u6M ]Tc(S[@WGwbmLQ4mKha7[wiLYS 1.ѩ %qYt$/nS{bȑFzK̡dRVCsM)K"T9XlצLVK$e)ǐǵKKS*:RR;]tWb@ʾimˬwYTQF=tPg4KJoؖfu%P2S?Q2CH]oZiRΧ?&'Xm2|Y1] 40Ta(؎clG#9x)gXDa34.dJ]+:܅$Ec8ɞE)!ssO t㣠Ř5dĨ26e0@dq5FIЧx}!rX1q݂Ǘ}7 5ZI t&UC !a%*TQ5Hf㣠dT;>X&`?Kcl춾ض7"עVX]Dm;r~U윙%:{6sRN4l-ע|;&rK (M'ya+Pϔr:GUg\+u?ww-uAY#Q;XZj>=_7-|avsi(/Ia >[fbM=K\SKD:>Y b%֘y6S{co@R] w.UyF*[] w\AG8BWm:[|lջ.R᭞q L/, .y5eY86eym(o/+蜵+Vl߽*%|30j2bD弹x7\2pӒ-ŁJJw28GG"7^( zmdd.W~)1yf"x=@βb{]r@Dhő$ }5 7Shb(YkaB"Zm,ǢqمJlr_nV,n~<3cl@&e∯*X4sH HQj[C w1#L rP'!2E\攱aE13,VUW4h莫EyD~ck2h4xQMhx rpS]JG߆"H]BKAɢQݬ{8a bꛑ0qA0wEJ{+@P˥GP$!=#l9pUEo@h1`cβEA&IWN춂. v9g$u9Ž$S=VUs>QЕmݲEJo \Sf5Aui-Mv)@^G/\@?["w/e0"12.(xB W$΅L^sqwb>sjX+@2cRqllXcxf$e V(#;N'7-~ >1gGQa.1V_9 I@x2&Q9 Jp!@M$L: I(ڧ뚓;Lx_a[$ =T ~ãYCYG'7G^W}bԁ=:K@kA+d3tQ9Wopk? x[}dӷM2fBKo_~R߃"gZN%|&RK^{_Ѕ%xŽrmo(uoJaݼ¯9ngZ j4unw~=K&Vvh K{Ukk)f~JȐ".@I't`jq^570JLCG|Ub{!,74Rj* W`H4Qα0>w }uK_+˭/|G!rS-QP X|@$ ȩ.ng79llo6tV@ށb=vw)b~x,4t^ pф p! 5NWWzGӨt!OMQUC0_5!Ԡ34 $ç3c=CHwq9g] sA' 3/~8)|KlAA` JPJ>R> uejÛ=,Dnn1nB s7_J@V.;ƑȯTbb TX ]$DCjS(ddZ[6frE>VB_5vY<BMo0\kuc(pʺ dYUT}%LP.ܲ{>\D^E -@37!5 m\[j]3jsbY !:Ui%evR>EBZi`-)7#+n pX/otar]+}(a|ݐt-dZi՝V%!!YOe\^ fU*>gV^r\ Y%5(3`Be>,n)C-ѫwź)pi<+ckW`"gY}[rC *Τ/'Щh̖&Ud{KUK*i@C }1ct١ٰC3ŅH TDdJ*ç8wg1溹Jm l :C9`L'XkV+'_Ah@.rBd+įzi*ֈȣ *'宝y]Cή!// YǍsW @mhM > ʠbVX< . X8 =o^aRF4}/;+5'>Ri\援 yWO "FFz6p PWҜJIi+g2l&~$ҶfndeV1ac.Aɶx;`@u%j/O@""~dmѵb\"|zcH_v;8bd؆.#5H%ˇfJP$EJT˝DQKQ^umIƿ>É꿜dtG" n)ņ%鵮++ޕ^Ȼ%Et&_ӔsYx"vXY$BDT:jQQ_KL({U.Ŀ7|U)]q\Fo^1<#2ƽU/Oz0gL ;!> Fgi]#~(UC5cԼlԲ'8,-&_0;nO"w|u'tb\IMWOzwy=|rPA<ސ᙮hD@SQKX;~#tS 1PC RF?5 yM%k(`L0׈?s'6EY麥ñK(™b8 Q7G"qj)BWm|"gJ d#VjOk$> endobj 6 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs1 7 0 R /Cs2 8 0 R >> /ExtGState << /Gs1 9 0 R /Gs2 10 0 R >> >> endobj 9 0 obj << /Type /ExtGState /ca 0.2500000 >> endobj 10 0 obj << /Type /ExtGState /ca 1 >> endobj 11 0 obj << /Length 12 0 R /N 1 /Alternate /DeviceGray /Filter /FlateDecode >> stream xROHQ6Axw )vuYm[Ңgߺ3ӛ5œ]`鲙}v*b{a[QÓ'a?dy֭S{=5ڊ^-CT#hsM9s1F9 1w7;aYf ]%{w;ћ9 \Ir< X}I<>Uw(gRVzWOelπ~v{|u׶>UEP>,l%KTn)=J+vp,ZSk9xw"zmMWzmʨ)(ͳDf[xf8:罊ZIE?9Z*UVPog~~\?A< =ѯ tIsQIi!3NTc)[d@f endstream endobj 12 0 obj 704 endobj 7 0 obj [ /ICCBased 11 0 R ] endobj 13 0 obj << /Length 14 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xMHaї$T& R+SeL b}wg-E"u.VDNC:DuE^";cT03y|URcE4`λޘvztLUF\)s:k-iYj6|vP4*wd>,y vڴ=S԰79 ڸ@`ӋmvUl5`P=Gj)kP*}6~^/~.~a2 nײ0%f|U 9l7?j`l7"tiNf]?uhgM Zʲ4i[&LY_x {xO$̥߬S]%֧&7g̞>r=g8`候 8rʶ<dWT'<eL~.u"A=9뗚]>313X3-$e}u,gmg664$ыEzL*LZ_j_]Xy[?Xs N/ ]|msϚƫk_WfȸA2)oz-di2|m٣j|5ԥej8ɮeE7[Q|IM%ײxf)|6\ k`Ҳ䍐.> endobj 15 0 obj << /Type /Catalog /Pages 3 0 R /Version /1.4 >> endobj 1 0 obj << /Title (oolite-gov-symbols.graffle) /Author (Jens Ayton) /Subject () /AAPL:Keywords [ () ] /Keywords () /Creator (OmniGraffle) /Producer (Mac OS X 10.5.1 Quartz PDFContext) /CreationDate (D:20080209180000Z00'00') /ModDate (D:20080209180000Z00'00') >> endobj xref 0 16 0000000000 65535 f 0000034517 00000 n 0000032233 00000 n 0000034369 00000 n 0000000022 00000 n 0000032212 00000 n 0000032338 00000 n 0000033382 00000 n 0000034333 00000 n 0000032457 00000 n 0000032509 00000 n 0000032554 00000 n 0000033362 00000 n 0000033418 00000 n 0000034313 00000 n 0000034453 00000 n trailer << /Size 16 /Root 15 0 R /Info 1 0 R /ID [ ] >> startxref 34786 %%EOF oolite-1.82/tools/fonttexgen/template.plist000066400000000000000000000026031256642440500211610ustar00rootroot00000000000000 texture oolite-font.png substitutions Control (Command) (Option)   b = #  widths 8.000 7.000 8.000 8.000 7.000 6.000 7.000 6.000 6.000 6.000 6.000 6.000 6.000 6.000 6.000 6.000 5.000 6.000 6.000 6.000 6.000 6.000 7.500 8.000 6.000 6.000 6.000 6.000 6.000 6.000 6.000 6.000 oolite-1.82/tools/fonttexgen/toprows-big.png000066400000000000000000005435231256642440500212660ustar00rootroot00000000000000PNG  IHDRDY pHYs'_'_jIDATxgEֆ0CI dEAtUTqEvE#k ,+3HqŰkNT, (J!a~ t~SUtuy4cFp<uu.יJ t<|pW<8 G.y8L}y^Npl?d%}UOxpJop& Jo88YeoU\w8HrAc9'sBTSǨ ^/ m #`͆HsU`KdI,M&;^  glc獀H2'2XB[/8T?5@}`DVFܾۜS׮ڟܦy:Y x#{.$hd %AN P sGG4zr*| :\8Sb݅pڥ_=u݃/٠@t^o;1գ1X`>ƌs}/ip$~{ ;5y"q[z4F"H?NPMoW|%$ E3QǞ3FkMN+lFṋ@?n/ Xq nB dviw䛺f«Q͈?Q egNuc1LIx@di9EyC-]8@WNW`+Ꮗ?Dߊ+s C"N͜ ]S?pR`-Yٛ@a:Lj빧!j"`?Ol{DwG[ $x{>Msu2ix܅tގU{R XDþfXn~a~s|+0]H!XL{ٴp/ bHpSxQ'0N$l% m[ ޿ u 9|=@}1ut gM0$;W8@M϶MxH@yZRS1cÛBq}Q Ods@+ᏽ ~) 8˨?jKQ'@ǒʹ"=?tvDNMp&|A= OH!XBɹQXs'Mp8@w T[V!3)b:LZ~g,ܣ2!,1_ 6C dz]e히Q طb @乖rHUu(b:;Jfכ'߫kZ?*pᐸdAVQ F2a(@@9ƔrͼEn B`K/zp?pGϰ Q!-T_N@]V 1jr !@O57 Ds)j1!v޺} ,&ˣ//#yƧ^"Nw!ZFYuLDmڀX `K/"iR*"NG!TwtvF/0?W : ,qڧ_?= qD; $N=Z; DVCޔu|7xduxu#=Ұ@-gFP8@0Șa@mR\Α& aO OtO`@3'干Mx"1RxhSrժ֯t0&c֮ ,Iž3߄CC $3@HSrMoWt*MYf]vO, 9՗!A&1h?Xl~Y/ޔ=U ⟿idz{x- s^w^p-F #;U ;UIYI2.{62PE`ϼ߾ ϜPC|k![< )/O}\\QDK͙ 70 i7n)̭=~`1WOaRN^"oLCtC?Wl(PG/$_F8oF߆1r3Ş Q!3T 1B|Ћo  H#f Nu,t 4 W$._E'prnYpXC9PyU~(P}>0SrƩq Fċ4Kzc[`k&4(;r- ?(P67AjOH`5Ɵh9}EOO @aa7{{o]_7xs g]$_@8d̈́ l f ; 'J8>Cq7A@LOH`2pU{cq5ZLIy#M~ %_{H)@NS鲹rcx:f(?(gO!"=C?@W"gh:n`=hLJR^N~\Eo4?(o]O!"mG2lgRoh+=?p^w H{c@siC @1)1}7 PhU9*<,C(f\vREʴ&y Xʐ.xf@OS{[Kى?(7dWH`Ῠr%EeJ\6vv5-dpSuIj-}x@oz7SBMA}+$_TCy5'Pjd.~HXhSiȺsk;0ẁQ\#ll:ӓm:WUqo3[j\=XW᧥|Xj!+.qw1 P2kj̀lF6N_!^&=l/i&_sOc`EKoN#ۖ3QVo2fAe.?[H`Ὲ҉HO5=)۝p5Ez۬Mc}H#[AeYT{X0!370 cl`iM2w>1Mttv`#;Ceyo!"Jk#&Uָ󏊹㺥Ֆ7(m3> @csKM8D/XZnf?Bgk% ,MF{hR`lSJca9*7u9OL/{RsOQa1Es^͸ ǿ޽C<#2wѤlHٮ-M/ְ97L;I>pB> ~"ĨM X(&<ǫ͕ G96LM8@&G#I;wPeLzIJ9E?B"> 1+z^`'qP0_IEd?:Q1J͕<P=~70> ɛ:: c:3d_sѬ24d й@sG&L^Z,ɜOm2c?сz0O˓Dz1^:|9&ߛuQok!-9ƊCB<,z>C EńGf!xyC(}_﨑g-)1!qY:l54Iad bV6~:Xjэj ,I&<_b DvxΜ3ʧǗƥYet/#>Dn`}l5-%9z21Eܝm7Dh/j!'s5Ӕ .Ɛ}~|N8pTxǀn(>׫uh{m`1n99ֈ}䧖&(61^|5^C E|fS[åwwwf}6L6'8gLYwnwMt#?T_ܾ @K~ql>M̆Et~%⯕W <Pa^C ESߪV;.O˷˱1l^@r*Ogпl5n.^!@Cz`1F~W}Zˬxo ,E^$< /b o 5BFIΑՙ+8!@pzׇ?|ۖ1}3m=lĖdX/wXt1üL_=y?2X/z#<[n8\g(T-_C?bbZ1Ό @4OYN0뿏~렩ȷYguBP[nC E{jb nmzIO]գe s^X o}gCgaw d-s@X .уLڪXBY c8ۓko:ݧ5>oK\mĄEp-;cBNw܁3M|/(!qog uKjX>|.p+Dnۮ}O'?$[WpWocbp?gE̷nپ1V->GUMjw^wo`]l\H``HhGSb Bg_i d[ښKhcDGZ-ݿAЅ{Ϧޔ#yd_3JN7F4ԋ 6 KJ{Db n.3{;Otol [E ?2m<cl$ fCq35&?t\H`ῠ8r*O,<'lf~~{D}kO|uf yɮ j&kpmy1`wy2;Z2R;"g3pe#G`.!q:l''l2шޙ͑u?/Qm3$^[n ceO\.Ǽ@yA~"^Xl%v\(9UWu ΔKzXIYx^Ca/i0R(u${8-\n+ƭ=cvs&f\Yߏg @Eng6__5X/P75/P|e .FÜ?wy͎\VNrdo`b*u%1uףވqɃ^/?pj ub F[4X/X\r(b4V_N9,'l˒|uZ|Wc0|1:r%!m?8d-q~/M|dF޾֞ČB x:ٸyi_LZh^G*9jӹ70Y@/YNc :/NgqIΝUc?Q]ͱ1^v'~2ؖ1 h.?DyCY(!y="<.9)-?c'qe/#+s[jK[%:_XTHӁ厎o sJ>/Fsp8ݏ|cV{mZ-C@x9ozX]A^?t  hU5#p1NܪZGm x Gy?cW c 4Jm'(:H}eIS [}h[cT\џùܼAsF 9qϛ̓@h`<7CyVaY4p1ߏ*sX`w({< @ J95"jU>_``܋Qݬ{o ru=sl#7f/(貟σϦ˷ѼN[ {6\#yd8h"s1x.>;\]؏8c<׭4J[k㓣1CD|Sz(7=ꔑ|> >A B'csP3[0cߍʎ_\wi#iMؿhw\W:Zϧ~aI{۲T28-Us4GO-5Һ$G]3O~ [we?i? [ ~F  6s.W-^G[옾|'UKB&;jC`F2Har Swf?9莱ˁcgtH/dCղ[{U}Nl^$ngz5z~jT} |}1y NG}|8 o 3JK?Pћ;26R`cF\ @;c ORW`0SQݬ[~?}JoKؿWZ+?1ږ9Kw:rU-L(Q1!ʯ"9Jڏ*lmʣ#Lݥ? m 4P}r)63YxGܺ.?˪ 2 rN0u\^[ğ1u[` ?b߄\Yo(U|/:*sj3 +gm>:Kx70,Q!mNyHK]W'_OCS'u[`?RfhZHk,X(L%_?t2Wǎ3[yivX;SCqn Q<ky7"O~/1C*i#Enk|p$T5_?nxn;:*$Yhzt䩒70,v߫yH ]#j @,L5dZ`k3SW$_?Rkv}O[?Np#a'<_\U 70,QEkyzؙH7}/"IIX~і,19fkJj$}ӭn)lk@/L@/<r{3#Lpas=~˸a=|ՂŲh`U^?m^ ppQ}e':km`L$K/ٓQqAbr}&pV~_vV8bpm Ms*$X@'}|`#]N5{t yqWZ,s|ޝs)1'c ]lNm?%} @y7GC+ż\V$?`ٲ94g ΋פϯ-g95 ,P䛻44ЋV>kEOB \󼕙?4Pg3!_J @q/=_!@p}KYƌt#WS[: ,Pi{nDOƐ'x @G@܉?̂NV= ,ٿ϶+|u2wB-ՠ}>y}-xW5 { ~H7"}1|?_̱$^@I;?ppY`]VaEy]fRn>ۭ%+3Oif߻MHuM@^NuLt>CY^p_D寽-k:\h(m#'A`+6k1=0 KlP> 8nڧ}K|w* ܧi:t$X@3"#݈ E~ /qŶSp g ? !wq̭]A_g[ p|=-u[9pz&.6ν K,{S~Dc6g.I 9W`^kwxDT~ f ٿ$v*F>TQ_W[x] `cln-X,Vrߵ{~8p1v7/GY SE_Q#Lv;&Χ/j<@,ڿ6*[$POwvC4˫5jF(lf&F3槅`_iwgp164PDO`OL/v;^ Q~ٿ)$, ,̖lpst;ki 't~_݈ n1'N0qh?Osو?xۥP@0z@1^t]MOf7)E$X@cU8rjk}\:^iۣB7IJ-t]'WWv^ڪ$=N7]ypq~B*r5ܙ%q%q 8k>K y=;OS[7i1mQ$PITUg giV ]mPm^a. ],.:ԕ.>8rƩ^Tc%>TZ# qhy wHm‡+cak>ۡ7IDX(MIS;=i>oHnXczo706X6犝j#`Wl=z^_mz5m\x8"G!7qyg\&6//翅@H*N{$|s׆yla׉ }UIW}45N0ƚz;*$M*ƧKlC1V_]gLLLn}ڿp&( BOGwvn?-}?O[[ =R*RizP_eUN+.MzE5I R z is{`rL~QN1K"* BH ;)Ck~yI_[|H>nf?אB5r{cJS'=_<ǣ_I/"pu/֑Ub[ /Q&$~-پ֢^@3j};A/udRoPRőB0I}75^w15'mx")|/fn[O/yK C(cW<^~KO4ySca% @?S&Q?Cg~H_[3薦k?1s'?C Fjz ƪ3<*%ꑯJהL D"pI@czQoO~. $7lݿD@@{>^k8~>:j[wZ z#`җz ؝oV_oQD$J|Uʽ@_C1)c{(Gl*S`)mA#?>ӞL7ݿXAIe$پٲGZlqދRJݘe'Ν`,k#KcU%֥?W@ /z؇tSx'KS`ar AHY  e,Z3WkNuw7/G  >/Ļ́<}I=^ƕĊ̵?կ,-^%{K)}ѳ@j A(UFVGrWs3{|ܹ<-HC@wT'?DjCUďx GXU⹇czv= ;(7l->RBNjowd`ab AHB[ /y- 6׌t[AT~6]w{m8t X,0ѳ{O0{r?5 ?5=8k)HnҽOx$Z AHB@ι9^H_㗿klZ_*06t37wLG4t2';k늋'MwɈSOә~ GVu$0,&G/ _ؔ'O~m/FDAv_,U$l_DY=*:-2duuWͧn /҇h_Es 5JV^LGڎL,o! ßj1XmIg @ZZ  `z1Ԏ`m\SY҇ |@^y5z' L@j ?" :`wY &e|_5clil޿X%IL#LVЭx]|s~Nu0CL~pY{Qė>N ,АiR迁3'|7Mb{SBt1_J~ Y &e⤍ !|;/)vّ}p vJO#w:pt@cMW{]͵ ,АDO1WO0v {SLk͝Q#@,m\75C{iN#i ؽL;01^FwAGOLt[5doq[{".t9{ CseX[dB5%2ۑ>V8,2I.;q/bН/V)Aw@2#M6ܦjV@l:QL7L{M}o#* 8{~;X>8'% ѧ\UM-? 8("%=p<~z*ŖE @]O] :{ngb I{'D?~ ꁲsN?\cEPW>G+_Qd, ѥ7gVY_[mp%[ޖ:}nBH`HL3O0v9Ŀgg$Sߧ#sғ.EbP)݅/( TWe*atAu~'!oNx/ k׹b G';ꠝ\ſ&[@RBq_4~ #enbB{_PLt@2&ޑ;@ MmU) sql[-VYdw'ȋM=qcMrxO89IʿE(䶧| #eWͫH $]پRw!hZzn[Y7_ w[\[|!Epz'M//]aʼPTrp )f&) D``101_ !hR>Ǭ=KC[F;ԓgo1c{"= ׇVĆ[.2>'dMb`E\/8TMG$/!+ Kd0: &>L}؜Xsovzğ { $H8d(sޭŭ:3O S;`e{uᏒe˰%TT,abl_B0.z-oF=Kɯ5%@Z'kMpyw|q󥭯,GXH^ee[˭%DX(uMӏBD<6Ƞ[.Fs1}J,ND4ڳwucl!9¶.BS2%)ӟ$LɭG3=N"nej2b s CHUgl_wF 3jO4죿;s2bp6`O?0v߻0 o͗Kto&s+=oOװG[2ߛaKH f!n&daɄ@_?*OMp](&GmFc44ؓ1gG !K Ŀ0KJl_کeLq,Q=ok됇]2LJpؾ E0(dDRMټ>>'_ p:my"PL&?@4 )]qM[ϘJ'ϐy)acny뚆;V".\¶#2MBH r]IټHldkd^G?}=@hA X,!0,I N06H%U\ҽ_eX#{:X"4(g 2 !$*ʆۄ ){mWNz_>Dx ={b R7/Y}4Gi8ymJ;5t+ uE-`mwDgbX0d\Fِ<}H$*S ] 3Tۗ~W--1،F!-aW?Hy%aq tj6? sE-xh߆}G/FSr/@H6r]mCټ:ސ%jK[\lhȵ6&# q[ s{_eQH/|X҅C%Vy},{3=p &)QڿLʍ8Q6:u3ew40I\`- q+'[ q{~熇gCK/Ԁ X7%ad.euAl<1/9 ])guLfp dZRPԠO;gٍ 15qa`ZWjJ>v@ޤ^ ӁG>!e Dib(1іq\WϧlNw{P-OaF ّt a*1`OZYq2q\[ZV^|M9[(-RT`[ϩH{i@y oE-PG/FRIiNٸTdyB,P b'|dO j(n{u,rbE埕t |7 vS"ntucd쮮luX;J?h,pH(Έh=t)X݂p# ٝ@/P! OST16 {~}voJZ髅^4bO~4P4$X@q35;$N'I&Gb2J~+]Z ȕ 3i$̻@ce/ NL|1;a=@r#l|"UƋ쟱f'dVR0Oim` -IDu'F;P3J~={Ա{*`K] M R2/ 3R9!C/2֔=@rk~M|M~c'^TH.bNk~Chd(gS G 1t6׏`η$LY1+igho w. U dwH FB/&nX^h_4&a3]ńE_p2I/$ϦPg#j4i s A=ߛBPeJ @a/#^ܯLK`B-T2 {}Dt-m=f*q  ޢC_UQ>GC[|,BX,hAjx>oCp Õп䗽<¯A@u+08".|ԋ[k-y>єCHΣ r]ݠ#mDJ+7 ^1jDuq!9 ]C-U ,PC_2h/~OSN*yZ%?A'( Q{ށs ;BCes$GL@D/ZRw yC;ٿeu+WǪ iogŭ@7µ]kXU=(.A(`G=oO*N^CY-{a݆\WX+'(ҶJ_DkfVkiHSh8фmT{w!jwuB *w4qs0,Y*}L2v p SFs8M>@d/? a5Ӑ'Mʻ*p-oKj>"IKo&278jM$X@ RFNԣQY<$}qΠ # z]S*J4rG ԴLuݿ(&v32RC5(C1 q ҵk]p!P &Ϸ!j٧~c+f"#)(?&1{+ \[d3W3 "ғNg @'%A-`CUce|bcn9Gp /#+UHڽ#4ޚ@r.HoMYY =#Q /V 4ن?@c݈ 6(_OZ0x}ڥ8hXc][C}b ߊ*F.^}0d"oO 9=)#R;Q{b"+r?jPv%Bw[ jWKmx^F8 tL@෯f&t.PDR |e*v^0WJA.UdmWaT*oQ@-R@A?uQ#d7\FoPzw@79tiэQXުK4Q__ܶW-*L+`CT˘oFDcn+D~p^ν0Jl@"Hm!$dy2==x2& ])Je%ݐA4 J5 ,P|N `^cAӰ ogob_H`(be, @v9'(IcLyA e}Mzf}hH/U?@+Ӿ*Lv&KZ9pt~CͤZ ,PPD$mAωc2ؿhR!$eD;Y.& QZk~hB?bbDx5DD~r0+5mHo}3U 9aD`~Ҳ2@"J-!$U!#0UX@ALZ8lL ,P4p^vhƿ`_0F ?*I_`f| X efH\Hm!$ e6FY@|OIQe ۛH v`7PWRy1XvR CD@w %\e} @vRWVD (Wkf$޶@RF齔&-(~a@G-ӭp>n]a .4ԉ{0Ay-xj #T2V>P1 +zR컖:iI4-n@ vdz:yB?*OXP8W(t$TB`j u"'X{j܃U]W4GྃlIЇuMMx@@r`i~'c):{/zs?P0DfcFSƝ/xCpm=!Pg=Q;?vf )ree @6a^ 2"/T uA/؃ѩ!`CPK`089Z+,œt?m 4KԭI3[=$}[ۥP?K{Eš_@~|PDcG}paO0CH)2=^Xk c*F oGSsR^ڰ4xd=U>ej(l ۖށ6xlys2m-qҲ aNڽIŲ؄8ѥ!_+a^{_!`H,k1D[{a w>piةމ^^kov.JI^m7҃8 &5uuo&čl Fޒ[O1hL|%Z#etOGmm6VcD?6Ӆ< D_D/Mwq͙t>JL]^AGdEO4_`nh z*OIB<ʑ1b qf!Jyt*5-Z pU/A :۪A5570,D pyL1&^˨,ԥSQŌh ~{J:jvϯpJ`\ٿ(!RadLOG92%xUM8$Q3~Рɢ}f>+Xf ,(mQ% @g751fdCsM75ȫpf"CH#ez0 q@|9 Z$V}ݪkg4hPDFjN$ZAaLC78% $v(b֯*Ttvw j%",{ʓw+qW^bJ Թ\R' :zhƿ/"#^t`"lqcT.u3)1,35̷_ci)R]bOAٻXgrh-ּ$n"NЀiC<7;jMD,\ݸTu lϪ\;+251^+=Rpz}Sq @ ȷw>eo<)^`^dk9= އ2Ёx`Oj ^̈TW/kga/KPu_^Si^ZϾ؈8UB!?eX7 <^pZc-8$TCq0xV}`I?+oc 8;D,+ْ3>Tvd lBܲUPc^ mMy,=g_j-t/Gkݸa NT~OT"+ gENU}WrZVWr񼑮}Sb$*^pqs3pk"CHA/g;L${Pufd ~Jw;H370,TF55@t ꨙ)oICVLaM|*EJ{ HWtvUa f i~@ a3pFe]D+jl\.%5L @O8+4|xH|ϭ ැf+oFꙠy2%*<˻3~Ow%`eYGM⽡s){0ypGf6lB?y__K!wp0! W0v|*|Ϟ ̭d+oYOHHyʎvJ`'G-@Y00pǪƛH,6k f h\0'-K@1%76 ,oITwةr ўD^nKoOmph"SV(YT1B0Xc_yJ@_h?D [3+\=ZǯpJs.\ٿ3QPV$ ]K٫y,:±)ڮOri󧐽XAo=G70,ӆyAn Ձ6!z1ٞ|_2f-x @+\=7J[̮5[_Dɤ*[){PŚV=N~mMt}aݸa ja !gۚG: |cO<]\2o, Qߒdk2>PE GH  8S !e_Wl=6\ ^wj:hZ h4? > z(? \1jRo>pe{5 hdk3~峥nL,M=dQEͶZ!k>ENק'Ie4XȳZh{M϶ D-^ {Dtqk _R z+ ͘VnW+B؎tG_/8h|BR-?Q@%GjYgE=-}+`%IjR_0 ?*o9D#H= (x/#@~cB>l` N ;@d2x_"P?K>  1G儕;an9<F.Z v(1_E@} uc,!e?8C9ăJlݶ3>T~("=%?@ V$>/O}.}!<}]x3~#;SV o-4 >P3|T*B߼v*=X`j V#>4d*ܽT,g{W8.Z;vFHdRJ8he{@,J .Q W}* yd m P>DŽ1iO,'i߅U HewD{zo}`S .];E(P֌' @v0Q&6?%߼ oVW\t]4}jJp6SMt hCq-<5\[p-& o;x`]MƘ!R/P!Qa ;(-6\A[`zo\+ߋw{KtMn<;pj#:F70,3&#w '&S-^?Ƙy͢ @vp;T5%ߴ V'4OSI!&_O[u|P&j~Ն *ݜvW EwJC+ʗ=6G2p~+JӲ% _)x;08<#ǟ{jJ->] K> VP;2Nԧpos xJ1ƞVU +5<ߑC\pPy0`EoVVwtaoT:ZXjK M ڱJ08 0,8@~;1?PA-PEy޽K *Tvwox ߨ@bh.an <|0Eʁ*kBSV4@TbYGYD^@owo|V`N\H3/+**~[\9ryw*ЖV XT /ݿ`ӆ yVӗڢ$xx ݟi-Goٿ]@npƷԆ\i+[\"8z>=FU/ɶ)RpeFxFY XgNhzw^zK[Ӱ{r}(qKl8 y5q=@3!+cRJ_!Mo<[;s* ɚ m \P=՘4<;@ho*Q/m%WF18z15 {3q$:p& ^0C m\yOF3/kha F@2sj*Ϧ1CyVU`?`odg~JSm|8|+4F::'|k V(Wq](12>TT= )2#\mUbJ[lv/G*O sI*H-85 Sَ6v`9D/>?2=xLƘ!~\ lF*-ʗb.'v)Rp6)kgc+?*)gs6}=_ϭ؄ QԎ6P}Lj8z۳@ ]ݜVz*VteĆ;Ǒ?\DopLo (3g Z7Lcv}Xb3sa?dO W40a|wX?L XjYrՋNJZӁ)e9y=+U .@ ^iZ³IY|Lk#橪kcJgiWݼr` ՍAnE_P 4;H*W(@LV)R݃)3+8 ,lk(L %S,x%8@ƻyӳOu[o;x`^цOPH*WJ@1y{)hLƘ |AxccK(EaiZ{M5j?&LVK}SώiRQmf,8pmQSO-uXjQ_"*TAE ɱLs-eHLcޡR) {U}=Is-X{/ًj֩$@b+rQoI8g _yޢM.W9J"TWzJb/JI- \Ksg7Iyγ*-/m8ImSX$ڳ@zV{-'RR:6W^*jOP$э *TvwhiM/@m[@H%Sfݔ4v L$<{P|.Z pZНtTLt? (/;,OHЍEO|=bS<d/@ QA Yp!wC@F;y ܫ ha˿ޟܕ"D @da|myi凓 @ $yWA`n(vs/4=Ztm9w/L=H.5(hjp-h Pz󬟢v =)SГ]ҫ>2d ZhoRo8כ99Oa؅W@E`/)f}D16SI%.4~S2Omx1lśg<L>DiwS`uƢiߐ綪ܕ"H [we~ƜeW@EhO)f;R}x"5TӃP GJEk룽E@ˉbwTP?Ϛ?#K~l(E<ĬBd~›'愡[W@Ep)fc8=ހkY|G^I;]Y6ū='޴cح.S j\K].HM+{ךvHV_-r@i'_'*ܯY+ k5 x,5̡=S]}`_7xBHH֚hMGImR(.r&__o:9XXw@ ܝHH=U q^"xLhB q>uv;6}@Bh [zԩAF}6O\jxv#g;z*rp"w@ \HIֽT q~ 8`jT FCwd{%R6/i6Gj)Li?@bozԩm 󕉿ntwɿ{U~@d~ߢoA9W98Pm;hd/Ruf̢9;cDo54BWK`wO:O+w^;e*.EE~_U/E5 V G>8TG)j= @Px"-QN`4Bq ZIrAN9cEhcgpEzZDdd/7u:zwI\8ϽN # صr{ 8f|?n?:95,JT@Ez 6|lCu_^yԩ8Pq?ʿ6Ųw4pi5* @p.BzQ>3ܜNSyD/@j>T~h(lyD\a^N @rkʿګE$&?_$杻ը+ɹ 3{7avٺ3< -R: 874{|X'.ӝ=visSP뚖+>@pNI_+RRsfOs>0!2(;ǣ`k05dWg7Sx\j:E[:!z% K AOk 3zko !i.d¢C:]q.7qN p5<[v`nmNNxC3ͯȒ?~*#  rT@ bZ4;W JQ6AhXb27D0Ӟ8JBSv2h(WTf:H~3Mnt U|F Ɂ"I? tuۄT@ }jR4; zBMD;`;-gŋޜӌYCx>>f `'E(kǍN fO, FWʟy2B:? |_ d_\N]32hvn4['@pHo v3Hvz r-RM %IǦl$2굱zԩKɈ`ĿmlڭuPAU@E+Z|]C8\aq-݋u_| kdJ{7o`1?X4L2K@yF@ 6i0Iz @Ń4; ~d5lt:b=݋SD^w1K(LmR_I:ԩhAM%?j_Ҋ0I("Wp ȝxS^fr>f>ܼ?݉'S]\F35_C7k&S6Aۜ:#w.j_n#ahi# ߊ )S<7'h,njf[4szVp=h1yc8aKwF,n|]FB_F]N ԡgvU*8C'2C0ya>O1[1O6m->F4`z*>T;u"v7m΢UYC.Lq'+WKN@.z\y 1 .);(v^S; 8't.1 =nOe4ZR;/팯5OQ679u*fRGTߧ\zUzVվ|KD?I3Ҝm{HW2RlU5}Nۗ{:)C?+?OUk)EtK@ߵC0+q -? sY8Bͦp!{ 0GN~gz_݊{70[b >@~N H'{Ŀ3(1~^91&u_|eu2 ߣ0硵~3O1׊s02|/n )eV0SlyM]xA92g(qpf\G JI:Vs3dpe_ ,p | :- A2ՙb=)3,ؤX@i^a"ۋ^z30X{8{6^ F@>*'G /)hȒt]Y/EY{L{.*\"fs{.1 |1S"weLo?PLT7b("rA/s;x)T#'ث9>j4\zfRw8e*j = %&X\*2`Bʷ@-E90{o/n )Sn!+ ͯ0Z~&o/Nw~aS&#Fj'ج;>jtѯk|S0+]TJt1؞Ū(WZ&;:] !dLP |b ".47|f@6?t{^O7h& ԭ s@'l:auzyӮU[UJ)P|Eh@g=}O_` ؚr @hn(kft=JoNҌaDج;>j_7LEу]M B>*6U{Q` ] !bT/}8L/f9AXzW|;dw32K0B;nUКwooXDQ:O뾪} -Uo(%B:FvE_ SXGtd۩xW\߻ v V VYMZI+ޔhUBƿs+},Wq7TQ.wqZ !d_% ) qa@t+!-nsz9E{gFn/oqN4Ђu~VcܪKhP6˷w7e*,>~MP)?ZY/qEez MpN ]TX@`@G=H=(SҸVY&S1!6/ҽwٖ&P`\KH=OonT4Ρ$+U7m%`fw\N)p-Q{/JR+siOt%!t/OIE~ N]`|.%92ҔHˬ/S}ʥ'_5ڗ"y>sq(_ x`E` )sG/ ].:/g}Q_Hl s@3#Ϊ{*7FYUpn񷏍('| :i@ OQ=13~!Brn/ԭN_`|h& n_ڔ蓶6zM"3]η>Ey~ \Kvt0A0E<.Y@ }S6Gd^zQ7NGݸv s@7`omT=dP%_b:[TDhTx`iÃ` `Kʃ`N~+ťO¥^]F_n<~WWn`+ϝ=3^?T=)޻C~6Qe#6==ם_˱@X@=ݞ( S ]c:6 x;ѻ?⌱^_Rm6mK$>Sn*Y\MjSv(J@ŋF ]վU".TDWF(ïX !d._% fkh U\@uEi˘[4ϝq M[R7u nT8+5 ,“wHTL{Y}(am}*\U0̿C%c®109`w D-6OWx}O>snLٕd7ҍ 's@;X<%hm1@WOŽy@YAQ^TDKu0/)Ä`J }2nN=θ&IJӼ=?|`OL7>n:ʅ GB~YA?Axhk~O_俇=~<=ϯ:" #(@P@=Cݝ%Q 8NS 14&C6vauW>\m~|qRh|nh(RiuWs`[#$=`SK7n>rHER;?X["v0z/~L7G qZ{= U =?ٽ/{ds=\PjYWGH@/B\PƟ~c. a^ !n_V ])H1 W(v@G W?~[_n/~x3 ; 5: WY\'_'h)QSB"N e3{.n0z/ wXL@˿1XtVSQrK.UhҞx+tc#ndtbOU8 5: I[-~:5*FXtk_msI8ݩu? @κ2/vz/ enp @Qi6@,p!?^%h}h'8p8>gCPleOV#*#_?  ߣ%BFR%/4h>1)?!$5ꎜU?xߤIh%DK'Rvo8-,'s,jTKۓ.sExСǟFF @FJ- !]m !ldz?S cCZC&m!hQ+{w7X}EG_zkQ>9%*FYyZ?783S>@rf(H0/f0Rz/~L/ЯqJV/CT+bo|u-1$h@qr?D G,% $#`33Ӵ?+s@JIa^ !^_2)O/IZrCRˢ]$JV/G|<l/C |9ȇ,w]wxHm .z{;KX A#w6u;_aؖɪѿN/n`M`/x_( /DzRZZ|)>:*R} ] !x? @fEtL:V( FSMI6 =X](hʓ}8e5O3ZOƟַOFx[2@+n+9Nu .z{?Kd?4[4I#u"~kvzKU aV^,iǓo@&x6tė{* q .zϫ0Kd?;hlR}@eƈYpm< ]oi MBؖ8h`"L5 @JKwdŏMx/]/ʊ=  .'&uT`mӂSd{Rt+ lKw0t^K_j S-A!Ww\3oJ Hluml"+nNAMS.Smw{ Rd¶a@;=ՙd%T2U^[+Op/@z5dADzFXckV奱=]{^ҁ}S) l3x+=/+ZlğŸ@oxhB4aI#aघ 8GԲ`(/y]70|yLS{sxCj uLZcR/qBư+q)Pp57 x/E١]{bA@/%\[ۧ 8]g% @Pgr&V,WiOğ+e &6Lq 0  VKtF PrkT@/@H tb֐鈿=܊fop{ieۼL>j .=CC1 J,4:x-1jSkÉWaC ZW gA|*`F< 4l_vgi[,7 x@]9{1U_0c96G4o/1=}1ɣUw,NGJXȷѴƟߤ 0 VfVKlwL lfvXI>?VymN@ na8ˆ3^*1ğ =\p4+f 04ϳnX` g0&꘿P /^:ضީW=Xp. PfU (bpğ = ]DH! HtrrW.cr˦tğ?s4P:=hKluOY:ќ3@N8T%6 cnSKh  K\Osw/@˅t# 7쫚<4+bƆ?7rz݈ŧWN ^'@.=C#'sY@1@|>[C'\_aln`ֳ}w*;!Hht#!hN,pkp蚴5N_t w@a͑9oyӵ O35t^Ku'mc`=)d6??% 4?o(HHoXx/E.SYO&H`?9yb?8rS-K5Na g_F{r ?tlNýE1;pB!۝.7%]Y{17G ZZq ZZةUu^u+1]8}o'+$ :şy@+Xuw;13K{.=C#Tg99b5/8eSho}!e hYO I.tT$M(bCBmD`3@sƾ2ǰBg90˽,]Y{17G )|j[qz? y^CݲOU-\+-Vwv7Ve4Y)kn P8BH\K8 anS(# ie6lEC^@Iƈc;{,SՉSE7BHKbq#b~H`?"9bEyCl pVn`tC'[M o$ g!6LUT? s.?Iѯ !\]l g@!͑3✃FR( '_^YiAAКN< @+Pm ?!92 an}x8%aUϻCf)  /f%U4]yhIR<nYn%$BZtx @N3 K0H`?Ѓl6l(wea,ԴSBs!@Ћ^܋'Hbhl:ЋX-]mPQ ?.=?$Hw٠~3@9F-%=tIE) EkƘ+p*{6#ChWH~eZn _B?. =Ø#©40YO(V%XOBH'vl^8ib_&HF+I@RYo[z0Z%B9O KH`?М[lBg?>#ra I:'iBÉEViYٻ Ɠ"3qF3z3mWqo{~*嵑p 0wd ,м3X8e0hg8#W=-V9]v :jRșJľVcP CTKNf[T`+'rkDKbzwz07G Z40~ }"F-2tR#k.%`6;CɢsYh9JǶvArR^L!"B-mxl+)X5%eNMpʠ^x,Io59M3~_brs(披5fxvr@I &Bf8O uH`?A[h+p`$!vO;tk?_Ttd:3&}_4A? U%Ůoϭx}@DO!o-`h0+XOвL?/y\;/?:e))70A<ņ v>ń ǜw \u6%UB >'KZ ᾊ(y{ ( Ԑ/1.}"ݏnITR4u.6ܟ fc?+w0u!2 6+j4!HJ !dlj U0H`?A{E㷨ɻgDߜ4bG JsMΝT22'c-ъT[}V@"E?!ֻ/`h0*X_g3A{'x.vNW$Z ^I%DO@[z~ny=V7;'R/MU{2: BH܀qKb0-QdzpCE Zml&QLPRY M^9s*h:HT4+ 9dׂv_zĂ]|<,$_P_1?ǟAxoD\L !/J%L]-zq7E )QLPM-כn){O e- %B݋5#H9nn!*F *fTNa%N ! ) f EnH`?;Lx 'ۘ=>7qO[WmԽAm:}kz:QV7nU/N+m7ŢF ^"l $Bn f E0H`?;l&r)& (qdC[Uw? x?㏻C7wf w,{ bPРǍ,!(=dD+*~iHK !?v C@!H}8%sQo 6eMn74I4{]xuhGĪω+2]! , Gi pD/ȌO6z0<ha*0KYP:_遗ebQSY;VrlZL *}scp{F~mrJnyҗ_u?QU MpD/`HG}:7 h#&2mwf w,{ ѠǍ,Q.p(wr~*CK; tf`oa V02f @fy a_huLfS @ׅ_Y-~]O̜N:G;$|X:֤exXR\CO:P/v41lgE[_w+kCw:K.8 B.zĭw=KlTҎ7^@i6ֈI_@h0bU/c1b`RftagM9^v}jmnFQ-|80O?+Y ?4[e]2G$8 <N:b n! K(]M{?038i 'hW{!Q/4<}]v=714FTO3c}ZEf0 ay,S >_Bw<!tP!K(]z=05 )b`+Bg +z !n<@#t3NȀU`9tj I{0|%{~f13KHv׊<@͍n(wĮ(W՚@!zo. ;07s)oO#Ɵy5t^ֹ(!Yn-YNR 3v40, KL&n0NO#Ѷ1".ZQ z^ƗpUG='bW0$&@^?s?-oI&4GKxfR] c<B@G?8g'_mݛ`1QEޭ_}p΃vo/̃fT.0p `& tOm? 9U^;TzwV~.F}\u:Y]{ݣYhմ.r~_6 . {30?ӞgZo&m_qN֜8x/q2j5pL(Ǫ. {34k(9O-?ͭo>ѹ-$~rb> TOuMs`A@7]ch@G^ui-vjv+tЛnGm*M=U zZL?Fȴ `BY @`6ws}s~`f/ILZz1bMэs$-Lmח9?B31ŭ>䒫 !m$ tq! *Dq]O..c^HыEN^OnqI]կ:0b -D~C⩮UxѦ/̾'7ih]UR˸j G oWQKW fQwE^׎2_׊>/Oc`rgFs!{N.}hoXEgA#iR߰k[pzpSʻɤbv=8l` (K7(8?ר#҂f8;Ҩϻɶ <>8n@o,Z.'ub]}U_n'd\+6ho>A~cӶ4_}+/KaٷnwAE(Upl;ZT B jtBmb =rcXta.0 ɱV89DM_/f-;.`珆 ?{K;R=܀.O|)p 5o~~s> jO.I&yqjz3Ey|>ו ~ȓrD}ܒܤŻIBvF* Q'FF_fLt{}^F0Spjl%my~$><7!,NC܏H`Ge5pIe"_$x&!?aYjx;V!ՙs;=Eڥ/N V֎qo[vv 1й퇗9WI:.^Âe6sF/~tfDɌ4Zãh*6s "#mYk<;\7.4y ]yΆcw'97K۴>Ymlͻ?./4(ʻm˒caVIĿ6a?99 ^F 21}_Qi1 sOeh9gzeyw@?S#'eG{ji47jUe =ӥ{HNvim3? `m$EMe6jgD='&(S^  eE}HYc"h[Ԭ;&Ug/b5INJ0=BGƿUu/GL͆aeg28雨+ƼcP ;MӞsn ?Y 9Λ6=/V!Mؾ`JL=C*H4*(xk쿤ϰwD'u+MǜW7㤔XQ,&AܡgLZ0BpGU.6_F!7H<& ÿEul=#͸Nuujb@ҨOY>567rʢb{[vZ;6C"fOݸ qy$S/_Ɯ<5!4$C41o]nI &6 *ȴ0Ø'u&4G0K)6\ ,;51?oX_{ Ir0K0pL 5Ϭ`^eE9)Vi]l{n˰oq qϨa F'I#{o-bc8TXbdf\f7^1Ŀ?/BQ'op(zƪXrЩorD%^OZ'5ED.! -|}P?|ɧdkM-8HG\bi\|Z < ȷqz^i| LVpo8y䶰s*x+C10Lkv2ykx_0#7Go}dn&\RߍeoKڒgQTpQ_ޥ'kKl:uy|6p."9qVpoPIρ{Gߪxc1W\)c>RќTxg_ό0#RIύEfE}O9ǨOȚcW|ۉ^rŠs@ڴҸ XO+xw>Rw~6U_!f.;/3~غ|`HEZ&lRHFO7(8QQu Tz~)s X5|?EinOF~hT!Q]4zFc}CHӂ3Q]}^vsçD<'D&EUܘ_tfQ4-=x Nb޸{ gT5!L㶒HӌAE2@F KcΙupWԭN 9~|_HLr+ƍkr5#7f(#5|-^qN v˿/;2Ҥ|Ovfd%TIn%V| |ʼn$ǜ?"yDz]^6r`FԒ_c*xw_Ʒ"m֡cQ,VQ'߹u}◗G5qXqF7O﫝\*xiG5C"QOL뉳4P0^w7L/ IyP엃O #.i^;;<'fFEqB(+9+ZxFǦh0BaI,^ܻ<}eG_UDq48R6~1侬C Ambon>oGmްؕ`BaW'WZ 14S| ?fh~aSůbOyd9x2;l.a[Es#CF=2~Z~O9}!R~AD18;bgwssIN?^[?JٿuIm--K-$K{ )> _@9 $̙OLP~tVbߝ7p=y{v" [޶NaT㽟⪿/{ !dCWKjV=t<8o F1/c?WۊV9jbaY܊pJ?2x,>UA|0U|  WM/{*lZSXfP1Pֆ1QtGZ-~+#tr(+Ixc bO6(zޏ?<'6m\Yނ2zhjɤ(#WG=ː'=Eh4iKëlU}wz׸:l!:]zc~Ŷ}Y9ey gʮM!ϛrRS%8^-E//w?_P(ĭ4gÒ3,C^#GǤ}m{yG#ܽ/ͅ|k̂I1lS5Ï=(w|-rFvnyяǓh5*EHbV1OQw㞿G3oGCQ߯W=^6>˲)N-ӳ5*n_O( ?m]>ϙ_`?d84e =f0^bg{q !+;';rm3sCM9}>ĜkX5} Cߪ:_:>%Uۖz^&JxC'C^>]rOc~i#w:eWWt n!R5 l-OSB t߀sJ?\ ݈CCٺ6|O4s?j ]-oƙ?DC=?;#.ogEֱ]t\qv܈飿7iZIէ.CO !Sڀl̾0'j?ZT]~_I34lB_ͯ^{ڈAv55e6?.\&L"V9ٴ_ :K ϊS_FY)֧GψN斓K#Hf űxD>Ư%i=N}cG]̓͢<\v8zZ<}\Bw#Mƅxq{!H^(>f{K~woiļ/ЈZn<9o}K~Y΄Ý[VxZ_2?K!dZ#L/h[x+&u7 Hڰaoc>4٩u߫ ;-mo_HT .U![}_ZD{ck-K@m87yQ4 ~Ҁ dBHe6giD[hD s}Mᢈhd 73s2"?"f ©a ɉߺԂRprMGbxFŕso>idZmk7u%ذP΅VdD9rf/sE R5$e"pnpqxv9eB^_-Қc^ؚr1w݁Q@Z QAQQѯR,HQ tQ@T+*/bi*(ҕ&r{w$\6{Iɾٹyo|R L5 fٰY)Ð?m6G _ ea&)Ll(OtP%阵?I#} .t3d|{qmu."Qԏfvu7S,:_,x~+k|=!&zC3eh:O(&ɭnY.rc̠Ɏo[#< sQT|\e귺lKV7JE "4vT|X/]L̄k }w-ίfc־yyӭyV,(x-#GFn{F4xfgIπp|d y`XϹcs(\|t%L~d{UtjΗb׋Ÿ*%eZ+-R1XS(JaǕ7qJ)*juhQCGJ&xevTQ@wPT޷'샵'_z4[. ?\P?M:q,vT7DɖB(ȼQ˹7mk,NC(aY>D+rL,mupzАɧ'q)V@Z_])x?\\ʪ)I[[̴ֱgCN0)y\V.sWv?ZՄ~}m==}u %кh"Nk8TFnw+nVk([ @[A %݉)=!Qiϯ4E(v?7: jX]T%.&&b/o-?2,l"} Y]*V[t:$`0wŲ.|})* ٻh=H!1F,rZΨ>ed&&BknGD鸏ٹ TX :w/իMl>c1Ÿ|b잎o?bӌP/zq* l|/>`qpKťR6"I$ȄveYo\[1Q{Gq:Yt=\oS-ETBfҗzO4z:ٳ0Z[Z[QWr\BDcNF٤nyH4YӒjTw'bI?ښ&7/CL&},wkxxArw'>nsOۦBGhd SfBO6@WzIzM!L/G')|7侬~ll1%;J!{YGG%sdnRyaVvg 7ܜB8 &v)t1i}ӒkZn\% g~(:.R$R?g"g&D{X?h=!)W^BR:}֤%&u/"VG=a}%3ϯVr5,iZU銿y,FC0_i2^/7[ ?R^":d'Q.G}*R0[{Hvt-}8\v2qIF!8f%.fe q:aRB/$s?=G>a*IpiVn9 ɽkB'E;;5nv K|{s%(uvLTOOŮ!;#\E|>3t3]e[Uc#,uTs;z:_A y3 "^s(EaD=و kIl"7l;)ŖٓOtd"naԓP1!z=h1@ +נ2q1)N__ƹ#|MpEri~|k&ne>%z@2< }ʎ[/y&!p0b LkbR6jE8!DƩz6%X5whBG5W*ggT".U(R+sٿ8?sK!'5şrd6qW?>R*DPi ו\ͧ6B]ʩdۻ q[\s (#b !=VV2:~ɺ`~%d@=Q3.lJ`kޛCpਫ ̿ȻBŒ{yw7GƎ&o{cNҬ.a_W1Pj_d?Hs|7-K"IN|I~Ž|]YxΰL?wX}^Tuϥȏa}[r&~~7q%$ɹ>#4ғǹnKXjټD]ߞq$>]pTKhCԍo2v>k|^!3L} [ØQRGH]h"-pTm(s*|s$k@qH%+\.p ٷH w9wJd <`Ul $Ls3$ó:+݆h ;ri >3Hnɏ` ] G72 ^tHe[:LwJw}XfmY,3.|),(Kk;[krn9ݷ-45\oiY됟N1 q[0gM]IJ? wtX[0/<&|eȥQ te۹!gm^}ΏiiKDt_0姀}{"Q?ȃLЊB Kc%c~vWTXq.cT}ME*qVe!N%ȕs,Ȣ([O+_p'd ;CY8#@h7ϣOϥg!"}0?rVB1(ֽVH̏K@)mr8+ZB3n5ǡF K_o|l`:?K!}>]MB=u~+ëvz }NG3vToQ|]ZADw䃽V\̀kTO:4WQZϼ=2'vW| Ҵ fD6ί^z?s~ם!_zXΫ}ʴ9~kiE~$b}6*UPH{BGic8L߹-D+G +̉f$0]kz6aD4hָ.2JlcKcL9 K>[捖Gݤ #+xJ^NdmDT/3r=%; &/8CDi&oi )p6׋5^֒K18 ؚIG?|tpH;Vd~yق3>3o?<΀Žk<{{`[Y'k,.,W': 3YFc.* K[nehcZKdxXOΏc mWGܵJXlmc O؛9z ǔ=\J)=|T.XJg -5Xlm)*/g4 t6*u*]V:t1kF4W.b-vSGu m.@FlPilkX7k# 4`ħ 2i[_\~?i~;_ Ƞw_-E)w4-'{󸅻En2]_n[Cλ,ԯm?&c[u&q^2*c`^J rDvfԪhVȳNg:(/3TZh=v;%U ߞg}ϔ;qF)JqoJmM6m|~T+, sBO<LJدL`]D>2K?|Aɡk>b1/E|}d-}Nx5Qh[UQ/W]Hΰ,"Fȣe}d4ޚRD~ubܥ-/UHP{q.۬;ݚ @F]}{JFK`>_TpeߺR~1u\~y-}5zD0n,r`JpZ < (*߄`}ax] NK]²#a!1lni1R8X WorQwLiJ4NZFW:D]!DkU&n||=4o1EA@[UiD"rgҏ]ҿ̕`ESH2K{]#!}9j?2,ɋT`"Ŷ#UDS? yJ\HMO]϶6pc6i {\y}U!1~/N}%w3ҲlvKu{G4Hs D5956=꼈)|h9mw i נe0;>hUl?Q*K9ΆZgy퀃WE7|l |PAsKJC\@ƨIDMRUc%OШ+DtyΛ>߇AQ<4/;V <\M>J`c̷OJDڡҘ`ɆIm\y_##IꪟOvy1]ڠ6w|~ur/n{["VeVw< :P&\yey締 $P'7^]ev˄+m-UZJj1aAoI*pK_Qܞek P0-۲PT 7M/\jl<)gƝzv@C7_cX~Bud ;zp%x۩R:uQksD$ '4;~G݀pE< ,]v'۱? Y:_ YPCYZɡJҗzde?UQ gx^)^]"WyQy]F)Eng/n+t$kbǷE > 1.]A[FWM㥵 \݇e}C zN|ט/q^@M>|Ƣ_ c.W,ZHӓt= nOK KO[>g5víg .7AK˼ VI9r oݷ;,U X ˘o; U'F7W*/CNak$N?|x1$|7ёh ގV 8S(s{d\'P(oL^=J&E8}%L%"jco 68cP.n8FDd;{:ݤ6dg%̇7*YN$Ɇx2s0|6ձE!DLBAs QsRjNģv3$(R9I\_`cꜩuz~4IS*wu6s{[^_Ǻb5^Wo^`YBsu& +ct'VW9+΅`Xy  бj+ݪ],"h4[@); s}iE 3m/LCg@DL v՜/8ɵXy(7#$"E^ܻ==q,-J_ʺl4x!*j&qEwjAOQ]r~4I:`*1s>Io8b4CfP+B\yY,%qeAKM(3׌ WN0":{^kZG,}2ۡjx ~'3zm*'߅P `gPNL %y3#69Vs q3vwj"^o:DHbnPK*č; W:11$ND\A^f`> _]KBȔTG ,7Oi %=6'ryq|=bGtArCQi}v.Ln:(\k,/{f2GZ D*\ePʽi<\^xSVh}ےi+w)ҝsG9KBҐп6RҼM|sz<УƘ)`&[E2[*b{`ogR]> # NMݮ..CFB O!Q 9t֩~5#7~3xF yڋ~"P̎{KmJoNBSd-k<ԷHԘĽ{w..%"Ͱ Y*pg/"LtQ?m˾iazSoZ˸Lȼ"1v>`iwr̿>!yU9d쉨ttVOٱ,b>~-- ѣ:#/ !q}Laݫ2"CqcXلkuno""}ޫdׄ%1&=K5$"A-6;r+$'=%O7u9LXJD@ҟKɛ 19FDT.hǡJVP=ek8LX LD-UUNx sIV7oſy@|yLsBjg֏z똃^021,NBT6f2Zr9C2HD4= 53o_Cu@W*DRv~v nʅEŏL'"18Bo DDܽ)3$CK2a3b8NJgDD4Ec{$z-Ap+l0gO2I#sxZWLt!ЫDDCt}L_9`A_DDռ?}ƖULlezJC#x](p?LInac %ok.Ojs3EeRPgs͛Y"c6*C%r鿯xJGu`24wt ]- pMs0XJCuN=iTFB AG"Y6<9DX#zAfl#R' &QjviX3SO"""jIz^""pٮqtO{Ӓ%_0&x ~B C+_̄K*#ZqҖ/{&d)\e~8;0jDDr^V?^ q wV71O8dzịd Q2DHx'"4j6 9p`V%1?w"}.{]cbV}I^ o *&. kI]*NdDDtO=®+^5U>R9CZo3a[ /ـ72 #=Cć-=ݒƑ0(e)h`|]LjޗFb<@2L;H:? )7xUL[8ƒ.lBToZa%Nw7ew M DR&2+v_E%n 㨵(KDF,!J¥|ͷ$ n/=N(=]6%׾{q-t &N {*eMD4,QKl#crd՚vy\Mď[& HE ] kh$pn]c"wܜerխ@du")DM3ܔہ(Xk?qy4".&Vr[l #V0RO$\%\q:2YÓwFfmLJ؉댛L Zfp-Oӣdh~he/UP1DTKxH, Bx8М҃#];gl Xl*WaքhT.`<υBZBԝJVQ5ty_?zC9ց&:Dpo""@Dg{'F |*9^̙NDE|Q cܛtah=a;P'0MY~.rz눈ZqteZ۔w}`|jF\,#A"GJD5DŪqߎtoq% 厘6U)٩\eYsKĜsaT@ˣx fDDgyI gRj7ƮIpsG}1S:zr5ZYySC Dgs"4Oo_x:P3{!^<8pGnfVϐ:v9X;LܘC,%QU}L)dr;\|S3E1 w "y>]GB-"ZwH4eѦI(jCU&ewW"7C/B "{~*/{"K} *C5P=,c >*[E-vQ-o#&p:E[_h'ŚoU݀{:{i\E PVޥFO̕X*lHXQy"]ɾ>W߶|mvOg`ʻLc_fdv-ȫ˯VN7$5fp"A6:Y6*?Ij]dV"btg` ֖$s W0 "Q˦vG65$US-YD[3gzhlH/:J0Qb ,W1w ({S]X5KoJAoXW;Qv 2{>'}m\HDDml[=FxDDb#(mCP!(?BGݓ;éV',?ŵn/ƻ7 S=p")M[[m$Kjrs 2fFnӬgxcuܝ?x"t{;D+ί/_mbC 3 kD B*Ё nSrJQxWBM"TN Ey:ѽ 9lj:\[gR?:5P?ޢKQ4/>U I!}@0FIo'kp#꓍S)`)>&~@EhR܍U+_"6vd]&CfݚEQA%V*+ ͳbM`qf`JBkeug{#"*1>AD%;dI~Q|zVbQ݁)wŻS\I I?3+q$IsMq\eZK$/ p=g?Os"j;T+{lU1oL˕hIztdeOo= ހODD'BD:2.#$J&!LjʡZ aVaWS~*4 a/*;%PvKqoS""{~&C搴 U ^YUuVJD,KVLweO|xn}JwS ϰa]!yܢo,bH?n$?`KXg|ƛ2NwnD@E`PCjDC=P͏!b@e;'Ka[xhȜ-?%bG?,8z7a!ں02+l7fn O{2`I7)r5,:H 1_K#l{n \bf*lZ}D٨y-G,'"j$Tm KTq[W]+ػ=#e7CBdI٫>D]Hzg ұr1oPtYZBo;)3`_,N%yPhLK0t {^j ZtH6tY_;͠ƶ+J# @pnxŗ᪐TGbM:ۂo-v w aWSBra{b0c_kt0闘uUgcOfx"RFZ"":h:f=F<̋-j&{W&A.,w:ٷg жɈߟgyٮ{ҜlTI]hH+Ud^.0c$"xXCwDSqI?%[Ck$$ҭ{D5DDCg2\\Ջ9%&-ю|C?WY1G{=%Xɾq~%˜|vʼx#DD,< æQYFGb-هaDD@<2ruB^0n_g_1m{1ɯ͙? {ʲ\%SIJ! Y42oʸ v^%_z8GnaϗG $Z =8@ŧW]!y=+D͜|L|H$1b=ODRS 11QzeOW>[U;$ (r=0bWfw(Sgh)S@5l/ԿJyt_߹(Rv>b@ڣB/9C>\yG!ŀ|Gs" 30x3IDlr8}Rnn8SH>2/"B A+a^Gy㈈֚9t/_Hrg {;0!_^؃̍3\Q IWcsH(!MJ5͍J,ՃupC uLsLDpL-S='g빢F6{j<`(<<'/_eu o#e*K>?m+Ҳ:̌SF'"wX]1-_Ux MɗAD>%g6fgC9G N*$USZ"pGѸ<3=\Oj5+w-$)..2h_!zl-UVBoANM w{-A$e(S!$kb/UY3li7D䘜ʫm01 {Y#}4 4# ,?R7")i<طm*ӌa$"B Hu#wl$#Tښ3lͫ^C '"zO3iD+)ywDD oI*'Ƞy>|(}U:%qrU%&]ZѼw~?)엺`B8h7K#Y&6ƞ7hj" ]6Qư8,hpwjDz|M$;@=\ag2jM ࿑u [rRj)*˽= X.x@_#>"&"4k1y) 0ۍt["G;-`E\±,ܿXaFf널'GC=lr8nFH_7DZ8e [lgJ;S 'd26Bdfd |YDr'<qtQbIDY^19@39V9RoQV=9g{ /mhRqou_~W֜ngFqO1mB0@9G`<fE,`|\@PrD每u7`B颛(Ʃ/"z$~r3(x [+VAXb>;h)(teJݎg F'sDs]i}X~sS "Zhiu֕TG_-JH2Nƕv=朎WZif}^_IS8dm ̦r py/2m7ow ph|4h$AqDHڬaf/kF^?\ ';;RU_)XmB٬Dd*nVgW+q鼪dm+ܣ7w!(B҅ pv0 `ŖaJ_yH=,"":b^?QV:˧`/!VcL;ߩGؠςDC1rL""*c/H2ⷰTg¤6Q3?`s~6:I;tޗҁ:!7>[bɶP R $~1DDW¡!0EU'AGcZ1VG[""V1_;Xr]UZ>ǝT(9#e\UIEߒM:IXcP94-rY =۹j+p8s|n|CY`Dg`ϟ~06(]RƟ.v3 y:ߞDQT|QSϚ~3eqN4EŠV;ư375@F]T ggG*>C*zhvjL.G_Tvb3CDIޖ<Lt \}ugijI-e`ǞA3%O} l+a}??5h/2J89#X #4 ݩ7mU'AWqqi/JȌ#hLnIᩁ(sliD$< 82M= i5f1{=K>}][Z0_>+Fv.(.w~?3PK7bMQz,VU',GqjwBA3 pX.YԣhtS 4IDRф-U %r{ ]"Q37cwO.\>#̚,{YpɮˁC(s%G|Gk1¯fRʴ [Δ^g@򝩖X=F%vQ6't^W ѥnӚEF7}ې-y2l=IDR 1^Z"撧㚻?jX[}Op ( u:+T߮p$\5&aH tt`@{5+ppRjཁ/eḒSns:]1G!WuGOL<|$Π+t< { ␧cU<2 $i#3 @6%q$h㴟5.DA#U]2)+uQh n߿,J`W__^ 4Xᅳ0 l+DG=JZ@ɽ-cWr]>">@]*V,x/]^)\x @o4q` }ZlPᒈ[ vHcQD&"J,j?KH*QѶAɕ?ԏR]Mz2|JTIqJ֪y%(fBR&DᾟpBO Qcs 6.q .w:NjyȽ8{I\uZWc( RkyheaƤsPtYls{^H~0wjl [yu)h%:AB5knGw,#Z,%*U6]~Q7{죚Ch/6˲Cȝ`NA97Tju4>M{a~٤ $>~gf@g1^Hx$cWm AFc OD)bkp"Fy{23Bd6L$M,&e 哿ޣ+'pFZZ7~*%3%U WvX D&zlڷƣ %l0@'r"Jذ G `\9})02(|='cH]YŢtxO.xbUofE:$է!v"{mM:I:CjbqG7QG6B#6Wwoi AR:3QzVM'"W˗ B4nnaO>G;Pq:\copTM{8b멺5P  #DT_I ZU@{:u fuADOݣhg$BF+}3YED-X]V]GȈ/GmWM7"pST* V "*w0="7dhDSe s}RDD7x oB^s:0cbn ( ;_n\w\E-z5|z\6X<<kBXef嵗"!"ꞙ19 _E ⶦ} b=A5`HDQ2%o,Hr5j%-7qB^hv񰟘DDO3Y ݱu59 gtU8(K'h͐Ur6,9"$폹tȽ`BudG'R=Es:df|o{`|k@\cQ(#+LX4E(o7`o< FehY ߘOD&e XSن5d>Mx_:#`9 ^bJ bJ%gfx]NnI17dXQ[Us~ ?c:$yP NTj~^8TKC#]M~'6V1Xm-hsNDD)U'"CeZRr/Q҅JTWv)dsLX(ŃQ>p_SY^KqfɴcSvIU!]q7mlAˆvԯ_<}]j¥t]?gq*!A,gʙLttYP'o\k*e&gQ0%iL(L>Smdٯnтc sH#DwkVd:>̽@U+ї^.< O]5|w=+[GAKB o U RKH r:""W:Q u&"rM _E`QQF^gl7 {ԃ:e^:}*OrQg]F~n }MDW rO_.jkW8B.ڗ`ܭg#GR(|\=Z.p&a-gvbB\skRZ;CaW1.|T̍5ǹʬHRl@9b qn/p˭[t1ꮼܙd>˺|m8,?|%O^ky!3Ը8\sk*!Ʉh'` Lc<Z@3*"cRa@_2kт g058rdR gٞEH룷IDD@Up]r;vQ4E:V9 +T]qnH_aիk!a^pdR#OgR 7c`?KD4sVG3 !82vJ`Q!s}BRvdzID4gpD479hVb>GGxC@Y[+ !8V晼T/~iEU7$}G'* k%g:(;2H(YQ"f0"@AATL93*fQ`B1Drݙݽ}?zBrwNޮUVK)#bL$6 =?_ivS{o'"_eQ7,IbaKA)R[/$}j%y ǿI։g<$g! >)i3xUNyDo#BtȏHMt08*&? c4:gRgS?p̴Ć}{܍c30 Qjն>Ϫ.yjH*OWʐ?7*xZ7%78TGn+^34ßgHffYl Y l<'wZ?&wzġeh9Tk J;DU*M"WxR~ n`P;-i jQD,d^ViQ?EDHd9Gk'$B06p`xHd]G+rH)֋̉'gv0"\b_ 吱'2.Jk,%! Wߡo@sRc϶g(gJ6tmG6/oz; U޷vחE7$wITp`wZ6eCkIR h?? EkB'Czs-2 ." MN{߸0Kp4>^% Kl@+ ʒ)f@5akmt8 #Og_̬ (ͦ!ZJ>6wxoTjէ"@p:sՕwJGIw_XoHRo?z˟n7^g=w{ɜ*{n0+K;ZB%'}OBj%"6LNmдh4/bPIb D@( t3XϏ&F:67Oo&tu^ 8x>.v dJ9%!᮲\;efYt%+2r~#U>rȹ:5WZ*yA_{;v-R(rkJ@m4M$ ~1ɼq;6he=:bk*ہ̰t@IJ }ʴR\ 0_Gтw?#-YX)ctIna1YI;RZ:az+]igic./g^mG6]UtN.F 0̌NΎxs{F`dۅNN53pVv I/O.XDi[HRO4Q v z>-b KhǚEf7󮶬U~2Lj8Ba߹ѐ)$|*_1U~ k拐vA{-p@&XD?bpޣ. ^\`fZő5Sj"4NsR@'|u&+ Ft[? U"wg 7%RSJK}'ewѿcJү$y9R.Vub7405쬠2e% iF;cb#[Oj/HߏX~C#ݙpgYcekI2"1Ğ&a cUsPE&)a_<ѭ'W9ݧ+T裲킊G`n̈{1\wlGHI`3GL+0YeH,,tlhR;*{T$3[!GVƅJ& Tʭ&2epx؎6džF;Hh/ğ5n .;m8ٱY>{{L\9N֯rH.Q9<^J۳ZsyޛyLԫ@rFB5/aD=\]*cQw(k%!_|@\FmV}m]F'՚$dz@7XMHXN|*;W<]bwV_)϶=D78v?W2^1JX0|H} y`:A,f7_Dh$8ȾꔟN)K{$ 8m xfz]Ref:;-z..Prj&obxAM@@ʺQ2s&h~F6c8"6i$G,VAzB Drֹ@~s1!e  &nCBv jWYE _(0t0jF?O+%uudB; H+FLMxbf!vY egn\g$YGdB0l7[#\dRMnhTh!iݾ5 3IRo`3Cɀ2-wķ; :ȒוF.Y<q"&AH4OW,afOᏑfmýzP}IӠ(,M~k2\ZxoX Yz,$o$9(-~X>Az"o |Tg~rqkڷΰTrGn}~K^D @GU݁}MU~ITzVΤ;@R>X~9fs OД̓O)4,LX\-T{̺NN~޴O K8SH)& bc\<~Hk\#Yr zwI2[8WeQ|`u> \Lua/~qKW@'簀jCCvyDɼI:Ed{ Q ;H2bF׍Et+2 >ݤ#g%Wq@+Ĝ u; KQ16Cd[(L]̸A;WU՞ |LPʘ@7XT|`6 -ʬkTjW3g L=mQ ]1@#@K|B8vp:tR .:47d$aHnVn225a=ݯ0$̤Q+_WY _˖_2B:L%L˧wKHSa5mt$YWlYD:^O7j@ |+dMḲA280dX<ɋՏ+YwU$puAsDI"Գ|v8J_v=\D4 ڷKV8^wV{&:Y-*%>EUR/HNxԺ+Ij &@Ƭ.zS,U}ޘY &K7.Nݭs!"~KdQ93D`fO"D Q2`&H 8a- U2:.2byF@8+\Uzy p>/X@/`\ h'/ՀQ8K?Rֳ, T0c H#p2HHF]PB Tkіu|d3pup5BF ]6sD6c2= {}m@OV/dxnR70$KCrfURГ/{ $}D$̷yyC%I'k%`_ː%O=G]d?Iw֩sT;߬~M<)?S _ƶUL0B#^W[;$sY*.˿P.ט v%;xrxJD +sdAꮒmؼG Yj8Z ׍֢P? Nru;h^eѿX ATSA$!OLV ^v>4@jL&y%r#w ɦD ^Ճ 8200.0I /#1;YHF՝c fmd1^n{zmq ;?3 oC˻Z[p](,en&Jo.bSB_ ߲ZSu{ paEw.3.~1t`C6tZNtTԒtjp)VepuC2JpP^@HhRA;(MW#ɬ';"?at\@- dw&QH(ݲM7H͕}'ya){UEe'XYޡ-]~Q2q5kw)ѹ;wEҎPoSWafcѧV\nd<*hpp0Ƙo r/ep`j BdP]A_Wi^R7X9:%;N1K4?p@0ahNiwIL1CTi曫AqzaD*=H+EA*YW':I {fuP$=NT`1@l҈<8KH:Ä!SyvKt>+?j&N=!ll4O?<^/"GS)ZRkO`h(@_xNl/!cəS<~^̊#pBůHcˠd7'UH3K4r/ 嫱?5} /0vEVm &3k;}7d3hKPHwӌ]&Hg64Nx2m#zG<V@L%ݘ_lx1xHtjA h^i>_}yOv^vbX8K@]?ݡ@=㣍^fx46 eө; _j?hO&orgAO2@ KVdoefeD/H'g+oဿݧ|Q$}:;ngg!+~}BuHFhB!J)J`((uܛƗWx/wV]v\KAs3%yUAq8xZK& 4I2`y?X9mºUݨa=L3\+hAPI^cRu]`r95 x/(㊯;XBnHi)t+tV=?6QZ? Yd^]" 꺑yA)-W|j G+ s/Wm/5t#ɬir>K1= &/6J~ pV,."/8?>]ƃpp^s?'K`]zqOI1 Τ!Rt}Rz/fsy 檜m7o|,&U ȚX%+<x75n[[8FPk4e lK3<^|㓄* ot/Mf^9[@z|B/.l ;嫻0]9c !iŽcwzOL5`-pp4d6( G?_{焽B"/?/f*+ք` ~sf5[9D#65탁bY0cr'\Q 1֬ JӸIrnΛcdɒ2`4|8`f_`8"#<g4x@ޏ3oj V|P`xhuf~S%›aNkm\؎VRZ$E @DtK ѯXfv@_2ZO mfq=I ~P,y_5~z5yPEnW\8c#3۟j)I/9fSQ`'$>X,][G lpt$5u"b Wcr/>T6ɷ0 4ug 1.^n|$ޜiQ2Yђfs|g0k꫖c43,uz<!`?M~I&ҳaIMK =~=k\)<7X 4{;97#c@6Eq+QvM[AB iWI2Fҁ.?"INMM [Ƙy#^u#@}"sf8`}$wv ENkru2Škd`;Ϯn 1Śâ) N+ӨZa#7=#Ňb!vC'&=բ{S!_{\,2EqW0 gI6 @4F3{Gy@(O뮶W7pUh\Lη?|qi& ZgPb|,e,ڭf8|#] K|*fH+3"]]-3ptix~7 6zsq02'Im]V8_+YJoK$&̔7c5~CCqDžҿF (@([2llY<)>#^>lުAHlQ({9L@d#ROTG0J7^{ 3P] }\wM C|:I5Lr "1?xĝnt#3tbujI!i/UrSVqJU7GԸ#WZ[TIYnǃJ9S(c r~vhmt01{߼3d_)S 肒sN_+R@) \zF XƂ]\5_o,mpTۦS߸]C22J6ϼGWfȄAj|3|۽`Be1>wv\. [% ӖTf[@Zx['?E?>Hy N@kHkJInMRQWYF{t?Pw'WYL o1{Ӗ8VY9q"o$ xN @cʺ)^?\. B$/pVȍ&-r,oe#WɕVfA51 '!˳D켣 TvL*cwXp_U_z7辶2g[j2c#Wl ɛYR =~]q=o*q$nZ*GBy1lғstN"i;)~< oMh䫊?U~>`)v]m)9d>')ś)u [cʙo)2ƫM,G_ckFpwCҬ]sl5K >&Fݖqdp|@$pMd\@&i$y!s溙ygjF-d\^Ӆ&#$"8-ukG(+/S0 Z}LX(`+IN ŎƊe$x$qT@%ҙow%ZyF?fEXz̙%*'᯽ hH倆Xφyh. ajgEN |W.{#s݃)Ŏ, nt4jr6bMˣ7Ōb\$KstN<0 i$WFo+ڷl.*0b\Qa/fV7H2T_*W,ts-t 0q/]$ț, '( ſ!TW.s}1SlC>9\}?g=ϙW[jؾVͫKBw|VqܮJglugCn9|F| MV? E~JZl߅ UL1@g~Y<#]Khw  .|l,HʑhQg fv~M_|4,zO[߬au|0a\7&H2i^)ZJ̾ ŸC! t (P\- z@3d$TFeEW(#}e懼<9*h| W I_q$CY+&?]Z;`@A4g%k=;zl/Y/Iʰ2 dؼߌ[<) { TYLhȅv߇s`Q?RjclʂR8kv^ZkXԪ{t #IL654b=zJj˸66-znDI35KS~BN gqKQdNc?6ĕp`llg}UB09$H~R JI2<Xta c9c@N"ʀpH2ZOcY(^!;s>`!=H. ֿ.L!{DP`'$CkU!aȈx HJ) UgP4 mno `Ƭщ]f5$J;@-[$E< _@dꪹƓhM ARlR5[G>qS ttOe>̟516dP. SIО6nL{݈O:Yzq[|n?s Xl$<`j8 Ӡֺl?7jπضӤ `pT$8G]iǠq1_&m4jR`3w;{H]k V9 r:>hATa࿎fp;aSuQ1Y]9\'I xHAP-<,MM'ӥ2{2)A_CL'Kj`7p x>MU4m$? zש O^_{9~p*i,r爐h>|QFGG I4X:tj(? /᠟9Yh;5:–}Nz.IԚsN)U{z*?G'p>Jg\jt_M)=LlGg1{z.FQZ$`q.Pdn Ol4?_rru/Q[QQ]I v@/c\M@XKY,R%O)G&j2d'([ \l8@g=|?Wt_CIPW`mA˜>o߻xdDL(&ERnGZ)^w qrHDv40Ҙ8.,"z `xRm)Je&D"`` H9`T/ӻxOl]kv Jv)%$4g "|YlPh7HruOt,#̫RI ~nNKqKj -R'#J_R_^}dD)i/(~?s?ty7c3{>Tm'bL@' 3u\/YNiN$]gd9I28i,jDOn ofA `FfbKv C٧X= ;OqwYbZ5Q KClۖtd! bk.G Kɀpj4䷍Ӑsc/\.%\K~_H3=Ij^lrКh/苷JJ㢠v8pN]+- kj͍O\mؔ=%USn_]WRsKN Rϊ{L%0?.όJѾx}]x4 ` u:JX-*O n?ѯy7#o@}^WVx49-|h(= WCv6vzOPБ|i(ɼ`@(b9V{H5:1J/V.kpɟ/ׁ¶Ҷ Ց rw2Cta?QzDwڱ#;_?H2z~F+N'?p6HNmдh4O6ܖ"ÒA*L1X{r)U{{{%/W^|?Άn?Y_hst9_jF$h*Aj  ȼcm-mdwdc,Sr*[e:Yj8@EUd~Gc~B$Er& Ǵ-dV =׆ Yx+ө)nP -@5] [IFdWWE},pÅ@Pq$m Q̽)X _8OC9ytki[I2 8jM HL);U_[5#]ϛ:cS:x=[<sq<ڠFxM-q-6\C}IJHzRl`;|U > Dy.+A$ۼt̫/l-O}3&_[l-V-sMՕb0܋͍R@1̚vjȹOImRm'luyVqt޽A;0J_)L 8Ɩ,#?ϔNm~eThMq;τ<{[83Z`{&Fd`e[$oE֌ʛ&miZ ժOۢ^7ՂD$^}ȁ6@]I \P՗X@lekMDcB$Fd\ow8Dd5aC4ǘx+;#j$_)~zC6B2Zg c P}AaO6g94\Cy0wAz\ÕI^;_@Ro/)ɴD]ۉWY"LԚG$p|^MTڌ~rO<؟m2wI^vX )kיǝ7@1dIS  JOzU<<@K-]ڞzbpPf? )X;V#sfv@Ive tcd{x hh5R+7y0ӲJɨX$oщpǤ/UMZjt,5Φest뛘6;DKrgTGieL>U.%JvgW@`}wRa㷦ICY-\[r<)]KG/r/tT]&+_J ~vFND1x2 O2r/*o9Gyt!cEidKJ ?vu][  *厖IsלďJ,C8G?4d"{jy? )Qc4'6,34  |ņ)uYM=)QFu'=2LrRiJzI,F,LeڦvDsz7:Kz&'z6=M7oǟFH=G dղfQ:\JMxFrQxs-t2CqBuv#/Zʼn6[=pzT b22mH'|ߠk]7_6n-72g4׋/V%#c*/Y9 b?Ԧ)3b$L Aas3=s6T<)]nonZ*|5p3U\2z|ɬDbn1ZKdZ^sf#|۷/_K8YZ\ rk$QC ' ;ۯc=^oӀVTΠ Yr wmt)>zg"+D^A9Mӷɼ7=o/KL_~dsbJ%T7WF[]Y;p[}f Q2x-[3-ʔuO~wM) Dۭ@C%PQm_c q[IJKnK}%aVy"f-[O$4N8G>Ki'GxyX[,m/8*8 )u:F-KIH(ڏHo73T r(C䫷)]YWz0vJOp=ԝkB|'p@tf JݝTgT(! ceMd/z%ةޝWEu.y1 % 2dlePߋywCՆ/GqoYZ<%`qpa$k"6pYQa দڳ_d#LG0{ȚA%Tʩ[ 8z Ŷlz3>"xd\Oe \/l<~ֵ^/)Jbۑ{Z'WX)>7* WO Aeuf-CyJyߒP_mm|EGtbM0b x,VbY/R ,xU̚=k"uC$-Wl_IIEGK m<|` TggWcD,چY5J@m5>T.#}Ve E{;`o 20•jDlWP wjM' ·zP$=ˁ;8#}CE yAKLO~ڦaN`k=vگTqEtT2D:@Q}p{Ȯ {8j1@/#3s$ϖz%]ug1\-5 3}k  Nō+BMS~Q&*Qc;n̛cwW++uKI{򹥝Kg^\~&AWZ%2VP}z8ZX6k!Kgid[fX<`Xw_YegCy$K?,km긢D6;=<1Ue$_3Z~<2ڧ+ U6d.aKkB .s>+ɜh/G v>^O;P8|+ a"nB^iQ} }u xӁ֟S)w&Z3W:+DFl)s aO!#hǖ=t=Fl\?㨬,X!'^;1b %Ypsx+BMT#f;f&Zl/כ%7BE?_ƐGaM%V$bn?hx#g"|7>F,ԡmi IV${7lR÷5X+Yk+E5 j ْν8f. gN>#l z`fDf{'A~)3#" "$e)JQNQd'씯5 O䕬^@8'"qZ|;6;M\Q}Ȍ3dZ ;|v0@t_Sn{s^ިľ 2պ=\bC|?{(7xr^˟yNm\5d&.($/GZ TLq*֠O~0nP?D2xc}2t.FD.#jIπ$pl"3ACFB7i̧V/auStZ)+z;$ w\~4vd״JH%]'*ʇ%ue&E#̯J`O!~ lo9: x} @s$$3̀.e`+0HA#,ǚ".脎F%6媀\T@LojO À]޻:iS N< }rڹ"Hc\~Ƕ M(B2ط wE?e4n6;?+9pa)(Yv5+(Ufeb/7l:'OAZuW/kxi_`n(%Hzc3jq؈BbBT=ϛ!4Q.6Z~~P[)$ 86Yn&vpg+pL mITG#/.Kr~Za/+0+[ rFΫUWe*# VT\3qZ $CP2~t|ɲN;=^ \ڳGÛ.MvN!W_dHxZQe NNWc4r߬@fc !ͅ:|OFo .#`F(e|HڦO%c7k~,!^{}u%mnXhDۧZq5TEkRvx||7fLC'֝ciWƼ~ۏt^.6qy>߂}ҩ]D]#ѓ%\n\kbDtfm== Ã:W̏HAm? `X(xĴwtVR|?\Q<[;,-v3=j-H@Жgljl0DĐ}ϒ,J+"J6) I*ҞRDEv3޽f̛w~{s>gP~nl` )UХ|dBս?鑃5//09>K椋\tR\RJeO`O|u@~{ H'"TW|k$Hֹ>M2?q"lW Uݪ?PO pj)&}kQ;'ѿʊQS J\&BncW6F8NQ`{Ϗ\~Ӛc *BAvLcP7;U\0%PA ?BU{m+]nSPM,}* ". 3}? $2K]K*$uo֯y2)by,S2U$JF[A p4/" b@^ $_+I IԇBn͔Z*%h0*Djа= y'+I:pTU|-l/'jdΧO~eM&)Sfox dOӞw˟F$hd0rDBf:U77: X>ea՟ D|_@Q%lW1X گF \Ɣ"aZVQ❛f }_,""$"q ^ ᝴GHq0(U|QT@W1ӷ|OWa܂ q7@s&"^UEorX0/RԲt-nzFtd}Ya\*TصAYolPz*ĝ!ڋN%ul%B_3(ΞŐd#[+ۦ7@} 9U0&N"-L*!4EzlF T|m\DDD` 'r(#f0wݷn9U3=ɦUdԖ:._[ݬDOdHVWW"c9"acί-2M$zZK`)io +zFv.j k_o 8NQ}_+HÝ*fC' w_jR86V@88 +Uh Ԗ%x~u`#됧Zb2'K?@D 3Cn*YDDWExoDM?U֚p:q.U#TbO#M]S6J$ey+^ \"5(}+f#p @3a~]9SO@[{*ءQ 8{1VUDrjRFŽy{z_m;YA[Iw#"qoD`j98ݬWޤPOy.Iag$ N,!)W;AD?0qӵH|R!XOSTj O#>%fI P{ EUMȱؚG}DDRIbˋ@`X%CyeEY$wߡcLy<'6hdpLɐ h8Jì,"":{ÏJ^ r]~NF16(<eլ Ғ&(%zo>nl\±f*aKݏ'4'<.x{B8yVxqlN{ZyHR( Ts1Ϭ[}觽RWųy:ۦ37{x٧egD*UkJ_VUzc8ԏS->bCCjLF"qL=FRSrX/6:..3Iq}ſeT )1@`B}ڔk 8?% Y[4{rb\GDon6~"nSI{zvzv#WPH0 T?YoW[e{A+%:;oN@=+^"7sM ̢ V+?vD`oQɥ ^ VԼ k=F(XNk - ~h?[5?B|ewև/tN?1Nن= U>  s*\ tևj%OI. )w1vŝ""=zou) AH[*Htr-e/["?% ENKGt '!ʚs`p%-:UIKUP(V@,N6cr{CČ.Uz#Y qJrUlvX7yG&G wBz4ԶWcd1~9dڥV\+w> \ADZ[_s&YYFDJ 4vm]Bĭ yOM|h_~և#zL*Z2cK7TYn"7WZdY GT~WVǞPi9]#^Rsif+.{Rl'L27Y,^tǫmjMNHˊY'ђ:}l𲏄J :@$Hޙbk?%@6P4*Ej%F*h74 XaeaO ѹ̊Q g'1+~xf\b ܅%-K^|J΀1$;h;OCj w_ 4PKjҙ+d=23,DD>"1ɷ!@'Uހ0ϛJ/hS'8-1gyNbTpv<2A/>+\&MD|=CJ"h\mvt v#l`C"Kleb7Y~C[*$CVY&TtdMBJNB 6kў%8]y~x"pi|s`fT3m  mz6/HS`F#ҔI74ʊft][qxX\@::xe*)/KEs0x3f\ ѹʻ9+@ },-q5`nz>==gf9PϷ5`copOɾK? +<)h~B77 TmϽVIiPqM~"]T` R;ŏZusC%0M">1"lvlv},`*ZT^N!Z,a밌sK_ ȇC^|c'9\9<0QɥOYdW[*h[ʸ a0H^(ne+4b$JZ  0mh@Kt*i˂XgT 2)e 潜O .DUP_yhvYW~ՇDD(\ {-ŧ1=T{jx?{:[{+X\-N" g x) b^u8.0AZBٷ_" ;RJ^?r!ʓSb>uů8. ]kՍvcP3ctN8|n&^`;%>ZYKA=N(R˒bB[Q$)@*E' RR@NNgIJyrtwĚET;0u3H[ڞZj;2uWuTWI\/*P+u)ϲ3ĤX*.q"]<7sf!9+ us[u[*h 15پ9o1=Qfiٶ>#} k Nvzc?Q횖')ݢ7[?#I&TB/Uo.Q./[rPu0/8"g. \v o[1?Z*~n 7Sc|Ckq*[f&9x6GDK u1ɖC3u^[Ign""?pœ+h#, Uadyw!mMrGc 0OTc-yHղwO#tndzy?k( rnÖekT(h8!\C`ﰧ( UǟC}T;r?if&Ss^4ߧ薪)B@A*#{R)iuQH<)N{E䛘l`|d41ZPe o6_dt so*o.o}7by$1pv P+}aຢ[ɱ`V;4u=p5sxi3#E\@N0AԽEI٠"g0G~/72SC2*}!7 WG.t??7nuc03G[#>LGo5V/L@s2 _`Qh\D#=D g$O]A.IUi.܈aI*oa٭*@Պpx+cGN9=PJNQ&R2rf(fWsLg5,~DL`+x@KԴOYaC6'#9E:J2**̂Sh+?d}J!{P}[/VWPY1&๳(.Cwp/YQm l@&ΕɄ&2_4'/tW#YK~*%Szw&\0^,笮zHSC0gێ܄v2Rvil_):YBۦFYOn=-V`Oa?lhFЪ${uE+QfOQfwz oVq 0s^/$=[ \;D#Bxel}4bgD%(aO)/ܶg4kMQܡ) epSJA M >3GxXPa@N O/2GyM%&T7M8;8F-Pg DD"`Y f {RXL;P%cB Qs֬J§"{3/%Fټ/i>^_=(ÖqŝڪL>@<_U9}6SF>632(\80:^âU r ަrDRRX38CDs_A#>gUůU)'sN2#IL^(þ0O(݅ |'Wpym5`Iz*2,PJb? ( "10'uG#u7q.{o}:Ğsj[QHc]^zpmh?kЂ-2R!6='~ wq"Zg߂% kӃZUKq*6%]`\oXL.[Qs|Sě{ĺw[cg "HoLNbm E( 3l0+'l "|97)CK`P;{xx_DQ=yZͭ%}Ōlp2G[P+13bp)`X8Ճ&<aBU};{ > Y$R m]E!3&[{e[6:ьU˝4Q |Z7/gh,(3Xp%WnełW r[o$Fu;(3=2{C0qux՗M66r zj) 0bI4=ǛU[z0>[Tb>›Ss9ҊD.$7'Rg Cw)ufj[J2$~YN1ss;}Dy#&s"|#tvI0^6 K4 (~>kZ†`tW:D7aBuq$ 5? w%iAGiZUzyzuluE.c'wg9mix$*ZRtVt219/Y6S YDDo&7 8!-^)u@S:Yk$0)-t3W<2$[ϸq :D`l>1Z(7>':8N㫝Y(_S0#`7kl^TtIn /y AqFaP?3[RQ▫7GEA%nL g8^VV4p/,m"SOu+)h#o%Fv#@F"2%p${8ED䝅4x9d# Pr.~\y'6 $o+gW:?~4#crG{mE'[B "6Gq6\D!,}@3{|o"hs3OH¢lEF'g*&re`|Hq&x> _35Rx~@Tψeo}rk .Xyur)"qB"ƹVtO6wc]op#Tߒ"ki_En=j 9"<Y#"eR[@wb dmQ}[c˧֓Q| joeLBd4|U@TT}Znm/ ֤>n+)ġ!.IDDc fe^`ਯп)>%z2$2 I&RV:pv潇b m0|!F4ybFL=?!1ZkQ:RNnNk\WKXRb:""CMɡMrna<:2`BeHw':t?6:fKH_Z:H 7paE&衈hIzYœ Yfv%ރzP>a CZ6}ϒ@a0?koɲS#SK{^ؽ.u 2Ӹ.X[uZ3!oҊJ 5BRqetU}">5h3tT}K.(q4n^Mǫ2$#EbgD+nLx)wseBaԷOi;.5G%WۜODk+B )U#SI}.Kfh1{>vpPWP 䫛%$<&g}C15z<xCw伊{k`䦽b]y"3W#7-4YxKwVM^`@`\r 7:2):e$\S 8aṪO~\A=d p¼$P:gbg4a{Hzȥ`jBtS_%Hֺ J\Q?T `t4Cir {eE%{YyXM O(phR> NW"h=wIMt"^/SL0[{pX<}&#:~6J2mUhNW9l`y"Mq-vy&T.bh׹$sM5!N2yn+ `dlӕ\f&0 ‹A`dh>v l%ohȶ%oǛ,X/0_;w:d@v@!"Y89O䂗FeRv۩J3@u.^4V2y 宔oV6"K(**Բ/[@0jcT#*=֜1W*Kb6' Q`$w|99+0fP ,6<;N9'vnIH֞>FCSOp"RxLW3jDDזObYpw9y5 ɖM(M0#`D1MUd48ȱ(hnJsl?P)z lXnH[eTbL2q@}`޲9"WW3Nͱ@ p?+̂p .2\L{3`zQWFɾEFw|7?½ 2K{::>\+E-"%nY~;%7'z:BOpjVhdD0zS*n pv_V(6O='/iVX-0@@% G4}T DD0>,C<=RS;J^HΙ +}? Y9o5i_𝹇@)" %J `YH3&(!rJ@}R'aZ{_4x/٪\f  m,Cip+?=F>Kj+4+biZj|fXFO8Ol":Vw?)ӹEAx`!y*geTr5<)ZK+FY>lRm (qNXP@%%!htxMƈ_nUaVj e]r=Vױ2ϰ*˥|wKD)QspO0?,9 (Q")Az_[|"OL'Z֋73heD~z_l)ŏ|$fvuhV^Ē,R=B0}ϳf?D˪6;+)diA~/i}a_$he^[;\I -Mi\{ȗ~!1nUx^:iC ׇsܚ+GbVy(Tp4]åd.Xc'#2Tvf0Q‡ʍd{JНVof-XhhB=ŠZ[fQF dނ`@=ً}DЌ%=;<{7o/bIJhvB/^B qdpedywPHV_Ie/1{N~`9L?pTw,>I&u6x;R,|i)5Cw\:`6[q lQ>jGܪe.n":įKdY&N\RѾ0p]-gdO4sB5Ym$Vx J[6wo% &h1k5ʖ/CvioINR|*I)d1OeRg:8/QjfV_\G,? yqϺyI\X־/řF~ y QVCz.)W:zoĵ9|H5l%@{g,7ɐqp&$h\JCEj~;,\:@`(Xwe.u>h6uџ1l~@ i&dah3{X òdò u8L;ͶtsRp?P#R:%CO*Dt xos#taVٺ}ot孠;ھ`)Y˶G:[-mj*En\V  cwRgL^V!65lܲ5JBfr[Ån6gM•>{aQdʳ[DT]|ܵ&UgeK._%*n& b9@&2G=8*AW2Nr@$a9ϛŤKk2D m0c{"QeEN:.rN:* _@D2L1+#_4]Y BqZ3ٻ|$R4)-URzh+/R{;į:wkJԶlHǹɪl;SYQ]~SRp}(7J[M4Vк$p-2mHf@?y[*NPBl$"|e{I3 hS;KCq6A-jc{6ۥR*;YAr9۲-§ehR|y"iTMqjFFQK]`z:ZKV=pp@,Sb Gf# H&eAu`(T.t]g8;xkdYKD3E/)Z~9g?7H Ԓe=s@=X\mˊSRg6NLhdzB'ɫ^gFpp2܆?zYt~o<]Dr[vH7?7y;c\h^&ފd_CDyI;/X/ݐu xR:rU0k Ti8/| jQ.˜ki$`c v sU ғ'J"O_m_V~Ys 7"kX`'+&Odr)]zp #ſcxцD GD!"Wwީk6eJls`iwO138 ʀӱPAlf <[DDlR/xS?&1Bf5.0"p~ "%L1Qg "JKBsR JmQ&Hd}j0V?/ L;[|s`М I9kzL&y D{C@r6FEeav\#_׳0ڈ^^Tlr|#72a-6ʋF* [5!R^-Et o[\<&@7ڨui/)FNS yTs *g4^8EL 2ΜX~k U`B_/-~Zon<8`%NՓu;\}ǻBTVq@}Ou0clk3>(Q%na//züd}g9neBg_R"[)0@t IZi2 PKVLmJD+m-(3e}[@Z 2?Q3YI/`ul&=U"`vע|"Suv0\ 97OO`9T&ŘFT  Ix$Px"""eF fSexnG+gݜQa~ri 78rQzVy 8O=Wp [8/ +lo<$[}eOsBG#)#Ӻ;n0T3˓/]  pX){MJ;HvRDt%cZi9[$߭Ә gvv0ha%t'"odj &c/^lIFB%hy~w2ē}ޓOzDDas`;tPz22hҌlexdNNſ%o侹(4QF"*yh>!$ ƛQ?rJe/hϞƚA>4!$"#KIf|'@f'ŨsZd5@`]3Lֿf܍#ӪӉdjMZ9R8AClp2Y2OD)? .m2/d[&qIz&8jwO6<lt2T'"Y71*)b/ϖXPF4ɤ*? 5b5KYIxyUD3bBF)KC9 n=]8CqU5X C͂Bx#y*Ad׫WF$N89e/ڏ"}de4#k#P/D[cVF.0"#|DW#!D+fL  ]8G%1 /"S@ ~OD쾆X?U Tr !+l |#Fg9yc/fnLv# h5c`VWchxpNmZQq"¡M JRYn{ 8+~~Dcp(ɉGme?+-gm@o(Sm-P &[Cv}nP<ȹxwdha@+c;|E5c"‰p9:XyGkp 4H7"# r{ D졁wWUs>3OT$"KM9pxs$:ZQ˞f .ڈ\N""J{}7"Z1:ɀoط,+c*9KZ6`w?A/#l ;(Ky2l QYT\uR:(K]6Wh$hؑgPo2˧{t& 9׳ܨF:}Y>0Ubyֿ; ǂ5 }N}nR;7/> d+:QP,qn#0mMQIDsk+w66$cA@sEJI껈X\C w%|fa쯚3X$ɭE'YMǼ0xW+gE*5w6+Wˢm4\LnaeQmijCQ^LuiQqi(\= bŞ Ò{L{E} J4D+E%!w3 f#"J& "ʪA|[Xv95/ h Vn59 q< { 3m5kz5k9W|_\ѹy{쵹TrYQ7RC[I6$} cr6 fˌag'ZdU{Ζp$'`gWdOG X2٪_P5gRn7CZWDD&}'D2c,"~?ɛ5D!\^(]4L`Z5 עS.XOasQO^Ιy,x$~4YYg$(Q!P{%7}˙>;TQZ[BÔ ߊ2:Fq 5C$}GD({s/eC"C='P~u (#R%F0OrZwpz]͜h\>ۏZW\#:I9ng+XI֕rX~N?i4F3d+~^."CRd7Ю`; ;4*f$.vTNdψUPݔ6P-$|i+GYbgE1P[:4f 6(up&=]^]UҧZ12 IeGPƟ8"sJEԗ#GK(֢=\-&?遲sn4:CV2X*z3>m q^UV3GC1-V&Z;U>˷ 6!uCiǙVa#\߹z}ySzc5&%B$]D*+WIͤ:CoOh2Fo G[LS""WsZCXm&UܜeVѢM [Ѿ-<"",sR`@ԌyL E_ne~-1+a.ޝʛ$y W9QϱB>V'fA~tGWCx2HRөxL= MH\Nw]T(M c~/շJq˒Qe;TiDT6zf.p::Dwq2lTOf>>>콽 Ȭsr~dD[UA.hD[pL(Z%R>p޺6˳ ¨S!` C lp/QwaCg}+ g:`QTEahL,!gaNk/:OUg l K@HD+7W+?}孨"5ѩڨ"$ dOğSEU~suR(ŋҧ_+ƭ1tqlnYJgI˔ާrv*n_rHx?0o@Os-j fS9^ҌڗroGFQZHn6k[*Zai٣#"[e@BăG(}Qhc#]X~Wܭh?:hU_Rܯ<'f_R$n]w8G301.PZrc^UAImK 7孯k`4f]5pe *gP_}pC9S&"r&xf &hg-TUFnJL#"rurEdkb(\=@uxy9DDW\aF@{Z>$$!_ )0Hd fnO U\ 8Es #rM3{{i˒]ss7JBv|k+RRL1kb mg -ڹ6 />LX# Z"p+..^2yɻݲob fYv  њ=̺q.pb?Y$zC$'"W=ӾDn= 6i&qJ:sDbNɵ^:2:ѫ0\]`,dMdFI `} 䖯ON`eWdP"('plQ{2`j#5Uy°n]nn!o vtM/S[[&pE 4{TLf>5zcYIrBn7V{Q'H)԰ꚜwnNSgx O;0]/ٓ$@8*")bwpW5pF3H'u=Y%U b}36 \YLT1"mPc:n>^/7Km@L^_dR3@m*Coq0\e,}l%Q5.K`>T#HKZU0Dm"f >gdbeQz{kv jC_ߩ>ԏlUL>p.?Z2};[Y6!vlNWkDr#~EW= 0; NY[xZ<~$|p L XM:nK:8`F{:U@q  #1&es@ʷͻX#@}5p=\r DD-*-KUک]T+@$Ϡ?62؏RPF9H;3H0,Uj! [3ODX z@ʞb֔] )Q#Mژ`?gF Z|dj[.q>35[Qkw1Q*}{$KiNk-;f#}==  [A'NiEt5S"[ k߲/Vi`7<0\YK5V/N?1K> _9 3gS966nH=b ZB5).P"dRf&fz.Өh :*QI04mS~'Wpp6S& I8Nk &P?  ZQs KPS/r>r-pg]Êz6Nwj*6W+&Dxv wvK +sn|T(2V@޲\ -LݟN4&$-|ld[6u^CG?=y;P%ITKE7ljAse_Lu]Ӡ|Q fۏ?7Lr7={ $hHq}v>(<pJlN)˧ cֳΘ6H.6ɮM:7#eVٻ)&.q9xHFQTTĄDDsN`DČOD $6^[ߏٝۮ? 33S=]^ať® RVGAlfH>*+lQr{Cޟ(xKaK"RqJ͎7UܫúT?@3gpniȒ>Mmb; ?+@'z؄+F3&6GDGy;+ {˥mWPanFO84eϋK6.ݽq3{;"gi ,/"E"+k@{#KztVc'͝UpERX8fnrQA#[)uY]Qh~7XFM&{ p駅R֡5:'^a<`^DZP(ߒL2@&WT:&`܏Ĺzv4H|OGAQ<-?/ XY'FeȮZa\A=-+D{u}‶~^Mb\oЕ+# b ٘+fx.b؉d^0C!h!?D0dEhv~(G^".ak]` i18_|v,GQ{/2J@y3#MfH q<2]| 8ooq\Sz)ldĆƌf[dƿ&?|':X%̲RUCx].Y/]u>{0KQbDn7QΜ*AΖ˩.e*۫M$Ew[ΠRy:1D}Y^umB6VևO/0_lYM1:6mtZ5{ܫ!*Kim1ZR[ ȑq&45JBx}6tO"g9| Ȼ4mp `Z_ gf,0p0"dYXY''tj,Q*eѿZhc]s^`*%YvfHz.0^dԴ u#r$w /:b VёXba>B]w5:i"ܷ2pL%Fm;cfV3d?_X=d~/3~T3vg@P!%? c0Rt p:<' EPijqh94Z9%m6ha6K[v6gG`?ENALJwJ+@3' H=`0n3]WV8+ y>=kf_*nW/SpVfU21o~9]']م\Oھu|]^C6SyDj;G=Їj"d3(G:>nmq߆JdG^!w:/Ǯ;G_aFWoSZ5ky >CB؍-#$a@9Vnj7%8wD`vDD.p5Gt6ixV&4pR HZ$̩b @rɻ[Ѳo'}ɚ3@R:.p{$z<`n&;Z.߲ +J"+e/C%DɨhN^>>(&v;&癟*PM]bZ7~'SD'E d]tw7ncSz[Έ,S0;w16wnz +P-KWWnVR2BiJB nΙ9V!./DX2qQUdStxԏ*"Z*k+qkWr_87gn38΋J+u뢓C@#N*v} ')sOmYSW~[UaEZ13Uspޒj gKF2bCr>K]D9Wی#ErmڨA#&fʦ|6jT_߰.FRDE[-Q[`w؄wW[KD(Um[oϡ`IJOu Gmv ZڊW,fu8""Ooq1lvt oΚLmipI@Ѷr |?MwBM;Vt #@*=fq yWP,1ܴSw>}OA:FgS*)PI>W3!H{SIՎHA+o#8Pٜ-v:94bқ.hG\q&\̘mU&ʞ]ߚOd/;q7G3E-cknە.9(Y?hiݿqhOUI{y]m FFGǠ5 (lj~ҙ *\zWⅣBjv 9L""yE`XJQͥL|ꒃ 1Sc4)2j3:ʂup?F/P@Ǡ/dǬ_ٟ~-, [ b""W-$[h9e+O+wi=miU."Kt/M ã#GE1J<%(Lڨa6w罛~qjl%s8o|HE(HoEv7Tc|O8hpٿ:vr "rD`Jō"Z\r,'"rVp @kVN󊐚Fr@'xKr_Xa|ߥ@D/b>0Lsꣿ ?OF,{F,:/cqq;"ߋKdcuED3FhIDLEO5f)d'?d4d_EO/Օo[s[ɳ:u #<{5_:G\Sr`פ XaVՐf1[SgpFP-%Ӽ'tTa_kE=U{'z_)>XpyBS4ϣT uK9z|5v^+_'pT>"JͿP*?4>|e͖ڞm4^9/ptW>ZJ\RPgϪ˔ׁJiΆ ^:Ey#j2\~dٍ\ s)yՌQ4 Vcfb85J(O\p B?V1{'0a%a([g}GE?\k%a߲Kt x"o-<vrw]5Y@m6>>p9mcP+.^BL?_'1Ӡ Uf]8.4tSSؕƢ-IK_dDG,Ilt\T)n|ccZljf } uKiIJpS9cxeӠn+Y?q)m]4QS(\ݿ7ju?&lK9Iow} vgX͎"i{,T@݆r,98Ֆ銏|/S,O}4,6c=Mk]T/C`Viyg ՠ p#0άbOYKxc`yw-}ED!f->q~fN`R|dmO)]it yA/z>(}=Qm1Фo>] <|?f W }&t`5lUH}h`!6y p[0@,:uPzZ0L s|p6G|)f>>,v6xpA9xǜx= /xKު)SI oV;^u4pNF/ymݹJ%$bzى᠋gijb凖دO9] LIp9ewW@'na)_ʃ/Q RL pfF_*L'"*{Ǫ V?G"`e)6CNuc3w>ojs!-P4&Z5rZ&P7UZ.p0GNk'ieipAJ@2^$Ϳ?%{=x9LִٖC[x ɧi3Ŵ;wbK2#moh)lΟO@VyL⛝/7~xQm!M ֗Na%7ؿ9rpz4oI˾ہk\L&nz!6{_P-r)yls+|@t0.," 2\=:ܟp9j 5pVb|2h̸ 95D)br%z&(ʞvgdh]7\ўH>Mml13JDMs;T{Nk9ASxnKW)z+\l9×ixQmS\+FGL 3qd HdHQ#I.T-":@jg99Ļju̾v\ޒ/s ډna.mYJ\Fo\mUV]_9h"f6* iN-<݌-i `efՖ~?v5x޼D@K!)|*_?` `IDT*$;l  mI@ue%{~l =tEEFģϰ~Go71a->ŗ>"rK#>}0 E';3Ns5bޜ{'_ { UKiGcvTzԐC ~WCVGb4U4@ҟfŢ@䥙W5 ?YKʦ5-]J)[ئZo?aOGx8$r4Z4*=DDJeWLb XpAo?6'"R]W*|8!K 9CH>MھYRb&vM'%8UsC31YנG|T^cFJe /k.H""dF\~!MDm!Fs*g[=rf}LɮnPnd$FY,fu4AfY;nZt*]*ݼnz%=!49Y^ yn=񎬠Mբ_F;Z7Ы,[gu4!3tZhxl2;bwމ(Pٲ\j/(M?R :+\.KiϬh~8xM4 J[Y μsl542LC*7ixqmZx[bZfF$fMlY?Y76IŸy6l鞪y @mP:H򸛳N~ PNI{yJܨS`_nh81L6[dj+!wnsq{6gv ZݵCnv_x:w iVC-Aʻܬ.gl0+~f$@ܰj}<_Z\n3^Zғi$|g (ښI7xQ \IKebl{*{YU+NN ψXWY) ̖.g"x^ k3*"ߚ's_@%gET $m縲 E?Q-/Xo!zcfF[78 Cc@HDVZMCU.g7חu༴ڽ9d)*X. 6B wt~#B9s cKuOc5鮔1FGo9/x^ BÌ3g]mij[3ðS\5|ُ):QZ؝ (sz1v= l )b(1kz :k}o3fe}'[;Ā*alzcd>Ef'/m-lc`h7StLdP)'3 @} cL~$"B4aKcĬ(/ds3 Ivai/L`e単7??{AiսIlj'7^O^9ѵT#2"CrݽKDd9G^\Ĝ2kG]RX~ Z^xׄjN탵5`g'쉖lA[IDOx6n6?vSg:\.G)C ?p\=#Vg3Ѥ0UbI_u^(PҒUwx663-T{WʻwZ+a|MM`kɚrɸ<ߜWyD]$`^Wu[Qٳk7l ڼTy`xĬ@-O)eu~# @R2)2ݪ4)^6!O:[:T"Sn~G$`;vw){x^S! sQ1i}A+.z~j3$rU?_0OQwh 76Pa$NHi8Jkʴzɋϵ ^mMZ4;iOZmiV!+'JlϘUB;/R)z!>nl!'hFHpS/SzP~c9O+ TM'EF$Cy$c} TJٕ{QF;g+h>*Q#o'{m9oM6K',6'ж8U1!kU֧1h|{#.XA NHzڀ1꥿{ P%V!=nHskviG*x'a|T /pj{<ǂA{Dao`S:.&$ׁ>rA10K.5N𣋘l-)b1>.bZ!~D.1td[f[[]~xQmNs+w7I,|GX^MQ^}BFD cO$^eQe~ҿObOU9eSe`tEG~`S6( e Mq@T?i;*'کڈ~w=ݖ7Gr~_P@Dt0ls&Eʈw~mgߛIDKѓx.kTP+{,mX xu'Ϳl"N+23@jǔ39D@=Ao?YNR649\P~ҷKW(}O"6U V[ ۍj7>O_Šќv ͹0Jvf.G:B*Y8FOUӭ3SX΢E{)?z8܃7ḟ/.L4C<y@԰耚eFgGP@͙6n+!UȞU!"|{ Q޳%)ڶ׈fd04瓽ɪFw* OކF'g8?Web3զ`6ܕ>!5@O{VFΙJx"{)*h)Ǣ\f%VsyoQ:rOH7 `TI4wTHF!=˭v5vsow0WAݨ)U78Iޫ jiE3]GS4%SW' m]I@erOfE:DsgǤԏ~v; -/BX&]N9Ŋg"*X^q^c:Y8wp/>{Osps~P_V9جk`7*;QrPc @}`  CE/MOr]%HL>pgF$z42#3HX%^oXGNEU^4EkaxQmNݣaJ\mނR/#C<#Ԙ3<n"tuAm!#?J@LUkU:Ss6Yt>pȥ*ѡgP\)0ډc$fJ:rjMy|K /#BRa1sh؞[ ٫ҽlQE4+4f[̾]xVNɬ%+Yg G:%D 0l}/ȳN8TNRQa]M*}D/B7{x_y}]bIRND9[Am"*ٰGqqt+I %W_DeZ ~uҰlr^%pBƑ`g̷Mj;]Gy&$뢌`5f\:(Ơ鼅UkaE^))f L{ fڃL>M]T0\^27J@:k? 6i#kf/ఞh/v p)-娛g`oy#!h w.f+O}D=1KC-Hy(Xknr%vOZa`/^ G7R ujE2KAob yQdKYk}ytY!8V`5:6H[BDD5vo\Cvr cƥ 2E\OJcLH@*=|:prIxX;# Iًdo!#*G](kWH.t} B&c=.7>c73xgi0xl&$j}&/#i5 +܅F2EH+'IC$Oco@0{J׺xOKƾ dBݫ6~(96pZۻ}84r$E[2"es#ŔQАė'pkH׃=bT[κ)hX&7f?Χ!] ^0Ɵ}8ED+ꭚI\ 1G˯N0;O AF=vj-'/#?FZl Y{(Kӽ$2 p˒S~a 5)ߢ%"w{=,r0T[KSvQXawrۓ@V6O7S10׺Nn2 8:0+!lTog9~:ȴ0.=տ5Iǐ'wu1>Τv+$ DCůtqH*͜|eq*)7y{IDE UT0RDCS RdDè~OXkN6J~ރx)he5g+XQ x'IԽ&93\uBlX^  w`GxI"RPd;GS0?Z3[2.[qe1wm7T_aLMoK~9ܣ+f@\Ml2[{o}1)aID~w@^SFܐݼ #Z=ސY4@uQw;rksEYF|:^mҾ_7#+۫ggL ț u3J-k: ZmS! FEXg kh}  D-3If&A5ud^ v%RJ]]e hZ`;0V h,u4<.p i:pcʸ̹4U{0DK,+|?I>h>/{ξm/.Sw:ίA#8? vn/U־YsR8gA2CU⇱U BcD4U-j A~gPQ+֥oGεE ^m^<'fVUXGl~f Gu)xV^fR @N*T?H6}X%ެus4K2l'&?]: U6"ŔӀ5Ml[p]GP1-[]^9]Uם0Kl 5' uUp GK Vy%."v@􀐥͕ 'ް'^p4ۿX QT5 T`{\͞>lrDH0"F_ĆߗUAÇjQ{1&`3VQ཯.}8 MA$ou=1GDSU=}!(1UsٹGǴϷ=:ocmk?SnG9KO YNʛ0l-=#9?DFG*W}fnsɎbWtʭY ~ָۢYR%ƚaP_MatYZrMM?oiϳsYˁaOf%EmvTLwXNNvqQмDE=qh J̥o]C5.UJ'_>_(7 D~f弞QA/<휢8gY/wkDJw7GjVYr'>y3@ ?asYbݶ@#AUѲkep~_)\T[GTz@zjk%"Ĕ1!Vs%32-2FĔJ ɉ\.ane@]/O87|1S‰_b Aļ/>/ "G{XH$FtP {o3'*44&{ʏ7/ogN?#2>7뵐,ͳXW^F{zoh^cTG̈,(F _(P5/I;e~Plh];m>&WU~* [MTpBD1d/^\\)ЖZ sVDd+$-T:S~1bxsiMFUlSU~:A9ois3 bM`o7QLAo)D:!9z?{p""rHr( /Kٗr:ெ1wcN zXɴj q|<^, O2F(Wc:6"^kXz@D<; tFPUog,ϓ!i}T?}",,FVq]wf9ޢHӠ3̾5gn)BCJ|X~Ox2PeΖFp,jdG~*#{s"FJƻ)NdODT|kި1.Pl9 PY6M93Mw~]! iӑMAy_HG4yxN3BnpxBuH%UŹ'bmY;wj_D }?+HƫGE~2et ULQxr]Rusĩ [|. H8^ΖpY h::}!gv3g=o]'mz`ѿ] V <bʘa|jM74AxVVeSsctgd~%"J%5c8xj伓YJe=Aw*PKݵ2(Ƶ3q?"JK#/Y[ߑ?KN/ QA/A?G9_鈛!"<-^&:BHc^M{5)M#cufcxHt;=DnҝQ *d'V9Ț9ޜM,2X(o#x:<8TW솭 yn}eW]H-!VE*f֝bQzm2_Ķ/ļ rN[3l*4~ Lѿ/Qmk&N=nAt` gH3Mj Uu4~ W9=}"_ *{`(1YHLKut ƽx+#A-TAnM.xQm}5QRsky\T\ p]P>b6:wJD oz{KJgD'[6MyUjh-(giuG%{ -Z uo 2atY~۶/2ܪS3г dJ,w5uɓQ үgʈhN5m ̢|gRBwNj͐K!S:wh.':rTc= %o dh(OXNeKDiUopSWFdu$_~$:Az4lIM1xgX&.-;< {0C,h5U}'ͦ:mcfBr*zY\e͌|zv3KXX/5#ۤ?}H@ ggL 8ovw=`@IޭV0Y)!/+JkyIi1ٲ }h] .gbe L+Euj5Tڂ.IJz@W':Rv @{Œ> T5Fzr翬Xe,ѫoh_ /^cT0ca1κQk6tqFٯ}KJ}~ >1zEFX WK._m7w>*RGdNԗ3-L81;zKɔ.14R3<˶}zYꮱ>Y| z(Q Z Zȱ >WVί/S , )v֏Y CDsU/)ҲI|X+:CEF~5^a﷉oQ^)^cT203 _56֕XrIٍهN"b,"oشhHh0Q94Toprl!5ޞ F1k3Nfny}q\)-(B )ΕVN3+L*xJ9JCH69+jLY0?W,ʦBVٵp))O LjNZbG_s盜_T!~d G`ɳ3T?@Brql*#OfT0|u>M%sD%pcʻu5? 1ExɁ@)r',.S{(MDW0YxxQm-i²֕-u+itjX-P#k T<VXicX6{\7?>abʠ߲$"_`30o{D{e%)Lߙk&r\q X0S4&ڞA3U^" i:V\&"4au]$-l/b4'&,>ż23ޒɚ-b]Y-j"a4U7#˓ BS:`kk \$h1)CWfWN{:P{^ vY 6Շ/lzoGa0=j׷TNp}+[7&MOjVUM $k<JyJ؎. T<@*c9D_V8+>/]3_*Lj?o1x.uu3(`7!X kY%֯.;2 #{^ K(Ws*P8* ui@Dq^dŬ ab{m!-魵??Z ߑ V#CSݞ a1|-dzˁ뾥~r6`dz)tuD5'(|7ΧxxrI:EiWDDE[T4^}nQN_HKu5TE0̹yemg8]Y{5⣗p>]y2 qYJa[L#2=1IwS-k0K -Jd C-67g`tR8H$w(MuSx>|6?.25: '8Ek2i!a? OCPLDq`5l"]N\FYOx@ǬX;'QKCygfw_iYOZPt s"87_%ިMa(Ø^QO6Wm3 VdxF@h]- Wyc軏 :(}M"2Myq.Н;^98+kT89Ni1|- )|4HʙDDsmߋe=xF_}m#: WM이@i""k)Q"Cӳ*/|EÒD>dْmA{6fFsizDgR?v6m{ .-ҘqDi .2_}AWā )W~D I˕ȉ;ɉd暖CÞX VӧQ5sYe"Tx>=uMdIG)=\!@vԪZ+zE[9.jc:zm~#\O U1|<$:I -)ڻZR#&>U빩̴W},GM?w/b)^ KxJ"$#7u v+DjEMrCG͍OL5ObZ;t1x8Q]ZZ[-D)ƟI?tD` 2Ij=7 =ss5/y "(b!9u⽟6V([sk3B i$ᶆ0xkB^R_]˫YD+%?5]sq`L Wgcfo[gn)f 3ePv$7갃 oLB"i_]]nE>a0ev:k)deDC5'z)2ż|UH;$or5yR$W D3J 1 .sLF͞c+{H9iQ"! 1 ʾp9쯓īRNre CS-fByzԳ9[~ ,SNg(e2TSUaVǬ(X&oWK@쳌5IM&dX~$\fgżk${ɞ-]Zokt^>z*F1I )*$\5"A9{","<3nʶ M0", L*p1WA=xt5'kdUM~W] wJ|a!#k1Q,ިG#u#l2n &NyD|4(zK(jz5,yZ岮6ƪ}3FrI$Cr93?LH;?B`INÔѪYoKB֝1oxq$e`>IYȥϐ ~vhSU'QpĎm @bA+ \IVį{l{oo__H,#js힐h"mjP]5rjD~41fV#)峮NѲ}]Iك#,L1K ^F͸ Gq; B!$loC+w𵖮A7s5랿 I?4@S,[Q9"}=^_{%g*Gwwҭ()!" (6X JHRux{wϷ3͹3svô5cCSn)4!Qe@!zx m rft6^^_/aO7򡯅({V:HJIɕX˧vA; OkΆoڒ/U"|r]˅3vر,2$/*\lD<}*GkGU>-Ob^_U߷h/rq)5R})`eGӽ6}%0͹CJvz~xjQ2<STnm YrujcdTc**,BBQDNQ3xp^_0I_B8:*^tAc7+c&]f%])=Bx%z7y$x@]1jv" pNt-Hp/nر $4,U2 ʈ {Ux7!tDh47y%R0mO[?nWzy(mP m\'uc'#мt@$4 q6U:8P~\Jg.HS 'Teuy9ALQ6,՗ۯg0ƶƞ_BQwwP;8>'2s-};c[pC$B4q__) x۔5*iI}:uWlCkzuѸcy%;&+K;2f/Uu?8 Z&DB/ftD JWF]ҠgB ?H <[u>ډ{yV|qQepdc9Jiح~e9V~89+NkQnήN[/@z`R}iBc;qm/%PY;Vwgki"M1z3FzvmαX0Vgz3j~9[J}F!J㒦=:=s84ڴX#Ju5/VE6`1Yi&GI^q1]S=,J\T܃SsŜ ˆ`Pe ^/70~}_-'ڑ.XE.@dOIt,~ *+$BmT_.]'^\]^ZO?Gk]/]W@P;{jSߟB9umRI^!5tf&(SٞaPI'kPj*H<|0g {)z c"KZkk{;w옇}俫dG1]x~6kF!b5(?sԓ;Cmie#խLeqceT; oܥ&r7wx%*Iͨ+I&Wߩh,6g'd,}C`ui^Ώʔ2-=R}aBDΝ'RUַ+Y9?DMʰZ WМe)7nvɅ|;C$=f_>ds*e:+Bėw'C=G/fܹ(GWtͦhjHj>I;M}EHÅ~o䥳] !(T6m2ܐ${Wƛ\]y+t>ղ`ņ̛HH~aS#}ƥ:]qz[KE1!4(RL\W{o#=N)Y_ʗ}K+?vHTvɧpAo=QekƠJ,e8j$_\ $= 2>CY?̯|aU~zy*M2 w1=tkgHb;]rKZVkSKg & %|_LL›\]y[ԡ2մj: [A!R~k-] չ}sxb!!Vמ]T_A=?q-clRY7*nZYibFS('$v`}g\.0H!dm~R#I :|!xVI-MkV._HwLn!13I%=x,kLc0wx3•ȓ}:OY(, qu3@dZ 5Ie:vֽs˄z)p2LuxÅ˥-0_b&SZK)&x_S.ɼE@w8Oڪ@;Gb/lZ3e}<^ʫ|-fֶA<)cbuDJ*0FI\! n5 7&aYgY쒜kdts! |5  QHEA9G?W}jO鶘B3I򟃶9Qө۰w-MQ{kX4~}m$!'%w߼#3sR-r_n9=Uq˥始5;^$TDUgm#I`MH~auRi$a`3+.2&i/?d9z_:-A#3D  3YЎzg+#B91CB;4e x,<0$ 3gW(H?I@1S' -#Oٕx)^+X]3COi(( զdu@AK;vw~P 2u&__.%3BT!TCALA9 5#YyO}aeDe;b/,oruA-7/Sk(uV5 B>іrf弲ZO*]޳]yk/PdkS7!a9/2c+/dq⛞@ig[?ɴXBB&8ƚo)Hϵ*0+6/mcgOI(q6/O毲o&X-/^P4X&~!xlE"e ySF \#\[˗/B(O(C azN5sg z/lzZ08zV%m긜…3M J~marXPGɗݯjƨ/ZؕE!^{MV>gQ$!aɯNuwk6'\bfJA^q`/`FgYD TsþvOn8Ͽhr,f(fNmȱ%L&5%B y~6c[ KW}Ie.jǢE>W`쨎)ӣ@5Vx'ovvz8gÔ'Qc?ЧōBhI*ٰ֜" cGg,{s~K狄iN 3Wu& clLW^/B' 4okepY?;tKHDHI2:_NCYu#Jm`[M06+@w?}fgl)!Nkّ״ >B\rmk!]GcwBw M1*nؚ| 6`ZfAY'*0NSϓvi!>c|HIt=Mؚ`PW>14036 a ,_|*[J| I_ƟW]w#c7aZ g.Wٮ-zOiiXN-{?.%8pJiDP2y$e_Prޫ70F~ ώ 46Uq21Wi;$ o2~a$@TJyl+z/BuaqrK 7㽙fc 1PwdXi5ml1.~9ءs$ ~Sl1p_3Kyʜ  ^\S7-@&(=*=8fqt c@4^rvP`[uAK:$0m"|a#;x Bm`nVb,-$\:KptkgN'[Ne!ϕS%@^V΍ZzN wW6nf(?JWO/E2O0;*t^ёL!2&c_NǟxۻgoEm=Qgi{[rL~E"U^߫)HzC/Q+P |[t5+[y~[Idghq,+0u]\ qC^9 fnzu#^}glO2efy11NdBa\z±>qـeB[nbZU@Q^c(蔮u > ESĀB5J$-T70r𬈳}'%&z1+5s5Zoyp.οZu=Iֹ@!55`8bPuT@6h(/^bP?"E( >,Sҟb޽wU$d4ύ|aA葒s2e0ڈ||]0+?eN %Gaqc f{S,۞z(BL5`ϋByt/ta0mNEp;y%$) ec >f҅(^j-vc'O{?]ƕ!Ep;Yx^Pŗ;/ @o;:F U4&RD0^*Y&#KU 9s? Zjb,pn 4Ď6)y B}Ǖυqυ&g9wIm3? - O3/4Gݖ#= IҒS2 n<?8kՏ%VaaHR$5@F͸Ԟa<1uMzd_`/@4Z:+@Q2ZM 8Cz`dbv9  㹑Yt/7)E<^rl4>_VV;f덇lď񭝙n^T(L[3w?aLIaz+ù4*# ahN+62IV$ RZ;=x1_'D$j7O)ذˋ 3;k3g娵xA{@p6,QuuR⏁7<77YVH$9)SL?: xO̪'̜D/x<-4Xp!) 2F;KVJ/g_jw;' wG] Uk2/-$- ~IXxF:_KeVZԋ瀒YK @y,u :*GHl$dԆƜ8d &J)E\qsF@_%>e M'{k6/6uCOȯod}-8J[vJo{%Z[6 *ռCW$-6}%z1'Zj Fc::4v97$`9Bd$?3hxɤ3Gm Ȍf1_B&r&%˔5hsQ'?9:)<7cV uI9ob&jg;T5sbU Gk_{VNσ<Lsc'ޛ+B(ǟ3gBvgU@@$P^# {*uPm 5ȊNC @ ,_ $VZä֣iSl^[Y6;6 n1[M]ixcQv (cpqU.$'UlJaI_%2ܦ㱞όd[ϰ%ʍ<[Zr7RqsLs2Zfi(7̚B '- _IJP @֩?)5pdVkk&弫CHb}T  ׅǽN:W0$e7grb3 EpM*#boȱ p7M,. .|JWII\Y烀6-hOnq/=iQ伆 y˴,_9Um#_Oйq{Op2M?gu`x&R}7A@+`}ܮMLe&Y&/ ;N2אP'90vϤ?ʔy9Ժ|X- +@Q:39[k~ߌ=1^nPC4#qxhr\qQ*vڽG$Aq7LsT^,y-׃7k@xHۯ8eh9mPg- 5=tyx!+.l xHeVo$!Aʘ+e2@.{ī|B?zXu YFy&|Th'^ee'fIO|jX'tՎr#u9)3×c+1/g>B.⨁FDŽHfmxg'8&dF/1n!95%":U2Pms1I_BK<+:zx׋"1GҾyƸ V)V,k>kB=y[JցNTMee,u2/BZ>wm>+Ro  cҹ2ebt\iw SdT)y}O"ټfWk+=q=p ! .ee}0lܒ]%5 ܶwfve~ڷ S" 6I]!c>~ȼ$6JiO~څ,L[Gv1 r > Un xO(Ra5I9/S/e%Ɇ'Gk2D7y1YGӋ"PHeZxp 6c$hP=dx&-I_!g9by|' ?j]ۆģ_߬BNy*0kd2 0uD=rM@O(z@٣ An,58 fw&\Ns֏3<:vLL' +\[jO(RPҹF+`y_s|:S`Aսs)5 @7T'ȑ]Y<5Lg?^Ǖ!7H)4z6kcw$Z0*Qu\Kr2ecrs|BUrcjHnɎ8xvl%2y'N9Iyu8^Wޅ:FO7VfwuH Ir:9"=`'%dcq:&9S|Vu5&[ej)k7uPO>%|ΑcrEg(a!4+3 ;crsR6CB NkEY4w75߽dǥޭq+ɿ7v}gu؁?Q]4aC!|G|`L_iujG6+< W4(٘So{jV`y?M|2|0QnCk7Ej3 P9dw>WEA' R/'=e[W1vmHʑ͒44b;Nm9VK%^ͺ-O"6uêʤzvlP{ȷ3( Qycll;P8vgjpLbt cebz-U8fzZDsMN_mq  B@I9ϢE:3Nv+Mkf%2XAȄBD0JOaHeH`N\l(ihƷGjQZ^V o&ɑKx}YF=>-&jǤK/RU{ԕΫP 0f4<~:<XVXq/z@ xކu^ vd<U4 /O:9^ֳ݆nRίe@x_&Rmڬ6z!ΩQ^ gPZa@*YcFB\rp pMI؃M*jK´/cu ޕx )zF>qs=Ɂ4ojC /bI A[*?_'B{!˸+i&t xos5l 'wA^r~PRa#OJ_6kyozcg}IO|!kq!pX6cϣve`BqL=N%:ٺ_biR 矞>Up9&X0p\`9Ԟ }*$McPXkKR2elXႾ%$"NDq9/RL 30p֪ٙpC*+ Vjt';忡}鿪6$R w)=/Gj)O|z[ /p0hH,\NzQ`pHi8g5ttDX'˜uz+lz\g>tBvK :&kQ>ilEodkdO^ϘpnB2.ǔaPLGŒc{kR?+8"uM]IJ.fNz<]:d2|o-pF63ujډp>,zL_荝>UCC cxnVu)=tȋ;t#E'9l2ewiXN[H Tb d;:!SiϳTBŌsR;2QZ@Z(x!dGK.|&o.v˱0W~}+F1yjP:*JTtZ#W߱'8,> A8@ P22V|IӰJ؞pX4M\4$%ݴ"1+y`cB,sr^X t ՙe=JUuw81 CAWE71Nݡ$b (/cKQ o%<fw%VO4#\ƿL@u6G3v87 ۩cNyAcI-e<}Ėuu;~R- OcL P,@w8?k]U/BggT.w ZZi< {7 6u~j~rOAn}WpS!bcE,6#z[wg ;A 1` 0rvzv%g&ɿYpAO^Q&EDEY)~:GO *&5mmmcyG}y@1o- աe|10pV ts|x:Js@Ԫ?8`9NB>8BGF] LZm4_ˬ"_GRYx͉q{_E#]sz'B=j Eәufct0I}WK(7,OPnvr_ ;"V?wVAN=l`rrWPH'bmmP#Rs r*?JXBg􇀜 5xu[C{)BqLlNڊê.muZפ:3j ;PobЄD\j|/֦GSL!rdEXKdM2dGc:! A1[ƫRCt qà#C|b'nZo>}+}O~n  ۨBY p%!`náa@{SKg.ymde}8($5poTg6EA9?O0q/60K\:۞eyKQHX]ӭl|sK9 FjpZ`ByVR= F߄ h.DƂSr*%7|,>T~ϭ?'@Mv'O1|0::SX@uEi eΜhqsnm# c|=< qiс)yY({qW_ڀ<\9bM?0?7fLTU>I\-ZAx˽<2u6Tfi kZѮsЀyaC@JʇUo:h I)S `h-SheΜq4>v/0;3n7M# 3O0+8ɿx4~>V'GYL;X‚.wo͙F Z UӮooB0**t\$bx~)ZN m8r#>T~| |#cV$ː99jʔl5'%ecKDɋ=h>ttcO\ 4DG8R]Ivd:[yKNMe5S]}2UB^Qw}/~>F 챿S[MOh)~ 40TCB~yUDZvWPULKC@ EK3'@~$ːӜp4ue5uwJkNZZCjRy12I61=^vDװ/U:v̰g<؏Չe@ɿ_GV'j<"SvNGM^m?xa>{9ۇ=,P?J'o>P-OJ ثEiO2'g䙄w%Ӥo즠7?*26'k~S. yԭ&~&ͼast󡣗S0z&Fdܼ䛫KE/`"}(9㪦7}moUڨQG]lA5XlޝutIwXO8Nw r]%DQnQƧMNԕ<@Hȿi%hyG5L,F\a+A2 ?"c|ͼx 0F(z s?]cPNxJ^y\]Zm.yy $I!=iبjH9ƍT*Mz9P(t1ߞABҵ3t$%_м^o.QQ r A~s$ZNB%6 KIZH+5l#(JtqCG2e|J[DO1>je' SGHmcsB]IZw1Ʈ M0(M7Z4ul_'D}ר@},f濁䟂,oFj14JAr3uǪL\~1fs(?NN|7 l-gdA%$-$$l_!WP(̮D htrPД&(S@*ʪQvͫ:cZU2R9%/=ʾۘZvO4ƶ VavjЌkZ@)z<J%D(ڿE 'lOlxkу|iinS9w.7Emj4BrS*PZvKJ 31g4k^WKo24\q-FtӅ\r 9*7z}-%+v'2U0qگ?`_I )ŏ4TxȨM~oBIn*fTb8U 4$. B: 3ۂUKYoK2eܥxrBNXk&/. Ha9|)Hc MPB3>iq*ڇCMCɿ*L#5 )ժSN! )Ul[u2PKr@I% QxI0wq4FqoJ"V w'XsbmetkN*&OSWޗ#g  OƬdPj9zX_9ʯ|MF/5UjV Y4u7p(?;BjP:CL?>%B S /gN 6;zH b:q+k\&h:u e(ho0k H- 55n.gQn4F%FmQ]u@ @ygFkzpGӲMAzrz{Vav̊F }QΣLՙ"X2&XzJ%<u.?S6:~Dw.ưq^vAW|=pbɪz U T}TglcS2gQhAĒr?v#D=#&R],OMI\KCVBǂ0cM9!BW1VACm&I&û \:2VX4oѶYFb'6LkT#KxKZu~-uOh?Twro|O>+ o1{ނO,EzboDy6C^˙S .D ".$r?#GDpw@8#-MXMˀgy;=5loR}Vf+SFZ,KƷny>򭳑P@ilK8gXVYd9MQ#$r+QB%c_pG<^;>W.g=!›HŤ7$jbHL۸O z/gN 6| J*Ŏ 1$v$YPքq ܬ-YMa 9;DzgZwHHfX3NגM@G/s":k;  ^G%ɬp#ZFq;*6V{KxSi\/rl1PJ>šLXu~` _5V?A}]*37)$&&c|2ADHI4(@țp.]x_ փ 1 $y.YWRLGuS7axJ}[gp6KÚ0ƶ?o$Q{s1Rï}h%/B%@+Pԩ~ŝ\U*\)k"x:W-Z]U䥈گ骘^DpL-7Th?ΜM84,1Ia2sDݩP"DB[;VfZEG9-0SF39+>?pX 'ȷܕpSG2+!>% :%- /1\@mqqTG6.Tn-()S|g=˸¹[7_O2 q`9H*lm$ЌmCg.;Npkj򺘑Q "3eL`t#|}O^l)"U/_|CLY֜E=+t [h̢6n1I>.`11 :"um:ԤƂqzU5s<cR9%8@f,10I"R&I xr-x2GĐ'XM)~pX}*337ȰcfH/{KNbA(#}X2+5 2H%уΞ[U{fڐ\C =1T9ҫ syB}3!'4X =!x)9hpJ?~$K`97ť~eI CJtx]ODc+K}j߉3eı- wѝgV\96O1eP*A-սvYPON/X +ɿn{lJcsS~()Xf~y~g{r(k/OH8sJpTCtp:$=@BsZѿzZQGҙ'uXuX6ˢ>Bߕr*y. c,d0y$F`%CN*ImSI_B)f7Y yP3h[۞T费 ?.#evw)=prR?&I4 .vNfQ$b9TĈ1e=+:Ǖ EV<*x0cf:nk5=F,NN}ɿ[/Oq#u5^J( - 4cx_irq葪ksr3 ?n#e> xw~IE{n$g&*QƇh6) vXnXij[>2f&Rcj;("5'r>tA8ʳ=xc/m0 YC^UKXI_B7?w,K8 |[b uѷ7YYCo?S?Β\>$t&QBI4 e'd'_ 71ǚ"3e]ſ13N_AsTDJɟvBhuvZa +5}6cɺ& KYwwo[Y}ʽcw2:6Bap#% ]:8E~}>DpHH%[f-D;ЖڈX"q9?OxoߚÝ8 ƛPwb7Q-b't:.UB.Uh.y7h BN,q1Bcxɇ_[̗w7]x6eFx~: K{9Q#s46B)y+NYB/NT(c$S=@l.hHk81sRnڻ]rW7zޑx[P|R)3zm|)cl Z eGygjFIsSЭɿxcBo>SK+Ƴ+;cγѱK9SฑBcRRBv[rEJe(JL.DpEHk-[f["Q9`LkShpHjF-2ea>"C_HGDQ*'"-bD[t-?B qƲ3ndJO Sc]LWl$ِK~)s\Mtݠ1v]a/j8^S;YvQWv>ۥֱ\ -[24gtnyPcFY//Kf ,cqe~—aH;~1kfъ`s/T~M7wȳqX87x,}S/yJ( wUnA<OԯY #Z8WCv)wޑg8)PG Bc*R2XA!,@c9od/ BG=fG^6mr^+4uSOs3u3}xnkjrl>?ދ{BڸqR8L{9q/|M"05@-I蓾O]Ky_Z8Ex5p@ WW;|.ߨFr6Q3E~!lL {q%g+[}»<`m޺XEdʨs ._LsoV{cbkk8*2Ln<:kt<?/PtֵKj72ѽ/ϥĄᵏ)O{ox Qgx W׍ skmäÀXA3ȏ.jdFYm{>ҩƫp;7XwȄ׳ Ȕ)_7E^zǽɳ~+q8pD.|kQh%7J-)15#%%jcL?ŰE2{(l龬ƛ"rk?ăk٠nݞ}m o`C=@^;ZbE E%C2 :0a ~HN~j sM OUuz>ƧO <˖RZKݏ̉db8nʌFSj: /qCf35HRjl|v"-VNо sH3gFG娥P9VQY023pJ(ЀT7Ke9C4N`C I.Œ:OZ70=Cf?J V_JӸqXIh2nV~BɊ>2nojz8HpKFwRFpɛ˒M:$$0ZC@ָ-t-C%i1xō5H64!=>f(k0l78H0@$Q?=OPe0}_R+7[5L[1{%1fn>I'qJ\7Ŭ !ˌ [[Hm7&[[ S /CIatl7[(5ixŏ5XmBΟ-SF31a<"7J(z{ծ.7lE%2wM'JW$ϐ̘qh|/^qpi8 @,$cP9 )Fd}Ǔ*͙m` ݯTZ XHCkiBΉ|2قa5\*n(p8ߩz 6l3qVNA?Evߚk{IdᡓX{W?ah|~_)|]Mxh2 k[1P9PBD|FVi f9Dc`@xZedyL91"/.AR0|W cn*gBR+ ѳoG_oKvLL {"? yOs<*i7:@Ӡhc:I'\d`o*xF2M8=@8e}}[E2QM[ v2%Vr>ÇL!ᷲ/PܲQ/|ΰaɀpXV8X+n6KzBOX{5B5^@ I3 AR2],NRD?'K)FMd O,ʅﰧ]4h`E C* i:*y ZxM2 xa3C5]Q_dovi^6!͔>ϊWT[qVa*{?/6 UmZq -IzLg ^QL^ _j7Dv WHp:4)da2nJao rM62DsJ!PWj#[W6 UNI@ `RIO,A81e_5V?EI9w@Ji7sic'1B̥D5eܑL h+K9c@J΁&|L=kT9lآT[aQ wh[ c(Ӯt pdz! qPD悏Q~c.׽FOӤ:AHq[d> gJn@rGd%0ZjPr6B EQr/e\a6}ڝ(|^j#FqJc7ˏPe'RFԖ옆 -Buۍ8Qx}IQ`#5s>sKK!k7N8#\ 1Wr'+ B2 c7hkD93@^%'&2+T<;\7fU 7TY~b8V+g!ɭPɰ]2oXD=(d<{B(lf(TZ=\swLb<(pFaR bxtruDAf qxlG)_ޓaOk>d[}‹ q!7(cp-vذqQCXպ߲PKvW5KvB6IS9m !^/.tYV7N(!#\HK?J _"dn'(?K.yP9*("[k8.n$B8%ʑO7$YGԿp !~*\o7BH8hE4 2IsRŪ<%h,i)ʋ}̔1!+!\jUcvz+Uh Շ'yCƟ^B9IJιaUvÑwRv&3y3q8:OHAKcnE((FO*T 2QG)!kDָm=AIq|W)w6ǷK\r% ՐH*2 K\KR5ä)4SIWaRdRR*#) ˸r gY쵿z\L4O}'UFdM-98!s*i;[Nj뢑G5~}onru~J &W%olcN*ō靈]Z }ܬ.+~04d'󂕩œi{BsWC^yMdW(eΈ+h {:ܹd*]ώN\RBZo{ܵYWڎbIz7_U<#phA.1Vu7ULI@QOd@UjH_o 3WKXRޠѐlDNf_XqNϘRP<$??OlY8fNe۶l6{@ MeVE/Xmylߧo8vإ(\ ~ ̗ ^ͯv94_hoީǹsuڥWF R[w}DS;Tm,G? ?˄<wΰ[ˆ'6}eyUi(aXAÎ}ٲ?W Y{5|jlj wiQKwO:|%>^Ɲtޮ-}4aj붋h\F~Y(Q,Rk4Md߿OVV7ϤuN_]Orx$q.u(j-i\r-dkMf0{]>4qoq-ض`U'-Dz57+gf>RiĚs}>flOL^_PQkܥ..78mdxX6wڱ(NP;$gx{iiX㧳xYᕟ.5M»7`stүt*폗mїPy 7k~ P `A]|ۼGfv\nb e_VДljQdR؞tE!kkx_"E5D|P<:H^}fRb)uE~O6?C~ƃo`q6W"77 GDnhܦ.)8}rHЌZ;RPC  ;'cV$l*˟_u!Q)72[E aw󼞼6qNt^3Xh@xeĿsTvT?KM پF jdK@QdO 㭕ūEJ0_vT٭%Hhviڈ ,u5|٭ɢk@A;珖\S5i|f‡>b6u}_*+WT,-6^bI 審> .yW4찑bM$F=[Y::Ww7U'eM/Emz7$n'џ2L7Dnm1[:peBq+*Y; jd8eoY# +T"pW~]x$4a"Z6Ҟ %eM8,zɢ6ζ,_gma?R/# jm݌.e2tE+{|5v2]ul(Ҩ>~٨Æ\(؅`"lvgh*۶d{XtlF|+Y\F[%sX?'X2B ޝgWt3-fgO]&}q^+[6ջv8eKtYy\RPy N" g- :wET7E9||7"T+_-Y)j󨨅Ciܳ%?\[^UqL@b#HM )[+fYwxeòxM\qsU(oTn)R(p:kU>G[=Vr{qHi1u|$DEY2BGtw8.\y<ᮣ3LyWO+ RkZ|>k+i:H"mSJO>nᶻ=sg4ֲvmgY?oҨu/K?(+ ͏i;{i :F2e[M#ŐĦhZf2h<ޅwpӱ׳eJZwg~UUi73`ה갹OoYx4z04RղѢНrf嘑io'XpWmA\Ý=⦑bvb3* H˾ZYպb;*k_<^ki ]Q1nЯij}*4اSaBu)}_F W;=lLL-1ݙ'\p?vh$5Awk~'zsڇ|)QvISO6U)zYx;ﬞj\'v4Q[YrDiiF%!:rQ8^WGdcṅOܒ&~;Xw69?nJu)f1ȣrL wљ&C9s7$zy:ߎ雑HrwmҝM0({{(vu٠+4ٵP?x 0!c}ʝ$5AO]s=}>co^Rúec8uzHcZPw F]Ύމ՘F=t/b-]% n$݉EIN#/sRERm̑] AxaȔ!l>g_djIMKz4|NAzI,?E9??f0wJy"*73#|4-hޖ b&!Me1V7*'G"NJmWw]G`T+gOq|AuSM ~ڦvR/i2|[/y䱸uQgdS#' ]]\)YlHemAD.2t7OfPch$ı) $u٢+ ٵk^b޸Li ^LJ"5)SxVif_@(Ptfcsݥ%vHSYAY#lBQlWI:fv YVfjW[si6j]6Ni81+*_9+.^ *%ZAߜDjS?{c?i3ߑ3 (K8윐6kNj)fk5rZt0/l#{p:-7cԹ~Wu9l4͂! o_kաh-9Xow]4yxuKS םZ9 '`o\:m-W5TXX TWcm^Gv~Qm\*-8=D"d۲ޜ8kZ`*_ G#~#t`?-1̜ r#Cn܆-^mY5]. ]L壀9#ڒ֤z^ըUOFzO B=F cu;b z$ȶڠ-jO vll*bsM*4_߬1{縼_cMҾi5m}eƐrIpH*h@cHbx kGRH'u%+rDz^ikyȨ<2£$tr` "i!xHz-3JG\B@2yŤ;w"C@ƪ`V*ɞ;IԨw\Bc_dX /I'IlE["X$X@>5@:!@& );{I\I@Et%EޱF>2VzHo${rIhHwlw,#C@c!+ђ=äjwt$EޱU kX^JVH'$O٤jrP v&EޱW2[@:Mz#6_+)򎰼`52$-NfZ xZ6_)2yZRdWtY ,8GHe9qXזK(4^˾y!XdtmY5].U@mIg/X+dzx}z͐r"hb/HqK`wh )VEtBx2yRX`+)2nnid|Ig_ ;H oA<փa)2U9=c+o&E1bKLUa1m>s )?_ u1":Ƒ"_ĵ@kX:tI .09q-Z2V)٤[*5 r3h-"2Sh=E)2?ns t>`^ݴ|[.En7v`+T+AR<ϫW |-oC4 ..p`IENDB`oolite-1.82/tools/fonttexgen/toprows.png000066400000000000000000000302571256642440500205220ustar00rootroot00000000000000PNG  IHDRX< pHYs'_'_j0aIDATx]y|LgNV"D$BLBbbozjRJSZK[*TmUPZKA"b|~~{gΝ{{sss=" Gm?M{>jajA} nSٲ_dˣt]L=*]{"i]V#s.A5{N\yK'=TP~6n2hGt,W3;*].s=*ymjEyk(!megDZҤ~?dYl#!A ɩy_0#wd>$_B;I ~$ƑTuQ}S!1|PZO aIraMJھ \Hä= }_}$nFN2l@.$Kc,SJ: `0qAr{o)L$O %9s;IԳiARHooYɮ^RA@gUMm녓]E'iF$$wl$HP`ɔXٔVbSHΒ⏹+(.ßIM/wF535' #@@fFVCp&A鹀]$3iTv 2H>/_xdsN[Ti).۱2oێT@IvdCwp"A0$|YsI&/#6IhVhH[V*Wnժ-${ݵf괟 Qh3C9n 8B sLg!)zAM$yQcu_(W9Hg ,IqL <%hlR L2E~@` c= ZKP뜯ÝY$-qw@E@1\Iv;IPLz..%LukIf.n,IZ4%D+Jwm$oq{xyy@9T5ՓduiK8fJPP4#djQWM;>(ȻYrA@2<ɴ.^+<*%2}?@d^h/ U0*yUc/Y%x]S `8ɗ\\0kx#KZ8aYuH&[HK2ɧʃ #9L\F2LESIGʼn8POm`сɜr(C.w}[$ ;Q$垑O`.؟C,^?t869 %@W}$~g3jkVk3㊖G~\v] !9A\@G˅>@#$9…5Wsdծa%X99N=*9.$=I tzn__9b]>m2dVy{ a 8''d5s%]iC)}|$sTGx?߹GAFs3p*IȦS:hk1$Fi޷JP*%&K`A0oair*-D<^H$9IߑxLpjH4_f! yF~'.~G^E*A2=@j$Xd rcq ,@|~Q0O%bdJ+U Y?PwJ(s*7,s$[_hy%L-hr%DjfppWz@+pa97$T3j<7B7s|> )$cFqq#yL6 l&iESXPNK&;chMBp η(ɜs#@}(ҹT3ng{ ?_(>kgO hsvT4FpԺU@|I2^q}@g~,ǂulj?44~ Y45_='p@mpf9-E@ЪT*`7}_$}ݺ4sQs#A@cB>jc~tďZ@d $9P/́l 3)p9'p1 T*]PB =\]v%[;wN1CqAc@Cq1%l^,r'`F5ȇ#.BAY MPS T/M`8Q9֥AH#*AbVt뜚oҮ`+harS6y:`-0tI~]`ԥA3+X u1/A@#(DҴyi~' rr h~#&2^\ V7Q E .,z%\{('C'J脃R4?x3~[(9D1d)(Er,T .P, {[p AA_\_W@/!ĺ:GgYk:b{9yFwe2IaP"WF !$@9CdHPܫm*f1tV\fq#%̹O Vkmxub_9{xӦ$ɶX#~mIM@S:(fI& *ֱقHsuVu7BkPtXY8q50DҹHJ>b 1)ՁdS.ƙxH-I [r-D@p@&-\Sdi8=mj$6lJkF JNjfC U قT?]^I/ qYWwgS=Ipn%LFY(!"vQ[~NrNZ, sdi‚}ד$Wwrji+P 4L8P_s,-)Ӯy:ҋCl^ۻ9o,jg[g Ǵ X+$~15WloDy;4*<,7}M$wص汄\O+=] (l ey'%O-7rxp(V ,_+aR9bP6X+?bD|P烒DGt](S%pD$] oj#OkDCb"Y~(L CIS2ɩ<2_iryʥ+o8o+ٷiO\Ya؀@a Pdh7N*S{ryj7]GkrS[^,/ Ck}ڔlZuc(L"u͛3ާd Vi敏v,(`v{ uB5T5FEpKA{T-TElvO4`W~)=)}a9R %<7.QGhx(B]իnP7c|hJcJS3%e4"YR`'=fjUu+%-bm ~).0BYqW?vle"mаg VQAU=P7n}Au?#N;+k3g)(}sn|@[m}Ej('k:vP7KY1^bO|_DnDS a@̄(9dQYұ0TmP AhDKl$ oA/uL/e$3څ<5`zvwލ5BzV ,4uR 96G/+m5-WMU3|jãzJDW ,휿W#v((Nc vEgDֺ&r}'kYIr~@6&64ȅ[HZ'!%& ep@ʄԡ?7d٤lƂS+$ӫISUGV*2 DVJ֤ Ut޿zƖP`k6 riZtҗTxnX,68uu%ZT 3 ˆbdž73>{B؁v!ril_˭*7ubG)SfWsQSGzz7$MD0P&2zWQѩ^Q(jlQg\]8e\xxP_}3 7X₹ Sjr<1F< .+CuC%IMk;"&oG4z_ⷧI6N'ӽ˓u+Yj"0)O|<0+6Vj>x NS670 5pmS~z}nP1N?rt.S§[ܖ{=DwոhWI}IOZkZ$2}/f DX뉲k hn$'a;Kܴ&{aa¨kBh??M-x~Ցߝ0AM|ϟ`*t0ZAWxͯ^҂뷺TF%V$Vr-eѺKaS@u#=}{ V%(PbaC[@W~%# q4ʱ{3>'ojr<.;rĀd+k81)c\VCi^f:ɼsQ4=GyHxWբ=ӻ #RRsH%sw%g5݆޵"uoU{[%;DCTHlH߶pWQ0<^I4*}9MQg- zJ5FlEof)ANP7<|dxtlp Tp:ͬU ?vi |+s*ILEa N0Q %YK]4&IHS g'y\džߒ*<c '֔@ Cͻ FaFW,Ň=~:Mg~rzƝMAe޹5w>aJ=|:ڧܱWU&`I?͍,C4W&O'nm"ysT 㯔!V:T쐼wjA Q_˽wdƒ}BQE=̞8 Ǿ{.D4Fj`9!*|(_q/ Gћg~9#4\tp JvgyϳxRRexD!fI.9; @HrwTչQ=*3t(TifbGy8 ?n#Crmuw=~:7כ(Q͇qPfұrC@ƽՠZļGO䚏c| YقBgh5b%,|0Kdޝxߖ_ϒGSfw>˜?g9Ҍ\Ɛg/*䅫.R๓z[ ?Kۓ$i7p4wa+zӧ+n7-zlwT?y np2FlF:5g)sz[(_s)ܚ>u"w ȘY':ɳxY_sOO#~=dؓb9 ~huS1BJF'OI]IdK'oD@-7lE}Iџ5X%p4/iIǓke<`.<6 aog×?ږO~q-c$߾{>r+7 3z]֦;2@a RJyv PU/&HaSDbe|$Sފ:&v}IǗ+Y%pA-˩䲭yONKW]fZlpͿi9:̾:WhL:G!zEۍKym3_geQ-+Ns\0e'Il~7#J>]'zjKygo-䃝-ٛMI@'6q%] H`ȭ9I^ IK6b835y'R4}?8uɂS%'G6?6/s*y~t=={x2Cӌ7)? hO.+ s* оdJQ狶;ZU1_|^(q$XI}wz8o8Rf CU$e*XMK933t>  /"wܼ4Cz4=MyFcuQu - vBPRcąH 2HrA7s}w xL:RhI0KIi7MKjw"'&NS7l޶m!x0('ʬ!3X'yw0」k&2hA/cOVO:A>d'V  ck)IN绩7 ~ϿQo%= ~IޫqN T|#-G=O)2x&z O= 吅 R/GG5u^H XPoYQ'`q# 40Z͵6f 7@J@&T6 I@6ɭ5=uCѣ3|k }ݜgfMrg2{ rݏ>\ mxXHv.njvFk qaVvkV"0J:0Ɗ=}}(\NZop=3Հ$P_^gD ~#9w farɧzd꺏MR+{"kvFU#À7BOMM9l[eɫΒVn5~d;{e6@a՟5)%,:f87.1{0@ dtZfg_ x{U{j`I#6M\2 @K?nnEu3Z70侊FlK^>ٿ٦A,ʴŴG^I} q6M@"C%ݸԼf",*PҟܸnW6{2lUb^P,Gd1#0ݼX4JƝ6H:I_].ʞ9@>dy17J׬ $iZ;a,NqGXzi׿ -/:e=gq6(:.ky{-$-7^΍6Z@節1QRh3EVpkO0g@>&P|P֓4KD䢕v1LVGFAL'~ w*&U#$Oi:i=2 ?%wtTp$M$y[\~YR;h&ɬ.3Ga=ɶ ~g\$eHLZ/DZ^}( 9bÆcq 7+Vf53lE!Lga3Kw_5uc ¯-Td2}DHK\GQR+ Th|v2HIX,R" j'8Fe!ܧ ]MŧU*ן'Oڭ9JfӆZOM2^) 4bzIB=t XOjuMxhM$kŪd +ڮdڵ&rbHnꪛڻbzr(-مm&yCnIN0$/}ؤ|"M撼P[W9@븲=tՖUjl^4"%,~1UTL{ `Z?)I@9B2q|0Jyղ7W-0s9`]SB.+P*i:{\3;0\ ?I^L$}nG}I[-.B ]+Qe^KI6;EZ{$߳`4OOOX8;慨"IᅲJA6˽IF̼M~?$e=M__M>XS8f–Wa˪)we2=a@r03?'1&m|ۙ3C| U5ݩ4`mĢ =o_C8HD5de?;2;ngyaBFIBy=!GhѠ G,"}wsSl5L R KdjeW9um-Y =Ig#֓,${đf@|ޯc{2 a@"*i0Qs-RkZ?ѿe])958zb(łJ5ؕfsh\I<# fA#n&IE#%EU*T=1ɨz0z̙DUJY0zSU#Y&?]l|^A$BrbdsKPyKő@Zںъ/kl73ɛVw0_fldGܽa-`@ޯ.$w"6ŝ09m]D"q0FY 9䳀Yo=DER)zРEi$s3lkSU Kq6xcmCma|@BH$1{kDDuy9@~>b  $;Xt@뤖-uL6RR@D\g D;$FK˃HZDwxT@] TttIgD7)v.NPy3 ddJx2̆Xn2G!@ŸN$y"0vϓgܩWVo{Nnnn5<Ŏ-E#%e]*k0ic)b|}l>P`|zz˯rȋ!9~{~T; ,[a4A^xP7 t H y@*g]cI03֎Wdxԅ#ς!>|Dg6[wGIENDB`oolite-1.82/tools/gdb-macros.txt000066400000000000000000000035331256642440500166720ustar00rootroot00000000000000# Oolite gdb conveniences # To use, copy into ~/.gdbinit. define pjsv call (char *)JSValueToStrSafeDbg($arg0) end document psjv pjsv Oolite Print a description of the specified JavaScript value (jsval). See also: pjso, pjss, pjsid, pjsvfull end define pjso call (char *)JSObjectToStrSafeDbg($arg0) end document pjso pjso Oolite Print a description of the specified JavaScript object (JSObject *). See also: pjsv, pjss, pjsid, pjsofull end define pjss call (char *)JSStringToStrSafeDbg($arg0) end document pjss pjss Oolite Print a description of the specified JavaScript string (JSString *). See also: pjsv, pjso, pjsid, pjssfull end define pjsvfull call (char *)JSValueToStrDbg($arg0) end document pjsvfull pjsvfull Oolite Print a description of the specified JavaScript value (jsval), even if this involves calling into SpiderMonkey and possibly invoking JS toString() methods. See also: pjsofull, pjssfull, pjsv end define pjso call (char *)JSObjectToStrSafeDbg($arg0) end document pjsofull pjsofull Oolite Print a description of the specified JavaScript object (JSObject *), even if this involves calling into SpiderMonkey and possibly invoking JS toString() methods. See also: pjsvfull, pjssfull, pjso end define pjss call (char *)JSStringToStrSafeDbg($arg0) end document pjssfull pjssfull Oolite Print a description of the specified JavaScript string (JSString *), even if this involves calling into SpiderMonkey and possibly invoking JS toString() methods. See also: pjsvfull, pjsofull, pjss end define pjsid call (char *)JSIDToStrSafeDbg($arg0) end document pjsid Oolite Print a description of the specified JavaScript identifier (jsid). See also: pjsv, pjso, pjss end define jsstack call OOJSDumpStack(context) end document jsstack jsstack Oolite Dump the current JavaScript call stack to the log. end oolite-1.82/tools/icosmesh/000077500000000000000000000000001256642440500157215ustar00rootroot00000000000000oolite-1.82/tools/icosmesh/GNUmakefile000066400000000000000000000002571256642440500177770ustar00rootroot00000000000000include $(GNUSTEP_MAKEFILES)/common.make TOOL_NAME = icosmesh icosmesh_OBJC_FILES = main.m JAIcosMesh.m JAIcosTriangle.m JAVertexSet.m include $(GNUSTEP_MAKEFILES)/tool.make oolite-1.82/tools/icosmesh/JAIcosMesh.h000066400000000000000000000013511256642440500200170ustar00rootroot00000000000000#import "icosmesh.h" @class JAVertexSet, JAIcosTriangle; @interface JAIcosMesh: NSObject { @private JAVertexSet *_vertexSet; NSMutableArray *_indices; NSUInteger _maxIndex; } // Vertex set may optionally be specified, so one can be shared between multiple meshes. + (id) meshWithVertexSet:(JAVertexSet *)vertexSet; - (id) initWithVertexSet:(JAVertexSet *)vertexSet; - (JAVertexSet*) vertexSet; - (NSUInteger) faceCount; - (NSUInteger) maxIndex; - (void) addTriangle:(JAIcosTriangle *)triangle; - (void) addTriangles:(NSArray *)triangles; - (NSArray *) indexArray; // NSArray of faceCount * 3 NSNumbers, being indices into vertexSet. - (void) addOneVertex:(Vertex)v; - (JAVertexSet*) vertexSet; - (NSUInteger) maxIndex; @end oolite-1.82/tools/icosmesh/JAIcosMesh.m000066400000000000000000000030601256642440500200230ustar00rootroot00000000000000// // JAIcosMesh.m // icosmesh // // Created by Jens Ayton on 2009-11-18. // Copyright 2009 Jens Ayton. All rights reserved. // #import "JAIcosMesh.h" #import "JAVertexSet.h" #import "JAIcosTriangle.h" @implementation JAIcosMesh - (JAVertexSet*) vertexSet { return _vertexSet; } - (NSUInteger) maxIndex { return _maxIndex; } - (id) init { return [self initWithVertexSet:nil]; } + (id) meshWithVertexSet:(JAVertexSet *)vertexSet { return [[self alloc] initWithVertexSet:vertexSet]; } - (id) initWithVertexSet:(JAVertexSet *)vertexSet { if ((self = [super init])) { if (vertexSet == nil) vertexSet = [[JAVertexSet alloc] init]; _vertexSet = vertexSet; _indices = [NSMutableArray array]; if (vertexSet == nil || _indices == nil) return nil; } return self; } - (NSUInteger) faceCount { return [_indices count] / 3; } - (void) addTriangle:(JAIcosTriangle *)triangle { if (triangle == nil) return; [self addOneVertex:[triangle vertexA]]; [self addOneVertex:[triangle vertexB]]; [self addOneVertex:[triangle vertexC]]; } - (void) addTriangles:(NSArray *)triangles { JAIcosTriangle *triangle; unsigned i; for (i = 0; i < [triangles count]; i++) { triangle = (JAIcosTriangle*)[triangles objectAtIndex: i]; [self addTriangle:triangle]; } } - (NSArray *) indexArray { return [_indices copy]; } - (void) addOneVertex:(Vertex)v { NSUInteger index = [[self vertexSet] indexForVertex:v]; [_indices addObject:[NSNumber numberWithUnsignedInteger:index]]; if (_maxIndex < index) _maxIndex = index; } @end oolite-1.82/tools/icosmesh/JAIcosTriangle.h000066400000000000000000000013471256642440500206750ustar00rootroot00000000000000// // JAIcosTriangle.h // icosmesh // // Created by Jens Ayton on 2009-11-18. // Copyright 2009 Jens Ayton. All rights reserved. // #ifdef __MINGW32__ #include #endif #import "icosmesh.h" @interface JAIcosTriangle: NSObject { @private Vertex _vertices[3]; } // Note: order is not guaranteed to be maintained. + (id) triangleWithVectorA:(Vector)a b:(Vector)b c:(Vector)c; - (id) initWithVectorA:(Vector)a b:(Vector)b c:(Vector)c; - (Vertex) vertexA; - (Vertex) vertexB; - (Vertex) vertexC; - (void) rotate; // a = b, b = c, c = a - (void) generateTextureCoordinatesAndBinormals; // Requires that any polar coordinate is in [0]. - (void) fixUpWinding; - (NSArray *) subdivide; // A list of four JAIcosTriangles. @end oolite-1.82/tools/icosmesh/JAIcosTriangle.m000066400000000000000000000135411256642440500207010ustar00rootroot00000000000000// // JAIcosTriangle.m // icosmesh // // Created by Jens Ayton on 2009-11-18. // Copyright 2009 Jens Ayton. All rights reserved. // #import "JAIcosTriangle.h" static inline BOOL IsPolarVector(Vector v) { return v.x == 0.0 && v.z == 0.0; } @interface JAIcosTriangle (Private) @end static NSComparisonResult CompareVertices(Vertex *a, Vertex *b) { return NSOrderedSame; } static NSString *VertexDescription(Vertex v) { return [NSString stringWithFormat:@"{ %g, %g, %g} (%g, %g)", v.v.x, v.v.y, v.v.z, v.s, v.t]; } @implementation JAIcosTriangle + (id) triangleWithVectorA:(Vector)a b:(Vector)b c:(Vector)c { return [[self alloc] initWithVectorA:a b:b c:c]; } - (id) initWithVectorA:(Vector)a b:(Vector)b c:(Vector)c { if ((self = [super init])) { _vertices[0].v = VectorNormal(a); _vertices[1].v = VectorNormal(b); _vertices[2].v = VectorNormal(c); // If one of our vertices is a pole, make it the first. if (IsPolarVector(_vertices[2].v)) [self rotate]; if (IsPolarVector(_vertices[1].v)) [self rotate]; // Ensure winding is consistent. [self fixUpWinding]; [self generateTextureCoordinatesAndBinormals]; } return self; } - (NSString *) description { return [NSString stringWithFormat:@"{%@, %@, %@}", VertexDescription(_vertices[0]), VertexDescription(_vertices[1]), VertexDescription(_vertices[2])]; } - (Vertex) vertexA { return _vertices[0]; } - (Vertex) vertexB { return _vertices[1]; } - (Vertex) vertexC { return _vertices[2]; } - (void) rotate { Vertex temp = _vertices[0]; _vertices[0] = _vertices[1]; _vertices[1] = _vertices[2]; _vertices[2] = temp; } static inline Vector BinormalFromNormal(Vector v) { return VectorNormal(VectorCross(v, (Vector){0, 1.0, 0.0})); } - (void) generateTextureCoordinatesAndBinormals { VectorToCoords0_1(_vertices[1].v, &_vertices[1].t, &_vertices[1].s); VectorToCoords0_1(_vertices[2].v, &_vertices[2].t, &_vertices[2].s); if (!IsPolarVector(_vertices[0].v)) VectorToCoords0_1(_vertices[0].v, &_vertices[0].t, &_vertices[0].s); else { // Use longitude of average of v1 and v2. VectorToCoords0_1(VectorAdd(_vertices[1].v, _vertices[2].v), NULL, &_vertices[0].s); _vertices[0].t = (_vertices[0].v.y < 0) ? 1.0 : 0.0; } /* Texture seam handling At the back of the mesh, at the longitude = 180°/-180° meridian, the texture wraps around. However, there isn't a convenient matching seam in the geometry - there are no great circles touching "poles" (source vertices) on a subdivided icosahedron - so we need to adjust texture coordinates and use the GL_REPEAT texture wrapping mode to cover it over. The technique is to establish whether we have at least one vertex in each of the (x, -z) and (-x, -z) quadrants, and if so, add 1 to the texture coordinates for the vertices in (-x, -z) -- corresponding to the east Pacific. NOTE: this technique is suboptimal because the selection of wrapped vertices changes at each subdivision level. Interpolating texture coordinates during subidivision, then finding the "nearest" option for "correct" calculated s for interpolated vertices could fix this. */ bool haveNXNZ = false; bool havePXNZ = false; unsigned i; for (i = 0; i < 3; i++) { if (_vertices[i].v.z < 0) { if (_vertices[i].v.x < 0) { haveNXNZ = true; } else { havePXNZ = true; } } } if (haveNXNZ && havePXNZ) { for (i = 0; i < 3; i++) { if (_vertices[i].v.z < 0 && _vertices[i].v.x >= 0) { // printf("Remapping %g -> %g\n", _vertices[i].s, _vertices[i].s + 1.0); _vertices[i].s += 1.0; } } if (_vertices[0].v.y == -1.0) { /* Special case: also need to wrap the polar coordinate of the southernmost seam triangle. */ _vertices[0].s += 1.0; } } #if OUTPUT_BINORMALS /* Binormals For non-polar points, the binormal is the cross product of the normal (equal to the vertex, for a unit sphere) and the Y axis. At the poles, this is a singularity and we use the average of the other two binormals instead. Note: for non-polar vertices, it makes sense to calculate this in the shaders instead. It may be better to use texture coordinates to handle polar coordinates instead of using an attribute. */ _vertices[1].binormal = BinormalFromNormal(_vertices[1].v); _vertices[2].binormal = BinormalFromNormal(_vertices[2].v); if (!IsPolarVector(_vertices[0].v)) _vertices[0].binormal = BinormalFromNormal(_vertices[0].v); else { _vertices[0].binormal = VectorNormal(VectorAdd(_vertices[1].binormal, _vertices[2].binormal)); } #endif } - (NSArray *) subdivide { Vector a = _vertices[0].v; Vector b = _vertices[1].v; Vector c = _vertices[2].v; Vector ab = VectorNormal(VectorAdd(a, b)); Vector bc = VectorNormal(VectorAdd(b, c)); Vector ca = VectorNormal(VectorAdd(c, a)); /* Note: vertex orders preserve winding. Triangle order is intended to be somewhat cache-friendly, but not as good as actually optimizing the data. */ JAIcosTriangle *subTris[4]; subTris[0] = [JAIcosTriangle triangleWithVectorA:a b:ab c:ca]; subTris[3] = [JAIcosTriangle triangleWithVectorA:ab b:bc c:ca]; subTris[1] = [JAIcosTriangle triangleWithVectorA:ab b:b c:bc]; subTris[2] = [JAIcosTriangle triangleWithVectorA:ca b:bc c:c]; return [NSArray arrayWithObjects:subTris count:4]; } - (void) fixUpWinding { // Construct vectors from v0 to v1 and v0 to v2. Vector ab = VectorSubtract(_vertices[1].v, _vertices[0].v); Vector ac = VectorSubtract(_vertices[2].v, _vertices[0].v); // Take their cross product. Vector cross = VectorCross(ab, ac); // For a correctly-wound triangle, this should point inwards. // Since our corner vectors point generally outwards, the dot product of // any of these with cross should be negative. if (VectorDot(cross, _vertices[0].v) > 0) { // If not, we swap v1 and v2. Vertex temp = _vertices[1]; _vertices[1] = _vertices[2]; _vertices[2] = temp; } } @end oolite-1.82/tools/icosmesh/JAVertexSet.h000066400000000000000000000010371256642440500202370ustar00rootroot00000000000000#import "icosmesh.h" @interface JAVertexSet: NSObject { @private NSMutableDictionary *_indices; NSMutableArray *_vertices; } - (NSUInteger) indexForVertex:(Vertex)vertex; - (Vertex) vertexAtIndex:(NSUInteger)index; - (NSUInteger) count; - (NSArray *) positionArray; // Array of 3 * count numbers representing vertex positions #if OUTPUT_BINORMALS - (NSArray *) binormalArray; // Array of 3 * count numbers representing binormals #endif - (NSArray *) texCoordArray; // Array of 2 * count numbers representing texture coordinates @end oolite-1.82/tools/icosmesh/JAVertexSet.m000066400000000000000000000042011256642440500202400ustar00rootroot00000000000000#import "JAVertexSet.h" @implementation JAVertexSet - (id) init { if ((self = [super init])) { _indices = [NSMutableDictionary dictionary]; _vertices = [NSMutableArray array]; if (_indices == NULL || _vertices == NULL) return nil; } return self; } - (NSUInteger) indexForVertex:(Vertex)vertex { NSValue *value = [NSValue value:&vertex withObjCType:@encode(Vertex)]; NSNumber *number = [_indices objectForKey:value]; if (number == nil) { number = [NSNumber numberWithUnsignedInteger:[self count]]; [_indices setObject:number forKey:value]; [_vertices addObject:value]; } return [number unsignedIntegerValue]; } - (Vertex) vertexAtIndex:(NSUInteger)index { if (index >= [self count]) { [NSException raise:NSRangeException format:@"%s: attempt to access element %u of %u", __FUNCTION__, index, [self count]]; } Vertex result; [[_vertices objectAtIndex:index] getValue:&result]; return result; } - (NSUInteger) count { return [_vertices count]; } - (NSArray *) positionArray { unsigned i, count = [self count]; NSMutableArray *result = [NSMutableArray arrayWithCapacity:count * 3]; for (i = 0; i < count; i++) { Vertex v = [self vertexAtIndex:i]; [result addObject:[NSNumber numberWithDouble:v.v.x]]; [result addObject:[NSNumber numberWithDouble:v.v.y]]; [result addObject:[NSNumber numberWithDouble:v.v.z]]; } return result; } #if OUTPUT_BINORMALS - (NSArray *) binormalArray { unsigned i, count = self.count; NSMutableArray *result = [NSMutableArray arrayWithCapacity:count * 3]; for (i = 0; i < count; i++) { Vertex v = [self vertexAtIndex:i]; [result addObject:[NSNumber numberWithDouble:v.binormal.x]]; [result addObject:[NSNumber numberWithDouble:v.binormal.y]]; [result addObject:[NSNumber numberWithDouble:v.binormal.z]]; } return result; } #endif - (NSArray *) texCoordArray { unsigned i, count = [self count]; NSMutableArray *result = [NSMutableArray arrayWithCapacity:count * 2]; for (i = 0; i < count; i++) { Vertex v = [self vertexAtIndex:i]; [result addObject:[NSNumber numberWithDouble:v.s]]; [result addObject:[NSNumber numberWithDouble:v.t]]; } return result; } @end oolite-1.82/tools/icosmesh/OOOpenGLOnly.h000066400000000000000000000004501256642440500203150ustar00rootroot00000000000000typedef float GLfloat; typedef unsigned long GLenum; typedef unsigned char GLubyte; typedef unsigned short GLushort; typedef unsigned long GLuint; #define GL_UNSIGNED_BYTE 0x1401 #define GL_UNSIGNED_SHORT 0x1403 #define GL_UNSIGNED_INT 0x1405 oolite-1.82/tools/icosmesh/generatemesh.c000066400000000000000000000043551256642440500205430ustar00rootroot00000000000000#import "OOPlanetData.h" #import "OOMaths.h" #import #import #define kScale 500 #define UNWRAP 0 // Generate flat "unwrapped" mesh, demonstrating texture map construction. #if !UNWRAP static Vector GetVector(GLuint idx) { return make_vector(kOOPlanetVertices[idx * 3], kOOPlanetVertices[idx * 3 + 1], kOOPlanetVertices[idx * 3 + 2]); } static Vector GetNormal(GLuint a, GLuint b, GLuint c) { Vector va = GetVector(a); Vector vb = GetVector(b); Vector vc = GetVector(c); return vector_normal(vector_add(va, vector_add(vb, vc))); } #else static Vector GetVector(GLuint idx) { return make_vector(1.0 - kOOPlanetTexCoords[idx * 2] * 2.0f, 0.5 - kOOPlanetTexCoords[idx * 2 + 1], 0); } static Vector GetNormal(GLuint a, GLuint b, GLuint c) { return kBasisZVector; } #endif static void WriteDAT(unsigned i); int main (int argc, const char * argv[]) { unsigned i; for (i = 0; i < kOOPlanetDataLevels; i++) { WriteDAT(i); } return EXIT_SUCCESS; } static void WriteDAT(unsigned level) { const OOPlanetDataLevel *data = &kPlanetData[level]; char name[20]; snprintf(name, 20, "level_%u.dat", level); FILE *file = fopen(name, "w"); fprintf(file, "# Planet mesh export (level %u)\n\nNVERTS %u\nNFACES %u\n\nVERTEX\n", level, data->vertexCount, data->faceCount); unsigned i; for (i = 0; i < data->vertexCount; i++) { Vector v = vector_multiply_scalar(GetVector(i), kScale); fprintf(file, "%g, %g, %g\n", v.x, v.y, v.z); } fprintf(file, "\nFACES\n"); for (i = 0; i < data->faceCount; i++) { GLuint a = data->indices[i * 3]; GLuint b = data->indices[i * 3 + 1]; GLuint c = data->indices[i * 3 + 2]; Vector n = GetNormal(a, b, c); fprintf(file, "1,0,0, %+.5f, %+.5f, %+.5f, 3, %u,%u,%u\n", n.x, n.y, n.z, a, b, c); } fprintf(file, "\nTEXTURES\n"); for (i = 0; i < data->faceCount; i++) { GLuint a = data->indices[i * 3]; GLuint b = data->indices[i * 3 + 1]; GLuint c = data->indices[i * 3 + 2]; fprintf(file, "world.png 1 1 %.5f %.5f %.5f %.5f %.5f %.5f\n", kOOPlanetTexCoords[a * 2], kOOPlanetTexCoords[a * 2 + 1], kOOPlanetTexCoords[b * 2], kOOPlanetTexCoords[b * 2 + 1], kOOPlanetTexCoords[c * 2], kOOPlanetTexCoords[c * 2 + 1]); } fprintf(file, "\nEND\n"); fclose(file); } oolite-1.82/tools/icosmesh/icosmesh.h000066400000000000000000000025311256642440500177050ustar00rootroot00000000000000#import #import #define OUTPUT_BINORMALS 0 typedef struct { double x, y, z; } Vector; typedef struct { Vector v; #if OUTPUT_BINORMALS Vector binormal; #endif double s, t; // Lat/long texture coordinates } Vertex; // Convert vector to latitude and longitude (or θ and φ). void VectorToCoordsRad(Vector vc, double *latitude, double *longitude); void VectorToCoords0_1(Vector vc, double *latitude, double *longitude); static inline Vector VectorAdd(Vector u, Vector v) { return (Vector){ u.x + v.x, u.y + v.y, u.z + v.z }; } static inline Vector VectorSubtract(Vector u, Vector v) { return (Vector){ u.x - v.x, u.y - v.y, u.z - v.z }; } static inline double VectorDot(Vector u, Vector v) { return u.x * v.x + u.y * v.y + u.z * v.z; } static inline Vector VectorCross(Vector u, Vector v) { return (Vector) { u.y * v.z - v.y * u.z, u.z * v.x - v.z * u.x, u.x * v.y - v.x * u.y }; } static inline double VectorMagnitude(Vector v) { return sqrt(VectorDot(v, v)); } static inline Vector VectorScale(Vector v, double s) { return (Vector){ v.x * s, v.y * s, v.z * s }; } static inline Vector VectorScaleReciprocal(Vector v, double s) { return (Vector){ v.x / s, v.y / s, v.z / s }; } static inline Vector VectorNormal(Vector v) { return VectorScaleReciprocal(v, VectorMagnitude(v)); } oolite-1.82/tools/icosmesh/main.m000066400000000000000000000252311256642440500170260ustar00rootroot00000000000000/* icosmesh Tool to generate subdivided icosahedron mesh data. */ #import #import "icosmesh.h" #import "JAIcosTriangle.h" #import "JAVertexSet.h" #import "JAIcosMesh.h" #define kFileName "OOPlanetData" #define kLevels 6 /* Coordinates of icosahedron with poles on the Y axis. Based on http://www.csee.umbc.edu/~squire/reference/polyhedra.shtml#icosahedron */ /* Original vertices with the poles on the y axis static const Vector kBaseVertices[12] = { { +0.000000000000, +1.000000000000, +0.000000000000 }, { +0.894427200187, +0.447213577125, +0.000000000000 }, { +0.276393205089, +0.447213577125, +0.850650817090 }, { -0.723606805183, +0.447213577125, +0.525731117519 }, { -0.723606805183, +0.447213577125, -0.525731117519 }, { +0.276393205089, +0.447213577125, -0.850650817090 }, { +0.723606805183, -0.447213577125, +0.525731117519 }, { -0.276393205089, -0.447213577125, +0.850650817090 }, { -0.894427200187, -0.447213577125, +0.000000000000 }, { -0.276393205089, -0.447213577125, -0.850650817090 }, { +0.723606805183, -0.447213577125, -0.525731117519 }, { +0.000000000000, -1.000000000000, +0.000000000000 } };*/ /* New coordinates with y axis through the centre of the top face */ static const Vector kBaseVertices[12] = { { +0.607061998207, +0.794654472292, +0.000000000000 }, { +0.982246946377, -0.187592474085, +0.000000000000 }, { +0.491123473188, +0.187592474085, +0.850650808352 }, { -0.303530999103, +0.794654472292, +0.525731112119 }, { -0.303530999103, +0.794654472292, -0.525731112119 }, { +0.491123473188, +0.187592474085, -0.850650808352 }, { +0.303530999103, -0.794654472292, +0.525731112119 }, { -0.491123473188, -0.187592474085, +0.850650808352 }, { -0.982246946377, +0.187592474085, +0.000000000000 }, { -0.491123473188, -0.187592474085, -0.850650808352 }, { +0.303530999103, -0.794654472292, -0.525731112119 }, { -0.607061998207, -0.794654472292, +0.000000000000 } }; #define kBaseFaceCount 20 /* Original faces static const struct { unsigned a, b, c; } kBaseTriangles[kBaseFaceCount] = { { 0, 1, 2 }, { 0, 2, 3 }, { 0, 3, 4 }, { 0, 4, 5 }, { 0, 5, 1 }, { 11, 6, 7 }, { 11, 7, 8 }, { 11, 8, 9 }, { 11, 9, 10 }, { 11, 10, 6 }, { 1, 2, 6 }, { 2, 3, 7 }, { 3, 4, 8 }, { 4, 5, 9 }, { 5, 1, 10 }, { 6, 7, 2 }, { 7, 8, 3 }, { 8, 9, 4 }, { 9, 10, 5 }, { 10, 6, 1 } };*/ /* new faces with consistent winding */ static const struct { unsigned a, b, c; } kBaseTriangles[kBaseFaceCount] = { { 1, 0, 2 }, { 2, 0, 3 }, { 3, 0, 4 }, { 4, 0, 5 }, { 5, 0, 1 }, { 1, 2, 6 }, { 2, 3, 7 }, { 3, 4, 8 }, { 4, 5, 9 }, { 5, 1, 10 }, { 6, 10, 1 }, { 7, 6, 2 }, { 8, 7, 3 }, { 9, 8, 4 }, { 10, 9, 5 }, { 6, 11, 10 }, { 7, 11, 6 }, { 8, 11, 7 }, { 9, 11, 8 }, { 10, 11, 9 } }; static NSArray *SubdivideMesh(NSArray *triangles); static void WritePrelude(FILE *header, FILE *source); static void WriteVertices(FILE *header, FILE *source, JAVertexSet *vertices); static void WriteMeshForTriangles(FILE *source, unsigned level, NSArray *triangles, JAVertexSet *vertices, unsigned *faceCount, unsigned *maxVertex); static void WriteMesh(FILE *source, unsigned level, JAIcosMesh *mesh); static const char *SizeEnumeratorForMaximum(unsigned maxValue); static const char *SizeTypeForMaximum(unsigned maxValue); int main (int argc, const char * argv[]) { /* Apparently, someone wants to run this under Linux, so we make an autorelease pool to suppress warnings when running without gc. */ (void)[NSAutoreleasePool new]; FILE *header = fopen(kFileName ".h", "w"); FILE *source = fopen(kFileName ".c", "w"); if (header == NULL || source == NULL) { fprintf(stderr, "Failed to open output files.\n"); return EXIT_FAILURE; } WritePrelude(header, source); // Load up the base triangles. unsigned i; NSMutableArray *baseTriangles = [NSMutableArray arrayWithCapacity:kBaseFaceCount]; for (i = 0; i < kBaseFaceCount; i++) { Vector a = kBaseVertices[kBaseTriangles[i].a]; Vector b = kBaseVertices[kBaseTriangles[i].b]; Vector c = kBaseVertices[kBaseTriangles[i].c]; // printf("%g %g %g %g %g %g %g %g %g\n", a.x, a.y, a.z, b.x, b.y, b.z, c.x, c.y, c.z); JAIcosTriangle *tri = [JAIcosTriangle triangleWithVectorA:a b:b c:c]; [baseTriangles addObject:tri]; } unsigned faceCount[kLevels]; unsigned maxIndex[kLevels]; JAVertexSet *vertices = [[JAVertexSet alloc] init]; WriteMeshForTriangles(source, 0, baseTriangles, vertices, &faceCount[0], &maxIndex[0]); NSArray *triangles = baseTriangles; for (i = 1; i < kLevels; i++) { triangles = (NSMutableArray *)SubdivideMesh(triangles); WriteMeshForTriangles(source, i, triangles, vertices, &faceCount[i], &maxIndex[i]); } WriteVertices(header, source, vertices); fprintf(source, "\n\nconst OOPlanetDataLevel kPlanetData[kOOPlanetDataLevels] =\n{\n"); for (i = 0; i < kLevels; i++) { if (i != 0) fprintf(source, ",\n"); fprintf(source, "\t{ %u, %u, %s, kFaceIndicesLevel%u }", maxIndex[i], faceCount[i], SizeEnumeratorForMaximum(maxIndex[i]), i); } fprintf(source, "\n};\n"); fclose(header); fclose(source); return EXIT_SUCCESS; } static NSArray *SubdivideMesh(NSArray *triangles) { NSMutableArray *result = [NSMutableArray arrayWithCapacity:[triangles count] * 4]; JAIcosTriangle *triangle; NSInteger i; for( i = 0; i < [triangles count]; i++ ) { triangle = (JAIcosTriangle*)[triangles objectAtIndex: i]; [result addObjectsFromArray:[triangle subdivide]]; } return result; } static void WritePrelude(FILE *header, FILE *source) { fprintf(header, "/*\n" "\t%s.h\n" "\tFor Oolite\n" "\t\n" "\tThis file was automatically generated by tools/icosmesh. Do not modify.\n" "\t\n" "\tThis data may be used freely.\n" "*/\n" "\n" "#include \"OOOpenGLOnly.h\"\n" "\n" "\n" "#define kOOPlanetDataLevels %u\n" "\n" "\n" "typedef struct\n" "{\n" "\tunsigned vertexCount;\n" "\tunsigned faceCount;\n" "\tGLenum type;\n" "\tconst void *indices; // faceCount * 3\n" "} OOPlanetDataLevel;\n" "\n" "\n" "extern const OOPlanetDataLevel kPlanetData[kOOPlanetDataLevels];\n", kFileName, kLevels); fprintf(source, "/*\n" "\t%s.c\n" "\tFor Oolite\n" "\t\n" "\tThis file was automatically generated by tools/icosmesh. Do not modify.\n" "\t\n" "\tThis data may be used freely.\n" "*/\n" "\n" "#include \"OOPlanetData.h\"\n", kFileName); } static void WriteVertices(FILE *header, FILE *source, JAVertexSet *vertices) { unsigned i, count = [vertices count]; fprintf(header, "\n\n#define kOOPlanetDataVertexCount %u\n\n", count); fprintf(header, "extern const GLfloat kOOPlanetVertices[kOOPlanetDataVertexCount * 3];\n"); #if OUTPUT_BINORMALS fprintf(header, "extern const GLfloat kOOPlanetBinormals[kOOPlanetDataVertexCount * 3];\n"); #endif fprintf(header, "extern const GLfloat kOOPlanetTexCoords[kOOPlanetDataVertexCount * 2];\n"); fprintf(source, "\n\n/* Shared vertex array\n %u vertices\n*/\nconst GLfloat kOOPlanetVertices[kOOPlanetDataVertexCount * 3] =\n{\n", count); NSArray *data = [vertices positionArray]; for (i = 0; i < count; i++) { if (i != 0) fprintf(source, ",\n"); fprintf(source, "\t%+.8ff, %+.8ff, %+.8ff", [[data objectAtIndex:i * 3] doubleValue], [[data objectAtIndex:i * 3 + 1] doubleValue], [[data objectAtIndex:i * 3 + 2] doubleValue]); } #if OUTPUT_BINORMALS fprintf(source, "\n};\n\n/* Shared binormal array\n %u vectors\n*/\nconst GLfloat kOOPlanetBinormals[kOOPlanetDataVertexCount * 3] =\n{\n", count); data = [vertices binormalArray]; for (i = 0; i < count; i++) { if (i != 0) fprintf(source, ",\n"); fprintf(source, "\t%+.8ff, %+.8ff, %+.8ff", [[data objectAtIndex:i * 3] doubleValue], [[data objectAtIndex:i * 3 + 1] doubleValue], [[data objectAtIndex:i * 3 + 2] doubleValue]); } #endif fprintf(source, "\n};\n\n/* Shared texture coordinate array\n %u pairs\n*/\nconst GLfloat kOOPlanetTexCoords[kOOPlanetDataVertexCount * 2] =\n{\n", count); data = [vertices texCoordArray]; for (i = 0; i < count; i++) { if (i != 0) fprintf(source, ",\n"); fprintf(source, "\t%+.8ff, %+.8ff", [[data objectAtIndex:i * 2] doubleValue], [[data objectAtIndex:i * 2 + 1] doubleValue]); } fprintf(source, "\n};\n"); } static void WriteMeshForTriangles(FILE *source, unsigned level, NSArray *triangles, JAVertexSet *vertices, unsigned *faceCount, unsigned *maxVertex) { JAIcosMesh *mesh = [JAIcosMesh meshWithVertexSet:vertices]; [mesh addTriangles:triangles]; WriteMesh(source, level, mesh); *faceCount = [mesh faceCount]; *maxVertex = [mesh maxIndex] + 1; } static void WriteMesh(FILE *source, unsigned level, JAIcosMesh *mesh) { unsigned i, count = [mesh faceCount]; NSArray *indices = [mesh indexArray]; fprintf(source, "\n\n/* Level %u index array\n %u faces\n*/\nstatic const %s kFaceIndicesLevel%u[%u] =\n{\n", level, count, SizeTypeForMaximum([mesh maxIndex]), level, count * 3); for (i = 0; i < count; i++) { if (i != 0) fprintf(source, ",\n"); fprintf(source, "\t%5u, %5u, %5u", [[indices objectAtIndex:i * 3] unsignedIntValue], [[indices objectAtIndex:i * 3 + 1] unsignedIntValue], [[indices objectAtIndex:i * 3 + 2] unsignedIntValue]); } fprintf(source, "\n};\n"); } // Convert vector to latitude and longitude (or θ and φ). void VectorToCoordsRad(Vector v, double *latitude, double *longitude) { v = VectorNormal(v); double las = v.y; if (las != 1.0f) { double lat = asin(las); double rlac = 1.0 / sqrt(1.0 - las * las); // Equivalent to abs(1/cos(lat)) if (latitude != NULL) *latitude = lat; if (longitude != NULL) { double los = v.x * rlac; double lon = asin(fmin(1.0, fmax(-1.0, los))); // Quadrant rectification. if (v.z < 0.0f) { // We're beyond 90 degrees of longitude in some direction. if (v.x < 0.0f) { // ...specifically, west. lon = -M_PI - lon; } else { // ...specifically, east. lon = M_PI - lon; } } *longitude = lon; } } else { // Straight up, avoid divide-by-zero if (latitude != NULL) *latitude = M_PI / 2.0f; if (longitude != NULL) *longitude = 0.0f; // arbitrary } } void VectorToCoords0_1(Vector v, double *latitude, double *longitude) { VectorToCoordsRad(v, latitude, longitude); if (latitude != NULL) *latitude = 1.0 - (*latitude / M_PI + 0.5); if (longitude != NULL) *longitude = 1.0 - (*longitude / (M_PI * 2.0) + 0.5); } static const char *SizeEnumeratorForMaximum(unsigned maxValue) { if (maxValue <= UCHAR_MAX) return "GL_UNSIGNED_BYTE"; if (maxValue <= USHRT_MAX) return "GL_UNSIGNED_SHORT"; return "GL_UNSIGNED_INT"; } const char *SizeTypeForMaximum(unsigned maxValue) { if (maxValue <= UCHAR_MAX) return "GLubyte"; if (maxValue <= USHRT_MAX) return "GLushort"; return "GLuint"; } oolite-1.82/tools/mkmanifest.sh000077500000000000000000000011751256642440500166100ustar00rootroot00000000000000#! /bin/sh OOLITE_VERSION_FILE="src/Cocoa/oolite-version.xcconfig" if [ $# -ge 1 ]; then cd $1 if [ $? ]; then exit $? fi fi # Extract definition of $OOLITE_VERSION from the xcconfig . $OOLITE_VERSION_FILE echo "{" echo " title = \"Oolite core\";" echo " identifier = \"org.oolite.oolite\";" echo " " echo " version = \"$OOLITE_VERSION\";" echo " required_oolite_version = \"$OOLITE_VERSION\";" echo " " echo " license = \"GPL 2+ / CC-BY-NC-SA 3.0 - see LICENSE.TXT for details\";" echo " author = \"Giles Williams, Jens Ayton and contributors\";" echo " information_url = \"http://www.oolite.org/\";" echo "}" oolite-1.82/tools/mknightly000077500000000000000000000036571256642440500160560ustar00rootroot00000000000000#!/bin/sh # This shell script is used to make a nightly GNUstep/SDL build from # cron. It is intended to update an rsync repository. # # Arguments: # tools/mknightly # # where destination is the rsync repository. The destination must already # exist. # # The script should be run from the root of the svn working copy. # # Dylan Smith, 2006-03-10 # OS=`uname` CPU=`uname -m` VERSION=`grep SoftwareVersion installers/autopackage/default.x86.apspec|cut -d ' ' -f 2` DESTINATION=$1 TREEROOT=`pwd` if [ "$DESTINATION" == "" ] then echo "Usage: tools/mknightly " exit 255 fi if [ "$VERSION" == "" ] then echo "I can't find the apspec file. This script needs to be run from" echo "the repository top level directory to find all it needs." exit 255 fi svn up >/dev/null if [ $? != 0 ] then echo "Nightly build could not svn up" exit 255 fi # check whether we should even bother building LASTBUILD=`cat LASTBUILD` THISBUILD=`svn info . | grep Revision | cut -d ' ' -f 2` if [ $? != 0 ] then echo "Could not get build revision" exit 255 fi if [ "$LASTBUILD" == "$THISBUILD" ] then echo "Nightly build up to date - lastbuild=$LASTBUILD thisbuild=$THISBUILD" exit 0 fi if [ "$CPU" == 'i686' ] then CPU="x86" fi DEPSDIR="$TREEROOT/deps/$OS-$CPU-deps" if [ ! -d $DEPSDIR ] then echo "Dependencies directory $DEPSDIR not found" exit 255 fi make > make.out 2> make.error if [ $? != 0 ] then echo "Oolite mknightly died." exit $? fi echo $THISBUILD > LASTBUILD svn log > $DESTINATION/changelog.txt cp -rf oolite.app $DESTINATION cd $DEPSDIR cp -rf oolite-deps $DESTINATION find $DESTINATION -name .svn -exec rm -rf {} \; 2>/dev/null cd $DESTINATION echo $VERSION-$THISBUILD > release.txt echo "Oolite-$OS $CPU (`whoami`@`uname -n`) SVN revision $THISBUILD" \ > buildinfo.txt echo "Build date: `date`" >> buildinfo.txt echo "Nightly build complete for Oolite-$OS version $VERSION-$THISBUILD" oolite-1.82/tools/mksrctarballs000077500000000000000000000041471256642440500167070ustar00rootroot00000000000000#!/bin/sh VERSION=`grep SoftwareVersion installers/autopackage/default.x86.apspec|cut -d ' ' -f 2` DESTINATION=`pwd`/TarballPackages TREEROOT=`pwd` if [ "$VERSION" == "" ] then echo "I can't find the apspec file. This script needs to be run from" echo "the repository top level directory to find all it needs." exit 255 fi if [ -e $DESTINATION/oolite-$VERSION-src.tar.gz ]; then echo "Source Tarball for Version $VERSION already exists" # TODO - query / param to continue anyway exit 0 fi # clear out the cruft rm -rf $DESTINATION/oolite-$VERSION-src rm -f $DESTINATION/oolite-$VERSION-src.tar.gz rm -rf $DESTINATION/oolite-$VERSION-data rm -f $DESTINATION/oolite-$VERSION-data.tar.gz if ! mkdir -p $DESTINATION/oolite-$VERSION-src; then echo "Couldn't create directory for the source files" exit 255 fi if ! mkdir -p $DESTINATION/oolite-$VERSION-data; then echo "Couldn't create directory for the data files" exit 255 fi for i in autopackage debian deps Doc FreeDesktop installers Schemata Oolite-importer Oolite.xcodeproj src tools GNUmakefile GNUmakefile.postamble Makefile README.txt contributors.txt do if ! cp -r $i $DESTINATION/oolite-$VERSION-src; then echo "Failed to copy $i to $DESTINATION/oolite-$VERSION-src" exit 255 fi done if ! cp -r Resources $DESTINATION/oolite-$VERSION-data; then echo "Failed to copy Resources to $DESTINATION/oolite-$VERSION-data" exit 255 fi # Save the SVN Revision number in the source tarball for the tools/mkversion script echo `tools/mkversion --subversion` > $DESTINATION/oolite-$VERSION-src/svnrevision #echo "Press a key to continue" #read -s -n 1 cd $DESTINATION mv oolite-$VERSION-src oolite-$VERSION if ! tar zcf oolite-$VERSION-src.tar.gz oolite-$VERSION --exclude .svn then echo "Failed to create src tarball" exit 255 fi # so the data tarball has the same path as src rm -rf oolite-$VERSION mv oolite-$VERSION-data oolite-$VERSION if ! tar zcf oolite-$VERSION-data.tar.gz oolite-$VERSION --exclude .svn then echo "Failed to create data tarball" exit 255 fi rm -rf oolite-$VERSION echo "Tarballs placed in $DESTINATION." oolite-1.82/tools/mktarballs000077500000000000000000000024341256642440500161740ustar00rootroot00000000000000#!/bin/sh OS=`uname` CPU=`uname -m` VERSION=`grep SoftwareVersion installers/autopackage/default.x86.apspec|cut -d ' ' -f 2` DESTINATION=`pwd`/TarballPackages TREEROOT=`pwd` if [ "$VERSION" == "" ] then echo "I can't find the apspec file. This script needs to be run from" echo "the repository top level directory to find all it needs." exit 255 fi if [ $CPU == 'i686' ] then CPU="x86" fi DEPSDIR="$TREEROOT/deps/$OS-$CPU-deps" if [ -d $DEPSDIR ] then echo "Creating tarball binary package Oolite-$OS-$VERSION-$CPU.tar.gz" else echo "No dependencies package exists for platform $OS-$CPU" exit 255 fi if make; then echo "Build completed OK" else echo "Build failed, exiting." exit 255 fi echo "Bundling oolite.app" PKGDIR=$DESTINATION/oolite-installer rm -rf $PKGDIR mkdir -p $PKGDIR tar cf $PKGDIR/oolite-app.tar oolite.app --exclude .svn echo "Bundling dependency package" cp Doc/FAQ.TXT Doc/LICENSE.TXT $PKGDIR cd $DEPSDIR cp install oolite-update.src oolite.src PLAYING.TXT README.TXT $PKGDIR tar cf $PKGDIR/oolite-deps.tar oolite-deps --exclude .svn echo $VERSION >$PKGDIR/release.txt cd $DESTINATION tar zcf Oolite-$OS-$VERSION-$CPU.tar.gz oolite-installer echo "Removing temporary files" rm -rf $PKGDIR echo "Created $DESTINATION/Oolite-$OS-$VERSION-$CPU.tar.gz" oolite-1.82/tools/mkwininst.sh000066400000000000000000000016031256642440500164660ustar00rootroot00000000000000# Assumed to be running in trunk export SRC="/c/Program Files/Oolite" export DST=$GNUSTEP_LOCAL_ROOT/oolite/tmp export VER=`awk -- '/SoftwareVersion/ { print $2 }' installers/autopackage/default.x86.apspec` echo building: $VER if [ -d $DST ]; then echo "removing old setup files" rm -rf $DST fi mkdir $DST mkdir $DST/oolite.app mkdir $DST/AddOns echo "coping existing installation to setup folder" cp "$SRC/"* $DST cp -r "$SRC/oolite.app/"* $DST/oolite.app echo "cleaning up unwanted files" rm $DST/*.txt rm $DST/*.exe rm $DST/*.bat rm -rf $DST/oolite.app/GNUstep/Defaults/.GNUstepDefaults rm -rf $DST/oolite.app/oolite-saves/* echo "making Oolite" make echo "copying new build to setup folder" cp -r oolite.app/* $DST/oolite.app rm $DST/oolite.app/oolite.exe.a echo "making installer" cd installers/win32 "/c/Program Files/NSIS/makensis" oolite.nsi oolite-1.82/tools/mkwinsnapshotinst.sh000066400000000000000000000021561256642440500202520ustar00rootroot00000000000000# Assumed to be running in the root of a working copy (eg trunk, tags/1.64, etc) # so this script has to be run like: tools/mkwinsnapshotinst.sh svn up export SRC="/c/Program Files/Oolite" export DST=$GNUSTEP_LOCAL_ROOT/oolite/tmp export VER=`awk -- '/SoftwareVersion/ { print $2 }' installers/autopackage/default.x86.apspec` export SVNREV=`svn info . | awk -- '/Revision:/ { print $2 }'` echo building: $VER from svn revision $SVNREV if [ -d $DST ]; then echo "removing old setup files" rm -rf $DST fi mkdir $DST mkdir $DST/oolite.app mkdir $DST/AddOns echo "making Oolite" make clean make debug=no echo "copying new build to setup folder" cp -r oolite.app/* $DST/oolite.app rm $DST/oolite.app/oolite.exe.a find $DST -type d -name '.svn' -exec rm -rf {} \; cp deps/Windows-x86-deps/DLLs/* $DST/oolite.app echo "making installer" cd installers/win32 echo Oolite v$VER, snapshot build of svn revision $SVNREV \(`date -I`\) >$DST/Oolite_Readme.txt cat Oolite_Readme.txt >>$DST/Oolite_Readme.txt cp ../../Doc/OoliteRS.pdf $DST "/c/Program Files/NSIS/makensis" OoliteSnapshot_ModernUI.nsi oolite-1.82/tools/nightly-build/000077500000000000000000000000001256642440500166625ustar00rootroot00000000000000oolite-1.82/tools/nightly-build/mac/000077500000000000000000000000001256642440500174225ustar00rootroot00000000000000oolite-1.82/tools/nightly-build/mac/README.txt000066400000000000000000000020121256642440500211130ustar00rootroot00000000000000Running Mac nightly builds: * Get a Mac, install Xcode, etc. * Change config/ftp_url to something suitable, and add whatever credentials are necessary to config/ftp_credentials (including the appropriate curl command line switches, e.g. "-u user:password"). * Set appropriate path and user info in org.oolite.oolite.nightly.plist. * Copy org.oolite.oolite.nightly.plist to /Library/LaunchAgents, and change its owner and group to root:wheel. * Run this command: sudo launchctl load /Library/LaunchAgents/org.oolite.oolite.nightly.plist To test the build, run ./build-nighly. The code will be checked out automatically and dependencies will be downloaded. A directory named "var" will appear alongside the build script when it is run. This contains a number of log files as well as files tracking build state. To force a build when the repository has not been updated, delete the var directory or just var/revision. BUGS: in true UNIX tradition, this script will blow up in your face if there's a space in its working path. oolite-1.82/tools/nightly-build/mac/build-nightly000077500000000000000000000163011256642440500221240ustar00rootroot00000000000000#! /bin/bash REPOSITORY="https://github.com/OoliteProject/oolite.git" BRANCH="master" CONFIGURATION="TestRelease" VAR_DIR_NAME="var" REPO_DIR_NAME="working_copy" VAR="`pwd`/$VAR_DIR_NAME/" LOG="$VAR/log/" LOG_OLD="$VAR/log-old/" LOG_SUCCESS="$VAR/log-success/" COCOA_SRC_DIR="src/Cocoa/" SNAPSHOT_MACRO_FILE="$COCOA_SRC_DIR/oolite-snapshot.xcconfig" BUILD_DIR="build/$CONFIGURATION/" LAST_REVISION_FILE="$VAR/revision" SUCCESS_REVISION_FILE="$VAR/success_revision" PREVIOUS_SUCCESS_REVISION_FILE="$VAR/previous_success_revision" LATEST_NAME_FILE="$VAR/latest" CHANGELOG_FILE="$LOG/change_log" DIAGNOSTICS_FILE="$LOG/diagnostics" STDERR_FILE="$LOG/xcb_stderr.txt" STDOUT_FILE="$LOG/xcb_stdout.txt" STATUS_FILE="$LOG/build_log.txt" GIT_LOG_FILE="$LOG/git_log.txt" LAST_UPDATED_FILE="$VAR/last_updated" OOLITE_APP_PKG="$BUILD_DIR/Oolite.app" DEBUG_OXP_PKG="DebugOXP/Debug.oxp" FTP_URL_FILE="`pwd`/config/ftp_url" FTP_CREDENTIALS_FILE="`pwd`/config/ftp_credentials" FTP_INFO_SET=0 if [ ! "$GIT" ] then GIT="git" fi if [ ! "$CURL" ] then CURL="curl" fi if [ ! "$DITTO" ] then DITTO="ditto" fi function report { if [ `which growlnotify` ] then growlnotify -n "Oolite nightly build" -s "$1" -m "$2" fi echo $2 } function fail { if [ $FTP_INFO_SET -eq 1 ] && [ -e "$LAST_REVISION_FILE" ] then curl --silent $FTP_CREDENTIALS -T "$LAST_REVISION_FILE" "$FTP_URL/revision" fi report "Oolite nightly build failure" "$1" exit 1 } # Ensure output directories exist. if [ ! -e $VAR ] then mkdir $VAR fi if [ -e $LOG ] then if [ -e $LOG_OLD ] then rm -rf $LOG_OLD fi mv $LOG $LOG_OLD fi mkdir $LOG # Clear git log so we can append to it freely touch "$GIT_LOG_FILE" echo "" > "$GIT_LOG_FILE" if [ ! -e "$REPO_DIR_NAME" ] then # Clone repo if necessary. CLEAN_CHECKOUT=1 echo "Cloning Oolite..." "$GIT" clone --quiet "$REPOSITORY" "$REPO_DIR_NAME" >> "$GIT_LOG_FILE" if [ ! $? ] then fail "Failed to clone repository." fi STATUS=0 cd "$REPO_DIR_NAME" "$GIT" checkout "$BRANCH" else # Revert the file we manipulate for watermarking, then update repo. CLEAN_CHECKOUT=0 cd "$REPO_DIR_NAME" echo "Updating..." "$GIT" checkout -- "$SNAPSHOT_MACRO_FILE" >> "$GIT_LOG_FILE" "$GIT" pull --quiet --ff-only >> "$GIT_LOG_FILE" STATUS=$? fi # Get $OOLITE_VERSION source $COCOA_SRC_DIR/oolite-version.xcconfig # Make sure we're on the right branch, and update submodules. "$GIT" checkout "$BRANCH" STATUS=$? if [ $STATUS ] then "$GIT" reset --hard "origin/$BRANCH" STATUS=$? fi if [ $STATUS ] then "$GIT" submodule update --quiet --init --recursive >> "$GIT_LOG_FILE" STATUS=$? fi if [ ! $STATUS ] then echo "Update failed: " > $DIAGNOSTICS_FILE cat $GIT_LOG_FILE >> $DIAGNOSTICS_FILE echo $STATUS fail "Code update failed." fi # Get last revision. if [ ! -e $LAST_REVISION_FILE ] then LAST_REVISION=0 else LAST_REVISION=`head -n 1 "$LAST_REVISION_FILE"` fi # Get current revision. CURRENT_REVISION=`"$GIT" rev-parse --short HEAD` if [ ! $? ] then fail "Could not get revision hash." fi if [ $LAST_REVISION == $CURRENT_REVISION ] then report "Oolite nightly unchanged" "No change, still at revision $LAST_REVISION." exit 0 fi echo "Updated from revision $LAST_REVISION to $CURRENT_REVISION, building..." # Delete existing build products so no old files are left lying around. if [ -e $OOLITE_APP_PKG ] then rm -rf $OOLITE_APP_PKG fi # Nastytacular: overwrite the oolite-snapshot.xcconfig file to generate the nightly watermark macros. # The backslash jungle builds a string like -DOOLITE_SNAPSHOT_VERSION="\"1.77.1-130705\"". DATESTAMP=`date "+%y%m%d"` echo "SNAPSHOT_MACROS= -DSNAPSHOT_BUILD=1 -DOOLITE_SNAPSHOT_VERSION=\"\\\"$OOLITE_VERSION-$DATESTAMP\\\"\"" > src/Cocoa/oolite-snapshot.xcconfig # Buildez la vache! xcodebuild clean build -project Oolite.xcodeproj -target "Build All" -configuration $CONFIGURATION RUN_CLANG_STATIC_ANALYZER=NO >$STDOUT_FILE 2>$STDERR_FILE # Generate status and diagnostics files. grep "^[^\ ]" $STDOUT_FILE > $STATUS_FILE grep -e "warning:" -e "error:" $STDOUT_FILE > $DIAGNOSTICS_FILE # Set last revision regardless of success or failure, on the basis that a # failed build isn't likely to suddenly work if you just retry it a day later. echo $CURRENT_REVISION > $LAST_REVISION_FILE # Check for failure. grep -e "^\*\* BUILD FAILED \*\*$" $STDERR_FILE if [ $? -ne 1 ] then ERROR_COUNT=`grep "error:" $STDOUT_FILE | wc -l | tr -d " "` WARNING_COUNT=`grep "warning:" $STDOUT_FILE | wc -l | tr -d " "` fail "Build failed with $ERROR_COUNT errors and $WARNING_COUNT warnings." fi # Write changelog. if [ $LAST_REVISION != 0 ] then "$GIT" log --pretty=format:"%h %an %ad %s" --no-merges $LAST_REVISION..$CURRENT_REVISION > $CHANGELOG_FILE else echo "Initial checkout." > $CHANGELOG_FILE fi mv $SUCCESS_REVISION_FILE $PREVIOUS_SUCCESS_REVISION_FILE echo $CURRENT_REVISION > $SUCCESS_REVISION_FILE # Keep logs of latest successful build. if [ -e $LOG_SUCCESS ] then rm -rf $LOG_SUCCESS fi cp -R $LOG $LOG_SUCCESS # Pack up a zip file. if ! echo "$OOLITE_VERSION" | grep "[0-9]*\.[0-9]*\.[0-9]*" > /dev/null then OOLITE_VERSION="$OOLITE_VERSION.0" fi FULLVERSION=$OOLITE_VERSION.$DATESTAMP-dev echo "Packaging nightly $FULLVERSION..." PACKAGE_NAME=Oolite-nightly-$FULLVERSION PACKAGE_DIR=$VAR/$PACKAGE_NAME/ ADDONS_DIR=$PACKAGE_DIR/AddOns/ ZIP_NAME=oolite-trunk-$FULLVERSION.mac.zip echo $ZIP_NAME > "$LATEST_NAME_FILE" mkdir $PACKAGE_DIR cp -R $OOLITE_APP_PKG $PACKAGE_DIR mkdir $ADDONS_DIR cp -R $DEBUG_OXP_PKG $ADDONS_DIR pushd $VAR > /dev/null "$DITTO" -ck --noqtn --noacl --nocache --zlibCompressionLevel 9 "$PACKAGE_NAME" "$ZIP_NAME" if [ ! $? ] then fail "Could not create zip archive (ditto failed with exit code $?)." fi rm -rf $PACKAGE_NAME popd > /dev/null # ftp_url should contain a URL to an upload directory. # ftp_credentials should contain command line arguments for curl authentication, # such as -u user:password. if [ -e $FTP_URL_FILE ] && [ -e $FTP_CREDENTIALS_FILE ] then FTP_URL=`head -n 1 "$FTP_URL_FILE"` FTP_CREDENTIALS=`head -n 1 "$FTP_CREDENTIALS_FILE"` FTP_INFO_SET=1 else FTP_INFO_SET=0 fi # Record date of this momentous event. date > $LAST_UPDATED_FILE if [ ! -e $VAR/$ZIP_NAME ] then fail "Archive was not made for some reason." fi if [ $FTP_INFO_SET -eq 0 ] then report "Oolite nightly success" "Build succeeded, but ftp_url and/or ftp_credentials are missing, so data cannot be uploaded." exit 0 fi echo "Uploading $ZIP_NAME..." "$CURL" "-#" $FTP_CREDENTIALS -T "$VAR/$ZIP_NAME" "$FTP_URL/$ZIP_NAME" if [ ! $? ] then echo "" fail "Upload failed ($?)." fi echo "" echo "Uploading metadata..." "$CURL" --silent $FTP_CREDENTIALS -T "$LAST_UPDATED_FILE" "$FTP_URL/last_updated" "$CURL" --silent $FTP_CREDENTIALS -T "$CHANGELOG_FILE" "$FTP_URL/change_log" "$CURL" --silent $FTP_CREDENTIALS -T "$DIAGNOSTICS_FILE" "$FTP_URL/diagnostics" "$CURL" --silent $FTP_CREDENTIALS -T "$STATUS_FILE" "$FTP_URL/build_log" "$CURL" --silent $FTP_CREDENTIALS -T "$LAST_REVISION_FILE" "$FTP_URL/revision" "$CURL" --silent $FTP_CREDENTIALS -T "$PREVIOUS_SUCCESS_REVISION_FILE" "$FTP_URL/previous_successful" "$CURL" --silent $FTP_CREDENTIALS -T "$LATEST_NAME_FILE" "$FTP_URL/latest" rm $VAR/$ZIP_NAME report $"Oolite nightly success" "Successfully built commit $CURRENT_REVISION and uploaded as $ZIP_NAME." oolite-1.82/tools/nightly-build/mac/config/000077500000000000000000000000001256642440500206675ustar00rootroot00000000000000oolite-1.82/tools/nightly-build/mac/config/ftp_url000066400000000000000000000000321256642440500222600ustar00rootroot00000000000000ftp://ftp.oolite.org/mac/ oolite-1.82/tools/nightly-build/mac/index.php000066400000000000000000000010611256642440500212400ustar00rootroot00000000000000 Oolite nightly (Mac OS X)

    Changes since last nightly

    oolite-1.82/tools/nightly-build/mac/org.oolite.oolite.nightly.plist000066400000000000000000000017701256642440500255340ustar00rootroot00000000000000 UserName jayton WorkingDirectory /Users/jayton/Programming/Projects/Oolite/nightly/ Program /Users/jayton/Programming/Projects/Oolite/nightly/build StandardOutPath /Users/jayton/Programming/Projects/Oolite/nightly/build/stdout.txt StandardErrorPath /Users/jayton/Programming/Projects/Oolite/nightly/build/stderr.txt StartCalendarInterval Hour 4 Minute 30 Label org.oolite.oolite.nightly LowPriorityIO Nice 1 oolite-1.82/tools/oxp-templates/000077500000000000000000000000001256642440500167115ustar00rootroot00000000000000oolite-1.82/tools/oxp-templates/encodings/000077500000000000000000000000001256642440500206625ustar00rootroot00000000000000oolite-1.82/tools/oxp-templates/encodings/Cyrillic.oxp/000077500000000000000000000000001256642440500232415ustar00rootroot00000000000000oolite-1.82/tools/oxp-templates/encodings/Cyrillic.oxp/Config/000077500000000000000000000000001256642440500244465ustar00rootroot00000000000000oolite-1.82/tools/oxp-templates/encodings/Cyrillic.oxp/Config/descriptions.plist000066400000000000000000000001021256642440500302220ustar00rootroot00000000000000{ condition = ( "Доскєd", "Green", "Yellow", "Red" ); } oolite-1.82/tools/oxp-templates/encodings/Cyrillic.oxp/Config/oolite-font.plist000066400000000000000000000125041256642440500277640ustar00rootroot00000000000000{ encoding = "windows-cyrillic"; texture = "oolite-font-cy.png"; substitutions = { /* This section specifies automatic string conversions. Some of these were hard-coded in previous versions of Oolite. Other previously hard-coded conversions are no longer needed because of the expanded font (curly quotes are now supported, for instance). */ "₢" = "€"; // Cruziero Sign to Euro Sign. Oolite's font uses a Cruziero sign in place of the Euro sign. This way, either can be used in text to generate ₢, the sign used as a credits currency symbol. "⌘" = "(Command)"; // Place Of Interest Sign, used under Mac OS X as symbol for the Command key. "⌥" = "(Option)"; // Mac OS X option key "⌃" = "(Control)"; // Up Arrowhead, Mac OS X control key // The following are for compatibility with GrowlTunes (and have been included since Growl support was added). "★" = "\b"; // Black Star "✯" = "\b"; // Pinwheel Star "☆" = "\030"; // White Star "♭" = "b"; // Musical Flat Sign "♮" = "="; // Musical Natural Sign "♯" = "#"; // Musical Sharp Sign " " = "\037"; // Hair Space }; widths = ( 8, 7, 8, 8, 7, 6, 7, 6, 6.25, 1.7364501953125, 6, 6, 6, 6, 6, 6, 5, 6, 6, 6, 6, 6, 7.5, 8, 6.25, 6, 6, 6, 6, 6, 6, 0.1, // the 'unit separator' control character \037. Usable as a narrow space for lining up columns 1.7364501953125, 2.081298828125, 2.9632568359375, 3.4759521484375, 3.4759521484375, 5.5572509765625, 4.5135498046875, 1.4862060546875, 2.081298828125, 2.081298828125, 2.4322509765625, 3.64990234375, 1.7364501953125, 2.081298828125, 1.7364501953125, 1.7364501953125, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.4759521484375, 2.081298828125, 2.081298828125, 3.64990234375, 3.64990234375, 3.64990234375, 3.8177490234375, 6.0943603515625, 4.5135498046875, 4.5135498046875, 4.5135498046875, 4.5135498046875, 4.168701171875, 3.8177490234375, 4.8614501953125, 4.5135498046875, 1.7364501953125, 3.4759521484375, 4.5135498046875, 3.8177490234375, 5.206298828125, 4.5135498046875, 4.8614501953125, 4.168701171875, 4.8614501953125, 4.5135498046875, 4.168701171875, 3.8177490234375, 4.5135498046875, 4.168701171875, 5.8990478515625, 4.168701171875, 4.168701171875, 3.8177490234375, 2.081298828125, 1.7364501953125, 2.081298828125, 3.64990234375, 3.4759521484375, 2.081298828125, 3.4759521484375, 3.8177490234375, 3.4759521484375, 3.8177490234375, 3.4759521484375, 2.081298828125, 3.8177490234375, 3.8177490234375, 1.7364501953125, 1.7364501953125, 3.4759521484375, 1.7364501953125, 5.5572509765625, 3.8177490234375, 3.8177490234375, 3.8177490234375, 3.8177490234375, 2.4322509765625, 3.4759521484375, 2.081298828125, 3.8177490234375, 3.4759521484375, 4.8614501953125, 3.4759521484375, 3.4759521484375, 3.125, 2.4322509765625, 1.7486572265625, 2.4322509765625, 3.64990234375, 0, 4.5135498046875, 0, 1.7364501953125, 0, 3.125, 6.25, 3.4759521484375, 3.4759521484375, 0, 6.25, 4.168701171875, 2.081298828125, 4.168701171875, 3.8177490234375, 3.8177490234375, 3.8177490234375, 0, 1.7364501953125, 1.7364501953125, 3.125, 3.125, 2.1881103515625, 3.4759521484375, 6.25, 0, 6.25, 3.4759521484375, 2.081298828125, 3.4759521484375, 2.9052734375, 3.125, 3.125, 1.7364501953125, 2.081298828125, 2.081298828125, 3.8177490234375, 3.4759521484375, 4.5135498046875, 1.7486572265625, 3.4759521484375, 2.081298828125, 4.6051025390625, 4.168701171875, 3.4759521484375, 3.64990234375, 2.081298828125, 4.6051025390625, 3.8177490234375, 2.4993896484375, 3.43017578125, 2.081298828125, 1.7364501953125, 2.081298828125, 3.60107421875, 3.4759521484375, 1.7364501953125, 2.081298828125, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.8177490234375, 2.081298828125, 2.655029296875, 3.125, 4.5135498046875, 4.5135498046875, 4.5135498046875, 4.5135498046875, 4.5135498046875, 3.8177490234375, 4.5135498046875, 4.5135498046875, 4.5135498046875, 4.168701171875, 4.168701171875, 4.168701171875, 4.168701171875, 1.7364501953125, 1.7364501953125, 4.5135498046875, 4.5135498046875, 4.5135498046875, 4.5135498046875, 4.8614501953125, 4.8614501953125, 4.8614501953125, 4.8614501953125, 3.64990234375, 4.5135498046875, 4.5135498046875, 4.5135498046875, 4.5135498046875, 4.5135498046875, 4.168701171875, 3.8177490234375, 3.8177490234375, 2.4322509765625, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.4759521484375, 1.9073486328125, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.4759521484375, 1.7364501953125, 1.7364501953125, 4.6875, 3.8177490234375, 3.8177490234375, 3.8177490234375, 3.8177490234375, 3.8177490234375, 3.8177490234375, 3.8177490234375, 3.43017578125, 2.4322509765625, 3.8177490234375, 3.8177490234375, 3.8177490234375, 3.8177490234375, 3.4759521484375, 2.081298828125, 2.081298828125, ); }oolite-1.82/tools/oxp-templates/encodings/Cyrillic.oxp/Textures/000077500000000000000000000000001256642440500250645ustar00rootroot00000000000000oolite-1.82/tools/oxp-templates/encodings/Cyrillic.oxp/Textures/oolite-font-cy.png000066400000000000000000001762151256642440500304560ustar00rootroot00000000000000PNG  IHDRZvt_TIDATx]"EMlfWe 'f10c0cψa3fL穘1c,f V˺no*v"UuL~nnW^)Dܲ $$,QŒl ! xdHVFwwor#pr|1אӏ}D:ߎ8fVP>|M/;.5 T4"$@CsS +J% )A Ѥq0L._$bA{rҸ$NX~V GP""}}l`!).y/H << ,tC 3|(Z`Ě"K#hvW# uFP1SUF ت!cZϼci{ ǡu_Oa%qƭ,z7O Ǥn 6FbmAA:fAqeY [!تo.j|5nt)QL'!Fʗhhn~<)b<:;R*@уrB#h/0ex ڳ[fywhScCg  .A' C?v 4j^B F({岵=:ԫ&YP}rEWbd. OA\Hpi\3Cs *'X'գ#ayTDMxy*kM5K\0o,ϡ hB !_KZm굟>]E؄M=5ŒaR?;HV,=NJi<AgX=:Y@$RGT>4ʞ5AJ]$',%{1rx,d g%o竊:>>cԘ~A=2{]E6,5F*A<[~US(SdB$cvSNj*PJit3]U,BzJK8!pR_"^fCLn7 W{tyY>Ȫ s$jWq:[4 -F(% `PAz%Ь1e3l-.JLTN X̚$˖ژ!ɜ-f T", ]I0Oyކ!:#Y:bU3 (j?ڨ|n?- u]"`šbZP5:%kp+[t79>'.t7YeC HC(DM9@i+,8vN}}ԕ~>X1jijhA{ju6AST@=g˂=o\wӀ:ՈUriMQz_/֕?aX,ZYӴ rfk5ho])\E@y:Os`R-#]$P{j˛k s.CšzShRS/DVKY}?ɆyW gP<'P$"i|B1X0.eYy]d+o `cjsz2\0#rТdZT$wG6n6+(n.1'x4-(ÊY8U[>|4 'k@ -_.t.t1PRMuL@むr ` kլy4\:S&y<_ j W 4Pqk$y @HvRoFI6FQ T ?fLfS{Fz20n|,-"A YvՋ@ IQPVJTo 2?SWʸ9@nWn5@CX~A`@ k8RK79J?}gw^mtCZ?n˹]t1UoYc bzxJWp -"{qY1 =LIEk p-  ۓyrkRH3wC!LRL `h^i#xݬv: Pݪ9߇jJn jsaE7*&U =ySz ǡ`D$W6~ V%m R?lepV,8'Z#mP$;mPސ D]__=_b bL%xxwq5P8 ]~_J#p]sǛޱX J@ Sް-Զ16* u54 ;{:(V z(/"#hSZjݢ"EQz4cYjҥ5yc] 1PS>n)G HJ[_JE<#hTEPWh q *?H\& W S0(bj`96LD$$te#p:l=dFJT u);W0DzR8-U>WWƕ@d_Q)Wg3$ԍmaO]F|<ؠ8v% ^F.p\:.?F.h1h/ySh]Qþi aC 6+No3v4NXr A7WW}8GHg.ֿKC}>b|.lû}H >|A&"f@ IYZwER"BIԞRX.O;~872T>vpԍm, p!92e$:E5<%ݠ+\xwwuyO:!FQ@_&# ס"A3\_'wji0Ly+IԒON-UnRVPR! 8U]:Q@ם.Lpb2j 0nOnFx%3>W2́I5qŠSqc>{ޫg4\Wt(SQ:1|, s@T-.>@`@vpZ,q2쌹R#G"K*z`/S( okTeSp9/{dd/_V!pZʌ` jc8@ ,d ȡh^wGT[7nIpx.B]r[lw eBJA;^D d҅Jp03\N DK_ÁltzFjHWW! <6C g j= NTFw킩xPsf,URr{Ą,SPC ]FSv =gAIQɐHofZz,A24I h 0L`{ <UffLb<ܵ?DZdž/Nn".AԤVKPIn1՘].h'h$B(MƗJ]9WWrj ݕ| Q)2ٽ)#T^<1p |`҅L"_vФR2W/==4﯀nA p[>P>Yw8 3[@X_FbcJs1>U9Vx0(ŵ Z*O97^" oyqgFU6$Fh W^@>̦;7Ӣ`9jbHX8|f.uNwHtS*LSHT݃Hryn*Aap%*!cWZU-XgA7fSn5NLoJJQe0IyC ]tP*t*4ԑ~.3E'~)їpYDE wM BNp M\q8Ö'Z20!7=6HBf@`y2Wx!f $(sCw.#A*E[Qrt%=c $-%gDIsEQ{UPW2F]fJMV OA@ NJ3pi +p yFp-w+ ,c2]ybчJE(z"^-cJ]X@Mԉ-ݕgW~B*IFlE eO}W&IY  MKzM8dתUPQI#45UhlXāW+k{Z1ZS WFYa0{Ԓ~*6ڕ)xAJ >B(EL Hg]IEA1^-}_8E E"e`T)VqV)F*q: Dk7վ.ɍ5ަԼHF?_řJ1@ªe QH ܹ<jQ jGe%\Lŭ0 Ս+Vaꑋ΍u/:w8vTty W!U"$otS\X .$n.]C!7`̂ [`KC̀F1Ljv"~X #<#uhA QxF 'ZӕګdK>JpCcE9rýew2%M #z}`)Z/MTC@RdNs.h43r]h z;9!e] p5Lp"Kc@&"TNo q,$Ly"lgHd4%+jIp9p5V,b ^*#_0 LRKat6kU}ݨY yB(g!c ۗȓB:[ԈW!U %$zsEi<kSK$HH8O-Ҏ-5/_DG$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$36V$(M@ V+OA\;͗` :o'CQ 0UV!'Foc7K\unQG=%:>~#& rp#!s[oM*+ٖ{_Hٯ(.7\FOM@cE`! oXad U ̧Qy]yܝnpIŪܭ5X G~_Q9N A}=˶ ! Y yY6m٦x`wk|;ԎWJh/.a>G`ov/A.H!{+$ pI0y.}vO{Nڽ7n;mnxS-t>G.m"[ca/|4gۖ=C?Y[&*B *9WRG^1l}tQ5YLX qo;zRa)}YHx:g!G+4I}E6ۿ 03O\vfy|xp;-%.)<'ؓ>b\(pg>Ht )BN`YtW'Ͼ;K3x텛a3e@Q{fP:p=8=?K??S6&_(#v0'A@'p ? x v?zAa@3؎S?*ژ<<_d#N,|bˑĞ*?ɷ6 `|; Ss)Md)?.0̞aOz zh# :qm0h>0p D_k{QHЯ3L ?wޗŎ.MuV : 䀊D;} egVbIx0;nGo[F As6i"Ian8xy+AtxpdK;$/e[UBy@GG<B4٠y">=cT(f"߶6Iom=V2`n b;.;GGKq@y_1r(NDg`25.^~jm;Znk 02v+l碁q<6孥X 4MU-΢9LHJk~TyӑXO8FhS)À%2ŬH5!(bQ vُw`F v!~pe2>$|UEv;Sl@\͍h\x-R}Gy 6דq& {_J68<.A'Iv^,ՎAֿzω WgZ;ђ/Mo׆9m%K~ R#|vM^la`m?yv K2~~֛`ZV O2sW1M #B\NV$ug4gw&fYd :c+ApAg\ @]O;Kh9v7>qԇo |Q@=_<6Ƶ`"U&#><4Sut%AoW)@!+jp$˴iIGÕsFs2a$R)p' rcN\h"S@1nV<4.KDa{嵎NHME}#zYȠHx-H2qv Z'r#hǍ 8%q( W-H2<6- D5`jrfky  9"WCܞMEL׊Q/P$ްO1x 6Ssg 1a2 7}vd*p C}oz{-Y>ק5iv X]ϽvdWv?hIܽ)[- T,f@`RNB놋d. TG؇<}F]kY %vdwƦ?NDW%sB4'ĐSyc+anZ~I!N.qP &IF8^53 ;\ߙiX]o402$ae2gp=nI%|}R C[18NV :+X_noug`,pWJ3Hh-tk0 q!= |Tw$V ?Aoˍ*>:O>ЂUתBObxϽ ǾVE lY{[eG=OWvLN}ǵ9H㈑e$ĂR6 :UۺVO"tnجEӀoM+/s\~.A<+M[p3'{"W!l)W$c.|.ඓ6YĩS'vqx+n}y78 ŶW=070aG p2ԛ#d+%$(tIvk 8T00yÍHbHCn8M+! U%=a\w! c7@*Uga5 ֜hkY]r4 ,*Π\0#e@k!\`h'߾]eIHm 0aX8JM#[OBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB„k㪶˛KRןqR頣!';bC'_q6K۽=O_ٽw&\av[ă&fb{zOrsn{:q%"'ޟaᏯEm8]6mv7 nGt;fxN^d?m7EXpi8B;=~"WtJò*޲Ɩc7q[PTM|Y=7"7I-E;jt5Fg 1:Ć1waWwHl-my?"$;O1'0%#-%f#cȜE2~هgU? CNUkvzNpyFdU?f]$˝]UonG@D"UɧR}[-~S,0I[.E`??j/6 ~pOn/I]Ovo-ղuƏߟM-푪P#Z ]j% Ê7q̙ߢbDˣ7^uv9%-M,wTr]jxVܻt=S{4zzi`28~&޲o8 (ױXTmD?:_`p4:>5i HCƟ.%^tO n ݔSO)cgiCpI NeƭB>D>28tq3zxO%o "~h3v!A#ȣ_ aΥFhA]6!ȿ'x!gDZq 14kD r&:AvtJNo=o"ۈX}◊xS 5%v_J" QKMWۀR&ߗdⴿwߴ>lrEz-x}SrINy:s* 1ڼe2_t&J)xDCX_QA!:IkZ_d7rM| vD8޵v5h dشm9vK@|=sb@~9{@= NcI-OX1!GxΊxA´4ҟ}yǓܙGZ'^w%'xбî8`HדĊn@; 3H n6_"hާ-C;/rl6SD|/fU8)u{;5G~4uOCJJO&O1~Ŋֹ7f̶ =^:4"׽?h'#'l(C1#|M~O#NFaO(f L w_Q|'|~Ȍތeaw&YD!"' `Yp\.q½ð8Hv*`P _6<1e$sb 8eE.fD(L~v#ô7/ܑ[w'Om}:vN ~sY<1MNcg0@4vbkT0T[%~-ΜI7ÎҴ0so [m,y՜{αf" 848S2{hi `-B2ɯX:rIy0?ؒ=@pdX[ʰZ ѳbxs>m+x/PoռK%)yثWu%WdY.P3KGXt^zj[|iաBoӎ6`Zᄇa5;6j2MT q!b@QyL[:[SHcp,k}z`!<Gާ/, ,s֗?F^ a{=dYFdt%OD:R?3m 㼉|puP jAO# gՀ)Mfڼ~ced@9y̼obeM X_lN i9>gwխh+(^PCϼW0Ҍf4'9~>LEV  QVorp1;ԅpHbo B=\Hj/M;u[L~w /0}% FzfV|}i| #Gƥ_|g쵟[>Glgs?M=bRU~ZZB>f`' 8s UׇvM7ַ\iT)E>+P_tKMBD€-oqyoGJ X^S[ =0:ka4z*66kbEcLPMyK>4uA ^*fHYOON?,wlX,`M [ <.B7 jZIk/yUl-Aybh -QK:#j'Ng(co 4l eq|݆m~-oj?.v!}Wa+S(/S^'3~` XaVQ8UqoOAvÍK {ҿ<_^ |u4_~BhU@珴ñK,B[OOyD };Nm7]"<rM]tadj6Nbe AI?T̂b>D~ VuH'L~]X2K>*q@ve91fN_uh `̗Bze䁣1^w&k07vJ"[ֱ?  eZLX0xL;WV0-#8>eW!n:L'ZfۙcA.E5eykQce-#ZQVA#jYZbƽf4A[ ll!Ԉ'~`{-z8">mvT?y" eqH>}WТ6:et`}c^ Y&꧟Uil;)kh#O9(M`zҤ>H*u3RlUoXW~24!(W֐CihB>E}ԛX{J J4o(aXʚ`C!gH js(-#S&n_[`zGaؙ:Vi >Kq}p BFcrLʨخhl*̟i'g 6dAcI"OUi¥l¯J P4P~kީp<2ԕח|!^᳽;Ѫ{K\l=5ZB gh\Xv[鋘tܭw*<!Yӹa>Onfqҙݧ۰-4{?z{dK~~g̏z6u2wW|V>kiEBB) {YBBV1P >_5$$$ Nnٌ5eCHH Di !1@ rzl Uv&ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBs2#d+HH5tGXg?u7;)X n'y1/X|c N>!Psg敡1.7ո'Y@--bΏ=ru ^>[ֳ޽6蝵yv ]/9ݜ>ϒ7|^ӇYCNedow+=mjSy?6/9c :cQ'y{VsNj2";/m jCxݞzEm%^ 'sߤ2 @fM&3Y#{|=gϟ%o-rާQ}K%#q1]-Ql{'W(g[!XTC~֩&8Ci%ȿjy˷գ1 dP NjO[ۑ[Q HXYӽ35 :=0w-.m6:έC `;.-_^ё#Z @X;'+-&ӇlǹAAg/aB7s];Uw~x%2؉?'`WA= p03VZHMOߤ&=Xsv/uON\+Ơc꿕R.N~mK:{~c8ﱻzDyX@a置 [#Utkėl4ێ-9x19pL31"טxߨ|gWgkQe_ͮqiFbSO] Z '6C::"o>sÿ'v@d}4qg_P0i )%_>4;3AS٧׼, Ba۴m)gLx0G|m&E"e ^ `[*e·2:`}|fH\~w sj>"=7o8 UtIC׸% a&\,1c@(izŞc,1`D0E@i5<2~ }:R~='u}Kzg߀ %X=]ʺ?]k q#"ۘe@=PZ7TGjN%AFmI0qUL/TZN/i|0e8!y Ey՛^b6GFpo.hXv.C9"Q?t(c9t242TMš `Я4Tf2g.(.,ݜ@vFhKQ.VB I-E9N֯;1厦@3'͌c85FKR\I^|(X= B&s-(}'~w'CH=[XLb>@p` 1:?$D;?/t&) Hn^Wbb >ghҢ2 @m"u "oQ_Ӄ?'G8&z4r c  yr$6=(.&~{^&aŜ!N?&p8:qI $ocEUE~_zU;`gzz}\bE[{ @b8@-53?v[(܃ڰ"HVMP"(1|9Kz/AM)NM3*48 WVzN^ &`% @GqD["Ox0(J+ yt|͐%F"w: 6N!tuVi 2ܾ\Ns1'8&ӌߗ Bdmx'w6I* {Mg!C{쪽T[jm3oO?p\گfї)  ;V_;`~($|Qyx0Moh\5`)IIzhaH @hRSiN]AwexʴnB9<Q;%<9d3@E) QebvrvK<m3]<_ˣژ*Ӹ2-o`KeE|1)"7 `MFxhq'Tf ^ p=+<7Lzn4( 8nXeُв'J'{TJfhߍD6Kn/lVzYDK #mKhGT@L9`VH\8翸x- ӺKzkXf->g^蠃uMHAmzfhӯ#j~dU?b"owwNDWazvh=I`ǻ=J?~@pd@+/eϨKw&A;%糖VŞ ljۗӷc̈́bk2o߼p1m> 0֝-'i8\qI{ 9e=b9d|Pi|I,coCxݷ4ތv 0zgn^1pm'3,A{قm`#" tr6t!e;6G8Q[D U @v>0%ISqGxG '6`rdT|˵w1A;ӭ an~!w/,8 t(}`mꕔKŝjRX K+~^Řk/H[wU=T!F)q/U?篯>)Pv,ZfF>'u5?].,3{U%?/(+Y .&]y()dU?P1*Zk?v=i ;/ :ְ;teX N.5 @Gޭ$%K  + \6&xo}=}Δ>cO߽wAU׿N[{ [_|g w:o=~چd{HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH#tƻP$1vl^Ò/p?_?}ڭ>ٯs{r˙{b`}tmewD/;Bl! ̘ͳɓw3nNX w =Lj h} `:%֣>tGrf*t~ϰ9a]=/~쭆fٴ 7 +ciϗL?@yPsgԗ.v1wWPVD{߰ ?h|@5-s?|jzzfw1!Г^Z"}S &w8 [N!>ATͧ?fTӻ_K^XsGZX~3ک3詳9~wKŚ"XŃabB|5[u8!(¯ "54&8,1zh/[ `ޙ?re*9_'9ퟒ8PK0$7c\XlVgso,r\8#ll^yPC6gB ` :ִ _C493=|>I'G!}!NmO,ӿL94QaR D3.\Mys\u " = `Ѣ'u/6QGZLOs7g5&h6ܡ=[[ijd5@d(>@Qü/K?VlhDYo19^o@)#pn񭂉ζ@'K_$-g۬OtE|?tŲkOˁbI #')`3t"FJ}8!tB.ތ=6颤yOf9QKah!(Ox/"sY83ඔ.[÷ ?.87^s#k`eyfq@;o=e 0c`MWߞ.žt{Ӊj9D/0Ӟ$apjaPm:|7?0M4pp$kA#՚bu%CU:ZȴNU):.wޕ!(O==VtrE]lJg!P~M%dOTr@/}QO*Ÿe+~>4åҵڠTJ2 0y!Cl]e d듊 %A³jJxt[i ɀ5x.:2yA90] bY9`[pF*B>KPGn qd:/iK'HU8q`ע:.yk5? F>dS[Y}\IbӰ !re? T_gӓ}*@ K҂PEڬ/'-]J#:qS]1O2.pfi\>м,e)Ͷx.a*|1w3. kLxrPzGgpŔ4 J:I@;"-IWN<yY C 2%Z]ӡk5Y1Fv-N?F-\SG {DyD,%l;B82 =p)~~n.xPH|䥶լTJ Rbc ?±q|iK$%qcwU/ٙ 9還K 晧5U.+zU Н/Gڭh7pޞ˓IgTO%f ꗸ1uGO f7Awy \@q?2q fd5#J5 :@ \H,3,O im؞b}iC ՖaCfӬ́9)qu edۛ'2-U@\Ȭ}iH6ЭnӅ X~mYxh3kWZeK(eǔM7mM D6ih,򺦷\*K< {pwoMPfJ'.+⑭juE'jٞi=WB}dJn3D}iyn67jPx8ԔP%ǭ5)#|)AS- gD ;;䕿8+*{< Z\˹,:Ĺ%{ЩÏvƄyol,c.Oy e aY$?Ԣr 0%TrXwxzvsD9-FWVߝhݍƷ*P8dtdZଂ3peYTV]FV|L/- n6[Lb (pa5.܁8vK Qc }!$4n1Ϯ_C]]Dt1gC ZTs8̌s暃nڱtB@2HI=S 1M!@_U7! @HrUqR5@ C%5=N3 tY;б._ Hv5@T|VTSݽ6 @ @ @ @ @ @ @ @8x6 N$? $ *_uf%0^>+ y zw3/ HrG9BOwGv^a ;?Yxcx7[ygjo^ )'I~1°Jox+N" ؾ=E~i$@-DUz,% 9rdK0G][&~KMz\(XY0`6l M,0[!Ū %rrF/[9,f+W>XVnZpZ;{ba^]H[a%jB!٧-S73zG%'ciUPo䖔pzT!RL-]ybqkln>4]x[RŮtΝ-gxG&ClYeP7yH8jcv÷św*U7P_nWi IFGߗ^8.Oox'$1ͼ+[HGY¥~<HN«l=}tPtb *S>~Rv˯;{Q}1 x*#)KUGk.F&przZJў*l#lӂ vTB7:ZLm 3:R5P}:"wz' %0 NV.? F6NZ g;쯰QBrl (ײ qG.8dK gI'!:bd'=Onv.O UbZjT,cj]G@+8l G}rhK{ct9]kaF>?r \Hg+L'Ł?NUzGD ߾=h\U'p( h8J*xBF) ٕ}]2( Z/]{[KH_=^u4 "g~+6meSж,i&|hȯVVt&?*k'mhi\u=?V])2xF+Bk[$v's/̥N(4L" E @/|u|z(!E7zF~_]q KiQC|j |,9/mB. [>M+;p-d91j i}IЌ0ߎv;#af\S7mO[')qZ[d.3:tv yI# 2 ˰}e bľ>i>^gt*N" Z2p~BȪ c`-eU#!oԀftw*6ͩJ xqCA8fznS2\׻P )HŐ)hJNɘP4 ν&p tvA2@-dBK= #wkReW Và2Ȼ@+n59/#Q[xJ[b@FR ,펺wU!3Qu6^1A&QX YzN̻<|׭j)m3Vs`o&k!pʹDb]5θ =n uP-@T\)S@3⽦nq.dW_"3`*8U`!`ط#C,8m@[l|V4o.@*;:98unUnk ui&;N$}6TAXp:&dDXPeR%#}.veI E9p9Kiɦ @P7UAY@9Qo ڂv(nS)M u]Ԁɴ? )V|E] <"u4kX=I qN֗} %G}}N=yHO Bԍ:0m&1iɜxwQ$ʬh, Յffo2 > %kc$k@dy<IcI`sV/Ɍh8v ji2 e:' ڎ >/dڀ}B}go/A嬶"<̝K~8B*Ѵ)h@㲶:W\4s$G mی i0 rڒ?1@SQ> %HZ)@RH400nb D܆^l'G%;5p~Nd!0@G) o!Yr 3Lw'sFcZ,ԓ@k_.,6[ڇR8ZߓӁbx1:H[F ]`L0]hiF?9jڼ3mM.SDlk1tշ"\s @] mA(ׯ[dWB9`Y%l~:R%aU~*p1=Hs1H?,=;>QumŸ(hkN 2mU՗Pʥ,4H>&QkbEh geϴqʛKMe|${3e×8  Tmh eT}dE,]5vENev+㘟Oc\VU `_ X$` lBY̺&6ʾ ̴|DR`5j_;Ρ2O]9LPvSka_φqmbƦ@{Μٽ2$Yt#-; In8tɝ;eiAB Jʐ]mCm2-qk 1MW {&̮3,Ifg@z33,bXse_wͶn9b\v;8d&?ǧ?S< /k'4s-WCՖz$*f M(dN^q? [%h3壖>jb^8`zoܓ~mg'Qbp@&-AقV͚n>q{}F#+>-K r">O/jxi 2.6I[5\pXW@+yi ،9dhqz'vdt>y;>[fm'Y|uO2C _Y2x0)6&w~! 2> K6 ࣒:'t%'?h}A) ^Hw0@g<8}L@[[W^fXHEiHI(mđi|;+4&6J_f _Ҁ[wםUuWif~%@3HnŴ({*47_`a_GƻW?h">OpK&F#-8tb19dBfcw``rz!K88ة;GΟ)9 йI;Ok{ I'},*\yDxeex6eħEMб?̦5r+ @x*VoJ@bksw*4`W|G+@lUrJy8uCPq8f@.j?n׳؍TYq=l o#ÃB&; 4i~? uDsl&:%m派w;! h5P3Hiq*B-W&e@ŋ }Goh0~V"X7ϴAOyiaE$@KWpGspx{цUbZ O V8jav h%>+O ~9a+ˢp$&@ykDe~JceBF=kzueC:RV3z&R?˻2H*0C~:mF0-t(,x+[Nl1*MG" 3 )d9hS.3'bToծUcC3ꙑ*1lbJ=H"¾@ yL6<c7 ^?Y^k)E(5iˎI,Go 2Ç̸dǜgBlPjK7CwD"l{yI2zݜ|N#0¤q`YQXJ"wE悶Fc1$`kY6g31A2vNnoW'[ 8 [^UO~C0jYSJ&GULo>h@62 }%A.0!;e> ^NIH n1ӆwoW)S>p_4^F>Hc.0WNԅ@YMvӾM[čMi*^9ýuɽu8h L?)t3}Չu_\_w)xӍ}m#pyAE hܫK \sދ/ϣRj'aݻBF}~t"nOp4C?WWV+WPt"jw~VR:j3xI_]ē*,Bc$޴s"1^Ҟ-Q'_gݬT\R34-#ǻ~ x,- pK#n[)#S&i.$Rʂ\Pyg]I³OcM:$2>b]jBcSL@D2mk**M#4'푦W4[ygQ~D?A3Bi֙诔LKL#>rF@QrntPmhݬԡʞ^y% 6o4&y}JSx&X?@!~ʙ' 0etKPbo^vIch_;$ӭcI5$8ꄏ)Ü q/@Z'Y?WmQNbp/_#8U`z(N)OH(Ο#hG 8![ojes"_ZMT8/0TJ^i2 W:ᑒb02Ǣ/hHJ :6|Oi-(Kz W,r) rݻwںfrꞖoD =m"~~1gܺD.t#C$[O5 Pf^J+e:&z qb@]45|;IUW#HnLΓ> Qm "b2(kov;FNkO3>L:8'Hv=Dݚ[X[DϪ$4`*4- С"DhINۂÀ7@GƈSfd>ka0\,?m. @`k/a,WIGۉ@p)#9!F- @|U@JAe83@Z '~=?N몡( |s%%`AZق2}7 UN/g{7-aRqW3[r@ŢEKTi25Ui*Q&2T<,_*$:[ȟ+ɟ3}:& Djr;6"tOnL/f;!6\B*]6f[?nZO{Ckq }''Y4iO" (5zP3 ps@j:H|?Wi!E4^v@uq/ V#jBҘL`+=>ʞ5;:*UθC#8]mIX=4 UA[MR$@D][7v0M@\S-C!_N[hh WwBH8jT. qp)]7A E&D!e H^Zu'Nmi>"1xxr^Ftt88@#@*@-gj#p{^^U3\> O5A\m~LPWy'&\\)m#߇x } uν,G!xn !M@c@6DIKNڷcn~ǩy]ǙWmW,{c 96M?p2kn7 '[6Mtz\nT#Lm v !{`@^ 7vNiAf ىY%,ۨ6WI]gRO%g+ pZT#s:2c&u/&rګt\EuCp1#n@VgC:S"RT$TSw]hOo^Z 1"w_LL8^jXJHmC,hD;p]-x[' #&J݋dl~%z0\$p?b]L&żTSݿywak( eb/| ]pf13SMu.fCY7zsSMupG! Wf)aeq@c{HHNxBZH@ @ @ @ @ @ @ @ Pjл;em){#gUUr" ey /ŻQqt_xtAÖ7KQ&ۆU8/ecԗU>L)Fjbtӫ: [#Zs {, *^7gnڞ0>.;>S6 ƷS|g6B1i&? \\T]ymk,]7zBD>9&nθ7 /|Ӥ|5?#|CB @0T$nt #l7294H? lrBff)~2ΉG({9Nx{2)*G 6& jz 䠠 +o LSk ̡5vR'N<$C;q$ABy nN@sʟN P-D^q#BnoKǏkmaaA82 hi+G`~ 3mpUJ㮂 eRg{ä[c@*RqSjr[o's@#y BȗI7FƂ< h+4&})]qVҖ絔VaC`|OhzZ#CT%-DPABeؘw{] h? 9 @Ϙ[2Q2ɢKяC|,ZgҜ$@InIߠr%F0^,ً\/ MZ\./KlW>P *DzοK /e/?|ؑɾ؜~x8b{8 # XCPXR࿒F~0>`tHN=> N9R-VAPd=@R=R#*˂5RМ%@RMחNIO&rYOVB#o9 Q`f!V^yOP(Bo?W < ".̞fIK2W)0!N>Z]NlD~tqZ)0R(~v#|k0[eآ\&wn\(}ցj?VX#6Ms0u"`P5-ј{3/q)i&$| '2{(<6fq>]V {4B(x&fۼd<ӣr -&;8@<&fd@jOJT܊G+~Ig@֨Z $}*@u Uݢ»iIOiRE2 (ZU`8pduH  ;q>^ 5 c<5D'l ٫[UTwpٳ=U3 t]rٳѻ >OSHk86"9@Nve^^j\I)`׏o7 siF'HAaGHq lf3 !P>AsprUҍubV6 iƧ= f/ukYYȧ 9kN(Y@ o-PfJf97ؓ;p#'t&@[['4@:tV&m~C:Ni .5q^P>>,t=0/w` ǔfKw C[0:]62]|dK D{w6`T`'> jj=[ >*.kjHBI kv 2o% snI @} tDenRJ^Ihd Z-MÂWi ȝ p߶ 3b'bKp T9K'#@ځ9!q7%:Qۅ{'!s_Gyh/`nU௠YB@. 0@T d=0HRJHqQRYIY>8I9aD@R@> BB@(F@*@ @ @ @ IYʗW&\|1HwrRWA@iqfqG!IJ=n*$o!>lZTĕD㦽..$vSH “B蒘 I@[kPw|dW72 ,8+:Z8^W򮡻 AȧP;{u.@ U(+|;w=;X (8N-@ODST(,U_w=AjxF`i\ot3au(:,Nrz&'=~xfIOؚ?r[&7 d\?EƑę~cRƓWV.x6 "3H6j 't*650 nٞ@e낌I?U2|{Ge| lBsbͷN?2֬ Q ! = ԯ~_o{6nf8z|K>93I_dPg_sۓ_ρLjp߳@~?kZ!w.sVG'D^@UBZ/i LW ̶l;Đ^;ƺ(tԢ?Jc(ljnWS}_v'vї~'o 1~Jw7^66qTUcDL|)MiBXxZ.A)K%>oe<#(:|4%/6wC^t 2B˾VQy |1 8|=b5E>u|_hi߮ p2uIctl)^"b$4.ezT`2 dgpm@|+(8|+?ص{㈶UZU-_fI>xp;7/nW!R'/k9Oi5KU C~3xEi[-h/e))q!j߶%tyZPhWy^iյ}Y9RpW~ŔY8dAM1.b[{M,q_^Y}+;NroRo2 L\[ Ղ~6kMUPZ㓘״N$%%XʿGg2eK #\63B>J ,(y"N`9t%J*/Jy'Z_߹|,dZɑʼn_QZ&ʀX[=@?*ܺEZR1~tPr:'U u$Ao&L֯k (%pK@[+BSwUmIRːaCgwۊe|8|)KԎOg5aoC鸲tؑS'I井+@:;c1 I*Gj|8A4؆TKKD`ɭs1^ Ba$ޥA@vTq KJP2@_c\nak× ;//[a.2 w?7"eqnk/ȕ9v.^|tH>N ?ݹE# $/J<%iKdTLI<FOS*e0{PcMnoZ>8 YeO ?(^%T,Wט~){2>K;AңU8~ᓴk~W|0^w@-Vq%n^%(OʽiMdBLJ$Z{_T>9qjdj @& KU U3u vTT ij25bIe{ /8d*|˃Dn?!pN}goɽ+p ekb ܔISvn,ŮdҾxgqI/.ؙ06М~)tW` ?Ǖ .yeWs p Xɯn2_:VDC#ZP8(GSҚ~)dG },C?|xy#Jyu 0\=R H+9JKc7dUQQQ㤣9 ٭8-.E"^uԝ+%N/CG9RJ-?oF HV<`v'ȪrQiQZitz*NQ5c ՗-Zs[EԉI;|nO\@o~Q@z7-RۼZkt]R?N{ e}"FFgzfA0NbJ($o!3t H5֏vWU@'7@I?'VKAhlq;Ors6U r`2>,k8lNh)Xb5b%Ez\W,]Lqr h(EPOʒ/ k]!2(2Ezғ\|k-_6t? BeG0qegwu 3ۅw^j֍ޏV!m"[ګ?Tx]:ܦ5R-(0G 䔮PZ]Wg#ɳg2/@i1ڧZWnW(t+_q`95(?+ nS ?}/ 0x6˖w؇6rI[ ˳p=e=e+mV|\0H]؏ڟT}*|0_nNPDuy~*Kg՘~)iX h1v\")~#;+iOw`+̲x ˹8MeL5 NzQDL:/; T_5_ @Cs?KcKA.7C7HOpMcʿ} $t$;zb?pZаl=O0W´>*3.M+nnϔ:(/2," ]6%lS!^Yfq0SJ4<to^ 9n2Q^0ݧ=:^!=0V%.gt8|.]^X̓W%Yʰ9߸{|F:/e`>K)t@ocUeb^q]MEO;R4u{̱}m` k*   8|]nzpzCcn`jBŷω3R,G:~vշLĺg1=8}|x$-PR G~,ccYĢԜd.ŁoX!%EeCBU؜TdKb15  4`ie=ԅ5 kw-=q_JԀPx'- 0 KCG@'!1*IØY!9@Yf}&X! $Tcԩ @ @ @ @ @ @ @ @ PQP@H (P7H ^(+7BYeު@|4U4eMKƴnTq+ByS7l5wPj3bUӸPveXnypH_@˪uYt2$8׃L3,YƌwwSo@.7]˗ڴong/@h_ αBjPw7[vUf k$7?NRv,B|-^y_-{+O |l=|֧,3CQrx> un;PL2(ExU+0ScH!;򆾁8QQ6z)4^1_7w}8(@Y|3 I|NwBVOPh]} M8SIf:jRGH9vI޸B.P?YBnҡ3|T Kk ]d7h}]#` 6}o_ OR:"ľ `'|]y]%0{Lc\g`G7G!3`J-qh@@xՀE UWH ;Eww?UI漀!gF@ /HM̂i@23pV 8M? ^N4j=+)'3>Ug~9,d$}qY4V8hY*I`i9z`Y??ii_Xf uh2 ]]RCX:~-5 ŠA֔ f=F85? M1(#KZ QHB nmo!7TO#pV vxK.- 8ӅLE/Oגt  O9G0RQC㈱zk8K:#rR (Ux;H;wlXɦM؜M4GLjt ?ak.p8?qnm4xz~' ^Uܪ8@C%_P³H8I! :CEn 3ܖ r Cd-ô-?Mz¥" u6fL.#-r9sLnPGؗl oJOU.\tvq:$렶/hO9?d:kTU?ƩuiaۡN&k(;Dn%5|oBpSh {j/n]t6#Ǚog2 ^1s2M v%2^tƜ= .E#>;ޓ\+y+ 1A`2HC;s-(@}h_k1_NՎA%^D%W0Huuz&u~i^IvXIl(".+?e_gs{oO*w<Hq~bTcE-CE3;(-ϮFZ1zj V7;Ї,6G.yEnw@|QF5:Ng ;aWas7v<@G/lrQ=Š 04 7!@[ԳNHxEN CG8e"wu Do7YGuLUm&҅ ȴW0P}i}<-vd|Ɓ=^R=Xvxؾ};ܳea\ KNn"2%1d-+g/kC ?Ad&>.V2 piUE`5K@+d]x{8_%  T yӍW'vXx8-'KC:ڇ^WZfYEt/qV|?#o#@6}t ŕ*lqxV^iUgM?3 p Pgأ2Jz~h\7gA.%Nˀ0Y'ؕ)VU@57i*@>aX\'^dsIw,^ym] |E}$)@-ŪY+ΊTM">8EaMc2D']'lسhl$d6S;w}$*@a&36R:i޻t|LPHȚIr3ໂ/(_3[[6k5m&ॲxwB4wE#w^,ॺEC $Y?n Hibƍ)G -@캓TG`Qr& >j(bf䄤+; >ުN8C>H N5R@ @ @ P_lgʗ4|FSe]EQ߱ln _Olx R!U>ʵ/n}^{ SxSc d@ [dþ}߷2$hqs_;cdpSf(&#nk蟿 ʫUak,x,#? :uaښr ~cM?ݥ Lp6sAv4\f i '*9+|CZl.?Mw벏l׳5vy~gnMy d./qתhQ.vg!5Rӳ@e\yIU%T(޾qFLϵ~e)a/z@ZmjvT+}?{L.8%psL[]* `-!)h P4"+B69)oi@ȏJ.W zŭd \jֶLp@P w]W`f4j}p< |.wi?>0;`sGv@tw3XNgΙ4HG(#K4/Gc|5>Ҕk-Ec #1}9+L ѭN4Ն+쿡fM;|yƣtߐ/qf|8=|;] 삐C_cN%_oJ>0;@?-Y8_mȢ;^ @1b/{o$j,Vt1P( _bዣNvƴ hXf<@'|;=K_b/SlzN?+znc~~ ui,ަ vfX@Yv '&A, 8mA{c~OSqE(yQՂS&0=vrJ{Mnd"_ 3]FrZGKd 4"8M\Iw\𵇴YN{ tZBik(_/U $FeMn>rq6D}V%?zl@]T6vlF)&,:ӪҫK5s8`:./ 8r#T LH ^ O b+@J=6m\`'$:+\sj,f0<,5Ii #SƧ3 +lrIGC&;oBNW?*Ukk2|5}})ْ+VlLQUQHպbx49d{@q <_b>Fdm!3.3)Ҟ]$e4]G2McFT] @8/wk^"1WH*x4+$СoA*+;ll|ߦPxՏ{ ej;.pٻ]Ou98T1}luTj]]F*/БX}5. Ew J<w;w<@ BI u#/Jv$}58+}txh@{!]"zR +bK&0 ªH,h$P\^݀l"T//>Bz35TG*yQ OFi $$n|C C:Ȍu1&[G:׼~|H =ތptqf%MӅٵgM c޹Պ Y +Ω"e{{~z y;1/* B@0ݽin z6 I@3P7`}6&H !wc;wx:)M\5 Ez|,WNo%wm=q9 C@ @ @ ңkdetއT@ @ @ @ @ @ @ @ @ @ PW`~W[9_H oRe[-չz$\ۣg߿fƁ#™/Svކgd6"1iX?탫ͬO u.4TI WH~kQ8֣FwP4ʋ~K3sy5kgnIo]`՞.m*Cvҕ9GK \nCȾ/gi0K|w^ (O~[.x6 "w`~_qGy4\AG]>l8q@Zz^ K N/#+;H^{ vh+j%Vq6 u Z Aؚ!q" eWzF0@U()A9_> (sލn &W%\rW@x[s wu'otxu= h*WlQmϸʼqqK%R%$ %ZS DK~MUa[;sbn6NfZ-vnQH)>-no.j_@gB2@xCc 詭lqUsXPTHksQ;0stBi"HQ@QiRD)(cC@DTMIST)J#%$yw736&y?fovvf3oʶfףE48b$P0)gh)|D\ImVn\7jv$k5*k9W?as{p.o> LL`p^#ؾZدS4eu%qXiWr$ aZ,3\ {(q]je5L?X\qS>Q|{Lv3}1OЫ)RFK ʧSGؑ܋XyU7~e6==: &8 npB 6\׭G0!4p**}ά` 5Ԁ !eagZB>ێpljf Kiaa0FN@nBaޚV'53_c:i"J)]6 P A{&);73|Z%N*jA. 4Qs&> >غb(@˞?oa qI<ƩaD<[ښdU_{* v&8|K)[Zqd?J #ROO^IZ y;ι|}qs,[#*tór<lĶ_F~QʼWO[T<ȿ8]Tѷ-=8pPv+X6tm@YW&Fgސ|A8=#8a,=(P߮ }sHuY,@k/^.z]QKk-֛D G0>V!kY@)͠N)a'h`Fsg\=%pJNZ'OܺOZlڔ(}2v0<8|vm;%L-*~`='Hc/5 5 ގpsD,OεUTA! {K8]_[H}$[>Gnf  "TgRJ@r.E4oc vﳞUD~ _N "=3לIs)O1T!A{):n?c2< Q5X2i/ GS(bV*ՇUf̱#TW A6UOkr!jVRNۓM-}]44\nodI`MkRKȖ'$Ul M.+~ ~xVn_b=jZժcKRX3oV4?f@^g0r=D>( ;}v^aRiE4:/)M,q9=wzQRPsej;2_᷆EJm!җ hj>O5ћD>5G5a^;o15鰓 K}dϡzIɛ?Q?wtǸ*rO#H %rl 9's4M6H~||h,+SZ |i L%"Vͫ^~p`@*r9$$X7/uH 6|ma;V< 3L;/5~#[Iw( W`ۋ)C,v "LP&m$bOJ)'gBj3 `ܙH@cϣbKZ^ }b{c4O p1!s7DaHiC믧~570Az\0V-~yH_yzLvc;WnauG.;pd$C|j=UMNX7{Tӏ_𩽫uGPS @ @ @2fpgF[eBP( BP( BP( BP( BP( BP( BP( BP( BP( Bش1i%W[T3"i b(0K2B6}#s̐LaooY̦̒??fI&ғ"6f9T''7`d*M!K/% 3%S)ϞO|#sQONfJ&SPi'|Ys#s-x|7uˍbdF9ԻeMs<(TfS$# Jb :kR3,p&Ͽ?پ]4|jM:z~`֝UKtGb{;$o\~fNxIy$oL7pJO7QG~`+ך9a+H~#?d=9C`pɲ6Ce2>T ֟P- ']ޗg3Ve-S\mo8  HKj/xZ1uk\=jcO`CWD|[sQ^ ճUaw-QL]dWv?K(*k?.REcXO_U@a+Xi 0hag>6XN+hXo`@8;l@{zF6n tZrgLh7 E09l :.gBT@oQ$ IY>gfͿ+&[NLަW^EQFd9By+AcEo* n@RfOʤ#[Y/  on{Mqh#ALjRnjB咝X!o 6?빚fK k wapS=x.Ko+*^GС{ĸ^a(GB@&Je@Q(6:@_C+Nm!{ >kL4f( a)̶XUV;C_wuC|o{>Q>i}Gp=WWw}%VfEb8#`bB -# "Ta1G!ӃB6}ܔ@kW?h.3(X|Pi7&y_tq6 ln=zo? ѥo.I{r(I G10*ru 0,ڥOЇFQM-n,4jp0' v*Q~;,?S.ї,Ox Lv̈́C)pk9i ҍfqZ_ãymm#M0 fUtjP ]6wnWkZdGt aI}PN)q(7yJ*]5wBh]Q$mW%Ic1*l hKw\zw̌KYX;sĨ.")(.[N yGGUβ9@hAFXE7ƫ(Ma:&rlvgh0wwc4% ԠBÀDb⯔<2"6kɇ0`>8S$WRD.8ĕܲW)O f_?a9/5u8$@hLү՛QMѩ$C XE4TI)/p"xNcA3 ,Wh{9+MD -aA˿4). Եk<,ɻݫqSKBou^joɿ+ $8XP^떥c{!8}~f˼{<&Q[6}nTpʭ?NyV->a3=  @P @P Tnyx#! BP( BP( BP( BP( BP( BP( BP( BP( BP( BP( B&u 5OӮ( B( B( B(Y';)g`ݶD[/ѸSBzTic9RZIM؂7t>LlI']zlO+OGV;\pxͣCu`ӕ=x/wgWo0x ꁮ_]jBOVc^y;PEz?{}W^Q;7Ӻs Zy_Z{VsӪZNGhl|:|>/MK$%)|_%{ls *&yzvϡi4\.{2v(_۲qp^C9wnޫBޥ'G߽4ԌFk^_-%>h[{?sYxCʼ9_MlwGd z7G Z%Җ)sp)v'=9Ctx{tcMZF gr@Ad4T,Cl{AM(Veᾃ.6(2;ٸJdb.8W9&Oj̅Wlk3l5z[@5_"Z(_DC<8Eύ,mRcNbN( 1/ʞp0\au4 w?۾ K5ms.=FA`4Ad8@o7/v#%&ԇѯKW>k.EI'gdN7ܟPn ] ֝&m|#Ro^}IvQ@_ݻA/FM @ȝ"o w2;@S-AZ>-r/>Yljg# ߠW^Ch0ir^p<I?jpΥq {P)è4 *R0Pŷy 8۪b"Ҋ=3m@# E?aށ ଋ8埜"H%E9C T;rkZ;*ULbʻ\jûjJ@rhzDqCGip8Bi;10K"!nc 8̞g4%Rq=0׷y@uFD).\+BO6J}A՜GQ)e'Ѹ/$CK +ߣzG^BO\4n?>x[@ u)ȵi>4]_Y2$4,.1*(Z+V|3 rpm<4BzKAZ45cUz?kaQ5n.k%90OQW_ai +YVè?P셴RA<yXem_&soWtV %?xݯ٠*Z!_@(oNm3ʸP:5P{ +  ǠB1Hvja--!֡3} M,W g8u6^?2=~-E]fMg17zo889 4\t(s/p $Ow${0 Xs!o&2Àz{yܳ8c4N:N@fxXG ! Vd/f]Xx2Z!{tx3^}F!0DU뙍+3@k/.rN,Y^Moh:ÑmɰxIvTYl4 orS э5IPoৼѕ ds2Dz(J @{$0670uZ1NF&5^m{@VF0]ڢG?c=׿(.Y*"a}KixT0\q2L@H >*u>7ƻC6A`N@=Nb%d [l5Y`p4`mDڬdI1􏆅QV=zSzP V>3~lZK>!SDSPΓu:wKTJ?A@/L]M'=>iKtx؞JTgKж8#q*,{.WM֝MemdiXls T3NЖPE.o6ubA5EDpI, 2]mmF}GxzhsO &koD!r7.$T{>X۝f@hꒂ*fK}*[:8|d' gw-dAH YBnpu_~ _!&Vp io@u`ט4HhkvG+Iy}d֙'iTӵfMࡏo_N4n) ^w>cfM4=nO GuaG=Hׂ݌Xu`6eT<23{Xe\@e.pWiP'\w2BGo% >hi$B`N I7-5T&SnILȪȃBP( BP( }IENDB`oolite-1.82/tools/oxp-templates/encodings/Eastern European.oxp/000077500000000000000000000000001256642440500246275ustar00rootroot00000000000000oolite-1.82/tools/oxp-templates/encodings/Eastern European.oxp/Config/000077500000000000000000000000001256642440500260345ustar00rootroot00000000000000oolite-1.82/tools/oxp-templates/encodings/Eastern European.oxp/Config/oolite-font.plist000066400000000000000000000125031256642440500313510ustar00rootroot00000000000000{ encoding = "windows-latin-2"; texture = "oolite-font-l2.png"; substitutions = { /* This section specifies automatic string conversions. Some of these were hard-coded in previous versions of Oolite. Other previously hard-coded conversions are no longer needed because of the expanded font (curly quotes are now supported, for instance). */ "₢" = "€"; // Cruziero Sign to Euro Sign. Oolite's font uses a Cruziero sign in place of the Euro sign. This way, either can be used in text to generate ₢, the sign used as a credits currency symbol. "⌘" = "(Command)"; // Place Of Interest Sign, used under Mac OS X as symbol for the Command key. "⌥" = "(Option)"; // Mac OS X option key "⌃" = "(Control)"; // Up Arrowhead, Mac OS X control key // The following are for compatibility with GrowlTunes (and have been included since Growl support was added). "★" = "\b"; // Black Star "✯" = "\b"; // Pinwheel Star "☆" = "\030"; // White Star "♭" = "b"; // Musical Flat Sign "♮" = "="; // Musical Natural Sign "♯" = "#"; // Musical Sharp Sign " " = "\037"; // Hair Space }; widths = ( 8, 7, 8, 8, 7, 6, 7, 6, 6.25, 1.7364501953125, 6, 6, 6, 6, 6, 6, 5, 6, 6, 6, 6, 6, 7.5, 8, 6.25, 6, 6, 6, 6, 6, 6, 0.1, // the 'unit separator' control character \037. Usable as a narrow space for lining up columns 1.7364501953125, 2.081298828125, 2.9632568359375, 3.4759521484375, 3.4759521484375, 5.5572509765625, 4.5135498046875, 1.4862060546875, 2.081298828125, 2.081298828125, 2.4322509765625, 3.64990234375, 1.7364501953125, 2.081298828125, 1.7364501953125, 1.7364501953125, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.4759521484375, 2.081298828125, 2.081298828125, 3.64990234375, 3.64990234375, 3.64990234375, 3.8177490234375, 6.0943603515625, 4.5135498046875, 4.5135498046875, 4.5135498046875, 4.5135498046875, 4.168701171875, 3.8177490234375, 4.8614501953125, 4.5135498046875, 1.7364501953125, 3.4759521484375, 4.5135498046875, 3.8177490234375, 5.206298828125, 4.5135498046875, 4.8614501953125, 4.168701171875, 4.8614501953125, 4.5135498046875, 4.168701171875, 3.8177490234375, 4.5135498046875, 4.168701171875, 5.8990478515625, 4.168701171875, 4.168701171875, 3.8177490234375, 2.081298828125, 1.7364501953125, 2.081298828125, 3.64990234375, 3.4759521484375, 2.081298828125, 3.4759521484375, 3.8177490234375, 3.4759521484375, 3.8177490234375, 3.4759521484375, 2.081298828125, 3.8177490234375, 3.8177490234375, 1.7364501953125, 1.7364501953125, 3.4759521484375, 1.7364501953125, 5.5572509765625, 3.8177490234375, 3.8177490234375, 3.8177490234375, 3.8177490234375, 2.4322509765625, 3.4759521484375, 2.081298828125, 3.8177490234375, 3.4759521484375, 4.8614501953125, 3.4759521484375, 3.4759521484375, 3.125, 2.4322509765625, 1.7486572265625, 2.4322509765625, 3.64990234375, 0, 4.5135498046875, 0, 1.7364501953125, 0, 3.125, 6.25, 3.4759521484375, 3.4759521484375, 0, 6.25, 4.168701171875, 2.081298828125, 4.168701171875, 3.8177490234375, 3.8177490234375, 3.8177490234375, 0, 1.7364501953125, 1.7364501953125, 3.125, 3.125, 2.1881103515625, 3.4759521484375, 6.25, 0, 6.25, 3.4759521484375, 2.081298828125, 3.4759521484375, 2.9052734375, 3.125, 3.125, 1.7364501953125, 2.081298828125, 2.081298828125, 3.8177490234375, 3.4759521484375, 4.5135498046875, 1.7486572265625, 3.4759521484375, 2.081298828125, 4.6051025390625, 4.168701171875, 3.4759521484375, 3.64990234375, 2.081298828125, 4.6051025390625, 3.8177490234375, 2.4993896484375, 3.43017578125, 2.081298828125, 1.7364501953125, 2.081298828125, 3.60107421875, 3.4759521484375, 1.7364501953125, 2.081298828125, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.8177490234375, 2.081298828125, 2.655029296875, 3.125, 4.5135498046875, 4.5135498046875, 4.5135498046875, 4.5135498046875, 4.5135498046875, 3.8177490234375, 4.5135498046875, 4.5135498046875, 4.5135498046875, 4.168701171875, 4.168701171875, 4.168701171875, 4.168701171875, 1.7364501953125, 1.7364501953125, 4.5135498046875, 4.5135498046875, 4.5135498046875, 4.5135498046875, 4.8614501953125, 4.8614501953125, 4.8614501953125, 4.8614501953125, 3.64990234375, 4.5135498046875, 4.5135498046875, 4.5135498046875, 4.5135498046875, 4.5135498046875, 4.168701171875, 3.8177490234375, 3.8177490234375, 2.4322509765625, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.4759521484375, 1.9073486328125, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.4759521484375, 3.4759521484375, 1.7364501953125, 1.7364501953125, 4.6875, 3.8177490234375, 3.8177490234375, 3.8177490234375, 3.8177490234375, 3.8177490234375, 3.8177490234375, 3.8177490234375, 3.43017578125, 2.4322509765625, 3.8177490234375, 3.8177490234375, 3.8177490234375, 3.8177490234375, 3.4759521484375, 2.081298828125, 2.081298828125, ); }oolite-1.82/tools/oxp-templates/encodings/Eastern European.oxp/Textures/000077500000000000000000000000001256642440500264525ustar00rootroot00000000000000oolite-1.82/tools/oxp-templates/encodings/Eastern European.oxp/Textures/oolite-font-l2.png000066400000000000000000001676441256642440500317540ustar00rootroot00000000000000PNG  IHDRZvt_kIDATx]"EMlfWe 'f10c0cψa3fL穘1c,f V˺no*v"UuL~nnW^)Dܲ $$,QŒl ! xdHVFwwor#pr|1אӏ}D:ߎ8fVP>|M/;.5 T4"$@CsS +J% )A Ѥq0L._$bA{rҸ$NX~V GP""}}l`!).y/H << ,tC 3|(Z`Ě"K#hvW# uFP1SUF ت!cZϼci{ ǡu_Oa%qƭ,z7O Ǥn 6FbmAA:fAqeY [!تo.j|5nt)QL'!Fʗhhn~<)b<:;R*@уrB#h/0ex ڳ[fywhScCg  .A' C?v 4j^B F({岵=:ԫ&YP}rEWbd. OA\Hpi\3Cs *'X'գ#ayTDMxy*kM5K\0o,ϡ hB !_KZm굟>]E؄M=5ŒaR?;HV,=NJi<AgX=:Y@$RGT>4ʞ5AJ]$',%{1rx,d g%o竊:>>cԘ~A=2{]E6,5F*A<[~US(SdB$cvSNj*PJit3]U,BzJK8!pR_"^fCLn7 W{tyY>Ȫ s$jWq:[4 -F(% `PAz%Ь1e3l-.JLTN X̚$˖ژ!ɜ-f T", ]I0Oyކ!:#Y:bU3 (j?ڨ|n?- u]"`šbZP5:%kp+[t79>'.t7YeC HC(DM9@i+,8vN}}ԕ~>X1jijhA{ju6AST@=g˂=o\wӀ:ՈUriMQz_/֕?aX,ZYӴ rfk5ho])\E@y:Os`R-#]$P{j˛k s.CšzShRS/DVKY}?ɆyW gP<'P$"i|B1X0.eYy]d+o `cjsz2\0#rТdZT$wG6n6+(n.1'x4-(ÊY8U[>|4 'k@ -_.t.t1PRMuL@むr ` kլy4\:S&y<_ j W 4Pqk$y @HvRoFI6FQ T ?fLfS{Fz20n|,-"A YvՋ@ IQPVJTo 2?SWʸ9@nWn5@CX~A`@ k8RK79J?}gw^mtCZ?n˹]t1UoYc bzxJWp -"{qY1 =LIEk p-  ۓyrkRH3wC!LRL `h^i#xݬv: Pݪ9߇jJn jsaE7*&U =ySz ǡ`D$W6~ V%m R?lepV,8'Z#mP$;mPސ D]__=_b bL%xxwq5P8 ]~_J#p]sǛޱX J@ Sް-Զ16* u54 ;{:(V z(/"#hSZjݢ"EQz4cYjҥ5yc] 1PS>n)G HJ[_JE<#hTEPWh q *?H\& W S0(bj`96LD$$te#p:l=dFJT u);W0DzR8-U>WWƕ@d_Q)Wg3$ԍmaO]F|<ؠ8v% ^F.p\:.?F.h1h/ySh]Qþi aC 6+No3v4NXr A7WW}8GHg.ֿKC}>b|.lû}H >|A&"f@ IYZwER"BIԞRX.O;~872T>vpԍm, p!92e$:E5<%ݠ+\xwwuyO:!FQ@_&# ס"A3\_'wji0Ly+IԒON-UnRVPR! 8U]:Q@ם.Lpb2j 0nOnFx%3>W2́I5qŠSqc>{ޫg4\Wt(SQ:1|, s@T-.>@`@vpZ,q2쌹R#G"K*z`/S( okTeSp9/{dd/_V!pZʌ` jc8@ ,d ȡh^wGT[7nIpx.B]r[lw eBJA;^D d҅Jp03\N DK_ÁltzFjHWW! <6C g j= NTFw킩xPsf,URr{Ą,SPC ]FSv =gAIQɐHofZz,A24I h 0L`{ <UffLb<ܵ?DZdž/Nn".AԤVKPIn1՘].h'h$B(MƗJ]9WWrj ݕ| Q)2ٽ)#T^<1p |`҅L"_vФR2W/==4﯀nA p[>P>Yw8 3[@X_FbcJs1>U9Vx0(ŵ Z*O97^" oyqgFU6$Fh W^@>̦;7Ӣ`9jbHX8|f.uNwHtS*LSHT݃Hryn*Aap%*!cWZU-XgA7fSn5NLoJJQe0IyC ]tP*t*4ԑ~.3E'~)їpYDE wM BNp M\q8Ö'Z20!7=6HBf@`y2Wx!f $(sCw.#A*E[Qrt%=c $-%gDIsEQ{UPW2F]fJMV OA@ NJ3pi +p yFp-w+ ,c2]ybчJE(z"^-cJ]X@Mԉ-ݕgW~B*IFlE eO}W&IY  MKzM8dתUPQI#45UhlXāW+k{Z1ZS WFYa0{Ԓ~*6ڕ)xAJ >B(EL Hg]IEA1^-}_8E E"e`T)VqV)F*q: Dk7վ.ɍ5ަԼHF?_řJ1@ªe QH ܹ<jQ jGe%\Lŭ0 Ս+Vaꑋ΍u/:w8vTty W!U"$otS\X .$n.]C!7`̂ [`KC̀F1Ljv"~X #<#uhA QxF 'ZӕګdK>JpCcE9rýew2%M #z}`)Z/MTC@RdNs.h43r]h z;9!e] p5Lp"Kc@&"TNo q,$Ly"lgHd4%+jIp9p5V,b ^*#_0 LRKat6kU}ݨY yB(g!c ۗȓB:[ԈW!U %$zsEi<kSK$HH8O-Ҏ-5/_DG$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$36V$(M@ V+OA\;͗` :o'CQ 0UV!'Foc7K\unQG=%:>~#& rp#!s[oM*+ٖ{_Hٯ(.7\FOM@cE`! oXad U ̧Qy]yܝnpIŪܭ5X G~_Q9N A}=˶ ! Y yY6m٦x`wk|;ԎWJh/.a>G`ov/A.H!{+$ pI0y.}vO{Nڽ7n;mnxS-t>G.m"[ca/|4gۖ=C?Y[&*B *9WRG^1l}tQ5YLX qo;zRa)}YHx:g!G+4I}E6ۿ 03O\vfy|xp;-%.)<'ؓ>b\(pg>Ht )BN`YtW'Ͼ;K3x텛a3e@Q{fP:p=8=?K??S6&_(#v0'A@'p ? x v?zAa@3؎S?*ژ<<_d#N,|bˑĞ*?ɷ6 `|; Ss)Md)?.0̞aOz zh# :qm0h>0p D_k{QHЯ3L ?wޗŎ.MuV : 䀊D;} egVbIx0;nGo[F As6i"Ian8xy+AtxpdK;$/e[UBy@GG<B4٠y">=cT(f"߶6Iom=V2`n b;.;GGKq@y_1r(NDg`25.^~jm;Znk 02v+l碁q<6孥X 4MU-΢9LHJk~TyӑXO8FhS)À%2ŬH5!(bQ vُw`F v!~pe2>$|UEv;Sl@\͍h\x-R}Gy 6דq& {_J68<.A'Iv^,ՎAֿzω WgZ;ђ/Mo׆9m%K~ R#|vM^la`m?yv K2~~֛`ZV O2sW1M #B\NV$ug4gw&fYd :c+ApAg\ @]O;Kh9v7>qԇo |Q@=_<6Ƶ`"U&#><4Sut%AoW)@!+jp$˴iIGÕsFs2a$R)p' rcN\h"S@1nV<4.KDa{嵎NHME}#zYȠHx-H2qv Z'r#hǍ 8%q( W-H2<6- D5`jrfky  9"WCܞMEL׊Q/P$ްO1x 6Ssg 1a2 7}vd*p C}oz{-Y>ק5iv X]ϽvdWv?hIܽ)[- T,f@`RNB놋d. TG؇<}F]kY %vdwƦ?NDW%sB4'ĐSyc+anZ~I!N.qP &IF8^53 ;\ߙiX]o402$ae2gp=nI%|}R C[18NV :+X_noug`,pWJ3Hh-tk0 q!= |Tw$V ?Aoˍ*>:O>ЂUתBObxϽ ǾVE lY{[eG=OWvLN}ǵ9H㈑e$ĂR6 :UۺVO"tnجEӀoM+/s\~.A<+M[p3'{"W!l)W$c.|.ඓ6YĩS'vqx+n}y78 ŶW=070aG p2ԛ#d+%$(tIvk 8T00yÍHbHCn8M+! U%=a\w! c7@*Uga5 ֜hkY]r4 ,*Π\0#e@k!\`h'߾]eIHm 0aX8JM#[OBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB„k㪶˛KRןqR頣!';bC'_q6K۽=O_ٽw&\av[ă&fb{zOrsn{:q%"'ޟaᏯEm8]6mv7 nGt;fxN^d?m7EXpi8B;=~"WtJò*޲Ɩc7q[PTM|Y=7"7I-E;jt5Fg 1:Ć1waWwHl-my?"$;O1'0%#-%f#cȜE2~هgU? CNUkvzNpyFdU?f]$˝]UonG@D"UɧR}[-~S,0I[.E`??j/6 ~pOn/I]Ovo-ղuƏߟM-푪P#Z ]j% Ê7q̙ߢbDˣ7^uv9%-M,wTr]jxVܻt=S{4zzi`28~&޲o8 (ױXTmD?:_`p4:>5i HCƟ.%^tO n ݔSO)cgiCpI NeƭB>D>28tq3zxO%o "~h3v!A#ȣ_ aΥFhA]6!ȿ'x!gDZq 14kD r&:AvtJNo=o"ۈX}◊xS 5%v_J" QKMWۀR&ߗdⴿwߴ>lrEz-x}SrINy:s* 1ڼe2_t&J)xDCX_QA!:IkZ_d7rM| vD8޵v5h dشm9vK@|=sb@~9{@= NcI-OX1!GxΊxA´4ҟ}yǓܙGZ'^w%'xбî8`HדĊn@; 3H n6_"hާ-C;/rl6SD|/fU8)u{;5G~4uOCJJO&O1~Ŋֹ7f̶ =^:4"׽?h'#'l(C1#|M~O#NFaO(f L w_Q|'|~Ȍތeaw&YD!"' `Yp\.q½ð8Hv*`P _6<1e$sb 8eE.fD(L~v#ô7/ܑ[w'Om}:vN ~sY<1MNcg0@4vbkT0T[%~-ΜI7ÎҴ0so [m,y՜{αf" 848S2{hi `-B2ɯX:rIy0?ؒ=@pdX[ʰZ ѳbxs>m+x/PoռK%)yثWu%WdY.P3KGXt^zj[|iաBoӎ6`Zᄇa5;6j2MT q!b@QyL[:[SHcp,k}z`!<Gާ/, ,s֗?F^ a{=dYFdt%OD:R?3m 㼉|puP jAO# gՀ)Mfڼ~ced@9y̼obeM X_lN i9>gwխh+(^PCϼW0Ҍf4'9~>LEV  QVorp1;ԅpHbo B=\Hj/M;u[L~w /0}% FzfV|}i| #Gƥ_|g쵟[>Glgs?M=bRU~ZZB>f`' 8s UׇvM7ַ\iT)E>+P_tKMBD€-oqyoGJ X^S[ =0:ka4z*66kbEcLPMyK>4uA ^*fHYOON?,wlX,`M [ <.B7 jZIk/yUl-Aybh -QK:#j'Ng(co 4l eq|݆m~-oj?.v!}Wa+S(/S^'3~` XaVQ8UqoOAvÍK {ҿ<_^ |u4_~BhU@珴ñK,B[OOyD };Nm7]"<rM]tadj6Nbe AI?T̂b>D~ VuH'L~]X2K>*q@ve91fN_uh `̗Bze䁣1^w&k07vJ"[ֱ?  eZLX0xL;WV0-#8>eW!n:L'ZfۙcA.E5eykQce-#ZQVA#jYZbƽf4A[ ll!Ԉ'~`{-z8">mvT?y" eqH>}WТ6:et`}c^ Y&꧟Uil;)kh#O9(M`zҤ>H*u3RlUoXW~24!(W֐CihB>E}ԛX{J J4o(aXʚ`C!gH js(-#S&n_[`zGaؙ:Vi >Kq}p BFcrLʨخhl*̟i'g 6dAcI"OUi¥l¯J P4P~kީp<2ԕח|!^᳽;Ѫ{K\l=5ZB gh\Xv[鋘tܭw*<!Yӹa>Onfqҙݧ۰-4{?z{dK~~g̏z6u2wW|V>kiEBB) {YBBV1P >_5$$$ Nnٌ5eCHH Di !1@ rzl Uv&ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBs2#d+HH5tGXg?u7;)X n'y1/X|c N>!Psg敡1.7ո'Y@--bΏ=ru ^>[ֳ޽6蝵yv ]/9ݜ>ϒ7|^ӇYCNedow+=mjSy?6/9c :cQ'y{VsNj2";/m jCxݞzEm%^ 'sߤ2 @fM&3Y#{|=gϟ%o-rާQ}K%#q1]-Ql{'W(g[!XTC~֩&8Ci%ȿjy˷գ1 dP NjO[ۑ[Q HXYӽ35 :=0w-.m6:έC `;.-_^ё#Z @X;'+-&ӇlǹAAg/aB7s];Uw~x%2؉?'`WA= p03VZHMOߤ&=Xsv/uON\+Ơc꿕R.N~mK:{~c8ﱻzDyX@a置 [#Utkėl4ێ-9x19pL31"טxߨ|gWgkQe_ͮqiFbSO] Z '6C::"o>sÿ'v@d}4qg_P0i )%_>4;3AS٧׼, Ba۴m)gLx0G|m&E"e ^ `[*e·2:`}|fH\~w sj>"=7o8 UtIC׸% a&\,1c@(izŞc,1`D0E@i5<2~ }:R~='u}Kzg߀ %X=]ʺ?]k q#"ۘe@=PZ7TGjN%AFmI0qUL/TZN/i|0e8!y Ey՛^b6GFpo.hXv.C9"Q?t(c9t242TMš `Я4Tf2g.(.,ݜ@vFhKQ.VB I-E9N֯;1厦@3'͌c85FKR\I^|(X= B&s-(}'~w'CH=[XLb>@p` 1:?$D;?/t&) Hn^Wbb >ghҢ2 @m"u "oQ_Ӄ?'G8&z4r c  yr$6=(.&~{^&aŜ!N?&p8:qI $ocEUE~_zU;`gzz}\bE[{ @b8@-53?v[(܃ڰ"HVMP"(1|9Kz/AM)NM3*48 WVzN^ &`% @GqD["Ox0(J+ yt|͐%F"w: 6N!tuVi 2ܾ\Ns1'8&ӌߗ Bdmx'w6I* {Mg!C{쪽T[jm3oO?p\گfї)  ;V_;`~($|Qyx0Moh\5`)IIzhaH @hRSiN]AwexʴnB9<Q;%<9d3@E) QebvrvK<m3]<_ˣژ*Ӹ2-o`KeE|1)"7 `MFxhq'Tf ^ p=+<7Lzn4( 8nXeُв'J'{TJfhߍD6Kn/lVzYDK #mKhGT@L9`VH\8翸x- ӺKzkXf->g^蠃uMHAmzfhӯ#j~dU?b"owwNDWazvh=I`ǻ=J?~@pd@+/eϨKw&A;%糖VŞ ljۗӷc̈́bk2o߼p1m> 0֝-'i8\qI{ 9e=b9d|Pi|I,coCxݷ4ތv 0zgn^1pm'3,A{قm`#" tr6t!e;6G8Q[D U @v>0%ISqGxG '6`rdT|˵w1A;ӭ an~!w/,8 t(}`mꕔKŝjRX K+~^Řk/H[wU=T!F)q/U?篯>)Pv,ZfF>'u5?].,3{U%?/(+Y .&]y()dU?P1*Zk?v=i ;/ :ְ;teX N.5 @Gޭ$%K  + \6&xo}=}Δ>cO߽wAU׿N[{ [_|g w:o=~چd{HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH#tƻP$1vl^Ò/p?_?}ڭ>ٯs{r˙{b`}tmewD/;Bl! ̘ͳɓw3nNX w =Lj h} `:%֣>tGrf*t~ϰ9a]=/~쭆fٴ 7 +ciϗL?@yPsgԗ.v1wWPVD{߰ ?h|@5-s?|jzzfw1!Г^Z"}S &w8 [N!>ATͧ?fTӻ_K^XsGZX~3ک3詳9~wKŚ"XŃabB|5[u8!(¯ "54&8,1zh/[ `ޙ?re*9_'9ퟒ8PK0$7c\XlVgso,r\8#ll^yPC6gB ` :ִ _C493=|>I'G!}!NmO,ӿL94QaR D3.\Mys\u " = `Ѣ'u/6QGZLOs7g5&h6ܡ=[[ijd5@d(>@Qü/K?VlhDYo19^o@)#pn񭂉ζ@'K_$-g۬OtE|?tŲkOˁbI #')`3t"FJ}8!tB.ތ=6颤yOf9QKah!(Ox/"sY83ඔ.[÷ ?.87^s#k`l dI{NeXM6/1-GQ `C-C10o>OővpCFP|c"^4Oj1dz&՗^sâD xj7û]>r@rg NDž.Z fQd7r)O<՗!JbNtQL[37ƿ`B'E62}]HXk*sF KYA(AP;Eڬ.'nɨ@5W|aPMWpaf<;-yQ#/7⹬QPqXi&1a?.PT0vG'ER|\q #E$@ @<(BV2]yfr:`eLլ4'cXB : d_i Ig2f@Wm'\I׍cN |yݛJR7|\~@^#G8>ϛt/KRR;L~7C~\:$|RR0B3  en7P !*%@nSn䗋T` k I~=WZ]P}=jhM M0Rky@%@?⃡d avr@IgUkVuH0Y\FߞA i_༞"}#م@+R\+CjW{:%Ckl\ yu0o5 =Zq~UB vy,b64utS5-m'n=txYtdb]ߘ̃Xؕ]7I{]?iMAp@Jw&[p;5Cg(gxlFPBxpg j HIk9@@v4<@cЍCU^Oe*LF`m7/ez !+|6,dk^ YxDT~ W@w `&*ݿA@ _z5 8&^(\{0; ٢`6<~٢̠"K ,o2Urڸ|,:JE8rU^(Rv` nt &V4ogZ]0 sq@da.@ĜMiC\-_ELn / u^#%|Ah,3Tm Go£}qvyNqD pr 3ϔ1A U;Txm;k RK`cv'Eߐ8⹑<qB$6໪P ;T' .9VtU(PIJSa5g$OT1|G}k&khE|}ۀTGMTg~XoR|HJ'T0.]\#9_G]Z5ig1LEDq~ K;|CjPDZk Q3ƿ^A ?"G$^XLV#tI0.EOyw< G+Yɴpڲ/EÏD4' 2 Xj(2f&KmC#DuN?[ᗘ ?Y~qďF*,}GxGqsA"_bЀ,/3&8&]& ҪYsMe?)1D4aR^hתWXf@ se4fOlsM k28 8>Mɞr=k@d E @3Hbٗ(U˕s8:_ej #dIE^[s]LU|qT"|I "bàE)d+Nj m.DD|?L2 %k7 Pf!sZ3k]ǩ>ػY'" u@Vo2 #tvS40o_u[VOBtSG6C1l7] LǒuZ sqTU؞JvTQr@tBOxD-Ҫ|МXW˘m]Cv0`(g;-HU8t4:bSi|g I/@0 %N0du3!J٧zD!dDXx4AЎ]4WW "d$ɻ&[:d uM59_;ec(ym^J׉ 1?$f/깘c?2}|z@2@zsKzO HPӬe!B]LwIhSo~20-jFRwо}4$N,{(K#dі_6 ӅO"&+erlGM_$ `陝sYDVCN/@Ղ F[+ @@^k#W^c-1wLr}K/01D9mO%g'咣@Ȅ?*1@6r>hqaQ@I  !ѤjC}E5F 06<"K ެCi"sM`v~5{z &̰_{D NawىlD/&<~O#&/B=r Le GC1V]#O?jL%V].; YےkhpW?k!GYS ݤ7=)͡y@ :}iEFQpR=hfE?7U_{'8-yb"v~ x}_p1U@,6L u]~o$ŻL^CAuٛ_7vwx3)+ԙ9.MmU+l}"qsfZW{g3O[]U; Cw>TTZ0[#sL/}Oq>Os]r/.}mn?yLO6RodEW@)~%]Amx5 )H́`?PlY_xU^`2.phJ 3%(v?gW]DPoCSx5 \l<Zۑ?,FC;|<,FμEU`y9Ј?B:ȹvLU_ {-'yHg}Vb^GumC"xeٵtvw$`J! k,#d* \M&q4U7an b+nGKN0q#Seh )` q !f`3jkI𲵮c3\@,(S.2D^>ɐ3?SBk[$pߏf@ů7PP)R(&97ɕpCjD(Q)o=Q\S>pwEM2ݺu;%H([hr"\vk8+MD ~<SN`n}hĻ$B[5 lz LV{NAk"P9&A)7So% ġ_b >w]t㵺osןXV>?aZc6kʼq{M#~,G L9FD"4~n@τh?w"{pQ+R({W%טc1v}[ɏ\Exf2%=Z~i]j)'"[1O54퓱xSZ0˱ kDq1d'e5rߋ>?'}kQLdA7çZ߾7(M#Z KS6xG~'<pI^ =fF|јDC]đ 7Ҥ/lIx{4 ?O๳j.K{vOTF W rDRQܹsobeR LſB&s~h%CN$$wa2QTkxON,e =w}Iok?G緹 Π!'+䱈lf.G৾3,CPoj9 " h9,'s!N+>C|TI&eP'a47gNF>i:NQd *G ?aF]Qsg+k~C xO7탪DY lȾ~mﳖ.> ^2&.'w jTcbbbVJv߫!hR>sV#EO>9kq0Ld+=1XО}.?6JNO_K;W l 1A<,w6$ub˃4(kN;ɗ/(AVC %v*̠,OMe{r_EzuBq* \vDQDs?ȣ~U+8<GElm_J0-E"czϔ?56 tQȰفqVY&{EZ$ 9&gXe2z ;D&I%ݟ20 ~b/I$LbA=.jF~x%.C5Jxk$V|[NOa-G=-EvәS(%8ad5^Q,ema@j1M>0qؓC!^;dm&O$!q_&b;ZblS %C]9jrux*)'KORJF)V-tlU D :#M `\vsq7fEŋ)U=VoTE":*/Km"ijѺP m ]AN=F 6zfA$Q;*f08Xx2J &@ |:I0.b%X8n4H;pa@Jwi?&ٍADQŚn%6f}, |u2?R.֬{c x^=_ʪF&xNPXHR`ZKLC\m<sooa@,! 'l PzY`!:j2F n˜0_| o+Qm1{^+j" %ohzz90C0T+G8M-݊) /FˎAj  њ-{3("?=r5>qENND5꿩 `^.|O1|6(mE~F]YD-5dh97APjr(Y 1;?Nd؄fIzD$hqلI+Ll8xblR;b' y!rڤ-JAc/@$).1dܡ=WlveN8 aL ,GBu6Ntl<%ng‰iEyz"U6s @%-t F[Ԏ1~Ufh޿=tdMN] d]D)Ik=\T=rH*-MD㴙hbЭSU @d8Vˁ-_/o3;SkĊ.?}.bjԺ -}4W,"ibE~M0%3y̳3. \)>dL}V@@ފLC@3ri ^չkԠcS Pyt%e.:9%x`I Bq o'fa%)tsN΂8 >/J[ @@MIKX~KDB{2/"eNPg#0 eG(٤ăL";'S"KoCX;)$H&)t`es lFœ_] `)dDQ"9ZeN1°3}E6؄3=XWo— `1yE%kT u7Is9"/0 ?>Qg:35ХL*Cutx].};? (VLP*Jcx+;+~8! & 9oHmT;%f&rU3Qe%J<͜^ѰQV%F@?XA2a߂x]  &[x/o2ŷsk ; 4o;ߝtZ\G% ۆ~m(^Amg|_%]lp|zMmuH}7[s_!!<Ґ@T  ZQi j.9g4=`ieF ٳngHN4r2Ο [5$0SԾ\x%@i< CUi{n6N#B=\% K ş͠Lun7j%'tBM]p L P tl: #GE=E%;٠wГ .$ٓ8# B3:i2I3/'тv{6yQgcy] O%n v4>  PAy&;Bvj&O KW{)XL&*14䞫3ұw8yGVE-Bi:{ NyFAC?`xۣ*QWmA+SuF$;Uz3$r8 M CT:2I:Lrhp .upW!`:%ߘJW'Hy(ޔCgɣB#\q%Hkh-7t:>0]C>c`l75x҇6iqKr!rVw  wqp]D.@UGctJv+'4y%j~_h z$c&"a0qn$c)D6fNG@[r-7@>}1NyQ zS-W_R' )7@ݏOlj"6-oApIp^\=zPyQ\(?Y]쳋zY6|(r{w(;cAĘ_\3pWm4Cͤ7~meשּׁe--~9 -+3)KT15J,A#HfLdYdo}0ˊ6qZn!:' _OBT` B|y,̚YԐnxHS xXj_+ "+E *, :Q!v `.gݍ~?'w'0X{Qs;V]{)rޗl&xS+zw (~ / e _q %$P~t4 :Gow YSl@{` &\ɷzWRQ_x܏0.mFdI;UZ -Wn ZԘ>zѷH 6ԔrCr8l?|T"ZoT>ĥ576s ݉|Ud]545,0`lh;`@D>1MK=Mm %):c_7 V\'H 9)1 m񁚙5t;c|37ƽ 9af֟nXDkCW)Htñ@wF#y+Ng9 NJ;΄X&O 6vb y:5`QYu!ܓ?7|FlOdah⺪ \¿JYz7oeq! 6H$B-|ԕ|ycTݓ @l(x<doOs={tN ?%u?ehAA{!}*dgq>7p[-[eD5&,gdNS,D ̳h.e\y\N/ G6ւg4+AU~C{u7ܐ @Tq/& &BzC,ض߻:Iq`DB‰RK&;]u㚄_fy@LG3O> ^H CJs 0]$$ij-IbЅ(yhG_Ck>C_y6\ GyH=.( "cg^1/C&/a| g  <-EXV`)(a4r4F6ok:|"iQ/srKT^NoSʦU ƔP7S.CwSehwO{Q$4]^\ zq7-E74S)ec'%>}ߤ׸4ֻ/UU9B]xc 5|\O5aV}ԑ.93bYyc_e\7YϒV)T^& uJU3jn(/d-rHd 5(  d@u$%vD)f87z*fta{i/?a?=1`]蜫(J@:YQ6ɡv`o @IoJ }rXƑפVs~,Ɯk-o~(+_Kw]#* h?+wXr$p tُzo"i@z8P.$}xϘYȽz.hEr` [Rg̘OǠ;cLi"Y?LWP+⚴ p %go#x[rwӐfI(vI0a-z /l -/[(N>}nv2:6s EWhmd9!ib{U(Dy?.a"(r_6o.t=a(cW8@yB@fԀ~F֟~)>tϼ^p `*8l Mೖ8N@rX>ג`P&G>6kqfm%!D<9NT0ˊT2b=:QGrݓ{`Nz8K~;l~1eh?WԄY}7dN#!YtI:$0Gڤ@Gr-ȚFnП;v |y;/W!+Kz9ku @wn tG)}5o5L@2J\l)t_?H=yYȲ&e|9k4;GN8L&Йn;@9?)j.-6+gRz +a<#Qt4Հ x׬9*F0G0& (oI KM寓& MNJTF a4vyK7 5~o۽H_P-oQgZܽk8UW4qn)@ ~p%3RL@M[R迓f? @hKCffl-Ʃv:*rUid1eg95 tcf")"S TﲟdbSK z"{eGc]$T2k eo@'*x4*T/Ɂ+N"dhe$mAɓ}vQB\pMlGSS@9;hdj(Q#x)̣j^9|:xOsTŶ9v܋M^9oѹ(Sb4&07޵āIkhRj)4֟?ؼ._/~0)c]sp$0wI +`nK._O6f w ~![S@dAsԡx"ܫ?U 8Ϗ"//GQ.zڍW%rۈA'g~#kwVQ(Gv, H"~X̫Εxj=s:71L5ӊIJT܈ @{%CJkP( 9\`E]ux D2Fj]v'g?J#C-i z$%op a~*BJ5kPԚ$EeLԐ(CuV65z_RN['`W3%3"%ρ7d?k*;ޛ?oIHbuIGcDm@ #O6>3$GS/S{4+rtw҉9F.+OdZA5NRQsl7`w)*804ڄٌPgܥ2|(R@ }^ۡ(sG0¤![Fm86FttT9 iiO.餝Y0[b/U8POQfk@GAEF>Id b@SiN0/lxy&RYuZSJGMZ@ꭣKq |WQHG}#  * `W@?.@FtНGQl=uސP{͞L 'Ҝ=6&JC&3cRġEwDQZg+Q(cxh}TƿР\͢w^1Ia$E=RNQbQ =-6G!/7G_t̒RC Ͷ,=s } ootSFʼQ5tZ,Fl~2p/ j|32id'tfEP |2B8L/$   m:ɢ x3ʬ%$2,SNfi-W/=!w@rh;W2_1\lRFT T})- S`P?Ȗ/4H+bO[V~Kh_@1AZlm )*%$o!Aŏg4 ސmμzʳcՔo+S!:އCtNY>O2sIy "i n*+Z錳H+Fp{_Ff.MFGgWj<>|z37 n,3c잩MWw*=? k{F#IZfȿ@X.ivU? `+CmZJBqޯǛ]{/ZWrh*^`xEv3ӘQݍG$m4tՑWK2lNtD'YْUJ<.PI6!g鏍#XemĤ:>uY~39oarx, 1 NlHV'GVq=VG0p7ƨj[7<28 Fzhi8Ydw,x(;tի/>.TɄ` WB/ v7 -pE[l֬<;5)Px`׺+D=6$3KPV c`yފ/C]hAϱ{aP2Giڿ۵|"p Wש*ЄP)VC':vP6A]gt1S]f9)%#+t6fYϒM0/$~ژGn>y;>`pw)t%1ZXw1^.w6[dۙ5ܧrɆ~En.k\ ߉?(ؖ政&Wֆi Mv #f.HP(w"v1*G;];HY,.9/X&ʿKl%y@EUk\~ŜN,KߍQ/*X!WC{|#{=@SYy#s j./aX|( M'-Al@CGF>?߿&~ 3gҥ5ҁy0i }Rq}KQ6s ƾgRo`H/f2<r7mtdgl m.]^U`HX*8\K?-z :^}"@2:oA))e9H 8!@qDtBoF98#$w)]Q#`7L%Ki(e 7 4yPH#Op )8-Ufue5΍esK-@ۙwL ]!fH!flBNA[3~%m_߲rpA{ēMkDx e6 ^~-X6ZJqqG9 Iw2y\Òd?C}Pn#҇X|E0^? aY8$DqbqVq)M6 qg(,~ttn&b1 @M0e TF8Ҕ,4i?d$X4 JaB:`qdln"`cv㗰qvt 8+)C`8}1R$7@䟒@‡f@ \5ГU|!,<~\i7%qe[NB%}(t<-(*%GopÕ8B+n *Hc1g<mxM;KPwfq ґ`|"S2ܶ6x~8( " '~ĞfdBGNsż|Yil +fX\eLY& DIoà_RukY3 %sljh6R",Q ~FH6Nb=:6+XD 6wZ/"L#.8hTp9:EXc?WOTvhKw6mTiD+@O!x|a4SO+K1Bt z |Idcrw@= qs'-[;ĥDΠ*i se5!bMwGS~JM@P%k#o&W0qXƒ'6+dEOKAێ0~bfHB2<9kq.&ߖ"&e O  'VIZ}! 46~B+<'zJpvF& ߆tyI@@YeF;oS^ 9V} Zӳ 4'790{7r⑼Hbs=R,(70(tq~?{ &dU?- Vv?c#&},? .xTp]=@FS~/{ /y} UQn4+_^zlI.L%Oկ[}d4tণ k_賰Cup&F9O"X[ ~yz@bg7$EnFdwA@P' .k(xO"J\rςM8Qu(VA #좉Iω#Ć"rI/j8*<]D1*En8^P`GT˥nj 76\o[~m o2Oe'|fYqwZ]/PwY苜G#iߐ|,O6wyF9s ƫi荊/5Gu< gЏ8U7c70*8|A%;>mUnUƶ3Y"rr|P/N[9pdƠ8SuH8c `@oI֡9gz*},H @~e`fb(>+j|Y?p et߇s? $ sW %t)e}ArG @:f:-:Lqt7z#|SW(L L~ѵ0nUm9ơN /yOrþ'Hj|l| ѩ@2(o鯧 y ]ݥ:GEJsٯғ}$)A᪋9Q )մ f}nP[duff[n+ 75K+˔G6J -@!mQ2ϧAFp$:h9rXge7*$OQ-GQfp7d #ŷskɚ+?OnӚ}Y5$$֔/O,f64e%A~'OWSc2)J?QfXS.gqc?o ɝƼ-WuK&~Q.֔w ԾW<d_W7'TUEA=\U2HMlT9Ģn l YA3 @|R]7ij8w>&O  Bj&gy %a>AnO$~^Y_e(nQN*[1VKc[@ E@Ky0yx${5 X<aJKx|/f]dqydMHlڷd$1 Iu{zwP70@Jm 'NGm\K}'/ $MZ&r?ƕ4MO+%ɾA7.,х<0V^ :[tehDp$}˻[,WONv <b׼$E1yi)UCZM M'ProI(ϟ?G@V ,,[1_~zq.99tLL:w Iu,\,d?#řၖ-8ix[L02]O U}i}*p*tEZhi4"jtq#t`Cwt\J;y]7HWi,Dvoav54y$eHaJ?^#C5_4Л;@2H |0,R/- {\<L-cW:˼$ݟ4:y7#aнVndxpe}`Z%`sX3^<]w:pGYl1s?OnZ+'Rb-\ИH4+$Gڿ~,Mx׷U5qP u/^W$O^ VA)܌htSq"*~Omiu RǶ1- b*x vvL˪$sbp7Rp#t|6hG[] MDz*@GΒע@l ~8eIz!d@ZJ+=.r)ln nG|ŧ |AE8  M"ڛj#!A].w2AK)ЙOX%bȿ8/ z]i$2| !SI3+O":4аHp˧N%1Lqҿ{ͻM>Is ML# IDe3R@&*14KD?~<f&_ T%XmG<*=L䳊t*&xM绚n؀B=+d;vI'=-?:RsyA(_#EŅRz  5#IlR,y>P,fj㭼-TB4*} ů xN(Abhī -.ςAդǵ6\\\>|QW|/M R^gC{D#Y{>誑}dLոı>/c0PF2[C2~5sio#L# 9 S")d)jY Y]87Q`K` ŅC"Gh7@Voj?Kf}@=:/[(pԗ䛐r'wg#1ӵ<5D J ``$q[f:7t:8)'pO$V.̮3YZ^J~Up#57^0;E9&"#?h=\jL @onH- f|}V6S/ӵP_. ?@ @h9.nIg\Ti !On?&.` OMzv4Elj#8w؇R%vi9au +g}Jw`ܷP@;jj$IB"Ȯ )CXN!8 ]ҍy_kyixF2;cޙGQ<-h@ r=" "eSPP((UP> (7TDG@A喻- JϝLn۟d4mf^d2d29ߦ{ه|`,xl]N`;/7n[֒X?N:'LnaڑhA׻Njl6)lm 0J]!9 ]9m,⿰$=5./+9rXS&h߲E #ҋkz'g^ۛM2%\g0ʤMShAg1:g |]q/ڮ[!-z[w5{}l#}X}Z2J^@Zm^3V=vcdQϰܚ%].k}x; -7чKeȗx}Sr@?p(' Q3?_VߵY)bP 9d= c}Tf}eiEVj uݏPToڡJ*F?; @=ћlH}"4|1" m36by㇌~sRK5AD dw18afVjWw"bj3I94;\b׹NaNOk9["Fx_x-b/5s0 z} K|L ҤC@=!>&Av}@^CQ䣾14E>_AcM߰?j<#2þ/O'> a~+&>_s}. rc:'%5p8 P.V@)R H|XnU:H4w Q-2)D(":~CeÙ:c$q*d<&ԸFR@0ڷ0 ^(H2l @=/x @Acy @AݷoڲPYm2#111*%QN#TdH~5Py&6dmlՄ@C iL:r?őgSL!=.#D^g 3JڱbQUHU|CAp-M;Iwy1HUNfZk0IYFc -d\:N)ηUKp$~Op܊s"B:X㿙,fۋ#Q7 82ߗI;. 2qm9#zDu?toa!Ԛga_ Th}Fgx{nxH g+1TBr098֜hoixhvWi&#?C/Lj9SwC888 m~m:vε)1HRp"瘕kS;tڌ,~43`΋!Kvt>aHw~f#o;-:z1j\ω<mHon/ǣ²Fw"#1ubPF;Z1PCoIG=v-35Ed1ub@/;=>j6DcK|И3en\/h4H$~wqa5vǼcsfZ>u9+1e~jୂۭо\]j6:c=yCSE/X#ORToԦ6KimSh6*4?b{N)UP5ˌm^RHol-Mk|w俵@iR*zicij{gZ9.Jz^] )6sZ܏oI}U~Xǣz}h~d+/8?j[C)}, l.3+h TRU/v74z5בKzG?Jƽ{7|R_*\$ըǘX9x j'zU#!YIz;(n@\d?Υ82&CږƄ{hzOgIMac1 GLMeaĶ9-ߥD*x=}sN:+?4k׆ %Oǐ~&I ,E#4p۸&=/r3" )ly$zpHa%#om/ҴRpc<=2 ƺ-A^G7R=hkb#^f9kcy!IUpџ4q,2mNfO;%v/bN&1D\oW:o{uNLSbi>i5qV C3 ~OШH krr']+n PFn3fL? ~Pn+Ad{tzsGIFH.bn\U-ġ@Rm8`tZw<@Kcm\~uk0k" ^&!݅N WHUǭpK"wpmI^I}Ҫ;ԯoq$~1f'm3f<h oL|HjΊ56 "rz>5"E~\R"H ҫj0xF]cѡub-ӛܸ/mˣ Du0Mg ]Ī_Ɏ*[֓7j"=UֳjқC~tcecs#o#/h1&Czi߲HnFNp^^*n3ܻ64+>ܞ|39 `A2^K͐BzSDc֎2dT\hr@"2AOR(@zF+ĕ;2z@l0yZndW$lЊO@қC rXb81"SC]Z);Б~5Җ c\2!֎\/G+̞h;8 #%V"0.}9n1'PĞ( Ffe;sq]͵b 7T"XQ7Q] pSh%GᷲU*f]8 uPE@_&]/q]_$o z@K0NpW6}G5zT"FbkL@bXDl z@c@`Ld+*hǮ2n 3pMʦtU3$ԦBݻVdIГFv%)0;71+1 VfE\Ɓ^2@hы'e2'Akd@}nmnb,@u,(H =Q%1fNhab%vU+Մs]S(U0Z2g4͐Z [4Cv1wE2c]o,OTRA^eQi;:]L#Nɤ=Bp4 ,$|`%ɮ7xƣ|(4̱'6ɩ{4 M4󞘐W:-3BǜI^xtAZdTu(N&dr2#mJcBn"zjn 4û.{u2,0,^v>._ z5al-IfBgˋz I9c]ː5n@<WBlZ*X|򦱭u-^@gZ_\d0Є=m1̊@w} ]Tek(~>O(@uҋ'bswuS㑤j5GNfQ *m&N~EzF{iSH d4sLI)fPg! r*yxG;#fj(Jz<Ùc`O2PS0$5iBd"4e8.u7Vi) "*ĥ[!;@.^XqÇ6>@gɜ$+@nz8ku;<{MIENDB`oolite-1.82/tools/oxp-templates/encodings/Greek.oxp/000077500000000000000000000000001256642440500225245ustar00rootroot00000000000000oolite-1.82/tools/oxp-templates/encodings/Greek.oxp/Config/000077500000000000000000000000001256642440500237315ustar00rootroot00000000000000oolite-1.82/tools/oxp-templates/encodings/Greek.oxp/Config/oolite-font.plist000066400000000000000000000174011256642440500272500ustar00rootroot00000000000000 encoding windows-greek substitutions (Control) (Command) (Option)   b = #   texture oolite-font-gr.png widths 8 7 8 8 7 6 7 6 6.25 1.7364501953125 6 6 6 6 6 6 5 6 6 6 6 6 7.5 8 6.25 6 6 6 6 6 6 0.1 1.7364501953125 2.081298828125 2.9632568359375 3.4759521484375 3.4759521484375 5.5572509765625 4.5135498046875 1.4862060546875 2.081298828125 2.081298828125 2.4322509765625 3.64990234375 1.7364501953125 2.081298828125 1.7364501953125 1.7364501953125 3.4759521484375 3.4759521484375 3.4759521484375 3.4759521484375 3.4759521484375 3.4759521484375 3.4759521484375 3.4759521484375 3.4759521484375 3.4759521484375 2.081298828125 2.081298828125 3.64990234375 3.64990234375 3.64990234375 3.8177490234375 6.0943603515625 4.5135498046875 4.5135498046875 4.5135498046875 4.5135498046875 4.168701171875 3.8177490234375 4.8614501953125 4.5135498046875 1.7364501953125 3.4759521484375 4.5135498046875 3.8177490234375 5.206298828125 4.5135498046875 4.8614501953125 4.168701171875 4.8614501953125 4.5135498046875 4.168701171875 3.8177490234375 4.5135498046875 4.168701171875 5.8990478515625 4.168701171875 4.168701171875 3.8177490234375 2.081298828125 1.7364501953125 2.081298828125 3.64990234375 3.4759521484375 2.081298828125 3.4759521484375 3.8177490234375 3.4759521484375 3.8177490234375 3.4759521484375 2.081298828125 3.8177490234375 3.8177490234375 1.7364501953125 1.7364501953125 3.4759521484375 1.7364501953125 5.5572509765625 3.8177490234375 3.8177490234375 3.8177490234375 3.8177490234375 2.4322509765625 3.4759521484375 2.081298828125 3.8177490234375 3.4759521484375 4.8614501953125 3.4759521484375 3.4759521484375 3.125 2.4322509765625 1.7486572265625 2.4322509765625 3.64990234375 0.0 4.5135498046875 0.0 1.7364501953125 3.4759521484375 3.125 6.25 3.4759521484375 3.4759521484375 0.0 6.25 0.0 2.081298828125 0.0 0.0 0.0 0.0 0.0 1.7364501953125 1.7364501953125 3.125 3.125 2.1881103515625 3.4759521484375 6.25 0.0 6.25 0.0 2.081298828125 0.0 0.0 0.0 0.0 1.7364501953125 2.4688720703125 5.3131103515625 3.4759521484375 3.4759521484375 3.4759521484375 1.7486572265625 3.4759521484375 2.081298828125 4.6051025390625 0.0 3.4759521484375 3.64990234375 2.081298828125 4.6051025390625 6.25 2.4993896484375 3.43017578125 2.081298828125 2.081298828125 1.666259765625 3.60107421875 3.4759521484375 1.7364501953125 5.87158203125 6.085205078125 3.3782958984375 3.4759521484375 6.11572265625 5.21240234375 5.8258056640625 6.451416015625 1.7364501953125 4.5135498046875 4.5135498046875 3.8177490234375 4.5135498046875 4.168701171875 3.8177490234375 4.5135498046875 4.8614501953125 1.849365234375 4.5135498046875 4.1748046875 5.548095703125 4.8614501953125 3.8177490234375 4.8614501953125 4.5135498046875 4.168701171875 0.0 3.8177490234375 3.8238525390625 4.168701171875 4.5135498046875 4.168701171875 5.21240234375 4.8614501953125 1.849365234375 4.168701171875 3.8238525390625 3.4759521484375 3.8116455078125 1.7364501953125 3.8116455078125 3.8238525390625 3.7750244140625 3.4759521484375 3.8177490234375 3.4759521484375 2.7740478515625 3.8116455078125 3.8116455078125 1.7364501953125 3.4759521484375 3.4759521484375 3.8116455078125 3.4759521484375 3.125 3.8238525390625 3.43017578125 3.8177490234375 3.4759521484375 3.8238525390625 3.125 3.8116455078125 4.5135498046875 3.4759521484375 4.5135498046875 4.5135498046875 1.7364501953125 3.8116455078125 3.8238525390625 3.8116455078125 4.5135498046875 0.0 oolite-1.82/tools/oxp-templates/encodings/Greek.oxp/Textures/000077500000000000000000000000001256642440500243475ustar00rootroot00000000000000oolite-1.82/tools/oxp-templates/encodings/Greek.oxp/Textures/oolite-font-gr.png000066400000000000000000001711021256642440500277240ustar00rootroot00000000000000PNG  IHDRZvt_ IDATx]"EMlfWe 'f10c0cψa3fL穘1c,f V˺no*v"UuL~nnW^)Dܲ $$,QŒl ! xdHVFwwor#pr|1אӏ}D:ߎ8fVP>|M/;.5 T4"$@CsS +J% )A Ѥq0L._$bA{rҸ$NX~V GP""}}l`!).y/H << ,tC 3|(Z`Ě"K#hvW# uFP1SUF ت!cZϼci{ ǡu_Oa%qƭ,z7O Ǥn 6FbmAA:fAqeY [!تo.j|5nt)QL'!Fʗhhn~<)b<:;R*@уrB#h/0ex ڳ[fywhScCg  .A' C?v 4j^B F({岵=:ԫ&YP}rEWbd. OA\Hpi\3Cs *'X'գ#ayTDMxy*kM5K\0o,ϡ hB !_KZm굟>]E؄M=5ŒaR?;HV,=NJi<AgX=:Y@$RGT>4ʞ5AJ]$',%{1rx,d g%o竊:>>cԘ~A=2{]E6,5F*A<[~US(SdB$cvSNj*PJit3]U,BzJK8!pR_"^fCLn7 W{tyY>Ȫ s$jWq:[4 -F(% `PAz%Ь1e3l-.JLTN X̚$˖ژ!ɜ-f T", ]I0Oyކ!:#Y:bU3 (j?ڨ|n?- u]"`šbZP5:%kp+[t79>'.t7YeC HC(DM9@i+,8vN}}ԕ~>X1jijhA{ju6AST@=g˂=o\wӀ:ՈUriMQz_/֕?aX,ZYӴ rfk5ho])\E@y:Os`R-#]$P{j˛k s.CšzShRS/DVKY}?ɆyW gP<'P$"i|B1X0.eYy]d+o `cjsz2\0#rТdZT$wG6n6+(n.1'x4-(ÊY8U[>|4 'k@ -_.t.t1PRMuL@むr ` kլy4\:S&y<_ j W 4Pqk$y @HvRoFI6FQ T ?fLfS{Fz20n|,-"A YvՋ@ IQPVJTo 2?SWʸ9@nWn5@CX~A`@ k8RK79J?}gw^mtCZ?n˹]t1UoYc bzxJWp -"{qY1 =LIEk p-  ۓyrkRH3wC!LRL `h^i#xݬv: Pݪ9߇jJn jsaE7*&U =ySz ǡ`D$W6~ V%m R?lepV,8'Z#mP$;mPސ D]__=_b bL%xxwq5P8 ]~_J#p]sǛޱX J@ Sް-Զ16* u54 ;{:(V z(/"#hSZjݢ"EQz4cYjҥ5yc] 1PS>n)G HJ[_JE<#hTEPWh q *?H\& W S0(bj`96LD$$te#p:l=dFJT u);W0DzR8-U>WWƕ@d_Q)Wg3$ԍmaO]F|<ؠ8v% ^F.p\:.?F.h1h/ySh]Qþi aC 6+No3v4NXr A7WW}8GHg.ֿKC}>b|.lû}H >|A&"f@ IYZwER"BIԞRX.O;~872T>vpԍm, p!92e$:E5<%ݠ+\xwwuyO:!FQ@_&# ס"A3\_'wji0Ly+IԒON-UnRVPR! 8U]:Q@ם.Lpb2j 0nOnFx%3>W2́I5qŠSqc>{ޫg4\Wt(SQ:1|, s@T-.>@`@vpZ,q2쌹R#G"K*z`/S( okTeSp9/{dd/_V!pZʌ` jc8@ ,d ȡh^wGT[7nIpx.B]r[lw eBJA;^D d҅Jp03\N DK_ÁltzFjHWW! <6C g j= NTFw킩xPsf,URr{Ą,SPC ]FSv =gAIQɐHofZz,A24I h 0L`{ <UffLb<ܵ?DZdž/Nn".AԤVKPIn1՘].h'h$B(MƗJ]9WWrj ݕ| Q)2ٽ)#T^<1p |`҅L"_vФR2W/==4﯀nA p[>P>Yw8 3[@X_FbcJs1>U9Vx0(ŵ Z*O97^" oyqgFU6$Fh W^@>̦;7Ӣ`9jbHX8|f.uNwHtS*LSHT݃Hryn*Aap%*!cWZU-XgA7fSn5NLoJJQe0IyC ]tP*t*4ԑ~.3E'~)їpYDE wM BNp M\q8Ö'Z20!7=6HBf@`y2Wx!f $(sCw.#A*E[Qrt%=c $-%gDIsEQ{UPW2F]fJMV OA@ NJ3pi +p yFp-w+ ,c2]ybчJE(z"^-cJ]X@Mԉ-ݕgW~B*IFlE eO}W&IY  MKzM8dתUPQI#45UhlXāW+k{Z1ZS WFYa0{Ԓ~*6ڕ)xAJ >B(EL Hg]IEA1^-}_8E E"e`T)VqV)F*q: Dk7վ.ɍ5ަԼHF?_řJ1@ªe QH ܹ<jQ jGe%\Lŭ0 Ս+Vaꑋ΍u/:w8vTty W!U"$otS\X .$n.]C!7`̂ [`KC̀F1Ljv"~X #<#uhA QxF 'ZӕګdK>JpCcE9rýew2%M #z}`)Z/MTC@RdNs.h43r]h z;9!e] p5Lp"Kc@&"TNo q,$Ly"lgHd4%+jIp9p5V,b ^*#_0 LRKat6kU}ݨY yB(g!c ۗȓB:[ԈW!U %$zsEi<kSK$HH8O-Ҏ-5/_DG$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$36V$(M@ V+OA\;͗` :o'CQ 0UV!'Foc7K\unQG=%:>~#& rp#!s[oM*+ٖ{_Hٯ(.7\FOM@cE`! oXad U ̧Qy]yܝnpIŪܭ5X G~_Q9N A}=˶ ! Y yY6m٦x`wk|;ԎWJh/.a>G`ov/A.H!{+$ pI0y.}vO{Nڽ7n;mnxS-t>G.m"[ca/|4gۖ=C?Y[&*B *9WRG^1l}tQ5YLX qo;zRa)}YHx:g!G+4I}E6ۿ 03O\vfy|xp;-%.)<'ؓ>b\(pg>Ht )BN`YtW'Ͼ;K3x텛a3e@Q{fP:p=8=?K??S6&_(#v0'A@'p ? x v?zAa@3؎S?*ژ<<_d#N,|bˑĞ*?ɷ6 `|; Ss)Md)?.0̞aOz zh# :qm0h>0p D_k{QHЯ3L ?wޗŎ.MuV : 䀊D;} egVbIx0;nGo[F As6i"Ian8xy+AtxpdK;$/e[UBy@GG<B4٠y">=cT(f"߶6Iom=V2`n b;.;GGKq@y_1r(NDg`25.^~jm;Znk 02v+l碁q<6孥X 4MU-΢9LHJk~TyӑXO8FhS)À%2ŬH5!(bQ vُw`F v!~pe2>$|UEv;Sl@\͍h\x-R}Gy 6דq& {_J68<.A'Iv^,ՎAֿzω WgZ;ђ/Mo׆9m%K~ R#|vM^la`m?yv K2~~֛`ZV O2sW1M #B\NV$ug4gw&fYd :c+ApAg\ @]O;Kh9v7>qԇo |Q@=_<6Ƶ`"U&#><4Sut%AoW)@!+jp$˴iIGÕsFs2a$R)p' rcN\h"S@1nV<4.KDa{嵎NHME}#zYȠHx-H2qv Z'r#hǍ 8%q( W-H2<6- D5`jrfky  9"WCܞMEL׊Q/P$ްO1x 6Ssg 1a2 7}vd*p C}oz{-Y>ק5iv X]ϽvdWv?hIܽ)[- T,f@`RNB놋d. TG؇<}F]kY %vdwƦ?NDW%sB4'ĐSyc+anZ~I!N.qP &IF8^53 ;\ߙiX]o402$ae2gp=nI%|}R C[18NV :+X_noug`,pWJ3Hh-tk0 q!= |Tw$V ?Aoˍ*>:O>ЂUתBObxϽ ǾVE lY{[eG=OWvLN}ǵ9H㈑e$ĂR6 :UۺVO"tnجEӀoM+/s\~.A<+M[p3'{"W!l)W$c.|.ඓ6YĩS'vqx+n}y78 ŶW=070aG p2ԛ#d+%$(tIvk 8T00yÍHbHCn8M+! U%=a\w! c7@*Uga5 ֜hkY]r4 ,*Π\0#e@k!\`h'߾]eIHm 0aX8JM#[OBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB„k㪶˛KRןqR頣!';bC'_q6K۽=O_ٽw&\av[ă&fb{zOrsn{:q%"'ޟaᏯEm8]6mv7 nGt;fxN^d?m7EXpi8B;=~"WtJò*޲Ɩc7q[PTM|Y=7"7I-E;jt5Fg 1:Ć1waWwHl-my?"$;O1'0%#-%f#cȜE2~هgU? CNUkvzNpyFdU?f]$˝]UonG@D"UɧR}[-~S,0I[.E`??j/6 ~pOn/I]Ovo-ղuƏߟM-푪P#Z ]j% Ê7q̙ߢbDˣ7^uv9%-M,wTr]jxVܻt=S{4zzi`28~&޲o8 (ױXTmD?:_`p4:>5i HCƟ.%^tO n ݔSO)cgiCpI NeƭB>D>28tq3zxO%o "~h3v!A#ȣ_ aΥFhA]6!ȿ'x!gDZq 14kD r&:AvtJNo=o"ۈX}◊xS 5%v_J" QKMWۀR&ߗdⴿwߴ>lrEz-x}SrINy:s* 1ڼe2_t&J)xDCX_QA!:IkZ_d7rM| vD8޵v5h dشm9vK@|=sb@~9{@= NcI-OX1!GxΊxA´4ҟ}yǓܙGZ'^w%'xбî8`HדĊn@; 3H n6_"hާ-C;/rl6SD|/fU8)u{;5G~4uOCJJO&O1~Ŋֹ7f̶ =^:4"׽?h'#'l(C1#|M~O#NFaO(f L w_Q|'|~Ȍތeaw&YD!"' `Yp\.q½ð8Hv*`P _6<1e$sb 8eE.fD(L~v#ô7/ܑ[w'Om}:vN ~sY<1MNcg0@4vbkT0T[%~-ΜI7ÎҴ0so [m,y՜{αf" 848S2{hi `-B2ɯX:rIy0?ؒ=@pdX[ʰZ ѳbxs>m+x/PoռK%)yثWu%WdY.P3KGXt^zj[|iաBoӎ6`Zᄇa5;6j2MT q!b@QyL[:[SHcp,k}z`!<Gާ/, ,s֗?F^ a{=dYFdt%OD:R?3m 㼉|puP jAO# gՀ)Mfڼ~ced@9y̼obeM X_lN i9>gwխh+(^PCϼW0Ҍf4'9~>LEV  QVorp1;ԅpHbo B=\Hj/M;u[L~w /0}% FzfV|}i| #Gƥ_|g쵟[>Glgs?M=bRU~ZZB>f`' 8s UׇvM7ַ\iT)E>+P_tKMBD€-oqyoGJ X^S[ =0:ka4z*66kbEcLPMyK>4uA ^*fHYOON?,wlX,`M [ <.B7 jZIk/yUl-Aybh -QK:#j'Ng(co 4l eq|݆m~-oj?.v!}Wa+S(/S^'3~` XaVQ8UqoOAvÍK {ҿ<_^ |u4_~BhU@珴ñK,B[OOyD };Nm7]"<rM]tadj6Nbe AI?T̂b>D~ VuH'L~]X2K>*q@ve91fN_uh `̗Bze䁣1^w&k07vJ"[ֱ?  eZLX0xL;WV0-#8>eW!n:L'ZfۙcA.E5eykQce-#ZQVA#jYZbƽf4A[ ll!Ԉ'~`{-z8">mvT?y" eqH>}WТ6:et`}c^ Y&꧟Uil;)kh#O9(M`zҤ>H*u3RlUoXW~24!(W֐CihB>E}ԛX{J J4o(aXʚ`C!gH js(-#S&n_[`zGaؙ:Vi >Kq}p BFcrLʨخhl*̟i'g 6dAcI"OUi¥l¯J P4P~kީp<2ԕח|!^᳽;Ѫ{K\l=5ZB gh\Xv[鋘tܭw*<!Yӹa>Onfqҙݧ۰-4{?z{dK~~g̏z6u2wW|V>kiEBB) {YBBV1P >_5$$$ Nnٌ5eCHH Di !1@ rzl Uv&ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBs2#d+HH5tGXg?u7;)X n'y1/X|c N>!Psg敡1.7ո'Y@--bΏ=ru ^>[ֳ޽6蝵yv ]/9ݜ>ϒ7|^ӇYCNedow+=mjSy?6/9c :cQ'y{VsNj2";/m jCxݞzEm%^ 'sߤ2 @fM&3Y#{|=gϟ%o-rާQ}K%#q1]-Ql{'W(g[!XTC~֩&8Ci%ȿjy˷գ1 dP NjO[ۑ[Q HXYӽ35 :=0w-.m6:έC `;.-_^ё#Z @X;'+-&ӇlǹAAg/aB7s];Uw~x%2؉?'`WA= p03VZHMOߤ&=Xsv/uON\+Ơc꿕R.N~mK:{~c8ﱻzDyX@a置 [#Utkėl4ێ-9x19pL31"טxߨ|gWgkQe_ͮqiFbSO] Z '6C::"o>sÿ'v@d}4qg_P0i )%_>4;3AS٧׼, Ba۴m)gLx0G|m&E"e ^ `[*e·2:`}|fH\~w sj>"=7o8 UtIC׸% a&\,1c@(izŞc,1`D0E@i5<2~ }:R~='u}Kzg߀ %X=]ʺ?]k q#"ۘe@=PZ7TGjN%AFmI0qUL/TZN/i|0e8!y Ey՛^b6GFpo.hXv.C9"Q?t(c9t242TMš `Я4Tf2g.(.,ݜ@vFhKQ.VB I-E9N֯;1厦@3'͌c85FKR\I^|(X= B&s-(}'~w'CH=[XLb>@p` 1:?$D;?/t&) Hn^Wbb >ghҢ2 @m"u "oQ_Ӄ?'G8&z4r c  yr$6=(.&~{^&aŜ!N?&p8:qI $ocEUE~_zU;`gzz}\bE[{ @b8@-53?v[(܃ڰ"HVMP"(1|9Kz/AM)NM3*48 WVzN^ &`% @GqD["Ox0(J+ yt|͐%F"w: 6N!tuVi 2ܾ\Ns1'8&ӌߗ Bdmx'w6I* {Mg!C{쪽T[jm3oO?p\گfї)  ;V_;`~($|Qyx0Moh\5`)IIzhaH @hRSiN]AwexʴnB9<Q;%<9d3@E) QebvrvK<m3]<_ˣژ*Ӹ2-o`KeE|1)"7 `MFxhq'Tf ^ p=+<7Lzn4( 8nXeُв'J'{TJfhߍD6Kn/lVzYDK #mKhGT@L9`VH\8翸x- ӺKzkXf->g^蠃uMHAmzfhӯ#j~dU?b"owwNDWazvh=I`ǻ=J?~@pd@+/eϨKw&A;%糖VŞ ljۗӷc̈́bk2o߼p1m> 0֝-'i8\qI{ 9e=b9d|Pi|I,coCxݷ4ތv 0zgn^1pm'3,A{قm`#" tr6t!e;6G8Q[D U @v>0%ISqGxG '6`rdT|˵w1A;ӭ an~!w/,8 t(}`mꕔKŝjRX K+~^Řk/H[wU=T!F)q/U?篯>)Pv,ZfF>'u5?].,3{U%?/(+Y .&]y()dU?P1*Zk?v=i ;/ :ְ;teX N.5 @Gޭ$%K  + \6&xo}=}Δ>cO߽wAU׿N[{ [_|g w:o=~چd{HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH#tƻP$1vl^Ò/p?_?}ڭ>ٯs{r˙{b`}tmewD/;Bl! ̘ͳɓw3nNX w =Lj h} `:%֣>tGrf*t~ϰ9a]=/~쭆fٴ 7 +ciϗL?@yPsgԗ.v1wWPVD{߰ ?h|@5-s?|jzzfw1!Г^Z"}S &w8 [N!>ATͧ?fTӻ_K^XsGZX~3ک3詳9~wKŚ"XŃabB|5[u8!(¯ "54&8,1zh/[ `ޙ?re*9_'9ퟒ8PK0$7c\XlVgso,r\8#ll^yPC6gB ` :ִ _C493=|>I'G!}!NmO,ӿL94QaR D3.\Mys\u " = `Ѣ'u/6QGZLOs7g5&h6ܡ=[[ijd5@d(>@Qü/K?VlhDYo19^o@)#pn񭂉ζ@'K_$-g۬OtE|?tŲkOˁbI #')`3t"FJ}8!tB.ތ=6颤yOf9QKah!(Ox/"sY83ඔ.[÷ ?.87^s#k`U%ܻ4xO `b|s?qلRVȔt?ΆszO%xP)iHұ|( UR=х?'Xm~AG<5á[m% Y!<FN_эI(U51O\z.LxK<'|V3iC>t 8"ؾV? קB5tK%̸@U6Ȥu]2^zK b\@Սͪwv`" >.Y58?$䖜 gֺ3ɹ%A]Cd!I=7 8wžzx"tix d 6EC_N(J!MF Y8j#5W$jw9y[˼zKSx.c6 Sn ZLhH)(X @WSM#+yqILXTeɮ,y[Kԡ鿲 %V!k5 B8~pM,!£$b Ym C~e#fUؖR7~?j] Q%ȶd/lw nb83 ِE14{j$s#Jo c@Mn cT2-A?Jy4* L٘+ dm<a&W d B$=LȪH kB洎rF x.\CX?`۟b}@Omh(ecr[^9 o )&zթѬb4O~яeaIdYg ʟ􏑿g,lM E/IE;}dz2 Y(^Mgq}d|Tzһي^~4;ENH,5KrT;Sn>rqH5LrdpMʈ=r4^Ѫŗ_8*},7qy೔q\LD]~h|/aƺBJ{ LF-&1#M+:<ӂ9U8i/k_;Ciuu!<1rwX?Qu.>cw8ArLo\oxނ<@ \%hF*mx}hgP@ytlgKJ0R< 5@^ nxׂ@ @ @ @ @ @ @ @ H>#d䅪[ dצ]|oJjs@0C5_/'OE]M;y2IJqopT~%d5&~V9:x)O2/i'Kf}fXȶfwuUOIH; m*`iTx0Q.$3UC3mzŷҤ %1OF!w`)l҆"T) `LoTɊ,*S6XrWlR`(C̿֗ j 0+%\ +}hٟҨ)!精1R6FQu׊nIɧN@DM^ AZ!n&K# ֭ؕۿrp ο xupk<_R)jT^&M lL^(eHIuzr#QSD@|{ڠvK8ᓔGzJS,3XomF_GK7L?+_ԗ7WG7"CES@?egPoEISB[~t9"_Fc3tԔ&W9HI[^[aT }^ξBbʧW*jDk-]|*F3g?ho=2Q^aMC(dM Q@JR cP_Bu6T[Lž | _V>s["VwI/"el@E /T׸5 6R' Y)F{ W CG5'  q;PZة Lw`M3qr `˿&s@y-F .I{mrec1Fs9z|ŒAp/h8|Zk}˽L 'ڛPu߈@ˆ}=h]\UoĉǑ:ph 8J"xŧ@,i ɕ뜺-y@Ѡnb/\'s@5_t5w5:r>S/iN.9p-t4wVH|Ze<}q?dA#ÎEp{q8Dۄ:3`J^8^L]@nF>GkO< oؾ>^t*vF&郖\<G#mOCr΍4$#^ ~Q5M h9DRI6 &6tD,਺q[l9:OqE*™& /k*P@7mџ-\U\jr+XqyVX|"!%qMv/`r/`!}#0LSi = V3t#Jϗy WoͿ4ȈSu?{*@pO0XGQ;W`-L_]Ȃ@g6ES@<۠ӵ6h*+i.gSeUm!pҾFb]G18(`71gA?2kiklUd@S콦q._S`";U!`:wS48 @d|VZO lj#8 ܪt֑>IV1.c6A3t6$ Ļhˤ!4M%cƥvyKۛ? efM"ɗ'Jg7n)#m[vz)nc~iMR u_ >xr=BR IόHE(QQh:P/{'\xr󨑞@@I5i@M@9.wI-Y[~ h~7h )`_sid@T4x8cm`um֠/!Eh8mn j4 y+ ڎƷ(T5q6XT#YmUx^;A>.9nǒATKp뒀f$m5 dA8 qH&Wmw(ӧ(9%n^t7pab )aGx.zqT 쁳J\;9b54J-`Gxˁ3}c=ϟ߭G"GOcei?uC Y 8A_&$ا? ShC]u}?>+ajFu0IeЇ~r~j=V_9cD{Q Iз#\ /:N7@aF@Y:1_)wh26/m\ {¿fY6}BDd@nJchj*9>t-f_g[)KEpo `@&{&!Nў~y'L -M-[Xz  <-/>~p {%w 3=xZwS `5dfyKYuzS#7s֑Va<`@9o[2z̙]?-ř$HoG H^}]#!7Z~e^{e*ɺ5 mɜfOPzãU|{&LU)$3W})O>Gd~O*OTG 1hCGxæ2XpiA 6M/>< ԛy@ `9Y9o!@ 8jJ .gUx @ @ @ @ @ @ @ @ k~1%Yw$:m A S<$z>o` <<0VCXGR OU[7cTC0䄧jzW2 2E=71_@Ȋk<|ygC >ER1_JXC#cx:anݺ]D"7u9( ߶G!5#iQt=>: $Cql_6 J[ۨ,VtTs6s % 5_|ܒ뇎j4>)Pc6MPV#K( '[V?4E8>NKO:@lXB4ş# |5ϋMі{yGj0>kO`eJ=TX7dȌAmOnq{< b|iot>+&KjOX ,LU?ߴV%H@+t(D>O|GyFt{U< :_11K'!Y\}wͮ0?'Wj;+q5_}xtE _X>ZkJgƍg͚u,kn r~7{ehHd#4"|l4:A 4'7Tt;|vz<525jdx0O7J/ .gR&?t=FtN@ u:Gj-}Ac9# (*7..qܢrBG)n~ria^t*IF-E;Y~{翇@?|Zv 31!:^sM%QNMM ,ǵf85G@s%nB#f1~]բ.9z r_QМ녪x0 ufy#t1g|֣34 h&>25&ǦƮ0AA4vظ$^dK>~׿h2TzD>==((JO/ek쀏ęWnHRG:(NYL@Ey* V)5ǦuW"6Ag8Eh>>.Fb-NM%G+z qlBLg=iIl y)gO隁nENx6;hЇ1]x?7ghiirn̫P_IMyh5mY:S6f É'o>?,UK5SEzۨmRxk$)BW-vSn0 -Yv)~az@xut&(A7^[y:UhMHx9ya@f,'2q0=6hTTtBWKhY}  dv תy1ğؖ]E 4 RAxˏgEE7q*ɔDs'R# E+RGE 筂&?ܝR~.`[Wv&s@{B>bM֏[x3ZjxyƂpmy<xRD۞<~B n)_=ZGWD1՘򿱾`^KOoOv||<~F:XD<59dhlA(>Z 7 lE fӦ& ?Ȃ̤V-OcDl-m= |3 <;<)q4$v 9ozQ @îg/uK'G=Z[h * L:Fh/6ybIp8"\F%6Y1K9i/pÏ?q |&5J02MB=߬xV_R%E>U4gc''}\|SU~|2Y4IIؼI!g[I x?4! q2i5O5y#~I!O|-W* O`ً }Coj0n+Nޑu`7h7[On?U#Jj.6O/PX68*A4Q!~̒6Q2l*F)#񎎱r-d2]'IxLy\ezrӀhjMQ]+) G>vmJ6FO`# MI]ړ% KfPmJuUQLGwЙ4#YR@1\o҉…l:c'`ɀ׆=r"§/cL)g4WG, @JjaEEgy){%]f{īf$ R@rOgi6r֕[2 l9gᭋp|j YvôXBZulՈ=j9oU M2 Ӆ|0 ̺+Sy0h+fz=Icihf!O7̽*4} Ќ؇s=K-`^W;t8cJ5࿗1>7Hʳw1YIP&? SJJI|m^$w 98D^r8sj~~ 5}l󭤢P5Cmݽ }ʚL@ @ @ @ @ @ @ @ Pjԇcf8cT= |Ǯo5'l>'7jڴ/"M90GD?-𰔿%m4,վ`#I~Ƨ^æF[T)OR΅c9=,sPwE$Ǐ['x~7)ORh>U"5#F5_ 'Ryދw'%=6T$!,JaAMX7Axϣn#N|rz*)}0WR샥@C= Vy<|W8I %:k00@I-ZAh τZ99DҕGB>YG>eQ^"}/ZyڑGTxt 4 0Lg8Klnsٱf|Į#QԜ 8Ѓ҅LLwo^PU'2.97JPq&ƹFBQ ZA"Qc\Ϝht"Դp\1A PY<{|Fg?EڎR?e(R?`EVctV9m_'6T*Ry-:% ;3OI{}҃ "(7 -2#~xmP[q TAgu"O*M5LW@efI>\eOTw %(2fN>>LKL]sc h>jI] ɤbfdca ŸR%<mǺvӟ͸paM|Lop Q C((JsU$S?*4?@?L6 Yup?d6Tpb8ȶ:=>|zdOuB Am}ҝEP$@I<a|_@2?5Il+<>ZTo@=/dlTx^Ag`~yϲZ+?[3k}xRc'o:{'a1 (%?X̪jIP2A 8pE0 Cq-\?NRko>l>Ɍ Ļ p :|o#`)A>Q|ZcAUf'VmرdEܛEc!1ES+ 2ݿd|qqA 0G .e8Nry^|8nT]>95/O)El݁]DM8] J5]f 3KSy#:Ghci`5)MZ%I Cjn<75NJ SPt+g:i=F5η4'Nmi'>U[";f@pڠ82ڪs*p_ y B0+H F-Ե)Kv6p 6:DZ{ݻuܩcK .aej-AHG!Gn;ڟQu>HE}c 2mS,r򑀑 S{``giPnCbZU7zs_=DeGlQYkHO/u Xbg1VuFgkp0xϗs_|7H@eyr*,8Q"ѩ؞6a?#lַ- Tqh_>pCY 16Gm}UMx~;xϿ˱`9R1&c q\d| xM՟GedjnUAI :u40K \_,c\s,@̫WɴZ^A@n c *G. Yy6Hx.  %yn 3}!@ /@ [2V,yy! @ @ @ @ @ @ @ @ @ @ @ @ @ @ k2FEy|j(5AF*;LiXwJͱ"(|jS7v׳2DN^oө>ytfF(6:ԓK-S@ x"kvGxY[ FO.+@_d5 EF{S5S+HLWu֥ɼRv߇qA%pЏƵS| xS^Ĺ٠V{O($9uT7wAkfM 팺8֠($ahgsg D?>jR.WﭔJP%_ lTJǣyFN 8 $ 3ư1?`4;> ߾ >~P$X6 r|I`vMANG RtKzN>NүB/ D!1[1߃/S!A/Bbqœt$ڤ7x( ꐥAlMX>AŒ!t3 B\!WXE οbqۤIPN4WJ`\ ۊNgi!6/,Oy V0D Q ,o*}qԀ{Y(ؕSJH_ahEka6]ϋh]I[ZJ43p7QZ磳?9]wе? J:ʺ?+eЖ5=z{wo]8;PK4k{Q@{AӊIDRAkk`IzMCLYo\ .==;L-OKkʿ:a)^YDlP|;RBǝ=qt3q);b~7Q?J d8Tr-)3PA Iw$/%qC C]iÑr@$ts'aSL 4"f̽! <j? 3:$?q"&nfEvqip#{ |Q(8o#EB|(pdn,brN ]ЎqFS#|=*;9n:r}s0!sٽBk#[ 1e3n\=%@kXM4 c},Wt$$E;ڗp #ONiBМ~0(F/f4JfAggg, eT"> 2 ToJY=Q+7u\O%:JSU62(ma6 أcF-[(c_9ӫ_zj1aVh^mi+0Zj,Pò,Z Y= HG<$s )& ʿ4"\?W.|._b7xQ#H?op|Ƕ%7yq :ZO,cΤ[In2SyV٨K#ʙkCmڅ46qWT]khEܓ}&)(##1?))d {ó=!~c` AJ: >cR] Urݩb=DɧjL "+jPmxt m)H͂Gÿ2Օ f?)f{6 )۴MDfEhM_ @0(7F1hCatTK&,,,E Y2)ʜ*lNVhRDZ i{J-oPP_1E"5Z >P@4czO}"J+{ڌ6=gxI)[Q_.^:5F4זh=m/sGj6/$Zu 5g=\"6晅 mA_i& 3A< xiAn|./X8'F֓@su 6chi,85CēA_]TMDCa<#؛Γ b m/Ns&,ᮗH}h݀NB:<G& wBSH4mp86BLEя4..OIOګӔ f8Kr׶aƈ;1i¼]S n\!…,IM IC\K?Q(W:40Ҁ|wGXg?KWER>ߒ@hӤf&@o6v#ɺ ZvyOhT^ yg Mڀɶ$ntX"m yϦe+~> 5}76)p-NFqh!4.WxF(ՓOxW;|?x*@iWSTzc^'ї;{KNb`":˟Bޕ W:oB?잓Ú߁7?(mxQr9l<kml"zD*DjJ͝)#}}eXO5C{f6z*eXQT|-@:2ZZn.sa0x)3r;/8!IAuD@M#4'5#:UeX>|3cw=ۄ5–SE\|e*2ܰ{v1R0(]'gw9I][)6.[o>OvM@JﱻG[\ @~][H_[#|-9Z/ ~s.9o~]U?}*xgϣ#7eޞV{S\9:/A^8gBPmHQz.yLPOSqoJpouf@g hpTܛߋ50"i5Y۞V#)2Gʔ1r)]{{:XMRSj>6q|N~2D hl?@ڿ'}irw9B9%ș!-ґx!GD>KU#*p1dxTT\>`oO^Xgw?ىumTҘ5@8ul~ZiMqU_5o]?mDíqaU)}Eb7 GSwf(C8E DЇWnaeH2/ /ކ4a'X? b3*vVZ+^{Pڛ0=س؏?2JETp>Nk6֍ a 4~c:%{J5m)`mA5PRVFfb /26x뗊1=Mjg%wYZb8fw "x4G~Uyq9fd~,geSEӎZc4pWe}͎o`bÕ٬J}x}]l;J;h;OKR_Yf2VӌpiJ{uGF0֑zEma/t8I9ۋ>I{Wp鮋:.*}z4 Mrzr2וηح/M}约"ck77Ow1q\fi@y᝼Cs ?+ZA,NR/}yxlpNG'ur^" @ގX#:&9.Ytݹ6 pIZ@Cى Sʞun_ Q[" ?+Y[{_AFs<A6<`/_:E/ tF:bz*Ǖ`/Z>>i2k.-[ѣG%SʞHϼt<[ȑ#Q E`7"+|tU Oq4CcT4hKG~~swdW1yx Ro${ бm-rm[[ q4 vNT]MFߧc0tb$iņ6Yo2Cq_[?V,z ,L:yֶ]Mvwtx(`Kd7 K^)NT _ɔ6[to[nKܔ {PXov0:ryDC>8ohox;:JR[O.ʋU7C^rQ~Of;'dQ=(" *p>k~՛2%ٖXPl`dJu*9ޕ@}!y#XtzB S(@Cf~VN.}Or8((-+Z&<;u$svC;֍"IՉ2z~î;Մ Б*% Pޟ@Y:#83ݯzqc@+4G\DpeHPh2ג4D {%;˒4 嗝i/\ ?({r(t vx%N:I0{Kx[i'cTr:WKJJdfd~@A`3N) *Wm%c qkhea?%5+%5:^hy9Tv;orf(Lj-})Ӣ@$k技jQiuΪՌ>lÙomkhδA/֋a[p՝'`C\:tҝqfZ{R}rE7B+lDU\*lR@:!UmQ@C:mz$/i-?=g]/pty_mXDDgz@Kn{vaB/}U'jm+B_^w}k{$tNX ug9]MS/di)}(gU[lnbV#v_5qLTo7oE6Hp~Jכz HJvN@N'/Dݵ<Bws~rYy ;,k5fNHTsꉭKe]zv ;Pj@ qO:OwVW -^;@ $-g , POgK9@ @ @ @ J#zw #@ /䅪. yKCy|eC!#@ oT=QDy d zT@z~M@3oAcFͪ cOT!j7eݼU}t7Wc諶Oy,UL1fZs2<\(BR'y]Hp{E?}0>?}j3|ct>EtCc=.R[eK˚èN8|?m}Q}Vd~Nex{EnKSKjV=G]O)CP`3[޾qv]D;*]@3b%/57 sxG7B ML3b(1lڶm}=FO/l[*DX:MvG&;94'Ī333-`:/QqKҶ߾-A6-eI F$4MTg1O  ֈ/gVirF4t2H\gpflC9`~~n@Eeĕ CX^ʗij ,F Sg?ޯsr[q>iL |jͺ _Z_9Xn ?w 'QT{+8À#-rO}:4_s(3~u3:m9I<>=N4mƥgGl$5DL@BIZ.5D  ˸ tЋ 9I_[-`+#+|v>*Aϕ (~cNgRc1:7X.r}h [m\DenJ~m"((?MRqoneVPTk(J}=羪6-Kj& C{t`<;ٸ xXRUf6 t[jNiAګ`gJ=*=Ln2 MhGsá+9qO,.RZ8[qylK ?nJR^DXA88= MS`S(Ȱ 8CиT#` gx$QyzYޱs3'= 2"gʆ@s2ġ~H^[(FR_`|3?z09Ȅ鏾DcǹY**@%,=G_dyU¥/=ҿy#7nBG|P#eјz=@@|Q{}a w^}ttu"6Lȡ\1U8@֙kD#r'Ie@ia[b|+j!OukN9XAz 8T }n grFx 8ndmq?XZdӫX"nVϲyqQ'f2 %T@;d(pۍoHJ{yZ! u;ԏ%\ x~6'PW}r{.9WI]]œY;Fmp<ڛʃޢ@3t\8(4>?/f BNn n'Lpԫ#0 %ϢD嚽<3WB8: y Hsʪ[% H_Z] OaJ?Q/$OeHVha]x%Ƀq .*_QBB!{9jNj|~x+>͊G2<}K87` e2\禃&3q1{(<-7ޠY+>ms!LG;rv$~~n i=:24fOu\n_x+xr_oW\AxY3:.z ӻVd7t??7h# 'v 1NbIKtFKrW)G^Ľ%`~\ҟ`I\g`tX ii+tlɚ>+<$ ZIBRƭYDx>|x,Xth -CdjpzXgx\O7'+`mMDa] @ 3N Je֞]ysqmuUu @P+CS"Ч}QH5Osf n&kp5*`(\OVֻ}x*!58i?clPlOl>?9~g-xeԯbmAFj { ϩE>4ҝݛK 3mkYס u@i)&yd3ݨt>?H tBJY ^N2Bǧ~eqgqIOouiHuqQF[KMۢkb$_Pl,c@XC@'G.l/%`vvBZ>ҥ-$dW0z4ǿkJFٙyR>i6\/Ӽ7}\(8 ,-T栾^5i/t;<POQ-e qZw|׆JC3bk0jׁD9@4=Ihϥ:f wx|$O q& :A<͊t>e٠ {~zױ%b 4Ku?M Rc&T@`zbMm>.n`/o|Yo _.Еwau(™ǻ*™pj|5F7zNő Z.T .W`pcmbm9tj1;5|j;)B`:FX@ ZxSacU tmt *ĻTmu9#RdwoSpG`򁡼C| `~ /m>zȦY ;@ @ @ @ @ @ @ @ @ D5I"Ҿn% j*#/$>\&~/r1_}z(O 6H_lH'`Q2y߄G2[J ̤g5!GmH_&̐ҸO6OA1 +Ӥk6:͗`ViRL(2Ha$Ii|V~pVtVA[1l4ceS3 zq:nĤ f.O t`/gVJCZR4na+~ pSY엑1Чt0,jPDO^ R ru~/GgYKGv?\? ) Yz~<շGb hϒ5z;VnA^!SnQ: byt\3!X ; (`d|aߒm'OVM+Yo;|rHLÆwp7'.tou 'vjVEA'"ϝEo#"sZ;:)`uzyqAZuP{bg#}n #$pyPT_ vg1 η0a@o{.+d 0DvXf]tx+dy?MjC_ ^W CO:濤jؔ}^E5]+{</qXUCw;hlU<hݓCK}rzk /C>F9ipxTUa  p 4)뼞D\P.5)'i pX c3 `7q8.@MaHWhm3ٖ=8&I{) iN*?&+ȘD>*Sfe\pcܾ S@2/6֗]CL}[U"Aw&jn"U'Uehms;3T=pXN:2u@_r#FSfS/>hi=OV[n9%*&ڍ LN'H_X $'=l#71Hp&".=dqCu+_?:Ѽ:X&p?dEJ `P2y ssFA-iMB!)+=-l:4$=7v-RCtt@rT,ծɨ@ t_r'V_GF%`r9}cr`ylؓ*$աMT[Zz[s'X~H Zٮ2hkTrA8P̡`ko$gsF5I*hH̚r9w{>NZ'g<!iNffʅ,*tNW&kU~)ya6W@5MɟB %%}tz]p!?9M֦ =cmMZ4 [@:S_v؉緌cs'09 -dWS~ k QdM,&xr)V伟PN.Jp8 ^x80? ҫ%%2e[ kTqVxTO@Gkd.IO{pbw(0&;fP>\ M׵,p:C^yJ. lGmkkG`W>@q]֭$<!'!MI(!={.r6{RG{!2mLkǷΠٴZHٰc\p->t~h!GSO@~Wi턒AU*'kgc_{2Jc%wNKW% lο[G/"v<+WIa~IˁvOftoy)*wКdHezoCyCFLfTb8OެK}1dD;({&[[*A6 ŪԾrO+vE+D "P`Sn N 6s1 +)6#0Xf1◕ ̗ttju{'E{6’3(QV$Y AIEXP@DT@A8r$AAqdtt^0L^W߮>͊Peʐ}.Z,[D@@ @  D@ :Ha@ @ @ @ @ ,RXalDhK "5#3:IB$8ߤ!Q4s:ʝ!a46@H|ni%&@"bpl猯NxgQN 0S=Ax psF ;S[lvA5> L`Hk0Cs;+='_SƠ3bì M`??w ow#-{VʓqR|s׬ XE(_?P"Իy>ҵkX'QIc40IԷ%:VD* _ Wj +xczwB|_Oqy jj\P'C{?M鶰jCY{#x%n=ψ"v/$b^=|sA_VDU#٣L#+ò 4ߍf=ߕrgBp?4эƐx֓(IMa&:6'։>ÎB,UJ86Kcw=kZdb{w 9%9[pKp{?yc fdB@l*Sߘpl/U16qBnOH9`<~wdߐ@ʍ#?Wr|x>zCMM3խiIOcxe=MjIޙya&0"·+JKqְ̋$ =v)ۚ ܎_과/nI &sZDua&@E W3RO^N/4D]H$,1~Hf™Ow)=wx~ꝱl/,t>dzbroH YۙTx\79&> 'W`y?zkK& z05GW& sГ~Zʫ[3;.OJa3L(0SOE㆚o!2Ye*O>L z- ZqB ;K|eRp9 -~h{#2};Ȧ8F,w d:MqZb쥬 P'-1V+vcEzY4!&N@C y0%zt:cG)>߹R H?Lnc~S1A zc~(CH6]aҬ$q6 }xaH'(dθah`9* v:sBz0v?>?D|4#BOqsY]ܢ4ϲ ` WOʩHw,CH\J R4$`Io 0/L.AڮZ/d7W28?cP(u]ᆀykH1I`s5Ukm op\E׸I])hq3E&9Kk,TޓIٺ<4΂h-B[i O+ 49G`p+>EYU 6&l ¿؅:""*~c>D.g aMj?7^c-z|ZzҺAyQEF%J'w`ĽUo3BP]@ &WeثK/C?anYsH `i:eع<;%ǀSl,ۯVZʰ:ޏ5g*#,Yߕf@C}ѥ0J*K  ƏX :?8LVqc(>F`D (2TawRBtz)+_tLjJu ^)Xd <aV'6H?-Ibkv jQl rQqY[)tPS@ah|+u@p?p}p2 ;g_wZ= 7`ݾrK.h"3 |pw mNr|} I0RV}9yp p= Gh\? p,>o;|R۠7 Dq~7'2dh>T;}FBۦ0Ȗ0#~7~Wו!ϝG~ 3}pB\"ra=3>:Ox{lUo4gߛEƚ J>߫yd݆ן;O2ZIuЇD+6m{w#=FY/}|/b g/rf(%嬎!-}\%#/x~]f@Tǟ?/L(zG|$ăB*zF0{@p|g߉i!=3p|Y>J*& x?HoQ~ &z#~GmHkvž{ @j#`?)2[Бm B 3dL^GV@xD_z?9{G-/Â6Ts9[ʧA+jeNzO/6 @t$}[I-xD#=DZ<̟H ixnܫ꼓O_._v,AG54YЪ,eݰk$&aީ7= km|-F >4hGB\U(5`Uqc~`۫Mi+JҤX&Ԋld\C^DL}GtSD.w}FiV Ѽ0o'F$x]5Z>̀IrqZ)CFm[&ba Gn eI F7:!xq-rWC<: ջmuWo;$ D"A< eg{IUדkϿsJ}SX|)bY!"0cym<8ϿrT)$gT,@4$~|z ةO0"y(1CWNW>E[.!2Eeq)֝sDpZIDD@5ՏDZJ.R"D~nk([NQOdϿʔɓ'O tNu ~?p#SWv{0!\|E~n˙ZSNسߎ{izX@U"5Y kAvQbQ1xZ_v ?QA x/9 ru2&W?g@p,9w;FeZ~ò `? ;&_U!@~&\׎"9= B*{3bVKvN#Ue+5ീ7BÀ OIIIlߗ0%g'%vķҎ-0Njx rMcD 0Pex,(tf $:F.]ˆ%BOCYrpF3$|RD'OgtS~q'nKUU((s,wHw s=R%'}ٯbG<\~s9#_Y'ALUQ4zwX{D?c.Y,y{Z[`toq7.EӳݖG/uX3f}+08kHkCc?zg+/ehdrCu!O3DCmK LCxs u/5[pkcBk?3@T{LYew{ ZT|SQ,vasxv{74d()vBu+"Tb- #8\~;"_]A'gpB,r wTYӾ+3]xv6YƟ#7orAsژAl'ڏ :$^Ul1w*6%}WaA+c7o)c4;`7sRL٭8\~2NW_ BΨ10 [,+eܾvq25/aGs:'OtC*7  bo P^` 6o?;3iX3t@}(8dP5 U(P]I0$\y‚ ࢵ?EMs'eQP!: P%f`|qQZP+N6 6@/$44,ּZY<_~/T@vXtdaRs|EG|; !2(}Dy3FC؅>o7r 6;H4g^FbKWGMB.+Hp\a5~"Sq;9 3.[\W,f'41LF!j ~0KKT @Q&_]!Cc ] 4:],{w|^O9,Y>z|t( >v9 YXAlCn-L}?Cw2˹jPտW_@vWE%Ev^y}1|`!8{ Ug6$xl&}pXhn3ps߮؞rD` N(Nd{rPԿW_~wY]{kX?}ا80_^yC$cG'ţ9JP-ٿWJrQ+[/ڕ!y|]: /콘Wa]i @U&_YCGHGk8 #J "9>^DQڙ#ڕF֟ݥo`[׎O-2kUQ%) \c˜1/Oh ۜm%_2D9Y!K ] eڏ|U ho[Lt\dvD?wGO VAEy%k 1 mO}9H.R'eڏ|E 곜i:bȹZzDF- JP`?n':>nV3UQ!ػ,m2 txc, ]GrKѮH,>@j_Cy^Pg_H£~um~"pڋYݍYEG韊JUQ M}R( XgӨӠQV`1ѥfݟyP+Fk\׎:=>,ui%cc RFj^0j?JC@7n"rg׾gGHTpR>!q *^$ Ɵ|' )4h$6̊D$<#^Fpʉ2Dz Nݬ_D"B8@Ic5%-@ DzwDp$#ft'jU? S!V6.  8w9nŷ{,Tքaڶ;]Z @ @ @A?êQaupIENDB`oolite-1.82/tools/oxp-templates/encodings/Read Me.txt000066400000000000000000000013421256642440500226200ustar00rootroot00000000000000These OXPs change the font and text encoding of Oolite to support additional languages. They are not intended to be used as-is, but rather to be used as the basis for localizations (translations) of Oolite. Modifications to the substitutions dictionary in oolite-font.plist may be needed. All five font textures (these, and the one in Oolite) are based on Apple's version of Linotype Helvetica Bold, 50 pt. The tool used to generate the images and can be found in the Oolite source repository under tools/fonttexgen. This is a hacked-together tool for Mac OS X 10.5 and later. It could be used to generate textures based on other fonts, but would require modification. These OXPs are hereby placed in the public domain. -- Jens Ayton oolite-1.82/tools/oxp-templates/encodings/Turkish.oxp/000077500000000000000000000000001256642440500231205ustar00rootroot00000000000000oolite-1.82/tools/oxp-templates/encodings/Turkish.oxp/Config/000077500000000000000000000000001256642440500243255ustar00rootroot00000000000000oolite-1.82/tools/oxp-templates/encodings/Turkish.oxp/Config/oolite-font.plist000066400000000000000000000176011256642440500276460ustar00rootroot00000000000000 encoding windows-turkish substitutions (Control) (Command) (Option)   b = #   texture oolite-font-tu.png widths 8 7 8 8 7 6 7 6 6.25 1.7364501953125 6 6 6 6 6 6 5 6 6 6 6 6 7.5 8 6.25 6 6 6 6 6 6 0.1 1.7364501953125 2.081298828125 2.9632568359375 3.4759521484375 3.4759521484375 5.5572509765625 4.5135498046875 1.4862060546875 2.081298828125 2.081298828125 2.4322509765625 3.64990234375 1.7364501953125 2.081298828125 1.7364501953125 1.7364501953125 3.4759521484375 3.4759521484375 3.4759521484375 3.4759521484375 3.4759521484375 3.4759521484375 3.4759521484375 3.4759521484375 3.4759521484375 3.4759521484375 2.081298828125 2.081298828125 3.64990234375 3.64990234375 3.64990234375 3.8177490234375 6.0943603515625 4.5135498046875 4.5135498046875 4.5135498046875 4.5135498046875 4.168701171875 3.8177490234375 4.8614501953125 4.5135498046875 1.7364501953125 3.4759521484375 4.5135498046875 3.8177490234375 5.206298828125 4.5135498046875 4.8614501953125 4.168701171875 4.8614501953125 4.5135498046875 4.168701171875 3.8177490234375 4.5135498046875 4.168701171875 5.8990478515625 4.168701171875 4.168701171875 3.8177490234375 2.081298828125 1.7364501953125 2.081298828125 3.64990234375 3.4759521484375 2.081298828125 3.4759521484375 3.8177490234375 3.4759521484375 3.8177490234375 3.4759521484375 2.081298828125 3.8177490234375 3.8177490234375 1.7364501953125 1.7364501953125 3.4759521484375 1.7364501953125 5.5572509765625 3.8177490234375 3.8177490234375 3.8177490234375 3.8177490234375 2.4322509765625 3.4759521484375 2.081298828125 3.8177490234375 3.4759521484375 4.8614501953125 3.4759521484375 3.4759521484375 3.125 2.4322509765625 1.7486572265625 2.4322509765625 3.64990234375 0.0 4.5135498046875 0.0 1.7364501953125 3.4759521484375 3.125 6.25 3.4759521484375 3.4759521484375 2.081298828125 6.25 4.168701171875 2.081298828125 6.25 0.0 0.0 0.0 0.0 1.7364501953125 1.7364501953125 3.125 3.125 2.1881103515625 3.4759521484375 6.25 2.081298828125 6.25 3.4759521484375 2.081298828125 5.8990478515625 0.0 0.0 4.168701171875 1.7364501953125 2.081298828125 3.4759521484375 3.4759521484375 3.4759521484375 3.4759521484375 1.7486572265625 3.4759521484375 2.081298828125 4.6051025390625 2.313232421875 3.4759521484375 3.64990234375 2.081298828125 4.6051025390625 2.081298828125 2.4993896484375 3.43017578125 2.081298828125 2.081298828125 2.081298828125 3.60107421875 3.4759521484375 1.7364501953125 2.081298828125 2.081298828125 2.28271484375 3.4759521484375 5.21240234375 5.21240234375 5.21240234375 3.8177490234375 4.5135498046875 4.5135498046875 4.5135498046875 4.5135498046875 4.5135498046875 4.5135498046875 6.25 4.5135498046875 4.168701171875 4.168701171875 4.168701171875 4.168701171875 1.7364501953125 1.7364501953125 1.7364501953125 1.7364501953125 4.8614501953125 4.5135498046875 4.8614501953125 4.8614501953125 4.8614501953125 4.8614501953125 4.8614501953125 3.64990234375 4.8614501953125 4.5135498046875 4.5135498046875 4.5135498046875 4.5135498046875 1.7364501953125 4.168701171875 3.8177490234375 3.4759521484375 3.4759521484375 3.4759521484375 3.4759521484375 3.4759521484375 3.4759521484375 5.5572509765625 3.4759521484375 3.4759521484375 3.4759521484375 3.4759521484375 3.4759521484375 1.7364501953125 1.7364501953125 1.7364501953125 1.7364501953125 3.8177490234375 3.8177490234375 3.8177490234375 3.8177490234375 3.8177490234375 3.8177490234375 3.8177490234375 3.43017578125 3.8177490234375 3.8177490234375 3.8177490234375 3.8177490234375 3.8177490234375 1.7364501953125 3.4759521484375 3.4759521484375 oolite-1.82/tools/oxp-templates/encodings/Turkish.oxp/Textures/000077500000000000000000000000001256642440500247435ustar00rootroot00000000000000oolite-1.82/tools/oxp-templates/encodings/Turkish.oxp/Textures/oolite-font-tu.png000066400000000000000000001720101256642440500303370ustar00rootroot00000000000000PNG  IHDRZvt_IDATx]"EMlfWe 'f10c0cψa3fL穘1c,f V˺no*v"UuL~nnW^)Dܲ $$,QŒl ! xdHVFwwor#pr|1אӏ}D:ߎ8fVP>|M/;.5 T4"$@CsS +J% )A Ѥq0L._$bA{rҸ$NX~V GP""}}l`!).y/H << ,tC 3|(Z`Ě"K#hvW# uFP1SUF ت!cZϼci{ ǡu_Oa%qƭ,z7O Ǥn 6FbmAA:fAqeY [!تo.j|5nt)QL'!Fʗhhn~<)b<:;R*@уrB#h/0ex ڳ[fywhScCg  .A' C?v 4j^B F({岵=:ԫ&YP}rEWbd. OA\Hpi\3Cs *'X'գ#ayTDMxy*kM5K\0o,ϡ hB !_KZm굟>]E؄M=5ŒaR?;HV,=NJi<AgX=:Y@$RGT>4ʞ5AJ]$',%{1rx,d g%o竊:>>cԘ~A=2{]E6,5F*A<[~US(SdB$cvSNj*PJit3]U,BzJK8!pR_"^fCLn7 W{tyY>Ȫ s$jWq:[4 -F(% `PAz%Ь1e3l-.JLTN X̚$˖ژ!ɜ-f T", ]I0Oyކ!:#Y:bU3 (j?ڨ|n?- u]"`šbZP5:%kp+[t79>'.t7YeC HC(DM9@i+,8vN}}ԕ~>X1jijhA{ju6AST@=g˂=o\wӀ:ՈUriMQz_/֕?aX,ZYӴ rfk5ho])\E@y:Os`R-#]$P{j˛k s.CšzShRS/DVKY}?ɆyW gP<'P$"i|B1X0.eYy]d+o `cjsz2\0#rТdZT$wG6n6+(n.1'x4-(ÊY8U[>|4 'k@ -_.t.t1PRMuL@むr ` kլy4\:S&y<_ j W 4Pqk$y @HvRoFI6FQ T ?fLfS{Fz20n|,-"A YvՋ@ IQPVJTo 2?SWʸ9@nWn5@CX~A`@ k8RK79J?}gw^mtCZ?n˹]t1UoYc bzxJWp -"{qY1 =LIEk p-  ۓyrkRH3wC!LRL `h^i#xݬv: Pݪ9߇jJn jsaE7*&U =ySz ǡ`D$W6~ V%m R?lepV,8'Z#mP$;mPސ D]__=_b bL%xxwq5P8 ]~_J#p]sǛޱX J@ Sް-Զ16* u54 ;{:(V z(/"#hSZjݢ"EQz4cYjҥ5yc] 1PS>n)G HJ[_JE<#hTEPWh q *?H\& W S0(bj`96LD$$te#p:l=dFJT u);W0DzR8-U>WWƕ@d_Q)Wg3$ԍmaO]F|<ؠ8v% ^F.p\:.?F.h1h/ySh]Qþi aC 6+No3v4NXr A7WW}8GHg.ֿKC}>b|.lû}H >|A&"f@ IYZwER"BIԞRX.O;~872T>vpԍm, p!92e$:E5<%ݠ+\xwwuyO:!FQ@_&# ס"A3\_'wji0Ly+IԒON-UnRVPR! 8U]:Q@ם.Lpb2j 0nOnFx%3>W2́I5qŠSqc>{ޫg4\Wt(SQ:1|, s@T-.>@`@vpZ,q2쌹R#G"K*z`/S( okTeSp9/{dd/_V!pZʌ` jc8@ ,d ȡh^wGT[7nIpx.B]r[lw eBJA;^D d҅Jp03\N DK_ÁltzFjHWW! <6C g j= NTFw킩xPsf,URr{Ą,SPC ]FSv =gAIQɐHofZz,A24I h 0L`{ <UffLb<ܵ?DZdž/Nn".AԤVKPIn1՘].h'h$B(MƗJ]9WWrj ݕ| Q)2ٽ)#T^<1p |`҅L"_vФR2W/==4﯀nA p[>P>Yw8 3[@X_FbcJs1>U9Vx0(ŵ Z*O97^" oyqgFU6$Fh W^@>̦;7Ӣ`9jbHX8|f.uNwHtS*LSHT݃Hryn*Aap%*!cWZU-XgA7fSn5NLoJJQe0IyC ]tP*t*4ԑ~.3E'~)їpYDE wM BNp M\q8Ö'Z20!7=6HBf@`y2Wx!f $(sCw.#A*E[Qrt%=c $-%gDIsEQ{UPW2F]fJMV OA@ NJ3pi +p yFp-w+ ,c2]ybчJE(z"^-cJ]X@Mԉ-ݕgW~B*IFlE eO}W&IY  MKzM8dתUPQI#45UhlXāW+k{Z1ZS WFYa0{Ԓ~*6ڕ)xAJ >B(EL Hg]IEA1^-}_8E E"e`T)VqV)F*q: Dk7վ.ɍ5ަԼHF?_řJ1@ªe QH ܹ<jQ jGe%\Lŭ0 Ս+Vaꑋ΍u/:w8vTty W!U"$otS\X .$n.]C!7`̂ [`KC̀F1Ljv"~X #<#uhA QxF 'ZӕګdK>JpCcE9rýew2%M #z}`)Z/MTC@RdNs.h43r]h z;9!e] p5Lp"Kc@&"TNo q,$Ly"lgHd4%+jIp9p5V,b ^*#_0 LRKat6kU}ݨY yB(g!c ۗȓB:[ԈW!U %$zsEi<kSK$HH8O-Ҏ-5/_DG$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$36V$(M@ V+OA\;͗` :o'CQ 0UV!'Foc7K\unQG=%:>~#& rp#!s[oM*+ٖ{_Hٯ(.7\FOM@cE`! oXad U ̧Qy]yܝnpIŪܭ5X G~_Q9N A}=˶ ! Y yY6m٦x`wk|;ԎWJh/.a>G`ov/A.H!{+$ pI0y.}vO{Nڽ7n;mnxS-t>G.m"[ca/|4gۖ=C?Y[&*B *9WRG^1l}tQ5YLX qo;zRa)}YHx:g!G+4I}E6ۿ 03O\vfy|xp;-%.)<'ؓ>b\(pg>Ht )BN`YtW'Ͼ;K3x텛a3e@Q{fP:p=8=?K??S6&_(#v0'A@'p ? x v?zAa@3؎S?*ژ<<_d#N,|bˑĞ*?ɷ6 `|; Ss)Md)?.0̞aOz zh# :qm0h>0p D_k{QHЯ3L ?wޗŎ.MuV : 䀊D;} egVbIx0;nGo[F As6i"Ian8xy+AtxpdK;$/e[UBy@GG<B4٠y">=cT(f"߶6Iom=V2`n b;.;GGKq@y_1r(NDg`25.^~jm;Znk 02v+l碁q<6孥X 4MU-΢9LHJk~TyӑXO8FhS)À%2ŬH5!(bQ vُw`F v!~pe2>$|UEv;Sl@\͍h\x-R}Gy 6דq& {_J68<.A'Iv^,ՎAֿzω WgZ;ђ/Mo׆9m%K~ R#|vM^la`m?yv K2~~֛`ZV O2sW1M #B\NV$ug4gw&fYd :c+ApAg\ @]O;Kh9v7>qԇo |Q@=_<6Ƶ`"U&#><4Sut%AoW)@!+jp$˴iIGÕsFs2a$R)p' rcN\h"S@1nV<4.KDa{嵎NHME}#zYȠHx-H2qv Z'r#hǍ 8%q( W-H2<6- D5`jrfky  9"WCܞMEL׊Q/P$ްO1x 6Ssg 1a2 7}vd*p C}oz{-Y>ק5iv X]ϽvdWv?hIܽ)[- T,f@`RNB놋d. TG؇<}F]kY %vdwƦ?NDW%sB4'ĐSyc+anZ~I!N.qP &IF8^53 ;\ߙiX]o402$ae2gp=nI%|}R C[18NV :+X_noug`,pWJ3Hh-tk0 q!= |Tw$V ?Aoˍ*>:O>ЂUתBObxϽ ǾVE lY{[eG=OWvLN}ǵ9H㈑e$ĂR6 :UۺVO"tnجEӀoM+/s\~.A<+M[p3'{"W!l)W$c.|.ඓ6YĩS'vqx+n}y78 ŶW=070aG p2ԛ#d+%$(tIvk 8T00yÍHbHCn8M+! U%=a\w! c7@*Uga5 ֜hkY]r4 ,*Π\0#e@k!\`h'߾]eIHm 0aX8JM#[OBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB„k㪶˛KRןqR頣!';bC'_q6K۽=O_ٽw&\av[ă&fb{zOrsn{:q%"'ޟaᏯEm8]6mv7 nGt;fxN^d?m7EXpi8B;=~"WtJò*޲Ɩc7q[PTM|Y=7"7I-E;jt5Fg 1:Ć1waWwHl-my?"$;O1'0%#-%f#cȜE2~هgU? CNUkvzNpyFdU?f]$˝]UonG@D"UɧR}[-~S,0I[.E`??j/6 ~pOn/I]Ovo-ղuƏߟM-푪P#Z ]j% Ê7q̙ߢbDˣ7^uv9%-M,wTr]jxVܻt=S{4zzi`28~&޲o8 (ױXTmD?:_`p4:>5i HCƟ.%^tO n ݔSO)cgiCpI NeƭB>D>28tq3zxO%o "~h3v!A#ȣ_ aΥFhA]6!ȿ'x!gDZq 14kD r&:AvtJNo=o"ۈX}◊xS 5%v_J" QKMWۀR&ߗdⴿwߴ>lrEz-x}SrINy:s* 1ڼe2_t&J)xDCX_QA!:IkZ_d7rM| vD8޵v5h dشm9vK@|=sb@~9{@= NcI-OX1!GxΊxA´4ҟ}yǓܙGZ'^w%'xбî8`HדĊn@; 3H n6_"hާ-C;/rl6SD|/fU8)u{;5G~4uOCJJO&O1~Ŋֹ7f̶ =^:4"׽?h'#'l(C1#|M~O#NFaO(f L w_Q|'|~Ȍތeaw&YD!"' `Yp\.q½ð8Hv*`P _6<1e$sb 8eE.fD(L~v#ô7/ܑ[w'Om}:vN ~sY<1MNcg0@4vbkT0T[%~-ΜI7ÎҴ0so [m,y՜{αf" 848S2{hi `-B2ɯX:rIy0?ؒ=@pdX[ʰZ ѳbxs>m+x/PoռK%)yثWu%WdY.P3KGXt^zj[|iաBoӎ6`Zᄇa5;6j2MT q!b@QyL[:[SHcp,k}z`!<Gާ/, ,s֗?F^ a{=dYFdt%OD:R?3m 㼉|puP jAO# gՀ)Mfڼ~ced@9y̼obeM X_lN i9>gwխh+(^PCϼW0Ҍf4'9~>LEV  QVorp1;ԅpHbo B=\Hj/M;u[L~w /0}% FzfV|}i| #Gƥ_|g쵟[>Glgs?M=bRU~ZZB>f`' 8s UׇvM7ַ\iT)E>+P_tKMBD€-oqyoGJ X^S[ =0:ka4z*66kbEcLPMyK>4uA ^*fHYOON?,wlX,`M [ <.B7 jZIk/yUl-Aybh -QK:#j'Ng(co 4l eq|݆m~-oj?.v!}Wa+S(/S^'3~` XaVQ8UqoOAvÍK {ҿ<_^ |u4_~BhU@珴ñK,B[OOyD };Nm7]"<rM]tadj6Nbe AI?T̂b>D~ VuH'L~]X2K>*q@ve91fN_uh `̗Bze䁣1^w&k07vJ"[ֱ?  eZLX0xL;WV0-#8>eW!n:L'ZfۙcA.E5eykQce-#ZQVA#jYZbƽf4A[ ll!Ԉ'~`{-z8">mvT?y" eqH>}WТ6:et`}c^ Y&꧟Uil;)kh#O9(M`zҤ>H*u3RlUoXW~24!(W֐CihB>E}ԛX{J J4o(aXʚ`C!gH js(-#S&n_[`zGaؙ:Vi >Kq}p BFcrLʨخhl*̟i'g 6dAcI"OUi¥l¯J P4P~kީp<2ԕח|!^᳽;Ѫ{K\l=5ZB gh\Xv[鋘tܭw*<!Yӹa>Onfqҙݧ۰-4{?z{dK~~g̏z6u2wW|V>kiEBB) {YBBV1P >_5$$$ Nnٌ5eCHH Di !1@ rzl Uv&ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBs2#d+HH5tGXg?u7;)X n'y1/X|c N>!Psg敡1.7ո'Y@--bΏ=ru ^>[ֳ޽6蝵yv ]/9ݜ>ϒ7|^ӇYCNedow+=mjSy?6/9c :cQ'y{VsNj2";/m jCxݞzEm%^ 'sߤ2 @fM&3Y#{|=gϟ%o-rާQ}K%#q1]-Ql{'W(g[!XTC~֩&8Ci%ȿjy˷գ1 dP NjO[ۑ[Q HXYӽ35 :=0w-.m6:έC `;.-_^ё#Z @X;'+-&ӇlǹAAg/aB7s];Uw~x%2؉?'`WA= p03VZHMOߤ&=Xsv/uON\+Ơc꿕R.N~mK:{~c8ﱻzDyX@a置 [#Utkėl4ێ-9x19pL31"טxߨ|gWgkQe_ͮqiFbSO] Z '6C::"o>sÿ'v@d}4qg_P0i )%_>4;3AS٧׼, Ba۴m)gLx0G|m&E"e ^ `[*e·2:`}|fH\~w sj>"=7o8 UtIC׸% a&\,1c@(izŞc,1`D0E@i5<2~ }:R~='u}Kzg߀ %X=]ʺ?]k q#"ۘe@=PZ7TGjN%AFmI0qUL/TZN/i|0e8!y Ey՛^b6GFpo.hXv.C9"Q?t(c9t242TMš `Я4Tf2g.(.,ݜ@vFhKQ.VB I-E9N֯;1厦@3'͌c85FKR\I^|(X= B&s-(}'~w'CH=[XLb>@p` 1:?$D;?/t&) Hn^Wbb >ghҢ2 @m"u "oQ_Ӄ?'G8&z4r c  yr$6=(.&~{^&aŜ!N?&p8:qI $ocEUE~_zU;`gzz}\bE[{ @b8@-53?v[(܃ڰ"HVMP"(1|9Kz/AM)NM3*48 WVzN^ &`% @GqD["Ox0(J+ yt|͐%F"w: 6N!tuVi 2ܾ\Ns1'8&ӌߗ Bdmx'w6I* {Mg!C{쪽T[jm3oO?p\گfї)  ;V_;`~($|Qyx0Moh\5`)IIzhaH @hRSiN]AwexʴnB9<Q;%<9d3@E) QebvrvK<m3]<_ˣژ*Ӹ2-o`KeE|1)"7 `MFxhq'Tf ^ p=+<7Lzn4( 8nXeُв'J'{TJfhߍD6Kn/lVzYDK #mKhGT@L9`VH\8翸x- ӺKzkXf->g^蠃uMHAmzfhӯ#j~dU?b"owwNDWazvh=I`ǻ=J?~@pd@+/eϨKw&A;%糖VŞ ljۗӷc̈́bk2o߼p1m> 0֝-'i8\qI{ 9e=b9d|Pi|I,coCxݷ4ތv 0zgn^1pm'3,A{قm`#" tr6t!e;6G8Q[D U @v>0%ISqGxG '6`rdT|˵w1A;ӭ an~!w/,8 t(}`mꕔKŝjRX K+~^Řk/H[wU=T!F)q/U?篯>)Pv,ZfF>'u5?].,3{U%?/(+Y .&]y()dU?P1*Zk?v=i ;/ :ְ;teX N.5 @Gޭ$%K  + \6&xo}=}Δ>cO߽wAU׿N[{ [_|g w:o=~چd{HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH#tƻP$1vl^Ò/p?_?}ڭ>ٯs{r˙{b`}tmewD/;Bl! ̘ͳɓw3nNX w =Lj h} `:%֣>tGrf*t~ϰ9a]=/~쭆fٴ 7 +ciϗL?@yPsgԗ.v1wWPVD{߰ ?h|@5-s?|jzzfw1!Г^Z"}S &w8 [N!>ATͧ?fTӻ_K^XsGZX~3ک3詳9~wKŚ"XŃabB|5[u8!(¯ "54&8,1zh/[ `ޙ?re*9_'9ퟒ8PK0$7c\XlVgso,r\8#ll^yPC6gB ` :ִ _C493=|>I'G!}!NmO,ӿL94QaR D3.\Mys\u " = `Ѣ'u/6QGZLOs7g5&h6ܡ=[[ijd5@d(>@Qü/K?VlhDYo19^o@)#pn񭂉ζ@'K_$-g۬OtE|?tŲkOˁbI #')`3t"FJ}8!tB.ތ=6颤yOf9QKah!(Ox/"sY83ඔ.[÷ ?.87^s#k`evrٙgΜ9 rh4MA\T'K6f qdҺ޵bD UGv!q3ݨ~H'q#9 '{~^Z{/s%NNtQL9+?wL;,dPlֻ" -dGd8 B%i&@J1 :8X1 2B_{xK݉84D[#*?η('os g/ў1(Ljc঺ZC <rd]E'B S F@Gm';@#/Ӽy5ʪeXh>i #MKE4|!) {"2p4g.J."F^%3  ~a7S*.@VCN䗋Tl$3P]<8ϕ*t— f LXz>3iP=05~$G Q (>H(m'j&:-^C7KCᥟsiF@0M-{pMHK<J QKت Ɏ D_5@(y9]yokq,x]  \edp9y4*(qdnP\tI!'2'$HU/X2Na/p A?ʝug01k&:!47oU"qmS$f GkK %22;&Xjn%|&p@*@#J;DC^mX.c܆^لp#H:?iQnZ . ҧpCV8{7dg=á^C}\@|q9ht@I 4 2J^zoY@&4C.3v j @OTeiqEVTѽҫ2|Ch5%@÷t5a󼇐W># mT{O4 _ȸrt2UJl{0<ʯzoM2>Bvq5 ӧ5[hXI#w\f%,H;#@,]^ʏ&7a0D0 ^Ͽ(` = P0e;vl1@z@n4p7=|[eJy#p/fـܓڛ@ʀWBvBdᬀg3[T.%gFr.#l~W '_jTf$/M>D1^Q6%'1Y z-)|Hw[6w| MW"{f#Fa_g+ީSXr%"[=C 4\P*÷gўjmhIDF&cc%hRv]@$Pnv6a#CQ3RūC=y f,^ʿ;, lAR;-@(w^@n7TcF>VH2VxK^#|:m!p?LW.נdk/W|fӌѵg9/`jvnPVo`zJ= CmWKuGW#yPМ!y/ {J}#}˗5<#1w}|V^k n;l~_Fe$~̤'}msS]3#\$՟&Mڋ6$?᭹>;ˊc%4|NY:V({OGh.}-z@diNk'1Ns&8{}7~wQ?).{dLTF CZou?qBړ'O>E6Hɑ/RѪ=?.9O gRmނ\K>a@"ો62hҗzQnQz@e\^Xet_ }(%t@^ á?@$7N/xJg~Kd`2 5@Y"U 'BLGIN%@׫Kj[.Y|>%ڌKeS?\&mqCP$N\/ϲd`MED x&ZGJm5)2|K/A ]Z&"r-.l6B.ifvtwQhrY&Q#bsi+(/ј0`!. 03THuB/['Ɣp]: D9#33E`!>As&#dxѩ8`ۤsbvb\&Q_[ӈ !C̹aΈ@_U4`-#X<rh#vs8E'8iXͦdM ґ @#rIք"ٓ&JUI[gۯ>iP D^zy<]:֚ U|fU2< DćA9rP2RZ=sU!:5|41[wҒEұgi3yrfLVQ!XOıK`d$c"$&y_#ӿu'~̪<nݿeHf8#6j.H Yw몬%,/7>P:Vp@w²XgGM5>hL3e̾t:iOC0`*ڷ%K,p&>hUY?xG@Ŝ>ttu:whw$uo&+y7QU,p*>h4$ /R&X&@d".WPBeP!='qlR|y28 C,@nZl!Z)#.0gR 4J&dL%K 6k-L*̓,M*5# @1FtA.w5$7"^<'aԍ22u9ΩIxwQ$#VW2m ÅFm`_+%ܒ)&x8.57qDf'ܧ aRz]sYP#9`xl>,%Ⱦcɣ/Ny @؂}ʚC}9gϼ[~#Em]x";ABԈu2-hFS;"D]D$yHUt)1|3n't Cqie32L⃲2PL2ߊCf"Zi.c Ƹ =%VLl7G%;8nL~c)'٧$@zM /swU]#a4Zӣԓ@-6} -1[/ P8Ҁ3ہNR=_OtGݎ) :|^rFM Ϣ]hA?Yi4_Z}IkL.E"x"~ BѮERbE{F H7}yݑ.OҺ ] g.vcX M+vѱf?N^s,j~ 4u~ٍ @IaP7@(arzgc$Sl>lǙߜ1(h?#DLx.T":GBpǘ^E~e'bKH?SX ϩ ܥ>ژ5,xvQ3 P5]Sayb+2(!bXRNFjܱm1<Qb^Uٝ:fL,Ɛ6am`9~3Ho`˭'sK7}U= =CwtGPZLuXEC3xKa3.UG[vĖ+qt޶㻖>6O.\AHFf4sU#sInj LG&#` #b^`2ؔ^"co=}m+FV4Q'nٹ 1%+xLC(Q+TX)cSQ\ O¯*,;utKz%9A==V(cu?֟XBɈ>?˚lTxk82*uN?)r@kWRo3,-K;828'xD4 4A3믁ExGf(UkKd#@ շ(pΜ92)YCw&_ߦgu}W?MrTOe8lI 0Ak vF &'Ց˜(3 |~BFKOXi$O.@̥t_r9F\\tَy /+̧\Ed)[[7_5AV䡳{X^@&&c"4;NGtҕ`|7BFcLCCQ\YJD 惿vTFw|G/G; 8o@@v˝I-ImaF0q./q}%jPJŷ̚kSy^" WY[7t@G\i%Cx>ʏ!+BbXO0#LNcWZ`6 #KXEcنI@U9Vڑ8{u@o 4LhP-{ABpīEaɓu+# @.^l'\R[Ut"=&j{Б f"j/,R>1HS )KA&ب  \Wfd - aܟ>l 1ݮPJJ=aV>v*k8pAu[@f%bu/}P"m cCNMlF>zFC%Q;&*n18u[x(NjY%R_ >5UЕnr,erUeMћjZa+G*YC BVcW`oصwZI7[ū %vxv)\`CUR}}R@xB*癙 @YDcsفtS&5C2a4RiB ~x'cKF~ZC-0{o }%h|z:0UЗT38-tՋ! OFK l bb'bFԟg/cߟS-#V6ZnrWa ^W3r#Z ̥Bfvo}xsgKKoG&@ lOu#cvۏe'?72evs)B3u谜Mk?>V [8{e 9_0 9D悠7DW$đKJYoJ!c?S;m}Az*?k99"ߌ{xp[h$ډ#4p@'<#L}f+#5b-s_؀|Zԕ#g3@1Ni,9~Ӛ*BF[< Nzҵ5諪ck(ͧF.EM㰐n*(HVYweHj1Qۢ@b:~2|H?,eu6)8.;Z4ZelX*,Do۪%g($"` ij({/o9f16C !/&W&`+^Mw4$>۬۬g:sT8DJzk hnvo/BWlvւXv!?AG9j@ $cεkѺh0##O hXpbdaQ(\De39'J46Oط }c/ @mz\"!OĐFE\gXTg%$*h6-XVxQ0+&\5*f߹Ky1UCQw P6opGrvɍ1=|Ňqw.ܐBSWm[6s7m X30)"vMV(0o}/VTax697FH_Aoس9ZzڂBf ݱ.=0j\Y=E+ڏё?BQ=s}ܪ46~颂{|mQmJf5>/nJUz@#3jR~]W\Y{JP)9@ZL*(wWuZ ,1tp]$% Y` (qgG.5aBf>v_e,EI]ݖ6 P@gW )>5 oF.Șe@[|#ari椳yW+ā\7.9o.q&}AɃ[^2Iw]#g8~~,3OyIaܖ֊Dpo @Fa(>I#9dF?k%m `yj&zб(waBhyH@#%S9yN `b=g~@^uo1sF)P8{~]#ӇSp?AD}c PE*ehOZ?Xݵoj7ߢ i$M-TUNJU!}M ?W| 9+?7qJ qez~ܓCPJ6B L` _B#jؘK;3 s]aK5MbQ*l=សB~HrMH KrD.9ϻf3Y d:yO< Ywh]@8|ߪ5F( B2xn0cvz CJ@Ĵ[WN\EqD&nܮБ5IЭbΥ!>q(Yb(5NG$3+Y%V V˼%$ `ۆHcp)\<r #2[\( Kv"-m:8!:9~{ˊkdeVGq޹D !4RkwR٥0b e)[H:v=5QVn9yI1ca!44+N=; @MJkeJg;\9z0`Y|0f1MKN#C vy)ʑ~YjO(T^yࣄ5;yպs@qf2I_(NYYL~RLZ9x'޶CVZ &pG|LCd5w7 ocN \SF _;ۿz7Wr.|oFZLvwvdz !Wj&P^Pc99;/x{qO8u $u"L֓>׵K۵mc+u, k ڎ\}OXsN:oSi8 fz[WQȊF4`cVԫ ɕwNwhP`@cUrzHh?tvRs^YR:?Doҟ ~ ʌrfx؍c?uȬo+m0xsy^&C4-X=}MWN-.mͩ|L5!}G2|p-Q3$bh[$(A@D +ٌX5vwV[  l;M3Tr V$69ТH+|=z1m B'OfҎOT&O\-%@Ka㹥<]-_:`Б//}lwy  W+ciS{ wM1IW <4 jXl;'kJdr`8dT |k$X*@GqFL~#tsЉ93baס:Ϟ/Jx!sIINx/悥kC(Jy$3L8l]\T9ݧ~O9PH:{saIUF\M顭QXf)Fs]QZu(MHlϫ${. 6n8 %E'P(@st>wi?SQ"t_ I*~cׅKz~-mlE2dѣsy2n9;5JW@v;IKW?'.^ݚT>*vX&P~q (E"'Eݘb~ߝ z5:ЩYP*5zI'S1`*t[i#hMQ,[d'ݜgxqFVV9Jc7aJ>͆_XJ5D 4? ;UQWbgM6S:4^8F)8_SG BkьI4(甈~8׃Wԩ?Ɗ>E'Z1Q,vbSN="#Q˝:u(Fx@r~[)ƚ Dñn?V߅[l_(ȡ IKШ:I qHF<%B=) Zlo%_obQURl"䶡2UnU#OZ4O]^4]W=5Ǘȥ^\&wE*49 $iؿ$s<2НN=}~ xWݭbD-gfaHޒrVaS}.:Q@#iw8,}JtSke#$!0:!aPxox\U~xO9kN[A䢚Y`$|$q_r?@zIBz mHYߪ{ΰ {M΍ )ZtUZ f!UbOqPJ ȯbR*_)a'#1K]\PsLWi[ N׭G vl*|yyd&B?U̥c>qξE+,M `Kx= {%6#CɗV=[Ԃ)z 'qjIuCXpeT@ շ֞C`-^1tھ>glS)Yy#rA #-Fj) b&+%Fv@3 J:ڔKp14藺x`CC @_r_e#Y z-(!,]皚՟z Up[wN0N B+w*)⑒S,OhQU5X rI v`9-/gʝ;ܕ3ηi 57_ɍ_fٜ ;i]:)E/䙭 n!Ȋ־ߐ8ƧSt; 7J=Ql~/d/bd:m;#zvW[& yW @hmz1[*>~>p ڷgE/W5q'K@_\:zH| ›F\ͳ!Mk4%MH6u޶]ccmdr[]3{$ j]$qm!Ѫ(p.8t*C7lFvdr Y< $7EP6'so<aXi蚓6@)dqi 0gݾJ(t҃*7ͨg@ lċc>(ܦӂUGI r=Wp_we fImyT6봖|l8["=! ©#jQ觃D1n7jmve8ѕ*XʌحN^Gp\θ ܆vh{p[[t+s+ު]8IN/Mk|@u땅4 P <}?ٟ=u,F ;8)5Z`TџǼk13k<~jMGݵ!]{=DҫƤĿYxn/ћ򞒥#֨LbuBY _sϩw2훟qEt@tE{Xfz3fՙYorf(QLx-\qkqKP fէch߻@:ڥb-՟FyQFpB21hI9"aSG)cKO]٩_(1ý3geꛆ!Seɵ.8/plU7;+w6 zޙf:J~^xa.@Mk/'g#kR> ?AJwEZLY/_S*ߴ&P^;^] ,P?_}jb t(ZUt>ɉ,Eސ3T4OslO2,"@1^TSj(i%D6?r 5DY)~ =rA W6_FwӬ*7%Yxk8ٍ#UM6{d3˒pql9l˫%w.H `46 (S@WB`JMC%\;,{M}i/kXbg73le8$J[n %}Y<#-W4OE+zHP#|)dځOQKw/Y~~ak.ר-eG-u鬫R?D>bUUX}('P~yd.FB EU%K֟AFI:XqBOC/VXkS% 6O>񟗮Ck GU* S-D8J٤gڈWA ʖ@ܟN7(kϟ[6~?kh6qt\x،=;b1fCz/E;6P".F ^Huϻ(\tlH!*Kɋx>*\)̭?ZEG%r/UmYW<}NadPQjp[WC\O>hO.tN~FDcAsD_@!7tI~K ýf,F`{4_,шsWi/}6mu[.F+0gm"Ƒ }&(|$|#k% G#` 4'3H@XY jMBȚu^;Tu>~lr,nR<ƃ7޼ZGF8sbV+Qd(Le&PZH#{ z/)&s.h]}caр/w;~ËtBHyw:;4fݎ$7΃(1 Fʵ'n1S{e)'zXguz/ &۸D In<Gy+VNs Oה ә0vcS䲢N \u>gEWtz奲<..B#@0~ 6 Ni?WB:袿$I$Q].!U$;olb݀s/V9W ևQ!E,G'*EH~ZG(𶒰ן]lnKtFj}RlQM\eU|穓0Q@2a*/{u{uST1L\.g}T}l@pMt#$ӟr7\2|S$4y%5 kπ^ўdd4nW3A \л$?DG1 nArrW]Bgl՟d{䡟\|@ضCbzg ( |# ,f!"zu$w. n]!,v%UџKDI*7C.QX{pzę3Tś6,A|,8_7 G7]is6\}I֒VTw}^/2e7`"?qAtaܵZicޒeA?\±x)] G*w {7$*U/Omh*֯c/2ߕ"?i% KkX5.9_ t2P !Aڏt.￝Kuc3T+SX#ek><8< hq2Y}3o8 }o%&M?~Լ8\ O<@BO~Z;\W>U~+5+ٙ b.ߌU_GXlbN9>Woh]Wzla=YɶfN%iY{-V @?uRڤ'E>9&УH4wn[ y}9Xe{U:t~sQYO4r5T@|E KO2avDūG>|}OB@/@h8 T-f4o@ xOU L.M ^@Jp m(}@ 7Bj7; ffuE04^M2@8QA c :q-Aݳ .B'Am3zvEy-L^0} l:EآP={f3kT+I/[~WKg֨_3_w>+UJ?}.as%|xKqkjL+Utjf~o~c=Aq-3\h*5kβ2?N|!e?| \W W^l檀Ÿj,ؘ)Z4?\P8B=?#rMR&۟Ȃ;p39#w)[b38m D h(,E:ʉ&elMvR*_/`Gʟ~B] );vh-|Gv˩ҦV|M7Ǖ`Mˊ-r4 *?~x '*<) &dقmN/T+Y3{&(JJM̅nNd!,5: ݸq/9f<%x+C&# CZnɟ@\qT$²7yJN!5`l+$diH3r3\ɭoqF*sc+ N,qF@OzKc[n{sXI|=;WV[򷳦Eȼ5GWoIpDT6C\O|u l%Ώd_RH bwy ld^;jМBAj#Wx:X0A?nF(ߑ#CV{8#Qρ"G;뗜DͰxOXV,鲭IsS4[۰W!Ҹ<> 6:Ex=]j7n@τ  Df($Mi[uk}ibM`u*9_N՗PfQ3 P7PA^C?ʩ&Oǵn':34ifNdm @|^x0QJc{$%8i<@^rpWS?ȿBކ65'h%*X$q 1qQ_ն*>d7X&cg_Lm$Hײـħ]%<"sa'x5;y|q3 2\#P3?{ /V=k_\Pxg"c-h#ǽ.{42Լh&5z𡬼4@d0UI:fW HvqW$7Rw8 \'1K.#9iPRŻȆ(ɣ<;*/i4Jdڹύ핌j?a8wQ2zETmpmo'X%{\Sm`-NOe^{֠d"5rJZG8⡝܆r0 JPi?b<mO h$FQYtKƻ h=VZ LHu BFn:t6SN%cqQBTlU"h |n2BsKJxKnols O=Ω'R`Y@ @^T2~!^s~W%ؕO` %Ś.0leHt,bء2kDf["ju?HZG`egL8$i[Vf>Ӆy8\*-]UH}q %آrȆ7tqV|?#~2^rnX$; ,ۋL 5-ʕe#@T~D݋J2Y$se>A]5K7 fI h'Whgſ:pe?]nqb6ɓGjAja"xp,1 ]m Lỵsi9/Rߘ| PP#<tK0;JЧ}2(IЃ(v1LXX n{.M9Qa$#  CVݝ:?[ވvHfU6ۗ"yEnxk$["xE"}6 ,8GZ0=^y1Cnw'< !<]UIS35gndAΟ89$q# @yK0;zTos sAK%$߯ӶURꢓH%x`1=iϼ@Th%)F} vROp!;x_OǭO^O5ӧ8U EIF:`V$4z-oҬGTj/F,5.>`h-f]~͠=pkY Kv` iJ(mh'hFPGNhkuL33 T@o %cҝФqA>M5EGL2Z_ w_VtI`X4)cOL~GE6&N~k ǡD)6vUW̓<:͌Guvی~5- @<>=MG$N y"3Ch4gPx&}`Q S뀐H4Yu#!>5a0<E“<¹hfg ./γjt h[6i$|E{n\$'ΚxJR2.(c7ߢa((z.g(݂5q`8dӣ&7(+L͉ Я[r W#,$Y\Jom=WK9|4E/FvzpH(x٣v<k\5h#T1X]F7;9d E9LX_L,(LUvL$饽PZ,Ь@?ڐg-WKp=4nUWg$ @spL.቏O>y}'Z3 *'l[a0&F=y==nYCr@=c  K}l2ykP^zmi8 XA h)Ofm`@ܲȑA^?WX3d%?>$iH،o3MڎMʺ~ރڒ V~۰tAM7tea$q'W :C@F7q*zɇ|lNCNv\@@2 )(>afK9%99NK p#A[yOb;l" (KqĂ |q"PvEE_F> dDk QY]r} k$vФ-ܼ]i<bI~Bw[ _h{J]T@Gl ~K1pD"<O&O 51xc*(B$CuId{'@o^ /% r.)@"H +On0:߰)C lJ mkOnkD Seso ld;3_:-Ow^]p.%թhV s &^|hK|"ӂ|ʗ"s?@ǶhyU3$#f# ᄐ2]4 S:~ޢ-> e@ ݝrͫ+ldď @:`$jfm!qA>98LA{@b8_[_' @' @@4Q2?*A>%v8 |~OU/|2w!?"B?ԿVQs w9uQZ{rasSZ_ӂ|A[7Q|$]40>cZ.BP% z lY,kR? 'A!;G/#7Y@x>4T+/KF7* րp;lVGsHd_\&9d{|'ۯ[7M8]) %,#\鈪\3>Ew)׹A0NX?@ ydI1+pY:؝ᢽhbPxYX xxrȐ%Q"GVg*H_%K\8jk-r}3tRW\]r1F{#;C{>=‘ɪV p.qdó<o ba>$gJ_B*FBn'.d]h|FWoBFvv) ojȶG[syJw H_p/> 0{\y3~,?Q5ߨcR*mE}23X >l +!3pZn.+n @rp8D=P۹[lC Tt߻e}JRlzHooN;r#ZH> ۤlc4 &S.[tQYrq@hN%bBpsǛLgxHӁҳ<=:؟U$WGCW;;(J>}\GN@fL7n\D7x{DM؀DrPF'*v-'NE O5޶Tiv-~xG7~^d*JH6< HY_yD=Wm%7FF7i)%ּ/`F<9~\!hQ^)aI nڤ~4R#<}S[`)KO$?[&7 ybĸ1=)R#]09' N!\5YfU/HY Y߇̻ڹYeķӳ1U]g 6Q⫄K?&6ܩ\\҇IodcY:os hՋڨ9o(Z~KGFN`ƹȰ6ξ}UlIKPJtlح\> Nu^׺LJvn-p1cI?yt͚.*Jt*-DI%$+|D۵c{i/|c*=q~4&~<o/$.rw}Z-靐}y D$YWx?3Q~3,5H{Kç; Y|1Z0aa[" أPQ4&+!}+$/4u:ڏ9npYw4-mt ^۬<4Һ]M( mj^No[#IAlrӱd $X:oR>-7 ?FI\MkPvnY JB0 ;ь@,mrG)t>@r@],nGTT|o7h&R i#0"^ڡA>ݤ:CDQ@Δ%%  3uvK ]ZObroo󬆑IlDv: 5h=1܆jR^5"|.X@/ Pk |z}_7*_T{ PVf!֦ihoWxj?ue҃ZYV@&9lK 7K@|/iv L*@'#u )ۻ*-<) EB((H{$'M@PB}((HQDQAB%ғ}3{k,,νٙowfΜI21; id[FkJ~)z QquΊ @Nék\vht @qx؋K]HYX_ Vuty[?"A 'O.W^\.Pl$[T!\AqyRbivPIJqO.񍲿LC1y_HG`}vy'ʁt jhR՘J65'pzn҅gV5XJPq~I>9 0ut cD= fʙDR z6,q4 G7ϦwTfl=xu ]Z:݋AZC̚+r (f5H|VP* wX!߾xV|m[_ጅә?DefM0 z @S|2̔_?52I0! !{|׽ xw<1q<@S6sz|> 1|g3 ]AsyǜDȨJ'Sg%p@CVfhI5e[ ˳~UW2y F^h7ehई-,<Ϯ K!f]K3F1;_UuOYm/\' 4Qr@U 78 t!7j PSDTil+ pdXȲ*`UItF@[=ެUSd1Zň[(#ܝ-%$..<@Ɠج \^^47jHN2o;wŹr;A[wuD&}X2|K赼_x)0gLLs7;F{,[HH>08Ve?kњR䐺nf' qjS2KcnmyBПc4K{'Ã0r+ %b3Ym8Z`imT; ji?N$bZKS%x%ܺ!~U4OZzrg38J%Qli_.Z0|@kvW>eWof,a8'`[b: hA؛Bj6Ek^k$V&iQMA}TADԱH~:Sa(#P؝B9H9?M:/ QԤm;.[F&:ݚE &MX}vUleaZ @*ջN֌q\cgսWPdgl?_Mc TY l^XP=+\SwRJT|亯}_Ƚۮ-'̵6NHĖC<-=M7xq["ƻE:814>@܊w|b̽n +"To6fc;Kbp|O]uwd|ۃ"u*Y %"&SV@\j e8oY$ Zo> _ᢲ63).7?/v6&.,~9@f͡˷vL(2yGvg >`!2 hq쵨3U.*vG?"5gk٩g׍|CPrm!Ge8ma J6G~^9/2_j?ǵ'_!1:?*))j0':KυY3"*emUP|ݪ^b 0'P 9OjLһ&0,3;0Bm&CHS@0^:0#␾'@MCZ'676z3jI #:Z뤬^T.~zM, Fnrfgb<>(@ >l8y;ńgQar5L1"TGi$U<rT~l|h"dT|ךȭܿrfwq:_ʒ`9ӆ_TϭP9v&М(@$I`>"ܥCE⽉;o% Ucc{{ F+Ix$uJ+=X-(cjEI홙MI=/F"#E5ڒ?)z#=EðQ=U󧖃.rH'`*^?|~GVr>-%rPW Q*RZH5JG@,eoZ)ȴ|s\ g/t"Ӑ&ioUB^!|ɶ_FO|KE(h] aqÎi,)B]vs_IhE{*yTržPD{<)s| ʽI5c 0PL_{]B?Ct`WF(KO'Yn+oJo?wh A|Y^2ۈGl̡?8* oY{$G=a~ =CgM%ot1mϼPVoMW3'-i.Yϔ@ SL C z9f^Bz 9=|7 矐K`GN|=@| A[=)xjV]Tfh 5`m x 2B6`P3/ھ,1ƨV|OBߠ%" 7-Xo֬sA1 sIʨ8>7}6۶zw`|iOWQH><[ Xw 5 Nc7@Fu,㗟qpSU%b> \(<S i{vq^2oWkY?.Nf:cѠ @Uo4 TA|KKG6GdgΈ`+3ozRK-, ,љsCi>渪W(y!M/11g>Kɪ_zR!1#M}#߁ozxЗ:1w?T+!!%y5r~6| Bq/lǧf>ZG4aěozufo ]GTڨ+k8j:[N*&;nn.4Hj?F~o W^ s}doC_3DB}_ҙ)huw} .h6R7O҅!Jv}Z~ ̡t;#ЕfyVbnpɀA]LXv/]"N7Tk_sٯRA- |cO95--_9\^+DH`"3E-sJ7q2ٙUIN{8CW8Kܮ_SJL^Armmw^qgJpa9M˱e:mn1VyqL$ Ř[NݺrXY-Hf>,9D )\cǶ562%pKfA-&*dXT`Jts[KBUt(`:TlU L"וYP)l 0)eW j04CP?j?2"7,qqqaِƖo1DoFuH>q7ƒjތZ|b#:p;(][2)f%95mğE{ OͿEDN.Ttn3I/''?8ZAC'<.l$ pw?Jj*BeQG~er5+Ig*Cr-͠%\]MWBAjb iq"܉O"wk&6$*)GkLᣟV$iN&Xu岊>"ܥًM=(ȳ>g1UB;z}+}_?EIwAi V! ˧ulDn[?k!OW j/|<:,r{pwF_s8h_BX׃/-O8^vmǟUtΗ_QquƓvw/L:6),rQB=+\pcDd <'}L|F" j\ >U]EqBӕwpjzÝoK@h'r Da(!\Z>ᣯM_(ڋ_!I?;GQ7L{퉉 D1j"~~SY=s[qOTo\u˕ DP~.sh!.wb(У4hHUJVݹծ):oQTC$(ٲרa:E6=HhܽYŀGTw![efL y j5e%|=_zI(JB^#Mq NaCFyIrԏݎ=)ԝM74ہ(VM2)iıE׿.;L.Kp0\5 DTEf4o\G @M:7s_=@~i| xfC9c=mT]AKI)XA*IRK$W1.ѵ\Ba_]S<#"F 5|.L2# Ѹ)GuǩxmtL=oف<@K0HsE7:#![jK\`؜V&D+qv O1+ѱ[1k 0W.$2ee HuG|V|dwЛԛyQHz\ XA@!9B0R!g9y:_dEf7{pݛ4r zG]\W~gTD7B5)f(25"ݞہMqˇ~ѧ8àp,g9[@6^:EE/:5'$)eԢ-MF`4 :@,_8-gvİ =G|- C,il0lpy>6=EvR!V1>]TnvR)QhNNa$L\@bJP/{M Àu*^̹I3V?&=S$C|s9#BYnp71Hľ0Sܙ?m< 0X7͈ZeOT·_;C4)e^8DklQB|Y/!xr1Θ+`;.~/VTXu3eQ?T1TIENDB`oolite-1.82/tools/oxp-templates/translation overrides/000077500000000000000000000000001256642440500232325ustar00rootroot00000000000000oolite-1.82/tools/oxp-templates/translation overrides/shipdata-overrides.plist000066400000000000000000000066051256642440500301130ustar00rootroot00000000000000{ "adder" = { name = "Adder"; }; "adder-player" = { name = "Adder"; }; "alloy" = { name = "Metal fragment"; }; "anaconda" = { name = "Anaconda"; }; "anaconda-player" = { name = "Anaconda"; }; "arc-detail" = { name = "Arc Detail"; }; "asp" = { name = "Asp Mark II"; }; "asp-cloaked" = { name = "Asp Mark II"; }; "asp-player" = { name = "Asp Mark II"; }; "asteroid" = { name = "Asteroid"; }; "barrel" = { name = "Cargo container"; }; "boa" = { name = "Boa"; }; "boa-mk2" = { name = "Boa Class Cruiser"; }; "boa-mk2-player" = { name = "Boa Class Cruiser"; }; "boa-player" = { name = "Boa"; }; "boulder" = { name = "Boulder"; }; "buoy" = { name = "Navigation Buoy"; }; "buoy-witchpoint" = { name = "Witchpoint Beacon"; }; "cloaking-device" = { name = "Unusual cargo container"; }; "cobra3-alternate" = { name = "Cobra Mark III"; }; "cobra3-pirate" = { name = "Cobra Mark III"; }; "cobra3-player" = { name = "Cobra Mark III"; }; "cobra3-trader" = { name = "Cobra Mark III"; }; "cobramk1" = { name = "Cobra Mark I"; }; "cobramk1-alt" = { name = "Cobra Mark I"; }; "cobramk1-miner" = { name = "Cobra Mark I"; }; "cobramk1-player" = { name = "Cobra Mark I"; }; "constrictor" = { name = "Constrictor"; }; "coriolis-station" = { name = "Coriolis Station"; }; "dock" = { name = "Docking Slit (Vertical)"; }; "dock-flat" = { name = "Docking Slit (horizontal)"; }; "dodecahedron-station" = { name = "Dodecahedron Station"; }; "ecm-proof-missile" = { name = "ECM Hardened Missile"; }; "escape-capsule" = { name = "Escape capsule"; }; "ferdelance" = { name = "Fer-de-Lance"; }; "ferdelance-player" = { name = "Fer-de-Lance"; }; "gecko" = { name = "Gecko"; }; "hermit-docking-slit" = { name = "Docking Slit"; }; "hermitage" = { name = "Rock Hermit living area"; }; "icosahedron-station" = { name = "Icosahedron Station"; }; "krait" = { name = "Krait"; }; "mamba" = { name = "Mamba"; }; "mamba-escort" = { name = "Mamba Escort"; }; "missile" = { name = "Missile"; }; "moray" = { name = "Moray Star Boat"; }; "moray-player" = { name = "Moray Star Boat"; }; "morayMED" = { name = "Moray Medical Boat"; }; "morayMED-player" = { name = "Moray Medical Boat"; }; "python" = { name = "Python"; }; "python-blackdog" = { name = "Python"; }; "python-player" = { name = "Python"; }; "python-trader" = { name = "Python"; }; "qbomb" = { name = "Quirium Cascade Mine"; }; "rock-hermit" = { name = "Rock Hermit"; }; "shuttle" = { name = "Orbital Shuttle"; }; "sidewinder" = { name = "Sidewinder Scout Ship"; }; "sidewinder-escort" = { name = "Sidewinder Scout Ship"; }; "splinter" = { name = "Splinter"; }; "strut" = { name = "Strut 10-10-30"; }; "tharglet" = { name = "Thargoid Robot Fighter"; }; "thargoid" = { name = "Thargoid Warship"; }; "transporter" = { name = "Transporter"; }; "transporter-miner" = { name = "Mining Transporter"; }; "viper" = { name = "GalCop Viper"; }; "viper-interceptor" = { name = "GalCop Viper Interceptor"; }; "viper-pursuit" = { name = "GalCop Viper"; }; "worm" = { name = "Worm"; }; "worm-miner" = { name = "Worm"; }; "wreckage-component" = { name = "Wreckage"; }; "ballturret" = { name = "Ball Turret"; }; } oolite-1.82/tools/planetinfo-source/000077500000000000000000000000001256642440500175445ustar00rootroot00000000000000oolite-1.82/tools/planetinfo-source/1.80-planetinfo-data-extract.log000066400000000000000000210464471256642440500253730ustar00rootroot0000000000000011:05:42.703 [PLANETINFO LOGGING]: 0 0 11:05:43.491 [planetinfo.record]: seed = 72 2 83 183 11:05:43.491 [planetinfo.record]: coordinates = 2 90 11:05:43.491 [planetinfo.record]: government = 1; 11:05:43.491 [planetinfo.record]: economy = 2; 11:05:43.491 [planetinfo.record]: techlevel = 8; 11:05:43.491 [planetinfo.record]: population = 36; 11:05:43.491 [planetinfo.record]: productivity = 11520; 11:05:43.491 [planetinfo.record]: name = "Tibedied"; 11:05:43.491 [planetinfo.record]: inhabitant = "Human Colonial"; 11:05:43.491 [planetinfo.record]: inhabitants = "Human Colonials"; 11:05:43.491 [planetinfo.record]: description = "This planet is most notable for Tibediedian Arma brandy but scourged by deadly edible grubs."; 11:05:43.508 [planetinfo.record]: air_color = 0.735411, 0.597965, 0.878678, 1; 11:05:43.508 [planetinfo.record]: cloud_alpha = 1.000000; 11:05:43.508 [planetinfo.record]: cloud_color = 0.638672, 0.409149, 0.527497, 1; 11:05:43.508 [planetinfo.record]: cloud_fraction = 0.300000; 11:05:43.508 [planetinfo.record]: land_color = 0.077525, 0.0438281, 0.66, 1; 11:05:43.508 [planetinfo.record]: land_fraction = 0.650000; 11:05:43.508 [planetinfo.record]: polar_cloud_color = 0.787402, 0.610544, 0.701737, 1; 11:05:43.508 [planetinfo.record]: polar_land_color = 0.727927, 0.716006, 0.934, 1; 11:05:43.508 [planetinfo.record]: polar_sea_color = 0.888758, 0.932617, 0.883801, 1; 11:05:43.508 [planetinfo.record]: sea_color = 0.547074, 0.673828, 0.532745, 1; 11:05:43.508 [planetinfo.record]: rotation_speed = 0.002623 11:05:43.509 [planetinfo.record]: planet zpos = 507100.000000 11:05:43.509 [planetinfo.record]: sun_radius = 150815.429688 11:05:43.509 [planetinfo.record]: sun_vector = 0.484 -0.815 0.319 11:05:43.509 [planetinfo.record]: sun_distance = 783700 11:05:43.509 [planetinfo.record]: corona_flare = 0.009911 11:05:43.509 [planetinfo.record]: corona_hues = 0.658028 11:05:43.509 [planetinfo.record]: sun_color = 0.781, 0.520397, 0.402831, 1 11:05:43.509 [planetinfo.record]: corona_shimmer = 0.349376 11:05:43.510 [planetinfo.record]: station_vector = -0.717 -0.668 0.201 11:05:43.510 [planetinfo.record]: station = coriolis 11:05:48.120 [PLANETINFO OVER]: Done 11:05:48.121 [PLANETINFO LOGGING]: 0 1 11:05:49.141 [planetinfo.record]: seed = 184 152 29 122 11:05:49.141 [planetinfo.record]: coordinates = 152 205 11:05:49.141 [planetinfo.record]: government = 7; 11:05:49.141 [planetinfo.record]: economy = 5; 11:05:49.141 [planetinfo.record]: techlevel = 6; 11:05:49.141 [planetinfo.record]: population = 37; 11:05:49.141 [planetinfo.record]: productivity = 16280; 11:05:49.141 [planetinfo.record]: name = "Qube"; 11:05:49.141 [planetinfo.record]: inhabitant = "Human Colonial"; 11:05:49.141 [planetinfo.record]: inhabitants = "Human Colonials"; 11:05:49.141 [planetinfo.record]: description = "Qube is reasonably well known for its great dense forests but scourged by deadly civil war."; 11:05:49.152 [planetinfo.record]: air_color = 0.706241, 0.835974, 0.88258, 1; 11:05:49.153 [planetinfo.record]: cloud_alpha = 1.000000; 11:05:49.153 [planetinfo.record]: cloud_color = 0.650391, 0.650391, 0.650391, 1; 11:05:49.153 [planetinfo.record]: cloud_fraction = 0.380000; 11:05:49.153 [planetinfo.record]: land_color = 0.529805, 0.66, 0.399609, 1; 11:05:49.153 [planetinfo.record]: land_fraction = 0.420000; 11:05:49.153 [planetinfo.record]: polar_cloud_color = 0.792676, 0.792676, 0.792676, 1; 11:05:49.153 [planetinfo.record]: polar_land_color = 0.887938, 0.934, 0.841877, 1; 11:05:49.153 [planetinfo.record]: polar_sea_color = 0.877931, 0.929297, 0.879938, 1; 11:05:49.153 [planetinfo.record]: sea_color = 0.550711, 0.707031, 0.556817, 1; 11:05:49.153 [planetinfo.record]: rotation_speed = 0.000664 11:05:49.153 [planetinfo.record]: planet zpos = 608080.000000 11:05:49.153 [planetinfo.record]: sun_radius = 133270.551758 11:05:49.153 [planetinfo.record]: sun_vector = -0.822 0.449 0.349 11:05:49.153 [planetinfo.record]: sun_distance = 1271440 11:05:49.153 [planetinfo.record]: corona_flare = 0.022607 11:05:49.154 [planetinfo.record]: corona_hues = 0.548996 11:05:49.154 [planetinfo.record]: sun_color = 0.789368, 0.649365, 0.607485, 1 11:05:49.154 [planetinfo.record]: corona_shimmer = 0.342672 11:05:49.154 [planetinfo.record]: station_vector = -0.642 0.757 0.120 11:05:49.154 [planetinfo.record]: station = coriolis 11:05:53.684 [PLANETINFO OVER]: Done 11:05:53.685 [PLANETINFO LOGGING]: 0 2 11:05:54.704 [planetinfo.record]: seed = 156 77 27 33 11:05:54.705 [planetinfo.record]: coordinates = 77 243 11:05:54.705 [planetinfo.record]: government = 3; 11:05:54.705 [planetinfo.record]: economy = 3; 11:05:54.705 [planetinfo.record]: techlevel = 7; 11:05:54.705 [planetinfo.record]: population = 35; 11:05:54.705 [planetinfo.record]: productivity = 13720; 11:05:54.705 [planetinfo.record]: name = "Leleer"; 11:05:54.705 [planetinfo.record]: inhabitant = "Human Colonial"; 11:05:54.705 [planetinfo.record]: inhabitants = "Human Colonials"; 11:05:54.705 [planetinfo.record]: description = "The world Leleer is very noted for its pink Leleerian Er plant plantations but beset by frequent civil war."; 11:05:54.727 [planetinfo.record]: air_color = 0.484674, 0.620879, 0.870223, 1; 11:05:54.727 [planetinfo.record]: cloud_alpha = 1.000000; 11:05:54.727 [planetinfo.record]: cloud_color = 0.165298, 0.613281, 0.592282, 1; 11:05:54.727 [planetinfo.record]: cloud_fraction = 0.500000; 11:05:54.727 [planetinfo.record]: land_color = 0.0283594, 0.319506, 0.66, 1; 11:05:54.727 [planetinfo.record]: land_fraction = 0.380000; 11:05:54.728 [planetinfo.record]: polar_cloud_color = 0.42171, 0.775977, 0.75937, 1; 11:05:54.728 [planetinfo.record]: polar_land_color = 0.710533, 0.813537, 0.934, 1; 11:05:54.728 [planetinfo.record]: polar_sea_color = 0.941406, 0.938395, 0.893233, 1; 11:05:54.728 [planetinfo.record]: sea_color = 0.585938, 0.578442, 0.466003, 1; 11:05:54.728 [planetinfo.record]: rotation_speed = 0.004284 11:05:54.728 [planetinfo.record]: planet zpos = 440860.000000 11:05:54.728 [planetinfo.record]: sun_radius = 106508.428650 11:05:54.729 [planetinfo.record]: sun_vector = -0.611 0.787 -0.082 11:05:54.729 [planetinfo.record]: sun_distance = 661290 11:05:54.729 [planetinfo.record]: corona_flare = 0.033124 11:05:54.729 [planetinfo.record]: corona_hues = 0.843491 11:05:54.729 [planetinfo.record]: sun_color = 0.330061, 0.574241, 0.813336, 1 11:05:54.729 [planetinfo.record]: corona_shimmer = 0.316976 11:05:54.730 [planetinfo.record]: station_vector = 0.176 -0.984 0.032 11:05:54.730 [planetinfo.record]: station = coriolis 11:05:58.925 [PLANETINFO OVER]: Done 11:05:58.925 [PLANETINFO LOGGING]: 0 3 11:05:59.928 [planetinfo.record]: seed = 148 83 13 134 11:05:59.928 [planetinfo.record]: coordinates = 83 208 11:05:59.928 [planetinfo.record]: government = 2; 11:05:59.928 [planetinfo.record]: economy = 0; 11:05:59.928 [planetinfo.record]: techlevel = 11; 11:05:59.928 [planetinfo.record]: population = 47; 11:05:59.928 [planetinfo.record]: productivity = 22560; 11:05:59.928 [planetinfo.record]: name = "Biarge"; 11:05:59.928 [planetinfo.record]: inhabitant = "Human Colonial"; 11:05:59.928 [planetinfo.record]: inhabitants = "Human Colonials"; 11:05:59.928 [planetinfo.record]: description = "This world is very fabled for the Biargeian edible poet."; 11:05:59.948 [planetinfo.record]: air_color = 0.65648, 0.750663, 0.989244, 1; 11:05:59.948 [planetinfo.record]: cloud_alpha = 1.000000; 11:05:59.948 [planetinfo.record]: cloud_color = 0.572563, 0.905383, 0.970703, 1; 11:05:59.948 [planetinfo.record]: cloud_fraction = 0.390000; 11:05:59.948 [planetinfo.record]: land_color = 0.507812, 0.416751, 0.0595093, 1; 11:05:59.948 [planetinfo.record]: land_fraction = 0.490000; 11:05:59.948 [planetinfo.record]: polar_cloud_color = 0.696666, 0.897417, 0.936816, 1; 11:05:59.948 [planetinfo.record]: polar_land_color = 0.949219, 0.906665, 0.739723, 1; 11:05:59.948 [planetinfo.record]: polar_sea_color = 0.947461, 0.887319, 0.933835, 1; 11:05:59.948 [planetinfo.record]: sea_color = 0.525391, 0.391991, 0.495167, 1; 11:05:59.948 [planetinfo.record]: rotation_speed = 0.002378 11:05:59.948 [planetinfo.record]: planet zpos = 532200.000000 11:05:59.949 [planetinfo.record]: sun_radius = 98050.341034 11:05:59.949 [planetinfo.record]: sun_vector = 0.927 -0.174 0.332 11:05:59.949 [planetinfo.record]: sun_distance = 887000 11:05:59.949 [planetinfo.record]: corona_flare = 0.025475 11:05:59.949 [planetinfo.record]: corona_hues = 0.751572 11:05:59.949 [planetinfo.record]: sun_color = 0.794492, 0.397627, 0.336652, 1 11:05:59.949 [planetinfo.record]: corona_shimmer = 0.539178 11:05:59.949 [planetinfo.record]: station_vector = 0.896 -0.269 0.354 11:05:59.949 [planetinfo.record]: station = dodecahedron 11:06:05.119 [PLANETINFO OVER]: Done 11:06:05.120 [PLANETINFO LOGGING]: 0 4 11:06:06.127 [planetinfo.record]: seed = 32 180 51 226 11:06:06.127 [planetinfo.record]: coordinates = 180 131 11:06:06.127 [planetinfo.record]: government = 4; 11:06:06.127 [planetinfo.record]: economy = 3; 11:06:06.127 [planetinfo.record]: techlevel = 6; 11:06:06.127 [planetinfo.record]: population = 32; 11:06:06.127 [planetinfo.record]: productivity = 14336; 11:06:06.127 [planetinfo.record]: name = "Xequerin"; 11:06:06.127 [planetinfo.record]: inhabitant = "Human Colonial"; 11:06:06.127 [planetinfo.record]: inhabitants = "Human Colonials"; 11:06:06.127 [planetinfo.record]: description = "The world Xequerin is fabled for its weird volcanoes and the Xequerinian mountain lobstoid."; 11:06:06.144 [planetinfo.record]: air_color = 0.627757, 0.532498, 0.905344, 1; 11:06:06.144 [planetinfo.record]: cloud_alpha = 1.000000; 11:06:06.144 [planetinfo.record]: cloud_color = 0.618641, 0.261108, 0.71875, 1; 11:06:06.144 [planetinfo.record]: cloud_fraction = 0.190000; 11:06:06.144 [planetinfo.record]: land_color = 0.19043, 0.540642, 0.609375, 1; 11:06:06.144 [planetinfo.record]: land_fraction = 0.300000; 11:06:06.144 [planetinfo.record]: polar_cloud_color = 0.751756, 0.495751, 0.823438, 1; 11:06:06.144 [planetinfo.record]: polar_land_color = 0.777661, 0.912583, 0.939062, 1; 11:06:06.144 [planetinfo.record]: polar_sea_color = 0.932422, 0.928671, 0.872415, 1; 11:06:06.144 [planetinfo.record]: sea_color = 0.675781, 0.664909, 0.50182, 1; 11:06:06.144 [planetinfo.record]: rotation_speed = 0.002357 11:06:06.144 [planetinfo.record]: planet zpos = 491120.000000 11:06:06.144 [planetinfo.record]: sun_radius = 88926.322632 11:06:06.144 [planetinfo.record]: sun_vector = 0.355 -0.935 0.018 11:06:06.144 [planetinfo.record]: sun_distance = 806840 11:06:06.144 [planetinfo.record]: corona_flare = 0.083528 11:06:06.144 [planetinfo.record]: corona_hues = 0.872391 11:06:06.144 [planetinfo.record]: sun_color = 0.605841, 0.747134, 0.805695, 1 11:06:06.144 [planetinfo.record]: corona_shimmer = 0.372199 11:06:06.144 [planetinfo.record]: station_vector = -0.427 0.896 0.125 11:06:06.144 [planetinfo.record]: station = coriolis 11:06:10.393 [PLANETINFO OVER]: Done 11:06:10.394 [PLANETINFO LOGGING]: 0 5 11:06:11.397 [planetinfo.record]: seed = 224 172 141 119 11:06:11.397 [planetinfo.record]: coordinates = 172 176 11:06:11.397 [planetinfo.record]: government = 4; 11:06:11.397 [planetinfo.record]: economy = 0; 11:06:11.397 [planetinfo.record]: techlevel = 9; 11:06:11.397 [planetinfo.record]: population = 41; 11:06:11.397 [planetinfo.record]: productivity = 26240; 11:06:11.397 [planetinfo.record]: name = "Tiraor"; 11:06:11.397 [planetinfo.record]: inhabitant = "Blue Fat Insect"; 11:06:11.397 [planetinfo.record]: inhabitants = "Blue Fat Insects"; 11:06:11.397 [planetinfo.record]: description = "Tiraor is a revolting little planet."; 11:06:11.405 [planetinfo.record]: air_color = 0.716473, 0.581367, 0.886482, 1; 11:06:11.405 [planetinfo.record]: cloud_alpha = 1.000000; 11:06:11.405 [planetinfo.record]: cloud_color = 0.662109, 0.375023, 0.563423, 1; 11:06:11.405 [planetinfo.record]: cloud_fraction = 0.110000; 11:06:11.405 [planetinfo.record]: land_color = 0.507891, 0.642175, 0.66, 1; 11:06:11.405 [planetinfo.record]: land_fraction = 0.600000; 11:06:11.405 [planetinfo.record]: polar_cloud_color = 0.797949, 0.581708, 0.723616, 1; 11:06:11.405 [planetinfo.record]: polar_land_color = 0.880186, 0.927694, 0.934, 1; 11:06:11.405 [planetinfo.record]: polar_sea_color = 0.923546, 0.948438, 0.749303, 1; 11:06:11.405 [planetinfo.record]: sea_color = 0.461494, 0.515625, 0.0825806, 1; 11:06:11.406 [planetinfo.record]: rotation_speed = 0.000353 11:06:11.406 [planetinfo.record]: planet zpos = 573600.000000 11:06:11.406 [planetinfo.record]: sun_radius = 143111.169434 11:06:11.406 [planetinfo.record]: sun_vector = 0.194 -0.899 0.392 11:06:11.406 [planetinfo.record]: sun_distance = 1099400 11:06:11.406 [planetinfo.record]: corona_flare = 0.087144 11:06:11.406 [planetinfo.record]: corona_hues = 0.687195 11:06:11.406 [planetinfo.record]: sun_color = 0.740216, 0.655512, 0.529065, 1 11:06:11.406 [planetinfo.record]: corona_shimmer = 0.391110 11:06:11.406 [planetinfo.record]: station_vector = 0.842 -0.025 0.540 11:06:11.406 [planetinfo.record]: station = coriolis 11:06:16.022 [PLANETINFO OVER]: Done 11:06:16.023 [PLANETINFO LOGGING]: 0 6 11:06:17.026 [planetinfo.record]: seed = 212 69 27 20 11:06:17.026 [planetinfo.record]: coordinates = 69 249 11:06:17.026 [planetinfo.record]: government = 2; 11:06:17.026 [planetinfo.record]: economy = 1; 11:06:17.026 [planetinfo.record]: techlevel = 8; 11:06:17.026 [planetinfo.record]: population = 36; 11:06:17.026 [planetinfo.record]: productivity = 15552; 11:06:17.026 [planetinfo.record]: name = "Rabedira"; 11:06:17.026 [planetinfo.record]: inhabitant = "Human Colonial"; 11:06:17.026 [planetinfo.record]: inhabitants = "Human Colonials"; 11:06:17.026 [planetinfo.record]: description = "The planet Rabedira is well known for its inhabitants’ ancient loathing of sit coms but ravaged by dreadful civil war."; 11:06:17.036 [planetinfo.record]: air_color = 0.68918, 0.573184, 0.884531, 1; 11:06:17.036 [planetinfo.record]: cloud_alpha = 1.000000; 11:06:17.036 [planetinfo.record]: cloud_color = 0.65625, 0.356323, 0.616416, 1; 11:06:17.036 [planetinfo.record]: cloud_fraction = 0.170000; 11:06:17.036 [planetinfo.record]: land_color = 0.595703, 0.428162, 0.522404, 1; 11:06:17.036 [planetinfo.record]: land_fraction = 0.650000; 11:06:17.036 [planetinfo.record]: polar_cloud_color = 0.795313, 0.568136, 0.765141, 1; 11:06:17.037 [planetinfo.record]: polar_land_color = 0.94043, 0.874306, 0.9115, 1; 11:06:17.037 [planetinfo.record]: polar_sea_color = 0.921777, 0.929492, 0.867768, 1; 11:06:17.037 [planetinfo.record]: sea_color = 0.681667, 0.705078, 0.517792, 1; 11:06:17.037 [planetinfo.record]: rotation_speed = 0.001464 11:06:17.037 [planetinfo.record]: planet zpos = 390900.000000 11:06:17.037 [planetinfo.record]: sun_radius = 94914.451904 11:06:17.037 [planetinfo.record]: sun_vector = -0.937 0.247 -0.245 11:06:17.037 [planetinfo.record]: sun_distance = 859980 11:06:17.037 [planetinfo.record]: corona_flare = 0.056177 11:06:17.037 [planetinfo.record]: corona_hues = 0.560776 11:06:17.037 [planetinfo.record]: sun_color = 0.731796, 0.553063, 0.334029, 1 11:06:17.037 [planetinfo.record]: corona_shimmer = 0.291800 11:06:17.038 [planetinfo.record]: station_vector = 0.451 -0.892 0.036 11:06:17.038 [planetinfo.record]: station = coriolis 11:06:21.577 [PLANETINFO OVER]: Done 11:06:21.578 [PLANETINFO LOGGING]: 0 7 11:06:22.597 [planetinfo.record]: seed = 156 20 29 21 11:06:22.598 [planetinfo.record]: coordinates = 20 173 11:06:22.598 [planetinfo.record]: government = 3; 11:06:22.598 [planetinfo.record]: economy = 5; 11:06:22.598 [planetinfo.record]: techlevel = 4; 11:06:22.598 [planetinfo.record]: population = 25; 11:06:22.598 [planetinfo.record]: productivity = 7000; 11:06:22.598 [planetinfo.record]: name = "Lave"; 11:06:22.598 [planetinfo.record]: inhabitant = "Human Colonial"; 11:06:22.598 [planetinfo.record]: inhabitants = "Human Colonials"; 11:06:22.598 [planetinfo.record]: description = "Lave is most famous for its vast rain forests and the Laveian tree grub."; 11:06:22.611 [planetinfo.record]: air_color = 0.49792, 0.996398, 0.910277, 1; 11:06:22.611 [planetinfo.record]: cloud_alpha = 1.000000; 11:06:22.611 [planetinfo.record]: cloud_color = 0.992188, 0.537546, 0.0968933, 1; 11:06:22.611 [planetinfo.record]: cloud_fraction = 0.260000; 11:06:22.611 [planetinfo.record]: land_color = 0.66, 0.58882, 0.420234, 1; 11:06:22.611 [planetinfo.record]: land_fraction = 0.650000; 11:06:22.611 [planetinfo.record]: polar_cloud_color = 0.946484, 0.675422, 0.4127, 1; 11:06:22.611 [planetinfo.record]: polar_land_color = 0.934, 0.908817, 0.849174, 1; 11:06:22.611 [planetinfo.record]: polar_sea_color = 0.941016, 0.862904, 0.934303, 1; 11:06:22.611 [planetinfo.record]: sea_color = 0.589844, 0.393997, 0.573013, 1; 11:06:22.611 [planetinfo.record]: rotation_speed = 0.004504 11:06:22.611 [planetinfo.record]: planet zpos = 452760.000000 11:06:22.612 [planetinfo.record]: sun_radius = 84449.095459 11:06:22.612 [planetinfo.record]: sun_vector = 0.865 0.376 -0.334 11:06:22.612 [planetinfo.record]: sun_distance = 905520 11:06:22.612 [planetinfo.record]: corona_flare = 0.028528 11:06:22.612 [planetinfo.record]: corona_hues = 0.621948 11:06:22.612 [planetinfo.record]: sun_color = 1, 1, 1, 1 11:06:22.612 [planetinfo.record]: corona_shimmer = 0.597422 11:06:22.612 [planetinfo.record]: station_vector = 0.601 -0.738 0.305 11:06:22.612 [planetinfo.record]: station = coriolis 11:06:27.178 [PLANETINFO OVER]: Done 11:06:27.179 [PLANETINFO LOGGING]: 0 8 11:06:28.181 [planetinfo.record]: seed = 184 236 83 196 11:06:28.181 [planetinfo.record]: coordinates = 236 0 11:06:28.181 [planetinfo.record]: government = 7; 11:06:28.181 [planetinfo.record]: economy = 0; 11:06:28.181 [planetinfo.record]: techlevel = 11; 11:06:28.181 [planetinfo.record]: population = 52; 11:06:28.181 [planetinfo.record]: productivity = 45760; 11:06:28.181 [planetinfo.record]: name = "Zaatxe"; 11:06:28.181 [planetinfo.record]: inhabitant = "Human Colonial"; 11:06:28.181 [planetinfo.record]: inhabitants = "Human Colonials"; 11:06:28.182 [planetinfo.record]: description = "This planet is mildly noted for the Zaatxeian deadly Ouenbeoid but plagued by lethal spotted craboids."; 11:06:28.212 [planetinfo.record]: air_color = 0.719797, 0.772381, 0.942416, 1; 11:06:28.212 [planetinfo.record]: cloud_alpha = 1.000000; 11:06:28.212 [planetinfo.record]: cloud_color = 0.739288, 0.802416, 0.830078, 1; 11:06:28.212 [planetinfo.record]: cloud_fraction = 0.360000; 11:06:28.212 [planetinfo.record]: land_color = 0.656697, 0.654844, 0.66, 1; 11:06:28.212 [planetinfo.record]: land_fraction = 0.560000; 11:06:28.212 [planetinfo.record]: polar_cloud_color = 0.813821, 0.855341, 0.873535, 1; 11:06:28.212 [planetinfo.record]: polar_land_color = 0.932831, 0.932176, 0.934, 1; 11:06:28.212 [planetinfo.record]: polar_sea_color = 0.912258, 0.941211, 0.709585, 1; 11:06:28.212 [planetinfo.record]: sea_color = 0.515553, 0.587891, 0.00918579, 1; 11:06:28.212 [planetinfo.record]: rotation_speed = 0.002039 11:06:28.212 [planetinfo.record]: planet zpos = 570640.000000 11:06:28.212 [planetinfo.record]: sun_radius = 129574.830933 11:06:28.212 [planetinfo.record]: sun_vector = 0.447 -0.497 -0.744 11:06:28.212 [planetinfo.record]: sun_distance = 815200 11:06:28.212 [planetinfo.record]: corona_flare = 0.037599 11:06:28.212 [planetinfo.record]: corona_hues = 0.830612 11:06:28.212 [planetinfo.record]: sun_color = 0.561819, 0.593857, 0.693158, 1 11:06:28.212 [planetinfo.record]: corona_shimmer = 0.320188 11:06:28.213 [planetinfo.record]: station_vector = 0.733 -0.680 0.016 11:06:28.213 [planetinfo.record]: station = icosahedron 11:06:33.545 [PLANETINFO OVER]: Done 11:06:33.545 [PLANETINFO LOGGING]: 0 9 11:06:34.549 [planetinfo.record]: seed = 200 216 61 237 11:06:34.549 [planetinfo.record]: coordinates = 216 98 11:06:34.549 [planetinfo.record]: government = 1; 11:06:34.549 [planetinfo.record]: economy = 2; 11:06:34.549 [planetinfo.record]: techlevel = 6; 11:06:34.549 [planetinfo.record]: population = 28; 11:06:34.549 [planetinfo.record]: productivity = 8960; 11:06:34.549 [planetinfo.record]: name = "Diusreza"; 11:06:34.549 [planetinfo.record]: inhabitant = "Human Colonial"; 11:06:34.549 [planetinfo.record]: inhabitants = "Human Colonials"; 11:06:34.549 [planetinfo.record]: description = "This planet is mildly fabled for its inhabitants’ eccentric love for tourists but beset by deadly edible moths."; 11:06:34.555 [planetinfo.record]: air_color = 0.498995, 0.502954, 0.900141, 1; 11:06:34.556 [planetinfo.record]: cloud_alpha = 1.000000; 11:06:34.556 [planetinfo.record]: cloud_color = 0.184021, 0.192132, 0.703125, 1; 11:06:34.556 [planetinfo.record]: cloud_fraction = 0.420000; 11:06:34.556 [planetinfo.record]: land_color = 0.139219, 0.403678, 0.66, 1; 11:06:34.556 [planetinfo.record]: land_fraction = 0.290000; 11:06:34.556 [planetinfo.record]: polar_cloud_color = 0.439695, 0.445581, 0.816406, 1; 11:06:34.556 [planetinfo.record]: polar_land_color = 0.749754, 0.843316, 0.934, 1; 11:06:34.556 [planetinfo.record]: polar_sea_color = 0.930078, 0.90884, 0.863774, 1; 11:06:34.556 [planetinfo.record]: sea_color = 0.699219, 0.635353, 0.499832, 1; 11:06:34.556 [planetinfo.record]: rotation_speed = 0.000102 11:06:34.556 [planetinfo.record]: planet zpos = 636000.000000 11:06:34.556 [planetinfo.record]: sun_radius = 173245.367432 11:06:34.556 [planetinfo.record]: sun_vector = 0.535 0.784 0.315 11:06:34.556 [planetinfo.record]: sun_distance = 1335600 11:06:34.556 [planetinfo.record]: corona_flare = 0.012115 11:06:34.556 [planetinfo.record]: corona_hues = 0.587791 11:06:34.556 [planetinfo.record]: sun_color = 0.418406, 0.55664, 0.806241, 1 11:06:34.557 [planetinfo.record]: corona_shimmer = 0.433806 11:06:34.557 [planetinfo.record]: station_vector = -0.943 -0.172 0.286 11:06:34.557 [planetinfo.record]: station = coriolis 11:06:39.847 [PLANETINFO OVER]: Done 11:06:39.847 [PLANETINFO LOGGING]: 0 10 11:06:40.851 [planetinfo.record]: seed = 204 4 91 28 11:06:40.851 [planetinfo.record]: coordinates = 4 238 11:06:40.851 [planetinfo.record]: government = 1; 11:06:40.851 [planetinfo.record]: economy = 6; 11:06:40.852 [planetinfo.record]: techlevel = 2; 11:06:40.852 [planetinfo.record]: population = 16; 11:06:40.852 [planetinfo.record]: productivity = 2560; 11:06:40.852 [planetinfo.record]: name = "Teaatis"; 11:06:40.852 [planetinfo.record]: inhabitant = "Human Colonial"; 11:06:40.852 [planetinfo.record]: inhabitants = "Human Colonials"; 11:06:40.852 [planetinfo.record]: description = "Teaatis is mildly well known for Teaatisian vicious brew."; 11:06:40.868 [planetinfo.record]: air_color = 0.435444, 0.599129, 0.909246, 1; 11:06:40.868 [planetinfo.record]: cloud_alpha = 1.000000; 11:06:40.868 [planetinfo.record]: cloud_color = 0.0256805, 0.730469, 0.71395, 1; 11:06:40.868 [planetinfo.record]: cloud_fraction = 0.570000; 11:06:40.868 [planetinfo.record]: land_color = 0.66, 0.0670312, 0.414474, 1; 11:06:40.868 [planetinfo.record]: land_fraction = 0.560000; 11:06:40.868 [planetinfo.record]: polar_cloud_color = 0.328976, 0.828711, 0.816998, 1; 11:06:40.868 [planetinfo.record]: polar_land_color = 0.934, 0.724215, 0.847136, 1; 11:06:40.868 [planetinfo.record]: polar_sea_color = 0.937695, 0.929211, 0.883393, 1; 11:06:40.868 [planetinfo.record]: sea_color = 0.623047, 0.600496, 0.478724, 1; 11:06:40.868 [planetinfo.record]: rotation_speed = 0.003646 11:06:40.868 [planetinfo.record]: planet zpos = 824880.000000 11:06:40.868 [planetinfo.record]: sun_radius = 161673.977051 11:06:40.868 [planetinfo.record]: sun_vector = 0.392 0.276 0.877 11:06:40.868 [planetinfo.record]: sun_distance = 1296240 11:06:40.868 [planetinfo.record]: corona_flare = 0.015961 11:06:40.868 [planetinfo.record]: corona_hues = 0.769394 11:06:40.868 [planetinfo.record]: sun_color = 0.711258, 0.439991, 0.380102, 1 11:06:40.868 [planetinfo.record]: corona_shimmer = 0.405304 11:06:40.868 [planetinfo.record]: station_vector = 0.899 -0.030 0.437 11:06:40.868 [planetinfo.record]: station = coriolis 11:06:45.110 [PLANETINFO OVER]: Done 11:06:45.111 [PLANETINFO LOGGING]: 0 11 11:06:46.130 [planetinfo.record]: seed = 100 93 109 158 11:06:46.130 [planetinfo.record]: coordinates = 93 49 11:06:46.130 [planetinfo.record]: government = 4; 11:06:46.130 [planetinfo.record]: economy = 1; 11:06:46.130 [planetinfo.record]: techlevel = 9; 11:06:46.130 [planetinfo.record]: population = 42; 11:06:46.130 [planetinfo.record]: productivity = 24192; 11:06:46.130 [planetinfo.record]: name = "Riinus"; 11:06:46.130 [planetinfo.record]: inhabitant = "Human Colonial"; 11:06:46.130 [planetinfo.record]: inhabitants = "Human Colonials"; 11:06:46.130 [planetinfo.record]: description = "This world is mildly famous for its vast rain forests and the Riinusian tree grub."; 11:06:46.144 [planetinfo.record]: air_color = 0.724136, 0.716409, 0.981439, 1; 11:06:46.144 [planetinfo.record]: cloud_alpha = 1.000000; 11:06:46.144 [planetinfo.record]: cloud_color = 0.767745, 0.747452, 0.947266, 1; 11:06:46.144 [planetinfo.record]: cloud_fraction = 0.340000; 11:06:46.144 [planetinfo.record]: land_color = 0.528516, 0.552142, 0.66, 1; 11:06:46.144 [planetinfo.record]: land_fraction = 0.360000; 11:06:46.144 [planetinfo.record]: polar_cloud_color = 0.816556, 0.804154, 0.92627, 1; 11:06:46.144 [planetinfo.record]: polar_land_color = 0.887482, 0.895841, 0.934, 1; 11:06:46.144 [planetinfo.record]: polar_sea_color = 0.938086, 0.888365, 0.87689, 1; 11:06:46.144 [planetinfo.record]: sea_color = 0.619141, 0.487876, 0.457584, 1; 11:06:46.144 [planetinfo.record]: rotation_speed = 0.001766 11:06:46.144 [planetinfo.record]: planet zpos = 779160.000000 11:06:46.144 [planetinfo.record]: sun_radius = 186570.711670 11:06:46.144 [planetinfo.record]: sun_vector = -0.637 0.438 0.634 11:06:46.144 [planetinfo.record]: sun_distance = 1493390 11:06:46.144 [planetinfo.record]: corona_flare = 0.023495 11:06:46.144 [planetinfo.record]: corona_hues = 0.594597 11:06:46.144 [planetinfo.record]: sun_color = 0.47652, 0.684685, 0.705454, 1 11:06:46.144 [planetinfo.record]: corona_shimmer = 0.346704 11:06:46.144 [planetinfo.record]: station_vector = -0.187 0.970 0.154 11:06:46.144 [planetinfo.record]: station = coriolis 11:06:50.819 [PLANETINFO OVER]: Done 11:06:50.820 [PLANETINFO LOGGING]: 0 12 11:06:51.825 [planetinfo.record]: seed = 16 244 179 73 11:06:51.825 [planetinfo.record]: coordinates = 244 40 11:06:51.825 [planetinfo.record]: government = 2; 11:06:51.825 [planetinfo.record]: economy = 0; 11:06:51.825 [planetinfo.record]: techlevel = 8; 11:06:51.825 [planetinfo.record]: population = 35; 11:06:51.825 [planetinfo.record]: productivity = 16800; 11:06:51.825 [planetinfo.record]: name = "Esbiza"; 11:06:51.825 [planetinfo.record]: inhabitant = "Small Yellow Fat Humanoid"; 11:06:51.825 [planetinfo.record]: inhabitants = "Small Yellow Fat Humanoids"; 11:06:51.825 [planetinfo.record]: description = "The planet Esbiza is most famous for its vast rain forests."; 11:06:51.835 [planetinfo.record]: air_color = 0.796892, 0.745189, 0.984041, 1; 11:06:51.836 [planetinfo.record]: cloud_alpha = 1.000000; 11:06:51.836 [planetinfo.record]: cloud_color = 0.913719, 0.831963, 0.955078, 1; 11:06:51.836 [planetinfo.record]: cloud_fraction = 0.110000; 11:06:51.836 [planetinfo.record]: land_color = 0.501586, 0.66, 0.291328, 1; 11:06:51.836 [planetinfo.record]: land_fraction = 0.370000; 11:06:51.836 [planetinfo.record]: polar_cloud_color = 0.90462, 0.854876, 0.929785, 1; 11:06:51.836 [planetinfo.record]: polar_land_color = 0.877955, 0.934, 0.803568, 1; 11:06:51.836 [planetinfo.record]: polar_sea_color = 0.94375, 0.927244, 0.895733, 1; 11:06:51.836 [planetinfo.record]: sea_color = 0.5625, 0.523148, 0.448022, 1; 11:06:51.836 [planetinfo.record]: rotation_speed = 0.001672 11:06:51.836 [planetinfo.record]: planet zpos = 804600.000000 11:06:51.837 [planetinfo.record]: sun_radius = 116348.774414 11:06:51.837 [planetinfo.record]: sun_vector = -0.232 -0.650 -0.723 11:06:51.837 [planetinfo.record]: sun_distance = 1126440 11:06:51.837 [planetinfo.record]: corona_flare = 0.017609 11:06:51.837 [planetinfo.record]: corona_hues = 0.945900 11:06:51.837 [planetinfo.record]: sun_color = 0.838211, 0.70909, 0.695905, 1 11:06:51.837 [planetinfo.record]: corona_shimmer = 0.386124 11:06:51.837 [planetinfo.record]: station_vector = -0.004 -0.331 0.944 11:06:51.837 [planetinfo.record]: station = coriolis 11:06:57.634 [PLANETINFO OVER]: Done 11:06:57.634 [PLANETINFO LOGGING]: 0 13 11:06:58.638 [planetinfo.record]: seed = 112 84 45 95 11:06:58.638 [planetinfo.record]: coordinates = 84 164 11:06:58.638 [planetinfo.record]: government = 6; 11:06:58.638 [planetinfo.record]: economy = 4; 11:06:58.638 [planetinfo.record]: techlevel = 6; 11:06:58.639 [planetinfo.record]: population = 35; 11:06:58.639 [planetinfo.record]: productivity = 16800; 11:06:58.639 [planetinfo.record]: name = "Ontimaxe"; 11:06:58.639 [planetinfo.record]: inhabitant = "Human Colonial"; 11:06:58.639 [planetinfo.record]: inhabitants = "Human Colonials"; 11:06:58.639 [planetinfo.record]: description = "This planet is reasonably famous for its inhabitants’ exceptional love for food blenders."; 11:06:58.651 [planetinfo.record]: air_color = 0.455862, 0.749097, 0.846158, 1; 11:06:58.651 [planetinfo.record]: cloud_alpha = 1.000000; 11:06:58.651 [planetinfo.record]: cloud_color = 0.219111, 0.541016, 0.116234, 1; 11:06:58.651 [planetinfo.record]: cloud_fraction = 0.370000; 11:06:58.651 [planetinfo.record]: land_color = 0.128705, 0.0979688, 0.66, 1; 11:06:58.651 [planetinfo.record]: land_fraction = 0.600000; 11:06:58.651 [planetinfo.record]: polar_cloud_color = 0.466983, 0.743457, 0.378626, 1; 11:06:58.651 [planetinfo.record]: polar_land_color = 0.746034, 0.73516, 0.934, 1; 11:06:58.651 [planetinfo.record]: polar_sea_color = 0.898672, 0.93418, 0.885829, 1; 11:06:58.651 [planetinfo.record]: sea_color = 0.558131, 0.658203, 0.521935, 1; 11:06:58.651 [planetinfo.record]: rotation_speed = 0.004704 11:06:58.651 [planetinfo.record]: planet zpos = 741400.000000 11:06:58.652 [planetinfo.record]: sun_radius = 135093.106079 11:06:58.652 [planetinfo.record]: sun_vector = -0.729 -0.417 0.544 11:06:58.652 [planetinfo.record]: sun_distance = 1348000 11:06:58.652 [planetinfo.record]: corona_flare = 0.071040 11:06:58.652 [planetinfo.record]: corona_hues = 0.676010 11:06:58.652 [planetinfo.record]: sun_color = 0.691145, 0.715044, 0.7409, 1 11:06:58.652 [planetinfo.record]: corona_shimmer = 0.367465 11:06:58.652 [planetinfo.record]: station_vector = -0.767 0.611 0.198 11:06:58.652 [planetinfo.record]: station = coriolis 11:07:03.477 [PLANETINFO OVER]: Done 11:07:03.477 [PLANETINFO LOGGING]: 0 14 11:07:04.497 [planetinfo.record]: seed = 132 194 219 37 11:07:04.497 [planetinfo.record]: coordinates = 194 11 11:07:04.497 [planetinfo.record]: government = 0; 11:07:04.497 [planetinfo.record]: economy = 3; 11:07:04.497 [planetinfo.record]: techlevel = 6; 11:07:04.497 [planetinfo.record]: population = 28; 11:07:04.497 [planetinfo.record]: productivity = 6272; 11:07:04.497 [planetinfo.record]: name = "Cebetela"; 11:07:04.497 [planetinfo.record]: inhabitant = "Fierce Red Bug-Eyed Lizard"; 11:07:04.497 [planetinfo.record]: inhabitants = "Fierce Red Bug-Eyed Lizards"; 11:07:04.497 [planetinfo.record]: description = "This world is most notable for its fabulous Cebetelaian lethal brandy but scourged by killer mountain Esbionoids."; 11:07:04.520 [planetinfo.record]: air_color = 0.510164, 0.970383, 0.819035, 1; 11:07:04.520 [planetinfo.record]: cloud_alpha = 1.000000; 11:07:04.520 [planetinfo.record]: cloud_color = 0.914062, 0.178333, 0.160675, 1; 11:07:04.520 [planetinfo.record]: cloud_fraction = 0.310000; 11:07:04.520 [planetinfo.record]: land_color = 0.0438281, 0.501143, 0.66, 1; 11:07:04.520 [planetinfo.record]: land_fraction = 0.630000; 11:07:04.520 [planetinfo.record]: polar_cloud_color = 0.911328, 0.452872, 0.44187, 1; 11:07:04.520 [planetinfo.record]: polar_land_color = 0.716006, 0.877798, 0.934, 1; 11:07:04.520 [planetinfo.record]: polar_sea_color = 0.933398, 0.906024, 0.876884, 1; 11:07:04.520 [planetinfo.record]: sea_color = 0.666016, 0.587886, 0.504715, 1; 11:07:04.520 [planetinfo.record]: rotation_speed = 0.000867 11:07:04.520 [planetinfo.record]: planet zpos = 514800.000000 11:07:04.520 [planetinfo.record]: sun_radius = 135071.896362 11:07:04.520 [planetinfo.record]: sun_vector = -0.942 -0.277 0.192 11:07:04.520 [planetinfo.record]: sun_distance = 858000 11:07:04.520 [planetinfo.record]: corona_flare = 0.001292 11:07:04.520 [planetinfo.record]: corona_hues = 0.646393 11:07:04.521 [planetinfo.record]: sun_color = 0.749329, 0.637436, 0.406665, 1 11:07:04.521 [planetinfo.record]: corona_shimmer = 0.452766 11:07:04.521 [planetinfo.record]: station_vector = -0.249 -0.844 0.475 11:07:04.521 [planetinfo.record]: station = coriolis 11:07:09.461 [PLANETINFO OVER]: Done 11:07:09.462 [PLANETINFO LOGGING]: 0 15 11:07:10.480 [planetinfo.record]: seed = 236 245 253 197 11:07:10.480 [planetinfo.record]: coordinates = 245 220 11:07:10.480 [planetinfo.record]: government = 5; 11:07:10.480 [planetinfo.record]: economy = 4; 11:07:10.480 [planetinfo.record]: techlevel = 7; 11:07:10.480 [planetinfo.record]: population = 38; 11:07:10.480 [planetinfo.record]: productivity = 16416; 11:07:10.480 [planetinfo.record]: name = "Ceedra"; 11:07:10.480 [planetinfo.record]: inhabitant = "Fierce Bug-Eyed Lizard"; 11:07:10.481 [planetinfo.record]: inhabitants = "Fierce Bug-Eyed Lizards"; 11:07:10.481 [planetinfo.record]: description = "This planet is most fabled for its inhabitants’ ingrained silliness but scourged by deadly civil war."; 11:07:10.496 [planetinfo.record]: air_color = 0.651782, 0.883068, 0.91575, 1; 11:07:10.496 [planetinfo.record]: cloud_alpha = 1.000000; 11:07:10.496 [planetinfo.record]: cloud_color = 0.673737, 0.75, 0.550781, 1; 11:07:10.496 [planetinfo.record]: cloud_fraction = 0.310000; 11:07:10.496 [planetinfo.record]: land_color = 0.259028, 0.554688, 0.0931702, 1; 11:07:10.496 [planetinfo.record]: land_fraction = 0.670000; 11:07:10.496 [planetinfo.record]: polar_cloud_color = 0.784274, 0.8375, 0.698462, 1; 11:07:10.496 [planetinfo.record]: polar_land_color = 0.818668, 0.944531, 0.748061, 1; 11:07:10.496 [planetinfo.record]: polar_sea_color = 0.95, 0.898603, 0.915468, 1; 11:07:10.496 [planetinfo.record]: sea_color = 0.5, 0.391797, 0.427301, 1; 11:07:10.496 [planetinfo.record]: rotation_speed = 0.003995 11:07:10.496 [planetinfo.record]: planet zpos = 564330.000000 11:07:10.496 [planetinfo.record]: sun_radius = 85436.942139 11:07:10.496 [planetinfo.record]: sun_vector = 0.154 0.163 -0.975 11:07:10.496 [planetinfo.record]: sun_distance = 737970 11:07:10.496 [planetinfo.record]: corona_flare = 0.020139 11:07:10.496 [planetinfo.record]: corona_hues = 0.783577 11:07:10.496 [planetinfo.record]: sun_color = 0.756821, 0.507581, 0.451178, 1 11:07:10.497 [planetinfo.record]: corona_shimmer = 0.609308 11:07:10.497 [planetinfo.record]: station_vector = -0.815 0.561 0.143 11:07:10.497 [planetinfo.record]: station = coriolis 11:07:14.678 [PLANETINFO OVER]: Done 11:07:14.679 [PLANETINFO LOGGING]: 0 16 11:07:15.684 [planetinfo.record]: seed = 40 178 83 158 11:07:15.684 [planetinfo.record]: coordinates = 178 84 11:07:15.684 [planetinfo.record]: government = 5; 11:07:15.684 [planetinfo.record]: economy = 4; 11:07:15.684 [planetinfo.record]: techlevel = 8; 11:07:15.684 [planetinfo.record]: population = 42; 11:07:15.684 [planetinfo.record]: productivity = 18144; 11:07:15.684 [planetinfo.record]: name = "Rizala"; 11:07:15.684 [planetinfo.record]: inhabitant = "Human Colonial"; 11:07:15.684 [planetinfo.record]: inhabitants = "Human Colonials"; 11:07:15.684 [planetinfo.record]: description = "The planet Rizala is mildly notable for Rizalaian lethal brandy."; 11:07:15.716 [planetinfo.record]: air_color = 0.671108, 0.726842, 0.991846, 1; 11:07:15.716 [planetinfo.record]: cloud_alpha = 1.000000; 11:07:15.716 [planetinfo.record]: cloud_color = 0.615395, 0.799792, 0.978516, 1; 11:07:15.719 [planetinfo.record]: cloud_fraction = 0.350000; 11:07:15.720 [planetinfo.record]: land_color = 0.487266, 0.66, 0.542595, 1; 11:07:15.720 [planetinfo.record]: land_fraction = 0.560000; 11:07:15.720 [planetinfo.record]: polar_cloud_color = 0.722237, 0.832989, 0.940332, 1; 11:07:15.720 [planetinfo.record]: polar_land_color = 0.872889, 0.934, 0.892463, 1; 11:07:15.720 [planetinfo.record]: polar_sea_color = 0.914718, 0.948633, 0.75131, 1; 11:07:15.720 [planetinfo.record]: sea_color = 0.440214, 0.513672, 0.0862808, 1; 11:07:15.720 [planetinfo.record]: rotation_speed = 0.001388 11:07:15.720 [planetinfo.record]: planet zpos = 986700.000000 11:07:15.720 [planetinfo.record]: sun_radius = 151601.340027 11:07:15.720 [planetinfo.record]: sun_vector = 0.227 0.974 0.029 11:07:15.720 [planetinfo.record]: sun_distance = 1184040 11:07:15.720 [planetinfo.record]: corona_flare = 0.037984 11:07:15.720 [planetinfo.record]: corona_hues = 0.599327 11:07:15.720 [planetinfo.record]: sun_color = 0.848245, 0.777289, 0.490596, 1 11:07:15.721 [planetinfo.record]: corona_shimmer = 0.240501 11:07:15.721 [planetinfo.record]: station_vector = 0.638 0.645 0.421 11:07:15.721 [planetinfo.record]: station = coriolis 11:07:20.237 [PLANETINFO OVER]: Done 11:07:20.238 [PLANETINFO LOGGING]: 0 17 11:07:21.241 [planetinfo.record]: seed = 216 55 93 209 11:07:21.241 [planetinfo.record]: coordinates = 55 245 11:07:21.241 [planetinfo.record]: government = 3; 11:07:21.241 [planetinfo.record]: economy = 5; 11:07:21.241 [planetinfo.record]: techlevel = 7; 11:07:21.241 [planetinfo.record]: population = 37; 11:07:21.241 [planetinfo.record]: productivity = 10360; 11:07:21.241 [planetinfo.record]: name = "Atriso"; 11:07:21.242 [planetinfo.record]: inhabitant = "Human Colonial"; 11:07:21.242 [planetinfo.record]: inhabitants = "Human Colonials"; 11:07:21.242 [planetinfo.record]: description = "Atriso is mildly well known for its exotic cuisine and its inhabitants’ ingrained silliness."; 11:07:21.264 [planetinfo.record]: air_color = 0.711287, 0.806334, 0.920303, 1; 11:07:21.264 [planetinfo.record]: cloud_alpha = 1.000000; 11:07:21.264 [planetinfo.record]: cloud_color = 0.701027, 0.763672, 0.741648, 1; 11:07:21.264 [planetinfo.record]: cloud_fraction = 0.400000; 11:07:21.264 [planetinfo.record]: land_color = 0.638672, 0.501457, 0.479004, 1; 11:07:21.264 [planetinfo.record]: land_fraction = 0.330000; 11:07:21.264 [planetinfo.record]: polar_cloud_color = 0.800399, 0.843652, 0.828446, 1; 11:07:21.264 [planetinfo.record]: polar_land_color = 0.936133, 0.885852, 0.877625, 1; 11:07:21.264 [planetinfo.record]: polar_sea_color = 0.744402, 0.897644, 0.933008, 1; 11:07:21.264 [planetinfo.record]: sea_color = 0.128227, 0.568354, 0.669922, 1; 11:07:21.264 [planetinfo.record]: rotation_speed = 0.004439 11:07:21.264 [planetinfo.record]: planet zpos = 469050.000000 11:07:21.264 [planetinfo.record]: sun_radius = 71377.153168 11:07:21.264 [planetinfo.record]: sun_vector = -0.148 -0.001 -0.989 11:07:21.264 [planetinfo.record]: sun_distance = 594130 11:07:21.264 [planetinfo.record]: corona_flare = 0.086372 11:07:21.264 [planetinfo.record]: corona_hues = 0.562889 11:07:21.264 [planetinfo.record]: sun_color = 0.716061, 0.659283, 0.576121, 1 11:07:21.265 [planetinfo.record]: corona_shimmer = 0.261991 11:07:21.265 [planetinfo.record]: station_vector = -0.089 0.988 0.127 11:07:21.265 [planetinfo.record]: station = coriolis 11:07:26.299 [PLANETINFO OVER]: Done 11:07:26.300 [PLANETINFO LOGGING]: 0 18 11:07:27.303 [planetinfo.record]: seed = 252 214 155 220 11:07:27.303 [planetinfo.record]: coordinates = 214 7 11:07:27.303 [planetinfo.record]: government = 7; 11:07:27.303 [planetinfo.record]: economy = 7; 11:07:27.303 [planetinfo.record]: techlevel = 6; 11:07:27.303 [planetinfo.record]: population = 39; 11:07:27.303 [planetinfo.record]: productivity = 10296; 11:07:27.303 [planetinfo.record]: name = "Teanrebi"; 11:07:27.303 [planetinfo.record]: inhabitant = "Bug-Eyed Frog"; 11:07:27.303 [planetinfo.record]: inhabitants = "Bug-Eyed Frogs"; 11:07:27.303 [planetinfo.record]: description = "This planet is plagued by frequent earthquakes."; 11:07:27.308 [planetinfo.record]: air_color = 0.574459, 0.927457, 0.8279, 1; 11:07:27.308 [planetinfo.record]: cloud_alpha = 1.000000; 11:07:27.308 [planetinfo.record]: cloud_color = 0.785156, 0.428783, 0.358841, 1; 11:07:27.308 [planetinfo.record]: cloud_fraction = 0.140000; 11:07:27.308 [planetinfo.record]: land_color = 0.636719, 0.549628, 0.502411, 1; 11:07:27.308 [planetinfo.record]: land_fraction = 0.590000; 11:07:27.308 [planetinfo.record]: polar_cloud_color = 0.85332, 0.61125, 0.563741, 1; 11:07:27.308 [planetinfo.record]: polar_land_color = 0.936328, 0.90431, 0.886951, 1; 11:07:27.308 [planetinfo.record]: polar_sea_color = 0.757899, 0.937305, 0.92469, 1; 11:07:27.308 [planetinfo.record]: sea_color = 0.146942, 0.626953, 0.593202, 1; 11:07:27.308 [planetinfo.record]: rotation_speed = 0.003140 11:07:27.309 [planetinfo.record]: planet zpos = 854280.000000 11:07:27.309 [planetinfo.record]: sun_radius = 169899.024353 11:07:27.309 [planetinfo.record]: sun_vector = 0.195 0.874 -0.444 11:07:27.309 [planetinfo.record]: sun_distance = 1220400 11:07:27.309 [planetinfo.record]: corona_flare = 0.074188 11:07:27.309 [planetinfo.record]: corona_hues = 0.647072 11:07:27.309 [planetinfo.record]: sun_color = 0.749193, 0.788018, 0.800446, 1 11:07:27.309 [planetinfo.record]: corona_shimmer = 0.367009 11:07:27.309 [planetinfo.record]: station_vector = -0.238 0.798 0.554 11:07:27.309 [planetinfo.record]: station = coriolis 11:07:31.989 [PLANETINFO OVER]: Done 11:07:31.990 [PLANETINFO LOGGING]: 0 19 11:07:32.996 [planetinfo.record]: seed = 52 6 205 47 11:07:32.996 [planetinfo.record]: coordinates = 6 110 11:07:32.996 [planetinfo.record]: government = 6; 11:07:32.996 [planetinfo.record]: economy = 6; 11:07:32.996 [planetinfo.record]: techlevel = 6; 11:07:32.996 [planetinfo.record]: population = 37; 11:07:32.996 [planetinfo.record]: productivity = 11840; 11:07:32.996 [planetinfo.record]: name = "Azaqu"; 11:07:32.996 [planetinfo.record]: inhabitant = "Red Slimy Lobster"; 11:07:32.996 [planetinfo.record]: inhabitants = "Red Slimy Lobsters"; 11:07:32.996 [planetinfo.record]: description = "The planet Azaqu is most famous for its pink oceans and Zero-G cricket."; 11:07:33.008 [planetinfo.record]: air_color = 0.556143, 0.546926, 0.863068, 1; 11:07:33.008 [planetinfo.record]: cloud_alpha = 1.000000; 11:07:33.008 [planetinfo.record]: cloud_color = 0.325951, 0.295898, 0.591797, 1; 11:07:33.015 [planetinfo.record]: cloud_fraction = 0.420000; 11:07:33.015 [planetinfo.record]: land_color = 0.198833, 0.599609, 0.0538712, 1; 11:07:33.015 [planetinfo.record]: land_fraction = 0.300000; 11:07:33.016 [planetinfo.record]: polar_cloud_color = 0.551158, 0.526837, 0.766309, 1; 11:07:33.016 [planetinfo.record]: polar_land_color = 0.782959, 0.940039, 0.726143, 1; 11:07:33.016 [planetinfo.record]: polar_sea_color = 0.9, 0.84375, 0.85725, 1; 11:07:33.016 [planetinfo.record]: sea_color = 1, 0.75, 0.81, 1; 11:07:33.016 [planetinfo.record]: rotation_speed = 0.001077 11:07:33.016 [planetinfo.record]: planet zpos = 799440.000000 11:07:33.016 [planetinfo.record]: sun_radius = 179628.810425 11:07:33.016 [planetinfo.record]: sun_vector = 0.206 -0.474 -0.856 11:07:33.016 [planetinfo.record]: sun_distance = 1399020 11:07:33.016 [planetinfo.record]: corona_flare = 0.038925 11:07:33.016 [planetinfo.record]: corona_hues = 0.602066 11:07:33.016 [planetinfo.record]: sun_color = 0.584958, 0.809818, 0.833252, 1 11:07:33.016 [planetinfo.record]: corona_shimmer = 0.350843 11:07:33.017 [planetinfo.record]: station_vector = -0.336 0.915 0.225 11:07:33.017 [planetinfo.record]: station = coriolis 11:07:38.063 [PLANETINFO OVER]: Done 11:07:38.065 [PLANETINFO LOGGING]: 0 20 11:07:39.070 [planetinfo.record]: seed = 0 175 51 46 11:07:39.070 [planetinfo.record]: coordinates = 175 218 11:07:39.070 [planetinfo.record]: government = 0; 11:07:39.070 [planetinfo.record]: economy = 2; 11:07:39.070 [planetinfo.record]: techlevel = 8; 11:07:39.070 [planetinfo.record]: population = 35; 11:07:39.071 [planetinfo.record]: productivity = 8960; 11:07:39.071 [planetinfo.record]: name = "Retila"; 11:07:39.071 [planetinfo.record]: inhabitant = "Human Colonial"; 11:07:39.071 [planetinfo.record]: inhabitants = "Human Colonials"; 11:07:39.071 [planetinfo.record]: description = "This world is ravaged by occasional solar activity."; 11:07:39.075 [planetinfo.record]: air_color = 0.618297, 0.883231, 0.794036, 1; 11:07:39.075 [planetinfo.record]: cloud_alpha = 1.000000; 11:07:39.075 [planetinfo.record]: cloud_color = 0.652344, 0.456131, 0.456131, 1; 11:07:39.075 [planetinfo.record]: cloud_fraction = 0.240000; 11:07:39.075 [planetinfo.record]: land_color = 0.56606, 0.113438, 0.66, 1; 11:07:39.075 [planetinfo.record]: land_fraction = 0.530000; 11:07:39.075 [planetinfo.record]: polar_cloud_color = 0.793555, 0.644376, 0.644376, 1; 11:07:39.075 [planetinfo.record]: polar_land_color = 0.900765, 0.740633, 0.934, 1; 11:07:39.076 [planetinfo.record]: polar_sea_color = 0.927246, 0.93418, 0.878713, 1; 11:07:39.076 [planetinfo.record]: sea_color = 0.638663, 0.658203, 0.50188, 1; 11:07:39.076 [planetinfo.record]: rotation_speed = 0.001024 11:07:39.076 [planetinfo.record]: planet zpos = 723250.000000 11:07:39.076 [planetinfo.record]: sun_radius = 183766.113281 11:07:39.076 [planetinfo.record]: sun_vector = 0.999 -0.039 -0.022 11:07:39.076 [planetinfo.record]: sun_distance = 1446500 11:07:39.076 [planetinfo.record]: corona_flare = 0.040916 11:07:39.076 [planetinfo.record]: corona_hues = 0.714066 11:07:39.076 [planetinfo.record]: sun_color = 0.714352, 0.776033, 0.812781, 1 11:07:39.076 [planetinfo.record]: corona_shimmer = 0.360290 11:07:39.076 [planetinfo.record]: station_vector = 0.899 0.078 0.431 11:07:39.077 [planetinfo.record]: station = coriolis 11:07:44.539 [PLANETINFO OVER]: Done 11:07:44.540 [PLANETINFO LOGGING]: 0 21 11:07:45.544 [planetinfo.record]: seed = 0 123 205 199 11:07:45.544 [planetinfo.record]: coordinates = 123 149 11:07:45.544 [planetinfo.record]: government = 0; 11:07:45.544 [planetinfo.record]: economy = 7; 11:07:45.544 [planetinfo.record]: techlevel = 3; 11:07:45.544 [planetinfo.record]: population = 20; 11:07:45.544 [planetinfo.record]: productivity = 1920; 11:07:45.544 [planetinfo.record]: name = "Sotiqu"; 11:07:45.544 [planetinfo.record]: inhabitant = "Fierce Frog"; 11:07:45.544 [planetinfo.record]: inhabitants = "Fierce Frogs"; 11:07:45.544 [planetinfo.record]: description = "The planet Sotiqu is famous for its exotic goat soup but ravaged by killer disease."; 11:07:45.593 [planetinfo.record]: air_color = 0.533671, 0.468421, 0.939814, 1; 11:07:45.594 [planetinfo.record]: cloud_alpha = 1.000000; 11:07:45.594 [planetinfo.record]: cloud_color = 0.397281, 0.0770874, 0.822266, 1; 11:07:45.594 [planetinfo.record]: cloud_fraction = 0.080000; 11:07:45.594 [planetinfo.record]: land_color = 0.644531, 0.455704, 0.460129, 1; 11:07:45.594 [planetinfo.record]: land_fraction = 0.420000; 11:07:45.594 [planetinfo.record]: polar_cloud_color = 0.588978, 0.377235, 0.87002, 1; 11:07:45.594 [planetinfo.record]: polar_land_color = 0.935547, 0.867025, 0.868631, 1; 11:07:45.594 [planetinfo.record]: polar_sea_color = 0.949023, 0.771632, 0.717328, 1; 11:07:45.594 [planetinfo.record]: sea_color = 0.509766, 0.128624, 0.0119476, 1; 11:07:45.594 [planetinfo.record]: rotation_speed = 0.004133 11:07:45.594 [planetinfo.record]: planet zpos = 567720.000000 11:07:45.594 [planetinfo.record]: sun_radius = 134358.061066 11:07:45.594 [planetinfo.record]: sun_vector = 0.676 0.689 -0.262 11:07:45.594 [planetinfo.record]: sun_distance = 993510 11:07:45.594 [planetinfo.record]: corona_flare = 0.080800 11:07:45.594 [planetinfo.record]: corona_hues = 0.803818 11:07:45.594 [planetinfo.record]: sun_color = 0.663593, 0.411385, 0.285864, 1 11:07:45.595 [planetinfo.record]: corona_shimmer = 0.547557 11:07:45.595 [planetinfo.record]: station_vector = 0.955 -0.269 0.121 11:07:45.595 [planetinfo.record]: station = coriolis 11:07:50.306 [PLANETINFO OVER]: Done 11:07:50.307 [PLANETINFO LOGGING]: 0 22 11:07:51.310 [planetinfo.record]: seed = 52 186 155 172 11:07:51.310 [planetinfo.record]: coordinates = 186 26 11:07:51.310 [planetinfo.record]: government = 6; 11:07:51.310 [planetinfo.record]: economy = 2; 11:07:51.310 [planetinfo.record]: techlevel = 10; 11:07:51.310 [planetinfo.record]: population = 49; 11:07:51.310 [planetinfo.record]: productivity = 31360; 11:07:51.310 [planetinfo.record]: name = "Inleus"; 11:07:51.310 [planetinfo.record]: inhabitant = "Harmless Slimy Rodent"; 11:07:51.310 [planetinfo.record]: inhabitants = "Harmless Slimy Rodents"; 11:07:51.310 [planetinfo.record]: description = "The world Inleus is most famous for the Inleusian spotted wolf."; 11:07:51.313 [planetinfo.record]: air_color = 0.675126, 0.84793, 0.9151, 1; 11:07:51.313 [planetinfo.record]: cloud_alpha = 1.000000; 11:07:51.313 [planetinfo.record]: cloud_color = 0.628608, 0.748047, 0.607788, 1; 11:07:51.313 [planetinfo.record]: cloud_fraction = 0.210000; 11:07:51.313 [planetinfo.record]: land_color = 0.638672, 0.436592, 0.488691, 1; 11:07:51.313 [planetinfo.record]: land_fraction = 0.310000; 11:07:51.313 [planetinfo.record]: polar_cloud_color = 0.753133, 0.836621, 0.73858, 1; 11:07:51.313 [planetinfo.record]: polar_land_color = 0.936133, 0.862083, 0.881174, 1; 11:07:51.314 [planetinfo.record]: polar_sea_color = 0.933984, 0.869511, 0.862841, 1; 11:07:51.314 [planetinfo.record]: sea_color = 0.660156, 0.477872, 0.459015, 1; 11:07:51.314 [planetinfo.record]: rotation_speed = 0.000302 11:07:51.314 [planetinfo.record]: planet zpos = 546660.000000 11:07:51.314 [planetinfo.record]: sun_radius = 174161.310120 11:07:51.314 [planetinfo.record]: sun_vector = 0.388 -0.522 -0.760 11:07:51.314 [planetinfo.record]: sun_distance = 1214800 11:07:51.314 [planetinfo.record]: corona_flare = 0.099280 11:07:51.314 [planetinfo.record]: corona_hues = 0.813416 11:07:51.314 [planetinfo.record]: sun_color = 0.278227, 0.651899, 0.664398, 1 11:07:51.314 [planetinfo.record]: corona_shimmer = 0.288466 11:07:51.314 [planetinfo.record]: station_vector = 0.881 0.398 0.256 11:07:51.314 [planetinfo.record]: station = coriolis 11:07:55.601 [PLANETINFO OVER]: Done 11:07:55.602 [PLANETINFO LOGGING]: 0 23 11:07:56.604 [planetinfo.record]: seed = 60 22 221 127 11:07:56.604 [planetinfo.record]: coordinates = 22 232 11:07:56.604 [planetinfo.record]: government = 7; 11:07:56.604 [planetinfo.record]: economy = 0; 11:07:56.605 [planetinfo.record]: techlevel = 13; 11:07:56.605 [planetinfo.record]: population = 60; 11:07:56.605 [planetinfo.record]: productivity = 52800; 11:07:56.605 [planetinfo.record]: name = "Onrira"; 11:07:56.605 [planetinfo.record]: inhabitant = "Blue Frog"; 11:07:56.605 [planetinfo.record]: inhabitants = "Blue Frogs"; 11:07:56.605 [planetinfo.record]: description = "The world Onrira is mildly noted for the Onriraian deadly Esonatoid but plagued by lethal spotted yaks."; 11:07:56.620 [planetinfo.record]: air_color = 0.693216, 0.735964, 0.977537, 1; 11:07:56.620 [planetinfo.record]: cloud_alpha = 1.000000; 11:07:56.620 [planetinfo.record]: cloud_color = 0.679733, 0.791652, 0.935547, 1; 11:07:56.620 [planetinfo.record]: cloud_fraction = 0.630000; 11:07:56.620 [planetinfo.record]: land_color = 0.66, 0.57045, 0.219141, 1; 11:07:56.620 [planetinfo.record]: land_fraction = 0.300000; 11:07:56.620 [planetinfo.record]: polar_cloud_color = 0.763599, 0.83246, 0.920996, 1; 11:07:56.620 [planetinfo.record]: polar_land_color = 0.934, 0.902318, 0.778029, 1; 11:07:56.620 [planetinfo.record]: polar_sea_color = 0.944336, 0.887713, 0.894348, 1; 11:07:56.620 [planetinfo.record]: sea_color = 0.556641, 0.423134, 0.438779, 1; 11:07:56.620 [planetinfo.record]: rotation_speed = 0.003303 11:07:56.620 [planetinfo.record]: planet zpos = 801360.000000 11:07:56.620 [planetinfo.record]: sun_radius = 156695.984802 11:07:56.620 [planetinfo.record]: sun_vector = -0.661 0.746 -0.077 11:07:56.620 [planetinfo.record]: sun_distance = 1335600 11:07:56.620 [planetinfo.record]: corona_flare = 0.086743 11:07:56.620 [planetinfo.record]: corona_hues = 0.994728 11:07:56.620 [planetinfo.record]: sun_color = 0.548853, 0.729793, 0.754706, 1 11:07:56.621 [planetinfo.record]: corona_shimmer = 0.596248 11:07:56.621 [planetinfo.record]: station_vector = 0.486 0.771 0.412 11:07:56.621 [planetinfo.record]: station = dodecahedron 11:08:01.376 [PLANETINFO OVER]: Done 11:08:01.377 [PLANETINFO LOGGING]: 0 24 11:08:02.381 [planetinfo.record]: seed = 152 18 83 165 11:08:02.381 [planetinfo.record]: coordinates = 18 20 11:08:02.381 [planetinfo.record]: government = 3; 11:08:02.381 [planetinfo.record]: economy = 4; 11:08:02.381 [planetinfo.record]: techlevel = 7; 11:08:02.381 [planetinfo.record]: population = 36; 11:08:02.382 [planetinfo.record]: productivity = 12096; 11:08:02.382 [planetinfo.record]: name = "Ceinzala"; 11:08:02.382 [planetinfo.record]: inhabitant = "Human Colonial"; 11:08:02.382 [planetinfo.record]: inhabitants = "Human Colonials"; 11:08:02.382 [planetinfo.record]: description = "This planet is most notable for vicious Inzalodi gargle blasters but scourged by unpredictable earthquakes."; 11:08:02.391 [planetinfo.record]: air_color = 0.538454, 0.464548, 0.944367, 1; 11:08:02.392 [planetinfo.record]: cloud_alpha = 1.000000; 11:08:02.392 [planetinfo.record]: cloud_color = 0.430852, 0.0620422, 0.835938, 1; 11:08:02.392 [planetinfo.record]: cloud_fraction = 0.500000; 11:08:02.392 [planetinfo.record]: land_color = 0.281677, 0.417712, 0.507812, 1; 11:08:02.392 [planetinfo.record]: land_fraction = 0.630000; 11:08:02.392 [planetinfo.record]: polar_cloud_color = 0.610808, 0.369207, 0.876172, 1; 11:08:02.392 [planetinfo.record]: polar_land_color = 0.843544, 0.907114, 0.949219, 1; 11:08:02.392 [planetinfo.record]: polar_sea_color = 0.925, 0.923786, 0.847314, 1; 11:08:02.392 [planetinfo.record]: sea_color = 0.75, 0.746063, 0.498047, 1; 11:08:02.392 [planetinfo.record]: rotation_speed = 0.000742 11:08:02.392 [planetinfo.record]: planet zpos = 452540.000000 11:08:02.392 [planetinfo.record]: sun_radius = 101328.342285 11:08:02.392 [planetinfo.record]: sun_vector = -0.401 -0.217 -0.890 11:08:02.393 [planetinfo.record]: sun_distance = 863940 11:08:02.393 [planetinfo.record]: corona_flare = 0.064320 11:08:02.393 [planetinfo.record]: corona_hues = 0.919098 11:08:02.393 [planetinfo.record]: sun_color = 0.444677, 0.634466, 0.780072, 1 11:08:02.393 [planetinfo.record]: corona_shimmer = 0.372241 11:08:02.393 [planetinfo.record]: station_vector = -0.856 0.304 0.418 11:08:02.393 [planetinfo.record]: station = coriolis 11:08:07.249 [PLANETINFO OVER]: Done 11:08:07.250 [PLANETINFO LOGGING]: 0 25 11:08:08.255 [planetinfo.record]: seed = 232 245 125 70 11:08:08.255 [planetinfo.record]: coordinates = 245 132 11:08:08.255 [planetinfo.record]: government = 5; 11:08:08.255 [planetinfo.record]: economy = 4; 11:08:08.255 [planetinfo.record]: techlevel = 7; 11:08:08.255 [planetinfo.record]: population = 38; 11:08:08.255 [planetinfo.record]: productivity = 16416; 11:08:08.255 [planetinfo.record]: name = "Biisza"; 11:08:08.255 [planetinfo.record]: inhabitant = "Human Colonial"; 11:08:08.256 [planetinfo.record]: inhabitants = "Human Colonials"; 11:08:08.256 [planetinfo.record]: description = "The planet Biisza is most famous for its vast rain forests."; 11:08:08.280 [planetinfo.record]: air_color = 0.587346, 0.928758, 0.864444, 1; 11:08:08.280 [planetinfo.record]: cloud_alpha = 1.000000; 11:08:08.280 [planetinfo.record]: cloud_color = 0.789062, 0.568511, 0.391449, 1; 11:08:08.280 [planetinfo.record]: cloud_fraction = 0.250000; 11:08:08.280 [planetinfo.record]: land_color = 0.490247, 0.66, 0.154688, 1; 11:08:08.280 [planetinfo.record]: land_fraction = 0.360000; 11:08:08.280 [planetinfo.record]: polar_cloud_color = 0.855078, 0.705701, 0.585779, 1; 11:08:08.280 [planetinfo.record]: polar_land_color = 0.873943, 0.934, 0.755227, 1; 11:08:08.281 [planetinfo.record]: polar_sea_color = 0.91958, 0.937109, 0.888332, 1; 11:08:08.281 [planetinfo.record]: sea_color = 0.58185, 0.628906, 0.497966, 1; 11:08:08.281 [planetinfo.record]: rotation_speed = 0.003780 11:08:08.281 [planetinfo.record]: planet zpos = 551640.000000 11:08:08.281 [planetinfo.record]: sun_radius = 78285.641785 11:08:08.281 [planetinfo.record]: sun_vector = -0.088 0.995 0.053 11:08:08.281 [planetinfo.record]: sun_distance = 965370 11:08:08.281 [planetinfo.record]: corona_flare = 0.087514 11:08:08.281 [planetinfo.record]: corona_hues = 0.858398 11:08:08.281 [planetinfo.record]: sun_color = 0.540902, 0.75031, 0.764413, 1 11:08:08.281 [planetinfo.record]: corona_shimmer = 0.289521 11:08:08.282 [planetinfo.record]: station_vector = -0.253 0.419 0.872 11:08:08.282 [planetinfo.record]: station = coriolis 11:08:12.917 [PLANETINFO OVER]: Done 11:08:12.918 [PLANETINFO LOGGING]: 0 26 11:08:13.922 [planetinfo.record]: seed = 44 4 219 193 11:08:13.922 [planetinfo.record]: coordinates = 4 253 11:08:13.922 [planetinfo.record]: government = 5; 11:08:13.922 [planetinfo.record]: economy = 5; 11:08:13.922 [planetinfo.record]: techlevel = 5; 11:08:13.922 [planetinfo.record]: population = 31; 11:08:13.922 [planetinfo.record]: productivity = 11160; 11:08:13.922 [planetinfo.record]: name = "Legees"; 11:08:13.922 [planetinfo.record]: inhabitant = "Large Bug-Eyed Lizard"; 11:08:13.922 [planetinfo.record]: inhabitants = "Large Bug-Eyed Lizards"; 11:08:13.922 [planetinfo.record]: description = "This planet is most notable for its exotic night life but ravaged by frequent earthquakes."; 11:08:13.928 [planetinfo.record]: air_color = 0.567331, 0.945958, 0.977537, 1; 11:08:13.929 [planetinfo.record]: cloud_alpha = 1.000000; 11:08:13.929 [planetinfo.record]: cloud_color = 0.78597, 0.935547, 0.31794, 1; 11:08:13.929 [planetinfo.record]: cloud_fraction = 0.290000; 11:08:13.929 [planetinfo.record]: land_color = 0.66, 0.0953906, 0.360051, 1; 11:08:13.929 [planetinfo.record]: land_fraction = 0.300000; 11:08:13.929 [planetinfo.record]: polar_cloud_color = 0.828965, 0.920996, 0.540995, 1; 11:08:13.929 [planetinfo.record]: polar_land_color = 0.934, 0.734248, 0.827882, 1; 11:08:13.929 [planetinfo.record]: polar_sea_color = 0.91929, 0.936914, 0.887873, 1; 11:08:13.930 [planetinfo.record]: sea_color = 0.583391, 0.630859, 0.498773, 1; 11:08:13.930 [planetinfo.record]: rotation_speed = 0.002472 11:08:13.930 [planetinfo.record]: planet zpos = 430640.000000 11:08:13.930 [planetinfo.record]: sun_radius = 69113.311768 11:08:13.930 [planetinfo.record]: sun_vector = -0.062 0.995 0.084 11:08:13.930 [planetinfo.record]: sun_distance = 645960 11:08:13.930 [planetinfo.record]: corona_flare = 0.055644 11:08:13.930 [planetinfo.record]: corona_hues = 0.859482 11:08:13.930 [planetinfo.record]: sun_color = 0.733419, 0.752607, 0.779276, 1 11:08:13.931 [planetinfo.record]: corona_shimmer = 0.430128 11:08:13.931 [planetinfo.record]: station_vector = 0.110 0.952 0.285 11:08:13.931 [planetinfo.record]: station = coriolis 11:08:18.292 [PLANETINFO OVER]: Done 11:08:18.293 [PLANETINFO LOGGING]: 0 27 11:08:19.296 [planetinfo.record]: seed = 4 14 45 90 11:08:19.296 [planetinfo.record]: coordinates = 14 137 11:08:19.296 [planetinfo.record]: government = 0; 11:08:19.296 [planetinfo.record]: economy = 3; 11:08:19.296 [planetinfo.record]: techlevel = 6; 11:08:19.296 [planetinfo.record]: population = 28; 11:08:19.296 [planetinfo.record]: productivity = 6272; 11:08:19.296 [planetinfo.record]: name = "Quator"; 11:08:19.296 [planetinfo.record]: inhabitant = "Human Colonial"; 11:08:19.296 [planetinfo.record]: inhabitants = "Human Colonials"; 11:08:19.296 [planetinfo.record]: description = "The world Quator is scourged by deadly edible arts graduates."; 11:08:19.311 [planetinfo.record]: air_color = 0.647331, 0.863719, 0.806068, 1; 11:08:19.312 [planetinfo.record]: cloud_alpha = 1.000000; 11:08:19.312 [planetinfo.record]: cloud_color = 0.59375, 0.522376, 0.503296, 1; 11:08:19.312 [planetinfo.record]: cloud_fraction = 0.510000; 11:08:19.312 [planetinfo.record]: land_color = 0.631238, 0.134062, 0.66, 1; 11:08:19.312 [planetinfo.record]: land_fraction = 0.320000; 11:08:19.312 [planetinfo.record]: polar_cloud_color = 0.767187, 0.709548, 0.69414, 1; 11:08:19.312 [planetinfo.record]: polar_land_color = 0.923824, 0.74793, 0.934, 1; 11:08:19.313 [planetinfo.record]: polar_sea_color = 0.941211, 0.938201, 0.893047, 1; 11:08:19.313 [planetinfo.record]: sea_color = 0.587891, 0.58037, 0.467557, 1; 11:08:19.313 [planetinfo.record]: rotation_speed = 0.000473 11:08:19.313 [planetinfo.record]: planet zpos = 646800.000000 11:08:19.313 [planetinfo.record]: sun_radius = 126546.073914 11:08:19.313 [planetinfo.record]: sun_vector = 0.569 0.597 -0.566 11:08:19.313 [planetinfo.record]: sun_distance = 1024100 11:08:19.313 [planetinfo.record]: corona_flare = 0.016846 11:08:19.313 [planetinfo.record]: corona_hues = 0.954643 11:08:19.313 [planetinfo.record]: sun_color = 0.536381, 0.544416, 0.729077, 1 11:08:19.313 [planetinfo.record]: corona_shimmer = 0.431550 11:08:19.314 [planetinfo.record]: station_vector = 0.094 -0.662 0.743 11:08:19.314 [planetinfo.record]: station = coriolis 11:08:23.975 [PLANETINFO OVER]: Done 11:08:23.975 [PLANETINFO LOGGING]: 0 28 11:08:24.980 [planetinfo.record]: seed = 240 164 179 239 11:08:24.980 [planetinfo.record]: coordinates = 164 89 11:08:24.980 [planetinfo.record]: government = 6; 11:08:24.980 [planetinfo.record]: economy = 1; 11:08:24.980 [planetinfo.record]: techlevel = 9; 11:08:24.980 [planetinfo.record]: population = 44; 11:08:24.980 [planetinfo.record]: productivity = 31680; 11:08:24.980 [planetinfo.record]: name = "Arexe"; 11:08:24.980 [planetinfo.record]: inhabitant = "Furry Rodent"; 11:08:24.981 [planetinfo.record]: inhabitants = "Furry Rodents"; 11:08:24.981 [planetinfo.record]: description = "The world Arexe is fabled for its exciting sit coms and its inhabitants’ ancient loathing of sit coms."; 11:08:24.986 [planetinfo.record]: air_color = 0.658997, 0.870223, 0.840324, 1; 11:08:24.986 [planetinfo.record]: cloud_alpha = 1.000000; 11:08:24.986 [planetinfo.record]: cloud_color = 0.613281, 0.580547, 0.534225, 1; 11:08:24.986 [planetinfo.record]: cloud_fraction = 0.510000; 11:08:24.987 [planetinfo.record]: land_color = 0.66, 0.262969, 0.532826, 1; 11:08:24.987 [planetinfo.record]: land_fraction = 0.280000; 11:08:24.987 [planetinfo.record]: polar_cloud_color = 0.775977, 0.75009, 0.713459, 1; 11:08:24.987 [planetinfo.record]: polar_land_color = 0.934, 0.793535, 0.889007, 1; 11:08:24.987 [planetinfo.record]: polar_sea_color = 0.941211, 0.940463, 0.893323, 1; 11:08:24.987 [planetinfo.record]: sea_color = 0.587891, 0.586021, 0.468246, 1; 11:08:24.987 [planetinfo.record]: rotation_speed = 0.000485 11:08:24.987 [planetinfo.record]: planet zpos = 750200.000000 11:08:24.987 [planetinfo.record]: sun_radius = 219838.229370 11:08:24.987 [planetinfo.record]: sun_vector = 0.397 0.909 -0.126 11:08:24.987 [planetinfo.record]: sun_distance = 1364000 11:08:24.987 [planetinfo.record]: corona_flare = 0.093323 11:08:24.987 [planetinfo.record]: corona_hues = 0.660469 11:08:24.987 [planetinfo.record]: sun_color = 0.513365, 0.734486, 0.747351, 1 11:08:24.987 [planetinfo.record]: corona_shimmer = 0.899750 11:08:24.988 [planetinfo.record]: station_vector = -0.695 0.424 0.581 11:08:24.988 [planetinfo.record]: station = coriolis 11:08:29.634 [PLANETINFO OVER]: Done 11:08:29.635 [PLANETINFO LOGGING]: 0 29 11:08:30.640 [planetinfo.record]: seed = 144 96 109 209 11:08:30.640 [planetinfo.record]: coordinates = 96 130 11:08:30.640 [planetinfo.record]: government = 2; 11:08:30.640 [planetinfo.record]: economy = 2; 11:08:30.640 [planetinfo.record]: techlevel = 6; 11:08:30.641 [planetinfo.record]: population = 29; 11:08:30.641 [planetinfo.record]: productivity = 11136; 11:08:30.641 [planetinfo.record]: name = "Atrabiin"; 11:08:30.641 [planetinfo.record]: inhabitant = "Human Colonial"; 11:08:30.641 [planetinfo.record]: inhabitants = "Human Colonials"; 11:08:30.641 [planetinfo.record]: description = "Atrabiin is cursed by killer edible Nuatoids."; 11:08:30.646 [planetinfo.record]: air_color = 0.701583, 0.780821, 0.947619, 1; 11:08:30.646 [planetinfo.record]: cloud_alpha = 1.000000; 11:08:30.646 [planetinfo.record]: cloud_color = 0.693741, 0.83858, 0.845703, 1; 11:08:30.646 [planetinfo.record]: cloud_fraction = 0.420000; 11:08:30.646 [planetinfo.record]: land_color = 0.613594, 0.66, 0.645861, 1; 11:08:30.647 [planetinfo.record]: land_fraction = 0.250000; 11:08:30.647 [planetinfo.record]: polar_cloud_color = 0.781675, 0.875931, 0.880566, 1; 11:08:30.647 [planetinfo.record]: polar_land_color = 0.917582, 0.934, 0.928998, 1; 11:08:30.647 [planetinfo.record]: polar_sea_color = 0.869298, 0.919336, 0.841228, 1; 11:08:30.647 [planetinfo.record]: sea_color = 0.631025, 0.806641, 0.532509, 1; 11:08:30.647 [planetinfo.record]: rotation_speed = 0.003508 11:08:30.647 [planetinfo.record]: planet zpos = 411840.000000 11:08:30.647 [planetinfo.record]: sun_radius = 88328.012695 11:08:30.647 [planetinfo.record]: sun_vector = 0.344 -0.938 -0.042 11:08:30.647 [planetinfo.record]: sun_distance = 633600 11:08:30.647 [planetinfo.record]: corona_flare = 0.055750 11:08:30.647 [planetinfo.record]: corona_hues = 0.912773 11:08:30.647 [planetinfo.record]: sun_color = 0.361829, 0.528898, 0.760806, 1 11:08:30.648 [planetinfo.record]: corona_shimmer = 0.300307 11:08:30.648 [planetinfo.record]: station_vector = 0.188 -0.136 0.973 11:08:30.648 [planetinfo.record]: station = coriolis 11:08:35.674 [PLANETINFO OVER]: Done 11:08:35.675 [PLANETINFO LOGGING]: 0 30 11:08:36.678 [planetinfo.record]: seed = 228 108 91 8 11:08:36.678 [planetinfo.record]: coordinates = 108 230 11:08:36.678 [planetinfo.record]: government = 4; 11:08:36.678 [planetinfo.record]: economy = 6; 11:08:36.678 [planetinfo.record]: techlevel = 3; 11:08:36.678 [planetinfo.record]: population = 23; 11:08:36.678 [planetinfo.record]: productivity = 5888; 11:08:36.678 [planetinfo.record]: name = "Usanat"; 11:08:36.678 [planetinfo.record]: inhabitant = "Human Colonial"; 11:08:36.678 [planetinfo.record]: inhabitants = "Human Colonials"; 11:08:36.678 [planetinfo.record]: description = "The world Usanat is a boring world."; 11:08:36.708 [planetinfo.record]: air_color = 0.760412, 0.598387, 0.887783, 1; 11:08:36.708 [planetinfo.record]: cloud_alpha = 1.000000; 11:08:36.708 [planetinfo.record]: cloud_color = 0.666016, 0.413658, 0.490548, 1; 11:08:36.708 [planetinfo.record]: cloud_fraction = 0.160000; 11:08:36.708 [planetinfo.record]: land_color = 0.580078, 0.531308, 0.514366, 1; 11:08:36.708 [planetinfo.record]: land_fraction = 0.450000; 11:08:36.708 [planetinfo.record]: polar_cloud_color = 0.799707, 0.610323, 0.668026, 1; 11:08:36.708 [planetinfo.record]: polar_land_color = 0.941992, 0.922192, 0.915315, 1; 11:08:36.708 [planetinfo.record]: polar_sea_color = 0.84708, 0.914062, 0.813194, 1; 11:08:36.708 [planetinfo.record]: sea_color = 0.607474, 0.859375, 0.480042, 1; 11:08:36.708 [planetinfo.record]: rotation_speed = 0.004590 11:08:36.708 [planetinfo.record]: planet zpos = 696080.000000 11:08:36.708 [planetinfo.record]: sun_radius = 141691.682129 11:08:36.708 [planetinfo.record]: sun_vector = -0.011 -0.933 0.361 11:08:36.708 [planetinfo.record]: sun_distance = 944680 11:08:36.708 [planetinfo.record]: corona_flare = 0.014404 11:08:36.708 [planetinfo.record]: corona_hues = 0.861115 11:08:36.709 [planetinfo.record]: sun_color = 0.383831, 0.403111, 0.650809, 1 11:08:36.709 [planetinfo.record]: corona_shimmer = 0.429451 11:08:36.709 [planetinfo.record]: station_vector = -0.251 0.219 0.943 11:08:36.709 [planetinfo.record]: station = coriolis 11:08:41.026 [PLANETINFO OVER]: Done 11:08:41.026 [PLANETINFO LOGGING]: 0 31 11:08:42.045 [planetinfo.record]: seed = 140 53 189 98 11:08:42.045 [planetinfo.record]: coordinates = 53 209 11:08:42.045 [planetinfo.record]: government = 1; 11:08:42.045 [planetinfo.record]: economy = 3; 11:08:42.045 [planetinfo.record]: techlevel = 6; 11:08:42.046 [planetinfo.record]: population = 29; 11:08:42.046 [planetinfo.record]: productivity = 8120; 11:08:42.046 [planetinfo.record]: name = "Xeesle"; 11:08:42.046 [planetinfo.record]: inhabitant = "Large Blue Fat Feline"; 11:08:42.046 [planetinfo.record]: inhabitants = "Large Blue Fat Felines"; 11:08:42.046 [planetinfo.record]: description = "The world Xeesle is a boring planet."; 11:08:42.068 [planetinfo.record]: air_color = 0.427738, 0.673349, 0.895588, 1; 11:08:42.068 [planetinfo.record]: cloud_alpha = 1.000000; 11:08:42.068 [planetinfo.record]: cloud_color = 0.0215454, 0.689453, 0.313755, 1; 11:08:42.068 [planetinfo.record]: cloud_fraction = 0.250000; 11:08:42.068 [planetinfo.record]: land_color = 0.66, 0.548617, 0.252656, 1; 11:08:42.068 [planetinfo.record]: land_fraction = 0.310000; 11:08:42.068 [planetinfo.record]: polar_cloud_color = 0.31967, 0.810254, 0.534301, 1; 11:08:42.068 [planetinfo.record]: polar_land_color = 0.934, 0.894594, 0.789887, 1; 11:08:42.068 [planetinfo.record]: polar_sea_color = 0.873545, 0.926953, 0.88189, 1; 11:08:42.068 [planetinfo.record]: sea_color = 0.562119, 0.730469, 0.588423, 1; 11:08:42.068 [planetinfo.record]: rotation_speed = 0.002670 11:08:42.068 [planetinfo.record]: planet zpos = 473340.000000 11:08:42.068 [planetinfo.record]: sun_radius = 85737.880096 11:08:42.068 [planetinfo.record]: sun_vector = 0.468 0.398 0.789 11:08:42.068 [planetinfo.record]: sun_distance = 642390 11:08:42.068 [planetinfo.record]: corona_flare = 0.006587 11:08:42.069 [planetinfo.record]: corona_hues = 0.889587 11:08:42.069 [planetinfo.record]: sun_color = 0.7686, 0.760823, 0.590008, 1 11:08:42.069 [planetinfo.record]: corona_shimmer = 0.968751 11:08:42.069 [planetinfo.record]: station_vector = 0.099 0.186 0.978 11:08:42.069 [planetinfo.record]: station = coriolis 11:08:46.903 [PLANETINFO OVER]: Done 11:08:46.904 [PLANETINFO LOGGING]: 0 32 11:08:47.908 [planetinfo.record]: seed = 8 206 83 57 11:08:47.908 [planetinfo.record]: coordinates = 206 1 11:08:47.908 [planetinfo.record]: government = 1; 11:08:47.908 [planetinfo.record]: economy = 3; 11:08:47.908 [planetinfo.record]: techlevel = 7; 11:08:47.909 [planetinfo.record]: population = 33; 11:08:47.909 [planetinfo.record]: productivity = 9240; 11:08:47.909 [planetinfo.record]: name = "Oreseren"; 11:08:47.909 [planetinfo.record]: inhabitant = "Human Colonial"; 11:08:47.909 [planetinfo.record]: inhabitants = "Human Colonials"; 11:08:47.909 [planetinfo.record]: description = "Oreseren is a revolting little planet."; 11:08:47.923 [planetinfo.record]: air_color = 0.731386, 0.526371, 0.938514, 1; 11:08:47.923 [planetinfo.record]: cloud_alpha = 1.000000; 11:08:47.923 [planetinfo.record]: cloud_color = 0.818359, 0.230164, 0.519666, 1; 11:08:47.924 [planetinfo.record]: cloud_fraction = 0.510000; 11:08:47.924 [planetinfo.record]: land_color = 0.66, 0.172734, 0.640966, 1; 11:08:47.924 [planetinfo.record]: land_fraction = 0.310000; 11:08:47.924 [planetinfo.record]: polar_cloud_color = 0.868262, 0.478222, 0.670195, 1; 11:08:47.924 [planetinfo.record]: polar_land_color = 0.934, 0.761611, 0.927266, 1; 11:08:47.924 [planetinfo.record]: polar_sea_color = 0.94668, 0.899064, 0.895463, 1; 11:08:47.924 [planetinfo.record]: sea_color = 0.533203, 0.425928, 0.417815, 1; 11:08:47.924 [planetinfo.record]: rotation_speed = 0.000096 11:08:47.924 [planetinfo.record]: planet zpos = 585860.000000 11:08:47.924 [planetinfo.record]: sun_radius = 132411.271057 11:08:47.924 [planetinfo.record]: sun_vector = -0.471 0.816 -0.336 11:08:47.924 [planetinfo.record]: sun_distance = 1118460 11:08:47.924 [planetinfo.record]: corona_flare = 0.011723 11:08:47.924 [planetinfo.record]: corona_hues = 0.952454 11:08:47.924 [planetinfo.record]: sun_color = 0.848883, 0.798123, 0.722067, 1 11:08:47.924 [planetinfo.record]: corona_shimmer = 0.444023 11:08:47.925 [planetinfo.record]: station_vector = -0.717 -0.140 0.683 11:08:47.925 [planetinfo.record]: station = coriolis 11:08:52.413 [PLANETINFO OVER]: Done 11:08:52.413 [PLANETINFO LOGGING]: 0 33 11:08:53.419 [planetinfo.record]: seed = 248 82 157 108 11:08:53.419 [planetinfo.record]: coordinates = 82 16 11:08:53.419 [planetinfo.record]: government = 7; 11:08:53.419 [planetinfo.record]: economy = 0; 11:08:53.419 [planetinfo.record]: techlevel = 13; 11:08:53.419 [planetinfo.record]: population = 60; 11:08:53.419 [planetinfo.record]: productivity = 52800; 11:08:53.419 [planetinfo.record]: name = "Inera"; 11:08:53.419 [planetinfo.record]: inhabitant = "Blue Horned Lizard"; 11:08:53.419 [planetinfo.record]: inhabitants = "Blue Horned Lizards"; 11:08:53.419 [planetinfo.record]: description = "This planet is noted for its exotic fish meat."; 11:08:53.428 [planetinfo.record]: air_color = 0.656849, 0.849537, 0.936563, 1; 11:08:53.428 [planetinfo.record]: cloud_alpha = 1.000000; 11:08:53.428 [planetinfo.record]: cloud_color = 0.58448, 0.8125, 0.571289, 1; 11:08:53.428 [planetinfo.record]: cloud_fraction = 0.540000; 11:08:53.428 [planetinfo.record]: land_color = 0.482109, 0.518243, 0.66, 1; 11:08:53.428 [planetinfo.record]: land_fraction = 0.710000; 11:08:53.428 [planetinfo.record]: polar_cloud_color = 0.713795, 0.865625, 0.705011, 1; 11:08:53.428 [planetinfo.record]: polar_land_color = 0.871064, 0.883848, 0.934, 1; 11:08:53.428 [planetinfo.record]: polar_sea_color = 0.934232, 0.937109, 0.884488, 1; 11:08:53.428 [planetinfo.record]: sea_color = 0.621181, 0.628906, 0.487648, 1; 11:08:53.428 [planetinfo.record]: rotation_speed = 0.003147 11:08:53.428 [planetinfo.record]: planet zpos = 835800.000000 11:08:53.428 [planetinfo.record]: sun_radius = 132207.952881 11:08:53.429 [planetinfo.record]: sun_vector = -0.474 -0.352 -0.807 11:08:53.429 [planetinfo.record]: sun_distance = 1194000 11:08:53.429 [planetinfo.record]: corona_flare = 0.080739 11:08:53.429 [planetinfo.record]: corona_hues = 0.737762 11:08:53.429 [planetinfo.record]: sun_color = 0.633503, 0.733461, 0.779367, 1 11:08:53.429 [planetinfo.record]: corona_shimmer = 0.374873 11:08:53.436 [planetinfo.record]: station_vector = -0.109 0.094 0.989 11:08:53.436 [planetinfo.record]: station = icosahedron 11:08:58.437 [PLANETINFO OVER]: Done 11:08:58.438 [PLANETINFO LOGGING]: 0 34 11:08:59.445 [planetinfo.record]: seed = 92 204 27 44 11:08:59.445 [planetinfo.record]: coordinates = 204 143 11:08:59.445 [planetinfo.record]: government = 3; 11:08:59.445 [planetinfo.record]: economy = 7; 11:08:59.445 [planetinfo.record]: techlevel = 2; 11:08:59.445 [planetinfo.record]: population = 19; 11:08:59.446 [planetinfo.record]: productivity = 3192; 11:08:59.446 [planetinfo.record]: name = "Inus"; 11:08:59.446 [planetinfo.record]: inhabitant = "Human Colonial"; 11:08:59.446 [planetinfo.record]: inhabitants = "Human Colonials"; 11:08:59.446 [planetinfo.record]: description = "This world is reasonably well known for the Inusian tree wolf but scourged by unpredictable earthquakes."; 11:08:59.462 [planetinfo.record]: air_color = 0.71229, 0.698885, 0.99835, 1; 11:08:59.464 [planetinfo.record]: cloud_alpha = 1.000000; 11:08:59.464 [planetinfo.record]: cloud_color = 0.742413, 0.697853, 0.998047, 1; 11:08:59.464 [planetinfo.record]: cloud_fraction = 0.210000; 11:08:59.464 [planetinfo.record]: land_color = 0.142645, 0.568359, 0.106567, 1; 11:08:59.464 [planetinfo.record]: land_fraction = 0.320000; 11:08:59.464 [planetinfo.record]: polar_cloud_color = 0.797182, 0.770697, 0.949121, 1; 11:08:59.464 [planetinfo.record]: polar_land_color = 0.766551, 0.943164, 0.751584, 1; 11:08:59.465 [planetinfo.record]: polar_sea_color = 0.947852, 0.894072, 0.909198, 1; 11:08:59.465 [planetinfo.record]: sea_color = 0.521484, 0.403132, 0.436419, 1; 11:08:59.465 [planetinfo.record]: rotation_speed = 0.001808 11:08:59.466 [planetinfo.record]: planet zpos = 670120.000000 11:08:59.466 [planetinfo.record]: sun_radius = 155028.274536 11:08:59.466 [planetinfo.record]: sun_vector = 0.115 -0.471 -0.875 11:08:59.466 [planetinfo.record]: sun_distance = 974720 11:08:59.466 [planetinfo.record]: corona_flare = 0.060123 11:08:59.466 [planetinfo.record]: corona_hues = 0.987480 11:08:59.466 [planetinfo.record]: sun_color = 0.280055, 0.590106, 0.731754, 1 11:08:59.467 [planetinfo.record]: corona_shimmer = 0.671847 11:08:59.467 [planetinfo.record]: station_vector = 0.996 0.071 0.057 11:08:59.467 [planetinfo.record]: station = coriolis 11:09:04.521 [PLANETINFO OVER]: Done 11:09:04.522 [PLANETINFO LOGGING]: 0 35 11:09:06.973 [planetinfo.record]: seed = 212 52 141 61 11:09:06.973 [planetinfo.record]: coordinates = 52 128 11:09:06.973 [planetinfo.record]: government = 2; 11:09:06.973 [planetinfo.record]: economy = 0; 11:09:06.973 [planetinfo.record]: techlevel = 8; 11:09:06.973 [planetinfo.record]: population = 35; 11:09:06.973 [planetinfo.record]: productivity = 16800; 11:09:06.973 [planetinfo.record]: name = "Isence"; 11:09:06.973 [planetinfo.record]: inhabitant = "Red Fat Humanoid"; 11:09:06.973 [planetinfo.record]: inhabitants = "Red Fat Humanoids"; 11:09:06.974 [planetinfo.record]: description = "The world Isence is very famous for its unusual casinos but beset by evil disease."; 11:09:06.990 [planetinfo.record]: air_color = 0.636273, 0.530442, 0.909896, 1; 11:09:06.990 [planetinfo.record]: cloud_alpha = 1.000000; 11:09:06.990 [planetinfo.record]: cloud_color = 0.6615, 0.254631, 0.732422, 1; 11:09:06.990 [planetinfo.record]: cloud_fraction = 0.320000; 11:09:06.990 [planetinfo.record]: land_color = 0.222046, 0.53125, 0.487768, 1; 11:09:06.990 [planetinfo.record]: land_fraction = 0.420000; 11:09:06.990 [planetinfo.record]: polar_cloud_color = 0.779383, 0.491354, 0.82959, 1; 11:09:06.990 [planetinfo.record]: polar_land_color = 0.809097, 0.946875, 0.9275, 1; 11:09:06.990 [planetinfo.record]: polar_sea_color = 0.925, 0.867554, 0.825635, 1; 11:09:06.990 [planetinfo.record]: sea_color = 0.75, 0.56369, 0.427734, 1; 11:09:06.990 [planetinfo.record]: rotation_speed = 0.004840 11:09:06.990 [planetinfo.record]: planet zpos = 805480.000000 11:09:06.991 [planetinfo.record]: sun_radius = 125990.501709 11:09:06.991 [planetinfo.record]: sun_vector = -0.099 0.875 -0.474 11:09:06.991 [planetinfo.record]: sun_distance = 1425080 11:09:06.991 [planetinfo.record]: corona_flare = 0.036688 11:09:06.991 [planetinfo.record]: corona_hues = 0.963715 11:09:06.991 [planetinfo.record]: sun_color = 0.811569, 0.717584, 0.237637, 1 11:09:06.991 [planetinfo.record]: corona_shimmer = 0.340631 11:09:06.991 [planetinfo.record]: station_vector = -0.901 0.139 0.410 11:09:06.991 [planetinfo.record]: station = coriolis 11:09:12.488 [PLANETINFO OVER]: Done 11:09:12.489 [PLANETINFO LOGGING]: 0 36 11:09:13.496 [planetinfo.record]: seed = 224 149 51 238 11:09:13.496 [planetinfo.record]: coordinates = 149 101 11:09:13.496 [planetinfo.record]: government = 4; 11:09:13.496 [planetinfo.record]: economy = 5; 11:09:13.496 [planetinfo.record]: techlevel = 5; 11:09:13.496 [planetinfo.record]: population = 30; 11:09:13.496 [planetinfo.record]: productivity = 9600; 11:09:13.496 [planetinfo.record]: name = "Reesdice"; 11:09:13.496 [planetinfo.record]: inhabitant = "Human Colonial"; 11:09:13.496 [planetinfo.record]: inhabitants = "Human Colonials"; 11:09:13.496 [planetinfo.record]: description = "The world Reesdice is reasonably famous for the Reesdiceian deadly lobstoid."; 11:09:13.528 [planetinfo.record]: air_color = 0.488231, 0.989895, 0.840578, 1; 11:09:13.528 [planetinfo.record]: cloud_alpha = 1.000000; 11:09:13.528 [planetinfo.record]: cloud_color = 0.972656, 0.181067, 0.0759888, 1; 11:09:13.529 [planetinfo.record]: cloud_fraction = 0.310000; 11:09:13.529 [planetinfo.record]: land_color = 0.626484, 0.627794, 0.66, 1; 11:09:13.529 [planetinfo.record]: land_fraction = 0.590000; 11:09:13.529 [planetinfo.record]: polar_cloud_color = 0.937695, 0.460735, 0.397422, 1; 11:09:13.529 [planetinfo.record]: polar_land_color = 0.922143, 0.922606, 0.934, 1; 11:09:13.529 [planetinfo.record]: polar_sea_color = 0.94707, 0.938761, 0.734349, 1; 11:09:13.529 [planetinfo.record]: sea_color = 0.529297, 0.510721, 0.0537567, 1; 11:09:13.529 [planetinfo.record]: rotation_speed = 0.004857 11:09:13.529 [planetinfo.record]: planet zpos = 654900.000000 11:09:13.530 [planetinfo.record]: sun_radius = 183140.362701 11:09:13.530 [planetinfo.record]: sun_vector = 0.552 0.823 0.137 11:09:13.530 [planetinfo.record]: sun_distance = 1375290 11:09:13.530 [planetinfo.record]: corona_flare = 0.014259 11:09:13.530 [planetinfo.record]: corona_hues = 0.609909 11:09:13.530 [planetinfo.record]: sun_color = 0.678311, 0.539322, 0.460887, 1 11:09:13.530 [planetinfo.record]: corona_shimmer = 1.081202 11:09:13.530 [planetinfo.record]: station_vector = 0.035 0.996 0.076 11:09:13.531 [planetinfo.record]: station = coriolis 11:09:17.951 [PLANETINFO OVER]: Done 11:09:17.952 [PLANETINFO LOGGING]: 0 37 11:09:18.974 [planetinfo.record]: seed = 32 69 13 156 11:09:18.974 [planetinfo.record]: coordinates = 69 109 11:09:18.974 [planetinfo.record]: government = 4; 11:09:18.974 [planetinfo.record]: economy = 5; 11:09:18.974 [planetinfo.record]: techlevel = 5; 11:09:18.974 [planetinfo.record]: population = 30; 11:09:18.974 [planetinfo.record]: productivity = 9600; 11:09:18.974 [planetinfo.record]: name = "Terea"; 11:09:18.974 [planetinfo.record]: inhabitant = "Human Colonial"; 11:09:18.974 [planetinfo.record]: inhabitants = "Human Colonials"; 11:09:18.974 [planetinfo.record]: description = "This world is very fabled for the Tereaian edible poet."; 11:09:18.985 [planetinfo.record]: air_color = 0.712579, 0.713135, 0.981439, 1; 11:09:18.985 [planetinfo.record]: cloud_alpha = 1.000000; 11:09:18.985 [planetinfo.record]: cloud_color = 0.737999, 0.736351, 0.947266, 1; 11:09:18.985 [planetinfo.record]: cloud_fraction = 0.280000; 11:09:18.986 [planetinfo.record]: land_color = 0.523359, 0.66, 0.525494, 1; 11:09:18.986 [planetinfo.record]: land_fraction = 0.570000; 11:09:18.986 [planetinfo.record]: polar_cloud_color = 0.798377, 0.79737, 0.92627, 1; 11:09:18.986 [planetinfo.record]: polar_land_color = 0.885658, 0.934, 0.886414, 1; 11:09:18.986 [planetinfo.record]: polar_sea_color = 0.91314, 0.878429, 0.947852, 1; 11:09:18.986 [planetinfo.record]: sea_color = 0.445095, 0.368706, 0.521484, 1; 11:09:18.986 [planetinfo.record]: rotation_speed = 0.002855 11:09:18.986 [planetinfo.record]: planet zpos = 595700.000000 11:09:18.986 [planetinfo.record]: sun_radius = 165699.059753 11:09:18.986 [planetinfo.record]: sun_vector = 0.147 0.756 0.637 11:09:18.986 [planetinfo.record]: sun_distance = 1370110 11:09:18.986 [planetinfo.record]: corona_flare = 0.018314 11:09:18.986 [planetinfo.record]: corona_hues = 0.570892 11:09:18.986 [planetinfo.record]: sun_color = 0.697026, 0.708687, 0.714777, 1 11:09:18.986 [planetinfo.record]: corona_shimmer = 0.448226 11:09:18.987 [planetinfo.record]: station_vector = -0.579 -0.774 0.255 11:09:18.987 [planetinfo.record]: station = coriolis 11:09:23.805 [PLANETINFO OVER]: Done 11:09:23.806 [PLANETINFO LOGGING]: 0 38 11:09:24.811 [planetinfo.record]: seed = 148 26 27 153 11:09:24.811 [planetinfo.record]: coordinates = 26 47 11:09:24.811 [planetinfo.record]: government = 2; 11:09:24.811 [planetinfo.record]: economy = 7; 11:09:24.811 [planetinfo.record]: techlevel = 3; 11:09:24.811 [planetinfo.record]: population = 22; 11:09:24.811 [planetinfo.record]: productivity = 3168; 11:09:24.811 [planetinfo.record]: name = "Orgetibe"; 11:09:24.811 [planetinfo.record]: inhabitant = "Human Colonial"; 11:09:24.811 [planetinfo.record]: inhabitants = "Human Colonials"; 11:09:24.811 [planetinfo.record]: description = "This planet is a dull world."; 11:09:24.840 [planetinfo.record]: air_color = 0.605354, 0.510172, 0.920303, 1; 11:09:24.840 [planetinfo.record]: cloud_alpha = 1.000000; 11:09:24.840 [planetinfo.record]: cloud_color = 0.600697, 0.199867, 0.763672, 1; 11:09:24.840 [planetinfo.record]: cloud_fraction = 0.240000; 11:09:24.840 [planetinfo.record]: land_color = 0.66, 0.12375, 0.488232, 1; 11:09:24.840 [planetinfo.record]: land_fraction = 0.590000; 11:09:24.840 [planetinfo.record]: polar_cloud_color = 0.731125, 0.454369, 0.843652, 1; 11:09:24.840 [planetinfo.record]: polar_land_color = 0.934, 0.744281, 0.873231, 1; 11:09:24.840 [planetinfo.record]: polar_sea_color = 0.938477, 0.89082, 0.87808, 1; 11:09:24.840 [planetinfo.record]: sea_color = 0.615234, 0.490267, 0.45686, 1; 11:09:24.840 [planetinfo.record]: rotation_speed = 0.004019 11:09:24.841 [planetinfo.record]: planet zpos = 463140.000000 11:09:24.841 [planetinfo.record]: sun_radius = 134184.211426 11:09:24.841 [planetinfo.record]: sun_vector = 0.248 -0.764 0.596 11:09:24.841 [planetinfo.record]: sun_distance = 1029200 11:09:24.841 [planetinfo.record]: corona_flare = 0.020438 11:09:24.841 [planetinfo.record]: corona_hues = 0.535202 11:09:24.841 [planetinfo.record]: sun_color = 0.675426, 0.6782, 0.783832, 1 11:09:24.841 [planetinfo.record]: corona_shimmer = 0.373225 11:09:24.841 [planetinfo.record]: station_vector = -0.852 -0.281 0.441 11:09:24.841 [planetinfo.record]: station = coriolis 11:09:29.555 [PLANETINFO OVER]: Done 11:09:29.556 [PLANETINFO LOGGING]: 0 39 11:09:30.574 [planetinfo.record]: seed = 220 19 157 142 11:09:30.574 [planetinfo.record]: coordinates = 19 151 11:09:30.574 [planetinfo.record]: government = 3; 11:09:30.574 [planetinfo.record]: economy = 7; 11:09:30.574 [planetinfo.record]: techlevel = 5; 11:09:30.574 [planetinfo.record]: population = 31; 11:09:30.575 [planetinfo.record]: productivity = 5208; 11:09:30.575 [planetinfo.record]: name = "Reorte"; 11:09:30.575 [planetinfo.record]: inhabitant = "Black Fat Feline"; 11:09:30.575 [planetinfo.record]: inhabitants = "Black Fat Felines"; 11:09:30.575 [planetinfo.record]: description = "This planet is mildly fabled for its inhabitants’ eccentric love for tourists but plagued by deadly earthquakes."; 11:09:30.581 [planetinfo.record]: air_color = 0.483663, 0.996398, 0.859793, 1; 11:09:30.581 [planetinfo.record]: cloud_alpha = 1.000000; 11:09:30.581 [planetinfo.record]: cloud_color = 0.992188, 0.252104, 0.0542603, 1; 11:09:30.581 [planetinfo.record]: cloud_fraction = 0.420000; 11:09:30.581 [planetinfo.record]: land_color = 0.520781, 0.542534, 0.66, 1; 11:09:30.582 [planetinfo.record]: land_fraction = 0.420000; 11:09:30.582 [planetinfo.record]: polar_cloud_color = 0.946484, 0.505239, 0.387282, 1; 11:09:30.582 [planetinfo.record]: polar_land_color = 0.884746, 0.892442, 0.934, 1; 11:09:30.582 [planetinfo.record]: polar_sea_color = 0.929297, 0.890586, 0.853065, 1; 11:09:30.582 [planetinfo.record]: sea_color = 0.707031, 0.589221, 0.475037, 1; 11:09:30.582 [planetinfo.record]: rotation_speed = 0.002059 11:09:30.582 [planetinfo.record]: planet zpos = 834470.000000 11:09:30.582 [planetinfo.record]: sun_radius = 174174.730377 11:09:30.582 [planetinfo.record]: sun_vector = -0.031 -0.858 -0.514 11:09:30.582 [planetinfo.record]: sun_distance = 1412180 11:09:30.582 [planetinfo.record]: corona_flare = 0.020842 11:09:30.582 [planetinfo.record]: corona_hues = 0.759155 11:09:30.582 [planetinfo.record]: sun_color = 0.84859, 0.799722, 0.52698, 1 11:09:30.582 [planetinfo.record]: corona_shimmer = 0.626132 11:09:30.583 [planetinfo.record]: station_vector = 0.401 -0.497 0.770 11:09:30.583 [planetinfo.record]: station = coriolis 11:09:35.497 [PLANETINFO OVER]: Done 11:09:35.497 [PLANETINFO LOGGING]: 0 40 11:09:36.503 [planetinfo.record]: seed = 120 164 83 186 11:09:36.503 [planetinfo.record]: coordinates = 164 220 11:09:36.503 [planetinfo.record]: government = 7; 11:09:36.503 [planetinfo.record]: economy = 4; 11:09:36.503 [planetinfo.record]: techlevel = 7; 11:09:36.503 [planetinfo.record]: population = 40; 11:09:36.503 [planetinfo.record]: productivity = 21120; 11:09:36.503 [planetinfo.record]: name = "Ququor"; 11:09:36.503 [planetinfo.record]: inhabitant = "Human Colonial"; 11:09:36.503 [planetinfo.record]: inhabitants = "Human Colonials"; 11:09:36.503 [planetinfo.record]: description = "The planet Ququor is mildly well known for its exotic cuisine."; 11:09:36.524 [planetinfo.record]: air_color = 0.473264, 0.459429, 0.933961, 1; 11:09:36.524 [planetinfo.record]: cloud_alpha = 1.000000; 11:09:36.524 [planetinfo.record]: cloud_color = 0.135383, 0.0597229, 0.804688, 1; 11:09:36.524 [planetinfo.record]: cloud_fraction = 0.270000; 11:09:36.524 [planetinfo.record]: land_color = 0.0592969, 0.308026, 0.66, 1; 11:09:36.524 [planetinfo.record]: land_fraction = 0.710000; 11:09:36.524 [planetinfo.record]: polar_cloud_color = 0.413944, 0.363281, 0.862109, 1; 11:09:36.524 [planetinfo.record]: polar_land_color = 0.721479, 0.809476, 0.934, 1; 11:09:36.524 [planetinfo.record]: polar_sea_color = 0.902751, 0.932617, 0.880249, 1; 11:09:36.524 [planetinfo.record]: sea_color = 0.587512, 0.673828, 0.52248, 1; 11:09:36.524 [planetinfo.record]: rotation_speed = 0.004523 11:09:36.524 [planetinfo.record]: planet zpos = 775600.000000 11:09:36.524 [planetinfo.record]: sun_radius = 137464.462280 11:09:36.524 [planetinfo.record]: sun_vector = 0.298 -0.555 0.777 11:09:36.524 [planetinfo.record]: sun_distance = 1108000 11:09:36.524 [planetinfo.record]: corona_flare = 0.001776 11:09:36.524 [planetinfo.record]: corona_hues = 0.944748 11:09:36.524 [planetinfo.record]: sun_color = 0.227861, 0.44279, 0.714767, 1 11:09:36.525 [planetinfo.record]: corona_shimmer = 0.907625 11:09:36.525 [planetinfo.record]: station_vector = 0.166 -0.709 0.686 11:09:36.525 [planetinfo.record]: station = coriolis 11:09:41.171 [PLANETINFO OVER]: Done 11:09:41.172 [PLANETINFO LOGGING]: 0 41 11:09:42.177 [planetinfo.record]: seed = 8 143 189 99 11:09:42.177 [planetinfo.record]: coordinates = 143 153 11:09:42.177 [planetinfo.record]: government = 1; 11:09:42.177 [planetinfo.record]: economy = 3; 11:09:42.177 [planetinfo.record]: techlevel = 8; 11:09:42.178 [planetinfo.record]: population = 37; 11:09:42.178 [planetinfo.record]: productivity = 10360; 11:09:42.178 [planetinfo.record]: name = "Geinona"; 11:09:42.178 [planetinfo.record]: inhabitant = "Large Blue Frog"; 11:09:42.178 [planetinfo.record]: inhabitants = "Large Blue Frogs"; 11:09:42.178 [planetinfo.record]: description = "This world is ravaged by unpredictable solar activity."; 11:09:42.195 [planetinfo.record]: air_color = 0.626881, 0.907295, 0.880734, 1; 11:09:42.195 [planetinfo.record]: cloud_alpha = 1.000000; 11:09:42.195 [planetinfo.record]: cloud_color = 0.724609, 0.659596, 0.486847, 1; 11:09:42.195 [planetinfo.record]: cloud_fraction = 0.300000; 11:09:42.195 [planetinfo.record]: land_color = 0.588397, 0.543984, 0.66, 1; 11:09:42.195 [planetinfo.record]: land_fraction = 0.260000; 11:09:42.195 [planetinfo.record]: polar_cloud_color = 0.826074, 0.779751, 0.656664, 1; 11:09:42.195 [planetinfo.record]: polar_land_color = 0.908668, 0.892955, 0.934, 1; 11:09:42.195 [planetinfo.record]: polar_sea_color = 0.945898, 0.832251, 0.711271, 1; 11:09:42.196 [planetinfo.record]: sea_color = 0.541016, 0.281008, 0.00422668, 1; 11:09:42.196 [planetinfo.record]: rotation_speed = 0.002528 11:09:42.196 [planetinfo.record]: planet zpos = 447240.000000 11:09:42.196 [planetinfo.record]: sun_radius = 97626.744995 11:09:42.196 [planetinfo.record]: sun_vector = -0.595 0.786 -0.169 11:09:42.196 [planetinfo.record]: sun_distance = 745400 11:09:42.196 [planetinfo.record]: corona_flare = 0.091489 11:09:42.196 [planetinfo.record]: corona_hues = 0.624260 11:09:42.196 [planetinfo.record]: sun_color = 0.437948, 0.579346, 0.806802, 1 11:09:42.196 [planetinfo.record]: corona_shimmer = 0.256411 11:09:42.196 [planetinfo.record]: station_vector = 0.955 0.244 0.167 11:09:42.196 [planetinfo.record]: station = coriolis 11:09:47.484 [PLANETINFO OVER]: Done 11:09:47.485 [PLANETINFO LOGGING]: 0 42 11:09:48.491 [planetinfo.record]: seed = 140 111 91 123 11:09:48.492 [planetinfo.record]: coordinates = 111 127 11:09:48.492 [planetinfo.record]: government = 1; 11:09:48.492 [planetinfo.record]: economy = 7; 11:09:48.492 [planetinfo.record]: techlevel = 4; 11:09:48.492 [planetinfo.record]: population = 25; 11:09:48.492 [planetinfo.record]: productivity = 3000; 11:09:48.492 [planetinfo.record]: name = "Anarlaqu"; 11:09:48.492 [planetinfo.record]: inhabitant = "Human Colonial"; 11:09:48.492 [planetinfo.record]: inhabitants = "Human Colonials"; 11:09:48.492 [planetinfo.record]: description = "This world is mildly famous for its hoopy night life and its exotic night life."; 11:09:48.508 [planetinfo.record]: air_color = 0.453817, 0.484368, 0.927457, 1; 11:09:48.508 [planetinfo.record]: cloud_alpha = 1.000000; 11:09:48.508 [planetinfo.record]: cloud_color = 0.0521393, 0.183853, 0.785156, 1; 11:09:48.508 [planetinfo.record]: cloud_fraction = 0.200000; 11:09:48.508 [planetinfo.record]: land_color = 0.66, 0.62155, 0.446016, 1; 11:09:48.508 [planetinfo.record]: land_fraction = 0.660000; 11:09:48.508 [planetinfo.record]: polar_cloud_color = 0.355411, 0.444879, 0.85332, 1; 11:09:48.508 [planetinfo.record]: polar_land_color = 0.934, 0.920397, 0.858295, 1; 11:09:48.508 [planetinfo.record]: polar_sea_color = 0.932255, 0.885286, 0.947266, 1; 11:09:48.508 [planetinfo.record]: sea_color = 0.493918, 0.389328, 0.527344, 1; 11:09:48.508 [planetinfo.record]: rotation_speed = 0.001205 11:09:48.508 [planetinfo.record]: planet zpos = 804020.000000 11:09:48.508 [planetinfo.record]: sun_radius = 164100.861969 11:09:48.508 [planetinfo.record]: sun_vector = 0.497 0.518 -0.696 11:09:48.508 [planetinfo.record]: sun_distance = 1206030 11:09:48.508 [planetinfo.record]: corona_flare = 0.052779 11:09:48.508 [planetinfo.record]: corona_hues = 0.712135 11:09:48.508 [planetinfo.record]: sun_color = 0.271004, 0.432494, 0.663058, 1 11:09:48.509 [planetinfo.record]: corona_shimmer = 0.433634 11:09:48.509 [planetinfo.record]: station_vector = 0.983 -0.144 0.111 11:09:48.509 [planetinfo.record]: station = coriolis 11:09:53.134 [PLANETINFO OVER]: Done 11:09:53.135 [PLANETINFO LOGGING]: 0 43 11:09:54.149 [planetinfo.record]: seed = 164 58 237 249 11:09:54.149 [planetinfo.record]: coordinates = 58 85 11:09:54.149 [planetinfo.record]: government = 4; 11:09:54.149 [planetinfo.record]: economy = 5; 11:09:54.149 [planetinfo.record]: techlevel = 6; 11:09:54.149 [planetinfo.record]: population = 34; 11:09:54.149 [planetinfo.record]: productivity = 10880; 11:09:54.149 [planetinfo.record]: name = "Oresri"; 11:09:54.149 [planetinfo.record]: inhabitant = "Rodent"; 11:09:54.150 [planetinfo.record]: inhabitants = "Rodents"; 11:09:54.150 [planetinfo.record]: description = "The planet Oresri is cursed by dreadful civil war."; 11:09:54.184 [planetinfo.record]: air_color = 0.727674, 0.787338, 0.927457, 1; 11:09:54.184 [planetinfo.record]: cloud_alpha = 1.000000; 11:09:54.184 [planetinfo.record]: cloud_color = 0.748352, 0.780843, 0.785156, 1; 11:09:54.184 [planetinfo.record]: cloud_fraction = 0.360000; 11:09:54.184 [planetinfo.record]: land_color = 0.529297, 0.48381, 0.491273, 1; 11:09:54.184 [planetinfo.record]: land_fraction = 0.280000; 11:09:54.184 [planetinfo.record]: polar_cloud_color = 0.828321, 0.850391, 0.85332, 1; 11:09:54.184 [planetinfo.record]: polar_land_color = 0.94707, 0.926723, 0.930061, 1; 11:09:54.184 [planetinfo.record]: polar_sea_color = 0.932031, 0.925321, 0.870685, 1; 11:09:54.184 [planetinfo.record]: sea_color = 0.679688, 0.660115, 0.500739, 1; 11:09:54.184 [planetinfo.record]: rotation_speed = 0.004302 11:09:54.184 [planetinfo.record]: planet zpos = 621360.000000 11:09:54.184 [planetinfo.record]: sun_radius = 133504.793701 11:09:54.184 [planetinfo.record]: sun_vector = 0.187 0.345 0.920 11:09:54.184 [planetinfo.record]: sun_distance = 1242720 11:09:54.184 [planetinfo.record]: corona_flare = 0.067471 11:09:54.184 [planetinfo.record]: corona_hues = 0.935898 11:09:54.184 [planetinfo.record]: sun_color = 0.809204, 0.671428, 0.21998, 1 11:09:54.184 [planetinfo.record]: corona_shimmer = 0.389072 11:09:54.185 [planetinfo.record]: station_vector = 0.723 -0.216 0.656 11:09:54.185 [planetinfo.record]: station = coriolis 11:09:59.170 [PLANETINFO OVER]: Done 11:09:59.171 [PLANETINFO LOGGING]: 0 44 11:10:00.196 [planetinfo.record]: seed = 208 65 179 137 11:10:00.196 [planetinfo.record]: coordinates = 65 190 11:10:00.196 [planetinfo.record]: government = 2; 11:10:00.196 [planetinfo.record]: economy = 6; 11:10:00.196 [planetinfo.record]: techlevel = 3; 11:10:00.196 [planetinfo.record]: population = 21; 11:10:00.196 [planetinfo.record]: productivity = 4032; 11:10:00.196 [planetinfo.record]: name = "Esesla"; 11:10:00.196 [planetinfo.record]: inhabitant = "Small Black Rodent"; 11:10:00.196 [planetinfo.record]: inhabitants = "Small Black Rodents"; 11:10:00.196 [planetinfo.record]: description = "This planet is noted for Zero-G hockey."; 11:10:00.224 [planetinfo.record]: air_color = 0.546413, 0.524479, 0.887783, 1; 11:10:00.224 [planetinfo.record]: cloud_alpha = 1.000000; 11:10:00.224 [planetinfo.record]: cloud_color = 0.328963, 0.247154, 0.666016, 1; 11:10:00.224 [planetinfo.record]: cloud_fraction = 0.280000; 11:10:00.224 [planetinfo.record]: land_color = 0.484688, 0.487427, 0.66, 1; 11:10:00.224 [planetinfo.record]: land_fraction = 0.350000; 11:10:00.224 [planetinfo.record]: polar_cloud_color = 0.546763, 0.485369, 0.799707, 1; 11:10:00.224 [planetinfo.record]: polar_land_color = 0.871977, 0.872946, 0.934, 1; 11:10:00.224 [planetinfo.record]: polar_sea_color = 0.934766, 0.881349, 0.870866, 1; 11:10:00.224 [planetinfo.record]: sea_color = 0.652344, 0.503233, 0.473969, 1; 11:10:00.224 [planetinfo.record]: rotation_speed = 0.004174 11:10:00.224 [planetinfo.record]: planet zpos = 570350.000000 11:10:00.224 [planetinfo.record]: sun_radius = 128852.028656 11:10:00.224 [planetinfo.record]: sun_vector = 0.271 -0.872 0.407 11:10:00.224 [planetinfo.record]: sun_distance = 1192550 11:10:00.224 [planetinfo.record]: corona_flare = 0.051810 11:10:00.224 [planetinfo.record]: corona_hues = 0.910545 11:10:00.224 [planetinfo.record]: sun_color = 0.751886, 0.550429, 0.497989, 1 11:10:00.225 [planetinfo.record]: corona_shimmer = 0.556824 11:10:00.225 [planetinfo.record]: station_vector = -0.345 0.926 0.155 11:10:00.225 [planetinfo.record]: station = coriolis 11:10:04.686 [PLANETINFO OVER]: Done 11:10:04.687 [PLANETINFO LOGGING]: 0 45 11:10:05.689 [planetinfo.record]: seed = 176 104 173 71 11:10:05.689 [planetinfo.record]: coordinates = 104 85 11:10:05.689 [planetinfo.record]: government = 6; 11:10:05.689 [planetinfo.record]: economy = 5; 11:10:05.689 [planetinfo.record]: techlevel = 5; 11:10:05.689 [planetinfo.record]: population = 32; 11:10:05.689 [planetinfo.record]: productivity = 12800; 11:10:05.689 [planetinfo.record]: name = "Socelage"; 11:10:05.689 [planetinfo.record]: inhabitant = "Fierce Yellow Furry Rodent"; 11:10:05.689 [planetinfo.record]: inhabitants = "Fierce Yellow Furry Rodents"; 11:10:05.689 [planetinfo.record]: description = "This planet is reasonably noted for its exotic goat meat."; 11:10:05.708 [planetinfo.record]: air_color = 0.5584, 0.96648, 0.937382, 1; 11:10:05.708 [planetinfo.record]: cloud_alpha = 1.000000; 11:10:05.708 [planetinfo.record]: cloud_color = 0.902344, 0.779913, 0.299606, 1; 11:10:05.708 [planetinfo.record]: cloud_fraction = 0.370000; 11:10:05.708 [planetinfo.record]: land_color = 0.319768, 0.66, 0.237188, 1; 11:10:05.708 [planetinfo.record]: land_fraction = 0.560000; 11:10:05.708 [planetinfo.record]: polar_cloud_color = 0.906055, 0.829221, 0.527795, 1; 11:10:05.708 [planetinfo.record]: polar_land_color = 0.81363, 0.934, 0.784414, 1; 11:10:05.708 [planetinfo.record]: polar_sea_color = 0.913102, 0.936328, 0.887592, 1; 11:10:05.708 [planetinfo.record]: sea_color = 0.573542, 0.636719, 0.504152, 1; 11:10:05.708 [planetinfo.record]: rotation_speed = 0.002196 11:10:05.708 [planetinfo.record]: planet zpos = 565440.000000 11:10:05.708 [planetinfo.record]: sun_radius = 130006.363525 11:10:05.708 [planetinfo.record]: sun_vector = -0.073 0.987 -0.146 11:10:05.708 [planetinfo.record]: sun_distance = 989520 11:10:05.708 [planetinfo.record]: corona_flare = 0.031056 11:10:05.708 [planetinfo.record]: corona_hues = 0.833656 11:10:05.708 [planetinfo.record]: sun_color = 0.695975, 0.700391, 0.786505, 1 11:10:05.708 [planetinfo.record]: corona_shimmer = 0.360337 11:10:05.709 [planetinfo.record]: station_vector = 0.307 0.946 0.104 11:10:05.709 [planetinfo.record]: station = coriolis 11:10:10.397 [PLANETINFO OVER]: Done 11:10:10.398 [PLANETINFO LOGGING]: 0 46 11:10:11.402 [planetinfo.record]: seed = 68 3 219 190 11:10:11.402 [planetinfo.record]: coordinates = 3 181 11:10:11.402 [planetinfo.record]: government = 0; 11:10:11.402 [planetinfo.record]: economy = 7; 11:10:11.402 [planetinfo.record]: techlevel = 3; 11:10:11.402 [planetinfo.record]: population = 20; 11:10:11.402 [planetinfo.record]: productivity = 1920; 11:10:11.402 [planetinfo.record]: name = "Riedquat"; 11:10:11.402 [planetinfo.record]: inhabitant = "Harmless Rodent"; 11:10:11.402 [planetinfo.record]: inhabitants = "Harmless Rodents"; 11:10:11.402 [planetinfo.record]: description = "This planet is most notable for its fabulous cuisine but beset by occasional civil war."; 11:10:11.412 [planetinfo.record]: air_color = 0.620803, 0.904717, 0.939164, 1; 11:10:11.413 [planetinfo.record]: cloud_alpha = 1.000000; 11:10:11.413 [planetinfo.record]: cloud_color = 0.705131, 0.820312, 0.477448, 1; 11:10:11.413 [planetinfo.record]: cloud_fraction = 0.580000; 11:10:11.413 [planetinfo.record]: land_color = 0.244637, 0.504784, 0.517578, 1; 11:10:11.413 [planetinfo.record]: land_fraction = 0.260000; 11:10:11.413 [planetinfo.record]: polar_cloud_color = 0.792867, 0.869141, 0.642095, 1; 11:10:11.413 [planetinfo.record]: polar_land_color = 0.82323, 0.942382, 0.948242, 1; 11:10:11.413 [planetinfo.record]: polar_sea_color = 0.866504, 0.877593, 0.95, 1; 11:10:11.413 [planetinfo.record]: sea_color = 0.324219, 0.347565, 0.5, 1; 11:10:11.413 [planetinfo.record]: rotation_speed = 0.003408 11:10:11.413 [planetinfo.record]: planet zpos = 960450.000000 11:10:11.413 [planetinfo.record]: sun_radius = 154051.670074 11:10:11.414 [planetinfo.record]: sun_vector = 0.371 -0.898 -0.235 11:10:11.414 [planetinfo.record]: sun_distance = 1152540 11:10:11.414 [planetinfo.record]: corona_flare = 0.093787 11:10:11.414 [planetinfo.record]: corona_hues = 0.721039 11:10:11.414 [planetinfo.record]: sun_color = 0.742908, 0.701483, 0.59767, 1 11:10:11.414 [planetinfo.record]: corona_shimmer = 0.346202 11:10:11.414 [planetinfo.record]: station_vector = -0.423 -0.904 0.056 11:10:11.414 [planetinfo.record]: station = coriolis 11:10:16.995 [PLANETINFO OVER]: Done 11:10:16.996 [PLANETINFO LOGGING]: 0 47 11:10:18.001 [planetinfo.record]: seed = 44 113 125 35 11:10:18.001 [planetinfo.record]: coordinates = 113 58 11:10:18.001 [planetinfo.record]: government = 5; 11:10:18.001 [planetinfo.record]: economy = 2; 11:10:18.001 [planetinfo.record]: techlevel = 9; 11:10:18.001 [planetinfo.record]: population = 44; 11:10:18.001 [planetinfo.record]: productivity = 25344; 11:10:18.002 [planetinfo.record]: name = "Gerege"; 11:10:18.002 [planetinfo.record]: inhabitant = "Human Colonial"; 11:10:18.002 [planetinfo.record]: inhabitants = "Human Colonials"; 11:10:18.002 [planetinfo.record]: description = "The world Gerege is reasonably famous for the Geregeian spotted wolf."; 11:10:18.036 [planetinfo.record]: air_color = 0.534951, 0.60677, 0.839004, 1; 11:10:18.036 [planetinfo.record]: cloud_alpha = 1.000000; 11:10:18.037 [planetinfo.record]: cloud_color = 0.265854, 0.442239, 0.519531, 1; 11:10:18.037 [planetinfo.record]: cloud_fraction = 0.450000; 11:10:18.037 [planetinfo.record]: land_color = 0.599052, 0.66, 0.430547, 1; 11:10:18.037 [planetinfo.record]: land_fraction = 0.280000; 11:10:18.037 [planetinfo.record]: polar_cloud_color = 0.509854, 0.665559, 0.733789, 1; 11:10:18.037 [planetinfo.record]: polar_land_color = 0.912437, 0.934, 0.852822, 1; 11:10:18.037 [planetinfo.record]: polar_sea_color = 0.945117, 0.887893, 0.937517, 1; 11:10:18.037 [planetinfo.record]: sea_color = 0.548828, 0.415909, 0.531175, 1; 11:10:18.037 [planetinfo.record]: rotation_speed = 0.001400 11:10:18.038 [planetinfo.record]: planet zpos = 406670.000000 11:10:18.038 [planetinfo.record]: sun_radius = 107939.921722 11:10:18.038 [planetinfo.record]: sun_vector = 0.907 -0.038 -0.419 11:10:18.038 [planetinfo.record]: sun_distance = 776370 11:10:18.038 [planetinfo.record]: corona_flare = 0.088503 11:10:18.038 [planetinfo.record]: corona_hues = 0.757866 11:10:18.038 [planetinfo.record]: sun_color = 0.696667, 0.529587, 0.356591, 1 11:10:18.038 [planetinfo.record]: corona_shimmer = 0.491842 11:10:18.039 [planetinfo.record]: station_vector = 0.898 0.425 0.115 11:10:18.039 [planetinfo.record]: station = coriolis 11:10:22.871 [PLANETINFO OVER]: Done 11:10:22.872 [PLANETINFO LOGGING]: 0 48 11:10:23.894 [planetinfo.record]: seed = 232 85 83 136 11:10:23.894 [planetinfo.record]: coordinates = 85 99 11:10:23.894 [planetinfo.record]: government = 5; 11:10:23.894 [planetinfo.record]: economy = 3; 11:10:23.894 [planetinfo.record]: techlevel = 8; 11:10:23.894 [planetinfo.record]: population = 41; 11:10:23.894 [planetinfo.record]: productivity = 20664; 11:10:23.894 [planetinfo.record]: name = "Usle"; 11:10:23.894 [planetinfo.record]: inhabitant = "Human Colonial"; 11:10:23.894 [planetinfo.record]: inhabitants = "Human Colonials"; 11:10:23.894 [planetinfo.record]: description = "This world is very notable for the Usleian tree ant and its inhabitants’ exceptional loathing of sit coms."; 11:10:23.909 [planetinfo.record]: air_color = 0.735592, 0.566285, 0.906645, 1; 11:10:23.910 [planetinfo.record]: cloud_alpha = 1.000000; 11:10:23.910 [planetinfo.record]: cloud_color = 0.722656, 0.341568, 0.529135, 1; 11:10:23.910 [planetinfo.record]: cloud_fraction = 0.560000; 11:10:23.910 [planetinfo.record]: land_color = 0.115562, 0.597656, 0.074707, 1; 11:10:23.910 [planetinfo.record]: land_fraction = 0.540000; 11:10:23.910 [planetinfo.record]: polar_cloud_color = 0.825195, 0.553219, 0.687083, 1; 11:10:23.910 [planetinfo.record]: polar_land_color = 0.750627, 0.940234, 0.734558, 1; 11:10:23.910 [planetinfo.record]: polar_sea_color = 0.932813, 0.897947, 0.869957, 1; 11:10:23.910 [planetinfo.record]: sea_color = 0.671875, 0.571426, 0.490784, 1; 11:10:23.910 [planetinfo.record]: rotation_speed = 0.003864 11:10:23.910 [planetinfo.record]: planet zpos = 445410.000000 11:10:23.910 [planetinfo.record]: sun_radius = 130152.900391 11:10:23.910 [planetinfo.record]: sun_vector = -0.047 -0.980 -0.195 11:10:23.910 [planetinfo.record]: sun_distance = 1088780 11:10:23.911 [planetinfo.record]: corona_flare = 0.038078 11:10:23.911 [planetinfo.record]: corona_hues = 0.521667 11:10:23.911 [planetinfo.record]: sun_color = 0.512532, 0.552487, 0.775519, 1 11:10:23.911 [planetinfo.record]: corona_shimmer = 0.545888 11:10:23.911 [planetinfo.record]: station_vector = 0.269 0.887 0.375 11:10:23.911 [planetinfo.record]: station = coriolis 11:10:28.561 [PLANETINFO OVER]: Done 11:10:28.562 [PLANETINFO LOGGING]: 0 49 11:10:29.566 [planetinfo.record]: seed = 24 234 221 75 11:10:29.566 [planetinfo.record]: coordinates = 234 32 11:10:29.566 [planetinfo.record]: government = 3; 11:10:29.566 [planetinfo.record]: economy = 0; 11:10:29.566 [planetinfo.record]: techlevel = 11; 11:10:29.566 [planetinfo.record]: population = 48; 11:10:29.566 [planetinfo.record]: productivity = 26880; 11:10:29.566 [planetinfo.record]: name = "Malama"; 11:10:29.566 [planetinfo.record]: inhabitant = "Small Yellow Horned Humanoid"; 11:10:29.566 [planetinfo.record]: inhabitants = "Small Yellow Horned Humanoids"; 11:10:29.566 [planetinfo.record]: description = "The planet Malama is mildly well known for its exotic cuisine."; 11:10:29.572 [planetinfo.record]: air_color = 0.64955, 0.49845, 0.941115, 1; 11:10:29.573 [planetinfo.record]: cloud_alpha = 1.000000; 11:10:29.573 [planetinfo.record]: cloud_color = 0.826172, 0.154907, 0.799951, 1; 11:10:29.573 [planetinfo.record]: cloud_fraction = 0.440000; 11:10:29.573 [planetinfo.record]: land_color = 0.66, 0.629888, 0.492422, 1; 11:10:29.573 [planetinfo.record]: land_fraction = 0.300000; 11:10:29.573 [planetinfo.record]: polar_cloud_color = 0.871777, 0.429078, 0.854484, 1; 11:10:29.573 [planetinfo.record]: polar_land_color = 0.934, 0.923347, 0.874713, 1; 11:10:29.573 [planetinfo.record]: polar_sea_color = 0.866708, 0.872037, 0.91543, 1; 11:10:29.573 [planetinfo.record]: sea_color = 0.665661, 0.685353, 0.845703, 1; 11:10:29.573 [planetinfo.record]: rotation_speed = 0.001906 11:10:29.573 [planetinfo.record]: planet zpos = 703920.000000 11:10:29.573 [planetinfo.record]: sun_radius = 177008.447571 11:10:29.573 [planetinfo.record]: sun_vector = -0.743 0.664 0.088 11:10:29.573 [planetinfo.record]: sun_distance = 1231860 11:10:29.574 [planetinfo.record]: corona_flare = 0.038293 11:10:29.574 [planetinfo.record]: corona_hues = 0.521149 11:10:29.574 [planetinfo.record]: sun_color = 0.703883, 0.728465, 0.732693, 1 11:10:29.574 [planetinfo.record]: corona_shimmer = 0.313798 11:10:29.574 [planetinfo.record]: station_vector = 0.608 -0.597 0.524 11:10:29.580 [planetinfo.record]: station = dodecahedron 11:10:34.947 [PLANETINFO OVER]: Done 11:10:34.948 [PLANETINFO LOGGING]: 0 50 11:10:35.970 [planetinfo.record]: seed = 188 45 155 15 11:10:35.970 [planetinfo.record]: coordinates = 45 139 11:10:35.970 [planetinfo.record]: government = 7; 11:10:35.970 [planetinfo.record]: economy = 3; 11:10:35.970 [planetinfo.record]: techlevel = 9; 11:10:35.970 [planetinfo.record]: population = 47; 11:10:35.970 [planetinfo.record]: productivity = 28952; 11:10:35.970 [planetinfo.record]: name = "Aesbion"; 11:10:35.970 [planetinfo.record]: inhabitant = "Green Frog"; 11:10:35.970 [planetinfo.record]: inhabitants = "Green Frogs"; 11:10:35.970 [planetinfo.record]: description = "The planet Aesbion is cursed by dreadful civil war."; 11:10:35.972 [planetinfo.record]: air_color = 0.683963, 0.802083, 0.943717, 1; 11:10:35.972 [planetinfo.record]: cloud_alpha = 1.000000; 11:10:35.972 [planetinfo.record]: cloud_color = 0.645035, 0.833984, 0.767557, 1; 11:10:35.972 [planetinfo.record]: cloud_fraction = 0.210000; 11:10:35.973 [planetinfo.record]: land_color = 0.66, 0.373828, 0.554921, 1; 11:10:35.973 [planetinfo.record]: land_fraction = 0.370000; 11:10:35.973 [planetinfo.record]: polar_cloud_color = 0.75135, 0.875293, 0.831719, 1; 11:10:35.973 [planetinfo.record]: polar_land_color = 0.934, 0.832756, 0.896824, 1; 11:10:35.973 [planetinfo.record]: polar_sea_color = 0.946875, 0.91488, 0.899254, 1; 11:10:35.973 [planetinfo.record]: sea_color = 0.53125, 0.459445, 0.424377, 1; 11:10:35.973 [planetinfo.record]: rotation_speed = 0.000521 11:10:35.973 [planetinfo.record]: planet zpos = 1005150.000000 11:10:35.973 [planetinfo.record]: sun_radius = 153464.720001 11:10:35.973 [planetinfo.record]: sun_vector = -0.323 -0.861 -0.393 11:10:35.973 [planetinfo.record]: sun_distance = 1340200 11:10:35.973 [planetinfo.record]: corona_flare = 0.078337 11:10:35.973 [planetinfo.record]: corona_hues = 0.923714 11:10:35.973 [planetinfo.record]: sun_color = 0.409563, 0.616359, 0.714316, 1 11:10:35.973 [planetinfo.record]: corona_shimmer = 0.636729 11:10:35.974 [planetinfo.record]: station_vector = -0.268 -0.482 0.834 11:10:35.974 [planetinfo.record]: station = coriolis 11:10:41.196 [PLANETINFO OVER]: Done 11:10:41.197 [PLANETINFO LOGGING]: 0 51 11:10:42.205 [planetinfo.record]: seed = 116 223 77 175 11:10:42.205 [planetinfo.record]: coordinates = 223 6 11:10:42.205 [planetinfo.record]: government = 6; 11:10:42.205 [planetinfo.record]: economy = 6; 11:10:42.205 [planetinfo.record]: techlevel = 7; 11:10:42.205 [planetinfo.record]: population = 41; 11:10:42.205 [planetinfo.record]: productivity = 13120; 11:10:42.205 [planetinfo.record]: name = "Alaza"; 11:10:42.206 [planetinfo.record]: inhabitant = "Human Colonial"; 11:10:42.206 [planetinfo.record]: inhabitants = "Human Colonials"; 11:10:42.206 [planetinfo.record]: description = "The world Alaza is scourged by evil disease."; 11:10:42.216 [planetinfo.record]: air_color = 0.487458, 0.586136, 0.879328, 1; 11:10:42.216 [planetinfo.record]: cloud_alpha = 1.000000; 11:10:42.217 [planetinfo.record]: cloud_color = 0.167664, 0.51869, 0.640625, 1; 11:10:42.217 [planetinfo.record]: cloud_fraction = 0.450000; 11:10:42.217 [planetinfo.record]: land_color = 0.486964, 0.476953, 0.66, 1; 11:10:42.217 [planetinfo.record]: land_fraction = 0.710000; 11:10:42.217 [planetinfo.record]: polar_cloud_color = 0.424548, 0.694506, 0.788281, 1; 11:10:42.217 [planetinfo.record]: polar_land_color = 0.872782, 0.86924, 0.934, 1; 11:10:42.217 [planetinfo.record]: polar_sea_color = 0.933203, 0.897376, 0.871233, 1; 11:10:42.217 [planetinfo.record]: sea_color = 0.667969, 0.565392, 0.49054, 1; 11:10:42.217 [planetinfo.record]: rotation_speed = 0.003668 11:10:42.217 [planetinfo.record]: planet zpos = 756690.000000 11:10:42.217 [planetinfo.record]: sun_radius = 152582.677460 11:10:42.217 [planetinfo.record]: sun_vector = 0.381 0.298 -0.875 11:10:42.217 [planetinfo.record]: sun_distance = 1444590 11:10:42.217 [planetinfo.record]: corona_flare = 0.060658 11:10:42.217 [planetinfo.record]: corona_hues = 0.880478 11:10:42.218 [planetinfo.record]: sun_color = 0.709387, 0.493362, 0.1964, 1 11:10:42.219 [planetinfo.record]: corona_shimmer = 0.470676 11:10:42.220 [planetinfo.record]: station_vector = -0.157 -0.976 0.154 11:10:42.220 [planetinfo.record]: station = coriolis 11:10:46.733 [PLANETINFO OVER]: Done 11:10:46.734 [PLANETINFO LOGGING]: 0 52 11:10:47.758 [planetinfo.record]: seed = 192 104 51 34 11:10:47.758 [planetinfo.record]: coordinates = 104 36 11:10:47.758 [planetinfo.record]: government = 0; 11:10:47.758 [planetinfo.record]: economy = 6; 11:10:47.758 [planetinfo.record]: techlevel = 1; 11:10:47.758 [planetinfo.record]: population = 11; 11:10:47.758 [planetinfo.record]: productivity = 1408; 11:10:47.758 [planetinfo.record]: name = "Xeaqu"; 11:10:47.758 [planetinfo.record]: inhabitant = "Human Colonial"; 11:10:47.758 [planetinfo.record]: inhabitants = "Human Colonials"; 11:10:47.758 [planetinfo.record]: description = "The world Xeaqu is a dull place."; 11:10:47.762 [planetinfo.record]: air_color = 0.708109, 0.536379, 0.924205, 1; 11:10:47.762 [planetinfo.record]: cloud_alpha = 1.000000; 11:10:47.763 [planetinfo.record]: cloud_color = 0.775391, 0.263512, 0.599432, 1; 11:10:47.763 [planetinfo.record]: cloud_fraction = 0.320000; 11:10:47.763 [planetinfo.record]: land_color = 0.66, 0.136641, 0.381965, 1; 11:10:47.763 [planetinfo.record]: land_fraction = 0.660000; 11:10:47.763 [planetinfo.record]: polar_cloud_color = 0.848926, 0.498661, 0.728522, 1; 11:10:47.763 [planetinfo.record]: polar_land_color = 0.934, 0.748842, 0.835635, 1; 11:10:47.763 [planetinfo.record]: polar_sea_color = 0.922052, 0.938086, 0.890357, 1; 11:10:47.763 [planetinfo.record]: sea_color = 0.576811, 0.619141, 0.493136, 1; 11:10:47.763 [planetinfo.record]: rotation_speed = 0.003507 11:10:47.763 [planetinfo.record]: planet zpos = 377520.000000 11:10:47.763 [planetinfo.record]: sun_radius = 72038.170166 11:10:47.763 [planetinfo.record]: sun_vector = -0.712 0.268 -0.649 11:10:47.763 [planetinfo.record]: sun_distance = 583440 11:10:47.763 [planetinfo.record]: corona_flare = 0.032054 11:10:47.763 [planetinfo.record]: corona_hues = 0.822044 11:10:47.763 [planetinfo.record]: sun_color = 0.193022, 0.456387, 0.745538, 1 11:10:47.764 [planetinfo.record]: corona_shimmer = 0.943616 11:10:47.764 [planetinfo.record]: station_vector = 0.522 -0.586 0.620 11:10:47.764 [planetinfo.record]: station = coriolis 11:10:52.926 [PLANETINFO OVER]: Done 11:10:52.927 [PLANETINFO LOGGING]: 0 53 11:10:53.939 [planetinfo.record]: seed = 64 11 77 244 11:10:53.939 [planetinfo.record]: coordinates = 11 58 11:10:53.939 [planetinfo.record]: government = 0; 11:10:53.939 [planetinfo.record]: economy = 2; 11:10:53.939 [planetinfo.record]: techlevel = 8; 11:10:53.939 [planetinfo.record]: population = 35; 11:10:53.940 [planetinfo.record]: productivity = 8960; 11:10:53.940 [planetinfo.record]: name = "Raoror"; 11:10:53.940 [planetinfo.record]: inhabitant = "Human Colonial"; 11:10:53.940 [planetinfo.record]: inhabitants = "Human Colonials"; 11:10:53.940 [planetinfo.record]: description = "This world is very fabled for its weird volcanoes."; 11:10:53.948 [planetinfo.record]: air_color = 0.780705, 0.610704, 0.883881, 1; 11:10:53.948 [planetinfo.record]: cloud_alpha = 1.000000; 11:10:53.948 [planetinfo.record]: cloud_color = 0.654297, 0.439606, 0.464765, 1; 11:10:53.948 [planetinfo.record]: cloud_fraction = 0.400000; 11:10:53.948 [planetinfo.record]: land_color = 0.628906, 0.560734, 0.510986, 1; 11:10:53.949 [planetinfo.record]: land_fraction = 0.390000; 11:10:53.949 [planetinfo.record]: polar_cloud_color = 0.794434, 0.631513, 0.650605, 1; 11:10:53.949 [planetinfo.record]: polar_land_color = 0.937109, 0.911714, 0.893182, 1; 11:10:53.949 [planetinfo.record]: polar_sea_color = 0.775648, 0.942187, 0.887542, 1; 11:10:53.949 [planetinfo.record]: sea_color = 0.169373, 0.578125, 0.444003, 1; 11:10:53.949 [planetinfo.record]: rotation_speed = 0.001636 11:10:53.949 [planetinfo.record]: planet zpos = 385100.000000 11:10:53.949 [planetinfo.record]: sun_radius = 101182.180939 11:10:53.949 [planetinfo.record]: sun_vector = -0.254 -0.967 -0.004 11:10:53.949 [planetinfo.record]: sun_distance = 808710 11:10:53.949 [planetinfo.record]: corona_flare = 0.085837 11:10:53.949 [planetinfo.record]: corona_hues = 0.604118 11:10:53.949 [planetinfo.record]: sun_color = 0.398553, 0.523837, 0.827963, 1 11:10:53.949 [planetinfo.record]: corona_shimmer = 0.458858 11:10:53.950 [planetinfo.record]: station_vector = 0.514 0.135 0.847 11:10:53.950 [planetinfo.record]: station = coriolis 11:10:58.488 [PLANETINFO OVER]: Done 11:10:58.489 [PLANETINFO LOGGING]: 0 54 11:10:59.493 [planetinfo.record]: seed = 244 102 155 217 11:10:59.493 [planetinfo.record]: coordinates = 102 57 11:10:59.493 [planetinfo.record]: government = 6; 11:10:59.493 [planetinfo.record]: economy = 1; 11:10:59.493 [planetinfo.record]: techlevel = 11; 11:10:59.493 [planetinfo.record]: population = 52; 11:10:59.493 [planetinfo.record]: productivity = 37440; 11:10:59.493 [planetinfo.record]: name = "Ororqu"; 11:10:59.493 [planetinfo.record]: inhabitant = "Rodent"; 11:10:59.493 [planetinfo.record]: inhabitants = "Rodents"; 11:10:59.493 [planetinfo.record]: description = "The planet Ororqu is well known for its inhabitants’ ancient mating traditions but ravaged by unpredictable solar activity."; 11:10:59.508 [planetinfo.record]: air_color = 0.718351, 0.738125, 0.96518, 1; 11:10:59.508 [planetinfo.record]: cloud_alpha = 1.000000; 11:10:59.508 [planetinfo.record]: cloud_color = 0.747528, 0.781718, 0.898438, 1; 11:10:59.508 [planetinfo.record]: cloud_fraction = 0.260000; 11:10:59.508 [planetinfo.record]: land_color = 0.286469, 0.311868, 0.582031, 1; 11:10:59.508 [planetinfo.record]: land_fraction = 0.400000; 11:10:59.508 [planetinfo.record]: polar_cloud_color = 0.809363, 0.830872, 0.904297, 1; 11:10:59.508 [planetinfo.record]: polar_land_color = 0.822233, 0.832508, 0.941797, 1; 11:10:59.508 [planetinfo.record]: polar_sea_color = 0.926953, 0.901147, 0.846388, 1; 11:10:59.508 [planetinfo.record]: sea_color = 0.730469, 0.649125, 0.476517, 1; 11:10:59.508 [planetinfo.record]: rotation_speed = 0.002810 11:10:59.508 [planetinfo.record]: planet zpos = 522200.000000 11:10:59.508 [planetinfo.record]: sun_radius = 111636.026917 11:10:59.508 [planetinfo.record]: sun_vector = -0.534 -0.068 0.843 11:10:59.508 [planetinfo.record]: sun_distance = 939960 11:10:59.508 [planetinfo.record]: corona_flare = 0.066829 11:10:59.508 [planetinfo.record]: corona_hues = 0.504677 11:10:59.508 [planetinfo.record]: sun_color = 0.788443, 0.501674, 0.40529, 1 11:10:59.508 [planetinfo.record]: corona_shimmer = 0.246165 11:10:59.508 [planetinfo.record]: station_vector = 0.114 -0.710 0.695 11:10:59.508 [planetinfo.record]: station = dodecahedron 11:11:03.905 [PLANETINFO OVER]: Done 11:11:03.905 [PLANETINFO LOGGING]: 0 55 11:11:04.912 [planetinfo.record]: seed = 124 13 93 65 11:11:04.912 [planetinfo.record]: coordinates = 13 186 11:11:04.912 [planetinfo.record]: government = 7; 11:11:04.912 [planetinfo.record]: economy = 2; 11:11:04.912 [planetinfo.record]: techlevel = 10; 11:11:04.912 [planetinfo.record]: population = 50; 11:11:04.912 [planetinfo.record]: productivity = 35200; 11:11:04.912 [planetinfo.record]: name = "Leesti"; 11:11:04.912 [planetinfo.record]: inhabitant = "Human Colonial"; 11:11:04.912 [planetinfo.record]: inhabitants = "Human Colonials"; 11:11:04.912 [planetinfo.record]: description = "The planet Leesti is reasonably fabled for Zero-G cricket and Leestiian evil juice."; 11:11:04.915 [planetinfo.record]: air_color = 0.666644, 0.872824, 0.856513, 1; 11:11:04.915 [planetinfo.record]: cloud_alpha = 1.000000; 11:11:04.915 [planetinfo.record]: cloud_color = 0.621094, 0.605703, 0.553162, 1; 11:11:04.916 [planetinfo.record]: cloud_fraction = 0.450000; 11:11:04.916 [planetinfo.record]: land_color = 0.389297, 0.571176, 0.66, 1; 11:11:04.916 [planetinfo.record]: land_fraction = 0.550000; 11:11:04.916 [planetinfo.record]: polar_cloud_color = 0.779492, 0.76742, 0.726207, 1; 11:11:04.916 [planetinfo.record]: polar_land_color = 0.838229, 0.902575, 0.934, 1; 11:11:04.916 [planetinfo.record]: polar_sea_color = 0.941406, 0.919746, 0.889096, 1; 11:11:04.916 [planetinfo.record]: sea_color = 0.585938, 0.532013, 0.455704, 1; 11:11:04.916 [planetinfo.record]: rotation_speed = 0.000773 11:11:04.916 [planetinfo.record]: planet zpos = 370200.000000 11:11:04.916 [planetinfo.record]: sun_radius = 97136.828613 11:11:04.916 [planetinfo.record]: sun_vector = 0.768 0.067 -0.636 11:11:04.916 [planetinfo.record]: sun_distance = 524450 11:11:04.916 [planetinfo.record]: corona_flare = 0.070724 11:11:04.916 [planetinfo.record]: corona_hues = 0.562256 11:11:04.916 [planetinfo.record]: sun_color = 0.84183, 0.707257, 0.670673, 1 11:11:04.916 [planetinfo.record]: corona_shimmer = 0.359677 11:11:04.916 [planetinfo.record]: station_vector = 0.956 -0.132 0.263 11:11:04.916 [planetinfo.record]: station = coriolis 11:11:09.877 [PLANETINFO OVER]: Done 11:11:09.878 [PLANETINFO LOGGING]: 0 56 11:11:10.901 [planetinfo.record]: seed = 88 162 83 3 11:11:10.901 [planetinfo.record]: coordinates = 162 87 11:11:10.901 [planetinfo.record]: government = 3; 11:11:10.901 [planetinfo.record]: economy = 7; 11:11:10.901 [planetinfo.record]: techlevel = 4; 11:11:10.901 [planetinfo.record]: population = 27; 11:11:10.901 [planetinfo.record]: productivity = 4536; 11:11:10.901 [planetinfo.record]: name = "Geisgeza"; 11:11:10.901 [planetinfo.record]: inhabitant = "Human Colonial"; 11:11:10.901 [planetinfo.record]: inhabitants = "Human Colonials"; 11:11:10.901 [planetinfo.record]: description = "This planet is notable for its unusual oceans and the Geisgezaian mountain slug."; 11:11:10.920 [planetinfo.record]: air_color = 0.579224, 0.959326, 0.958922, 1; 11:11:10.920 [planetinfo.record]: cloud_alpha = 1.000000; 11:11:10.920 [planetinfo.record]: cloud_color = 0.8768, 0.880859, 0.36129, 1; 11:11:10.920 [planetinfo.record]: cloud_fraction = 0.420000; 11:11:10.920 [planetinfo.record]: land_color = 0.234009, 0.49456, 0.554688, 1; 11:11:10.920 [planetinfo.record]: land_fraction = 0.460000; 11:11:10.920 [planetinfo.record]: polar_cloud_color = 0.893805, 0.896387, 0.565932, 1; 11:11:10.920 [planetinfo.record]: polar_land_color = 0.808017, 0.918935, 0.944531, 1; 11:11:10.920 [planetinfo.record]: polar_sea_color = 0.879896, 0.929492, 0.877027, 1; 11:11:10.920 [planetinfo.record]: sea_color = 0.554591, 0.705078, 0.545885, 1; 11:11:10.920 [planetinfo.record]: rotation_speed = 0.003189 11:11:10.920 [planetinfo.record]: planet zpos = 449520.000000 11:11:10.920 [planetinfo.record]: sun_radius = 102700.623169 11:11:10.920 [planetinfo.record]: sun_vector = 0.202 -0.877 -0.435 11:11:10.920 [planetinfo.record]: sun_distance = 899040 11:11:10.920 [planetinfo.record]: corona_flare = 0.095018 11:11:10.920 [planetinfo.record]: corona_hues = 0.759415 11:11:10.920 [planetinfo.record]: sun_color = 0.709079, 0.756319, 0.786255, 1 11:11:10.920 [planetinfo.record]: corona_shimmer = 0.291803 11:11:10.920 [planetinfo.record]: station_vector = -0.956 -0.077 0.282 11:11:10.920 [planetinfo.record]: station = coriolis 11:11:15.619 [PLANETINFO OVER]: Done 11:11:15.620 [PLANETINFO LOGGING]: 0 57 11:11:16.623 [planetinfo.record]: seed = 40 164 253 68 11:11:16.623 [planetinfo.record]: coordinates = 164 163 11:11:16.623 [planetinfo.record]: government = 5; 11:11:16.623 [planetinfo.record]: economy = 3; 11:11:16.623 [planetinfo.record]: techlevel = 7; 11:11:16.623 [planetinfo.record]: population = 37; 11:11:16.623 [planetinfo.record]: productivity = 18648; 11:11:16.623 [planetinfo.record]: name = "Zainlabi"; 11:11:16.623 [planetinfo.record]: inhabitant = "Fierce Yellow Insect"; 11:11:16.623 [planetinfo.record]: inhabitants = "Fierce Yellow Insects"; 11:11:16.623 [planetinfo.record]: description = "This world is ravaged by unpredictable civil war."; 11:11:16.648 [planetinfo.record]: air_color = 0.719691, 0.496864, 0.959326, 1; 11:11:16.648 [planetinfo.record]: cloud_alpha = 1.000000; 11:11:16.648 [planetinfo.record]: cloud_color = 0.880859, 0.134193, 0.536693, 1; 11:11:16.648 [planetinfo.record]: cloud_fraction = 0.400000; 11:11:16.648 [planetinfo.record]: land_color = 0.523359, 0.66, 0.60876, 1; 11:11:16.648 [planetinfo.record]: land_fraction = 0.650000; 11:11:16.648 [planetinfo.record]: polar_cloud_color = 0.896387, 0.421494, 0.677491, 1; 11:11:16.648 [planetinfo.record]: polar_land_color = 0.885658, 0.934, 0.915872, 1; 11:11:16.648 [planetinfo.record]: polar_sea_color = 0.904358, 0.927734, 0.866671, 1; 11:11:16.648 [planetinfo.record]: sea_color = 0.649822, 0.722656, 0.532394, 1; 11:11:16.648 [planetinfo.record]: rotation_speed = 0.001267 11:11:16.648 [planetinfo.record]: planet zpos = 520520.000000 11:11:16.648 [planetinfo.record]: sun_radius = 100254.573364 11:11:16.648 [planetinfo.record]: sun_vector = 0.474 0.879 0.046 11:11:16.648 [planetinfo.record]: sun_distance = 840840 11:11:16.648 [planetinfo.record]: corona_flare = 0.041861 11:11:16.648 [planetinfo.record]: corona_hues = 0.579567 11:11:16.648 [planetinfo.record]: sun_color = 0.757355, 0.453888, 0.352551, 1 11:11:16.648 [planetinfo.record]: corona_shimmer = 0.720065 11:11:16.649 [planetinfo.record]: station_vector = -0.543 -0.796 0.267 11:11:16.649 [planetinfo.record]: station = coriolis 11:11:21.315 [PLANETINFO OVER]: Done 11:11:21.316 [PLANETINFO LOGGING]: 0 58 11:11:22.330 [planetinfo.record]: seed = 236 70 219 72 11:11:22.330 [planetinfo.record]: coordinates = 70 117 11:11:22.330 [planetinfo.record]: government = 5; 11:11:22.330 [planetinfo.record]: economy = 5; 11:11:22.330 [planetinfo.record]: techlevel = 7; 11:11:22.330 [planetinfo.record]: population = 39; 11:11:22.330 [planetinfo.record]: productivity = 14040; 11:11:22.330 [planetinfo.record]: name = "Uscela"; 11:11:22.330 [planetinfo.record]: inhabitant = "Small Yellow Bony Lobster"; 11:11:22.330 [planetinfo.record]: inhabitants = "Small Yellow Bony Lobsters"; 11:11:22.330 [planetinfo.record]: description = "The world Uscela is a boring world."; 11:11:22.340 [planetinfo.record]: air_color = 0.671664, 0.601788, 0.845508, 1; 11:11:22.340 [planetinfo.record]: cloud_alpha = 1.000000; 11:11:22.340 [planetinfo.record]: cloud_color = 0.521164, 0.395874, 0.539062, 1; 11:11:22.340 [planetinfo.record]: cloud_fraction = 0.480000; 11:11:22.347 [planetinfo.record]: land_color = 0.405894, 0.66, 0.232031, 1; 11:11:22.347 [planetinfo.record]: land_fraction = 0.710000; 11:11:22.347 [planetinfo.record]: polar_cloud_color = 0.727168, 0.619299, 0.742578, 1; 11:11:22.347 [planetinfo.record]: polar_land_color = 0.8441, 0.934, 0.78259, 1; 11:11:22.347 [planetinfo.record]: polar_sea_color = 0.940234, 0.891264, 0.881653, 1; 11:11:22.348 [planetinfo.record]: sea_color = 0.597656, 0.473146, 0.448709, 1; 11:11:22.348 [planetinfo.record]: rotation_speed = 0.004926 11:11:22.348 [planetinfo.record]: planet zpos = 444060.000000 11:11:22.348 [planetinfo.record]: sun_radius = 125575.479736 11:11:22.348 [planetinfo.record]: sun_vector = -0.203 0.110 -0.973 11:11:22.348 [planetinfo.record]: sun_distance = 888120 11:11:22.348 [planetinfo.record]: corona_flare = 0.089973 11:11:22.348 [planetinfo.record]: corona_hues = 0.702057 11:11:22.348 [planetinfo.record]: sun_color = 0.331434, 0.507403, 0.70946, 1 11:11:22.348 [planetinfo.record]: corona_shimmer = 0.364676 11:11:22.348 [planetinfo.record]: station_vector = -0.846 -0.386 0.368 11:11:22.348 [planetinfo.record]: station = coriolis 11:11:27.387 [PLANETINFO OVER]: Done 11:11:27.388 [PLANETINFO LOGGING]: 0 59 11:11:28.406 [planetinfo.record]: seed = 68 227 173 125 11:11:28.406 [planetinfo.record]: coordinates = 227 149 11:11:28.406 [planetinfo.record]: government = 0; 11:11:28.406 [planetinfo.record]: economy = 7; 11:11:28.406 [planetinfo.record]: techlevel = 3; 11:11:28.407 [planetinfo.record]: population = 20; 11:11:28.407 [planetinfo.record]: productivity = 1920; 11:11:28.407 [planetinfo.record]: name = "Isveve"; 11:11:28.407 [planetinfo.record]: inhabitant = "Blue Insect"; 11:11:28.407 [planetinfo.record]: inhabitants = "Blue Insects"; 11:11:28.407 [planetinfo.record]: description = "The planet Isveve is reasonably noted for its inhabitants’ eccentric shyness and its inhabitants’ eccentric shyness."; 11:11:28.440 [planetinfo.record]: air_color = 0.702624, 0.604545, 0.857215, 1; 11:11:28.440 [planetinfo.record]: cloud_alpha = 1.000000; 11:11:28.440 [planetinfo.record]: cloud_color = 0.574219, 0.410477, 0.544796, 1; 11:11:28.440 [planetinfo.record]: cloud_fraction = 0.320000; 11:11:28.440 [planetinfo.record]: land_color = 0.199585, 0.412268, 0.638672, 1; 11:11:28.440 [planetinfo.record]: land_fraction = 0.250000; 11:11:28.440 [planetinfo.record]: polar_cloud_color = 0.758398, 0.623235, 0.734111, 1; 11:11:28.440 [planetinfo.record]: polar_land_color = 0.775235, 0.85317, 0.936133, 1; 11:11:28.441 [planetinfo.record]: polar_sea_color = 0.941016, 0.935628, 0.891759, 1; 11:11:28.441 [planetinfo.record]: sea_color = 0.589844, 0.576336, 0.466345, 1; 11:11:28.441 [planetinfo.record]: rotation_speed = 0.003021 11:11:28.441 [planetinfo.record]: planet zpos = 764520.000000 11:11:28.441 [planetinfo.record]: sun_radius = 123501.313934 11:11:28.441 [planetinfo.record]: sun_vector = -0.028 0.955 -0.294 11:11:28.441 [planetinfo.record]: sun_distance = 1337910 11:11:28.441 [planetinfo.record]: corona_flare = 0.079329 11:11:28.442 [planetinfo.record]: corona_hues = 0.695427 11:11:28.442 [planetinfo.record]: sun_color = 0.703989, 0.706676, 0.735327, 1 11:11:28.442 [planetinfo.record]: corona_shimmer = 0.331191 11:11:28.442 [planetinfo.record]: station_vector = -0.864 -0.492 0.106 11:11:28.442 [planetinfo.record]: station = coriolis 11:11:33.169 [PLANETINFO OVER]: Done 11:11:33.169 [PLANETINFO LOGGING]: 0 60 11:11:34.176 [planetinfo.record]: seed = 176 202 179 23 11:11:34.177 [planetinfo.record]: coordinates = 202 86 11:11:34.177 [planetinfo.record]: government = 6; 11:11:34.177 [planetinfo.record]: economy = 6; 11:11:34.177 [planetinfo.record]: techlevel = 6; 11:11:34.177 [planetinfo.record]: population = 37; 11:11:34.177 [planetinfo.record]: productivity = 11840; 11:11:34.177 [planetinfo.record]: name = "Tioranin"; 11:11:34.177 [planetinfo.record]: inhabitant = "Green Fat Insect"; 11:11:34.177 [planetinfo.record]: inhabitants = "Green Fat Insects"; 11:11:34.177 [planetinfo.record]: description = "This world is most notable for Tioraninian vicious brew but ravaged by unpredictable civil war."; 11:11:34.200 [planetinfo.record]: air_color = 0.838427, 0.771539, 0.976887, 1; 11:11:34.200 [planetinfo.record]: cloud_alpha = 1.000000; 11:11:34.200 [planetinfo.record]: cloud_color = 0.933366, 0.904419, 0.933594, 1; 11:11:34.200 [planetinfo.record]: cloud_fraction = 0.270000; 11:11:34.200 [planetinfo.record]: land_color = 0.630859, 0.501676, 0.480537, 1; 11:11:34.200 [planetinfo.record]: land_fraction = 0.580000; 11:11:34.200 [planetinfo.record]: polar_cloud_color = 0.919977, 0.902146, 0.920117, 1; 11:11:34.200 [planetinfo.record]: polar_land_color = 0.936914, 0.88895, 0.881102, 1; 11:11:34.200 [planetinfo.record]: polar_sea_color = 0.757267, 0.936523, 0.936523, 1; 11:11:34.200 [planetinfo.record]: sea_color = 0.148773, 0.634766, 0.634766, 1; 11:11:34.200 [planetinfo.record]: rotation_speed = 0.002893 11:11:34.200 [planetinfo.record]: planet zpos = 673400.000000 11:11:34.200 [planetinfo.record]: sun_radius = 125398.643494 11:11:34.200 [planetinfo.record]: sun_vector = 0.802 -0.561 0.204 11:11:34.200 [planetinfo.record]: sun_distance = 1058200 11:11:34.200 [planetinfo.record]: corona_flare = 0.085091 11:11:34.200 [planetinfo.record]: corona_hues = 0.924553 11:11:34.200 [planetinfo.record]: sun_color = 0.595645, 0.633129, 0.658536, 1 11:11:34.200 [planetinfo.record]: corona_shimmer = 0.332060 11:11:34.200 [planetinfo.record]: station_vector = -0.045 -0.370 0.928 11:11:34.200 [planetinfo.record]: station = coriolis 11:11:38.792 [PLANETINFO OVER]: Done 11:11:38.793 [PLANETINFO LOGGING]: 0 61 11:11:39.797 [planetinfo.record]: seed = 208 108 237 193 11:11:39.797 [planetinfo.record]: coordinates = 108 27 11:11:39.797 [planetinfo.record]: government = 2; 11:11:39.797 [planetinfo.record]: economy = 3; 11:11:39.797 [planetinfo.record]: techlevel = 5; 11:11:39.797 [planetinfo.record]: population = 26; 11:11:39.797 [planetinfo.record]: productivity = 8736; 11:11:39.797 [planetinfo.record]: name = "Learorce"; 11:11:39.797 [planetinfo.record]: inhabitant = "Large Rodent"; 11:11:39.797 [planetinfo.record]: inhabitants = "Large Rodents"; 11:11:39.797 [planetinfo.record]: description = "Learorce is reasonably notable for its great dense forests but scourged by deadly edible poets."; 11:11:39.812 [planetinfo.record]: air_color = 0.768069, 0.601411, 0.887133, 1; 11:11:39.812 [planetinfo.record]: cloud_alpha = 1.000000; 11:11:39.812 [planetinfo.record]: cloud_color = 0.664062, 0.420227, 0.477376, 1; 11:11:39.812 [planetinfo.record]: cloud_fraction = 0.140000; 11:11:39.812 [planetinfo.record]: land_color = 0.66, 0.226875, 0.531416, 1; 11:11:39.812 [planetinfo.record]: land_fraction = 0.700000; 11:11:39.812 [planetinfo.record]: polar_cloud_color = 0.798828, 0.615503, 0.65847, 1; 11:11:39.812 [planetinfo.record]: polar_land_color = 0.934, 0.780766, 0.888509, 1; 11:11:39.812 [planetinfo.record]: polar_sea_color = 0.910833, 0.933594, 0.880623, 1; 11:11:39.812 [planetinfo.record]: sea_color = 0.599304, 0.664062, 0.513351, 1; 11:11:39.812 [planetinfo.record]: rotation_speed = 0.001003 11:11:39.812 [planetinfo.record]: planet zpos = 445200.000000 11:11:39.812 [planetinfo.record]: sun_radius = 80809.634399 11:11:39.812 [planetinfo.record]: sun_vector = -0.237 -0.684 0.690 11:11:39.812 [planetinfo.record]: sun_distance = 636000 11:11:39.812 [planetinfo.record]: corona_flare = 0.091461 11:11:39.812 [planetinfo.record]: corona_hues = 0.799271 11:11:39.812 [planetinfo.record]: sun_color = 0.459893, 0.483426, 0.72037, 1 11:11:39.812 [planetinfo.record]: corona_shimmer = 0.267154 11:11:39.812 [planetinfo.record]: station_vector = 0.818 0.248 0.519 11:11:39.812 [planetinfo.record]: station = coriolis 11:11:44.776 [PLANETINFO OVER]: Done 11:11:44.776 [PLANETINFO LOGGING]: 0 62 11:11:45.780 [planetinfo.record]: seed = 164 133 91 73 11:11:45.780 [planetinfo.record]: coordinates = 133 121 11:11:45.780 [planetinfo.record]: government = 4; 11:11:45.780 [planetinfo.record]: economy = 1; 11:11:45.780 [planetinfo.record]: techlevel = 9; 11:11:45.780 [planetinfo.record]: population = 42; 11:11:45.780 [planetinfo.record]: productivity = 24192; 11:11:45.780 [planetinfo.record]: name = "Esusti"; 11:11:45.780 [planetinfo.record]: inhabitant = "Human Colonial"; 11:11:45.780 [planetinfo.record]: inhabitants = "Human Colonials"; 11:11:45.780 [planetinfo.record]: description = "This world is very well known for its inhabitants’ ancient loathing of discos and the Esustiian spotted cat."; 11:11:45.796 [planetinfo.record]: air_color = 0.592267, 0.945014, 0.950221, 1; 11:11:45.796 [planetinfo.record]: cloud_alpha = 1.000000; 11:11:45.796 [planetinfo.record]: cloud_color = 0.828719, 0.853516, 0.400085, 1; 11:11:45.796 [planetinfo.record]: cloud_fraction = 0.630000; 11:11:45.796 [planetinfo.record]: land_color = 0.0154688, 0.66, 0.448513, 1; 11:11:45.796 [planetinfo.record]: land_fraction = 0.380000; 11:11:45.796 [planetinfo.record]: polar_cloud_color = 0.868029, 0.884082, 0.590539, 1; 11:11:45.796 [planetinfo.record]: polar_land_color = 0.705973, 0.934, 0.859179, 1; 11:11:45.796 [planetinfo.record]: polar_sea_color = 0.944336, 0.92801, 0.896842, 1; 11:11:45.796 [planetinfo.record]: sea_color = 0.556641, 0.518147, 0.44466, 1; 11:11:45.796 [planetinfo.record]: rotation_speed = 0.002123 11:11:45.796 [planetinfo.record]: planet zpos = 630360.000000 11:11:45.796 [planetinfo.record]: sun_radius = 159507.293701 11:11:45.796 [planetinfo.record]: sun_vector = 0.789 -0.612 -0.059 11:11:45.796 [planetinfo.record]: sun_distance = 1103130 11:11:45.796 [planetinfo.record]: corona_flare = 0.079471 11:11:45.796 [planetinfo.record]: corona_hues = 0.635406 11:11:45.796 [planetinfo.record]: sun_color = 0.693704, 0.690963, 0.471902, 1 11:11:45.796 [planetinfo.record]: corona_shimmer = 1.077328 11:11:45.796 [planetinfo.record]: station_vector = 0.574 0.661 0.483 11:11:45.796 [planetinfo.record]: station = coriolis 11:11:50.375 [PLANETINFO OVER]: Done 11:11:50.377 [PLANETINFO LOGGING]: 0 63 11:11:51.384 [planetinfo.record]: seed = 204 168 61 8 11:11:51.385 [planetinfo.record]: coordinates = 168 23 11:11:51.385 [planetinfo.record]: government = 1; 11:11:51.385 [planetinfo.record]: economy = 7; 11:11:51.385 [planetinfo.record]: techlevel = 1; 11:11:51.385 [planetinfo.record]: population = 13; 11:11:51.385 [planetinfo.record]: productivity = 1560; 11:11:51.385 [planetinfo.record]: name = "Ususor"; 11:11:51.385 [planetinfo.record]: inhabitant = "Human Colonial"; 11:11:51.385 [planetinfo.record]: inhabitants = "Human Colonials"; 11:11:51.385 [planetinfo.record]: description = "This planet is very notable for its inhabitants’ weird shyness and the Ususorian edible poet."; 11:11:51.404 [planetinfo.record]: air_color = 0.72964, 0.776456, 0.934611, 1; 11:11:51.404 [planetinfo.record]: cloud_alpha = 1.000000; 11:11:51.404 [planetinfo.record]: cloud_color = 0.759377, 0.791132, 0.806641, 1; 11:11:51.404 [planetinfo.record]: cloud_fraction = 0.380000; 11:11:51.404 [planetinfo.record]: land_color = 0.66, 0.162422, 0.523943, 1; 11:11:51.404 [planetinfo.record]: land_fraction = 0.560000; 11:11:51.404 [planetinfo.record]: polar_cloud_color = 0.831385, 0.852618, 0.862988, 1; 11:11:51.404 [planetinfo.record]: polar_land_color = 0.934, 0.757963, 0.885865, 1; 11:11:51.404 [planetinfo.record]: polar_sea_color = 0.933398, 0.864757, 0.859565, 1; 11:11:51.404 [planetinfo.record]: sea_color = 0.666016, 0.470101, 0.455284, 1; 11:11:51.404 [planetinfo.record]: rotation_speed = 0.000146 11:11:51.404 [planetinfo.record]: planet zpos = 704480.000000 11:11:51.404 [planetinfo.record]: sun_radius = 114785.589600 11:11:51.404 [planetinfo.record]: sun_vector = -0.889 -0.362 -0.282 11:11:51.404 [planetinfo.record]: sun_distance = 1006400 11:11:51.404 [planetinfo.record]: corona_flare = 0.053717 11:11:51.404 [planetinfo.record]: corona_hues = 0.708305 11:11:51.404 [planetinfo.record]: sun_color = 0.391074, 0.638344, 0.728015, 1 11:11:51.404 [planetinfo.record]: corona_shimmer = 0.698708 11:11:51.412 [planetinfo.record]: station_vector = 0.079 0.987 0.139 11:11:51.412 [planetinfo.record]: station = coriolis 11:11:56.534 [PLANETINFO OVER]: Done 11:11:56.535 [PLANETINFO LOGGING]: 0 64 11:11:57.555 [planetinfo.record]: seed = 200 73 83 139 11:11:57.555 [planetinfo.record]: coordinates = 73 121 11:11:57.555 [planetinfo.record]: government = 1; 11:11:57.555 [planetinfo.record]: economy = 3; 11:11:57.555 [planetinfo.record]: techlevel = 6; 11:11:57.555 [planetinfo.record]: population = 29; 11:11:57.555 [planetinfo.record]: productivity = 8120; 11:11:57.555 [planetinfo.record]: name = "Maregeis"; 11:11:57.555 [planetinfo.record]: inhabitant = "Human Colonial"; 11:11:57.555 [planetinfo.record]: inhabitants = "Human Colonials"; 11:11:57.556 [planetinfo.record]: description = "This world is fabled for its ancient Maregeisian Bidial tulip plantations."; 11:11:57.556 [planetinfo.record]: air_color = 0.583766, 0.920063, 0.971033, 1; 11:11:57.556 [planetinfo.record]: cloud_alpha = 1.000000; 11:11:57.556 [planetinfo.record]: cloud_color = 0.693609, 0.916016, 0.368553, 1; 11:11:57.556 [planetinfo.record]: cloud_fraction = 0.550000; 11:11:57.556 [planetinfo.record]: land_color = 0.527344, 0.473785, 0.490104, 1; 11:11:57.556 [planetinfo.record]: land_fraction = 0.440000; 11:11:57.557 [planetinfo.record]: polar_cloud_color = 0.773781, 0.912207, 0.571466, 1; 11:11:57.557 [planetinfo.record]: polar_land_color = 0.947266, 0.923214, 0.930542, 1; 11:11:57.557 [planetinfo.record]: polar_sea_color = 0.931055, 0.92864, 0.869227, 1; 11:11:57.557 [planetinfo.record]: sea_color = 0.689453, 0.682299, 0.506317, 1; 11:11:57.557 [planetinfo.record]: rotation_speed = 0.002613 11:11:57.557 [planetinfo.record]: planet zpos = 741650.000000 11:11:57.557 [planetinfo.record]: sun_radius = 157469.873810 11:11:57.557 [planetinfo.record]: sun_vector = 0.095 0.938 -0.333 11:11:57.557 [planetinfo.record]: sun_distance = 912800 11:11:57.557 [planetinfo.record]: corona_flare = 0.042410 11:11:57.557 [planetinfo.record]: corona_hues = 0.762062 11:11:57.557 [planetinfo.record]: sun_color = 0.707233, 0.375802, 0.283911, 1 11:11:57.557 [planetinfo.record]: corona_shimmer = 0.376095 11:11:57.557 [planetinfo.record]: station_vector = -0.818 0.562 0.126 11:11:57.557 [planetinfo.record]: station = coriolis 11:12:02.112 [PLANETINFO OVER]: Done 11:12:02.113 [PLANETINFO LOGGING]: 0 65 11:12:03.134 [planetinfo.record]: seed = 56 253 29 111 11:12:03.134 [planetinfo.record]: coordinates = 253 35 11:12:03.134 [planetinfo.record]: government = 7; 11:12:03.134 [planetinfo.record]: economy = 3; 11:12:03.134 [planetinfo.record]: techlevel = 9; 11:12:03.134 [planetinfo.record]: population = 47; 11:12:03.134 [planetinfo.record]: productivity = 28952; 11:12:03.134 [planetinfo.record]: name = "Aate"; 11:12:03.134 [planetinfo.record]: inhabitant = "Human Colonial"; 11:12:03.134 [planetinfo.record]: inhabitants = "Human Colonials"; 11:12:03.134 [planetinfo.record]: description = "The world Aate is scourged by killer mountain lobstoids."; 11:12:03.136 [planetinfo.record]: air_color = 0.670132, 0.824338, 0.939814, 1; 11:12:03.137 [planetinfo.record]: cloud_alpha = 1.000000; 11:12:03.137 [planetinfo.record]: cloud_color = 0.607063, 0.822266, 0.670951, 1; 11:12:03.137 [planetinfo.record]: cloud_fraction = 0.400000; 11:12:03.137 [planetinfo.record]: land_color = 0.27897, 0.548828, 0.0943298, 1; 11:12:03.137 [planetinfo.record]: land_fraction = 0.360000; 11:12:03.137 [planetinfo.record]: polar_cloud_color = 0.727707, 0.87002, 0.769956, 1; 11:12:03.137 [planetinfo.record]: polar_land_color = 0.828939, 0.945117, 0.749448, 1; 11:12:03.137 [planetinfo.record]: polar_sea_color = 0.892173, 0.861508, 0.909375, 1; 11:12:03.137 [planetinfo.record]: sea_color = 0.837678, 0.715442, 0.90625, 1; 11:12:03.137 [planetinfo.record]: rotation_speed = 0.000675 11:12:03.137 [planetinfo.record]: planet zpos = 898170.000000 11:12:03.137 [planetinfo.record]: sun_radius = 197294.878235 11:12:03.137 [planetinfo.record]: sun_vector = -0.808 0.164 -0.567 11:12:03.137 [planetinfo.record]: sun_distance = 1312710 11:12:03.138 [planetinfo.record]: corona_flare = 0.075233 11:12:03.138 [planetinfo.record]: corona_hues = 0.759209 11:12:03.138 [planetinfo.record]: sun_color = 0.541152, 0.601932, 0.714694, 1 11:12:03.138 [planetinfo.record]: corona_shimmer = 0.384913 11:12:03.138 [planetinfo.record]: station_vector = 0.856 -0.185 0.483 11:12:03.138 [planetinfo.record]: station = coriolis 11:12:07.763 [PLANETINFO OVER]: Done 11:12:07.764 [PLANETINFO LOGGING]: 0 66 11:12:08.783 [planetinfo.record]: seed = 28 251 27 135 11:12:08.783 [planetinfo.record]: coordinates = 251 252 11:12:08.783 [planetinfo.record]: government = 3; 11:12:08.783 [planetinfo.record]: economy = 4; 11:12:08.783 [planetinfo.record]: techlevel = 8; 11:12:08.783 [planetinfo.record]: population = 40; 11:12:08.783 [planetinfo.record]: productivity = 13440; 11:12:08.783 [planetinfo.record]: name = "Sori"; 11:12:08.783 [planetinfo.record]: inhabitant = "Human Colonial"; 11:12:08.784 [planetinfo.record]: inhabitants = "Human Colonials"; 11:12:08.784 [planetinfo.record]: description = "The world Sori is beset by evil disease."; 11:12:08.804 [planetinfo.record]: air_color = 0.598435, 0.942416, 0.936681, 1; 11:12:08.804 [planetinfo.record]: cloud_alpha = 1.000000; 11:12:08.804 [planetinfo.record]: cloud_color = 0.830078, 0.813992, 0.418282, 1; 11:12:08.804 [planetinfo.record]: cloud_fraction = 0.420000; 11:12:08.804 [planetinfo.record]: land_color = 0.619515, 0.141797, 0.66, 1; 11:12:08.804 [planetinfo.record]: land_fraction = 0.580000; 11:12:08.804 [planetinfo.record]: polar_cloud_color = 0.873535, 0.862955, 0.602688, 1; 11:12:08.804 [planetinfo.record]: polar_land_color = 0.919677, 0.750666, 0.934, 1; 11:12:08.804 [planetinfo.record]: polar_sea_color = 0.916679, 0.935156, 0.883741, 1; 11:12:08.804 [planetinfo.record]: sea_color = 0.597189, 0.648438, 0.505832, 1; 11:12:08.804 [planetinfo.record]: rotation_speed = 0.004357 11:12:08.804 [planetinfo.record]: planet zpos = 583080.000000 11:12:08.804 [planetinfo.record]: sun_radius = 82539.830627 11:12:08.804 [planetinfo.record]: sun_vector = 0.001 -0.508 0.861 11:12:08.804 [planetinfo.record]: sun_distance = 971800 11:12:08.804 [planetinfo.record]: corona_flare = 0.047322 11:12:08.804 [planetinfo.record]: corona_hues = 0.914474 11:12:08.804 [planetinfo.record]: sun_color = 0.666388, 0.626852, 0.539251, 1 11:12:08.804 [planetinfo.record]: corona_shimmer = 0.627655 11:12:08.805 [planetinfo.record]: station_vector = -0.022 0.744 0.668 11:12:08.805 [planetinfo.record]: station = coriolis 11:12:13.442 [PLANETINFO OVER]: Done 11:12:13.443 [PLANETINFO LOGGING]: 0 67 11:12:14.461 [planetinfo.record]: seed = 20 6 13 133 11:12:14.461 [planetinfo.record]: coordinates = 6 0 11:12:14.461 [planetinfo.record]: government = 2; 11:12:14.461 [planetinfo.record]: economy = 0; 11:12:14.461 [planetinfo.record]: techlevel = 10; 11:12:14.461 [planetinfo.record]: population = 43; 11:12:14.461 [planetinfo.record]: productivity = 20640; 11:12:14.461 [planetinfo.record]: name = "Cemave"; 11:12:14.461 [planetinfo.record]: inhabitant = "Human Colonial"; 11:12:14.461 [planetinfo.record]: inhabitants = "Human Colonials"; 11:12:14.461 [planetinfo.record]: description = "The world Cemave is beset by dreadful earthquakes."; 11:12:14.480 [planetinfo.record]: air_color = 0.460251, 0.461222, 0.930709, 1; 11:12:14.480 [planetinfo.record]: cloud_alpha = 1.000000; 11:12:14.480 [planetinfo.record]: cloud_color = 0.0709093, 0.0652084, 0.794922, 1; 11:12:14.480 [planetinfo.record]: cloud_fraction = 0.380000; 11:12:14.480 [planetinfo.record]: land_color = 0.180817, 0.585938, 0.528967, 1; 11:12:14.480 [planetinfo.record]: land_fraction = 0.670000; 11:12:14.480 [planetinfo.record]: polar_cloud_color = 0.369462, 0.365618, 0.857715, 1; 11:12:14.480 [planetinfo.record]: polar_land_color = 0.778683, 0.941406, 0.918523, 1; 11:12:14.480 [planetinfo.record]: polar_sea_color = 0.937305, 0.901183, 0.877259, 1; 11:12:14.480 [planetinfo.record]: sea_color = 0.626953, 0.530308, 0.466296, 1; 11:12:14.480 [planetinfo.record]: rotation_speed = 0.002366 11:12:14.480 [planetinfo.record]: planet zpos = 369180.000000 11:12:14.480 [planetinfo.record]: sun_radius = 113203.708191 11:12:14.480 [planetinfo.record]: sun_vector = 0.317 -0.901 0.295 11:12:14.480 [planetinfo.record]: sun_distance = 656320 11:12:14.480 [planetinfo.record]: corona_flare = 0.015576 11:12:14.480 [planetinfo.record]: corona_hues = 0.804153 11:12:14.480 [planetinfo.record]: sun_color = 0.84231, 0.795916, 0.652518, 1 11:12:14.480 [planetinfo.record]: corona_shimmer = 0.338208 11:12:14.480 [planetinfo.record]: station_vector = -0.586 -0.712 0.388 11:12:14.481 [planetinfo.record]: station = coriolis 11:12:18.938 [PLANETINFO OVER]: Done 11:12:18.939 [PLANETINFO LOGGING]: 0 68 11:12:19.945 [planetinfo.record]: seed = 160 39 51 202 11:12:19.945 [planetinfo.record]: coordinates = 39 22 11:12:19.945 [planetinfo.record]: government = 4; 11:12:19.945 [planetinfo.record]: economy = 6; 11:12:19.945 [planetinfo.record]: techlevel = 6; 11:12:19.945 [planetinfo.record]: population = 35; 11:12:19.945 [planetinfo.record]: productivity = 8960; 11:12:19.945 [planetinfo.record]: name = "Arusqudi"; 11:12:19.945 [planetinfo.record]: inhabitant = "Human Colonial"; 11:12:19.945 [planetinfo.record]: inhabitants = "Human Colonials"; 11:12:19.945 [planetinfo.record]: description = "This world is very fabled for its unusual oceans."; 11:12:19.959 [planetinfo.record]: air_color = 0.565683, 0.955424, 0.912424, 1; 11:12:19.959 [planetinfo.record]: cloud_alpha = 1.000000; 11:12:19.959 [planetinfo.record]: cloud_color = 0.869141, 0.695143, 0.325928, 1; 11:12:19.959 [planetinfo.record]: cloud_fraction = 0.390000; 11:12:19.960 [planetinfo.record]: land_color = 0.448849, 0.364494, 0.533203, 1; 11:12:19.960 [planetinfo.record]: land_fraction = 0.250000; 11:12:19.960 [planetinfo.record]: polar_cloud_color = 0.891113, 0.779615, 0.543022, 1; 11:12:19.960 [planetinfo.record]: polar_land_color = 0.909238, 0.871796, 0.94668, 1; 11:12:19.960 [planetinfo.record]: polar_sea_color = 0.88839, 0.929102, 0.873664, 1; 11:12:19.960 [planetinfo.record]: sea_color = 0.584717, 0.708984, 0.53977, 1; 11:12:19.960 [planetinfo.record]: rotation_speed = 0.002324 11:12:19.960 [planetinfo.record]: planet zpos = 595650.000000 11:12:19.960 [planetinfo.record]: sun_radius = 115967.724609 11:12:19.960 [planetinfo.record]: sun_vector = -0.932 0.197 0.304 11:12:19.960 [planetinfo.record]: sun_distance = 1191300 11:12:19.960 [planetinfo.record]: corona_flare = 0.022995 11:12:19.960 [planetinfo.record]: corona_hues = 0.739700 11:12:19.960 [planetinfo.record]: sun_color = 0.6944, 0.339239, 0.323222, 1 11:12:19.961 [planetinfo.record]: corona_shimmer = 0.412410 11:12:19.961 [planetinfo.record]: station_vector = -0.905 0.418 0.078 11:12:19.962 [planetinfo.record]: station = coriolis 11:12:24.578 [PLANETINFO OVER]: Done 11:12:24.578 [PLANETINFO LOGGING]: 0 69 11:12:25.600 [planetinfo.record]: seed = 96 205 141 208 11:12:25.600 [planetinfo.record]: coordinates = 205 250 11:12:25.600 [planetinfo.record]: government = 4; 11:12:25.600 [planetinfo.record]: economy = 2; 11:12:25.600 [planetinfo.record]: techlevel = 8; 11:12:25.600 [planetinfo.record]: population = 39; 11:12:25.600 [planetinfo.record]: productivity = 19968; 11:12:25.600 [planetinfo.record]: name = "Eredve"; 11:12:25.600 [planetinfo.record]: inhabitant = "Insect"; 11:12:25.600 [planetinfo.record]: inhabitants = "Insects"; 11:12:25.600 [planetinfo.record]: description = "This planet is beset by evil disease."; 11:12:25.613 [planetinfo.record]: air_color = 0.559829, 0.947619, 0.865489, 1; 11:12:25.613 [planetinfo.record]: cloud_alpha = 1.000000; 11:12:25.613 [planetinfo.record]: cloud_color = 0.845703, 0.513286, 0.313835, 1; 11:12:25.613 [planetinfo.record]: cloud_fraction = 0.350000; 11:12:25.613 [planetinfo.record]: land_color = 0.479531, 0.66, 0.50773, 1; 11:12:25.613 [planetinfo.record]: land_fraction = 0.380000; 11:12:25.614 [planetinfo.record]: polar_cloud_color = 0.880566, 0.664241, 0.534445, 1; 11:12:25.614 [planetinfo.record]: polar_land_color = 0.870152, 0.934, 0.880129, 1; 11:12:25.614 [planetinfo.record]: polar_sea_color = 0.924609, 0.824969, 0.811742, 1; 11:12:25.614 [planetinfo.record]: sea_color = 0.753906, 0.428927, 0.385788, 1; 11:12:25.614 [planetinfo.record]: rotation_speed = 0.000400 11:12:25.614 [planetinfo.record]: planet zpos = 332310.000000 11:12:25.614 [planetinfo.record]: sun_radius = 72723.697357 11:12:25.614 [planetinfo.record]: sun_vector = -0.177 0.364 -0.914 11:12:25.614 [planetinfo.record]: sun_distance = 694830 11:12:25.614 [planetinfo.record]: corona_flare = 0.001381 11:12:25.614 [planetinfo.record]: corona_hues = 0.991394 11:12:25.614 [planetinfo.record]: sun_color = 0.372186, 0.518352, 0.69628, 1 11:12:25.614 [planetinfo.record]: corona_shimmer = 0.537727 11:12:25.614 [planetinfo.record]: station_vector = -0.346 -0.904 0.252 11:12:25.615 [planetinfo.record]: station = coriolis 11:12:29.930 [PLANETINFO OVER]: Done 11:12:29.930 [PLANETINFO LOGGING]: 0 70 11:12:30.949 [planetinfo.record]: seed = 84 159 27 110 11:12:30.949 [planetinfo.record]: coordinates = 159 54 11:12:30.949 [planetinfo.record]: government = 2; 11:12:30.949 [planetinfo.record]: economy = 6; 11:12:30.949 [planetinfo.record]: techlevel = 5; 11:12:30.949 [planetinfo.record]: population = 29; 11:12:30.949 [planetinfo.record]: productivity = 5568; 11:12:30.949 [planetinfo.record]: name = "Regeatge"; 11:12:30.949 [planetinfo.record]: inhabitant = "Human Colonial"; 11:12:30.949 [planetinfo.record]: inhabitants = "Human Colonials"; 11:12:30.949 [planetinfo.record]: description = "Regeatge is reasonably well known for its great dense forests but scourged by frequent civil war."; 11:12:30.980 [planetinfo.record]: air_color = 0.616392, 0.565466, 0.86567, 1; 11:12:30.980 [planetinfo.record]: cloud_alpha = 1.000000; 11:12:30.980 [planetinfo.record]: cloud_color = 0.473477, 0.334938, 0.599609, 1; 11:12:30.980 [planetinfo.record]: cloud_fraction = 0.380000; 11:12:30.980 [planetinfo.record]: land_color = 0.414084, 0.591797, 0.0231171, 1; 11:12:30.980 [planetinfo.record]: land_fraction = 0.370000; 11:12:30.980 [planetinfo.record]: polar_cloud_color = 0.668613, 0.557446, 0.769824, 1; 11:12:30.980 [planetinfo.record]: polar_land_color = 0.87019, 0.94082, 0.714803, 1; 11:12:30.980 [planetinfo.record]: polar_sea_color = 0.925781, 0.917277, 0.84803, 1; 11:12:30.980 [planetinfo.record]: sea_color = 0.742188, 0.714917, 0.492859, 1; 11:12:30.980 [planetinfo.record]: rotation_speed = 0.001519 11:12:30.980 [planetinfo.record]: planet zpos = 721490.000000 11:12:30.980 [planetinfo.record]: sun_radius = 184032.513275 11:12:30.980 [planetinfo.record]: sun_vector = 0.643 0.157 -0.750 11:12:30.980 [planetinfo.record]: sun_distance = 1180620 11:12:30.980 [planetinfo.record]: corona_flare = 0.027161 11:12:30.980 [planetinfo.record]: corona_hues = 0.848473 11:12:30.980 [planetinfo.record]: sun_color = 0.839526, 0.686746, 0.520957, 1 11:12:30.980 [planetinfo.record]: corona_shimmer = 0.404039 11:12:30.981 [planetinfo.record]: station_vector = 0.763 0.353 0.542 11:12:30.981 [planetinfo.record]: station = coriolis 11:12:36.270 [PLANETINFO OVER]: Done 11:12:36.270 [PLANETINFO LOGGING]: 0 71 11:12:37.296 [planetinfo.record]: seed = 28 3 29 152 11:12:37.296 [planetinfo.record]: coordinates = 3 81 11:12:37.296 [planetinfo.record]: government = 3; 11:12:37.296 [planetinfo.record]: economy = 1; 11:12:37.296 [planetinfo.record]: techlevel = 11; 11:12:37.296 [planetinfo.record]: population = 49; 11:12:37.296 [planetinfo.record]: productivity = 24696; 11:12:37.296 [planetinfo.record]: name = "Edinso"; 11:12:37.296 [planetinfo.record]: inhabitant = "Human Colonial"; 11:12:37.296 [planetinfo.record]: inhabitants = "Human Colonials"; 11:12:37.296 [planetinfo.record]: description = "This planet is mildly noted for its pink Edinsoian Maarleil Maarleilweed plantations but scourged by dreadful solar activity."; 11:12:37.315 [planetinfo.record]: air_color = 0.744934, 0.756352, 0.946318, 1; 11:12:37.315 [planetinfo.record]: cloud_alpha = 1.000000; 11:12:37.315 [planetinfo.record]: cloud_color = 0.808914, 0.814052, 0.841797, 1; 11:12:37.315 [planetinfo.record]: cloud_fraction = 0.320000; 11:12:37.315 [planetinfo.record]: land_color = 0.66, 0.309375, 0.613433, 1; 11:12:37.315 [planetinfo.record]: land_fraction = 0.350000; 11:12:37.315 [planetinfo.record]: polar_cloud_color = 0.857353, 0.860706, 0.878809, 1; 11:12:37.315 [planetinfo.record]: polar_land_color = 0.934, 0.809953, 0.917525, 1; 11:12:37.315 [planetinfo.record]: polar_sea_color = 0.92549, 0.930273, 0.869043, 1; 11:12:37.315 [planetinfo.record]: sea_color = 0.682924, 0.697266, 0.513689, 1; 11:12:37.315 [planetinfo.record]: rotation_speed = 0.004562 11:12:37.315 [planetinfo.record]: planet zpos = 584040.000000 11:12:37.315 [planetinfo.record]: sun_radius = 119998.849640 11:12:37.315 [planetinfo.record]: sun_vector = 0.106 0.947 0.303 11:12:37.315 [planetinfo.record]: sun_distance = 778720 11:12:37.315 [planetinfo.record]: corona_flare = 0.067958 11:12:37.315 [planetinfo.record]: corona_hues = 0.563347 11:12:37.315 [planetinfo.record]: sun_color = 0.7388, 0.706373, 0.619129, 1 11:12:37.315 [planetinfo.record]: corona_shimmer = 0.411762 11:12:37.315 [planetinfo.record]: station_vector = -0.553 -0.450 0.702 11:12:37.315 [planetinfo.record]: station = icosahedron 11:12:42.476 [PLANETINFO OVER]: Done 11:12:42.477 [PLANETINFO LOGGING]: 0 72 11:12:43.483 [planetinfo.record]: seed = 56 12 83 128 11:12:43.483 [planetinfo.record]: coordinates = 12 135 11:12:43.483 [planetinfo.record]: government = 7; 11:12:43.483 [planetinfo.record]: economy = 7; 11:12:43.483 [planetinfo.record]: techlevel = 4; 11:12:43.483 [planetinfo.record]: population = 31; 11:12:43.483 [planetinfo.record]: productivity = 8184; 11:12:43.483 [planetinfo.record]: name = "Ra"; 11:12:43.483 [planetinfo.record]: inhabitant = "Human Colonial"; 11:12:43.483 [planetinfo.record]: inhabitants = "Human Colonials"; 11:12:43.483 [planetinfo.record]: description = "The world Ra is beset by deadly earthquakes."; 11:12:43.488 [planetinfo.record]: air_color = 0.434508, 0.648679, 0.897539, 1; 11:12:43.488 [planetinfo.record]: cloud_alpha = 1.000000; 11:12:43.488 [planetinfo.record]: cloud_color = 0.0353088, 0.695312, 0.447811, 1; 11:12:43.488 [planetinfo.record]: cloud_fraction = 0.360000; 11:12:43.488 [planetinfo.record]: land_color = 0.0979688, 0.35703, 0.66, 1; 11:12:43.488 [planetinfo.record]: land_fraction = 0.680000; 11:12:43.488 [planetinfo.record]: polar_cloud_color = 0.330634, 0.812891, 0.632044, 1; 11:12:43.488 [planetinfo.record]: polar_land_color = 0.73516, 0.826813, 0.934, 1; 11:12:43.488 [planetinfo.record]: polar_sea_color = 0.879185, 0.92832, 0.897227, 1; 11:12:43.489 [planetinfo.record]: sea_color = 0.565038, 0.716797, 0.620762, 1; 11:12:43.489 [planetinfo.record]: rotation_speed = 0.001974 11:12:43.489 [planetinfo.record]: planet zpos = 367640.000000 11:12:43.489 [planetinfo.record]: sun_radius = 81106.932983 11:12:43.489 [planetinfo.record]: sun_vector = -0.954 0.220 -0.201 11:12:43.489 [planetinfo.record]: sun_distance = 452480 11:12:43.489 [planetinfo.record]: corona_flare = 0.090234 11:12:43.489 [planetinfo.record]: corona_hues = 0.958115 11:12:43.489 [planetinfo.record]: sun_color = 0.814172, 0.683129, 0.652207, 1 11:12:43.489 [planetinfo.record]: corona_shimmer = 0.386766 11:12:43.489 [planetinfo.record]: station_vector = 0.109 -0.987 0.120 11:12:43.489 [planetinfo.record]: station = coriolis 11:12:47.820 [PLANETINFO OVER]: Done 11:12:47.821 [PLANETINFO LOGGING]: 0 73 11:12:48.844 [planetinfo.record]: seed = 72 53 61 234 11:12:48.844 [planetinfo.record]: coordinates = 53 160 11:12:48.844 [planetinfo.record]: government = 1; 11:12:48.844 [planetinfo.record]: economy = 2; 11:12:48.844 [planetinfo.record]: techlevel = 7; 11:12:48.844 [planetinfo.record]: population = 32; 11:12:48.844 [planetinfo.record]: productivity = 10240; 11:12:48.844 [planetinfo.record]: name = "Aronar"; 11:12:48.844 [planetinfo.record]: inhabitant = "Human Colonial"; 11:12:48.844 [planetinfo.record]: inhabitants = "Human Colonials"; 11:12:48.844 [planetinfo.record]: description = "Aronar is most famous for the Aronarian deadly goat and its hoopy casinos."; 11:12:48.860 [planetinfo.record]: air_color = 0.723193, 0.632192, 0.837703, 1; 11:12:48.860 [planetinfo.record]: cloud_alpha = 1.000000; 11:12:48.860 [planetinfo.record]: cloud_color = 0.515625, 0.445129, 0.491392, 1; 11:12:48.860 [planetinfo.record]: cloud_fraction = 0.330000; 11:12:48.860 [planetinfo.record]: land_color = 0.434708, 0.519531, 0.0852356, 1; 11:12:48.861 [planetinfo.record]: land_fraction = 0.550000; 11:12:48.861 [planetinfo.record]: polar_cloud_color = 0.732031, 0.66948, 0.710529, 1; 11:12:48.861 [planetinfo.record]: polar_land_color = 0.90935, 0.948047, 0.74992, 1; 11:12:48.861 [planetinfo.record]: polar_sea_color = 0.90838, 0.930469, 0.872769, 1; 11:12:48.861 [planetinfo.record]: sea_color = 0.629289, 0.695312, 0.522842, 1; 11:12:48.861 [planetinfo.record]: rotation_speed = 0.000075 11:12:48.861 [planetinfo.record]: planet zpos = 651480.000000 11:12:48.861 [planetinfo.record]: sun_radius = 126391.421051 11:12:48.861 [planetinfo.record]: sun_vector = 0.027 0.959 0.281 11:12:48.861 [planetinfo.record]: sun_distance = 1085800 11:12:48.861 [planetinfo.record]: corona_flare = 0.032338 11:12:48.861 [planetinfo.record]: corona_hues = 0.904243 11:12:48.862 [planetinfo.record]: sun_color = 0.711954, 0.758815, 0.760324, 1 11:12:48.862 [planetinfo.record]: corona_shimmer = 0.504612 11:12:48.862 [planetinfo.record]: station_vector = 0.845 -0.255 0.470 11:12:48.863 [planetinfo.record]: station = coriolis 11:12:53.281 [PLANETINFO OVER]: Done 11:12:53.281 [PLANETINFO LOGGING]: 0 74 11:12:54.300 [planetinfo.record]: seed = 76 138 91 42 11:12:54.300 [planetinfo.record]: coordinates = 138 223 11:12:54.300 [planetinfo.record]: government = 1; 11:12:54.300 [planetinfo.record]: economy = 7; 11:12:54.300 [planetinfo.record]: techlevel = 3; 11:12:54.300 [planetinfo.record]: population = 21; 11:12:54.300 [planetinfo.record]: productivity = 2520; 11:12:54.300 [planetinfo.record]: name = "Arraesso"; 11:12:54.300 [planetinfo.record]: inhabitant = "Human Colonial"; 11:12:54.301 [planetinfo.record]: inhabitants = "Human Colonials"; 11:12:54.301 [planetinfo.record]: description = "This planet is notable for its unusual oceans and its inhabitants’ exceptional loathing of food blenders."; 11:12:54.308 [planetinfo.record]: air_color = 0.487826, 0.642467, 0.859816, 1; 11:12:54.308 [planetinfo.record]: cloud_alpha = 1.000000; 11:12:54.309 [planetinfo.record]: cloud_color = 0.175064, 0.582031, 0.486648, 1; 11:12:54.309 [planetinfo.record]: cloud_fraction = 0.170000; 11:12:54.309 [planetinfo.record]: land_color = 0.560722, 0.250078, 0.66, 1; 11:12:54.309 [planetinfo.record]: land_fraction = 0.600000; 11:12:54.309 [planetinfo.record]: polar_cloud_color = 0.428949, 0.761914, 0.683875, 1; 11:12:54.309 [planetinfo.record]: polar_land_color = 0.898877, 0.788975, 0.934, 1; 11:12:54.309 [planetinfo.record]: polar_sea_color = 0.932813, 0.924767, 0.872234, 1; 11:12:54.309 [planetinfo.record]: sea_color = 0.671875, 0.648695, 0.497345, 1; 11:12:54.309 [planetinfo.record]: rotation_speed = 0.003673 11:12:54.309 [planetinfo.record]: planet zpos = 606540.000000 11:12:54.309 [planetinfo.record]: sun_radius = 118493.450317 11:12:54.309 [planetinfo.record]: sun_vector = 0.324 -0.249 -0.912 11:12:54.309 [planetinfo.record]: sun_distance = 1102800 11:12:54.310 [planetinfo.record]: corona_flare = 0.052530 11:12:54.310 [planetinfo.record]: corona_hues = 0.788620 11:12:54.310 [planetinfo.record]: sun_color = 0.38214, 0.672398, 0.739752, 1 11:12:54.310 [planetinfo.record]: corona_shimmer = 1.046433 11:12:54.310 [planetinfo.record]: station_vector = -0.818 0.509 0.269 11:12:54.310 [planetinfo.record]: station = coriolis 11:12:59.044 [PLANETINFO OVER]: Done 11:12:59.045 [PLANETINFO LOGGING]: 0 75 11:13:00.051 [planetinfo.record]: seed = 228 7 109 229 11:13:00.052 [planetinfo.record]: coordinates = 7 73 11:13:00.052 [planetinfo.record]: government = 4; 11:13:00.052 [planetinfo.record]: economy = 1; 11:13:00.052 [planetinfo.record]: techlevel = 11; 11:13:00.052 [planetinfo.record]: population = 50; 11:13:00.052 [planetinfo.record]: productivity = 28800; 11:13:00.052 [planetinfo.record]: name = "Cevege"; 11:13:00.052 [planetinfo.record]: inhabitant = "Human Colonial"; 11:13:00.052 [planetinfo.record]: inhabitants = "Human Colonials"; 11:13:00.052 [planetinfo.record]: description = "This world is a revolting dump."; 11:13:00.062 [planetinfo.record]: air_color = 0.435044, 0.806322, 0.844207, 1; 11:13:00.062 [planetinfo.record]: cloud_alpha = 1.000000; 11:13:00.062 [planetinfo.record]: cloud_color = 0.402821, 0.535156, 0.0773468, 1; 11:13:00.062 [planetinfo.record]: cloud_fraction = 0.250000; 11:13:00.062 [planetinfo.record]: land_color = 0.66, 0.518203, 0.433125, 1; 11:13:00.062 [planetinfo.record]: land_fraction = 0.690000; 11:13:00.062 [planetinfo.record]: polar_cloud_color = 0.626325, 0.74082, 0.344727, 1; 11:13:00.062 [planetinfo.record]: polar_land_color = 0.934, 0.883834, 0.853734, 1; 11:13:00.062 [planetinfo.record]: polar_sea_color = 0.877383, 0.848037, 0.904102, 1; 11:13:00.062 [planetinfo.record]: sea_color = 0.845623, 0.721111, 0.958984, 1; 11:13:00.062 [planetinfo.record]: rotation_speed = 0.001785 11:13:00.062 [planetinfo.record]: planet zpos = 574420.000000 11:13:00.062 [planetinfo.record]: sun_radius = 97490.074768 11:13:00.063 [planetinfo.record]: sun_vector = 0.287 -0.047 -0.957 11:13:00.063 [planetinfo.record]: sun_distance = 902660 11:13:00.063 [planetinfo.record]: corona_flare = 0.095323 11:13:00.063 [planetinfo.record]: corona_hues = 0.641968 11:13:00.063 [planetinfo.record]: sun_color = 0.369937, 0.441129, 0.748132, 1 11:13:00.063 [planetinfo.record]: corona_shimmer = 0.311934 11:13:00.063 [planetinfo.record]: station_vector = 0.233 0.189 0.954 11:13:00.063 [planetinfo.record]: station = dodecahedron 11:13:04.949 [PLANETINFO OVER]: Done 11:13:04.950 [PLANETINFO LOGGING]: 0 76 11:13:05.968 [planetinfo.record]: seed = 144 63 179 153 11:13:05.969 [planetinfo.record]: coordinates = 63 35 11:13:05.969 [planetinfo.record]: government = 2; 11:13:05.969 [planetinfo.record]: economy = 3; 11:13:05.969 [planetinfo.record]: techlevel = 8; 11:13:05.969 [planetinfo.record]: population = 38; 11:13:05.969 [planetinfo.record]: productivity = 12768; 11:13:05.969 [planetinfo.record]: name = "Orteve"; 11:13:05.969 [planetinfo.record]: inhabitant = "Black Fat Humanoid"; 11:13:05.969 [planetinfo.record]: inhabitants = "Black Fat Humanoids"; 11:13:05.969 [planetinfo.record]: description = "This world is fabled for its fabulous vicious Gezabeza gargle blasters."; 11:13:05.980 [planetinfo.record]: air_color = 0.530131, 0.991195, 0.976311, 1; 11:13:05.981 [planetinfo.record]: cloud_alpha = 1.000000; 11:13:05.981 [planetinfo.record]: cloud_color = 0.976562, 0.909686, 0.198364, 1; 11:13:05.981 [planetinfo.record]: cloud_fraction = 0.360000; 11:13:05.981 [planetinfo.record]: land_color = 0.409922, 0.66, 0.478303, 1; 11:13:05.981 [planetinfo.record]: land_fraction = 0.480000; 11:13:05.981 [planetinfo.record]: polar_cloud_color = 0.939453, 0.899244, 0.471561, 1; 11:13:05.981 [planetinfo.record]: polar_land_color = 0.845525, 0.934, 0.869718, 1; 11:13:05.981 [planetinfo.record]: polar_sea_color = 0.886377, 0.930078, 0.877307, 1; 11:13:05.982 [planetinfo.record]: sea_color = 0.567804, 0.699219, 0.540529, 1; 11:13:05.982 [planetinfo.record]: rotation_speed = 0.001681 11:13:05.982 [planetinfo.record]: planet zpos = 621960.000000 11:13:05.982 [planetinfo.record]: sun_radius = 141170.633850 11:13:05.982 [planetinfo.record]: sun_vector = -0.353 -0.934 0.054 11:13:05.982 [planetinfo.record]: sun_distance = 1140260 11:13:05.982 [planetinfo.record]: corona_flare = 0.021663 11:13:05.982 [planetinfo.record]: corona_hues = 0.844582 11:13:05.982 [planetinfo.record]: sun_color = 0.688882, 0.564156, 0.493518, 1 11:13:05.983 [planetinfo.record]: corona_shimmer = 0.343487 11:13:05.983 [planetinfo.record]: station_vector = -0.385 -0.866 0.318 11:13:05.983 [planetinfo.record]: station = coriolis 11:13:09.997 [PLANETINFO OVER]: Done 11:13:09.998 [PLANETINFO LOGGING]: 0 77 11:13:11.013 [planetinfo.record]: seed = 240 108 45 64 11:13:11.013 [planetinfo.record]: coordinates = 108 214 11:13:11.013 [planetinfo.record]: government = 6; 11:13:11.013 [planetinfo.record]: economy = 6; 11:13:11.013 [planetinfo.record]: techlevel = 4; 11:13:11.013 [planetinfo.record]: population = 29; 11:13:11.013 [planetinfo.record]: productivity = 9280; 11:13:11.013 [planetinfo.record]: name = "Geerra"; 11:13:11.013 [planetinfo.record]: inhabitant = "Human Colonial"; 11:13:11.013 [planetinfo.record]: inhabitants = "Human Colonials"; 11:13:11.014 [planetinfo.record]: description = "This planet is reasonably noted for its exotic goat soup."; 11:13:11.044 [planetinfo.record]: air_color = 0.648665, 0.823512, 0.958676, 1; 11:13:11.044 [planetinfo.record]: cloud_alpha = 1.000000; 11:13:11.044 [planetinfo.record]: cloud_color = 0.55275, 0.878906, 0.657222, 1; 11:13:11.044 [planetinfo.record]: cloud_fraction = 0.360000; 11:13:11.044 [planetinfo.record]: land_color = 0.195129, 0.542969, 0.428834, 1; 11:13:11.044 [planetinfo.record]: land_fraction = 0.530000; 11:13:11.044 [planetinfo.record]: polar_cloud_color = 0.687809, 0.895508, 0.754338, 1; 11:13:11.044 [planetinfo.record]: polar_land_color = 0.794243, 0.945703, 0.896005, 1; 11:13:11.044 [planetinfo.record]: polar_sea_color = 0.907501, 0.935938, 0.888044, 1; 11:13:11.044 [planetinfo.record]: sea_color = 0.562768, 0.640625, 0.509497, 1; 11:13:11.044 [planetinfo.record]: rotation_speed = 0.004693 11:13:11.044 [planetinfo.record]: planet zpos = 321640.000000 11:13:11.044 [planetinfo.record]: sun_radius = 73452.471924 11:13:11.044 [planetinfo.record]: sun_vector = 0.200 0.851 0.485 11:13:11.044 [planetinfo.record]: sun_distance = 555560 11:13:11.044 [planetinfo.record]: corona_flare = 0.078940 11:13:11.045 [planetinfo.record]: corona_hues = 0.802193 11:13:11.045 [planetinfo.record]: sun_color = 0.788065, 0.749304, 0.664491, 1 11:13:11.045 [planetinfo.record]: corona_shimmer = 0.393993 11:13:11.045 [planetinfo.record]: station_vector = 0.618 -0.675 0.404 11:13:11.045 [planetinfo.record]: station = coriolis 11:13:15.364 [PLANETINFO OVER]: Done 11:13:15.364 [PLANETINFO LOGGING]: 0 78 11:13:16.382 [planetinfo.record]: seed = 4 244 219 167 11:13:16.382 [planetinfo.record]: coordinates = 244 48 11:13:16.382 [planetinfo.record]: government = 0; 11:13:16.382 [planetinfo.record]: economy = 2; 11:13:16.382 [planetinfo.record]: techlevel = 5; 11:13:16.382 [planetinfo.record]: population = 23; 11:13:16.382 [planetinfo.record]: productivity = 5888; 11:13:16.382 [planetinfo.record]: name = "Soinuste"; 11:13:16.383 [planetinfo.record]: inhabitant = "Fierce Harmless Fat Insect"; 11:13:16.383 [planetinfo.record]: inhabitants = "Fierce Harmless Fat Insects"; 11:13:16.383 [planetinfo.record]: description = "This planet is beset by deadly earthquakes."; 11:13:16.389 [planetinfo.record]: air_color = 0.632564, 0.913799, 0.90911, 1; 11:13:16.390 [planetinfo.record]: cloud_alpha = 1.000000; 11:13:16.390 [planetinfo.record]: cloud_color = 0.744141, 0.734716, 0.502876, 1; 11:13:16.390 [planetinfo.record]: cloud_fraction = 0.260000; 11:13:16.390 [planetinfo.record]: land_color = 0.635629, 0.66, 0.634219, 1; 11:13:16.390 [planetinfo.record]: land_fraction = 0.350000; 11:13:16.390 [planetinfo.record]: polar_cloud_color = 0.834863, 0.828255, 0.665689, 1; 11:13:16.390 [planetinfo.record]: polar_land_color = 0.925378, 0.934, 0.924879, 1; 11:13:16.390 [planetinfo.record]: polar_sea_color = 0.759061, 0.941016, 0.838666, 1; 11:13:16.390 [planetinfo.record]: sea_color = 0.133636, 0.589844, 0.333227, 1; 11:13:16.390 [planetinfo.record]: rotation_speed = 0.000934 11:13:16.390 [planetinfo.record]: planet zpos = 679280.000000 11:13:16.391 [planetinfo.record]: sun_radius = 122368.334351 11:13:16.391 [planetinfo.record]: sun_vector = 0.170 0.404 -0.899 11:13:16.391 [planetinfo.record]: sun_distance = 1067440 11:13:16.391 [planetinfo.record]: corona_flare = 0.073120 11:13:16.391 [planetinfo.record]: corona_hues = 0.920746 11:13:16.391 [planetinfo.record]: sun_color = 0.296121, 0.444684, 0.805905, 1 11:13:16.391 [planetinfo.record]: corona_shimmer = 0.418407 11:13:16.391 [planetinfo.record]: station_vector = 0.166 0.968 0.189 11:13:16.391 [planetinfo.record]: station = coriolis 11:13:21.518 [PLANETINFO OVER]: Done 11:13:21.519 [PLANETINFO LOGGING]: 0 79 11:13:22.531 [planetinfo.record]: seed = 108 220 253 16 11:13:22.531 [planetinfo.record]: coordinates = 220 104 11:13:22.531 [planetinfo.record]: government = 5; 11:13:22.531 [planetinfo.record]: economy = 0; 11:13:22.531 [planetinfo.record]: techlevel = 10; 11:13:22.531 [planetinfo.record]: population = 46; 11:13:22.531 [planetinfo.record]: productivity = 33120; 11:13:22.531 [planetinfo.record]: name = "Erlage"; 11:13:22.531 [planetinfo.record]: inhabitant = "Green Fat Bird"; 11:13:22.531 [planetinfo.record]: inhabitants = "Green Fat Birds"; 11:13:22.531 [planetinfo.record]: description = "This world is reasonably well known for the Erlageian tree ant but cursed by vicious mountain goats."; 11:13:22.558 [planetinfo.record]: air_color = 0.612284, 0.901441, 0.83343, 1; 11:13:22.559 [planetinfo.record]: cloud_alpha = 1.000000; 11:13:22.559 [planetinfo.record]: cloud_color = 0.707031, 0.528439, 0.45018, 1; 11:13:22.559 [planetinfo.record]: cloud_fraction = 0.270000; 11:13:22.559 [planetinfo.record]: land_color = 0.363516, 0.604409, 0.66, 1; 11:13:22.559 [planetinfo.record]: land_fraction = 0.690000; 11:13:22.559 [planetinfo.record]: polar_cloud_color = 0.818164, 0.688999, 0.632399, 1; 11:13:22.560 [planetinfo.record]: polar_land_color = 0.829107, 0.914333, 0.934, 1; 11:13:22.560 [planetinfo.record]: polar_sea_color = 0.947266, 0.90743, 0.898237, 1; 11:13:22.560 [planetinfo.record]: sea_color = 0.527344, 0.438638, 0.418167, 1; 11:13:22.560 [planetinfo.record]: rotation_speed = 0.003908 11:13:22.560 [planetinfo.record]: planet zpos = 303600.000000 11:13:22.560 [planetinfo.record]: sun_radius = 79432.333374 11:13:22.560 [planetinfo.record]: sun_vector = -0.002 -0.940 0.341 11:13:22.560 [planetinfo.record]: sun_distance = 667920 11:13:22.560 [planetinfo.record]: corona_flare = 0.038710 11:13:22.561 [planetinfo.record]: corona_hues = 0.890488 11:13:22.561 [planetinfo.record]: sun_color = 0.344284, 0.36746, 0.68447, 1 11:13:22.561 [planetinfo.record]: corona_shimmer = 0.375049 11:13:22.561 [planetinfo.record]: station_vector = -0.981 0.132 0.146 11:13:22.561 [planetinfo.record]: station = coriolis 11:13:26.994 [PLANETINFO OVER]: Done 11:13:26.995 [PLANETINFO LOGGING]: 0 80 11:13:28.001 [planetinfo.record]: seed = 168 169 83 66 11:13:28.001 [planetinfo.record]: coordinates = 169 67 11:13:28.001 [planetinfo.record]: government = 5; 11:13:28.001 [planetinfo.record]: economy = 3; 11:13:28.001 [planetinfo.record]: techlevel = 8; 11:13:28.001 [planetinfo.record]: population = 41; 11:13:28.001 [planetinfo.record]: productivity = 20664; 11:13:28.001 [planetinfo.record]: name = "Xeaan"; 11:13:28.001 [planetinfo.record]: inhabitant = "Human Colonial"; 11:13:28.001 [planetinfo.record]: inhabitants = "Human Colonials"; 11:13:28.001 [planetinfo.record]: description = "This world is ravaged by unpredictable civil war."; 11:13:28.020 [planetinfo.record]: air_color = 0.663412, 0.809548, 0.954773, 1; 11:13:28.021 [planetinfo.record]: cloud_alpha = 1.000000; 11:13:28.021 [planetinfo.record]: cloud_color = 0.592804, 0.867188, 0.732139, 1; 11:13:28.021 [planetinfo.record]: cloud_fraction = 0.210000; 11:13:28.021 [planetinfo.record]: land_color = 0.20625, 0.234609, 0.66, 1; 11:13:28.021 [planetinfo.record]: land_fraction = 0.670000; 11:13:28.021 [planetinfo.record]: polar_cloud_color = 0.714187, 0.890234, 0.803586, 1; 11:13:28.021 [planetinfo.record]: polar_land_color = 0.773469, 0.783502, 0.934, 1; 11:13:28.021 [planetinfo.record]: polar_sea_color = 0.928516, 0.859787, 0.838747, 1; 11:13:28.021 [planetinfo.record]: sea_color = 0.714844, 0.503192, 0.4384, 1; 11:13:28.021 [planetinfo.record]: rotation_speed = 0.001344 11:13:28.021 [planetinfo.record]: planet zpos = 419640.000000 11:13:28.021 [planetinfo.record]: sun_radius = 65692.545166 11:13:28.021 [planetinfo.record]: sun_vector = 0.120 0.288 0.950 11:13:28.021 [planetinfo.record]: sun_distance = 769340 11:13:28.021 [planetinfo.record]: corona_flare = 0.030502 11:13:28.021 [planetinfo.record]: corona_hues = 0.700912 11:13:28.021 [planetinfo.record]: sun_color = 0.5537, 0.657419, 0.803247, 1 11:13:28.021 [planetinfo.record]: corona_shimmer = 0.318016 11:13:28.022 [planetinfo.record]: station_vector = -0.311 -0.932 0.187 11:13:28.022 [planetinfo.record]: station = coriolis 11:13:32.511 [PLANETINFO OVER]: Done 11:13:32.512 [PLANETINFO LOGGING]: 0 81 11:13:33.526 [planetinfo.record]: seed = 88 140 93 214 11:13:33.526 [planetinfo.record]: coordinates = 140 27 11:13:33.526 [planetinfo.record]: government = 3; 11:13:33.526 [planetinfo.record]: economy = 3; 11:13:33.526 [planetinfo.record]: techlevel = 6; 11:13:33.526 [planetinfo.record]: population = 31; 11:13:33.526 [planetinfo.record]: productivity = 12152; 11:13:33.526 [planetinfo.record]: name = "Veis"; 11:13:33.526 [planetinfo.record]: inhabitant = "Human Colonial"; 11:13:33.526 [planetinfo.record]: inhabitants = "Human Colonials"; 11:13:33.526 [planetinfo.record]: description = "The planet Veis is a boring world."; 11:13:33.540 [planetinfo.record]: air_color = 0.688814, 0.611372, 0.843557, 1; 11:13:33.540 [planetinfo.record]: cloud_alpha = 1.000000; 11:13:33.540 [planetinfo.record]: cloud_color = 0.533203, 0.412399, 0.531316, 1; 11:13:33.540 [planetinfo.record]: cloud_fraction = 0.460000; 11:13:33.540 [planetinfo.record]: land_color = 0.297363, 0.246094, 0.65625, 1; 11:13:33.540 [planetinfo.record]: land_fraction = 0.270000; 11:13:33.540 [planetinfo.record]: polar_cloud_color = 0.739941, 0.635165, 0.738304, 1; 11:13:33.540 [planetinfo.record]: polar_land_color = 0.806628, 0.788379, 0.934375, 1; 11:13:33.540 [planetinfo.record]: polar_sea_color = 0.931445, 0.926153, 0.869864, 1; 11:13:33.540 [planetinfo.record]: sea_color = 0.685547, 0.669967, 0.504252, 1; 11:13:33.540 [planetinfo.record]: rotation_speed = 0.004455 11:13:33.540 [planetinfo.record]: planet zpos = 449200.000000 11:13:33.540 [planetinfo.record]: sun_radius = 122403.161621 11:13:33.540 [planetinfo.record]: sun_vector = -0.061 0.426 -0.903 11:13:33.540 [planetinfo.record]: sun_distance = 898400 11:13:33.540 [planetinfo.record]: corona_flare = 0.076566 11:13:33.540 [planetinfo.record]: corona_hues = 0.624847 11:13:33.540 [planetinfo.record]: sun_color = 0.250376, 0.820519, 0.84953, 1 11:13:33.540 [planetinfo.record]: corona_shimmer = 0.268705 11:13:33.541 [planetinfo.record]: station_vector = -0.669 -0.381 0.639 11:13:33.541 [planetinfo.record]: station = coriolis 11:13:38.643 [PLANETINFO OVER]: Done 11:13:38.644 [PLANETINFO LOGGING]: 0 82 11:13:39.649 [planetinfo.record]: seed = 124 52 155 146 11:13:39.649 [planetinfo.record]: coordinates = 52 224 11:13:39.649 [planetinfo.record]: government = 7; 11:13:39.649 [planetinfo.record]: economy = 0; 11:13:39.649 [planetinfo.record]: techlevel = 11; 11:13:39.649 [planetinfo.record]: population = 52; 11:13:39.649 [planetinfo.record]: productivity = 45760; 11:13:39.649 [planetinfo.record]: name = "Ensoreus"; 11:13:39.649 [planetinfo.record]: inhabitant = "Black Fat Feline"; 11:13:39.649 [planetinfo.record]: inhabitants = "Black Fat Felines"; 11:13:39.649 [planetinfo.record]: description = "This planet is a tedious little planet."; 11:13:39.653 [planetinfo.record]: air_color = 0.617196, 0.911123, 0.941115, 1; 11:13:39.653 [planetinfo.record]: cloud_alpha = 1.000000; 11:13:39.653 [planetinfo.record]: cloud_color = 0.722623, 0.826172, 0.467949, 1; 11:13:39.653 [planetinfo.record]: cloud_fraction = 0.290000; 11:13:39.653 [planetinfo.record]: land_color = 0.299949, 0.237188, 0.66, 1; 11:13:39.653 [planetinfo.record]: land_fraction = 0.310000; 11:13:39.653 [planetinfo.record]: polar_cloud_color = 0.803487, 0.871777, 0.635529, 1; 11:13:39.653 [planetinfo.record]: polar_land_color = 0.806618, 0.784414, 0.934, 1; 11:13:39.653 [planetinfo.record]: polar_sea_color = 0.940039, 0.882641, 0.879818, 1; 11:13:39.653 [planetinfo.record]: sea_color = 0.599609, 0.453162, 0.445959, 1; 11:13:39.653 [planetinfo.record]: rotation_speed = 0.003082 11:13:39.653 [planetinfo.record]: planet zpos = 405600.000000 11:13:39.653 [planetinfo.record]: sun_radius = 82438.558960 11:13:39.653 [planetinfo.record]: sun_vector = 0.307 0.951 0.035 11:13:39.653 [planetinfo.record]: sun_distance = 811200 11:13:39.653 [planetinfo.record]: corona_flare = 0.074181 11:13:39.653 [planetinfo.record]: corona_hues = 0.671570 11:13:39.653 [planetinfo.record]: sun_color = 0.825558, 0.716284, 0.53435, 1 11:13:39.654 [planetinfo.record]: corona_shimmer = 0.298997 11:13:39.654 [planetinfo.record]: station_vector = 0.408 0.913 0.019 11:13:39.654 [planetinfo.record]: station = dodecahedron 11:13:44.468 [PLANETINFO OVER]: Done 11:13:44.469 [PLANETINFO LOGGING]: 0 83 11:13:45.483 [planetinfo.record]: seed = 180 168 205 190 11:13:45.483 [planetinfo.record]: coordinates = 168 110 11:13:45.483 [planetinfo.record]: government = 6; 11:13:45.483 [planetinfo.record]: economy = 6; 11:13:45.483 [planetinfo.record]: techlevel = 4; 11:13:45.483 [planetinfo.record]: population = 29; 11:13:45.483 [planetinfo.record]: productivity = 9280; 11:13:45.483 [planetinfo.record]: name = "Riveis"; 11:13:45.483 [planetinfo.record]: inhabitant = "Harmless Rodent"; 11:13:45.483 [planetinfo.record]: inhabitants = "Harmless Rodents"; 11:13:45.483 [planetinfo.record]: description = "The world Riveis is most well known for its hoopy casinos."; 11:13:45.504 [planetinfo.record]: air_color = 0.569701, 0.450969, 0.962578, 1; 11:13:45.504 [planetinfo.record]: cloud_alpha = 1.000000; 11:13:45.504 [planetinfo.record]: cloud_color = 0.634184, 0.003479, 0.890625, 1; 11:13:45.504 [planetinfo.record]: cloud_fraction = 0.290000; 11:13:45.504 [planetinfo.record]: land_color = 0.177307, 0.460721, 0.648438, 1; 11:13:45.504 [planetinfo.record]: land_fraction = 0.310000; 11:13:45.504 [planetinfo.record]: polar_cloud_color = 0.738678, 0.339992, 0.900781, 1; 11:13:45.504 [planetinfo.record]: polar_land_color = 0.765294, 0.867477, 0.935156, 1; 11:13:45.504 [planetinfo.record]: polar_sea_color = 0.932227, 0.868301, 0.855755, 1; 11:13:45.504 [planetinfo.record]: sea_color = 0.677734, 0.491837, 0.455353, 1; 11:13:45.504 [planetinfo.record]: rotation_speed = 0.001167 11:13:45.504 [planetinfo.record]: planet zpos = 722480.000000 11:13:45.504 [planetinfo.record]: sun_radius = 130722.602539 11:13:45.504 [planetinfo.record]: sun_vector = 0.328 0.183 0.927 11:13:45.504 [planetinfo.record]: sun_distance = 1510640 11:13:45.504 [planetinfo.record]: corona_flare = 0.048828 11:13:45.504 [planetinfo.record]: corona_hues = 0.834213 11:13:45.504 [planetinfo.record]: sun_color = 0.403377, 0.453364, 0.81365, 1 11:13:45.504 [planetinfo.record]: corona_shimmer = 0.263502 11:13:45.504 [planetinfo.record]: station_vector = 0.230 0.909 0.347 11:13:45.504 [planetinfo.record]: station = coriolis 11:13:50.638 [PLANETINFO OVER]: Done 11:13:50.638 [PLANETINFO LOGGING]: 0 84 11:13:51.646 [planetinfo.record]: seed = 128 210 51 230 11:13:51.646 [planetinfo.record]: coordinates = 210 61 11:13:51.646 [planetinfo.record]: government = 0; 11:13:51.646 [planetinfo.record]: economy = 7; 11:13:51.646 [planetinfo.record]: techlevel = 2; 11:13:51.646 [planetinfo.record]: population = 16; 11:13:51.646 [planetinfo.record]: productivity = 1536; 11:13:51.646 [planetinfo.record]: name = "Bivea"; 11:13:51.646 [planetinfo.record]: inhabitant = "Human Colonial"; 11:13:51.646 [planetinfo.record]: inhabitants = "Human Colonials"; 11:13:51.646 [planetinfo.record]: description = "This planet is plagued by frequent solar activity."; 11:13:51.650 [planetinfo.record]: air_color = 0.578916, 0.907, 0.98209, 1; 11:13:51.650 [planetinfo.record]: cloud_alpha = 1.000000; 11:13:51.650 [planetinfo.record]: cloud_color = 0.606645, 0.949219, 0.348541, 1; 11:13:51.650 [planetinfo.record]: cloud_fraction = 0.300000; 11:13:51.651 [planetinfo.record]: land_color = 0.66, 0.296484, 0.466882, 1; 11:13:51.651 [planetinfo.record]: land_fraction = 0.640000; 11:13:51.651 [planetinfo.record]: polar_cloud_color = 0.718018, 0.927148, 0.560454, 1; 11:13:51.651 [planetinfo.record]: polar_land_color = 0.934, 0.805393, 0.865677, 1; 11:13:51.651 [planetinfo.record]: polar_sea_color = 0.945312, 0.913047, 0.895554, 1; 11:13:51.651 [planetinfo.record]: sea_color = 0.546875, 0.472212, 0.431732, 1; 11:13:51.651 [planetinfo.record]: rotation_speed = 0.001113 11:13:51.651 [planetinfo.record]: planet zpos = 547440.000000 11:13:51.651 [planetinfo.record]: sun_radius = 143206.398010 11:13:51.651 [planetinfo.record]: sun_vector = 0.312 0.069 -0.948 11:13:51.651 [planetinfo.record]: sun_distance = 866780 11:13:51.651 [planetinfo.record]: corona_flare = 0.079179 11:13:51.651 [planetinfo.record]: corona_hues = 0.814957 11:13:51.651 [planetinfo.record]: sun_color = 0.590134, 0.618065, 0.721704, 1 11:13:51.652 [planetinfo.record]: corona_shimmer = 0.320350 11:13:51.652 [planetinfo.record]: station_vector = 0.585 -0.281 0.761 11:13:51.652 [planetinfo.record]: station = coriolis 11:13:56.666 [PLANETINFO OVER]: Done 11:13:56.667 [PLANETINFO LOGGING]: 0 85 11:13:57.692 [planetinfo.record]: seed = 128 139 205 48 11:13:57.692 [planetinfo.record]: coordinates = 139 175 11:13:57.692 [planetinfo.record]: government = 0; 11:13:57.692 [planetinfo.record]: economy = 7; 11:13:57.692 [planetinfo.record]: techlevel = 3; 11:13:57.692 [planetinfo.record]: population = 20; 11:13:57.693 [planetinfo.record]: productivity = 1920; 11:13:57.693 [planetinfo.record]: name = "Ermaso"; 11:13:57.693 [planetinfo.record]: inhabitant = "Red Fat Bird"; 11:13:57.693 [planetinfo.record]: inhabitants = "Red Fat Birds"; 11:13:57.693 [planetinfo.record]: description = "This planet is very notable for the Ermasoian edible grub and the Ermasoian tree ant."; 11:13:57.710 [planetinfo.record]: air_color = 0.672461, 0.808644, 0.94827, 1; 11:13:57.710 [planetinfo.record]: cloud_alpha = 1.000000; 11:13:57.710 [planetinfo.record]: cloud_color = 0.615875, 0.847656, 0.739009, 1; 11:13:57.710 [planetinfo.record]: cloud_fraction = 0.280000; 11:13:57.711 [planetinfo.record]: land_color = 0.66, 0.188203, 0.320896, 1; 11:13:57.711 [planetinfo.record]: land_fraction = 0.670000; 11:13:57.711 [planetinfo.record]: polar_cloud_color = 0.730808, 0.881445, 0.810834, 1; 11:13:57.711 [planetinfo.record]: polar_land_color = 0.934, 0.767084, 0.814029, 1; 11:13:57.711 [planetinfo.record]: polar_sea_color = 0.934375, 0.890434, 0.873239, 1; 11:13:57.711 [planetinfo.record]: sea_color = 0.65625, 0.532803, 0.484497, 1; 11:13:57.711 [planetinfo.record]: rotation_speed = 0.004066 11:13:57.711 [planetinfo.record]: planet zpos = 354600.000000 11:13:57.711 [planetinfo.record]: sun_radius = 72118.304443 11:13:57.711 [planetinfo.record]: sun_vector = 0.259 -0.897 0.359 11:13:57.711 [planetinfo.record]: sun_distance = 561450 11:13:57.711 [planetinfo.record]: corona_flare = 0.000346 11:13:57.711 [planetinfo.record]: corona_hues = 0.603554 11:13:57.711 [planetinfo.record]: sun_color = 0.733499, 0.729809, 0.674798, 1 11:13:57.711 [planetinfo.record]: corona_shimmer = 0.298238 11:13:57.712 [planetinfo.record]: station_vector = 0.904 0.356 0.234 11:13:57.712 [planetinfo.record]: station = coriolis 11:14:01.988 [PLANETINFO OVER]: Done 11:14:01.988 [PLANETINFO LOGGING]: 0 86 11:14:02.992 [planetinfo.record]: seed = 180 195 155 86 11:14:02.992 [planetinfo.record]: coordinates = 195 39 11:14:02.992 [planetinfo.record]: government = 6; 11:14:02.992 [planetinfo.record]: economy = 7; 11:14:02.993 [planetinfo.record]: techlevel = 6; 11:14:02.993 [planetinfo.record]: population = 38; 11:14:02.993 [planetinfo.record]: productivity = 9120; 11:14:02.993 [planetinfo.record]: name = "Velete"; 11:14:02.993 [planetinfo.record]: inhabitant = "Yellow Fat Feline"; 11:14:02.993 [planetinfo.record]: inhabitants = "Yellow Fat Felines"; 11:14:02.993 [planetinfo.record]: description = "Velete is a revolting dump."; 11:14:03.011 [planetinfo.record]: air_color = 0.600057, 0.941115, 0.93809, 1; 11:14:03.011 [planetinfo.record]: cloud_alpha = 1.000000; 11:14:03.011 [planetinfo.record]: cloud_color = 0.826172, 0.819869, 0.422768, 1; 11:14:03.011 [planetinfo.record]: cloud_fraction = 0.420000; 11:14:03.011 [planetinfo.record]: land_color = 0.0799219, 0.102581, 0.66, 1; 11:14:03.011 [planetinfo.record]: land_fraction = 0.500000; 11:14:03.011 [planetinfo.record]: polar_cloud_color = 0.871777, 0.86762, 0.605732, 1; 11:14:03.011 [planetinfo.record]: polar_land_color = 0.728775, 0.736792, 0.934, 1; 11:14:03.011 [planetinfo.record]: polar_sea_color = 0.94043, 0.915581, 0.88652, 1; 11:14:03.011 [planetinfo.record]: sea_color = 0.595703, 0.532742, 0.45911, 1; 11:14:03.011 [planetinfo.record]: rotation_speed = 0.000264 11:14:03.011 [planetinfo.record]: planet zpos = 591110.000000 11:14:03.011 [planetinfo.record]: sun_radius = 110291.947632 11:14:03.011 [planetinfo.record]: sun_vector = -0.140 0.982 0.127 11:14:03.011 [planetinfo.record]: sun_distance = 954870 11:14:03.012 [planetinfo.record]: corona_flare = 0.005443 11:14:03.012 [planetinfo.record]: corona_hues = 0.727547 11:14:03.012 [planetinfo.record]: sun_color = 0.222977, 0.496567, 0.803522, 1 11:14:03.012 [planetinfo.record]: corona_shimmer = 0.411508 11:14:03.012 [planetinfo.record]: station_vector = -0.077 -0.995 0.057 11:14:03.012 [planetinfo.record]: station = coriolis 11:14:07.248 [PLANETINFO OVER]: Done 11:14:07.249 [PLANETINFO LOGGING]: 0 87 11:14:08.258 [planetinfo.record]: seed = 188 244 221 146 11:14:08.258 [planetinfo.record]: coordinates = 244 92 11:14:08.258 [planetinfo.record]: government = 7; 11:14:08.258 [planetinfo.record]: economy = 4; 11:14:08.258 [planetinfo.record]: techlevel = 7; 11:14:08.258 [planetinfo.record]: population = 40; 11:14:08.258 [planetinfo.record]: productivity = 21120; 11:14:08.258 [planetinfo.record]: name = "Engema"; 11:14:08.258 [planetinfo.record]: inhabitant = "Black Slimy Lizard"; 11:14:08.258 [planetinfo.record]: inhabitants = "Black Slimy Lizards"; 11:14:08.258 [planetinfo.record]: description = "The world Engema is beset by evil disease."; 11:14:08.268 [planetinfo.record]: air_color = 0.612691, 0.551919, 0.879979, 1; 11:14:08.268 [planetinfo.record]: cloud_alpha = 1.000000; 11:14:08.268 [planetinfo.record]: cloud_color = 0.499132, 0.308739, 0.642578, 1; 11:14:08.268 [planetinfo.record]: cloud_fraction = 0.220000; 11:14:08.268 [planetinfo.record]: land_color = 0.66, 0.371391, 0.244922, 1; 11:14:08.268 [planetinfo.record]: land_fraction = 0.630000; 11:14:08.268 [planetinfo.record]: polar_cloud_color = 0.679054, 0.532914, 0.78916, 1; 11:14:08.268 [planetinfo.record]: polar_land_color = 0.934, 0.831894, 0.78715, 1; 11:14:08.268 [planetinfo.record]: polar_sea_color = 0.892215, 0.93418, 0.887471, 1; 11:14:08.268 [planetinfo.record]: sea_color = 0.539932, 0.658203, 0.526563, 1; 11:14:08.268 [planetinfo.record]: rotation_speed = 0.003346 11:14:08.268 [planetinfo.record]: planet zpos = 392920.000000 11:14:08.269 [planetinfo.record]: sun_radius = 96219.877930 11:14:08.269 [planetinfo.record]: sun_vector = 0.506 0.859 0.080 11:14:08.269 [planetinfo.record]: sun_distance = 750120 11:14:08.269 [planetinfo.record]: corona_flare = 0.014861 11:14:08.269 [planetinfo.record]: corona_hues = 0.927208 11:14:08.269 [planetinfo.record]: sun_color = 0.69895, 0.669869, 0.567961, 1 11:14:08.269 [planetinfo.record]: corona_shimmer = 0.549826 11:14:08.269 [planetinfo.record]: station_vector = -0.983 0.123 0.137 11:14:08.269 [planetinfo.record]: station = coriolis 11:14:12.855 [PLANETINFO OVER]: Done 11:14:12.856 [PLANETINFO LOGGING]: 0 88 11:14:13.865 [planetinfo.record]: seed = 24 226 83 49 11:14:13.865 [planetinfo.record]: coordinates = 226 107 11:14:13.865 [planetinfo.record]: government = 3; 11:14:13.865 [planetinfo.record]: economy = 3; 11:14:13.865 [planetinfo.record]: techlevel = 8; 11:14:13.865 [planetinfo.record]: population = 39; 11:14:13.865 [planetinfo.record]: productivity = 15288; 11:14:13.865 [planetinfo.record]: name = "Atrienxe"; 11:14:13.865 [planetinfo.record]: inhabitant = "Human Colonial"; 11:14:13.865 [planetinfo.record]: inhabitants = "Human Colonials"; 11:14:13.865 [planetinfo.record]: description = "Atrienxe is an unremarkable dump."; 11:14:13.875 [planetinfo.record]: air_color = 0.430223, 0.644208, 0.900791, 1; 11:14:13.876 [planetinfo.record]: cloud_alpha = 1.000000; 11:14:13.876 [planetinfo.record]: cloud_color = 0.0220337, 0.705078, 0.464945, 1; 11:14:13.876 [planetinfo.record]: cloud_fraction = 0.370000; 11:14:13.876 [planetinfo.record]: land_color = 0.171089, 0.526091, 0.634766, 1; 11:14:13.876 [planetinfo.record]: land_fraction = 0.430000; 11:14:13.876 [planetinfo.record]: polar_cloud_color = 0.322445, 0.817285, 0.643318, 1; 11:14:13.876 [planetinfo.record]: polar_land_color = 0.765498, 0.896439, 0.936523, 1; 11:14:13.876 [planetinfo.record]: polar_sea_color = 0.936133, 0.872139, 0.872139, 1; 11:14:13.876 [planetinfo.record]: sea_color = 0.638672, 0.464035, 0.464035, 1; 11:14:13.876 [planetinfo.record]: rotation_speed = 0.000721 11:14:13.876 [planetinfo.record]: planet zpos = 362780.000000 11:14:13.876 [planetinfo.record]: sun_radius = 96202.402344 11:14:13.877 [planetinfo.record]: sun_vector = -0.097 0.658 -0.747 11:14:13.877 [planetinfo.record]: sun_distance = 692580 11:14:13.877 [planetinfo.record]: corona_flare = 0.072260 11:14:13.877 [planetinfo.record]: corona_hues = 0.920746 11:14:13.877 [planetinfo.record]: sun_color = 0.447876, 0.520569, 0.690189, 1 11:14:13.877 [planetinfo.record]: corona_shimmer = 0.333374 11:14:13.878 [planetinfo.record]: station_vector = -0.750 0.494 0.441 11:14:13.879 [planetinfo.record]: station = coriolis 11:14:18.271 [PLANETINFO OVER]: Done 11:14:18.272 [PLANETINFO LOGGING]: 0 89 11:14:19.281 [planetinfo.record]: seed = 104 66 125 83 11:14:19.281 [planetinfo.record]: coordinates = 66 146 11:14:19.281 [planetinfo.record]: government = 5; 11:14:19.281 [planetinfo.record]: economy = 2; 11:14:19.281 [planetinfo.record]: techlevel = 10; 11:14:19.281 [planetinfo.record]: population = 48; 11:14:19.281 [planetinfo.record]: productivity = 27648; 11:14:19.281 [planetinfo.record]: name = "Beusrior"; 11:14:19.281 [planetinfo.record]: inhabitant = "Human Colonial"; 11:14:19.281 [planetinfo.record]: inhabitants = "Human Colonials"; 11:14:19.281 [planetinfo.record]: description = "The world Beusrior is a dull world."; 11:14:19.291 [planetinfo.record]: air_color = 0.527072, 0.973635, 0.886027, 1; 11:14:19.292 [planetinfo.record]: cloud_alpha = 1.000000; 11:14:19.292 [planetinfo.record]: cloud_color = 0.923828, 0.508658, 0.205696, 1; 11:14:19.292 [planetinfo.record]: cloud_fraction = 0.190000; 11:14:19.292 [planetinfo.record]: land_color = 0.460215, 0.66, 0.378984, 1; 11:14:19.292 [planetinfo.record]: land_fraction = 0.300000; 11:14:19.292 [planetinfo.record]: polar_cloud_color = 0.915723, 0.658518, 0.470828, 1; 11:14:19.292 [planetinfo.record]: polar_land_color = 0.863319, 0.934, 0.83458, 1; 11:14:19.292 [planetinfo.record]: polar_sea_color = 0.858376, 0.878957, 0.949219, 1; 11:14:19.292 [planetinfo.record]: sea_color = 0.313416, 0.357459, 0.507812, 1; 11:14:19.292 [planetinfo.record]: rotation_speed = 0.003759 11:14:19.292 [planetinfo.record]: planet zpos = 401500.000000 11:14:19.292 [planetinfo.record]: sun_radius = 84117.195129 11:14:19.292 [planetinfo.record]: sun_vector = 0.717 0.037 0.697 11:14:19.293 [planetinfo.record]: sun_distance = 730000 11:14:19.293 [planetinfo.record]: corona_flare = 0.024481 11:14:19.293 [planetinfo.record]: corona_hues = 0.951416 11:14:19.293 [planetinfo.record]: sun_color = 0.186869, 0.224503, 0.679681, 1 11:14:19.293 [planetinfo.record]: corona_shimmer = 0.295761 11:14:19.293 [planetinfo.record]: station_vector = 0.765 -0.628 0.144 11:14:19.293 [planetinfo.record]: station = coriolis 11:14:23.383 [PLANETINFO OVER]: Done 11:14:23.384 [PLANETINFO LOGGING]: 0 90 11:14:24.393 [planetinfo.record]: seed = 172 57 219 31 11:14:24.393 [planetinfo.record]: coordinates = 57 190 11:14:24.394 [planetinfo.record]: government = 5; 11:14:24.394 [planetinfo.record]: economy = 6; 11:14:24.394 [planetinfo.record]: techlevel = 5; 11:14:24.394 [planetinfo.record]: population = 32; 11:14:24.394 [planetinfo.record]: productivity = 9216; 11:14:24.394 [planetinfo.record]: name = "Ontiat"; 11:14:24.394 [planetinfo.record]: inhabitant = "Green Lizard"; 11:14:24.394 [planetinfo.record]: inhabitants = "Green Lizards"; 11:14:24.394 [planetinfo.record]: description = "The planet Ontiat is scourged by evil disease."; 11:14:24.408 [planetinfo.record]: air_color = 0.570399, 0.961277, 0.945608, 1; 11:14:24.408 [planetinfo.record]: cloud_alpha = 1.000000; 11:14:24.408 [planetinfo.record]: cloud_color = 0.886719, 0.826482, 0.335983, 1; 11:14:24.408 [planetinfo.record]: cloud_fraction = 0.400000; 11:14:24.408 [planetinfo.record]: land_color = 0.66, 0.547207, 0.37125, 1; 11:14:24.408 [planetinfo.record]: land_fraction = 0.650000; 11:14:24.408 [planetinfo.record]: polar_cloud_color = 0.899023, 0.860853, 0.550037, 1; 11:14:24.408 [planetinfo.record]: polar_land_color = 0.934, 0.894095, 0.831844, 1; 11:14:24.408 [planetinfo.record]: polar_sea_color = 0.864525, 0.92168, 0.88953, 1; 11:14:24.408 [planetinfo.record]: sea_color = 0.588932, 0.783203, 0.673926, 1; 11:14:24.408 [planetinfo.record]: rotation_speed = 0.002405 11:14:24.408 [planetinfo.record]: planet zpos = 671300.000000 11:14:24.409 [planetinfo.record]: sun_radius = 123010.890198 11:14:24.409 [planetinfo.record]: sun_vector = 0.735 0.116 0.669 11:14:24.409 [planetinfo.record]: sun_distance = 1141210 11:14:24.409 [planetinfo.record]: corona_flare = 0.064455 11:14:24.409 [planetinfo.record]: corona_hues = 0.750244 11:14:24.409 [planetinfo.record]: sun_color = 0.73226, 0.330132, 0.292089, 1 11:14:24.409 [planetinfo.record]: corona_shimmer = 0.399454 11:14:24.409 [planetinfo.record]: station_vector = -0.576 -0.465 0.672 11:14:24.409 [planetinfo.record]: station = coriolis 11:14:28.985 [PLANETINFO OVER]: Done 11:14:28.986 [PLANETINFO LOGGING]: 0 91 11:14:29.990 [planetinfo.record]: seed = 132 168 45 49 11:14:29.990 [planetinfo.record]: coordinates = 168 113 11:14:29.990 [planetinfo.record]: government = 0; 11:14:29.990 [planetinfo.record]: economy = 3; 11:14:29.990 [planetinfo.record]: techlevel = 4; 11:14:29.990 [planetinfo.record]: population = 20; 11:14:29.990 [planetinfo.record]: productivity = 4480; 11:14:29.990 [planetinfo.record]: name = "Atarza"; 11:14:29.990 [planetinfo.record]: inhabitant = "Human Colonial"; 11:14:29.990 [planetinfo.record]: inhabitants = "Human Colonials"; 11:14:29.990 [planetinfo.record]: description = "This world is plagued by occasional solar activity."; 11:14:30.002 [planetinfo.record]: air_color = 0.608665, 0.928107, 0.910315, 1; 11:14:30.003 [planetinfo.record]: cloud_alpha = 1.000000; 11:14:30.003 [planetinfo.record]: cloud_color = 0.787109, 0.733783, 0.445824, 1; 11:14:30.003 [planetinfo.record]: cloud_fraction = 0.320000; 11:14:30.003 [planetinfo.record]: land_color = 0.162422, 0.298478, 0.66, 1; 11:14:30.003 [planetinfo.record]: land_fraction = 0.490000; 11:14:30.003 [planetinfo.record]: polar_cloud_color = 0.854199, 0.81803, 0.622715, 1; 11:14:30.003 [planetinfo.record]: polar_land_color = 0.757963, 0.806098, 0.934, 1; 11:14:30.003 [planetinfo.record]: polar_sea_color = 0.875649, 0.92832, 0.878941, 1; 11:14:30.003 [planetinfo.record]: sea_color = 0.554118, 0.716797, 0.564285, 1; 11:14:30.003 [planetinfo.record]: rotation_speed = 0.000477 11:14:30.003 [planetinfo.record]: planet zpos = 324000.000000 11:14:30.003 [planetinfo.record]: sun_radius = 57747.601318 11:14:30.003 [planetinfo.record]: sun_vector = 0.206 0.583 -0.786 11:14:30.003 [planetinfo.record]: sun_distance = 615600 11:14:30.003 [planetinfo.record]: corona_flare = 0.052481 11:14:30.003 [planetinfo.record]: corona_hues = 0.906410 11:14:30.003 [planetinfo.record]: sun_color = 0.834796, 0.76761, 0.590533, 1 11:14:30.003 [planetinfo.record]: corona_shimmer = 0.318625 11:14:30.004 [planetinfo.record]: station_vector = 0.997 0.071 0.019 11:14:30.004 [planetinfo.record]: station = coriolis 11:14:34.925 [PLANETINFO OVER]: Done 11:14:34.925 [PLANETINFO LOGGING]: 0 92 11:14:35.931 [planetinfo.record]: seed = 112 160 179 15 11:14:35.931 [planetinfo.record]: coordinates = 160 36 11:14:35.931 [planetinfo.record]: government = 6; 11:14:35.931 [planetinfo.record]: economy = 4; 11:14:35.931 [planetinfo.record]: techlevel = 6; 11:14:35.931 [planetinfo.record]: population = 35; 11:14:35.931 [planetinfo.record]: productivity = 16800; 11:14:35.931 [planetinfo.record]: name = "Arazaes"; 11:14:35.931 [planetinfo.record]: inhabitant = "Green Fat Insect"; 11:14:35.931 [planetinfo.record]: inhabitants = "Green Fat Insects"; 11:14:35.931 [planetinfo.record]: description = "This planet is very notable for the Arazaesian tree ant and Arazaesian wolf meat."; 11:14:35.959 [planetinfo.record]: air_color = 0.486255, 0.996398, 0.872429, 1; 11:14:35.959 [planetinfo.record]: cloud_alpha = 1.000000; 11:14:35.959 [planetinfo.record]: cloud_color = 0.992188, 0.323624, 0.0620117, 1; 11:14:35.959 [planetinfo.record]: cloud_fraction = 0.260000; 11:14:35.959 [planetinfo.record]: land_color = 0.558084, 0.66, 0.422813, 1; 11:14:35.959 [planetinfo.record]: land_fraction = 0.560000; 11:14:35.959 [planetinfo.record]: polar_cloud_color = 0.946484, 0.54788, 0.391904, 1; 11:14:35.959 [planetinfo.record]: polar_land_color = 0.897943, 0.934, 0.850086, 1; 11:14:35.967 [planetinfo.record]: polar_sea_color = 0.864908, 0.923242, 0.874023, 1; 11:14:35.967 [planetinfo.record]: sea_color = 0.573585, 0.767578, 0.603896, 1; 11:14:35.968 [planetinfo.record]: rotation_speed = 0.000383 11:14:35.968 [planetinfo.record]: planet zpos = 1022400.000000 11:14:35.968 [planetinfo.record]: sun_radius = 196924.116211 11:14:35.968 [planetinfo.record]: sun_vector = 0.717 0.002 -0.697 11:14:35.968 [planetinfo.record]: sun_distance = 1363200 11:14:35.968 [planetinfo.record]: corona_flare = 0.048862 11:14:35.968 [planetinfo.record]: corona_hues = 0.873863 11:14:35.968 [planetinfo.record]: sun_color = 0.754443, 0.756989, 0.757416, 1 11:14:35.968 [planetinfo.record]: corona_shimmer = 0.337203 11:14:35.968 [planetinfo.record]: station_vector = -0.310 0.631 0.711 11:14:35.968 [planetinfo.record]: station = coriolis 11:14:40.634 [PLANETINFO OVER]: Done 11:14:40.635 [PLANETINFO LOGGING]: 0 93 11:14:41.644 [planetinfo.record]: seed = 16 105 109 194 11:14:41.644 [planetinfo.record]: coordinates = 105 132 11:14:41.644 [planetinfo.record]: government = 2; 11:14:41.644 [planetinfo.record]: economy = 4; 11:14:41.644 [planetinfo.record]: techlevel = 5; 11:14:41.644 [planetinfo.record]: population = 27; 11:14:41.644 [planetinfo.record]: productivity = 7776; 11:14:41.644 [planetinfo.record]: name = "Xeeranre"; 11:14:41.644 [planetinfo.record]: inhabitant = "Human Colonial"; 11:14:41.644 [planetinfo.record]: inhabitants = "Human Colonials"; 11:14:41.644 [planetinfo.record]: description = "Xeeranre is cursed by killer mountain Reetaboids."; 11:14:41.668 [planetinfo.record]: air_color = 0.498268, 0.617743, 0.860467, 1; 11:14:41.668 [planetinfo.record]: cloud_alpha = 1.000000; 11:14:41.668 [planetinfo.record]: cloud_color = 0.196182, 0.574895, 0.583984, 1; 11:14:41.668 [planetinfo.record]: cloud_fraction = 0.130000; 11:14:41.668 [planetinfo.record]: land_color = 0.142273, 0.578125, 0.322743, 1; 11:14:41.668 [planetinfo.record]: land_fraction = 0.670000; 11:14:41.668 [planetinfo.record]: polar_cloud_color = 0.446204, 0.755373, 0.762793, 1; 11:14:41.669 [planetinfo.record]: polar_land_color = 0.764607, 0.942187, 0.838137, 1; 11:14:41.669 [planetinfo.record]: polar_sea_color = 0.944727, 0.911943, 0.894169, 1; 11:14:41.669 [planetinfo.record]: sea_color = 0.552734, 0.476011, 0.434415, 1; 11:14:41.669 [planetinfo.record]: rotation_speed = 0.003503 11:14:41.669 [planetinfo.record]: planet zpos = 411960.000000 11:14:41.669 [planetinfo.record]: sun_radius = 78706.092834 11:14:41.669 [planetinfo.record]: sun_vector = 0.458 0.855 0.241 11:14:41.669 [planetinfo.record]: sun_distance = 823920 11:14:41.669 [planetinfo.record]: corona_flare = 0.030005 11:14:41.669 [planetinfo.record]: corona_hues = 0.900513 11:14:41.669 [planetinfo.record]: sun_color = 0.24505, 0.551958, 0.730453, 1 11:14:41.669 [planetinfo.record]: corona_shimmer = 0.318414 11:14:41.669 [planetinfo.record]: station_vector = 0.875 0.019 0.484 11:14:41.670 [planetinfo.record]: station = coriolis 11:14:47.801 [PLANETINFO OVER]: Done 11:14:47.801 [PLANETINFO LOGGING]: 0 94 11:14:48.820 [planetinfo.record]: seed = 100 78 91 218 11:14:48.820 [planetinfo.record]: coordinates = 78 219 11:14:48.820 [planetinfo.record]: government = 4; 11:14:48.820 [planetinfo.record]: economy = 3; 11:14:48.820 [planetinfo.record]: techlevel = 8; 11:14:48.820 [planetinfo.record]: population = 40; 11:14:48.820 [planetinfo.record]: productivity = 17920; 11:14:48.820 [planetinfo.record]: name = "Quzadi"; 11:14:48.820 [planetinfo.record]: inhabitant = "Human Colonial"; 11:14:48.820 [planetinfo.record]: inhabitants = "Human Colonials"; 11:14:48.820 [planetinfo.record]: description = "Quzadi is cursed by dreadful civil war."; 11:14:48.844 [planetinfo.record]: air_color = 0.574406, 0.937213, 0.863205, 1; 11:14:48.844 [planetinfo.record]: cloud_alpha = 1.000000; 11:14:48.844 [planetinfo.record]: cloud_color = 0.814453, 0.538859, 0.356323, 1; 11:14:48.844 [planetinfo.record]: cloud_fraction = 0.330000; 11:14:48.844 [planetinfo.record]: land_color = 0.66, 0.394453, 0.450467, 1; 11:14:48.844 [planetinfo.record]: land_fraction = 0.650000; 11:14:48.844 [planetinfo.record]: polar_cloud_color = 0.866504, 0.68325, 0.561874, 1; 11:14:48.844 [planetinfo.record]: polar_land_color = 0.934, 0.840053, 0.85987, 1; 11:14:48.844 [planetinfo.record]: polar_sea_color = 0.944727, 0.921176, 0.896106, 1; 11:14:48.844 [planetinfo.record]: sea_color = 0.552734, 0.497619, 0.438949, 1; 11:14:48.844 [planetinfo.record]: rotation_speed = 0.004681 11:14:48.844 [planetinfo.record]: planet zpos = 654480.000000 11:14:48.844 [planetinfo.record]: sun_radius = 120791.752625 11:14:48.844 [planetinfo.record]: sun_vector = -0.535 0.806 0.254 11:14:48.844 [planetinfo.record]: sun_distance = 872640 11:14:48.844 [planetinfo.record]: corona_flare = 0.038568 11:14:48.844 [planetinfo.record]: corona_hues = 0.761116 11:14:48.844 [planetinfo.record]: sun_color = 0.706787, 0.474861, 0.450073, 1 11:14:48.844 [planetinfo.record]: corona_shimmer = 0.558809 11:14:48.844 [planetinfo.record]: station_vector = 0.996 -0.007 0.092 11:14:48.845 [planetinfo.record]: station = coriolis 11:14:53.331 [PLANETINFO OVER]: Done 11:14:53.332 [PLANETINFO LOGGING]: 0 95 11:14:54.338 [planetinfo.record]: seed = 12 12 189 61 11:14:54.339 [planetinfo.record]: coordinates = 12 45 11:14:54.339 [planetinfo.record]: government = 1; 11:14:54.339 [planetinfo.record]: economy = 7; 11:14:54.339 [planetinfo.record]: techlevel = 1; 11:14:54.339 [planetinfo.record]: population = 13; 11:14:54.339 [planetinfo.record]: productivity = 1560; 11:14:54.339 [planetinfo.record]: name = "Isti"; 11:14:54.339 [planetinfo.record]: inhabitant = "Red Bug-Eyed Lizard"; 11:14:54.339 [planetinfo.record]: inhabitants = "Red Bug-Eyed Lizards"; 11:14:54.339 [planetinfo.record]: description = "The planet Isti is reasonably noted for its inhabitants’ eccentric shyness and Zero-G hockey."; 11:14:54.352 [planetinfo.record]: air_color = 0.623313, 0.482503, 0.94827, 1; 11:14:54.352 [planetinfo.record]: cloud_alpha = 1.000000; 11:14:54.352 [planetinfo.record]: cloud_color = 0.789711, 0.105957, 0.847656, 1; 11:14:54.352 [planetinfo.record]: cloud_fraction = 0.460000; 11:14:54.352 [planetinfo.record]: land_color = 0.134062, 0.351834, 0.66, 1; 11:14:54.352 [planetinfo.record]: land_fraction = 0.490000; 11:14:54.352 [planetinfo.record]: polar_cloud_color = 0.843786, 0.399405, 0.881445, 1; 11:14:54.352 [planetinfo.record]: polar_land_color = 0.74793, 0.824974, 0.934, 1; 11:14:54.352 [planetinfo.record]: polar_sea_color = 0.937891, 0.886057, 0.875884, 1; 11:14:54.352 [planetinfo.record]: sea_color = 0.621094, 0.483791, 0.456844, 1; 11:14:54.352 [planetinfo.record]: rotation_speed = 0.002647 11:14:54.352 [planetinfo.record]: planet zpos = 923400.000000 11:14:54.352 [planetinfo.record]: sun_radius = 128574.695435 11:14:54.352 [planetinfo.record]: sun_vector = 0.069 0.159 -0.985 11:14:54.352 [planetinfo.record]: sun_distance = 1231200 11:14:54.352 [planetinfo.record]: corona_flare = 0.008910 11:14:54.352 [planetinfo.record]: corona_hues = 0.958595 11:14:54.352 [planetinfo.record]: sun_color = 0.793711, 0.801656, 0.817975, 1 11:14:54.352 [planetinfo.record]: corona_shimmer = 1.915025 11:14:54.352 [planetinfo.record]: station_vector = -0.985 -0.007 0.173 11:14:54.352 [planetinfo.record]: station = coriolis 11:14:59.296 [PLANETINFO OVER]: Done 11:14:59.296 [PLANETINFO LOGGING]: 0 96 11:15:00.304 [planetinfo.record]: seed = 136 117 83 173 11:15:00.304 [planetinfo.record]: coordinates = 117 192 11:15:00.304 [planetinfo.record]: government = 1; 11:15:00.304 [planetinfo.record]: economy = 2; 11:15:00.304 [planetinfo.record]: techlevel = 7; 11:15:00.304 [planetinfo.record]: population = 32; 11:15:00.304 [planetinfo.record]: productivity = 10240; 11:15:00.304 [planetinfo.record]: name = "Digebiti"; 11:15:00.304 [planetinfo.record]: inhabitant = "Human Colonial"; 11:15:00.304 [planetinfo.record]: inhabitants = "Human Colonials"; 11:15:00.304 [planetinfo.record]: description = "Digebiti is cursed by killer mountain Seoids."; 11:15:00.316 [planetinfo.record]: air_color = 0.649315, 0.883507, 0.919002, 1; 11:15:00.316 [planetinfo.record]: cloud_alpha = 1.000000; 11:15:00.316 [planetinfo.record]: cloud_color = 0.672956, 0.759766, 0.546082, 1; 11:15:00.316 [planetinfo.record]: cloud_fraction = 0.390000; 11:15:00.316 [planetinfo.record]: land_color = 0.578125, 0.467468, 0.49081, 1; 11:15:00.316 [planetinfo.record]: land_fraction = 0.580000; 11:15:00.316 [planetinfo.record]: polar_cloud_color = 0.781774, 0.841895, 0.693905, 1; 11:15:00.316 [planetinfo.record]: polar_land_color = 0.942187, 0.897102, 0.906613, 1; 11:15:00.316 [planetinfo.record]: polar_sea_color = 0.817459, 0.941992, 0.732252, 1; 11:15:00.316 [planetinfo.record]: sea_color = 0.273328, 0.580078, 0.063446, 1; 11:15:00.316 [planetinfo.record]: rotation_speed = 0.000135 11:15:00.316 [planetinfo.record]: planet zpos = 751320.000000 11:15:00.316 [planetinfo.record]: sun_radius = 185181.762085 11:15:00.316 [planetinfo.record]: sun_vector = -0.365 -0.728 -0.580 11:15:00.316 [planetinfo.record]: sun_distance = 1189590 11:15:00.316 [planetinfo.record]: corona_flare = 0.011839 11:15:00.316 [planetinfo.record]: corona_hues = 0.676620 11:15:00.316 [planetinfo.record]: sun_color = 0.682144, 0.49475, 0.253964, 1 11:15:00.317 [planetinfo.record]: corona_shimmer = 0.758659 11:15:00.317 [planetinfo.record]: station_vector = -0.779 0.580 0.236 11:15:00.317 [planetinfo.record]: station = coriolis 11:15:04.894 [PLANETINFO OVER]: Done 11:15:04.897 [PLANETINFO LOGGING]: 0 97 11:15:05.908 [planetinfo.record]: seed = 120 151 157 129 11:15:05.908 [planetinfo.record]: coordinates = 151 6 11:15:05.908 [planetinfo.record]: government = 7; 11:15:05.908 [planetinfo.record]: economy = 6; 11:15:05.908 [planetinfo.record]: techlevel = 8; 11:15:05.908 [planetinfo.record]: population = 46; 11:15:05.908 [planetinfo.record]: productivity = 16192; 11:15:05.908 [planetinfo.record]: name = "Leoned"; 11:15:05.909 [planetinfo.record]: inhabitant = "Large Black Bug-Eyed Lizard"; 11:15:05.909 [planetinfo.record]: inhabitants = "Large Black Bug-Eyed Lizards"; 11:15:05.909 [planetinfo.record]: description = "Leoned is reasonably well known for the Leonedian tree snake but scourged by unpredictable earthquakes."; 11:15:05.921 [planetinfo.record]: air_color = 0.443357, 0.459837, 0.938514, 1; 11:15:05.921 [planetinfo.record]: cloud_alpha = 1.000000; 11:15:05.921 [planetinfo.record]: cloud_color = 0.0127869, 0.0820158, 0.818359, 1; 11:15:05.921 [planetinfo.record]: cloud_fraction = 0.430000; 11:15:05.921 [planetinfo.record]: land_color = 0.226135, 0.411767, 0.609375, 1; 11:15:05.922 [planetinfo.record]: land_fraction = 0.490000; 11:15:05.922 [planetinfo.record]: polar_cloud_color = 0.334077, 0.379984, 0.868262, 1; 11:15:05.922 [planetinfo.record]: polar_land_color = 0.791417, 0.862933, 0.939062, 1; 11:15:05.922 [planetinfo.record]: polar_sea_color = 0.940625, 0.887014, 0.881468, 1; 11:15:05.922 [planetinfo.record]: sea_color = 0.59375, 0.458388, 0.444385, 1; 11:15:05.922 [planetinfo.record]: rotation_speed = 0.003168 11:15:05.922 [planetinfo.record]: planet zpos = 290070.000000 11:15:05.922 [planetinfo.record]: sun_radius = 85575.036774 11:15:05.922 [planetinfo.record]: sun_vector = -0.031 0.985 -0.169 11:15:05.922 [planetinfo.record]: sun_distance = 676830 11:15:05.923 [planetinfo.record]: corona_flare = 0.034210 11:15:05.923 [planetinfo.record]: corona_hues = 0.829048 11:15:05.923 [planetinfo.record]: sun_color = 0.703262, 0.596361, 0.532488, 1 11:15:05.923 [planetinfo.record]: corona_shimmer = 0.307987 11:15:05.923 [planetinfo.record]: station_vector = 0.172 -0.983 0.067 11:15:05.923 [planetinfo.record]: station = coriolis 11:15:09.437 [PLANETINFO OVER]: Done 11:15:09.438 [PLANETINFO LOGGING]: 0 98 11:15:10.445 [planetinfo.record]: seed = 220 217 27 50 11:15:10.445 [planetinfo.record]: coordinates = 217 56 11:15:10.445 [planetinfo.record]: government = 3; 11:15:10.445 [planetinfo.record]: economy = 0; 11:15:10.445 [planetinfo.record]: techlevel = 10; 11:15:10.445 [planetinfo.record]: population = 44; 11:15:10.445 [planetinfo.record]: productivity = 24640; 11:15:10.445 [planetinfo.record]: name = "Enzaer"; 11:15:10.445 [planetinfo.record]: inhabitant = "Human Colonial"; 11:15:10.445 [planetinfo.record]: inhabitants = "Human Colonials"; 11:15:10.445 [planetinfo.record]: description = "Enzaer is a revolting dump."; 11:15:10.462 [planetinfo.record]: air_color = 0.674905, 0.813591, 0.943066, 1; 11:15:10.462 [planetinfo.record]: cloud_alpha = 1.000000; 11:15:10.462 [planetinfo.record]: cloud_color = 0.620773, 0.832031, 0.71815, 1; 11:15:10.462 [planetinfo.record]: cloud_fraction = 0.230000; 11:15:10.462 [planetinfo.record]: land_color = 0.613281, 0.496045, 0.483917, 1; 11:15:10.462 [planetinfo.record]: land_fraction = 0.350000; 11:15:10.462 [planetinfo.record]: polar_cloud_color = 0.735652, 0.874414, 0.799613, 1; 11:15:10.462 [planetinfo.record]: polar_land_color = 0.938672, 0.893812, 0.889172, 1; 11:15:10.462 [planetinfo.record]: polar_sea_color = 0.785554, 0.944141, 0.888388, 1; 11:15:10.462 [planetinfo.record]: sea_color = 0.183289, 0.558594, 0.426651, 1; 11:15:10.462 [planetinfo.record]: rotation_speed = 0.001813 11:15:10.462 [planetinfo.record]: planet zpos = 354500.000000 11:15:10.463 [planetinfo.record]: sun_radius = 72044.054413 11:15:10.463 [planetinfo.record]: sun_vector = -0.269 -0.680 -0.682 11:15:10.463 [planetinfo.record]: sun_distance = 779900 11:15:10.463 [planetinfo.record]: corona_flare = 0.050272 11:15:10.463 [planetinfo.record]: corona_hues = 0.939911 11:15:10.463 [planetinfo.record]: sun_color = 0.79382, 0.759529, 0.407954, 1 11:15:10.463 [planetinfo.record]: corona_shimmer = 0.405409 11:15:10.463 [planetinfo.record]: station_vector = -0.296 -0.528 0.796 11:15:10.463 [planetinfo.record]: station = coriolis 11:15:14.997 [PLANETINFO OVER]: Done 11:15:14.997 [PLANETINFO LOGGING]: 0 99 11:15:16.003 [planetinfo.record]: seed = 84 199 141 92 11:15:16.003 [planetinfo.record]: coordinates = 199 80 11:15:16.003 [planetinfo.record]: government = 2; 11:15:16.003 [planetinfo.record]: economy = 0; 11:15:16.003 [planetinfo.record]: techlevel = 11; 11:15:16.003 [planetinfo.record]: population = 47; 11:15:16.003 [planetinfo.record]: productivity = 22560; 11:15:16.004 [planetinfo.record]: name = "Teraed"; 11:15:16.004 [planetinfo.record]: inhabitant = "Yellow Insect"; 11:15:16.004 [planetinfo.record]: inhabitants = "Yellow Insects"; 11:15:16.004 [planetinfo.record]: description = "Teraed is an unremarkable dump."; 11:15:16.012 [planetinfo.record]: air_color = 0.543516, 0.969082, 0.908844, 1; 11:15:16.012 [planetinfo.record]: cloud_alpha = 1.000000; 11:15:16.012 [planetinfo.record]: cloud_color = 0.910156, 0.639287, 0.255981, 1; 11:15:16.012 [planetinfo.record]: cloud_fraction = 0.500000; 11:15:16.012 [planetinfo.record]: land_color = 0.0335156, 0.557217, 0.66, 1; 11:15:16.012 [planetinfo.record]: land_fraction = 0.600000; 11:15:16.012 [planetinfo.record]: polar_cloud_color = 0.90957, 0.740386, 0.500974, 1; 11:15:16.013 [planetinfo.record]: polar_land_color = 0.712357, 0.897637, 0.934, 1; 11:15:16.013 [planetinfo.record]: polar_sea_color = 0.932617, 0.90533, 0.873418, 1; 11:15:16.013 [planetinfo.record]: sea_color = 0.673828, 0.594967, 0.502739, 1; 11:15:16.013 [planetinfo.record]: rotation_speed = 0.004877 11:15:16.013 [planetinfo.record]: planet zpos = 791310.000000 11:15:16.013 [planetinfo.record]: sun_radius = 147173.398590 11:15:16.013 [planetinfo.record]: sun_vector = -0.014 -0.631 -0.776 11:15:16.013 [planetinfo.record]: sun_distance = 1339140 11:15:16.013 [planetinfo.record]: corona_flare = 0.017070 11:15:16.013 [planetinfo.record]: corona_hues = 0.698196 11:15:16.013 [planetinfo.record]: sun_color = 0.815857, 0.814772, 0.810908, 1 11:15:16.013 [planetinfo.record]: corona_shimmer = 0.394448 11:15:16.013 [planetinfo.record]: station_vector = -0.442 -0.801 0.403 11:15:16.013 [planetinfo.record]: station = icosahedron 11:15:21.205 [PLANETINFO OVER]: Done 11:15:21.205 [PLANETINFO LOGGING]: 0 100 11:15:22.218 [planetinfo.record]: seed = 96 105 51 118 11:15:22.218 [planetinfo.record]: coordinates = 105 152 11:15:22.218 [planetinfo.record]: government = 4; 11:15:22.218 [planetinfo.record]: economy = 0; 11:15:22.218 [planetinfo.record]: techlevel = 10; 11:15:22.219 [planetinfo.record]: population = 45; 11:15:22.219 [planetinfo.record]: productivity = 28800; 11:15:22.219 [planetinfo.record]: name = "Vetitice"; 11:15:22.219 [planetinfo.record]: inhabitant = "Human Colonial"; 11:15:22.219 [planetinfo.record]: inhabitants = "Human Colonials"; 11:15:22.219 [planetinfo.record]: description = "This world is very well known for Vetiticeian lethal brandy and its great parking meters."; 11:15:22.231 [planetinfo.record]: air_color = 0.431718, 0.584031, 0.9164, 1; 11:15:22.232 [planetinfo.record]: cloud_alpha = 1.000000; 11:15:22.232 [planetinfo.record]: cloud_color = 0.00881195, 0.699701, 0.751953, 1; 11:15:22.232 [planetinfo.record]: cloud_fraction = 0.210000; 11:15:22.232 [planetinfo.record]: land_color = 0.523359, 0.66, 0.611962, 1; 11:15:22.232 [planetinfo.record]: land_fraction = 0.550000; 11:15:22.232 [planetinfo.record]: polar_cloud_color = 0.320533, 0.801968, 0.838379, 1; 11:15:22.232 [planetinfo.record]: polar_land_color = 0.885658, 0.934, 0.917005, 1; 11:15:22.232 [planetinfo.record]: polar_sea_color = 0.92207, 0.889478, 0.820318, 1; 11:15:22.232 [planetinfo.record]: sea_color = 0.779297, 0.669114, 0.43531, 1; 11:15:22.232 [planetinfo.record]: rotation_speed = 0.004796 11:15:22.232 [planetinfo.record]: planet zpos = 445700.000000 11:15:22.232 [planetinfo.record]: sun_radius = 152066.833496 11:15:22.233 [planetinfo.record]: sun_vector = 0.638 -0.483 -0.600 11:15:22.233 [planetinfo.record]: sun_distance = 891400 11:15:22.233 [planetinfo.record]: corona_flare = 0.032104 11:15:22.233 [planetinfo.record]: corona_hues = 0.964699 11:15:22.233 [planetinfo.record]: sun_color = 0.736761, 0.556634, 0.286195, 1 11:15:22.233 [planetinfo.record]: corona_shimmer = 0.400059 11:15:22.233 [planetinfo.record]: station_vector = -0.505 -0.563 0.654 11:15:22.233 [planetinfo.record]: station = coriolis 11:15:27.044 [PLANETINFO OVER]: Done 11:15:27.045 [PLANETINFO LOGGING]: 0 101 11:15:28.060 [planetinfo.record]: seed = 160 69 13 21 11:15:28.060 [planetinfo.record]: coordinates = 69 87 11:15:28.060 [planetinfo.record]: government = 4; 11:15:28.060 [planetinfo.record]: economy = 7; 11:15:28.060 [planetinfo.record]: techlevel = 3; 11:15:28.060 [planetinfo.record]: population = 24; 11:15:28.060 [planetinfo.record]: productivity = 4608; 11:15:28.060 [planetinfo.record]: name = "Laenin"; 11:15:28.060 [planetinfo.record]: inhabitant = "Human Colonial"; 11:15:28.060 [planetinfo.record]: inhabitants = "Human Colonials"; 11:15:28.060 [planetinfo.record]: description = "The planet Laenin is famous for its inhabitants’ ancient loathing of sit coms but cursed by killer disease."; 11:15:28.072 [planetinfo.record]: air_color = 0.620387, 0.873571, 0.957375, 1; 11:15:28.072 [planetinfo.record]: cloud_alpha = 1.000000; 11:15:28.072 [planetinfo.record]: cloud_color = 0.571949, 0.875, 0.475098, 1; 11:15:28.072 [planetinfo.record]: cloud_fraction = 0.510000; 11:15:28.072 [planetinfo.record]: land_color = 0.66, 0.51345, 0.384141, 1; 11:15:28.072 [planetinfo.record]: land_fraction = 0.660000; 11:15:28.072 [planetinfo.record]: polar_cloud_color = 0.700284, 0.89375, 0.638455, 1; 11:15:28.072 [planetinfo.record]: polar_land_color = 0.934, 0.882152, 0.836404, 1; 11:15:28.072 [planetinfo.record]: polar_sea_color = 0.933594, 0.836952, 0.886783, 1; 11:15:28.072 [planetinfo.record]: sea_color = 0.664062, 0.389099, 0.530877, 1; 11:15:28.072 [planetinfo.record]: rotation_speed = 0.002793 11:15:28.072 [planetinfo.record]: planet zpos = 583100.000000 11:15:28.072 [planetinfo.record]: sun_radius = 99146.904755 11:15:28.073 [planetinfo.record]: sun_vector = -0.260 -0.948 0.185 11:15:28.073 [planetinfo.record]: sun_distance = 874650 11:15:28.073 [planetinfo.record]: corona_flare = 0.001965 11:15:28.073 [planetinfo.record]: corona_hues = 0.829300 11:15:28.073 [planetinfo.record]: sun_color = 0.768994, 0.628502, 0.470791, 1 11:15:28.073 [planetinfo.record]: corona_shimmer = 0.390509 11:15:28.073 [planetinfo.record]: station_vector = -0.363 0.811 0.460 11:15:28.073 [planetinfo.record]: station = coriolis 11:15:32.652 [PLANETINFO OVER]: Done 11:15:32.653 [PLANETINFO LOGGING]: 0 102 11:15:33.658 [planetinfo.record]: seed = 20 212 27 147 11:15:33.658 [planetinfo.record]: coordinates = 212 12 11:15:33.658 [planetinfo.record]: government = 2; 11:15:33.658 [planetinfo.record]: economy = 4; 11:15:33.658 [planetinfo.record]: techlevel = 4; 11:15:33.658 [planetinfo.record]: population = 23; 11:15:33.658 [planetinfo.record]: productivity = 6624; 11:15:33.658 [planetinfo.record]: name = "Beraanxe"; 11:15:33.658 [planetinfo.record]: inhabitant = "Human Colonial"; 11:15:33.658 [planetinfo.record]: inhabitants = "Human Colonials"; 11:15:33.658 [planetinfo.record]: description = "The world Beraanxe is reasonably noted for its inhabitants’ exceptional love for tourists and its unusual oceans."; 11:15:33.660 [planetinfo.record]: air_color = 0.66819, 0.882427, 0.883881, 1; 11:15:33.660 [planetinfo.record]: cloud_alpha = 1.000000; 11:15:33.660 [planetinfo.record]: cloud_color = 0.651581, 0.654297, 0.567398, 1; 11:15:33.660 [planetinfo.record]: cloud_fraction = 0.530000; 11:15:33.660 [planetinfo.record]: land_color = 0.66, 0.328066, 0.128906, 1; 11:15:33.660 [planetinfo.record]: land_fraction = 0.310000; 11:15:33.660 [planetinfo.record]: polar_cloud_color = 0.792373, 0.794434, 0.728489, 1; 11:15:33.670 [planetinfo.record]: polar_land_color = 0.934, 0.816566, 0.746105, 1; 11:15:33.670 [planetinfo.record]: polar_sea_color = 0.873134, 0.924219, 0.906259, 1; 11:15:33.670 [planetinfo.record]: sea_color = 0.590265, 0.757812, 0.698909, 1; 11:15:33.670 [planetinfo.record]: rotation_speed = 0.004045 11:15:33.670 [planetinfo.record]: planet zpos = 417560.000000 11:15:33.670 [planetinfo.record]: sun_radius = 75719.588623 11:15:33.670 [planetinfo.record]: sun_vector = -0.369 0.910 0.190 11:15:33.670 [planetinfo.record]: sun_distance = 797160 11:15:33.670 [planetinfo.record]: corona_flare = 0.064929 11:15:33.670 [planetinfo.record]: corona_hues = 0.857239 11:15:33.670 [planetinfo.record]: sun_color = 0.584656, 0.646426, 0.827197, 1 11:15:33.670 [planetinfo.record]: corona_shimmer = 0.672054 11:15:33.671 [planetinfo.record]: station_vector = 0.099 0.930 0.354 11:15:33.671 [planetinfo.record]: station = coriolis 11:15:38.621 [PLANETINFO OVER]: Done 11:15:38.622 [PLANETINFO LOGGING]: 0 103 11:15:39.637 [planetinfo.record]: seed = 92 226 157 49 11:15:39.637 [planetinfo.record]: coordinates = 226 219 11:15:39.637 [planetinfo.record]: government = 3; 11:15:39.637 [planetinfo.record]: economy = 3; 11:15:39.637 [planetinfo.record]: techlevel = 8; 11:15:39.637 [planetinfo.record]: population = 39; 11:15:39.637 [planetinfo.record]: productivity = 15288; 11:15:39.637 [planetinfo.record]: name = "Atage"; 11:15:39.637 [planetinfo.record]: inhabitant = "Red Bug-Eyed Lizard"; 11:15:39.637 [planetinfo.record]: inhabitants = "Red Bug-Eyed Lizards"; 11:15:39.637 [planetinfo.record]: description = "Atage is an unremarkable planet."; 11:15:39.656 [planetinfo.record]: air_color = 0.760945, 0.589668, 0.895588, 1; 11:15:39.656 [planetinfo.record]: cloud_alpha = 1.000000; 11:15:39.656 [planetinfo.record]: cloud_color = 0.689453, 0.395897, 0.48534, 1; 11:15:39.656 [planetinfo.record]: cloud_fraction = 0.430000; 11:15:39.656 [planetinfo.record]: land_color = 0.378984, 0.66, 0.468997, 1; 11:15:39.656 [planetinfo.record]: land_fraction = 0.440000; 11:15:39.656 [planetinfo.record]: polar_cloud_color = 0.810254, 0.594635, 0.660331, 1; 11:15:39.656 [planetinfo.record]: polar_land_color = 0.83458, 0.934, 0.866426, 1; 11:15:39.656 [planetinfo.record]: polar_sea_color = 0.911916, 0.934961, 0.884104, 1; 11:15:39.656 [planetinfo.record]: sea_color = 0.586269, 0.650391, 0.50888, 1; 11:15:39.657 [planetinfo.record]: rotation_speed = 0.002049 11:15:39.657 [planetinfo.record]: planet zpos = 461720.000000 11:15:39.657 [planetinfo.record]: sun_radius = 70946.051025 11:15:39.657 [planetinfo.record]: sun_vector = 0.158 0.744 -0.649 11:15:39.657 [planetinfo.record]: sun_distance = 692580 11:15:39.657 [planetinfo.record]: corona_flare = 0.090189 11:15:39.657 [planetinfo.record]: corona_hues = 0.737190 11:15:39.657 [planetinfo.record]: sun_color = 0.675238, 0.512491, 0.491053, 1 11:15:39.657 [planetinfo.record]: corona_shimmer = 0.545370 11:15:39.658 [planetinfo.record]: station_vector = -0.213 0.952 0.221 11:15:39.659 [planetinfo.record]: station = coriolis 11:15:44.226 [PLANETINFO OVER]: Done 11:15:44.228 [PLANETINFO LOGGING]: 0 104 11:15:45.245 [planetinfo.record]: seed = 248 35 83 22 11:15:45.245 [planetinfo.record]: coordinates = 35 3 11:15:45.246 [planetinfo.record]: government = 7; 11:15:45.246 [planetinfo.record]: economy = 3; 11:15:45.246 [planetinfo.record]: techlevel = 11; 11:15:45.246 [planetinfo.record]: population = 55; 11:15:45.246 [planetinfo.record]: productivity = 33880; 11:15:45.246 [planetinfo.record]: name = "Veisti"; 11:15:45.246 [planetinfo.record]: inhabitant = "Human Colonial"; 11:15:45.246 [planetinfo.record]: inhabitants = "Human Colonials"; 11:15:45.246 [planetinfo.record]: description = "The planet Veisti is reasonably noted for its inhabitants’ eccentric shyness and Zero-G cricket."; 11:15:45.258 [planetinfo.record]: air_color = 0.721555, 0.762957, 0.94957, 1; 11:15:45.258 [planetinfo.record]: cloud_alpha = 1.000000; 11:15:45.258 [planetinfo.record]: cloud_color = 0.748444, 0.803226, 0.851562, 1; 11:15:45.258 [planetinfo.record]: cloud_fraction = 0.180000; 11:15:45.258 [planetinfo.record]: land_color = 0.643887, 0.5775, 0.66, 1; 11:15:45.258 [planetinfo.record]: land_fraction = 0.340000; 11:15:45.258 [planetinfo.record]: polar_cloud_color = 0.816359, 0.85187, 0.883203, 1; 11:15:45.258 [planetinfo.record]: polar_land_color = 0.928299, 0.904813, 0.934, 1; 11:15:45.258 [planetinfo.record]: polar_sea_color = 0.948438, 0.786792, 0.715959, 1; 11:15:45.258 [planetinfo.record]: sea_color = 0.515625, 0.164107, 0.0100708, 1; 11:15:45.258 [planetinfo.record]: rotation_speed = 0.004429 11:15:45.258 [planetinfo.record]: planet zpos = 482570.000000 11:15:45.259 [planetinfo.record]: sun_radius = 95730.664520 11:15:45.259 [planetinfo.record]: sun_vector = 0.473 0.173 0.864 11:15:45.259 [planetinfo.record]: sun_distance = 965140 11:15:45.259 [planetinfo.record]: corona_flare = 0.032329 11:15:45.259 [planetinfo.record]: corona_hues = 0.981873 11:15:45.259 [planetinfo.record]: sun_color = 0.39587, 0.701567, 0.748456, 1 11:15:45.259 [planetinfo.record]: corona_shimmer = 0.385379 11:15:45.268 [planetinfo.record]: station_vector = -0.453 0.041 0.890 11:15:45.268 [planetinfo.record]: station = dodecahedron 11:15:49.772 [PLANETINFO OVER]: Done 11:15:49.772 [PLANETINFO LOGGING]: 0 105 11:15:50.778 [planetinfo.record]: seed = 136 203 189 128 11:15:50.778 [planetinfo.record]: coordinates = 203 119 11:15:50.778 [planetinfo.record]: government = 1; 11:15:50.778 [planetinfo.record]: economy = 7; 11:15:50.778 [planetinfo.record]: techlevel = 4; 11:15:50.778 [planetinfo.record]: population = 25; 11:15:50.778 [planetinfo.record]: productivity = 3000; 11:15:50.778 [planetinfo.record]: name = "Zaerla"; 11:15:50.778 [planetinfo.record]: inhabitant = "Large Black Fat Bird"; 11:15:50.778 [planetinfo.record]: inhabitants = "Large Black Fat Birds"; 11:15:50.778 [planetinfo.record]: description = "The planet Zaerla is mildly well known for its exotic cuisine."; 11:15:50.804 [planetinfo.record]: air_color = 0.568974, 0.474949, 0.943066, 1; 11:15:50.804 [planetinfo.record]: cloud_alpha = 1.000000; 11:15:50.804 [planetinfo.record]: cloud_color = 0.548357, 0.0910034, 0.832031, 1; 11:15:50.804 [planetinfo.record]: cloud_fraction = 0.150000; 11:15:50.804 [planetinfo.record]: land_color = 0.528516, 0.644592, 0.66, 1; 11:15:50.804 [planetinfo.record]: land_fraction = 0.510000; 11:15:50.804 [planetinfo.record]: polar_cloud_color = 0.688086, 0.38768, 0.874414, 1; 11:15:50.804 [planetinfo.record]: polar_land_color = 0.887482, 0.928549, 0.934, 1; 11:15:50.804 [planetinfo.record]: polar_sea_color = 0.808634, 0.945117, 0.75314, 1; 11:15:50.804 [planetinfo.record]: sea_color = 0.231805, 0.548828, 0.102905, 1; 11:15:50.804 [planetinfo.record]: rotation_speed = 0.002550 11:15:50.804 [planetinfo.record]: planet zpos = 362280.000000 11:15:50.804 [planetinfo.record]: sun_radius = 62212.056122 11:15:50.804 [planetinfo.record]: sun_vector = -0.143 -0.712 -0.688 11:15:50.804 [planetinfo.record]: sun_distance = 633990 11:15:50.804 [planetinfo.record]: corona_flare = 0.074255 11:15:50.805 [planetinfo.record]: corona_hues = 0.685265 11:15:50.805 [planetinfo.record]: sun_color = 0.824673, 0.820479, 0.561382, 1 11:15:50.805 [planetinfo.record]: corona_shimmer = 0.629778 11:15:50.805 [planetinfo.record]: station_vector = -0.625 0.658 0.420 11:15:50.805 [planetinfo.record]: station = coriolis 11:15:55.792 [PLANETINFO OVER]: Done 11:15:55.792 [PLANETINFO LOGGING]: 0 106 11:15:56.795 [planetinfo.record]: seed = 12 85 91 41 11:15:56.795 [planetinfo.record]: coordinates = 85 16 11:15:56.795 [planetinfo.record]: government = 1; 11:15:56.795 [planetinfo.record]: economy = 2; 11:15:56.795 [planetinfo.record]: techlevel = 7; 11:15:56.795 [planetinfo.record]: population = 32; 11:15:56.795 [planetinfo.record]: productivity = 10240; 11:15:56.795 [planetinfo.record]: name = "Esredice"; 11:15:56.795 [planetinfo.record]: inhabitant = "Human Colonial"; 11:15:56.795 [planetinfo.record]: inhabitants = "Human Colonials"; 11:15:56.795 [planetinfo.record]: description = "The world Esredice is a boring planet."; 11:15:56.808 [planetinfo.record]: air_color = 0.659341, 0.812804, 0.956074, 1; 11:15:56.808 [planetinfo.record]: cloud_alpha = 1.000000; 11:15:56.808 [planetinfo.record]: cloud_color = 0.581863, 0.871094, 0.715181, 1; 11:15:56.808 [planetinfo.record]: cloud_fraction = 0.580000; 11:15:56.808 [planetinfo.record]: land_color = 0.477104, 0.374062, 0.541016, 1; 11:15:56.808 [planetinfo.record]: land_fraction = 0.540000; 11:15:56.808 [planetinfo.record]: polar_cloud_color = 0.706886, 0.891992, 0.792209, 1; 11:15:56.808 [planetinfo.record]: polar_land_color = 0.917963, 0.872924, 0.945898, 1; 11:15:56.808 [planetinfo.record]: polar_sea_color = 0.862775, 0.919336, 0.872939, 1; 11:15:56.809 [planetinfo.record]: sea_color = 0.608131, 0.806641, 0.643801, 1; 11:15:56.809 [planetinfo.record]: rotation_speed = 0.001164 11:15:56.809 [planetinfo.record]: planet zpos = 624600.000000 11:15:56.809 [planetinfo.record]: sun_radius = 126631.226349 11:15:56.809 [planetinfo.record]: sun_vector = -0.119 0.843 -0.525 11:15:56.809 [planetinfo.record]: sun_distance = 1093050 11:15:56.809 [planetinfo.record]: corona_flare = 0.068758 11:15:56.809 [planetinfo.record]: corona_hues = 0.501297 11:15:56.809 [planetinfo.record]: sun_color = 0.435626, 0.49996, 0.71835, 1 11:15:56.809 [planetinfo.record]: corona_shimmer = 0.544998 11:15:56.809 [planetinfo.record]: station_vector = 0.192 0.320 0.928 11:15:56.809 [planetinfo.record]: station = coriolis 11:16:01.466 [PLANETINFO OVER]: Done 11:16:01.467 [PLANETINFO LOGGING]: 0 107 11:16:02.471 [planetinfo.record]: seed = 36 197 237 96 11:16:02.471 [planetinfo.record]: coordinates = 197 13 11:16:02.471 [planetinfo.record]: government = 4; 11:16:02.471 [planetinfo.record]: economy = 5; 11:16:02.471 [planetinfo.record]: techlevel = 5; 11:16:02.471 [planetinfo.record]: population = 30; 11:16:02.471 [planetinfo.record]: productivity = 9600; 11:16:02.471 [planetinfo.record]: name = "Beor"; 11:16:02.471 [planetinfo.record]: inhabitant = "Large Blue Slimy Rodent"; 11:16:02.471 [planetinfo.record]: inhabitants = "Large Blue Slimy Rodents"; 11:16:02.471 [planetinfo.record]: description = "Beor is an unremarkable dump."; 11:16:02.475 [planetinfo.record]: air_color = 0.449306, 0.4914, 0.928107, 1; 11:16:02.476 [planetinfo.record]: cloud_alpha = 1.000000; 11:16:02.476 [planetinfo.record]: cloud_color = 0.0399704, 0.226755, 0.787109, 1; 11:16:02.476 [planetinfo.record]: cloud_fraction = 0.340000; 11:16:02.476 [planetinfo.record]: land_color = 0.66, 0.594681, 0.296484, 1; 11:16:02.476 [planetinfo.record]: land_fraction = 0.620000; 11:16:02.476 [planetinfo.record]: polar_cloud_color = 0.347436, 0.474126, 0.854199, 1; 11:16:02.476 [planetinfo.record]: polar_land_color = 0.934, 0.910891, 0.805393, 1; 11:16:02.476 [planetinfo.record]: polar_sea_color = 0.928906, 0.90817, 0.859057, 1; 11:16:02.476 [planetinfo.record]: sea_color = 0.710938, 0.647455, 0.497101, 1; 11:16:02.477 [planetinfo.record]: rotation_speed = 0.004253 11:16:02.477 [planetinfo.record]: planet zpos = 391690.000000 11:16:02.477 [planetinfo.record]: sun_radius = 93505.247803 11:16:02.477 [planetinfo.record]: sun_vector = 0.654 0.700 -0.286 11:16:02.477 [planetinfo.record]: sun_distance = 632730 11:16:02.477 [planetinfo.record]: corona_flare = 0.060924 11:16:02.477 [planetinfo.record]: corona_hues = 0.522865 11:16:02.477 [planetinfo.record]: sun_color = 0.663995, 0.628942, 0.481122, 1 11:16:02.477 [planetinfo.record]: corona_shimmer = 0.358759 11:16:02.477 [planetinfo.record]: station_vector = -0.959 -0.014 0.281 11:16:02.477 [planetinfo.record]: station = coriolis 11:16:07.243 [PLANETINFO OVER]: Done 11:16:07.244 [PLANETINFO LOGGING]: 0 108 11:16:08.249 [planetinfo.record]: seed = 80 237 179 121 11:16:08.249 [planetinfo.record]: coordinates = 237 89 11:16:08.249 [planetinfo.record]: government = 2; 11:16:08.249 [planetinfo.record]: economy = 1; 11:16:08.249 [planetinfo.record]: techlevel = 8; 11:16:08.249 [planetinfo.record]: population = 36; 11:16:08.249 [planetinfo.record]: productivity = 15552; 11:16:08.249 [planetinfo.record]: name = "Orso"; 11:16:08.249 [planetinfo.record]: inhabitant = "Blue Fat Humanoid"; 11:16:08.249 [planetinfo.record]: inhabitants = "Blue Fat Humanoids"; 11:16:08.249 [planetinfo.record]: description = "The world Orso is reasonably fabled for its exciting sit coms and its inhabitants’ exceptional love for food blenders."; 11:16:08.259 [planetinfo.record]: air_color = 0.592549, 0.93049, 0.958676, 1; 11:16:08.260 [planetinfo.record]: cloud_alpha = 1.000000; 11:16:08.260 [planetinfo.record]: cloud_color = 0.762498, 0.878906, 0.398254, 1; 11:16:08.260 [planetinfo.record]: cloud_fraction = 0.210000; 11:16:08.260 [planetinfo.record]: land_color = 0.66, 0.268528, 0.216563, 1; 11:16:08.260 [planetinfo.record]: land_fraction = 0.310000; 11:16:08.260 [planetinfo.record]: polar_cloud_color = 0.821379, 0.895508, 0.589426, 1; 11:16:08.261 [planetinfo.record]: polar_land_color = 0.934, 0.795502, 0.777117, 1; 11:16:08.261 [planetinfo.record]: polar_sea_color = 0.877096, 0.928125, 0.887461, 1; 11:16:08.261 [planetinfo.record]: sea_color = 0.560681, 0.71875, 0.592789, 1; 11:16:08.261 [planetinfo.record]: rotation_speed = 0.004193 11:16:08.261 [planetinfo.record]: planet zpos = 535700.000000 11:16:08.261 [planetinfo.record]: sun_radius = 101155.717010 11:16:08.261 [planetinfo.record]: sun_vector = -0.984 0.178 -0.009 11:16:08.261 [planetinfo.record]: sun_distance = 1071400 11:16:08.261 [planetinfo.record]: corona_flare = 0.017856 11:16:08.261 [planetinfo.record]: corona_hues = 0.783485 11:16:08.262 [planetinfo.record]: sun_color = 0.795358, 0.774031, 0.76878, 1 11:16:08.262 [planetinfo.record]: corona_shimmer = 0.400323 11:16:08.262 [planetinfo.record]: station_vector = -0.903 -0.372 0.217 11:16:08.263 [planetinfo.record]: station = coriolis 11:16:13.033 [PLANETINFO OVER]: Done 11:16:13.035 [PLANETINFO LOGGING]: 0 109 11:16:14.050 [planetinfo.record]: seed = 48 97 173 72 11:16:14.050 [planetinfo.record]: coordinates = 97 39 11:16:14.050 [planetinfo.record]: government = 6; 11:16:14.050 [planetinfo.record]: economy = 7; 11:16:14.050 [planetinfo.record]: techlevel = 4; 11:16:14.050 [planetinfo.record]: population = 30; 11:16:14.050 [planetinfo.record]: productivity = 7200; 11:16:14.050 [planetinfo.record]: name = "Usatqura"; 11:16:14.050 [planetinfo.record]: inhabitant = "Small Yellow Feline"; 11:16:14.050 [planetinfo.record]: inhabitants = "Small Yellow Felines"; 11:16:14.050 [planetinfo.record]: description = "This planet is reasonably famous for its inhabitants’ exceptional loathing of sit coms."; 11:16:14.072 [planetinfo.record]: air_color = 0.569596, 0.927235, 0.981439, 1; 11:16:14.072 [planetinfo.record]: cloud_alpha = 1.000000; 11:16:14.072 [planetinfo.record]: cloud_color = 0.69322, 0.947266, 0.321922, 1; 11:16:14.072 [planetinfo.record]: cloud_fraction = 0.390000; 11:16:14.072 [planetinfo.record]: land_color = 0.66, 0.394453, 0.425572, 1; 11:16:14.072 [planetinfo.record]: land_fraction = 0.500000; 11:16:14.072 [planetinfo.record]: polar_cloud_color = 0.77101, 0.92627, 0.544093, 1; 11:16:14.072 [planetinfo.record]: polar_land_color = 0.934, 0.840053, 0.851062, 1; 11:16:14.072 [planetinfo.record]: polar_sea_color = 0.939844, 0.925489, 0.887345, 1; 11:16:14.072 [planetinfo.record]: sea_color = 0.601562, 0.564809, 0.467151, 1; 11:16:14.072 [planetinfo.record]: rotation_speed = 0.002196 11:16:14.072 [planetinfo.record]: planet zpos = 694540.000000 11:16:14.073 [planetinfo.record]: sun_radius = 112206.895142 11:16:14.073 [planetinfo.record]: sun_vector = 0.365 0.871 -0.330 11:16:14.073 [planetinfo.record]: sun_distance = 942590 11:16:14.073 [planetinfo.record]: corona_flare = 0.079352 11:16:14.073 [planetinfo.record]: corona_hues = 0.545364 11:16:14.073 [planetinfo.record]: sun_color = 0.702396, 0.701123, 0.698819, 1 11:16:14.073 [planetinfo.record]: corona_shimmer = 0.252347 11:16:14.073 [planetinfo.record]: station_vector = -0.505 -0.558 0.659 11:16:14.073 [planetinfo.record]: station = coriolis 11:16:19.153 [PLANETINFO OVER]: Done 11:16:19.153 [PLANETINFO LOGGING]: 0 110 11:16:20.158 [planetinfo.record]: seed = 196 148 219 224 11:16:20.158 [planetinfo.record]: coordinates = 148 122 11:16:20.158 [planetinfo.record]: government = 0; 11:16:20.158 [planetinfo.record]: economy = 2; 11:16:20.158 [planetinfo.record]: techlevel = 5; 11:16:20.158 [planetinfo.record]: population = 23; 11:16:20.158 [planetinfo.record]: productivity = 5888; 11:16:20.158 [planetinfo.record]: name = "Erbiti"; 11:16:20.158 [planetinfo.record]: inhabitant = "Large Feline"; 11:16:20.158 [planetinfo.record]: inhabitants = "Large Felines"; 11:16:20.158 [planetinfo.record]: description = "The world Erbiti is most well known for its great dense forests."; 11:16:20.160 [planetinfo.record]: air_color = 0.665873, 0.846968, 0.928758, 1; 11:16:20.160 [planetinfo.record]: cloud_alpha = 1.000000; 11:16:20.160 [planetinfo.record]: cloud_color = 0.602585, 0.789062, 0.591797, 1; 11:16:20.161 [planetinfo.record]: cloud_fraction = 0.480000; 11:16:20.161 [planetinfo.record]: land_color = 0.588397, 0.543984, 0.66, 1; 11:16:20.161 [planetinfo.record]: land_fraction = 0.450000; 11:16:20.161 [planetinfo.record]: polar_cloud_color = 0.728779, 0.855078, 0.721472, 1; 11:16:20.161 [planetinfo.record]: polar_land_color = 0.908668, 0.892955, 0.934, 1; 11:16:20.161 [planetinfo.record]: polar_sea_color = 0.945312, 0.923398, 0.89777, 1; 11:16:20.161 [planetinfo.record]: sea_color = 0.546875, 0.496165, 0.436859, 1; 11:16:20.161 [planetinfo.record]: rotation_speed = 0.003445 11:16:20.161 [planetinfo.record]: planet zpos = 326040.000000 11:16:20.161 [planetinfo.record]: sun_radius = 53067.160034 11:16:20.161 [planetinfo.record]: sun_vector = 0.145 -0.940 0.308 11:16:20.161 [planetinfo.record]: sun_distance = 592800 11:16:20.161 [planetinfo.record]: corona_flare = 0.065544 11:16:20.161 [planetinfo.record]: corona_hues = 0.719742 11:16:20.162 [planetinfo.record]: sun_color = 0.784186, 0.561302, 0.506234, 1 11:16:20.162 [planetinfo.record]: corona_shimmer = 0.292561 11:16:20.162 [planetinfo.record]: station_vector = 0.585 -0.766 0.265 11:16:20.162 [planetinfo.record]: station = coriolis 11:16:25.778 [PLANETINFO OVER]: Done 11:16:25.778 [PLANETINFO LOGGING]: 0 111 11:16:26.784 [planetinfo.record]: seed = 172 55 125 142 11:16:26.784 [planetinfo.record]: coordinates = 55 102 11:16:26.784 [planetinfo.record]: government = 5; 11:16:26.784 [planetinfo.record]: economy = 6; 11:16:26.784 [planetinfo.record]: techlevel = 7; 11:16:26.784 [planetinfo.record]: population = 40; 11:16:26.784 [planetinfo.record]: productivity = 11520; 11:16:26.784 [planetinfo.record]: name = "Reinen"; 11:16:26.784 [planetinfo.record]: inhabitant = "Human Colonial"; 11:16:26.784 [planetinfo.record]: inhabitants = "Human Colonials"; 11:16:26.784 [planetinfo.record]: description = "This planet is a tedious little planet."; 11:16:26.788 [planetinfo.record]: air_color = 0.678938, 0.609015, 0.840305, 1; 11:16:26.788 [planetinfo.record]: cloud_alpha = 1.000000; 11:16:26.788 [planetinfo.record]: cloud_color = 0.514173, 0.404846, 0.523438, 1; 11:16:26.789 [planetinfo.record]: cloud_fraction = 0.600000; 11:16:26.789 [planetinfo.record]: land_color = 0.618105, 0.66, 0.12375, 1; 11:16:26.789 [planetinfo.record]: land_fraction = 0.290000; 11:16:26.789 [planetinfo.record]: polar_cloud_color = 0.72741, 0.631392, 0.735547, 1; 11:16:26.789 [planetinfo.record]: polar_land_color = 0.919178, 0.934, 0.744281, 1; 11:16:26.789 [planetinfo.record]: polar_sea_color = 0.927039, 0.938672, 0.890638, 1; 11:16:26.789 [planetinfo.record]: sea_color = 0.582879, 0.613281, 0.48775, 1; 11:16:26.789 [planetinfo.record]: rotation_speed = 0.001440 11:16:26.789 [planetinfo.record]: planet zpos = 645500.000000 11:16:26.789 [planetinfo.record]: sun_radius = 218081.804657 11:16:26.789 [planetinfo.record]: sun_vector = -0.296 -0.889 0.349 11:16:26.790 [planetinfo.record]: sun_distance = 1226450 11:16:26.790 [planetinfo.record]: corona_flare = 0.033209 11:16:26.790 [planetinfo.record]: corona_hues = 0.581802 11:16:26.790 [planetinfo.record]: sun_color = 0.520182, 0.665409, 0.815112, 1 11:16:26.790 [planetinfo.record]: corona_shimmer = 0.313407 11:16:26.790 [planetinfo.record]: station_vector = -0.613 -0.770 0.175 11:16:26.790 [planetinfo.record]: station = coriolis 11:16:31.461 [PLANETINFO OVER]: Done 11:16:31.462 [PLANETINFO LOGGING]: 0 112 11:16:32.482 [planetinfo.record]: seed = 104 173 83 204 11:16:32.482 [planetinfo.record]: coordinates = 173 242 11:16:32.482 [planetinfo.record]: government = 5; 11:16:32.482 [planetinfo.record]: economy = 2; 11:16:32.482 [planetinfo.record]: techlevel = 9; 11:16:32.482 [planetinfo.record]: population = 44; 11:16:32.482 [planetinfo.record]: productivity = 25344; 11:16:32.482 [planetinfo.record]: name = "Ininbi"; 11:16:32.482 [planetinfo.record]: inhabitant = "Human Colonial"; 11:16:32.483 [planetinfo.record]: inhabitants = "Human Colonials"; 11:16:32.483 [planetinfo.record]: description = "The world Ininbi is reasonably famous for its inhabitants’ exceptional loathing of casinos."; 11:16:32.493 [planetinfo.record]: air_color = 0.798, 0.542419, 0.943066, 1; 11:16:32.493 [planetinfo.record]: cloud_alpha = 1.000000; 11:16:32.493 [planetinfo.record]: cloud_color = 0.832031, 0.26976, 0.309295, 1; 11:16:32.493 [planetinfo.record]: cloud_fraction = 0.330000; 11:16:32.493 [planetinfo.record]: land_color = 0.243378, 0.311599, 0.623047, 1; 11:16:32.493 [planetinfo.record]: land_fraction = 0.500000; 11:16:32.493 [planetinfo.record]: polar_cloud_color = 0.874414, 0.505094, 0.531062, 1; 11:16:32.493 [planetinfo.record]: polar_land_color = 0.794843, 0.820512, 0.937695, 1; 11:16:32.494 [planetinfo.record]: polar_sea_color = 0.945117, 0.915357, 0.895646, 1; 11:16:32.494 [planetinfo.record]: sea_color = 0.548828, 0.479702, 0.433917, 1; 11:16:32.494 [planetinfo.record]: rotation_speed = 0.003908 11:16:32.494 [planetinfo.record]: planet zpos = 545490.000000 11:16:32.494 [planetinfo.record]: sun_radius = 183346.729736 11:16:32.494 [planetinfo.record]: sun_vector = 0.446 -0.693 0.566 11:16:32.494 [planetinfo.record]: sun_distance = 1151590 11:16:32.494 [planetinfo.record]: corona_flare = 0.014738 11:16:32.494 [planetinfo.record]: corona_hues = 0.871170 11:16:32.494 [planetinfo.record]: sun_color = 0.799335, 0.684595, 0.461347, 1 11:16:32.494 [planetinfo.record]: corona_shimmer = 0.326160 11:16:32.494 [planetinfo.record]: station_vector = -0.126 -0.915 0.383 11:16:32.494 [planetinfo.record]: station = coriolis 11:16:37.745 [PLANETINFO OVER]: Done 11:16:37.746 [PLANETINFO LOGGING]: 0 113 11:16:38.749 [planetinfo.record]: seed = 152 30 221 112 11:16:38.749 [planetinfo.record]: coordinates = 30 230 11:16:38.749 [planetinfo.record]: government = 3; 11:16:38.749 [planetinfo.record]: economy = 6; 11:16:38.749 [planetinfo.record]: techlevel = 5; 11:16:38.749 [planetinfo.record]: population = 30; 11:16:38.749 [planetinfo.record]: productivity = 6720; 11:16:38.749 [planetinfo.record]: name = "Erlaza"; 11:16:38.749 [planetinfo.record]: inhabitant = "Blue Slimy Rodent"; 11:16:38.750 [planetinfo.record]: inhabitants = "Blue Slimy Rodents"; 11:16:38.750 [planetinfo.record]: description = "The world Erlaza is mildly noted for its ancient mountains but plagued by lethal disease."; 11:16:38.756 [planetinfo.record]: air_color = 0.718003, 0.722544, 0.974936, 1; 11:16:38.756 [planetinfo.record]: cloud_alpha = 1.000000; 11:16:38.757 [planetinfo.record]: cloud_color = 0.75016, 0.757097, 0.927734, 1; 11:16:38.757 [planetinfo.record]: cloud_fraction = 0.300000; 11:16:38.757 [planetinfo.record]: land_color = 0.66, 0.265627, 0.201094, 1; 11:16:38.757 [planetinfo.record]: land_fraction = 0.710000; 11:16:38.757 [planetinfo.record]: polar_cloud_color = 0.807723, 0.812011, 0.91748, 1; 11:16:38.757 [planetinfo.record]: polar_land_color = 0.934, 0.794476, 0.771645, 1; 11:16:38.757 [planetinfo.record]: polar_sea_color = 0.865788, 0.921875, 0.89427, 1; 11:16:38.757 [planetinfo.record]: sea_color = 0.591125, 0.78125, 0.687673, 1; 11:16:38.757 [planetinfo.record]: rotation_speed = 0.001892 11:16:38.757 [planetinfo.record]: planet zpos = 341520.000000 11:16:38.757 [planetinfo.record]: sun_radius = 74510.343628 11:16:38.757 [planetinfo.record]: sun_vector = 0.811 0.539 -0.227 11:16:38.757 [planetinfo.record]: sun_distance = 569200 11:16:38.757 [planetinfo.record]: corona_flare = 0.012961 11:16:38.757 [planetinfo.record]: corona_hues = 0.783073 11:16:38.757 [planetinfo.record]: sun_color = 0.600186, 0.663362, 0.692532, 1 11:16:38.757 [planetinfo.record]: corona_shimmer = 0.347078 11:16:38.758 [planetinfo.record]: station_vector = -0.295 0.323 0.899 11:16:38.758 [planetinfo.record]: station = coriolis 11:16:43.421 [PLANETINFO OVER]: Done 11:16:43.422 [PLANETINFO LOGGING]: 0 114 11:16:44.428 [planetinfo.record]: seed = 60 235 155 101 11:16:44.428 [planetinfo.record]: coordinates = 235 4 11:16:44.428 [planetinfo.record]: government = 7; 11:16:44.428 [planetinfo.record]: economy = 4; 11:16:44.428 [planetinfo.record]: techlevel = 10; 11:16:44.428 [planetinfo.record]: population = 52; 11:16:44.428 [planetinfo.record]: productivity = 27456; 11:16:44.428 [planetinfo.record]: name = "Celabile"; 11:16:44.428 [planetinfo.record]: inhabitant = "Fierce Blue Rodent"; 11:16:44.429 [planetinfo.record]: inhabitants = "Fierce Blue Rodents"; 11:16:44.429 [planetinfo.record]: description = "The planet Celabile is most famous for the Celabileian evil poet and Zero-G hockey."; 11:16:44.457 [planetinfo.record]: air_color = 0.507567, 0.529051, 0.886482, 1; 11:16:44.457 [planetinfo.record]: cloud_alpha = 1.000000; 11:16:44.457 [planetinfo.record]: cloud_color = 0.209496, 0.280216, 0.662109, 1; 11:16:44.457 [planetinfo.record]: cloud_fraction = 0.330000; 11:16:44.457 [planetinfo.record]: land_color = 0.2013, 0.638672, 0.00249481, 1; 11:16:44.457 [planetinfo.record]: land_fraction = 0.570000; 11:16:44.457 [planetinfo.record]: polar_cloud_color = 0.457029, 0.510297, 0.797949, 1; 11:16:44.457 [planetinfo.record]: polar_land_color = 0.775863, 0.936133, 0.703014, 1; 11:16:44.457 [planetinfo.record]: polar_sea_color = 0.911058, 0.933008, 0.878977, 1; 11:16:44.458 [planetinfo.record]: sea_color = 0.60688, 0.669922, 0.514741, 1; 11:16:44.458 [planetinfo.record]: rotation_speed = 0.000590 11:16:44.458 [planetinfo.record]: planet zpos = 519720.000000 11:16:44.458 [planetinfo.record]: sun_radius = 112926.780548 11:16:44.458 [planetinfo.record]: sun_vector = -0.313 0.528 0.789 11:16:44.458 [planetinfo.record]: sun_distance = 736270 11:16:44.458 [planetinfo.record]: corona_flare = 0.035080 11:16:44.458 [planetinfo.record]: corona_hues = 0.580589 11:16:44.458 [planetinfo.record]: sun_color = 0.211073, 0.409362, 0.702292, 1 11:16:44.458 [planetinfo.record]: corona_shimmer = 0.455326 11:16:44.460 [planetinfo.record]: station_vector = -0.988 -0.094 0.120 11:16:44.460 [planetinfo.record]: station = coriolis 11:16:48.974 [PLANETINFO OVER]: Done 11:16:48.975 [PLANETINFO LOGGING]: 0 115 11:16:49.978 [planetinfo.record]: seed = 244 97 77 94 11:16:49.978 [planetinfo.record]: coordinates = 97 166 11:16:49.978 [planetinfo.record]: government = 6; 11:16:49.978 [planetinfo.record]: economy = 6; 11:16:49.978 [planetinfo.record]: techlevel = 5; 11:16:49.978 [planetinfo.record]: population = 33; 11:16:49.978 [planetinfo.record]: productivity = 10560; 11:16:49.978 [planetinfo.record]: name = "Ribiso"; 11:16:49.978 [planetinfo.record]: inhabitant = "Human Colonial"; 11:16:49.978 [planetinfo.record]: inhabitants = "Human Colonials"; 11:16:49.978 [planetinfo.record]: description = "This planet is fabled for its exciting vacuum cricket."; 11:16:50.016 [planetinfo.record]: air_color = 0.521099, 0.615261, 0.844857, 1; 11:16:50.016 [planetinfo.record]: cloud_alpha = 1.000000; 11:16:50.016 [planetinfo.record]: cloud_color = 0.24128, 0.495508, 0.537109, 1; 11:16:50.016 [planetinfo.record]: cloud_fraction = 0.380000; 11:16:50.016 [planetinfo.record]: land_color = 0.66, 0.0257813, 0.590632, 1; 11:16:50.016 [planetinfo.record]: land_fraction = 0.240000; 11:16:50.016 [planetinfo.record]: polar_cloud_color = 0.486378, 0.705795, 0.741699, 1; 11:16:50.016 [planetinfo.record]: polar_land_color = 0.934, 0.709621, 0.909459, 1; 11:16:50.016 [planetinfo.record]: polar_sea_color = 0.94082, 0.913293, 0.886613, 1; 11:16:50.016 [planetinfo.record]: sea_color = 0.591797, 0.522536, 0.455406, 1; 11:16:50.016 [planetinfo.record]: rotation_speed = 0.003612 11:16:50.016 [planetinfo.record]: planet zpos = 584730.000000 11:16:50.016 [planetinfo.record]: sun_radius = 185572.346954 11:16:50.016 [planetinfo.record]: sun_vector = -0.479 -0.414 -0.774 11:16:50.016 [planetinfo.record]: sun_distance = 1104490 11:16:50.016 [planetinfo.record]: corona_flare = 0.015868 11:16:50.016 [planetinfo.record]: corona_hues = 0.760490 11:16:50.016 [planetinfo.record]: sun_color = 0.657651, 0.247968, 0.21115, 1 11:16:50.016 [planetinfo.record]: corona_shimmer = 0.284976 11:16:50.017 [planetinfo.record]: station_vector = -0.619 -0.773 0.141 11:16:50.017 [planetinfo.record]: station = coriolis 11:16:55.430 [PLANETINFO OVER]: Done 11:16:55.431 [PLANETINFO LOGGING]: 0 116 11:16:56.434 [planetinfo.record]: seed = 64 236 51 122 11:16:56.434 [planetinfo.record]: coordinates = 236 39 11:16:56.434 [planetinfo.record]: government = 0; 11:16:56.434 [planetinfo.record]: economy = 7; 11:16:56.434 [planetinfo.record]: techlevel = 0; 11:16:56.434 [planetinfo.record]: population = 8; 11:16:56.434 [planetinfo.record]: productivity = 768; 11:16:56.434 [planetinfo.record]: name = "Qudira"; 11:16:56.434 [planetinfo.record]: inhabitant = "Human Colonial"; 11:16:56.434 [planetinfo.record]: inhabitants = "Human Colonials"; 11:16:56.434 [planetinfo.record]: description = "The world Qudira is reasonably fabled for Qudiraian Bima water and its great volcanoes."; 11:16:56.441 [planetinfo.record]: air_color = 0.52625, 0.571284, 0.860467, 1; 11:16:56.441 [planetinfo.record]: cloud_alpha = 1.000000; 11:16:56.441 [planetinfo.record]: cloud_color = 0.253212, 0.38242, 0.583984, 1; 11:16:56.442 [planetinfo.record]: cloud_fraction = 0.370000; 11:16:56.442 [planetinfo.record]: land_color = 0.66, 0.66, 0.66, 1; 11:16:56.442 [planetinfo.record]: land_fraction = 0.300000; 11:16:56.442 [planetinfo.record]: polar_cloud_color = 0.492761, 0.598242, 0.762793, 1; 11:16:56.442 [planetinfo.record]: polar_land_color = 0.934, 0.934, 0.934, 1; 11:16:56.442 [planetinfo.record]: polar_sea_color = 0.924219, 0.834019, 0.813204, 1; 11:16:56.442 [planetinfo.record]: sea_color = 0.757812, 0.461977, 0.393707, 1; 11:16:56.442 [planetinfo.record]: rotation_speed = 0.003568 11:16:56.442 [planetinfo.record]: planet zpos = 785680.000000 11:16:56.442 [planetinfo.record]: sun_radius = 137600.869141 11:16:56.442 [planetinfo.record]: sun_vector = -0.257 -0.932 -0.254 11:16:56.442 [planetinfo.record]: sun_distance = 1010160 11:16:56.442 [planetinfo.record]: corona_flare = 0.033598 11:16:56.442 [planetinfo.record]: corona_hues = 0.847672 11:16:56.442 [planetinfo.record]: sun_color = 0.707003, 0.748562, 0.800739, 1 11:16:56.443 [planetinfo.record]: corona_shimmer = 0.808448 11:16:56.443 [planetinfo.record]: station_vector = -0.764 0.461 0.451 11:16:56.443 [planetinfo.record]: station = coriolis 11:17:01.690 [PLANETINFO OVER]: Done 11:17:01.691 [PLANETINFO LOGGING]: 0 117 11:17:02.710 [planetinfo.record]: seed = 192 251 77 125 11:17:02.710 [planetinfo.record]: coordinates = 251 244 11:17:02.710 [planetinfo.record]: government = 0; 11:17:02.710 [planetinfo.record]: economy = 6; 11:17:02.710 [planetinfo.record]: techlevel = 4; 11:17:02.710 [planetinfo.record]: population = 23; 11:17:02.710 [planetinfo.record]: productivity = 2944; 11:17:02.710 [planetinfo.record]: name = "Isdibi"; 11:17:02.710 [planetinfo.record]: inhabitant = "Human Colonial"; 11:17:02.710 [planetinfo.record]: inhabitants = "Human Colonials"; 11:17:02.710 [planetinfo.record]: description = "The world Isdibi is scourged by deadly tree ants."; 11:17:02.729 [planetinfo.record]: air_color = 0.659932, 0.728903, 0.996398, 1; 11:17:02.729 [planetinfo.record]: cloud_alpha = 1.000000; 11:17:02.729 [planetinfo.record]: cloud_color = 0.58136, 0.828498, 0.992188, 1; 11:17:02.729 [planetinfo.record]: cloud_fraction = 0.430000; 11:17:02.729 [planetinfo.record]: land_color = 0.4125, 0.66, 0.636797, 1; 11:17:02.729 [planetinfo.record]: land_fraction = 0.480000; 11:17:02.729 [planetinfo.record]: polar_cloud_color = 0.701545, 0.848891, 0.946484, 1; 11:17:02.729 [planetinfo.record]: polar_land_color = 0.846438, 0.934, 0.925791, 1; 11:17:02.729 [planetinfo.record]: polar_sea_color = 0.907831, 0.932422, 0.878152, 1; 11:17:02.729 [planetinfo.record]: sea_color = 0.604491, 0.675781, 0.518451, 1; 11:17:02.729 [planetinfo.record]: rotation_speed = 0.001618 11:17:02.730 [planetinfo.record]: planet zpos = 703450.000000 11:17:02.730 [planetinfo.record]: sun_radius = 196029.349518 11:17:02.730 [planetinfo.record]: sun_vector = 0.654 -0.384 0.652 11:17:02.730 [planetinfo.record]: sun_distance = 1151100 11:17:02.730 [planetinfo.record]: corona_flare = 0.045734 11:17:02.730 [planetinfo.record]: corona_hues = 0.715172 11:17:02.730 [planetinfo.record]: sun_color = 0.727523, 0.778674, 0.783182, 1 11:17:02.730 [planetinfo.record]: corona_shimmer = 0.402654 11:17:02.730 [planetinfo.record]: station_vector = 0.709 0.628 0.321 11:17:02.730 [planetinfo.record]: station = coriolis 11:17:07.065 [PLANETINFO OVER]: Done 11:17:07.066 [PLANETINFO LOGGING]: 0 118 11:17:08.068 [planetinfo.record]: seed = 116 208 155 35 11:17:08.068 [planetinfo.record]: coordinates = 208 230 11:17:08.068 [planetinfo.record]: government = 6; 11:17:08.068 [planetinfo.record]: economy = 6; 11:17:08.068 [planetinfo.record]: techlevel = 4; 11:17:08.068 [planetinfo.record]: population = 29; 11:17:08.068 [planetinfo.record]: productivity = 9280; 11:17:08.068 [planetinfo.record]: name = "Gequre"; 11:17:08.068 [planetinfo.record]: inhabitant = "Large Red Frog"; 11:17:08.068 [planetinfo.record]: inhabitants = "Large Red Frogs"; 11:17:08.068 [planetinfo.record]: description = "This world is reasonably well known for the Gequreian tree ant but ravaged by dreadful civil war."; 11:17:08.071 [planetinfo.record]: air_color = 0.611798, 0.911197, 0.859471, 1; 11:17:08.071 [planetinfo.record]: cloud_alpha = 1.000000; 11:17:08.071 [planetinfo.record]: cloud_color = 0.736328, 0.591728, 0.451576, 1; 11:17:08.071 [planetinfo.record]: cloud_fraction = 0.320000; 11:17:08.071 [planetinfo.record]: land_color = 0.66, 0.239766, 0.387504, 1; 11:17:08.071 [planetinfo.record]: land_fraction = 0.280000; 11:17:08.071 [planetinfo.record]: polar_cloud_color = 0.831348, 0.72931, 0.630412, 1; 11:17:08.072 [planetinfo.record]: polar_land_color = 0.934, 0.785326, 0.837594, 1; 11:17:08.072 [planetinfo.record]: polar_sea_color = 0.940234, 0.903499, 0.883582, 1; 11:17:08.072 [planetinfo.record]: sea_color = 0.597656, 0.504252, 0.453612, 1; 11:17:08.072 [planetinfo.record]: rotation_speed = 0.002743 11:17:08.072 [planetinfo.record]: planet zpos = 455040.000000 11:17:08.072 [planetinfo.record]: sun_radius = 64613.166504 11:17:08.072 [planetinfo.record]: sun_vector = -0.802 -0.240 0.547 11:17:08.072 [planetinfo.record]: sun_distance = 834240 11:17:08.072 [planetinfo.record]: corona_flare = 0.099928 11:17:08.072 [planetinfo.record]: corona_hues = 0.958717 11:17:08.072 [planetinfo.record]: sun_color = 0.735931, 0.650616, 0.549172, 1 11:17:08.072 [planetinfo.record]: corona_shimmer = 0.319897 11:17:08.073 [planetinfo.record]: station_vector = -0.901 0.418 0.113 11:17:08.073 [planetinfo.record]: station = coriolis 11:17:12.258 [PLANETINFO OVER]: Done 11:17:12.258 [PLANETINFO LOGGING]: 0 119 11:17:13.261 [planetinfo.record]: seed = 252 203 93 116 11:17:13.261 [planetinfo.record]: coordinates = 203 206 11:17:13.261 [planetinfo.record]: government = 7; 11:17:13.261 [planetinfo.record]: economy = 6; 11:17:13.262 [planetinfo.record]: techlevel = 8; 11:17:13.262 [planetinfo.record]: population = 46; 11:17:13.262 [planetinfo.record]: productivity = 16192; 11:17:13.262 [planetinfo.record]: name = "Rarere"; 11:17:13.262 [planetinfo.record]: inhabitant = "Human Colonial"; 11:17:13.262 [planetinfo.record]: inhabitants = "Human Colonials"; 11:17:13.262 [planetinfo.record]: description = "The planet Rarere is mildly notable for Rarereian lethal brandy."; 11:17:13.280 [planetinfo.record]: air_color = 0.494587, 0.529727, 0.894287, 1; 11:17:13.280 [planetinfo.record]: cloud_alpha = 1.000000; 11:17:13.280 [planetinfo.record]: cloud_color = 0.176743, 0.303944, 0.685547, 1; 11:17:13.280 [planetinfo.record]: cloud_fraction = 0.420000; 11:17:13.280 [planetinfo.record]: land_color = 0.610049, 0.020625, 0.66, 1; 11:17:13.280 [planetinfo.record]: land_fraction = 0.400000; 11:17:13.280 [planetinfo.record]: polar_cloud_color = 0.433461, 0.52722, 0.808496, 1; 11:17:13.280 [planetinfo.record]: polar_land_color = 0.916328, 0.707797, 0.934, 1; 11:17:13.280 [planetinfo.record]: polar_sea_color = 0.941992, 0.912499, 0.888821, 1; 11:17:13.280 [planetinfo.record]: sea_color = 0.580078, 0.50743, 0.449107, 1; 11:17:13.280 [planetinfo.record]: rotation_speed = 0.000826 11:17:13.280 [planetinfo.record]: planet zpos = 404300.000000 11:17:13.280 [planetinfo.record]: sun_radius = 100770.861969 11:17:13.280 [planetinfo.record]: sun_vector = 0.729 0.657 0.194 11:17:13.280 [planetinfo.record]: sun_distance = 889460 11:17:13.280 [planetinfo.record]: corona_flare = 0.063696 11:17:13.280 [planetinfo.record]: corona_hues = 0.946953 11:17:13.280 [planetinfo.record]: sun_color = 0.531524, 0.571799, 0.723837, 1 11:17:13.280 [planetinfo.record]: corona_shimmer = 0.346847 11:17:13.281 [planetinfo.record]: station_vector = -0.243 0.959 0.143 11:17:13.281 [planetinfo.record]: station = coriolis 11:17:17.816 [PLANETINFO OVER]: Done 11:17:17.817 [PLANETINFO LOGGING]: 0 120 11:17:18.836 [planetinfo.record]: seed = 216 209 83 47 11:17:18.836 [planetinfo.record]: coordinates = 209 78 11:17:18.836 [planetinfo.record]: government = 3; 11:17:18.836 [planetinfo.record]: economy = 6; 11:17:18.836 [planetinfo.record]: techlevel = 4; 11:17:18.836 [planetinfo.record]: population = 26; 11:17:18.837 [planetinfo.record]: productivity = 5824; 11:17:18.837 [planetinfo.record]: name = "Aerater"; 11:17:18.837 [planetinfo.record]: inhabitant = "Human Colonial"; 11:17:18.837 [planetinfo.record]: inhabitants = "Human Colonials"; 11:17:18.837 [planetinfo.record]: description = "Aerater is a revolting little planet."; 11:17:18.852 [planetinfo.record]: air_color = 0.606946, 0.84023, 0.984041, 1; 11:17:18.852 [planetinfo.record]: cloud_alpha = 1.000000; 11:17:18.853 [planetinfo.record]: cloud_color = 0.429039, 0.955078, 0.511233, 1; 11:17:18.853 [planetinfo.record]: cloud_fraction = 0.260000; 11:17:18.853 [planetinfo.record]: land_color = 0.497739, 0.40863, 0.507812, 1; 11:17:18.853 [planetinfo.record]: land_fraction = 0.250000; 11:17:18.853 [planetinfo.record]: polar_cloud_color = 0.609717, 0.929785, 0.659728, 1; 11:17:18.853 [planetinfo.record]: polar_land_color = 0.944511, 0.90287, 0.949219, 1; 11:17:18.853 [planetinfo.record]: polar_sea_color = 0.911637, 0.927344, 0.873007, 1; 11:17:18.853 [planetinfo.record]: sea_color = 0.677339, 0.726562, 0.556274, 1; 11:17:18.853 [planetinfo.record]: rotation_speed = 0.003217 11:17:18.853 [planetinfo.record]: planet zpos = 892450.000000 11:17:18.853 [planetinfo.record]: sun_radius = 149656.497192 11:17:18.853 [planetinfo.record]: sun_vector = -0.720 0.209 0.662 11:17:18.853 [planetinfo.record]: sun_distance = 1304350 11:17:18.853 [planetinfo.record]: corona_flare = 0.073399 11:17:18.853 [planetinfo.record]: corona_hues = 0.515175 11:17:18.854 [planetinfo.record]: sun_color = 0.500625, 0.649638, 0.712762, 1 11:17:18.854 [planetinfo.record]: corona_shimmer = 0.327434 11:17:18.854 [planetinfo.record]: station_vector = 0.733 -0.611 0.299 11:17:18.854 [planetinfo.record]: station = coriolis 11:17:24.343 [PLANETINFO OVER]: Done 11:17:24.343 [PLANETINFO LOGGING]: 0 121 11:17:25.349 [planetinfo.record]: seed = 168 208 253 113 11:17:25.349 [planetinfo.record]: coordinates = 208 81 11:17:25.349 [planetinfo.record]: government = 5; 11:17:25.349 [planetinfo.record]: economy = 1; 11:17:25.349 [planetinfo.record]: techlevel = 9; 11:17:25.349 [planetinfo.record]: population = 43; 11:17:25.349 [planetinfo.record]: productivity = 27864; 11:17:25.349 [planetinfo.record]: name = "Atbevete"; 11:17:25.349 [planetinfo.record]: inhabitant = "Blue Bug-Eyed Lizard"; 11:17:25.349 [planetinfo.record]: inhabitants = "Blue Bug-Eyed Lizards"; 11:17:25.349 [planetinfo.record]: description = "The planet Atbevete is mildly well known for killer A’erin gargle blasters."; 11:17:25.367 [planetinfo.record]: air_color = 0.751993, 0.646357, 0.837703, 1; 11:17:25.367 [planetinfo.record]: cloud_alpha = 1.000000; 11:17:25.367 [planetinfo.record]: cloud_color = 0.515625, 0.471313, 0.485853, 1; 11:17:25.367 [planetinfo.record]: cloud_fraction = 0.360000; 11:17:25.367 [planetinfo.record]: land_color = 0.510831, 0.244922, 0.66, 1; 11:17:25.367 [planetinfo.record]: land_fraction = 0.420000; 11:17:25.367 [planetinfo.record]: polar_cloud_color = 0.732031, 0.692713, 0.705614, 1; 11:17:25.367 [planetinfo.record]: polar_land_color = 0.881226, 0.78715, 0.934, 1; 11:17:25.367 [planetinfo.record]: polar_sea_color = 0.930859, 0.856226, 0.846318, 1; 11:17:25.367 [planetinfo.record]: sea_color = 0.691406, 0.469666, 0.440231, 1; 11:17:25.367 [planetinfo.record]: rotation_speed = 0.001294 11:17:25.367 [planetinfo.record]: planet zpos = 426400.000000 11:17:25.367 [planetinfo.record]: sun_radius = 63743.188477 11:17:25.367 [planetinfo.record]: sun_vector = -0.611 -0.691 0.386 11:17:25.367 [planetinfo.record]: sun_distance = 557600 11:17:25.367 [planetinfo.record]: corona_flare = 0.078293 11:17:25.367 [planetinfo.record]: corona_hues = 0.654655 11:17:25.367 [planetinfo.record]: sun_color = 0.265591, 0.667918, 0.764938, 1 11:17:25.367 [planetinfo.record]: corona_shimmer = 0.267444 11:17:25.368 [planetinfo.record]: station_vector = -0.063 0.280 0.958 11:17:25.368 [planetinfo.record]: station = coriolis 11:17:30.207 [PLANETINFO OVER]: Done 11:17:30.208 [PLANETINFO LOGGING]: 0 122 11:17:31.211 [planetinfo.record]: seed = 108 220 219 70 11:17:31.211 [planetinfo.record]: coordinates = 220 214 11:17:31.211 [planetinfo.record]: government = 5; 11:17:31.211 [planetinfo.record]: economy = 6; 11:17:31.211 [planetinfo.record]: techlevel = 4; 11:17:31.211 [planetinfo.record]: population = 28; 11:17:31.211 [planetinfo.record]: productivity = 8064; 11:17:31.211 [planetinfo.record]: name = "Bioris"; 11:17:31.211 [planetinfo.record]: inhabitant = "Fierce Yellow Horned Bird"; 11:17:31.211 [planetinfo.record]: inhabitants = "Fierce Yellow Horned Birds"; 11:17:31.211 [planetinfo.record]: description = "Bioris is very fabled for the Biorisian edible poet."; 11:17:31.220 [planetinfo.record]: air_color = 0.593473, 0.903126, 0.970383, 1; 11:17:31.220 [planetinfo.record]: cloud_alpha = 1.000000; 11:17:31.220 [planetinfo.record]: cloud_color = 0.630929, 0.914062, 0.396332, 1; 11:17:31.220 [planetinfo.record]: cloud_fraction = 0.440000; 11:17:31.221 [planetinfo.record]: land_color = 0.177891, 0.366215, 0.66, 1; 11:17:31.221 [planetinfo.record]: land_fraction = 0.590000; 11:17:31.221 [planetinfo.record]: polar_cloud_color = 0.734899, 0.911328, 0.588714, 1; 11:17:31.221 [planetinfo.record]: polar_land_color = 0.763436, 0.830062, 0.934, 1; 11:17:31.221 [planetinfo.record]: polar_sea_color = 0.934961, 0.892961, 0.876526, 1; 11:17:31.221 [planetinfo.record]: sea_color = 0.650391, 0.533524, 0.487793, 1; 11:17:31.221 [planetinfo.record]: rotation_speed = 0.004948 11:17:31.221 [planetinfo.record]: planet zpos = 502920.000000 11:17:31.221 [planetinfo.record]: sun_radius = 134959.669189 11:17:31.222 [planetinfo.record]: sun_vector = 0.829 0.436 -0.350 11:17:31.222 [planetinfo.record]: sun_distance = 914400 11:17:31.222 [planetinfo.record]: corona_flare = 0.032404 11:17:31.222 [planetinfo.record]: corona_hues = 0.850700 11:17:31.222 [planetinfo.record]: sun_color = 0.380741, 0.521552, 0.759958, 1 11:17:31.222 [planetinfo.record]: corona_shimmer = 0.532173 11:17:31.222 [planetinfo.record]: station_vector = -0.978 0.183 0.096 11:17:31.223 [planetinfo.record]: station = coriolis 11:17:36.184 [PLANETINFO OVER]: Done 11:17:36.187 [PLANETINFO LOGGING]: 0 123 11:17:37.189 [planetinfo.record]: seed = 196 93 173 116 11:17:37.189 [planetinfo.record]: coordinates = 93 29 11:17:37.189 [planetinfo.record]: government = 0; 11:17:37.189 [planetinfo.record]: economy = 7; 11:17:37.189 [planetinfo.record]: techlevel = 1; 11:17:37.189 [planetinfo.record]: population = 12; 11:17:37.189 [planetinfo.record]: productivity = 1152; 11:17:37.189 [planetinfo.record]: name = "Raale"; 11:17:37.189 [planetinfo.record]: inhabitant = "Blue Slimy Rodent"; 11:17:37.189 [planetinfo.record]: inhabitants = "Blue Slimy Rodents"; 11:17:37.189 [planetinfo.record]: description = "This world is very fabled for the Raaleian edible poet."; 11:17:37.222 [planetinfo.record]: air_color = 0.626459, 0.524463, 0.911848, 1; 11:17:37.222 [planetinfo.record]: cloud_alpha = 1.000000; 11:17:37.223 [planetinfo.record]: cloud_color = 0.640837, 0.239365, 0.738281, 1; 11:17:37.223 [planetinfo.record]: cloud_fraction = 0.220000; 11:17:37.223 [planetinfo.record]: land_color = 0.66, 0.654844, 0.65569, 1; 11:17:37.223 [planetinfo.record]: land_fraction = 0.420000; 11:17:37.223 [planetinfo.record]: polar_cloud_color = 0.763574, 0.480725, 0.832227, 1; 11:17:37.223 [planetinfo.record]: polar_land_color = 0.934, 0.932176, 0.932475, 1; 11:17:37.223 [planetinfo.record]: polar_sea_color = 0.93418, 0.897688, 0.875793, 1; 11:17:37.223 [planetinfo.record]: sea_color = 0.658203, 0.555359, 0.493652, 1; 11:17:37.223 [planetinfo.record]: rotation_speed = 0.002996 11:17:37.223 [planetinfo.record]: planet zpos = 432630.000000 11:17:37.224 [planetinfo.record]: sun_radius = 99498.250580 11:17:37.224 [planetinfo.record]: sun_vector = -0.243 -0.471 0.848 11:17:37.224 [planetinfo.record]: sun_distance = 865260 11:17:37.224 [planetinfo.record]: corona_flare = 0.039349 11:17:37.224 [planetinfo.record]: corona_hues = 0.603104 11:17:37.224 [planetinfo.record]: sun_color = 0.514647, 0.559371, 0.73082, 1 11:17:37.225 [planetinfo.record]: corona_shimmer = 0.464939 11:17:37.225 [planetinfo.record]: station_vector = -0.858 0.486 0.164 11:17:37.225 [planetinfo.record]: station = coriolis 11:17:42.042 [PLANETINFO OVER]: Done 11:17:42.042 [PLANETINFO LOGGING]: 0 124 11:17:43.049 [planetinfo.record]: seed = 48 38 179 215 11:17:43.049 [planetinfo.record]: coordinates = 38 193 11:17:43.049 [planetinfo.record]: government = 6; 11:17:43.049 [planetinfo.record]: economy = 1; 11:17:43.049 [planetinfo.record]: techlevel = 11; 11:17:43.049 [planetinfo.record]: population = 52; 11:17:43.049 [planetinfo.record]: productivity = 37440; 11:17:43.049 [planetinfo.record]: name = "Tionisla"; 11:17:43.049 [planetinfo.record]: inhabitant = "Lizard"; 11:17:43.049 [planetinfo.record]: inhabitants = "Lizards"; 11:17:43.050 [planetinfo.record]: description = "This world is very notable for its inhabitants’ ingrained shyness."; 11:17:43.068 [planetinfo.record]: air_color = 0.456263, 0.674608, 0.872174, 1; 11:17:43.068 [planetinfo.record]: cloud_alpha = 1.000000; 11:17:43.068 [planetinfo.record]: cloud_color = 0.103996, 0.619141, 0.329372, 1; 11:17:43.068 [planetinfo.record]: cloud_fraction = 0.350000; 11:17:43.068 [planetinfo.record]: land_color = 0.568359, 0.553703, 0.539497, 1; 11:17:43.068 [planetinfo.record]: land_fraction = 0.380000; 11:17:43.068 [planetinfo.record]: polar_cloud_color = 0.373719, 0.778613, 0.55086, 1; 11:17:43.068 [planetinfo.record]: polar_land_color = 0.943164, 0.937084, 0.93119, 1; 11:17:43.068 [planetinfo.record]: polar_sea_color = 0.912252, 0.923633, 0.846964, 1; 11:17:43.068 [planetinfo.record]: sea_color = 0.726034, 0.763672, 0.510109, 1; 11:17:43.068 [planetinfo.record]: rotation_speed = 0.002956 11:17:43.068 [planetinfo.record]: planet zpos = 603980.000000 11:17:43.068 [planetinfo.record]: sun_radius = 96122.915649 11:17:43.068 [planetinfo.record]: sun_vector = -0.611 -0.121 0.782 11:17:43.068 [planetinfo.record]: sun_distance = 1115040 11:17:43.068 [planetinfo.record]: corona_flare = 0.055591 11:17:43.069 [planetinfo.record]: corona_hues = 0.600601 11:17:43.069 [planetinfo.record]: sun_color = 0.790427, 0.712919, 0.605487, 1 11:17:43.069 [planetinfo.record]: corona_shimmer = 0.342797 11:17:43.069 [planetinfo.record]: station_vector = -0.846 -0.325 0.422 11:17:43.069 [planetinfo.record]: station = dodecahedron 11:17:47.485 [PLANETINFO OVER]: Done 11:17:47.486 [PLANETINFO LOGGING]: 0 125 11:17:48.490 [planetinfo.record]: seed = 80 85 237 210 11:17:48.490 [planetinfo.record]: coordinates = 85 189 11:17:48.490 [planetinfo.record]: government = 2; 11:17:48.490 [planetinfo.record]: economy = 5; 11:17:48.490 [planetinfo.record]: techlevel = 4; 11:17:48.491 [planetinfo.record]: population = 24; 11:17:48.491 [planetinfo.record]: productivity = 5760; 11:17:48.491 [planetinfo.record]: name = "Encereso"; 11:17:48.491 [planetinfo.record]: inhabitant = "Slimy Lizard"; 11:17:48.491 [planetinfo.record]: inhabitants = "Slimy Lizards"; 11:17:48.491 [planetinfo.record]: description = "Encereso is cursed by dreadful civil war."; 11:17:48.515 [planetinfo.record]: air_color = 0.479603, 0.656069, 0.861117, 1; 11:17:48.515 [planetinfo.record]: cloud_alpha = 1.000000; 11:17:48.515 [planetinfo.record]: cloud_color = 0.157928, 0.585938, 0.425434, 1; 11:17:48.516 [planetinfo.record]: cloud_fraction = 0.440000; 11:17:48.516 [planetinfo.record]: land_color = 0.627308, 0.634766, 0.617409, 1; 11:17:48.516 [planetinfo.record]: land_fraction = 0.640000; 11:17:48.516 [planetinfo.record]: polar_cloud_color = 0.415023, 0.763672, 0.632928, 1; 11:17:48.516 [planetinfo.record]: polar_land_color = 0.933773, 0.936523, 0.930121, 1; 11:17:48.516 [planetinfo.record]: polar_sea_color = 0.801563, 0.90625, 0.776154, 1; 11:17:48.516 [planetinfo.record]: sea_color = 0.504313, 0.9375, 0.39917, 1; 11:17:48.516 [planetinfo.record]: rotation_speed = 0.001007 11:17:48.516 [planetinfo.record]: planet zpos = 409560.000000 11:17:48.516 [planetinfo.record]: sun_radius = 103859.127350 11:17:48.517 [planetinfo.record]: sun_vector = -0.128 0.949 0.288 11:17:48.517 [planetinfo.record]: sun_distance = 819120 11:17:48.517 [planetinfo.record]: corona_flare = 0.027188 11:17:48.517 [planetinfo.record]: corona_hues = 0.862083 11:17:48.517 [planetinfo.record]: sun_color = 0.454662, 0.475182, 0.837946, 1 11:17:48.517 [planetinfo.record]: corona_shimmer = 0.322609 11:17:48.517 [planetinfo.record]: station_vector = 0.071 0.965 0.251 11:17:48.517 [planetinfo.record]: station = coriolis 11:17:53.870 [PLANETINFO OVER]: Done 11:17:53.871 [PLANETINFO LOGGING]: 0 126 11:17:54.889 [planetinfo.record]: seed = 36 199 91 187 11:17:54.889 [planetinfo.record]: coordinates = 199 14 11:17:54.889 [planetinfo.record]: government = 4; 11:17:54.889 [planetinfo.record]: economy = 6; 11:17:54.889 [planetinfo.record]: techlevel = 6; 11:17:54.889 [planetinfo.record]: population = 35; 11:17:54.889 [planetinfo.record]: productivity = 8960; 11:17:54.889 [planetinfo.record]: name = "Anerbe"; 11:17:54.889 [planetinfo.record]: inhabitant = "Human Colonial"; 11:17:54.889 [planetinfo.record]: inhabitants = "Human Colonials"; 11:17:54.889 [planetinfo.record]: description = "The world Anerbe is reasonably fabled for its exciting vacuum karate and its great volcanoes."; 11:17:54.893 [planetinfo.record]: air_color = 0.613597, 0.908596, 0.855327, 1; 11:17:54.894 [planetinfo.record]: cloud_alpha = 1.000000; 11:17:54.894 [planetinfo.record]: cloud_color = 0.728516, 0.583382, 0.455322, 1; 11:17:54.894 [planetinfo.record]: cloud_fraction = 0.180000; 11:17:54.894 [planetinfo.record]: land_color = 0.193394, 0.511719, 0.147919, 1; 11:17:54.894 [planetinfo.record]: land_fraction = 0.580000; 11:17:54.894 [planetinfo.record]: polar_cloud_color = 0.827832, 0.724757, 0.633809, 1; 11:17:54.894 [planetinfo.record]: polar_land_color = 0.801269, 0.948828, 0.780189, 1; 11:17:54.894 [planetinfo.record]: polar_sea_color = 0.871289, 0.922266, 0.916292, 1; 11:17:54.894 [planetinfo.record]: sea_color = 0.605478, 0.777344, 0.757203, 1; 11:17:54.894 [planetinfo.record]: rotation_speed = 0.002186 11:17:54.894 [planetinfo.record]: planet zpos = 583100.000000 11:17:54.894 [planetinfo.record]: sun_radius = 198887.138977 11:17:54.894 [planetinfo.record]: sun_vector = 0.774 -0.006 0.633 11:17:54.894 [planetinfo.record]: sun_distance = 1166200 11:17:54.894 [planetinfo.record]: corona_flare = 0.032985 11:17:54.894 [planetinfo.record]: corona_hues = 0.752228 11:17:54.894 [planetinfo.record]: sun_color = 0.195194, 0.667045, 0.7008, 1 11:17:54.895 [planetinfo.record]: corona_shimmer = 1.443970 11:17:54.895 [planetinfo.record]: station_vector = 0.946 0.160 0.283 11:17:54.895 [planetinfo.record]: station = coriolis 11:17:59.407 [PLANETINFO OVER]: Done 11:17:59.408 [PLANETINFO LOGGING]: 0 127 11:18:00.423 [planetinfo.record]: seed = 76 95 61 3 11:18:00.423 [planetinfo.record]: coordinates = 95 19 11:18:00.423 [planetinfo.record]: government = 1; 11:18:00.423 [planetinfo.record]: economy = 3; 11:18:00.423 [planetinfo.record]: techlevel = 8; 11:18:00.423 [planetinfo.record]: population = 37; 11:18:00.423 [planetinfo.record]: productivity = 10360; 11:18:00.423 [planetinfo.record]: name = "Gelaed"; 11:18:00.423 [planetinfo.record]: inhabitant = "Human Colonial"; 11:18:00.423 [planetinfo.record]: inhabitants = "Human Colonials"; 11:18:00.423 [planetinfo.record]: description = "The planet Gelaed is very noted for its pink Gelaedian So Soweed plantations but scourged by deadly solar activity."; 11:18:00.436 [planetinfo.record]: air_color = 0.646844, 0.877384, 0.927457, 1; 11:18:00.436 [planetinfo.record]: cloud_alpha = 1.000000; 11:18:00.436 [planetinfo.record]: cloud_color = 0.652652, 0.785156, 0.542862, 1; 11:18:00.436 [planetinfo.record]: cloud_fraction = 0.440000; 11:18:00.436 [planetinfo.record]: land_color = 0.66, 0.487185, 0.319688, 1; 11:18:00.436 [planetinfo.record]: land_fraction = 0.260000; 11:18:00.436 [planetinfo.record]: polar_cloud_color = 0.763315, 0.85332, 0.688739, 1; 11:18:00.436 [planetinfo.record]: polar_land_color = 0.934, 0.87286, 0.813602, 1; 11:18:00.436 [planetinfo.record]: polar_sea_color = 0.873405, 0.922266, 0.866425, 1; 11:18:00.436 [planetinfo.record]: sea_color = 0.612614, 0.777344, 0.589081, 1; 11:18:00.436 [planetinfo.record]: rotation_speed = 0.000132 11:18:00.436 [planetinfo.record]: planet zpos = 515060.000000 11:18:00.436 [planetinfo.record]: sun_radius = 104129.240265 11:18:00.436 [planetinfo.record]: sun_vector = 0.250 0.565 0.786 11:18:00.437 [planetinfo.record]: sun_distance = 735800 11:18:00.437 [planetinfo.record]: corona_flare = 0.085246 11:18:00.437 [planetinfo.record]: corona_hues = 0.609718 11:18:00.437 [planetinfo.record]: sun_color = 0.666388, 0.682588, 0.684402, 1 11:18:00.437 [planetinfo.record]: corona_shimmer = 0.341953 11:18:00.437 [planetinfo.record]: station_vector = -0.541 0.825 0.162 11:18:00.437 [planetinfo.record]: station = coriolis 11:18:04.981 [PLANETINFO OVER]: Done 11:18:04.983 [PLANETINFO LOGGING]: 0 128 11:18:06.002 [planetinfo.record]: seed = 72 81 83 159 11:18:06.002 [planetinfo.record]: coordinates = 81 216 11:18:06.002 [planetinfo.record]: government = 1; 11:18:06.002 [planetinfo.record]: economy = 2; 11:18:06.002 [planetinfo.record]: techlevel = 7; 11:18:06.002 [planetinfo.record]: population = 32; 11:18:06.002 [planetinfo.record]: productivity = 10240; 11:18:06.002 [planetinfo.record]: name = "Onusorle"; 11:18:06.002 [planetinfo.record]: inhabitant = "Human Colonial"; 11:18:06.002 [planetinfo.record]: inhabitants = "Human Colonials"; 11:18:06.002 [planetinfo.record]: description = "This world is mildly well known for Onusorleian vicious brew and Onusorleian wolf cutlet."; 11:18:06.005 [planetinfo.record]: air_color = 0.610156, 0.823962, 0.989244, 1; 11:18:06.005 [planetinfo.record]: cloud_alpha = 1.000000; 11:18:06.005 [planetinfo.record]: cloud_color = 0.436058, 0.970703, 0.607312, 1; 11:18:06.005 [planetinfo.record]: cloud_fraction = 0.310000; 11:18:06.005 [planetinfo.record]: land_color = 0.66, 0.250078, 0.326938, 1; 11:18:06.005 [planetinfo.record]: land_fraction = 0.240000; 11:18:06.006 [planetinfo.record]: polar_cloud_color = 0.614328, 0.936816, 0.717625, 1; 11:18:06.006 [planetinfo.record]: polar_land_color = 0.934, 0.788975, 0.816167, 1; 11:18:06.006 [planetinfo.record]: polar_sea_color = 0.929564, 0.939844, 0.892852, 1; 11:18:06.006 [planetinfo.record]: sea_color = 0.575244, 0.601562, 0.48125, 1; 11:18:06.006 [planetinfo.record]: rotation_speed = 0.002624 11:18:06.006 [planetinfo.record]: planet zpos = 808440.000000 11:18:06.006 [planetinfo.record]: sun_radius = 218254.498444 11:18:06.006 [planetinfo.record]: sun_vector = -0.152 -0.690 -0.708 11:18:06.006 [planetinfo.record]: sun_distance = 1145290 11:18:06.006 [planetinfo.record]: corona_flare = 0.088098 11:18:06.006 [planetinfo.record]: corona_hues = 0.530914 11:18:06.006 [planetinfo.record]: sun_color = 0.740866, 0.280492, 0.198689, 1 11:18:06.006 [planetinfo.record]: corona_shimmer = 0.358249 11:18:06.006 [planetinfo.record]: station_vector = 0.408 0.423 0.809 11:18:06.007 [planetinfo.record]: station = coriolis 11:18:10.371 [PLANETINFO OVER]: Done 11:18:10.371 [PLANETINFO LOGGING]: 0 129 11:18:11.377 [planetinfo.record]: seed = 184 33 29 164 11:18:11.378 [planetinfo.record]: coordinates = 33 185 11:18:11.378 [planetinfo.record]: government = 7; 11:18:11.378 [planetinfo.record]: economy = 1; 11:18:11.378 [planetinfo.record]: techlevel = 11; 11:18:11.378 [planetinfo.record]: population = 53; 11:18:11.378 [planetinfo.record]: productivity = 41976; 11:18:11.378 [planetinfo.record]: name = "Zaonce"; 11:18:11.378 [planetinfo.record]: inhabitant = "Human Colonial"; 11:18:11.378 [planetinfo.record]: inhabitants = "Human Colonials"; 11:18:11.378 [planetinfo.record]: description = "This planet is a tedious place."; 11:18:11.395 [planetinfo.record]: air_color = 0.663605, 0.879328, 0.867313, 1; 11:18:11.395 [planetinfo.record]: cloud_alpha = 1.000000; 11:18:11.395 [planetinfo.record]: cloud_color = 0.640625, 0.62694, 0.55304, 1; 11:18:11.396 [planetinfo.record]: cloud_fraction = 0.460000; 11:18:11.396 [planetinfo.record]: land_color = 0.529881, 0.40493, 0.537109, 1; 11:18:11.396 [planetinfo.record]: land_fraction = 0.290000; 11:18:11.396 [planetinfo.record]: polar_cloud_color = 0.788281, 0.777757, 0.720923, 1; 11:18:11.396 [planetinfo.record]: polar_land_color = 0.943105, 0.88807, 0.946289, 1; 11:18:11.396 [planetinfo.record]: polar_sea_color = 0.878716, 0.927539, 0.902365, 1; 11:18:11.396 [planetinfo.record]: sea_color = 0.572045, 0.724609, 0.645943, 1; 11:18:11.397 [planetinfo.record]: rotation_speed = 0.000665 11:18:11.397 [planetinfo.record]: planet zpos = 387300.000000 11:18:11.397 [planetinfo.record]: sun_radius = 95063.309784 11:18:11.397 [planetinfo.record]: sun_vector = -0.170 0.779 0.603 11:18:11.397 [planetinfo.record]: sun_distance = 735870 11:18:11.397 [planetinfo.record]: corona_flare = 0.091243 11:18:11.397 [planetinfo.record]: corona_hues = 0.651939 11:18:11.397 [planetinfo.record]: sun_color = 0.281663, 0.323689, 0.836652, 1 11:18:11.397 [planetinfo.record]: corona_shimmer = 0.448220 11:18:11.398 [planetinfo.record]: station_vector = -0.819 0.196 0.540 11:18:11.398 [planetinfo.record]: station = icosahedron 11:18:16.582 [PLANETINFO OVER]: Done 11:18:16.583 [PLANETINFO LOGGING]: 0 130 11:18:17.588 [planetinfo.record]: seed = 156 104 27 45 11:18:17.588 [planetinfo.record]: coordinates = 104 69 11:18:17.588 [planetinfo.record]: government = 3; 11:18:17.588 [planetinfo.record]: economy = 5; 11:18:17.588 [planetinfo.record]: techlevel = 4; 11:18:17.588 [planetinfo.record]: population = 25; 11:18:17.589 [planetinfo.record]: productivity = 7000; 11:18:17.589 [planetinfo.record]: name = "Diquer"; 11:18:17.589 [planetinfo.record]: inhabitant = "Human Colonial"; 11:18:17.589 [planetinfo.record]: inhabitants = "Human Colonials"; 11:18:17.589 [planetinfo.record]: description = "The world Diquer is a dull place."; 11:18:17.608 [planetinfo.record]: air_color = 0.58371, 0.474125, 0.946318, 1; 11:18:17.608 [planetinfo.record]: cloud_alpha = 1.000000; 11:18:17.608 [planetinfo.record]: cloud_color = 0.623178, 0.085495, 0.841797, 1; 11:18:17.608 [planetinfo.record]: cloud_fraction = 0.430000; 11:18:17.608 [planetinfo.record]: land_color = 0.491495, 0.66, 0.430547, 1; 11:18:17.608 [planetinfo.record]: land_fraction = 0.290000; 11:18:17.608 [planetinfo.record]: polar_cloud_color = 0.736164, 0.385337, 0.878809, 1; 11:18:17.608 [planetinfo.record]: polar_land_color = 0.874385, 0.934, 0.852822, 1; 11:18:17.608 [planetinfo.record]: polar_sea_color = 0.9375, 0.893543, 0.876343, 1; 11:18:17.608 [planetinfo.record]: sea_color = 0.625, 0.507782, 0.461914, 1; 11:18:17.608 [planetinfo.record]: rotation_speed = 0.004294 11:18:17.608 [planetinfo.record]: planet zpos = 687280.000000 11:18:17.609 [planetinfo.record]: sun_radius = 209721.190186 11:18:17.609 [planetinfo.record]: sun_vector = 0.919 -0.324 0.225 11:18:17.609 [planetinfo.record]: sun_distance = 1187120 11:18:17.609 [planetinfo.record]: corona_flare = 0.073625 11:18:17.609 [planetinfo.record]: corona_hues = 0.762337 11:18:17.609 [planetinfo.record]: sun_color = 0.687915, 0.412903, 0.244343, 1 11:18:17.609 [planetinfo.record]: corona_shimmer = 0.451748 11:18:17.609 [planetinfo.record]: station_vector = 0.649 0.743 0.165 11:18:17.609 [planetinfo.record]: station = coriolis 11:18:22.757 [PLANETINFO OVER]: Done 11:18:22.758 [PLANETINFO LOGGING]: 0 131 11:18:23.762 [planetinfo.record]: seed = 148 120 13 196 11:18:23.762 [planetinfo.record]: coordinates = 120 112 11:18:23.762 [planetinfo.record]: government = 2; 11:18:23.762 [planetinfo.record]: economy = 0; 11:18:23.762 [planetinfo.record]: techlevel = 8; 11:18:23.762 [planetinfo.record]: population = 35; 11:18:23.762 [planetinfo.record]: productivity = 16800; 11:18:23.762 [planetinfo.record]: name = "Zadies"; 11:18:23.762 [planetinfo.record]: inhabitant = "Human Colonial"; 11:18:23.762 [planetinfo.record]: inhabitants = "Human Colonials"; 11:18:23.762 [planetinfo.record]: description = "The planet Zadies is famous for its inhabitants’ exceptional love for food blenders but scourged by dreadful solar activity."; 11:18:23.776 [planetinfo.record]: air_color = 0.647095, 0.886482, 0.858202, 1; 11:18:23.776 [planetinfo.record]: cloud_alpha = 1.000000; 11:18:23.776 [planetinfo.record]: cloud_color = 0.662109, 0.6141, 0.522446, 1; 11:18:23.776 [planetinfo.record]: cloud_fraction = 0.380000; 11:18:23.776 [planetinfo.record]: land_color = 0.556641, 0.487061, 0.491953, 1; 11:18:23.776 [planetinfo.record]: land_fraction = 0.360000; 11:18:23.776 [planetinfo.record]: polar_cloud_color = 0.797949, 0.761787, 0.692751, 1; 11:18:23.776 [planetinfo.record]: polar_land_color = 0.944336, 0.914825, 0.9169, 1; 11:18:23.776 [planetinfo.record]: polar_sea_color = 0.884982, 0.920117, 0.838349, 1; 11:18:23.776 [planetinfo.record]: sea_color = 0.676815, 0.798828, 0.51487, 1; 11:18:23.776 [planetinfo.record]: rotation_speed = 0.002412 11:18:23.776 [planetinfo.record]: planet zpos = 435600.000000 11:18:23.776 [planetinfo.record]: sun_radius = 84834.008789 11:18:23.776 [planetinfo.record]: sun_vector = -0.512 0.637 0.576 11:18:23.776 [planetinfo.record]: sun_distance = 712800 11:18:23.776 [planetinfo.record]: corona_flare = 0.022888 11:18:23.776 [planetinfo.record]: corona_hues = 0.517326 11:18:23.776 [planetinfo.record]: sun_color = 0.807513, 0.779316, 0.73813, 1 11:18:23.777 [planetinfo.record]: corona_shimmer = 0.260022 11:18:23.777 [planetinfo.record]: station_vector = 0.717 -0.223 0.661 11:18:23.777 [planetinfo.record]: station = coriolis 11:18:28.078 [PLANETINFO OVER]: Done 11:18:28.079 [PLANETINFO LOGGING]: 0 132 11:18:29.081 [planetinfo.record]: seed = 32 91 51 242 11:18:29.081 [planetinfo.record]: coordinates = 91 233 11:18:29.081 [planetinfo.record]: government = 4; 11:18:29.081 [planetinfo.record]: economy = 1; 11:18:29.081 [planetinfo.record]: techlevel = 11; 11:18:29.081 [planetinfo.record]: population = 50; 11:18:29.081 [planetinfo.record]: productivity = 28800; 11:18:29.081 [planetinfo.record]: name = "Entizadi"; 11:18:29.081 [planetinfo.record]: inhabitant = "Human Colonial"; 11:18:29.081 [planetinfo.record]: inhabitants = "Human Colonials"; 11:18:29.081 [planetinfo.record]: description = "The planet Entizadi is famous for its inhabitants’ exceptional love for food blenders but scourged by dreadful solar activity."; 11:18:29.086 [planetinfo.record]: air_color = 0.681188, 0.862092, 0.885832, 1; 11:18:29.086 [planetinfo.record]: cloud_alpha = 1.000000; 11:18:29.086 [planetinfo.record]: cloud_color = 0.637915, 0.660156, 0.598267, 1; 11:18:29.086 [planetinfo.record]: cloud_fraction = 0.190000; 11:18:29.086 [planetinfo.record]: land_color = 0.583984, 0.497014, 0.492737, 1; 11:18:29.087 [planetinfo.record]: land_fraction = 0.530000; 11:18:29.087 [planetinfo.record]: polar_cloud_color = 0.780286, 0.79707, 0.750367, 1; 11:18:29.087 [planetinfo.record]: polar_land_color = 0.941602, 0.906544, 0.90482, 1; 11:18:29.087 [planetinfo.record]: polar_sea_color = 0.803426, 0.90918, 0.797308, 1; 11:18:29.087 [planetinfo.record]: sea_color = 0.485643, 0.908203, 0.461197, 1; 11:18:29.087 [planetinfo.record]: rotation_speed = 0.002351 11:18:29.087 [planetinfo.record]: planet zpos = 376090.000000 11:18:29.087 [planetinfo.record]: sun_radius = 88173.222046 11:18:29.087 [planetinfo.record]: sun_vector = -0.515 0.752 0.412 11:18:29.087 [planetinfo.record]: sun_distance = 752180 11:18:29.087 [planetinfo.record]: corona_flare = 0.042856 11:18:29.087 [planetinfo.record]: corona_hues = 0.793205 11:18:29.087 [planetinfo.record]: sun_color = 0.496132, 0.758551, 0.830808, 1 11:18:29.088 [planetinfo.record]: corona_shimmer = 0.313668 11:18:29.088 [planetinfo.record]: station_vector = -0.405 -0.692 0.598 11:18:29.088 [planetinfo.record]: station = dodecahedron 11:18:33.759 [PLANETINFO OVER]: Done 11:18:33.760 [PLANETINFO LOGGING]: 0 133 11:18:34.763 [planetinfo.record]: seed = 224 173 141 105 11:18:34.763 [planetinfo.record]: coordinates = 173 132 11:18:34.763 [planetinfo.record]: government = 4; 11:18:34.763 [planetinfo.record]: economy = 4; 11:18:34.763 [planetinfo.record]: techlevel = 6; 11:18:34.763 [planetinfo.record]: population = 33; 11:18:34.763 [planetinfo.record]: productivity = 12672; 11:18:34.763 [planetinfo.record]: name = "Esanbe"; 11:18:34.763 [planetinfo.record]: inhabitant = "Small Blue Bug-Eyed Lizard"; 11:18:34.763 [planetinfo.record]: inhabitants = "Small Blue Bug-Eyed Lizards"; 11:18:34.763 [planetinfo.record]: description = "Esanbe is famous for its inhabitants’ ancient loathing of casinos but plagued by deadly earthquakes."; 11:18:34.776 [planetinfo.record]: air_color = 0.631858, 0.484069, 0.94892, 1; 11:18:34.776 [planetinfo.record]: cloud_alpha = 1.000000; 11:18:34.776 [planetinfo.record]: cloud_color = 0.826482, 0.10952, 0.849609, 1; 11:18:34.776 [planetinfo.record]: cloud_fraction = 0.370000; 11:18:34.776 [planetinfo.record]: land_color = 0.456167, 0.00773438, 0.66, 1; 11:18:34.776 [planetinfo.record]: land_fraction = 0.470000; 11:18:34.776 [planetinfo.record]: polar_cloud_color = 0.867313, 0.401957, 0.882324, 1; 11:18:34.776 [planetinfo.record]: polar_land_color = 0.861886, 0.703236, 0.934, 1; 11:18:34.776 [planetinfo.record]: polar_sea_color = 0.938672, 0.923883, 0.884588, 1; 11:18:34.783 [planetinfo.record]: sea_color = 0.613281, 0.574633, 0.471939, 1; 11:18:34.783 [planetinfo.record]: rotation_speed = 0.000347 11:18:34.784 [planetinfo.record]: planet zpos = 793950.000000 11:18:34.784 [planetinfo.record]: sun_radius = 158979.797211 11:18:34.784 [planetinfo.record]: sun_vector = 0.919 0.224 -0.325 11:18:34.784 [planetinfo.record]: sun_distance = 1164460 11:18:34.784 [planetinfo.record]: corona_flare = 0.050757 11:18:34.784 [planetinfo.record]: corona_hues = 0.555573 11:18:34.784 [planetinfo.record]: sun_color = 0.757297, 0.554051, 0.546465, 1 11:18:34.784 [planetinfo.record]: corona_shimmer = 0.891064 11:18:34.784 [planetinfo.record]: station_vector = 0.135 0.989 0.063 11:18:34.784 [planetinfo.record]: station = coriolis 11:18:40.214 [PLANETINFO OVER]: Done 11:18:40.215 [PLANETINFO LOGGING]: 0 134 11:18:41.218 [planetinfo.record]: seed = 212 184 27 8 11:18:41.219 [planetinfo.record]: coordinates = 184 179 11:18:41.219 [planetinfo.record]: government = 2; 11:18:41.219 [planetinfo.record]: economy = 3; 11:18:41.219 [planetinfo.record]: techlevel = 5; 11:18:41.219 [planetinfo.record]: population = 26; 11:18:41.219 [planetinfo.record]: productivity = 8736; 11:18:41.219 [planetinfo.record]: name = "Usralaat"; 11:18:41.219 [planetinfo.record]: inhabitant = "Human Colonial"; 11:18:41.219 [planetinfo.record]: inhabitants = "Human Colonials"; 11:18:41.219 [planetinfo.record]: description = "This planet is plagued by deadly earthquakes."; 11:18:41.232 [planetinfo.record]: air_color = 0.588912, 0.874848, 0.985992, 1; 11:18:41.232 [planetinfo.record]: cloud_alpha = 1.000000; 11:18:41.232 [planetinfo.record]: cloud_color = 0.462287, 0.960938, 0.375366, 1; 11:18:41.232 [planetinfo.record]: cloud_fraction = 0.160000; 11:18:41.232 [planetinfo.record]: land_color = 0.625, 0.530777, 0.498047, 1; 11:18:41.232 [planetinfo.record]: land_fraction = 0.240000; 11:18:41.232 [planetinfo.record]: polar_cloud_color = 0.630014, 0.932422, 0.5773, 1; 11:18:41.232 [planetinfo.record]: polar_land_color = 0.9375, 0.902166, 0.889893, 1; 11:18:41.232 [planetinfo.record]: polar_sea_color = 0.774247, 0.941602, 0.898455, 1; 11:18:41.232 [planetinfo.record]: sea_color = 0.168808, 0.583984, 0.476947, 1; 11:18:41.232 [planetinfo.record]: rotation_speed = 0.001476 11:18:41.232 [planetinfo.record]: planet zpos = 555280.000000 11:18:41.232 [planetinfo.record]: sun_radius = 119382.396240 11:18:41.232 [planetinfo.record]: sun_vector = 0.417 0.731 0.540 11:18:41.232 [planetinfo.record]: sun_distance = 1161040 11:18:41.232 [planetinfo.record]: corona_flare = 0.057478 11:18:41.232 [planetinfo.record]: corona_hues = 0.759346 11:18:41.232 [planetinfo.record]: sun_color = 0.326238, 0.588706, 0.695544, 1 11:18:41.232 [planetinfo.record]: corona_shimmer = 0.326131 11:18:41.233 [planetinfo.record]: station_vector = -0.059 -0.578 0.814 11:18:41.233 [planetinfo.record]: station = coriolis 11:18:46.049 [PLANETINFO OVER]: Done 11:18:46.050 [PLANETINFO LOGGING]: 0 135 11:18:47.052 [planetinfo.record]: seed = 156 177 29 91 11:18:47.052 [planetinfo.record]: coordinates = 177 53 11:18:47.052 [planetinfo.record]: government = 3; 11:18:47.052 [planetinfo.record]: economy = 5; 11:18:47.053 [planetinfo.record]: techlevel = 5; 11:18:47.053 [planetinfo.record]: population = 29; 11:18:47.053 [planetinfo.record]: productivity = 8120; 11:18:47.053 [planetinfo.record]: name = "Anlere"; 11:18:47.053 [planetinfo.record]: inhabitant = "Human Colonial"; 11:18:47.053 [planetinfo.record]: inhabitants = "Human Colonials"; 11:18:47.053 [planetinfo.record]: description = "Anlere is reasonably well known for the Anlereian spotted shrew but plagued by evil tree leopards."; 11:18:47.054 [planetinfo.record]: air_color = 0.624035, 0.899578, 0.938514, 1; 11:18:47.054 [planetinfo.record]: cloud_alpha = 1.000000; 11:18:47.054 [planetinfo.record]: cloud_color = 0.69109, 0.818359, 0.485901, 1; 11:18:47.054 [planetinfo.record]: cloud_fraction = 0.390000; 11:18:47.054 [planetinfo.record]: land_color = 0.140083, 0.587891, 0.346494, 1; 11:18:47.054 [planetinfo.record]: land_fraction = 0.360000; 11:18:47.054 [planetinfo.record]: polar_cloud_color = 0.783868, 0.868262, 0.647805, 1; 11:18:47.054 [planetinfo.record]: polar_land_color = 0.761976, 0.941211, 0.844592, 1; 11:18:47.054 [planetinfo.record]: polar_sea_color = 0.947461, 0.892038, 0.912822, 1; 11:18:47.054 [planetinfo.record]: sea_color = 0.525391, 0.402457, 0.448557, 1; 11:18:47.054 [planetinfo.record]: rotation_speed = 0.004560 11:18:47.055 [planetinfo.record]: planet zpos = 813260.000000 11:18:47.055 [planetinfo.record]: sun_radius = 110500.943756 11:18:47.055 [planetinfo.record]: sun_vector = -0.876 -0.055 -0.479 11:18:47.055 [planetinfo.record]: sun_distance = 1219890 11:18:47.055 [planetinfo.record]: corona_flare = 0.024710 11:18:47.055 [planetinfo.record]: corona_hues = 0.515976 11:18:47.055 [planetinfo.record]: sun_color = 0.734149, 0.344827, 0.276241, 1 11:18:47.055 [planetinfo.record]: corona_shimmer = 0.316550 11:18:47.055 [planetinfo.record]: station_vector = 0.703 0.112 0.702 11:18:47.055 [planetinfo.record]: station = coriolis 11:18:51.710 [PLANETINFO OVER]: Done 11:18:51.713 [PLANETINFO LOGGING]: 0 136 11:18:52.731 [planetinfo.record]: seed = 184 235 83 124 11:18:52.731 [planetinfo.record]: coordinates = 235 78 11:18:52.731 [planetinfo.record]: government = 7; 11:18:52.731 [planetinfo.record]: economy = 6; 11:18:52.731 [planetinfo.record]: techlevel = 8; 11:18:52.731 [planetinfo.record]: population = 46; 11:18:52.731 [planetinfo.record]: productivity = 16192; 11:18:52.732 [planetinfo.record]: name = "Teveri"; 11:18:52.732 [planetinfo.record]: inhabitant = "Human Colonial"; 11:18:52.732 [planetinfo.record]: inhabitants = "Human Colonials"; 11:18:52.732 [planetinfo.record]: description = "The world Teveri is reasonably fabled for Teveriian evil juice and its inhabitants’ ingrained shyness."; 11:18:52.753 [planetinfo.record]: air_color = 0.619653, 0.910547, 0.873912, 1; 11:18:52.753 [planetinfo.record]: cloud_alpha = 1.000000; 11:18:52.753 [planetinfo.record]: cloud_color = 0.734375, 0.637468, 0.470459, 1; 11:18:52.753 [planetinfo.record]: cloud_fraction = 0.100000; 11:18:52.753 [planetinfo.record]: land_color = 0.66, 0.159844, 0.476349, 1; 11:18:52.753 [planetinfo.record]: land_fraction = 0.310000; 11:18:52.754 [planetinfo.record]: polar_cloud_color = 0.830469, 0.761977, 0.643938, 1; 11:18:52.754 [planetinfo.record]: polar_land_color = 0.934, 0.757051, 0.869026, 1; 11:18:52.754 [planetinfo.record]: polar_sea_color = 0.938672, 0.900571, 0.879913, 1; 11:18:52.754 [planetinfo.record]: sea_color = 0.613281, 0.513707, 0.459721, 1; 11:18:52.754 [planetinfo.record]: rotation_speed = 0.002006 11:18:52.754 [planetinfo.record]: planet zpos = 673530.000000 11:18:52.754 [planetinfo.record]: sun_radius = 136685.585632 11:18:52.754 [planetinfo.record]: sun_vector = 0.169 -0.970 -0.176 11:18:52.754 [planetinfo.record]: sun_distance = 1163370 11:18:52.754 [planetinfo.record]: corona_flare = 0.070160 11:18:52.755 [planetinfo.record]: corona_hues = 0.886627 11:18:52.755 [planetinfo.record]: sun_color = 0.457119, 0.47144, 0.670102, 1 11:18:52.755 [planetinfo.record]: corona_shimmer = 0.416556 11:18:52.755 [planetinfo.record]: station_vector = -0.206 -0.946 0.248 11:18:52.755 [planetinfo.record]: station = coriolis 11:18:57.418 [PLANETINFO OVER]: Done 11:18:57.418 [PLANETINFO LOGGING]: 0 137 11:18:58.434 [planetinfo.record]: seed = 200 81 61 39 11:18:58.434 [planetinfo.record]: coordinates = 81 30 11:18:58.434 [planetinfo.record]: government = 1; 11:18:58.434 [planetinfo.record]: economy = 6; 11:18:58.434 [planetinfo.record]: techlevel = 3; 11:18:58.434 [planetinfo.record]: population = 20; 11:18:58.434 [planetinfo.record]: productivity = 3200; 11:18:58.434 [planetinfo.record]: name = "Sotiera"; 11:18:58.434 [planetinfo.record]: inhabitant = "Human Colonial"; 11:18:58.434 [planetinfo.record]: inhabitants = "Human Colonials"; 11:18:58.434 [planetinfo.record]: description = "The world Sotiera is mildly fabled for the Sotieraian mountain poet but cursed by unpredictable earthquakes."; 11:18:58.440 [planetinfo.record]: air_color = 0.684315, 0.742133, 0.977537, 1; 11:18:58.440 [planetinfo.record]: cloud_alpha = 1.000000; 11:18:58.440 [planetinfo.record]: cloud_color = 0.654152, 0.816833, 0.935547, 1; 11:18:58.440 [planetinfo.record]: cloud_fraction = 0.440000; 11:18:58.441 [planetinfo.record]: land_color = 0.66, 0.148887, 0.0309375, 1; 11:18:58.441 [planetinfo.record]: land_fraction = 0.480000; 11:18:58.441 [planetinfo.record]: polar_cloud_color = 0.74786, 0.847954, 0.920996, 1; 11:18:58.441 [planetinfo.record]: polar_land_color = 0.934, 0.753174, 0.711445, 1; 11:18:58.441 [planetinfo.record]: polar_sea_color = 0.899327, 0.934961, 0.887939, 1; 11:18:58.441 [planetinfo.record]: sea_color = 0.551238, 0.650391, 0.51955, 1; 11:18:58.441 [planetinfo.record]: rotation_speed = 0.004990 11:18:58.441 [planetinfo.record]: planet zpos = 656460.000000 11:18:58.441 [planetinfo.record]: sun_radius = 91599.202881 11:18:58.441 [planetinfo.record]: sun_vector = 0.593 -0.725 -0.349 11:18:58.441 [planetinfo.record]: sun_distance = 984690 11:18:58.441 [planetinfo.record]: corona_flare = 0.021927 11:18:58.441 [planetinfo.record]: corona_hues = 0.654289 11:18:58.441 [planetinfo.record]: sun_color = 0.557342, 0.652416, 0.835367, 1 11:18:58.441 [planetinfo.record]: corona_shimmer = 0.326739 11:18:58.442 [planetinfo.record]: station_vector = 0.630 0.594 0.500 11:18:58.442 [planetinfo.record]: station = coriolis 11:19:03.676 [PLANETINFO OVER]: Done 11:19:03.679 [PLANETINFO LOGGING]: 0 138 11:19:04.700 [planetinfo.record]: seed = 204 207 91 120 11:19:04.700 [planetinfo.record]: coordinates = 207 16 11:19:04.701 [planetinfo.record]: government = 1; 11:19:04.701 [planetinfo.record]: economy = 2; 11:19:04.701 [planetinfo.record]: techlevel = 9; 11:19:04.701 [planetinfo.record]: population = 40; 11:19:04.701 [planetinfo.record]: productivity = 12800; 11:19:04.701 [planetinfo.record]: name = "Ededleen"; 11:19:04.701 [planetinfo.record]: inhabitant = "Human Colonial"; 11:19:04.701 [planetinfo.record]: inhabitants = "Human Colonials"; 11:19:04.701 [planetinfo.record]: description = "The planet Ededleen is mildly well known for its exotic cuisine."; 11:19:04.718 [planetinfo.record]: air_color = 0.587285, 0.898243, 0.976887, 1; 11:19:04.718 [planetinfo.record]: cloud_alpha = 1.000000; 11:19:04.718 [planetinfo.record]: cloud_color = 0.589223, 0.933594, 0.375626, 1; 11:19:04.718 [planetinfo.record]: cloud_fraction = 0.320000; 11:19:04.718 [planetinfo.record]: land_color = 0.66, 0.0979688, 0.177004, 1; 11:19:04.718 [planetinfo.record]: land_fraction = 0.470000; 11:19:04.719 [planetinfo.record]: polar_cloud_color = 0.707992, 0.920117, 0.576421, 1; 11:19:04.719 [planetinfo.record]: polar_land_color = 0.934, 0.73516, 0.763122, 1; 11:19:04.719 [planetinfo.record]: polar_sea_color = 0.94043, 0.900839, 0.88349, 1; 11:19:04.719 [planetinfo.record]: sea_color = 0.595703, 0.495389, 0.451431, 1; 11:19:04.719 [planetinfo.record]: rotation_speed = 0.003719 11:19:04.720 [planetinfo.record]: planet zpos = 557810.000000 11:19:04.720 [planetinfo.record]: sun_radius = 129288.989105 11:19:04.720 [planetinfo.record]: sun_vector = 0.845 -0.050 -0.533 11:19:04.720 [planetinfo.record]: sun_distance = 1217040 11:19:04.720 [planetinfo.record]: corona_flare = 0.051476 11:19:04.720 [planetinfo.record]: corona_hues = 0.833473 11:19:04.720 [planetinfo.record]: sun_color = 0.222962, 0.737635, 0.820383, 1 11:19:04.721 [planetinfo.record]: corona_shimmer = 0.315775 11:19:04.721 [planetinfo.record]: station_vector = -0.662 0.726 0.185 11:19:04.722 [planetinfo.record]: station = coriolis 11:19:10.212 [PLANETINFO OVER]: Done 11:19:10.213 [PLANETINFO LOGGING]: 0 139 11:19:11.231 [planetinfo.record]: seed = 100 114 109 108 11:19:11.231 [planetinfo.record]: coordinates = 114 161 11:19:11.231 [planetinfo.record]: government = 4; 11:19:11.231 [planetinfo.record]: economy = 1; 11:19:11.231 [planetinfo.record]: techlevel = 10; 11:19:11.231 [planetinfo.record]: population = 46; 11:19:11.231 [planetinfo.record]: productivity = 26496; 11:19:11.231 [planetinfo.record]: name = "Inonri"; 11:19:11.231 [planetinfo.record]: inhabitant = "Human Colonial"; 11:19:11.231 [planetinfo.record]: inhabitants = "Human Colonials"; 11:19:11.231 [planetinfo.record]: description = "This world is very well known for Inonriian wolf meat and its weird volcanoes."; 11:19:11.233 [planetinfo.record]: air_color = 0.569433, 0.569988, 0.838354, 1; 11:19:11.233 [planetinfo.record]: cloud_alpha = 1.000000; 11:19:11.233 [planetinfo.record]: cloud_color = 0.331021, 0.329552, 0.517578, 1; 11:19:11.233 [planetinfo.record]: cloud_fraction = 0.160000; 11:19:11.234 [planetinfo.record]: land_color = 0.5625, 0.419678, 0.533489, 1; 11:19:11.234 [planetinfo.record]: land_fraction = 0.550000; 11:19:11.234 [planetinfo.record]: polar_cloud_color = 0.567802, 0.566502, 0.73291, 1; 11:19:11.234 [planetinfo.record]: polar_land_color = 0.94375, 0.883844, 0.931582, 1; 11:19:11.234 [planetinfo.record]: polar_sea_color = 0.914453, 0.752816, 0.752816, 1; 11:19:11.234 [planetinfo.record]: sea_color = 0.855469, 0.250626, 0.250626, 1; 11:19:11.234 [planetinfo.record]: rotation_speed = 0.001746 11:19:11.234 [planetinfo.record]: planet zpos = 660220.000000 11:19:11.234 [planetinfo.record]: sun_radius = 173871.719666 11:19:11.234 [planetinfo.record]: sun_vector = -0.404 0.449 0.797 11:19:11.234 [planetinfo.record]: sun_distance = 1200400 11:19:11.234 [planetinfo.record]: corona_flare = 0.079927 11:19:11.234 [planetinfo.record]: corona_hues = 0.961861 11:19:11.234 [planetinfo.record]: sun_color = 0.725082, 0.510847, 0.188996, 1 11:19:11.234 [planetinfo.record]: corona_shimmer = 0.947490 11:19:11.235 [planetinfo.record]: station_vector = -0.818 -0.223 0.530 11:19:11.235 [planetinfo.record]: station = coriolis 11:19:15.910 [PLANETINFO OVER]: Done 11:19:15.911 [PLANETINFO LOGGING]: 0 140 11:19:16.931 [planetinfo.record]: seed = 16 75 179 41 11:19:16.931 [planetinfo.record]: coordinates = 75 94 11:19:16.931 [planetinfo.record]: government = 2; 11:19:16.931 [planetinfo.record]: economy = 6; 11:19:16.931 [planetinfo.record]: techlevel = 5; 11:19:16.931 [planetinfo.record]: population = 29; 11:19:16.931 [planetinfo.record]: productivity = 5568; 11:19:16.931 [planetinfo.record]: name = "Esbeus"; 11:19:16.931 [planetinfo.record]: inhabitant = "Small Red Furry Feline"; 11:19:16.931 [planetinfo.record]: inhabitants = "Small Red Furry Felines"; 11:19:16.931 [planetinfo.record]: description = "The world Esbeus is mildly noted for its ancient mountains but plagued by frequent earthquakes."; 11:19:16.957 [planetinfo.record]: air_color = 0.64111, 0.895042, 0.920953, 1; 11:19:16.957 [planetinfo.record]: cloud_alpha = 1.000000; 11:19:16.957 [planetinfo.record]: cloud_color = 0.696465, 0.765625, 0.526367, 1; 11:19:16.957 [planetinfo.record]: cloud_fraction = 0.560000; 11:19:16.957 [planetinfo.record]: land_color = 0.600703, 0.626646, 0.66, 1; 11:19:16.958 [planetinfo.record]: land_fraction = 0.440000; 11:19:16.958 [planetinfo.record]: polar_cloud_color = 0.796851, 0.844531, 0.679584, 1; 11:19:16.958 [planetinfo.record]: polar_land_color = 0.913022, 0.9222, 0.934, 1; 11:19:16.958 [planetinfo.record]: polar_sea_color = 0.925195, 0.821037, 0.81316, 1; 11:19:16.958 [planetinfo.record]: sea_color = 0.748047, 0.411188, 0.385712, 1; 11:19:16.959 [planetinfo.record]: rotation_speed = 0.001632 11:19:16.960 [planetinfo.record]: planet zpos = 623400.000000 11:19:16.960 [planetinfo.record]: sun_radius = 119393.206024 11:19:16.960 [planetinfo.record]: sun_vector = 0.796 -0.377 0.473 11:19:16.960 [planetinfo.record]: sun_distance = 987050 11:19:16.960 [planetinfo.record]: corona_flare = 0.051686 11:19:16.960 [planetinfo.record]: corona_hues = 0.633217 11:19:16.960 [planetinfo.record]: sun_color = 0.711682, 0.643868, 0.407654, 1 11:19:16.960 [planetinfo.record]: corona_shimmer = 0.418365 11:19:16.961 [planetinfo.record]: station_vector = -0.953 0.007 0.302 11:19:16.961 [planetinfo.record]: station = coriolis 11:19:21.861 [PLANETINFO OVER]: Done 11:19:21.862 [PLANETINFO LOGGING]: 0 141 11:19:22.871 [planetinfo.record]: seed = 112 69 45 97 11:19:22.872 [planetinfo.record]: coordinates = 69 72 11:19:22.872 [planetinfo.record]: government = 6; 11:19:22.872 [planetinfo.record]: economy = 0; 11:19:22.872 [planetinfo.record]: techlevel = 11; 11:19:22.872 [planetinfo.record]: population = 51; 11:19:22.872 [planetinfo.record]: productivity = 40800; 11:19:22.872 [planetinfo.record]: name = "Lerelace"; 11:19:22.872 [planetinfo.record]: inhabitant = "Human Colonial"; 11:19:22.872 [planetinfo.record]: inhabitants = "Human Colonials"; 11:19:22.872 [planetinfo.record]: description = "This planet is a dull place."; 11:19:22.895 [planetinfo.record]: air_color = 0.771197, 0.651971, 0.843557, 1; 11:19:22.895 [planetinfo.record]: cloud_alpha = 1.000000; 11:19:22.895 [planetinfo.record]: cloud_color = 0.533203, 0.489464, 0.49459, 1; 11:19:22.895 [planetinfo.record]: cloud_fraction = 0.450000; 11:19:22.895 [planetinfo.record]: land_color = 0.583945, 0.0515625, 0.66, 1; 11:19:22.895 [planetinfo.record]: land_fraction = 0.470000; 11:19:22.895 [planetinfo.record]: polar_cloud_color = 0.739941, 0.702005, 0.706451, 1; 11:19:22.896 [planetinfo.record]: polar_land_color = 0.907093, 0.718742, 0.934, 1; 11:19:22.896 [planetinfo.record]: polar_sea_color = 0.942578, 0.922982, 0.892412, 1; 11:19:22.896 [planetinfo.record]: sea_color = 0.574219, 0.526466, 0.451973, 1; 11:19:22.896 [planetinfo.record]: rotation_speed = 0.004703 11:19:22.896 [planetinfo.record]: planet zpos = 439740.000000 11:19:22.896 [planetinfo.record]: sun_radius = 63205.339966 11:19:22.896 [planetinfo.record]: sun_vector = 0.091 -0.779 -0.620 11:19:22.896 [planetinfo.record]: sun_distance = 596790 11:19:22.897 [planetinfo.record]: corona_flare = 0.042047 11:19:22.897 [planetinfo.record]: corona_hues = 0.829933 11:19:22.897 [planetinfo.record]: sun_color = 0.413559, 0.73521, 0.823074, 1 11:19:22.897 [planetinfo.record]: corona_shimmer = 0.404383 11:19:22.897 [planetinfo.record]: station_vector = -0.057 -0.825 0.563 11:19:22.897 [planetinfo.record]: station = dodecahedron 11:19:27.862 [PLANETINFO OVER]: Done 11:19:27.863 [PLANETINFO LOGGING]: 0 142 11:19:28.867 [planetinfo.record]: seed = 132 229 219 105 11:19:28.867 [planetinfo.record]: coordinates = 229 149 11:19:28.867 [planetinfo.record]: government = 0; 11:19:28.867 [planetinfo.record]: economy = 7; 11:19:28.867 [planetinfo.record]: techlevel = 1; 11:19:28.867 [planetinfo.record]: population = 12; 11:19:28.867 [planetinfo.record]: productivity = 1152; 11:19:28.867 [planetinfo.record]: name = "Eszaraxe"; 11:19:28.868 [planetinfo.record]: inhabitant = "Small Blue Slimy Frog"; 11:19:28.868 [planetinfo.record]: inhabitants = "Small Blue Slimy Frogs"; 11:19:28.868 [planetinfo.record]: description = "The planet Eszaraxe is most famous for the Eszaraxeian spotted shrew and the Eszaraxeian mountain poet."; 11:19:28.908 [planetinfo.record]: air_color = 0.661267, 0.834391, 0.941115, 1; 11:19:28.908 [planetinfo.record]: cloud_alpha = 1.000000; 11:19:28.908 [planetinfo.record]: cloud_color = 0.584129, 0.826172, 0.621948, 1; 11:19:28.908 [planetinfo.record]: cloud_fraction = 0.110000; 11:19:28.908 [planetinfo.record]: land_color = 0.66, 0.474657, 0.384141, 1; 11:19:28.908 [planetinfo.record]: land_fraction = 0.540000; 11:19:28.908 [planetinfo.record]: polar_cloud_color = 0.71215, 0.871777, 0.737092, 1; 11:19:28.908 [planetinfo.record]: polar_land_color = 0.934, 0.868428, 0.836404, 1; 11:19:28.909 [planetinfo.record]: polar_sea_color = 0.936914, 0.850908, 0.905334, 1; 11:19:28.909 [planetinfo.record]: sea_color = 0.630859, 0.399216, 0.545803, 1; 11:19:28.909 [planetinfo.record]: rotation_speed = 0.000903 11:19:28.909 [planetinfo.record]: planet zpos = 695370.000000 11:19:28.909 [planetinfo.record]: sun_radius = 164579.529877 11:19:28.909 [planetinfo.record]: sun_vector = 0.461 -0.508 -0.728 11:19:28.909 [planetinfo.record]: sun_distance = 1123290 11:19:28.909 [planetinfo.record]: corona_flare = 0.048920 11:19:28.909 [planetinfo.record]: corona_hues = 0.680176 11:19:28.909 [planetinfo.record]: sun_color = 0.401209, 0.766341, 0.842313, 1 11:19:28.910 [planetinfo.record]: corona_shimmer = 0.315967 11:19:28.910 [planetinfo.record]: station_vector = -0.475 0.871 0.124 11:19:28.910 [planetinfo.record]: station = coriolis 11:19:34.625 [PLANETINFO OVER]: Done 11:19:34.628 [PLANETINFO LOGGING]: 0 143 11:19:35.642 [planetinfo.record]: seed = 236 130 253 155 11:19:35.642 [planetinfo.record]: coordinates = 130 52 11:19:35.642 [planetinfo.record]: government = 5; 11:19:35.642 [planetinfo.record]: economy = 4; 11:19:35.642 [planetinfo.record]: techlevel = 8; 11:19:35.642 [planetinfo.record]: population = 42; 11:19:35.642 [planetinfo.record]: productivity = 18144; 11:19:35.642 [planetinfo.record]: name = "Anbeen"; 11:19:35.642 [planetinfo.record]: inhabitant = "Black Frog"; 11:19:35.642 [planetinfo.record]: inhabitants = "Black Frogs"; 11:19:35.642 [planetinfo.record]: description = "Anbeen is reasonably notable for its great tropical forests but cursed by dreadful solar activity."; 11:19:35.652 [planetinfo.record]: air_color = 0.413069, 0.83751, 0.857865, 1; 11:19:35.652 [planetinfo.record]: cloud_alpha = 1.000000; 11:19:35.652 [planetinfo.record]: cloud_color = 0.494321, 0.576172, 0.0247574, 1; 11:19:35.652 [planetinfo.record]: cloud_fraction = 0.280000; 11:19:35.652 [planetinfo.record]: land_color = 0.66, 0.631641, 0.641611, 1; 11:19:35.652 [planetinfo.record]: land_fraction = 0.540000; 11:19:35.652 [planetinfo.record]: polar_cloud_color = 0.691863, 0.759277, 0.30512, 1; 11:19:35.652 [planetinfo.record]: polar_land_color = 0.934, 0.923967, 0.927494, 1; 11:19:35.652 [planetinfo.record]: polar_sea_color = 0.94707, 0.908392, 0.898052, 1; 11:19:35.652 [planetinfo.record]: sea_color = 0.529297, 0.442831, 0.419716, 1; 11:19:35.652 [planetinfo.record]: rotation_speed = 0.003958 11:19:35.652 [planetinfo.record]: planet zpos = 691440.000000 11:19:35.652 [planetinfo.record]: sun_radius = 119322.178650 11:19:35.652 [planetinfo.record]: sun_vector = -0.341 -0.853 -0.396 11:19:35.652 [planetinfo.record]: sun_distance = 1325260 11:19:35.652 [planetinfo.record]: corona_flare = 0.005069 11:19:35.652 [planetinfo.record]: corona_hues = 0.890228 11:19:35.652 [planetinfo.record]: sun_color = 0.839914, 0.443504, 0.37172, 1 11:19:35.653 [planetinfo.record]: corona_shimmer = 0.588284 11:19:35.656 [planetinfo.record]: station_vector = -0.821 -0.534 0.200 11:19:35.656 [planetinfo.record]: station = coriolis 11:19:40.030 [PLANETINFO OVER]: Done 11:19:40.031 [PLANETINFO LOGGING]: 0 144 11:19:41.050 [planetinfo.record]: seed = 40 97 83 38 11:19:41.050 [planetinfo.record]: coordinates = 97 114 11:19:41.050 [planetinfo.record]: government = 5; 11:19:41.050 [planetinfo.record]: economy = 2; 11:19:41.050 [planetinfo.record]: techlevel = 9; 11:19:41.050 [planetinfo.record]: population = 44; 11:19:41.050 [planetinfo.record]: productivity = 25344; 11:19:41.050 [planetinfo.record]: name = "Biorle"; 11:19:41.050 [planetinfo.record]: inhabitant = "Human Colonial"; 11:19:41.050 [planetinfo.record]: inhabitants = "Human Colonials"; 11:19:41.050 [planetinfo.record]: description = "The world Biorle is a dull world."; 11:19:41.072 [planetinfo.record]: air_color = 0.587583, 0.547192, 0.876076, 1; 11:19:41.072 [planetinfo.record]: cloud_alpha = 1.000000; 11:19:41.072 [planetinfo.record]: cloud_color = 0.425534, 0.29818, 0.630859, 1; 11:19:41.072 [planetinfo.record]: cloud_fraction = 0.420000; 11:19:41.072 [planetinfo.record]: land_color = 0.611016, 0.66, 0.639335, 1; 11:19:41.072 [planetinfo.record]: land_fraction = 0.630000; 11:19:41.072 [planetinfo.record]: polar_cloud_color = 0.624429, 0.525526, 0.783887, 1; 11:19:41.072 [planetinfo.record]: polar_land_color = 0.91667, 0.934, 0.926689, 1; 11:19:41.072 [planetinfo.record]: polar_sea_color = 0.863085, 0.853702, 0.946094, 1; 11:19:41.072 [planetinfo.record]: sea_color = 0.349877, 0.328491, 0.539062, 1; 11:19:41.072 [planetinfo.record]: rotation_speed = 0.001321 11:19:41.072 [planetinfo.record]: planet zpos = 533880.000000 11:19:41.072 [planetinfo.record]: sun_radius = 108699.627686 11:19:41.072 [planetinfo.record]: sun_vector = 0.096 -0.954 0.284 11:19:41.072 [planetinfo.record]: sun_distance = 800820 11:19:41.073 [planetinfo.record]: corona_flare = 0.058958 11:19:41.073 [planetinfo.record]: corona_hues = 0.540535 11:19:41.073 [planetinfo.record]: sun_color = 0.577615, 0.61041, 0.688547, 1 11:19:41.073 [planetinfo.record]: corona_shimmer = 0.435623 11:19:41.073 [planetinfo.record]: station_vector = 0.887 -0.427 0.174 11:19:41.073 [planetinfo.record]: station = coriolis 11:19:46.013 [PLANETINFO OVER]: Done 11:19:46.014 [PLANETINFO LOGGING]: 0 145 11:19:47.019 [planetinfo.record]: seed = 216 160 93 27 11:19:47.019 [planetinfo.record]: coordinates = 160 129 11:19:47.019 [planetinfo.record]: government = 3; 11:19:47.019 [planetinfo.record]: economy = 1; 11:19:47.019 [planetinfo.record]: techlevel = 8; 11:19:47.019 [planetinfo.record]: population = 37; 11:19:47.019 [planetinfo.record]: productivity = 18648; 11:19:47.019 [planetinfo.record]: name = "Anisor"; 11:19:47.019 [planetinfo.record]: inhabitant = "Human Colonial"; 11:19:47.019 [planetinfo.record]: inhabitants = "Human Colonials"; 11:19:47.019 [planetinfo.record]: description = "This planet is very well known for its inhabitants’ ancient mating traditions and its inhabitants’ ancient loathing of casinos."; 11:19:47.043 [planetinfo.record]: air_color = 0.5579, 0.96286, 0.978838, 1; 11:19:47.044 [planetinfo.record]: cloud_alpha = 1.000000; 11:19:47.044 [planetinfo.record]: cloud_color = 0.85826, 0.939453, 0.289909, 1; 11:19:47.044 [planetinfo.record]: cloud_fraction = 0.520000; 11:19:47.044 [planetinfo.record]: land_color = 0.1474, 0.539062, 0.181058, 1; 11:19:47.044 [planetinfo.record]: land_fraction = 0.680000; 11:19:47.044 [planetinfo.record]: polar_cloud_color = 0.87291, 0.922754, 0.524005, 1; 11:19:47.045 [planetinfo.record]: polar_land_color = 0.774245, 0.946094, 0.789013, 1; 11:19:47.045 [planetinfo.record]: polar_sea_color = 0.941406, 0.936107, 0.892957, 1; 11:19:47.045 [planetinfo.record]: sea_color = 0.585938, 0.572745, 0.465317, 1; 11:19:47.045 [planetinfo.record]: rotation_speed = 0.004372 11:19:47.045 [planetinfo.record]: planet zpos = 579200.000000 11:19:47.045 [planetinfo.record]: sun_radius = 136124.726562 11:19:47.045 [planetinfo.record]: sun_vector = 0.031 0.479 -0.877 11:19:47.045 [planetinfo.record]: sun_distance = 1158400 11:19:47.045 [planetinfo.record]: corona_flare = 0.016626 11:19:47.046 [planetinfo.record]: corona_hues = 0.578423 11:19:47.046 [planetinfo.record]: sun_color = 0.548326, 0.632068, 0.687283, 1 11:19:47.046 [planetinfo.record]: corona_shimmer = 0.554244 11:19:47.046 [planetinfo.record]: station_vector = -0.927 0.360 0.107 11:19:47.046 [planetinfo.record]: station = coriolis 11:19:52.009 [PLANETINFO OVER]: Done 11:19:52.010 [PLANETINFO LOGGING]: 0 146 11:19:53.029 [planetinfo.record]: seed = 252 81 155 136 11:19:53.029 [planetinfo.record]: coordinates = 81 249 11:19:53.029 [planetinfo.record]: government = 7; 11:19:53.029 [planetinfo.record]: economy = 1; 11:19:53.029 [planetinfo.record]: techlevel = 11; 11:19:53.029 [planetinfo.record]: population = 53; 11:19:53.029 [planetinfo.record]: productivity = 41976; 11:19:53.029 [planetinfo.record]: name = "Usrarema"; 11:19:53.029 [planetinfo.record]: inhabitant = "Small Black Slimy Rodent"; 11:19:53.029 [planetinfo.record]: inhabitants = "Small Black Slimy Rodents"; 11:19:53.029 [planetinfo.record]: description = "This world is very notable for the Usraremaian edible poet."; 11:19:53.056 [planetinfo.record]: air_color = 0.692062, 0.590883, 0.868271, 1; 11:19:53.056 [planetinfo.record]: cloud_alpha = 1.000000; 11:19:53.056 [planetinfo.record]: cloud_color = 0.607422, 0.38913, 0.583546, 1; 11:19:53.056 [planetinfo.record]: cloud_fraction = 0.470000; 11:19:53.056 [planetinfo.record]: land_color = 0.169296, 0.598054, 0.619141, 1; 11:19:53.056 [planetinfo.record]: land_fraction = 0.500000; 11:19:53.056 [planetinfo.record]: polar_cloud_color = 0.77334, 0.59964, 0.754341, 1; 11:19:53.056 [planetinfo.record]: polar_land_color = 0.767691, 0.930099, 0.938086, 1; 11:19:53.056 [planetinfo.record]: polar_sea_color = 0.890531, 0.927734, 0.86966, 1; 11:19:53.056 [planetinfo.record]: sea_color = 0.606737, 0.722656, 0.54171, 1; 11:19:53.056 [planetinfo.record]: rotation_speed = 0.003082 11:19:53.056 [planetinfo.record]: planet zpos = 543950.000000 11:19:53.056 [planetinfo.record]: sun_radius = 144841.355896 11:19:53.056 [planetinfo.record]: sun_vector = -0.145 -0.772 -0.619 11:19:53.056 [planetinfo.record]: sun_distance = 1038450 11:19:53.056 [planetinfo.record]: corona_flare = 0.079408 11:19:53.056 [planetinfo.record]: corona_hues = 0.889702 11:19:53.057 [planetinfo.record]: sun_color = 0.745374, 0.629452, 0.522481, 1 11:19:53.057 [planetinfo.record]: corona_shimmer = 0.289054 11:19:53.057 [planetinfo.record]: station_vector = -0.204 -0.846 0.492 11:19:53.057 [planetinfo.record]: station = icosahedron 11:19:58.597 [PLANETINFO OVER]: Done 11:19:58.598 [PLANETINFO LOGGING]: 0 147 11:19:59.603 [planetinfo.record]: seed = 52 11 205 141 11:19:59.603 [planetinfo.record]: coordinates = 11 174 11:19:59.603 [planetinfo.record]: government = 6; 11:19:59.603 [planetinfo.record]: economy = 6; 11:19:59.603 [planetinfo.record]: techlevel = 7; 11:19:59.603 [planetinfo.record]: population = 41; 11:19:59.603 [planetinfo.record]: productivity = 13120; 11:19:59.603 [planetinfo.record]: name = "Diso"; 11:19:59.603 [planetinfo.record]: inhabitant = "Black Furry Feline"; 11:19:59.603 [planetinfo.record]: inhabitants = "Black Furry Felines"; 11:19:59.603 [planetinfo.record]: description = "This planet is mildly noted for its ancient Ouza tulip plantations but ravaged by frequent earthquakes."; 11:19:59.630 [planetinfo.record]: air_color = 0.460732, 0.472607, 0.926807, 1; 11:19:59.632 [planetinfo.record]: cloud_alpha = 1.000000; 11:19:59.632 [planetinfo.record]: cloud_color = 0.0703659, 0.114918, 0.783203, 1; 11:19:59.632 [planetinfo.record]: cloud_fraction = 0.130000; 11:19:59.632 [planetinfo.record]: land_color = 0.66, 0.146953, 0.471616, 1; 11:19:59.632 [planetinfo.record]: land_fraction = 0.330000; 11:19:59.633 [planetinfo.record]: polar_cloud_color = 0.367532, 0.397839, 0.852441, 1; 11:19:59.633 [planetinfo.record]: polar_land_color = 0.934, 0.75249, 0.867352, 1; 11:19:59.633 [planetinfo.record]: polar_sea_color = 0.944141, 0.919045, 0.894721, 1; 11:19:59.633 [planetinfo.record]: sea_color = 0.558594, 0.499202, 0.441638, 1; 11:19:59.633 [planetinfo.record]: rotation_speed = 0.001121 11:19:59.633 [planetinfo.record]: planet zpos = 677050.000000 11:19:59.633 [planetinfo.record]: sun_radius = 161528.365326 11:19:59.633 [planetinfo.record]: sun_vector = -0.931 -0.346 0.116 11:19:59.633 [planetinfo.record]: sun_distance = 1169450 11:19:59.634 [planetinfo.record]: corona_flare = 0.017542 11:19:59.634 [planetinfo.record]: corona_hues = 0.822113 11:19:59.634 [planetinfo.record]: sun_color = 0.815241, 0.516776, 0.463287, 1 11:19:59.634 [planetinfo.record]: corona_shimmer = 0.461807 11:19:59.636 [planetinfo.record]: station_vector = -0.037 -0.842 0.538 11:19:59.636 [planetinfo.record]: station = coriolis 11:20:05.134 [PLANETINFO OVER]: Done 11:20:05.135 [PLANETINFO LOGGING]: 0 148 11:20:06.138 [planetinfo.record]: seed = 0 182 51 222 11:20:06.138 [planetinfo.record]: coordinates = 182 224 11:20:06.138 [planetinfo.record]: government = 0; 11:20:06.138 [planetinfo.record]: economy = 2; 11:20:06.138 [planetinfo.record]: techlevel = 7; 11:20:06.138 [planetinfo.record]: population = 31; 11:20:06.138 [planetinfo.record]: productivity = 7936; 11:20:06.138 [planetinfo.record]: name = "Riraes"; 11:20:06.138 [planetinfo.record]: inhabitant = "Human Colonial"; 11:20:06.138 [planetinfo.record]: inhabitants = "Human Colonials"; 11:20:06.138 [planetinfo.record]: description = "The world Riraes is fabled for its weird rock formations and its pink oceans."; 11:20:06.163 [planetinfo.record]: air_color = 0.582105, 0.928107, 0.846726, 1; 11:20:06.163 [planetinfo.record]: cloud_alpha = 1.000000; 11:20:06.163 [planetinfo.record]: cloud_color = 0.787109, 0.502777, 0.378181, 1; 11:20:06.163 [planetinfo.record]: cloud_fraction = 0.410000; 11:20:06.163 [planetinfo.record]: land_color = 0.66, 0.440295, 0.381563, 1; 11:20:06.163 [planetinfo.record]: land_fraction = 0.280000; 11:20:06.163 [planetinfo.record]: polar_cloud_color = 0.854199, 0.661344, 0.576835, 1; 11:20:06.163 [planetinfo.record]: polar_land_color = 0.934, 0.856271, 0.835492, 1; 11:20:06.163 [planetinfo.record]: polar_sea_color = 0.938281, 0.856731, 0.91216, 1; 11:20:06.164 [planetinfo.record]: sea_color = 0.617188, 0.402618, 0.548458, 1; 11:20:06.164 [planetinfo.record]: rotation_speed = 0.001106 11:20:06.164 [planetinfo.record]: planet zpos = 789840.000000 11:20:06.164 [planetinfo.record]: sun_radius = 175188.904724 11:20:06.164 [planetinfo.record]: sun_vector = 0.492 -0.331 -0.805 11:20:06.164 [planetinfo.record]: sun_distance = 1184760 11:20:06.164 [planetinfo.record]: corona_flare = 0.009143 11:20:06.170 [planetinfo.record]: corona_hues = 0.593636 11:20:06.170 [planetinfo.record]: sun_color = 0.782501, 0.727115, 0.715634, 1 11:20:06.170 [planetinfo.record]: corona_shimmer = 0.367800 11:20:06.170 [planetinfo.record]: station_vector = 0.859 -0.511 0.020 11:20:06.171 [planetinfo.record]: station = coriolis 11:20:10.991 [PLANETINFO OVER]: Done 11:20:10.992 [PLANETINFO LOGGING]: 0 149 11:20:11.999 [planetinfo.record]: seed = 0 92 205 217 11:20:11.999 [planetinfo.record]: coordinates = 92 9 11:20:11.999 [planetinfo.record]: government = 0; 11:20:11.999 [planetinfo.record]: economy = 3; 11:20:11.999 [planetinfo.record]: techlevel = 4; 11:20:11.999 [planetinfo.record]: population = 20; 11:20:11.999 [planetinfo.record]: productivity = 4480; 11:20:11.999 [planetinfo.record]: name = "Orrira"; 11:20:11.999 [planetinfo.record]: inhabitant = "Furry Feline"; 11:20:11.999 [planetinfo.record]: inhabitants = "Furry Felines"; 11:20:11.999 [planetinfo.record]: description = "The planet Orrira is cursed by killer edible talking treeoids."; 11:20:12.006 [planetinfo.record]: air_color = 0.705003, 0.465262, 0.979488, 1; 11:20:12.007 [planetinfo.record]: cloud_alpha = 1.000000; 11:20:12.007 [planetinfo.record]: cloud_color = 0.941406, 0.0220642, 0.560741, 1; 11:20:12.007 [planetinfo.record]: cloud_fraction = 0.490000; 11:20:12.007 [planetinfo.record]: land_color = 0.66, 0.234891, 0.213984, 1; 11:20:12.007 [planetinfo.record]: land_fraction = 0.290000; 11:20:12.007 [planetinfo.record]: polar_cloud_color = 0.923633, 0.359892, 0.690209, 1; 11:20:12.007 [planetinfo.record]: polar_land_color = 0.934, 0.783602, 0.776205, 1; 11:20:12.007 [planetinfo.record]: polar_sea_color = 0.880392, 0.929883, 0.888125, 1; 11:20:12.007 [planetinfo.record]: sea_color = 0.551899, 0.701172, 0.575223, 1; 11:20:12.007 [planetinfo.record]: rotation_speed = 0.004137 11:20:12.007 [planetinfo.record]: planet zpos = 781800.000000 11:20:12.007 [planetinfo.record]: sun_radius = 148341.905518 11:20:12.007 [planetinfo.record]: sun_vector = 0.163 0.922 0.350 11:20:12.007 [planetinfo.record]: sun_distance = 1042400 11:20:12.008 [planetinfo.record]: corona_flare = 0.047998 11:20:12.008 [planetinfo.record]: corona_hues = 0.924927 11:20:12.008 [planetinfo.record]: sun_color = 0.709161, 0.621117, 0.439355, 1 11:20:12.008 [planetinfo.record]: corona_shimmer = 0.268610 11:20:12.008 [planetinfo.record]: station_vector = 0.855 -0.516 0.056 11:20:12.008 [planetinfo.record]: station = coriolis 11:20:16.582 [PLANETINFO OVER]: Done 11:20:16.583 [PLANETINFO LOGGING]: 0 150 11:20:17.590 [planetinfo.record]: seed = 52 141 155 64 11:20:17.590 [planetinfo.record]: coordinates = 141 116 11:20:17.590 [planetinfo.record]: government = 6; 11:20:17.591 [planetinfo.record]: economy = 4; 11:20:17.591 [planetinfo.record]: techlevel = 7; 11:20:17.591 [planetinfo.record]: population = 39; 11:20:17.591 [planetinfo.record]: productivity = 18720; 11:20:17.591 [planetinfo.record]: name = "Xeer"; 11:20:17.591 [planetinfo.record]: inhabitant = "Large Yellow Bug-Eyed Frog"; 11:20:17.591 [planetinfo.record]: inhabitants = "Large Yellow Bug-Eyed Frogs"; 11:20:17.591 [planetinfo.record]: description = "This world is very well known for Xeerian wolf meat and its fabulous cuisine."; 11:20:17.603 [planetinfo.record]: air_color = 0.606792, 0.811327, 0.996398, 1; 11:20:17.603 [planetinfo.record]: cloud_alpha = 1.000000; 11:20:17.603 [planetinfo.record]: cloud_color = 0.422455, 0.992188, 0.671713, 1; 11:20:17.603 [planetinfo.record]: cloud_fraction = 0.230000; 11:20:17.603 [planetinfo.record]: land_color = 0.605469, 0.385513, 0.591722, 1; 11:20:17.603 [planetinfo.record]: land_fraction = 0.380000; 11:20:17.603 [planetinfo.record]: polar_cloud_color = 0.606804, 0.946484, 0.755414, 1; 11:20:17.603 [planetinfo.record]: polar_land_color = 0.939453, 0.854132, 0.934121, 1; 11:20:17.603 [planetinfo.record]: polar_sea_color = 0.927454, 0.948047, 0.745291, 1; 11:20:17.603 [planetinfo.record]: sea_color = 0.474393, 0.519531, 0.0750885, 1; 11:20:17.604 [planetinfo.record]: rotation_speed = 0.000246 11:20:17.604 [planetinfo.record]: planet zpos = 413980.000000 11:20:17.604 [planetinfo.record]: sun_radius = 87958.296814 11:20:17.604 [planetinfo.record]: sun_vector = -0.107 0.720 0.685 11:20:17.604 [planetinfo.record]: sun_distance = 561830 11:20:17.604 [planetinfo.record]: corona_flare = 0.055379 11:20:17.604 [planetinfo.record]: corona_hues = 0.659752 11:20:17.604 [planetinfo.record]: sun_color = 0.513528, 0.530388, 0.810178, 1 11:20:17.604 [planetinfo.record]: corona_shimmer = 0.359874 11:20:17.604 [planetinfo.record]: station_vector = -0.260 0.592 0.763 11:20:17.604 [planetinfo.record]: station = coriolis 11:20:22.604 [PLANETINFO OVER]: Done 11:20:22.605 [PLANETINFO LOGGING]: 0 151 11:20:23.608 [planetinfo.record]: seed = 60 147 221 229 11:20:23.608 [planetinfo.record]: coordinates = 147 16 11:20:23.608 [planetinfo.record]: government = 7; 11:20:23.608 [planetinfo.record]: economy = 0; 11:20:23.608 [planetinfo.record]: techlevel = 14; 11:20:23.608 [planetinfo.record]: population = 64; 11:20:23.608 [planetinfo.record]: productivity = 56320; 11:20:23.608 [planetinfo.record]: name = "Ceesxe"; 11:20:23.608 [planetinfo.record]: inhabitant = "Fierce Bony Bird"; 11:20:23.608 [planetinfo.record]: inhabitants = "Fierce Bony Birds"; 11:20:23.608 [planetinfo.record]: description = "The world Ceesxe is most well known for its vast rain forests."; 11:20:23.623 [planetinfo.record]: air_color = 0.63164, 0.885832, 0.828029, 1; 11:20:23.623 [planetinfo.record]: cloud_alpha = 1.000000; 11:20:23.623 [planetinfo.record]: cloud_color = 0.660156, 0.544073, 0.487381, 1; 11:20:23.623 [planetinfo.record]: cloud_fraction = 0.390000; 11:20:23.623 [planetinfo.record]: land_color = 0.635045, 0.66, 0.203672, 1; 11:20:23.623 [planetinfo.record]: land_fraction = 0.490000; 11:20:23.623 [planetinfo.record]: polar_cloud_color = 0.79707, 0.709471, 0.66669, 1; 11:20:23.623 [planetinfo.record]: polar_land_color = 0.925171, 0.934, 0.772557, 1; 11:20:23.623 [planetinfo.record]: polar_sea_color = 0.934963, 0.938867, 0.888898, 1; 11:20:23.623 [planetinfo.record]: sea_color = 0.60116, 0.611328, 0.481182, 1; 11:20:23.623 [planetinfo.record]: rotation_speed = 0.003369 11:20:23.623 [planetinfo.record]: planet zpos = 466730.000000 11:20:23.623 [planetinfo.record]: sun_radius = 135502.007446 11:20:23.623 [planetinfo.record]: sun_vector = 0.177 0.009 -0.984 11:20:23.623 [planetinfo.record]: sun_distance = 806170 11:20:23.623 [planetinfo.record]: corona_flare = 0.044345 11:20:23.624 [planetinfo.record]: corona_hues = 0.806778 11:20:23.624 [planetinfo.record]: sun_color = 0.169981, 0.378625, 0.678095, 1 11:20:23.624 [planetinfo.record]: corona_shimmer = 0.397135 11:20:23.624 [planetinfo.record]: station_vector = 0.620 -0.706 0.343 11:20:23.624 [planetinfo.record]: station = dodecahedron 11:20:28.233 [PLANETINFO OVER]: Done 11:20:28.233 [PLANETINFO LOGGING]: 0 152 11:20:29.236 [planetinfo.record]: seed = 152 113 83 253 11:20:29.236 [planetinfo.record]: coordinates = 113 2 11:20:29.236 [planetinfo.record]: government = 3; 11:20:29.236 [planetinfo.record]: economy = 2; 11:20:29.237 [planetinfo.record]: techlevel = 8; 11:20:29.237 [planetinfo.record]: population = 38; 11:20:29.237 [planetinfo.record]: productivity = 17024; 11:20:29.237 [planetinfo.record]: name = "Isatre"; 11:20:29.237 [planetinfo.record]: inhabitant = "Human Colonial"; 11:20:29.237 [planetinfo.record]: inhabitants = "Human Colonials"; 11:20:29.237 [planetinfo.record]: description = "The world Isatre is a boring planet."; 11:20:29.239 [planetinfo.record]: air_color = 0.505842, 0.632339, 0.84876, 1; 11:20:29.240 [planetinfo.record]: cloud_alpha = 1.000000; 11:20:29.240 [planetinfo.record]: cloud_color = 0.212242, 0.548828, 0.517273, 1; 11:20:29.240 [planetinfo.record]: cloud_fraction = 0.440000; 11:20:29.240 [planetinfo.record]: land_color = 0.448594, 0.66, 0.640181, 1; 11:20:29.240 [planetinfo.record]: land_fraction = 0.380000; 11:20:29.240 [planetinfo.record]: polar_cloud_color = 0.460657, 0.746973, 0.720131, 1; 11:20:29.240 [planetinfo.record]: polar_land_color = 0.859207, 0.934, 0.926988, 1; 11:20:29.240 [planetinfo.record]: polar_sea_color = 0.944336, 0.895528, 0.890479, 1; 11:20:29.240 [planetinfo.record]: sea_color = 0.556641, 0.441562, 0.429657, 1; 11:20:29.240 [planetinfo.record]: rotation_speed = 0.000796 11:20:29.241 [planetinfo.record]: planet zpos = 750840.000000 11:20:29.241 [planetinfo.record]: sun_radius = 208929.150543 11:20:29.241 [planetinfo.record]: sun_vector = -0.618 0.184 -0.764 11:20:29.241 [planetinfo.record]: sun_distance = 1063690 11:20:29.241 [planetinfo.record]: corona_flare = 0.057458 11:20:29.241 [planetinfo.record]: corona_hues = 0.886887 11:20:29.241 [planetinfo.record]: sun_color = 0.784006, 0.675855, 0.581359, 1 11:20:29.241 [planetinfo.record]: corona_shimmer = 0.335449 11:20:29.242 [planetinfo.record]: station_vector = -0.640 0.743 0.195 11:20:29.242 [planetinfo.record]: station = coriolis 11:20:34.246 [PLANETINFO OVER]: Done 11:20:34.247 [PLANETINFO LOGGING]: 0 153 11:20:35.265 [planetinfo.record]: seed = 232 78 125 160 11:20:35.265 [planetinfo.record]: coordinates = 78 224 11:20:35.265 [planetinfo.record]: government = 5; 11:20:35.265 [planetinfo.record]: economy = 0; 11:20:35.265 [planetinfo.record]: techlevel = 12; 11:20:35.265 [planetinfo.record]: population = 54; 11:20:35.265 [planetinfo.record]: productivity = 38880; 11:20:35.265 [planetinfo.record]: name = "Aona"; 11:20:35.265 [planetinfo.record]: inhabitant = "Human Colonial"; 11:20:35.266 [planetinfo.record]: inhabitants = "Human Colonials"; 11:20:35.266 [planetinfo.record]: description = "This world is very well known for Aonaian lethal brandy and its great volcanoes."; 11:20:35.283 [planetinfo.record]: air_color = 0.494686, 0.573666, 0.879979, 1; 11:20:35.283 [planetinfo.record]: cloud_alpha = 1.000000; 11:20:35.283 [planetinfo.record]: cloud_color = 0.183235, 0.459559, 0.642578, 1; 11:20:35.283 [planetinfo.record]: cloud_fraction = 0.200000; 11:20:35.283 [planetinfo.record]: land_color = 0.293906, 0.66, 0.488394, 1; 11:20:35.283 [planetinfo.record]: land_fraction = 0.710000; 11:20:35.283 [planetinfo.record]: polar_cloud_color = 0.436581, 0.648679, 0.78916, 1; 11:20:35.284 [planetinfo.record]: polar_land_color = 0.80448, 0.934, 0.873288, 1; 11:20:35.284 [planetinfo.record]: polar_sea_color = 0.916386, 0.937109, 0.888881, 1; 11:20:35.284 [planetinfo.record]: sea_color = 0.573276, 0.628906, 0.49944, 1; 11:20:35.284 [planetinfo.record]: rotation_speed = 0.003795 11:20:35.284 [planetinfo.record]: planet zpos = 434100.000000 11:20:35.284 [planetinfo.record]: sun_radius = 50399.917908 11:20:35.284 [planetinfo.record]: sun_vector = 0.176 -0.163 -0.971 11:20:35.284 [planetinfo.record]: sun_distance = 491980 11:20:35.284 [planetinfo.record]: corona_flare = 0.068925 11:20:35.284 [planetinfo.record]: corona_hues = 0.853844 11:20:35.284 [planetinfo.record]: sun_color = 0.739127, 0.732931, 0.350926, 1 11:20:35.284 [planetinfo.record]: corona_shimmer = 0.361140 11:20:35.284 [planetinfo.record]: station_vector = -0.822 0.373 0.430 11:20:35.284 [planetinfo.record]: station = icosahedron 11:20:40.276 [PLANETINFO OVER]: Done 11:20:40.277 [PLANETINFO LOGGING]: 0 154 11:20:41.280 [planetinfo.record]: seed = 44 47 219 189 11:20:41.280 [planetinfo.record]: coordinates = 47 191 11:20:41.280 [planetinfo.record]: government = 5; 11:20:41.280 [planetinfo.record]: economy = 7; 11:20:41.280 [planetinfo.record]: techlevel = 6; 11:20:41.280 [planetinfo.record]: population = 37; 11:20:41.280 [planetinfo.record]: productivity = 7992; 11:20:41.280 [planetinfo.record]: name = "Isinor"; 11:20:41.280 [planetinfo.record]: inhabitant = "Harmless Slimy Frog"; 11:20:41.280 [planetinfo.record]: inhabitants = "Harmless Slimy Frogs"; 11:20:41.280 [planetinfo.record]: description = "This world is very fabled for its unusual oceans."; 11:20:41.282 [planetinfo.record]: air_color = 0.766834, 0.756659, 0.950221, 1; 11:20:41.282 [planetinfo.record]: cloud_alpha = 1.000000; 11:20:41.282 [planetinfo.record]: cloud_color = 0.845233, 0.843513, 0.853516, 1; 11:20:41.282 [planetinfo.record]: cloud_fraction = 0.220000; 11:20:41.282 [planetinfo.record]: land_color = 0.337473, 0.0438281, 0.66, 1; 11:20:41.282 [planetinfo.record]: land_fraction = 0.690000; 11:20:41.282 [planetinfo.record]: polar_cloud_color = 0.87872, 0.877607, 0.884082, 1; 11:20:41.282 [planetinfo.record]: polar_land_color = 0.819894, 0.716006, 0.934, 1; 11:20:41.282 [planetinfo.record]: polar_sea_color = 0.935742, 0.896334, 0.880914, 1; 11:20:41.282 [planetinfo.record]: sea_color = 0.642578, 0.534331, 0.491974, 1; 11:20:41.282 [planetinfo.record]: rotation_speed = 0.002476 11:20:41.282 [planetinfo.record]: planet zpos = 681010.000000 11:20:41.282 [planetinfo.record]: sun_radius = 137618.818512 11:20:41.282 [planetinfo.record]: sun_vector = -0.387 -0.705 0.594 11:20:41.282 [planetinfo.record]: sun_distance = 1176290 11:20:41.282 [planetinfo.record]: corona_flare = 0.053661 11:20:41.282 [planetinfo.record]: corona_hues = 0.810669 11:20:41.282 [planetinfo.record]: sun_color = 0.588983, 0.806236, 0.813354, 1 11:20:41.283 [planetinfo.record]: corona_shimmer = 0.368159 11:20:41.283 [planetinfo.record]: station_vector = -0.587 0.798 0.137 11:20:41.283 [planetinfo.record]: station = coriolis 11:20:46.352 [PLANETINFO OVER]: Done 11:20:46.353 [PLANETINFO LOGGING]: 0 155 11:20:47.373 [planetinfo.record]: seed = 4 3 45 72 11:20:47.373 [planetinfo.record]: coordinates = 3 153 11:20:47.373 [planetinfo.record]: government = 0; 11:20:47.374 [planetinfo.record]: economy = 3; 11:20:47.374 [planetinfo.record]: techlevel = 7; 11:20:47.374 [planetinfo.record]: population = 32; 11:20:47.374 [planetinfo.record]: productivity = 7168; 11:20:47.374 [planetinfo.record]: name = "Uszaa"; 11:20:47.374 [planetinfo.record]: inhabitant = "Human Colonial"; 11:20:47.374 [planetinfo.record]: inhabitants = "Human Colonials"; 11:20:47.374 [planetinfo.record]: description = "The planet Uszaa is reasonably noted for its inhabitants’ eccentric love for tourists and the Uszaaian tree grub."; 11:20:47.387 [planetinfo.record]: air_color = 0.643013, 0.555998, 0.886482, 1; 11:20:47.388 [planetinfo.record]: cloud_alpha = 1.000000; 11:20:47.388 [planetinfo.record]: cloud_color = 0.594925, 0.318123, 0.662109, 1; 11:20:47.388 [planetinfo.record]: cloud_fraction = 0.380000; 11:20:47.388 [planetinfo.record]: land_color = 0.588152, 0.335327, 0.640625, 1; 11:20:47.388 [planetinfo.record]: land_fraction = 0.510000; 11:20:47.388 [planetinfo.record]: polar_cloud_color = 0.747344, 0.538849, 0.797949, 1; 11:20:47.388 [planetinfo.record]: polar_land_color = 0.916772, 0.824429, 0.935938, 1; 11:20:47.388 [planetinfo.record]: polar_sea_color = 0.886991, 0.932227, 0.883157, 1; 11:20:47.389 [planetinfo.record]: sea_color = 0.546188, 0.677734, 0.53504, 1; 11:20:47.389 [planetinfo.record]: rotation_speed = 0.000463 11:20:47.389 [planetinfo.record]: planet zpos = 535370.000000 11:20:47.389 [planetinfo.record]: sun_radius = 115243.692017 11:20:47.389 [planetinfo.record]: sun_vector = -0.790 -0.459 -0.406 11:20:47.389 [planetinfo.record]: sun_distance = 1022070 11:20:47.389 [planetinfo.record]: corona_flare = 0.044383 11:20:47.389 [planetinfo.record]: corona_hues = 0.988953 11:20:47.389 [planetinfo.record]: sun_color = 0.257044, 0.752965, 0.777399, 1 11:20:47.390 [planetinfo.record]: corona_shimmer = 0.548501 11:20:47.390 [planetinfo.record]: station_vector = -0.180 -0.391 0.903 11:20:47.390 [planetinfo.record]: station = coriolis 11:20:52.722 [PLANETINFO OVER]: Done 11:20:52.723 [PLANETINFO LOGGING]: 0 156 11:20:53.725 [planetinfo.record]: seed = 240 91 179 111 11:20:53.725 [planetinfo.record]: coordinates = 91 47 11:20:53.725 [planetinfo.record]: government = 6; 11:20:53.725 [planetinfo.record]: economy = 7; 11:20:53.725 [planetinfo.record]: techlevel = 6; 11:20:53.726 [planetinfo.record]: population = 38; 11:20:53.726 [planetinfo.record]: productivity = 9120; 11:20:53.726 [planetinfo.record]: name = "Aanbiat"; 11:20:53.726 [planetinfo.record]: inhabitant = "Blue Fat Insect"; 11:20:53.726 [planetinfo.record]: inhabitants = "Blue Fat Insects"; 11:20:53.726 [planetinfo.record]: description = "This planet is fabled for its ancient Aanbiatian Alinet banana plantations."; 11:20:53.738 [planetinfo.record]: air_color = 0.547035, 0.948944, 0.993797, 1; 11:20:53.738 [planetinfo.record]: cloud_alpha = 1.000000; 11:20:53.738 [planetinfo.record]: cloud_color = 0.753662, 0.984375, 0.246094, 1; 11:20:53.738 [planetinfo.record]: cloud_fraction = 0.220000; 11:20:53.739 [planetinfo.record]: land_color = 0.625, 0.463867, 0.47142, 1; 11:20:53.739 [planetinfo.record]: land_fraction = 0.350000; 11:20:53.739 [planetinfo.record]: polar_cloud_color = 0.804839, 0.942969, 0.500952, 1; 11:20:53.739 [planetinfo.record]: polar_land_color = 0.9375, 0.877075, 0.879908, 1; 11:20:53.739 [planetinfo.record]: polar_sea_color = 0.932227, 0.875769, 0.858486, 1; 11:20:53.739 [planetinfo.record]: sea_color = 0.677734, 0.513554, 0.463295, 1; 11:20:53.739 [planetinfo.record]: rotation_speed = 0.000416 11:20:53.739 [planetinfo.record]: planet zpos = 809640.000000 11:20:53.739 [planetinfo.record]: sun_radius = 159421.759644 11:20:53.739 [planetinfo.record]: sun_vector = -0.119 -0.990 -0.077 11:20:53.739 [planetinfo.record]: sun_distance = 1551810 11:20:53.739 [planetinfo.record]: corona_flare = 0.004817 11:20:53.739 [planetinfo.record]: corona_hues = 0.927780 11:20:53.739 [planetinfo.record]: sun_color = 0.204001, 0.575332, 0.680005, 1 11:20:53.739 [planetinfo.record]: corona_shimmer = 0.345166 11:20:53.739 [planetinfo.record]: station_vector = 0.061 0.811 0.582 11:20:53.739 [planetinfo.record]: station = coriolis 11:20:58.495 [PLANETINFO OVER]: Done 11:20:58.495 [PLANETINFO LOGGING]: 0 157 11:20:59.501 [planetinfo.record]: seed = 144 49 109 243 11:20:59.501 [planetinfo.record]: coordinates = 49 198 11:20:59.501 [planetinfo.record]: government = 2; 11:20:59.501 [planetinfo.record]: economy = 6; 11:20:59.501 [planetinfo.record]: techlevel = 3; 11:20:59.501 [planetinfo.record]: population = 21; 11:20:59.501 [planetinfo.record]: productivity = 4032; 11:20:59.501 [planetinfo.record]: name = "Bemaera"; 11:20:59.501 [planetinfo.record]: inhabitant = "Human Colonial"; 11:20:59.501 [planetinfo.record]: inhabitants = "Human Colonials"; 11:20:59.501 [planetinfo.record]: description = "Bemaera is most noted for the Bemaeraian deadly Noseoid and the Bemaeraian evil Noseoid."; 11:20:59.519 [planetinfo.record]: air_color = 0.479722, 0.596699, 0.88193, 1; 11:20:59.519 [planetinfo.record]: cloud_alpha = 1.000000; 11:20:59.519 [planetinfo.record]: cloud_color = 0.149445, 0.578267, 0.648438, 1; 11:20:59.519 [planetinfo.record]: cloud_fraction = 0.370000; 11:20:59.519 [planetinfo.record]: land_color = 0.129089, 0.550781, 0.135678, 1; 11:20:59.520 [planetinfo.record]: land_fraction = 0.600000; 11:20:59.520 [planetinfo.record]: polar_cloud_color = 0.410977, 0.738244, 0.791797, 1; 11:20:59.520 [planetinfo.record]: polar_land_color = 0.764058, 0.944922, 0.766884, 1; 11:20:59.520 [planetinfo.record]: polar_sea_color = 0.888327, 0.932422, 0.883342, 1; 11:20:59.520 [planetinfo.record]: sea_color = 0.547948, 0.675781, 0.533498, 1; 11:20:59.520 [planetinfo.record]: rotation_speed = 0.003516 11:20:59.520 [planetinfo.record]: planet zpos = 435960.000000 11:20:59.520 [planetinfo.record]: sun_radius = 101456.358948 11:20:59.520 [planetinfo.record]: sun_vector = -0.113 0.790 0.603 11:20:59.520 [planetinfo.record]: sun_distance = 690270 11:20:59.520 [planetinfo.record]: corona_flare = 0.026566 11:20:59.520 [planetinfo.record]: corona_hues = 0.632622 11:20:59.520 [planetinfo.record]: sun_color = 0.645796, 0.689013, 0.735315, 1 11:20:59.520 [planetinfo.record]: corona_shimmer = 0.528371 11:20:59.520 [planetinfo.record]: station_vector = 0.473 -0.733 0.489 11:20:59.520 [planetinfo.record]: station = coriolis 11:21:05.453 [PLANETINFO OVER]: Done 11:21:05.455 [PLANETINFO LOGGING]: 0 158 11:21:06.459 [planetinfo.record]: seed = 228 239 91 236 11:21:06.459 [planetinfo.record]: coordinates = 239 16 11:21:06.459 [planetinfo.record]: government = 4; 11:21:06.459 [planetinfo.record]: economy = 0; 11:21:06.459 [planetinfo.record]: techlevel = 12; 11:21:06.459 [planetinfo.record]: population = 53; 11:21:06.459 [planetinfo.record]: productivity = 33920; 11:21:06.460 [planetinfo.record]: name = "Inines"; 11:21:06.460 [planetinfo.record]: inhabitant = "Human Colonial"; 11:21:06.460 [planetinfo.record]: inhabitants = "Human Colonials"; 11:21:06.460 [planetinfo.record]: description = "This world is a tedious place."; 11:21:06.477 [planetinfo.record]: air_color = 0.773256, 0.580437, 0.906645, 1; 11:21:06.477 [planetinfo.record]: cloud_alpha = 1.000000; 11:21:06.477 [planetinfo.record]: cloud_color = 0.722656, 0.375443, 0.448683, 1; 11:21:06.477 [planetinfo.record]: cloud_fraction = 0.510000; 11:21:06.477 [planetinfo.record]: land_color = 0.207049, 0.515625, 0.143005, 1; 11:21:06.477 [planetinfo.record]: land_fraction = 0.520000; 11:21:06.477 [planetinfo.record]: polar_cloud_color = 0.825195, 0.577395, 0.629665, 1; 11:21:06.478 [planetinfo.record]: polar_land_color = 0.806539, 0.948438, 0.777089, 1; 11:21:06.478 [planetinfo.record]: polar_sea_color = 0.921094, 0.917369, 0.825746, 1; 11:21:06.478 [planetinfo.record]: sea_color = 0.789062, 0.7763, 0.462341, 1; 11:21:06.478 [planetinfo.record]: rotation_speed = 0.004714 11:21:06.478 [planetinfo.record]: planet zpos = 673970.000000 11:21:06.478 [planetinfo.record]: sun_radius = 159354.728699 11:21:06.478 [planetinfo.record]: sun_vector = -0.039 -0.793 0.608 11:21:06.478 [planetinfo.record]: sun_distance = 1225400 11:21:06.478 [planetinfo.record]: corona_flare = 0.094402 11:21:06.478 [planetinfo.record]: corona_hues = 0.718750 11:21:06.478 [planetinfo.record]: sun_color = 0.587018, 0.605884, 0.671478, 1 11:21:06.479 [planetinfo.record]: corona_shimmer = 0.459470 11:21:06.479 [planetinfo.record]: station_vector = -0.681 0.731 0.031 11:21:06.479 [planetinfo.record]: station = icosahedron 11:21:11.382 [PLANETINFO OVER]: Done 11:21:11.383 [PLANETINFO LOGGING]: 0 159 11:21:12.392 [planetinfo.record]: seed = 140 162 189 88 11:21:12.392 [planetinfo.record]: coordinates = 162 201 11:21:12.392 [planetinfo.record]: government = 1; 11:21:12.392 [planetinfo.record]: economy = 3; 11:21:12.392 [planetinfo.record]: techlevel = 7; 11:21:12.392 [planetinfo.record]: population = 33; 11:21:12.393 [planetinfo.record]: productivity = 9240; 11:21:12.393 [planetinfo.record]: name = "Edzaon"; 11:21:12.393 [planetinfo.record]: inhabitant = "Yellow Bony Lobster"; 11:21:12.393 [planetinfo.record]: inhabitants = "Yellow Bony Lobsters"; 11:21:12.393 [planetinfo.record]: description = "This world is most notable for Edzaonian lethal water but plagued by occasional solar activity."; 11:21:12.412 [planetinfo.record]: air_color = 0.42823, 0.819063, 0.848109, 1; 11:21:12.412 [planetinfo.record]: cloud_alpha = 1.000000; 11:21:12.412 [planetinfo.record]: cloud_color = 0.440798, 0.546875, 0.0619507, 1; 11:21:12.412 [planetinfo.record]: cloud_fraction = 0.260000; 11:21:12.412 [planetinfo.record]: land_color = 0.360938, 0.589907, 0.66, 1; 11:21:12.412 [planetinfo.record]: land_fraction = 0.340000; 11:21:12.412 [planetinfo.record]: polar_cloud_color = 0.655644, 0.746094, 0.332609, 1; 11:21:12.412 [planetinfo.record]: polar_land_color = 0.828195, 0.909202, 0.934, 1; 11:21:12.412 [planetinfo.record]: polar_sea_color = 0.944727, 0.902299, 0.892508, 1; 11:21:12.412 [planetinfo.record]: sea_color = 0.552734, 0.453442, 0.430528, 1; 11:21:12.412 [planetinfo.record]: rotation_speed = 0.002682 11:21:12.412 [planetinfo.record]: planet zpos = 653380.000000 11:21:12.412 [planetinfo.record]: sun_radius = 124393.806763 11:21:12.412 [planetinfo.record]: sun_vector = -0.013 0.663 0.748 11:21:12.412 [planetinfo.record]: sun_distance = 1105720 11:21:12.412 [planetinfo.record]: corona_flare = 0.042062 11:21:12.412 [planetinfo.record]: corona_hues = 0.562103 11:21:12.413 [planetinfo.record]: sun_color = 0.427794, 0.580826, 0.747125, 1 11:21:12.413 [planetinfo.record]: corona_shimmer = 0.496711 11:21:12.413 [planetinfo.record]: station_vector = -0.783 -0.622 0.010 11:21:12.413 [planetinfo.record]: station = coriolis 11:21:17.746 [PLANETINFO OVER]: Done 11:21:17.747 [PLANETINFO LOGGING]: 0 160 11:21:18.750 [planetinfo.record]: seed = 8 221 83 97 11:21:18.750 [planetinfo.record]: coordinates = 221 191 11:21:18.750 [planetinfo.record]: government = 1; 11:21:18.750 [planetinfo.record]: economy = 7; 11:21:18.750 [planetinfo.record]: techlevel = 2; 11:21:18.750 [planetinfo.record]: population = 17; 11:21:18.750 [planetinfo.record]: productivity = 2040; 11:21:18.750 [planetinfo.record]: name = "Leritean"; 11:21:18.750 [planetinfo.record]: inhabitant = "Human Colonial"; 11:21:18.750 [planetinfo.record]: inhabitants = "Human Colonials"; 11:21:18.750 [planetinfo.record]: description = "The planet Leritean is mildly well known for its exotic cuisine."; 11:21:18.760 [planetinfo.record]: air_color = 0.680604, 0.587987, 0.86567, 1; 11:21:18.760 [planetinfo.record]: cloud_alpha = 1.000000; 11:21:18.761 [planetinfo.record]: cloud_color = 0.599609, 0.381783, 0.596206, 1; 11:21:18.761 [planetinfo.record]: cloud_fraction = 0.290000; 11:21:18.761 [planetinfo.record]: land_color = 0.212135, 0.301949, 0.654297, 1; 11:21:18.761 [planetinfo.record]: land_fraction = 0.380000; 11:21:18.761 [planetinfo.record]: polar_cloud_color = 0.769824, 0.595035, 0.767093, 1; 11:21:18.761 [planetinfo.record]: polar_land_color = 0.776679, 0.808751, 0.93457, 1; 11:21:18.761 [planetinfo.record]: polar_sea_color = 0.941211, 0.912694, 0.887532, 1; 11:21:18.761 [planetinfo.record]: sea_color = 0.587891, 0.516643, 0.453778, 1; 11:21:18.761 [planetinfo.record]: rotation_speed = 0.000117 11:21:18.761 [planetinfo.record]: planet zpos = 395160.000000 11:21:18.761 [planetinfo.record]: sun_radius = 80594.486694 11:21:18.761 [planetinfo.record]: sun_vector = 0.427 -0.850 -0.309 11:21:18.761 [planetinfo.record]: sun_distance = 691530 11:21:18.761 [planetinfo.record]: corona_flare = 0.037402 11:21:18.761 [planetinfo.record]: corona_hues = 0.536858 11:21:18.761 [planetinfo.record]: sun_color = 0.699844, 0.520491, 0.235109, 1 11:21:18.761 [planetinfo.record]: corona_shimmer = 0.727224 11:21:18.761 [planetinfo.record]: station_vector = -0.509 -0.618 0.599 11:21:18.761 [planetinfo.record]: station = coriolis 11:21:22.827 [PLANETINFO OVER]: Done 11:21:22.827 [PLANETINFO LOGGING]: 0 161 11:21:23.830 [planetinfo.record]: seed = 248 155 157 214 11:21:23.830 [planetinfo.record]: coordinates = 155 60 11:21:23.830 [planetinfo.record]: government = 7; 11:21:23.830 [planetinfo.record]: economy = 4; 11:21:23.830 [planetinfo.record]: techlevel = 10; 11:21:23.830 [planetinfo.record]: population = 52; 11:21:23.830 [planetinfo.record]: productivity = 27456; 11:21:23.830 [planetinfo.record]: name = "Veale"; 11:21:23.830 [planetinfo.record]: inhabitant = "Frog"; 11:21:23.830 [planetinfo.record]: inhabitants = "Frogs"; 11:21:23.830 [planetinfo.record]: description = "The world Veale is most well known for its vast dense forests."; 11:21:23.854 [planetinfo.record]: air_color = 0.769064, 0.643115, 0.850711, 1; 11:21:23.854 [planetinfo.record]: cloud_alpha = 1.000000; 11:21:23.854 [planetinfo.record]: cloud_color = 0.554688, 0.481018, 0.493104, 1; 11:21:23.854 [planetinfo.record]: cloud_fraction = 0.270000; 11:21:23.854 [planetinfo.record]: land_color = 0.387468, 0.33902, 0.539062, 1; 11:21:23.854 [planetinfo.record]: land_fraction = 0.420000; 11:21:23.854 [planetinfo.record]: polar_cloud_color = 0.749609, 0.687386, 0.697594, 1; 11:21:23.854 [planetinfo.record]: polar_land_color = 0.879579, 0.858321, 0.946094, 1; 11:21:23.854 [planetinfo.record]: polar_sea_color = 0.923242, 0.887157, 0.824967, 1; 11:21:23.854 [planetinfo.record]: sea_color = 0.767578, 0.647574, 0.440758, 1; 11:21:23.854 [planetinfo.record]: rotation_speed = 0.003207 11:21:23.854 [planetinfo.record]: planet zpos = 585910.000000 11:21:23.854 [planetinfo.record]: sun_radius = 139321.839752 11:21:23.854 [planetinfo.record]: sun_vector = 0.070 -0.775 0.628 11:21:23.854 [planetinfo.record]: sun_distance = 811260 11:21:23.854 [planetinfo.record]: corona_flare = 0.049150 11:21:23.854 [planetinfo.record]: corona_hues = 0.826469 11:21:23.854 [planetinfo.record]: sun_color = 0.835385, 0.816269, 0.812565, 1 11:21:23.855 [planetinfo.record]: corona_shimmer = 0.310786 11:21:23.855 [planetinfo.record]: station_vector = -0.199 -0.973 0.115 11:21:23.855 [planetinfo.record]: station = coriolis 11:21:28.364 [PLANETINFO OVER]: Done 11:21:28.364 [PLANETINFO LOGGING]: 0 162 11:21:29.368 [planetinfo.record]: seed = 92 167 27 120 11:21:29.368 [planetinfo.record]: coordinates = 167 33 11:21:29.368 [planetinfo.record]: government = 3; 11:21:29.368 [planetinfo.record]: economy = 1; 11:21:29.368 [planetinfo.record]: techlevel = 11; 11:21:29.368 [planetinfo.record]: population = 49; 11:21:29.368 [planetinfo.record]: productivity = 24696; 11:21:29.368 [planetinfo.record]: name = "Edle"; 11:21:29.368 [planetinfo.record]: inhabitant = "Human Colonial"; 11:21:29.368 [planetinfo.record]: inhabitants = "Human Colonials"; 11:21:29.368 [planetinfo.record]: description = "Edle is famous for its inhabitants’ exceptional love for food blenders but scourged by frequent civil war."; 11:21:29.374 [planetinfo.record]: air_color = 0.725037, 0.72935, 0.969082, 1; 11:21:29.374 [planetinfo.record]: cloud_alpha = 1.000000; 11:21:29.375 [planetinfo.record]: cloud_color = 0.767944, 0.773499, 0.910156, 1; 11:21:29.375 [planetinfo.record]: cloud_fraction = 0.280000; 11:21:29.375 [planetinfo.record]: land_color = 0.360938, 0.624954, 0.66, 1; 11:21:29.375 [planetinfo.record]: land_fraction = 0.550000; 11:21:29.375 [planetinfo.record]: polar_cloud_color = 0.820745, 0.824215, 0.90957, 1; 11:21:29.375 [planetinfo.record]: polar_land_color = 0.828195, 0.921601, 0.934, 1; 11:21:29.375 [planetinfo.record]: polar_sea_color = 0.941992, 0.899381, 0.886337, 1; 11:21:29.375 [planetinfo.record]: sea_color = 0.580078, 0.47512, 0.442989, 1; 11:21:29.375 [planetinfo.record]: rotation_speed = 0.001837 11:21:29.375 [planetinfo.record]: planet zpos = 603720.000000 11:21:29.375 [planetinfo.record]: sun_radius = 126392.206421 11:21:29.375 [planetinfo.record]: sun_vector = 0.039 -0.902 -0.430 11:21:29.375 [planetinfo.record]: sun_distance = 955890 11:21:29.375 [planetinfo.record]: corona_flare = 0.006670 11:21:29.375 [planetinfo.record]: corona_hues = 0.824158 11:21:29.375 [planetinfo.record]: sun_color = 0.524658, 0.723422, 0.827856, 1 11:21:29.376 [planetinfo.record]: corona_shimmer = 0.353416 11:21:29.376 [planetinfo.record]: station_vector = 0.974 0.014 0.227 11:21:29.376 [planetinfo.record]: station = icosahedron 11:21:34.790 [PLANETINFO OVER]: Done 11:21:34.791 [PLANETINFO LOGGING]: 0 163 11:21:35.805 [planetinfo.record]: seed = 212 25 141 187 11:21:35.805 [planetinfo.record]: coordinates = 25 96 11:21:35.805 [planetinfo.record]: government = 2; 11:21:35.805 [planetinfo.record]: economy = 0; 11:21:35.805 [planetinfo.record]: techlevel = 9; 11:21:35.805 [planetinfo.record]: population = 39; 11:21:35.805 [planetinfo.record]: productivity = 18720; 11:21:35.805 [planetinfo.record]: name = "Anlama"; 11:21:35.805 [planetinfo.record]: inhabitant = "Harmless Bug-Eyed Bird"; 11:21:35.805 [planetinfo.record]: inhabitants = "Harmless Bug-Eyed Birds"; 11:21:35.805 [planetinfo.record]: description = "This world is a tedious little planet."; 11:21:35.816 [planetinfo.record]: air_color = 0.633573, 0.897952, 0.927457, 1; 11:21:35.816 [planetinfo.record]: cloud_alpha = 1.000000; 11:21:35.816 [planetinfo.record]: cloud_color = 0.698896, 0.785156, 0.509125, 1; 11:21:35.817 [planetinfo.record]: cloud_fraction = 0.120000; 11:21:35.817 [planetinfo.record]: land_color = 0.659617, 0.611016, 0.66, 1; 11:21:35.817 [planetinfo.record]: land_fraction = 0.450000; 11:21:35.818 [planetinfo.record]: polar_cloud_color = 0.794727, 0.85332, 0.665823, 1; 11:21:35.818 [planetinfo.record]: polar_land_color = 0.933865, 0.91667, 0.934, 1; 11:21:35.818 [planetinfo.record]: polar_sea_color = 0.942773, 0.928273, 0.710763, 1; 11:21:35.818 [planetinfo.record]: sea_color = 0.572266, 0.537058, 0.00894165, 1; 11:21:35.818 [planetinfo.record]: rotation_speed = 0.004893 11:21:35.818 [planetinfo.record]: planet zpos = 678840.000000 11:21:35.818 [planetinfo.record]: sun_radius = 167335.365143 11:21:35.819 [planetinfo.record]: sun_vector = -0.811 -0.071 0.580 11:21:35.819 [planetinfo.record]: sun_distance = 905120 11:21:35.819 [planetinfo.record]: corona_flare = 0.072588 11:21:35.819 [planetinfo.record]: corona_hues = 0.887749 11:21:35.819 [planetinfo.record]: sun_color = 0.791696, 0.689899, 0.574359, 1 11:21:35.819 [planetinfo.record]: corona_shimmer = 0.578991 11:21:35.819 [planetinfo.record]: station_vector = -0.794 0.028 0.607 11:21:35.819 [planetinfo.record]: station = coriolis 11:21:40.381 [PLANETINFO OVER]: Done 11:21:40.382 [PLANETINFO LOGGING]: 0 164 11:21:41.385 [planetinfo.record]: seed = 224 252 51 62 11:21:41.385 [planetinfo.record]: coordinates = 252 11 11:21:41.385 [planetinfo.record]: government = 4; 11:21:41.385 [planetinfo.record]: economy = 3; 11:21:41.385 [planetinfo.record]: techlevel = 6; 11:21:41.385 [planetinfo.record]: population = 32; 11:21:41.385 [planetinfo.record]: productivity = 14336; 11:21:41.385 [planetinfo.record]: name = "Ribilebi"; 11:21:41.385 [planetinfo.record]: inhabitant = "Human Colonial"; 11:21:41.385 [planetinfo.record]: inhabitants = "Human Colonials"; 11:21:41.385 [planetinfo.record]: description = "The planet Ribilebi is most famous for its vast oceans and its fabulous goat soup."; 11:21:41.400 [planetinfo.record]: air_color = 0.632574, 0.833928, 0.96648, 1; 11:21:41.400 [planetinfo.record]: cloud_alpha = 1.000000; 11:21:41.400 [planetinfo.record]: cloud_color = 0.507568, 0.902344, 0.587757, 1; 11:21:41.400 [planetinfo.record]: cloud_fraction = 0.430000; 11:21:41.400 [planetinfo.record]: land_color = 0.152109, 0.231467, 0.66, 1; 11:21:41.400 [planetinfo.record]: land_fraction = 0.340000; 11:21:41.400 [planetinfo.record]: polar_cloud_color = 0.658305, 0.906055, 0.708629, 1; 11:21:41.400 [planetinfo.record]: polar_land_color = 0.754314, 0.78239, 0.934, 1; 11:21:41.400 [planetinfo.record]: polar_sea_color = 0.906, 0.935547, 0.887673, 1; 11:21:41.400 [planetinfo.record]: sea_color = 0.563107, 0.644531, 0.512604, 1; 11:21:41.400 [planetinfo.record]: rotation_speed = 0.004793 11:21:41.400 [planetinfo.record]: planet zpos = 731720.000000 11:21:41.400 [planetinfo.record]: sun_radius = 190309.156494 11:21:41.400 [planetinfo.record]: sun_vector = -0.653 -0.589 0.476 11:21:41.402 [planetinfo.record]: sun_distance = 1330400 11:21:41.402 [planetinfo.record]: corona_flare = 0.086357 11:21:41.402 [planetinfo.record]: corona_hues = 0.655777 11:21:41.402 [planetinfo.record]: sun_color = 0.697378, 0.706958, 0.221019, 1 11:21:41.402 [planetinfo.record]: corona_shimmer = 0.359117 11:21:41.402 [planetinfo.record]: station_vector = -0.249 -0.758 0.602 11:21:41.402 [planetinfo.record]: station = coriolis 11:21:46.125 [PLANETINFO OVER]: Done 11:21:46.127 [PLANETINFO LOGGING]: 0 165 11:21:47.146 [planetinfo.record]: seed = 32 6 13 206 11:21:47.146 [planetinfo.record]: coordinates = 6 129 11:21:47.146 [planetinfo.record]: government = 4; 11:21:47.146 [planetinfo.record]: economy = 1; 11:21:47.146 [planetinfo.record]: techlevel = 10; 11:21:47.146 [planetinfo.record]: population = 46; 11:21:47.146 [planetinfo.record]: productivity = 26496; 11:21:47.146 [planetinfo.record]: name = "Relaes"; 11:21:47.146 [planetinfo.record]: inhabitant = "Human Colonial"; 11:21:47.146 [planetinfo.record]: inhabitants = "Human Colonials"; 11:21:47.146 [planetinfo.record]: description = "This world is a tedious place."; 11:21:47.165 [planetinfo.record]: air_color = 0.587653, 0.949434, 0.954773, 1; 11:21:47.165 [planetinfo.record]: cloud_alpha = 1.000000; 11:21:47.166 [planetinfo.record]: cloud_color = 0.840882, 0.867188, 0.386169, 1; 11:21:47.166 [planetinfo.record]: cloud_fraction = 0.450000; 11:21:47.166 [planetinfo.record]: land_color = 0.65625, 0.597871, 0.515259, 1; 11:21:47.166 [planetinfo.record]: land_fraction = 0.440000; 11:21:47.166 [planetinfo.record]: polar_cloud_color = 0.873356, 0.890234, 0.581608, 1; 11:21:47.166 [planetinfo.record]: polar_land_color = 0.934375, 0.913595, 0.884189, 1; 11:21:47.166 [planetinfo.record]: polar_sea_color = 0.736032, 0.899549, 0.931641, 1; 11:21:47.166 [planetinfo.record]: sea_color = 0.109482, 0.589404, 0.683594, 1; 11:21:47.166 [planetinfo.record]: rotation_speed = 0.002868 11:21:47.166 [planetinfo.record]: planet zpos = 832780.000000 11:21:47.166 [planetinfo.record]: sun_radius = 178367.257996 11:21:47.166 [planetinfo.record]: sun_vector = 0.209 -0.243 -0.947 11:21:47.166 [planetinfo.record]: sun_distance = 1089020 11:21:47.166 [planetinfo.record]: corona_flare = 0.034637 11:21:47.166 [planetinfo.record]: corona_hues = 0.820717 11:21:47.166 [planetinfo.record]: sun_color = 0.79834, 0.734657, 0.637075, 1 11:21:47.166 [planetinfo.record]: corona_shimmer = 0.684549 11:21:47.167 [planetinfo.record]: station_vector = -0.806 -0.323 0.496 11:21:47.167 [planetinfo.record]: station = coriolis 11:21:52.202 [PLANETINFO OVER]: Done 11:21:52.202 [PLANETINFO LOGGING]: 0 166 11:21:53.223 [planetinfo.record]: seed = 148 77 27 205 11:21:53.223 [planetinfo.record]: coordinates = 77 41 11:21:53.223 [planetinfo.record]: government = 2; 11:21:53.223 [planetinfo.record]: economy = 1; 11:21:53.223 [planetinfo.record]: techlevel = 8; 11:21:53.223 [planetinfo.record]: population = 36; 11:21:53.223 [planetinfo.record]: productivity = 15552; 11:21:53.223 [planetinfo.record]: name = "Dizaoner"; 11:21:53.223 [planetinfo.record]: inhabitant = "Human Colonial"; 11:21:53.223 [planetinfo.record]: inhabitants = "Human Colonials"; 11:21:53.223 [planetinfo.record]: description = "Dizaoner is ravaged by unpredictable solar activity."; 11:21:53.246 [planetinfo.record]: air_color = 0.764088, 0.576899, 0.906645, 1; 11:21:53.246 [planetinfo.record]: cloud_alpha = 1.000000; 11:21:53.246 [planetinfo.record]: cloud_color = 0.722656, 0.366974, 0.46701, 1; 11:21:53.246 [planetinfo.record]: cloud_fraction = 0.270000; 11:21:53.246 [planetinfo.record]: land_color = 0.0103125, 0.568638, 0.66, 1; 11:21:53.246 [planetinfo.record]: land_fraction = 0.500000; 11:21:53.246 [planetinfo.record]: polar_cloud_color = 0.825195, 0.571351, 0.642745, 1; 11:21:53.246 [planetinfo.record]: polar_land_color = 0.704148, 0.901677, 0.934, 1; 11:21:53.246 [planetinfo.record]: polar_sea_color = 0.880186, 0.928516, 0.900197, 1; 11:21:53.246 [planetinfo.record]: sea_color = 0.566011, 0.714844, 0.627637, 1; 11:21:53.255 [planetinfo.record]: rotation_speed = 0.004050 11:21:53.256 [planetinfo.record]: planet zpos = 870940.000000 11:21:53.256 [planetinfo.record]: sun_radius = 157861.102448 11:21:53.256 [planetinfo.record]: sun_vector = -0.954 0.186 -0.236 11:21:53.256 [planetinfo.record]: sun_distance = 1306410 11:21:53.256 [planetinfo.record]: corona_flare = 0.030159 11:21:53.256 [planetinfo.record]: corona_hues = 0.710274 11:21:53.256 [planetinfo.record]: sun_color = 0.331803, 0.3857, 0.823154, 1 11:21:53.256 [planetinfo.record]: corona_shimmer = 0.335288 11:21:53.256 [planetinfo.record]: station_vector = -0.303 0.820 0.486 11:21:53.256 [planetinfo.record]: station = coriolis 11:21:58.899 [PLANETINFO OVER]: Done 11:21:58.900 [PLANETINFO LOGGING]: 0 167 11:21:59.905 [planetinfo.record]: seed = 220 112 157 20 11:21:59.905 [planetinfo.record]: coordinates = 112 95 11:21:59.905 [planetinfo.record]: government = 3; 11:21:59.905 [planetinfo.record]: economy = 7; 11:21:59.905 [planetinfo.record]: techlevel = 2; 11:21:59.905 [planetinfo.record]: population = 19; 11:21:59.905 [planetinfo.record]: productivity = 3192; 11:21:59.905 [planetinfo.record]: name = "Razaar"; 11:21:59.905 [planetinfo.record]: inhabitant = "Green Insect"; 11:21:59.905 [planetinfo.record]: inhabitants = "Green Insects"; 11:21:59.905 [planetinfo.record]: description = "The world Razaar is a dull place."; 11:21:59.936 [planetinfo.record]: air_color = 0.654215, 0.882536, 0.912498, 1; 11:21:59.936 [planetinfo.record]: cloud_alpha = 1.000000; 11:21:59.936 [planetinfo.record]: cloud_color = 0.673729, 0.740234, 0.555176, 1; 11:21:59.936 [planetinfo.record]: cloud_fraction = 0.380000; 11:21:59.936 [planetinfo.record]: land_color = 0.0909119, 0.646484, 0.425123, 1; 11:21:59.936 [planetinfo.record]: land_fraction = 0.610000; 11:21:59.936 [planetinfo.record]: polar_cloud_color = 0.786325, 0.833105, 0.702933, 1; 11:21:59.936 [planetinfo.record]: polar_land_color = 0.734397, 0.935352, 0.855284, 1; 11:21:59.936 [planetinfo.record]: polar_sea_color = 0.875456, 0.927539, 0.886035, 1; 11:21:59.936 [planetinfo.record]: sea_color = 0.561855, 0.724609, 0.594915, 1; 11:21:59.936 [planetinfo.record]: rotation_speed = 0.002018 11:21:59.936 [planetinfo.record]: planet zpos = 474240.000000 11:21:59.936 [planetinfo.record]: sun_radius = 68946.528320 11:21:59.936 [planetinfo.record]: sun_vector = 0.114 0.109 0.988 11:21:59.936 [planetinfo.record]: sun_distance = 750880 11:21:59.937 [planetinfo.record]: corona_flare = 0.024352 11:21:59.937 [planetinfo.record]: corona_hues = 0.668259 11:21:59.937 [planetinfo.record]: sun_color = 0.550976, 0.561307, 0.660739, 1 11:21:59.937 [planetinfo.record]: corona_shimmer = 0.362915 11:21:59.937 [planetinfo.record]: station_vector = -0.886 0.125 0.447 11:21:59.937 [planetinfo.record]: station = coriolis 11:22:05.919 [PLANETINFO OVER]: Done 11:22:05.919 [PLANETINFO LOGGING]: 0 168 11:22:06.922 [planetinfo.record]: seed = 120 99 83 178 11:22:06.922 [planetinfo.record]: coordinates = 99 106 11:22:06.922 [planetinfo.record]: government = 7; 11:22:06.922 [planetinfo.record]: economy = 2; 11:22:06.922 [planetinfo.record]: techlevel = 12; 11:22:06.922 [planetinfo.record]: population = 58; 11:22:06.922 [planetinfo.record]: productivity = 40832; 11:22:06.922 [planetinfo.record]: name = "Enonla"; 11:22:06.922 [planetinfo.record]: inhabitant = "Human Colonial"; 11:22:06.922 [planetinfo.record]: inhabitants = "Human Colonials"; 11:22:06.923 [planetinfo.record]: description = "Enonla is ravaged by dreadful civil war."; 11:22:06.940 [planetinfo.record]: air_color = 0.551931, 0.527548, 0.885182, 1; 11:22:06.940 [planetinfo.record]: cloud_alpha = 1.000000; 11:22:06.940 [planetinfo.record]: cloud_color = 0.342841, 0.254539, 0.658203, 1; 11:22:06.940 [planetinfo.record]: cloud_fraction = 0.340000; 11:22:06.940 [planetinfo.record]: land_color = 0.271089, 0.589844, 0.0529938, 1; 11:22:06.940 [planetinfo.record]: land_fraction = 0.460000; 11:22:06.940 [planetinfo.record]: polar_cloud_color = 0.557769, 0.491011, 0.796191, 1; 11:22:06.940 [planetinfo.record]: polar_land_color = 0.813883, 0.941016, 0.726898, 1; 11:22:06.940 [planetinfo.record]: polar_sea_color = 0.932031, 0.903083, 0.869228, 1; 11:22:06.940 [planetinfo.record]: sea_color = 0.679688, 0.595245, 0.49649, 1; 11:22:06.940 [planetinfo.record]: rotation_speed = 0.004510 11:22:06.940 [planetinfo.record]: planet zpos = 376970.000000 11:22:06.940 [planetinfo.record]: sun_radius = 86628.803711 11:22:06.940 [planetinfo.record]: sun_vector = 0.416 0.827 0.379 11:22:06.940 [planetinfo.record]: sun_distance = 651130 11:22:06.940 [planetinfo.record]: corona_flare = 0.053900 11:22:06.940 [planetinfo.record]: corona_hues = 0.719002 11:22:06.940 [planetinfo.record]: sun_color = 0.665632, 0.712697, 0.809052, 1 11:22:06.940 [planetinfo.record]: corona_shimmer = 0.326703 11:22:06.940 [planetinfo.record]: station_vector = -0.285 0.612 0.738 11:22:06.940 [planetinfo.record]: station = dodecahedron 11:22:11.431 [PLANETINFO OVER]: Done 11:22:11.432 [PLANETINFO LOGGING]: 0 169 11:22:12.449 [planetinfo.record]: seed = 8 200 189 221 11:22:12.450 [planetinfo.record]: coordinates = 200 149 11:22:12.450 [planetinfo.record]: government = 1; 11:22:12.450 [planetinfo.record]: economy = 7; 11:22:12.450 [planetinfo.record]: techlevel = 1; 11:22:12.450 [planetinfo.record]: population = 13; 11:22:12.450 [planetinfo.record]: productivity = 1560; 11:22:12.450 [planetinfo.record]: name = "Isanlequ"; 11:22:12.450 [planetinfo.record]: inhabitant = "Furry Feline"; 11:22:12.450 [planetinfo.record]: inhabitants = "Furry Felines"; 11:22:12.450 [planetinfo.record]: description = "This planet is beset by evil disease."; 11:22:12.459 [planetinfo.record]: air_color = 0.617179, 0.894938, 0.825272, 1; 11:22:12.459 [planetinfo.record]: cloud_alpha = 1.000000; 11:22:12.459 [planetinfo.record]: cloud_color = 0.6875, 0.51808, 0.459229, 1; 11:22:12.459 [planetinfo.record]: cloud_fraction = 0.220000; 11:22:12.459 [planetinfo.record]: land_color = 0.289604, 0.260353, 0.634766, 1; 11:22:12.459 [planetinfo.record]: land_fraction = 0.450000; 11:22:12.459 [planetinfo.record]: polar_cloud_color = 0.809375, 0.684716, 0.641414, 1; 11:22:12.459 [planetinfo.record]: polar_land_color = 0.809212, 0.798423, 0.936523, 1; 11:22:12.460 [planetinfo.record]: polar_sea_color = 0.936523, 0.925117, 0.880369, 1; 11:22:12.460 [planetinfo.record]: sea_color = 0.634766, 0.603841, 0.482521, 1; 11:22:12.460 [planetinfo.record]: rotation_speed = 0.002592 11:22:12.460 [planetinfo.record]: planet zpos = 951600.000000 11:22:12.460 [planetinfo.record]: sun_radius = 158246.673584 11:22:12.460 [planetinfo.record]: sun_vector = -0.990 -0.115 -0.076 11:22:12.460 [planetinfo.record]: sun_distance = 1459120 11:22:12.461 [planetinfo.record]: corona_flare = 0.062918 11:22:12.461 [planetinfo.record]: corona_hues = 0.979439 11:22:12.461 [planetinfo.record]: sun_color = 0.631176, 0.804256, 0.821973, 1 11:22:12.461 [planetinfo.record]: corona_shimmer = 0.348577 11:22:12.461 [planetinfo.record]: station_vector = 0.486 -0.811 0.326 11:22:12.461 [planetinfo.record]: station = coriolis 11:22:17.527 [PLANETINFO OVER]: Done 11:22:17.528 [PLANETINFO LOGGING]: 0 170 11:22:18.550 [planetinfo.record]: seed = 140 250 91 23 11:22:18.550 [planetinfo.record]: coordinates = 250 225 11:22:18.550 [planetinfo.record]: government = 1; 11:22:18.550 [planetinfo.record]: economy = 3; 11:22:18.550 [planetinfo.record]: techlevel = 7; 11:22:18.550 [planetinfo.record]: population = 33; 11:22:18.550 [planetinfo.record]: productivity = 9240; 11:22:18.550 [planetinfo.record]: name = "Tibecea"; 11:22:18.550 [planetinfo.record]: inhabitant = "Human Colonial"; 11:22:18.550 [planetinfo.record]: inhabitants = "Human Colonials"; 11:22:18.550 [planetinfo.record]: description = "Tibecea is very fabled for the Tibeceaian edible poet."; 11:22:18.558 [planetinfo.record]: air_color = 0.7068, 0.781047, 0.943066, 1; 11:22:18.558 [planetinfo.record]: cloud_alpha = 1.000000; 11:22:18.558 [planetinfo.record]: cloud_color = 0.705276, 0.823119, 0.832031, 1; 11:22:18.558 [planetinfo.record]: cloud_fraction = 0.500000; 11:22:18.558 [planetinfo.record]: land_color = 0.380564, 0.293564, 0.621094, 1; 11:22:18.559 [planetinfo.record]: land_fraction = 0.410000; 11:22:18.559 [planetinfo.record]: polar_cloud_color = 0.791157, 0.86856, 0.874414, 1; 11:22:18.559 [planetinfo.record]: polar_land_color = 0.847087, 0.814243, 0.937891, 1; 11:22:18.559 [planetinfo.record]: polar_sea_color = 0.925, 0.840892, 0.818408, 1; 11:22:18.559 [planetinfo.record]: sea_color = 0.75, 0.477219, 0.404297, 1; 11:22:18.559 [planetinfo.record]: rotation_speed = 0.001181 11:22:18.560 [planetinfo.record]: planet zpos = 534380.000000 11:22:18.560 [planetinfo.record]: sun_radius = 138146.409912 11:22:18.560 [planetinfo.record]: sun_vector = -0.021 -0.200 -0.980 11:22:18.560 [planetinfo.record]: sun_distance = 923020 11:22:18.560 [planetinfo.record]: corona_flare = 0.008879 11:22:18.560 [planetinfo.record]: corona_hues = 0.854500 11:22:18.560 [planetinfo.record]: sun_color = 0.747104, 0.509349, 0.508775, 1 11:22:18.561 [planetinfo.record]: corona_shimmer = 0.376142 11:22:18.561 [planetinfo.record]: station_vector = -0.005 0.471 0.882 11:22:18.561 [planetinfo.record]: station = coriolis 11:22:23.066 [PLANETINFO OVER]: Done 11:22:23.066 [PLANETINFO LOGGING]: 0 171 11:22:24.086 [planetinfo.record]: seed = 164 15 237 7 11:22:24.086 [planetinfo.record]: coordinates = 15 5 11:22:24.086 [planetinfo.record]: government = 4; 11:22:24.086 [planetinfo.record]: economy = 5; 11:22:24.086 [planetinfo.record]: techlevel = 7; 11:22:24.086 [planetinfo.record]: population = 38; 11:22:24.086 [planetinfo.record]: productivity = 12160; 11:22:24.086 [planetinfo.record]: name = "Sotera"; 11:22:24.086 [planetinfo.record]: inhabitant = "Fierce Green Horned Humanoid"; 11:22:24.087 [planetinfo.record]: inhabitants = "Fierce Green Horned Humanoids"; 11:22:24.087 [planetinfo.record]: description = "Sotera is mildly notable for Soteraian lethal brandy."; 11:22:24.097 [planetinfo.record]: air_color = 0.484668, 0.581763, 0.88258, 1; 11:22:24.097 [planetinfo.record]: cloud_alpha = 1.000000; 11:22:24.097 [planetinfo.record]: cloud_color = 0.160057, 0.512484, 0.650391, 1; 11:22:24.098 [planetinfo.record]: cloud_fraction = 0.410000; 11:22:24.098 [planetinfo.record]: land_color = 0.379982, 0.333252, 0.546875, 1; 11:22:24.098 [planetinfo.record]: land_fraction = 0.470000; 11:22:24.098 [planetinfo.record]: polar_cloud_color = 0.419174, 0.687628, 0.792676, 1; 11:22:24.098 [planetinfo.record]: polar_land_color = 0.873191, 0.852997, 0.945312, 1; 11:22:24.098 [planetinfo.record]: polar_sea_color = 0.915625, 0.896205, 0.791336, 1; 11:22:24.098 [planetinfo.record]: sea_color = 0.84375, 0.772167, 0.38562, 1; 11:22:24.098 [planetinfo.record]: rotation_speed = 0.004185 11:22:24.098 [planetinfo.record]: planet zpos = 508530.000000 11:22:24.098 [planetinfo.record]: sun_radius = 128632.915192 11:22:24.098 [planetinfo.record]: sun_vector = -0.792 0.611 0.011 11:22:24.098 [planetinfo.record]: sun_distance = 1109520 11:22:24.098 [planetinfo.record]: corona_flare = 0.063986 11:22:24.098 [planetinfo.record]: corona_hues = 0.807594 11:22:24.098 [planetinfo.record]: sun_color = 0.706085, 0.696465, 0.660544, 1 11:22:24.098 [planetinfo.record]: corona_shimmer = 0.226391 11:22:24.099 [planetinfo.record]: station_vector = -0.938 -0.323 0.127 11:22:24.100 [planetinfo.record]: station = coriolis 11:22:28.736 [PLANETINFO OVER]: Done 11:22:28.737 [PLANETINFO LOGGING]: 0 172 11:22:29.739 [planetinfo.record]: seed = 208 88 179 169 11:22:29.740 [planetinfo.record]: coordinates = 88 52 11:22:29.740 [planetinfo.record]: government = 2; 11:22:29.740 [planetinfo.record]: economy = 4; 11:22:29.740 [planetinfo.record]: techlevel = 4; 11:22:29.740 [planetinfo.record]: population = 23; 11:22:29.740 [planetinfo.record]: productivity = 6624; 11:22:29.740 [planetinfo.record]: name = "Esveor"; 11:22:29.740 [planetinfo.record]: inhabitant = "Small Harmless Fat Humanoid"; 11:22:29.740 [planetinfo.record]: inhabitants = "Small Harmless Fat Humanoids"; 11:22:29.740 [planetinfo.record]: description = "Esveor is mildly famous for its pink oceans and Zero-G hockey."; 11:22:29.756 [planetinfo.record]: air_color = 0.55764, 0.46372, 0.950221, 1; 11:22:29.756 [planetinfo.record]: cloud_alpha = 1.000000; 11:22:29.756 [planetinfo.record]: cloud_color = 0.528446, 0.0533447, 0.853516, 1; 11:22:29.756 [planetinfo.record]: cloud_fraction = 0.260000; 11:22:29.756 [planetinfo.record]: land_color = 0.629063, 0.656375, 0.66, 1; 11:22:29.756 [planetinfo.record]: land_fraction = 0.260000; 11:22:29.756 [planetinfo.record]: polar_cloud_color = 0.673638, 0.366065, 0.884082, 1; 11:22:29.756 [planetinfo.record]: polar_land_color = 0.923055, 0.932717, 0.934, 1; 11:22:29.756 [planetinfo.record]: polar_sea_color = 0.905, 0.85975, 0.89233, 1; 11:22:29.756 [planetinfo.record]: sea_color = 0.95, 0.76, 0.8968, 1; 11:22:29.757 [planetinfo.record]: rotation_speed = 0.004192 11:22:29.757 [planetinfo.record]: planet zpos = 624960.000000 11:22:29.757 [planetinfo.record]: sun_radius = 127453.593750 11:22:29.757 [planetinfo.record]: sun_vector = -0.357 0.933 0.034 11:22:29.757 [planetinfo.record]: sun_distance = 1197840 11:22:29.757 [planetinfo.record]: corona_flare = 0.097742 11:22:29.757 [planetinfo.record]: corona_hues = 0.610794 11:22:29.757 [planetinfo.record]: sun_color = 0.654282, 0.63851, 0.469306, 1 11:22:29.757 [planetinfo.record]: corona_shimmer = 0.386452 11:22:29.757 [planetinfo.record]: station_vector = 0.597 0.793 0.122 11:22:29.757 [planetinfo.record]: station = coriolis 11:22:34.887 [PLANETINFO OVER]: Done 11:22:34.888 [PLANETINFO LOGGING]: 0 173 11:22:35.892 [planetinfo.record]: seed = 176 25 173 137 11:22:35.892 [planetinfo.record]: coordinates = 25 57 11:22:35.892 [planetinfo.record]: government = 6; 11:22:35.892 [planetinfo.record]: economy = 1; 11:22:35.892 [planetinfo.record]: techlevel = 10; 11:22:35.893 [planetinfo.record]: population = 48; 11:22:35.893 [planetinfo.record]: productivity = 34560; 11:22:35.893 [planetinfo.record]: name = "Esteonbi"; 11:22:35.893 [planetinfo.record]: inhabitant = "Small Black Slimy Frog"; 11:22:35.893 [planetinfo.record]: inhabitants = "Small Black Slimy Frogs"; 11:22:35.893 [planetinfo.record]: description = "This planet is mildly fabled for its inhabitants’ ingrained shyness but cursed by unpredictable solar activity."; 11:22:35.897 [planetinfo.record]: air_color = 0.665603, 0.83315, 0.776743, 1; 11:22:35.897 [planetinfo.record]: cloud_alpha = 1.000000; 11:22:35.897 [planetinfo.record]: cloud_color = 0.501953, 0.499992, 0.499992, 1; 11:22:35.897 [planetinfo.record]: cloud_fraction = 0.370000; 11:22:35.897 [planetinfo.record]: land_color = 0.66, 0.0257813, 0.263613, 1; 11:22:35.898 [planetinfo.record]: land_fraction = 0.430000; 11:22:35.898 [planetinfo.record]: polar_cloud_color = 0.725879, 0.724107, 0.724107, 1; 11:22:35.898 [planetinfo.record]: polar_land_color = 0.934, 0.709621, 0.793763, 1; 11:22:35.898 [planetinfo.record]: polar_sea_color = 0.941992, 0.916638, 0.889649, 1; 11:22:35.898 [planetinfo.record]: sea_color = 0.580078, 0.517627, 0.451147, 1; 11:22:35.898 [planetinfo.record]: rotation_speed = 0.002214 11:22:35.898 [planetinfo.record]: planet zpos = 565950.000000 11:22:35.898 [planetinfo.record]: sun_radius = 142008.782959 11:22:35.898 [planetinfo.record]: sun_vector = 0.103 0.450 -0.887 11:22:35.898 [planetinfo.record]: sun_distance = 1029000 11:22:35.898 [planetinfo.record]: corona_flare = 0.074982 11:22:35.898 [planetinfo.record]: corona_hues = 0.545532 11:22:35.898 [planetinfo.record]: sun_color = 0.755078, 0.675027, 0.322077, 1 11:22:35.899 [planetinfo.record]: corona_shimmer = 0.374402 11:22:35.899 [planetinfo.record]: station_vector = -0.605 -0.554 0.572 11:22:35.899 [planetinfo.record]: station = coriolis 11:22:41.337 [PLANETINFO OVER]: Done 11:22:41.338 [PLANETINFO LOGGING]: 0 174 11:22:42.342 [planetinfo.record]: seed = 68 230 219 66 11:22:42.342 [planetinfo.record]: coordinates = 230 127 11:22:42.342 [planetinfo.record]: government = 0; 11:22:42.342 [planetinfo.record]: economy = 7; 11:22:42.342 [planetinfo.record]: techlevel = 2; 11:22:42.342 [planetinfo.record]: population = 16; 11:22:42.342 [planetinfo.record]: productivity = 1536; 11:22:42.342 [planetinfo.record]: name = "Xeesenri"; 11:22:42.342 [planetinfo.record]: inhabitant = "Large Yellow Bug-Eyed Lobster"; 11:22:42.342 [planetinfo.record]: inhabitants = "Large Yellow Bug-Eyed Lobsters"; 11:22:42.342 [planetinfo.record]: description = "Xeesenri is mildly notable for its inhabitants’ weird shyness."; 11:22:42.350 [planetinfo.record]: air_color = 0.710438, 0.828061, 0.893637, 1; 11:22:42.351 [planetinfo.record]: cloud_alpha = 1.000000; 11:22:42.351 [planetinfo.record]: cloud_color = 0.672913, 0.683594, 0.673831, 1; 11:22:42.351 [planetinfo.record]: cloud_fraction = 0.320000; 11:22:42.351 [planetinfo.record]: land_color = 0.487266, 0.639758, 0.66, 1; 11:22:42.351 [planetinfo.record]: land_fraction = 0.650000; 11:22:42.351 [planetinfo.record]: polar_cloud_color = 0.79973, 0.807617, 0.800408, 1; 11:22:42.351 [planetinfo.record]: polar_land_color = 0.872889, 0.926839, 0.934, 1; 11:22:42.351 [planetinfo.record]: polar_sea_color = 0.93457, 0.928262, 0.87689, 1; 11:22:42.351 [planetinfo.record]: sea_color = 0.654297, 0.63663, 0.492767, 1; 11:22:42.351 [planetinfo.record]: rotation_speed = 0.003385 11:22:42.351 [planetinfo.record]: planet zpos = 426960.000000 11:22:42.351 [planetinfo.record]: sun_radius = 83781.518555 11:22:42.351 [planetinfo.record]: sun_vector = -0.685 0.684 -0.250 11:22:42.351 [planetinfo.record]: sun_distance = 676020 11:22:42.351 [planetinfo.record]: corona_flare = 0.075815 11:22:42.351 [planetinfo.record]: corona_hues = 0.577980 11:22:42.351 [planetinfo.record]: sun_color = 0.4521, 0.584955, 0.698621, 1 11:22:42.352 [planetinfo.record]: corona_shimmer = 0.536187 11:22:42.352 [planetinfo.record]: station_vector = 0.986 -0.054 0.160 11:22:42.352 [planetinfo.record]: station = coriolis 11:22:46.929 [PLANETINFO OVER]: Done 11:22:46.930 [PLANETINFO LOGGING]: 0 175 11:22:47.935 [planetinfo.record]: seed = 44 190 125 57 11:22:47.935 [planetinfo.record]: coordinates = 190 210 11:22:47.935 [planetinfo.record]: government = 5; 11:22:47.935 [planetinfo.record]: economy = 2; 11:22:47.935 [planetinfo.record]: techlevel = 10; 11:22:47.935 [planetinfo.record]: population = 48; 11:22:47.935 [planetinfo.record]: productivity = 27648; 11:22:47.935 [planetinfo.record]: name = "Oresle"; 11:22:47.935 [planetinfo.record]: inhabitant = "Human Colonial"; 11:22:47.935 [planetinfo.record]: inhabitants = "Human Colonials"; 11:22:47.935 [planetinfo.record]: description = "This world is reasonably notable for its great volcanoes but ravaged by vicious disease."; 11:22:47.938 [planetinfo.record]: air_color = 0.726086, 0.783817, 0.930059, 1; 11:22:47.939 [planetinfo.record]: cloud_alpha = 1.000000; 11:22:47.939 [planetinfo.record]: cloud_color = 0.746506, 0.785346, 0.792969, 1; 11:22:47.939 [planetinfo.record]: cloud_fraction = 0.250000; 11:22:47.939 [planetinfo.record]: land_color = 0.114021, 0.595703, 0.245731, 1; 11:22:47.939 [planetinfo.record]: land_fraction = 0.310000; 11:22:47.939 [planetinfo.record]: polar_cloud_color = 0.825458, 0.851688, 0.856836, 1; 11:22:47.939 [planetinfo.record]: polar_land_color = 0.750323, 0.94043, 0.802306, 1; 11:22:47.939 [planetinfo.record]: polar_sea_color = 0.94043, 0.879908, 0.879908, 1; 11:22:47.940 [planetinfo.record]: sea_color = 0.595703, 0.442356, 0.442356, 1; 11:22:47.940 [planetinfo.record]: rotation_speed = 0.001422 11:22:47.940 [planetinfo.record]: planet zpos = 743400.000000 11:22:47.940 [planetinfo.record]: sun_radius = 151620.529175 11:22:47.940 [planetinfo.record]: sun_vector = 0.785 0.007 -0.619 11:22:47.940 [planetinfo.record]: sun_distance = 1274400 11:22:47.940 [planetinfo.record]: corona_flare = 0.047809 11:22:47.940 [planetinfo.record]: corona_hues = 0.914589 11:22:47.940 [planetinfo.record]: sun_color = 0.765565, 0.837649, 0.839514, 1 11:22:47.940 [planetinfo.record]: corona_shimmer = 0.357331 11:22:47.941 [planetinfo.record]: station_vector = -0.464 -0.875 0.141 11:22:47.941 [planetinfo.record]: station = coriolis 11:22:52.313 [PLANETINFO OVER]: Done 11:22:52.314 [PLANETINFO LOGGING]: 0 176 11:22:53.318 [planetinfo.record]: seed = 232 196 83 80 11:22:53.318 [planetinfo.record]: coordinates = 196 193 11:22:53.318 [planetinfo.record]: government = 5; 11:22:53.318 [planetinfo.record]: economy = 1; 11:22:53.318 [planetinfo.record]: techlevel = 9; 11:22:53.318 [planetinfo.record]: population = 43; 11:22:53.318 [planetinfo.record]: productivity = 27864; 11:22:53.318 [planetinfo.record]: name = "Ervein"; 11:22:53.318 [planetinfo.record]: inhabitant = "Human Colonial"; 11:22:53.319 [planetinfo.record]: inhabitants = "Human Colonials"; 11:22:53.319 [planetinfo.record]: description = "Ervein is a revolting little planet."; 11:22:53.344 [planetinfo.record]: air_color = 0.613153, 0.848681, 0.975586, 1; 11:22:53.344 [planetinfo.record]: cloud_alpha = 1.000000; 11:22:53.344 [planetinfo.record]: cloud_color = 0.450317, 0.929688, 0.480278, 1; 11:22:53.344 [planetinfo.record]: cloud_fraction = 0.230000; 11:22:53.344 [planetinfo.record]: land_color = 0.66, 0.04125, 0.548818, 1; 11:22:53.344 [planetinfo.record]: land_fraction = 0.450000; 11:22:53.344 [planetinfo.record]: polar_cloud_color = 0.622404, 0.918359, 0.640901, 1; 11:22:53.344 [planetinfo.record]: polar_land_color = 0.934, 0.715094, 0.894665, 1; 11:22:53.344 [planetinfo.record]: polar_sea_color = 0.943945, 0.917403, 0.893983, 1; 11:22:53.344 [planetinfo.record]: sea_color = 0.560547, 0.497499, 0.441869, 1; 11:22:53.344 [planetinfo.record]: rotation_speed = 0.003855 11:22:53.344 [planetinfo.record]: planet zpos = 421680.000000 11:22:53.345 [planetinfo.record]: sun_radius = 78356.304932 11:22:53.345 [planetinfo.record]: sun_vector = 0.241 0.180 -0.954 11:22:53.345 [planetinfo.record]: sun_distance = 602400 11:22:53.345 [planetinfo.record]: corona_flare = 0.026529 11:22:53.345 [planetinfo.record]: corona_hues = 0.891121 11:22:53.345 [planetinfo.record]: sun_color = 0.743231, 0.745726, 0.808307, 1 11:22:53.345 [planetinfo.record]: corona_shimmer = 0.349186 11:22:53.346 [planetinfo.record]: station_vector = -0.937 -0.284 0.205 11:22:53.346 [planetinfo.record]: station = coriolis 11:22:57.737 [PLANETINFO OVER]: Done 11:22:57.738 [PLANETINFO LOGGING]: 0 177 11:22:58.740 [planetinfo.record]: seed = 24 19 221 213 11:22:58.740 [planetinfo.record]: coordinates = 19 236 11:22:58.740 [planetinfo.record]: government = 3; 11:22:58.740 [planetinfo.record]: economy = 4; 11:22:58.740 [planetinfo.record]: techlevel = 8; 11:22:58.740 [planetinfo.record]: population = 40; 11:22:58.740 [planetinfo.record]: productivity = 13440; 11:22:58.740 [planetinfo.record]: name = "Larais"; 11:22:58.740 [planetinfo.record]: inhabitant = "Rodent"; 11:22:58.740 [planetinfo.record]: inhabitants = "Rodents"; 11:22:58.740 [planetinfo.record]: description = "This world is a revolting dump."; 11:22:58.747 [planetinfo.record]: air_color = 0.463438, 0.636329, 0.879328, 1; 11:22:58.748 [planetinfo.record]: cloud_alpha = 1.000000; 11:22:58.748 [planetinfo.record]: cloud_color = 0.115112, 0.640625, 0.517458, 1; 11:22:58.748 [planetinfo.record]: cloud_fraction = 0.400000; 11:22:58.748 [planetinfo.record]: land_color = 0.66, 0.0128906, 0.164557, 1; 11:22:58.748 [planetinfo.record]: land_fraction = 0.650000; 11:22:58.748 [planetinfo.record]: polar_cloud_color = 0.384133, 0.788281, 0.693559, 1; 11:22:58.748 [planetinfo.record]: polar_land_color = 0.934, 0.705061, 0.758718, 1; 11:22:58.748 [planetinfo.record]: polar_sea_color = 0.939453, 0.911127, 0.883673, 1; 11:22:58.748 [planetinfo.record]: sea_color = 0.605469, 0.532446, 0.46167, 1; 11:22:58.748 [planetinfo.record]: rotation_speed = 0.001936 11:22:58.748 [planetinfo.record]: planet zpos = 452650.000000 11:22:58.748 [planetinfo.record]: sun_radius = 125547.183228 11:22:58.748 [planetinfo.record]: sun_vector = 0.076 0.827 0.557 11:22:58.748 [planetinfo.record]: sun_distance = 905300 11:22:58.749 [planetinfo.record]: corona_flare = 0.052463 11:22:58.749 [planetinfo.record]: corona_hues = 0.907898 11:22:58.749 [planetinfo.record]: sun_color = 0.817111, 0.738148, 0.702093, 1 11:22:58.749 [planetinfo.record]: corona_shimmer = 0.399191 11:22:58.749 [planetinfo.record]: station_vector = 0.326 -0.638 0.697 11:22:58.749 [planetinfo.record]: station = coriolis 11:23:03.415 [PLANETINFO OVER]: Done 11:23:03.416 [PLANETINFO LOGGING]: 0 178 11:23:04.437 [planetinfo.record]: seed = 188 104 155 251 11:23:04.437 [planetinfo.record]: coordinates = 104 189 11:23:04.437 [planetinfo.record]: government = 7; 11:23:04.437 [planetinfo.record]: economy = 5; 11:23:04.437 [planetinfo.record]: techlevel = 6; 11:23:04.437 [planetinfo.record]: population = 37; 11:23:04.437 [planetinfo.record]: productivity = 16280; 11:23:04.437 [planetinfo.record]: name = "Anxebiza"; 11:23:04.438 [planetinfo.record]: inhabitant = "Furry Rodent"; 11:23:04.438 [planetinfo.record]: inhabitants = "Furry Rodents"; 11:23:04.438 [planetinfo.record]: description = "The planet Anxebiza is an unremarkable dump."; 11:23:04.452 [planetinfo.record]: air_color = 0.700379, 0.731163, 0.976887, 1; 11:23:04.452 [planetinfo.record]: cloud_alpha = 1.000000; 11:23:04.452 [planetinfo.record]: cloud_color = 0.700195, 0.774956, 0.933594, 1; 11:23:04.452 [planetinfo.record]: cloud_fraction = 0.390000; 11:23:04.452 [planetinfo.record]: land_color = 0.523438, 0.419159, 0.51692, 1; 11:23:04.452 [planetinfo.record]: land_fraction = 0.280000; 11:23:04.452 [planetinfo.record]: polar_cloud_color = 0.776349, 0.8224, 0.920117, 1; 11:23:04.452 [planetinfo.record]: polar_land_color = 0.947656, 0.900459, 0.944706, 1; 11:23:04.452 [planetinfo.record]: polar_sea_color = 0.887779, 0.922461, 0.853096, 1; 11:23:04.452 [planetinfo.record]: sea_color = 0.658779, 0.775391, 0.542168, 1; 11:23:04.453 [planetinfo.record]: rotation_speed = 0.000638 11:23:04.453 [planetinfo.record]: planet zpos = 688320.000000 11:23:04.453 [planetinfo.record]: sun_radius = 119332.536621 11:23:04.453 [planetinfo.record]: sun_vector = -0.648 0.250 -0.720 11:23:04.453 [planetinfo.record]: sun_distance = 1147200 11:23:04.453 [planetinfo.record]: corona_flare = 0.071519 11:23:04.453 [planetinfo.record]: corona_hues = 0.997665 11:23:04.453 [planetinfo.record]: sun_color = 0.684564, 0.409583, 0.289085, 1 11:23:04.453 [planetinfo.record]: corona_shimmer = 0.439067 11:23:04.454 [planetinfo.record]: station_vector = 0.476 0.470 0.744 11:23:04.454 [planetinfo.record]: station = coriolis 11:23:09.465 [PLANETINFO OVER]: Done 11:23:09.466 [PLANETINFO LOGGING]: 0 179 11:23:10.471 [planetinfo.record]: seed = 116 164 77 77 11:23:10.471 [planetinfo.record]: coordinates = 164 134 11:23:10.471 [planetinfo.record]: government = 6; 11:23:10.471 [planetinfo.record]: economy = 6; 11:23:10.471 [planetinfo.record]: techlevel = 4; 11:23:10.471 [planetinfo.record]: population = 29; 11:23:10.471 [planetinfo.record]: productivity = 9280; 11:23:10.471 [planetinfo.record]: name = "Diedar"; 11:23:10.471 [planetinfo.record]: inhabitant = "Human Colonial"; 11:23:10.472 [planetinfo.record]: inhabitants = "Human Colonials"; 11:23:10.472 [planetinfo.record]: description = "This world is ravaged by dreadful civil war."; 11:23:10.520 [planetinfo.record]: air_color = 0.549666, 0.53431, 0.877377, 1; 11:23:10.520 [planetinfo.record]: cloud_alpha = 1.000000; 11:23:10.520 [planetinfo.record]: cloud_color = 0.324376, 0.270271, 0.634766, 1; 11:23:10.521 [planetinfo.record]: cloud_fraction = 0.350000; 11:23:10.521 [planetinfo.record]: land_color = 0.515409, 0.355949, 0.587891, 1; 11:23:10.521 [planetinfo.record]: land_fraction = 0.260000; 11:23:10.521 [planetinfo.record]: polar_cloud_color = 0.54554, 0.503687, 0.785645, 1; 11:23:10.521 [planetinfo.record]: polar_land_color = 0.9122, 0.848377, 0.941211, 1; 11:23:10.521 [planetinfo.record]: polar_sea_color = 0.925977, 0.874313, 0.832836, 1; 11:23:10.521 [planetinfo.record]: sea_color = 0.740234, 0.575033, 0.442406, 1; 11:23:10.521 [planetinfo.record]: rotation_speed = 0.003614 11:23:10.521 [planetinfo.record]: planet zpos = 630800.000000 11:23:10.521 [planetinfo.record]: sun_radius = 209854.385376 11:23:10.521 [planetinfo.record]: sun_vector = -0.922 -0.233 0.310 11:23:10.521 [planetinfo.record]: sun_distance = 1198520 11:23:10.521 [planetinfo.record]: corona_flare = 0.028027 11:23:10.521 [planetinfo.record]: corona_hues = 0.913513 11:23:10.521 [planetinfo.record]: sun_color = 0.722982, 0.79094, 0.802505, 1 11:23:10.522 [planetinfo.record]: corona_shimmer = 0.321794 11:23:10.522 [planetinfo.record]: station_vector = -0.374 -0.715 0.591 11:23:10.522 [planetinfo.record]: station = coriolis 11:23:15.135 [PLANETINFO OVER]: Done 11:23:15.136 [PLANETINFO LOGGING]: 0 180 11:23:16.139 [planetinfo.record]: seed = 192 47 51 18 11:23:16.140 [planetinfo.record]: coordinates = 47 106 11:23:16.140 [planetinfo.record]: government = 0; 11:23:16.140 [planetinfo.record]: economy = 2; 11:23:16.140 [planetinfo.record]: techlevel = 8; 11:23:16.140 [planetinfo.record]: population = 35; 11:23:16.140 [planetinfo.record]: productivity = 8960; 11:23:16.140 [planetinfo.record]: name = "Eninre"; 11:23:16.140 [planetinfo.record]: inhabitant = "Human Colonial"; 11:23:16.140 [planetinfo.record]: inhabitants = "Human Colonials"; 11:23:16.140 [planetinfo.record]: description = "The planet Eninre is cursed by deadly civil war."; 11:23:16.142 [planetinfo.record]: air_color = 0.727212, 0.627083, 0.845508, 1; 11:23:16.142 [planetinfo.record]: cloud_alpha = 1.000000; 11:23:16.142 [planetinfo.record]: cloud_color = 0.539062, 0.444305, 0.502048, 1; 11:23:16.142 [planetinfo.record]: cloud_fraction = 0.300000; 11:23:16.142 [planetinfo.record]: land_color = 0.234146, 0.269414, 0.644531, 1; 11:23:16.142 [planetinfo.record]: land_fraction = 0.410000; 11:23:16.142 [planetinfo.record]: polar_cloud_color = 0.742578, 0.660996, 0.71071, 1; 11:23:16.142 [planetinfo.record]: polar_land_color = 0.786627, 0.799425, 0.935547, 1; 11:23:16.142 [planetinfo.record]: polar_sea_color = 0.896777, 0.929883, 0.874126, 1; 11:23:16.142 [planetinfo.record]: sea_color = 0.60132, 0.701172, 0.533, 1; 11:23:16.143 [planetinfo.record]: rotation_speed = 0.003491 11:23:16.143 [planetinfo.record]: planet zpos = 405000.000000 11:23:16.143 [planetinfo.record]: sun_radius = 72440.757751 11:23:16.143 [planetinfo.record]: sun_vector = -0.773 0.075 -0.629 11:23:16.143 [planetinfo.record]: sun_distance = 708750 11:23:16.143 [planetinfo.record]: corona_flare = 0.006631 11:23:16.143 [planetinfo.record]: corona_hues = 0.657768 11:23:16.143 [planetinfo.record]: sun_color = 0.737448, 0.714211, 0.485649, 1 11:23:16.143 [planetinfo.record]: corona_shimmer = 0.380514 11:23:16.143 [planetinfo.record]: station_vector = -0.506 -0.844 0.176 11:23:16.143 [planetinfo.record]: station = coriolis 11:23:21.370 [PLANETINFO OVER]: Done 11:23:21.371 [PLANETINFO LOGGING]: 0 181 11:23:22.383 [planetinfo.record]: seed = 64 172 77 70 11:23:22.383 [planetinfo.record]: coordinates = 172 238 11:23:22.383 [planetinfo.record]: government = 0; 11:23:22.383 [planetinfo.record]: economy = 6; 11:23:22.383 [planetinfo.record]: techlevel = 1; 11:23:22.383 [planetinfo.record]: population = 11; 11:23:22.383 [planetinfo.record]: productivity = 1408; 11:23:22.383 [planetinfo.record]: name = "Bibe"; 11:23:22.383 [planetinfo.record]: inhabitant = "Human Colonial"; 11:23:22.384 [planetinfo.record]: inhabitants = "Human Colonials"; 11:23:22.384 [planetinfo.record]: description = "This world is most fabled for Bibeian lethal brandy but beset by evil disease."; 11:23:22.400 [planetinfo.record]: air_color = 0.693788, 0.587866, 0.872174, 1; 11:23:22.400 [planetinfo.record]: cloud_alpha = 1.000000; 11:23:22.400 [planetinfo.record]: cloud_color = 0.619141, 0.384544, 0.587983, 1; 11:23:22.400 [planetinfo.record]: cloud_fraction = 0.280000; 11:23:22.400 [planetinfo.record]: land_color = 0.118594, 0.393527, 0.66, 1; 11:23:22.400 [planetinfo.record]: land_fraction = 0.260000; 11:23:22.400 [planetinfo.record]: polar_cloud_color = 0.778613, 0.594225, 0.754124, 1; 11:23:22.400 [planetinfo.record]: polar_land_color = 0.742457, 0.839725, 0.934, 1; 11:23:22.400 [planetinfo.record]: polar_sea_color = 0.932953, 0.939062, 0.890184, 1; 11:23:22.400 [planetinfo.record]: sea_color = 0.593516, 0.609375, 0.482501, 1; 11:23:22.400 [planetinfo.record]: rotation_speed = 0.001581 11:23:22.400 [planetinfo.record]: planet zpos = 588120.000000 11:23:22.400 [planetinfo.record]: sun_radius = 120073.487549 11:23:22.400 [planetinfo.record]: sun_vector = -0.278 0.243 -0.929 11:23:22.400 [planetinfo.record]: sun_distance = 904800 11:23:22.400 [planetinfo.record]: corona_flare = 0.050725 11:23:22.401 [planetinfo.record]: corona_hues = 0.807747 11:23:22.401 [planetinfo.record]: sun_color = 0.733316, 0.762058, 0.811441, 1 11:23:22.401 [planetinfo.record]: corona_shimmer = 0.250457 11:23:22.401 [planetinfo.record]: station_vector = -0.676 0.489 0.551 11:23:22.401 [planetinfo.record]: station = coriolis 11:23:27.110 [PLANETINFO OVER]: Done 11:23:27.111 [PLANETINFO LOGGING]: 0 182 11:23:28.129 [planetinfo.record]: seed = 244 249 155 173 11:23:28.129 [planetinfo.record]: coordinates = 249 211 11:23:28.129 [planetinfo.record]: government = 6; 11:23:28.129 [planetinfo.record]: economy = 3; 11:23:28.129 [planetinfo.record]: techlevel = 8; 11:23:28.129 [planetinfo.record]: population = 42; 11:23:28.129 [planetinfo.record]: productivity = 23520; 11:23:28.129 [planetinfo.record]: name = "Diquxe"; 11:23:28.129 [planetinfo.record]: inhabitant = "Harmless Horned Lobster"; 11:23:28.129 [planetinfo.record]: inhabitants = "Harmless Horned Lobsters"; 11:23:28.129 [planetinfo.record]: description = "This planet is mildly noted for its ancient mountains but plagued by frequent earthquakes."; 11:23:28.148 [planetinfo.record]: air_color = 0.753806, 0.518459, 0.950871, 1; 11:23:28.148 [planetinfo.record]: cloud_alpha = 1.000000; 11:23:28.148 [planetinfo.record]: cloud_color = 0.855469, 0.2005, 0.430763, 1; 11:23:28.148 [planetinfo.record]: cloud_fraction = 0.080000; 11:23:28.148 [planetinfo.record]: land_color = 0.66, 0.253381, 0.128906, 1; 11:23:28.148 [planetinfo.record]: land_fraction = 0.470000; 11:23:28.148 [planetinfo.record]: polar_cloud_color = 0.884961, 0.461493, 0.610369, 1; 11:23:28.148 [planetinfo.record]: polar_land_color = 0.934, 0.790143, 0.746105, 1; 11:23:28.148 [planetinfo.record]: polar_sea_color = 0.943945, 0.887069, 0.893734, 1; 11:23:28.148 [planetinfo.record]: sea_color = 0.560547, 0.425446, 0.441278, 1; 11:23:28.148 [planetinfo.record]: rotation_speed = 0.002812 11:23:28.148 [planetinfo.record]: planet zpos = 958950.000000 11:23:28.148 [planetinfo.record]: sun_radius = 132850.629272 11:23:28.148 [planetinfo.record]: sun_vector = -0.972 -0.232 0.024 11:23:28.148 [planetinfo.record]: sun_distance = 1214670 11:23:28.148 [planetinfo.record]: corona_flare = 0.078288 11:23:28.148 [planetinfo.record]: corona_hues = 0.808960 11:23:28.148 [planetinfo.record]: sun_color = 0.76303, 0.819003, 0.827512, 1 11:23:28.148 [planetinfo.record]: corona_shimmer = 0.422966 11:23:28.149 [planetinfo.record]: station_vector = 0.452 0.419 0.787 11:23:28.149 [planetinfo.record]: station = coriolis 11:23:32.423 [PLANETINFO OVER]: Done 11:23:32.425 [PLANETINFO LOGGING]: 0 183 11:23:33.439 [planetinfo.record]: seed = 124 74 93 231 11:23:33.439 [planetinfo.record]: coordinates = 74 34 11:23:33.439 [planetinfo.record]: government = 7; 11:23:33.439 [planetinfo.record]: economy = 2; 11:23:33.439 [planetinfo.record]: techlevel = 11; 11:23:33.439 [planetinfo.record]: population = 54; 11:23:33.439 [planetinfo.record]: productivity = 38016; 11:23:33.439 [planetinfo.record]: name = "Sorace"; 11:23:33.439 [planetinfo.record]: inhabitant = "Human Colonial"; 11:23:33.439 [planetinfo.record]: inhabitants = "Human Colonials"; 11:23:33.439 [planetinfo.record]: description = "Sorace is cursed by deadly civil war."; 11:23:33.448 [planetinfo.record]: air_color = 0.769097, 0.495349, 0.971033, 1; 11:23:33.448 [planetinfo.record]: cloud_alpha = 1.000000; 11:23:33.448 [planetinfo.record]: cloud_color = 0.916016, 0.11808, 0.323798, 1; 11:23:33.448 [planetinfo.record]: cloud_fraction = 0.210000; 11:23:33.448 [planetinfo.record]: land_color = 0.66, 0.473187, 0.208828, 1; 11:23:33.448 [planetinfo.record]: land_fraction = 0.260000; 11:23:33.448 [planetinfo.record]: polar_cloud_color = 0.912207, 0.415571, 0.54361, 1; 11:23:33.448 [planetinfo.record]: polar_land_color = 0.934, 0.867908, 0.774381, 1; 11:23:33.448 [planetinfo.record]: polar_sea_color = 0.874805, 0.926562, 0.892597, 1; 11:23:33.448 [planetinfo.record]: sea_color = 0.570288, 0.734375, 0.626693, 1; 11:23:33.449 [planetinfo.record]: rotation_speed = 0.000859 11:23:33.449 [planetinfo.record]: planet zpos = 515020.000000 11:23:33.449 [planetinfo.record]: sun_radius = 139942.048035 11:23:33.449 [planetinfo.record]: sun_vector = -0.881 0.467 -0.079 11:23:33.449 [planetinfo.record]: sun_distance = 983220 11:23:33.449 [planetinfo.record]: corona_flare = 0.054576 11:23:33.449 [planetinfo.record]: corona_hues = 0.905045 11:23:33.449 [planetinfo.record]: sun_color = 0.667926, 0.656914, 0.65416, 1 11:23:33.449 [planetinfo.record]: corona_shimmer = 0.576485 11:23:33.449 [planetinfo.record]: station_vector = -0.980 0.195 0.033 11:23:33.449 [planetinfo.record]: station = dodecahedron 11:23:38.679 [PLANETINFO OVER]: Done 11:23:38.680 [PLANETINFO LOGGING]: 0 184 11:23:39.694 [planetinfo.record]: seed = 88 193 83 155 11:23:39.694 [planetinfo.record]: coordinates = 193 133 11:23:39.694 [planetinfo.record]: government = 3; 11:23:39.694 [planetinfo.record]: economy = 5; 11:23:39.694 [planetinfo.record]: techlevel = 5; 11:23:39.694 [planetinfo.record]: population = 29; 11:23:39.694 [planetinfo.record]: productivity = 8120; 11:23:39.694 [planetinfo.record]: name = "Anxeonis"; 11:23:39.694 [planetinfo.record]: inhabitant = "Human Colonial"; 11:23:39.694 [planetinfo.record]: inhabitants = "Human Colonials"; 11:23:39.694 [planetinfo.record]: description = "The planet Anxeonis is most famous for its vast rain forests."; 11:23:39.703 [planetinfo.record]: air_color = 0.807435, 0.497395, 0.977537, 1; 11:23:39.705 [planetinfo.record]: cloud_alpha = 1.000000; 11:23:39.705 [planetinfo.record]: cloud_color = 0.935547, 0.116943, 0.155315, 1; 11:23:39.705 [planetinfo.record]: cloud_fraction = 0.200000; 11:23:39.705 [planetinfo.record]: land_color = 0.66, 0.195938, 0.337332, 1; 11:23:39.705 [planetinfo.record]: land_fraction = 0.690000; 11:23:39.705 [planetinfo.record]: polar_cloud_color = 0.920996, 0.417326, 0.440936, 1; 11:23:39.705 [planetinfo.record]: polar_land_color = 0.934, 0.76982, 0.819844, 1; 11:23:39.705 [planetinfo.record]: polar_sea_color = 0.934315, 0.938281, 0.887519, 1; 11:23:39.705 [planetinfo.record]: sea_color = 0.606753, 0.617188, 0.483624, 1; 11:23:39.705 [planetinfo.record]: rotation_speed = 0.003264 11:23:39.705 [planetinfo.record]: planet zpos = 757250.000000 11:23:39.705 [planetinfo.record]: sun_radius = 152084.087372 11:23:39.706 [planetinfo.record]: sun_vector = 0.165 0.961 -0.222 11:23:39.706 [planetinfo.record]: sun_distance = 1165000 11:23:39.706 [planetinfo.record]: corona_flare = 0.040735 11:23:39.706 [planetinfo.record]: corona_hues = 0.723778 11:23:39.706 [planetinfo.record]: sun_color = 0.585787, 0.714842, 0.789575, 1 11:23:39.706 [planetinfo.record]: corona_shimmer = 0.422742 11:23:39.706 [planetinfo.record]: station_vector = 0.982 -0.058 0.178 11:23:39.706 [planetinfo.record]: station = coriolis 11:23:45.296 [PLANETINFO OVER]: Done 11:23:45.297 [PLANETINFO LOGGING]: 0 185 11:23:46.300 [planetinfo.record]: seed = 40 189 253 222 11:23:46.300 [planetinfo.record]: coordinates = 189 63 11:23:46.300 [planetinfo.record]: government = 5; 11:23:46.300 [planetinfo.record]: economy = 7; 11:23:46.300 [planetinfo.record]: techlevel = 4; 11:23:46.300 [planetinfo.record]: population = 29; 11:23:46.300 [planetinfo.record]: productivity = 6264; 11:23:46.300 [planetinfo.record]: name = "Riantiat"; 11:23:46.300 [planetinfo.record]: inhabitant = "Horned Bird"; 11:23:46.300 [planetinfo.record]: inhabitants = "Horned Birds"; 11:23:46.300 [planetinfo.record]: description = "This planet is notable for the Riantiatian edible grub and the Riantiatian spotted wolf."; 11:23:46.316 [planetinfo.record]: air_color = 0.587265, 0.575529, 0.837703, 1; 11:23:46.316 [planetinfo.record]: cloud_alpha = 1.000000; 11:23:46.316 [planetinfo.record]: cloud_color = 0.366404, 0.340393, 0.515625, 1; 11:23:46.316 [planetinfo.record]: cloud_fraction = 0.420000; 11:23:46.316 [planetinfo.record]: land_color = 0.66, 0.630815, 0.497578, 1; 11:23:46.316 [planetinfo.record]: land_fraction = 0.360000; 11:23:46.316 [planetinfo.record]: polar_cloud_color = 0.599626, 0.576546, 0.732031, 1; 11:23:46.316 [planetinfo.record]: polar_land_color = 0.934, 0.923675, 0.876537, 1; 11:23:46.316 [planetinfo.record]: polar_sea_color = 0.869531, 0.879171, 0.916992, 1; 11:23:46.316 [planetinfo.record]: sea_color = 0.658226, 0.693133, 0.830078, 1; 11:23:46.316 [planetinfo.record]: rotation_speed = 0.001341 11:23:46.316 [planetinfo.record]: planet zpos = 790680.000000 11:23:46.316 [planetinfo.record]: sun_radius = 156516.901245 11:23:46.316 [planetinfo.record]: sun_vector = 0.486 -0.183 -0.854 11:23:46.316 [planetinfo.record]: sun_distance = 1251910 11:23:46.316 [planetinfo.record]: corona_flare = 0.048679 11:23:46.316 [planetinfo.record]: corona_hues = 0.876488 11:23:46.316 [planetinfo.record]: sun_color = 0.530332, 0.760824, 0.764386, 1 11:23:46.316 [planetinfo.record]: corona_shimmer = 0.270960 11:23:46.316 [planetinfo.record]: station_vector = 0.970 -0.235 0.059 11:23:46.316 [planetinfo.record]: station = coriolis 11:23:51.094 [PLANETINFO OVER]: Done 11:23:51.095 [PLANETINFO LOGGING]: 0 186 11:23:52.099 [planetinfo.record]: seed = 236 49 219 132 11:23:52.099 [planetinfo.record]: coordinates = 49 119 11:23:52.099 [planetinfo.record]: government = 5; 11:23:52.099 [planetinfo.record]: economy = 7; 11:23:52.099 [planetinfo.record]: techlevel = 4; 11:23:52.099 [planetinfo.record]: population = 29; 11:23:52.099 [planetinfo.record]: productivity = 6264; 11:23:52.099 [planetinfo.record]: name = "Zarece"; 11:23:52.099 [planetinfo.record]: inhabitant = "Fierce Black Feline"; 11:23:52.099 [planetinfo.record]: inhabitants = "Fierce Black Felines"; 11:23:52.099 [planetinfo.record]: description = "This planet is a tedious place."; 11:23:52.116 [planetinfo.record]: air_color = 0.710238, 0.729236, 0.972984, 1; 11:23:52.116 [planetinfo.record]: cloud_alpha = 1.000000; 11:23:52.116 [planetinfo.record]: cloud_color = 0.727417, 0.766916, 0.921875, 1; 11:23:52.116 [planetinfo.record]: cloud_fraction = 0.220000; 11:23:52.116 [planetinfo.record]: land_color = 0.0154688, 0.312557, 0.66, 1; 11:23:52.116 [planetinfo.record]: land_fraction = 0.620000; 11:23:52.116 [planetinfo.record]: polar_cloud_color = 0.794234, 0.818733, 0.914844, 1; 11:23:52.116 [planetinfo.record]: polar_land_color = 0.705973, 0.811079, 0.934, 1; 11:23:52.116 [planetinfo.record]: polar_sea_color = 0.940234, 0.933515, 0.889642, 1; 11:23:52.116 [planetinfo.record]: sea_color = 0.597656, 0.580572, 0.46902, 1; 11:23:52.116 [planetinfo.record]: rotation_speed = 0.004950 11:23:52.116 [planetinfo.record]: planet zpos = 544460.000000 11:23:52.116 [planetinfo.record]: sun_radius = 97781.029205 11:23:52.116 [planetinfo.record]: sun_vector = -0.364 0.647 0.669 11:23:52.117 [planetinfo.record]: sun_distance = 816690 11:23:52.117 [planetinfo.record]: corona_flare = 0.046735 11:23:52.117 [planetinfo.record]: corona_hues = 0.906525 11:23:52.117 [planetinfo.record]: sun_color = 0.845078, 0.548207, 0.409982, 1 11:23:52.117 [planetinfo.record]: corona_shimmer = 0.263485 11:23:52.117 [planetinfo.record]: station_vector = 0.958 -0.088 0.272 11:23:52.117 [planetinfo.record]: station = coriolis 11:23:57.086 [PLANETINFO OVER]: Done 11:23:57.087 [PLANETINFO LOGGING]: 0 187 11:23:58.107 [planetinfo.record]: seed = 68 152 173 171 11:23:58.107 [planetinfo.record]: coordinates = 152 229 11:23:58.107 [planetinfo.record]: government = 0; 11:23:58.107 [planetinfo.record]: economy = 7; 11:23:58.107 [planetinfo.record]: techlevel = 0; 11:23:58.107 [planetinfo.record]: population = 8; 11:23:58.107 [planetinfo.record]: productivity = 768; 11:23:58.107 [planetinfo.record]: name = "Maesin"; 11:23:58.107 [planetinfo.record]: inhabitant = "Small Harmless Furry Rodent"; 11:23:58.107 [planetinfo.record]: inhabitants = "Small Harmless Furry Rodents"; 11:23:58.107 [planetinfo.record]: description = "The planet Maesin is an unremarkable dump."; 11:23:58.111 [planetinfo.record]: air_color = 0.766631, 0.739562, 0.974936, 1; 11:23:58.111 [planetinfo.record]: cloud_alpha = 1.000000; 11:23:58.111 [planetinfo.record]: cloud_color = 0.853443, 0.811768, 0.927734, 1; 11:23:58.111 [planetinfo.record]: cloud_fraction = 0.190000; 11:23:58.111 [planetinfo.record]: land_color = 0.66, 0.414957, 0.252656, 1; 11:23:58.111 [planetinfo.record]: land_fraction = 0.440000; 11:23:58.112 [planetinfo.record]: polar_cloud_color = 0.871562, 0.845802, 0.91748, 1; 11:23:58.112 [planetinfo.record]: polar_land_color = 0.934, 0.847307, 0.789887, 1; 11:23:58.112 [planetinfo.record]: polar_sea_color = 0.894591, 0.934375, 0.887656, 1; 11:23:58.112 [planetinfo.record]: sea_color = 0.544482, 0.65625, 0.525, 1; 11:23:58.112 [planetinfo.record]: rotation_speed = 0.003031 11:23:58.112 [planetinfo.record]: planet zpos = 867600.000000 11:23:58.112 [planetinfo.record]: sun_radius = 112887.553711 11:23:58.112 [planetinfo.record]: sun_vector = 0.860 0.039 0.510 11:23:58.112 [planetinfo.record]: sun_distance = 1156800 11:23:58.112 [planetinfo.record]: corona_flare = 0.048651 11:23:58.113 [planetinfo.record]: corona_hues = 0.571228 11:23:58.113 [planetinfo.record]: sun_color = 0.300605, 0.346934, 0.718777, 1 11:23:58.113 [planetinfo.record]: corona_shimmer = 0.313780 11:23:58.113 [planetinfo.record]: station_vector = 0.509 0.860 0.039 11:23:58.113 [planetinfo.record]: station = coriolis 11:24:03.520 [PLANETINFO OVER]: Done 11:24:03.521 [PLANETINFO LOGGING]: 0 188 11:24:04.524 [planetinfo.record]: seed = 176 65 179 215 11:24:04.525 [planetinfo.record]: coordinates = 65 108 11:24:04.525 [planetinfo.record]: government = 6; 11:24:04.525 [planetinfo.record]: economy = 4; 11:24:04.525 [planetinfo.record]: techlevel = 7; 11:24:04.525 [planetinfo.record]: population = 39; 11:24:04.525 [planetinfo.record]: productivity = 18720; 11:24:04.525 [planetinfo.record]: name = "Tibionis"; 11:24:04.525 [planetinfo.record]: inhabitant = "Furry Rodent"; 11:24:04.525 [planetinfo.record]: inhabitants = "Furry Rodents"; 11:24:04.525 [planetinfo.record]: description = "Tibionis is most noted for the Tibionisian deadly goat and its vast rain forests."; 11:24:04.552 [planetinfo.record]: air_color = 0.759904, 0.597233, 0.887783, 1; 11:24:04.552 [planetinfo.record]: cloud_alpha = 1.000000; 11:24:04.552 [planetinfo.record]: cloud_color = 0.666016, 0.411057, 0.488739, 1; 11:24:04.552 [planetinfo.record]: cloud_fraction = 0.420000; 11:24:04.552 [planetinfo.record]: land_color = 0.564609, 0.586221, 0.66, 1; 11:24:04.553 [planetinfo.record]: land_fraction = 0.490000; 11:24:04.553 [planetinfo.record]: polar_cloud_color = 0.799707, 0.608371, 0.666669, 1; 11:24:04.553 [planetinfo.record]: polar_land_color = 0.900252, 0.907898, 0.934, 1; 11:24:04.553 [planetinfo.record]: polar_sea_color = 0.938672, 0.921172, 0.884038, 1; 11:24:04.553 [planetinfo.record]: sea_color = 0.613281, 0.567547, 0.470502, 1; 11:24:04.553 [planetinfo.record]: rotation_speed = 0.002960 11:24:04.553 [planetinfo.record]: planet zpos = 514030.000000 11:24:04.553 [planetinfo.record]: sun_radius = 119260.755615 11:24:04.553 [planetinfo.record]: sun_vector = 0.052 0.244 0.968 11:24:04.553 [planetinfo.record]: sun_distance = 1028060 11:24:04.553 [planetinfo.record]: corona_flare = 0.088710 11:24:04.553 [planetinfo.record]: corona_hues = 0.754234 11:24:04.553 [planetinfo.record]: sun_color = 0.694278, 0.640932, 0.630945, 1 11:24:04.554 [planetinfo.record]: corona_shimmer = 0.407385 11:24:04.555 [planetinfo.record]: station_vector = 0.810 -0.586 0.009 11:24:04.555 [planetinfo.record]: station = coriolis 11:24:09.804 [PLANETINFO OVER]: Done 11:24:09.805 [PLANETINFO LOGGING]: 0 189 11:24:10.826 [planetinfo.record]: seed = 208 253 237 35 11:24:10.826 [planetinfo.record]: coordinates = 253 159 11:24:10.826 [planetinfo.record]: government = 2; 11:24:10.826 [planetinfo.record]: economy = 7; 11:24:10.826 [planetinfo.record]: techlevel = 2; 11:24:10.826 [planetinfo.record]: population = 18; 11:24:10.826 [planetinfo.record]: productivity = 2592; 11:24:10.826 [planetinfo.record]: name = "Gelegeus"; 11:24:10.826 [planetinfo.record]: inhabitant = "Large Red Horned Humanoid"; 11:24:10.826 [planetinfo.record]: inhabitants = "Large Red Horned Humanoids"; 11:24:10.826 [planetinfo.record]: description = "Gelegeus is mildly notable for Gelegeusian Di water."; 11:24:10.836 [planetinfo.record]: air_color = 0.456424, 0.707184, 0.861768, 1; 11:24:10.837 [planetinfo.record]: cloud_alpha = 1.000000; 11:24:10.837 [planetinfo.record]: cloud_color = 0.110229, 0.587891, 0.184864, 1; 11:24:10.837 [planetinfo.record]: cloud_fraction = 0.480000; 11:24:10.837 [planetinfo.record]: land_color = 0.414957, 0.66, 0.262969, 1; 11:24:10.837 [planetinfo.record]: land_fraction = 0.570000; 11:24:10.837 [planetinfo.record]: polar_cloud_color = 0.376302, 0.764551, 0.436966, 1; 11:24:10.837 [planetinfo.record]: polar_land_color = 0.847307, 0.934, 0.793535, 1; 11:24:10.838 [planetinfo.record]: polar_sea_color = 0.938427, 0.941016, 0.893689, 1; 11:24:10.838 [planetinfo.record]: sea_color = 0.583355, 0.589844, 0.471184, 1; 11:24:10.838 [planetinfo.record]: rotation_speed = 0.000953 11:24:10.838 [planetinfo.record]: planet zpos = 498810.000000 11:24:10.838 [planetinfo.record]: sun_radius = 98464.811096 11:24:10.838 [planetinfo.record]: sun_vector = 0.983 -0.178 -0.035 11:24:10.838 [planetinfo.record]: sun_distance = 729030 11:24:10.838 [planetinfo.record]: corona_flare = 0.064738 11:24:10.838 [planetinfo.record]: corona_hues = 0.820419 11:24:10.838 [planetinfo.record]: sun_color = 0.832465, 0.815851, 0.808752, 1 11:24:10.839 [planetinfo.record]: corona_shimmer = 0.282237 11:24:10.839 [planetinfo.record]: station_vector = -0.062 0.898 0.435 11:24:10.839 [planetinfo.record]: station = coriolis 11:24:15.848 [PLANETINFO OVER]: Done 11:24:15.848 [PLANETINFO LOGGING]: 0 190 11:24:16.867 [planetinfo.record]: seed = 164 200 91 109 11:24:16.867 [planetinfo.record]: coordinates = 200 227 11:24:16.867 [planetinfo.record]: government = 4; 11:24:16.867 [planetinfo.record]: economy = 3; 11:24:16.867 [planetinfo.record]: techlevel = 6; 11:24:16.867 [planetinfo.record]: population = 32; 11:24:16.867 [planetinfo.record]: productivity = 14336; 11:24:16.868 [planetinfo.record]: name = "Diora"; 11:24:16.868 [planetinfo.record]: inhabitant = "Human Colonial"; 11:24:16.868 [planetinfo.record]: inhabitants = "Human Colonials"; 11:24:16.868 [planetinfo.record]: description = "The planet Diora is an unremarkable planet."; 11:24:16.884 [planetinfo.record]: air_color = 0.705498, 0.772674, 0.94957, 1; 11:24:16.884 [planetinfo.record]: cloud_alpha = 1.000000; 11:24:16.884 [planetinfo.record]: cloud_color = 0.7052, 0.82412, 0.851562, 1; 11:24:16.884 [planetinfo.record]: cloud_fraction = 0.210000; 11:24:16.884 [planetinfo.record]: land_color = 0.417466, 0.523438, 0.0858765, 1; 11:24:16.884 [planetinfo.record]: land_fraction = 0.290000; 11:24:16.884 [planetinfo.record]: polar_cloud_color = 0.788328, 0.865414, 0.883203, 1; 11:24:16.884 [planetinfo.record]: polar_land_color = 0.899692, 0.947656, 0.749611, 1; 11:24:16.884 [planetinfo.record]: polar_sea_color = 0.937891, 0.868282, 0.892754, 1; 11:24:16.884 [planetinfo.record]: sea_color = 0.621094, 0.436707, 0.50153, 1; 11:24:16.884 [planetinfo.record]: rotation_speed = 0.002150 11:24:16.884 [planetinfo.record]: planet zpos = 824720.000000 11:24:16.884 [planetinfo.record]: sun_radius = 188303.619385 11:24:16.885 [planetinfo.record]: sun_vector = -0.714 0.204 -0.669 11:24:16.885 [planetinfo.record]: sun_distance = 1268800 11:24:16.885 [planetinfo.record]: corona_flare = 0.033260 11:24:16.885 [planetinfo.record]: corona_hues = 0.889404 11:24:16.885 [planetinfo.record]: sun_color = 0.578636, 0.627863, 0.70488, 1 11:24:16.885 [planetinfo.record]: corona_shimmer = 0.402397 11:24:16.885 [planetinfo.record]: station_vector = -0.443 -0.895 0.054 11:24:16.885 [planetinfo.record]: station = coriolis 11:24:21.739 [PLANETINFO OVER]: Done 11:24:21.740 [PLANETINFO LOGGING]: 0 191 11:24:22.742 [planetinfo.record]: seed = 204 213 61 62 11:24:22.742 [planetinfo.record]: coordinates = 213 79 11:24:22.742 [planetinfo.record]: government = 1; 11:24:22.742 [planetinfo.record]: economy = 7; 11:24:22.742 [planetinfo.record]: techlevel = 2; 11:24:22.742 [planetinfo.record]: population = 17; 11:24:22.742 [planetinfo.record]: productivity = 2040; 11:24:22.742 [planetinfo.record]: name = "Rigeti"; 11:24:22.742 [planetinfo.record]: inhabitant = "Human Colonial"; 11:24:22.742 [planetinfo.record]: inhabitants = "Human Colonials"; 11:24:22.742 [planetinfo.record]: description = "Rigeti is a revolting dump."; 11:24:22.743 [planetinfo.record]: air_color = 0.538195, 0.957375, 0.842424, 1; 11:24:22.743 [planetinfo.record]: cloud_alpha = 1.000000; 11:24:22.743 [planetinfo.record]: cloud_color = 0.875, 0.366791, 0.249512, 1; 11:24:22.744 [planetinfo.record]: cloud_fraction = 0.620000; 11:24:22.744 [planetinfo.record]: land_color = 0.538748, 0.105703, 0.66, 1; 11:24:22.744 [planetinfo.record]: land_fraction = 0.430000; 11:24:22.744 [planetinfo.record]: polar_cloud_color = 0.89375, 0.569313, 0.494443, 1; 11:24:22.744 [planetinfo.record]: polar_land_color = 0.891102, 0.737897, 0.934, 1; 11:24:22.744 [planetinfo.record]: polar_sea_color = 0.943555, 0.893785, 0.888637, 1; 11:24:22.744 [planetinfo.record]: sea_color = 0.564453, 0.445361, 0.433041, 1; 11:24:22.744 [planetinfo.record]: rotation_speed = 0.000177 11:24:22.744 [planetinfo.record]: planet zpos = 595170.000000 11:24:22.744 [planetinfo.record]: sun_radius = 146372.765198 11:24:22.744 [planetinfo.record]: sun_vector = 0.146 0.851 -0.504 11:24:22.744 [planetinfo.record]: sun_distance = 1190340 11:24:22.744 [planetinfo.record]: corona_flare = 0.014296 11:24:22.744 [planetinfo.record]: corona_hues = 0.832207 11:24:22.744 [planetinfo.record]: sun_color = 0.405271, 0.48122, 0.726791, 1 11:24:22.745 [planetinfo.record]: corona_shimmer = 0.323695 11:24:22.745 [planetinfo.record]: station_vector = -0.750 0.385 0.538 11:24:22.745 [planetinfo.record]: station = coriolis 11:24:27.640 [PLANETINFO OVER]: Done 11:24:27.641 [PLANETINFO LOGGING]: 0 192 11:24:28.661 [planetinfo.record]: seed = 200 24 83 243 11:24:28.661 [planetinfo.record]: coordinates = 24 119 11:24:28.661 [planetinfo.record]: government = 1; 11:24:28.661 [planetinfo.record]: economy = 7; 11:24:28.661 [planetinfo.record]: techlevel = 1; 11:24:28.661 [planetinfo.record]: population = 13; 11:24:28.661 [planetinfo.record]: productivity = 1560; 11:24:28.661 [planetinfo.record]: name = "Begeabi"; 11:24:28.661 [planetinfo.record]: inhabitant = "Human Colonial"; 11:24:28.662 [planetinfo.record]: inhabitants = "Human Colonials"; 11:24:28.662 [planetinfo.record]: description = "Begeabi is very notable for its inhabitants’ weird silliness."; 11:24:28.663 [planetinfo.record]: air_color = 0.612589, 0.504984, 0.926156, 1; 11:24:28.663 [planetinfo.record]: cloud_alpha = 1.000000; 11:24:28.663 [planetinfo.record]: cloud_color = 0.650406, 0.183105, 0.78125, 1; 11:24:28.663 [planetinfo.record]: cloud_fraction = 0.220000; 11:24:28.663 [planetinfo.record]: land_color = 0.573552, 0.66, 0.469219, 1; 11:24:28.663 [planetinfo.record]: land_fraction = 0.350000; 11:24:28.663 [planetinfo.record]: polar_cloud_color = 0.762425, 0.444077, 0.851562, 1; 11:24:28.663 [planetinfo.record]: polar_land_color = 0.903416, 0.934, 0.866504, 1; 11:24:28.664 [planetinfo.record]: polar_sea_color = 0.937537, 0.853924, 0.940234, 1; 11:24:28.664 [planetinfo.record]: sea_color = 0.590798, 0.378204, 0.597656, 1; 11:24:28.664 [planetinfo.record]: rotation_speed = 0.002653 11:24:28.664 [planetinfo.record]: planet zpos = 360800.000000 11:24:28.664 [planetinfo.record]: sun_radius = 61865.506592 11:24:28.664 [planetinfo.record]: sun_vector = -0.124 -0.459 -0.880 11:24:28.664 [planetinfo.record]: sun_distance = 577280 11:24:28.664 [planetinfo.record]: corona_flare = 0.090060 11:24:28.664 [planetinfo.record]: corona_hues = 0.662354 11:24:28.664 [planetinfo.record]: sun_color = 0.660519, 0.391961, 0.386731, 1 11:24:28.665 [planetinfo.record]: corona_shimmer = 0.600498 11:24:28.665 [planetinfo.record]: station_vector = -0.180 -0.944 0.275 11:24:28.665 [planetinfo.record]: station = coriolis 11:24:33.522 [PLANETINFO OVER]: Done 11:24:33.524 [PLANETINFO LOGGING]: 0 193 11:24:34.545 [planetinfo.record]: seed = 56 6 29 25 11:24:34.545 [planetinfo.record]: coordinates = 6 143 11:24:34.545 [planetinfo.record]: government = 7; 11:24:34.545 [planetinfo.record]: economy = 7; 11:24:34.545 [planetinfo.record]: techlevel = 6; 11:24:34.545 [planetinfo.record]: population = 39; 11:24:34.545 [planetinfo.record]: productivity = 10296; 11:24:34.545 [planetinfo.record]: name = "Orrere"; 11:24:34.545 [planetinfo.record]: inhabitant = "Human Colonial"; 11:24:34.545 [planetinfo.record]: inhabitants = "Human Colonials"; 11:24:34.545 [planetinfo.record]: description = "Orrere is mildly well known for Orrereian vicious brew."; 11:24:34.547 [planetinfo.record]: air_color = 0.717389, 0.618929, 0.84941, 1; 11:24:34.547 [planetinfo.record]: cloud_alpha = 1.000000; 11:24:34.547 [planetinfo.record]: cloud_color = 0.550781, 0.432449, 0.515651, 1; 11:24:34.547 [planetinfo.record]: cloud_fraction = 0.200000; 11:24:34.548 [planetinfo.record]: land_color = 0.511719, 0.419769, 0.508127, 1; 11:24:34.548 [planetinfo.record]: land_fraction = 0.550000; 11:24:34.548 [planetinfo.record]: polar_cloud_color = 0.747852, 0.647432, 0.71804, 1; 11:24:34.548 [planetinfo.record]: polar_land_color = 0.948828, 0.906205, 0.947163, 1; 11:24:34.548 [planetinfo.record]: polar_sea_color = 0.914569, 0.927734, 0.873375, 1; 11:24:34.548 [planetinfo.record]: sea_color = 0.681636, 0.722656, 0.553284, 1; 11:24:34.548 [planetinfo.record]: rotation_speed = 0.000598 11:24:34.548 [planetinfo.record]: planet zpos = 615120.000000 11:24:34.548 [planetinfo.record]: sun_radius = 106268.919373 11:24:34.548 [planetinfo.record]: sun_vector = -0.008 0.926 -0.377 11:24:34.548 [planetinfo.record]: sun_distance = 922680 11:24:34.548 [planetinfo.record]: corona_flare = 0.003983 11:24:34.548 [planetinfo.record]: corona_hues = 0.780128 11:24:34.548 [planetinfo.record]: sun_color = 0.712302, 0.592113, 0.542845, 1 11:24:34.548 [planetinfo.record]: corona_shimmer = 0.442739 11:24:34.549 [planetinfo.record]: station_vector = -0.521 -0.665 0.535 11:24:34.549 [planetinfo.record]: station = coriolis 11:24:39.434 [PLANETINFO OVER]: Done 11:24:39.434 [PLANETINFO LOGGING]: 0 194 11:24:40.448 [planetinfo.record]: seed = 28 150 27 19 11:24:40.449 [planetinfo.record]: coordinates = 150 206 11:24:40.449 [planetinfo.record]: government = 3; 11:24:40.449 [planetinfo.record]: economy = 6; 11:24:40.449 [planetinfo.record]: techlevel = 5; 11:24:40.449 [planetinfo.record]: population = 30; 11:24:40.449 [planetinfo.record]: productivity = 6720; 11:24:40.449 [planetinfo.record]: name = "Beti"; 11:24:40.449 [planetinfo.record]: inhabitant = "Human Colonial"; 11:24:40.449 [planetinfo.record]: inhabitants = "Human Colonials"; 11:24:40.449 [planetinfo.record]: description = "This planet is fabled for its weird volcanoes and the Betiian mountain lobstoid."; 11:24:40.473 [planetinfo.record]: air_color = 0.702883, 0.830226, 0.89884, 1; 11:24:40.473 [planetinfo.record]: cloud_alpha = 1.000000; 11:24:40.473 [planetinfo.record]: cloud_color = 0.66098, 0.699219, 0.66337, 1; 11:24:40.473 [planetinfo.record]: cloud_fraction = 0.390000; 11:24:40.473 [planetinfo.record]: land_color = 0.193771, 0.550326, 0.597656, 1; 11:24:40.473 [planetinfo.record]: land_fraction = 0.490000; 11:24:40.473 [planetinfo.record]: polar_cloud_color = 0.786804, 0.814648, 0.788544, 1; 11:24:40.473 [planetinfo.record]: polar_land_color = 0.781386, 0.921619, 0.940234, 1; 11:24:40.473 [planetinfo.record]: polar_sea_color = 0.926758, 0.845135, 0.826299, 1; 11:24:40.473 [planetinfo.record]: sea_color = 0.732422, 0.474393, 0.414848, 1; 11:24:40.473 [planetinfo.record]: rotation_speed = 0.004289 11:24:40.473 [planetinfo.record]: planet zpos = 336060.000000 11:24:40.474 [planetinfo.record]: sun_radius = 106301.856689 11:24:40.474 [planetinfo.record]: sun_vector = 0.263 0.175 0.949 11:24:40.474 [planetinfo.record]: sun_distance = 784140 11:24:40.474 [planetinfo.record]: corona_flare = 0.099870 11:24:40.474 [planetinfo.record]: corona_hues = 0.683487 11:24:40.474 [planetinfo.record]: sun_color = 0.744428, 0.598884, 0.409679, 1 11:24:40.474 [planetinfo.record]: corona_shimmer = 0.337895 11:24:40.474 [planetinfo.record]: station_vector = -0.732 -0.681 0.018 11:24:40.475 [planetinfo.record]: station = coriolis 11:24:45.240 [PLANETINFO OVER]: Done 11:24:45.241 [PLANETINFO LOGGING]: 0 195 11:24:46.244 [planetinfo.record]: seed = 20 171 13 67 11:24:46.244 [planetinfo.record]: coordinates = 171 32 11:24:46.244 [planetinfo.record]: government = 2; 11:24:46.244 [planetinfo.record]: economy = 0; 11:24:46.244 [planetinfo.record]: techlevel = 11; 11:24:46.244 [planetinfo.record]: population = 47; 11:24:46.244 [planetinfo.record]: productivity = 22560; 11:24:46.244 [planetinfo.record]: name = "Gerete"; 11:24:46.244 [planetinfo.record]: inhabitant = "Human Colonial"; 11:24:46.244 [planetinfo.record]: inhabitants = "Human Colonials"; 11:24:46.244 [planetinfo.record]: description = "This world is most fabled for Zero-G cricket but cursed by unpredictable solar activity."; 11:24:46.255 [planetinfo.record]: air_color = 0.682134, 0.747012, 0.976236, 1; 11:24:46.256 [planetinfo.record]: cloud_alpha = 1.000000; 11:24:46.256 [planetinfo.record]: cloud_color = 0.647781, 0.831846, 0.931641, 1; 11:24:46.256 [planetinfo.record]: cloud_fraction = 0.280000; 11:24:46.256 [planetinfo.record]: land_color = 0.376064, 0.66, 0.373828, 1; 11:24:46.256 [planetinfo.record]: land_fraction = 0.700000; 11:24:46.256 [planetinfo.record]: polar_cloud_color = 0.744188, 0.857697, 0.919238, 1; 11:24:46.256 [planetinfo.record]: polar_land_color = 0.833547, 0.934, 0.832756, 1; 11:24:46.256 [planetinfo.record]: polar_sea_color = 0.932031, 0.929678, 0.871777, 1; 11:24:46.257 [planetinfo.record]: sea_color = 0.679688, 0.672822, 0.503925, 1; 11:24:46.257 [planetinfo.record]: rotation_speed = 0.002361 11:24:46.257 [planetinfo.record]: planet zpos = 450600.000000 11:24:46.257 [planetinfo.record]: sun_radius = 101566.516113 11:24:46.257 [planetinfo.record]: sun_vector = -0.651 -0.419 -0.633 11:24:46.258 [planetinfo.record]: sun_distance = 713450 11:24:46.258 [planetinfo.record]: corona_flare = 0.015991 11:24:46.259 [planetinfo.record]: corona_hues = 0.809784 11:24:46.259 [planetinfo.record]: sun_color = 0.669891, 0.594547, 0.552106, 1 11:24:46.259 [planetinfo.record]: corona_shimmer = 0.616450 11:24:46.260 [planetinfo.record]: station_vector = -0.829 0.201 0.522 11:24:46.260 [planetinfo.record]: station = dodecahedron 11:24:51.550 [PLANETINFO OVER]: Done 11:24:51.551 [PLANETINFO LOGGING]: 0 196 11:24:52.561 [planetinfo.record]: seed = 160 78 51 90 11:24:52.561 [planetinfo.record]: coordinates = 78 252 11:24:52.561 [planetinfo.record]: government = 4; 11:24:52.561 [planetinfo.record]: economy = 4; 11:24:52.561 [planetinfo.record]: techlevel = 7; 11:24:52.561 [planetinfo.record]: population = 37; 11:24:52.561 [planetinfo.record]: productivity = 14208; 11:24:52.561 [planetinfo.record]: name = "Qucerere"; 11:24:52.561 [planetinfo.record]: inhabitant = "Human Colonial"; 11:24:52.561 [planetinfo.record]: inhabitants = "Human Colonials"; 11:24:52.561 [planetinfo.record]: description = "This planet is a tedious place."; 11:24:52.584 [planetinfo.record]: air_color = 0.76968, 0.720492, 0.997699, 1; 11:24:52.584 [planetinfo.record]: cloud_alpha = 1.000000; 11:24:52.584 [planetinfo.record]: cloud_color = 0.890307, 0.762634, 0.996094, 1; 11:24:52.584 [planetinfo.record]: cloud_fraction = 0.370000; 11:24:52.584 [planetinfo.record]: land_color = 0.587954, 0.66, 0.471797, 1; 11:24:52.584 [planetinfo.record]: land_fraction = 0.480000; 11:24:52.584 [planetinfo.record]: polar_cloud_color = 0.885302, 0.80934, 0.948242, 1; 11:24:52.584 [planetinfo.record]: polar_land_color = 0.908511, 0.934, 0.867416, 1; 11:24:52.584 [planetinfo.record]: polar_sea_color = 0.929182, 0.868356, 0.943945, 1; 11:24:52.584 [planetinfo.record]: sea_color = 0.525478, 0.380997, 0.560547, 1; 11:24:52.584 [planetinfo.record]: rotation_speed = 0.002279 11:24:52.584 [planetinfo.record]: planet zpos = 654480.000000 11:24:52.584 [planetinfo.record]: sun_radius = 119946.222839 11:24:52.584 [planetinfo.record]: sun_vector = 0.583 -0.812 -0.005 11:24:52.584 [planetinfo.record]: sun_distance = 1145340 11:24:52.584 [planetinfo.record]: corona_flare = 0.083139 11:24:52.584 [planetinfo.record]: corona_hues = 0.533737 11:24:52.584 [planetinfo.record]: sun_color = 0.522783, 0.62416, 0.712289, 1 11:24:52.585 [planetinfo.record]: corona_shimmer = 0.246918 11:24:52.585 [planetinfo.record]: station_vector = -0.017 -0.778 0.628 11:24:52.585 [planetinfo.record]: station = coriolis 11:24:57.061 [PLANETINFO OVER]: Done 11:24:57.063 [PLANETINFO LOGGING]: 0 197 11:24:58.082 [planetinfo.record]: seed = 96 78 141 66 11:24:58.082 [planetinfo.record]: coordinates = 78 78 11:24:58.082 [planetinfo.record]: government = 4; 11:24:58.082 [planetinfo.record]: economy = 6; 11:24:58.082 [planetinfo.record]: techlevel = 5; 11:24:58.082 [planetinfo.record]: population = 31; 11:24:58.082 [planetinfo.record]: productivity = 7936; 11:24:58.082 [planetinfo.record]: name = "Xeoner"; 11:24:58.082 [planetinfo.record]: inhabitant = "Large Yellow Slimy Lizard"; 11:24:58.082 [planetinfo.record]: inhabitants = "Large Yellow Slimy Lizards"; 11:24:58.083 [planetinfo.record]: description = "The world Xeoner is a dull world."; 11:24:58.093 [planetinfo.record]: air_color = 0.795594, 0.474379, 0.990545, 1; 11:24:58.094 [planetinfo.record]: cloud_alpha = 1.000000; 11:24:58.094 [planetinfo.record]: cloud_color = 0.974609, 0.0342636, 0.14446, 1; 11:24:58.094 [planetinfo.record]: cloud_fraction = 0.490000; 11:24:58.094 [planetinfo.record]: land_color = 0.270996, 0.354977, 0.578125, 1; 11:24:58.094 [planetinfo.record]: land_fraction = 0.250000; 11:24:58.094 [planetinfo.record]: polar_cloud_color = 0.938574, 0.372588, 0.438915, 1; 11:24:58.094 [planetinfo.record]: polar_land_color = 0.817053, 0.85127, 0.942187, 1; 11:24:58.094 [planetinfo.record]: polar_sea_color = 0.885713, 0.932617, 0.888645, 1; 11:24:58.094 [planetinfo.record]: sea_color = 0.538273, 0.673828, 0.546745, 1; 11:24:58.094 [planetinfo.record]: rotation_speed = 0.000315 11:24:58.094 [planetinfo.record]: planet zpos = 340600.000000 11:24:58.094 [planetinfo.record]: sun_radius = 88240.221558 11:24:58.094 [planetinfo.record]: sun_vector = 0.400 0.776 -0.487 11:24:58.094 [planetinfo.record]: sun_distance = 681200 11:24:58.094 [planetinfo.record]: corona_flare = 0.027541 11:24:58.094 [planetinfo.record]: corona_hues = 0.708267 11:24:58.094 [planetinfo.record]: sun_color = 0.681946, 0.62889, 0.280066, 1 11:24:58.095 [planetinfo.record]: corona_shimmer = 0.360909 11:24:58.095 [planetinfo.record]: station_vector = 0.318 0.822 0.473 11:24:58.095 [planetinfo.record]: station = coriolis 11:25:03.568 [PLANETINFO OVER]: Done 11:25:03.568 [PLANETINFO LOGGING]: 0 198 11:25:04.571 [planetinfo.record]: seed = 84 146 27 226 11:25:04.571 [planetinfo.record]: coordinates = 146 112 11:25:04.571 [planetinfo.record]: government = 2; 11:25:04.571 [planetinfo.record]: economy = 0; 11:25:04.571 [planetinfo.record]: techlevel = 10; 11:25:04.571 [planetinfo.record]: population = 43; 11:25:04.571 [planetinfo.record]: productivity = 20640; 11:25:04.571 [planetinfo.record]: name = "Xezaor"; 11:25:04.571 [planetinfo.record]: inhabitant = "Human Colonial"; 11:25:04.571 [planetinfo.record]: inhabitants = "Human Colonials"; 11:25:04.571 [planetinfo.record]: description = "The world Xezaor is most well known for its hoopy casinos."; 11:25:04.580 [planetinfo.record]: air_color = 0.766332, 0.634017, 0.857865, 1; 11:25:04.580 [planetinfo.record]: cloud_alpha = 1.000000; 11:25:04.580 [planetinfo.record]: cloud_color = 0.576172, 0.47039, 0.492704, 1; 11:25:04.580 [planetinfo.record]: cloud_fraction = 0.470000; 11:25:04.580 [planetinfo.record]: land_color = 0.407344, 0.66, 0.4409, 1; 11:25:04.580 [planetinfo.record]: land_fraction = 0.440000; 11:25:04.580 [planetinfo.record]: polar_cloud_color = 0.759277, 0.672153, 0.690531, 1; 11:25:04.580 [planetinfo.record]: polar_land_color = 0.844613, 0.934, 0.856485, 1; 11:25:04.580 [planetinfo.record]: polar_sea_color = 0.928125, 0.913949, 0.858334, 1; 11:25:04.580 [planetinfo.record]: sea_color = 0.71875, 0.674837, 0.502563, 1; 11:25:04.580 [planetinfo.record]: rotation_speed = 0.001570 11:25:04.580 [planetinfo.record]: planet zpos = 416880.000000 11:25:04.580 [planetinfo.record]: sun_radius = 94804.535522 11:25:04.580 [planetinfo.record]: sun_vector = -0.106 -0.936 0.336 11:25:04.580 [planetinfo.record]: sun_distance = 833760 11:25:04.580 [planetinfo.record]: corona_flare = 0.040836 11:25:04.580 [planetinfo.record]: corona_hues = 0.513924 11:25:04.580 [planetinfo.record]: sun_color = 0.181204, 0.472203, 0.658063, 1 11:25:04.581 [planetinfo.record]: corona_shimmer = 0.290664 11:25:04.588 [planetinfo.record]: station_vector = 0.781 -0.544 0.305 11:25:04.588 [planetinfo.record]: station = coriolis 11:25:09.493 [PLANETINFO OVER]: Done 11:25:09.494 [PLANETINFO LOGGING]: 0 199 11:25:10.499 [planetinfo.record]: seed = 28 32 29 94 11:25:10.499 [planetinfo.record]: coordinates = 32 89 11:25:10.499 [planetinfo.record]: government = 3; 11:25:10.499 [planetinfo.record]: economy = 1; 11:25:10.499 [planetinfo.record]: techlevel = 8; 11:25:10.499 [planetinfo.record]: population = 37; 11:25:10.499 [planetinfo.record]: productivity = 18648; 11:25:10.499 [planetinfo.record]: name = "Ritila"; 11:25:10.499 [planetinfo.record]: inhabitant = "Human Colonial"; 11:25:10.499 [planetinfo.record]: inhabitants = "Human Colonials"; 11:25:10.500 [planetinfo.record]: description = "The world Ritila is very famous for its hoopy casinos but beset by evil disease."; 11:25:10.520 [planetinfo.record]: air_color = 0.722292, 0.627473, 0.841605, 1; 11:25:10.520 [planetinfo.record]: cloud_alpha = 1.000000; 11:25:10.520 [planetinfo.record]: cloud_color = 0.527344, 0.440826, 0.497603, 1; 11:25:10.520 [planetinfo.record]: cloud_fraction = 0.560000; 11:25:10.520 [planetinfo.record]: land_color = 0.66, 0.628982, 0.376406, 1; 11:25:10.520 [planetinfo.record]: land_fraction = 0.540000; 11:25:10.520 [planetinfo.record]: polar_cloud_color = 0.737305, 0.661702, 0.711316, 1; 11:25:10.520 [planetinfo.record]: polar_land_color = 0.934, 0.923026, 0.833668, 1; 11:25:10.520 [planetinfo.record]: polar_sea_color = 0.906466, 0.948828, 0.755171, 1; 11:25:10.520 [planetinfo.record]: sea_color = 0.420331, 0.511719, 0.0939484, 1; 11:25:10.520 [planetinfo.record]: rotation_speed = 0.004540 11:25:10.520 [planetinfo.record]: planet zpos = 964800.000000 11:25:10.520 [planetinfo.record]: sun_radius = 160150.283203 11:25:10.520 [planetinfo.record]: sun_vector = -0.774 0.568 0.281 11:25:10.520 [planetinfo.record]: sun_distance = 1479360 11:25:10.520 [planetinfo.record]: corona_flare = 0.088620 11:25:10.520 [planetinfo.record]: corona_hues = 0.602219 11:25:10.520 [planetinfo.record]: sun_color = 0.629609, 0.736403, 0.769635, 1 11:25:10.520 [planetinfo.record]: corona_shimmer = 0.280462 11:25:10.521 [planetinfo.record]: station_vector = -0.974 -0.222 0.051 11:25:10.521 [planetinfo.record]: station = coriolis 11:25:16.064 [PLANETINFO OVER]: Done 11:25:16.064 [PLANETINFO LOGGING]: 0 200 11:25:17.068 [planetinfo.record]: seed = 56 139 83 184 11:25:17.068 [planetinfo.record]: coordinates = 139 85 11:25:17.068 [planetinfo.record]: government = 7; 11:25:17.068 [planetinfo.record]: economy = 5; 11:25:17.068 [planetinfo.record]: techlevel = 9; 11:25:17.068 [planetinfo.record]: population = 49; 11:25:17.068 [planetinfo.record]: productivity = 21560; 11:25:17.068 [planetinfo.record]: name = "Edorte"; 11:25:17.068 [planetinfo.record]: inhabitant = "Human Colonial"; 11:25:17.068 [planetinfo.record]: inhabitants = "Human Colonials"; 11:25:17.068 [planetinfo.record]: description = "The planet Edorte is an unremarkable dump."; 11:25:17.100 [planetinfo.record]: air_color = 0.66683, 0.770647, 0.974285, 1; 11:25:17.100 [planetinfo.record]: cloud_alpha = 1.000000; 11:25:17.100 [planetinfo.record]: cloud_color = 0.603928, 0.925781, 0.925781, 1; 11:25:17.100 [planetinfo.record]: cloud_fraction = 0.400000; 11:25:17.100 [planetinfo.record]: land_color = 0.629627, 0.592969, 0.66, 1; 11:25:17.100 [planetinfo.record]: land_fraction = 0.430000; 11:25:17.100 [planetinfo.record]: polar_cloud_color = 0.717438, 0.916602, 0.916602, 1; 11:25:17.100 [planetinfo.record]: polar_land_color = 0.923254, 0.910285, 0.934, 1; 11:25:17.100 [planetinfo.record]: polar_sea_color = 0.793292, 0.949609, 0.78825, 1; 11:25:17.100 [planetinfo.record]: sea_color = 0.172111, 0.503906, 0.161407, 1; 11:25:17.100 [planetinfo.record]: rotation_speed = 0.002019 11:25:17.100 [planetinfo.record]: planet zpos = 500300.000000 11:25:17.100 [planetinfo.record]: sun_radius = 137458.829651 11:25:17.100 [planetinfo.record]: sun_vector = 0.320 -0.895 -0.311 11:25:17.100 [planetinfo.record]: sun_distance = 1100660 11:25:17.100 [planetinfo.record]: corona_flare = 0.086687 11:25:17.100 [planetinfo.record]: corona_hues = 0.538643 11:25:17.100 [planetinfo.record]: sun_color = 0.750623, 0.734372, 0.711726, 1 11:25:17.100 [planetinfo.record]: corona_shimmer = 0.652879 11:25:17.100 [planetinfo.record]: station_vector = 0.018 0.847 0.531 11:25:17.100 [planetinfo.record]: station = coriolis 11:25:22.643 [PLANETINFO OVER]: Done 11:25:22.644 [PLANETINFO LOGGING]: 0 201 11:25:23.647 [planetinfo.record]: seed = 72 46 61 164 11:25:23.647 [planetinfo.record]: coordinates = 46 220 11:25:23.647 [planetinfo.record]: government = 1; 11:25:23.647 [planetinfo.record]: economy = 6; 11:25:23.647 [planetinfo.record]: techlevel = 4; 11:25:23.647 [planetinfo.record]: population = 24; 11:25:23.647 [planetinfo.record]: productivity = 3840; 11:25:23.647 [planetinfo.record]: name = "Zaalela"; 11:25:23.647 [planetinfo.record]: inhabitant = "Human Colonial"; 11:25:23.647 [planetinfo.record]: inhabitants = "Human Colonials"; 11:25:23.647 [planetinfo.record]: description = "This world is noted for its fabulous goat soup."; 11:25:23.658 [planetinfo.record]: air_color = 0.446106, 0.740325, 0.859166, 1; 11:25:23.659 [planetinfo.record]: cloud_alpha = 1.000000; 11:25:23.659 [planetinfo.record]: cloud_color = 0.151817, 0.580078, 0.0906372, 1; 11:25:23.659 [planetinfo.record]: cloud_fraction = 0.480000; 11:25:23.659 [planetinfo.record]: land_color = 0.288086, 0.319595, 0.576172, 1; 11:25:23.659 [planetinfo.record]: land_fraction = 0.420000; 11:25:23.659 [planetinfo.record]: polar_cloud_color = 0.409874, 0.761035, 0.359708, 1; 11:25:23.659 [planetinfo.record]: polar_land_color = 0.824585, 0.837469, 0.942383, 1; 11:25:23.659 [planetinfo.record]: polar_sea_color = 0.897736, 0.923633, 0.855984, 1; 11:25:23.659 [planetinfo.record]: sea_color = 0.678024, 0.763672, 0.53994, 1; 11:25:23.659 [planetinfo.record]: rotation_speed = 0.000042 11:25:23.659 [planetinfo.record]: planet zpos = 427460.000000 11:25:23.659 [planetinfo.record]: sun_radius = 92631.671143 11:25:23.659 [planetinfo.record]: sun_vector = -0.006 0.896 0.444 11:25:23.659 [planetinfo.record]: sun_distance = 816060 11:25:23.659 [planetinfo.record]: corona_flare = 0.061424 11:25:23.659 [planetinfo.record]: corona_hues = 0.696037 11:25:23.659 [planetinfo.record]: sun_color = 0.533252, 0.778025, 0.802826, 1 11:25:23.659 [planetinfo.record]: corona_shimmer = 0.380382 11:25:23.660 [planetinfo.record]: station_vector = 0.813 0.424 0.400 11:25:23.660 [planetinfo.record]: station = coriolis 11:25:28.188 [PLANETINFO OVER]: Done 11:25:28.189 [PLANETINFO LOGGING]: 0 202 11:25:29.203 [planetinfo.record]: seed = 76 213 91 6 11:25:29.203 [planetinfo.record]: coordinates = 213 129 11:25:29.203 [planetinfo.record]: government = 1; 11:25:29.203 [planetinfo.record]: economy = 3; 11:25:29.203 [planetinfo.record]: techlevel = 6; 11:25:29.203 [planetinfo.record]: population = 29; 11:25:29.203 [planetinfo.record]: productivity = 8120; 11:25:29.203 [planetinfo.record]: name = "Biisorte"; 11:25:29.203 [planetinfo.record]: inhabitant = "Human Colonial"; 11:25:29.203 [planetinfo.record]: inhabitants = "Human Colonials"; 11:25:29.203 [planetinfo.record]: description = "This world is most notable for its fabulous Biisorteian lethal water but beset by lethal disease."; 11:25:29.211 [planetinfo.record]: air_color = 0.626694, 0.825821, 0.974936, 1; 11:25:29.219 [planetinfo.record]: cloud_alpha = 1.000000; 11:25:29.220 [planetinfo.record]: cloud_color = 0.489235, 0.927734, 0.619414, 1; 11:25:29.220 [planetinfo.record]: cloud_fraction = 0.200000; 11:25:29.220 [planetinfo.record]: land_color = 0.66, 0.611016, 0.65005, 1; 11:25:29.220 [planetinfo.record]: land_fraction = 0.510000; 11:25:29.220 [planetinfo.record]: polar_cloud_color = 0.646447, 0.91748, 0.72691, 1; 11:25:29.220 [planetinfo.record]: polar_land_color = 0.934, 0.91667, 0.93048, 1; 11:25:29.220 [planetinfo.record]: polar_sea_color = 0.922165, 0.9375, 0.888428, 1; 11:25:29.220 [planetinfo.record]: sea_color = 0.584106, 0.625, 0.494141, 1; 11:25:29.220 [planetinfo.record]: rotation_speed = 0.003668 11:25:29.220 [planetinfo.record]: planet zpos = 547800.000000 11:25:29.220 [planetinfo.record]: sun_radius = 97161.583710 11:25:29.220 [planetinfo.record]: sun_vector = -0.515 0.261 0.816 11:25:29.220 [planetinfo.record]: sun_distance = 821700 11:25:29.220 [planetinfo.record]: corona_flare = 0.032162 11:25:29.220 [planetinfo.record]: corona_hues = 0.678085 11:25:29.220 [planetinfo.record]: sun_color = 0.801895, 0.776999, 0.589779, 1 11:25:29.220 [planetinfo.record]: corona_shimmer = 0.496876 11:25:29.220 [planetinfo.record]: station_vector = -0.021 0.963 0.269 11:25:29.220 [planetinfo.record]: station = coriolis 11:25:34.395 [PLANETINFO OVER]: Done 11:25:34.396 [PLANETINFO LOGGING]: 0 203 11:25:35.415 [planetinfo.record]: seed = 228 156 109 51 11:25:35.415 [planetinfo.record]: coordinates = 156 57 11:25:35.415 [planetinfo.record]: government = 4; 11:25:35.415 [planetinfo.record]: economy = 1; 11:25:35.415 [planetinfo.record]: techlevel = 8; 11:25:35.415 [planetinfo.record]: population = 38; 11:25:35.415 [planetinfo.record]: productivity = 21888; 11:25:35.415 [planetinfo.record]: name = "Beesor"; 11:25:35.415 [planetinfo.record]: inhabitant = "Human Colonial"; 11:25:35.415 [planetinfo.record]: inhabitants = "Human Colonials"; 11:25:35.415 [planetinfo.record]: description = "This world is plagued by deadly earthquakes."; 11:25:35.424 [planetinfo.record]: air_color = 0.480128, 0.994447, 0.841364, 1; 11:25:35.424 [planetinfo.record]: cloud_alpha = 1.000000; 11:25:35.424 [planetinfo.record]: cloud_color = 0.986328, 0.156401, 0.0462341, 1; 11:25:35.424 [planetinfo.record]: cloud_fraction = 0.300000; 11:25:35.424 [planetinfo.record]: land_color = 0.19012, 0.595703, 0.0605011, 1; 11:25:35.431 [planetinfo.record]: land_fraction = 0.400000; 11:25:35.431 [planetinfo.record]: polar_cloud_color = 0.943848, 0.447484, 0.381595, 1; 11:25:35.432 [planetinfo.record]: polar_land_color = 0.780357, 0.94043, 0.7292, 1; 11:25:35.432 [planetinfo.record]: polar_sea_color = 0.930859, 0.901827, 0.864499, 1; 11:25:35.432 [planetinfo.record]: sea_color = 0.691406, 0.605149, 0.494247, 1; 11:25:35.432 [planetinfo.record]: rotation_speed = 0.001726 11:25:35.432 [planetinfo.record]: planet zpos = 486200.000000 11:25:35.432 [planetinfo.record]: sun_radius = 90254.550171 11:25:35.432 [planetinfo.record]: sun_vector = 0.760 -0.650 0.015 11:25:35.432 [planetinfo.record]: sun_distance = 710600 11:25:35.432 [planetinfo.record]: corona_flare = 0.032312 11:25:35.432 [planetinfo.record]: corona_hues = 0.510872 11:25:35.432 [planetinfo.record]: sun_color = 0.67176, 0.694393, 0.696658, 1 11:25:35.432 [planetinfo.record]: corona_shimmer = 0.489462 11:25:35.432 [planetinfo.record]: station_vector = -0.765 0.294 0.573 11:25:35.432 [planetinfo.record]: station = coriolis 11:25:39.830 [PLANETINFO OVER]: Done 11:25:39.832 [PLANETINFO LOGGING]: 0 204 11:25:40.835 [planetinfo.record]: seed = 144 22 179 249 11:25:40.835 [planetinfo.record]: coordinates = 22 217 11:25:40.835 [planetinfo.record]: government = 2; 11:25:40.835 [planetinfo.record]: economy = 1; 11:25:40.835 [planetinfo.record]: techlevel = 9; 11:25:40.835 [planetinfo.record]: population = 40; 11:25:40.835 [planetinfo.record]: productivity = 17280; 11:25:40.835 [planetinfo.record]: name = "Oresqu"; 11:25:40.836 [planetinfo.record]: inhabitant = "Rodent"; 11:25:40.836 [planetinfo.record]: inhabitants = "Rodents"; 11:25:40.836 [planetinfo.record]: description = "Oresqu is mildly notable for its inhabitants’ unusual mating traditions."; 11:25:40.856 [planetinfo.record]: air_color = 0.508694, 0.635813, 0.846158, 1; 11:25:40.856 [planetinfo.record]: cloud_alpha = 1.000000; 11:25:40.856 [planetinfo.record]: cloud_color = 0.217674, 0.541016, 0.503124, 1; 11:25:40.856 [planetinfo.record]: cloud_fraction = 0.490000; 11:25:40.856 [planetinfo.record]: land_color = 0.432902, 0.337547, 0.572266, 1; 11:25:40.856 [planetinfo.record]: land_fraction = 0.390000; 11:25:40.856 [planetinfo.record]: polar_cloud_color = 0.46575, 0.743457, 0.710913, 1; 11:25:40.856 [planetinfo.record]: polar_land_color = 0.885375, 0.846102, 0.942773, 1; 11:25:40.856 [planetinfo.record]: polar_sea_color = 0.940039, 0.895408, 0.881746, 1; 11:25:40.856 [planetinfo.record]: sea_color = 0.599609, 0.485737, 0.450878, 1; 11:25:40.856 [planetinfo.record]: rotation_speed = 0.001719 11:25:40.856 [planetinfo.record]: planet zpos = 668460.000000 11:25:40.857 [planetinfo.record]: sun_radius = 137757.362366 11:25:40.857 [planetinfo.record]: sun_vector = -0.645 -0.748 0.157 11:25:40.857 [planetinfo.record]: sun_distance = 1131240 11:25:40.857 [planetinfo.record]: corona_flare = 0.070436 11:25:40.857 [planetinfo.record]: corona_hues = 0.639290 11:25:40.859 [planetinfo.record]: sun_color = 0.72504, 0.642847, 0.580698, 1 11:25:40.859 [planetinfo.record]: corona_shimmer = 0.423715 11:25:40.859 [planetinfo.record]: station_vector = -0.864 -0.485 0.134 11:25:40.859 [planetinfo.record]: station = coriolis 11:25:45.849 [PLANETINFO OVER]: Done 11:25:45.850 [PLANETINFO LOGGING]: 0 205 11:25:46.854 [planetinfo.record]: seed = 240 221 45 194 11:25:46.854 [planetinfo.record]: coordinates = 221 250 11:25:46.854 [planetinfo.record]: government = 6; 11:25:46.854 [planetinfo.record]: economy = 2; 11:25:46.854 [planetinfo.record]: techlevel = 9; 11:25:46.854 [planetinfo.record]: population = 45; 11:25:46.854 [planetinfo.record]: productivity = 28800; 11:25:46.854 [planetinfo.record]: name = "Xeququti"; 11:25:46.854 [planetinfo.record]: inhabitant = "Human Colonial"; 11:25:46.854 [planetinfo.record]: inhabitants = "Human Colonials"; 11:25:46.854 [planetinfo.record]: description = "This planet is beset by dreadful earthquakes."; 11:25:46.864 [planetinfo.record]: air_color = 0.736777, 0.727662, 0.974285, 1; 11:25:46.868 [planetinfo.record]: cloud_alpha = 1.000000; 11:25:46.868 [planetinfo.record]: cloud_color = 0.796045, 0.777512, 0.925781, 1; 11:25:46.868 [planetinfo.record]: cloud_fraction = 0.500000; 11:25:46.868 [planetinfo.record]: land_color = 0.520781, 0.591478, 0.66, 1; 11:25:46.868 [planetinfo.record]: land_fraction = 0.400000; 11:25:46.868 [planetinfo.record]: polar_cloud_color = 0.836321, 0.824852, 0.916602, 1; 11:25:46.868 [planetinfo.record]: polar_land_color = 0.884746, 0.909758, 0.934, 1; 11:25:46.868 [planetinfo.record]: polar_sea_color = 0.942578, 0.920268, 0.891583, 1; 11:25:46.868 [planetinfo.record]: sea_color = 0.574219, 0.519853, 0.449954, 1; 11:25:46.868 [planetinfo.record]: rotation_speed = 0.004770 11:25:46.868 [planetinfo.record]: planet zpos = 354900.000000 11:25:46.868 [planetinfo.record]: sun_radius = 83265.249939 11:25:46.868 [planetinfo.record]: sun_vector = -0.816 -0.570 -0.095 11:25:46.868 [planetinfo.record]: sun_distance = 816270 11:25:46.868 [planetinfo.record]: corona_flare = 0.016652 11:25:46.868 [planetinfo.record]: corona_hues = 0.688637 11:25:46.868 [planetinfo.record]: sun_color = 0.652838, 0.596573, 0.581205, 1 11:25:46.868 [planetinfo.record]: corona_shimmer = 0.841932 11:25:46.868 [planetinfo.record]: station_vector = 0.230 0.933 0.278 11:25:46.868 [planetinfo.record]: station = coriolis 11:25:50.889 [PLANETINFO OVER]: Done 11:25:50.890 [PLANETINFO LOGGING]: 0 206 11:25:51.892 [planetinfo.record]: seed = 4 151 219 107 11:25:51.892 [planetinfo.record]: coordinates = 151 58 11:25:51.892 [planetinfo.record]: government = 0; 11:25:51.892 [planetinfo.record]: economy = 2; 11:25:51.892 [planetinfo.record]: techlevel = 8; 11:25:51.892 [planetinfo.record]: population = 35; 11:25:51.892 [planetinfo.record]: productivity = 8960; 11:25:51.892 [planetinfo.record]: name = "Maises"; 11:25:51.892 [planetinfo.record]: inhabitant = "Small Blue Furry Rodent"; 11:25:51.892 [planetinfo.record]: inhabitants = "Small Blue Furry Rodents"; 11:25:51.892 [planetinfo.record]: description = "Maises is reasonably notable for its fabulous Maisesian lethal water but beset by lethal disease."; 11:25:51.905 [planetinfo.record]: air_color = 0.625576, 0.91575, 0.904118, 1; 11:25:51.906 [planetinfo.record]: cloud_alpha = 1.000000; 11:25:51.906 [planetinfo.record]: cloud_color = 0.75, 0.721161, 0.486328, 1; 11:25:51.906 [planetinfo.record]: cloud_fraction = 0.190000; 11:25:51.906 [planetinfo.record]: land_color = 0.66, 0.644531, 0.659758, 1; 11:25:51.906 [planetinfo.record]: land_fraction = 0.260000; 11:25:51.906 [planetinfo.record]: polar_cloud_color = 0.8375, 0.817373, 0.653479, 1; 11:25:51.906 [planetinfo.record]: polar_land_color = 0.934, 0.928527, 0.933914, 1; 11:25:51.906 [planetinfo.record]: polar_sea_color = 0.95, 0.861264, 0.735693, 1; 11:25:51.906 [planetinfo.record]: sea_color = 0.5, 0.313187, 0.0488281, 1; 11:25:51.906 [planetinfo.record]: rotation_speed = 0.000892 11:25:51.906 [planetinfo.record]: planet zpos = 636130.000000 11:25:51.906 [planetinfo.record]: sun_radius = 151754.334717 11:25:51.906 [planetinfo.record]: sun_vector = 0.719 0.203 0.665 11:25:51.906 [planetinfo.record]: sun_distance = 1214430 11:25:51.908 [planetinfo.record]: corona_flare = 0.036819 11:25:51.908 [planetinfo.record]: corona_hues = 0.978981 11:25:51.908 [planetinfo.record]: sun_color = 0.56592, 0.636868, 0.798209, 1 11:25:51.908 [planetinfo.record]: corona_shimmer = 0.269478 11:25:51.908 [planetinfo.record]: station_vector = 0.269 0.098 0.958 11:25:51.908 [planetinfo.record]: station = coriolis 11:25:56.807 [PLANETINFO OVER]: Done 11:25:56.808 [PLANETINFO LOGGING]: 0 207 11:25:57.815 [planetinfo.record]: seed = 108 233 253 102 11:25:57.815 [planetinfo.record]: coordinates = 233 64 11:25:57.815 [planetinfo.record]: government = 5; 11:25:57.815 [planetinfo.record]: economy = 0; 11:25:57.815 [planetinfo.record]: techlevel = 11; 11:25:57.815 [planetinfo.record]: population = 50; 11:25:57.815 [planetinfo.record]: productivity = 36000; 11:25:57.815 [planetinfo.record]: name = "Bierle"; 11:25:57.815 [planetinfo.record]: inhabitant = "Fierce Blue Bug-Eyed Lobster"; 11:25:57.815 [planetinfo.record]: inhabitants = "Fierce Blue Bug-Eyed Lobsters"; 11:25:57.815 [planetinfo.record]: description = "The planet Bierle is most famous for the Bierleian deadly Diusoid and the Bierleian evil arts graduate."; 11:25:57.850 [planetinfo.record]: air_color = 0.644003, 0.546192, 0.896889, 1; 11:25:57.850 [planetinfo.record]: cloud_alpha = 1.000000; 11:25:57.850 [planetinfo.record]: cloud_color = 0.63426, 0.295219, 0.693359, 1; 11:25:57.850 [planetinfo.record]: cloud_fraction = 0.360000; 11:25:57.851 [planetinfo.record]: land_color = 0.639355, 0.66, 0.554297, 1; 11:25:57.851 [planetinfo.record]: land_fraction = 0.560000; 11:25:57.851 [planetinfo.record]: polar_cloud_color = 0.768754, 0.520591, 0.812012, 1; 11:25:57.851 [planetinfo.record]: polar_land_color = 0.926696, 0.934, 0.896604, 1; 11:25:57.851 [planetinfo.record]: polar_sea_color = 0.723443, 0.900516, 0.929492, 1; 11:25:57.851 [planetinfo.record]: sea_color = 0.0798721, 0.617159, 0.705078, 1; 11:25:57.851 [planetinfo.record]: rotation_speed = 0.003949 11:25:57.851 [planetinfo.record]: planet zpos = 596050.000000 11:25:57.851 [planetinfo.record]: sun_radius = 70847.960663 11:25:57.852 [planetinfo.record]: sun_vector = -0.451 -0.856 0.253 11:25:57.852 [planetinfo.record]: sun_distance = 917000 11:25:57.852 [planetinfo.record]: corona_flare = 0.079852 11:25:57.852 [planetinfo.record]: corona_hues = 0.892532 11:25:57.852 [planetinfo.record]: sun_color = 0.652426, 0.554079, 0.505562, 1 11:25:57.852 [planetinfo.record]: corona_shimmer = 0.270629 11:25:57.853 [planetinfo.record]: station_vector = -0.770 0.267 0.580 11:25:57.853 [planetinfo.record]: station = dodecahedron 11:26:02.652 [PLANETINFO OVER]: Done 11:26:02.652 [PLANETINFO LOGGING]: 0 208 11:26:03.656 [planetinfo.record]: seed = 168 216 83 74 11:26:03.656 [planetinfo.record]: coordinates = 216 225 11:26:03.656 [planetinfo.record]: government = 5; 11:26:03.656 [planetinfo.record]: economy = 1; 11:26:03.656 [planetinfo.record]: techlevel = 9; 11:26:03.656 [planetinfo.record]: population = 43; 11:26:03.656 [planetinfo.record]: productivity = 27864; 11:26:03.656 [planetinfo.record]: name = "Arzaso"; 11:26:03.656 [planetinfo.record]: inhabitant = "Human Colonial"; 11:26:03.656 [planetinfo.record]: inhabitants = "Human Colonials"; 11:26:03.656 [planetinfo.record]: description = "Arzaso is an unremarkable planet."; 11:26:03.669 [planetinfo.record]: air_color = 0.748123, 0.549173, 0.925506, 1; 11:26:03.670 [planetinfo.record]: cloud_alpha = 1.000000; 11:26:03.670 [planetinfo.record]: cloud_color = 0.779297, 0.29528, 0.488131, 1; 11:26:03.670 [planetinfo.record]: cloud_fraction = 0.460000; 11:26:03.670 [planetinfo.record]: land_color = 0.66, 0.66, 0.66, 1; 11:26:03.670 [planetinfo.record]: land_fraction = 0.580000; 11:26:03.670 [planetinfo.record]: polar_cloud_color = 0.850684, 0.520462, 0.652035, 1; 11:26:03.670 [planetinfo.record]: polar_land_color = 0.934, 0.934, 0.934, 1; 11:26:03.670 [planetinfo.record]: polar_sea_color = 0.903779, 0.932031, 0.878057, 1; 11:26:03.670 [planetinfo.record]: sea_color = 0.597276, 0.679688, 0.522244, 1; 11:26:03.670 [planetinfo.record]: rotation_speed = 0.001355 11:26:03.670 [planetinfo.record]: planet zpos = 726960.000000 11:26:03.670 [planetinfo.record]: sun_radius = 103076.048584 11:26:03.671 [planetinfo.record]: sun_vector = -0.379 0.491 -0.785 11:26:03.671 [planetinfo.record]: sun_distance = 1118400 11:26:03.671 [planetinfo.record]: corona_flare = 0.057808 11:26:03.671 [planetinfo.record]: corona_hues = 0.958374 11:26:03.671 [planetinfo.record]: sun_color = 0.827866, 0.787332, 0.386455, 1 11:26:03.671 [planetinfo.record]: corona_shimmer = 0.341038 11:26:03.671 [planetinfo.record]: station_vector = -0.570 0.330 0.752 11:26:03.671 [planetinfo.record]: station = coriolis 11:26:08.702 [PLANETINFO OVER]: Done 11:26:08.702 [PLANETINFO LOGGING]: 0 209 11:26:09.706 [planetinfo.record]: seed = 88 117 93 160 11:26:09.706 [planetinfo.record]: coordinates = 117 39 11:26:09.706 [planetinfo.record]: government = 3; 11:26:09.706 [planetinfo.record]: economy = 7; 11:26:09.706 [planetinfo.record]: techlevel = 3; 11:26:09.706 [planetinfo.record]: population = 23; 11:26:09.706 [planetinfo.record]: productivity = 3864; 11:26:09.706 [planetinfo.record]: name = "Teen"; 11:26:09.706 [planetinfo.record]: inhabitant = "Human Colonial"; 11:26:09.707 [planetinfo.record]: inhabitants = "Human Colonials"; 11:26:09.707 [planetinfo.record]: description = "Teen is cursed by deadly civil war."; 11:26:09.720 [planetinfo.record]: air_color = 0.509567, 0.547914, 0.879979, 1; 11:26:09.720 [planetinfo.record]: cloud_alpha = 1.000000; 11:26:09.720 [planetinfo.record]: cloud_color = 0.215866, 0.342546, 0.642578, 1; 11:26:09.720 [planetinfo.record]: cloud_fraction = 0.450000; 11:26:09.720 [planetinfo.record]: land_color = 0.66, 0.290885, 0.262969, 1; 11:26:09.720 [planetinfo.record]: land_fraction = 0.460000; 11:26:09.721 [planetinfo.record]: polar_cloud_color = 0.461628, 0.558864, 0.78916, 1; 11:26:09.721 [planetinfo.record]: polar_land_color = 0.934, 0.803412, 0.793535, 1; 11:26:09.721 [planetinfo.record]: polar_sea_color = 0.848229, 0.825803, 0.940625, 1; 11:26:09.721 [planetinfo.record]: sea_color = 0.360457, 0.303833, 0.59375, 1; 11:26:09.721 [planetinfo.record]: rotation_speed = 0.004427 11:26:09.721 [planetinfo.record]: planet zpos = 381290.000000 11:26:09.721 [planetinfo.record]: sun_radius = 81503.798676 11:26:09.721 [planetinfo.record]: sun_vector = 0.095 -0.645 -0.758 11:26:09.721 [planetinfo.record]: sun_distance = 615930 11:26:09.721 [planetinfo.record]: corona_flare = 0.079308 11:26:09.721 [planetinfo.record]: corona_hues = 0.890015 11:26:09.721 [planetinfo.record]: sun_color = 0.715146, 0.633433, 0.510549, 1 11:26:09.721 [planetinfo.record]: corona_shimmer = 0.329032 11:26:09.722 [planetinfo.record]: station_vector = 0.832 -0.201 0.518 11:26:09.722 [planetinfo.record]: station = coriolis 11:26:14.840 [PLANETINFO OVER]: Done 11:26:14.841 [PLANETINFO LOGGING]: 0 210 11:26:15.843 [planetinfo.record]: seed = 124 47 155 190 11:26:15.843 [planetinfo.record]: coordinates = 47 82 11:26:15.843 [planetinfo.record]: government = 7; 11:26:15.843 [planetinfo.record]: economy = 2; 11:26:15.843 [planetinfo.record]: techlevel = 12; 11:26:15.843 [planetinfo.record]: population = 58; 11:26:15.843 [planetinfo.record]: productivity = 40832; 11:26:15.843 [planetinfo.record]: name = "Riredi"; 11:26:15.843 [planetinfo.record]: inhabitant = "Harmless Furry Insect"; 11:26:15.843 [planetinfo.record]: inhabitants = "Harmless Furry Insects"; 11:26:15.843 [planetinfo.record]: description = "This world is very fabled for the Rirediian mountain slug."; 11:26:15.846 [planetinfo.record]: air_color = 0.532715, 0.963229, 0.85525, 1; 11:26:15.846 [planetinfo.record]: cloud_alpha = 1.000000; 11:26:15.847 [planetinfo.record]: cloud_color = 0.892578, 0.400908, 0.230118, 1; 11:26:15.847 [planetinfo.record]: cloud_fraction = 0.390000; 11:26:15.847 [planetinfo.record]: land_color = 0.66, 0.592324, 0.226875, 1; 11:26:15.847 [planetinfo.record]: land_fraction = 0.700000; 11:26:15.847 [planetinfo.record]: polar_cloud_color = 0.90166, 0.59124, 0.48341, 1; 11:26:15.847 [planetinfo.record]: polar_land_color = 0.934, 0.910057, 0.780766, 1; 11:26:15.847 [planetinfo.record]: polar_sea_color = 0.902867, 0.934375, 0.885193, 1; 11:26:15.847 [planetinfo.record]: sea_color = 0.567734, 0.65625, 0.518079, 1; 11:26:15.847 [planetinfo.record]: rotation_speed = 0.003102 11:26:15.847 [planetinfo.record]: planet zpos = 838110.000000 11:26:15.847 [planetinfo.record]: sun_radius = 155367.033691 11:26:15.847 [planetinfo.record]: sun_vector = 0.113 -0.038 -0.993 11:26:15.847 [planetinfo.record]: sun_distance = 1095990 11:26:15.848 [planetinfo.record]: corona_flare = 0.047560 11:26:15.848 [planetinfo.record]: corona_hues = 0.677521 11:26:15.848 [planetinfo.record]: sun_color = 0.764722, 0.418108, 0.271353, 1 11:26:15.848 [planetinfo.record]: corona_shimmer = 0.267844 11:26:15.848 [planetinfo.record]: station_vector = 0.975 -0.222 0.008 11:26:15.848 [planetinfo.record]: station = dodecahedron 11:26:20.316 [PLANETINFO OVER]: Done 11:26:20.317 [PLANETINFO LOGGING]: 0 211 11:26:21.322 [planetinfo.record]: seed = 180 45 205 156 11:26:21.322 [planetinfo.record]: coordinates = 45 46 11:26:21.322 [planetinfo.record]: government = 6; 11:26:21.322 [planetinfo.record]: economy = 6; 11:26:21.322 [planetinfo.record]: techlevel = 5; 11:26:21.322 [planetinfo.record]: population = 33; 11:26:21.322 [planetinfo.record]: productivity = 10560; 11:26:21.322 [planetinfo.record]: name = "Teorge"; 11:26:21.322 [planetinfo.record]: inhabitant = "Black Bony Lobster"; 11:26:21.322 [planetinfo.record]: inhabitants = "Black Bony Lobsters"; 11:26:21.322 [planetinfo.record]: description = "This planet is a tedious little planet."; 11:26:21.352 [planetinfo.record]: air_color = 0.668615, 0.816571, 0.946318, 1; 11:26:21.352 [planetinfo.record]: cloud_alpha = 1.000000; 11:26:21.352 [planetinfo.record]: cloud_color = 0.605042, 0.841797, 0.703073, 1; 11:26:21.352 [planetinfo.record]: cloud_fraction = 0.180000; 11:26:21.352 [planetinfo.record]: land_color = 0.632906, 0.634766, 0.575256, 1; 11:26:21.353 [planetinfo.record]: land_fraction = 0.340000; 11:26:21.353 [planetinfo.record]: polar_cloud_color = 0.724331, 0.878809, 0.788294, 1; 11:26:21.353 [planetinfo.record]: polar_land_color = 0.935838, 0.936523, 0.914574, 1; 11:26:21.353 [planetinfo.record]: polar_sea_color = 0.797523, 0.949609, 0.828416, 1; 11:26:21.353 [planetinfo.record]: sea_color = 0.181091, 0.503906, 0.246663, 1; 11:26:21.353 [planetinfo.record]: rotation_speed = 0.001133 11:26:21.353 [planetinfo.record]: planet zpos = 830620.000000 11:26:21.354 [planetinfo.record]: sun_radius = 183458.035889 11:26:21.354 [planetinfo.record]: sun_vector = -0.758 0.546 -0.357 11:26:21.354 [planetinfo.record]: sun_distance = 1245930 11:26:21.354 [planetinfo.record]: corona_flare = 0.075110 11:26:21.355 [planetinfo.record]: corona_hues = 0.900658 11:26:21.355 [planetinfo.record]: sun_color = 0.668909, 0.601606, 0.189687, 1 11:26:21.355 [planetinfo.record]: corona_shimmer = 0.290669 11:26:21.356 [planetinfo.record]: station_vector = -0.016 -0.329 0.944 11:26:21.356 [planetinfo.record]: station = coriolis 11:26:26.150 [PLANETINFO OVER]: Done 11:26:26.151 [PLANETINFO LOGGING]: 0 212 11:26:27.156 [planetinfo.record]: seed = 128 89 51 22 11:26:27.156 [planetinfo.record]: coordinates = 89 195 11:26:27.156 [planetinfo.record]: government = 0; 11:26:27.156 [planetinfo.record]: economy = 3; 11:26:27.156 [planetinfo.record]: techlevel = 5; 11:26:27.156 [planetinfo.record]: population = 24; 11:26:27.156 [planetinfo.record]: productivity = 5376; 11:26:27.156 [planetinfo.record]: name = "Vebege"; 11:26:27.156 [planetinfo.record]: inhabitant = "Human Colonial"; 11:26:27.156 [planetinfo.record]: inhabitants = "Human Colonials"; 11:26:27.156 [planetinfo.record]: description = "The world Vebege is mildly fabled for the Vebegeian mountain lobstoid but beset by deadly solar activity."; 11:26:27.166 [planetinfo.record]: air_color = 0.68389, 0.839588, 0.912498, 1; 11:26:27.166 [planetinfo.record]: cloud_alpha = 1.000000; 11:26:27.166 [planetinfo.record]: cloud_color = 0.630988, 0.740234, 0.627464, 1; 11:26:27.167 [planetinfo.record]: cloud_fraction = 0.370000; 11:26:27.167 [planetinfo.record]: land_color = 0.66, 0.623443, 0.234609, 1; 11:26:27.167 [planetinfo.record]: land_fraction = 0.390000; 11:26:27.167 [planetinfo.record]: polar_cloud_color = 0.75626, 0.833105, 0.753781, 1; 11:26:27.167 [planetinfo.record]: polar_land_color = 0.934, 0.921067, 0.783502, 1; 11:26:27.167 [planetinfo.record]: polar_sea_color = 0.938086, 0.889799, 0.87689, 1; 11:26:27.167 [planetinfo.record]: sea_color = 0.619141, 0.491662, 0.457584, 1; 11:26:27.167 [planetinfo.record]: rotation_speed = 0.000999 11:26:27.167 [planetinfo.record]: planet zpos = 399690.000000 11:26:27.167 [planetinfo.record]: sun_radius = 105125.441589 11:26:27.167 [planetinfo.record]: sun_vector = 0.033 0.322 0.946 11:26:27.167 [planetinfo.record]: sun_distance = 932610 11:26:27.167 [planetinfo.record]: corona_flare = 0.098495 11:26:27.168 [planetinfo.record]: corona_hues = 0.538887 11:26:27.168 [planetinfo.record]: sun_color = 0.589473, 0.686849, 0.74751, 1 11:26:27.169 [planetinfo.record]: corona_shimmer = 0.408446 11:26:27.169 [planetinfo.record]: station_vector = 0.701 -0.563 0.438 11:26:27.169 [planetinfo.record]: station = coriolis 11:26:32.595 [PLANETINFO OVER]: Done 11:26:32.596 [PLANETINFO LOGGING]: 0 213 11:26:33.616 [planetinfo.record]: seed = 128 236 205 194 11:26:33.616 [planetinfo.record]: coordinates = 236 163 11:26:33.616 [planetinfo.record]: government = 0; 11:26:33.616 [planetinfo.record]: economy = 3; 11:26:33.616 [planetinfo.record]: techlevel = 4; 11:26:33.616 [planetinfo.record]: population = 20; 11:26:33.616 [planetinfo.record]: productivity = 4480; 11:26:33.616 [planetinfo.record]: name = "Xeenle"; 11:26:33.616 [planetinfo.record]: inhabitant = "Large Frog"; 11:26:33.616 [planetinfo.record]: inhabitants = "Large Frogs"; 11:26:33.616 [planetinfo.record]: description = "This planet is mildly noted for its ancient mountains but plagued by lethal disease."; 11:26:33.636 [planetinfo.record]: air_color = 0.660064, 0.859816, 0.817511, 1; 11:26:33.636 [planetinfo.record]: cloud_alpha = 1.000000; 11:26:33.636 [planetinfo.record]: cloud_color = 0.582031, 0.546507, 0.525192, 1; 11:26:33.636 [planetinfo.record]: cloud_fraction = 0.350000; 11:26:33.636 [planetinfo.record]: land_color = 0.66, 0.265547, 0.348752, 1; 11:26:33.636 [planetinfo.record]: land_fraction = 0.540000; 11:26:33.636 [planetinfo.record]: polar_cloud_color = 0.761914, 0.732849, 0.715411, 1; 11:26:33.636 [planetinfo.record]: polar_land_color = 0.934, 0.794447, 0.823884, 1; 11:26:33.636 [planetinfo.record]: polar_sea_color = 0.941992, 0.919334, 0.890201, 1; 11:26:33.636 [planetinfo.record]: sea_color = 0.580078, 0.524265, 0.452506, 1; 11:26:33.636 [planetinfo.record]: rotation_speed = 0.004148 11:26:33.636 [planetinfo.record]: planet zpos = 392040.000000 11:26:33.636 [planetinfo.record]: sun_radius = 81007.366333 11:26:33.636 [planetinfo.record]: sun_vector = -0.300 -0.335 0.893 11:26:33.636 [planetinfo.record]: sun_distance = 712800 11:26:33.636 [planetinfo.record]: corona_flare = 0.095201 11:26:33.636 [planetinfo.record]: corona_hues = 0.708626 11:26:33.636 [planetinfo.record]: sun_color = 0.182608, 0.590355, 0.693872, 1 11:26:33.637 [planetinfo.record]: corona_shimmer = 1.112779 11:26:33.637 [planetinfo.record]: station_vector = 0.893 0.443 0.080 11:26:33.637 [planetinfo.record]: station = coriolis 11:26:38.092 [PLANETINFO OVER]: Done 11:26:38.093 [PLANETINFO LOGGING]: 0 214 11:26:39.096 [planetinfo.record]: seed = 180 22 155 106 11:26:39.096 [planetinfo.record]: coordinates = 22 1 11:26:39.096 [planetinfo.record]: government = 6; 11:26:39.096 [planetinfo.record]: economy = 1; 11:26:39.096 [planetinfo.record]: techlevel = 11; 11:26:39.096 [planetinfo.record]: population = 52; 11:26:39.096 [planetinfo.record]: productivity = 37440; 11:26:39.096 [planetinfo.record]: name = "Arxeza"; 11:26:39.096 [planetinfo.record]: inhabitant = "Small Blue Frog"; 11:26:39.096 [planetinfo.record]: inhabitants = "Small Blue Frogs"; 11:26:39.096 [planetinfo.record]: description = "The world Arxeza is beset by dreadful earthquakes."; 11:26:39.108 [planetinfo.record]: air_color = 0.422157, 0.799358, 0.860467, 1; 11:26:39.109 [planetinfo.record]: cloud_alpha = 1.000000; 11:26:39.109 [planetinfo.record]: cloud_color = 0.350697, 0.583984, 0.0410614, 1; 11:26:39.109 [planetinfo.record]: cloud_fraction = 0.490000; 11:26:39.109 [planetinfo.record]: land_color = 0.378984, 0.580964, 0.66, 1; 11:26:39.109 [planetinfo.record]: land_fraction = 0.570000; 11:26:39.109 [planetinfo.record]: polar_cloud_color = 0.572345, 0.762793, 0.319569, 1; 11:26:39.109 [planetinfo.record]: polar_land_color = 0.83458, 0.906038, 0.934, 1; 11:26:39.109 [planetinfo.record]: polar_sea_color = 0.937305, 0.89479, 0.87616, 1; 11:26:39.109 [planetinfo.record]: sea_color = 0.626953, 0.513203, 0.463358, 1; 11:26:39.109 [planetinfo.record]: rotation_speed = 0.000247 11:26:39.109 [planetinfo.record]: planet zpos = 755720.000000 11:26:39.109 [planetinfo.record]: sun_radius = 135985.352478 11:26:39.109 [planetinfo.record]: sun_vector = -0.803 0.410 0.434 11:26:39.109 [planetinfo.record]: sun_distance = 1079600 11:26:39.110 [planetinfo.record]: corona_flare = 0.033546 11:26:39.110 [planetinfo.record]: corona_hues = 0.692802 11:26:39.110 [planetinfo.record]: sun_color = 0.439587, 0.659483, 0.791776, 1 11:26:39.110 [planetinfo.record]: corona_shimmer = 0.325612 11:26:39.110 [planetinfo.record]: station_vector = -0.254 -0.177 0.951 11:26:39.110 [planetinfo.record]: station = dodecahedron 11:26:43.894 [PLANETINFO OVER]: Done 11:26:43.895 [PLANETINFO LOGGING]: 0 215 11:26:44.910 [planetinfo.record]: seed = 188 241 221 120 11:26:44.910 [planetinfo.record]: coordinates = 241 4 11:26:44.910 [planetinfo.record]: government = 7; 11:26:44.910 [planetinfo.record]: economy = 4; 11:26:44.910 [planetinfo.record]: techlevel = 8; 11:26:44.910 [planetinfo.record]: population = 44; 11:26:44.910 [planetinfo.record]: productivity = 23232; 11:26:44.910 [planetinfo.record]: name = "Edreor"; 11:26:44.910 [planetinfo.record]: inhabitant = "Blue Furry Humanoid"; 11:26:44.910 [planetinfo.record]: inhabitants = "Blue Furry Humanoids"; 11:26:44.911 [planetinfo.record]: description = "The world Edreor is reasonably fabled for its fabulous killer Abususlo juice and its ancient Abususlo plant plantations."; 11:26:44.920 [planetinfo.record]: air_color = 0.495306, 0.988594, 0.85717, 1; 11:26:44.920 [planetinfo.record]: cloud_alpha = 1.000000; 11:26:44.920 [planetinfo.record]: cloud_color = 0.96875, 0.281981, 0.0983887, 1; 11:26:44.920 [planetinfo.record]: cloud_fraction = 0.350000; 11:26:44.920 [planetinfo.record]: land_color = 0.625, 0.583344, 0.529785, 1; 11:26:44.920 [planetinfo.record]: land_fraction = 0.340000; 11:26:44.920 [planetinfo.record]: polar_cloud_color = 0.935938, 0.521245, 0.410387, 1; 11:26:44.921 [planetinfo.record]: polar_land_color = 0.9375, 0.921879, 0.901794, 1; 11:26:44.921 [planetinfo.record]: polar_sea_color = 0.791528, 0.946875, 0.859492, 1; 11:26:44.921 [planetinfo.record]: sea_color = 0.182617, 0.53125, 0.335144, 1; 11:26:44.921 [planetinfo.record]: rotation_speed = 0.003334 11:26:44.921 [planetinfo.record]: planet zpos = 714700.000000 11:26:44.921 [planetinfo.record]: sun_radius = 138582.646942 11:26:44.922 [planetinfo.record]: sun_vector = -0.461 0.883 0.088 11:26:44.922 [planetinfo.record]: sun_distance = 918900 11:26:44.922 [planetinfo.record]: corona_flare = 0.018919 11:26:44.922 [planetinfo.record]: corona_hues = 0.921135 11:26:44.922 [planetinfo.record]: sun_color = 0.588044, 0.603071, 0.719974, 1 11:26:44.922 [planetinfo.record]: corona_shimmer = 0.334511 11:26:44.922 [planetinfo.record]: station_vector = -0.978 0.210 0.004 11:26:44.922 [planetinfo.record]: station = coriolis 11:26:50.402 [PLANETINFO OVER]: Done 11:26:50.403 [PLANETINFO LOGGING]: 0 216 11:26:51.409 [planetinfo.record]: seed = 24 193 83 9 11:26:51.409 [planetinfo.record]: coordinates = 193 217 11:26:51.409 [planetinfo.record]: government = 3; 11:26:51.409 [planetinfo.record]: economy = 1; 11:26:51.409 [planetinfo.record]: techlevel = 9; 11:26:51.409 [planetinfo.record]: population = 41; 11:26:51.409 [planetinfo.record]: productivity = 20664; 11:26:51.409 [planetinfo.record]: name = "Esgerean"; 11:26:51.409 [planetinfo.record]: inhabitant = "Human Colonial"; 11:26:51.409 [planetinfo.record]: inhabitants = "Human Colonials"; 11:26:51.409 [planetinfo.record]: description = "This planet is plagued by occasional solar activity."; 11:26:51.423 [planetinfo.record]: air_color = 0.640646, 0.887133, 0.850319, 1; 11:26:51.423 [planetinfo.record]: cloud_alpha = 1.000000; 11:26:51.423 [planetinfo.record]: cloud_color = 0.664062, 0.59597, 0.508423, 1; 11:26:51.423 [planetinfo.record]: cloud_fraction = 0.510000; 11:26:51.423 [planetinfo.record]: land_color = 0.590391, 0.66, 0.642054, 1; 11:26:51.423 [planetinfo.record]: land_fraction = 0.340000; 11:26:51.424 [planetinfo.record]: polar_cloud_color = 0.798828, 0.747634, 0.681812, 1; 11:26:51.424 [planetinfo.record]: polar_land_color = 0.909373, 0.934, 0.927651, 1; 11:26:51.424 [planetinfo.record]: polar_sea_color = 0.940234, 0.932329, 0.889642, 1; 11:26:51.424 [planetinfo.record]: sea_color = 0.597656, 0.577557, 0.46902, 1; 11:26:51.424 [planetinfo.record]: rotation_speed = 0.000697 11:26:51.424 [planetinfo.record]: planet zpos = 637560.000000 11:26:51.424 [planetinfo.record]: sun_radius = 157633.214264 11:26:51.424 [planetinfo.record]: sun_vector = -0.571 -0.732 0.372 11:26:51.424 [planetinfo.record]: sun_distance = 1062600 11:26:51.424 [planetinfo.record]: corona_flare = 0.002455 11:26:51.424 [planetinfo.record]: corona_hues = 0.606354 11:26:51.425 [planetinfo.record]: sun_color = 0.489553, 0.657541, 0.666162, 1 11:26:51.425 [planetinfo.record]: corona_shimmer = 0.275927 11:26:51.425 [planetinfo.record]: station_vector = 0.124 -0.926 0.358 11:26:51.426 [planetinfo.record]: station = coriolis 11:26:57.086 [PLANETINFO OVER]: Done 11:26:57.087 [PLANETINFO LOGGING]: 0 217 11:26:58.090 [planetinfo.record]: seed = 104 27 125 45 11:26:58.090 [planetinfo.record]: coordinates = 27 110 11:26:58.090 [planetinfo.record]: government = 5; 11:26:58.090 [planetinfo.record]: economy = 6; 11:26:58.091 [planetinfo.record]: techlevel = 7; 11:26:58.091 [planetinfo.record]: population = 40; 11:26:58.091 [planetinfo.record]: productivity = 11520; 11:26:58.091 [planetinfo.record]: name = "Ditiza"; 11:26:58.091 [planetinfo.record]: inhabitant = "Human Colonial"; 11:26:58.091 [planetinfo.record]: inhabitants = "Human Colonials"; 11:26:58.091 [planetinfo.record]: description = "The planet Ditiza is reasonably fabled for Ditizaian evil juice and its inhabitants’ ingrained silliness."; 11:26:58.109 [planetinfo.record]: air_color = 0.577367, 0.534089, 0.886482, 1; 11:26:58.111 [planetinfo.record]: cloud_alpha = 1.000000; 11:26:58.111 [planetinfo.record]: cloud_color = 0.419476, 0.268982, 0.662109, 1; 11:26:58.111 [planetinfo.record]: cloud_fraction = 0.210000; 11:26:58.111 [planetinfo.record]: land_color = 0.171539, 0.601562, 0.561248, 1; 11:26:58.111 [planetinfo.record]: land_fraction = 0.490000; 11:26:58.111 [planetinfo.record]: polar_cloud_color = 0.615191, 0.501835, 0.797949, 1; 11:26:58.111 [planetinfo.record]: polar_land_color = 0.771883, 0.939844, 0.924097, 1; 11:26:58.111 [planetinfo.record]: polar_sea_color = 0.926563, 0.929492, 0.875937, 1; 11:26:58.111 [planetinfo.record]: sea_color = 0.696191, 0.705078, 0.54258, 1; 11:26:58.111 [planetinfo.record]: rotation_speed = 0.003735 11:26:58.111 [planetinfo.record]: planet zpos = 617100.000000 11:26:58.111 [planetinfo.record]: sun_radius = 145438.839111 11:26:58.111 [planetinfo.record]: sun_vector = 0.158 -0.249 0.956 11:26:58.111 [planetinfo.record]: sun_distance = 1295910 11:26:58.111 [planetinfo.record]: corona_flare = 0.026363 11:26:58.111 [planetinfo.record]: corona_hues = 0.574448 11:26:58.111 [planetinfo.record]: sun_color = 0.680246, 0.748586, 0.841266, 1 11:26:58.111 [planetinfo.record]: corona_shimmer = 0.297272 11:26:58.111 [planetinfo.record]: station_vector = -0.886 0.404 0.227 11:26:58.112 [planetinfo.record]: station = coriolis 11:27:02.732 [PLANETINFO OVER]: Done 11:27:02.733 [PLANETINFO LOGGING]: 0 218 11:27:03.741 [planetinfo.record]: seed = 172 228 219 155 11:27:03.741 [planetinfo.record]: coordinates = 228 0 11:27:03.741 [planetinfo.record]: government = 5; 11:27:03.741 [planetinfo.record]: economy = 0; 11:27:03.741 [planetinfo.record]: techlevel = 10; 11:27:03.741 [planetinfo.record]: population = 46; 11:27:03.741 [planetinfo.record]: productivity = 33120; 11:27:03.741 [planetinfo.record]: name = "Anle"; 11:27:03.741 [planetinfo.record]: inhabitant = "Black Fat Insect"; 11:27:03.741 [planetinfo.record]: inhabitants = "Black Fat Insects"; 11:27:03.741 [planetinfo.record]: description = "The world Anle is notable for its great tropical forests and Anleian evil brandy."; 11:27:03.746 [planetinfo.record]: air_color = 0.467208, 0.608783, 0.886482, 1; 11:27:03.746 [planetinfo.record]: cloud_alpha = 1.000000; 11:27:03.746 [planetinfo.record]: cloud_color = 0.118973, 0.662109, 0.662109, 1; 11:27:03.746 [planetinfo.record]: cloud_fraction = 0.340000; 11:27:03.746 [planetinfo.record]: land_color = 0.66, 0.649688, 0.65428, 1; 11:27:03.746 [planetinfo.record]: land_fraction = 0.560000; 11:27:03.746 [planetinfo.record]: polar_cloud_color = 0.388844, 0.797949, 0.797949, 1; 11:27:03.746 [planetinfo.record]: polar_land_color = 0.934, 0.930352, 0.931976, 1; 11:27:03.747 [planetinfo.record]: polar_sea_color = 0.914273, 0.942578, 0.716138, 1; 11:27:03.747 [planetinfo.record]: sea_color = 0.505245, 0.574219, 0.0224304, 1; 11:27:03.747 [planetinfo.record]: rotation_speed = 0.002488 11:27:03.747 [planetinfo.record]: planet zpos = 879000.000000 11:27:03.747 [planetinfo.record]: sun_radius = 154389.218140 11:27:03.747 [planetinfo.record]: sun_vector = -0.716 0.643 0.273 11:27:03.747 [planetinfo.record]: sun_distance = 996200 11:27:03.747 [planetinfo.record]: corona_flare = 0.036334 11:27:03.747 [planetinfo.record]: corona_hues = 0.855911 11:27:03.747 [planetinfo.record]: sun_color = 0.712628, 0.674861, 0.547465, 1 11:27:03.747 [planetinfo.record]: corona_shimmer = 0.390509 11:27:03.747 [planetinfo.record]: station_vector = 0.931 0.245 0.272 11:27:03.747 [planetinfo.record]: station = coriolis 11:27:09.022 [PLANETINFO OVER]: Done 11:27:09.024 [PLANETINFO LOGGING]: 0 219 11:27:10.045 [planetinfo.record]: seed = 132 29 45 159 11:27:10.045 [planetinfo.record]: coordinates = 29 1 11:27:10.045 [planetinfo.record]: government = 0; 11:27:10.045 [planetinfo.record]: economy = 3; 11:27:10.045 [planetinfo.record]: techlevel = 5; 11:27:10.045 [planetinfo.record]: population = 24; 11:27:10.045 [planetinfo.record]: productivity = 5376; 11:27:10.045 [planetinfo.record]: name = "Onisqu"; 11:27:10.045 [planetinfo.record]: inhabitant = "Human Colonial"; 11:27:10.045 [planetinfo.record]: inhabitants = "Human Colonials"; 11:27:10.045 [planetinfo.record]: description = "This planet is a dull place."; 11:27:10.052 [planetinfo.record]: air_color = 0.645463, 0.855121, 0.943066, 1; 11:27:10.052 [planetinfo.record]: cloud_alpha = 1.000000; 11:27:10.052 [planetinfo.record]: cloud_color = 0.572148, 0.832031, 0.54277, 1; 11:27:10.052 [planetinfo.record]: cloud_fraction = 0.280000; 11:27:10.052 [planetinfo.record]: land_color = 0.0438281, 0.28452, 0.66, 1; 11:27:10.052 [planetinfo.record]: land_fraction = 0.680000; 11:27:10.053 [planetinfo.record]: polar_cloud_color = 0.703713, 0.874414, 0.684417, 1; 11:27:10.053 [planetinfo.record]: polar_land_color = 0.716006, 0.80116, 0.934, 1; 11:27:10.053 [planetinfo.record]: polar_sea_color = 0.941406, 0.911472, 0.887441, 1; 11:27:10.053 [planetinfo.record]: sea_color = 0.585938, 0.511413, 0.451584, 1; 11:27:10.053 [planetinfo.record]: rotation_speed = 0.000506 11:27:10.053 [planetinfo.record]: planet zpos = 869050.000000 11:27:10.053 [planetinfo.record]: sun_radius = 119576.387024 11:27:10.053 [planetinfo.record]: sun_vector = -0.185 0.004 0.983 11:27:10.053 [planetinfo.record]: sun_distance = 1403850 11:27:10.053 [planetinfo.record]: corona_flare = 0.042767 11:27:10.053 [planetinfo.record]: corona_hues = 0.568443 11:27:10.053 [planetinfo.record]: sun_color = 0.809137, 0.657585, 0.573456, 1 11:27:10.053 [planetinfo.record]: corona_shimmer = 0.507465 11:27:10.054 [planetinfo.record]: station_vector = 0.610 0.707 0.357 11:27:10.054 [planetinfo.record]: station = coriolis 11:27:15.515 [PLANETINFO OVER]: Done 11:27:15.516 [PLANETINFO LOGGING]: 0 220 11:27:16.522 [planetinfo.record]: seed = 112 215 179 15 11:27:16.522 [planetinfo.record]: coordinates = 215 122 11:27:16.522 [planetinfo.record]: government = 6; 11:27:16.522 [planetinfo.record]: economy = 2; 11:27:16.522 [planetinfo.record]: techlevel = 11; 11:27:16.522 [planetinfo.record]: population = 53; 11:27:16.522 [planetinfo.record]: productivity = 33920; 11:27:16.522 [planetinfo.record]: name = "Aleusqu"; 11:27:16.522 [planetinfo.record]: inhabitant = "Green Furry Rodent"; 11:27:16.522 [planetinfo.record]: inhabitants = "Green Furry Rodents"; 11:27:16.522 [planetinfo.record]: description = "This world is reasonably notable for its great volcanoes but ravaged by vicious vicious shrews."; 11:27:16.540 [planetinfo.record]: air_color = 0.45751, 0.465861, 0.930059, 1; 11:27:16.551 [planetinfo.record]: cloud_alpha = 1.000000; 11:27:16.551 [planetinfo.record]: cloud_color = 0.0588531, 0.0875295, 0.792969, 1; 11:27:16.552 [planetinfo.record]: cloud_fraction = 0.450000; 11:27:16.552 [planetinfo.record]: land_color = 0.319688, 0.66, 0.340957, 1; 11:27:16.552 [planetinfo.record]: land_fraction = 0.630000; 11:27:16.552 [planetinfo.record]: polar_cloud_color = 0.361059, 0.380426, 0.856836, 1; 11:27:16.552 [planetinfo.record]: polar_land_color = 0.813602, 0.934, 0.821126, 1; 11:27:16.552 [planetinfo.record]: polar_sea_color = 0.935352, 0.930485, 0.878719, 1; 11:27:16.552 [planetinfo.record]: sea_color = 0.646484, 0.633029, 0.489914, 1; 11:27:16.552 [planetinfo.record]: rotation_speed = 0.000391 11:27:16.552 [planetinfo.record]: planet zpos = 824520.000000 11:27:16.552 [planetinfo.record]: sun_radius = 196148.933105 11:27:16.552 [planetinfo.record]: sun_vector = -0.933 -0.172 -0.316 11:27:16.552 [planetinfo.record]: sun_distance = 1374200 11:27:16.552 [planetinfo.record]: corona_flare = 0.031999 11:27:16.552 [planetinfo.record]: corona_hues = 0.891350 11:27:16.552 [planetinfo.record]: sun_color = 0.287828, 0.350557, 0.802686, 1 11:27:16.552 [planetinfo.record]: corona_shimmer = 0.372842 11:27:16.552 [planetinfo.record]: station_vector = 0.369 0.071 0.927 11:27:16.552 [planetinfo.record]: station = dodecahedron 11:27:21.689 [PLANETINFO OVER]: Done 11:27:21.690 [PLANETINFO LOGGING]: 0 221 11:27:22.707 [planetinfo.record]: seed = 16 186 109 100 11:27:22.707 [planetinfo.record]: coordinates = 186 72 11:27:22.707 [planetinfo.record]: government = 2; 11:27:22.708 [planetinfo.record]: economy = 0; 11:27:22.708 [planetinfo.record]: techlevel = 10; 11:27:22.708 [planetinfo.record]: population = 43; 11:27:22.708 [planetinfo.record]: productivity = 20640; 11:27:22.708 [planetinfo.record]: name = "Zasoceat"; 11:27:22.708 [planetinfo.record]: inhabitant = "Human Colonial"; 11:27:22.708 [planetinfo.record]: inhabitants = "Human Colonials"; 11:27:22.708 [planetinfo.record]: description = "Zasoceat is a revolting dump."; 11:27:22.719 [planetinfo.record]: air_color = 0.437569, 0.626787, 0.901441, 1; 11:27:22.719 [planetinfo.record]: cloud_alpha = 1.000000; 11:27:22.719 [planetinfo.record]: cloud_color = 0.0386658, 0.707031, 0.566048, 1; 11:27:22.719 [planetinfo.record]: cloud_fraction = 0.420000; 11:27:22.719 [planetinfo.record]: land_color = 0.187549, 0.619141, 0.0314407, 1; 11:27:22.719 [planetinfo.record]: land_fraction = 0.540000; 11:27:22.719 [planetinfo.record]: polar_cloud_color = 0.334776, 0.818164, 0.716199, 1; 11:27:22.719 [planetinfo.record]: polar_land_color = 0.774605, 0.938086, 0.715474, 1; 11:27:22.719 [planetinfo.record]: polar_sea_color = 0.864881, 0.921484, 0.892299, 1; 11:27:22.719 [planetinfo.record]: sea_color = 0.592241, 0.785156, 0.685684, 1; 11:27:22.719 [planetinfo.record]: rotation_speed = 0.003472 11:27:22.719 [planetinfo.record]: planet zpos = 442860.000000 11:27:22.719 [planetinfo.record]: sun_radius = 93033.060608 11:27:22.719 [planetinfo.record]: sun_vector = 0.692 0.461 0.556 11:27:22.719 [planetinfo.record]: sun_distance = 724680 11:27:22.719 [planetinfo.record]: corona_flare = 0.088591 11:27:22.719 [planetinfo.record]: corona_hues = 0.798737 11:27:22.719 [planetinfo.record]: sun_color = 0.479926, 0.61908, 0.776743, 1 11:27:22.720 [planetinfo.record]: corona_shimmer = 0.421058 11:27:22.720 [planetinfo.record]: station_vector = 0.674 -0.621 0.400 11:27:22.720 [planetinfo.record]: station = coriolis 11:27:27.794 [PLANETINFO OVER]: Done 11:27:27.794 [PLANETINFO LOGGING]: 0 222 11:27:28.796 [planetinfo.record]: seed = 100 81 91 62 11:27:28.796 [planetinfo.record]: coordinates = 81 133 11:27:28.796 [planetinfo.record]: government = 4; 11:27:28.796 [planetinfo.record]: economy = 5; 11:27:28.796 [planetinfo.record]: techlevel = 5; 11:27:28.797 [planetinfo.record]: population = 30; 11:27:28.797 [planetinfo.record]: productivity = 9600; 11:27:28.797 [planetinfo.record]: name = "Rilace"; 11:27:28.797 [planetinfo.record]: inhabitant = "Human Colonial"; 11:27:28.797 [planetinfo.record]: inhabitants = "Human Colonials"; 11:27:28.797 [planetinfo.record]: description = "The world Rilace is a dull world."; 11:27:28.805 [planetinfo.record]: air_color = 0.622821, 0.922254, 0.919598, 1; 11:27:28.805 [planetinfo.record]: cloud_alpha = 1.000000; 11:27:28.805 [planetinfo.record]: cloud_color = 0.769531, 0.765022, 0.480957, 1; 11:27:28.805 [planetinfo.record]: cloud_fraction = 0.170000; 11:27:28.805 [planetinfo.record]: land_color = 0.538627, 0.66, 0.322266, 1; 11:27:28.805 [planetinfo.record]: land_fraction = 0.240000; 11:27:28.805 [planetinfo.record]: polar_cloud_color = 0.846289, 0.84319, 0.64794, 1; 11:27:28.806 [planetinfo.record]: polar_land_color = 0.89106, 0.934, 0.814514, 1; 11:27:28.806 [planetinfo.record]: polar_sea_color = 0.910955, 0.936719, 0.889608, 1; 11:27:28.806 [planetinfo.record]: sea_color = 0.563193, 0.632812, 0.505508, 1; 11:27:28.806 [planetinfo.record]: rotation_speed = 0.004610 11:27:28.806 [planetinfo.record]: planet zpos = 842530.000000 11:27:28.806 [planetinfo.record]: sun_radius = 150820.512390 11:27:28.806 [planetinfo.record]: sun_vector = -0.006 -0.978 0.208 11:27:28.806 [planetinfo.record]: sun_distance = 1296200 11:27:28.806 [planetinfo.record]: corona_flare = 0.045323 11:27:28.806 [planetinfo.record]: corona_hues = 0.592659 11:27:28.806 [planetinfo.record]: sun_color = 0.691067, 0.723517, 0.754044, 1 11:27:28.806 [planetinfo.record]: corona_shimmer = 0.322168 11:27:28.806 [planetinfo.record]: station_vector = 0.857 0.019 0.516 11:27:28.807 [planetinfo.record]: station = coriolis 11:27:33.497 [PLANETINFO OVER]: Done 11:27:33.498 [PLANETINFO LOGGING]: 0 223 11:27:34.513 [planetinfo.record]: seed = 12 249 189 179 11:27:34.513 [planetinfo.record]: coordinates = 249 165 11:27:34.513 [planetinfo.record]: government = 1; 11:27:34.513 [planetinfo.record]: economy = 7; 11:27:34.513 [planetinfo.record]: techlevel = 2; 11:27:34.513 [planetinfo.record]: population = 17; 11:27:34.513 [planetinfo.record]: productivity = 2040; 11:27:34.513 [planetinfo.record]: name = "Beenri"; 11:27:34.513 [planetinfo.record]: inhabitant = "Harmless Fat Insect"; 11:27:34.513 [planetinfo.record]: inhabitants = "Harmless Fat Insects"; 11:27:34.513 [planetinfo.record]: description = "This planet is mildly noted for the Beenriian mountain A’oid but plagued by unpredictable civil war."; 11:27:34.529 [planetinfo.record]: air_color = 0.59635, 0.937213, 0.912907, 1; 11:27:34.529 [planetinfo.record]: cloud_alpha = 1.000000; 11:27:34.529 [planetinfo.record]: cloud_color = 0.814453, 0.733028, 0.413589, 1; 11:27:34.529 [planetinfo.record]: cloud_fraction = 0.250000; 11:27:34.529 [planetinfo.record]: land_color = 0.576172, 0.506823, 0.499649, 1; 11:27:34.529 [planetinfo.record]: land_fraction = 0.520000; 11:27:34.529 [planetinfo.record]: polar_cloud_color = 0.866504, 0.812361, 0.599952, 1; 11:27:34.529 [planetinfo.record]: polar_land_color = 0.942383, 0.914026, 0.911093, 1; 11:27:34.529 [planetinfo.record]: polar_sea_color = 0.841812, 0.913477, 0.812673, 1; 11:27:34.529 [planetinfo.record]: sea_color = 0.593713, 0.865234, 0.483315, 1; 11:27:34.529 [planetinfo.record]: rotation_speed = 0.002737 11:27:34.529 [planetinfo.record]: planet zpos = 383300.000000 11:27:34.530 [planetinfo.record]: sun_radius = 109390.460510 11:27:34.530 [planetinfo.record]: sun_vector = -0.486 0.305 -0.819 11:27:34.530 [planetinfo.record]: sun_distance = 766600 11:27:34.530 [planetinfo.record]: corona_flare = 0.043532 11:27:34.530 [planetinfo.record]: corona_hues = 0.697723 11:27:34.530 [planetinfo.record]: sun_color = 0.778296, 0.250683, 0.21677, 1 11:27:34.530 [planetinfo.record]: corona_shimmer = 0.843927 11:27:34.530 [planetinfo.record]: station_vector = -0.647 -0.074 0.759 11:27:34.530 [planetinfo.record]: station = coriolis 11:27:39.597 [PLANETINFO OVER]: Done 11:27:39.598 [PLANETINFO LOGGING]: 0 224 11:27:40.601 [planetinfo.record]: seed = 136 4 83 85 11:27:40.601 [planetinfo.record]: coordinates = 4 254 11:27:40.601 [planetinfo.record]: government = 1; 11:27:40.601 [planetinfo.record]: economy = 6; 11:27:40.601 [planetinfo.record]: techlevel = 2; 11:27:40.601 [planetinfo.record]: population = 16; 11:27:40.601 [planetinfo.record]: productivity = 2560; 11:27:40.601 [planetinfo.record]: name = "Laeden"; 11:27:40.601 [planetinfo.record]: inhabitant = "Human Colonial"; 11:27:40.601 [planetinfo.record]: inhabitants = "Human Colonials"; 11:27:40.601 [planetinfo.record]: description = "The planet Laeden is reasonably fabled for its exciting sit coms and its inhabitants’ exceptional love for food blenders."; 11:27:40.611 [planetinfo.record]: air_color = 0.579066, 0.950871, 0.927261, 1; 11:27:40.612 [planetinfo.record]: cloud_alpha = 1.000000; 11:27:40.612 [planetinfo.record]: cloud_color = 0.855469, 0.767202, 0.364243, 1; 11:27:40.612 [planetinfo.record]: cloud_fraction = 0.280000; 11:27:40.612 [planetinfo.record]: land_color = 0.471918, 0.244922, 0.66, 1; 11:27:40.612 [planetinfo.record]: land_fraction = 0.650000; 11:27:40.612 [planetinfo.record]: polar_cloud_color = 0.884961, 0.827892, 0.56736, 1; 11:27:40.614 [planetinfo.record]: polar_land_color = 0.867459, 0.78715, 0.934, 1; 11:27:40.614 [planetinfo.record]: polar_sea_color = 0.928711, 0.843231, 0.834389, 1; 11:27:40.614 [planetinfo.record]: sea_color = 0.712891, 0.45043, 0.423279, 1; 11:27:40.614 [planetinfo.record]: rotation_speed = 0.000078 11:27:40.614 [planetinfo.record]: planet zpos = 369000.000000 11:27:40.614 [planetinfo.record]: sun_radius = 85806.838989 11:27:40.614 [planetinfo.record]: sun_vector = -0.627 -0.667 0.403 11:27:40.614 [planetinfo.record]: sun_distance = 902000 11:27:40.614 [planetinfo.record]: corona_flare = 0.074507 11:27:40.614 [planetinfo.record]: corona_hues = 0.632431 11:27:40.614 [planetinfo.record]: sun_color = 0.215152, 0.399614, 0.79516, 1 11:27:40.614 [planetinfo.record]: corona_shimmer = 0.396041 11:27:40.614 [planetinfo.record]: station_vector = 0.303 -0.926 0.223 11:27:40.614 [planetinfo.record]: station = coriolis 11:27:45.150 [PLANETINFO OVER]: Done 11:27:45.151 [PLANETINFO LOGGING]: 0 225 11:27:46.156 [planetinfo.record]: seed = 120 96 157 107 11:27:46.156 [planetinfo.record]: coordinates = 96 178 11:27:46.156 [planetinfo.record]: government = 7; 11:27:46.156 [planetinfo.record]: economy = 2; 11:27:46.156 [planetinfo.record]: techlevel = 9; 11:27:46.156 [planetinfo.record]: population = 46; 11:27:46.156 [planetinfo.record]: productivity = 32384; 11:27:46.157 [planetinfo.record]: name = "Mariar"; 11:27:46.157 [planetinfo.record]: inhabitant = "Small Blue Horned Humanoid"; 11:27:46.157 [planetinfo.record]: inhabitants = "Small Blue Horned Humanoids"; 11:27:46.157 [planetinfo.record]: description = "This world is fabled for its unusual tropical forests."; 11:27:46.162 [planetinfo.record]: air_color = 0.451908, 0.508097, 0.923555, 1; 11:27:46.162 [planetinfo.record]: cloud_alpha = 1.000000; 11:27:46.162 [planetinfo.record]: cloud_color = 0.0513611, 0.299575, 0.773438, 1; 11:27:46.163 [planetinfo.record]: cloud_fraction = 0.360000; 11:27:46.163 [planetinfo.record]: land_color = 0.507891, 0.513832, 0.66, 1; 11:27:46.163 [planetinfo.record]: land_fraction = 0.360000; 11:27:46.163 [planetinfo.record]: polar_cloud_color = 0.353215, 0.523313, 0.848047, 1; 11:27:46.163 [planetinfo.record]: polar_land_color = 0.880186, 0.882288, 0.934, 1; 11:27:46.163 [planetinfo.record]: polar_sea_color = 0.942578, 0.930382, 0.893792, 1; 11:27:46.163 [planetinfo.record]: sea_color = 0.574219, 0.544498, 0.455338, 1; 11:27:46.163 [planetinfo.record]: rotation_speed = 0.003149 11:27:46.163 [planetinfo.record]: planet zpos = 687360.000000 11:27:46.163 [planetinfo.record]: sun_radius = 154980.087891 11:27:46.163 [planetinfo.record]: sun_vector = 0.614 0.588 -0.526 11:27:46.163 [planetinfo.record]: sun_distance = 1260160 11:27:46.163 [planetinfo.record]: corona_flare = 0.079787 11:27:46.163 [planetinfo.record]: corona_hues = 0.702446 11:27:46.163 [planetinfo.record]: sun_color = 0.713913, 0.655421, 0.624303, 1 11:27:46.164 [planetinfo.record]: corona_shimmer = 0.256658 11:27:46.164 [planetinfo.record]: station_vector = -0.304 -0.908 0.288 11:27:46.164 [planetinfo.record]: station = coriolis 11:27:50.602 [PLANETINFO OVER]: Done 11:27:50.603 [PLANETINFO LOGGING]: 0 226 11:27:51.609 [planetinfo.record]: seed = 220 52 27 254 11:27:51.609 [planetinfo.record]: coordinates = 52 74 11:27:51.609 [planetinfo.record]: government = 3; 11:27:51.609 [planetinfo.record]: economy = 2; 11:27:51.609 [planetinfo.record]: techlevel = 7; 11:27:51.609 [planetinfo.record]: population = 34; 11:27:51.609 [planetinfo.record]: productivity = 15232; 11:27:51.609 [planetinfo.record]: name = "Riiser"; 11:27:51.609 [planetinfo.record]: inhabitant = "Human Colonial"; 11:27:51.609 [planetinfo.record]: inhabitants = "Human Colonials"; 11:27:51.609 [planetinfo.record]: description = "Riiser is cursed by dreadful civil war."; 11:27:51.620 [planetinfo.record]: air_color = 0.793142, 0.552144, 0.934611, 1; 11:27:51.620 [planetinfo.record]: cloud_alpha = 1.000000; 11:27:51.620 [planetinfo.record]: cloud_color = 0.806641, 0.299339, 0.346899, 1; 11:27:51.620 [planetinfo.record]: cloud_fraction = 0.200000; 11:27:51.620 [planetinfo.record]: land_color = 0.592928, 0.66, 0.427969, 1; 11:27:51.620 [planetinfo.record]: land_fraction = 0.260000; 11:27:51.620 [planetinfo.record]: polar_cloud_color = 0.862988, 0.523777, 0.555578, 1; 11:27:51.621 [planetinfo.record]: polar_land_color = 0.910271, 0.934, 0.85191, 1; 11:27:51.621 [planetinfo.record]: polar_sea_color = 0.928141, 0.937891, 0.887974, 1; 11:27:51.621 [planetinfo.record]: sea_color = 0.595269, 0.621094, 0.488869, 1; 11:27:51.621 [planetinfo.record]: rotation_speed = 0.001881 11:27:51.621 [planetinfo.record]: planet zpos = 709720.000000 11:27:51.621 [planetinfo.record]: sun_radius = 192662.138672 11:27:51.621 [planetinfo.record]: sun_vector = 0.677 -0.108 0.728 11:27:51.621 [planetinfo.record]: sun_distance = 1290400 11:27:51.622 [planetinfo.record]: corona_flare = 0.015019 11:27:51.622 [planetinfo.record]: corona_hues = 0.875679 11:27:51.622 [planetinfo.record]: sun_color = 0.657028, 0.404879, 0.300781, 1 11:27:51.622 [planetinfo.record]: corona_shimmer = 0.654627 11:27:51.622 [planetinfo.record]: station_vector = -0.744 0.407 0.529 11:27:51.622 [planetinfo.record]: station = coriolis 11:27:57.010 [PLANETINFO OVER]: Done 11:27:57.011 [PLANETINFO LOGGING]: 0 227 11:27:58.024 [planetinfo.record]: seed = 84 44 141 90 11:27:58.024 [planetinfo.record]: coordinates = 44 176 11:27:58.024 [planetinfo.record]: government = 2; 11:27:58.024 [planetinfo.record]: economy = 0; 11:27:58.024 [planetinfo.record]: techlevel = 8; 11:27:58.024 [planetinfo.record]: population = 35; 11:27:58.024 [planetinfo.record]: productivity = 16800; 11:27:58.024 [planetinfo.record]: name = "Qutiri"; 11:27:58.024 [planetinfo.record]: inhabitant = "Yellow Fat Feline"; 11:27:58.024 [planetinfo.record]: inhabitants = "Yellow Fat Felines"; 11:27:58.024 [planetinfo.record]: description = "The world Qutiri is mildly noted for its ancient mountains but plagued by deadly earthquakes."; 11:27:58.048 [planetinfo.record]: air_color = 0.450496, 0.705289, 0.867621, 1; 11:27:58.048 [planetinfo.record]: cloud_alpha = 1.000000; 11:27:58.048 [planetinfo.record]: cloud_color = 0.0946045, 0.605469, 0.1864, 1; 11:27:58.048 [planetinfo.record]: cloud_fraction = 0.340000; 11:27:58.048 [planetinfo.record]: land_color = 0.66, 0.0386719, 0.475543, 1; 11:27:58.048 [planetinfo.record]: land_fraction = 0.630000; 11:27:58.048 [planetinfo.record]: polar_cloud_color = 0.365108, 0.772461, 0.438305, 1; 11:27:58.048 [planetinfo.record]: polar_land_color = 0.934, 0.714182, 0.868741, 1; 11:27:58.048 [planetinfo.record]: polar_sea_color = 0.939258, 0.938472, 0.888993, 1; 11:27:58.048 [planetinfo.record]: sea_color = 0.607422, 0.60539, 0.477396, 1; 11:27:58.048 [planetinfo.record]: rotation_speed = 0.004852 11:27:58.048 [planetinfo.record]: planet zpos = 650400.000000 11:27:58.048 [planetinfo.record]: sun_radius = 136381.610107 11:27:58.048 [planetinfo.record]: sun_vector = -0.053 -0.721 0.691 11:27:58.048 [planetinfo.record]: sun_distance = 1084000 11:27:58.048 [planetinfo.record]: corona_flare = 0.095195 11:27:58.048 [planetinfo.record]: corona_hues = 0.587379 11:27:58.048 [planetinfo.record]: sun_color = 0.612425, 0.628298, 0.743295, 1 11:27:58.048 [planetinfo.record]: corona_shimmer = 0.633712 11:27:58.049 [planetinfo.record]: station_vector = 0.880 0.213 0.425 11:27:58.049 [planetinfo.record]: station = coriolis 11:28:03.052 [PLANETINFO OVER]: Done 11:28:03.054 [PLANETINFO LOGGING]: 0 228 11:28:04.058 [planetinfo.record]: seed = 96 80 51 70 11:28:04.058 [planetinfo.record]: coordinates = 80 190 11:28:04.058 [planetinfo.record]: government = 4; 11:28:04.058 [planetinfo.record]: economy = 6; 11:28:04.058 [planetinfo.record]: techlevel = 3; 11:28:04.058 [planetinfo.record]: population = 23; 11:28:04.058 [planetinfo.record]: productivity = 5888; 11:28:04.058 [planetinfo.record]: name = "Biramabi"; 11:28:04.058 [planetinfo.record]: inhabitant = "Human Colonial"; 11:28:04.058 [planetinfo.record]: inhabitants = "Human Colonials"; 11:28:04.058 [planetinfo.record]: description = "The world Biramabi is a dull world."; 11:28:04.068 [planetinfo.record]: air_color = 0.509579, 0.553474, 0.878027, 1; 11:28:04.068 [planetinfo.record]: cloud_alpha = 1.000000; 11:28:04.068 [planetinfo.record]: cloud_color = 0.216385, 0.360875, 0.636719, 1; 11:28:04.068 [planetinfo.record]: cloud_fraction = 0.480000; 11:28:04.068 [planetinfo.record]: land_color = 0.378984, 0.66, 0.528274, 1; 11:28:04.068 [planetinfo.record]: land_fraction = 0.300000; 11:28:04.068 [planetinfo.record]: polar_cloud_color = 0.462006, 0.573559, 0.786523, 1; 11:28:04.068 [planetinfo.record]: polar_land_color = 0.83458, 0.934, 0.887397, 1; 11:28:04.068 [planetinfo.record]: polar_sea_color = 0.915227, 0.932617, 0.87697, 1; 11:28:04.068 [planetinfo.record]: sea_color = 0.623571, 0.673828, 0.513004, 1; 11:28:04.068 [planetinfo.record]: rotation_speed = 0.004770 11:28:04.068 [planetinfo.record]: planet zpos = 487520.000000 11:28:04.069 [planetinfo.record]: sun_radius = 109145.168457 11:28:04.069 [planetinfo.record]: sun_vector = -0.215 0.856 0.471 11:28:04.069 [planetinfo.record]: sun_distance = 842080 11:28:04.069 [planetinfo.record]: corona_flare = 0.026309 11:28:04.069 [planetinfo.record]: corona_hues = 0.948486 11:28:04.069 [planetinfo.record]: sun_color = 0.209416, 0.744733, 0.822418, 1 11:28:04.069 [planetinfo.record]: corona_shimmer = 0.359602 11:28:04.069 [planetinfo.record]: station_vector = 0.682 0.404 0.609 11:28:04.069 [planetinfo.record]: station = coriolis 11:28:08.842 [PLANETINFO OVER]: Done 11:28:08.843 [PLANETINFO LOGGING]: 0 229 11:28:09.848 [planetinfo.record]: seed = 160 134 13 199 11:28:09.848 [planetinfo.record]: coordinates = 134 235 11:28:09.848 [planetinfo.record]: government = 4; 11:28:09.848 [planetinfo.record]: economy = 3; 11:28:09.848 [planetinfo.record]: techlevel = 8; 11:28:09.848 [planetinfo.record]: population = 40; 11:28:09.848 [planetinfo.record]: productivity = 17920; 11:28:09.848 [planetinfo.record]: name = "Soorbi"; 11:28:09.848 [planetinfo.record]: inhabitant = "Human Colonial"; 11:28:09.848 [planetinfo.record]: inhabitants = "Human Colonials"; 11:28:09.848 [planetinfo.record]: description = "The planet Soorbi is an unremarkable dump."; 11:28:09.871 [planetinfo.record]: air_color = 0.660342, 0.591143, 0.853963, 1; 11:28:09.871 [planetinfo.record]: cloud_alpha = 1.000000; 11:28:09.871 [planetinfo.record]: cloud_color = 0.52871, 0.381447, 0.564453, 1; 11:28:09.871 [planetinfo.record]: cloud_fraction = 0.400000; 11:28:09.871 [planetinfo.record]: land_color = 0.66, 0.0489844, 0.636132, 1; 11:28:09.871 [planetinfo.record]: land_fraction = 0.530000; 11:28:09.871 [planetinfo.record]: polar_cloud_color = 0.724162, 0.601215, 0.754004, 1; 11:28:09.871 [planetinfo.record]: polar_land_color = 0.934, 0.71783, 0.925556, 1; 11:28:09.872 [planetinfo.record]: polar_sea_color = 0.939062, 0.895026, 0.879729, 1; 11:28:09.872 [planetinfo.record]: sea_color = 0.609375, 0.495071, 0.455365, 1; 11:28:09.872 [planetinfo.record]: rotation_speed = 0.002884 11:28:09.872 [planetinfo.record]: planet zpos = 426780.000000 11:28:09.872 [planetinfo.record]: sun_radius = 104771.746216 11:28:09.872 [planetinfo.record]: sun_vector = 0.336 0.760 -0.556 11:28:09.873 [planetinfo.record]: sun_distance = 1043240 11:28:09.873 [planetinfo.record]: corona_flare = 0.094627 11:28:09.873 [planetinfo.record]: corona_hues = 0.564255 11:28:09.873 [planetinfo.record]: sun_color = 0.43584, 0.460039, 0.718594, 1 11:28:09.873 [planetinfo.record]: corona_shimmer = 0.291522 11:28:09.874 [planetinfo.record]: station_vector = 0.258 0.014 0.966 11:28:09.874 [planetinfo.record]: station = coriolis 11:28:14.700 [PLANETINFO OVER]: Done 11:28:14.701 [PLANETINFO LOGGING]: 0 230 11:28:15.704 [planetinfo.record]: seed = 20 135 27 71 11:28:15.704 [planetinfo.record]: coordinates = 135 134 11:28:15.704 [planetinfo.record]: government = 2; 11:28:15.704 [planetinfo.record]: economy = 6; 11:28:15.704 [planetinfo.record]: techlevel = 5; 11:28:15.704 [planetinfo.record]: population = 29; 11:28:15.704 [planetinfo.record]: productivity = 5568; 11:28:15.704 [planetinfo.record]: name = "Solageon"; 11:28:15.704 [planetinfo.record]: inhabitant = "Human Colonial"; 11:28:15.704 [planetinfo.record]: inhabitants = "Human Colonials"; 11:28:15.704 [planetinfo.record]: description = "This world is very well known for Solageonian lethal water and the Solageonian tree wolf."; 11:28:15.716 [planetinfo.record]: air_color = 0.440752, 0.807745, 0.838354, 1; 11:28:15.716 [planetinfo.record]: cloud_alpha = 1.000000; 11:28:15.716 [planetinfo.record]: cloud_color = 0.414262, 0.517578, 0.0909805, 1; 11:28:15.716 [planetinfo.record]: cloud_fraction = 0.310000; 11:28:15.716 [planetinfo.record]: land_color = 0.362444, 0.307724, 0.587891, 1; 11:28:15.716 [planetinfo.record]: land_fraction = 0.380000; 11:28:15.716 [planetinfo.record]: polar_cloud_color = 0.641473, 0.73291, 0.355361, 1; 11:28:15.716 [planetinfo.record]: polar_land_color = 0.850976, 0.829074, 0.941211, 1; 11:28:15.716 [planetinfo.record]: polar_sea_color = 0.939453, 0.915411, 0.884499, 1; 11:28:15.716 [planetinfo.record]: sea_color = 0.605469, 0.543488, 0.463799, 1; 11:28:15.716 [planetinfo.record]: rotation_speed = 0.003998 11:28:15.716 [planetinfo.record]: planet zpos = 569160.000000 11:28:15.716 [planetinfo.record]: sun_radius = 99534.535675 11:28:15.716 [planetinfo.record]: sun_vector = 0.274 -0.367 -0.889 11:28:15.716 [planetinfo.record]: sun_distance = 948600 11:28:15.716 [planetinfo.record]: corona_flare = 0.011966 11:28:15.716 [planetinfo.record]: corona_hues = 0.693237 11:28:15.716 [planetinfo.record]: sun_color = 0.386474, 0.634417, 0.650995, 1 11:28:15.716 [planetinfo.record]: corona_shimmer = 0.460587 11:28:15.716 [planetinfo.record]: station_vector = 0.221 -0.021 0.975 11:28:15.716 [planetinfo.record]: station = coriolis 11:28:20.285 [PLANETINFO OVER]: Done 11:28:20.286 [PLANETINFO LOGGING]: 0 231 11:28:21.289 [planetinfo.record]: seed = 92 191 157 55 11:28:21.289 [planetinfo.record]: coordinates = 191 35 11:28:21.289 [planetinfo.record]: government = 3; 11:28:21.289 [planetinfo.record]: economy = 3; 11:28:21.289 [planetinfo.record]: techlevel = 9; 11:28:21.289 [planetinfo.record]: population = 43; 11:28:21.289 [planetinfo.record]: productivity = 16856; 11:28:21.289 [planetinfo.record]: name = "Tiquat"; 11:28:21.289 [planetinfo.record]: inhabitant = "Red Fat Insect"; 11:28:21.289 [planetinfo.record]: inhabitants = "Red Fat Insects"; 11:28:21.289 [planetinfo.record]: description = "This world is reasonably well known for its great parking meters but cursed by unpredictable earthquakes."; 11:28:21.300 [planetinfo.record]: air_color = 0.697841, 0.620205, 0.837703, 1; 11:28:21.300 [planetinfo.record]: cloud_alpha = 1.000000; 11:28:21.300 [planetinfo.record]: cloud_color = 0.515625, 0.422974, 0.507663, 1; 11:28:21.300 [planetinfo.record]: cloud_fraction = 0.240000; 11:28:21.300 [planetinfo.record]: land_color = 0.630392, 0.66, 0.281016, 1; 11:28:21.300 [planetinfo.record]: land_fraction = 0.310000; 11:28:21.300 [planetinfo.record]: polar_cloud_color = 0.732031, 0.649821, 0.724966, 1; 11:28:21.300 [planetinfo.record]: polar_land_color = 0.923525, 0.934, 0.79992, 1; 11:28:21.300 [planetinfo.record]: polar_sea_color = 0.876613, 0.928906, 0.876202, 1; 11:28:21.300 [planetinfo.record]: sea_color = 0.550849, 0.710938, 0.549588, 1; 11:28:21.300 [planetinfo.record]: rotation_speed = 0.002046 11:28:21.300 [planetinfo.record]: planet zpos = 431910.000000 11:28:21.300 [planetinfo.record]: sun_radius = 104075.236969 11:28:21.300 [planetinfo.record]: sun_vector = -0.116 0.934 0.338 11:28:21.300 [planetinfo.record]: sun_distance = 1103770 11:28:21.300 [planetinfo.record]: corona_flare = 0.053975 11:28:21.300 [planetinfo.record]: corona_hues = 0.790787 11:28:21.300 [planetinfo.record]: sun_color = 0.737674, 0.797416, 0.825589, 1 11:28:21.300 [planetinfo.record]: corona_shimmer = 0.427680 11:28:21.300 [planetinfo.record]: station_vector = 0.417 0.857 0.301 11:28:21.300 [planetinfo.record]: station = coriolis 11:28:26.387 [PLANETINFO OVER]: Done 11:28:26.387 [PLANETINFO LOGGING]: 0 232 11:28:27.391 [planetinfo.record]: seed = 248 98 83 142 11:28:27.391 [planetinfo.record]: coordinates = 98 17 11:28:27.391 [planetinfo.record]: government = 7; 11:28:27.391 [planetinfo.record]: economy = 1; 11:28:27.391 [planetinfo.record]: techlevel = 12; 11:28:27.391 [planetinfo.record]: population = 57; 11:28:27.391 [planetinfo.record]: productivity = 45144; 11:28:27.391 [planetinfo.record]: name = "Rexebe"; 11:28:27.391 [planetinfo.record]: inhabitant = "Human Colonial"; 11:28:27.392 [planetinfo.record]: inhabitants = "Human Colonials"; 11:28:27.392 [planetinfo.record]: description = "This world is mildly famous for its hoopy night life and its exotic cuisine."; 11:28:27.396 [planetinfo.record]: air_color = 0.620244, 0.907945, 0.867222, 1; 11:28:27.396 [planetinfo.record]: cloud_alpha = 1.000000; 11:28:27.396 [planetinfo.record]: cloud_color = 0.726562, 0.620798, 0.47113, 1; 11:28:27.396 [planetinfo.record]: cloud_fraction = 0.300000; 11:28:27.396 [planetinfo.record]: land_color = 0.205078, 0.546875, 0.482788, 1; 11:28:27.396 [planetinfo.record]: land_fraction = 0.250000; 11:28:27.396 [planetinfo.record]: polar_cloud_color = 0.826953, 0.751716, 0.64525, 1; 11:28:27.397 [planetinfo.record]: polar_land_color = 0.797607, 0.945312, 0.917618, 1; 11:28:27.397 [planetinfo.record]: polar_sea_color = 0.923633, 0.90968, 0.834336, 1; 11:28:27.397 [planetinfo.record]: sea_color = 0.763672, 0.717527, 0.468346, 1; 11:28:27.397 [planetinfo.record]: rotation_speed = 0.004493 11:28:27.397 [planetinfo.record]: planet zpos = 779760.000000 11:28:27.397 [planetinfo.record]: sun_radius = 133523.508911 11:28:27.397 [planetinfo.record]: sun_vector = -0.271 -0.571 -0.775 11:28:27.397 [planetinfo.record]: sun_distance = 1169640 11:28:27.397 [planetinfo.record]: corona_flare = 0.054688 11:28:27.397 [planetinfo.record]: corona_hues = 0.895706 11:28:27.397 [planetinfo.record]: sun_color = 0.605687, 0.676184, 0.680093, 1 11:28:27.397 [planetinfo.record]: corona_shimmer = 0.455459 11:28:27.397 [planetinfo.record]: station_vector = 0.261 0.937 0.231 11:28:27.397 [planetinfo.record]: station = dodecahedron 11:28:32.827 [PLANETINFO OVER]: Done 11:28:32.828 [PLANETINFO LOGGING]: 0 233 11:28:33.850 [planetinfo.record]: seed = 136 132 189 122 11:28:33.850 [planetinfo.record]: coordinates = 132 243 11:28:33.850 [planetinfo.record]: government = 1; 11:28:33.850 [planetinfo.record]: economy = 3; 11:28:33.850 [planetinfo.record]: techlevel = 5; 11:28:33.850 [planetinfo.record]: population = 25; 11:28:33.850 [planetinfo.record]: productivity = 7000; 11:28:33.850 [planetinfo.record]: name = "Qubeen"; 11:28:33.850 [planetinfo.record]: inhabitant = "Blue Frog"; 11:28:33.850 [planetinfo.record]: inhabitants = "Blue Frogs"; 11:28:33.850 [planetinfo.record]: description = "This world is ravaged by unpredictable civil war."; 11:28:33.864 [planetinfo.record]: air_color = 0.46698, 0.467926, 0.924855, 1; 11:28:33.864 [planetinfo.record]: cloud_alpha = 1.000000; 11:28:33.864 [planetinfo.record]: cloud_color = 0.0934435, 0.0880585, 0.777344, 1; 11:28:33.864 [planetinfo.record]: cloud_fraction = 0.240000; 11:28:33.864 [planetinfo.record]: land_color = 0.585234, 0.66, 0.645981, 1; 11:28:33.864 [planetinfo.record]: land_fraction = 0.380000; 11:28:33.864 [planetinfo.record]: polar_cloud_color = 0.382523, 0.378844, 0.849805, 1; 11:28:33.864 [planetinfo.record]: polar_land_color = 0.907549, 0.934, 0.92904, 1; 11:28:33.864 [planetinfo.record]: polar_sea_color = 0.887635, 0.923828, 0.865187, 1; 11:28:33.864 [planetinfo.record]: sea_color = 0.642352, 0.761719, 0.568314, 1; 11:28:33.864 [planetinfo.record]: rotation_speed = 0.002537 11:28:33.864 [planetinfo.record]: planet zpos = 605880.000000 11:28:33.864 [planetinfo.record]: sun_radius = 116191.098633 11:28:33.864 [planetinfo.record]: sun_vector = -0.183 0.958 -0.221 11:28:33.864 [planetinfo.record]: sun_distance = 1211760 11:28:33.864 [planetinfo.record]: corona_flare = 0.047691 11:28:33.864 [planetinfo.record]: corona_hues = 0.864838 11:28:33.864 [planetinfo.record]: sun_color = 0.753384, 0.752409, 0.659941, 1 11:28:33.865 [planetinfo.record]: corona_shimmer = 1.102965 11:28:33.865 [planetinfo.record]: station_vector = 0.077 0.699 0.711 11:28:33.865 [planetinfo.record]: station = coriolis 11:28:39.206 [PLANETINFO OVER]: Done 11:28:39.207 [PLANETINFO LOGGING]: 0 234 11:28:40.210 [planetinfo.record]: seed = 12 96 91 69 11:28:40.210 [planetinfo.record]: coordinates = 96 242 11:28:40.210 [planetinfo.record]: government = 1; 11:28:40.211 [planetinfo.record]: economy = 2; 11:28:40.211 [planetinfo.record]: techlevel = 6; 11:28:40.211 [planetinfo.record]: population = 28; 11:28:40.211 [planetinfo.record]: productivity = 8960; 11:28:40.211 [planetinfo.record]: name = "Cetiisqu"; 11:28:40.211 [planetinfo.record]: inhabitant = "Human Colonial"; 11:28:40.211 [planetinfo.record]: inhabitants = "Human Colonials"; 11:28:40.211 [planetinfo.record]: description = "This planet is reasonably famous for the Cetiisquian evil Stoid."; 11:28:40.219 [planetinfo.record]: air_color = 0.58162, 0.514687, 0.909246, 1; 11:28:40.220 [planetinfo.record]: cloud_alpha = 1.000000; 11:28:40.220 [planetinfo.record]: cloud_color = 0.485701, 0.216858, 0.730469, 1; 11:28:40.220 [planetinfo.record]: cloud_fraction = 0.580000; 11:28:40.220 [planetinfo.record]: land_color = 0.652266, 0.66, 0.652749, 1; 11:28:40.220 [planetinfo.record]: land_fraction = 0.450000; 11:28:40.220 [planetinfo.record]: polar_cloud_color = 0.655157, 0.464531, 0.828711, 1; 11:28:40.220 [planetinfo.record]: polar_land_color = 0.931264, 0.934, 0.931435, 1; 11:28:40.220 [planetinfo.record]: polar_sea_color = 0.76988, 0.944141, 0.813445, 1; 11:28:40.220 [planetinfo.record]: sea_color = 0.146194, 0.558594, 0.249294, 1; 11:28:40.220 [planetinfo.record]: rotation_speed = 0.001179 11:28:40.220 [planetinfo.record]: planet zpos = 544960.000000 11:28:40.220 [planetinfo.record]: sun_radius = 100838.017578 11:28:40.220 [planetinfo.record]: sun_vector = 0.004 -0.671 -0.742 11:28:40.220 [planetinfo.record]: sun_distance = 796480 11:28:40.220 [planetinfo.record]: corona_flare = 0.046107 11:28:40.220 [planetinfo.record]: corona_hues = 0.718681 11:28:40.220 [planetinfo.record]: sun_color = 0.610857, 0.734736, 0.744547, 1 11:28:40.221 [planetinfo.record]: corona_shimmer = 0.641478 11:28:40.221 [planetinfo.record]: station_vector = -0.857 0.372 0.358 11:28:40.221 [planetinfo.record]: station = coriolis 11:28:44.795 [PLANETINFO OVER]: Done 11:28:44.796 [PLANETINFO LOGGING]: 0 235 11:28:45.800 [planetinfo.record]: seed = 36 26 237 238 11:28:45.800 [planetinfo.record]: coordinates = 26 61 11:28:45.800 [planetinfo.record]: government = 4; 11:28:45.800 [planetinfo.record]: economy = 5; 11:28:45.800 [planetinfo.record]: techlevel = 6; 11:28:45.800 [planetinfo.record]: population = 34; 11:28:45.800 [planetinfo.record]: productivity = 10880; 11:28:45.800 [planetinfo.record]: name = "Rebia"; 11:28:45.800 [planetinfo.record]: inhabitant = "Frog"; 11:28:45.800 [planetinfo.record]: inhabitants = "Frogs"; 11:28:45.800 [planetinfo.record]: description = "Rebia is very notable for its inhabitants’ weird shyness."; 11:28:45.816 [planetinfo.record]: air_color = 0.723415, 0.809782, 0.904043, 1; 11:28:45.816 [planetinfo.record]: cloud_alpha = 1.000000; 11:28:45.816 [planetinfo.record]: cloud_color = 0.714844, 0.714844, 0.714844, 1; 11:28:45.816 [planetinfo.record]: cloud_fraction = 0.570000; 11:28:45.816 [planetinfo.record]: land_color = 0.613281, 0.395279, 0.569, 1; 11:28:45.816 [planetinfo.record]: land_fraction = 0.330000; 11:28:45.816 [planetinfo.record]: polar_cloud_color = 0.82168, 0.82168, 0.82168, 1; 11:28:45.816 [planetinfo.record]: polar_land_color = 0.938672, 0.855255, 0.921728, 1; 11:28:45.816 [planetinfo.record]: polar_sea_color = 0.917578, 0.84501, 0.786752, 1; 11:28:45.816 [planetinfo.record]: sea_color = 0.824219, 0.563481, 0.354156, 1; 11:28:45.816 [planetinfo.record]: rotation_speed = 0.004292 11:28:45.816 [planetinfo.record]: planet zpos = 771120.000000 11:28:45.816 [planetinfo.record]: sun_radius = 199615.273132 11:28:45.816 [planetinfo.record]: sun_vector = 0.171 0.917 -0.359 11:28:45.816 [planetinfo.record]: sun_distance = 1285200 11:28:45.816 [planetinfo.record]: corona_flare = 0.018170 11:28:45.816 [planetinfo.record]: corona_hues = 0.784355 11:28:45.816 [planetinfo.record]: sun_color = 0.817975, 0.765844, 0.722596, 1 11:28:45.817 [planetinfo.record]: corona_shimmer = 0.337300 11:28:45.817 [planetinfo.record]: station_vector = -0.502 -0.862 0.064 11:28:45.817 [planetinfo.record]: station = coriolis 11:28:50.467 [PLANETINFO OVER]: Done 11:28:50.468 [PLANETINFO LOGGING]: 0 236 11:28:51.475 [planetinfo.record]: seed = 80 132 179 25 11:28:51.475 [planetinfo.record]: coordinates = 132 79 11:28:51.475 [planetinfo.record]: government = 2; 11:28:51.475 [planetinfo.record]: economy = 7; 11:28:51.475 [planetinfo.record]: techlevel = 1; 11:28:51.475 [planetinfo.record]: population = 14; 11:28:51.475 [planetinfo.record]: productivity = 2016; 11:28:51.475 [planetinfo.record]: name = "Ordima"; 11:28:51.475 [planetinfo.record]: inhabitant = "Green Bony Bird"; 11:28:51.475 [planetinfo.record]: inhabitants = "Green Bony Birds"; 11:28:51.475 [planetinfo.record]: description = "This planet is reasonably noted for its exotic goat soup."; 11:28:51.508 [planetinfo.record]: air_color = 0.602545, 0.934691, 0.945018, 1; 11:28:51.508 [planetinfo.record]: cloud_alpha = 1.000000; 11:28:51.508 [planetinfo.record]: cloud_color = 0.796339, 0.837891, 0.428764, 1; 11:28:51.508 [planetinfo.record]: cloud_fraction = 0.060000; 11:28:51.508 [planetinfo.record]: land_color = 0.66, 0.0309375, 0.502734, 1; 11:28:51.508 [planetinfo.record]: land_fraction = 0.380000; 11:28:51.508 [planetinfo.record]: polar_cloud_color = 0.849867, 0.877051, 0.609396, 1; 11:28:51.508 [planetinfo.record]: polar_land_color = 0.934, 0.711445, 0.878361, 1; 11:28:51.508 [planetinfo.record]: polar_sea_color = 0.944336, 0.920661, 0.895459, 1; 11:28:51.508 [planetinfo.record]: sea_color = 0.556641, 0.50082, 0.441399, 1; 11:28:51.508 [planetinfo.record]: rotation_speed = 0.004133 11:28:51.508 [planetinfo.record]: planet zpos = 787800.000000 11:28:51.508 [planetinfo.record]: sun_radius = 106315.815430 11:28:51.508 [planetinfo.record]: sun_vector = 0.628 -0.721 -0.294 11:28:51.508 [planetinfo.record]: sun_distance = 1207960 11:28:51.508 [planetinfo.record]: corona_flare = 0.004103 11:28:51.508 [planetinfo.record]: corona_hues = 0.628014 11:28:51.508 [planetinfo.record]: sun_color = 0.660074, 0.566461, 0.454546, 1 11:28:51.508 [planetinfo.record]: corona_shimmer = 0.650043 11:28:51.509 [planetinfo.record]: station_vector = -0.339 -0.757 0.558 11:28:51.509 [planetinfo.record]: station = coriolis 11:28:56.573 [PLANETINFO OVER]: Done 11:28:56.574 [PLANETINFO LOGGING]: 0 237 11:28:57.576 [planetinfo.record]: seed = 48 146 173 10 11:28:57.577 [planetinfo.record]: coordinates = 146 139 11:28:57.577 [planetinfo.record]: government = 6; 11:28:57.577 [planetinfo.record]: economy = 3; 11:28:57.577 [planetinfo.record]: techlevel = 9; 11:28:57.577 [planetinfo.record]: population = 46; 11:28:57.577 [planetinfo.record]: productivity = 25760; 11:28:57.577 [planetinfo.record]: name = "Aruszati"; 11:28:57.577 [planetinfo.record]: inhabitant = "Small Green Bug-Eyed Lobster"; 11:28:57.577 [planetinfo.record]: inhabitants = "Small Green Bug-Eyed Lobsters"; 11:28:57.577 [planetinfo.record]: description = "This planet is noted for Zero-G cricket."; 11:28:57.608 [planetinfo.record]: air_color = 0.680866, 0.499772, 0.94827, 1; 11:28:57.608 [planetinfo.record]: cloud_alpha = 1.000000; 11:28:57.615 [planetinfo.record]: cloud_color = 0.847656, 0.152313, 0.690118, 1; 11:28:57.615 [planetinfo.record]: cloud_fraction = 0.220000; 11:28:57.616 [planetinfo.record]: land_color = 0.513047, 0.556674, 0.66, 1; 11:28:57.616 [planetinfo.record]: land_fraction = 0.370000; 11:28:57.616 [planetinfo.record]: polar_cloud_color = 0.881445, 0.429532, 0.779059, 1; 11:28:57.616 [planetinfo.record]: polar_land_color = 0.88201, 0.897444, 0.934, 1; 11:28:57.616 [planetinfo.record]: polar_sea_color = 0.913328, 0.947852, 0.746988, 1; 11:28:57.616 [planetinfo.record]: sea_color = 0.445509, 0.521484, 0.0794449, 1; 11:28:57.616 [planetinfo.record]: rotation_speed = 0.002175 11:28:57.616 [planetinfo.record]: planet zpos = 717860.000000 11:28:57.616 [planetinfo.record]: sun_radius = 125710.264587 11:28:57.616 [planetinfo.record]: sun_vector = -0.417 -0.889 0.190 11:28:57.616 [planetinfo.record]: sun_distance = 1270060 11:28:57.616 [planetinfo.record]: corona_flare = 0.084695 11:28:57.616 [planetinfo.record]: corona_hues = 0.997299 11:28:57.616 [planetinfo.record]: sun_color = 0.676862, 0.720401, 0.726685, 1 11:28:57.616 [planetinfo.record]: corona_shimmer = 0.433399 11:28:57.617 [planetinfo.record]: station_vector = 0.628 0.151 0.763 11:28:57.617 [planetinfo.record]: station = coriolis 11:29:01.964 [PLANETINFO OVER]: Done 11:29:01.965 [PLANETINFO LOGGING]: 0 238 11:29:02.969 [planetinfo.record]: seed = 196 247 219 228 11:29:02.969 [planetinfo.record]: coordinates = 247 196 11:29:02.969 [planetinfo.record]: government = 0; 11:29:02.969 [planetinfo.record]: economy = 6; 11:29:02.969 [planetinfo.record]: techlevel = 4; 11:29:02.969 [planetinfo.record]: population = 23; 11:29:02.969 [planetinfo.record]: productivity = 2944; 11:29:02.969 [planetinfo.record]: name = "Zaleriza"; 11:29:02.969 [planetinfo.record]: inhabitant = "Fierce Bony Lobster"; 11:29:02.969 [planetinfo.record]: inhabitants = "Fierce Bony Lobsters"; 11:29:02.970 [planetinfo.record]: description = "This world is a tedious place."; 11:29:02.978 [planetinfo.record]: air_color = 0.70764, 0.763937, 0.954123, 1; 11:29:02.979 [planetinfo.record]: cloud_alpha = 1.000000; 11:29:02.979 [planetinfo.record]: cloud_color = 0.713142, 0.815329, 0.865234, 1; 11:29:02.979 [planetinfo.record]: cloud_fraction = 0.540000; 11:29:02.979 [planetinfo.record]: land_color = 0.448449, 0.341003, 0.578125, 1; 11:29:02.979 [planetinfo.record]: land_fraction = 0.520000; 11:29:02.980 [planetinfo.record]: polar_cloud_color = 0.791648, 0.857295, 0.889355, 1; 11:29:02.980 [planetinfo.record]: polar_land_color = 0.889353, 0.845576, 0.942187, 1; 11:29:02.980 [planetinfo.record]: polar_sea_color = 0.931055, 0.868637, 0.851951, 1; 11:29:02.980 [planetinfo.record]: sea_color = 0.689453, 0.504571, 0.455147, 1; 11:29:02.980 [planetinfo.record]: rotation_speed = 0.003462 11:29:02.980 [planetinfo.record]: planet zpos = 490440.000000 11:29:02.980 [planetinfo.record]: sun_radius = 111392.202759 11:29:02.980 [planetinfo.record]: sun_vector = -0.073 -0.981 0.180 11:29:02.980 [planetinfo.record]: sun_distance = 858270 11:29:02.980 [planetinfo.record]: corona_flare = 0.044519 11:29:02.981 [planetinfo.record]: corona_hues = 0.750961 11:29:02.981 [planetinfo.record]: sun_color = 0.32954, 0.459733, 0.741595, 1 11:29:02.981 [planetinfo.record]: corona_shimmer = 0.267306 11:29:02.981 [planetinfo.record]: station_vector = -0.848 0.268 0.458 11:29:02.981 [planetinfo.record]: station = coriolis 11:29:08.071 [PLANETINFO OVER]: Done 11:29:08.072 [PLANETINFO LOGGING]: 0 239 11:29:09.075 [planetinfo.record]: seed = 172 4 125 36 11:29:09.075 [planetinfo.record]: coordinates = 4 126 11:29:09.075 [planetinfo.record]: government = 5; 11:29:09.075 [planetinfo.record]: economy = 6; 11:29:09.075 [planetinfo.record]: techlevel = 4; 11:29:09.075 [planetinfo.record]: population = 28; 11:29:09.075 [planetinfo.record]: productivity = 8064; 11:29:09.075 [planetinfo.record]: name = "Zasoer"; 11:29:09.075 [planetinfo.record]: inhabitant = "Human Colonial"; 11:29:09.075 [planetinfo.record]: inhabitants = "Human Colonials"; 11:29:09.075 [planetinfo.record]: description = "Zasoer is mildly well known for its exotic night life."; 11:29:09.104 [planetinfo.record]: air_color = 0.624873, 0.894287, 0.837228, 1; 11:29:09.104 [planetinfo.record]: cloud_alpha = 1.000000; 11:29:09.104 [planetinfo.record]: cloud_color = 0.685547, 0.554998, 0.476669, 1; 11:29:09.104 [planetinfo.record]: cloud_fraction = 0.550000; 11:29:09.104 [planetinfo.record]: land_color = 0.126999, 0.560547, 0.164257, 1; 11:29:09.104 [planetinfo.record]: land_fraction = 0.640000; 11:29:09.104 [planetinfo.record]: polar_cloud_color = 0.808496, 0.71227, 0.654534, 1; 11:29:09.104 [planetinfo.record]: polar_land_color = 0.761425, 0.943945, 0.77711, 1; 11:29:09.104 [planetinfo.record]: polar_sea_color = 0.948047, 0.888701, 0.935992, 1; 11:29:09.104 [planetinfo.record]: sea_color = 0.519531, 0.389445, 0.493108, 1; 11:29:09.104 [planetinfo.record]: rotation_speed = 0.001384 11:29:09.104 [planetinfo.record]: planet zpos = 499720.000000 11:29:09.104 [planetinfo.record]: sun_radius = 95640.146484 11:29:09.104 [planetinfo.record]: sun_vector = -0.556 0.418 -0.719 11:29:09.104 [planetinfo.record]: sun_distance = 768800 11:29:09.104 [planetinfo.record]: corona_flare = 0.068030 11:29:09.104 [planetinfo.record]: corona_hues = 0.523232 11:29:09.104 [planetinfo.record]: sun_color = 0.71304, 0.68748, 0.675006, 1 11:29:09.104 [planetinfo.record]: corona_shimmer = 0.539187 11:29:09.105 [planetinfo.record]: station_vector = -0.950 0.309 0.037 11:29:09.105 [planetinfo.record]: station = coriolis 11:29:13.381 [PLANETINFO OVER]: Done 11:29:13.382 [PLANETINFO LOGGING]: 0 240 11:29:14.385 [planetinfo.record]: seed = 104 156 83 20 11:29:14.385 [planetinfo.record]: coordinates = 156 208 11:29:14.385 [planetinfo.record]: government = 5; 11:29:14.385 [planetinfo.record]: economy = 0; 11:29:14.385 [planetinfo.record]: techlevel = 10; 11:29:14.385 [planetinfo.record]: population = 46; 11:29:14.385 [planetinfo.record]: productivity = 33120; 11:29:14.385 [planetinfo.record]: name = "Raleen"; 11:29:14.385 [planetinfo.record]: inhabitant = "Human Colonial"; 11:29:14.385 [planetinfo.record]: inhabitants = "Human Colonials"; 11:29:14.385 [planetinfo.record]: description = "This planet is notable for the Raleenian tree grub and its inhabitants’ unusual silliness."; 11:29:14.412 [planetinfo.record]: air_color = 0.693192, 0.838275, 0.901441, 1; 11:29:14.412 [planetinfo.record]: cloud_alpha = 1.000000; 11:29:14.412 [planetinfo.record]: cloud_color = 0.645926, 0.707031, 0.640747, 1; 11:29:14.412 [planetinfo.record]: cloud_fraction = 0.270000; 11:29:14.412 [planetinfo.record]: land_color = 0.651138, 0.66, 0.546562, 1; 11:29:14.412 [planetinfo.record]: land_fraction = 0.570000; 11:29:14.412 [planetinfo.record]: polar_cloud_color = 0.77397, 0.818164, 0.770225, 1; 11:29:14.412 [planetinfo.record]: polar_land_color = 0.930865, 0.934, 0.893867, 1; 11:29:14.412 [planetinfo.record]: polar_sea_color = 0.725563, 0.90115, 0.929883, 1; 11:29:14.412 [planetinfo.record]: sea_color = 0.0849075, 0.61451, 0.701172, 1; 11:29:14.412 [planetinfo.record]: rotation_speed = 0.003821 11:29:14.412 [planetinfo.record]: planet zpos = 559440.000000 11:29:14.412 [planetinfo.record]: sun_radius = 126689.589844 11:29:14.412 [planetinfo.record]: sun_vector = -0.774 -0.113 -0.622 11:29:14.412 [planetinfo.record]: sun_distance = 759240 11:29:14.412 [planetinfo.record]: corona_flare = 0.076398 11:29:14.412 [planetinfo.record]: corona_hues = 0.790359 11:29:14.413 [planetinfo.record]: sun_color = 0.177929, 0.454473, 0.655762, 1 11:29:14.413 [planetinfo.record]: corona_shimmer = 0.382272 11:29:14.413 [planetinfo.record]: station_vector = 0.243 0.378 0.893 11:29:14.413 [planetinfo.record]: station = coriolis 11:29:19.687 [PLANETINFO OVER]: Done 11:29:19.687 [PLANETINFO LOGGING]: 0 241 11:29:20.694 [planetinfo.record]: seed = 152 199 221 122 11:29:20.694 [planetinfo.record]: coordinates = 199 50 11:29:20.694 [planetinfo.record]: government = 3; 11:29:20.694 [planetinfo.record]: economy = 2; 11:29:20.694 [planetinfo.record]: techlevel = 10; 11:29:20.695 [planetinfo.record]: population = 46; 11:29:20.695 [planetinfo.record]: productivity = 20608; 11:29:20.695 [planetinfo.record]: name = "Qurave"; 11:29:20.695 [planetinfo.record]: inhabitant = "Blue Furry Insect"; 11:29:20.695 [planetinfo.record]: inhabitants = "Blue Furry Insects"; 11:29:20.695 [planetinfo.record]: description = "The planet Qurave is mildly notable for Quraveian Aron water."; 11:29:20.708 [planetinfo.record]: air_color = 0.73144, 0.55649, 0.913799, 1; 11:29:20.708 [planetinfo.record]: cloud_alpha = 1.000000; 11:29:20.708 [planetinfo.record]: cloud_color = 0.744141, 0.316841, 0.537167, 1; 11:29:20.708 [planetinfo.record]: cloud_fraction = 0.300000; 11:29:20.708 [planetinfo.record]: land_color = 0.66, 0.0515625, 0.607712, 1; 11:29:20.708 [planetinfo.record]: land_fraction = 0.420000; 11:29:20.708 [planetinfo.record]: polar_cloud_color = 0.834863, 0.535242, 0.689734, 1; 11:29:20.708 [planetinfo.record]: polar_land_color = 0.934, 0.718742, 0.915501, 1; 11:29:20.708 [planetinfo.record]: polar_sea_color = 0.94082, 0.893865, 0.88303, 1; 11:29:20.708 [planetinfo.record]: sea_color = 0.591797, 0.473654, 0.446391, 1; 11:29:20.708 [planetinfo.record]: rotation_speed = 0.001922 11:29:20.708 [planetinfo.record]: planet zpos = 836250.000000 11:29:20.708 [planetinfo.record]: sun_radius = 139654.872894 11:29:20.708 [planetinfo.record]: sun_vector = 0.853 0.224 0.471 11:29:20.708 [planetinfo.record]: sun_distance = 1003500 11:29:20.708 [planetinfo.record]: corona_flare = 0.045375 11:29:20.708 [planetinfo.record]: corona_hues = 0.640854 11:29:20.709 [planetinfo.record]: sun_color = 0.810999, 0.501697, 0.265156, 1 11:29:20.709 [planetinfo.record]: corona_shimmer = 0.313294 11:29:20.709 [planetinfo.record]: station_vector = 0.430 -0.125 0.894 11:29:20.709 [planetinfo.record]: station = coriolis 11:29:26.593 [PLANETINFO OVER]: Done 11:29:26.593 [PLANETINFO LOGGING]: 0 242 11:29:27.598 [planetinfo.record]: seed = 60 166 155 209 11:29:27.598 [planetinfo.record]: coordinates = 166 182 11:29:27.598 [planetinfo.record]: government = 7; 11:29:27.598 [planetinfo.record]: economy = 6; 11:29:27.598 [planetinfo.record]: techlevel = 7; 11:29:27.598 [planetinfo.record]: population = 42; 11:29:27.598 [planetinfo.record]: productivity = 14784; 11:29:27.598 [planetinfo.record]: name = "Atrebibi"; 11:29:27.598 [planetinfo.record]: inhabitant = "Slimy Frog"; 11:29:27.598 [planetinfo.record]: inhabitants = "Slimy Frogs"; 11:29:27.598 [planetinfo.record]: description = "The world Atrebibi is most famous for the Atrebibiian deadly monkey."; 11:29:27.613 [planetinfo.record]: air_color = 0.548712, 0.967131, 0.917701, 1; 11:29:27.613 [planetinfo.record]: cloud_alpha = 1.000000; 11:29:27.613 [planetinfo.record]: cloud_color = 0.904297, 0.686943, 0.271996, 1; 11:29:27.613 [planetinfo.record]: cloud_fraction = 0.150000; 11:29:27.614 [planetinfo.record]: land_color = 0.593875, 0.66, 0.487266, 1; 11:29:27.614 [planetinfo.record]: land_fraction = 0.480000; 11:29:27.614 [planetinfo.record]: polar_cloud_color = 0.906934, 0.770692, 0.510593, 1; 11:29:27.614 [planetinfo.record]: polar_land_color = 0.910606, 0.934, 0.872889, 1; 11:29:27.614 [planetinfo.record]: polar_sea_color = 0.922106, 0.889149, 0.949414, 1; 11:29:27.614 [planetinfo.record]: sea_color = 0.44766, 0.377419, 0.505859, 1; 11:29:27.614 [planetinfo.record]: rotation_speed = 0.000629 11:29:27.614 [planetinfo.record]: planet zpos = 420940.000000 11:29:27.614 [planetinfo.record]: sun_radius = 83264.268799 11:29:27.614 [planetinfo.record]: sun_vector = -0.632 -0.574 0.520 11:29:27.614 [planetinfo.record]: sun_distance = 647600 11:29:27.614 [planetinfo.record]: corona_flare = 0.076268 11:29:27.614 [planetinfo.record]: corona_hues = 0.593597 11:29:27.614 [planetinfo.record]: sun_color = 0.222489, 0.504022, 0.735822, 1 11:29:27.614 [planetinfo.record]: corona_shimmer = 0.342493 11:29:27.614 [planetinfo.record]: station_vector = -0.959 -0.249 0.134 11:29:27.614 [planetinfo.record]: station = coriolis 11:29:33.007 [PLANETINFO OVER]: Done 11:29:33.008 [PLANETINFO LOGGING]: 0 243 11:29:34.010 [planetinfo.record]: seed = 244 166 77 124 11:29:34.011 [planetinfo.record]: coordinates = 166 166 11:29:34.011 [planetinfo.record]: government = 6; 11:29:34.011 [planetinfo.record]: economy = 6; 11:29:34.011 [planetinfo.record]: techlevel = 6; 11:29:34.011 [planetinfo.record]: population = 37; 11:29:34.011 [planetinfo.record]: productivity = 11840; 11:29:34.011 [planetinfo.record]: name = "Teesdi"; 11:29:34.011 [planetinfo.record]: inhabitant = "Human Colonial"; 11:29:34.011 [planetinfo.record]: inhabitants = "Human Colonials"; 11:29:34.011 [planetinfo.record]: description = "Teesdi is famous for Teesdiian shrew cutlet but ravaged by occasional solar activity."; 11:29:34.020 [planetinfo.record]: air_color = 0.427633, 0.643682, 0.902742, 1; 11:29:34.020 [planetinfo.record]: cloud_alpha = 1.000000; 11:29:34.020 [planetinfo.record]: cloud_color = 0.0138855, 0.710938, 0.46588, 1; 11:29:34.020 [planetinfo.record]: cloud_fraction = 0.120000; 11:29:34.020 [planetinfo.record]: land_color = 0.66, 0.278438, 0.376809, 1; 11:29:34.020 [planetinfo.record]: land_fraction = 0.590000; 11:29:34.020 [planetinfo.record]: polar_cloud_color = 0.317479, 0.819922, 0.643282, 1; 11:29:34.020 [planetinfo.record]: polar_land_color = 0.934, 0.799008, 0.833811, 1; 11:29:34.020 [planetinfo.record]: polar_sea_color = 0.942187, 0.910029, 0.888729, 1; 11:29:34.020 [planetinfo.record]: sea_color = 0.578125, 0.499196, 0.446918, 1; 11:29:34.020 [planetinfo.record]: rotation_speed = 0.003636 11:29:34.020 [planetinfo.record]: planet zpos = 726480.000000 11:29:34.020 [planetinfo.record]: sun_radius = 108434.552307 11:29:34.020 [planetinfo.record]: sun_vector = 0.595 0.760 0.261 11:29:34.020 [planetinfo.record]: sun_distance = 1210800 11:29:34.020 [planetinfo.record]: corona_flare = 0.073961 11:29:34.020 [planetinfo.record]: corona_hues = 0.662712 11:29:34.020 [planetinfo.record]: sun_color = 0.657384, 0.679139, 0.712814, 1 11:29:34.020 [planetinfo.record]: corona_shimmer = 0.453110 11:29:34.021 [planetinfo.record]: station_vector = 0.276 -0.092 0.957 11:29:34.027 [planetinfo.record]: station = coriolis 11:29:38.984 [PLANETINFO OVER]: Done 11:29:38.986 [PLANETINFO LOGGING]: 0 244 11:29:39.988 [planetinfo.record]: seed = 64 51 51 234 11:29:39.988 [planetinfo.record]: coordinates = 51 237 11:29:39.988 [planetinfo.record]: government = 0; 11:29:39.988 [planetinfo.record]: economy = 7; 11:29:39.989 [planetinfo.record]: techlevel = 3; 11:29:39.989 [planetinfo.record]: population = 20; 11:29:39.989 [planetinfo.record]: productivity = 1920; 11:29:39.989 [planetinfo.record]: name = "Ararus"; 11:29:39.989 [planetinfo.record]: inhabitant = "Human Colonial"; 11:29:39.989 [planetinfo.record]: inhabitants = "Human Colonials"; 11:29:39.989 [planetinfo.record]: description = "Ararus is most famous for its pink Stalbien plant plantations and its unusual dense forests."; 11:29:40.004 [planetinfo.record]: air_color = 0.610938, 0.514334, 0.917051, 1; 11:29:40.004 [planetinfo.record]: cloud_alpha = 1.000000; 11:29:40.004 [planetinfo.record]: cloud_color = 0.609972, 0.212036, 0.753906, 1; 11:29:40.004 [planetinfo.record]: cloud_fraction = 0.160000; 11:29:40.004 [planetinfo.record]: land_color = 0.477437, 0.66, 0.433125, 1; 11:29:40.004 [planetinfo.record]: land_fraction = 0.530000; 11:29:40.004 [planetinfo.record]: polar_cloud_color = 0.739114, 0.462247, 0.839258, 1; 11:29:40.004 [planetinfo.record]: polar_land_color = 0.869411, 0.934, 0.853734, 1; 11:29:40.004 [planetinfo.record]: polar_sea_color = 0.935742, 0.901967, 0.883655, 1; 11:29:40.004 [planetinfo.record]: sea_color = 0.642578, 0.549804, 0.499504, 1; 11:29:40.004 [planetinfo.record]: rotation_speed = 0.003591 11:29:40.004 [planetinfo.record]: planet zpos = 596970.000000 11:29:40.004 [planetinfo.record]: sun_radius = 134894.106903 11:29:40.004 [planetinfo.record]: sun_vector = 0.459 -0.357 -0.813 11:29:40.004 [planetinfo.record]: sun_distance = 1193940 11:29:40.004 [planetinfo.record]: corona_flare = 0.022517 11:29:40.004 [planetinfo.record]: corona_hues = 0.907150 11:29:40.004 [planetinfo.record]: sun_color = 0.482167, 0.569928, 0.68363, 1 11:29:40.004 [planetinfo.record]: corona_shimmer = 0.456557 11:29:40.005 [planetinfo.record]: station_vector = -0.111 0.978 0.178 11:29:40.005 [planetinfo.record]: station = coriolis 11:29:45.135 [PLANETINFO OVER]: Done 11:29:45.135 [PLANETINFO LOGGING]: 0 245 11:29:46.159 [planetinfo.record]: seed = 192 28 77 79 11:29:46.159 [planetinfo.record]: coordinates = 28 40 11:29:46.159 [planetinfo.record]: government = 0; 11:29:46.159 [planetinfo.record]: economy = 2; 11:29:46.160 [planetinfo.record]: techlevel = 5; 11:29:46.160 [planetinfo.record]: population = 23; 11:29:46.160 [planetinfo.record]: productivity = 5888; 11:29:46.160 [planetinfo.record]: name = "Ara"; 11:29:46.160 [planetinfo.record]: inhabitant = "Human Colonial"; 11:29:46.160 [planetinfo.record]: inhabitants = "Human Colonials"; 11:29:46.160 [planetinfo.record]: description = "The world Ara is scourged by evil disease."; 11:29:46.174 [planetinfo.record]: air_color = 0.454573, 0.561508, 0.907295, 1; 11:29:46.174 [planetinfo.record]: cloud_alpha = 1.000000; 11:29:46.174 [planetinfo.record]: cloud_color = 0.0735931, 0.526253, 0.724609, 1; 11:29:46.174 [planetinfo.record]: cloud_fraction = 0.160000; 11:29:46.175 [planetinfo.record]: land_color = 0.66, 0.324844, 0.332699, 1; 11:29:46.175 [planetinfo.record]: land_fraction = 0.350000; 11:29:46.184 [planetinfo.record]: polar_cloud_color = 0.362214, 0.684742, 0.826074, 1; 11:29:46.184 [planetinfo.record]: polar_land_color = 0.934, 0.815426, 0.818205, 1; 11:29:46.184 [planetinfo.record]: polar_sea_color = 0.929688, 0.872873, 0.847977, 1; 11:29:46.184 [planetinfo.record]: sea_color = 0.703125, 0.531249, 0.455933, 1; 11:29:46.184 [planetinfo.record]: rotation_speed = 0.001563 11:29:46.184 [planetinfo.record]: planet zpos = 668400.000000 11:29:46.184 [planetinfo.record]: sun_radius = 149625.076904 11:29:46.184 [planetinfo.record]: sun_vector = 0.537 0.432 0.724 11:29:46.185 [planetinfo.record]: sun_distance = 1336800 11:29:46.185 [planetinfo.record]: corona_flare = 0.059349 11:29:46.185 [planetinfo.record]: corona_hues = 0.950493 11:29:46.185 [planetinfo.record]: sun_color = 0.73465, 0.663323, 0.310698, 1 11:29:46.185 [planetinfo.record]: corona_shimmer = 0.718518 11:29:46.186 [planetinfo.record]: station_vector = 0.429 -0.249 0.868 11:29:46.186 [planetinfo.record]: station = coriolis 11:29:50.597 [PLANETINFO OVER]: Done 11:29:50.598 [PLANETINFO LOGGING]: 0 246 11:29:51.611 [planetinfo.record]: seed = 116 227 155 119 11:29:51.611 [planetinfo.record]: coordinates = 227 0 11:29:51.611 [planetinfo.record]: government = 6; 11:29:51.611 [planetinfo.record]: economy = 0; 11:29:51.611 [planetinfo.record]: techlevel = 13; 11:29:51.611 [planetinfo.record]: population = 59; 11:29:51.611 [planetinfo.record]: productivity = 47200; 11:29:51.611 [planetinfo.record]: name = "Tianve"; 11:29:51.611 [planetinfo.record]: inhabitant = "Blue Bony Feline"; 11:29:51.611 [planetinfo.record]: inhabitants = "Blue Bony Felines"; 11:29:51.611 [planetinfo.record]: description = "The planet Tianve is reasonably noted for its inhabitants’ exceptional loathing of food blenders and Zero-G cricket."; 11:29:51.644 [planetinfo.record]: air_color = 0.659452, 0.876934, 0.909896, 1; 11:29:51.644 [planetinfo.record]: cloud_alpha = 1.000000; 11:29:51.644 [planetinfo.record]: cloud_color = 0.665009, 0.732422, 0.566483, 1; 11:29:51.644 [planetinfo.record]: cloud_fraction = 0.320000; 11:29:51.644 [planetinfo.record]: land_color = 0.382771, 0.66, 0.373828, 1; 11:29:51.644 [planetinfo.record]: land_fraction = 0.670000; 11:29:51.644 [planetinfo.record]: polar_cloud_color = 0.781867, 0.82959, 0.712119, 1; 11:29:51.644 [planetinfo.record]: polar_land_color = 0.83592, 0.934, 0.832756, 1; 11:29:51.644 [planetinfo.record]: polar_sea_color = 0.930273, 0.911394, 0.866681, 1; 11:29:51.644 [planetinfo.record]: sea_color = 0.697266, 0.640664, 0.506607, 1; 11:29:51.644 [planetinfo.record]: rotation_speed = 0.002784 11:29:51.644 [planetinfo.record]: planet zpos = 435150.000000 11:29:51.644 [planetinfo.record]: sun_radius = 78835.820007 11:29:51.644 [planetinfo.record]: sun_vector = -0.247 -0.476 -0.844 11:29:51.644 [planetinfo.record]: sun_distance = 870300 11:29:51.644 [planetinfo.record]: corona_flare = 0.090872 11:29:51.644 [planetinfo.record]: corona_hues = 0.622223 11:29:51.644 [planetinfo.record]: sun_color = 0.303727, 0.427148, 0.693707, 1 11:29:51.645 [planetinfo.record]: corona_shimmer = 0.362157 11:29:51.645 [planetinfo.record]: station_vector = -0.907 0.272 0.322 11:29:51.645 [planetinfo.record]: station = dodecahedron 11:29:56.555 [PLANETINFO OVER]: Done 11:29:56.557 [PLANETINFO LOGGING]: 0 247 11:29:57.558 [planetinfo.record]: seed = 252 136 93 154 11:29:57.558 [planetinfo.record]: coordinates = 136 182 11:29:57.558 [planetinfo.record]: government = 7; 11:29:57.559 [planetinfo.record]: economy = 6; 11:29:57.559 [planetinfo.record]: techlevel = 5; 11:29:57.559 [planetinfo.record]: population = 34; 11:29:57.559 [planetinfo.record]: productivity = 11968; 11:29:57.559 [planetinfo.record]: name = "Quorte"; 11:29:57.559 [planetinfo.record]: inhabitant = "Human Colonial"; 11:29:57.559 [planetinfo.record]: inhabitants = "Human Colonials"; 11:29:57.559 [planetinfo.record]: description = "Quorte is well known for the Quorteian tree wolf but scourged by dreadful solar activity."; 11:29:57.560 [planetinfo.record]: air_color = 0.456147, 0.672314, 0.874125, 1; 11:29:57.560 [planetinfo.record]: cloud_alpha = 1.000000; 11:29:57.560 [planetinfo.record]: cloud_color = 0.102539, 0.625, 0.343361, 1; 11:29:57.560 [planetinfo.record]: cloud_fraction = 0.390000; 11:29:57.560 [planetinfo.record]: land_color = 0.143005, 0.554688, 0.21698, 1; 11:29:57.560 [planetinfo.record]: land_fraction = 0.590000; 11:29:57.560 [planetinfo.record]: polar_cloud_color = 0.373077, 0.78125, 0.561219, 1; 11:29:57.560 [planetinfo.record]: polar_land_color = 0.769276, 0.944531, 0.800768, 1; 11:29:57.560 [planetinfo.record]: polar_sea_color = 0.923828, 0.845367, 0.814665, 1; 11:29:57.561 [planetinfo.record]: sea_color = 0.761719, 0.502946, 0.401688, 1; 11:29:57.561 [planetinfo.record]: rotation_speed = 0.000833 11:29:57.561 [planetinfo.record]: planet zpos = 716560.000000 11:29:57.561 [planetinfo.record]: sun_radius = 138161.657715 11:29:57.561 [planetinfo.record]: sun_vector = 0.222 0.849 -0.480 11:29:57.561 [planetinfo.record]: sun_distance = 1047280 11:29:57.561 [planetinfo.record]: corona_flare = 0.031300 11:29:57.561 [planetinfo.record]: corona_hues = 0.548286 11:29:57.561 [planetinfo.record]: sun_color = 0.735834, 0.603216, 0.521975, 1 11:29:57.561 [planetinfo.record]: corona_shimmer = 0.367643 11:29:57.561 [planetinfo.record]: station_vector = -0.665 0.682 0.303 11:29:57.561 [planetinfo.record]: station = coriolis 11:30:01.924 [PLANETINFO OVER]: Done 11:30:01.925 [PLANETINFO LOGGING]: 0 248 11:30:02.928 [planetinfo.record]: seed = 216 112 83 71 11:30:02.929 [planetinfo.record]: coordinates = 112 252 11:30:02.929 [planetinfo.record]: government = 3; 11:30:02.929 [planetinfo.record]: economy = 4; 11:30:02.929 [planetinfo.record]: techlevel = 5; 11:30:02.929 [planetinfo.record]: population = 28; 11:30:02.929 [planetinfo.record]: productivity = 9408; 11:30:02.929 [planetinfo.record]: name = "Soladies"; 11:30:02.929 [planetinfo.record]: inhabitant = "Human Colonial"; 11:30:02.929 [planetinfo.record]: inhabitants = "Human Colonials"; 11:30:02.929 [planetinfo.record]: description = "This planet is fabled for its exciting Soladiesian evil brandy."; 11:30:02.930 [planetinfo.record]: air_color = 0.577682, 0.93266, 0.851938, 1; 11:30:02.930 [planetinfo.record]: cloud_alpha = 1.000000; 11:30:02.930 [planetinfo.record]: cloud_color = 0.800781, 0.508651, 0.365982, 1; 11:30:02.930 [planetinfo.record]: cloud_fraction = 0.250000; 11:30:02.930 [planetinfo.record]: land_color = 0.352908, 0.529297, 0.095108, 1; 11:30:02.930 [planetinfo.record]: land_fraction = 0.640000; 11:30:02.930 [planetinfo.record]: polar_cloud_color = 0.860352, 0.664188, 0.568387, 1; 11:30:02.930 [planetinfo.record]: polar_land_color = 0.868167, 0.94707, 0.752847, 1; 11:30:02.930 [planetinfo.record]: polar_sea_color = 0.927539, 0.840377, 0.828807, 1; 11:30:02.930 [planetinfo.record]: sea_color = 0.724609, 0.45224, 0.416084, 1; 11:30:02.930 [planetinfo.record]: rotation_speed = 0.003213 11:30:02.930 [planetinfo.record]: planet zpos = 472000.000000 11:30:02.930 [planetinfo.record]: sun_radius = 104930.261230 11:30:02.930 [planetinfo.record]: sun_vector = 0.661 0.750 0.024 11:30:02.931 [planetinfo.record]: sun_distance = 1085600 11:30:02.931 [planetinfo.record]: corona_flare = 0.056349 11:30:02.931 [planetinfo.record]: corona_hues = 0.612068 11:30:02.931 [planetinfo.record]: sun_color = 0.702005, 0.672681, 0.398407, 1 11:30:02.931 [planetinfo.record]: corona_shimmer = 0.424549 11:30:02.931 [planetinfo.record]: station_vector = -0.131 0.882 0.452 11:30:02.931 [planetinfo.record]: station = coriolis 11:30:07.458 [PLANETINFO OVER]: Done 11:30:07.459 [PLANETINFO LOGGING]: 0 249 11:30:08.463 [planetinfo.record]: seed = 168 105 253 139 11:30:08.463 [planetinfo.record]: coordinates = 105 109 11:30:08.463 [planetinfo.record]: government = 5; 11:30:08.463 [planetinfo.record]: economy = 5; 11:30:08.463 [planetinfo.record]: techlevel = 6; 11:30:08.463 [planetinfo.record]: population = 35; 11:30:08.463 [planetinfo.record]: productivity = 12600; 11:30:08.463 [planetinfo.record]: name = "Maxeedso"; 11:30:08.463 [planetinfo.record]: inhabitant = "Small Black Fat Insect"; 11:30:08.463 [planetinfo.record]: inhabitants = "Small Black Fat Insects"; 11:30:08.463 [planetinfo.record]: description = "This world is mildly famous for its vast rain forests and the Maxeedsoian tree wolf."; 11:30:08.466 [planetinfo.record]: air_color = 0.668501, 0.801816, 0.956725, 1; 11:30:08.467 [planetinfo.record]: cloud_alpha = 1.000000; 11:30:08.467 [planetinfo.record]: cloud_color = 0.60704, 0.873047, 0.773294, 1; 11:30:08.467 [planetinfo.record]: cloud_fraction = 0.180000; 11:30:08.467 [planetinfo.record]: land_color = 0.549141, 0.66, 0.641812, 1; 11:30:08.467 [planetinfo.record]: land_fraction = 0.290000; 11:30:08.467 [planetinfo.record]: polar_cloud_color = 0.722842, 0.892871, 0.82911, 1; 11:30:08.467 [planetinfo.record]: polar_land_color = 0.894779, 0.934, 0.927565, 1; 11:30:08.467 [planetinfo.record]: polar_sea_color = 0.789079, 0.943945, 0.911278, 1; 11:30:08.467 [planetinfo.record]: sea_color = 0.192688, 0.560547, 0.482952, 1; 11:30:08.467 [planetinfo.record]: rotation_speed = 0.001290 11:30:08.467 [planetinfo.record]: planet zpos = 688440.000000 11:30:08.467 [planetinfo.record]: sun_radius = 114096.583405 11:30:08.467 [planetinfo.record]: sun_vector = -0.012 -0.759 -0.651 11:30:08.467 [planetinfo.record]: sun_distance = 1032660 11:30:08.468 [planetinfo.record]: corona_flare = 0.008908 11:30:08.468 [planetinfo.record]: corona_hues = 0.949356 11:30:08.468 [planetinfo.record]: sun_color = 0.342753, 0.407919, 0.83519, 1 11:30:08.468 [planetinfo.record]: corona_shimmer = 0.227526 11:30:08.468 [planetinfo.record]: station_vector = 0.593 0.654 0.470 11:30:08.468 [planetinfo.record]: station = coriolis 11:30:13.254 [PLANETINFO OVER]: Done 11:30:13.255 [PLANETINFO LOGGING]: 0 250 11:30:14.260 [planetinfo.record]: seed = 108 71 219 2 11:30:14.260 [planetinfo.record]: coordinates = 71 88 11:30:14.260 [planetinfo.record]: government = 5; 11:30:14.260 [planetinfo.record]: economy = 0; 11:30:14.260 [planetinfo.record]: techlevel = 13; 11:30:14.260 [planetinfo.record]: population = 58; 11:30:14.260 [planetinfo.record]: productivity = 41760; 11:30:14.260 [planetinfo.record]: name = "Xexedi"; 11:30:14.260 [planetinfo.record]: inhabitant = "Large Green Frog"; 11:30:14.260 [planetinfo.record]: inhabitants = "Large Green Frogs"; 11:30:14.260 [planetinfo.record]: description = "The planet Xexedi is scourged by deadly disease."; 11:30:14.269 [planetinfo.record]: air_color = 0.620329, 0.887783, 0.812352, 1; 11:30:14.269 [planetinfo.record]: cloud_alpha = 1.000000; 11:30:14.269 [planetinfo.record]: cloud_color = 0.666016, 0.496382, 0.463089, 1; 11:30:14.270 [planetinfo.record]: cloud_fraction = 0.080000; 11:30:14.270 [planetinfo.record]: land_color = 0.66, 0.291328, 0.455502, 1; 11:30:14.270 [planetinfo.record]: land_fraction = 0.500000; 11:30:14.270 [planetinfo.record]: polar_cloud_color = 0.799707, 0.672404, 0.647419, 1; 11:30:14.270 [planetinfo.record]: polar_land_color = 0.934, 0.803568, 0.861651, 1; 11:30:14.270 [planetinfo.record]: polar_sea_color = 0.92626, 0.938086, 0.889258, 1; 11:30:14.270 [planetinfo.record]: sea_color = 0.587921, 0.619141, 0.490234, 1; 11:30:14.270 [planetinfo.record]: rotation_speed = 0.004894 11:30:14.270 [planetinfo.record]: planet zpos = 407880.000000 11:30:14.271 [planetinfo.record]: sun_radius = 71205.460968 11:30:14.271 [planetinfo.record]: sun_vector = -0.104 -0.774 0.625 11:30:14.271 [planetinfo.record]: sun_distance = 713790 11:30:14.271 [planetinfo.record]: corona_flare = 0.028917 11:30:14.271 [planetinfo.record]: corona_hues = 0.563080 11:30:14.271 [planetinfo.record]: sun_color = 0.70643, 0.755372, 0.767682, 1 11:30:14.271 [planetinfo.record]: corona_shimmer = 0.305398 11:30:14.272 [planetinfo.record]: station_vector = -0.137 0.842 0.521 11:30:14.272 [planetinfo.record]: station = dodecahedron 11:30:19.158 [PLANETINFO OVER]: Done 11:30:19.158 [PLANETINFO LOGGING]: 0 251 11:30:20.162 [planetinfo.record]: seed = 196 146 173 34 11:30:20.162 [planetinfo.record]: coordinates = 146 237 11:30:20.163 [planetinfo.record]: government = 0; 11:30:20.163 [planetinfo.record]: economy = 7; 11:30:20.163 [planetinfo.record]: techlevel = 2; 11:30:20.163 [planetinfo.record]: population = 16; 11:30:20.163 [planetinfo.record]: productivity = 1536; 11:30:20.163 [planetinfo.record]: name = "Xexeti"; 11:30:20.163 [planetinfo.record]: inhabitant = "Large Red Frog"; 11:30:20.163 [planetinfo.record]: inhabitants = "Large Red Frogs"; 11:30:20.163 [planetinfo.record]: description = "This planet is notable for the Xexetiian edible arts graduate and its great volcanoes."; 11:30:20.180 [planetinfo.record]: air_color = 0.615643, 0.906233, 0.944367, 1; 11:30:20.180 [planetinfo.record]: cloud_alpha = 1.000000; 11:30:20.180 [planetinfo.record]: cloud_color = 0.702159, 0.835938, 0.463684, 1; 11:30:20.180 [planetinfo.record]: cloud_fraction = 0.510000; 11:30:20.180 [planetinfo.record]: land_color = 0.37125, 0.66, 0.578789, 1; 11:30:20.180 [planetinfo.record]: land_fraction = 0.610000; 11:30:20.180 [planetinfo.record]: polar_cloud_color = 0.788536, 0.876172, 0.632315, 1; 11:30:20.180 [planetinfo.record]: polar_land_color = 0.831844, 0.934, 0.905269, 1; 11:30:20.180 [planetinfo.record]: polar_sea_color = 0.936719, 0.90029, 0.876161, 1; 11:30:20.180 [planetinfo.record]: sea_color = 0.632812, 0.534372, 0.469171, 1; 11:30:20.180 [planetinfo.record]: rotation_speed = 0.002967 11:30:20.180 [planetinfo.record]: planet zpos = 347400.000000 11:30:20.180 [planetinfo.record]: sun_radius = 88820.875854 11:30:20.180 [planetinfo.record]: sun_vector = 0.725 0.622 0.296 11:30:20.180 [planetinfo.record]: sun_distance = 660060 11:30:20.180 [planetinfo.record]: corona_flare = 0.046455 11:30:20.180 [planetinfo.record]: corona_hues = 0.919930 11:30:20.180 [planetinfo.record]: sun_color = 0.230651, 0.730407, 0.811105, 1 11:30:20.181 [planetinfo.record]: corona_shimmer = 0.335041 11:30:20.181 [planetinfo.record]: station_vector = -0.869 -0.129 0.477 11:30:20.181 [planetinfo.record]: station = coriolis 11:30:25.475 [PLANETINFO OVER]: Done 11:30:25.476 [PLANETINFO LOGGING]: 0 252 11:30:26.479 [planetinfo.record]: seed = 48 29 179 23 11:30:26.479 [planetinfo.record]: coordinates = 29 87 11:30:26.479 [planetinfo.record]: government = 6; 11:30:26.479 [planetinfo.record]: economy = 7; 11:30:26.479 [planetinfo.record]: techlevel = 4; 11:30:26.479 [planetinfo.record]: population = 30; 11:30:26.479 [planetinfo.record]: productivity = 7200; 11:30:26.479 [planetinfo.record]: name = "Tiinlebi"; 11:30:26.479 [planetinfo.record]: inhabitant = "Green Horned Humanoid"; 11:30:26.479 [planetinfo.record]: inhabitants = "Green Horned Humanoids"; 11:30:26.479 [planetinfo.record]: description = "The planet Tiinlebi is most noted for the Tiinlebiian mountain slug and its inhabitants’ exceptional loathing of food blenders."; 11:30:26.481 [planetinfo.record]: air_color = 0.486202, 0.461286, 0.935262, 1; 11:30:26.481 [planetinfo.record]: cloud_alpha = 1.000000; 11:30:26.482 [planetinfo.record]: cloud_color = 0.191291, 0.0631714, 0.808594, 1; 11:30:26.482 [planetinfo.record]: cloud_fraction = 0.490000; 11:30:26.482 [planetinfo.record]: land_color = 0.344587, 0.311783, 0.574219, 1; 11:30:26.482 [planetinfo.record]: land_fraction = 0.290000; 11:30:26.482 [planetinfo.record]: polar_cloud_color = 0.45168, 0.366131, 0.863867, 1; 11:30:26.482 [planetinfo.record]: polar_land_color = 0.848343, 0.834881, 0.942578, 1; 11:30:26.482 [planetinfo.record]: polar_sea_color = 0.94366, 0.949805, 0.753165, 1; 11:30:26.482 [planetinfo.record]: sea_color = 0.488963, 0.501953, 0.0862732, 1; 11:30:26.482 [planetinfo.record]: rotation_speed = 0.002866 11:30:26.482 [planetinfo.record]: planet zpos = 463700.000000 11:30:26.482 [planetinfo.record]: sun_radius = 102718.578339 11:30:26.482 [planetinfo.record]: sun_vector = -0.327 -0.685 0.651 11:30:26.482 [planetinfo.record]: sun_distance = 788290 11:30:26.482 [planetinfo.record]: corona_flare = 0.016382 11:30:26.482 [planetinfo.record]: corona_hues = 0.772491 11:30:26.482 [planetinfo.record]: sun_color = 0.818707, 0.80436, 0.634852, 1 11:30:26.483 [planetinfo.record]: corona_shimmer = 0.246678 11:30:26.483 [planetinfo.record]: station_vector = -0.034 0.851 0.524 11:30:26.483 [planetinfo.record]: station = coriolis 11:30:30.289 [PLANETINFO OVER]: Done 11:30:30.291 [PLANETINFO LOGGING]: 0 253 11:30:31.293 [planetinfo.record]: seed = 80 102 237 180 11:30:31.294 [planetinfo.record]: coordinates = 102 193 11:30:31.294 [planetinfo.record]: government = 2; 11:30:31.294 [planetinfo.record]: economy = 1; 11:30:31.294 [planetinfo.record]: techlevel = 9; 11:30:31.294 [planetinfo.record]: population = 40; 11:30:31.294 [planetinfo.record]: productivity = 17280; 11:30:31.294 [planetinfo.record]: name = "Rateedar"; 11:30:31.294 [planetinfo.record]: inhabitant = "Harmless Insect"; 11:30:31.294 [planetinfo.record]: inhabitants = "Harmless Insects"; 11:30:31.294 [planetinfo.record]: description = "Rateedar is cursed by dreadful civil war."; 11:30:31.313 [planetinfo.record]: air_color = 0.433794, 0.577727, 0.91575, 1; 11:30:31.314 [planetinfo.record]: cloud_alpha = 1.000000; 11:30:31.314 [planetinfo.record]: cloud_color = 0.0146484, 0.663826, 0.75, 1; 11:30:31.314 [planetinfo.record]: cloud_fraction = 0.480000; 11:30:31.314 [planetinfo.record]: land_color = 0.290451, 0.355534, 0.550781, 1; 11:30:31.314 [planetinfo.record]: land_fraction = 0.510000; 11:30:31.314 [planetinfo.record]: polar_cloud_color = 0.324286, 0.777358, 0.8375, 1; 11:30:31.314 [planetinfo.record]: polar_land_color = 0.833266, 0.86118, 0.944922, 1; 11:30:31.314 [planetinfo.record]: polar_sea_color = 0.892889, 0.929102, 0.872575, 1; 11:30:31.314 [planetinfo.record]: sea_color = 0.598452, 0.708984, 0.536446, 1; 11:30:31.314 [planetinfo.record]: rotation_speed = 0.000996 11:30:31.314 [planetinfo.record]: planet zpos = 433620.000000 11:30:31.314 [planetinfo.record]: sun_radius = 80983.751221 11:30:31.314 [planetinfo.record]: sun_vector = 0.408 -0.395 -0.823 11:30:31.314 [planetinfo.record]: sun_distance = 709560 11:30:31.314 [planetinfo.record]: corona_flare = 0.041090 11:30:31.314 [planetinfo.record]: corona_hues = 0.908623 11:30:31.314 [planetinfo.record]: sun_color = 0.370487, 0.385256, 0.753851, 1 11:30:31.314 [planetinfo.record]: corona_shimmer = 0.335068 11:30:31.315 [planetinfo.record]: station_vector = 0.495 0.712 0.498 11:30:31.315 [planetinfo.record]: station = coriolis 11:30:35.856 [PLANETINFO OVER]: Done 11:30:35.857 [PLANETINFO LOGGING]: 0 254 11:30:36.861 [planetinfo.record]: seed = 36 138 91 95 11:30:36.861 [planetinfo.record]: coordinates = 138 248 11:30:36.861 [planetinfo.record]: government = 4; 11:30:36.861 [planetinfo.record]: economy = 0; 11:30:36.861 [planetinfo.record]: techlevel = 11; 11:30:36.861 [planetinfo.record]: population = 49; 11:30:36.861 [planetinfo.record]: productivity = 31360; 11:30:36.861 [planetinfo.record]: name = "Onlema"; 11:30:36.861 [planetinfo.record]: inhabitant = "Human Colonial"; 11:30:36.861 [planetinfo.record]: inhabitants = "Human Colonials"; 11:30:36.861 [planetinfo.record]: description = "This world is plagued by frequent solar activity."; 11:30:36.875 [planetinfo.record]: air_color = 0.640793, 0.867786, 0.939814, 1; 11:30:36.875 [planetinfo.record]: cloud_alpha = 1.000000; 11:30:36.875 [planetinfo.record]: cloud_color = 0.607615, 0.822266, 0.529976, 1; 11:30:36.875 [planetinfo.record]: cloud_fraction = 0.380000; 11:30:36.876 [planetinfo.record]: land_color = 0.572266, 0.549946, 0.532028, 1; 11:30:36.876 [planetinfo.record]: land_fraction = 0.650000; 11:30:36.876 [planetinfo.record]: polar_cloud_color = 0.728072, 0.87002, 0.676729, 1; 11:30:36.876 [planetinfo.record]: polar_land_color = 0.942773, 0.933581, 0.926201, 1; 11:30:36.876 [planetinfo.record]: polar_sea_color = 0.894102, 0.920508, 0.836008, 1; 11:30:36.876 [planetinfo.record]: sea_color = 0.703708, 0.794922, 0.503036, 1; 11:30:36.876 [planetinfo.record]: rotation_speed = 0.002135 11:30:36.876 [planetinfo.record]: planet zpos = 747340.000000 11:30:36.883 [planetinfo.record]: sun_radius = 170964.433289 11:30:36.884 [planetinfo.record]: sun_vector = -0.636 -0.681 -0.363 11:30:36.884 [planetinfo.record]: sun_distance = 1426740 11:30:36.884 [planetinfo.record]: corona_flare = 0.055128 11:30:36.884 [planetinfo.record]: corona_hues = 0.681061 11:30:36.884 [planetinfo.record]: sun_color = 0.751859, 0.645489, 0.314474, 1 11:30:36.884 [planetinfo.record]: corona_shimmer = 0.388408 11:30:36.884 [planetinfo.record]: station_vector = -0.217 0.678 0.702 11:30:36.884 [planetinfo.record]: station = dodecahedron 11:30:41.403 [PLANETINFO OVER]: Done 11:30:41.405 [PLANETINFO LOGGING]: 0 255 11:30:42.408 [planetinfo.record]: seed = 76 12 61 185 11:30:42.408 [planetinfo.record]: coordinates = 12 203 11:30:42.408 [planetinfo.record]: government = 1; 11:30:42.408 [planetinfo.record]: economy = 3; 11:30:42.408 [planetinfo.record]: techlevel = 5; 11:30:42.408 [planetinfo.record]: population = 25; 11:30:42.408 [planetinfo.record]: productivity = 7000; 11:30:42.408 [planetinfo.record]: name = "Orerve"; 11:30:42.408 [planetinfo.record]: inhabitant = "Human Colonial"; 11:30:42.408 [planetinfo.record]: inhabitants = "Human Colonials"; 11:30:42.408 [planetinfo.record]: description = "This planet is a dull place."; 11:30:42.411 [planetinfo.record]: air_color = 0.452782, 0.779924, 0.836402, 1; 11:30:42.411 [planetinfo.record]: cloud_alpha = 1.000000; 11:30:42.411 [planetinfo.record]: cloud_color = 0.331474, 0.511719, 0.113937, 1; 11:30:42.411 [planetinfo.record]: cloud_fraction = 0.450000; 11:30:42.411 [planetinfo.record]: land_color = 0.550781, 0.494843, 0.494843, 1; 11:30:42.411 [planetinfo.record]: land_fraction = 0.290000; 11:30:42.411 [planetinfo.record]: polar_cloud_color = 0.569507, 0.730273, 0.375477, 1; 11:30:42.412 [planetinfo.record]: polar_land_color = 0.944922, 0.92093, 0.92093, 1; 11:30:42.412 [planetinfo.record]: polar_sea_color = 0.908813, 0.924414, 0.853097, 1; 11:30:42.412 [planetinfo.record]: sea_color = 0.704835, 0.755859, 0.522606, 1; 11:30:42.412 [planetinfo.record]: rotation_speed = 0.000203 11:30:42.412 [planetinfo.record]: planet zpos = 461880.000000 11:30:42.412 [planetinfo.record]: sun_radius = 141078.316650 11:30:42.412 [planetinfo.record]: sun_vector = 0.115 -0.510 -0.852 11:30:42.412 [planetinfo.record]: sun_distance = 923760 11:30:42.412 [planetinfo.record]: corona_flare = 0.036794 11:30:42.412 [planetinfo.record]: corona_hues = 0.821037 11:30:42.412 [planetinfo.record]: sun_color = 0.347402, 0.626936, 0.72515, 1 11:30:42.412 [planetinfo.record]: corona_shimmer = 0.521617 11:30:42.412 [planetinfo.record]: station_vector = 0.026 -0.971 0.236 11:30:42.412 [planetinfo.record]: station = coriolis 11:30:47.715 [PLANETINFO OVER]: Done 11:48:20.624 [planetinfo.record]: seed = 208 10 250 161 11:48:20.624 [planetinfo.record]: coordinates = 10 190 11:48:20.624 [planetinfo.record]: government = 2; 11:48:20.624 [planetinfo.record]: economy = 6; 11:48:20.624 [planetinfo.record]: techlevel = 4; 11:48:20.624 [planetinfo.record]: population = 25; 11:48:20.624 [planetinfo.record]: productivity = 4800; 11:48:20.624 [planetinfo.record]: name = "Lemaed"; 11:48:20.624 [planetinfo.record]: inhabitant = "Large Harmless Fat Humanoid"; 11:48:20.624 [planetinfo.record]: inhabitants = "Large Harmless Fat Humanoids"; 11:48:20.624 [planetinfo.record]: description = "This planet is mildly fabled for its inhabitants’ eccentric shyness but cursed by unpredictable earthquakes."; 11:48:20.628 [planetinfo.record]: air_color = 0.668291, 0.877975, 0.891686, 1; 11:48:20.628 [planetinfo.record]: cloud_alpha = 1.000000; 11:48:20.628 [planetinfo.record]: cloud_color = 0.657569, 0.677734, 0.574486, 1; 11:48:20.628 [planetinfo.record]: cloud_fraction = 0.470000; 11:48:20.628 [planetinfo.record]: land_color = 0.560501, 0.66, 0.492422, 1; 11:48:20.628 [planetinfo.record]: land_fraction = 0.520000; 11:48:20.628 [planetinfo.record]: polar_cloud_color = 0.790011, 0.80498, 0.728334, 1; 11:48:20.628 [planetinfo.record]: polar_land_color = 0.898798, 0.934, 0.874713, 1; 11:48:20.628 [planetinfo.record]: polar_sea_color = 0.921572, 0.877162, 0.946484, 1; 11:48:20.628 [planetinfo.record]: sea_color = 0.478812, 0.378372, 0.535156, 1; 11:48:20.628 [planetinfo.record]: rotation_speed = 0.002497 11:48:20.628 [planetinfo.record]: planet zpos = 369840.000000 11:48:20.628 [planetinfo.record]: sun_radius = 67446.966553 11:48:20.628 [planetinfo.record]: sun_vector = 0.278 0.894 0.352 11:48:20.628 [planetinfo.record]: sun_distance = 585580 11:48:20.628 [planetinfo.record]: corona_flare = 0.032404 11:48:20.628 [planetinfo.record]: corona_hues = 0.815834 11:48:20.628 [planetinfo.record]: sun_color = 0.653179, 0.653369, 0.653583, 1 11:48:20.628 [planetinfo.record]: corona_shimmer = 0.386452 11:48:20.629 [planetinfo.record]: station_vector = -0.640 -0.757 0.131 11:48:20.629 [planetinfo.record]: station = coriolis 11:48:24.658 [PLANETINFO OVER]: Done 11:50:24.883 [PLANETINFO LOGGING]: 1 0 11:50:25.670 [planetinfo.record]: seed = 144 4 166 111 11:50:25.670 [planetinfo.record]: coordinates = 4 180 11:50:25.670 [planetinfo.record]: government = 2; 11:50:25.670 [planetinfo.record]: economy = 4; 11:50:25.671 [planetinfo.record]: techlevel = 4; 11:50:25.671 [planetinfo.record]: population = 23; 11:50:25.671 [planetinfo.record]: productivity = 6624; 11:50:25.671 [planetinfo.record]: name = "Ausis"; 11:50:25.671 [planetinfo.record]: inhabitant = "Blue Slimy Lobster"; 11:50:25.671 [planetinfo.record]: inhabitants = "Blue Slimy Lobsters"; 11:50:25.671 [planetinfo.record]: description = "The world Ausis is scourged by deadly tree ants."; 11:50:25.673 [planetinfo.record]: air_color = 0.630091, 0.496728, 0.937863, 1; 11:50:25.674 [planetinfo.record]: cloud_alpha = 1.000000; 11:50:25.674 [planetinfo.record]: cloud_color = 0.764584, 0.153076, 0.816406, 1; 11:50:25.674 [planetinfo.record]: cloud_fraction = 0.380000; 11:50:25.674 [planetinfo.record]: land_color = 0.66, 0.626484, 0.646122, 1; 11:50:25.674 [planetinfo.record]: land_fraction = 0.430000; 11:50:25.674 [planetinfo.record]: polar_cloud_color = 0.832971, 0.426915, 0.867383, 1; 11:50:25.674 [planetinfo.record]: polar_land_color = 0.934, 0.922143, 0.92909, 1; 11:50:25.675 [planetinfo.record]: polar_sea_color = 0.948438, 0.910325, 0.736336, 1; 11:50:25.675 [planetinfo.record]: sea_color = 0.515625, 0.432745, 0.0543823, 1; 11:50:25.675 [planetinfo.record]: rotation_speed = 0.000129 11:50:25.675 [planetinfo.record]: planet zpos = 732600.000000 11:50:25.675 [planetinfo.record]: sun_radius = 215050.643921 11:50:25.675 [planetinfo.record]: sun_vector = 0.257 -0.899 0.356 11:50:25.675 [planetinfo.record]: sun_distance = 1132200 11:50:25.675 [planetinfo.record]: corona_flare = 0.084846 11:50:25.675 [planetinfo.record]: corona_hues = 0.613647 11:50:25.675 [planetinfo.record]: sun_color = 0.666535, 0.705181, 0.760623, 1 11:50:25.676 [planetinfo.record]: corona_shimmer = 0.334986 11:50:25.676 [planetinfo.record]: station_vector = 0.005 0.206 0.979 11:50:25.676 [planetinfo.record]: station = coriolis 11:50:29.586 [PLANETINFO OVER]: Done 11:50:29.586 [PLANETINFO LOGGING]: 1 1 11:50:30.589 [planetinfo.record]: seed = 112 53 58 251 11:50:30.589 [planetinfo.record]: coordinates = 53 157 11:50:30.589 [planetinfo.record]: government = 6; 11:50:30.589 [planetinfo.record]: economy = 5; 11:50:30.589 [planetinfo.record]: techlevel = 6; 11:50:30.589 [planetinfo.record]: population = 36; 11:50:30.589 [planetinfo.record]: productivity = 14400; 11:50:30.589 [planetinfo.record]: name = "Andiri"; 11:50:30.589 [planetinfo.record]: inhabitant = "Human Colonial"; 11:50:30.589 [planetinfo.record]: inhabitants = "Human Colonials"; 11:50:30.589 [planetinfo.record]: description = "Andiri is ravaged by unpredictable solar activity."; 11:50:30.604 [planetinfo.record]: air_color = 0.671645, 0.837559, 0.930059, 1; 11:50:30.604 [planetinfo.record]: cloud_alpha = 1.000000; 11:50:30.604 [planetinfo.record]: cloud_color = 0.607117, 0.792969, 0.623088, 1; 11:50:30.604 [planetinfo.record]: cloud_fraction = 0.280000; 11:50:30.604 [planetinfo.record]: land_color = 0.66, 0.00257813, 0.387786, 1; 11:50:30.604 [planetinfo.record]: land_fraction = 0.250000; 11:50:30.604 [planetinfo.record]: polar_cloud_color = 0.731323, 0.856836, 0.742109, 1; 11:50:30.604 [planetinfo.record]: polar_land_color = 0.934, 0.701412, 0.837694, 1; 11:50:30.604 [planetinfo.record]: polar_sea_color = 0.941992, 0.899381, 0.886337, 1; 11:50:30.604 [planetinfo.record]: sea_color = 0.580078, 0.47512, 0.442989, 1; 11:50:30.604 [planetinfo.record]: rotation_speed = 0.001294 11:50:30.604 [planetinfo.record]: planet zpos = 739050.000000 11:50:30.604 [planetinfo.record]: sun_radius = 140529.737091 11:50:30.604 [planetinfo.record]: sun_vector = -0.001 0.735 -0.678 11:50:30.604 [planetinfo.record]: sun_distance = 909600 11:50:30.604 [planetinfo.record]: corona_flare = 0.020485 11:50:30.604 [planetinfo.record]: corona_hues = 0.897713 11:50:30.604 [planetinfo.record]: sun_color = 0.804047, 0.634065, 0.554463, 1 11:50:30.605 [planetinfo.record]: corona_shimmer = 0.307254 11:50:30.605 [planetinfo.record]: station_vector = -0.570 -0.437 0.696 11:50:30.605 [planetinfo.record]: station = coriolis 11:50:35.250 [PLANETINFO OVER]: Done 11:50:35.252 [PLANETINFO LOGGING]: 1 2 11:50:36.271 [planetinfo.record]: seed = 56 199 54 147 11:50:36.271 [planetinfo.record]: coordinates = 199 254 11:50:36.271 [planetinfo.record]: government = 7; 11:50:36.272 [planetinfo.record]: economy = 6; 11:50:36.272 [planetinfo.record]: techlevel = 8; 11:50:36.272 [planetinfo.record]: population = 46; 11:50:36.272 [planetinfo.record]: productivity = 16192; 11:50:36.272 [planetinfo.record]: name = "Beedbeon"; 11:50:36.272 [planetinfo.record]: inhabitant = "Human Colonial"; 11:50:36.272 [planetinfo.record]: inhabitants = "Human Colonials"; 11:50:36.272 [planetinfo.record]: description = "The planet Beedbeon is most famous for its vast oceans."; 11:50:36.284 [planetinfo.record]: air_color = 0.701557, 0.844101, 0.876727, 1; 11:50:36.285 [planetinfo.record]: cloud_alpha = 1.000000; 11:50:36.285 [planetinfo.record]: cloud_color = 0.632812, 0.632812, 0.632812, 1; 11:50:36.285 [planetinfo.record]: cloud_fraction = 0.240000; 11:50:36.285 [planetinfo.record]: land_color = 0.182594, 0.513672, 0.280883, 1; 11:50:36.285 [planetinfo.record]: land_fraction = 0.660000; 11:50:36.285 [planetinfo.record]: polar_cloud_color = 0.784766, 0.784766, 0.784766, 1; 11:50:36.285 [planetinfo.record]: polar_land_color = 0.795777, 0.948633, 0.841156, 1; 11:50:36.285 [planetinfo.record]: polar_sea_color = 0.921094, 0.872296, 0.809555, 1; 11:50:36.285 [planetinfo.record]: sea_color = 0.789062, 0.621849, 0.40686, 1; 11:50:36.285 [planetinfo.record]: rotation_speed = 0.003613 11:50:36.286 [planetinfo.record]: planet zpos = 416130.000000 11:50:36.286 [planetinfo.record]: sun_radius = 120129.414368 11:50:36.286 [planetinfo.record]: sun_vector = 0.435 -0.889 -0.144 11:50:36.286 [planetinfo.record]: sun_distance = 718770 11:50:36.286 [planetinfo.record]: corona_flare = 0.040021 11:50:36.286 [planetinfo.record]: corona_hues = 0.538445 11:50:36.286 [planetinfo.record]: sun_color = 0.510266, 0.569404, 0.759363, 1 11:50:36.286 [planetinfo.record]: corona_shimmer = 0.291580 11:50:36.286 [planetinfo.record]: station_vector = 0.800 0.536 0.270 11:50:36.286 [planetinfo.record]: station = coriolis 11:50:41.109 [PLANETINFO OVER]: Done 11:50:41.110 [PLANETINFO LOGGING]: 1 3 11:50:42.112 [planetinfo.record]: seed = 40 159 26 171 11:50:42.112 [planetinfo.record]: coordinates = 159 179 11:50:42.112 [planetinfo.record]: government = 5; 11:50:42.112 [planetinfo.record]: economy = 3; 11:50:42.112 [planetinfo.record]: techlevel = 10; 11:50:42.112 [planetinfo.record]: population = 49; 11:50:42.112 [planetinfo.record]: productivity = 24696; 11:50:42.112 [planetinfo.record]: name = "Maisso"; 11:50:42.112 [planetinfo.record]: inhabitant = "Human Colonial"; 11:50:42.112 [planetinfo.record]: inhabitants = "Human Colonials"; 11:50:42.112 [planetinfo.record]: description = "The planet Maisso is a boring planet."; 11:50:42.121 [planetinfo.record]: air_color = 0.5656, 0.928758, 0.809329, 1; 11:50:42.121 [planetinfo.record]: cloud_alpha = 1.000000; 11:50:42.121 [planetinfo.record]: cloud_color = 0.789062, 0.346587, 0.335968, 1; 11:50:42.121 [planetinfo.record]: cloud_fraction = 0.550000; 11:50:42.121 [planetinfo.record]: land_color = 0.317109, 0.66, 0.483197, 1; 11:50:42.121 [planetinfo.record]: land_fraction = 0.500000; 11:50:42.122 [planetinfo.record]: polar_cloud_color = 0.855078, 0.555394, 0.548202, 1; 11:50:42.122 [planetinfo.record]: polar_land_color = 0.812689, 0.934, 0.871449, 1; 11:50:42.122 [planetinfo.record]: polar_sea_color = 0.937305, 0.888191, 0.875062, 1; 11:50:42.122 [planetinfo.record]: sea_color = 0.626953, 0.495547, 0.460419, 1; 11:50:42.122 [planetinfo.record]: rotation_speed = 0.004711 11:50:42.122 [planetinfo.record]: planet zpos = 694920.000000 11:50:42.122 [planetinfo.record]: sun_radius = 173471.094513 11:50:42.122 [planetinfo.record]: sun_vector = 0.882 0.472 -0.003 11:50:42.122 [planetinfo.record]: sun_distance = 1216110 11:50:42.122 [planetinfo.record]: corona_flare = 0.038908 11:50:42.122 [planetinfo.record]: corona_hues = 0.802155 11:50:42.122 [planetinfo.record]: sun_color = 0.760681, 0.64367, 0.637188, 1 11:50:42.122 [planetinfo.record]: corona_shimmer = 0.578153 11:50:42.122 [planetinfo.record]: station_vector = -0.777 0.629 0.033 11:50:42.123 [planetinfo.record]: station = coriolis 11:50:46.683 [PLANETINFO OVER]: Done 11:50:46.684 [PLANETINFO LOGGING]: 1 4 11:50:47.702 [planetinfo.record]: seed = 64 240 102 53 11:50:47.702 [planetinfo.record]: coordinates = 240 71 11:50:47.702 [planetinfo.record]: government = 0; 11:50:47.702 [planetinfo.record]: economy = 7; 11:50:47.702 [planetinfo.record]: techlevel = 0; 11:50:47.702 [planetinfo.record]: population = 8; 11:50:47.702 [planetinfo.record]: productivity = 768; 11:50:47.702 [planetinfo.record]: name = "Ladibe"; 11:50:47.702 [planetinfo.record]: inhabitant = "Human Colonial"; 11:50:47.702 [planetinfo.record]: inhabitants = "Human Colonials"; 11:50:47.702 [planetinfo.record]: description = "Ladibe is mildly notable for its inhabitants’ unusual silliness."; 11:50:47.704 [planetinfo.record]: air_color = 0.55598, 0.478096, 0.937213, 1; 11:50:47.704 [planetinfo.record]: cloud_alpha = 1.000000; 11:50:47.704 [planetinfo.record]: cloud_color = 0.476349, 0.104988, 0.814453, 1; 11:50:47.704 [planetinfo.record]: cloud_fraction = 0.370000; 11:50:47.704 [planetinfo.record]: land_color = 0.429459, 0.66, 0.355781, 1; 11:50:47.704 [planetinfo.record]: land_fraction = 0.610000; 11:50:47.704 [planetinfo.record]: polar_cloud_color = 0.641684, 0.39475, 0.866504, 1; 11:50:47.704 [planetinfo.record]: polar_land_color = 0.852437, 0.934, 0.826371, 1; 11:50:47.704 [planetinfo.record]: polar_sea_color = 0.937468, 0.896197, 0.949023, 1; 11:50:47.704 [planetinfo.record]: sea_color = 0.484937, 0.396263, 0.509766, 1; 11:50:47.704 [planetinfo.record]: rotation_speed = 0.004514 11:50:47.704 [planetinfo.record]: planet zpos = 563680.000000 11:50:47.705 [planetinfo.record]: sun_radius = 127325.009766 11:50:47.705 [planetinfo.record]: sun_vector = -0.270 0.597 -0.755 11:50:47.705 [planetinfo.record]: sun_distance = 823840 11:50:47.705 [planetinfo.record]: corona_flare = 0.032024 11:50:47.705 [planetinfo.record]: corona_hues = 0.749718 11:50:47.705 [planetinfo.record]: sun_color = 0.475452, 0.486292, 0.682886, 1 11:50:47.705 [planetinfo.record]: corona_shimmer = 0.301639 11:50:47.705 [planetinfo.record]: station_vector = -0.010 0.845 0.535 11:50:47.705 [planetinfo.record]: station = coriolis 11:50:52.596 [PLANETINFO OVER]: Done 11:50:52.597 [PLANETINFO LOGGING]: 1 5 11:50:53.601 [planetinfo.record]: seed = 192 53 26 54 11:50:53.601 [planetinfo.record]: coordinates = 53 147 11:50:53.601 [planetinfo.record]: government = 0; 11:50:53.601 [planetinfo.record]: economy = 3; 11:50:53.601 [planetinfo.record]: techlevel = 5; 11:50:53.601 [planetinfo.record]: population = 24; 11:50:53.601 [planetinfo.record]: productivity = 5376; 11:50:53.601 [planetinfo.record]: name = "Veriar"; 11:50:53.601 [planetinfo.record]: inhabitant = "Human Colonial"; 11:50:53.601 [planetinfo.record]: inhabitants = "Human Colonials"; 11:50:53.601 [planetinfo.record]: description = "The world Veriar is very famous for Veriarian shrew steak but ravaged by dreadful civil war."; 11:50:53.632 [planetinfo.record]: air_color = 0.758589, 0.633354, 0.853963, 1; 11:50:53.632 [planetinfo.record]: cloud_alpha = 1.000000; 11:50:53.632 [planetinfo.record]: cloud_color = 0.564453, 0.465233, 0.493139, 1; 11:50:53.632 [planetinfo.record]: cloud_fraction = 0.460000; 11:50:53.632 [planetinfo.record]: land_color = 0.487266, 0.66, 0.314531, 1; 11:50:53.632 [planetinfo.record]: land_fraction = 0.350000; 11:50:53.632 [planetinfo.record]: polar_cloud_color = 0.754004, 0.671167, 0.694465, 1; 11:50:53.632 [planetinfo.record]: polar_land_color = 0.872889, 0.934, 0.811777, 1; 11:50:53.632 [planetinfo.record]: polar_sea_color = 0.889204, 0.931055, 0.879047, 1; 11:50:53.632 [planetinfo.record]: sea_color = 0.565491, 0.689453, 0.535403, 1; 11:50:53.632 [planetinfo.record]: rotation_speed = 0.000579 11:50:53.632 [planetinfo.record]: planet zpos = 440500.000000 11:50:53.632 [planetinfo.record]: sun_radius = 123508.843994 11:50:53.632 [planetinfo.record]: sun_vector = 0.199 0.909 0.365 11:50:53.632 [planetinfo.record]: sun_distance = 969100 11:50:53.632 [planetinfo.record]: corona_flare = 0.023370 11:50:53.632 [planetinfo.record]: corona_hues = 0.964149 11:50:53.632 [planetinfo.record]: sun_color = 0.821469, 0.688744, 0.40604, 1 11:50:53.633 [planetinfo.record]: corona_shimmer = 0.258002 11:50:53.633 [planetinfo.record]: station_vector = -0.770 -0.619 0.159 11:50:53.633 [planetinfo.record]: station = coriolis 11:50:58.086 [PLANETINFO OVER]: Done 11:50:58.087 [PLANETINFO LOGGING]: 1 6 11:50:59.092 [planetinfo.record]: seed = 168 159 54 9 11:50:59.092 [planetinfo.record]: coordinates = 159 106 11:50:59.092 [planetinfo.record]: government = 5; 11:50:59.092 [planetinfo.record]: economy = 2; 11:50:59.092 [planetinfo.record]: techlevel = 11; 11:50:59.092 [planetinfo.record]: population = 52; 11:50:59.092 [planetinfo.record]: productivity = 29952; 11:50:59.092 [planetinfo.record]: name = "Esbete"; 11:50:59.092 [planetinfo.record]: inhabitant = "Human Colonial"; 11:50:59.092 [planetinfo.record]: inhabitants = "Human Colonials"; 11:50:59.092 [planetinfo.record]: description = "This planet is notable for its unusual oceans and its inhabitants’ exceptional loathing of food blenders."; 11:50:59.099 [planetinfo.record]: air_color = 0.695045, 0.777855, 0.952172, 1; 11:50:59.099 [planetinfo.record]: cloud_alpha = 1.000000; 11:50:59.100 [planetinfo.record]: cloud_color = 0.678101, 0.850878, 0.859375, 1; 11:50:59.100 [planetinfo.record]: cloud_fraction = 0.050000; 11:50:59.100 [planetinfo.record]: land_color = 0.425922, 0.301231, 0.626953, 1; 11:50:59.100 [planetinfo.record]: land_fraction = 0.480000; 11:50:59.100 [planetinfo.record]: polar_cloud_color = 0.769817, 0.881239, 0.886719, 1; 11:50:59.100 [planetinfo.record]: polar_land_color = 0.862168, 0.815565, 0.937305, 1; 11:50:59.100 [planetinfo.record]: polar_sea_color = 0.923085, 0.938086, 0.890082, 1; 11:50:59.100 [planetinfo.record]: sea_color = 0.579537, 0.619141, 0.49241, 1; 11:50:59.100 [planetinfo.record]: rotation_speed = 0.002918 11:50:59.100 [planetinfo.record]: planet zpos = 580690.000000 11:50:59.100 [planetinfo.record]: sun_radius = 124801.920319 11:50:59.100 [planetinfo.record]: sun_vector = 0.223 0.137 0.965 11:50:59.100 [planetinfo.record]: sun_distance = 1108590 11:50:59.100 [planetinfo.record]: corona_flare = 0.030692 11:50:59.100 [planetinfo.record]: corona_hues = 0.875916 11:50:59.100 [planetinfo.record]: sun_color = 0.787585, 0.807382, 0.835001, 1 11:50:59.101 [planetinfo.record]: corona_shimmer = 0.449517 11:50:59.101 [planetinfo.record]: station_vector = 0.841 0.538 0.054 11:50:59.101 [planetinfo.record]: station = dodecahedron 11:51:03.353 [PLANETINFO OVER]: Done 11:51:03.354 [PLANETINFO LOGGING]: 1 7 11:51:04.357 [planetinfo.record]: seed = 56 217 58 169 11:51:04.357 [planetinfo.record]: coordinates = 217 188 11:51:04.357 [planetinfo.record]: government = 7; 11:51:04.357 [planetinfo.record]: economy = 4; 11:51:04.357 [planetinfo.record]: techlevel = 8; 11:51:04.357 [planetinfo.record]: population = 44; 11:51:04.357 [planetinfo.record]: productivity = 23232; 11:51:04.357 [planetinfo.record]: name = "Esrilees"; 11:51:04.357 [planetinfo.record]: inhabitant = "Human Colonial"; 11:51:04.357 [planetinfo.record]: inhabitants = "Human Colonials"; 11:51:04.357 [planetinfo.record]: description = "The world Esrilees is notable for its unusual tropical forests and its fabulous Esrileesian lethal water."; 11:51:04.372 [planetinfo.record]: air_color = 0.537939, 0.565894, 0.855914, 1; 11:51:04.372 [planetinfo.record]: cloud_alpha = 1.000000; 11:51:04.372 [planetinfo.record]: cloud_color = 0.276245, 0.349762, 0.570312, 1; 11:51:04.372 [planetinfo.record]: cloud_fraction = 0.430000; 11:51:04.372 [planetinfo.record]: land_color = 0.629063, 0.66, 0.641873, 1; 11:51:04.372 [planetinfo.record]: land_fraction = 0.580000; 11:51:04.372 [planetinfo.record]: polar_cloud_color = 0.512801, 0.573761, 0.756641, 1; 11:51:04.372 [planetinfo.record]: polar_land_color = 0.923055, 0.934, 0.927587, 1; 11:51:04.372 [planetinfo.record]: polar_sea_color = 0.706226, 0.927148, 0.927148, 1; 11:51:04.372 [planetinfo.record]: sea_color = 0.0341492, 0.728516, 0.728516, 1; 11:51:04.372 [planetinfo.record]: rotation_speed = 0.004094 11:51:04.372 [planetinfo.record]: planet zpos = 693810.000000 11:51:04.372 [planetinfo.record]: sun_radius = 127942.717896 11:51:04.372 [planetinfo.record]: sun_vector = 0.570 0.650 -0.502 11:51:04.372 [planetinfo.record]: sun_distance = 1120770 11:51:04.372 [planetinfo.record]: corona_flare = 0.098083 11:51:04.372 [planetinfo.record]: corona_hues = 0.614853 11:51:04.372 [planetinfo.record]: sun_color = 0.201978, 0.2599, 0.737769, 1 11:51:04.373 [planetinfo.record]: corona_shimmer = 0.350617 11:51:04.373 [planetinfo.record]: station_vector = 0.002 -0.779 0.627 11:51:04.373 [planetinfo.record]: station = coriolis 11:51:09.020 [PLANETINFO OVER]: Done 11:51:09.021 [PLANETINFO LOGGING]: 1 8 11:51:10.042 [planetinfo.record]: seed = 112 169 166 169 11:51:10.042 [planetinfo.record]: coordinates = 169 193 11:51:10.042 [planetinfo.record]: government = 6; 11:51:10.042 [planetinfo.record]: economy = 1; 11:51:10.042 [planetinfo.record]: techlevel = 10; 11:51:10.042 [planetinfo.record]: population = 48; 11:51:10.042 [planetinfo.record]: productivity = 34560; 11:51:10.042 [planetinfo.record]: name = "Esrasoce"; 11:51:10.042 [planetinfo.record]: inhabitant = "Small Harmless Slimy Frog"; 11:51:10.042 [planetinfo.record]: inhabitants = "Small Harmless Slimy Frogs"; 11:51:10.042 [planetinfo.record]: description = "The planet Esrasoce is a boring world."; 11:51:10.071 [planetinfo.record]: air_color = 0.589856, 0.940465, 0.912728, 1; 11:51:10.071 [planetinfo.record]: cloud_alpha = 1.000000; 11:51:10.071 [planetinfo.record]: cloud_color = 0.824219, 0.727203, 0.396011, 1; 11:51:10.072 [planetinfo.record]: cloud_fraction = 0.530000; 11:51:10.072 [planetinfo.record]: land_color = 0.652266, 0.66, 0.652749, 1; 11:51:10.072 [planetinfo.record]: land_fraction = 0.590000; 11:51:10.072 [planetinfo.record]: polar_cloud_color = 0.870898, 0.80683, 0.588112, 1; 11:51:10.072 [planetinfo.record]: polar_land_color = 0.931264, 0.934, 0.931435, 1; 11:51:10.072 [planetinfo.record]: polar_sea_color = 0.77004, 0.944336, 0.813614, 1; 11:51:10.073 [planetinfo.record]: sea_color = 0.145683, 0.556641, 0.248423, 1; 11:51:10.073 [planetinfo.record]: rotation_speed = 0.003931 11:51:10.073 [planetinfo.record]: planet zpos = 581790.000000 11:51:10.073 [planetinfo.record]: sun_radius = 116796.866913 11:51:10.073 [planetinfo.record]: sun_vector = -0.439 -0.528 0.727 11:51:10.073 [planetinfo.record]: sun_distance = 1110690 11:51:10.073 [planetinfo.record]: corona_flare = 0.058655 11:51:10.073 [planetinfo.record]: corona_hues = 0.663345 11:51:10.073 [planetinfo.record]: sun_color = 0.461349, 0.659668, 0.733511, 1 11:51:10.074 [planetinfo.record]: corona_shimmer = 0.940690 11:51:10.074 [planetinfo.record]: station_vector = 0.393 0.872 0.292 11:51:10.074 [planetinfo.record]: station = coriolis 11:51:14.717 [PLANETINFO OVER]: Done 11:51:14.718 [PLANETINFO LOGGING]: 1 9 11:51:15.737 [planetinfo.record]: seed = 144 37 122 161 11:51:15.737 [planetinfo.record]: coordinates = 37 103 11:51:15.738 [planetinfo.record]: government = 2; 11:51:15.738 [planetinfo.record]: economy = 7; 11:51:15.738 [planetinfo.record]: techlevel = 2; 11:51:15.738 [planetinfo.record]: population = 18; 11:51:15.738 [planetinfo.record]: productivity = 2592; 11:51:15.738 [planetinfo.record]: name = "Lerela"; 11:51:15.738 [planetinfo.record]: inhabitant = "Human Colonial"; 11:51:15.738 [planetinfo.record]: inhabitants = "Human Colonials"; 11:51:15.738 [planetinfo.record]: description = "This planet is a tedious little planet."; 11:51:15.741 [planetinfo.record]: air_color = 0.630744, 0.785373, 0.989244, 1; 11:51:15.742 [planetinfo.record]: cloud_alpha = 1.000000; 11:51:15.742 [planetinfo.record]: cloud_color = 0.496727, 0.970703, 0.837397, 1; 11:51:15.742 [planetinfo.record]: cloud_fraction = 0.260000; 11:51:15.742 [planetinfo.record]: land_color = 0.528867, 0.33886, 0.615234, 1; 11:51:15.742 [planetinfo.record]: land_fraction = 0.300000; 11:51:15.742 [planetinfo.record]: polar_cloud_color = 0.650923, 0.936816, 0.856409, 1; 11:51:15.742 [planetinfo.record]: polar_land_color = 0.905541, 0.833081, 0.938477, 1; 11:51:15.742 [planetinfo.record]: polar_sea_color = 0.92793, 0.870932, 0.84003, 1; 11:51:15.742 [planetinfo.record]: sea_color = 0.720703, 0.543629, 0.447624, 1; 11:51:15.742 [planetinfo.record]: rotation_speed = 0.000001 11:51:15.742 [planetinfo.record]: planet zpos = 466350.000000 11:51:15.742 [planetinfo.record]: sun_radius = 75320.287933 11:51:15.742 [planetinfo.record]: sun_vector = 0.182 -0.647 0.741 11:51:15.742 [planetinfo.record]: sun_distance = 590710 11:51:15.742 [planetinfo.record]: corona_flare = 0.006175 11:51:15.743 [planetinfo.record]: corona_hues = 0.970184 11:51:15.743 [planetinfo.record]: sun_color = 0.717381, 0.769708, 0.777185, 1 11:51:15.743 [planetinfo.record]: corona_shimmer = 0.652190 11:51:15.743 [planetinfo.record]: station_vector = 0.117 0.670 0.733 11:51:15.743 [planetinfo.record]: station = coriolis 11:51:20.454 [PLANETINFO OVER]: Done 11:51:20.455 [PLANETINFO LOGGING]: 1 10 11:51:21.458 [planetinfo.record]: seed = 152 197 182 233 11:51:21.459 [planetinfo.record]: coordinates = 197 245 11:51:21.459 [planetinfo.record]: government = 3; 11:51:21.459 [planetinfo.record]: economy = 5; 11:51:21.459 [planetinfo.record]: techlevel = 5; 11:51:21.459 [planetinfo.record]: population = 29; 11:51:21.459 [planetinfo.record]: productivity = 8120; 11:51:21.459 [planetinfo.record]: name = "Eszara"; 11:51:21.459 [planetinfo.record]: inhabitant = "Small Slimy Frog"; 11:51:21.459 [planetinfo.record]: inhabitants = "Small Slimy Frogs"; 11:51:21.459 [planetinfo.record]: description = "This planet is beset by lethal disease."; 11:51:21.484 [planetinfo.record]: air_color = 0.536472, 0.581174, 0.850061, 1; 11:51:21.484 [planetinfo.record]: cloud_alpha = 1.000000; 11:51:21.484 [planetinfo.record]: cloud_color = 0.272049, 0.38827, 0.552734, 1; 11:51:21.484 [planetinfo.record]: cloud_fraction = 0.330000; 11:51:21.484 [planetinfo.record]: land_color = 0.66, 0.447305, 0.319688, 1; 11:51:21.484 [planetinfo.record]: land_fraction = 0.540000; 11:51:21.484 [planetinfo.record]: polar_cloud_color = 0.511096, 0.609492, 0.74873, 1; 11:51:21.484 [planetinfo.record]: polar_land_color = 0.934, 0.858751, 0.813602, 1; 11:51:21.484 [planetinfo.record]: polar_sea_color = 0.876358, 0.927344, 0.891494, 1; 11:51:21.484 [planetinfo.record]: sea_color = 0.566776, 0.726562, 0.614212, 1; 11:51:21.484 [planetinfo.record]: rotation_speed = 0.002402 11:51:21.484 [planetinfo.record]: planet zpos = 531700.000000 11:51:21.484 [planetinfo.record]: sun_radius = 145556.282501 11:51:21.484 [planetinfo.record]: sun_vector = 0.461 0.817 -0.346 11:51:21.484 [planetinfo.record]: sun_distance = 957060 11:51:21.484 [planetinfo.record]: corona_flare = 0.040640 11:51:21.485 [planetinfo.record]: corona_hues = 0.685516 11:51:21.485 [planetinfo.record]: sun_color = 0.814005, 0.569614, 0.526126, 1 11:51:21.485 [planetinfo.record]: corona_shimmer = 0.306352 11:51:21.485 [planetinfo.record]: station_vector = -0.988 -0.129 0.091 11:51:21.485 [planetinfo.record]: station = coriolis 11:51:26.057 [PLANETINFO OVER]: Done 11:51:26.057 [PLANETINFO LOGGING]: 1 11 11:51:27.060 [planetinfo.record]: seed = 200 226 218 219 11:51:27.060 [planetinfo.record]: coordinates = 226 84 11:51:27.060 [planetinfo.record]: government = 1; 11:51:27.060 [planetinfo.record]: economy = 6; 11:51:27.060 [planetinfo.record]: techlevel = 4; 11:51:27.060 [planetinfo.record]: population = 24; 11:51:27.060 [planetinfo.record]: productivity = 3840; 11:51:27.060 [planetinfo.record]: name = "Anenat"; 11:51:27.060 [planetinfo.record]: inhabitant = "Frog"; 11:51:27.060 [planetinfo.record]: inhabitants = "Frogs"; 11:51:27.060 [planetinfo.record]: description = "This planet is mildly fabled for the Anenatian mountain poet but scourged by frequent civil war."; 11:51:27.062 [planetinfo.record]: air_color = 0.499265, 0.562449, 0.879979, 1; 11:51:27.062 [planetinfo.record]: cloud_alpha = 1.000000; 11:51:27.062 [planetinfo.record]: cloud_color = 0.193275, 0.410906, 0.642578, 1; 11:51:27.062 [planetinfo.record]: cloud_fraction = 0.260000; 11:51:27.062 [planetinfo.record]: land_color = 0.446725, 0.580078, 0.0294571, 1; 11:51:27.062 [planetinfo.record]: land_fraction = 0.700000; 11:51:27.062 [planetinfo.record]: polar_cloud_color = 0.444288, 0.611335, 0.78916, 1; 11:51:27.062 [planetinfo.record]: polar_land_color = 0.887854, 0.941992, 0.718453, 1; 11:51:27.062 [planetinfo.record]: polar_sea_color = 0.930664, 0.910286, 0.867044, 1; 11:51:27.062 [planetinfo.record]: sea_color = 0.693359, 0.632631, 0.503769, 1; 11:51:27.062 [planetinfo.record]: rotation_speed = 0.003494 11:51:27.062 [planetinfo.record]: planet zpos = 761540.000000 11:51:27.062 [planetinfo.record]: sun_radius = 134272.947083 11:51:27.062 [planetinfo.record]: sun_vector = -0.626 0.775 -0.088 11:51:27.062 [planetinfo.record]: sun_distance = 995860 11:51:27.063 [planetinfo.record]: corona_flare = 0.077666 11:51:27.063 [planetinfo.record]: corona_hues = 0.516922 11:51:27.063 [planetinfo.record]: sun_color = 0.68053, 0.647307, 0.362302, 1 11:51:27.063 [planetinfo.record]: corona_shimmer = 0.809186 11:51:27.063 [planetinfo.record]: station_vector = 0.751 -0.642 0.155 11:51:27.063 [planetinfo.record]: station = coriolis 11:51:31.789 [PLANETINFO OVER]: Done 11:51:31.790 [PLANETINFO LOGGING]: 1 12 11:51:32.793 [planetinfo.record]: seed = 32 192 102 164 11:51:32.793 [planetinfo.record]: coordinates = 192 209 11:51:32.793 [planetinfo.record]: government = 4; 11:51:32.794 [planetinfo.record]: economy = 1; 11:51:32.794 [planetinfo.record]: techlevel = 8; 11:51:32.794 [planetinfo.record]: population = 38; 11:51:32.794 [planetinfo.record]: productivity = 21888; 11:51:32.794 [planetinfo.record]: name = "Zalaqura"; 11:51:32.794 [planetinfo.record]: inhabitant = "Human Colonial"; 11:51:32.794 [planetinfo.record]: inhabitants = "Human Colonials"; 11:51:32.794 [planetinfo.record]: description = "The planet Zalaqura is most famous for the Zalaquraian spotted wolf."; 11:51:32.824 [planetinfo.record]: air_color = 0.857757, 0.775397, 0.983391, 1; 11:51:32.824 [planetinfo.record]: cloud_alpha = 1.000000; 11:51:32.824 [planetinfo.record]: cloud_color = 0.953125, 0.919617, 0.946319, 1; 11:51:32.824 [planetinfo.record]: cloud_fraction = 0.220000; 11:51:32.824 [planetinfo.record]: land_color = 0.519531, 0.501695, 0.062912, 1; 11:51:32.824 [planetinfo.record]: land_fraction = 0.280000; 11:51:32.824 [planetinfo.record]: polar_cloud_color = 0.928906, 0.908496, 0.92476, 1; 11:51:32.824 [planetinfo.record]: polar_land_color = 0.948047, 0.93991, 0.739736, 1; 11:51:32.824 [planetinfo.record]: polar_sea_color = 0.92832, 0.902767, 0.853982, 1; 11:51:32.824 [planetinfo.record]: sea_color = 0.716797, 0.637872, 0.487198, 1; 11:51:32.824 [planetinfo.record]: rotation_speed = 0.003307 11:51:32.824 [planetinfo.record]: planet zpos = 362880.000000 11:51:32.824 [planetinfo.record]: sun_radius = 81050.361328 11:51:32.824 [planetinfo.record]: sun_vector = 0.787 0.465 0.406 11:51:32.824 [planetinfo.record]: sun_distance = 967680 11:51:32.824 [planetinfo.record]: corona_flare = 0.090469 11:51:32.825 [planetinfo.record]: corona_hues = 0.842690 11:51:32.825 [planetinfo.record]: sun_color = 0.792215, 0.72815, 0.719831, 1 11:51:32.825 [planetinfo.record]: corona_shimmer = 1.144774 11:51:32.825 [planetinfo.record]: station_vector = -0.474 0.879 0.059 11:51:32.825 [planetinfo.record]: station = coriolis 11:51:37.456 [PLANETINFO OVER]: Done 11:51:37.457 [PLANETINFO LOGGING]: 1 13 11:51:38.460 [planetinfo.record]: seed = 224 116 90 69 11:51:38.460 [planetinfo.record]: coordinates = 116 154 11:51:38.460 [planetinfo.record]: government = 4; 11:51:38.460 [planetinfo.record]: economy = 2; 11:51:38.460 [planetinfo.record]: techlevel = 7; 11:51:38.460 [planetinfo.record]: population = 35; 11:51:38.460 [planetinfo.record]: productivity = 17920; 11:51:38.460 [planetinfo.record]: name = "Cerare"; 11:51:38.460 [planetinfo.record]: inhabitant = "Human Colonial"; 11:51:38.460 [planetinfo.record]: inhabitants = "Human Colonials"; 11:51:38.460 [planetinfo.record]: description = "This planet is fabled for its ancient Cerareian Lonuce tulip plantations."; 11:51:38.462 [planetinfo.record]: air_color = 0.80198, 0.75066, 0.979488, 1; 11:51:38.463 [planetinfo.record]: cloud_alpha = 1.000000; 11:51:38.463 [planetinfo.record]: cloud_color = 0.911528, 0.845795, 0.941406, 1; 11:51:38.463 [planetinfo.record]: cloud_fraction = 0.320000; 11:51:38.463 [planetinfo.record]: land_color = 0.66, 0.393245, 0.216563, 1; 11:51:38.463 [planetinfo.record]: land_fraction = 0.660000; 11:51:38.463 [planetinfo.record]: polar_cloud_color = 0.905311, 0.865004, 0.923633, 1; 11:51:38.463 [planetinfo.record]: polar_land_color = 0.934, 0.839625, 0.777117, 1; 11:51:38.463 [planetinfo.record]: polar_sea_color = 0.90243, 0.880985, 0.949609, 1; 11:51:38.463 [planetinfo.record]: sea_color = 0.403765, 0.358246, 0.503906, 1; 11:51:38.463 [planetinfo.record]: rotation_speed = 0.004345 11:51:38.463 [planetinfo.record]: planet zpos = 421200.000000 11:51:38.463 [planetinfo.record]: sun_radius = 110701.252441 11:51:38.463 [planetinfo.record]: sun_vector = -0.847 -0.343 0.407 11:51:38.463 [planetinfo.record]: sun_distance = 800280 11:51:38.464 [planetinfo.record]: corona_flare = 0.048401 11:51:38.464 [planetinfo.record]: corona_hues = 0.965912 11:51:38.464 [planetinfo.record]: sun_color = 0.744565, 0.545744, 0.291476, 1 11:51:38.464 [planetinfo.record]: corona_shimmer = 0.558933 11:51:38.464 [planetinfo.record]: station_vector = 0.924 0.383 0.006 11:51:38.464 [planetinfo.record]: station = coriolis 11:51:42.423 [PLANETINFO OVER]: Done 11:51:42.424 [PLANETINFO LOGGING]: 1 14 11:51:43.428 [planetinfo.record]: seed = 8 169 182 12 11:51:43.428 [planetinfo.record]: coordinates = 169 14 11:51:43.428 [planetinfo.record]: government = 1; 11:51:43.428 [planetinfo.record]: economy = 6; 11:51:43.428 [planetinfo.record]: techlevel = 3; 11:51:43.428 [planetinfo.record]: population = 20; 11:51:43.428 [planetinfo.record]: productivity = 3200; 11:51:43.428 [planetinfo.record]: name = "Inzaquma"; 11:51:43.428 [planetinfo.record]: inhabitant = "Green Insect"; 11:51:43.428 [planetinfo.record]: inhabitants = "Green Insects"; 11:51:43.428 [planetinfo.record]: description = "This planet is very well known for its inhabitants’ unusual mating traditions and its exotic night life."; 11:51:43.433 [planetinfo.record]: air_color = 0.661527, 0.865991, 0.917701, 1; 11:51:43.434 [planetinfo.record]: cloud_alpha = 1.000000; 11:51:43.434 [planetinfo.record]: cloud_color = 0.6447, 0.755859, 0.575752, 1; 11:51:43.434 [planetinfo.record]: cloud_fraction = 0.270000; 11:51:43.434 [planetinfo.record]: land_color = 0.580078, 0.66, 0.616917, 1; 11:51:43.434 [planetinfo.record]: land_fraction = 0.610000; 11:51:43.434 [planetinfo.record]: polar_cloud_color = 0.762915, 0.840137, 0.715019, 1; 11:51:43.434 [planetinfo.record]: polar_land_color = 0.905725, 0.934, 0.918758, 1; 11:51:43.434 [planetinfo.record]: polar_sea_color = 0.881829, 0.925781, 0.865931, 1; 11:51:43.434 [planetinfo.record]: sea_color = 0.601243, 0.742188, 0.550262, 1; 11:51:43.434 [planetinfo.record]: rotation_speed = 0.001672 11:51:43.435 [planetinfo.record]: planet zpos = 847980.000000 11:51:43.435 [planetinfo.record]: sun_radius = 119893.220673 11:51:43.435 [planetinfo.record]: sun_vector = 0.257 0.490 -0.833 11:51:43.435 [planetinfo.record]: sun_distance = 1271970 11:51:43.435 [planetinfo.record]: corona_flare = 0.040271 11:51:43.435 [planetinfo.record]: corona_hues = 0.825310 11:51:43.435 [planetinfo.record]: sun_color = 0.398155, 0.748759, 0.77735, 1 11:51:43.435 [planetinfo.record]: corona_shimmer = 0.541128 11:51:43.436 [planetinfo.record]: station_vector = 0.859 -0.484 0.166 11:51:43.436 [planetinfo.record]: station = coriolis 11:51:48.374 [PLANETINFO OVER]: Done 11:51:48.374 [PLANETINFO LOGGING]: 1 15 11:51:49.382 [planetinfo.record]: seed = 216 75 250 138 11:51:49.382 [planetinfo.record]: coordinates = 75 122 11:51:49.382 [planetinfo.record]: government = 3; 11:51:49.382 [planetinfo.record]: economy = 2; 11:51:49.382 [planetinfo.record]: techlevel = 10; 11:51:49.382 [planetinfo.record]: population = 46; 11:51:49.382 [planetinfo.record]: productivity = 20608; 11:51:49.382 [planetinfo.record]: name = "Aratusza"; 11:51:49.382 [planetinfo.record]: inhabitant = "Small Black Bug-Eyed Lobster"; 11:51:49.382 [planetinfo.record]: inhabitants = "Small Black Bug-Eyed Lobsters"; 11:51:49.382 [planetinfo.record]: description = "This world is fabled for its ancient mountains."; 11:51:49.400 [planetinfo.record]: air_color = 0.672893, 0.713958, 0.996398, 1; 11:51:49.400 [planetinfo.record]: cloud_alpha = 1.000000; 11:51:49.400 [planetinfo.record]: cloud_color = 0.620117, 0.756737, 0.992188, 1; 11:51:49.400 [planetinfo.record]: cloud_fraction = 0.550000; 11:51:49.400 [planetinfo.record]: land_color = 0.639375, 0.66, 0.649365, 1; 11:51:49.400 [planetinfo.record]: land_fraction = 0.460000; 11:51:49.400 [planetinfo.record]: polar_cloud_color = 0.724652, 0.806106, 0.946484, 1; 11:51:49.400 [planetinfo.record]: polar_land_color = 0.926703, 0.934, 0.930238, 1; 11:51:49.400 [planetinfo.record]: polar_sea_color = 0.714516, 0.929688, 0.899429, 1; 11:51:49.400 [planetinfo.record]: sea_color = 0.0521851, 0.703125, 0.611587, 1; 11:51:49.400 [planetinfo.record]: rotation_speed = 0.002809 11:51:49.400 [planetinfo.record]: planet zpos = 817650.000000 11:51:49.400 [planetinfo.record]: sun_radius = 157172.052612 11:51:49.400 [planetinfo.record]: sun_vector = -0.964 0.249 0.093 11:51:49.400 [planetinfo.record]: sun_distance = 1090200 11:51:49.400 [planetinfo.record]: corona_flare = 0.047823 11:51:49.400 [planetinfo.record]: corona_hues = 0.543724 11:51:49.400 [planetinfo.record]: sun_color = 0.282023, 0.411423, 0.715607, 1 11:51:49.401 [planetinfo.record]: corona_shimmer = 0.392629 11:51:49.401 [planetinfo.record]: station_vector = 0.573 0.485 0.661 11:51:49.401 [planetinfo.record]: station = coriolis 11:51:53.932 [PLANETINFO OVER]: Done 11:51:53.932 [PLANETINFO LOGGING]: 1 16 11:51:54.936 [planetinfo.record]: seed = 80 4 166 125 11:51:54.936 [planetinfo.record]: coordinates = 4 40 11:51:54.936 [planetinfo.record]: government = 2; 11:51:54.936 [planetinfo.record]: economy = 0; 11:51:54.936 [planetinfo.record]: techlevel = 8; 11:51:54.936 [planetinfo.record]: population = 35; 11:51:54.936 [planetinfo.record]: productivity = 16800; 11:51:54.936 [planetinfo.record]: name = "Isarin"; 11:51:54.937 [planetinfo.record]: inhabitant = "Blue Fat Humanoid"; 11:51:54.937 [planetinfo.record]: inhabitants = "Blue Fat Humanoids"; 11:51:54.937 [planetinfo.record]: description = "The world Isarin is beset by lethal disease."; 11:51:54.955 [planetinfo.record]: air_color = 0.871355, 0.771462, 0.993146, 1; 11:51:54.956 [planetinfo.record]: cloud_alpha = 1.000000; 11:51:54.956 [planetinfo.record]: cloud_color = 0.982422, 0.913345, 0.957058, 1; 11:51:54.956 [planetinfo.record]: cloud_fraction = 0.430000; 11:51:54.956 [planetinfo.record]: land_color = 0.521265, 0.0257813, 0.66, 1; 11:51:54.956 [planetinfo.record]: land_fraction = 0.580000; 11:51:54.956 [planetinfo.record]: polar_cloud_color = 0.94209, 0.900689, 0.926888, 1; 11:51:54.956 [planetinfo.record]: polar_land_color = 0.884917, 0.709621, 0.934, 1; 11:51:54.956 [planetinfo.record]: polar_sea_color = 0.943555, 0.909916, 0.891678, 1; 11:51:54.956 [planetinfo.record]: sea_color = 0.564453, 0.483959, 0.440318, 1; 11:51:54.956 [planetinfo.record]: rotation_speed = 0.002636 11:51:54.956 [planetinfo.record]: planet zpos = 676280.000000 11:51:54.956 [planetinfo.record]: sun_radius = 151775.935669 11:51:54.956 [planetinfo.record]: sun_vector = 0.110 -0.385 0.916 11:51:54.956 [planetinfo.record]: sun_distance = 1168120 11:51:54.956 [planetinfo.record]: corona_flare = 0.017487 11:51:54.956 [planetinfo.record]: corona_hues = 0.785652 11:51:54.956 [planetinfo.record]: sun_color = 0.816269, 0.724945, 0.21197, 1 11:51:54.957 [planetinfo.record]: corona_shimmer = 0.550399 11:51:54.957 [planetinfo.record]: station_vector = 0.724 0.602 0.337 11:51:54.957 [planetinfo.record]: station = coriolis 11:51:59.641 [PLANETINFO OVER]: Done 11:51:59.642 [PLANETINFO LOGGING]: 1 17 11:52:00.651 [planetinfo.record]: seed = 176 83 186 41 11:52:00.651 [planetinfo.record]: coordinates = 83 44 11:52:00.651 [planetinfo.record]: government = 6; 11:52:00.651 [planetinfo.record]: economy = 4; 11:52:00.651 [planetinfo.record]: techlevel = 9; 11:52:00.651 [planetinfo.record]: population = 47; 11:52:00.651 [planetinfo.record]: productivity = 22560; 11:52:00.651 [planetinfo.record]: name = "Esesbi"; 11:52:00.651 [planetinfo.record]: inhabitant = "Small Red Rodent"; 11:52:00.651 [planetinfo.record]: inhabitants = "Small Red Rodents"; 11:52:00.651 [planetinfo.record]: description = "The world Esesbi is a boring planet."; 11:52:00.670 [planetinfo.record]: air_color = 0.809482, 0.752654, 0.98209, 1; 11:52:00.670 [planetinfo.record]: cloud_alpha = 1.000000; 11:52:00.670 [planetinfo.record]: cloud_color = 0.925871, 0.852814, 0.949219, 1; 11:52:00.670 [planetinfo.record]: cloud_fraction = 0.340000; 11:52:00.670 [planetinfo.record]: land_color = 0.477724, 0.320251, 0.621094, 1; 11:52:00.670 [planetinfo.record]: land_fraction = 0.530000; 11:52:00.670 [planetinfo.record]: polar_cloud_color = 0.912895, 0.868296, 0.927148, 1; 11:52:00.670 [planetinfo.record]: polar_land_color = 0.883766, 0.824318, 0.937891, 1; 11:52:00.670 [planetinfo.record]: polar_sea_color = 0.923437, 0.871232, 0.820633, 1; 11:52:00.670 [planetinfo.record]: sea_color = 0.765625, 0.59249, 0.424683, 1; 11:52:00.670 [planetinfo.record]: rotation_speed = 0.003703 11:52:00.671 [planetinfo.record]: planet zpos = 676390.000000 11:52:00.671 [planetinfo.record]: sun_radius = 122571.711273 11:52:00.671 [planetinfo.record]: sun_vector = 0.845 -0.257 0.469 11:52:00.671 [planetinfo.record]: sun_distance = 988570 11:52:00.671 [planetinfo.record]: corona_flare = 0.049101 11:52:00.671 [planetinfo.record]: corona_hues = 0.825378 11:52:00.671 [planetinfo.record]: sun_color = 0.672232, 0.53562, 0.34373, 1 11:52:00.671 [planetinfo.record]: corona_shimmer = 0.485524 11:52:00.671 [planetinfo.record]: station_vector = -0.405 -0.824 0.396 11:52:00.671 [planetinfo.record]: station = coriolis 11:52:05.387 [PLANETINFO OVER]: Done 11:52:05.388 [PLANETINFO LOGGING]: 1 18 11:52:06.392 [planetinfo.record]: seed = 248 249 54 202 11:52:06.392 [planetinfo.record]: coordinates = 249 38 11:52:06.392 [planetinfo.record]: government = 7; 11:52:06.392 [planetinfo.record]: economy = 6; 11:52:06.392 [planetinfo.record]: techlevel = 6; 11:52:06.392 [planetinfo.record]: population = 38; 11:52:06.392 [planetinfo.record]: productivity = 13376; 11:52:06.392 [planetinfo.record]: name = "Armaaza"; 11:52:06.392 [planetinfo.record]: inhabitant = "Human Colonial"; 11:52:06.392 [planetinfo.record]: inhabitants = "Human Colonials"; 11:52:06.392 [planetinfo.record]: description = "This planet is most notable for Armaazaian lethal water but plagued by occasional solar activity."; 11:52:06.404 [planetinfo.record]: air_color = 0.584719, 0.547111, 0.874125, 1; 11:52:06.404 [planetinfo.record]: cloud_alpha = 1.000000; 11:52:06.404 [planetinfo.record]: cloud_color = 0.415421, 0.297852, 0.625, 1; 11:52:06.404 [planetinfo.record]: cloud_fraction = 0.410000; 11:52:06.404 [planetinfo.record]: land_color = 0.608115, 0.66, 0.600703, 1; 11:52:06.404 [planetinfo.record]: land_fraction = 0.280000; 11:52:06.404 [planetinfo.record]: polar_cloud_color = 0.617516, 0.525665, 0.78125, 1; 11:52:06.409 [planetinfo.record]: polar_land_color = 0.915644, 0.934, 0.913022, 1; 11:52:06.409 [planetinfo.record]: polar_sea_color = 0.720114, 0.929883, 0.924966, 1; 11:52:06.409 [planetinfo.record]: sea_color = 0.0684738, 0.701172, 0.686343, 1; 11:52:06.409 [planetinfo.record]: rotation_speed = 0.001145 11:52:06.409 [planetinfo.record]: planet zpos = 506250.000000 11:52:06.410 [planetinfo.record]: sun_radius = 132411.861420 11:52:06.410 [planetinfo.record]: sun_vector = -0.494 0.350 0.796 11:52:06.410 [planetinfo.record]: sun_distance = 1068750 11:52:06.410 [planetinfo.record]: corona_flare = 0.077318 11:52:06.410 [planetinfo.record]: corona_hues = 0.611954 11:52:06.410 [planetinfo.record]: sun_color = 0.771475, 0.43144, 0.23779, 1 11:52:06.410 [planetinfo.record]: corona_shimmer = 0.615627 11:52:06.410 [planetinfo.record]: station_vector = 0.515 -0.396 0.760 11:52:06.410 [planetinfo.record]: station = coriolis 11:52:11.161 [PLANETINFO OVER]: Done 11:52:11.162 [PLANETINFO LOGGING]: 1 19 11:52:12.165 [planetinfo.record]: seed = 104 100 154 254 11:52:12.165 [planetinfo.record]: coordinates = 100 175 11:52:12.165 [planetinfo.record]: government = 5; 11:52:12.165 [planetinfo.record]: economy = 7; 11:52:12.165 [planetinfo.record]: techlevel = 3; 11:52:12.165 [planetinfo.record]: population = 25; 11:52:12.165 [planetinfo.record]: productivity = 5400; 11:52:12.165 [planetinfo.record]: name = "Rienla"; 11:52:12.165 [planetinfo.record]: inhabitant = "Bony Humanoid"; 11:52:12.165 [planetinfo.record]: inhabitants = "Bony Humanoids"; 11:52:12.165 [planetinfo.record]: description = "The planet Rienla is very noted for its ancient mountains but plagued by lethal disease."; 11:52:12.170 [planetinfo.record]: air_color = 0.419437, 0.829455, 0.852662, 1; 11:52:12.171 [planetinfo.record]: cloud_alpha = 1.000000; 11:52:12.171 [planetinfo.record]: cloud_color = 0.471353, 0.560547, 0.0416031, 1; 11:52:12.171 [planetinfo.record]: cloud_fraction = 0.290000; 11:52:12.171 [planetinfo.record]: land_color = 0.65625, 0.366577, 0.59741, 1; 11:52:12.171 [planetinfo.record]: land_fraction = 0.270000; 11:52:12.171 [planetinfo.record]: polar_cloud_color = 0.677436, 0.752246, 0.316987, 1; 11:52:12.171 [planetinfo.record]: polar_land_color = 0.934375, 0.831265, 0.913431, 1; 11:52:12.171 [planetinfo.record]: polar_sea_color = 0.88267, 0.930273, 0.894571, 1; 11:52:12.171 [planetinfo.record]: sea_color = 0.554544, 0.697266, 0.590224, 1; 11:52:12.171 [planetinfo.record]: rotation_speed = 0.002241 11:52:12.171 [planetinfo.record]: planet zpos = 780000.000000 11:52:12.171 [planetinfo.record]: sun_radius = 176831.817627 11:52:12.171 [planetinfo.record]: sun_vector = -0.463 0.415 0.783 11:52:12.171 [planetinfo.record]: sun_distance = 1430000 11:52:12.172 [planetinfo.record]: corona_flare = 0.008670 11:52:12.172 [planetinfo.record]: corona_hues = 0.647873 11:52:12.172 [planetinfo.record]: sun_color = 0.785236, 0.779505, 0.766041, 1 11:52:12.172 [planetinfo.record]: corona_shimmer = 0.327248 11:52:12.172 [planetinfo.record]: station_vector = -0.955 0.297 0.019 11:52:12.172 [planetinfo.record]: station = coriolis 11:52:17.216 [PLANETINFO OVER]: Done 11:52:17.217 [PLANETINFO LOGGING]: 1 20 11:52:18.234 [planetinfo.record]: seed = 0 134 102 13 11:52:18.234 [planetinfo.record]: coordinates = 134 117 11:52:18.234 [planetinfo.record]: government = 0; 11:52:18.234 [planetinfo.record]: economy = 7; 11:52:18.234 [planetinfo.record]: techlevel = 2; 11:52:18.234 [planetinfo.record]: population = 16; 11:52:18.234 [planetinfo.record]: productivity = 1536; 11:52:18.234 [planetinfo.record]: name = "Diuste"; 11:52:18.234 [planetinfo.record]: inhabitant = "Human Colonial"; 11:52:18.234 [planetinfo.record]: inhabitants = "Human Colonials"; 11:52:18.234 [planetinfo.record]: description = "The world Diuste is reasonably famous for its inhabitants’ exceptional loathing of sit coms."; 11:52:18.255 [planetinfo.record]: air_color = 0.740336, 0.627623, 0.850711, 1; 11:52:18.255 [planetinfo.record]: cloud_alpha = 1.000000; 11:52:18.255 [planetinfo.record]: cloud_color = 0.554688, 0.450684, 0.499435, 1; 11:52:18.255 [planetinfo.record]: cloud_fraction = 0.340000; 11:52:18.255 [planetinfo.record]: land_color = 0.66, 0.582173, 0.226875, 1; 11:52:18.255 [planetinfo.record]: land_fraction = 0.270000; 11:52:18.255 [planetinfo.record]: polar_cloud_color = 0.749609, 0.661765, 0.702942, 1; 11:52:18.256 [planetinfo.record]: polar_land_color = 0.934, 0.906466, 0.780766, 1; 11:52:18.256 [planetinfo.record]: polar_sea_color = 0.89141, 0.931641, 0.880146, 1; 11:52:18.256 [planetinfo.record]: sea_color = 0.565517, 0.683594, 0.532455, 1; 11:52:18.256 [planetinfo.record]: rotation_speed = 0.001979 11:52:18.256 [planetinfo.record]: planet zpos = 690580.000000 11:52:18.256 [planetinfo.record]: sun_radius = 143267.646179 11:52:18.256 [planetinfo.record]: sun_vector = 0.295 0.732 -0.614 11:52:18.257 [planetinfo.record]: sun_distance = 1255600 11:52:18.257 [planetinfo.record]: corona_flare = 0.009290 11:52:18.257 [planetinfo.record]: corona_hues = 0.774475 11:52:18.257 [planetinfo.record]: sun_color = 0.819571, 0.602678, 0.435463, 1 11:52:18.257 [planetinfo.record]: corona_shimmer = 0.433078 11:52:18.258 [planetinfo.record]: station_vector = -0.531 0.047 0.846 11:52:18.258 [planetinfo.record]: station = coriolis 11:52:23.130 [PLANETINFO OVER]: Done 11:52:23.131 [PLANETINFO LOGGING]: 1 21 11:52:24.145 [planetinfo.record]: seed = 0 178 154 86 11:52:24.145 [planetinfo.record]: coordinates = 178 156 11:52:24.145 [planetinfo.record]: government = 0; 11:52:24.145 [planetinfo.record]: economy = 6; 11:52:24.145 [planetinfo.record]: techlevel = 3; 11:52:24.145 [planetinfo.record]: population = 19; 11:52:24.145 [planetinfo.record]: productivity = 2432; 11:52:24.145 [planetinfo.record]: name = "Vezadi"; 11:52:24.145 [planetinfo.record]: inhabitant = "Yellow Rodent"; 11:52:24.145 [planetinfo.record]: inhabitants = "Yellow Rodents"; 11:52:24.145 [planetinfo.record]: description = "Vezadi is reasonably well known for the Vezadiian tree ant but cursed by killer edible Aroids."; 11:52:24.147 [planetinfo.record]: air_color = 0.57766, 0.924855, 0.826935, 1; 11:52:24.148 [planetinfo.record]: cloud_alpha = 1.000000; 11:52:24.148 [planetinfo.record]: cloud_color = 0.777344, 0.43467, 0.367416, 1; 11:52:24.148 [planetinfo.record]: cloud_fraction = 0.370000; 11:52:24.148 [planetinfo.record]: land_color = 0.642376, 0.66, 0.337734, 1; 11:52:24.148 [planetinfo.record]: land_fraction = 0.320000; 11:52:24.148 [planetinfo.record]: polar_cloud_color = 0.849805, 0.615669, 0.569718, 1; 11:52:24.148 [planetinfo.record]: polar_land_color = 0.927765, 0.934, 0.819986, 1; 11:52:24.148 [planetinfo.record]: polar_sea_color = 0.846094, 0.91347, 0.95, 1; 11:52:24.148 [planetinfo.record]: sea_color = 0.28125, 0.423096, 0.5, 1; 11:52:24.148 [planetinfo.record]: rotation_speed = 0.003113 11:52:24.148 [planetinfo.record]: planet zpos = 453000.000000 11:52:24.148 [planetinfo.record]: sun_radius = 156329.791260 11:52:24.148 [planetinfo.record]: sun_vector = -0.672 0.698 -0.247 11:52:24.148 [planetinfo.record]: sun_distance = 860700 11:52:24.148 [planetinfo.record]: corona_flare = 0.081723 11:52:24.148 [planetinfo.record]: corona_hues = 0.504715 11:52:24.148 [planetinfo.record]: sun_color = 0.50582, 0.765478, 0.803983, 1 11:52:24.149 [planetinfo.record]: corona_shimmer = 0.431783 11:52:24.149 [planetinfo.record]: station_vector = 0.430 0.866 0.256 11:52:24.149 [planetinfo.record]: station = coriolis 11:52:28.469 [PLANETINFO OVER]: Done 11:52:28.470 [PLANETINFO LOGGING]: 1 22 11:52:29.490 [planetinfo.record]: seed = 104 168 54 250 11:52:29.490 [planetinfo.record]: coordinates = 168 173 11:52:29.490 [planetinfo.record]: government = 5; 11:52:29.490 [planetinfo.record]: economy = 5; 11:52:29.490 [planetinfo.record]: techlevel = 5; 11:52:29.490 [planetinfo.record]: population = 31; 11:52:29.490 [planetinfo.record]: productivity = 11160; 11:52:29.490 [planetinfo.record]: name = "Quaen"; 11:52:29.490 [planetinfo.record]: inhabitant = "Human Colonial"; 11:52:29.490 [planetinfo.record]: inhabitants = "Human Colonials"; 11:52:29.490 [planetinfo.record]: description = "This planet is notable for its unusual oceans and its inhabitants’ eccentric love for tourists."; 11:52:29.494 [planetinfo.record]: air_color = 0.633027, 0.823663, 0.971033, 1; 11:52:29.495 [planetinfo.record]: cloud_alpha = 1.000000; 11:52:29.495 [planetinfo.record]: cloud_color = 0.508102, 0.916016, 0.638762, 1; 11:52:29.495 [planetinfo.record]: cloud_fraction = 0.090000; 11:52:29.495 [planetinfo.record]: land_color = 0.425391, 0.66, 0.632507, 1; 11:52:29.495 [planetinfo.record]: land_fraction = 0.430000; 11:52:29.495 [planetinfo.record]: polar_cloud_color = 0.658321, 0.912207, 0.739644, 1; 11:52:29.495 [planetinfo.record]: polar_land_color = 0.850998, 0.934, 0.924273, 1; 11:52:29.495 [planetinfo.record]: polar_sea_color = 0.929986, 0.938477, 0.889078, 1; 11:52:29.495 [planetinfo.record]: sea_color = 0.59297, 0.615234, 0.485699, 1; 11:52:29.495 [planetinfo.record]: rotation_speed = 0.000530 11:52:29.495 [planetinfo.record]: planet zpos = 776160.000000 11:52:29.495 [planetinfo.record]: sun_radius = 134828.767090 11:52:29.495 [planetinfo.record]: sun_vector = -0.010 0.999 0.047 11:52:29.495 [planetinfo.record]: sun_distance = 1330560 11:52:29.495 [planetinfo.record]: corona_flare = 0.034209 11:52:29.495 [planetinfo.record]: corona_hues = 0.958382 11:52:29.496 [planetinfo.record]: sun_color = 0.621879, 0.655152, 0.694144, 1 11:52:29.496 [planetinfo.record]: corona_shimmer = 0.405244 11:52:29.496 [planetinfo.record]: station_vector = -0.448 0.530 0.720 11:52:29.496 [planetinfo.record]: station = coriolis 11:52:34.244 [PLANETINFO OVER]: Done 11:52:34.245 [PLANETINFO LOGGING]: 1 23 11:52:35.248 [planetinfo.record]: seed = 120 60 186 126 11:52:35.248 [planetinfo.record]: coordinates = 60 242 11:52:35.248 [planetinfo.record]: government = 7; 11:52:35.248 [planetinfo.record]: economy = 2; 11:52:35.248 [planetinfo.record]: techlevel = 9; 11:52:35.248 [planetinfo.record]: population = 46; 11:52:35.248 [planetinfo.record]: productivity = 32384; 11:52:35.248 [planetinfo.record]: name = "Ridiusla"; 11:52:35.248 [planetinfo.record]: inhabitant = "Blue Rodent"; 11:52:35.248 [planetinfo.record]: inhabitants = "Blue Rodents"; 11:52:35.248 [planetinfo.record]: description = "This planet is a tedious little planet."; 11:52:35.263 [planetinfo.record]: air_color = 0.442911, 0.773171, 0.84876, 1; 11:52:35.264 [planetinfo.record]: cloud_alpha = 1.000000; 11:52:35.264 [planetinfo.record]: cloud_color = 0.287177, 0.548828, 0.0900421, 1; 11:52:35.264 [planetinfo.record]: cloud_fraction = 0.590000; 11:52:35.264 [planetinfo.record]: land_color = 0.0852981, 0.640625, 0.022522, 1; 11:52:35.264 [planetinfo.record]: land_fraction = 0.670000; 11:52:35.264 [planetinfo.record]: polar_cloud_color = 0.5244, 0.746973, 0.356709, 1; 11:52:35.264 [planetinfo.record]: polar_land_color = 0.733108, 0.935938, 0.710179, 1; 11:52:35.264 [planetinfo.record]: polar_sea_color = 0.887201, 0.930469, 0.878221, 1; 11:52:35.264 [planetinfo.record]: sea_color = 0.565981, 0.695312, 0.539139, 1; 11:52:35.264 [planetinfo.record]: rotation_speed = 0.001551 11:52:35.264 [planetinfo.record]: planet zpos = 710600.000000 11:52:35.264 [planetinfo.record]: sun_radius = 147070.077515 11:52:35.264 [planetinfo.record]: sun_vector = -0.765 -0.045 0.642 11:52:35.264 [planetinfo.record]: sun_distance = 1292000 11:52:35.264 [planetinfo.record]: corona_flare = 0.036812 11:52:35.264 [planetinfo.record]: corona_hues = 0.779922 11:52:35.264 [planetinfo.record]: sun_color = 0.676749, 0.656837, 0.563574, 1 11:52:35.264 [planetinfo.record]: corona_shimmer = 0.378567 11:52:35.264 [planetinfo.record]: station_vector = 0.674 0.689 0.266 11:52:35.265 [planetinfo.record]: station = coriolis 11:52:39.628 [PLANETINFO OVER]: Done 11:52:39.629 [PLANETINFO LOGGING]: 1 24 11:52:40.633 [planetinfo.record]: seed = 48 149 166 171 11:52:40.633 [planetinfo.record]: coordinates = 149 104 11:52:40.633 [planetinfo.record]: government = 6; 11:52:40.633 [planetinfo.record]: economy = 0; 11:52:40.633 [planetinfo.record]: techlevel = 11; 11:52:40.633 [planetinfo.record]: population = 51; 11:52:40.633 [planetinfo.record]: productivity = 40800; 11:52:40.633 [planetinfo.record]: name = "Maesaron"; 11:52:40.633 [planetinfo.record]: inhabitant = "Small Harmless Furry Rodent"; 11:52:40.633 [planetinfo.record]: inhabitants = "Small Harmless Furry Rodents"; 11:52:40.633 [planetinfo.record]: description = "The planet Maesaron is an unremarkable dump."; 11:52:40.650 [planetinfo.record]: air_color = 0.418077, 0.834451, 0.788513, 1; 11:52:40.651 [planetinfo.record]: cloud_alpha = 1.000000; 11:52:40.651 [planetinfo.record]: cloud_color = 0.505859, 0.360283, 0.0513763, 1; 11:52:40.651 [planetinfo.record]: cloud_fraction = 0.440000; 11:52:40.651 [planetinfo.record]: land_color = 0.159119, 0.515625, 0.164689, 1; 11:52:40.651 [planetinfo.record]: land_fraction = 0.410000; 11:52:40.651 [planetinfo.record]: polar_cloud_color = 0.727637, 0.596762, 0.319052, 1; 11:52:40.651 [planetinfo.record]: polar_land_color = 0.784499, 0.948438, 0.78706, 1; 11:52:40.651 [planetinfo.record]: polar_sea_color = 0.943945, 0.882091, 0.913985, 1; 11:52:40.651 [planetinfo.record]: sea_color = 0.560547, 0.413622, 0.48938, 1; 11:52:40.651 [planetinfo.record]: rotation_speed = 0.001429 11:52:40.651 [planetinfo.record]: planet zpos = 578100.000000 11:52:40.652 [planetinfo.record]: sun_radius = 142751.075592 11:52:40.652 [planetinfo.record]: sun_vector = 0.052 0.956 0.289 11:52:40.652 [planetinfo.record]: sun_distance = 1329630 11:52:40.652 [planetinfo.record]: corona_flare = 0.058459 11:52:40.652 [planetinfo.record]: corona_hues = 0.515854 11:52:40.652 [planetinfo.record]: sun_color = 0.536432, 0.560718, 0.78157, 1 11:52:40.652 [planetinfo.record]: corona_shimmer = 0.313599 11:52:40.652 [planetinfo.record]: station_vector = -0.817 0.064 0.573 11:52:40.652 [planetinfo.record]: station = dodecahedron 11:52:45.348 [PLANETINFO OVER]: Done 11:52:45.348 [PLANETINFO LOGGING]: 1 25 11:52:46.351 [planetinfo.record]: seed = 208 63 250 211 11:52:46.351 [planetinfo.record]: coordinates = 63 234 11:52:46.351 [planetinfo.record]: government = 2; 11:52:46.351 [planetinfo.record]: economy = 2; 11:52:46.351 [planetinfo.record]: techlevel = 9; 11:52:46.351 [planetinfo.record]: population = 41; 11:52:46.351 [planetinfo.record]: productivity = 15744; 11:52:46.351 [planetinfo.record]: name = "Berien"; 11:52:46.351 [planetinfo.record]: inhabitant = "Furry Rodent"; 11:52:46.351 [planetinfo.record]: inhabitants = "Furry Rodents"; 11:52:46.351 [planetinfo.record]: description = "Berien is mildly famous for its pink oceans and its unusual casinos."; 11:52:46.357 [planetinfo.record]: air_color = 0.623781, 0.788571, 0.992496, 1; 11:52:46.357 [planetinfo.record]: cloud_alpha = 1.000000; 11:52:46.357 [planetinfo.record]: cloud_color = 0.474915, 0.980469, 0.814584, 1; 11:52:46.357 [planetinfo.record]: cloud_fraction = 0.480000; 11:52:46.357 [planetinfo.record]: land_color = 0.570471, 0.569766, 0.66, 1; 11:52:46.357 [planetinfo.record]: land_fraction = 0.430000; 11:52:46.357 [planetinfo.record]: polar_cloud_color = 0.637891, 0.941211, 0.841684, 1; 11:52:46.357 [planetinfo.record]: polar_land_color = 0.902326, 0.902076, 0.934, 1; 11:52:46.357 [planetinfo.record]: polar_sea_color = 0.91, 0.8645, 0.8918, 1; 11:52:46.357 [planetinfo.record]: sea_color = 0.9, 0.72, 0.828, 1; 11:52:46.357 [planetinfo.record]: rotation_speed = 0.002528 11:52:46.357 [planetinfo.record]: planet zpos = 401170.000000 11:52:46.357 [planetinfo.record]: sun_radius = 77266.694489 11:52:46.357 [planetinfo.record]: sun_vector = 0.266 -0.037 0.963 11:52:46.357 [planetinfo.record]: sun_distance = 729400 11:52:46.357 [planetinfo.record]: corona_flare = 0.065834 11:52:46.357 [planetinfo.record]: corona_hues = 0.928894 11:52:46.358 [planetinfo.record]: sun_color = 0.77319, 0.716965, 0.679989, 1 11:52:46.358 [planetinfo.record]: corona_shimmer = 0.356422 11:52:46.358 [planetinfo.record]: station_vector = 0.727 -0.506 0.464 11:52:46.358 [planetinfo.record]: station = coriolis 11:52:51.257 [PLANETINFO OVER]: Done 11:52:51.258 [PLANETINFO LOGGING]: 1 26 11:52:52.260 [planetinfo.record]: seed = 88 228 182 244 11:52:52.260 [planetinfo.record]: coordinates = 228 18 11:52:52.260 [planetinfo.record]: government = 3; 11:52:52.260 [planetinfo.record]: economy = 2; 11:52:52.260 [planetinfo.record]: techlevel = 7; 11:52:52.261 [planetinfo.record]: population = 34; 11:52:52.261 [planetinfo.record]: productivity = 15232; 11:52:52.261 [planetinfo.record]: name = "Ramaza"; 11:52:52.261 [planetinfo.record]: inhabitant = "Feline"; 11:52:52.261 [planetinfo.record]: inhabitants = "Felines"; 11:52:52.261 [planetinfo.record]: description = "This world is most notable for its exotic night life but ravaged by frequent earthquakes."; 11:52:52.272 [planetinfo.record]: air_color = 0.659607, 0.868141, 0.918352, 1; 11:52:52.272 [planetinfo.record]: cloud_alpha = 1.000000; 11:52:52.272 [planetinfo.record]: cloud_color = 0.647082, 0.757812, 0.57132, 1; 11:52:52.272 [planetinfo.record]: cloud_fraction = 0.330000; 11:52:52.272 [planetinfo.record]: land_color = 0.523158, 0.252656, 0.66, 1; 11:52:52.272 [planetinfo.record]: land_fraction = 0.490000; 11:52:52.272 [planetinfo.record]: polar_cloud_color = 0.764211, 0.841016, 0.71166, 1; 11:52:52.272 [planetinfo.record]: polar_land_color = 0.885587, 0.789887, 0.934, 1; 11:52:52.272 [planetinfo.record]: polar_sea_color = 0.928516, 0.842232, 0.833306, 1; 11:52:52.272 [planetinfo.record]: sea_color = 0.714844, 0.449133, 0.421646, 1; 11:52:52.272 [planetinfo.record]: rotation_speed = 0.004912 11:52:52.273 [planetinfo.record]: planet zpos = 488160.000000 11:52:52.273 [planetinfo.record]: sun_radius = 79738.038940 11:52:52.273 [planetinfo.record]: sun_vector = -0.281 -0.483 0.830 11:52:52.273 [planetinfo.record]: sun_distance = 854280 11:52:52.273 [planetinfo.record]: corona_flare = 0.064233 11:52:52.273 [planetinfo.record]: corona_hues = 0.985832 11:52:52.273 [planetinfo.record]: sun_color = 0.312415, 0.363784, 0.789078, 1 11:52:52.273 [planetinfo.record]: corona_shimmer = 0.467060 11:52:52.273 [planetinfo.record]: station_vector = -0.119 0.006 0.993 11:52:52.273 [planetinfo.record]: station = coriolis 11:52:56.788 [PLANETINFO OVER]: Done 11:52:56.789 [PLANETINFO LOGGING]: 1 27 11:52:57.792 [planetinfo.record]: seed = 8 164 90 83 11:52:57.792 [planetinfo.record]: coordinates = 164 196 11:52:57.792 [planetinfo.record]: government = 1; 11:52:57.792 [planetinfo.record]: economy = 6; 11:52:57.792 [planetinfo.record]: techlevel = 2; 11:52:57.792 [planetinfo.record]: population = 16; 11:52:57.792 [planetinfo.record]: productivity = 2560; 11:52:57.792 [planetinfo.record]: name = "Beanen"; 11:52:57.792 [planetinfo.record]: inhabitant = "Human Colonial"; 11:52:57.792 [planetinfo.record]: inhabitants = "Human Colonials"; 11:52:57.792 [planetinfo.record]: description = "This world is most fabled for Beanenian lethal brandy but scourged by deadly tree ants."; 11:52:57.808 [planetinfo.record]: air_color = 0.778733, 0.554727, 0.928758, 1; 11:52:57.808 [planetinfo.record]: cloud_alpha = 1.000000; 11:52:57.808 [planetinfo.record]: cloud_color = 0.789062, 0.308228, 0.398384, 1; 11:52:57.808 [planetinfo.record]: cloud_fraction = 0.280000; 11:52:57.808 [planetinfo.record]: land_color = 0.556875, 0.626162, 0.66, 1; 11:52:57.808 [planetinfo.record]: land_fraction = 0.310000; 11:52:57.808 [planetinfo.record]: polar_cloud_color = 0.855078, 0.529414, 0.590476, 1; 11:52:57.808 [planetinfo.record]: polar_land_color = 0.897516, 0.922029, 0.934, 1; 11:52:57.808 [planetinfo.record]: polar_sea_color = 0.921875, 0.837109, 0.80394, 1; 11:52:57.808 [planetinfo.record]: sea_color = 0.78125, 0.493908, 0.38147, 1; 11:52:57.808 [planetinfo.record]: rotation_speed = 0.000922 11:52:57.808 [planetinfo.record]: planet zpos = 487240.000000 11:52:57.808 [planetinfo.record]: sun_radius = 81735.864258 11:52:57.808 [planetinfo.record]: sun_vector = -0.688 0.180 0.703 11:52:57.808 [planetinfo.record]: sun_distance = 637160 11:52:57.808 [planetinfo.record]: corona_flare = 0.018018 11:52:57.808 [planetinfo.record]: corona_hues = 0.743896 11:52:57.808 [planetinfo.record]: sun_color = 0.663915, 0.665464, 0.66842, 1 11:52:57.809 [planetinfo.record]: corona_shimmer = 0.484798 11:52:57.809 [planetinfo.record]: station_vector = 0.630 -0.767 0.122 11:52:57.809 [planetinfo.record]: station = coriolis 11:53:03.140 [PLANETINFO OVER]: Done 11:53:03.141 [PLANETINFO LOGGING]: 1 28 11:53:04.145 [planetinfo.record]: seed = 224 193 102 48 11:53:04.145 [planetinfo.record]: coordinates = 193 178 11:53:04.145 [planetinfo.record]: government = 4; 11:53:04.145 [planetinfo.record]: economy = 2; 11:53:04.145 [planetinfo.record]: techlevel = 8; 11:53:04.145 [planetinfo.record]: population = 39; 11:53:04.145 [planetinfo.record]: productivity = 19968; 11:53:04.145 [planetinfo.record]: name = "Ercetidi"; 11:53:04.145 [planetinfo.record]: inhabitant = "Human Colonial"; 11:53:04.145 [planetinfo.record]: inhabitants = "Human Colonials"; 11:53:04.145 [planetinfo.record]: description = "This world is reasonably notable for its weird tropical forests but scourged by dreadful solar activity."; 11:53:04.152 [planetinfo.record]: air_color = 0.4564, 0.769128, 0.837053, 1; 11:53:04.152 [planetinfo.record]: cloud_alpha = 1.000000; 11:53:04.152 [planetinfo.record]: cloud_color = 0.298597, 0.513672, 0.120392, 1; 11:53:04.153 [planetinfo.record]: cloud_fraction = 0.330000; 11:53:04.153 [planetinfo.record]: land_color = 0.348047, 0.66, 0.528395, 1; 11:53:04.153 [planetinfo.record]: land_fraction = 0.260000; 11:53:04.153 [planetinfo.record]: polar_cloud_color = 0.539818, 0.731152, 0.381285, 1; 11:53:04.153 [planetinfo.record]: polar_land_color = 0.823635, 0.934, 0.88744, 1; 11:53:04.153 [planetinfo.record]: polar_sea_color = 0.937891, 0.917241, 0.881654, 1; 11:53:04.153 [planetinfo.record]: sea_color = 0.621094, 0.566395, 0.472128, 1; 11:53:04.153 [planetinfo.record]: rotation_speed = 0.000754 11:53:04.153 [planetinfo.record]: planet zpos = 361080.000000 11:53:04.153 [planetinfo.record]: sun_radius = 70573.483429 11:53:04.153 [planetinfo.record]: sun_vector = -0.872 0.039 0.488 11:53:04.153 [planetinfo.record]: sun_distance = 601800 11:53:04.153 [planetinfo.record]: corona_flare = 0.075322 11:53:04.153 [planetinfo.record]: corona_hues = 0.803322 11:53:04.153 [planetinfo.record]: sun_color = 0.726224, 0.530947, 0.368628, 1 11:53:04.154 [planetinfo.record]: corona_shimmer = 0.356635 11:53:04.154 [planetinfo.record]: station_vector = -0.560 0.034 0.828 11:53:04.154 [planetinfo.record]: station = coriolis 11:53:08.605 [PLANETINFO OVER]: Done 11:53:08.605 [PLANETINFO LOGGING]: 1 29 11:53:09.607 [planetinfo.record]: seed = 32 109 218 169 11:53:09.607 [planetinfo.record]: coordinates = 109 151 11:53:09.607 [planetinfo.record]: government = 4; 11:53:09.607 [planetinfo.record]: economy = 7; 11:53:09.607 [planetinfo.record]: techlevel = 3; 11:53:09.607 [planetinfo.record]: population = 24; 11:53:09.607 [planetinfo.record]: productivity = 4608; 11:53:09.607 [planetinfo.record]: name = "Esrece"; 11:53:09.607 [planetinfo.record]: inhabitant = "Small Harmless Horned Lobster"; 11:53:09.607 [planetinfo.record]: inhabitants = "Small Harmless Horned Lobsters"; 11:53:09.607 [planetinfo.record]: description = "Esrece is cursed by dreadful civil war."; 11:53:09.614 [planetinfo.record]: air_color = 0.451443, 0.582887, 0.903393, 1; 11:53:09.615 [planetinfo.record]: cloud_alpha = 1.000000; 11:53:09.615 [planetinfo.record]: cloud_color = 0.0696182, 0.62243, 0.712891, 1; 11:53:09.615 [planetinfo.record]: cloud_fraction = 0.390000; 11:53:09.615 [planetinfo.record]: land_color = 0.160629, 0.623047, 0.623047, 1; 11:53:09.615 [planetinfo.record]: land_fraction = 0.470000; 11:53:09.615 [planetinfo.record]: polar_cloud_color = 0.357898, 0.755705, 0.820801, 1; 11:53:09.615 [planetinfo.record]: polar_land_color = 0.763709, 0.937695, 0.937695, 1; 11:53:09.615 [planetinfo.record]: polar_sea_color = 0.921283, 0.927539, 0.865945, 1; 11:53:09.615 [planetinfo.record]: sea_color = 0.705061, 0.724609, 0.532135, 1; 11:53:09.615 [planetinfo.record]: rotation_speed = 0.001891 11:53:09.615 [planetinfo.record]: planet zpos = 679770.000000 11:53:09.615 [planetinfo.record]: sun_radius = 172982.430725 11:53:09.615 [planetinfo.record]: sun_vector = -0.271 0.903 0.335 11:53:09.615 [planetinfo.record]: sun_distance = 888930 11:53:09.615 [planetinfo.record]: corona_flare = 0.077682 11:53:09.615 [planetinfo.record]: corona_hues = 0.726616 11:53:09.615 [planetinfo.record]: sun_color = 0.816287, 0.478874, 0.386813, 1 11:53:09.616 [planetinfo.record]: corona_shimmer = 0.275999 11:53:09.616 [planetinfo.record]: station_vector = -0.997 0.004 0.077 11:53:09.616 [planetinfo.record]: station = coriolis 11:53:14.057 [PLANETINFO OVER]: Done 11:53:14.057 [PLANETINFO LOGGING]: 1 30 11:53:15.060 [planetinfo.record]: seed = 200 29 182 145 11:53:15.060 [planetinfo.record]: coordinates = 29 197 11:53:15.060 [planetinfo.record]: government = 1; 11:53:15.060 [planetinfo.record]: economy = 7; 11:53:15.060 [planetinfo.record]: techlevel = 2; 11:53:15.060 [planetinfo.record]: population = 17; 11:53:15.060 [planetinfo.record]: productivity = 2040; 11:53:15.060 [planetinfo.record]: name = "Atrazama"; 11:53:15.060 [planetinfo.record]: inhabitant = "Black Slimy Frog"; 11:53:15.060 [planetinfo.record]: inhabitants = "Black Slimy Frogs"; 11:53:15.060 [planetinfo.record]: description = "This planet is a dull world."; 11:53:15.062 [planetinfo.record]: air_color = 0.790597, 0.607533, 0.891035, 1; 11:53:15.062 [planetinfo.record]: cloud_alpha = 1.000000; 11:53:15.062 [planetinfo.record]: cloud_color = 0.675781, 0.435562, 0.446822, 1; 11:53:15.062 [planetinfo.record]: cloud_fraction = 0.480000; 11:53:15.062 [planetinfo.record]: land_color = 0.525391, 0.462154, 0.0492554, 1; 11:53:15.062 [planetinfo.record]: land_fraction = 0.550000; 11:53:15.062 [planetinfo.record]: polar_cloud_color = 0.804102, 0.625456, 0.63383, 1; 11:53:15.062 [planetinfo.record]: polar_land_color = 0.947461, 0.918952, 0.732802, 1; 11:53:15.062 [planetinfo.record]: polar_sea_color = 0.901563, 0.855956, 0.867714, 1; 11:53:15.062 [planetinfo.record]: sea_color = 0.984375, 0.785193, 0.836545, 1; 11:53:15.062 [planetinfo.record]: rotation_speed = 0.004211 11:53:15.062 [planetinfo.record]: planet zpos = 341110.000000 11:53:15.063 [planetinfo.record]: sun_radius = 99867.852631 11:53:15.063 [planetinfo.record]: sun_vector = 0.157 -0.732 0.663 11:53:15.063 [planetinfo.record]: sun_distance = 744240 11:53:15.063 [planetinfo.record]: corona_flare = 0.001024 11:53:15.063 [planetinfo.record]: corona_hues = 0.965195 11:53:15.063 [planetinfo.record]: sun_color = 0.68712, 0.761303, 0.841043, 1 11:53:15.063 [planetinfo.record]: corona_shimmer = 0.655504 11:53:15.063 [planetinfo.record]: station_vector = 0.768 0.271 0.581 11:53:15.063 [planetinfo.record]: station = coriolis 11:53:19.799 [PLANETINFO OVER]: Done 11:53:19.800 [PLANETINFO LOGGING]: 1 31 11:53:20.805 [planetinfo.record]: seed = 24 43 122 196 11:53:20.805 [planetinfo.record]: coordinates = 43 36 11:53:20.805 [planetinfo.record]: government = 3; 11:53:20.805 [planetinfo.record]: economy = 4; 11:53:20.805 [planetinfo.record]: techlevel = 8; 11:53:20.805 [planetinfo.record]: population = 40; 11:53:20.805 [planetinfo.record]: productivity = 13440; 11:53:20.805 [planetinfo.record]: name = "Zaragete"; 11:53:20.805 [planetinfo.record]: inhabitant = "Human Colonial"; 11:53:20.805 [planetinfo.record]: inhabitants = "Human Colonials"; 11:53:20.805 [planetinfo.record]: description = "This world is very fabled for its unusual oceans."; 11:53:20.816 [planetinfo.record]: air_color = 0.623785, 0.56938, 0.863068, 1; 11:53:20.816 [planetinfo.record]: cloud_alpha = 1.000000; 11:53:20.816 [planetinfo.record]: cloud_color = 0.484519, 0.342133, 0.591797, 1; 11:53:20.816 [planetinfo.record]: cloud_fraction = 0.260000; 11:53:20.816 [planetinfo.record]: land_color = 0.66, 0.444968, 0.255234, 1; 11:53:20.816 [planetinfo.record]: land_fraction = 0.710000; 11:53:20.816 [planetinfo.record]: polar_cloud_color = 0.679489, 0.564255, 0.766309, 1; 11:53:20.816 [planetinfo.record]: polar_land_color = 0.934, 0.857924, 0.790799, 1; 11:53:20.816 [planetinfo.record]: polar_sea_color = 0.893938, 0.93418, 0.886923, 1; 11:53:20.816 [planetinfo.record]: sea_color = 0.544789, 0.658203, 0.52502, 1; 11:53:20.816 [planetinfo.record]: rotation_speed = 0.000330 11:53:20.816 [planetinfo.record]: planet zpos = 504790.000000 11:53:20.816 [planetinfo.record]: sun_radius = 101391.353607 11:53:20.816 [planetinfo.record]: sun_vector = 0.049 0.394 -0.918 11:53:20.816 [planetinfo.record]: sun_distance = 893090 11:53:20.816 [planetinfo.record]: corona_flare = 0.004449 11:53:20.817 [planetinfo.record]: corona_hues = 0.657181 11:53:20.817 [planetinfo.record]: sun_color = 0.7651, 0.698235, 0.664066, 1 11:53:20.817 [planetinfo.record]: corona_shimmer = 0.504309 11:53:20.817 [planetinfo.record]: station_vector = 0.964 -0.112 0.239 11:53:20.817 [planetinfo.record]: station = coriolis 11:53:24.769 [PLANETINFO OVER]: Done 11:53:24.770 [PLANETINFO LOGGING]: 1 32 11:53:25.773 [planetinfo.record]: seed = 16 220 166 243 11:53:25.773 [planetinfo.record]: coordinates = 220 3 11:53:25.773 [planetinfo.record]: government = 2; 11:53:25.773 [planetinfo.record]: economy = 3; 11:53:25.773 [planetinfo.record]: techlevel = 5; 11:53:25.773 [planetinfo.record]: population = 26; 11:53:25.773 [planetinfo.record]: productivity = 8736; 11:53:25.773 [planetinfo.record]: name = "Bebege"; 11:53:25.773 [planetinfo.record]: inhabitant = "Lizard"; 11:53:25.773 [planetinfo.record]: inhabitants = "Lizards"; 11:53:25.773 [planetinfo.record]: description = "This world is most fabled for Zero-G hockey but ravaged by occasional earthquakes."; 11:53:25.790 [planetinfo.record]: air_color = 0.590615, 0.93201, 0.883686, 1; 11:53:25.791 [planetinfo.record]: cloud_alpha = 1.000000; 11:53:25.791 [planetinfo.record]: cloud_color = 0.798828, 0.633446, 0.399414, 1; 11:53:25.791 [planetinfo.record]: cloud_fraction = 0.500000; 11:53:25.791 [planetinfo.record]: land_color = 0.519531, 0.45256, 0.490232, 1; 11:53:25.791 [planetinfo.record]: land_fraction = 0.390000; 11:53:25.791 [planetinfo.record]: polar_cloud_color = 0.859473, 0.748262, 0.590887, 1; 11:53:25.791 [planetinfo.record]: polar_land_color = 0.948047, 0.917495, 0.93468, 1; 11:53:25.791 [planetinfo.record]: polar_sea_color = 0.930578, 0.931055, 0.870045, 1; 11:53:25.791 [planetinfo.record]: sea_color = 0.688041, 0.689453, 0.508741, 1; 11:53:25.791 [planetinfo.record]: rotation_speed = 0.000223 11:53:25.791 [planetinfo.record]: planet zpos = 418440.000000 11:53:25.791 [planetinfo.record]: sun_radius = 94992.037354 11:53:25.791 [planetinfo.record]: sun_vector = 0.852 -0.147 0.503 11:53:25.791 [planetinfo.record]: sun_distance = 760800 11:53:25.791 [planetinfo.record]: corona_flare = 0.089258 11:53:25.791 [planetinfo.record]: corona_hues = 0.781609 11:53:25.791 [planetinfo.record]: sun_color = 0.699924, 0.69156, 0.579982, 1 11:53:25.792 [planetinfo.record]: corona_shimmer = 0.280310 11:53:25.792 [planetinfo.record]: station_vector = -0.124 0.454 0.882 11:53:25.792 [planetinfo.record]: station = coriolis 11:53:30.004 [PLANETINFO OVER]: Done 11:53:30.004 [PLANETINFO LOGGING]: 1 33 11:53:31.007 [planetinfo.record]: seed = 240 105 58 224 11:53:31.007 [planetinfo.record]: coordinates = 105 163 11:53:31.007 [planetinfo.record]: government = 6; 11:53:31.007 [planetinfo.record]: economy = 3; 11:53:31.007 [planetinfo.record]: techlevel = 8; 11:53:31.007 [planetinfo.record]: population = 42; 11:53:31.007 [planetinfo.record]: productivity = 23520; 11:53:31.007 [planetinfo.record]: name = "Diti"; 11:53:31.007 [planetinfo.record]: inhabitant = "Human Colonial"; 11:53:31.007 [planetinfo.record]: inhabitants = "Human Colonials"; 11:53:31.007 [planetinfo.record]: description = "The planet Diti is famous for Ditiian shrew steak but ravaged by unpredictable civil war."; 11:53:31.028 [planetinfo.record]: air_color = 0.533486, 0.571898, 0.855914, 1; 11:53:31.028 [planetinfo.record]: cloud_alpha = 1.000000; 11:53:31.028 [planetinfo.record]: cloud_color = 0.267334, 0.371483, 0.570312, 1; 11:53:31.028 [planetinfo.record]: cloud_fraction = 0.390000; 11:53:31.028 [planetinfo.record]: land_color = 0.66, 0.0567188, 0.523319, 1; 11:53:31.028 [planetinfo.record]: land_fraction = 0.500000; 11:53:31.028 [planetinfo.record]: polar_cloud_color = 0.505412, 0.591772, 0.756641, 1; 11:53:31.028 [planetinfo.record]: polar_land_color = 0.934, 0.720566, 0.885644, 1; 11:53:31.028 [planetinfo.record]: polar_sea_color = 0.941211, 0.926094, 0.89029, 1; 11:53:31.028 [planetinfo.record]: sea_color = 0.587891, 0.550121, 0.460667, 1; 11:53:31.028 [planetinfo.record]: rotation_speed = 0.001290 11:53:31.028 [planetinfo.record]: planet zpos = 262890.000000 11:53:31.028 [planetinfo.record]: sun_radius = 57228.173523 11:53:31.028 [planetinfo.record]: sun_vector = -0.529 0.795 0.298 11:53:31.028 [planetinfo.record]: sun_distance = 554990 11:53:31.028 [planetinfo.record]: corona_flare = 0.072487 11:53:31.028 [planetinfo.record]: corona_hues = 0.779716 11:53:31.028 [planetinfo.record]: sun_color = 0.39764, 0.581596, 0.70625, 1 11:53:31.028 [planetinfo.record]: corona_shimmer = 0.343801 11:53:31.029 [planetinfo.record]: station_vector = -0.538 0.024 0.843 11:53:31.029 [planetinfo.record]: station = coriolis 11:53:35.859 [PLANETINFO OVER]: Done 11:53:35.860 [PLANETINFO LOGGING]: 1 34 11:53:36.862 [planetinfo.record]: seed = 184 4 54 41 11:53:36.862 [planetinfo.record]: coordinates = 4 55 11:53:36.862 [planetinfo.record]: government = 7; 11:53:36.862 [planetinfo.record]: economy = 7; 11:53:36.862 [planetinfo.record]: techlevel = 4; 11:53:36.862 [planetinfo.record]: population = 31; 11:53:36.862 [planetinfo.record]: productivity = 8184; 11:53:36.862 [planetinfo.record]: name = "Escebele"; 11:53:36.862 [planetinfo.record]: inhabitant = "Human Colonial"; 11:53:36.862 [planetinfo.record]: inhabitants = "Human Colonials"; 11:53:36.862 [planetinfo.record]: description = "Escebele is mildly well known for its hoopy casinos."; 11:53:36.887 [planetinfo.record]: air_color = 0.601738, 0.577356, 0.843557, 1; 11:53:36.887 [planetinfo.record]: cloud_alpha = 1.000000; 11:53:36.887 [planetinfo.record]: cloud_color = 0.401416, 0.347832, 0.533203, 1; 11:53:36.887 [planetinfo.record]: cloud_fraction = 0.230000; 11:53:36.888 [planetinfo.record]: land_color = 0.464063, 0.66, 0.490085, 1; 11:53:36.888 [planetinfo.record]: land_fraction = 0.690000; 11:53:36.888 [planetinfo.record]: polar_cloud_color = 0.625638, 0.579163, 0.739941, 1; 11:53:36.888 [planetinfo.record]: polar_land_color = 0.86468, 0.934, 0.873886, 1; 11:53:36.888 [planetinfo.record]: polar_sea_color = 0.798761, 0.94668, 0.89121, 1; 11:53:36.888 [planetinfo.record]: sea_color = 0.199951, 0.533203, 0.408234, 1; 11:53:36.888 [planetinfo.record]: rotation_speed = 0.003534 11:53:36.888 [planetinfo.record]: planet zpos = 563640.000000 11:53:36.888 [planetinfo.record]: sun_radius = 147325.946045 11:53:36.888 [planetinfo.record]: sun_vector = 0.736 -0.584 0.341 11:53:36.888 [planetinfo.record]: sun_distance = 1076040 11:53:36.888 [planetinfo.record]: corona_flare = 0.057813 11:53:36.888 [planetinfo.record]: corona_hues = 0.664215 11:53:36.888 [planetinfo.record]: sun_color = 0.221298, 0.56495, 0.713071, 1 11:53:36.888 [planetinfo.record]: corona_shimmer = 0.444378 11:53:36.889 [planetinfo.record]: station_vector = -0.996 0.093 0.011 11:53:36.889 [planetinfo.record]: station = coriolis 11:53:43.262 [PLANETINFO OVER]: Done 11:53:43.263 [PLANETINFO LOGGING]: 1 35 11:53:44.269 [planetinfo.record]: seed = 168 33 26 26 11:53:44.269 [planetinfo.record]: coordinates = 33 147 11:53:44.269 [planetinfo.record]: government = 5; 11:53:44.269 [planetinfo.record]: economy = 3; 11:53:44.269 [planetinfo.record]: techlevel = 8; 11:53:44.269 [planetinfo.record]: population = 41; 11:53:44.269 [planetinfo.record]: productivity = 20664; 11:53:44.269 [planetinfo.record]: name = "Qurear"; 11:53:44.269 [planetinfo.record]: inhabitant = "Human Colonial"; 11:53:44.269 [planetinfo.record]: inhabitants = "Human Colonials"; 11:53:44.269 [planetinfo.record]: description = "The planet Qurear is reasonably noted for its inhabitants’ eccentric shyness and mud tennis."; 11:53:44.271 [planetinfo.record]: air_color = 0.71859, 0.770455, 0.945668, 1; 11:53:44.271 [planetinfo.record]: cloud_alpha = 1.000000; 11:53:44.271 [planetinfo.record]: cloud_color = 0.738144, 0.806473, 0.839844, 1; 11:53:44.271 [planetinfo.record]: cloud_fraction = 0.330000; 11:53:44.271 [planetinfo.record]: land_color = 0.53625, 0.66, 0.561387, 1; 11:53:44.271 [planetinfo.record]: land_fraction = 0.350000; 11:53:44.271 [planetinfo.record]: polar_cloud_color = 0.811485, 0.856127, 0.87793, 1; 11:53:44.271 [planetinfo.record]: polar_land_color = 0.890219, 0.934, 0.899112, 1; 11:53:44.271 [planetinfo.record]: polar_sea_color = 0.908686, 0.876397, 0.947656, 1; 11:53:44.271 [planetinfo.record]: sea_color = 0.437337, 0.365997, 0.523438, 1; 11:53:44.271 [planetinfo.record]: rotation_speed = 0.004626 11:53:44.271 [planetinfo.record]: planet zpos = 540900.000000 11:53:44.271 [planetinfo.record]: sun_radius = 144992.167053 11:53:44.272 [planetinfo.record]: sun_vector = 0.465 0.573 -0.675 11:53:44.272 [planetinfo.record]: sun_distance = 919530 11:53:44.272 [planetinfo.record]: corona_flare = 0.027438 11:53:44.272 [planetinfo.record]: corona_hues = 0.593956 11:53:44.272 [planetinfo.record]: sun_color = 0.822412, 0.783677, 0.536022, 1 11:53:44.272 [planetinfo.record]: corona_shimmer = 0.346744 11:53:44.272 [planetinfo.record]: station_vector = -0.786 -0.199 0.586 11:53:44.272 [planetinfo.record]: station = coriolis 11:53:48.284 [PLANETINFO OVER]: Done 11:53:48.285 [PLANETINFO LOGGING]: 1 36 11:53:49.288 [planetinfo.record]: seed = 192 243 102 205 11:53:49.288 [planetinfo.record]: coordinates = 243 10 11:53:49.288 [planetinfo.record]: government = 0; 11:53:49.288 [planetinfo.record]: economy = 2; 11:53:49.288 [planetinfo.record]: techlevel = 8; 11:53:49.288 [planetinfo.record]: population = 35; 11:53:49.288 [planetinfo.record]: productivity = 8960; 11:53:49.288 [planetinfo.record]: name = "Dimadi"; 11:53:49.288 [planetinfo.record]: inhabitant = "Human Colonial"; 11:53:49.288 [planetinfo.record]: inhabitants = "Human Colonials"; 11:53:49.288 [planetinfo.record]: description = "This planet is most fabled for Zero-G cricket but cursed by unpredictable solar activity."; 11:53:49.294 [planetinfo.record]: air_color = 0.711325, 0.581665, 0.885182, 1; 11:53:49.294 [planetinfo.record]: cloud_alpha = 1.000000; 11:53:49.294 [planetinfo.record]: cloud_color = 0.658203, 0.375381, 0.57424, 1; 11:53:49.294 [planetinfo.record]: cloud_fraction = 0.400000; 11:53:49.295 [planetinfo.record]: land_color = 0.311953, 0.66, 0.431594, 1; 11:53:49.295 [planetinfo.record]: land_fraction = 0.240000; 11:53:49.295 [planetinfo.record]: polar_cloud_color = 0.796191, 0.58237, 0.732713, 1; 11:53:49.295 [planetinfo.record]: polar_land_color = 0.810865, 0.934, 0.853193, 1; 11:53:49.295 [planetinfo.record]: polar_sea_color = 0.932746, 0.939844, 0.892026, 1; 11:53:49.295 [planetinfo.record]: sea_color = 0.58339, 0.601562, 0.479135, 1; 11:53:49.295 [planetinfo.record]: rotation_speed = 0.004583 11:53:49.295 [planetinfo.record]: planet zpos = 958050.000000 11:53:49.295 [planetinfo.record]: sun_radius = 212314.927826 11:53:49.295 [planetinfo.record]: sun_vector = 0.353 -0.930 0.097 11:53:49.295 [planetinfo.record]: sun_distance = 1213530 11:53:49.295 [planetinfo.record]: corona_flare = 0.007820 11:53:49.295 [planetinfo.record]: corona_hues = 0.929993 11:53:49.295 [planetinfo.record]: sun_color = 0.848621, 0.66086, 0.643345, 1 11:53:49.295 [planetinfo.record]: corona_shimmer = 0.478882 11:53:49.296 [planetinfo.record]: station_vector = 0.858 -0.476 0.194 11:53:49.296 [planetinfo.record]: station = coriolis 11:53:53.835 [PLANETINFO OVER]: Done 11:53:53.836 [PLANETINFO LOGGING]: 1 37 11:53:54.844 [planetinfo.record]: seed = 64 38 26 127 11:53:54.844 [planetinfo.record]: coordinates = 38 141 11:53:54.844 [planetinfo.record]: government = 0; 11:53:54.844 [planetinfo.record]: economy = 7; 11:53:54.845 [planetinfo.record]: techlevel = 2; 11:53:54.845 [planetinfo.record]: population = 16; 11:53:54.845 [planetinfo.record]: productivity = 1536; 11:53:54.845 [planetinfo.record]: name = "Onenti"; 11:53:54.845 [planetinfo.record]: inhabitant = "Human Colonial"; 11:53:54.845 [planetinfo.record]: inhabitants = "Human Colonials"; 11:53:54.845 [planetinfo.record]: description = "This planet is most notable for Onentiian Cea’allo water but beset by evil disease."; 11:53:54.872 [planetinfo.record]: air_color = 0.712289, 0.601499, 0.86567, 1; 11:53:54.872 [planetinfo.record]: cloud_alpha = 1.000000; 11:53:54.872 [planetinfo.record]: cloud_color = 0.599609, 0.409889, 0.547733, 1; 11:53:54.872 [planetinfo.record]: cloud_fraction = 0.290000; 11:53:54.872 [planetinfo.record]: land_color = 0.66, 0.489199, 0.386719, 1; 11:53:54.872 [planetinfo.record]: land_fraction = 0.450000; 11:53:54.872 [planetinfo.record]: polar_cloud_color = 0.769824, 0.617588, 0.728197, 1; 11:53:54.872 [planetinfo.record]: polar_land_color = 0.934, 0.873573, 0.837316, 1; 11:53:54.872 [planetinfo.record]: polar_sea_color = 0.936133, 0.847456, 0.901493, 1; 11:53:54.872 [planetinfo.record]: sea_color = 0.638672, 0.396675, 0.544142, 1; 11:53:54.872 [planetinfo.record]: rotation_speed = 0.000610 11:53:54.872 [planetinfo.record]: planet zpos = 803280.000000 11:53:54.872 [planetinfo.record]: sun_radius = 218202.582397 11:53:54.872 [planetinfo.record]: sun_vector = 0.426 -0.679 0.598 11:53:54.875 [planetinfo.record]: sun_distance = 1271860 11:53:54.875 [planetinfo.record]: corona_flare = 0.061316 11:53:54.875 [planetinfo.record]: corona_hues = 0.641602 11:53:54.875 [planetinfo.record]: sun_color = 0.79733, 0.364488, 0.297898, 1 11:53:54.876 [planetinfo.record]: corona_shimmer = 0.783942 11:53:54.876 [planetinfo.record]: station_vector = -0.879 0.358 0.316 11:53:54.876 [planetinfo.record]: station = coriolis 11:53:59.668 [PLANETINFO OVER]: Done 11:53:59.669 [PLANETINFO LOGGING]: 1 38 11:54:00.680 [planetinfo.record]: seed = 40 137 54 147 11:54:00.680 [planetinfo.record]: coordinates = 137 215 11:54:00.680 [planetinfo.record]: government = 5; 11:54:00.680 [planetinfo.record]: economy = 7; 11:54:00.680 [planetinfo.record]: techlevel = 4; 11:54:00.680 [planetinfo.record]: population = 29; 11:54:00.680 [planetinfo.record]: productivity = 6264; 11:54:00.680 [planetinfo.record]: name = "Beraer"; 11:54:00.680 [planetinfo.record]: inhabitant = "Human Colonial"; 11:54:00.680 [planetinfo.record]: inhabitants = "Human Colonials"; 11:54:00.680 [planetinfo.record]: description = "Beraer is reasonably well known for its great dense forests but beset by deadly tree grubs."; 11:54:00.682 [planetinfo.record]: air_color = 0.527345, 0.958025, 0.816391, 1; 11:54:00.682 [planetinfo.record]: cloud_alpha = 1.000000; 11:54:00.682 [planetinfo.record]: cloud_color = 0.876953, 0.234653, 0.219238, 1; 11:54:00.682 [planetinfo.record]: cloud_fraction = 0.430000; 11:54:00.683 [planetinfo.record]: land_color = 0.66, 0.391553, 0.309375, 1; 11:54:00.683 [planetinfo.record]: land_fraction = 0.520000; 11:54:00.683 [planetinfo.record]: polar_cloud_color = 0.894629, 0.4851, 0.475272, 1; 11:54:00.683 [planetinfo.record]: polar_land_color = 0.934, 0.839027, 0.809953, 1; 11:54:00.683 [planetinfo.record]: polar_sea_color = 0.832922, 0.852976, 0.944531, 1; 11:54:00.683 [planetinfo.record]: sea_color = 0.292511, 0.339621, 0.554688, 1; 11:54:00.683 [planetinfo.record]: rotation_speed = 0.002978 11:54:00.683 [planetinfo.record]: planet zpos = 483730.000000 11:54:00.683 [planetinfo.record]: sun_radius = 103293.292999 11:54:00.683 [planetinfo.record]: sun_vector = 0.802 0.567 0.188 11:54:00.683 [planetinfo.record]: sun_distance = 706990 11:54:00.683 [planetinfo.record]: corona_flare = 0.012883 11:54:00.683 [planetinfo.record]: corona_hues = 0.644455 11:54:00.684 [planetinfo.record]: sun_color = 0.799823, 0.292191, 0.278967, 1 11:54:00.684 [planetinfo.record]: corona_shimmer = 1.047269 11:54:00.684 [planetinfo.record]: station_vector = -0.877 -0.456 0.151 11:54:00.684 [planetinfo.record]: station = coriolis 11:54:04.969 [PLANETINFO OVER]: Done 11:54:04.970 [PLANETINFO LOGGING]: 1 39 11:54:05.979 [planetinfo.record]: seed = 184 151 58 156 11:54:05.979 [planetinfo.record]: coordinates = 151 16 11:54:05.979 [planetinfo.record]: government = 7; 11:54:05.979 [planetinfo.record]: economy = 0; 11:54:05.979 [planetinfo.record]: techlevel = 14; 11:54:05.979 [planetinfo.record]: population = 64; 11:54:05.979 [planetinfo.record]: productivity = 56320; 11:54:05.979 [planetinfo.record]: name = "Tezaeded"; 11:54:05.979 [planetinfo.record]: inhabitant = "Human Colonial"; 11:54:05.979 [planetinfo.record]: inhabitants = "Human Colonials"; 11:54:05.979 [planetinfo.record]: description = "The planet Tezaeded is famous for its exotic fish cutlet but beset by evil spotted cats."; 11:54:06.009 [planetinfo.record]: air_color = 0.631786, 0.878027, 0.810501, 1; 11:54:06.009 [planetinfo.record]: cloud_alpha = 1.000000; 11:54:06.009 [planetinfo.record]: cloud_color = 0.636719, 0.511427, 0.482513, 1; 11:54:06.009 [planetinfo.record]: cloud_fraction = 0.140000; 11:54:06.009 [planetinfo.record]: land_color = 0.0721875, 0.17781, 0.66, 1; 11:54:06.009 [planetinfo.record]: land_fraction = 0.270000; 11:54:06.010 [planetinfo.record]: polar_cloud_color = 0.786523, 0.689792, 0.66747, 1; 11:54:06.010 [planetinfo.record]: polar_land_color = 0.726039, 0.763407, 0.934, 1; 11:54:06.010 [planetinfo.record]: polar_sea_color = 0.904696, 0.933203, 0.881075, 1; 11:54:06.010 [planetinfo.record]: sea_color = 0.586348, 0.667969, 0.518719, 1; 11:54:06.010 [planetinfo.record]: rotation_speed = 0.004078 11:54:06.010 [planetinfo.record]: planet zpos = 724680.000000 11:54:06.010 [planetinfo.record]: sun_radius = 117700.419617 11:54:06.010 [planetinfo.record]: sun_vector = -0.589 0.459 0.665 11:54:06.010 [planetinfo.record]: sun_distance = 1087020 11:54:06.010 [planetinfo.record]: corona_flare = 0.008258 11:54:06.010 [planetinfo.record]: corona_hues = 0.713676 11:54:06.010 [planetinfo.record]: sun_color = 0.799625, 0.775662, 0.721759, 1 11:54:06.011 [planetinfo.record]: corona_shimmer = 0.668271 11:54:06.011 [planetinfo.record]: station_vector = -0.912 0.325 0.250 11:54:06.011 [planetinfo.record]: station = icosahedron 11:54:10.553 [PLANETINFO OVER]: Done 11:54:10.553 [PLANETINFO LOGGING]: 1 40 11:54:11.565 [planetinfo.record]: seed = 240 88 166 21 11:54:11.565 [planetinfo.record]: coordinates = 88 120 11:54:11.565 [planetinfo.record]: government = 6; 11:54:11.565 [planetinfo.record]: economy = 0; 11:54:11.566 [planetinfo.record]: techlevel = 10; 11:54:11.566 [planetinfo.record]: population = 47; 11:54:11.566 [planetinfo.record]: productivity = 37600; 11:54:11.566 [planetinfo.record]: name = "Labilaen"; 11:54:11.566 [planetinfo.record]: inhabitant = "Green Slimy Frog"; 11:54:11.566 [planetinfo.record]: inhabitants = "Green Slimy Frogs"; 11:54:11.566 [planetinfo.record]: description = "The world Labilaen is mildly noted for its ancient mountains but ravaged by unpredictable solar activity."; 11:54:11.584 [planetinfo.record]: air_color = 0.69872, 0.808091, 0.927457, 1; 11:54:11.584 [planetinfo.record]: cloud_alpha = 1.000000; 11:54:11.584 [planetinfo.record]: cloud_color = 0.674744, 0.785156, 0.738576, 1; 11:54:11.584 [planetinfo.record]: cloud_fraction = 0.150000; 11:54:11.584 [planetinfo.record]: land_color = 0.511758, 0.185625, 0.66, 1; 11:54:11.584 [planetinfo.record]: land_fraction = 0.380000; 11:54:11.584 [planetinfo.record]: polar_cloud_color = 0.778321, 0.85332, 0.82168, 1; 11:54:11.584 [planetinfo.record]: polar_land_color = 0.881554, 0.766172, 0.934, 1; 11:54:11.584 [planetinfo.record]: polar_sea_color = 0.924704, 0.932031, 0.873415, 1; 11:54:11.584 [planetinfo.record]: sea_color = 0.658315, 0.679688, 0.508704, 1; 11:54:11.584 [planetinfo.record]: rotation_speed = 0.003850 11:54:11.585 [planetinfo.record]: planet zpos = 502080.000000 11:54:11.585 [planetinfo.record]: sun_radius = 78166.538086 11:54:11.585 [planetinfo.record]: sun_vector = -0.461 0.690 -0.558 11:54:11.585 [planetinfo.record]: sun_distance = 920480 11:54:11.585 [planetinfo.record]: corona_flare = 0.013469 11:54:11.585 [planetinfo.record]: corona_hues = 0.969429 11:54:11.585 [planetinfo.record]: sun_color = 0.205599, 0.43636, 0.708719, 1 11:54:11.585 [planetinfo.record]: corona_shimmer = 0.410853 11:54:11.585 [planetinfo.record]: station_vector = -0.752 0.167 0.638 11:54:11.586 [planetinfo.record]: station = coriolis 11:54:16.759 [PLANETINFO OVER]: Done 11:54:16.760 [PLANETINFO LOGGING]: 1 41 11:54:17.774 [planetinfo.record]: seed = 16 82 122 142 11:54:17.774 [planetinfo.record]: coordinates = 82 85 11:54:17.774 [planetinfo.record]: government = 2; 11:54:17.774 [planetinfo.record]: economy = 5; 11:54:17.774 [planetinfo.record]: techlevel = 5; 11:54:17.774 [planetinfo.record]: population = 28; 11:54:17.774 [planetinfo.record]: productivity = 6720; 11:54:17.774 [planetinfo.record]: name = "Reveve"; 11:54:17.774 [planetinfo.record]: inhabitant = "Human Colonial"; 11:54:17.774 [planetinfo.record]: inhabitants = "Human Colonials"; 11:54:17.774 [planetinfo.record]: description = "This world is very notable for the Reveveian tree grub."; 11:54:17.782 [planetinfo.record]: air_color = 0.437467, 0.71524, 0.875426, 1; 11:54:17.782 [planetinfo.record]: cloud_alpha = 1.000000; 11:54:17.782 [planetinfo.record]: cloud_color = 0.0614166, 0.628906, 0.123486, 1; 11:54:17.782 [planetinfo.record]: cloud_fraction = 0.360000; 11:54:17.782 [planetinfo.record]: land_color = 0.548103, 0.357178, 0.601562, 1; 11:54:17.782 [planetinfo.record]: land_fraction = 0.560000; 11:54:17.783 [planetinfo.record]: polar_cloud_color = 0.341419, 0.783008, 0.389718, 1; 11:54:17.783 [planetinfo.record]: polar_land_color = 0.918963, 0.844391, 0.939844, 1; 11:54:17.783 [planetinfo.record]: polar_sea_color = 0.928906, 0.90817, 0.859057, 1; 11:54:17.783 [planetinfo.record]: sea_color = 0.710938, 0.647455, 0.497101, 1; 11:54:17.783 [planetinfo.record]: rotation_speed = 0.004999 11:54:17.783 [planetinfo.record]: planet zpos = 713020.000000 11:54:17.783 [planetinfo.record]: sun_radius = 123650.163574 11:54:17.783 [planetinfo.record]: sun_vector = -0.952 -0.143 0.272 11:54:17.783 [planetinfo.record]: sun_distance = 1490860 11:54:17.783 [planetinfo.record]: corona_flare = 0.084465 11:54:17.783 [planetinfo.record]: corona_hues = 0.524200 11:54:17.783 [planetinfo.record]: sun_color = 0.794277, 0.800043, 0.230975, 1 11:54:17.783 [planetinfo.record]: corona_shimmer = 0.366172 11:54:17.784 [planetinfo.record]: station_vector = 0.394 0.914 0.099 11:54:17.784 [planetinfo.record]: station = coriolis 11:54:23.001 [PLANETINFO OVER]: Done 11:54:23.002 [PLANETINFO LOGGING]: 1 42 11:54:24.009 [planetinfo.record]: seed = 24 219 182 39 11:54:24.009 [planetinfo.record]: coordinates = 219 22 11:54:24.009 [planetinfo.record]: government = 3; 11:54:24.009 [planetinfo.record]: economy = 6; 11:54:24.009 [planetinfo.record]: techlevel = 6; 11:54:24.009 [planetinfo.record]: population = 34; 11:54:24.009 [planetinfo.record]: productivity = 7616; 11:54:24.009 [planetinfo.record]: name = "Soorte"; 11:54:24.009 [planetinfo.record]: inhabitant = "Fierce Red Furry Rodent"; 11:54:24.009 [planetinfo.record]: inhabitants = "Fierce Red Furry Rodents"; 11:54:24.009 [planetinfo.record]: description = "Soorte is a revolting little planet."; 11:54:24.013 [planetinfo.record]: air_color = 0.453541, 0.696397, 0.866971, 1; 11:54:24.014 [planetinfo.record]: cloud_alpha = 1.000000; 11:54:24.014 [planetinfo.record]: cloud_color = 0.101372, 0.603516, 0.226908, 1; 11:54:24.014 [planetinfo.record]: cloud_fraction = 0.230000; 11:54:24.014 [planetinfo.record]: land_color = 0.554688, 0.391944, 0.00650024, 1; 11:54:24.014 [planetinfo.record]: land_fraction = 0.260000; 11:54:24.014 [planetinfo.record]: polar_cloud_color = 0.370344, 0.771582, 0.470654, 1; 11:54:24.014 [planetinfo.record]: polar_land_color = 0.944531, 0.875251, 0.711166, 1; 11:54:24.014 [planetinfo.record]: polar_sea_color = 0.934766, 0.909403, 0.873513, 1; 11:54:24.014 [planetinfo.record]: sea_color = 0.652344, 0.581545, 0.481358, 1; 11:54:24.014 [planetinfo.record]: rotation_speed = 0.002317 11:54:24.014 [planetinfo.record]: planet zpos = 579240.000000 11:54:24.015 [planetinfo.record]: sun_radius = 114200.798035 11:54:24.015 [planetinfo.record]: sun_vector = -0.300 0.873 0.385 11:54:24.015 [planetinfo.record]: sun_distance = 1013670 11:54:24.015 [planetinfo.record]: corona_flare = 0.062817 11:54:24.015 [planetinfo.record]: corona_hues = 0.769684 11:54:24.015 [planetinfo.record]: sun_color = 0.239421, 0.314288, 0.770502, 1 11:54:24.015 [planetinfo.record]: corona_shimmer = 0.449455 11:54:24.015 [planetinfo.record]: station_vector = -0.119 0.182 0.976 11:54:24.015 [planetinfo.record]: station = coriolis 11:54:28.571 [PLANETINFO OVER]: Done 11:54:28.572 [PLANETINFO LOGGING]: 1 43 11:54:29.576 [planetinfo.record]: seed = 72 93 218 146 11:54:29.576 [planetinfo.record]: coordinates = 93 28 11:54:29.576 [planetinfo.record]: government = 1; 11:54:29.576 [planetinfo.record]: economy = 6; 11:54:29.576 [planetinfo.record]: techlevel = 3; 11:54:29.576 [planetinfo.record]: population = 20; 11:54:29.576 [planetinfo.record]: productivity = 3200; 11:54:29.576 [planetinfo.record]: name = "Eninte"; 11:54:29.576 [planetinfo.record]: inhabitant = "Black Bug-Eyed Lobster"; 11:54:29.576 [planetinfo.record]: inhabitants = "Black Bug-Eyed Lobsters"; 11:54:29.576 [planetinfo.record]: description = "This world is mildly well known for its exotic cuisine and its inhabitants’ ingrained silliness."; 11:54:29.592 [planetinfo.record]: air_color = 0.561917, 0.970383, 0.96676, 1; 11:54:29.592 [planetinfo.record]: cloud_alpha = 1.000000; 11:54:29.592 [planetinfo.record]: cloud_color = 0.914062, 0.904578, 0.307068, 1; 11:54:29.592 [planetinfo.record]: cloud_fraction = 0.270000; 11:54:29.592 [planetinfo.record]: land_color = 0.642578, 0.610536, 0.534645, 1; 11:54:29.592 [planetinfo.record]: land_fraction = 0.240000; 11:54:29.592 [planetinfo.record]: polar_cloud_color = 0.911328, 0.905418, 0.533091, 1; 11:54:29.592 [planetinfo.record]: polar_land_color = 0.935742, 0.924077, 0.896448, 1; 11:54:29.592 [planetinfo.record]: polar_sea_color = 0.765618, 0.940039, 0.890983, 1; 11:54:29.592 [planetinfo.record]: sea_color = 0.154587, 0.599609, 0.474447, 1; 11:54:29.592 [planetinfo.record]: rotation_speed = 0.003440 11:54:29.592 [planetinfo.record]: planet zpos = 444730.000000 11:54:29.592 [planetinfo.record]: sun_radius = 65981.723175 11:54:29.592 [planetinfo.record]: sun_vector = 0.852 -0.522 -0.033 11:54:29.592 [planetinfo.record]: sun_distance = 718410 11:54:29.592 [planetinfo.record]: corona_flare = 0.037274 11:54:29.592 [planetinfo.record]: corona_hues = 0.531281 11:54:29.592 [planetinfo.record]: sun_color = 0.259167, 0.466427, 0.669272, 1 11:54:29.593 [planetinfo.record]: corona_shimmer = 0.377290 11:54:29.593 [planetinfo.record]: station_vector = -0.195 -0.888 0.416 11:54:29.593 [planetinfo.record]: station = coriolis 11:54:34.678 [PLANETINFO OVER]: Done 11:54:34.679 [PLANETINFO LOGGING]: 1 44 11:54:35.693 [planetinfo.record]: seed = 160 155 102 164 11:54:35.693 [planetinfo.record]: coordinates = 155 252 11:54:35.693 [planetinfo.record]: government = 4; 11:54:35.693 [planetinfo.record]: economy = 4; 11:54:35.693 [planetinfo.record]: techlevel = 8; 11:54:35.693 [planetinfo.record]: population = 41; 11:54:35.694 [planetinfo.record]: productivity = 15744; 11:54:35.694 [planetinfo.record]: name = "Zateteis"; 11:54:35.694 [planetinfo.record]: inhabitant = "Human Colonial"; 11:54:35.694 [planetinfo.record]: inhabitants = "Human Colonials"; 11:54:35.694 [planetinfo.record]: description = "The planet Zateteis is an unremarkable dump."; 11:54:35.704 [planetinfo.record]: air_color = 0.746375, 0.596196, 0.884531, 1; 11:54:35.705 [planetinfo.record]: cloud_alpha = 1.000000; 11:54:35.705 [planetinfo.record]: cloud_color = 0.65625, 0.407593, 0.512495, 1; 11:54:35.705 [planetinfo.record]: cloud_fraction = 0.210000; 11:54:35.705 [planetinfo.record]: land_color = 0.591317, 0.66, 0.500156, 1; 11:54:35.705 [planetinfo.record]: land_fraction = 0.390000; 11:54:35.705 [planetinfo.record]: polar_cloud_color = 0.795313, 0.606969, 0.686427, 1; 11:54:35.705 [planetinfo.record]: polar_land_color = 0.909701, 0.934, 0.877449, 1; 11:54:35.705 [planetinfo.record]: polar_sea_color = 0.931506, 0.93457, 0.878533, 1; 11:54:35.705 [planetinfo.record]: sea_color = 0.645715, 0.654297, 0.497368, 1; 11:54:35.705 [planetinfo.record]: rotation_speed = 0.003301 11:54:35.706 [planetinfo.record]: planet zpos = 399500.000000 11:54:35.706 [planetinfo.record]: sun_radius = 61290.478516 11:54:35.706 [planetinfo.record]: sun_vector = 0.635 -0.758 -0.146 11:54:35.706 [planetinfo.record]: sun_distance = 759050 11:54:35.706 [planetinfo.record]: corona_flare = 0.097310 11:54:35.706 [planetinfo.record]: corona_hues = 0.795235 11:54:35.706 [planetinfo.record]: sun_color = 0.843579, 0.749366, 0.706463, 1 11:54:35.706 [planetinfo.record]: corona_shimmer = 0.307236 11:54:35.706 [planetinfo.record]: station_vector = -0.867 -0.280 0.412 11:54:35.706 [planetinfo.record]: station = coriolis 11:54:40.159 [PLANETINFO OVER]: Done 11:54:40.159 [PLANETINFO LOGGING]: 1 45 11:54:41.182 [planetinfo.record]: seed = 96 93 90 22 11:54:41.182 [planetinfo.record]: coordinates = 93 124 11:54:41.182 [planetinfo.record]: government = 4; 11:54:41.182 [planetinfo.record]: economy = 4; 11:54:41.182 [planetinfo.record]: techlevel = 6; 11:54:41.182 [planetinfo.record]: population = 33; 11:54:41.182 [planetinfo.record]: productivity = 12672; 11:54:41.182 [planetinfo.record]: name = "Veerge"; 11:54:41.182 [planetinfo.record]: inhabitant = "Human Colonial"; 11:54:41.182 [planetinfo.record]: inhabitants = "Human Colonials"; 11:54:41.182 [planetinfo.record]: description = "The world Veerge is a boring planet."; 11:54:41.184 [planetinfo.record]: air_color = 0.724267, 0.751729, 0.954773, 1; 11:54:41.184 [planetinfo.record]: cloud_alpha = 1.000000; 11:54:41.184 [planetinfo.record]: cloud_color = 0.758789, 0.796051, 0.867188, 1; 11:54:41.184 [planetinfo.record]: cloud_fraction = 0.350000; 11:54:41.184 [planetinfo.record]: land_color = 0.66, 0.553471, 0.327422, 1; 11:54:41.184 [planetinfo.record]: land_fraction = 0.440000; 11:54:41.184 [planetinfo.record]: polar_cloud_color = 0.820685, 0.844592, 0.890234, 1; 11:54:41.184 [planetinfo.record]: polar_land_color = 0.934, 0.896311, 0.816338, 1; 11:54:41.184 [planetinfo.record]: polar_sea_color = 0.820904, 0.845579, 0.942383, 1; 11:54:41.185 [planetinfo.record]: sea_color = 0.279083, 0.339429, 0.576172, 1; 11:54:41.185 [planetinfo.record]: rotation_speed = 0.004320 11:54:41.185 [planetinfo.record]: planet zpos = 577850.000000 11:54:41.185 [planetinfo.record]: sun_radius = 141219.093323 11:54:41.185 [planetinfo.record]: sun_vector = 0.240 0.971 0.019 11:54:41.185 [planetinfo.record]: sun_distance = 711200 11:54:41.185 [planetinfo.record]: corona_flare = 0.085381 11:54:41.186 [planetinfo.record]: corona_hues = 0.849991 11:54:41.186 [planetinfo.record]: sun_color = 0.336742, 0.555165, 0.76416, 1 11:54:41.186 [planetinfo.record]: corona_shimmer = 0.259505 11:54:41.186 [planetinfo.record]: station_vector = -0.310 -0.750 0.585 11:54:41.186 [planetinfo.record]: station = coriolis 11:54:45.357 [PLANETINFO OVER]: Done 11:54:45.358 [PLANETINFO LOGGING]: 1 46 11:54:46.367 [planetinfo.record]: seed = 136 106 182 190 11:54:46.367 [planetinfo.record]: coordinates = 106 99 11:54:46.368 [planetinfo.record]: government = 1; 11:54:46.368 [planetinfo.record]: economy = 3; 11:54:46.368 [planetinfo.record]: techlevel = 7; 11:54:46.368 [planetinfo.record]: population = 33; 11:54:46.368 [planetinfo.record]: productivity = 9240; 11:54:46.368 [planetinfo.record]: name = "Ridivexe"; 11:54:46.368 [planetinfo.record]: inhabitant = "Harmless Bug-Eyed Lobster"; 11:54:46.368 [planetinfo.record]: inhabitants = "Harmless Bug-Eyed Lobsters"; 11:54:46.368 [planetinfo.record]: description = "Ridivexe is cursed by killer mountain goats."; 11:54:46.384 [planetinfo.record]: air_color = 0.573241, 0.931359, 0.838743, 1; 11:54:46.384 [planetinfo.record]: cloud_alpha = 1.000000; 11:54:46.384 [planetinfo.record]: cloud_color = 0.796875, 0.458456, 0.354858, 1; 11:54:46.384 [planetinfo.record]: cloud_fraction = 0.580000; 11:54:46.384 [planetinfo.record]: land_color = 0.66, 0.565093, 0.464063, 1; 11:54:46.384 [planetinfo.record]: land_fraction = 0.640000; 11:54:46.384 [planetinfo.record]: polar_cloud_color = 0.858594, 0.6307, 0.560937, 1; 11:54:46.384 [planetinfo.record]: polar_land_color = 0.934, 0.900423, 0.86468, 1; 11:54:46.384 [planetinfo.record]: polar_sea_color = 0.867157, 0.860791, 0.911719, 1; 11:54:46.384 [planetinfo.record]: sea_color = 0.710216, 0.685559, 0.882812, 1; 11:54:46.384 [planetinfo.record]: rotation_speed = 0.001744 11:54:46.384 [planetinfo.record]: planet zpos = 845780.000000 11:54:46.384 [planetinfo.record]: sun_radius = 155394.086609 11:54:46.384 [planetinfo.record]: sun_vector = 0.123 -0.900 -0.417 11:54:46.385 [planetinfo.record]: sun_distance = 1236140 11:54:46.385 [planetinfo.record]: corona_flare = 0.095132 11:54:46.385 [planetinfo.record]: corona_hues = 0.862183 11:54:46.385 [planetinfo.record]: sun_color = 0.679837, 0.666646, 0.315681, 1 11:54:46.385 [planetinfo.record]: corona_shimmer = 0.301196 11:54:46.385 [planetinfo.record]: station_vector = 0.147 -0.939 0.312 11:54:46.385 [planetinfo.record]: station = coriolis 11:54:50.758 [PLANETINFO OVER]: Done 11:54:50.759 [PLANETINFO LOGGING]: 1 47 11:54:51.781 [planetinfo.record]: seed = 88 2 250 69 11:54:51.781 [planetinfo.record]: coordinates = 2 182 11:54:51.781 [planetinfo.record]: government = 3; 11:54:51.781 [planetinfo.record]: economy = 6; 11:54:51.781 [planetinfo.record]: techlevel = 5; 11:54:51.781 [planetinfo.record]: population = 30; 11:54:51.781 [planetinfo.record]: productivity = 6720; 11:54:51.781 [planetinfo.record]: name = "Cerisoma"; 11:54:51.781 [planetinfo.record]: inhabitant = "Fierce Yellow Fat Humanoid"; 11:54:51.781 [planetinfo.record]: inhabitants = "Fierce Yellow Fat Humanoids"; 11:54:51.781 [planetinfo.record]: description = "The planet Cerisoma is most noted for its inhabitants’ exceptional loathing of food blenders and its exciting sit coms."; 11:54:51.790 [planetinfo.record]: air_color = 0.544381, 0.98021, 0.986643, 1; 11:54:51.790 [planetinfo.record]: cloud_alpha = 1.000000; 11:54:51.791 [planetinfo.record]: cloud_color = 0.923603, 0.962891, 0.244484, 1; 11:54:51.791 [planetinfo.record]: cloud_fraction = 0.190000; 11:54:51.791 [planetinfo.record]: land_color = 0.66, 0.335156, 0.654924, 1; 11:54:51.791 [planetinfo.record]: land_fraction = 0.480000; 11:54:51.791 [planetinfo.record]: polar_cloud_color = 0.9095, 0.933301, 0.498095, 1; 11:54:51.791 [planetinfo.record]: polar_land_color = 0.934, 0.819074, 0.932204, 1; 11:54:51.791 [planetinfo.record]: polar_sea_color = 0.939453, 0.924878, 0.88615, 1; 11:54:51.791 [planetinfo.record]: sea_color = 0.605469, 0.567895, 0.468056, 1; 11:54:51.791 [planetinfo.record]: rotation_speed = 0.002766 11:54:51.791 [planetinfo.record]: planet zpos = 491760.000000 11:54:51.791 [planetinfo.record]: sun_radius = 101612.091064 11:54:51.791 [planetinfo.record]: sun_vector = -0.507 -0.846 0.163 11:54:51.792 [planetinfo.record]: sun_distance = 737640 11:54:51.792 [planetinfo.record]: corona_flare = 0.060893 11:54:51.792 [planetinfo.record]: corona_hues = 0.726601 11:54:51.792 [planetinfo.record]: sun_color = 0.758426, 0.638601, 0.557617, 1 11:54:51.792 [planetinfo.record]: corona_shimmer = 0.815652 11:54:51.792 [planetinfo.record]: station_vector = -0.267 0.802 0.534 11:54:51.792 [planetinfo.record]: station = coriolis 11:54:56.190 [PLANETINFO OVER]: Done 11:54:56.191 [PLANETINFO LOGGING]: 1 48 11:54:57.198 [planetinfo.record]: seed = 208 139 166 209 11:54:57.198 [planetinfo.record]: coordinates = 139 71 11:54:57.198 [planetinfo.record]: government = 2; 11:54:57.198 [planetinfo.record]: economy = 7; 11:54:57.198 [planetinfo.record]: techlevel = 4; 11:54:57.198 [planetinfo.record]: population = 26; 11:54:57.198 [planetinfo.record]: productivity = 3744; 11:54:57.198 [planetinfo.record]: name = "Atzaxe"; 11:54:57.198 [planetinfo.record]: inhabitant = "Fat Humanoid"; 11:54:57.198 [planetinfo.record]: inhabitants = "Fat Humanoids"; 11:54:57.198 [planetinfo.record]: description = "The planet Atzaxe is a boring planet."; 11:54:57.203 [planetinfo.record]: air_color = 0.731648, 0.786332, 0.924855, 1; 11:54:57.204 [planetinfo.record]: cloud_alpha = 1.000000; 11:54:57.204 [planetinfo.record]: cloud_color = 0.756088, 0.773857, 0.777344, 1; 11:54:57.204 [planetinfo.record]: cloud_fraction = 0.410000; 11:54:57.204 [planetinfo.record]: land_color = 0.66, 0.108281, 0.276383, 1; 11:54:57.204 [planetinfo.record]: land_fraction = 0.690000; 11:54:57.204 [planetinfo.record]: polar_cloud_color = 0.835282, 0.847422, 0.849805, 1; 11:54:57.204 [planetinfo.record]: polar_land_color = 0.934, 0.738809, 0.798281, 1; 11:54:57.204 [planetinfo.record]: polar_sea_color = 0.930863, 0.937305, 0.885771, 1; 11:54:57.204 [planetinfo.record]: sea_color = 0.609718, 0.626953, 0.489072, 1; 11:54:57.205 [planetinfo.record]: rotation_speed = 0.002694 11:54:57.205 [planetinfo.record]: planet zpos = 417430.000000 11:54:57.205 [planetinfo.record]: sun_radius = 90667.045593 11:54:57.205 [planetinfo.record]: sun_vector = 0.468 -0.336 -0.817 11:54:57.205 [planetinfo.record]: sun_distance = 545870 11:54:57.205 [planetinfo.record]: corona_flare = 0.086723 11:54:57.205 [planetinfo.record]: corona_hues = 0.634865 11:54:57.205 [planetinfo.record]: sun_color = 0.246635, 0.427796, 0.728815, 1 11:54:57.205 [planetinfo.record]: corona_shimmer = 1.269930 11:54:57.206 [planetinfo.record]: station_vector = -0.808 0.461 0.366 11:54:57.206 [planetinfo.record]: station = coriolis 11:55:01.669 [PLANETINFO OVER]: Done 11:55:01.670 [PLANETINFO LOGGING]: 1 49 11:55:02.675 [planetinfo.record]: seed = 48 120 186 30 11:55:02.675 [planetinfo.record]: coordinates = 120 2 11:55:02.675 [planetinfo.record]: government = 6; 11:55:02.675 [planetinfo.record]: economy = 2; 11:55:02.675 [planetinfo.record]: techlevel = 8; 11:55:02.675 [planetinfo.record]: population = 41; 11:55:02.675 [planetinfo.record]: productivity = 26240; 11:55:02.675 [planetinfo.record]: name = "Rieda"; 11:55:02.676 [planetinfo.record]: inhabitant = "Green Horned Bird"; 11:55:02.676 [planetinfo.record]: inhabitants = "Green Horned Birds"; 11:55:02.676 [planetinfo.record]: description = "The world Rieda is reasonably famous for the Riedaian evil Zanuoid."; 11:55:02.696 [planetinfo.record]: air_color = 0.620324, 0.832558, 0.976887, 1; 11:55:02.696 [planetinfo.record]: cloud_alpha = 1.000000; 11:55:02.696 [planetinfo.record]: cloud_color = 0.470444, 0.933594, 0.575376, 1; 11:55:02.696 [planetinfo.record]: cloud_fraction = 0.310000; 11:55:02.696 [planetinfo.record]: land_color = 0.420718, 0.66, 0.311953, 1; 11:55:02.696 [planetinfo.record]: land_fraction = 0.310000; 11:55:02.696 [planetinfo.record]: polar_cloud_color = 0.634827, 0.920117, 0.699463, 1; 11:55:02.696 [planetinfo.record]: polar_land_color = 0.849345, 0.934, 0.810865, 1; 11:55:02.696 [planetinfo.record]: polar_sea_color = 0.932724, 0.938867, 0.889723, 1; 11:55:02.696 [planetinfo.record]: sea_color = 0.595329, 0.611328, 0.483331, 1; 11:55:02.696 [planetinfo.record]: rotation_speed = 0.003704 11:55:02.696 [planetinfo.record]: planet zpos = 717200.000000 11:55:02.696 [planetinfo.record]: sun_radius = 120008.551025 11:55:02.696 [planetinfo.record]: sun_vector = 0.541 0.445 0.713 11:55:02.696 [planetinfo.record]: sun_distance = 1108400 11:55:02.697 [planetinfo.record]: corona_flare = 0.010800 11:55:02.697 [planetinfo.record]: corona_hues = 0.594696 11:55:02.697 [planetinfo.record]: sun_color = 0.342511, 0.383855, 0.662378, 1 11:55:02.697 [planetinfo.record]: corona_shimmer = 0.510286 11:55:02.697 [planetinfo.record]: station_vector = 0.477 0.558 0.679 11:55:02.697 [planetinfo.record]: station = coriolis 11:55:07.085 [PLANETINFO OVER]: Done 11:55:07.086 [PLANETINFO LOGGING]: 1 50 11:55:08.108 [planetinfo.record]: seed = 120 231 54 176 11:55:08.108 [planetinfo.record]: coordinates = 231 47 11:55:08.108 [planetinfo.record]: government = 7; 11:55:08.108 [planetinfo.record]: economy = 7; 11:55:08.108 [planetinfo.record]: techlevel = 7; 11:55:08.109 [planetinfo.record]: population = 43; 11:55:08.109 [planetinfo.record]: productivity = 11352; 11:55:08.109 [planetinfo.record]: name = "Ersoonve"; 11:55:08.109 [planetinfo.record]: inhabitant = "Human Colonial"; 11:55:08.109 [planetinfo.record]: inhabitants = "Human Colonials"; 11:55:08.109 [planetinfo.record]: description = "The world Ersoonve is beset by dreadful earthquakes."; 11:55:08.111 [planetinfo.record]: air_color = 0.408689, 0.844207, 0.802955, 1; 11:55:08.111 [planetinfo.record]: cloud_alpha = 1.000000; 11:55:08.111 [planetinfo.record]: cloud_color = 0.535156, 0.396255, 0.0271759, 1; 11:55:08.111 [planetinfo.record]: cloud_fraction = 0.320000; 11:55:08.111 [planetinfo.record]: land_color = 0.59375, 0.398926, 0.567875, 1; 11:55:08.111 [planetinfo.record]: land_fraction = 0.320000; 11:55:08.111 [planetinfo.record]: polar_cloud_color = 0.74082, 0.620644, 0.30132, 1; 11:55:08.111 [planetinfo.record]: polar_land_color = 0.940625, 0.863464, 0.930377, 1; 11:55:08.111 [planetinfo.record]: polar_sea_color = 0.903538, 0.924414, 0.857611, 1; 11:55:08.111 [planetinfo.record]: sea_color = 0.687581, 0.755859, 0.537369, 1; 11:55:08.111 [planetinfo.record]: rotation_speed = 0.001131 11:55:08.112 [planetinfo.record]: planet zpos = 335170.000000 11:55:08.112 [planetinfo.record]: sun_radius = 87883.930664 11:55:08.112 [planetinfo.record]: sun_vector = -0.432 -0.854 -0.290 11:55:08.112 [planetinfo.record]: sun_distance = 548460 11:55:08.112 [planetinfo.record]: corona_flare = 0.085593 11:55:08.112 [planetinfo.record]: corona_hues = 0.996956 11:55:08.112 [planetinfo.record]: sun_color = 0.475398, 0.535783, 0.707413, 1 11:55:08.112 [planetinfo.record]: corona_shimmer = 0.356503 11:55:08.112 [planetinfo.record]: station_vector = -0.172 0.613 0.771 11:55:08.112 [planetinfo.record]: station = coriolis 11:55:12.867 [PLANETINFO OVER]: Done 11:55:12.868 [PLANETINFO LOGGING]: 1 51 11:55:13.875 [planetinfo.record]: seed = 232 214 154 253 11:55:13.875 [planetinfo.record]: coordinates = 214 95 11:55:13.875 [planetinfo.record]: government = 5; 11:55:13.875 [planetinfo.record]: economy = 7; 11:55:13.875 [planetinfo.record]: techlevel = 5; 11:55:13.875 [planetinfo.record]: population = 33; 11:55:13.875 [planetinfo.record]: productivity = 7128; 11:55:13.875 [planetinfo.record]: name = "Isbeus"; 11:55:13.875 [planetinfo.record]: inhabitant = "Bug-Eyed Lizard"; 11:55:13.875 [planetinfo.record]: inhabitants = "Bug-Eyed Lizards"; 11:55:13.875 [planetinfo.record]: description = "This world is most fabled for Isbeusian Onablo brandy but scourged by killer mountain goats."; 11:55:13.896 [planetinfo.record]: air_color = 0.707385, 0.728767, 0.974285, 1; 11:55:13.896 [planetinfo.record]: cloud_alpha = 1.000000; 11:55:13.896 [planetinfo.record]: cloud_color = 0.71965, 0.766352, 0.925781, 1; 11:55:13.896 [planetinfo.record]: cloud_fraction = 0.410000; 11:55:13.896 [planetinfo.record]: land_color = 0.26355, 0.443411, 0.53125, 1; 11:55:13.896 [planetinfo.record]: land_fraction = 0.440000; 11:55:13.896 [planetinfo.record]: polar_cloud_color = 0.789047, 0.817946, 0.916602, 1; 11:55:13.896 [planetinfo.record]: polar_land_color = 0.827591, 0.907735, 0.946875, 1; 11:55:13.896 [planetinfo.record]: polar_sea_color = 0.876347, 0.926758, 0.898402, 1; 11:55:13.896 [planetinfo.record]: sea_color = 0.573063, 0.732422, 0.642782, 1; 11:55:13.896 [planetinfo.record]: rotation_speed = 0.002257 11:55:13.896 [planetinfo.record]: planet zpos = 635800.000000 11:55:13.896 [planetinfo.record]: sun_radius = 200325.119629 11:55:13.896 [planetinfo.record]: sun_vector = 0.416 0.816 -0.401 11:55:13.896 [planetinfo.record]: sun_distance = 1080860 11:55:13.896 [planetinfo.record]: corona_flare = 0.029013 11:55:13.896 [planetinfo.record]: corona_hues = 0.501114 11:55:13.896 [planetinfo.record]: sun_color = 0.516967, 0.604069, 0.661179, 1 11:55:13.896 [planetinfo.record]: corona_shimmer = 0.289600 11:55:13.897 [planetinfo.record]: station_vector = -0.739 -0.360 0.569 11:55:13.897 [planetinfo.record]: station = coriolis 11:55:18.845 [PLANETINFO OVER]: Done 11:55:18.846 [PLANETINFO LOGGING]: 1 52 11:55:19.872 [planetinfo.record]: seed = 128 57 102 117 11:55:19.872 [planetinfo.record]: coordinates = 57 8 11:55:19.872 [planetinfo.record]: government = 0; 11:55:19.872 [planetinfo.record]: economy = 2; 11:55:19.872 [planetinfo.record]: techlevel = 6; 11:55:19.872 [planetinfo.record]: population = 27; 11:55:19.872 [planetinfo.record]: productivity = 6912; 11:55:19.872 [planetinfo.record]: name = "Latibi"; 11:55:19.872 [planetinfo.record]: inhabitant = "Human Colonial"; 11:55:19.872 [planetinfo.record]: inhabitants = "Human Colonials"; 11:55:19.872 [planetinfo.record]: description = "This planet is a dull place."; 11:55:19.892 [planetinfo.record]: air_color = 0.548243, 0.932083, 0.99835, 1; 11:55:19.892 [planetinfo.record]: cloud_alpha = 1.000000; 11:55:19.892 [planetinfo.record]: cloud_color = 0.6571, 0.998047, 0.245613, 1; 11:55:19.892 [planetinfo.record]: cloud_fraction = 0.060000; 11:55:19.892 [planetinfo.record]: land_color = 0.562031, 0.66, 0.563562, 1; 11:55:19.892 [planetinfo.record]: land_fraction = 0.540000; 11:55:19.892 [planetinfo.record]: polar_cloud_color = 0.746476, 0.949121, 0.501903, 1; 11:55:19.892 [planetinfo.record]: polar_land_color = 0.89934, 0.934, 0.899881, 1; 11:55:19.892 [planetinfo.record]: polar_sea_color = 0.824108, 0.865302, 0.943945, 1; 11:55:19.892 [planetinfo.record]: sea_color = 0.275894, 0.373744, 0.560547, 1; 11:55:19.892 [planetinfo.record]: rotation_speed = 0.002015 11:55:19.892 [planetinfo.record]: planet zpos = 498360.000000 11:55:19.892 [planetinfo.record]: sun_radius = 72881.550598 11:55:19.893 [planetinfo.record]: sun_vector = 0.894 -0.418 -0.164 11:55:19.893 [planetinfo.record]: sun_distance = 955190 11:55:19.893 [planetinfo.record]: corona_flare = 0.095085 11:55:19.893 [planetinfo.record]: corona_hues = 0.975960 11:55:19.893 [planetinfo.record]: sun_color = 0.265184, 0.475795, 0.709445, 1 11:55:19.893 [planetinfo.record]: corona_shimmer = 0.387787 11:55:19.893 [planetinfo.record]: station_vector = 0.978 0.094 0.185 11:55:19.893 [planetinfo.record]: station = coriolis 11:55:25.096 [PLANETINFO OVER]: Done 11:55:25.097 [PLANETINFO LOGGING]: 1 53 11:55:26.109 [planetinfo.record]: seed = 128 146 154 175 11:55:26.109 [planetinfo.record]: coordinates = 146 102 11:55:26.109 [planetinfo.record]: government = 0; 11:55:26.109 [planetinfo.record]: economy = 6; 11:55:26.109 [planetinfo.record]: techlevel = 3; 11:55:26.109 [planetinfo.record]: population = 19; 11:55:26.109 [planetinfo.record]: productivity = 2432; 11:55:26.109 [planetinfo.record]: name = "Ausar"; 11:55:26.109 [planetinfo.record]: inhabitant = "Harmless Fat Insect"; 11:55:26.109 [planetinfo.record]: inhabitants = "Harmless Fat Insects"; 11:55:26.109 [planetinfo.record]: description = "The planet Ausar is an unremarkable dump."; 11:55:26.118 [planetinfo.record]: air_color = 0.717708, 0.568077, 0.900141, 1; 11:55:26.118 [planetinfo.record]: cloud_alpha = 1.000000; 11:55:26.119 [planetinfo.record]: cloud_color = 0.703125, 0.346069, 0.572019, 1; 11:55:26.119 [planetinfo.record]: cloud_fraction = 0.310000; 11:55:26.119 [planetinfo.record]: land_color = 0.66, 0.629063, 0.64719, 1; 11:55:26.119 [planetinfo.record]: land_fraction = 0.410000; 11:55:26.119 [planetinfo.record]: polar_cloud_color = 0.816406, 0.557293, 0.721263, 1; 11:55:26.119 [planetinfo.record]: polar_land_color = 0.934, 0.923055, 0.929468, 1; 11:55:26.119 [planetinfo.record]: polar_sea_color = 0.938727, 0.945508, 0.728521, 1; 11:55:26.119 [planetinfo.record]: sea_color = 0.52929, 0.544922, 0.0447006, 1; 11:55:26.119 [planetinfo.record]: rotation_speed = 0.003149 11:55:26.119 [planetinfo.record]: planet zpos = 952280.000000 11:55:26.119 [planetinfo.record]: sun_radius = 197655.101624 11:55:26.119 [planetinfo.record]: sun_vector = 0.923 -0.360 0.135 11:55:26.119 [planetinfo.record]: sun_distance = 1428420 11:55:26.119 [planetinfo.record]: corona_flare = 0.054997 11:55:26.119 [planetinfo.record]: corona_hues = 0.858765 11:55:26.119 [planetinfo.record]: sun_color = 0.828491, 0.65205, 0.473311, 1 11:55:26.120 [planetinfo.record]: corona_shimmer = 0.380904 11:55:26.120 [planetinfo.record]: station_vector = 0.805 0.585 0.096 11:55:26.120 [planetinfo.record]: station = coriolis 11:55:30.801 [PLANETINFO OVER]: Done 11:55:30.802 [PLANETINFO LOGGING]: 1 54 11:55:31.806 [planetinfo.record]: seed = 232 65 54 212 11:55:31.806 [planetinfo.record]: coordinates = 65 234 11:55:31.806 [planetinfo.record]: government = 5; 11:55:31.806 [planetinfo.record]: economy = 2; 11:55:31.806 [planetinfo.record]: techlevel = 9; 11:55:31.806 [planetinfo.record]: population = 44; 11:55:31.806 [planetinfo.record]: productivity = 25344; 11:55:31.806 [planetinfo.record]: name = "Rave"; 11:55:31.806 [planetinfo.record]: inhabitant = "Human Colonial"; 11:55:31.806 [planetinfo.record]: inhabitants = "Human Colonials"; 11:55:31.806 [planetinfo.record]: description = "Rave is most famous for the Raveian spotted shrew and the Raveian edible arts graduate."; 11:55:31.820 [planetinfo.record]: air_color = 0.448261, 0.663031, 0.883231, 1; 11:55:31.820 [planetinfo.record]: cloud_alpha = 1.000000; 11:55:31.820 [planetinfo.record]: cloud_color = 0.0789948, 0.652344, 0.383586, 1; 11:55:31.820 [planetinfo.record]: cloud_fraction = 0.490000; 11:55:31.820 [planetinfo.record]: land_color = 0.105703, 0.491113, 0.66, 1; 11:55:31.820 [planetinfo.record]: land_fraction = 0.460000; 11:55:31.820 [planetinfo.record]: polar_cloud_color = 0.357642, 0.793555, 0.589221, 1; 11:55:31.820 [planetinfo.record]: polar_land_color = 0.737897, 0.87425, 0.934, 1; 11:55:31.820 [planetinfo.record]: polar_sea_color = 0.924186, 0.936523, 0.885582, 1; 11:55:31.820 [planetinfo.record]: sea_color = 0.601317, 0.634766, 0.496655, 1; 11:55:31.820 [planetinfo.record]: rotation_speed = 0.000497 11:55:31.820 [planetinfo.record]: planet zpos = 351450.000000 11:55:31.820 [planetinfo.record]: sun_radius = 79047.410583 11:55:31.820 [planetinfo.record]: sun_vector = -0.213 0.976 0.045 11:55:31.820 [planetinfo.record]: sun_distance = 781000 11:55:31.820 [planetinfo.record]: corona_flare = 0.022791 11:55:31.820 [planetinfo.record]: corona_hues = 0.834740 11:55:31.821 [planetinfo.record]: sun_color = 0.380303, 0.583663, 0.79628, 1 11:55:31.821 [planetinfo.record]: corona_shimmer = 0.363786 11:55:31.821 [planetinfo.record]: station_vector = 0.897 -0.440 0.045 11:55:31.821 [planetinfo.record]: station = coriolis 11:55:36.027 [PLANETINFO OVER]: Done 11:55:36.028 [PLANETINFO LOGGING]: 1 55 11:55:37.034 [planetinfo.record]: seed = 248 234 186 1 11:55:37.034 [planetinfo.record]: coordinates = 234 22 11:55:37.034 [planetinfo.record]: government = 7; 11:55:37.034 [planetinfo.record]: economy = 6; 11:55:37.034 [planetinfo.record]: techlevel = 7; 11:55:37.034 [planetinfo.record]: population = 42; 11:55:37.034 [planetinfo.record]: productivity = 14784; 11:55:37.034 [planetinfo.record]: name = "Legeara"; 11:55:37.034 [planetinfo.record]: inhabitant = "Large Green Fat Humanoid"; 11:55:37.034 [planetinfo.record]: inhabitants = "Large Green Fat Humanoids"; 11:55:37.034 [planetinfo.record]: description = "This world is a tedious little planet."; 11:55:37.052 [planetinfo.record]: air_color = 0.674136, 0.855445, 0.910547, 1; 11:55:37.052 [planetinfo.record]: cloud_alpha = 1.000000; 11:55:37.052 [planetinfo.record]: cloud_color = 0.640561, 0.734375, 0.602417, 1; 11:55:37.052 [planetinfo.record]: cloud_fraction = 0.080000; 11:55:37.052 [planetinfo.record]: land_color = 0.66, 0.300573, 0.105703, 1; 11:55:37.052 [planetinfo.record]: land_fraction = 0.680000; 11:55:37.052 [planetinfo.record]: polar_cloud_color = 0.764163, 0.830469, 0.737203, 1; 11:55:37.052 [planetinfo.record]: polar_land_color = 0.934, 0.806839, 0.737897, 1; 11:55:37.052 [planetinfo.record]: polar_sea_color = 0.943555, 0.888844, 0.887531, 1; 11:55:37.052 [planetinfo.record]: sea_color = 0.564453, 0.433537, 0.430396, 1; 11:55:37.053 [planetinfo.record]: rotation_speed = 0.001520 11:55:37.053 [planetinfo.record]: planet zpos = 429780.000000 11:55:37.053 [planetinfo.record]: sun_radius = 91251.471863 11:55:37.053 [planetinfo.record]: sun_vector = -0.277 0.051 -0.959 11:55:37.053 [planetinfo.record]: sun_distance = 727320 11:55:37.053 [planetinfo.record]: corona_flare = 0.058873 11:55:37.056 [planetinfo.record]: corona_hues = 0.564865 11:55:37.056 [planetinfo.record]: sun_color = 0.704602, 0.513378, 0.361652, 1 11:55:37.056 [planetinfo.record]: corona_shimmer = 0.439349 11:55:37.057 [planetinfo.record]: station_vector = -0.280 -0.500 0.819 11:55:37.057 [planetinfo.record]: station = coriolis 11:55:42.189 [PLANETINFO OVER]: Done 11:55:42.190 [PLANETINFO LOGGING]: 1 56 11:55:43.195 [planetinfo.record]: seed = 176 244 166 231 11:55:43.195 [planetinfo.record]: coordinates = 244 239 11:55:43.195 [planetinfo.record]: government = 6; 11:55:43.195 [planetinfo.record]: economy = 7; 11:55:43.195 [planetinfo.record]: techlevel = 3; 11:55:43.195 [planetinfo.record]: population = 26; 11:55:43.195 [planetinfo.record]: productivity = 6240; 11:55:43.195 [planetinfo.record]: name = "Soinuste"; 11:55:43.195 [planetinfo.record]: inhabitant = "Fierce Bony Feline"; 11:55:43.195 [planetinfo.record]: inhabitants = "Fierce Bony Felines"; 11:55:43.195 [planetinfo.record]: description = "This world is a tedious place."; 11:55:43.223 [planetinfo.record]: air_color = 0.600392, 0.869029, 0.977537, 1; 11:55:43.223 [planetinfo.record]: cloud_alpha = 1.000000; 11:55:43.223 [planetinfo.record]: cloud_color = 0.47828, 0.935547, 0.412956, 1; 11:55:43.224 [planetinfo.record]: cloud_fraction = 0.350000; 11:55:43.224 [planetinfo.record]: land_color = 0.122776, 0.613281, 0.0527039, 1; 11:55:43.224 [planetinfo.record]: land_fraction = 0.520000; 11:55:43.224 [planetinfo.record]: polar_cloud_color = 0.639649, 0.920996, 0.599457, 1; 11:55:43.224 [planetinfo.record]: polar_land_color = 0.750983, 0.938672, 0.724171, 1; 11:55:43.224 [planetinfo.record]: polar_sea_color = 0.948047, 0.895923, 0.904474, 1; 11:55:43.224 [planetinfo.record]: sea_color = 0.519531, 0.405275, 0.42402, 1; 11:55:43.224 [planetinfo.record]: rotation_speed = 0.001470 11:55:43.224 [planetinfo.record]: planet zpos = 582240.000000 11:55:43.224 [planetinfo.record]: sun_radius = 139099.649658 11:55:43.224 [planetinfo.record]: sun_vector = -0.051 -0.538 -0.841 11:55:43.224 [planetinfo.record]: sun_distance = 873360 11:55:43.224 [planetinfo.record]: corona_flare = 0.074060 11:55:43.224 [planetinfo.record]: corona_hues = 0.503036 11:55:43.225 [planetinfo.record]: sun_color = 0.544993, 0.571867, 0.742203, 1 11:55:43.225 [planetinfo.record]: corona_shimmer = 0.243915 11:55:43.226 [planetinfo.record]: station_vector = -0.611 0.351 0.710 11:55:43.226 [planetinfo.record]: station = coriolis 11:55:48.128 [PLANETINFO OVER]: Done 11:55:48.129 [PLANETINFO LOGGING]: 1 57 11:55:49.135 [planetinfo.record]: seed = 80 92 250 208 11:55:49.135 [planetinfo.record]: coordinates = 92 168 11:55:49.135 [planetinfo.record]: government = 2; 11:55:49.135 [planetinfo.record]: economy = 0; 11:55:49.135 [planetinfo.record]: techlevel = 8; 11:55:49.135 [planetinfo.record]: population = 35; 11:55:49.135 [planetinfo.record]: productivity = 16800; 11:55:49.135 [planetinfo.record]: name = "Erlage"; 11:55:49.135 [planetinfo.record]: inhabitant = "Fat Bird"; 11:55:49.135 [planetinfo.record]: inhabitants = "Fat Birds"; 11:55:49.135 [planetinfo.record]: description = "Erlage is cursed by vicious mountain bisons."; 11:55:49.160 [planetinfo.record]: air_color = 0.669864, 0.796689, 0.958676, 1; 11:55:49.160 [planetinfo.record]: cloud_alpha = 1.000000; 11:55:49.160 [planetinfo.record]: cloud_color = 0.611115, 0.878906, 0.797313, 1; 11:55:49.160 [planetinfo.record]: cloud_fraction = 0.500000; 11:55:49.160 [planetinfo.record]: land_color = 0.451172, 0.66, 0.66, 1; 11:55:49.160 [planetinfo.record]: land_fraction = 0.690000; 11:55:49.160 [planetinfo.record]: polar_cloud_color = 0.724977, 0.895508, 0.843549, 1; 11:55:49.160 [planetinfo.record]: polar_land_color = 0.860119, 0.934, 0.934, 1; 11:55:49.160 [planetinfo.record]: polar_sea_color = 0.936719, 0.904839, 0.87671, 1; 11:55:49.160 [planetinfo.record]: sea_color = 0.632812, 0.546666, 0.470654, 1; 11:55:49.161 [planetinfo.record]: rotation_speed = 0.002531 11:55:49.161 [planetinfo.record]: planet zpos = 378040.000000 11:55:49.161 [planetinfo.record]: sun_radius = 75684.054565 11:55:49.161 [planetinfo.record]: sun_vector = -0.552 0.227 -0.802 11:55:49.161 [planetinfo.record]: sun_distance = 668840 11:55:49.161 [planetinfo.record]: corona_flare = 0.053252 11:55:49.161 [planetinfo.record]: corona_hues = 0.999748 11:55:49.161 [planetinfo.record]: sun_color = 0.638871, 0.811388, 0.823343, 1 11:55:49.161 [planetinfo.record]: corona_shimmer = 0.317800 11:55:49.161 [planetinfo.record]: station_vector = 0.028 0.664 0.747 11:55:49.161 [planetinfo.record]: station = coriolis 11:55:54.520 [PLANETINFO OVER]: Done 11:55:54.521 [PLANETINFO LOGGING]: 1 58 11:55:55.544 [planetinfo.record]: seed = 216 169 182 130 11:55:55.544 [planetinfo.record]: coordinates = 169 3 11:55:55.544 [planetinfo.record]: government = 3; 11:55:55.544 [planetinfo.record]: economy = 3; 11:55:55.544 [planetinfo.record]: techlevel = 7; 11:55:55.544 [planetinfo.record]: population = 35; 11:55:55.544 [planetinfo.record]: productivity = 13720; 11:55:55.544 [planetinfo.record]: name = "Xeate"; 11:55:55.544 [planetinfo.record]: inhabitant = "Large Black Horned Bird"; 11:55:55.544 [planetinfo.record]: inhabitants = "Large Black Horned Birds"; 11:55:55.544 [planetinfo.record]: description = "Xeate is an unremarkable dump."; 11:55:55.568 [planetinfo.record]: air_color = 0.636616, 0.902282, 0.919652, 1; 11:55:55.568 [planetinfo.record]: cloud_alpha = 1.000000; 11:55:55.568 [planetinfo.record]: cloud_color = 0.713484, 0.761719, 0.514755, 1; 11:55:55.568 [planetinfo.record]: cloud_fraction = 0.310000; 11:55:55.568 [planetinfo.record]: land_color = 0.501953, 0.46522, 0.0745087, 1; 11:55:55.568 [planetinfo.record]: land_fraction = 0.680000; 11:55:55.568 [planetinfo.record]: polar_cloud_color = 0.809419, 0.842773, 0.671997, 1; 11:55:55.568 [planetinfo.record]: polar_land_color = 0.949805, 0.932428, 0.7476, 1; 11:55:55.568 [planetinfo.record]: polar_sea_color = 0.928564, 0.936328, 0.884025, 1; 11:55:55.568 [planetinfo.record]: sea_color = 0.615601, 0.636719, 0.494452, 1; 11:55:55.568 [planetinfo.record]: rotation_speed = 0.004850 11:55:55.568 [planetinfo.record]: planet zpos = 384670.000000 11:55:55.568 [planetinfo.record]: sun_radius = 90271.221619 11:55:55.568 [planetinfo.record]: sun_vector = 0.103 0.966 -0.236 11:55:55.568 [planetinfo.record]: sun_distance = 839280 11:55:55.568 [planetinfo.record]: corona_flare = 0.016684 11:55:55.568 [planetinfo.record]: corona_hues = 0.774132 11:55:55.568 [planetinfo.record]: sun_color = 0.776608, 0.766257, 0.719728, 1 11:55:55.568 [planetinfo.record]: corona_shimmer = 0.322187 11:55:55.569 [planetinfo.record]: station_vector = -0.048 -0.936 0.348 11:55:55.569 [planetinfo.record]: station = coriolis 11:56:00.657 [PLANETINFO OVER]: Done 11:56:00.658 [PLANETINFO LOGGING]: 1 59 11:56:01.681 [planetinfo.record]: seed = 136 14 90 154 11:56:01.681 [planetinfo.record]: coordinates = 14 92 11:56:01.681 [planetinfo.record]: government = 1; 11:56:01.681 [planetinfo.record]: economy = 6; 11:56:01.681 [planetinfo.record]: techlevel = 4; 11:56:01.681 [planetinfo.record]: population = 24; 11:56:01.681 [planetinfo.record]: productivity = 3840; 11:56:01.681 [planetinfo.record]: name = "Qucedi"; 11:56:01.681 [planetinfo.record]: inhabitant = "Human Colonial"; 11:56:01.681 [planetinfo.record]: inhabitants = "Human Colonials"; 11:56:01.681 [planetinfo.record]: description = "This planet is mildly fabled for its inhabitants’ eccentric shyness but ravaged by killer disease."; 11:56:01.684 [planetinfo.record]: air_color = 0.652287, 0.856056, 0.935262, 1; 11:56:01.685 [planetinfo.record]: cloud_alpha = 1.000000; 11:56:01.685 [planetinfo.record]: cloud_color = 0.596106, 0.808594, 0.559067, 1; 11:56:01.685 [planetinfo.record]: cloud_fraction = 0.330000; 11:56:01.685 [planetinfo.record]: land_color = 0.66, 0.244922, 0.410305, 1; 11:56:01.685 [planetinfo.record]: land_fraction = 0.480000; 11:56:01.685 [planetinfo.record]: polar_cloud_color = 0.721984, 0.863867, 0.697252, 1; 11:56:01.685 [planetinfo.record]: polar_land_color = 0.934, 0.78715, 0.845661, 1; 11:56:01.685 [planetinfo.record]: polar_sea_color = 0.93132, 0.937695, 0.88669, 1; 11:56:01.685 [planetinfo.record]: sea_color = 0.606102, 0.623047, 0.487485, 1; 11:56:01.685 [planetinfo.record]: rotation_speed = 0.000931 11:56:01.685 [planetinfo.record]: planet zpos = 808500.000000 11:56:01.685 [planetinfo.record]: sun_radius = 146795.584106 11:56:01.685 [planetinfo.record]: sun_vector = 0.236 -0.095 -0.967 11:56:01.686 [planetinfo.record]: sun_distance = 1239700 11:56:01.686 [planetinfo.record]: corona_flare = 0.026398 11:56:01.686 [planetinfo.record]: corona_hues = 0.626625 11:56:01.686 [planetinfo.record]: sun_color = 0.797321, 0.503991, 0.485259, 1 11:56:01.686 [planetinfo.record]: corona_shimmer = 0.371145 11:56:01.686 [planetinfo.record]: station_vector = -0.487 -0.256 0.835 11:56:01.686 [planetinfo.record]: station = coriolis 11:56:05.937 [PLANETINFO OVER]: Done 11:56:05.938 [PLANETINFO LOGGING]: 1 60 11:56:06.942 [planetinfo.record]: seed = 96 77 102 0 11:56:06.942 [planetinfo.record]: coordinates = 77 173 11:56:06.942 [planetinfo.record]: government = 4; 11:56:06.942 [planetinfo.record]: economy = 5; 11:56:06.942 [planetinfo.record]: techlevel = 5; 11:56:06.942 [planetinfo.record]: population = 30; 11:56:06.942 [planetinfo.record]: productivity = 9600; 11:56:06.942 [planetinfo.record]: name = "Anesce"; 11:56:06.942 [planetinfo.record]: inhabitant = "Human Colonial"; 11:56:06.942 [planetinfo.record]: inhabitants = "Human Colonials"; 11:56:06.942 [planetinfo.record]: description = "The planet Anesce is well known for its inhabitants’ unusual silliness but cursed by deadly civil war."; 11:56:06.964 [planetinfo.record]: air_color = 0.481479, 0.653818, 0.860467, 1; 11:56:06.964 [planetinfo.record]: cloud_alpha = 1.000000; 11:56:06.964 [planetinfo.record]: cloud_color = 0.161964, 0.583984, 0.435618, 1; 11:56:06.964 [planetinfo.record]: cloud_fraction = 0.460000; 11:56:06.964 [planetinfo.record]: land_color = 0.291328, 0.66, 0.340292, 1; 11:56:06.964 [planetinfo.record]: land_fraction = 0.540000; 11:56:06.964 [planetinfo.record]: polar_cloud_color = 0.41827, 0.762793, 0.641672, 1; 11:56:06.964 [planetinfo.record]: polar_land_color = 0.803568, 0.934, 0.820891, 1; 11:56:06.964 [planetinfo.record]: polar_sea_color = 0.945508, 0.919951, 0.897401, 1; 11:56:06.964 [planetinfo.record]: sea_color = 0.544922, 0.486006, 0.434022, 1; 11:56:06.964 [planetinfo.record]: rotation_speed = 0.000715 11:56:06.964 [planetinfo.record]: planet zpos = 347160.000000 11:56:06.964 [planetinfo.record]: sun_radius = 56479.627228 11:56:06.964 [planetinfo.record]: sun_vector = -0.821 0.444 0.359 11:56:06.964 [planetinfo.record]: sun_distance = 665390 11:56:06.964 [planetinfo.record]: corona_flare = 0.047188 11:56:06.964 [planetinfo.record]: corona_hues = 0.593430 11:56:06.964 [planetinfo.record]: sun_color = 0.634651, 0.74291, 0.765866, 1 11:56:06.964 [planetinfo.record]: corona_shimmer = 0.613495 11:56:06.965 [planetinfo.record]: station_vector = 0.817 0.277 0.505 11:56:06.965 [planetinfo.record]: station = coriolis 11:56:11.761 [PLANETINFO OVER]: Done 11:56:11.761 [PLANETINFO LOGGING]: 1 61 11:56:12.768 [planetinfo.record]: seed = 160 69 218 138 11:56:12.768 [planetinfo.record]: coordinates = 69 73 11:56:12.768 [planetinfo.record]: government = 4; 11:56:12.768 [planetinfo.record]: economy = 1; 11:56:12.768 [planetinfo.record]: techlevel = 9; 11:56:12.768 [planetinfo.record]: population = 42; 11:56:12.768 [planetinfo.record]: productivity = 24192; 11:56:12.768 [planetinfo.record]: name = "Arorar"; 11:56:12.769 [planetinfo.record]: inhabitant = "Small Black Fat Feline"; 11:56:12.769 [planetinfo.record]: inhabitants = "Small Black Fat Felines"; 11:56:12.769 [planetinfo.record]: description = "This world is noted for its exciting sit coms."; 11:56:12.781 [planetinfo.record]: air_color = 0.560213, 0.94827, 0.869111, 1; 11:56:12.781 [planetinfo.record]: cloud_alpha = 1.000000; 11:56:12.781 [planetinfo.record]: cloud_color = 0.847656, 0.526965, 0.31456, 1; 11:56:12.781 [planetinfo.record]: cloud_fraction = 0.500000; 11:56:12.781 [planetinfo.record]: land_color = 0.494361, 0.318375, 0.626953, 1; 11:56:12.781 [planetinfo.record]: land_fraction = 0.400000; 11:56:12.781 [planetinfo.record]: polar_cloud_color = 0.881445, 0.673024, 0.534979, 1; 11:56:12.781 [planetinfo.record]: polar_land_color = 0.887748, 0.821972, 0.937305, 1; 11:56:12.781 [planetinfo.record]: polar_sea_color = 0.938111, 0.939648, 0.890464, 1; 11:56:12.781 [planetinfo.record]: sea_color = 0.599567, 0.603516, 0.477155, 1; 11:56:12.781 [planetinfo.record]: rotation_speed = 0.001870 11:56:12.781 [planetinfo.record]: planet zpos = 707850.000000 11:56:12.782 [planetinfo.record]: sun_radius = 155502.706146 11:56:12.782 [planetinfo.record]: sun_vector = 0.695 0.456 0.556 11:56:12.782 [planetinfo.record]: sun_distance = 1252350 11:56:12.782 [planetinfo.record]: corona_flare = 0.078989 11:56:12.782 [planetinfo.record]: corona_hues = 0.892128 11:56:12.782 [planetinfo.record]: sun_color = 0.676218, 0.578106, 0.234911, 1 11:56:12.782 [planetinfo.record]: corona_shimmer = 0.547036 11:56:12.782 [planetinfo.record]: station_vector = -0.108 -0.777 0.620 11:56:12.782 [planetinfo.record]: station = coriolis 11:56:17.734 [PLANETINFO OVER]: Done 11:56:17.735 [PLANETINFO LOGGING]: 1 62 11:56:18.738 [planetinfo.record]: seed = 72 143 182 147 11:56:18.738 [planetinfo.record]: coordinates = 143 234 11:56:18.738 [planetinfo.record]: government = 1; 11:56:18.738 [planetinfo.record]: economy = 2; 11:56:18.738 [planetinfo.record]: techlevel = 9; 11:56:18.739 [planetinfo.record]: population = 40; 11:56:18.739 [planetinfo.record]: productivity = 12800; 11:56:18.739 [planetinfo.record]: name = "Bedierat"; 11:56:18.739 [planetinfo.record]: inhabitant = "Black Furry Rodent"; 11:56:18.739 [planetinfo.record]: inhabitants = "Black Furry Rodents"; 11:56:18.739 [planetinfo.record]: description = "Bedierat is well known for the Bedieratian spotted leopard but ravaged by frequent earthquakes."; 11:56:18.764 [planetinfo.record]: air_color = 0.774094, 0.759451, 0.952172, 1; 11:56:18.764 [planetinfo.record]: cloud_alpha = 1.000000; 11:56:18.764 [planetinfo.record]: cloud_color = 0.854287, 0.852661, 0.859375, 1; 11:56:18.764 [planetinfo.record]: cloud_fraction = 0.120000; 11:56:18.764 [planetinfo.record]: land_color = 0.372738, 0.274429, 0.644531, 1; 11:56:18.764 [planetinfo.record]: land_fraction = 0.590000; 11:56:18.764 [planetinfo.record]: polar_cloud_color = 0.883438, 0.882389, 0.886719, 1; 11:56:18.764 [planetinfo.record]: polar_land_color = 0.836919, 0.801245, 0.935547, 1; 11:56:18.764 [planetinfo.record]: polar_sea_color = 0.92197, 0.929688, 0.86795, 1; 11:56:18.764 [planetinfo.record]: sea_color = 0.679779, 0.703125, 0.516357, 1; 11:56:18.764 [planetinfo.record]: rotation_speed = 0.004230 11:56:18.764 [planetinfo.record]: planet zpos = 447240.000000 11:56:18.764 [planetinfo.record]: sun_radius = 98646.415253 11:56:18.764 [planetinfo.record]: sun_vector = 0.514 -0.438 0.738 11:56:18.764 [planetinfo.record]: sun_distance = 782670 11:56:18.764 [planetinfo.record]: corona_flare = 0.058316 11:56:18.764 [planetinfo.record]: corona_hues = 0.631584 11:56:18.764 [planetinfo.record]: sun_color = 0.782715, 0.749601, 0.647251, 1 11:56:18.765 [planetinfo.record]: corona_shimmer = 0.276869 11:56:18.765 [planetinfo.record]: station_vector = -0.599 0.698 0.393 11:56:18.765 [planetinfo.record]: station = coriolis 11:56:24.624 [PLANETINFO OVER]: Done 11:56:24.625 [PLANETINFO LOGGING]: 1 63 11:56:25.630 [planetinfo.record]: seed = 152 209 122 15 11:56:25.630 [planetinfo.record]: coordinates = 209 48 11:56:25.630 [planetinfo.record]: government = 3; 11:56:25.630 [planetinfo.record]: economy = 0; 11:56:25.630 [planetinfo.record]: techlevel = 10; 11:56:25.630 [planetinfo.record]: population = 44; 11:56:25.630 [planetinfo.record]: productivity = 24640; 11:56:25.630 [planetinfo.record]: name = "Aatenbe"; 11:56:25.630 [planetinfo.record]: inhabitant = "Human Colonial"; 11:56:25.630 [planetinfo.record]: inhabitants = "Human Colonials"; 11:56:25.630 [planetinfo.record]: description = "This world is most notable for its fabulous vicious Soa’ gargle blasters but scourged by unpredictable earthquakes."; 11:56:25.638 [planetinfo.record]: air_color = 0.739353, 0.527466, 0.940465, 1; 11:56:25.638 [planetinfo.record]: cloud_alpha = 1.000000; 11:56:25.638 [planetinfo.record]: cloud_color = 0.824219, 0.231812, 0.495618, 1; 11:56:25.638 [planetinfo.record]: cloud_fraction = 0.320000; 11:56:25.638 [planetinfo.record]: land_color = 0.556641, 0.43705, 0.509925, 1; 11:56:25.638 [planetinfo.record]: land_fraction = 0.560000; 11:56:25.638 [planetinfo.record]: polar_cloud_color = 0.870898, 0.479675, 0.653891, 1; 11:56:25.638 [planetinfo.record]: polar_land_color = 0.944336, 0.893615, 0.924523, 1; 11:56:25.638 [planetinfo.record]: polar_sea_color = 0.921032, 0.938086, 0.890632, 1; 11:56:25.638 [planetinfo.record]: sea_color = 0.574118, 0.619141, 0.493861, 1; 11:56:25.638 [planetinfo.record]: rotation_speed = 0.000272 11:56:25.638 [planetinfo.record]: planet zpos = 1029750.000000 11:56:25.638 [planetinfo.record]: sun_radius = 215611.238861 11:56:25.639 [planetinfo.record]: sun_vector = 0.268 0.894 -0.359 11:56:25.639 [planetinfo.record]: sun_distance = 1510300 11:56:25.639 [planetinfo.record]: corona_flare = 0.073473 11:56:25.639 [planetinfo.record]: corona_hues = 0.936584 11:56:25.639 [planetinfo.record]: sun_color = 0.203634, 0.312301, 0.747818, 1 11:56:25.639 [planetinfo.record]: corona_shimmer = 0.270187 11:56:25.639 [planetinfo.record]: station_vector = -0.348 -0.936 0.058 11:56:25.639 [planetinfo.record]: station = coriolis 11:56:30.052 [PLANETINFO OVER]: Done 11:56:30.053 [PLANETINFO LOGGING]: 1 64 11:56:31.069 [planetinfo.record]: seed = 144 19 166 23 11:56:31.069 [planetinfo.record]: coordinates = 19 242 11:56:31.069 [planetinfo.record]: government = 2; 11:56:31.069 [planetinfo.record]: economy = 2; 11:56:31.069 [planetinfo.record]: techlevel = 9; 11:56:31.069 [planetinfo.record]: population = 41; 11:56:31.069 [planetinfo.record]: productivity = 15744; 11:56:31.069 [planetinfo.record]: name = "Tiises"; 11:56:31.069 [planetinfo.record]: inhabitant = "Green Bug-Eyed Bird"; 11:56:31.069 [planetinfo.record]: inhabitants = "Green Bug-Eyed Birds"; 11:56:31.069 [planetinfo.record]: description = "Tiises is most famous for its vast rain forests and the Tiisesian tree grub."; 11:56:31.086 [planetinfo.record]: air_color = 0.605357, 0.889738, 0.96518, 1; 11:56:31.086 [planetinfo.record]: cloud_alpha = 1.000000; 11:56:31.086 [planetinfo.record]: cloud_color = 0.599415, 0.898438, 0.431671, 1; 11:56:31.086 [planetinfo.record]: cloud_fraction = 0.380000; 11:56:31.086 [planetinfo.record]: land_color = 0.604611, 0.66, 0.337734, 1; 11:56:31.086 [planetinfo.record]: land_fraction = 0.660000; 11:56:31.086 [planetinfo.record]: polar_cloud_color = 0.716189, 0.904297, 0.610665, 1; 11:56:31.086 [planetinfo.record]: polar_land_color = 0.914404, 0.934, 0.819986, 1; 11:56:31.086 [planetinfo.record]: polar_sea_color = 0.905088, 0.880442, 0.949023, 1; 11:56:31.086 [planetinfo.record]: sea_color = 0.415367, 0.362411, 0.509766, 1; 11:56:31.086 [planetinfo.record]: rotation_speed = 0.000091 11:56:31.086 [planetinfo.record]: planet zpos = 555240.000000 11:56:31.086 [planetinfo.record]: sun_radius = 151953.345947 11:56:31.086 [planetinfo.record]: sun_vector = -0.103 0.993 -0.051 11:56:31.086 [planetinfo.record]: sun_distance = 971670 11:56:31.086 [planetinfo.record]: corona_flare = 0.041432 11:56:31.086 [planetinfo.record]: corona_hues = 0.880081 11:56:31.086 [planetinfo.record]: sun_color = 0.66701, 0.552734, 0.528496, 1 11:56:31.087 [planetinfo.record]: corona_shimmer = 0.276909 11:56:31.088 [planetinfo.record]: station_vector = -0.477 0.848 0.231 11:56:31.088 [planetinfo.record]: station = coriolis 11:56:35.354 [PLANETINFO OVER]: Done 11:56:35.355 [PLANETINFO LOGGING]: 1 65 11:56:36.360 [planetinfo.record]: seed = 112 126 58 229 11:56:36.360 [planetinfo.record]: coordinates = 126 73 11:56:36.360 [planetinfo.record]: government = 6; 11:56:36.360 [planetinfo.record]: economy = 1; 11:56:36.360 [planetinfo.record]: techlevel = 11; 11:56:36.360 [planetinfo.record]: population = 52; 11:56:36.360 [planetinfo.record]: productivity = 37440; 11:56:36.360 [planetinfo.record]: name = "Ceiner"; 11:56:36.360 [planetinfo.record]: inhabitant = "Human Colonial"; 11:56:36.360 [planetinfo.record]: inhabitants = "Human Colonials"; 11:56:36.360 [planetinfo.record]: description = "The planet Ceiner is a boring planet."; 11:56:36.388 [planetinfo.record]: air_color = 0.589832, 0.934611, 0.888499, 1; 11:56:36.388 [planetinfo.record]: cloud_alpha = 1.000000; 11:56:36.388 [planetinfo.record]: cloud_color = 0.806641, 0.646632, 0.397018, 1; 11:56:36.388 [planetinfo.record]: cloud_fraction = 0.330000; 11:56:36.388 [planetinfo.record]: land_color = 0.216888, 0.539062, 0.493757, 1; 11:56:36.388 [planetinfo.record]: land_fraction = 0.280000; 11:56:36.388 [planetinfo.record]: polar_cloud_color = 0.862988, 0.755997, 0.589091, 1; 11:56:36.388 [planetinfo.record]: polar_land_color = 0.804734, 0.946094, 0.926215, 1; 11:56:36.388 [planetinfo.record]: polar_sea_color = 0.94043, 0.899504, 0.88349, 1; 11:56:36.388 [planetinfo.record]: sea_color = 0.595703, 0.492008, 0.451431, 1; 11:56:36.388 [planetinfo.record]: rotation_speed = 0.001295 11:56:36.388 [planetinfo.record]: planet zpos = 506640.000000 11:56:36.388 [planetinfo.record]: sun_radius = 103117.402344 11:56:36.388 [planetinfo.record]: sun_vector = -0.005 -0.996 0.085 11:56:36.388 [planetinfo.record]: sun_distance = 971060 11:56:36.388 [planetinfo.record]: corona_flare = 0.054340 11:56:36.388 [planetinfo.record]: corona_hues = 0.787300 11:56:36.388 [planetinfo.record]: sun_color = 0.230725, 0.498163, 0.767358, 1 11:56:36.388 [planetinfo.record]: corona_shimmer = 0.326258 11:56:36.388 [planetinfo.record]: station_vector = 0.458 -0.048 0.888 11:56:36.388 [planetinfo.record]: station = dodecahedron 11:56:40.617 [PLANETINFO OVER]: Done 11:56:40.618 [PLANETINFO LOGGING]: 1 66 11:56:41.639 [planetinfo.record]: seed = 56 162 54 95 11:56:41.639 [planetinfo.record]: coordinates = 162 16 11:56:41.639 [planetinfo.record]: government = 7; 11:56:41.639 [planetinfo.record]: economy = 0; 11:56:41.639 [planetinfo.record]: techlevel = 13; 11:56:41.639 [planetinfo.record]: population = 60; 11:56:41.639 [planetinfo.record]: productivity = 52800; 11:56:41.639 [planetinfo.record]: name = "Onatbeza"; 11:56:41.639 [planetinfo.record]: inhabitant = "Human Colonial"; 11:56:41.639 [planetinfo.record]: inhabitants = "Human Colonials"; 11:56:41.639 [planetinfo.record]: description = "Onatbeza is reasonably notable for its exotic cuisine but plagued by vicious disease."; 11:56:41.652 [planetinfo.record]: air_color = 0.570527, 0.554512, 0.859166, 1; 11:56:41.652 [planetinfo.record]: cloud_alpha = 1.000000; 11:56:41.652 [planetinfo.record]: cloud_color = 0.356778, 0.310432, 0.580078, 1; 11:56:41.652 [planetinfo.record]: cloud_fraction = 0.400000; 11:56:41.652 [planetinfo.record]: land_color = 0.66, 0.39685, 0.141797, 1; 11:56:41.652 [planetinfo.record]: land_fraction = 0.250000; 11:56:41.652 [planetinfo.record]: polar_cloud_color = 0.577935, 0.539934, 0.761035, 1; 11:56:41.652 [planetinfo.record]: polar_land_color = 0.934, 0.840901, 0.750666, 1; 11:56:41.652 [planetinfo.record]: polar_sea_color = 0.946289, 0.884004, 0.935097, 1; 11:56:41.652 [planetinfo.record]: sea_color = 0.537109, 0.395699, 0.5117, 1; 11:56:41.652 [planetinfo.record]: rotation_speed = 0.003583 11:56:41.652 [planetinfo.record]: planet zpos = 818160.000000 11:56:41.652 [planetinfo.record]: sun_radius = 220680.940857 11:56:41.652 [planetinfo.record]: sun_vector = -0.279 -0.709 -0.648 11:56:41.652 [planetinfo.record]: sun_distance = 1431780 11:56:41.652 [planetinfo.record]: corona_flare = 0.082150 11:56:41.652 [planetinfo.record]: corona_hues = 0.771065 11:56:41.652 [planetinfo.record]: sun_color = 0.258726, 0.510633, 0.846344, 1 11:56:41.652 [planetinfo.record]: corona_shimmer = 0.585634 11:56:41.652 [planetinfo.record]: station_vector = -0.731 0.656 0.188 11:56:41.652 [planetinfo.record]: station = dodecahedron 11:56:46.541 [PLANETINFO OVER]: Done 11:56:46.542 [PLANETINFO LOGGING]: 1 67 11:56:47.553 [planetinfo.record]: seed = 40 132 26 169 11:56:47.553 [planetinfo.record]: coordinates = 132 19 11:56:47.553 [planetinfo.record]: government = 5; 11:56:47.553 [planetinfo.record]: economy = 3; 11:56:47.553 [planetinfo.record]: techlevel = 7; 11:56:47.553 [planetinfo.record]: population = 37; 11:56:47.553 [planetinfo.record]: productivity = 18648; 11:56:47.553 [planetinfo.record]: name = "Esdi"; 11:56:47.553 [planetinfo.record]: inhabitant = "Human Colonial"; 11:56:47.553 [planetinfo.record]: inhabitants = "Human Colonials"; 11:56:47.553 [planetinfo.record]: description = "Esdi is well known for its inhabitants’ ancient mating traditions but ravaged by unpredictable solar activity."; 11:56:47.557 [planetinfo.record]: air_color = 0.508932, 0.639949, 0.842906, 1; 11:56:47.557 [planetinfo.record]: cloud_alpha = 1.000000; 11:56:47.557 [planetinfo.record]: cloud_color = 0.217896, 0.53125, 0.47984, 1; 11:56:47.557 [planetinfo.record]: cloud_fraction = 0.590000; 11:56:47.557 [planetinfo.record]: land_color = 0.451555, 0.66, 0.322266, 1; 11:56:47.557 [planetinfo.record]: land_fraction = 0.690000; 11:56:47.557 [planetinfo.record]: polar_cloud_color = 0.466605, 0.739062, 0.694363, 1; 11:56:47.557 [planetinfo.record]: polar_land_color = 0.860255, 0.934, 0.814514, 1; 11:56:47.557 [planetinfo.record]: polar_sea_color = 0.888393, 0.930664, 0.878132, 1; 11:56:47.557 [planetinfo.record]: sea_color = 0.567388, 0.693359, 0.536812, 1; 11:56:47.558 [planetinfo.record]: rotation_speed = 0.004706 11:56:47.558 [planetinfo.record]: planet zpos = 577720.000000 11:56:47.558 [planetinfo.record]: sun_radius = 158289.266357 11:56:47.558 [planetinfo.record]: sun_vector = 0.782 -0.428 0.453 11:56:47.558 [planetinfo.record]: sun_distance = 945360 11:56:47.558 [planetinfo.record]: corona_flare = 0.054033 11:56:47.558 [planetinfo.record]: corona_hues = 0.868477 11:56:47.558 [planetinfo.record]: sun_color = 0.738037, 0.687142, 0.62295, 1 11:56:47.558 [planetinfo.record]: corona_shimmer = 0.266236 11:56:47.558 [planetinfo.record]: station_vector = 0.458 -0.886 0.070 11:56:47.558 [planetinfo.record]: station = coriolis 11:56:52.786 [PLANETINFO OVER]: Done 11:56:52.787 [PLANETINFO LOGGING]: 1 68 11:56:53.812 [planetinfo.record]: seed = 64 87 102 5 11:56:53.812 [planetinfo.record]: coordinates = 87 109 11:56:53.812 [planetinfo.record]: government = 0; 11:56:53.812 [planetinfo.record]: economy = 7; 11:56:53.812 [planetinfo.record]: techlevel = 3; 11:56:53.812 [planetinfo.record]: population = 20; 11:56:53.812 [planetinfo.record]: productivity = 1920; 11:56:53.812 [planetinfo.record]: name = "Cearso"; 11:56:53.812 [planetinfo.record]: inhabitant = "Human Colonial"; 11:56:53.812 [planetinfo.record]: inhabitants = "Human Colonials"; 11:56:53.812 [planetinfo.record]: description = "The planet Cearso is very famous for its unusual casinos but beset by lethal disease."; 11:56:53.821 [planetinfo.record]: air_color = 0.582408, 0.921579, 0.972984, 1; 11:56:53.821 [planetinfo.record]: cloud_alpha = 1.000000; 11:56:53.821 [planetinfo.record]: cloud_color = 0.69512, 0.921875, 0.363708, 1; 11:56:53.821 [planetinfo.record]: cloud_fraction = 0.130000; 11:56:53.827 [planetinfo.record]: land_color = 0.498787, 0.211406, 0.66, 1; 11:56:53.828 [planetinfo.record]: land_fraction = 0.680000; 11:56:53.828 [planetinfo.record]: polar_cloud_color = 0.774203, 0.914844, 0.56865, 1; 11:56:53.828 [planetinfo.record]: polar_land_color = 0.876965, 0.775293, 0.934, 1; 11:56:53.828 [planetinfo.record]: polar_sea_color = 0.887464, 0.932031, 0.882426, 1; 11:56:53.828 [planetinfo.record]: sea_color = 0.549684, 0.679688, 0.534988, 1; 11:56:53.828 [planetinfo.record]: rotation_speed = 0.004468 11:56:53.828 [planetinfo.record]: planet zpos = 418300.000000 11:56:53.828 [planetinfo.record]: sun_radius = 128306.708221 11:56:53.828 [planetinfo.record]: sun_vector = -0.173 -0.985 -0.017 11:56:53.828 [planetinfo.record]: sun_distance = 711110 11:56:53.828 [planetinfo.record]: corona_flare = 0.087578 11:56:53.828 [planetinfo.record]: corona_hues = 0.773666 11:56:53.828 [planetinfo.record]: sun_color = 0.65123, 0.623594, 0.593002, 1 11:56:53.828 [planetinfo.record]: corona_shimmer = 0.409743 11:56:53.828 [planetinfo.record]: station_vector = 0.549 0.802 0.236 11:56:53.829 [planetinfo.record]: station = coriolis 11:56:58.843 [PLANETINFO OVER]: Done 11:56:58.844 [PLANETINFO LOGGING]: 1 69 11:56:59.853 [planetinfo.record]: seed = 192 246 26 232 11:56:59.853 [planetinfo.record]: coordinates = 246 39 11:56:59.853 [planetinfo.record]: government = 0; 11:56:59.853 [planetinfo.record]: economy = 7; 11:56:59.853 [planetinfo.record]: techlevel = 2; 11:56:59.853 [planetinfo.record]: population = 16; 11:56:59.853 [planetinfo.record]: productivity = 1536; 11:56:59.853 [planetinfo.record]: name = "Usceza"; 11:56:59.853 [planetinfo.record]: inhabitant = "Human Colonial"; 11:56:59.853 [planetinfo.record]: inhabitants = "Human Colonials"; 11:56:59.853 [planetinfo.record]: description = "This world is a tedious place."; 11:56:59.856 [planetinfo.record]: air_color = 0.802078, 0.503265, 0.971684, 1; 11:56:59.856 [planetinfo.record]: cloud_alpha = 1.000000; 11:56:59.856 [planetinfo.record]: cloud_color = 0.917969, 0.139847, 0.194559, 1; 11:56:59.857 [planetinfo.record]: cloud_fraction = 0.370000; 11:56:59.857 [planetinfo.record]: land_color = 0.639375, 0.65855, 0.66, 1; 11:56:59.857 [planetinfo.record]: land_fraction = 0.540000; 11:56:59.857 [planetinfo.record]: polar_cloud_color = 0.913086, 0.429347, 0.463359, 1; 11:56:59.857 [planetinfo.record]: polar_land_color = 0.926703, 0.933487, 0.934, 1; 11:56:59.857 [planetinfo.record]: polar_sea_color = 0.893788, 0.929492, 0.873759, 1; 11:56:59.857 [planetinfo.record]: sea_color = 0.596743, 0.705078, 0.53597, 1; 11:56:59.857 [planetinfo.record]: rotation_speed = 0.000690 11:56:59.857 [planetinfo.record]: planet zpos = 664300.000000 11:56:59.857 [planetinfo.record]: sun_radius = 79343.167114 11:56:59.857 [planetinfo.record]: sun_vector = 0.044 -0.747 0.664 11:56:59.857 [planetinfo.record]: sun_distance = 970900 11:56:59.857 [planetinfo.record]: corona_flare = 0.068579 11:56:59.857 [planetinfo.record]: corona_hues = 0.579102 11:56:59.857 [planetinfo.record]: sun_color = 0.548497, 0.643669, 0.694873, 1 11:56:59.857 [planetinfo.record]: corona_shimmer = 0.511648 11:56:59.858 [planetinfo.record]: station_vector = 0.037 -0.813 0.581 11:56:59.858 [planetinfo.record]: station = coriolis 11:57:04.273 [PLANETINFO OVER]: Done 11:57:04.274 [PLANETINFO LOGGING]: 1 70 11:57:05.281 [planetinfo.record]: seed = 168 210 54 189 11:57:05.281 [planetinfo.record]: coordinates = 210 228 11:57:05.281 [planetinfo.record]: government = 5; 11:57:05.281 [planetinfo.record]: economy = 4; 11:57:05.281 [planetinfo.record]: techlevel = 8; 11:57:05.281 [planetinfo.record]: population = 42; 11:57:05.281 [planetinfo.record]: productivity = 18144; 11:57:05.281 [planetinfo.record]: name = "Israza"; 11:57:05.281 [planetinfo.record]: inhabitant = "Human Colonial"; 11:57:05.281 [planetinfo.record]: inhabitants = "Human Colonials"; 11:57:05.281 [planetinfo.record]: description = "The world Israza is notable for the Israzaian edible grub and the Israzaian tree snake."; 11:57:05.292 [planetinfo.record]: air_color = 0.705092, 0.837056, 0.88258, 1; 11:57:05.292 [planetinfo.record]: cloud_alpha = 1.000000; 11:57:05.292 [planetinfo.record]: cloud_color = 0.648406, 0.650391, 0.64785, 1; 11:57:05.292 [planetinfo.record]: cloud_fraction = 0.350000; 11:57:05.292 [planetinfo.record]: land_color = 0.53125, 0.429565, 0.510595, 1; 11:57:05.292 [planetinfo.record]: land_fraction = 0.710000; 11:57:05.292 [planetinfo.record]: polar_cloud_color = 0.791164, 0.792676, 0.790741, 1; 11:57:05.293 [planetinfo.record]: polar_land_color = 0.946875, 0.901566, 0.937671, 1; 11:57:05.293 [planetinfo.record]: polar_sea_color = 0.880757, 0.921094, 0.847334, 1; 11:57:05.293 [planetinfo.record]: sea_color = 0.650842, 0.789062, 0.536316, 1; 11:57:05.293 [planetinfo.record]: rotation_speed = 0.003008 11:57:05.293 [planetinfo.record]: planet zpos = 762480.000000 11:57:05.293 [planetinfo.record]: sun_radius = 139812.044678 11:57:05.293 [planetinfo.record]: sun_vector = -0.582 0.793 0.181 11:57:05.293 [planetinfo.record]: sun_distance = 1143720 11:57:05.293 [planetinfo.record]: corona_flare = 0.003456 11:57:05.293 [planetinfo.record]: corona_hues = 0.517334 11:57:05.293 [planetinfo.record]: sun_color = 0.557753, 0.653097, 0.688483, 1 11:57:05.294 [planetinfo.record]: corona_shimmer = 0.331258 11:57:05.294 [planetinfo.record]: station_vector = 0.764 -0.644 0.041 11:57:05.294 [planetinfo.record]: station = coriolis 11:57:09.886 [PLANETINFO OVER]: Done 11:57:09.887 [PLANETINFO LOGGING]: 1 71 11:57:10.894 [planetinfo.record]: seed = 56 54 58 175 11:57:10.894 [planetinfo.record]: coordinates = 54 4 11:57:10.894 [planetinfo.record]: government = 7; 11:57:10.894 [planetinfo.record]: economy = 4; 11:57:10.894 [planetinfo.record]: techlevel = 9; 11:57:10.894 [planetinfo.record]: population = 48; 11:57:10.894 [planetinfo.record]: productivity = 25344; 11:57:10.894 [planetinfo.record]: name = "Aesaus"; 11:57:10.894 [planetinfo.record]: inhabitant = "Human Colonial"; 11:57:10.894 [planetinfo.record]: inhabitants = "Human Colonials"; 11:57:10.894 [planetinfo.record]: description = "This world is very fabled for its weird volcanoes."; 11:57:10.899 [planetinfo.record]: air_color = 0.479368, 0.596259, 0.881279, 1; 11:57:10.899 [planetinfo.record]: cloud_alpha = 1.000000; 11:57:10.899 [planetinfo.record]: cloud_color = 0.148994, 0.576525, 0.646484, 1; 11:57:10.899 [planetinfo.record]: cloud_fraction = 0.240000; 11:57:10.899 [planetinfo.record]: land_color = 0.55325, 0.531094, 0.66, 1; 11:57:10.900 [planetinfo.record]: land_fraction = 0.610000; 11:57:10.900 [planetinfo.record]: polar_cloud_color = 0.41052, 0.737425, 0.790918, 1; 11:57:10.900 [planetinfo.record]: polar_land_color = 0.896233, 0.888395, 0.934, 1; 11:57:10.900 [planetinfo.record]: polar_sea_color = 0.946094, 0.838775, 0.713266, 1; 11:57:10.900 [planetinfo.record]: sea_color = 0.539062, 0.294471, 0.00842285, 1; 11:57:10.900 [planetinfo.record]: rotation_speed = 0.004072 11:57:10.900 [planetinfo.record]: planet zpos = 805200.000000 11:57:10.900 [planetinfo.record]: sun_radius = 167404.957581 11:57:10.900 [planetinfo.record]: sun_vector = -0.081 -0.596 0.799 11:57:10.900 [planetinfo.record]: sun_distance = 1476200 11:57:10.900 [planetinfo.record]: corona_flare = 0.001903 11:57:10.900 [planetinfo.record]: corona_hues = 0.795807 11:57:10.900 [planetinfo.record]: sun_color = 0.763043, 0.515523, 0.347576, 1 11:57:10.901 [planetinfo.record]: corona_shimmer = 0.457328 11:57:10.901 [planetinfo.record]: station_vector = 0.687 0.093 0.721 11:57:10.901 [planetinfo.record]: station = coriolis 11:57:15.770 [PLANETINFO OVER]: Done 11:57:15.771 [PLANETINFO LOGGING]: 1 72 11:57:16.799 [planetinfo.record]: seed = 112 104 166 33 11:57:16.799 [planetinfo.record]: coordinates = 104 207 11:57:16.799 [planetinfo.record]: government = 6; 11:57:16.799 [planetinfo.record]: economy = 7; 11:57:16.799 [planetinfo.record]: techlevel = 3; 11:57:16.799 [planetinfo.record]: population = 26; 11:57:16.799 [planetinfo.record]: productivity = 6240; 11:57:16.799 [planetinfo.record]: name = "Leorgeri"; 11:57:16.800 [planetinfo.record]: inhabitant = "Large Red Rodent"; 11:57:16.800 [planetinfo.record]: inhabitants = "Large Red Rodents"; 11:57:16.800 [planetinfo.record]: description = "The world Leorgeri is a dull place."; 11:57:16.816 [planetinfo.record]: air_color = 0.697958, 0.729229, 0.978838, 1; 11:57:16.816 [planetinfo.record]: cloud_alpha = 1.000000; 11:57:16.816 [planetinfo.record]: cloud_color = 0.693581, 0.772337, 0.939453, 1; 11:57:16.816 [planetinfo.record]: cloud_fraction = 0.370000; 11:57:16.816 [planetinfo.record]: land_color = 0.250816, 0.49519, 0.513672, 1; 11:57:16.816 [planetinfo.record]: land_fraction = 0.660000; 11:57:16.816 [planetinfo.record]: polar_cloud_color = 0.771815, 0.820163, 0.922754, 1; 11:57:16.816 [planetinfo.record]: polar_land_color = 0.827275, 0.9401, 0.948633, 1; 11:57:16.816 [planetinfo.record]: polar_sea_color = 0.901894, 0.933398, 0.882353, 1; 11:57:16.816 [planetinfo.record]: sea_color = 0.576097, 0.666016, 0.520325, 1; 11:57:16.816 [planetinfo.record]: rotation_speed = 0.003859 11:57:16.816 [planetinfo.record]: planet zpos = 381120.000000 11:57:16.817 [planetinfo.record]: sun_radius = 74590.639648 11:57:16.817 [planetinfo.record]: sun_vector = -0.668 0.637 -0.385 11:57:16.817 [planetinfo.record]: sun_distance = 603440 11:57:16.817 [planetinfo.record]: corona_flare = 0.049484 11:57:16.817 [planetinfo.record]: corona_hues = 0.610863 11:57:16.817 [planetinfo.record]: sun_color = 0.841867, 0.586658, 0.376757, 1 11:57:16.817 [planetinfo.record]: corona_shimmer = 0.395653 11:57:16.817 [planetinfo.record]: station_vector = 0.434 -0.881 0.186 11:57:16.817 [planetinfo.record]: station = coriolis 11:57:21.490 [PLANETINFO OVER]: Done 11:57:21.491 [PLANETINFO LOGGING]: 1 73 11:57:22.493 [planetinfo.record]: seed = 144 94 122 155 11:57:22.493 [planetinfo.record]: coordinates = 94 227 11:57:22.493 [planetinfo.record]: government = 2; 11:57:22.493 [planetinfo.record]: economy = 3; 11:57:22.494 [planetinfo.record]: techlevel = 7; 11:57:22.494 [planetinfo.record]: population = 34; 11:57:22.494 [planetinfo.record]: productivity = 11424; 11:57:22.494 [planetinfo.record]: name = "Anisti"; 11:57:22.494 [planetinfo.record]: inhabitant = "Human Colonial"; 11:57:22.494 [planetinfo.record]: inhabitants = "Human Colonials"; 11:57:22.494 [planetinfo.record]: description = "This world is mildly famous for its unusual sit coms and the Anistiian spotted wolf."; 11:57:22.503 [planetinfo.record]: air_color = 0.661761, 0.777047, 0.974285, 1; 11:57:22.503 [planetinfo.record]: cloud_alpha = 1.000000; 11:57:22.503 [planetinfo.record]: cloud_color = 0.589462, 0.925781, 0.894251, 1; 11:57:22.503 [planetinfo.record]: cloud_fraction = 0.210000; 11:57:22.503 [planetinfo.record]: land_color = 0.568215, 0.66, 0.420234, 1; 11:57:22.503 [planetinfo.record]: land_fraction = 0.330000; 11:57:22.503 [planetinfo.record]: polar_cloud_color = 0.708486, 0.916602, 0.897091, 1; 11:57:22.503 [planetinfo.record]: polar_land_color = 0.901528, 0.934, 0.849174, 1; 11:57:22.503 [planetinfo.record]: polar_sea_color = 0.921973, 0.933984, 0.879076, 1; 11:57:22.503 [planetinfo.record]: sea_color = 0.626198, 0.660156, 0.504916, 1; 11:57:22.503 [planetinfo.record]: rotation_speed = 0.000006 11:57:22.504 [planetinfo.record]: planet zpos = 572600.000000 11:57:22.504 [planetinfo.record]: sun_radius = 136930.873413 11:57:22.504 [planetinfo.record]: sun_vector = 0.534 0.817 -0.217 11:57:22.504 [planetinfo.record]: sun_distance = 1202460 11:57:22.504 [planetinfo.record]: corona_flare = 0.061186 11:57:22.504 [planetinfo.record]: corona_hues = 0.559570 11:57:22.504 [planetinfo.record]: sun_color = 0.744966, 0.772758, 0.778854, 1 11:57:22.504 [planetinfo.record]: corona_shimmer = 0.496937 11:57:22.504 [planetinfo.record]: station_vector = -0.608 -0.524 0.596 11:57:22.504 [planetinfo.record]: station = coriolis 11:57:26.830 [PLANETINFO OVER]: Done 11:57:26.831 [PLANETINFO LOGGING]: 1 74 11:57:27.845 [planetinfo.record]: seed = 152 80 182 5 11:57:27.845 [planetinfo.record]: coordinates = 80 215 11:57:27.845 [planetinfo.record]: government = 3; 11:57:27.845 [planetinfo.record]: economy = 7; 11:57:27.845 [planetinfo.record]: techlevel = 2; 11:57:27.845 [planetinfo.record]: population = 19; 11:57:27.845 [planetinfo.record]: productivity = 3192; 11:57:27.845 [planetinfo.record]: name = "Cediza"; 11:57:27.845 [planetinfo.record]: inhabitant = "Fierce Green Rodent"; 11:57:27.845 [planetinfo.record]: inhabitants = "Fierce Green Rodents"; 11:57:27.845 [planetinfo.record]: description = "The planet Cediza is famous for Cedizaian wolf meat but scourged by deadly civil war."; 11:57:27.872 [planetinfo.record]: air_color = 0.510737, 0.653174, 0.835102, 1; 11:57:27.872 [planetinfo.record]: cloud_alpha = 1.000000; 11:57:27.872 [planetinfo.record]: cloud_color = 0.220184, 0.507812, 0.420176, 1; 11:57:27.872 [planetinfo.record]: cloud_fraction = 0.250000; 11:57:27.872 [planetinfo.record]: land_color = 0.66, 0.634058, 0.515625, 1; 11:57:27.872 [planetinfo.record]: land_fraction = 0.610000; 11:57:27.872 [planetinfo.record]: polar_cloud_color = 0.470618, 0.728516, 0.649938, 1; 11:57:27.872 [planetinfo.record]: polar_land_color = 0.934, 0.924822, 0.882922, 1; 11:57:27.872 [planetinfo.record]: polar_sea_color = 0.698225, 0.927344, 0.879014, 1; 11:57:27.873 [planetinfo.record]: sea_color = 0.0085144, 0.726562, 0.575099, 1; 11:57:27.873 [planetinfo.record]: rotation_speed = 0.002280 11:57:27.873 [planetinfo.record]: planet zpos = 626400.000000 11:57:27.873 [planetinfo.record]: sun_radius = 124350.314941 11:57:27.873 [planetinfo.record]: sun_vector = -0.871 -0.459 0.177 11:57:27.873 [planetinfo.record]: sun_distance = 835200 11:57:27.873 [planetinfo.record]: corona_flare = 0.024193 11:57:27.873 [planetinfo.record]: corona_hues = 0.783218 11:57:27.873 [planetinfo.record]: sun_color = 0.785605, 0.685163, 0.291863, 1 11:57:27.873 [planetinfo.record]: corona_shimmer = 0.332751 11:57:27.873 [planetinfo.record]: station_vector = 0.176 -0.961 0.211 11:57:27.873 [planetinfo.record]: station = coriolis 11:57:32.488 [PLANETINFO OVER]: Done 11:57:32.489 [PLANETINFO LOGGING]: 1 75 11:57:33.499 [planetinfo.record]: seed = 200 183 218 105 11:57:33.499 [planetinfo.record]: coordinates = 183 132 11:57:33.499 [planetinfo.record]: government = 1; 11:57:33.499 [planetinfo.record]: economy = 6; 11:57:33.499 [planetinfo.record]: techlevel = 5; 11:57:33.499 [planetinfo.record]: population = 28; 11:57:33.499 [planetinfo.record]: productivity = 4480; 11:57:33.499 [planetinfo.record]: name = "Esceso"; 11:57:33.499 [planetinfo.record]: inhabitant = "Small Blue Bony Bird"; 11:57:33.499 [planetinfo.record]: inhabitants = "Small Blue Bony Birds"; 11:57:33.500 [planetinfo.record]: description = "Esceso is a revolting dump."; 11:57:33.509 [planetinfo.record]: air_color = 0.645922, 0.576984, 0.864369, 1; 11:57:33.509 [planetinfo.record]: cloud_alpha = 1.000000; 11:57:33.509 [planetinfo.record]: cloud_color = 0.532657, 0.358353, 0.595703, 1; 11:57:33.509 [planetinfo.record]: cloud_fraction = 0.140000; 11:57:33.509 [planetinfo.record]: land_color = 0.147079, 0.597656, 0.42869, 1; 11:57:33.515 [planetinfo.record]: land_fraction = 0.570000; 11:57:33.515 [planetinfo.record]: polar_cloud_color = 0.717261, 0.5768, 0.768066, 1; 11:57:33.515 [planetinfo.record]: polar_land_color = 0.763022, 0.940234, 0.87378, 1; 11:57:33.515 [planetinfo.record]: polar_sea_color = 0.938281, 0.901572, 0.879547, 1; 11:57:33.515 [planetinfo.record]: sea_color = 0.617188, 0.520601, 0.46265, 1; 11:57:33.515 [planetinfo.record]: rotation_speed = 0.003435 11:57:33.515 [planetinfo.record]: planet zpos = 636360.000000 11:57:33.515 [planetinfo.record]: sun_radius = 127405.999146 11:57:33.515 [planetinfo.record]: sun_vector = -0.817 -0.236 0.527 11:57:33.516 [planetinfo.record]: sun_distance = 1219690 11:57:33.516 [planetinfo.record]: corona_flare = 0.070877 11:57:33.516 [planetinfo.record]: corona_hues = 0.847900 11:57:33.516 [planetinfo.record]: sun_color = 0.553734, 0.635993, 0.67153, 1 11:57:33.516 [planetinfo.record]: corona_shimmer = 0.279393 11:57:33.516 [planetinfo.record]: station_vector = -0.321 -0.212 0.923 11:57:33.516 [planetinfo.record]: station = coriolis 11:57:38.313 [PLANETINFO OVER]: Done 11:57:38.314 [PLANETINFO LOGGING]: 1 76 11:57:39.323 [planetinfo.record]: seed = 32 215 102 68 11:57:39.323 [planetinfo.record]: coordinates = 215 199 11:57:39.323 [planetinfo.record]: government = 4; 11:57:39.323 [planetinfo.record]: economy = 7; 11:57:39.323 [planetinfo.record]: techlevel = 5; 11:57:39.323 [planetinfo.record]: population = 32; 11:57:39.323 [planetinfo.record]: productivity = 6144; 11:57:39.323 [planetinfo.record]: name = "Zaxerice"; 11:57:39.323 [planetinfo.record]: inhabitant = "Human Colonial"; 11:57:39.323 [planetinfo.record]: inhabitants = "Human Colonials"; 11:57:39.323 [planetinfo.record]: description = "Zaxerice is an unremarkable dump."; 11:57:39.327 [planetinfo.record]: air_color = 0.55948, 0.972135, 0.974936, 1; 11:57:39.327 [planetinfo.record]: cloud_alpha = 1.000000; 11:57:39.327 [planetinfo.record]: cloud_color = 0.908029, 0.927734, 0.297165, 1; 11:57:39.327 [planetinfo.record]: cloud_fraction = 0.550000; 11:57:39.327 [planetinfo.record]: land_color = 0.554297, 0.65009, 0.66, 1; 11:57:39.327 [planetinfo.record]: land_fraction = 0.510000; 11:57:39.327 [planetinfo.record]: polar_cloud_color = 0.905301, 0.91748, 0.52773, 1; 11:57:39.327 [planetinfo.record]: polar_land_color = 0.896604, 0.930494, 0.934, 1; 11:57:39.327 [planetinfo.record]: polar_sea_color = 0.911948, 0.935547, 0.886029, 1; 11:57:39.328 [planetinfo.record]: sea_color = 0.5795, 0.644531, 0.508072, 1; 11:57:39.328 [planetinfo.record]: rotation_speed = 0.003267 11:57:39.328 [planetinfo.record]: planet zpos = 567700.000000 11:57:39.328 [planetinfo.record]: sun_radius = 83780.398560 11:57:39.328 [planetinfo.record]: sun_vector = -0.559 -0.761 0.329 11:57:39.328 [planetinfo.record]: sun_distance = 729900 11:57:39.328 [planetinfo.record]: corona_flare = 0.009744 11:57:39.328 [planetinfo.record]: corona_hues = 0.842873 11:57:39.328 [planetinfo.record]: sun_color = 0.301416, 0.70069, 0.716034, 1 11:57:39.328 [planetinfo.record]: corona_shimmer = 0.857913 11:57:39.328 [planetinfo.record]: station_vector = -0.390 -0.879 0.274 11:57:39.328 [planetinfo.record]: station = coriolis 11:57:43.689 [PLANETINFO OVER]: Done 11:57:43.690 [PLANETINFO LOGGING]: 1 77 11:57:44.701 [planetinfo.record]: seed = 224 37 90 7 11:57:44.701 [planetinfo.record]: coordinates = 37 254 11:57:44.701 [planetinfo.record]: government = 4; 11:57:44.701 [planetinfo.record]: economy = 6; 11:57:44.701 [planetinfo.record]: techlevel = 4; 11:57:44.701 [planetinfo.record]: population = 27; 11:57:44.701 [planetinfo.record]: productivity = 6912; 11:57:44.701 [planetinfo.record]: name = "Somaed"; 11:57:44.701 [planetinfo.record]: inhabitant = "Human Colonial"; 11:57:44.701 [planetinfo.record]: inhabitants = "Human Colonials"; 11:57:44.701 [planetinfo.record]: description = "Somaed is most noted for the Somaedian evil poet and Somaedian Ilrebedi brandy."; 11:57:44.732 [planetinfo.record]: air_color = 0.634232, 0.905994, 0.890858, 1; 11:57:44.732 [planetinfo.record]: cloud_alpha = 1.000000; 11:57:44.732 [planetinfo.record]: cloud_color = 0.720703, 0.686832, 0.503929, 1; 11:57:44.732 [planetinfo.record]: cloud_fraction = 0.390000; 11:57:44.732 [planetinfo.record]: land_color = 0.58894, 0.66, 0.335156, 1; 11:57:44.732 [planetinfo.record]: land_fraction = 0.690000; 11:57:44.732 [planetinfo.record]: polar_cloud_color = 0.824316, 0.800104, 0.669355, 1; 11:57:44.732 [planetinfo.record]: polar_land_color = 0.90886, 0.934, 0.819074, 1; 11:57:44.732 [planetinfo.record]: polar_sea_color = 0.949414, 0.898606, 0.909323, 1; 11:57:44.732 [planetinfo.record]: sea_color = 0.505859, 0.397574, 0.420415, 1; 11:57:44.732 [planetinfo.record]: rotation_speed = 0.004305 11:57:44.732 [planetinfo.record]: planet zpos = 603850.000000 11:57:44.732 [planetinfo.record]: sun_radius = 126486.519623 11:57:44.732 [planetinfo.record]: sun_vector = -0.059 -0.383 0.922 11:57:44.732 [planetinfo.record]: sun_distance = 1021900 11:57:44.732 [planetinfo.record]: corona_flare = 0.052580 11:57:44.732 [planetinfo.record]: corona_hues = 0.960068 11:57:44.732 [planetinfo.record]: sun_color = 0.460751, 0.61652, 0.759552, 1 11:57:44.733 [planetinfo.record]: corona_shimmer = 0.313033 11:57:44.733 [planetinfo.record]: station_vector = 0.954 0.216 0.207 11:57:44.733 [planetinfo.record]: station = coriolis 11:57:49.416 [PLANETINFO OVER]: Done 11:57:49.417 [PLANETINFO LOGGING]: 1 78 11:57:50.426 [planetinfo.record]: seed = 8 140 182 16 11:57:50.426 [planetinfo.record]: coordinates = 140 88 11:57:50.426 [planetinfo.record]: government = 1; 11:57:50.426 [planetinfo.record]: economy = 2; 11:57:50.426 [planetinfo.record]: techlevel = 6; 11:57:50.426 [planetinfo.record]: population = 28; 11:57:50.426 [planetinfo.record]: productivity = 8960; 11:57:50.426 [planetinfo.record]: name = "Erlaened"; 11:57:50.426 [planetinfo.record]: inhabitant = "Green Fat Bird"; 11:57:50.426 [planetinfo.record]: inhabitants = "Green Fat Birds"; 11:57:50.426 [planetinfo.record]: description = "This planet is reasonably noted for mud tennis."; 11:57:50.430 [planetinfo.record]: air_color = 0.580441, 0.521995, 0.901441, 1; 11:57:50.430 [planetinfo.record]: cloud_alpha = 1.000000; 11:57:50.430 [planetinfo.record]: cloud_color = 0.461271, 0.237518, 0.707031, 1; 11:57:50.430 [planetinfo.record]: cloud_fraction = 0.360000; 11:57:50.430 [planetinfo.record]: land_color = 0.638672, 0.411644, 0.518063, 1; 11:57:50.430 [planetinfo.record]: land_fraction = 0.360000; 11:57:50.430 [planetinfo.record]: polar_cloud_color = 0.64042, 0.478594, 0.818164, 1; 11:57:50.430 [planetinfo.record]: polar_land_color = 0.936133, 0.852941, 0.891937, 1; 11:57:50.430 [planetinfo.record]: polar_sea_color = 0.923633, 0.902788, 0.83163, 1; 11:57:50.430 [planetinfo.record]: sea_color = 0.763672, 0.694734, 0.459396, 1; 11:57:50.430 [planetinfo.record]: rotation_speed = 0.001669 11:57:50.430 [planetinfo.record]: planet zpos = 325160.000000 11:57:50.430 [planetinfo.record]: sun_radius = 58961.230469 11:57:50.430 [planetinfo.record]: sun_vector = -0.676 0.433 -0.597 11:57:50.430 [planetinfo.record]: sun_distance = 620760 11:57:50.430 [planetinfo.record]: corona_flare = 0.011497 11:57:50.430 [planetinfo.record]: corona_hues = 0.519127 11:57:50.430 [planetinfo.record]: sun_color = 0.825021, 0.593812, 0.51443, 1 11:57:50.431 [planetinfo.record]: corona_shimmer = 1.010018 11:57:50.431 [planetinfo.record]: station_vector = 0.980 -0.185 0.066 11:57:50.431 [planetinfo.record]: station = coriolis 11:57:54.577 [PLANETINFO OVER]: Done 11:57:54.577 [PLANETINFO LOGGING]: 1 79 11:57:55.581 [planetinfo.record]: seed = 216 152 250 32 11:57:55.581 [planetinfo.record]: coordinates = 152 146 11:57:55.582 [planetinfo.record]: government = 3; 11:57:55.582 [planetinfo.record]: economy = 2; 11:57:55.582 [planetinfo.record]: techlevel = 7; 11:57:55.582 [planetinfo.record]: population = 34; 11:57:55.582 [planetinfo.record]: productivity = 15232; 11:57:55.582 [planetinfo.record]: name = "Inbibe"; 11:57:55.582 [planetinfo.record]: inhabitant = "Large Red Horned Lizard"; 11:57:55.582 [planetinfo.record]: inhabitants = "Large Red Horned Lizards"; 11:57:55.582 [planetinfo.record]: description = "This planet is very notable for its inhabitants’ ingrained shyness and its fabulous cuisine."; 11:57:55.598 [planetinfo.record]: air_color = 0.451867, 0.736055, 0.855264, 1; 11:57:55.598 [planetinfo.record]: cloud_alpha = 1.000000; 11:57:55.598 [planetinfo.record]: cloud_color = 0.151473, 0.568359, 0.104347, 1; 11:57:55.598 [planetinfo.record]: cloud_fraction = 0.520000; 11:57:55.598 [planetinfo.record]: land_color = 0.537109, 0.266489, 0.00419617, 1; 11:57:55.598 [planetinfo.record]: land_fraction = 0.650000; 11:57:55.598 [planetinfo.record]: polar_cloud_color = 0.409297, 0.755762, 0.370131, 1; 11:57:55.598 [planetinfo.record]: polar_land_color = 0.946289, 0.827093, 0.711565, 1; 11:57:55.598 [planetinfo.record]: polar_sea_color = 0.939258, 0.904882, 0.882114, 1; 11:57:55.598 [planetinfo.record]: sea_color = 0.607422, 0.518498, 0.4596, 1; 11:57:55.607 [planetinfo.record]: rotation_speed = 0.002772 11:57:55.608 [planetinfo.record]: planet zpos = 296800.000000 11:57:55.608 [planetinfo.record]: sun_radius = 86452.691650 11:57:55.608 [planetinfo.record]: sun_vector = 0.503 -0.723 0.474 11:57:55.608 [planetinfo.record]: sun_distance = 504560 11:57:55.608 [planetinfo.record]: corona_flare = 0.022246 11:57:55.608 [planetinfo.record]: corona_hues = 0.830688 11:57:55.608 [planetinfo.record]: sun_color = 0.676575, 0.63221, 0.412019, 1 11:57:55.608 [planetinfo.record]: corona_shimmer = 0.605794 11:57:55.608 [planetinfo.record]: station_vector = -0.012 0.938 0.346 11:57:55.608 [planetinfo.record]: station = coriolis 11:57:59.907 [PLANETINFO OVER]: Done 11:57:59.908 [PLANETINFO LOGGING]: 1 80 11:58:00.913 [planetinfo.record]: seed = 80 115 166 197 11:58:00.913 [planetinfo.record]: coordinates = 115 6 11:58:00.913 [planetinfo.record]: government = 2; 11:58:00.913 [planetinfo.record]: economy = 6; 11:58:00.913 [planetinfo.record]: techlevel = 5; 11:58:00.913 [planetinfo.record]: population = 29; 11:58:00.913 [planetinfo.record]: productivity = 5568; 11:58:00.913 [planetinfo.record]: name = "Ceoned"; 11:58:00.913 [planetinfo.record]: inhabitant = "Fierce Furry Feline"; 11:58:00.913 [planetinfo.record]: inhabitants = "Fierce Furry Felines"; 11:58:00.913 [planetinfo.record]: description = "The planet Ceoned is well known for its inhabitants’ ancient mating traditions but cursed by dreadful civil war."; 11:58:00.932 [planetinfo.record]: air_color = 0.643793, 0.889804, 0.919652, 1; 11:58:00.932 [planetinfo.record]: cloud_alpha = 1.000000; 11:58:00.932 [planetinfo.record]: cloud_color = 0.684752, 0.761719, 0.532608, 1; 11:58:00.932 [planetinfo.record]: cloud_fraction = 0.440000; 11:58:00.932 [planetinfo.record]: land_color = 0.66, 0.314531, 0.646505, 1; 11:58:00.932 [planetinfo.record]: land_fraction = 0.330000; 11:58:00.932 [planetinfo.record]: polar_cloud_color = 0.78955, 0.842773, 0.684342, 1; 11:58:00.932 [planetinfo.record]: polar_land_color = 0.934, 0.811777, 0.929226, 1; 11:58:00.932 [planetinfo.record]: polar_sea_color = 0.886904, 0.928906, 0.87348, 1; 11:58:00.932 [planetinfo.record]: sea_color = 0.582351, 0.710938, 0.541257, 1; 11:58:00.932 [planetinfo.record]: rotation_speed = 0.002686 11:58:00.932 [planetinfo.record]: planet zpos = 505320.000000 11:58:00.932 [planetinfo.record]: sun_radius = 98812.256165 11:58:00.932 [planetinfo.record]: sun_vector = 0.631 -0.251 0.735 11:58:00.932 [planetinfo.record]: sun_distance = 842200 11:58:00.932 [planetinfo.record]: corona_flare = 0.098149 11:58:00.932 [planetinfo.record]: corona_hues = 0.702599 11:58:00.932 [planetinfo.record]: sun_color = 0.42771, 0.653737, 0.712558, 1 11:58:00.933 [planetinfo.record]: corona_shimmer = 1.110629 11:58:00.933 [planetinfo.record]: station_vector = -1.000 0.024 0.017 11:58:00.933 [planetinfo.record]: station = coriolis 11:58:05.147 [PLANETINFO OVER]: Done 11:58:05.147 [PLANETINFO LOGGING]: 1 81 11:58:06.166 [planetinfo.record]: seed = 176 124 186 51 11:58:06.166 [planetinfo.record]: coordinates = 124 120 11:58:06.166 [planetinfo.record]: government = 6; 11:58:06.166 [planetinfo.record]: economy = 0; 11:58:06.166 [planetinfo.record]: techlevel = 10; 11:58:06.166 [planetinfo.record]: population = 47; 11:58:06.166 [planetinfo.record]: productivity = 37600; 11:58:06.166 [planetinfo.record]: name = "Beused"; 11:58:06.166 [planetinfo.record]: inhabitant = "Red Fat Insect"; 11:58:06.166 [planetinfo.record]: inhabitants = "Red Fat Insects"; 11:58:06.166 [planetinfo.record]: description = "This planet is fabled for its exciting Zero-G cricket."; 11:58:06.184 [planetinfo.record]: air_color = 0.610775, 0.508013, 0.922904, 1; 11:58:06.184 [planetinfo.record]: cloud_alpha = 1.000000; 11:58:06.184 [planetinfo.record]: cloud_color = 0.631351, 0.192871, 0.771484, 1; 11:58:06.184 [planetinfo.record]: cloud_fraction = 0.300000; 11:58:06.184 [planetinfo.record]: land_color = 0.66, 0.410728, 0.190781, 1; 11:58:06.184 [planetinfo.record]: land_fraction = 0.560000; 11:58:06.184 [planetinfo.record]: polar_cloud_color = 0.750993, 0.450058, 0.847168, 1; 11:58:06.184 [planetinfo.record]: polar_land_color = 0.934, 0.84581, 0.767996, 1; 11:58:06.184 [planetinfo.record]: polar_sea_color = 0.939844, 0.888575, 0.880186, 1; 11:58:06.184 [planetinfo.record]: sea_color = 0.601562, 0.470301, 0.448822, 1; 11:58:06.184 [planetinfo.record]: rotation_speed = 0.003714 11:58:06.184 [planetinfo.record]: planet zpos = 444960.000000 11:58:06.184 [planetinfo.record]: sun_radius = 86129.978027 11:58:06.184 [planetinfo.record]: sun_vector = 0.449 -0.747 -0.490 11:58:06.184 [planetinfo.record]: sun_distance = 593280 11:58:06.184 [planetinfo.record]: corona_flare = 0.038275 11:58:06.184 [planetinfo.record]: corona_hues = 0.908768 11:58:06.184 [planetinfo.record]: sun_color = 0.328698, 0.468579, 0.663138, 1 11:58:06.185 [planetinfo.record]: corona_shimmer = 0.318297 11:58:06.186 [planetinfo.record]: station_vector = -0.607 -0.792 0.061 11:58:06.186 [planetinfo.record]: station = coriolis 11:58:10.776 [PLANETINFO OVER]: Done 11:58:10.777 [PLANETINFO LOGGING]: 1 82 11:58:11.780 [planetinfo.record]: seed = 248 52 54 54 11:58:11.781 [planetinfo.record]: coordinates = 52 216 11:58:11.781 [planetinfo.record]: government = 7; 11:58:11.781 [planetinfo.record]: economy = 0; 11:58:11.781 [planetinfo.record]: techlevel = 11; 11:58:11.781 [planetinfo.record]: population = 52; 11:58:11.781 [planetinfo.record]: productivity = 45760; 11:58:11.781 [planetinfo.record]: name = "Vezaaes"; 11:58:11.781 [planetinfo.record]: inhabitant = "Human Colonial"; 11:58:11.781 [planetinfo.record]: inhabitants = "Human Colonials"; 11:58:11.781 [planetinfo.record]: description = "The world Vezaaes is very famous for Vezaaesian shrew steak but ravaged by dreadful civil war."; 11:58:11.800 [planetinfo.record]: air_color = 0.711083, 0.826454, 0.892986, 1; 11:58:11.800 [planetinfo.record]: cloud_alpha = 1.000000; 11:58:11.800 [planetinfo.record]: cloud_color = 0.673653, 0.681641, 0.674526, 1; 11:58:11.800 [planetinfo.record]: cloud_fraction = 0.530000; 11:58:11.800 [planetinfo.record]: land_color = 0.474929, 0.36129, 0.560547, 1; 11:58:11.800 [planetinfo.record]: land_fraction = 0.350000; 11:58:11.800 [planetinfo.record]: polar_cloud_color = 0.80083, 0.806738, 0.801476, 1; 11:58:11.800 [planetinfo.record]: polar_land_color = 0.907901, 0.860059, 0.943945, 1; 11:58:11.800 [planetinfo.record]: polar_sea_color = 0.923927, 0.936328, 0.885123, 1; 11:58:11.801 [planetinfo.record]: sea_color = 0.602986, 0.636719, 0.497437, 1; 11:58:11.801 [planetinfo.record]: rotation_speed = 0.001048 11:58:11.801 [planetinfo.record]: planet zpos = 616560.000000 11:58:11.801 [planetinfo.record]: sun_radius = 113871.247559 11:58:11.801 [planetinfo.record]: sun_vector = -0.751 0.010 -0.660 11:58:11.801 [planetinfo.record]: sun_distance = 836760 11:58:11.801 [planetinfo.record]: corona_flare = 0.080524 11:58:11.801 [planetinfo.record]: corona_hues = 0.697739 11:58:11.801 [planetinfo.record]: sun_color = 0.818677, 0.790204, 0.756345, 1 11:58:11.801 [planetinfo.record]: corona_shimmer = 0.392572 11:58:11.802 [planetinfo.record]: station_vector = -0.791 0.078 0.607 11:58:11.804 [planetinfo.record]: station = dodecahedron 11:58:16.475 [PLANETINFO OVER]: Done 11:58:16.476 [PLANETINFO LOGGING]: 1 83 11:58:17.487 [planetinfo.record]: seed = 104 41 154 28 11:58:17.487 [planetinfo.record]: coordinates = 41 175 11:58:17.487 [planetinfo.record]: government = 5; 11:58:17.487 [planetinfo.record]: economy = 7; 11:58:17.488 [planetinfo.record]: techlevel = 4; 11:58:17.488 [planetinfo.record]: population = 29; 11:58:17.488 [planetinfo.record]: productivity = 6264; 11:58:17.488 [planetinfo.record]: name = "Telaan"; 11:58:17.488 [planetinfo.record]: inhabitant = "Green Feline"; 11:58:17.488 [planetinfo.record]: inhabitants = "Green Felines"; 11:58:17.488 [planetinfo.record]: description = "The planet Telaan is cursed by deadly civil war."; 11:58:17.503 [planetinfo.record]: air_color = 0.621465, 0.494288, 0.937863, 1; 11:58:17.503 [planetinfo.record]: cloud_alpha = 1.000000; 11:58:17.503 [planetinfo.record]: cloud_color = 0.732693, 0.146698, 0.816406, 1; 11:58:17.503 [planetinfo.record]: cloud_fraction = 0.300000; 11:58:17.503 [planetinfo.record]: land_color = 0.621328, 0.66, 0.653655, 1; 11:58:17.503 [planetinfo.record]: land_fraction = 0.460000; 11:58:17.503 [planetinfo.record]: polar_cloud_color = 0.811795, 0.42268, 0.867383, 1; 11:58:17.503 [planetinfo.record]: polar_land_color = 0.920318, 0.934, 0.931755, 1; 11:58:17.503 [planetinfo.record]: polar_sea_color = 0.91875, 0.849075, 0.79314, 1; 11:58:17.504 [planetinfo.record]: sea_color = 0.8125, 0.566032, 0.368164, 1; 11:58:17.504 [planetinfo.record]: rotation_speed = 0.002128 11:58:17.504 [planetinfo.record]: planet zpos = 652190.000000 11:58:17.504 [planetinfo.record]: sun_radius = 173300.392609 11:58:17.504 [planetinfo.record]: sun_vector = -0.052 0.097 -0.994 11:58:17.504 [planetinfo.record]: sun_distance = 1304380 11:58:17.504 [planetinfo.record]: corona_flare = 0.062730 11:58:17.504 [planetinfo.record]: corona_hues = 0.622002 11:58:17.504 [planetinfo.record]: sun_color = 0.391565, 0.683951, 0.743954, 1 11:58:17.504 [planetinfo.record]: corona_shimmer = 0.630130 11:58:17.504 [planetinfo.record]: station_vector = 0.901 0.119 0.417 11:58:17.504 [planetinfo.record]: station = coriolis 11:58:23.076 [PLANETINFO OVER]: Done 11:58:23.077 [PLANETINFO LOGGING]: 1 84 11:58:24.080 [planetinfo.record]: seed = 0 77 102 125 11:58:24.080 [planetinfo.record]: coordinates = 77 59 11:58:24.080 [planetinfo.record]: government = 0; 11:58:24.081 [planetinfo.record]: economy = 3; 11:58:24.081 [planetinfo.record]: techlevel = 5; 11:58:24.081 [planetinfo.record]: population = 24; 11:58:24.081 [planetinfo.record]: productivity = 5376; 11:58:24.081 [planetinfo.record]: name = "Isceer"; 11:58:24.081 [planetinfo.record]: inhabitant = "Human Colonial"; 11:58:24.081 [planetinfo.record]: inhabitants = "Human Colonials"; 11:58:24.081 [planetinfo.record]: description = "This planet is noted for its exciting sit coms."; 11:58:24.120 [planetinfo.record]: air_color = 0.605283, 0.88145, 0.969082, 1; 11:58:24.120 [planetinfo.record]: cloud_alpha = 1.000000; 11:58:24.120 [planetinfo.record]: cloud_color = 0.557682, 0.910156, 0.430191, 1; 11:58:24.120 [planetinfo.record]: cloud_fraction = 0.620000; 11:58:24.120 [planetinfo.record]: land_color = 0.66, 0.66, 0.66, 1; 11:58:24.120 [planetinfo.record]: land_fraction = 0.340000; 11:58:24.120 [planetinfo.record]: polar_cloud_color = 0.689416, 0.90957, 0.609785, 1; 11:58:24.120 [planetinfo.record]: polar_land_color = 0.934, 0.934, 0.934, 1; 11:58:24.120 [planetinfo.record]: polar_sea_color = 0.768036, 0.944141, 0.807935, 1; 11:58:24.120 [planetinfo.record]: sea_color = 0.14183, 0.558594, 0.236253, 1; 11:58:24.120 [planetinfo.record]: rotation_speed = 0.002022 11:58:24.120 [planetinfo.record]: planet zpos = 746520.000000 11:58:24.121 [planetinfo.record]: sun_radius = 139082.104187 11:58:24.121 [planetinfo.record]: sun_vector = -0.434 0.150 -0.888 11:58:24.121 [planetinfo.record]: sun_distance = 1244200 11:58:24.121 [planetinfo.record]: corona_flare = 0.084108 11:58:24.121 [planetinfo.record]: corona_hues = 0.800758 11:58:24.121 [planetinfo.record]: sun_color = 0.48729, 0.703041, 0.826279, 1 11:58:24.121 [planetinfo.record]: corona_shimmer = 0.412560 11:58:24.122 [planetinfo.record]: station_vector = 0.610 0.196 0.767 11:58:24.122 [planetinfo.record]: station = coriolis 11:58:29.119 [PLANETINFO OVER]: Done 11:58:29.119 [PLANETINFO LOGGING]: 1 85 11:58:30.124 [planetinfo.record]: seed = 0 83 154 40 11:58:30.124 [planetinfo.record]: coordinates = 83 208 11:58:30.124 [planetinfo.record]: government = 0; 11:58:30.124 [planetinfo.record]: economy = 2; 11:58:30.124 [planetinfo.record]: techlevel = 8; 11:58:30.124 [planetinfo.record]: population = 35; 11:58:30.124 [planetinfo.record]: productivity = 8960; 11:58:30.124 [planetinfo.record]: name = "Usmaso"; 11:58:30.124 [planetinfo.record]: inhabitant = "Small Red Bony Lobster"; 11:58:30.125 [planetinfo.record]: inhabitants = "Small Red Bony Lobsters"; 11:58:30.125 [planetinfo.record]: description = "The world Usmaso is a boring planet."; 11:58:30.126 [planetinfo.record]: air_color = 0.417518, 0.84876, 0.848301, 1; 11:58:30.127 [planetinfo.record]: cloud_alpha = 1.000000; 11:58:30.127 [planetinfo.record]: cloud_color = 0.544859, 0.548828, 0.0407333, 1; 11:58:30.127 [planetinfo.record]: cloud_fraction = 0.360000; 11:58:30.127 [planetinfo.record]: land_color = 0.60876, 0.66, 0.250078, 1; 11:58:30.127 [planetinfo.record]: land_fraction = 0.510000; 11:58:30.127 [planetinfo.record]: polar_cloud_color = 0.743596, 0.746973, 0.314764, 1; 11:58:30.127 [planetinfo.record]: polar_land_color = 0.915872, 0.934, 0.788975, 1; 11:58:30.127 [planetinfo.record]: polar_sea_color = 0.942578, 0.929238, 0.893792, 1; 11:58:30.127 [planetinfo.record]: sea_color = 0.574219, 0.541712, 0.455338, 1; 11:58:30.127 [planetinfo.record]: rotation_speed = 0.003078 11:58:30.127 [planetinfo.record]: planet zpos = 643110.000000 11:58:30.127 [planetinfo.record]: sun_radius = 125857.277985 11:58:30.127 [planetinfo.record]: sun_vector = -0.933 -0.345 0.098 11:58:30.127 [planetinfo.record]: sun_distance = 1088340 11:58:30.127 [planetinfo.record]: corona_flare = 0.022968 11:58:30.127 [planetinfo.record]: corona_hues = 0.837563 11:58:30.127 [planetinfo.record]: sun_color = 0.584995, 0.633451, 0.735565, 1 11:58:30.128 [planetinfo.record]: corona_shimmer = 0.421372 11:58:30.128 [planetinfo.record]: station_vector = 0.670 0.515 0.535 11:58:30.128 [planetinfo.record]: station = coriolis 11:58:35.238 [PLANETINFO OVER]: Done 11:58:35.239 [PLANETINFO LOGGING]: 1 86 11:58:36.247 [planetinfo.record]: seed = 104 59 54 78 11:58:36.247 [planetinfo.record]: coordinates = 59 199 11:58:36.247 [planetinfo.record]: government = 5; 11:58:36.247 [planetinfo.record]: economy = 7; 11:58:36.247 [planetinfo.record]: techlevel = 6; 11:58:36.247 [planetinfo.record]: population = 37; 11:58:36.248 [planetinfo.record]: productivity = 7992; 11:58:36.248 [planetinfo.record]: name = "Reerqu"; 11:58:36.248 [planetinfo.record]: inhabitant = "Human Colonial"; 11:58:36.248 [planetinfo.record]: inhabitants = "Human Colonials"; 11:58:36.248 [planetinfo.record]: description = "The world Reerqu is mildly fabled for the Reerquian edible moth but plagued by vicious disease."; 11:58:36.268 [planetinfo.record]: air_color = 0.665778, 0.797265, 0.959977, 1; 11:58:36.268 [planetinfo.record]: cloud_alpha = 1.000000; 11:58:36.268 [planetinfo.record]: cloud_color = 0.600037, 0.882812, 0.790027, 1; 11:58:36.268 [planetinfo.record]: cloud_fraction = 0.400000; 11:58:36.268 [planetinfo.record]: land_color = 0.559453, 0.66, 0.579877, 1; 11:58:36.268 [planetinfo.record]: land_fraction = 0.660000; 11:58:36.268 [planetinfo.record]: polar_cloud_color = 0.717637, 0.897266, 0.838325, 1; 11:58:36.268 [planetinfo.record]: polar_land_color = 0.898428, 0.934, 0.905653, 1; 11:58:36.268 [planetinfo.record]: polar_sea_color = 0.948242, 0.827715, 0.721368, 1; 11:58:36.268 [planetinfo.record]: sea_color = 0.517578, 0.25443, 0.0222397, 1; 11:58:36.268 [planetinfo.record]: rotation_speed = 0.000435 11:58:36.268 [planetinfo.record]: planet zpos = 710490.000000 11:58:36.268 [planetinfo.record]: sun_radius = 169623.652954 11:58:36.269 [planetinfo.record]: sun_vector = -0.096 0.688 0.719 11:58:36.269 [planetinfo.record]: sun_distance = 1098030 11:58:36.269 [planetinfo.record]: corona_flare = 0.096117 11:58:36.269 [planetinfo.record]: corona_hues = 0.657921 11:58:36.269 [planetinfo.record]: sun_color = 0.23803, 0.362885, 0.72605, 1 11:58:36.269 [planetinfo.record]: corona_shimmer = 0.523308 11:58:36.269 [planetinfo.record]: station_vector = 0.207 -0.017 0.978 11:58:36.269 [planetinfo.record]: station = coriolis 11:58:40.906 [PLANETINFO OVER]: Done 11:58:40.907 [PLANETINFO LOGGING]: 1 87 11:58:41.911 [planetinfo.record]: seed = 120 121 186 164 11:58:41.911 [planetinfo.record]: coordinates = 121 218 11:58:41.911 [planetinfo.record]: government = 7; 11:58:41.911 [planetinfo.record]: economy = 2; 11:58:41.911 [planetinfo.record]: techlevel = 10; 11:58:41.911 [planetinfo.record]: population = 50; 11:58:41.911 [planetinfo.record]: productivity = 35200; 11:58:41.911 [planetinfo.record]: name = "Zaedvera"; 11:58:41.911 [planetinfo.record]: inhabitant = "Fierce Harmless Bony Lobster"; 11:58:41.911 [planetinfo.record]: inhabitants = "Fierce Harmless Bony Lobsters"; 11:58:41.911 [planetinfo.record]: description = "Zaedvera is cursed by killer mountain goats."; 11:58:41.936 [planetinfo.record]: air_color = 0.612466, 0.53335, 0.89884, 1; 11:58:41.936 [planetinfo.record]: cloud_alpha = 1.000000; 11:58:41.936 [planetinfo.record]: cloud_color = 0.553328, 0.264938, 0.699219, 1; 11:58:41.936 [planetinfo.record]: cloud_fraction = 0.200000; 11:58:41.936 [planetinfo.record]: land_color = 0.143639, 0.548828, 0.206949, 1; 11:58:41.936 [planetinfo.record]: land_fraction = 0.540000; 11:58:41.936 [planetinfo.record]: polar_cloud_color = 0.708414, 0.498415, 0.814648, 1; 11:58:41.936 [planetinfo.record]: polar_land_color = 0.770677, 0.945117, 0.797933, 1; 11:58:41.936 [planetinfo.record]: polar_sea_color = 0.925781, 0.859571, 0.827236, 1; 11:58:41.936 [planetinfo.record]: sea_color = 0.742188, 0.529869, 0.426178, 1; 11:58:41.936 [planetinfo.record]: rotation_speed = 0.001578 11:58:41.936 [planetinfo.record]: planet zpos = 554540.000000 11:58:41.936 [planetinfo.record]: sun_radius = 87563.146362 11:58:41.936 [planetinfo.record]: sun_vector = -0.654 0.653 -0.381 11:58:41.936 [planetinfo.record]: sun_distance = 712980 11:58:41.936 [planetinfo.record]: corona_flare = 0.005443 11:58:41.936 [planetinfo.record]: corona_hues = 0.548584 11:58:41.936 [planetinfo.record]: sun_color = 0.647603, 0.649383, 0.667188, 1 11:58:41.936 [planetinfo.record]: corona_shimmer = 0.614473 11:58:41.936 [planetinfo.record]: station_vector = 0.717 -0.633 0.291 11:58:41.936 [planetinfo.record]: station = coriolis 11:58:46.578 [PLANETINFO OVER]: Done 11:58:46.579 [PLANETINFO LOGGING]: 1 88 11:58:47.583 [planetinfo.record]: seed = 48 180 166 195 11:58:47.583 [planetinfo.record]: coordinates = 180 22 11:58:47.583 [planetinfo.record]: government = 6; 11:58:47.583 [planetinfo.record]: economy = 6; 11:58:47.583 [planetinfo.record]: techlevel = 4; 11:58:47.583 [planetinfo.record]: population = 29; 11:58:47.583 [planetinfo.record]: productivity = 9280; 11:58:47.583 [planetinfo.record]: name = "Gerebied"; 11:58:47.583 [planetinfo.record]: inhabitant = "Large Horned Humanoid"; 11:58:47.583 [planetinfo.record]: inhabitants = "Large Horned Humanoids"; 11:58:47.583 [planetinfo.record]: description = "The world Gerebied is reasonably noted for its inhabitants’ exceptional loathing of food blenders."; 11:58:47.604 [planetinfo.record]: air_color = 0.613889, 0.587405, 0.834451, 1; 11:58:47.604 [planetinfo.record]: cloud_alpha = 1.000000; 11:58:47.604 [planetinfo.record]: cloud_color = 0.408757, 0.359634, 0.505859, 1; 11:58:47.604 [planetinfo.record]: cloud_fraction = 0.420000; 11:58:47.604 [planetinfo.record]: land_color = 0.550692, 0.66, 0.451172, 1; 11:58:47.604 [planetinfo.record]: land_fraction = 0.480000; 11:58:47.604 [planetinfo.record]: polar_cloud_color = 0.640341, 0.596179, 0.727637, 1; 11:58:47.604 [planetinfo.record]: polar_land_color = 0.895328, 0.934, 0.860119, 1; 11:58:47.604 [planetinfo.record]: polar_sea_color = 0.946103, 0.88887, 0.949414, 1; 11:58:47.604 [planetinfo.record]: sea_color = 0.498803, 0.376826, 0.505859, 1; 11:58:47.604 [planetinfo.record]: rotation_speed = 0.001445 11:58:47.604 [planetinfo.record]: planet zpos = 414040.000000 11:58:47.604 [planetinfo.record]: sun_radius = 91553.372803 11:58:47.604 [planetinfo.record]: sun_vector = -0.163 0.518 -0.840 11:58:47.604 [planetinfo.record]: sun_distance = 865720 11:58:47.604 [planetinfo.record]: corona_flare = 0.003726 11:58:47.604 [planetinfo.record]: corona_hues = 0.706131 11:58:47.604 [planetinfo.record]: sun_color = 0.764395, 0.280294, 0.238118, 1 11:58:47.605 [planetinfo.record]: corona_shimmer = 0.274542 11:58:47.605 [planetinfo.record]: station_vector = 0.626 0.646 0.437 11:58:47.605 [planetinfo.record]: station = coriolis 11:58:52.062 [PLANETINFO OVER]: Done 11:58:52.065 [PLANETINFO LOGGING]: 1 89 11:58:53.083 [planetinfo.record]: seed = 208 88 250 237 11:58:53.083 [planetinfo.record]: coordinates = 88 6 11:58:53.083 [planetinfo.record]: government = 2; 11:58:53.083 [planetinfo.record]: economy = 6; 11:58:53.083 [planetinfo.record]: techlevel = 2; 11:58:53.083 [planetinfo.record]: population = 17; 11:58:53.083 [planetinfo.record]: productivity = 3264; 11:58:53.083 [planetinfo.record]: name = "Didira"; 11:58:53.083 [planetinfo.record]: inhabitant = "Insect"; 11:58:53.083 [planetinfo.record]: inhabitants = "Insects"; 11:58:53.083 [planetinfo.record]: description = "The planet Didira is well known for its inhabitants’ weird silliness but ravaged by unpredictable solar activity."; 11:58:53.104 [planetinfo.record]: air_color = 0.427983, 0.809441, 0.852012, 1; 11:58:53.104 [planetinfo.record]: cloud_alpha = 1.000000; 11:58:53.104 [planetinfo.record]: cloud_color = 0.402444, 0.558594, 0.0589142, 1; 11:58:53.104 [planetinfo.record]: cloud_fraction = 0.260000; 11:58:53.104 [planetinfo.record]: land_color = 0.66, 0.213984, 0.569403, 1; 11:58:53.104 [planetinfo.record]: land_fraction = 0.460000; 11:58:53.104 [planetinfo.record]: polar_cloud_color = 0.620093, 0.751367, 0.331291, 1; 11:58:53.104 [planetinfo.record]: polar_land_color = 0.934, 0.776205, 0.901948, 1; 11:58:53.104 [planetinfo.record]: polar_sea_color = 0.900376, 0.93457, 0.886473, 1; 11:58:53.104 [planetinfo.record]: sea_color = 0.558538, 0.654297, 0.519604, 1; 11:58:53.104 [planetinfo.record]: rotation_speed = 0.002544 11:58:53.104 [planetinfo.record]: planet zpos = 872480.000000 11:58:53.105 [planetinfo.record]: sun_radius = 129872.004395 11:58:53.105 [planetinfo.record]: sun_vector = -0.625 -0.780 -0.037 11:58:53.105 [planetinfo.record]: sun_distance = 1371040 11:58:53.105 [planetinfo.record]: corona_flare = 0.065768 11:58:53.105 [planetinfo.record]: corona_hues = 0.864799 11:58:53.105 [planetinfo.record]: sun_color = 0.646387, 0.755566, 0.807138, 1 11:58:53.105 [planetinfo.record]: corona_shimmer = 0.381867 11:58:53.105 [planetinfo.record]: station_vector = -0.625 -0.664 0.410 11:58:53.105 [planetinfo.record]: station = coriolis 11:58:57.779 [PLANETINFO OVER]: Done 11:58:57.780 [PLANETINFO LOGGING]: 1 90 11:58:58.785 [planetinfo.record]: seed = 88 207 182 176 11:58:58.785 [planetinfo.record]: coordinates = 207 148 11:58:58.785 [planetinfo.record]: government = 3; 11:58:58.785 [planetinfo.record]: economy = 4; 11:58:58.785 [planetinfo.record]: techlevel = 8; 11:58:58.785 [planetinfo.record]: population = 40; 11:58:58.785 [planetinfo.record]: productivity = 13440; 11:58:58.785 [planetinfo.record]: name = "Errara"; 11:58:58.785 [planetinfo.record]: inhabitant = "Harmless Bony Lobster"; 11:58:58.785 [planetinfo.record]: inhabitants = "Harmless Bony Lobsters"; 11:58:58.785 [planetinfo.record]: description = "The world Errara is fabled for its fabulous cuisine and its ancient mountains."; 11:58:58.802 [planetinfo.record]: air_color = 0.70739, 0.809908, 0.918352, 1; 11:58:58.802 [planetinfo.record]: cloud_alpha = 1.000000; 11:58:58.802 [planetinfo.record]: cloud_color = 0.689728, 0.757812, 0.727494, 1; 11:58:58.802 [planetinfo.record]: cloud_fraction = 0.570000; 11:58:58.802 [planetinfo.record]: land_color = 0.290344, 0.381396, 0.523438, 1; 11:58:58.802 [planetinfo.record]: land_fraction = 0.560000; 11:58:58.802 [planetinfo.record]: polar_cloud_color = 0.793791, 0.841016, 0.819986, 1; 11:58:58.802 [planetinfo.record]: polar_land_color = 0.842155, 0.883367, 0.947656, 1; 11:58:58.802 [planetinfo.record]: polar_sea_color = 0.920117, 0.859886, 0.801508, 1; 11:58:58.802 [planetinfo.record]: sea_color = 0.798828, 0.589662, 0.386932, 1; 11:58:58.802 [planetinfo.record]: rotation_speed = 0.004878 11:58:58.802 [planetinfo.record]: planet zpos = 392990.000000 11:58:58.802 [planetinfo.record]: sun_radius = 61157.445068 11:58:58.802 [planetinfo.record]: sun_vector = 0.059 -0.861 0.505 11:58:58.802 [planetinfo.record]: sun_distance = 544140 11:58:58.803 [planetinfo.record]: corona_flare = 0.030370 11:58:58.803 [planetinfo.record]: corona_hues = 0.654198 11:58:58.803 [planetinfo.record]: sun_color = 0.273731, 0.454663, 0.830194, 1 11:58:58.803 [planetinfo.record]: corona_shimmer = 0.369679 11:58:58.803 [planetinfo.record]: station_vector = -0.639 0.667 0.384 11:58:58.803 [planetinfo.record]: station = coriolis 11:59:04.196 [PLANETINFO OVER]: Done 11:59:04.197 [PLANETINFO LOGGING]: 1 91 11:59:05.201 [planetinfo.record]: seed = 8 89 90 1 11:59:05.201 [planetinfo.record]: coordinates = 89 148 11:59:05.201 [planetinfo.record]: government = 1; 11:59:05.201 [planetinfo.record]: economy = 6; 11:59:05.201 [planetinfo.record]: techlevel = 3; 11:59:05.201 [planetinfo.record]: population = 20; 11:59:05.201 [planetinfo.record]: productivity = 3200; 11:59:05.201 [planetinfo.record]: name = "Lereus"; 11:59:05.201 [planetinfo.record]: inhabitant = "Human Colonial"; 11:59:05.201 [planetinfo.record]: inhabitants = "Human Colonials"; 11:59:05.201 [planetinfo.record]: description = "The planet Lereus is very famous for its hoopy night life but scourged by deadly tree grubs."; 11:59:05.204 [planetinfo.record]: air_color = 0.494674, 0.485516, 0.914449, 1; 11:59:05.204 [planetinfo.record]: cloud_alpha = 1.000000; 11:59:05.204 [planetinfo.record]: cloud_color = 0.189939, 0.142807, 0.746094, 1; 11:59:05.204 [planetinfo.record]: cloud_fraction = 0.550000; 11:59:05.204 [planetinfo.record]: land_color = 0.23291, 0.492977, 0.5625, 1; 11:59:05.204 [planetinfo.record]: land_fraction = 0.660000; 11:59:05.204 [planetinfo.record]: polar_cloud_color = 0.446379, 0.413382, 0.835742, 1; 11:59:05.204 [planetinfo.record]: polar_land_color = 0.805505, 0.914589, 0.94375, 1; 11:59:05.204 [planetinfo.record]: polar_sea_color = 0.859914, 0.920117, 0.860855, 1; 11:59:05.205 [planetinfo.record]: sea_color = 0.58976, 0.798828, 0.593027, 1; 11:59:05.205 [planetinfo.record]: rotation_speed = 0.000873 11:59:05.205 [planetinfo.record]: planet zpos = 379320.000000 11:59:05.205 [planetinfo.record]: sun_radius = 72316.267548 11:59:05.205 [planetinfo.record]: sun_vector = -0.541 0.828 -0.148 11:59:05.205 [planetinfo.record]: sun_distance = 727030 11:59:05.205 [planetinfo.record]: corona_flare = 0.054828 11:59:05.205 [planetinfo.record]: corona_hues = 0.996475 11:59:05.205 [planetinfo.record]: sun_color = 0.585522, 0.677854, 0.704169, 1 11:59:05.205 [planetinfo.record]: corona_shimmer = 0.573459 11:59:05.205 [planetinfo.record]: station_vector = 0.988 0.135 0.075 11:59:05.205 [planetinfo.record]: station = coriolis 11:59:09.607 [PLANETINFO OVER]: Done 11:59:09.607 [PLANETINFO LOGGING]: 1 92 11:59:10.612 [planetinfo.record]: seed = 224 56 102 112 11:59:10.612 [planetinfo.record]: coordinates = 56 72 11:59:10.612 [planetinfo.record]: government = 4; 11:59:10.612 [planetinfo.record]: economy = 0; 11:59:10.612 [planetinfo.record]: techlevel = 9; 11:59:10.612 [planetinfo.record]: population = 41; 11:59:10.612 [planetinfo.record]: productivity = 26240; 11:59:10.612 [planetinfo.record]: name = "Erenanri"; 11:59:10.612 [planetinfo.record]: inhabitant = "Human Colonial"; 11:59:10.612 [planetinfo.record]: inhabitants = "Human Colonials"; 11:59:10.612 [planetinfo.record]: description = "This planet is a dull place."; 11:59:10.622 [planetinfo.record]: air_color = 0.657404, 0.874125, 0.84514, 1; 11:59:10.622 [planetinfo.record]: cloud_alpha = 1.000000; 11:59:10.622 [planetinfo.record]: cloud_color = 0.625, 0.589714, 0.534668, 1; 11:59:10.622 [planetinfo.record]: cloud_fraction = 0.450000; 11:59:10.622 [planetinfo.record]: land_color = 0.0799219, 0.53764, 0.66, 1; 11:59:10.622 [planetinfo.record]: land_fraction = 0.490000; 11:59:10.622 [planetinfo.record]: polar_cloud_color = 0.78125, 0.753683, 0.710678, 1; 11:59:10.622 [planetinfo.record]: polar_land_color = 0.728775, 0.89071, 0.934, 1; 11:59:10.622 [planetinfo.record]: polar_sea_color = 0.940234, 0.917917, 0.886336, 1; 11:59:10.622 [planetinfo.record]: sea_color = 0.597656, 0.540913, 0.460616, 1; 11:59:10.622 [planetinfo.record]: rotation_speed = 0.000762 11:59:10.622 [planetinfo.record]: planet zpos = 373360.000000 11:59:10.622 [planetinfo.record]: sun_radius = 68475.568848 11:59:10.622 [planetinfo.record]: sun_vector = 0.039 0.916 -0.400 11:59:10.622 [planetinfo.record]: sun_distance = 545680 11:59:10.622 [planetinfo.record]: corona_flare = 0.034171 11:59:10.622 [planetinfo.record]: corona_hues = 0.565063 11:59:10.622 [planetinfo.record]: sun_color = 0.703171, 0.628807, 0.562837, 1 11:59:10.622 [planetinfo.record]: corona_shimmer = 0.567864 11:59:10.623 [planetinfo.record]: station_vector = 0.346 -0.703 0.621 11:59:10.623 [planetinfo.record]: station = coriolis 11:59:14.990 [PLANETINFO OVER]: Done 11:59:14.991 [PLANETINFO LOGGING]: 1 93 11:59:16.012 [planetinfo.record]: seed = 32 254 218 139 11:59:16.012 [planetinfo.record]: coordinates = 254 155 11:59:16.012 [planetinfo.record]: government = 4; 11:59:16.012 [planetinfo.record]: economy = 3; 11:59:16.012 [planetinfo.record]: techlevel = 8; 11:59:16.012 [planetinfo.record]: population = 40; 11:59:16.012 [planetinfo.record]: productivity = 17920; 11:59:16.012 [planetinfo.record]: name = "Macea"; 11:59:16.012 [planetinfo.record]: inhabitant = "Small Black Furry Rodent"; 11:59:16.012 [planetinfo.record]: inhabitants = "Small Black Furry Rodents"; 11:59:16.012 [planetinfo.record]: description = "The world Macea is beset by dreadful earthquakes."; 11:59:16.021 [planetinfo.record]: air_color = 0.529444, 0.98274, 0.936266, 1; 11:59:16.021 [planetinfo.record]: cloud_alpha = 1.000000; 11:59:16.021 [planetinfo.record]: cloud_color = 0.951172, 0.72946, 0.204353, 1; 11:59:16.022 [planetinfo.record]: cloud_fraction = 0.430000; 11:59:16.022 [planetinfo.record]: land_color = 0.53625, 0.66, 0.575889, 1; 11:59:16.022 [planetinfo.record]: land_fraction = 0.660000; 11:59:16.022 [planetinfo.record]: polar_cloud_color = 0.928027, 0.792829, 0.472623, 1; 11:59:16.023 [planetinfo.record]: polar_land_color = 0.890219, 0.934, 0.904242, 1; 11:59:16.023 [planetinfo.record]: polar_sea_color = 0.911367, 0.863182, 0.944336, 1; 11:59:16.023 [planetinfo.record]: sea_color = 0.478907, 0.365295, 0.556641, 1; 11:59:16.023 [planetinfo.record]: rotation_speed = 0.001899 11:59:16.023 [planetinfo.record]: planet zpos = 706320.000000 11:59:16.023 [planetinfo.record]: sun_radius = 131809.899902 11:59:16.023 [planetinfo.record]: sun_vector = -0.143 -0.681 0.719 11:59:16.023 [planetinfo.record]: sun_distance = 1000620 11:59:16.023 [planetinfo.record]: corona_flare = 0.049814 11:59:16.023 [planetinfo.record]: corona_hues = 0.789642 11:59:16.023 [planetinfo.record]: sun_color = 0.460826, 0.542223, 0.661139, 1 11:59:16.024 [planetinfo.record]: corona_shimmer = 0.372575 11:59:16.024 [planetinfo.record]: station_vector = 0.788 -0.357 0.501 11:59:16.024 [planetinfo.record]: station = coriolis 11:59:20.559 [PLANETINFO OVER]: Done 11:59:20.560 [PLANETINFO LOGGING]: 1 94 11:59:21.563 [planetinfo.record]: seed = 200 96 182 53 11:59:21.563 [planetinfo.record]: coordinates = 96 175 11:59:21.563 [planetinfo.record]: government = 1; 11:59:21.563 [planetinfo.record]: economy = 7; 11:59:21.563 [planetinfo.record]: techlevel = 1; 11:59:21.563 [planetinfo.record]: population = 13; 11:59:21.563 [planetinfo.record]: productivity = 1560; 11:59:21.563 [planetinfo.record]: name = "Laceteed"; 11:59:21.563 [planetinfo.record]: inhabitant = "Red Rodent"; 11:59:21.563 [planetinfo.record]: inhabitants = "Red Rodents"; 11:59:21.563 [planetinfo.record]: description = "The world Laceteed is a dull place."; 11:59:21.576 [planetinfo.record]: air_color = 0.620081, 0.889084, 0.813216, 1; 11:59:21.576 [planetinfo.record]: cloud_alpha = 1.000000; 11:59:21.576 [planetinfo.record]: cloud_color = 0.669922, 0.497105, 0.463188, 1; 11:59:21.576 [planetinfo.record]: cloud_fraction = 0.070000; 11:59:21.576 [planetinfo.record]: land_color = 0.484688, 0.66, 0.561387, 1; 11:59:21.576 [planetinfo.record]: land_fraction = 0.300000; 11:59:21.576 [planetinfo.record]: polar_cloud_color = 0.801465, 0.672246, 0.646885, 1; 11:59:21.576 [planetinfo.record]: polar_land_color = 0.871977, 0.934, 0.899112, 1; 11:59:21.576 [planetinfo.record]: polar_sea_color = 0.949414, 0.754102, 0.715769, 1; 11:59:21.576 [planetinfo.record]: sea_color = 0.505859, 0.0895998, 0.00790405, 1; 11:59:21.576 [planetinfo.record]: rotation_speed = 0.004179 11:59:21.576 [planetinfo.record]: planet zpos = 503040.000000 11:59:21.576 [planetinfo.record]: sun_radius = 94151.132812 11:59:21.576 [planetinfo.record]: sun_vector = 0.938 -0.195 0.287 11:59:21.576 [planetinfo.record]: sun_distance = 1006080 11:59:21.576 [planetinfo.record]: corona_flare = 0.002563 11:59:21.576 [planetinfo.record]: corona_hues = 0.506950 11:59:21.576 [planetinfo.record]: sun_color = 0.740848, 0.676691, 0.497189, 1 11:59:21.576 [planetinfo.record]: corona_shimmer = 0.376419 11:59:21.576 [planetinfo.record]: station_vector = -0.104 0.842 0.529 11:59:21.577 [planetinfo.record]: station = coriolis 11:59:26.841 [PLANETINFO OVER]: Done 11:59:26.842 [PLANETINFO LOGGING]: 1 95 11:59:27.846 [planetinfo.record]: seed = 24 88 122 122 11:59:27.846 [planetinfo.record]: coordinates = 88 220 11:59:27.846 [planetinfo.record]: government = 3; 11:59:27.846 [planetinfo.record]: economy = 4; 11:59:27.846 [planetinfo.record]: techlevel = 5; 11:59:27.847 [planetinfo.record]: population = 28; 11:59:27.847 [planetinfo.record]: productivity = 9408; 11:59:27.847 [planetinfo.record]: name = "Qualema"; 11:59:27.847 [planetinfo.record]: inhabitant = "Human Colonial"; 11:59:27.847 [planetinfo.record]: inhabitants = "Human Colonials"; 11:59:27.847 [planetinfo.record]: description = "This world is fabled for its unusual tropical forests."; 11:59:27.863 [planetinfo.record]: air_color = 0.441515, 0.671304, 0.885832, 1; 11:59:27.863 [planetinfo.record]: cloud_alpha = 1.000000; 11:59:27.863 [planetinfo.record]: cloud_color = 0.0618896, 0.660156, 0.337653, 1; 11:59:27.863 [planetinfo.record]: cloud_fraction = 0.360000; 11:59:27.863 [planetinfo.record]: land_color = 0.528516, 0.66, 0.656918, 1; 11:59:27.863 [planetinfo.record]: land_fraction = 0.420000; 11:59:27.863 [planetinfo.record]: polar_cloud_color = 0.345605, 0.79707, 0.553702, 1; 11:59:27.863 [planetinfo.record]: polar_land_color = 0.887482, 0.934, 0.93291, 1; 11:59:27.863 [planetinfo.record]: polar_sea_color = 0.893331, 0.933789, 0.886279, 1; 11:59:27.863 [planetinfo.record]: sea_color = 0.547362, 0.662109, 0.52736, 1; 11:59:27.863 [planetinfo.record]: rotation_speed = 0.000303 11:59:27.864 [planetinfo.record]: planet zpos = 655680.000000 11:59:27.864 [planetinfo.record]: sun_radius = 144096.992188 11:59:27.864 [planetinfo.record]: sun_vector = 0.016 -0.993 -0.116 11:59:27.866 [planetinfo.record]: sun_distance = 1092800 11:59:27.866 [planetinfo.record]: corona_flare = 0.078166 11:59:27.866 [planetinfo.record]: corona_hues = 0.588676 11:59:27.866 [planetinfo.record]: sun_color = 0.792944, 0.645779, 0.536589, 1 11:59:27.866 [planetinfo.record]: corona_shimmer = 0.684092 11:59:27.866 [planetinfo.record]: station_vector = 0.993 -0.082 0.081 11:59:27.866 [planetinfo.record]: station = coriolis 11:59:32.202 [PLANETINFO OVER]: Done 11:59:32.203 [PLANETINFO LOGGING]: 1 96 11:59:33.223 [planetinfo.record]: seed = 16 171 166 219 11:59:33.223 [planetinfo.record]: coordinates = 171 129 11:59:33.223 [planetinfo.record]: government = 2; 11:59:33.223 [planetinfo.record]: economy = 1; 11:59:33.223 [planetinfo.record]: techlevel = 10; 11:59:33.223 [planetinfo.record]: population = 44; 11:59:33.223 [planetinfo.record]: productivity = 19008; 11:59:33.223 [planetinfo.record]: name = "Anusa"; 11:59:33.223 [planetinfo.record]: inhabitant = "Horned Humanoid"; 11:59:33.223 [planetinfo.record]: inhabitants = "Horned Humanoids"; 11:59:33.223 [planetinfo.record]: description = "The world Anusa is reasonably noted for its fabulous goat soup."; 11:59:33.237 [planetinfo.record]: air_color = 0.670471, 0.774082, 0.970383, 1; 11:59:33.238 [planetinfo.record]: cloud_alpha = 1.000000; 11:59:33.238 [planetinfo.record]: cloud_color = 0.614136, 0.914062, 0.907033, 1; 11:59:33.238 [planetinfo.record]: cloud_fraction = 0.480000; 11:59:33.238 [planetinfo.record]: land_color = 0.226412, 0.121172, 0.66, 1; 11:59:33.238 [planetinfo.record]: land_fraction = 0.620000; 11:59:33.238 [planetinfo.record]: polar_cloud_color = 0.724435, 0.911328, 0.906948, 1; 11:59:33.238 [planetinfo.record]: polar_land_color = 0.780602, 0.743369, 0.934, 1; 11:59:33.238 [planetinfo.record]: polar_sea_color = 0.934766, 0.895513, 0.876343, 1; 11:59:33.238 [planetinfo.record]: sea_color = 0.652344, 0.54277, 0.489258, 1; 11:59:33.238 [planetinfo.record]: rotation_speed = 0.000204 11:59:33.238 [planetinfo.record]: planet zpos = 696360.000000 11:59:33.238 [planetinfo.record]: sun_radius = 147709.265900 11:59:33.238 [planetinfo.record]: sun_vector = -0.126 0.986 0.111 11:59:33.238 [planetinfo.record]: sun_distance = 1102570 11:59:33.238 [planetinfo.record]: corona_flare = 0.034789 11:59:33.239 [planetinfo.record]: corona_hues = 0.921402 11:59:33.239 [planetinfo.record]: sun_color = 0.713846, 0.703467, 0.686087, 1 11:59:33.239 [planetinfo.record]: corona_shimmer = 1.163884 11:59:33.239 [planetinfo.record]: station_vector = -0.906 0.181 0.382 11:59:33.239 [planetinfo.record]: station = coriolis 11:59:37.978 [PLANETINFO OVER]: Done 11:59:37.979 [PLANETINFO LOGGING]: 1 97 11:59:38.982 [planetinfo.record]: seed = 240 114 58 10 11:59:38.982 [planetinfo.record]: coordinates = 114 143 11:59:38.982 [planetinfo.record]: government = 6; 11:59:38.982 [planetinfo.record]: economy = 7; 11:59:38.983 [planetinfo.record]: techlevel = 5; 11:59:38.983 [planetinfo.record]: population = 34; 11:59:38.983 [planetinfo.record]: productivity = 8160; 11:59:38.983 [planetinfo.record]: name = "Arines"; 11:59:38.983 [planetinfo.record]: inhabitant = "Human Colonial"; 11:59:38.983 [planetinfo.record]: inhabitants = "Human Colonials"; 11:59:38.983 [planetinfo.record]: description = "The world Arines is a dull world."; 11:59:39.003 [planetinfo.record]: air_color = 0.590614, 0.56944, 0.848109, 1; 11:59:39.003 [planetinfo.record]: cloud_alpha = 1.000000; 11:59:39.003 [planetinfo.record]: cloud_color = 0.386608, 0.335388, 0.546875, 1; 11:59:39.003 [planetinfo.record]: cloud_fraction = 0.460000; 11:59:39.003 [planetinfo.record]: land_color = 0.66, 0.154688, 0.320493, 1; 11:59:39.003 [planetinfo.record]: land_fraction = 0.530000; 11:59:39.003 [planetinfo.record]: polar_cloud_color = 0.609437, 0.565763, 0.746094, 1; 11:59:39.003 [planetinfo.record]: polar_land_color = 0.934, 0.755227, 0.813887, 1; 11:59:39.003 [planetinfo.record]: polar_sea_color = 0.940039, 0.938106, 0.890558, 1; 11:59:39.003 [planetinfo.record]: sea_color = 0.599609, 0.594678, 0.473364, 1; 11:59:39.003 [planetinfo.record]: rotation_speed = 0.001193 11:59:39.003 [planetinfo.record]: planet zpos = 658800.000000 11:59:39.003 [planetinfo.record]: sun_radius = 115855.787659 11:59:39.003 [planetinfo.record]: sun_vector = -0.224 0.887 -0.404 11:59:39.003 [planetinfo.record]: sun_distance = 1043100 11:59:39.003 [planetinfo.record]: corona_flare = 0.034210 11:59:39.003 [planetinfo.record]: corona_hues = 0.641983 11:59:39.003 [planetinfo.record]: sun_color = 0.566237, 0.581055, 0.666321, 1 11:59:39.004 [planetinfo.record]: corona_shimmer = 0.411436 11:59:39.004 [planetinfo.record]: station_vector = -0.113 -0.993 0.044 11:59:39.004 [planetinfo.record]: station = coriolis 11:59:44.200 [PLANETINFO OVER]: Done 11:59:44.201 [PLANETINFO LOGGING]: 1 98 11:59:45.221 [planetinfo.record]: seed = 184 159 54 53 11:59:45.221 [planetinfo.record]: coordinates = 159 137 11:59:45.221 [planetinfo.record]: government = 7; 11:59:45.221 [planetinfo.record]: economy = 1; 11:59:45.221 [planetinfo.record]: techlevel = 13; 11:59:45.221 [planetinfo.record]: population = 61; 11:59:45.221 [planetinfo.record]: productivity = 48312; 11:59:45.221 [planetinfo.record]: name = "Laribebi"; 11:59:45.221 [planetinfo.record]: inhabitant = "Human Colonial"; 11:59:45.221 [planetinfo.record]: inhabitants = "Human Colonials"; 11:59:45.221 [planetinfo.record]: description = "This world is plagued by frequent earthquakes."; 11:59:45.227 [planetinfo.record]: air_color = 0.439437, 0.794092, 0.844207, 1; 11:59:45.228 [planetinfo.record]: cloud_alpha = 1.000000; 11:59:45.228 [planetinfo.record]: cloud_color = 0.363102, 0.535156, 0.0857086, 1; 11:59:45.228 [planetinfo.record]: cloud_fraction = 0.350000; 11:59:45.228 [planetinfo.record]: land_color = 0.194702, 0.482496, 0.623047, 1; 11:59:45.228 [planetinfo.record]: land_fraction = 0.440000; 11:59:45.228 [planetinfo.record]: polar_cloud_color = 0.59196, 0.74082, 0.351962, 1; 11:59:45.228 [planetinfo.record]: polar_land_color = 0.776529, 0.884813, 0.937695, 1; 11:59:45.228 [planetinfo.record]: polar_sea_color = 0.877248, 0.926562, 0.904602, 1; 11:59:45.228 [planetinfo.record]: sea_color = 0.578033, 0.734375, 0.664754, 1; 11:59:45.229 [planetinfo.record]: rotation_speed = 0.003563 11:59:45.229 [planetinfo.record]: planet zpos = 510600.000000 11:59:45.229 [planetinfo.record]: sun_radius = 117840.308380 11:59:45.229 [planetinfo.record]: sun_vector = -0.334 -0.870 -0.362 11:59:45.229 [planetinfo.record]: sun_distance = 851000 11:59:45.229 [planetinfo.record]: corona_flare = 0.026311 11:59:45.229 [planetinfo.record]: corona_hues = 0.813187 11:59:45.229 [planetinfo.record]: sun_color = 0.279229, 0.673797, 0.81731, 1 11:59:45.229 [planetinfo.record]: corona_shimmer = 0.391578 11:59:45.229 [planetinfo.record]: station_vector = -0.960 -0.269 0.073 11:59:45.229 [planetinfo.record]: station = dodecahedron 11:59:50.537 [PLANETINFO OVER]: Done 11:59:50.539 [PLANETINFO LOGGING]: 1 99 11:59:51.541 [planetinfo.record]: seed = 168 198 26 88 11:59:51.541 [planetinfo.record]: coordinates = 198 51 11:59:51.541 [planetinfo.record]: government = 5; 11:59:51.541 [planetinfo.record]: economy = 3; 11:59:51.541 [planetinfo.record]: techlevel = 9; 11:59:51.541 [planetinfo.record]: population = 45; 11:59:51.541 [planetinfo.record]: productivity = 22680; 11:59:51.541 [planetinfo.record]: name = "Edater"; 11:59:51.541 [planetinfo.record]: inhabitant = "Human Colonial"; 11:59:51.541 [planetinfo.record]: inhabitants = "Human Colonials"; 11:59:51.541 [planetinfo.record]: description = "Edater is an unremarkable dump."; 11:59:51.564 [planetinfo.record]: air_color = 0.6496, 0.885182, 0.861028, 1; 11:59:51.564 [planetinfo.record]: cloud_alpha = 1.000000; 11:59:51.564 [planetinfo.record]: cloud_color = 0.658203, 0.619275, 0.527077, 1; 11:59:51.564 [planetinfo.record]: cloud_fraction = 0.350000; 11:59:51.564 [planetinfo.record]: land_color = 0.411767, 0.328598, 0.576172, 1; 11:59:51.564 [planetinfo.record]: land_fraction = 0.540000; 11:59:51.564 [planetinfo.record]: polar_cloud_color = 0.796191, 0.766761, 0.697056, 1; 11:59:51.564 [planetinfo.record]: polar_land_color = 0.875158, 0.84115, 0.942383, 1; 11:59:51.564 [planetinfo.record]: polar_sea_color = 0.927539, 0.871493, 0.837865, 1; 11:59:51.564 [planetinfo.record]: sea_color = 0.724609, 0.549472, 0.444389, 1; 11:59:51.564 [planetinfo.record]: rotation_speed = 0.004679 11:59:51.564 [planetinfo.record]: planet zpos = 658060.000000 11:59:51.564 [planetinfo.record]: sun_radius = 128166.632996 11:59:51.564 [planetinfo.record]: sun_vector = -0.426 0.018 0.905 11:59:51.564 [planetinfo.record]: sun_distance = 1063020 11:59:51.564 [planetinfo.record]: corona_flare = 0.054376 11:59:51.564 [planetinfo.record]: corona_hues = 0.608070 11:59:51.564 [planetinfo.record]: sun_color = 0.840988, 0.797872, 0.542229, 1 11:59:51.565 [planetinfo.record]: corona_shimmer = 0.496681 11:59:51.565 [planetinfo.record]: station_vector = 0.944 -0.322 0.077 11:59:51.565 [planetinfo.record]: station = coriolis 11:59:57.106 [PLANETINFO OVER]: Done 11:59:57.107 [PLANETINFO LOGGING]: 1 100 11:59:58.113 [planetinfo.record]: seed = 192 26 102 221 11:59:58.113 [planetinfo.record]: coordinates = 26 112 11:59:58.113 [planetinfo.record]: government = 0; 11:59:58.113 [planetinfo.record]: economy = 2; 11:59:58.113 [planetinfo.record]: techlevel = 7; 11:59:58.113 [planetinfo.record]: population = 31; 11:59:58.113 [planetinfo.record]: productivity = 7936; 11:59:58.113 [planetinfo.record]: name = "Isusle"; 11:59:58.113 [planetinfo.record]: inhabitant = "Human Colonial"; 11:59:58.113 [planetinfo.record]: inhabitants = "Human Colonials"; 11:59:58.113 [planetinfo.record]: description = "This world is very fabled for its unusual tropical forests."; 11:59:58.129 [planetinfo.record]: air_color = 0.495944, 0.585272, 0.874125, 1; 11:59:58.129 [planetinfo.record]: cloud_alpha = 1.000000; 11:59:58.130 [planetinfo.record]: cloud_color = 0.187988, 0.491848, 0.625, 1; 11:59:58.130 [planetinfo.record]: cloud_fraction = 0.330000; 11:59:58.130 [planetinfo.record]: land_color = 0.00257813, 0.66, 0.151525, 1; 11:59:58.130 [planetinfo.record]: land_fraction = 0.310000; 11:59:58.130 [planetinfo.record]: polar_cloud_color = 0.439835, 0.677225, 0.78125, 1; 11:59:58.130 [planetinfo.record]: polar_land_color = 0.701412, 0.934, 0.754108, 1; 11:59:58.130 [planetinfo.record]: polar_sea_color = 0.943359, 0.918906, 0.892875, 1; 11:59:58.130 [planetinfo.record]: sea_color = 0.566406, 0.507678, 0.44516, 1; 11:59:58.130 [planetinfo.record]: rotation_speed = 0.004558 11:59:58.130 [planetinfo.record]: planet zpos = 740400.000000 11:59:58.130 [planetinfo.record]: sun_radius = 151054.660034 11:59:58.130 [planetinfo.record]: sun_vector = 0.493 -0.061 0.868 11:59:58.130 [planetinfo.record]: sun_distance = 1357400 11:59:58.131 [planetinfo.record]: corona_flare = 0.014151 11:59:58.131 [planetinfo.record]: corona_hues = 0.566566 11:59:58.131 [planetinfo.record]: sun_color = 0.630064, 0.757359, 0.830176, 1 11:59:58.131 [planetinfo.record]: corona_shimmer = 0.534482 11:59:58.131 [planetinfo.record]: station_vector = 0.670 -0.730 0.135 11:59:58.132 [planetinfo.record]: station = coriolis 12:00:03.543 [PLANETINFO OVER]: Done 12:00:03.544 [PLANETINFO LOGGING]: 1 101 12:00:04.547 [planetinfo.record]: seed = 64 167 26 113 12:00:04.547 [planetinfo.record]: coordinates = 167 97 12:00:04.547 [planetinfo.record]: government = 0; 12:00:04.547 [planetinfo.record]: economy = 3; 12:00:04.547 [planetinfo.record]: techlevel = 7; 12:00:04.548 [planetinfo.record]: population = 32; 12:00:04.548 [planetinfo.record]: productivity = 7168; 12:00:04.548 [planetinfo.record]: name = "Atorat"; 12:00:04.548 [planetinfo.record]: inhabitant = "Human Colonial"; 12:00:04.548 [planetinfo.record]: inhabitants = "Human Colonials"; 12:00:04.548 [planetinfo.record]: description = "Atorat is well known for its inhabitants’ ancient mating traditions but ravaged by occasional solar activity."; 12:00:04.560 [planetinfo.record]: air_color = 0.615623, 0.922254, 0.902782, 1; 12:00:04.560 [planetinfo.record]: cloud_alpha = 1.000000; 12:00:04.560 [planetinfo.record]: cloud_color = 0.769531, 0.714437, 0.462921, 1; 12:00:04.560 [planetinfo.record]: cloud_fraction = 0.140000; 12:00:04.560 [planetinfo.record]: land_color = 0.4953, 0.414062, 0.5, 1; 12:00:04.560 [planetinfo.record]: land_fraction = 0.480000; 12:00:04.560 [planetinfo.record]: polar_cloud_color = 0.846289, 0.808421, 0.635543, 1; 12:00:04.560 [planetinfo.record]: polar_land_color = 0.947768, 0.90918, 0.95, 1; 12:00:04.560 [planetinfo.record]: polar_sea_color = 0.9292, 0.932422, 0.873508, 1; 12:00:04.560 [planetinfo.record]: sea_color = 0.666441, 0.675781, 0.504988, 1; 12:00:04.560 [planetinfo.record]: rotation_speed = 0.000623 12:00:04.560 [planetinfo.record]: planet zpos = 356290.000000 12:00:04.560 [planetinfo.record]: sun_radius = 103298.973236 12:00:04.560 [planetinfo.record]: sun_vector = 0.646 -0.749 -0.148 12:00:04.560 [planetinfo.record]: sun_distance = 615410 12:00:04.560 [planetinfo.record]: corona_flare = 0.017192 12:00:04.560 [planetinfo.record]: corona_hues = 0.584320 12:00:04.560 [planetinfo.record]: sun_color = 0.618778, 0.637087, 0.685074, 1 12:00:04.561 [planetinfo.record]: corona_shimmer = 1.001253 12:00:04.561 [planetinfo.record]: station_vector = 0.370 -0.773 0.516 12:00:04.561 [planetinfo.record]: station = coriolis 12:00:08.928 [PLANETINFO OVER]: Done 12:00:08.928 [PLANETINFO LOGGING]: 1 102 12:00:09.932 [planetinfo.record]: seed = 40 124 54 135 12:00:09.932 [planetinfo.record]: coordinates = 124 145 12:00:09.932 [planetinfo.record]: government = 5; 12:00:09.932 [planetinfo.record]: economy = 1; 12:00:09.932 [planetinfo.record]: techlevel = 9; 12:00:09.932 [planetinfo.record]: population = 43; 12:00:09.932 [planetinfo.record]: productivity = 27864; 12:00:09.932 [planetinfo.record]: name = "Solaed"; 12:00:09.932 [planetinfo.record]: inhabitant = "Human Colonial"; 12:00:09.932 [planetinfo.record]: inhabitants = "Human Colonials"; 12:00:09.932 [planetinfo.record]: description = "Solaed is ravaged by unpredictable civil war."; 12:00:09.956 [planetinfo.record]: air_color = 0.521668, 0.980139, 0.897351, 1; 12:00:09.956 [planetinfo.record]: cloud_alpha = 1.000000; 12:00:09.956 [planetinfo.record]: cloud_color = 0.943359, 0.540082, 0.18425, 1; 12:00:09.956 [planetinfo.record]: cloud_fraction = 0.090000; 12:00:09.956 [planetinfo.record]: land_color = 0.323789, 0.309753, 0.566406, 1; 12:00:09.956 [planetinfo.record]: land_fraction = 0.270000; 12:00:09.956 [planetinfo.record]: polar_cloud_color = 0.924512, 0.677499, 0.459547, 1; 12:00:09.956 [planetinfo.record]: polar_land_color = 0.842339, 0.836494, 0.943359, 1; 12:00:09.956 [planetinfo.record]: polar_sea_color = 0.930273, 0.866979, 0.847603, 1; 12:00:09.956 [planetinfo.record]: sea_color = 0.697266, 0.507501, 0.449409, 1; 12:00:09.956 [planetinfo.record]: rotation_speed = 0.002971 12:00:09.956 [planetinfo.record]: planet zpos = 473200.000000 12:00:09.956 [planetinfo.record]: sun_radius = 132158.948975 12:00:09.956 [planetinfo.record]: sun_vector = 0.303 0.743 0.597 12:00:09.956 [planetinfo.record]: sun_distance = 899080 12:00:09.956 [planetinfo.record]: corona_flare = 0.063310 12:00:09.956 [planetinfo.record]: corona_hues = 0.891029 12:00:09.956 [planetinfo.record]: sun_color = 0.654626, 0.465342, 0.427421, 1 12:00:09.957 [planetinfo.record]: corona_shimmer = 0.508646 12:00:09.957 [planetinfo.record]: station_vector = -0.114 0.760 0.639 12:00:09.957 [planetinfo.record]: station = coriolis 12:00:14.448 [PLANETINFO OVER]: Done 12:00:14.448 [PLANETINFO LOGGING]: 1 103 12:00:15.465 [planetinfo.record]: seed = 184 180 58 226 12:00:15.466 [planetinfo.record]: coordinates = 180 152 12:00:15.466 [planetinfo.record]: government = 7; 12:00:15.466 [planetinfo.record]: economy = 0; 12:00:15.466 [planetinfo.record]: techlevel = 11; 12:00:15.466 [planetinfo.record]: population = 52; 12:00:15.466 [planetinfo.record]: productivity = 45760; 12:00:15.466 [planetinfo.record]: name = "Xeabiti"; 12:00:15.466 [planetinfo.record]: inhabitant = "Human Colonial"; 12:00:15.466 [planetinfo.record]: inhabitants = "Human Colonials"; 12:00:15.466 [planetinfo.record]: description = "The world Xeabiti is fabled for its weird rock formations and the Xeabitiian deadly monkey."; 12:00:15.478 [planetinfo.record]: air_color = 0.441659, 0.716818, 0.870223, 1; 12:00:15.478 [planetinfo.record]: cloud_alpha = 1.000000; 12:00:15.478 [planetinfo.record]: cloud_color = 0.0742645, 0.613281, 0.120586, 1; 12:00:15.478 [planetinfo.record]: cloud_fraction = 0.400000; 12:00:15.478 [planetinfo.record]: land_color = 0.469219, 0.66, 0.579514, 1; 12:00:15.478 [planetinfo.record]: land_fraction = 0.300000; 12:00:15.479 [planetinfo.record]: polar_cloud_color = 0.34972, 0.775977, 0.386351, 1; 12:00:15.479 [planetinfo.record]: polar_land_color = 0.866504, 0.934, 0.905525, 1; 12:00:15.479 [planetinfo.record]: polar_sea_color = 0.881394, 0.930078, 0.890142, 1; 12:00:15.479 [planetinfo.record]: sea_color = 0.55282, 0.699219, 0.579126, 1; 12:00:15.479 [planetinfo.record]: rotation_speed = 0.004115 12:00:15.479 [planetinfo.record]: planet zpos = 385880.000000 12:00:15.479 [planetinfo.record]: sun_radius = 101016.118774 12:00:15.479 [planetinfo.record]: sun_vector = -0.255 0.572 -0.780 12:00:15.479 [planetinfo.record]: sun_distance = 736680 12:00:15.479 [planetinfo.record]: corona_flare = 0.067245 12:00:15.479 [planetinfo.record]: corona_hues = 0.887314 12:00:15.479 [planetinfo.record]: sun_color = 0.750983, 0.672914, 0.583454, 1 12:00:15.479 [planetinfo.record]: corona_shimmer = 0.542265 12:00:15.480 [planetinfo.record]: station_vector = -0.465 0.086 0.881 12:00:15.480 [planetinfo.record]: station = dodecahedron 12:00:20.347 [PLANETINFO OVER]: Done 12:00:20.348 [PLANETINFO LOGGING]: 1 104 12:00:21.353 [planetinfo.record]: seed = 240 215 166 205 12:00:21.353 [planetinfo.record]: coordinates = 215 198 12:00:21.353 [planetinfo.record]: government = 6; 12:00:21.353 [planetinfo.record]: economy = 6; 12:00:21.353 [planetinfo.record]: techlevel = 7; 12:00:21.354 [planetinfo.record]: population = 41; 12:00:21.354 [planetinfo.record]: productivity = 13120; 12:00:21.354 [planetinfo.record]: name = "Dimaatma"; 12:00:21.354 [planetinfo.record]: inhabitant = "Bug-Eyed Lizard"; 12:00:21.354 [planetinfo.record]: inhabitants = "Bug-Eyed Lizards"; 12:00:21.354 [planetinfo.record]: description = "This planet is beset by dreadful earthquakes."; 12:00:21.368 [planetinfo.record]: air_color = 0.639771, 0.811427, 0.971684, 1; 12:00:21.368 [planetinfo.record]: cloud_alpha = 1.000000; 12:00:21.368 [planetinfo.record]: cloud_color = 0.527115, 0.917969, 0.707274, 1; 12:00:21.368 [planetinfo.record]: cloud_fraction = 0.240000; 12:00:21.368 [planetinfo.record]: land_color = 0.350625, 0.66, 0.565737, 1; 12:00:21.368 [planetinfo.record]: land_fraction = 0.450000; 12:00:21.368 [planetinfo.record]: polar_cloud_color = 0.670102, 0.913086, 0.782102, 1; 12:00:21.368 [planetinfo.record]: polar_land_color = 0.824547, 0.934, 0.900651, 1; 12:00:21.368 [planetinfo.record]: polar_sea_color = 0.941602, 0.88458, 0.883211, 1; 12:00:21.368 [planetinfo.record]: sea_color = 0.583984, 0.442524, 0.439129, 1; 12:00:21.368 [planetinfo.record]: rotation_speed = 0.003954 12:00:21.368 [planetinfo.record]: planet zpos = 826670.000000 12:00:21.368 [planetinfo.record]: sun_radius = 168768.302460 12:00:21.368 [planetinfo.record]: sun_vector = 0.573 -0.460 -0.678 12:00:21.369 [planetinfo.record]: sun_distance = 1208210 12:00:21.369 [planetinfo.record]: corona_flare = 0.087123 12:00:21.369 [planetinfo.record]: corona_hues = 0.652283 12:00:21.369 [planetinfo.record]: sun_color = 0.69463, 0.698312, 0.739014, 1 12:00:21.369 [planetinfo.record]: corona_shimmer = 0.258494 12:00:21.369 [planetinfo.record]: station_vector = -0.963 -0.094 0.254 12:00:21.370 [planetinfo.record]: station = coriolis 12:00:26.829 [PLANETINFO OVER]: Done 12:00:26.830 [PLANETINFO LOGGING]: 1 105 12:00:27.833 [planetinfo.record]: seed = 16 75 122 200 12:00:27.833 [planetinfo.record]: coordinates = 75 17 12:00:27.833 [planetinfo.record]: government = 2; 12:00:27.833 [planetinfo.record]: economy = 1; 12:00:27.833 [planetinfo.record]: techlevel = 10; 12:00:27.833 [planetinfo.record]: population = 44; 12:00:27.833 [planetinfo.record]: productivity = 19008; 12:00:27.833 [planetinfo.record]: name = "Usceed"; 12:00:27.833 [planetinfo.record]: inhabitant = "Human Colonial"; 12:00:27.833 [planetinfo.record]: inhabitants = "Human Colonials"; 12:00:27.833 [planetinfo.record]: description = "Usceed is most noted for the Usceedian deadly lobstoid and its pink Usceedian Benulobi Benulobiweed plantations."; 12:00:27.848 [planetinfo.record]: air_color = 0.438591, 0.726259, 0.870873, 1; 12:00:27.848 [planetinfo.record]: cloud_alpha = 1.000000; 12:00:27.848 [planetinfo.record]: cloud_color = 0.0672913, 0.615234, 0.0758529, 1; 12:00:27.848 [planetinfo.record]: cloud_fraction = 0.390000; 12:00:27.848 [planetinfo.record]: land_color = 0.461714, 0.282822, 0.658203, 1; 12:00:27.848 [planetinfo.record]: land_fraction = 0.430000; 12:00:27.848 [planetinfo.record]: polar_cloud_color = 0.344426, 0.776855, 0.351183, 1; 12:00:27.848 [planetinfo.record]: polar_land_color = 0.864461, 0.800986, 0.93418, 1; 12:00:27.848 [planetinfo.record]: polar_sea_color = 0.940234, 0.896016, 0.88248, 1; 12:00:27.848 [planetinfo.record]: sea_color = 0.597656, 0.485227, 0.45081, 1; 12:00:27.848 [planetinfo.record]: rotation_speed = 0.000025 12:00:27.848 [planetinfo.record]: planet zpos = 642070.000000 12:00:27.848 [planetinfo.record]: sun_radius = 92332.681732 12:00:27.848 [planetinfo.record]: sun_vector = -0.437 0.699 0.566 12:00:27.848 [planetinfo.record]: sun_distance = 938410 12:00:27.848 [planetinfo.record]: corona_flare = 0.057660 12:00:27.848 [planetinfo.record]: corona_hues = 0.834099 12:00:27.848 [planetinfo.record]: sun_color = 0.697675, 0.643694, 0.556449, 1 12:00:27.848 [planetinfo.record]: corona_shimmer = 0.243655 12:00:27.848 [planetinfo.record]: station_vector = -0.709 -0.530 0.465 12:00:27.848 [planetinfo.record]: station = coriolis 12:00:33.038 [PLANETINFO OVER]: Done 12:00:33.039 [PLANETINFO LOGGING]: 1 106 12:00:34.042 [planetinfo.record]: seed = 24 38 182 131 12:00:34.042 [planetinfo.record]: coordinates = 38 56 12:00:34.042 [planetinfo.record]: government = 3; 12:00:34.042 [planetinfo.record]: economy = 0; 12:00:34.042 [planetinfo.record]: techlevel = 11; 12:00:34.042 [planetinfo.record]: population = 48; 12:00:34.042 [planetinfo.record]: productivity = 26880; 12:00:34.042 [planetinfo.record]: name = "Gexein"; 12:00:34.042 [planetinfo.record]: inhabitant = "Large Black Frog"; 12:00:34.042 [planetinfo.record]: inhabitants = "Large Black Frogs"; 12:00:34.042 [planetinfo.record]: description = "This planet is a dull world."; 12:00:34.066 [planetinfo.record]: air_color = 0.52365, 0.532646, 0.876727, 1; 12:00:34.066 [planetinfo.record]: cloud_alpha = 1.000000; 12:00:34.066 [planetinfo.record]: cloud_color = 0.247192, 0.271294, 0.632812, 1; 12:00:34.066 [planetinfo.record]: cloud_fraction = 0.370000; 12:00:34.066 [planetinfo.record]: land_color = 0.1309, 0.0386719, 0.66, 1; 12:00:34.066 [planetinfo.record]: land_fraction = 0.490000; 12:00:34.066 [planetinfo.record]: polar_cloud_color = 0.48588, 0.504561, 0.784766, 1; 12:00:34.066 [planetinfo.record]: polar_land_color = 0.746811, 0.714182, 0.934, 1; 12:00:34.066 [planetinfo.record]: polar_sea_color = 0.942187, 0.889152, 0.885141, 1; 12:00:34.066 [planetinfo.record]: sea_color = 0.578125, 0.447955, 0.43811, 1; 12:00:34.066 [planetinfo.record]: rotation_speed = 0.002331 12:00:34.066 [planetinfo.record]: planet zpos = 470860.000000 12:00:34.066 [planetinfo.record]: sun_radius = 87352.232056 12:00:34.067 [planetinfo.record]: sun_vector = 0.588 0.336 -0.736 12:00:34.067 [planetinfo.record]: sun_distance = 724400 12:00:34.067 [planetinfo.record]: corona_flare = 0.077840 12:00:34.067 [planetinfo.record]: corona_hues = 0.891823 12:00:34.067 [planetinfo.record]: sun_color = 0.710281, 0.710238, 0.709477, 1 12:00:34.067 [planetinfo.record]: corona_shimmer = 0.595327 12:00:34.067 [planetinfo.record]: station_vector = -0.843 0.533 0.070 12:00:34.067 [planetinfo.record]: station = dodecahedron 12:00:38.808 [PLANETINFO OVER]: Done 12:00:38.809 [PLANETINFO LOGGING]: 1 107 12:00:39.813 [planetinfo.record]: seed = 72 242 218 96 12:00:39.813 [planetinfo.record]: coordinates = 242 140 12:00:39.813 [planetinfo.record]: government = 1; 12:00:39.813 [planetinfo.record]: economy = 6; 12:00:39.813 [planetinfo.record]: techlevel = 4; 12:00:39.813 [planetinfo.record]: population = 24; 12:00:39.813 [planetinfo.record]: productivity = 3840; 12:00:39.813 [planetinfo.record]: name = "Onen"; 12:00:39.813 [planetinfo.record]: inhabitant = "Large Blue Feline"; 12:00:39.813 [planetinfo.record]: inhabitants = "Large Blue Felines"; 12:00:39.813 [planetinfo.record]: description = "The planet Onen is reasonably noted for its inhabitants’ exceptional love for tourists and its weird volcanoes."; 12:00:39.828 [planetinfo.record]: air_color = 0.475974, 0.633974, 0.870873, 1; 12:00:39.828 [planetinfo.record]: cloud_alpha = 1.000000; 12:00:39.828 [planetinfo.record]: cloud_color = 0.146599, 0.615234, 0.527365, 1; 12:00:39.828 [planetinfo.record]: cloud_fraction = 0.320000; 12:00:39.828 [planetinfo.record]: land_color = 0.535156, 0.416, 0.524916, 1; 12:00:39.828 [planetinfo.record]: land_fraction = 0.590000; 12:00:39.828 [planetinfo.record]: polar_cloud_color = 0.407015, 0.776855, 0.70751, 1; 12:00:39.828 [planetinfo.record]: polar_land_color = 0.946484, 0.893799, 0.941957, 1; 12:00:39.828 [planetinfo.record]: polar_sea_color = 0.85302, 0.916797, 0.832638, 1; 12:00:39.828 [planetinfo.record]: sea_color = 0.600511, 0.832031, 0.52652, 1; 12:00:39.828 [planetinfo.record]: rotation_speed = 0.003440 12:00:39.828 [planetinfo.record]: planet zpos = 366960.000000 12:00:39.828 [planetinfo.record]: sun_radius = 87829.776611 12:00:39.828 [planetinfo.record]: sun_vector = 0.150 0.965 0.216 12:00:39.828 [planetinfo.record]: sun_distance = 581020 12:00:39.828 [planetinfo.record]: corona_flare = 0.078535 12:00:39.828 [planetinfo.record]: corona_hues = 0.895737 12:00:39.828 [planetinfo.record]: sun_color = 0.676354, 0.723613, 0.735217, 1 12:00:39.828 [planetinfo.record]: corona_shimmer = 0.502700 12:00:39.829 [planetinfo.record]: station_vector = -0.840 0.492 0.227 12:00:39.829 [planetinfo.record]: station = coriolis 12:00:44.205 [PLANETINFO OVER]: Done 12:00:44.207 [PLANETINFO LOGGING]: 1 108 12:00:45.209 [planetinfo.record]: seed = 160 114 102 132 12:00:45.209 [planetinfo.record]: coordinates = 114 50 12:00:45.209 [planetinfo.record]: government = 4; 12:00:45.209 [planetinfo.record]: economy = 2; 12:00:45.209 [planetinfo.record]: techlevel = 9; 12:00:45.209 [planetinfo.record]: population = 43; 12:00:45.209 [planetinfo.record]: productivity = 22016; 12:00:45.209 [planetinfo.record]: name = "Zaesre"; 12:00:45.209 [planetinfo.record]: inhabitant = "Human Colonial"; 12:00:45.209 [planetinfo.record]: inhabitants = "Human Colonials"; 12:00:45.209 [planetinfo.record]: description = "This world is plagued by frequent earthquakes."; 12:00:45.232 [planetinfo.record]: air_color = 0.493692, 0.522846, 0.896889, 1; 12:00:45.232 [planetinfo.record]: cloud_alpha = 1.000000; 12:00:45.232 [planetinfo.record]: cloud_color = 0.17334, 0.278969, 0.693359, 1; 12:00:45.232 [planetinfo.record]: cloud_fraction = 0.510000; 12:00:45.232 [planetinfo.record]: land_color = 0.66, 0.43667, 0.149531, 1; 12:00:45.232 [planetinfo.record]: land_fraction = 0.300000; 12:00:45.232 [planetinfo.record]: polar_cloud_color = 0.431381, 0.508697, 0.812012, 1; 12:00:45.232 [planetinfo.record]: polar_land_color = 0.934, 0.854989, 0.753402, 1; 12:00:45.232 [planetinfo.record]: polar_sea_color = 0.943555, 0.881726, 0.912157, 1; 12:00:45.232 [planetinfo.record]: sea_color = 0.564453, 0.416505, 0.489323, 1; 12:00:45.232 [planetinfo.record]: rotation_speed = 0.003280 12:00:45.232 [planetinfo.record]: planet zpos = 593100.000000 12:00:45.232 [planetinfo.record]: sun_radius = 62602.385559 12:00:45.233 [planetinfo.record]: sun_vector = -0.245 -0.141 0.959 12:00:45.233 [planetinfo.record]: sun_distance = 909420 12:00:45.233 [planetinfo.record]: corona_flare = 0.083516 12:00:45.233 [planetinfo.record]: corona_hues = 0.803246 12:00:45.233 [planetinfo.record]: sun_color = 0.62922, 0.720177, 0.844211, 1 12:00:45.233 [planetinfo.record]: corona_shimmer = 0.467180 12:00:45.234 [planetinfo.record]: station_vector = -0.129 -0.188 0.974 12:00:45.234 [planetinfo.record]: station = coriolis 12:00:50.368 [PLANETINFO OVER]: Done 12:00:50.369 [PLANETINFO LOGGING]: 1 109 12:00:51.372 [planetinfo.record]: seed = 96 206 90 24 12:00:51.372 [planetinfo.record]: coordinates = 206 32 12:00:51.372 [planetinfo.record]: government = 4; 12:00:51.372 [planetinfo.record]: economy = 0; 12:00:51.373 [planetinfo.record]: techlevel = 11; 12:00:51.373 [planetinfo.record]: population = 49; 12:00:51.373 [planetinfo.record]: productivity = 31360; 12:00:51.373 [planetinfo.record]: name = "Edsodi"; 12:00:51.373 [planetinfo.record]: inhabitant = "Human Colonial"; 12:00:51.373 [planetinfo.record]: inhabitants = "Human Colonials"; 12:00:51.373 [planetinfo.record]: description = "This world is reasonably notable for its exotic night life but ravaged by frequent earthquakes."; 12:00:51.388 [planetinfo.record]: air_color = 0.633697, 0.895104, 0.929408, 1; 12:00:51.388 [planetinfo.record]: cloud_alpha = 1.000000; 12:00:51.388 [planetinfo.record]: cloud_color = 0.689966, 0.791016, 0.509834, 1; 12:00:51.388 [planetinfo.record]: cloud_fraction = 0.450000; 12:00:51.388 [planetinfo.record]: land_color = 0.470508, 0.281016, 0.66, 1; 12:00:51.388 [planetinfo.record]: land_fraction = 0.630000; 12:00:51.388 [planetinfo.record]: polar_cloud_color = 0.787616, 0.855957, 0.665791, 1; 12:00:51.388 [planetinfo.record]: polar_land_color = 0.86696, 0.79992, 0.934, 1; 12:00:51.388 [planetinfo.record]: polar_sea_color = 0.941602, 0.917358, 0.889004, 1; 12:00:51.388 [planetinfo.record]: sea_color = 0.583984, 0.523839, 0.4535, 1; 12:00:51.388 [planetinfo.record]: rotation_speed = 0.004339 12:00:51.388 [planetinfo.record]: planet zpos = 608400.000000 12:00:51.388 [planetinfo.record]: sun_radius = 157417.094421 12:00:51.388 [planetinfo.record]: sun_vector = 0.036 0.417 -0.908 12:00:51.388 [planetinfo.record]: sun_distance = 811200 12:00:51.388 [planetinfo.record]: corona_flare = 0.075905 12:00:51.388 [planetinfo.record]: corona_hues = 0.889717 12:00:51.388 [planetinfo.record]: sun_color = 0.569347, 0.649775, 0.764996, 1 12:00:51.388 [planetinfo.record]: corona_shimmer = 0.370440 12:00:51.388 [planetinfo.record]: station_vector = -0.045 -0.588 0.808 12:00:51.388 [planetinfo.record]: station = icosahedron 12:00:56.449 [PLANETINFO OVER]: Done 12:00:56.450 [PLANETINFO LOGGING]: 1 110 12:00:57.453 [planetinfo.record]: seed = 136 13 182 2 12:00:57.453 [planetinfo.record]: coordinates = 13 237 12:00:57.453 [planetinfo.record]: government = 1; 12:00:57.453 [planetinfo.record]: economy = 7; 12:00:57.453 [planetinfo.record]: techlevel = 2; 12:00:57.453 [planetinfo.record]: population = 17; 12:00:57.453 [planetinfo.record]: productivity = 2040; 12:00:57.453 [planetinfo.record]: name = "Xerirea"; 12:00:57.453 [planetinfo.record]: inhabitant = "Large Green Slimy Lizard"; 12:00:57.453 [planetinfo.record]: inhabitants = "Large Green Slimy Lizards"; 12:00:57.453 [planetinfo.record]: description = "Xerirea is very notable for its inhabitants’ ingrained shyness."; 12:00:57.468 [planetinfo.record]: air_color = 0.444831, 0.552537, 0.9164, 1; 12:00:57.468 [planetinfo.record]: cloud_alpha = 1.000000; 12:00:57.468 [planetinfo.record]: cloud_color = 0.0411224, 0.518712, 0.751953, 1; 12:00:57.468 [planetinfo.record]: cloud_fraction = 0.430000; 12:00:57.468 [planetinfo.record]: land_color = 0.66, 0.446499, 0.144375, 1; 12:00:57.468 [planetinfo.record]: land_fraction = 0.390000; 12:00:57.468 [planetinfo.record]: polar_cloud_color = 0.343048, 0.675848, 0.838379, 1; 12:00:57.468 [planetinfo.record]: polar_land_color = 0.934, 0.858466, 0.751578, 1; 12:00:57.468 [planetinfo.record]: polar_sea_color = 0.904495, 0.879515, 0.949023, 1; 12:00:57.468 [planetinfo.record]: sea_color = 0.414091, 0.36042, 0.509766, 1; 12:00:57.468 [planetinfo.record]: rotation_speed = 0.001643 12:00:57.468 [planetinfo.record]: planet zpos = 467740.000000 12:00:57.468 [planetinfo.record]: sun_radius = 86343.153076 12:00:57.468 [planetinfo.record]: sun_vector = -0.458 -0.795 -0.399 12:00:57.468 [planetinfo.record]: sun_distance = 567970 12:00:57.468 [planetinfo.record]: corona_flare = 0.006754 12:00:57.468 [planetinfo.record]: corona_hues = 0.539978 12:00:57.468 [planetinfo.record]: sun_color = 0.734464, 0.716584, 0.561181, 1 12:00:57.468 [planetinfo.record]: corona_shimmer = 0.596020 12:00:57.468 [planetinfo.record]: station_vector = -0.485 -0.846 0.223 12:00:57.468 [planetinfo.record]: station = coriolis 12:01:02.147 [PLANETINFO OVER]: Done 12:01:02.147 [PLANETINFO LOGGING]: 1 111 12:01:03.150 [planetinfo.record]: seed = 88 15 250 27 12:01:03.150 [planetinfo.record]: coordinates = 15 14 12:01:03.150 [planetinfo.record]: government = 3; 12:01:03.150 [planetinfo.record]: economy = 6; 12:01:03.150 [planetinfo.record]: techlevel = 6; 12:01:03.150 [planetinfo.record]: population = 34; 12:01:03.150 [planetinfo.record]: productivity = 7616; 12:01:03.150 [planetinfo.record]: name = "Anorcequ"; 12:01:03.150 [planetinfo.record]: inhabitant = "Green Bug-Eyed Bird"; 12:01:03.150 [planetinfo.record]: inhabitants = "Green Bug-Eyed Birds"; 12:01:03.150 [planetinfo.record]: description = "Anorcequ is most famous for its vast dense forests and its great volcanoes."; 12:01:03.164 [planetinfo.record]: air_color = 0.728958, 0.508022, 0.952172, 1; 12:01:03.164 [planetinfo.record]: cloud_alpha = 1.000000; 12:01:03.164 [planetinfo.record]: cloud_color = 0.859375, 0.171204, 0.509913, 1; 12:01:03.164 [planetinfo.record]: cloud_fraction = 0.220000; 12:01:03.164 [planetinfo.record]: land_color = 0.66, 0.206814, 0.195938, 1; 12:01:03.164 [planetinfo.record]: land_fraction = 0.670000; 12:01:03.164 [planetinfo.record]: polar_cloud_color = 0.886719, 0.442926, 0.661355, 1; 12:01:03.164 [planetinfo.record]: polar_land_color = 0.934, 0.773668, 0.76982, 1; 12:01:03.164 [planetinfo.record]: polar_sea_color = 0.869333, 0.923633, 0.894362, 1; 12:01:03.164 [planetinfo.record]: sea_color = 0.58409, 0.763672, 0.666866, 1; 12:01:03.164 [planetinfo.record]: rotation_speed = 0.002749 12:01:03.164 [planetinfo.record]: planet zpos = 621170.000000 12:01:03.164 [planetinfo.record]: sun_radius = 141298.217926 12:01:03.164 [planetinfo.record]: sun_vector = 0.600 -0.608 -0.520 12:01:03.164 [planetinfo.record]: sun_distance = 1129400 12:01:03.164 [planetinfo.record]: corona_flare = 0.098407 12:01:03.164 [planetinfo.record]: corona_hues = 0.606659 12:01:03.164 [planetinfo.record]: sun_color = 0.701874, 0.528435, 0.430454, 1 12:01:03.164 [planetinfo.record]: corona_shimmer = 0.578715 12:01:03.164 [planetinfo.record]: station_vector = 0.260 0.944 0.205 12:01:03.164 [planetinfo.record]: station = coriolis 12:01:07.751 [PLANETINFO OVER]: Done 12:01:07.752 [PLANETINFO LOGGING]: 1 112 12:01:08.754 [planetinfo.record]: seed = 208 186 166 89 12:01:08.754 [planetinfo.record]: coordinates = 186 101 12:01:08.755 [planetinfo.record]: government = 2; 12:01:08.755 [planetinfo.record]: economy = 5; 12:01:08.755 [planetinfo.record]: techlevel = 5; 12:01:08.755 [planetinfo.record]: population = 28; 12:01:08.755 [planetinfo.record]: productivity = 6720; 12:01:08.755 [planetinfo.record]: name = "Ororre"; 12:01:08.755 [planetinfo.record]: inhabitant = "Yellow Rodent"; 12:01:08.755 [planetinfo.record]: inhabitants = "Yellow Rodents"; 12:01:08.755 [planetinfo.record]: description = "Ororre is a revolting little planet."; 12:01:08.776 [planetinfo.record]: air_color = 0.46294, 0.754169, 0.835102, 1; 12:01:08.776 [planetinfo.record]: cloud_alpha = 1.000000; 12:01:08.776 [planetinfo.record]: cloud_color = 0.25885, 0.507812, 0.132904, 1; 12:01:08.776 [planetinfo.record]: cloud_fraction = 0.560000; 12:01:08.776 [planetinfo.record]: land_color = 0.2005, 0.570312, 0.552978, 1; 12:01:08.776 [planetinfo.record]: land_fraction = 0.440000; 12:01:08.776 [planetinfo.record]: polar_cloud_color = 0.505287, 0.728516, 0.39236, 1; 12:01:08.776 [planetinfo.record]: polar_land_color = 0.790105, 0.942969, 0.935803, 1; 12:01:08.776 [planetinfo.record]: polar_sea_color = 0.924609, 0.923382, 0.846054, 1; 12:01:08.776 [planetinfo.record]: sea_color = 0.753906, 0.749903, 0.497696, 1; 12:01:08.776 [planetinfo.record]: rotation_speed = 0.002647 12:01:08.776 [planetinfo.record]: planet zpos = 530600.000000 12:01:08.776 [planetinfo.record]: sun_radius = 99865.597839 12:01:08.776 [planetinfo.record]: sun_vector = -0.147 -0.336 0.930 12:01:08.776 [planetinfo.record]: sun_distance = 1114260 12:01:08.776 [planetinfo.record]: corona_flare = 0.001425 12:01:08.776 [planetinfo.record]: corona_hues = 0.799728 12:01:08.776 [planetinfo.record]: sun_color = 0.722156, 0.679812, 0.596156, 1 12:01:08.776 [planetinfo.record]: corona_shimmer = 0.245213 12:01:08.776 [planetinfo.record]: station_vector = -0.289 0.587 0.756 12:01:08.776 [planetinfo.record]: station = coriolis 12:01:13.623 [PLANETINFO OVER]: Done 12:01:13.623 [PLANETINFO LOGGING]: 1 113 12:01:14.644 [planetinfo.record]: seed = 48 97 186 104 12:01:14.644 [planetinfo.record]: coordinates = 97 142 12:01:14.644 [planetinfo.record]: government = 6; 12:01:14.644 [planetinfo.record]: economy = 6; 12:01:14.644 [planetinfo.record]: techlevel = 5; 12:01:14.644 [planetinfo.record]: population = 33; 12:01:14.644 [planetinfo.record]: productivity = 10560; 12:01:14.644 [planetinfo.record]: name = "Ustile"; 12:01:14.644 [planetinfo.record]: inhabitant = "Small Blue Insect"; 12:01:14.644 [planetinfo.record]: inhabitants = "Small Blue Insects"; 12:01:14.644 [planetinfo.record]: description = "This planet is fabled for its weird volcanoes."; 12:01:14.660 [planetinfo.record]: air_color = 0.707535, 0.576868, 0.888434, 1; 12:01:14.660 [planetinfo.record]: cloud_alpha = 1.000000; 12:01:14.660 [planetinfo.record]: cloud_color = 0.667969, 0.365295, 0.585207, 1; 12:01:14.660 [planetinfo.record]: cloud_fraction = 0.490000; 12:01:14.660 [planetinfo.record]: land_color = 0.556641, 0.555842, 0.554466, 1; 12:01:14.660 [planetinfo.record]: land_fraction = 0.340000; 12:01:14.660 [planetinfo.record]: polar_cloud_color = 0.800586, 0.573857, 0.73859, 1; 12:01:14.660 [planetinfo.record]: polar_land_color = 0.944336, 0.943997, 0.943414, 1; 12:01:14.660 [planetinfo.record]: polar_sea_color = 0.932031, 0.91685, 0.87651, 1; 12:01:14.660 [planetinfo.record]: sea_color = 0.679688, 0.635402, 0.517731, 1; 12:01:14.660 [planetinfo.record]: rotation_speed = 0.003734 12:01:14.660 [planetinfo.record]: planet zpos = 694540.000000 12:01:14.660 [planetinfo.record]: sun_radius = 89338.271790 12:01:14.660 [planetinfo.record]: sun_vector = -0.811 -0.503 -0.300 12:01:14.660 [planetinfo.record]: sun_distance = 1190640 12:01:14.660 [planetinfo.record]: corona_flare = 0.043938 12:01:14.660 [planetinfo.record]: corona_hues = 0.801048 12:01:14.660 [planetinfo.record]: sun_color = 0.817191, 0.65838, 0.293871, 1 12:01:14.661 [planetinfo.record]: corona_shimmer = 0.265528 12:01:14.661 [planetinfo.record]: station_vector = -0.282 -0.617 0.735 12:01:14.661 [planetinfo.record]: station = coriolis 12:01:19.638 [PLANETINFO OVER]: Done 12:01:19.639 [PLANETINFO LOGGING]: 1 114 12:01:20.648 [planetinfo.record]: seed = 120 226 54 92 12:01:20.649 [planetinfo.record]: coordinates = 226 33 12:01:20.649 [planetinfo.record]: government = 7; 12:01:20.649 [planetinfo.record]: economy = 1; 12:01:20.649 [planetinfo.record]: techlevel = 12; 12:01:20.649 [planetinfo.record]: population = 57; 12:01:20.649 [planetinfo.record]: productivity = 45144; 12:01:20.649 [planetinfo.record]: name = "Teonan"; 12:01:20.649 [planetinfo.record]: inhabitant = "Human Colonial"; 12:01:20.649 [planetinfo.record]: inhabitants = "Human Colonials"; 12:01:20.649 [planetinfo.record]: description = "The planet Teonan is mildly well known for its hoopy night life."; 12:01:20.659 [planetinfo.record]: air_color = 0.440535, 0.794228, 0.844207, 1; 12:01:20.659 [planetinfo.record]: cloud_alpha = 1.000000; 12:01:20.659 [planetinfo.record]: cloud_color = 0.363902, 0.535156, 0.0877991, 1; 12:01:20.659 [planetinfo.record]: cloud_fraction = 0.430000; 12:01:20.660 [planetinfo.record]: land_color = 0.564287, 0.103125, 0.66, 1; 12:01:20.660 [planetinfo.record]: land_fraction = 0.390000; 12:01:20.660 [planetinfo.record]: polar_cloud_color = 0.592653, 0.74082, 0.353771, 1; 12:01:20.660 [planetinfo.record]: polar_land_color = 0.900138, 0.736984, 0.934, 1; 12:01:20.660 [planetinfo.record]: polar_sea_color = 0.941016, 0.905143, 0.885694, 1; 12:01:20.660 [planetinfo.record]: sea_color = 0.589844, 0.499902, 0.451138, 1; 12:01:20.660 [planetinfo.record]: rotation_speed = 0.001091 12:01:20.660 [planetinfo.record]: planet zpos = 733680.000000 12:01:20.660 [planetinfo.record]: sun_radius = 119157.508850 12:01:20.660 [planetinfo.record]: sun_vector = 0.756 0.551 0.353 12:01:20.660 [planetinfo.record]: sun_distance = 1222800 12:01:20.661 [planetinfo.record]: corona_flare = 0.088301 12:01:20.661 [planetinfo.record]: corona_hues = 0.611046 12:01:20.661 [planetinfo.record]: sun_color = 0.499167, 0.530743, 0.677124, 1 12:01:20.661 [planetinfo.record]: corona_shimmer = 0.371212 12:01:20.662 [planetinfo.record]: station_vector = 0.413 -0.898 0.151 12:01:20.662 [planetinfo.record]: station = icosahedron 12:01:26.442 [PLANETINFO OVER]: Done 12:01:26.443 [PLANETINFO LOGGING]: 1 115 12:01:27.455 [planetinfo.record]: seed = 232 91 154 91 12:01:27.455 [planetinfo.record]: coordinates = 91 159 12:01:27.455 [planetinfo.record]: government = 5; 12:01:27.455 [planetinfo.record]: economy = 7; 12:01:27.455 [planetinfo.record]: techlevel = 6; 12:01:27.455 [planetinfo.record]: population = 37; 12:01:27.455 [planetinfo.record]: productivity = 7992; 12:01:27.455 [planetinfo.record]: name = "Anvere"; 12:01:27.455 [planetinfo.record]: inhabitant = "Yellow Fat Insect"; 12:01:27.455 [planetinfo.record]: inhabitants = "Yellow Fat Insects"; 12:01:27.455 [planetinfo.record]: description = "This planet is reasonably noted for its fabulous goat soup."; 12:01:27.472 [planetinfo.record]: air_color = 0.659361, 0.876727, 0.856138, 1; 12:01:27.472 [planetinfo.record]: cloud_alpha = 1.000000; 12:01:27.472 [planetinfo.record]: cloud_color = 0.632812, 0.607804, 0.541351, 1; 12:01:27.472 [planetinfo.record]: cloud_fraction = 0.350000; 12:01:27.472 [planetinfo.record]: land_color = 0.325508, 0.333011, 0.517578, 1; 12:01:27.472 [planetinfo.record]: land_fraction = 0.630000; 12:01:27.472 [planetinfo.record]: polar_cloud_color = 0.784766, 0.765382, 0.713876, 1; 12:01:27.472 [planetinfo.record]: polar_land_color = 0.860271, 0.863707, 0.948242, 1; 12:01:27.472 [planetinfo.record]: polar_sea_color = 0.884941, 0.92793, 0.871203, 1; 12:01:27.472 [planetinfo.record]: sea_color = 0.58715, 0.720703, 0.544469, 1; 12:01:27.472 [planetinfo.record]: rotation_speed = 0.002164 12:01:27.472 [planetinfo.record]: planet zpos = 743990.000000 12:01:27.472 [planetinfo.record]: sun_radius = 191512.140045 12:01:27.472 [planetinfo.record]: sun_vector = 0.730 -0.453 -0.512 12:01:27.472 [planetinfo.record]: sun_distance = 972910 12:01:27.472 [planetinfo.record]: corona_flare = 0.065512 12:01:27.472 [planetinfo.record]: corona_hues = 0.717377 12:01:27.472 [planetinfo.record]: sun_color = 0.632743, 0.640338, 0.698914, 1 12:01:27.472 [planetinfo.record]: corona_shimmer = 0.380025 12:01:27.473 [planetinfo.record]: station_vector = 0.209 -0.858 0.470 12:01:27.473 [planetinfo.record]: station = coriolis 12:01:32.843 [PLANETINFO OVER]: Done 12:01:32.845 [PLANETINFO LOGGING]: 1 116 12:01:33.848 [planetinfo.record]: seed = 128 192 102 37 12:01:33.848 [planetinfo.record]: coordinates = 192 14 12:01:33.848 [planetinfo.record]: government = 0; 12:01:33.848 [planetinfo.record]: economy = 6; 12:01:33.848 [planetinfo.record]: techlevel = 1; 12:01:33.848 [planetinfo.record]: population = 11; 12:01:33.848 [planetinfo.record]: productivity = 1408; 12:01:33.848 [planetinfo.record]: name = "Ceraqu"; 12:01:33.848 [planetinfo.record]: inhabitant = "Human Colonial"; 12:01:33.849 [planetinfo.record]: inhabitants = "Human Colonials"; 12:01:33.849 [planetinfo.record]: description = "This world is most fabled for Ceraquian lethal brandy but scourged by frequent civil war."; 12:01:33.855 [planetinfo.record]: air_color = 0.462256, 0.618507, 0.885832, 1; 12:01:33.856 [planetinfo.record]: cloud_alpha = 1.000000; 12:01:33.856 [planetinfo.record]: cloud_color = 0.108307, 0.660156, 0.60842, 1; 12:01:33.856 [planetinfo.record]: cloud_fraction = 0.550000; 12:01:33.856 [planetinfo.record]: land_color = 0.66, 0.365328, 0.170156, 1; 12:01:33.856 [planetinfo.record]: land_fraction = 0.610000; 12:01:33.856 [planetinfo.record]: polar_cloud_color = 0.380632, 0.79707, 0.758029, 1; 12:01:33.856 [planetinfo.record]: polar_land_color = 0.934, 0.829749, 0.760699, 1; 12:01:33.856 [planetinfo.record]: polar_sea_color = 0.941797, 0.928166, 0.891948, 1; 12:01:33.856 [planetinfo.record]: sea_color = 0.582031, 0.548336, 0.458804, 1; 12:01:33.856 [planetinfo.record]: rotation_speed = 0.001999 12:01:33.856 [planetinfo.record]: planet zpos = 557440.000000 12:01:33.856 [planetinfo.record]: sun_radius = 117021.650391 12:01:33.857 [planetinfo.record]: sun_vector = -0.288 -0.785 -0.549 12:01:33.857 [planetinfo.record]: sun_distance = 986240 12:01:33.857 [planetinfo.record]: corona_flare = 0.076730 12:01:33.857 [planetinfo.record]: corona_hues = 0.704262 12:01:33.857 [planetinfo.record]: sun_color = 0.568371, 0.618144, 0.820322, 1 12:01:33.857 [planetinfo.record]: corona_shimmer = 0.368441 12:01:33.857 [planetinfo.record]: station_vector = -0.125 -0.992 0.015 12:01:33.857 [planetinfo.record]: station = coriolis 12:01:39.180 [PLANETINFO OVER]: Done 12:01:39.181 [PLANETINFO LOGGING]: 1 117 12:01:40.185 [planetinfo.record]: seed = 128 243 154 193 12:01:40.185 [planetinfo.record]: coordinates = 243 218 12:01:40.185 [planetinfo.record]: government = 0; 12:01:40.186 [planetinfo.record]: economy = 2; 12:01:40.186 [planetinfo.record]: techlevel = 8; 12:01:40.186 [planetinfo.record]: population = 35; 12:01:40.186 [planetinfo.record]: productivity = 8960; 12:01:40.186 [planetinfo.record]: name = "Leaza"; 12:01:40.186 [planetinfo.record]: inhabitant = "Large Bug-Eyed Lizard"; 12:01:40.186 [planetinfo.record]: inhabitants = "Large Bug-Eyed Lizards"; 12:01:40.186 [planetinfo.record]: description = "This planet is mildly fabled for the Leazaian mountain poet but scourged by deadly solar activity."; 12:01:40.199 [planetinfo.record]: air_color = 0.624529, 0.77885, 0.995748, 1; 12:01:40.199 [planetinfo.record]: cloud_alpha = 1.000000; 12:01:40.199 [planetinfo.record]: cloud_color = 0.475777, 0.990234, 0.869658, 1; 12:01:40.199 [planetinfo.record]: cloud_fraction = 0.380000; 12:01:40.199 [planetinfo.record]: land_color = 0.376406, 0.66, 0.513772, 1; 12:01:40.199 [planetinfo.record]: land_fraction = 0.600000; 12:01:40.200 [planetinfo.record]: polar_cloud_color = 0.638561, 0.945605, 0.873642, 1; 12:01:40.200 [planetinfo.record]: polar_land_color = 0.833668, 0.934, 0.882266, 1; 12:01:40.200 [planetinfo.record]: polar_sea_color = 0.947461, 0.896479, 0.896479, 1; 12:01:40.200 [planetinfo.record]: sea_color = 0.525391, 0.412308, 0.412308, 1; 12:01:40.200 [planetinfo.record]: rotation_speed = 0.003172 12:01:40.200 [planetinfo.record]: planet zpos = 298350.000000 12:01:40.200 [planetinfo.record]: sun_radius = 60729.306793 12:01:40.200 [planetinfo.record]: sun_vector = 0.528 -0.840 0.127 12:01:40.200 [planetinfo.record]: sun_distance = 696150 12:01:40.200 [planetinfo.record]: corona_flare = 0.069907 12:01:40.200 [planetinfo.record]: corona_hues = 0.536896 12:01:40.200 [planetinfo.record]: sun_color = 0.667014, 0.311718, 0.275201, 1 12:01:40.200 [planetinfo.record]: corona_shimmer = 0.268916 12:01:40.200 [planetinfo.record]: station_vector = 0.523 -0.406 0.750 12:01:40.200 [planetinfo.record]: station = coriolis 12:01:44.971 [PLANETINFO OVER]: Done 12:01:44.972 [PLANETINFO LOGGING]: 1 118 12:01:45.988 [planetinfo.record]: seed = 232 148 54 104 12:01:45.988 [planetinfo.record]: coordinates = 148 68 12:01:45.988 [planetinfo.record]: government = 5; 12:01:45.988 [planetinfo.record]: economy = 4; 12:01:45.988 [planetinfo.record]: techlevel = 6; 12:01:45.988 [planetinfo.record]: population = 34; 12:01:45.988 [planetinfo.record]: productivity = 14688; 12:01:45.988 [planetinfo.record]: name = "Usleri"; 12:01:45.988 [planetinfo.record]: inhabitant = "Human Colonial"; 12:01:45.988 [planetinfo.record]: inhabitants = "Human Colonials"; 12:01:45.988 [planetinfo.record]: description = "This world is ravaged by dreadful civil war."; 12:01:46.024 [planetinfo.record]: air_color = 0.613649, 0.929864, 0.93201, 1; 12:01:46.024 [planetinfo.record]: cloud_alpha = 1.000000; 12:01:46.024 [planetinfo.record]: cloud_color = 0.788199, 0.798828, 0.458702, 1; 12:01:46.024 [planetinfo.record]: cloud_fraction = 0.450000; 12:01:46.024 [planetinfo.record]: land_color = 0.66, 0.0747656, 0.294229, 1; 12:01:46.024 [planetinfo.record]: land_fraction = 0.370000; 12:01:46.024 [planetinfo.record]: polar_cloud_color = 0.852325, 0.859473, 0.630756, 1; 12:01:46.024 [planetinfo.record]: polar_land_color = 0.934, 0.726951, 0.804595, 1; 12:01:46.024 [planetinfo.record]: polar_sea_color = 0.941992, 0.932033, 0.892961, 1; 12:01:46.024 [planetinfo.record]: sea_color = 0.580078, 0.555546, 0.459304, 1; 12:01:46.024 [planetinfo.record]: rotation_speed = 0.000460 12:01:46.024 [planetinfo.record]: planet zpos = 701680.000000 12:01:46.024 [planetinfo.record]: sun_radius = 152938.806152 12:01:46.024 [planetinfo.record]: sun_vector = -0.866 -0.456 0.207 12:01:46.024 [planetinfo.record]: sun_distance = 1002400 12:01:46.025 [planetinfo.record]: corona_flare = 0.070711 12:01:46.025 [planetinfo.record]: corona_hues = 0.969376 12:01:46.025 [planetinfo.record]: sun_color = 0.60412, 0.616031, 0.721698, 1 12:01:46.025 [planetinfo.record]: corona_shimmer = 0.361339 12:01:46.025 [planetinfo.record]: station_vector = -0.092 0.257 0.962 12:01:46.026 [planetinfo.record]: station = coriolis 12:01:50.995 [PLANETINFO OVER]: Done 12:01:50.996 [PLANETINFO LOGGING]: 1 119 12:01:52.015 [planetinfo.record]: seed = 248 231 186 103 12:01:52.016 [planetinfo.record]: coordinates = 231 62 12:01:52.016 [planetinfo.record]: government = 7; 12:01:52.016 [planetinfo.record]: economy = 6; 12:01:52.016 [planetinfo.record]: techlevel = 8; 12:01:52.016 [planetinfo.record]: population = 46; 12:01:52.016 [planetinfo.record]: productivity = 16192; 12:01:52.016 [planetinfo.record]: name = "Soreisbe"; 12:01:52.016 [planetinfo.record]: inhabitant = "Fierce Blue Bug-Eyed Bird"; 12:01:52.016 [planetinfo.record]: inhabitants = "Fierce Blue Bug-Eyed Birds"; 12:01:52.016 [planetinfo.record]: description = "The planet Soreisbe is most famous for the Soreisbeian deadly goat and its hoopy casinos."; 12:01:52.032 [planetinfo.record]: air_color = 0.459622, 0.752076, 0.840955, 1; 12:01:52.032 [planetinfo.record]: cloud_alpha = 1.000000; 12:01:52.032 [planetinfo.record]: cloud_color = 0.240874, 0.525391, 0.125191, 1; 12:01:52.032 [planetinfo.record]: cloud_fraction = 0.160000; 12:01:52.032 [planetinfo.record]: land_color = 0.0902344, 0.312799, 0.66, 1; 12:01:52.032 [planetinfo.record]: land_fraction = 0.550000; 12:01:52.032 [planetinfo.record]: polar_cloud_color = 0.487176, 0.736426, 0.385832, 1; 12:01:52.032 [planetinfo.record]: polar_land_color = 0.732424, 0.811165, 0.934, 1; 12:01:52.032 [planetinfo.record]: polar_sea_color = 0.941797, 0.913556, 0.888637, 1; 12:01:52.032 [planetinfo.record]: sea_color = 0.582031, 0.512219, 0.450619, 1; 12:01:52.032 [planetinfo.record]: rotation_speed = 0.001566 12:01:52.032 [planetinfo.record]: planet zpos = 580680.000000 12:01:52.032 [planetinfo.record]: sun_radius = 130040.741272 12:01:52.032 [planetinfo.record]: sun_vector = 0.485 0.163 0.859 12:01:52.032 [planetinfo.record]: sun_distance = 967800 12:01:52.033 [planetinfo.record]: corona_flare = 0.047441 12:01:52.033 [planetinfo.record]: corona_hues = 0.585556 12:01:52.034 [planetinfo.record]: sun_color = 0.543222, 0.652816, 0.696695, 1 12:01:52.034 [planetinfo.record]: corona_shimmer = 0.254207 12:01:52.034 [planetinfo.record]: station_vector = -0.954 -0.266 0.140 12:01:52.034 [planetinfo.record]: station = coriolis 12:01:57.313 [PLANETINFO OVER]: Done 12:01:57.316 [PLANETINFO LOGGING]: 1 120 12:01:58.333 [planetinfo.record]: seed = 176 211 166 63 12:01:58.333 [planetinfo.record]: coordinates = 211 221 12:01:58.333 [planetinfo.record]: government = 6; 12:01:58.333 [planetinfo.record]: economy = 5; 12:01:58.333 [planetinfo.record]: techlevel = 8; 12:01:58.333 [planetinfo.record]: population = 44; 12:01:58.333 [planetinfo.record]: productivity = 17600; 12:01:58.333 [planetinfo.record]: name = "Onatzala"; 12:01:58.334 [planetinfo.record]: inhabitant = "Red Frog"; 12:01:58.334 [planetinfo.record]: inhabitants = "Red Frogs"; 12:01:58.334 [planetinfo.record]: description = "Onatzala is a revolting dump."; 12:01:58.341 [planetinfo.record]: air_color = 0.795519, 0.536409, 0.947619, 1; 12:01:58.341 [planetinfo.record]: cloud_alpha = 1.000000; 12:01:58.341 [planetinfo.record]: cloud_color = 0.845703, 0.251068, 0.306815, 1; 12:01:58.341 [planetinfo.record]: cloud_fraction = 0.360000; 12:01:58.341 [planetinfo.record]: land_color = 0.33858, 0.139219, 0.66, 1; 12:01:58.341 [planetinfo.record]: land_fraction = 0.430000; 12:01:58.342 [planetinfo.record]: polar_cloud_color = 0.880566, 0.493599, 0.529877, 1; 12:01:58.342 [planetinfo.record]: polar_land_color = 0.820286, 0.749754, 0.934, 1; 12:01:58.342 [planetinfo.record]: polar_sea_color = 0.93418, 0.910161, 0.872692, 1; 12:01:58.342 [planetinfo.record]: sea_color = 0.658203, 0.590511, 0.484911, 1; 12:01:58.342 [planetinfo.record]: rotation_speed = 0.001388 12:01:58.342 [planetinfo.record]: planet zpos = 892710.000000 12:01:58.342 [planetinfo.record]: sun_radius = 206734.044342 12:01:58.342 [planetinfo.record]: sun_vector = -0.722 0.654 -0.223 12:01:58.342 [planetinfo.record]: sun_distance = 1442070 12:01:58.342 [planetinfo.record]: corona_flare = 0.011322 12:01:58.342 [planetinfo.record]: corona_hues = 0.725861 12:01:58.342 [planetinfo.record]: sun_color = 0.748456, 0.746256, 0.475468, 1 12:01:58.342 [planetinfo.record]: corona_shimmer = 0.445048 12:01:58.342 [planetinfo.record]: station_vector = -0.723 -0.227 0.653 12:01:58.342 [planetinfo.record]: station = coriolis 12:02:03.330 [PLANETINFO OVER]: Done 12:02:03.332 [PLANETINFO LOGGING]: 1 121 12:02:04.352 [planetinfo.record]: seed = 80 53 250 42 12:02:04.352 [planetinfo.record]: coordinates = 53 4 12:02:04.352 [planetinfo.record]: government = 2; 12:02:04.352 [planetinfo.record]: economy = 4; 12:02:04.352 [planetinfo.record]: techlevel = 5; 12:02:04.352 [planetinfo.record]: population = 27; 12:02:04.352 [planetinfo.record]: productivity = 7776; 12:02:04.352 [planetinfo.record]: name = "Arzace"; 12:02:04.352 [planetinfo.record]: inhabitant = "Small Red Bug-Eyed Lobster"; 12:02:04.352 [planetinfo.record]: inhabitants = "Small Red Bug-Eyed Lobsters"; 12:02:04.352 [planetinfo.record]: description = "The planet Arzace is scourged by evil disease."; 12:02:04.376 [planetinfo.record]: air_color = 0.650067, 0.864369, 0.810619, 1; 12:02:04.377 [planetinfo.record]: cloud_alpha = 1.000000; 12:02:04.377 [planetinfo.record]: cloud_color = 0.595703, 0.531802, 0.509605, 1; 12:02:04.377 [planetinfo.record]: cloud_fraction = 0.160000; 12:02:04.377 [planetinfo.record]: land_color = 0.640819, 0.654297, 0.585289, 1; 12:02:04.377 [planetinfo.record]: land_fraction = 0.240000; 12:02:04.377 [planetinfo.record]: polar_cloud_color = 0.768066, 0.716573, 0.698685, 1; 12:02:04.377 [planetinfo.record]: polar_land_color = 0.929757, 0.93457, 0.909928, 1; 12:02:04.377 [planetinfo.record]: polar_sea_color = 0.770288, 0.942383, 0.85768, 1; 12:02:04.377 [planetinfo.record]: sea_color = 0.155296, 0.576172, 0.369022, 1; 12:02:04.377 [planetinfo.record]: rotation_speed = 0.002449 12:02:04.377 [planetinfo.record]: planet zpos = 651480.000000 12:02:04.377 [planetinfo.record]: sun_radius = 94931.287231 12:02:04.377 [planetinfo.record]: sun_vector = 0.830 0.310 0.463 12:02:04.377 [planetinfo.record]: sun_distance = 1248670 12:02:04.377 [planetinfo.record]: corona_flare = 0.015591 12:02:04.377 [planetinfo.record]: corona_hues = 0.516251 12:02:04.377 [planetinfo.record]: sun_color = 0.331583, 0.600012, 0.813461, 1 12:02:04.378 [planetinfo.record]: corona_shimmer = 0.379136 12:02:04.379 [planetinfo.record]: station_vector = 0.789 -0.613 0.020 12:02:04.379 [planetinfo.record]: station = coriolis 12:02:09.435 [PLANETINFO OVER]: Done 12:02:09.435 [PLANETINFO LOGGING]: 1 122 12:02:10.438 [planetinfo.record]: seed = 216 84 182 126 12:02:10.438 [planetinfo.record]: coordinates = 84 197 12:02:10.438 [planetinfo.record]: government = 3; 12:02:10.438 [planetinfo.record]: economy = 5; 12:02:10.438 [planetinfo.record]: techlevel = 4; 12:02:10.438 [planetinfo.record]: population = 25; 12:02:10.438 [planetinfo.record]: productivity = 7000; 12:02:10.438 [planetinfo.record]: name = "Riedin"; 12:02:10.438 [planetinfo.record]: inhabitant = "Blue Bug-Eyed Lobster"; 12:02:10.438 [planetinfo.record]: inhabitants = "Blue Bug-Eyed Lobsters"; 12:02:10.438 [planetinfo.record]: description = "This world is fabled for its unusual tropical forests."; 12:02:10.446 [planetinfo.record]: air_color = 0.534691, 0.985342, 0.960242, 1; 12:02:10.447 [planetinfo.record]: cloud_alpha = 1.000000; 12:02:10.447 [planetinfo.record]: cloud_color = 0.958984, 0.843091, 0.21727, 1; 12:02:10.447 [planetinfo.record]: cloud_fraction = 0.190000; 12:02:10.447 [planetinfo.record]: land_color = 0.0464063, 0.66, 0.573713, 1; 12:02:10.447 [planetinfo.record]: land_fraction = 0.430000; 12:02:10.447 [planetinfo.record]: polar_cloud_color = 0.931543, 0.861183, 0.481237, 1; 12:02:10.447 [planetinfo.record]: polar_land_color = 0.716918, 0.934, 0.903473, 1; 12:02:10.447 [planetinfo.record]: polar_sea_color = 0.936523, 0.929175, 0.881192, 1; 12:02:10.447 [planetinfo.record]: sea_color = 0.634766, 0.614842, 0.484753, 1; 12:02:10.447 [planetinfo.record]: rotation_speed = 0.004836 12:02:10.448 [planetinfo.record]: planet zpos = 778080.000000 12:02:10.448 [planetinfo.record]: sun_radius = 106175.302124 12:02:10.448 [planetinfo.record]: sun_vector = 0.034 0.040 -0.999 12:02:10.448 [planetinfo.record]: sun_distance = 1231960 12:02:10.448 [planetinfo.record]: corona_flare = 0.030182 12:02:10.448 [planetinfo.record]: corona_hues = 0.653839 12:02:10.448 [planetinfo.record]: sun_color = 0.555103, 0.690649, 0.80957, 1 12:02:10.448 [planetinfo.record]: corona_shimmer = 0.431152 12:02:10.448 [planetinfo.record]: station_vector = 0.013 -0.955 0.297 12:02:10.448 [planetinfo.record]: station = coriolis 12:02:15.214 [PLANETINFO OVER]: Done 12:02:15.216 [PLANETINFO LOGGING]: 1 123 12:02:16.235 [planetinfo.record]: seed = 136 131 90 136 12:02:16.235 [planetinfo.record]: coordinates = 131 108 12:02:16.235 [planetinfo.record]: government = 1; 12:02:16.236 [planetinfo.record]: economy = 6; 12:02:16.236 [planetinfo.record]: techlevel = 5; 12:02:16.236 [planetinfo.record]: population = 28; 12:02:16.236 [planetinfo.record]: productivity = 4480; 12:02:16.236 [planetinfo.record]: name = "Usedge"; 12:02:16.236 [planetinfo.record]: inhabitant = "Human Colonial"; 12:02:16.236 [planetinfo.record]: inhabitants = "Human Colonials"; 12:02:16.236 [planetinfo.record]: description = "Usedge is cursed by dreadful civil war."; 12:02:16.276 [planetinfo.record]: air_color = 0.791521, 0.588693, 0.906645, 1; 12:02:16.276 [planetinfo.record]: cloud_alpha = 1.000000; 12:02:16.276 [planetinfo.record]: cloud_color = 0.722656, 0.395203, 0.418227, 1; 12:02:16.276 [planetinfo.record]: cloud_fraction = 0.210000; 12:02:16.276 [planetinfo.record]: land_color = 0.456328, 0.66, 0.554982, 1; 12:02:16.276 [planetinfo.record]: land_fraction = 0.350000; 12:02:16.276 [planetinfo.record]: polar_cloud_color = 0.825195, 0.591497, 0.607929, 1; 12:02:16.276 [planetinfo.record]: polar_land_color = 0.861943, 0.934, 0.896846, 1; 12:02:16.276 [planetinfo.record]: polar_sea_color = 0.916699, 0.933594, 0.879529, 1; 12:02:16.276 [planetinfo.record]: sea_color = 0.615993, 0.664062, 0.510239, 1; 12:02:16.276 [planetinfo.record]: rotation_speed = 0.000941 12:02:16.276 [planetinfo.record]: planet zpos = 499500.000000 12:02:16.277 [planetinfo.record]: sun_radius = 133040.196991 12:02:16.277 [planetinfo.record]: sun_vector = -0.706 -0.703 0.088 12:02:16.277 [planetinfo.record]: sun_distance = 799200 12:02:16.277 [planetinfo.record]: corona_flare = 0.062733 12:02:16.277 [planetinfo.record]: corona_hues = 0.807198 12:02:16.277 [planetinfo.record]: sun_color = 0.671967, 0.655593, 0.557177, 1 12:02:16.277 [planetinfo.record]: corona_shimmer = 0.734840 12:02:16.277 [planetinfo.record]: station_vector = -0.185 0.676 0.713 12:02:16.278 [planetinfo.record]: station = coriolis 12:02:21.208 [PLANETINFO OVER]: Done 12:02:21.210 [PLANETINFO LOGGING]: 1 124 12:02:22.212 [planetinfo.record]: seed = 96 132 102 128 12:02:22.212 [planetinfo.record]: coordinates = 132 131 12:02:22.212 [planetinfo.record]: government = 4; 12:02:22.212 [planetinfo.record]: economy = 3; 12:02:22.212 [planetinfo.record]: techlevel = 6; 12:02:22.212 [planetinfo.record]: population = 32; 12:02:22.212 [planetinfo.record]: productivity = 14336; 12:02:22.212 [planetinfo.record]: name = "Usdive"; 12:02:22.212 [planetinfo.record]: inhabitant = "Human Colonial"; 12:02:22.212 [planetinfo.record]: inhabitants = "Human Colonials"; 12:02:22.212 [planetinfo.record]: description = "Usdive is ravaged by dreadful civil war."; 12:02:22.220 [planetinfo.record]: air_color = 0.852436, 0.757633, 0.997049, 1; 12:02:22.221 [planetinfo.record]: cloud_alpha = 1.000000; 12:02:22.221 [planetinfo.record]: cloud_color = 0.994141, 0.873756, 0.969688, 1; 12:02:22.221 [planetinfo.record]: cloud_fraction = 0.420000; 12:02:22.221 [planetinfo.record]: land_color = 0.589844, 0.46312, 0.492821, 1; 12:02:22.221 [planetinfo.record]: land_fraction = 0.290000; 12:02:22.221 [planetinfo.record]: polar_cloud_color = 0.947363, 0.875663, 0.932799, 1; 12:02:22.221 [planetinfo.record]: polar_land_color = 0.941016, 0.890473, 0.902319, 1; 12:02:22.221 [planetinfo.record]: polar_sea_color = 0.924414, 0.87166, 0.825112, 1; 12:02:22.221 [planetinfo.record]: sea_color = 0.755859, 0.583318, 0.431076, 1; 12:02:22.221 [planetinfo.record]: rotation_speed = 0.000782 12:02:22.221 [planetinfo.record]: planet zpos = 265320.000000 12:02:22.221 [planetinfo.record]: sun_radius = 83255.269775 12:02:22.221 [planetinfo.record]: sun_vector = -0.181 0.389 0.903 12:02:22.221 [planetinfo.record]: sun_distance = 560120 12:02:22.221 [planetinfo.record]: corona_flare = 0.019658 12:02:22.221 [planetinfo.record]: corona_hues = 0.607674 12:02:22.221 [planetinfo.record]: sun_color = 0.334344, 0.412067, 0.765475, 1 12:02:22.221 [planetinfo.record]: corona_shimmer = 0.632270 12:02:22.221 [planetinfo.record]: station_vector = -0.455 -0.890 0.023 12:02:22.221 [planetinfo.record]: station = coriolis 12:02:26.448 [PLANETINFO OVER]: Done 12:02:26.449 [PLANETINFO LOGGING]: 1 125 12:02:27.452 [planetinfo.record]: seed = 160 150 218 172 12:02:27.452 [planetinfo.record]: coordinates = 150 141 12:02:27.452 [planetinfo.record]: government = 4; 12:02:27.452 [planetinfo.record]: economy = 5; 12:02:27.452 [planetinfo.record]: techlevel = 6; 12:02:27.452 [planetinfo.record]: population = 34; 12:02:27.452 [planetinfo.record]: productivity = 10880; 12:02:27.452 [planetinfo.record]: name = "Inerra"; 12:02:27.452 [planetinfo.record]: inhabitant = "Harmless Bony Lobster"; 12:02:27.452 [planetinfo.record]: inhabitants = "Harmless Bony Lobsters"; 12:02:27.452 [planetinfo.record]: description = "The planet Inerra is an unremarkable dump."; 12:02:27.454 [planetinfo.record]: air_color = 0.620408, 0.896238, 0.835667, 1; 12:02:27.455 [planetinfo.record]: cloud_alpha = 1.000000; 12:02:27.455 [planetinfo.record]: cloud_color = 0.691406, 0.546048, 0.467239, 1; 12:02:27.455 [planetinfo.record]: cloud_fraction = 0.110000; 12:02:27.455 [planetinfo.record]: land_color = 0.66, 0.646465, 0.53625, 1; 12:02:27.455 [planetinfo.record]: land_fraction = 0.430000; 12:02:27.455 [planetinfo.record]: polar_cloud_color = 0.811133, 0.704552, 0.646767, 1; 12:02:27.455 [planetinfo.record]: polar_land_color = 0.934, 0.929211, 0.890219, 1; 12:02:27.456 [planetinfo.record]: polar_sea_color = 0.729655, 0.902229, 0.930469, 1; 12:02:27.456 [planetinfo.record]: sea_color = 0.0950623, 0.610902, 0.695312, 1; 12:02:27.456 [planetinfo.record]: rotation_speed = 0.001898 12:02:27.456 [planetinfo.record]: planet zpos = 724560.000000 12:02:27.456 [planetinfo.record]: sun_radius = 167874.752808 12:02:27.456 [planetinfo.record]: sun_vector = 0.228 -0.402 -0.887 12:02:27.456 [planetinfo.record]: sun_distance = 1388740 12:02:27.456 [planetinfo.record]: corona_flare = 0.031444 12:02:27.456 [planetinfo.record]: corona_hues = 0.641594 12:02:27.456 [planetinfo.record]: sun_color = 0.724341, 0.406212, 0.223502, 1 12:02:27.457 [planetinfo.record]: corona_shimmer = 0.643734 12:02:27.457 [planetinfo.record]: station_vector = -0.971 0.192 0.142 12:02:27.457 [planetinfo.record]: station = coriolis 12:02:32.216 [PLANETINFO OVER]: Done 12:02:32.216 [PLANETINFO LOGGING]: 1 126 12:02:33.224 [planetinfo.record]: seed = 72 146 182 119 12:02:33.224 [planetinfo.record]: coordinates = 146 20 12:02:33.224 [planetinfo.record]: government = 1; 12:02:33.224 [planetinfo.record]: economy = 6; 12:02:33.224 [planetinfo.record]: techlevel = 4; 12:02:33.224 [planetinfo.record]: population = 24; 12:02:33.224 [planetinfo.record]: productivity = 3840; 12:02:33.224 [planetinfo.record]: name = "Tiriusri"; 12:02:33.224 [planetinfo.record]: inhabitant = "Blue Frog"; 12:02:33.225 [planetinfo.record]: inhabitants = "Blue Frogs"; 12:02:33.225 [planetinfo.record]: description = "Tiriusri is reasonably notable for its fabulous cuisine but plagued by lethal disease."; 12:02:33.242 [planetinfo.record]: air_color = 0.690404, 0.757769, 0.96648, 1; 12:02:33.243 [planetinfo.record]: cloud_alpha = 1.000000; 12:02:33.243 [planetinfo.record]: cloud_color = 0.669708, 0.836915, 0.902344, 1; 12:02:33.243 [planetinfo.record]: cloud_fraction = 0.500000; 12:02:33.243 [planetinfo.record]: land_color = 0.538828, 0.66, 0.64012, 1; 12:02:33.243 [planetinfo.record]: land_fraction = 0.340000; 12:02:33.243 [planetinfo.record]: polar_cloud_color = 0.76006, 0.864994, 0.906055, 1; 12:02:33.243 [planetinfo.record]: polar_land_color = 0.891131, 0.934, 0.926967, 1; 12:02:33.243 [planetinfo.record]: polar_sea_color = 0.9133, 0.935742, 0.886214, 1; 12:02:33.243 [planetinfo.record]: sea_color = 0.580932, 0.642578, 0.506532, 1; 12:02:33.243 [planetinfo.record]: rotation_speed = 0.004217 12:02:33.244 [planetinfo.record]: planet zpos = 618020.000000 12:02:33.244 [planetinfo.record]: sun_radius = 126714.817505 12:02:33.244 [planetinfo.record]: sun_vector = -0.527 0.660 -0.536 12:02:33.244 [planetinfo.record]: sun_distance = 998340 12:02:33.244 [planetinfo.record]: corona_flare = 0.077139 12:02:33.244 [planetinfo.record]: corona_hues = 0.768494 12:02:33.244 [planetinfo.record]: sun_color = 0.671042, 0.485675, 0.420254, 1 12:02:33.244 [planetinfo.record]: corona_shimmer = 0.702739 12:02:33.252 [planetinfo.record]: station_vector = 0.500 -0.862 0.078 12:02:33.252 [planetinfo.record]: station = coriolis 12:02:38.835 [PLANETINFO OVER]: Done 12:02:38.835 [PLANETINFO LOGGING]: 1 127 12:02:39.842 [planetinfo.record]: seed = 152 190 122 5 12:02:39.842 [planetinfo.record]: coordinates = 190 40 12:02:39.842 [planetinfo.record]: government = 3; 12:02:39.842 [planetinfo.record]: economy = 0; 12:02:39.842 [planetinfo.record]: techlevel = 11; 12:02:39.842 [planetinfo.record]: population = 48; 12:02:39.842 [planetinfo.record]: productivity = 26880; 12:02:39.842 [planetinfo.record]: name = "Ceinerxe"; 12:02:39.842 [planetinfo.record]: inhabitant = "Human Colonial"; 12:02:39.842 [planetinfo.record]: inhabitants = "Human Colonials"; 12:02:39.842 [planetinfo.record]: description = "This planet is plagued by frequent earthquakes."; 12:02:39.852 [planetinfo.record]: air_color = 0.741236, 0.72597, 0.978838, 1; 12:02:39.852 [planetinfo.record]: cloud_alpha = 1.000000; 12:02:39.852 [planetinfo.record]: cloud_color = 0.806568, 0.774315, 0.939453, 1; 12:02:39.852 [planetinfo.record]: cloud_fraction = 0.410000; 12:02:39.852 [planetinfo.record]: land_color = 0.656596, 0.626484, 0.66, 1; 12:02:39.859 [planetinfo.record]: land_fraction = 0.270000; 12:02:39.859 [planetinfo.record]: polar_cloud_color = 0.841177, 0.821377, 0.922754, 1; 12:02:39.860 [planetinfo.record]: polar_land_color = 0.932796, 0.922143, 0.934, 1; 12:02:39.860 [planetinfo.record]: polar_sea_color = 0.861711, 0.946289, 0.749454, 1; 12:02:39.860 [planetinfo.record]: sea_color = 0.345086, 0.537109, 0.0902176, 1; 12:02:39.860 [planetinfo.record]: rotation_speed = 0.000265 12:02:39.860 [planetinfo.record]: planet zpos = 428600.000000 12:02:39.860 [planetinfo.record]: sun_radius = 135435.140991 12:02:39.860 [planetinfo.record]: sun_vector = -0.872 -0.487 0.055 12:02:39.860 [planetinfo.record]: sun_distance = 814340 12:02:39.860 [planetinfo.record]: corona_flare = 0.014583 12:02:39.860 [planetinfo.record]: corona_hues = 0.892273 12:02:39.860 [planetinfo.record]: sun_color = 0.764124, 0.627134, 0.501678, 1 12:02:39.860 [planetinfo.record]: corona_shimmer = 0.282866 12:02:39.860 [planetinfo.record]: station_vector = 0.953 0.260 0.157 12:02:39.860 [planetinfo.record]: station = dodecahedron 12:02:44.443 [PLANETINFO OVER]: Done 12:02:44.444 [PLANETINFO LOGGING]: 1 128 12:02:45.450 [planetinfo.record]: seed = 144 162 166 63 12:02:45.450 [planetinfo.record]: coordinates = 162 176 12:02:45.450 [planetinfo.record]: government = 2; 12:02:45.450 [planetinfo.record]: economy = 0; 12:02:45.450 [planetinfo.record]: techlevel = 10; 12:02:45.450 [planetinfo.record]: population = 43; 12:02:45.450 [planetinfo.record]: productivity = 20640; 12:02:45.450 [planetinfo.record]: name = "Onenla"; 12:02:45.450 [planetinfo.record]: inhabitant = "Red Horned Humanoid"; 12:02:45.450 [planetinfo.record]: inhabitants = "Red Horned Humanoids"; 12:02:45.450 [planetinfo.record]: description = "This world is plagued by occasional solar activity."; 12:02:45.468 [planetinfo.record]: air_color = 0.644763, 0.870873, 0.815926, 1; 12:02:45.475 [planetinfo.record]: cloud_alpha = 1.000000; 12:02:45.475 [planetinfo.record]: cloud_color = 0.615234, 0.535777, 0.504684, 1; 12:02:45.475 [planetinfo.record]: cloud_fraction = 0.590000; 12:02:45.475 [planetinfo.record]: land_color = 0.378984, 0.66, 0.640241, 1; 12:02:45.475 [planetinfo.record]: land_fraction = 0.570000; 12:02:45.476 [planetinfo.record]: polar_cloud_color = 0.776855, 0.714148, 0.689611, 1; 12:02:45.476 [planetinfo.record]: polar_land_color = 0.83458, 0.934, 0.92701, 1; 12:02:45.476 [planetinfo.record]: polar_sea_color = 0.944531, 0.924747, 0.896751, 1; 12:02:45.476 [planetinfo.record]: sea_color = 0.554688, 0.508214, 0.44245, 1; 12:02:45.476 [planetinfo.record]: rotation_speed = 0.000131 12:02:45.476 [planetinfo.record]: planet zpos = 886340.000000 12:02:45.476 [planetinfo.record]: sun_radius = 215810.049133 12:02:45.476 [planetinfo.record]: sun_vector = -0.963 -0.261 -0.067 12:02:45.476 [planetinfo.record]: sun_distance = 1159060 12:02:45.476 [planetinfo.record]: corona_flare = 0.060915 12:02:45.476 [planetinfo.record]: corona_hues = 0.825104 12:02:45.476 [planetinfo.record]: sun_color = 0.686499, 0.58125, 0.356406, 1 12:02:45.477 [planetinfo.record]: corona_shimmer = 0.256599 12:02:45.477 [planetinfo.record]: station_vector = -0.968 -0.173 0.179 12:02:45.477 [planetinfo.record]: station = coriolis 12:02:50.801 [PLANETINFO OVER]: Done 12:02:50.802 [PLANETINFO LOGGING]: 1 129 12:02:51.805 [planetinfo.record]: seed = 112 71 58 79 12:02:51.805 [planetinfo.record]: coordinates = 71 117 12:02:51.806 [planetinfo.record]: government = 6; 12:02:51.806 [planetinfo.record]: economy = 5; 12:02:51.806 [planetinfo.record]: techlevel = 8; 12:02:51.806 [planetinfo.record]: population = 44; 12:02:51.806 [planetinfo.record]: productivity = 17600; 12:02:51.806 [planetinfo.record]: name = "Amaxe"; 12:02:51.806 [planetinfo.record]: inhabitant = "Human Colonial"; 12:02:51.806 [planetinfo.record]: inhabitants = "Human Colonials"; 12:02:51.806 [planetinfo.record]: description = "The world Amaxe is a dull world."; 12:02:51.841 [planetinfo.record]: air_color = 0.753946, 0.521099, 0.94892, 1; 12:02:51.841 [planetinfo.record]: cloud_alpha = 1.000000; 12:02:51.842 [planetinfo.record]: cloud_color = 0.849609, 0.209084, 0.434268, 1; 12:02:51.842 [planetinfo.record]: cloud_fraction = 0.380000; 12:02:51.842 [planetinfo.record]: land_color = 0.66, 0.339124, 0.198516, 1; 12:02:51.842 [planetinfo.record]: land_fraction = 0.310000; 12:02:51.842 [planetinfo.record]: polar_cloud_color = 0.882324, 0.466581, 0.61274, 1; 12:02:51.842 [planetinfo.record]: polar_land_color = 0.934, 0.820478, 0.770732, 1; 12:02:51.842 [planetinfo.record]: polar_sea_color = 0.941211, 0.869517, 0.924968, 1; 12:02:51.842 [planetinfo.record]: sea_color = 0.587891, 0.408768, 0.547308, 1; 12:02:51.842 [planetinfo.record]: rotation_speed = 0.001218 12:02:51.842 [planetinfo.record]: planet zpos = 739970.000000 12:02:51.842 [planetinfo.record]: sun_radius = 174004.259186 12:02:51.842 [planetinfo.record]: sun_vector = -0.027 0.989 -0.144 12:02:51.842 [planetinfo.record]: sun_distance = 1547210 12:02:51.842 [planetinfo.record]: corona_flare = 0.081114 12:02:51.842 [planetinfo.record]: corona_hues = 0.817154 12:02:51.842 [planetinfo.record]: sun_color = 0.652032, 0.617324, 0.572936, 1 12:02:51.843 [planetinfo.record]: corona_shimmer = 0.808248 12:02:51.843 [planetinfo.record]: station_vector = -0.863 -0.290 0.413 12:02:51.843 [planetinfo.record]: station = coriolis 12:02:56.684 [PLANETINFO OVER]: Done 12:02:56.685 [PLANETINFO LOGGING]: 1 130 12:02:57.691 [planetinfo.record]: seed = 56 253 54 171 12:02:57.691 [planetinfo.record]: coordinates = 253 162 12:02:57.691 [planetinfo.record]: government = 7; 12:02:57.691 [planetinfo.record]: economy = 2; 12:02:57.691 [planetinfo.record]: techlevel = 10; 12:02:57.691 [planetinfo.record]: population = 50; 12:02:57.691 [planetinfo.record]: productivity = 35200; 12:02:57.691 [planetinfo.record]: name = "Maarbees"; 12:02:57.691 [planetinfo.record]: inhabitant = "Human Colonial"; 12:02:57.691 [planetinfo.record]: inhabitants = "Human Colonials"; 12:02:57.691 [planetinfo.record]: description = "This planet is mildly noted for the Maarbeesian mountain lobstoid but beset by deadly earthquakes."; 12:02:57.708 [planetinfo.record]: air_color = 0.642876, 0.872931, 0.933961, 1; 12:02:57.708 [planetinfo.record]: cloud_alpha = 1.000000; 12:02:57.708 [planetinfo.record]: cloud_color = 0.631511, 0.804688, 0.534363, 1; 12:02:57.709 [planetinfo.record]: cloud_fraction = 0.430000; 12:02:57.709 [planetinfo.record]: land_color = 0.523214, 0.34655, 0.603516, 1; 12:02:57.709 [planetinfo.record]: land_fraction = 0.480000; 12:02:57.709 [planetinfo.record]: polar_cloud_color = 0.74615, 0.862109, 0.6811, 1; 12:02:57.709 [planetinfo.record]: polar_land_color = 0.908392, 0.839627, 0.939648, 1; 12:02:57.709 [planetinfo.record]: polar_sea_color = 0.911442, 0.935938, 0.886947, 1; 12:02:57.709 [planetinfo.record]: sea_color = 0.57356, 0.640625, 0.506494, 1; 12:02:57.709 [planetinfo.record]: rotation_speed = 0.003632 12:02:57.709 [planetinfo.record]: planet zpos = 765050.000000 12:02:57.709 [planetinfo.record]: sun_radius = 183855.963135 12:02:57.710 [planetinfo.record]: sun_vector = 0.303 -0.892 -0.336 12:02:57.710 [planetinfo.record]: sun_distance = 1177000 12:02:57.713 [planetinfo.record]: corona_flare = 0.084753 12:02:57.713 [planetinfo.record]: corona_hues = 0.797173 12:02:57.713 [planetinfo.record]: sun_color = 0.371612, 0.837319, 0.842807, 1 12:02:57.713 [planetinfo.record]: corona_shimmer = 0.318705 12:02:57.713 [planetinfo.record]: station_vector = -0.071 -0.958 0.277 12:02:57.713 [planetinfo.record]: station = coriolis 12:03:02.802 [PLANETINFO OVER]: Done 12:03:02.802 [PLANETINFO LOGGING]: 1 131 12:03:03.806 [planetinfo.record]: seed = 40 233 26 39 12:03:03.806 [planetinfo.record]: coordinates = 233 243 12:03:03.806 [planetinfo.record]: government = 5; 12:03:03.806 [planetinfo.record]: economy = 3; 12:03:03.806 [planetinfo.record]: techlevel = 8; 12:03:03.806 [planetinfo.record]: population = 41; 12:03:03.806 [planetinfo.record]: productivity = 20664; 12:03:03.806 [planetinfo.record]: name = "Sogebe"; 12:03:03.806 [planetinfo.record]: inhabitant = "Human Colonial"; 12:03:03.806 [planetinfo.record]: inhabitants = "Human Colonials"; 12:03:03.806 [planetinfo.record]: description = "Sogebe is an unremarkable planet."; 12:03:03.819 [planetinfo.record]: air_color = 0.65235, 0.88605, 0.909896, 1; 12:03:03.820 [planetinfo.record]: cloud_alpha = 1.000000; 12:03:03.820 [planetinfo.record]: cloud_color = 0.679493, 0.732422, 0.549316, 1; 12:03:03.820 [planetinfo.record]: cloud_fraction = 0.480000; 12:03:03.820 [planetinfo.record]: land_color = 0.566406, 0.565853, 0.564194, 1; 12:03:03.820 [planetinfo.record]: land_fraction = 0.400000; 12:03:03.820 [planetinfo.record]: polar_cloud_color = 0.792121, 0.82959, 0.699966, 1; 12:03:03.820 [planetinfo.record]: polar_land_color = 0.943359, 0.943129, 0.942438, 1; 12:03:03.821 [planetinfo.record]: polar_sea_color = 0.929297, 0.919371, 0.865771, 1; 12:03:03.821 [planetinfo.record]: sea_color = 0.707031, 0.676824, 0.513702, 1; 12:03:03.821 [planetinfo.record]: rotation_speed = 0.004662 12:03:03.821 [planetinfo.record]: planet zpos = 677740.000000 12:03:03.821 [planetinfo.record]: sun_radius = 147452.682037 12:03:03.821 [planetinfo.record]: sun_vector = 0.308 0.861 -0.405 12:03:03.821 [planetinfo.record]: sun_distance = 968200 12:03:03.821 [planetinfo.record]: corona_flare = 0.028166 12:03:03.821 [planetinfo.record]: corona_hues = 0.933395 12:03:03.821 [planetinfo.record]: sun_color = 0.481672, 0.513204, 0.712442, 1 12:03:03.821 [planetinfo.record]: corona_shimmer = 0.385329 12:03:03.822 [planetinfo.record]: station_vector = -0.788 0.254 0.561 12:03:03.823 [planetinfo.record]: station = coriolis 12:03:08.661 [PLANETINFO OVER]: Done 12:03:08.662 [PLANETINFO LOGGING]: 1 132 12:03:09.665 [planetinfo.record]: seed = 64 62 102 85 12:03:09.665 [planetinfo.record]: coordinates = 62 19 12:03:09.665 [planetinfo.record]: government = 0; 12:03:09.665 [planetinfo.record]: economy = 3; 12:03:09.665 [planetinfo.record]: techlevel = 6; 12:03:09.665 [planetinfo.record]: population = 28; 12:03:09.665 [planetinfo.record]: productivity = 6272; 12:03:09.665 [planetinfo.record]: name = "Lasoan"; 12:03:09.665 [planetinfo.record]: inhabitant = "Human Colonial"; 12:03:09.665 [planetinfo.record]: inhabitants = "Human Colonials"; 12:03:09.666 [planetinfo.record]: description = "The world Lasoan is a dull world."; 12:03:09.667 [planetinfo.record]: air_color = 0.482291, 0.678232, 0.850061, 1; 12:03:09.667 [planetinfo.record]: cloud_alpha = 1.000000; 12:03:09.667 [planetinfo.record]: cloud_color = 0.166252, 0.552734, 0.32628, 1; 12:03:09.667 [planetinfo.record]: cloud_fraction = 0.250000; 12:03:09.667 [planetinfo.record]: land_color = 0.522352, 0.66, 0.397031, 1; 12:03:09.667 [planetinfo.record]: land_fraction = 0.270000; 12:03:09.667 [planetinfo.record]: polar_cloud_color = 0.421526, 0.74873, 0.557009, 1; 12:03:09.667 [planetinfo.record]: polar_land_color = 0.885302, 0.934, 0.840965, 1; 12:03:09.667 [planetinfo.record]: polar_sea_color = 0.930078, 0.850235, 0.841975, 1; 12:03:09.667 [planetinfo.record]: sea_color = 0.699219, 0.459118, 0.43428, 1; 12:03:09.667 [planetinfo.record]: rotation_speed = 0.004501 12:03:09.667 [planetinfo.record]: planet zpos = 457380.000000 12:03:09.667 [planetinfo.record]: sun_radius = 125753.867798 12:03:09.667 [planetinfo.record]: sun_vector = -0.397 0.915 -0.074 12:03:09.667 [planetinfo.record]: sun_distance = 706860 12:03:09.667 [planetinfo.record]: corona_flare = 0.087828 12:03:09.668 [planetinfo.record]: corona_hues = 0.814240 12:03:09.668 [planetinfo.record]: sun_color = 0.585626, 0.707982, 0.801248, 1 12:03:09.668 [planetinfo.record]: corona_shimmer = 0.817810 12:03:09.668 [planetinfo.record]: station_vector = 0.680 -0.646 0.346 12:03:09.668 [planetinfo.record]: station = coriolis 12:03:14.710 [PLANETINFO OVER]: Done 12:03:14.711 [PLANETINFO LOGGING]: 1 133 12:03:15.716 [planetinfo.record]: seed = 192 55 26 26 12:03:15.716 [planetinfo.record]: coordinates = 55 59 12:03:15.716 [planetinfo.record]: government = 0; 12:03:15.716 [planetinfo.record]: economy = 3; 12:03:15.716 [planetinfo.record]: techlevel = 7; 12:03:15.716 [planetinfo.record]: population = 32; 12:03:15.716 [planetinfo.record]: productivity = 7168; 12:03:15.716 [planetinfo.record]: name = "Quinri"; 12:03:15.716 [planetinfo.record]: inhabitant = "Human Colonial"; 12:03:15.716 [planetinfo.record]: inhabitants = "Human Colonials"; 12:03:15.716 [planetinfo.record]: description = "The planet Quinri is scourged by deadly tree grubs."; 12:03:15.718 [planetinfo.record]: air_color = 0.504155, 0.622713, 0.855264, 1; 12:03:15.718 [planetinfo.record]: cloud_alpha = 1.000000; 12:03:15.718 [planetinfo.record]: cloud_color = 0.208694, 0.568359, 0.568359, 1; 12:03:15.718 [planetinfo.record]: cloud_fraction = 0.430000; 12:03:15.718 [planetinfo.record]: land_color = 0.650735, 0.634219, 0.66, 1; 12:03:15.718 [planetinfo.record]: land_fraction = 0.570000; 12:03:15.719 [planetinfo.record]: polar_cloud_color = 0.456852, 0.755762, 0.755762, 1; 12:03:15.719 [planetinfo.record]: polar_land_color = 0.930722, 0.924879, 0.934, 1; 12:03:15.719 [planetinfo.record]: polar_sea_color = 0.939844, 0.924176, 0.887069, 1; 12:03:15.719 [planetinfo.record]: sea_color = 0.601562, 0.56145, 0.466446, 1; 12:03:15.719 [planetinfo.record]: rotation_speed = 0.000566 12:03:15.719 [planetinfo.record]: planet zpos = 651720.000000 12:03:15.719 [planetinfo.record]: sun_radius = 153321.996155 12:03:15.719 [planetinfo.record]: sun_vector = 0.336 -0.795 -0.505 12:03:15.719 [planetinfo.record]: sun_distance = 1086200 12:03:15.719 [planetinfo.record]: corona_flare = 0.016075 12:03:15.719 [planetinfo.record]: corona_hues = 0.740616 12:03:15.719 [planetinfo.record]: sun_color = 0.747604, 0.62521, 0.328523, 1 12:03:15.719 [planetinfo.record]: corona_shimmer = 0.332704 12:03:15.720 [planetinfo.record]: station_vector = -0.911 0.385 0.149 12:03:15.720 [planetinfo.record]: station = coriolis 12:03:20.211 [PLANETINFO OVER]: Done 12:03:20.212 [PLANETINFO LOGGING]: 1 134 12:03:21.217 [planetinfo.record]: seed = 168 133 54 241 12:03:21.217 [planetinfo.record]: coordinates = 133 222 12:03:21.217 [planetinfo.record]: government = 5; 12:03:21.217 [planetinfo.record]: economy = 6; 12:03:21.217 [planetinfo.record]: techlevel = 5; 12:03:21.217 [planetinfo.record]: population = 32; 12:03:21.217 [planetinfo.record]: productivity = 9216; 12:03:21.217 [planetinfo.record]: name = "Atlain"; 12:03:21.217 [planetinfo.record]: inhabitant = "Human Colonial"; 12:03:21.217 [planetinfo.record]: inhabitants = "Human Colonials"; 12:03:21.217 [planetinfo.record]: description = "The world Atlain is a dull place."; 12:03:21.222 [planetinfo.record]: air_color = 0.489754, 0.549893, 0.889734, 1; 12:03:21.222 [planetinfo.record]: cloud_alpha = 1.000000; 12:03:21.222 [planetinfo.record]: cloud_color = 0.167969, 0.388428, 0.671875, 1; 12:03:21.222 [planetinfo.record]: cloud_fraction = 0.380000; 12:03:21.222 [planetinfo.record]: land_color = 0.66, 0.425854, 0.420234, 1; 12:03:21.222 [planetinfo.record]: land_fraction = 0.460000; 12:03:21.222 [planetinfo.record]: polar_cloud_color = 0.426245, 0.590788, 0.802344, 1; 12:03:21.222 [planetinfo.record]: polar_land_color = 0.934, 0.851162, 0.849174, 1; 12:03:21.222 [planetinfo.record]: polar_sea_color = 0.87749, 0.854791, 0.907617, 1; 12:03:21.222 [planetinfo.record]: sea_color = 0.801166, 0.708749, 0.923828, 1; 12:03:21.222 [planetinfo.record]: rotation_speed = 0.003020 12:03:21.222 [planetinfo.record]: planet zpos = 416650.000000 12:03:21.222 [planetinfo.record]: sun_radius = 70845.386505 12:03:21.222 [planetinfo.record]: sun_vector = -0.534 0.348 0.771 12:03:21.222 [planetinfo.record]: sun_distance = 576900 12:03:21.222 [planetinfo.record]: corona_flare = 0.054912 12:03:21.222 [planetinfo.record]: corona_hues = 0.648323 12:03:21.222 [planetinfo.record]: sun_color = 0.812598, 0.557161, 0.287923, 1 12:03:21.223 [planetinfo.record]: corona_shimmer = 0.461964 12:03:21.223 [planetinfo.record]: station_vector = -0.983 0.168 0.079 12:03:21.223 [planetinfo.record]: station = coriolis 12:03:25.812 [PLANETINFO OVER]: Done 12:03:25.812 [PLANETINFO LOGGING]: 1 135 12:03:26.815 [planetinfo.record]: seed = 56 19 58 53 12:03:26.815 [planetinfo.record]: coordinates = 19 204 12:03:26.815 [planetinfo.record]: government = 7; 12:03:26.815 [planetinfo.record]: economy = 4; 12:03:26.815 [planetinfo.record]: techlevel = 10; 12:03:26.815 [planetinfo.record]: population = 52; 12:03:26.815 [planetinfo.record]: productivity = 27456; 12:03:26.815 [planetinfo.record]: name = "Laraisso"; 12:03:26.815 [planetinfo.record]: inhabitant = "Human Colonial"; 12:03:26.815 [planetinfo.record]: inhabitants = "Human Colonials"; 12:03:26.815 [planetinfo.record]: description = "The planet Laraisso is reasonably noted for mud tennis and its ancient mountains."; 12:03:26.828 [planetinfo.record]: air_color = 0.808492, 0.525209, 0.958676, 1; 12:03:26.828 [planetinfo.record]: cloud_alpha = 1.000000; 12:03:26.828 [planetinfo.record]: cloud_color = 0.878906, 0.21286, 0.228471, 1; 12:03:26.828 [planetinfo.record]: cloud_fraction = 0.350000; 12:03:26.828 [planetinfo.record]: land_color = 0.66, 0.103125, 0.14228, 1; 12:03:26.828 [planetinfo.record]: land_fraction = 0.480000; 12:03:26.828 [planetinfo.record]: polar_cloud_color = 0.895508, 0.471366, 0.481307, 1; 12:03:26.828 [planetinfo.record]: polar_land_color = 0.934, 0.736984, 0.750837, 1; 12:03:26.828 [planetinfo.record]: polar_sea_color = 0.940039, 0.879267, 0.879267, 1; 12:03:26.828 [planetinfo.record]: sea_color = 0.599609, 0.444554, 0.444554, 1; 12:03:26.828 [planetinfo.record]: rotation_speed = 0.004011 12:03:26.828 [planetinfo.record]: planet zpos = 617250.000000 12:03:26.828 [planetinfo.record]: sun_radius = 107241.410828 12:03:26.828 [planetinfo.record]: sun_vector = -0.093 -0.671 0.735 12:03:26.828 [planetinfo.record]: sun_distance = 740700 12:03:26.828 [planetinfo.record]: corona_flare = 0.077083 12:03:26.829 [planetinfo.record]: corona_hues = 0.913246 12:03:26.829 [planetinfo.record]: sun_color = 0.256818, 0.334865, 0.746436, 1 12:03:26.829 [planetinfo.record]: corona_shimmer = 0.454991 12:03:26.829 [planetinfo.record]: station_vector = 0.540 0.145 0.829 12:03:26.829 [planetinfo.record]: station = coriolis 12:03:31.494 [PLANETINFO OVER]: Done 12:03:31.495 [PLANETINFO LOGGING]: 1 136 12:03:32.506 [planetinfo.record]: seed = 112 167 166 25 12:03:32.506 [planetinfo.record]: coordinates = 167 93 12:03:32.506 [planetinfo.record]: government = 6; 12:03:32.506 [planetinfo.record]: economy = 5; 12:03:32.506 [planetinfo.record]: techlevel = 8; 12:03:32.506 [planetinfo.record]: population = 44; 12:03:32.506 [planetinfo.record]: productivity = 17600; 12:03:32.506 [planetinfo.record]: name = "Orrionti"; 12:03:32.506 [planetinfo.record]: inhabitant = "Green Horned Lobster"; 12:03:32.507 [planetinfo.record]: inhabitants = "Green Horned Lobsters"; 12:03:32.507 [planetinfo.record]: description = "This planet is beset by lethal disease."; 12:03:32.540 [planetinfo.record]: air_color = 0.468153, 0.69025, 0.856564, 1; 12:03:32.540 [planetinfo.record]: cloud_alpha = 1.000000; 12:03:32.540 [planetinfo.record]: cloud_color = 0.13636, 0.572266, 0.26577, 1; 12:03:32.540 [planetinfo.record]: cloud_fraction = 0.300000; 12:03:32.540 [planetinfo.record]: land_color = 0.315717, 0.291824, 0.597656, 1; 12:03:32.540 [planetinfo.record]: land_fraction = 0.250000; 12:03:32.540 [planetinfo.record]: polar_cloud_color = 0.396884, 0.75752, 0.503948, 1; 12:03:32.540 [planetinfo.record]: polar_land_color = 0.829348, 0.81995, 0.940234, 1; 12:03:32.540 [planetinfo.record]: polar_sea_color = 0.935352, 0.897915, 0.879632, 1; 12:03:32.540 [planetinfo.record]: sea_color = 0.646484, 0.542985, 0.492439, 1; 12:03:32.540 [planetinfo.record]: rotation_speed = 0.003865 12:03:32.540 [planetinfo.record]: planet zpos = 475830.000000 12:03:32.540 [planetinfo.record]: sun_radius = 122394.985809 12:03:32.540 [planetinfo.record]: sun_vector = 0.872 0.428 -0.238 12:03:32.540 [planetinfo.record]: sun_distance = 1004530 12:03:32.540 [planetinfo.record]: corona_flare = 0.088837 12:03:32.540 [planetinfo.record]: corona_hues = 0.962830 12:03:32.540 [planetinfo.record]: sun_color = 0.487383, 0.581632, 0.681091, 1 12:03:32.541 [planetinfo.record]: corona_shimmer = 0.606512 12:03:32.541 [planetinfo.record]: station_vector = 0.703 0.641 0.309 12:03:32.541 [planetinfo.record]: station = coriolis 12:03:37.755 [PLANETINFO OVER]: Done 12:03:37.756 [PLANETINFO LOGGING]: 1 137 12:03:38.775 [planetinfo.record]: seed = 144 23 122 21 12:03:38.775 [planetinfo.record]: coordinates = 23 223 12:03:38.775 [planetinfo.record]: government = 2; 12:03:38.775 [planetinfo.record]: economy = 7; 12:03:38.775 [planetinfo.record]: techlevel = 4; 12:03:38.775 [planetinfo.record]: population = 26; 12:03:38.775 [planetinfo.record]: productivity = 3744; 12:03:38.775 [planetinfo.record]: name = "Lainor"; 12:03:38.776 [planetinfo.record]: inhabitant = "Human Colonial"; 12:03:38.776 [planetinfo.record]: inhabitants = "Human Colonials"; 12:03:38.776 [planetinfo.record]: description = "Lainor is most famous for its pink oceans and its exciting sit coms."; 12:03:38.778 [planetinfo.record]: air_color = 0.658635, 0.842256, 0.78187, 1; 12:03:38.778 [planetinfo.record]: cloud_alpha = 1.000000; 12:03:38.778 [planetinfo.record]: cloud_color = 0.529297, 0.501029, 0.500351, 1; 12:03:38.778 [planetinfo.record]: cloud_fraction = 0.150000; 12:03:38.778 [planetinfo.record]: land_color = 0.187035, 0.146953, 0.66, 1; 12:03:38.778 [planetinfo.record]: land_fraction = 0.680000; 12:03:38.778 [planetinfo.record]: polar_cloud_color = 0.738184, 0.713544, 0.712953, 1; 12:03:38.778 [planetinfo.record]: polar_land_color = 0.766671, 0.75249, 0.934, 1; 12:03:38.778 [planetinfo.record]: polar_sea_color = 0.9, 0.84375, 0.84375, 1; 12:03:38.778 [planetinfo.record]: sea_color = 1, 0.75, 0.75, 1; 12:03:38.778 [planetinfo.record]: rotation_speed = 0.004934 12:03:38.778 [planetinfo.record]: planet zpos = 576660.000000 12:03:38.778 [planetinfo.record]: sun_radius = 104374.690704 12:03:38.778 [planetinfo.record]: sun_vector = 0.006 -0.976 0.218 12:03:38.778 [planetinfo.record]: sun_distance = 906180 12:03:38.778 [planetinfo.record]: corona_flare = 0.007744 12:03:38.778 [planetinfo.record]: corona_hues = 0.614540 12:03:38.778 [planetinfo.record]: sun_color = 0.1906, 0.539669, 0.715283, 1 12:03:38.779 [planetinfo.record]: corona_shimmer = 0.384434 12:03:38.779 [planetinfo.record]: station_vector = 0.563 0.554 0.614 12:03:38.779 [planetinfo.record]: station = coriolis 12:03:43.354 [PLANETINFO OVER]: Done 12:03:43.355 [PLANETINFO LOGGING]: 1 138 12:03:44.364 [planetinfo.record]: seed = 152 91 182 161 12:03:44.364 [planetinfo.record]: coordinates = 91 57 12:03:44.364 [planetinfo.record]: government = 3; 12:03:44.364 [planetinfo.record]: economy = 1; 12:03:44.364 [planetinfo.record]: techlevel = 11; 12:03:44.364 [planetinfo.record]: population = 49; 12:03:44.364 [planetinfo.record]: productivity = 24696; 12:03:44.364 [planetinfo.record]: name = "Levera"; 12:03:44.364 [planetinfo.record]: inhabitant = "Large Harmless Horned Lobster"; 12:03:44.364 [planetinfo.record]: inhabitants = "Large Harmless Horned Lobsters"; 12:03:44.364 [planetinfo.record]: description = "This world is mildly famous for its vast dense forests and its unusual dense forests."; 12:03:44.380 [planetinfo.record]: air_color = 0.651902, 0.885182, 0.863085, 1; 12:03:44.380 [planetinfo.record]: cloud_alpha = 1.000000; 12:03:44.380 [planetinfo.record]: cloud_color = 0.658203, 0.623754, 0.532219, 1; 12:03:44.380 [planetinfo.record]: cloud_fraction = 0.550000; 12:03:44.380 [planetinfo.record]: land_color = 0.58604, 0.66, 0.381563, 1; 12:03:44.380 [planetinfo.record]: land_fraction = 0.360000; 12:03:44.380 [planetinfo.record]: polar_cloud_color = 0.796191, 0.770147, 0.700944, 1; 12:03:44.380 [planetinfo.record]: polar_land_color = 0.907834, 0.934, 0.835492, 1; 12:03:44.380 [planetinfo.record]: polar_sea_color = 0.844297, 0.902388, 0.949023, 1; 12:03:44.380 [planetinfo.record]: sea_color = 0.284752, 0.409564, 0.509766, 1; 12:03:44.380 [planetinfo.record]: rotation_speed = 0.002353 12:03:44.380 [planetinfo.record]: planet zpos = 379560.000000 12:03:44.380 [planetinfo.record]: sun_radius = 90354.191589 12:03:44.380 [planetinfo.record]: sun_vector = -0.337 -0.720 -0.606 12:03:44.380 [planetinfo.record]: sun_distance = 759120 12:03:44.380 [planetinfo.record]: corona_flare = 0.087041 12:03:44.380 [planetinfo.record]: corona_hues = 0.981873 12:03:44.380 [planetinfo.record]: sun_color = 0.386659, 0.747953, 0.833926, 1 12:03:44.380 [planetinfo.record]: corona_shimmer = 0.303607 12:03:44.381 [planetinfo.record]: station_vector = 0.042 -0.999 0.021 12:03:44.381 [planetinfo.record]: station = dodecahedron 12:03:48.979 [PLANETINFO OVER]: Done 12:03:48.980 [PLANETINFO LOGGING]: 1 139 12:03:49.982 [planetinfo.record]: seed = 200 12 218 119 12:03:49.983 [planetinfo.record]: coordinates = 12 52 12:03:49.983 [planetinfo.record]: government = 1; 12:03:49.983 [planetinfo.record]: economy = 6; 12:03:49.983 [planetinfo.record]: techlevel = 2; 12:03:49.983 [planetinfo.record]: population = 16; 12:03:49.983 [planetinfo.record]: productivity = 2560; 12:03:49.983 [planetinfo.record]: name = "Tiedis"; 12:03:49.983 [planetinfo.record]: inhabitant = "Blue Slimy Lobster"; 12:03:49.983 [planetinfo.record]: inhabitants = "Blue Slimy Lobsters"; 12:03:49.983 [planetinfo.record]: description = "The world Tiedis is beset by dreadful earthquakes."; 12:03:49.988 [planetinfo.record]: air_color = 0.651155, 0.893637, 0.883916, 1; 12:03:49.989 [planetinfo.record]: cloud_alpha = 1.000000; 12:03:49.989 [planetinfo.record]: cloud_color = 0.683594, 0.66753, 0.536728, 1; 12:03:49.989 [planetinfo.record]: cloud_fraction = 0.370000; 12:03:49.989 [planetinfo.record]: land_color = 0.62163, 0.621328, 0.66, 1; 12:03:49.989 [planetinfo.record]: land_fraction = 0.600000; 12:03:49.989 [planetinfo.record]: polar_cloud_color = 0.807617, 0.795756, 0.699172, 1; 12:03:49.989 [planetinfo.record]: polar_land_color = 0.920425, 0.920318, 0.934, 1; 12:03:49.989 [planetinfo.record]: polar_sea_color = 0.932352, 0.932813, 0.873874, 1; 12:03:49.989 [planetinfo.record]: sea_color = 0.670548, 0.671875, 0.502069, 1; 12:03:49.989 [planetinfo.record]: rotation_speed = 0.003415 12:03:49.989 [planetinfo.record]: planet zpos = 693000.000000 12:03:49.989 [planetinfo.record]: sun_radius = 115465.457153 12:03:49.989 [planetinfo.record]: sun_vector = 0.259 -0.662 0.704 12:03:49.989 [planetinfo.record]: sun_distance = 877800 12:03:49.989 [planetinfo.record]: corona_flare = 0.057700 12:03:49.989 [planetinfo.record]: corona_hues = 0.726768 12:03:49.989 [planetinfo.record]: sun_color = 0.198872, 0.579566, 0.765323, 1 12:03:49.989 [planetinfo.record]: corona_shimmer = 0.307525 12:03:49.989 [planetinfo.record]: station_vector = -0.886 -0.087 0.456 12:03:49.989 [planetinfo.record]: station = coriolis 12:03:54.446 [PLANETINFO OVER]: Done 12:03:54.447 [PLANETINFO LOGGING]: 1 140 12:03:55.451 [planetinfo.record]: seed = 32 110 102 100 12:03:55.451 [planetinfo.record]: coordinates = 110 61 12:03:55.451 [planetinfo.record]: government = 4; 12:03:55.451 [planetinfo.record]: economy = 5; 12:03:55.451 [planetinfo.record]: techlevel = 6; 12:03:55.451 [planetinfo.record]: population = 34; 12:03:55.452 [planetinfo.record]: productivity = 10880; 12:03:55.452 [planetinfo.record]: name = "Zaaxeve"; 12:03:55.452 [planetinfo.record]: inhabitant = "Human Colonial"; 12:03:55.452 [planetinfo.record]: inhabitants = "Human Colonials"; 12:03:55.452 [planetinfo.record]: description = "This world is very notable for the Zaaxeveian tree wolf and its unusual sit coms."; 12:03:55.456 [planetinfo.record]: air_color = 0.831437, 0.748555, 0.997049, 1; 12:03:55.456 [planetinfo.record]: cloud_alpha = 1.000000; 12:03:55.456 [planetinfo.record]: cloud_color = 0.994141, 0.846573, 0.991835, 1; 12:03:55.456 [planetinfo.record]: cloud_fraction = 0.500000; 12:03:55.456 [planetinfo.record]: land_color = 0.66, 0.0386719, 0.51923, 1; 12:03:55.456 [planetinfo.record]: land_fraction = 0.420000; 12:03:55.456 [planetinfo.record]: polar_cloud_color = 0.947363, 0.859473, 0.94599, 1; 12:03:55.456 [planetinfo.record]: polar_land_color = 0.934, 0.714182, 0.884197, 1; 12:03:55.456 [planetinfo.record]: polar_sea_color = 0.926444, 0.939062, 0.891559, 1; 12:03:55.456 [planetinfo.record]: sea_color = 0.576623, 0.609375, 0.486072, 1; 12:03:55.456 [planetinfo.record]: rotation_speed = 0.003265 12:03:55.456 [planetinfo.record]: planet zpos = 434500.000000 12:03:55.456 [planetinfo.record]: sun_radius = 122780.532837 12:03:55.456 [planetinfo.record]: sun_vector = 0.239 0.955 0.178 12:03:55.457 [planetinfo.record]: sun_distance = 671500 12:03:55.457 [planetinfo.record]: corona_flare = 0.009781 12:03:55.457 [planetinfo.record]: corona_hues = 0.528900 12:03:55.457 [planetinfo.record]: sun_color = 0.765781, 0.408403, 0.352346, 1 12:03:55.457 [planetinfo.record]: corona_shimmer = 0.395524 12:03:55.457 [planetinfo.record]: station_vector = -0.213 0.952 0.221 12:03:55.457 [planetinfo.record]: station = coriolis 12:03:59.850 [PLANETINFO OVER]: Done 12:03:59.852 [PLANETINFO LOGGING]: 1 141 12:04:00.853 [planetinfo.record]: seed = 224 86 90 73 12:04:00.853 [planetinfo.record]: coordinates = 86 226 12:04:00.853 [planetinfo.record]: government = 4; 12:04:00.853 [planetinfo.record]: economy = 2; 12:04:00.854 [planetinfo.record]: techlevel = 9; 12:04:00.854 [planetinfo.record]: population = 43; 12:04:00.854 [planetinfo.record]: productivity = 22016; 12:04:00.854 [planetinfo.record]: name = "Esxexe"; 12:04:00.854 [planetinfo.record]: inhabitant = "Human Colonial"; 12:04:00.854 [planetinfo.record]: inhabitants = "Human Colonials"; 12:04:00.854 [planetinfo.record]: description = "This planet is a dull place."; 12:04:00.888 [planetinfo.record]: air_color = 0.77487, 0.666166, 0.8325, 1; 12:04:00.888 [planetinfo.record]: cloud_alpha = 1.000000; 12:04:00.888 [planetinfo.record]: cloud_color = 0.5, 0.5, 0.5, 1; 12:04:00.888 [planetinfo.record]: cloud_fraction = 0.390000; 12:04:00.888 [planetinfo.record]: land_color = 0.108032, 0.576172, 0.126319, 1; 12:04:00.888 [planetinfo.record]: land_fraction = 0.400000; 12:04:00.888 [planetinfo.record]: polar_cloud_color = 0.725, 0.725, 0.725, 1; 12:04:00.888 [planetinfo.record]: polar_land_color = 0.750961, 0.942383, 0.758439, 1; 12:04:00.888 [planetinfo.record]: polar_sea_color = 0.945508, 0.889645, 0.901429, 1; 12:04:00.888 [planetinfo.record]: sea_color = 0.544922, 0.416142, 0.443306, 1; 12:04:00.888 [planetinfo.record]: rotation_speed = 0.004343 12:04:00.888 [planetinfo.record]: planet zpos = 624720.000000 12:04:00.888 [planetinfo.record]: sun_radius = 137650.465698 12:04:00.888 [planetinfo.record]: sun_vector = 0.098 -0.111 -0.989 12:04:00.888 [planetinfo.record]: sun_distance = 1145320 12:04:00.888 [planetinfo.record]: corona_flare = 0.041298 12:04:00.888 [planetinfo.record]: corona_hues = 0.547531 12:04:00.888 [planetinfo.record]: sun_color = 0.203479, 0.567375, 0.680057, 1 12:04:00.888 [planetinfo.record]: corona_shimmer = 0.444035 12:04:00.888 [planetinfo.record]: station_vector = -0.515 -0.596 0.616 12:04:00.888 [planetinfo.record]: station = coriolis 12:04:06.146 [PLANETINFO OVER]: Done 12:04:06.147 [PLANETINFO LOGGING]: 1 142 12:04:07.150 [planetinfo.record]: seed = 8 239 182 148 12:04:07.150 [planetinfo.record]: coordinates = 239 34 12:04:07.150 [planetinfo.record]: government = 1; 12:04:07.150 [planetinfo.record]: economy = 2; 12:04:07.150 [planetinfo.record]: techlevel = 9; 12:04:07.151 [planetinfo.record]: population = 40; 12:04:07.151 [planetinfo.record]: productivity = 12800; 12:04:07.151 [planetinfo.record]: name = "Rabiarce"; 12:04:07.151 [planetinfo.record]: inhabitant = "Black Furry Humanoid"; 12:04:07.151 [planetinfo.record]: inhabitants = "Black Furry Humanoids"; 12:04:07.151 [planetinfo.record]: description = "The world Rabiarce is scourged by deadly disease."; 12:04:07.164 [planetinfo.record]: air_color = 0.503418, 0.976887, 0.824876, 1; 12:04:07.164 [planetinfo.record]: cloud_alpha = 1.000000; 12:04:07.164 [planetinfo.record]: cloud_color = 0.933594, 0.172371, 0.134933, 1; 12:04:07.164 [planetinfo.record]: cloud_fraction = 0.530000; 12:04:07.164 [planetinfo.record]: land_color = 0.600703, 0.66, 0.636374, 1; 12:04:07.164 [planetinfo.record]: land_fraction = 0.590000; 12:04:07.164 [planetinfo.record]: polar_cloud_color = 0.920117, 0.451221, 0.42816, 1; 12:04:07.164 [planetinfo.record]: polar_land_color = 0.913022, 0.934, 0.925641, 1; 12:04:07.164 [planetinfo.record]: polar_sea_color = 0.790058, 0.943359, 0.747133, 1; 12:04:07.164 [planetinfo.record]: sea_color = 0.198228, 0.566406, 0.0951385, 1; 12:04:07.164 [planetinfo.record]: rotation_speed = 0.001745 12:04:07.164 [planetinfo.record]: planet zpos = 489480.000000 12:04:07.164 [planetinfo.record]: sun_radius = 74891.002655 12:04:07.164 [planetinfo.record]: sun_vector = 0.063 0.187 -0.980 12:04:07.164 [planetinfo.record]: sun_distance = 734220 12:04:07.165 [planetinfo.record]: corona_flare = 0.086539 12:04:07.165 [planetinfo.record]: corona_hues = 0.856079 12:04:07.165 [planetinfo.record]: sun_color = 0.422812, 0.621193, 0.652243, 1 12:04:07.165 [planetinfo.record]: corona_shimmer = 0.561192 12:04:07.165 [planetinfo.record]: station_vector = 0.388 -0.889 0.243 12:04:07.165 [planetinfo.record]: station = coriolis 12:04:12.541 [PLANETINFO OVER]: Done 12:04:12.542 [PLANETINFO LOGGING]: 1 143 12:04:13.550 [planetinfo.record]: seed = 216 101 250 54 12:04:13.550 [planetinfo.record]: coordinates = 101 42 12:04:13.550 [planetinfo.record]: government = 3; 12:04:13.550 [planetinfo.record]: economy = 2; 12:04:13.550 [planetinfo.record]: techlevel = 8; 12:04:13.551 [planetinfo.record]: population = 38; 12:04:13.551 [planetinfo.record]: productivity = 17024; 12:04:13.551 [planetinfo.record]: name = "Vesozaxe"; 12:04:13.551 [planetinfo.record]: inhabitant = "Red Frog"; 12:04:13.551 [planetinfo.record]: inhabitants = "Red Frogs"; 12:04:13.551 [planetinfo.record]: description = "This planet is reasonably famous for the Vesozaxeian spotted shrew."; 12:04:13.572 [planetinfo.record]: air_color = 0.496828, 0.981439, 0.825852, 1; 12:04:13.572 [planetinfo.record]: cloud_alpha = 1.000000; 12:04:13.572 [planetinfo.record]: cloud_color = 0.947266, 0.150207, 0.111008, 1; 12:04:13.572 [planetinfo.record]: cloud_fraction = 0.490000; 12:04:13.572 [planetinfo.record]: land_color = 0.465271, 0.328979, 0.601562, 1; 12:04:13.572 [planetinfo.record]: land_fraction = 0.360000; 12:04:13.572 [planetinfo.record]: polar_cloud_color = 0.92627, 0.43915, 0.415193, 1; 12:04:13.572 [planetinfo.record]: polar_land_color = 0.88661, 0.833377, 0.939844, 1; 12:04:13.572 [planetinfo.record]: polar_sea_color = 0.898651, 0.933789, 0.884364, 1; 12:04:13.572 [planetinfo.record]: sea_color = 0.562449, 0.662109, 0.521928, 1; 12:04:13.572 [planetinfo.record]: rotation_speed = 0.002774 12:04:13.572 [planetinfo.record]: planet zpos = 578890.000000 12:04:13.572 [planetinfo.record]: sun_radius = 130411.421204 12:04:13.572 [planetinfo.record]: sun_vector = 0.036 -0.106 -0.994 12:04:13.572 [planetinfo.record]: sun_distance = 846070 12:04:13.572 [planetinfo.record]: corona_flare = 0.079350 12:04:13.572 [planetinfo.record]: corona_hues = 0.844078 12:04:13.572 [planetinfo.record]: sun_color = 0.713251, 0.714216, 0.7401, 1 12:04:13.573 [planetinfo.record]: corona_shimmer = 0.644354 12:04:13.573 [planetinfo.record]: station_vector = 0.349 0.881 0.319 12:04:13.573 [planetinfo.record]: station = coriolis 12:04:18.559 [PLANETINFO OVER]: Done 12:04:18.560 [PLANETINFO LOGGING]: 1 144 12:04:19.564 [planetinfo.record]: seed = 80 98 166 141 12:04:19.564 [planetinfo.record]: coordinates = 98 100 12:04:19.564 [planetinfo.record]: government = 2; 12:04:19.564 [planetinfo.record]: economy = 4; 12:04:19.564 [planetinfo.record]: techlevel = 6; 12:04:19.564 [planetinfo.record]: population = 31; 12:04:19.564 [planetinfo.record]: productivity = 8928; 12:04:19.564 [planetinfo.record]: name = "Diraza"; 12:04:19.564 [planetinfo.record]: inhabitant = "Black Insect"; 12:04:19.564 [planetinfo.record]: inhabitants = "Black Insects"; 12:04:19.564 [planetinfo.record]: description = "This world is mildly well known for its hoopy night life and vicious Mare brew."; 12:04:19.570 [planetinfo.record]: air_color = 0.537632, 0.548506, 0.864369, 1; 12:04:19.570 [planetinfo.record]: cloud_alpha = 1.000000; 12:04:19.570 [planetinfo.record]: cloud_color = 0.276909, 0.304305, 0.595703, 1; 12:04:19.570 [planetinfo.record]: cloud_fraction = 0.350000; 12:04:19.570 [planetinfo.record]: land_color = 0.66, 0.273281, 0.545193, 1; 12:04:19.570 [planetinfo.record]: land_fraction = 0.240000; 12:04:19.571 [planetinfo.record]: polar_cloud_color = 0.511169, 0.533246, 0.768066, 1; 12:04:19.571 [planetinfo.record]: polar_land_color = 0.934, 0.797184, 0.893383, 1; 12:04:19.571 [planetinfo.record]: polar_sea_color = 0.935742, 0.885326, 0.875431, 1; 12:04:19.571 [planetinfo.record]: sea_color = 0.642578, 0.504093, 0.476913, 1; 12:04:19.571 [planetinfo.record]: rotation_speed = 0.002657 12:04:19.571 [planetinfo.record]: planet zpos = 811460.000000 12:04:19.571 [planetinfo.record]: sun_radius = 149694.277039 12:04:19.571 [planetinfo.record]: sun_vector = -0.126 0.899 -0.420 12:04:19.571 [planetinfo.record]: sun_distance = 1185980 12:04:19.571 [planetinfo.record]: corona_flare = 0.014149 12:04:19.571 [planetinfo.record]: corona_hues = 0.943886 12:04:19.571 [planetinfo.record]: sun_color = 0.524901, 0.570287, 0.685883, 1 12:04:19.572 [planetinfo.record]: corona_shimmer = 0.349354 12:04:19.572 [planetinfo.record]: station_vector = -0.056 -0.980 0.193 12:04:19.572 [planetinfo.record]: station = coriolis 12:04:24.643 [PLANETINFO OVER]: Done 12:04:24.644 [PLANETINFO LOGGING]: 1 145 12:04:25.665 [planetinfo.record]: seed = 176 37 186 189 12:04:25.665 [planetinfo.record]: coordinates = 37 68 12:04:25.665 [planetinfo.record]: government = 6; 12:04:25.665 [planetinfo.record]: economy = 4; 12:04:25.665 [planetinfo.record]: techlevel = 7; 12:04:25.666 [planetinfo.record]: population = 39; 12:04:25.666 [planetinfo.record]: productivity = 18720; 12:04:25.666 [planetinfo.record]: name = "Issoar"; 12:04:25.666 [planetinfo.record]: inhabitant = "Harmless Bug-Eyed Lizard"; 12:04:25.666 [planetinfo.record]: inhabitants = "Harmless Bug-Eyed Lizards"; 12:04:25.666 [planetinfo.record]: description = "This world is noted for its exciting sit coms."; 12:04:25.688 [planetinfo.record]: air_color = 0.456239, 0.696745, 0.86567, 1; 12:04:25.688 [planetinfo.record]: cloud_alpha = 1.000000; 12:04:25.688 [planetinfo.record]: cloud_color = 0.107742, 0.599609, 0.230709, 1; 12:04:25.688 [planetinfo.record]: cloud_fraction = 0.190000; 12:04:25.688 [planetinfo.record]: land_color = 0.37125, 0.66, 0.490811, 1; 12:04:25.688 [planetinfo.record]: land_fraction = 0.590000; 12:04:25.688 [planetinfo.record]: polar_cloud_color = 0.375139, 0.769824, 0.47381, 1; 12:04:25.688 [planetinfo.record]: polar_land_color = 0.831844, 0.934, 0.874143, 1; 12:04:25.688 [planetinfo.record]: polar_sea_color = 0.936719, 0.92531, 0.880552, 1; 12:04:25.688 [planetinfo.record]: sea_color = 0.632812, 0.601983, 0.481036, 1; 12:04:25.688 [planetinfo.record]: rotation_speed = 0.003764 12:04:25.688 [planetinfo.record]: planet zpos = 679910.000000 12:04:25.688 [planetinfo.record]: sun_radius = 140689.994965 12:04:25.688 [planetinfo.record]: sun_vector = 0.522 0.391 0.758 12:04:25.688 [planetinfo.record]: sun_distance = 1421630 12:04:25.688 [planetinfo.record]: corona_flare = 0.025908 12:04:25.688 [planetinfo.record]: corona_hues = 0.736115 12:04:25.688 [planetinfo.record]: sun_color = 0.709283, 0.685172, 0.647975, 1 12:04:25.689 [planetinfo.record]: corona_shimmer = 0.619152 12:04:25.689 [planetinfo.record]: station_vector = 0.935 -0.329 0.133 12:04:25.689 [planetinfo.record]: station = coriolis 12:04:30.888 [PLANETINFO OVER]: Done 12:04:30.888 [PLANETINFO LOGGING]: 1 146 12:04:31.908 [planetinfo.record]: seed = 248 239 54 34 12:04:31.908 [planetinfo.record]: coordinates = 239 10 12:04:31.908 [planetinfo.record]: government = 7; 12:04:31.908 [planetinfo.record]: economy = 2; 12:04:31.908 [planetinfo.record]: techlevel = 12; 12:04:31.909 [planetinfo.record]: population = 58; 12:04:31.909 [planetinfo.record]: productivity = 40832; 12:04:31.909 [planetinfo.record]: name = "Xeisare"; 12:04:31.909 [planetinfo.record]: inhabitant = "Human Colonial"; 12:04:31.909 [planetinfo.record]: inhabitants = "Human Colonials"; 12:04:31.909 [planetinfo.record]: description = "Xeisare is very fabled for its unusual tropical forests."; 12:04:31.928 [planetinfo.record]: air_color = 0.613849, 0.888434, 0.800277, 1; 12:04:31.928 [planetinfo.record]: cloud_alpha = 1.000000; 12:04:31.928 [planetinfo.record]: cloud_color = 0.667969, 0.459065, 0.448792, 1; 12:04:31.928 [planetinfo.record]: cloud_fraction = 0.310000; 12:04:31.928 [planetinfo.record]: land_color = 0.638771, 0.66, 0.580078, 1; 12:04:31.928 [planetinfo.record]: land_fraction = 0.420000; 12:04:31.928 [planetinfo.record]: polar_cloud_color = 0.800586, 0.644099, 0.636403, 1; 12:04:31.928 [planetinfo.record]: polar_land_color = 0.926489, 0.934, 0.905725, 1; 12:04:31.928 [planetinfo.record]: polar_sea_color = 0.751491, 0.937305, 0.8894, 1; 12:04:31.928 [planetinfo.record]: sea_color = 0.129799, 0.626953, 0.498781, 1; 12:04:31.928 [planetinfo.record]: rotation_speed = 0.001067 12:04:31.928 [planetinfo.record]: planet zpos = 392370.000000 12:04:31.928 [planetinfo.record]: sun_radius = 88579.012299 12:04:31.928 [planetinfo.record]: sun_vector = -0.605 0.722 0.336 12:04:31.928 [planetinfo.record]: sun_distance = 820410 12:04:31.929 [planetinfo.record]: corona_flare = 0.055736 12:04:31.929 [planetinfo.record]: corona_hues = 0.946777 12:04:31.929 [planetinfo.record]: sun_color = 0.2346, 0.290728, 0.775171, 1 12:04:31.929 [planetinfo.record]: corona_shimmer = 0.292402 12:04:31.929 [planetinfo.record]: station_vector = 0.601 0.799 0.011 12:04:31.929 [planetinfo.record]: station = dodecahedron 12:04:36.860 [PLANETINFO OVER]: Done 12:04:36.861 [PLANETINFO LOGGING]: 1 147 12:04:37.863 [planetinfo.record]: seed = 104 110 154 186 12:04:37.863 [planetinfo.record]: coordinates = 110 47 12:04:37.863 [planetinfo.record]: government = 5; 12:04:37.863 [planetinfo.record]: economy = 7; 12:04:37.863 [planetinfo.record]: techlevel = 5; 12:04:37.863 [planetinfo.record]: population = 33; 12:04:37.863 [planetinfo.record]: productivity = 7128; 12:04:37.863 [planetinfo.record]: name = "Quedle"; 12:04:37.863 [planetinfo.record]: inhabitant = "Harmless Bug-Eyed Lobster"; 12:04:37.863 [planetinfo.record]: inhabitants = "Harmless Bug-Eyed Lobsters"; 12:04:37.863 [planetinfo.record]: description = "Quedle is cursed by killer edible talking treeoids."; 12:04:37.867 [planetinfo.record]: air_color = 0.583306, 0.849784, 0.99835, 1; 12:04:37.867 [planetinfo.record]: cloud_alpha = 1.000000; 12:04:37.867 [planetinfo.record]: cloud_color = 0.350876, 0.998047, 0.406492, 1; 12:04:37.867 [planetinfo.record]: cloud_fraction = 0.480000; 12:04:37.867 [planetinfo.record]: land_color = 0.628906, 0.570177, 0.518356, 1; 12:04:37.867 [planetinfo.record]: land_fraction = 0.650000; 12:04:37.867 [planetinfo.record]: polar_cloud_color = 0.564468, 0.949121, 0.597524, 1; 12:04:37.867 [planetinfo.record]: polar_land_color = 0.937109, 0.915232, 0.895928, 1; 12:04:37.867 [planetinfo.record]: polar_sea_color = 0.779377, 0.943359, 0.878023, 1; 12:04:37.869 [planetinfo.record]: sea_color = 0.172577, 0.566406, 0.40949, 1; 12:04:37.869 [planetinfo.record]: rotation_speed = 0.002211 12:04:37.869 [planetinfo.record]: planet zpos = 548600.000000 12:04:37.869 [planetinfo.record]: sun_radius = 151635.129395 12:04:37.869 [planetinfo.record]: sun_vector = 0.060 -0.638 0.767 12:04:37.869 [planetinfo.record]: sun_distance = 1152060 12:04:37.869 [planetinfo.record]: corona_flare = 0.015318 12:04:37.869 [planetinfo.record]: corona_hues = 0.764229 12:04:37.869 [planetinfo.record]: sun_color = 0.735037, 0.723587, 0.692263, 1 12:04:37.869 [planetinfo.record]: corona_shimmer = 0.365470 12:04:37.870 [planetinfo.record]: station_vector = 0.742 -0.633 0.221 12:04:37.870 [planetinfo.record]: station = coriolis 12:04:43.429 [PLANETINFO OVER]: Done 12:04:43.430 [PLANETINFO LOGGING]: 1 148 12:04:44.450 [planetinfo.record]: seed = 0 148 102 109 12:04:44.450 [planetinfo.record]: coordinates = 148 129 12:04:44.450 [planetinfo.record]: government = 0; 12:04:44.450 [planetinfo.record]: economy = 3; 12:04:44.450 [planetinfo.record]: techlevel = 4; 12:04:44.450 [planetinfo.record]: population = 20; 12:04:44.450 [planetinfo.record]: productivity = 4480; 12:04:44.450 [planetinfo.record]: name = "Dixeza"; 12:04:44.450 [planetinfo.record]: inhabitant = "Human Colonial"; 12:04:44.450 [planetinfo.record]: inhabitants = "Human Colonials"; 12:04:44.450 [planetinfo.record]: description = "Dixeza is ravaged by dreadful civil war."; 12:04:44.488 [planetinfo.record]: air_color = 0.49506, 0.667134, 0.843557, 1; 12:04:44.488 [planetinfo.record]: cloud_alpha = 1.000000; 12:04:44.488 [planetinfo.record]: cloud_color = 0.19162, 0.533203, 0.373086, 1; 12:04:44.488 [planetinfo.record]: cloud_fraction = 0.230000; 12:04:44.488 [planetinfo.record]: land_color = 0.103125, 0.333706, 0.66, 1; 12:04:44.488 [planetinfo.record]: land_fraction = 0.250000; 12:04:44.488 [planetinfo.record]: polar_cloud_color = 0.443676, 0.739941, 0.601067, 1; 12:04:44.488 [planetinfo.record]: polar_land_color = 0.736984, 0.818561, 0.934, 1; 12:04:44.488 [planetinfo.record]: polar_sea_color = 0.880469, 0.929102, 0.894907, 1; 12:04:44.488 [planetinfo.record]: sea_color = 0.560541, 0.708984, 0.60461, 1; 12:04:44.488 [planetinfo.record]: rotation_speed = 0.002025 12:04:44.488 [planetinfo.record]: planet zpos = 817960.000000 12:04:44.488 [planetinfo.record]: sun_radius = 138611.984253 12:04:44.488 [planetinfo.record]: sun_vector = 0.014 0.959 0.284 12:04:44.488 [planetinfo.record]: sun_distance = 1258400 12:04:44.489 [planetinfo.record]: corona_flare = 0.016475 12:04:44.489 [planetinfo.record]: corona_hues = 0.933975 12:04:44.489 [planetinfo.record]: sun_color = 0.303571, 0.807154, 0.81702, 1 12:04:44.489 [planetinfo.record]: corona_shimmer = 0.312133 12:04:44.489 [planetinfo.record]: station_vector = 0.842 -0.058 0.536 12:04:44.489 [planetinfo.record]: station = coriolis 12:04:49.270 [PLANETINFO OVER]: Done 12:04:49.270 [PLANETINFO LOGGING]: 1 149 12:04:50.275 [planetinfo.record]: seed = 0 116 154 122 12:04:50.275 [planetinfo.record]: coordinates = 116 132 12:04:50.275 [planetinfo.record]: government = 0; 12:04:50.275 [planetinfo.record]: economy = 6; 12:04:50.275 [planetinfo.record]: techlevel = 1; 12:04:50.275 [planetinfo.record]: population = 11; 12:04:50.275 [planetinfo.record]: productivity = 1408; 12:04:50.275 [planetinfo.record]: name = "Quenle"; 12:04:50.275 [planetinfo.record]: inhabitant = "Blue Slimy Lizard"; 12:04:50.275 [planetinfo.record]: inhabitants = "Blue Slimy Lizards"; 12:04:50.275 [planetinfo.record]: description = "This world is mildly famous for its vast rain forests and its great dense forests."; 12:04:50.277 [planetinfo.record]: air_color = 0.793008, 0.521114, 0.958025, 1; 12:04:50.277 [planetinfo.record]: cloud_alpha = 1.000000; 12:04:50.277 [planetinfo.record]: cloud_color = 0.876953, 0.20211, 0.281193, 1; 12:04:50.277 [planetinfo.record]: cloud_fraction = 0.330000; 12:04:50.277 [planetinfo.record]: land_color = 0.62727, 0.66, 0.492422, 1; 12:04:50.277 [planetinfo.record]: land_fraction = 0.700000; 12:04:50.277 [planetinfo.record]: polar_cloud_color = 0.894629, 0.464351, 0.514774, 1; 12:04:50.278 [planetinfo.record]: polar_land_color = 0.922421, 0.934, 0.874713, 1; 12:04:50.278 [planetinfo.record]: polar_sea_color = 0.94668, 0.874425, 0.721104, 1; 12:04:50.278 [planetinfo.record]: sea_color = 0.533203, 0.370417, 0.0249939, 1; 12:04:50.278 [planetinfo.record]: rotation_speed = 0.003120 12:04:50.278 [planetinfo.record]: planet zpos = 659040.000000 12:04:50.278 [planetinfo.record]: sun_radius = 135290.445557 12:04:50.278 [planetinfo.record]: sun_vector = -0.316 -0.929 0.195 12:04:50.278 [planetinfo.record]: sun_distance = 1208240 12:04:50.278 [planetinfo.record]: corona_flare = 0.072926 12:04:50.278 [planetinfo.record]: corona_hues = 0.833481 12:04:50.278 [planetinfo.record]: sun_color = 0.492522, 0.749988, 0.846552, 1 12:04:50.278 [planetinfo.record]: corona_shimmer = 0.477090 12:04:50.278 [planetinfo.record]: station_vector = 0.132 0.936 0.327 12:04:50.278 [planetinfo.record]: station = coriolis 12:04:54.879 [PLANETINFO OVER]: Done 12:04:54.879 [PLANETINFO LOGGING]: 1 150 12:04:55.883 [planetinfo.record]: seed = 104 78 54 34 12:04:55.883 [planetinfo.record]: coordinates = 78 97 12:04:55.883 [planetinfo.record]: government = 5; 12:04:55.883 [planetinfo.record]: economy = 1; 12:04:55.883 [planetinfo.record]: techlevel = 11; 12:04:55.883 [planetinfo.record]: population = 51; 12:04:55.883 [planetinfo.record]: productivity = 33048; 12:04:55.883 [planetinfo.record]: name = "Xeatxe"; 12:04:55.883 [planetinfo.record]: inhabitant = "Human Colonial"; 12:04:55.883 [planetinfo.record]: inhabitants = "Human Colonials"; 12:04:55.883 [planetinfo.record]: description = "The world Xeatxe is very noted for its ancient Xeatxeian Onin banana plantations but cursed by deadly civil war."; 12:04:55.896 [planetinfo.record]: air_color = 0.65534, 0.49382, 0.946318, 1; 12:04:55.896 [planetinfo.record]: cloud_alpha = 1.000000; 12:04:55.896 [planetinfo.record]: cloud_color = 0.841797, 0.138107, 0.781324, 1; 12:04:55.896 [planetinfo.record]: cloud_fraction = 0.510000; 12:04:55.896 [planetinfo.record]: land_color = 0.504809, 0.66, 0.363516, 1; 12:04:55.896 [planetinfo.record]: land_fraction = 0.410000; 12:04:55.896 [planetinfo.record]: polar_cloud_color = 0.878809, 0.419665, 0.839351, 1; 12:04:55.896 [planetinfo.record]: polar_land_color = 0.879095, 0.934, 0.829107, 1; 12:04:55.896 [planetinfo.record]: polar_sea_color = 0.936523, 0.906196, 0.876802, 1; 12:04:55.896 [planetinfo.record]: sea_color = 0.634766, 0.552543, 0.472851, 1; 12:04:55.896 [planetinfo.record]: rotation_speed = 0.000417 12:04:55.896 [planetinfo.record]: planet zpos = 408720.000000 12:04:55.896 [planetinfo.record]: sun_radius = 90139.777527 12:04:55.896 [planetinfo.record]: sun_vector = -0.611 0.753 -0.242 12:04:55.896 [planetinfo.record]: sun_distance = 579020 12:04:55.896 [planetinfo.record]: corona_flare = 0.057016 12:04:55.897 [planetinfo.record]: corona_hues = 0.984680 12:04:55.897 [planetinfo.record]: sun_color = 0.255135, 0.418586, 0.698865, 1 12:04:55.897 [planetinfo.record]: corona_shimmer = 0.431857 12:04:55.897 [planetinfo.record]: station_vector = 0.972 -0.235 0.004 12:04:55.897 [planetinfo.record]: station = dodecahedron 12:05:00.716 [PLANETINFO OVER]: Done 12:05:00.718 [PLANETINFO LOGGING]: 1 151 12:05:01.719 [planetinfo.record]: seed = 120 54 186 74 12:05:01.719 [planetinfo.record]: coordinates = 54 66 12:05:01.719 [planetinfo.record]: government = 7; 12:05:01.719 [planetinfo.record]: economy = 2; 12:05:01.719 [planetinfo.record]: techlevel = 11; 12:05:01.719 [planetinfo.record]: population = 54; 12:05:01.719 [planetinfo.record]: productivity = 38016; 12:05:01.719 [planetinfo.record]: name = "Argezabe"; 12:05:01.719 [planetinfo.record]: inhabitant = "Small Yellow Fat Feline"; 12:05:01.719 [planetinfo.record]: inhabitants = "Small Yellow Fat Felines"; 12:05:01.719 [planetinfo.record]: description = "The world Argezabe is mildly noted for its ancient mountains but ravaged by frequent earthquakes."; 12:05:01.739 [planetinfo.record]: air_color = 0.636419, 0.802503, 0.978188, 1; 12:05:01.739 [planetinfo.record]: cloud_alpha = 1.000000; 12:05:01.739 [planetinfo.record]: cloud_color = 0.516357, 0.9375, 0.74996, 1; 12:05:01.739 [planetinfo.record]: cloud_fraction = 0.320000; 12:05:01.739 [planetinfo.record]: land_color = 0.652266, 0.66, 0.655468, 1; 12:05:01.739 [planetinfo.record]: land_fraction = 0.570000; 12:05:01.739 [planetinfo.record]: polar_cloud_color = 0.663048, 0.921875, 0.806616, 1; 12:05:01.739 [planetinfo.record]: polar_land_color = 0.931264, 0.934, 0.932397, 1; 12:05:01.739 [planetinfo.record]: polar_sea_color = 0.740044, 0.936719, 0.849137, 1; 12:05:01.739 [planetinfo.record]: sea_color = 0.101349, 0.632812, 0.396145, 1; 12:05:01.739 [planetinfo.record]: rotation_speed = 0.001526 12:05:01.739 [planetinfo.record]: planet zpos = 705900.000000 12:05:01.739 [planetinfo.record]: sun_radius = 125892.713928 12:05:01.739 [planetinfo.record]: sun_vector = -0.703 -0.293 -0.648 12:05:01.739 [planetinfo.record]: sun_distance = 1031700 12:05:01.740 [planetinfo.record]: corona_flare = 0.089447 12:05:01.740 [planetinfo.record]: corona_hues = 0.721176 12:05:01.740 [planetinfo.record]: sun_color = 0.804501, 0.801256, 0.799106, 1 12:05:01.740 [planetinfo.record]: corona_shimmer = 0.334866 12:05:01.740 [planetinfo.record]: station_vector = 0.824 0.483 0.297 12:05:01.740 [planetinfo.record]: station = dodecahedron 12:05:06.738 [PLANETINFO OVER]: Done 12:05:06.738 [PLANETINFO LOGGING]: 1 152 12:05:07.760 [planetinfo.record]: seed = 48 83 166 91 12:05:07.760 [planetinfo.record]: coordinates = 83 68 12:05:07.760 [planetinfo.record]: government = 6; 12:05:07.760 [planetinfo.record]: economy = 4; 12:05:07.760 [planetinfo.record]: techlevel = 9; 12:05:07.760 [planetinfo.record]: population = 47; 12:05:07.760 [planetinfo.record]: productivity = 22560; 12:05:07.760 [planetinfo.record]: name = "Anbexeat"; 12:05:07.760 [planetinfo.record]: inhabitant = "Yellow Lizard"; 12:05:07.760 [planetinfo.record]: inhabitants = "Yellow Lizards"; 12:05:07.760 [planetinfo.record]: description = "This planet is reasonably famous for its inhabitants’ ancient mating traditions."; 12:05:07.800 [planetinfo.record]: air_color = 0.559477, 0.550458, 0.859816, 1; 12:05:07.800 [planetinfo.record]: cloud_alpha = 1.000000; 12:05:07.800 [planetinfo.record]: cloud_color = 0.330785, 0.302383, 0.582031, 1; 12:05:07.800 [planetinfo.record]: cloud_fraction = 0.390000; 12:05:07.800 [planetinfo.record]: land_color = 0.650896, 0.66, 0.368672, 1; 12:05:07.800 [planetinfo.record]: land_fraction = 0.550000; 12:05:07.800 [planetinfo.record]: polar_cloud_color = 0.556354, 0.533117, 0.761914, 1; 12:05:07.800 [planetinfo.record]: polar_land_color = 0.930779, 0.934, 0.830932, 1; 12:05:07.800 [planetinfo.record]: polar_sea_color = 0.864881, 0.921484, 0.892299, 1; 12:05:07.800 [planetinfo.record]: sea_color = 0.592241, 0.785156, 0.685684, 1; 12:05:07.800 [planetinfo.record]: rotation_speed = 0.001382 12:05:07.800 [planetinfo.record]: planet zpos = 685800.000000 12:05:07.800 [planetinfo.record]: sun_radius = 146829.700470 12:05:07.800 [planetinfo.record]: sun_vector = -0.724 0.466 0.508 12:05:07.800 [planetinfo.record]: sun_distance = 914400 12:05:07.800 [planetinfo.record]: corona_flare = 0.040620 12:05:07.800 [planetinfo.record]: corona_hues = 0.735504 12:05:07.800 [planetinfo.record]: sun_color = 0.735712, 0.558055, 0.289964, 1 12:05:07.800 [planetinfo.record]: corona_shimmer = 0.410920 12:05:07.801 [planetinfo.record]: station_vector = 0.920 -0.201 0.336 12:05:07.801 [planetinfo.record]: station = coriolis 12:05:13.058 [PLANETINFO OVER]: Done 12:05:13.060 [PLANETINFO LOGGING]: 1 153 12:05:14.077 [planetinfo.record]: seed = 208 241 250 135 12:05:14.077 [planetinfo.record]: coordinates = 241 162 12:05:14.077 [planetinfo.record]: government = 2; 12:05:14.077 [planetinfo.record]: economy = 2; 12:05:14.077 [planetinfo.record]: techlevel = 7; 12:05:14.077 [planetinfo.record]: population = 33; 12:05:14.077 [planetinfo.record]: productivity = 12672; 12:05:14.077 [planetinfo.record]: name = "Soteve"; 12:05:14.077 [planetinfo.record]: inhabitant = "Fierce Black Bony Feline"; 12:05:14.077 [planetinfo.record]: inhabitants = "Fierce Black Bony Felines"; 12:05:14.077 [planetinfo.record]: description = "The world Soteve is scourged by killer edible arts graduates."; 12:05:14.086 [planetinfo.record]: air_color = 0.778318, 0.655263, 0.843557, 1; 12:05:14.086 [planetinfo.record]: cloud_alpha = 1.000000; 12:05:14.086 [planetinfo.record]: cloud_color = 0.533203, 0.495712, 0.496591, 1; 12:05:14.086 [planetinfo.record]: cloud_fraction = 0.210000; 12:05:14.086 [planetinfo.record]: land_color = 0.66, 0.376185, 0.188203, 1; 12:05:14.096 [planetinfo.record]: land_fraction = 0.490000; 12:05:14.096 [planetinfo.record]: polar_cloud_color = 0.739941, 0.707424, 0.708187, 1; 12:05:14.096 [planetinfo.record]: polar_land_color = 0.934, 0.83359, 0.767084, 1; 12:05:14.096 [planetinfo.record]: polar_sea_color = 0.940625, 0.880917, 0.906107, 1; 12:05:14.096 [planetinfo.record]: sea_color = 0.59375, 0.442993, 0.506594, 1; 12:05:14.096 [planetinfo.record]: rotation_speed = 0.002520 12:05:14.096 [planetinfo.record]: planet zpos = 630370.000000 12:05:14.096 [planetinfo.record]: sun_radius = 100032.082062 12:05:14.096 [planetinfo.record]: sun_vector = 0.848 0.228 0.479 12:05:14.096 [planetinfo.record]: sun_distance = 921310 12:05:14.096 [planetinfo.record]: corona_flare = 0.052481 12:05:14.096 [planetinfo.record]: corona_hues = 0.721237 12:05:14.096 [planetinfo.record]: sun_color = 0.839145, 0.452499, 0.306808, 1 12:05:14.097 [planetinfo.record]: corona_shimmer = 0.322428 12:05:14.097 [planetinfo.record]: station_vector = -0.042 -0.934 0.355 12:05:14.097 [planetinfo.record]: station = coriolis 12:05:19.164 [PLANETINFO OVER]: Done 12:05:19.165 [PLANETINFO LOGGING]: 1 154 12:05:20.170 [planetinfo.record]: seed = 88 58 182 236 12:05:20.170 [planetinfo.record]: coordinates = 58 150 12:05:20.170 [planetinfo.record]: government = 3; 12:05:20.170 [planetinfo.record]: economy = 6; 12:05:20.170 [planetinfo.record]: techlevel = 5; 12:05:20.170 [planetinfo.record]: population = 30; 12:05:20.170 [planetinfo.record]: productivity = 6720; 12:05:20.170 [planetinfo.record]: name = "Inisza"; 12:05:20.170 [planetinfo.record]: inhabitant = "Fat Bird"; 12:05:20.170 [planetinfo.record]: inhabitants = "Fat Birds"; 12:05:20.171 [planetinfo.record]: description = "Inisza is most noted for its inhabitants’ exceptional love for tourists and its unusual oceans."; 12:05:20.188 [planetinfo.record]: air_color = 0.640158, 0.566733, 0.872824, 1; 12:05:20.188 [planetinfo.record]: cloud_alpha = 1.000000; 12:05:20.188 [planetinfo.record]: cloud_color = 0.546338, 0.339661, 0.621094, 1; 12:05:20.188 [planetinfo.record]: cloud_fraction = 0.290000; 12:05:20.188 [planetinfo.record]: land_color = 0.135849, 0.599609, 0.393091, 1; 12:05:20.188 [planetinfo.record]: land_fraction = 0.630000; 12:05:20.188 [planetinfo.record]: polar_cloud_color = 0.720854, 0.558738, 0.779492, 1; 12:05:20.188 [planetinfo.record]: polar_land_color = 0.758274, 0.940039, 0.859097, 1; 12:05:20.188 [planetinfo.record]: polar_sea_color = 0.87416, 0.926367, 0.869736, 1; 12:05:20.188 [planetinfo.record]: sea_color = 0.57034, 0.736328, 0.556273, 1; 12:05:20.189 [planetinfo.record]: rotation_speed = 0.004882 12:05:20.189 [planetinfo.record]: planet zpos = 594600.000000 12:05:20.189 [planetinfo.record]: sun_radius = 123221.450500 12:05:20.195 [planetinfo.record]: sun_vector = 0.048 0.993 -0.112 12:05:20.195 [planetinfo.record]: sun_distance = 1070280 12:05:20.196 [planetinfo.record]: corona_flare = 0.021730 12:05:20.196 [planetinfo.record]: corona_hues = 0.712120 12:05:20.196 [planetinfo.record]: sun_color = 0.656717, 0.687666, 0.70722, 1 12:05:20.196 [planetinfo.record]: corona_shimmer = 0.383036 12:05:20.196 [planetinfo.record]: station_vector = -0.031 0.141 0.989 12:05:20.196 [planetinfo.record]: station = coriolis 12:05:24.901 [PLANETINFO OVER]: Done 12:05:24.902 [PLANETINFO LOGGING]: 1 155 12:05:25.926 [planetinfo.record]: seed = 8 142 90 47 12:05:25.926 [planetinfo.record]: coordinates = 142 228 12:05:25.926 [planetinfo.record]: government = 1; 12:05:25.926 [planetinfo.record]: economy = 6; 12:05:25.926 [planetinfo.record]: techlevel = 4; 12:05:25.926 [planetinfo.record]: population = 24; 12:05:25.926 [planetinfo.record]: productivity = 3840; 12:05:25.926 [planetinfo.record]: name = "Aleri"; 12:05:25.927 [planetinfo.record]: inhabitant = "Human Colonial"; 12:05:25.927 [planetinfo.record]: inhabitants = "Human Colonials"; 12:05:25.927 [planetinfo.record]: description = "This planet is very notable for the Aleriian tree snake and its unusual casinos."; 12:05:25.937 [planetinfo.record]: air_color = 0.487551, 0.996398, 0.872744, 1; 12:05:25.937 [planetinfo.record]: cloud_alpha = 1.000000; 12:05:25.937 [planetinfo.record]: cloud_color = 0.992188, 0.326409, 0.0658875, 1; 12:05:25.937 [planetinfo.record]: cloud_fraction = 0.320000; 12:05:25.938 [planetinfo.record]: land_color = 0.250702, 0.492558, 0.517578, 1; 12:05:25.938 [planetinfo.record]: land_fraction = 0.690000; 12:05:25.938 [planetinfo.record]: polar_cloud_color = 0.946484, 0.54954, 0.394214, 1; 12:05:25.938 [planetinfo.record]: polar_land_color = 0.826008, 0.936783, 0.948242, 1; 12:05:25.938 [planetinfo.record]: polar_sea_color = 0.87011, 0.921875, 0.914595, 1; 12:05:25.938 [planetinfo.record]: sea_color = 0.605774, 0.78125, 0.756574, 1; 12:05:25.938 [planetinfo.record]: rotation_speed = 0.000902 12:05:25.938 [planetinfo.record]: planet zpos = 747780.000000 12:05:25.938 [planetinfo.record]: sun_radius = 150977.920532 12:05:25.938 [planetinfo.record]: sun_vector = 0.325 0.240 -0.915 12:05:25.938 [planetinfo.record]: sun_distance = 1427580 12:05:25.938 [planetinfo.record]: corona_flare = 0.009508 12:05:25.938 [planetinfo.record]: corona_hues = 0.631828 12:05:25.938 [planetinfo.record]: sun_color = 0.320574, 0.354408, 0.826505, 1 12:05:25.938 [planetinfo.record]: corona_shimmer = 0.325884 12:05:25.939 [planetinfo.record]: station_vector = -0.341 0.189 0.921 12:05:25.940 [planetinfo.record]: station = coriolis 12:05:30.632 [PLANETINFO OVER]: Done 12:05:30.633 [PLANETINFO LOGGING]: 1 156 12:05:31.641 [planetinfo.record]: seed = 224 47 102 48 12:05:31.641 [planetinfo.record]: coordinates = 47 94 12:05:31.641 [planetinfo.record]: government = 4; 12:05:31.641 [planetinfo.record]: economy = 6; 12:05:31.641 [planetinfo.record]: techlevel = 6; 12:05:31.641 [planetinfo.record]: population = 35; 12:05:31.641 [planetinfo.record]: productivity = 8960; 12:05:31.641 [planetinfo.record]: name = "Eronona"; 12:05:31.642 [planetinfo.record]: inhabitant = "Human Colonial"; 12:05:31.642 [planetinfo.record]: inhabitants = "Human Colonials"; 12:05:31.642 [planetinfo.record]: description = "The planet Eronona is scourged by evil disease."; 12:05:31.648 [planetinfo.record]: air_color = 0.704369, 0.751371, 0.963229, 1; 12:05:31.648 [planetinfo.record]: cloud_alpha = 1.000000; 12:05:31.648 [planetinfo.record]: cloud_color = 0.707787, 0.805957, 0.892578, 1; 12:05:31.648 [planetinfo.record]: cloud_fraction = 0.450000; 12:05:31.648 [planetinfo.record]: land_color = 0.141797, 0.222766, 0.66, 1; 12:05:31.648 [planetinfo.record]: land_fraction = 0.240000; 12:05:31.649 [planetinfo.record]: polar_cloud_color = 0.78499, 0.846971, 0.90166, 1; 12:05:31.649 [planetinfo.record]: polar_land_color = 0.750666, 0.779312, 0.934, 1; 12:05:31.649 [planetinfo.record]: polar_sea_color = 0.897086, 0.929492, 0.87267, 1; 12:05:31.649 [planetinfo.record]: sea_color = 0.606748, 0.705078, 0.532664, 1; 12:05:31.649 [planetinfo.record]: rotation_speed = 0.000732 12:05:31.649 [planetinfo.record]: planet zpos = 286300.000000 12:05:31.649 [planetinfo.record]: sun_radius = 69764.218903 12:05:31.649 [planetinfo.record]: sun_vector = 0.457 -0.704 -0.544 12:05:31.649 [planetinfo.record]: sun_distance = 658490 12:05:31.649 [planetinfo.record]: corona_flare = 0.004765 12:05:31.649 [planetinfo.record]: corona_hues = 0.917381 12:05:31.649 [planetinfo.record]: sun_color = 0.704875, 0.758894, 0.817941, 1 12:05:31.650 [planetinfo.record]: corona_shimmer = 0.721953 12:05:31.650 [planetinfo.record]: station_vector = 0.201 -0.040 0.979 12:05:31.650 [planetinfo.record]: station = coriolis 12:05:35.718 [PLANETINFO OVER]: Done 12:05:35.720 [PLANETINFO LOGGING]: 1 157 12:05:36.721 [planetinfo.record]: seed = 32 15 218 237 12:05:36.721 [planetinfo.record]: coordinates = 15 31 12:05:36.721 [planetinfo.record]: government = 4; 12:05:36.721 [planetinfo.record]: economy = 7; 12:05:36.721 [planetinfo.record]: techlevel = 5; 12:05:36.721 [planetinfo.record]: population = 32; 12:05:36.721 [planetinfo.record]: productivity = 6144; 12:05:36.721 [planetinfo.record]: name = "Diteor"; 12:05:36.721 [planetinfo.record]: inhabitant = "Slimy Frog"; 12:05:36.721 [planetinfo.record]: inhabitants = "Slimy Frogs"; 12:05:36.721 [planetinfo.record]: description = "This world is very fabled for its weird volcanoes."; 12:05:36.740 [planetinfo.record]: air_color = 0.736364, 0.538566, 0.930059, 1; 12:05:36.740 [planetinfo.record]: cloud_alpha = 1.000000; 12:05:36.740 [planetinfo.record]: cloud_color = 0.792969, 0.266388, 0.513223, 1; 12:05:36.740 [planetinfo.record]: cloud_fraction = 0.430000; 12:05:36.740 [planetinfo.record]: land_color = 0.603845, 0.66, 0.572344, 1; 12:05:36.740 [planetinfo.record]: land_fraction = 0.370000; 12:05:36.740 [planetinfo.record]: polar_cloud_color = 0.856836, 0.501216, 0.667913, 1; 12:05:36.740 [planetinfo.record]: polar_land_color = 0.914133, 0.934, 0.902988, 1; 12:05:36.740 [planetinfo.record]: polar_sea_color = 0.706955, 0.929297, 0.866763, 1; 12:05:36.740 [planetinfo.record]: sea_color = 0.0303802, 0.707031, 0.516723, 1; 12:05:36.740 [planetinfo.record]: rotation_speed = 0.001907 12:05:36.740 [planetinfo.record]: planet zpos = 677490.000000 12:05:36.740 [planetinfo.record]: sun_radius = 204302.570801 12:05:36.740 [planetinfo.record]: sun_vector = 0.452 -0.645 0.616 12:05:36.740 [planetinfo.record]: sun_distance = 1231800 12:05:36.740 [planetinfo.record]: corona_flare = 0.045001 12:05:36.740 [planetinfo.record]: corona_hues = 0.828346 12:05:36.740 [planetinfo.record]: sun_color = 0.454551, 0.465777, 0.65029, 1 12:05:36.740 [planetinfo.record]: corona_shimmer = 0.363923 12:05:36.740 [planetinfo.record]: station_vector = -0.797 -0.286 0.533 12:05:36.740 [planetinfo.record]: station = coriolis 12:05:41.866 [PLANETINFO OVER]: Done 12:05:41.867 [PLANETINFO LOGGING]: 1 158 12:05:42.872 [planetinfo.record]: seed = 200 35 182 89 12:05:42.872 [planetinfo.record]: coordinates = 35 25 12:05:42.872 [planetinfo.record]: government = 1; 12:05:42.872 [planetinfo.record]: economy = 3; 12:05:42.872 [planetinfo.record]: techlevel = 8; 12:05:42.872 [planetinfo.record]: population = 37; 12:05:42.872 [planetinfo.record]: productivity = 10360; 12:05:42.873 [planetinfo.record]: name = "Orverace"; 12:05:42.873 [planetinfo.record]: inhabitant = "Yellow Horned Lobster"; 12:05:42.873 [planetinfo.record]: inhabitants = "Yellow Horned Lobsters"; 12:05:42.873 [planetinfo.record]: description = "The world Orverace is very famous for its unusual casinos but beset by lethal disease."; 12:05:42.884 [planetinfo.record]: air_color = 0.5175, 0.979488, 0.881643, 1; 12:05:42.885 [planetinfo.record]: cloud_alpha = 1.000000; 12:05:42.885 [planetinfo.record]: cloud_color = 0.941406, 0.46105, 0.172836, 1; 12:05:42.885 [planetinfo.record]: cloud_fraction = 0.360000; 12:05:42.885 [planetinfo.record]: land_color = 0.578145, 0.5775, 0.66, 1; 12:05:42.885 [planetinfo.record]: land_fraction = 0.530000; 12:05:42.885 [planetinfo.record]: polar_cloud_color = 0.923633, 0.629078, 0.452346, 1; 12:05:42.885 [planetinfo.record]: polar_land_color = 0.905041, 0.904813, 0.934, 1; 12:05:42.885 [planetinfo.record]: polar_sea_color = 0.774085, 0.945898, 0.768542, 1; 12:05:42.885 [planetinfo.record]: sea_color = 0.147934, 0.541016, 0.135254, 1; 12:05:42.886 [planetinfo.record]: rotation_speed = 0.004186 12:05:42.886 [planetinfo.record]: planet zpos = 463950.000000 12:05:42.886 [planetinfo.record]: sun_radius = 116387.874603 12:05:42.886 [planetinfo.record]: sun_vector = 0.536 -0.707 0.461 12:05:42.886 [planetinfo.record]: sun_distance = 1237200 12:05:42.886 [planetinfo.record]: corona_flare = 0.047881 12:05:42.886 [planetinfo.record]: corona_hues = 0.694336 12:05:42.886 [planetinfo.record]: sun_color = 0.602017, 0.661396, 0.701126, 1 12:05:42.886 [planetinfo.record]: corona_shimmer = 0.319140 12:05:42.887 [planetinfo.record]: station_vector = 0.620 -0.749 0.234 12:05:42.887 [planetinfo.record]: station = coriolis 12:05:47.468 [PLANETINFO OVER]: Done 12:05:47.469 [PLANETINFO LOGGING]: 1 159 12:05:48.490 [planetinfo.record]: seed = 24 5 122 176 12:05:48.490 [planetinfo.record]: coordinates = 5 20 12:05:48.490 [planetinfo.record]: government = 3; 12:05:48.490 [planetinfo.record]: economy = 4; 12:05:48.490 [planetinfo.record]: techlevel = 6; 12:05:48.490 [planetinfo.record]: population = 32; 12:05:48.490 [planetinfo.record]: productivity = 10752; 12:05:48.490 [planetinfo.record]: name = "Eraronqu"; 12:05:48.490 [planetinfo.record]: inhabitant = "Human Colonial"; 12:05:48.490 [planetinfo.record]: inhabitants = "Human Colonials"; 12:05:48.490 [planetinfo.record]: description = "This planet is most fabled for mud hockey but ravaged by frequent earthquakes."; 12:05:48.491 [planetinfo.record]: air_color = 0.643207, 0.852462, 0.946969, 1; 12:05:48.491 [planetinfo.record]: cloud_alpha = 1.000000; 12:05:48.492 [planetinfo.record]: cloud_color = 0.553994, 0.84375, 0.537231, 1; 12:05:48.492 [planetinfo.record]: cloud_fraction = 0.480000; 12:05:48.492 [planetinfo.record]: land_color = 0.601562, 0.430023, 0.518473, 1; 12:05:48.492 [planetinfo.record]: land_fraction = 0.610000; 12:05:48.492 [planetinfo.record]: polar_cloud_color = 0.690877, 0.879687, 0.679954, 1; 12:05:48.492 [planetinfo.record]: polar_land_color = 0.939844, 0.872843, 0.90739, 1; 12:05:48.492 [planetinfo.record]: polar_sea_color = 0.934375, 0.866538, 0.863202, 1; 12:05:48.492 [planetinfo.record]: sea_color = 0.65625, 0.465672, 0.456299, 1; 12:05:48.492 [planetinfo.record]: rotation_speed = 0.000315 12:05:48.493 [planetinfo.record]: planet zpos = 310310.000000 12:05:48.493 [planetinfo.record]: sun_radius = 74744.705658 12:05:48.493 [planetinfo.record]: sun_vector = -0.513 0.618 -0.596 12:05:48.493 [planetinfo.record]: sun_distance = 479570 12:05:48.493 [planetinfo.record]: corona_flare = 0.049759 12:05:48.493 [planetinfo.record]: corona_hues = 0.699547 12:05:48.493 [planetinfo.record]: sun_color = 0.792828, 0.556924, 0.485737, 1 12:05:48.493 [planetinfo.record]: corona_shimmer = 0.305429 12:05:48.494 [planetinfo.record]: station_vector = 0.695 -0.714 0.086 12:05:48.494 [planetinfo.record]: station = coriolis 12:05:52.642 [PLANETINFO OVER]: Done 12:05:52.643 [PLANETINFO LOGGING]: 1 160 12:05:53.645 [planetinfo.record]: seed = 16 250 166 67 12:05:53.645 [planetinfo.record]: coordinates = 250 127 12:05:53.645 [planetinfo.record]: government = 2; 12:05:53.645 [planetinfo.record]: economy = 7; 12:05:53.645 [planetinfo.record]: techlevel = 3; 12:05:53.645 [planetinfo.record]: population = 22; 12:05:53.646 [planetinfo.record]: productivity = 3168; 12:05:53.646 [planetinfo.record]: name = "Geisan"; 12:05:53.646 [planetinfo.record]: inhabitant = "Large Yellow Furry Rodent"; 12:05:53.646 [planetinfo.record]: inhabitants = "Large Yellow Furry Rodents"; 12:05:53.646 [planetinfo.record]: description = "The planet Geisan is most famous for its pink oceans and Zero-G hockey."; 12:05:53.673 [planetinfo.record]: air_color = 0.721835, 0.532422, 0.93201, 1; 12:05:53.674 [planetinfo.record]: cloud_alpha = 1.000000; 12:05:53.674 [planetinfo.record]: cloud_color = 0.798828, 0.249634, 0.558556, 1; 12:05:53.674 [planetinfo.record]: cloud_fraction = 0.350000; 12:05:53.674 [planetinfo.record]: land_color = 0.657422, 0.66, 0.659879, 1; 12:05:53.674 [planetinfo.record]: land_fraction = 0.370000; 12:05:53.674 [planetinfo.record]: polar_cloud_color = 0.859473, 0.490168, 0.697902, 1; 12:05:53.674 [planetinfo.record]: polar_land_color = 0.933088, 0.934, 0.933957, 1; 12:05:53.674 [planetinfo.record]: polar_sea_color = 0.9, 0.842895, 0.83925, 1; 12:05:53.674 [planetinfo.record]: sea_color = 1, 0.7462, 0.73, 1; 12:05:53.674 [planetinfo.record]: rotation_speed = 0.000147 12:05:53.674 [planetinfo.record]: planet zpos = 498420.000000 12:05:53.674 [planetinfo.record]: sun_radius = 99963.874512 12:05:53.674 [planetinfo.record]: sun_vector = -0.053 -0.206 -0.977 12:05:53.674 [planetinfo.record]: sun_distance = 690120 12:05:53.675 [planetinfo.record]: corona_flare = 0.012743 12:05:53.675 [planetinfo.record]: corona_hues = 0.869621 12:05:53.675 [planetinfo.record]: sun_color = 0.248168, 0.319122, 0.719452, 1 12:05:53.675 [planetinfo.record]: corona_shimmer = 0.354720 12:05:53.675 [planetinfo.record]: station_vector = 0.812 -0.558 0.168 12:05:53.675 [planetinfo.record]: station = coriolis 12:05:57.508 [PLANETINFO OVER]: Done 12:05:57.511 [PLANETINFO LOGGING]: 1 161 12:05:58.514 [planetinfo.record]: seed = 240 251 58 180 12:05:58.514 [planetinfo.record]: coordinates = 251 251 12:05:58.514 [planetinfo.record]: government = 6; 12:05:58.514 [planetinfo.record]: economy = 3; 12:05:58.514 [planetinfo.record]: techlevel = 10; 12:05:58.514 [planetinfo.record]: population = 50; 12:05:58.514 [planetinfo.record]: productivity = 28000; 12:05:58.514 [planetinfo.record]: name = "Ramaan"; 12:05:58.514 [planetinfo.record]: inhabitant = "Human Colonial"; 12:05:58.514 [planetinfo.record]: inhabitants = "Human Colonials"; 12:05:58.514 [planetinfo.record]: description = "This planet is mildly fabled for the Ramaanian edible moth but beset by evil tree wolfs."; 12:05:58.536 [planetinfo.record]: air_color = 0.797997, 0.739806, 0.990545, 1; 12:05:58.536 [planetinfo.record]: cloud_alpha = 1.000000; 12:05:58.536 [planetinfo.record]: cloud_color = 0.92949, 0.81852, 0.974609, 1; 12:05:58.536 [planetinfo.record]: cloud_fraction = 0.290000; 12:05:58.536 [planetinfo.record]: land_color = 0.568359, 0.50956, 0.501755, 1; 12:05:58.536 [planetinfo.record]: land_fraction = 0.560000; 12:05:58.536 [planetinfo.record]: polar_cloud_color = 0.911417, 0.844625, 0.938574, 1; 12:05:58.536 [planetinfo.record]: polar_land_color = 0.943164, 0.91877, 0.915532, 1; 12:05:58.536 [planetinfo.record]: polar_sea_color = 0.87086, 0.917773, 0.828147, 1; 12:05:58.536 [planetinfo.record]: sea_color = 0.654139, 0.822266, 0.501068, 1; 12:05:58.536 [planetinfo.record]: rotation_speed = 0.001292 12:05:58.536 [planetinfo.record]: planet zpos = 450010.000000 12:05:58.536 [planetinfo.record]: sun_radius = 77732.495728 12:05:58.536 [planetinfo.record]: sun_vector = 0.200 -0.855 -0.479 12:05:58.536 [planetinfo.record]: sun_distance = 736380 12:05:58.536 [planetinfo.record]: corona_flare = 0.095959 12:05:58.536 [planetinfo.record]: corona_hues = 0.691589 12:05:58.536 [planetinfo.record]: sun_color = 0.60665, 0.65022, 0.757562, 1 12:05:58.536 [planetinfo.record]: corona_shimmer = 0.394862 12:05:58.536 [planetinfo.record]: station_vector = 0.345 0.043 0.938 12:05:58.536 [planetinfo.record]: station = coriolis 12:06:02.676 [PLANETINFO OVER]: Done 12:06:02.677 [PLANETINFO LOGGING]: 1 162 12:06:03.694 [planetinfo.record]: seed = 184 186 54 193 12:06:03.694 [planetinfo.record]: coordinates = 186 91 12:06:03.694 [planetinfo.record]: government = 7; 12:06:03.694 [planetinfo.record]: economy = 3; 12:06:03.694 [planetinfo.record]: techlevel = 10; 12:06:03.694 [planetinfo.record]: population = 51; 12:06:03.694 [planetinfo.record]: productivity = 31416; 12:06:03.694 [planetinfo.record]: name = "Letibema"; 12:06:03.694 [planetinfo.record]: inhabitant = "Human Colonial"; 12:06:03.694 [planetinfo.record]: inhabitants = "Human Colonials"; 12:06:03.694 [planetinfo.record]: description = "The world Letibema is reasonably noted for its inhabitants’ eccentric shyness and its inhabitants’ ingrained silliness."; 12:06:03.708 [planetinfo.record]: air_color = 0.651023, 0.879328, 0.848794, 1; 12:06:03.708 [planetinfo.record]: cloud_alpha = 1.000000; 12:06:03.708 [planetinfo.record]: cloud_color = 0.640625, 0.595659, 0.525513, 1; 12:06:03.708 [planetinfo.record]: cloud_fraction = 0.550000; 12:06:03.708 [planetinfo.record]: land_color = 0.478162, 0.278438, 0.66, 1; 12:06:03.708 [planetinfo.record]: land_fraction = 0.510000; 12:06:03.708 [planetinfo.record]: polar_cloud_color = 0.788281, 0.7537, 0.699754, 1; 12:06:03.709 [planetinfo.record]: polar_land_color = 0.869668, 0.799008, 0.934, 1; 12:06:03.715 [planetinfo.record]: polar_sea_color = 0.915648, 0.930078, 0.870495, 1; 12:06:03.715 [planetinfo.record]: sea_color = 0.655825, 0.699219, 0.520044, 1; 12:06:03.716 [planetinfo.record]: rotation_speed = 0.003632 12:06:03.716 [planetinfo.record]: planet zpos = 293220.000000 12:06:03.716 [planetinfo.record]: sun_radius = 86337.795410 12:06:03.716 [planetinfo.record]: sun_vector = 0.400 0.311 0.862 12:06:03.716 [planetinfo.record]: sun_distance = 781920 12:06:03.716 [planetinfo.record]: corona_flare = 0.032080 12:06:03.716 [planetinfo.record]: corona_hues = 0.849030 12:06:03.716 [planetinfo.record]: sun_color = 0.735455, 0.766492, 0.803149, 1 12:06:03.716 [planetinfo.record]: corona_shimmer = 0.475788 12:06:03.716 [planetinfo.record]: station_vector = 0.590 -0.405 0.699 12:06:03.716 [planetinfo.record]: station = coriolis 12:06:08.453 [PLANETINFO OVER]: Done 12:06:08.454 [PLANETINFO LOGGING]: 1 163 12:06:09.459 [planetinfo.record]: seed = 168 235 26 22 12:06:09.459 [planetinfo.record]: coordinates = 235 83 12:06:09.459 [planetinfo.record]: government = 5; 12:06:09.459 [planetinfo.record]: economy = 3; 12:06:09.459 [planetinfo.record]: techlevel = 10; 12:06:09.459 [planetinfo.record]: population = 49; 12:06:09.459 [planetinfo.record]: productivity = 24696; 12:06:09.459 [planetinfo.record]: name = "Verave"; 12:06:09.459 [planetinfo.record]: inhabitant = "Human Colonial"; 12:06:09.459 [planetinfo.record]: inhabitants = "Human Colonials"; 12:06:09.459 [planetinfo.record]: description = "Verave is a revolting dump."; 12:06:09.536 [planetinfo.record]: air_color = 0.596479, 0.913148, 0.831252, 1; 12:06:09.537 [planetinfo.record]: cloud_alpha = 1.000000; 12:06:09.537 [planetinfo.record]: cloud_color = 0.742188, 0.491364, 0.414581, 1; 12:06:09.537 [planetinfo.record]: cloud_fraction = 0.110000; 12:06:09.537 [planetinfo.record]: land_color = 0.625, 0.567932, 0.517578, 1; 12:06:09.537 [planetinfo.record]: land_fraction = 0.250000; 12:06:09.537 [planetinfo.record]: polar_cloud_color = 0.833984, 0.65783, 0.603906, 1; 12:06:09.537 [planetinfo.record]: polar_land_color = 0.9375, 0.9161, 0.897217, 1; 12:06:09.537 [planetinfo.record]: polar_sea_color = 0.785282, 0.944922, 0.870091, 1; 12:06:09.537 [planetinfo.record]: sea_color = 0.178574, 0.550781, 0.376309, 1; 12:06:09.537 [planetinfo.record]: rotation_speed = 0.004655 12:06:09.537 [planetinfo.record]: planet zpos = 550440.000000 12:06:09.538 [planetinfo.record]: sun_radius = 117295.502930 12:06:09.538 [planetinfo.record]: sun_vector = -0.787 0.388 -0.480 12:06:09.538 [planetinfo.record]: sun_distance = 825660 12:06:09.538 [planetinfo.record]: corona_flare = 0.051326 12:06:09.538 [planetinfo.record]: corona_hues = 0.589592 12:06:09.538 [planetinfo.record]: sun_color = 0.407475, 0.628031, 0.779984, 1 12:06:09.538 [planetinfo.record]: corona_shimmer = 0.659661 12:06:09.538 [planetinfo.record]: station_vector = 0.731 0.040 0.682 12:06:09.539 [planetinfo.record]: station = coriolis 12:06:14.472 [PLANETINFO OVER]: Done 12:06:14.473 [PLANETINFO LOGGING]: 1 164 12:06:15.476 [planetinfo.record]: seed = 192 193 102 109 12:06:15.476 [planetinfo.record]: coordinates = 193 86 12:06:15.476 [planetinfo.record]: government = 0; 12:06:15.476 [planetinfo.record]: economy = 6; 12:06:15.476 [planetinfo.record]: techlevel = 2; 12:06:15.476 [planetinfo.record]: population = 15; 12:06:15.476 [planetinfo.record]: productivity = 1920; 12:06:15.477 [planetinfo.record]: name = "Dicela"; 12:06:15.477 [planetinfo.record]: inhabitant = "Human Colonial"; 12:06:15.477 [planetinfo.record]: inhabitants = "Human Colonials"; 12:06:15.477 [planetinfo.record]: description = "Dicela is an unremarkable planet."; 12:06:15.480 [planetinfo.record]: air_color = 0.693461, 0.543801, 0.914449, 1; 12:06:15.481 [planetinfo.record]: cloud_alpha = 1.000000; 12:06:15.481 [planetinfo.record]: cloud_color = 0.746094, 0.285614, 0.641766, 1; 12:06:15.481 [planetinfo.record]: cloud_fraction = 0.350000; 12:06:15.481 [planetinfo.record]: land_color = 0.02314, 0.658203, 0.0926, 1; 12:06:15.481 [planetinfo.record]: land_fraction = 0.700000; 12:06:15.481 [planetinfo.record]: polar_cloud_color = 0.835742, 0.513361, 0.762703, 1; 12:06:15.481 [planetinfo.record]: polar_land_color = 0.708845, 0.93418, 0.733491, 1; 12:06:15.481 [planetinfo.record]: polar_sea_color = 0.891442, 0.92832, 0.870754, 1; 12:06:15.481 [planetinfo.record]: sea_color = 0.602894, 0.716797, 0.538998, 1; 12:06:15.481 [planetinfo.record]: rotation_speed = 0.004532 12:06:15.481 [planetinfo.record]: planet zpos = 823810.000000 12:06:15.482 [planetinfo.record]: sun_radius = 153720.790863 12:06:15.482 [planetinfo.record]: sun_vector = -0.427 0.816 -0.389 12:06:15.482 [planetinfo.record]: sun_distance = 1394140 12:06:15.482 [planetinfo.record]: corona_flare = 0.059612 12:06:15.482 [planetinfo.record]: corona_hues = 0.972618 12:06:15.482 [planetinfo.record]: sun_color = 0.287366, 0.50113, 0.816403, 1 12:06:15.482 [planetinfo.record]: corona_shimmer = 0.402788 12:06:15.482 [planetinfo.record]: station_vector = -0.596 0.679 0.429 12:06:15.482 [planetinfo.record]: station = coriolis 12:06:19.940 [PLANETINFO OVER]: Done 12:06:19.941 [PLANETINFO LOGGING]: 1 165 12:06:20.946 [planetinfo.record]: seed = 64 168 26 227 12:06:20.946 [planetinfo.record]: coordinates = 168 181 12:06:20.946 [planetinfo.record]: government = 0; 12:06:20.946 [planetinfo.record]: economy = 7; 12:06:20.946 [planetinfo.record]: techlevel = 0; 12:06:20.946 [planetinfo.record]: population = 8; 12:06:20.946 [planetinfo.record]: productivity = 768; 12:06:20.946 [planetinfo.record]: name = "Gema"; 12:06:20.946 [planetinfo.record]: inhabitant = "Human Colonial"; 12:06:20.946 [planetinfo.record]: inhabitants = "Human Colonials"; 12:06:20.946 [planetinfo.record]: description = "The world Gema is reasonably noted for mud tennis and the Gemaian evil poet."; 12:06:20.964 [planetinfo.record]: air_color = 0.764542, 0.500233, 0.96583, 1; 12:06:20.964 [planetinfo.record]: cloud_alpha = 1.000000; 12:06:20.964 [planetinfo.record]: cloud_color = 0.900391, 0.137169, 0.351825, 1; 12:06:20.964 [planetinfo.record]: cloud_fraction = 0.360000; 12:06:20.964 [planetinfo.record]: land_color = 0.290726, 0.396823, 0.509766, 1; 12:06:20.964 [planetinfo.record]: land_fraction = 0.670000; 12:06:20.964 [planetinfo.record]: polar_cloud_color = 0.905176, 0.425627, 0.5605, 1; 12:06:20.964 [planetinfo.record]: polar_land_color = 0.847078, 0.896458, 0.949023, 1; 12:06:20.964 [planetinfo.record]: polar_sea_color = 0.861002, 0.919922, 0.854342, 1; 12:06:20.964 [planetinfo.record]: sea_color = 0.595625, 0.800781, 0.572433, 1; 12:06:20.964 [planetinfo.record]: rotation_speed = 0.000676 12:06:20.964 [planetinfo.record]: planet zpos = 375200.000000 12:06:20.964 [planetinfo.record]: sun_radius = 78982.874756 12:06:20.964 [planetinfo.record]: sun_vector = -0.222 -0.705 0.674 12:06:20.964 [planetinfo.record]: sun_distance = 712880 12:06:20.964 [planetinfo.record]: corona_flare = 0.036481 12:06:20.964 [planetinfo.record]: corona_hues = 0.979256 12:06:20.964 [planetinfo.record]: sun_color = 0.669882, 0.657033, 0.645688, 1 12:06:20.964 [planetinfo.record]: corona_shimmer = 0.480659 12:06:20.964 [planetinfo.record]: station_vector = -0.712 -0.372 0.595 12:06:20.964 [planetinfo.record]: station = coriolis 12:06:25.520 [PLANETINFO OVER]: Done 12:06:25.522 [PLANETINFO LOGGING]: 1 166 12:06:26.527 [planetinfo.record]: seed = 40 239 54 251 12:06:26.527 [planetinfo.record]: coordinates = 239 203 12:06:26.527 [planetinfo.record]: government = 5; 12:06:26.527 [planetinfo.record]: economy = 3; 12:06:26.527 [planetinfo.record]: techlevel = 10; 12:06:26.527 [planetinfo.record]: population = 49; 12:06:26.527 [planetinfo.record]: productivity = 24696; 12:06:26.527 [planetinfo.record]: name = "Anve"; 12:06:26.527 [planetinfo.record]: inhabitant = "Human Colonial"; 12:06:26.527 [planetinfo.record]: inhabitants = "Human Colonials"; 12:06:26.527 [planetinfo.record]: description = "This world is a tedious little planet."; 12:06:26.544 [planetinfo.record]: air_color = 0.719531, 0.495952, 0.959977, 1; 12:06:26.544 [planetinfo.record]: cloud_alpha = 1.000000; 12:06:26.544 [planetinfo.record]: cloud_color = 0.882812, 0.131042, 0.536294, 1; 12:06:26.544 [planetinfo.record]: cloud_fraction = 0.430000; 12:06:26.544 [planetinfo.record]: land_color = 0.139618, 0.595703, 0.0744629, 1; 12:06:26.544 [planetinfo.record]: land_fraction = 0.660000; 12:06:26.544 [planetinfo.record]: polar_cloud_color = 0.897266, 0.419717, 0.677146, 1; 12:06:26.544 [planetinfo.record]: polar_land_color = 0.760426, 0.94043, 0.734711, 1; 12:06:26.544 [planetinfo.record]: polar_sea_color = 0.933398, 0.89666, 0.872326, 1; 12:06:26.544 [planetinfo.record]: sea_color = 0.666016, 0.561158, 0.491707, 1; 12:06:26.544 [planetinfo.record]: rotation_speed = 0.003042 12:06:26.544 [planetinfo.record]: planet zpos = 880650.000000 12:06:26.544 [planetinfo.record]: sun_radius = 154757.861481 12:06:26.544 [planetinfo.record]: sun_vector = 0.045 -0.554 0.832 12:06:26.544 [planetinfo.record]: sun_distance = 1232910 12:06:26.544 [planetinfo.record]: corona_flare = 0.013950 12:06:26.544 [planetinfo.record]: corona_hues = 0.710327 12:06:26.544 [planetinfo.record]: sun_color = 0.746976, 0.744866, 0.739804, 1 12:06:26.544 [planetinfo.record]: corona_shimmer = 0.483489 12:06:26.544 [planetinfo.record]: station_vector = -0.057 -0.987 0.150 12:06:26.544 [planetinfo.record]: station = coriolis 12:06:30.711 [PLANETINFO OVER]: Done 12:06:30.711 [PLANETINFO LOGGING]: 1 167 12:06:31.715 [planetinfo.record]: seed = 184 81 58 168 12:06:31.715 [planetinfo.record]: coordinates = 81 160 12:06:31.715 [planetinfo.record]: government = 7; 12:06:31.715 [planetinfo.record]: economy = 0; 12:06:31.715 [planetinfo.record]: techlevel = 12; 12:06:31.715 [planetinfo.record]: population = 56; 12:06:31.715 [planetinfo.record]: productivity = 49280; 12:06:31.715 [planetinfo.record]: name = "Usqurave"; 12:06:31.715 [planetinfo.record]: inhabitant = "Human Colonial"; 12:06:31.715 [planetinfo.record]: inhabitants = "Human Colonials"; 12:06:31.715 [planetinfo.record]: description = "This world is mildly well known for its exotic night life and its inhabitants’ ingrained silliness."; 12:06:31.728 [planetinfo.record]: air_color = 0.454287, 0.657521, 0.879328, 1; 12:06:31.728 [planetinfo.record]: cloud_alpha = 1.000000; 12:06:31.728 [planetinfo.record]: cloud_color = 0.0950928, 0.640625, 0.410479, 1; 12:06:31.728 [planetinfo.record]: cloud_fraction = 0.100000; 12:06:31.728 [planetinfo.record]: land_color = 0.618428, 0.66, 0.53625, 1; 12:06:31.728 [planetinfo.record]: land_fraction = 0.330000; 12:06:31.728 [planetinfo.record]: polar_cloud_color = 0.368737, 0.788281, 0.611286, 1; 12:06:31.728 [planetinfo.record]: polar_land_color = 0.919292, 0.934, 0.890219, 1; 12:06:31.728 [planetinfo.record]: polar_sea_color = 0.871921, 0.89029, 0.918945, 1; 12:06:31.728 [planetinfo.record]: sea_color = 0.644638, 0.709446, 0.810547, 1; 12:06:31.728 [planetinfo.record]: rotation_speed = 0.004073 12:06:31.728 [planetinfo.record]: planet zpos = 494500.000000 12:06:31.728 [planetinfo.record]: sun_radius = 98109.234619 12:06:31.728 [planetinfo.record]: sun_vector = 0.223 0.575 -0.787 12:06:31.728 [planetinfo.record]: sun_distance = 840650 12:06:31.728 [planetinfo.record]: corona_flare = 0.095230 12:06:31.728 [planetinfo.record]: corona_hues = 0.781677 12:06:31.728 [planetinfo.record]: sun_color = 0.834006, 0.812713, 0.335271, 1 12:06:31.728 [planetinfo.record]: corona_shimmer = 0.399790 12:06:31.729 [planetinfo.record]: station_vector = -0.952 -0.291 0.091 12:06:31.729 [planetinfo.record]: station = icosahedron 12:06:37.022 [PLANETINFO OVER]: Done 12:06:37.023 [PLANETINFO LOGGING]: 1 168 12:06:38.034 [planetinfo.record]: seed = 240 214 166 5 12:06:38.034 [planetinfo.record]: coordinates = 214 148 12:06:38.034 [planetinfo.record]: government = 6; 12:06:38.034 [planetinfo.record]: economy = 4; 12:06:38.035 [planetinfo.record]: techlevel = 8; 12:06:38.035 [planetinfo.record]: population = 43; 12:06:38.035 [planetinfo.record]: productivity = 20640; 12:06:38.035 [planetinfo.record]: name = "Ceerdiza"; 12:06:38.035 [planetinfo.record]: inhabitant = "Fierce Green Horned Lobster"; 12:06:38.035 [planetinfo.record]: inhabitants = "Fierce Green Horned Lobsters"; 12:06:38.035 [planetinfo.record]: description = "This world is most notable for its fabulous vicious Geer brew but ravaged by unpredictable civil war."; 12:06:38.040 [planetinfo.record]: air_color = 0.579315, 0.46683, 0.951522, 1; 12:06:38.040 [planetinfo.record]: cloud_alpha = 1.000000; 12:06:38.040 [planetinfo.record]: cloud_color = 0.627, 0.0602875, 0.857422, 1; 12:06:38.040 [planetinfo.record]: cloud_fraction = 0.440000; 12:06:38.041 [planetinfo.record]: land_color = 0.66, 0.51917, 0.185625, 1; 12:06:38.041 [planetinfo.record]: land_fraction = 0.520000; 12:06:38.041 [planetinfo.record]: polar_cloud_color = 0.737053, 0.371118, 0.88584, 1; 12:06:38.041 [planetinfo.record]: polar_land_color = 0.934, 0.884176, 0.766172, 1; 12:06:38.041 [planetinfo.record]: polar_sea_color = 0.933257, 0.937305, 0.885497, 1; 12:06:38.041 [planetinfo.record]: sea_color = 0.616124, 0.626953, 0.488338, 1; 12:06:38.041 [planetinfo.record]: rotation_speed = 0.003862 12:06:38.041 [planetinfo.record]: planet zpos = 431000.000000 12:06:38.041 [planetinfo.record]: sun_radius = 77628.140259 12:06:38.041 [planetinfo.record]: sun_vector = -0.421 0.707 0.568 12:06:38.041 [planetinfo.record]: sun_distance = 948200 12:06:38.041 [planetinfo.record]: corona_flare = 0.055322 12:06:38.041 [planetinfo.record]: corona_hues = 0.558632 12:06:38.042 [planetinfo.record]: sun_color = 0.706415, 0.236562, 0.184866, 1 12:06:38.042 [planetinfo.record]: corona_shimmer = 1.514373 12:06:38.042 [planetinfo.record]: station_vector = -0.457 -0.832 0.315 12:06:38.042 [planetinfo.record]: station = coriolis 12:06:43.441 [PLANETINFO OVER]: Done 12:06:43.444 [PLANETINFO LOGGING]: 1 169 12:06:44.460 [planetinfo.record]: seed = 16 196 122 130 12:06:44.460 [planetinfo.record]: coordinates = 196 77 12:06:44.460 [planetinfo.record]: government = 2; 12:06:44.460 [planetinfo.record]: economy = 5; 12:06:44.460 [planetinfo.record]: techlevel = 3; 12:06:44.460 [planetinfo.record]: population = 20; 12:06:44.460 [planetinfo.record]: productivity = 4800; 12:06:44.460 [planetinfo.record]: name = "Xeraqu"; 12:06:44.460 [planetinfo.record]: inhabitant = "Human Colonial"; 12:06:44.460 [planetinfo.record]: inhabitants = "Human Colonials"; 12:06:44.460 [planetinfo.record]: description = "The planet Xeraqu is mildly notable for Xeraquian lethal water."; 12:06:44.477 [planetinfo.record]: air_color = 0.661867, 0.766209, 0.978188, 1; 12:06:44.478 [planetinfo.record]: cloud_alpha = 1.000000; 12:06:44.478 [planetinfo.record]: cloud_color = 0.5896, 0.929346, 0.9375, 1; 12:06:44.478 [planetinfo.record]: cloud_fraction = 0.110000; 12:06:44.478 [planetinfo.record]: land_color = 0.531899, 0.505313, 0.66, 1; 12:06:44.478 [planetinfo.record]: land_fraction = 0.460000; 12:06:44.478 [planetinfo.record]: polar_cloud_color = 0.708061, 0.916864, 0.921875, 1; 12:06:44.478 [planetinfo.record]: polar_land_color = 0.88868, 0.879273, 0.934, 1; 12:06:44.478 [planetinfo.record]: polar_sea_color = 0.935907, 0.936328, 0.88238, 1; 12:06:44.478 [planetinfo.record]: sea_color = 0.635572, 0.636719, 0.489975, 1; 12:06:44.479 [planetinfo.record]: rotation_speed = 0.000011 12:06:44.479 [planetinfo.record]: planet zpos = 422880.000000 12:06:44.479 [planetinfo.record]: sun_radius = 65018.918457 12:06:44.479 [planetinfo.record]: sun_vector = 0.053 -0.803 -0.594 12:06:44.479 [planetinfo.record]: sun_distance = 740040 12:06:44.479 [planetinfo.record]: corona_flare = 0.097958 12:06:44.479 [planetinfo.record]: corona_hues = 0.831818 12:06:44.479 [planetinfo.record]: sun_color = 0.22468, 0.321037, 0.654181, 1 12:06:44.480 [planetinfo.record]: corona_shimmer = 0.520664 12:06:44.480 [planetinfo.record]: station_vector = -0.971 0.143 0.192 12:06:44.480 [planetinfo.record]: station = coriolis 12:06:49.216 [PLANETINFO OVER]: Done 12:06:49.217 [PLANETINFO LOGGING]: 1 170 12:06:50.232 [planetinfo.record]: seed = 24 241 182 95 12:06:50.232 [planetinfo.record]: coordinates = 241 218 12:06:50.232 [planetinfo.record]: government = 3; 12:06:50.232 [planetinfo.record]: economy = 2; 12:06:50.232 [planetinfo.record]: techlevel = 8; 12:06:50.232 [planetinfo.record]: population = 38; 12:06:50.232 [planetinfo.record]: productivity = 17024; 12:06:50.232 [planetinfo.record]: name = "Onmate"; 12:06:50.232 [planetinfo.record]: inhabitant = "Yellow Bony Feline"; 12:06:50.233 [planetinfo.record]: inhabitants = "Yellow Bony Felines"; 12:06:50.233 [planetinfo.record]: description = "The planet Onmate is most noted for its inhabitants’ exceptional loathing of casinos and the Onmateian spotted wolf."; 12:06:50.234 [planetinfo.record]: air_color = 0.567943, 0.936119, 0.980789, 1; 12:06:50.234 [planetinfo.record]: cloud_alpha = 1.000000; 12:06:50.234 [planetinfo.record]: cloud_color = 0.734429, 0.945312, 0.317566, 1; 12:06:50.235 [planetinfo.record]: cloud_fraction = 0.160000; 12:06:50.235 [planetinfo.record]: land_color = 0.505393, 0.66, 0.399609, 1; 12:06:50.235 [planetinfo.record]: land_fraction = 0.560000; 12:06:50.235 [planetinfo.record]: polar_cloud_color = 0.796366, 0.925391, 0.541317, 1; 12:06:50.235 [planetinfo.record]: polar_land_color = 0.879302, 0.934, 0.841877, 1; 12:06:50.235 [planetinfo.record]: polar_sea_color = 0.898263, 0.870665, 0.947461, 1; 12:06:50.235 [planetinfo.record]: sea_color = 0.416266, 0.355049, 0.525391, 1; 12:06:50.235 [planetinfo.record]: rotation_speed = 0.002346 12:06:50.235 [planetinfo.record]: planet zpos = 689700.000000 12:06:50.235 [planetinfo.record]: sun_radius = 159396.304321 12:06:50.235 [planetinfo.record]: sun_vector = 0.503 -0.858 0.099 12:06:50.235 [planetinfo.record]: sun_distance = 1517340 12:06:50.235 [planetinfo.record]: corona_flare = 0.046129 12:06:50.235 [planetinfo.record]: corona_hues = 0.647385 12:06:50.235 [planetinfo.record]: sun_color = 0.39394, 0.552929, 0.791074, 1 12:06:50.236 [planetinfo.record]: corona_shimmer = 0.272404 12:06:50.236 [planetinfo.record]: station_vector = 0.039 0.986 0.163 12:06:50.236 [planetinfo.record]: station = coriolis 12:06:54.422 [PLANETINFO OVER]: Done 12:06:54.423 [PLANETINFO LOGGING]: 1 171 12:06:55.431 [planetinfo.record]: seed = 72 7 218 174 12:06:55.431 [planetinfo.record]: coordinates = 7 124 12:06:55.431 [planetinfo.record]: government = 1; 12:06:55.431 [planetinfo.record]: economy = 6; 12:06:55.431 [planetinfo.record]: techlevel = 5; 12:06:55.431 [planetinfo.record]: population = 28; 12:06:55.431 [planetinfo.record]: productivity = 4480; 12:06:55.431 [planetinfo.record]: name = "Reenus"; 12:06:55.431 [planetinfo.record]: inhabitant = "Harmless Bony Humanoid"; 12:06:55.431 [planetinfo.record]: inhabitants = "Harmless Bony Humanoids"; 12:06:55.431 [planetinfo.record]: description = "This planet is most notable for Reenusian Thitle brandy but scourged by deadly edible moths."; 12:06:55.452 [planetinfo.record]: air_color = 0.695218, 0.78956, 0.945668, 1; 12:06:55.452 [planetinfo.record]: cloud_alpha = 1.000000; 12:06:55.452 [planetinfo.record]: cloud_color = 0.675812, 0.839844, 0.820621, 1; 12:06:55.452 [planetinfo.record]: cloud_fraction = 0.350000; 12:06:55.452 [planetinfo.record]: land_color = 0.146953, 0.419509, 0.66, 1; 12:06:55.452 [planetinfo.record]: land_fraction = 0.620000; 12:06:55.452 [planetinfo.record]: polar_cloud_color = 0.770761, 0.87793, 0.865371, 1; 12:06:55.452 [planetinfo.record]: polar_land_color = 0.75249, 0.848917, 0.934, 1; 12:06:55.452 [planetinfo.record]: polar_sea_color = 0.932031, 0.90117, 0.868318, 1; 12:06:55.452 [planetinfo.record]: sea_color = 0.679688, 0.589665, 0.493835, 1; 12:06:55.452 [planetinfo.record]: rotation_speed = 0.003440 12:06:55.452 [planetinfo.record]: planet zpos = 961050.000000 12:06:55.452 [planetinfo.record]: sun_radius = 125706.677399 12:06:55.452 [planetinfo.record]: sun_vector = 0.580 0.785 0.216 12:06:55.452 [planetinfo.record]: sun_distance = 1281400 12:06:55.452 [planetinfo.record]: corona_flare = 0.021327 12:06:55.452 [planetinfo.record]: corona_hues = 0.869415 12:06:55.452 [planetinfo.record]: sun_color = 0.547277, 0.70171, 0.772574, 1 12:06:55.453 [planetinfo.record]: corona_shimmer = 0.431067 12:06:55.453 [planetinfo.record]: station_vector = -0.235 -0.902 0.363 12:06:55.453 [planetinfo.record]: station = coriolis 12:06:59.775 [PLANETINFO OVER]: Done 12:06:59.776 [PLANETINFO LOGGING]: 1 172 12:07:00.781 [planetinfo.record]: seed = 160 201 102 228 12:07:00.781 [planetinfo.record]: coordinates = 201 232 12:07:00.781 [planetinfo.record]: government = 4; 12:07:00.781 [planetinfo.record]: economy = 0; 12:07:00.781 [planetinfo.record]: techlevel = 10; 12:07:00.781 [planetinfo.record]: population = 45; 12:07:00.781 [planetinfo.record]: productivity = 28800; 12:07:00.781 [planetinfo.record]: name = "Zavezaon"; 12:07:00.781 [planetinfo.record]: inhabitant = "Human Colonial"; 12:07:00.781 [planetinfo.record]: inhabitants = "Human Colonials"; 12:07:00.781 [planetinfo.record]: description = "This planet is beset by dreadful earthquakes."; 12:07:00.793 [planetinfo.record]: air_color = 0.639781, 0.878027, 0.82385, 1; 12:07:00.794 [planetinfo.record]: cloud_alpha = 1.000000; 12:07:00.794 [planetinfo.record]: cloud_color = 0.636719, 0.54481, 0.499924, 1; 12:07:00.794 [planetinfo.record]: cloud_fraction = 0.220000; 12:07:00.794 [planetinfo.record]: land_color = 0.66, 0.128906, 0.290724, 1; 12:07:00.794 [planetinfo.record]: land_fraction = 0.530000; 12:07:00.794 [planetinfo.record]: polar_cloud_color = 0.786523, 0.715565, 0.680911, 1; 12:07:00.794 [planetinfo.record]: polar_land_color = 0.934, 0.746105, 0.803355, 1; 12:07:00.795 [planetinfo.record]: polar_sea_color = 0.932959, 0.938086, 0.887609, 1; 12:07:00.795 [planetinfo.record]: sea_color = 0.605606, 0.619141, 0.48588, 1; 12:07:00.795 [planetinfo.record]: rotation_speed = 0.003337 12:07:00.796 [planetinfo.record]: planet zpos = 484920.000000 12:07:00.796 [planetinfo.record]: sun_radius = 99349.060364 12:07:00.796 [planetinfo.record]: sun_vector = -0.078 0.903 0.423 12:07:00.796 [planetinfo.record]: sun_distance = 767790 12:07:00.796 [planetinfo.record]: corona_flare = 0.025842 12:07:00.796 [planetinfo.record]: corona_hues = 0.746872 12:07:00.796 [planetinfo.record]: sun_color = 0.752637, 0.67985, 0.574394, 1 12:07:00.796 [planetinfo.record]: corona_shimmer = 0.352598 12:07:00.796 [planetinfo.record]: station_vector = 0.555 0.024 0.831 12:07:00.796 [planetinfo.record]: station = coriolis 12:07:05.624 [PLANETINFO OVER]: Done 12:07:05.625 [PLANETINFO LOGGING]: 1 173 12:07:06.646 [planetinfo.record]: seed = 96 191 90 154 12:07:06.646 [planetinfo.record]: coordinates = 191 68 12:07:06.646 [planetinfo.record]: government = 4; 12:07:06.646 [planetinfo.record]: economy = 4; 12:07:06.646 [planetinfo.record]: techlevel = 8; 12:07:06.646 [planetinfo.record]: population = 41; 12:07:06.646 [planetinfo.record]: productivity = 15744; 12:07:06.646 [planetinfo.record]: name = "Quriti"; 12:07:06.646 [planetinfo.record]: inhabitant = "Human Colonial"; 12:07:06.646 [planetinfo.record]: inhabitants = "Human Colonials"; 12:07:06.646 [planetinfo.record]: description = "The planet Quriti is most well known for its great parking meters."; 12:07:06.674 [planetinfo.record]: air_color = 0.6098, 0.858041, 0.974285, 1; 12:07:06.674 [planetinfo.record]: cloud_alpha = 1.000000; 12:07:06.674 [planetinfo.record]: cloud_color = 0.456336, 0.925781, 0.441193, 1; 12:07:06.674 [planetinfo.record]: cloud_fraction = 0.490000; 12:07:06.674 [planetinfo.record]: land_color = 0.564609, 0.66, 0.595164, 1; 12:07:06.675 [planetinfo.record]: land_fraction = 0.660000; 12:07:06.675 [planetinfo.record]: polar_cloud_color = 0.626108, 0.916602, 0.616737, 1; 12:07:06.675 [planetinfo.record]: polar_land_color = 0.900252, 0.934, 0.911062, 1; 12:07:06.675 [planetinfo.record]: polar_sea_color = 0.941992, 0.929665, 0.892685, 1; 12:07:06.675 [planetinfo.record]: sea_color = 0.580078, 0.549715, 0.458624, 1; 12:07:06.675 [planetinfo.record]: rotation_speed = 0.004396 12:07:06.675 [planetinfo.record]: planet zpos = 612370.000000 12:07:06.675 [planetinfo.record]: sun_radius = 168070.122070 12:07:06.676 [planetinfo.record]: sun_vector = 0.494 -0.482 -0.724 12:07:06.676 [planetinfo.record]: sun_distance = 890720 12:07:06.676 [planetinfo.record]: corona_flare = 0.067436 12:07:06.676 [planetinfo.record]: corona_hues = 0.671371 12:07:06.676 [planetinfo.record]: sun_color = 0.721242, 0.828828, 0.843747, 1 12:07:06.676 [planetinfo.record]: corona_shimmer = 0.413847 12:07:06.676 [planetinfo.record]: station_vector = -0.510 -0.835 0.208 12:07:06.683 [planetinfo.record]: station = coriolis 12:07:11.856 [PLANETINFO OVER]: Done 12:07:11.857 [PLANETINFO LOGGING]: 1 174 12:07:12.872 [planetinfo.record]: seed = 136 48 182 198 12:07:12.872 [planetinfo.record]: coordinates = 48 247 12:07:12.872 [planetinfo.record]: government = 1; 12:07:12.872 [planetinfo.record]: economy = 7; 12:07:12.872 [planetinfo.record]: techlevel = 1; 12:07:12.872 [planetinfo.record]: population = 13; 12:07:12.872 [planetinfo.record]: productivity = 1560; 12:07:12.872 [planetinfo.record]: name = "Biabite"; 12:07:12.872 [planetinfo.record]: inhabitant = "Fierce Frog"; 12:07:12.872 [planetinfo.record]: inhabitants = "Fierce Frogs"; 12:07:12.872 [planetinfo.record]: description = "This world is very notable for the Biabiteian tree snake."; 12:07:12.893 [planetinfo.record]: air_color = 0.651665, 0.889611, 0.907295, 1; 12:07:12.893 [planetinfo.record]: cloud_alpha = 1.000000; 12:07:12.893 [planetinfo.record]: cloud_color = 0.685601, 0.724609, 0.546288, 1; 12:07:12.893 [planetinfo.record]: cloud_fraction = 0.250000; 12:07:12.893 [planetinfo.record]: land_color = 0.66, 0.486299, 0.154688, 1; 12:07:12.893 [planetinfo.record]: land_fraction = 0.300000; 12:07:12.893 [planetinfo.record]: polar_cloud_color = 0.79828, 0.826074, 0.699017, 1; 12:07:12.893 [planetinfo.record]: polar_land_color = 0.934, 0.872547, 0.755227, 1; 12:07:12.893 [planetinfo.record]: polar_sea_color = 0.946289, 0.886222, 0.925641, 1; 12:07:12.893 [planetinfo.record]: sea_color = 0.537109, 0.400734, 0.49023, 1; 12:07:12.894 [planetinfo.record]: rotation_speed = 0.001738 12:07:12.894 [planetinfo.record]: planet zpos = 484000.000000 12:07:12.894 [planetinfo.record]: sun_radius = 106431.579590 12:07:12.894 [planetinfo.record]: sun_vector = 0.046 -0.998 0.049 12:07:12.894 [planetinfo.record]: sun_distance = 836000 12:07:12.894 [planetinfo.record]: corona_flare = 0.037737 12:07:12.894 [planetinfo.record]: corona_hues = 0.626785 12:07:12.894 [planetinfo.record]: sun_color = 0.482181, 0.785219, 0.799814, 1 12:07:12.894 [planetinfo.record]: corona_shimmer = 0.623025 12:07:12.894 [planetinfo.record]: station_vector = 0.728 -0.685 0.031 12:07:12.895 [planetinfo.record]: station = coriolis 12:07:17.909 [PLANETINFO OVER]: Done 12:07:17.910 [PLANETINFO LOGGING]: 1 175 12:07:18.914 [planetinfo.record]: seed = 88 156 250 113 12:07:18.914 [planetinfo.record]: coordinates = 156 230 12:07:18.914 [planetinfo.record]: government = 3; 12:07:18.914 [planetinfo.record]: economy = 6; 12:07:18.914 [planetinfo.record]: techlevel = 3; 12:07:18.914 [planetinfo.record]: population = 22; 12:07:18.914 [planetinfo.record]: productivity = 4928; 12:07:18.914 [planetinfo.record]: name = "Atragees"; 12:07:18.914 [planetinfo.record]: inhabitant = "Blue Horned Lobster"; 12:07:18.914 [planetinfo.record]: inhabitants = "Blue Horned Lobsters"; 12:07:18.915 [planetinfo.record]: description = "Atragees is reasonably notable for its great tropical forests but cursed by dreadful solar activity."; 12:07:18.923 [planetinfo.record]: air_color = 0.729965, 0.756283, 0.950871, 1; 12:07:18.923 [planetinfo.record]: cloud_alpha = 1.000000; 12:07:18.923 [planetinfo.record]: cloud_color = 0.771927, 0.800644, 0.855469, 1; 12:07:18.923 [planetinfo.record]: cloud_fraction = 0.630000; 12:07:18.924 [planetinfo.record]: land_color = 0.65146, 0.66, 0.386719, 1; 12:07:18.924 [planetinfo.record]: land_fraction = 0.380000; 12:07:18.924 [planetinfo.record]: polar_cloud_color = 0.830947, 0.849514, 0.884961, 1; 12:07:18.924 [planetinfo.record]: polar_land_color = 0.930979, 0.934, 0.837316, 1; 12:07:18.924 [planetinfo.record]: polar_sea_color = 0.925391, 0.897312, 0.837732, 1; 12:07:18.924 [planetinfo.record]: sea_color = 0.746094, 0.655542, 0.463394, 1; 12:07:18.924 [planetinfo.record]: rotation_speed = 0.002810 12:07:18.925 [planetinfo.record]: planet zpos = 451920.000000 12:07:18.925 [planetinfo.record]: sun_radius = 108169.720459 12:07:18.925 [planetinfo.record]: sun_vector = -0.017 -0.312 -0.950 12:07:18.925 [planetinfo.record]: sun_distance = 613320 12:07:18.925 [planetinfo.record]: corona_flare = 0.007355 12:07:18.925 [planetinfo.record]: corona_hues = 0.829887 12:07:18.925 [planetinfo.record]: sun_color = 0.739807, 0.694485, 0.691438, 1 12:07:18.925 [planetinfo.record]: corona_shimmer = 0.527614 12:07:18.926 [planetinfo.record]: station_vector = -0.639 -0.438 0.633 12:07:18.926 [planetinfo.record]: station = coriolis 12:07:23.182 [PLANETINFO OVER]: Done 12:07:23.183 [PLANETINFO LOGGING]: 1 176 12:07:24.204 [planetinfo.record]: seed = 208 105 166 97 12:07:24.204 [planetinfo.record]: coordinates = 105 3 12:07:24.204 [planetinfo.record]: government = 2; 12:07:24.204 [planetinfo.record]: economy = 3; 12:07:24.204 [planetinfo.record]: techlevel = 6; 12:07:24.204 [planetinfo.record]: population = 30; 12:07:24.204 [planetinfo.record]: productivity = 10080; 12:07:24.204 [planetinfo.record]: name = "Lerequ"; 12:07:24.204 [planetinfo.record]: inhabitant = "Large Blue Horned Lobster"; 12:07:24.204 [planetinfo.record]: inhabitants = "Large Blue Horned Lobsters"; 12:07:24.204 [planetinfo.record]: description = "This planet is very notable for the Lerequian edible poet and Zero-G cricket."; 12:07:24.210 [planetinfo.record]: air_color = 0.440219, 0.683163, 0.883231, 1; 12:07:24.211 [planetinfo.record]: cloud_alpha = 1.000000; 12:07:24.211 [planetinfo.record]: cloud_color = 0.0611572, 0.652344, 0.278234, 1; 12:07:24.211 [planetinfo.record]: cloud_fraction = 0.400000; 12:07:24.211 [planetinfo.record]: land_color = 0.66, 0.159562, 0.0257813, 1; 12:07:24.211 [planetinfo.record]: land_fraction = 0.350000; 12:07:24.211 [planetinfo.record]: polar_cloud_color = 0.34408, 0.793555, 0.509122, 1; 12:07:24.211 [planetinfo.record]: polar_land_color = 0.934, 0.756951, 0.709621, 1; 12:07:24.211 [planetinfo.record]: polar_sea_color = 0.891792, 0.933594, 0.88582, 1; 12:07:24.211 [planetinfo.record]: sea_color = 0.545128, 0.664062, 0.528137, 1; 12:07:24.211 [planetinfo.record]: rotation_speed = 0.002638 12:07:24.211 [planetinfo.record]: planet zpos = 476550.000000 12:07:24.211 [planetinfo.record]: sun_radius = 61218.912964 12:07:24.211 [planetinfo.record]: sun_vector = 0.310 -0.727 -0.613 12:07:24.211 [planetinfo.record]: sun_distance = 635400 12:07:24.211 [planetinfo.record]: corona_flare = 0.002316 12:07:24.211 [planetinfo.record]: corona_hues = 0.993668 12:07:24.211 [planetinfo.record]: sun_color = 0.670023, 0.598664, 0.288327, 1 12:07:24.211 [planetinfo.record]: corona_shimmer = 0.370381 12:07:24.212 [planetinfo.record]: station_vector = 0.920 0.373 0.121 12:07:24.212 [planetinfo.record]: station = coriolis 12:07:28.601 [PLANETINFO OVER]: Done 12:07:28.602 [PLANETINFO LOGGING]: 1 177 12:07:29.607 [planetinfo.record]: seed = 48 202 186 50 12:07:29.607 [planetinfo.record]: coordinates = 202 154 12:07:29.607 [planetinfo.record]: government = 6; 12:07:29.607 [planetinfo.record]: economy = 2; 12:07:29.607 [planetinfo.record]: techlevel = 10; 12:07:29.607 [planetinfo.record]: population = 49; 12:07:29.607 [planetinfo.record]: productivity = 31360; 12:07:29.607 [planetinfo.record]: name = "Envebe"; 12:07:29.607 [planetinfo.record]: inhabitant = "Red Slimy Lizard"; 12:07:29.607 [planetinfo.record]: inhabitants = "Red Slimy Lizards"; 12:07:29.607 [planetinfo.record]: description = "This world is reasonably well known for the Envebeian spotted shrew but plagued by evil tree wolfs."; 12:07:29.628 [planetinfo.record]: air_color = 0.582105, 0.928107, 0.846726, 1; 12:07:29.629 [planetinfo.record]: cloud_alpha = 1.000000; 12:07:29.629 [planetinfo.record]: cloud_color = 0.787109, 0.502777, 0.378181, 1; 12:07:29.630 [planetinfo.record]: cloud_fraction = 0.380000; 12:07:29.630 [planetinfo.record]: land_color = 0.66, 0.0128906, 0.28589, 1; 12:07:29.630 [planetinfo.record]: land_fraction = 0.370000; 12:07:29.630 [planetinfo.record]: polar_cloud_color = 0.854199, 0.661344, 0.576835, 1; 12:07:29.630 [planetinfo.record]: polar_land_color = 0.934, 0.705061, 0.801644, 1; 12:07:29.630 [planetinfo.record]: polar_sea_color = 0.941797, 0.939947, 0.894431, 1; 12:07:29.630 [planetinfo.record]: sea_color = 0.582031, 0.577457, 0.464943, 1; 12:07:29.630 [planetinfo.record]: rotation_speed = 0.003725 12:07:29.631 [planetinfo.record]: planet zpos = 458900.000000 12:07:29.631 [planetinfo.record]: sun_radius = 62645.973206 12:07:29.631 [planetinfo.record]: sun_vector = -0.588 0.217 -0.779 12:07:29.631 [planetinfo.record]: sun_distance = 741300 12:07:29.631 [planetinfo.record]: corona_flare = 0.004813 12:07:29.631 [planetinfo.record]: corona_hues = 0.595078 12:07:29.631 [planetinfo.record]: sun_color = 0.793466, 0.411702, 0.410087, 1 12:07:29.631 [planetinfo.record]: corona_shimmer = 0.445441 12:07:29.631 [planetinfo.record]: station_vector = 0.519 -0.854 0.040 12:07:29.631 [planetinfo.record]: station = coriolis 12:07:34.214 [PLANETINFO OVER]: Done 12:07:34.215 [PLANETINFO LOGGING]: 1 178 12:07:35.234 [planetinfo.record]: seed = 120 93 54 136 12:07:35.234 [planetinfo.record]: coordinates = 93 147 12:07:35.234 [planetinfo.record]: government = 7; 12:07:35.234 [planetinfo.record]: economy = 3; 12:07:35.234 [planetinfo.record]: techlevel = 9; 12:07:35.234 [planetinfo.record]: population = 47; 12:07:35.234 [planetinfo.record]: productivity = 28952; 12:07:35.234 [planetinfo.record]: name = "Usoron"; 12:07:35.234 [planetinfo.record]: inhabitant = "Human Colonial"; 12:07:35.234 [planetinfo.record]: inhabitants = "Human Colonials"; 12:07:35.234 [planetinfo.record]: description = "This world is very well known for Usoronian lethal brandy and its exotic night life."; 12:07:35.265 [planetinfo.record]: air_color = 0.699489, 0.796193, 0.936563, 1; 12:07:35.265 [planetinfo.record]: cloud_alpha = 1.000000; 12:07:35.265 [planetinfo.record]: cloud_color = 0.682373, 0.8125, 0.785051, 1; 12:07:35.265 [planetinfo.record]: cloud_fraction = 0.300000; 12:07:35.265 [planetinfo.record]: land_color = 0.3897, 0.0928125, 0.66, 1; 12:07:35.265 [planetinfo.record]: land_fraction = 0.620000; 12:07:35.265 [planetinfo.record]: polar_cloud_color = 0.778978, 0.865625, 0.847348, 1; 12:07:35.265 [planetinfo.record]: polar_land_color = 0.838371, 0.733336, 0.934, 1; 12:07:35.265 [planetinfo.record]: polar_sea_color = 0.939648, 0.934088, 0.888812, 1; 12:07:35.265 [planetinfo.record]: sea_color = 0.603516, 0.589231, 0.472911, 1; 12:07:35.265 [planetinfo.record]: rotation_speed = 0.001091 12:07:35.265 [planetinfo.record]: planet zpos = 446130.000000 12:07:35.265 [planetinfo.record]: sun_radius = 99135.461731 12:07:35.265 [planetinfo.record]: sun_vector = -0.104 0.994 0.021 12:07:35.265 [planetinfo.record]: sun_distance = 1090540 12:07:35.265 [planetinfo.record]: corona_flare = 0.005922 12:07:35.265 [planetinfo.record]: corona_hues = 0.578293 12:07:35.265 [planetinfo.record]: sun_color = 0.366502, 0.586675, 0.827844, 1 12:07:35.265 [planetinfo.record]: corona_shimmer = 0.255073 12:07:35.265 [planetinfo.record]: station_vector = 0.049 -0.995 0.091 12:07:35.265 [planetinfo.record]: station = coriolis 12:07:40.568 [PLANETINFO OVER]: Done 12:07:40.569 [PLANETINFO LOGGING]: 1 179 12:07:41.584 [planetinfo.record]: seed = 232 96 154 57 12:07:41.584 [planetinfo.record]: coordinates = 96 95 12:07:41.584 [planetinfo.record]: government = 5; 12:07:41.584 [planetinfo.record]: economy = 7; 12:07:41.584 [planetinfo.record]: techlevel = 3; 12:07:41.584 [planetinfo.record]: population = 25; 12:07:41.585 [planetinfo.record]: productivity = 5400; 12:07:41.585 [planetinfo.record]: name = "Ororra"; 12:07:41.585 [planetinfo.record]: inhabitant = "Red Rodent"; 12:07:41.585 [planetinfo.record]: inhabitants = "Red Rodents"; 12:07:41.585 [planetinfo.record]: description = "This planet is a dull place."; 12:07:41.596 [planetinfo.record]: air_color = 0.588884, 0.579534, 0.8325, 1; 12:07:41.596 [planetinfo.record]: cloud_alpha = 1.000000; 12:07:41.596 [planetinfo.record]: cloud_color = 0.363281, 0.34375, 0.5, 1; 12:07:41.596 [planetinfo.record]: cloud_fraction = 0.230000; 12:07:41.596 [planetinfo.record]: land_color = 0.462048, 0.172734, 0.66, 1; 12:07:41.596 [planetinfo.record]: land_fraction = 0.340000; 12:07:41.596 [planetinfo.record]: polar_cloud_color = 0.601099, 0.583398, 0.725, 1; 12:07:41.603 [planetinfo.record]: polar_land_color = 0.863967, 0.761611, 0.934, 1; 12:07:41.603 [planetinfo.record]: polar_sea_color = 0.947656, 0.90154, 0.898052, 1; 12:07:41.604 [planetinfo.record]: sea_color = 0.523438, 0.421549, 0.413843, 1; 12:07:41.604 [planetinfo.record]: rotation_speed = 0.002149 12:07:41.604 [planetinfo.record]: planet zpos = 625920.000000 12:07:41.604 [planetinfo.record]: sun_radius = 123521.845703 12:07:41.604 [planetinfo.record]: sun_vector = 0.833 0.376 -0.405 12:07:41.604 [planetinfo.record]: sun_distance = 1251840 12:07:41.604 [planetinfo.record]: corona_flare = 0.022072 12:07:41.604 [planetinfo.record]: corona_hues = 0.648209 12:07:41.604 [planetinfo.record]: sun_color = 0.421421, 0.738286, 0.779181, 1 12:07:41.604 [planetinfo.record]: corona_shimmer = 0.289388 12:07:41.604 [planetinfo.record]: station_vector = 0.329 0.935 0.133 12:07:41.604 [planetinfo.record]: station = coriolis 12:07:46.787 [PLANETINFO OVER]: Done 12:07:46.787 [PLANETINFO LOGGING]: 1 180 12:07:47.807 [planetinfo.record]: seed = 128 199 102 85 12:07:47.807 [planetinfo.record]: coordinates = 199 148 12:07:47.807 [planetinfo.record]: government = 0; 12:07:47.807 [planetinfo.record]: economy = 6; 12:07:47.807 [planetinfo.record]: techlevel = 4; 12:07:47.807 [planetinfo.record]: population = 23; 12:07:47.807 [planetinfo.record]: productivity = 2944; 12:07:47.807 [planetinfo.record]: name = "Laatre"; 12:07:47.807 [planetinfo.record]: inhabitant = "Human Colonial"; 12:07:47.808 [planetinfo.record]: inhabitants = "Human Colonials"; 12:07:47.808 [planetinfo.record]: description = "Laatre is an unremarkable dump."; 12:07:47.811 [planetinfo.record]: air_color = 0.515538, 0.610771, 0.852012, 1; 12:07:47.812 [planetinfo.record]: cloud_alpha = 1.000000; 12:07:47.812 [planetinfo.record]: cloud_color = 0.231293, 0.504896, 0.558594, 1; 12:07:47.812 [planetinfo.record]: cloud_fraction = 0.550000; 12:07:47.812 [planetinfo.record]: land_color = 0.539312, 0.108281, 0.66, 1; 12:07:47.812 [planetinfo.record]: land_fraction = 0.520000; 12:07:47.812 [planetinfo.record]: polar_cloud_color = 0.476208, 0.706224, 0.751367, 1; 12:07:47.812 [planetinfo.record]: polar_land_color = 0.891302, 0.738809, 0.934, 1; 12:07:47.812 [planetinfo.record]: polar_sea_color = 0.927298, 0.938867, 0.891099, 1; 12:07:47.812 [planetinfo.record]: sea_color = 0.581196, 0.611328, 0.486913, 1; 12:07:47.812 [planetinfo.record]: rotation_speed = 0.002022 12:07:47.812 [planetinfo.record]: planet zpos = 601300.000000 12:07:47.812 [planetinfo.record]: sun_radius = 115514.633179 12:07:47.812 [planetinfo.record]: sun_vector = 0.271 0.959 0.087 12:07:47.813 [planetinfo.record]: sun_distance = 987850 12:07:47.813 [planetinfo.record]: corona_flare = 0.057600 12:07:47.813 [planetinfo.record]: corona_hues = 0.560814 12:07:47.813 [planetinfo.record]: sun_color = 0.228697, 0.492821, 0.765451, 1 12:07:47.813 [planetinfo.record]: corona_shimmer = 0.461476 12:07:47.813 [planetinfo.record]: station_vector = 0.186 0.684 0.705 12:07:47.813 [planetinfo.record]: station = coriolis 12:07:53.270 [PLANETINFO OVER]: Done 12:07:53.272 [PLANETINFO LOGGING]: 1 181 12:07:54.280 [planetinfo.record]: seed = 128 212 154 83 12:07:54.280 [planetinfo.record]: coordinates = 212 206 12:07:54.280 [planetinfo.record]: government = 0; 12:07:54.280 [planetinfo.record]: economy = 6; 12:07:54.280 [planetinfo.record]: techlevel = 1; 12:07:54.281 [planetinfo.record]: population = 11; 12:07:54.281 [planetinfo.record]: productivity = 1408; 12:07:54.281 [planetinfo.record]: name = "Beveri"; 12:07:54.281 [planetinfo.record]: inhabitant = "Yellow Horned Humanoid"; 12:07:54.281 [planetinfo.record]: inhabitants = "Yellow Horned Humanoids"; 12:07:54.281 [planetinfo.record]: description = "Beveri is an unremarkable planet."; 12:07:54.289 [planetinfo.record]: air_color = 0.744407, 0.772809, 0.933311, 1; 12:07:54.290 [planetinfo.record]: cloud_alpha = 1.000000; 12:07:54.290 [planetinfo.record]: cloud_color = 0.796463, 0.799207, 0.802734, 1; 12:07:54.290 [planetinfo.record]: cloud_fraction = 0.440000; 12:07:54.290 [planetinfo.record]: land_color = 0.621094, 0.427002, 0.513433, 1; 12:07:54.290 [planetinfo.record]: land_fraction = 0.630000; 12:07:54.290 [planetinfo.record]: polar_cloud_color = 0.857025, 0.858865, 0.86123, 1; 12:07:54.290 [planetinfo.record]: polar_land_color = 0.937891, 0.864618, 0.897247, 1; 12:07:54.290 [planetinfo.record]: polar_sea_color = 0.924605, 0.946094, 0.734516, 1; 12:07:54.290 [planetinfo.record]: sea_color = 0.490088, 0.539062, 0.0568542, 1; 12:07:54.290 [planetinfo.record]: rotation_speed = 0.003117 12:07:54.290 [planetinfo.record]: planet zpos = 455520.000000 12:07:54.290 [planetinfo.record]: sun_radius = 111296.083374 12:07:54.290 [planetinfo.record]: sun_vector = -0.143 0.604 0.784 12:07:54.290 [planetinfo.record]: sun_distance = 911040 12:07:54.290 [planetinfo.record]: corona_flare = 0.024232 12:07:54.290 [planetinfo.record]: corona_hues = 0.854393 12:07:54.290 [planetinfo.record]: sun_color = 0.779102, 0.758542, 0.757961, 1 12:07:54.291 [planetinfo.record]: corona_shimmer = 0.324144 12:07:54.291 [planetinfo.record]: station_vector = -0.782 0.079 0.618 12:07:54.291 [planetinfo.record]: station = coriolis 12:07:59.285 [PLANETINFO OVER]: Done 12:07:59.286 [PLANETINFO LOGGING]: 1 182 12:08:00.294 [planetinfo.record]: seed = 232 103 54 124 12:08:00.294 [planetinfo.record]: coordinates = 103 30 12:08:00.294 [planetinfo.record]: government = 5; 12:08:00.294 [planetinfo.record]: economy = 6; 12:08:00.294 [planetinfo.record]: techlevel = 7; 12:08:00.294 [planetinfo.record]: population = 40; 12:08:00.294 [planetinfo.record]: productivity = 11520; 12:08:00.294 [planetinfo.record]: name = "Texebi"; 12:08:00.294 [planetinfo.record]: inhabitant = "Human Colonial"; 12:08:00.294 [planetinfo.record]: inhabitants = "Human Colonials"; 12:08:00.294 [planetinfo.record]: description = "This world is very well known for vicious Numaoudi brew and its exotic fish cutlet."; 12:08:00.303 [planetinfo.record]: air_color = 0.660877, 0.581919, 0.86502, 1; 12:08:00.304 [planetinfo.record]: cloud_alpha = 1.000000; 12:08:00.304 [planetinfo.record]: cloud_color = 0.563695, 0.368866, 0.597656, 1; 12:08:00.304 [planetinfo.record]: cloud_fraction = 0.090000; 12:08:00.304 [planetinfo.record]: land_color = 0.389478, 0.66, 0.373828, 1; 12:08:00.304 [planetinfo.record]: land_fraction = 0.600000; 12:08:00.304 [planetinfo.record]: polar_cloud_color = 0.741636, 0.584969, 0.768945, 1; 12:08:00.304 [planetinfo.record]: polar_land_color = 0.838293, 0.934, 0.832756, 1; 12:08:00.304 [planetinfo.record]: polar_sea_color = 0.931445, 0.910496, 0.870501, 1; 12:08:00.304 [planetinfo.record]: sea_color = 0.685547, 0.623871, 0.506126, 1; 12:08:00.304 [planetinfo.record]: rotation_speed = 0.000462 12:08:00.304 [planetinfo.record]: planet zpos = 659010.000000 12:08:00.305 [planetinfo.record]: sun_radius = 183621.553802 12:08:00.305 [planetinfo.record]: sun_vector = -0.053 -0.934 0.352 12:08:00.305 [planetinfo.record]: sun_distance = 1198200 12:08:00.305 [planetinfo.record]: corona_flare = 0.053978 12:08:00.305 [planetinfo.record]: corona_hues = 0.514458 12:08:00.305 [planetinfo.record]: sun_color = 0.336129, 0.370639, 0.770493, 1 12:08:00.305 [planetinfo.record]: corona_shimmer = 0.333147 12:08:00.305 [planetinfo.record]: station_vector = -0.236 0.899 0.369 12:08:00.305 [planetinfo.record]: station = coriolis 12:08:04.663 [PLANETINFO OVER]: Done 12:08:04.664 [PLANETINFO LOGGING]: 1 183 12:08:05.667 [planetinfo.record]: seed = 248 100 186 77 12:08:05.667 [planetinfo.record]: coordinates = 100 230 12:08:05.667 [planetinfo.record]: government = 7; 12:08:05.667 [planetinfo.record]: economy = 6; 12:08:05.667 [planetinfo.record]: techlevel = 5; 12:08:05.667 [planetinfo.record]: population = 34; 12:08:05.667 [planetinfo.record]: productivity = 11968; 12:08:05.667 [planetinfo.record]: name = "Diormaen"; 12:08:05.667 [planetinfo.record]: inhabitant = "Yellow Horned Lobster"; 12:08:05.668 [planetinfo.record]: inhabitants = "Yellow Horned Lobsters"; 12:08:05.668 [planetinfo.record]: description = "This planet is reasonably noted for its exotic goat meat."; 12:08:05.688 [planetinfo.record]: air_color = 0.462257, 0.443656, 0.946969, 1; 12:08:05.688 [planetinfo.record]: cloud_alpha = 1.000000; 12:08:05.688 [planetinfo.record]: cloud_color = 0.108353, 0.0032959, 0.84375, 1; 12:08:05.688 [planetinfo.record]: cloud_fraction = 0.320000; 12:08:05.688 [planetinfo.record]: land_color = 0.66, 0.626102, 0.404766, 1; 12:08:05.688 [planetinfo.record]: land_fraction = 0.580000; 12:08:05.688 [planetinfo.record]: polar_cloud_color = 0.400488, 0.33203, 0.879687, 1; 12:08:05.688 [planetinfo.record]: polar_land_color = 0.934, 0.922007, 0.843701, 1; 12:08:05.688 [planetinfo.record]: polar_sea_color = 0.947266, 0.884361, 0.946283, 1; 12:08:05.690 [planetinfo.record]: sea_color = 0.527344, 0.387268, 0.525155, 1; 12:08:05.690 [planetinfo.record]: rotation_speed = 0.001534 12:08:05.690 [planetinfo.record]: planet zpos = 686840.000000 12:08:05.690 [planetinfo.record]: sun_radius = 174104.282837 12:08:05.690 [planetinfo.record]: sun_vector = -0.879 -0.178 -0.443 12:08:05.690 [planetinfo.record]: sun_distance = 1311240 12:08:05.690 [planetinfo.record]: corona_flare = 0.013361 12:08:05.690 [planetinfo.record]: corona_hues = 0.506836 12:08:05.690 [planetinfo.record]: sun_color = 0.415552, 0.467123, 0.652448, 1 12:08:05.690 [planetinfo.record]: corona_shimmer = 0.253409 12:08:05.691 [planetinfo.record]: station_vector = -0.691 -0.161 0.705 12:08:05.692 [planetinfo.record]: station = coriolis 12:08:11.417 [PLANETINFO OVER]: Done 12:08:11.418 [PLANETINFO LOGGING]: 1 184 12:08:12.429 [planetinfo.record]: seed = 176 50 166 23 12:08:12.429 [planetinfo.record]: coordinates = 50 75 12:08:12.429 [planetinfo.record]: government = 6; 12:08:12.429 [planetinfo.record]: economy = 3; 12:08:12.429 [planetinfo.record]: techlevel = 9; 12:08:12.429 [planetinfo.record]: population = 46; 12:08:12.429 [planetinfo.record]: productivity = 25760; 12:08:12.429 [planetinfo.record]: name = "Tivere"; 12:08:12.430 [planetinfo.record]: inhabitant = "Green Bug-Eyed Bird"; 12:08:12.430 [planetinfo.record]: inhabitants = "Green Bug-Eyed Birds"; 12:08:12.430 [planetinfo.record]: description = "The planet Tivere is cursed by killer mountain goats."; 12:08:12.444 [planetinfo.record]: air_color = 0.733351, 0.760303, 0.945668, 1; 12:08:12.444 [planetinfo.record]: cloud_alpha = 1.000000; 12:08:12.444 [planetinfo.record]: cloud_color = 0.777512, 0.800399, 0.839844, 1; 12:08:12.444 [planetinfo.record]: cloud_fraction = 0.490000; 12:08:12.444 [planetinfo.record]: land_color = 0.65625, 0.644474, 0.548584, 1; 12:08:12.444 [planetinfo.record]: land_fraction = 0.500000; 12:08:12.444 [planetinfo.record]: polar_cloud_color = 0.837205, 0.852159, 0.87793, 1; 12:08:12.444 [planetinfo.record]: polar_land_color = 0.934375, 0.930183, 0.896051, 1; 12:08:12.444 [planetinfo.record]: polar_sea_color = 0.749638, 0.936133, 0.914278, 1; 12:08:12.444 [planetinfo.record]: sea_color = 0.12973, 0.638672, 0.57903, 1; 12:08:12.444 [planetinfo.record]: rotation_speed = 0.001346 12:08:12.444 [planetinfo.record]: planet zpos = 465800.000000 12:08:12.444 [planetinfo.record]: sun_radius = 99707.469482 12:08:12.444 [planetinfo.record]: sun_vector = -0.450 -0.037 -0.892 12:08:12.444 [planetinfo.record]: sun_distance = 885020 12:08:12.444 [planetinfo.record]: corona_flare = 0.046585 12:08:12.444 [planetinfo.record]: corona_hues = 0.528877 12:08:12.444 [planetinfo.record]: sun_color = 0.275497, 0.513755, 0.739072, 1 12:08:12.444 [planetinfo.record]: corona_shimmer = 0.578092 12:08:12.445 [planetinfo.record]: station_vector = 0.691 -0.230 0.685 12:08:12.445 [planetinfo.record]: station = coriolis 12:08:17.580 [PLANETINFO OVER]: Done 12:08:17.581 [PLANETINFO LOGGING]: 1 185 12:08:18.587 [planetinfo.record]: seed = 80 142 250 4 12:08:18.588 [planetinfo.record]: coordinates = 142 224 12:08:18.588 [planetinfo.record]: government = 2; 12:08:18.588 [planetinfo.record]: economy = 0; 12:08:18.588 [planetinfo.record]: techlevel = 10; 12:08:18.588 [planetinfo.record]: population = 43; 12:08:18.588 [planetinfo.record]: productivity = 20640; 12:08:18.588 [planetinfo.record]: name = "Zabeso"; 12:08:18.588 [planetinfo.record]: inhabitant = "Fierce Green Feline"; 12:08:18.588 [planetinfo.record]: inhabitants = "Fierce Green Felines"; 12:08:18.588 [planetinfo.record]: description = "The world Zabeso is reasonably noted for its inhabitants’ exceptional love for tourists."; 12:08:18.597 [planetinfo.record]: air_color = 0.472739, 0.581168, 0.890385, 1; 12:08:18.597 [planetinfo.record]: cloud_alpha = 1.000000; 12:08:18.597 [planetinfo.record]: cloud_color = 0.128975, 0.546128, 0.673828, 1; 12:08:18.597 [planetinfo.record]: cloud_fraction = 0.520000; 12:08:18.597 [planetinfo.record]: land_color = 0.648438, 0.392609, 0.548504, 1; 12:08:18.597 [planetinfo.record]: land_fraction = 0.270000; 12:08:18.597 [planetinfo.record]: polar_cloud_color = 0.397297, 0.708084, 0.803223, 1; 12:08:18.597 [planetinfo.record]: polar_land_color = 0.935156, 0.842919, 0.899126, 1; 12:08:18.597 [planetinfo.record]: polar_sea_color = 0.926367, 0.880887, 0.836806, 1; 12:08:18.597 [planetinfo.record]: sea_color = 0.736328, 0.591728, 0.451576, 1; 12:08:18.597 [planetinfo.record]: rotation_speed = 0.002445 12:08:18.597 [planetinfo.record]: planet zpos = 438020.000000 12:08:18.597 [planetinfo.record]: sun_radius = 68520.221252 12:08:18.597 [planetinfo.record]: sun_vector = -0.674 -0.692 -0.259 12:08:18.597 [planetinfo.record]: sun_distance = 796400 12:08:18.597 [planetinfo.record]: corona_flare = 0.050580 12:08:18.597 [planetinfo.record]: corona_hues = 0.523621 12:08:18.597 [planetinfo.record]: sun_color = 0.789462, 0.61041, 0.366206, 1 12:08:18.597 [planetinfo.record]: corona_shimmer = 0.562821 12:08:18.598 [planetinfo.record]: station_vector = -0.207 0.512 0.834 12:08:18.598 [planetinfo.record]: station = coriolis 12:08:24.022 [PLANETINFO OVER]: Done 12:08:24.023 [PLANETINFO LOGGING]: 1 186 12:08:25.027 [planetinfo.record]: seed = 216 127 182 250 12:08:25.027 [planetinfo.record]: coordinates = 127 7 12:08:25.027 [planetinfo.record]: government = 3; 12:08:25.027 [planetinfo.record]: economy = 7; 12:08:25.027 [planetinfo.record]: techlevel = 5; 12:08:25.027 [planetinfo.record]: population = 31; 12:08:25.027 [planetinfo.record]: productivity = 5208; 12:08:25.027 [planetinfo.record]: name = "Qulete"; 12:08:25.027 [planetinfo.record]: inhabitant = "Slimy Lizard"; 12:08:25.027 [planetinfo.record]: inhabitants = "Slimy Lizards"; 12:08:25.027 [planetinfo.record]: description = "The world Qulete is a dull place."; 12:08:25.029 [planetinfo.record]: air_color = 0.629065, 0.864075, 0.955424, 1; 12:08:25.029 [planetinfo.record]: cloud_alpha = 1.000000; 12:08:25.029 [planetinfo.record]: cloud_color = 0.554008, 0.869141, 0.499077, 1; 12:08:25.029 [planetinfo.record]: cloud_fraction = 0.580000; 12:08:25.029 [planetinfo.record]: land_color = 0.589402, 0.322037, 0.654297, 1; 12:08:25.029 [planetinfo.record]: land_fraction = 0.500000; 12:08:25.029 [planetinfo.record]: polar_cloud_color = 0.689176, 0.891113, 0.653976, 1; 12:08:25.029 [planetinfo.record]: polar_land_color = 0.911397, 0.815924, 0.93457, 1; 12:08:25.029 [planetinfo.record]: polar_sea_color = 0.945117, 0.917983, 0.8962, 1; 12:08:25.029 [planetinfo.record]: sea_color = 0.548828, 0.485802, 0.435204, 1; 12:08:25.029 [planetinfo.record]: rotation_speed = 0.004899 12:08:25.029 [planetinfo.record]: planet zpos = 715390.000000 12:08:25.029 [planetinfo.record]: sun_radius = 138663.239746 12:08:25.029 [planetinfo.record]: sun_vector = 0.128 0.989 0.079 12:08:25.029 [planetinfo.record]: sun_distance = 1100600 12:08:25.029 [planetinfo.record]: corona_flare = 0.050993 12:08:25.030 [planetinfo.record]: corona_hues = 0.914291 12:08:25.030 [planetinfo.record]: sun_color = 0.812241, 0.400449, 0.266956, 1 12:08:25.030 [planetinfo.record]: corona_shimmer = 0.311288 12:08:25.030 [planetinfo.record]: station_vector = -0.863 0.502 0.053 12:08:25.030 [planetinfo.record]: station = coriolis 12:08:29.984 [PLANETINFO OVER]: Done 12:08:29.984 [PLANETINFO LOGGING]: 1 187 12:08:30.988 [planetinfo.record]: seed = 136 120 90 246 12:08:30.988 [planetinfo.record]: coordinates = 120 252 12:08:30.988 [planetinfo.record]: government = 1; 12:08:30.988 [planetinfo.record]: economy = 6; 12:08:30.988 [planetinfo.record]: techlevel = 2; 12:08:30.988 [planetinfo.record]: population = 16; 12:08:30.988 [planetinfo.record]: productivity = 2560; 12:08:30.988 [planetinfo.record]: name = "Vemaor"; 12:08:30.988 [planetinfo.record]: inhabitant = "Human Colonial"; 12:08:30.988 [planetinfo.record]: inhabitants = "Human Colonials"; 12:08:30.988 [planetinfo.record]: description = "The world Vemaor is a boring world."; 12:08:30.1000 [planetinfo.record]: air_color = 0.683609, 0.522473, 0.929408, 1; 12:08:30.1000 [planetinfo.record]: cloud_alpha = 1.000000; 12:08:30.1000 [planetinfo.record]: cloud_color = 0.791016, 0.225563, 0.676158, 1; 12:08:30.1000 [planetinfo.record]: cloud_fraction = 0.280000; 12:08:30.1000 [planetinfo.record]: land_color = 0.66, 0.337734, 0.541668, 1; 12:08:30.1000 [planetinfo.record]: land_fraction = 0.380000; 12:08:30.1000 [planetinfo.record]: polar_cloud_color = 0.855957, 0.473535, 0.778278, 1; 12:08:30.1000 [planetinfo.record]: polar_land_color = 0.934, 0.819986, 0.892136, 1; 12:08:30.1000 [planetinfo.record]: polar_sea_color = 0.91694, 0.931055, 0.872773, 1; 12:08:30.1000 [planetinfo.record]: sea_color = 0.647644, 0.689453, 0.51682, 1; 12:08:30.1000 [planetinfo.record]: rotation_speed = 0.000990 12:08:30.1000 [planetinfo.record]: planet zpos = 581360.000000 12:08:31.000 [planetinfo.record]: sun_radius = 115815.083008 12:08:31.000 [planetinfo.record]: sun_vector = 0.111 -0.940 0.324 12:08:31.000 [planetinfo.record]: sun_distance = 849680 12:08:31.000 [planetinfo.record]: corona_flare = 0.027766 12:08:31.000 [planetinfo.record]: corona_hues = 0.618568 12:08:31.000 [planetinfo.record]: sun_color = 0.672369, 0.73442, 0.818173, 1 12:08:31.000 [planetinfo.record]: corona_shimmer = 0.298453 12:08:31.000 [planetinfo.record]: station_vector = -0.690 0.658 0.302 12:08:31.000 [planetinfo.record]: station = coriolis 12:08:35.373 [PLANETINFO OVER]: Done 12:08:35.374 [PLANETINFO LOGGING]: 1 188 12:08:36.377 [planetinfo.record]: seed = 96 59 102 128 12:08:36.377 [planetinfo.record]: coordinates = 59 217 12:08:36.377 [planetinfo.record]: government = 4; 12:08:36.377 [planetinfo.record]: economy = 1; 12:08:36.377 [planetinfo.record]: techlevel = 11; 12:08:36.377 [planetinfo.record]: population = 50; 12:08:36.377 [planetinfo.record]: productivity = 28800; 12:08:36.377 [planetinfo.record]: name = "Laatso"; 12:08:36.377 [planetinfo.record]: inhabitant = "Human Colonial"; 12:08:36.377 [planetinfo.record]: inhabitants = "Human Colonials"; 12:08:36.377 [planetinfo.record]: description = "This planet is a tedious little planet."; 12:08:36.392 [planetinfo.record]: air_color = 0.590477, 0.906635, 0.971684, 1; 12:08:36.392 [planetinfo.record]: cloud_alpha = 1.000000; 12:08:36.392 [planetinfo.record]: cloud_color = 0.64018, 0.917969, 0.387268, 1; 12:08:36.392 [planetinfo.record]: cloud_fraction = 0.210000; 12:08:36.392 [planetinfo.record]: land_color = 0.415078, 0.562414, 0.66, 1; 12:08:36.392 [planetinfo.record]: land_fraction = 0.680000; 12:08:36.392 [planetinfo.record]: polar_cloud_color = 0.740392, 0.913086, 0.583162, 1; 12:08:36.392 [planetinfo.record]: polar_land_color = 0.84735, 0.899475, 0.934, 1; 12:08:36.392 [planetinfo.record]: polar_sea_color = 0.946875, 0.91488, 0.899254, 1; 12:08:36.392 [planetinfo.record]: sea_color = 0.53125, 0.459445, 0.424377, 1; 12:08:36.392 [planetinfo.record]: rotation_speed = 0.000770 12:08:36.392 [planetinfo.record]: planet zpos = 402500.000000 12:08:36.393 [planetinfo.record]: sun_radius = 82506.656647 12:08:36.393 [planetinfo.record]: sun_vector = -0.224 0.921 -0.318 12:08:36.393 [planetinfo.record]: sun_distance = 661250 12:08:36.393 [planetinfo.record]: corona_flare = 0.086768 12:08:36.393 [planetinfo.record]: corona_hues = 0.865936 12:08:36.393 [planetinfo.record]: sun_color = 0.688071, 0.491885, 0.205586, 1 12:08:36.393 [planetinfo.record]: corona_shimmer = 0.305860 12:08:36.393 [planetinfo.record]: station_vector = 0.567 -0.781 0.260 12:08:36.393 [planetinfo.record]: station = icosahedron 12:08:40.593 [PLANETINFO OVER]: Done 12:08:40.593 [PLANETINFO LOGGING]: 1 189 12:08:41.598 [planetinfo.record]: seed = 160 103 218 78 12:08:41.598 [planetinfo.record]: coordinates = 103 81 12:08:41.598 [planetinfo.record]: government = 4; 12:08:41.598 [planetinfo.record]: economy = 1; 12:08:41.598 [planetinfo.record]: techlevel = 11; 12:08:41.598 [planetinfo.record]: population = 50; 12:08:41.598 [planetinfo.record]: productivity = 28800; 12:08:41.598 [planetinfo.record]: name = "Resori"; 12:08:41.598 [planetinfo.record]: inhabitant = "Yellow Rodent"; 12:08:41.598 [planetinfo.record]: inhabitants = "Yellow Rodents"; 12:08:41.598 [planetinfo.record]: description = "This planet is noted for Zero-G cricket."; 12:08:41.629 [planetinfo.record]: air_color = 0.481137, 0.585866, 0.884531, 1; 12:08:41.629 [planetinfo.record]: cloud_alpha = 1.000000; 12:08:41.629 [planetinfo.record]: cloud_color = 0.151245, 0.537889, 0.65625, 1; 12:08:41.629 [planetinfo.record]: cloud_fraction = 0.140000; 12:08:41.629 [planetinfo.record]: land_color = 0.407344, 0.606705, 0.66, 1; 12:08:41.629 [planetinfo.record]: land_fraction = 0.620000; 12:08:41.629 [planetinfo.record]: polar_cloud_color = 0.412801, 0.705661, 0.795313, 1; 12:08:41.629 [planetinfo.record]: polar_land_color = 0.844613, 0.915145, 0.934, 1; 12:08:41.629 [planetinfo.record]: polar_sea_color = 0.922631, 0.935352, 0.882829, 1; 12:08:41.629 [planetinfo.record]: sea_color = 0.611317, 0.646484, 0.501278, 1; 12:08:41.630 [planetinfo.record]: rotation_speed = 0.001848 12:08:41.630 [planetinfo.record]: planet zpos = 975450.000000 12:08:41.630 [planetinfo.record]: sun_radius = 187192.451019 12:08:41.630 [planetinfo.record]: sun_vector = -0.922 0.387 0.026 12:08:41.630 [planetinfo.record]: sun_distance = 1040480 12:08:41.630 [planetinfo.record]: corona_flare = 0.022104 12:08:41.630 [planetinfo.record]: corona_hues = 0.889297 12:08:41.630 [planetinfo.record]: sun_color = 0.326349, 0.578088, 0.767462, 1 12:08:41.630 [planetinfo.record]: corona_shimmer = 0.356927 12:08:41.631 [planetinfo.record]: station_vector = 0.246 0.965 0.087 12:08:41.632 [planetinfo.record]: station = dodecahedron 12:08:47.067 [PLANETINFO OVER]: Done 12:08:47.069 [PLANETINFO LOGGING]: 1 190 12:08:48.074 [planetinfo.record]: seed = 72 21 182 219 12:08:48.074 [planetinfo.record]: coordinates = 21 190 12:08:48.074 [planetinfo.record]: government = 1; 12:08:48.074 [planetinfo.record]: economy = 6; 12:08:48.074 [planetinfo.record]: techlevel = 3; 12:08:48.074 [planetinfo.record]: population = 20; 12:08:48.075 [planetinfo.record]: productivity = 3200; 12:08:48.075 [planetinfo.record]: name = "Anama"; 12:08:48.075 [planetinfo.record]: inhabitant = "Bony Feline"; 12:08:48.075 [planetinfo.record]: inhabitants = "Bony Felines"; 12:08:48.075 [planetinfo.record]: description = "This world is noted for its exciting lethal Zamaon water."; 12:08:48.077 [planetinfo.record]: air_color = 0.691601, 0.561358, 0.896889, 1; 12:08:48.077 [planetinfo.record]: cloud_alpha = 1.000000; 12:08:48.077 [planetinfo.record]: cloud_color = 0.693359, 0.330429, 0.628145, 1; 12:08:48.077 [planetinfo.record]: cloud_fraction = 0.170000; 12:08:48.077 [planetinfo.record]: land_color = 0.581448, 0.66, 0.466641, 1; 12:08:48.077 [planetinfo.record]: land_fraction = 0.250000; 12:08:48.078 [planetinfo.record]: polar_cloud_color = 0.812012, 0.546363, 0.764278, 1; 12:08:48.078 [planetinfo.record]: polar_land_color = 0.906209, 0.934, 0.865592, 1; 12:08:48.078 [planetinfo.record]: polar_sea_color = 0.937789, 0.855938, 0.94043, 1; 12:08:48.078 [planetinfo.record]: sea_color = 0.589013, 0.381622, 0.595703, 1; 12:08:48.078 [planetinfo.record]: rotation_speed = 0.004244 12:08:48.078 [planetinfo.record]: planet zpos = 565300.000000 12:08:48.078 [planetinfo.record]: sun_radius = 150957.423553 12:08:48.078 [planetinfo.record]: sun_vector = -0.932 0.350 0.089 12:08:48.078 [planetinfo.record]: sun_distance = 1187130 12:08:48.078 [planetinfo.record]: corona_flare = 0.089632 12:08:48.078 [planetinfo.record]: corona_hues = 0.965477 12:08:48.078 [planetinfo.record]: sun_color = 0.252872, 0.618187, 0.661896, 1 12:08:48.079 [planetinfo.record]: corona_shimmer = 0.391059 12:08:48.079 [planetinfo.record]: station_vector = -0.128 -0.250 0.960 12:08:48.079 [planetinfo.record]: station = coriolis 12:08:54.001 [PLANETINFO OVER]: Done 12:08:54.002 [PLANETINFO LOGGING]: 1 191 12:08:55.006 [planetinfo.record]: seed = 152 43 122 123 12:08:55.006 [planetinfo.record]: coordinates = 43 160 12:08:55.006 [planetinfo.record]: government = 3; 12:08:55.006 [planetinfo.record]: economy = 0; 12:08:55.006 [planetinfo.record]: techlevel = 12; 12:08:55.006 [planetinfo.record]: population = 52; 12:08:55.006 [planetinfo.record]: productivity = 29120; 12:08:55.006 [planetinfo.record]: name = "Ansoreat"; 12:08:55.006 [planetinfo.record]: inhabitant = "Human Colonial"; 12:08:55.006 [planetinfo.record]: inhabitants = "Human Colonials"; 12:08:55.006 [planetinfo.record]: description = "This planet is a dull world."; 12:08:55.032 [planetinfo.record]: air_color = 0.574816, 0.920303, 0.803989, 1; 12:08:55.032 [planetinfo.record]: cloud_alpha = 1.000000; 12:08:55.032 [planetinfo.record]: cloud_color = 0.763672, 0.360954, 0.360954, 1; 12:08:55.032 [planetinfo.record]: cloud_fraction = 0.200000; 12:08:55.032 [planetinfo.record]: land_color = 0.431837, 0.343773, 0.560547, 1; 12:08:55.032 [planetinfo.record]: land_fraction = 0.460000; 12:08:55.032 [planetinfo.record]: polar_cloud_color = 0.843652, 0.565593, 0.565593, 1; 12:08:55.032 [planetinfo.record]: polar_land_color = 0.889759, 0.852685, 0.943945, 1; 12:08:55.032 [planetinfo.record]: polar_sea_color = 0.944922, 0.941461, 0.723456, 1; 12:08:55.032 [planetinfo.record]: sea_color = 0.550781, 0.542713, 0.0344238, 1; 12:08:55.032 [planetinfo.record]: rotation_speed = 0.000297 12:08:55.032 [planetinfo.record]: planet zpos = 737750.000000 12:08:55.032 [planetinfo.record]: sun_radius = 179641.078949 12:08:55.032 [planetinfo.record]: sun_vector = 0.065 0.463 -0.884 12:08:55.032 [planetinfo.record]: sun_distance = 908000 12:08:55.032 [planetinfo.record]: corona_flare = 0.065401 12:08:55.032 [planetinfo.record]: corona_hues = 0.775414 12:08:55.032 [planetinfo.record]: sun_color = 0.234317, 0.705937, 0.726541, 1 12:08:55.032 [planetinfo.record]: corona_shimmer = 0.369742 12:08:55.032 [planetinfo.record]: station_vector = 0.306 0.838 0.451 12:08:55.033 [planetinfo.record]: station = dodecahedron 12:08:59.547 [PLANETINFO OVER]: Done 12:08:59.548 [PLANETINFO LOGGING]: 1 192 12:09:00.564 [planetinfo.record]: seed = 144 177 166 231 12:09:00.564 [planetinfo.record]: coordinates = 177 238 12:09:00.564 [planetinfo.record]: government = 2; 12:09:00.564 [planetinfo.record]: economy = 6; 12:09:00.564 [planetinfo.record]: techlevel = 3; 12:09:00.564 [planetinfo.record]: population = 21; 12:09:00.564 [planetinfo.record]: productivity = 4032; 12:09:00.564 [planetinfo.record]: name = "Sosole"; 12:09:00.564 [planetinfo.record]: inhabitant = "Fierce Lizard"; 12:09:00.564 [planetinfo.record]: inhabitants = "Fierce Lizards"; 12:09:00.564 [planetinfo.record]: description = "The world Sosole is fabled for its weird rock formations and the Sosoleian deadly goat."; 12:09:00.588 [planetinfo.record]: air_color = 0.503753, 0.977537, 0.832821, 1; 12:09:00.588 [planetinfo.record]: cloud_alpha = 1.000000; 12:09:00.588 [planetinfo.record]: cloud_color = 0.935547, 0.210247, 0.135216, 1; 12:09:00.588 [planetinfo.record]: cloud_fraction = 0.310000; 12:09:00.588 [planetinfo.record]: land_color = 0.481948, 0.0902344, 0.66, 1; 12:09:00.588 [planetinfo.record]: land_fraction = 0.320000; 12:09:00.588 [planetinfo.record]: polar_cloud_color = 0.920996, 0.474734, 0.428569, 1; 12:09:00.588 [planetinfo.record]: polar_land_color = 0.871007, 0.732424, 0.934, 1; 12:09:00.588 [planetinfo.record]: polar_sea_color = 0.943164, 0.922608, 0.893519, 1; 12:09:00.588 [planetinfo.record]: sea_color = 0.568359, 0.51881, 0.448693, 1; 12:09:00.588 [planetinfo.record]: rotation_speed = 0.000211 12:09:00.588 [planetinfo.record]: planet zpos = 478500.000000 12:09:00.588 [planetinfo.record]: sun_radius = 145122.706604 12:09:00.588 [planetinfo.record]: sun_vector = 0.085 0.887 -0.454 12:09:00.588 [planetinfo.record]: sun_distance = 1100550 12:09:00.588 [planetinfo.record]: corona_flare = 0.075545 12:09:00.588 [planetinfo.record]: corona_hues = 0.996140 12:09:00.588 [planetinfo.record]: sun_color = 0.689709, 0.511054, 0.229873, 1 12:09:00.588 [planetinfo.record]: corona_shimmer = 0.285684 12:09:00.588 [planetinfo.record]: station_vector = 0.412 -0.911 0.030 12:09:00.588 [planetinfo.record]: station = coriolis 12:09:05.185 [PLANETINFO OVER]: Done 12:09:05.188 [PLANETINFO LOGGING]: 1 193 12:09:06.189 [planetinfo.record]: seed = 112 144 58 57 12:09:06.189 [planetinfo.record]: coordinates = 144 33 12:09:06.189 [planetinfo.record]: government = 6; 12:09:06.189 [planetinfo.record]: economy = 1; 12:09:06.189 [planetinfo.record]: techlevel = 9; 12:09:06.189 [planetinfo.record]: population = 44; 12:09:06.190 [planetinfo.record]: productivity = 31680; 12:09:06.190 [planetinfo.record]: name = "Orarra"; 12:09:06.190 [planetinfo.record]: inhabitant = "Human Colonial"; 12:09:06.190 [planetinfo.record]: inhabitants = "Human Colonials"; 12:09:06.190 [planetinfo.record]: description = "This planet is very well known for vicious Zast brew and its exotic goat soup."; 12:09:06.205 [planetinfo.record]: air_color = 0.700118, 0.837033, 0.892336, 1; 12:09:06.205 [planetinfo.record]: cloud_alpha = 1.000000; 12:09:06.205 [planetinfo.record]: cloud_color = 0.65181, 0.679688, 0.647827, 1; 12:09:06.205 [planetinfo.record]: cloud_fraction = 0.210000; 12:09:06.205 [planetinfo.record]: land_color = 0.461484, 0.511113, 0.66, 1; 12:09:06.205 [planetinfo.record]: land_fraction = 0.340000; 12:09:06.206 [planetinfo.record]: polar_cloud_color = 0.785201, 0.805859, 0.78225, 1; 12:09:06.206 [planetinfo.record]: polar_land_color = 0.863768, 0.881326, 0.934, 1; 12:09:06.206 [planetinfo.record]: polar_sea_color = 0.917517, 0.937109, 0.888881, 1; 12:09:06.206 [planetinfo.record]: sea_color = 0.576311, 0.628906, 0.49944, 1; 12:09:06.206 [planetinfo.record]: rotation_speed = 0.001219 12:09:06.206 [planetinfo.record]: planet zpos = 526400.000000 12:09:06.206 [planetinfo.record]: sun_radius = 134605.659180 12:09:06.206 [planetinfo.record]: sun_vector = -0.054 -0.967 0.248 12:09:06.206 [planetinfo.record]: sun_distance = 1052800 12:09:06.207 [planetinfo.record]: corona_flare = 0.016440 12:09:06.207 [planetinfo.record]: corona_hues = 0.620995 12:09:06.207 [planetinfo.record]: sun_color = 0.235006, 0.46402, 0.729611, 1 12:09:06.207 [planetinfo.record]: corona_shimmer = 0.952088 12:09:06.207 [planetinfo.record]: station_vector = 0.263 -0.956 0.129 12:09:06.207 [planetinfo.record]: station = coriolis 12:09:10.640 [PLANETINFO OVER]: Done 12:09:10.641 [PLANETINFO LOGGING]: 1 194 12:09:11.644 [planetinfo.record]: seed = 56 216 54 119 12:09:11.645 [planetinfo.record]: coordinates = 216 180 12:09:11.645 [planetinfo.record]: government = 7; 12:09:11.645 [planetinfo.record]: economy = 4; 12:09:11.645 [planetinfo.record]: techlevel = 7; 12:09:11.645 [planetinfo.record]: population = 40; 12:09:11.645 [planetinfo.record]: productivity = 21120; 12:09:11.645 [planetinfo.record]: name = "Tigebere"; 12:09:11.645 [planetinfo.record]: inhabitant = "Human Colonial"; 12:09:11.645 [planetinfo.record]: inhabitants = "Human Colonials"; 12:09:11.645 [planetinfo.record]: description = "The planet Tigebere is most famous for its vast rain forests."; 12:09:11.646 [planetinfo.record]: air_color = 0.577641, 0.488246, 0.933311, 1; 12:09:11.646 [planetinfo.record]: cloud_alpha = 1.000000; 12:09:11.646 [planetinfo.record]: cloud_color = 0.547054, 0.134834, 0.802734, 1; 12:09:11.646 [planetinfo.record]: cloud_fraction = 0.140000; 12:09:11.646 [planetinfo.record]: land_color = 0.226959, 0.523438, 0.488694, 1; 12:09:11.647 [planetinfo.record]: land_fraction = 0.550000; 12:09:11.647 [planetinfo.record]: polar_cloud_color = 0.689785, 0.413374, 0.86123, 1; 12:09:11.647 [planetinfo.record]: polar_land_color = 0.813467, 0.947656, 0.931931, 1; 12:09:11.647 [planetinfo.record]: polar_sea_color = 0.841412, 0.916602, 0.844349, 1; 12:09:11.647 [planetinfo.record]: sea_color = 0.560333, 0.833984, 0.571023, 1; 12:09:11.647 [planetinfo.record]: rotation_speed = 0.003602 12:09:11.647 [planetinfo.record]: planet zpos = 482400.000000 12:09:11.647 [planetinfo.record]: sun_radius = 153662.684326 12:09:11.647 [planetinfo.record]: sun_vector = -0.588 -0.306 -0.748 12:09:11.647 [planetinfo.record]: sun_distance = 1061280 12:09:11.647 [planetinfo.record]: corona_flare = 0.028442 12:09:11.647 [planetinfo.record]: corona_hues = 0.766502 12:09:11.647 [planetinfo.record]: sun_color = 0.364683, 0.709831, 0.727988, 1 12:09:11.647 [planetinfo.record]: corona_shimmer = 0.560213 12:09:11.648 [planetinfo.record]: station_vector = -0.471 0.803 0.366 12:09:11.648 [planetinfo.record]: station = coriolis 12:09:15.860 [PLANETINFO OVER]: Done 12:09:15.861 [PLANETINFO LOGGING]: 1 195 12:09:16.865 [planetinfo.record]: seed = 40 206 26 37 12:09:16.865 [planetinfo.record]: coordinates = 206 83 12:09:16.865 [planetinfo.record]: government = 5; 12:09:16.866 [planetinfo.record]: economy = 3; 12:09:16.866 [planetinfo.record]: techlevel = 9; 12:09:16.866 [planetinfo.record]: population = 45; 12:09:16.866 [planetinfo.record]: productivity = 22680; 12:09:16.866 [planetinfo.record]: name = "Cebior"; 12:09:16.866 [planetinfo.record]: inhabitant = "Human Colonial"; 12:09:16.866 [planetinfo.record]: inhabitants = "Human Colonials"; 12:09:16.866 [planetinfo.record]: description = "This world is reasonably well known for its vast oceans but ravaged by vicious vicious wolfs."; 12:09:16.892 [planetinfo.record]: air_color = 0.751911, 0.735831, 0.971684, 1; 12:09:16.892 [planetinfo.record]: cloud_alpha = 1.000000; 12:09:16.892 [planetinfo.record]: cloud_color = 0.825522, 0.799637, 0.917969, 1; 12:09:16.892 [planetinfo.record]: cloud_fraction = 0.480000; 12:09:16.892 [planetinfo.record]: land_color = 0.66, 0.00257813, 0.0796198, 1; 12:09:16.892 [planetinfo.record]: land_fraction = 0.270000; 12:09:16.892 [planetinfo.record]: polar_cloud_color = 0.855614, 0.839522, 0.913086, 1; 12:09:16.892 [planetinfo.record]: polar_land_color = 0.934, 0.701412, 0.728669, 1; 12:09:16.892 [planetinfo.record]: polar_sea_color = 0.941211, 0.898002, 0.884775, 1; 12:09:16.892 [planetinfo.record]: sea_color = 0.587891, 0.479936, 0.446889, 1; 12:09:16.892 [planetinfo.record]: rotation_speed = 0.004657 12:09:16.892 [planetinfo.record]: planet zpos = 387180.000000 12:09:16.892 [planetinfo.record]: sun_radius = 131824.896240 12:09:16.892 [planetinfo.record]: sun_vector = 0.299 0.306 -0.904 12:09:16.892 [planetinfo.record]: sun_distance = 946440 12:09:16.892 [planetinfo.record]: corona_flare = 0.061824 12:09:16.892 [planetinfo.record]: corona_hues = 0.823860 12:09:16.892 [planetinfo.record]: sun_color = 0.345321, 0.60321, 0.658786, 1 12:09:16.892 [planetinfo.record]: corona_shimmer = 0.361897 12:09:16.893 [planetinfo.record]: station_vector = 0.983 -0.048 0.179 12:09:16.893 [planetinfo.record]: station = coriolis 12:09:21.629 [PLANETINFO OVER]: Done 12:09:21.630 [PLANETINFO LOGGING]: 1 196 12:09:22.634 [planetinfo.record]: seed = 64 165 102 37 12:09:22.634 [planetinfo.record]: coordinates = 165 57 12:09:22.634 [planetinfo.record]: government = 0; 12:09:22.634 [planetinfo.record]: economy = 3; 12:09:22.634 [planetinfo.record]: techlevel = 5; 12:09:22.634 [planetinfo.record]: population = 24; 12:09:22.634 [planetinfo.record]: productivity = 5376; 12:09:22.634 [planetinfo.record]: name = "Cezaa"; 12:09:22.634 [planetinfo.record]: inhabitant = "Human Colonial"; 12:09:22.634 [planetinfo.record]: inhabitants = "Human Colonials"; 12:09:22.634 [planetinfo.record]: description = "This planet is plagued by deadly earthquakes."; 12:09:22.640 [planetinfo.record]: air_color = 0.685478, 0.846532, 0.902092, 1; 12:09:22.640 [planetinfo.record]: cloud_alpha = 1.000000; 12:09:22.640 [planetinfo.record]: cloud_color = 0.641911, 0.708984, 0.623131, 1; 12:09:22.640 [planetinfo.record]: cloud_fraction = 0.250000; 12:09:22.641 [planetinfo.record]: land_color = 0.41814, 0.252656, 0.66, 1; 12:09:22.641 [planetinfo.record]: land_fraction = 0.340000; 12:09:22.641 [planetinfo.record]: polar_cloud_color = 0.770615, 0.819043, 0.757055, 1; 12:09:22.641 [planetinfo.record]: polar_land_color = 0.848433, 0.789887, 0.934, 1; 12:09:22.641 [planetinfo.record]: polar_sea_color = 0.899055, 0.932422, 0.880337, 1; 12:09:22.641 [planetinfo.record]: sea_color = 0.57905, 0.675781, 0.524786, 1; 12:09:22.641 [planetinfo.record]: rotation_speed = 0.004495 12:09:22.641 [planetinfo.record]: planet zpos = 511320.000000 12:09:22.641 [planetinfo.record]: sun_radius = 127621.293182 12:09:22.641 [planetinfo.record]: sun_vector = 0.276 -0.957 -0.088 12:09:22.641 [planetinfo.record]: sun_distance = 724370 12:09:22.641 [planetinfo.record]: corona_flare = 0.081953 12:09:22.641 [planetinfo.record]: corona_hues = 0.937973 12:09:22.641 [planetinfo.record]: sun_color = 0.764676, 0.666622, 0.551991, 1 12:09:22.641 [planetinfo.record]: corona_shimmer = 0.373795 12:09:22.642 [planetinfo.record]: station_vector = -0.956 0.028 0.294 12:09:22.642 [planetinfo.record]: station = coriolis 12:09:27.684 [PLANETINFO OVER]: Done 12:09:27.685 [PLANETINFO LOGGING]: 1 197 12:09:28.690 [planetinfo.record]: seed = 192 248 26 204 12:09:28.690 [planetinfo.record]: coordinates = 248 207 12:09:28.690 [planetinfo.record]: government = 0; 12:09:28.690 [planetinfo.record]: economy = 7; 12:09:28.690 [planetinfo.record]: techlevel = 0; 12:09:28.690 [planetinfo.record]: population = 8; 12:09:28.690 [planetinfo.record]: productivity = 768; 12:09:28.690 [planetinfo.record]: name = "Inbeed"; 12:09:28.690 [planetinfo.record]: inhabitant = "Human Colonial"; 12:09:28.690 [planetinfo.record]: inhabitants = "Human Colonials"; 12:09:28.690 [planetinfo.record]: description = "This planet is most notable for Inbeedian lethal brandy but beset by evil disease."; 12:09:28.698 [planetinfo.record]: air_color = 0.75622, 0.610102, 0.874775, 1; 12:09:28.698 [planetinfo.record]: cloud_alpha = 1.000000; 12:09:28.698 [planetinfo.record]: cloud_color = 0.626953, 0.433479, 0.496963, 1; 12:09:28.698 [planetinfo.record]: cloud_fraction = 0.180000; 12:09:28.698 [planetinfo.record]: land_color = 0.66, 0.375198, 0.299063, 1; 12:09:28.698 [planetinfo.record]: land_fraction = 0.280000; 12:09:28.698 [planetinfo.record]: polar_cloud_color = 0.782129, 0.631279, 0.680777, 1; 12:09:28.698 [planetinfo.record]: polar_land_color = 0.934, 0.83324, 0.806305, 1; 12:09:28.698 [planetinfo.record]: polar_sea_color = 0.912682, 0.934766, 0.883372, 1; 12:09:28.699 [planetinfo.record]: sea_color = 0.590699, 0.652344, 0.508879, 1; 12:09:28.699 [planetinfo.record]: rotation_speed = 0.000677 12:09:28.699 [planetinfo.record]: planet zpos = 674960.000000 12:09:28.699 [planetinfo.record]: sun_radius = 96455.493164 12:09:28.699 [planetinfo.record]: sun_vector = -0.542 -0.079 0.837 12:09:28.699 [planetinfo.record]: sun_distance = 1349920 12:09:28.699 [planetinfo.record]: corona_flare = 0.044006 12:09:28.699 [planetinfo.record]: corona_hues = 0.530876 12:09:28.699 [planetinfo.record]: sun_color = 0.765677, 0.686892, 0.562975, 1 12:09:28.699 [planetinfo.record]: corona_shimmer = 0.324009 12:09:28.699 [planetinfo.record]: station_vector = -0.859 -0.494 0.130 12:09:28.699 [planetinfo.record]: station = coriolis 12:09:33.175 [PLANETINFO OVER]: Done 12:09:33.176 [PLANETINFO LOGGING]: 1 198 12:09:34.196 [planetinfo.record]: seed = 168 184 54 165 12:09:34.196 [planetinfo.record]: coordinates = 184 88 12:09:34.196 [planetinfo.record]: government = 5; 12:09:34.196 [planetinfo.record]: economy = 0; 12:09:34.196 [planetinfo.record]: techlevel = 10; 12:09:34.197 [planetinfo.record]: population = 46; 12:09:34.197 [planetinfo.record]: productivity = 33120; 12:09:34.197 [planetinfo.record]: name = "Cevera"; 12:09:34.197 [planetinfo.record]: inhabitant = "Human Colonial"; 12:09:34.197 [planetinfo.record]: inhabitants = "Human Colonials"; 12:09:34.197 [planetinfo.record]: description = "The planet Cevera is most well known for its hoopy casinos."; 12:09:34.206 [planetinfo.record]: air_color = 0.420193, 0.68533, 0.896889, 1; 12:09:34.206 [planetinfo.record]: cloud_alpha = 1.000000; 12:09:34.206 [planetinfo.record]: cloud_color = 0.00270844, 0.693359, 0.24012, 1; 12:09:34.206 [planetinfo.record]: cloud_fraction = 0.090000; 12:09:34.207 [planetinfo.record]: land_color = 0.589844, 0.586604, 0.569107, 1; 12:09:34.207 [planetinfo.record]: land_fraction = 0.690000; 12:09:34.207 [planetinfo.record]: polar_cloud_color = 0.306487, 0.812012, 0.480261, 1; 12:09:34.207 [planetinfo.record]: polar_land_color = 0.941016, 0.939723, 0.932745, 1; 12:09:34.207 [planetinfo.record]: polar_sea_color = 0.89053, 0.919141, 0.827586, 1; 12:09:34.207 [planetinfo.record]: sea_color = 0.707914, 0.808594, 0.48642, 1; 12:09:34.207 [planetinfo.record]: rotation_speed = 0.002993 12:09:34.207 [planetinfo.record]: planet zpos = 428000.000000 12:09:34.207 [planetinfo.record]: sun_radius = 95624.719238 12:09:34.207 [planetinfo.record]: sun_vector = -0.197 0.886 0.420 12:09:34.207 [planetinfo.record]: sun_distance = 770400 12:09:34.207 [planetinfo.record]: corona_flare = 0.033684 12:09:34.207 [planetinfo.record]: corona_hues = 0.580727 12:09:34.207 [planetinfo.record]: sun_color = 0.222118, 0.490056, 0.652176, 1 12:09:34.208 [planetinfo.record]: corona_shimmer = 0.505890 12:09:34.208 [planetinfo.record]: station_vector = -0.907 -0.330 0.260 12:09:34.208 [planetinfo.record]: station = coriolis 12:09:38.725 [PLANETINFO OVER]: Done 12:09:38.725 [PLANETINFO LOGGING]: 1 199 12:09:39.728 [planetinfo.record]: seed = 56 112 58 59 12:09:39.728 [planetinfo.record]: coordinates = 112 20 12:09:39.728 [planetinfo.record]: government = 7; 12:09:39.728 [planetinfo.record]: economy = 4; 12:09:39.728 [planetinfo.record]: techlevel = 7; 12:09:39.729 [planetinfo.record]: population = 40; 12:09:39.729 [planetinfo.record]: productivity = 21120; 12:09:39.729 [planetinfo.record]: name = "Anonmabi"; 12:09:39.729 [planetinfo.record]: inhabitant = "Human Colonial"; 12:09:39.729 [planetinfo.record]: inhabitants = "Human Colonials"; 12:09:39.729 [planetinfo.record]: description = "This planet is reasonably noted for its exotic goat soup."; 12:09:39.743 [planetinfo.record]: air_color = 0.423246, 0.698033, 0.891035, 1; 12:09:39.743 [planetinfo.record]: cloud_alpha = 1.000000; 12:09:39.743 [planetinfo.record]: cloud_color = 0.0158386, 0.675781, 0.180824, 1; 12:09:39.744 [planetinfo.record]: cloud_fraction = 0.220000; 12:09:39.744 [planetinfo.record]: land_color = 0.202394, 0.636719, 0.00497437, 1; 12:09:39.744 [planetinfo.record]: land_fraction = 0.510000; 12:09:39.744 [planetinfo.record]: polar_cloud_color = 0.313317, 0.804102, 0.436013, 1; 12:09:39.744 [planetinfo.record]: polar_land_color = 0.776654, 0.936328, 0.704075, 1; 12:09:39.744 [planetinfo.record]: polar_sea_color = 0.862547, 0.921875, 0.874598, 1; 12:09:39.744 [planetinfo.record]: sea_color = 0.580139, 0.78125, 0.62099, 1; 12:09:39.744 [planetinfo.record]: rotation_speed = 0.004028 12:09:39.744 [planetinfo.record]: planet zpos = 574400.000000 12:09:39.744 [planetinfo.record]: sun_radius = 146100.554199 12:09:39.744 [planetinfo.record]: sun_vector = -0.107 -0.986 -0.126 12:09:39.744 [planetinfo.record]: sun_distance = 1206240 12:09:39.744 [planetinfo.record]: corona_flare = 0.046646 12:09:39.745 [planetinfo.record]: corona_hues = 0.830536 12:09:39.745 [planetinfo.record]: sun_color = 0.79057, 0.274648, 0.243069, 1 12:09:39.745 [planetinfo.record]: corona_shimmer = 0.536617 12:09:39.745 [planetinfo.record]: station_vector = 0.196 0.755 0.626 12:09:39.745 [planetinfo.record]: station = coriolis 12:09:45.222 [PLANETINFO OVER]: Done 12:09:45.223 [PLANETINFO LOGGING]: 1 200 12:09:46.227 [planetinfo.record]: seed = 112 102 166 145 12:09:46.227 [planetinfo.record]: coordinates = 102 107 12:09:46.227 [planetinfo.record]: government = 6; 12:09:46.227 [planetinfo.record]: economy = 3; 12:09:46.227 [planetinfo.record]: techlevel = 9; 12:09:46.227 [planetinfo.record]: population = 46; 12:09:46.227 [planetinfo.record]: productivity = 25760; 12:09:46.227 [planetinfo.record]: name = "Atgeaner"; 12:09:46.227 [planetinfo.record]: inhabitant = "Black Furry Feline"; 12:09:46.227 [planetinfo.record]: inhabitants = "Black Furry Felines"; 12:09:46.228 [planetinfo.record]: description = "This world is mildly famous for its vast oceans and its inhabitants’ exceptional love for tourists."; 12:09:46.261 [planetinfo.record]: air_color = 0.434365, 0.632455, 0.902092, 1; 12:09:46.261 [planetinfo.record]: cloud_alpha = 1.000000; 12:09:46.261 [planetinfo.record]: cloud_color = 0.0304642, 0.708984, 0.534053, 1; 12:09:46.261 [planetinfo.record]: cloud_fraction = 0.240000; 12:09:46.261 [planetinfo.record]: land_color = 0.66, 0.652266, 0.659335, 1; 12:09:46.261 [planetinfo.record]: land_fraction = 0.320000; 12:09:46.261 [planetinfo.record]: polar_cloud_color = 0.329137, 0.819043, 0.692739, 1; 12:09:46.261 [planetinfo.record]: polar_land_color = 0.934, 0.931264, 0.933765, 1; 12:09:46.261 [planetinfo.record]: polar_sea_color = 0.928906, 0.843409, 0.834564, 1; 12:09:46.261 [planetinfo.record]: sea_color = 0.710938, 0.449196, 0.422119, 1; 12:09:46.261 [planetinfo.record]: rotation_speed = 0.003910 12:09:46.261 [planetinfo.record]: planet zpos = 444360.000000 12:09:46.261 [planetinfo.record]: sun_radius = 71979.710083 12:09:46.261 [planetinfo.record]: sun_vector = -0.637 -0.524 0.566 12:09:46.262 [planetinfo.record]: sun_distance = 603060 12:09:46.262 [planetinfo.record]: corona_flare = 0.082075 12:09:46.262 [planetinfo.record]: corona_hues = 0.575249 12:09:46.262 [planetinfo.record]: sun_color = 0.69996, 0.236248, 0.211974, 1 12:09:46.262 [planetinfo.record]: corona_shimmer = 0.476917 12:09:46.262 [planetinfo.record]: station_vector = 0.664 -0.650 0.369 12:09:46.262 [planetinfo.record]: station = coriolis 12:09:51.339 [PLANETINFO OVER]: Done 12:09:51.340 [PLANETINFO LOGGING]: 1 201 12:09:52.348 [planetinfo.record]: seed = 144 80 122 15 12:09:52.348 [planetinfo.record]: coordinates = 80 91 12:09:52.348 [planetinfo.record]: government = 2; 12:09:52.348 [planetinfo.record]: economy = 3; 12:09:52.348 [planetinfo.record]: techlevel = 5; 12:09:52.348 [planetinfo.record]: population = 26; 12:09:52.348 [planetinfo.record]: productivity = 8736; 12:09:52.348 [planetinfo.record]: name = "Aanan"; 12:09:52.348 [planetinfo.record]: inhabitant = "Human Colonial"; 12:09:52.348 [planetinfo.record]: inhabitants = "Human Colonials"; 12:09:52.348 [planetinfo.record]: description = "The planet Aanan is very famous for its hoopy night life but scourged by killer edible Onzaoids."; 12:09:52.350 [planetinfo.record]: air_color = 0.443723, 0.582748, 0.909246, 1; 12:09:52.350 [planetinfo.record]: cloud_alpha = 1.000000; 12:09:52.350 [planetinfo.record]: cloud_color = 0.0456543, 0.650217, 0.730469, 1; 12:09:52.350 [planetinfo.record]: cloud_fraction = 0.260000; 12:09:52.350 [planetinfo.record]: land_color = 0.66, 0.556613, 0.203672, 1; 12:09:52.350 [planetinfo.record]: land_fraction = 0.710000; 12:09:52.350 [planetinfo.record]: polar_cloud_color = 0.343138, 0.771808, 0.828711, 1; 12:09:52.350 [planetinfo.record]: polar_land_color = 0.934, 0.897423, 0.772557, 1; 12:09:52.350 [planetinfo.record]: polar_sea_color = 0.929023, 0.937695, 0.887239, 1; 12:09:52.350 [planetinfo.record]: sea_color = 0.599998, 0.623047, 0.488946, 1; 12:09:52.350 [planetinfo.record]: rotation_speed = 0.004940 12:09:52.350 [planetinfo.record]: planet zpos = 875680.000000 12:09:52.350 [planetinfo.record]: sun_radius = 168582.954102 12:09:52.350 [planetinfo.record]: sun_vector = -0.201 -0.869 0.452 12:09:52.350 [planetinfo.record]: sun_distance = 1279840 12:09:52.350 [planetinfo.record]: corona_flare = 0.001491 12:09:52.350 [planetinfo.record]: corona_hues = 0.681198 12:09:52.350 [planetinfo.record]: sun_color = 0.34034, 0.417492, 0.728186, 1 12:09:52.351 [planetinfo.record]: corona_shimmer = 0.925493 12:09:52.351 [planetinfo.record]: station_vector = -0.595 -0.622 0.509 12:09:52.351 [planetinfo.record]: station = coriolis 12:09:57.386 [PLANETINFO OVER]: Done 12:09:57.387 [PLANETINFO LOGGING]: 1 202 12:09:58.408 [planetinfo.record]: seed = 152 230 182 189 12:09:58.408 [planetinfo.record]: coordinates = 230 27 12:09:58.408 [planetinfo.record]: government = 3; 12:09:58.408 [planetinfo.record]: economy = 3; 12:09:58.408 [planetinfo.record]: techlevel = 8; 12:09:58.408 [planetinfo.record]: population = 39; 12:09:58.408 [planetinfo.record]: productivity = 15288; 12:09:58.408 [planetinfo.record]: name = "Isonza"; 12:09:58.408 [planetinfo.record]: inhabitant = "Harmless Furry Feline"; 12:09:58.408 [planetinfo.record]: inhabitants = "Harmless Furry Felines"; 12:09:58.408 [planetinfo.record]: description = "This planet is beset by deadly earthquakes."; 12:09:58.428 [planetinfo.record]: air_color = 0.493767, 0.640959, 0.854613, 1; 12:09:58.428 [planetinfo.record]: cloud_alpha = 1.000000; 12:09:58.428 [planetinfo.record]: cloud_color = 0.188065, 0.566406, 0.4866, 1; 12:09:58.428 [planetinfo.record]: cloud_fraction = 0.430000; 12:09:58.428 [planetinfo.record]: land_color = 0.601348, 0.66, 0.5775, 1; 12:09:58.428 [planetinfo.record]: land_fraction = 0.430000; 12:09:58.428 [planetinfo.record]: polar_cloud_color = 0.439734, 0.754883, 0.688406, 1; 12:09:58.428 [planetinfo.record]: polar_land_color = 0.91325, 0.934, 0.904813, 1; 12:09:58.428 [planetinfo.record]: polar_sea_color = 0.706955, 0.929297, 0.871974, 1; 12:09:58.428 [planetinfo.record]: sea_color = 0.0303802, 0.707031, 0.532582, 1; 12:09:58.428 [planetinfo.record]: rotation_speed = 0.002387 12:09:58.428 [planetinfo.record]: planet zpos = 828620.000000 12:09:58.428 [planetinfo.record]: sun_radius = 175230.534668 12:09:58.428 [planetinfo.record]: sun_vector = 0.203 -0.827 0.524 12:09:58.428 [planetinfo.record]: sun_distance = 1338540 12:09:58.429 [planetinfo.record]: corona_flare = 0.092311 12:09:58.429 [planetinfo.record]: corona_hues = 0.693932 12:09:58.429 [planetinfo.record]: sun_color = 0.59582, 0.69694, 0.803259, 1 12:09:58.429 [planetinfo.record]: corona_shimmer = 0.514904 12:09:58.429 [planetinfo.record]: station_vector = 0.614 0.774 0.157 12:09:58.429 [planetinfo.record]: station = coriolis 12:10:03.569 [PLANETINFO OVER]: Done 12:10:03.569 [PLANETINFO LOGGING]: 1 203 12:10:04.582 [planetinfo.record]: seed = 200 225 218 5 12:10:04.583 [planetinfo.record]: coordinates = 225 100 12:10:04.583 [planetinfo.record]: government = 1; 12:10:04.583 [planetinfo.record]: economy = 6; 12:10:04.583 [planetinfo.record]: techlevel = 3; 12:10:04.583 [planetinfo.record]: population = 20; 12:10:04.583 [planetinfo.record]: productivity = 3200; 12:10:04.583 [planetinfo.record]: name = "Cemabe"; 12:10:04.583 [planetinfo.record]: inhabitant = "Fierce Green Furry Feline"; 12:10:04.583 [planetinfo.record]: inhabitants = "Fierce Green Furry Felines"; 12:10:04.583 [planetinfo.record]: description = "This world is reasonably well known for its vast oceans but ravaged by vicious vicious wolfs."; 12:10:04.616 [planetinfo.record]: air_color = 0.40627, 0.8325, 0.738902, 1; 12:10:04.616 [planetinfo.record]: cloud_alpha = 1.000000; 12:10:04.616 [planetinfo.record]: cloud_color = 0.5, 0.196045, 0.03125, 1; 12:10:04.616 [planetinfo.record]: cloud_fraction = 0.180000; 12:10:04.616 [planetinfo.record]: land_color = 0.66, 0.0902344, 0.437435, 1; 12:10:04.616 [planetinfo.record]: land_fraction = 0.250000; 12:10:04.616 [planetinfo.record]: polar_cloud_color = 0.725, 0.449541, 0.300195, 1; 12:10:04.616 [planetinfo.record]: polar_land_color = 0.934, 0.732424, 0.855259, 1; 12:10:04.616 [planetinfo.record]: polar_sea_color = 0.938867, 0.888359, 0.878446, 1; 12:10:04.616 [planetinfo.record]: sea_color = 0.611328, 0.479777, 0.453959, 1; 12:10:04.616 [planetinfo.record]: rotation_speed = 0.003396 12:10:04.616 [planetinfo.record]: planet zpos = 432100.000000 12:10:04.616 [planetinfo.record]: sun_radius = 105842.610168 12:10:04.616 [planetinfo.record]: sun_vector = -0.369 -0.833 -0.413 12:10:04.616 [planetinfo.record]: sun_distance = 734570 12:10:04.616 [planetinfo.record]: corona_flare = 0.019038 12:10:04.617 [planetinfo.record]: corona_hues = 0.611084 12:10:04.617 [planetinfo.record]: sun_color = 0.655087, 0.602715, 0.434334, 1 12:10:04.617 [planetinfo.record]: corona_shimmer = 0.520135 12:10:04.617 [planetinfo.record]: station_vector = -0.062 0.699 0.712 12:10:04.617 [planetinfo.record]: station = coriolis 12:10:09.733 [PLANETINFO OVER]: Done 12:10:09.734 [PLANETINFO LOGGING]: 1 204 12:10:10.739 [planetinfo.record]: seed = 32 133 102 4 12:10:10.739 [planetinfo.record]: coordinates = 133 51 12:10:10.739 [planetinfo.record]: government = 4; 12:10:10.739 [planetinfo.record]: economy = 3; 12:10:10.739 [planetinfo.record]: techlevel = 7; 12:10:10.740 [planetinfo.record]: population = 36; 12:10:10.740 [planetinfo.record]: productivity = 16128; 12:10:10.740 [planetinfo.record]: name = "Zatebiso"; 12:10:10.740 [planetinfo.record]: inhabitant = "Human Colonial"; 12:10:10.740 [planetinfo.record]: inhabitants = "Human Colonials"; 12:10:10.740 [planetinfo.record]: description = "The world Zatebiso is most famous for the Zatebisoian spotted shrew."; 12:10:10.743 [planetinfo.record]: air_color = 0.662561, 0.863068, 0.829992, 1; 12:10:10.743 [planetinfo.record]: cloud_alpha = 1.000000; 12:10:10.743 [planetinfo.record]: cloud_color = 0.591797, 0.563804, 0.534004, 1; 12:10:10.743 [planetinfo.record]: cloud_fraction = 0.340000; 12:10:10.743 [planetinfo.record]: land_color = 0.607189, 0.66, 0.513047, 1; 12:10:10.743 [planetinfo.record]: land_fraction = 0.650000; 12:10:10.743 [planetinfo.record]: polar_cloud_color = 0.766309, 0.743654, 0.719537, 1; 12:10:10.743 [planetinfo.record]: polar_land_color = 0.915316, 0.934, 0.88201, 1; 12:10:10.744 [planetinfo.record]: polar_sea_color = 0.876717, 0.927148, 0.895235, 1; 12:10:10.744 [planetinfo.record]: sea_color = 0.570007, 0.728516, 0.628209, 1; 12:10:10.744 [planetinfo.record]: rotation_speed = 0.003225 12:10:10.744 [planetinfo.record]: planet zpos = 476760.000000 12:10:10.744 [planetinfo.record]: sun_radius = 125717.296600 12:10:10.744 [planetinfo.record]: sun_vector = -0.249 -0.513 -0.822 12:10:10.744 [planetinfo.record]: sun_distance = 834330 12:10:10.744 [planetinfo.record]: corona_flare = 0.008206 12:10:10.744 [planetinfo.record]: corona_hues = 0.946465 12:10:10.744 [planetinfo.record]: sun_color = 0.8104, 0.388042, 0.244538, 1 12:10:10.744 [planetinfo.record]: corona_shimmer = 0.324867 12:10:10.744 [planetinfo.record]: station_vector = -0.679 0.275 0.680 12:10:10.744 [planetinfo.record]: station = coriolis 12:10:14.642 [PLANETINFO OVER]: Done 12:10:14.643 [PLANETINFO LOGGING]: 1 205 12:10:15.662 [planetinfo.record]: seed = 224 7 90 11 12:10:15.662 [planetinfo.record]: coordinates = 7 70 12:10:15.662 [planetinfo.record]: government = 4; 12:10:15.662 [planetinfo.record]: economy = 6; 12:10:15.662 [planetinfo.record]: techlevel = 6; 12:10:15.662 [planetinfo.record]: population = 35; 12:10:15.662 [planetinfo.record]: productivity = 8960; 12:10:15.662 [planetinfo.record]: name = "Maorin"; 12:10:15.662 [planetinfo.record]: inhabitant = "Human Colonial"; 12:10:15.662 [planetinfo.record]: inhabitants = "Human Colonials"; 12:10:15.662 [planetinfo.record]: description = "Maorin is very notable for its inhabitants’ ingrained silliness."; 12:10:15.666 [planetinfo.record]: air_color = 0.679192, 0.83157, 0.925506, 1; 12:10:15.667 [planetinfo.record]: cloud_alpha = 1.000000; 12:10:15.667 [planetinfo.record]: cloud_color = 0.624046, 0.779297, 0.648304, 1; 12:10:15.667 [planetinfo.record]: cloud_fraction = 0.530000; 12:10:15.667 [planetinfo.record]: land_color = 0.203674, 0.554688, 0.505326, 1; 12:10:15.667 [planetinfo.record]: land_fraction = 0.430000; 12:10:15.667 [planetinfo.record]: polar_cloud_color = 0.744763, 0.850684, 0.761313, 1; 12:10:15.668 [planetinfo.record]: polar_land_color = 0.795103, 0.944531, 0.923518, 1; 12:10:15.668 [planetinfo.record]: polar_sea_color = 0.873491, 0.924023, 0.910996, 1; 12:10:15.668 [planetinfo.record]: sea_color = 0.593567, 0.759766, 0.716918, 1; 12:10:15.668 [planetinfo.record]: rotation_speed = 0.004302 12:10:15.668 [planetinfo.record]: planet zpos = 845850.000000 12:10:15.668 [planetinfo.record]: sun_radius = 154446.957855 12:10:15.668 [planetinfo.record]: sun_vector = 0.189 0.515 0.836 12:10:15.668 [planetinfo.record]: sun_distance = 1127800 12:10:15.668 [planetinfo.record]: corona_flare = 0.074812 12:10:15.668 [planetinfo.record]: corona_hues = 0.950371 12:10:15.669 [planetinfo.record]: sun_color = 0.639314, 0.644789, 0.65802, 1 12:10:15.669 [planetinfo.record]: corona_shimmer = 0.273443 12:10:15.669 [planetinfo.record]: station_vector = 0.289 -0.956 0.040 12:10:15.669 [planetinfo.record]: station = coriolis 12:10:20.950 [PLANETINFO OVER]: Done 12:10:20.950 [PLANETINFO LOGGING]: 1 206 12:10:21.956 [planetinfo.record]: seed = 8 210 182 152 12:10:21.956 [planetinfo.record]: coordinates = 210 108 12:10:21.956 [planetinfo.record]: government = 1; 12:10:21.956 [planetinfo.record]: economy = 6; 12:10:21.956 [planetinfo.record]: techlevel = 4; 12:10:21.956 [planetinfo.record]: population = 24; 12:10:21.956 [planetinfo.record]: productivity = 3840; 12:10:21.956 [planetinfo.record]: name = "Edtixeen"; 12:10:21.956 [planetinfo.record]: inhabitant = "Black Feline"; 12:10:21.956 [planetinfo.record]: inhabitants = "Black Felines"; 12:10:21.956 [planetinfo.record]: description = "The world Edtixeen is reasonably noted for its inhabitants’ eccentric love for tourists and the Edtixeenian tree snake."; 12:10:21.983 [planetinfo.record]: air_color = 0.634151, 0.776745, 0.990545, 1; 12:10:21.983 [planetinfo.record]: cloud_alpha = 1.000000; 12:10:21.983 [planetinfo.record]: cloud_color = 0.50634, 0.974609, 0.886809, 1; 12:10:21.983 [planetinfo.record]: cloud_fraction = 0.330000; 12:10:21.983 [planetinfo.record]: land_color = 0.634766, 0.428963, 0.496492, 1; 12:10:21.983 [planetinfo.record]: land_fraction = 0.340000; 12:10:21.983 [planetinfo.record]: polar_cloud_color = 0.656727, 0.938574, 0.885728, 1; 12:10:21.983 [planetinfo.record]: polar_land_color = 0.936523, 0.860614, 0.885522, 1; 12:10:21.983 [planetinfo.record]: polar_sea_color = 0.943945, 0.911854, 0.8926, 1; 12:10:21.983 [planetinfo.record]: sea_color = 0.560547, 0.48432, 0.438584, 1; 12:10:21.984 [planetinfo.record]: rotation_speed = 0.001742 12:10:21.984 [planetinfo.record]: planet zpos = 659620.000000 12:10:21.984 [planetinfo.record]: sun_radius = 93931.248169 12:10:21.984 [planetinfo.record]: sun_vector = 0.060 0.535 0.843 12:10:21.986 [planetinfo.record]: sun_distance = 913320 12:10:21.987 [planetinfo.record]: corona_flare = 0.005769 12:10:21.987 [planetinfo.record]: corona_hues = 0.534027 12:10:21.988 [planetinfo.record]: sun_color = 0.735754, 0.622467, 0.404662, 1 12:10:21.988 [planetinfo.record]: corona_shimmer = 1.085069 12:10:21.988 [planetinfo.record]: station_vector = -0.022 -0.882 0.471 12:10:21.989 [planetinfo.record]: station = coriolis 12:10:27.054 [PLANETINFO OVER]: Done 12:10:27.055 [PLANETINFO LOGGING]: 1 207 12:10:28.063 [planetinfo.record]: seed = 216 178 250 204 12:10:28.063 [planetinfo.record]: coordinates = 178 66 12:10:28.063 [planetinfo.record]: government = 3; 12:10:28.063 [planetinfo.record]: economy = 2; 12:10:28.063 [planetinfo.record]: techlevel = 9; 12:10:28.063 [planetinfo.record]: population = 42; 12:10:28.063 [planetinfo.record]: productivity = 18816; 12:10:28.063 [planetinfo.record]: name = "Inxexeat"; 12:10:28.063 [planetinfo.record]: inhabitant = "Slimy Rodent"; 12:10:28.063 [planetinfo.record]: inhabitants = "Slimy Rodents"; 12:10:28.063 [planetinfo.record]: description = "The world Inxexeat is reasonably noted for its inhabitants’ eccentric shyness."; 12:10:28.072 [planetinfo.record]: air_color = 0.664873, 0.872377, 0.905994, 1; 12:10:28.072 [planetinfo.record]: cloud_alpha = 1.000000; 12:10:28.072 [planetinfo.record]: cloud_color = 0.65901, 0.720703, 0.577126, 1; 12:10:28.072 [planetinfo.record]: cloud_fraction = 0.140000; 12:10:28.079 [planetinfo.record]: land_color = 0.386719, 0.66, 0.525494, 1; 12:10:28.080 [planetinfo.record]: land_fraction = 0.550000; 12:10:28.080 [planetinfo.record]: polar_cloud_color = 0.780215, 0.824316, 0.721679, 1; 12:10:28.080 [planetinfo.record]: polar_land_color = 0.837316, 0.934, 0.886414, 1; 12:10:28.080 [planetinfo.record]: polar_sea_color = 0.908421, 0.933398, 0.880986, 1; 12:10:28.080 [planetinfo.record]: sea_color = 0.594725, 0.666016, 0.516422, 1; 12:10:28.080 [planetinfo.record]: rotation_speed = 0.002855 12:10:28.080 [planetinfo.record]: planet zpos = 727920.000000 12:10:28.080 [planetinfo.record]: sun_radius = 168063.631897 12:10:28.080 [planetinfo.record]: sun_vector = 0.007 -0.994 0.107 12:10:28.080 [planetinfo.record]: sun_distance = 1395180 12:10:28.080 [planetinfo.record]: corona_flare = 0.090683 12:10:28.080 [planetinfo.record]: corona_hues = 0.528717 12:10:28.080 [planetinfo.record]: sun_color = 0.702856, 0.514124, 0.441449, 1 12:10:28.080 [planetinfo.record]: corona_shimmer = 0.421264 12:10:28.080 [planetinfo.record]: station_vector = -0.419 0.892 0.170 12:10:28.080 [planetinfo.record]: station = coriolis 12:10:32.539 [PLANETINFO OVER]: Done 12:10:32.540 [PLANETINFO LOGGING]: 1 208 12:10:33.560 [planetinfo.record]: seed = 80 209 166 213 12:10:33.560 [planetinfo.record]: coordinates = 209 66 12:10:33.560 [planetinfo.record]: government = 2; 12:10:33.560 [planetinfo.record]: economy = 2; 12:10:33.560 [planetinfo.record]: techlevel = 7; 12:10:33.560 [planetinfo.record]: population = 33; 12:10:33.560 [planetinfo.record]: productivity = 12672; 12:10:33.560 [planetinfo.record]: name = "Laeser"; 12:10:33.560 [planetinfo.record]: inhabitant = "Bony Bird"; 12:10:33.560 [planetinfo.record]: inhabitants = "Bony Birds"; 12:10:33.560 [planetinfo.record]: description = "This planet is beset by dreadful earthquakes."; 12:10:33.584 [planetinfo.record]: air_color = 0.459204, 0.482035, 0.926156, 1; 12:10:33.585 [planetinfo.record]: cloud_alpha = 1.000000; 12:10:33.585 [planetinfo.record]: cloud_color = 0.0671387, 0.161982, 0.78125, 1; 12:10:33.585 [planetinfo.record]: cloud_fraction = 0.320000; 12:10:33.585 [planetinfo.record]: land_color = 0.539062, 0.459045, 0.494678, 1; 12:10:33.585 [planetinfo.record]: land_fraction = 0.470000; 12:10:33.585 [planetinfo.record]: polar_cloud_color = 0.365074, 0.429686, 0.851562, 1; 12:10:33.585 [planetinfo.record]: polar_land_color = 0.946094, 0.910985, 0.926619, 1; 12:10:33.585 [planetinfo.record]: polar_sea_color = 0.895786, 0.923047, 0.851835, 1; 12:10:33.586 [planetinfo.record]: sea_color = 0.678624, 0.769531, 0.532059, 1; 12:10:33.586 [planetinfo.record]: rotation_speed = 0.002707 12:10:33.586 [planetinfo.record]: planet zpos = 430500.000000 12:10:33.586 [planetinfo.record]: sun_radius = 97981.842041 12:10:33.586 [planetinfo.record]: sun_vector = -0.100 -0.995 0.005 12:10:33.586 [planetinfo.record]: sun_distance = 861000 12:10:33.586 [planetinfo.record]: corona_flare = 0.020418 12:10:33.586 [planetinfo.record]: corona_hues = 0.672096 12:10:33.586 [planetinfo.record]: sun_color = 0.671384, 0.645498, 0.565315, 1 12:10:33.586 [planetinfo.record]: corona_shimmer = 0.403930 12:10:33.587 [planetinfo.record]: station_vector = -0.323 -0.922 0.213 12:10:33.587 [planetinfo.record]: station = coriolis 12:10:38.268 [PLANETINFO OVER]: Done 12:10:38.269 [PLANETINFO LOGGING]: 1 209 12:10:39.271 [planetinfo.record]: seed = 176 78 186 199 12:10:39.271 [planetinfo.record]: coordinates = 78 144 12:10:39.271 [planetinfo.record]: government = 6; 12:10:39.271 [planetinfo.record]: economy = 0; 12:10:39.271 [planetinfo.record]: techlevel = 12; 12:10:39.271 [planetinfo.record]: population = 55; 12:10:39.271 [planetinfo.record]: productivity = 44000; 12:10:39.271 [planetinfo.record]: name = "Sobite"; 12:10:39.271 [planetinfo.record]: inhabitant = "Fierce Frog"; 12:10:39.271 [planetinfo.record]: inhabitants = "Fierce Frogs"; 12:10:39.272 [planetinfo.record]: description = "Sobite is most noted for the Sobiteian mountain lobstoid and the Sobiteian evil poet."; 12:10:39.308 [planetinfo.record]: air_color = 0.694097, 0.845159, 0.889084, 1; 12:10:39.308 [planetinfo.record]: cloud_alpha = 1.000000; 12:10:39.308 [planetinfo.record]: cloud_color = 0.642935, 0.669922, 0.630669, 1; 12:10:39.308 [planetinfo.record]: cloud_fraction = 0.360000; 12:10:39.308 [planetinfo.record]: land_color = 0.379226, 0.221719, 0.66, 1; 12:10:39.308 [planetinfo.record]: land_fraction = 0.460000; 12:10:39.308 [planetinfo.record]: polar_cloud_color = 0.781286, 0.801465, 0.772114, 1; 12:10:39.308 [planetinfo.record]: polar_land_color = 0.834666, 0.778941, 0.934, 1; 12:10:39.308 [planetinfo.record]: polar_sea_color = 0.938672, 0.926543, 0.885138, 1; 12:10:39.308 [planetinfo.record]: sea_color = 0.613281, 0.581584, 0.473376, 1; 12:10:39.308 [planetinfo.record]: rotation_speed = 0.003775 12:10:39.308 [planetinfo.record]: planet zpos = 656040.000000 12:10:39.308 [planetinfo.record]: sun_radius = 105116.098022 12:10:39.308 [planetinfo.record]: sun_vector = 0.495 -0.586 -0.642 12:10:39.308 [planetinfo.record]: sun_distance = 937200 12:10:39.308 [planetinfo.record]: corona_flare = 0.007559 12:10:39.308 [planetinfo.record]: corona_hues = 0.673996 12:10:39.309 [planetinfo.record]: sun_color = 0.655476, 0.691059, 0.691223, 1 12:10:39.309 [planetinfo.record]: corona_shimmer = 0.362306 12:10:39.309 [planetinfo.record]: station_vector = -0.493 0.440 0.750 12:10:39.309 [planetinfo.record]: station = dodecahedron 12:10:44.146 [PLANETINFO OVER]: Done 12:10:44.147 [PLANETINFO LOGGING]: 1 210 12:10:45.165 [planetinfo.record]: seed = 248 42 54 142 12:10:45.165 [planetinfo.record]: coordinates = 42 188 12:10:45.165 [planetinfo.record]: government = 7; 12:10:45.165 [planetinfo.record]: economy = 4; 12:10:45.165 [planetinfo.record]: techlevel = 9; 12:10:45.165 [planetinfo.record]: population = 48; 12:10:45.165 [planetinfo.record]: productivity = 25344; 12:10:45.165 [planetinfo.record]: name = "Reveabe"; 12:10:45.165 [planetinfo.record]: inhabitant = "Human Colonial"; 12:10:45.166 [planetinfo.record]: inhabitants = "Human Colonials"; 12:10:45.166 [planetinfo.record]: description = "This planet is a tedious place."; 12:10:45.174 [planetinfo.record]: air_color = 0.428296, 0.725349, 0.879979, 1; 12:10:45.174 [planetinfo.record]: cloud_alpha = 1.000000; 12:10:45.174 [planetinfo.record]: cloud_color = 0.0376511, 0.642578, 0.061281, 1; 12:10:45.174 [planetinfo.record]: cloud_fraction = 0.270000; 12:10:45.174 [planetinfo.record]: land_color = 0.322212, 0.330149, 0.525391, 1; 12:10:45.183 [planetinfo.record]: land_fraction = 0.650000; 12:10:45.183 [planetinfo.record]: polar_cloud_color = 0.324835, 0.78916, 0.342973, 1; 12:10:45.184 [planetinfo.record]: polar_land_color = 0.855861, 0.859439, 0.947461, 1; 12:10:45.184 [planetinfo.record]: polar_sea_color = 0.947656, 0.903669, 0.731102, 1; 12:10:45.184 [planetinfo.record]: sea_color = 0.523438, 0.426251, 0.0449829, 1; 12:10:45.184 [planetinfo.record]: rotation_speed = 0.001086 12:10:45.184 [planetinfo.record]: planet zpos = 773040.000000 12:10:45.184 [planetinfo.record]: sun_radius = 162817.382202 12:10:45.184 [planetinfo.record]: sun_vector = -0.835 0.516 0.189 12:10:45.184 [planetinfo.record]: sun_distance = 1288400 12:10:45.184 [planetinfo.record]: corona_flare = 0.023563 12:10:45.184 [planetinfo.record]: corona_hues = 0.827065 12:10:45.184 [planetinfo.record]: sun_color = 0.705621, 0.768913, 0.777438, 1 12:10:45.184 [planetinfo.record]: corona_shimmer = 0.755249 12:10:45.185 [planetinfo.record]: station_vector = -0.208 -0.446 0.870 12:10:45.185 [planetinfo.record]: station = coriolis 12:10:51.362 [PLANETINFO OVER]: Done 12:10:51.366 [PLANETINFO LOGGING]: 1 211 12:10:52.366 [planetinfo.record]: seed = 104 51 154 216 12:10:52.366 [planetinfo.record]: coordinates = 51 47 12:10:52.366 [planetinfo.record]: government = 5; 12:10:52.366 [planetinfo.record]: economy = 7; 12:10:52.366 [planetinfo.record]: techlevel = 6; 12:10:52.366 [planetinfo.record]: population = 37; 12:10:52.366 [planetinfo.record]: productivity = 7992; 12:10:52.366 [planetinfo.record]: name = "Edanso"; 12:10:52.366 [planetinfo.record]: inhabitant = "Fat Bird"; 12:10:52.366 [planetinfo.record]: inhabitants = "Fat Birds"; 12:10:52.366 [planetinfo.record]: description = "Edanso is mildly well known for its hoopy night life and Edansoian It water."; 12:10:52.387 [planetinfo.record]: air_color = 0.615412, 0.881852, 0.959326, 1; 12:10:52.387 [planetinfo.record]: cloud_alpha = 1.000000; 12:10:52.387 [planetinfo.record]: cloud_color = 0.592257, 0.880859, 0.461075, 1; 12:10:52.387 [planetinfo.record]: cloud_fraction = 0.290000; 12:10:52.388 [planetinfo.record]: land_color = 0.189794, 0.136641, 0.66, 1; 12:10:52.388 [planetinfo.record]: land_fraction = 0.360000; 12:10:52.388 [planetinfo.record]: polar_cloud_color = 0.712831, 0.896387, 0.629397, 1; 12:10:52.388 [planetinfo.record]: polar_land_color = 0.767647, 0.748842, 0.934, 1; 12:10:52.388 [planetinfo.record]: polar_sea_color = 0.930206, 0.937695, 0.887239, 1; 12:10:52.388 [planetinfo.record]: sea_color = 0.603141, 0.623047, 0.488946, 1; 12:10:52.388 [planetinfo.record]: rotation_speed = 0.002216 12:10:52.388 [planetinfo.record]: planet zpos = 638950.000000 12:10:52.388 [planetinfo.record]: sun_radius = 136586.692047 12:10:52.388 [planetinfo.record]: sun_vector = 0.966 0.020 -0.256 12:10:52.388 [planetinfo.record]: sun_distance = 884700 12:10:52.388 [planetinfo.record]: corona_flare = 0.009154 12:10:52.388 [planetinfo.record]: corona_hues = 0.582909 12:10:52.388 [planetinfo.record]: sun_color = 0.677086, 0.703889, 0.712027, 1 12:10:52.389 [planetinfo.record]: corona_shimmer = 0.446979 12:10:52.389 [planetinfo.record]: station_vector = -0.489 0.747 0.451 12:10:52.389 [planetinfo.record]: station = coriolis 12:10:57.688 [PLANETINFO OVER]: Done 12:10:57.689 [PLANETINFO LOGGING]: 1 212 12:10:58.693 [planetinfo.record]: seed = 0 91 102 221 12:10:58.693 [planetinfo.record]: coordinates = 91 71 12:10:58.693 [planetinfo.record]: government = 0; 12:10:58.693 [planetinfo.record]: economy = 7; 12:10:58.693 [planetinfo.record]: techlevel = 3; 12:10:58.693 [planetinfo.record]: population = 20; 12:10:58.693 [planetinfo.record]: productivity = 1920; 12:10:58.693 [planetinfo.record]: name = "Isoned"; 12:10:58.693 [planetinfo.record]: inhabitant = "Human Colonial"; 12:10:58.693 [planetinfo.record]: inhabitants = "Human Colonials"; 12:10:58.693 [planetinfo.record]: description = "The planet Isoned is scourged by killer edible arts graduates."; 12:10:58.708 [planetinfo.record]: air_color = 0.593708, 0.919787, 0.962578, 1; 12:10:58.708 [planetinfo.record]: cloud_alpha = 1.000000; 12:10:58.708 [planetinfo.record]: cloud_color = 0.714337, 0.890625, 0.400085, 1; 12:10:58.708 [planetinfo.record]: cloud_fraction = 0.440000; 12:10:58.708 [planetinfo.record]: land_color = 0.506779, 0.414185, 0.509766, 1; 12:10:58.708 [planetinfo.record]: land_fraction = 0.320000; 12:10:58.708 [planetinfo.record]: polar_cloud_color = 0.789345, 0.900781, 0.590698, 1; 12:10:58.708 [planetinfo.record]: polar_land_color = 0.947633, 0.904538, 0.949023, 1; 12:10:58.708 [planetinfo.record]: polar_sea_color = 0.915174, 0.928125, 0.874649, 1; 12:10:58.708 [planetinfo.record]: sea_color = 0.678632, 0.71875, 0.553101, 1; 12:10:58.708 [planetinfo.record]: rotation_speed = 0.002068 12:10:58.708 [planetinfo.record]: planet zpos = 623500.000000 12:10:58.708 [planetinfo.record]: sun_radius = 134465.020752 12:10:58.708 [planetinfo.record]: sun_vector = -0.427 0.094 0.899 12:10:58.715 [planetinfo.record]: sun_distance = 1247000 12:10:58.715 [planetinfo.record]: corona_flare = 0.061465 12:10:58.716 [planetinfo.record]: corona_hues = 0.543091 12:10:58.716 [planetinfo.record]: sun_color = 0.727402, 0.546152, 0.291658, 1 12:10:58.716 [planetinfo.record]: corona_shimmer = 0.301080 12:10:58.716 [planetinfo.record]: station_vector = 0.295 0.906 0.302 12:10:58.716 [planetinfo.record]: station = coriolis 12:11:05.011 [PLANETINFO OVER]: Done 12:11:05.012 [PLANETINFO LOGGING]: 1 213 12:11:06.028 [planetinfo.record]: seed = 0 21 154 76 12:11:06.028 [planetinfo.record]: coordinates = 21 184 12:11:06.028 [planetinfo.record]: government = 0; 12:11:06.028 [planetinfo.record]: economy = 2; 12:11:06.028 [planetinfo.record]: techlevel = 6; 12:11:06.028 [planetinfo.record]: population = 27; 12:11:06.028 [planetinfo.record]: productivity = 6912; 12:11:06.028 [planetinfo.record]: name = "Inoran"; 12:11:06.028 [planetinfo.record]: inhabitant = "Yellow Furry Humanoid"; 12:11:06.028 [planetinfo.record]: inhabitants = "Yellow Furry Humanoids"; 12:11:06.028 [planetinfo.record]: description = "The planet Inoran is scourged by killer edible talking treeoids."; 12:11:06.040 [planetinfo.record]: air_color = 0.493388, 0.992496, 0.875103, 1; 12:11:06.040 [planetinfo.record]: cloud_alpha = 1.000000; 12:11:06.040 [planetinfo.record]: cloud_color = 0.980469, 0.359986, 0.088089, 1; 12:11:06.040 [planetinfo.record]: cloud_fraction = 0.450000; 12:11:06.040 [planetinfo.record]: land_color = 0.592203, 0.66, 0.404766, 1; 12:11:06.040 [planetinfo.record]: land_fraction = 0.250000; 12:11:06.040 [planetinfo.record]: polar_cloud_color = 0.941211, 0.568937, 0.405805, 1; 12:11:06.040 [planetinfo.record]: polar_land_color = 0.910014, 0.934, 0.843701, 1; 12:11:06.040 [planetinfo.record]: polar_sea_color = 0.944922, 0.889648, 0.89483, 1; 12:11:06.040 [planetinfo.record]: sea_color = 0.550781, 0.421907, 0.433989, 1; 12:11:06.040 [planetinfo.record]: rotation_speed = 0.003085 12:11:06.040 [planetinfo.record]: planet zpos = 649990.000000 12:11:06.040 [planetinfo.record]: sun_radius = 151065.583038 12:11:06.040 [planetinfo.record]: sun_vector = 0.175 -0.749 -0.639 12:11:06.040 [planetinfo.record]: sun_distance = 1181800 12:11:06.040 [planetinfo.record]: corona_flare = 0.020757 12:11:06.040 [planetinfo.record]: corona_hues = 0.584541 12:11:06.040 [planetinfo.record]: sun_color = 0.376891, 0.545531, 0.662024, 1 12:11:06.041 [planetinfo.record]: corona_shimmer = 0.457788 12:11:06.041 [planetinfo.record]: station_vector = -0.951 -0.309 0.009 12:11:06.041 [planetinfo.record]: station = coriolis 12:11:10.763 [PLANETINFO OVER]: Done 12:11:10.766 [PLANETINFO LOGGING]: 1 214 12:11:11.786 [planetinfo.record]: seed = 104 225 54 118 12:11:11.786 [planetinfo.record]: coordinates = 225 123 12:11:11.786 [planetinfo.record]: government = 5; 12:11:11.786 [planetinfo.record]: economy = 3; 12:11:11.786 [planetinfo.record]: techlevel = 8; 12:11:11.786 [planetinfo.record]: population = 41; 12:11:11.786 [planetinfo.record]: productivity = 20664; 12:11:11.786 [planetinfo.record]: name = "Veenar"; 12:11:11.786 [planetinfo.record]: inhabitant = "Human Colonial"; 12:11:11.786 [planetinfo.record]: inhabitants = "Human Colonials"; 12:11:11.786 [planetinfo.record]: description = "The planet Veenar is most famous for the Veenarian evil arts graduate and its fabulous Be juice."; 12:11:11.791 [planetinfo.record]: air_color = 0.692205, 0.702199, 0.992496, 1; 12:11:11.791 [planetinfo.record]: cloud_alpha = 1.000000; 12:11:11.791 [planetinfo.record]: cloud_color = 0.677902, 0.703904, 0.980469, 1; 12:11:11.792 [planetinfo.record]: cloud_fraction = 0.500000; 12:11:11.792 [planetinfo.record]: land_color = 0.66, 0.634622, 0.497578, 1; 12:11:11.792 [planetinfo.record]: land_fraction = 0.640000; 12:11:11.792 [planetinfo.record]: polar_cloud_color = 0.759679, 0.775279, 0.941211, 1; 12:11:11.792 [planetinfo.record]: polar_land_color = 0.934, 0.925021, 0.876537, 1; 12:11:11.792 [planetinfo.record]: polar_sea_color = 0.869077, 0.877652, 0.916797, 1; 12:11:11.792 [planetinfo.record]: sea_color = 0.6588, 0.689927, 0.832031, 1; 12:11:11.792 [planetinfo.record]: rotation_speed = 0.000478 12:11:11.792 [planetinfo.record]: planet zpos = 411930.000000 12:11:11.792 [planetinfo.record]: sun_radius = 114104.436798 12:11:11.793 [planetinfo.record]: sun_vector = -0.308 0.060 -0.950 12:11:11.793 [planetinfo.record]: sun_distance = 1098480 12:11:11.793 [planetinfo.record]: corona_flare = 0.015729 12:11:11.793 [planetinfo.record]: corona_hues = 0.919739 12:11:11.793 [planetinfo.record]: sun_color = 0.729706, 0.631292, 0.59155, 1 12:11:11.793 [planetinfo.record]: corona_shimmer = 0.526250 12:11:11.794 [planetinfo.record]: station_vector = 0.043 -0.989 0.139 12:11:11.794 [planetinfo.record]: station = coriolis 12:11:16.593 [PLANETINFO OVER]: Done 12:11:16.594 [PLANETINFO LOGGING]: 1 215 12:11:17.610 [planetinfo.record]: seed = 120 115 186 112 12:11:17.610 [planetinfo.record]: coordinates = 115 42 12:11:17.610 [planetinfo.record]: government = 7; 12:11:17.610 [planetinfo.record]: economy = 2; 12:11:17.610 [planetinfo.record]: techlevel = 12; 12:11:17.610 [planetinfo.record]: population = 58; 12:11:17.610 [planetinfo.record]: productivity = 40832; 12:11:17.610 [planetinfo.record]: name = "Erreenen"; 12:11:17.610 [planetinfo.record]: inhabitant = "Blue Bug-Eyed Frog"; 12:11:17.610 [planetinfo.record]: inhabitants = "Blue Bug-Eyed Frogs"; 12:11:17.611 [planetinfo.record]: description = "This world is mildly well known for its hoopy night life and Erreenenian Enleno brandy."; 12:11:17.636 [planetinfo.record]: air_color = 0.75386, 0.648033, 0.837053, 1; 12:11:17.636 [planetinfo.record]: cloud_alpha = 1.000000; 12:11:17.636 [planetinfo.record]: cloud_color = 0.513672, 0.473541, 0.485769, 1; 12:11:17.636 [planetinfo.record]: cloud_fraction = 0.130000; 12:11:17.636 [planetinfo.record]: land_color = 0.132179, 0.615234, 0.456732, 1; 12:11:17.636 [planetinfo.record]: land_fraction = 0.600000; 12:11:17.636 [planetinfo.record]: polar_cloud_color = 0.731152, 0.695452, 0.706329, 1; 12:11:17.636 [planetinfo.record]: polar_land_color = 0.754264, 0.938477, 0.878032, 1; 12:11:17.636 [planetinfo.record]: polar_sea_color = 0.936914, 0.884676, 0.879272, 1; 12:11:17.636 [planetinfo.record]: sea_color = 0.630859, 0.490164, 0.475609, 1; 12:11:17.636 [planetinfo.record]: rotation_speed = 0.001553 12:11:17.636 [planetinfo.record]: planet zpos = 351720.000000 12:11:17.636 [planetinfo.record]: sun_radius = 66019.952087 12:11:17.636 [planetinfo.record]: sun_vector = -0.481 0.832 -0.277 12:11:17.636 [planetinfo.record]: sun_distance = 644820 12:11:17.636 [planetinfo.record]: corona_flare = 0.017151 12:11:17.637 [planetinfo.record]: corona_hues = 0.846985 12:11:17.637 [planetinfo.record]: sun_color = 0.585279, 0.625036, 0.659067, 1 12:11:17.637 [planetinfo.record]: corona_shimmer = 0.505562 12:11:17.637 [planetinfo.record]: station_vector = 0.535 0.788 0.305 12:11:17.637 [planetinfo.record]: station = icosahedron 12:11:22.425 [PLANETINFO OVER]: Done 12:11:22.426 [PLANETINFO LOGGING]: 1 216 12:11:23.445 [planetinfo.record]: seed = 48 114 166 115 12:11:23.445 [planetinfo.record]: coordinates = 114 242 12:11:23.445 [planetinfo.record]: government = 6; 12:11:23.445 [planetinfo.record]: economy = 2; 12:11:23.445 [planetinfo.record]: techlevel = 10; 12:11:23.445 [planetinfo.record]: population = 49; 12:11:23.445 [planetinfo.record]: productivity = 31360; 12:11:23.445 [planetinfo.record]: name = "Beedriar"; 12:11:23.445 [planetinfo.record]: inhabitant = "Blue Slimy Lobster"; 12:11:23.445 [planetinfo.record]: inhabitants = "Blue Slimy Lobsters"; 12:11:23.445 [planetinfo.record]: description = "This world is mildly well known for its hoopy night life and Beedriarian lethal brandy."; 12:11:23.449 [planetinfo.record]: air_color = 0.735582, 0.638234, 0.837053, 1; 12:11:23.449 [planetinfo.record]: cloud_alpha = 1.000000; 12:11:23.449 [planetinfo.record]: cloud_color = 0.513672, 0.455482, 0.485486, 1; 12:11:23.449 [planetinfo.record]: cloud_fraction = 0.350000; 12:11:23.449 [planetinfo.record]: land_color = 0.580078, 0.562322, 0.541557, 1; 12:11:23.449 [planetinfo.record]: land_fraction = 0.620000; 12:11:23.449 [planetinfo.record]: polar_cloud_color = 0.731152, 0.679386, 0.706078, 1; 12:11:23.450 [planetinfo.record]: polar_land_color = 0.941992, 0.934784, 0.926354, 1; 12:11:23.450 [planetinfo.record]: polar_sea_color = 0.887394, 0.919336, 0.830455, 1; 12:11:23.450 [planetinfo.record]: sea_color = 0.694536, 0.806641, 0.494698, 1; 12:11:23.450 [planetinfo.record]: rotation_speed = 0.001398 12:11:23.450 [planetinfo.record]: planet zpos = 480740.000000 12:11:23.450 [planetinfo.record]: sun_radius = 93641.738281 12:11:23.450 [planetinfo.record]: sun_vector = 0.585 0.520 -0.622 12:11:23.450 [planetinfo.record]: sun_distance = 591680 12:11:23.450 [planetinfo.record]: corona_flare = 0.030559 12:11:23.450 [planetinfo.record]: corona_hues = 0.531693 12:11:23.450 [planetinfo.record]: sun_color = 0.752063, 0.66735, 0.594655, 1 12:11:23.451 [planetinfo.record]: corona_shimmer = 0.346345 12:11:23.451 [planetinfo.record]: station_vector = -0.724 0.571 0.388 12:11:23.451 [planetinfo.record]: station = coriolis 12:11:28.269 [PLANETINFO OVER]: Done 12:11:28.270 [PLANETINFO LOGGING]: 1 217 12:11:29.273 [planetinfo.record]: seed = 208 10 250 161 12:11:29.273 [planetinfo.record]: coordinates = 10 190 12:11:29.273 [planetinfo.record]: government = 2; 12:11:29.273 [planetinfo.record]: economy = 6; 12:11:29.273 [planetinfo.record]: techlevel = 4; 12:11:29.273 [planetinfo.record]: population = 25; 12:11:29.273 [planetinfo.record]: productivity = 4800; 12:11:29.273 [planetinfo.record]: name = "Lemaed"; 12:11:29.273 [planetinfo.record]: inhabitant = "Large Harmless Fat Humanoid"; 12:11:29.273 [planetinfo.record]: inhabitants = "Large Harmless Fat Humanoids"; 12:11:29.273 [planetinfo.record]: description = "This planet is mildly fabled for its inhabitants’ eccentric shyness but cursed by unpredictable earthquakes."; 12:11:29.289 [planetinfo.record]: air_color = 0.668291, 0.877975, 0.891686, 1; 12:11:29.289 [planetinfo.record]: cloud_alpha = 1.000000; 12:11:29.289 [planetinfo.record]: cloud_color = 0.657569, 0.677734, 0.574486, 1; 12:11:29.289 [planetinfo.record]: cloud_fraction = 0.470000; 12:11:29.290 [planetinfo.record]: land_color = 0.560501, 0.66, 0.492422, 1; 12:11:29.290 [planetinfo.record]: land_fraction = 0.520000; 12:11:29.290 [planetinfo.record]: polar_cloud_color = 0.790011, 0.80498, 0.728334, 1; 12:11:29.290 [planetinfo.record]: polar_land_color = 0.898798, 0.934, 0.874713, 1; 12:11:29.290 [planetinfo.record]: polar_sea_color = 0.921572, 0.877162, 0.946484, 1; 12:11:29.290 [planetinfo.record]: sea_color = 0.478812, 0.378372, 0.535156, 1; 12:11:29.290 [planetinfo.record]: rotation_speed = 0.002497 12:11:29.290 [planetinfo.record]: planet zpos = 369840.000000 12:11:29.290 [planetinfo.record]: sun_radius = 67446.966553 12:11:29.290 [planetinfo.record]: sun_vector = 0.278 0.894 0.352 12:11:29.290 [planetinfo.record]: sun_distance = 585580 12:11:29.290 [planetinfo.record]: corona_flare = 0.032404 12:11:29.290 [planetinfo.record]: corona_hues = 0.815834 12:11:29.290 [planetinfo.record]: sun_color = 0.653179, 0.653369, 0.653583, 1 12:11:29.291 [planetinfo.record]: corona_shimmer = 0.386452 12:11:29.291 [planetinfo.record]: station_vector = -0.640 -0.757 0.131 12:11:29.291 [planetinfo.record]: station = coriolis 12:11:34.149 [PLANETINFO OVER]: Done 12:11:34.150 [PLANETINFO LOGGING]: 1 218 12:11:35.156 [planetinfo.record]: seed = 88 37 182 168 12:11:35.156 [planetinfo.record]: coordinates = 37 24 12:11:35.156 [planetinfo.record]: government = 3; 12:11:35.156 [planetinfo.record]: economy = 0; 12:11:35.156 [planetinfo.record]: techlevel = 10; 12:11:35.156 [planetinfo.record]: population = 44; 12:11:35.156 [planetinfo.record]: productivity = 24640; 12:11:35.156 [planetinfo.record]: name = "Usbira"; 12:11:35.156 [planetinfo.record]: inhabitant = "Small Harmless Furry Humanoid"; 12:11:35.156 [planetinfo.record]: inhabitants = "Small Harmless Furry Humanoids"; 12:11:35.156 [planetinfo.record]: description = "This world is a tedious little planet."; 12:11:35.196 [planetinfo.record]: air_color = 0.698532, 0.614512, 0.844857, 1; 12:11:35.196 [planetinfo.record]: cloud_alpha = 1.000000; 12:11:35.196 [planetinfo.record]: cloud_color = 0.537109, 0.419617, 0.524259, 1; 12:11:35.196 [planetinfo.record]: cloud_fraction = 0.180000; 12:11:35.196 [planetinfo.record]: land_color = 0.461484, 0.66, 0.262969, 1; 12:11:35.196 [planetinfo.record]: land_fraction = 0.380000; 12:11:35.196 [planetinfo.record]: polar_cloud_color = 0.741699, 0.640295, 0.730608, 1; 12:11:35.196 [planetinfo.record]: polar_land_color = 0.863768, 0.934, 0.793535, 1; 12:11:35.196 [planetinfo.record]: polar_sea_color = 0.905774, 0.933203, 0.880802, 1; 12:11:35.197 [planetinfo.record]: sea_color = 0.589436, 0.667969, 0.517937, 1; 12:11:35.197 [planetinfo.record]: rotation_speed = 0.004848 12:11:35.197 [planetinfo.record]: planet zpos = 735150.000000 12:11:35.197 [planetinfo.record]: sun_radius = 104650.289612 12:11:35.197 [planetinfo.record]: sun_vector = -0.171 -0.800 0.575 12:11:35.199 [planetinfo.record]: sun_distance = 980200 12:11:35.199 [planetinfo.record]: corona_flare = 0.048178 12:11:35.199 [planetinfo.record]: corona_hues = 0.697182 12:11:35.199 [planetinfo.record]: sun_color = 0.706467, 0.665051, 0.663754, 1 12:11:35.199 [planetinfo.record]: corona_shimmer = 0.318795 12:11:35.200 [planetinfo.record]: station_vector = -0.843 0.028 0.537 12:11:35.200 [planetinfo.record]: station = coriolis 12:11:40.415 [PLANETINFO OVER]: Done 12:11:40.416 [PLANETINFO LOGGING]: 1 219 12:11:41.420 [planetinfo.record]: seed = 8 67 90 221 12:11:41.420 [planetinfo.record]: coordinates = 67 180 12:11:41.420 [planetinfo.record]: government = 1; 12:11:41.420 [planetinfo.record]: economy = 6; 12:11:41.420 [planetinfo.record]: techlevel = 5; 12:11:41.420 [planetinfo.record]: population = 28; 12:11:41.420 [planetinfo.record]: productivity = 4480; 12:11:41.420 [planetinfo.record]: name = "Israra"; 12:11:41.420 [planetinfo.record]: inhabitant = "Human Colonial"; 12:11:41.420 [planetinfo.record]: inhabitants = "Human Colonials"; 12:11:41.420 [planetinfo.record]: description = "Israra is most noted for its inhabitants’ exceptional loathing of food blenders and its exciting sit coms."; 12:11:41.440 [planetinfo.record]: air_color = 0.893047, 0.782821, 0.994447, 1; 12:11:41.440 [planetinfo.record]: cloud_alpha = 1.000000; 12:11:41.440 [planetinfo.record]: cloud_color = 0.986328, 0.9478, 0.964054, 1; 12:11:41.440 [planetinfo.record]: cloud_fraction = 0.220000; 12:11:41.440 [planetinfo.record]: land_color = 0.0360938, 0.66, 0.645377, 1; 12:11:41.440 [planetinfo.record]: land_fraction = 0.560000; 12:11:41.440 [planetinfo.record]: polar_cloud_color = 0.943848, 0.920805, 0.930526, 1; 12:11:41.440 [planetinfo.record]: polar_land_color = 0.71327, 0.934, 0.928827, 1; 12:11:41.440 [planetinfo.record]: polar_sea_color = 0.941016, 0.921816, 0.888727, 1; 12:11:41.440 [planetinfo.record]: sea_color = 0.589844, 0.541705, 0.458742, 1; 12:11:41.440 [planetinfo.record]: rotation_speed = 0.000970 12:11:41.440 [planetinfo.record]: planet zpos = 621100.000000 12:11:41.440 [planetinfo.record]: sun_radius = 132940.002899 12:11:41.440 [planetinfo.record]: sun_vector = -0.730 -0.670 -0.139 12:11:41.440 [planetinfo.record]: sun_distance = 1180090 12:11:41.440 [planetinfo.record]: corona_flare = 0.032309 12:11:41.440 [planetinfo.record]: corona_hues = 0.689453 12:11:41.440 [planetinfo.record]: sun_color = 0.667288, 0.607977, 0.392702, 1 12:11:41.440 [planetinfo.record]: corona_shimmer = 0.268309 12:11:41.441 [planetinfo.record]: station_vector = -0.597 -0.745 0.300 12:11:41.441 [planetinfo.record]: station = coriolis 12:11:45.580 [PLANETINFO OVER]: Done 12:11:45.581 [PLANETINFO LOGGING]: 1 220 12:11:46.599 [planetinfo.record]: seed = 224 166 102 112 12:11:46.599 [planetinfo.record]: coordinates = 166 244 12:11:46.599 [planetinfo.record]: government = 4; 12:11:46.599 [planetinfo.record]: economy = 4; 12:11:46.599 [planetinfo.record]: techlevel = 7; 12:11:46.599 [planetinfo.record]: population = 37; 12:11:46.599 [planetinfo.record]: productivity = 14208; 12:11:46.599 [planetinfo.record]: name = "Eringe"; 12:11:46.599 [planetinfo.record]: inhabitant = "Human Colonial"; 12:11:46.599 [planetinfo.record]: inhabitants = "Human Colonials"; 12:11:46.599 [planetinfo.record]: description = "Eringe is well known for its inhabitants’ unusual silliness but cursed by deadly civil war."; 12:11:46.604 [planetinfo.record]: air_color = 0.673186, 0.875426, 0.872054, 1; 12:11:46.605 [planetinfo.record]: cloud_alpha = 1.000000; 12:11:46.605 [planetinfo.record]: cloud_color = 0.628906, 0.626603, 0.569946, 1; 12:11:46.605 [planetinfo.record]: cloud_fraction = 0.120000; 12:11:46.605 [planetinfo.record]: land_color = 0.593875, 0.533672, 0.66, 1; 12:11:46.605 [planetinfo.record]: land_fraction = 0.470000; 12:11:46.605 [planetinfo.record]: polar_cloud_color = 0.783008, 0.781216, 0.737128, 1; 12:11:46.605 [planetinfo.record]: polar_land_color = 0.910606, 0.889307, 0.934, 1; 12:11:46.605 [planetinfo.record]: polar_sea_color = 0.939453, 0.92885, 0.887251, 1; 12:11:46.605 [planetinfo.record]: sea_color = 0.605469, 0.578133, 0.470894, 1; 12:11:46.605 [planetinfo.record]: rotation_speed = 0.000779 12:11:46.605 [planetinfo.record]: planet zpos = 447300.000000 12:11:46.605 [planetinfo.record]: sun_radius = 69046.113281 12:11:46.605 [planetinfo.record]: sun_vector = -0.508 0.131 -0.851 12:11:46.605 [planetinfo.record]: sun_distance = 566580 12:11:46.605 [planetinfo.record]: corona_flare = 0.075580 12:11:46.606 [planetinfo.record]: corona_hues = 0.607132 12:11:46.606 [planetinfo.record]: sun_color = 0.307963, 0.608666, 0.673306, 1 12:11:46.606 [planetinfo.record]: corona_shimmer = 0.375066 12:11:46.606 [planetinfo.record]: station_vector = 0.739 0.647 0.187 12:11:46.606 [planetinfo.record]: station = coriolis 12:11:50.671 [PLANETINFO OVER]: Done 12:11:50.672 [PLANETINFO LOGGING]: 1 221 12:11:51.689 [planetinfo.record]: seed = 32 160 218 207 12:11:51.689 [planetinfo.record]: coordinates = 160 35 12:11:51.689 [planetinfo.record]: government = 4; 12:11:51.689 [planetinfo.record]: economy = 3; 12:11:51.689 [planetinfo.record]: techlevel = 6; 12:11:51.689 [planetinfo.record]: population = 32; 12:11:51.689 [planetinfo.record]: productivity = 14336; 12:11:51.689 [planetinfo.record]: name = "Abege"; 12:11:51.689 [planetinfo.record]: inhabitant = "Bony Feline"; 12:11:51.689 [planetinfo.record]: inhabitants = "Bony Felines"; 12:11:51.689 [planetinfo.record]: description = "The world Abege is most well known for its vast dense forests."; 12:11:51.720 [planetinfo.record]: air_color = 0.720742, 0.787944, 0.929408, 1; 12:11:51.720 [planetinfo.record]: cloud_alpha = 1.000000; 12:11:51.720 [planetinfo.record]: cloud_color = 0.732307, 0.788264, 0.791016, 1; 12:11:51.720 [planetinfo.record]: cloud_fraction = 0.450000; 12:11:51.720 [planetinfo.record]: land_color = 0.66, 0.645095, 0.278438, 1; 12:11:51.720 [planetinfo.record]: land_fraction = 0.400000; 12:11:51.721 [planetinfo.record]: polar_cloud_color = 0.816252, 0.854096, 0.855957, 1; 12:11:51.721 [planetinfo.record]: polar_land_color = 0.934, 0.928727, 0.799008, 1; 12:11:51.721 [planetinfo.record]: polar_sea_color = 0.883745, 0.930664, 0.879768, 1; 12:11:51.721 [planetinfo.record]: sea_color = 0.553536, 0.693359, 0.541687, 1; 12:11:51.721 [planetinfo.record]: rotation_speed = 0.001916 12:11:51.721 [planetinfo.record]: planet zpos = 954240.000000 12:11:51.721 [planetinfo.record]: sun_radius = 153249.755859 12:11:51.721 [planetinfo.record]: sun_vector = -0.513 -0.858 -0.023 12:11:51.721 [planetinfo.record]: sun_distance = 1363200 12:11:51.721 [planetinfo.record]: corona_flare = 0.063399 12:11:51.721 [planetinfo.record]: corona_hues = 0.609688 12:11:51.721 [planetinfo.record]: sun_color = 0.341103, 0.60661, 0.67796, 1 12:11:51.722 [planetinfo.record]: corona_shimmer = 0.552897 12:11:51.722 [planetinfo.record]: station_vector = -0.860 -0.117 0.497 12:11:51.722 [planetinfo.record]: station = coriolis 12:11:56.638 [PLANETINFO OVER]: Done 12:11:56.640 [PLANETINFO LOGGING]: 1 222 12:11:57.649 [planetinfo.record]: seed = 200 102 182 253 12:11:57.649 [planetinfo.record]: coordinates = 102 3 12:11:57.649 [planetinfo.record]: government = 1; 12:11:57.649 [planetinfo.record]: economy = 3; 12:11:57.649 [planetinfo.record]: techlevel = 7; 12:11:57.649 [planetinfo.record]: population = 33; 12:11:57.649 [planetinfo.record]: productivity = 9240; 12:11:57.649 [planetinfo.record]: name = "Issoinen"; 12:11:57.649 [planetinfo.record]: inhabitant = "Furry Feline"; 12:11:57.649 [planetinfo.record]: inhabitants = "Furry Felines"; 12:11:57.649 [planetinfo.record]: description = "The planet Issoinen is very noted for its ancient mountains but ravaged by occasional earthquakes."; 12:11:57.662 [planetinfo.record]: air_color = 0.664246, 0.839654, 0.784707, 1; 12:11:57.662 [planetinfo.record]: cloud_alpha = 1.000000; 12:11:57.662 [planetinfo.record]: cloud_color = 0.521484, 0.508228, 0.507225, 1; 12:11:57.662 [planetinfo.record]: cloud_fraction = 0.460000; 12:11:57.662 [planetinfo.record]: land_color = 0.66, 0.340313, 0.475181, 1; 12:11:57.662 [planetinfo.record]: land_fraction = 0.280000; 12:11:57.662 [planetinfo.record]: polar_cloud_color = 0.734668, 0.722995, 0.722113, 1; 12:11:57.662 [planetinfo.record]: polar_land_color = 0.934, 0.820898, 0.868613, 1; 12:11:57.662 [planetinfo.record]: polar_sea_color = 0.93418, 0.872971, 0.864846, 1; 12:11:57.662 [planetinfo.record]: sea_color = 0.658203, 0.485698, 0.462799, 1; 12:11:57.662 [planetinfo.record]: rotation_speed = 0.004271 12:11:57.663 [planetinfo.record]: planet zpos = 874440.000000 12:11:57.663 [planetinfo.record]: sun_radius = 193359.523315 12:11:57.663 [planetinfo.record]: sun_vector = 0.789 -0.567 -0.237 12:11:57.663 [planetinfo.record]: sun_distance = 1311660 12:11:57.663 [planetinfo.record]: corona_flare = 0.025276 12:11:57.663 [planetinfo.record]: corona_hues = 0.548531 12:11:57.663 [planetinfo.record]: sun_color = 0.751135, 0.542595, 0.488087, 1 12:11:57.663 [planetinfo.record]: corona_shimmer = 0.784449 12:11:57.663 [planetinfo.record]: station_vector = 0.817 0.172 0.550 12:11:57.663 [planetinfo.record]: station = coriolis 12:12:02.701 [PLANETINFO OVER]: Done 12:12:02.703 [PLANETINFO LOGGING]: 1 223 12:12:03.708 [planetinfo.record]: seed = 24 50 122 102 12:12:03.708 [planetinfo.record]: coordinates = 50 204 12:12:03.708 [planetinfo.record]: government = 3; 12:12:03.708 [planetinfo.record]: economy = 4; 12:12:03.708 [planetinfo.record]: techlevel = 7; 12:12:03.708 [planetinfo.record]: population = 36; 12:12:03.708 [planetinfo.record]: productivity = 12096; 12:12:03.708 [planetinfo.record]: name = "Biceises"; 12:12:03.708 [planetinfo.record]: inhabitant = "Human Colonial"; 12:12:03.708 [planetinfo.record]: inhabitants = "Human Colonials"; 12:12:03.708 [planetinfo.record]: description = "The world Biceises is mildly fabled for its inhabitants’ ingrained silliness but beset by deadly tree snakes."; 12:12:03.725 [planetinfo.record]: air_color = 0.635463, 0.87786, 0.939164, 1; 12:12:03.725 [planetinfo.record]: cloud_alpha = 1.000000; 12:12:03.725 [planetinfo.record]: cloud_color = 0.632433, 0.820312, 0.5159, 1; 12:12:03.725 [planetinfo.record]: cloud_fraction = 0.170000; 12:12:03.725 [planetinfo.record]: land_color = 0.0819041, 0.603516, 0.0777969, 1; 12:12:03.725 [planetinfo.record]: land_fraction = 0.320000; 12:12:03.725 [planetinfo.record]: polar_cloud_color = 0.744726, 0.869141, 0.667558, 1; 12:12:03.726 [planetinfo.record]: polar_land_color = 0.736617, 0.939648, 0.735018, 1; 12:12:03.726 [planetinfo.record]: polar_sea_color = 0.931641, 0.895874, 0.864315, 1; 12:12:03.726 [planetinfo.record]: sea_color = 0.683594, 0.578618, 0.485992, 1; 12:12:03.726 [planetinfo.record]: rotation_speed = 0.000288 12:12:03.726 [planetinfo.record]: planet zpos = 440200.000000 12:12:03.726 [planetinfo.record]: sun_radius = 117781.173706 12:12:03.726 [planetinfo.record]: sun_vector = 0.295 -0.321 0.900 12:12:03.726 [planetinfo.record]: sun_distance = 836380 12:12:03.726 [planetinfo.record]: corona_flare = 0.065698 12:12:03.726 [planetinfo.record]: corona_hues = 0.656143 12:12:03.735 [planetinfo.record]: sun_color = 0.74747, 0.49793, 0.348771, 1 12:12:03.736 [planetinfo.record]: corona_shimmer = 0.781102 12:12:03.736 [planetinfo.record]: station_vector = -0.148 -0.045 0.988 12:12:03.736 [planetinfo.record]: station = coriolis 12:12:08.713 [PLANETINFO OVER]: Done 12:12:08.715 [PLANETINFO LOGGING]: 1 224 12:12:09.729 [planetinfo.record]: seed = 16 201 166 43 12:12:09.729 [planetinfo.record]: coordinates = 201 253 12:12:09.729 [planetinfo.record]: government = 2; 12:12:09.729 [planetinfo.record]: economy = 5; 12:12:09.729 [planetinfo.record]: techlevel = 4; 12:12:09.729 [planetinfo.record]: population = 24; 12:12:09.729 [planetinfo.record]: productivity = 5760; 12:12:09.729 [planetinfo.record]: name = "Maenso"; 12:12:09.729 [planetinfo.record]: inhabitant = "Small Red Fat Insect"; 12:12:09.729 [planetinfo.record]: inhabitants = "Small Red Fat Insects"; 12:12:09.729 [planetinfo.record]: description = "This world is reasonably well known for its great dense forests but scourged by killer mountain Ilinataroids."; 12:12:09.751 [planetinfo.record]: air_color = 0.455404, 0.739126, 0.84941, 1; 12:12:09.752 [planetinfo.record]: cloud_alpha = 1.000000; 12:12:09.752 [planetinfo.record]: cloud_color = 0.178859, 0.550781, 0.114029, 1; 12:12:09.752 [planetinfo.record]: cloud_fraction = 0.210000; 12:12:09.752 [planetinfo.record]: land_color = 0.335579, 0.66, 0.203672, 1; 12:12:09.752 [planetinfo.record]: land_fraction = 0.280000; 12:12:09.752 [planetinfo.record]: polar_cloud_color = 0.432229, 0.747852, 0.377212, 1; 12:12:09.752 [planetinfo.record]: polar_land_color = 0.819224, 0.934, 0.772557, 1; 12:12:09.752 [planetinfo.record]: polar_sea_color = 0.939062, 0.880413, 0.877528, 1; 12:12:09.752 [planetinfo.record]: sea_color = 0.609375, 0.457139, 0.449652, 1; 12:12:09.752 [planetinfo.record]: rotation_speed = 0.000128 12:12:09.752 [planetinfo.record]: planet zpos = 583300.000000 12:12:09.752 [planetinfo.record]: sun_radius = 154897.230377 12:12:09.752 [planetinfo.record]: sun_vector = 0.858 -0.513 0.006 12:12:09.752 [planetinfo.record]: sun_distance = 1283260 12:12:09.752 [planetinfo.record]: corona_flare = 0.045940 12:12:09.753 [planetinfo.record]: corona_hues = 0.756477 12:12:09.753 [planetinfo.record]: sun_color = 0.684452, 0.724477, 0.768277, 1 12:12:09.753 [planetinfo.record]: corona_shimmer = 0.473389 12:12:09.754 [planetinfo.record]: station_vector = -0.238 -0.966 0.102 12:12:09.754 [planetinfo.record]: station = coriolis 12:12:13.877 [PLANETINFO OVER]: Done 12:12:13.878 [PLANETINFO LOGGING]: 1 225 12:12:14.894 [planetinfo.record]: seed = 240 4 58 222 12:12:14.894 [planetinfo.record]: coordinates = 4 231 12:12:14.894 [planetinfo.record]: government = 6; 12:12:14.894 [planetinfo.record]: economy = 7; 12:12:14.894 [planetinfo.record]: techlevel = 3; 12:12:14.894 [planetinfo.record]: population = 26; 12:12:14.894 [planetinfo.record]: productivity = 6240; 12:12:14.895 [planetinfo.record]: name = "Riardi"; 12:12:14.895 [planetinfo.record]: inhabitant = "Human Colonial"; 12:12:14.895 [planetinfo.record]: inhabitants = "Human Colonials"; 12:12:14.895 [planetinfo.record]: description = "This world is a tedious little planet."; 12:12:14.912 [planetinfo.record]: air_color = 0.723652, 0.739147, 0.963879, 1; 12:12:14.912 [planetinfo.record]: cloud_alpha = 1.000000; 12:12:14.912 [planetinfo.record]: cloud_color = 0.761749, 0.785609, 0.894531, 1; 12:12:14.912 [planetinfo.record]: cloud_fraction = 0.540000; 12:12:14.912 [planetinfo.record]: land_color = 0.53125, 0.306091, 0.0166016, 1; 12:12:14.912 [planetinfo.record]: land_fraction = 0.590000; 12:12:14.912 [planetinfo.record]: polar_cloud_color = 0.818807, 0.833853, 0.902539, 1; 12:12:14.912 [planetinfo.record]: polar_land_color = 0.946875, 0.846547, 0.717554, 1; 12:12:14.912 [planetinfo.record]: polar_sea_color = 0.94375, 0.900019, 0.889927, 1; 12:12:14.912 [planetinfo.record]: sea_color = 0.5625, 0.45824, 0.43418, 1; 12:12:14.912 [planetinfo.record]: rotation_speed = 0.001273 12:12:14.912 [planetinfo.record]: planet zpos = 896560.000000 12:12:14.912 [planetinfo.record]: sun_radius = 129607.321167 12:12:14.912 [planetinfo.record]: sun_vector = 0.616 -0.786 0.053 12:12:14.912 [planetinfo.record]: sun_distance = 1152720 12:12:14.912 [planetinfo.record]: corona_flare = 0.052698 12:12:14.912 [planetinfo.record]: corona_hues = 0.801781 12:12:14.913 [planetinfo.record]: sun_color = 0.655704, 0.67829, 0.707245, 1 12:12:14.913 [planetinfo.record]: corona_shimmer = 0.368296 12:12:14.913 [planetinfo.record]: station_vector = 0.762 0.323 0.561 12:12:14.913 [planetinfo.record]: station = coriolis 12:12:19.404 [PLANETINFO OVER]: Done 12:12:19.405 [PLANETINFO LOGGING]: 1 226 12:12:20.410 [planetinfo.record]: seed = 184 85 54 205 12:12:20.410 [planetinfo.record]: coordinates = 85 173 12:12:20.410 [planetinfo.record]: government = 7; 12:12:20.410 [planetinfo.record]: economy = 5; 12:12:20.411 [planetinfo.record]: techlevel = 7; 12:12:20.411 [planetinfo.record]: population = 41; 12:12:20.411 [planetinfo.record]: productivity = 18040; 12:12:20.411 [planetinfo.record]: name = "Dierbeer"; 12:12:20.411 [planetinfo.record]: inhabitant = "Human Colonial"; 12:12:20.411 [planetinfo.record]: inhabitants = "Human Colonials"; 12:12:20.411 [planetinfo.record]: description = "Dierbeer is cursed by dreadful civil war."; 12:12:20.428 [planetinfo.record]: air_color = 0.513521, 0.641463, 0.839654, 1; 12:12:20.429 [planetinfo.record]: cloud_alpha = 1.000000; 12:12:20.429 [planetinfo.record]: cloud_color = 0.226112, 0.521484, 0.473025, 1; 12:12:20.435 [planetinfo.record]: cloud_fraction = 0.570000; 12:12:20.435 [planetinfo.record]: land_color = 0.66, 0.020625, 0.200449, 1; 12:12:20.436 [planetinfo.record]: land_fraction = 0.580000; 12:12:20.436 [planetinfo.record]: polar_cloud_color = 0.474593, 0.734668, 0.691999, 1; 12:12:20.436 [planetinfo.record]: polar_land_color = 0.934, 0.707797, 0.771417, 1; 12:12:20.436 [planetinfo.record]: polar_sea_color = 0.944727, 0.926167, 0.897213, 1; 12:12:20.436 [planetinfo.record]: sea_color = 0.552734, 0.509299, 0.44154, 1; 12:12:20.436 [planetinfo.record]: rotation_speed = 0.003622 12:12:20.436 [planetinfo.record]: planet zpos = 872060.000000 12:12:20.436 [planetinfo.record]: sun_radius = 168458.446198 12:12:20.436 [planetinfo.record]: sun_vector = 0.197 -0.290 -0.936 12:12:20.436 [planetinfo.record]: sun_distance = 1308090 12:12:20.436 [planetinfo.record]: corona_flare = 0.082112 12:12:20.436 [planetinfo.record]: corona_hues = 0.724670 12:12:20.437 [planetinfo.record]: sun_color = 0.685764, 0.36459, 0.359289, 1 12:12:20.437 [planetinfo.record]: corona_shimmer = 0.500603 12:12:20.437 [planetinfo.record]: station_vector = 0.366 0.917 0.159 12:12:20.437 [planetinfo.record]: station = coriolis 12:12:25.257 [PLANETINFO OVER]: Done 12:12:25.260 [PLANETINFO LOGGING]: 1 227 12:12:26.276 [planetinfo.record]: seed = 168 144 26 84 12:12:26.276 [planetinfo.record]: coordinates = 144 243 12:12:26.276 [planetinfo.record]: government = 5; 12:12:26.276 [planetinfo.record]: economy = 3; 12:12:26.276 [planetinfo.record]: techlevel = 7; 12:12:26.276 [planetinfo.record]: population = 37; 12:12:26.276 [planetinfo.record]: productivity = 18648; 12:12:26.276 [planetinfo.record]: name = "Ratite"; 12:12:26.276 [planetinfo.record]: inhabitant = "Human Colonial"; 12:12:26.276 [planetinfo.record]: inhabitants = "Human Colonials"; 12:12:26.276 [planetinfo.record]: description = "This world is plagued by frequent solar activity."; 12:12:26.293 [planetinfo.record]: air_color = 0.669562, 0.755041, 0.980139, 1; 12:12:26.293 [planetinfo.record]: cloud_alpha = 1.000000; 12:12:26.293 [planetinfo.record]: cloud_color = 0.61171, 0.881175, 0.943359, 1; 12:12:26.293 [planetinfo.record]: cloud_fraction = 0.640000; 12:12:26.293 [planetinfo.record]: land_color = 0.386719, 0.66, 0.551114, 1; 12:12:26.293 [planetinfo.record]: land_fraction = 0.600000; 12:12:26.293 [planetinfo.record]: polar_cloud_color = 0.721372, 0.886423, 0.924512, 1; 12:12:26.293 [planetinfo.record]: polar_land_color = 0.837316, 0.934, 0.895478, 1; 12:12:26.293 [planetinfo.record]: polar_sea_color = 0.899994, 0.931055, 0.876592, 1; 12:12:26.293 [planetinfo.record]: sea_color = 0.59745, 0.689453, 0.528132, 1; 12:12:26.293 [planetinfo.record]: rotation_speed = 0.004669 12:12:26.293 [planetinfo.record]: planet zpos = 438240.000000 12:12:26.293 [planetinfo.record]: sun_radius = 102334.987793 12:12:26.293 [planetinfo.record]: sun_vector = 0.631 0.199 -0.750 12:12:26.293 [planetinfo.record]: sun_distance = 796800 12:12:26.293 [planetinfo.record]: corona_flare = 0.066196 12:12:26.293 [planetinfo.record]: corona_hues = 0.876572 12:12:26.294 [planetinfo.record]: sun_color = 0.520846, 0.663287, 0.82193, 1 12:12:26.294 [planetinfo.record]: corona_shimmer = 0.228062 12:12:26.294 [planetinfo.record]: station_vector = 0.121 -0.299 0.947 12:12:26.300 [planetinfo.record]: station = coriolis 12:12:32.055 [PLANETINFO OVER]: Done 12:12:32.056 [PLANETINFO LOGGING]: 1 228 12:12:33.063 [planetinfo.record]: seed = 192 232 102 125 12:12:33.063 [planetinfo.record]: coordinates = 232 188 12:12:33.063 [planetinfo.record]: government = 0; 12:12:33.063 [planetinfo.record]: economy = 6; 12:12:33.063 [planetinfo.record]: techlevel = 1; 12:12:33.063 [planetinfo.record]: population = 11; 12:12:33.063 [planetinfo.record]: productivity = 1408; 12:12:33.063 [planetinfo.record]: name = "Isxees"; 12:12:33.063 [planetinfo.record]: inhabitant = "Human Colonial"; 12:12:33.063 [planetinfo.record]: inhabitants = "Human Colonials"; 12:12:33.063 [planetinfo.record]: description = "The planet Isxees is reasonably fabled for Zero-G hockey and Isxeesian Atlethma brandy."; 12:12:33.067 [planetinfo.record]: air_color = 0.457176, 0.508663, 0.919652, 1; 12:12:33.068 [planetinfo.record]: cloud_alpha = 1.000000; 12:12:33.068 [planetinfo.record]: cloud_color = 0.0684357, 0.290503, 0.761719, 1; 12:12:33.068 [planetinfo.record]: cloud_fraction = 0.310000; 12:12:33.068 [planetinfo.record]: land_color = 0.376064, 0.66, 0.373828, 1; 12:12:33.068 [planetinfo.record]: land_fraction = 0.290000; 12:12:33.068 [planetinfo.record]: polar_cloud_color = 0.363364, 0.516925, 0.842773, 1; 12:12:33.068 [planetinfo.record]: polar_land_color = 0.833547, 0.934, 0.832756, 1; 12:12:33.068 [planetinfo.record]: polar_sea_color = 0.9168, 0.936719, 0.887687, 1; 12:12:33.068 [planetinfo.record]: sea_color = 0.578986, 0.632812, 0.500317, 1; 12:12:33.068 [planetinfo.record]: rotation_speed = 0.004545 12:12:33.068 [planetinfo.record]: planet zpos = 637600.000000 12:12:33.068 [planetinfo.record]: sun_radius = 152363.984375 12:12:33.068 [planetinfo.record]: sun_vector = -0.226 0.850 0.475 12:12:33.068 [planetinfo.record]: sun_distance = 1402720 12:12:33.068 [planetinfo.record]: corona_flare = 0.030209 12:12:33.068 [planetinfo.record]: corona_hues = 0.936317 12:12:33.069 [planetinfo.record]: sun_color = 0.566432, 0.59081, 0.705615, 1 12:12:33.069 [planetinfo.record]: corona_shimmer = 0.359803 12:12:33.069 [planetinfo.record]: station_vector = 0.466 -0.820 0.331 12:12:33.069 [planetinfo.record]: station = coriolis 12:12:38.088 [PLANETINFO OVER]: Done 12:12:38.089 [PLANETINFO LOGGING]: 1 229 12:12:39.094 [planetinfo.record]: seed = 64 41 26 213 12:12:39.094 [planetinfo.record]: coordinates = 41 137 12:12:39.094 [planetinfo.record]: government = 0; 12:12:39.094 [planetinfo.record]: economy = 3; 12:12:39.094 [planetinfo.record]: techlevel = 5; 12:12:39.094 [planetinfo.record]: population = 24; 12:12:39.094 [planetinfo.record]: productivity = 5376; 12:12:39.094 [planetinfo.record]: name = "Lasoce"; 12:12:39.094 [planetinfo.record]: inhabitant = "Human Colonial"; 12:12:39.094 [planetinfo.record]: inhabitants = "Human Colonials"; 12:12:39.094 [planetinfo.record]: description = "This world is very notable for its inhabitants’ ingrained shyness."; 12:12:39.112 [planetinfo.record]: air_color = 0.677227, 0.868922, 0.862733, 1; 12:12:39.112 [planetinfo.record]: cloud_alpha = 1.000000; 12:12:39.112 [planetinfo.record]: cloud_color = 0.609375, 0.606102, 0.571289, 1; 12:12:39.112 [planetinfo.record]: cloud_fraction = 0.160000; 12:12:39.112 [planetinfo.record]: land_color = 0.53625, 0.66, 0.546885, 1; 12:12:39.112 [planetinfo.record]: land_fraction = 0.380000; 12:12:39.112 [planetinfo.record]: polar_cloud_color = 0.774219, 0.77162, 0.743976, 1; 12:12:39.112 [planetinfo.record]: polar_land_color = 0.890219, 0.934, 0.893981, 1; 12:12:39.112 [planetinfo.record]: polar_sea_color = 0.910492, 0.885986, 0.95, 1; 12:12:39.112 [planetinfo.record]: sea_color = 0.416824, 0.365234, 0.5, 1; 12:12:39.112 [planetinfo.record]: rotation_speed = 0.000650 12:12:39.112 [planetinfo.record]: planet zpos = 537810.000000 12:12:39.112 [planetinfo.record]: sun_radius = 132169.246674 12:12:39.112 [planetinfo.record]: sun_vector = 0.180 -0.963 0.200 12:12:39.112 [planetinfo.record]: sun_distance = 703290 12:12:39.112 [planetinfo.record]: corona_flare = 0.070403 12:12:39.113 [planetinfo.record]: corona_hues = 0.594131 12:12:39.113 [planetinfo.record]: sun_color = 0.795422, 0.651085, 0.573175, 1 12:12:39.113 [planetinfo.record]: corona_shimmer = 0.289312 12:12:39.113 [planetinfo.record]: station_vector = -0.885 0.150 0.441 12:12:39.113 [planetinfo.record]: station = coriolis 12:12:43.519 [PLANETINFO OVER]: Done 12:12:43.520 [PLANETINFO LOGGING]: 1 230 12:12:44.535 [planetinfo.record]: seed = 40 226 54 239 12:12:44.535 [planetinfo.record]: coordinates = 226 133 12:12:44.535 [planetinfo.record]: government = 5; 12:12:44.535 [planetinfo.record]: economy = 5; 12:12:44.536 [planetinfo.record]: techlevel = 7; 12:12:44.536 [planetinfo.record]: population = 39; 12:12:44.536 [planetinfo.record]: productivity = 14040; 12:12:44.536 [planetinfo.record]: name = "Atius"; 12:12:44.536 [planetinfo.record]: inhabitant = "Human Colonial"; 12:12:44.536 [planetinfo.record]: inhabitants = "Human Colonials"; 12:12:44.536 [planetinfo.record]: description = "This world is most notable for Atiusian lethal brandy but beset by deadly tree wolfs."; 12:12:44.556 [planetinfo.record]: air_color = 0.550043, 0.557786, 0.853963, 1; 12:12:44.556 [planetinfo.record]: cloud_alpha = 1.000000; 12:12:44.556 [planetinfo.record]: cloud_color = 0.299866, 0.316402, 0.564453, 1; 12:12:44.556 [planetinfo.record]: cloud_fraction = 0.430000; 12:12:44.556 [planetinfo.record]: land_color = 0.66, 0.134062, 0.294309, 1; 12:12:44.556 [planetinfo.record]: land_fraction = 0.410000; 12:12:44.556 [planetinfo.record]: polar_cloud_color = 0.533104, 0.546911, 0.754004, 1; 12:12:44.556 [planetinfo.record]: polar_land_color = 0.934, 0.74793, 0.804623, 1; 12:12:44.556 [planetinfo.record]: polar_sea_color = 0.941602, 0.938607, 0.893694, 1; 12:12:44.556 [planetinfo.record]: sea_color = 0.583984, 0.576556, 0.465134, 1; 12:12:44.556 [planetinfo.record]: rotation_speed = 0.003034 12:12:44.556 [planetinfo.record]: planet zpos = 825840.000000 12:12:44.556 [planetinfo.record]: sun_radius = 182572.100830 12:12:44.556 [planetinfo.record]: sun_vector = -0.299 -0.703 0.645 12:12:44.556 [planetinfo.record]: sun_distance = 1445220 12:12:44.556 [planetinfo.record]: corona_flare = 0.016808 12:12:44.556 [planetinfo.record]: corona_hues = 0.802307 12:12:44.556 [planetinfo.record]: sun_color = 0.776105, 0.579453, 0.50004, 1 12:12:44.556 [planetinfo.record]: corona_shimmer = 0.388265 12:12:44.557 [planetinfo.record]: station_vector = 0.324 -0.564 0.760 12:12:44.557 [planetinfo.record]: station = coriolis 12:12:48.855 [PLANETINFO OVER]: Done 12:12:48.857 [PLANETINFO LOGGING]: 1 231 12:12:49.860 [planetinfo.record]: seed = 184 110 58 238 12:12:49.861 [planetinfo.record]: coordinates = 110 40 12:12:49.861 [planetinfo.record]: government = 7; 12:12:49.861 [planetinfo.record]: economy = 0; 12:12:49.861 [planetinfo.record]: techlevel = 13; 12:12:49.861 [planetinfo.record]: population = 60; 12:12:49.861 [planetinfo.record]: productivity = 52800; 12:12:49.861 [planetinfo.record]: name = "Recexela"; 12:12:49.861 [planetinfo.record]: inhabitant = "Human Colonial"; 12:12:49.861 [planetinfo.record]: inhabitants = "Human Colonials"; 12:12:49.862 [planetinfo.record]: description = "The planet Recexela is very noted for its ancient Innu tulip plantations but ravaged by occasional solar activity."; 12:12:49.867 [planetinfo.record]: air_color = 0.634008, 0.496538, 0.939814, 1; 12:12:49.868 [planetinfo.record]: cloud_alpha = 1.000000; 12:12:49.868 [planetinfo.record]: cloud_color = 0.785554, 0.150963, 0.822266, 1; 12:12:49.868 [planetinfo.record]: cloud_fraction = 0.410000; 12:12:49.868 [planetinfo.record]: land_color = 0.66, 0.309536, 0.273281, 1; 12:12:49.868 [planetinfo.record]: land_fraction = 0.680000; 12:12:49.868 [planetinfo.record]: polar_cloud_color = 0.845742, 0.426089, 0.87002, 1; 12:12:49.868 [planetinfo.record]: polar_land_color = 0.934, 0.81001, 0.797184, 1; 12:12:49.868 [planetinfo.record]: polar_sea_color = 0.901134, 0.929688, 0.872581, 1; 12:12:49.869 [planetinfo.record]: sea_color = 0.616745, 0.703125, 0.530365, 1; 12:12:49.869 [planetinfo.record]: rotation_speed = 0.004109 12:12:49.869 [planetinfo.record]: planet zpos = 846300.000000 12:12:49.869 [planetinfo.record]: sun_radius = 189740.235901 12:12:49.869 [planetinfo.record]: sun_vector = 0.213 0.964 -0.160 12:12:49.869 [planetinfo.record]: sun_distance = 1302000 12:12:49.869 [planetinfo.record]: corona_flare = 0.055646 12:12:49.869 [planetinfo.record]: corona_hues = 0.703247 12:12:49.869 [planetinfo.record]: sun_color = 0.313839, 0.446175, 0.784961, 1 12:12:49.870 [planetinfo.record]: corona_shimmer = 0.351102 12:12:49.870 [planetinfo.record]: station_vector = -0.569 -0.821 0.052 12:12:49.870 [planetinfo.record]: station = dodecahedron 12:12:55.307 [PLANETINFO OVER]: Done 12:12:55.310 [PLANETINFO LOGGING]: 1 232 12:12:56.328 [planetinfo.record]: seed = 240 85 166 189 12:12:56.328 [planetinfo.record]: coordinates = 85 226 12:12:56.328 [planetinfo.record]: government = 6; 12:12:56.328 [planetinfo.record]: economy = 2; 12:12:56.328 [planetinfo.record]: techlevel = 9; 12:12:56.329 [planetinfo.record]: population = 45; 12:12:56.329 [planetinfo.record]: productivity = 28800; 12:12:56.329 [planetinfo.record]: name = "Islaesis"; 12:12:56.329 [planetinfo.record]: inhabitant = "Harmless Rodent"; 12:12:56.329 [planetinfo.record]: inhabitants = "Harmless Rodents"; 12:12:56.329 [planetinfo.record]: description = "Islaesis is ravaged by occasional solar activity."; 12:12:56.348 [planetinfo.record]: air_color = 0.710855, 0.609844, 0.855264, 1; 12:12:56.349 [planetinfo.record]: cloud_alpha = 1.000000; 12:12:56.349 [planetinfo.record]: cloud_color = 0.568359, 0.419609, 0.531172, 1; 12:12:56.349 [planetinfo.record]: cloud_fraction = 0.460000; 12:12:56.349 [planetinfo.record]: land_color = 0.543691, 0.334106, 0.628906, 1; 12:12:56.349 [planetinfo.record]: land_fraction = 0.430000; 12:12:56.355 [planetinfo.record]: polar_cloud_color = 0.755762, 0.632139, 0.724856, 1; 12:12:56.356 [planetinfo.record]: polar_land_color = 0.905365, 0.827292, 0.937109, 1; 12:12:56.356 [planetinfo.record]: polar_sea_color = 0.94668, 0.900767, 0.896018, 1; 12:12:56.356 [planetinfo.record]: sea_color = 0.533203, 0.429765, 0.419064, 1; 12:12:56.356 [planetinfo.record]: rotation_speed = 0.003927 12:12:56.356 [planetinfo.record]: planet zpos = 934350.000000 12:12:56.356 [planetinfo.record]: sun_radius = 108824.059296 12:12:56.356 [planetinfo.record]: sun_vector = 0.387 0.728 -0.566 12:12:56.356 [planetinfo.record]: sun_distance = 1370380 12:12:56.356 [planetinfo.record]: corona_flare = 0.038171 12:12:56.356 [planetinfo.record]: corona_hues = 0.640038 12:12:56.356 [planetinfo.record]: sun_color = 0.586406, 0.608102, 0.783701, 1 12:12:56.356 [planetinfo.record]: corona_shimmer = 0.564236 12:12:56.357 [planetinfo.record]: station_vector = 0.711 -0.546 0.443 12:12:56.357 [planetinfo.record]: station = coriolis 12:13:02.317 [PLANETINFO OVER]: Done 12:13:02.317 [PLANETINFO LOGGING]: 1 233 12:13:03.327 [planetinfo.record]: seed = 16 189 122 188 12:13:03.327 [planetinfo.record]: coordinates = 189 9 12:13:03.327 [planetinfo.record]: government = 2; 12:13:03.327 [planetinfo.record]: economy = 1; 12:13:03.327 [planetinfo.record]: techlevel = 8; 12:13:03.327 [planetinfo.record]: population = 36; 12:13:03.327 [planetinfo.record]: productivity = 15552; 12:13:03.327 [planetinfo.record]: name = "Tegete"; 12:13:03.327 [planetinfo.record]: inhabitant = "Human Colonial"; 12:13:03.327 [planetinfo.record]: inhabitants = "Human Colonials"; 12:13:03.327 [planetinfo.record]: description = "The world Tegete is reasonably noted for its inhabitants’ eccentric shyness and its inhabitants’ ingrained shyness."; 12:13:03.344 [planetinfo.record]: air_color = 0.592463, 0.556875, 0.86632, 1; 12:13:03.344 [planetinfo.record]: cloud_alpha = 1.000000; 12:13:03.344 [planetinfo.record]: cloud_color = 0.419412, 0.31723, 0.601562, 1; 12:13:03.344 [planetinfo.record]: cloud_fraction = 0.300000; 12:13:03.344 [planetinfo.record]: land_color = 0.313705, 0.341301, 0.521484, 1; 12:13:03.344 [planetinfo.record]: land_fraction = 0.490000; 12:13:03.344 [planetinfo.record]: polar_cloud_color = 0.62485, 0.54303, 0.770703, 1; 12:13:03.344 [planetinfo.record]: polar_land_color = 0.853437, 0.865976, 0.947852, 1; 12:13:03.344 [planetinfo.record]: polar_sea_color = 0.928044, 0.938672, 0.890088, 1; 12:13:03.344 [planetinfo.record]: sea_color = 0.585507, 0.613281, 0.486313, 1; 12:13:03.344 [planetinfo.record]: rotation_speed = 0.000036 12:13:03.344 [planetinfo.record]: planet zpos = 668470.000000 12:13:03.344 [planetinfo.record]: sun_radius = 109807.169037 12:13:03.344 [planetinfo.record]: sun_vector = -0.019 -0.956 -0.291 12:13:03.344 [planetinfo.record]: sun_distance = 1093860 12:13:03.344 [planetinfo.record]: corona_flare = 0.094490 12:13:03.344 [planetinfo.record]: corona_hues = 0.858360 12:13:03.344 [planetinfo.record]: sun_color = 0.73584, 0.705013, 0.340945, 1 12:13:03.344 [planetinfo.record]: corona_shimmer = 0.326592 12:13:03.345 [planetinfo.record]: station_vector = -0.851 0.524 0.038 12:13:03.345 [planetinfo.record]: station = coriolis 12:13:08.025 [PLANETINFO OVER]: Done 12:13:08.026 [PLANETINFO LOGGING]: 1 234 12:13:09.046 [planetinfo.record]: seed = 24 60 182 187 12:13:09.046 [planetinfo.record]: coordinates = 60 252 12:13:09.046 [planetinfo.record]: government = 3; 12:13:09.046 [planetinfo.record]: economy = 4; 12:13:09.046 [planetinfo.record]: techlevel = 5; 12:13:09.046 [planetinfo.record]: population = 28; 12:13:09.046 [planetinfo.record]: productivity = 9408; 12:13:09.046 [planetinfo.record]: name = "Anrain"; 12:13:09.046 [planetinfo.record]: inhabitant = "Harmless Slimy Lobster"; 12:13:09.046 [planetinfo.record]: inhabitants = "Harmless Slimy Lobsters"; 12:13:09.046 [planetinfo.record]: description = "This world is very notable for its inhabitants’ unusual silliness and its fabulous Noal brandy."; 12:13:09.060 [planetinfo.record]: air_color = 0.607374, 0.909896, 0.845825, 1; 12:13:09.060 [planetinfo.record]: cloud_alpha = 1.000000; 12:13:09.060 [planetinfo.record]: cloud_color = 0.732422, 0.550032, 0.440598, 1; 12:13:09.060 [planetinfo.record]: cloud_fraction = 0.340000; 12:13:09.060 [planetinfo.record]: land_color = 0.595703, 0.388603, 0.587613, 1; 12:13:09.060 [planetinfo.record]: land_fraction = 0.310000; 12:13:09.060 [planetinfo.record]: polar_cloud_color = 0.82959, 0.700473, 0.623003, 1; 12:13:09.060 [planetinfo.record]: polar_land_color = 0.94043, 0.858693, 0.937237, 1; 12:13:09.060 [planetinfo.record]: polar_sea_color = 0.936328, 0.873236, 0.873236, 1; 12:13:09.060 [planetinfo.record]: sea_color = 0.636719, 0.465103, 0.465103, 1; 12:13:09.060 [planetinfo.record]: rotation_speed = 0.002360 12:13:09.060 [planetinfo.record]: planet zpos = 853800.000000 12:13:09.060 [planetinfo.record]: sun_radius = 134156.660156 12:13:09.060 [planetinfo.record]: sun_vector = -0.627 -0.127 -0.768 12:13:09.060 [planetinfo.record]: sun_distance = 1195320 12:13:09.060 [planetinfo.record]: corona_flare = 0.063408 12:13:09.060 [planetinfo.record]: corona_hues = 0.707703 12:13:09.060 [planetinfo.record]: sun_color = 0.598021, 0.721579, 0.764798, 1 12:13:09.060 [planetinfo.record]: corona_shimmer = 0.319875 12:13:09.060 [planetinfo.record]: station_vector = 0.022 -0.908 0.418 12:13:09.060 [planetinfo.record]: station = coriolis 12:13:14.303 [PLANETINFO OVER]: Done 12:13:14.303 [PLANETINFO LOGGING]: 1 235 12:13:15.309 [planetinfo.record]: seed = 72 156 218 124 12:13:15.309 [planetinfo.record]: coordinates = 156 236 12:13:15.309 [planetinfo.record]: government = 1; 12:13:15.309 [planetinfo.record]: economy = 6; 12:13:15.309 [planetinfo.record]: techlevel = 2; 12:13:15.309 [planetinfo.record]: population = 16; 12:13:15.309 [planetinfo.record]: productivity = 2560; 12:13:15.309 [planetinfo.record]: name = "Teceri"; 12:13:15.309 [planetinfo.record]: inhabitant = "Blue Slimy Rodent"; 12:13:15.309 [planetinfo.record]: inhabitants = "Blue Slimy Rodents"; 12:13:15.309 [planetinfo.record]: description = "Teceri is well known for the Teceriian spotted leopard but plagued by lethal lethal yaks."; 12:13:15.320 [planetinfo.record]: air_color = 0.54326, 0.596841, 0.838354, 1; 12:13:15.321 [planetinfo.record]: cloud_alpha = 1.000000; 12:13:15.321 [planetinfo.record]: cloud_color = 0.281029, 0.406696, 0.517578, 1; 12:13:15.321 [planetinfo.record]: cloud_fraction = 0.420000; 12:13:15.321 [planetinfo.record]: land_color = 0.166231, 0.570312, 0.0957947, 1; 12:13:15.321 [planetinfo.record]: land_fraction = 0.490000; 12:13:15.321 [planetinfo.record]: polar_cloud_color = 0.523558, 0.634777, 0.73291, 1; 12:13:15.321 [planetinfo.record]: polar_land_color = 0.775939, 0.942969, 0.746824, 1; 12:13:15.321 [planetinfo.record]: polar_sea_color = 0.945312, 0.889738, 0.898856, 1; 12:13:15.321 [planetinfo.record]: sea_color = 0.546875, 0.418274, 0.439373, 1; 12:13:15.321 [planetinfo.record]: rotation_speed = 0.003440 12:13:15.321 [planetinfo.record]: planet zpos = 604400.000000 12:13:15.322 [planetinfo.record]: sun_radius = 115163.948975 12:13:15.322 [planetinfo.record]: sun_vector = -0.827 0.373 0.420 12:13:15.322 [planetinfo.record]: sun_distance = 1087920 12:13:15.322 [planetinfo.record]: corona_flare = 0.042188 12:13:15.322 [planetinfo.record]: corona_hues = 0.763069 12:13:15.322 [planetinfo.record]: sun_color = 0.69715, 0.468398, 0.264545, 1 12:13:15.322 [planetinfo.record]: corona_shimmer = 0.606725 12:13:15.322 [planetinfo.record]: station_vector = 0.969 -0.232 0.087 12:13:15.323 [planetinfo.record]: station = coriolis 12:13:20.781 [PLANETINFO OVER]: Done 12:13:20.782 [PLANETINFO LOGGING]: 1 236 12:13:21.799 [planetinfo.record]: seed = 160 160 102 196 12:13:21.799 [planetinfo.record]: coordinates = 160 30 12:13:21.799 [planetinfo.record]: government = 4; 12:13:21.799 [planetinfo.record]: economy = 6; 12:13:21.799 [planetinfo.record]: techlevel = 3; 12:13:21.799 [planetinfo.record]: population = 23; 12:13:21.799 [planetinfo.record]: productivity = 5888; 12:13:21.799 [planetinfo.record]: name = "Zageuser"; 12:13:21.799 [planetinfo.record]: inhabitant = "Human Colonial"; 12:13:21.800 [planetinfo.record]: inhabitants = "Human Colonials"; 12:13:21.800 [planetinfo.record]: description = "The planet Zageuser is most well known for its exotic cuisine."; 12:13:21.816 [planetinfo.record]: air_color = 0.550451, 0.540868, 0.869572, 1; 12:13:21.816 [planetinfo.record]: cloud_alpha = 1.000000; 12:13:21.816 [planetinfo.record]: cloud_color = 0.317399, 0.284172, 0.611328, 1; 12:13:21.816 [planetinfo.record]: cloud_fraction = 0.310000; 12:13:21.816 [planetinfo.record]: land_color = 0.539223, 0.38002, 0.572266, 1; 12:13:21.816 [planetinfo.record]: land_fraction = 0.280000; 12:13:21.816 [planetinfo.record]: polar_cloud_color = 0.542179, 0.515849, 0.775098, 1; 12:13:21.816 [planetinfo.record]: polar_land_color = 0.929165, 0.863595, 0.942773, 1; 12:13:21.816 [planetinfo.record]: polar_sea_color = 0.914844, 0.77682, 0.758498, 1; 12:13:21.816 [planetinfo.record]: sea_color = 0.851562, 0.337657, 0.26944, 1; 12:13:21.816 [planetinfo.record]: rotation_speed = 0.003317 12:13:21.816 [planetinfo.record]: planet zpos = 520000.000000 12:13:21.816 [planetinfo.record]: sun_radius = 100304.565430 12:13:21.816 [planetinfo.record]: sun_vector = 0.170 0.101 0.980 12:13:21.817 [planetinfo.record]: sun_distance = 920000 12:13:21.817 [planetinfo.record]: corona_flare = 0.083690 12:13:21.817 [planetinfo.record]: corona_hues = 0.746140 12:13:21.817 [planetinfo.record]: sun_color = 0.834308, 0.437393, 0.245823, 1 12:13:21.817 [planetinfo.record]: corona_shimmer = 0.557269 12:13:21.817 [planetinfo.record]: station_vector = 0.149 0.986 0.069 12:13:21.817 [planetinfo.record]: station = coriolis 12:13:26.827 [PLANETINFO OVER]: Done 12:13:26.828 [PLANETINFO LOGGING]: 1 237 12:13:27.846 [planetinfo.record]: seed = 96 48 90 156 12:13:27.846 [planetinfo.record]: coordinates = 48 232 12:13:27.846 [planetinfo.record]: government = 4; 12:13:27.846 [planetinfo.record]: economy = 0; 12:13:27.846 [planetinfo.record]: techlevel = 9; 12:13:27.846 [planetinfo.record]: population = 41; 12:13:27.847 [planetinfo.record]: productivity = 26240; 12:13:27.847 [planetinfo.record]: name = "Telale"; 12:13:27.847 [planetinfo.record]: inhabitant = "Human Colonial"; 12:13:27.847 [planetinfo.record]: inhabitants = "Human Colonials"; 12:13:27.847 [planetinfo.record]: description = "This world is a tedious little planet."; 12:13:27.864 [planetinfo.record]: air_color = 0.558094, 0.580494, 0.837703, 1; 12:13:27.864 [planetinfo.record]: cloud_alpha = 1.000000; 12:13:27.864 [planetinfo.record]: cloud_color = 0.308167, 0.355169, 0.515625, 1; 12:13:27.864 [planetinfo.record]: cloud_fraction = 0.440000; 12:13:27.864 [planetinfo.record]: land_color = 0.621094, 0.50642, 0.487656, 1; 12:13:27.864 [planetinfo.record]: land_fraction = 0.370000; 12:13:27.864 [planetinfo.record]: polar_cloud_color = 0.547951, 0.589657, 0.732031, 1; 12:13:27.864 [planetinfo.record]: polar_land_color = 0.937891, 0.8946, 0.887516, 1; 12:13:27.864 [planetinfo.record]: polar_sea_color = 0.776247, 0.941797, 0.902996, 1; 12:13:27.864 [planetinfo.record]: sea_color = 0.172791, 0.582031, 0.486115, 1; 12:13:27.865 [planetinfo.record]: rotation_speed = 0.004375 12:13:27.865 [planetinfo.record]: planet zpos = 831040.000000 12:13:27.865 [planetinfo.record]: sun_radius = 184433.918457 12:13:27.865 [planetinfo.record]: sun_vector = -0.635 -0.697 -0.332 12:13:27.865 [planetinfo.record]: sun_distance = 1127840 12:13:27.865 [planetinfo.record]: corona_flare = 0.036324 12:13:27.865 [planetinfo.record]: corona_hues = 0.848984 12:13:27.865 [planetinfo.record]: sun_color = 0.528827, 0.82079, 0.827887, 1 12:13:27.865 [planetinfo.record]: corona_shimmer = 0.401702 12:13:27.865 [planetinfo.record]: station_vector = 0.959 0.277 0.067 12:13:27.865 [planetinfo.record]: station = coriolis 12:13:33.055 [PLANETINFO OVER]: Done 12:13:33.055 [PLANETINFO LOGGING]: 1 238 12:13:34.061 [planetinfo.record]: seed = 136 211 182 10 12:13:34.061 [planetinfo.record]: coordinates = 211 129 12:13:34.061 [planetinfo.record]: government = 1; 12:13:34.061 [planetinfo.record]: economy = 3; 12:13:34.061 [planetinfo.record]: techlevel = 8; 12:13:34.061 [planetinfo.record]: population = 37; 12:13:34.061 [planetinfo.record]: productivity = 10360; 12:13:34.061 [planetinfo.record]: name = "Arries"; 12:13:34.061 [planetinfo.record]: inhabitant = "Small Green Horned Bird"; 12:13:34.061 [planetinfo.record]: inhabitants = "Small Green Horned Birds"; 12:13:34.061 [planetinfo.record]: description = "This world is most notable for vicious Za brew but ravaged by vicious vicious shrews."; 12:13:34.100 [planetinfo.record]: air_color = 0.448116, 0.702265, 0.869572, 1; 12:13:34.100 [planetinfo.record]: cloud_alpha = 1.000000; 12:13:34.100 [planetinfo.record]: cloud_color = 0.088356, 0.611328, 0.194585, 1; 12:13:34.100 [planetinfo.record]: cloud_fraction = 0.190000; 12:13:34.100 [planetinfo.record]: land_color = 0.66, 0.614883, 0.495, 1; 12:13:34.100 [planetinfo.record]: land_fraction = 0.530000; 12:13:34.100 [planetinfo.record]: polar_cloud_color = 0.360678, 0.775098, 0.444857, 1; 12:13:34.100 [planetinfo.record]: polar_land_color = 0.934, 0.918038, 0.875625, 1; 12:13:34.100 [planetinfo.record]: polar_sea_color = 0.871718, 0.882241, 0.918164, 1; 12:13:34.100 [planetinfo.record]: sea_color = 0.652769, 0.690286, 0.818359, 1; 12:13:34.100 [planetinfo.record]: rotation_speed = 0.001677 12:13:34.100 [planetinfo.record]: planet zpos = 670440.000000 12:13:34.100 [planetinfo.record]: sun_radius = 137139.639587 12:13:34.100 [planetinfo.record]: sun_vector = -0.485 0.139 0.863 12:13:34.100 [planetinfo.record]: sun_distance = 1061530 12:13:34.101 [planetinfo.record]: corona_flare = 0.050288 12:13:34.101 [planetinfo.record]: corona_hues = 0.503136 12:13:34.101 [planetinfo.record]: sun_color = 0.241348, 0.340815, 0.694199, 1 12:13:34.101 [planetinfo.record]: corona_shimmer = 0.363338 12:13:34.101 [planetinfo.record]: station_vector = 0.278 0.843 0.460 12:13:34.101 [planetinfo.record]: station = coriolis 12:13:39.705 [PLANETINFO OVER]: Done 12:13:39.705 [PLANETINFO LOGGING]: 1 239 12:13:40.713 [planetinfo.record]: seed = 88 169 250 71 12:13:40.713 [planetinfo.record]: coordinates = 169 62 12:13:40.713 [planetinfo.record]: government = 3; 12:13:40.713 [planetinfo.record]: economy = 6; 12:13:40.713 [planetinfo.record]: techlevel = 4; 12:13:40.713 [planetinfo.record]: population = 26; 12:13:40.713 [planetinfo.record]: productivity = 5824; 12:13:40.713 [planetinfo.record]: name = "Soaleed"; 12:13:40.713 [planetinfo.record]: inhabitant = "Fierce Yellow Lizard"; 12:13:40.713 [planetinfo.record]: inhabitants = "Fierce Yellow Lizards"; 12:13:40.713 [planetinfo.record]: description = "This world is most fabled for Soaleedian Atonilno water but scourged by evil disease."; 12:13:40.724 [planetinfo.record]: air_color = 0.652618, 0.748446, 0.991195, 1; 12:13:40.724 [planetinfo.record]: cloud_alpha = 1.000000; 12:13:40.724 [planetinfo.record]: cloud_color = 0.56076, 0.908345, 0.976562, 1; 12:13:40.724 [planetinfo.record]: cloud_fraction = 0.330000; 12:13:40.724 [planetinfo.record]: land_color = 0.2475, 0.66, 0.263613, 1; 12:13:40.724 [planetinfo.record]: land_fraction = 0.250000; 12:13:40.724 [planetinfo.record]: polar_cloud_color = 0.689452, 0.898437, 0.939453, 1; 12:13:40.724 [planetinfo.record]: polar_land_color = 0.788063, 0.934, 0.793763, 1; 12:13:40.724 [planetinfo.record]: polar_sea_color = 0.93132, 0.937695, 0.88669, 1; 12:13:40.724 [planetinfo.record]: sea_color = 0.606102, 0.623047, 0.487485, 1; 12:13:40.724 [planetinfo.record]: rotation_speed = 0.002792 12:13:40.724 [planetinfo.record]: planet zpos = 621010.000000 12:13:40.724 [planetinfo.record]: sun_radius = 161157.418976 12:13:40.724 [planetinfo.record]: sun_vector = -0.223 0.241 -0.945 12:13:40.724 [planetinfo.record]: sun_distance = 1003170 12:13:40.724 [planetinfo.record]: corona_flare = 0.089069 12:13:40.731 [planetinfo.record]: corona_hues = 0.685196 12:13:40.731 [planetinfo.record]: sun_color = 0.777322, 0.362251, 0.260408, 1 12:13:40.732 [planetinfo.record]: corona_shimmer = 1.307477 12:13:40.732 [planetinfo.record]: station_vector = 0.996 0.085 0.018 12:13:40.732 [planetinfo.record]: station = coriolis 12:13:45.745 [PLANETINFO OVER]: Done 12:13:45.746 [PLANETINFO LOGGING]: 1 240 12:13:46.770 [planetinfo.record]: seed = 208 152 166 233 12:13:46.770 [planetinfo.record]: coordinates = 152 33 12:13:46.770 [planetinfo.record]: government = 2; 12:13:46.770 [planetinfo.record]: economy = 1; 12:13:46.770 [planetinfo.record]: techlevel = 7; 12:13:46.770 [planetinfo.record]: population = 32; 12:13:46.770 [planetinfo.record]: productivity = 13824; 12:13:46.770 [planetinfo.record]: name = "Esgebi"; 12:13:46.770 [planetinfo.record]: inhabitant = "Small Bug-Eyed Lizard"; 12:13:46.771 [planetinfo.record]: inhabitants = "Small Bug-Eyed Lizards"; 12:13:46.771 [planetinfo.record]: description = "The world Esgebi is reasonably famous for the Esgebiian evil Atthoid."; 12:13:46.781 [planetinfo.record]: air_color = 0.475847, 0.55546, 0.896238, 1; 12:13:46.781 [planetinfo.record]: cloud_alpha = 1.000000; 12:13:46.782 [planetinfo.record]: cloud_color = 0.132339, 0.442447, 0.691406, 1; 12:13:46.782 [planetinfo.record]: cloud_fraction = 0.340000; 12:13:46.782 [planetinfo.record]: land_color = 0.149531, 0.420718, 0.66, 1; 12:13:46.782 [planetinfo.record]: land_fraction = 0.580000; 12:13:46.782 [planetinfo.record]: polar_cloud_color = 0.40121, 0.628589, 0.811133, 1; 12:13:46.782 [planetinfo.record]: polar_land_color = 0.753402, 0.849345, 0.934, 1; 12:13:46.782 [planetinfo.record]: polar_sea_color = 0.879815, 0.928125, 0.900951, 1; 12:13:46.782 [planetinfo.record]: sea_color = 0.569104, 0.71875, 0.634574, 1; 12:13:46.782 [planetinfo.record]: rotation_speed = 0.002707 12:13:46.782 [planetinfo.record]: planet zpos = 632640.000000 12:13:46.782 [planetinfo.record]: sun_radius = 147506.756592 12:13:46.782 [planetinfo.record]: sun_vector = 0.092 -0.779 0.621 12:13:46.782 [planetinfo.record]: sun_distance = 1107120 12:13:46.782 [planetinfo.record]: corona_flare = 0.001913 12:13:46.782 [planetinfo.record]: corona_hues = 0.593750 12:13:46.782 [planetinfo.record]: sun_color = 0.530502, 0.711513, 0.738596, 1 12:13:46.783 [planetinfo.record]: corona_shimmer = 0.335226 12:13:46.783 [planetinfo.record]: station_vector = -0.970 0.215 0.111 12:13:46.783 [planetinfo.record]: station = coriolis 12:13:51.939 [PLANETINFO OVER]: Done 12:13:51.940 [PLANETINFO LOGGING]: 1 241 12:13:52.955 [planetinfo.record]: seed = 48 179 186 124 12:13:52.955 [planetinfo.record]: coordinates = 179 38 12:13:52.955 [planetinfo.record]: government = 6; 12:13:52.955 [planetinfo.record]: economy = 6; 12:13:52.955 [planetinfo.record]: techlevel = 7; 12:13:52.955 [planetinfo.record]: population = 41; 12:13:52.955 [planetinfo.record]: productivity = 13120; 12:13:52.955 [planetinfo.record]: name = "Telace"; 12:13:52.955 [planetinfo.record]: inhabitant = "Blue Furry Humanoid"; 12:13:52.955 [planetinfo.record]: inhabitants = "Blue Furry Humanoids"; 12:13:52.955 [planetinfo.record]: description = "Telace is an unremarkable dump."; 12:13:52.962 [planetinfo.record]: air_color = 0.656659, 0.540742, 0.905344, 1; 12:13:52.963 [planetinfo.record]: cloud_alpha = 1.000000; 12:13:52.963 [planetinfo.record]: cloud_color = 0.705063, 0.280762, 0.71875, 1; 12:13:52.963 [planetinfo.record]: cloud_fraction = 0.360000; 12:13:52.963 [planetinfo.record]: land_color = 0.475123, 0.329811, 0.607422, 1; 12:13:52.963 [planetinfo.record]: land_fraction = 0.240000; 12:13:52.963 [planetinfo.record]: polar_cloud_color = 0.813637, 0.509824, 0.823438, 1; 12:13:52.963 [planetinfo.record]: polar_land_color = 0.888114, 0.83194, 0.939258, 1; 12:13:52.963 [planetinfo.record]: polar_sea_color = 0.942578, 0.929238, 0.893792, 1; 12:13:52.963 [planetinfo.record]: sea_color = 0.574219, 0.541712, 0.455338, 1; 12:13:52.963 [planetinfo.record]: rotation_speed = 0.003756 12:13:52.963 [planetinfo.record]: planet zpos = 728040.000000 12:13:52.963 [planetinfo.record]: sun_radius = 105254.155273 12:13:52.963 [planetinfo.record]: sun_vector = -0.587 0.657 -0.473 12:13:52.964 [planetinfo.record]: sun_distance = 1092060 12:13:52.964 [planetinfo.record]: corona_flare = 0.050157 12:13:52.964 [planetinfo.record]: corona_hues = 0.673065 12:13:52.964 [planetinfo.record]: sun_color = 0.799072, 0.788866, 0.779338, 1 12:13:52.964 [planetinfo.record]: corona_shimmer = 0.486345 12:13:52.964 [planetinfo.record]: station_vector = -0.146 -0.988 0.045 12:13:52.964 [planetinfo.record]: station = coriolis 12:13:57.915 [PLANETINFO OVER]: Done 12:13:57.916 [PLANETINFO LOGGING]: 1 242 12:13:58.920 [planetinfo.record]: seed = 120 88 54 52 12:13:58.920 [planetinfo.record]: coordinates = 88 133 12:13:58.920 [planetinfo.record]: government = 7; 12:13:58.920 [planetinfo.record]: economy = 5; 12:13:58.920 [planetinfo.record]: techlevel = 6; 12:13:58.920 [planetinfo.record]: population = 37; 12:13:58.920 [planetinfo.record]: productivity = 16280; 12:13:58.920 [planetinfo.record]: name = "Raenonce"; 12:13:58.920 [planetinfo.record]: inhabitant = "Human Colonial"; 12:13:58.920 [planetinfo.record]: inhabitants = "Human Colonials"; 12:13:58.920 [planetinfo.record]: description = "The world Raenonce is a dull world."; 12:13:58.936 [planetinfo.record]: air_color = 0.753744, 0.616192, 0.868922, 1; 12:13:58.936 [planetinfo.record]: cloud_alpha = 1.000000; 12:13:58.936 [planetinfo.record]: cloud_color = 0.609375, 0.442749, 0.501328, 1; 12:13:58.936 [planetinfo.record]: cloud_fraction = 0.300000; 12:13:58.936 [planetinfo.record]: land_color = 0.382515, 0.338135, 0.541016, 1; 12:13:58.936 [planetinfo.record]: land_fraction = 0.690000; 12:13:58.936 [planetinfo.record]: polar_cloud_color = 0.774219, 0.641906, 0.688422, 1; 12:13:58.936 [planetinfo.record]: polar_land_color = 0.876619, 0.85722, 0.945898, 1; 12:13:58.936 [planetinfo.record]: polar_sea_color = 0.93418, 0.89168, 0.873057, 1; 12:13:58.936 [planetinfo.record]: sea_color = 0.658203, 0.538426, 0.485939, 1; 12:13:58.936 [planetinfo.record]: rotation_speed = 0.001052 12:13:58.936 [planetinfo.record]: planet zpos = 549920.000000 12:13:58.936 [planetinfo.record]: sun_radius = 81095.914307 12:13:58.936 [planetinfo.record]: sun_vector = 0.584 -0.743 0.327 12:13:58.936 [planetinfo.record]: sun_distance = 746320 12:13:58.936 [planetinfo.record]: corona_flare = 0.016075 12:13:58.936 [planetinfo.record]: corona_hues = 0.827576 12:13:58.936 [planetinfo.record]: sun_color = 0.799454, 0.508077, 0.251327, 1 12:13:58.936 [planetinfo.record]: corona_shimmer = 0.551195 12:13:58.937 [planetinfo.record]: station_vector = 0.769 -0.198 0.608 12:13:58.937 [planetinfo.record]: station = coriolis 12:14:04.103 [PLANETINFO OVER]: Done 12:14:04.104 [PLANETINFO LOGGING]: 1 243 12:14:05.108 [planetinfo.record]: seed = 232 229 154 151 12:14:05.108 [planetinfo.record]: coordinates = 229 159 12:14:05.108 [planetinfo.record]: government = 5; 12:14:05.108 [planetinfo.record]: economy = 7; 12:14:05.108 [planetinfo.record]: techlevel = 4; 12:14:05.108 [planetinfo.record]: population = 29; 12:14:05.108 [planetinfo.record]: productivity = 6264; 12:14:05.108 [planetinfo.record]: name = "Titequ"; 12:14:05.108 [planetinfo.record]: inhabitant = "Black Horned Humanoid"; 12:14:05.108 [planetinfo.record]: inhabitants = "Black Horned Humanoids"; 12:14:05.108 [planetinfo.record]: description = "The world Titequ is notable for its great volcanoes and its inhabitants’ eccentric shyness."; 12:14:05.140 [planetinfo.record]: air_color = 0.486532, 0.610521, 0.871523, 1; 12:14:05.140 [planetinfo.record]: cloud_alpha = 1.000000; 12:14:05.140 [planetinfo.record]: cloud_color = 0.168762, 0.596168, 0.617188, 1; 12:14:05.140 [planetinfo.record]: cloud_fraction = 0.220000; 12:14:05.140 [planetinfo.record]: land_color = 0.66, 0.260391, 0.569464, 1; 12:14:05.140 [planetinfo.record]: land_fraction = 0.530000; 12:14:05.140 [planetinfo.record]: polar_cloud_color = 0.424564, 0.76118, 0.777734, 1; 12:14:05.140 [planetinfo.record]: polar_land_color = 0.934, 0.792623, 0.901969, 1; 12:14:05.140 [planetinfo.record]: polar_sea_color = 0.931836, 0.88581, 0.860856, 1; 12:14:05.140 [planetinfo.record]: sea_color = 0.681641, 0.546968, 0.473953, 1; 12:14:05.140 [planetinfo.record]: rotation_speed = 0.002213 12:14:05.140 [planetinfo.record]: planet zpos = 725550.000000 12:14:05.140 [planetinfo.record]: sun_radius = 155561.775665 12:14:05.140 [planetinfo.record]: sun_vector = 0.395 0.767 -0.505 12:14:05.140 [planetinfo.record]: sun_distance = 870660 12:14:05.140 [planetinfo.record]: corona_flare = 0.081523 12:14:05.140 [planetinfo.record]: corona_hues = 0.918518 12:14:05.141 [planetinfo.record]: sun_color = 0.575151, 0.640468, 0.844971, 1 12:14:05.141 [planetinfo.record]: corona_shimmer = 0.511257 12:14:05.141 [planetinfo.record]: station_vector = -0.414 0.817 0.401 12:14:05.141 [planetinfo.record]: station = coriolis 12:14:10.241 [PLANETINFO OVER]: Done 12:14:10.242 [PLANETINFO LOGGING]: 1 244 12:14:11.262 [planetinfo.record]: seed = 128 78 102 5 12:14:11.262 [planetinfo.record]: coordinates = 78 154 12:14:11.262 [planetinfo.record]: government = 0; 12:14:11.262 [planetinfo.record]: economy = 2; 12:14:11.262 [planetinfo.record]: techlevel = 7; 12:14:11.262 [planetinfo.record]: population = 31; 12:14:11.262 [planetinfo.record]: productivity = 7936; 12:14:11.262 [planetinfo.record]: name = "Cerexe"; 12:14:11.262 [planetinfo.record]: inhabitant = "Human Colonial"; 12:14:11.262 [planetinfo.record]: inhabitants = "Human Colonials"; 12:14:11.262 [planetinfo.record]: description = "The planet Cerexe is well known for its inhabitants’ ancient loathing of casinos but plagued by occasional solar activity."; 12:14:11.273 [planetinfo.record]: air_color = 0.731994, 0.501175, 0.958025, 1; 12:14:11.274 [planetinfo.record]: cloud_alpha = 1.000000; 12:14:11.274 [planetinfo.record]: cloud_color = 0.876953, 0.147301, 0.489325, 1; 12:14:11.274 [planetinfo.record]: cloud_fraction = 0.430000; 12:14:11.274 [planetinfo.record]: land_color = 0.376406, 0.613473, 0.66, 1; 12:14:11.274 [planetinfo.record]: land_fraction = 0.590000; 12:14:11.274 [planetinfo.record]: polar_cloud_color = 0.894629, 0.429404, 0.647478, 1; 12:14:11.274 [planetinfo.record]: polar_land_color = 0.833668, 0.917539, 0.934, 1; 12:14:11.274 [planetinfo.record]: polar_sea_color = 0.945898, 0.916781, 0.897495, 1; 12:14:11.274 [planetinfo.record]: sea_color = 0.541016, 0.474399, 0.430277, 1; 12:14:11.274 [planetinfo.record]: rotation_speed = 0.001967 12:14:11.274 [planetinfo.record]: planet zpos = 459140.000000 12:14:11.275 [planetinfo.record]: sun_radius = 76125.694275 12:14:11.275 [planetinfo.record]: sun_vector = 0.235 0.734 0.637 12:14:11.275 [planetinfo.record]: sun_distance = 667840 12:14:11.275 [planetinfo.record]: corona_flare = 0.046973 12:14:11.275 [planetinfo.record]: corona_hues = 0.747940 12:14:11.275 [planetinfo.record]: sun_color = 0.6633, 0.658278, 0.208759, 1 12:14:11.275 [planetinfo.record]: corona_shimmer = 0.513026 12:14:11.275 [planetinfo.record]: station_vector = -0.632 0.132 0.763 12:14:11.275 [planetinfo.record]: station = coriolis 12:14:15.588 [PLANETINFO OVER]: Done 12:14:15.589 [PLANETINFO LOGGING]: 1 245 12:14:16.592 [planetinfo.record]: seed = 128 53 154 101 12:14:16.592 [planetinfo.record]: coordinates = 53 66 12:14:16.592 [planetinfo.record]: government = 0; 12:14:16.592 [planetinfo.record]: economy = 2; 12:14:16.592 [planetinfo.record]: techlevel = 6; 12:14:16.592 [planetinfo.record]: population = 27; 12:14:16.592 [planetinfo.record]: productivity = 6912; 12:14:16.592 [planetinfo.record]: name = "Ceised"; 12:14:16.592 [planetinfo.record]: inhabitant = "Fierce Blue Rodent"; 12:14:16.592 [planetinfo.record]: inhabitants = "Fierce Blue Rodents"; 12:14:16.592 [planetinfo.record]: description = "This planet is a dull world."; 12:14:16.604 [planetinfo.record]: air_color = 0.630171, 0.592503, 0.837053, 1; 12:14:16.604 [planetinfo.record]: cloud_alpha = 1.000000; 12:14:16.604 [planetinfo.record]: cloud_color = 0.439101, 0.371208, 0.513672, 1; 12:14:16.604 [planetinfo.record]: cloud_fraction = 0.370000; 12:14:16.604 [planetinfo.record]: land_color = 0.173172, 0.568359, 0.420164, 1; 12:14:16.605 [planetinfo.record]: land_fraction = 0.340000; 12:14:16.605 [planetinfo.record]: polar_cloud_color = 0.664813, 0.604415, 0.731152, 1; 12:14:16.605 [planetinfo.record]: polar_land_color = 0.779216, 0.943164, 0.881683, 1; 12:14:16.605 [planetinfo.record]: polar_sea_color = 0.926953, 0.839825, 0.825568, 1; 12:14:16.605 [planetinfo.record]: sea_color = 0.730469, 0.45583, 0.410889, 1; 12:14:16.605 [planetinfo.record]: rotation_speed = 0.003101 12:14:16.605 [planetinfo.record]: planet zpos = 456390.000000 12:14:16.605 [planetinfo.record]: sun_radius = 125225.906067 12:14:16.605 [planetinfo.record]: sun_vector = -0.872 -0.296 -0.390 12:14:16.605 [planetinfo.record]: sun_distance = 705330 12:14:16.605 [planetinfo.record]: corona_flare = 0.002356 12:14:16.605 [planetinfo.record]: corona_hues = 0.783638 12:14:16.605 [planetinfo.record]: sun_color = 0.747122, 0.578429, 0.507556, 1 12:14:16.606 [planetinfo.record]: corona_shimmer = 0.261425 12:14:16.606 [planetinfo.record]: station_vector = -0.880 -0.024 0.475 12:14:16.606 [planetinfo.record]: station = coriolis 12:14:21.206 [PLANETINFO OVER]: Done 12:14:21.207 [PLANETINFO LOGGING]: 1 246 12:14:22.214 [planetinfo.record]: seed = 232 186 54 16 12:14:22.214 [planetinfo.record]: coordinates = 186 120 12:14:22.214 [planetinfo.record]: government = 5; 12:14:22.214 [planetinfo.record]: economy = 0; 12:14:22.214 [planetinfo.record]: techlevel = 12; 12:14:22.214 [planetinfo.record]: population = 54; 12:14:22.214 [planetinfo.record]: productivity = 38880; 12:14:22.214 [planetinfo.record]: name = "Ergere"; 12:14:22.214 [planetinfo.record]: inhabitant = "Human Colonial"; 12:14:22.215 [planetinfo.record]: inhabitants = "Human Colonials"; 12:14:22.215 [planetinfo.record]: description = "This planet is plagued by frequent solar activity."; 12:14:22.236 [planetinfo.record]: air_color = 0.571315, 0.478532, 0.940465, 1; 12:14:22.236 [planetinfo.record]: cloud_alpha = 1.000000; 12:14:22.236 [planetinfo.record]: cloud_color = 0.548138, 0.103027, 0.824219, 1; 12:14:22.236 [planetinfo.record]: cloud_fraction = 0.360000; 12:14:22.236 [planetinfo.record]: land_color = 0.385513, 0.309486, 0.595703, 1; 12:14:22.236 [planetinfo.record]: land_fraction = 0.350000; 12:14:22.236 [planetinfo.record]: polar_cloud_color = 0.688575, 0.394626, 0.870898, 1; 12:14:22.237 [planetinfo.record]: polar_land_color = 0.857473, 0.827468, 0.94043, 1; 12:14:22.237 [planetinfo.record]: polar_sea_color = 0.926172, 0.869586, 0.832108, 1; 12:14:22.237 [planetinfo.record]: sea_color = 0.738281, 0.557857, 0.438354, 1; 12:14:22.237 [planetinfo.record]: rotation_speed = 0.000426 12:14:22.237 [planetinfo.record]: planet zpos = 360240.000000 12:14:22.237 [planetinfo.record]: sun_radius = 92825.819702 12:14:22.237 [planetinfo.record]: sun_vector = 0.006 0.997 -0.075 12:14:22.237 [planetinfo.record]: sun_distance = 600400 12:14:22.237 [planetinfo.record]: corona_flare = 0.079730 12:14:22.237 [planetinfo.record]: corona_hues = 0.563873 12:14:22.237 [planetinfo.record]: sun_color = 0.298103, 0.338366, 0.78078, 1 12:14:22.237 [planetinfo.record]: corona_shimmer = 0.287192 12:14:22.237 [planetinfo.record]: station_vector = -0.450 -0.239 0.860 12:14:22.238 [planetinfo.record]: station = icosahedron 12:14:26.590 [PLANETINFO OVER]: Done 12:14:26.591 [PLANETINFO LOGGING]: 1 247 12:14:27.595 [planetinfo.record]: seed = 248 97 186 179 12:14:27.595 [planetinfo.record]: coordinates = 97 14 12:14:27.595 [planetinfo.record]: government = 7; 12:14:27.595 [planetinfo.record]: economy = 6; 12:14:27.595 [planetinfo.record]: techlevel = 6; 12:14:27.595 [planetinfo.record]: population = 38; 12:14:27.595 [planetinfo.record]: productivity = 13376; 12:14:27.596 [planetinfo.record]: name = "Bezaorat"; 12:14:27.596 [planetinfo.record]: inhabitant = "Harmless Lizard"; 12:14:27.596 [planetinfo.record]: inhabitants = "Harmless Lizards"; 12:14:27.596 [planetinfo.record]: description = "Bezaorat is ravaged by unpredictable civil war."; 12:14:27.608 [planetinfo.record]: air_color = 0.538683, 0.591886, 0.844857, 1; 12:14:27.609 [planetinfo.record]: cloud_alpha = 1.000000; 12:14:27.609 [planetinfo.record]: cloud_color = 0.274849, 0.408028, 0.537109, 1; 12:14:27.609 [planetinfo.record]: cloud_fraction = 0.340000; 12:14:27.609 [planetinfo.record]: land_color = 0.351861, 0.323723, 0.548828, 1; 12:14:27.609 [planetinfo.record]: land_fraction = 0.450000; 12:14:27.609 [planetinfo.record]: polar_cloud_color = 0.515351, 0.630293, 0.741699, 1; 12:14:27.609 [planetinfo.record]: polar_land_color = 0.86032, 0.848206, 0.945117, 1; 12:14:27.609 [planetinfo.record]: polar_sea_color = 0.869839, 0.921875, 0.914558, 1; 12:14:27.609 [planetinfo.record]: sea_color = 0.604858, 0.78125, 0.756445, 1; 12:14:27.609 [planetinfo.record]: rotation_speed = 0.001581 12:14:27.609 [planetinfo.record]: planet zpos = 368100.000000 12:14:27.609 [planetinfo.record]: sun_radius = 99958.112183 12:14:27.609 [planetinfo.record]: sun_vector = 0.420 0.487 0.766 12:14:27.609 [planetinfo.record]: sun_distance = 699390 12:14:27.609 [planetinfo.record]: corona_flare = 0.045465 12:14:27.609 [planetinfo.record]: corona_hues = 0.781143 12:14:27.609 [planetinfo.record]: sun_color = 0.6961, 0.584325, 0.495176, 1 12:14:27.609 [planetinfo.record]: corona_shimmer = 0.589533 12:14:27.610 [planetinfo.record]: station_vector = -0.781 0.594 0.191 12:14:27.610 [planetinfo.record]: station = coriolis 12:14:33.135 [PLANETINFO OVER]: Done 12:14:33.136 [PLANETINFO LOGGING]: 1 248 12:14:34.138 [planetinfo.record]: seed = 176 17 166 111 12:14:34.138 [planetinfo.record]: coordinates = 17 57 12:14:34.138 [planetinfo.record]: government = 6; 12:14:34.138 [planetinfo.record]: economy = 1; 12:14:34.138 [planetinfo.record]: techlevel = 10; 12:14:34.138 [planetinfo.record]: population = 48; 12:14:34.138 [planetinfo.record]: productivity = 34560; 12:14:34.138 [planetinfo.record]: name = "Aanteso"; 12:14:34.138 [planetinfo.record]: inhabitant = "Blue Slimy Lobster"; 12:14:34.138 [planetinfo.record]: inhabitants = "Blue Slimy Lobsters"; 12:14:34.139 [planetinfo.record]: description = "The world Aanteso is beset by dreadful earthquakes."; 12:14:34.152 [planetinfo.record]: air_color = 0.781323, 0.493206, 0.974285, 1; 12:14:34.152 [planetinfo.record]: cloud_alpha = 1.000000; 12:14:34.152 [planetinfo.record]: cloud_color = 0.925781, 0.10849, 0.261732, 1; 12:14:34.152 [planetinfo.record]: cloud_fraction = 0.350000; 12:14:34.152 [planetinfo.record]: land_color = 0.634219, 0.649728, 0.66, 1; 12:14:34.152 [planetinfo.record]: land_fraction = 0.570000; 12:14:34.152 [planetinfo.record]: polar_cloud_color = 0.916602, 0.410859, 0.505686, 1; 12:14:34.152 [planetinfo.record]: polar_land_color = 0.924879, 0.930366, 0.934, 1; 12:14:34.152 [planetinfo.record]: polar_sea_color = 0.937891, 0.937076, 0.885775, 1; 12:14:34.152 [planetinfo.record]: sea_color = 0.621094, 0.618937, 0.483046, 1; 12:14:34.152 [planetinfo.record]: rotation_speed = 0.001381 12:14:34.152 [planetinfo.record]: planet zpos = 1000950.000000 12:14:34.152 [planetinfo.record]: sun_radius = 139916.526642 12:14:34.152 [planetinfo.record]: sun_vector = -0.145 0.748 -0.648 12:14:34.153 [planetinfo.record]: sun_distance = 1267870 12:14:34.153 [planetinfo.record]: corona_flare = 0.020007 12:14:34.153 [planetinfo.record]: corona_hues = 0.646965 12:14:34.153 [planetinfo.record]: sun_color = 0.588992, 0.646785, 0.66265, 1 12:14:34.153 [planetinfo.record]: corona_shimmer = 0.351328 12:14:34.153 [planetinfo.record]: station_vector = -0.844 0.250 0.475 12:14:34.153 [planetinfo.record]: station = coriolis 12:14:39.183 [PLANETINFO OVER]: Done 12:14:39.186 [PLANETINFO LOGGING]: 1 249 12:14:40.191 [planetinfo.record]: seed = 80 103 250 94 12:14:40.191 [planetinfo.record]: coordinates = 103 60 12:14:40.191 [planetinfo.record]: government = 2; 12:14:40.191 [planetinfo.record]: economy = 4; 12:14:40.191 [planetinfo.record]: techlevel = 7; 12:14:40.191 [planetinfo.record]: population = 35; 12:14:40.191 [planetinfo.record]: productivity = 10080; 12:14:40.191 [planetinfo.record]: name = "Rixees"; 12:14:40.191 [planetinfo.record]: inhabitant = "Yellow Bony Humanoid"; 12:14:40.191 [planetinfo.record]: inhabitants = "Yellow Bony Humanoids"; 12:14:40.191 [planetinfo.record]: description = "This planet is fabled for its weird volcanoes."; 12:14:40.208 [planetinfo.record]: air_color = 0.612623, 0.922979, 0.937863, 1; 12:14:40.208 [planetinfo.record]: cloud_alpha = 1.000000; 12:14:40.208 [planetinfo.record]: cloud_color = 0.762914, 0.816406, 0.456039, 1; 12:14:40.208 [planetinfo.record]: cloud_fraction = 0.420000; 12:14:40.208 [planetinfo.record]: land_color = 0.538828, 0.589001, 0.66, 1; 12:14:40.208 [planetinfo.record]: land_fraction = 0.300000; 12:14:40.208 [planetinfo.record]: polar_cloud_color = 0.831863, 0.867383, 0.62809, 1; 12:14:40.208 [planetinfo.record]: polar_land_color = 0.891131, 0.908881, 0.934, 1; 12:14:40.208 [planetinfo.record]: polar_sea_color = 0.947461, 0.788939, 0.711521, 1; 12:14:40.208 [planetinfo.record]: sea_color = 0.525391, 0.173773, 0.00205231, 1; 12:14:40.208 [planetinfo.record]: rotation_speed = 0.002480 12:14:40.208 [planetinfo.record]: planet zpos = 910420.000000 12:14:40.209 [planetinfo.record]: sun_radius = 174230.309753 12:14:40.209 [planetinfo.record]: sun_vector = 0.139 0.990 0.012 12:14:40.209 [planetinfo.record]: sun_distance = 1430660 12:14:40.209 [planetinfo.record]: corona_flare = 0.042265 12:14:40.209 [planetinfo.record]: corona_hues = 0.884277 12:14:40.209 [planetinfo.record]: sun_color = 0.845251, 0.816022, 0.579937, 1 12:14:40.209 [planetinfo.record]: corona_shimmer = 0.441397 12:14:40.209 [planetinfo.record]: station_vector = 0.638 0.768 0.052 12:14:40.209 [planetinfo.record]: station = coriolis 12:14:45.250 [PLANETINFO OVER]: Done 12:14:45.251 [PLANETINFO LOGGING]: 1 250 12:14:46.256 [planetinfo.record]: seed = 216 42 182 246 12:14:46.257 [planetinfo.record]: coordinates = 42 201 12:14:46.257 [planetinfo.record]: government = 3; 12:14:46.257 [planetinfo.record]: economy = 1; 12:14:46.257 [planetinfo.record]: techlevel = 10; 12:14:46.257 [planetinfo.record]: population = 45; 12:14:46.257 [planetinfo.record]: productivity = 22680; 12:14:46.257 [planetinfo.record]: name = "Vearin"; 12:14:46.257 [planetinfo.record]: inhabitant = "Bony Humanoid"; 12:14:46.257 [planetinfo.record]: inhabitants = "Bony Humanoids"; 12:14:46.257 [planetinfo.record]: description = "Vearin is most famous for its pink Vearinian A’stou A’stouweed plantations and its exciting Vearinian evil juice."; 12:14:46.280 [planetinfo.record]: air_color = 0.535107, 0.566064, 0.858516, 1; 12:14:46.280 [planetinfo.record]: cloud_alpha = 1.000000; 12:14:46.280 [planetinfo.record]: cloud_color = 0.270996, 0.354977, 0.578125, 1; 12:14:46.280 [planetinfo.record]: cloud_fraction = 0.230000; 12:14:46.280 [planetinfo.record]: land_color = 0.66, 0.56332, 0.273281, 1; 12:14:46.280 [planetinfo.record]: land_fraction = 0.570000; 12:14:46.280 [planetinfo.record]: polar_cloud_color = 0.507761, 0.576775, 0.760156, 1; 12:14:46.280 [planetinfo.record]: polar_land_color = 0.934, 0.899796, 0.797184, 1; 12:14:46.280 [planetinfo.record]: polar_sea_color = 0.897636, 0.93457, 0.887294, 1; 12:14:46.280 [planetinfo.record]: sea_color = 0.550865, 0.654297, 0.521904, 1; 12:14:46.280 [planetinfo.record]: rotation_speed = 0.004884 12:14:46.280 [planetinfo.record]: planet zpos = 439400.000000 12:14:46.281 [planetinfo.record]: sun_radius = 113184.253235 12:14:46.281 [planetinfo.record]: sun_vector = -0.020 -0.586 -0.810 12:14:46.281 [planetinfo.record]: sun_distance = 878800 12:14:46.281 [planetinfo.record]: corona_flare = 0.025499 12:14:46.281 [planetinfo.record]: corona_hues = 0.800385 12:14:46.281 [planetinfo.record]: sun_color = 0.204105, 0.631215, 0.766754, 1 12:14:46.281 [planetinfo.record]: corona_shimmer = 0.408594 12:14:46.281 [planetinfo.record]: station_vector = -0.783 0.361 0.506 12:14:46.281 [planetinfo.record]: station = coriolis 12:14:51.641 [PLANETINFO OVER]: Done 12:14:51.642 [PLANETINFO LOGGING]: 1 251 12:14:52.663 [planetinfo.record]: seed = 136 237 90 228 12:14:52.663 [planetinfo.record]: coordinates = 237 12 12:14:52.663 [planetinfo.record]: government = 1; 12:14:52.663 [planetinfo.record]: economy = 6; 12:14:52.663 [planetinfo.record]: techlevel = 3; 12:14:52.663 [planetinfo.record]: population = 20; 12:14:52.663 [planetinfo.record]: productivity = 3200; 12:14:52.663 [planetinfo.record]: name = "Zaria"; 12:14:52.663 [planetinfo.record]: inhabitant = "Human Colonial"; 12:14:52.663 [planetinfo.record]: inhabitants = "Human Colonials"; 12:14:52.663 [planetinfo.record]: description = "This world is most notable for its fabulous cuisine but plagued by evil spotted cats."; 12:14:52.700 [planetinfo.record]: air_color = 0.583363, 0.94862, 0.959977, 1; 12:14:52.700 [planetinfo.record]: cloud_alpha = 1.000000; 12:14:52.700 [planetinfo.record]: cloud_color = 0.830977, 0.882812, 0.372437, 1; 12:14:52.700 [planetinfo.record]: cloud_fraction = 0.440000; 12:14:52.700 [planetinfo.record]: land_color = 0.549141, 0.66, 0.576855, 1; 12:14:52.700 [planetinfo.record]: land_fraction = 0.410000; 12:14:52.700 [planetinfo.record]: polar_cloud_color = 0.864338, 0.897266, 0.573058, 1; 12:14:52.700 [planetinfo.record]: polar_land_color = 0.894779, 0.934, 0.904584, 1; 12:14:52.701 [planetinfo.record]: polar_sea_color = 0.907325, 0.928906, 0.868854, 1; 12:14:52.701 [planetinfo.record]: sea_color = 0.644869, 0.710938, 0.527093, 1; 12:14:52.701 [planetinfo.record]: rotation_speed = 0.000999 12:14:52.701 [planetinfo.record]: planet zpos = 489240.000000 12:14:52.701 [planetinfo.record]: sun_radius = 103138.718719 12:14:52.701 [planetinfo.record]: sun_vector = -0.883 0.156 -0.442 12:14:52.701 [planetinfo.record]: sun_distance = 896940 12:14:52.701 [planetinfo.record]: corona_flare = 0.012926 12:14:52.701 [planetinfo.record]: corona_hues = 0.916412 12:14:52.701 [planetinfo.record]: sun_color = 0.755914, 0.553127, 0.335773, 1 12:14:52.701 [planetinfo.record]: corona_shimmer = 0.495509 12:14:52.701 [planetinfo.record]: station_vector = -0.732 -0.643 0.226 12:14:52.701 [planetinfo.record]: station = coriolis 12:14:57.643 [PLANETINFO OVER]: Done 12:14:57.644 [PLANETINFO LOGGING]: 1 252 12:14:58.650 [planetinfo.record]: seed = 96 114 102 0 12:14:58.650 [planetinfo.record]: coordinates = 114 175 12:14:58.650 [planetinfo.record]: government = 4; 12:14:58.650 [planetinfo.record]: economy = 7; 12:14:58.650 [planetinfo.record]: techlevel = 4; 12:14:58.650 [planetinfo.record]: population = 28; 12:14:58.650 [planetinfo.record]: productivity = 5376; 12:14:58.650 [planetinfo.record]: name = "Xelaed"; 12:14:58.650 [planetinfo.record]: inhabitant = "Human Colonial"; 12:14:58.650 [planetinfo.record]: inhabitants = "Human Colonials"; 12:14:58.650 [planetinfo.record]: description = "The world Xelaed is a boring world."; 12:14:58.665 [planetinfo.record]: air_color = 0.614105, 0.887133, 0.795213, 1; 12:14:58.666 [planetinfo.record]: cloud_alpha = 1.000000; 12:14:58.666 [planetinfo.record]: cloud_color = 0.664062, 0.448761, 0.448761, 1; 12:14:58.666 [planetinfo.record]: cloud_fraction = 0.400000; 12:14:58.666 [planetinfo.record]: land_color = 0.601048, 0.601562, 0.585114, 1; 12:14:58.666 [planetinfo.record]: land_fraction = 0.430000; 12:14:58.666 [planetinfo.record]: polar_cloud_color = 0.798828, 0.636956, 0.636956, 1; 12:14:58.666 [planetinfo.record]: polar_land_color = 0.939643, 0.939844, 0.933419, 1; 12:14:58.666 [planetinfo.record]: polar_sea_color = 0.875687, 0.916406, 0.816174, 1; 12:14:58.667 [planetinfo.record]: sea_color = 0.687363, 0.835938, 0.470215, 1; 12:14:58.667 [planetinfo.record]: rotation_speed = 0.000720 12:14:58.667 [planetinfo.record]: planet zpos = 322300.000000 12:14:58.667 [planetinfo.record]: sun_radius = 85823.748779 12:14:58.667 [planetinfo.record]: sun_vector = 0.892 0.231 -0.389 12:14:58.667 [planetinfo.record]: sun_distance = 498100 12:14:58.667 [planetinfo.record]: corona_flare = 0.067090 12:14:58.667 [planetinfo.record]: corona_hues = 0.623276 12:14:58.667 [planetinfo.record]: sun_color = 0.734902, 0.824944, 0.841113, 1 12:14:58.667 [planetinfo.record]: corona_shimmer = 0.536776 12:14:58.667 [planetinfo.record]: station_vector = -0.559 -0.823 0.099 12:14:58.667 [planetinfo.record]: station = coriolis 12:15:03.275 [PLANETINFO OVER]: Done 12:15:03.276 [PLANETINFO LOGGING]: 1 253 12:15:04.278 [planetinfo.record]: seed = 160 184 218 112 12:15:04.279 [planetinfo.record]: coordinates = 184 149 12:15:04.279 [planetinfo.record]: government = 4; 12:15:04.279 [planetinfo.record]: economy = 5; 12:15:04.279 [planetinfo.record]: techlevel = 4; 12:15:04.279 [planetinfo.record]: population = 26; 12:15:04.279 [planetinfo.record]: productivity = 8320; 12:15:04.279 [planetinfo.record]: name = "Errius"; 12:15:04.279 [planetinfo.record]: inhabitant = "Blue Furry Humanoid"; 12:15:04.279 [planetinfo.record]: inhabitants = "Blue Furry Humanoids"; 12:15:04.279 [planetinfo.record]: description = "Errius is an unremarkable dump."; 12:15:04.281 [planetinfo.record]: air_color = 0.754789, 0.642697, 0.842906, 1; 12:15:04.281 [planetinfo.record]: cloud_alpha = 1.000000; 12:15:04.281 [planetinfo.record]: cloud_color = 0.53125, 0.471069, 0.489406, 1; 12:15:04.281 [planetinfo.record]: cloud_fraction = 0.130000; 12:15:04.282 [planetinfo.record]: land_color = 0.5, 0.460938, 0.479248, 1; 12:15:04.282 [planetinfo.record]: land_fraction = 0.650000; 12:15:04.282 [planetinfo.record]: polar_cloud_color = 0.739062, 0.686736, 0.702679, 1; 12:15:04.282 [planetinfo.record]: polar_land_color = 0.95, 0.931445, 0.940143, 1; 12:15:04.282 [planetinfo.record]: polar_sea_color = 0.941797, 0.913702, 0.888913, 1; 12:15:04.282 [planetinfo.record]: sea_color = 0.582031, 0.512581, 0.451302, 1; 12:15:04.282 [planetinfo.record]: rotation_speed = 0.001876 12:15:04.282 [planetinfo.record]: planet zpos = 300000.000000 12:15:04.282 [planetinfo.record]: sun_radius = 84089.355469 12:15:04.282 [planetinfo.record]: sun_vector = 0.028 0.871 0.491 12:15:04.282 [planetinfo.record]: sun_distance = 480000 12:15:04.282 [planetinfo.record]: corona_flare = 0.063846 12:15:04.282 [planetinfo.record]: corona_hues = 0.847626 12:15:04.282 [planetinfo.record]: sun_color = 0.752319, 0.627398, 0.598974, 1 12:15:04.282 [planetinfo.record]: corona_shimmer = 0.298366 12:15:04.282 [planetinfo.record]: station_vector = 0.346 -0.923 0.169 12:15:04.282 [planetinfo.record]: station = coriolis 12:15:08.928 [PLANETINFO OVER]: Done 12:15:08.929 [PLANETINFO LOGGING]: 1 254 12:15:09.932 [planetinfo.record]: seed = 72 24 182 191 12:15:09.932 [planetinfo.record]: coordinates = 24 232 12:15:09.932 [planetinfo.record]: government = 1; 12:15:09.932 [planetinfo.record]: economy = 2; 12:15:09.932 [planetinfo.record]: techlevel = 6; 12:15:09.932 [planetinfo.record]: population = 28; 12:15:09.932 [planetinfo.record]: productivity = 8960; 12:15:09.932 [planetinfo.record]: name = "Oneded"; 12:15:09.932 [planetinfo.record]: inhabitant = "Harmless Slimy Lobster"; 12:15:09.932 [planetinfo.record]: inhabitants = "Harmless Slimy Lobsters"; 12:15:09.932 [planetinfo.record]: description = "This world is a tedious little planet."; 12:15:09.944 [planetinfo.record]: air_color = 0.648735, 0.879328, 0.843089, 1; 12:15:09.945 [planetinfo.record]: cloud_alpha = 1.000000; 12:15:09.945 [planetinfo.record]: cloud_color = 0.640625, 0.585258, 0.520508, 1; 12:15:09.945 [planetinfo.record]: cloud_fraction = 0.110000; 12:15:09.945 [planetinfo.record]: land_color = 0.66, 0.0644531, 0.0784113, 1; 12:15:09.945 [planetinfo.record]: land_fraction = 0.480000; 12:15:09.945 [planetinfo.record]: polar_cloud_color = 0.788281, 0.745701, 0.695905, 1; 12:15:09.945 [planetinfo.record]: polar_land_color = 0.934, 0.723303, 0.728241, 1; 12:15:09.945 [planetinfo.record]: polar_sea_color = 0.939062, 0.890407, 0.879179, 1; 12:15:09.945 [planetinfo.record]: sea_color = 0.609375, 0.483081, 0.453937, 1; 12:15:09.945 [planetinfo.record]: rotation_speed = 0.004231 12:15:09.945 [planetinfo.record]: planet zpos = 1002000.000000 12:15:09.946 [planetinfo.record]: sun_radius = 179626.928711 12:15:09.946 [planetinfo.record]: sun_vector = 0.113 -0.893 0.435 12:15:09.946 [planetinfo.record]: sun_distance = 1402800 12:15:09.946 [planetinfo.record]: corona_flare = 0.072545 12:15:09.946 [planetinfo.record]: corona_hues = 0.666351 12:15:09.946 [planetinfo.record]: sun_color = 0.76463, 0.780624, 0.789719, 1 12:15:09.946 [planetinfo.record]: corona_shimmer = 0.416541 12:15:09.946 [planetinfo.record]: station_vector = -0.193 -0.927 0.321 12:15:09.946 [planetinfo.record]: station = coriolis 12:15:14.503 [PLANETINFO OVER]: Done 12:15:14.504 [PLANETINFO LOGGING]: 1 255 12:15:15.525 [planetinfo.record]: seed = 152 24 122 113 12:15:15.525 [planetinfo.record]: coordinates = 24 152 12:15:15.525 [planetinfo.record]: government = 3; 12:15:15.525 [planetinfo.record]: economy = 0; 12:15:15.525 [planetinfo.record]: techlevel = 9; 12:15:15.525 [planetinfo.record]: population = 40; 12:15:15.525 [planetinfo.record]: productivity = 22400; 12:15:15.526 [planetinfo.record]: name = "Atxein"; 12:15:15.526 [planetinfo.record]: inhabitant = "Human Colonial"; 12:15:15.526 [planetinfo.record]: inhabitants = "Human Colonials"; 12:15:15.526 [planetinfo.record]: description = "The world Atxein is very famous for its unusual sit coms but ravaged by lethal spotted batoids."; 12:15:15.540 [planetinfo.record]: air_color = 0.735662, 0.602826, 0.874125, 1; 12:15:15.540 [planetinfo.record]: cloud_alpha = 1.000000; 12:15:15.540 [planetinfo.record]: cloud_color = 0.625, 0.41748, 0.524483, 1; 12:15:15.540 [planetinfo.record]: cloud_fraction = 0.220000; 12:15:15.540 [planetinfo.record]: land_color = 0.513047, 0.525676, 0.66, 1; 12:15:15.540 [planetinfo.record]: land_fraction = 0.650000; 12:15:15.540 [planetinfo.record]: polar_cloud_color = 0.78125, 0.619125, 0.702721, 1; 12:15:15.540 [planetinfo.record]: polar_land_color = 0.88201, 0.886478, 0.934, 1; 12:15:15.540 [planetinfo.record]: polar_sea_color = 0.946094, 0.850454, 0.715114, 1; 12:15:15.540 [planetinfo.record]: sea_color = 0.539062, 0.321088, 0.0126343, 1; 12:15:15.540 [planetinfo.record]: rotation_speed = 0.000289 12:15:15.540 [planetinfo.record]: planet zpos = 371520.000000 12:15:15.540 [planetinfo.record]: sun_radius = 98598.076172 12:15:15.540 [planetinfo.record]: sun_vector = -0.677 0.731 -0.088 12:15:15.540 [planetinfo.record]: sun_distance = 712080 12:15:15.540 [planetinfo.record]: corona_flare = 0.033179 12:15:15.540 [planetinfo.record]: corona_hues = 0.561935 12:15:15.540 [planetinfo.record]: sun_color = 0.804926, 0.628848, 0.387436, 1 12:15:15.540 [planetinfo.record]: corona_shimmer = 0.393213 12:15:15.540 [planetinfo.record]: station_vector = 0.967 0.113 0.230 12:15:15.540 [planetinfo.record]: station = coriolis 12:15:20.110 [PLANETINFO OVER]: Done 12:15:37.008 [planetinfo.record]: seed = 161 21 77 108 12:15:37.008 [planetinfo.record]: coordinates = 21 129 12:15:37.008 [planetinfo.record]: government = 4; 12:15:37.008 [planetinfo.record]: economy = 1; 12:15:37.008 [planetinfo.record]: techlevel = 9; 12:15:37.008 [planetinfo.record]: population = 42; 12:15:37.008 [planetinfo.record]: productivity = 24192; 12:15:37.008 [planetinfo.record]: name = "Ingece"; 12:15:37.008 [planetinfo.record]: inhabitant = "Human Colonial"; 12:15:37.008 [planetinfo.record]: inhabitants = "Human Colonials"; 12:15:37.008 [planetinfo.record]: description = "The world Ingece is beset by dreadful earthquakes."; 12:15:37.011 [planetinfo.record]: air_color = 0.583804, 0.508478, 0.9151, 1; 12:15:37.011 [planetinfo.record]: cloud_alpha = 1.000000; 12:15:37.011 [planetinfo.record]: cloud_color = 0.511999, 0.1987, 0.748047, 1; 12:15:37.011 [planetinfo.record]: cloud_fraction = 0.140000; 12:15:37.011 [planetinfo.record]: land_color = 0.513672, 0.44545, 0.488622, 1; 12:15:37.012 [planetinfo.record]: land_fraction = 0.570000; 12:15:37.012 [planetinfo.record]: polar_cloud_color = 0.671623, 0.452625, 0.836621, 1; 12:15:37.012 [planetinfo.record]: polar_land_color = 0.948633, 0.917135, 0.937067, 1; 12:15:37.012 [planetinfo.record]: polar_sea_color = 0.932617, 0.930283, 0.872871, 1; 12:15:37.012 [planetinfo.record]: sea_color = 0.673828, 0.667083, 0.50116, 1; 12:15:37.012 [planetinfo.record]: rotation_speed = 0.000364 12:15:37.012 [planetinfo.record]: planet zpos = 709080.000000 12:15:37.012 [planetinfo.record]: sun_radius = 188288.964996 12:15:37.012 [planetinfo.record]: sun_vector = 0.195 0.566 -0.801 12:15:37.012 [planetinfo.record]: sun_distance = 1122710 12:15:37.013 [planetinfo.record]: corona_flare = 0.050887 12:15:37.013 [planetinfo.record]: corona_hues = 0.961090 12:15:37.013 [planetinfo.record]: sun_color = 0.223857, 0.662681, 0.835138, 1 12:15:37.013 [planetinfo.record]: corona_shimmer = 0.398187 12:15:37.013 [planetinfo.record]: station_vector = 0.784 0.204 0.586 12:15:37.013 [planetinfo.record]: station = coriolis 12:15:41.272 [PLANETINFO OVER]: Done 12:15:46.890 [PLANETINFO LOGGING]: 2 0 12:15:47.677 [planetinfo.record]: seed = 33 8 77 222 12:15:47.677 [planetinfo.record]: coordinates = 8 105 12:15:47.677 [planetinfo.record]: government = 4; 12:15:47.677 [planetinfo.record]: economy = 1; 12:15:47.677 [planetinfo.record]: techlevel = 8; 12:15:47.677 [planetinfo.record]: population = 38; 12:15:47.677 [planetinfo.record]: productivity = 21888; 12:15:47.677 [planetinfo.record]: name = "Riave"; 12:15:47.677 [planetinfo.record]: inhabitant = "Human Colonial"; 12:15:47.677 [planetinfo.record]: inhabitants = "Human Colonials"; 12:15:47.677 [planetinfo.record]: description = "This world is a revolting little planet."; 12:15:47.678 [planetinfo.record]: air_color = 0.684708, 0.591601, 0.864369, 1; 12:15:47.679 [planetinfo.record]: cloud_alpha = 1.000000; 12:15:47.679 [planetinfo.record]: cloud_color = 0.595703, 0.388603, 0.587613, 1; 12:15:47.679 [planetinfo.record]: cloud_fraction = 0.270000; 12:15:47.679 [planetinfo.record]: land_color = 0.609243, 0.567188, 0.66, 1; 12:15:47.679 [planetinfo.record]: land_fraction = 0.620000; 12:15:47.679 [planetinfo.record]: polar_cloud_color = 0.768066, 0.601177, 0.761547, 1; 12:15:47.679 [planetinfo.record]: polar_land_color = 0.916043, 0.901164, 0.934, 1; 12:15:47.679 [planetinfo.record]: polar_sea_color = 0.925586, 0.855824, 0.825254, 1; 12:15:47.679 [planetinfo.record]: sea_color = 0.744141, 0.519795, 0.421486, 1; 12:15:47.679 [planetinfo.record]: rotation_speed = 0.000415 12:15:47.679 [planetinfo.record]: planet zpos = 833040.000000 12:15:47.679 [planetinfo.record]: sun_radius = 167265.461426 12:15:47.679 [planetinfo.record]: sun_vector = 0.236 0.967 0.094 12:15:47.679 [planetinfo.record]: sun_distance = 1089360 12:15:47.679 [planetinfo.record]: corona_flare = 0.069116 12:15:47.679 [planetinfo.record]: corona_hues = 0.642853 12:15:47.679 [planetinfo.record]: sun_color = 0.691306, 0.512031, 0.370278, 1 12:15:47.680 [planetinfo.record]: corona_shimmer = 0.389770 12:15:47.680 [planetinfo.record]: station_vector = -0.302 -0.780 0.548 12:15:47.680 [planetinfo.record]: station = coriolis 12:15:52.533 [PLANETINFO OVER]: Done 12:15:52.534 [PLANETINFO LOGGING]: 2 1 12:15:53.538 [planetinfo.record]: seed = 233 99 133 233 12:15:53.538 [planetinfo.record]: coordinates = 99 54 12:15:53.538 [planetinfo.record]: government = 5; 12:15:53.538 [planetinfo.record]: economy = 6; 12:15:53.538 [planetinfo.record]: techlevel = 7; 12:15:53.538 [planetinfo.record]: population = 40; 12:15:53.538 [planetinfo.record]: productivity = 11520; 12:15:53.538 [planetinfo.record]: name = "Esgeer"; 12:15:53.538 [planetinfo.record]: inhabitant = "Small Furry Feline"; 12:15:53.539 [planetinfo.record]: inhabitants = "Small Furry Felines"; 12:15:53.539 [planetinfo.record]: description = "The planet Esgeer is famous for Esgeerian shrew steak but ravaged by occasional solar activity."; 12:15:53.541 [planetinfo.record]: air_color = 0.618938, 0.582642, 0.844857, 1; 12:15:53.541 [planetinfo.record]: cloud_alpha = 1.000000; 12:15:53.541 [planetinfo.record]: cloud_color = 0.435401, 0.358772, 0.537109, 1; 12:15:53.541 [planetinfo.record]: cloud_fraction = 0.410000; 12:15:53.541 [planetinfo.record]: land_color = 0.554297, 0.627794, 0.66, 1; 12:15:53.541 [planetinfo.record]: land_fraction = 0.530000; 12:15:53.541 [planetinfo.record]: polar_cloud_color = 0.653918, 0.587782, 0.741699, 1; 12:15:53.541 [planetinfo.record]: polar_land_color = 0.896604, 0.922606, 0.934, 1; 12:15:53.541 [planetinfo.record]: polar_sea_color = 0.922656, 0.83639, 0.806423, 1; 12:15:53.541 [planetinfo.record]: sea_color = 0.773438, 0.484177, 0.383698, 1; 12:15:53.541 [planetinfo.record]: rotation_speed = 0.004809 12:15:53.541 [planetinfo.record]: planet zpos = 730660.000000 12:15:53.541 [planetinfo.record]: sun_radius = 105150.076447 12:15:53.541 [planetinfo.record]: sun_vector = 0.298 0.779 -0.551 12:15:53.542 [planetinfo.record]: sun_distance = 1043800 12:15:53.542 [planetinfo.record]: corona_flare = 0.028461 12:15:53.542 [planetinfo.record]: corona_hues = 0.568405 12:15:53.542 [planetinfo.record]: sun_color = 0.813629, 0.768146, 0.759112, 1 12:15:53.542 [planetinfo.record]: corona_shimmer = 0.496583 12:15:53.542 [planetinfo.record]: station_vector = 0.761 0.637 0.126 12:15:53.542 [planetinfo.record]: station = coriolis 12:15:57.846 [PLANETINFO OVER]: Done 12:15:57.846 [PLANETINFO LOGGING]: 2 2 12:15:58.865 [planetinfo.record]: seed = 217 61 45 146 12:15:58.865 [planetinfo.record]: coordinates = 61 208 12:15:58.865 [planetinfo.record]: government = 3; 12:15:58.865 [planetinfo.record]: economy = 0; 12:15:58.865 [planetinfo.record]: techlevel = 10; 12:15:58.865 [planetinfo.record]: population = 44; 12:15:58.865 [planetinfo.record]: productivity = 24640; 12:15:58.865 [planetinfo.record]: name = "Enerza"; 12:15:58.865 [planetinfo.record]: inhabitant = "Human Colonial"; 12:15:58.865 [planetinfo.record]: inhabitants = "Human Colonials"; 12:15:58.865 [planetinfo.record]: description = "This world is noted for Zero-G hockey."; 12:15:58.868 [planetinfo.record]: air_color = 0.622629, 0.587971, 0.838354, 1; 12:15:58.868 [planetinfo.record]: cloud_alpha = 1.000000; 12:15:58.868 [planetinfo.record]: cloud_color = 0.429946, 0.363922, 0.517578, 1; 12:15:58.868 [planetinfo.record]: cloud_fraction = 0.180000; 12:15:58.868 [planetinfo.record]: land_color = 0.210938, 0.5, 0.364502, 1; 12:15:58.868 [planetinfo.record]: land_fraction = 0.390000; 12:15:58.868 [planetinfo.record]: polar_cloud_color = 0.655354, 0.596921, 0.73291, 1; 12:15:58.868 [planetinfo.record]: polar_land_color = 0.812695, 0.95, 0.885638, 1; 12:15:58.868 [planetinfo.record]: polar_sea_color = 0.939258, 0.879153, 0.877711, 1; 12:15:58.868 [planetinfo.record]: sea_color = 0.607422, 0.451942, 0.448211, 1; 12:15:58.868 [planetinfo.record]: rotation_speed = 0.003825 12:15:58.869 [planetinfo.record]: planet zpos = 338900.000000 12:15:58.869 [planetinfo.record]: sun_radius = 73372.139587 12:15:58.869 [planetinfo.record]: sun_vector = -0.163 0.893 -0.421 12:15:58.869 [planetinfo.record]: sun_distance = 677800 12:15:58.869 [planetinfo.record]: corona_flare = 0.085286 12:15:58.869 [planetinfo.record]: corona_hues = 0.702461 12:15:58.869 [planetinfo.record]: sun_color = 0.823547, 0.754123, 0.499166, 1 12:15:58.869 [planetinfo.record]: corona_shimmer = 0.418953 12:15:58.869 [planetinfo.record]: station_vector = -0.535 -0.820 0.204 12:15:58.869 [planetinfo.record]: station = coriolis 12:16:03.563 [PLANETINFO OVER]: Done 12:16:03.563 [PLANETINFO LOGGING]: 2 3 12:16:04.566 [planetinfo.record]: seed = 1 164 213 181 12:16:04.566 [planetinfo.record]: coordinates = 164 112 12:16:04.566 [planetinfo.record]: government = 0; 12:16:04.566 [planetinfo.record]: economy = 2; 12:16:04.566 [planetinfo.record]: techlevel = 5; 12:16:04.567 [planetinfo.record]: population = 23; 12:16:04.567 [planetinfo.record]: productivity = 5888; 12:16:04.567 [planetinfo.record]: name = "Laarzace"; 12:16:04.567 [planetinfo.record]: inhabitant = "Harmless Fat Humanoid"; 12:16:04.567 [planetinfo.record]: inhabitants = "Harmless Fat Humanoids"; 12:16:04.567 [planetinfo.record]: description = "The planet Laarzace is a boring world."; 12:16:04.571 [planetinfo.record]: air_color = 0.647443, 0.900952, 0.904693, 1; 12:16:04.571 [planetinfo.record]: cloud_alpha = 1.000000; 12:16:04.571 [planetinfo.record]: cloud_color = 0.706844, 0.716797, 0.534798, 1; 12:16:04.571 [planetinfo.record]: cloud_fraction = 0.270000; 12:16:04.571 [planetinfo.record]: land_color = 0.348717, 0.334076, 0.521484, 1; 12:16:04.571 [planetinfo.record]: land_fraction = 0.650000; 12:16:04.571 [planetinfo.record]: polar_cloud_color = 0.81542, 0.822559, 0.692026, 1; 12:16:04.571 [planetinfo.record]: polar_land_color = 0.869346, 0.862693, 0.947852, 1; 12:16:04.571 [planetinfo.record]: polar_sea_color = 0.88554, 0.928125, 0.87193, 1; 12:16:04.572 [planetinfo.record]: sea_color = 0.586836, 0.71875, 0.544678, 1; 12:16:04.572 [planetinfo.record]: rotation_speed = 0.000108 12:16:04.572 [planetinfo.record]: planet zpos = 596400.000000 12:16:04.572 [planetinfo.record]: sun_radius = 134926.217651 12:16:04.572 [planetinfo.record]: sun_vector = 0.422 -0.541 0.728 12:16:04.572 [planetinfo.record]: sun_distance = 809400 12:16:04.572 [planetinfo.record]: corona_flare = 0.087219 12:16:04.572 [planetinfo.record]: corona_hues = 0.684875 12:16:04.572 [planetinfo.record]: sun_color = 0.246903, 0.483899, 0.830392, 1 12:16:04.572 [planetinfo.record]: corona_shimmer = 0.316409 12:16:04.572 [planetinfo.record]: station_vector = 0.972 -0.170 0.164 12:16:04.573 [planetinfo.record]: station = coriolis 12:16:09.042 [PLANETINFO OVER]: Done 12:16:09.042 [PLANETINFO LOGGING]: 2 4 12:16:10.062 [planetinfo.record]: seed = 49 165 141 148 12:16:10.062 [planetinfo.record]: coordinates = 165 36 12:16:10.062 [planetinfo.record]: government = 6; 12:16:10.062 [planetinfo.record]: economy = 4; 12:16:10.062 [planetinfo.record]: techlevel = 7; 12:16:10.063 [planetinfo.record]: population = 39; 12:16:10.063 [planetinfo.record]: productivity = 18720; 12:16:10.063 [planetinfo.record]: name = "Raried"; 12:16:10.063 [planetinfo.record]: inhabitant = "Black Bug-Eyed Frog"; 12:16:10.063 [planetinfo.record]: inhabitants = "Black Bug-Eyed Frogs"; 12:16:10.063 [planetinfo.record]: description = "The planet Raried is an unremarkable dump."; 12:16:10.088 [planetinfo.record]: air_color = 0.779707, 0.629941, 0.867621, 1; 12:16:10.088 [planetinfo.record]: cloud_alpha = 1.000000; 12:16:10.088 [planetinfo.record]: cloud_color = 0.605469, 0.470657, 0.483296, 1; 12:16:10.088 [planetinfo.record]: cloud_fraction = 0.300000; 12:16:10.088 [planetinfo.record]: land_color = 0.62157, 0.613594, 0.66, 1; 12:16:10.088 [planetinfo.record]: land_fraction = 0.330000; 12:16:10.088 [planetinfo.record]: polar_cloud_color = 0.772461, 0.664965, 0.675043, 1; 12:16:10.088 [planetinfo.record]: polar_land_color = 0.920404, 0.917582, 0.934, 1; 12:16:10.088 [planetinfo.record]: polar_sea_color = 0.920117, 0.849988, 0.798813, 1; 12:16:10.088 [planetinfo.record]: sea_color = 0.798828, 0.555289, 0.377571, 1; 12:16:10.088 [planetinfo.record]: rotation_speed = 0.003530 12:16:10.088 [planetinfo.record]: planet zpos = 440550.000000 12:16:10.088 [planetinfo.record]: sun_radius = 120292.389679 12:16:10.088 [planetinfo.record]: sun_vector = 0.382 0.613 0.692 12:16:10.088 [planetinfo.record]: sun_distance = 760950 12:16:10.088 [planetinfo.record]: corona_flare = 0.013658 12:16:10.088 [planetinfo.record]: corona_hues = 0.696556 12:16:10.089 [planetinfo.record]: sun_color = 0.713535, 0.768312, 0.787289, 1 12:16:10.089 [planetinfo.record]: corona_shimmer = 0.341060 12:16:10.089 [planetinfo.record]: station_vector = -0.233 -0.183 0.955 12:16:10.089 [planetinfo.record]: station = coriolis 12:16:14.067 [PLANETINFO OVER]: Done 12:16:14.068 [PLANETINFO LOGGING]: 2 5 12:16:15.070 [planetinfo.record]: seed = 249 138 101 129 12:16:15.070 [planetinfo.record]: coordinates = 138 152 12:16:15.070 [planetinfo.record]: government = 7; 12:16:15.071 [planetinfo.record]: economy = 0; 12:16:15.071 [planetinfo.record]: techlevel = 13; 12:16:15.071 [planetinfo.record]: population = 60; 12:16:15.071 [planetinfo.record]: productivity = 52800; 12:16:15.071 [planetinfo.record]: name = "Lezaer"; 12:16:15.071 [planetinfo.record]: inhabitant = "Human Colonial"; 12:16:15.071 [planetinfo.record]: inhabitants = "Human Colonials"; 12:16:15.071 [planetinfo.record]: description = "Lezaer is cursed by vicious vicious shrews."; 12:16:15.083 [planetinfo.record]: air_color = 0.518295, 0.616597, 0.847459, 1; 12:16:15.083 [planetinfo.record]: cloud_alpha = 1.000000; 12:16:15.083 [planetinfo.record]: cloud_color = 0.236275, 0.508752, 0.544922, 1; 12:16:15.083 [planetinfo.record]: cloud_fraction = 0.460000; 12:16:15.083 [planetinfo.record]: land_color = 0.0515625, 0.66, 0.389055, 1; 12:16:15.084 [planetinfo.record]: land_fraction = 0.360000; 12:16:15.084 [planetinfo.record]: polar_cloud_color = 0.481406, 0.7143, 0.745215, 1; 12:16:15.084 [planetinfo.record]: polar_land_color = 0.718742, 0.934, 0.838143, 1; 12:16:15.084 [planetinfo.record]: polar_sea_color = 0.902664, 0.932031, 0.87833, 1; 12:16:15.084 [planetinfo.record]: sea_color = 0.594021, 0.679688, 0.523041, 1; 12:16:15.084 [planetinfo.record]: rotation_speed = 0.001642 12:16:15.084 [planetinfo.record]: planet zpos = 353100.000000 12:16:15.084 [planetinfo.record]: sun_radius = 80380.778503 12:16:15.084 [planetinfo.record]: sun_vector = 0.375 0.277 -0.885 12:16:15.084 [planetinfo.record]: sun_distance = 738300 12:16:15.084 [planetinfo.record]: corona_flare = 0.034705 12:16:15.084 [planetinfo.record]: corona_hues = 0.796341 12:16:15.084 [planetinfo.record]: sun_color = 0.765417, 0.748005, 0.746549, 1 12:16:15.084 [planetinfo.record]: corona_shimmer = 0.337804 12:16:15.085 [planetinfo.record]: station_vector = -0.759 0.520 0.392 12:16:15.085 [planetinfo.record]: station = dodecahedron 12:16:20.427 [PLANETINFO OVER]: Done 12:16:20.428 [PLANETINFO LOGGING]: 2 6 12:16:21.448 [planetinfo.record]: seed = 169 214 237 43 12:16:21.448 [planetinfo.record]: coordinates = 214 176 12:16:21.448 [planetinfo.record]: government = 5; 12:16:21.448 [planetinfo.record]: economy = 0; 12:16:21.448 [planetinfo.record]: techlevel = 12; 12:16:21.448 [planetinfo.record]: population = 54; 12:16:21.449 [planetinfo.record]: productivity = 38880; 12:16:21.449 [planetinfo.record]: name = "Mabelala"; 12:16:21.449 [planetinfo.record]: inhabitant = "Small Red Frog"; 12:16:21.449 [planetinfo.record]: inhabitants = "Small Red Frogs"; 12:16:21.449 [planetinfo.record]: description = "Mabelala is a revolting little planet."; 12:16:21.470 [planetinfo.record]: air_color = 0.525264, 0.565663, 0.864369, 1; 12:16:21.480 [planetinfo.record]: cloud_alpha = 1.000000; 12:16:21.480 [planetinfo.record]: cloud_color = 0.251312, 0.369697, 0.595703, 1; 12:16:21.480 [planetinfo.record]: cloud_fraction = 0.290000; 12:16:21.480 [planetinfo.record]: land_color = 0.66, 0.520016, 0.301641, 1; 12:16:21.480 [planetinfo.record]: land_fraction = 0.260000; 12:16:21.480 [planetinfo.record]: polar_cloud_color = 0.490542, 0.585941, 0.768066, 1; 12:16:21.480 [planetinfo.record]: polar_land_color = 0.934, 0.884475, 0.807217, 1; 12:16:21.480 [planetinfo.record]: polar_sea_color = 0.873825, 0.869122, 0.91543, 1; 12:16:21.480 [planetinfo.record]: sea_color = 0.69196, 0.67458, 0.845703, 1; 12:16:21.480 [planetinfo.record]: rotation_speed = 0.004427 12:16:21.481 [planetinfo.record]: planet zpos = 701520.000000 12:16:21.481 [planetinfo.record]: sun_radius = 107028.292542 12:16:21.481 [planetinfo.record]: sun_vector = -0.070 0.854 -0.516 12:16:21.481 [planetinfo.record]: sun_distance = 1286120 12:16:21.481 [planetinfo.record]: corona_flare = 0.023087 12:16:21.481 [planetinfo.record]: corona_hues = 0.754532 12:16:21.481 [planetinfo.record]: sun_color = 0.763513, 0.611755, 0.255826, 1 12:16:21.481 [planetinfo.record]: corona_shimmer = 0.645027 12:16:21.482 [planetinfo.record]: station_vector = -0.491 0.844 0.217 12:16:21.482 [planetinfo.record]: station = dodecahedron 12:16:26.543 [PLANETINFO OVER]: Done 12:16:26.544 [PLANETINFO LOGGING]: 2 7 12:16:27.549 [planetinfo.record]: seed = 81 149 181 254 12:16:27.549 [planetinfo.record]: coordinates = 149 181 12:16:27.549 [planetinfo.record]: government = 2; 12:16:27.549 [planetinfo.record]: economy = 5; 12:16:27.549 [planetinfo.record]: techlevel = 4; 12:16:27.549 [planetinfo.record]: population = 24; 12:16:27.549 [planetinfo.record]: productivity = 5760; 12:16:27.549 [planetinfo.record]: name = "Riarribi"; 12:16:27.549 [planetinfo.record]: inhabitant = "Slimy Lizard"; 12:16:27.549 [planetinfo.record]: inhabitants = "Slimy Lizards"; 12:16:27.549 [planetinfo.record]: description = "The world Riarribi is reasonably noted for its exotic fish meat."; 12:16:27.553 [planetinfo.record]: air_color = 0.604525, 0.896889, 0.803023, 1; 12:16:27.554 [planetinfo.record]: cloud_alpha = 1.000000; 12:16:27.554 [planetinfo.record]: cloud_color = 0.693359, 0.442956, 0.430641, 1; 12:16:27.554 [planetinfo.record]: cloud_fraction = 0.340000; 12:16:27.554 [planetinfo.record]: land_color = 0.591498, 0.66, 0.198516, 1; 12:16:27.554 [planetinfo.record]: land_fraction = 0.280000; 12:16:27.554 [planetinfo.record]: polar_cloud_color = 0.812012, 0.628728, 0.619714, 1; 12:16:27.554 [planetinfo.record]: polar_land_color = 0.909765, 0.934, 0.770732, 1; 12:16:27.554 [planetinfo.record]: polar_sea_color = 0.946289, 0.893984, 0.893984, 1; 12:16:27.554 [planetinfo.record]: sea_color = 0.537109, 0.418358, 0.418358, 1; 12:16:27.554 [planetinfo.record]: rotation_speed = 0.004514 12:16:27.555 [planetinfo.record]: planet zpos = 916860.000000 12:16:27.555 [planetinfo.record]: sun_radius = 151332.704315 12:16:27.555 [planetinfo.record]: sun_vector = -0.234 0.946 -0.223 12:16:27.555 [planetinfo.record]: sun_distance = 1375290 12:16:27.555 [planetinfo.record]: corona_flare = 0.092123 12:16:27.555 [planetinfo.record]: corona_hues = 0.683830 12:16:27.555 [planetinfo.record]: sun_color = 0.462171, 0.551987, 0.717685, 1 12:16:27.555 [planetinfo.record]: corona_shimmer = 0.340847 12:16:27.556 [planetinfo.record]: station_vector = 0.876 0.316 0.364 12:16:27.556 [planetinfo.record]: station = coriolis 12:16:32.818 [PLANETINFO OVER]: Done 12:16:32.819 [PLANETINFO LOGGING]: 2 8 12:16:33.823 [planetinfo.record]: seed = 193 38 205 78 12:16:33.823 [planetinfo.record]: coordinates = 38 222 12:16:33.824 [planetinfo.record]: government = 0; 12:16:33.824 [planetinfo.record]: economy = 6; 12:16:33.824 [planetinfo.record]: techlevel = 3; 12:16:33.824 [planetinfo.record]: population = 19; 12:16:33.824 [planetinfo.record]: productivity = 2432; 12:16:33.824 [planetinfo.record]: name = "Rebees"; 12:16:33.824 [planetinfo.record]: inhabitant = "Yellow Slimy Lizard"; 12:16:33.824 [planetinfo.record]: inhabitants = "Yellow Slimy Lizards"; 12:16:33.824 [planetinfo.record]: description = "The world Rebees is very famous for its fabulous goat burgers but plagued by unpredictable civil war."; 12:16:33.838 [planetinfo.record]: air_color = 0.626341, 0.565044, 0.870223, 1; 12:16:33.838 [planetinfo.record]: cloud_alpha = 1.000000; 12:16:33.838 [planetinfo.record]: cloud_color = 0.5069, 0.335388, 0.613281, 1; 12:16:33.838 [planetinfo.record]: cloud_fraction = 0.250000; 12:16:33.838 [planetinfo.record]: land_color = 0.647919, 0.65625, 0.574219, 1; 12:16:33.838 [planetinfo.record]: land_fraction = 0.450000; 12:16:33.838 [planetinfo.record]: polar_cloud_color = 0.69185, 0.556218, 0.775977, 1; 12:16:33.838 [planetinfo.record]: polar_land_color = 0.931409, 0.934375, 0.905176, 1; 12:16:33.838 [planetinfo.record]: polar_sea_color = 0.763181, 0.94043, 0.873961, 1; 12:16:33.838 [planetinfo.record]: sea_color = 0.146599, 0.595703, 0.427289, 1; 12:16:33.838 [planetinfo.record]: rotation_speed = 0.001604 12:16:33.838 [planetinfo.record]: planet zpos = 836940.000000 12:16:33.838 [planetinfo.record]: sun_radius = 133372.184143 12:16:33.838 [planetinfo.record]: sun_vector = -0.124 -0.991 0.056 12:16:33.838 [planetinfo.record]: sun_distance = 1351980 12:16:33.838 [planetinfo.record]: corona_flare = 0.058136 12:16:33.839 [planetinfo.record]: corona_hues = 0.941437 12:16:33.839 [planetinfo.record]: sun_color = 0.365824, 0.44102, 0.696017, 1 12:16:33.839 [planetinfo.record]: corona_shimmer = 0.455035 12:16:33.839 [planetinfo.record]: station_vector = 0.538 0.526 0.658 12:16:33.839 [planetinfo.record]: station = coriolis 12:16:38.911 [PLANETINFO OVER]: Done 12:16:38.912 [PLANETINFO LOGGING]: 2 9 12:16:39.918 [planetinfo.record]: seed = 137 107 69 136 12:16:39.918 [planetinfo.record]: coordinates = 107 201 12:16:39.918 [planetinfo.record]: government = 1; 12:16:39.918 [planetinfo.record]: economy = 3; 12:16:39.918 [planetinfo.record]: techlevel = 8; 12:16:39.919 [planetinfo.record]: population = 37; 12:16:39.919 [planetinfo.record]: productivity = 10360; 12:16:39.919 [planetinfo.record]: name = "Usteer"; 12:16:39.919 [planetinfo.record]: inhabitant = "Human Colonial"; 12:16:39.919 [planetinfo.record]: inhabitants = "Human Colonials"; 12:16:39.919 [planetinfo.record]: description = "This world is mildly famous for its vast rain forests and its great dense forests."; 12:16:39.932 [planetinfo.record]: air_color = 0.424617, 0.840955, 0.837263, 1; 12:16:39.933 [planetinfo.record]: cloud_alpha = 1.000000; 12:16:39.933 [planetinfo.record]: cloud_color = 0.525391, 0.518111, 0.0595169, 1; 12:16:39.933 [planetinfo.record]: cloud_fraction = 0.460000; 12:16:39.933 [planetinfo.record]: land_color = 0.64294, 0.641953, 0.66, 1; 12:16:39.933 [planetinfo.record]: land_fraction = 0.270000; 12:16:39.933 [planetinfo.record]: polar_cloud_color = 0.736426, 0.730049, 0.328299, 1; 12:16:39.933 [planetinfo.record]: polar_land_color = 0.927964, 0.927615, 0.934, 1; 12:16:39.933 [planetinfo.record]: polar_sea_color = 0.942969, 0.917062, 0.891953, 1; 12:16:39.933 [planetinfo.record]: sea_color = 0.570312, 0.507639, 0.446893, 1; 12:16:39.934 [planetinfo.record]: rotation_speed = 0.003515 12:16:39.934 [planetinfo.record]: planet zpos = 546810.000000 12:16:39.934 [planetinfo.record]: sun_radius = 122085.927429 12:16:39.934 [planetinfo.record]: sun_vector = -0.203 0.677 0.707 12:16:39.934 [planetinfo.record]: sun_distance = 1093620 12:16:39.934 [planetinfo.record]: corona_flare = 0.015396 12:16:39.934 [planetinfo.record]: corona_hues = 0.820435 12:16:39.934 [planetinfo.record]: sun_color = 0.759467, 0.749522, 0.640055, 1 12:16:39.934 [planetinfo.record]: corona_shimmer = 0.269813 12:16:39.935 [planetinfo.record]: station_vector = 0.111 0.154 0.982 12:16:39.935 [planetinfo.record]: station = coriolis 12:16:44.628 [PLANETINFO OVER]: Done 12:16:44.629 [PLANETINFO LOGGING]: 2 10 12:16:45.633 [planetinfo.record]: seed = 249 245 173 99 12:16:45.633 [planetinfo.record]: coordinates = 245 176 12:16:45.633 [planetinfo.record]: government = 7; 12:16:45.633 [planetinfo.record]: economy = 0; 12:16:45.633 [planetinfo.record]: techlevel = 12; 12:16:45.633 [planetinfo.record]: population = 56; 12:16:45.633 [planetinfo.record]: productivity = 49280; 12:16:45.633 [planetinfo.record]: name = "Gearzaen"; 12:16:45.633 [planetinfo.record]: inhabitant = "Large Blue Furry Rodent"; 12:16:45.633 [planetinfo.record]: inhabitants = "Large Blue Furry Rodents"; 12:16:45.633 [planetinfo.record]: description = "The planet Gearzaen is reasonably noted for its exotic fish meat and its exciting sit coms."; 12:16:45.635 [planetinfo.record]: air_color = 0.432591, 0.730299, 0.874775, 1; 12:16:45.635 [planetinfo.record]: cloud_alpha = 1.000000; 12:16:45.635 [planetinfo.record]: cloud_color = 0.055926, 0.626953, 0.0514297, 1; 12:16:45.635 [planetinfo.record]: cloud_fraction = 0.420000; 12:16:45.635 [planetinfo.record]: land_color = 0.631641, 0.652024, 0.66, 1; 12:16:45.635 [planetinfo.record]: land_fraction = 0.650000; 12:16:45.635 [planetinfo.record]: polar_cloud_color = 0.336903, 0.782129, 0.333398, 1; 12:16:45.636 [planetinfo.record]: polar_land_color = 0.923967, 0.931178, 0.934, 1; 12:16:45.636 [planetinfo.record]: polar_sea_color = 0.948047, 0.839074, 0.723071, 1; 12:16:45.636 [planetinfo.record]: sea_color = 0.519531, 0.280662, 0.0263824, 1; 12:16:45.636 [planetinfo.record]: rotation_speed = 0.000082 12:16:45.636 [planetinfo.record]: planet zpos = 382900.000000 12:16:45.636 [planetinfo.record]: sun_radius = 113296.006165 12:16:45.636 [planetinfo.record]: sun_vector = 0.057 0.645 -0.762 12:16:45.636 [planetinfo.record]: sun_distance = 880670 12:16:45.636 [planetinfo.record]: corona_flare = 0.053662 12:16:45.636 [planetinfo.record]: corona_hues = 0.786263 12:16:45.636 [planetinfo.record]: sun_color = 0.696725, 0.516812, 0.43999, 1 12:16:45.636 [planetinfo.record]: corona_shimmer = 0.481535 12:16:45.637 [planetinfo.record]: station_vector = -0.763 -0.563 0.319 12:16:45.637 [planetinfo.record]: station = dodecahedron 12:16:49.936 [PLANETINFO OVER]: Done 12:16:49.937 [PLANETINFO LOGGING]: 2 11 12:16:50.940 [planetinfo.record]: seed = 33 210 149 64 12:16:50.940 [planetinfo.record]: coordinates = 210 100 12:16:50.940 [planetinfo.record]: government = 4; 12:16:50.940 [planetinfo.record]: economy = 4; 12:16:50.940 [planetinfo.record]: techlevel = 7; 12:16:50.941 [planetinfo.record]: population = 37; 12:16:50.941 [planetinfo.record]: productivity = 14208; 12:16:50.941 [planetinfo.record]: name = "Vees"; 12:16:50.941 [planetinfo.record]: inhabitant = "Large Yellow Feline"; 12:16:50.941 [planetinfo.record]: inhabitants = "Large Yellow Felines"; 12:16:50.941 [planetinfo.record]: description = "Vees is a revolting dump."; 12:16:50.943 [planetinfo.record]: air_color = 0.616594, 0.89437, 0.951522, 1; 12:16:50.943 [planetinfo.record]: cloud_alpha = 1.000000; 12:16:50.943 [planetinfo.record]: cloud_color = 0.652303, 0.857422, 0.465553, 1; 12:16:50.943 [planetinfo.record]: cloud_fraction = 0.310000; 12:16:50.943 [planetinfo.record]: land_color = 0.378984, 0.554619, 0.66, 1; 12:16:50.943 [planetinfo.record]: land_fraction = 0.420000; 12:16:50.943 [planetinfo.record]: polar_cloud_color = 0.753392, 0.88584, 0.632805, 1; 12:16:50.943 [planetinfo.record]: polar_land_color = 0.83458, 0.896718, 0.934, 1; 12:16:50.943 [planetinfo.record]: polar_sea_color = 0.944727, 0.92131, 0.896383, 1; 12:16:50.943 [planetinfo.record]: sea_color = 0.552734, 0.497933, 0.439597, 1; 12:16:50.943 [planetinfo.record]: rotation_speed = 0.003811 12:16:50.944 [planetinfo.record]: planet zpos = 302600.000000 12:16:50.944 [planetinfo.record]: sun_radius = 63527.715454 12:16:50.944 [planetinfo.record]: sun_vector = -0.328 -0.678 0.658 12:16:50.944 [planetinfo.record]: sun_distance = 635460 12:16:50.944 [planetinfo.record]: corona_flare = 0.036768 12:16:50.944 [planetinfo.record]: corona_hues = 0.779976 12:16:50.944 [planetinfo.record]: sun_color = 0.842334, 0.646889, 0.432221, 1 12:16:50.944 [planetinfo.record]: corona_shimmer = 0.521211 12:16:50.944 [planetinfo.record]: station_vector = -0.816 -0.544 0.194 12:16:50.944 [planetinfo.record]: station = coriolis 12:16:55.196 [PLANETINFO OVER]: Done 12:16:55.197 [PLANETINFO LOGGING]: 2 12 12:16:56.216 [planetinfo.record]: seed = 209 64 13 65 12:16:56.216 [planetinfo.record]: coordinates = 64 137 12:16:56.216 [planetinfo.record]: government = 2; 12:16:56.216 [planetinfo.record]: economy = 1; 12:16:56.216 [planetinfo.record]: techlevel = 7; 12:16:56.216 [planetinfo.record]: population = 32; 12:16:56.216 [planetinfo.record]: productivity = 13824; 12:16:56.216 [planetinfo.record]: name = "Lemadior"; 12:16:56.216 [planetinfo.record]: inhabitant = "Human Colonial"; 12:16:56.216 [planetinfo.record]: inhabitants = "Human Colonials"; 12:16:56.217 [planetinfo.record]: description = "The world Lemadior is mildly fabled for the Lemadiorian mountain Atetoid but ravaged by vicious disease."; 12:16:56.248 [planetinfo.record]: air_color = 0.684698, 0.85414, 0.894938, 1; 12:16:56.248 [planetinfo.record]: cloud_alpha = 1.000000; 12:16:56.248 [planetinfo.record]: cloud_color = 0.644447, 0.6875, 0.61499, 1; 12:16:56.248 [planetinfo.record]: cloud_fraction = 0.350000; 12:16:56.248 [planetinfo.record]: land_color = 0.609042, 0.66, 0.603281, 1; 12:16:56.248 [planetinfo.record]: land_fraction = 0.570000; 12:16:56.248 [planetinfo.record]: polar_cloud_color = 0.777697, 0.809375, 0.756023, 1; 12:16:56.248 [planetinfo.record]: polar_land_color = 0.915972, 0.934, 0.913934, 1; 12:16:56.248 [planetinfo.record]: polar_sea_color = 0.720266, 0.930078, 0.920243, 1; 12:16:56.249 [planetinfo.record]: sea_color = 0.0682831, 0.699219, 0.669644, 1; 12:16:56.249 [planetinfo.record]: rotation_speed = 0.004726 12:16:56.249 [planetinfo.record]: planet zpos = 376320.000000 12:16:56.249 [planetinfo.record]: sun_radius = 78518.671875 12:16:56.249 [planetinfo.record]: sun_vector = -0.354 0.572 0.740 12:16:56.249 [planetinfo.record]: sun_distance = 658560 12:16:56.249 [planetinfo.record]: corona_flare = 0.036284 12:16:56.249 [planetinfo.record]: corona_hues = 0.852112 12:16:56.249 [planetinfo.record]: sun_color = 0.808554, 0.540952, 0.468824, 1 12:16:56.249 [planetinfo.record]: corona_shimmer = 0.655806 12:16:56.250 [planetinfo.record]: station_vector = -0.501 0.838 0.214 12:16:56.250 [planetinfo.record]: station = coriolis 12:17:01.639 [PLANETINFO OVER]: Done 12:17:01.640 [PLANETINFO LOGGING]: 2 13 12:17:02.642 [planetinfo.record]: seed = 153 217 37 114 12:17:02.643 [planetinfo.record]: coordinates = 217 141 12:17:02.643 [planetinfo.record]: government = 3; 12:17:02.643 [planetinfo.record]: economy = 5; 12:17:02.643 [planetinfo.record]: techlevel = 5; 12:17:02.643 [planetinfo.record]: population = 29; 12:17:02.643 [planetinfo.record]: productivity = 8120; 12:17:02.643 [planetinfo.record]: name = "Enedza"; 12:17:02.643 [planetinfo.record]: inhabitant = "Human Colonial"; 12:17:02.643 [planetinfo.record]: inhabitants = "Human Colonials"; 12:17:02.643 [planetinfo.record]: description = "The planet Enedza is most well known for its vast dense forests."; 12:17:02.652 [planetinfo.record]: air_color = 0.55282, 0.963686, 0.983391, 1; 12:17:02.652 [planetinfo.record]: cloud_alpha = 1.000000; 12:17:02.652 [planetinfo.record]: cloud_color = 0.851989, 0.953125, 0.27179, 1; 12:17:02.652 [planetinfo.record]: cloud_fraction = 0.310000; 12:17:02.652 [planetinfo.record]: land_color = 0.649688, 0.651782, 0.66, 1; 12:17:02.652 [planetinfo.record]: land_fraction = 0.510000; 12:17:02.652 [planetinfo.record]: polar_cloud_color = 0.867303, 0.928906, 0.513892, 1; 12:17:02.652 [planetinfo.record]: polar_land_color = 0.930352, 0.931093, 0.934, 1; 12:17:02.652 [planetinfo.record]: polar_sea_color = 0.785525, 0.939453, 0.722939, 1; 12:17:02.652 [planetinfo.record]: sea_color = 0.208647, 0.605469, 0.0473022, 1; 12:17:02.652 [planetinfo.record]: rotation_speed = 0.000397 12:17:02.659 [planetinfo.record]: planet zpos = 319050.000000 12:17:02.659 [planetinfo.record]: sun_radius = 65255.998230 12:17:02.660 [planetinfo.record]: sun_vector = 0.165 -0.919 0.358 12:17:02.660 [planetinfo.record]: sun_distance = 744450 12:17:02.660 [planetinfo.record]: corona_flare = 0.052644 12:17:02.660 [planetinfo.record]: corona_hues = 0.687317 12:17:02.660 [planetinfo.record]: sun_color = 0.70482, 0.735593, 0.761002, 1 12:17:02.660 [planetinfo.record]: corona_shimmer = 0.444704 12:17:02.660 [planetinfo.record]: station_vector = 0.206 -0.965 0.161 12:17:02.660 [planetinfo.record]: station = coriolis 12:17:07.380 [PLANETINFO OVER]: Done 12:17:07.381 [PLANETINFO LOGGING]: 2 14 12:17:08.383 [planetinfo.record]: seed = 201 111 109 109 12:17:08.384 [planetinfo.record]: coordinates = 111 36 12:17:08.384 [planetinfo.record]: government = 1; 12:17:08.384 [planetinfo.record]: economy = 6; 12:17:08.384 [planetinfo.record]: techlevel = 5; 12:17:08.384 [planetinfo.record]: population = 28; 12:17:08.384 [planetinfo.record]: productivity = 4480; 12:17:08.384 [planetinfo.record]: name = "Dileon"; 12:17:08.384 [planetinfo.record]: inhabitant = "Human Colonial"; 12:17:08.384 [planetinfo.record]: inhabitants = "Human Colonials"; 12:17:08.384 [planetinfo.record]: description = "This world is very well known for Dileonian lethal water and the Dileonian tree wolf."; 12:17:08.386 [planetinfo.record]: air_color = 0.621219, 0.547576, 0.887783, 1; 12:17:08.386 [planetinfo.record]: cloud_alpha = 1.000000; 12:17:08.386 [planetinfo.record]: cloud_color = 0.542784, 0.299187, 0.666016, 1; 12:17:08.386 [planetinfo.record]: cloud_fraction = 0.290000; 12:17:08.387 [planetinfo.record]: land_color = 0.564609, 0.66, 0.657764, 1; 12:17:08.387 [planetinfo.record]: land_fraction = 0.530000; 12:17:08.387 [planetinfo.record]: polar_cloud_color = 0.707227, 0.524417, 0.799707, 1; 12:17:08.387 [planetinfo.record]: polar_land_color = 0.900252, 0.934, 0.933209, 1; 12:17:08.387 [planetinfo.record]: polar_sea_color = 0.925391, 0.857994, 0.82508, 1; 12:17:08.387 [planetinfo.record]: sea_color = 0.746094, 0.528741, 0.422592, 1; 12:17:08.387 [planetinfo.record]: rotation_speed = 0.000691 12:17:08.387 [planetinfo.record]: planet zpos = 875700.000000 12:17:08.387 [planetinfo.record]: sun_radius = 170330.781555 12:17:08.387 [planetinfo.record]: sun_vector = -0.149 -0.619 0.771 12:17:08.387 [planetinfo.record]: sun_distance = 1376100 12:17:08.387 [planetinfo.record]: corona_flare = 0.082321 12:17:08.387 [planetinfo.record]: corona_hues = 0.844589 12:17:08.387 [planetinfo.record]: sun_color = 0.454815, 0.628607, 0.794412, 1 12:17:08.388 [planetinfo.record]: corona_shimmer = 1.160217 12:17:08.388 [planetinfo.record]: station_vector = 0.654 0.639 0.404 12:17:08.388 [planetinfo.record]: station = coriolis 12:17:12.881 [PLANETINFO OVER]: Done 12:17:12.882 [PLANETINFO LOGGING]: 2 15 12:17:13.903 [planetinfo.record]: seed = 113 78 117 47 12:17:13.903 [planetinfo.record]: coordinates = 78 223 12:17:13.903 [planetinfo.record]: government = 6; 12:17:13.903 [planetinfo.record]: economy = 7; 12:17:13.903 [planetinfo.record]: techlevel = 5; 12:17:13.903 [planetinfo.record]: population = 34; 12:17:13.903 [planetinfo.record]: productivity = 8160; 12:17:13.903 [planetinfo.record]: name = "Aisqu"; 12:17:13.903 [planetinfo.record]: inhabitant = "Human Colonial"; 12:17:13.903 [planetinfo.record]: inhabitants = "Human Colonials"; 12:17:13.903 [planetinfo.record]: description = "The world Aisqu is a boring planet."; 12:17:13.913 [planetinfo.record]: air_color = 0.776662, 0.749996, 0.967131, 1; 12:17:13.913 [planetinfo.record]: cloud_alpha = 1.000000; 12:17:13.913 [planetinfo.record]: cloud_color = 0.862874, 0.837181, 0.904297, 1; 12:17:13.913 [planetinfo.record]: cloud_fraction = 0.300000; 12:17:13.913 [planetinfo.record]: land_color = 0.324582, 0.188203, 0.66, 1; 12:17:13.913 [planetinfo.record]: land_fraction = 0.540000; 12:17:13.913 [planetinfo.record]: polar_cloud_color = 0.880969, 0.864864, 0.906934, 1; 12:17:13.914 [planetinfo.record]: polar_land_color = 0.815333, 0.767084, 0.934, 1; 12:17:13.914 [planetinfo.record]: polar_sea_color = 0.897021, 0.934375, 0.886561, 1; 12:17:13.914 [planetinfo.record]: sea_color = 0.551308, 0.65625, 0.521924, 1; 12:17:13.914 [planetinfo.record]: rotation_speed = 0.003158 12:17:13.914 [planetinfo.record]: planet zpos = 740740.000000 12:17:13.914 [planetinfo.record]: sun_radius = 160407.217407 12:17:13.914 [planetinfo.record]: sun_vector = 0.411 0.899 0.153 12:17:13.914 [planetinfo.record]: sun_distance = 1414140 12:17:13.914 [planetinfo.record]: corona_flare = 0.093321 12:17:13.914 [planetinfo.record]: corona_hues = 0.555191 12:17:13.914 [planetinfo.record]: sun_color = 0.265069, 0.474954, 0.826941, 1 12:17:13.914 [planetinfo.record]: corona_shimmer = 0.395878 12:17:13.915 [planetinfo.record]: station_vector = 0.631 -0.740 0.233 12:17:13.915 [planetinfo.record]: station = coriolis 12:17:18.558 [PLANETINFO OVER]: Done 12:17:18.559 [PLANETINFO LOGGING]: 2 16 12:17:19.562 [planetinfo.record]: seed = 97 103 77 159 12:17:19.562 [planetinfo.record]: coordinates = 103 218 12:17:19.562 [planetinfo.record]: government = 4; 12:17:19.562 [planetinfo.record]: economy = 2; 12:17:19.562 [planetinfo.record]: techlevel = 10; 12:17:19.562 [planetinfo.record]: population = 47; 12:17:19.562 [planetinfo.record]: productivity = 24064; 12:17:19.562 [planetinfo.record]: name = "Onleuses"; 12:17:19.562 [planetinfo.record]: inhabitant = "Human Colonial"; 12:17:19.562 [planetinfo.record]: inhabitants = "Human Colonials"; 12:17:19.562 [planetinfo.record]: description = "Onleuses is ravaged by occasional solar activity."; 12:17:19.579 [planetinfo.record]: air_color = 0.469876, 0.675611, 0.861768, 1; 12:17:19.579 [planetinfo.record]: cloud_alpha = 1.000000; 12:17:19.579 [planetinfo.record]: cloud_color = 0.137787, 0.587891, 0.334707, 1; 12:17:19.579 [planetinfo.record]: cloud_fraction = 0.380000; 12:17:19.580 [planetinfo.record]: land_color = 0.461493, 0.29129, 0.648438, 1; 12:17:19.580 [planetinfo.record]: land_fraction = 0.310000; 12:17:19.580 [planetinfo.record]: polar_cloud_color = 0.398701, 0.764551, 0.55876, 1; 12:17:19.580 [planetinfo.record]: polar_land_color = 0.867755, 0.80639, 0.935156, 1; 12:17:19.580 [planetinfo.record]: polar_sea_color = 0.934375, 0.929419, 0.876707, 1; 12:17:19.580 [planetinfo.record]: sea_color = 0.65625, 0.642327, 0.494238, 1; 12:17:19.580 [planetinfo.record]: rotation_speed = 0.002900 12:17:19.580 [planetinfo.record]: planet zpos = 743490.000000 12:17:19.580 [planetinfo.record]: sun_radius = 158691.493378 12:17:19.580 [planetinfo.record]: sun_vector = -0.007 0.673 -0.739 12:17:19.580 [planetinfo.record]: sun_distance = 1622160 12:17:19.580 [planetinfo.record]: corona_flare = 0.011548 12:17:19.580 [planetinfo.record]: corona_hues = 0.611046 12:17:19.580 [planetinfo.record]: sun_color = 0.710455, 0.340184, 0.180419, 1 12:17:19.581 [planetinfo.record]: corona_shimmer = 0.339776 12:17:19.581 [planetinfo.record]: station_vector = 0.386 -0.913 0.131 12:17:19.581 [planetinfo.record]: station = coriolis 12:17:24.666 [PLANETINFO OVER]: Done 12:17:24.667 [PLANETINFO LOGGING]: 2 17 12:17:25.686 [planetinfo.record]: seed = 41 105 5 51 12:17:25.686 [planetinfo.record]: coordinates = 105 232 12:17:25.686 [planetinfo.record]: government = 5; 12:17:25.686 [planetinfo.record]: economy = 0; 12:17:25.686 [planetinfo.record]: techlevel = 11; 12:17:25.686 [planetinfo.record]: population = 50; 12:17:25.687 [planetinfo.record]: productivity = 36000; 12:17:25.687 [planetinfo.record]: name = "Bezaed"; 12:17:25.687 [planetinfo.record]: inhabitant = "Human Colonial"; 12:17:25.687 [planetinfo.record]: inhabitants = "Human Colonials"; 12:17:25.687 [planetinfo.record]: description = "This planet is reasonably famous for the Bezaedian spotted leopard."; 12:17:25.690 [planetinfo.record]: air_color = 0.436183, 0.789682, 0.850711, 1; 12:17:25.690 [planetinfo.record]: cloud_alpha = 1.000000; 12:17:25.690 [planetinfo.record]: cloud_color = 0.337708, 0.554688, 0.0758362, 1; 12:17:25.690 [planetinfo.record]: cloud_fraction = 0.300000; 12:17:25.690 [planetinfo.record]: land_color = 0.597656, 0.429565, 0.516237, 1; 12:17:25.690 [planetinfo.record]: land_fraction = 0.360000; 12:17:25.690 [planetinfo.record]: polar_cloud_color = 0.566342, 0.749609, 0.345157, 1; 12:17:25.691 [planetinfo.record]: polar_land_color = 0.940234, 0.874124, 0.908212, 1; 12:17:25.691 [planetinfo.record]: polar_sea_color = 0.914062, 0.876962, 0.778381, 1; 12:17:25.691 [planetinfo.record]: sea_color = 0.859375, 0.719852, 0.349121, 1; 12:17:25.691 [planetinfo.record]: rotation_speed = 0.002226 12:17:25.691 [planetinfo.record]: planet zpos = 516460.000000 12:17:25.691 [planetinfo.record]: sun_radius = 69890.946503 12:17:25.691 [planetinfo.record]: sun_vector = -0.654 -0.690 -0.308 12:17:25.691 [planetinfo.record]: sun_distance = 737800 12:17:25.691 [planetinfo.record]: corona_flare = 0.022423 12:17:25.691 [planetinfo.record]: corona_hues = 0.560585 12:17:25.691 [planetinfo.record]: sun_color = 0.420702, 0.702576, 0.78858, 1 12:17:25.692 [planetinfo.record]: corona_shimmer = 0.272519 12:17:25.692 [planetinfo.record]: station_vector = 0.321 -0.370 0.872 12:17:25.692 [planetinfo.record]: station = dodecahedron 12:17:30.136 [PLANETINFO OVER]: Done 12:17:30.137 [PLANETINFO LOGGING]: 2 18 12:17:31.139 [planetinfo.record]: seed = 25 216 45 125 12:17:31.139 [planetinfo.record]: coordinates = 216 32 12:17:31.139 [planetinfo.record]: government = 3; 12:17:31.139 [planetinfo.record]: economy = 0; 12:17:31.139 [planetinfo.record]: techlevel = 9; 12:17:31.139 [planetinfo.record]: population = 40; 12:17:31.139 [planetinfo.record]: productivity = 22400; 12:17:31.139 [planetinfo.record]: name = "Islama"; 12:17:31.139 [planetinfo.record]: inhabitant = "Human Colonial"; 12:17:31.139 [planetinfo.record]: inhabitants = "Human Colonials"; 12:17:31.139 [planetinfo.record]: description = "The planet Islama is most famous for the Islamaian spotted shrew."; 12:17:31.152 [planetinfo.record]: air_color = 0.610598, 0.525008, 0.906645, 1; 12:17:31.152 [planetinfo.record]: cloud_alpha = 1.000000; 12:17:31.152 [planetinfo.record]: cloud_color = 0.572691, 0.242767, 0.722656, 1; 12:17:31.152 [planetinfo.record]: cloud_fraction = 0.290000; 12:17:31.152 [planetinfo.record]: land_color = 0.510469, 0.56187, 0.66, 1; 12:17:31.152 [planetinfo.record]: land_fraction = 0.610000; 12:17:31.152 [planetinfo.record]: polar_cloud_color = 0.718168, 0.482707, 0.825195, 1; 12:17:31.152 [planetinfo.record]: polar_land_color = 0.881098, 0.899283, 0.934, 1; 12:17:31.152 [planetinfo.record]: polar_sea_color = 0.932031, 0.93109, 0.871777, 1; 12:17:31.152 [planetinfo.record]: sea_color = 0.679688, 0.676941, 0.503925, 1; 12:17:31.152 [planetinfo.record]: rotation_speed = 0.001340 12:17:31.152 [planetinfo.record]: planet zpos = 636000.000000 12:17:31.152 [planetinfo.record]: sun_radius = 116546.301270 12:17:31.152 [planetinfo.record]: sun_vector = -0.754 0.532 0.387 12:17:31.152 [planetinfo.record]: sun_distance = 1335600 12:17:31.152 [planetinfo.record]: corona_flare = 0.087260 12:17:31.152 [planetinfo.record]: corona_hues = 0.761459 12:17:31.152 [planetinfo.record]: sun_color = 0.206282, 0.619575, 0.743942, 1 12:17:31.152 [planetinfo.record]: corona_shimmer = 0.255800 12:17:31.153 [planetinfo.record]: station_vector = 0.221 -0.798 0.561 12:17:31.153 [planetinfo.record]: station = coriolis 12:17:36.333 [PLANETINFO OVER]: Done 12:17:36.333 [PLANETINFO LOGGING]: 2 19 12:17:37.352 [planetinfo.record]: seed = 65 190 85 255 12:17:37.352 [planetinfo.record]: coordinates = 190 203 12:17:37.352 [planetinfo.record]: government = 0; 12:17:37.352 [planetinfo.record]: economy = 3; 12:17:37.352 [planetinfo.record]: techlevel = 6; 12:17:37.352 [planetinfo.record]: population = 28; 12:17:37.352 [planetinfo.record]: productivity = 6272; 12:17:37.353 [planetinfo.record]: name = "Onusbi"; 12:17:37.353 [planetinfo.record]: inhabitant = "Human Colonial"; 12:17:37.353 [planetinfo.record]: inhabitants = "Human Colonials"; 12:17:37.353 [planetinfo.record]: description = "This planet is plagued by deadly earthquakes."; 12:17:37.370 [planetinfo.record]: air_color = 0.549823, 0.49426, 0.919652, 1; 12:17:37.371 [planetinfo.record]: cloud_alpha = 1.000000; 12:17:37.371 [planetinfo.record]: cloud_color = 0.404849, 0.160675, 0.761719, 1; 12:17:37.371 [planetinfo.record]: cloud_fraction = 0.260000; 12:17:37.371 [planetinfo.record]: land_color = 0.469219, 0.583986, 0.66, 1; 12:17:37.371 [planetinfo.record]: land_fraction = 0.690000; 12:17:37.371 [planetinfo.record]: polar_cloud_color = 0.595996, 0.427148, 0.842773, 1; 12:17:37.371 [planetinfo.record]: polar_land_color = 0.866504, 0.907107, 0.934, 1; 12:17:37.371 [planetinfo.record]: polar_sea_color = 0.942383, 0.936088, 0.894988, 1; 12:17:37.371 [planetinfo.record]: sea_color = 0.576172, 0.560778, 0.460262, 1; 12:17:37.378 [planetinfo.record]: rotation_speed = 0.002645 12:17:37.378 [planetinfo.record]: planet zpos = 753060.000000 12:17:37.378 [planetinfo.record]: sun_radius = 194916.074524 12:17:37.378 [planetinfo.record]: sun_vector = -0.165 0.833 -0.528 12:17:37.378 [planetinfo.record]: sun_distance = 1232280 12:17:37.378 [planetinfo.record]: corona_flare = 0.019768 12:17:37.378 [planetinfo.record]: corona_hues = 0.563065 12:17:37.378 [planetinfo.record]: sun_color = 0.757932, 0.651537, 0.559144, 1 12:17:37.379 [planetinfo.record]: corona_shimmer = 0.354367 12:17:37.379 [planetinfo.record]: station_vector = -0.104 0.180 0.978 12:17:37.379 [planetinfo.record]: station = coriolis 12:17:42.220 [PLANETINFO OVER]: Done 12:17:42.221 [PLANETINFO LOGGING]: 2 20 12:17:43.226 [planetinfo.record]: seed = 113 206 141 157 12:17:43.226 [planetinfo.record]: coordinates = 206 70 12:17:43.226 [planetinfo.record]: government = 6; 12:17:43.226 [planetinfo.record]: economy = 6; 12:17:43.226 [planetinfo.record]: techlevel = 6; 12:17:43.227 [planetinfo.record]: population = 37; 12:17:43.227 [planetinfo.record]: productivity = 11840; 12:17:43.227 [planetinfo.record]: name = "Isenrire"; 12:17:43.227 [planetinfo.record]: inhabitant = "Black Slimy Frog"; 12:17:43.227 [planetinfo.record]: inhabitants = "Black Slimy Frogs"; 12:17:43.227 [planetinfo.record]: description = "The world Isenrire is reasonably noted for its inhabitants’ eccentric love for poetry and its inhabitants’ weird shyness."; 12:17:43.229 [planetinfo.record]: air_color = 0.743462, 0.646674, 0.8325, 1; 12:17:43.230 [planetinfo.record]: cloud_alpha = 1.000000; 12:17:43.230 [planetinfo.record]: cloud_color = 0.5, 0.464844, 0.479675, 1; 12:17:43.230 [planetinfo.record]: cloud_fraction = 0.340000; 12:17:43.230 [planetinfo.record]: land_color = 0.564453, 0.463028, 0.493931, 1; 12:17:43.230 [planetinfo.record]: land_fraction = 0.360000; 12:17:43.230 [planetinfo.record]: polar_cloud_color = 0.725, 0.69314, 0.706581, 1; 12:17:43.230 [planetinfo.record]: polar_land_color = 0.943555, 0.901168, 0.914083, 1; 12:17:43.230 [planetinfo.record]: polar_sea_color = 0.880052, 0.94082, 0.712047, 1; 12:17:43.230 [planetinfo.record]: sea_color = 0.438899, 0.591797, 0.0161819, 1; 12:17:43.230 [planetinfo.record]: rotation_speed = 0.001040 12:17:43.230 [planetinfo.record]: planet zpos = 889000.000000 12:17:43.230 [planetinfo.record]: sun_radius = 109304.374695 12:17:43.230 [planetinfo.record]: sun_vector = -0.234 0.600 -0.765 12:17:43.230 [planetinfo.record]: sun_distance = 1270000 12:17:43.230 [planetinfo.record]: corona_flare = 0.030798 12:17:43.230 [planetinfo.record]: corona_hues = 0.800491 12:17:43.230 [planetinfo.record]: sun_color = 0.338412, 0.497768, 0.681964, 1 12:17:43.230 [planetinfo.record]: corona_shimmer = 0.323022 12:17:43.231 [planetinfo.record]: station_vector = -0.667 -0.745 0.025 12:17:43.231 [planetinfo.record]: station = coriolis 12:17:47.891 [PLANETINFO OVER]: Done 12:17:47.892 [PLANETINFO LOGGING]: 2 21 12:17:48.896 [planetinfo.record]: seed = 57 110 229 62 12:17:48.896 [planetinfo.record]: coordinates = 110 30 12:17:48.896 [planetinfo.record]: government = 7; 12:17:48.896 [planetinfo.record]: economy = 6; 12:17:48.896 [planetinfo.record]: techlevel = 7; 12:17:48.896 [planetinfo.record]: population = 42; 12:17:48.896 [planetinfo.record]: productivity = 14784; 12:17:48.896 [planetinfo.record]: name = "Rimaedxe"; 12:17:48.896 [planetinfo.record]: inhabitant = "Red Slimy Lizard"; 12:17:48.896 [planetinfo.record]: inhabitants = "Red Slimy Lizards"; 12:17:48.896 [planetinfo.record]: description = "This planet is reasonably noted for its exotic fish meat."; 12:17:48.898 [planetinfo.record]: air_color = 0.557397, 0.962578, 0.921037, 1; 12:17:48.898 [planetinfo.record]: cloud_alpha = 1.000000; 12:17:48.898 [planetinfo.record]: cloud_color = 0.890625, 0.715044, 0.299194, 1; 12:17:48.898 [planetinfo.record]: cloud_fraction = 0.150000; 12:17:48.898 [planetinfo.record]: land_color = 0.612667, 0.66, 0.595547, 1; 12:17:48.898 [planetinfo.record]: land_fraction = 0.530000; 12:17:48.898 [planetinfo.record]: polar_cloud_color = 0.900781, 0.789792, 0.526922, 1; 12:17:48.898 [planetinfo.record]: polar_land_color = 0.917254, 0.934, 0.911197, 1; 12:17:48.898 [planetinfo.record]: polar_sea_color = 0.724659, 0.931055, 0.92138, 1; 12:17:48.898 [planetinfo.record]: sea_color = 0.0781021, 0.689453, 0.660796, 1; 12:17:48.898 [planetinfo.record]: rotation_speed = 0.004107 12:17:48.898 [planetinfo.record]: planet zpos = 781200.000000 12:17:48.898 [planetinfo.record]: sun_radius = 150996.716309 12:17:48.898 [planetinfo.record]: sun_vector = -0.736 0.492 -0.465 12:17:48.898 [planetinfo.record]: sun_distance = 1041600 12:17:48.898 [planetinfo.record]: corona_flare = 0.018503 12:17:48.899 [planetinfo.record]: corona_hues = 0.671181 12:17:48.899 [planetinfo.record]: sun_color = 0.778354, 0.740426, 0.73525, 1 12:17:48.899 [planetinfo.record]: corona_shimmer = 0.293760 12:17:48.899 [planetinfo.record]: station_vector = 0.662 0.669 0.338 12:17:48.899 [planetinfo.record]: station = coriolis 12:17:53.656 [PLANETINFO OVER]: Done 12:17:53.657 [PLANETINFO LOGGING]: 2 22 12:17:54.662 [planetinfo.record]: seed = 233 130 237 198 12:17:54.662 [planetinfo.record]: coordinates = 130 120 12:17:54.662 [planetinfo.record]: government = 5; 12:17:54.662 [planetinfo.record]: economy = 0; 12:17:54.662 [planetinfo.record]: techlevel = 12; 12:17:54.662 [planetinfo.record]: population = 54; 12:17:54.662 [planetinfo.record]: productivity = 38880; 12:17:54.662 [planetinfo.record]: name = "Bixein"; 12:17:54.662 [planetinfo.record]: inhabitant = "Fierce Horned Bird"; 12:17:54.663 [planetinfo.record]: inhabitants = "Fierce Horned Birds"; 12:17:54.663 [planetinfo.record]: description = "The planet Bixein is an unremarkable dump."; 12:17:54.672 [planetinfo.record]: air_color = 0.487374, 0.624439, 0.866971, 1; 12:17:54.672 [planetinfo.record]: cloud_alpha = 1.000000; 12:17:54.672 [planetinfo.record]: cloud_color = 0.172096, 0.603516, 0.573181, 1; 12:17:54.672 [planetinfo.record]: cloud_fraction = 0.520000; 12:17:54.672 [planetinfo.record]: land_color = 0.289274, 0.517578, 0.117264, 1; 12:17:54.672 [planetinfo.record]: land_fraction = 0.490000; 12:17:54.672 [planetinfo.record]: polar_cloud_color = 0.426857, 0.771582, 0.747343, 1; 12:17:54.672 [planetinfo.record]: polar_land_color = 0.843675, 0.948242, 0.764891, 1; 12:17:54.672 [planetinfo.record]: polar_sea_color = 0.859945, 0.915464, 0.916797, 1; 12:17:54.672 [planetinfo.record]: sea_color = 0.625648, 0.827194, 0.832031, 1; 12:17:54.672 [planetinfo.record]: rotation_speed = 0.001985 12:17:54.672 [planetinfo.record]: planet zpos = 537840.000000 12:17:54.672 [planetinfo.record]: sun_radius = 112940.436401 12:17:54.672 [planetinfo.record]: sun_vector = -0.369 0.916 -0.156 12:17:54.672 [planetinfo.record]: sun_distance = 941220 12:17:54.672 [planetinfo.record]: corona_flare = 0.055258 12:17:54.672 [planetinfo.record]: corona_hues = 0.911346 12:17:54.672 [planetinfo.record]: sun_color = 0.610449, 0.66707, 0.679425, 1 12:17:54.672 [planetinfo.record]: corona_shimmer = 0.516731 12:17:54.672 [planetinfo.record]: station_vector = 0.420 0.907 0.025 12:17:54.672 [planetinfo.record]: station = dodecahedron 12:17:59.837 [PLANETINFO OVER]: Done 12:17:59.838 [PLANETINFO LOGGING]: 2 23 12:18:00.843 [planetinfo.record]: seed = 145 149 53 100 12:18:00.843 [planetinfo.record]: coordinates = 149 12 12:18:00.843 [planetinfo.record]: government = 2; 12:18:00.843 [planetinfo.record]: economy = 4; 12:18:00.843 [planetinfo.record]: techlevel = 5; 12:18:00.843 [planetinfo.record]: population = 27; 12:18:00.843 [planetinfo.record]: productivity = 7776; 12:18:00.843 [planetinfo.record]: name = "Zabion"; 12:18:00.843 [planetinfo.record]: inhabitant = "Human Colonial"; 12:18:00.843 [planetinfo.record]: inhabitants = "Human Colonials"; 12:18:00.843 [planetinfo.record]: description = "This world is ravaged by dreadful civil war."; 12:18:00.862 [planetinfo.record]: air_color = 0.714904, 0.793552, 0.928107, 1; 12:18:00.862 [planetinfo.record]: cloud_alpha = 1.000000; 12:18:00.862 [planetinfo.record]: cloud_color = 0.716393, 0.787109, 0.78048, 1; 12:18:00.862 [planetinfo.record]: cloud_fraction = 0.330000; 12:18:00.862 [planetinfo.record]: land_color = 0.404766, 0.648036, 0.66, 1; 12:18:00.862 [planetinfo.record]: land_fraction = 0.330000; 12:18:00.862 [planetinfo.record]: polar_cloud_color = 0.806234, 0.854199, 0.849702, 1; 12:18:00.862 [planetinfo.record]: polar_land_color = 0.843701, 0.929767, 0.934, 1; 12:18:00.862 [planetinfo.record]: polar_sea_color = 0.897504, 0.933203, 0.882989, 1; 12:18:00.863 [planetinfo.record]: sea_color = 0.565757, 0.667969, 0.524199, 1; 12:18:00.863 [planetinfo.record]: rotation_speed = 0.001943 12:18:00.863 [planetinfo.record]: planet zpos = 359010.000000 12:18:00.863 [planetinfo.record]: sun_radius = 86753.567657 12:18:00.863 [planetinfo.record]: sun_vector = 0.029 0.974 -0.225 12:18:00.863 [planetinfo.record]: sun_distance = 718020 12:18:00.863 [planetinfo.record]: corona_flare = 0.055276 12:18:00.863 [planetinfo.record]: corona_hues = 0.924232 12:18:00.865 [planetinfo.record]: sun_color = 0.824387, 0.789247, 0.654153, 1 12:18:00.865 [planetinfo.record]: corona_shimmer = 0.332009 12:18:00.865 [planetinfo.record]: station_vector = -0.769 0.602 0.215 12:18:00.865 [planetinfo.record]: station = coriolis 12:18:06.645 [PLANETINFO OVER]: Done 12:18:06.645 [PLANETINFO LOGGING]: 2 24 12:18:07.648 [planetinfo.record]: seed = 1 106 205 111 12:18:07.649 [planetinfo.record]: coordinates = 106 255 12:18:07.649 [planetinfo.record]: government = 0; 12:18:07.649 [planetinfo.record]: economy = 7; 12:18:07.649 [planetinfo.record]: techlevel = 2; 12:18:07.649 [planetinfo.record]: population = 16; 12:18:07.649 [planetinfo.record]: productivity = 1536; 12:18:07.649 [planetinfo.record]: name = "Aorbete"; 12:18:07.649 [planetinfo.record]: inhabitant = "Blue Furry Rodent"; 12:18:07.649 [planetinfo.record]: inhabitants = "Blue Furry Rodents"; 12:18:07.649 [planetinfo.record]: description = "This world is very notable for its inhabitants’ weird silliness and its inhabitants’ ingrained silliness."; 12:18:07.660 [planetinfo.record]: air_color = 0.477228, 0.54391, 0.89884, 1; 12:18:07.660 [planetinfo.record]: cloud_alpha = 1.000000; 12:18:07.660 [planetinfo.record]: cloud_color = 0.133835, 0.394441, 0.699219, 1; 12:18:07.660 [planetinfo.record]: cloud_fraction = 0.170000; 12:18:07.660 [planetinfo.record]: land_color = 0.621812, 0.66, 0.598125, 1; 12:18:07.660 [planetinfo.record]: land_fraction = 0.490000; 12:18:07.660 [planetinfo.record]: polar_cloud_color = 0.402949, 0.592717, 0.814648, 1; 12:18:07.660 [planetinfo.record]: polar_land_color = 0.920489, 0.934, 0.912109, 1; 12:18:07.660 [planetinfo.record]: polar_sea_color = 0.740636, 0.935156, 0.889566, 1; 12:18:07.660 [planetinfo.record]: sea_color = 0.108917, 0.648438, 0.521987, 1; 12:18:07.660 [planetinfo.record]: rotation_speed = 0.004129 12:18:07.660 [planetinfo.record]: planet zpos = 879060.000000 12:18:07.660 [planetinfo.record]: sun_radius = 186285.175781 12:18:07.660 [planetinfo.record]: sun_vector = -0.422 0.401 -0.813 12:18:07.660 [planetinfo.record]: sun_distance = 1555260 12:18:07.660 [planetinfo.record]: corona_flare = 0.095264 12:18:07.660 [planetinfo.record]: corona_hues = 0.765274 12:18:07.660 [planetinfo.record]: sun_color = 0.822595, 0.721328, 0.551335, 1 12:18:07.660 [planetinfo.record]: corona_shimmer = 1.027822 12:18:07.661 [planetinfo.record]: station_vector = -0.091 -0.991 0.098 12:18:07.661 [planetinfo.record]: station = coriolis 12:18:12.180 [PLANETINFO OVER]: Done 12:18:12.181 [PLANETINFO LOGGING]: 2 25 12:18:13.186 [planetinfo.record]: seed = 201 252 197 137 12:18:13.186 [planetinfo.record]: coordinates = 252 179 12:18:13.186 [planetinfo.record]: government = 1; 12:18:13.186 [planetinfo.record]: economy = 3; 12:18:13.186 [planetinfo.record]: techlevel = 5; 12:18:13.186 [planetinfo.record]: population = 25; 12:18:13.186 [planetinfo.record]: productivity = 7000; 12:18:13.186 [planetinfo.record]: name = "Esorza"; 12:18:13.186 [planetinfo.record]: inhabitant = "Small Black Rodent"; 12:18:13.186 [planetinfo.record]: inhabitants = "Small Black Rodents"; 12:18:13.186 [planetinfo.record]: description = "The world Esorza is beset by evil disease."; 12:18:13.198 [planetinfo.record]: air_color = 0.587694, 0.571212, 0.844207, 1; 12:18:13.198 [planetinfo.record]: cloud_alpha = 1.000000; 12:18:13.199 [planetinfo.record]: cloud_color = 0.375351, 0.336563, 0.535156, 1; 12:18:13.199 [planetinfo.record]: cloud_fraction = 0.170000; 12:18:13.199 [planetinfo.record]: land_color = 0.381975, 0.507812, 0.105133, 1; 12:18:13.199 [planetinfo.record]: land_fraction = 0.620000; 12:18:13.199 [planetinfo.record]: polar_cloud_color = 0.602558, 0.568999, 0.74082, 1; 12:18:13.199 [planetinfo.record]: polar_land_color = 0.890414, 0.949219, 0.761044, 1; 12:18:13.199 [planetinfo.record]: polar_sea_color = 0.868121, 0.921484, 0.910228, 1; 12:18:13.199 [planetinfo.record]: sea_color = 0.603282, 0.785156, 0.746792, 1; 12:18:13.199 [planetinfo.record]: rotation_speed = 0.001038 12:18:13.199 [planetinfo.record]: planet zpos = 590920.000000 12:18:13.199 [planetinfo.record]: sun_radius = 167292.191772 12:18:13.199 [planetinfo.record]: sun_vector = 0.810 0.552 0.198 12:18:13.199 [planetinfo.record]: sun_distance = 966960 12:18:13.199 [planetinfo.record]: corona_flare = 0.070732 12:18:13.199 [planetinfo.record]: corona_hues = 0.752655 12:18:13.199 [planetinfo.record]: sun_color = 0.669006, 0.573322, 0.571038, 1 12:18:13.199 [planetinfo.record]: corona_shimmer = 0.343505 12:18:13.199 [planetinfo.record]: station_vector = 0.587 0.458 0.668 12:18:13.199 [planetinfo.record]: station = coriolis 12:18:17.839 [PLANETINFO OVER]: Done 12:18:17.840 [PLANETINFO LOGGING]: 2 26 12:18:18.844 [planetinfo.record]: seed = 57 132 173 126 12:18:18.844 [planetinfo.record]: coordinates = 132 192 12:18:18.844 [planetinfo.record]: government = 7; 12:18:18.844 [planetinfo.record]: economy = 0; 12:18:18.844 [planetinfo.record]: techlevel = 11; 12:18:18.844 [planetinfo.record]: population = 52; 12:18:18.844 [planetinfo.record]: productivity = 45760; 12:18:18.844 [planetinfo.record]: name = "Rigebi"; 12:18:18.844 [planetinfo.record]: inhabitant = "Blue Fat Feline"; 12:18:18.844 [planetinfo.record]: inhabitants = "Blue Fat Felines"; 12:18:18.844 [planetinfo.record]: description = "Rigebi is ravaged by dreadful civil war."; 12:18:18.860 [planetinfo.record]: air_color = 0.638009, 0.540756, 0.89949, 1; 12:18:18.860 [planetinfo.record]: cloud_alpha = 1.000000; 12:18:18.860 [planetinfo.record]: cloud_color = 0.629146, 0.282112, 0.701172, 1; 12:18:18.860 [planetinfo.record]: cloud_fraction = 0.080000; 12:18:18.860 [planetinfo.record]: land_color = 0.519531, 0.48909, 0.493371, 1; 12:18:18.860 [planetinfo.record]: land_fraction = 0.270000; 12:18:18.860 [planetinfo.record]: polar_cloud_color = 0.763169, 0.510899, 0.815527, 1; 12:18:18.860 [planetinfo.record]: polar_land_color = 0.948047, 0.934159, 0.936112, 1; 12:18:18.860 [planetinfo.record]: polar_sea_color = 0.937891, 0.915708, 0.881104, 1; 12:18:18.860 [planetinfo.record]: sea_color = 0.621094, 0.562335, 0.470673, 1; 12:18:18.860 [planetinfo.record]: rotation_speed = 0.002578 12:18:18.860 [planetinfo.record]: planet zpos = 718520.000000 12:18:18.860 [planetinfo.record]: sun_radius = 180212.075195 12:18:18.860 [planetinfo.record]: sun_vector = -0.267 -0.889 -0.371 12:18:18.860 [planetinfo.record]: sun_distance = 1306400 12:18:18.860 [planetinfo.record]: corona_flare = 0.030093 12:18:18.860 [planetinfo.record]: corona_hues = 0.794144 12:18:18.860 [planetinfo.record]: sun_color = 0.279347, 0.657701, 0.782706, 1 12:18:18.860 [planetinfo.record]: corona_shimmer = 0.277815 12:18:18.861 [planetinfo.record]: station_vector = -0.966 -0.198 0.166 12:18:18.861 [planetinfo.record]: station = dodecahedron 12:18:23.163 [PLANETINFO OVER]: Done 12:18:23.164 [PLANETINFO LOGGING]: 2 27 12:18:24.168 [planetinfo.record]: seed = 97 8 21 146 12:18:24.168 [planetinfo.record]: coordinates = 8 198 12:18:24.168 [planetinfo.record]: government = 4; 12:18:24.168 [planetinfo.record]: economy = 6; 12:18:24.168 [planetinfo.record]: techlevel = 3; 12:18:24.168 [planetinfo.record]: population = 23; 12:18:24.168 [planetinfo.record]: productivity = 5888; 12:18:24.168 [planetinfo.record]: name = "Enanre"; 12:18:24.168 [planetinfo.record]: inhabitant = "Human Colonial"; 12:18:24.168 [planetinfo.record]: inhabitants = "Human Colonials"; 12:18:24.168 [planetinfo.record]: description = "This planet is mildly noted for its ancient A’et tulip plantations but ravaged by frequent earthquakes."; 12:18:24.175 [planetinfo.record]: air_color = 0.646996, 0.872401, 0.929408, 1; 12:18:24.176 [planetinfo.record]: cloud_alpha = 1.000000; 12:18:24.176 [planetinfo.record]: cloud_color = 0.638452, 0.791016, 0.543823, 1; 12:18:24.176 [planetinfo.record]: cloud_fraction = 0.380000; 12:18:24.176 [planetinfo.record]: land_color = 0.480886, 0.294708, 0.650391, 1; 12:18:24.176 [planetinfo.record]: land_fraction = 0.340000; 12:18:24.176 [planetinfo.record]: polar_cloud_color = 0.752776, 0.855957, 0.688778, 1; 12:18:24.176 [planetinfo.record]: polar_land_color = 0.874044, 0.807134, 0.934961, 1; 12:18:24.176 [planetinfo.record]: polar_sea_color = 0.934375, 0.929419, 0.876707, 1; 12:18:24.176 [planetinfo.record]: sea_color = 0.65625, 0.642327, 0.494238, 1; 12:18:24.176 [planetinfo.record]: rotation_speed = 0.001317 12:18:24.176 [planetinfo.record]: planet zpos = 366960.000000 12:18:24.176 [planetinfo.record]: sun_radius = 63948.314209 12:18:24.177 [planetinfo.record]: sun_vector = 0.206 0.380 -0.902 12:18:24.177 [planetinfo.record]: sun_distance = 533760 12:18:24.177 [planetinfo.record]: corona_flare = 0.007896 12:18:24.177 [planetinfo.record]: corona_hues = 0.664093 12:18:24.177 [planetinfo.record]: sun_color = 0.218829, 0.339384, 0.824585, 1 12:18:24.177 [planetinfo.record]: corona_shimmer = 0.740102 12:18:24.177 [planetinfo.record]: station_vector = 0.669 0.020 0.743 12:18:24.177 [planetinfo.record]: station = coriolis 12:18:28.992 [PLANETINFO OVER]: Done 12:18:28.993 [PLANETINFO LOGGING]: 2 28 12:18:29.995 [planetinfo.record]: seed = 17 238 13 74 12:18:29.995 [planetinfo.record]: coordinates = 238 251 12:18:29.995 [planetinfo.record]: government = 2; 12:18:29.995 [planetinfo.record]: economy = 3; 12:18:29.995 [planetinfo.record]: techlevel = 7; 12:18:29.995 [planetinfo.record]: population = 34; 12:18:29.995 [planetinfo.record]: productivity = 11424; 12:18:29.995 [planetinfo.record]: name = "Arbema"; 12:18:29.995 [planetinfo.record]: inhabitant = "Human Colonial"; 12:18:29.995 [planetinfo.record]: inhabitants = "Human Colonials"; 12:18:29.995 [planetinfo.record]: description = "The planet Arbema is mildly well known for its hoopy night life."; 12:18:30.002 [planetinfo.record]: air_color = 0.52479, 0.53101, 0.876727, 1; 12:18:30.002 [planetinfo.record]: cloud_alpha = 1.000000; 12:18:30.002 [planetinfo.record]: cloud_color = 0.249664, 0.264631, 0.632812, 1; 12:18:30.002 [planetinfo.record]: cloud_fraction = 0.260000; 12:18:30.002 [planetinfo.record]: land_color = 0.66, 0.304219, 0.304219, 1; 12:18:30.002 [planetinfo.record]: land_fraction = 0.320000; 12:18:30.002 [planetinfo.record]: polar_cloud_color = 0.487796, 0.499397, 0.784766, 1; 12:18:30.002 [planetinfo.record]: polar_land_color = 0.934, 0.808129, 0.808129, 1; 12:18:30.002 [planetinfo.record]: polar_sea_color = 0.879118, 0.929688, 0.882278, 1; 12:18:30.002 [planetinfo.record]: sea_color = 0.55014, 0.703125, 0.559702, 1; 12:18:30.003 [planetinfo.record]: rotation_speed = 0.002257 12:18:30.003 [planetinfo.record]: planet zpos = 729820.000000 12:18:30.003 [planetinfo.record]: sun_radius = 120041.910095 12:18:30.003 [planetinfo.record]: sun_vector = 0.190 -0.910 0.367 12:18:30.003 [planetinfo.record]: sun_distance = 898240 12:18:30.003 [planetinfo.record]: corona_flare = 0.092715 12:18:30.003 [planetinfo.record]: corona_hues = 0.744980 12:18:30.003 [planetinfo.record]: sun_color = 0.551857, 0.610906, 0.672794, 1 12:18:30.003 [planetinfo.record]: corona_shimmer = 0.751473 12:18:30.003 [planetinfo.record]: station_vector = -0.992 0.062 0.112 12:18:30.003 [planetinfo.record]: station = coriolis 12:18:35.072 [PLANETINFO OVER]: Done 12:18:35.073 [PLANETINFO LOGGING]: 2 29 12:18:36.078 [planetinfo.record]: seed = 217 232 165 135 12:18:36.078 [planetinfo.record]: coordinates = 232 107 12:18:36.078 [planetinfo.record]: government = 3; 12:18:36.078 [planetinfo.record]: economy = 3; 12:18:36.078 [planetinfo.record]: techlevel = 6; 12:18:36.078 [planetinfo.record]: population = 31; 12:18:36.078 [planetinfo.record]: productivity = 12152; 12:18:36.078 [planetinfo.record]: name = "Soaniner"; 12:18:36.078 [planetinfo.record]: inhabitant = "Fierce Black Bony Feline"; 12:18:36.078 [planetinfo.record]: inhabitants = "Fierce Black Bony Felines"; 12:18:36.078 [planetinfo.record]: description = "The world Soaniner is reasonably fabled for its fabulous cuisine and its ancient Soaninerian Sestloxe banana plantations."; 12:18:36.096 [planetinfo.record]: air_color = 0.796072, 0.736907, 0.991846, 1; 12:18:36.096 [planetinfo.record]: cloud_alpha = 1.000000; 12:18:36.096 [planetinfo.record]: cloud_color = 0.9299, 0.810333, 0.978516, 1; 12:18:36.096 [planetinfo.record]: cloud_fraction = 0.400000; 12:18:36.096 [planetinfo.record]: land_color = 0.444586, 0.66, 0.420234, 1; 12:18:36.096 [planetinfo.record]: land_fraction = 0.400000; 12:18:36.096 [planetinfo.record]: polar_cloud_color = 0.911133, 0.83932, 0.940332, 1; 12:18:36.096 [planetinfo.record]: polar_land_color = 0.857789, 0.934, 0.849174, 1; 12:18:36.096 [planetinfo.record]: polar_sea_color = 0.946484, 0.884186, 0.93675, 1; 12:18:36.096 [planetinfo.record]: sea_color = 0.535156, 0.39426, 0.513141, 1; 12:18:36.096 [planetinfo.record]: rotation_speed = 0.002909 12:18:36.096 [planetinfo.record]: planet zpos = 580800.000000 12:18:36.096 [planetinfo.record]: sun_radius = 75782.305908 12:18:36.096 [planetinfo.record]: sun_vector = 0.080 -0.949 0.305 12:18:36.096 [planetinfo.record]: sun_distance = 919600 12:18:36.096 [planetinfo.record]: corona_flare = 0.002550 12:18:36.096 [planetinfo.record]: corona_hues = 0.549210 12:18:36.096 [planetinfo.record]: sun_color = 0.84407, 0.336631, 0.249308, 1 12:18:36.097 [planetinfo.record]: corona_shimmer = 0.390026 12:18:36.097 [planetinfo.record]: station_vector = -0.436 0.900 0.003 12:18:36.097 [planetinfo.record]: station = coriolis 12:18:41.159 [PLANETINFO OVER]: Done 12:18:41.159 [PLANETINFO LOGGING]: 2 30 12:18:42.163 [planetinfo.record]: seed = 9 176 109 216 12:18:42.163 [planetinfo.record]: coordinates = 176 76 12:18:42.163 [planetinfo.record]: government = 1; 12:18:42.163 [planetinfo.record]: economy = 6; 12:18:42.163 [planetinfo.record]: techlevel = 2; 12:18:42.163 [planetinfo.record]: population = 16; 12:18:42.163 [planetinfo.record]: productivity = 2560; 12:18:42.163 [planetinfo.record]: name = "Edraisar"; 12:18:42.163 [planetinfo.record]: inhabitant = "Human Colonial"; 12:18:42.163 [planetinfo.record]: inhabitants = "Human Colonials"; 12:18:42.163 [planetinfo.record]: description = "The world Edraisar is reasonably noted for Zero-G hockey and Edraisarian Usenbiza brandy."; 12:18:42.180 [planetinfo.record]: air_color = 0.691695, 0.730648, 0.980789, 1; 12:18:42.180 [planetinfo.record]: cloud_alpha = 1.000000; 12:18:42.180 [planetinfo.record]: cloud_color = 0.675751, 0.781048, 0.945312, 1; 12:18:42.180 [planetinfo.record]: cloud_fraction = 0.290000; 12:18:42.180 [planetinfo.record]: land_color = 0.632812, 0.620453, 0.553711, 1; 12:18:42.180 [planetinfo.record]: land_fraction = 0.640000; 12:18:42.180 [planetinfo.record]: polar_cloud_color = 0.760465, 0.824889, 0.925391, 1; 12:18:42.180 [planetinfo.record]: polar_land_color = 0.936719, 0.932145, 0.907446, 1; 12:18:42.180 [planetinfo.record]: polar_sea_color = 0.792018, 0.947461, 0.845452, 1; 12:18:42.180 [planetinfo.record]: sea_color = 0.180603, 0.525391, 0.299124, 1; 12:18:42.180 [planetinfo.record]: rotation_speed = 0.003250 12:18:42.180 [planetinfo.record]: planet zpos = 453600.000000 12:18:42.180 [planetinfo.record]: sun_radius = 169001.806641 12:18:42.180 [planetinfo.record]: sun_vector = 0.587 -0.246 -0.771 12:18:42.180 [planetinfo.record]: sun_distance = 1058400 12:18:42.180 [planetinfo.record]: corona_flare = 0.062909 12:18:42.180 [planetinfo.record]: corona_hues = 0.820908 12:18:42.180 [planetinfo.record]: sun_color = 0.82392, 0.748865, 0.684625, 1 12:18:42.180 [planetinfo.record]: corona_shimmer = 0.319956 12:18:42.181 [planetinfo.record]: station_vector = -0.318 -0.927 0.200 12:18:42.181 [planetinfo.record]: station = coriolis 12:18:46.322 [PLANETINFO OVER]: Done 12:18:46.323 [PLANETINFO LOGGING]: 2 31 12:18:47.326 [planetinfo.record]: seed = 177 10 245 60 12:18:47.327 [planetinfo.record]: coordinates = 10 93 12:18:47.327 [planetinfo.record]: government = 6; 12:18:47.327 [planetinfo.record]: economy = 5; 12:18:47.327 [planetinfo.record]: techlevel = 7; 12:18:47.327 [planetinfo.record]: population = 40; 12:18:47.327 [planetinfo.record]: productivity = 16000; 12:18:47.327 [planetinfo.record]: name = "Teceinre"; 12:18:47.327 [planetinfo.record]: inhabitant = "Red Insect"; 12:18:47.327 [planetinfo.record]: inhabitants = "Red Insects"; 12:18:47.327 [planetinfo.record]: description = "The planet Teceinre is reasonably noted for its inhabitants’ eccentric love for poetry and the Teceinreian tree wolf."; 12:18:47.336 [planetinfo.record]: air_color = 0.592279, 0.468106, 0.954123, 1; 12:18:47.336 [planetinfo.record]: cloud_alpha = 1.000000; 12:18:47.336 [planetinfo.record]: cloud_color = 0.689272, 0.0608368, 0.865234, 1; 12:18:47.336 [planetinfo.record]: cloud_fraction = 0.260000; 12:18:47.336 [planetinfo.record]: land_color = 0.517578, 0.501404, 0.501404, 1; 12:18:47.336 [planetinfo.record]: land_fraction = 0.470000; 12:18:47.336 [planetinfo.record]: polar_cloud_color = 0.776313, 0.372591, 0.889355, 1; 12:18:47.336 [planetinfo.record]: polar_land_color = 0.948242, 0.940834, 0.940834, 1; 12:18:47.336 [planetinfo.record]: polar_sea_color = 0.941602, 0.908965, 0.887349, 1; 12:18:47.336 [planetinfo.record]: sea_color = 0.583984, 0.50302, 0.449394, 1; 12:18:47.336 [planetinfo.record]: rotation_speed = 0.000654 12:18:47.336 [planetinfo.record]: planet zpos = 707760.000000 12:18:47.336 [planetinfo.record]: sun_radius = 186657.804565 12:18:47.337 [planetinfo.record]: sun_vector = -0.532 0.349 -0.772 12:18:47.337 [planetinfo.record]: sun_distance = 1238580 12:18:47.337 [planetinfo.record]: corona_flare = 0.050725 12:18:47.337 [planetinfo.record]: corona_hues = 0.990387 12:18:47.337 [planetinfo.record]: sun_color = 0.796054, 0.791129, 0.779118, 1 12:18:47.337 [planetinfo.record]: corona_shimmer = 0.358979 12:18:47.337 [planetinfo.record]: station_vector = -0.661 -0.628 0.411 12:18:47.337 [planetinfo.record]: station = coriolis 12:18:51.527 [PLANETINFO OVER]: Done 12:18:51.528 [PLANETINFO LOGGING]: 2 32 12:18:52.532 [planetinfo.record]: seed = 161 206 77 96 12:18:52.532 [planetinfo.record]: coordinates = 206 236 12:18:52.532 [planetinfo.record]: government = 4; 12:18:52.532 [planetinfo.record]: economy = 4; 12:18:52.532 [planetinfo.record]: techlevel = 7; 12:18:52.532 [planetinfo.record]: population = 37; 12:18:52.532 [planetinfo.record]: productivity = 14208; 12:18:52.532 [planetinfo.record]: name = "Anar"; 12:18:52.532 [planetinfo.record]: inhabitant = "Human Colonial"; 12:18:52.532 [planetinfo.record]: inhabitants = "Human Colonials"; 12:18:52.532 [planetinfo.record]: description = "Anar is an unremarkable planet."; 12:18:52.546 [planetinfo.record]: air_color = 0.656832, 0.799455, 0.96518, 1; 12:18:52.547 [planetinfo.record]: cloud_alpha = 1.000000; 12:18:52.547 [planetinfo.record]: cloud_color = 0.575562, 0.898438, 0.777359, 1; 12:18:52.547 [planetinfo.record]: cloud_fraction = 0.230000; 12:18:52.547 [planetinfo.record]: land_color = 0.360857, 0.252656, 0.66, 1; 12:18:52.547 [planetinfo.record]: land_fraction = 0.700000; 12:18:52.547 [planetinfo.record]: polar_cloud_color = 0.701183, 0.904297, 0.828129, 1; 12:18:52.547 [planetinfo.record]: polar_land_color = 0.828167, 0.789887, 0.934, 1; 12:18:52.547 [planetinfo.record]: polar_sea_color = 0.919321, 0.92793, 0.869934, 1; 12:18:52.547 [planetinfo.record]: sea_color = 0.693958, 0.720703, 0.540527, 1; 12:18:52.547 [planetinfo.record]: rotation_speed = 0.000387 12:18:52.547 [planetinfo.record]: planet zpos = 332420.000000 12:18:52.547 [planetinfo.record]: sun_radius = 93063.360596 12:18:52.547 [planetinfo.record]: sun_vector = 0.794 -0.582 0.178 12:18:52.547 [planetinfo.record]: sun_distance = 604400 12:18:52.547 [planetinfo.record]: corona_flare = 0.029051 12:18:52.547 [planetinfo.record]: corona_hues = 0.716240 12:18:52.547 [planetinfo.record]: sun_color = 0.724551, 0.586223, 0.347829, 1 12:18:52.547 [planetinfo.record]: corona_shimmer = 0.261648 12:18:52.547 [planetinfo.record]: station_vector = 0.928 -0.362 0.085 12:18:52.547 [planetinfo.record]: station = coriolis 12:18:56.799 [PLANETINFO OVER]: Done 12:18:56.799 [PLANETINFO LOGGING]: 2 33 12:18:57.802 [planetinfo.record]: seed = 105 198 133 44 12:18:57.802 [planetinfo.record]: coordinates = 198 74 12:18:57.802 [planetinfo.record]: government = 5; 12:18:57.802 [planetinfo.record]: economy = 2; 12:18:57.802 [planetinfo.record]: techlevel = 10; 12:18:57.802 [planetinfo.record]: population = 48; 12:18:57.802 [planetinfo.record]: productivity = 27648; 12:18:57.802 [planetinfo.record]: name = "Iniser"; 12:18:57.802 [planetinfo.record]: inhabitant = "Red Fat Bird"; 12:18:57.802 [planetinfo.record]: inhabitants = "Red Fat Birds"; 12:18:57.802 [planetinfo.record]: description = "This world is reasonably well known for its great parking meters but cursed by occasional earthquakes."; 12:18:57.820 [planetinfo.record]: air_color = 0.424857, 0.768383, 0.868271, 1; 12:18:57.821 [planetinfo.record]: cloud_alpha = 1.000000; 12:18:57.821 [planetinfo.record]: cloud_color = 0.217551, 0.607422, 0.0403366, 1; 12:18:57.821 [planetinfo.record]: cloud_fraction = 0.330000; 12:18:57.821 [planetinfo.record]: land_color = 0.66, 0.261196, 0.195938, 1; 12:18:57.821 [planetinfo.record]: land_fraction = 0.270000; 12:18:57.821 [planetinfo.record]: polar_cloud_color = 0.463112, 0.77334, 0.322099, 1; 12:18:57.821 [planetinfo.record]: polar_land_color = 0.934, 0.792908, 0.76982, 1; 12:18:57.821 [planetinfo.record]: polar_sea_color = 0.906733, 0.857861, 0.943555, 1; 12:18:57.821 [planetinfo.record]: sea_color = 0.476343, 0.359398, 0.564453, 1; 12:18:57.821 [planetinfo.record]: rotation_speed = 0.004738 12:18:57.821 [planetinfo.record]: planet zpos = 669460.000000 12:18:57.821 [planetinfo.record]: sun_radius = 157651.322021 12:18:57.821 [planetinfo.record]: sun_vector = -0.945 0.290 0.148 12:18:57.821 [planetinfo.record]: sun_distance = 1278060 12:18:57.821 [planetinfo.record]: corona_flare = 0.013940 12:18:57.821 [planetinfo.record]: corona_hues = 0.968018 12:18:57.821 [planetinfo.record]: sun_color = 0.722235, 0.543439, 0.529124, 1 12:18:57.821 [planetinfo.record]: corona_shimmer = 0.735071 12:18:57.821 [planetinfo.record]: station_vector = 0.987 -0.102 0.122 12:18:57.822 [planetinfo.record]: station = coriolis 12:19:02.656 [PLANETINFO OVER]: Done 12:19:02.657 [PLANETINFO LOGGING]: 2 34 12:19:03.675 [planetinfo.record]: seed = 89 154 45 8 12:19:03.676 [planetinfo.record]: coordinates = 154 48 12:19:03.676 [planetinfo.record]: government = 3; 12:19:03.676 [planetinfo.record]: economy = 0; 12:19:03.676 [planetinfo.record]: techlevel = 11; 12:19:03.676 [planetinfo.record]: population = 48; 12:19:03.676 [planetinfo.record]: productivity = 26880; 12:19:03.676 [planetinfo.record]: name = "Usenlaer"; 12:19:03.676 [planetinfo.record]: inhabitant = "Human Colonial"; 12:19:03.676 [planetinfo.record]: inhabitants = "Human Colonials"; 12:19:03.676 [planetinfo.record]: description = "This planet is fabled for its fabulous cuisine and the Usenlaerian mountain Beoid."; 12:19:03.692 [planetinfo.record]: air_color = 0.584946, 0.523962, 0.900791, 1; 12:19:03.692 [planetinfo.record]: cloud_alpha = 1.000000; 12:19:03.692 [planetinfo.record]: cloud_color = 0.473724, 0.242371, 0.705078, 1; 12:19:03.692 [planetinfo.record]: cloud_fraction = 0.540000; 12:19:03.692 [planetinfo.record]: land_color = 0.386719, 0.615165, 0.66, 1; 12:19:03.692 [planetinfo.record]: land_fraction = 0.420000; 12:19:03.692 [planetinfo.record]: polar_cloud_color = 0.649678, 0.482071, 0.817285, 1; 12:19:03.692 [planetinfo.record]: polar_land_color = 0.837316, 0.918138, 0.934, 1; 12:19:03.692 [planetinfo.record]: polar_sea_color = 0.9375, 0.932905, 0.884033, 1; 12:19:03.692 [planetinfo.record]: sea_color = 0.625, 0.612747, 0.482422, 1; 12:19:03.692 [planetinfo.record]: rotation_speed = 0.003776 12:19:03.692 [planetinfo.record]: planet zpos = 652340.000000 12:19:03.692 [planetinfo.record]: sun_radius = 135881.706543 12:19:03.692 [planetinfo.record]: sun_vector = -0.615 0.574 -0.540 12:19:03.692 [planetinfo.record]: sun_distance = 1103960 12:19:03.692 [planetinfo.record]: corona_flare = 0.073488 12:19:03.692 [planetinfo.record]: corona_hues = 0.935173 12:19:03.692 [planetinfo.record]: sun_color = 0.174953, 0.652628, 0.656903, 1 12:19:03.692 [planetinfo.record]: corona_shimmer = 0.470251 12:19:03.692 [planetinfo.record]: station_vector = 0.513 0.487 0.707 12:19:03.692 [planetinfo.record]: station = icosahedron 12:19:08.683 [PLANETINFO OVER]: Done 12:19:08.684 [PLANETINFO LOGGING]: 2 35 12:19:09.687 [planetinfo.record]: seed = 129 80 213 152 12:19:09.687 [planetinfo.record]: coordinates = 80 117 12:19:09.687 [planetinfo.record]: government = 0; 12:19:09.687 [planetinfo.record]: economy = 7; 12:19:09.687 [planetinfo.record]: techlevel = 0; 12:19:09.687 [planetinfo.record]: population = 8; 12:19:09.687 [planetinfo.record]: productivity = 768; 12:19:09.687 [planetinfo.record]: name = "Edriuson"; 12:19:09.687 [planetinfo.record]: inhabitant = "Black Furry Humanoid"; 12:19:09.687 [planetinfo.record]: inhabitants = "Black Furry Humanoids"; 12:19:09.687 [planetinfo.record]: description = "This world is very notable for its inhabitants’ weird shyness and its inhabitants’ eccentric shyness."; 12:19:09.691 [planetinfo.record]: air_color = 0.69623, 0.54177, 0.917051, 1; 12:19:09.691 [planetinfo.record]: cloud_alpha = 1.000000; 12:19:09.691 [planetinfo.record]: cloud_color = 0.753906, 0.27977, 0.635372, 1; 12:19:09.691 [planetinfo.record]: cloud_fraction = 0.230000; 12:19:09.691 [planetinfo.record]: land_color = 0.556641, 0.392679, 0.00434875, 1; 12:19:09.691 [planetinfo.record]: land_fraction = 0.640000; 12:19:09.691 [planetinfo.record]: polar_cloud_color = 0.839258, 0.509374, 0.756787, 1; 12:19:09.691 [planetinfo.record]: polar_land_color = 0.944336, 0.874796, 0.710096, 1; 12:19:09.691 [planetinfo.record]: polar_sea_color = 0.933398, 0.915538, 0.881442, 1; 12:19:09.692 [planetinfo.record]: sea_color = 0.666016, 0.61504, 0.517723, 1; 12:19:09.692 [planetinfo.record]: rotation_speed = 0.000082 12:19:09.692 [planetinfo.record]: planet zpos = 494400.000000 12:19:09.692 [planetinfo.record]: sun_radius = 135519.433594 12:19:09.692 [planetinfo.record]: sun_vector = 0.632 0.330 -0.701 12:19:09.692 [planetinfo.record]: sun_distance = 1038240 12:19:09.692 [planetinfo.record]: corona_flare = 0.052927 12:19:09.692 [planetinfo.record]: corona_hues = 0.897247 12:19:09.692 [planetinfo.record]: sun_color = 0.640972, 0.706376, 0.816418, 1 12:19:09.692 [planetinfo.record]: corona_shimmer = 0.351038 12:19:09.692 [planetinfo.record]: station_vector = -0.608 0.755 0.246 12:19:09.692 [planetinfo.record]: station = coriolis 12:19:14.509 [PLANETINFO OVER]: Done 12:19:14.510 [PLANETINFO LOGGING]: 2 36 12:19:15.516 [planetinfo.record]: seed = 177 63 141 230 12:19:15.516 [planetinfo.record]: coordinates = 63 72 12:19:15.516 [planetinfo.record]: government = 6; 12:19:15.516 [planetinfo.record]: economy = 0; 12:19:15.516 [planetinfo.record]: techlevel = 13; 12:19:15.516 [planetinfo.record]: population = 59; 12:19:15.516 [planetinfo.record]: productivity = 47200; 12:19:15.516 [planetinfo.record]: name = "Birera"; 12:19:15.516 [planetinfo.record]: inhabitant = "Fierce Frog"; 12:19:15.517 [planetinfo.record]: inhabitants = "Fierce Frogs"; 12:19:15.517 [planetinfo.record]: description = "Birera is most noted for its inhabitants’ eccentric love for tourists and the Bireraian tree grub."; 12:19:15.536 [planetinfo.record]: air_color = 0.692866, 0.554853, 0.903393, 1; 12:19:15.536 [planetinfo.record]: cloud_alpha = 1.000000; 12:19:15.536 [planetinfo.record]: cloud_color = 0.712891, 0.314674, 0.632003, 1; 12:19:15.536 [planetinfo.record]: cloud_fraction = 0.500000; 12:19:15.536 [planetinfo.record]: land_color = 0.629063, 0.652749, 0.66, 1; 12:19:15.536 [planetinfo.record]: land_fraction = 0.610000; 12:19:15.536 [planetinfo.record]: polar_cloud_color = 0.820801, 0.534242, 0.762594, 1; 12:19:15.536 [planetinfo.record]: polar_land_color = 0.923055, 0.931435, 0.934, 1; 12:19:15.536 [planetinfo.record]: polar_sea_color = 0.949219, 0.83103, 0.726746, 1; 12:19:15.536 [planetinfo.record]: sea_color = 0.507812, 0.254898, 0.0317383, 1; 12:19:15.536 [planetinfo.record]: rotation_speed = 0.003552 12:19:15.536 [planetinfo.record]: planet zpos = 441500.000000 12:19:15.537 [planetinfo.record]: sun_radius = 110738.111115 12:19:15.537 [planetinfo.record]: sun_vector = 0.446 -0.227 -0.866 12:19:15.537 [planetinfo.record]: sun_distance = 1059600 12:19:15.537 [planetinfo.record]: corona_flare = 0.007152 12:19:15.537 [planetinfo.record]: corona_hues = 0.948555 12:19:15.537 [planetinfo.record]: sun_color = 0.680727, 0.745318, 0.776535, 1 12:19:15.537 [planetinfo.record]: corona_shimmer = 0.634080 12:19:15.537 [planetinfo.record]: station_vector = -0.548 0.791 0.272 12:19:15.537 [planetinfo.record]: station = dodecahedron 12:19:20.197 [PLANETINFO OVER]: Done 12:19:20.197 [PLANETINFO LOGGING]: 2 37 12:19:21.201 [planetinfo.record]: seed = 121 233 101 236 12:19:21.201 [planetinfo.record]: coordinates = 233 148 12:19:21.201 [planetinfo.record]: government = 7; 12:19:21.201 [planetinfo.record]: economy = 4; 12:19:21.201 [planetinfo.record]: techlevel = 8; 12:19:21.201 [planetinfo.record]: population = 44; 12:19:21.201 [planetinfo.record]: productivity = 23232; 12:19:21.201 [planetinfo.record]: name = "Inar"; 12:19:21.201 [planetinfo.record]: inhabitant = "Human Colonial"; 12:19:21.201 [planetinfo.record]: inhabitants = "Human Colonials"; 12:19:21.201 [planetinfo.record]: description = "This world is most notable for its exotic night life but ravaged by frequent earthquakes."; 12:19:21.224 [planetinfo.record]: air_color = 0.670583, 0.860236, 0.910547, 1; 12:19:21.224 [planetinfo.record]: cloud_alpha = 1.000000; 12:19:21.224 [planetinfo.record]: cloud_color = 0.644326, 0.734375, 0.593811, 1; 12:19:21.224 [planetinfo.record]: cloud_fraction = 0.160000; 12:19:21.224 [planetinfo.record]: land_color = 0.53625, 0.651299, 0.66, 1; 12:19:21.224 [planetinfo.record]: land_fraction = 0.460000; 12:19:21.224 [planetinfo.record]: polar_cloud_color = 0.766824, 0.830469, 0.731121, 1; 12:19:21.224 [planetinfo.record]: polar_land_color = 0.890219, 0.930922, 0.934, 1; 12:19:21.224 [planetinfo.record]: polar_sea_color = 0.857832, 0.943555, 0.732545, 1; 12:19:21.224 [planetinfo.record]: sea_color = 0.359329, 0.564453, 0.0595322, 1; 12:19:21.224 [planetinfo.record]: rotation_speed = 0.001705 12:19:21.224 [planetinfo.record]: planet zpos = 734520.000000 12:19:21.224 [planetinfo.record]: sun_radius = 175443.573456 12:19:21.224 [planetinfo.record]: sun_vector = 0.958 0.209 0.194 12:19:21.224 [planetinfo.record]: sun_distance = 1469040 12:19:21.224 [planetinfo.record]: corona_flare = 0.062616 12:19:21.225 [planetinfo.record]: corona_hues = 0.802414 12:19:21.225 [planetinfo.record]: sun_color = 0.677939, 0.508352, 0.243492, 1 12:19:21.225 [planetinfo.record]: corona_shimmer = 0.323734 12:19:21.225 [planetinfo.record]: station_vector = -0.602 -0.790 0.117 12:19:21.225 [planetinfo.record]: station = coriolis 12:19:26.360 [PLANETINFO OVER]: Done 12:19:26.361 [PLANETINFO LOGGING]: 2 38 12:19:27.365 [planetinfo.record]: seed = 41 151 237 65 12:19:27.366 [planetinfo.record]: coordinates = 151 64 12:19:27.366 [planetinfo.record]: government = 5; 12:19:27.366 [planetinfo.record]: economy = 0; 12:19:27.366 [planetinfo.record]: techlevel = 13; 12:19:27.366 [planetinfo.record]: population = 58; 12:19:27.366 [planetinfo.record]: productivity = 41760; 12:19:27.366 [planetinfo.record]: name = "Leorendi"; 12:19:27.366 [planetinfo.record]: inhabitant = "Large Yellow Rodent"; 12:19:27.366 [planetinfo.record]: inhabitants = "Large Yellow Rodents"; 12:19:27.366 [planetinfo.record]: description = "This world is plagued by frequent earthquakes."; 12:19:27.384 [planetinfo.record]: air_color = 0.50266, 0.626997, 0.854613, 1; 12:19:27.384 [planetinfo.record]: cloud_alpha = 1.000000; 12:19:27.384 [planetinfo.record]: cloud_color = 0.205765, 0.566406, 0.549501, 1; 12:19:27.384 [planetinfo.record]: cloud_fraction = 0.340000; 12:19:27.384 [planetinfo.record]: land_color = 0.410333, 0.273476, 0.654297, 1; 12:19:27.384 [planetinfo.record]: land_fraction = 0.490000; 12:19:27.384 [planetinfo.record]: polar_cloud_color = 0.454478, 0.754883, 0.740801, 1; 12:19:27.384 [planetinfo.record]: polar_land_color = 0.847453, 0.798583, 0.93457, 1; 12:19:27.384 [planetinfo.record]: polar_sea_color = 0.939648, 0.886506, 0.879452, 1; 12:19:27.384 [planetinfo.record]: sea_color = 0.603516, 0.466988, 0.448865, 1; 12:19:27.384 [planetinfo.record]: rotation_speed = 0.004428 12:19:27.385 [planetinfo.record]: planet zpos = 354530.000000 12:19:27.385 [planetinfo.record]: sun_radius = 75872.004852 12:19:27.385 [planetinfo.record]: sun_vector = 0.678 -0.700 -0.221 12:19:27.385 [planetinfo.record]: sun_distance = 547910 12:19:27.385 [planetinfo.record]: corona_flare = 0.026215 12:19:27.385 [planetinfo.record]: corona_hues = 0.559486 12:19:27.385 [planetinfo.record]: sun_color = 0.683093, 0.435533, 0.286822, 1 12:19:27.385 [planetinfo.record]: corona_shimmer = 0.356507 12:19:27.385 [planetinfo.record]: station_vector = 0.802 -0.583 0.133 12:19:27.386 [planetinfo.record]: station = dodecahedron 12:19:32.670 [PLANETINFO OVER]: Done 12:19:32.671 [PLANETINFO LOGGING]: 2 39 12:19:33.687 [planetinfo.record]: seed = 209 77 181 89 12:19:33.687 [planetinfo.record]: coordinates = 77 242 12:19:33.687 [planetinfo.record]: government = 2; 12:19:33.687 [planetinfo.record]: economy = 2; 12:19:33.687 [planetinfo.record]: techlevel = 7; 12:19:33.687 [planetinfo.record]: population = 33; 12:19:33.687 [planetinfo.record]: productivity = 12672; 12:19:33.687 [planetinfo.record]: name = "Orqulela"; 12:19:33.687 [planetinfo.record]: inhabitant = "Yellow Rodent"; 12:19:33.688 [planetinfo.record]: inhabitants = "Yellow Rodents"; 12:19:33.688 [planetinfo.record]: description = "This planet is a tedious place."; 12:19:33.708 [planetinfo.record]: air_color = 0.505007, 0.600587, 0.862418, 1; 12:19:33.708 [planetinfo.record]: cloud_alpha = 1.000000; 12:19:33.708 [planetinfo.record]: cloud_color = 0.209671, 0.509651, 0.589844, 1; 12:19:33.708 [planetinfo.record]: cloud_fraction = 0.380000; 12:19:33.708 [planetinfo.record]: land_color = 0.66, 0.221819, 0.0696094, 1; 12:19:33.708 [planetinfo.record]: land_fraction = 0.470000; 12:19:33.708 [planetinfo.record]: polar_cloud_color = 0.45709, 0.700389, 0.76543, 1; 12:19:33.708 [planetinfo.record]: polar_land_color = 0.934, 0.778977, 0.725127, 1; 12:19:33.708 [planetinfo.record]: polar_sea_color = 0.879358, 0.92793, 0.900608, 1; 12:19:33.708 [planetinfo.record]: sea_color = 0.569806, 0.720703, 0.635823, 1; 12:19:33.708 [planetinfo.record]: rotation_speed = 0.004427 12:19:33.708 [planetinfo.record]: planet zpos = 571670.000000 12:19:33.708 [planetinfo.record]: sun_radius = 102775.084076 12:19:33.708 [planetinfo.record]: sun_vector = -0.519 -0.212 0.828 12:19:33.708 [planetinfo.record]: sun_distance = 1143340 12:19:33.708 [planetinfo.record]: corona_flare = 0.052499 12:19:33.708 [planetinfo.record]: corona_hues = 0.627899 12:19:33.708 [planetinfo.record]: sun_color = 0.246738, 0.534754, 0.658966, 1 12:19:33.709 [planetinfo.record]: corona_shimmer = 0.633064 12:19:33.709 [planetinfo.record]: station_vector = -0.154 -0.852 0.500 12:19:33.709 [planetinfo.record]: station = coriolis 12:19:38.677 [PLANETINFO OVER]: Done 12:19:38.678 [PLANETINFO LOGGING]: 2 40 12:19:39.682 [planetinfo.record]: seed = 65 53 205 16 12:19:39.682 [planetinfo.record]: coordinates = 53 65 12:19:39.682 [planetinfo.record]: government = 0; 12:19:39.682 [planetinfo.record]: economy = 3; 12:19:39.682 [planetinfo.record]: techlevel = 5; 12:19:39.682 [planetinfo.record]: population = 24; 12:19:39.682 [planetinfo.record]: productivity = 5376; 12:19:39.682 [planetinfo.record]: name = "Ersodi"; 12:19:39.682 [planetinfo.record]: inhabitant = "Green Fat Bird"; 12:19:39.682 [planetinfo.record]: inhabitants = "Green Fat Birds"; 12:19:39.682 [planetinfo.record]: description = "The planet Ersodi is cursed by killer mountain monkeys."; 12:19:39.707 [planetinfo.record]: air_color = 0.459473, 0.623906, 0.887133, 1; 12:19:39.707 [planetinfo.record]: cloud_alpha = 1.000000; 12:19:39.707 [planetinfo.record]: cloud_color = 0.101166, 0.664062, 0.584905, 1; 12:19:39.707 [planetinfo.record]: cloud_fraction = 0.290000; 12:19:39.707 [planetinfo.record]: land_color = 0.550781, 0.471176, 0.491699, 1; 12:19:39.707 [planetinfo.record]: land_fraction = 0.460000; 12:19:39.707 [planetinfo.record]: polar_cloud_color = 0.375621, 0.798828, 0.739315, 1; 12:19:39.707 [planetinfo.record]: polar_land_color = 0.944922, 0.910779, 0.919582, 1; 12:19:39.707 [planetinfo.record]: polar_sea_color = 0.882193, 0.920313, 0.840324, 1; 12:19:39.708 [planetinfo.record]: sea_color = 0.664849, 0.796875, 0.519836, 1; 12:19:39.708 [planetinfo.record]: rotation_speed = 0.001578 12:19:39.708 [planetinfo.record]: planet zpos = 372970.000000 12:19:39.708 [planetinfo.record]: sun_radius = 46121.749115 12:19:39.708 [planetinfo.record]: sun_vector = 0.993 0.001 -0.121 12:19:39.708 [planetinfo.record]: sun_distance = 659870 12:19:39.708 [planetinfo.record]: corona_flare = 0.011519 12:19:39.708 [planetinfo.record]: corona_hues = 0.906212 12:19:39.708 [planetinfo.record]: sun_color = 0.6658, 0.687311, 0.785385, 1 12:19:39.708 [planetinfo.record]: corona_shimmer = 0.231686 12:19:39.708 [planetinfo.record]: station_vector = -0.181 0.984 0.002 12:19:39.708 [planetinfo.record]: station = coriolis 12:19:44.165 [PLANETINFO OVER]: Done 12:19:44.166 [PLANETINFO LOGGING]: 2 41 12:19:45.169 [planetinfo.record]: seed = 9 102 69 187 12:19:45.169 [planetinfo.record]: coordinates = 102 205 12:19:45.169 [planetinfo.record]: government = 1; 12:19:45.169 [planetinfo.record]: economy = 7; 12:19:45.169 [planetinfo.record]: techlevel = 3; 12:19:45.169 [planetinfo.record]: population = 21; 12:19:45.169 [planetinfo.record]: productivity = 2520; 12:19:45.169 [planetinfo.record]: name = "Anreer"; 12:19:45.169 [planetinfo.record]: inhabitant = "Human Colonial"; 12:19:45.169 [planetinfo.record]: inhabitants = "Human Colonials"; 12:19:45.169 [planetinfo.record]: description = "Anreer is cursed by deadly civil war."; 12:19:45.179 [planetinfo.record]: air_color = 0.645179, 0.894611, 0.913148, 1; 12:19:45.179 [planetinfo.record]: cloud_alpha = 1.000000; 12:19:45.179 [planetinfo.record]: cloud_color = 0.696526, 0.742188, 0.533447, 1; 12:19:45.179 [planetinfo.record]: cloud_fraction = 0.150000; 12:19:45.179 [planetinfo.record]: land_color = 0.6202, 0.592969, 0.66, 1; 12:19:45.179 [planetinfo.record]: land_fraction = 0.570000; 12:19:45.179 [planetinfo.record]: polar_cloud_color = 0.801916, 0.833984, 0.687386, 1; 12:19:45.179 [planetinfo.record]: polar_land_color = 0.919919, 0.910285, 0.934, 1; 12:19:45.179 [planetinfo.record]: polar_sea_color = 0.948828, 0.764582, 0.715327, 1; 12:19:45.179 [planetinfo.record]: sea_color = 0.511719, 0.11425, 0.00799561, 1; 12:19:45.179 [planetinfo.record]: rotation_speed = 0.003538 12:19:45.179 [planetinfo.record]: planet zpos = 688080.000000 12:19:45.179 [planetinfo.record]: sun_radius = 167483.441467 12:19:45.179 [planetinfo.record]: sun_vector = -0.825 0.560 -0.074 12:19:45.179 [planetinfo.record]: sun_distance = 1146800 12:19:45.179 [planetinfo.record]: corona_flare = 0.054430 12:19:45.179 [planetinfo.record]: corona_hues = 0.550896 12:19:45.179 [planetinfo.record]: sun_color = 0.81365, 0.770065, 0.655364, 1 12:19:45.180 [planetinfo.record]: corona_shimmer = 0.273534 12:19:45.180 [planetinfo.record]: station_vector = -0.500 -0.774 0.387 12:19:45.180 [planetinfo.record]: station = coriolis 12:19:49.319 [PLANETINFO OVER]: Done 12:19:49.319 [PLANETINFO LOGGING]: 2 42 12:19:50.324 [planetinfo.record]: seed = 121 186 173 185 12:19:50.324 [planetinfo.record]: coordinates = 186 16 12:19:50.324 [planetinfo.record]: government = 7; 12:19:50.324 [planetinfo.record]: economy = 0; 12:19:50.324 [planetinfo.record]: techlevel = 13; 12:19:50.324 [planetinfo.record]: population = 60; 12:19:50.324 [planetinfo.record]: productivity = 52800; 12:19:50.324 [planetinfo.record]: name = "Orzaedve"; 12:19:50.324 [planetinfo.record]: inhabitant = "Harmless Horned Lobster"; 12:19:50.324 [planetinfo.record]: inhabitants = "Harmless Horned Lobsters"; 12:19:50.324 [planetinfo.record]: description = "The world Orzaedve is reasonably noted for its inhabitants’ exceptional loathing of food blenders."; 12:19:50.329 [planetinfo.record]: air_color = 0.420867, 0.826772, 0.853312, 1; 12:19:50.329 [planetinfo.record]: cloud_alpha = 1.000000; 12:19:50.330 [planetinfo.record]: cloud_color = 0.46122, 0.5625, 0.0439453, 1; 12:19:50.330 [planetinfo.record]: cloud_fraction = 0.350000; 12:19:50.330 [planetinfo.record]: land_color = 0.66, 0.0979688, 0.20335, 1; 12:19:50.330 [planetinfo.record]: land_fraction = 0.440000; 12:19:50.330 [planetinfo.record]: polar_cloud_color = 0.668373, 0.753125, 0.319196, 1; 12:19:50.330 [planetinfo.record]: polar_land_color = 0.934, 0.73516, 0.772443, 1; 12:19:50.330 [planetinfo.record]: polar_sea_color = 0.94043, 0.92104, 0.887622, 1; 12:19:50.330 [planetinfo.record]: sea_color = 0.595703, 0.546573, 0.461903, 1; 12:19:50.330 [planetinfo.record]: rotation_speed = 0.000113 12:19:50.330 [planetinfo.record]: planet zpos = 689780.000000 12:19:50.330 [planetinfo.record]: sun_radius = 129039.853821 12:19:50.330 [planetinfo.record]: sun_vector = -0.747 -0.245 -0.619 12:19:50.330 [planetinfo.record]: sun_distance = 1220380 12:19:50.330 [planetinfo.record]: corona_flare = 0.059846 12:19:50.330 [planetinfo.record]: corona_hues = 0.627998 12:19:50.330 [planetinfo.record]: sun_color = 0.646197, 0.737364, 0.769849, 1 12:19:50.331 [planetinfo.record]: corona_shimmer = 0.404224 12:19:50.331 [planetinfo.record]: station_vector = -0.891 -0.443 0.099 12:19:50.331 [planetinfo.record]: station = dodecahedron 12:19:55.313 [PLANETINFO OVER]: Done 12:19:55.314 [PLANETINFO LOGGING]: 2 43 12:19:56.320 [planetinfo.record]: seed = 161 54 149 179 12:19:56.320 [planetinfo.record]: coordinates = 54 248 12:19:56.320 [planetinfo.record]: government = 4; 12:19:56.320 [planetinfo.record]: economy = 0; 12:19:56.320 [planetinfo.record]: techlevel = 11; 12:19:56.320 [planetinfo.record]: population = 49; 12:19:56.320 [planetinfo.record]: productivity = 31360; 12:19:56.320 [planetinfo.record]: name = "Bexein"; 12:19:56.320 [planetinfo.record]: inhabitant = "Harmless Frog"; 12:19:56.320 [planetinfo.record]: inhabitants = "Harmless Frogs"; 12:19:56.321 [planetinfo.record]: description = "This world is very fabled for its unusual oceans."; 12:19:56.336 [planetinfo.record]: air_color = 0.457057, 0.674643, 0.871523, 1; 12:19:56.336 [planetinfo.record]: cloud_alpha = 1.000000; 12:19:56.336 [planetinfo.record]: cloud_color = 0.106079, 0.617188, 0.329689, 1; 12:19:56.336 [planetinfo.record]: cloud_fraction = 0.450000; 12:19:56.336 [planetinfo.record]: land_color = 0.621094, 0.393036, 0.569424, 1; 12:19:56.336 [planetinfo.record]: land_fraction = 0.650000; 12:19:56.336 [planetinfo.record]: polar_cloud_color = 0.375196, 0.777734, 0.551307, 1; 12:19:56.336 [planetinfo.record]: polar_land_color = 0.937891, 0.851795, 0.918385, 1; 12:19:56.336 [planetinfo.record]: polar_sea_color = 0.939844, 0.899894, 0.882389, 1; 12:19:56.336 [planetinfo.record]: sea_color = 0.601562, 0.499281, 0.454462, 1; 12:19:56.336 [planetinfo.record]: rotation_speed = 0.003839 12:19:56.336 [planetinfo.record]: planet zpos = 363800.000000 12:19:56.336 [planetinfo.record]: sun_radius = 95083.384399 12:19:56.337 [planetinfo.record]: sun_vector = -0.103 -0.278 -0.955 12:19:56.337 [planetinfo.record]: sun_distance = 836740 12:19:56.337 [planetinfo.record]: corona_flare = 0.056454 12:19:56.337 [planetinfo.record]: corona_hues = 0.545815 12:19:56.337 [planetinfo.record]: sun_color = 0.382749, 0.44936, 0.821539, 1 12:19:56.337 [planetinfo.record]: corona_shimmer = 0.433663 12:19:56.337 [planetinfo.record]: station_vector = 0.476 0.875 0.093 12:19:56.337 [planetinfo.record]: station = dodecahedron 12:20:01.333 [PLANETINFO OVER]: Done 12:20:01.334 [PLANETINFO LOGGING]: 2 44 12:20:02.337 [planetinfo.record]: seed = 81 99 13 19 12:20:02.337 [planetinfo.record]: coordinates = 99 204 12:20:02.337 [planetinfo.record]: government = 2; 12:20:02.337 [planetinfo.record]: economy = 4; 12:20:02.337 [planetinfo.record]: techlevel = 7; 12:20:02.337 [planetinfo.record]: population = 35; 12:20:02.337 [planetinfo.record]: productivity = 10080; 12:20:02.337 [planetinfo.record]: name = "Begeorer"; 12:20:02.337 [planetinfo.record]: inhabitant = "Human Colonial"; 12:20:02.337 [planetinfo.record]: inhabitants = "Human Colonials"; 12:20:02.337 [planetinfo.record]: description = "The world Begeorer is a boring planet."; 12:20:02.347 [planetinfo.record]: air_color = 0.627541, 0.911391, 0.927457, 1; 12:20:02.347 [planetinfo.record]: cloud_alpha = 1.000000; 12:20:02.347 [planetinfo.record]: cloud_color = 0.735078, 0.785156, 0.49379, 1; 12:20:02.347 [planetinfo.record]: cloud_fraction = 0.440000; 12:20:02.348 [planetinfo.record]: land_color = 0.464626, 0.66, 0.402188, 1; 12:20:02.348 [planetinfo.record]: land_fraction = 0.460000; 12:20:02.348 [planetinfo.record]: polar_cloud_color = 0.819304, 0.85332, 0.655407, 1; 12:20:02.348 [planetinfo.record]: polar_land_color = 0.864879, 0.934, 0.842789, 1; 12:20:02.348 [planetinfo.record]: polar_sea_color = 0.843081, 0.883931, 0.947656, 1; 12:20:02.348 [planetinfo.record]: sea_color = 0.292389, 0.382642, 0.523438, 1; 12:20:02.348 [planetinfo.record]: rotation_speed = 0.004711 12:20:02.348 [planetinfo.record]: planet zpos = 441960.000000 12:20:02.348 [planetinfo.record]: sun_radius = 111060.972900 12:20:02.348 [planetinfo.record]: sun_vector = -0.747 -0.221 0.627 12:20:02.348 [planetinfo.record]: sun_distance = 736600 12:20:02.348 [planetinfo.record]: corona_flare = 0.006020 12:20:02.348 [planetinfo.record]: corona_hues = 0.819862 12:20:02.348 [planetinfo.record]: sun_color = 0.240362, 0.342142, 0.681494, 1 12:20:02.348 [planetinfo.record]: corona_shimmer = 0.346048 12:20:02.348 [planetinfo.record]: station_vector = -0.891 -0.303 0.339 12:20:02.348 [planetinfo.record]: station = coriolis 12:20:06.770 [PLANETINFO OVER]: Done 12:20:06.771 [PLANETINFO LOGGING]: 2 45 12:20:07.773 [planetinfo.record]: seed = 25 16 37 13 12:20:07.773 [planetinfo.record]: coordinates = 16 185 12:20:07.773 [planetinfo.record]: government = 3; 12:20:07.773 [planetinfo.record]: economy = 1; 12:20:07.773 [planetinfo.record]: techlevel = 8; 12:20:07.774 [planetinfo.record]: population = 37; 12:20:07.774 [planetinfo.record]: productivity = 18648; 12:20:07.774 [planetinfo.record]: name = "Divera"; 12:20:07.774 [planetinfo.record]: inhabitant = "Human Colonial"; 12:20:07.774 [planetinfo.record]: inhabitants = "Human Colonials"; 12:20:07.774 [planetinfo.record]: description = "Divera is mildly well known for Diveraian shrew cutlet."; 12:20:07.788 [planetinfo.record]: air_color = 0.695524, 0.840526, 0.896889, 1; 12:20:07.788 [planetinfo.record]: cloud_alpha = 1.000000; 12:20:07.788 [planetinfo.record]: cloud_color = 0.649538, 0.693359, 0.641899, 1; 12:20:07.788 [planetinfo.record]: cloud_fraction = 0.110000; 12:20:07.788 [planetinfo.record]: land_color = 0.635105, 0.66, 0.629063, 1; 12:20:07.788 [planetinfo.record]: land_fraction = 0.530000; 12:20:07.788 [planetinfo.record]: polar_cloud_color = 0.779936, 0.812012, 0.774345, 1; 12:20:07.788 [planetinfo.record]: polar_land_color = 0.925192, 0.934, 0.923055, 1; 12:20:07.788 [planetinfo.record]: polar_sea_color = 0.768163, 0.943164, 0.832421, 1; 12:20:07.788 [planetinfo.record]: sea_color = 0.14653, 0.568359, 0.301421, 1; 12:20:07.788 [planetinfo.record]: rotation_speed = 0.000321 12:20:07.788 [planetinfo.record]: planet zpos = 616000.000000 12:20:07.788 [planetinfo.record]: sun_radius = 156443.847656 12:20:07.788 [planetinfo.record]: sun_vector = -0.454 -0.166 0.876 12:20:07.788 [planetinfo.record]: sun_distance = 1108800 12:20:07.788 [planetinfo.record]: corona_flare = 0.065140 12:20:07.788 [planetinfo.record]: corona_hues = 0.880806 12:20:07.788 [planetinfo.record]: sun_color = 0.285667, 0.479404, 0.677002, 1 12:20:07.789 [planetinfo.record]: corona_shimmer = 0.358822 12:20:07.789 [planetinfo.record]: station_vector = -0.193 -0.957 0.218 12:20:07.789 [planetinfo.record]: station = coriolis 12:20:12.164 [PLANETINFO OVER]: Done 12:20:12.164 [PLANETINFO LOGGING]: 2 46 12:20:13.167 [planetinfo.record]: seed = 73 216 109 163 12:20:13.167 [planetinfo.record]: coordinates = 216 244 12:20:13.167 [planetinfo.record]: government = 1; 12:20:13.167 [planetinfo.record]: economy = 6; 12:20:13.167 [planetinfo.record]: techlevel = 2; 12:20:13.167 [planetinfo.record]: population = 16; 12:20:13.167 [planetinfo.record]: productivity = 2560; 12:20:13.167 [planetinfo.record]: name = "Geama"; 12:20:13.167 [planetinfo.record]: inhabitant = "Human Colonial"; 12:20:13.167 [planetinfo.record]: inhabitants = "Human Colonials"; 12:20:13.167 [planetinfo.record]: description = "The world Geama is fabled for its fabulous cuisine and its weird volcanoes."; 12:20:13.188 [planetinfo.record]: air_color = 0.521587, 0.519488, 0.885182, 1; 12:20:13.188 [planetinfo.record]: cloud_alpha = 1.000000; 12:20:13.188 [planetinfo.record]: cloud_color = 0.249719, 0.236542, 0.658203, 1; 12:20:13.188 [planetinfo.record]: cloud_fraction = 0.510000; 12:20:13.188 [planetinfo.record]: land_color = 0.195938, 0.344583, 0.66, 1; 12:20:13.188 [planetinfo.record]: land_fraction = 0.510000; 12:20:13.188 [planetinfo.record]: polar_cloud_color = 0.487366, 0.477404, 0.796191, 1; 12:20:13.188 [planetinfo.record]: polar_land_color = 0.76982, 0.822409, 0.934, 1; 12:20:13.188 [planetinfo.record]: polar_sea_color = 0.931445, 0.861874, 0.85049, 1; 12:20:13.188 [planetinfo.record]: sea_color = 0.685547, 0.480728, 0.447212, 1; 12:20:13.188 [planetinfo.record]: rotation_speed = 0.000732 12:20:13.188 [planetinfo.record]: planet zpos = 456000.000000 12:20:13.188 [planetinfo.record]: sun_radius = 80444.427490 12:20:13.188 [planetinfo.record]: sun_vector = 0.807 -0.104 -0.581 12:20:13.188 [planetinfo.record]: sun_distance = 760000 12:20:13.188 [planetinfo.record]: corona_flare = 0.046327 12:20:13.188 [planetinfo.record]: corona_hues = 0.611588 12:20:13.188 [planetinfo.record]: sun_color = 0.747894, 0.739034, 0.67213, 1 12:20:13.189 [planetinfo.record]: corona_shimmer = 0.399366 12:20:13.189 [planetinfo.record]: station_vector = 0.817 0.355 0.454 12:20:13.189 [planetinfo.record]: station = coriolis 12:20:17.741 [PLANETINFO OVER]: Done 12:20:17.742 [PLANETINFO LOGGING]: 2 47 12:20:18.744 [planetinfo.record]: seed = 241 254 117 90 12:20:18.744 [planetinfo.record]: coordinates = 254 235 12:20:18.744 [planetinfo.record]: government = 6; 12:20:18.744 [planetinfo.record]: economy = 3; 12:20:18.744 [planetinfo.record]: techlevel = 9; 12:20:18.744 [planetinfo.record]: population = 46; 12:20:18.744 [planetinfo.record]: productivity = 25760; 12:20:18.744 [planetinfo.record]: name = "Quceri"; 12:20:18.744 [planetinfo.record]: inhabitant = "Human Colonial"; 12:20:18.744 [planetinfo.record]: inhabitants = "Human Colonials"; 12:20:18.744 [planetinfo.record]: description = "The planet Quceri is reasonably fabled for Zero-G cricket and vicious Le brew."; 12:20:18.746 [planetinfo.record]: air_color = 0.728661, 0.5835, 0.889734, 1; 12:20:18.746 [planetinfo.record]: cloud_alpha = 1.000000; 12:20:18.746 [planetinfo.record]: cloud_color = 0.671875, 0.380554, 0.544422, 1; 12:20:18.746 [planetinfo.record]: cloud_fraction = 0.210000; 12:20:18.746 [planetinfo.record]: land_color = 0.66, 0.435703, 0.456731, 1; 12:20:18.746 [planetinfo.record]: land_fraction = 0.650000; 12:20:18.746 [planetinfo.record]: polar_cloud_color = 0.802344, 0.584912, 0.707217, 1; 12:20:18.747 [planetinfo.record]: polar_land_color = 0.934, 0.854647, 0.862086, 1; 12:20:18.747 [planetinfo.record]: polar_sea_color = 0.940039, 0.913537, 0.885326, 1; 12:20:18.747 [planetinfo.record]: sea_color = 0.599609, 0.531992, 0.460013, 1; 12:20:18.747 [planetinfo.record]: rotation_speed = 0.003204 12:20:18.747 [planetinfo.record]: planet zpos = 844500.000000 12:20:18.747 [planetinfo.record]: sun_radius = 154335.330200 12:20:18.747 [planetinfo.record]: sun_vector = 0.706 -0.141 0.694 12:20:18.747 [planetinfo.record]: sun_distance = 1294900 12:20:18.747 [planetinfo.record]: corona_flare = 0.009047 12:20:18.747 [planetinfo.record]: corona_hues = 0.703812 12:20:18.747 [planetinfo.record]: sun_color = 0.728213, 0.683211, 0.678086, 1 12:20:18.747 [planetinfo.record]: corona_shimmer = 0.332707 12:20:18.748 [planetinfo.record]: station_vector = -0.123 -0.979 0.161 12:20:18.748 [planetinfo.record]: station = coriolis 12:20:23.356 [PLANETINFO OVER]: Done 12:20:23.356 [PLANETINFO LOGGING]: 2 48 12:20:24.359 [planetinfo.record]: seed = 225 61 77 33 12:20:24.359 [planetinfo.record]: coordinates = 61 158 12:20:24.359 [planetinfo.record]: government = 4; 12:20:24.359 [planetinfo.record]: economy = 6; 12:20:24.359 [planetinfo.record]: techlevel = 4; 12:20:24.360 [planetinfo.record]: population = 27; 12:20:24.360 [planetinfo.record]: productivity = 6912; 12:20:24.360 [planetinfo.record]: name = "Leistean"; 12:20:24.360 [planetinfo.record]: inhabitant = "Human Colonial"; 12:20:24.360 [planetinfo.record]: inhabitants = "Human Colonials"; 12:20:24.360 [planetinfo.record]: description = "The planet Leistean is well known for its inhabitants’ ancient loathing of sit coms but ravaged by vicious vicious wolfs."; 12:20:24.372 [planetinfo.record]: air_color = 0.71658, 0.717121, 0.978188, 1; 12:20:24.373 [planetinfo.record]: cloud_alpha = 1.000000; 12:20:24.373 [planetinfo.record]: cloud_color = 0.748558, 0.74707, 0.9375, 1; 12:20:24.373 [planetinfo.record]: cloud_fraction = 0.150000; 12:20:24.373 [planetinfo.record]: land_color = 0.507812, 0.493927, 0.495229, 1; 12:20:24.373 [planetinfo.record]: land_fraction = 0.710000; 12:20:24.373 [planetinfo.record]: polar_cloud_color = 0.805754, 0.80484, 0.921875, 1; 12:20:24.373 [planetinfo.record]: polar_land_color = 0.949219, 0.94273, 0.943338, 1; 12:20:24.373 [planetinfo.record]: polar_sea_color = 0.944922, 0.906774, 0.893523, 1; 12:20:24.373 [planetinfo.record]: sea_color = 0.550781, 0.461839, 0.430943, 1; 12:20:24.373 [planetinfo.record]: rotation_speed = 0.002836 12:20:24.373 [planetinfo.record]: planet zpos = 407290.000000 12:20:24.373 [planetinfo.record]: sun_radius = 61608.272705 12:20:24.373 [planetinfo.record]: sun_vector = -0.516 0.054 0.855 12:20:24.373 [planetinfo.record]: sun_distance = 501280 12:20:24.373 [planetinfo.record]: corona_flare = 0.051167 12:20:24.373 [planetinfo.record]: corona_hues = 0.801163 12:20:24.373 [planetinfo.record]: sun_color = 0.682196, 0.472051, 0.412663, 1 12:20:24.373 [planetinfo.record]: corona_shimmer = 0.316611 12:20:24.374 [planetinfo.record]: station_vector = -0.922 -0.195 0.334 12:20:24.374 [planetinfo.record]: station = coriolis 12:20:29.356 [PLANETINFO OVER]: Done 12:20:29.357 [PLANETINFO LOGGING]: 2 49 12:20:30.359 [planetinfo.record]: seed = 169 123 5 214 12:20:30.359 [planetinfo.record]: coordinates = 123 92 12:20:30.359 [planetinfo.record]: government = 5; 12:20:30.359 [planetinfo.record]: economy = 4; 12:20:30.359 [planetinfo.record]: techlevel = 9; 12:20:30.359 [planetinfo.record]: population = 46; 12:20:30.359 [planetinfo.record]: productivity = 19872; 12:20:30.359 [planetinfo.record]: name = "Vereza"; 12:20:30.359 [planetinfo.record]: inhabitant = "Human Colonial"; 12:20:30.360 [planetinfo.record]: inhabitants = "Human Colonials"; 12:20:30.360 [planetinfo.record]: description = "The planet Vereza is an unremarkable dump."; 12:20:30.372 [planetinfo.record]: air_color = 0.725629, 0.708523, 0.991846, 1; 12:20:30.372 [planetinfo.record]: cloud_alpha = 1.000000; 12:20:30.372 [planetinfo.record]: cloud_color = 0.775514, 0.726242, 0.978516, 1; 12:20:30.372 [planetinfo.record]: cloud_fraction = 0.620000; 12:20:30.372 [planetinfo.record]: land_color = 0.66, 0.597158, 0.257812, 1; 12:20:30.372 [planetinfo.record]: land_fraction = 0.570000; 12:20:30.372 [planetinfo.record]: polar_cloud_color = 0.818407, 0.788814, 0.940332, 1; 12:20:30.372 [planetinfo.record]: polar_land_color = 0.934, 0.911767, 0.791711, 1; 12:20:30.372 [planetinfo.record]: polar_sea_color = 0.870446, 0.925391, 0.881606, 1; 12:20:30.372 [planetinfo.record]: sea_color = 0.568896, 0.746094, 0.60489, 1; 12:20:30.372 [planetinfo.record]: rotation_speed = 0.002303 12:20:30.372 [planetinfo.record]: planet zpos = 537000.000000 12:20:30.372 [planetinfo.record]: sun_radius = 101311.607361 12:20:30.372 [planetinfo.record]: sun_vector = 0.642 -0.649 -0.408 12:20:30.372 [planetinfo.record]: sun_distance = 895000 12:20:30.372 [planetinfo.record]: corona_flare = 0.043135 12:20:30.372 [planetinfo.record]: corona_hues = 0.934196 12:20:30.372 [planetinfo.record]: sun_color = 0.71693, 0.806623, 0.810458, 1 12:20:30.373 [planetinfo.record]: corona_shimmer = 0.684835 12:20:30.373 [planetinfo.record]: station_vector = -0.147 0.760 0.633 12:20:30.373 [planetinfo.record]: station = coriolis 12:20:34.703 [PLANETINFO OVER]: Done 12:20:34.704 [PLANETINFO LOGGING]: 2 50 12:20:35.707 [planetinfo.record]: seed = 153 132 45 51 12:20:35.707 [planetinfo.record]: coordinates = 132 0 12:20:35.707 [planetinfo.record]: government = 3; 12:20:35.707 [planetinfo.record]: economy = 0; 12:20:35.707 [planetinfo.record]: techlevel = 9; 12:20:35.707 [planetinfo.record]: population = 40; 12:20:35.707 [planetinfo.record]: productivity = 22400; 12:20:35.707 [planetinfo.record]: name = "Betia"; 12:20:35.707 [planetinfo.record]: inhabitant = "Human Colonial"; 12:20:35.707 [planetinfo.record]: inhabitants = "Human Colonials"; 12:20:35.707 [planetinfo.record]: description = "This planet is fabled for its unusual dense forests and its exotic cuisine."; 12:20:35.732 [planetinfo.record]: air_color = 0.639276, 0.869572, 0.802824, 1; 12:20:35.732 [planetinfo.record]: cloud_alpha = 1.000000; 12:20:35.732 [planetinfo.record]: cloud_color = 0.611328, 0.508719, 0.491928, 1; 12:20:35.732 [planetinfo.record]: cloud_fraction = 0.230000; 12:20:35.732 [planetinfo.record]: land_color = 0.253876, 0.439442, 0.550781, 1; 12:20:35.732 [planetinfo.record]: land_fraction = 0.630000; 12:20:35.732 [planetinfo.record]: polar_cloud_color = 0.775098, 0.693787, 0.680481, 1; 12:20:35.732 [planetinfo.record]: polar_land_color = 0.817579, 0.897168, 0.944922, 1; 12:20:35.732 [planetinfo.record]: polar_sea_color = 0.87267, 0.921875, 0.862457, 1; 12:20:35.732 [planetinfo.record]: sea_color = 0.614452, 0.78125, 0.579834, 1; 12:20:35.732 [planetinfo.record]: rotation_speed = 0.001293 12:20:35.732 [planetinfo.record]: planet zpos = 371600.000000 12:20:35.732 [planetinfo.record]: sun_radius = 90673.892822 12:20:35.732 [planetinfo.record]: sun_vector = -0.234 0.220 0.947 12:20:35.732 [planetinfo.record]: sun_distance = 706040 12:20:35.732 [planetinfo.record]: corona_flare = 0.036612 12:20:35.732 [planetinfo.record]: corona_hues = 0.563965 12:20:35.732 [planetinfo.record]: sun_color = 0.598135, 0.602382, 0.7974, 1 12:20:35.733 [planetinfo.record]: corona_shimmer = 0.362682 12:20:35.733 [planetinfo.record]: station_vector = 0.638 -0.730 0.243 12:20:35.733 [planetinfo.record]: station = coriolis 12:20:40.374 [PLANETINFO OVER]: Done 12:20:40.375 [PLANETINFO LOGGING]: 2 51 12:20:41.377 [planetinfo.record]: seed = 193 90 85 130 12:20:41.377 [planetinfo.record]: coordinates = 90 111 12:20:41.377 [planetinfo.record]: government = 0; 12:20:41.377 [planetinfo.record]: economy = 7; 12:20:41.377 [planetinfo.record]: techlevel = 2; 12:20:41.377 [planetinfo.record]: population = 16; 12:20:41.377 [planetinfo.record]: productivity = 1536; 12:20:41.377 [planetinfo.record]: name = "Xeines"; 12:20:41.377 [planetinfo.record]: inhabitant = "Human Colonial"; 12:20:41.377 [planetinfo.record]: inhabitants = "Human Colonials"; 12:20:41.377 [planetinfo.record]: description = "This world is very notable for the Xeinesian tree snake and its exotic goat soup."; 12:20:41.396 [planetinfo.record]: air_color = 0.567978, 0.467701, 0.94827, 1; 12:20:41.396 [planetinfo.record]: cloud_alpha = 1.000000; 12:20:41.396 [planetinfo.record]: cloud_color = 0.566829, 0.0662231, 0.847656, 1; 12:20:41.396 [planetinfo.record]: cloud_fraction = 0.310000; 12:20:41.396 [planetinfo.record]: land_color = 0.583984, 0.424301, 0.525351, 1; 12:20:41.396 [planetinfo.record]: land_fraction = 0.520000; 12:20:41.396 [planetinfo.record]: polar_cloud_color = 0.698932, 0.373581, 0.881445, 1; 12:20:41.396 [planetinfo.record]: polar_land_color = 0.941602, 0.877234, 0.917967, 1; 12:20:41.396 [planetinfo.record]: polar_sea_color = 0.918945, 0.842287, 0.791513, 1; 12:20:41.396 [planetinfo.record]: sea_color = 0.810547, 0.540084, 0.360947, 1; 12:20:41.396 [planetinfo.record]: rotation_speed = 0.002573 12:20:41.396 [planetinfo.record]: planet zpos = 410160.000000 12:20:41.396 [planetinfo.record]: sun_radius = 85524.580994 12:20:41.396 [planetinfo.record]: sun_vector = 0.295 -0.743 0.600 12:20:41.396 [planetinfo.record]: sun_distance = 615240 12:20:41.396 [planetinfo.record]: corona_flare = 0.000002 12:20:41.396 [planetinfo.record]: corona_hues = 0.852715 12:20:41.396 [planetinfo.record]: sun_color = 0.65408, 0.50956, 0.386015, 1 12:20:41.396 [planetinfo.record]: corona_shimmer = 0.306883 12:20:41.396 [planetinfo.record]: station_vector = 0.305 -0.889 0.342 12:20:41.397 [planetinfo.record]: station = coriolis 12:20:46.205 [PLANETINFO OVER]: Done 12:20:46.206 [PLANETINFO LOGGING]: 2 52 12:20:47.226 [planetinfo.record]: seed = 241 248 141 111 12:20:47.226 [planetinfo.record]: coordinates = 248 41 12:20:47.226 [planetinfo.record]: government = 6; 12:20:47.226 [planetinfo.record]: economy = 1; 12:20:47.226 [planetinfo.record]: techlevel = 9; 12:20:47.226 [planetinfo.record]: population = 44; 12:20:47.226 [planetinfo.record]: productivity = 31680; 12:20:47.226 [planetinfo.record]: name = "Aenqute"; 12:20:47.226 [planetinfo.record]: inhabitant = "Blue Bug-Eyed Bird"; 12:20:47.226 [planetinfo.record]: inhabitants = "Blue Bug-Eyed Birds"; 12:20:47.226 [planetinfo.record]: description = "The world Aenqute is reasonably fabled for its fabulous cuisine and the Aenquteian mountain slug."; 12:20:47.227 [planetinfo.record]: air_color = 0.765083, 0.538206, 0.937863, 1; 12:20:47.227 [planetinfo.record]: cloud_alpha = 1.000000; 12:20:47.227 [planetinfo.record]: cloud_color = 0.816406, 0.261505, 0.417571, 1; 12:20:47.227 [planetinfo.record]: cloud_fraction = 0.430000; 12:20:47.227 [planetinfo.record]: land_color = 0.66, 0.177891, 0.302184, 1; 12:20:47.228 [planetinfo.record]: land_fraction = 0.320000; 12:20:47.228 [planetinfo.record]: polar_cloud_color = 0.867383, 0.498915, 0.602546, 1; 12:20:47.228 [planetinfo.record]: polar_land_color = 0.934, 0.763436, 0.807409, 1; 12:20:47.228 [planetinfo.record]: polar_sea_color = 0.942187, 0.929927, 0.893146, 1; 12:20:47.228 [planetinfo.record]: sea_color = 0.578125, 0.548033, 0.457758, 1; 12:20:47.228 [planetinfo.record]: rotation_speed = 0.001025 12:20:47.228 [planetinfo.record]: planet zpos = 828480.000000 12:20:47.228 [planetinfo.record]: sun_radius = 153931.514893 12:20:47.228 [planetinfo.record]: sun_vector = 0.329 -0.422 -0.845 12:20:47.228 [planetinfo.record]: sun_distance = 1104640 12:20:47.228 [planetinfo.record]: corona_flare = 0.075459 12:20:47.228 [planetinfo.record]: corona_hues = 0.828133 12:20:47.228 [planetinfo.record]: sun_color = 0.754509, 0.75599, 0.772427, 1 12:20:47.228 [planetinfo.record]: corona_shimmer = 0.495764 12:20:47.228 [planetinfo.record]: station_vector = -0.400 -0.551 0.732 12:20:47.228 [planetinfo.record]: station = coriolis 12:20:52.669 [PLANETINFO OVER]: Done 12:20:52.670 [PLANETINFO LOGGING]: 2 53 12:20:53.673 [planetinfo.record]: seed = 185 252 229 137 12:20:53.674 [planetinfo.record]: coordinates = 252 250 12:20:53.674 [planetinfo.record]: government = 7; 12:20:53.674 [planetinfo.record]: economy = 2; 12:20:53.674 [planetinfo.record]: techlevel = 9; 12:20:53.674 [planetinfo.record]: population = 46; 12:20:53.674 [planetinfo.record]: productivity = 32384; 12:20:53.674 [planetinfo.record]: name = "Esleusbe"; 12:20:53.674 [planetinfo.record]: inhabitant = "Small Black Insect"; 12:20:53.674 [planetinfo.record]: inhabitants = "Small Black Insects"; 12:20:53.674 [planetinfo.record]: description = "The world Esleusbe is beset by evil disease."; 12:20:53.695 [planetinfo.record]: air_color = 0.816865, 0.75493, 0.983391, 1; 12:20:53.695 [planetinfo.record]: cloud_alpha = 1.000000; 12:20:53.695 [planetinfo.record]: cloud_color = 0.937127, 0.860046, 0.953125, 1; 12:20:53.695 [planetinfo.record]: cloud_fraction = 0.310000; 12:20:53.695 [planetinfo.record]: land_color = 0.319688, 0.66, 0.516431, 1; 12:20:53.696 [planetinfo.record]: land_fraction = 0.620000; 12:20:53.696 [planetinfo.record]: polar_cloud_color = 0.919162, 0.87221, 0.928906, 1; 12:20:53.696 [planetinfo.record]: polar_land_color = 0.813602, 0.934, 0.883207, 1; 12:20:53.696 [planetinfo.record]: polar_sea_color = 0.943945, 0.912027, 0.892876, 1; 12:20:53.696 [planetinfo.record]: sea_color = 0.560547, 0.484731, 0.439241, 1; 12:20:53.696 [planetinfo.record]: rotation_speed = 0.004163 12:20:53.696 [planetinfo.record]: planet zpos = 805800.000000 12:20:53.696 [planetinfo.record]: sun_radius = 143742.149048 12:20:53.696 [planetinfo.record]: sun_vector = 0.055 0.796 0.603 12:20:53.696 [planetinfo.record]: sun_distance = 1020680 12:20:53.696 [planetinfo.record]: corona_flare = 0.004855 12:20:53.696 [planetinfo.record]: corona_hues = 0.773636 12:20:53.696 [planetinfo.record]: sun_color = 0.759531, 0.640932, 0.465919, 1 12:20:53.696 [planetinfo.record]: corona_shimmer = 0.587588 12:20:53.696 [planetinfo.record]: station_vector = 0.721 -0.692 0.051 12:20:53.696 [planetinfo.record]: station = coriolis 12:20:58.634 [PLANETINFO OVER]: Done 12:20:58.635 [PLANETINFO LOGGING]: 2 54 12:20:59.637 [planetinfo.record]: seed = 105 19 237 156 12:20:59.637 [planetinfo.record]: coordinates = 19 8 12:20:59.637 [planetinfo.record]: government = 5; 12:20:59.637 [planetinfo.record]: economy = 0; 12:20:59.637 [planetinfo.record]: techlevel = 13; 12:20:59.637 [planetinfo.record]: population = 58; 12:20:59.637 [planetinfo.record]: productivity = 41760; 12:20:59.637 [planetinfo.record]: name = "Teedus"; 12:20:59.637 [planetinfo.record]: inhabitant = "Black Bony Lobster"; 12:20:59.637 [planetinfo.record]: inhabitants = "Black Bony Lobsters"; 12:20:59.637 [planetinfo.record]: description = "This planet is most notable for Teedusian Dier water but beset by lethal disease."; 12:20:59.641 [planetinfo.record]: air_color = 0.632622, 0.865803, 0.94957, 1; 12:20:59.641 [planetinfo.record]: cloud_alpha = 1.000000; 12:20:59.641 [planetinfo.record]: cloud_color = 0.57586, 0.851562, 0.508942, 1; 12:20:59.641 [planetinfo.record]: cloud_fraction = 0.360000; 12:20:59.641 [planetinfo.record]: land_color = 0.283594, 0.66, 0.421805, 1; 12:20:59.642 [planetinfo.record]: land_fraction = 0.560000; 12:20:59.642 [planetinfo.record]: polar_cloud_color = 0.704486, 0.883203, 0.661109, 1; 12:20:59.642 [planetinfo.record]: polar_land_color = 0.800832, 0.934, 0.84973, 1; 12:20:59.642 [planetinfo.record]: polar_sea_color = 0.946289, 0.913755, 0.897866, 1; 12:20:59.642 [planetinfo.record]: sea_color = 0.537109, 0.463244, 0.42717, 1; 12:20:59.642 [planetinfo.record]: rotation_speed = 0.001949 12:20:59.642 [planetinfo.record]: planet zpos = 886050.000000 12:20:59.642 [planetinfo.record]: sun_radius = 184301.716919 12:20:59.642 [planetinfo.record]: sun_vector = 0.008 -0.952 0.307 12:20:59.642 [planetinfo.record]: sun_distance = 1004190 12:20:59.642 [planetinfo.record]: corona_flare = 0.018961 12:20:59.642 [planetinfo.record]: corona_hues = 0.948517 12:20:59.642 [planetinfo.record]: sun_color = 0.190497, 0.6276, 0.667828, 1 12:20:59.642 [planetinfo.record]: corona_shimmer = 0.460969 12:20:59.642 [planetinfo.record]: station_vector = 0.129 -0.192 0.973 12:20:59.642 [planetinfo.record]: station = icosahedron 12:21:05.024 [PLANETINFO OVER]: Done 12:21:05.025 [PLANETINFO LOGGING]: 2 55 12:21:06.042 [planetinfo.record]: seed = 17 190 53 223 12:21:06.042 [planetinfo.record]: coordinates = 190 104 12:21:06.042 [planetinfo.record]: government = 2; 12:21:06.042 [planetinfo.record]: economy = 0; 12:21:06.042 [planetinfo.record]: techlevel = 10; 12:21:06.042 [planetinfo.record]: population = 43; 12:21:06.042 [planetinfo.record]: productivity = 20640; 12:21:06.042 [planetinfo.record]: name = "Onbige"; 12:21:06.043 [planetinfo.record]: inhabitant = "Human Colonial"; 12:21:06.043 [planetinfo.record]: inhabitants = "Human Colonials"; 12:21:06.043 [planetinfo.record]: description = "This planet is notable for the Onbigeian tree grub and its inhabitants’ ancient loathing of casinos."; 12:21:06.076 [planetinfo.record]: air_color = 0.604023, 0.881146, 0.969082, 1; 12:21:06.076 [planetinfo.record]: cloud_alpha = 1.000000; 12:21:06.076 [planetinfo.record]: cloud_color = 0.555071, 0.910156, 0.426636, 1; 12:21:06.076 [planetinfo.record]: cloud_fraction = 0.260000; 12:21:06.076 [planetinfo.record]: land_color = 0.66, 0.183047, 0.205404, 1; 12:21:06.076 [planetinfo.record]: land_fraction = 0.370000; 12:21:06.076 [planetinfo.record]: polar_cloud_color = 0.687785, 0.90957, 0.607565, 1; 12:21:06.076 [planetinfo.record]: polar_land_color = 0.934, 0.76526, 0.773169, 1; 12:21:06.076 [planetinfo.record]: polar_sea_color = 0.941602, 0.929073, 0.891487, 1; 12:21:06.076 [planetinfo.record]: sea_color = 0.583984, 0.552903, 0.45966, 1; 12:21:06.076 [planetinfo.record]: rotation_speed = 0.002006 12:21:06.076 [planetinfo.record]: planet zpos = 821520.000000 12:21:06.076 [planetinfo.record]: sun_radius = 175136.257324 12:21:06.076 [planetinfo.record]: sun_vector = -0.031 0.343 -0.939 12:21:06.076 [planetinfo.record]: sun_distance = 1506120 12:21:06.076 [planetinfo.record]: corona_flare = 0.034973 12:21:06.076 [planetinfo.record]: corona_hues = 0.989487 12:21:06.076 [planetinfo.record]: sun_color = 0.45899, 0.509665, 0.751318, 1 12:21:06.076 [planetinfo.record]: corona_shimmer = 0.425470 12:21:06.076 [planetinfo.record]: station_vector = -0.893 0.043 0.448 12:21:06.076 [planetinfo.record]: station = coriolis 12:21:11.439 [PLANETINFO OVER]: Done 12:21:11.439 [PLANETINFO LOGGING]: 2 56 12:21:12.459 [planetinfo.record]: seed = 129 136 205 49 12:21:12.459 [planetinfo.record]: coordinates = 136 163 12:21:12.459 [planetinfo.record]: government = 0; 12:21:12.459 [planetinfo.record]: economy = 3; 12:21:12.459 [planetinfo.record]: techlevel = 4; 12:21:12.459 [planetinfo.record]: population = 20; 12:21:12.459 [planetinfo.record]: productivity = 4480; 12:21:12.459 [planetinfo.record]: name = "Atistiso"; 12:21:12.459 [planetinfo.record]: inhabitant = "Red Bony Bird"; 12:21:12.459 [planetinfo.record]: inhabitants = "Red Bony Birds"; 12:21:12.459 [planetinfo.record]: description = "This planet is notable for the Atistisoian edible poet and Atistisoian evil brandy."; 12:21:12.472 [planetinfo.record]: air_color = 0.697963, 0.811764, 0.924855, 1; 12:21:12.472 [planetinfo.record]: cloud_alpha = 1.000000; 12:21:12.472 [planetinfo.record]: cloud_color = 0.671066, 0.777344, 0.725035, 1; 12:21:12.472 [planetinfo.record]: cloud_fraction = 0.240000; 12:21:12.472 [planetinfo.record]: land_color = 0.655529, 0.0876563, 0.66, 1; 12:21:12.472 [planetinfo.record]: land_fraction = 0.650000; 12:21:12.472 [planetinfo.record]: polar_cloud_color = 0.77719, 0.849805, 0.814064, 1; 12:21:12.472 [planetinfo.record]: polar_land_color = 0.932418, 0.731512, 0.934, 1; 12:21:12.472 [planetinfo.record]: polar_sea_color = 0.938477, 0.93645, 0.886604, 1; 12:21:12.472 [planetinfo.record]: sea_color = 0.615234, 0.609921, 0.47921, 1; 12:21:12.472 [planetinfo.record]: rotation_speed = 0.004105 12:21:12.472 [planetinfo.record]: planet zpos = 481200.000000 12:21:12.472 [planetinfo.record]: sun_radius = 73191.800537 12:21:12.472 [planetinfo.record]: sun_vector = 0.220 0.600 -0.769 12:21:12.473 [planetinfo.record]: sun_distance = 705760 12:21:12.473 [planetinfo.record]: corona_flare = 0.048752 12:21:12.473 [planetinfo.record]: corona_hues = 0.916122 12:21:12.473 [planetinfo.record]: sun_color = 0.450206, 0.577883, 0.724002, 1 12:21:12.473 [planetinfo.record]: corona_shimmer = 0.425128 12:21:12.473 [planetinfo.record]: station_vector = 0.465 -0.696 0.547 12:21:12.473 [planetinfo.record]: station = coriolis 12:21:18.108 [PLANETINFO OVER]: Done 12:21:18.109 [PLANETINFO LOGGING]: 2 57 12:21:19.120 [planetinfo.record]: seed = 73 167 197 28 12:21:19.120 [planetinfo.record]: coordinates = 167 23 12:21:19.120 [planetinfo.record]: government = 1; 12:21:19.120 [planetinfo.record]: economy = 7; 12:21:19.120 [planetinfo.record]: techlevel = 4; 12:21:19.120 [planetinfo.record]: population = 25; 12:21:19.120 [planetinfo.record]: productivity = 3000; 12:21:19.120 [planetinfo.record]: name = "Teaned"; 12:21:19.120 [planetinfo.record]: inhabitant = "Green Slimy Rodent"; 12:21:19.120 [planetinfo.record]: inhabitants = "Green Slimy Rodents"; 12:21:19.120 [planetinfo.record]: description = "This planet is beset by deadly earthquakes."; 12:21:19.125 [planetinfo.record]: air_color = 0.808057, 0.483742, 0.985992, 1; 12:21:19.125 [planetinfo.record]: cloud_alpha = 1.000000; 12:21:19.125 [planetinfo.record]: cloud_color = 0.960938, 0.0675659, 0.109443, 1; 12:21:19.125 [planetinfo.record]: cloud_fraction = 0.460000; 12:21:19.126 [planetinfo.record]: land_color = 0.618831, 0.66, 0.587813, 1; 12:21:19.126 [planetinfo.record]: land_fraction = 0.280000; 12:21:19.126 [planetinfo.record]: polar_cloud_color = 0.932422, 0.390634, 0.41603, 1; 12:21:19.126 [planetinfo.record]: polar_land_color = 0.919435, 0.934, 0.908461, 1; 12:21:19.126 [planetinfo.record]: polar_sea_color = 0.731186, 0.932422, 0.913556, 1; 12:21:19.126 [planetinfo.record]: sea_color = 0.092392, 0.675781, 0.621089, 1; 12:21:19.126 [planetinfo.record]: rotation_speed = 0.000976 12:21:19.126 [planetinfo.record]: planet zpos = 908250.000000 12:21:19.126 [planetinfo.record]: sun_radius = 166285.215759 12:21:19.126 [planetinfo.record]: sun_vector = 0.505 0.251 -0.826 12:21:19.126 [planetinfo.record]: sun_distance = 1211000 12:21:19.126 [planetinfo.record]: corona_flare = 0.078662 12:21:19.126 [planetinfo.record]: corona_hues = 0.580894 12:21:19.126 [planetinfo.record]: sun_color = 0.311773, 0.614057, 0.718185, 1 12:21:19.126 [planetinfo.record]: corona_shimmer = 0.301617 12:21:19.127 [planetinfo.record]: station_vector = -0.510 0.259 0.821 12:21:19.127 [planetinfo.record]: station = coriolis 12:21:23.453 [PLANETINFO OVER]: Done 12:21:23.454 [PLANETINFO LOGGING]: 2 58 12:21:24.460 [planetinfo.record]: seed = 185 152 173 20 12:21:24.460 [planetinfo.record]: coordinates = 152 160 12:21:24.460 [planetinfo.record]: government = 7; 12:21:24.460 [planetinfo.record]: economy = 0; 12:21:24.460 [planetinfo.record]: techlevel = 11; 12:21:24.460 [planetinfo.record]: population = 52; 12:21:24.460 [planetinfo.record]: productivity = 45760; 12:21:24.460 [planetinfo.record]: name = "Radiqu"; 12:21:24.460 [planetinfo.record]: inhabitant = "Green Slimy Rodent"; 12:21:24.460 [planetinfo.record]: inhabitants = "Green Slimy Rodents"; 12:21:24.460 [planetinfo.record]: description = "This planet is notable for its unusual oceans and its inhabitants’ exceptional love for food blenders."; 12:21:24.472 [planetinfo.record]: air_color = 0.485979, 0.668916, 0.850711, 1; 12:21:24.472 [planetinfo.record]: cloud_alpha = 1.000000; 12:21:24.472 [planetinfo.record]: cloud_color = 0.17334, 0.554688, 0.366993, 1; 12:21:24.472 [planetinfo.record]: cloud_fraction = 0.430000; 12:21:24.472 [planetinfo.record]: land_color = 0.472119, 0.66, 0.386719, 1; 12:21:24.472 [planetinfo.record]: land_fraction = 0.530000; 12:21:24.472 [planetinfo.record]: polar_cloud_color = 0.427512, 0.749609, 0.591077, 1; 12:21:24.472 [planetinfo.record]: polar_land_color = 0.86753, 0.934, 0.837316, 1; 12:21:24.472 [planetinfo.record]: polar_sea_color = 0.942969, 0.918258, 0.891953, 1; 12:21:24.472 [planetinfo.record]: sea_color = 0.570312, 0.510531, 0.446893, 1; 12:21:24.472 [planetinfo.record]: rotation_speed = 0.002532 12:21:24.472 [planetinfo.record]: planet zpos = 479040.000000 12:21:24.473 [planetinfo.record]: sun_radius = 92812.050781 12:21:24.473 [planetinfo.record]: sun_vector = 0.061 -0.494 -0.868 12:21:24.473 [planetinfo.record]: sun_distance = 918160 12:21:24.473 [planetinfo.record]: corona_flare = 0.021869 12:21:24.473 [planetinfo.record]: corona_hues = 0.654900 12:21:24.473 [planetinfo.record]: sun_color = 0.7595, 0.652669, 0.555904, 1 12:21:24.473 [planetinfo.record]: corona_shimmer = 0.660352 12:21:24.473 [planetinfo.record]: station_vector = -0.953 -0.303 0.001 12:21:24.473 [planetinfo.record]: station = icosahedron 12:21:29.663 [PLANETINFO OVER]: Done 12:21:29.663 [PLANETINFO LOGGING]: 2 59 12:21:30.677 [planetinfo.record]: seed = 225 92 21 165 12:21:30.677 [planetinfo.record]: coordinates = 92 250 12:21:30.677 [planetinfo.record]: government = 4; 12:21:30.677 [planetinfo.record]: economy = 2; 12:21:30.677 [planetinfo.record]: techlevel = 7; 12:21:30.677 [planetinfo.record]: population = 35; 12:21:30.677 [planetinfo.record]: productivity = 17920; 12:21:30.678 [planetinfo.record]: name = "Ceteri"; 12:21:30.678 [planetinfo.record]: inhabitant = "Human Colonial"; 12:21:30.678 [planetinfo.record]: inhabitants = "Human Colonials"; 12:21:30.678 [planetinfo.record]: description = "Ceteri is mildly famous for its pink oceans and its exciting sit coms."; 12:21:30.696 [planetinfo.record]: air_color = 0.70335, 0.616085, 0.845508, 1; 12:21:30.696 [planetinfo.record]: cloud_alpha = 1.000000; 12:21:30.696 [planetinfo.record]: cloud_color = 0.539062, 0.423248, 0.520967, 1; 12:21:30.696 [planetinfo.record]: cloud_fraction = 0.360000; 12:21:30.696 [planetinfo.record]: land_color = 0.493389, 0.66, 0.417656, 1; 12:21:30.696 [planetinfo.record]: land_fraction = 0.250000; 12:21:30.696 [planetinfo.record]: polar_cloud_color = 0.742578, 0.642867, 0.726998, 1; 12:21:30.696 [planetinfo.record]: polar_land_color = 0.875055, 0.934, 0.848262, 1; 12:21:30.696 [planetinfo.record]: polar_sea_color = 0.9, 0.84465, 0.8325, 1; 12:21:30.696 [planetinfo.record]: sea_color = 1, 0.754, 0.7, 1; 12:21:30.696 [planetinfo.record]: rotation_speed = 0.001339 12:21:30.696 [planetinfo.record]: planet zpos = 460680.000000 12:21:30.696 [planetinfo.record]: sun_radius = 98437.426758 12:21:30.696 [planetinfo.record]: sun_vector = 0.413 0.738 -0.534 12:21:30.696 [planetinfo.record]: sun_distance = 837600 12:21:30.696 [planetinfo.record]: corona_flare = 0.065472 12:21:30.696 [planetinfo.record]: corona_hues = 0.704498 12:21:30.696 [planetinfo.record]: sun_color = 0.687863, 0.630389, 0.443879, 1 12:21:30.697 [planetinfo.record]: corona_shimmer = 0.446455 12:21:30.697 [planetinfo.record]: station_vector = -0.749 -0.457 0.479 12:21:30.697 [planetinfo.record]: station = coriolis 12:21:35.822 [PLANETINFO OVER]: Done 12:21:35.822 [PLANETINFO LOGGING]: 2 60 12:21:36.829 [planetinfo.record]: seed = 145 160 13 156 12:21:36.829 [planetinfo.record]: coordinates = 160 254 12:21:36.829 [planetinfo.record]: government = 2; 12:21:36.829 [planetinfo.record]: economy = 6; 12:21:36.829 [planetinfo.record]: techlevel = 2; 12:21:36.829 [planetinfo.record]: population = 17; 12:21:36.830 [planetinfo.record]: productivity = 3264; 12:21:36.830 [planetinfo.record]: name = "Teanti"; 12:21:36.830 [planetinfo.record]: inhabitant = "Human Colonial"; 12:21:36.830 [planetinfo.record]: inhabitants = "Human Colonials"; 12:21:36.830 [planetinfo.record]: description = "The planet Teanti is an unremarkable dump."; 12:21:36.848 [planetinfo.record]: air_color = 0.629225, 0.840903, 0.967131, 1; 12:21:36.848 [planetinfo.record]: cloud_alpha = 1.000000; 12:21:36.848 [planetinfo.record]: cloud_color = 0.49807, 0.904297, 0.552022, 1; 12:21:36.848 [planetinfo.record]: cloud_fraction = 0.620000; 12:21:36.848 [planetinfo.record]: land_color = 0.638672, 0.414139, 0.514126, 1; 12:21:36.848 [planetinfo.record]: land_fraction = 0.360000; 12:21:36.848 [planetinfo.record]: polar_cloud_color = 0.652301, 0.906934, 0.68612, 1; 12:21:36.848 [planetinfo.record]: polar_land_color = 0.936133, 0.853855, 0.890495, 1; 12:21:36.848 [planetinfo.record]: polar_sea_color = 0.930664, 0.92864, 0.87886, 1; 12:21:36.848 [planetinfo.record]: sea_color = 0.693359, 0.687329, 0.538979, 1; 12:21:36.848 [planetinfo.record]: rotation_speed = 0.002283 12:21:36.848 [planetinfo.record]: planet zpos = 665280.000000 12:21:36.848 [planetinfo.record]: sun_radius = 158881.816406 12:21:36.848 [planetinfo.record]: sun_vector = 0.693 0.459 0.556 12:21:36.848 [planetinfo.record]: sun_distance = 1330560 12:21:36.848 [planetinfo.record]: corona_flare = 0.030385 12:21:36.848 [planetinfo.record]: corona_hues = 0.979988 12:21:36.848 [planetinfo.record]: sun_color = 0.563093, 0.706211, 0.733954, 1 12:21:36.848 [planetinfo.record]: corona_shimmer = 0.307078 12:21:36.848 [planetinfo.record]: station_vector = 0.278 0.850 0.447 12:21:36.848 [planetinfo.record]: station = coriolis 12:21:41.138 [PLANETINFO OVER]: Done 12:21:41.138 [PLANETINFO LOGGING]: 2 61 12:21:42.149 [planetinfo.record]: seed = 89 79 165 2 12:21:42.149 [planetinfo.record]: coordinates = 79 119 12:21:42.149 [planetinfo.record]: government = 3; 12:21:42.149 [planetinfo.record]: economy = 7; 12:21:42.149 [planetinfo.record]: techlevel = 5; 12:21:42.149 [planetinfo.record]: population = 31; 12:21:42.149 [planetinfo.record]: productivity = 5208; 12:21:42.149 [planetinfo.record]: name = "Xeesanus"; 12:21:42.149 [planetinfo.record]: inhabitant = "Large Green Slimy Lizard"; 12:21:42.149 [planetinfo.record]: inhabitants = "Large Green Slimy Lizards"; 12:21:42.149 [planetinfo.record]: description = "The planet Xeesanus is well known for its inhabitants’ ancient loathing of casinos but ravaged by unpredictable solar activity."; 12:21:42.172 [planetinfo.record]: air_color = 0.634803, 0.880629, 0.82281, 1; 12:21:42.172 [planetinfo.record]: cloud_alpha = 1.000000; 12:21:42.172 [planetinfo.record]: cloud_color = 0.644531, 0.537745, 0.490952, 1; 12:21:42.172 [planetinfo.record]: cloud_fraction = 0.430000; 12:21:42.179 [planetinfo.record]: land_color = 0.546875, 0.474243, 0.491266, 1; 12:21:42.179 [planetinfo.record]: land_fraction = 0.580000; 12:21:42.180 [planetinfo.record]: polar_cloud_color = 0.790039, 0.708231, 0.672382, 1; 12:21:42.180 [planetinfo.record]: polar_land_color = 0.945312, 0.913925, 0.921282, 1; 12:21:42.180 [planetinfo.record]: polar_sea_color = 0.896104, 0.922656, 0.848772, 1; 12:21:42.180 [planetinfo.record]: sea_color = 0.684405, 0.773438, 0.525696, 1; 12:21:42.180 [planetinfo.record]: rotation_speed = 0.002825 12:21:42.180 [planetinfo.record]: planet zpos = 442910.000000 12:21:42.180 [planetinfo.record]: sun_radius = 111001.469879 12:21:42.180 [planetinfo.record]: sun_vector = -0.329 -0.151 -0.932 12:21:42.180 [planetinfo.record]: sun_distance = 783610 12:21:42.180 [planetinfo.record]: corona_flare = 0.021886 12:21:42.180 [planetinfo.record]: corona_hues = 0.863281 12:21:42.180 [planetinfo.record]: sun_color = 0.848581, 0.840046, 0.811523, 1 12:21:42.180 [planetinfo.record]: corona_shimmer = 0.347413 12:21:42.180 [planetinfo.record]: station_vector = -0.202 0.777 0.596 12:21:42.180 [planetinfo.record]: station = coriolis 12:21:47.238 [PLANETINFO OVER]: Done 12:21:47.239 [PLANETINFO LOGGING]: 2 62 12:21:48.249 [planetinfo.record]: seed = 137 232 109 206 12:21:48.249 [planetinfo.record]: coordinates = 232 27 12:21:48.249 [planetinfo.record]: government = 1; 12:21:48.249 [planetinfo.record]: economy = 3; 12:21:48.249 [planetinfo.record]: techlevel = 5; 12:21:48.249 [planetinfo.record]: population = 25; 12:21:48.249 [planetinfo.record]: productivity = 7000; 12:21:48.249 [planetinfo.record]: name = "Reenesma"; 12:21:48.249 [planetinfo.record]: inhabitant = "Human Colonial"; 12:21:48.249 [planetinfo.record]: inhabitants = "Human Colonials"; 12:21:48.249 [planetinfo.record]: description = "This planet is mildly noted for the Reenesmaian mountain lobstoid but plagued by occasional solar activity."; 12:21:48.268 [planetinfo.record]: air_color = 0.72443, 0.790543, 0.924855, 1; 12:21:48.268 [planetinfo.record]: cloud_alpha = 1.000000; 12:21:48.268 [planetinfo.record]: cloud_color = 0.737869, 0.776419, 0.777344, 1; 12:21:48.268 [planetinfo.record]: cloud_fraction = 0.280000; 12:21:48.268 [planetinfo.record]: land_color = 0.190781, 0.198113, 0.66, 1; 12:21:48.268 [planetinfo.record]: land_fraction = 0.620000; 12:21:48.268 [planetinfo.record]: polar_cloud_color = 0.822833, 0.849173, 0.849805, 1; 12:21:48.268 [planetinfo.record]: polar_land_color = 0.767996, 0.77059, 0.934, 1; 12:21:48.268 [planetinfo.record]: polar_sea_color = 0.941992, 0.899593, 0.886613, 1; 12:21:48.268 [planetinfo.record]: sea_color = 0.580078, 0.47564, 0.443669, 1; 12:21:48.268 [planetinfo.record]: rotation_speed = 0.003254 12:21:48.268 [planetinfo.record]: planet zpos = 862160.000000 12:21:48.268 [planetinfo.record]: sun_radius = 121122.850342 12:21:48.268 [planetinfo.record]: sun_vector = 0.682 0.095 0.725 12:21:48.268 [planetinfo.record]: sun_distance = 1127440 12:21:48.268 [planetinfo.record]: corona_flare = 0.003271 12:21:48.268 [planetinfo.record]: corona_hues = 0.886002 12:21:48.268 [planetinfo.record]: sun_color = 0.537122, 0.634894, 0.671304, 1 12:21:48.269 [planetinfo.record]: corona_shimmer = 0.282702 12:21:48.269 [planetinfo.record]: station_vector = 0.253 -0.967 0.014 12:21:48.269 [planetinfo.record]: station = coriolis 12:21:52.726 [PLANETINFO OVER]: Done 12:21:52.727 [PLANETINFO LOGGING]: 2 63 12:21:53.746 [planetinfo.record]: seed = 49 43 245 135 12:21:53.746 [planetinfo.record]: coordinates = 43 137 12:21:53.746 [planetinfo.record]: government = 6; 12:21:53.746 [planetinfo.record]: economy = 1; 12:21:53.746 [planetinfo.record]: techlevel = 12; 12:21:53.746 [planetinfo.record]: population = 56; 12:21:53.746 [planetinfo.record]: productivity = 40320; 12:21:53.746 [planetinfo.record]: name = "Soiserla"; 12:21:53.746 [planetinfo.record]: inhabitant = "Fierce Black Horned Humanoid"; 12:21:53.746 [planetinfo.record]: inhabitants = "Fierce Black Horned Humanoids"; 12:21:53.746 [planetinfo.record]: description = "This planet is a dull place."; 12:21:53.772 [planetinfo.record]: air_color = 0.60849, 0.564889, 0.863068, 1; 12:21:53.772 [planetinfo.record]: cloud_alpha = 1.000000; 12:21:53.772 [planetinfo.record]: cloud_color = 0.450205, 0.332886, 0.591797, 1; 12:21:53.772 [planetinfo.record]: cloud_fraction = 0.360000; 12:21:53.772 [planetinfo.record]: land_color = 0.560722, 0.250078, 0.66, 1; 12:21:53.772 [planetinfo.record]: land_fraction = 0.590000; 12:21:53.772 [planetinfo.record]: polar_cloud_color = 0.651718, 0.556771, 0.766309, 1; 12:21:53.772 [planetinfo.record]: polar_land_color = 0.898877, 0.788975, 0.934, 1; 12:21:53.772 [planetinfo.record]: polar_sea_color = 0.937305, 0.902749, 0.877533, 1; 12:21:53.772 [planetinfo.record]: sea_color = 0.626953, 0.534498, 0.467031, 1; 12:21:53.772 [planetinfo.record]: rotation_speed = 0.000693 12:21:53.772 [planetinfo.record]: planet zpos = 465100.000000 12:21:53.772 [planetinfo.record]: sun_radius = 120419.567871 12:21:53.772 [planetinfo.record]: sun_vector = -0.710 0.632 -0.309 12:21:53.772 [planetinfo.record]: sun_distance = 837180 12:21:53.772 [planetinfo.record]: corona_flare = 0.031357 12:21:53.772 [planetinfo.record]: corona_hues = 0.608368 12:21:53.772 [planetinfo.record]: sun_color = 0.218438, 0.490897, 0.672177, 1 12:21:53.772 [planetinfo.record]: corona_shimmer = 0.457634 12:21:53.772 [planetinfo.record]: station_vector = 0.770 -0.211 0.602 12:21:53.772 [planetinfo.record]: station = dodecahedron 12:21:57.917 [PLANETINFO OVER]: Done 12:21:57.917 [PLANETINFO LOGGING]: 2 64 12:21:58.931 [planetinfo.record]: seed = 33 181 77 226 12:21:58.931 [planetinfo.record]: coordinates = 181 240 12:21:58.931 [planetinfo.record]: government = 4; 12:21:58.931 [planetinfo.record]: economy = 0; 12:21:58.931 [planetinfo.record]: techlevel = 10; 12:21:58.931 [planetinfo.record]: population = 45; 12:21:58.931 [planetinfo.record]: productivity = 28800; 12:21:58.931 [planetinfo.record]: name = "Xesoon"; 12:21:58.931 [planetinfo.record]: inhabitant = "Human Colonial"; 12:21:58.931 [planetinfo.record]: inhabitants = "Human Colonials"; 12:21:58.931 [planetinfo.record]: description = "The world Xesoon is fabled for its weird exuberant forests and its inhabitants’ eccentric love for poetry."; 12:21:58.951 [planetinfo.record]: air_color = 0.627802, 0.799382, 0.986643, 1; 12:21:58.952 [planetinfo.record]: cloud_alpha = 1.000000; 12:21:58.952 [planetinfo.record]: cloud_color = 0.488968, 0.962891, 0.762954, 1; 12:21:58.952 [planetinfo.record]: cloud_fraction = 0.270000; 12:21:58.952 [planetinfo.record]: land_color = 0.45375, 0.66, 0.519814, 1; 12:21:58.952 [planetinfo.record]: land_fraction = 0.310000; 12:21:58.952 [planetinfo.record]: polar_cloud_color = 0.646201, 0.933301, 0.812181, 1; 12:21:58.952 [planetinfo.record]: polar_land_color = 0.861031, 0.934, 0.884404, 1; 12:21:58.952 [planetinfo.record]: polar_sea_color = 0.91522, 0.928711, 0.867039, 1; 12:21:58.952 [planetinfo.record]: sea_color = 0.671468, 0.712891, 0.523529, 1; 12:21:58.952 [planetinfo.record]: rotation_speed = 0.000443 12:21:58.952 [planetinfo.record]: planet zpos = 421080.000000 12:21:58.953 [planetinfo.record]: sun_radius = 87693.409576 12:21:58.953 [planetinfo.record]: sun_vector = -0.799 -0.601 -0.010 12:21:58.953 [planetinfo.record]: sun_distance = 736890 12:21:58.953 [planetinfo.record]: corona_flare = 0.013490 12:21:58.953 [planetinfo.record]: corona_hues = 0.604996 12:21:58.953 [planetinfo.record]: sun_color = 0.280937, 0.329058, 0.651135, 1 12:21:58.953 [planetinfo.record]: corona_shimmer = 0.309798 12:21:58.953 [planetinfo.record]: station_vector = -0.803 -0.590 0.083 12:21:58.953 [planetinfo.record]: station = coriolis 12:22:03.962 [PLANETINFO OVER]: Done 12:22:03.963 [PLANETINFO LOGGING]: 2 65 12:22:04.987 [planetinfo.record]: seed = 233 136 133 47 12:22:04.987 [planetinfo.record]: coordinates = 136 31 12:22:04.987 [planetinfo.record]: government = 5; 12:22:04.987 [planetinfo.record]: economy = 7; 12:22:04.987 [planetinfo.record]: techlevel = 3; 12:22:04.987 [planetinfo.record]: population = 25; 12:22:04.987 [planetinfo.record]: productivity = 5400; 12:22:04.987 [planetinfo.record]: name = "Atia"; 12:22:04.987 [planetinfo.record]: inhabitant = "Red Lizard"; 12:22:04.987 [planetinfo.record]: inhabitants = "Red Lizards"; 12:22:04.987 [planetinfo.record]: description = "This planet is notable for the Atiaian edible poet and Atiaian In brandy."; 12:22:05.004 [planetinfo.record]: air_color = 0.54139, 0.52217, 0.887783, 1; 12:22:05.004 [planetinfo.record]: cloud_alpha = 1.000000; 12:22:05.004 [planetinfo.record]: cloud_color = 0.314837, 0.241951, 0.666016, 1; 12:22:05.004 [planetinfo.record]: cloud_fraction = 0.480000; 12:22:05.004 [planetinfo.record]: land_color = 0.558164, 0.66, 0.456328, 1; 12:22:05.004 [planetinfo.record]: land_fraction = 0.640000; 12:22:05.004 [planetinfo.record]: polar_cloud_color = 0.536162, 0.481464, 0.799707, 1; 12:22:05.004 [planetinfo.record]: polar_land_color = 0.897972, 0.934, 0.861943, 1; 12:22:05.004 [planetinfo.record]: polar_sea_color = 0.948633, 0.888973, 0.942107, 1; 12:22:05.004 [planetinfo.record]: sea_color = 0.513672, 0.384451, 0.499538, 1; 12:22:05.004 [planetinfo.record]: rotation_speed = 0.004729 12:22:05.004 [planetinfo.record]: planet zpos = 882960.000000 12:22:05.004 [planetinfo.record]: sun_radius = 144522.766113 12:22:05.004 [planetinfo.record]: sun_vector = 0.769 0.578 0.273 12:22:05.004 [planetinfo.record]: sun_distance = 1494240 12:22:05.004 [planetinfo.record]: corona_flare = 0.095259 12:22:05.004 [planetinfo.record]: corona_hues = 0.997414 12:22:05.004 [planetinfo.record]: sun_color = 0.709016, 0.740828, 0.75152, 1 12:22:05.005 [planetinfo.record]: corona_shimmer = 0.355494 12:22:05.005 [planetinfo.record]: station_vector = -0.745 0.440 0.501 12:22:05.005 [planetinfo.record]: station = coriolis 12:22:09.973 [PLANETINFO OVER]: Done 12:22:09.975 [PLANETINFO LOGGING]: 2 66 12:22:10.988 [planetinfo.record]: seed = 217 150 45 254 12:22:10.988 [planetinfo.record]: coordinates = 150 143 12:22:10.988 [planetinfo.record]: government = 3; 12:22:10.988 [planetinfo.record]: economy = 7; 12:22:10.988 [planetinfo.record]: techlevel = 4; 12:22:10.988 [planetinfo.record]: population = 27; 12:22:10.988 [planetinfo.record]: productivity = 4536; 12:22:10.988 [planetinfo.record]: name = "Rizaoris"; 12:22:10.988 [planetinfo.record]: inhabitant = "Human Colonial"; 12:22:10.988 [planetinfo.record]: inhabitants = "Human Colonials"; 12:22:10.988 [planetinfo.record]: description = "The world Rizaoris is reasonably noted for its exotic fish meat."; 12:22:11.003 [planetinfo.record]: air_color = 0.682473, 0.866971, 0.865334, 1; 12:22:11.003 [planetinfo.record]: cloud_alpha = 1.000000; 12:22:11.003 [planetinfo.record]: cloud_color = 0.603516, 0.603147, 0.579941, 1; 12:22:11.003 [planetinfo.record]: cloud_fraction = 0.520000; 12:22:11.003 [planetinfo.record]: land_color = 0.66, 0.277632, 0.226875, 1; 12:22:11.003 [planetinfo.record]: land_fraction = 0.280000; 12:22:11.003 [planetinfo.record]: polar_cloud_color = 0.771582, 0.771288, 0.752745, 1; 12:22:11.003 [planetinfo.record]: polar_land_color = 0.934, 0.798723, 0.780766, 1; 12:22:11.003 [planetinfo.record]: polar_sea_color = 0.885723, 0.932813, 0.885352, 1; 12:22:11.003 [planetinfo.record]: sea_color = 0.536206, 0.671875, 0.535138, 1; 12:22:11.003 [planetinfo.record]: rotation_speed = 0.003888 12:22:11.004 [planetinfo.record]: planet zpos = 851500.000000 12:22:11.004 [planetinfo.record]: sun_radius = 198479.911804 12:22:11.004 [planetinfo.record]: sun_vector = 0.456 -0.529 0.716 12:22:11.004 [planetinfo.record]: sun_distance = 1244500 12:22:11.004 [planetinfo.record]: corona_flare = 0.065216 12:22:11.004 [planetinfo.record]: corona_hues = 0.776512 12:22:11.004 [planetinfo.record]: sun_color = 0.776273, 0.435944, 0.342515, 1 12:22:11.005 [planetinfo.record]: corona_shimmer = 0.836665 12:22:11.005 [planetinfo.record]: station_vector = -0.026 -0.340 0.940 12:22:11.006 [planetinfo.record]: station = coriolis 12:22:16.152 [PLANETINFO OVER]: Done 12:22:16.152 [PLANETINFO LOGGING]: 2 67 12:22:17.163 [planetinfo.record]: seed = 1 221 213 187 12:22:17.163 [planetinfo.record]: coordinates = 221 185 12:22:17.163 [planetinfo.record]: government = 0; 12:22:17.163 [planetinfo.record]: economy = 3; 12:22:17.163 [planetinfo.record]: techlevel = 5; 12:22:17.163 [planetinfo.record]: population = 24; 12:22:17.164 [planetinfo.record]: productivity = 5376; 12:22:17.164 [planetinfo.record]: name = "Anenmaqu"; 12:22:17.164 [planetinfo.record]: inhabitant = "Harmless Fat Insect"; 12:22:17.164 [planetinfo.record]: inhabitants = "Harmless Fat Insects"; 12:22:17.164 [planetinfo.record]: description = "The world Anenmaqu is beset by evil disease."; 12:22:17.171 [planetinfo.record]: air_color = 0.708689, 0.608543, 0.856564, 1; 12:22:17.171 [planetinfo.record]: cloud_alpha = 1.000000; 12:22:17.171 [planetinfo.record]: cloud_color = 0.572266, 0.418022, 0.53732, 1; 12:22:17.171 [planetinfo.record]: cloud_fraction = 0.260000; 12:22:17.171 [planetinfo.record]: land_color = 0.572344, 0.66, 0.60453, 1; 12:22:17.171 [planetinfo.record]: land_fraction = 0.320000; 12:22:17.171 [planetinfo.record]: polar_cloud_color = 0.75752, 0.62991, 0.728608, 1; 12:22:17.171 [planetinfo.record]: polar_land_color = 0.902988, 0.934, 0.914375, 1; 12:22:17.171 [planetinfo.record]: polar_sea_color = 0.910742, 0.785323, 0.741757, 1; 12:22:17.171 [planetinfo.record]: sea_color = 0.892578, 0.400908, 0.230118, 1; 12:22:17.171 [planetinfo.record]: rotation_speed = 0.000119 12:22:17.171 [planetinfo.record]: planet zpos = 760890.000000 12:22:17.171 [planetinfo.record]: sun_radius = 183036.642151 12:22:17.172 [planetinfo.record]: sun_vector = 0.589 -0.700 -0.403 12:22:17.172 [planetinfo.record]: sun_distance = 1170600 12:22:17.172 [planetinfo.record]: corona_flare = 0.054388 12:22:17.172 [planetinfo.record]: corona_hues = 0.960770 12:22:17.172 [planetinfo.record]: sun_color = 0.67644, 0.457968, 0.430354, 1 12:22:17.172 [planetinfo.record]: corona_shimmer = 0.432792 12:22:17.172 [planetinfo.record]: station_vector = -0.098 -0.784 0.614 12:22:17.172 [planetinfo.record]: station = coriolis 12:22:22.184 [PLANETINFO OVER]: Done 12:22:22.185 [PLANETINFO LOGGING]: 2 68 12:22:23.205 [planetinfo.record]: seed = 49 250 141 56 12:22:23.205 [planetinfo.record]: coordinates = 250 235 12:22:23.205 [planetinfo.record]: government = 6; 12:22:23.205 [planetinfo.record]: economy = 3; 12:22:23.205 [planetinfo.record]: techlevel = 9; 12:22:23.205 [planetinfo.record]: population = 46; 12:22:23.205 [planetinfo.record]: productivity = 25760; 12:22:23.205 [planetinfo.record]: name = "Edriat"; 12:22:23.205 [planetinfo.record]: inhabitant = "Red Bug-Eyed Frog"; 12:22:23.205 [planetinfo.record]: inhabitants = "Red Bug-Eyed Frogs"; 12:22:23.205 [planetinfo.record]: description = "Edriat is mildly well known for its hoopy casinos."; 12:22:23.216 [planetinfo.record]: air_color = 0.731487, 0.588574, 0.885182, 1; 12:22:23.216 [planetinfo.record]: cloud_alpha = 1.000000; 12:22:23.216 [planetinfo.record]: cloud_color = 0.658203, 0.390808, 0.534951, 1; 12:22:23.216 [planetinfo.record]: cloud_fraction = 0.330000; 12:22:23.216 [planetinfo.record]: land_color = 0.623141, 0.66, 0.582656, 1; 12:22:23.216 [planetinfo.record]: land_fraction = 0.260000; 12:22:23.216 [planetinfo.record]: polar_cloud_color = 0.796191, 0.594033, 0.703009, 1; 12:22:23.216 [planetinfo.record]: polar_land_color = 0.92096, 0.934, 0.906637, 1; 12:22:23.216 [planetinfo.record]: polar_sea_color = 0.733315, 0.932813, 0.918785, 1; 12:22:23.216 [planetinfo.record]: sea_color = 0.0971069, 0.671875, 0.631462, 1; 12:22:23.216 [planetinfo.record]: rotation_speed = 0.003501 12:22:23.216 [planetinfo.record]: planet zpos = 511400.000000 12:22:23.216 [planetinfo.record]: sun_radius = 103188.309326 12:22:23.216 [planetinfo.record]: sun_vector = -0.601 -0.789 -0.129 12:22:23.216 [planetinfo.record]: sun_distance = 1022800 12:22:23.216 [planetinfo.record]: corona_flare = 0.004579 12:22:23.216 [planetinfo.record]: corona_hues = 0.774963 12:22:23.216 [planetinfo.record]: sun_color = 0.787036, 0.787625, 0.732948, 1 12:22:23.216 [planetinfo.record]: corona_shimmer = 0.431857 12:22:23.216 [planetinfo.record]: station_vector = 0.335 0.920 0.204 12:22:23.217 [planetinfo.record]: station = coriolis 12:22:27.681 [PLANETINFO OVER]: Done 12:22:27.682 [PLANETINFO LOGGING]: 2 69 12:22:28.707 [planetinfo.record]: seed = 249 167 101 23 12:22:28.707 [planetinfo.record]: coordinates = 167 81 12:22:28.707 [planetinfo.record]: government = 7; 12:22:28.707 [planetinfo.record]: economy = 1; 12:22:28.707 [planetinfo.record]: techlevel = 13; 12:22:28.707 [planetinfo.record]: population = 61; 12:22:28.707 [planetinfo.record]: productivity = 48312; 12:22:28.707 [planetinfo.record]: name = "Tiera"; 12:22:28.707 [planetinfo.record]: inhabitant = "Human Colonial"; 12:22:28.707 [planetinfo.record]: inhabitants = "Human Colonials"; 12:22:28.707 [planetinfo.record]: description = "This planet is beset by lethal disease."; 12:22:28.716 [planetinfo.record]: air_color = 0.503378, 0.654252, 0.840955, 1; 12:22:28.716 [planetinfo.record]: cloud_alpha = 1.000000; 12:22:28.716 [planetinfo.record]: cloud_color = 0.207283, 0.525391, 0.421012, 1; 12:22:28.716 [planetinfo.record]: cloud_fraction = 0.460000; 12:22:28.716 [planetinfo.record]: land_color = 0.321854, 0.334331, 0.521484, 1; 12:22:28.716 [planetinfo.record]: land_fraction = 0.710000; 12:22:28.716 [planetinfo.record]: polar_cloud_color = 0.457749, 0.736426, 0.644985, 1; 12:22:28.716 [planetinfo.record]: polar_land_color = 0.857139, 0.862809, 0.947852, 1; 12:22:28.716 [planetinfo.record]: polar_sea_color = 0.852516, 0.916992, 0.868635, 1; 12:22:28.716 [planetinfo.record]: sea_color = 0.596619, 0.830078, 0.654984, 1; 12:22:28.716 [planetinfo.record]: rotation_speed = 0.001598 12:22:28.716 [planetinfo.record]: planet zpos = 668500.000000 12:22:28.716 [planetinfo.record]: sun_radius = 122410.377502 12:22:28.716 [planetinfo.record]: sun_vector = -0.202 0.979 0.025 12:22:28.716 [planetinfo.record]: sun_distance = 907250 12:22:28.716 [planetinfo.record]: corona_flare = 0.008752 12:22:28.716 [planetinfo.record]: corona_hues = 0.863235 12:22:28.716 [planetinfo.record]: sun_color = 0.822174, 0.733572, 0.716962, 1 12:22:28.716 [planetinfo.record]: corona_shimmer = 0.474150 12:22:28.716 [planetinfo.record]: station_vector = 0.338 0.941 0.037 12:22:28.717 [planetinfo.record]: station = dodecahedron 12:22:33.859 [PLANETINFO OVER]: Done 12:22:33.860 [PLANETINFO LOGGING]: 2 70 12:22:34.871 [planetinfo.record]: seed = 169 247 237 215 12:22:34.871 [planetinfo.record]: coordinates = 247 207 12:22:34.871 [planetinfo.record]: government = 5; 12:22:34.871 [planetinfo.record]: economy = 7; 12:22:34.871 [planetinfo.record]: techlevel = 6; 12:22:34.871 [planetinfo.record]: population = 37; 12:22:34.871 [planetinfo.record]: productivity = 7992; 12:22:34.871 [planetinfo.record]: name = "Tionrebi"; 12:22:34.871 [planetinfo.record]: inhabitant = "Slimy Lobster"; 12:22:34.871 [planetinfo.record]: inhabitants = "Slimy Lobsters"; 12:22:34.871 [planetinfo.record]: description = "This world is most notable for its exotic night life but ravaged by unpredictable solar activity."; 12:22:34.877 [planetinfo.record]: air_color = 0.498255, 0.622715, 0.858516, 1; 12:22:34.877 [planetinfo.record]: cloud_alpha = 1.000000; 12:22:34.877 [planetinfo.record]: cloud_color = 0.196472, 0.578125, 0.56918, 1; 12:22:34.877 [planetinfo.record]: cloud_fraction = 0.160000; 12:22:34.878 [planetinfo.record]: land_color = 0.66, 0.509482, 0.250078, 1; 12:22:34.878 [planetinfo.record]: land_fraction = 0.390000; 12:22:34.878 [planetinfo.record]: polar_cloud_color = 0.446518, 0.760156, 0.752805, 1; 12:22:34.878 [planetinfo.record]: polar_land_color = 0.934, 0.880749, 0.788975, 1; 12:22:34.878 [planetinfo.record]: polar_sea_color = 0.87729, 0.928906, 0.881726, 1; 12:22:34.878 [planetinfo.record]: sea_color = 0.552921, 0.710938, 0.5665, 1; 12:22:34.878 [planetinfo.record]: rotation_speed = 0.004511 12:22:34.878 [planetinfo.record]: planet zpos = 534050.000000 12:22:34.878 [planetinfo.record]: sun_radius = 130316.627502 12:22:34.878 [planetinfo.record]: sun_vector = 0.280 0.924 0.259 12:22:34.878 [planetinfo.record]: sun_distance = 1068100 12:22:34.879 [planetinfo.record]: corona_flare = 0.002092 12:22:34.879 [planetinfo.record]: corona_hues = 0.955383 12:22:34.879 [planetinfo.record]: sun_color = 0.818109, 0.754293, 0.725945, 1 12:22:34.879 [planetinfo.record]: corona_shimmer = 0.430710 12:22:34.879 [planetinfo.record]: station_vector = -0.203 0.969 0.142 12:22:34.879 [planetinfo.record]: station = coriolis 12:22:40.237 [PLANETINFO OVER]: Done 12:22:40.238 [PLANETINFO LOGGING]: 2 71 12:22:41.254 [planetinfo.record]: seed = 81 230 181 244 12:22:41.254 [planetinfo.record]: coordinates = 230 110 12:22:41.254 [planetinfo.record]: government = 2; 12:22:41.254 [planetinfo.record]: economy = 6; 12:22:41.254 [planetinfo.record]: techlevel = 4; 12:22:41.254 [planetinfo.record]: population = 25; 12:22:41.254 [planetinfo.record]: productivity = 4800; 12:22:41.254 [planetinfo.record]: name = "Raarcege"; 12:22:41.254 [planetinfo.record]: inhabitant = "Slimy Rodent"; 12:22:41.254 [planetinfo.record]: inhabitants = "Slimy Rodents"; 12:22:41.254 [planetinfo.record]: description = "This world is reasonably notable for its weird tropical forests but scourged by deadly civil war."; 12:22:41.259 [planetinfo.record]: air_color = 0.86289, 0.772561, 0.987943, 1; 12:22:41.259 [planetinfo.record]: cloud_alpha = 1.000000; 12:22:41.260 [planetinfo.record]: cloud_color = 0.966797, 0.913925, 0.95234, 1; 12:22:41.260 [planetinfo.record]: cloud_fraction = 0.440000; 12:22:41.260 [planetinfo.record]: land_color = 0.66, 0.0592969, 0.284561, 1; 12:22:41.260 [planetinfo.record]: land_fraction = 0.510000; 12:22:41.260 [planetinfo.record]: polar_cloud_color = 0.935059, 0.903099, 0.92632, 1; 12:22:41.260 [planetinfo.record]: polar_land_color = 0.934, 0.721479, 0.801174, 1; 12:22:41.260 [planetinfo.record]: polar_sea_color = 0.940625, 0.933976, 0.890562, 1; 12:22:41.260 [planetinfo.record]: sea_color = 0.59375, 0.576962, 0.467346, 1; 12:22:41.260 [planetinfo.record]: rotation_speed = 0.004522 12:22:41.260 [planetinfo.record]: planet zpos = 529100.000000 12:22:41.260 [planetinfo.record]: sun_radius = 92284.219360 12:22:41.260 [planetinfo.record]: sun_vector = -0.616 0.786 0.052 12:22:41.260 [planetinfo.record]: sun_distance = 936100 12:22:41.260 [planetinfo.record]: corona_flare = 0.013303 12:22:41.260 [planetinfo.record]: corona_hues = 0.594841 12:22:41.260 [planetinfo.record]: sun_color = 0.670436, 0.730931, 0.749301, 1 12:22:41.260 [planetinfo.record]: corona_shimmer = 0.349227 12:22:41.261 [planetinfo.record]: station_vector = -0.060 0.002 0.998 12:22:41.261 [planetinfo.record]: station = coriolis 12:22:46.287 [PLANETINFO OVER]: Done 12:22:46.287 [PLANETINFO LOGGING]: 2 72 12:22:47.302 [planetinfo.record]: seed = 193 99 205 210 12:22:47.302 [planetinfo.record]: coordinates = 99 37 12:22:47.302 [planetinfo.record]: government = 0; 12:22:47.302 [planetinfo.record]: economy = 7; 12:22:47.302 [planetinfo.record]: techlevel = 3; 12:22:47.302 [planetinfo.record]: population = 20; 12:22:47.302 [planetinfo.record]: productivity = 1920; 12:22:47.302 [planetinfo.record]: name = "Enanen"; 12:22:47.302 [planetinfo.record]: inhabitant = "Rodent"; 12:22:47.302 [planetinfo.record]: inhabitants = "Rodents"; 12:22:47.302 [planetinfo.record]: description = "The planet Enanen is well known for the Enanenian tree grub but ravaged by occasional solar activity."; 12:22:47.320 [planetinfo.record]: air_color = 0.713535, 0.608812, 0.858516, 1; 12:22:47.320 [planetinfo.record]: cloud_alpha = 1.000000; 12:22:47.320 [planetinfo.record]: cloud_color = 0.578125, 0.420044, 0.5349, 1; 12:22:47.320 [planetinfo.record]: cloud_fraction = 0.400000; 12:22:47.320 [planetinfo.record]: land_color = 0.464063, 0.494678, 0.66, 1; 12:22:47.320 [planetinfo.record]: land_fraction = 0.300000; 12:22:47.320 [planetinfo.record]: polar_cloud_color = 0.760156, 0.630247, 0.724634, 1; 12:22:47.320 [planetinfo.record]: polar_land_color = 0.86468, 0.875511, 0.934, 1; 12:22:47.320 [planetinfo.record]: polar_sea_color = 0.931445, 0.874737, 0.855038, 1; 12:22:47.320 [planetinfo.record]: sea_color = 0.685547, 0.518595, 0.460602, 1; 12:22:47.320 [planetinfo.record]: rotation_speed = 0.001674 12:22:47.320 [planetinfo.record]: planet zpos = 411240.000000 12:22:47.320 [planetinfo.record]: sun_radius = 66374.070740 12:22:47.320 [planetinfo.record]: sun_vector = 0.104 -0.734 -0.671 12:22:47.320 [planetinfo.record]: sun_distance = 753940 12:22:47.320 [planetinfo.record]: corona_flare = 0.016985 12:22:47.320 [planetinfo.record]: corona_hues = 0.584572 12:22:47.320 [planetinfo.record]: sun_color = 0.566801, 0.649125, 0.764551, 1 12:22:47.320 [planetinfo.record]: corona_shimmer = 0.321419 12:22:47.320 [planetinfo.record]: station_vector = -0.130 0.987 0.095 12:22:47.320 [planetinfo.record]: station = coriolis 12:22:51.573 [PLANETINFO OVER]: Done 12:22:51.574 [PLANETINFO LOGGING]: 2 73 12:22:52.587 [planetinfo.record]: seed = 137 192 69 174 12:22:52.587 [planetinfo.record]: coordinates = 192 146 12:22:52.587 [planetinfo.record]: government = 1; 12:22:52.587 [planetinfo.record]: economy = 2; 12:22:52.587 [planetinfo.record]: techlevel = 6; 12:22:52.587 [planetinfo.record]: population = 28; 12:22:52.587 [planetinfo.record]: productivity = 8960; 12:22:52.588 [planetinfo.record]: name = "Rea"; 12:22:52.588 [planetinfo.record]: inhabitant = "Human Colonial"; 12:22:52.588 [planetinfo.record]: inhabitants = "Human Colonials"; 12:22:52.588 [planetinfo.record]: description = "The world Rea is reasonably famous for its inhabitants’ exceptional loathing of casinos."; 12:22:52.595 [planetinfo.record]: air_color = 0.541568, 0.564204, 0.854613, 1; 12:22:52.595 [planetinfo.record]: cloud_alpha = 1.000000; 12:22:52.595 [planetinfo.record]: cloud_color = 0.283203, 0.340729, 0.566406, 1; 12:22:52.595 [planetinfo.record]: cloud_fraction = 0.200000; 12:22:52.595 [planetinfo.record]: land_color = 0.626484, 0.66, 0.647432, 1; 12:22:52.595 [planetinfo.record]: land_fraction = 0.380000; 12:22:52.595 [planetinfo.record]: polar_cloud_color = 0.518982, 0.566899, 0.754883, 1; 12:22:52.595 [planetinfo.record]: polar_land_color = 0.922143, 0.934, 0.929554, 1; 12:22:52.595 [planetinfo.record]: polar_sea_color = 0.90332, 0.760026, 0.703955, 1; 12:22:52.595 [planetinfo.record]: sea_color = 0.966797, 0.353343, 0.113297, 1; 12:22:52.596 [planetinfo.record]: rotation_speed = 0.003546 12:22:52.596 [planetinfo.record]: planet zpos = 922880.000000 12:22:52.596 [planetinfo.record]: sun_radius = 156556.982422 12:22:52.596 [planetinfo.record]: sun_vector = 0.947 0.320 0.010 12:22:52.596 [planetinfo.record]: sun_distance = 1186560 12:22:52.596 [planetinfo.record]: corona_flare = 0.063245 12:22:52.596 [planetinfo.record]: corona_hues = 0.845924 12:22:52.596 [planetinfo.record]: sun_color = 0.750931, 0.691018, 0.627302, 1 12:22:52.596 [planetinfo.record]: corona_shimmer = 0.328307 12:22:52.596 [planetinfo.record]: station_vector = 0.518 -0.842 0.150 12:22:52.596 [planetinfo.record]: station = coriolis 12:22:56.967 [PLANETINFO OVER]: Done 12:22:56.968 [PLANETINFO LOGGING]: 2 74 12:22:57.986 [planetinfo.record]: seed = 249 30 173 143 12:22:57.986 [planetinfo.record]: coordinates = 30 111 12:22:57.986 [planetinfo.record]: government = 7; 12:22:57.986 [planetinfo.record]: economy = 7; 12:22:57.986 [planetinfo.record]: techlevel = 6; 12:22:57.986 [planetinfo.record]: population = 39; 12:22:57.987 [planetinfo.record]: productivity = 10296; 12:22:57.987 [planetinfo.record]: name = "Aridian"; 12:22:57.987 [planetinfo.record]: inhabitant = "Black Bug-Eyed Bird"; 12:22:57.987 [planetinfo.record]: inhabitants = "Black Bug-Eyed Birds"; 12:22:57.987 [planetinfo.record]: description = "This planet is a dull world."; 12:22:58.020 [planetinfo.record]: air_color = 0.687259, 0.863068, 0.862881, 1; 12:22:58.020 [planetinfo.record]: cloud_alpha = 1.000000; 12:22:58.020 [planetinfo.record]: cloud_color = 0.591743, 0.591797, 0.584862, 1; 12:22:58.020 [planetinfo.record]: cloud_fraction = 0.480000; 12:22:58.020 [planetinfo.record]: land_color = 0.66, 0.415078, 0.478222, 1; 12:22:58.020 [planetinfo.record]: land_fraction = 0.540000; 12:22:58.020 [planetinfo.record]: polar_cloud_color = 0.766265, 0.766309, 0.760696, 1; 12:22:58.020 [planetinfo.record]: polar_land_color = 0.934, 0.84735, 0.869689, 1; 12:22:58.020 [planetinfo.record]: polar_sea_color = 0.92207, 0.847332, 0.806812, 1; 12:22:58.020 [planetinfo.record]: sea_color = 0.779297, 0.526634, 0.389648, 1; 12:22:58.020 [planetinfo.record]: rotation_speed = 0.000069 12:22:58.020 [planetinfo.record]: planet zpos = 869180.000000 12:22:58.020 [planetinfo.record]: sun_radius = 204669.992371 12:22:58.021 [planetinfo.record]: sun_vector = 0.944 0.016 -0.328 12:22:58.021 [planetinfo.record]: sun_distance = 1069760 12:22:58.021 [planetinfo.record]: corona_flare = 0.021671 12:22:58.021 [planetinfo.record]: corona_hues = 0.720947 12:22:58.021 [planetinfo.record]: sun_color = 0.682269, 0.638662, 0.573895, 1 12:22:58.021 [planetinfo.record]: corona_shimmer = 0.683293 12:22:58.021 [planetinfo.record]: station_vector = 0.136 -0.462 0.876 12:22:58.021 [planetinfo.record]: station = coriolis 12:23:03.008 [PLANETINFO OVER]: Done 12:23:03.009 [PLANETINFO LOGGING]: 2 75 12:23:04.016 [planetinfo.record]: seed = 33 123 149 102 12:23:04.016 [planetinfo.record]: coordinates = 123 205 12:23:04.016 [planetinfo.record]: government = 4; 12:23:04.016 [planetinfo.record]: economy = 5; 12:23:04.016 [planetinfo.record]: techlevel = 7; 12:23:04.016 [planetinfo.record]: population = 38; 12:23:04.016 [planetinfo.record]: productivity = 12160; 12:23:04.016 [planetinfo.record]: name = "Bireer"; 12:23:04.016 [planetinfo.record]: inhabitant = "Fierce Blue Rodent"; 12:23:04.016 [planetinfo.record]: inhabitants = "Fierce Blue Rodents"; 12:23:04.016 [planetinfo.record]: description = "This world is mildly famous for its hoopy night life and vicious Le brew."; 12:23:04.029 [planetinfo.record]: air_color = 0.493902, 0.491511, 0.907945, 1; 12:23:04.029 [planetinfo.record]: cloud_alpha = 1.000000; 12:23:04.029 [planetinfo.record]: cloud_color = 0.179423, 0.161774, 0.726562, 1; 12:23:04.029 [planetinfo.record]: cloud_fraction = 0.470000; 12:23:04.029 [planetinfo.record]: land_color = 0.446016, 0.66, 0.454374, 1; 12:23:04.029 [planetinfo.record]: land_fraction = 0.570000; 12:23:04.029 [planetinfo.record]: polar_cloud_color = 0.437742, 0.425186, 0.826953, 1; 12:23:04.029 [planetinfo.record]: polar_land_color = 0.858295, 0.934, 0.861252, 1; 12:23:04.030 [planetinfo.record]: polar_sea_color = 0.885625, 0.932813, 0.886363, 1; 12:23:04.030 [planetinfo.record]: sea_color = 0.535925, 0.671875, 0.53805, 1; 12:23:04.030 [planetinfo.record]: rotation_speed = 0.003815 12:23:04.030 [planetinfo.record]: planet zpos = 581750.000000 12:23:04.030 [planetinfo.record]: sun_radius = 95138.134003 12:23:04.030 [planetinfo.record]: sun_vector = -0.644 0.746 -0.170 12:23:04.030 [planetinfo.record]: sun_distance = 805500 12:23:04.030 [planetinfo.record]: corona_flare = 0.038571 12:23:04.030 [planetinfo.record]: corona_hues = 0.941353 12:23:04.030 [planetinfo.record]: sun_color = 0.828146, 0.785713, 0.7151, 1 12:23:04.030 [planetinfo.record]: corona_shimmer = 0.468235 12:23:04.031 [planetinfo.record]: station_vector = 0.943 0.330 0.046 12:23:04.031 [planetinfo.record]: station = coriolis 12:23:09.548 [PLANETINFO OVER]: Done 12:23:09.549 [PLANETINFO LOGGING]: 2 76 12:23:10.570 [planetinfo.record]: seed = 209 165 13 229 12:23:10.570 [planetinfo.record]: coordinates = 165 144 12:23:10.570 [planetinfo.record]: government = 2; 12:23:10.570 [planetinfo.record]: economy = 0; 12:23:10.570 [planetinfo.record]: techlevel = 9; 12:23:10.570 [planetinfo.record]: population = 39; 12:23:10.570 [planetinfo.record]: productivity = 18720; 12:23:10.570 [planetinfo.record]: name = "Ceanbibi"; 12:23:10.570 [planetinfo.record]: inhabitant = "Human Colonial"; 12:23:10.570 [planetinfo.record]: inhabitants = "Human Colonials"; 12:23:10.570 [planetinfo.record]: description = "The world Ceanbibi is reasonably noted for its exotic goat soup."; 12:23:10.581 [planetinfo.record]: air_color = 0.503733, 0.480584, 0.920953, 1; 12:23:10.581 [planetinfo.record]: cloud_alpha = 1.000000; 12:23:10.581 [planetinfo.record]: cloud_color = 0.235613, 0.12561, 0.765625, 1; 12:23:10.581 [planetinfo.record]: cloud_fraction = 0.290000; 12:23:10.581 [planetinfo.record]: land_color = 0.274225, 0.241684, 0.658203, 1; 12:23:10.581 [planetinfo.record]: land_fraction = 0.660000; 12:23:10.581 [planetinfo.record]: polar_cloud_color = 0.479134, 0.403297, 0.844531, 1; 12:23:10.581 [planetinfo.record]: polar_land_color = 0.797936, 0.78639, 0.93418, 1; 12:23:10.581 [planetinfo.record]: polar_sea_color = 0.918993, 0.927734, 0.868845, 1; 12:23:10.581 [planetinfo.record]: sea_color = 0.69542, 0.722656, 0.539169, 1; 12:23:10.581 [planetinfo.record]: rotation_speed = 0.004817 12:23:10.581 [planetinfo.record]: planet zpos = 468710.000000 12:23:10.582 [planetinfo.record]: sun_radius = 98875.667572 12:23:10.582 [planetinfo.record]: sun_vector = 0.184 0.584 0.791 12:23:10.582 [planetinfo.record]: sun_distance = 894810 12:23:10.582 [planetinfo.record]: corona_flare = 0.008299 12:23:10.582 [planetinfo.record]: corona_hues = 0.910057 12:23:10.582 [planetinfo.record]: sun_color = 0.386049, 0.534956, 0.698743, 1 12:23:10.582 [planetinfo.record]: corona_shimmer = 0.356433 12:23:10.582 [planetinfo.record]: station_vector = 0.243 0.690 0.682 12:23:10.582 [planetinfo.record]: station = coriolis 12:23:15.920 [PLANETINFO OVER]: Done 12:23:15.920 [PLANETINFO LOGGING]: 2 77 12:23:16.932 [planetinfo.record]: seed = 153 166 37 104 12:23:16.932 [planetinfo.record]: coordinates = 166 166 12:23:16.932 [planetinfo.record]: government = 3; 12:23:16.932 [planetinfo.record]: economy = 6; 12:23:16.932 [planetinfo.record]: techlevel = 5; 12:23:16.932 [planetinfo.record]: population = 30; 12:23:16.932 [planetinfo.record]: productivity = 6720; 12:23:16.932 [planetinfo.record]: name = "Usrage"; 12:23:16.932 [planetinfo.record]: inhabitant = "Human Colonial"; 12:23:16.932 [planetinfo.record]: inhabitants = "Human Colonials"; 12:23:16.932 [planetinfo.record]: description = "Usrage is reasonably well known for the Usrageian tree ant but ravaged by vicious vicious wolfs."; 12:23:16.947 [planetinfo.record]: air_color = 0.465074, 0.731252, 0.842906, 1; 12:23:16.948 [planetinfo.record]: cloud_alpha = 1.000000; 12:23:16.948 [planetinfo.record]: cloud_color = 0.175143, 0.53125, 0.134888, 1; 12:23:16.948 [planetinfo.record]: cloud_fraction = 0.400000; 12:23:16.948 [planetinfo.record]: land_color = 0.66, 0.0979688, 0.348248, 1; 12:23:16.948 [planetinfo.record]: land_fraction = 0.380000; 12:23:16.949 [planetinfo.record]: polar_cloud_color = 0.429433, 0.739062, 0.394431, 1; 12:23:16.949 [planetinfo.record]: polar_land_color = 0.934, 0.73516, 0.823706, 1; 12:23:16.949 [planetinfo.record]: polar_sea_color = 0.946289, 0.911086, 0.897311, 1; 12:23:16.949 [planetinfo.record]: sea_color = 0.537109, 0.457186, 0.425911, 1; 12:23:16.949 [planetinfo.record]: rotation_speed = 0.000385 12:23:16.949 [planetinfo.record]: planet zpos = 603600.000000 12:23:16.949 [planetinfo.record]: sun_radius = 94565.013123 12:23:16.949 [planetinfo.record]: sun_vector = -0.527 -0.464 -0.712 12:23:16.949 [planetinfo.record]: sun_distance = 955700 12:23:16.949 [planetinfo.record]: corona_flare = 0.092245 12:23:16.949 [planetinfo.record]: corona_hues = 0.800514 12:23:16.949 [planetinfo.record]: sun_color = 0.78816, 0.788738, 0.794434, 1 12:23:16.949 [planetinfo.record]: corona_shimmer = 0.349689 12:23:16.949 [planetinfo.record]: station_vector = -0.817 0.211 0.536 12:23:16.949 [planetinfo.record]: station = coriolis 12:23:21.710 [PLANETINFO OVER]: Done 12:23:21.711 [PLANETINFO LOGGING]: 2 78 12:23:22.729 [planetinfo.record]: seed = 201 224 109 89 12:23:22.729 [planetinfo.record]: coordinates = 224 195 12:23:22.729 [planetinfo.record]: government = 1; 12:23:22.729 [planetinfo.record]: economy = 3; 12:23:22.729 [planetinfo.record]: techlevel = 5; 12:23:22.729 [planetinfo.record]: population = 25; 12:23:22.729 [planetinfo.record]: productivity = 7000; 12:23:22.729 [planetinfo.record]: name = "Orised"; 12:23:22.729 [planetinfo.record]: inhabitant = "Human Colonial"; 12:23:22.729 [planetinfo.record]: inhabitants = "Human Colonials"; 12:23:22.729 [planetinfo.record]: description = "The planet Orised is mildly well known for killer Geceouso juice."; 12:23:22.751 [planetinfo.record]: air_color = 0.588248, 0.881557, 0.98274, 1; 12:23:22.751 [planetinfo.record]: cloud_alpha = 1.000000; 12:23:22.751 [planetinfo.record]: cloud_color = 0.501246, 0.951172, 0.375267, 1; 12:23:22.751 [planetinfo.record]: cloud_fraction = 0.470000; 12:23:22.751 [planetinfo.record]: land_color = 0.0959873, 0.644531, 0.0176239, 1; 12:23:22.751 [planetinfo.record]: land_fraction = 0.340000; 12:23:22.751 [planetinfo.record]: polar_cloud_color = 0.653666, 0.928027, 0.576845, 1; 12:23:22.751 [planetinfo.record]: polar_land_color = 0.736492, 0.935547, 0.708055, 1; 12:23:22.751 [planetinfo.record]: polar_sea_color = 0.878543, 0.92793, 0.897835, 1; 12:23:22.751 [planetinfo.record]: sea_color = 0.567272, 0.720703, 0.627206, 1; 12:23:22.751 [planetinfo.record]: rotation_speed = 0.000699 12:23:22.751 [planetinfo.record]: planet zpos = 694720.000000 12:23:22.751 [planetinfo.record]: sun_radius = 142528.139648 12:23:22.751 [planetinfo.record]: sun_vector = -0.797 -0.407 0.447 12:23:22.751 [planetinfo.record]: sun_distance = 961920 12:23:22.752 [planetinfo.record]: corona_flare = 0.069786 12:23:22.752 [planetinfo.record]: corona_hues = 0.991493 12:23:22.752 [planetinfo.record]: sun_color = 0.723077, 0.723747, 0.723889, 1 12:23:22.752 [planetinfo.record]: corona_shimmer = 0.297858 12:23:22.752 [planetinfo.record]: station_vector = -0.765 -0.599 0.238 12:23:22.752 [planetinfo.record]: station = coriolis 12:23:27.475 [PLANETINFO OVER]: Done 12:23:27.475 [PLANETINFO LOGGING]: 2 79 12:23:28.483 [planetinfo.record]: seed = 113 143 117 197 12:23:28.483 [planetinfo.record]: coordinates = 143 56 12:23:28.483 [planetinfo.record]: government = 6; 12:23:28.483 [planetinfo.record]: economy = 0; 12:23:28.483 [planetinfo.record]: techlevel = 13; 12:23:28.483 [planetinfo.record]: population = 59; 12:23:28.483 [planetinfo.record]: productivity = 47200; 12:23:28.483 [planetinfo.record]: name = "Cedile"; 12:23:28.483 [planetinfo.record]: inhabitant = "Human Colonial"; 12:23:28.483 [planetinfo.record]: inhabitants = "Human Colonials"; 12:23:28.483 [planetinfo.record]: description = "The planet Cedile is a boring planet."; 12:23:28.485 [planetinfo.record]: air_color = 0.632275, 0.895469, 0.93266, 1; 12:23:28.486 [planetinfo.record]: cloud_alpha = 1.000000; 12:23:28.486 [planetinfo.record]: cloud_color = 0.68822, 0.800781, 0.506744, 1; 12:23:28.486 [planetinfo.record]: cloud_fraction = 0.610000; 12:23:28.486 [planetinfo.record]: land_color = 0.24128, 0.495508, 0.537109, 1; 12:23:28.486 [planetinfo.record]: land_fraction = 0.610000; 12:23:28.486 [planetinfo.record]: polar_cloud_color = 0.784768, 0.860352, 0.662908, 1; 12:23:28.486 [planetinfo.record]: polar_land_color = 0.815989, 0.927966, 0.946289, 1; 12:23:28.486 [planetinfo.record]: polar_sea_color = 0.931055, 0.862649, 0.849224, 1; 12:23:28.486 [planetinfo.record]: sea_color = 0.689453, 0.486834, 0.447067, 1; 12:23:28.486 [planetinfo.record]: rotation_speed = 0.003237 12:23:28.486 [planetinfo.record]: planet zpos = 423900.000000 12:23:28.486 [planetinfo.record]: sun_radius = 136984.200897 12:23:28.486 [planetinfo.record]: sun_vector = -0.928 -0.219 0.302 12:23:28.486 [planetinfo.record]: sun_distance = 890190 12:23:28.486 [planetinfo.record]: corona_flare = 0.000926 12:23:28.486 [planetinfo.record]: corona_hues = 0.712921 12:23:28.486 [planetinfo.record]: sun_color = 0.652301, 0.323344, 0.165763, 1 12:23:28.486 [planetinfo.record]: corona_shimmer = 0.313485 12:23:28.487 [planetinfo.record]: station_vector = 0.004 -0.993 0.116 12:23:28.487 [planetinfo.record]: station = dodecahedron 12:23:32.498 [PLANETINFO OVER]: Done 12:23:32.499 [PLANETINFO LOGGING]: 2 80 12:23:33.508 [planetinfo.record]: seed = 97 52 77 163 12:23:33.509 [planetinfo.record]: coordinates = 52 225 12:23:33.509 [planetinfo.record]: government = 4; 12:23:33.509 [planetinfo.record]: economy = 1; 12:23:33.509 [planetinfo.record]: techlevel = 8; 12:23:33.509 [planetinfo.record]: population = 38; 12:23:33.509 [planetinfo.record]: productivity = 21888; 12:23:33.509 [planetinfo.record]: name = "Georatre"; 12:23:33.509 [planetinfo.record]: inhabitant = "Human Colonial"; 12:23:33.509 [planetinfo.record]: inhabitants = "Human Colonials"; 12:23:33.509 [planetinfo.record]: description = "This world is noted for its unusual sit coms."; 12:23:33.528 [planetinfo.record]: air_color = 0.733761, 0.511388, 0.951522, 1; 12:23:33.528 [planetinfo.record]: cloud_alpha = 1.000000; 12:23:33.528 [planetinfo.record]: cloud_color = 0.857422, 0.180862, 0.498, 1; 12:23:33.528 [planetinfo.record]: cloud_fraction = 0.560000; 12:23:33.528 [planetinfo.record]: land_color = 0.66, 0.227761, 0.183047, 1; 12:23:33.528 [planetinfo.record]: land_fraction = 0.480000; 12:23:33.528 [planetinfo.record]: polar_cloud_color = 0.88584, 0.448975, 0.653756, 1; 12:23:33.528 [planetinfo.record]: polar_land_color = 0.934, 0.781079, 0.76526, 1; 12:23:33.528 [planetinfo.record]: polar_sea_color = 0.931395, 0.933203, 0.875334, 1; 12:23:33.528 [planetinfo.record]: sea_color = 0.662791, 0.667969, 0.502281, 1; 12:23:33.528 [planetinfo.record]: rotation_speed = 0.002894 12:23:33.529 [planetinfo.record]: planet zpos = 363600.000000 12:23:33.529 [planetinfo.record]: sun_radius = 86421.577148 12:23:33.529 [planetinfo.record]: sun_vector = 0.579 0.387 -0.718 12:23:33.529 [planetinfo.record]: sun_distance = 727200 12:23:33.529 [planetinfo.record]: corona_flare = 0.047000 12:23:33.529 [planetinfo.record]: corona_hues = 0.753204 12:23:33.529 [planetinfo.record]: sun_color = 0.685867, 0.581097, 0.50511, 1 12:23:33.529 [planetinfo.record]: corona_shimmer = 0.903546 12:23:33.530 [planetinfo.record]: station_vector = 0.817 -0.073 0.573 12:23:33.530 [planetinfo.record]: station = coriolis 12:23:38.740 [PLANETINFO OVER]: Done 12:23:38.741 [PLANETINFO LOGGING]: 2 81 12:23:39.750 [planetinfo.record]: seed = 41 238 5 57 12:23:39.750 [planetinfo.record]: coordinates = 238 145 12:23:39.750 [planetinfo.record]: government = 5; 12:23:39.750 [planetinfo.record]: economy = 1; 12:23:39.750 [planetinfo.record]: techlevel = 11; 12:23:39.750 [planetinfo.record]: population = 51; 12:23:39.750 [planetinfo.record]: productivity = 33048; 12:23:39.750 [planetinfo.record]: name = "Oredonat"; 12:23:39.750 [planetinfo.record]: inhabitant = "Human Colonial"; 12:23:39.750 [planetinfo.record]: inhabitants = "Human Colonials"; 12:23:39.750 [planetinfo.record]: description = "Oredonat is mildly notable for its inhabitants’ unusual silliness."; 12:23:39.753 [planetinfo.record]: air_color = 0.741838, 0.733092, 0.969732, 1; 12:23:39.753 [planetinfo.record]: cloud_alpha = 1.000000; 12:23:39.753 [planetinfo.record]: cloud_color = 0.806112, 0.79097, 0.912109, 1; 12:23:39.753 [planetinfo.record]: cloud_fraction = 0.120000; 12:23:39.753 [planetinfo.record]: land_color = 0.592365, 0.541406, 0.66, 1; 12:23:39.753 [planetinfo.record]: land_fraction = 0.630000; 12:23:39.753 [planetinfo.record]: polar_cloud_color = 0.844322, 0.834875, 0.910449, 1; 12:23:39.753 [planetinfo.record]: polar_land_color = 0.910071, 0.892043, 0.934, 1; 12:23:39.753 [planetinfo.record]: polar_sea_color = 0.943945, 0.92383, 0.895365, 1; 12:23:39.753 [planetinfo.record]: sea_color = 0.560547, 0.512767, 0.445153, 1; 12:23:39.754 [planetinfo.record]: rotation_speed = 0.002249 12:23:39.754 [planetinfo.record]: planet zpos = 482220.000000 12:23:39.754 [planetinfo.record]: sun_radius = 96817.791138 12:23:39.754 [planetinfo.record]: sun_vector = 0.118 0.929 0.351 12:23:39.754 [planetinfo.record]: sun_distance = 1232340 12:23:39.754 [planetinfo.record]: corona_flare = 0.022276 12:23:39.754 [planetinfo.record]: corona_hues = 0.715477 12:23:39.754 [planetinfo.record]: sun_color = 0.728943, 0.665047, 0.604062, 1 12:23:39.754 [planetinfo.record]: corona_shimmer = 0.388753 12:23:39.754 [planetinfo.record]: station_vector = 0.430 0.150 0.890 12:23:39.754 [planetinfo.record]: station = dodecahedron 12:23:44.416 [PLANETINFO OVER]: Done 12:23:44.416 [PLANETINFO LOGGING]: 2 82 12:23:45.421 [planetinfo.record]: seed = 25 209 45 105 12:23:45.421 [planetinfo.record]: coordinates = 209 223 12:23:45.421 [planetinfo.record]: government = 3; 12:23:45.421 [planetinfo.record]: economy = 7; 12:23:45.421 [planetinfo.record]: techlevel = 3; 12:23:45.421 [planetinfo.record]: population = 23; 12:23:45.421 [planetinfo.record]: productivity = 3864; 12:23:45.421 [planetinfo.record]: name = "Esorra"; 12:23:45.421 [planetinfo.record]: inhabitant = "Human Colonial"; 12:23:45.421 [planetinfo.record]: inhabitants = "Human Colonials"; 12:23:45.421 [planetinfo.record]: description = "The planet Esorra is mildly notable for its inhabitants’ unusual mating traditions."; 12:23:45.435 [planetinfo.record]: air_color = 0.411031, 0.842256, 0.798044, 1; 12:23:45.435 [planetinfo.record]: cloud_alpha = 1.000000; 12:23:45.435 [planetinfo.record]: cloud_color = 0.529297, 0.381983, 0.0330811, 1; 12:23:45.435 [planetinfo.record]: cloud_fraction = 0.080000; 12:23:45.436 [planetinfo.record]: land_color = 0.655851, 0.128906, 0.66, 1; 12:23:45.436 [planetinfo.record]: land_fraction = 0.340000; 12:23:45.436 [planetinfo.record]: polar_cloud_color = 0.738184, 0.609776, 0.305654, 1; 12:23:45.436 [planetinfo.record]: polar_land_color = 0.932532, 0.746105, 0.934, 1; 12:23:45.436 [planetinfo.record]: polar_sea_color = 0.942773, 0.900973, 0.888177, 1; 12:23:45.436 [planetinfo.record]: sea_color = 0.572266, 0.470774, 0.439706, 1; 12:23:45.436 [planetinfo.record]: rotation_speed = 0.001330 12:23:45.436 [planetinfo.record]: planet zpos = 692770.000000 12:23:45.436 [planetinfo.record]: sun_radius = 98667.326202 12:23:45.436 [planetinfo.record]: sun_vector = 0.031 0.969 0.247 12:23:45.437 [planetinfo.record]: sun_distance = 905930 12:23:45.437 [planetinfo.record]: corona_flare = 0.064037 12:23:45.437 [planetinfo.record]: corona_hues = 0.822945 12:23:45.437 [planetinfo.record]: sun_color = 0.587145, 0.669645, 0.678812, 1 12:23:45.437 [planetinfo.record]: corona_shimmer = 0.371570 12:23:45.437 [planetinfo.record]: station_vector = -0.952 0.307 0.018 12:23:45.438 [planetinfo.record]: station = coriolis 12:23:50.266 [PLANETINFO OVER]: Done 12:23:50.267 [PLANETINFO LOGGING]: 2 83 12:23:51.278 [planetinfo.record]: seed = 65 215 85 69 12:23:51.278 [planetinfo.record]: coordinates = 215 84 12:23:51.278 [planetinfo.record]: government = 0; 12:23:51.278 [planetinfo.record]: economy = 6; 12:23:51.278 [planetinfo.record]: techlevel = 4; 12:23:51.278 [planetinfo.record]: population = 23; 12:23:51.278 [planetinfo.record]: productivity = 2944; 12:23:51.278 [planetinfo.record]: name = "Ceerdi"; 12:23:51.278 [planetinfo.record]: inhabitant = "Human Colonial"; 12:23:51.278 [planetinfo.record]: inhabitants = "Human Colonials"; 12:23:51.278 [planetinfo.record]: description = "Ceerdi is an unremarkable planet."; 12:23:51.285 [planetinfo.record]: air_color = 0.577076, 0.475604, 0.944367, 1; 12:23:51.286 [planetinfo.record]: cloud_alpha = 1.000000; 12:23:51.286 [planetinfo.record]: cloud_color = 0.58583, 0.0914307, 0.835938, 1; 12:23:51.286 [planetinfo.record]: cloud_fraction = 0.290000; 12:23:51.286 [planetinfo.record]: land_color = 0.45375, 0.66, 0.543984, 1; 12:23:51.286 [planetinfo.record]: land_fraction = 0.520000; 12:23:51.286 [planetinfo.record]: polar_cloud_color = 0.712331, 0.388459, 0.876172, 1; 12:23:51.286 [planetinfo.record]: polar_land_color = 0.861031, 0.934, 0.892955, 1; 12:23:51.286 [planetinfo.record]: polar_sea_color = 0.920678, 0.934766, 0.881729, 1; 12:23:51.286 [planetinfo.record]: sea_color = 0.613018, 0.652344, 0.504292, 1; 12:23:51.287 [planetinfo.record]: rotation_speed = 0.002564 12:23:51.287 [planetinfo.record]: planet zpos = 603540.000000 12:23:51.287 [planetinfo.record]: sun_radius = 127942.028503 12:23:51.287 [planetinfo.record]: sun_vector = -0.127 -0.992 0.001 12:23:51.287 [planetinfo.record]: sun_distance = 948420 12:23:51.287 [planetinfo.record]: corona_flare = 0.015320 12:23:51.287 [planetinfo.record]: corona_hues = 0.611855 12:23:51.287 [planetinfo.record]: sun_color = 0.578621, 0.660113, 0.66105, 1 12:23:51.287 [planetinfo.record]: corona_shimmer = 0.352140 12:23:51.288 [planetinfo.record]: station_vector = -0.000 -0.080 0.997 12:23:51.288 [planetinfo.record]: station = coriolis 12:23:56.277 [PLANETINFO OVER]: Done 12:23:56.277 [PLANETINFO LOGGING]: 2 84 12:23:57.289 [planetinfo.record]: seed = 113 67 141 65 12:23:57.289 [planetinfo.record]: coordinates = 67 141 12:23:57.289 [planetinfo.record]: government = 6; 12:23:57.289 [planetinfo.record]: economy = 5; 12:23:57.289 [planetinfo.record]: techlevel = 8; 12:23:57.290 [planetinfo.record]: population = 44; 12:23:57.290 [planetinfo.record]: productivity = 17600; 12:23:57.290 [planetinfo.record]: name = "Leentima"; 12:23:57.290 [planetinfo.record]: inhabitant = "Large Yellow Insect"; 12:23:57.290 [planetinfo.record]: inhabitants = "Large Yellow Insects"; 12:23:57.290 [planetinfo.record]: description = "The world Leentima is mildly fabled for its inhabitants’ eccentric love for poetry but plagued by frequent earthquakes."; 12:23:57.300 [planetinfo.record]: air_color = 0.798946, 0.755039, 0.973635, 1; 12:23:57.300 [planetinfo.record]: cloud_alpha = 1.000000; 12:23:57.300 [planetinfo.record]: cloud_color = 0.89758, 0.855263, 0.923828, 1; 12:23:57.300 [planetinfo.record]: cloud_fraction = 0.550000; 12:23:57.300 [planetinfo.record]: land_color = 0.66, 0.622617, 0.510469, 1; 12:23:57.300 [planetinfo.record]: land_fraction = 0.610000; 12:23:57.300 [planetinfo.record]: polar_cloud_color = 0.899462, 0.873245, 0.915723, 1; 12:23:57.300 [planetinfo.record]: polar_land_color = 0.934, 0.920774, 0.881098, 1; 12:23:57.301 [planetinfo.record]: polar_sea_color = 0.707306, 0.817259, 0.923828, 1; 12:23:57.301 [planetinfo.record]: sea_color = 0.0476074, 0.410242, 0.761719, 1; 12:23:57.301 [planetinfo.record]: rotation_speed = 0.000977 12:23:57.301 [planetinfo.record]: planet zpos = 282510.000000 12:23:57.301 [planetinfo.record]: sun_radius = 90267.803802 12:23:57.301 [planetinfo.record]: sun_vector = 0.131 -0.898 -0.421 12:23:57.301 [planetinfo.record]: sun_distance = 721970 12:23:57.301 [planetinfo.record]: corona_flare = 0.097890 12:23:57.301 [planetinfo.record]: corona_hues = 0.914742 12:23:57.301 [planetinfo.record]: sun_color = 0.65538, 0.569384, 0.383144, 1 12:23:57.301 [planetinfo.record]: corona_shimmer = 0.518112 12:23:57.301 [planetinfo.record]: station_vector = -0.415 -0.176 0.892 12:23:57.301 [planetinfo.record]: station = coriolis 12:24:01.468 [PLANETINFO OVER]: Done 12:24:01.468 [PLANETINFO LOGGING]: 2 85 12:24:02.478 [planetinfo.record]: seed = 57 235 229 148 12:24:02.478 [planetinfo.record]: coordinates = 235 151 12:24:02.478 [planetinfo.record]: government = 7; 12:24:02.479 [planetinfo.record]: economy = 7; 12:24:02.479 [planetinfo.record]: techlevel = 7; 12:24:02.479 [planetinfo.record]: population = 43; 12:24:02.479 [planetinfo.record]: productivity = 11352; 12:24:02.479 [planetinfo.record]: name = "Ratitige"; 12:24:02.479 [planetinfo.record]: inhabitant = "Black Fat Bird"; 12:24:02.479 [planetinfo.record]: inhabitants = "Black Fat Birds"; 12:24:02.479 [planetinfo.record]: description = "The world Ratitige is scourged by deadly tree grubs."; 12:24:02.508 [planetinfo.record]: air_color = 0.600374, 0.904143, 0.963229, 1; 12:24:02.508 [planetinfo.record]: cloud_alpha = 1.000000; 12:24:02.508 [planetinfo.record]: cloud_color = 0.655487, 0.892578, 0.418396, 1; 12:24:02.508 [planetinfo.record]: cloud_fraction = 0.260000; 12:24:02.508 [planetinfo.record]: land_color = 0.456328, 0.483378, 0.66, 1; 12:24:02.508 [planetinfo.record]: land_fraction = 0.560000; 12:24:02.508 [planetinfo.record]: polar_cloud_color = 0.75197, 0.90166, 0.602281, 1; 12:24:02.508 [planetinfo.record]: polar_land_color = 0.861943, 0.871513, 0.934, 1; 12:24:02.508 [planetinfo.record]: polar_sea_color = 0.935352, 0.919342, 0.876801, 1; 12:24:02.508 [planetinfo.record]: sea_color = 0.646484, 0.602222, 0.484611, 1; 12:24:02.508 [planetinfo.record]: rotation_speed = 0.004166 12:24:02.508 [planetinfo.record]: planet zpos = 611250.000000 12:24:02.509 [planetinfo.record]: sun_radius = 88764.438629 12:24:02.509 [planetinfo.record]: sun_vector = -0.810 -0.380 -0.446 12:24:02.509 [planetinfo.record]: sun_distance = 937250 12:24:02.509 [planetinfo.record]: corona_flare = 0.067946 12:24:02.509 [planetinfo.record]: corona_hues = 0.617165 12:24:02.509 [planetinfo.record]: sun_color = 0.814243, 0.768945, 0.617907, 1 12:24:02.509 [planetinfo.record]: corona_shimmer = 0.345869 12:24:02.509 [planetinfo.record]: station_vector = 0.387 -0.892 0.235 12:24:02.509 [planetinfo.record]: station = coriolis 12:24:07.685 [PLANETINFO OVER]: Done 12:24:07.686 [PLANETINFO LOGGING]: 2 86 12:24:08.697 [planetinfo.record]: seed = 233 67 237 242 12:24:08.697 [planetinfo.record]: coordinates = 67 151 12:24:08.697 [planetinfo.record]: government = 5; 12:24:08.697 [planetinfo.record]: economy = 7; 12:24:08.697 [planetinfo.record]: techlevel = 6; 12:24:08.697 [planetinfo.record]: population = 37; 12:24:08.698 [planetinfo.record]: productivity = 7992; 12:24:08.698 [planetinfo.record]: name = "Enrece"; 12:24:08.698 [planetinfo.record]: inhabitant = "Fat Feline"; 12:24:08.698 [planetinfo.record]: inhabitants = "Fat Felines"; 12:24:08.698 [planetinfo.record]: description = "The planet Enrece is cursed by deadly civil war."; 12:24:08.704 [planetinfo.record]: air_color = 0.696841, 0.516605, 0.938514, 1; 12:24:08.705 [planetinfo.record]: cloud_alpha = 1.000000; 12:24:08.705 [planetinfo.record]: cloud_color = 0.818359, 0.20459, 0.636147, 1; 12:24:08.705 [planetinfo.record]: cloud_fraction = 0.440000; 12:24:08.705 [planetinfo.record]: land_color = 0.621094, 0.40274, 0.546035, 1; 12:24:08.705 [planetinfo.record]: land_fraction = 0.300000; 12:24:08.705 [planetinfo.record]: polar_cloud_color = 0.868262, 0.461264, 0.747434, 1; 12:24:08.705 [planetinfo.record]: polar_land_color = 0.937891, 0.855459, 0.909555, 1; 12:24:08.705 [planetinfo.record]: polar_sea_color = 0.927539, 0.855415, 0.833336, 1; 12:24:08.705 [planetinfo.record]: sea_color = 0.724609, 0.49923, 0.430237, 1; 12:24:08.706 [planetinfo.record]: rotation_speed = 0.001996 12:24:08.706 [planetinfo.record]: planet zpos = 509250.000000 12:24:08.706 [planetinfo.record]: sun_radius = 85793.477631 12:24:08.706 [planetinfo.record]: sun_vector = -0.835 -0.030 -0.550 12:24:08.706 [planetinfo.record]: sun_distance = 611100 12:24:08.706 [planetinfo.record]: corona_flare = 0.043048 12:24:08.706 [planetinfo.record]: corona_hues = 0.874359 12:24:08.706 [planetinfo.record]: sun_color = 0.660739, 0.684121, 0.764301, 1 12:24:08.707 [planetinfo.record]: corona_shimmer = 0.353412 12:24:08.707 [planetinfo.record]: station_vector = -0.770 -0.160 0.618 12:24:08.707 [planetinfo.record]: station = coriolis 12:24:14.208 [PLANETINFO OVER]: Done 12:24:14.209 [PLANETINFO LOGGING]: 2 87 12:24:15.212 [planetinfo.record]: seed = 145 198 53 154 12:24:15.212 [planetinfo.record]: coordinates = 198 5 12:24:15.212 [planetinfo.record]: government = 2; 12:24:15.212 [planetinfo.record]: economy = 5; 12:24:15.212 [planetinfo.record]: techlevel = 5; 12:24:15.212 [planetinfo.record]: population = 28; 12:24:15.212 [planetinfo.record]: productivity = 6720; 12:24:15.212 [planetinfo.record]: name = "Qubibi"; 12:24:15.212 [planetinfo.record]: inhabitant = "Human Colonial"; 12:24:15.212 [planetinfo.record]: inhabitants = "Human Colonials"; 12:24:15.212 [planetinfo.record]: description = "The planet Qubibi is most famous for the Qubibiian spotted shrew."; 12:24:15.223 [planetinfo.record]: air_color = 0.454702, 0.720273, 0.858516, 1; 12:24:15.223 [planetinfo.record]: cloud_alpha = 1.000000; 12:24:15.223 [planetinfo.record]: cloud_color = 0.108398, 0.578125, 0.126747, 1; 12:24:15.223 [planetinfo.record]: cloud_fraction = 0.450000; 12:24:15.223 [planetinfo.record]: land_color = 0.0934296, 0.613281, 0.235577, 1; 12:24:15.223 [planetinfo.record]: land_fraction = 0.240000; 12:24:15.223 [planetinfo.record]: polar_cloud_color = 0.374139, 0.760156, 0.389218, 1; 12:24:15.224 [planetinfo.record]: polar_land_color = 0.739754, 0.938672, 0.794146, 1; 12:24:15.224 [planetinfo.record]: polar_sea_color = 0.930469, 0.892333, 0.858685, 1; 12:24:15.224 [planetinfo.record]: sea_color = 0.695312, 0.581323, 0.480743, 1; 12:24:15.224 [planetinfo.record]: rotation_speed = 0.001975 12:24:15.224 [planetinfo.record]: planet zpos = 668880.000000 12:24:15.224 [planetinfo.record]: sun_radius = 117311.198730 12:24:15.224 [planetinfo.record]: sun_vector = 0.287 -0.314 -0.905 12:24:15.224 [planetinfo.record]: sun_distance = 1059060 12:24:15.224 [planetinfo.record]: corona_flare = 0.016504 12:24:15.224 [planetinfo.record]: corona_hues = 0.744736 12:24:15.225 [planetinfo.record]: sun_color = 0.756467, 0.706393, 0.704187, 1 12:24:15.225 [planetinfo.record]: corona_shimmer = 0.717464 12:24:15.225 [planetinfo.record]: station_vector = -0.680 -0.183 0.710 12:24:15.226 [planetinfo.record]: station = coriolis 12:24:19.694 [PLANETINFO OVER]: Done 12:24:19.695 [PLANETINFO LOGGING]: 2 88 12:24:20.705 [planetinfo.record]: seed = 1 199 205 243 12:24:20.705 [planetinfo.record]: coordinates = 199 198 12:24:20.705 [planetinfo.record]: government = 0; 12:24:20.705 [planetinfo.record]: economy = 6; 12:24:20.705 [planetinfo.record]: techlevel = 4; 12:24:20.705 [planetinfo.record]: population = 23; 12:24:20.705 [planetinfo.record]: productivity = 2944; 12:24:20.705 [planetinfo.record]: name = "Beleteat"; 12:24:20.706 [planetinfo.record]: inhabitant = "Bug-Eyed Bird"; 12:24:20.706 [planetinfo.record]: inhabitants = "Bug-Eyed Birds"; 12:24:20.706 [planetinfo.record]: description = "This planet is plagued by deadly earthquakes."; 12:24:20.719 [planetinfo.record]: air_color = 0.67146, 0.877587, 0.880629, 1; 12:24:20.719 [planetinfo.record]: cloud_alpha = 1.000000; 12:24:20.719 [planetinfo.record]: cloud_color = 0.640538, 0.644531, 0.571518, 1; 12:24:20.719 [planetinfo.record]: cloud_fraction = 0.310000; 12:24:20.719 [planetinfo.record]: land_color = 0.484688, 0.66, 0.503862, 1; 12:24:20.720 [planetinfo.record]: land_fraction = 0.660000; 12:24:20.720 [planetinfo.record]: polar_cloud_color = 0.78698, 0.790039, 0.734104, 1; 12:24:20.720 [planetinfo.record]: polar_land_color = 0.871977, 0.934, 0.87876, 1; 12:24:20.720 [planetinfo.record]: polar_sea_color = 0.92168, 0.89095, 0.818171, 1; 12:24:20.720 [planetinfo.record]: sea_color = 0.783203, 0.678754, 0.431374, 1; 12:24:20.720 [planetinfo.record]: rotation_speed = 0.004204 12:24:20.720 [planetinfo.record]: planet zpos = 453960.000000 12:24:20.720 [planetinfo.record]: sun_radius = 98358.808136 12:24:20.720 [planetinfo.record]: sun_vector = -0.069 -0.617 0.784 12:24:20.720 [planetinfo.record]: sun_distance = 870090 12:24:20.720 [planetinfo.record]: corona_flare = 0.030304 12:24:20.720 [planetinfo.record]: corona_hues = 0.856773 12:24:20.720 [planetinfo.record]: sun_color = 0.750391, 0.516023, 0.385644, 1 12:24:20.720 [planetinfo.record]: corona_shimmer = 0.271425 12:24:20.720 [planetinfo.record]: station_vector = -0.876 0.350 0.332 12:24:20.720 [planetinfo.record]: station = coriolis 12:24:25.419 [PLANETINFO OVER]: Done 12:24:25.420 [PLANETINFO LOGGING]: 2 89 12:24:26.441 [planetinfo.record]: seed = 201 177 197 111 12:24:26.441 [planetinfo.record]: coordinates = 177 60 12:24:26.441 [planetinfo.record]: government = 1; 12:24:26.441 [planetinfo.record]: economy = 6; 12:24:26.441 [planetinfo.record]: techlevel = 3; 12:24:26.441 [planetinfo.record]: population = 20; 12:24:26.441 [planetinfo.record]: productivity = 3200; 12:24:26.441 [planetinfo.record]: name = "Aisondi"; 12:24:26.441 [planetinfo.record]: inhabitant = "Blue Furry Rodent"; 12:24:26.441 [planetinfo.record]: inhabitants = "Blue Furry Rodents"; 12:24:26.441 [planetinfo.record]: description = "Aisondi is a revolting little planet."; 12:24:26.464 [planetinfo.record]: air_color = 0.630788, 0.9164, 0.916096, 1; 12:24:26.464 [planetinfo.record]: cloud_alpha = 1.000000; 12:24:26.464 [planetinfo.record]: cloud_color = 0.74998, 0.751953, 0.499344, 1; 12:24:26.464 [planetinfo.record]: cloud_fraction = 0.440000; 12:24:26.464 [planetinfo.record]: land_color = 0.66, 0.522735, 0.286172, 1; 12:24:26.464 [planetinfo.record]: land_fraction = 0.570000; 12:24:26.464 [planetinfo.record]: polar_cloud_color = 0.837004, 0.838379, 0.662352, 1; 12:24:26.464 [planetinfo.record]: polar_land_color = 0.934, 0.885437, 0.801744, 1; 12:24:26.464 [planetinfo.record]: polar_sea_color = 0.933008, 0.904321, 0.873784, 1; 12:24:26.464 [planetinfo.record]: sea_color = 0.669922, 0.587531, 0.499825, 1; 12:24:26.464 [planetinfo.record]: rotation_speed = 0.001015 12:24:26.464 [planetinfo.record]: planet zpos = 683300.000000 12:24:26.464 [planetinfo.record]: sun_radius = 217297.240601 12:24:26.464 [planetinfo.record]: sun_vector = -0.036 0.774 0.632 12:24:26.464 [planetinfo.record]: sun_distance = 1434930 12:24:26.464 [planetinfo.record]: corona_flare = 0.013277 12:24:26.464 [planetinfo.record]: corona_hues = 0.595551 12:24:26.464 [planetinfo.record]: sun_color = 0.621168, 0.648557, 0.702304, 1 12:24:26.464 [planetinfo.record]: corona_shimmer = 0.919571 12:24:26.464 [planetinfo.record]: station_vector = -0.263 0.951 0.162 12:24:26.464 [planetinfo.record]: station = coriolis 12:24:30.924 [PLANETINFO OVER]: Done 12:24:30.925 [PLANETINFO LOGGING]: 2 90 12:24:31.930 [planetinfo.record]: seed = 57 77 173 42 12:24:31.930 [planetinfo.record]: coordinates = 77 127 12:24:31.930 [planetinfo.record]: government = 7; 12:24:31.930 [planetinfo.record]: economy = 7; 12:24:31.930 [planetinfo.record]: techlevel = 5; 12:24:31.930 [planetinfo.record]: population = 35; 12:24:31.930 [planetinfo.record]: productivity = 9240; 12:24:31.930 [planetinfo.record]: name = "Artia"; 12:24:31.931 [planetinfo.record]: inhabitant = "Small Red Horned Bird"; 12:24:31.931 [planetinfo.record]: inhabitants = "Small Red Horned Birds"; 12:24:31.931 [planetinfo.record]: description = "The world Artia is mildly fabled for the Artiaian mountain lobstoid but plagued by occasional civil war."; 12:24:31.946 [planetinfo.record]: air_color = 0.654359, 0.527455, 0.917051, 1; 12:24:31.946 [planetinfo.record]: cloud_alpha = 1.000000; 12:24:31.946 [planetinfo.record]: cloud_color = 0.749926, 0.244431, 0.753906, 1; 12:24:31.946 [planetinfo.record]: cloud_fraction = 0.280000; 12:24:31.946 [planetinfo.record]: land_color = 0.66, 0.115915, 0.0747656, 1; 12:24:31.947 [planetinfo.record]: land_fraction = 0.480000; 12:24:31.947 [planetinfo.record]: polar_cloud_color = 0.836489, 0.484786, 0.839258, 1; 12:24:31.947 [planetinfo.record]: polar_land_color = 0.934, 0.741509, 0.726951, 1; 12:24:31.947 [planetinfo.record]: polar_sea_color = 0.942187, 0.883301, 0.916425, 1; 12:24:31.947 [planetinfo.record]: sea_color = 0.578125, 0.433594, 0.514893, 1; 12:24:31.947 [planetinfo.record]: rotation_speed = 0.002531 12:24:31.947 [planetinfo.record]: planet zpos = 763420.000000 12:24:31.948 [planetinfo.record]: sun_radius = 101144.596405 12:24:31.948 [planetinfo.record]: sun_vector = 0.847 -0.497 -0.188 12:24:31.948 [planetinfo.record]: sun_distance = 927010 12:24:31.948 [planetinfo.record]: corona_flare = 0.081346 12:24:31.948 [planetinfo.record]: corona_hues = 0.563904 12:24:31.948 [planetinfo.record]: sun_color = 0.721191, 0.69654, 0.404171, 1 12:24:31.948 [planetinfo.record]: corona_shimmer = 0.291005 12:24:31.948 [planetinfo.record]: station_vector = 0.624 0.768 0.141 12:24:31.948 [planetinfo.record]: station = coriolis 12:24:37.252 [PLANETINFO OVER]: Done 12:24:37.252 [PLANETINFO LOGGING]: 2 91 12:24:38.258 [planetinfo.record]: seed = 97 145 21 248 12:24:38.258 [planetinfo.record]: coordinates = 145 111 12:24:38.258 [planetinfo.record]: government = 4; 12:24:38.258 [planetinfo.record]: economy = 7; 12:24:38.258 [planetinfo.record]: techlevel = 3; 12:24:38.258 [planetinfo.record]: population = 24; 12:24:38.258 [planetinfo.record]: productivity = 4608; 12:24:38.258 [planetinfo.record]: name = "Ededxebe"; 12:24:38.258 [planetinfo.record]: inhabitant = "Human Colonial"; 12:24:38.258 [planetinfo.record]: inhabitants = "Human Colonials"; 12:24:38.258 [planetinfo.record]: description = "The world Ededxebe is reasonably famous for its inhabitants’ ancient mating traditions."; 12:24:38.259 [planetinfo.record]: air_color = 0.67047, 0.587578, 0.861768, 1; 12:24:38.259 [planetinfo.record]: cloud_alpha = 1.000000; 12:24:38.259 [planetinfo.record]: cloud_color = 0.571564, 0.378914, 0.587891, 1; 12:24:38.259 [planetinfo.record]: cloud_fraction = 0.300000; 12:24:38.259 [planetinfo.record]: land_color = 0.201094, 0.229775, 0.66, 1; 12:24:38.259 [planetinfo.record]: land_fraction = 0.650000; 12:24:38.259 [planetinfo.record]: polar_cloud_color = 0.751281, 0.594692, 0.764551, 1; 12:24:38.259 [planetinfo.record]: polar_land_color = 0.771645, 0.781792, 0.934, 1; 12:24:38.259 [planetinfo.record]: polar_sea_color = 0.939258, 0.898568, 0.880738, 1; 12:24:38.259 [planetinfo.record]: sea_color = 0.607422, 0.502165, 0.456041, 1; 12:24:38.260 [planetinfo.record]: rotation_speed = 0.001385 12:24:38.260 [planetinfo.record]: planet zpos = 500900.000000 12:24:38.260 [planetinfo.record]: sun_radius = 87983.097229 12:24:38.260 [planetinfo.record]: sun_vector = -0.777 0.040 0.628 12:24:38.260 [planetinfo.record]: sun_distance = 951710 12:24:38.260 [planetinfo.record]: corona_flare = 0.083940 12:24:38.260 [planetinfo.record]: corona_hues = 0.654892 12:24:38.260 [planetinfo.record]: sun_color = 0.664416, 0.521332, 0.301297, 1 12:24:38.260 [planetinfo.record]: corona_shimmer = 0.427112 12:24:38.260 [planetinfo.record]: station_vector = 0.449 0.799 0.400 12:24:38.260 [planetinfo.record]: station = coriolis 12:24:43.087 [PLANETINFO OVER]: Done 12:24:43.088 [PLANETINFO LOGGING]: 2 92 12:24:44.106 [planetinfo.record]: seed = 17 115 13 238 12:24:44.106 [planetinfo.record]: coordinates = 115 130 12:24:44.106 [planetinfo.record]: government = 2; 12:24:44.106 [planetinfo.record]: economy = 2; 12:24:44.106 [planetinfo.record]: techlevel = 9; 12:24:44.106 [planetinfo.record]: population = 41; 12:24:44.106 [planetinfo.record]: productivity = 15744; 12:24:44.106 [planetinfo.record]: name = "Regeza"; 12:24:44.106 [planetinfo.record]: inhabitant = "Human Colonial"; 12:24:44.107 [planetinfo.record]: inhabitants = "Human Colonials"; 12:24:44.107 [planetinfo.record]: description = "The planet Regeza is a boring planet."; 12:24:44.136 [planetinfo.record]: air_color = 0.631778, 0.909246, 0.895957, 1; 12:24:44.136 [planetinfo.record]: cloud_alpha = 1.000000; 12:24:44.136 [planetinfo.record]: cloud_color = 0.730469, 0.699772, 0.499344, 1; 12:24:44.136 [planetinfo.record]: cloud_fraction = 0.310000; 12:24:44.136 [planetinfo.record]: land_color = 0.539062, 0.492737, 0.495994, 1; 12:24:44.136 [planetinfo.record]: land_fraction = 0.250000; 12:24:44.136 [planetinfo.record]: polar_cloud_color = 0.828711, 0.806946, 0.66483, 1; 12:24:44.136 [planetinfo.record]: polar_land_color = 0.946094, 0.925767, 0.927197, 1; 12:24:44.136 [planetinfo.record]: polar_sea_color = 0.92885, 0.929297, 0.872123, 1; 12:24:44.136 [planetinfo.record]: sea_color = 0.705672, 0.707031, 0.533035, 1; 12:24:44.136 [planetinfo.record]: rotation_speed = 0.002313 12:24:44.136 [planetinfo.record]: planet zpos = 781800.000000 12:24:44.136 [planetinfo.record]: sun_radius = 136495.294189 12:24:44.136 [planetinfo.record]: sun_vector = -0.647 -0.629 -0.430 12:24:44.136 [planetinfo.record]: sun_distance = 1498450 12:24:44.136 [planetinfo.record]: corona_flare = 0.051245 12:24:44.136 [planetinfo.record]: corona_hues = 0.747063 12:24:44.136 [planetinfo.record]: sun_color = 0.653452, 0.64821, 0.619082, 1 12:24:44.136 [planetinfo.record]: corona_shimmer = 0.480906 12:24:44.137 [planetinfo.record]: station_vector = 0.229 0.963 0.141 12:24:44.137 [planetinfo.record]: station = coriolis 12:24:49.168 [PLANETINFO OVER]: Done 12:24:49.168 [PLANETINFO LOGGING]: 2 93 12:24:50.174 [planetinfo.record]: seed = 217 21 165 61 12:24:50.174 [planetinfo.record]: coordinates = 21 68 12:24:50.174 [planetinfo.record]: government = 3; 12:24:50.174 [planetinfo.record]: economy = 4; 12:24:50.174 [planetinfo.record]: techlevel = 6; 12:24:50.174 [planetinfo.record]: population = 32; 12:24:50.174 [planetinfo.record]: productivity = 10752; 12:24:50.174 [planetinfo.record]: name = "Istimale"; 12:24:50.174 [planetinfo.record]: inhabitant = "Red Bug-Eyed Lizard"; 12:24:50.174 [planetinfo.record]: inhabitants = "Red Bug-Eyed Lizards"; 12:24:50.174 [planetinfo.record]: description = "The planet Istimale is cursed by deadly civil war."; 12:24:50.188 [planetinfo.record]: air_color = 0.679379, 0.50737, 0.941766, 1; 12:24:50.188 [planetinfo.record]: cloud_alpha = 1.000000; 12:24:50.188 [planetinfo.record]: cloud_color = 0.828125, 0.177917, 0.696052, 1; 12:24:50.188 [planetinfo.record]: cloud_fraction = 0.360000; 12:24:50.188 [planetinfo.record]: land_color = 0.288025, 0.0335156, 0.66, 1; 12:24:50.188 [planetinfo.record]: land_fraction = 0.590000; 12:24:50.188 [planetinfo.record]: polar_cloud_color = 0.872656, 0.444424, 0.785672, 1; 12:24:50.188 [planetinfo.record]: polar_land_color = 0.8024, 0.712357, 0.934, 1; 12:24:50.188 [planetinfo.record]: polar_sea_color = 0.945508, 0.905363, 0.894631, 1; 12:24:50.188 [planetinfo.record]: sea_color = 0.544922, 0.452376, 0.427636, 1; 12:24:50.188 [planetinfo.record]: rotation_speed = 0.002843 12:24:50.188 [planetinfo.record]: planet zpos = 924750.000000 12:24:50.188 [planetinfo.record]: sun_radius = 169658.850861 12:24:50.188 [planetinfo.record]: sun_vector = -0.806 -0.542 -0.237 12:24:50.188 [planetinfo.record]: sun_distance = 1171350 12:24:50.188 [planetinfo.record]: corona_flare = 0.046490 12:24:50.188 [planetinfo.record]: corona_hues = 0.536003 12:24:50.188 [planetinfo.record]: sun_color = 0.679144, 0.390972, 0.257985, 1 12:24:50.188 [planetinfo.record]: corona_shimmer = 0.776787 12:24:50.188 [planetinfo.record]: station_vector = 0.230 -0.973 0.021 12:24:50.188 [planetinfo.record]: station = coriolis 12:24:55.349 [PLANETINFO OVER]: Done 12:24:55.350 [PLANETINFO LOGGING]: 2 94 12:24:56.364 [planetinfo.record]: seed = 9 193 109 68 12:24:56.364 [planetinfo.record]: coordinates = 193 235 12:24:56.364 [planetinfo.record]: government = 1; 12:24:56.364 [planetinfo.record]: economy = 3; 12:24:56.364 [planetinfo.record]: techlevel = 6; 12:24:56.364 [planetinfo.record]: population = 29; 12:24:56.364 [planetinfo.record]: productivity = 8120; 12:24:56.364 [planetinfo.record]: name = "Zaervema"; 12:24:56.364 [planetinfo.record]: inhabitant = "Human Colonial"; 12:24:56.364 [planetinfo.record]: inhabitants = "Human Colonials"; 12:24:56.364 [planetinfo.record]: description = "Zaervema is well known for the Zaervemaian spotted cat but cursed by dreadful solar activity."; 12:24:56.370 [planetinfo.record]: air_color = 0.440731, 0.747204, 0.861768, 1; 12:24:56.371 [planetinfo.record]: cloud_alpha = 1.000000; 12:24:56.371 [planetinfo.record]: cloud_color = 0.165703, 0.587891, 0.0780792, 1; 12:24:56.371 [planetinfo.record]: cloud_fraction = 0.300000; 12:24:56.371 [planetinfo.record]: land_color = 0.283051, 0.402147, 0.517578, 1; 12:24:56.371 [planetinfo.record]: land_fraction = 0.290000; 12:24:56.371 [planetinfo.record]: polar_cloud_color = 0.421392, 0.764551, 0.35017, 1; 12:24:56.371 [planetinfo.record]: polar_land_color = 0.840824, 0.895372, 0.948242, 1; 12:24:56.371 [planetinfo.record]: polar_sea_color = 0.825005, 0.949219, 0.940485, 1; 12:24:56.371 [planetinfo.record]: sea_color = 0.242004, 0.507812, 0.489123, 1; 12:24:56.371 [planetinfo.record]: rotation_speed = 0.003185 12:24:56.372 [planetinfo.record]: planet zpos = 483960.000000 12:24:56.372 [planetinfo.record]: sun_radius = 98847.761688 12:24:56.372 [planetinfo.record]: sun_vector = -0.903 -0.422 0.079 12:24:56.372 [planetinfo.record]: sun_distance = 967920 12:24:56.372 [planetinfo.record]: corona_flare = 0.042731 12:24:56.372 [planetinfo.record]: corona_hues = 0.870483 12:24:56.372 [planetinfo.record]: sun_color = 0.320916, 0.635513, 0.843158, 1 12:24:56.372 [planetinfo.record]: corona_shimmer = 0.265704 12:24:56.372 [planetinfo.record]: station_vector = -0.786 0.265 0.559 12:24:56.372 [planetinfo.record]: station = coriolis 12:25:01.288 [PLANETINFO OVER]: Done 12:25:01.289 [PLANETINFO LOGGING]: 2 95 12:25:02.295 [planetinfo.record]: seed = 177 43 245 18 12:25:02.295 [planetinfo.record]: coordinates = 43 246 12:25:02.295 [planetinfo.record]: government = 6; 12:25:02.295 [planetinfo.record]: economy = 6; 12:25:02.295 [planetinfo.record]: techlevel = 7; 12:25:02.295 [planetinfo.record]: population = 41; 12:25:02.295 [planetinfo.record]: productivity = 13120; 12:25:02.295 [planetinfo.record]: name = "Enlabean"; 12:25:02.296 [planetinfo.record]: inhabitant = "Green Furry Insect"; 12:25:02.296 [planetinfo.record]: inhabitants = "Green Furry Insects"; 12:25:02.296 [planetinfo.record]: description = "The planet Enlabean is cursed by dreadful civil war."; 12:25:02.308 [planetinfo.record]: air_color = 0.682598, 0.857215, 0.844764, 1; 12:25:02.308 [planetinfo.record]: cloud_alpha = 1.000000; 12:25:02.308 [planetinfo.record]: cloud_color = 0.574219, 0.572852, 0.56749, 1; 12:25:02.308 [planetinfo.record]: cloud_fraction = 0.110000; 12:25:02.308 [planetinfo.record]: land_color = 0.527344, 0.465546, 0.490168, 1; 12:25:02.308 [planetinfo.record]: land_fraction = 0.380000; 12:25:02.308 [planetinfo.record]: polar_cloud_color = 0.758398, 0.75727, 0.752844, 1; 12:25:02.308 [planetinfo.record]: polar_land_color = 0.947266, 0.919514, 0.930571, 1; 12:25:02.308 [planetinfo.record]: polar_sea_color = 0.928432, 0.930078, 0.877398, 1; 12:25:02.308 [planetinfo.record]: sea_color = 0.694268, 0.699219, 0.540802, 1; 12:25:02.308 [planetinfo.record]: rotation_speed = 0.000640 12:25:02.308 [planetinfo.record]: planet zpos = 505650.000000 12:25:02.308 [planetinfo.record]: sun_radius = 107025.237885 12:25:02.308 [planetinfo.record]: sun_vector = -0.340 0.700 -0.628 12:25:02.308 [planetinfo.record]: sun_distance = 606780 12:25:02.308 [planetinfo.record]: corona_flare = 0.036163 12:25:02.308 [planetinfo.record]: corona_hues = 0.876923 12:25:02.308 [planetinfo.record]: sun_color = 0.435673, 0.507474, 0.75946, 1 12:25:02.309 [planetinfo.record]: corona_shimmer = 0.343675 12:25:02.309 [planetinfo.record]: station_vector = 0.934 -0.010 0.357 12:25:02.309 [planetinfo.record]: station = coriolis 12:25:06.575 [PLANETINFO OVER]: Done 12:25:06.576 [PLANETINFO LOGGING]: 2 96 12:25:07.600 [planetinfo.record]: seed = 161 187 77 100 12:25:07.600 [planetinfo.record]: coordinates = 187 115 12:25:07.600 [planetinfo.record]: government = 4; 12:25:07.600 [planetinfo.record]: economy = 3; 12:25:07.600 [planetinfo.record]: techlevel = 9; 12:25:07.600 [planetinfo.record]: population = 44; 12:25:07.600 [planetinfo.record]: productivity = 19712; 12:25:07.600 [planetinfo.record]: name = "Zabebe"; 12:25:07.600 [planetinfo.record]: inhabitant = "Human Colonial"; 12:25:07.600 [planetinfo.record]: inhabitants = "Human Colonials"; 12:25:07.600 [planetinfo.record]: description = "Zabebe is a revolting dump."; 12:25:07.616 [planetinfo.record]: air_color = 0.69726, 0.724462, 0.981439, 1; 12:25:07.616 [planetinfo.record]: cloud_alpha = 1.000000; 12:25:07.616 [planetinfo.record]: cloud_color = 0.691948, 0.761761, 0.947266, 1; 12:25:07.616 [planetinfo.record]: cloud_fraction = 0.160000; 12:25:07.616 [planetinfo.record]: land_color = 0.139219, 0.330443, 0.66, 1; 12:25:07.616 [planetinfo.record]: land_fraction = 0.550000; 12:25:07.616 [planetinfo.record]: polar_cloud_color = 0.770233, 0.812899, 0.92627, 1; 12:25:07.616 [planetinfo.record]: polar_land_color = 0.749754, 0.817407, 0.934, 1; 12:25:07.616 [planetinfo.record]: polar_sea_color = 0.874281, 0.927734, 0.876369, 1; 12:25:07.616 [planetinfo.record]: sea_color = 0.556107, 0.722656, 0.562612, 1; 12:25:07.616 [planetinfo.record]: rotation_speed = 0.000386 12:25:07.616 [planetinfo.record]: planet zpos = 402700.000000 12:25:07.616 [planetinfo.record]: sun_radius = 124373.319855 12:25:07.616 [planetinfo.record]: sun_vector = 0.810 0.584 0.051 12:25:07.616 [planetinfo.record]: sun_distance = 805400 12:25:07.616 [planetinfo.record]: corona_flare = 0.038538 12:25:07.616 [planetinfo.record]: corona_hues = 0.560349 12:25:07.616 [planetinfo.record]: sun_color = 0.784033, 0.56861, 0.435252, 1 12:25:07.616 [planetinfo.record]: corona_shimmer = 0.264940 12:25:07.616 [planetinfo.record]: station_vector = -0.007 -0.003 1.000 12:25:07.616 [planetinfo.record]: station = coriolis 12:25:12.757 [PLANETINFO OVER]: Done 12:25:12.758 [PLANETINFO LOGGING]: 2 97 12:25:13.761 [planetinfo.record]: seed = 105 171 133 242 12:25:13.761 [planetinfo.record]: coordinates = 171 179 12:25:13.761 [planetinfo.record]: government = 5; 12:25:13.761 [planetinfo.record]: economy = 3; 12:25:13.761 [planetinfo.record]: techlevel = 10; 12:25:13.761 [planetinfo.record]: population = 49; 12:25:13.761 [planetinfo.record]: productivity = 24696; 12:25:13.761 [planetinfo.record]: name = "Enata"; 12:25:13.761 [planetinfo.record]: inhabitant = "Slimy Lizard"; 12:25:13.761 [planetinfo.record]: inhabitants = "Slimy Lizards"; 12:25:13.761 [planetinfo.record]: description = "This planet is fabled for its unusual tropical forests and its exciting Zero-G cricket."; 12:25:13.780 [planetinfo.record]: air_color = 0.693154, 0.844357, 0.892336, 1; 12:25:13.780 [planetinfo.record]: cloud_alpha = 1.000000; 12:25:13.780 [planetinfo.record]: cloud_color = 0.644591, 0.679688, 0.631897, 1; 12:25:13.780 [planetinfo.record]: cloud_fraction = 0.480000; 12:25:13.780 [planetinfo.record]: land_color = 0.66, 0.427969, 0.466036, 1; 12:25:13.780 [planetinfo.record]: land_fraction = 0.380000; 12:25:13.780 [planetinfo.record]: polar_cloud_color = 0.779852, 0.805859, 0.770446, 1; 12:25:13.780 [planetinfo.record]: polar_land_color = 0.934, 0.85191, 0.865378, 1; 12:25:13.780 [planetinfo.record]: polar_sea_color = 0.933984, 0.92759, 0.875519, 1; 12:25:13.780 [planetinfo.record]: sea_color = 0.660156, 0.642077, 0.494859, 1; 12:25:13.780 [planetinfo.record]: rotation_speed = 0.004824 12:25:13.780 [planetinfo.record]: planet zpos = 489860.000000 12:25:13.780 [planetinfo.record]: sun_radius = 86154.652863 12:25:13.780 [planetinfo.record]: sun_vector = -0.307 0.000 0.952 12:25:13.780 [planetinfo.record]: sun_distance = 804770 12:25:13.780 [planetinfo.record]: corona_flare = 0.031154 12:25:13.780 [planetinfo.record]: corona_hues = 0.676758 12:25:13.780 [planetinfo.record]: sun_color = 0.732678, 0.337727, 0.263144, 1 12:25:13.781 [planetinfo.record]: corona_shimmer = 0.487517 12:25:13.781 [planetinfo.record]: station_vector = -0.148 0.938 0.314 12:25:13.781 [planetinfo.record]: station = coriolis 12:25:18.078 [PLANETINFO OVER]: Done 12:25:18.078 [PLANETINFO LOGGING]: 2 98 12:25:19.082 [planetinfo.record]: seed = 89 51 45 116 12:25:19.082 [planetinfo.record]: coordinates = 51 239 12:25:19.082 [planetinfo.record]: government = 3; 12:25:19.082 [planetinfo.record]: economy = 7; 12:25:19.082 [planetinfo.record]: techlevel = 5; 12:25:19.082 [planetinfo.record]: population = 31; 12:25:19.082 [planetinfo.record]: productivity = 5208; 12:25:19.082 [planetinfo.record]: name = "Raveries"; 12:25:19.082 [planetinfo.record]: inhabitant = "Human Colonial"; 12:25:19.082 [planetinfo.record]: inhabitants = "Human Colonials"; 12:25:19.082 [planetinfo.record]: description = "This planet is a dull world."; 12:25:19.094 [planetinfo.record]: air_color = 0.639764, 0.591164, 0.842906, 1; 12:25:19.094 [planetinfo.record]: cloud_alpha = 1.000000; 12:25:19.094 [planetinfo.record]: cloud_color = 0.467178, 0.373535, 0.53125, 1; 12:25:19.094 [planetinfo.record]: cloud_fraction = 0.080000; 12:25:19.094 [planetinfo.record]: land_color = 0.335156, 0.66, 0.416367, 1; 12:25:19.094 [planetinfo.record]: land_fraction = 0.470000; 12:25:19.095 [planetinfo.record]: polar_cloud_color = 0.683353, 0.601932, 0.739062, 1; 12:25:19.095 [planetinfo.record]: polar_land_color = 0.819074, 0.934, 0.847806, 1; 12:25:19.095 [planetinfo.record]: polar_sea_color = 0.919508, 0.932031, 0.87478, 1; 12:25:19.095 [planetinfo.record]: sea_color = 0.643156, 0.679688, 0.512686, 1; 12:25:19.095 [planetinfo.record]: rotation_speed = 0.003810 12:25:19.095 [planetinfo.record]: planet zpos = 466920.000000 12:25:19.095 [planetinfo.record]: sun_radius = 104793.269806 12:25:19.095 [planetinfo.record]: sun_vector = -0.102 -0.642 0.760 12:25:19.095 [planetinfo.record]: sun_distance = 856020 12:25:19.095 [planetinfo.record]: corona_flare = 0.029419 12:25:19.095 [planetinfo.record]: corona_hues = 0.974243 12:25:19.096 [planetinfo.record]: sun_color = 0.711991, 0.770987, 0.809937, 1 12:25:19.096 [planetinfo.record]: corona_shimmer = 0.248273 12:25:19.096 [planetinfo.record]: station_vector = -0.955 0.065 0.290 12:25:19.096 [planetinfo.record]: station = coriolis 12:25:23.611 [PLANETINFO OVER]: Done 12:25:23.612 [PLANETINFO LOGGING]: 2 99 12:25:24.615 [planetinfo.record]: seed = 129 73 213 30 12:25:24.615 [planetinfo.record]: coordinates = 73 62 12:25:24.615 [planetinfo.record]: government = 0; 12:25:24.616 [planetinfo.record]: economy = 6; 12:25:24.616 [planetinfo.record]: techlevel = 2; 12:25:24.616 [planetinfo.record]: population = 15; 12:25:24.616 [planetinfo.record]: productivity = 1920; 12:25:24.616 [planetinfo.record]: name = "Ribiara"; 12:25:24.616 [planetinfo.record]: inhabitant = "Green Frog"; 12:25:24.616 [planetinfo.record]: inhabitants = "Green Frogs"; 12:25:24.616 [planetinfo.record]: description = "The world Ribiara is very famous for its unusual casinos but beset by deadly earthquakes."; 12:25:24.620 [planetinfo.record]: air_color = 0.685363, 0.853745, 0.894287, 1; 12:25:24.620 [planetinfo.record]: cloud_alpha = 1.000000; 12:25:24.620 [planetinfo.record]: cloud_color = 0.644207, 0.685547, 0.615921, 1; 12:25:24.620 [planetinfo.record]: cloud_fraction = 0.170000; 12:25:24.621 [planetinfo.record]: land_color = 0.66, 0.570974, 0.484688, 1; 12:25:24.621 [planetinfo.record]: land_fraction = 0.310000; 12:25:24.621 [planetinfo.record]: polar_cloud_color = 0.778025, 0.808496, 0.757176, 1; 12:25:24.621 [planetinfo.record]: polar_land_color = 0.934, 0.902504, 0.871977, 1; 12:25:24.621 [planetinfo.record]: polar_sea_color = 0.855929, 0.888973, 0.913086, 1; 12:25:24.621 [planetinfo.record]: sea_color = 0.651516, 0.77733, 0.869141, 1; 12:25:24.621 [planetinfo.record]: rotation_speed = 0.000025 12:25:24.621 [planetinfo.record]: planet zpos = 841490.000000 12:25:24.621 [planetinfo.record]: sun_radius = 183498.132172 12:25:24.621 [planetinfo.record]: sun_vector = -0.454 0.748 -0.485 12:25:24.621 [planetinfo.record]: sun_distance = 1165140 12:25:24.621 [planetinfo.record]: corona_flare = 0.008211 12:25:24.621 [planetinfo.record]: corona_hues = 0.837601 12:25:24.621 [planetinfo.record]: sun_color = 0.534779, 0.601803, 0.671835, 1 12:25:24.621 [planetinfo.record]: corona_shimmer = 0.511536 12:25:24.622 [planetinfo.record]: station_vector = 0.916 -0.389 0.100 12:25:24.622 [planetinfo.record]: station = coriolis 12:25:28.926 [PLANETINFO OVER]: Done 12:25:28.927 [PLANETINFO LOGGING]: 2 100 12:25:29.935 [planetinfo.record]: seed = 177 212 141 138 12:25:29.935 [planetinfo.record]: coordinates = 212 15 12:25:29.935 [planetinfo.record]: government = 6; 12:25:29.936 [planetinfo.record]: economy = 7; 12:25:29.936 [planetinfo.record]: techlevel = 3; 12:25:29.936 [planetinfo.record]: population = 26; 12:25:29.936 [planetinfo.record]: productivity = 6240; 12:25:29.936 [planetinfo.record]: name = "Arredi"; 12:25:29.936 [planetinfo.record]: inhabitant = "Small Black Bony Humanoid"; 12:25:29.936 [planetinfo.record]: inhabitants = "Small Black Bony Humanoids"; 12:25:29.936 [planetinfo.record]: description = "The planet Arredi is most famous for the Arrediian evil poet and Zero-G hockey."; 12:25:29.964 [planetinfo.record]: air_color = 0.659832, 0.872824, 0.847662, 1; 12:25:29.964 [planetinfo.record]: cloud_alpha = 1.000000; 12:25:29.964 [planetinfo.record]: cloud_color = 0.621094, 0.592738, 0.538605, 1; 12:25:29.964 [planetinfo.record]: cloud_fraction = 0.410000; 12:25:29.964 [planetinfo.record]: land_color = 0.598125, 0.66, 0.600542, 1; 12:25:29.964 [planetinfo.record]: land_fraction = 0.700000; 12:25:29.964 [planetinfo.record]: polar_cloud_color = 0.779492, 0.75725, 0.714788, 1; 12:25:29.964 [planetinfo.record]: polar_land_color = 0.912109, 0.934, 0.912964, 1; 12:25:29.964 [planetinfo.record]: polar_sea_color = 0.708012, 0.929492, 0.872392, 1; 12:25:29.964 [planetinfo.record]: sea_color = 0.0330505, 0.705078, 0.531821, 1; 12:25:29.964 [planetinfo.record]: rotation_speed = 0.003532 12:25:29.964 [planetinfo.record]: planet zpos = 726440.000000 12:25:29.964 [planetinfo.record]: sun_radius = 138716.029053 12:25:29.964 [planetinfo.record]: sun_vector = -0.171 -0.348 -0.922 12:25:29.964 [planetinfo.record]: sun_distance = 1005840 12:25:29.964 [planetinfo.record]: corona_flare = 0.037563 12:25:29.964 [planetinfo.record]: corona_hues = 0.720360 12:25:29.964 [planetinfo.record]: sun_color = 0.735419, 0.550555, 0.286473, 1 12:25:29.965 [planetinfo.record]: corona_shimmer = 1.775562 12:25:29.965 [planetinfo.record]: station_vector = 0.553 -0.433 0.712 12:25:29.965 [planetinfo.record]: station = coriolis 12:25:34.313 [PLANETINFO OVER]: Done 12:25:34.314 [PLANETINFO LOGGING]: 2 101 12:25:35.316 [planetinfo.record]: seed = 121 198 101 2 12:25:35.316 [planetinfo.record]: coordinates = 198 205 12:25:35.316 [planetinfo.record]: government = 7; 12:25:35.316 [planetinfo.record]: economy = 5; 12:25:35.316 [planetinfo.record]: techlevel = 8; 12:25:35.316 [planetinfo.record]: population = 45; 12:25:35.316 [planetinfo.record]: productivity = 19800; 12:25:35.316 [planetinfo.record]: name = "Xeveon"; 12:25:35.316 [planetinfo.record]: inhabitant = "Human Colonial"; 12:25:35.316 [planetinfo.record]: inhabitants = "Human Colonials"; 12:25:35.316 [planetinfo.record]: description = "This planet is plagued by occasional solar activity."; 12:25:35.332 [planetinfo.record]: air_color = 0.625185, 0.549829, 0.885832, 1; 12:25:35.332 [planetinfo.record]: cloud_alpha = 1.000000; 12:25:35.332 [planetinfo.record]: cloud_color = 0.548948, 0.304291, 0.660156, 1; 12:25:35.332 [planetinfo.record]: cloud_fraction = 0.550000; 12:25:35.332 [planetinfo.record]: land_color = 0.572266, 0.48732, 0.491302, 1; 12:25:35.332 [planetinfo.record]: land_fraction = 0.330000; 12:25:35.332 [planetinfo.record]: polar_cloud_color = 0.71315, 0.528526, 0.79707, 1; 12:25:35.332 [planetinfo.record]: polar_land_color = 0.942773, 0.907788, 0.909428, 1; 12:25:35.332 [planetinfo.record]: polar_sea_color = 0.933935, 0.945703, 0.730519, 1; 12:25:35.332 [planetinfo.record]: sea_color = 0.515943, 0.542969, 0.0487823, 1; 12:25:35.332 [planetinfo.record]: rotation_speed = 0.001593 12:25:35.332 [planetinfo.record]: planet zpos = 528900.000000 12:25:35.332 [planetinfo.record]: sun_radius = 72619.373169 12:25:35.332 [planetinfo.record]: sun_vector = -0.034 0.999 -0.030 12:25:35.332 [planetinfo.record]: sun_distance = 740460 12:25:35.332 [planetinfo.record]: corona_flare = 0.023788 12:25:35.332 [planetinfo.record]: corona_hues = 0.523239 12:25:35.332 [planetinfo.record]: sun_color = 0.181667, 0.330453, 0.657394, 1 12:25:35.333 [planetinfo.record]: corona_shimmer = 0.464524 12:25:35.333 [planetinfo.record]: station_vector = -0.093 -0.873 0.480 12:25:35.333 [planetinfo.record]: station = coriolis 12:25:40.118 [PLANETINFO OVER]: Done 12:25:40.119 [PLANETINFO LOGGING]: 2 102 12:25:41.138 [planetinfo.record]: seed = 41 248 237 237 12:25:41.138 [planetinfo.record]: coordinates = 248 95 12:25:41.138 [planetinfo.record]: government = 5; 12:25:41.138 [planetinfo.record]: economy = 7; 12:25:41.138 [planetinfo.record]: techlevel = 3; 12:25:41.139 [planetinfo.record]: population = 25; 12:25:41.139 [planetinfo.record]: productivity = 5400; 12:25:41.139 [planetinfo.record]: name = "Dicemari"; 12:25:41.139 [planetinfo.record]: inhabitant = "Rodent"; 12:25:41.139 [planetinfo.record]: inhabitants = "Rodents"; 12:25:41.139 [planetinfo.record]: description = "This world is a tedious little planet."; 12:25:41.156 [planetinfo.record]: air_color = 0.418921, 0.741491, 0.88193, 1; 12:25:41.156 [planetinfo.record]: cloud_alpha = 1.000000; 12:25:41.156 [planetinfo.record]: cloud_color = 0.0646696, 0.648438, 0.0151978, 1; 12:25:41.156 [planetinfo.record]: cloud_fraction = 0.550000; 12:25:41.156 [planetinfo.record]: land_color = 0.66, 0.224297, 0.275356, 1; 12:25:41.156 [planetinfo.record]: land_fraction = 0.620000; 12:25:41.156 [planetinfo.record]: polar_cloud_color = 0.346278, 0.791797, 0.308522, 1; 12:25:41.156 [planetinfo.record]: polar_land_color = 0.934, 0.779854, 0.797918, 1; 12:25:41.156 [planetinfo.record]: polar_sea_color = 0.936523, 0.903092, 0.876253, 1; 12:25:41.156 [planetinfo.record]: sea_color = 0.634766, 0.544128, 0.471363, 1; 12:25:41.156 [planetinfo.record]: rotation_speed = 0.004521 12:25:41.156 [planetinfo.record]: planet zpos = 894880.000000 12:25:41.156 [planetinfo.record]: sun_radius = 138541.450195 12:25:41.156 [planetinfo.record]: sun_vector = 0.639 0.321 -0.699 12:25:41.156 [planetinfo.record]: sun_distance = 1278400 12:25:41.156 [planetinfo.record]: corona_flare = 0.012056 12:25:41.156 [planetinfo.record]: corona_hues = 0.710136 12:25:41.156 [planetinfo.record]: sun_color = 0.694244, 0.671518, 0.603735, 1 12:25:41.157 [planetinfo.record]: corona_shimmer = 0.871513 12:25:41.157 [planetinfo.record]: station_vector = -0.921 0.373 0.113 12:25:41.157 [planetinfo.record]: station = coriolis 12:25:45.651 [PLANETINFO OVER]: Done 12:25:45.652 [PLANETINFO LOGGING]: 2 103 12:25:46.678 [planetinfo.record]: seed = 209 94 181 207 12:25:46.678 [planetinfo.record]: coordinates = 94 43 12:25:46.678 [planetinfo.record]: government = 2; 12:25:46.678 [planetinfo.record]: economy = 3; 12:25:46.678 [planetinfo.record]: techlevel = 7; 12:25:46.678 [planetinfo.record]: population = 34; 12:25:46.678 [planetinfo.record]: productivity = 11424; 12:25:46.678 [planetinfo.record]: name = "Aquusen"; 12:25:46.678 [planetinfo.record]: inhabitant = "Furry Rodent"; 12:25:46.678 [planetinfo.record]: inhabitants = "Furry Rodents"; 12:25:46.678 [planetinfo.record]: description = "Aquusen is cursed by killer mountain goats."; 12:25:46.688 [planetinfo.record]: air_color = 0.500705, 0.984041, 0.855269, 1; 12:25:46.688 [planetinfo.record]: cloud_alpha = 1.000000; 12:25:46.688 [planetinfo.record]: cloud_color = 0.955078, 0.295664, 0.119385, 1; 12:25:46.695 [planetinfo.record]: cloud_fraction = 0.210000; 12:25:46.695 [planetinfo.record]: land_color = 0.66, 0.354049, 0.260391, 1; 12:25:46.696 [planetinfo.record]: land_fraction = 0.700000; 12:25:46.696 [planetinfo.record]: polar_cloud_color = 0.929785, 0.528566, 0.421309, 1; 12:25:46.696 [planetinfo.record]: polar_land_color = 0.934, 0.825758, 0.792623, 1; 12:25:46.696 [planetinfo.record]: polar_sea_color = 0.933984, 0.902617, 0.877435, 1; 12:25:46.696 [planetinfo.record]: sea_color = 0.660156, 0.571472, 0.500275, 1; 12:25:46.696 [planetinfo.record]: rotation_speed = 0.004483 12:25:46.696 [planetinfo.record]: planet zpos = 945000.000000 12:25:46.696 [planetinfo.record]: sun_radius = 194435.348511 12:25:46.696 [planetinfo.record]: sun_vector = -0.197 0.748 0.634 12:25:46.696 [planetinfo.record]: sun_distance = 1485000 12:25:46.696 [planetinfo.record]: corona_flare = 0.082713 12:25:46.696 [planetinfo.record]: corona_hues = 0.915588 12:25:46.696 [planetinfo.record]: sun_color = 0.759848, 0.746847, 0.736726, 1 12:25:46.696 [planetinfo.record]: corona_shimmer = 0.410869 12:25:46.696 [planetinfo.record]: station_vector = -0.999 -0.045 0.002 12:25:46.696 [planetinfo.record]: station = coriolis 12:25:51.478 [PLANETINFO OVER]: Done 12:25:51.479 [PLANETINFO LOGGING]: 2 104 12:25:52.488 [planetinfo.record]: seed = 65 178 205 148 12:25:52.488 [planetinfo.record]: coordinates = 178 136 12:25:52.488 [planetinfo.record]: government = 0; 12:25:52.488 [planetinfo.record]: economy = 2; 12:25:52.488 [planetinfo.record]: techlevel = 7; 12:25:52.488 [planetinfo.record]: population = 31; 12:25:52.488 [planetinfo.record]: productivity = 7936; 12:25:52.488 [planetinfo.record]: name = "Raave"; 12:25:52.488 [planetinfo.record]: inhabitant = "Black Horned Lizard"; 12:25:52.488 [planetinfo.record]: inhabitants = "Black Horned Lizards"; 12:25:52.488 [planetinfo.record]: description = "The planet Raave is a boring world."; 12:25:52.532 [planetinfo.record]: air_color = 0.885998, 0.777369, 0.995748, 1; 12:25:52.532 [planetinfo.record]: cloud_alpha = 1.000000; 12:25:52.532 [planetinfo.record]: cloud_color = 0.990234, 0.932213, 0.96077, 1; 12:25:52.532 [planetinfo.record]: cloud_fraction = 0.050000; 12:25:52.532 [planetinfo.record]: land_color = 0.451172, 0.454435, 0.66, 1; 12:25:52.532 [planetinfo.record]: land_fraction = 0.470000; 12:25:52.532 [planetinfo.record]: polar_cloud_color = 0.945605, 0.910976, 0.92802, 1; 12:25:52.532 [planetinfo.record]: polar_land_color = 0.860119, 0.861274, 0.934, 1; 12:25:52.532 [planetinfo.record]: polar_sea_color = 0.92413, 0.938086, 0.889807, 1; 12:25:52.532 [planetinfo.record]: sea_color = 0.582298, 0.619141, 0.491685, 1; 12:25:52.532 [planetinfo.record]: rotation_speed = 0.001657 12:25:52.532 [planetinfo.record]: planet zpos = 482160.000000 12:25:52.532 [planetinfo.record]: sun_radius = 97746.237183 12:25:52.533 [planetinfo.record]: sun_vector = -0.218 0.147 -0.965 12:25:52.533 [planetinfo.record]: sun_distance = 883960 12:25:52.539 [planetinfo.record]: corona_flare = 0.034035 12:25:52.540 [planetinfo.record]: corona_hues = 0.768517 12:25:52.540 [planetinfo.record]: sun_color = 0.658688, 0.656747, 0.646333, 1 12:25:52.540 [planetinfo.record]: corona_shimmer = 1.082595 12:25:52.540 [planetinfo.record]: station_vector = 0.242 0.967 0.076 12:25:52.540 [planetinfo.record]: station = coriolis 12:25:57.547 [PLANETINFO OVER]: Done 12:25:57.548 [PLANETINFO LOGGING]: 2 105 12:25:58.569 [planetinfo.record]: seed = 9 123 69 97 12:25:58.569 [planetinfo.record]: coordinates = 123 22 12:25:58.569 [planetinfo.record]: government = 1; 12:25:58.569 [planetinfo.record]: economy = 6; 12:25:58.569 [planetinfo.record]: techlevel = 5; 12:25:58.569 [planetinfo.record]: population = 28; 12:25:58.569 [planetinfo.record]: productivity = 4480; 12:25:58.569 [planetinfo.record]: name = "Leena"; 12:25:58.569 [planetinfo.record]: inhabitant = "Human Colonial"; 12:25:58.569 [planetinfo.record]: inhabitants = "Human Colonials"; 12:25:58.569 [planetinfo.record]: description = "This world is mildly well known for Leenaian shrew cutlet and the Leenaian tree wolf."; 12:25:58.582 [planetinfo.record]: air_color = 0.469816, 0.579848, 0.893637, 1; 12:25:58.582 [planetinfo.record]: cloud_alpha = 1.000000; 12:25:58.582 [planetinfo.record]: cloud_color = 0.120163, 0.55154, 0.683594, 1; 12:25:58.582 [planetinfo.record]: cloud_fraction = 0.170000; 12:25:58.582 [planetinfo.record]: land_color = 0.444082, 0.2475, 0.66, 1; 12:25:58.582 [planetinfo.record]: land_fraction = 0.520000; 12:25:58.582 [planetinfo.record]: polar_cloud_color = 0.391584, 0.710109, 0.807617, 1; 12:25:58.582 [planetinfo.record]: polar_land_color = 0.857611, 0.788063, 0.934, 1; 12:25:58.583 [planetinfo.record]: polar_sea_color = 0.930664, 0.843684, 0.841597, 1; 12:25:58.583 [planetinfo.record]: sea_color = 0.693359, 0.434154, 0.427933, 1; 12:25:58.583 [planetinfo.record]: rotation_speed = 0.003500 12:25:58.583 [planetinfo.record]: planet zpos = 479250.000000 12:25:58.583 [planetinfo.record]: sun_radius = 95008.543396 12:25:58.583 [planetinfo.record]: sun_vector = 0.055 0.886 0.460 12:25:58.583 [planetinfo.record]: sun_distance = 702900 12:25:58.583 [planetinfo.record]: corona_flare = 0.045135 12:25:58.583 [planetinfo.record]: corona_hues = 0.682587 12:25:58.583 [planetinfo.record]: sun_color = 0.367294, 0.844325, 0.849619, 1 12:25:58.591 [planetinfo.record]: corona_shimmer = 0.342360 12:25:58.592 [planetinfo.record]: station_vector = 0.341 -0.749 0.569 12:25:58.592 [planetinfo.record]: station = coriolis 12:26:02.956 [PLANETINFO OVER]: Done 12:26:02.957 [PLANETINFO LOGGING]: 2 106 12:26:03.977 [planetinfo.record]: seed = 121 35 173 229 12:26:03.977 [planetinfo.record]: coordinates = 35 207 12:26:03.977 [planetinfo.record]: government = 7; 12:26:03.977 [planetinfo.record]: economy = 7; 12:26:03.977 [planetinfo.record]: techlevel = 7; 12:26:03.977 [planetinfo.record]: population = 43; 12:26:03.977 [planetinfo.record]: productivity = 11352; 12:26:03.977 [planetinfo.record]: name = "Ceedleon"; 12:26:03.977 [planetinfo.record]: inhabitant = "Fierce Fat Humanoid"; 12:26:03.977 [planetinfo.record]: inhabitants = "Fierce Fat Humanoids"; 12:26:03.977 [planetinfo.record]: description = "Ceedleon is mildly notable for Ceedleonian Erzaaler water."; 12:26:03.998 [planetinfo.record]: air_color = 0.585701, 0.926156, 0.848736, 1; 12:26:03.998 [planetinfo.record]: cloud_alpha = 1.000000; 12:26:03.998 [planetinfo.record]: cloud_color = 0.78125, 0.516748, 0.387573, 1; 12:26:03.998 [planetinfo.record]: cloud_fraction = 0.270000; 12:26:03.998 [planetinfo.record]: land_color = 0.541406, 0.568275, 0.66, 1; 12:26:03.998 [planetinfo.record]: land_fraction = 0.330000; 12:26:03.998 [planetinfo.record]: polar_cloud_color = 0.851562, 0.671371, 0.58337, 1; 12:26:03.998 [planetinfo.record]: polar_land_color = 0.892043, 0.901549, 0.934, 1; 12:26:03.998 [planetinfo.record]: polar_sea_color = 0.946094, 0.927276, 0.727125, 1; 12:26:03.998 [planetinfo.record]: sea_color = 0.539062, 0.496175, 0.0400085, 1; 12:26:03.998 [planetinfo.record]: rotation_speed = 0.000110 12:26:03.998 [planetinfo.record]: planet zpos = 495720.000000 12:26:03.998 [planetinfo.record]: sun_radius = 103035.470581 12:26:03.998 [planetinfo.record]: sun_vector = -0.787 -0.345 0.511 12:26:03.998 [planetinfo.record]: sun_distance = 784890 12:26:03.999 [planetinfo.record]: corona_flare = 0.056264 12:26:03.999 [planetinfo.record]: corona_hues = 0.945595 12:26:03.999 [planetinfo.record]: sun_color = 0.747168, 0.578952, 0.565082, 1 12:26:03.999 [planetinfo.record]: corona_shimmer = 0.693019 12:26:03.999 [planetinfo.record]: station_vector = -0.250 -0.967 0.051 12:26:03.999 [planetinfo.record]: station = coriolis 12:26:08.196 [PLANETINFO OVER]: Done 12:26:08.197 [PLANETINFO LOGGING]: 2 107 12:26:09.200 [planetinfo.record]: seed = 161 159 149 89 12:26:09.200 [planetinfo.record]: coordinates = 159 225 12:26:09.200 [planetinfo.record]: government = 4; 12:26:09.200 [planetinfo.record]: economy = 1; 12:26:09.200 [planetinfo.record]: techlevel = 11; 12:26:09.200 [planetinfo.record]: population = 50; 12:26:09.200 [planetinfo.record]: productivity = 28800; 12:26:09.200 [planetinfo.record]: name = "Orqube"; 12:26:09.200 [planetinfo.record]: inhabitant = "Yellow Insect"; 12:26:09.200 [planetinfo.record]: inhabitants = "Yellow Insects"; 12:26:09.200 [planetinfo.record]: description = "This world is ravaged by dreadful civil war."; 12:26:09.231 [planetinfo.record]: air_color = 0.566976, 0.903281, 0.992496, 1; 12:26:09.231 [planetinfo.record]: cloud_alpha = 1.000000; 12:26:09.232 [planetinfo.record]: cloud_color = 0.548641, 0.980469, 0.306396, 1; 12:26:09.232 [planetinfo.record]: cloud_fraction = 0.610000; 12:26:09.232 [planetinfo.record]: land_color = 0.66, 0.154688, 0.462612, 1; 12:26:09.232 [planetinfo.record]: land_fraction = 0.320000; 12:26:09.232 [planetinfo.record]: polar_cloud_color = 0.682125, 0.941211, 0.536784, 1; 12:26:09.232 [planetinfo.record]: polar_land_color = 0.934, 0.755227, 0.864167, 1; 12:26:09.232 [planetinfo.record]: polar_sea_color = 0.94375, 0.891132, 0.888544, 1; 12:26:09.232 [planetinfo.record]: sea_color = 0.5625, 0.437053, 0.430884, 1; 12:26:09.232 [planetinfo.record]: rotation_speed = 0.003814 12:26:09.232 [planetinfo.record]: planet zpos = 686270.000000 12:26:09.232 [planetinfo.record]: sun_radius = 137984.921112 12:26:09.232 [planetinfo.record]: sun_vector = -0.117 -0.858 0.500 12:26:09.232 [planetinfo.record]: sun_distance = 1214170 12:26:09.232 [planetinfo.record]: corona_flare = 0.002609 12:26:09.232 [planetinfo.record]: corona_hues = 0.577919 12:26:09.232 [planetinfo.record]: sun_color = 0.484542, 0.650427, 0.826898, 1 12:26:09.233 [planetinfo.record]: corona_shimmer = 0.360651 12:26:09.233 [planetinfo.record]: station_vector = 0.423 0.597 0.682 12:26:09.233 [planetinfo.record]: station = dodecahedron 12:26:14.632 [PLANETINFO OVER]: Done 12:26:14.633 [PLANETINFO LOGGING]: 2 108 12:26:15.647 [planetinfo.record]: seed = 81 8 13 183 12:26:15.647 [planetinfo.record]: coordinates = 8 211 12:26:15.647 [planetinfo.record]: government = 2; 12:26:15.648 [planetinfo.record]: economy = 3; 12:26:15.648 [planetinfo.record]: techlevel = 5; 12:26:15.648 [planetinfo.record]: population = 26; 12:26:15.648 [planetinfo.record]: productivity = 8736; 12:26:15.648 [planetinfo.record]: name = "Tibeenis"; 12:26:15.648 [planetinfo.record]: inhabitant = "Human Colonial"; 12:26:15.648 [planetinfo.record]: inhabitants = "Human Colonials"; 12:26:15.648 [planetinfo.record]: description = "This planet is most notable for its fabulous It brandy but ravaged by frequent earthquakes."; 12:26:15.651 [planetinfo.record]: air_color = 0.444199, 0.555429, 0.9151, 1; 12:26:15.651 [planetinfo.record]: cloud_alpha = 1.000000; 12:26:15.651 [planetinfo.record]: cloud_color = 0.0409088, 0.532591, 0.748047, 1; 12:26:15.651 [planetinfo.record]: cloud_fraction = 0.370000; 12:26:15.651 [planetinfo.record]: land_color = 0.66, 0.04125, 0.389297, 1; 12:26:15.651 [planetinfo.record]: land_fraction = 0.710000; 12:26:15.651 [planetinfo.record]: polar_cloud_color = 0.342328, 0.686016, 0.836621, 1; 12:26:15.651 [planetinfo.record]: polar_land_color = 0.934, 0.715094, 0.838229, 1; 12:26:15.651 [planetinfo.record]: polar_sea_color = 0.937891, 0.937072, 0.885501, 1; 12:26:15.651 [planetinfo.record]: sea_color = 0.621094, 0.618925, 0.482318, 1; 12:26:15.651 [planetinfo.record]: rotation_speed = 0.004772 12:26:15.651 [planetinfo.record]: planet zpos = 507760.000000 12:26:15.651 [planetinfo.record]: sun_radius = 136278.919678 12:26:15.651 [planetinfo.record]: sun_vector = 0.133 0.881 -0.455 12:26:15.651 [planetinfo.record]: sun_distance = 738560 12:26:15.651 [planetinfo.record]: corona_flare = 0.050974 12:26:15.651 [planetinfo.record]: corona_hues = 0.555237 12:26:15.651 [planetinfo.record]: sun_color = 0.740036, 0.633377, 0.621469, 1 12:26:15.651 [planetinfo.record]: corona_shimmer = 0.706237 12:26:15.652 [planetinfo.record]: station_vector = -0.173 -0.984 0.051 12:26:15.652 [planetinfo.record]: station = coriolis 12:26:20.492 [PLANETINFO OVER]: Done 12:26:20.493 [PLANETINFO LOGGING]: 2 109 12:26:21.507 [planetinfo.record]: seed = 25 157 37 131 12:26:21.507 [planetinfo.record]: coordinates = 157 82 12:26:21.507 [planetinfo.record]: government = 3; 12:26:21.507 [planetinfo.record]: economy = 2; 12:26:21.507 [planetinfo.record]: techlevel = 8; 12:26:21.508 [planetinfo.record]: population = 38; 12:26:21.508 [planetinfo.record]: productivity = 17024; 12:26:21.508 [planetinfo.record]: name = "Geenbe"; 12:26:21.508 [planetinfo.record]: inhabitant = "Human Colonial"; 12:26:21.508 [planetinfo.record]: inhabitants = "Human Colonials"; 12:26:21.508 [planetinfo.record]: description = "Geenbe is famous for its inhabitants’ ancient mating traditions but cursed by vicious killer cats."; 12:26:21.516 [planetinfo.record]: air_color = 0.766207, 0.634651, 0.857215, 1; 12:26:21.517 [planetinfo.record]: cloud_alpha = 1.000000; 12:26:21.517 [planetinfo.record]: cloud_color = 0.574219, 0.471039, 0.492803, 1; 12:26:21.517 [planetinfo.record]: cloud_fraction = 0.120000; 12:26:21.517 [planetinfo.record]: land_color = 0.613281, 0.37851, 0.598608, 1; 12:26:21.517 [planetinfo.record]: land_fraction = 0.560000; 12:26:21.517 [planetinfo.record]: polar_cloud_color = 0.758398, 0.673227, 0.691193, 1; 12:26:21.517 [planetinfo.record]: polar_land_color = 0.938672, 0.848838, 0.933057, 1; 12:26:21.517 [planetinfo.record]: polar_sea_color = 0.931836, 0.896545, 0.865406, 1; 12:26:21.517 [planetinfo.record]: sea_color = 0.681641, 0.578379, 0.487267, 1; 12:26:21.517 [planetinfo.record]: rotation_speed = 0.000396 12:26:21.517 [planetinfo.record]: planet zpos = 486330.000000 12:26:21.517 [planetinfo.record]: sun_radius = 88475.426331 12:26:21.518 [planetinfo.record]: sun_vector = -0.291 0.740 -0.606 12:26:21.518 [planetinfo.record]: sun_distance = 785610 12:26:21.518 [planetinfo.record]: corona_flare = 0.096753 12:26:21.518 [planetinfo.record]: corona_hues = 0.643341 12:26:21.518 [planetinfo.record]: sun_color = 0.530654, 0.647455, 0.663809, 1 12:26:21.518 [planetinfo.record]: corona_shimmer = 0.472019 12:26:21.518 [planetinfo.record]: station_vector = -0.350 0.936 0.030 12:26:21.518 [planetinfo.record]: station = coriolis 12:26:25.984 [PLANETINFO OVER]: Done 12:26:25.985 [PLANETINFO LOGGING]: 2 110 12:26:26.988 [planetinfo.record]: seed = 73 137 109 143 12:26:26.988 [planetinfo.record]: coordinates = 137 147 12:26:26.988 [planetinfo.record]: government = 1; 12:26:26.988 [planetinfo.record]: economy = 3; 12:26:26.988 [planetinfo.record]: techlevel = 6; 12:26:26.988 [planetinfo.record]: population = 29; 12:26:26.988 [planetinfo.record]: productivity = 8120; 12:26:26.988 [planetinfo.record]: name = "Amaza"; 12:26:26.988 [planetinfo.record]: inhabitant = "Human Colonial"; 12:26:26.988 [planetinfo.record]: inhabitants = "Human Colonials"; 12:26:26.988 [planetinfo.record]: description = "Amaza is reasonably notable for its great parking meters but ravaged by occasional earthquakes."; 12:26:27.012 [planetinfo.record]: air_color = 0.685693, 0.850452, 0.896238, 1; 12:26:27.012 [planetinfo.record]: cloud_alpha = 1.000000; 12:26:27.012 [planetinfo.record]: cloud_color = 0.642982, 0.691406, 0.618484, 1; 12:26:27.012 [planetinfo.record]: cloud_fraction = 0.280000; 12:26:27.012 [planetinfo.record]: land_color = 0.309939, 0.284729, 0.607422, 1; 12:26:27.012 [planetinfo.record]: land_fraction = 0.480000; 12:26:27.012 [planetinfo.record]: polar_cloud_color = 0.775627, 0.811133, 0.757665, 1; 12:26:27.012 [planetinfo.record]: polar_land_color = 0.824258, 0.814513, 0.939258, 1; 12:26:27.012 [planetinfo.record]: polar_sea_color = 0.881481, 0.930078, 0.87867, 1; 12:26:27.012 [planetinfo.record]: sea_color = 0.55308, 0.699219, 0.544626, 1; 12:26:27.012 [planetinfo.record]: rotation_speed = 0.000711 12:26:27.012 [planetinfo.record]: planet zpos = 747230.000000 12:26:27.012 [planetinfo.record]: sun_radius = 148157.386475 12:26:27.012 [planetinfo.record]: sun_vector = -0.523 -0.424 0.739 12:26:27.012 [planetinfo.record]: sun_distance = 1494460 12:26:27.012 [planetinfo.record]: corona_flare = 0.093571 12:26:27.012 [planetinfo.record]: corona_hues = 0.680405 12:26:27.012 [planetinfo.record]: sun_color = 0.51436, 0.752691, 0.794351, 1 12:26:27.013 [planetinfo.record]: corona_shimmer = 0.234834 12:26:27.013 [planetinfo.record]: station_vector = 0.110 -0.993 0.047 12:26:27.013 [planetinfo.record]: station = coriolis 12:26:32.098 [PLANETINFO OVER]: Done 12:26:32.099 [PLANETINFO LOGGING]: 2 111 12:26:33.109 [planetinfo.record]: seed = 241 255 117 112 12:26:33.109 [planetinfo.record]: coordinates = 255 196 12:26:33.109 [planetinfo.record]: government = 6; 12:26:33.109 [planetinfo.record]: economy = 4; 12:26:33.109 [planetinfo.record]: techlevel = 9; 12:26:33.109 [planetinfo.record]: population = 47; 12:26:33.109 [planetinfo.record]: productivity = 22560; 12:26:33.109 [planetinfo.record]: name = "Erlace"; 12:26:33.109 [planetinfo.record]: inhabitant = "Human Colonial"; 12:26:33.109 [planetinfo.record]: inhabitants = "Human Colonials"; 12:26:33.109 [planetinfo.record]: description = "The world Erlace is scourged by killer edible arts graduates."; 12:26:33.182 [planetinfo.record]: air_color = 0.619489, 0.456537, 0.969082, 1; 12:26:33.182 [planetinfo.record]: cloud_alpha = 1.000000; 12:26:33.182 [planetinfo.record]: cloud_color = 0.882047, 0.0106659, 0.910156, 1; 12:26:33.182 [planetinfo.record]: cloud_fraction = 0.110000; 12:26:33.182 [planetinfo.record]: land_color = 0.66, 0.328973, 0.213984, 1; 12:26:33.183 [planetinfo.record]: land_fraction = 0.400000; 12:26:33.183 [planetinfo.record]: polar_cloud_color = 0.892013, 0.347751, 0.90957, 1; 12:26:33.183 [planetinfo.record]: polar_land_color = 0.934, 0.816887, 0.776205, 1; 12:26:33.183 [planetinfo.record]: polar_sea_color = 0.869885, 0.924219, 0.891109, 1; 12:26:33.183 [planetinfo.record]: sea_color = 0.579608, 0.757812, 0.649219, 1; 12:26:33.183 [planetinfo.record]: rotation_speed = 0.003214 12:26:33.183 [planetinfo.record]: planet zpos = 307100.000000 12:26:33.183 [planetinfo.record]: sun_radius = 83671.348114 12:26:33.183 [planetinfo.record]: sun_vector = -0.932 0.308 -0.189 12:26:33.183 [planetinfo.record]: sun_distance = 583490 12:26:33.183 [planetinfo.record]: corona_flare = 0.037383 12:26:33.183 [planetinfo.record]: corona_hues = 0.508118 12:26:33.183 [planetinfo.record]: sun_color = 0.540555, 0.673841, 0.840555, 1 12:26:33.184 [planetinfo.record]: corona_shimmer = 0.279045 12:26:33.184 [planetinfo.record]: station_vector = 0.837 0.393 0.381 12:26:33.184 [planetinfo.record]: station = coriolis 12:26:37.942 [PLANETINFO OVER]: Done 12:26:37.943 [PLANETINFO LOGGING]: 2 112 12:26:38.952 [planetinfo.record]: seed = 225 74 77 37 12:26:38.952 [planetinfo.record]: coordinates = 74 165 12:26:38.952 [planetinfo.record]: government = 4; 12:26:38.952 [planetinfo.record]: economy = 5; 12:26:38.952 [planetinfo.record]: techlevel = 6; 12:26:38.952 [planetinfo.record]: population = 34; 12:26:38.952 [planetinfo.record]: productivity = 10880; 12:26:38.952 [planetinfo.record]: name = "Celace"; 12:26:38.952 [planetinfo.record]: inhabitant = "Human Colonial"; 12:26:38.952 [planetinfo.record]: inhabitants = "Human Colonials"; 12:26:38.952 [planetinfo.record]: description = "The world Celace is very noted for its pink Celaceian Ondiab Ondiabweed plantations but scourged by deadly solar activity."; 12:26:38.956 [planetinfo.record]: air_color = 0.687796, 0.577819, 0.879328, 1; 12:26:38.956 [planetinfo.record]: cloud_alpha = 1.000000; 12:26:38.956 [planetinfo.record]: cloud_color = 0.640625, 0.365356, 0.610518, 1; 12:26:38.956 [planetinfo.record]: cloud_fraction = 0.190000; 12:26:38.956 [planetinfo.record]: land_color = 0.9, 0.72, 0.828, 1; 12:26:38.956 [planetinfo.record]: land_fraction = 0.400000; 12:26:38.956 [planetinfo.record]: polar_cloud_color = 0.788281, 0.576585, 0.765127, 1; 12:26:38.956 [planetinfo.record]: polar_land_color = 0.91, 0.8645, 0.8918, 1; 12:26:38.956 [planetinfo.record]: polar_sea_color = 0.90081, 0.927148, 0.865852, 1; 12:26:38.956 [planetinfo.record]: sea_color = 0.645733, 0.728516, 0.535857, 1; 12:26:38.956 [planetinfo.record]: rotation_speed = 0.002840 12:26:38.957 [planetinfo.record]: planet zpos = 500400.000000 12:26:38.957 [planetinfo.record]: sun_radius = 81538.847351 12:26:38.957 [planetinfo.record]: sun_vector = -0.613 -0.350 -0.708 12:26:38.957 [planetinfo.record]: sun_distance = 834000 12:26:38.957 [planetinfo.record]: corona_flare = 0.079953 12:26:38.957 [planetinfo.record]: corona_hues = 0.810036 12:26:38.957 [planetinfo.record]: sun_color = 0.636577, 0.668786, 0.719446, 1 12:26:38.957 [planetinfo.record]: corona_shimmer = 1.109651 12:26:38.957 [planetinfo.record]: station_vector = -0.584 -0.545 0.602 12:26:38.957 [planetinfo.record]: station = coriolis 12:26:43.411 [PLANETINFO OVER]: Done 12:26:43.411 [PLANETINFO LOGGING]: 2 113 12:26:44.419 [planetinfo.record]: seed = 169 192 5 92 12:26:44.419 [planetinfo.record]: coordinates = 192 133 12:26:44.419 [planetinfo.record]: government = 5; 12:26:44.419 [planetinfo.record]: economy = 5; 12:26:44.419 [planetinfo.record]: techlevel = 5; 12:26:44.419 [planetinfo.record]: population = 31; 12:26:44.419 [planetinfo.record]: productivity = 11160; 12:26:44.419 [planetinfo.record]: name = "Texeonis"; 12:26:44.419 [planetinfo.record]: inhabitant = "Human Colonial"; 12:26:44.419 [planetinfo.record]: inhabitants = "Human Colonials"; 12:26:44.419 [planetinfo.record]: description = "Texeonis is a revolting dump."; 12:26:44.432 [planetinfo.record]: air_color = 0.845752, 0.770794, 0.980789, 1; 12:26:44.432 [planetinfo.record]: cloud_alpha = 1.000000; 12:26:44.432 [planetinfo.record]: cloud_color = 0.945312, 0.904694, 0.941822, 1; 12:26:44.432 [planetinfo.record]: cloud_fraction = 0.190000; 12:26:44.432 [planetinfo.record]: land_color = 0.611896, 0.613281, 0.60849, 1; 12:26:44.432 [planetinfo.record]: land_fraction = 0.520000; 12:26:44.432 [planetinfo.record]: polar_cloud_color = 0.925391, 0.900539, 0.923255, 1; 12:26:44.432 [planetinfo.record]: polar_land_color = 0.938142, 0.938672, 0.936839, 1; 12:26:44.432 [planetinfo.record]: polar_sea_color = 0.868592, 0.914648, 0.807463, 1; 12:26:44.432 [planetinfo.record]: sea_color = 0.681604, 0.853516, 0.45343, 1; 12:26:44.432 [planetinfo.record]: rotation_speed = 0.002258 12:26:44.432 [planetinfo.record]: planet zpos = 668800.000000 12:26:44.432 [planetinfo.record]: sun_radius = 140456.201172 12:26:44.432 [planetinfo.record]: sun_vector = -0.360 -0.868 -0.342 12:26:44.432 [planetinfo.record]: sun_distance = 1155200 12:26:44.432 [planetinfo.record]: corona_flare = 0.026360 12:26:44.432 [planetinfo.record]: corona_hues = 0.605438 12:26:44.432 [planetinfo.record]: sun_color = 0.849893, 0.788277, 0.358163, 1 12:26:44.433 [planetinfo.record]: corona_shimmer = 0.436517 12:26:44.433 [planetinfo.record]: station_vector = 0.917 0.385 0.103 12:26:44.433 [planetinfo.record]: station = coriolis 12:26:49.283 [PLANETINFO OVER]: Done 12:26:49.285 [PLANETINFO LOGGING]: 2 114 12:26:50.287 [planetinfo.record]: seed = 153 189 45 31 12:26:50.287 [planetinfo.record]: coordinates = 189 191 12:26:50.287 [planetinfo.record]: government = 3; 12:26:50.287 [planetinfo.record]: economy = 7; 12:26:50.287 [planetinfo.record]: techlevel = 3; 12:26:50.287 [planetinfo.record]: population = 23; 12:26:50.287 [planetinfo.record]: productivity = 3864; 12:26:50.287 [planetinfo.record]: name = "Onaned"; 12:26:50.287 [planetinfo.record]: inhabitant = "Human Colonial"; 12:26:50.287 [planetinfo.record]: inhabitants = "Human Colonials"; 12:26:50.287 [planetinfo.record]: description = "This world is most fabled for its inhabitants’ eccentric shyness but cursed by unpredictable solar activity."; 12:26:50.304 [planetinfo.record]: air_color = 0.420717, 0.81842, 0.855264, 1; 12:26:50.304 [planetinfo.record]: cloud_alpha = 1.000000; 12:26:50.304 [planetinfo.record]: cloud_color = 0.428594, 0.568359, 0.0421829, 1; 12:26:50.304 [planetinfo.record]: cloud_fraction = 0.220000; 12:26:50.304 [planetinfo.record]: land_color = 0.576172, 0.522894, 0.508652, 1; 12:26:50.304 [planetinfo.record]: land_fraction = 0.520000; 12:26:50.304 [planetinfo.record]: polar_cloud_color = 0.639606, 0.755762, 0.318468, 1; 12:26:50.304 [planetinfo.record]: polar_land_color = 0.942383, 0.920598, 0.914774, 1; 12:26:50.304 [planetinfo.record]: polar_sea_color = 0.854924, 0.915039, 0.817637, 1; 12:26:50.304 [planetinfo.record]: sea_color = 0.626343, 0.849609, 0.487862, 1; 12:26:50.304 [planetinfo.record]: rotation_speed = 0.001292 12:26:50.304 [planetinfo.record]: planet zpos = 889850.000000 12:26:50.304 [planetinfo.record]: sun_radius = 165586.206818 12:26:50.304 [planetinfo.record]: sun_vector = 0.195 0.818 0.542 12:26:50.304 [planetinfo.record]: sun_distance = 1574350 12:26:50.304 [planetinfo.record]: corona_flare = 0.066864 12:26:50.305 [planetinfo.record]: corona_hues = 0.823196 12:26:50.305 [planetinfo.record]: sun_color = 0.847018, 0.81288, 0.477459, 1 12:26:50.305 [planetinfo.record]: corona_shimmer = 0.894201 12:26:50.305 [planetinfo.record]: station_vector = -0.277 -0.185 0.943 12:26:50.305 [planetinfo.record]: station = coriolis 12:26:55.437 [PLANETINFO OVER]: Done 12:26:55.438 [PLANETINFO LOGGING]: 2 115 12:26:56.456 [planetinfo.record]: seed = 193 51 85 72 12:26:56.456 [planetinfo.record]: coordinates = 51 120 12:26:56.456 [planetinfo.record]: government = 0; 12:26:56.456 [planetinfo.record]: economy = 2; 12:26:56.456 [planetinfo.record]: techlevel = 8; 12:26:56.456 [planetinfo.record]: population = 35; 12:26:56.456 [planetinfo.record]: productivity = 8960; 12:26:56.456 [planetinfo.record]: name = "Usraer"; 12:26:56.456 [planetinfo.record]: inhabitant = "Human Colonial"; 12:26:56.456 [planetinfo.record]: inhabitants = "Human Colonials"; 12:26:56.456 [planetinfo.record]: description = "The world Usraer is very noted for its pink oceans but cursed by unpredictable earthquakes."; 12:26:56.464 [planetinfo.record]: air_color = 0.449745, 0.454549, 0.936563, 1; 12:26:56.464 [planetinfo.record]: cloud_alpha = 1.000000; 12:26:56.464 [planetinfo.record]: cloud_color = 0.0317383, 0.0439377, 0.8125, 1; 12:26:56.465 [planetinfo.record]: cloud_fraction = 0.510000; 12:26:56.465 [planetinfo.record]: land_color = 0.2, 0.62, 0.8, 1; 12:26:56.465 [planetinfo.record]: land_fraction = 0.510000; 12:26:56.465 [planetinfo.record]: polar_cloud_color = 0.345743, 0.353866, 0.865625, 1; 12:26:56.465 [planetinfo.record]: polar_land_color = 0.7475, 0.86825, 0.92, 1; 12:26:56.465 [planetinfo.record]: polar_sea_color = 0.91, 0.807625, 0.826053, 1; 12:26:56.465 [planetinfo.record]: sea_color = 0.9, 0.495, 0.5679, 1; 12:26:56.465 [planetinfo.record]: rotation_speed = 0.002540 12:26:56.466 [planetinfo.record]: planet zpos = 540650.000000 12:26:56.466 [planetinfo.record]: sun_radius = 126272.361755 12:26:56.466 [planetinfo.record]: sun_vector = -0.487 0.836 -0.254 12:26:56.466 [planetinfo.record]: sun_distance = 983000 12:26:56.466 [planetinfo.record]: corona_flare = 0.019888 12:26:56.466 [planetinfo.record]: corona_hues = 0.522659 12:26:56.466 [planetinfo.record]: sun_color = 0.783038, 0.759845, 0.634167, 1 12:26:56.466 [planetinfo.record]: corona_shimmer = 0.458602 12:26:56.467 [planetinfo.record]: station_vector = 0.983 -0.147 0.112 12:26:56.467 [planetinfo.record]: station = coriolis 12:27:01.046 [PLANETINFO OVER]: Done 12:27:01.047 [PLANETINFO LOGGING]: 2 116 12:27:02.055 [planetinfo.record]: seed = 241 173 141 19 12:27:02.055 [planetinfo.record]: coordinates = 173 112 12:27:02.055 [planetinfo.record]: government = 6; 12:27:02.055 [planetinfo.record]: economy = 0; 12:27:02.055 [planetinfo.record]: techlevel = 11; 12:27:02.055 [planetinfo.record]: population = 51; 12:27:02.055 [planetinfo.record]: productivity = 40800; 12:27:02.055 [planetinfo.record]: name = "Beenbeor"; 12:27:02.055 [planetinfo.record]: inhabitant = "Green Furry Rodent"; 12:27:02.055 [planetinfo.record]: inhabitants = "Green Furry Rodents"; 12:27:02.055 [planetinfo.record]: description = "This planet is beset by deadly earthquakes."; 12:27:02.075 [planetinfo.record]: air_color = 0.671688, 0.810738, 0.94892, 1; 12:27:02.076 [planetinfo.record]: cloud_alpha = 1.000000; 12:27:02.076 [planetinfo.record]: cloud_color = 0.613976, 0.849609, 0.733633, 1; 12:27:02.076 [planetinfo.record]: cloud_fraction = 0.450000; 12:27:02.076 [planetinfo.record]: land_color = 0.129593, 0.582031, 0.263911, 1; 12:27:02.076 [planetinfo.record]: land_fraction = 0.250000; 12:27:02.076 [planetinfo.record]: polar_cloud_color = 0.729382, 0.882324, 0.807048, 1; 12:27:02.076 [planetinfo.record]: polar_land_color = 0.758772, 0.941797, 0.813107, 1; 12:27:02.076 [planetinfo.record]: polar_sea_color = 0.94707, 0.89167, 0.909849, 1; 12:27:02.076 [planetinfo.record]: sea_color = 0.529297, 0.40545, 0.446087, 1; 12:27:02.076 [planetinfo.record]: rotation_speed = 0.000972 12:27:02.076 [planetinfo.record]: planet zpos = 563550.000000 12:27:02.076 [planetinfo.record]: sun_radius = 125630.993500 12:27:02.076 [planetinfo.record]: sun_vector = 0.857 -0.343 0.384 12:27:02.076 [planetinfo.record]: sun_distance = 826540 12:27:02.076 [planetinfo.record]: corona_flare = 0.095317 12:27:02.076 [planetinfo.record]: corona_hues = 0.615395 12:27:02.076 [planetinfo.record]: sun_color = 0.18506, 0.281179, 0.701025, 1 12:27:02.077 [planetinfo.record]: corona_shimmer = 0.411090 12:27:02.077 [planetinfo.record]: station_vector = -0.967 0.243 0.072 12:27:02.077 [planetinfo.record]: station = dodecahedron 12:27:06.663 [PLANETINFO OVER]: Done 12:27:06.664 [PLANETINFO LOGGING]: 2 117 12:27:07.669 [planetinfo.record]: seed = 185 57 229 95 12:27:07.669 [planetinfo.record]: coordinates = 57 243 12:27:07.669 [planetinfo.record]: government = 7; 12:27:07.669 [planetinfo.record]: economy = 3; 12:27:07.669 [planetinfo.record]: techlevel = 9; 12:27:07.669 [planetinfo.record]: population = 47; 12:27:07.669 [planetinfo.record]: productivity = 28952; 12:27:07.669 [planetinfo.record]: name = "Ondisora"; 12:27:07.669 [planetinfo.record]: inhabitant = "Yellow Horned Humanoid"; 12:27:07.669 [planetinfo.record]: inhabitants = "Yellow Horned Humanoids"; 12:27:07.669 [planetinfo.record]: description = "This planet is a dull world."; 12:27:07.686 [planetinfo.record]: air_color = 0.45904, 0.463695, 0.930709, 1; 12:27:07.686 [planetinfo.record]: cloud_alpha = 1.000000; 12:27:07.686 [planetinfo.record]: cloud_color = 0.0621033, 0.0735536, 0.794922, 1; 12:27:07.686 [planetinfo.record]: cloud_fraction = 0.340000; 12:27:07.687 [planetinfo.record]: land_color = 0.630859, 0.518079, 0.48793, 1; 12:27:07.687 [planetinfo.record]: land_fraction = 0.330000; 12:27:07.687 [planetinfo.record]: polar_cloud_color = 0.363524, 0.371245, 0.857715, 1; 12:27:07.687 [planetinfo.record]: polar_land_color = 0.936914, 0.895041, 0.883847, 1; 12:27:07.687 [planetinfo.record]: polar_sea_color = 0.760962, 0.937695, 0.925269, 1; 12:27:07.687 [planetinfo.record]: sea_color = 0.153328, 0.623047, 0.59002, 1; 12:27:07.687 [planetinfo.record]: rotation_speed = 0.004114 12:27:07.687 [planetinfo.record]: planet zpos = 671300.000000 12:27:07.687 [planetinfo.record]: sun_radius = 189878.663635 12:27:07.687 [planetinfo.record]: sun_vector = -0.533 0.842 -0.083 12:27:07.687 [planetinfo.record]: sun_distance = 1342600 12:27:07.687 [planetinfo.record]: corona_flare = 0.019049 12:27:07.687 [planetinfo.record]: corona_hues = 0.604431 12:27:07.687 [planetinfo.record]: sun_color = 0.789401, 0.785184, 0.777693, 1 12:27:07.688 [planetinfo.record]: corona_shimmer = 0.389641 12:27:07.688 [planetinfo.record]: station_vector = 0.051 0.990 0.133 12:27:07.688 [planetinfo.record]: station = coriolis 12:27:12.900 [PLANETINFO OVER]: Done 12:27:12.901 [PLANETINFO LOGGING]: 2 118 12:27:13.911 [planetinfo.record]: seed = 105 20 237 200 12:27:13.911 [planetinfo.record]: coordinates = 20 39 12:27:13.912 [planetinfo.record]: government = 5; 12:27:13.912 [planetinfo.record]: economy = 7; 12:27:13.912 [planetinfo.record]: techlevel = 3; 12:27:13.912 [planetinfo.record]: population = 25; 12:27:13.912 [planetinfo.record]: productivity = 5400; 12:27:13.912 [planetinfo.record]: name = "Uszale"; 12:27:13.912 [planetinfo.record]: inhabitant = "Small Bony Lobster"; 12:27:13.912 [planetinfo.record]: inhabitants = "Small Bony Lobsters"; 12:27:13.912 [planetinfo.record]: description = "This world is a revolting little planet."; 12:27:13.924 [planetinfo.record]: air_color = 0.685269, 0.859166, 0.853552, 1; 12:27:13.924 [planetinfo.record]: cloud_alpha = 1.000000; 12:27:13.924 [planetinfo.record]: cloud_color = 0.580078, 0.579689, 0.575546, 1; 12:27:13.924 [planetinfo.record]: cloud_fraction = 0.440000; 12:27:13.924 [planetinfo.record]: land_color = 0.597239, 0.66, 0.562031, 1; 12:27:13.924 [planetinfo.record]: land_fraction = 0.530000; 12:27:13.924 [planetinfo.record]: polar_cloud_color = 0.761035, 0.760716, 0.757319, 1; 12:27:13.924 [planetinfo.record]: polar_land_color = 0.911796, 0.934, 0.89934, 1; 12:27:13.924 [planetinfo.record]: polar_sea_color = 0.873943, 0.898681, 0.920508, 1; 12:27:13.924 [planetinfo.record]: sea_color = 0.634074, 0.719525, 0.794922, 1; 12:27:13.924 [planetinfo.record]: rotation_speed = 0.001969 12:27:13.924 [planetinfo.record]: planet zpos = 488400.000000 12:27:13.925 [planetinfo.record]: sun_radius = 150779.042358 12:27:13.925 [planetinfo.record]: sun_vector = 0.157 -0.802 0.576 12:27:13.925 [planetinfo.record]: sun_distance = 830280 12:27:13.925 [planetinfo.record]: corona_flare = 0.086133 12:27:13.925 [planetinfo.record]: corona_hues = 0.835892 12:27:13.925 [planetinfo.record]: sun_color = 0.319056, 0.580039, 0.767706, 1 12:27:13.925 [planetinfo.record]: corona_shimmer = 0.377938 12:27:13.925 [planetinfo.record]: station_vector = -0.366 0.787 0.497 12:27:13.925 [planetinfo.record]: station = coriolis 12:27:18.627 [PLANETINFO OVER]: Done 12:27:18.627 [PLANETINFO LOGGING]: 2 119 12:27:19.635 [planetinfo.record]: seed = 17 175 53 149 12:27:19.636 [planetinfo.record]: coordinates = 175 225 12:27:19.636 [planetinfo.record]: government = 2; 12:27:19.636 [planetinfo.record]: economy = 1; 12:27:19.636 [planetinfo.record]: techlevel = 10; 12:27:19.636 [planetinfo.record]: population = 44; 12:27:19.636 [planetinfo.record]: productivity = 19008; 12:27:19.636 [planetinfo.record]: name = "Labiar"; 12:27:19.636 [planetinfo.record]: inhabitant = "Human Colonial"; 12:27:19.636 [planetinfo.record]: inhabitants = "Human Colonials"; 12:27:19.636 [planetinfo.record]: description = "The planet Labiar is a boring planet."; 12:27:19.656 [planetinfo.record]: air_color = 0.773454, 0.722616, 0.997049, 1; 12:27:19.656 [planetinfo.record]: cloud_alpha = 1.000000; 12:27:19.656 [planetinfo.record]: cloud_color = 0.89736, 0.768906, 0.994141, 1; 12:27:19.656 [planetinfo.record]: cloud_fraction = 0.130000; 12:27:19.656 [planetinfo.record]: land_color = 0.66, 0.515484, 0.208828, 1; 12:27:19.656 [planetinfo.record]: land_fraction = 0.440000; 12:27:19.656 [planetinfo.record]: polar_cloud_color = 0.889722, 0.813215, 0.947363, 1; 12:27:19.656 [planetinfo.record]: polar_land_color = 0.934, 0.882872, 0.774381, 1; 12:27:19.656 [planetinfo.record]: polar_sea_color = 0.917907, 0.931836, 0.874324, 1; 12:27:19.656 [planetinfo.record]: sea_color = 0.640885, 0.681641, 0.513361, 1; 12:27:19.656 [planetinfo.record]: rotation_speed = 0.001970 12:27:19.656 [planetinfo.record]: planet zpos = 640650.000000 12:27:19.656 [planetinfo.record]: sun_radius = 111977.544098 12:27:19.656 [planetinfo.record]: sun_vector = 0.107 0.583 -0.805 12:27:19.656 [planetinfo.record]: sun_distance = 811490 12:27:19.656 [planetinfo.record]: corona_flare = 0.068962 12:27:19.656 [planetinfo.record]: corona_hues = 0.704445 12:27:19.656 [planetinfo.record]: sun_color = 0.836615, 0.748146, 0.70223, 1 12:27:19.657 [planetinfo.record]: corona_shimmer = 0.375487 12:27:19.657 [planetinfo.record]: station_vector = -0.828 0.487 0.276 12:27:19.657 [planetinfo.record]: station = coriolis 12:27:24.493 [PLANETINFO OVER]: Done 12:27:24.494 [PLANETINFO LOGGING]: 2 120 12:27:25.497 [planetinfo.record]: seed = 129 37 205 181 12:27:25.497 [planetinfo.record]: coordinates = 37 106 12:27:25.497 [planetinfo.record]: government = 0; 12:27:25.497 [planetinfo.record]: economy = 2; 12:27:25.497 [planetinfo.record]: techlevel = 6; 12:27:25.497 [planetinfo.record]: population = 27; 12:27:25.497 [planetinfo.record]: productivity = 6912; 12:27:25.497 [planetinfo.record]: name = "Lacete"; 12:27:25.497 [planetinfo.record]: inhabitant = "Harmless Rodent"; 12:27:25.497 [planetinfo.record]: inhabitants = "Harmless Rodents"; 12:27:25.497 [planetinfo.record]: description = "This world is a revolting dump."; 12:27:25.515 [planetinfo.record]: air_color = 0.49792, 0.996398, 0.906387, 1; 12:27:25.515 [planetinfo.record]: cloud_alpha = 1.000000; 12:27:25.515 [planetinfo.record]: cloud_color = 0.992188, 0.516562, 0.0968933, 1; 12:27:25.515 [planetinfo.record]: cloud_fraction = 0.150000; 12:27:25.515 [planetinfo.record]: land_color = 0.603516, 0.603147, 0.579941, 1; 12:27:25.515 [planetinfo.record]: land_fraction = 0.500000; 12:27:25.515 [planetinfo.record]: polar_cloud_color = 0.946484, 0.662912, 0.4127, 1; 12:27:25.515 [planetinfo.record]: polar_land_color = 0.939648, 0.939505, 0.930472, 1; 12:27:25.515 [planetinfo.record]: polar_sea_color = 0.862211, 0.914453, 0.80997, 1; 12:27:25.515 [planetinfo.record]: sea_color = 0.659981, 0.855469, 0.464493, 1; 12:27:25.515 [planetinfo.record]: rotation_speed = 0.004151 12:27:25.516 [planetinfo.record]: planet zpos = 413300.000000 12:27:25.516 [planetinfo.record]: sun_radius = 92860.064392 12:27:25.516 [planetinfo.record]: sun_vector = 0.344 0.829 0.441 12:27:25.516 [planetinfo.record]: sun_distance = 743940 12:27:25.516 [planetinfo.record]: corona_flare = 0.087361 12:27:25.517 [planetinfo.record]: corona_hues = 0.671783 12:27:25.517 [planetinfo.record]: sun_color = 0.74505, 0.503146, 0.375541, 1 12:27:25.517 [planetinfo.record]: corona_shimmer = 0.431130 12:27:25.517 [planetinfo.record]: station_vector = 0.524 -0.848 0.085 12:27:25.517 [planetinfo.record]: station = coriolis 12:27:29.920 [PLANETINFO OVER]: Done 12:27:29.921 [PLANETINFO LOGGING]: 2 121 12:27:30.926 [planetinfo.record]: seed = 73 28 197 130 12:27:30.926 [planetinfo.record]: coordinates = 28 32 12:27:30.926 [planetinfo.record]: government = 1; 12:27:30.926 [planetinfo.record]: economy = 2; 12:27:30.926 [planetinfo.record]: techlevel = 6; 12:27:30.926 [planetinfo.record]: population = 28; 12:27:30.926 [planetinfo.record]: productivity = 8960; 12:27:30.926 [planetinfo.record]: name = "Xeononle"; 12:27:30.926 [planetinfo.record]: inhabitant = "Large Black Fat Feline"; 12:27:30.926 [planetinfo.record]: inhabitants = "Large Black Fat Felines"; 12:27:30.926 [planetinfo.record]: description = "This planet is mildly fabled for its inhabitants’ eccentric shyness but ravaged by killer disease."; 12:27:30.944 [planetinfo.record]: air_color = 0.415013, 0.739349, 0.885832, 1; 12:27:30.944 [planetinfo.record]: cloud_alpha = 1.000000; 12:27:30.944 [planetinfo.record]: cloud_color = 0.03854, 0.660156, 0.00257874, 1; 12:27:30.944 [planetinfo.record]: cloud_fraction = 0.370000; 12:27:30.944 [planetinfo.record]: land_color = 0.407344, 0.66, 0.506038, 1; 12:27:30.944 [planetinfo.record]: land_fraction = 0.390000; 12:27:30.944 [planetinfo.record]: polar_cloud_color = 0.327985, 0.79707, 0.300847, 1; 12:27:30.944 [planetinfo.record]: polar_land_color = 0.844613, 0.934, 0.87953, 1; 12:27:30.944 [planetinfo.record]: polar_sea_color = 0.934375, 0.910401, 0.882364, 1; 12:27:30.944 [planetinfo.record]: sea_color = 0.65625, 0.588899, 0.510132, 1; 12:27:30.944 [planetinfo.record]: rotation_speed = 0.001001 12:27:30.944 [planetinfo.record]: planet zpos = 335600.000000 12:27:30.944 [planetinfo.record]: sun_radius = 92575.231323 12:27:30.945 [planetinfo.record]: sun_vector = -0.282 -0.959 -0.034 12:27:30.945 [planetinfo.record]: sun_distance = 536960 12:27:30.945 [planetinfo.record]: corona_flare = 0.009479 12:27:30.945 [planetinfo.record]: corona_hues = 0.587013 12:27:30.945 [planetinfo.record]: sun_color = 0.631804, 0.788177, 0.811447, 1 12:27:30.945 [planetinfo.record]: corona_shimmer = 0.422508 12:27:30.945 [planetinfo.record]: station_vector = -0.569 -0.822 0.020 12:27:30.945 [planetinfo.record]: station = coriolis 12:27:35.245 [PLANETINFO OVER]: Done 12:27:35.246 [PLANETINFO LOGGING]: 2 122 12:27:36.255 [planetinfo.record]: seed = 185 161 173 192 12:27:36.255 [planetinfo.record]: coordinates = 161 95 12:27:36.255 [planetinfo.record]: government = 7; 12:27:36.255 [planetinfo.record]: economy = 7; 12:27:36.255 [planetinfo.record]: techlevel = 5; 12:27:36.255 [planetinfo.record]: population = 35; 12:27:36.255 [planetinfo.record]: productivity = 9240; 12:27:36.255 [planetinfo.record]: name = "Lege"; 12:27:36.255 [planetinfo.record]: inhabitant = "Large Feline"; 12:27:36.255 [planetinfo.record]: inhabitants = "Large Felines"; 12:27:36.255 [planetinfo.record]: description = "The planet Lege is mildly well known for its exotic cuisine."; 12:27:36.268 [planetinfo.record]: air_color = 0.594539, 0.866351, 0.984691, 1; 12:27:36.268 [planetinfo.record]: cloud_alpha = 1.000000; 12:27:36.269 [planetinfo.record]: cloud_color = 0.436634, 0.957031, 0.392532, 1; 12:27:36.269 [planetinfo.record]: cloud_fraction = 0.420000; 12:27:36.269 [planetinfo.record]: land_color = 0.500156, 0.596312, 0.66, 1; 12:27:36.269 [planetinfo.record]: land_fraction = 0.260000; 12:27:36.269 [planetinfo.record]: polar_cloud_color = 0.614377, 0.930664, 0.587573, 1; 12:27:36.269 [planetinfo.record]: polar_land_color = 0.877449, 0.911468, 0.934, 1; 12:27:36.269 [planetinfo.record]: polar_sea_color = 0.928906, 0.900204, 0.855428, 1; 12:27:36.269 [planetinfo.record]: sea_color = 0.710938, 0.623068, 0.485992, 1; 12:27:36.269 [planetinfo.record]: rotation_speed = 0.002612 12:27:36.269 [planetinfo.record]: planet zpos = 327470.000000 12:27:36.269 [planetinfo.record]: sun_radius = 65295.854340 12:27:36.269 [planetinfo.record]: sun_vector = -0.359 -0.245 -0.901 12:27:36.269 [planetinfo.record]: sun_distance = 595400 12:27:36.269 [planetinfo.record]: corona_flare = 0.048358 12:27:36.269 [planetinfo.record]: corona_hues = 0.515701 12:27:36.269 [planetinfo.record]: sun_color = 0.6965, 0.619483, 0.44903, 1 12:27:36.269 [planetinfo.record]: corona_shimmer = 0.520168 12:27:36.270 [planetinfo.record]: station_vector = -0.287 0.450 0.845 12:27:36.270 [planetinfo.record]: station = coriolis 12:27:40.644 [PLANETINFO OVER]: Done 12:27:40.645 [PLANETINFO LOGGING]: 2 123 12:27:41.649 [planetinfo.record]: seed = 225 165 21 139 12:27:41.649 [planetinfo.record]: coordinates = 165 35 12:27:41.649 [planetinfo.record]: government = 4; 12:27:41.649 [planetinfo.record]: economy = 3; 12:27:41.649 [planetinfo.record]: techlevel = 7; 12:27:41.649 [planetinfo.record]: population = 36; 12:27:41.649 [planetinfo.record]: productivity = 16128; 12:27:41.649 [planetinfo.record]: name = "Maracece"; 12:27:41.649 [planetinfo.record]: inhabitant = "Human Colonial"; 12:27:41.649 [planetinfo.record]: inhabitants = "Human Colonials"; 12:27:41.649 [planetinfo.record]: description = "Maracece is an unremarkable dump."; 12:27:41.654 [planetinfo.record]: air_color = 0.5487, 0.478318, 0.935262, 1; 12:27:41.655 [planetinfo.record]: cloud_alpha = 1.000000; 12:27:41.655 [planetinfo.record]: cloud_color = 0.441558, 0.107391, 0.808594, 1; 12:27:41.655 [planetinfo.record]: cloud_fraction = 0.390000; 12:27:41.655 [planetinfo.record]: land_color = 0.649688, 0.66, 0.655891, 1; 12:27:41.655 [planetinfo.record]: land_fraction = 0.240000; 12:27:41.655 [planetinfo.record]: polar_cloud_color = 0.618789, 0.395658, 0.863867, 1; 12:27:41.655 [planetinfo.record]: polar_land_color = 0.930352, 0.934, 0.932546, 1; 12:27:41.655 [planetinfo.record]: polar_sea_color = 0.716025, 0.930469, 0.890261, 1; 12:27:41.655 [planetinfo.record]: sea_color = 0.0543213, 0.695312, 0.575127, 1; 12:27:41.655 [planetinfo.record]: rotation_speed = 0.001338 12:27:41.655 [planetinfo.record]: planet zpos = 811580.000000 12:27:41.655 [planetinfo.record]: sun_radius = 134712.847137 12:27:41.655 [planetinfo.record]: sun_vector = -0.744 0.538 -0.396 12:27:41.655 [planetinfo.record]: sun_distance = 1101430 12:27:41.655 [planetinfo.record]: corona_flare = 0.062053 12:27:41.656 [planetinfo.record]: corona_hues = 0.715996 12:27:41.656 [planetinfo.record]: sun_color = 0.668683, 0.571397, 0.409654, 1 12:27:41.656 [planetinfo.record]: corona_shimmer = 0.323801 12:27:41.656 [planetinfo.record]: station_vector = -0.092 -0.607 0.789 12:27:41.656 [planetinfo.record]: station = coriolis 12:27:46.629 [PLANETINFO OVER]: Done 12:27:46.630 [PLANETINFO LOGGING]: 2 124 12:27:47.643 [planetinfo.record]: seed = 145 101 13 64 12:27:47.643 [planetinfo.record]: coordinates = 101 133 12:27:47.643 [planetinfo.record]: government = 2; 12:27:47.643 [planetinfo.record]: economy = 5; 12:27:47.643 [planetinfo.record]: techlevel = 4; 12:27:47.643 [planetinfo.record]: population = 24; 12:27:47.643 [planetinfo.record]: productivity = 5760; 12:27:47.643 [planetinfo.record]: name = "Maer"; 12:27:47.643 [planetinfo.record]: inhabitant = "Human Colonial"; 12:27:47.644 [planetinfo.record]: inhabitants = "Human Colonials"; 12:27:47.644 [planetinfo.record]: description = "This planet is reasonably famous for its inhabitants’ ancient mating traditions."; 12:27:47.660 [planetinfo.record]: air_color = 0.646752, 0.895588, 0.883671, 1; 12:27:47.660 [planetinfo.record]: cloud_alpha = 1.000000; 12:27:47.660 [planetinfo.record]: cloud_color = 0.689453, 0.667992, 0.527863, 1; 12:27:47.660 [planetinfo.record]: cloud_fraction = 0.130000; 12:27:47.660 [planetinfo.record]: land_color = 0.53125, 0.523792, 0.0539551, 1; 12:27:47.660 [planetinfo.record]: land_fraction = 0.450000; 12:27:47.660 [planetinfo.record]: polar_cloud_color = 0.810254, 0.79449, 0.691564, 1; 12:27:47.660 [planetinfo.record]: polar_land_color = 0.946875, 0.943552, 0.734198, 1; 12:27:47.660 [planetinfo.record]: polar_sea_color = 0.900391, 0.853525, 0.86341, 1; 12:27:47.660 [planetinfo.record]: sea_color = 0.996094, 0.788704, 0.83245, 1; 12:27:47.660 [planetinfo.record]: rotation_speed = 0.002231 12:27:47.660 [planetinfo.record]: planet zpos = 291700.000000 12:27:47.660 [planetinfo.record]: sun_radius = 50502.253876 12:27:47.660 [planetinfo.record]: sun_vector = -0.671 0.208 -0.712 12:27:47.660 [planetinfo.record]: sun_distance = 583400 12:27:47.661 [planetinfo.record]: corona_flare = 0.099129 12:27:47.661 [planetinfo.record]: corona_hues = 0.598038 12:27:47.661 [planetinfo.record]: sun_color = 0.69173, 0.585578, 0.583016, 1 12:27:47.661 [planetinfo.record]: corona_shimmer = 0.385810 12:27:47.661 [planetinfo.record]: station_vector = 0.937 -0.119 0.328 12:27:47.661 [planetinfo.record]: station = coriolis 12:27:52.374 [PLANETINFO OVER]: Done 12:27:52.375 [PLANETINFO LOGGING]: 2 125 12:27:53.396 [planetinfo.record]: seed = 89 60 165 56 12:27:53.396 [planetinfo.record]: coordinates = 60 208 12:27:53.396 [planetinfo.record]: government = 3; 12:27:53.396 [planetinfo.record]: economy = 0; 12:27:53.396 [planetinfo.record]: techlevel = 9; 12:27:53.396 [planetinfo.record]: population = 40; 12:27:53.396 [planetinfo.record]: productivity = 22400; 12:27:53.397 [planetinfo.record]: name = "Edcequor"; 12:27:53.397 [planetinfo.record]: inhabitant = "Red Fat Bird"; 12:27:53.397 [planetinfo.record]: inhabitants = "Red Fat Birds"; 12:27:53.397 [planetinfo.record]: description = "The world Edcequor is very noted for its pink oceans but cursed by unpredictable earthquakes."; 12:27:53.408 [planetinfo.record]: air_color = 0.455727, 0.467845, 0.931359, 1; 12:27:53.408 [planetinfo.record]: cloud_alpha = 1.000000; 12:27:53.408 [planetinfo.record]: cloud_color = 0.0529175, 0.0994148, 0.796875, 1; 12:27:53.408 [planetinfo.record]: cloud_fraction = 0.460000; 12:27:53.408 [planetinfo.record]: land_color = 0.515625, 0.548335, 0.66, 1; 12:27:53.408 [planetinfo.record]: land_fraction = 0.450000; 12:27:53.408 [planetinfo.record]: polar_cloud_color = 0.357608, 0.388919, 0.858594, 1; 12:27:53.408 [planetinfo.record]: polar_land_color = 0.882922, 0.894494, 0.934, 1; 12:27:53.408 [planetinfo.record]: polar_sea_color = 0.9, 0.85275, 0.872595, 1; 12:27:53.408 [planetinfo.record]: sea_color = 1, 0.79, 0.8782, 1; 12:27:53.408 [planetinfo.record]: rotation_speed = 0.002847 12:27:53.408 [planetinfo.record]: planet zpos = 590880.000000 12:27:53.408 [planetinfo.record]: sun_radius = 159063.773193 12:27:53.408 [planetinfo.record]: sun_vector = -0.639 -0.188 -0.746 12:27:53.408 [planetinfo.record]: sun_distance = 1132520 12:27:53.408 [planetinfo.record]: corona_flare = 0.079509 12:27:53.408 [planetinfo.record]: corona_hues = 0.626030 12:27:53.415 [planetinfo.record]: sun_color = 0.737211, 0.740408, 0.383996, 1 12:27:53.416 [planetinfo.record]: corona_shimmer = 0.360698 12:27:53.416 [planetinfo.record]: station_vector = -0.654 0.731 0.195 12:27:53.416 [planetinfo.record]: station = coriolis 12:27:58.236 [PLANETINFO OVER]: Done 12:27:58.236 [PLANETINFO LOGGING]: 2 126 12:27:59.244 [planetinfo.record]: seed = 137 57 109 58 12:27:59.244 [planetinfo.record]: coordinates = 57 186 12:27:59.244 [planetinfo.record]: government = 1; 12:27:59.244 [planetinfo.record]: economy = 2; 12:27:59.244 [planetinfo.record]: techlevel = 7; 12:27:59.244 [planetinfo.record]: population = 32; 12:27:59.244 [planetinfo.record]: productivity = 10240; 12:27:59.244 [planetinfo.record]: name = "Qurexein"; 12:27:59.244 [planetinfo.record]: inhabitant = "Human Colonial"; 12:27:59.244 [planetinfo.record]: inhabitants = "Human Colonials"; 12:27:59.244 [planetinfo.record]: description = "The world Qurexein is very famous for its unusual sit coms but plagued by occasional solar activity."; 12:27:59.254 [planetinfo.record]: air_color = 0.461379, 0.465985, 0.928107, 1; 12:27:59.254 [planetinfo.record]: cloud_alpha = 1.000000; 12:27:59.254 [planetinfo.record]: cloud_color = 0.0707169, 0.0819105, 0.787109, 1; 12:27:59.254 [planetinfo.record]: cloud_fraction = 0.300000; 12:27:59.254 [planetinfo.record]: land_color = 0.634219, 0.66, 0.637643, 1; 12:27:59.254 [planetinfo.record]: land_fraction = 0.430000; 12:27:59.254 [planetinfo.record]: polar_cloud_color = 0.36829, 0.375882, 0.854199, 1; 12:27:59.255 [planetinfo.record]: polar_land_color = 0.924879, 0.934, 0.92609, 1; 12:27:59.255 [planetinfo.record]: polar_sea_color = 0.741719, 0.936523, 0.858906, 1; 12:27:59.255 [planetinfo.record]: sea_color = 0.106621, 0.634766, 0.424333, 1; 12:27:59.255 [planetinfo.record]: rotation_speed = 0.003160 12:27:59.255 [planetinfo.record]: planet zpos = 651960.000000 12:27:59.255 [planetinfo.record]: sun_radius = 165393.300018 12:27:59.255 [planetinfo.record]: sun_vector = -0.073 -0.977 -0.201 12:27:59.255 [planetinfo.record]: sun_distance = 977940 12:27:59.255 [planetinfo.record]: corona_flare = 0.033647 12:27:59.255 [planetinfo.record]: corona_hues = 0.745232 12:27:59.255 [planetinfo.record]: sun_color = 0.701498, 0.601272, 0.5792, 1 12:27:59.255 [planetinfo.record]: corona_shimmer = 0.294912 12:27:59.256 [planetinfo.record]: station_vector = 0.455 -0.686 0.568 12:27:59.256 [planetinfo.record]: station = coriolis 12:28:03.913 [PLANETINFO OVER]: Done 12:28:03.914 [PLANETINFO LOGGING]: 2 127 12:28:04.929 [planetinfo.record]: seed = 49 12 245 221 12:28:04.929 [planetinfo.record]: coordinates = 12 162 12:28:04.929 [planetinfo.record]: government = 6; 12:28:04.929 [planetinfo.record]: economy = 2; 12:28:04.929 [planetinfo.record]: techlevel = 8; 12:28:04.929 [planetinfo.record]: population = 41; 12:28:04.929 [planetinfo.record]: productivity = 26240; 12:28:04.929 [planetinfo.record]: name = "Isditixe"; 12:28:04.929 [planetinfo.record]: inhabitant = "Insect"; 12:28:04.929 [planetinfo.record]: inhabitants = "Insects"; 12:28:04.929 [planetinfo.record]: description = "This world is a revolting dump."; 12:28:04.960 [planetinfo.record]: air_color = 0.748508, 0.759539, 0.943066, 1; 12:28:04.960 [planetinfo.record]: cloud_alpha = 1.000000; 12:28:04.960 [planetinfo.record]: cloud_color = 0.815781, 0.81832, 0.832031, 1; 12:28:04.960 [planetinfo.record]: cloud_fraction = 0.240000; 12:28:04.960 [planetinfo.record]: land_color = 0.59375, 0.470825, 0.488111, 1; 12:28:04.960 [planetinfo.record]: land_fraction = 0.660000; 12:28:04.960 [planetinfo.record]: polar_cloud_color = 0.86374, 0.865408, 0.874414, 1; 12:28:04.960 [planetinfo.record]: polar_land_color = 0.940625, 0.89194, 0.898787, 1; 12:28:04.960 [planetinfo.record]: polar_sea_color = 0.916406, 0.897529, 0.795591, 1; 12:28:04.960 [planetinfo.record]: sea_color = 0.835938, 0.767058, 0.395111, 1; 12:28:04.960 [planetinfo.record]: rotation_speed = 0.000728 12:28:04.960 [planetinfo.record]: planet zpos = 800280.000000 12:28:04.960 [planetinfo.record]: sun_radius = 156702.024536 12:28:04.960 [planetinfo.record]: sun_vector = 0.681 0.398 -0.615 12:28:04.960 [planetinfo.record]: sun_distance = 1108080 12:28:04.960 [planetinfo.record]: corona_flare = 0.078453 12:28:04.960 [planetinfo.record]: corona_hues = 0.813049 12:28:04.960 [planetinfo.record]: sun_color = 0.221845, 0.662631, 0.674634, 1 12:28:04.961 [planetinfo.record]: corona_shimmer = 0.833219 12:28:04.961 [planetinfo.record]: station_vector = -0.677 -0.733 0.063 12:28:04.961 [planetinfo.record]: station = coriolis 12:28:09.590 [PLANETINFO OVER]: Done 12:28:09.591 [PLANETINFO LOGGING]: 2 128 12:28:10.600 [planetinfo.record]: seed = 33 226 77 230 12:28:10.600 [planetinfo.record]: coordinates = 226 119 12:28:10.600 [planetinfo.record]: government = 4; 12:28:10.600 [planetinfo.record]: economy = 7; 12:28:10.600 [planetinfo.record]: techlevel = 4; 12:28:10.600 [planetinfo.record]: population = 28; 12:28:10.600 [planetinfo.record]: productivity = 5376; 12:28:10.600 [planetinfo.record]: name = "Bionus"; 12:28:10.600 [planetinfo.record]: inhabitant = "Human Colonial"; 12:28:10.600 [planetinfo.record]: inhabitants = "Human Colonials"; 12:28:10.600 [planetinfo.record]: description = "This planet is most fabled for its inhabitants’ ingrained silliness but scourged by frequent civil war."; 12:28:10.628 [planetinfo.record]: air_color = 0.630115, 0.874125, 0.795784, 1; 12:28:10.628 [planetinfo.record]: cloud_alpha = 1.000000; 12:28:10.628 [planetinfo.record]: cloud_color = 0.625, 0.483055, 0.476074, 1; 12:28:10.628 [planetinfo.record]: cloud_fraction = 0.310000; 12:28:10.628 [planetinfo.record]: land_color = 0.261841, 0.461693, 0.507812, 1; 12:28:10.628 [planetinfo.record]: land_fraction = 0.320000; 12:28:10.628 [planetinfo.record]: polar_cloud_color = 0.78125, 0.670356, 0.664902, 1; 12:28:10.628 [planetinfo.record]: polar_land_color = 0.834274, 0.927667, 0.949219, 1; 12:28:10.628 [planetinfo.record]: polar_sea_color = 0.825762, 0.949023, 0.949023, 1; 12:28:10.628 [planetinfo.record]: sea_color = 0.244926, 0.509766, 0.509766, 1; 12:28:10.628 [planetinfo.record]: rotation_speed = 0.000452 12:28:10.628 [planetinfo.record]: planet zpos = 686700.000000 12:28:10.628 [planetinfo.record]: sun_radius = 112985.844727 12:28:10.629 [planetinfo.record]: sun_vector = 0.855 -0.518 -0.028 12:28:10.629 [planetinfo.record]: sun_distance = 1098720 12:28:10.629 [planetinfo.record]: corona_flare = 0.093091 12:28:10.629 [planetinfo.record]: corona_hues = 0.549049 12:28:10.629 [planetinfo.record]: sun_color = 0.795337, 0.773382, 0.729457, 1 12:28:10.629 [planetinfo.record]: corona_shimmer = 0.362169 12:28:10.629 [planetinfo.record]: station_vector = 0.668 0.434 0.604 12:28:10.629 [planetinfo.record]: station = coriolis 12:28:15.433 [PLANETINFO OVER]: Done 12:28:15.434 [PLANETINFO LOGGING]: 2 129 12:28:16.438 [planetinfo.record]: seed = 233 45 133 117 12:28:16.438 [planetinfo.record]: coordinates = 45 8 12:28:16.438 [planetinfo.record]: government = 5; 12:28:16.438 [planetinfo.record]: economy = 0; 12:28:16.438 [planetinfo.record]: techlevel = 11; 12:28:16.438 [planetinfo.record]: population = 50; 12:28:16.438 [planetinfo.record]: productivity = 36000; 12:28:16.438 [planetinfo.record]: name = "Lamare"; 12:28:16.438 [planetinfo.record]: inhabitant = "Blue Furry Feline"; 12:28:16.438 [planetinfo.record]: inhabitants = "Blue Furry Felines"; 12:28:16.438 [planetinfo.record]: description = "This planet is a dull world."; 12:28:16.452 [planetinfo.record]: air_color = 0.532312, 0.976402, 0.997699, 1; 12:28:16.452 [planetinfo.record]: cloud_alpha = 1.000000; 12:28:16.452 [planetinfo.record]: cloud_color = 0.877692, 0.996094, 0.198441, 1; 12:28:16.452 [planetinfo.record]: cloud_fraction = 0.420000; 12:28:16.452 [planetinfo.record]: land_color = 0.358359, 0.66, 0.568094, 1; 12:28:16.452 [planetinfo.record]: land_fraction = 0.430000; 12:28:16.452 [planetinfo.record]: polar_cloud_color = 0.877796, 0.948242, 0.473658, 1; 12:28:16.452 [planetinfo.record]: polar_land_color = 0.827283, 0.934, 0.901485, 1; 12:28:16.452 [planetinfo.record]: polar_sea_color = 0.933789, 0.887909, 0.869956, 1; 12:28:16.452 [planetinfo.record]: sea_color = 0.662109, 0.531983, 0.481064, 1; 12:28:16.452 [planetinfo.record]: rotation_speed = 0.004747 12:28:16.452 [planetinfo.record]: planet zpos = 496920.000000 12:28:16.452 [planetinfo.record]: sun_radius = 88348.199615 12:28:16.452 [planetinfo.record]: sun_vector = -0.395 -0.340 0.853 12:28:16.452 [planetinfo.record]: sun_distance = 828200 12:28:16.452 [planetinfo.record]: corona_flare = 0.031357 12:28:16.452 [planetinfo.record]: corona_hues = 0.542679 12:28:16.452 [planetinfo.record]: sun_color = 0.652451, 0.620046, 0.561469, 1 12:28:16.452 [planetinfo.record]: corona_shimmer = 0.291663 12:28:16.452 [planetinfo.record]: station_vector = 0.709 0.321 0.627 12:28:16.452 [planetinfo.record]: station = dodecahedron 12:28:21.375 [PLANETINFO OVER]: Done 12:28:21.376 [PLANETINFO LOGGING]: 2 130 12:28:22.386 [planetinfo.record]: seed = 217 111 45 106 12:28:22.386 [planetinfo.record]: coordinates = 111 78 12:28:22.386 [planetinfo.record]: government = 3; 12:28:22.386 [planetinfo.record]: economy = 6; 12:28:22.386 [planetinfo.record]: techlevel = 6; 12:28:22.386 [planetinfo.record]: population = 34; 12:28:22.386 [planetinfo.record]: productivity = 7616; 12:28:22.386 [planetinfo.record]: name = "Arusxeve"; 12:28:22.386 [planetinfo.record]: inhabitant = "Human Colonial"; 12:28:22.386 [planetinfo.record]: inhabitants = "Human Colonials"; 12:28:22.386 [planetinfo.record]: description = "This world is very well known for its inhabitants’ unusual mating traditions and its vast dense forests."; 12:28:22.412 [planetinfo.record]: air_color = 0.478298, 0.637637, 0.868922, 1; 12:28:22.413 [planetinfo.record]: cloud_alpha = 1.000000; 12:28:22.413 [planetinfo.record]: cloud_color = 0.152344, 0.609375, 0.51297, 1; 12:28:22.413 [planetinfo.record]: cloud_fraction = 0.350000; 12:28:22.413 [planetinfo.record]: land_color = 0.66, 0.516108, 0.175313, 1; 12:28:22.413 [planetinfo.record]: land_fraction = 0.490000; 12:28:22.413 [planetinfo.record]: polar_cloud_color = 0.411304, 0.774219, 0.697666, 1; 12:28:22.413 [planetinfo.record]: polar_land_color = 0.934, 0.883093, 0.762523, 1; 12:28:22.413 [planetinfo.record]: polar_sea_color = 0.932496, 0.938672, 0.889263, 1; 12:28:22.413 [planetinfo.record]: sea_color = 0.597141, 0.613281, 0.484157, 1; 12:28:22.414 [planetinfo.record]: rotation_speed = 0.003815 12:28:22.414 [planetinfo.record]: planet zpos = 658440.000000 12:28:22.414 [planetinfo.record]: sun_radius = 118685.176392 12:28:22.414 [planetinfo.record]: sun_vector = 0.146 -0.901 0.408 12:28:22.414 [planetinfo.record]: sun_distance = 1097400 12:28:22.414 [planetinfo.record]: corona_flare = 0.036075 12:28:22.414 [planetinfo.record]: corona_hues = 0.984299 12:28:22.414 [planetinfo.record]: sun_color = 0.837219, 0.771692, 0.750222, 1 12:28:22.415 [planetinfo.record]: corona_shimmer = 0.283252 12:28:22.415 [planetinfo.record]: station_vector = -0.836 -0.528 0.147 12:28:22.415 [planetinfo.record]: station = coriolis 12:28:26.772 [PLANETINFO OVER]: Done 12:28:26.772 [PLANETINFO LOGGING]: 2 131 12:28:27.788 [planetinfo.record]: seed = 1 150 213 193 12:28:27.788 [planetinfo.record]: coordinates = 150 2 12:28:27.788 [planetinfo.record]: government = 0; 12:28:27.788 [planetinfo.record]: economy = 2; 12:28:27.788 [planetinfo.record]: techlevel = 7; 12:28:27.788 [planetinfo.record]: population = 31; 12:28:27.788 [planetinfo.record]: productivity = 7936; 12:28:27.788 [planetinfo.record]: name = "Lequena"; 12:28:27.788 [planetinfo.record]: inhabitant = "Large Fat Humanoid"; 12:28:27.788 [planetinfo.record]: inhabitants = "Large Fat Humanoids"; 12:28:27.788 [planetinfo.record]: description = "The planet Lequena is a boring planet."; 12:28:27.791 [planetinfo.record]: air_color = 0.399002, 0.83315, 0.720871, 1; 12:28:27.791 [planetinfo.record]: cloud_alpha = 1.000000; 12:28:27.791 [planetinfo.record]: cloud_color = 0.501953, 0.131156, 0.0176468, 1; 12:28:27.791 [planetinfo.record]: cloud_fraction = 0.240000; 12:28:27.792 [planetinfo.record]: land_color = 0.061875, 0.26748, 0.66, 1; 12:28:27.792 [planetinfo.record]: land_fraction = 0.630000; 12:28:27.792 [planetinfo.record]: polar_cloud_color = 0.725879, 0.390746, 0.288154, 1; 12:28:27.792 [planetinfo.record]: polar_land_color = 0.722391, 0.795131, 0.934, 1; 12:28:27.792 [planetinfo.record]: polar_sea_color = 0.932617, 0.924537, 0.871778, 1; 12:28:27.792 [planetinfo.record]: sea_color = 0.673828, 0.650476, 0.498001, 1; 12:28:27.792 [planetinfo.record]: rotation_speed = 0.000111 12:28:27.792 [planetinfo.record]: planet zpos = 386640.000000 12:28:27.792 [planetinfo.record]: sun_radius = 102048.846130 12:28:27.792 [planetinfo.record]: sun_vector = 0.729 -0.579 -0.364 12:28:27.792 [planetinfo.record]: sun_distance = 644400 12:28:27.792 [planetinfo.record]: corona_flare = 0.059277 12:28:27.792 [planetinfo.record]: corona_hues = 0.534386 12:28:27.792 [planetinfo.record]: sun_color = 0.669672, 0.617388, 0.527976, 1 12:28:27.792 [planetinfo.record]: corona_shimmer = 0.232660 12:28:27.793 [planetinfo.record]: station_vector = 0.547 0.336 0.767 12:28:27.793 [planetinfo.record]: station = coriolis 12:28:31.892 [PLANETINFO OVER]: Done 12:28:31.893 [PLANETINFO LOGGING]: 2 132 12:28:32.903 [planetinfo.record]: seed = 49 207 141 220 12:28:32.903 [planetinfo.record]: coordinates = 207 178 12:28:32.903 [planetinfo.record]: government = 6; 12:28:32.903 [planetinfo.record]: economy = 2; 12:28:32.903 [planetinfo.record]: techlevel = 11; 12:28:32.903 [planetinfo.record]: population = 53; 12:28:32.903 [planetinfo.record]: productivity = 33920; 12:28:32.903 [planetinfo.record]: name = "Teriar"; 12:28:32.903 [planetinfo.record]: inhabitant = "Furry Humanoid"; 12:28:32.903 [planetinfo.record]: inhabitants = "Furry Humanoids"; 12:28:32.903 [planetinfo.record]: description = "This planet is beset by dreadful earthquakes."; 12:28:32.932 [planetinfo.record]: air_color = 0.495612, 0.482976, 0.9164, 1; 12:28:32.932 [planetinfo.record]: cloud_alpha = 1.000000; 12:28:32.932 [planetinfo.record]: cloud_color = 0.197764, 0.135117, 0.751953, 1; 12:28:32.932 [planetinfo.record]: cloud_fraction = 0.270000; 12:28:32.932 [planetinfo.record]: land_color = 0.575405, 0.66, 0.551719, 1; 12:28:32.932 [planetinfo.record]: land_fraction = 0.510000; 12:28:32.932 [planetinfo.record]: polar_cloud_color = 0.452201, 0.408546, 0.838379, 1; 12:28:32.932 [planetinfo.record]: polar_land_color = 0.904071, 0.934, 0.895691, 1; 12:28:32.932 [planetinfo.record]: polar_sea_color = 0.755699, 0.941406, 0.806478, 1; 12:28:32.932 [planetinfo.record]: sea_color = 0.123596, 0.585938, 0.250018, 1; 12:28:32.932 [planetinfo.record]: rotation_speed = 0.003569 12:28:32.932 [planetinfo.record]: planet zpos = 792350.000000 12:28:32.932 [planetinfo.record]: sun_radius = 178251.035309 12:28:32.932 [planetinfo.record]: sun_vector = 0.041 0.984 0.176 12:28:32.932 [planetinfo.record]: sun_distance = 1340900 12:28:32.932 [planetinfo.record]: corona_flare = 0.038420 12:28:32.932 [planetinfo.record]: corona_hues = 0.508682 12:28:32.932 [planetinfo.record]: sun_color = 0.570624, 0.680933, 0.714545, 1 12:28:32.932 [planetinfo.record]: corona_shimmer = 0.281200 12:28:32.933 [planetinfo.record]: station_vector = -0.859 0.272 0.433 12:28:32.933 [planetinfo.record]: station = icosahedron 12:28:37.924 [PLANETINFO OVER]: Done 12:28:37.924 [PLANETINFO LOGGING]: 2 133 12:28:38.940 [planetinfo.record]: seed = 249 68 101 173 12:28:38.940 [planetinfo.record]: coordinates = 68 10 12:28:38.940 [planetinfo.record]: government = 7; 12:28:38.940 [planetinfo.record]: economy = 2; 12:28:38.940 [planetinfo.record]: techlevel = 9; 12:28:38.940 [planetinfo.record]: population = 46; 12:28:38.940 [planetinfo.record]: productivity = 32384; 12:28:38.940 [planetinfo.record]: name = "Ditere"; 12:28:38.940 [planetinfo.record]: inhabitant = "Human Colonial"; 12:28:38.940 [planetinfo.record]: inhabitants = "Human Colonials"; 12:28:38.940 [planetinfo.record]: description = "This world is very notable for its inhabitants’ unusual silliness and its exotic cuisine."; 12:28:38.956 [planetinfo.record]: air_color = 0.631148, 0.894938, 0.851422, 1; 12:28:38.956 [planetinfo.record]: cloud_alpha = 1.000000; 12:28:38.956 [planetinfo.record]: cloud_color = 0.6875, 0.592541, 0.491455, 1; 12:28:38.956 [planetinfo.record]: cloud_fraction = 0.560000; 12:28:38.956 [planetinfo.record]: land_color = 0.0964355, 0.617188, 0.263239, 1; 12:28:38.956 [planetinfo.record]: land_fraction = 0.260000; 12:28:38.956 [planetinfo.record]: polar_cloud_color = 0.809375, 0.739504, 0.665126, 1; 12:28:38.956 [planetinfo.record]: polar_land_color = 0.740363, 0.938281, 0.803758, 1; 12:28:38.956 [planetinfo.record]: polar_sea_color = 0.930078, 0.892718, 0.856508, 1; 12:28:38.956 [planetinfo.record]: sea_color = 0.699219, 0.586872, 0.477982, 1; 12:28:38.956 [planetinfo.record]: rotation_speed = 0.001651 12:28:38.956 [planetinfo.record]: planet zpos = 807560.000000 12:28:38.956 [planetinfo.record]: sun_radius = 156271.572876 12:28:38.956 [planetinfo.record]: sun_vector = 0.689 -0.249 0.681 12:28:38.956 [planetinfo.record]: sun_distance = 993920 12:28:38.956 [planetinfo.record]: corona_flare = 0.069482 12:28:38.956 [planetinfo.record]: corona_hues = 0.759087 12:28:38.956 [planetinfo.record]: sun_color = 0.729642, 0.369572, 0.336428, 1 12:28:38.956 [planetinfo.record]: corona_shimmer = 0.324128 12:28:38.956 [planetinfo.record]: station_vector = -0.780 -0.342 0.523 12:28:38.956 [planetinfo.record]: station = coriolis 12:28:43.747 [PLANETINFO OVER]: Done 12:28:43.748 [PLANETINFO LOGGING]: 2 134 12:28:44.763 [planetinfo.record]: seed = 169 152 237 131 12:28:44.763 [planetinfo.record]: coordinates = 152 238 12:28:44.763 [planetinfo.record]: government = 5; 12:28:44.763 [planetinfo.record]: economy = 6; 12:28:44.763 [planetinfo.record]: techlevel = 4; 12:28:44.763 [planetinfo.record]: population = 28; 12:28:44.763 [planetinfo.record]: productivity = 8064; 12:28:44.763 [planetinfo.record]: name = "Gemasoti"; 12:28:44.763 [planetinfo.record]: inhabitant = "Large Black Frog"; 12:28:44.763 [planetinfo.record]: inhabitants = "Large Black Frogs"; 12:28:44.763 [planetinfo.record]: description = "Gemasoti is well known for the Gemasotiian spotted leopard but plagued by lethal spotted batoids."; 12:28:44.780 [planetinfo.record]: air_color = 0.753208, 0.651514, 0.83315, 1; 12:28:44.780 [planetinfo.record]: cloud_alpha = 1.000000; 12:28:44.780 [planetinfo.record]: cloud_color = 0.501953, 0.474503, 0.482866, 1; 12:28:44.780 [planetinfo.record]: cloud_fraction = 0.330000; 12:28:44.780 [planetinfo.record]: land_color = 0.1968, 0.354065, 0.654297, 1; 12:28:44.780 [planetinfo.record]: land_fraction = 0.520000; 12:28:44.780 [planetinfo.record]: polar_cloud_color = 0.725879, 0.701069, 0.708628, 1; 12:28:44.780 [planetinfo.record]: polar_land_color = 0.771203, 0.827361, 0.93457, 1; 12:28:44.780 [planetinfo.record]: polar_sea_color = 0.933789, 0.863387, 0.859925, 1; 12:28:44.780 [planetinfo.record]: sea_color = 0.662109, 0.462434, 0.452614, 1; 12:28:44.780 [planetinfo.record]: rotation_speed = 0.004458 12:28:44.780 [planetinfo.record]: planet zpos = 523040.000000 12:28:44.780 [planetinfo.record]: sun_radius = 67439.656982 12:28:44.780 [planetinfo.record]: sun_vector = 0.378 0.266 0.887 12:28:44.780 [planetinfo.record]: sun_distance = 709840 12:28:44.780 [planetinfo.record]: corona_flare = 0.088785 12:28:44.780 [planetinfo.record]: corona_hues = 0.845161 12:28:44.780 [planetinfo.record]: sun_color = 0.687708, 0.196789, 0.187368, 1 12:28:44.781 [planetinfo.record]: corona_shimmer = 1.086436 12:28:44.781 [planetinfo.record]: station_vector = -0.282 -0.398 0.873 12:28:44.781 [planetinfo.record]: station = coriolis 12:28:49.184 [PLANETINFO OVER]: Done 12:28:49.185 [PLANETINFO LOGGING]: 2 135 12:28:50.195 [planetinfo.record]: seed = 81 183 181 234 12:28:50.195 [planetinfo.record]: coordinates = 183 39 12:28:50.195 [planetinfo.record]: government = 2; 12:28:50.195 [planetinfo.record]: economy = 7; 12:28:50.195 [planetinfo.record]: techlevel = 4; 12:28:50.195 [planetinfo.record]: population = 26; 12:28:50.195 [planetinfo.record]: productivity = 3744; 12:28:50.195 [planetinfo.record]: name = "Ararin"; 12:28:50.195 [planetinfo.record]: inhabitant = "Small Slimy Lizard"; 12:28:50.195 [planetinfo.record]: inhabitants = "Small Slimy Lizards"; 12:28:50.195 [planetinfo.record]: description = "This planet is notable for the Ararinian tree snake and its fabulous monkey burgers."; 12:28:50.208 [planetinfo.record]: air_color = 0.509408, 0.479011, 0.924855, 1; 12:28:50.208 [planetinfo.record]: cloud_alpha = 1.000000; 12:28:50.208 [planetinfo.record]: cloud_color = 0.262562, 0.118423, 0.777344, 1; 12:28:50.208 [planetinfo.record]: cloud_fraction = 0.410000; 12:28:50.208 [planetinfo.record]: land_color = 0.64625, 0.654297, 0.575066, 1; 12:28:50.208 [planetinfo.record]: land_fraction = 0.420000; 12:28:50.208 [planetinfo.record]: polar_cloud_color = 0.498075, 0.399591, 0.849805, 1; 12:28:50.208 [planetinfo.record]: polar_land_color = 0.931697, 0.93457, 0.906278, 1; 12:28:50.208 [planetinfo.record]: polar_sea_color = 0.766572, 0.941211, 0.871628, 1; 12:28:50.208 [planetinfo.record]: sea_color = 0.151566, 0.587891, 0.414042, 1; 12:28:50.208 [planetinfo.record]: rotation_speed = 0.004510 12:28:50.208 [planetinfo.record]: planet zpos = 667080.000000 12:28:50.208 [planetinfo.record]: sun_radius = 128087.889862 12:28:50.208 [planetinfo.record]: sun_vector = -0.340 0.872 -0.352 12:28:50.208 [planetinfo.record]: sun_distance = 1056210 12:28:50.208 [planetinfo.record]: corona_flare = 0.046899 12:28:50.208 [planetinfo.record]: corona_hues = 0.657310 12:28:50.208 [planetinfo.record]: sun_color = 0.760175, 0.470727, 0.254733, 1 12:28:50.208 [planetinfo.record]: corona_shimmer = 0.612417 12:28:50.208 [planetinfo.record]: station_vector = 0.336 0.853 0.400 12:28:50.208 [planetinfo.record]: station = coriolis 12:28:54.935 [PLANETINFO OVER]: Done 12:28:54.936 [PLANETINFO LOGGING]: 2 136 12:28:55.941 [planetinfo.record]: seed = 193 32 205 86 12:28:55.942 [planetinfo.record]: coordinates = 32 108 12:28:55.942 [planetinfo.record]: government = 0; 12:28:55.942 [planetinfo.record]: economy = 6; 12:28:55.942 [planetinfo.record]: techlevel = 1; 12:28:55.942 [planetinfo.record]: population = 11; 12:28:55.942 [planetinfo.record]: productivity = 1408; 12:28:55.942 [planetinfo.record]: name = "Vegean"; 12:28:55.942 [planetinfo.record]: inhabitant = "Yellow Fat Feline"; 12:28:55.942 [planetinfo.record]: inhabitants = "Yellow Fat Felines"; 12:28:55.942 [planetinfo.record]: description = "The world Vegean is beset by dreadful earthquakes."; 12:28:55.965 [planetinfo.record]: air_color = 0.758812, 0.747687, 0.959326, 1; 12:28:55.971 [planetinfo.record]: cloud_alpha = 1.000000; 12:28:55.972 [planetinfo.record]: cloud_color = 0.835268, 0.825806, 0.880859, 1; 12:28:55.972 [planetinfo.record]: cloud_fraction = 0.590000; 12:28:55.972 [planetinfo.record]: land_color = 0.284302, 0.388171, 0.535156, 1; 12:28:55.972 [planetinfo.record]: land_fraction = 0.470000; 12:28:55.972 [planetinfo.record]: polar_cloud_color = 0.86739, 0.861372, 0.896387, 1; 12:28:55.972 [planetinfo.record]: polar_land_color = 0.835568, 0.881494, 0.946484, 1; 12:28:55.972 [planetinfo.record]: polar_sea_color = 0.914673, 0.932227, 0.876056, 1; 12:28:55.972 [planetinfo.record]: sea_color = 0.626689, 0.677734, 0.51439, 1; 12:28:55.972 [planetinfo.record]: rotation_speed = 0.001607 12:28:55.972 [planetinfo.record]: planet zpos = 482240.000000 12:28:55.972 [planetinfo.record]: sun_radius = 90728.383789 12:28:55.972 [planetinfo.record]: sun_vector = 0.862 0.363 0.353 12:28:55.972 [planetinfo.record]: sun_distance = 876800 12:28:55.972 [planetinfo.record]: corona_flare = 0.013095 12:28:55.972 [planetinfo.record]: corona_hues = 0.731827 12:28:55.972 [planetinfo.record]: sun_color = 0.557714, 0.767965, 0.789642, 1 12:28:55.973 [planetinfo.record]: corona_shimmer = 0.528849 12:28:55.973 [planetinfo.record]: station_vector = 0.246 -0.953 0.179 12:28:55.973 [planetinfo.record]: station = coriolis 12:29:00.804 [PLANETINFO OVER]: Done 12:29:00.805 [PLANETINFO LOGGING]: 2 137 12:29:01.811 [planetinfo.record]: seed = 137 149 69 212 12:29:01.811 [planetinfo.record]: coordinates = 149 91 12:29:01.811 [planetinfo.record]: government = 1; 12:29:01.811 [planetinfo.record]: economy = 3; 12:29:01.811 [planetinfo.record]: techlevel = 6; 12:29:01.811 [planetinfo.record]: population = 29; 12:29:01.811 [planetinfo.record]: productivity = 8120; 12:29:01.811 [planetinfo.record]: name = "Razare"; 12:29:01.811 [planetinfo.record]: inhabitant = "Human Colonial"; 12:29:01.811 [planetinfo.record]: inhabitants = "Human Colonials"; 12:29:01.811 [planetinfo.record]: description = "The world Razare is a dull place."; 12:29:01.828 [planetinfo.record]: air_color = 0.496157, 0.662266, 0.843557, 1; 12:29:01.828 [planetinfo.record]: cloud_alpha = 1.000000; 12:29:01.828 [planetinfo.record]: cloud_color = 0.193703, 0.533203, 0.389976, 1; 12:29:01.828 [planetinfo.record]: cloud_fraction = 0.290000; 12:29:01.828 [planetinfo.record]: land_color = 0.620583, 0.611016, 0.66, 1; 12:29:01.828 [planetinfo.record]: land_fraction = 0.330000; 12:29:01.828 [planetinfo.record]: polar_cloud_color = 0.445482, 0.739941, 0.615716, 1; 12:29:01.828 [planetinfo.record]: polar_land_color = 0.920055, 0.91667, 0.934, 1; 12:29:01.828 [planetinfo.record]: polar_sea_color = 0.948047, 0.773514, 0.712887, 1; 12:29:01.828 [planetinfo.record]: sea_color = 0.519531, 0.136954, 0.00405884, 1; 12:29:01.828 [planetinfo.record]: rotation_speed = 0.003557 12:29:01.828 [planetinfo.record]: planet zpos = 359010.000000 12:29:01.828 [planetinfo.record]: sun_radius = 94699.794922 12:29:01.828 [planetinfo.record]: sun_vector = 0.131 0.989 -0.066 12:29:01.828 [planetinfo.record]: sun_distance = 718020 12:29:01.828 [planetinfo.record]: corona_flare = 0.039003 12:29:01.829 [planetinfo.record]: corona_hues = 0.997078 12:29:01.829 [planetinfo.record]: sun_color = 0.322934, 0.548213, 0.786438, 1 12:29:01.829 [planetinfo.record]: corona_shimmer = 0.817565 12:29:01.829 [planetinfo.record]: station_vector = -0.193 -0.820 0.539 12:29:01.829 [planetinfo.record]: station = coriolis 12:29:06.544 [PLANETINFO OVER]: Done 12:29:06.545 [PLANETINFO LOGGING]: 2 138 12:29:07.558 [planetinfo.record]: seed = 249 199 173 187 12:29:07.558 [planetinfo.record]: coordinates = 199 46 12:29:07.558 [planetinfo.record]: government = 7; 12:29:07.558 [planetinfo.record]: economy = 6; 12:29:07.558 [planetinfo.record]: techlevel = 8; 12:29:07.559 [planetinfo.record]: population = 46; 12:29:07.559 [planetinfo.record]: productivity = 16192; 12:29:07.559 [planetinfo.record]: name = "Anenveza"; 12:29:07.559 [planetinfo.record]: inhabitant = "Harmless Bug-Eyed Bird"; 12:29:07.559 [planetinfo.record]: inhabitants = "Harmless Bug-Eyed Birds"; 12:29:07.559 [planetinfo.record]: description = "The world Anenveza is fabled for its fabulous lethal Dilose water and the Anenvezaian tree wolf."; 12:29:07.560 [planetinfo.record]: air_color = 0.582741, 0.917701, 0.815389, 1; 12:29:07.561 [planetinfo.record]: cloud_alpha = 1.000000; 12:29:07.561 [planetinfo.record]: cloud_color = 0.755859, 0.416036, 0.380882, 1; 12:29:07.561 [planetinfo.record]: cloud_fraction = 0.240000; 12:29:07.561 [planetinfo.record]: land_color = 0.66, 0.198516, 0.566261, 1; 12:29:07.561 [planetinfo.record]: land_fraction = 0.590000; 12:29:07.561 [planetinfo.record]: polar_cloud_color = 0.840137, 0.604066, 0.579645, 1; 12:29:07.561 [planetinfo.record]: polar_land_color = 0.934, 0.770732, 0.900836, 1; 12:29:07.561 [planetinfo.record]: polar_sea_color = 0.947266, 0.907656, 0.898515, 1; 12:29:07.561 [planetinfo.record]: sea_color = 0.527344, 0.43914, 0.418785, 1; 12:29:07.561 [planetinfo.record]: rotation_speed = 0.000115 12:29:07.561 [planetinfo.record]: planet zpos = 699720.000000 12:29:07.561 [planetinfo.record]: sun_radius = 170452.828369 12:29:07.561 [planetinfo.record]: sun_vector = 0.829 0.302 0.471 12:29:07.561 [planetinfo.record]: sun_distance = 1282820 12:29:07.561 [planetinfo.record]: corona_flare = 0.084627 12:29:07.561 [planetinfo.record]: corona_hues = 0.976746 12:29:07.562 [planetinfo.record]: sun_color = 0.335184, 0.695643, 0.722836, 1 12:29:07.562 [planetinfo.record]: corona_shimmer = 0.427351 12:29:07.562 [planetinfo.record]: station_vector = -0.647 0.685 0.335 12:29:07.562 [planetinfo.record]: station = coriolis 12:29:11.787 [PLANETINFO OVER]: Done 12:29:11.788 [PLANETINFO LOGGING]: 2 139 12:29:12.797 [planetinfo.record]: seed = 33 164 149 140 12:29:12.797 [planetinfo.record]: coordinates = 164 54 12:29:12.797 [planetinfo.record]: government = 4; 12:29:12.797 [planetinfo.record]: economy = 6; 12:29:12.797 [planetinfo.record]: techlevel = 3; 12:29:12.797 [planetinfo.record]: population = 23; 12:29:12.797 [planetinfo.record]: productivity = 5888; 12:29:12.797 [planetinfo.record]: name = "Inbiti"; 12:29:12.797 [planetinfo.record]: inhabitant = "Black Horned Lizard"; 12:29:12.797 [planetinfo.record]: inhabitants = "Black Horned Lizards"; 12:29:12.797 [planetinfo.record]: description = "Inbiti is an unremarkable dump."; 12:29:12.816 [planetinfo.record]: air_color = 0.468725, 0.724012, 0.843557, 1; 12:29:12.816 [planetinfo.record]: cloud_alpha = 1.000000; 12:29:12.816 [planetinfo.record]: cloud_color = 0.153869, 0.533203, 0.141632, 1; 12:29:12.816 [planetinfo.record]: cloud_fraction = 0.190000; 12:29:12.816 [planetinfo.record]: land_color = 0.513047, 0.556674, 0.66, 1; 12:29:12.816 [planetinfo.record]: land_fraction = 0.240000; 12:29:12.816 [planetinfo.record]: polar_cloud_color = 0.410933, 0.739941, 0.40032, 1; 12:29:12.816 [planetinfo.record]: polar_land_color = 0.88201, 0.897444, 0.934, 1; 12:29:12.816 [planetinfo.record]: polar_sea_color = 0.939062, 0.883796, 0.878078, 1; 12:29:12.816 [planetinfo.record]: sea_color = 0.609375, 0.46592, 0.45108, 1; 12:29:12.816 [planetinfo.record]: rotation_speed = 0.003839 12:29:12.816 [planetinfo.record]: planet zpos = 726240.000000 12:29:12.816 [planetinfo.record]: sun_radius = 125428.291016 12:29:12.816 [planetinfo.record]: sun_vector = -0.302 -0.540 -0.785 12:29:12.816 [planetinfo.record]: sun_distance = 1270920 12:29:12.816 [planetinfo.record]: corona_flare = 0.065387 12:29:12.816 [planetinfo.record]: corona_hues = 0.670593 12:29:12.816 [planetinfo.record]: sun_color = 0.820514, 0.796174, 0.304287, 1 12:29:12.817 [planetinfo.record]: corona_shimmer = 0.284862 12:29:12.817 [planetinfo.record]: station_vector = 0.742 -0.575 0.344 12:29:12.817 [planetinfo.record]: station = coriolis 12:29:18.399 [PLANETINFO OVER]: Done 12:29:18.400 [PLANETINFO LOGGING]: 2 140 12:29:19.407 [planetinfo.record]: seed = 209 138 13 137 12:29:19.407 [planetinfo.record]: coordinates = 138 151 12:29:19.407 [planetinfo.record]: government = 2; 12:29:19.407 [planetinfo.record]: economy = 7; 12:29:19.407 [planetinfo.record]: techlevel = 3; 12:29:19.408 [planetinfo.record]: population = 22; 12:29:19.408 [planetinfo.record]: productivity = 3168; 12:29:19.408 [planetinfo.record]: name = "Esmaonbe"; 12:29:19.408 [planetinfo.record]: inhabitant = "Human Colonial"; 12:29:19.408 [planetinfo.record]: inhabitants = "Human Colonials"; 12:29:19.408 [planetinfo.record]: description = "Esmaonbe is reasonably notable for its weird exuberant forests but cursed by killer disease."; 12:29:19.424 [planetinfo.record]: air_color = 0.530554, 0.968432, 0.868858, 1; 12:29:19.424 [planetinfo.record]: cloud_alpha = 1.000000; 12:29:19.424 [planetinfo.record]: cloud_color = 0.908203, 0.445787, 0.219955, 1; 12:29:19.424 [planetinfo.record]: cloud_fraction = 0.160000; 12:29:19.424 [planetinfo.record]: land_color = 0.541406, 0.629425, 0.66, 1; 12:29:19.424 [planetinfo.record]: land_fraction = 0.430000; 12:29:19.424 [planetinfo.record]: polar_cloud_color = 0.908691, 0.619526, 0.478305, 1; 12:29:19.424 [planetinfo.record]: polar_land_color = 0.892043, 0.923183, 0.934, 1; 12:29:19.424 [planetinfo.record]: polar_sea_color = 0.946289, 0.845214, 0.715261, 1; 12:29:19.424 [planetinfo.record]: sea_color = 0.537109, 0.307631, 0.0125885, 1; 12:29:19.424 [planetinfo.record]: rotation_speed = 0.004771 12:29:19.424 [planetinfo.record]: planet zpos = 525800.000000 12:29:19.424 [planetinfo.record]: sun_radius = 126441.998901 12:29:19.424 [planetinfo.record]: sun_vector = -0.530 0.720 -0.448 12:29:19.424 [planetinfo.record]: sun_distance = 946440 12:29:19.424 [planetinfo.record]: corona_flare = 0.059334 12:29:19.424 [planetinfo.record]: corona_hues = 0.924522 12:29:19.424 [planetinfo.record]: sun_color = 0.801782, 0.774368, 0.675754, 1 12:29:19.425 [planetinfo.record]: corona_shimmer = 0.514896 12:29:19.425 [planetinfo.record]: station_vector = 0.030 0.989 0.145 12:29:19.425 [planetinfo.record]: station = coriolis 12:29:25.386 [PLANETINFO OVER]: Done 12:29:25.387 [PLANETINFO LOGGING]: 2 141 12:29:26.403 [planetinfo.record]: seed = 153 243 37 94 12:29:26.403 [planetinfo.record]: coordinates = 243 191 12:29:26.403 [planetinfo.record]: government = 3; 12:29:26.403 [planetinfo.record]: economy = 7; 12:29:26.403 [planetinfo.record]: techlevel = 5; 12:29:26.403 [planetinfo.record]: population = 31; 12:29:26.403 [planetinfo.record]: productivity = 5208; 12:29:26.403 [planetinfo.record]: name = "Rierxe"; 12:29:26.403 [planetinfo.record]: inhabitant = "Human Colonial"; 12:29:26.403 [planetinfo.record]: inhabitants = "Human Colonials"; 12:29:26.403 [planetinfo.record]: description = "The planet Rierxe is most noted for its inhabitants’ exceptional loathing of food blenders and its exciting sit coms."; 12:29:26.407 [planetinfo.record]: air_color = 0.748381, 0.734075, 0.971033, 1; 12:29:26.407 [planetinfo.record]: cloud_alpha = 1.000000; 12:29:26.407 [planetinfo.record]: cloud_color = 0.818119, 0.794357, 0.916016, 1; 12:29:26.407 [planetinfo.record]: cloud_fraction = 0.450000; 12:29:26.407 [planetinfo.record]: land_color = 0.348047, 0.20625, 0.66, 1; 12:29:26.408 [planetinfo.record]: land_fraction = 0.570000; 12:29:26.408 [planetinfo.record]: polar_cloud_color = 0.851276, 0.836487, 0.912207, 1; 12:29:26.408 [planetinfo.record]: polar_land_color = 0.823635, 0.773469, 0.934, 1; 12:29:26.408 [planetinfo.record]: polar_sea_color = 0.93043, 0.934766, 0.879264, 1; 12:29:26.408 [planetinfo.record]: sea_color = 0.64024, 0.652344, 0.497412, 1; 12:29:26.408 [planetinfo.record]: rotation_speed = 0.000392 12:29:26.408 [planetinfo.record]: planet zpos = 730730.000000 12:29:26.408 [planetinfo.record]: sun_radius = 122176.220093 12:29:26.408 [planetinfo.record]: sun_vector = -0.313 -0.643 -0.699 12:29:26.408 [planetinfo.record]: sun_distance = 1195740 12:29:26.408 [planetinfo.record]: corona_flare = 0.089487 12:29:26.408 [planetinfo.record]: corona_hues = 0.640717 12:29:26.408 [planetinfo.record]: sun_color = 0.746146, 0.621048, 0.399489, 1 12:29:26.408 [planetinfo.record]: corona_shimmer = 0.285562 12:29:26.409 [planetinfo.record]: station_vector = 0.544 -0.429 0.721 12:29:26.409 [planetinfo.record]: station = coriolis 12:29:31.084 [PLANETINFO OVER]: Done 12:29:31.085 [PLANETINFO LOGGING]: 2 142 12:29:32.102 [planetinfo.record]: seed = 201 209 109 69 12:29:32.102 [planetinfo.record]: coordinates = 209 98 12:29:32.102 [planetinfo.record]: government = 1; 12:29:32.102 [planetinfo.record]: economy = 2; 12:29:32.102 [planetinfo.record]: techlevel = 7; 12:29:32.102 [planetinfo.record]: population = 32; 12:29:32.102 [planetinfo.record]: productivity = 10240; 12:29:32.102 [planetinfo.record]: name = "Ceorat"; 12:29:32.102 [planetinfo.record]: inhabitant = "Human Colonial"; 12:29:32.102 [planetinfo.record]: inhabitants = "Human Colonials"; 12:29:32.102 [planetinfo.record]: description = "Ceorat is a revolting dump."; 12:29:32.115 [planetinfo.record]: air_color = 0.454582, 0.737668, 0.852012, 1; 12:29:32.115 [planetinfo.record]: cloud_alpha = 1.000000; 12:29:32.115 [planetinfo.record]: cloud_color = 0.167196, 0.558594, 0.111282, 1; 12:29:32.115 [planetinfo.record]: cloud_fraction = 0.180000; 12:29:32.115 [planetinfo.record]: land_color = 0.131484, 0.251226, 0.66, 1; 12:29:32.115 [planetinfo.record]: land_fraction = 0.470000; 12:29:32.115 [planetinfo.record]: polar_cloud_color = 0.422323, 0.751367, 0.375317, 1; 12:29:32.115 [planetinfo.record]: polar_land_color = 0.747018, 0.789381, 0.934, 1; 12:29:32.115 [planetinfo.record]: polar_sea_color = 0.905445, 0.930859, 0.874771, 1; 12:29:32.116 [planetinfo.record]: sea_color = 0.615898, 0.691406, 0.524767, 1; 12:29:32.116 [planetinfo.record]: rotation_speed = 0.000688 12:29:32.116 [planetinfo.record]: planet zpos = 516600.000000 12:29:32.116 [planetinfo.record]: sun_radius = 115851.901245 12:29:32.116 [planetinfo.record]: sun_vector = -0.305 0.669 0.678 12:29:32.116 [planetinfo.record]: sun_distance = 774900 12:29:32.116 [planetinfo.record]: corona_flare = 0.058276 12:29:32.116 [planetinfo.record]: corona_hues = 0.901375 12:29:32.116 [planetinfo.record]: sun_color = 0.435058, 0.664302, 0.713107, 1 12:29:32.116 [planetinfo.record]: corona_shimmer = 0.371431 12:29:32.116 [planetinfo.record]: station_vector = 0.557 0.386 0.735 12:29:32.116 [planetinfo.record]: station = coriolis 12:29:36.601 [PLANETINFO OVER]: Done 12:29:36.602 [PLANETINFO LOGGING]: 2 143 12:29:37.619 [planetinfo.record]: seed = 113 80 117 91 12:29:37.619 [planetinfo.record]: coordinates = 80 145 12:29:37.619 [planetinfo.record]: government = 6; 12:29:37.619 [planetinfo.record]: economy = 1; 12:29:37.619 [planetinfo.record]: techlevel = 9; 12:29:37.619 [planetinfo.record]: population = 44; 12:29:37.619 [planetinfo.record]: productivity = 31680; 12:29:37.619 [planetinfo.record]: name = "Anisus"; 12:29:37.619 [planetinfo.record]: inhabitant = "Human Colonial"; 12:29:37.619 [planetinfo.record]: inhabitants = "Human Colonials"; 12:29:37.619 [planetinfo.record]: description = "This planet is a tedious little planet."; 12:29:37.648 [planetinfo.record]: air_color = 0.632086, 0.507966, 0.929408, 1; 12:29:37.648 [planetinfo.record]: cloud_alpha = 1.000000; 12:29:37.648 [planetinfo.record]: cloud_color = 0.729821, 0.188484, 0.791016, 1; 12:29:37.648 [planetinfo.record]: cloud_fraction = 0.430000; 12:29:37.648 [planetinfo.record]: land_color = 0.607534, 0.373718, 0.609375, 1; 12:29:37.648 [planetinfo.record]: land_fraction = 0.520000; 12:29:37.648 [planetinfo.record]: polar_cloud_color = 0.81457, 0.448458, 0.855957, 1; 12:29:37.648 [planetinfo.record]: polar_land_color = 0.938353, 0.848274, 0.939062, 1; 12:29:37.648 [planetinfo.record]: polar_sea_color = 0.929157, 0.934766, 0.879538, 1; 12:29:37.648 [planetinfo.record]: sea_color = 0.636686, 0.652344, 0.498177, 1; 12:29:37.648 [planetinfo.record]: rotation_speed = 0.003179 12:29:37.648 [planetinfo.record]: planet zpos = 742560.000000 12:29:37.648 [planetinfo.record]: sun_radius = 191270.419922 12:29:37.648 [planetinfo.record]: sun_vector = 0.119 -0.461 -0.879 12:29:37.648 [planetinfo.record]: sun_distance = 1313760 12:29:37.648 [planetinfo.record]: corona_flare = 0.093921 12:29:37.648 [planetinfo.record]: corona_hues = 0.761253 12:29:37.648 [planetinfo.record]: sun_color = 0.754184, 0.272604, 0.209355, 1 12:29:37.648 [planetinfo.record]: corona_shimmer = 0.270285 12:29:37.649 [planetinfo.record]: station_vector = -0.500 -0.866 0.032 12:29:37.649 [planetinfo.record]: station = coriolis 12:29:42.609 [PLANETINFO OVER]: Done 12:29:42.610 [PLANETINFO LOGGING]: 2 144 12:29:43.633 [planetinfo.record]: seed = 97 129 77 167 12:29:43.633 [planetinfo.record]: coordinates = 129 232 12:29:43.633 [planetinfo.record]: government = 4; 12:29:43.633 [planetinfo.record]: economy = 0; 12:29:43.633 [planetinfo.record]: techlevel = 10; 12:29:43.634 [planetinfo.record]: population = 45; 12:29:43.634 [planetinfo.record]: productivity = 28800; 12:29:43.634 [planetinfo.record]: name = "Soatqube"; 12:29:43.634 [planetinfo.record]: inhabitant = "Human Colonial"; 12:29:43.634 [planetinfo.record]: inhabitants = "Human Colonials"; 12:29:43.634 [planetinfo.record]: description = "Soatqube is well known for the Soatqubeian spotted wolf but beset by frequent civil war."; 12:29:43.635 [planetinfo.record]: air_color = 0.653439, 0.809145, 0.963879, 1; 12:29:43.635 [planetinfo.record]: cloud_alpha = 1.000000; 12:29:43.635 [planetinfo.record]: cloud_color = 0.566071, 0.894531, 0.732867, 1; 12:29:43.635 [planetinfo.record]: cloud_fraction = 0.270000; 12:29:43.635 [planetinfo.record]: land_color = 0.48934, 0.0696094, 0.66, 1; 12:29:43.635 [planetinfo.record]: land_fraction = 0.650000; 12:29:43.635 [planetinfo.record]: polar_cloud_color = 0.695413, 0.902539, 0.800594, 1; 12:29:43.635 [planetinfo.record]: polar_land_color = 0.873623, 0.725127, 0.934, 1; 12:29:43.635 [planetinfo.record]: polar_sea_color = 0.945898, 0.905956, 0.895278, 1; 12:29:43.635 [planetinfo.record]: sea_color = 0.541016, 0.449633, 0.425204, 1; 12:29:43.635 [planetinfo.record]: rotation_speed = 0.002908 12:29:43.635 [planetinfo.record]: planet zpos = 615810.000000 12:29:43.636 [planetinfo.record]: sun_radius = 110169.800262 12:29:43.636 [planetinfo.record]: sun_vector = 0.634 0.001 -0.773 12:29:43.636 [planetinfo.record]: sun_distance = 805290 12:29:43.636 [planetinfo.record]: corona_flare = 0.034885 12:29:43.636 [planetinfo.record]: corona_hues = 0.971794 12:29:43.636 [planetinfo.record]: sun_color = 0.810699, 0.562213, 0.399363, 1 12:29:43.636 [planetinfo.record]: corona_shimmer = 0.412425 12:29:43.636 [planetinfo.record]: station_vector = -0.823 -0.565 0.054 12:29:43.636 [planetinfo.record]: station = coriolis 12:29:49.466 [PLANETINFO OVER]: Done 12:29:49.467 [PLANETINFO LOGGING]: 2 145 12:29:50.474 [planetinfo.record]: seed = 41 243 5 63 12:29:50.474 [planetinfo.record]: coordinates = 243 58 12:29:50.474 [planetinfo.record]: government = 5; 12:29:50.474 [planetinfo.record]: economy = 2; 12:29:50.474 [planetinfo.record]: techlevel = 11; 12:29:50.475 [planetinfo.record]: population = 52; 12:29:50.475 [planetinfo.record]: productivity = 29952; 12:29:50.475 [planetinfo.record]: name = "Oninriar"; 12:29:50.475 [planetinfo.record]: inhabitant = "Human Colonial"; 12:29:50.475 [planetinfo.record]: inhabitants = "Human Colonials"; 12:29:50.475 [planetinfo.record]: description = "Oninriar is mildly well known for its exotic cuisine."; 12:29:50.500 [planetinfo.record]: air_color = 0.481742, 0.716024, 0.833801, 1; 12:29:50.500 [planetinfo.record]: cloud_alpha = 1.000000; 12:29:50.500 [planetinfo.record]: cloud_color = 0.167313, 0.503906, 0.172572, 1; 12:29:50.500 [planetinfo.record]: cloud_fraction = 0.330000; 12:29:50.500 [planetinfo.record]: land_color = 0.570312, 0.542465, 0.525757, 1; 12:29:50.500 [planetinfo.record]: land_fraction = 0.260000; 12:29:50.500 [planetinfo.record]: polar_cloud_color = 0.423351, 0.726758, 0.428091, 1; 12:29:50.500 [planetinfo.record]: polar_land_color = 0.942969, 0.931458, 0.924551, 1; 12:29:50.500 [planetinfo.record]: polar_sea_color = 0.894572, 0.920703, 0.837085, 1; 12:29:50.500 [planetinfo.record]: sea_color = 0.702947, 0.792969, 0.504898, 1; 12:29:50.501 [planetinfo.record]: rotation_speed = 0.002253 12:29:50.501 [planetinfo.record]: planet zpos = 827880.000000 12:29:50.501 [planetinfo.record]: sun_radius = 124144.313202 12:29:50.501 [planetinfo.record]: sun_vector = 0.213 -0.448 -0.868 12:29:50.501 [planetinfo.record]: sun_distance = 1379800 12:29:50.501 [planetinfo.record]: corona_flare = 0.044908 12:29:50.501 [planetinfo.record]: corona_hues = 0.866646 12:29:50.501 [planetinfo.record]: sun_color = 0.747559, 0.754659, 0.757529, 1 12:29:50.501 [planetinfo.record]: corona_shimmer = 0.432450 12:29:50.502 [planetinfo.record]: station_vector = 0.568 -0.807 0.160 12:29:50.502 [planetinfo.record]: station = dodecahedron 12:29:55.218 [PLANETINFO OVER]: Done 12:29:55.218 [PLANETINFO LOGGING]: 2 146 12:29:56.226 [planetinfo.record]: seed = 25 74 45 85 12:29:56.226 [planetinfo.record]: coordinates = 74 158 12:29:56.226 [planetinfo.record]: government = 3; 12:29:56.226 [planetinfo.record]: economy = 6; 12:29:56.226 [planetinfo.record]: techlevel = 5; 12:29:56.227 [planetinfo.record]: population = 30; 12:29:56.227 [planetinfo.record]: productivity = 6720; 12:29:56.227 [planetinfo.record]: name = "Laisis"; 12:29:56.227 [planetinfo.record]: inhabitant = "Human Colonial"; 12:29:56.227 [planetinfo.record]: inhabitants = "Human Colonials"; 12:29:56.227 [planetinfo.record]: description = "This planet is a dull place."; 12:29:56.248 [planetinfo.record]: air_color = 0.462818, 0.471007, 0.926156, 1; 12:29:56.248 [planetinfo.record]: cloud_alpha = 1.000000; 12:29:56.248 [planetinfo.record]: cloud_color = 0.0762939, 0.103831, 0.78125, 1; 12:29:56.248 [planetinfo.record]: cloud_fraction = 0.280000; 12:29:56.248 [planetinfo.record]: land_color = 0.407344, 0.577097, 0.66, 1; 12:29:56.248 [planetinfo.record]: land_fraction = 0.390000; 12:29:56.248 [planetinfo.record]: polar_cloud_color = 0.371311, 0.390071, 0.851562, 1; 12:29:56.248 [planetinfo.record]: polar_land_color = 0.844613, 0.90467, 0.934, 1; 12:29:56.248 [planetinfo.record]: polar_sea_color = 0.935352, 0.898529, 0.880546, 1; 12:29:56.248 [planetinfo.record]: sea_color = 0.646484, 0.544682, 0.494965, 1; 12:29:56.248 [planetinfo.record]: rotation_speed = 0.001300 12:29:56.248 [planetinfo.record]: planet zpos = 500400.000000 12:29:56.248 [planetinfo.record]: sun_radius = 81340.960693 12:29:56.248 [planetinfo.record]: sun_vector = -0.521 -0.835 0.178 12:29:56.248 [planetinfo.record]: sun_distance = 708900 12:29:56.248 [planetinfo.record]: corona_flare = 0.098613 12:29:56.248 [planetinfo.record]: corona_hues = 0.701401 12:29:56.248 [planetinfo.record]: sun_color = 0.307031, 0.483141, 0.756735, 1 12:29:56.249 [planetinfo.record]: corona_shimmer = 0.432296 12:29:56.249 [planetinfo.record]: station_vector = -0.977 -0.126 0.175 12:29:56.249 [planetinfo.record]: station = coriolis 12:30:01.328 [PLANETINFO OVER]: Done 12:30:01.329 [PLANETINFO LOGGING]: 2 147 12:30:02.343 [planetinfo.record]: seed = 65 112 85 139 12:30:02.343 [planetinfo.record]: coordinates = 112 221 12:30:02.343 [planetinfo.record]: government = 0; 12:30:02.343 [planetinfo.record]: economy = 7; 12:30:02.343 [planetinfo.record]: techlevel = 0; 12:30:02.343 [planetinfo.record]: population = 8; 12:30:02.343 [planetinfo.record]: productivity = 768; 12:30:02.343 [planetinfo.record]: name = "Maedra"; 12:30:02.343 [planetinfo.record]: inhabitant = "Human Colonial"; 12:30:02.343 [planetinfo.record]: inhabitants = "Human Colonials"; 12:30:02.343 [planetinfo.record]: description = "This world is ravaged by occasional solar activity."; 12:30:02.376 [planetinfo.record]: air_color = 0.719563, 0.772509, 0.943717, 1; 12:30:02.376 [planetinfo.record]: cloud_alpha = 1.000000; 12:30:02.376 [planetinfo.record]: cloud_color = 0.73951, 0.805199, 0.833984, 1; 12:30:02.376 [planetinfo.record]: cloud_fraction = 0.340000; 12:30:02.376 [planetinfo.record]: land_color = 0.445882, 0.359268, 0.541016, 1; 12:30:02.376 [planetinfo.record]: land_fraction = 0.350000; 12:30:02.376 [planetinfo.record]: polar_cloud_color = 0.813322, 0.856411, 0.875293, 1; 12:30:02.376 [planetinfo.record]: polar_land_color = 0.904316, 0.866458, 0.945898, 1; 12:30:02.376 [planetinfo.record]: polar_sea_color = 0.945898, 0.916577, 0.725127, 1; 12:30:02.376 [planetinfo.record]: sea_color = 0.541016, 0.473934, 0.0359268, 1; 12:30:02.376 [planetinfo.record]: rotation_speed = 0.002580 12:30:02.376 [planetinfo.record]: planet zpos = 746720.000000 12:30:02.376 [planetinfo.record]: sun_radius = 171223.542480 12:30:02.376 [planetinfo.record]: sun_vector = -0.215 -0.891 -0.400 12:30:02.376 [planetinfo.record]: sun_distance = 1148800 12:30:02.377 [planetinfo.record]: corona_flare = 0.046967 12:30:02.377 [planetinfo.record]: corona_hues = 0.580559 12:30:02.377 [planetinfo.record]: sun_color = 0.215934, 0.510732, 0.728171, 1 12:30:02.377 [planetinfo.record]: corona_shimmer = 0.257359 12:30:02.377 [planetinfo.record]: station_vector = 0.249 0.967 0.046 12:30:02.377 [planetinfo.record]: station = coriolis 12:30:07.518 [PLANETINFO OVER]: Done 12:30:07.519 [PLANETINFO LOGGING]: 2 148 12:30:08.534 [planetinfo.record]: seed = 113 56 141 229 12:30:08.534 [planetinfo.record]: coordinates = 56 212 12:30:08.534 [planetinfo.record]: government = 6; 12:30:08.534 [planetinfo.record]: economy = 4; 12:30:08.534 [planetinfo.record]: techlevel = 6; 12:30:08.534 [planetinfo.record]: population = 35; 12:30:08.534 [planetinfo.record]: productivity = 16800; 12:30:08.534 [planetinfo.record]: name = "Ceenerus"; 12:30:08.534 [planetinfo.record]: inhabitant = "Fierce Fat Humanoid"; 12:30:08.534 [planetinfo.record]: inhabitants = "Fierce Fat Humanoids"; 12:30:08.534 [planetinfo.record]: description = "Ceenerus is most noted for the Ceenerusian deadly lobstoid and the Ceenerusian spotted shrew."; 12:30:08.562 [planetinfo.record]: air_color = 0.718343, 0.46781, 0.979488, 1; 12:30:08.562 [planetinfo.record]: cloud_alpha = 1.000000; 12:30:08.562 [planetinfo.record]: cloud_color = 0.941406, 0.0294189, 0.499662, 1; 12:30:08.563 [planetinfo.record]: cloud_fraction = 0.480000; 12:30:08.563 [planetinfo.record]: land_color = 0.508425, 0.390244, 0.537109, 1; 12:30:08.563 [planetinfo.record]: land_fraction = 0.540000; 12:30:08.563 [planetinfo.record]: polar_cloud_color = 0.923633, 0.364402, 0.652755, 1; 12:30:08.563 [planetinfo.record]: polar_land_color = 0.933655, 0.881601, 0.946289, 1; 12:30:08.563 [planetinfo.record]: polar_sea_color = 0.924111, 0.938867, 0.891649, 1; 12:30:08.563 [planetinfo.record]: sea_color = 0.572896, 0.611328, 0.488346, 1; 12:30:08.563 [planetinfo.record]: rotation_speed = 0.001051 12:30:08.563 [planetinfo.record]: planet zpos = 498240.000000 12:30:08.563 [planetinfo.record]: sun_radius = 73176.339111 12:30:08.563 [planetinfo.record]: sun_vector = 0.943 -0.139 0.303 12:30:08.563 [planetinfo.record]: sun_distance = 788880 12:30:08.563 [planetinfo.record]: corona_flare = 0.044081 12:30:08.563 [planetinfo.record]: corona_hues = 0.549698 12:30:08.563 [planetinfo.record]: sun_color = 0.650232, 0.57713, 0.461766, 1 12:30:08.564 [planetinfo.record]: corona_shimmer = 1.022658 12:30:08.564 [planetinfo.record]: station_vector = -0.462 0.832 0.306 12:30:08.564 [planetinfo.record]: station = coriolis 12:30:13.532 [PLANETINFO OVER]: Done 12:30:13.534 [PLANETINFO LOGGING]: 2 149 12:30:14.536 [planetinfo.record]: seed = 57 232 229 234 12:30:14.536 [planetinfo.record]: coordinates = 232 16 12:30:14.536 [planetinfo.record]: government = 7; 12:30:14.536 [planetinfo.record]: economy = 0; 12:30:14.536 [planetinfo.record]: techlevel = 11; 12:30:14.536 [planetinfo.record]: population = 52; 12:30:14.536 [planetinfo.record]: productivity = 45760; 12:30:14.536 [planetinfo.record]: name = "Argeveza"; 12:30:14.536 [planetinfo.record]: inhabitant = "Small Slimy Lizard"; 12:30:14.536 [planetinfo.record]: inhabitants = "Small Slimy Lizards"; 12:30:14.536 [planetinfo.record]: description = "This world is most notable for its fabulous cuisine but beset by occasional civil war."; 12:30:14.546 [planetinfo.record]: air_color = 0.6716, 0.86526, 0.900791, 1; 12:30:14.546 [planetinfo.record]: cloud_alpha = 1.000000; 12:30:14.546 [planetinfo.record]: cloud_color = 0.649951, 0.705078, 0.589401, 1; 12:30:14.546 [planetinfo.record]: cloud_fraction = 0.250000; 12:30:14.546 [planetinfo.record]: land_color = 0.245865, 0.511719, 0.511719, 1; 12:30:14.546 [planetinfo.record]: land_fraction = 0.430000; 12:30:14.546 [planetinfo.record]: polar_cloud_color = 0.777348, 0.817285, 0.733482, 1; 12:30:14.546 [planetinfo.record]: polar_land_color = 0.825592, 0.948828, 0.948828, 1; 12:30:14.546 [planetinfo.record]: polar_sea_color = 0.92832, 0.898574, 0.852169, 1; 12:30:14.546 [planetinfo.record]: sea_color = 0.716797, 0.624922, 0.481598, 1; 12:30:14.546 [planetinfo.record]: rotation_speed = 0.004205 12:30:14.546 [planetinfo.record]: planet zpos = 560800.000000 12:30:14.546 [planetinfo.record]: sun_radius = 118730.163574 12:30:14.546 [planetinfo.record]: sun_vector = 0.088 -0.336 -0.938 12:30:14.546 [planetinfo.record]: sun_distance = 1065520 12:30:14.546 [planetinfo.record]: corona_flare = 0.007912 12:30:14.546 [planetinfo.record]: corona_hues = 0.638626 12:30:14.546 [planetinfo.record]: sun_color = 0.404507, 0.502548, 0.82225, 1 12:30:14.547 [planetinfo.record]: corona_shimmer = 0.334866 12:30:14.547 [planetinfo.record]: station_vector = 0.712 0.695 0.100 12:30:14.547 [planetinfo.record]: station = dodecahedron 12:30:19.730 [PLANETINFO OVER]: Done 12:30:19.731 [PLANETINFO LOGGING]: 2 150 12:30:20.749 [planetinfo.record]: seed = 233 132 237 30 12:30:20.749 [planetinfo.record]: coordinates = 132 182 12:30:20.749 [planetinfo.record]: government = 5; 12:30:20.749 [planetinfo.record]: economy = 6; 12:30:20.749 [planetinfo.record]: techlevel = 4; 12:30:20.749 [planetinfo.record]: population = 28; 12:30:20.749 [planetinfo.record]: productivity = 8064; 12:30:20.749 [planetinfo.record]: name = "Riquri"; 12:30:20.749 [planetinfo.record]: inhabitant = "Green Horned Bird"; 12:30:20.749 [planetinfo.record]: inhabitants = "Green Horned Birds"; 12:30:20.749 [planetinfo.record]: description = "This planet is noted for Zero-G hockey."; 12:30:20.764 [planetinfo.record]: air_color = 0.44236, 0.627766, 0.896889, 1; 12:30:20.764 [planetinfo.record]: cloud_alpha = 1.000000; 12:30:20.764 [planetinfo.record]: cloud_color = 0.0541687, 0.693359, 0.55853, 1; 12:30:20.764 [planetinfo.record]: cloud_fraction = 0.450000; 12:30:20.764 [planetinfo.record]: land_color = 0.66, 0.118312, 0.0464063, 1; 12:30:20.764 [planetinfo.record]: land_fraction = 0.430000; 12:30:20.764 [planetinfo.record]: polar_cloud_color = 0.344153, 0.812012, 0.713323, 1; 12:30:20.764 [planetinfo.record]: polar_land_color = 0.934, 0.742357, 0.716918, 1; 12:30:20.764 [planetinfo.record]: polar_sea_color = 0.895817, 0.933789, 0.885185, 1; 12:30:20.764 [planetinfo.record]: sea_color = 0.554412, 0.662109, 0.524256, 1; 12:30:20.771 [planetinfo.record]: rotation_speed = 0.001909 12:30:20.771 [planetinfo.record]: planet zpos = 653200.000000 12:30:20.772 [planetinfo.record]: sun_radius = 172553.400879 12:30:20.772 [planetinfo.record]: sun_vector = -0.823 -0.393 -0.411 12:30:20.772 [planetinfo.record]: sun_distance = 1241080 12:30:20.772 [planetinfo.record]: corona_flare = 0.080557 12:30:20.772 [planetinfo.record]: corona_hues = 0.675163 12:30:20.772 [planetinfo.record]: sun_color = 0.77261, 0.53998, 0.465684, 1 12:30:20.772 [planetinfo.record]: corona_shimmer = 0.526681 12:30:20.772 [planetinfo.record]: station_vector = -0.989 -0.027 0.147 12:30:20.772 [planetinfo.record]: station = coriolis 12:30:25.963 [PLANETINFO OVER]: Done 12:30:25.964 [PLANETINFO LOGGING]: 2 151 12:30:26.983 [planetinfo.record]: seed = 145 119 53 208 12:30:26.983 [planetinfo.record]: coordinates = 119 254 12:30:26.983 [planetinfo.record]: government = 2; 12:30:26.983 [planetinfo.record]: economy = 6; 12:30:26.983 [planetinfo.record]: techlevel = 5; 12:30:26.983 [planetinfo.record]: population = 29; 12:30:26.983 [planetinfo.record]: productivity = 5568; 12:30:26.984 [planetinfo.record]: name = "Erbidi"; 12:30:26.984 [planetinfo.record]: inhabitant = "Human Colonial"; 12:30:26.984 [planetinfo.record]: inhabitants = "Human Colonials"; 12:30:26.984 [planetinfo.record]: description = "The planet Erbidi is very famous for its hoopy night life but scourged by deadly edible moths."; 12:30:26.987 [planetinfo.record]: air_color = 0.685907, 0.790107, 0.951522, 1; 12:30:26.987 [planetinfo.record]: cloud_alpha = 1.000000; 12:30:26.987 [planetinfo.record]: cloud_color = 0.653114, 0.857422, 0.823903, 1; 12:30:26.987 [planetinfo.record]: cloud_fraction = 0.310000; 12:30:26.987 [planetinfo.record]: land_color = 0.447628, 0.365959, 0.529297, 1; 12:30:26.987 [planetinfo.record]: land_fraction = 0.470000; 12:30:26.987 [planetinfo.record]: polar_cloud_color = 0.753915, 0.88584, 0.864196, 1; 12:30:26.987 [planetinfo.record]: polar_land_color = 0.910538, 0.874005, 0.94707, 1; 12:30:26.987 [planetinfo.record]: polar_sea_color = 0.912546, 0.935352, 0.885022, 1; 12:30:26.987 [planetinfo.record]: sea_color = 0.583434, 0.646484, 0.507339, 1; 12:30:26.987 [planetinfo.record]: rotation_speed = 0.001988 12:30:26.987 [planetinfo.record]: planet zpos = 322850.000000 12:30:26.987 [planetinfo.record]: sun_radius = 62060.184479 12:30:26.987 [planetinfo.record]: sun_vector = -0.615 0.788 -0.026 12:30:26.987 [planetinfo.record]: sun_distance = 557650 12:30:26.988 [planetinfo.record]: corona_flare = 0.092868 12:30:26.988 [planetinfo.record]: corona_hues = 0.982552 12:30:26.988 [planetinfo.record]: sun_color = 0.691452, 0.314481, 0.211795, 1 12:30:26.988 [planetinfo.record]: corona_shimmer = 0.381298 12:30:26.988 [planetinfo.record]: station_vector = 0.634 0.456 0.624 12:30:26.988 [planetinfo.record]: station = coriolis 12:30:31.486 [PLANETINFO OVER]: Done 12:30:31.487 [PLANETINFO LOGGING]: 2 152 12:30:32.491 [planetinfo.record]: seed = 1 164 205 119 12:30:32.491 [planetinfo.record]: coordinates = 164 141 12:30:32.491 [planetinfo.record]: government = 0; 12:30:32.491 [planetinfo.record]: economy = 7; 12:30:32.491 [planetinfo.record]: techlevel = 0; 12:30:32.491 [planetinfo.record]: population = 8; 12:30:32.491 [planetinfo.record]: productivity = 768; 12:30:32.491 [planetinfo.record]: name = "Tiescebi"; 12:30:32.491 [planetinfo.record]: inhabitant = "Blue Bug-Eyed Bird"; 12:30:32.491 [planetinfo.record]: inhabitants = "Blue Bug-Eyed Birds"; 12:30:32.491 [planetinfo.record]: description = "Tiescebi is famous for its inhabitants’ ancient mating traditions but ravaged by dreadful civil war."; 12:30:32.504 [planetinfo.record]: air_color = 0.705259, 0.757339, 0.959326, 1; 12:30:32.504 [planetinfo.record]: cloud_alpha = 1.000000; 12:30:32.504 [planetinfo.record]: cloud_color = 0.708817, 0.812311, 0.880859, 1; 12:30:32.504 [planetinfo.record]: cloud_fraction = 0.390000; 12:30:32.504 [planetinfo.record]: land_color = 0.66, 0.37125, 0.486299, 1; 12:30:32.504 [planetinfo.record]: land_fraction = 0.510000; 12:30:32.504 [planetinfo.record]: polar_cloud_color = 0.786965, 0.852789, 0.896387, 1; 12:30:32.504 [planetinfo.record]: polar_land_color = 0.934, 0.831844, 0.872547, 1; 12:30:32.504 [planetinfo.record]: polar_sea_color = 0.917789, 0.929688, 0.868768, 1; 12:30:32.504 [planetinfo.record]: sea_color = 0.66713, 0.703125, 0.518829, 1; 12:30:32.504 [planetinfo.record]: rotation_speed = 0.004142 12:30:32.504 [planetinfo.record]: planet zpos = 715800.000000 12:30:32.504 [planetinfo.record]: sun_radius = 129451.859131 12:30:32.504 [planetinfo.record]: sun_vector = 0.021 0.293 -0.956 12:30:32.504 [planetinfo.record]: sun_distance = 954400 12:30:32.504 [planetinfo.record]: corona_flare = 0.046577 12:30:32.504 [planetinfo.record]: corona_hues = 0.974854 12:30:32.504 [planetinfo.record]: sun_color = 0.847937, 0.79493, 0.283084, 1 12:30:32.505 [planetinfo.record]: corona_shimmer = 0.443638 12:30:32.512 [planetinfo.record]: station_vector = 0.149 -0.977 0.151 12:30:32.512 [planetinfo.record]: station = coriolis 12:30:37.476 [PLANETINFO OVER]: Done 12:30:37.477 [PLANETINFO LOGGING]: 2 153 12:30:38.482 [planetinfo.record]: seed = 201 230 197 85 12:30:38.483 [planetinfo.record]: coordinates = 230 197 12:30:38.483 [planetinfo.record]: government = 1; 12:30:38.483 [planetinfo.record]: economy = 7; 12:30:38.483 [planetinfo.record]: techlevel = 3; 12:30:38.483 [planetinfo.record]: population = 21; 12:30:38.483 [planetinfo.record]: productivity = 2520; 12:30:38.483 [planetinfo.record]: name = "Lalerive"; 12:30:38.483 [planetinfo.record]: inhabitant = "Yellow Bony Bird"; 12:30:38.483 [planetinfo.record]: inhabitants = "Yellow Bony Birds"; 12:30:38.483 [planetinfo.record]: description = "The planet Lalerive is mildly well known for its hoopy night life."; 12:30:38.487 [planetinfo.record]: air_color = 0.563619, 0.552277, 0.859166, 1; 12:30:38.487 [planetinfo.record]: cloud_alpha = 1.000000; 12:30:38.487 [planetinfo.record]: cloud_color = 0.340173, 0.305901, 0.580078, 1; 12:30:38.487 [planetinfo.record]: cloud_fraction = 0.310000; 12:30:38.487 [planetinfo.record]: land_color = 0.435703, 0.66, 0.649486, 1; 12:30:38.487 [planetinfo.record]: land_fraction = 0.360000; 12:30:38.488 [planetinfo.record]: polar_cloud_color = 0.56432, 0.536218, 0.761035, 1; 12:30:38.488 [planetinfo.record]: polar_land_color = 0.854647, 0.934, 0.93028, 1; 12:30:38.488 [planetinfo.record]: polar_sea_color = 0.937813, 0.94043, 0.892582, 1; 12:30:38.488 [planetinfo.record]: sea_color = 0.589073, 0.595703, 0.474468, 1; 12:30:38.488 [planetinfo.record]: rotation_speed = 0.001011 12:30:38.488 [planetinfo.record]: planet zpos = 562380.000000 12:30:38.488 [planetinfo.record]: sun_radius = 136961.175842 12:30:38.488 [planetinfo.record]: sun_vector = -0.124 0.699 -0.704 12:30:38.488 [planetinfo.record]: sun_distance = 865200 12:30:38.488 [planetinfo.record]: corona_flare = 0.072601 12:30:38.488 [planetinfo.record]: corona_hues = 0.849701 12:30:38.488 [planetinfo.record]: sun_color = 0.745996, 0.417901, 0.19834, 1 12:30:38.488 [planetinfo.record]: corona_shimmer = 0.418600 12:30:38.488 [planetinfo.record]: station_vector = 0.916 -0.234 0.326 12:30:38.488 [planetinfo.record]: station = coriolis 12:30:43.054 [PLANETINFO OVER]: Done 12:30:43.055 [PLANETINFO LOGGING]: 2 154 12:30:44.067 [planetinfo.record]: seed = 57 150 173 214 12:30:44.067 [planetinfo.record]: coordinates = 150 62 12:30:44.067 [planetinfo.record]: government = 7; 12:30:44.067 [planetinfo.record]: economy = 6; 12:30:44.067 [planetinfo.record]: techlevel = 7; 12:30:44.067 [planetinfo.record]: population = 42; 12:30:44.067 [planetinfo.record]: productivity = 14784; 12:30:44.067 [planetinfo.record]: name = "Vemaed"; 12:30:44.067 [planetinfo.record]: inhabitant = "Slimy Lizard"; 12:30:44.067 [planetinfo.record]: inhabitants = "Slimy Lizards"; 12:30:44.067 [planetinfo.record]: description = "The world Vemaed is most well known for its exotic night life."; 12:30:44.084 [planetinfo.record]: air_color = 0.465995, 0.477637, 0.922904, 1; 12:30:44.084 [planetinfo.record]: cloud_alpha = 1.000000; 12:30:44.084 [planetinfo.record]: cloud_color = 0.0873947, 0.13015, 0.771484, 1; 12:30:44.084 [planetinfo.record]: cloud_fraction = 0.410000; 12:30:44.084 [planetinfo.record]: land_color = 0.18045, 0.505586, 0.632812, 1; 12:30:44.084 [planetinfo.record]: land_fraction = 0.370000; 12:30:44.084 [planetinfo.record]: polar_cloud_color = 0.377668, 0.407012, 0.847168, 1; 12:30:44.084 [planetinfo.record]: polar_land_color = 0.769317, 0.889637, 0.936719, 1; 12:30:44.084 [planetinfo.record]: polar_sea_color = 0.901306, 0.927539, 0.866488, 1; 12:30:44.084 [planetinfo.record]: sea_color = 0.642635, 0.724609, 0.533833, 1; 12:30:44.084 [planetinfo.record]: rotation_speed = 0.002621 12:30:44.084 [planetinfo.record]: planet zpos = 585260.000000 12:30:44.084 [planetinfo.record]: sun_radius = 120797.529907 12:30:44.084 [planetinfo.record]: sun_vector = 0.161 0.877 0.454 12:30:44.084 [planetinfo.record]: sun_distance = 765340 12:30:44.084 [planetinfo.record]: corona_flare = 0.026706 12:30:44.084 [planetinfo.record]: corona_hues = 0.951279 12:30:44.084 [planetinfo.record]: sun_color = 0.254993, 0.459186, 0.689633, 1 12:30:44.084 [planetinfo.record]: corona_shimmer = 0.698618 12:30:44.085 [planetinfo.record]: station_vector = -0.929 0.354 0.108 12:30:44.085 [planetinfo.record]: station = coriolis 12:30:48.790 [PLANETINFO OVER]: Done 12:30:48.791 [PLANETINFO LOGGING]: 2 155 12:30:49.800 [planetinfo.record]: seed = 97 154 21 94 12:30:49.800 [planetinfo.record]: coordinates = 154 24 12:30:49.800 [planetinfo.record]: government = 4; 12:30:49.800 [planetinfo.record]: economy = 0; 12:30:49.800 [planetinfo.record]: techlevel = 11; 12:30:49.800 [planetinfo.record]: population = 49; 12:30:49.800 [planetinfo.record]: productivity = 31360; 12:30:49.800 [planetinfo.record]: name = "Rieresed"; 12:30:49.800 [planetinfo.record]: inhabitant = "Human Colonial"; 12:30:49.800 [planetinfo.record]: inhabitants = "Human Colonials"; 12:30:49.800 [planetinfo.record]: description = "This world is ravaged by dreadful civil war."; 12:30:49.802 [planetinfo.record]: air_color = 0.687197, 0.695125, 0.99835, 1; 12:30:49.802 [planetinfo.record]: cloud_alpha = 1.000000; 12:30:49.802 [planetinfo.record]: cloud_color = 0.662766, 0.683721, 0.998047, 1; 12:30:49.802 [planetinfo.record]: cloud_fraction = 0.540000; 12:30:49.802 [planetinfo.record]: land_color = 0.66, 0.298901, 0.103125, 1; 12:30:49.802 [planetinfo.record]: land_fraction = 0.320000; 12:30:49.802 [planetinfo.record]: polar_cloud_color = 0.749843, 0.762298, 0.949121, 1; 12:30:49.802 [planetinfo.record]: polar_land_color = 0.934, 0.806248, 0.736984, 1; 12:30:49.802 [planetinfo.record]: polar_sea_color = 0.934037, 0.936914, 0.884304, 1; 12:30:49.802 [planetinfo.record]: sea_color = 0.62311, 0.630859, 0.489162, 1; 12:30:49.803 [planetinfo.record]: rotation_speed = 0.001316 12:30:49.803 [planetinfo.record]: planet zpos = 852020.000000 12:30:49.803 [planetinfo.record]: sun_radius = 122144.454651 12:30:49.803 [planetinfo.record]: sun_vector = -0.107 0.848 0.519 12:30:49.803 [planetinfo.record]: sun_distance = 1245260 12:30:49.803 [planetinfo.record]: corona_flare = 0.052448 12:30:49.803 [planetinfo.record]: corona_hues = 0.611076 12:30:49.803 [planetinfo.record]: sun_color = 0.402548, 0.655033, 0.795093, 1 12:30:49.803 [planetinfo.record]: corona_shimmer = 0.312842 12:30:49.803 [planetinfo.record]: station_vector = 0.532 -0.800 0.279 12:30:49.803 [planetinfo.record]: station = dodecahedron 12:30:54.972 [PLANETINFO OVER]: Done 12:30:54.974 [PLANETINFO LOGGING]: 2 156 12:30:55.979 [planetinfo.record]: seed = 17 120 13 146 12:30:55.979 [planetinfo.record]: coordinates = 120 9 12:30:55.979 [planetinfo.record]: government = 2; 12:30:55.979 [planetinfo.record]: economy = 1; 12:30:55.979 [planetinfo.record]: techlevel = 7; 12:30:55.979 [planetinfo.record]: population = 32; 12:30:55.979 [planetinfo.record]: productivity = 13824; 12:30:55.979 [planetinfo.record]: name = "Enbeis"; 12:30:55.979 [planetinfo.record]: inhabitant = "Human Colonial"; 12:30:55.979 [planetinfo.record]: inhabitants = "Human Colonials"; 12:30:55.979 [planetinfo.record]: description = "Enbeis is ravaged by dreadful civil war."; 12:30:55.996 [planetinfo.record]: air_color = 0.720783, 0.591812, 0.878027, 1; 12:30:55.996 [planetinfo.record]: cloud_alpha = 1.000000; 12:30:55.996 [planetinfo.record]: cloud_color = 0.636719, 0.395462, 0.548132, 1; 12:30:55.996 [planetinfo.record]: cloud_fraction = 0.400000; 12:30:55.996 [planetinfo.record]: land_color = 0.204391, 0.556641, 0.507106, 1; 12:30:55.996 [planetinfo.record]: land_fraction = 0.340000; 12:30:55.996 [planetinfo.record]: polar_cloud_color = 0.786523, 0.600262, 0.718131, 1; 12:30:55.996 [planetinfo.record]: polar_land_color = 0.794939, 0.944336, 0.923327, 1; 12:30:55.996 [planetinfo.record]: polar_sea_color = 0.869201, 0.921484, 0.914132, 1; 12:30:55.996 [planetinfo.record]: sea_color = 0.606963, 0.785156, 0.760098, 1; 12:30:55.996 [planetinfo.record]: rotation_speed = 0.002272 12:30:55.996 [planetinfo.record]: planet zpos = 379280.000000 12:30:55.996 [planetinfo.record]: sun_radius = 74454.302979 12:30:55.996 [planetinfo.record]: sun_vector = -0.216 -0.665 -0.715 12:30:55.996 [planetinfo.record]: sun_distance = 655120 12:30:55.996 [planetinfo.record]: corona_flare = 0.027847 12:30:55.996 [planetinfo.record]: corona_hues = 0.628136 12:30:55.996 [planetinfo.record]: sun_color = 0.660093, 0.679859, 0.808664, 1 12:30:55.997 [planetinfo.record]: corona_shimmer = 0.995050 12:30:55.997 [planetinfo.record]: station_vector = -0.588 0.764 0.265 12:30:55.997 [planetinfo.record]: station = coriolis 12:31:01.514 [PLANETINFO OVER]: Done 12:31:01.515 [PLANETINFO LOGGING]: 2 157 12:31:02.522 [planetinfo.record]: seed = 217 194 165 243 12:31:02.522 [planetinfo.record]: coordinates = 194 29 12:31:02.522 [planetinfo.record]: government = 3; 12:31:02.522 [planetinfo.record]: economy = 5; 12:31:02.522 [planetinfo.record]: techlevel = 6; 12:31:02.522 [planetinfo.record]: population = 33; 12:31:02.522 [planetinfo.record]: productivity = 9240; 12:31:02.522 [planetinfo.record]: name = "Bebearen"; 12:31:02.522 [planetinfo.record]: inhabitant = "Lizard"; 12:31:02.522 [planetinfo.record]: inhabitants = "Lizards"; 12:31:02.522 [planetinfo.record]: description = "This planet is beset by deadly earthquakes."; 12:31:02.531 [planetinfo.record]: air_color = 0.674398, 0.875384, 0.879979, 1; 12:31:02.532 [planetinfo.record]: cloud_alpha = 1.000000; 12:31:02.532 [planetinfo.record]: cloud_color = 0.63748, 0.642578, 0.577316, 1; 12:31:02.532 [planetinfo.record]: cloud_fraction = 0.230000; 12:31:02.532 [planetinfo.record]: land_color = 0.276001, 0.303308, 0.59375, 1; 12:31:02.532 [planetinfo.record]: land_fraction = 0.620000; 12:31:02.532 [planetinfo.record]: polar_cloud_color = 0.785247, 0.78916, 0.739067, 1; 12:31:02.532 [planetinfo.record]: polar_land_color = 0.81478, 0.825594, 0.940625, 1; 12:31:02.532 [planetinfo.record]: polar_sea_color = 0.88907, 0.926367, 0.865936, 1; 12:31:02.532 [planetinfo.record]: sea_color = 0.617744, 0.736328, 0.544192, 1; 12:31:02.532 [planetinfo.record]: rotation_speed = 0.002953 12:31:02.532 [planetinfo.record]: planet zpos = 377800.000000 12:31:02.532 [planetinfo.record]: sun_radius = 94843.733826 12:31:02.532 [planetinfo.record]: sun_vector = -0.152 -0.826 0.543 12:31:02.532 [planetinfo.record]: sun_distance = 717820 12:31:02.532 [planetinfo.record]: corona_flare = 0.027403 12:31:02.532 [planetinfo.record]: corona_hues = 0.773552 12:31:02.533 [planetinfo.record]: sun_color = 0.264706, 0.335673, 0.753204, 1 12:31:02.533 [planetinfo.record]: corona_shimmer = 1.356797 12:31:02.534 [planetinfo.record]: station_vector = 0.168 -0.985 0.042 12:31:02.534 [planetinfo.record]: station = coriolis 12:31:06.609 [PLANETINFO OVER]: Done 12:31:06.609 [PLANETINFO LOGGING]: 2 158 12:31:07.613 [planetinfo.record]: seed = 9 82 109 176 12:31:07.613 [planetinfo.record]: coordinates = 82 138 12:31:07.613 [planetinfo.record]: government = 1; 12:31:07.613 [planetinfo.record]: economy = 2; 12:31:07.613 [planetinfo.record]: techlevel = 8; 12:31:07.614 [planetinfo.record]: population = 36; 12:31:07.614 [planetinfo.record]: productivity = 11520; 12:31:07.614 [planetinfo.record]: name = "Erinain"; 12:31:07.614 [planetinfo.record]: inhabitant = "Human Colonial"; 12:31:07.614 [planetinfo.record]: inhabitants = "Human Colonials"; 12:31:07.614 [planetinfo.record]: description = "Erinain is mildly famous for its vast rain forests and its great dense forests."; 12:31:07.636 [planetinfo.record]: air_color = 0.655167, 0.886482, 0.871793, 1; 12:31:07.636 [planetinfo.record]: cloud_alpha = 1.000000; 12:31:07.636 [planetinfo.record]: cloud_color = 0.662109, 0.640267, 0.54055, 1; 12:31:07.636 [planetinfo.record]: cloud_fraction = 0.470000; 12:31:07.636 [planetinfo.record]: land_color = 0.1427, 0.580675, 0.652344, 1; 12:31:07.636 [planetinfo.record]: land_fraction = 0.260000; 12:31:07.636 [planetinfo.record]: polar_cloud_color = 0.797949, 0.781497, 0.706388, 1; 12:31:07.636 [planetinfo.record]: polar_land_color = 0.752194, 0.909092, 0.934766, 1; 12:31:07.636 [planetinfo.record]: polar_sea_color = 0.872256, 0.925586, 0.888089, 1; 12:31:07.636 [planetinfo.record]: sea_color = 0.572639, 0.744141, 0.623554, 1; 12:31:07.636 [planetinfo.record]: rotation_speed = 0.003218 12:31:07.636 [planetinfo.record]: planet zpos = 318780.000000 12:31:07.636 [planetinfo.record]: sun_radius = 70749.299927 12:31:07.636 [planetinfo.record]: sun_vector = 0.613 0.042 -0.789 12:31:07.636 [planetinfo.record]: sun_distance = 521640 12:31:07.636 [planetinfo.record]: corona_flare = 0.043698 12:31:07.637 [planetinfo.record]: corona_hues = 0.785110 12:31:07.637 [planetinfo.record]: sun_color = 0.686264, 0.671588, 0.456827, 1 12:31:07.637 [planetinfo.record]: corona_shimmer = 0.237166 12:31:07.637 [planetinfo.record]: station_vector = 0.678 0.723 0.135 12:31:07.637 [planetinfo.record]: station = coriolis 12:31:12.663 [PLANETINFO OVER]: Done 12:31:12.664 [PLANETINFO LOGGING]: 2 159 12:31:13.668 [planetinfo.record]: seed = 177 204 245 232 12:31:13.668 [planetinfo.record]: coordinates = 204 143 12:31:13.668 [planetinfo.record]: government = 6; 12:31:13.668 [planetinfo.record]: economy = 7; 12:31:13.668 [planetinfo.record]: techlevel = 3; 12:31:13.668 [planetinfo.record]: population = 26; 12:31:13.668 [planetinfo.record]: productivity = 6240; 12:31:13.668 [planetinfo.record]: name = "Uscequus"; 12:31:13.668 [planetinfo.record]: inhabitant = "Small Bony Lobster"; 12:31:13.668 [planetinfo.record]: inhabitants = "Small Bony Lobsters"; 12:31:13.669 [planetinfo.record]: description = "This planet is plagued by deadly earthquakes."; 12:31:13.679 [planetinfo.record]: air_color = 0.500612, 0.504543, 0.89884, 1; 12:31:13.679 [planetinfo.record]: cloud_alpha = 1.000000; 12:31:13.680 [planetinfo.record]: cloud_color = 0.188461, 0.196442, 0.699219, 1; 12:31:13.680 [planetinfo.record]: cloud_fraction = 0.430000; 12:31:13.680 [planetinfo.record]: land_color = 0.525937, 0.66, 0.553169, 1; 12:31:13.680 [planetinfo.record]: land_fraction = 0.610000; 12:31:13.680 [planetinfo.record]: polar_cloud_color = 0.442726, 0.448538, 0.814648, 1; 12:31:13.680 [planetinfo.record]: polar_land_color = 0.88657, 0.934, 0.896204, 1; 12:31:13.680 [planetinfo.record]: polar_sea_color = 0.800211, 0.945117, 0.931532, 1; 12:31:13.680 [planetinfo.record]: sea_color = 0.212242, 0.548828, 0.517273, 1; 12:31:13.680 [planetinfo.record]: rotation_speed = 0.000762 12:31:13.680 [planetinfo.record]: planet zpos = 506800.000000 12:31:13.680 [planetinfo.record]: sun_radius = 147854.816895 12:31:13.680 [planetinfo.record]: sun_vector = -0.407 -0.371 -0.835 12:31:13.680 [planetinfo.record]: sun_distance = 912240 12:31:13.680 [planetinfo.record]: corona_flare = 0.061110 12:31:13.680 [planetinfo.record]: corona_hues = 0.634399 12:31:13.680 [planetinfo.record]: sun_color = 0.570023, 0.742441, 0.810181, 1 12:31:13.681 [planetinfo.record]: corona_shimmer = 0.318167 12:31:13.681 [planetinfo.record]: station_vector = -0.231 -0.769 0.596 12:31:13.681 [planetinfo.record]: station = coriolis 12:31:17.799 [PLANETINFO OVER]: Done 12:31:17.800 [PLANETINFO LOGGING]: 2 160 12:31:18.819 [planetinfo.record]: seed = 161 40 77 104 12:31:18.819 [planetinfo.record]: coordinates = 40 250 12:31:18.819 [planetinfo.record]: government = 4; 12:31:18.819 [planetinfo.record]: economy = 2; 12:31:18.819 [planetinfo.record]: techlevel = 7; 12:31:18.819 [planetinfo.record]: population = 35; 12:31:18.820 [planetinfo.record]: productivity = 17920; 12:31:18.820 [planetinfo.record]: name = "Usmate"; 12:31:18.820 [planetinfo.record]: inhabitant = "Human Colonial"; 12:31:18.820 [planetinfo.record]: inhabitants = "Human Colonials"; 12:31:18.820 [planetinfo.record]: description = "The world Usmate is mildly fabled for the Usmateian mountain A’oid but ravaged by lethal vicious wolfs."; 12:31:18.840 [planetinfo.record]: air_color = 0.416413, 0.84876, 0.844925, 1; 12:31:18.840 [planetinfo.record]: cloud_alpha = 1.000000; 12:31:18.840 [planetinfo.record]: cloud_color = 0.548828, 0.540856, 0.0385895, 1; 12:31:18.847 [planetinfo.record]: cloud_fraction = 0.340000; 12:31:18.847 [planetinfo.record]: land_color = 0.66, 0.0257813, 0.0257813, 1; 12:31:18.848 [planetinfo.record]: land_fraction = 0.240000; 12:31:18.848 [planetinfo.record]: polar_cloud_color = 0.746973, 0.740191, 0.312941, 1; 12:31:18.848 [planetinfo.record]: polar_land_color = 0.934, 0.709621, 0.709621, 1; 12:31:18.848 [planetinfo.record]: polar_sea_color = 0.944727, 0.882268, 0.923256, 1; 12:31:18.848 [planetinfo.record]: sea_color = 0.552734, 0.406562, 0.502488, 1; 12:31:18.848 [planetinfo.record]: rotation_speed = 0.000365 12:31:18.848 [planetinfo.record]: planet zpos = 637520.000000 12:31:18.848 [planetinfo.record]: sun_radius = 155825.617676 12:31:18.848 [planetinfo.record]: sun_vector = -0.169 -0.707 -0.687 12:31:18.848 [planetinfo.record]: sun_distance = 931760 12:31:18.848 [planetinfo.record]: corona_flare = 0.085503 12:31:18.848 [planetinfo.record]: corona_hues = 0.765297 12:31:18.848 [planetinfo.record]: sun_color = 0.392993, 0.443653, 0.656723, 1 12:31:18.848 [planetinfo.record]: corona_shimmer = 0.390793 12:31:18.848 [planetinfo.record]: station_vector = 0.501 0.101 0.859 12:31:18.848 [planetinfo.record]: station = coriolis 12:31:24.133 [PLANETINFO OVER]: Done 12:31:24.133 [PLANETINFO LOGGING]: 2 161 12:31:25.144 [planetinfo.record]: seed = 105 16 133 184 12:31:25.144 [planetinfo.record]: coordinates = 16 28 12:31:25.144 [planetinfo.record]: government = 5; 12:31:25.144 [planetinfo.record]: economy = 4; 12:31:25.144 [planetinfo.record]: techlevel = 6; 12:31:25.144 [planetinfo.record]: population = 34; 12:31:25.144 [planetinfo.record]: productivity = 14688; 12:31:25.144 [planetinfo.record]: name = "Edcere"; 12:31:25.144 [planetinfo.record]: inhabitant = "Harmless Fat Bird"; 12:31:25.144 [planetinfo.record]: inhabitants = "Harmless Fat Birds"; 12:31:25.144 [planetinfo.record]: description = "This planet is a tedious place."; 12:31:25.153 [planetinfo.record]: air_color = 0.614871, 0.465049, 0.960627, 1; 12:31:25.153 [planetinfo.record]: cloud_alpha = 1.000000; 12:31:25.153 [planetinfo.record]: cloud_color = 0.819153, 0.0449295, 0.884766, 1; 12:31:25.153 [planetinfo.record]: cloud_fraction = 0.130000; 12:31:25.154 [planetinfo.record]: land_color = 0.66, 0.66, 0.66, 1; 12:31:25.154 [planetinfo.record]: land_fraction = 0.330000; 12:31:25.154 [planetinfo.record]: polar_cloud_color = 0.856517, 0.36531, 0.898145, 1; 12:31:25.154 [planetinfo.record]: polar_land_color = 0.934, 0.934, 0.934, 1; 12:31:25.154 [planetinfo.record]: polar_sea_color = 0.736839, 0.936133, 0.847385, 1; 12:31:25.154 [planetinfo.record]: sea_color = 0.0948029, 0.638672, 0.39648, 1; 12:31:25.154 [planetinfo.record]: rotation_speed = 0.004774 12:31:25.154 [planetinfo.record]: planet zpos = 439200.000000 12:31:25.154 [planetinfo.record]: sun_radius = 126963.696289 12:31:25.154 [planetinfo.record]: sun_vector = -0.484 0.353 -0.801 12:31:25.154 [planetinfo.record]: sun_distance = 829600 12:31:25.154 [planetinfo.record]: corona_flare = 0.015114 12:31:25.154 [planetinfo.record]: corona_hues = 0.544914 12:31:25.154 [planetinfo.record]: sun_color = 0.784628, 0.668714, 0.454601, 1 12:31:25.154 [planetinfo.record]: corona_shimmer = 0.386310 12:31:25.155 [planetinfo.record]: station_vector = 0.854 0.124 0.505 12:31:25.155 [planetinfo.record]: station = coriolis 12:31:29.642 [PLANETINFO OVER]: Done 12:31:29.643 [PLANETINFO LOGGING]: 2 162 12:31:30.649 [planetinfo.record]: seed = 89 76 45 224 12:31:30.649 [planetinfo.record]: coordinates = 76 174 12:31:30.649 [planetinfo.record]: government = 3; 12:31:30.649 [planetinfo.record]: economy = 6; 12:31:30.649 [planetinfo.record]: techlevel = 3; 12:31:30.649 [planetinfo.record]: population = 22; 12:31:30.649 [planetinfo.record]: productivity = 4928; 12:31:30.649 [planetinfo.record]: name = "Qusoxe"; 12:31:30.649 [planetinfo.record]: inhabitant = "Human Colonial"; 12:31:30.649 [planetinfo.record]: inhabitants = "Human Colonials"; 12:31:30.649 [planetinfo.record]: description = "Qusoxe is cursed by dreadful civil war."; 12:31:30.672 [planetinfo.record]: air_color = 0.763496, 0.46536, 0.990545, 1; 12:31:30.672 [planetinfo.record]: cloud_alpha = 1.000000; 12:31:30.672 [planetinfo.record]: cloud_color = 0.974609, 0.00761414, 0.279582, 1; 12:31:30.672 [planetinfo.record]: cloud_fraction = 0.580000; 12:31:30.672 [planetinfo.record]: land_color = 0.401646, 0.277786, 0.646484, 1; 12:31:30.672 [planetinfo.record]: land_fraction = 0.680000; 12:31:30.672 [planetinfo.record]: polar_cloud_color = 0.938574, 0.356548, 0.520243, 1; 12:31:30.672 [planetinfo.record]: polar_land_color = 0.846792, 0.801991, 0.935352, 1; 12:31:30.672 [planetinfo.record]: polar_sea_color = 0.924276, 0.930469, 0.869498, 1; 12:31:30.672 [planetinfo.record]: sea_color = 0.676803, 0.695312, 0.513065, 1; 12:31:30.672 [planetinfo.record]: rotation_speed = 0.003863 12:31:30.672 [planetinfo.record]: planet zpos = 318120.000000 12:31:30.672 [planetinfo.record]: sun_radius = 75148.048096 12:31:30.672 [planetinfo.record]: sun_vector = -0.312 0.564 0.764 12:31:30.672 [planetinfo.record]: sun_distance = 520560 12:31:30.672 [planetinfo.record]: corona_flare = 0.011650 12:31:30.672 [planetinfo.record]: corona_hues = 0.687309 12:31:30.672 [planetinfo.record]: sun_color = 0.729321, 0.719576, 0.692731, 1 12:31:30.672 [planetinfo.record]: corona_shimmer = 0.323019 12:31:30.672 [planetinfo.record]: station_vector = 0.662 0.567 0.491 12:31:30.673 [planetinfo.record]: station = coriolis 12:31:35.084 [PLANETINFO OVER]: Done 12:31:35.085 [PLANETINFO LOGGING]: 2 163 12:31:36.099 [planetinfo.record]: seed = 129 194 213 164 12:31:36.099 [planetinfo.record]: coordinates = 194 7 12:31:36.099 [planetinfo.record]: government = 0; 12:31:36.099 [planetinfo.record]: economy = 7; 12:31:36.099 [planetinfo.record]: techlevel = 2; 12:31:36.099 [planetinfo.record]: population = 16; 12:31:36.099 [planetinfo.record]: productivity = 1536; 12:31:36.100 [planetinfo.record]: name = "Zarevees"; 12:31:36.100 [planetinfo.record]: inhabitant = "Fierce Harmless Furry Humanoid"; 12:31:36.100 [planetinfo.record]: inhabitants = "Fierce Harmless Furry Humanoids"; 12:31:36.100 [planetinfo.record]: description = "The world Zarevees is most famous for the Zareveesian spotted cat."; 12:31:36.109 [planetinfo.record]: air_color = 0.710165, 0.819447, 0.906645, 1; 12:31:36.109 [planetinfo.record]: cloud_alpha = 1.000000; 12:31:36.109 [planetinfo.record]: cloud_color = 0.685959, 0.722656, 0.698574, 1; 12:31:36.109 [planetinfo.record]: cloud_fraction = 0.560000; 12:31:36.109 [planetinfo.record]: land_color = 0.251175, 0.462306, 0.544922, 1; 12:31:36.109 [planetinfo.record]: land_fraction = 0.300000; 12:31:36.109 [planetinfo.record]: polar_cloud_color = 0.799005, 0.825195, 0.808008, 1; 12:31:36.109 [planetinfo.record]: polar_land_color = 0.818086, 0.90967, 0.945508, 1; 12:31:36.109 [planetinfo.record]: polar_sea_color = 0.923828, 0.872059, 0.821882, 1; 12:31:36.109 [planetinfo.record]: sea_color = 0.761719, 0.590978, 0.425491, 1; 12:31:36.109 [planetinfo.record]: rotation_speed = 0.000104 12:31:36.110 [planetinfo.record]: planet zpos = 484080.000000 12:31:36.110 [planetinfo.record]: sun_radius = 107343.942261 12:31:36.110 [planetinfo.record]: sun_vector = 0.192 -0.927 -0.322 12:31:36.110 [planetinfo.record]: sun_distance = 726120 12:31:36.110 [planetinfo.record]: corona_flare = 0.034882 12:31:36.110 [planetinfo.record]: corona_hues = 0.937263 12:31:36.110 [planetinfo.record]: sun_color = 0.820367, 0.801652, 0.705585, 1 12:31:36.110 [planetinfo.record]: corona_shimmer = 0.318108 12:31:36.110 [planetinfo.record]: station_vector = 0.235 0.807 0.541 12:31:36.110 [planetinfo.record]: station = coriolis 12:31:40.694 [PLANETINFO OVER]: Done 12:31:40.695 [PLANETINFO LOGGING]: 2 164 12:31:41.698 [planetinfo.record]: seed = 177 233 141 46 12:31:41.698 [planetinfo.record]: coordinates = 233 214 12:31:41.698 [planetinfo.record]: government = 6; 12:31:41.698 [planetinfo.record]: economy = 6; 12:31:41.698 [planetinfo.record]: techlevel = 5; 12:31:41.698 [planetinfo.record]: population = 33; 12:31:41.698 [planetinfo.record]: productivity = 10560; 12:31:41.698 [planetinfo.record]: name = "Rerebi"; 12:31:41.698 [planetinfo.record]: inhabitant = "Red Frog"; 12:31:41.698 [planetinfo.record]: inhabitants = "Red Frogs"; 12:31:41.698 [planetinfo.record]: description = "Rerebi is very fabled for the Rerebiian mountain Renuiloid."; 12:31:41.712 [planetinfo.record]: air_color = 0.4424, 0.496836, 0.931359, 1; 12:31:41.712 [planetinfo.record]: cloud_alpha = 1.000000; 12:31:41.712 [planetinfo.record]: cloud_color = 0.0186768, 0.267943, 0.796875, 1; 12:31:41.712 [planetinfo.record]: cloud_fraction = 0.560000; 12:31:41.712 [planetinfo.record]: land_color = 0.567188, 0.581689, 0.66, 1; 12:31:41.712 [planetinfo.record]: land_fraction = 0.470000; 12:31:41.712 [planetinfo.record]: polar_cloud_color = 0.33455, 0.502408, 0.858594, 1; 12:31:41.712 [planetinfo.record]: polar_land_color = 0.901164, 0.906295, 0.934, 1; 12:31:41.712 [planetinfo.record]: polar_sea_color = 0.925391, 0.906554, 0.84225, 1; 12:31:41.712 [planetinfo.record]: sea_color = 0.746094, 0.685346, 0.477966, 1; 12:31:41.712 [planetinfo.record]: rotation_speed = 0.003493 12:31:41.712 [planetinfo.record]: planet zpos = 795960.000000 12:31:41.712 [planetinfo.record]: sun_radius = 168255.089264 12:31:41.712 [planetinfo.record]: sun_vector = 0.256 -0.919 -0.301 12:31:41.719 [planetinfo.record]: sun_distance = 1392930 12:31:41.719 [planetinfo.record]: corona_flare = 0.059482 12:31:41.720 [planetinfo.record]: corona_hues = 0.834061 12:31:41.720 [planetinfo.record]: sun_color = 0.777295, 0.33815, 0.243427, 1 12:31:41.720 [planetinfo.record]: corona_shimmer = 0.420377 12:31:41.720 [planetinfo.record]: station_vector = 0.088 0.318 0.944 12:31:41.720 [planetinfo.record]: station = coriolis 12:31:46.744 [PLANETINFO OVER]: Done 12:31:46.745 [PLANETINFO LOGGING]: 2 165 12:31:47.754 [planetinfo.record]: seed = 121 35 101 24 12:31:47.754 [planetinfo.record]: coordinates = 35 6 12:31:47.754 [planetinfo.record]: government = 7; 12:31:47.754 [planetinfo.record]: economy = 6; 12:31:47.754 [planetinfo.record]: techlevel = 8; 12:31:47.754 [planetinfo.record]: population = 46; 12:31:47.754 [planetinfo.record]: productivity = 16192; 12:31:47.754 [planetinfo.record]: name = "Edxeri"; 12:31:47.754 [planetinfo.record]: inhabitant = "Human Colonial"; 12:31:47.754 [planetinfo.record]: inhabitants = "Human Colonials"; 12:31:47.754 [planetinfo.record]: description = "The planet Edxeri is reasonably noted for its fabulous goat soup and Zero-G cricket."; 12:31:47.765 [planetinfo.record]: air_color = 0.471869, 0.646511, 0.869572, 1; 12:31:47.765 [planetinfo.record]: cloud_alpha = 1.000000; 12:31:47.765 [planetinfo.record]: cloud_color = 0.138504, 0.611328, 0.467265, 1; 12:31:47.765 [planetinfo.record]: cloud_fraction = 0.380000; 12:31:47.765 [planetinfo.record]: land_color = 0.542969, 0.483582, 0.493325, 1; 12:31:47.765 [planetinfo.record]: land_fraction = 0.360000; 12:31:47.765 [planetinfo.record]: polar_cloud_color = 0.400417, 0.775098, 0.660937, 1; 12:31:47.765 [planetinfo.record]: polar_land_color = 0.945703, 0.919844, 0.924087, 1; 12:31:47.765 [planetinfo.record]: polar_sea_color = 0.914786, 0.925977, 0.860869, 1; 12:31:47.765 [planetinfo.record]: sea_color = 0.704452, 0.740234, 0.532043, 1; 12:31:47.765 [planetinfo.record]: rotation_speed = 0.001578 12:31:47.765 [planetinfo.record]: planet zpos = 489900.000000 12:31:47.766 [planetinfo.record]: sun_radius = 105103.195038 12:31:47.766 [planetinfo.record]: sun_vector = 0.641 -0.569 0.515 12:31:47.766 [planetinfo.record]: sun_distance = 1175760 12:31:47.766 [planetinfo.record]: corona_flare = 0.038522 12:31:47.766 [planetinfo.record]: corona_hues = 0.822815 12:31:47.766 [planetinfo.record]: sun_color = 0.26746, 0.624838, 0.746432, 1 12:31:47.766 [planetinfo.record]: corona_shimmer = 0.326957 12:31:47.766 [planetinfo.record]: station_vector = 0.659 -0.675 0.332 12:31:47.766 [planetinfo.record]: station = coriolis 12:31:52.277 [PLANETINFO OVER]: Done 12:31:52.278 [PLANETINFO LOGGING]: 2 166 12:31:53.281 [planetinfo.record]: seed = 41 217 237 153 12:31:53.282 [planetinfo.record]: coordinates = 217 126 12:31:53.282 [planetinfo.record]: government = 5; 12:31:53.282 [planetinfo.record]: economy = 6; 12:31:53.282 [planetinfo.record]: techlevel = 5; 12:31:53.282 [planetinfo.record]: population = 32; 12:31:53.282 [planetinfo.record]: productivity = 9216; 12:31:53.282 [planetinfo.record]: name = "Oratzaa"; 12:31:53.282 [planetinfo.record]: inhabitant = "Black Rodent"; 12:31:53.282 [planetinfo.record]: inhabitants = "Black Rodents"; 12:31:53.282 [planetinfo.record]: description = "The world Oratzaa is reasonably fabled for its exciting sit coms and its inhabitants’ ancient loathing of casinos."; 12:31:53.287 [planetinfo.record]: air_color = 0.640804, 0.835764, 0.959977, 1; 12:31:53.287 [planetinfo.record]: cloud_alpha = 1.000000; 12:31:53.287 [planetinfo.record]: cloud_color = 0.531067, 0.882812, 0.594271, 1; 12:31:53.287 [planetinfo.record]: cloud_fraction = 0.540000; 12:31:53.287 [planetinfo.record]: land_color = 0.370686, 0.172734, 0.66, 1; 12:31:53.287 [planetinfo.record]: land_fraction = 0.430000; 12:31:53.288 [planetinfo.record]: polar_cloud_color = 0.673825, 0.897266, 0.713975, 1; 12:31:53.288 [planetinfo.record]: polar_land_color = 0.831644, 0.761611, 0.934, 1; 12:31:53.288 [planetinfo.record]: polar_sea_color = 0.932944, 0.933398, 0.875243, 1; 12:31:53.288 [planetinfo.record]: sea_color = 0.664719, 0.666016, 0.500032, 1; 12:31:53.288 [planetinfo.record]: rotation_speed = 0.004478 12:31:53.288 [planetinfo.record]: planet zpos = 693810.000000 12:31:53.288 [planetinfo.record]: sun_radius = 119993.734589 12:31:53.288 [planetinfo.record]: sun_vector = 0.361 0.448 0.818 12:31:53.288 [planetinfo.record]: sun_distance = 1067400 12:31:53.288 [planetinfo.record]: corona_flare = 0.069635 12:31:53.288 [planetinfo.record]: corona_hues = 0.736191 12:31:53.288 [planetinfo.record]: sun_color = 0.313361, 0.619255, 0.781284, 1 12:31:53.288 [planetinfo.record]: corona_shimmer = 0.425597 12:31:53.288 [planetinfo.record]: station_vector = 0.352 -0.908 0.226 12:31:53.289 [planetinfo.record]: station = coriolis 12:31:58.231 [PLANETINFO OVER]: Done 12:31:58.232 [PLANETINFO LOGGING]: 2 167 12:31:59.250 [planetinfo.record]: seed = 209 239 181 69 12:31:59.250 [planetinfo.record]: coordinates = 239 100 12:31:59.250 [planetinfo.record]: government = 2; 12:31:59.250 [planetinfo.record]: economy = 4; 12:31:59.250 [planetinfo.record]: techlevel = 7; 12:31:59.250 [planetinfo.record]: population = 35; 12:31:59.250 [planetinfo.record]: productivity = 10080; 12:31:59.250 [planetinfo.record]: name = "Cequaa"; 12:31:59.250 [planetinfo.record]: inhabitant = "Fierce Yellow Bony Bird"; 12:31:59.250 [planetinfo.record]: inhabitants = "Fierce Yellow Bony Birds"; 12:31:59.250 [planetinfo.record]: description = "The planet Cequaa is mildly well known for its exotic night life."; 12:31:59.264 [planetinfo.record]: air_color = 0.545955, 0.583815, 0.844207, 1; 12:31:59.264 [planetinfo.record]: cloud_alpha = 1.000000; 12:31:59.264 [planetinfo.record]: cloud_color = 0.288483, 0.379058, 0.535156, 1; 12:31:59.264 [planetinfo.record]: cloud_fraction = 0.530000; 12:31:59.264 [planetinfo.record]: land_color = 0.451172, 0.61595, 0.66, 1; 12:31:59.264 [planetinfo.record]: land_fraction = 0.290000; 12:31:59.264 [planetinfo.record]: polar_cloud_color = 0.5274, 0.605766, 0.74082, 1; 12:31:59.264 [planetinfo.record]: polar_land_color = 0.860119, 0.918416, 0.934, 1; 12:31:59.264 [planetinfo.record]: polar_sea_color = 0.938111, 0.939648, 0.890464, 1; 12:31:59.264 [planetinfo.record]: sea_color = 0.599567, 0.603516, 0.477155, 1; 12:31:59.264 [planetinfo.record]: rotation_speed = 0.004442 12:31:59.264 [planetinfo.record]: planet zpos = 390150.000000 12:31:59.264 [planetinfo.record]: sun_radius = 125319.838715 12:31:59.264 [planetinfo.record]: sun_vector = -0.022 -0.803 0.595 12:31:59.264 [planetinfo.record]: sun_distance = 693600 12:31:59.264 [planetinfo.record]: corona_flare = 0.076042 12:31:59.264 [planetinfo.record]: corona_hues = 0.786110 12:31:59.264 [planetinfo.record]: sun_color = 0.823184, 0.770128, 0.646341, 1 12:31:59.265 [planetinfo.record]: corona_shimmer = 0.290419 12:31:59.265 [planetinfo.record]: station_vector = -0.957 0.258 0.132 12:31:59.265 [planetinfo.record]: station = coriolis 12:32:04.179 [PLANETINFO OVER]: Done 12:32:04.180 [PLANETINFO LOGGING]: 2 168 12:32:05.187 [planetinfo.record]: seed = 65 175 205 24 12:32:05.187 [planetinfo.record]: coordinates = 175 207 12:32:05.187 [planetinfo.record]: government = 0; 12:32:05.187 [planetinfo.record]: economy = 7; 12:32:05.187 [planetinfo.record]: techlevel = 3; 12:32:05.187 [planetinfo.record]: population = 20; 12:32:05.187 [planetinfo.record]: productivity = 1920; 12:32:05.187 [planetinfo.record]: name = "Edtion"; 12:32:05.187 [planetinfo.record]: inhabitant = "Green Slimy Rodent"; 12:32:05.188 [planetinfo.record]: inhabitants = "Green Slimy Rodents"; 12:32:05.188 [planetinfo.record]: description = "This planet is plagued by deadly earthquakes."; 12:32:05.200 [planetinfo.record]: air_color = 0.736312, 0.569006, 0.905344, 1; 12:32:05.201 [planetinfo.record]: cloud_alpha = 1.000000; 12:32:05.201 [planetinfo.record]: cloud_color = 0.71875, 0.348145, 0.530552, 1; 12:32:05.201 [planetinfo.record]: cloud_fraction = 0.400000; 12:32:05.201 [planetinfo.record]: land_color = 0.66, 0.337734, 0.586987, 1; 12:32:05.201 [planetinfo.record]: land_fraction = 0.320000; 12:32:05.201 [planetinfo.record]: polar_cloud_color = 0.823438, 0.558072, 0.688682, 1; 12:32:05.201 [planetinfo.record]: polar_land_color = 0.934, 0.819986, 0.908169, 1; 12:32:05.201 [planetinfo.record]: polar_sea_color = 0.919971, 0.936719, 0.886864, 1; 12:32:05.201 [planetinfo.record]: sea_color = 0.587555, 0.632812, 0.498093, 1; 12:32:05.201 [planetinfo.record]: rotation_speed = 0.001600 12:32:05.201 [planetinfo.record]: planet zpos = 755850.000000 12:32:05.201 [planetinfo.record]: sun_radius = 127255.202484 12:32:05.201 [planetinfo.record]: sun_vector = 0.115 0.828 0.549 12:32:05.201 [planetinfo.record]: sun_distance = 1158970 12:32:05.201 [planetinfo.record]: corona_flare = 0.045229 12:32:05.201 [planetinfo.record]: corona_hues = 0.888649 12:32:05.201 [planetinfo.record]: sun_color = 0.724799, 0.474011, 0.452667, 1 12:32:05.201 [planetinfo.record]: corona_shimmer = 0.304785 12:32:05.202 [planetinfo.record]: station_vector = -0.339 0.821 0.459 12:32:05.202 [planetinfo.record]: station = coriolis 12:32:09.796 [PLANETINFO OVER]: Done 12:32:09.796 [PLANETINFO LOGGING]: 2 169 12:32:10.802 [planetinfo.record]: seed = 9 16 69 7 12:32:10.802 [planetinfo.record]: coordinates = 16 95 12:32:10.802 [planetinfo.record]: government = 1; 12:32:10.802 [planetinfo.record]: economy = 7; 12:32:10.802 [planetinfo.record]: techlevel = 1; 12:32:10.802 [planetinfo.record]: population = 13; 12:32:10.802 [planetinfo.record]: productivity = 1560; 12:32:10.802 [planetinfo.record]: name = "Sovere"; 12:32:10.802 [planetinfo.record]: inhabitant = "Human Colonial"; 12:32:10.802 [planetinfo.record]: inhabitants = "Human Colonials"; 12:32:10.802 [planetinfo.record]: description = "Sovere is mildly well known for Sovereian lethal water."; 12:32:10.810 [planetinfo.record]: air_color = 0.663417, 0.885182, 0.883215, 1; 12:32:10.810 [planetinfo.record]: cloud_alpha = 1.000000; 12:32:10.810 [planetinfo.record]: cloud_color = 0.658203, 0.656636, 0.55793, 1; 12:32:10.810 [planetinfo.record]: cloud_fraction = 0.320000; 12:32:10.810 [planetinfo.record]: land_color = 0.606423, 0.66, 0.562031, 1; 12:32:10.810 [planetinfo.record]: land_fraction = 0.470000; 12:32:10.810 [planetinfo.record]: polar_cloud_color = 0.796191, 0.795007, 0.720382, 1; 12:32:10.810 [planetinfo.record]: polar_land_color = 0.915045, 0.934, 0.89934, 1; 12:32:10.810 [planetinfo.record]: polar_sea_color = 0.870991, 0.916397, 0.921094, 1; 12:32:10.810 [planetinfo.record]: sea_color = 0.61738, 0.772967, 0.789062, 1; 12:32:10.810 [planetinfo.record]: rotation_speed = 0.003443 12:32:10.810 [planetinfo.record]: planet zpos = 462400.000000 12:32:10.810 [planetinfo.record]: sun_radius = 143645.559082 12:32:10.810 [planetinfo.record]: sun_vector = -0.060 -0.979 0.196 12:32:10.810 [planetinfo.record]: sun_distance = 832320 12:32:10.810 [planetinfo.record]: corona_flare = 0.046068 12:32:10.810 [planetinfo.record]: corona_hues = 0.948341 12:32:10.810 [planetinfo.record]: sun_color = 0.253457, 0.500834, 0.836276, 1 12:32:10.811 [planetinfo.record]: corona_shimmer = 0.967492 12:32:10.811 [planetinfo.record]: station_vector = -0.614 -0.767 0.184 12:32:10.811 [planetinfo.record]: station = coriolis 12:32:15.646 [PLANETINFO OVER]: Done 12:32:15.647 [PLANETINFO LOGGING]: 2 170 12:32:16.651 [planetinfo.record]: seed = 121 12 173 17 12:32:16.651 [planetinfo.record]: coordinates = 12 142 12:32:16.651 [planetinfo.record]: government = 7; 12:32:16.651 [planetinfo.record]: economy = 6; 12:32:16.651 [planetinfo.record]: techlevel = 5; 12:32:16.651 [planetinfo.record]: population = 34; 12:32:16.651 [planetinfo.record]: productivity = 11968; 12:32:16.651 [planetinfo.record]: name = "Atinarus"; 12:32:16.651 [planetinfo.record]: inhabitant = "Green Horned Lobster"; 12:32:16.651 [planetinfo.record]: inhabitants = "Green Horned Lobsters"; 12:32:16.652 [planetinfo.record]: description = "Atinarus is mildly well known for killer Enesso juice."; 12:32:16.664 [planetinfo.record]: air_color = 0.601609, 0.935912, 0.922511, 1; 12:32:16.664 [planetinfo.record]: cloud_alpha = 1.000000; 12:32:16.664 [planetinfo.record]: cloud_color = 0.810547, 0.768644, 0.427437, 1; 12:32:16.664 [planetinfo.record]: cloud_fraction = 0.350000; 12:32:16.664 [planetinfo.record]: land_color = 0.560971, 0.319153, 0.648438, 1; 12:32:16.664 [planetinfo.record]: land_fraction = 0.540000; 12:32:16.664 [planetinfo.record]: polar_cloud_color = 0.864746, 0.836806, 0.609291, 1; 12:32:16.664 [planetinfo.record]: polar_land_color = 0.903621, 0.816435, 0.935156, 1; 12:32:16.664 [planetinfo.record]: polar_sea_color = 0.887699, 0.926953, 0.867842, 1; 12:32:16.664 [planetinfo.record]: sea_color = 0.606736, 0.730469, 0.544142, 1; 12:32:16.664 [planetinfo.record]: rotation_speed = 0.000009 12:32:16.664 [planetinfo.record]: planet zpos = 462600.000000 12:32:16.664 [planetinfo.record]: sun_radius = 82153.569946 12:32:16.664 [planetinfo.record]: sun_vector = 0.557 -0.339 -0.758 12:32:16.664 [planetinfo.record]: sun_distance = 585960 12:32:16.664 [planetinfo.record]: corona_flare = 0.044756 12:32:16.664 [planetinfo.record]: corona_hues = 0.720024 12:32:16.664 [planetinfo.record]: sun_color = 0.362781, 0.562934, 0.651956, 1 12:32:16.665 [planetinfo.record]: corona_shimmer = 0.383948 12:32:16.665 [planetinfo.record]: station_vector = -0.076 -0.745 0.663 12:32:16.665 [planetinfo.record]: station = coriolis 12:32:21.136 [PLANETINFO OVER]: Done 12:32:21.136 [PLANETINFO LOGGING]: 2 171 12:32:22.140 [planetinfo.record]: seed = 161 136 149 255 12:32:22.140 [planetinfo.record]: coordinates = 136 202 12:32:22.140 [planetinfo.record]: government = 4; 12:32:22.140 [planetinfo.record]: economy = 2; 12:32:22.140 [planetinfo.record]: techlevel = 7; 12:32:22.140 [planetinfo.record]: population = 35; 12:32:22.140 [planetinfo.record]: productivity = 17920; 12:32:22.140 [planetinfo.record]: name = "Onenqu"; 12:32:22.140 [planetinfo.record]: inhabitant = "Horned Humanoid"; 12:32:22.140 [planetinfo.record]: inhabitants = "Horned Humanoids"; 12:32:22.140 [planetinfo.record]: description = "The world Onenqu is most famous for the Onenquian spotted shrew."; 12:32:22.152 [planetinfo.record]: air_color = 0.537073, 0.472892, 0.936563, 1; 12:32:22.152 [planetinfo.record]: cloud_alpha = 1.000000; 12:32:22.152 [planetinfo.record]: cloud_color = 0.401613, 0.092041, 0.8125, 1; 12:32:22.152 [planetinfo.record]: cloud_fraction = 0.310000; 12:32:22.152 [planetinfo.record]: land_color = 0.551719, 0.66, 0.611781, 1; 12:32:22.152 [planetinfo.record]: land_fraction = 0.630000; 12:32:22.152 [planetinfo.record]: polar_cloud_color = 0.59203, 0.385896, 0.865625, 1; 12:32:22.152 [planetinfo.record]: polar_land_color = 0.895691, 0.934, 0.916941, 1; 12:32:22.152 [planetinfo.record]: polar_sea_color = 0.813211, 0.948438, 0.916744, 1; 12:32:22.152 [planetinfo.record]: sea_color = 0.221558, 0.515625, 0.446703, 1; 12:32:22.152 [planetinfo.record]: rotation_speed = 0.003887 12:32:22.152 [planetinfo.record]: planet zpos = 815040.000000 12:32:22.152 [planetinfo.record]: sun_radius = 170251.860352 12:32:22.159 [planetinfo.record]: sun_vector = 0.762 -0.629 0.151 12:32:22.159 [planetinfo.record]: sun_distance = 1290480 12:32:22.159 [planetinfo.record]: corona_flare = 0.051028 12:32:22.160 [planetinfo.record]: corona_hues = 0.861168 12:32:22.160 [planetinfo.record]: sun_color = 0.667911, 0.643514, 0.595036, 1 12:32:22.160 [planetinfo.record]: corona_shimmer = 0.328593 12:32:22.160 [planetinfo.record]: station_vector = 0.166 0.984 0.063 12:32:22.160 [planetinfo.record]: station = coriolis 12:32:26.874 [PLANETINFO OVER]: Done 12:32:26.875 [PLANETINFO LOGGING]: 2 172 12:32:27.884 [planetinfo.record]: seed = 81 45 13 91 12:32:27.884 [planetinfo.record]: coordinates = 45 218 12:32:27.884 [planetinfo.record]: government = 2; 12:32:27.884 [planetinfo.record]: economy = 2; 12:32:27.884 [planetinfo.record]: techlevel = 7; 12:32:27.884 [planetinfo.record]: population = 33; 12:32:27.884 [planetinfo.record]: productivity = 12672; 12:32:27.884 [planetinfo.record]: name = "Angemaar"; 12:32:27.884 [planetinfo.record]: inhabitant = "Human Colonial"; 12:32:27.884 [planetinfo.record]: inhabitants = "Human Colonials"; 12:32:27.884 [planetinfo.record]: description = "The world Angemaar is mildly noted for the Angemaarian deadly Enaboid but plagued by lethal spotted batoids."; 12:32:27.911 [planetinfo.record]: air_color = 0.738982, 0.487444, 0.970383, 1; 12:32:27.911 [planetinfo.record]: cloud_alpha = 1.000000; 12:32:27.911 [planetinfo.record]: cloud_color = 0.914062, 0.096405, 0.441354, 1; 12:32:27.911 [planetinfo.record]: cloud_fraction = 0.260000; 12:32:27.911 [planetinfo.record]: land_color = 0.66, 0.377776, 0.340313, 1; 12:32:27.911 [planetinfo.record]: land_fraction = 0.640000; 12:32:27.911 [planetinfo.record]: polar_cloud_color = 0.911328, 0.401821, 0.616769, 1; 12:32:27.911 [planetinfo.record]: polar_land_color = 0.934, 0.834153, 0.820898, 1; 12:32:27.911 [planetinfo.record]: polar_sea_color = 0.926953, 0.817421, 0.827689, 1; 12:32:27.911 [planetinfo.record]: sea_color = 0.730469, 0.385208, 0.417576, 1; 12:32:27.911 [planetinfo.record]: rotation_speed = 0.004735 12:32:27.911 [planetinfo.record]: planet zpos = 794780.000000 12:32:27.911 [planetinfo.record]: sun_radius = 170114.229431 12:32:27.911 [planetinfo.record]: sun_vector = -0.673 -0.118 -0.730 12:32:27.911 [planetinfo.record]: sun_distance = 908320 12:32:27.911 [planetinfo.record]: corona_flare = 0.058733 12:32:27.911 [planetinfo.record]: corona_hues = 0.507439 12:32:27.911 [planetinfo.record]: sun_color = 0.651883, 0.497128, 0.281573, 1 12:32:27.912 [planetinfo.record]: corona_shimmer = 0.241346 12:32:27.912 [planetinfo.record]: station_vector = -0.905 -0.408 0.117 12:32:27.912 [planetinfo.record]: station = coriolis 12:32:33.175 [PLANETINFO OVER]: Done 12:32:33.176 [PLANETINFO LOGGING]: 2 173 12:32:34.188 [planetinfo.record]: seed = 25 170 37 249 12:32:34.188 [planetinfo.record]: coordinates = 170 235 12:32:34.188 [planetinfo.record]: government = 3; 12:32:34.188 [planetinfo.record]: economy = 3; 12:32:34.188 [planetinfo.record]: techlevel = 8; 12:32:34.188 [planetinfo.record]: population = 39; 12:32:34.188 [planetinfo.record]: productivity = 15288; 12:32:34.188 [planetinfo.record]: name = "Orreen"; 12:32:34.188 [planetinfo.record]: inhabitant = "Human Colonial"; 12:32:34.188 [planetinfo.record]: inhabitants = "Human Colonials"; 12:32:34.188 [planetinfo.record]: description = "This planet is notable for its great tropical forests and Zero-G cricket."; 12:32:34.209 [planetinfo.record]: air_color = 0.665366, 0.884548, 0.887783, 1; 12:32:34.209 [planetinfo.record]: cloud_alpha = 1.000000; 12:32:34.209 [planetinfo.record]: cloud_color = 0.660467, 0.666016, 0.564552, 1; 12:32:34.209 [planetinfo.record]: cloud_fraction = 0.490000; 12:32:34.209 [planetinfo.record]: land_color = 0.66, 0.185625, 0.508052, 1; 12:32:34.209 [planetinfo.record]: land_fraction = 0.430000; 12:32:34.209 [planetinfo.record]: polar_cloud_color = 0.795543, 0.799707, 0.723563, 1; 12:32:34.209 [planetinfo.record]: polar_land_color = 0.934, 0.766172, 0.880243, 1; 12:32:34.209 [planetinfo.record]: polar_sea_color = 0.92666, 0.936719, 0.885218, 1; 12:32:34.209 [planetinfo.record]: sea_color = 0.605631, 0.632812, 0.493643, 1; 12:32:34.209 [planetinfo.record]: rotation_speed = 0.000452 12:32:34.209 [planetinfo.record]: planet zpos = 634800.000000 12:32:34.209 [planetinfo.record]: sun_radius = 120107.441711 12:32:34.209 [planetinfo.record]: sun_vector = 0.545 -0.353 0.760 12:32:34.209 [planetinfo.record]: sun_distance = 952200 12:32:34.209 [planetinfo.record]: corona_flare = 0.029890 12:32:34.209 [planetinfo.record]: corona_hues = 0.816208 12:32:34.210 [planetinfo.record]: sun_color = 0.715927, 0.611657, 0.45473, 1 12:32:34.210 [planetinfo.record]: corona_shimmer = 0.448330 12:32:34.210 [planetinfo.record]: station_vector = -0.699 -0.701 0.140 12:32:34.210 [planetinfo.record]: station = coriolis 12:32:38.740 [PLANETINFO OVER]: Done 12:32:38.741 [PLANETINFO LOGGING]: 2 174 12:32:39.757 [planetinfo.record]: seed = 73 186 109 123 12:32:39.757 [planetinfo.record]: coordinates = 186 50 12:32:39.757 [planetinfo.record]: government = 1; 12:32:39.757 [planetinfo.record]: economy = 2; 12:32:39.757 [planetinfo.record]: techlevel = 8; 12:32:39.757 [planetinfo.record]: population = 36; 12:32:39.757 [planetinfo.record]: productivity = 11520; 12:32:39.757 [planetinfo.record]: name = "Ansois"; 12:32:39.757 [planetinfo.record]: inhabitant = "Human Colonial"; 12:32:39.757 [planetinfo.record]: inhabitants = "Human Colonials"; 12:32:39.757 [planetinfo.record]: description = "The planet Ansois is an unremarkable planet."; 12:32:39.780 [planetinfo.record]: air_color = 0.564046, 0.552695, 0.859816, 1; 12:32:39.780 [planetinfo.record]: cloud_alpha = 1.000000; 12:32:39.780 [planetinfo.record]: cloud_color = 0.341318, 0.306931, 0.582031, 1; 12:32:39.780 [planetinfo.record]: cloud_fraction = 0.580000; 12:32:39.780 [planetinfo.record]: land_color = 0.546965, 0.66, 0.422813, 1; 12:32:39.780 [planetinfo.record]: land_fraction = 0.290000; 12:32:39.780 [planetinfo.record]: polar_cloud_color = 0.564972, 0.536837, 0.761914, 1; 12:32:39.780 [planetinfo.record]: polar_land_color = 0.89401, 0.934, 0.850086, 1; 12:32:39.780 [planetinfo.record]: polar_sea_color = 0.925067, 0.936328, 0.884848, 1; 12:32:39.780 [planetinfo.record]: sea_color = 0.606088, 0.636719, 0.49669, 1; 12:32:39.780 [planetinfo.record]: rotation_speed = 0.000709 12:32:39.780 [planetinfo.record]: planet zpos = 814520.000000 12:32:39.780 [planetinfo.record]: sun_radius = 125961.084900 12:32:39.780 [planetinfo.record]: sun_vector = 0.391 0.747 -0.538 12:32:39.780 [planetinfo.record]: sun_distance = 1163600 12:32:39.780 [planetinfo.record]: corona_flare = 0.005957 12:32:39.781 [planetinfo.record]: corona_hues = 0.676689 12:32:39.781 [planetinfo.record]: sun_color = 0.74028, 0.60644, 0.329252, 1 12:32:39.781 [planetinfo.record]: corona_shimmer = 0.315177 12:32:39.781 [planetinfo.record]: station_vector = 0.278 -0.956 0.093 12:32:39.781 [planetinfo.record]: station = coriolis 12:32:44.796 [PLANETINFO OVER]: Done 12:32:44.797 [PLANETINFO LOGGING]: 2 175 12:32:45.821 [planetinfo.record]: seed = 241 128 117 134 12:32:45.821 [planetinfo.record]: coordinates = 128 157 12:32:45.821 [planetinfo.record]: government = 6; 12:32:45.821 [planetinfo.record]: economy = 5; 12:32:45.821 [planetinfo.record]: techlevel = 5; 12:32:45.821 [planetinfo.record]: population = 32; 12:32:45.821 [planetinfo.record]: productivity = 12800; 12:32:45.821 [planetinfo.record]: name = "Bicein"; 12:32:45.821 [planetinfo.record]: inhabitant = "Human Colonial"; 12:32:45.821 [planetinfo.record]: inhabitants = "Human Colonials"; 12:32:45.821 [planetinfo.record]: description = "Bicein is ravaged by dreadful civil war."; 12:32:45.833 [planetinfo.record]: air_color = 0.572544, 0.875378, 0.997699, 1; 12:32:45.834 [planetinfo.record]: cloud_alpha = 1.000000; 12:32:45.834 [planetinfo.record]: cloud_color = 0.40369, 0.996094, 0.319061, 1; 12:32:45.834 [planetinfo.record]: cloud_fraction = 0.440000; 12:32:45.834 [planetinfo.record]: land_color = 0.652266, 0.655287, 0.66, 1; 12:32:45.834 [planetinfo.record]: land_fraction = 0.310000; 12:32:45.834 [planetinfo.record]: polar_cloud_color = 0.595777, 0.948242, 0.545424, 1; 12:32:45.834 [planetinfo.record]: polar_land_color = 0.931264, 0.932333, 0.934, 1; 12:32:45.834 [planetinfo.record]: polar_sea_color = 0.944922, 0.870437, 0.712382, 1; 12:32:45.834 [planetinfo.record]: sea_color = 0.550781, 0.377116, 0.00860596, 1; 12:32:45.834 [planetinfo.record]: rotation_speed = 0.003205 12:32:45.834 [planetinfo.record]: planet zpos = 582400.000000 12:32:45.834 [planetinfo.record]: sun_radius = 124898.046875 12:32:45.834 [planetinfo.record]: sun_vector = -0.241 -0.716 0.655 12:32:45.834 [planetinfo.record]: sun_distance = 851200 12:32:45.834 [planetinfo.record]: corona_flare = 0.006412 12:32:45.835 [planetinfo.record]: corona_hues = 0.554008 12:32:45.835 [planetinfo.record]: sun_color = 0.816156, 0.760033, 0.461062, 1 12:32:45.835 [planetinfo.record]: corona_shimmer = 1.001375 12:32:45.835 [planetinfo.record]: station_vector = 0.956 0.293 0.028 12:32:45.835 [planetinfo.record]: station = coriolis 12:32:50.643 [PLANETINFO OVER]: Done 12:32:50.644 [PLANETINFO LOGGING]: 2 176 12:32:51.659 [planetinfo.record]: seed = 225 215 77 41 12:32:51.659 [planetinfo.record]: coordinates = 215 172 12:32:51.659 [planetinfo.record]: government = 4; 12:32:51.659 [planetinfo.record]: economy = 4; 12:32:51.659 [planetinfo.record]: techlevel = 8; 12:32:51.659 [planetinfo.record]: population = 41; 12:32:51.659 [planetinfo.record]: productivity = 15744; 12:32:51.659 [planetinfo.record]: name = "Esdirece"; 12:32:51.659 [planetinfo.record]: inhabitant = "Human Colonial"; 12:32:51.659 [planetinfo.record]: inhabitants = "Human Colonials"; 12:32:51.659 [planetinfo.record]: description = "Esdirece is a revolting little planet."; 12:32:51.662 [planetinfo.record]: air_color = 0.614985, 0.559504, 0.872174, 1; 12:32:51.662 [planetinfo.record]: cloud_alpha = 1.000000; 12:32:51.662 [planetinfo.record]: cloud_color = 0.485442, 0.324081, 0.619141, 1; 12:32:51.662 [planetinfo.record]: cloud_fraction = 0.240000; 12:32:51.662 [planetinfo.record]: land_color = 0.415078, 0.625558, 0.66, 1; 12:32:51.662 [planetinfo.record]: land_fraction = 0.250000; 12:32:51.662 [planetinfo.record]: polar_cloud_color = 0.673528, 0.546702, 0.778613, 1; 12:32:51.662 [planetinfo.record]: polar_land_color = 0.84735, 0.921815, 0.934, 1; 12:32:51.662 [planetinfo.record]: polar_sea_color = 0.930019, 0.934375, 0.878623, 1; 12:32:51.662 [planetinfo.record]: sea_color = 0.644013, 0.65625, 0.499622, 1; 12:32:51.663 [planetinfo.record]: rotation_speed = 0.002863 12:32:51.663 [planetinfo.record]: planet zpos = 586850.000000 12:32:51.663 [planetinfo.record]: sun_radius = 99456.526184 12:32:51.663 [planetinfo.record]: sun_vector = -0.279 -0.919 -0.278 12:32:51.663 [planetinfo.record]: sun_distance = 1067000 12:32:51.663 [planetinfo.record]: corona_flare = 0.055586 12:32:51.663 [planetinfo.record]: corona_hues = 0.596405 12:32:51.663 [planetinfo.record]: sun_color = 0.732631, 0.762508, 0.780365, 1 12:32:51.663 [planetinfo.record]: corona_shimmer = 0.262724 12:32:51.663 [planetinfo.record]: station_vector = 0.271 0.899 0.344 12:32:51.663 [planetinfo.record]: station = coriolis 12:32:57.183 [PLANETINFO OVER]: Done 12:32:57.185 [PLANETINFO LOGGING]: 2 177 12:32:58.191 [planetinfo.record]: seed = 169 133 5 226 12:32:58.191 [planetinfo.record]: coordinates = 133 174 12:32:58.191 [planetinfo.record]: government = 5; 12:32:58.192 [planetinfo.record]: economy = 6; 12:32:58.192 [planetinfo.record]: techlevel = 5; 12:32:58.192 [planetinfo.record]: population = 32; 12:32:58.192 [planetinfo.record]: productivity = 9216; 12:32:58.192 [planetinfo.record]: name = "Xeverive"; 12:32:58.192 [planetinfo.record]: inhabitant = "Human Colonial"; 12:32:58.192 [planetinfo.record]: inhabitants = "Human Colonials"; 12:32:58.192 [planetinfo.record]: description = "The world Xeverive is a boring world."; 12:32:58.208 [planetinfo.record]: air_color = 0.702832, 0.716486, 0.98209, 1; 12:32:58.208 [planetinfo.record]: cloud_alpha = 1.000000; 12:32:58.208 [planetinfo.record]: cloud_color = 0.708206, 0.740216, 0.949219, 1; 12:32:58.208 [planetinfo.record]: cloud_fraction = 0.250000; 12:32:58.208 [planetinfo.record]: land_color = 0.534905, 0.356018, 0.599609, 1; 12:32:58.209 [planetinfo.record]: land_fraction = 0.310000; 12:32:58.209 [planetinfo.record]: polar_cloud_color = 0.780018, 0.799559, 0.927148, 1; 12:32:58.209 [planetinfo.record]: polar_land_color = 0.914679, 0.844566, 0.940039, 1; 12:32:58.209 [planetinfo.record]: polar_sea_color = 0.895039, 0.923242, 0.853819, 1; 12:32:58.209 [planetinfo.record]: sea_color = 0.673786, 0.767578, 0.536705, 1; 12:32:58.209 [planetinfo.record]: rotation_speed = 0.002310 12:32:58.209 [planetinfo.record]: planet zpos = 346100.000000 12:32:58.209 [planetinfo.record]: sun_radius = 77834.476318 12:32:58.209 [planetinfo.record]: sun_vector = 0.398 -0.451 -0.799 12:32:58.209 [planetinfo.record]: sun_distance = 657590 12:32:58.209 [planetinfo.record]: corona_flare = 0.005492 12:32:58.209 [planetinfo.record]: corona_hues = 0.898697 12:32:58.209 [planetinfo.record]: sun_color = 0.796738, 0.616263, 0.441384, 1 12:32:58.209 [planetinfo.record]: corona_shimmer = 0.340033 12:32:58.209 [planetinfo.record]: station_vector = -0.550 0.603 0.578 12:32:58.209 [planetinfo.record]: station = coriolis 12:33:03.142 [PLANETINFO OVER]: Done 12:33:03.142 [PLANETINFO LOGGING]: 2 178 12:33:04.150 [planetinfo.record]: seed = 153 118 45 11 12:33:04.150 [planetinfo.record]: coordinates = 118 126 12:33:04.150 [planetinfo.record]: government = 3; 12:33:04.150 [planetinfo.record]: economy = 6; 12:33:04.150 [planetinfo.record]: techlevel = 5; 12:33:04.150 [planetinfo.record]: population = 30; 12:33:04.151 [planetinfo.record]: productivity = 6720; 12:33:04.151 [planetinfo.record]: name = "Maonle"; 12:33:04.151 [planetinfo.record]: inhabitant = "Human Colonial"; 12:33:04.151 [planetinfo.record]: inhabitants = "Human Colonials"; 12:33:04.151 [planetinfo.record]: description = "The world Maonle is a dull place."; 12:33:04.163 [planetinfo.record]: air_color = 0.53271, 0.991195, 0.987129, 1; 12:33:04.163 [planetinfo.record]: cloud_alpha = 1.000000; 12:33:04.163 [planetinfo.record]: cloud_color = 0.976562, 0.964522, 0.205994, 1; 12:33:04.164 [planetinfo.record]: cloud_fraction = 0.630000; 12:33:04.164 [planetinfo.record]: land_color = 0.66, 0.222605, 0.201094, 1; 12:33:04.164 [planetinfo.record]: land_fraction = 0.570000; 12:33:04.164 [planetinfo.record]: polar_cloud_color = 0.939453, 0.932214, 0.476149, 1; 12:33:04.164 [planetinfo.record]: polar_land_color = 0.934, 0.779255, 0.771645, 1; 12:33:04.164 [planetinfo.record]: polar_sea_color = 0.871065, 0.924609, 0.891981, 1; 12:33:04.164 [planetinfo.record]: sea_color = 0.579271, 0.753906, 0.647488, 1; 12:33:04.164 [planetinfo.record]: rotation_speed = 0.001272 12:33:04.164 [planetinfo.record]: planet zpos = 690000.000000 12:33:04.164 [planetinfo.record]: sun_radius = 199994.468689 12:33:04.164 [planetinfo.record]: sun_vector = -0.320 -0.671 0.669 12:33:04.164 [planetinfo.record]: sun_distance = 1207500 12:33:04.164 [planetinfo.record]: corona_flare = 0.001494 12:33:04.164 [planetinfo.record]: corona_hues = 0.623726 12:33:04.164 [planetinfo.record]: sun_color = 0.618463, 0.693628, 0.787714, 1 12:33:04.165 [planetinfo.record]: corona_shimmer = 0.437712 12:33:04.165 [planetinfo.record]: station_vector = 0.828 -0.561 0.004 12:33:04.165 [planetinfo.record]: station = coriolis 12:33:09.138 [PLANETINFO OVER]: Done 12:33:09.139 [PLANETINFO LOGGING]: 2 179 12:33:10.146 [planetinfo.record]: seed = 193 140 85 14 12:33:10.146 [planetinfo.record]: coordinates = 140 129 12:33:10.146 [planetinfo.record]: government = 0; 12:33:10.146 [planetinfo.record]: economy = 3; 12:33:10.146 [planetinfo.record]: techlevel = 4; 12:33:10.146 [planetinfo.record]: population = 20; 12:33:10.146 [planetinfo.record]: productivity = 4480; 12:33:10.146 [planetinfo.record]: name = "Reteti"; 12:33:10.146 [planetinfo.record]: inhabitant = "Human Colonial"; 12:33:10.146 [planetinfo.record]: inhabitants = "Human Colonials"; 12:33:10.146 [planetinfo.record]: description = "This planet is reasonably noted for its exotic fish meat."; 12:33:10.184 [planetinfo.record]: air_color = 0.628343, 0.850331, 0.961928, 1; 12:33:10.184 [planetinfo.record]: cloud_alpha = 1.000000; 12:33:10.184 [planetinfo.record]: cloud_color = 0.496407, 0.888672, 0.502536, 1; 12:33:10.184 [planetinfo.record]: cloud_fraction = 0.360000; 12:33:10.184 [planetinfo.record]: land_color = 0.448594, 0.565858, 0.66, 1; 12:33:10.184 [planetinfo.record]: land_fraction = 0.340000; 12:33:10.184 [planetinfo.record]: polar_cloud_color = 0.651638, 0.899902, 0.655517, 1; 12:33:10.184 [planetinfo.record]: polar_land_color = 0.859207, 0.900694, 0.934, 1; 12:33:10.184 [planetinfo.record]: polar_sea_color = 0.916059, 0.933203, 0.878341, 1; 12:33:10.184 [planetinfo.record]: sea_color = 0.618882, 0.667969, 0.510892, 1; 12:33:10.184 [planetinfo.record]: rotation_speed = 0.002527 12:33:10.184 [planetinfo.record]: planet zpos = 915600.000000 12:33:10.184 [planetinfo.record]: sun_radius = 167157.394409 12:33:10.184 [planetinfo.record]: sun_vector = -0.114 0.993 0.012 12:33:10.184 [planetinfo.record]: sun_distance = 1111800 12:33:10.184 [planetinfo.record]: corona_flare = 0.062453 12:33:10.184 [planetinfo.record]: corona_hues = 0.617149 12:33:10.184 [planetinfo.record]: sun_color = 0.268135, 0.284179, 0.790024, 1 12:33:10.184 [planetinfo.record]: corona_shimmer = 0.347845 12:33:10.184 [planetinfo.record]: station_vector = -0.898 -0.141 0.417 12:33:10.185 [planetinfo.record]: station = coriolis 12:33:15.241 [PLANETINFO OVER]: Done 12:33:15.242 [PLANETINFO LOGGING]: 2 180 12:33:16.250 [planetinfo.record]: seed = 241 226 141 183 12:33:16.250 [planetinfo.record]: coordinates = 226 183 12:33:16.250 [planetinfo.record]: government = 6; 12:33:16.250 [planetinfo.record]: economy = 7; 12:33:16.250 [planetinfo.record]: techlevel = 5; 12:33:16.250 [planetinfo.record]: population = 34; 12:33:16.250 [planetinfo.record]: productivity = 8160; 12:33:16.250 [planetinfo.record]: name = "Tieninve"; 12:33:16.250 [planetinfo.record]: inhabitant = "Harmless Furry Rodent"; 12:33:16.250 [planetinfo.record]: inhabitants = "Harmless Furry Rodents"; 12:33:16.250 [planetinfo.record]: description = "This planet is beset by lethal disease."; 12:33:16.252 [planetinfo.record]: air_color = 0.686691, 0.597645, 0.858516, 1; 12:33:16.252 [planetinfo.record]: cloud_alpha = 1.000000; 12:33:16.252 [planetinfo.record]: cloud_color = 0.578125, 0.397461, 0.571068, 1; 12:33:16.252 [planetinfo.record]: cloud_fraction = 0.180000; 12:33:16.252 [planetinfo.record]: land_color = 0.169443, 0.568359, 0.0999069, 1; 12:33:16.252 [planetinfo.record]: land_fraction = 0.340000; 12:33:16.253 [planetinfo.record]: polar_cloud_color = 0.760156, 0.611688, 0.754357, 1; 12:33:16.253 [planetinfo.record]: polar_land_color = 0.777669, 0.943164, 0.748821, 1; 12:33:16.253 [planetinfo.record]: polar_sea_color = 0.946484, 0.891673, 0.903235, 1; 12:33:16.253 [planetinfo.record]: sea_color = 0.535156, 0.411192, 0.437341, 1; 12:33:16.253 [planetinfo.record]: rotation_speed = 0.001055 12:33:16.253 [planetinfo.record]: planet zpos = 483400.000000 12:33:16.253 [planetinfo.record]: sun_radius = 105894.960022 12:33:16.253 [planetinfo.record]: sun_vector = -0.132 0.230 -0.964 12:33:16.253 [planetinfo.record]: sun_distance = 773440 12:33:16.253 [planetinfo.record]: corona_flare = 0.067329 12:33:16.253 [planetinfo.record]: corona_hues = 0.710396 12:33:16.253 [planetinfo.record]: sun_color = 0.84285, 0.713284, 0.463371, 1 12:33:16.254 [planetinfo.record]: corona_shimmer = 0.326889 12:33:16.254 [planetinfo.record]: station_vector = -0.142 -0.911 0.387 12:33:16.254 [planetinfo.record]: station = coriolis 12:33:21.224 [PLANETINFO OVER]: Done 12:33:21.225 [PLANETINFO LOGGING]: 2 181 12:33:22.229 [planetinfo.record]: seed = 185 246 229 53 12:33:22.229 [planetinfo.record]: coordinates = 246 236 12:33:22.229 [planetinfo.record]: government = 7; 12:33:22.229 [planetinfo.record]: economy = 4; 12:33:22.229 [planetinfo.record]: techlevel = 9; 12:33:22.229 [planetinfo.record]: population = 48; 12:33:22.229 [planetinfo.record]: productivity = 25344; 12:33:22.229 [planetinfo.record]: name = "Laorbila"; 12:33:22.229 [planetinfo.record]: inhabitant = "Red Horned Lobster"; 12:33:22.229 [planetinfo.record]: inhabitants = "Red Horned Lobsters"; 12:33:22.229 [planetinfo.record]: description = "Laorbila is mildly well known for Laorbilaian wolf meat."; 12:33:22.248 [planetinfo.record]: air_color = 0.583278, 0.945668, 0.91417, 1; 12:33:22.248 [planetinfo.record]: cloud_alpha = 1.000000; 12:33:22.248 [planetinfo.record]: cloud_color = 0.839844, 0.724201, 0.377274, 1; 12:33:22.248 [planetinfo.record]: cloud_fraction = 0.470000; 12:33:22.249 [planetinfo.record]: land_color = 0.539062, 0.460098, 0.0336914, 1; 12:33:22.249 [planetinfo.record]: land_fraction = 0.680000; 12:33:22.249 [planetinfo.record]: polar_cloud_color = 0.87793, 0.802375, 0.575713, 1; 12:33:22.249 [planetinfo.record]: polar_land_color = 0.946094, 0.911447, 0.724353, 1; 12:33:22.249 [planetinfo.record]: polar_sea_color = 0.944141, 0.895092, 0.890018, 1; 12:33:22.249 [planetinfo.record]: sea_color = 0.558594, 0.442518, 0.43051, 1; 12:33:22.249 [planetinfo.record]: rotation_speed = 0.004124 12:33:22.249 [planetinfo.record]: planet zpos = 564460.000000 12:33:22.249 [planetinfo.record]: sun_radius = 119016.091003 12:33:22.249 [planetinfo.record]: sun_vector = -0.705 -0.608 -0.365 12:33:22.249 [planetinfo.record]: sun_distance = 824980 12:33:22.249 [planetinfo.record]: corona_flare = 0.087671 12:33:22.249 [planetinfo.record]: corona_hues = 0.759277 12:33:22.249 [planetinfo.record]: sun_color = 0.702591, 0.683061, 0.601682, 1 12:33:22.249 [planetinfo.record]: corona_shimmer = 0.342611 12:33:22.250 [planetinfo.record]: station_vector = 0.711 -0.619 0.335 12:33:22.250 [planetinfo.record]: station = coriolis 12:33:27.289 [PLANETINFO OVER]: Done 12:33:27.290 [PLANETINFO LOGGING]: 2 182 12:33:28.310 [planetinfo.record]: seed = 105 149 237 244 12:33:28.310 [planetinfo.record]: coordinates = 149 70 12:33:28.310 [planetinfo.record]: government = 5; 12:33:28.310 [planetinfo.record]: economy = 6; 12:33:28.310 [planetinfo.record]: techlevel = 5; 12:33:28.310 [planetinfo.record]: population = 32; 12:33:28.310 [planetinfo.record]: productivity = 9216; 12:33:28.310 [planetinfo.record]: name = "Raerqu"; 12:33:28.310 [planetinfo.record]: inhabitant = "Bony Lobster"; 12:33:28.310 [planetinfo.record]: inhabitants = "Bony Lobsters"; 12:33:28.310 [planetinfo.record]: description = "The world Raerqu is reasonably famous for its inhabitants’ exceptional loathing of sit coms."; 12:33:28.332 [planetinfo.record]: air_color = 0.606661, 0.892463, 0.961277, 1; 12:33:28.332 [planetinfo.record]: cloud_alpha = 1.000000; 12:33:28.332 [planetinfo.record]: cloud_color = 0.619361, 0.886719, 0.436432, 1; 12:33:28.332 [planetinfo.record]: cloud_fraction = 0.420000; 12:33:28.339 [planetinfo.record]: land_color = 0.180469, 0.221678, 0.66, 1; 12:33:28.339 [planetinfo.record]: land_fraction = 0.660000; 12:33:28.339 [planetinfo.record]: polar_cloud_color = 0.729606, 0.899023, 0.613689, 1; 12:33:28.340 [planetinfo.record]: polar_land_color = 0.764348, 0.778927, 0.934, 1; 12:33:28.340 [planetinfo.record]: polar_sea_color = 0.920305, 0.928516, 0.873204, 1; 12:33:28.340 [planetinfo.record]: sea_color = 0.68956, 0.714844, 0.54451, 1; 12:33:28.340 [planetinfo.record]: rotation_speed = 0.002009 12:33:28.340 [planetinfo.record]: planet zpos = 518570.000000 12:33:28.340 [planetinfo.record]: sun_radius = 118643.777161 12:33:28.340 [planetinfo.record]: sun_vector = 0.093 -0.451 -0.888 12:33:28.340 [planetinfo.record]: sun_distance = 917470 12:33:28.340 [planetinfo.record]: corona_flare = 0.019075 12:33:28.340 [planetinfo.record]: corona_hues = 0.652885 12:33:28.340 [planetinfo.record]: sun_color = 0.739102, 0.719006, 0.586353, 1 12:33:28.340 [planetinfo.record]: corona_shimmer = 0.445036 12:33:28.340 [planetinfo.record]: station_vector = 0.574 0.692 0.438 12:33:28.341 [planetinfo.record]: station = coriolis 12:33:33.748 [PLANETINFO OVER]: Done 12:33:33.749 [PLANETINFO LOGGING]: 2 183 12:33:34.754 [planetinfo.record]: seed = 17 32 53 75 12:33:34.754 [planetinfo.record]: coordinates = 32 90 12:33:34.754 [planetinfo.record]: government = 2; 12:33:34.754 [planetinfo.record]: economy = 2; 12:33:34.754 [planetinfo.record]: techlevel = 6; 12:33:34.754 [planetinfo.record]: population = 29; 12:33:34.754 [planetinfo.record]: productivity = 11136; 12:33:34.754 [planetinfo.record]: name = "Mabiat"; 12:33:34.754 [planetinfo.record]: inhabitant = "Human Colonial"; 12:33:34.755 [planetinfo.record]: inhabitants = "Human Colonials"; 12:33:34.755 [planetinfo.record]: description = "The world Mabiat is scourged by evil disease."; 12:33:34.772 [planetinfo.record]: air_color = 0.627956, 0.84447, 0.96518, 1; 12:33:34.772 [planetinfo.record]: cloud_alpha = 1.000000; 12:33:34.772 [planetinfo.record]: cloud_color = 0.494843, 0.898438, 0.529526, 1; 12:33:34.772 [planetinfo.record]: cloud_fraction = 0.340000; 12:33:34.772 [planetinfo.record]: land_color = 0.407424, 0.234609, 0.66, 1; 12:33:34.772 [planetinfo.record]: land_fraction = 0.350000; 12:33:34.772 [planetinfo.record]: polar_cloud_color = 0.650405, 0.904297, 0.672224, 1; 12:33:34.772 [planetinfo.record]: polar_land_color = 0.844642, 0.783502, 0.934, 1; 12:33:34.772 [planetinfo.record]: polar_sea_color = 0.92832, 0.849506, 0.834038, 1; 12:33:34.772 [planetinfo.record]: sea_color = 0.716797, 0.473373, 0.425598, 1; 12:33:34.772 [planetinfo.record]: rotation_speed = 0.001914 12:33:34.772 [planetinfo.record]: planet zpos = 566400.000000 12:33:34.772 [planetinfo.record]: sun_radius = 156636.357422 12:33:34.772 [planetinfo.record]: sun_vector = 0.800 0.431 0.417 12:33:34.772 [planetinfo.record]: sun_distance = 1132800 12:33:34.772 [planetinfo.record]: corona_flare = 0.027882 12:33:34.772 [planetinfo.record]: corona_hues = 0.965134 12:33:34.772 [planetinfo.record]: sun_color = 0.829895, 0.829547, 0.827274, 1 12:33:34.773 [planetinfo.record]: corona_shimmer = 0.262001 12:33:34.773 [planetinfo.record]: station_vector = 0.077 -0.631 0.772 12:33:34.773 [planetinfo.record]: station = coriolis 12:33:39.342 [PLANETINFO OVER]: Done 12:33:39.343 [PLANETINFO LOGGING]: 2 184 12:33:40.357 [planetinfo.record]: seed = 129 66 205 57 12:33:40.357 [planetinfo.record]: coordinates = 66 49 12:33:40.357 [planetinfo.record]: government = 0; 12:33:40.357 [planetinfo.record]: economy = 3; 12:33:40.357 [planetinfo.record]: techlevel = 6; 12:33:40.357 [planetinfo.record]: population = 28; 12:33:40.357 [planetinfo.record]: productivity = 6272; 12:33:40.357 [planetinfo.record]: name = "Ordiesat"; 12:33:40.357 [planetinfo.record]: inhabitant = "Red Bony Bird"; 12:33:40.357 [planetinfo.record]: inhabitants = "Red Bony Birds"; 12:33:40.357 [planetinfo.record]: description = "The world Ordiesat is mildly noted for its ancient mountains but ravaged by occasional solar activity."; 12:33:40.376 [planetinfo.record]: air_color = 0.655406, 0.595003, 0.846809, 1; 12:33:40.376 [planetinfo.record]: cloud_alpha = 1.000000; 12:33:40.376 [planetinfo.record]: cloud_color = 0.500715, 0.383896, 0.542969, 1; 12:33:40.376 [planetinfo.record]: cloud_fraction = 0.210000; 12:33:40.376 [planetinfo.record]: land_color = 0.605469, 0.477753, 0.480746, 1; 12:33:40.376 [planetinfo.record]: land_fraction = 0.510000; 12:33:40.376 [planetinfo.record]: polar_cloud_color = 0.708133, 0.608044, 0.744336, 1; 12:33:40.376 [planetinfo.record]: polar_land_color = 0.939453, 0.889912, 0.891073, 1; 12:33:40.376 [planetinfo.record]: polar_sea_color = 0.925781, 0.856633, 0.826332, 1; 12:33:40.376 [planetinfo.record]: sea_color = 0.742188, 0.520446, 0.423279, 1; 12:33:40.376 [planetinfo.record]: rotation_speed = 0.004098 12:33:40.376 [planetinfo.record]: planet zpos = 674180.000000 12:33:40.376 [planetinfo.record]: sun_radius = 120287.883606 12:33:40.376 [planetinfo.record]: sun_vector = 0.659 0.736 0.155 12:33:40.376 [planetinfo.record]: sun_distance = 933480 12:33:40.376 [planetinfo.record]: corona_flare = 0.090892 12:33:40.376 [planetinfo.record]: corona_hues = 0.878067 12:33:40.376 [planetinfo.record]: sun_color = 0.665012, 0.434439, 0.391454, 1 12:33:40.376 [planetinfo.record]: corona_shimmer = 0.404638 12:33:40.376 [planetinfo.record]: station_vector = -0.681 -0.664 0.308 12:33:40.376 [planetinfo.record]: station = coriolis 12:33:44.706 [PLANETINFO OVER]: Done 12:33:44.707 [PLANETINFO LOGGING]: 2 185 12:33:45.715 [planetinfo.record]: seed = 73 17 197 232 12:33:45.715 [planetinfo.record]: coordinates = 17 41 12:33:45.715 [planetinfo.record]: government = 1; 12:33:45.715 [planetinfo.record]: economy = 3; 12:33:45.715 [planetinfo.record]: techlevel = 6; 12:33:45.716 [planetinfo.record]: population = 29; 12:33:45.716 [planetinfo.record]: productivity = 8120; 12:33:45.716 [planetinfo.record]: name = "Usgeriar"; 12:33:45.716 [planetinfo.record]: inhabitant = "Small Slimy Rodent"; 12:33:45.716 [planetinfo.record]: inhabitants = "Small Slimy Rodents"; 12:33:45.716 [planetinfo.record]: description = "This world is very fabled for its ancient mountains."; 12:33:45.732 [planetinfo.record]: air_color = 0.501036, 0.984691, 0.859609, 1; 12:33:45.732 [planetinfo.record]: cloud_alpha = 1.000000; 12:33:45.732 [planetinfo.record]: cloud_color = 0.957031, 0.315895, 0.119629, 1; 12:33:45.732 [planetinfo.record]: cloud_fraction = 0.560000; 12:33:45.732 [planetinfo.record]: land_color = 0.66, 0.318237, 0.226875, 1; 12:33:45.732 [planetinfo.record]: land_fraction = 0.340000; 12:33:45.732 [planetinfo.record]: polar_cloud_color = 0.930664, 0.540994, 0.421707, 1; 12:33:45.732 [planetinfo.record]: polar_land_color = 0.934, 0.813089, 0.780766, 1; 12:33:45.732 [planetinfo.record]: polar_sea_color = 0.879662, 0.929688, 0.885134, 1; 12:33:45.732 [planetinfo.record]: sea_color = 0.551788, 0.703125, 0.568341, 1; 12:33:45.732 [planetinfo.record]: rotation_speed = 0.001046 12:33:45.732 [planetinfo.record]: planet zpos = 634530.000000 12:33:45.732 [planetinfo.record]: sun_radius = 131331.789551 12:33:45.732 [planetinfo.record]: sun_vector = -0.242 -0.282 -0.928 12:33:45.732 [planetinfo.record]: sun_distance = 1122630 12:33:45.732 [planetinfo.record]: corona_flare = 0.033157 12:33:45.732 [planetinfo.record]: corona_hues = 0.948296 12:33:45.732 [planetinfo.record]: sun_color = 0.552054, 0.573467, 0.661923, 1 12:33:45.732 [planetinfo.record]: corona_shimmer = 0.387626 12:33:45.732 [planetinfo.record]: station_vector = 0.035 -0.998 0.059 12:33:45.732 [planetinfo.record]: station = coriolis 12:33:50.122 [PLANETINFO OVER]: Done 12:33:50.124 [PLANETINFO LOGGING]: 2 186 12:33:51.134 [planetinfo.record]: seed = 185 42 173 108 12:33:51.134 [planetinfo.record]: coordinates = 42 30 12:33:51.134 [planetinfo.record]: government = 7; 12:33:51.134 [planetinfo.record]: economy = 6; 12:33:51.134 [planetinfo.record]: techlevel = 7; 12:33:51.134 [planetinfo.record]: population = 42; 12:33:51.134 [planetinfo.record]: productivity = 14784; 12:33:51.134 [planetinfo.record]: name = "Inlain"; 12:33:51.134 [planetinfo.record]: inhabitant = "Blue Fat Bird"; 12:33:51.134 [planetinfo.record]: inhabitants = "Blue Fat Birds"; 12:33:51.134 [planetinfo.record]: description = "The world Inlain is mildly fabled for its inhabitants’ eccentric love for poetry but ravaged by unpredictable civil war."; 12:33:51.138 [planetinfo.record]: air_color = 0.535211, 0.611383, 0.837703, 1; 12:33:51.139 [planetinfo.record]: cloud_alpha = 1.000000; 12:33:51.139 [planetinfo.record]: cloud_color = 0.265869, 0.451235, 0.515625, 1; 12:33:51.139 [planetinfo.record]: cloud_fraction = 0.440000; 12:33:51.139 [planetinfo.record]: land_color = 0.613594, 0.66, 0.618669, 1; 12:33:51.139 [planetinfo.record]: land_fraction = 0.310000; 12:33:51.139 [planetinfo.record]: polar_cloud_color = 0.51042, 0.674897, 0.732031, 1; 12:33:51.139 [planetinfo.record]: polar_land_color = 0.917582, 0.934, 0.919378, 1; 12:33:51.139 [planetinfo.record]: polar_sea_color = 0.712708, 0.928516, 0.928516, 1; 12:33:51.139 [planetinfo.record]: sea_color = 0.0502625, 0.714844, 0.714844, 1; 12:33:51.140 [planetinfo.record]: rotation_speed = 0.002555 12:33:51.140 [planetinfo.record]: planet zpos = 593000.000000 12:33:51.140 [planetinfo.record]: sun_radius = 138128.390503 12:33:51.140 [planetinfo.record]: sun_vector = 0.217 0.954 0.206 12:33:51.140 [planetinfo.record]: sun_distance = 1126700 12:33:51.140 [planetinfo.record]: corona_flare = 0.035675 12:33:51.140 [planetinfo.record]: corona_hues = 0.590332 12:33:51.140 [planetinfo.record]: sun_color = 0.47261, 0.696456, 0.833862, 1 12:33:51.140 [planetinfo.record]: corona_shimmer = 0.455238 12:33:51.141 [planetinfo.record]: station_vector = 0.395 -0.255 0.883 12:33:51.141 [planetinfo.record]: station = coriolis 12:33:55.676 [PLANETINFO OVER]: Done 12:33:55.677 [PLANETINFO LOGGING]: 2 187 12:33:56.688 [planetinfo.record]: seed = 225 110 21 113 12:33:56.688 [planetinfo.record]: coordinates = 110 76 12:33:56.688 [planetinfo.record]: government = 4; 12:33:56.688 [planetinfo.record]: economy = 4; 12:33:56.688 [planetinfo.record]: techlevel = 7; 12:33:56.688 [planetinfo.record]: population = 37; 12:33:56.688 [planetinfo.record]: productivity = 14208; 12:33:56.688 [planetinfo.record]: name = "Atininar"; 12:33:56.688 [planetinfo.record]: inhabitant = "Human Colonial"; 12:33:56.688 [planetinfo.record]: inhabitants = "Human Colonials"; 12:33:56.688 [planetinfo.record]: description = "This world is very well known for vicious Outh gargle blasters and its unusual casinos."; 12:33:56.701 [planetinfo.record]: air_color = 0.594666, 0.93266, 0.892731, 1; 12:33:56.701 [planetinfo.record]: cloud_alpha = 1.000000; 12:33:56.701 [planetinfo.record]: cloud_color = 0.800781, 0.666373, 0.409775, 1; 12:33:56.701 [planetinfo.record]: cloud_fraction = 0.500000; 12:33:56.701 [planetinfo.record]: land_color = 0.66, 0.447708, 0.221719, 1; 12:33:56.701 [planetinfo.record]: land_fraction = 0.550000; 12:33:56.702 [planetinfo.record]: polar_cloud_color = 0.860352, 0.770097, 0.597793, 1; 12:33:56.702 [planetinfo.record]: polar_land_color = 0.934, 0.858894, 0.778941, 1; 12:33:56.702 [planetinfo.record]: polar_sea_color = 0.908485, 0.86116, 0.944141, 1; 12:33:56.702 [planetinfo.record]: sea_color = 0.474211, 0.362213, 0.558594, 1; 12:33:56.702 [planetinfo.record]: rotation_speed = 0.001318 12:33:56.702 [planetinfo.record]: planet zpos = 286380.000000 12:33:56.702 [planetinfo.record]: sun_radius = 75645.330200 12:33:56.702 [planetinfo.record]: sun_vector = 0.598 -0.799 0.064 12:33:56.702 [planetinfo.record]: sun_distance = 700040 12:33:56.702 [planetinfo.record]: corona_flare = 0.032063 12:33:56.702 [planetinfo.record]: corona_hues = 0.750221 12:33:56.702 [planetinfo.record]: sun_color = 0.565421, 0.642696, 0.673349, 1 12:33:56.702 [planetinfo.record]: corona_shimmer = 0.350910 12:33:56.702 [planetinfo.record]: station_vector = 0.244 -0.292 0.925 12:33:56.702 [planetinfo.record]: station = coriolis 12:34:01.306 [PLANETINFO OVER]: Done 12:34:01.306 [PLANETINFO LOGGING]: 2 188 12:34:02.316 [planetinfo.record]: seed = 145 170 13 228 12:34:02.316 [planetinfo.record]: coordinates = 170 12 12:34:02.316 [planetinfo.record]: government = 2; 12:34:02.316 [planetinfo.record]: economy = 4; 12:34:02.316 [planetinfo.record]: techlevel = 6; 12:34:02.316 [planetinfo.record]: population = 31; 12:34:02.316 [planetinfo.record]: productivity = 8928; 12:34:02.316 [planetinfo.record]: name = "Zaanes"; 12:34:02.316 [planetinfo.record]: inhabitant = "Human Colonial"; 12:34:02.316 [planetinfo.record]: inhabitants = "Human Colonials"; 12:34:02.316 [planetinfo.record]: description = "The world Zaanes is reasonably noted for Zero-G cricket and its fabulous cuisine."; 12:34:02.340 [planetinfo.record]: air_color = 0.431308, 0.799924, 0.852012, 1; 12:34:02.340 [planetinfo.record]: cloud_alpha = 1.000000; 12:34:02.340 [planetinfo.record]: cloud_color = 0.369816, 0.558594, 0.0654602, 1; 12:34:02.340 [planetinfo.record]: cloud_fraction = 0.210000; 12:34:02.340 [planetinfo.record]: land_color = 0.66, 0.366094, 0.538304, 1; 12:34:02.340 [planetinfo.record]: land_fraction = 0.700000; 12:34:02.340 [planetinfo.record]: polar_cloud_color = 0.592664, 0.751367, 0.336794, 1; 12:34:02.340 [planetinfo.record]: polar_land_color = 0.934, 0.83002, 0.890946, 1; 12:34:02.340 [planetinfo.record]: polar_sea_color = 0.94375, 0.92098, 0.894351, 1; 12:34:02.340 [planetinfo.record]: sea_color = 0.5625, 0.508214, 0.444727, 1; 12:34:02.340 [planetinfo.record]: rotation_speed = 0.002317 12:34:02.340 [planetinfo.record]: planet zpos = 521300.000000 12:34:02.340 [planetinfo.record]: sun_radius = 102977.749634 12:34:02.340 [planetinfo.record]: sun_vector = 0.255 0.792 -0.554 12:34:02.340 [planetinfo.record]: sun_distance = 842100 12:34:02.341 [planetinfo.record]: corona_flare = 0.039561 12:34:02.341 [planetinfo.record]: corona_hues = 0.741371 12:34:02.341 [planetinfo.record]: sun_color = 0.70751, 0.755913, 0.842871, 1 12:34:02.341 [planetinfo.record]: corona_shimmer = 0.476251 12:34:02.341 [planetinfo.record]: station_vector = 0.909 0.329 0.255 12:34:02.341 [planetinfo.record]: station = coriolis 12:34:07.768 [PLANETINFO OVER]: Done 12:34:07.768 [PLANETINFO LOGGING]: 2 189 12:34:08.786 [planetinfo.record]: seed = 89 169 165 110 12:34:08.786 [planetinfo.record]: coordinates = 169 41 12:34:08.786 [planetinfo.record]: government = 3; 12:34:08.786 [planetinfo.record]: economy = 1; 12:34:08.786 [planetinfo.record]: techlevel = 9; 12:34:08.786 [planetinfo.record]: population = 41; 12:34:08.786 [planetinfo.record]: productivity = 20664; 12:34:08.786 [planetinfo.record]: name = "Releorar"; 12:34:08.786 [planetinfo.record]: inhabitant = "Blue Slimy Lizard"; 12:34:08.786 [planetinfo.record]: inhabitants = "Blue Slimy Lizards"; 12:34:08.786 [planetinfo.record]: description = "Releorar is famous for its inhabitants’ ancient mating traditions but cursed by vicious killer cats."; 12:34:08.797 [planetinfo.record]: air_color = 0.589715, 0.501516, 0.924205, 1; 12:34:08.797 [planetinfo.record]: cloud_alpha = 1.000000; 12:34:08.797 [planetinfo.record]: cloud_color = 0.559868, 0.175674, 0.775391, 1; 12:34:08.798 [planetinfo.record]: cloud_fraction = 0.520000; 12:34:08.798 [planetinfo.record]: land_color = 0.514071, 0.382965, 0.550781, 1; 12:34:08.798 [planetinfo.record]: land_fraction = 0.480000; 12:34:08.798 [planetinfo.record]: polar_cloud_color = 0.701449, 0.438556, 0.848926, 1; 12:34:08.798 [planetinfo.record]: polar_land_color = 0.929177, 0.872945, 0.944922, 1; 12:34:08.798 [planetinfo.record]: polar_sea_color = 0.921094, 0.919604, 0.825746, 1; 12:34:08.798 [planetinfo.record]: sea_color = 0.789062, 0.783957, 0.462341, 1; 12:34:08.798 [planetinfo.record]: rotation_speed = 0.002888 12:34:08.798 [planetinfo.record]: planet zpos = 722590.000000 12:34:08.798 [planetinfo.record]: sun_radius = 139434.883423 12:34:08.798 [planetinfo.record]: sun_vector = -0.623 -0.765 0.160 12:34:08.798 [planetinfo.record]: sun_distance = 1248110 12:34:08.798 [planetinfo.record]: corona_flare = 0.083539 12:34:08.798 [planetinfo.record]: corona_hues = 0.821846 12:34:08.798 [planetinfo.record]: sun_color = 0.602377, 0.645021, 0.682242, 1 12:34:08.798 [planetinfo.record]: corona_shimmer = 0.489798 12:34:08.798 [planetinfo.record]: station_vector = -0.961 -0.194 0.198 12:34:08.798 [planetinfo.record]: station = coriolis 12:34:13.509 [PLANETINFO OVER]: Done 12:34:13.509 [PLANETINFO LOGGING]: 2 190 12:34:14.521 [planetinfo.record]: seed = 137 10 109 166 12:34:14.521 [planetinfo.record]: coordinates = 10 89 12:34:14.521 [planetinfo.record]: government = 1; 12:34:14.521 [planetinfo.record]: economy = 3; 12:34:14.521 [planetinfo.record]: techlevel = 7; 12:34:14.521 [planetinfo.record]: population = 33; 12:34:14.521 [planetinfo.record]: productivity = 9240; 12:34:14.521 [planetinfo.record]: name = "Biarandi"; 12:34:14.521 [planetinfo.record]: inhabitant = "Human Colonial"; 12:34:14.521 [planetinfo.record]: inhabitants = "Human Colonials"; 12:34:14.521 [planetinfo.record]: description = "This planet is most fabled for its inhabitants’ ingrained silliness but scourged by deadly edible poets."; 12:34:14.568 [planetinfo.record]: air_color = 0.590325, 0.47839, 0.945018, 1; 12:34:14.568 [planetinfo.record]: cloud_alpha = 1.000000; 12:34:14.568 [planetinfo.record]: cloud_color = 0.641408, 0.0981903, 0.837891, 1; 12:34:14.568 [planetinfo.record]: cloud_fraction = 0.170000; 12:34:14.568 [planetinfo.record]: land_color = 0.417656, 0.591841, 0.66, 1; 12:34:14.568 [planetinfo.record]: land_fraction = 0.560000; 12:34:14.568 [planetinfo.record]: polar_cloud_color = 0.74851, 0.393131, 0.877051, 1; 12:34:14.568 [planetinfo.record]: polar_land_color = 0.848262, 0.909886, 0.934, 1; 12:34:14.568 [planetinfo.record]: polar_sea_color = 0.930675, 0.938086, 0.888159, 1; 12:34:14.568 [planetinfo.record]: sea_color = 0.599575, 0.619141, 0.487331, 1; 12:34:14.568 [planetinfo.record]: rotation_speed = 0.003202 12:34:14.568 [planetinfo.record]: planet zpos = 654300.000000 12:34:14.568 [planetinfo.record]: sun_radius = 130621.719360 12:34:14.569 [planetinfo.record]: sun_vector = -0.483 -0.082 -0.872 12:34:14.569 [planetinfo.record]: sun_distance = 872400 12:34:14.569 [planetinfo.record]: corona_flare = 0.022467 12:34:14.569 [planetinfo.record]: corona_hues = 0.818069 12:34:14.569 [planetinfo.record]: sun_color = 0.793835, 0.672795, 0.436869, 1 12:34:14.569 [planetinfo.record]: corona_shimmer = 0.291593 12:34:14.569 [planetinfo.record]: station_vector = -0.054 -0.899 0.435 12:34:14.569 [planetinfo.record]: station = coriolis 12:34:19.719 [PLANETINFO OVER]: Done 12:34:19.720 [PLANETINFO LOGGING]: 2 191 12:34:20.739 [planetinfo.record]: seed = 49 109 245 51 12:34:20.739 [planetinfo.record]: coordinates = 109 187 12:34:20.739 [planetinfo.record]: government = 6; 12:34:20.739 [planetinfo.record]: economy = 3; 12:34:20.739 [planetinfo.record]: techlevel = 8; 12:34:20.739 [planetinfo.record]: population = 42; 12:34:20.739 [planetinfo.record]: productivity = 23520; 12:34:20.739 [planetinfo.record]: name = "Beisria"; 12:34:20.739 [planetinfo.record]: inhabitant = "Red Frog"; 12:34:20.739 [planetinfo.record]: inhabitants = "Red Frogs"; 12:34:20.739 [planetinfo.record]: description = "This planet is reasonably famous for its inhabitants’ ancient loathing of casinos."; 12:34:20.776 [planetinfo.record]: air_color = 0.600582, 0.567317, 0.856564, 1; 12:34:20.776 [planetinfo.record]: cloud_alpha = 1.000000; 12:34:20.776 [planetinfo.record]: cloud_color = 0.420467, 0.335312, 0.572266, 1; 12:34:20.776 [planetinfo.record]: cloud_fraction = 0.570000; 12:34:20.776 [planetinfo.record]: land_color = 0.136641, 0.157084, 0.66, 1; 12:34:20.776 [planetinfo.record]: land_fraction = 0.410000; 12:34:20.776 [planetinfo.record]: polar_cloud_color = 0.631933, 0.561482, 0.75752, 1; 12:34:20.776 [planetinfo.record]: polar_land_color = 0.748842, 0.756075, 0.934, 1; 12:34:20.776 [planetinfo.record]: polar_sea_color = 0.891653, 0.933008, 0.884444, 1; 12:34:20.776 [planetinfo.record]: sea_color = 0.551146, 0.669922, 0.530442, 1; 12:34:20.776 [planetinfo.record]: rotation_speed = 0.000665 12:34:20.776 [planetinfo.record]: planet zpos = 443160.000000 12:34:20.776 [planetinfo.record]: sun_radius = 96363.091736 12:34:20.776 [planetinfo.record]: sun_vector = 0.256 0.658 0.708 12:34:20.776 [planetinfo.record]: sun_distance = 812460 12:34:20.776 [planetinfo.record]: corona_flare = 0.069850 12:34:20.776 [planetinfo.record]: corona_hues = 0.900749 12:34:20.777 [planetinfo.record]: sun_color = 0.603318, 0.710927, 0.734702, 1 12:34:20.777 [planetinfo.record]: corona_shimmer = 0.411348 12:34:20.777 [planetinfo.record]: station_vector = -0.600 -0.575 0.556 12:34:20.777 [planetinfo.record]: station = coriolis 12:34:25.674 [PLANETINFO OVER]: Done 12:34:25.675 [PLANETINFO LOGGING]: 2 192 12:34:26.688 [planetinfo.record]: seed = 33 143 77 234 12:34:26.688 [planetinfo.record]: coordinates = 143 254 12:34:26.689 [planetinfo.record]: government = 4; 12:34:26.689 [planetinfo.record]: economy = 6; 12:34:26.689 [planetinfo.record]: techlevel = 6; 12:34:26.689 [planetinfo.record]: population = 35; 12:34:26.689 [planetinfo.record]: productivity = 8960; 12:34:26.689 [planetinfo.record]: name = "Artiat"; 12:34:26.689 [planetinfo.record]: inhabitant = "Human Colonial"; 12:34:26.689 [planetinfo.record]: inhabitants = "Human Colonials"; 12:34:26.689 [planetinfo.record]: description = "The world Artiat is most well known for its great dense forests."; 12:34:26.692 [planetinfo.record]: air_color = 0.6022, 0.548817, 0.878678, 1; 12:34:26.692 [planetinfo.record]: cloud_alpha = 1.000000; 12:34:26.692 [planetinfo.record]: cloud_color = 0.470272, 0.301872, 0.638672, 1; 12:34:26.692 [planetinfo.record]: cloud_fraction = 0.480000; 12:34:26.692 [planetinfo.record]: land_color = 0.636313, 0.66, 0.226875, 1; 12:34:26.692 [planetinfo.record]: land_fraction = 0.490000; 12:34:26.692 [planetinfo.record]: polar_cloud_color = 0.657642, 0.527883, 0.787402, 1; 12:34:26.692 [planetinfo.record]: polar_land_color = 0.92562, 0.934, 0.780766, 1; 12:34:26.692 [planetinfo.record]: polar_sea_color = 0.939648, 0.900869, 0.88193, 1; 12:34:26.692 [planetinfo.record]: sea_color = 0.603516, 0.503886, 0.45523, 1; 12:34:26.692 [planetinfo.record]: rotation_speed = 0.000441 12:34:26.692 [planetinfo.record]: planet zpos = 551900.000000 12:34:26.692 [planetinfo.record]: sun_radius = 138913.977814 12:34:26.692 [planetinfo.record]: sun_vector = 0.088 0.410 -0.908 12:34:26.692 [planetinfo.record]: sun_distance = 1158990 12:34:26.692 [planetinfo.record]: corona_flare = 0.026434 12:34:26.692 [planetinfo.record]: corona_hues = 0.537254 12:34:26.692 [planetinfo.record]: sun_color = 0.703392, 0.833081, 0.848831, 1 12:34:26.693 [planetinfo.record]: corona_shimmer = 0.882759 12:34:26.693 [planetinfo.record]: station_vector = 0.170 -0.585 0.793 12:34:26.693 [planetinfo.record]: station = coriolis 12:34:31.435 [PLANETINFO OVER]: Done 12:34:31.436 [PLANETINFO LOGGING]: 2 193 12:34:32.449 [planetinfo.record]: seed = 233 82 133 187 12:34:32.449 [planetinfo.record]: coordinates = 82 241 12:34:32.449 [planetinfo.record]: government = 5; 12:34:32.449 [planetinfo.record]: economy = 1; 12:34:32.449 [planetinfo.record]: techlevel = 11; 12:34:32.449 [planetinfo.record]: population = 51; 12:34:32.449 [planetinfo.record]: productivity = 33048; 12:34:32.449 [planetinfo.record]: name = "Anondi"; 12:34:32.449 [planetinfo.record]: inhabitant = "Harmless Bony Feline"; 12:34:32.449 [planetinfo.record]: inhabitants = "Harmless Bony Felines"; 12:34:32.449 [planetinfo.record]: description = "Anondi is most noted for the Anondiian deadly goat and its pink oceans."; 12:34:32.475 [planetinfo.record]: air_color = 0.665556, 0.877377, 0.867232, 1; 12:34:32.475 [planetinfo.record]: cloud_alpha = 1.000000; 12:34:32.475 [planetinfo.record]: cloud_color = 0.634766, 0.624228, 0.55542, 1; 12:34:32.475 [planetinfo.record]: cloud_fraction = 0.460000; 12:34:32.475 [planetinfo.record]: land_color = 0.66, 0.616293, 0.260391, 1; 12:34:32.475 [planetinfo.record]: land_fraction = 0.380000; 12:34:32.475 [planetinfo.record]: polar_cloud_color = 0.785645, 0.777493, 0.724266, 1; 12:34:32.475 [planetinfo.record]: polar_land_color = 0.934, 0.918537, 0.792623, 1; 12:34:32.475 [planetinfo.record]: polar_sea_color = 0.915, 0.852597, 0.844088, 1; 12:34:32.475 [planetinfo.record]: sea_color = 0.85, 0.61812, 0.5865, 1; 12:34:32.475 [planetinfo.record]: rotation_speed = 0.004785 12:34:32.475 [planetinfo.record]: planet zpos = 628540.000000 12:34:32.475 [planetinfo.record]: sun_radius = 117655.947266 12:34:32.475 [planetinfo.record]: sun_vector = 0.071 -0.244 -0.967 12:34:32.475 [planetinfo.record]: sun_distance = 1085660 12:34:32.475 [planetinfo.record]: corona_flare = 0.079712 12:34:32.475 [planetinfo.record]: corona_hues = 0.592827 12:34:32.475 [planetinfo.record]: sun_color = 0.769, 0.751627, 0.712316, 1 12:34:32.476 [planetinfo.record]: corona_shimmer = 0.829948 12:34:32.476 [planetinfo.record]: station_vector = 0.471 -0.842 0.262 12:34:32.476 [planetinfo.record]: station = dodecahedron 12:34:36.984 [PLANETINFO OVER]: Done 12:34:36.984 [PLANETINFO LOGGING]: 2 194 12:34:38.003 [planetinfo.record]: seed = 217 200 45 214 12:34:38.003 [planetinfo.record]: coordinates = 200 13 12:34:38.003 [planetinfo.record]: government = 3; 12:34:38.003 [planetinfo.record]: economy = 5; 12:34:38.003 [planetinfo.record]: techlevel = 4; 12:34:38.003 [planetinfo.record]: population = 25; 12:34:38.003 [planetinfo.record]: productivity = 7000; 12:34:38.003 [planetinfo.record]: name = "Veinmaa"; 12:34:38.003 [planetinfo.record]: inhabitant = "Human Colonial"; 12:34:38.003 [planetinfo.record]: inhabitants = "Human Colonials"; 12:34:38.003 [planetinfo.record]: description = "This planet is beset by evil disease."; 12:34:38.022 [planetinfo.record]: air_color = 0.458694, 0.530569, 0.913148, 1; 12:34:38.022 [planetinfo.record]: cloud_alpha = 1.000000; 12:34:38.022 [planetinfo.record]: cloud_color = 0.0782776, 0.384299, 0.742188, 1; 12:34:38.023 [planetinfo.record]: cloud_fraction = 0.230000; 12:34:38.023 [planetinfo.record]: land_color = 0.27876, 0.12375, 0.66, 1; 12:34:38.023 [planetinfo.record]: land_fraction = 0.380000; 12:34:38.023 [planetinfo.record]: polar_cloud_color = 0.367719, 0.582638, 0.833984, 1; 12:34:38.023 [planetinfo.record]: polar_land_color = 0.799122, 0.744281, 0.934, 1; 12:34:38.023 [planetinfo.record]: polar_sea_color = 0.936328, 0.927599, 0.880459, 1; 12:34:38.023 [planetinfo.record]: sea_color = 0.636719, 0.612974, 0.484752, 1; 12:34:38.023 [planetinfo.record]: rotation_speed = 0.003878 12:34:38.023 [planetinfo.record]: planet zpos = 500720.000000 12:34:38.023 [planetinfo.record]: sun_radius = 137845.667725 12:34:38.023 [planetinfo.record]: sun_vector = 0.613 0.604 0.510 12:34:38.023 [planetinfo.record]: sun_distance = 864880 12:34:38.023 [planetinfo.record]: corona_flare = 0.088954 12:34:38.023 [planetinfo.record]: corona_hues = 0.640686 12:34:38.023 [planetinfo.record]: sun_color = 0.703864, 0.643302, 0.597102, 1 12:34:38.023 [planetinfo.record]: corona_shimmer = 0.315830 12:34:38.023 [planetinfo.record]: station_vector = -0.416 -0.580 0.701 12:34:38.023 [planetinfo.record]: station = coriolis 12:34:43.378 [PLANETINFO OVER]: Done 12:34:43.379 [PLANETINFO LOGGING]: 2 195 12:34:44.390 [planetinfo.record]: seed = 1 207 213 199 12:34:44.390 [planetinfo.record]: coordinates = 207 75 12:34:44.390 [planetinfo.record]: government = 0; 12:34:44.390 [planetinfo.record]: economy = 3; 12:34:44.390 [planetinfo.record]: techlevel = 7; 12:34:44.390 [planetinfo.record]: population = 32; 12:34:44.390 [planetinfo.record]: productivity = 7168; 12:34:44.390 [planetinfo.record]: name = "Soxeorza"; 12:34:44.390 [planetinfo.record]: inhabitant = "Fierce Fat Insect"; 12:34:44.390 [planetinfo.record]: inhabitants = "Fierce Fat Insects"; 12:34:44.390 [planetinfo.record]: description = "The world Soxeorza is notable for the Soxeorzaian tree wolf and its exciting sit coms."; 12:34:44.399 [planetinfo.record]: air_color = 0.671783, 0.745387, 0.983391, 1; 12:34:44.399 [planetinfo.record]: cloud_alpha = 1.000000; 12:34:44.399 [planetinfo.record]: cloud_color = 0.618042, 0.851029, 0.953125, 1; 12:34:44.399 [planetinfo.record]: cloud_fraction = 0.160000; 12:34:44.400 [planetinfo.record]: land_color = 0.180138, 0.503906, 0.161407, 1; 12:34:44.400 [planetinfo.record]: land_fraction = 0.300000; 12:34:44.400 [planetinfo.record]: polar_cloud_color = 0.724801, 0.866718, 0.928906, 1; 12:34:44.400 [planetinfo.record]: polar_land_color = 0.797074, 0.949609, 0.78825, 1; 12:34:44.400 [planetinfo.record]: polar_sea_color = 0.922852, 0.866356, 0.816507, 1; 12:34:44.400 [planetinfo.record]: sea_color = 0.771484, 0.582569, 0.415878, 1; 12:34:44.400 [planetinfo.record]: rotation_speed = 0.000122 12:34:44.400 [planetinfo.record]: planet zpos = 529650.000000 12:34:44.400 [planetinfo.record]: sun_radius = 150574.548340 12:34:44.400 [planetinfo.record]: sun_vector = 0.298 -0.824 0.483 12:34:44.400 [planetinfo.record]: sun_distance = 770400 12:34:44.400 [planetinfo.record]: corona_flare = 0.042403 12:34:44.400 [planetinfo.record]: corona_hues = 0.843246 12:34:44.400 [planetinfo.record]: sun_color = 0.690721, 0.757748, 0.769669, 1 12:34:44.401 [planetinfo.record]: corona_shimmer = 0.690536 12:34:44.401 [planetinfo.record]: station_vector = 0.052 0.674 0.737 12:34:44.401 [planetinfo.record]: station = coriolis 12:34:49.110 [PLANETINFO OVER]: Done 12:34:49.111 [PLANETINFO LOGGING]: 2 196 12:34:50.115 [planetinfo.record]: seed = 49 36 141 128 12:34:50.115 [planetinfo.record]: coordinates = 36 121 12:34:50.115 [planetinfo.record]: government = 6; 12:34:50.115 [planetinfo.record]: economy = 1; 12:34:50.115 [planetinfo.record]: techlevel = 9; 12:34:50.116 [planetinfo.record]: population = 44; 12:34:50.116 [planetinfo.record]: productivity = 31680; 12:34:50.116 [planetinfo.record]: name = "Rige"; 12:34:50.116 [planetinfo.record]: inhabitant = "Large Black Furry Humanoid"; 12:34:50.116 [planetinfo.record]: inhabitants = "Large Black Furry Humanoids"; 12:34:50.116 [planetinfo.record]: description = "This planet is most notable for Rigeian Ala’ water but scourged by deadly tree grubs."; 12:34:50.132 [planetinfo.record]: air_color = 0.768167, 0.721673, 0.995748, 1; 12:34:50.132 [planetinfo.record]: cloud_alpha = 1.000000; 12:34:50.132 [planetinfo.record]: cloud_color = 0.883318, 0.765884, 0.990234, 1; 12:34:50.132 [planetinfo.record]: cloud_fraction = 0.430000; 12:34:50.132 [planetinfo.record]: land_color = 0.53125, 0.419189, 0.52162, 1; 12:34:50.132 [planetinfo.record]: land_fraction = 0.440000; 12:34:50.132 [planetinfo.record]: polar_cloud_color = 0.881794, 0.811706, 0.945605, 1; 12:34:50.132 [planetinfo.record]: polar_land_color = 0.946875, 0.896942, 0.942584, 1; 12:34:50.132 [planetinfo.record]: polar_sea_color = 0.867283, 0.919141, 0.84105, 1; 12:34:50.132 [planetinfo.record]: sea_color = 0.626112, 0.808594, 0.533798, 1; 12:34:50.132 [planetinfo.record]: rotation_speed = 0.003501 12:34:50.132 [planetinfo.record]: planet zpos = 342240.000000 12:34:50.132 [planetinfo.record]: sun_radius = 59860.841064 12:34:50.133 [planetinfo.record]: sun_vector = -0.649 0.732 -0.209 12:34:50.133 [planetinfo.record]: sun_distance = 598920 12:34:50.133 [planetinfo.record]: corona_flare = 0.045334 12:34:50.133 [planetinfo.record]: corona_hues = 0.738800 12:34:50.133 [planetinfo.record]: sun_color = 0.648398, 0.656942, 0.83183, 1 12:34:50.133 [planetinfo.record]: corona_shimmer = 0.382103 12:34:50.133 [planetinfo.record]: station_vector = 0.671 -0.622 0.404 12:34:50.133 [planetinfo.record]: station = coriolis 12:34:54.603 [PLANETINFO OVER]: Done 12:34:54.604 [PLANETINFO LOGGING]: 2 197 12:34:55.613 [planetinfo.record]: seed = 249 97 101 67 12:34:55.613 [planetinfo.record]: coordinates = 97 195 12:34:55.613 [planetinfo.record]: government = 7; 12:34:55.613 [planetinfo.record]: economy = 3; 12:34:55.613 [planetinfo.record]: techlevel = 9; 12:34:55.613 [planetinfo.record]: population = 47; 12:34:55.613 [planetinfo.record]: productivity = 28952; 12:34:55.613 [planetinfo.record]: name = "Geusdi"; 12:34:55.613 [planetinfo.record]: inhabitant = "Human Colonial"; 12:34:55.613 [planetinfo.record]: inhabitants = "Human Colonials"; 12:34:55.613 [planetinfo.record]: description = "This planet is reasonably famous for the Geusdiian evil talking treeoid."; 12:34:55.618 [planetinfo.record]: air_color = 0.561522, 0.946318, 0.864822, 1; 12:34:55.619 [planetinfo.record]: cloud_alpha = 1.000000; 12:34:55.619 [planetinfo.record]: cloud_color = 0.841797, 0.515025, 0.318962, 1; 12:34:55.619 [planetinfo.record]: cloud_fraction = 0.170000; 12:34:55.619 [planetinfo.record]: land_color = 0.458906, 0.518606, 0.66, 1; 12:34:55.619 [planetinfo.record]: land_fraction = 0.450000; 12:34:55.619 [planetinfo.record]: polar_cloud_color = 0.878809, 0.665597, 0.53767, 1; 12:34:55.619 [planetinfo.record]: polar_land_color = 0.862855, 0.883977, 0.934, 1; 12:34:55.619 [planetinfo.record]: polar_sea_color = 0.938867, 0.889998, 0.878721, 1; 12:34:55.619 [planetinfo.record]: sea_color = 0.611328, 0.484048, 0.454675, 1; 12:34:55.619 [planetinfo.record]: rotation_speed = 0.001607 12:34:55.619 [planetinfo.record]: planet zpos = 441720.000000 12:34:55.619 [planetinfo.record]: sun_radius = 94790.692749 12:34:55.619 [planetinfo.record]: sun_vector = -0.280 0.536 0.796 12:34:55.619 [planetinfo.record]: sun_distance = 809820 12:34:55.619 [planetinfo.record]: corona_flare = 0.089471 12:34:55.619 [planetinfo.record]: corona_hues = 0.954620 12:34:55.620 [planetinfo.record]: sun_color = 0.497741, 0.759834, 0.838419, 1 12:34:55.620 [planetinfo.record]: corona_shimmer = 0.236082 12:34:55.620 [planetinfo.record]: station_vector = 0.105 0.658 0.745 12:34:55.620 [planetinfo.record]: station = coriolis 12:34:59.753 [PLANETINFO OVER]: Done 12:34:59.754 [PLANETINFO LOGGING]: 2 198 12:35:00.762 [planetinfo.record]: seed = 169 185 237 47 12:35:00.762 [planetinfo.record]: coordinates = 185 13 12:35:00.762 [planetinfo.record]: government = 5; 12:35:00.762 [planetinfo.record]: economy = 5; 12:35:00.762 [planetinfo.record]: techlevel = 6; 12:35:00.762 [planetinfo.record]: population = 35; 12:35:00.762 [planetinfo.record]: productivity = 12600; 12:35:00.762 [planetinfo.record]: name = "Atius"; 12:35:00.762 [planetinfo.record]: inhabitant = "Red Fat Insect"; 12:35:00.762 [planetinfo.record]: inhabitants = "Red Fat Insects"; 12:35:00.762 [planetinfo.record]: description = "This world is most fabled for Atiusian evil brandy but cursed by dreadful solar activity."; 12:35:00.783 [planetinfo.record]: air_color = 0.577304, 0.879522, 0.992496, 1; 12:35:00.783 [planetinfo.record]: cloud_alpha = 1.000000; 12:35:00.783 [planetinfo.record]: cloud_color = 0.447626, 0.980469, 0.337036, 1; 12:35:00.783 [planetinfo.record]: cloud_fraction = 0.270000; 12:35:00.784 [planetinfo.record]: land_color = 0.146953, 0.311288, 0.66, 1; 12:35:00.784 [planetinfo.record]: land_fraction = 0.650000; 12:35:00.784 [planetinfo.record]: polar_cloud_color = 0.621519, 0.941211, 0.555167, 1; 12:35:00.784 [planetinfo.record]: polar_land_color = 0.75249, 0.81063, 0.934, 1; 12:35:00.784 [planetinfo.record]: polar_sea_color = 0.899249, 0.93457, 0.886473, 1; 12:35:00.784 [planetinfo.record]: sea_color = 0.555382, 0.654297, 0.519604, 1; 12:35:00.784 [planetinfo.record]: rotation_speed = 0.004425 12:35:00.784 [planetinfo.record]: planet zpos = 889330.000000 12:35:00.784 [planetinfo.record]: sun_radius = 126285.427856 12:35:00.784 [planetinfo.record]: sun_vector = 0.404 0.850 0.338 12:35:00.785 [planetinfo.record]: sun_distance = 1505020 12:35:00.785 [planetinfo.record]: corona_flare = 0.071114 12:35:00.785 [planetinfo.record]: corona_hues = 0.843040 12:35:00.785 [planetinfo.record]: sun_color = 0.720126, 0.527781, 0.448513, 1 12:35:00.785 [planetinfo.record]: corona_shimmer = 0.371368 12:35:00.785 [planetinfo.record]: station_vector = 0.724 -0.364 0.586 12:35:00.786 [planetinfo.record]: station = coriolis 12:35:05.178 [PLANETINFO OVER]: Done 12:35:05.179 [PLANETINFO LOGGING]: 2 199 12:35:06.185 [planetinfo.record]: seed = 81 8 181 224 12:35:06.185 [planetinfo.record]: coordinates = 8 224 12:35:06.185 [planetinfo.record]: government = 2; 12:35:06.185 [planetinfo.record]: economy = 0; 12:35:06.186 [planetinfo.record]: techlevel = 8; 12:35:06.186 [planetinfo.record]: population = 35; 12:35:06.186 [planetinfo.record]: productivity = 16800; 12:35:06.186 [planetinfo.record]: name = "Arbeis"; 12:35:06.186 [planetinfo.record]: inhabitant = "Large Slimy Rodent"; 12:35:06.186 [planetinfo.record]: inhabitants = "Large Slimy Rodents"; 12:35:06.186 [planetinfo.record]: description = "This world is a revolting dump."; 12:35:06.224 [planetinfo.record]: air_color = 0.584064, 0.91628, 0.973635, 1; 12:35:06.224 [planetinfo.record]: cloud_alpha = 1.000000; 12:35:06.224 [planetinfo.record]: cloud_color = 0.672008, 0.923828, 0.368088, 1; 12:35:06.224 [planetinfo.record]: cloud_fraction = 0.330000; 12:35:06.224 [planetinfo.record]: land_color = 0.632812, 0.422699, 0.511341, 1; 12:35:06.224 [planetinfo.record]: land_fraction = 0.650000; 12:35:06.224 [planetinfo.record]: polar_cloud_color = 0.759716, 0.915723, 0.571432, 1; 12:35:06.224 [planetinfo.record]: polar_land_color = 0.936719, 0.858964, 0.891767, 1; 12:35:06.224 [planetinfo.record]: polar_sea_color = 0.909414, 0.926953, 0.870829, 1; 12:35:06.224 [planetinfo.record]: sea_color = 0.675184, 0.730469, 0.553558, 1; 12:35:06.224 [planetinfo.record]: rotation_speed = 0.004479 12:35:06.224 [planetinfo.record]: planet zpos = 310640.000000 12:35:06.224 [planetinfo.record]: sun_radius = 68367.895508 12:35:06.224 [planetinfo.record]: sun_vector = 0.125 -0.956 -0.266 12:35:06.224 [planetinfo.record]: sun_distance = 536560 12:35:06.224 [planetinfo.record]: corona_flare = 0.017929 12:35:06.224 [planetinfo.record]: corona_hues = 0.874870 12:35:06.224 [planetinfo.record]: sun_color = 0.393674, 0.517773, 0.815887, 1 12:35:06.225 [planetinfo.record]: corona_shimmer = 0.308060 12:35:06.225 [planetinfo.record]: station_vector = 0.798 -0.602 0.006 12:35:06.225 [planetinfo.record]: station = coriolis 12:35:11.246 [PLANETINFO OVER]: Done 12:35:11.247 [PLANETINFO LOGGING]: 2 200 12:35:12.250 [planetinfo.record]: seed = 193 93 205 218 12:35:12.250 [planetinfo.record]: coordinates = 93 179 12:35:12.250 [planetinfo.record]: government = 0; 12:35:12.250 [planetinfo.record]: economy = 3; 12:35:12.250 [planetinfo.record]: techlevel = 5; 12:35:12.250 [planetinfo.record]: population = 24; 12:35:12.250 [planetinfo.record]: productivity = 5376; 12:35:12.251 [planetinfo.record]: name = "Qumaza"; 12:35:12.251 [planetinfo.record]: inhabitant = "Rodent"; 12:35:12.251 [planetinfo.record]: inhabitants = "Rodents"; 12:35:12.251 [planetinfo.record]: description = "The planet Qumaza is scourged by deadly edible moths."; 12:35:12.275 [planetinfo.record]: air_color = 0.617876, 0.809938, 0.989244, 1; 12:35:12.276 [planetinfo.record]: cloud_alpha = 1.000000; 12:35:12.276 [planetinfo.record]: cloud_color = 0.458809, 0.970703, 0.69476, 1; 12:35:12.276 [planetinfo.record]: cloud_fraction = 0.320000; 12:35:12.276 [planetinfo.record]: land_color = 0.66, 0.375923, 0.237188, 1; 12:35:12.276 [planetinfo.record]: land_fraction = 0.320000; 12:35:12.276 [planetinfo.record]: polar_cloud_color = 0.628051, 0.936816, 0.770373, 1; 12:35:12.277 [planetinfo.record]: polar_land_color = 0.934, 0.833497, 0.784414, 1; 12:35:12.277 [planetinfo.record]: polar_sea_color = 0.881317, 0.930859, 0.883252, 1; 12:35:12.277 [planetinfo.record]: sea_color = 0.544212, 0.691406, 0.549962, 1; 12:35:12.277 [planetinfo.record]: rotation_speed = 0.001676 12:35:12.277 [planetinfo.record]: planet zpos = 765660.000000 12:35:12.277 [planetinfo.record]: sun_radius = 105808.326416 12:35:12.277 [planetinfo.record]: sun_vector = 0.024 -0.913 -0.407 12:35:12.277 [planetinfo.record]: sun_distance = 1148490 12:35:12.277 [planetinfo.record]: corona_flare = 0.022606 12:35:12.277 [planetinfo.record]: corona_hues = 0.558159 12:35:12.278 [planetinfo.record]: sun_color = 0.217066, 0.593993, 0.657104, 1 12:35:12.278 [planetinfo.record]: corona_shimmer = 0.356360 12:35:12.278 [planetinfo.record]: station_vector = 0.549 -0.831 0.083 12:35:12.278 [planetinfo.record]: station = coriolis 12:35:16.905 [PLANETINFO OVER]: Done 12:35:16.906 [PLANETINFO LOGGING]: 2 201 12:35:17.920 [planetinfo.record]: seed = 137 234 69 250 12:35:17.920 [planetinfo.record]: coordinates = 234 36 12:35:17.920 [planetinfo.record]: government = 1; 12:35:17.920 [planetinfo.record]: economy = 6; 12:35:17.920 [planetinfo.record]: techlevel = 4; 12:35:17.920 [planetinfo.record]: population = 24; 12:35:17.920 [planetinfo.record]: productivity = 3840; 12:35:17.920 [planetinfo.record]: name = "Quusdi"; 12:35:17.920 [planetinfo.record]: inhabitant = "Human Colonial"; 12:35:17.920 [planetinfo.record]: inhabitants = "Human Colonials"; 12:35:17.921 [planetinfo.record]: description = "This world is a tedious place."; 12:35:17.936 [planetinfo.record]: air_color = 0.483663, 0.996398, 0.863795, 1; 12:35:17.936 [planetinfo.record]: cloud_alpha = 1.000000; 12:35:17.936 [planetinfo.record]: cloud_color = 0.992188, 0.274087, 0.0542603, 1; 12:35:17.936 [planetinfo.record]: cloud_fraction = 0.070000; 12:35:17.936 [planetinfo.record]: land_color = 0.595547, 0.66, 0.644894, 1; 12:35:17.936 [planetinfo.record]: land_fraction = 0.600000; 12:35:17.936 [planetinfo.record]: polar_cloud_color = 0.946484, 0.518345, 0.387282, 1; 12:35:17.936 [planetinfo.record]: polar_land_color = 0.911197, 0.934, 0.928656, 1; 12:35:17.936 [planetinfo.record]: polar_sea_color = 0.94375, 0.929577, 0.89601, 1; 12:35:17.936 [planetinfo.record]: sea_color = 0.5625, 0.52871, 0.448682, 1; 12:35:17.936 [planetinfo.record]: rotation_speed = 0.003587 12:35:17.936 [planetinfo.record]: planet zpos = 673200.000000 12:35:17.936 [planetinfo.record]: sun_radius = 184738.114929 12:35:17.936 [planetinfo.record]: sun_vector = 0.073 -0.952 -0.296 12:35:17.936 [planetinfo.record]: sun_distance = 1065900 12:35:17.936 [planetinfo.record]: corona_flare = 0.066010 12:35:17.936 [planetinfo.record]: corona_hues = 0.859535 12:35:17.936 [planetinfo.record]: sun_color = 0.523178, 0.621576, 0.822491, 1 12:35:17.936 [planetinfo.record]: corona_shimmer = 0.387265 12:35:17.937 [planetinfo.record]: station_vector = -0.443 0.024 0.896 12:35:17.937 [planetinfo.record]: station = coriolis 12:35:22.673 [PLANETINFO OVER]: Done 12:35:22.674 [PLANETINFO LOGGING]: 2 202 12:35:23.685 [planetinfo.record]: seed = 249 240 173 231 12:35:23.685 [planetinfo.record]: coordinates = 240 237 12:35:23.685 [planetinfo.record]: government = 7; 12:35:23.685 [planetinfo.record]: economy = 5; 12:35:23.685 [planetinfo.record]: techlevel = 6; 12:35:23.686 [planetinfo.record]: population = 37; 12:35:23.686 [planetinfo.record]: productivity = 16280; 12:35:23.686 [planetinfo.record]: name = "Sobiondi"; 12:35:23.686 [planetinfo.record]: inhabitant = "Fierce Furry Rodent"; 12:35:23.686 [planetinfo.record]: inhabitants = "Fierce Furry Rodents"; 12:35:23.686 [planetinfo.record]: description = "This world is reasonably well known for its great volcanoes but ravaged by vicious disease."; 12:35:23.720 [planetinfo.record]: air_color = 0.754919, 0.593916, 0.889734, 1; 12:35:23.720 [planetinfo.record]: cloud_alpha = 1.000000; 12:35:23.720 [planetinfo.record]: cloud_color = 0.671875, 0.404175, 0.498288, 1; 12:35:23.720 [planetinfo.record]: cloud_fraction = 0.550000; 12:35:23.720 [planetinfo.record]: land_color = 0.640499, 0.652344, 0.634506, 1; 12:35:23.720 [planetinfo.record]: land_fraction = 0.480000; 12:35:23.720 [planetinfo.record]: polar_cloud_color = 0.802344, 0.602541, 0.672784, 1; 12:35:23.720 [planetinfo.record]: polar_land_color = 0.930522, 0.934766, 0.928376, 1; 12:35:23.720 [planetinfo.record]: polar_sea_color = 0.758135, 0.901367, 0.757008, 1; 12:35:23.720 [planetinfo.record]: sea_color = 0.359398, 0.986328, 0.354462, 1; 12:35:23.720 [planetinfo.record]: rotation_speed = 0.000142 12:35:23.720 [planetinfo.record]: planet zpos = 533280.000000 12:35:23.720 [planetinfo.record]: sun_radius = 138911.740723 12:35:23.720 [planetinfo.record]: sun_vector = -0.497 0.867 0.034 12:35:23.720 [planetinfo.record]: sun_distance = 1066560 12:35:23.720 [planetinfo.record]: corona_flare = 0.067213 12:35:23.720 [planetinfo.record]: corona_hues = 0.702507 12:35:23.720 [planetinfo.record]: sun_color = 0.817087, 0.703585, 0.690393, 1 12:35:23.721 [planetinfo.record]: corona_shimmer = 0.246795 12:35:23.721 [planetinfo.record]: station_vector = -0.259 -0.960 0.105 12:35:23.721 [planetinfo.record]: station = coriolis 12:35:28.265 [PLANETINFO OVER]: Done 12:35:28.265 [PLANETINFO LOGGING]: 2 203 12:35:29.279 [planetinfo.record]: seed = 33 77 149 178 12:35:29.279 [planetinfo.record]: coordinates = 77 159 12:35:29.279 [planetinfo.record]: government = 4; 12:35:29.279 [planetinfo.record]: economy = 7; 12:35:29.279 [planetinfo.record]: techlevel = 3; 12:35:29.279 [planetinfo.record]: population = 24; 12:35:29.279 [planetinfo.record]: productivity = 4608; 12:35:29.279 [planetinfo.record]: name = "Enriri"; 12:35:29.279 [planetinfo.record]: inhabitant = "Harmless Horned Bird"; 12:35:29.279 [planetinfo.record]: inhabitants = "Harmless Horned Birds"; 12:35:29.279 [planetinfo.record]: description = "This world is mildly famous for its hoopy night life and its exotic cuisine."; 12:35:29.300 [planetinfo.record]: air_color = 0.765368, 0.512391, 0.958025, 1; 12:35:29.300 [planetinfo.record]: cloud_alpha = 1.000000; 12:35:29.301 [planetinfo.record]: cloud_color = 0.876953, 0.178131, 0.374675, 1; 12:35:29.301 [planetinfo.record]: cloud_fraction = 0.330000; 12:35:29.301 [planetinfo.record]: land_color = 0.580078, 0.66, 0.609424, 1; 12:35:29.301 [planetinfo.record]: land_fraction = 0.390000; 12:35:29.301 [planetinfo.record]: polar_cloud_color = 0.894629, 0.449062, 0.574378, 1; 12:35:29.301 [planetinfo.record]: polar_land_color = 0.905725, 0.934, 0.916107, 1; 12:35:29.301 [planetinfo.record]: polar_sea_color = 0.839265, 0.873609, 0.946484, 1; 12:35:29.301 [planetinfo.record]: sea_color = 0.292664, 0.370337, 0.535156, 1; 12:35:29.301 [planetinfo.record]: rotation_speed = 0.003843 12:35:29.301 [planetinfo.record]: planet zpos = 374550.000000 12:35:29.301 [planetinfo.record]: sun_radius = 71474.553680 12:35:29.301 [planetinfo.record]: sun_vector = -0.571 0.815 -0.102 12:35:29.301 [planetinfo.record]: sun_distance = 612900 12:35:29.301 [planetinfo.record]: corona_flare = 0.045724 12:35:29.301 [planetinfo.record]: corona_hues = 0.723228 12:35:29.301 [planetinfo.record]: sun_color = 0.411454, 0.732189, 0.743033, 1 12:35:29.302 [planetinfo.record]: corona_shimmer = 0.268421 12:35:29.302 [planetinfo.record]: station_vector = 0.144 0.983 0.112 12:35:29.302 [planetinfo.record]: station = coriolis 12:35:34.548 [PLANETINFO OVER]: Done 12:35:34.548 [PLANETINFO LOGGING]: 2 204 12:35:35.558 [planetinfo.record]: seed = 209 239 13 45 12:35:35.558 [planetinfo.record]: coordinates = 239 158 12:35:35.558 [planetinfo.record]: government = 2; 12:35:35.558 [planetinfo.record]: economy = 6; 12:35:35.558 [planetinfo.record]: techlevel = 5; 12:35:35.558 [planetinfo.record]: population = 29; 12:35:35.558 [planetinfo.record]: productivity = 5568; 12:35:35.558 [planetinfo.record]: name = "Dianed"; 12:35:35.558 [planetinfo.record]: inhabitant = "Human Colonial"; 12:35:35.558 [planetinfo.record]: inhabitants = "Human Colonials"; 12:35:35.558 [planetinfo.record]: description = "Dianed is very notable for the Dianedian edible grub."; 12:35:35.588 [planetinfo.record]: air_color = 0.482771, 0.622907, 0.870873, 1; 12:35:35.588 [planetinfo.record]: cloud_alpha = 1.000000; 12:35:35.588 [planetinfo.record]: cloud_color = 0.161018, 0.615234, 0.583297, 1; 12:35:35.588 [planetinfo.record]: cloud_fraction = 0.480000; 12:35:35.588 [planetinfo.record]: land_color = 0.66, 0.180469, 0.450205, 1; 12:35:35.588 [planetinfo.record]: land_fraction = 0.520000; 12:35:35.588 [planetinfo.record]: polar_cloud_color = 0.418394, 0.776855, 0.751651, 1; 12:35:35.588 [planetinfo.record]: polar_land_color = 0.934, 0.764348, 0.859777, 1; 12:35:35.588 [planetinfo.record]: polar_sea_color = 0.937109, 0.897825, 0.876527, 1; 12:35:35.588 [planetinfo.record]: sea_color = 0.628906, 0.52345, 0.466275, 1; 12:35:35.588 [planetinfo.record]: rotation_speed = 0.004744 12:35:35.588 [planetinfo.record]: planet zpos = 829790.000000 12:35:35.589 [planetinfo.record]: sun_radius = 153763.329926 12:35:35.589 [planetinfo.record]: sun_vector = 0.666 0.587 -0.460 12:35:35.589 [planetinfo.record]: sun_distance = 1340430 12:35:35.589 [planetinfo.record]: corona_flare = 0.039948 12:35:35.589 [planetinfo.record]: corona_hues = 0.526520 12:35:35.589 [planetinfo.record]: sun_color = 0.702536, 0.654551, 0.228649, 1 12:35:35.589 [planetinfo.record]: corona_shimmer = 0.469496 12:35:35.589 [planetinfo.record]: station_vector = -0.279 -0.925 0.258 12:35:35.589 [planetinfo.record]: station = coriolis 12:35:40.215 [PLANETINFO OVER]: Done 12:35:40.216 [PLANETINFO LOGGING]: 2 205 12:35:41.223 [planetinfo.record]: seed = 153 192 37 84 12:35:41.224 [planetinfo.record]: coordinates = 192 216 12:35:41.224 [planetinfo.record]: government = 3; 12:35:41.224 [planetinfo.record]: economy = 0; 12:35:41.224 [planetinfo.record]: techlevel = 9; 12:35:41.224 [planetinfo.record]: population = 40; 12:35:41.224 [planetinfo.record]: productivity = 22400; 12:35:41.224 [planetinfo.record]: name = "Rainle"; 12:35:41.224 [planetinfo.record]: inhabitant = "Human Colonial"; 12:35:41.224 [planetinfo.record]: inhabitants = "Human Colonials"; 12:35:41.224 [planetinfo.record]: description = "Rainle is a revolting little planet."; 12:35:41.240 [planetinfo.record]: air_color = 0.694121, 0.746685, 0.971684, 1; 12:35:41.240 [planetinfo.record]: cloud_alpha = 1.000000; 12:35:41.240 [planetinfo.record]: cloud_color = 0.681305, 0.812579, 0.917969, 1; 12:35:41.240 [planetinfo.record]: cloud_fraction = 0.520000; 12:35:41.240 [planetinfo.record]: land_color = 0.66, 0.338822, 0.314531, 1; 12:35:41.240 [planetinfo.record]: land_fraction = 0.440000; 12:35:41.240 [planetinfo.record]: polar_cloud_color = 0.765958, 0.847568, 0.913086, 1; 12:35:41.240 [planetinfo.record]: polar_land_color = 0.934, 0.820371, 0.811777, 1; 12:35:41.240 [planetinfo.record]: polar_sea_color = 0.865362, 0.923437, 0.874436, 1; 12:35:41.240 [planetinfo.record]: sea_color = 0.573022, 0.765625, 0.603117, 1; 12:35:41.240 [planetinfo.record]: rotation_speed = 0.000379 12:35:41.240 [planetinfo.record]: planet zpos = 564480.000000 12:35:41.240 [planetinfo.record]: sun_radius = 75737.197266 12:35:41.240 [planetinfo.record]: sun_vector = 0.959 -0.140 -0.246 12:35:41.240 [planetinfo.record]: sun_distance = 846720 12:35:41.240 [planetinfo.record]: corona_flare = 0.017574 12:35:41.240 [planetinfo.record]: corona_hues = 0.937523 12:35:41.240 [planetinfo.record]: sun_color = 0.452357, 0.55858, 0.682117, 1 12:35:41.240 [planetinfo.record]: corona_shimmer = 0.647065 12:35:41.241 [planetinfo.record]: station_vector = 0.779 0.601 0.176 12:35:41.241 [planetinfo.record]: station = coriolis 12:35:46.604 [PLANETINFO OVER]: Done 12:35:46.605 [PLANETINFO LOGGING]: 2 206 12:35:47.626 [planetinfo.record]: seed = 201 66 109 49 12:35:47.626 [planetinfo.record]: coordinates = 66 1 12:35:47.626 [planetinfo.record]: government = 1; 12:35:47.626 [planetinfo.record]: economy = 3; 12:35:47.626 [planetinfo.record]: techlevel = 7; 12:35:47.626 [planetinfo.record]: population = 33; 12:35:47.626 [planetinfo.record]: productivity = 9240; 12:35:47.626 [planetinfo.record]: name = "Atlaar"; 12:35:47.626 [planetinfo.record]: inhabitant = "Human Colonial"; 12:35:47.626 [planetinfo.record]: inhabitants = "Human Colonials"; 12:35:47.626 [planetinfo.record]: description = "The world Atlaar is very noted for its pink oceans but scourged by dreadful solar activity."; 12:35:47.630 [planetinfo.record]: air_color = 0.674162, 0.773527, 0.968432, 1; 12:35:47.630 [planetinfo.record]: cloud_alpha = 1.000000; 12:35:47.630 [planetinfo.record]: cloud_color = 0.62439, 0.908203, 0.908203, 1; 12:35:47.630 [planetinfo.record]: cloud_fraction = 0.510000; 12:35:47.630 [planetinfo.record]: land_color = 0.200043, 0.539062, 0.427822, 1; 12:35:47.630 [planetinfo.record]: land_fraction = 0.440000; 12:35:47.630 [planetinfo.record]: polar_cloud_color = 0.731213, 0.908691, 0.908691, 1; 12:35:47.630 [planetinfo.record]: polar_land_color = 0.797343, 0.946094, 0.897285, 1; 12:35:47.630 [planetinfo.record]: polar_sea_color = 0.9, 0.828, 0.87552, 1; 12:35:47.631 [planetinfo.record]: sea_color = 1, 0.68, 0.8912, 1; 12:35:47.631 [planetinfo.record]: rotation_speed = 0.000657 12:35:47.631 [planetinfo.record]: planet zpos = 345180.000000 12:35:47.631 [planetinfo.record]: sun_radius = 87713.267212 12:35:47.631 [planetinfo.record]: sun_vector = -0.210 0.069 -0.975 12:35:47.631 [planetinfo.record]: sun_distance = 596220 12:35:47.631 [planetinfo.record]: corona_flare = 0.098563 12:35:47.631 [planetinfo.record]: corona_hues = 0.974678 12:35:47.631 [planetinfo.record]: sun_color = 0.768613, 0.691705, 0.197677, 1 12:35:47.632 [planetinfo.record]: corona_shimmer = 0.338289 12:35:47.632 [planetinfo.record]: station_vector = 0.304 0.477 0.825 12:35:47.632 [planetinfo.record]: station = coriolis 12:35:52.143 [PLANETINFO OVER]: Done 12:35:52.144 [PLANETINFO LOGGING]: 2 207 12:35:53.154 [planetinfo.record]: seed = 113 145 117 241 12:35:53.154 [planetinfo.record]: coordinates = 145 234 12:35:53.154 [planetinfo.record]: government = 6; 12:35:53.154 [planetinfo.record]: economy = 2; 12:35:53.154 [planetinfo.record]: techlevel = 9; 12:35:53.154 [planetinfo.record]: population = 45; 12:35:53.154 [planetinfo.record]: productivity = 28800; 12:35:53.154 [planetinfo.record]: name = "Atdia"; 12:35:53.154 [planetinfo.record]: inhabitant = "Human Colonial"; 12:35:53.154 [planetinfo.record]: inhabitants = "Human Colonials"; 12:35:53.154 [planetinfo.record]: description = "The world Atdia is most famous for the Atdiaian spotted leopard."; 12:35:53.163 [planetinfo.record]: air_color = 0.69192, 0.855296, 0.868922, 1; 12:35:53.163 [planetinfo.record]: cloud_alpha = 1.000000; 12:35:53.163 [planetinfo.record]: cloud_color = 0.607646, 0.609375, 0.602234, 1; 12:35:53.171 [planetinfo.record]: cloud_fraction = 0.380000; 12:35:53.172 [planetinfo.record]: land_color = 0.513047, 0.536008, 0.66, 1; 12:35:53.172 [planetinfo.record]: land_fraction = 0.590000; 12:35:53.172 [planetinfo.record]: polar_cloud_color = 0.772845, 0.774219, 0.768548, 1; 12:35:53.172 [planetinfo.record]: polar_land_color = 0.88201, 0.890133, 0.934, 1; 12:35:53.172 [planetinfo.record]: polar_sea_color = 0.948242, 0.839247, 0.72322, 1; 12:35:53.172 [planetinfo.record]: sea_color = 0.517578, 0.279607, 0.0262833, 1; 12:35:53.172 [planetinfo.record]: rotation_speed = 0.003257 12:35:53.172 [planetinfo.record]: planet zpos = 386040.000000 12:35:53.172 [planetinfo.record]: sun_radius = 102881.069794 12:35:53.172 [planetinfo.record]: sun_vector = -0.686 0.664 -0.299 12:35:53.172 [planetinfo.record]: sun_distance = 739910 12:35:53.172 [planetinfo.record]: corona_flare = 0.027811 12:35:53.172 [planetinfo.record]: corona_hues = 0.966171 12:35:53.172 [planetinfo.record]: sun_color = 0.527225, 0.66739, 0.689816, 1 12:35:53.173 [planetinfo.record]: corona_shimmer = 0.426356 12:35:53.173 [planetinfo.record]: station_vector = 0.775 0.623 0.110 12:35:53.173 [planetinfo.record]: station = coriolis 12:35:57.399 [PLANETINFO OVER]: Done 12:35:57.400 [PLANETINFO LOGGING]: 2 208 12:35:58.408 [planetinfo.record]: seed = 97 78 77 171 12:35:58.408 [planetinfo.record]: coordinates = 78 239 12:35:58.408 [planetinfo.record]: government = 4; 12:35:58.408 [planetinfo.record]: economy = 7; 12:35:58.408 [planetinfo.record]: techlevel = 4; 12:35:58.408 [planetinfo.record]: population = 28; 12:35:58.408 [planetinfo.record]: productivity = 5376; 12:35:58.408 [planetinfo.record]: name = "Maesgeed"; 12:35:58.408 [planetinfo.record]: inhabitant = "Human Colonial"; 12:35:58.408 [planetinfo.record]: inhabitants = "Human Colonials"; 12:35:58.408 [planetinfo.record]: description = "This world is mildly well known for its exotic cuisine and its inhabitants’ ingrained shyness."; 12:35:58.410 [planetinfo.record]: air_color = 0.765149, 0.564835, 0.917701, 1; 12:35:58.410 [planetinfo.record]: cloud_alpha = 1.000000; 12:35:58.410 [planetinfo.record]: cloud_color = 0.755859, 0.336594, 0.454512, 1; 12:35:58.410 [planetinfo.record]: cloud_fraction = 0.410000; 12:35:58.410 [planetinfo.record]: land_color = 0.560547, 0.535537, 0.523323, 1; 12:35:58.410 [planetinfo.record]: land_fraction = 0.340000; 12:35:58.410 [planetinfo.record]: polar_cloud_color = 0.840137, 0.548878, 0.630795, 1; 12:35:58.410 [planetinfo.record]: polar_land_color = 0.943945, 0.933416, 0.928274, 1; 12:35:58.410 [planetinfo.record]: polar_sea_color = 0.915773, 0.924805, 0.852554, 1; 12:35:58.410 [planetinfo.record]: sea_color = 0.72258, 0.751953, 0.516968, 1; 12:35:58.410 [planetinfo.record]: rotation_speed = 0.002902 12:35:58.410 [planetinfo.record]: planet zpos = 685200.000000 12:35:58.410 [planetinfo.record]: sun_radius = 134453.701782 12:35:58.410 [planetinfo.record]: sun_vector = -0.808 0.512 0.293 12:35:58.410 [planetinfo.record]: sun_distance = 1027800 12:35:58.410 [planetinfo.record]: corona_flare = 0.050739 12:35:58.410 [planetinfo.record]: corona_hues = 0.981789 12:35:58.411 [planetinfo.record]: sun_color = 0.797733, 0.579043, 0.540267, 1 12:35:58.411 [planetinfo.record]: corona_shimmer = 0.456620 12:35:58.411 [planetinfo.record]: station_vector = 0.999 -0.020 0.034 12:35:58.411 [planetinfo.record]: station = coriolis 12:36:03.438 [PLANETINFO OVER]: Done 12:36:03.439 [PLANETINFO LOGGING]: 2 209 12:36:04.449 [planetinfo.record]: seed = 41 120 5 69 12:36:04.449 [planetinfo.record]: coordinates = 120 227 12:36:04.449 [planetinfo.record]: government = 5; 12:36:04.449 [planetinfo.record]: economy = 3; 12:36:04.449 [planetinfo.record]: techlevel = 7; 12:36:04.450 [planetinfo.record]: population = 37; 12:36:04.450 [planetinfo.record]: productivity = 18648; 12:36:04.450 [planetinfo.record]: name = "Ceisge"; 12:36:04.450 [planetinfo.record]: inhabitant = "Human Colonial"; 12:36:04.450 [planetinfo.record]: inhabitants = "Human Colonials"; 12:36:04.450 [planetinfo.record]: description = "This planet is fabled for its weird tropical forests and Zero-G cricket."; 12:36:04.460 [planetinfo.record]: air_color = 0.498077, 0.561783, 0.88193, 1; 12:36:04.460 [planetinfo.record]: cloud_alpha = 1.000000; 12:36:04.460 [planetinfo.record]: cloud_color = 0.189972, 0.412041, 0.648438, 1; 12:36:04.460 [planetinfo.record]: cloud_fraction = 0.320000; 12:36:04.460 [planetinfo.record]: land_color = 0.66, 0.0154688, 0.453549, 1; 12:36:04.460 [planetinfo.record]: land_fraction = 0.690000; 12:36:04.460 [planetinfo.record]: polar_cloud_color = 0.441906, 0.611384, 0.791797, 1; 12:36:04.460 [planetinfo.record]: polar_land_color = 0.934, 0.705973, 0.86096, 1; 12:36:04.460 [planetinfo.record]: polar_sea_color = 0.942187, 0.895192, 0.885969, 1; 12:36:04.460 [planetinfo.record]: sea_color = 0.578125, 0.462781, 0.440143, 1; 12:36:04.460 [planetinfo.record]: rotation_speed = 0.002237 12:36:04.460 [planetinfo.record]: planet zpos = 463760.000000 12:36:04.460 [planetinfo.record]: sun_radius = 78923.911133 12:36:04.460 [planetinfo.record]: sun_vector = -0.037 0.903 0.429 12:36:04.460 [planetinfo.record]: sun_distance = 885360 12:36:04.460 [planetinfo.record]: corona_flare = 0.003482 12:36:04.460 [planetinfo.record]: corona_hues = 0.530151 12:36:04.460 [planetinfo.record]: sun_color = 0.676561, 0.680417, 0.750302, 1 12:36:04.460 [planetinfo.record]: corona_shimmer = 0.345997 12:36:04.460 [planetinfo.record]: station_vector = -0.358 0.916 0.183 12:36:04.460 [planetinfo.record]: station = coriolis 12:36:09.265 [PLANETINFO OVER]: Done 12:36:09.265 [PLANETINFO LOGGING]: 2 210 12:36:10.282 [planetinfo.record]: seed = 25 67 45 65 12:36:10.282 [planetinfo.record]: coordinates = 67 93 12:36:10.282 [planetinfo.record]: government = 3; 12:36:10.282 [planetinfo.record]: economy = 5; 12:36:10.282 [planetinfo.record]: techlevel = 7; 12:36:10.282 [planetinfo.record]: population = 37; 12:36:10.282 [planetinfo.record]: productivity = 10360; 12:36:10.282 [planetinfo.record]: name = "Lelebi"; 12:36:10.282 [planetinfo.record]: inhabitant = "Human Colonial"; 12:36:10.282 [planetinfo.record]: inhabitants = "Human Colonials"; 12:36:10.282 [planetinfo.record]: description = "The world Lelebi is mildly fabled for the Lelebiian edible moth but beset by evil disease."; 12:36:10.295 [planetinfo.record]: air_color = 0.708006, 0.777316, 0.946318, 1; 12:36:10.296 [planetinfo.record]: cloud_alpha = 1.000000; 12:36:10.296 [planetinfo.record]: cloud_color = 0.710266, 0.8233, 0.841797, 1; 12:36:10.296 [planetinfo.record]: cloud_fraction = 0.220000; 12:36:10.296 [planetinfo.record]: land_color = 0.446942, 0.0257813, 0.66, 1; 12:36:10.296 [planetinfo.record]: land_fraction = 0.600000; 12:36:10.296 [planetinfo.record]: polar_cloud_color = 0.792987, 0.86674, 0.878809, 1; 12:36:10.296 [planetinfo.record]: polar_land_color = 0.858623, 0.709621, 0.934, 1; 12:36:10.296 [planetinfo.record]: polar_sea_color = 0.944531, 0.910198, 0.893431, 1; 12:36:10.296 [planetinfo.record]: sea_color = 0.554688, 0.474037, 0.43465, 1; 12:36:10.296 [planetinfo.record]: rotation_speed = 0.001290 12:36:10.296 [planetinfo.record]: planet zpos = 470850.000000 12:36:10.296 [planetinfo.record]: sun_radius = 61827.321930 12:36:10.296 [planetinfo.record]: sun_vector = 0.938 -0.337 -0.078 12:36:10.296 [planetinfo.record]: sun_distance = 565020 12:36:10.296 [planetinfo.record]: corona_flare = 0.095045 12:36:10.296 [planetinfo.record]: corona_hues = 0.545479 12:36:10.296 [planetinfo.record]: sun_color = 0.735257, 0.423421, 0.357949, 1 12:36:10.296 [planetinfo.record]: corona_shimmer = 0.323089 12:36:10.297 [planetinfo.record]: station_vector = -0.647 0.651 0.397 12:36:10.297 [planetinfo.record]: station = coriolis 12:36:14.706 [PLANETINFO OVER]: Done 12:36:14.706 [PLANETINFO LOGGING]: 2 211 12:36:15.709 [planetinfo.record]: seed = 65 137 85 209 12:36:15.709 [planetinfo.record]: coordinates = 137 102 12:36:15.709 [planetinfo.record]: government = 0; 12:36:15.709 [planetinfo.record]: economy = 6; 12:36:15.709 [planetinfo.record]: techlevel = 2; 12:36:15.709 [planetinfo.record]: population = 15; 12:36:15.709 [planetinfo.record]: productivity = 1920; 12:36:15.709 [planetinfo.record]: name = "Atan"; 12:36:15.709 [planetinfo.record]: inhabitant = "Human Colonial"; 12:36:15.709 [planetinfo.record]: inhabitants = "Human Colonials"; 12:36:15.709 [planetinfo.record]: description = "The planet Atan is a boring planet."; 12:36:15.732 [planetinfo.record]: air_color = 0.683783, 0.860093, 0.883231, 1; 12:36:15.732 [planetinfo.record]: cloud_alpha = 1.000000; 12:36:15.732 [planetinfo.record]: cloud_color = 0.634028, 0.652344, 0.601379, 1; 12:36:15.732 [planetinfo.record]: cloud_fraction = 0.350000; 12:36:15.732 [planetinfo.record]: land_color = 0.422813, 0.5377, 0.66, 1; 12:36:15.732 [planetinfo.record]: land_fraction = 0.660000; 12:36:15.732 [planetinfo.record]: polar_cloud_color = 0.77963, 0.793555, 0.754807, 1; 12:36:15.732 [planetinfo.record]: polar_land_color = 0.850086, 0.890732, 0.934, 1; 12:36:15.732 [planetinfo.record]: polar_sea_color = 0.941797, 0.941061, 0.894707, 1; 12:36:15.732 [planetinfo.record]: sea_color = 0.582031, 0.580212, 0.465625, 1; 12:36:15.739 [planetinfo.record]: rotation_speed = 0.002616 12:36:15.740 [planetinfo.record]: planet zpos = 385080.000000 12:36:15.740 [planetinfo.record]: sun_radius = 93510.307007 12:36:15.740 [planetinfo.record]: sun_vector = 0.982 -0.125 -0.142 12:36:15.740 [planetinfo.record]: sun_distance = 641800 12:36:15.740 [planetinfo.record]: corona_flare = 0.073007 12:36:15.740 [planetinfo.record]: corona_hues = 0.722824 12:36:15.740 [planetinfo.record]: sun_color = 0.418742, 0.596602, 0.721512, 1 12:36:15.740 [planetinfo.record]: corona_shimmer = 0.286323 12:36:15.740 [planetinfo.record]: station_vector = -0.132 -0.744 0.654 12:36:15.740 [planetinfo.record]: station = coriolis 12:36:20.328 [PLANETINFO OVER]: Done 12:36:20.329 [PLANETINFO LOGGING]: 2 212 12:36:21.353 [planetinfo.record]: seed = 113 173 141 137 12:36:21.353 [planetinfo.record]: coordinates = 173 27 12:36:21.353 [planetinfo.record]: government = 6; 12:36:21.353 [planetinfo.record]: economy = 3; 12:36:21.353 [planetinfo.record]: techlevel = 8; 12:36:21.353 [planetinfo.record]: population = 42; 12:36:21.353 [planetinfo.record]: productivity = 23520; 12:36:21.353 [planetinfo.record]: name = "Esenesce"; 12:36:21.353 [planetinfo.record]: inhabitant = "Small Black Insect"; 12:36:21.353 [planetinfo.record]: inhabitants = "Small Black Insects"; 12:36:21.353 [planetinfo.record]: description = "The planet Esenesce is an unremarkable planet."; 12:36:21.358 [planetinfo.record]: air_color = 0.514123, 0.559574, 0.872174, 1; 12:36:21.358 [planetinfo.record]: cloud_alpha = 1.000000; 12:36:21.358 [planetinfo.record]: cloud_color = 0.227341, 0.371205, 0.619141, 1; 12:36:21.358 [planetinfo.record]: cloud_fraction = 0.130000; 12:36:21.358 [planetinfo.record]: land_color = 0.648438, 0.440735, 0.469943, 1; 12:36:21.358 [planetinfo.record]: land_fraction = 0.310000; 12:36:21.358 [planetinfo.record]: polar_cloud_color = 0.470666, 0.58374, 0.778613, 1; 12:36:21.358 [planetinfo.record]: polar_land_color = 0.935156, 0.860271, 0.870801, 1; 12:36:21.358 [planetinfo.record]: polar_sea_color = 0.949219, 0.746878, 0.713768, 1; 12:36:21.358 [planetinfo.record]: sea_color = 0.507812, 0.0748205, 0.00396729, 1; 12:36:21.358 [planetinfo.record]: rotation_speed = 0.001026 12:36:21.358 [planetinfo.record]: planet zpos = 582230.000000 12:36:21.358 [planetinfo.record]: sun_radius = 92978.019104 12:36:21.358 [planetinfo.record]: sun_vector = 0.388 0.744 -0.544 12:36:21.358 [planetinfo.record]: sun_distance = 1005670 12:36:21.358 [planetinfo.record]: corona_flare = 0.063046 12:36:21.358 [planetinfo.record]: corona_hues = 0.509880 12:36:21.358 [planetinfo.record]: sun_color = 0.310826, 0.36386, 0.75242, 1 12:36:21.359 [planetinfo.record]: corona_shimmer = 0.460315 12:36:21.359 [planetinfo.record]: station_vector = 0.711 -0.250 0.657 12:36:21.359 [planetinfo.record]: station = coriolis 12:36:25.594 [PLANETINFO OVER]: Done 12:36:25.595 [PLANETINFO LOGGING]: 2 213 12:36:26.612 [planetinfo.record]: seed = 57 101 229 64 12:36:26.612 [planetinfo.record]: coordinates = 101 137 12:36:26.612 [planetinfo.record]: government = 7; 12:36:26.612 [planetinfo.record]: economy = 1; 12:36:26.612 [planetinfo.record]: techlevel = 11; 12:36:26.612 [planetinfo.record]: population = 53; 12:36:26.612 [planetinfo.record]: productivity = 41976; 12:36:26.612 [planetinfo.record]: name = "Alace"; 12:36:26.612 [planetinfo.record]: inhabitant = "Large Yellow Fat Bird"; 12:36:26.612 [planetinfo.record]: inhabitants = "Large Yellow Fat Birds"; 12:36:26.612 [planetinfo.record]: description = "This planet is reasonably famous for its inhabitants’ ancient loathing of casinos."; 12:36:26.628 [planetinfo.record]: air_color = 0.595969, 0.467502, 0.955424, 1; 12:36:26.628 [planetinfo.record]: cloud_alpha = 1.000000; 12:36:26.628 [planetinfo.record]: cloud_color = 0.710659, 0.0577164, 0.869141, 1; 12:36:26.628 [planetinfo.record]: cloud_fraction = 0.430000; 12:36:26.628 [planetinfo.record]: land_color = 0.66, 0.177891, 0.59597, 1; 12:36:26.628 [planetinfo.record]: land_fraction = 0.460000; 12:36:26.628 [planetinfo.record]: polar_cloud_color = 0.789558, 0.371152, 0.891113, 1; 12:36:26.628 [planetinfo.record]: polar_land_color = 0.934, 0.763436, 0.911347, 1; 12:36:26.628 [planetinfo.record]: polar_sea_color = 0.897343, 0.934961, 0.888213, 1; 12:36:26.628 [planetinfo.record]: sea_color = 0.545718, 0.650391, 0.520312, 1; 12:36:26.628 [planetinfo.record]: rotation_speed = 0.004107 12:36:26.628 [planetinfo.record]: planet zpos = 379210.000000 12:36:26.628 [planetinfo.record]: sun_radius = 67780.102081 12:36:26.628 [planetinfo.record]: sun_vector = -0.002 0.685 -0.728 12:36:26.628 [planetinfo.record]: sun_distance = 641740 12:36:26.628 [planetinfo.record]: corona_flare = 0.017796 12:36:26.628 [planetinfo.record]: corona_hues = 0.725166 12:36:26.628 [planetinfo.record]: sun_color = 0.787506, 0.616215, 0.575636, 1 12:36:26.628 [planetinfo.record]: corona_shimmer = 1.060453 12:36:26.628 [planetinfo.record]: station_vector = -0.695 0.366 0.619 12:36:26.628 [planetinfo.record]: station = icosahedron 12:36:31.882 [PLANETINFO OVER]: Done 12:36:31.883 [PLANETINFO LOGGING]: 2 214 12:36:32.907 [planetinfo.record]: seed = 233 69 237 74 12:36:32.908 [planetinfo.record]: coordinates = 69 213 12:36:32.908 [planetinfo.record]: government = 5; 12:36:32.908 [planetinfo.record]: economy = 5; 12:36:32.908 [planetinfo.record]: techlevel = 6; 12:36:32.908 [planetinfo.record]: population = 35; 12:36:32.908 [planetinfo.record]: productivity = 12600; 12:36:32.908 [planetinfo.record]: name = "Arbiti"; 12:36:32.908 [planetinfo.record]: inhabitant = "Small Yellow Slimy Lizard"; 12:36:32.908 [planetinfo.record]: inhabitants = "Small Yellow Slimy Lizards"; 12:36:32.908 [planetinfo.record]: description = "The world Arbiti is a dull world."; 12:36:32.921 [planetinfo.record]: air_color = 0.738757, 0.526242, 0.940465, 1; 12:36:32.921 [planetinfo.record]: cloud_alpha = 1.000000; 12:36:32.921 [planetinfo.record]: cloud_color = 0.824219, 0.228592, 0.493832, 1; 12:36:32.921 [planetinfo.record]: cloud_fraction = 0.200000; 12:36:32.921 [planetinfo.record]: land_color = 0.284546, 0.316656, 0.578125, 1; 12:36:32.921 [planetinfo.record]: land_fraction = 0.240000; 12:36:32.921 [planetinfo.record]: polar_cloud_color = 0.870898, 0.477548, 0.652712, 1; 12:36:32.921 [planetinfo.record]: polar_land_color = 0.822574, 0.835657, 0.942187, 1; 12:36:32.921 [planetinfo.record]: polar_sea_color = 0.870134, 0.923047, 0.904445, 1; 12:36:32.921 [planetinfo.record]: sea_color = 0.59308, 0.769531, 0.707498, 1; 12:36:32.921 [planetinfo.record]: rotation_speed = 0.001919 12:36:32.922 [planetinfo.record]: planet zpos = 707850.000000 12:36:32.922 [planetinfo.record]: sun_radius = 144229.854584 12:36:32.922 [planetinfo.record]: sun_vector = 0.803 -0.484 0.349 12:36:32.922 [planetinfo.record]: sun_distance = 1143450 12:36:32.922 [planetinfo.record]: corona_flare = 0.017149 12:36:32.923 [planetinfo.record]: corona_hues = 0.787994 12:36:32.923 [planetinfo.record]: sun_color = 0.2689, 0.324816, 0.703766, 1 12:36:32.924 [planetinfo.record]: corona_shimmer = 0.358132 12:36:32.924 [planetinfo.record]: station_vector = -0.397 0.801 0.447 12:36:32.924 [planetinfo.record]: station = coriolis 12:36:37.957 [PLANETINFO OVER]: Done 12:36:37.958 [PLANETINFO LOGGING]: 2 215 12:36:38.961 [planetinfo.record]: seed = 145 168 53 6 12:36:38.961 [planetinfo.record]: coordinates = 168 247 12:36:38.961 [planetinfo.record]: government = 2; 12:36:38.961 [planetinfo.record]: economy = 7; 12:36:38.961 [planetinfo.record]: techlevel = 1; 12:36:38.961 [planetinfo.record]: population = 14; 12:36:38.961 [planetinfo.record]: productivity = 2016; 12:36:38.961 [planetinfo.record]: name = "Bibira"; 12:36:38.961 [planetinfo.record]: inhabitant = "Human Colonial"; 12:36:38.961 [planetinfo.record]: inhabitants = "Human Colonials"; 12:36:38.961 [planetinfo.record]: description = "This planet is very notable for the Bibiraian tree wolf and its pink oceans."; 12:36:38.995 [planetinfo.record]: air_color = 0.785842, 0.503178, 0.969082, 1; 12:36:38.995 [planetinfo.record]: cloud_alpha = 1.000000; 12:36:38.995 [planetinfo.record]: cloud_color = 0.910156, 0.142212, 0.268203, 1; 12:36:38.995 [planetinfo.record]: cloud_fraction = 0.460000; 12:36:38.995 [planetinfo.record]: land_color = 0.152109, 0.290986, 0.66, 1; 12:36:38.995 [planetinfo.record]: land_fraction = 0.540000; 12:36:38.995 [planetinfo.record]: polar_cloud_color = 0.90957, 0.429914, 0.508608, 1; 12:36:38.995 [planetinfo.record]: polar_land_color = 0.754314, 0.803447, 0.934, 1; 12:36:38.995 [planetinfo.record]: polar_sea_color = 0.905, 0.871062, 0.871062, 1; 12:36:38.995 [planetinfo.record]: sea_color = 0.95, 0.8075, 0.8075, 1; 12:36:38.995 [planetinfo.record]: rotation_speed = 0.001903 12:36:38.995 [planetinfo.record]: planet zpos = 632800.000000 12:36:38.995 [planetinfo.record]: sun_radius = 100876.501465 12:36:38.996 [planetinfo.record]: sun_vector = -0.036 0.999 0.039 12:36:39.003 [planetinfo.record]: sun_distance = 813600 12:36:39.004 [planetinfo.record]: corona_flare = 0.003688 12:36:39.004 [planetinfo.record]: corona_hues = 0.941055 12:36:39.004 [planetinfo.record]: sun_color = 0.687039, 0.712053, 0.722824, 1 12:36:39.004 [planetinfo.record]: corona_shimmer = 0.248357 12:36:39.004 [planetinfo.record]: station_vector = 0.591 -0.492 0.639 12:36:39.004 [planetinfo.record]: station = coriolis 12:36:44.389 [PLANETINFO OVER]: Done 12:36:44.389 [PLANETINFO LOGGING]: 2 216 12:36:45.395 [planetinfo.record]: seed = 1 1 205 251 12:36:45.395 [planetinfo.record]: coordinates = 1 84 12:36:45.395 [planetinfo.record]: government = 0; 12:36:45.395 [planetinfo.record]: economy = 6; 12:36:45.395 [planetinfo.record]: techlevel = 2; 12:36:45.395 [planetinfo.record]: population = 15; 12:36:45.396 [planetinfo.record]: productivity = 1920; 12:36:45.396 [planetinfo.record]: name = "Anatrean"; 12:36:45.396 [planetinfo.record]: inhabitant = "Furry Rodent"; 12:36:45.396 [planetinfo.record]: inhabitants = "Furry Rodents"; 12:36:45.396 [planetinfo.record]: description = "This world is a revolting dump."; 12:36:45.398 [planetinfo.record]: air_color = 0.568887, 0.506206, 0.913148, 1; 12:36:45.398 [planetinfo.record]: cloud_alpha = 1.000000; 12:36:45.399 [planetinfo.record]: cloud_color = 0.455374, 0.194244, 0.742188, 1; 12:36:45.399 [planetinfo.record]: cloud_fraction = 0.220000; 12:36:45.399 [planetinfo.record]: land_color = 0.66, 0.295518, 0.257812, 1; 12:36:45.399 [planetinfo.record]: land_fraction = 0.360000; 12:36:45.399 [planetinfo.record]: polar_cloud_color = 0.632554, 0.449162, 0.833984, 1; 12:36:45.399 [planetinfo.record]: polar_land_color = 0.934, 0.805051, 0.791711, 1; 12:36:45.399 [planetinfo.record]: polar_sea_color = 0.945898, 0.891676, 0.896759, 1; 12:36:45.399 [planetinfo.record]: sea_color = 0.541016, 0.416962, 0.428592, 1; 12:36:45.399 [planetinfo.record]: rotation_speed = 0.004177 12:36:45.399 [planetinfo.record]: planet zpos = 563300.000000 12:36:45.399 [planetinfo.record]: sun_radius = 153106.789703 12:36:45.399 [planetinfo.record]: sun_vector = -0.075 -0.966 0.249 12:36:45.400 [planetinfo.record]: sun_distance = 1295590 12:36:45.400 [planetinfo.record]: corona_flare = 0.075275 12:36:45.400 [planetinfo.record]: corona_hues = 0.720596 12:36:45.400 [planetinfo.record]: sun_color = 0.661261, 0.325876, 0.225621, 1 12:36:45.400 [planetinfo.record]: corona_shimmer = 0.378869 12:36:45.400 [planetinfo.record]: station_vector = -0.620 -0.726 0.298 12:36:45.400 [planetinfo.record]: station = coriolis 12:36:49.931 [PLANETINFO OVER]: Done 12:36:49.932 [PLANETINFO LOGGING]: 2 217 12:36:50.951 [planetinfo.record]: seed = 201 155 197 59 12:36:50.951 [planetinfo.record]: coordinates = 155 78 12:36:50.951 [planetinfo.record]: government = 1; 12:36:50.951 [planetinfo.record]: economy = 6; 12:36:50.951 [planetinfo.record]: techlevel = 5; 12:36:50.951 [planetinfo.record]: population = 28; 12:36:50.951 [planetinfo.record]: productivity = 4480; 12:36:50.951 [planetinfo.record]: name = "Anceison"; 12:36:50.951 [planetinfo.record]: inhabitant = "Red Furry Rodent"; 12:36:50.951 [planetinfo.record]: inhabitants = "Red Furry Rodents"; 12:36:50.951 [planetinfo.record]: description = "This world is plagued by frequent earthquakes."; 12:36:50.968 [planetinfo.record]: air_color = 0.566477, 0.502883, 0.91575, 1; 12:36:50.968 [planetinfo.record]: cloud_alpha = 1.000000; 12:36:50.968 [planetinfo.record]: cloud_color = 0.454033, 0.18457, 0.75, 1; 12:36:50.968 [planetinfo.record]: cloud_fraction = 0.270000; 12:36:50.968 [planetinfo.record]: land_color = 0.585234, 0.66, 0.605678, 1; 12:36:50.968 [planetinfo.record]: land_fraction = 0.470000; 12:36:50.968 [planetinfo.record]: polar_cloud_color = 0.63094, 0.442877, 0.8375, 1; 12:36:50.968 [planetinfo.record]: polar_land_color = 0.907549, 0.934, 0.914782, 1; 12:36:50.968 [planetinfo.record]: polar_sea_color = 0.865354, 0.922852, 0.879728, 1; 12:36:50.968 [planetinfo.record]: sea_color = 0.579216, 0.771484, 0.627283, 1; 12:36:50.968 [planetinfo.record]: rotation_speed = 0.000988 12:36:50.968 [planetinfo.record]: planet zpos = 694440.000000 12:36:50.968 [planetinfo.record]: sun_radius = 187033.763123 12:36:50.968 [planetinfo.record]: sun_vector = 0.980 0.121 -0.156 12:36:50.968 [planetinfo.record]: sun_distance = 1041660 12:36:50.968 [planetinfo.record]: corona_flare = 0.078096 12:36:50.968 [planetinfo.record]: corona_hues = 0.576523 12:36:50.968 [planetinfo.record]: sun_color = 0.477465, 0.619429, 0.803104, 1 12:36:50.968 [planetinfo.record]: corona_shimmer = 0.394477 12:36:50.968 [planetinfo.record]: station_vector = -0.939 0.218 0.266 12:36:50.968 [planetinfo.record]: station = coriolis 12:36:56.043 [PLANETINFO OVER]: Done 12:36:56.044 [PLANETINFO LOGGING]: 2 218 12:36:57.054 [planetinfo.record]: seed = 57 95 173 130 12:36:57.054 [planetinfo.record]: coordinates = 95 253 12:36:57.054 [planetinfo.record]: government = 7; 12:36:57.054 [planetinfo.record]: economy = 5; 12:36:57.054 [planetinfo.record]: techlevel = 9; 12:36:57.054 [planetinfo.record]: population = 49; 12:36:57.054 [planetinfo.record]: productivity = 21560; 12:36:57.054 [planetinfo.record]: name = "Xeonle"; 12:36:57.054 [planetinfo.record]: inhabitant = "Large Black Horned Bird"; 12:36:57.054 [planetinfo.record]: inhabitants = "Large Black Horned Birds"; 12:36:57.054 [planetinfo.record]: description = "This world is very notable for its inhabitants’ unusual silliness and its exotic cuisine."; 12:36:57.072 [planetinfo.record]: air_color = 0.675957, 0.815159, 0.941115, 1; 12:36:57.072 [planetinfo.record]: cloud_alpha = 1.000000; 12:36:57.072 [planetinfo.record]: cloud_color = 0.622856, 0.826172, 0.711807, 1; 12:36:57.072 [planetinfo.record]: cloud_fraction = 0.350000; 12:36:57.072 [planetinfo.record]: land_color = 0.257973, 0.413607, 0.564453, 1; 12:36:57.072 [planetinfo.record]: land_fraction = 0.580000; 12:36:57.072 [planetinfo.record]: polar_cloud_color = 0.737691, 0.871777, 0.796354, 1; 12:36:57.072 [planetinfo.record]: polar_land_color = 0.815475, 0.880515, 0.943555, 1; 12:36:57.072 [planetinfo.record]: polar_sea_color = 0.93298, 0.940039, 0.892486, 1; 12:36:57.072 [planetinfo.record]: sea_color = 0.5816, 0.599609, 0.478282, 1; 12:36:57.072 [planetinfo.record]: rotation_speed = 0.002574 12:36:57.072 [planetinfo.record]: planet zpos = 410760.000000 12:36:57.072 [planetinfo.record]: sun_radius = 95129.586639 12:36:57.072 [planetinfo.record]: sun_vector = -0.092 0.996 -0.011 12:36:57.072 [planetinfo.record]: sun_distance = 821520 12:36:57.072 [planetinfo.record]: corona_flare = 0.049510 12:36:57.072 [planetinfo.record]: corona_hues = 0.925850 12:36:57.072 [planetinfo.record]: sun_color = 0.593052, 0.707021, 0.783014, 1 12:36:57.072 [planetinfo.record]: corona_shimmer = 0.303554 12:36:57.072 [planetinfo.record]: station_vector = -0.233 -0.107 0.967 12:36:57.072 [planetinfo.record]: station = coriolis 12:37:01.693 [PLANETINFO OVER]: Done 12:37:01.694 [PLANETINFO LOGGING]: 2 219 12:37:02.696 [planetinfo.record]: seed = 97 35 21 196 12:37:02.696 [planetinfo.record]: coordinates = 35 193 12:37:02.696 [planetinfo.record]: government = 4; 12:37:02.696 [planetinfo.record]: economy = 1; 12:37:02.696 [planetinfo.record]: techlevel = 11; 12:37:02.696 [planetinfo.record]: population = 50; 12:37:02.696 [planetinfo.record]: productivity = 28800; 12:37:02.696 [planetinfo.record]: name = "Zauseris"; 12:37:02.696 [planetinfo.record]: inhabitant = "Human Colonial"; 12:37:02.697 [planetinfo.record]: inhabitants = "Human Colonials"; 12:37:02.697 [planetinfo.record]: description = "This world is noted for its exciting Zauserisian evil brandy."; 12:37:02.732 [planetinfo.record]: air_color = 0.729126, 0.560432, 0.910547, 1; 12:37:02.732 [planetinfo.record]: cloud_alpha = 1.000000; 12:37:02.732 [planetinfo.record]: cloud_color = 0.734375, 0.327026, 0.546613, 1; 12:37:02.732 [planetinfo.record]: cloud_fraction = 0.310000; 12:37:02.732 [planetinfo.record]: land_color = 0.66, 0.00515625, 0.158635, 1; 12:37:02.732 [planetinfo.record]: land_fraction = 0.630000; 12:37:02.732 [planetinfo.record]: polar_cloud_color = 0.830469, 0.542562, 0.697762, 1; 12:37:02.732 [planetinfo.record]: polar_land_color = 0.934, 0.702324, 0.756623, 1; 12:37:02.732 [planetinfo.record]: polar_sea_color = 0.941211, 0.911283, 0.887257, 1; 12:37:02.732 [planetinfo.record]: sea_color = 0.587891, 0.513118, 0.453089, 1; 12:37:02.732 [planetinfo.record]: rotation_speed = 0.001344 12:37:02.732 [planetinfo.record]: planet zpos = 465000.000000 12:37:02.732 [planetinfo.record]: sun_radius = 72206.878662 12:37:02.732 [planetinfo.record]: sun_vector = 0.106 0.069 0.992 12:37:02.732 [planetinfo.record]: sun_distance = 775000 12:37:02.732 [planetinfo.record]: corona_flare = 0.001913 12:37:02.732 [planetinfo.record]: corona_hues = 0.528076 12:37:02.732 [planetinfo.record]: sun_color = 0.722818, 0.631805, 0.460612, 1 12:37:02.732 [planetinfo.record]: corona_shimmer = 0.536073 12:37:02.732 [planetinfo.record]: station_vector = 0.109 0.030 0.994 12:37:02.732 [planetinfo.record]: station = icosahedron 12:37:07.331 [PLANETINFO OVER]: Done 12:37:07.332 [PLANETINFO LOGGING]: 2 220 12:37:08.348 [planetinfo.record]: seed = 17 253 13 54 12:37:08.348 [planetinfo.record]: coordinates = 253 144 12:37:08.348 [planetinfo.record]: government = 2; 12:37:08.348 [planetinfo.record]: economy = 0; 12:37:08.348 [planetinfo.record]: techlevel = 9; 12:37:08.348 [planetinfo.record]: population = 39; 12:37:08.348 [planetinfo.record]: productivity = 18720; 12:37:08.348 [planetinfo.record]: name = "Vegeve"; 12:37:08.348 [planetinfo.record]: inhabitant = "Human Colonial"; 12:37:08.348 [planetinfo.record]: inhabitants = "Human Colonials"; 12:37:08.348 [planetinfo.record]: description = "The planet Vegeve is mildly well known for its hoopy casinos."; 12:37:08.369 [planetinfo.record]: air_color = 0.49103, 0.59629, 0.873475, 1; 12:37:08.369 [planetinfo.record]: cloud_alpha = 1.000000; 12:37:08.369 [planetinfo.record]: cloud_color = 0.177666, 0.539538, 0.623047, 1; 12:37:08.369 [planetinfo.record]: cloud_fraction = 0.260000; 12:37:08.369 [planetinfo.record]: land_color = 0.541406, 0.66, 0.634984, 1; 12:37:08.369 [planetinfo.record]: land_fraction = 0.270000; 12:37:08.369 [planetinfo.record]: polar_cloud_color = 0.431719, 0.714999, 0.780371, 1; 12:37:08.369 [planetinfo.record]: polar_land_color = 0.892043, 0.934, 0.92515, 1; 12:37:08.370 [planetinfo.record]: polar_sea_color = 0.763241, 0.942773, 0.812332, 1; 12:37:08.370 [planetinfo.record]: sea_color = 0.13636, 0.572266, 0.255553, 1; 12:37:08.370 [planetinfo.record]: rotation_speed = 0.002250 12:37:08.370 [planetinfo.record]: planet zpos = 460500.000000 12:37:08.370 [planetinfo.record]: sun_radius = 98709.288025 12:37:08.370 [planetinfo.record]: sun_vector = -0.064 0.730 0.680 12:37:08.370 [planetinfo.record]: sun_distance = 736800 12:37:08.370 [planetinfo.record]: corona_flare = 0.001588 12:37:08.370 [planetinfo.record]: corona_hues = 0.906898 12:37:08.370 [planetinfo.record]: sun_color = 0.485577, 0.636896, 0.752988, 1 12:37:08.370 [planetinfo.record]: corona_shimmer = 0.323287 12:37:08.370 [planetinfo.record]: station_vector = 0.238 0.969 0.068 12:37:08.370 [planetinfo.record]: station = coriolis 12:37:13.057 [PLANETINFO OVER]: Done 12:37:13.058 [PLANETINFO LOGGING]: 2 221 12:37:14.067 [planetinfo.record]: seed = 217 239 165 169 12:37:14.068 [planetinfo.record]: coordinates = 239 246 12:37:14.068 [planetinfo.record]: government = 3; 12:37:14.068 [planetinfo.record]: economy = 6; 12:37:14.068 [planetinfo.record]: techlevel = 6; 12:37:14.068 [planetinfo.record]: population = 34; 12:37:14.068 [planetinfo.record]: productivity = 7616; 12:37:14.068 [planetinfo.record]: name = "Esaesge"; 12:37:14.068 [planetinfo.record]: inhabitant = "Small Harmless Bug-Eyed Lizard"; 12:37:14.068 [planetinfo.record]: inhabitants = "Small Harmless Bug-Eyed Lizards"; 12:37:14.068 [planetinfo.record]: description = "This planet is beset by deadly earthquakes."; 12:37:14.075 [planetinfo.record]: air_color = 0.499244, 0.496914, 0.902742, 1; 12:37:14.075 [planetinfo.record]: cloud_alpha = 1.000000; 12:37:14.075 [planetinfo.record]: cloud_color = 0.194397, 0.177734, 0.710938, 1; 12:37:14.076 [planetinfo.record]: cloud_fraction = 0.240000; 12:37:14.076 [planetinfo.record]: land_color = 0.595703, 0.554, 0.523567, 1; 12:37:14.076 [planetinfo.record]: land_fraction = 0.330000; 12:37:14.076 [planetinfo.record]: polar_cloud_color = 0.447594, 0.435583, 0.819922, 1; 12:37:14.076 [planetinfo.record]: polar_land_color = 0.94043, 0.92397, 0.91196, 1; 12:37:14.076 [planetinfo.record]: polar_sea_color = 0.808961, 0.90918, 0.794644, 1; 12:37:14.076 [planetinfo.record]: sea_color = 0.50776, 0.908203, 0.450554, 1; 12:37:14.076 [planetinfo.record]: rotation_speed = 0.002926 12:37:14.076 [planetinfo.record]: planet zpos = 696670.000000 12:37:14.076 [planetinfo.record]: sun_radius = 135929.347229 12:37:14.077 [planetinfo.record]: sun_vector = 0.230 -0.941 0.246 12:37:14.077 [planetinfo.record]: sun_distance = 857440 12:37:14.077 [planetinfo.record]: corona_flare = 0.094986 12:37:14.077 [planetinfo.record]: corona_hues = 0.869186 12:37:14.077 [planetinfo.record]: sun_color = 0.365682, 0.420456, 0.777496, 1 12:37:14.078 [planetinfo.record]: corona_shimmer = 0.523676 12:37:14.078 [planetinfo.record]: station_vector = -0.715 -0.449 0.535 12:37:14.078 [planetinfo.record]: station = coriolis 12:37:18.683 [PLANETINFO OVER]: Done 12:37:18.684 [PLANETINFO LOGGING]: 2 222 12:37:19.691 [planetinfo.record]: seed = 9 99 109 28 12:37:19.691 [planetinfo.record]: coordinates = 99 41 12:37:19.691 [planetinfo.record]: government = 1; 12:37:19.691 [planetinfo.record]: economy = 3; 12:37:19.691 [planetinfo.record]: techlevel = 8; 12:37:19.691 [planetinfo.record]: population = 37; 12:37:19.691 [planetinfo.record]: productivity = 10360; 12:37:19.691 [planetinfo.record]: name = "Teususdi"; 12:37:19.691 [planetinfo.record]: inhabitant = "Human Colonial"; 12:37:19.691 [planetinfo.record]: inhabitants = "Human Colonials"; 12:37:19.691 [planetinfo.record]: description = "The world Teususdi is a boring world."; 12:37:19.720 [planetinfo.record]: air_color = 0.572001, 0.544431, 0.873475, 1; 12:37:19.720 [planetinfo.record]: cloud_alpha = 1.000000; 12:37:19.720 [planetinfo.record]: cloud_color = 0.379973, 0.292053, 0.623047, 1; 12:37:19.720 [planetinfo.record]: cloud_fraction = 0.320000; 12:37:19.720 [planetinfo.record]: land_color = 0.537109, 0.478363, 0.490755, 1; 12:37:19.720 [planetinfo.record]: land_fraction = 0.550000; 12:37:19.720 [planetinfo.record]: polar_cloud_color = 0.590089, 0.521263, 0.780371, 1; 12:37:19.720 [planetinfo.record]: polar_land_color = 0.946289, 0.920414, 0.925872, 1; 12:37:19.720 [planetinfo.record]: polar_sea_color = 0.921661, 0.927734, 0.867939, 1; 12:37:19.720 [planetinfo.record]: sea_color = 0.703734, 0.722656, 0.536346, 1; 12:37:19.720 [planetinfo.record]: rotation_speed = 0.003153 12:37:19.720 [planetinfo.record]: planet zpos = 838180.000000 12:37:19.720 [planetinfo.record]: sun_radius = 152014.585419 12:37:19.720 [planetinfo.record]: sun_vector = 0.146 -0.945 -0.292 12:37:19.720 [planetinfo.record]: sun_distance = 1077660 12:37:19.720 [planetinfo.record]: corona_flare = 0.039308 12:37:19.720 [planetinfo.record]: corona_hues = 0.545166 12:37:19.720 [planetinfo.record]: sun_color = 0.684711, 0.684296, 0.678873, 1 12:37:19.721 [planetinfo.record]: corona_shimmer = 0.351562 12:37:19.721 [planetinfo.record]: station_vector = -0.671 -0.697 0.253 12:37:19.721 [planetinfo.record]: station = coriolis 12:37:24.980 [PLANETINFO OVER]: Done 12:37:24.981 [PLANETINFO LOGGING]: 2 223 12:37:25.998 [planetinfo.record]: seed = 177 237 245 190 12:37:25.998 [planetinfo.record]: coordinates = 237 40 12:37:25.998 [planetinfo.record]: government = 6; 12:37:25.998 [planetinfo.record]: economy = 0; 12:37:25.998 [planetinfo.record]: techlevel = 11; 12:37:25.998 [planetinfo.record]: population = 51; 12:37:25.998 [planetinfo.record]: productivity = 40800; 12:37:25.998 [planetinfo.record]: name = "Rilalela"; 12:37:25.998 [planetinfo.record]: inhabitant = "Harmless Furry Insect"; 12:37:25.998 [planetinfo.record]: inhabitants = "Harmless Furry Insects"; 12:37:25.998 [planetinfo.record]: description = "This planet is mildly noted for its ancient Rilalelaian Loenstet banana plantations but cursed by dreadful civil war."; 12:37:26.012 [planetinfo.record]: air_color = 0.565826, 0.968432, 0.964861, 1; 12:37:26.012 [planetinfo.record]: cloud_alpha = 1.000000; 12:37:26.012 [planetinfo.record]: cloud_color = 0.908203, 0.899001, 0.31929, 1; 12:37:26.012 [planetinfo.record]: cloud_fraction = 0.420000; 12:37:26.012 [planetinfo.record]: land_color = 0.506481, 0.66, 0.469219, 1; 12:37:26.012 [planetinfo.record]: land_fraction = 0.520000; 12:37:26.012 [planetinfo.record]: polar_cloud_color = 0.908691, 0.902937, 0.540423, 1; 12:37:26.012 [planetinfo.record]: polar_land_color = 0.879687, 0.934, 0.866504, 1; 12:37:26.012 [planetinfo.record]: polar_sea_color = 0.948047, 0.886479, 0.945642, 1; 12:37:26.012 [planetinfo.record]: sea_color = 0.519531, 0.384575, 0.51426, 1; 12:37:26.012 [planetinfo.record]: rotation_speed = 0.000748 12:37:26.012 [planetinfo.record]: planet zpos = 862810.000000 12:37:26.012 [planetinfo.record]: sun_radius = 194300.565033 12:37:26.012 [planetinfo.record]: sun_vector = -0.029 -0.114 -0.993 12:37:26.012 [planetinfo.record]: sun_distance = 1327400 12:37:26.012 [planetinfo.record]: corona_flare = 0.063095 12:37:26.013 [planetinfo.record]: corona_hues = 0.789413 12:37:26.013 [planetinfo.record]: sun_color = 0.332452, 0.391951, 0.758759, 1 12:37:26.013 [planetinfo.record]: corona_shimmer = 0.306366 12:37:26.013 [planetinfo.record]: station_vector = -0.868 0.318 0.381 12:37:26.013 [planetinfo.record]: station = dodecahedron 12:37:30.678 [PLANETINFO OVER]: Done 12:37:30.678 [PLANETINFO LOGGING]: 2 224 12:37:31.685 [planetinfo.record]: seed = 161 21 77 108 12:37:31.685 [planetinfo.record]: coordinates = 21 129 12:37:31.685 [planetinfo.record]: government = 4; 12:37:31.685 [planetinfo.record]: economy = 1; 12:37:31.685 [planetinfo.record]: techlevel = 9; 12:37:31.685 [planetinfo.record]: population = 42; 12:37:31.685 [planetinfo.record]: productivity = 24192; 12:37:31.685 [planetinfo.record]: name = "Ingece"; 12:37:31.685 [planetinfo.record]: inhabitant = "Human Colonial"; 12:37:31.685 [planetinfo.record]: inhabitants = "Human Colonials"; 12:37:31.685 [planetinfo.record]: description = "The world Ingece is beset by dreadful earthquakes."; 12:37:31.687 [planetinfo.record]: air_color = 0.583804, 0.508478, 0.9151, 1; 12:37:31.688 [planetinfo.record]: cloud_alpha = 1.000000; 12:37:31.688 [planetinfo.record]: cloud_color = 0.511999, 0.1987, 0.748047, 1; 12:37:31.688 [planetinfo.record]: cloud_fraction = 0.140000; 12:37:31.688 [planetinfo.record]: land_color = 0.513672, 0.44545, 0.488622, 1; 12:37:31.688 [planetinfo.record]: land_fraction = 0.570000; 12:37:31.688 [planetinfo.record]: polar_cloud_color = 0.671623, 0.452625, 0.836621, 1; 12:37:31.688 [planetinfo.record]: polar_land_color = 0.948633, 0.917135, 0.937067, 1; 12:37:31.688 [planetinfo.record]: polar_sea_color = 0.932617, 0.930283, 0.872871, 1; 12:37:31.688 [planetinfo.record]: sea_color = 0.673828, 0.667083, 0.50116, 1; 12:37:31.688 [planetinfo.record]: rotation_speed = 0.000364 12:37:31.688 [planetinfo.record]: planet zpos = 709080.000000 12:37:31.688 [planetinfo.record]: sun_radius = 188288.964996 12:37:31.688 [planetinfo.record]: sun_vector = 0.195 0.566 -0.801 12:37:31.688 [planetinfo.record]: sun_distance = 1122710 12:37:31.688 [planetinfo.record]: corona_flare = 0.050887 12:37:31.688 [planetinfo.record]: corona_hues = 0.961090 12:37:31.688 [planetinfo.record]: sun_color = 0.223857, 0.662681, 0.835138, 1 12:37:31.689 [planetinfo.record]: corona_shimmer = 0.398187 12:37:31.689 [planetinfo.record]: station_vector = 0.784 0.204 0.586 12:37:31.689 [planetinfo.record]: station = coriolis 12:37:36.615 [PLANETINFO OVER]: Done 12:37:36.616 [PLANETINFO LOGGING]: 2 225 12:37:37.632 [planetinfo.record]: seed = 105 245 133 126 12:37:37.632 [planetinfo.record]: coordinates = 245 133 12:37:37.632 [planetinfo.record]: government = 5; 12:37:37.632 [planetinfo.record]: economy = 5; 12:37:37.632 [planetinfo.record]: techlevel = 6; 12:37:37.632 [planetinfo.record]: population = 35; 12:37:37.632 [planetinfo.record]: productivity = 12600; 12:37:37.632 [planetinfo.record]: name = "Riordi"; 12:37:37.632 [planetinfo.record]: inhabitant = "Blue Slimy Lizard"; 12:37:37.632 [planetinfo.record]: inhabitants = "Blue Slimy Lizards"; 12:37:37.632 [planetinfo.record]: description = "The world Riordi is scourged by killer edible talking treeoids."; 12:37:37.652 [planetinfo.record]: air_color = 0.897005, 0.787997, 0.991195, 1; 12:37:37.652 [planetinfo.record]: cloud_alpha = 1.000000; 12:37:37.652 [planetinfo.record]: cloud_color = 0.976562, 0.961304, 0.967026, 1; 12:37:37.652 [planetinfo.record]: cloud_fraction = 0.340000; 12:37:37.652 [planetinfo.record]: land_color = 0.179901, 0.511719, 0.255079, 1; 12:37:37.652 [planetinfo.record]: land_fraction = 0.440000; 12:37:37.652 [planetinfo.record]: polar_cloud_color = 0.939453, 0.930279, 0.933719, 1; 12:37:37.652 [planetinfo.record]: polar_land_color = 0.795014, 0.948828, 0.829863, 1; 12:37:37.652 [planetinfo.record]: polar_sea_color = 0.925, 0.862332, 0.824731, 1; 12:37:37.652 [planetinfo.record]: sea_color = 0.75, 0.546753, 0.424805, 1; 12:37:37.652 [planetinfo.record]: rotation_speed = 0.004782 12:37:37.652 [planetinfo.record]: planet zpos = 797400.000000 12:37:37.652 [planetinfo.record]: sun_radius = 166186.850739 12:37:37.652 [planetinfo.record]: sun_vector = -0.701 -0.537 -0.469 12:37:37.652 [planetinfo.record]: sun_distance = 1262550 12:37:37.652 [planetinfo.record]: corona_flare = 0.012149 12:37:37.652 [planetinfo.record]: corona_hues = 0.620514 12:37:37.652 [planetinfo.record]: sun_color = 0.795782, 0.422479, 0.342226, 1 12:37:37.652 [planetinfo.record]: corona_shimmer = 0.382081 12:37:37.652 [planetinfo.record]: station_vector = -0.346 0.936 0.061 12:37:37.652 [planetinfo.record]: station = coriolis 12:37:42.621 [PLANETINFO OVER]: Done 12:37:42.621 [PLANETINFO LOGGING]: 2 226 12:37:43.627 [planetinfo.record]: seed = 89 229 45 76 12:37:43.627 [planetinfo.record]: coordinates = 229 109 12:37:43.627 [planetinfo.record]: government = 3; 12:37:43.627 [planetinfo.record]: economy = 5; 12:37:43.627 [planetinfo.record]: techlevel = 5; 12:37:43.627 [planetinfo.record]: population = 29; 12:37:43.627 [planetinfo.record]: productivity = 8120; 12:37:43.627 [planetinfo.record]: name = "Inrieran"; 12:37:43.627 [planetinfo.record]: inhabitant = "Human Colonial"; 12:37:43.627 [planetinfo.record]: inhabitants = "Human Colonials"; 12:37:43.627 [planetinfo.record]: description = "Inrieran is mildly notable for Inrieranian lethal water."; 12:37:43.648 [planetinfo.record]: air_color = 0.703821, 0.545766, 0.91575, 1; 12:37:43.648 [planetinfo.record]: cloud_alpha = 1.000000; 12:37:43.648 [planetinfo.record]: cloud_color = 0.75, 0.290039, 0.613449, 1; 12:37:43.648 [planetinfo.record]: cloud_fraction = 0.450000; 12:37:43.648 [planetinfo.record]: land_color = 0.214233, 0.442675, 0.609375, 1; 12:37:43.648 [planetinfo.record]: land_fraction = 0.250000; 12:37:43.648 [planetinfo.record]: polar_cloud_color = 0.8375, 0.516486, 0.742199, 1; 12:37:43.648 [planetinfo.record]: polar_land_color = 0.786832, 0.87484, 0.939062, 1; 12:37:43.648 [planetinfo.record]: polar_sea_color = 0.947461, 0.913212, 0.89981, 1; 12:37:43.648 [planetinfo.record]: sea_color = 0.525391, 0.449423, 0.419697, 1; 12:37:43.648 [planetinfo.record]: rotation_speed = 0.003819 12:37:43.648 [planetinfo.record]: planet zpos = 611700.000000 12:37:43.648 [planetinfo.record]: sun_radius = 159724.674225 12:37:43.648 [planetinfo.record]: sun_vector = 0.852 0.524 0.010 12:37:43.648 [planetinfo.record]: sun_distance = 1101060 12:37:43.649 [planetinfo.record]: corona_flare = 0.034254 12:37:43.649 [planetinfo.record]: corona_hues = 0.709518 12:37:43.649 [planetinfo.record]: sun_color = 0.390346, 0.464631, 0.652811, 1 12:37:43.649 [planetinfo.record]: corona_shimmer = 0.230686 12:37:43.649 [planetinfo.record]: station_vector = 0.981 0.110 0.160 12:37:43.649 [planetinfo.record]: station = coriolis 12:37:48.350 [PLANETINFO OVER]: Done 12:37:48.350 [PLANETINFO LOGGING]: 2 227 12:37:49.361 [planetinfo.record]: seed = 129 187 213 42 12:37:49.361 [planetinfo.record]: coordinates = 187 208 12:37:49.361 [planetinfo.record]: government = 0; 12:37:49.361 [planetinfo.record]: economy = 2; 12:37:49.361 [planetinfo.record]: techlevel = 8; 12:37:49.361 [planetinfo.record]: population = 35; 12:37:49.361 [planetinfo.record]: productivity = 8960; 12:37:49.361 [planetinfo.record]: name = "Arveisri"; 12:37:49.361 [planetinfo.record]: inhabitant = "Small Red Bony Humanoid"; 12:37:49.362 [planetinfo.record]: inhabitants = "Small Red Bony Humanoids"; 12:37:49.362 [planetinfo.record]: description = "This world is most fabled for Arveisriian Norest brandy but scourged by deadly civil war."; 12:37:49.388 [planetinfo.record]: air_color = 0.506902, 0.554178, 0.879328, 1; 12:37:49.388 [planetinfo.record]: cloud_alpha = 1.000000; 12:37:49.388 [planetinfo.record]: cloud_color = 0.210205, 0.36825, 0.640625, 1; 12:37:49.388 [planetinfo.record]: cloud_fraction = 0.550000; 12:37:49.388 [planetinfo.record]: land_color = 0.12375, 0.622295, 0.66, 1; 12:37:49.388 [planetinfo.record]: land_fraction = 0.610000; 12:37:49.388 [planetinfo.record]: polar_cloud_color = 0.457265, 0.57881, 0.788281, 1; 12:37:49.388 [planetinfo.record]: polar_land_color = 0.744281, 0.92066, 0.934, 1; 12:37:49.388 [planetinfo.record]: polar_sea_color = 0.926467, 0.936523, 0.885033, 1; 12:37:49.388 [planetinfo.record]: sea_color = 0.6075, 0.634766, 0.495167, 1; 12:37:49.388 [planetinfo.record]: rotation_speed = 0.000048 12:37:49.388 [planetinfo.record]: planet zpos = 834450.000000 12:37:49.388 [planetinfo.record]: sun_radius = 153244.793549 12:37:49.395 [planetinfo.record]: sun_vector = 0.729 0.636 -0.254 12:37:49.396 [planetinfo.record]: sun_distance = 1112600 12:37:49.396 [planetinfo.record]: corona_flare = 0.040137 12:37:49.396 [planetinfo.record]: corona_hues = 0.988914 12:37:49.396 [planetinfo.record]: sun_color = 0.67644, 0.669181, 0.58651, 1 12:37:49.396 [planetinfo.record]: corona_shimmer = 0.608528 12:37:49.396 [planetinfo.record]: station_vector = 0.156 -0.868 0.471 12:37:49.396 [planetinfo.record]: station = coriolis 12:37:54.065 [PLANETINFO OVER]: Done 12:37:54.065 [PLANETINFO LOGGING]: 2 228 12:37:55.074 [planetinfo.record]: seed = 177 126 141 210 12:37:55.074 [planetinfo.record]: coordinates = 126 157 12:37:55.074 [planetinfo.record]: government = 6; 12:37:55.074 [planetinfo.record]: economy = 5; 12:37:55.074 [planetinfo.record]: techlevel = 7; 12:37:55.074 [planetinfo.record]: population = 40; 12:37:55.074 [planetinfo.record]: productivity = 16000; 12:37:55.074 [planetinfo.record]: name = "Enreon"; 12:37:55.074 [planetinfo.record]: inhabitant = "Bony Humanoid"; 12:37:55.074 [planetinfo.record]: inhabitants = "Bony Humanoids"; 12:37:55.074 [planetinfo.record]: description = "The planet Enreon is an unremarkable dump."; 12:37:55.085 [planetinfo.record]: air_color = 0.424844, 0.811466, 0.854613, 1; 12:37:55.085 [planetinfo.record]: cloud_alpha = 1.000000; 12:37:55.085 [planetinfo.record]: cloud_color = 0.405307, 0.566406, 0.0508881, 1; 12:37:55.085 [planetinfo.record]: cloud_fraction = 0.490000; 12:37:55.085 [planetinfo.record]: land_color = 0.53625, 0.66, 0.616494, 1; 12:37:55.085 [planetinfo.record]: land_fraction = 0.560000; 12:37:55.085 [planetinfo.record]: polar_cloud_color = 0.620691, 0.754883, 0.325469, 1; 12:37:55.085 [planetinfo.record]: polar_land_color = 0.890219, 0.934, 0.918608, 1; 12:37:55.085 [planetinfo.record]: polar_sea_color = 0.818179, 0.948828, 0.924331, 1; 12:37:55.085 [planetinfo.record]: sea_color = 0.229874, 0.511719, 0.458873, 1; 12:37:55.085 [planetinfo.record]: rotation_speed = 0.003552 12:37:55.085 [planetinfo.record]: planet zpos = 518100.000000 12:37:55.085 [planetinfo.record]: sun_radius = 85778.690186 12:37:55.085 [planetinfo.record]: sun_vector = 0.311 0.875 0.371 12:37:55.085 [planetinfo.record]: sun_distance = 690800 12:37:55.085 [planetinfo.record]: corona_flare = 0.066270 12:37:55.085 [planetinfo.record]: corona_hues = 0.948326 12:37:55.085 [planetinfo.record]: sun_color = 0.700333, 0.69517, 0.581275, 1 12:37:55.086 [planetinfo.record]: corona_shimmer = 0.493130 12:37:55.086 [planetinfo.record]: station_vector = 0.183 -0.784 0.593 12:37:55.086 [planetinfo.record]: station = coriolis 12:37:59.869 [PLANETINFO OVER]: Done 12:37:59.870 [PLANETINFO LOGGING]: 2 229 12:38:00.876 [planetinfo.record]: seed = 121 0 101 46 12:38:00.876 [planetinfo.record]: coordinates = 0 63 12:38:00.876 [planetinfo.record]: government = 7; 12:38:00.876 [planetinfo.record]: economy = 7; 12:38:00.876 [planetinfo.record]: techlevel = 4; 12:38:00.876 [planetinfo.record]: population = 31; 12:38:00.876 [planetinfo.record]: productivity = 8184; 12:38:00.876 [planetinfo.record]: name = "Rereis"; 12:38:00.876 [planetinfo.record]: inhabitant = "Human Colonial"; 12:38:00.876 [planetinfo.record]: inhabitants = "Human Colonials"; 12:38:00.876 [planetinfo.record]: description = "Rereis is mildly well known for its hoopy casinos."; 12:38:00.892 [planetinfo.record]: air_color = 0.5512, 0.947619, 0.83891, 1; 12:38:00.892 [planetinfo.record]: cloud_alpha = 1.000000; 12:38:00.892 [planetinfo.record]: cloud_color = 0.845703, 0.394772, 0.29071, 1; 12:38:00.892 [planetinfo.record]: cloud_fraction = 0.270000; 12:38:00.892 [planetinfo.record]: land_color = 0.613594, 0.66, 0.646948, 1; 12:38:00.892 [planetinfo.record]: land_fraction = 0.710000; 12:38:00.892 [planetinfo.record]: polar_cloud_color = 0.880566, 0.587116, 0.519397, 1; 12:38:00.892 [planetinfo.record]: polar_land_color = 0.917582, 0.934, 0.929382, 1; 12:38:00.892 [planetinfo.record]: polar_sea_color = 0.926172, 0.832751, 0.82035, 1; 12:38:00.892 [planetinfo.record]: sea_color = 0.738281, 0.440405, 0.400864, 1; 12:38:00.892 [planetinfo.record]: rotation_speed = 0.001583 12:38:00.892 [planetinfo.record]: planet zpos = 832000.000000 12:38:00.893 [planetinfo.record]: sun_radius = 137675.781250 12:38:00.894 [planetinfo.record]: sun_vector = 0.474 0.779 -0.411 12:38:00.894 [planetinfo.record]: sun_distance = 1152000 12:38:00.894 [planetinfo.record]: corona_flare = 0.036208 12:38:00.894 [planetinfo.record]: corona_hues = 0.993736 12:38:00.895 [planetinfo.record]: sun_color = 0.791229, 0.583396, 0.383134, 1 12:38:00.895 [planetinfo.record]: corona_shimmer = 0.320328 12:38:00.895 [planetinfo.record]: station_vector = 0.143 -0.193 0.971 12:38:00.895 [planetinfo.record]: station = coriolis 12:38:05.730 [PLANETINFO OVER]: Done 12:38:05.731 [PLANETINFO LOGGING]: 2 230 12:38:06.752 [planetinfo.record]: seed = 41 58 237 69 12:38:06.752 [planetinfo.record]: coordinates = 58 157 12:38:06.752 [planetinfo.record]: government = 5; 12:38:06.752 [planetinfo.record]: economy = 5; 12:38:06.752 [planetinfo.record]: techlevel = 7; 12:38:06.752 [planetinfo.record]: population = 39; 12:38:06.752 [planetinfo.record]: productivity = 14040; 12:38:06.752 [planetinfo.record]: name = "Ceisis"; 12:38:06.752 [planetinfo.record]: inhabitant = "Fierce Yellow Rodent"; 12:38:06.752 [planetinfo.record]: inhabitants = "Fierce Yellow Rodents"; 12:38:06.753 [planetinfo.record]: description = "The world Ceisis is mildly noted for its ancient mountains but ravaged by occasional solar activity."; 12:38:06.792 [planetinfo.record]: air_color = 0.733192, 0.784969, 0.926807, 1; 12:38:06.792 [planetinfo.record]: cloud_alpha = 1.000000; 12:38:06.792 [planetinfo.record]: cloud_color = 0.761787, 0.778686, 0.783203, 1; 12:38:06.792 [planetinfo.record]: cloud_fraction = 0.510000; 12:38:06.792 [planetinfo.record]: land_color = 0.66, 0.121172, 0.159058, 1; 12:38:06.792 [planetinfo.record]: land_fraction = 0.560000; 12:38:06.792 [planetinfo.record]: polar_cloud_color = 0.837873, 0.849368, 0.852441, 1; 12:38:06.792 [planetinfo.record]: polar_land_color = 0.934, 0.743369, 0.756773, 1; 12:38:06.792 [planetinfo.record]: polar_sea_color = 0.94375, 0.900019, 0.889927, 1; 12:38:06.792 [planetinfo.record]: sea_color = 0.5625, 0.45824, 0.43418, 1; 12:38:06.792 [planetinfo.record]: rotation_speed = 0.004415 12:38:06.792 [planetinfo.record]: planet zpos = 498480.000000 12:38:06.792 [planetinfo.record]: sun_radius = 100083.662720 12:38:06.793 [planetinfo.record]: sun_vector = 0.759 -0.142 0.636 12:38:06.793 [planetinfo.record]: sun_distance = 996960 12:38:06.793 [planetinfo.record]: corona_flare = 0.038771 12:38:06.793 [planetinfo.record]: corona_hues = 0.636902 12:38:06.793 [planetinfo.record]: sun_color = 0.573582, 0.638617, 0.731445, 1 12:38:06.793 [planetinfo.record]: corona_shimmer = 0.421763 12:38:06.793 [planetinfo.record]: station_vector = -0.559 -0.763 0.327 12:38:06.793 [planetinfo.record]: station = coriolis 12:38:11.817 [PLANETINFO OVER]: Done 12:38:11.818 [PLANETINFO LOGGING]: 2 231 12:38:12.836 [planetinfo.record]: seed = 209 0 181 187 12:38:12.836 [planetinfo.record]: coordinates = 0 157 12:38:12.836 [planetinfo.record]: government = 2; 12:38:12.836 [planetinfo.record]: economy = 5; 12:38:12.836 [planetinfo.record]: techlevel = 3; 12:38:12.836 [planetinfo.record]: population = 20; 12:38:12.836 [planetinfo.record]: productivity = 4800; 12:38:12.836 [planetinfo.record]: name = "Anquvein"; 12:38:12.836 [planetinfo.record]: inhabitant = "Harmless Furry Rodent"; 12:38:12.836 [planetinfo.record]: inhabitants = "Harmless Furry Rodents"; 12:38:12.836 [planetinfo.record]: description = "This planet is most notable for Anquveinian evil juice but cursed by unpredictable solar activity."; 12:38:12.852 [planetinfo.record]: air_color = 0.560981, 0.534749, 0.879979, 1; 12:38:12.852 [planetinfo.record]: cloud_alpha = 1.000000; 12:38:12.852 [planetinfo.record]: cloud_color = 0.361058, 0.271088, 0.642578, 1; 12:38:12.852 [planetinfo.record]: cloud_fraction = 0.260000; 12:38:12.852 [planetinfo.record]: land_color = 0.641953, 0.656616, 0.66, 1; 12:38:12.852 [planetinfo.record]: land_fraction = 0.680000; 12:38:12.852 [planetinfo.record]: polar_cloud_color = 0.573073, 0.504014, 0.78916, 1; 12:38:12.852 [planetinfo.record]: polar_land_color = 0.927615, 0.932803, 0.934, 1; 12:38:12.852 [planetinfo.record]: polar_sea_color = 0.858886, 0.946875, 0.752692, 1; 12:38:12.852 [planetinfo.record]: sea_color = 0.333782, 0.53125, 0.095459, 1; 12:38:12.852 [planetinfo.record]: rotation_speed = 0.004460 12:38:12.852 [planetinfo.record]: planet zpos = 675840.000000 12:38:12.852 [planetinfo.record]: sun_radius = 110048.125000 12:38:12.853 [planetinfo.record]: sun_vector = -0.216 -0.845 -0.489 12:38:12.853 [planetinfo.record]: sun_distance = 1070080 12:38:12.853 [planetinfo.record]: corona_flare = 0.097665 12:38:12.853 [planetinfo.record]: corona_hues = 0.808868 12:38:12.853 [planetinfo.record]: sun_color = 0.357034, 0.473226, 0.766721, 1 12:38:12.853 [planetinfo.record]: corona_shimmer = 0.510365 12:38:12.853 [planetinfo.record]: station_vector = -0.371 -0.912 0.177 12:38:12.853 [planetinfo.record]: station = coriolis 12:38:18.036 [PLANETINFO OVER]: Done 12:38:18.037 [PLANETINFO LOGGING]: 2 232 12:38:19.043 [planetinfo.record]: seed = 65 44 205 156 12:38:19.043 [planetinfo.record]: coordinates = 44 22 12:38:19.043 [planetinfo.record]: government = 0; 12:38:19.043 [planetinfo.record]: economy = 6; 12:38:19.043 [planetinfo.record]: techlevel = 1; 12:38:19.043 [planetinfo.record]: population = 11; 12:38:19.043 [planetinfo.record]: productivity = 1408; 12:38:19.043 [planetinfo.record]: name = "Teonus"; 12:38:19.044 [planetinfo.record]: inhabitant = "Black Horned Lizard"; 12:38:19.044 [planetinfo.record]: inhabitants = "Black Horned Lizards"; 12:38:19.044 [planetinfo.record]: description = "This planet is a tedious little planet."; 12:38:19.060 [planetinfo.record]: air_color = 0.680335, 0.789879, 0.954123, 1; 12:38:19.060 [planetinfo.record]: cloud_alpha = 1.000000; 12:38:19.060 [planetinfo.record]: cloud_color = 0.638786, 0.865234, 0.822775, 1; 12:38:19.060 [planetinfo.record]: cloud_fraction = 0.480000; 12:38:19.060 [planetinfo.record]: land_color = 0.210411, 0.427549, 0.619141, 1; 12:38:19.060 [planetinfo.record]: land_fraction = 0.330000; 12:38:19.060 [planetinfo.record]: polar_cloud_color = 0.74388, 0.889355, 0.862079, 1; 12:38:19.060 [planetinfo.record]: polar_land_color = 0.783265, 0.865514, 0.938086, 1; 12:38:19.067 [planetinfo.record]: polar_sea_color = 0.929148, 0.931055, 0.870045, 1; 12:38:19.067 [planetinfo.record]: sea_color = 0.683806, 0.689453, 0.508741, 1; 12:38:19.068 [planetinfo.record]: rotation_speed = 0.001640 12:38:19.068 [planetinfo.record]: planet zpos = 593200.000000 12:38:19.068 [planetinfo.record]: sun_radius = 148933.605957 12:38:19.068 [planetinfo.record]: sun_vector = -0.438 0.133 -0.889 12:38:19.068 [planetinfo.record]: sun_distance = 1186400 12:38:19.068 [planetinfo.record]: corona_flare = 0.042397 12:38:19.068 [planetinfo.record]: corona_hues = 0.928925 12:38:19.068 [planetinfo.record]: sun_color = 0.809503, 0.645938, 0.497166, 1 12:38:19.068 [planetinfo.record]: corona_shimmer = 0.289286 12:38:19.068 [planetinfo.record]: station_vector = -0.528 -0.802 0.280 12:38:19.068 [planetinfo.record]: station = coriolis 12:38:23.510 [PLANETINFO OVER]: Done 12:38:23.511 [PLANETINFO LOGGING]: 2 233 12:38:24.516 [planetinfo.record]: seed = 9 37 69 173 12:38:24.516 [planetinfo.record]: coordinates = 37 168 12:38:24.516 [planetinfo.record]: government = 1; 12:38:24.516 [planetinfo.record]: economy = 2; 12:38:24.516 [planetinfo.record]: techlevel = 7; 12:38:24.516 [planetinfo.record]: population = 32; 12:38:24.516 [planetinfo.record]: productivity = 10240; 12:38:24.516 [planetinfo.record]: name = "Diqudi"; 12:38:24.516 [planetinfo.record]: inhabitant = "Human Colonial"; 12:38:24.516 [planetinfo.record]: inhabitants = "Human Colonials"; 12:38:24.516 [planetinfo.record]: description = "This world is a tedious little planet."; 12:38:24.552 [planetinfo.record]: air_color = 0.415456, 0.846809, 0.83625, 1; 12:38:24.552 [planetinfo.record]: cloud_alpha = 1.000000; 12:38:24.552 [planetinfo.record]: cloud_color = 0.542969, 0.511419, 0.0381775, 1; 12:38:24.552 [planetinfo.record]: cloud_fraction = 0.330000; 12:38:24.552 [planetinfo.record]: land_color = 0.386316, 0.66, 0.216563, 1; 12:38:24.552 [planetinfo.record]: land_fraction = 0.420000; 12:38:24.552 [planetinfo.record]: polar_cloud_color = 0.744336, 0.717305, 0.311836, 1; 12:38:24.552 [planetinfo.record]: polar_land_color = 0.837174, 0.934, 0.777117, 1; 12:38:24.552 [planetinfo.record]: polar_sea_color = 0.937891, 0.918559, 0.881654, 1; 12:38:24.552 [planetinfo.record]: sea_color = 0.621094, 0.569887, 0.472128, 1; 12:38:24.552 [planetinfo.record]: rotation_speed = 0.003522 12:38:24.552 [planetinfo.record]: planet zpos = 803530.000000 12:38:24.552 [planetinfo.record]: sun_radius = 183687.066650 12:38:24.552 [planetinfo.record]: sun_vector = -0.522 -0.683 -0.511 12:38:24.552 [planetinfo.record]: sun_distance = 1359820 12:38:24.552 [planetinfo.record]: corona_flare = 0.074863 12:38:24.552 [planetinfo.record]: corona_hues = 0.768387 12:38:24.552 [planetinfo.record]: sun_color = 0.823361, 0.829728, 0.829886, 1 12:38:24.553 [planetinfo.record]: corona_shimmer = 0.305293 12:38:24.553 [planetinfo.record]: station_vector = -0.227 0.441 0.868 12:38:24.553 [planetinfo.record]: station = coriolis 12:38:29.072 [PLANETINFO OVER]: Done 12:38:29.073 [PLANETINFO LOGGING]: 2 234 12:38:30.078 [planetinfo.record]: seed = 121 117 173 61 12:38:30.078 [planetinfo.record]: coordinates = 117 77 12:38:30.078 [planetinfo.record]: government = 7; 12:38:30.078 [planetinfo.record]: economy = 5; 12:38:30.078 [planetinfo.record]: techlevel = 7; 12:38:30.078 [planetinfo.record]: population = 41; 12:38:30.078 [planetinfo.record]: productivity = 18040; 12:38:30.078 [planetinfo.record]: name = "Isbeat"; 12:38:30.078 [planetinfo.record]: inhabitant = "Red Slimy Frog"; 12:38:30.078 [planetinfo.record]: inhabitants = "Red Slimy Frogs"; 12:38:30.078 [planetinfo.record]: description = "This planet is fabled for its exciting sit coms."; 12:38:30.096 [planetinfo.record]: air_color = 0.63833, 0.910004, 0.911848, 1; 12:38:30.096 [planetinfo.record]: cloud_alpha = 1.000000; 12:38:30.096 [planetinfo.record]: cloud_color = 0.731342, 0.738281, 0.51622, 1; 12:38:30.096 [planetinfo.record]: cloud_fraction = 0.440000; 12:38:30.096 [planetinfo.record]: land_color = 0.105423, 0.642578, 0.478914, 1; 12:38:30.096 [planetinfo.record]: land_fraction = 0.590000; 12:38:30.096 [planetinfo.record]: polar_cloud_color = 0.827338, 0.832227, 0.675778, 1; 12:38:30.096 [planetinfo.record]: polar_land_color = 0.740187, 0.935742, 0.876159, 1; 12:38:30.096 [planetinfo.record]: polar_sea_color = 0.89208, 0.92793, 0.869843, 1; 12:38:30.096 [planetinfo.record]: sea_color = 0.609327, 0.720703, 0.540246, 1; 12:38:30.096 [planetinfo.record]: rotation_speed = 0.000046 12:38:30.096 [planetinfo.record]: planet zpos = 626100.000000 12:38:30.096 [planetinfo.record]: sun_radius = 160654.034729 12:38:30.096 [planetinfo.record]: sun_vector = -0.078 0.980 -0.181 12:38:30.096 [planetinfo.record]: sun_distance = 1064370 12:38:30.096 [planetinfo.record]: corona_flare = 0.044862 12:38:30.096 [planetinfo.record]: corona_hues = 0.514351 12:38:30.096 [planetinfo.record]: sun_color = 0.68165, 0.623677, 0.599374, 1 12:38:30.097 [planetinfo.record]: corona_shimmer = 0.531784 12:38:30.097 [planetinfo.record]: station_vector = 0.358 0.038 0.933 12:38:30.097 [planetinfo.record]: station = coriolis 12:38:34.554 [PLANETINFO OVER]: Done 12:38:34.555 [PLANETINFO LOGGING]: 2 235 12:38:35.558 [planetinfo.record]: seed = 161 241 149 165 12:38:35.558 [planetinfo.record]: coordinates = 241 179 12:38:35.558 [planetinfo.record]: government = 4; 12:38:35.558 [planetinfo.record]: economy = 3; 12:38:35.558 [planetinfo.record]: techlevel = 7; 12:38:35.558 [planetinfo.record]: population = 36; 12:38:35.558 [planetinfo.record]: productivity = 16128; 12:38:35.558 [planetinfo.record]: name = "Cearle"; 12:38:35.558 [planetinfo.record]: inhabitant = "Fierce Harmless Horned Lobster"; 12:38:35.558 [planetinfo.record]: inhabitants = "Fierce Harmless Horned Lobsters"; 12:38:35.558 [planetinfo.record]: description = "The world Cearle is beset by dreadful earthquakes."; 12:38:35.576 [planetinfo.record]: air_color = 0.577636, 0.530649, 0.890385, 1; 12:38:35.577 [planetinfo.record]: cloud_alpha = 1.000000; 12:38:35.577 [planetinfo.record]: cloud_color = 0.428463, 0.260582, 0.673828, 1; 12:38:35.577 [planetinfo.record]: cloud_fraction = 0.250000; 12:38:35.577 [planetinfo.record]: land_color = 0.223022, 0.509766, 0.429119, 1; 12:38:35.577 [planetinfo.record]: land_fraction = 0.300000; 12:38:35.577 [planetinfo.record]: polar_cloud_color = 0.620421, 0.495347, 0.803223, 1; 12:38:35.577 [planetinfo.record]: polar_land_color = 0.815567, 0.949023, 0.911489, 1; 12:38:35.577 [planetinfo.record]: polar_sea_color = 0.881348, 0.927734, 0.911427, 1; 12:38:35.577 [planetinfo.record]: sea_color = 0.578125, 0.722656, 0.671844, 1; 12:38:35.577 [planetinfo.record]: rotation_speed = 0.003862 12:38:35.577 [planetinfo.record]: planet zpos = 650550.000000 12:38:35.577 [planetinfo.record]: sun_radius = 108723.459930 12:38:35.577 [planetinfo.record]: sun_vector = 0.451 -0.445 -0.774 12:38:35.578 [planetinfo.record]: sun_distance = 824030 12:38:35.578 [planetinfo.record]: corona_flare = 0.014178 12:38:35.578 [planetinfo.record]: corona_hues = 0.856285 12:38:35.578 [planetinfo.record]: sun_color = 0.386572, 0.518849, 0.795584, 1 12:38:35.578 [planetinfo.record]: corona_shimmer = 0.284882 12:38:35.578 [planetinfo.record]: station_vector = 0.194 -0.846 0.497 12:38:35.578 [planetinfo.record]: station = coriolis 12:38:40.937 [PLANETINFO OVER]: Done 12:38:40.938 [PLANETINFO LOGGING]: 2 236 12:38:41.954 [planetinfo.record]: seed = 81 210 13 255 12:38:41.954 [planetinfo.record]: coordinates = 210 225 12:38:41.954 [planetinfo.record]: government = 2; 12:38:41.954 [planetinfo.record]: economy = 1; 12:38:41.954 [planetinfo.record]: techlevel = 9; 12:38:41.954 [planetinfo.record]: population = 40; 12:38:41.954 [planetinfo.record]: productivity = 17280; 12:38:41.954 [planetinfo.record]: name = "Onbezati"; 12:38:41.954 [planetinfo.record]: inhabitant = "Human Colonial"; 12:38:41.954 [planetinfo.record]: inhabitants = "Human Colonials"; 12:38:41.954 [planetinfo.record]: description = "This world is most fabled for its inhabitants’ ingrained silliness but scourged by deadly civil war."; 12:38:41.976 [planetinfo.record]: air_color = 0.701928, 0.837091, 0.891686, 1; 12:38:41.976 [planetinfo.record]: cloud_alpha = 1.000000; 12:38:41.976 [planetinfo.record]: cloud_color = 0.65457, 0.677734, 0.65126, 1; 12:38:41.976 [planetinfo.record]: cloud_fraction = 0.430000; 12:38:41.976 [planetinfo.record]: land_color = 0.639375, 0.643564, 0.66, 1; 12:38:41.976 [planetinfo.record]: land_fraction = 0.410000; 12:38:41.976 [planetinfo.record]: polar_cloud_color = 0.787784, 0.80498, 0.785328, 1; 12:38:41.976 [planetinfo.record]: polar_land_color = 0.926703, 0.928185, 0.934, 1; 12:38:41.976 [planetinfo.record]: polar_sea_color = 0.92207, 0.860136, 0.810413, 1; 12:38:41.976 [planetinfo.record]: sea_color = 0.779297, 0.569918, 0.401825, 1; 12:38:41.976 [planetinfo.record]: rotation_speed = 0.004836 12:38:41.976 [planetinfo.record]: planet zpos = 617940.000000 12:38:41.976 [planetinfo.record]: sun_radius = 190408.503723 12:38:41.976 [planetinfo.record]: sun_vector = 0.595 0.781 0.188 12:38:41.976 [planetinfo.record]: sun_distance = 1304540 12:38:41.976 [planetinfo.record]: corona_flare = 0.081844 12:38:41.976 [planetinfo.record]: corona_hues = 0.922913 12:38:41.976 [planetinfo.record]: sun_color = 0.571173, 0.608418, 0.708249, 1 12:38:41.977 [planetinfo.record]: corona_shimmer = 0.324262 12:38:41.977 [planetinfo.record]: station_vector = 0.378 -0.181 0.908 12:38:41.977 [planetinfo.record]: station = coriolis 12:38:46.981 [PLANETINFO OVER]: Done 12:38:46.982 [PLANETINFO LOGGING]: 2 237 12:38:47.994 [planetinfo.record]: seed = 25 55 37 111 12:38:47.994 [planetinfo.record]: coordinates = 55 132 12:38:47.994 [planetinfo.record]: government = 3; 12:38:47.994 [planetinfo.record]: economy = 4; 12:38:47.994 [planetinfo.record]: techlevel = 8; 12:38:47.994 [planetinfo.record]: population = 40; 12:38:47.994 [planetinfo.record]: productivity = 13440; 12:38:47.994 [planetinfo.record]: name = "Aarat"; 12:38:47.994 [planetinfo.record]: inhabitant = "Human Colonial"; 12:38:47.994 [planetinfo.record]: inhabitants = "Human Colonials"; 12:38:47.994 [planetinfo.record]: description = "This planet is a dull world."; 12:38:48.015 [planetinfo.record]: air_color = 0.65318, 0.871523, 0.833801, 1; 12:38:48.015 [planetinfo.record]: cloud_alpha = 1.000000; 12:38:48.015 [planetinfo.record]: cloud_color = 0.617188, 0.569441, 0.523163, 1; 12:38:48.015 [planetinfo.record]: cloud_fraction = 0.460000; 12:38:48.016 [planetinfo.record]: land_color = 0.623906, 0.66, 0.644773, 1; 12:38:48.016 [planetinfo.record]: land_fraction = 0.460000; 12:38:48.016 [planetinfo.record]: polar_cloud_color = 0.777734, 0.74013, 0.703683, 1; 12:38:48.016 [planetinfo.record]: polar_land_color = 0.92123, 0.934, 0.928613, 1; 12:38:48.016 [planetinfo.record]: polar_sea_color = 0.880897, 0.948828, 0.759804, 1; 12:38:48.016 [planetinfo.record]: sea_color = 0.365174, 0.511719, 0.103943, 1; 12:38:48.016 [planetinfo.record]: rotation_speed = 0.000371 12:38:48.016 [planetinfo.record]: planet zpos = 1006650.000000 12:38:48.016 [planetinfo.record]: sun_radius = 231093.063812 12:38:48.016 [planetinfo.record]: sun_vector = -0.776 -0.389 -0.497 12:38:48.016 [planetinfo.record]: sun_distance = 1476420 12:38:48.016 [planetinfo.record]: corona_flare = 0.066640 12:38:48.016 [planetinfo.record]: corona_hues = 0.741531 12:38:48.016 [planetinfo.record]: sun_color = 0.699689, 0.637481, 0.636583, 1 12:38:48.017 [planetinfo.record]: corona_shimmer = 0.460911 12:38:48.017 [planetinfo.record]: station_vector = -0.682 -0.562 0.467 12:38:48.017 [planetinfo.record]: station = coriolis 12:38:53.024 [PLANETINFO OVER]: Done 12:38:53.025 [PLANETINFO LOGGING]: 2 238 12:38:54.038 [planetinfo.record]: seed = 73 107 109 103 12:38:54.038 [planetinfo.record]: coordinates = 107 209 12:38:54.038 [planetinfo.record]: government = 1; 12:38:54.038 [planetinfo.record]: economy = 3; 12:38:54.038 [planetinfo.record]: techlevel = 8; 12:38:54.038 [planetinfo.record]: population = 37; 12:38:54.038 [planetinfo.record]: productivity = 10360; 12:38:54.038 [planetinfo.record]: name = "Sogeve"; 12:38:54.038 [planetinfo.record]: inhabitant = "Human Colonial"; 12:38:54.038 [planetinfo.record]: inhabitants = "Human Colonials"; 12:38:54.038 [planetinfo.record]: description = "This world is very notable for the Sogeveian tree snake and its hoopy casinos."; 12:38:54.052 [planetinfo.record]: air_color = 0.665093, 0.585778, 0.862418, 1; 12:38:54.052 [planetinfo.record]: cloud_alpha = 1.000000; 12:38:54.052 [planetinfo.record]: cloud_color = 0.563059, 0.375565, 0.589844, 1; 12:38:54.052 [planetinfo.record]: cloud_fraction = 0.260000; 12:38:54.052 [planetinfo.record]: land_color = 0.551719, 0.66, 0.53625, 1; 12:38:54.052 [planetinfo.record]: land_fraction = 0.420000; 12:38:54.052 [planetinfo.record]: polar_cloud_color = 0.743706, 0.591638, 0.76543, 1; 12:38:54.052 [planetinfo.record]: polar_land_color = 0.895691, 0.934, 0.890219, 1; 12:38:54.052 [planetinfo.record]: polar_sea_color = 0.946094, 0.838775, 0.713266, 1; 12:38:54.052 [planetinfo.record]: sea_color = 0.539062, 0.294471, 0.00842285, 1; 12:38:54.052 [planetinfo.record]: rotation_speed = 0.000688 12:38:54.052 [planetinfo.record]: planet zpos = 424350.000000 12:38:54.052 [planetinfo.record]: sun_radius = 105101.850891 12:38:54.052 [planetinfo.record]: sun_vector = -0.670 -0.725 -0.161 12:38:54.052 [planetinfo.record]: sun_distance = 1037300 12:38:54.052 [planetinfo.record]: corona_flare = 0.017987 12:38:54.052 [planetinfo.record]: corona_hues = 0.973373 12:38:54.052 [planetinfo.record]: sun_color = 0.301728, 0.646571, 0.833075, 1 12:38:54.053 [planetinfo.record]: corona_shimmer = 1.196000 12:38:54.053 [planetinfo.record]: station_vector = 0.778 -0.537 0.326 12:38:54.053 [planetinfo.record]: station = coriolis 12:38:58.800 [PLANETINFO OVER]: Done 12:38:58.801 [PLANETINFO LOGGING]: 2 239 12:38:59.805 [planetinfo.record]: seed = 241 129 117 156 12:38:59.805 [planetinfo.record]: coordinates = 129 118 12:38:59.805 [planetinfo.record]: government = 6; 12:38:59.805 [planetinfo.record]: economy = 6; 12:38:59.805 [planetinfo.record]: techlevel = 5; 12:38:59.805 [planetinfo.record]: population = 33; 12:38:59.805 [planetinfo.record]: productivity = 10560; 12:38:59.805 [planetinfo.record]: name = "Telabe"; 12:38:59.805 [planetinfo.record]: inhabitant = "Human Colonial"; 12:38:59.805 [planetinfo.record]: inhabitants = "Human Colonials"; 12:38:59.805 [planetinfo.record]: description = "Telabe is reasonably notable for its great tropical forests but cursed by unpredictable earthquakes."; 12:38:59.816 [planetinfo.record]: air_color = 0.599691, 0.900141, 0.79899, 1; 12:38:59.816 [planetinfo.record]: cloud_alpha = 1.000000; 12:38:59.816 [planetinfo.record]: cloud_color = 0.703125, 0.420227, 0.420227, 1; 12:38:59.816 [planetinfo.record]: cloud_fraction = 0.450000; 12:38:59.816 [planetinfo.record]: land_color = 0.32872, 0.335563, 0.503906, 1; 12:38:59.816 [planetinfo.record]: land_fraction = 0.540000; 12:38:59.816 [planetinfo.record]: polar_cloud_color = 0.816406, 0.611109, 0.611109, 1; 12:38:59.816 [planetinfo.record]: polar_land_color = 0.867075, 0.870299, 0.949609, 1; 12:38:59.816 [planetinfo.record]: polar_sea_color = 0.8298, 0.913672, 0.842905, 1; 12:38:59.816 [planetinfo.record]: sea_color = 0.546295, 0.863281, 0.595824, 1; 12:38:59.816 [planetinfo.record]: rotation_speed = 0.003215 12:38:59.816 [planetinfo.record]: planet zpos = 722040.000000 12:38:59.816 [planetinfo.record]: sun_radius = 166740.934296 12:38:59.816 [planetinfo.record]: sun_vector = 0.256 -0.105 -0.961 12:38:59.816 [planetinfo.record]: sun_distance = 1203400 12:38:59.816 [planetinfo.record]: corona_flare = 0.085715 12:38:59.816 [planetinfo.record]: corona_hues = 0.974678 12:38:59.816 [planetinfo.record]: sun_color = 0.300767, 0.512659, 0.714688, 1 12:38:59.817 [planetinfo.record]: corona_shimmer = 0.634264 12:38:59.817 [planetinfo.record]: station_vector = 0.903 -0.428 0.025 12:38:59.817 [planetinfo.record]: station = coriolis 12:39:04.642 [PLANETINFO OVER]: Done 12:39:04.643 [PLANETINFO LOGGING]: 2 240 12:39:05.653 [planetinfo.record]: seed = 225 228 77 45 12:39:05.653 [planetinfo.record]: coordinates = 228 179 12:39:05.653 [planetinfo.record]: government = 4; 12:39:05.653 [planetinfo.record]: economy = 3; 12:39:05.653 [planetinfo.record]: techlevel = 6; 12:39:05.653 [planetinfo.record]: population = 32; 12:39:05.653 [planetinfo.record]: productivity = 14336; 12:39:05.653 [planetinfo.record]: name = "Dicetiar"; 12:39:05.653 [planetinfo.record]: inhabitant = "Human Colonial"; 12:39:05.653 [planetinfo.record]: inhabitants = "Human Colonials"; 12:39:05.653 [planetinfo.record]: description = "Dicetiar is an unremarkable dump."; 12:39:05.672 [planetinfo.record]: air_color = 0.695117, 0.85022, 0.878678, 1; 12:39:05.672 [planetinfo.record]: cloud_alpha = 1.000000; 12:39:05.672 [planetinfo.record]: cloud_color = 0.630349, 0.638672, 0.621208, 1; 12:39:05.672 [planetinfo.record]: cloud_fraction = 0.470000; 12:39:05.672 [planetinfo.record]: land_color = 0.66, 0.39403, 0.301641, 1; 12:39:05.672 [planetinfo.record]: land_fraction = 0.420000; 12:39:05.672 [planetinfo.record]: polar_cloud_color = 0.780989, 0.787402, 0.773946, 1; 12:39:05.672 [planetinfo.record]: polar_land_color = 0.934, 0.839903, 0.807217, 1; 12:39:05.672 [planetinfo.record]: polar_sea_color = 0.846325, 0.900987, 0.949219, 1; 12:39:05.672 [planetinfo.record]: sea_color = 0.287628, 0.404601, 0.507812, 1; 12:39:05.672 [planetinfo.record]: rotation_speed = 0.002867 12:39:05.672 [planetinfo.record]: planet zpos = 637200.000000 12:39:05.672 [planetinfo.record]: sun_radius = 181803.652954 12:39:05.672 [planetinfo.record]: sun_vector = -0.225 0.910 0.349 12:39:05.672 [planetinfo.record]: sun_distance = 1210680 12:39:05.672 [planetinfo.record]: corona_flare = 0.017189 12:39:05.672 [planetinfo.record]: corona_hues = 0.505951 12:39:05.673 [planetinfo.record]: sun_color = 0.241935, 0.261446, 0.689532, 1 12:39:05.673 [planetinfo.record]: corona_shimmer = 0.280743 12:39:05.673 [planetinfo.record]: station_vector = -0.892 0.135 0.431 12:39:05.673 [planetinfo.record]: station = coriolis 12:39:11.134 [PLANETINFO OVER]: Done 12:39:11.135 [PLANETINFO LOGGING]: 2 241 12:39:12.144 [planetinfo.record]: seed = 169 202 5 104 12:39:12.144 [planetinfo.record]: coordinates = 202 215 12:39:12.144 [planetinfo.record]: government = 5; 12:39:12.144 [planetinfo.record]: economy = 7; 12:39:12.144 [planetinfo.record]: techlevel = 5; 12:39:12.144 [planetinfo.record]: population = 33; 12:39:12.144 [planetinfo.record]: productivity = 7128; 12:39:12.144 [planetinfo.record]: name = "Usarisa"; 12:39:12.144 [planetinfo.record]: inhabitant = "Human Colonial"; 12:39:12.144 [planetinfo.record]: inhabitants = "Human Colonials"; 12:39:12.145 [planetinfo.record]: description = "Usarisa is an unremarkable planet."; 12:39:12.168 [planetinfo.record]: air_color = 0.744422, 0.757403, 0.945668, 1; 12:39:12.168 [planetinfo.record]: cloud_alpha = 1.000000; 12:39:12.168 [planetinfo.record]: cloud_color = 0.807037, 0.812932, 0.839844, 1; 12:39:12.168 [planetinfo.record]: cloud_fraction = 0.470000; 12:39:12.168 [planetinfo.record]: land_color = 0.66, 0.128906, 0.564569, 1; 12:39:12.168 [planetinfo.record]: land_fraction = 0.260000; 12:39:12.168 [planetinfo.record]: polar_cloud_color = 0.856496, 0.860347, 0.87793, 1; 12:39:12.168 [planetinfo.record]: polar_land_color = 0.934, 0.746105, 0.900238, 1; 12:39:12.168 [planetinfo.record]: polar_sea_color = 0.940625, 0.882317, 0.880917, 1; 12:39:12.168 [planetinfo.record]: sea_color = 0.59375, 0.446527, 0.442993, 1; 12:39:12.168 [planetinfo.record]: rotation_speed = 0.002265 12:39:12.168 [planetinfo.record]: planet zpos = 658580.000000 12:39:12.168 [planetinfo.record]: sun_radius = 116269.245300 12:39:12.168 [planetinfo.record]: sun_vector = 0.639 -0.131 0.758 12:39:12.168 [planetinfo.record]: sun_distance = 911880 12:39:12.168 [planetinfo.record]: corona_flare = 0.026199 12:39:12.168 [planetinfo.record]: corona_hues = 0.818405 12:39:12.168 [planetinfo.record]: sun_color = 0.438193, 0.646185, 0.822937, 1 12:39:12.169 [planetinfo.record]: corona_shimmer = 0.265160 12:39:12.169 [planetinfo.record]: station_vector = -0.702 0.466 0.538 12:39:12.169 [planetinfo.record]: station = coriolis 12:39:18.044 [PLANETINFO OVER]: Done 12:39:18.044 [PLANETINFO LOGGING]: 2 242 12:39:19.054 [planetinfo.record]: seed = 153 175 45 247 12:39:19.054 [planetinfo.record]: coordinates = 175 61 12:39:19.054 [planetinfo.record]: government = 3; 12:39:19.054 [planetinfo.record]: economy = 5; 12:39:19.054 [planetinfo.record]: techlevel = 7; 12:39:19.054 [planetinfo.record]: population = 37; 12:39:19.054 [planetinfo.record]: productivity = 10360; 12:39:19.054 [planetinfo.record]: name = "Tigear"; 12:39:19.054 [planetinfo.record]: inhabitant = "Human Colonial"; 12:39:19.054 [planetinfo.record]: inhabitants = "Human Colonials"; 12:39:19.054 [planetinfo.record]: description = "This planet is notable for the Tigearian tree grub and its inhabitants’ ancient mating traditions."; 12:39:19.079 [planetinfo.record]: air_color = 0.714002, 0.546929, 0.917701, 1; 12:39:19.079 [planetinfo.record]: cloud_alpha = 1.000000; 12:39:19.079 [planetinfo.record]: cloud_color = 0.755859, 0.292305, 0.585648, 1; 12:39:19.079 [planetinfo.record]: cloud_fraction = 0.420000; 12:39:19.079 [planetinfo.record]: land_color = 0.479531, 0.66, 0.638851, 1; 12:39:19.079 [planetinfo.record]: land_fraction = 0.460000; 12:39:19.079 [planetinfo.record]: polar_cloud_color = 0.840137, 0.518112, 0.721893, 1; 12:39:19.079 [planetinfo.record]: polar_land_color = 0.870152, 0.934, 0.926518, 1; 12:39:19.079 [planetinfo.record]: polar_sea_color = 0.945898, 0.894834, 0.720509, 1; 12:39:19.079 [planetinfo.record]: sea_color = 0.541016, 0.424187, 0.0253601, 1; 12:39:19.079 [planetinfo.record]: rotation_speed = 0.001389 12:39:19.079 [planetinfo.record]: planet zpos = 526130.000000 12:39:19.080 [planetinfo.record]: sun_radius = 108556.788483 12:39:19.080 [planetinfo.record]: sun_vector = 0.477 -0.293 0.829 12:39:19.080 [planetinfo.record]: sun_distance = 813110 12:39:19.080 [planetinfo.record]: corona_flare = 0.058913 12:39:19.080 [planetinfo.record]: corona_hues = 0.927673 12:39:19.080 [planetinfo.record]: sun_color = 0.719141, 0.707445, 0.636331, 1 12:39:19.080 [planetinfo.record]: corona_shimmer = 0.603051 12:39:19.080 [planetinfo.record]: station_vector = 0.805 0.514 0.294 12:39:19.080 [planetinfo.record]: station = coriolis 12:39:24.509 [PLANETINFO OVER]: Done 12:39:24.510 [PLANETINFO LOGGING]: 2 243 12:39:25.516 [planetinfo.record]: seed = 193 101 85 212 12:39:25.516 [planetinfo.record]: coordinates = 101 138 12:39:25.516 [planetinfo.record]: government = 0; 12:39:25.516 [planetinfo.record]: economy = 2; 12:39:25.516 [planetinfo.record]: techlevel = 6; 12:39:25.516 [planetinfo.record]: population = 27; 12:39:25.516 [planetinfo.record]: productivity = 6912; 12:39:25.516 [planetinfo.record]: name = "Razari"; 12:39:25.516 [planetinfo.record]: inhabitant = "Human Colonial"; 12:39:25.516 [planetinfo.record]: inhabitants = "Human Colonials"; 12:39:25.516 [planetinfo.record]: description = "The planet Razari is well known for the Razariian tree wolf but beset by deadly solar activity."; 12:39:25.527 [planetinfo.record]: air_color = 0.678961, 0.834093, 0.923555, 1; 12:39:25.527 [planetinfo.record]: cloud_alpha = 1.000000; 12:39:25.527 [planetinfo.record]: cloud_color = 0.622375, 0.773438, 0.638898, 1; 12:39:25.527 [planetinfo.record]: cloud_fraction = 0.220000; 12:39:25.527 [planetinfo.record]: land_color = 0.103125, 0.111826, 0.66, 1; 12:39:25.527 [planetinfo.record]: land_fraction = 0.330000; 12:39:25.527 [planetinfo.record]: polar_cloud_color = 0.744526, 0.848047, 0.755848, 1; 12:39:25.527 [planetinfo.record]: polar_land_color = 0.736984, 0.740063, 0.934, 1; 12:39:25.527 [planetinfo.record]: polar_sea_color = 0.934961, 0.905153, 0.873421, 1; 12:39:25.527 [planetinfo.record]: sea_color = 0.650391, 0.567448, 0.479155, 1; 12:39:25.528 [planetinfo.record]: rotation_speed = 0.002612 12:39:25.528 [planetinfo.record]: planet zpos = 512330.000000 12:39:25.528 [planetinfo.record]: sun_radius = 95972.875366 12:39:25.528 [planetinfo.record]: sun_vector = 0.804 -0.555 0.212 12:39:25.528 [planetinfo.record]: sun_distance = 945840 12:39:25.528 [planetinfo.record]: corona_flare = 0.083725 12:39:25.528 [planetinfo.record]: corona_hues = 0.672165 12:39:25.528 [planetinfo.record]: sun_color = 0.801132, 0.682868, 0.669082, 1 12:39:25.528 [planetinfo.record]: corona_shimmer = 0.499764 12:39:25.529 [planetinfo.record]: station_vector = 0.152 -0.716 0.681 12:39:25.529 [planetinfo.record]: station = coriolis 12:39:30.849 [PLANETINFO OVER]: Done 12:39:30.849 [PLANETINFO LOGGING]: 2 244 12:39:31.861 [planetinfo.record]: seed = 241 151 141 91 12:39:31.861 [planetinfo.record]: coordinates = 151 254 12:39:31.861 [planetinfo.record]: government = 6; 12:39:31.861 [planetinfo.record]: economy = 6; 12:39:31.861 [planetinfo.record]: techlevel = 7; 12:39:31.861 [planetinfo.record]: population = 41; 12:39:31.861 [planetinfo.record]: productivity = 13120; 12:39:31.861 [planetinfo.record]: name = "Anencebe"; 12:39:31.861 [planetinfo.record]: inhabitant = "Yellow Bug-Eyed Bird"; 12:39:31.861 [planetinfo.record]: inhabitants = "Yellow Bug-Eyed Birds"; 12:39:31.861 [planetinfo.record]: description = "This world is ravaged by unpredictable civil war."; 12:39:31.877 [planetinfo.record]: air_color = 0.474444, 0.672739, 0.857865, 1; 12:39:31.877 [planetinfo.record]: cloud_alpha = 1.000000; 12:39:31.877 [planetinfo.record]: cloud_color = 0.148544, 0.576172, 0.345654, 1; 12:39:31.877 [planetinfo.record]: cloud_fraction = 0.420000; 12:39:31.877 [planetinfo.record]: land_color = 0.31864, 0.0850781, 0.66, 1; 12:39:31.877 [planetinfo.record]: land_fraction = 0.270000; 12:39:31.877 [planetinfo.record]: polar_cloud_color = 0.407073, 0.759277, 0.569417, 1; 12:39:31.877 [planetinfo.record]: polar_land_color = 0.813231, 0.7306, 0.934, 1; 12:39:31.877 [planetinfo.record]: polar_sea_color = 0.941211, 0.901044, 0.885327, 1; 12:39:31.877 [planetinfo.record]: sea_color = 0.587891, 0.487536, 0.448267, 1; 12:39:31.878 [planetinfo.record]: rotation_speed = 0.001002 12:39:31.878 [planetinfo.record]: planet zpos = 751790.000000 12:39:31.878 [planetinfo.record]: sun_radius = 191125.079193 12:39:31.878 [planetinfo.record]: sun_vector = -0.215 -0.711 0.669 12:39:31.878 [planetinfo.record]: sun_distance = 1272260 12:39:31.878 [planetinfo.record]: corona_flare = 0.039546 12:39:31.878 [planetinfo.record]: corona_hues = 0.594986 12:39:31.878 [planetinfo.record]: sun_color = 0.353137, 0.411576, 0.753464, 1 12:39:31.878 [planetinfo.record]: corona_shimmer = 0.675796 12:39:31.878 [planetinfo.record]: station_vector = 0.873 0.488 0.000 12:39:31.878 [planetinfo.record]: station = coriolis 12:39:36.270 [PLANETINFO OVER]: Done 12:39:36.272 [PLANETINFO LOGGING]: 2 245 12:39:37.282 [planetinfo.record]: seed = 185 51 229 11 12:39:37.282 [planetinfo.record]: coordinates = 51 229 12:39:37.282 [planetinfo.record]: government = 7; 12:39:37.282 [planetinfo.record]: economy = 5; 12:39:37.282 [planetinfo.record]: techlevel = 9; 12:39:37.282 [planetinfo.record]: population = 49; 12:39:37.282 [planetinfo.record]: productivity = 21560; 12:39:37.282 [planetinfo.record]: name = "Maceceve"; 12:39:37.282 [planetinfo.record]: inhabitant = "Small Green Frog"; 12:39:37.282 [planetinfo.record]: inhabitants = "Small Green Frogs"; 12:39:37.282 [planetinfo.record]: description = "The planet Maceceve is cursed by deadly civil war."; 12:39:37.287 [planetinfo.record]: air_color = 0.456026, 0.666708, 0.876076, 1; 12:39:37.287 [planetinfo.record]: cloud_alpha = 1.000000; 12:39:37.287 [planetinfo.record]: cloud_color = 0.101036, 0.630859, 0.370087, 1; 12:39:37.287 [planetinfo.record]: cloud_fraction = 0.380000; 12:39:37.287 [planetinfo.record]: land_color = 0.435985, 0.232031, 0.66, 1; 12:39:37.287 [planetinfo.record]: land_fraction = 0.390000; 12:39:37.288 [planetinfo.record]: polar_cloud_color = 0.372423, 0.783887, 0.581369, 1; 12:39:37.288 [planetinfo.record]: polar_land_color = 0.854746, 0.78259, 0.934, 1; 12:39:37.288 [planetinfo.record]: polar_sea_color = 0.942187, 0.934654, 0.893974, 1; 12:39:37.288 [planetinfo.record]: sea_color = 0.578125, 0.559635, 0.45979, 1; 12:39:37.288 [planetinfo.record]: rotation_speed = 0.004075 12:39:37.288 [planetinfo.record]: planet zpos = 681960.000000 12:39:37.288 [planetinfo.record]: sun_radius = 164454.587402 12:39:37.288 [planetinfo.record]: sun_vector = 0.088 -0.962 -0.258 12:39:37.288 [planetinfo.record]: sun_distance = 1136600 12:39:37.288 [planetinfo.record]: corona_flare = 0.015668 12:39:37.288 [planetinfo.record]: corona_hues = 0.861160 12:39:37.288 [planetinfo.record]: sun_color = 0.582387, 0.753586, 0.782843, 1 12:39:37.288 [planetinfo.record]: corona_shimmer = 0.556692 12:39:37.288 [planetinfo.record]: station_vector = -0.225 0.974 0.000 12:39:37.288 [planetinfo.record]: station = coriolis 12:39:41.963 [PLANETINFO OVER]: Done 12:39:41.964 [PLANETINFO LOGGING]: 2 246 12:39:42.972 [planetinfo.record]: seed = 105 150 237 32 12:39:42.972 [planetinfo.record]: coordinates = 150 101 12:39:42.972 [planetinfo.record]: government = 5; 12:39:42.972 [planetinfo.record]: economy = 5; 12:39:42.972 [planetinfo.record]: techlevel = 7; 12:39:42.972 [planetinfo.record]: population = 39; 12:39:42.972 [planetinfo.record]: productivity = 14040; 12:39:42.972 [planetinfo.record]: name = "Tebe"; 12:39:42.972 [planetinfo.record]: inhabitant = "Large Red Bony Lobster"; 12:39:42.972 [planetinfo.record]: inhabitants = "Large Red Bony Lobsters"; 12:39:42.972 [planetinfo.record]: description = "This planet is very notable for the Tebeian tree snake and its exotic goat meat."; 12:39:42.988 [planetinfo.record]: air_color = 0.5398, 0.485708, 0.926156, 1; 12:39:42.988 [planetinfo.record]: cloud_alpha = 1.000000; 12:39:42.988 [planetinfo.record]: cloud_color = 0.381947, 0.134277, 0.78125, 1; 12:39:42.988 [planetinfo.record]: cloud_fraction = 0.230000; 12:39:42.988 [planetinfo.record]: land_color = 0.458906, 0.622295, 0.66, 1; 12:39:42.988 [planetinfo.record]: land_fraction = 0.630000; 12:39:42.988 [planetinfo.record]: polar_cloud_color = 0.579537, 0.410812, 0.851562, 1; 12:39:42.988 [planetinfo.record]: polar_land_color = 0.862855, 0.92066, 0.934, 1; 12:39:42.988 [planetinfo.record]: polar_sea_color = 0.937109, 0.915973, 0.879547, 1; 12:39:42.988 [planetinfo.record]: sea_color = 0.628906, 0.572167, 0.474382, 1; 12:39:42.988 [planetinfo.record]: rotation_speed = 0.001912 12:39:42.988 [planetinfo.record]: planet zpos = 355920.000000 12:39:42.988 [planetinfo.record]: sun_radius = 92803.811951 12:39:42.988 [planetinfo.record]: sun_vector = -0.112 -0.946 0.303 12:39:42.988 [planetinfo.record]: sun_distance = 622860 12:39:42.988 [planetinfo.record]: corona_flare = 0.011993 12:39:42.988 [planetinfo.record]: corona_hues = 0.874481 12:39:42.988 [planetinfo.record]: sun_color = 0.661755, 0.537571, 0.446291, 1 12:39:42.988 [planetinfo.record]: corona_shimmer = 0.563896 12:39:42.988 [planetinfo.record]: station_vector = -0.642 0.094 0.761 12:39:42.988 [planetinfo.record]: station = coriolis 12:39:47.998 [PLANETINFO OVER]: Done 12:39:47.998 [PLANETINFO LOGGING]: 2 247 12:39:49.008 [planetinfo.record]: seed = 17 17 53 1 12:39:49.008 [planetinfo.record]: coordinates = 17 211 12:39:49.008 [planetinfo.record]: government = 2; 12:39:49.008 [planetinfo.record]: economy = 3; 12:39:49.008 [planetinfo.record]: techlevel = 6; 12:39:49.008 [planetinfo.record]: population = 30; 12:39:49.008 [planetinfo.record]: productivity = 10080; 12:39:49.008 [planetinfo.record]: name = "Lebied"; 12:39:49.008 [planetinfo.record]: inhabitant = "Human Colonial"; 12:39:49.008 [planetinfo.record]: inhabitants = "Human Colonials"; 12:39:49.008 [planetinfo.record]: description = "Lebied is mildly notable for its inhabitants’ unusual mating traditions."; 12:39:49.032 [planetinfo.record]: air_color = 0.57402, 0.546932, 0.870223, 1; 12:39:49.032 [planetinfo.record]: cloud_alpha = 1.000000; 12:39:49.032 [planetinfo.record]: cloud_color = 0.381055, 0.297058, 0.613281, 1; 12:39:49.032 [planetinfo.record]: cloud_fraction = 0.200000; 12:39:49.032 [planetinfo.record]: land_color = 0.66, 0.260391, 0.606927, 1; 12:39:49.032 [planetinfo.record]: land_fraction = 0.420000; 12:39:49.032 [planetinfo.record]: polar_cloud_color = 0.592331, 0.525906, 0.775977, 1; 12:39:49.032 [planetinfo.record]: polar_land_color = 0.934, 0.792623, 0.915223, 1; 12:39:49.032 [planetinfo.record]: polar_sea_color = 0.942773, 0.892044, 0.886796, 1; 12:39:49.032 [planetinfo.record]: sea_color = 0.572266, 0.449094, 0.436353, 1; 12:39:49.032 [planetinfo.record]: rotation_speed = 0.001878 12:39:49.032 [planetinfo.record]: planet zpos = 401570.000000 12:39:49.032 [planetinfo.record]: sun_radius = 56499.533234 12:39:49.032 [planetinfo.record]: sun_vector = -0.465 -0.842 0.276 12:39:49.032 [planetinfo.record]: sun_distance = 679580 12:39:49.032 [planetinfo.record]: corona_flare = 0.019766 12:39:49.032 [planetinfo.record]: corona_hues = 0.856247 12:39:49.032 [planetinfo.record]: sun_color = 0.49304, 0.503364, 0.682318, 1 12:39:49.032 [planetinfo.record]: corona_shimmer = 0.472168 12:39:49.032 [planetinfo.record]: station_vector = 0.271 0.822 0.501 12:39:49.032 [planetinfo.record]: station = coriolis 12:39:53.608 [PLANETINFO OVER]: Done 12:39:53.609 [PLANETINFO LOGGING]: 2 248 12:39:54.611 [planetinfo.record]: seed = 129 223 205 189 12:39:54.611 [planetinfo.record]: coordinates = 223 248 12:39:54.611 [planetinfo.record]: government = 0; 12:39:54.611 [planetinfo.record]: economy = 2; 12:39:54.611 [planetinfo.record]: techlevel = 8; 12:39:54.611 [planetinfo.record]: population = 35; 12:39:54.611 [planetinfo.record]: productivity = 8960; 12:39:54.611 [planetinfo.record]: name = "Islaenbi"; 12:39:54.611 [planetinfo.record]: inhabitant = "Harmless Rodent"; 12:39:54.611 [planetinfo.record]: inhabitants = "Harmless Rodents"; 12:39:54.611 [planetinfo.record]: description = "This planet is beset by lethal disease."; 12:39:54.624 [planetinfo.record]: air_color = 0.568126, 0.945018, 0.87402, 1; 12:39:54.624 [planetinfo.record]: cloud_alpha = 1.000000; 12:39:54.624 [planetinfo.record]: cloud_color = 0.837891, 0.560119, 0.33712, 1; 12:39:54.624 [planetinfo.record]: cloud_fraction = 0.420000; 12:39:54.624 [planetinfo.record]: land_color = 0.415239, 0.66, 0.407344, 1; 12:39:54.624 [planetinfo.record]: land_fraction = 0.360000; 12:39:54.624 [planetinfo.record]: polar_cloud_color = 0.877051, 0.69533, 0.549441, 1; 12:39:54.624 [planetinfo.record]: polar_land_color = 0.847407, 0.934, 0.844613, 1; 12:39:54.624 [planetinfo.record]: polar_sea_color = 0.914853, 0.930469, 0.871678, 1; 12:39:54.624 [planetinfo.record]: sea_color = 0.648634, 0.695312, 0.519583, 1; 12:39:54.624 [planetinfo.record]: rotation_speed = 0.004183 12:39:54.624 [planetinfo.record]: planet zpos = 764040.000000 12:39:54.624 [planetinfo.record]: sun_radius = 198941.547241 12:39:54.624 [planetinfo.record]: sun_vector = -0.540 0.380 -0.751 12:39:54.624 [planetinfo.record]: sun_distance = 1337070 12:39:54.624 [planetinfo.record]: corona_flare = 0.001445 12:39:54.624 [planetinfo.record]: corona_hues = 0.533600 12:39:54.624 [planetinfo.record]: sun_color = 0.546661, 0.591986, 0.65643, 1 12:39:54.624 [planetinfo.record]: corona_shimmer = 0.360468 12:39:54.624 [planetinfo.record]: station_vector = 0.305 -0.157 0.939 12:39:54.624 [planetinfo.record]: station = coriolis 12:39:59.380 [PLANETINFO OVER]: Done 12:39:59.381 [PLANETINFO LOGGING]: 2 249 12:40:00.407 [planetinfo.record]: seed = 73 134 197 78 12:40:00.407 [planetinfo.record]: coordinates = 134 50 12:40:00.407 [planetinfo.record]: government = 1; 12:40:00.407 [planetinfo.record]: economy = 2; 12:40:00.407 [planetinfo.record]: techlevel = 8; 12:40:00.407 [planetinfo.record]: population = 36; 12:40:00.407 [planetinfo.record]: productivity = 11520; 12:40:00.407 [planetinfo.record]: name = "Resoisbe"; 12:40:00.407 [planetinfo.record]: inhabitant = "Yellow Fat Feline"; 12:40:00.407 [planetinfo.record]: inhabitants = "Yellow Fat Felines"; 12:40:00.407 [planetinfo.record]: description = "This world is very well known for Resoisbeian shrew cutlet and its inhabitants’ weird shyness."; 12:40:00.420 [planetinfo.record]: air_color = 0.566562, 0.942416, 0.862813, 1; 12:40:00.420 [planetinfo.record]: cloud_alpha = 1.000000; 12:40:00.420 [planetinfo.record]: cloud_color = 0.830078, 0.520015, 0.333977, 1; 12:40:00.420 [planetinfo.record]: cloud_fraction = 0.290000; 12:40:00.420 [planetinfo.record]: land_color = 0.536676, 0.552734, 0.0388641, 1; 12:40:00.420 [planetinfo.record]: land_fraction = 0.450000; 12:40:00.420 [planetinfo.record]: polar_cloud_color = 0.873535, 0.6696, 0.547239, 1; 12:40:00.420 [planetinfo.record]: polar_land_color = 0.937865, 0.944727, 0.725151, 1; 12:40:00.420 [planetinfo.record]: polar_sea_color = 0.94082, 0.895655, 0.883581, 1; 12:40:00.420 [planetinfo.record]: sea_color = 0.591797, 0.478157, 0.447778, 1; 12:40:00.420 [planetinfo.record]: rotation_speed = 0.000993 12:40:00.420 [planetinfo.record]: planet zpos = 784080.000000 12:40:00.420 [planetinfo.record]: sun_radius = 178395.867004 12:40:00.420 [planetinfo.record]: sun_vector = -0.913 -0.037 0.406 12:40:00.420 [planetinfo.record]: sun_distance = 1241460 12:40:00.420 [planetinfo.record]: corona_flare = 0.064098 12:40:00.420 [planetinfo.record]: corona_hues = 0.764809 12:40:00.420 [planetinfo.record]: sun_color = 0.635528, 0.64617, 0.680319, 1 12:40:00.420 [planetinfo.record]: corona_shimmer = 1.026888 12:40:00.421 [planetinfo.record]: station_vector = -0.955 0.175 0.241 12:40:00.421 [planetinfo.record]: station = coriolis 12:40:04.850 [PLANETINFO OVER]: Done 12:40:04.851 [PLANETINFO LOGGING]: 2 250 12:40:05.858 [planetinfo.record]: seed = 185 51 173 24 12:40:05.858 [planetinfo.record]: coordinates = 51 221 12:40:05.858 [planetinfo.record]: government = 7; 12:40:05.858 [planetinfo.record]: economy = 5; 12:40:05.858 [planetinfo.record]: techlevel = 9; 12:40:05.858 [planetinfo.record]: population = 49; 12:40:05.858 [planetinfo.record]: productivity = 21560; 12:40:05.858 [planetinfo.record]: name = "Edesla"; 12:40:05.859 [planetinfo.record]: inhabitant = "Green Feline"; 12:40:05.859 [planetinfo.record]: inhabitants = "Green Felines"; 12:40:05.859 [planetinfo.record]: description = "The planet Edesla is cursed by killer mountain Leoids."; 12:40:05.872 [planetinfo.record]: air_color = 0.587596, 0.563118, 0.855264, 1; 12:40:05.872 [planetinfo.record]: cloud_alpha = 1.000000; 12:40:05.872 [planetinfo.record]: cloud_color = 0.390643, 0.326363, 0.568359, 1; 12:40:05.872 [planetinfo.record]: cloud_fraction = 0.110000; 12:40:05.872 [planetinfo.record]: land_color = 0.0670312, 0.17358, 0.66, 1; 12:40:05.872 [planetinfo.record]: land_fraction = 0.520000; 12:40:05.872 [planetinfo.record]: polar_cloud_color = 0.608066, 0.554644, 0.755762, 1; 12:40:05.872 [planetinfo.record]: polar_land_color = 0.724215, 0.761911, 0.934, 1; 12:40:05.872 [planetinfo.record]: polar_sea_color = 0.938477, 0.903302, 0.880005, 1; 12:40:05.872 [planetinfo.record]: sea_color = 0.615234, 0.522998, 0.461906, 1; 12:40:05.872 [planetinfo.record]: rotation_speed = 0.002518 12:40:05.872 [planetinfo.record]: planet zpos = 638950.000000 12:40:05.872 [planetinfo.record]: sun_radius = 117232.979584 12:40:05.872 [planetinfo.record]: sun_vector = -0.731 -0.576 -0.365 12:40:05.872 [planetinfo.record]: sun_distance = 933850 12:40:05.872 [planetinfo.record]: corona_flare = 0.094313 12:40:05.872 [planetinfo.record]: corona_hues = 0.763046 12:40:05.872 [planetinfo.record]: sun_color = 0.433696, 0.733704, 0.79433, 1 12:40:05.872 [planetinfo.record]: corona_shimmer = 0.275618 12:40:05.873 [planetinfo.record]: station_vector = 0.819 -0.344 0.459 12:40:05.873 [planetinfo.record]: station = coriolis 12:40:11.090 [PLANETINFO OVER]: Done 12:40:11.091 [PLANETINFO LOGGING]: 2 251 12:40:12.112 [planetinfo.record]: seed = 225 183 21 87 12:40:12.112 [planetinfo.record]: coordinates = 183 117 12:40:12.112 [planetinfo.record]: government = 4; 12:40:12.112 [planetinfo.record]: economy = 5; 12:40:12.112 [planetinfo.record]: techlevel = 7; 12:40:12.112 [planetinfo.record]: population = 38; 12:40:12.112 [planetinfo.record]: productivity = 12160; 12:40:12.112 [planetinfo.record]: name = "Tizabea"; 12:40:12.112 [planetinfo.record]: inhabitant = "Human Colonial"; 12:40:12.112 [planetinfo.record]: inhabitants = "Human Colonials"; 12:40:12.112 [planetinfo.record]: description = "Tizabea is famous for its inhabitants’ ancient mating traditions but ravaged by dreadful civil war."; 12:40:12.114 [planetinfo.record]: air_color = 0.50805, 0.628414, 0.84876, 1; 12:40:12.115 [planetinfo.record]: cloud_alpha = 1.000000; 12:40:12.115 [planetinfo.record]: cloud_color = 0.21653, 0.548828, 0.533252, 1; 12:40:12.115 [planetinfo.record]: cloud_fraction = 0.540000; 12:40:12.115 [planetinfo.record]: land_color = 0.571377, 0.66, 0.45375, 1; 12:40:12.115 [planetinfo.record]: land_fraction = 0.380000; 12:40:12.115 [planetinfo.record]: polar_cloud_color = 0.464305, 0.746973, 0.733723, 1; 12:40:12.115 [planetinfo.record]: polar_land_color = 0.902646, 0.934, 0.861031, 1; 12:40:12.115 [planetinfo.record]: polar_sea_color = 0.869759, 0.84023, 0.942383, 1; 12:40:12.115 [planetinfo.record]: sea_color = 0.398562, 0.326347, 0.576172, 1; 12:40:12.115 [planetinfo.record]: rotation_speed = 0.001317 12:40:12.115 [planetinfo.record]: planet zpos = 574920.000000 12:40:12.116 [planetinfo.record]: sun_radius = 112620.227509 12:40:12.116 [planetinfo.record]: sun_vector = -0.439 -0.891 0.119 12:40:12.116 [planetinfo.record]: sun_distance = 1006110 12:40:12.116 [planetinfo.record]: corona_flare = 0.051770 12:40:12.116 [planetinfo.record]: corona_hues = 0.999283 12:40:12.116 [planetinfo.record]: sun_color = 0.680237, 0.511405, 0.450682, 1 12:40:12.116 [planetinfo.record]: corona_shimmer = 0.564819 12:40:12.116 [planetinfo.record]: station_vector = -0.354 0.914 0.197 12:40:12.116 [planetinfo.record]: station = coriolis 12:40:17.979 [PLANETINFO OVER]: Done 12:40:17.980 [PLANETINFO LOGGING]: 2 252 12:40:19.004 [planetinfo.record]: seed = 145 111 13 136 12:40:19.004 [planetinfo.record]: coordinates = 111 147 12:40:19.004 [planetinfo.record]: government = 2; 12:40:19.004 [planetinfo.record]: economy = 3; 12:40:19.004 [planetinfo.record]: techlevel = 8; 12:40:19.004 [planetinfo.record]: population = 38; 12:40:19.004 [planetinfo.record]: productivity = 12768; 12:40:19.004 [planetinfo.record]: name = "Usmaxe"; 12:40:19.004 [planetinfo.record]: inhabitant = "Human Colonial"; 12:40:19.004 [planetinfo.record]: inhabitants = "Human Colonials"; 12:40:19.004 [planetinfo.record]: description = "This world is plagued by frequent earthquakes."; 12:40:19.028 [planetinfo.record]: air_color = 0.790095, 0.592777, 0.902092, 1; 12:40:19.028 [planetinfo.record]: cloud_alpha = 1.000000; 12:40:19.028 [planetinfo.record]: cloud_color = 0.708984, 0.404343, 0.425763, 1; 12:40:19.028 [planetinfo.record]: cloud_fraction = 0.230000; 12:40:19.028 [planetinfo.record]: land_color = 0.566406, 0.478597, 0.00442505, 1; 12:40:19.028 [planetinfo.record]: land_fraction = 0.310000; 12:40:19.028 [planetinfo.record]: polar_cloud_color = 0.819043, 0.599085, 0.614551, 1; 12:40:19.028 [planetinfo.record]: polar_land_color = 0.943359, 0.906797, 0.709362, 1; 12:40:19.028 [planetinfo.record]: polar_sea_color = 0.930469, 0.916676, 0.869588, 1; 12:40:19.028 [planetinfo.record]: sea_color = 0.695312, 0.654083, 0.513336, 1; 12:40:19.028 [planetinfo.record]: rotation_speed = 0.002266 12:40:19.028 [planetinfo.record]: planet zpos = 597000.000000 12:40:19.028 [planetinfo.record]: sun_radius = 132947.795868 12:40:19.028 [planetinfo.record]: sun_vector = 0.272 0.952 -0.140 12:40:19.028 [planetinfo.record]: sun_distance = 945250 12:40:19.028 [planetinfo.record]: corona_flare = 0.017805 12:40:19.028 [planetinfo.record]: corona_hues = 0.579628 12:40:19.028 [planetinfo.record]: sun_color = 0.670264, 0.546156, 0.519537, 1 12:40:19.029 [planetinfo.record]: corona_shimmer = 0.419495 12:40:19.029 [planetinfo.record]: station_vector = 0.945 0.213 0.247 12:40:19.029 [planetinfo.record]: station = coriolis 12:40:24.329 [PLANETINFO OVER]: Done 12:40:24.330 [PLANETINFO LOGGING]: 2 253 12:40:25.336 [planetinfo.record]: seed = 89 150 165 164 12:40:25.336 [planetinfo.record]: coordinates = 150 130 12:40:25.336 [planetinfo.record]: government = 3; 12:40:25.336 [planetinfo.record]: economy = 2; 12:40:25.336 [planetinfo.record]: techlevel = 9; 12:40:25.336 [planetinfo.record]: population = 42; 12:40:25.336 [planetinfo.record]: productivity = 18816; 12:40:25.336 [planetinfo.record]: name = "Zaisedan"; 12:40:25.336 [planetinfo.record]: inhabitant = "Fierce Harmless Fat Bird"; 12:40:25.336 [planetinfo.record]: inhabitants = "Fierce Harmless Fat Birds"; 12:40:25.336 [planetinfo.record]: description = "The planet Zaisedan is an unremarkable dump."; 12:40:25.350 [planetinfo.record]: air_color = 0.653432, 0.72952, 0.99835, 1; 12:40:25.350 [planetinfo.record]: cloud_alpha = 1.000000; 12:40:25.350 [planetinfo.record]: cloud_color = 0.561401, 0.844539, 0.998047, 1; 12:40:25.350 [planetinfo.record]: cloud_fraction = 0.340000; 12:40:25.350 [planetinfo.record]: land_color = 0.402188, 0.66, 0.605618, 1; 12:40:25.351 [planetinfo.record]: land_fraction = 0.350000; 12:40:25.351 [planetinfo.record]: polar_cloud_color = 0.689596, 0.857882, 0.949121, 1; 12:40:25.351 [planetinfo.record]: polar_land_color = 0.842789, 0.934, 0.91476, 1; 12:40:25.351 [planetinfo.record]: polar_sea_color = 0.907009, 0.932617, 0.878882, 1; 12:40:25.351 [planetinfo.record]: sea_color = 0.59982, 0.673828, 0.518532, 1; 12:40:25.351 [planetinfo.record]: rotation_speed = 0.002910 12:40:25.351 [planetinfo.record]: planet zpos = 399000.000000 12:40:25.351 [planetinfo.record]: sun_radius = 83588.722229 12:40:25.351 [planetinfo.record]: sun_vector = -0.174 0.981 -0.090 12:40:25.351 [planetinfo.record]: sun_distance = 758100 12:40:25.351 [planetinfo.record]: corona_flare = 0.083891 12:40:25.351 [planetinfo.record]: corona_hues = 0.809143 12:40:25.351 [planetinfo.record]: sun_color = 0.653955, 0.266078, 0.249501, 1 12:40:25.351 [planetinfo.record]: corona_shimmer = 0.471231 12:40:25.351 [planetinfo.record]: station_vector = -0.589 -0.308 0.747 12:40:25.351 [planetinfo.record]: station = coriolis 12:40:30.270 [PLANETINFO OVER]: Done 12:40:30.270 [PLANETINFO LOGGING]: 2 254 12:40:31.280 [planetinfo.record]: seed = 137 91 109 18 12:40:31.280 [planetinfo.record]: coordinates = 91 248 12:40:31.280 [planetinfo.record]: government = 1; 12:40:31.280 [planetinfo.record]: economy = 2; 12:40:31.280 [planetinfo.record]: techlevel = 9; 12:40:31.280 [planetinfo.record]: population = 40; 12:40:31.280 [planetinfo.record]: productivity = 12800; 12:40:31.280 [planetinfo.record]: name = "Enbirare"; 12:40:31.280 [planetinfo.record]: inhabitant = "Human Colonial"; 12:40:31.280 [planetinfo.record]: inhabitants = "Human Colonials"; 12:40:31.280 [planetinfo.record]: description = "The world Enbirare is mildly noted for the Enbirareian deadly Ilreendioid but ravaged by killer disease."; 12:40:31.282 [planetinfo.record]: air_color = 0.535521, 0.449471, 0.956725, 1; 12:40:31.282 [planetinfo.record]: cloud_alpha = 1.000000; 12:40:31.282 [planetinfo.record]: cloud_color = 0.460236, 0.00682068, 0.873047, 1; 12:40:31.283 [planetinfo.record]: cloud_fraction = 0.320000; 12:40:31.283 [planetinfo.record]: land_color = 0.462814, 0.66, 0.201094, 1; 12:40:31.283 [planetinfo.record]: land_fraction = 0.370000; 12:40:31.283 [planetinfo.record]: polar_cloud_color = 0.629006, 0.339186, 0.892871, 1; 12:40:31.283 [planetinfo.record]: polar_land_color = 0.864238, 0.934, 0.771645, 1; 12:40:31.283 [planetinfo.record]: polar_sea_color = 0.946289, 0.89521, 0.893984, 1; 12:40:31.283 [planetinfo.record]: sea_color = 0.537109, 0.421141, 0.418358, 1; 12:40:31.283 [planetinfo.record]: rotation_speed = 0.003146 12:40:31.283 [planetinfo.record]: planet zpos = 341900.000000 12:40:31.283 [planetinfo.record]: sun_radius = 104356.815643 12:40:31.283 [planetinfo.record]: sun_vector = 0.322 -0.869 0.375 12:40:31.283 [planetinfo.record]: sun_distance = 717990 12:40:31.283 [planetinfo.record]: corona_flare = 0.097035 12:40:31.283 [planetinfo.record]: corona_hues = 0.908043 12:40:31.283 [planetinfo.record]: sun_color = 0.696808, 0.589249, 0.577942, 1 12:40:31.284 [planetinfo.record]: corona_shimmer = 0.609705 12:40:31.284 [planetinfo.record]: station_vector = -0.512 -0.641 0.571 12:40:31.284 [planetinfo.record]: station = coriolis 12:40:36.026 [PLANETINFO OVER]: Done 12:40:36.027 [PLANETINFO LOGGING]: 2 255 12:40:37.048 [planetinfo.record]: seed = 49 78 245 137 12:40:37.048 [planetinfo.record]: coordinates = 78 212 12:40:37.048 [planetinfo.record]: government = 6; 12:40:37.048 [planetinfo.record]: economy = 4; 12:40:37.048 [planetinfo.record]: techlevel = 8; 12:40:37.048 [planetinfo.record]: population = 43; 12:40:37.048 [planetinfo.record]: productivity = 20640; 12:40:37.048 [planetinfo.record]: name = "Esdicete"; 12:40:37.048 [planetinfo.record]: inhabitant = "Small Black Horned Lobster"; 12:40:37.048 [planetinfo.record]: inhabitants = "Small Black Horned Lobsters"; 12:40:37.048 [planetinfo.record]: description = "This world is fabled for its unusual tropical forests."; 12:40:37.052 [planetinfo.record]: air_color = 0.710026, 0.714823, 0.981439, 1; 12:40:37.053 [planetinfo.record]: cloud_alpha = 1.000000; 12:40:37.053 [planetinfo.record]: cloud_color = 0.728951, 0.737478, 0.947266, 1; 12:40:37.053 [planetinfo.record]: cloud_fraction = 0.480000; 12:40:37.053 [planetinfo.record]: land_color = 0.613281, 0.380905, 0.598758, 1; 12:40:37.053 [planetinfo.record]: land_fraction = 0.480000; 12:40:37.053 [planetinfo.record]: polar_cloud_color = 0.792847, 0.798059, 0.92627, 1; 12:40:37.053 [planetinfo.record]: polar_land_color = 0.938672, 0.849755, 0.933115, 1; 12:40:37.053 [planetinfo.record]: polar_sea_color = 0.921974, 0.933008, 0.876517, 1; 12:40:37.053 [planetinfo.record]: sea_color = 0.638233, 0.669922, 0.507675, 1; 12:40:37.053 [planetinfo.record]: rotation_speed = 0.000699 12:40:37.054 [planetinfo.record]: planet zpos = 779700.000000 12:40:37.054 [planetinfo.record]: sun_radius = 133367.691345 12:40:37.054 [planetinfo.record]: sun_vector = -0.574 0.415 -0.706 12:40:37.054 [planetinfo.record]: sun_distance = 1143560 12:40:37.054 [planetinfo.record]: corona_flare = 0.020552 12:40:37.054 [planetinfo.record]: corona_hues = 0.779037 12:40:37.054 [planetinfo.record]: sun_color = 0.79494, 0.731594, 0.679185, 1 12:40:37.054 [planetinfo.record]: corona_shimmer = 0.289705 12:40:37.054 [planetinfo.record]: station_vector = 0.303 -0.416 0.857 12:40:37.054 [planetinfo.record]: station = coriolis 12:40:41.194 [PLANETINFO OVER]: Done 12:41:00.293 [planetinfo.record]: seed = 66 75 42 234 12:41:00.293 [planetinfo.record]: coordinates = 75 217 12:41:00.293 [planetinfo.record]: government = 0; 12:41:00.293 [planetinfo.record]: economy = 3; 12:41:00.293 [planetinfo.record]: techlevel = 7; 12:41:00.293 [planetinfo.record]: population = 32; 12:41:00.293 [planetinfo.record]: productivity = 7168; 12:41:00.293 [planetinfo.record]: name = "Arrege"; 12:41:00.293 [planetinfo.record]: inhabitant = "Human Colonial"; 12:41:00.293 [planetinfo.record]: inhabitants = "Human Colonials"; 12:41:00.293 [planetinfo.record]: description = "The planet Arrege is cursed by vicious killer cats."; 12:41:00.300 [planetinfo.record]: air_color = 0.40486, 0.84799, 0.861768, 1; 12:41:00.301 [planetinfo.record]: cloud_alpha = 1.000000; 12:41:00.301 [planetinfo.record]: cloud_color = 0.528649, 0.587891, 0.0045929, 1; 12:41:00.301 [planetinfo.record]: cloud_fraction = 0.160000; 12:41:00.301 [planetinfo.record]: land_color = 0.66, 0.342891, 0.43951, 1; 12:41:00.301 [planetinfo.record]: land_fraction = 0.290000; 12:41:00.301 [planetinfo.record]: polar_cloud_color = 0.716399, 0.764551, 0.29044, 1; 12:41:00.301 [planetinfo.record]: polar_land_color = 0.934, 0.821811, 0.855993, 1; 12:41:00.301 [planetinfo.record]: polar_sea_color = 0.943359, 0.92911, 0.895362, 1; 12:41:00.302 [planetinfo.record]: sea_color = 0.566406, 0.532185, 0.451134, 1; 12:41:00.302 [planetinfo.record]: rotation_speed = 0.002618 12:41:00.302 [planetinfo.record]: planet zpos = 599610.000000 12:41:00.302 [planetinfo.record]: sun_radius = 100178.427429 12:41:00.302 [planetinfo.record]: sun_vector = -0.140 0.895 -0.423 12:41:00.302 [planetinfo.record]: sun_distance = 1144710 12:41:00.302 [planetinfo.record]: corona_flare = 0.085884 12:41:00.302 [planetinfo.record]: corona_hues = 0.565506 12:41:00.302 [planetinfo.record]: sun_color = 0.757123, 0.693196, 0.670555, 1 12:41:00.303 [planetinfo.record]: corona_shimmer = 0.436249 12:41:00.303 [planetinfo.record]: station_vector = -0.637 0.761 0.125 12:41:00.303 [planetinfo.record]: station = coriolis 12:41:05.067 [PLANETINFO OVER]: Done 12:43:10.071 [PLANETINFO LOGGING]: 3 0 12:43:10.859 [planetinfo.record]: seed = 66 16 154 189 12:43:10.859 [planetinfo.record]: coordinates = 16 210 12:43:10.859 [planetinfo.record]: government = 0; 12:43:10.859 [planetinfo.record]: economy = 2; 12:43:10.859 [planetinfo.record]: techlevel = 5; 12:43:10.859 [planetinfo.record]: population = 23; 12:43:10.859 [planetinfo.record]: productivity = 5888; 12:43:10.859 [planetinfo.record]: name = "Isrema"; 12:43:10.859 [planetinfo.record]: inhabitant = "Harmless Horned Lobster"; 12:43:10.859 [planetinfo.record]: inhabitants = "Harmless Horned Lobsters"; 12:43:10.859 [planetinfo.record]: description = "This world is a tedious place."; 12:43:10.870 [planetinfo.record]: air_color = 0.748184, 0.760628, 0.941115, 1; 12:43:10.870 [planetinfo.record]: cloud_alpha = 1.000000; 12:43:10.870 [planetinfo.record]: cloud_color = 0.813263, 0.815583, 0.826172, 1; 12:43:10.870 [planetinfo.record]: cloud_fraction = 0.440000; 12:43:10.870 [planetinfo.record]: land_color = 0.141797, 0.368511, 0.66, 1; 12:43:10.870 [planetinfo.record]: land_fraction = 0.380000; 12:43:10.870 [planetinfo.record]: polar_cloud_color = 0.863264, 0.864794, 0.871777, 1; 12:43:10.870 [planetinfo.record]: polar_land_color = 0.750666, 0.830875, 0.934, 1; 12:43:10.870 [planetinfo.record]: polar_sea_color = 0.896956, 0.933789, 0.885185, 1; 12:43:10.870 [planetinfo.record]: sea_color = 0.557642, 0.662109, 0.524256, 1; 12:43:10.870 [planetinfo.record]: rotation_speed = 0.000714 12:43:10.870 [planetinfo.record]: planet zpos = 677600.000000 12:43:10.870 [planetinfo.record]: sun_radius = 179041.918945 12:43:10.870 [planetinfo.record]: sun_vector = -0.062 -0.529 -0.846 12:43:10.870 [planetinfo.record]: sun_distance = 1232000 12:43:10.871 [planetinfo.record]: corona_flare = 0.047870 12:43:10.871 [planetinfo.record]: corona_hues = 0.734482 12:43:10.871 [planetinfo.record]: sun_color = 0.790384, 0.695398, 0.627434, 1 12:43:10.871 [planetinfo.record]: corona_shimmer = 0.276675 12:43:10.871 [planetinfo.record]: station_vector = -0.923 -0.262 0.281 12:43:10.871 [planetinfo.record]: station = coriolis 12:43:15.361 [PLANETINFO OVER]: Done 12:43:15.362 [PLANETINFO LOGGING]: 3 1 12:43:16.369 [planetinfo.record]: seed = 210 203 10 218 12:43:16.369 [planetinfo.record]: coordinates = 203 110 12:43:16.369 [planetinfo.record]: government = 2; 12:43:16.369 [planetinfo.record]: economy = 6; 12:43:16.369 [planetinfo.record]: techlevel = 5; 12:43:16.369 [planetinfo.record]: population = 29; 12:43:16.369 [planetinfo.record]: productivity = 5568; 12:43:16.369 [planetinfo.record]: name = "Qubeor"; 12:43:16.369 [planetinfo.record]: inhabitant = "Human Colonial"; 12:43:16.369 [planetinfo.record]: inhabitants = "Human Colonials"; 12:43:16.369 [planetinfo.record]: description = "This planet is beset by lethal disease."; 12:43:16.392 [planetinfo.record]: air_color = 0.453499, 0.491416, 0.926807, 1; 12:43:16.392 [planetinfo.record]: cloud_alpha = 1.000000; 12:43:16.392 [planetinfo.record]: cloud_color = 0.0520096, 0.217671, 0.783203, 1; 12:43:16.392 [planetinfo.record]: cloud_fraction = 0.460000; 12:43:16.392 [planetinfo.record]: land_color = 0.0721875, 0.604893, 0.66, 1; 12:43:16.392 [planetinfo.record]: land_fraction = 0.450000; 12:43:16.392 [planetinfo.record]: polar_cloud_color = 0.355045, 0.467737, 0.852441, 1; 12:43:16.392 [planetinfo.record]: polar_land_color = 0.726039, 0.914504, 0.934, 1; 12:43:16.392 [planetinfo.record]: polar_sea_color = 0.894491, 0.933594, 0.884999, 1; 12:43:16.392 [planetinfo.record]: sea_color = 0.552806, 0.664062, 0.525803, 1; 12:43:16.392 [planetinfo.record]: rotation_speed = 0.004505 12:43:16.392 [planetinfo.record]: planet zpos = 502110.000000 12:43:16.392 [planetinfo.record]: sun_radius = 151029.529877 12:43:16.392 [planetinfo.record]: sun_vector = -0.070 0.596 0.800 12:43:16.392 [planetinfo.record]: sun_distance = 1171590 12:43:16.392 [planetinfo.record]: corona_flare = 0.089485 12:43:16.392 [planetinfo.record]: corona_hues = 0.716393 12:43:16.392 [planetinfo.record]: sun_color = 0.794766, 0.649929, 0.386893, 1 12:43:16.392 [planetinfo.record]: corona_shimmer = 0.475119 12:43:16.392 [planetinfo.record]: station_vector = 0.940 0.090 0.328 12:43:16.392 [planetinfo.record]: station = coriolis 12:43:20.675 [PLANETINFO OVER]: Done 12:43:20.675 [PLANETINFO LOGGING]: 3 2 12:43:21.678 [planetinfo.record]: seed = 178 167 90 117 12:43:21.678 [planetinfo.record]: coordinates = 167 185 12:43:21.678 [planetinfo.record]: government = 6; 12:43:21.678 [planetinfo.record]: economy = 1; 12:43:21.678 [planetinfo.record]: techlevel = 12; 12:43:21.678 [planetinfo.record]: population = 56; 12:43:21.678 [planetinfo.record]: productivity = 40320; 12:43:21.678 [planetinfo.record]: name = "Lavebe"; 12:43:21.678 [planetinfo.record]: inhabitant = "Human Colonial"; 12:43:21.678 [planetinfo.record]: inhabitants = "Human Colonials"; 12:43:21.678 [planetinfo.record]: description = "Lavebe is famous for its inhabitants’ ancient loathing of casinos but plagued by occasional solar activity."; 12:43:21.679 [planetinfo.record]: air_color = 0.582113, 0.928372, 0.970383, 1; 12:43:21.679 [planetinfo.record]: cloud_alpha = 1.000000; 12:43:21.680 [planetinfo.record]: cloud_color = 0.729342, 0.914062, 0.364197, 1; 12:43:21.680 [planetinfo.record]: cloud_fraction = 0.230000; 12:43:21.680 [planetinfo.record]: land_color = 0.556875, 0.66, 0.570571, 1; 12:43:21.680 [planetinfo.record]: land_fraction = 0.520000; 12:43:21.680 [planetinfo.record]: polar_cloud_color = 0.796223, 0.911328, 0.56869, 1; 12:43:21.680 [planetinfo.record]: polar_land_color = 0.897516, 0.934, 0.902361, 1; 12:43:21.680 [planetinfo.record]: polar_sea_color = 0.85141, 0.868704, 0.947656, 1; 12:43:21.680 [planetinfo.record]: sea_color = 0.310791, 0.349001, 0.523438, 1; 12:43:21.680 [planetinfo.record]: rotation_speed = 0.002578 12:43:21.680 [planetinfo.record]: planet zpos = 639450.000000 12:43:21.680 [planetinfo.record]: sun_radius = 82205.986176 12:43:21.680 [planetinfo.record]: sun_vector = -0.537 0.821 -0.195 12:43:21.680 [planetinfo.record]: sun_distance = 852600 12:43:21.680 [planetinfo.record]: corona_flare = 0.032648 12:43:21.680 [planetinfo.record]: corona_hues = 0.805511 12:43:21.680 [planetinfo.record]: sun_color = 0.18177, 0.310737, 0.705414, 1 12:43:21.681 [planetinfo.record]: corona_shimmer = 0.373684 12:43:21.681 [planetinfo.record]: station_vector = 0.081 0.338 0.938 12:43:21.681 [planetinfo.record]: station = dodecahedron 12:43:26.318 [PLANETINFO OVER]: Done 12:43:26.319 [PLANETINFO LOGGING]: 3 3 12:43:27.321 [planetinfo.record]: seed = 2 64 170 10 12:43:27.322 [planetinfo.record]: coordinates = 64 243 12:43:27.322 [planetinfo.record]: government = 0; 12:43:27.322 [planetinfo.record]: economy = 3; 12:43:27.322 [planetinfo.record]: techlevel = 4; 12:43:27.322 [planetinfo.record]: population = 20; 12:43:27.322 [planetinfo.record]: productivity = 4480; 12:43:27.322 [planetinfo.record]: name = "Arriesen"; 12:43:27.322 [planetinfo.record]: inhabitant = "Small Green Bony Humanoid"; 12:43:27.322 [planetinfo.record]: inhabitants = "Small Green Bony Humanoids"; 12:43:27.322 [planetinfo.record]: description = "The planet Arriesen is scourged by deadly edible arts graduates."; 12:43:27.344 [planetinfo.record]: air_color = 0.658207, 0.739618, 0.991846, 1; 12:43:27.344 [planetinfo.record]: cloud_alpha = 1.000000; 12:43:27.344 [planetinfo.record]: cloud_color = 0.577171, 0.865638, 0.978516, 1; 12:43:27.344 [planetinfo.record]: cloud_fraction = 0.430000; 12:43:27.344 [planetinfo.record]: land_color = 0.630674, 0.626484, 0.66, 1; 12:43:27.344 [planetinfo.record]: land_fraction = 0.500000; 12:43:27.344 [planetinfo.record]: polar_cloud_color = 0.69928, 0.872536, 0.940332, 1; 12:43:27.344 [planetinfo.record]: polar_land_color = 0.923625, 0.922143, 0.934, 1; 12:43:27.344 [planetinfo.record]: polar_sea_color = 0.915625, 0.777057, 0.762723, 1; 12:43:27.344 [planetinfo.record]: sea_color = 0.84375, 0.332989, 0.280151, 1; 12:43:27.344 [planetinfo.record]: rotation_speed = 0.000014 12:43:27.344 [planetinfo.record]: planet zpos = 598400.000000 12:43:27.344 [planetinfo.record]: sun_radius = 122812.548828 12:43:27.344 [planetinfo.record]: sun_vector = -0.307 0.863 -0.401 12:43:27.344 [planetinfo.record]: sun_distance = 924800 12:43:27.344 [planetinfo.record]: corona_flare = 0.032466 12:43:27.344 [planetinfo.record]: corona_hues = 0.523056 12:43:27.344 [planetinfo.record]: sun_color = 0.360772, 0.470818, 0.67597, 1 12:43:27.345 [planetinfo.record]: corona_shimmer = 0.391877 12:43:27.345 [planetinfo.record]: station_vector = -0.863 0.181 0.472 12:43:27.345 [planetinfo.record]: station = coriolis 12:43:32.302 [PLANETINFO OVER]: Done 12:43:32.303 [PLANETINFO LOGGING]: 3 4 12:43:33.307 [planetinfo.record]: seed = 98 210 26 154 12:43:33.307 [planetinfo.record]: coordinates = 210 137 12:43:33.307 [planetinfo.record]: government = 4; 12:43:33.307 [planetinfo.record]: economy = 1; 12:43:33.307 [planetinfo.record]: techlevel = 10; 12:43:33.307 [planetinfo.record]: population = 46; 12:43:33.307 [planetinfo.record]: productivity = 26496; 12:43:33.307 [planetinfo.record]: name = "Qulaxe"; 12:43:33.307 [planetinfo.record]: inhabitant = "Human Colonial"; 12:43:33.307 [planetinfo.record]: inhabitants = "Human Colonials"; 12:43:33.307 [planetinfo.record]: description = "The world Qulaxe is reasonably noted for its inhabitants’ eccentric shyness and its inhabitants’ ingrained silliness."; 12:43:33.324 [planetinfo.record]: air_color = 0.675733, 0.729872, 0.987293, 1; 12:43:33.324 [planetinfo.record]: cloud_alpha = 1.000000; 12:43:33.324 [planetinfo.record]: cloud_color = 0.62941, 0.799747, 0.964844, 1; 12:43:33.324 [planetinfo.record]: cloud_fraction = 0.320000; 12:43:33.324 [planetinfo.record]: land_color = 0.572163, 0.66, 0.430547, 1; 12:43:33.324 [planetinfo.record]: land_fraction = 0.360000; 12:43:33.324 [planetinfo.record]: polar_cloud_color = 0.731196, 0.834274, 0.93418, 1; 12:43:33.324 [planetinfo.record]: polar_land_color = 0.902924, 0.934, 0.852822, 1; 12:43:33.324 [planetinfo.record]: polar_sea_color = 0.869956, 0.85665, 0.946289, 1; 12:43:33.324 [planetinfo.record]: sea_color = 0.363804, 0.333595, 0.537109, 1; 12:43:33.324 [planetinfo.record]: rotation_speed = 0.001977 12:43:33.324 [planetinfo.record]: planet zpos = 726180.000000 12:43:33.324 [planetinfo.record]: sun_radius = 150098.179321 12:43:33.324 [planetinfo.record]: sun_vector = -0.232 -0.973 0.009 12:43:33.324 [planetinfo.record]: sun_distance = 1005480 12:43:33.324 [planetinfo.record]: corona_flare = 0.074660 12:43:33.324 [planetinfo.record]: corona_hues = 0.883141 12:43:33.324 [planetinfo.record]: sun_color = 0.687936, 0.655748, 0.645966, 1 12:43:33.324 [planetinfo.record]: corona_shimmer = 0.525414 12:43:33.324 [planetinfo.record]: station_vector = -0.697 0.181 0.694 12:43:33.324 [planetinfo.record]: station = coriolis 12:43:37.743 [PLANETINFO OVER]: Done 12:43:37.744 [PLANETINFO LOGGING]: 3 5 12:43:38.748 [planetinfo.record]: seed = 242 241 202 73 12:43:38.748 [planetinfo.record]: coordinates = 241 98 12:43:38.748 [planetinfo.record]: government = 6; 12:43:38.748 [planetinfo.record]: economy = 2; 12:43:38.748 [planetinfo.record]: techlevel = 9; 12:43:38.748 [planetinfo.record]: population = 45; 12:43:38.748 [planetinfo.record]: productivity = 28800; 12:43:38.749 [planetinfo.record]: name = "Esisor"; 12:43:38.749 [planetinfo.record]: inhabitant = "Small Yellow Bony Bird"; 12:43:38.749 [planetinfo.record]: inhabitants = "Small Yellow Bony Birds"; 12:43:38.749 [planetinfo.record]: description = "The planet Esisor is most well known for its hoopy casinos."; 12:43:38.800 [planetinfo.record]: air_color = 0.606694, 0.825811, 0.989895, 1; 12:43:38.800 [planetinfo.record]: cloud_alpha = 1.000000; 12:43:38.800 [planetinfo.record]: cloud_color = 0.425537, 0.972656, 0.587963, 1; 12:43:38.800 [planetinfo.record]: cloud_fraction = 0.290000; 12:43:38.800 [planetinfo.record]: land_color = 0.161911, 0.511719, 0.175575, 1; 12:43:38.800 [planetinfo.record]: land_fraction = 0.350000; 12:43:38.800 [planetinfo.record]: polar_cloud_color = 0.608037, 0.937695, 0.705904, 1; 12:43:38.800 [planetinfo.record]: polar_land_color = 0.786675, 0.948828, 0.793009, 1; 12:43:38.800 [planetinfo.record]: polar_sea_color = 0.857138, 0.915234, 0.89481, 1; 12:43:38.800 [planetinfo.record]: sea_color = 0.632431, 0.847656, 0.771991, 1; 12:43:38.800 [planetinfo.record]: rotation_speed = 0.003195 12:43:38.800 [planetinfo.record]: planet zpos = 536100.000000 12:43:38.800 [planetinfo.record]: sun_radius = 142816.300507 12:43:38.800 [planetinfo.record]: sun_vector = -0.523 0.349 -0.778 12:43:38.800 [planetinfo.record]: sun_distance = 1072200 12:43:38.800 [planetinfo.record]: corona_flare = 0.053049 12:43:38.800 [planetinfo.record]: corona_hues = 0.835556 12:43:38.800 [planetinfo.record]: sun_color = 0.422049, 0.506351, 0.714944, 1 12:43:38.801 [planetinfo.record]: corona_shimmer = 0.334811 12:43:38.801 [planetinfo.record]: station_vector = 0.663 0.568 0.488 12:43:38.801 [planetinfo.record]: station = coriolis 12:43:43.343 [PLANETINFO OVER]: Done 12:43:43.344 [PLANETINFO LOGGING]: 3 6 12:43:44.346 [planetinfo.record]: seed = 82 193 218 56 12:43:44.346 [planetinfo.record]: coordinates = 193 217 12:43:44.346 [planetinfo.record]: government = 2; 12:43:44.346 [planetinfo.record]: economy = 1; 12:43:44.346 [planetinfo.record]: techlevel = 8; 12:43:44.346 [planetinfo.record]: population = 36; 12:43:44.346 [planetinfo.record]: productivity = 15552; 12:43:44.346 [planetinfo.record]: name = "Edbedi"; 12:43:44.346 [planetinfo.record]: inhabitant = "Red Slimy Rodent"; 12:43:44.346 [planetinfo.record]: inhabitants = "Red Slimy Rodents"; 12:43:44.347 [planetinfo.record]: description = "This world is reasonably well known for its vast rain forests but beset by lethal disease."; 12:43:44.348 [planetinfo.record]: air_color = 0.680765, 0.554427, 0.900791, 1; 12:43:44.348 [planetinfo.record]: cloud_alpha = 1.000000; 12:43:44.348 [planetinfo.record]: cloud_color = 0.705078, 0.31398, 0.662302, 1; 12:43:44.348 [planetinfo.record]: cloud_fraction = 0.490000; 12:43:44.348 [planetinfo.record]: land_color = 0.288374, 0.613281, 0.019165, 1; 12:43:44.348 [planetinfo.record]: land_fraction = 0.340000; 12:43:44.348 [planetinfo.record]: polar_cloud_color = 0.817285, 0.533949, 0.786295, 1; 12:43:44.349 [planetinfo.record]: polar_land_color = 0.814348, 0.938672, 0.711337, 1; 12:43:44.349 [planetinfo.record]: polar_sea_color = 0.94082, 0.904777, 0.885235, 1; 12:43:44.349 [planetinfo.record]: sea_color = 0.591797, 0.501108, 0.451939, 1; 12:43:44.349 [planetinfo.record]: rotation_speed = 0.003805 12:43:44.349 [planetinfo.record]: planet zpos = 657410.000000 12:43:44.349 [planetinfo.record]: sun_radius = 117854.428253 12:43:44.349 [planetinfo.record]: sun_vector = -0.666 -0.527 -0.528 12:43:44.349 [planetinfo.record]: sun_distance = 809120 12:43:44.349 [planetinfo.record]: corona_flare = 0.062964 12:43:44.349 [planetinfo.record]: corona_hues = 0.538338 12:43:44.349 [planetinfo.record]: sun_color = 0.751585, 0.77895, 0.807404, 1 12:43:44.349 [planetinfo.record]: corona_shimmer = 1.046767 12:43:44.349 [planetinfo.record]: station_vector = -0.809 0.578 0.107 12:43:44.349 [planetinfo.record]: station = coriolis 12:43:49.111 [PLANETINFO OVER]: Done 12:43:49.112 [PLANETINFO LOGGING]: 3 7 12:43:50.123 [planetinfo.record]: seed = 162 218 106 124 12:43:50.123 [planetinfo.record]: coordinates = 218 205 12:43:50.123 [planetinfo.record]: government = 4; 12:43:50.124 [planetinfo.record]: economy = 5; 12:43:50.124 [planetinfo.record]: techlevel = 6; 12:43:50.124 [planetinfo.record]: population = 34; 12:43:50.124 [planetinfo.record]: productivity = 10880; 12:43:50.124 [planetinfo.record]: name = "Teceteis"; 12:43:50.124 [planetinfo.record]: inhabitant = "Human Colonial"; 12:43:50.124 [planetinfo.record]: inhabitants = "Human Colonials"; 12:43:50.124 [planetinfo.record]: description = "The planet Teceteis is most famous for the Teceteisian spotted shrew."; 12:43:50.136 [planetinfo.record]: air_color = 0.65474, 0.763578, 0.984691, 1; 12:43:50.136 [planetinfo.record]: cloud_alpha = 1.000000; 12:43:50.136 [planetinfo.record]: cloud_color = 0.568237, 0.947919, 0.957031, 1; 12:43:50.136 [planetinfo.record]: cloud_fraction = 0.220000; 12:43:50.136 [planetinfo.record]: land_color = 0.185471, 0.558594, 0.436163, 1; 12:43:50.136 [planetinfo.record]: land_fraction = 0.620000; 12:43:50.136 [planetinfo.record]: polar_cloud_color = 0.694363, 0.925126, 0.930664, 1; 12:43:50.136 [planetinfo.record]: polar_land_color = 0.786476, 0.944141, 0.892407, 1; 12:43:50.136 [planetinfo.record]: polar_sea_color = 0.926136, 0.935352, 0.881733, 1; 12:43:50.136 [planetinfo.record]: sea_color = 0.621006, 0.646484, 0.498248, 1; 12:43:50.136 [planetinfo.record]: rotation_speed = 0.003839 12:43:50.136 [planetinfo.record]: planet zpos = 915900.000000 12:43:50.136 [planetinfo.record]: sun_radius = 149826.012268 12:43:50.136 [planetinfo.record]: sun_vector = 0.376 -0.786 0.490 12:43:50.137 [planetinfo.record]: sun_distance = 1038020 12:43:50.137 [planetinfo.record]: corona_flare = 0.036314 12:43:50.137 [planetinfo.record]: corona_hues = 0.636185 12:43:50.137 [planetinfo.record]: sun_color = 0.786603, 0.745588, 0.734031, 1 12:43:50.137 [planetinfo.record]: corona_shimmer = 0.551001 12:43:50.137 [planetinfo.record]: station_vector = -0.301 0.923 0.239 12:43:50.137 [planetinfo.record]: station = coriolis 12:43:54.756 [PLANETINFO OVER]: Done 12:43:54.756 [PLANETINFO LOGGING]: 3 8 12:43:55.761 [planetinfo.record]: seed = 130 29 154 190 12:43:55.761 [planetinfo.record]: coordinates = 29 124 12:43:55.761 [planetinfo.record]: government = 0; 12:43:55.761 [planetinfo.record]: economy = 6; 12:43:55.761 [planetinfo.record]: techlevel = 2; 12:43:55.761 [planetinfo.record]: population = 15; 12:43:55.761 [planetinfo.record]: productivity = 1920; 12:43:55.761 [planetinfo.record]: name = "Riedra"; 12:43:55.761 [planetinfo.record]: inhabitant = "Harmless Bug-Eyed Lobster"; 12:43:55.761 [planetinfo.record]: inhabitants = "Harmless Bug-Eyed Lobsters"; 12:43:55.761 [planetinfo.record]: description = "This world is a revolting dump."; 12:43:55.780 [planetinfo.record]: air_color = 0.772805, 0.664001, 0.8325, 1; 12:43:55.780 [planetinfo.record]: cloud_alpha = 1.000000; 12:43:55.780 [planetinfo.record]: cloud_color = 0.5, 0.496094, 0.496277, 1; 12:43:55.780 [planetinfo.record]: cloud_fraction = 0.390000; 12:43:55.780 [planetinfo.record]: land_color = 0.636837, 0.541406, 0.66, 1; 12:43:55.780 [planetinfo.record]: land_fraction = 0.510000; 12:43:55.780 [planetinfo.record]: polar_cloud_color = 0.725, 0.72146, 0.721626, 1; 12:43:55.780 [planetinfo.record]: polar_land_color = 0.925805, 0.892043, 0.934, 1; 12:43:55.780 [planetinfo.record]: polar_sea_color = 0.93125, 0.881757, 0.857587, 1; 12:43:55.780 [planetinfo.record]: sea_color = 0.6875, 0.541348, 0.469971, 1; 12:43:55.780 [planetinfo.record]: rotation_speed = 0.003216 12:43:55.780 [planetinfo.record]: planet zpos = 707190.000000 12:43:55.780 [planetinfo.record]: sun_radius = 105931.940460 12:43:55.780 [planetinfo.record]: sun_vector = 0.145 -0.826 -0.544 12:43:55.780 [planetinfo.record]: sun_distance = 1350090 12:43:55.780 [planetinfo.record]: corona_flare = 0.041322 12:43:55.780 [planetinfo.record]: corona_hues = 0.658310 12:43:55.780 [planetinfo.record]: sun_color = 0.240472, 0.638719, 0.790924, 1 12:43:55.780 [planetinfo.record]: corona_shimmer = 0.396995 12:43:55.780 [planetinfo.record]: station_vector = 0.187 0.156 0.970 12:43:55.781 [planetinfo.record]: station = coriolis 12:44:00.788 [PLANETINFO OVER]: Done 12:44:00.788 [PLANETINFO LOGGING]: 3 9 12:44:01.794 [planetinfo.record]: seed = 18 75 138 215 12:44:01.794 [planetinfo.record]: coordinates = 75 52 12:44:01.794 [planetinfo.record]: government = 2; 12:44:01.794 [planetinfo.record]: economy = 4; 12:44:01.794 [planetinfo.record]: techlevel = 7; 12:44:01.794 [planetinfo.record]: population = 35; 12:44:01.794 [planetinfo.record]: productivity = 10080; 12:44:01.794 [planetinfo.record]: name = "Tiveorso"; 12:44:01.794 [planetinfo.record]: inhabitant = "Lizard"; 12:44:01.795 [planetinfo.record]: inhabitants = "Lizards"; 12:44:01.795 [planetinfo.record]: description = "Tiveorso is ravaged by unpredictable solar activity."; 12:44:01.812 [planetinfo.record]: air_color = 0.677678, 0.586861, 0.86567, 1; 12:44:01.812 [planetinfo.record]: cloud_alpha = 1.000000; 12:44:01.812 [planetinfo.record]: cloud_color = 0.597889, 0.37944, 0.599609, 1; 12:44:01.812 [planetinfo.record]: cloud_fraction = 0.530000; 12:44:01.812 [planetinfo.record]: land_color = 0.66, 0.66, 0.66, 1; 12:44:01.812 [planetinfo.record]: land_fraction = 0.580000; 12:44:01.812 [planetinfo.record]: polar_cloud_color = 0.768444, 0.593156, 0.769824, 1; 12:44:01.812 [planetinfo.record]: polar_land_color = 0.934, 0.934, 0.934, 1; 12:44:01.812 [planetinfo.record]: polar_sea_color = 0.785748, 0.948828, 0.788296, 1; 12:44:01.812 [planetinfo.record]: sea_color = 0.159912, 0.511719, 0.165409, 1; 12:44:01.812 [planetinfo.record]: rotation_speed = 0.001985 12:44:01.812 [planetinfo.record]: planet zpos = 421470.000000 12:44:01.812 [planetinfo.record]: sun_radius = 118860.708160 12:44:01.812 [planetinfo.record]: sun_vector = 0.890 0.194 0.412 12:44:01.812 [planetinfo.record]: sun_distance = 936600 12:44:01.812 [planetinfo.record]: corona_flare = 0.001920 12:44:01.812 [planetinfo.record]: corona_hues = 0.900276 12:44:01.812 [planetinfo.record]: sun_color = 0.747922, 0.769199, 0.800546, 1 12:44:01.812 [planetinfo.record]: corona_shimmer = 0.290845 12:44:01.812 [planetinfo.record]: station_vector = -0.374 -0.760 0.531 12:44:01.812 [planetinfo.record]: station = coriolis 12:44:06.289 [PLANETINFO OVER]: Done 12:44:06.290 [PLANETINFO LOGGING]: 3 10 12:44:07.293 [planetinfo.record]: seed = 242 167 90 120 12:44:07.293 [planetinfo.record]: coordinates = 167 121 12:44:07.293 [planetinfo.record]: government = 6; 12:44:07.293 [planetinfo.record]: economy = 1; 12:44:07.293 [planetinfo.record]: techlevel = 12; 12:44:07.293 [planetinfo.record]: population = 56; 12:44:07.293 [planetinfo.record]: productivity = 40320; 12:44:07.293 [planetinfo.record]: name = "Edorqu"; 12:44:07.293 [planetinfo.record]: inhabitant = "Human Colonial"; 12:44:07.293 [planetinfo.record]: inhabitants = "Human Colonials"; 12:44:07.293 [planetinfo.record]: description = "Edorqu is famous for Edorquian wolf cutlet but beset by evil spotted cats."; 12:44:07.316 [planetinfo.record]: air_color = 0.503928, 0.464364, 0.936563, 1; 12:44:07.316 [planetinfo.record]: cloud_alpha = 1.000000; 12:44:07.316 [planetinfo.record]: cloud_color = 0.267097, 0.0698242, 0.8125, 1; 12:44:07.323 [planetinfo.record]: cloud_fraction = 0.260000; 12:44:07.323 [planetinfo.record]: land_color = 0.446377, 0.367363, 0.525391, 1; 12:44:07.324 [planetinfo.record]: land_fraction = 0.560000; 12:44:07.324 [planetinfo.record]: polar_cloud_color = 0.50246, 0.371103, 0.865625, 1; 12:44:07.324 [planetinfo.record]: polar_land_color = 0.911839, 0.876216, 0.947461, 1; 12:44:07.324 [planetinfo.record]: polar_sea_color = 0.945898, 0.937419, 0.728822, 1; 12:44:07.324 [planetinfo.record]: sea_color = 0.541016, 0.521616, 0.0443802, 1; 12:44:07.324 [planetinfo.record]: rotation_speed = 0.000080 12:44:07.324 [planetinfo.record]: planet zpos = 503100.000000 12:44:07.324 [planetinfo.record]: sun_radius = 134636.211090 12:44:07.324 [planetinfo.record]: sun_vector = -0.246 0.739 -0.628 12:44:07.324 [planetinfo.record]: sun_distance = 1106820 12:44:07.324 [planetinfo.record]: corona_flare = 0.007216 12:44:07.324 [planetinfo.record]: corona_hues = 0.806595 12:44:07.324 [planetinfo.record]: sun_color = 0.615147, 0.70777, 0.746329, 1 12:44:07.324 [planetinfo.record]: corona_shimmer = 0.325583 12:44:07.324 [planetinfo.record]: station_vector = -0.636 0.771 0.023 12:44:07.324 [planetinfo.record]: station = icosahedron 12:44:12.117 [PLANETINFO OVER]: Done 12:44:12.118 [PLANETINFO LOGGING]: 3 11 12:44:13.120 [planetinfo.record]: seed = 66 204 42 32 12:44:13.120 [planetinfo.record]: coordinates = 204 186 12:44:13.120 [planetinfo.record]: government = 0; 12:44:13.120 [planetinfo.record]: economy = 2; 12:44:13.120 [planetinfo.record]: techlevel = 5; 12:44:13.121 [planetinfo.record]: population = 23; 12:44:13.121 [planetinfo.record]: productivity = 5888; 12:44:13.121 [planetinfo.record]: name = "Bien"; 12:44:13.121 [planetinfo.record]: inhabitant = "Human Colonial"; 12:44:13.121 [planetinfo.record]: inhabitants = "Human Colonials"; 12:44:13.121 [planetinfo.record]: description = "This world is reasonably notable for its weird tropical forests but scourged by frequent civil war."; 12:44:13.131 [planetinfo.record]: air_color = 0.70865, 0.739676, 0.969082, 1; 12:44:13.131 [planetinfo.record]: cloud_alpha = 1.000000; 12:44:13.131 [planetinfo.record]: cloud_color = 0.721725, 0.786499, 0.910156, 1; 12:44:13.131 [planetinfo.record]: cloud_fraction = 0.370000; 12:44:13.132 [planetinfo.record]: land_color = 0.66, 0.368672, 0.573512, 1; 12:44:13.132 [planetinfo.record]: land_fraction = 0.680000; 12:44:13.132 [planetinfo.record]: polar_cloud_color = 0.791877, 0.832334, 0.90957, 1; 12:44:13.132 [planetinfo.record]: polar_land_color = 0.934, 0.830932, 0.903402, 1; 12:44:13.132 [planetinfo.record]: polar_sea_color = 0.89795, 0.928516, 0.870121, 1; 12:44:13.132 [planetinfo.record]: sea_color = 0.620715, 0.714844, 0.535016, 1; 12:44:13.132 [planetinfo.record]: rotation_speed = 0.002545 12:44:13.132 [planetinfo.record]: planet zpos = 422800.000000 12:44:13.132 [planetinfo.record]: sun_radius = 57532.345581 12:44:13.132 [planetinfo.record]: sun_vector = -0.215 0.473 -0.854 12:44:13.133 [planetinfo.record]: sun_distance = 604000 12:44:13.133 [planetinfo.record]: corona_flare = 0.005580 12:44:13.133 [planetinfo.record]: corona_hues = 0.722969 12:44:13.133 [planetinfo.record]: sun_color = 0.752832, 0.710231, 0.544544, 1 12:44:13.134 [planetinfo.record]: corona_shimmer = 0.239999 12:44:13.135 [planetinfo.record]: station_vector = -0.978 -0.011 0.209 12:44:13.135 [planetinfo.record]: station = coriolis 12:44:18.085 [PLANETINFO OVER]: Done 12:44:18.086 [PLANETINFO LOGGING]: 3 12 12:44:19.098 [planetinfo.record]: seed = 162 89 26 147 12:44:19.098 [planetinfo.record]: coordinates = 89 146 12:44:19.098 [planetinfo.record]: government = 4; 12:44:19.098 [planetinfo.record]: economy = 2; 12:44:19.098 [planetinfo.record]: techlevel = 8; 12:44:19.098 [planetinfo.record]: population = 39; 12:44:19.098 [planetinfo.record]: productivity = 19968; 12:44:19.098 [planetinfo.record]: name = "Beoninon"; 12:44:19.098 [planetinfo.record]: inhabitant = "Human Colonial"; 12:44:19.098 [planetinfo.record]: inhabitants = "Human Colonials"; 12:44:19.098 [planetinfo.record]: description = "This world is very well known for vicious Enoube brew and Beoninonian shrew steak."; 12:44:19.101 [planetinfo.record]: air_color = 0.750864, 0.533696, 0.938514, 1; 12:44:19.101 [planetinfo.record]: cloud_alpha = 1.000000; 12:44:19.102 [planetinfo.record]: cloud_color = 0.818359, 0.249344, 0.462725, 1; 12:44:19.102 [planetinfo.record]: cloud_fraction = 0.530000; 12:44:19.102 [planetinfo.record]: land_color = 0.254906, 0.488214, 0.505859, 1; 12:44:19.102 [planetinfo.record]: land_fraction = 0.680000; 12:44:19.102 [planetinfo.record]: polar_cloud_color = 0.868262, 0.490941, 0.632436, 1; 12:44:19.102 [planetinfo.record]: polar_land_color = 0.831665, 0.941135, 0.949414, 1; 12:44:19.102 [planetinfo.record]: polar_sea_color = 0.822891, 0.939065, 0.947852, 1; 12:44:19.102 [planetinfo.record]: sea_color = 0.246483, 0.502148, 0.521484, 1; 12:44:19.102 [planetinfo.record]: rotation_speed = 0.004455 12:44:19.102 [planetinfo.record]: planet zpos = 514220.000000 12:44:19.102 [planetinfo.record]: sun_radius = 92480.732727 12:44:19.102 [planetinfo.record]: sun_vector = -0.792 -0.314 0.523 12:44:19.102 [planetinfo.record]: sun_distance = 881520 12:44:19.102 [planetinfo.record]: corona_flare = 0.000725 12:44:19.102 [planetinfo.record]: corona_hues = 0.966629 12:44:19.102 [planetinfo.record]: sun_color = 0.628351, 0.789164, 0.813095, 1 12:44:19.102 [planetinfo.record]: corona_shimmer = 0.410318 12:44:19.102 [planetinfo.record]: station_vector = 0.694 0.217 0.687 12:44:19.103 [planetinfo.record]: station = coriolis 12:44:23.472 [PLANETINFO OVER]: Done 12:44:23.473 [PLANETINFO LOGGING]: 3 13 12:44:24.476 [planetinfo.record]: seed = 50 127 74 107 12:44:24.476 [planetinfo.record]: coordinates = 127 108 12:44:24.476 [planetinfo.record]: government = 6; 12:44:24.476 [planetinfo.record]: economy = 4; 12:44:24.476 [planetinfo.record]: techlevel = 9; 12:44:24.476 [planetinfo.record]: population = 47; 12:44:24.476 [planetinfo.record]: productivity = 22560; 12:44:24.476 [planetinfo.record]: name = "Mavelege"; 12:44:24.476 [planetinfo.record]: inhabitant = "Human Colonial"; 12:44:24.476 [planetinfo.record]: inhabitants = "Human Colonials"; 12:44:24.476 [planetinfo.record]: description = "This world is plagued by frequent earthquakes."; 12:44:24.481 [planetinfo.record]: air_color = 0.555746, 0.540948, 0.871523, 1; 12:44:24.482 [planetinfo.record]: cloud_alpha = 1.000000; 12:44:24.482 [planetinfo.record]: cloud_color = 0.33387, 0.284485, 0.617188, 1; 12:44:24.482 [planetinfo.record]: cloud_fraction = 0.080000; 12:44:24.482 [planetinfo.record]: land_color = 0.314026, 0.317664, 0.546875, 1; 12:44:24.482 [planetinfo.record]: land_fraction = 0.660000; 12:44:24.482 [planetinfo.record]: polar_cloud_color = 0.5546, 0.515705, 0.777734, 1; 12:44:24.482 [planetinfo.record]: polar_land_color = 0.844688, 0.846261, 0.945312, 1; 12:44:24.482 [planetinfo.record]: polar_sea_color = 0.912691, 0.931641, 0.875233, 1; 12:44:24.482 [planetinfo.record]: sea_color = 0.627977, 0.683594, 0.518036, 1; 12:44:24.482 [planetinfo.record]: rotation_speed = 0.000693 12:44:24.482 [planetinfo.record]: planet zpos = 806260.000000 12:44:24.482 [planetinfo.record]: sun_radius = 141664.756622 12:44:24.482 [planetinfo.record]: sun_vector = -0.157 0.932 -0.327 12:44:24.482 [planetinfo.record]: sun_distance = 1209390 12:44:24.482 [planetinfo.record]: corona_flare = 0.009102 12:44:24.483 [planetinfo.record]: corona_hues = 0.780243 12:44:24.483 [planetinfo.record]: sun_color = 0.762027, 0.827868, 0.83819, 1 12:44:24.483 [planetinfo.record]: corona_shimmer = 0.343023 12:44:24.483 [planetinfo.record]: station_vector = 0.381 -0.900 0.210 12:44:24.483 [planetinfo.record]: station = coriolis 12:44:28.589 [PLANETINFO OVER]: Done 12:44:28.589 [PLANETINFO LOGGING]: 3 14 12:44:29.593 [planetinfo.record]: seed = 146 3 218 155 12:44:29.593 [planetinfo.record]: coordinates = 3 65 12:44:29.593 [planetinfo.record]: government = 2; 12:44:29.593 [planetinfo.record]: economy = 1; 12:44:29.593 [planetinfo.record]: techlevel = 10; 12:44:29.593 [planetinfo.record]: population = 44; 12:44:29.593 [planetinfo.record]: productivity = 19008; 12:44:29.593 [planetinfo.record]: name = "Ante"; 12:44:29.593 [planetinfo.record]: inhabitant = "Black Horned Humanoid"; 12:44:29.593 [planetinfo.record]: inhabitants = "Black Horned Humanoids"; 12:44:29.593 [planetinfo.record]: description = "This planet is mildly noted for its ancient Anteian Enxeleer tulip plantations but cursed by dreadful civil war."; 12:44:29.596 [planetinfo.record]: air_color = 0.678407, 0.58176, 0.871523, 1; 12:44:29.597 [planetinfo.record]: cloud_alpha = 1.000000; 12:44:29.597 [planetinfo.record]: cloud_color = 0.617188, 0.371277, 0.613345, 1; 12:44:29.597 [planetinfo.record]: cloud_fraction = 0.430000; 12:44:29.597 [planetinfo.record]: land_color = 0.541016, 0.347793, 0.0147934, 1; 12:44:29.597 [planetinfo.record]: land_fraction = 0.380000; 12:44:29.597 [planetinfo.record]: polar_cloud_color = 0.777734, 0.58406, 0.774708, 1; 12:44:29.597 [planetinfo.record]: polar_land_color = 0.945898, 0.861442, 0.71589, 1; 12:44:29.597 [planetinfo.record]: polar_sea_color = 0.939844, 0.902766, 0.882664, 1; 12:44:29.597 [planetinfo.record]: sea_color = 0.601562, 0.506634, 0.455167, 1; 12:44:29.597 [planetinfo.record]: rotation_speed = 0.001321 12:44:29.597 [planetinfo.record]: planet zpos = 619850.000000 12:44:29.597 [planetinfo.record]: sun_radius = 176201.229095 12:44:29.597 [planetinfo.record]: sun_vector = 0.963 0.198 -0.185 12:44:29.597 [planetinfo.record]: sun_distance = 1183350 12:44:29.597 [planetinfo.record]: corona_flare = 0.040154 12:44:29.597 [planetinfo.record]: corona_hues = 0.699913 12:44:29.598 [planetinfo.record]: sun_color = 0.385023, 0.445898, 0.781409, 1 12:44:29.598 [planetinfo.record]: corona_shimmer = 0.371608 12:44:29.598 [planetinfo.record]: station_vector = 0.732 0.671 0.120 12:44:29.598 [planetinfo.record]: station = coriolis 12:44:33.982 [PLANETINFO OVER]: Done 12:44:33.982 [PLANETINFO LOGGING]: 3 15 12:44:34.985 [planetinfo.record]: seed = 226 252 234 93 12:44:34.986 [planetinfo.record]: coordinates = 252 128 12:44:34.986 [planetinfo.record]: government = 4; 12:44:34.986 [planetinfo.record]: economy = 0; 12:44:34.986 [planetinfo.record]: techlevel = 9; 12:44:34.986 [planetinfo.record]: population = 41; 12:44:34.986 [planetinfo.record]: productivity = 26240; 12:44:34.986 [planetinfo.record]: name = "Isanla"; 12:44:34.986 [planetinfo.record]: inhabitant = "Yellow Fat Humanoid"; 12:44:34.986 [planetinfo.record]: inhabitants = "Yellow Fat Humanoids"; 12:44:34.986 [planetinfo.record]: description = "The planet Isanla is reasonably fabled for Zero-G cricket and Isanlaian lethal brandy."; 12:44:35.005 [planetinfo.record]: air_color = 0.44527, 0.779791, 0.844857, 1; 12:44:35.005 [planetinfo.record]: cloud_alpha = 1.000000; 12:44:35.005 [planetinfo.record]: cloud_color = 0.316811, 0.537109, 0.0965118, 1; 12:44:35.006 [planetinfo.record]: cloud_fraction = 0.240000; 12:44:35.006 [planetinfo.record]: land_color = 0.609042, 0.66, 0.363516, 1; 12:44:35.006 [planetinfo.record]: land_fraction = 0.660000; 12:44:35.006 [planetinfo.record]: polar_cloud_color = 0.551566, 0.741699, 0.361434, 1; 12:44:35.006 [planetinfo.record]: polar_land_color = 0.915972, 0.934, 0.829107, 1; 12:44:35.006 [planetinfo.record]: polar_sea_color = 0.839958, 0.881875, 0.947266, 1; 12:44:35.006 [planetinfo.record]: sea_color = 0.288391, 0.381732, 0.527344, 1; 12:44:35.006 [planetinfo.record]: rotation_speed = 0.001331 12:44:35.006 [planetinfo.record]: planet zpos = 895440.000000 12:44:35.006 [planetinfo.record]: sun_radius = 204930.432129 12:44:35.006 [planetinfo.record]: sun_vector = 0.273 0.898 0.346 12:44:35.006 [planetinfo.record]: sun_distance = 1087320 12:44:35.006 [planetinfo.record]: corona_flare = 0.030579 12:44:35.006 [planetinfo.record]: corona_hues = 0.537987 12:44:35.007 [planetinfo.record]: sun_color = 0.592979, 0.671135, 0.726892, 1 12:44:35.007 [planetinfo.record]: corona_shimmer = 0.248602 12:44:35.007 [planetinfo.record]: station_vector = -0.615 0.784 0.082 12:44:35.007 [planetinfo.record]: station = coriolis 12:44:39.032 [PLANETINFO OVER]: Done 12:44:39.033 [PLANETINFO LOGGING]: 3 16 12:44:40.036 [planetinfo.record]: seed = 194 110 154 127 12:44:40.037 [planetinfo.record]: coordinates = 110 53 12:44:40.037 [planetinfo.record]: government = 0; 12:44:40.037 [planetinfo.record]: economy = 7; 12:44:40.037 [planetinfo.record]: techlevel = 2; 12:44:40.037 [planetinfo.record]: population = 16; 12:44:40.037 [planetinfo.record]: productivity = 1536; 12:44:40.037 [planetinfo.record]: name = "Onzaenve"; 12:44:40.037 [planetinfo.record]: inhabitant = "Blue Bony Feline"; 12:44:40.037 [planetinfo.record]: inhabitants = "Blue Bony Felines"; 12:44:40.037 [planetinfo.record]: description = "This world is mildly famous for its hoopy casinos and its vast oceans."; 12:44:40.046 [planetinfo.record]: air_color = 0.720451, 0.52393, 0.938514, 1; 12:44:40.047 [planetinfo.record]: cloud_alpha = 1.000000; 12:44:40.047 [planetinfo.record]: cloud_color = 0.818359, 0.22377, 0.558227, 1; 12:44:40.047 [planetinfo.record]: cloud_fraction = 0.530000; 12:44:40.047 [planetinfo.record]: land_color = 0.66, 0.183047, 0.496047, 1; 12:44:40.047 [planetinfo.record]: land_fraction = 0.150000; 12:44:40.047 [planetinfo.record]: polar_cloud_color = 0.868262, 0.473983, 0.695765, 1; 12:44:40.047 [planetinfo.record]: polar_land_color = 0.934, 0.76526, 0.875996, 1; 12:44:40.048 [planetinfo.record]: polar_sea_color = 0.943555, 0.904026, 0.890295, 1; 12:44:40.048 [planetinfo.record]: sea_color = 0.564453, 0.469867, 0.43701, 1; 12:44:40.048 [planetinfo.record]: rotation_speed = 0.000699 12:44:40.048 [planetinfo.record]: planet zpos = 676600.000000 12:44:40.048 [planetinfo.record]: sun_radius = 161980.947266 12:44:40.048 [planetinfo.record]: sun_vector = 0.570 0.740 -0.357 12:44:40.048 [planetinfo.record]: sun_distance = 1285540 12:44:40.048 [planetinfo.record]: corona_flare = 0.029907 12:44:40.048 [planetinfo.record]: corona_hues = 0.811699 12:44:40.048 [planetinfo.record]: sun_color = 0.675153, 0.703692, 0.789026, 1 12:44:40.048 [planetinfo.record]: corona_shimmer = 0.272089 12:44:40.049 [planetinfo.record]: station_vector = -0.558 0.339 0.758 12:44:40.049 [planetinfo.record]: station = coriolis 12:44:45.082 [PLANETINFO OVER]: Done 12:44:45.083 [PLANETINFO LOGGING]: 3 17 12:44:46.099 [planetinfo.record]: seed = 82 182 10 237 12:44:46.099 [planetinfo.record]: coordinates = 182 18 12:44:46.099 [planetinfo.record]: government = 2; 12:44:46.099 [planetinfo.record]: economy = 2; 12:44:46.099 [planetinfo.record]: techlevel = 8; 12:44:46.099 [planetinfo.record]: population = 37; 12:44:46.099 [planetinfo.record]: productivity = 14208; 12:44:46.099 [planetinfo.record]: name = "Dilaor"; 12:44:46.099 [planetinfo.record]: inhabitant = "Human Colonial"; 12:44:46.099 [planetinfo.record]: inhabitants = "Human Colonials"; 12:44:46.099 [planetinfo.record]: description = "This planet is notable for the Dilaorian tree snake and Dilaorian shrew steak."; 12:44:46.121 [planetinfo.record]: air_color = 0.761258, 0.755623, 0.94892, 1; 12:44:46.121 [planetinfo.record]: cloud_alpha = 1.000000; 12:44:46.121 [planetinfo.record]: cloud_color = 0.840664, 0.839653, 0.849609, 1; 12:44:46.121 [planetinfo.record]: cloud_fraction = 0.350000; 12:44:46.121 [planetinfo.record]: land_color = 0.585234, 0.633715, 0.66, 1; 12:44:46.121 [planetinfo.record]: land_fraction = 0.430000; 12:44:46.121 [planetinfo.record]: polar_cloud_color = 0.876518, 0.875862, 0.882324, 1; 12:44:46.122 [planetinfo.record]: polar_land_color = 0.907549, 0.924701, 0.934, 1; 12:44:46.122 [planetinfo.record]: polar_sea_color = 0.941797, 0.920357, 0.890016, 1; 12:44:46.122 [planetinfo.record]: sea_color = 0.582031, 0.529031, 0.45403, 1; 12:44:46.122 [planetinfo.record]: rotation_speed = 0.004510 12:44:46.122 [planetinfo.record]: planet zpos = 569340.000000 12:44:46.122 [planetinfo.record]: sun_radius = 139567.568054 12:44:46.122 [planetinfo.record]: sun_vector = 0.188 0.044 0.981 12:44:46.122 [planetinfo.record]: sun_distance = 1454980 12:44:46.122 [planetinfo.record]: corona_flare = 0.030205 12:44:46.122 [planetinfo.record]: corona_hues = 0.647743 12:44:46.122 [planetinfo.record]: sun_color = 0.722311, 0.66, 0.405848, 1 12:44:46.122 [planetinfo.record]: corona_shimmer = 0.436505 12:44:46.122 [planetinfo.record]: station_vector = -0.635 -0.772 0.039 12:44:46.122 [planetinfo.record]: station = coriolis 12:44:50.722 [PLANETINFO OVER]: Done 12:44:50.722 [PLANETINFO LOGGING]: 3 18 12:44:51.728 [planetinfo.record]: seed = 50 252 90 11 12:44:51.728 [planetinfo.record]: coordinates = 252 89 12:44:51.728 [planetinfo.record]: government = 6; 12:44:51.728 [planetinfo.record]: economy = 1; 12:44:51.728 [planetinfo.record]: techlevel = 9; 12:44:51.728 [planetinfo.record]: population = 44; 12:44:51.728 [planetinfo.record]: productivity = 31680; 12:44:51.728 [planetinfo.record]: name = "Mausra"; 12:44:51.728 [planetinfo.record]: inhabitant = "Human Colonial"; 12:44:51.728 [planetinfo.record]: inhabitants = "Human Colonials"; 12:44:51.728 [planetinfo.record]: description = "Mausra is very fabled for its ancient mountains."; 12:44:51.739 [planetinfo.record]: air_color = 0.414182, 0.798973, 0.869572, 1; 12:44:51.740 [planetinfo.record]: cloud_alpha = 1.000000; 12:44:51.740 [planetinfo.record]: cloud_color = 0.327958, 0.611328, 0.016716, 1; 12:44:51.740 [planetinfo.record]: cloud_fraction = 0.270000; 12:44:51.740 [planetinfo.record]: land_color = 0.36095, 0.303497, 0.597656, 1; 12:44:51.740 [planetinfo.record]: land_fraction = 0.310000; 12:44:51.740 [planetinfo.record]: polar_cloud_color = 0.550546, 0.775098, 0.303908, 1; 12:44:51.740 [planetinfo.record]: polar_land_color = 0.847138, 0.824541, 0.940234, 1; 12:44:51.740 [planetinfo.record]: polar_sea_color = 0.923437, 0.87925, 0.822437, 1; 12:44:51.740 [planetinfo.record]: sea_color = 0.765625, 0.61908, 0.430664, 1; 12:44:51.740 [planetinfo.record]: rotation_speed = 0.002543 12:44:51.740 [planetinfo.record]: planet zpos = 764920.000000 12:44:51.740 [planetinfo.record]: sun_radius = 203110.946655 12:44:51.740 [planetinfo.record]: sun_vector = -0.860 0.497 0.112 12:44:51.740 [planetinfo.record]: sun_distance = 1353320 12:44:51.740 [planetinfo.record]: corona_flare = 0.063011 12:44:51.740 [planetinfo.record]: corona_hues = 0.765419 12:44:51.740 [planetinfo.record]: sun_color = 0.758596, 0.786102, 0.826108, 1 12:44:51.740 [planetinfo.record]: corona_shimmer = 0.969955 12:44:51.740 [planetinfo.record]: station_vector = -0.734 -0.222 0.642 12:44:51.740 [planetinfo.record]: station = coriolis 12:44:56.500 [PLANETINFO OVER]: Done 12:44:56.501 [PLANETINFO LOGGING]: 3 19 12:44:57.505 [planetinfo.record]: seed = 130 212 170 157 12:44:57.505 [planetinfo.record]: coordinates = 212 104 12:44:57.505 [planetinfo.record]: government = 0; 12:44:57.506 [planetinfo.record]: economy = 2; 12:44:57.506 [planetinfo.record]: techlevel = 5; 12:44:57.506 [planetinfo.record]: population = 23; 12:44:57.506 [planetinfo.record]: productivity = 5888; 12:44:57.506 [planetinfo.record]: name = "Isquinza"; 12:44:57.506 [planetinfo.record]: inhabitant = "Black Fat Humanoid"; 12:44:57.506 [planetinfo.record]: inhabitants = "Black Fat Humanoids"; 12:44:57.506 [planetinfo.record]: description = "The world Isquinza is reasonably noted for Zero-G hockey and its inhabitants’ eccentric shyness."; 12:44:57.511 [planetinfo.record]: air_color = 0.641787, 0.750456, 0.996398, 1; 12:44:57.512 [planetinfo.record]: cloud_alpha = 1.000000; 12:44:57.512 [planetinfo.record]: cloud_color = 0.5271, 0.948586, 0.992188, 1; 12:44:57.512 [planetinfo.record]: cloud_fraction = 0.270000; 12:44:57.512 [planetinfo.record]: land_color = 0.210915, 0.551604, 0.568359, 1; 12:44:57.512 [planetinfo.record]: land_fraction = 0.410000; 12:44:57.512 [planetinfo.record]: polar_cloud_color = 0.669194, 0.920488, 0.946484, 1; 12:44:57.512 [planetinfo.record]: polar_land_color = 0.794874, 0.936213, 0.943164, 1; 12:44:57.512 [planetinfo.record]: polar_sea_color = 0.939062, 0.902839, 0.881105, 1; 12:44:57.513 [planetinfo.record]: sea_color = 0.609375, 0.51535, 0.458936, 1; 12:44:57.513 [planetinfo.record]: rotation_speed = 0.000104 12:44:57.513 [planetinfo.record]: planet zpos = 699160.000000 12:44:57.513 [planetinfo.record]: sun_radius = 162544.691162 12:44:57.513 [planetinfo.record]: sun_vector = -0.395 -0.776 0.491 12:44:57.513 [planetinfo.record]: sun_distance = 1271200 12:44:57.513 [planetinfo.record]: corona_flare = 0.096834 12:44:57.513 [planetinfo.record]: corona_hues = 0.896622 12:44:57.513 [planetinfo.record]: sun_color = 0.66937, 0.666278, 0.477126, 1 12:44:57.513 [planetinfo.record]: corona_shimmer = 0.302229 12:44:57.514 [planetinfo.record]: station_vector = -0.603 0.796 0.052 12:44:57.514 [planetinfo.record]: station = coriolis 12:45:02.315 [PLANETINFO OVER]: Done 12:45:02.316 [PLANETINFO LOGGING]: 3 20 12:45:03.332 [planetinfo.record]: seed = 226 196 26 236 12:45:03.332 [planetinfo.record]: coordinates = 196 76 12:45:03.332 [planetinfo.record]: government = 4; 12:45:03.332 [planetinfo.record]: economy = 4; 12:45:03.333 [planetinfo.record]: techlevel = 5; 12:45:03.333 [planetinfo.record]: population = 29; 12:45:03.333 [planetinfo.record]: productivity = 11136; 12:45:03.333 [planetinfo.record]: name = "Inisre"; 12:45:03.333 [planetinfo.record]: inhabitant = "Human Colonial"; 12:45:03.333 [planetinfo.record]: inhabitants = "Human Colonials"; 12:45:03.333 [planetinfo.record]: description = "This planet is beset by dreadful earthquakes."; 12:45:03.346 [planetinfo.record]: air_color = 0.80782, 0.757316, 0.974936, 1; 12:45:03.346 [planetinfo.record]: cloud_alpha = 1.000000; 12:45:03.346 [planetinfo.record]: cloud_color = 0.908878, 0.862503, 0.927734, 1; 12:45:03.346 [planetinfo.record]: cloud_fraction = 0.560000; 12:45:03.346 [planetinfo.record]: land_color = 0.53625, 0.66, 0.601992, 1; 12:45:03.346 [planetinfo.record]: land_fraction = 0.560000; 12:45:03.346 [planetinfo.record]: polar_cloud_color = 0.905826, 0.877162, 0.91748, 1; 12:45:03.346 [planetinfo.record]: polar_land_color = 0.890219, 0.934, 0.913478, 1; 12:45:03.346 [planetinfo.record]: polar_sea_color = 0.930078, 0.894882, 0.857416, 1; 12:45:03.347 [planetinfo.record]: sea_color = 0.699219, 0.59338, 0.480713, 1; 12:45:03.347 [planetinfo.record]: rotation_speed = 0.002012 12:45:03.347 [planetinfo.record]: planet zpos = 669240.000000 12:45:03.347 [planetinfo.record]: sun_radius = 190779.483032 12:45:03.347 [planetinfo.record]: sun_vector = 0.342 0.220 -0.914 12:45:03.347 [planetinfo.record]: sun_distance = 1338480 12:45:03.347 [planetinfo.record]: corona_flare = 0.007568 12:45:03.347 [planetinfo.record]: corona_hues = 0.916206 12:45:03.347 [planetinfo.record]: sun_color = 0.205142, 0.327188, 0.706335, 1 12:45:03.347 [planetinfo.record]: corona_shimmer = 0.293652 12:45:03.347 [planetinfo.record]: station_vector = -0.175 -0.960 0.218 12:45:03.347 [planetinfo.record]: station = coriolis 12:45:07.368 [PLANETINFO OVER]: Done 12:45:07.369 [PLANETINFO LOGGING]: 3 21 12:45:08.389 [planetinfo.record]: seed = 114 152 202 68 12:45:08.390 [planetinfo.record]: coordinates = 152 174 12:45:08.390 [planetinfo.record]: government = 6; 12:45:08.390 [planetinfo.record]: economy = 6; 12:45:08.390 [planetinfo.record]: techlevel = 4; 12:45:08.390 [planetinfo.record]: population = 29; 12:45:08.390 [planetinfo.record]: productivity = 9280; 12:45:08.390 [planetinfo.record]: name = "Zamaes"; 12:45:08.390 [planetinfo.record]: inhabitant = "Fierce Yellow Feline"; 12:45:08.390 [planetinfo.record]: inhabitants = "Fierce Yellow Felines"; 12:45:08.390 [planetinfo.record]: description = "This world is plagued by frequent earthquakes."; 12:45:08.408 [planetinfo.record]: air_color = 0.597445, 0.541763, 0.885832, 1; 12:45:08.408 [planetinfo.record]: cloud_alpha = 1.000000; 12:45:08.408 [planetinfo.record]: cloud_color = 0.473198, 0.28624, 0.660156, 1; 12:45:08.408 [planetinfo.record]: cloud_fraction = 0.400000; 12:45:08.408 [planetinfo.record]: land_color = 0.226944, 0.535562, 0.542969, 1; 12:45:08.408 [planetinfo.record]: land_fraction = 0.530000; 12:45:08.408 [planetinfo.record]: polar_cloud_color = 0.655987, 0.514904, 0.79707, 1; 12:45:08.408 [planetinfo.record]: polar_land_color = 0.808096, 0.942478, 0.945703, 1; 12:45:08.408 [planetinfo.record]: polar_sea_color = 0.881928, 0.929492, 0.898278, 1; 12:45:08.408 [planetinfo.record]: sea_color = 0.560757, 0.705078, 0.610368, 1; 12:45:08.408 [planetinfo.record]: rotation_speed = 0.003179 12:45:08.408 [planetinfo.record]: planet zpos = 439120.000000 12:45:08.408 [planetinfo.record]: sun_radius = 128641.127930 12:45:08.408 [planetinfo.record]: sun_vector = -0.136 -0.053 -0.989 12:45:08.408 [planetinfo.record]: sun_distance = 718560 12:45:08.408 [planetinfo.record]: corona_flare = 0.090361 12:45:08.408 [planetinfo.record]: corona_hues = 0.925827 12:45:08.409 [planetinfo.record]: sun_color = 0.523883, 0.55393, 0.734247, 1 12:45:08.409 [planetinfo.record]: corona_shimmer = 0.787352 12:45:08.409 [planetinfo.record]: station_vector = -0.567 0.298 0.768 12:45:08.409 [planetinfo.record]: station = coriolis 12:45:12.909 [PLANETINFO OVER]: Done 12:45:12.910 [PLANETINFO LOGGING]: 3 22 12:45:13.912 [planetinfo.record]: seed = 210 57 218 46 12:45:13.912 [planetinfo.record]: coordinates = 57 105 12:45:13.912 [planetinfo.record]: government = 2; 12:45:13.912 [planetinfo.record]: economy = 1; 12:45:13.912 [planetinfo.record]: techlevel = 8; 12:45:13.912 [planetinfo.record]: population = 36; 12:45:13.912 [planetinfo.record]: productivity = 15552; 12:45:13.912 [planetinfo.record]: name = "Reatqu"; 12:45:13.912 [planetinfo.record]: inhabitant = "Red Slimy Lizard"; 12:45:13.912 [planetinfo.record]: inhabitants = "Red Slimy Lizards"; 12:45:13.912 [planetinfo.record]: description = "The world Reatqu is very famous for its fabulous monkey burgers but beset by deadly solar activity."; 12:45:13.915 [planetinfo.record]: air_color = 0.797368, 0.745975, 0.983391, 1; 12:45:13.915 [planetinfo.record]: cloud_alpha = 1.000000; 12:45:13.915 [planetinfo.record]: cloud_color = 0.913101, 0.833984, 0.953125, 1; 12:45:13.915 [planetinfo.record]: cloud_fraction = 0.580000; 12:45:13.916 [planetinfo.record]: land_color = 0.621328, 0.66, 0.644592, 1; 12:45:13.916 [planetinfo.record]: land_fraction = 0.320000; 12:45:13.916 [planetinfo.record]: polar_cloud_color = 0.904527, 0.856335, 0.928906, 1; 12:45:13.916 [planetinfo.record]: polar_land_color = 0.920318, 0.934, 0.928549, 1; 12:45:13.916 [planetinfo.record]: polar_sea_color = 0.918749, 0.932422, 0.875967, 1; 12:45:13.916 [planetinfo.record]: sea_color = 0.636143, 0.675781, 0.512115, 1; 12:45:13.916 [planetinfo.record]: rotation_speed = 0.003780 12:45:13.916 [planetinfo.record]: planet zpos = 903980.000000 12:45:13.916 [planetinfo.record]: sun_radius = 124168.377991 12:45:13.916 [planetinfo.record]: sun_vector = 0.573 -0.671 0.469 12:45:13.916 [planetinfo.record]: sun_distance = 1162260 12:45:13.916 [planetinfo.record]: corona_flare = 0.088316 12:45:13.916 [planetinfo.record]: corona_hues = 0.913956 12:45:13.916 [planetinfo.record]: sun_color = 0.829657, 0.775381, 0.446689, 1 12:45:13.916 [planetinfo.record]: corona_shimmer = 0.437286 12:45:13.916 [planetinfo.record]: station_vector = 0.920 -0.135 0.367 12:45:13.916 [planetinfo.record]: station = coriolis 12:45:19.077 [PLANETINFO OVER]: Done 12:45:19.077 [PLANETINFO LOGGING]: 3 23 12:45:20.081 [planetinfo.record]: seed = 34 59 106 71 12:45:20.081 [planetinfo.record]: coordinates = 59 58 12:45:20.081 [planetinfo.record]: government = 4; 12:45:20.081 [planetinfo.record]: economy = 2; 12:45:20.081 [planetinfo.record]: techlevel = 10; 12:45:20.081 [planetinfo.record]: population = 47; 12:45:20.081 [planetinfo.record]: productivity = 24064; 12:45:20.081 [planetinfo.record]: name = "Soisonza"; 12:45:20.081 [planetinfo.record]: inhabitant = "Human Colonial"; 12:45:20.081 [planetinfo.record]: inhabitants = "Human Colonials"; 12:45:20.081 [planetinfo.record]: description = "The world Soisonza is mildly noted for the Soisonzaian mountain lobstoid but beset by deadly earthquakes."; 12:45:20.112 [planetinfo.record]: air_color = 0.522158, 0.528482, 0.879979, 1; 12:45:20.112 [planetinfo.record]: cloud_alpha = 1.000000; 12:45:20.112 [planetinfo.record]: cloud_color = 0.243477, 0.259067, 0.642578, 1; 12:45:20.112 [planetinfo.record]: cloud_fraction = 0.400000; 12:45:20.112 [planetinfo.record]: land_color = 0.66, 0.286172, 0.584066, 1; 12:45:20.112 [planetinfo.record]: land_fraction = 0.580000; 12:45:20.112 [planetinfo.record]: polar_cloud_color = 0.482821, 0.494787, 0.78916, 1; 12:45:20.112 [planetinfo.record]: polar_land_color = 0.934, 0.801744, 0.907136, 1; 12:45:20.112 [planetinfo.record]: polar_sea_color = 0.927148, 0.827906, 0.823025, 1; 12:45:20.112 [planetinfo.record]: sea_color = 0.728516, 0.416593, 0.401253, 1; 12:45:20.112 [planetinfo.record]: rotation_speed = 0.003791 12:45:20.112 [planetinfo.record]: planet zpos = 466700.000000 12:45:20.112 [planetinfo.record]: sun_radius = 144265.675049 12:45:20.112 [planetinfo.record]: sun_vector = -0.169 -0.598 -0.784 12:45:20.112 [planetinfo.record]: sun_distance = 1120080 12:45:20.112 [planetinfo.record]: corona_flare = 0.095474 12:45:20.112 [planetinfo.record]: corona_hues = 0.832558 12:45:20.112 [planetinfo.record]: sun_color = 0.665775, 0.607853, 0.500042, 1 12:45:20.113 [planetinfo.record]: corona_shimmer = 0.270510 12:45:20.113 [planetinfo.record]: station_vector = -0.979 -0.174 0.103 12:45:20.113 [planetinfo.record]: station = coriolis 12:45:24.997 [PLANETINFO OVER]: Done 12:45:24.998 [PLANETINFO LOGGING]: 3 24 12:45:26.002 [planetinfo.record]: seed = 2 68 154 64 12:45:26.002 [planetinfo.record]: coordinates = 68 63 12:45:26.002 [planetinfo.record]: government = 0; 12:45:26.002 [planetinfo.record]: economy = 7; 12:45:26.002 [planetinfo.record]: techlevel = 0; 12:45:26.002 [planetinfo.record]: population = 8; 12:45:26.002 [planetinfo.record]: productivity = 768; 12:45:26.002 [planetinfo.record]: name = "Zaus"; 12:45:26.002 [planetinfo.record]: inhabitant = "Large Yellow Bony Lobster"; 12:45:26.002 [planetinfo.record]: inhabitants = "Large Yellow Bony Lobsters"; 12:45:26.002 [planetinfo.record]: description = "The world Zaus is mildly fabled for the Zausian edible moth but beset by lethal disease."; 12:45:26.020 [planetinfo.record]: air_color = 0.662109, 0.848109, 0.797103, 1; 12:45:26.020 [planetinfo.record]: cloud_alpha = 1.000000; 12:45:26.020 [planetinfo.record]: cloud_color = 0.546875, 0.52084, 0.514832, 1; 12:45:26.020 [planetinfo.record]: cloud_fraction = 0.420000; 12:45:26.020 [planetinfo.record]: land_color = 0.118589, 0.585938, 0.0915527, 1; 12:45:26.020 [planetinfo.record]: land_fraction = 0.600000; 12:45:26.020 [planetinfo.record]: polar_cloud_color = 0.746094, 0.723894, 0.718771, 1; 12:45:26.020 [planetinfo.record]: polar_land_color = 0.753688, 0.941406, 0.742828, 1; 12:45:26.020 [planetinfo.record]: polar_sea_color = 0.939453, 0.886079, 0.878994, 1; 12:45:26.020 [planetinfo.record]: sea_color = 0.605469, 0.467873, 0.449608, 1; 12:45:26.020 [planetinfo.record]: rotation_speed = 0.003164 12:45:26.020 [planetinfo.record]: planet zpos = 346080.000000 12:45:26.020 [planetinfo.record]: sun_radius = 63897.656860 12:45:26.020 [planetinfo.record]: sun_vector = -0.441 0.219 0.871 12:45:26.020 [planetinfo.record]: sun_distance = 547960 12:45:26.020 [planetinfo.record]: corona_flare = 0.063641 12:45:26.020 [planetinfo.record]: corona_hues = 0.661644 12:45:26.020 [planetinfo.record]: sun_color = 0.702069, 0.601602, 0.326559, 1 12:45:26.020 [planetinfo.record]: corona_shimmer = 0.455155 12:45:26.021 [planetinfo.record]: station_vector = -0.098 0.824 0.558 12:45:26.021 [planetinfo.record]: station = coriolis 12:45:30.710 [PLANETINFO OVER]: Done 12:45:30.711 [PLANETINFO LOGGING]: 3 25 12:45:31.715 [planetinfo.record]: seed = 146 77 138 90 12:45:31.715 [planetinfo.record]: coordinates = 77 72 12:45:31.715 [planetinfo.record]: government = 2; 12:45:31.715 [planetinfo.record]: economy = 0; 12:45:31.715 [planetinfo.record]: techlevel = 9; 12:45:31.715 [planetinfo.record]: population = 39; 12:45:31.715 [planetinfo.record]: productivity = 18720; 12:45:31.715 [planetinfo.record]: name = "Querorza"; 12:45:31.715 [planetinfo.record]: inhabitant = "Yellow Furry Insect"; 12:45:31.715 [planetinfo.record]: inhabitants = "Yellow Furry Insects"; 12:45:31.715 [planetinfo.record]: description = "This planet is a tedious place."; 12:45:31.717 [planetinfo.record]: air_color = 0.646554, 0.59105, 0.847459, 1; 12:45:31.717 [planetinfo.record]: cloud_alpha = 1.000000; 12:45:31.717 [planetinfo.record]: cloud_color = 0.488431, 0.376762, 0.544922, 1; 12:45:31.717 [planetinfo.record]: cloud_fraction = 0.380000; 12:45:31.717 [planetinfo.record]: land_color = 0.400254, 0.020625, 0.66, 1; 12:45:31.717 [planetinfo.record]: land_fraction = 0.470000; 12:45:31.717 [planetinfo.record]: polar_cloud_color = 0.69693, 0.601484, 0.745215, 1; 12:45:31.717 [planetinfo.record]: polar_land_color = 0.842105, 0.707797, 0.934, 1; 12:45:31.717 [planetinfo.record]: polar_sea_color = 0.903011, 0.935547, 0.888221, 1; 12:45:31.717 [planetinfo.record]: sea_color = 0.55487, 0.644531, 0.514114, 1; 12:45:31.717 [planetinfo.record]: rotation_speed = 0.001927 12:45:31.717 [planetinfo.record]: planet zpos = 599830.000000 12:45:31.717 [planetinfo.record]: sun_radius = 116110.059357 12:45:31.718 [planetinfo.record]: sun_vector = -0.242 -0.263 0.934 12:45:31.718 [planetinfo.record]: sun_distance = 1254190 12:45:31.718 [planetinfo.record]: corona_flare = 0.005630 12:45:31.718 [planetinfo.record]: corona_hues = 0.872925 12:45:31.718 [planetinfo.record]: sun_color = 0.694556, 0.694806, 0.601766, 1 12:45:31.718 [planetinfo.record]: corona_shimmer = 0.508994 12:45:31.718 [planetinfo.record]: station_vector = -0.019 -0.366 0.930 12:45:31.718 [planetinfo.record]: station = coriolis 12:45:35.464 [PLANETINFO OVER]: Done 12:45:35.465 [PLANETINFO LOGGING]: 3 26 12:45:36.469 [planetinfo.record]: seed = 114 228 90 110 12:45:36.469 [planetinfo.record]: coordinates = 228 153 12:45:36.469 [planetinfo.record]: government = 6; 12:45:36.469 [planetinfo.record]: economy = 1; 12:45:36.469 [planetinfo.record]: techlevel = 9; 12:45:36.469 [planetinfo.record]: population = 44; 12:45:36.469 [planetinfo.record]: productivity = 31680; 12:45:36.469 [planetinfo.record]: name = "Remari"; 12:45:36.469 [planetinfo.record]: inhabitant = "Human Colonial"; 12:45:36.469 [planetinfo.record]: inhabitants = "Human Colonials"; 12:45:36.469 [planetinfo.record]: description = "The planet Remari is most famous for the Remariian evil Ernolooid and its inhabitants’ exceptional loathing of food blenders."; 12:45:36.492 [planetinfo.record]: air_color = 0.671782, 0.842256, 0.800829, 1; 12:45:36.492 [planetinfo.record]: cloud_alpha = 1.000000; 12:45:36.492 [planetinfo.record]: cloud_color = 0.529297, 0.526325, 0.525162, 1; 12:45:36.492 [planetinfo.record]: cloud_fraction = 0.180000; 12:45:36.492 [planetinfo.record]: land_color = 0.66, 0.12375, 0.639053, 1; 12:45:36.492 [planetinfo.record]: land_fraction = 0.590000; 12:45:36.492 [planetinfo.record]: polar_cloud_color = 0.738184, 0.735593, 0.734579, 1; 12:45:36.492 [planetinfo.record]: polar_land_color = 0.934, 0.744281, 0.926589, 1; 12:45:36.492 [planetinfo.record]: polar_sea_color = 0.939258, 0.880596, 0.877711, 1; 12:45:36.492 [planetinfo.record]: sea_color = 0.607422, 0.455674, 0.448211, 1; 12:45:36.492 [planetinfo.record]: rotation_speed = 0.000084 12:45:36.492 [planetinfo.record]: planet zpos = 861640.000000 12:45:36.492 [planetinfo.record]: sun_radius = 142189.087524 12:45:36.492 [planetinfo.record]: sun_vector = 0.452 -0.885 0.110 12:45:36.492 [planetinfo.record]: sun_distance = 1060480 12:45:36.492 [planetinfo.record]: corona_flare = 0.037831 12:45:36.492 [planetinfo.record]: corona_hues = 0.906227 12:45:36.492 [planetinfo.record]: sun_color = 0.655988, 0.635309, 0.632625, 1 12:45:36.493 [planetinfo.record]: corona_shimmer = 0.411028 12:45:36.493 [planetinfo.record]: station_vector = -0.164 -0.981 0.101 12:45:36.493 [planetinfo.record]: station = coriolis 12:45:41.400 [PLANETINFO OVER]: Done 12:45:41.400 [PLANETINFO LOGGING]: 3 27 12:45:42.405 [planetinfo.record]: seed = 194 152 42 195 12:45:42.405 [planetinfo.record]: coordinates = 152 62 12:45:42.405 [planetinfo.record]: government = 0; 12:45:42.405 [planetinfo.record]: economy = 6; 12:45:42.405 [planetinfo.record]: techlevel = 1; 12:45:42.405 [planetinfo.record]: population = 11; 12:45:42.405 [planetinfo.record]: productivity = 1408; 12:45:42.405 [planetinfo.record]: name = "Gequve"; 12:45:42.405 [planetinfo.record]: inhabitant = "Human Colonial"; 12:45:42.405 [planetinfo.record]: inhabitants = "Human Colonials"; 12:45:42.405 [planetinfo.record]: description = "The planet Gequve is a boring world."; 12:45:42.410 [planetinfo.record]: air_color = 0.743482, 0.763807, 0.939814, 1; 12:45:42.410 [planetinfo.record]: cloud_alpha = 1.000000; 12:45:42.411 [planetinfo.record]: cloud_color = 0.799782, 0.806457, 0.822266, 1; 12:45:42.411 [planetinfo.record]: cloud_fraction = 0.370000; 12:45:42.411 [planetinfo.record]: land_color = 0.66, 0.386719, 0.489199, 1; 12:45:42.411 [planetinfo.record]: land_fraction = 0.670000; 12:45:42.411 [planetinfo.record]: polar_cloud_color = 0.855151, 0.859565, 0.87002, 1; 12:45:42.411 [planetinfo.record]: polar_land_color = 0.934, 0.837316, 0.873573, 1; 12:45:42.411 [planetinfo.record]: polar_sea_color = 0.930664, 0.860768, 0.84705, 1; 12:45:42.411 [planetinfo.record]: sea_color = 0.693359, 0.485064, 0.444183, 1; 12:45:42.411 [planetinfo.record]: rotation_speed = 0.002612 12:45:42.411 [planetinfo.record]: planet zpos = 410960.000000 12:45:42.411 [planetinfo.record]: sun_radius = 86610.485840 12:45:42.411 [planetinfo.record]: sun_vector = -0.661 0.577 -0.480 12:45:42.411 [planetinfo.record]: sun_distance = 784560 12:45:42.411 [planetinfo.record]: corona_flare = 0.063727 12:45:42.411 [planetinfo.record]: corona_hues = 0.703316 12:45:42.411 [planetinfo.record]: sun_color = 0.220734, 0.293797, 0.759183, 1 12:45:42.412 [planetinfo.record]: corona_shimmer = 0.579483 12:45:42.412 [planetinfo.record]: station_vector = -0.454 0.685 0.570 12:45:42.412 [planetinfo.record]: station = coriolis 12:45:47.266 [PLANETINFO OVER]: Done 12:45:47.267 [PLANETINFO LOGGING]: 3 28 12:45:48.271 [planetinfo.record]: seed = 34 84 26 229 12:45:48.271 [planetinfo.record]: coordinates = 84 246 12:45:48.271 [planetinfo.record]: government = 4; 12:45:48.271 [planetinfo.record]: economy = 6; 12:45:48.271 [planetinfo.record]: techlevel = 3; 12:45:48.271 [planetinfo.record]: population = 23; 12:45:48.271 [planetinfo.record]: productivity = 5888; 12:45:48.271 [planetinfo.record]: name = "Ceausis"; 12:45:48.271 [planetinfo.record]: inhabitant = "Human Colonial"; 12:45:48.271 [planetinfo.record]: inhabitants = "Human Colonials"; 12:45:48.271 [planetinfo.record]: description = "The planet Ceausis is cursed by killer edible talking treeoids."; 12:45:48.280 [planetinfo.record]: air_color = 0.739733, 0.774645, 0.93201, 1; 12:45:48.280 [planetinfo.record]: cloud_alpha = 1.000000; 12:45:48.280 [planetinfo.record]: cloud_color = 0.783226, 0.791515, 0.798828, 1; 12:45:48.280 [planetinfo.record]: cloud_fraction = 0.360000; 12:45:48.280 [planetinfo.record]: land_color = 0.438684, 0.237188, 0.66, 1; 12:45:48.280 [planetinfo.record]: land_fraction = 0.330000; 12:45:48.280 [planetinfo.record]: polar_cloud_color = 0.848981, 0.854555, 0.859473, 1; 12:45:48.280 [planetinfo.record]: polar_land_color = 0.855701, 0.784414, 0.934, 1; 12:45:48.280 [planetinfo.record]: polar_sea_color = 0.917722, 0.934375, 0.881086, 1; 12:45:48.280 [planetinfo.record]: sea_color = 0.609467, 0.65625, 0.506543, 1; 12:45:48.280 [planetinfo.record]: rotation_speed = 0.004492 12:45:48.280 [planetinfo.record]: planet zpos = 459800.000000 12:45:48.280 [planetinfo.record]: sun_radius = 81965.911865 12:45:48.280 [planetinfo.record]: sun_vector = -0.436 -0.770 0.467 12:45:48.280 [planetinfo.record]: sun_distance = 836000 12:45:48.280 [planetinfo.record]: corona_flare = 0.087305 12:45:48.280 [planetinfo.record]: corona_hues = 0.862679 12:45:48.281 [planetinfo.record]: sun_color = 0.72865, 0.6855, 0.549375, 1 12:45:48.281 [planetinfo.record]: corona_shimmer = 0.397462 12:45:48.288 [planetinfo.record]: station_vector = 0.749 0.324 0.578 12:45:48.288 [planetinfo.record]: station = coriolis 12:45:52.712 [PLANETINFO OVER]: Done 12:45:52.713 [PLANETINFO LOGGING]: 3 29 12:45:53.714 [planetinfo.record]: seed = 178 125 74 22 12:45:53.715 [planetinfo.record]: coordinates = 125 104 12:45:53.715 [planetinfo.record]: government = 6; 12:45:53.715 [planetinfo.record]: economy = 0; 12:45:53.715 [planetinfo.record]: techlevel = 11; 12:45:53.715 [planetinfo.record]: population = 51; 12:45:53.715 [planetinfo.record]: productivity = 40800; 12:45:53.715 [planetinfo.record]: name = "Veteerza"; 12:45:53.715 [planetinfo.record]: inhabitant = "Human Colonial"; 12:45:53.715 [planetinfo.record]: inhabitants = "Human Colonials"; 12:45:53.715 [planetinfo.record]: description = "The world Veteerza is reasonably famous for the Veteerzaian evil Usa’geoid."; 12:45:53.728 [planetinfo.record]: air_color = 0.68708, 0.861425, 0.872824, 1; 12:45:53.728 [planetinfo.record]: cloud_alpha = 1.000000; 12:45:53.728 [planetinfo.record]: cloud_color = 0.616355, 0.621094, 0.596832, 1; 12:45:53.728 [planetinfo.record]: cloud_fraction = 0.370000; 12:45:53.728 [planetinfo.record]: land_color = 0.579879, 0.321075, 0.652344, 1; 12:45:53.728 [planetinfo.record]: land_fraction = 0.270000; 12:45:53.728 [planetinfo.record]: polar_cloud_color = 0.775775, 0.779492, 0.760462, 1; 12:45:53.728 [planetinfo.record]: polar_land_color = 0.908806, 0.816094, 0.934766, 1; 12:45:53.728 [planetinfo.record]: polar_sea_color = 0.947266, 0.900394, 0.89685, 1; 12:45:53.728 [planetinfo.record]: sea_color = 0.527344, 0.422971, 0.415077, 1; 12:45:53.728 [planetinfo.record]: rotation_speed = 0.000653 12:45:53.728 [planetinfo.record]: planet zpos = 402930.000000 12:45:53.728 [planetinfo.record]: sun_radius = 135398.235626 12:45:53.728 [planetinfo.record]: sun_vector = -0.232 -0.364 -0.902 12:45:53.728 [planetinfo.record]: sun_distance = 761090 12:45:53.728 [planetinfo.record]: corona_flare = 0.022401 12:45:53.728 [planetinfo.record]: corona_hues = 0.524986 12:45:53.728 [planetinfo.record]: sun_color = 0.69744, 0.626273, 0.559973, 1 12:45:53.729 [planetinfo.record]: corona_shimmer = 0.983817 12:45:53.729 [planetinfo.record]: station_vector = -0.887 -0.405 0.220 12:45:53.729 [planetinfo.record]: station = dodecahedron 12:45:58.531 [PLANETINFO OVER]: Done 12:45:58.532 [PLANETINFO LOGGING]: 3 30 12:45:59.535 [planetinfo.record]: seed = 18 164 218 49 12:45:59.535 [planetinfo.record]: coordinates = 164 144 12:45:59.535 [planetinfo.record]: government = 2; 12:45:59.535 [planetinfo.record]: economy = 0; 12:45:59.535 [planetinfo.record]: techlevel = 8; 12:45:59.535 [planetinfo.record]: population = 35; 12:45:59.535 [planetinfo.record]: productivity = 16800; 12:45:59.535 [planetinfo.record]: name = "Atbitela"; 12:45:59.535 [planetinfo.record]: inhabitant = "Red Fat Humanoid"; 12:45:59.535 [planetinfo.record]: inhabitants = "Red Fat Humanoids"; 12:45:59.535 [planetinfo.record]: description = "This world is plagued by frequent earthquakes."; 12:45:59.541 [planetinfo.record]: air_color = 0.441941, 0.618366, 0.900791, 1; 12:45:59.541 [planetinfo.record]: cloud_alpha = 1.000000; 12:45:59.541 [planetinfo.record]: cloud_color = 0.0495758, 0.705078, 0.612898, 1; 12:45:59.541 [planetinfo.record]: cloud_fraction = 0.230000; 12:45:59.541 [planetinfo.record]: land_color = 0.299183, 0.224297, 0.66, 1; 12:45:59.541 [planetinfo.record]: land_fraction = 0.450000; 12:45:59.541 [planetinfo.record]: polar_cloud_color = 0.342398, 0.817285, 0.750504, 1; 12:45:59.541 [planetinfo.record]: polar_land_color = 0.806347, 0.779854, 0.934, 1; 12:45:59.541 [planetinfo.record]: polar_sea_color = 0.930078, 0.8523, 0.841975, 1; 12:45:59.541 [planetinfo.record]: sea_color = 0.699219, 0.465328, 0.43428, 1; 12:45:59.541 [planetinfo.record]: rotation_speed = 0.001297 12:45:59.541 [planetinfo.record]: planet zpos = 453040.000000 12:45:59.541 [planetinfo.record]: sun_radius = 84259.147339 12:45:59.541 [planetinfo.record]: sun_vector = 0.080 0.962 0.262 12:45:59.541 [planetinfo.record]: sun_distance = 744280 12:45:59.541 [planetinfo.record]: corona_flare = 0.000798 12:45:59.541 [planetinfo.record]: corona_hues = 0.850090 12:45:59.542 [planetinfo.record]: sun_color = 0.832053, 0.215295, 0.214031, 1 12:45:59.542 [planetinfo.record]: corona_shimmer = 0.254885 12:45:59.542 [planetinfo.record]: station_vector = 0.991 -0.034 0.128 12:45:59.542 [planetinfo.record]: station = coriolis 12:46:03.557 [PLANETINFO OVER]: Done 12:46:03.558 [PLANETINFO LOGGING]: 3 31 12:46:04.560 [planetinfo.record]: seed = 98 213 234 120 12:46:04.560 [planetinfo.record]: coordinates = 213 60 12:46:04.560 [planetinfo.record]: government = 4; 12:46:04.560 [planetinfo.record]: economy = 4; 12:46:04.561 [planetinfo.record]: techlevel = 6; 12:46:04.561 [planetinfo.record]: population = 33; 12:46:04.561 [planetinfo.record]: productivity = 12672; 12:46:04.561 [planetinfo.record]: name = "Edmaor"; 12:46:04.561 [planetinfo.record]: inhabitant = "Blue Bug-Eyed Frog"; 12:46:04.561 [planetinfo.record]: inhabitants = "Blue Bug-Eyed Frogs"; 12:46:04.561 [planetinfo.record]: description = "The planet Edmaor is most well known for its vast dense forests."; 12:46:04.580 [planetinfo.record]: air_color = 0.65074, 0.897814, 0.89949, 1; 12:46:04.580 [planetinfo.record]: cloud_alpha = 1.000000; 12:46:04.580 [planetinfo.record]: cloud_color = 0.696122, 0.701172, 0.539574, 1; 12:46:04.580 [planetinfo.record]: cloud_fraction = 0.250000; 12:46:04.580 [planetinfo.record]: land_color = 0.66, 0.139219, 0.249071, 1; 12:46:04.580 [planetinfo.record]: land_fraction = 0.540000; 12:46:04.580 [planetinfo.record]: polar_cloud_color = 0.811856, 0.815527, 0.698056, 1; 12:46:04.580 [planetinfo.record]: polar_land_color = 0.934, 0.749754, 0.788618, 1; 12:46:04.580 [planetinfo.record]: polar_sea_color = 0.942383, 0.937229, 0.895264, 1; 12:46:04.580 [planetinfo.record]: sea_color = 0.576172, 0.563568, 0.460938, 1; 12:46:04.580 [planetinfo.record]: rotation_speed = 0.001337 12:46:04.580 [planetinfo.record]: planet zpos = 660010.000000 12:46:04.581 [planetinfo.record]: sun_radius = 137518.093567 12:46:04.581 [planetinfo.record]: sun_vector = 0.626 0.533 0.570 12:46:04.581 [planetinfo.record]: sun_distance = 1015400 12:46:04.581 [planetinfo.record]: corona_flare = 0.058949 12:46:04.581 [planetinfo.record]: corona_hues = 0.691307 12:46:04.581 [planetinfo.record]: sun_color = 0.368213, 0.527551, 0.747623, 1 12:46:04.581 [planetinfo.record]: corona_shimmer = 0.882687 12:46:04.581 [planetinfo.record]: station_vector = -0.945 -0.318 0.075 12:46:04.581 [planetinfo.record]: station = coriolis 12:46:08.557 [PLANETINFO OVER]: Done 12:46:08.558 [PLANETINFO LOGGING]: 3 32 12:46:09.562 [planetinfo.record]: seed = 66 221 154 65 12:46:09.562 [planetinfo.record]: coordinates = 221 217 12:46:09.562 [planetinfo.record]: government = 0; 12:46:09.562 [planetinfo.record]: economy = 3; 12:46:09.562 [planetinfo.record]: techlevel = 5; 12:46:09.562 [planetinfo.record]: population = 24; 12:46:09.562 [planetinfo.record]: productivity = 5376; 12:46:09.562 [planetinfo.record]: name = "Leedtier"; 12:46:09.562 [planetinfo.record]: inhabitant = "Large Yellow Fat Humanoid"; 12:46:09.562 [planetinfo.record]: inhabitants = "Large Yellow Fat Humanoids"; 12:46:09.562 [planetinfo.record]: description = "Leedtier is an unremarkable planet."; 12:46:09.606 [planetinfo.record]: air_color = 0.645802, 0.902302, 0.904043, 1; 12:46:09.608 [planetinfo.record]: cloud_alpha = 1.000000; 12:46:09.609 [planetinfo.record]: cloud_color = 0.709085, 0.714844, 0.530548, 1; 12:46:09.609 [planetinfo.record]: cloud_fraction = 0.180000; 12:46:09.609 [planetinfo.record]: land_color = 0.358359, 0.66, 0.41256, 1; 12:46:09.609 [planetinfo.record]: land_fraction = 0.550000; 12:46:09.609 [planetinfo.record]: polar_cloud_color = 0.817542, 0.82168, 0.68928, 1; 12:46:09.609 [planetinfo.record]: polar_land_color = 0.827283, 0.934, 0.846459, 1; 12:46:09.609 [planetinfo.record]: polar_sea_color = 0.891212, 0.929883, 0.875488, 1; 12:46:09.609 [planetinfo.record]: sea_color = 0.584533, 0.701172, 0.537109, 1; 12:46:09.609 [planetinfo.record]: rotation_speed = 0.000688 12:46:09.609 [planetinfo.record]: planet zpos = 329300.000000 12:46:09.615 [planetinfo.record]: sun_radius = 94479.293365 12:46:09.616 [planetinfo.record]: sun_vector = 0.341 -0.734 0.588 12:46:09.616 [planetinfo.record]: sun_distance = 559810 12:46:09.616 [planetinfo.record]: corona_flare = 0.057260 12:46:09.616 [planetinfo.record]: corona_hues = 0.759300 12:46:09.616 [planetinfo.record]: sun_color = 0.701169, 0.624604, 0.399185, 1 12:46:09.616 [planetinfo.record]: corona_shimmer = 0.277978 12:46:09.617 [planetinfo.record]: station_vector = -0.824 0.362 0.436 12:46:09.617 [planetinfo.record]: station = coriolis 12:46:14.460 [PLANETINFO OVER]: Done 12:46:14.461 [PLANETINFO LOGGING]: 3 33 12:46:15.464 [planetinfo.record]: seed = 210 80 10 96 12:46:15.464 [planetinfo.record]: coordinates = 80 23 12:46:15.464 [planetinfo.record]: government = 2; 12:46:15.464 [planetinfo.record]: economy = 7; 12:46:15.464 [planetinfo.record]: techlevel = 1; 12:46:15.464 [planetinfo.record]: population = 14; 12:46:15.464 [planetinfo.record]: productivity = 2016; 12:46:15.464 [planetinfo.record]: name = "Soed"; 12:46:15.464 [planetinfo.record]: inhabitant = "Human Colonial"; 12:46:15.464 [planetinfo.record]: inhabitants = "Human Colonials"; 12:46:15.464 [planetinfo.record]: description = "This planet is reasonably famous for the Soedian evil talking treeoid."; 12:46:15.466 [planetinfo.record]: air_color = 0.593835, 0.9164, 0.835496, 1; 12:46:15.466 [planetinfo.record]: cloud_alpha = 1.000000; 12:46:15.466 [planetinfo.record]: cloud_color = 0.751953, 0.496888, 0.408287, 1; 12:46:15.466 [planetinfo.record]: cloud_fraction = 0.280000; 12:46:15.466 [planetinfo.record]: land_color = 0.564453, 0.401291, 0.55808, 1; 12:46:15.466 [planetinfo.record]: land_fraction = 0.560000; 12:46:15.466 [planetinfo.record]: polar_cloud_color = 0.838379, 0.660641, 0.598901, 1; 12:46:15.467 [planetinfo.record]: polar_land_color = 0.943555, 0.875368, 0.940891, 1; 12:46:15.467 [planetinfo.record]: polar_sea_color = 0.925195, 0.86826, 0.826713, 1; 12:46:15.467 [planetinfo.record]: sea_color = 0.748047, 0.563912, 0.429543, 1; 12:46:15.467 [planetinfo.record]: rotation_speed = 0.004430 12:46:15.467 [planetinfo.record]: planet zpos = 347520.000000 12:46:15.467 [planetinfo.record]: sun_radius = 83214.042969 12:46:15.467 [planetinfo.record]: sun_vector = -0.558 0.789 0.257 12:46:15.467 [planetinfo.record]: sun_distance = 608160 12:46:15.467 [planetinfo.record]: corona_flare = 0.068726 12:46:15.467 [planetinfo.record]: corona_hues = 0.962013 12:46:15.467 [planetinfo.record]: sun_color = 0.761533, 0.627934, 0.593332, 1 12:46:15.467 [planetinfo.record]: corona_shimmer = 0.454732 12:46:15.467 [planetinfo.record]: station_vector = 0.888 -0.456 0.050 12:46:15.467 [planetinfo.record]: station = coriolis 12:46:19.492 [PLANETINFO OVER]: Done 12:46:19.493 [PLANETINFO LOGGING]: 3 34 12:46:20.513 [planetinfo.record]: seed = 178 160 90 225 12:46:20.513 [planetinfo.record]: coordinates = 160 120 12:46:20.513 [planetinfo.record]: government = 6; 12:46:20.513 [planetinfo.record]: economy = 0; 12:46:20.513 [planetinfo.record]: techlevel = 10; 12:46:20.514 [planetinfo.record]: population = 47; 12:46:20.514 [planetinfo.record]: productivity = 37600; 12:46:20.514 [planetinfo.record]: name = "Lequteor"; 12:46:20.514 [planetinfo.record]: inhabitant = "Human Colonial"; 12:46:20.514 [planetinfo.record]: inhabitants = "Human Colonials"; 12:46:20.514 [planetinfo.record]: description = "The world Lequteor is reasonably famous for its inhabitants’ ancient mating traditions."; 12:46:20.528 [planetinfo.record]: air_color = 0.748962, 0.747793, 0.951522, 1; 12:46:20.528 [planetinfo.record]: cloud_alpha = 1.000000; 12:46:20.528 [planetinfo.record]: cloud_color = 0.821731, 0.82058, 0.857422, 1; 12:46:20.528 [planetinfo.record]: cloud_fraction = 0.370000; 12:46:20.528 [planetinfo.record]: land_color = 0.658203, 0.576249, 0.503937, 1; 12:46:20.528 [planetinfo.record]: land_fraction = 0.570000; 12:46:20.528 [planetinfo.record]: polar_cloud_color = 0.862794, 0.86205, 0.88584, 1; 12:46:20.528 [planetinfo.record]: polar_land_color = 0.93418, 0.905101, 0.879443, 1; 12:46:20.528 [planetinfo.record]: polar_sea_color = 0.727522, 0.867547, 0.928906, 1; 12:46:20.528 [planetinfo.record]: sea_color = 0.0944214, 0.523093, 0.710938, 1; 12:46:20.528 [planetinfo.record]: rotation_speed = 0.002627 12:46:20.528 [planetinfo.record]: planet zpos = 323200.000000 12:46:20.528 [planetinfo.record]: sun_radius = 59909.570312 12:46:20.528 [planetinfo.record]: sun_vector = 0.242 -0.581 0.778 12:46:20.528 [planetinfo.record]: sun_distance = 711040 12:46:20.528 [planetinfo.record]: corona_flare = 0.054095 12:46:20.528 [planetinfo.record]: corona_hues = 0.681717 12:46:20.528 [planetinfo.record]: sun_color = 0.735492, 0.49113, 0.453109, 1 12:46:20.528 [planetinfo.record]: corona_shimmer = 0.513227 12:46:20.528 [planetinfo.record]: station_vector = 0.222 -0.767 0.602 12:46:20.528 [planetinfo.record]: station = coriolis 12:46:24.524 [PLANETINFO OVER]: Done 12:46:24.525 [PLANETINFO LOGGING]: 3 35 12:46:25.538 [planetinfo.record]: seed = 2 89 170 208 12:46:25.538 [planetinfo.record]: coordinates = 89 124 12:46:25.538 [planetinfo.record]: government = 0; 12:46:25.538 [planetinfo.record]: economy = 6; 12:46:25.538 [planetinfo.record]: techlevel = 2; 12:46:25.538 [planetinfo.record]: population = 15; 12:46:25.538 [planetinfo.record]: productivity = 1920; 12:46:25.538 [planetinfo.record]: name = "Erbierso"; 12:46:25.538 [planetinfo.record]: inhabitant = "Furry Humanoid"; 12:46:25.538 [planetinfo.record]: inhabitants = "Furry Humanoids"; 12:46:25.538 [planetinfo.record]: description = "Erbierso is cursed by deadly civil war."; 12:46:25.564 [planetinfo.record]: air_color = 0.6315, 0.473759, 0.958025, 1; 12:46:25.564 [planetinfo.record]: cloud_alpha = 1.000000; 12:46:25.564 [planetinfo.record]: cloud_color = 0.870664, 0.0719376, 0.876953, 1; 12:46:25.564 [planetinfo.record]: cloud_fraction = 0.460000; 12:46:25.564 [planetinfo.record]: land_color = 0.66, 0.343193, 0.281016, 1; 12:46:25.564 [planetinfo.record]: land_fraction = 0.650000; 12:46:25.564 [planetinfo.record]: polar_cloud_color = 0.890619, 0.381353, 0.894629, 1; 12:46:25.564 [planetinfo.record]: polar_land_color = 0.934, 0.821917, 0.79992, 1; 12:46:25.564 [planetinfo.record]: polar_sea_color = 0.891401, 0.863419, 0.910547, 1; 12:46:25.564 [planetinfo.record]: sea_color = 0.819295, 0.709335, 0.894531, 1; 12:46:25.564 [planetinfo.record]: rotation_speed = 0.000109 12:46:25.564 [planetinfo.record]: planet zpos = 406700.000000 12:46:25.564 [planetinfo.record]: sun_radius = 60913.864136 12:46:25.564 [planetinfo.record]: sun_vector = -0.316 -0.948 -0.050 12:46:25.564 [planetinfo.record]: sun_distance = 493850 12:46:25.564 [planetinfo.record]: corona_flare = 0.035992 12:46:25.564 [planetinfo.record]: corona_hues = 0.534576 12:46:25.564 [planetinfo.record]: sun_color = 0.674647, 0.685373, 0.743912, 1 12:46:25.564 [planetinfo.record]: corona_shimmer = 0.306897 12:46:25.564 [planetinfo.record]: station_vector = -0.054 -0.644 0.763 12:46:25.564 [planetinfo.record]: station = coriolis 12:46:31.115 [PLANETINFO OVER]: Done 12:46:31.116 [PLANETINFO LOGGING]: 3 36 12:46:32.138 [planetinfo.record]: seed = 98 71 26 190 12:46:32.139 [planetinfo.record]: coordinates = 71 208 12:46:32.139 [planetinfo.record]: government = 4; 12:46:32.139 [planetinfo.record]: economy = 0; 12:46:32.139 [planetinfo.record]: techlevel = 12; 12:46:32.139 [planetinfo.record]: population = 53; 12:46:32.139 [planetinfo.record]: productivity = 33920; 12:46:32.139 [planetinfo.record]: name = "Rilaan"; 12:46:32.139 [planetinfo.record]: inhabitant = "Human Colonial"; 12:46:32.139 [planetinfo.record]: inhabitants = "Human Colonials"; 12:46:32.139 [planetinfo.record]: description = "Rilaan is mildly famous for its hoopy casinos and its great dense forests."; 12:46:32.144 [planetinfo.record]: air_color = 0.586335, 0.552482, 0.868271, 1; 12:46:32.144 [planetinfo.record]: cloud_alpha = 1.000000; 12:46:32.144 [planetinfo.record]: cloud_color = 0.40889, 0.308456, 0.607422, 1; 12:46:32.144 [planetinfo.record]: cloud_fraction = 0.200000; 12:46:32.144 [planetinfo.record]: land_color = 0.66, 0.0696094, 0.401704, 1; 12:46:32.144 [planetinfo.record]: land_fraction = 0.290000; 12:46:32.144 [planetinfo.record]: polar_cloud_color = 0.615364, 0.535447, 0.77334, 1; 12:46:32.144 [planetinfo.record]: polar_land_color = 0.934, 0.725127, 0.842618, 1; 12:46:32.144 [planetinfo.record]: polar_sea_color = 0.940625, 0.880366, 0.880366, 1; 12:46:32.144 [planetinfo.record]: sea_color = 0.59375, 0.441602, 0.441602, 1; 12:46:32.144 [planetinfo.record]: rotation_speed = 0.001972 12:46:32.144 [planetinfo.record]: planet zpos = 776520.000000 12:46:32.144 [planetinfo.record]: sun_radius = 177799.453583 12:46:32.144 [planetinfo.record]: sun_vector = 0.203 0.744 0.637 12:46:32.145 [planetinfo.record]: sun_distance = 1100070 12:46:32.145 [planetinfo.record]: corona_flare = 0.096226 12:46:32.145 [planetinfo.record]: corona_hues = 0.884659 12:46:32.145 [planetinfo.record]: sun_color = 0.639515, 0.809382, 0.819748, 1 12:46:32.145 [planetinfo.record]: corona_shimmer = 0.303787 12:46:32.145 [planetinfo.record]: station_vector = -0.737 -0.673 0.067 12:46:32.145 [planetinfo.record]: station = dodecahedron 12:46:36.845 [PLANETINFO OVER]: Done 12:46:36.846 [PLANETINFO LOGGING]: 3 37 12:46:37.850 [planetinfo.record]: seed = 242 110 202 31 12:46:37.850 [planetinfo.record]: coordinates = 110 219 12:46:37.850 [planetinfo.record]: government = 6; 12:46:37.850 [planetinfo.record]: economy = 3; 12:46:37.850 [planetinfo.record]: techlevel = 9; 12:46:37.850 [planetinfo.record]: population = 46; 12:46:37.850 [planetinfo.record]: productivity = 25760; 12:46:37.850 [planetinfo.record]: name = "Onesed"; 12:46:37.850 [planetinfo.record]: inhabitant = "Green Furry Rodent"; 12:46:37.850 [planetinfo.record]: inhabitants = "Green Furry Rodents"; 12:46:37.850 [planetinfo.record]: description = "The world Onesed is a dull place."; 12:46:37.853 [planetinfo.record]: air_color = 0.7531, 0.727899, 0.981439, 1; 12:46:37.853 [planetinfo.record]: cloud_alpha = 1.000000; 12:46:37.853 [planetinfo.record]: cloud_color = 0.832789, 0.780754, 0.947266, 1; 12:46:37.853 [planetinfo.record]: cloud_fraction = 0.540000; 12:46:37.853 [planetinfo.record]: land_color = 0.615245, 0.66, 0.399609, 1; 12:46:37.853 [planetinfo.record]: land_fraction = 0.700000; 12:46:37.853 [planetinfo.record]: polar_cloud_color = 0.856307, 0.824507, 0.92627, 1; 12:46:37.853 [planetinfo.record]: polar_land_color = 0.918166, 0.934, 0.841877, 1; 12:46:37.853 [planetinfo.record]: polar_sea_color = 0.844297, 0.899933, 0.949023, 1; 12:46:37.853 [planetinfo.record]: sea_color = 0.284752, 0.40429, 0.509766, 1; 12:46:37.853 [planetinfo.record]: rotation_speed = 0.003155 12:46:37.853 [planetinfo.record]: planet zpos = 879580.000000 12:46:37.853 [planetinfo.record]: sun_radius = 187871.716919 12:46:37.854 [planetinfo.record]: sun_vector = 0.580 0.090 -0.810 12:46:37.854 [planetinfo.record]: sun_distance = 1285540 12:46:37.854 [planetinfo.record]: corona_flare = 0.026457 12:46:37.854 [planetinfo.record]: corona_hues = 0.584023 12:46:37.854 [planetinfo.record]: sun_color = 0.758142, 0.787093, 0.800629, 1 12:46:37.854 [planetinfo.record]: corona_shimmer = 0.315432 12:46:37.854 [planetinfo.record]: station_vector = -0.807 0.390 0.444 12:46:37.854 [planetinfo.record]: station = coriolis 12:46:42.653 [PLANETINFO OVER]: Done 12:46:42.654 [PLANETINFO LOGGING]: 3 38 12:46:43.662 [planetinfo.record]: seed = 82 130 218 228 12:46:43.662 [planetinfo.record]: coordinates = 130 248 12:46:43.663 [planetinfo.record]: government = 2; 12:46:43.663 [planetinfo.record]: economy = 0; 12:46:43.663 [planetinfo.record]: techlevel = 10; 12:46:43.663 [planetinfo.record]: population = 43; 12:46:43.663 [planetinfo.record]: productivity = 20640; 12:46:43.663 [planetinfo.record]: name = "Zaonbi"; 12:46:43.663 [planetinfo.record]: inhabitant = "Fierce Horned Lizard"; 12:46:43.663 [planetinfo.record]: inhabitants = "Fierce Horned Lizards"; 12:46:43.663 [planetinfo.record]: description = "The world Zaonbi is a boring world."; 12:46:43.665 [planetinfo.record]: air_color = 0.761138, 0.505719, 0.961928, 1; 12:46:43.665 [planetinfo.record]: cloud_alpha = 1.000000; 12:46:43.665 [planetinfo.record]: cloud_color = 0.888672, 0.156212, 0.379383, 1; 12:46:43.665 [planetinfo.record]: cloud_fraction = 0.520000; 12:46:43.665 [planetinfo.record]: land_color = 0.629063, 0.66, 0.638972, 1; 12:46:43.665 [planetinfo.record]: land_fraction = 0.310000; 12:46:43.665 [planetinfo.record]: polar_cloud_color = 0.899902, 0.43633, 0.577574, 1; 12:46:43.665 [planetinfo.record]: polar_land_color = 0.923055, 0.934, 0.926561, 1; 12:46:43.665 [planetinfo.record]: polar_sea_color = 0.715424, 0.929688, 0.9096, 1; 12:46:43.665 [planetinfo.record]: sea_color = 0.0549316, 0.703125, 0.642357, 1; 12:46:43.665 [planetinfo.record]: rotation_speed = 0.003874 12:46:43.665 [planetinfo.record]: planet zpos = 476400.000000 12:46:43.665 [planetinfo.record]: sun_radius = 89085.113525 12:46:43.665 [planetinfo.record]: sun_vector = 0.026 -0.011 -1.000 12:46:43.665 [planetinfo.record]: sun_distance = 754300 12:46:43.665 [planetinfo.record]: corona_flare = 0.093442 12:46:43.665 [planetinfo.record]: corona_hues = 0.692627 12:46:43.665 [planetinfo.record]: sun_color = 0.404703, 0.584701, 0.71503, 1 12:46:43.665 [planetinfo.record]: corona_shimmer = 0.428261 12:46:43.665 [planetinfo.record]: station_vector = -0.964 -0.232 0.128 12:46:43.666 [planetinfo.record]: station = coriolis 12:46:48.535 [PLANETINFO OVER]: Done 12:46:48.535 [PLANETINFO LOGGING]: 3 39 12:46:49.543 [planetinfo.record]: seed = 162 11 106 50 12:46:49.543 [planetinfo.record]: coordinates = 11 198 12:46:49.543 [planetinfo.record]: government = 4; 12:46:49.543 [planetinfo.record]: economy = 6; 12:46:49.543 [planetinfo.record]: techlevel = 6; 12:46:49.543 [planetinfo.record]: population = 35; 12:46:49.543 [planetinfo.record]: productivity = 8960; 12:46:49.544 [planetinfo.record]: name = "Encegequ"; 12:46:49.544 [planetinfo.record]: inhabitant = "Human Colonial"; 12:46:49.544 [planetinfo.record]: inhabitants = "Human Colonials"; 12:46:49.544 [planetinfo.record]: description = "The planet Encegequ is most noted for the Encegequian evil talking treeoid and its inhabitants’ ingrained shyness."; 12:46:49.552 [planetinfo.record]: air_color = 0.410419, 0.845508, 0.814483, 1; 12:46:49.553 [planetinfo.record]: cloud_alpha = 1.000000; 12:46:49.553 [planetinfo.record]: cloud_color = 0.539062, 0.435554, 0.02948, 1; 12:46:49.553 [planetinfo.record]: cloud_fraction = 0.400000; 12:46:49.553 [planetinfo.record]: land_color = 0.617844, 0.244922, 0.66, 1; 12:46:49.553 [planetinfo.record]: land_fraction = 0.370000; 12:46:49.553 [planetinfo.record]: polar_cloud_color = 0.742578, 0.653461, 0.303848, 1; 12:46:49.553 [planetinfo.record]: polar_land_color = 0.919086, 0.78715, 0.934, 1; 12:46:49.553 [planetinfo.record]: polar_sea_color = 0.895751, 0.932813, 0.882346, 1; 12:46:49.553 [planetinfo.record]: sea_color = 0.565098, 0.671875, 0.526477, 1; 12:46:49.553 [planetinfo.record]: rotation_speed = 0.003774 12:46:49.553 [planetinfo.record]: planet zpos = 333900.000000 12:46:49.553 [planetinfo.record]: sun_radius = 88064.494629 12:46:49.553 [planetinfo.record]: sun_vector = 0.364 -0.361 -0.859 12:46:49.554 [planetinfo.record]: sun_distance = 667800 12:46:49.554 [planetinfo.record]: corona_flare = 0.051846 12:46:49.554 [planetinfo.record]: corona_hues = 0.693817 12:46:49.554 [planetinfo.record]: sun_color = 0.698483, 0.527665, 0.40154, 1 12:46:49.554 [planetinfo.record]: corona_shimmer = 0.295561 12:46:49.554 [planetinfo.record]: station_vector = 0.104 -0.836 0.539 12:46:49.554 [planetinfo.record]: station = coriolis 12:46:54.167 [PLANETINFO OVER]: Done 12:46:54.168 [PLANETINFO LOGGING]: 3 40 12:46:55.177 [planetinfo.record]: seed = 130 122 154 194 12:46:55.177 [planetinfo.record]: coordinates = 122 67 12:46:55.177 [planetinfo.record]: government = 0; 12:46:55.177 [planetinfo.record]: economy = 3; 12:46:55.177 [planetinfo.record]: techlevel = 6; 12:46:55.177 [planetinfo.record]: population = 28; 12:46:55.177 [planetinfo.record]: productivity = 6272; 12:46:55.177 [planetinfo.record]: name = "Xeis"; 12:46:55.177 [planetinfo.record]: inhabitant = "Large Bug-Eyed Lobster"; 12:46:55.177 [planetinfo.record]: inhabitants = "Large Bug-Eyed Lobsters"; 12:46:55.177 [planetinfo.record]: description = "The planet Xeis is famous for Xeisian wolf meat but beset by frequent civil war."; 12:46:55.192 [planetinfo.record]: air_color = 0.713305, 0.484552, 0.967131, 1; 12:46:55.192 [planetinfo.record]: cloud_alpha = 1.000000; 12:46:55.192 [planetinfo.record]: cloud_color = 0.904297, 0.0918427, 0.548848, 1; 12:46:55.192 [planetinfo.record]: cloud_fraction = 0.180000; 12:46:55.192 [planetinfo.record]: land_color = 0.0916138, 0.617188, 0.247643, 1; 12:46:55.192 [planetinfo.record]: land_fraction = 0.360000; 12:46:55.192 [planetinfo.record]: polar_cloud_color = 0.906934, 0.397669, 0.68413, 1; 12:46:55.192 [planetinfo.record]: polar_land_color = 0.73853, 0.938281, 0.797831, 1; 12:46:55.192 [planetinfo.record]: polar_sea_color = 0.928906, 0.893783, 0.852707, 1; 12:46:55.192 [planetinfo.record]: sea_color = 0.710938, 0.603412, 0.477661, 1; 12:46:55.192 [planetinfo.record]: rotation_speed = 0.003232 12:46:55.192 [planetinfo.record]: planet zpos = 345000.000000 12:46:55.192 [planetinfo.record]: sun_radius = 89238.006592 12:46:55.192 [planetinfo.record]: sun_vector = -0.349 -0.437 0.829 12:46:55.192 [planetinfo.record]: sun_distance = 690000 12:46:55.192 [planetinfo.record]: corona_flare = 0.069507 12:46:55.192 [planetinfo.record]: corona_hues = 0.791931 12:46:55.192 [planetinfo.record]: sun_color = 0.711514, 0.279633, 0.256455, 1 12:46:55.193 [planetinfo.record]: corona_shimmer = 0.363455 12:46:55.193 [planetinfo.record]: station_vector = -0.844 -0.270 0.463 12:46:55.193 [planetinfo.record]: station = coriolis 12:47:00.446 [PLANETINFO OVER]: Done 12:47:00.447 [PLANETINFO LOGGING]: 3 41 12:47:01.457 [planetinfo.record]: seed = 18 0 138 61 12:47:01.458 [planetinfo.record]: coordinates = 0 189 12:47:01.458 [planetinfo.record]: government = 2; 12:47:01.458 [planetinfo.record]: economy = 5; 12:47:01.458 [planetinfo.record]: techlevel = 3; 12:47:01.458 [planetinfo.record]: population = 20; 12:47:01.458 [planetinfo.record]: productivity = 4800; 12:47:01.458 [planetinfo.record]: name = "Isqueder"; 12:47:01.458 [planetinfo.record]: inhabitant = "Red Furry Feline"; 12:47:01.458 [planetinfo.record]: inhabitants = "Red Furry Felines"; 12:47:01.458 [planetinfo.record]: description = "The planet Isqueder is most famous for its pink Ou plant plantations and its unusual dense forests."; 12:47:01.472 [planetinfo.record]: air_color = 0.73624, 0.634532, 0.842256, 1; 12:47:01.472 [planetinfo.record]: cloud_alpha = 1.000000; 12:47:01.472 [planetinfo.record]: cloud_color = 0.529297, 0.454865, 0.493244, 1; 12:47:01.472 [planetinfo.record]: cloud_fraction = 0.330000; 12:47:01.472 [planetinfo.record]: land_color = 0.561306, 0.66, 0.479531, 1; 12:47:01.472 [planetinfo.record]: land_fraction = 0.370000; 12:47:01.472 [planetinfo.record]: polar_cloud_color = 0.738184, 0.673304, 0.706758, 1; 12:47:01.472 [planetinfo.record]: polar_land_color = 0.899083, 0.934, 0.870152, 1; 12:47:01.472 [planetinfo.record]: polar_sea_color = 0.929165, 0.863595, 0.942773, 1; 12:47:01.472 [planetinfo.record]: sea_color = 0.539223, 0.38002, 0.572266, 1; 12:47:01.472 [planetinfo.record]: rotation_speed = 0.001903 12:47:01.472 [planetinfo.record]: planet zpos = 737280.000000 12:47:01.472 [planetinfo.record]: sun_radius = 165755.625000 12:47:01.472 [planetinfo.record]: sun_vector = -0.086 -0.283 -0.955 12:47:01.472 [planetinfo.record]: sun_distance = 1044480 12:47:01.472 [planetinfo.record]: corona_flare = 0.037285 12:47:01.472 [planetinfo.record]: corona_hues = 0.562340 12:47:01.472 [planetinfo.record]: sun_color = 0.501548, 0.581114, 0.696155, 1 12:47:01.472 [planetinfo.record]: corona_shimmer = 0.257823 12:47:01.473 [planetinfo.record]: station_vector = 0.179 -0.866 0.468 12:47:01.473 [planetinfo.record]: station = coriolis 12:47:06.165 [PLANETINFO OVER]: Done 12:47:06.166 [PLANETINFO LOGGING]: 3 42 12:47:07.183 [planetinfo.record]: seed = 242 112 90 164 12:47:07.183 [planetinfo.record]: coordinates = 112 56 12:47:07.183 [planetinfo.record]: government = 6; 12:47:07.184 [planetinfo.record]: economy = 0; 12:47:07.184 [planetinfo.record]: techlevel = 10; 12:47:07.184 [planetinfo.record]: population = 47; 12:47:07.184 [planetinfo.record]: productivity = 37600; 12:47:07.184 [planetinfo.record]: name = "Zadige"; 12:47:07.184 [planetinfo.record]: inhabitant = "Human Colonial"; 12:47:07.184 [planetinfo.record]: inhabitants = "Human Colonials"; 12:47:07.184 [planetinfo.record]: description = "Zadige is cursed by dreadful civil war."; 12:47:07.204 [planetinfo.record]: air_color = 0.628379, 0.842405, 0.96583, 1; 12:47:07.204 [planetinfo.record]: cloud_alpha = 1.000000; 12:47:07.204 [planetinfo.record]: cloud_color = 0.495918, 0.900391, 0.540157, 1; 12:47:07.204 [planetinfo.record]: cloud_fraction = 0.420000; 12:47:07.204 [planetinfo.record]: land_color = 0.364905, 0.66, 0.244922, 1; 12:47:07.204 [planetinfo.record]: land_fraction = 0.450000; 12:47:07.204 [planetinfo.record]: polar_cloud_color = 0.651037, 0.905176, 0.678833, 1; 12:47:07.204 [planetinfo.record]: polar_land_color = 0.829599, 0.934, 0.78715, 1; 12:47:07.204 [planetinfo.record]: polar_sea_color = 0.943164, 0.916056, 0.892137, 1; 12:47:07.204 [planetinfo.record]: sea_color = 0.568359, 0.503017, 0.445363, 1; 12:47:07.204 [planetinfo.record]: rotation_speed = 0.000091 12:47:07.204 [planetinfo.record]: planet zpos = 513760.000000 12:47:07.204 [planetinfo.record]: sun_radius = 105846.977539 12:47:07.204 [planetinfo.record]: sun_vector = -0.421 0.806 -0.416 12:47:07.204 [planetinfo.record]: sun_distance = 790400 12:47:07.205 [planetinfo.record]: corona_flare = 0.004982 12:47:07.205 [planetinfo.record]: corona_hues = 0.771240 12:47:07.205 [planetinfo.record]: sun_color = 0.752393, 0.660083, 0.214842, 1 12:47:07.205 [planetinfo.record]: corona_shimmer = 0.707870 12:47:07.205 [planetinfo.record]: station_vector = -0.316 0.135 0.939 12:47:07.205 [planetinfo.record]: station = coriolis 12:47:11.642 [PLANETINFO OVER]: Done 12:47:11.643 [PLANETINFO LOGGING]: 3 43 12:47:12.654 [planetinfo.record]: seed = 66 85 42 6 12:47:12.654 [planetinfo.record]: coordinates = 85 99 12:47:12.654 [planetinfo.record]: government = 0; 12:47:12.654 [planetinfo.record]: economy = 3; 12:47:12.654 [planetinfo.record]: techlevel = 5; 12:47:12.654 [planetinfo.record]: population = 24; 12:47:12.654 [planetinfo.record]: productivity = 5376; 12:47:12.654 [planetinfo.record]: name = "Birior"; 12:47:12.654 [planetinfo.record]: inhabitant = "Human Colonial"; 12:47:12.654 [planetinfo.record]: inhabitants = "Human Colonials"; 12:47:12.654 [planetinfo.record]: description = "The planet Birior is very famous for its exotic fish meat but beset by frequent civil war."; 12:47:12.672 [planetinfo.record]: air_color = 0.718211, 0.817953, 0.897539, 1; 12:47:12.672 [planetinfo.record]: cloud_alpha = 1.000000; 12:47:12.672 [planetinfo.record]: cloud_color = 0.695312, 0.695312, 0.695312, 1; 12:47:12.672 [planetinfo.record]: cloud_fraction = 0.300000; 12:47:12.672 [planetinfo.record]: land_color = 0.617844, 0.66, 0.600703, 1; 12:47:12.672 [planetinfo.record]: land_fraction = 0.670000; 12:47:12.672 [planetinfo.record]: polar_cloud_color = 0.812891, 0.812891, 0.812891, 1; 12:47:12.672 [planetinfo.record]: polar_land_color = 0.919086, 0.934, 0.913022, 1; 12:47:12.672 [planetinfo.record]: polar_sea_color = 0.738347, 0.93457, 0.893179, 1; 12:47:12.672 [planetinfo.record]: sea_color = 0.10479, 0.654297, 0.538385, 1; 12:47:12.672 [planetinfo.record]: rotation_speed = 0.002515 12:47:12.672 [planetinfo.record]: planet zpos = 399330.000000 12:47:12.672 [planetinfo.record]: sun_radius = 88675.004883 12:47:12.672 [planetinfo.record]: sun_vector = 0.108 -0.474 -0.874 12:47:12.672 [planetinfo.record]: sun_distance = 887400 12:47:12.672 [planetinfo.record]: corona_flare = 0.035141 12:47:12.672 [planetinfo.record]: corona_hues = 0.975891 12:47:12.672 [planetinfo.record]: sun_color = 0.725168, 0.395757, 0.367154, 1 12:47:12.673 [planetinfo.record]: corona_shimmer = 0.230125 12:47:12.673 [planetinfo.record]: station_vector = -0.456 -0.877 0.153 12:47:12.673 [planetinfo.record]: station = coriolis 12:47:17.367 [PLANETINFO OVER]: Done 12:47:17.368 [PLANETINFO LOGGING]: 3 44 12:47:18.387 [planetinfo.record]: seed = 162 222 26 183 12:47:18.387 [planetinfo.record]: coordinates = 222 25 12:47:18.387 [planetinfo.record]: government = 4; 12:47:18.387 [planetinfo.record]: economy = 1; 12:47:18.387 [planetinfo.record]: techlevel = 10; 12:47:18.387 [planetinfo.record]: population = 46; 12:47:18.387 [planetinfo.record]: productivity = 26496; 12:47:18.387 [planetinfo.record]: name = "Tiacein"; 12:47:18.387 [planetinfo.record]: inhabitant = "Human Colonial"; 12:47:18.387 [planetinfo.record]: inhabitants = "Human Colonials"; 12:47:18.387 [planetinfo.record]: description = "The world Tiacein is beset by evil disease."; 12:47:18.404 [planetinfo.record]: air_color = 0.765444, 0.553561, 0.926807, 1; 12:47:18.404 [planetinfo.record]: cloud_alpha = 1.000000; 12:47:18.404 [planetinfo.record]: cloud_color = 0.783203, 0.305939, 0.440169, 1; 12:47:18.404 [planetinfo.record]: cloud_fraction = 0.360000; 12:47:18.404 [planetinfo.record]: land_color = 0.66, 0.301641, 0.301641, 1; 12:47:18.404 [planetinfo.record]: land_fraction = 0.290000; 12:47:18.404 [planetinfo.record]: polar_cloud_color = 0.852441, 0.527781, 0.619092, 1; 12:47:18.404 [planetinfo.record]: polar_land_color = 0.934, 0.807217, 0.807217, 1; 12:47:18.404 [planetinfo.record]: polar_sea_color = 0.867797, 0.923437, 0.886923, 1; 12:47:18.404 [planetinfo.record]: sea_color = 0.581097, 0.765625, 0.644529, 1; 12:47:18.404 [planetinfo.record]: rotation_speed = 0.004492 12:47:18.404 [planetinfo.record]: planet zpos = 434700.000000 12:47:18.404 [planetinfo.record]: sun_radius = 116319.895935 12:47:18.404 [planetinfo.record]: sun_vector = 0.352 0.839 -0.414 12:47:18.404 [planetinfo.record]: sun_distance = 821100 12:47:18.404 [planetinfo.record]: corona_flare = 0.070364 12:47:18.404 [planetinfo.record]: corona_hues = 0.968521 12:47:18.404 [planetinfo.record]: sun_color = 0.828665, 0.820325, 0.81683, 1 12:47:18.405 [planetinfo.record]: corona_shimmer = 0.333357 12:47:18.405 [planetinfo.record]: station_vector = 0.869 0.443 0.219 12:47:18.405 [planetinfo.record]: station = coriolis 12:47:22.887 [PLANETINFO OVER]: Done 12:47:22.888 [PLANETINFO LOGGING]: 3 45 12:47:23.893 [planetinfo.record]: seed = 50 172 74 161 12:47:23.893 [planetinfo.record]: coordinates = 172 69 12:47:23.893 [planetinfo.record]: government = 6; 12:47:23.893 [planetinfo.record]: economy = 5; 12:47:23.893 [planetinfo.record]: techlevel = 5; 12:47:23.893 [planetinfo.record]: population = 32; 12:47:23.893 [planetinfo.record]: productivity = 12800; 12:47:23.893 [planetinfo.record]: name = "Leenra"; 12:47:23.893 [planetinfo.record]: inhabitant = "Human Colonial"; 12:47:23.893 [planetinfo.record]: inhabitants = "Human Colonials"; 12:47:23.893 [planetinfo.record]: description = "The planet Leenra is a boring world."; 12:47:23.912 [planetinfo.record]: air_color = 0.640382, 0.904693, 0.896161, 1; 12:47:23.912 [planetinfo.record]: cloud_alpha = 1.000000; 12:47:23.912 [planetinfo.record]: cloud_color = 0.716797, 0.699713, 0.517998, 1; 12:47:23.912 [planetinfo.record]: cloud_fraction = 0.480000; 12:47:23.912 [planetinfo.record]: land_color = 0.267982, 0.410599, 0.548828, 1; 12:47:23.912 [planetinfo.record]: land_fraction = 0.530000; 12:47:23.912 [planetinfo.record]: polar_cloud_color = 0.822559, 0.810305, 0.679976, 1; 12:47:23.912 [planetinfo.record]: polar_land_color = 0.824209, 0.885607, 0.945117, 1; 12:47:23.912 [planetinfo.record]: polar_sea_color = 0.928516, 0.865298, 0.84056, 1; 12:47:23.912 [planetinfo.record]: sea_color = 0.714844, 0.520164, 0.443985, 1; 12:47:23.912 [planetinfo.record]: rotation_speed = 0.000725 12:47:23.912 [planetinfo.record]: planet zpos = 421720.000000 12:47:23.912 [planetinfo.record]: sun_radius = 110042.364502 12:47:23.912 [planetinfo.record]: sun_vector = -0.768 -0.635 0.081 12:47:23.913 [planetinfo.record]: sun_distance = 778560 12:47:23.913 [planetinfo.record]: corona_flare = 0.058447 12:47:23.913 [planetinfo.record]: corona_hues = 0.536247 12:47:23.913 [planetinfo.record]: sun_color = 0.71358, 0.685716, 0.663447, 1 12:47:23.913 [planetinfo.record]: corona_shimmer = 1.692212 12:47:23.913 [planetinfo.record]: station_vector = 0.007 -0.998 0.062 12:47:23.913 [planetinfo.record]: station = coriolis 12:47:28.641 [PLANETINFO OVER]: Done 12:47:28.641 [PLANETINFO LOGGING]: 3 46 12:47:29.666 [planetinfo.record]: seed = 146 20 218 135 12:47:29.666 [planetinfo.record]: coordinates = 20 224 12:47:29.666 [planetinfo.record]: government = 2; 12:47:29.666 [planetinfo.record]: economy = 0; 12:47:29.666 [planetinfo.record]: techlevel = 8; 12:47:29.666 [planetinfo.record]: population = 35; 12:47:29.666 [planetinfo.record]: productivity = 16800; 12:47:29.666 [planetinfo.record]: name = "Soteoris"; 12:47:29.666 [planetinfo.record]: inhabitant = "Fierce Black Fat Insect"; 12:47:29.666 [planetinfo.record]: inhabitants = "Fierce Black Fat Insects"; 12:47:29.666 [planetinfo.record]: description = "This planet is mildly noted for the Soteorisian mountain Stoid but plagued by lethal spotted craboids."; 12:47:29.689 [planetinfo.record]: air_color = 0.7687, 0.481189, 0.980789, 1; 12:47:29.689 [planetinfo.record]: cloud_alpha = 1.000000; 12:47:29.689 [planetinfo.record]: cloud_color = 0.945312, 0.0664673, 0.293045, 1; 12:47:29.689 [planetinfo.record]: cloud_fraction = 0.290000; 12:47:29.689 [planetinfo.record]: land_color = 0.66, 0.280129, 0.131484, 1; 12:47:29.689 [planetinfo.record]: land_fraction = 0.350000; 12:47:29.689 [planetinfo.record]: polar_cloud_color = 0.925391, 0.387688, 0.526314, 1; 12:47:29.689 [planetinfo.record]: polar_land_color = 0.934, 0.799606, 0.747018, 1; 12:47:29.690 [planetinfo.record]: polar_sea_color = 0.948633, 0.906514, 0.900923, 1; 12:47:29.690 [planetinfo.record]: sea_color = 0.513672, 0.422445, 0.410336, 1; 12:47:29.690 [planetinfo.record]: rotation_speed = 0.001315 12:47:29.690 [planetinfo.record]: planet zpos = 647920.000000 12:47:29.690 [planetinfo.record]: sun_radius = 144898.996582 12:47:29.690 [planetinfo.record]: sun_vector = -0.503 0.609 -0.614 12:47:29.690 [planetinfo.record]: sun_distance = 925600 12:47:29.690 [planetinfo.record]: corona_flare = 0.020609 12:47:29.690 [planetinfo.record]: corona_hues = 0.545311 12:47:29.690 [planetinfo.record]: sun_color = 0.681853, 0.682424, 0.695114, 1 12:47:29.690 [planetinfo.record]: corona_shimmer = 0.552291 12:47:29.690 [planetinfo.record]: station_vector = -0.184 0.573 0.798 12:47:29.691 [planetinfo.record]: station = coriolis 12:47:34.860 [PLANETINFO OVER]: Done 12:47:34.861 [PLANETINFO LOGGING]: 3 47 12:47:35.879 [planetinfo.record]: seed = 226 29 234 179 12:47:35.879 [planetinfo.record]: coordinates = 29 25 12:47:35.879 [planetinfo.record]: government = 4; 12:47:35.879 [planetinfo.record]: economy = 1; 12:47:35.879 [planetinfo.record]: techlevel = 9; 12:47:35.879 [planetinfo.record]: population = 42; 12:47:35.879 [planetinfo.record]: productivity = 24192; 12:47:35.880 [planetinfo.record]: name = "Bemate"; 12:47:35.880 [planetinfo.record]: inhabitant = "Harmless Fat Insect"; 12:47:35.880 [planetinfo.record]: inhabitants = "Harmless Fat Insects"; 12:47:35.880 [planetinfo.record]: description = "This world is a tedious little planet."; 12:47:35.899 [planetinfo.record]: air_color = 0.668394, 0.775309, 0.971033, 1; 12:47:35.900 [planetinfo.record]: cloud_alpha = 1.000000; 12:47:35.900 [planetinfo.record]: cloud_color = 0.608292, 0.916016, 0.901591, 1; 12:47:35.900 [planetinfo.record]: cloud_fraction = 0.500000; 12:47:35.900 [planetinfo.record]: land_color = 0.636797, 0.642054, 0.66, 1; 12:47:35.900 [planetinfo.record]: land_fraction = 0.410000; 12:47:35.900 [planetinfo.record]: polar_cloud_color = 0.720679, 0.912207, 0.903229, 1; 12:47:35.900 [planetinfo.record]: polar_land_color = 0.925791, 0.927651, 0.934, 1; 12:47:35.900 [planetinfo.record]: polar_sea_color = 0.928516, 0.864646, 0.839654, 1; 12:47:35.900 [planetinfo.record]: sea_color = 0.714844, 0.518157, 0.441193, 1; 12:47:35.900 [planetinfo.record]: rotation_speed = 0.001336 12:47:35.900 [planetinfo.record]: planet zpos = 469690.000000 12:47:35.900 [planetinfo.record]: sun_radius = 82396.753998 12:47:35.900 [planetinfo.record]: sun_vector = 0.189 -0.125 0.974 12:47:35.900 [planetinfo.record]: sun_distance = 830990 12:47:35.900 [planetinfo.record]: corona_flare = 0.045610 12:47:35.900 [planetinfo.record]: corona_hues = 0.564461 12:47:35.900 [planetinfo.record]: sun_color = 0.73942, 0.528432, 0.395474, 1 12:47:35.900 [planetinfo.record]: corona_shimmer = 0.435149 12:47:35.900 [planetinfo.record]: station_vector = -0.789 0.088 0.608 12:47:35.900 [planetinfo.record]: station = coriolis 12:47:40.671 [PLANETINFO OVER]: Done 12:47:40.672 [PLANETINFO LOGGING]: 3 48 12:47:41.681 [planetinfo.record]: seed = 194 91 154 3 12:47:41.681 [planetinfo.record]: coordinates = 91 188 12:47:41.681 [planetinfo.record]: government = 0; 12:47:41.681 [planetinfo.record]: economy = 6; 12:47:41.681 [planetinfo.record]: techlevel = 4; 12:47:41.681 [planetinfo.record]: population = 23; 12:47:41.681 [planetinfo.record]: productivity = 2944; 12:47:41.681 [planetinfo.record]: name = "Geteanan"; 12:47:41.681 [planetinfo.record]: inhabitant = "Large Green Lizard"; 12:47:41.681 [planetinfo.record]: inhabitants = "Large Green Lizards"; 12:47:41.681 [planetinfo.record]: description = "The planet Geteanan is very noted for its unusual sit coms but ravaged by unpredictable civil war."; 12:47:41.692 [planetinfo.record]: air_color = 0.722374, 0.81284, 0.902742, 1; 12:47:41.692 [planetinfo.record]: cloud_alpha = 1.000000; 12:47:41.692 [planetinfo.record]: cloud_color = 0.710938, 0.710938, 0.710938, 1; 12:47:41.692 [planetinfo.record]: cloud_fraction = 0.590000; 12:47:41.692 [planetinfo.record]: land_color = 0.474879, 0.66, 0.399609, 1; 12:47:41.692 [planetinfo.record]: land_fraction = 0.710000; 12:47:41.692 [planetinfo.record]: polar_cloud_color = 0.819922, 0.819922, 0.819922, 1; 12:47:41.692 [planetinfo.record]: polar_land_color = 0.868506, 0.934, 0.841877, 1; 12:47:41.692 [planetinfo.record]: polar_sea_color = 0.914759, 0.932227, 0.876329, 1; 12:47:41.692 [planetinfo.record]: sea_color = 0.626937, 0.677734, 0.515184, 1; 12:47:41.692 [planetinfo.record]: rotation_speed = 0.000640 12:47:41.692 [planetinfo.record]: planet zpos = 477750.000000 12:47:41.692 [planetinfo.record]: sun_radius = 91755.558014 12:47:41.692 [planetinfo.record]: sun_vector = 0.010 -0.937 -0.348 12:47:41.692 [planetinfo.record]: sun_distance = 845250 12:47:41.692 [planetinfo.record]: corona_flare = 0.014729 12:47:41.692 [planetinfo.record]: corona_hues = 0.999199 12:47:41.692 [planetinfo.record]: sun_color = 0.828574, 0.826549, 0.79585, 1 12:47:41.693 [planetinfo.record]: corona_shimmer = 0.318392 12:47:41.693 [planetinfo.record]: station_vector = 0.877 0.223 0.425 12:47:41.693 [planetinfo.record]: station = coriolis 12:47:46.928 [PLANETINFO OVER]: Done 12:47:46.928 [PLANETINFO LOGGING]: 3 49 12:47:47.945 [planetinfo.record]: seed = 82 155 10 51 12:47:47.945 [planetinfo.record]: coordinates = 155 123 12:47:47.945 [planetinfo.record]: government = 2; 12:47:47.945 [planetinfo.record]: economy = 3; 12:47:47.945 [planetinfo.record]: techlevel = 8; 12:47:47.945 [planetinfo.record]: population = 38; 12:47:47.945 [planetinfo.record]: productivity = 12768; 12:47:47.945 [planetinfo.record]: name = "Beesed"; 12:47:47.945 [planetinfo.record]: inhabitant = "Human Colonial"; 12:47:47.945 [planetinfo.record]: inhabitants = "Human Colonials"; 12:47:47.945 [planetinfo.record]: description = "This world is plagued by deadly earthquakes."; 12:47:47.958 [planetinfo.record]: air_color = 0.691316, 0.852929, 0.88258, 1; 12:47:47.958 [planetinfo.record]: cloud_alpha = 1.000000; 12:47:47.958 [planetinfo.record]: cloud_color = 0.634651, 0.650391, 0.617363, 1; 12:47:47.958 [planetinfo.record]: cloud_fraction = 0.170000; 12:47:47.958 [planetinfo.record]: land_color = 0.636719, 0.477889, 0.470078, 1; 12:47:47.958 [planetinfo.record]: land_fraction = 0.380000; 12:47:47.958 [planetinfo.record]: polar_cloud_color = 0.780686, 0.792676, 0.767518, 1; 12:47:47.958 [planetinfo.record]: polar_land_color = 0.936328, 0.877936, 0.875064, 1; 12:47:47.958 [planetinfo.record]: polar_sea_color = 0.743024, 0.892471, 0.932422, 1; 12:47:47.958 [planetinfo.record]: sea_color = 0.126709, 0.559961, 0.675781, 1; 12:47:47.958 [planetinfo.record]: rotation_speed = 0.004421 12:47:47.958 [planetinfo.record]: planet zpos = 448680.000000 12:47:47.959 [planetinfo.record]: sun_radius = 88217.031250 12:47:47.959 [planetinfo.record]: sun_vector = -0.263 0.964 0.022 12:47:47.959 [planetinfo.record]: sun_distance = 822580 12:47:47.959 [planetinfo.record]: corona_flare = 0.007294 12:47:47.959 [planetinfo.record]: corona_hues = 0.718399 12:47:47.959 [planetinfo.record]: sun_color = 0.729428, 0.379195, 0.26302, 1 12:47:47.959 [planetinfo.record]: corona_shimmer = 0.284813 12:47:47.959 [planetinfo.record]: station_vector = 0.424 0.765 0.486 12:47:47.959 [planetinfo.record]: station = coriolis 12:47:52.173 [PLANETINFO OVER]: Done 12:47:52.173 [PLANETINFO LOGGING]: 3 50 12:47:53.189 [planetinfo.record]: seed = 50 149 90 247 12:47:53.189 [planetinfo.record]: coordinates = 149 24 12:47:53.189 [planetinfo.record]: government = 6; 12:47:53.189 [planetinfo.record]: economy = 0; 12:47:53.189 [planetinfo.record]: techlevel = 11; 12:47:53.189 [planetinfo.record]: population = 51; 12:47:53.189 [planetinfo.record]: productivity = 40800; 12:47:53.189 [planetinfo.record]: name = "Tizaatdi"; 12:47:53.189 [planetinfo.record]: inhabitant = "Human Colonial"; 12:47:53.189 [planetinfo.record]: inhabitants = "Human Colonials"; 12:47:53.189 [planetinfo.record]: description = "The world Tizaatdi is reasonably noted for its inhabitants’ exceptional love for food blenders."; 12:47:53.212 [planetinfo.record]: air_color = 0.675509, 0.706412, 0.99835, 1; 12:47:53.212 [planetinfo.record]: cloud_alpha = 1.000000; 12:47:53.212 [planetinfo.record]: cloud_color = 0.627678, 0.728951, 0.998047, 1; 12:47:53.212 [planetinfo.record]: cloud_fraction = 0.540000; 12:47:53.212 [planetinfo.record]: land_color = 0.560547, 0.532218, 0.521133, 1; 12:47:53.212 [planetinfo.record]: land_fraction = 0.680000; 12:47:53.212 [planetinfo.record]: polar_cloud_color = 0.728988, 0.789181, 0.949121, 1; 12:47:53.212 [planetinfo.record]: polar_land_color = 0.943945, 0.932019, 0.927352, 1; 12:47:53.212 [planetinfo.record]: polar_sea_color = 0.911306, 0.924023, 0.850029, 1; 12:47:53.212 [planetinfo.record]: sea_color = 0.717938, 0.759766, 0.516403, 1; 12:47:53.212 [planetinfo.record]: rotation_speed = 0.002635 12:47:53.212 [planetinfo.record]: planet zpos = 570840.000000 12:47:53.212 [planetinfo.record]: sun_radius = 111479.847870 12:47:53.212 [planetinfo.record]: sun_vector = 0.864 -0.485 -0.134 12:47:53.212 [planetinfo.record]: sun_distance = 903830 12:47:53.212 [planetinfo.record]: corona_flare = 0.010143 12:47:53.212 [planetinfo.record]: corona_hues = 0.682808 12:47:53.212 [planetinfo.record]: sun_color = 0.687671, 0.683216, 0.60821, 1 12:47:53.213 [planetinfo.record]: corona_shimmer = 0.289327 12:47:53.213 [planetinfo.record]: station_vector = 0.511 0.856 0.084 12:47:53.213 [planetinfo.record]: station = dodecahedron 12:47:57.613 [PLANETINFO OVER]: Done 12:47:57.614 [PLANETINFO LOGGING]: 3 51 12:47:58.638 [planetinfo.record]: seed = 130 205 170 163 12:47:58.638 [planetinfo.record]: coordinates = 205 49 12:47:58.638 [planetinfo.record]: government = 0; 12:47:58.638 [planetinfo.record]: economy = 3; 12:47:58.638 [planetinfo.record]: techlevel = 5; 12:47:58.638 [planetinfo.record]: population = 24; 12:47:58.638 [planetinfo.record]: productivity = 5376; 12:47:58.638 [planetinfo.record]: name = "Gexebeor"; 12:47:58.638 [planetinfo.record]: inhabitant = "Large Harmless Fat Insect"; 12:47:58.638 [planetinfo.record]: inhabitants = "Large Harmless Fat Insects"; 12:47:58.638 [planetinfo.record]: description = "The world Gexebeor is reasonably noted for its fabulous goat burgers and its inhabitants’ exceptional loathing of casinos."; 12:47:58.652 [planetinfo.record]: air_color = 0.5058, 0.56969, 0.873475, 1; 12:47:58.652 [planetinfo.record]: cloud_alpha = 1.000000; 12:47:58.652 [planetinfo.record]: cloud_color = 0.209305, 0.419408, 0.623047, 1; 12:47:58.652 [planetinfo.record]: cloud_fraction = 0.330000; 12:47:58.652 [planetinfo.record]: land_color = 0.66, 0.394453, 0.438019, 1; 12:47:58.652 [planetinfo.record]: land_fraction = 0.400000; 12:47:58.652 [planetinfo.record]: polar_cloud_color = 0.456487, 0.620959, 0.780371, 1; 12:47:58.652 [planetinfo.record]: polar_land_color = 0.934, 0.840053, 0.855466, 1; 12:47:58.652 [planetinfo.record]: polar_sea_color = 0.943164, 0.922493, 0.893243, 1; 12:47:58.652 [planetinfo.record]: sea_color = 0.568359, 0.518534, 0.448027, 1; 12:47:58.652 [planetinfo.record]: rotation_speed = 0.000106 12:47:58.652 [planetinfo.record]: planet zpos = 378900.000000 12:47:58.652 [planetinfo.record]: sun_radius = 96897.130280 12:47:58.652 [planetinfo.record]: sun_vector = -0.151 -0.651 -0.744 12:47:58.652 [planetinfo.record]: sun_distance = 682020 12:47:58.652 [planetinfo.record]: corona_flare = 0.080624 12:47:58.652 [planetinfo.record]: corona_hues = 0.614555 12:47:58.652 [planetinfo.record]: sun_color = 0.742892, 0.514747, 0.39155, 1 12:47:58.652 [planetinfo.record]: corona_shimmer = 0.347520 12:47:58.652 [planetinfo.record]: station_vector = -0.354 -0.883 0.308 12:47:58.652 [planetinfo.record]: station = coriolis 12:48:03.796 [PLANETINFO OVER]: Done 12:48:03.796 [PLANETINFO LOGGING]: 3 52 12:48:04.807 [planetinfo.record]: seed = 226 89 26 16 12:48:04.807 [planetinfo.record]: coordinates = 89 19 12:48:04.807 [planetinfo.record]: government = 4; 12:48:04.807 [planetinfo.record]: economy = 3; 12:48:04.807 [planetinfo.record]: techlevel = 7; 12:48:04.807 [planetinfo.record]: population = 36; 12:48:04.807 [planetinfo.record]: productivity = 16128; 12:48:04.807 [planetinfo.record]: name = "Erisso"; 12:48:04.808 [planetinfo.record]: inhabitant = "Human Colonial"; 12:48:04.808 [planetinfo.record]: inhabitants = "Human Colonials"; 12:48:04.808 [planetinfo.record]: description = "The world Erisso is very noted for its ancient A’ tulip plantations but ravaged by unpredictable solar activity."; 12:48:04.820 [planetinfo.record]: air_color = 0.656742, 0.879328, 0.856507, 1; 12:48:04.820 [planetinfo.record]: cloud_alpha = 1.000000; 12:48:04.820 [planetinfo.record]: cloud_color = 0.640625, 0.610166, 0.538025, 1; 12:48:04.820 [planetinfo.record]: cloud_fraction = 0.350000; 12:48:04.820 [planetinfo.record]: land_color = 0.338137, 0.175313, 0.66, 1; 12:48:04.820 [planetinfo.record]: land_fraction = 0.330000; 12:48:04.821 [planetinfo.record]: polar_cloud_color = 0.788281, 0.764856, 0.709376, 1; 12:48:04.821 [planetinfo.record]: polar_land_color = 0.820129, 0.762523, 0.934, 1; 12:48:04.821 [planetinfo.record]: polar_sea_color = 0.93298, 0.940039, 0.892486, 1; 12:48:04.821 [planetinfo.record]: sea_color = 0.5816, 0.599609, 0.478282, 1; 12:48:04.821 [planetinfo.record]: rotation_speed = 0.001895 12:48:04.821 [planetinfo.record]: planet zpos = 290500.000000 12:48:04.821 [planetinfo.record]: sun_radius = 68669.277954 12:48:04.821 [planetinfo.record]: sun_vector = -0.681 -0.490 -0.544 12:48:04.821 [planetinfo.record]: sun_distance = 668150 12:48:04.821 [planetinfo.record]: corona_flare = 0.096594 12:48:04.821 [planetinfo.record]: corona_hues = 0.516006 12:48:04.821 [planetinfo.record]: sun_color = 0.705267, 0.588016, 0.395295, 1 12:48:04.821 [planetinfo.record]: corona_shimmer = 0.639838 12:48:04.821 [planetinfo.record]: station_vector = 0.046 -0.993 0.105 12:48:04.821 [planetinfo.record]: station = coriolis 12:48:08.579 [PLANETINFO OVER]: Done 12:48:08.579 [PLANETINFO LOGGING]: 3 53 12:48:09.591 [planetinfo.record]: seed = 114 117 202 218 12:48:09.592 [planetinfo.record]: coordinates = 117 231 12:48:09.592 [planetinfo.record]: government = 6; 12:48:09.592 [planetinfo.record]: economy = 7; 12:48:09.592 [planetinfo.record]: techlevel = 4; 12:48:09.592 [planetinfo.record]: population = 30; 12:48:09.592 [planetinfo.record]: productivity = 7200; 12:48:09.592 [planetinfo.record]: name = "Qutius"; 12:48:09.592 [planetinfo.record]: inhabitant = "Horned Bird"; 12:48:09.592 [planetinfo.record]: inhabitants = "Horned Birds"; 12:48:09.592 [planetinfo.record]: description = "The planet Qutius is very noted for its exciting sit coms but ravaged by vicious mountain goats."; 12:48:09.605 [planetinfo.record]: air_color = 0.78443, 0.63978, 0.861117, 1; 12:48:09.605 [planetinfo.record]: cloud_alpha = 1.000000; 12:48:09.605 [planetinfo.record]: cloud_color = 0.585938, 0.485229, 0.48759, 1; 12:48:09.605 [planetinfo.record]: cloud_fraction = 0.190000; 12:48:09.605 [planetinfo.record]: land_color = 0.631641, 0.66, 0.657341, 1; 12:48:09.606 [planetinfo.record]: land_fraction = 0.560000; 12:48:09.606 [planetinfo.record]: polar_cloud_color = 0.763672, 0.681637, 0.68356, 1; 12:48:09.606 [planetinfo.record]: polar_land_color = 0.923967, 0.934, 0.933059, 1; 12:48:09.606 [planetinfo.record]: polar_sea_color = 0.77248, 0.939453, 0.939453, 1; 12:48:09.606 [planetinfo.record]: sea_color = 0.175018, 0.605469, 0.605469, 1; 12:48:09.606 [planetinfo.record]: rotation_speed = 0.003242 12:48:09.606 [planetinfo.record]: planet zpos = 769020.000000 12:48:09.606 [planetinfo.record]: sun_radius = 172178.426971 12:48:09.606 [planetinfo.record]: sun_vector = 0.036 0.983 -0.181 12:48:09.606 [planetinfo.record]: sun_distance = 988740 12:48:09.606 [planetinfo.record]: corona_flare = 0.009944 12:48:09.606 [planetinfo.record]: corona_hues = 0.645966 12:48:09.606 [planetinfo.record]: sun_color = 0.656946, 0.631577, 0.294421, 1 12:48:09.607 [planetinfo.record]: corona_shimmer = 0.548171 12:48:09.607 [planetinfo.record]: station_vector = -0.025 0.854 0.520 12:48:09.607 [planetinfo.record]: station = coriolis 12:48:13.832 [PLANETINFO OVER]: Done 12:48:13.833 [PLANETINFO LOGGING]: 3 54 12:48:14.856 [planetinfo.record]: seed = 210 154 218 90 12:48:14.856 [planetinfo.record]: coordinates = 154 136 12:48:14.856 [planetinfo.record]: government = 2; 12:48:14.856 [planetinfo.record]: economy = 0; 12:48:14.856 [planetinfo.record]: techlevel = 10; 12:48:14.856 [planetinfo.record]: population = 43; 12:48:14.856 [planetinfo.record]: productivity = 20640; 12:48:14.856 [planetinfo.record]: name = "Quisbe"; 12:48:14.856 [planetinfo.record]: inhabitant = "Yellow Horned Bird"; 12:48:14.856 [planetinfo.record]: inhabitants = "Yellow Horned Birds"; 12:48:14.856 [planetinfo.record]: description = "This world is ravaged by dreadful civil war."; 12:48:14.868 [planetinfo.record]: air_color = 0.47235, 0.565858, 0.896238, 1; 12:48:14.868 [planetinfo.record]: cloud_alpha = 1.000000; 12:48:14.868 [planetinfo.record]: cloud_color = 0.124237, 0.492011, 0.691406, 1; 12:48:14.868 [planetinfo.record]: cloud_fraction = 0.310000; 12:48:14.868 [planetinfo.record]: land_color = 0.607471, 0.66, 0.239766, 1; 12:48:14.868 [planetinfo.record]: land_fraction = 0.290000; 12:48:14.868 [planetinfo.record]: polar_cloud_color = 0.395269, 0.664931, 0.811133, 1; 12:48:14.868 [planetinfo.record]: polar_land_color = 0.915416, 0.934, 0.785326, 1; 12:48:14.868 [planetinfo.record]: polar_sea_color = 0.945117, 0.916677, 0.895923, 1; 12:48:14.868 [planetinfo.record]: sea_color = 0.548828, 0.482767, 0.43456, 1; 12:48:14.868 [planetinfo.record]: rotation_speed = 0.003814 12:48:14.868 [planetinfo.record]: planet zpos = 718900.000000 12:48:14.868 [planetinfo.record]: sun_radius = 101204.164124 12:48:14.868 [planetinfo.record]: sun_vector = -0.987 -0.161 -0.001 12:48:14.868 [planetinfo.record]: sun_distance = 1106000 12:48:14.868 [planetinfo.record]: corona_flare = 0.084706 12:48:14.868 [planetinfo.record]: corona_hues = 0.815018 12:48:14.868 [planetinfo.record]: sun_color = 0.65066, 0.727338, 0.729279, 1 12:48:14.868 [planetinfo.record]: corona_shimmer = 0.468704 12:48:14.869 [planetinfo.record]: station_vector = -0.981 -0.178 0.073 12:48:14.869 [planetinfo.record]: station = coriolis 12:48:20.165 [PLANETINFO OVER]: Done 12:48:20.166 [PLANETINFO LOGGING]: 3 55 12:48:21.173 [planetinfo.record]: seed = 34 76 106 61 12:48:21.173 [planetinfo.record]: coordinates = 76 115 12:48:21.173 [planetinfo.record]: government = 4; 12:48:21.173 [planetinfo.record]: economy = 3; 12:48:21.173 [planetinfo.record]: techlevel = 6; 12:48:21.173 [planetinfo.record]: population = 32; 12:48:21.173 [planetinfo.record]: productivity = 14336; 12:48:21.173 [planetinfo.record]: name = "Isisbile"; 12:48:21.173 [planetinfo.record]: inhabitant = "Human Colonial"; 12:48:21.173 [planetinfo.record]: inhabitants = "Human Colonials"; 12:48:21.173 [planetinfo.record]: description = "The world Isisbile is a boring world."; 12:48:21.188 [planetinfo.record]: air_color = 0.615407, 0.918352, 0.889657, 1; 12:48:21.188 [planetinfo.record]: cloud_alpha = 1.000000; 12:48:21.188 [planetinfo.record]: cloud_color = 0.757812, 0.676869, 0.461792, 1; 12:48:21.188 [planetinfo.record]: cloud_fraction = 0.410000; 12:48:21.188 [planetinfo.record]: land_color = 0.127419, 0.572266, 0.207352, 1; 12:48:21.188 [planetinfo.record]: land_fraction = 0.650000; 12:48:21.188 [planetinfo.record]: polar_cloud_color = 0.841016, 0.784872, 0.63569, 1; 12:48:21.188 [planetinfo.record]: polar_land_color = 0.759559, 0.942773, 0.79248, 1; 12:48:21.188 [planetinfo.record]: polar_sea_color = 0.95, 0.897212, 0.920719, 1; 12:48:21.188 [planetinfo.record]: sea_color = 0.5, 0.388867, 0.438356, 1; 12:48:21.188 [planetinfo.record]: rotation_speed = 0.003789 12:48:21.188 [planetinfo.record]: planet zpos = 808600.000000 12:48:21.188 [planetinfo.record]: sun_radius = 192065.847778 12:48:21.188 [planetinfo.record]: sun_vector = 0.630 0.178 -0.756 12:48:21.188 [planetinfo.record]: sun_distance = 1119600 12:48:21.188 [planetinfo.record]: corona_flare = 0.002200 12:48:21.188 [planetinfo.record]: corona_hues = 0.600258 12:48:21.188 [planetinfo.record]: sun_color = 0.55423, 0.610372, 0.754382, 1 12:48:21.189 [planetinfo.record]: corona_shimmer = 0.247069 12:48:21.189 [planetinfo.record]: station_vector = -0.727 0.431 0.534 12:48:21.189 [planetinfo.record]: station = coriolis 12:48:26.498 [PLANETINFO OVER]: Done 12:48:26.499 [PLANETINFO LOGGING]: 3 56 12:48:27.525 [planetinfo.record]: seed = 2 193 154 68 12:48:27.525 [planetinfo.record]: coordinates = 193 134 12:48:27.525 [planetinfo.record]: government = 0; 12:48:27.525 [planetinfo.record]: economy = 6; 12:48:27.525 [planetinfo.record]: techlevel = 2; 12:48:27.525 [planetinfo.record]: population = 15; 12:48:27.525 [planetinfo.record]: productivity = 1920; 12:48:27.525 [planetinfo.record]: name = "Zainat"; 12:48:27.525 [planetinfo.record]: inhabitant = "Fierce Yellow Insect"; 12:48:27.525 [planetinfo.record]: inhabitants = "Fierce Yellow Insects"; 12:48:27.525 [planetinfo.record]: description = "Zainat is reasonably well known for the Zainatian spotted cat but ravaged by occasional earthquakes."; 12:48:27.540 [planetinfo.record]: air_color = 0.446687, 0.470575, 0.935262, 1; 12:48:27.540 [planetinfo.record]: cloud_alpha = 1.000000; 12:48:27.540 [planetinfo.record]: cloud_color = 0.0252686, 0.129304, 0.808594, 1; 12:48:27.540 [planetinfo.record]: cloud_fraction = 0.450000; 12:48:27.540 [planetinfo.record]: land_color = 0.254486, 0.474596, 0.525391, 1; 12:48:27.540 [planetinfo.record]: land_fraction = 0.290000; 12:48:27.540 [planetinfo.record]: polar_cloud_color = 0.340823, 0.410289, 0.863867, 1; 12:48:27.540 [planetinfo.record]: polar_land_color = 0.825327, 0.924561, 0.947461, 1; 12:48:27.540 [planetinfo.record]: polar_sea_color = 0.87333, 0.925, 0.899569, 1; 12:48:27.540 [planetinfo.record]: sea_color = 0.582422, 0.75, 0.66752, 1; 12:48:27.540 [planetinfo.record]: rotation_speed = 0.003185 12:48:27.540 [planetinfo.record]: planet zpos = 443630.000000 12:48:27.540 [planetinfo.record]: sun_radius = 86082.789917 12:48:27.540 [planetinfo.record]: sun_vector = -0.253 0.348 -0.903 12:48:27.540 [planetinfo.record]: sun_distance = 887260 12:48:27.540 [planetinfo.record]: corona_flare = 0.050906 12:48:27.540 [planetinfo.record]: corona_hues = 0.600258 12:48:27.540 [planetinfo.record]: sun_color = 0.322158, 0.483131, 0.837857, 1 12:48:27.541 [planetinfo.record]: corona_shimmer = 0.453047 12:48:27.541 [planetinfo.record]: station_vector = 0.662 0.477 0.578 12:48:27.541 [planetinfo.record]: station = coriolis 12:48:32.463 [PLANETINFO OVER]: Done 12:48:32.463 [PLANETINFO LOGGING]: 3 57 12:48:33.481 [planetinfo.record]: seed = 146 98 138 128 12:48:33.481 [planetinfo.record]: coordinates = 98 145 12:48:33.481 [planetinfo.record]: government = 2; 12:48:33.481 [planetinfo.record]: economy = 1; 12:48:33.481 [planetinfo.record]: techlevel = 9; 12:48:33.481 [planetinfo.record]: population = 40; 12:48:33.481 [planetinfo.record]: productivity = 17280; 12:48:33.481 [planetinfo.record]: name = "Raeddi"; 12:48:33.481 [planetinfo.record]: inhabitant = "Large Black Bony Lobster"; 12:48:33.481 [planetinfo.record]: inhabitants = "Large Black Bony Lobsters"; 12:48:33.481 [planetinfo.record]: description = "This world is very well known for its inhabitants’ unusual mating traditions and its vast rain forests."; 12:48:33.537 [planetinfo.record]: air_color = 0.613887, 0.919652, 0.893076, 1; 12:48:33.537 [planetinfo.record]: cloud_alpha = 1.000000; 12:48:33.537 [planetinfo.record]: cloud_color = 0.761719, 0.685844, 0.458221, 1; 12:48:33.537 [planetinfo.record]: cloud_fraction = 0.540000; 12:48:33.537 [planetinfo.record]: land_color = 0.217201, 0.636719, 0.00497437, 1; 12:48:33.537 [planetinfo.record]: land_fraction = 0.580000; 12:48:33.537 [planetinfo.record]: polar_cloud_color = 0.842773, 0.790306, 0.632903, 1; 12:48:33.538 [planetinfo.record]: polar_land_color = 0.782097, 0.936328, 0.704075, 1; 12:48:33.538 [planetinfo.record]: polar_sea_color = 0.865362, 0.923437, 0.874436, 1; 12:48:33.538 [planetinfo.record]: sea_color = 0.573022, 0.765625, 0.603117, 1; 12:48:33.538 [planetinfo.record]: rotation_speed = 0.001948 12:48:33.538 [planetinfo.record]: planet zpos = 407960.000000 12:48:33.538 [planetinfo.record]: sun_radius = 60853.582764 12:48:33.538 [planetinfo.record]: sun_vector = 0.214 0.240 -0.947 12:48:33.538 [planetinfo.record]: sun_distance = 466240 12:48:33.538 [planetinfo.record]: corona_flare = 0.005824 12:48:33.538 [planetinfo.record]: corona_hues = 0.692398 12:48:33.538 [planetinfo.record]: sun_color = 0.347582, 0.614154, 0.718301, 1 12:48:33.538 [planetinfo.record]: corona_shimmer = 0.295008 12:48:33.539 [planetinfo.record]: station_vector = 0.686 0.616 0.387 12:48:33.539 [planetinfo.record]: station = coriolis 12:48:37.543 [PLANETINFO OVER]: Done 12:48:37.543 [PLANETINFO LOGGING]: 3 58 12:48:38.553 [planetinfo.record]: seed = 114 77 90 26 12:48:38.553 [planetinfo.record]: coordinates = 77 88 12:48:38.553 [planetinfo.record]: government = 6; 12:48:38.554 [planetinfo.record]: economy = 0; 12:48:38.554 [planetinfo.record]: techlevel = 11; 12:48:38.554 [planetinfo.record]: population = 51; 12:48:38.554 [planetinfo.record]: productivity = 40800; 12:48:38.554 [planetinfo.record]: name = "Quonso"; 12:48:38.554 [planetinfo.record]: inhabitant = "Human Colonial"; 12:48:38.554 [planetinfo.record]: inhabitants = "Human Colonials"; 12:48:38.554 [planetinfo.record]: description = "The world Quonso is very noted for its exciting sit coms but ravaged by vicious mountain beasts."; 12:48:38.592 [planetinfo.record]: air_color = 0.684299, 0.865042, 0.872174, 1; 12:48:38.592 [planetinfo.record]: cloud_alpha = 1.000000; 12:48:38.592 [planetinfo.record]: cloud_color = 0.615513, 0.619141, 0.590118, 1; 12:48:38.592 [planetinfo.record]: cloud_fraction = 0.130000; 12:48:38.592 [planetinfo.record]: land_color = 0.66, 0.237188, 0.237188, 1; 12:48:38.592 [planetinfo.record]: land_fraction = 0.320000; 12:48:38.592 [planetinfo.record]: polar_cloud_color = 0.775762, 0.778613, 0.755802, 1; 12:48:38.592 [planetinfo.record]: polar_land_color = 0.934, 0.784414, 0.784414, 1; 12:48:38.592 [planetinfo.record]: polar_sea_color = 0.937305, 0.847602, 0.916981, 1; 12:48:38.592 [planetinfo.record]: sea_color = 0.626953, 0.386948, 0.572577, 1; 12:48:38.592 [planetinfo.record]: rotation_speed = 0.000023 12:48:38.592 [planetinfo.record]: planet zpos = 654360.000000 12:48:38.592 [planetinfo.record]: sun_radius = 125488.227539 12:48:38.592 [planetinfo.record]: sun_vector = 0.670 -0.709 -0.221 12:48:38.592 [planetinfo.record]: sun_distance = 872480 12:48:38.592 [planetinfo.record]: corona_flare = 0.028293 12:48:38.593 [planetinfo.record]: corona_hues = 0.792755 12:48:38.593 [planetinfo.record]: sun_color = 0.724384, 0.724643, 0.370529, 1 12:48:38.593 [planetinfo.record]: corona_shimmer = 0.474040 12:48:38.593 [planetinfo.record]: station_vector = 0.465 -0.864 0.191 12:48:38.593 [planetinfo.record]: station = dodecahedron 12:48:43.820 [PLANETINFO OVER]: Done 12:48:43.821 [PLANETINFO LOGGING]: 3 59 12:48:44.843 [planetinfo.record]: seed = 194 1 42 233 12:48:44.843 [planetinfo.record]: coordinates = 1 39 12:48:44.843 [planetinfo.record]: government = 0; 12:48:44.843 [planetinfo.record]: economy = 7; 12:48:44.843 [planetinfo.record]: techlevel = 1; 12:48:44.843 [planetinfo.record]: population = 12; 12:48:44.843 [planetinfo.record]: productivity = 1152; 12:48:44.843 [planetinfo.record]: name = "Esenis"; 12:48:44.843 [planetinfo.record]: inhabitant = "Human Colonial"; 12:48:44.843 [planetinfo.record]: inhabitants = "Human Colonials"; 12:48:44.843 [planetinfo.record]: description = "This world is a revolting little planet."; 12:48:44.848 [planetinfo.record]: air_color = 0.679335, 0.866333, 0.88193, 1; 12:48:44.849 [planetinfo.record]: cloud_alpha = 1.000000; 12:48:44.849 [planetinfo.record]: cloud_color = 0.634328, 0.648438, 0.590179, 1; 12:48:44.849 [planetinfo.record]: cloud_fraction = 0.310000; 12:48:44.849 [planetinfo.record]: land_color = 0.45375, 0.66, 0.616494, 1; 12:48:44.849 [planetinfo.record]: land_fraction = 0.660000; 12:48:44.849 [planetinfo.record]: polar_cloud_color = 0.781029, 0.791797, 0.747336, 1; 12:48:44.849 [planetinfo.record]: polar_land_color = 0.861031, 0.934, 0.918608, 1; 12:48:44.849 [planetinfo.record]: polar_sea_color = 0.932227, 0.9018, 0.869411, 1; 12:48:44.849 [planetinfo.record]: sea_color = 0.677734, 0.589253, 0.495064, 1; 12:48:44.849 [planetinfo.record]: rotation_speed = 0.002606 12:48:44.849 [planetinfo.record]: planet zpos = 716940.000000 12:48:44.849 [planetinfo.record]: sun_radius = 122130.880280 12:48:44.849 [planetinfo.record]: sun_vector = -0.967 -0.249 0.052 12:48:44.849 [planetinfo.record]: sun_distance = 1024200 12:48:44.849 [planetinfo.record]: corona_flare = 0.086070 12:48:44.849 [planetinfo.record]: corona_hues = 0.999191 12:48:44.849 [planetinfo.record]: sun_color = 0.837299, 0.780907, 0.739915, 1 12:48:44.849 [planetinfo.record]: corona_shimmer = 0.421616 12:48:44.850 [planetinfo.record]: station_vector = 0.475 -0.605 0.639 12:48:44.850 [planetinfo.record]: station = coriolis 12:48:50.436 [PLANETINFO OVER]: Done 12:48:50.437 [PLANETINFO LOGGING]: 3 60 12:48:51.442 [planetinfo.record]: seed = 34 249 26 9 12:48:51.442 [planetinfo.record]: coordinates = 249 253 12:48:51.442 [planetinfo.record]: government = 4; 12:48:51.442 [planetinfo.record]: economy = 5; 12:48:51.442 [planetinfo.record]: techlevel = 5; 12:48:51.442 [planetinfo.record]: population = 30; 12:48:51.442 [planetinfo.record]: productivity = 9600; 12:48:51.442 [planetinfo.record]: name = "Esonlear"; 12:48:51.442 [planetinfo.record]: inhabitant = "Human Colonial"; 12:48:51.442 [planetinfo.record]: inhabitants = "Human Colonials"; 12:48:51.442 [planetinfo.record]: description = "Esonlear is a revolting dump."; 12:48:51.468 [planetinfo.record]: air_color = 0.419347, 0.843557, 0.829862, 1; 12:48:51.468 [planetinfo.record]: cloud_alpha = 1.000000; 12:48:51.468 [planetinfo.record]: cloud_color = 0.533203, 0.491498, 0.047905, 1; 12:48:51.468 [planetinfo.record]: cloud_fraction = 0.240000; 12:48:51.468 [planetinfo.record]: land_color = 0.66, 0.20625, 0.589102, 1; 12:48:51.468 [planetinfo.record]: land_fraction = 0.260000; 12:48:51.468 [planetinfo.record]: polar_cloud_color = 0.739941, 0.703769, 0.319027, 1; 12:48:51.468 [planetinfo.record]: polar_land_color = 0.934, 0.773469, 0.908917, 1; 12:48:51.468 [planetinfo.record]: polar_sea_color = 0.934961, 0.903571, 0.873148, 1; 12:48:51.468 [planetinfo.record]: sea_color = 0.650391, 0.563048, 0.478393, 1; 12:48:51.468 [planetinfo.record]: rotation_speed = 0.004416 12:48:51.468 [planetinfo.record]: planet zpos = 751660.000000 12:48:51.468 [planetinfo.record]: sun_radius = 108390.128326 12:48:51.468 [planetinfo.record]: sun_vector = -0.273 -0.941 -0.201 12:48:51.468 [planetinfo.record]: sun_distance = 1073800 12:48:51.468 [planetinfo.record]: corona_flare = 0.005705 12:48:51.468 [planetinfo.record]: corona_hues = 0.876099 12:48:51.468 [planetinfo.record]: sun_color = 0.560843, 0.575536, 0.654999, 1 12:48:51.468 [planetinfo.record]: corona_shimmer = 0.525524 12:48:51.469 [planetinfo.record]: station_vector = 0.133 -0.928 0.349 12:48:51.469 [planetinfo.record]: station = coriolis 12:48:56.113 [PLANETINFO OVER]: Done 12:48:56.114 [PLANETINFO LOGGING]: 3 61 12:48:57.122 [planetinfo.record]: seed = 178 10 74 12 12:48:57.122 [planetinfo.record]: coordinates = 10 1 12:48:57.122 [planetinfo.record]: government = 6; 12:48:57.122 [planetinfo.record]: economy = 1; 12:48:57.122 [planetinfo.record]: techlevel = 11; 12:48:57.123 [planetinfo.record]: population = 52; 12:48:57.123 [planetinfo.record]: productivity = 37440; 12:48:57.123 [planetinfo.record]: name = "Inedala"; 12:48:57.123 [planetinfo.record]: inhabitant = "Human Colonial"; 12:48:57.123 [planetinfo.record]: inhabitants = "Human Colonials"; 12:48:57.123 [planetinfo.record]: description = "Inedala is mildly notable for its inhabitants’ unusual silliness."; 12:48:57.152 [planetinfo.record]: air_color = 0.595011, 0.929408, 0.882075, 1; 12:48:57.152 [planetinfo.record]: cloud_alpha = 1.000000; 12:48:57.152 [planetinfo.record]: cloud_color = 0.791016, 0.633648, 0.410957, 1; 12:48:57.152 [planetinfo.record]: cloud_fraction = 0.370000; 12:48:57.152 [planetinfo.record]: land_color = 0.591797, 0.388367, 0.58385, 1; 12:48:57.152 [planetinfo.record]: land_fraction = 0.460000; 12:48:57.152 [planetinfo.record]: polar_cloud_color = 0.855957, 0.749527, 0.598919, 1; 12:48:57.152 [planetinfo.record]: polar_land_color = 0.94082, 0.859969, 0.937662, 1; 12:48:57.152 [planetinfo.record]: polar_sea_color = 0.884963, 0.928906, 0.874297, 1; 12:48:57.152 [planetinfo.record]: sea_color = 0.576409, 0.710938, 0.543756, 1; 12:48:57.152 [planetinfo.record]: rotation_speed = 0.000631 12:48:57.152 [planetinfo.record]: planet zpos = 707760.000000 12:48:57.152 [planetinfo.record]: sun_radius = 183048.951416 12:48:57.152 [planetinfo.record]: sun_vector = 0.440 0.117 -0.890 12:48:57.152 [planetinfo.record]: sun_distance = 1002660 12:48:57.152 [planetinfo.record]: corona_flare = 0.017694 12:48:57.152 [planetinfo.record]: corona_hues = 0.755966 12:48:57.153 [planetinfo.record]: sun_color = 0.680579, 0.562832, 0.394651, 1 12:48:57.153 [planetinfo.record]: corona_shimmer = 0.296251 12:48:57.153 [planetinfo.record]: station_vector = 0.229 -0.934 0.275 12:48:57.153 [planetinfo.record]: station = icosahedron 12:49:01.539 [PLANETINFO OVER]: Done 12:49:01.540 [PLANETINFO LOGGING]: 3 62 12:49:02.545 [planetinfo.record]: seed = 18 85 218 157 12:49:02.546 [planetinfo.record]: coordinates = 85 47 12:49:02.546 [planetinfo.record]: government = 2; 12:49:02.546 [planetinfo.record]: economy = 7; 12:49:02.546 [planetinfo.record]: techlevel = 2; 12:49:02.546 [planetinfo.record]: population = 18; 12:49:02.546 [planetinfo.record]: productivity = 2592; 12:49:02.546 [planetinfo.record]: name = "Isxelave"; 12:49:02.546 [planetinfo.record]: inhabitant = "Black Horned Lobster"; 12:49:02.546 [planetinfo.record]: inhabitants = "Black Horned Lobsters"; 12:49:02.546 [planetinfo.record]: description = "This world is mildly well known for Isxelaveian shrew cutlet and the Isxelaveian edible moth."; 12:49:02.580 [planetinfo.record]: air_color = 0.632793, 0.885832, 0.830266, 1; 12:49:02.580 [planetinfo.record]: cloud_alpha = 1.000000; 12:49:02.580 [planetinfo.record]: cloud_color = 0.660156, 0.549794, 0.48996, 1; 12:49:02.580 [planetinfo.record]: cloud_fraction = 0.330000; 12:49:02.580 [planetinfo.record]: land_color = 0.273584, 0.626953, 0.00734711, 1; 12:49:02.580 [planetinfo.record]: land_fraction = 0.260000; 12:49:02.580 [planetinfo.record]: polar_cloud_color = 0.79707, 0.713789, 0.668636, 1; 12:49:02.580 [planetinfo.record]: polar_land_color = 0.805232, 0.937305, 0.705725, 1; 12:49:02.580 [planetinfo.record]: polar_sea_color = 0.885577, 0.864078, 0.911523, 1; 12:49:02.580 [planetinfo.record]: sea_color = 0.784025, 0.700555, 0.884766, 1; 12:49:02.580 [planetinfo.record]: rotation_speed = 0.001335 12:49:02.580 [planetinfo.record]: planet zpos = 809770.000000 12:49:02.580 [planetinfo.record]: sun_radius = 160342.383118 12:49:02.581 [planetinfo.record]: sun_vector = -0.093 0.952 -0.291 12:49:02.581 [planetinfo.record]: sun_distance = 1308090 12:49:02.581 [planetinfo.record]: corona_flare = 0.089679 12:49:02.581 [planetinfo.record]: corona_hues = 0.770744 12:49:02.581 [planetinfo.record]: sun_color = 0.841183, 0.793481, 0.660416, 1 12:49:02.581 [planetinfo.record]: corona_shimmer = 0.461073 12:49:02.581 [planetinfo.record]: station_vector = 0.969 -0.138 0.203 12:49:02.581 [planetinfo.record]: station = coriolis 12:49:07.968 [PLANETINFO OVER]: Done 12:49:07.969 [PLANETINFO LOGGING]: 3 63 12:49:08.995 [planetinfo.record]: seed = 98 214 234 14 12:49:08.995 [planetinfo.record]: coordinates = 214 21 12:49:08.995 [planetinfo.record]: government = 4; 12:49:08.995 [planetinfo.record]: economy = 5; 12:49:08.995 [planetinfo.record]: techlevel = 6; 12:49:08.995 [planetinfo.record]: population = 34; 12:49:08.995 [planetinfo.record]: productivity = 10880; 12:49:08.995 [planetinfo.record]: name = "Rean"; 12:49:08.995 [planetinfo.record]: inhabitant = "Green Bony Humanoid"; 12:49:08.995 [planetinfo.record]: inhabitants = "Green Bony Humanoids"; 12:49:08.995 [planetinfo.record]: description = "This world is reasonably notable for its exotic night life but cursed by deadly civil war."; 12:49:09.012 [planetinfo.record]: air_color = 0.556884, 0.519359, 0.896889, 1; 12:49:09.012 [planetinfo.record]: cloud_alpha = 1.000000; 12:49:09.012 [planetinfo.record]: cloud_color = 0.376811, 0.232925, 0.693359, 1; 12:49:09.012 [planetinfo.record]: cloud_fraction = 0.510000; 12:49:09.012 [planetinfo.record]: land_color = 0.215332, 0.52375, 0.574219, 1; 12:49:09.012 [planetinfo.record]: land_fraction = 0.610000; 12:49:09.012 [planetinfo.record]: polar_cloud_color = 0.580313, 0.474995, 0.812012, 1; 12:49:09.012 [planetinfo.record]: polar_land_color = 0.7953, 0.921867, 0.942578, 1; 12:49:09.012 [planetinfo.record]: polar_sea_color = 0.867837, 0.920703, 0.856865, 1; 12:49:09.012 [planetinfo.record]: sea_color = 0.610843, 0.792969, 0.573044, 1; 12:49:09.012 [planetinfo.record]: rotation_speed = 0.001289 12:49:09.012 [planetinfo.record]: planet zpos = 793680.000000 12:49:09.012 [planetinfo.record]: sun_radius = 184243.538513 12:49:09.012 [planetinfo.record]: sun_vector = -0.418 -0.616 -0.668 12:49:09.012 [planetinfo.record]: sun_distance = 1322800 12:49:09.012 [planetinfo.record]: corona_flare = 0.045851 12:49:09.012 [planetinfo.record]: corona_hues = 0.756882 12:49:09.013 [planetinfo.record]: sun_color = 0.361827, 0.519735, 0.832718, 1 12:49:09.013 [planetinfo.record]: corona_shimmer = 0.310874 12:49:09.013 [planetinfo.record]: station_vector = 0.758 0.490 0.430 12:49:09.013 [planetinfo.record]: station = coriolis 12:49:14.576 [PLANETINFO OVER]: Done 12:49:14.577 [PLANETINFO LOGGING]: 3 64 12:49:15.596 [planetinfo.record]: seed = 66 234 154 197 12:49:15.596 [planetinfo.record]: coordinates = 234 224 12:49:15.596 [planetinfo.record]: government = 0; 12:49:15.596 [planetinfo.record]: economy = 2; 12:49:15.596 [planetinfo.record]: techlevel = 7; 12:49:15.596 [planetinfo.record]: population = 31; 12:49:15.596 [planetinfo.record]: productivity = 7936; 12:49:15.596 [planetinfo.record]: name = "Ceerla"; 12:49:15.596 [planetinfo.record]: inhabitant = "Fierce Horned Lobster"; 12:49:15.596 [planetinfo.record]: inhabitants = "Fierce Horned Lobsters"; 12:49:15.596 [planetinfo.record]: description = "This planet is mildly noted for its ancient mountains but plagued by deadly earthquakes."; 12:49:15.601 [planetinfo.record]: air_color = 0.616711, 0.916252, 0.938514, 1; 12:49:15.601 [planetinfo.record]: cloud_alpha = 1.000000; 12:49:15.601 [planetinfo.record]: cloud_color = 0.741438, 0.818359, 0.466721, 1; 12:49:15.601 [planetinfo.record]: cloud_fraction = 0.470000; 12:49:15.601 [planetinfo.record]: land_color = 0.574922, 0.66, 0.600179, 1; 12:49:15.601 [planetinfo.record]: land_fraction = 0.560000; 12:49:15.601 [planetinfo.record]: polar_cloud_color = 0.817254, 0.868262, 0.635086, 1; 12:49:15.601 [planetinfo.record]: polar_land_color = 0.9039, 0.934, 0.912836, 1; 12:49:15.602 [planetinfo.record]: polar_sea_color = 0.850259, 0.881707, 0.948438, 1; 12:49:15.602 [planetinfo.record]: sea_color = 0.302124, 0.370511, 0.515625, 1; 12:49:15.602 [planetinfo.record]: rotation_speed = 0.000751 12:49:15.602 [planetinfo.record]: planet zpos = 562900.000000 12:49:15.602 [planetinfo.record]: sun_radius = 119693.420410 12:49:15.602 [planetinfo.record]: sun_vector = -0.369 -0.237 -0.899 12:49:15.602 [planetinfo.record]: sun_distance = 736100 12:49:15.602 [planetinfo.record]: corona_flare = 0.033591 12:49:15.602 [planetinfo.record]: corona_hues = 0.625572 12:49:15.602 [planetinfo.record]: sun_color = 0.757019, 0.410697, 0.267277, 1 12:49:15.602 [planetinfo.record]: corona_shimmer = 0.546006 12:49:15.602 [planetinfo.record]: station_vector = -0.670 0.431 0.604 12:49:15.602 [planetinfo.record]: station = coriolis 12:49:20.249 [PLANETINFO OVER]: Done 12:49:20.250 [PLANETINFO LOGGING]: 3 65 12:49:21.260 [planetinfo.record]: seed = 210 149 10 102 12:49:21.260 [planetinfo.record]: coordinates = 149 64 12:49:21.260 [planetinfo.record]: government = 2; 12:49:21.260 [planetinfo.record]: economy = 0; 12:49:21.260 [planetinfo.record]: techlevel = 9; 12:49:21.260 [planetinfo.record]: population = 39; 12:49:21.260 [planetinfo.record]: productivity = 18720; 12:49:21.260 [planetinfo.record]: name = "Bianti"; 12:49:21.260 [planetinfo.record]: inhabitant = "Human Colonial"; 12:49:21.260 [planetinfo.record]: inhabitants = "Human Colonials"; 12:49:21.260 [planetinfo.record]: description = "This world is ravaged by dreadful civil war."; 12:49:21.271 [planetinfo.record]: air_color = 0.664462, 0.7612, 0.980139, 1; 12:49:21.271 [planetinfo.record]: cloud_alpha = 1.000000; 12:49:21.271 [planetinfo.record]: cloud_color = 0.59697, 0.910885, 0.943359, 1; 12:49:21.271 [planetinfo.record]: cloud_fraction = 0.250000; 12:49:21.271 [planetinfo.record]: land_color = 0.53625, 0.66, 0.596191, 1; 12:49:21.272 [planetinfo.record]: land_fraction = 0.350000; 12:49:21.272 [planetinfo.record]: polar_cloud_color = 0.712344, 0.904621, 0.924512, 1; 12:49:21.272 [planetinfo.record]: polar_land_color = 0.890219, 0.934, 0.911425, 1; 12:49:21.272 [planetinfo.record]: polar_sea_color = 0.92207, 0.884047, 0.818518, 1; 12:49:21.272 [planetinfo.record]: sea_color = 0.779297, 0.650754, 0.429222, 1; 12:49:21.272 [planetinfo.record]: rotation_speed = 0.004444 12:49:21.272 [planetinfo.record]: planet zpos = 495110.000000 12:49:21.272 [planetinfo.record]: sun_radius = 127191.573334 12:49:21.272 [planetinfo.record]: sun_vector = -0.682 0.716 0.146 12:49:21.272 [planetinfo.record]: sun_distance = 945210 12:49:21.272 [planetinfo.record]: corona_flare = 0.028284 12:49:21.272 [planetinfo.record]: corona_hues = 0.820786 12:49:21.272 [planetinfo.record]: sun_color = 0.288709, 0.484833, 0.719348, 1 12:49:21.273 [planetinfo.record]: corona_shimmer = 0.284585 12:49:21.273 [planetinfo.record]: station_vector = 0.728 0.578 0.369 12:49:21.273 [planetinfo.record]: station = coriolis 12:49:25.471 [PLANETINFO OVER]: Done 12:49:25.471 [PLANETINFO LOGGING]: 3 66 12:49:26.487 [planetinfo.record]: seed = 178 217 90 77 12:49:26.487 [planetinfo.record]: coordinates = 217 55 12:49:26.487 [planetinfo.record]: government = 6; 12:49:26.487 [planetinfo.record]: economy = 7; 12:49:26.487 [planetinfo.record]: techlevel = 4; 12:49:26.487 [planetinfo.record]: population = 30; 12:49:26.487 [planetinfo.record]: productivity = 7200; 12:49:26.487 [planetinfo.record]: name = "Diriceen"; 12:49:26.487 [planetinfo.record]: inhabitant = "Human Colonial"; 12:49:26.487 [planetinfo.record]: inhabitants = "Human Colonials"; 12:49:26.487 [planetinfo.record]: description = "Diriceen is an unremarkable planet."; 12:49:26.492 [planetinfo.record]: air_color = 0.612359, 0.920953, 0.894131, 1; 12:49:26.492 [planetinfo.record]: cloud_alpha = 1.000000; 12:49:26.492 [planetinfo.record]: cloud_color = 0.765625, 0.687866, 0.45459, 1; 12:49:26.492 [planetinfo.record]: cloud_fraction = 0.140000; 12:49:26.492 [planetinfo.record]: land_color = 0.66, 0.516592, 0.45375, 1; 12:49:26.492 [planetinfo.record]: land_fraction = 0.620000; 12:49:26.492 [planetinfo.record]: polar_cloud_color = 0.844531, 0.790923, 0.630099, 1; 12:49:26.492 [planetinfo.record]: polar_land_color = 0.934, 0.883264, 0.861031, 1; 12:49:26.492 [planetinfo.record]: polar_sea_color = 0.86841, 0.864591, 0.913477, 1; 12:49:26.492 [planetinfo.record]: sea_color = 0.69449, 0.68002, 0.865234, 1; 12:49:26.492 [planetinfo.record]: rotation_speed = 0.002568 12:49:26.493 [planetinfo.record]: planet zpos = 826930.000000 12:49:26.493 [planetinfo.record]: sun_radius = 122536.799164 12:49:26.493 [planetinfo.record]: sun_vector = 0.241 0.800 -0.549 12:49:26.493 [planetinfo.record]: sun_distance = 1272200 12:49:26.493 [planetinfo.record]: corona_flare = 0.056870 12:49:26.493 [planetinfo.record]: corona_hues = 0.966415 12:49:26.493 [planetinfo.record]: sun_color = 0.231182, 0.346096, 0.749036, 1 12:49:26.493 [planetinfo.record]: corona_shimmer = 0.549402 12:49:26.493 [planetinfo.record]: station_vector = 0.685 -0.632 0.363 12:49:26.493 [planetinfo.record]: station = coriolis 12:49:31.117 [PLANETINFO OVER]: Done 12:49:31.118 [PLANETINFO LOGGING]: 3 67 12:49:32.127 [planetinfo.record]: seed = 2 50 170 22 12:49:32.127 [planetinfo.record]: coordinates = 50 133 12:49:32.127 [planetinfo.record]: government = 0; 12:49:32.127 [planetinfo.record]: economy = 7; 12:49:32.127 [planetinfo.record]: techlevel = 2; 12:49:32.127 [planetinfo.record]: population = 16; 12:49:32.127 [planetinfo.record]: productivity = 1536; 12:49:32.127 [planetinfo.record]: name = "Veretite"; 12:49:32.127 [planetinfo.record]: inhabitant = "Green Frog"; 12:49:32.127 [planetinfo.record]: inhabitants = "Green Frogs"; 12:49:32.127 [planetinfo.record]: description = "The planet Veretite is cursed by killer mountain monkeys."; 12:49:32.135 [planetinfo.record]: air_color = 0.431426, 0.804764, 0.850061, 1; 12:49:32.136 [planetinfo.record]: cloud_alpha = 1.000000; 12:49:32.136 [planetinfo.record]: cloud_color = 0.389535, 0.552734, 0.0669327, 1; 12:49:32.136 [planetinfo.record]: cloud_fraction = 0.280000; 12:49:32.136 [planetinfo.record]: land_color = 0.245178, 0.353299, 0.609375, 1; 12:49:32.136 [planetinfo.record]: land_fraction = 0.480000; 12:49:32.136 [planetinfo.record]: polar_cloud_color = 0.610563, 0.74873, 0.337441, 1; 12:49:32.136 [planetinfo.record]: polar_land_color = 0.798753, 0.840408, 0.939062, 1; 12:49:32.136 [planetinfo.record]: polar_sea_color = 0.877445, 0.927344, 0.898106, 1; 12:49:32.136 [planetinfo.record]: sea_color = 0.570181, 0.726562, 0.634933, 1; 12:49:32.136 [planetinfo.record]: rotation_speed = 0.000017 12:49:32.136 [planetinfo.record]: planet zpos = 396180.000000 12:49:32.136 [planetinfo.record]: sun_radius = 99377.487488 12:49:32.136 [planetinfo.record]: sun_vector = 0.484 0.763 0.430 12:49:32.136 [planetinfo.record]: sun_distance = 1012460 12:49:32.137 [planetinfo.record]: corona_flare = 0.049641 12:49:32.137 [planetinfo.record]: corona_hues = 0.553452 12:49:32.137 [planetinfo.record]: sun_color = 0.846735, 0.806203, 0.471766, 1 12:49:32.137 [planetinfo.record]: corona_shimmer = 0.271027 12:49:32.137 [planetinfo.record]: station_vector = 0.688 -0.565 0.456 12:49:32.137 [planetinfo.record]: station = coriolis 12:49:36.653 [PLANETINFO OVER]: Done 12:49:36.654 [PLANETINFO LOGGING]: 3 68 12:49:37.673 [planetinfo.record]: seed = 98 252 26 226 12:49:37.674 [planetinfo.record]: coordinates = 252 23 12:49:37.674 [planetinfo.record]: government = 4; 12:49:37.674 [planetinfo.record]: economy = 7; 12:49:37.674 [planetinfo.record]: techlevel = 2; 12:49:37.674 [planetinfo.record]: population = 20; 12:49:37.674 [planetinfo.record]: productivity = 3840; 12:49:37.674 [planetinfo.record]: name = "Xelara"; 12:49:37.674 [planetinfo.record]: inhabitant = "Human Colonial"; 12:49:37.674 [planetinfo.record]: inhabitants = "Human Colonials"; 12:49:37.674 [planetinfo.record]: description = "This world is a tedious place."; 12:49:37.684 [planetinfo.record]: air_color = 0.721064, 0.549317, 0.917701, 1; 12:49:37.685 [planetinfo.record]: cloud_alpha = 1.000000; 12:49:37.685 [planetinfo.record]: cloud_color = 0.755859, 0.29821, 0.566364, 1; 12:49:37.685 [planetinfo.record]: cloud_fraction = 0.420000; 12:49:37.685 [planetinfo.record]: land_color = 0.634523, 0.355667, 0.636719, 1; 12:49:37.685 [planetinfo.record]: land_fraction = 0.540000; 12:49:37.685 [planetinfo.record]: polar_cloud_color = 0.840137, 0.522214, 0.708497, 1; 12:49:37.685 [planetinfo.record]: polar_land_color = 0.935521, 0.833003, 0.936328, 1; 12:49:37.685 [planetinfo.record]: polar_sea_color = 0.902627, 0.927539, 0.866216, 1; 12:49:37.685 [planetinfo.record]: sea_color = 0.646762, 0.724609, 0.532984, 1; 12:49:37.685 [planetinfo.record]: rotation_speed = 0.002016 12:49:37.685 [planetinfo.record]: planet zpos = 393800.000000 12:49:37.685 [planetinfo.record]: sun_radius = 93367.553711 12:49:37.685 [planetinfo.record]: sun_vector = 0.585 0.200 -0.786 12:49:37.685 [planetinfo.record]: sun_distance = 751800 12:49:37.685 [planetinfo.record]: corona_flare = 0.042261 12:49:37.685 [planetinfo.record]: corona_hues = 0.907608 12:49:37.685 [planetinfo.record]: sun_color = 0.731458, 0.697142, 0.371636, 1 12:49:37.686 [planetinfo.record]: corona_shimmer = 0.395338 12:49:37.686 [planetinfo.record]: station_vector = -0.068 0.978 0.198 12:49:37.686 [planetinfo.record]: station = coriolis 12:49:42.134 [PLANETINFO OVER]: Done 12:49:42.135 [PLANETINFO LOGGING]: 3 69 12:49:43.140 [planetinfo.record]: seed = 242 171 202 117 12:49:43.140 [planetinfo.record]: coordinates = 171 212 12:49:43.140 [planetinfo.record]: government = 6; 12:49:43.140 [planetinfo.record]: economy = 4; 12:49:43.140 [planetinfo.record]: techlevel = 9; 12:49:43.140 [planetinfo.record]: population = 47; 12:49:43.140 [planetinfo.record]: productivity = 22560; 12:49:43.140 [planetinfo.record]: name = "Lalati"; 12:49:43.140 [planetinfo.record]: inhabitant = "Blue Rodent"; 12:49:43.140 [planetinfo.record]: inhabitants = "Blue Rodents"; 12:49:43.140 [planetinfo.record]: description = "Lalati is a revolting little planet."; 12:49:43.164 [planetinfo.record]: air_color = 0.578892, 0.889904, 0.988594, 1; 12:49:43.164 [planetinfo.record]: cloud_alpha = 1.000000; 12:49:43.164 [planetinfo.record]: cloud_color = 0.510214, 0.96875, 0.34436, 1; 12:49:43.164 [planetinfo.record]: cloud_fraction = 0.350000; 12:49:43.164 [planetinfo.record]: land_color = 0.338439, 0.66, 0.260391, 1; 12:49:43.164 [planetinfo.record]: land_fraction = 0.570000; 12:49:43.164 [planetinfo.record]: polar_cloud_color = 0.659059, 0.935938, 0.558912, 1; 12:49:43.164 [planetinfo.record]: polar_land_color = 0.820236, 0.934, 0.792623, 1; 12:49:43.164 [planetinfo.record]: polar_sea_color = 0.93457, 0.929613, 0.87689, 1; 12:49:43.164 [planetinfo.record]: sea_color = 0.654297, 0.640415, 0.492767, 1; 12:49:43.164 [planetinfo.record]: rotation_speed = 0.003204 12:49:43.164 [planetinfo.record]: planet zpos = 512040.000000 12:49:43.164 [planetinfo.record]: sun_radius = 114165.819550 12:49:43.164 [planetinfo.record]: sun_vector = -0.440 0.118 -0.890 12:49:43.164 [planetinfo.record]: sun_distance = 768060 12:49:43.164 [planetinfo.record]: corona_flare = 0.029149 12:49:43.164 [planetinfo.record]: corona_hues = 0.647621 12:49:43.164 [planetinfo.record]: sun_color = 0.827875, 0.752401, 0.615462, 1 12:49:43.164 [planetinfo.record]: corona_shimmer = 0.320912 12:49:43.164 [planetinfo.record]: station_vector = -0.179 0.961 0.211 12:49:43.164 [planetinfo.record]: station = coriolis 12:49:48.110 [PLANETINFO OVER]: Done 12:49:48.111 [PLANETINFO LOGGING]: 3 70 12:49:49.123 [planetinfo.record]: seed = 82 131 218 144 12:49:49.123 [planetinfo.record]: coordinates = 131 23 12:49:49.123 [planetinfo.record]: government = 2; 12:49:49.123 [planetinfo.record]: economy = 7; 12:49:49.123 [planetinfo.record]: techlevel = 4; 12:49:49.123 [planetinfo.record]: population = 26; 12:49:49.123 [planetinfo.record]: productivity = 3744; 12:49:49.123 [planetinfo.record]: name = "Ermaon"; 12:49:49.123 [planetinfo.record]: inhabitant = "Black Fat Bird"; 12:49:49.123 [planetinfo.record]: inhabitants = "Black Fat Birds"; 12:49:49.123 [planetinfo.record]: description = "Ermaon is cursed by vicious mountain beasts."; 12:49:49.160 [planetinfo.record]: air_color = 0.702117, 0.726331, 0.977537, 1; 12:49:49.160 [planetinfo.record]: cloud_alpha = 1.000000; 12:49:49.160 [planetinfo.record]: cloud_color = 0.705315, 0.762873, 0.935547, 1; 12:49:49.160 [planetinfo.record]: cloud_fraction = 0.570000; 12:49:49.160 [planetinfo.record]: land_color = 0.626484, 0.5775, 0.66, 1; 12:49:49.160 [planetinfo.record]: land_fraction = 0.440000; 12:49:49.160 [planetinfo.record]: polar_cloud_color = 0.779339, 0.814753, 0.920996, 1; 12:49:49.160 [planetinfo.record]: polar_land_color = 0.922143, 0.904813, 0.934, 1; 12:49:49.160 [planetinfo.record]: polar_sea_color = 0.931445, 0.856431, 0.84867, 1; 12:49:49.160 [planetinfo.record]: sea_color = 0.685547, 0.464702, 0.441856, 1; 12:49:49.160 [planetinfo.record]: rotation_speed = 0.003836 12:49:49.160 [planetinfo.record]: planet zpos = 442050.000000 12:49:49.160 [planetinfo.record]: sun_radius = 67924.087067 12:49:49.161 [planetinfo.record]: sun_vector = 0.681 0.460 0.570 12:49:49.161 [planetinfo.record]: sun_distance = 677810 12:49:49.161 [planetinfo.record]: corona_flare = 0.007321 12:49:49.161 [planetinfo.record]: corona_hues = 0.710655 12:49:49.161 [planetinfo.record]: sun_color = 0.770496, 0.616836, 0.589285, 1 12:49:49.161 [planetinfo.record]: corona_shimmer = 0.430546 12:49:49.161 [planetinfo.record]: station_vector = 0.958 0.256 0.133 12:49:49.161 [planetinfo.record]: station = coriolis 12:49:53.810 [PLANETINFO OVER]: Done 12:49:53.814 [PLANETINFO LOGGING]: 3 71 12:49:54.814 [planetinfo.record]: seed = 162 252 106 104 12:49:54.814 [planetinfo.record]: coordinates = 252 63 12:49:54.814 [planetinfo.record]: government = 4; 12:49:54.814 [planetinfo.record]: economy = 7; 12:49:54.814 [planetinfo.record]: techlevel = 2; 12:49:54.814 [planetinfo.record]: population = 20; 12:49:54.814 [planetinfo.record]: productivity = 3840; 12:49:54.814 [planetinfo.record]: name = "Uscearti"; 12:49:54.814 [planetinfo.record]: inhabitant = "Human Colonial"; 12:49:54.814 [planetinfo.record]: inhabitants = "Human Colonials"; 12:49:54.814 [planetinfo.record]: description = "The planet Uscearti is reasonably fabled for its fabulous vicious Innoilza gargle blasters and its exciting sit coms."; 12:49:54.828 [planetinfo.record]: air_color = 0.466995, 0.662313, 0.868922, 1; 12:49:54.828 [planetinfo.record]: cloud_alpha = 1.000000; 12:49:54.828 [planetinfo.record]: cloud_color = 0.12854, 0.609375, 0.395253, 1; 12:49:54.828 [planetinfo.record]: cloud_fraction = 0.480000; 12:49:54.828 [planetinfo.record]: land_color = 0.600703, 0.66, 0.637764, 1; 12:49:54.828 [planetinfo.record]: land_fraction = 0.280000; 12:49:54.828 [planetinfo.record]: polar_cloud_color = 0.392402, 0.774219, 0.604191, 1; 12:49:54.828 [planetinfo.record]: polar_land_color = 0.913022, 0.934, 0.926133, 1; 12:49:54.828 [planetinfo.record]: polar_sea_color = 0.880567, 0.929492, 0.892798, 1; 12:49:54.828 [planetinfo.record]: sea_color = 0.556626, 0.705078, 0.593739, 1; 12:49:54.828 [planetinfo.record]: rotation_speed = 0.003835 12:49:54.828 [planetinfo.record]: planet zpos = 665080.000000 12:49:54.828 [planetinfo.record]: sun_radius = 125195.083618 12:49:54.829 [planetinfo.record]: sun_vector = 0.638 -0.728 0.250 12:49:54.829 [planetinfo.record]: sun_distance = 1023200 12:49:54.829 [planetinfo.record]: corona_flare = 0.070943 12:49:54.829 [planetinfo.record]: corona_hues = 0.693054 12:49:54.829 [planetinfo.record]: sun_color = 0.650939, 0.665445, 0.811395, 1 12:49:54.829 [planetinfo.record]: corona_shimmer = 0.397385 12:49:54.829 [planetinfo.record]: station_vector = 0.958 0.146 0.247 12:49:54.829 [planetinfo.record]: station = coriolis 12:49:59.094 [PLANETINFO OVER]: Done 12:49:59.095 [PLANETINFO LOGGING]: 3 72 12:50:00.104 [planetinfo.record]: seed = 130 23 154 198 12:50:00.104 [planetinfo.record]: coordinates = 23 10 12:50:00.104 [planetinfo.record]: government = 0; 12:50:00.104 [planetinfo.record]: economy = 2; 12:50:00.104 [planetinfo.record]: techlevel = 8; 12:50:00.104 [planetinfo.record]: population = 35; 12:50:00.104 [planetinfo.record]: productivity = 8960; 12:50:00.104 [planetinfo.record]: name = "Biusbi"; 12:50:00.104 [planetinfo.record]: inhabitant = "Fierce Furry Insect"; 12:50:00.104 [planetinfo.record]: inhabitants = "Fierce Furry Insects"; 12:50:00.104 [planetinfo.record]: description = "This world is a revolting little planet."; 12:50:00.124 [planetinfo.record]: air_color = 0.763868, 0.472199, 0.985992, 1; 12:50:00.124 [planetinfo.record]: cloud_alpha = 1.000000; 12:50:00.124 [planetinfo.record]: cloud_color = 0.960938, 0.033783, 0.294545, 1; 12:50:00.124 [planetinfo.record]: cloud_fraction = 0.310000; 12:50:00.124 [planetinfo.record]: land_color = 0.560138, 0.66, 0.314531, 1; 12:50:00.124 [planetinfo.record]: land_fraction = 0.530000; 12:50:00.124 [planetinfo.record]: polar_cloud_color = 0.932422, 0.370146, 0.528286, 1; 12:50:00.124 [planetinfo.record]: polar_land_color = 0.89867, 0.934, 0.811777, 1; 12:50:00.124 [planetinfo.record]: polar_sea_color = 0.908016, 0.936133, 0.888778, 1; 12:50:00.124 [planetinfo.record]: sea_color = 0.561941, 0.638672, 0.509441, 1; 12:50:00.124 [planetinfo.record]: rotation_speed = 0.003219 12:50:00.124 [planetinfo.record]: planet zpos = 568750.000000 12:50:00.124 [planetinfo.record]: sun_radius = 71995.639801 12:50:00.124 [planetinfo.record]: sun_vector = 0.088 0.864 0.495 12:50:00.124 [planetinfo.record]: sun_distance = 875000 12:50:00.124 [planetinfo.record]: corona_flare = 0.020761 12:50:00.124 [planetinfo.record]: corona_hues = 0.792465 12:50:00.124 [planetinfo.record]: sun_color = 0.680011, 0.648072, 0.445404, 1 12:50:00.124 [planetinfo.record]: corona_shimmer = 0.451705 12:50:00.125 [planetinfo.record]: station_vector = 0.546 0.121 0.829 12:50:00.125 [planetinfo.record]: station = coriolis 12:50:05.115 [PLANETINFO OVER]: Done 12:50:05.116 [PLANETINFO LOGGING]: 3 73 12:50:06.141 [planetinfo.record]: seed = 18 117 138 35 12:50:06.141 [planetinfo.record]: coordinates = 117 198 12:50:06.141 [planetinfo.record]: government = 2; 12:50:06.141 [planetinfo.record]: economy = 6; 12:50:06.141 [planetinfo.record]: techlevel = 3; 12:50:06.141 [planetinfo.record]: population = 21; 12:50:06.141 [planetinfo.record]: productivity = 4032; 12:50:06.141 [planetinfo.record]: name = "Geritior"; 12:50:06.141 [planetinfo.record]: inhabitant = "Large Red Bony Feline"; 12:50:06.141 [planetinfo.record]: inhabitants = "Large Red Bony Felines"; 12:50:06.141 [planetinfo.record]: description = "The world Geritior is reasonably famous for the Geritiorian deadly Nuthoid."; 12:50:06.160 [planetinfo.record]: air_color = 0.682673, 0.864369, 0.859922, 1; 12:50:06.160 [planetinfo.record]: cloud_alpha = 1.000000; 12:50:06.160 [planetinfo.record]: cloud_color = 0.595703, 0.59454, 0.577087, 1; 12:50:06.160 [planetinfo.record]: cloud_fraction = 0.260000; 12:50:06.160 [planetinfo.record]: land_color = 0.66, 0.629063, 0.64864, 1; 12:50:06.160 [planetinfo.record]: land_fraction = 0.320000; 12:50:06.160 [planetinfo.record]: polar_cloud_color = 0.768066, 0.767129, 0.753065, 1; 12:50:06.160 [planetinfo.record]: polar_land_color = 0.934, 0.923055, 0.929981, 1; 12:50:06.160 [planetinfo.record]: polar_sea_color = 0.929492, 0.901836, 0.858691, 1; 12:50:06.160 [planetinfo.record]: sea_color = 0.705078, 0.621161, 0.49025, 1; 12:50:06.160 [planetinfo.record]: rotation_speed = 0.001909 12:50:06.160 [planetinfo.record]: planet zpos = 407110.000000 12:50:06.160 [planetinfo.record]: sun_radius = 97843.041534 12:50:06.160 [planetinfo.record]: sun_vector = -0.285 -0.890 0.357 12:50:06.160 [planetinfo.record]: sun_distance = 703190 12:50:06.160 [planetinfo.record]: corona_flare = 0.079712 12:50:06.160 [planetinfo.record]: corona_hues = 0.671829 12:50:06.160 [planetinfo.record]: sun_color = 0.753195, 0.716563, 0.526197, 1 12:50:06.160 [planetinfo.record]: corona_shimmer = 0.506664 12:50:06.161 [planetinfo.record]: station_vector = -0.749 -0.661 0.038 12:50:06.161 [planetinfo.record]: station = coriolis 12:50:10.895 [PLANETINFO OVER]: Done 12:50:10.896 [PLANETINFO LOGGING]: 3 74 12:50:11.917 [planetinfo.record]: seed = 242 121 90 208 12:50:11.917 [planetinfo.record]: coordinates = 121 247 12:50:11.917 [planetinfo.record]: government = 6; 12:50:11.917 [planetinfo.record]: economy = 7; 12:50:11.917 [planetinfo.record]: techlevel = 4; 12:50:11.917 [planetinfo.record]: population = 30; 12:50:11.917 [planetinfo.record]: productivity = 7200; 12:50:11.917 [planetinfo.record]: name = "Erlein"; 12:50:11.917 [planetinfo.record]: inhabitant = "Human Colonial"; 12:50:11.917 [planetinfo.record]: inhabitants = "Human Colonials"; 12:50:11.917 [planetinfo.record]: description = "The planet Erlein is very noted for its unusual sit coms but plagued by occasional solar activity."; 12:50:11.926 [planetinfo.record]: air_color = 0.662173, 0.866971, 0.837982, 1; 12:50:11.926 [planetinfo.record]: cloud_alpha = 1.000000; 12:50:11.926 [planetinfo.record]: cloud_color = 0.603516, 0.576184, 0.537506, 1; 12:50:11.926 [planetinfo.record]: cloud_fraction = 0.090000; 12:50:11.926 [planetinfo.record]: land_color = 0.513672, 0.229466, 0.0220718, 1; 12:50:11.926 [planetinfo.record]: land_fraction = 0.500000; 12:50:11.935 [planetinfo.record]: polar_cloud_color = 0.771582, 0.749742, 0.718837, 1; 12:50:11.935 [planetinfo.record]: polar_land_color = 0.948633, 0.817417, 0.721665, 1; 12:50:11.936 [planetinfo.record]: polar_sea_color = 0.95, 0.901665, 0.906196, 1; 12:50:11.936 [planetinfo.record]: sea_color = 0.5, 0.398242, 0.407782, 1; 12:50:11.936 [planetinfo.record]: rotation_speed = 0.000113 12:50:11.936 [planetinfo.record]: planet zpos = 352440.000000 12:50:11.936 [planetinfo.record]: sun_radius = 77550.226593 12:50:11.936 [planetinfo.record]: sun_vector = -0.555 -0.415 0.721 12:50:11.936 [planetinfo.record]: sun_distance = 616770 12:50:11.936 [planetinfo.record]: corona_flare = 0.068668 12:50:11.936 [planetinfo.record]: corona_hues = 0.520584 12:50:11.936 [planetinfo.record]: sun_color = 0.192283, 0.396942, 0.707739, 1 12:50:11.936 [planetinfo.record]: corona_shimmer = 0.272304 12:50:11.936 [planetinfo.record]: station_vector = -0.507 -0.478 0.717 12:50:11.936 [planetinfo.record]: station = coriolis 12:50:16.436 [PLANETINFO OVER]: Done 12:50:16.437 [PLANETINFO LOGGING]: 3 75 12:50:17.440 [planetinfo.record]: seed = 66 158 42 108 12:50:17.440 [planetinfo.record]: coordinates = 158 140 12:50:17.440 [planetinfo.record]: government = 0; 12:50:17.440 [planetinfo.record]: economy = 6; 12:50:17.440 [planetinfo.record]: techlevel = 3; 12:50:17.440 [planetinfo.record]: population = 19; 12:50:17.440 [planetinfo.record]: productivity = 2432; 12:50:17.440 [planetinfo.record]: name = "Inve"; 12:50:17.440 [planetinfo.record]: inhabitant = "Human Colonial"; 12:50:17.440 [planetinfo.record]: inhabitants = "Human Colonials"; 12:50:17.440 [planetinfo.record]: description = "Inve is reasonably notable for its great volcanoes but plagued by lethal spotted snails."; 12:50:17.460 [planetinfo.record]: air_color = 0.598744, 0.833888, 0.993797, 1; 12:50:17.461 [planetinfo.record]: cloud_alpha = 1.000000; 12:50:17.461 [planetinfo.record]: cloud_color = 0.399902, 0.984375, 0.532322, 1; 12:50:17.461 [planetinfo.record]: cloud_fraction = 0.570000; 12:50:17.461 [planetinfo.record]: land_color = 0.587891, 0.447807, 0.500339, 1; 12:50:17.461 [planetinfo.record]: land_fraction = 0.340000; 12:50:17.461 [planetinfo.record]: polar_cloud_color = 0.593039, 0.942969, 0.67232, 1; 12:50:17.461 [planetinfo.record]: polar_land_color = 0.941211, 0.885143, 0.906168, 1; 12:50:17.461 [planetinfo.record]: polar_sea_color = 0.925195, 0.908311, 0.842072, 1; 12:50:17.461 [planetinfo.record]: sea_color = 0.748047, 0.693441, 0.479218, 1; 12:50:17.461 [planetinfo.record]: rotation_speed = 0.002573 12:50:17.461 [planetinfo.record]: planet zpos = 725520.000000 12:50:17.461 [planetinfo.record]: sun_radius = 113553.467102 12:50:17.461 [planetinfo.record]: sun_vector = -0.746 -0.432 0.506 12:50:17.462 [planetinfo.record]: sun_distance = 1209200 12:50:17.462 [planetinfo.record]: corona_flare = 0.046330 12:50:17.462 [planetinfo.record]: corona_hues = 0.617348 12:50:17.462 [planetinfo.record]: sun_color = 0.743451, 0.736221, 0.733743, 1 12:50:17.462 [planetinfo.record]: corona_shimmer = 0.568534 12:50:17.462 [planetinfo.record]: station_vector = -0.974 0.047 0.223 12:50:17.462 [planetinfo.record]: station = coriolis 12:50:22.423 [PLANETINFO OVER]: Done 12:50:22.424 [PLANETINFO LOGGING]: 3 76 12:50:23.430 [planetinfo.record]: seed = 162 163 26 219 12:50:23.430 [planetinfo.record]: coordinates = 163 160 12:50:23.430 [planetinfo.record]: government = 4; 12:50:23.430 [planetinfo.record]: economy = 0; 12:50:23.430 [planetinfo.record]: techlevel = 12; 12:50:23.430 [planetinfo.record]: population = 53; 12:50:23.430 [planetinfo.record]: productivity = 33920; 12:50:23.430 [planetinfo.record]: name = "Anonrior"; 12:50:23.430 [planetinfo.record]: inhabitant = "Human Colonial"; 12:50:23.430 [planetinfo.record]: inhabitants = "Human Colonials"; 12:50:23.430 [planetinfo.record]: description = "The world Anonrior is reasonably famous for the Anonriorian evil poet."; 12:50:23.439 [planetinfo.record]: air_color = 0.679007, 0.857367, 0.898189, 1; 12:50:23.439 [planetinfo.record]: cloud_alpha = 1.000000; 12:50:23.439 [planetinfo.record]: cloud_color = 0.644451, 0.697266, 0.60466, 1; 12:50:23.439 [planetinfo.record]: cloud_fraction = 0.470000; 12:50:23.439 [planetinfo.record]: land_color = 0.437979, 0.270703, 0.66, 1; 12:50:23.439 [planetinfo.record]: land_fraction = 0.540000; 12:50:23.439 [planetinfo.record]: polar_cloud_color = 0.775245, 0.81377, 0.74622, 1; 12:50:23.439 [planetinfo.record]: polar_land_color = 0.855452, 0.796272, 0.934, 1; 12:50:23.439 [planetinfo.record]: polar_sea_color = 0.877912, 0.928125, 0.892819, 1; 12:50:23.439 [planetinfo.record]: sea_color = 0.563208, 0.71875, 0.609385, 1; 12:50:23.439 [planetinfo.record]: rotation_speed = 0.004499 12:50:23.439 [planetinfo.record]: planet zpos = 695400.000000 12:50:23.439 [planetinfo.record]: sun_radius = 140170.806885 12:50:23.439 [planetinfo.record]: sun_vector = 0.512 -0.781 0.357 12:50:23.439 [planetinfo.record]: sun_distance = 1216950 12:50:23.439 [planetinfo.record]: corona_flare = 0.031206 12:50:23.439 [planetinfo.record]: corona_hues = 0.601982 12:50:23.439 [planetinfo.record]: sun_color = 0.440151, 0.486868, 0.766669, 1 12:50:23.439 [planetinfo.record]: corona_shimmer = 0.352560 12:50:23.440 [planetinfo.record]: station_vector = 0.205 0.674 0.709 12:50:23.441 [planetinfo.record]: station = dodecahedron 12:50:28.095 [PLANETINFO OVER]: Done 12:50:28.096 [PLANETINFO LOGGING]: 3 77 12:50:29.101 [planetinfo.record]: seed = 50 153 74 87 12:50:29.101 [planetinfo.record]: coordinates = 153 158 12:50:29.101 [planetinfo.record]: government = 6; 12:50:29.101 [planetinfo.record]: economy = 6; 12:50:29.101 [planetinfo.record]: techlevel = 5; 12:50:29.101 [planetinfo.record]: population = 33; 12:50:29.101 [planetinfo.record]: productivity = 10560; 12:50:29.101 [planetinfo.record]: name = "Tireonce"; 12:50:29.101 [planetinfo.record]: inhabitant = "Human Colonial"; 12:50:29.101 [planetinfo.record]: inhabitants = "Human Colonials"; 12:50:29.102 [planetinfo.record]: description = "This world is ravaged by unpredictable civil war."; 12:50:29.113 [planetinfo.record]: air_color = 0.620824, 0.867693, 0.959977, 1; 12:50:29.113 [planetinfo.record]: cloud_alpha = 1.000000; 12:50:29.113 [planetinfo.record]: cloud_color = 0.545831, 0.882812, 0.475891, 1; 12:50:29.113 [planetinfo.record]: cloud_fraction = 0.240000; 12:50:29.113 [planetinfo.record]: land_color = 0.627773, 0.595547, 0.66, 1; 12:50:29.113 [planetinfo.record]: land_fraction = 0.240000; 12:50:29.113 [planetinfo.record]: polar_cloud_color = 0.683204, 0.897266, 0.638776, 1; 12:50:29.113 [planetinfo.record]: polar_land_color = 0.922599, 0.911197, 0.934, 1; 12:50:29.113 [planetinfo.record]: polar_sea_color = 0.928125, 0.90805, 0.854709, 1; 12:50:29.114 [planetinfo.record]: sea_color = 0.71875, 0.656566, 0.491333, 1; 12:50:29.114 [planetinfo.record]: rotation_speed = 0.000688 12:50:29.114 [planetinfo.record]: planet zpos = 571320.000000 12:50:29.114 [planetinfo.record]: sun_radius = 117038.101959 12:50:29.114 [planetinfo.record]: sun_vector = -0.600 -0.290 -0.745 12:50:29.114 [planetinfo.record]: sun_distance = 856980 12:50:29.114 [planetinfo.record]: corona_flare = 0.052231 12:50:29.114 [planetinfo.record]: corona_hues = 0.992729 12:50:29.114 [planetinfo.record]: sun_color = 0.670493, 0.480192, 0.401686, 1 12:50:29.114 [planetinfo.record]: corona_shimmer = 0.460807 12:50:29.114 [planetinfo.record]: station_vector = 0.054 0.109 0.993 12:50:29.114 [planetinfo.record]: station = coriolis 12:50:33.840 [PLANETINFO OVER]: Done 12:50:33.841 [PLANETINFO LOGGING]: 3 78 12:50:34.865 [planetinfo.record]: seed = 146 101 218 115 12:50:34.866 [planetinfo.record]: coordinates = 101 127 12:50:34.866 [planetinfo.record]: government = 2; 12:50:34.866 [planetinfo.record]: economy = 7; 12:50:34.866 [planetinfo.record]: techlevel = 2; 12:50:34.866 [planetinfo.record]: population = 18; 12:50:34.866 [planetinfo.record]: productivity = 2592; 12:50:34.866 [planetinfo.record]: name = "Beedenri"; 12:50:34.866 [planetinfo.record]: inhabitant = "Blue Horned Humanoid"; 12:50:34.866 [planetinfo.record]: inhabitants = "Blue Horned Humanoids"; 12:50:34.866 [planetinfo.record]: description = "This world is very notable for its inhabitants’ ingrained shyness and its fabulous cuisine."; 12:50:34.867 [planetinfo.record]: air_color = 0.651458, 0.473187, 0.961928, 1; 12:50:34.868 [planetinfo.record]: cloud_alpha = 1.000000; 12:50:34.868 [planetinfo.record]: cloud_color = 0.888672, 0.0659561, 0.798687, 1; 12:50:34.868 [planetinfo.record]: cloud_fraction = 0.390000; 12:50:34.868 [planetinfo.record]: land_color = 0.481646, 0.66, 0.244922, 1; 12:50:34.868 [planetinfo.record]: land_fraction = 0.480000; 12:50:34.868 [planetinfo.record]: polar_cloud_color = 0.899902, 0.379207, 0.842951, 1; 12:50:34.868 [planetinfo.record]: polar_land_color = 0.870901, 0.934, 0.78715, 1; 12:50:34.868 [planetinfo.record]: polar_sea_color = 0.949805, 0.89814, 0.915093, 1; 12:50:34.868 [planetinfo.record]: sea_color = 0.501953, 0.392739, 0.428575, 1; 12:50:34.868 [planetinfo.record]: rotation_speed = 0.001318 12:50:34.868 [planetinfo.record]: planet zpos = 479050.000000 12:50:34.868 [planetinfo.record]: sun_radius = 114046.858978 12:50:34.868 [planetinfo.record]: sun_vector = -0.094 0.933 -0.348 12:50:34.869 [planetinfo.record]: sun_distance = 810700 12:50:34.869 [planetinfo.record]: corona_flare = 0.096194 12:50:34.869 [planetinfo.record]: corona_hues = 0.962051 12:50:34.869 [planetinfo.record]: sun_color = 0.724158, 0.501898, 0.454836, 1 12:50:34.869 [planetinfo.record]: corona_shimmer = 0.399288 12:50:34.869 [planetinfo.record]: station_vector = -0.131 0.991 0.000 12:50:34.869 [planetinfo.record]: station = coriolis 12:50:39.819 [PLANETINFO OVER]: Done 12:50:39.820 [PLANETINFO LOGGING]: 3 79 12:50:40.825 [planetinfo.record]: seed = 226 254 234 137 12:50:40.825 [planetinfo.record]: coordinates = 254 50 12:50:40.825 [planetinfo.record]: government = 4; 12:50:40.825 [planetinfo.record]: economy = 2; 12:50:40.825 [planetinfo.record]: techlevel = 9; 12:50:40.825 [planetinfo.record]: population = 43; 12:50:40.825 [planetinfo.record]: productivity = 22016; 12:50:40.825 [planetinfo.record]: name = "Esange"; 12:50:40.825 [planetinfo.record]: inhabitant = "Small Black Fat Humanoid"; 12:50:40.825 [planetinfo.record]: inhabitants = "Small Black Fat Humanoids"; 12:50:40.825 [planetinfo.record]: description = "The world Esange is beset by lethal disease."; 12:50:40.838 [planetinfo.record]: air_color = 0.56578, 0.941115, 0.855764, 1; 12:50:40.838 [planetinfo.record]: cloud_alpha = 1.000000; 12:50:40.838 [planetinfo.record]: cloud_color = 0.826172, 0.494422, 0.332405, 1; 12:50:40.838 [planetinfo.record]: cloud_fraction = 0.190000; 12:50:40.838 [planetinfo.record]: land_color = 0.590068, 0.66, 0.580078, 1; 12:50:40.838 [planetinfo.record]: land_fraction = 0.640000; 12:50:40.838 [planetinfo.record]: polar_cloud_color = 0.871777, 0.652988, 0.546138, 1; 12:50:40.838 [planetinfo.record]: polar_land_color = 0.909259, 0.934, 0.905725, 1; 12:50:40.838 [planetinfo.record]: polar_sea_color = 0.875967, 0.905862, 0.92207, 1; 12:50:40.838 [planetinfo.record]: sea_color = 0.623438, 0.724503, 0.779297, 1; 12:50:40.838 [planetinfo.record]: rotation_speed = 0.001351 12:50:40.838 [planetinfo.record]: planet zpos = 644880.000000 12:50:40.839 [planetinfo.record]: sun_radius = 170386.041870 12:50:40.839 [planetinfo.record]: sun_vector = 0.087 -0.970 0.229 12:50:40.839 [planetinfo.record]: sun_distance = 1021060 12:50:40.839 [planetinfo.record]: corona_flare = 0.049678 12:50:40.839 [planetinfo.record]: corona_hues = 0.603455 12:50:40.839 [planetinfo.record]: sun_color = 0.696237, 0.334528, 0.275115, 1 12:50:40.839 [planetinfo.record]: corona_shimmer = 0.314874 12:50:40.839 [planetinfo.record]: station_vector = -0.866 -0.090 0.491 12:50:40.839 [planetinfo.record]: station = coriolis 12:50:45.783 [PLANETINFO OVER]: Done 12:50:45.784 [PLANETINFO LOGGING]: 3 80 12:50:46.791 [planetinfo.record]: seed = 194 136 154 135 12:50:46.791 [planetinfo.record]: coordinates = 136 67 12:50:46.791 [planetinfo.record]: government = 0; 12:50:46.791 [planetinfo.record]: economy = 3; 12:50:46.791 [planetinfo.record]: techlevel = 4; 12:50:46.792 [planetinfo.record]: population = 20; 12:50:46.792 [planetinfo.record]: productivity = 4480; 12:50:46.792 [planetinfo.record]: name = "Soraza"; 12:50:46.792 [planetinfo.record]: inhabitant = "Fierce Black Bony Feline"; 12:50:46.792 [planetinfo.record]: inhabitants = "Fierce Black Bony Felines"; 12:50:46.792 [planetinfo.record]: description = "Soraza is cursed by vicious killer cats."; 12:50:46.816 [planetinfo.record]: air_color = 0.512989, 0.561387, 0.872174, 1; 12:50:46.816 [planetinfo.record]: cloud_alpha = 1.000000; 12:50:46.816 [planetinfo.record]: cloud_color = 0.224922, 0.378914, 0.619141, 1; 12:50:46.816 [planetinfo.record]: cloud_fraction = 0.510000; 12:50:46.816 [planetinfo.record]: land_color = 0.5, 0.466797, 0.480804, 1; 12:50:46.816 [planetinfo.record]: land_fraction = 0.400000; 12:50:46.816 [planetinfo.record]: polar_cloud_color = 0.468765, 0.589799, 0.778613, 1; 12:50:46.816 [planetinfo.record]: polar_land_color = 0.95, 0.934228, 0.940882, 1; 12:50:46.816 [planetinfo.record]: polar_sea_color = 0.942969, 0.911615, 0.890848, 1; 12:50:46.816 [planetinfo.record]: sea_color = 0.570312, 0.49446, 0.44422, 1; 12:50:46.816 [planetinfo.record]: rotation_speed = 0.000707 12:50:46.816 [planetinfo.record]: planet zpos = 569280.000000 12:50:46.817 [planetinfo.record]: sun_radius = 112523.776855 12:50:46.817 [planetinfo.record]: sun_vector = -0.054 0.587 -0.808 12:50:46.817 [planetinfo.record]: sun_distance = 806480 12:50:46.817 [planetinfo.record]: corona_flare = 0.013515 12:50:46.817 [planetinfo.record]: corona_hues = 0.694099 12:50:46.817 [planetinfo.record]: sun_color = 0.771609, 0.359864, 0.226211, 1 12:50:46.817 [planetinfo.record]: corona_shimmer = 0.860572 12:50:46.817 [planetinfo.record]: station_vector = -0.520 0.111 0.847 12:50:46.817 [planetinfo.record]: station = coriolis 12:50:51.816 [PLANETINFO OVER]: Done 12:50:51.817 [PLANETINFO LOGGING]: 3 81 12:50:52.825 [planetinfo.record]: seed = 82 64 10 249 12:50:52.825 [planetinfo.record]: coordinates = 64 100 12:50:52.825 [planetinfo.record]: government = 2; 12:50:52.825 [planetinfo.record]: economy = 4; 12:50:52.825 [planetinfo.record]: techlevel = 4; 12:50:52.825 [planetinfo.record]: population = 23; 12:50:52.825 [planetinfo.record]: productivity = 6624; 12:50:52.825 [planetinfo.record]: name = "Oristi"; 12:50:52.826 [planetinfo.record]: inhabitant = "Human Colonial"; 12:50:52.826 [planetinfo.record]: inhabitants = "Human Colonials"; 12:50:52.826 [planetinfo.record]: description = "The planet Oristi is cursed by deadly civil war."; 12:50:52.847 [planetinfo.record]: air_color = 0.737764, 0.760838, 0.945018, 1; 12:50:52.848 [planetinfo.record]: cloud_alpha = 1.000000; 12:50:52.848 [planetinfo.record]: cloud_color = 0.788795, 0.804521, 0.837891, 1; 12:50:52.848 [planetinfo.record]: cloud_fraction = 0.240000; 12:50:52.848 [planetinfo.record]: land_color = 0.20478, 0.0592969, 0.66, 1; 12:50:52.848 [planetinfo.record]: land_fraction = 0.330000; 12:50:52.849 [planetinfo.record]: polar_cloud_color = 0.844932, 0.85522, 0.877051, 1; 12:50:52.849 [planetinfo.record]: polar_land_color = 0.772949, 0.721479, 0.934, 1; 12:50:52.849 [planetinfo.record]: polar_sea_color = 0.927212, 0.93418, 0.878439, 1; 12:50:52.849 [planetinfo.record]: sea_color = 0.638566, 0.658203, 0.501109, 1; 12:50:52.849 [planetinfo.record]: rotation_speed = 0.004498 12:50:52.849 [planetinfo.record]: planet zpos = 570240.000000 12:50:52.849 [planetinfo.record]: sun_radius = 117743.466797 12:50:52.849 [planetinfo.record]: sun_vector = -0.819 -0.190 -0.541 12:50:52.849 [planetinfo.record]: sun_distance = 829440 12:50:52.849 [planetinfo.record]: corona_flare = 0.094725 12:50:52.850 [planetinfo.record]: corona_hues = 0.861069 12:50:52.850 [planetinfo.record]: sun_color = 0.753555, 0.352677, 0.329732, 1 12:50:52.850 [planetinfo.record]: corona_shimmer = 0.382268 12:50:52.850 [planetinfo.record]: station_vector = 0.863 0.359 0.354 12:50:52.850 [planetinfo.record]: station = coriolis 12:50:57.559 [PLANETINFO OVER]: Done 12:50:57.560 [PLANETINFO LOGGING]: 3 82 12:50:58.566 [planetinfo.record]: seed = 50 110 90 227 12:50:58.566 [planetinfo.record]: coordinates = 110 215 12:50:58.566 [planetinfo.record]: government = 6; 12:50:58.566 [planetinfo.record]: economy = 7; 12:50:58.566 [planetinfo.record]: techlevel = 5; 12:50:58.566 [planetinfo.record]: population = 34; 12:50:58.566 [planetinfo.record]: productivity = 8160; 12:50:58.566 [planetinfo.record]: name = "Geusqubi"; 12:50:58.566 [planetinfo.record]: inhabitant = "Human Colonial"; 12:50:58.566 [planetinfo.record]: inhabitants = "Human Colonials"; 12:50:58.566 [planetinfo.record]: description = "The planet Geusqubi is very noted for its unusual casinos but plagued by frequent solar activity."; 12:50:58.572 [planetinfo.record]: air_color = 0.439775, 0.794704, 0.844857, 1; 12:50:58.572 [planetinfo.record]: cloud_alpha = 1.000000; 12:50:58.572 [planetinfo.record]: cloud_color = 0.364427, 0.537109, 0.0860214, 1; 12:50:58.572 [planetinfo.record]: cloud_fraction = 0.140000; 12:50:58.572 [planetinfo.record]: land_color = 0.227966, 0.28381, 0.648438, 1; 12:50:58.572 [planetinfo.record]: land_fraction = 0.570000; 12:50:58.572 [planetinfo.record]: polar_cloud_color = 0.592663, 0.741699, 0.35238, 1; 12:50:58.572 [planetinfo.record]: polar_land_color = 0.783559, 0.803693, 0.935156, 1; 12:50:58.572 [planetinfo.record]: polar_sea_color = 0.930078, 0.866101, 0.846516, 1; 12:50:58.572 [planetinfo.record]: sea_color = 0.699219, 0.506831, 0.447937, 1; 12:50:58.572 [planetinfo.record]: rotation_speed = 0.002620 12:50:58.572 [planetinfo.record]: planet zpos = 406340.000000 12:50:58.573 [planetinfo.record]: sun_radius = 88010.947876 12:50:58.573 [planetinfo.record]: sun_vector = 0.071 0.359 -0.931 12:50:58.573 [planetinfo.record]: sun_distance = 591040 12:50:58.573 [planetinfo.record]: corona_flare = 0.019591 12:50:58.573 [planetinfo.record]: corona_hues = 0.626137 12:50:58.573 [planetinfo.record]: sun_color = 0.447809, 0.641096, 0.708105, 1 12:50:58.573 [planetinfo.record]: corona_shimmer = 0.408278 12:50:58.573 [planetinfo.record]: station_vector = 0.609 0.369 0.702 12:50:58.573 [planetinfo.record]: station = coriolis 12:51:03.493 [PLANETINFO OVER]: Done 12:51:03.494 [PLANETINFO LOGGING]: 3 83 12:51:04.501 [planetinfo.record]: seed = 130 134 170 41 12:51:04.501 [planetinfo.record]: coordinates = 134 122 12:51:04.501 [planetinfo.record]: government = 0; 12:51:04.501 [planetinfo.record]: economy = 2; 12:51:04.501 [planetinfo.record]: techlevel = 7; 12:51:04.501 [planetinfo.record]: population = 31; 12:51:04.501 [planetinfo.record]: productivity = 7936; 12:51:04.501 [planetinfo.record]: name = "Esarqure"; 12:51:04.501 [planetinfo.record]: inhabitant = "Small Red Fat Humanoid"; 12:51:04.501 [planetinfo.record]: inhabitants = "Small Red Fat Humanoids"; 12:51:04.501 [planetinfo.record]: description = "This planet is fabled for its ancient Esarqureian Eninarxe tulip plantations and its inhabitants’ unusual silliness."; 12:51:04.516 [planetinfo.record]: air_color = 0.670841, 0.857865, 0.831392, 1; 12:51:04.516 [planetinfo.record]: cloud_alpha = 1.000000; 12:51:04.516 [planetinfo.record]: cloud_color = 0.576172, 0.563125, 0.544662, 1; 12:51:04.516 [planetinfo.record]: cloud_fraction = 0.470000; 12:51:04.516 [planetinfo.record]: land_color = 0.269736, 0.213984, 0.66, 1; 12:51:04.516 [planetinfo.record]: land_fraction = 0.550000; 12:51:04.516 [planetinfo.record]: polar_cloud_color = 0.759277, 0.748532, 0.733325, 1; 12:51:04.516 [planetinfo.record]: polar_land_color = 0.795929, 0.776205, 0.934, 1; 12:51:04.516 [planetinfo.record]: polar_sea_color = 0.927539, 0.855415, 0.833336, 1; 12:51:04.516 [planetinfo.record]: sea_color = 0.724609, 0.49923, 0.430237, 1; 12:51:04.516 [planetinfo.record]: rotation_speed = 0.000039 12:51:04.516 [planetinfo.record]: planet zpos = 683020.000000 12:51:04.516 [planetinfo.record]: sun_radius = 88850.450439 12:51:04.516 [planetinfo.record]: sun_vector = -0.740 0.443 -0.507 12:51:04.516 [planetinfo.record]: sun_distance = 1155880 12:51:04.516 [planetinfo.record]: corona_flare = 0.005760 12:51:04.516 [planetinfo.record]: corona_hues = 0.890709 12:51:04.516 [planetinfo.record]: sun_color = 0.795847, 0.774093, 0.695743, 1 12:51:04.516 [planetinfo.record]: corona_shimmer = 0.424066 12:51:04.516 [planetinfo.record]: station_vector = 0.798 -0.507 0.325 12:51:04.516 [planetinfo.record]: station = coriolis 12:51:09.377 [PLANETINFO OVER]: Done 12:51:09.377 [PLANETINFO LOGGING]: 3 84 12:51:10.381 [planetinfo.record]: seed = 226 46 26 52 12:51:10.381 [planetinfo.record]: coordinates = 46 218 12:51:10.381 [planetinfo.record]: government = 4; 12:51:10.381 [planetinfo.record]: economy = 2; 12:51:10.381 [planetinfo.record]: techlevel = 9; 12:51:10.381 [planetinfo.record]: population = 43; 12:51:10.381 [planetinfo.record]: productivity = 22016; 12:51:10.381 [planetinfo.record]: name = "Rais"; 12:51:10.381 [planetinfo.record]: inhabitant = "Human Colonial"; 12:51:10.382 [planetinfo.record]: inhabitants = "Human Colonials"; 12:51:10.382 [planetinfo.record]: description = "The planet Rais is scourged by evil disease."; 12:51:10.396 [planetinfo.record]: air_color = 0.713043, 0.544118, 0.919002, 1; 12:51:10.396 [planetinfo.record]: cloud_alpha = 1.000000; 12:51:10.396 [planetinfo.record]: cloud_color = 0.759766, 0.284912, 0.585405, 1; 12:51:10.396 [planetinfo.record]: cloud_fraction = 0.460000; 12:51:10.396 [planetinfo.record]: land_color = 0.541084, 0.66, 0.474375, 1; 12:51:10.396 [planetinfo.record]: land_fraction = 0.260000; 12:51:10.396 [planetinfo.record]: polar_cloud_color = 0.841895, 0.513029, 0.721139, 1; 12:51:10.396 [planetinfo.record]: polar_land_color = 0.891929, 0.934, 0.868328, 1; 12:51:10.396 [planetinfo.record]: polar_sea_color = 0.758166, 0.942187, 0.804172, 1; 12:51:10.396 [planetinfo.record]: sea_color = 0.126465, 0.578125, 0.23938, 1; 12:51:10.396 [planetinfo.record]: rotation_speed = 0.001905 12:51:10.396 [planetinfo.record]: planet zpos = 505180.000000 12:51:10.396 [planetinfo.record]: sun_radius = 91879.209290 12:51:10.396 [planetinfo.record]: sun_vector = -0.282 -0.937 0.205 12:51:10.396 [planetinfo.record]: sun_distance = 738340 12:51:10.396 [planetinfo.record]: corona_flare = 0.069179 12:51:10.396 [planetinfo.record]: corona_hues = 0.659470 12:51:10.396 [planetinfo.record]: sun_color = 0.781531, 0.771369, 0.605631, 1 12:51:10.396 [planetinfo.record]: corona_shimmer = 0.435727 12:51:10.396 [planetinfo.record]: station_vector = -0.160 -0.943 0.292 12:51:10.396 [planetinfo.record]: station = coriolis 12:51:15.279 [PLANETINFO OVER]: Done 12:51:15.280 [PLANETINFO LOGGING]: 3 85 12:51:16.298 [planetinfo.record]: seed = 114 18 202 240 12:51:16.298 [planetinfo.record]: coordinates = 18 160 12:51:16.298 [planetinfo.record]: government = 6; 12:51:16.298 [planetinfo.record]: economy = 0; 12:51:16.298 [planetinfo.record]: techlevel = 12; 12:51:16.298 [planetinfo.record]: population = 55; 12:51:16.298 [planetinfo.record]: productivity = 44000; 12:51:16.298 [planetinfo.record]: name = "Ergeso"; 12:51:16.298 [planetinfo.record]: inhabitant = "Horned Lizard"; 12:51:16.298 [planetinfo.record]: inhabitants = "Horned Lizards"; 12:51:16.298 [planetinfo.record]: description = "Ergeso is very fabled for the Ergesoian edible arts graduate."; 12:51:16.308 [planetinfo.record]: air_color = 0.616778, 0.588491, 0.834451, 1; 12:51:16.308 [planetinfo.record]: cloud_alpha = 1.000000; 12:51:16.308 [planetinfo.record]: cloud_color = 0.41345, 0.36161, 0.505859, 1; 12:51:16.314 [planetinfo.record]: cloud_fraction = 0.360000; 12:51:16.314 [planetinfo.record]: land_color = 0.658167, 0.657422, 0.66, 1; 12:51:16.314 [planetinfo.record]: land_fraction = 0.270000; 12:51:16.314 [planetinfo.record]: polar_cloud_color = 0.64456, 0.597955, 0.727637, 1; 12:51:16.314 [planetinfo.record]: polar_land_color = 0.933352, 0.933088, 0.934, 1; 12:51:16.314 [planetinfo.record]: polar_sea_color = 0.796712, 0.944141, 0.749596, 1; 12:51:16.314 [planetinfo.record]: sea_color = 0.209694, 0.558594, 0.0981903, 1; 12:51:16.314 [planetinfo.record]: rotation_speed = 0.003237 12:51:16.314 [planetinfo.record]: planet zpos = 368420.000000 12:51:16.314 [planetinfo.record]: sun_radius = 90130.505981 12:51:16.314 [planetinfo.record]: sun_vector = 0.966 -0.195 -0.170 12:51:16.314 [planetinfo.record]: sun_distance = 538460 12:51:16.314 [planetinfo.record]: corona_flare = 0.034491 12:51:16.315 [planetinfo.record]: corona_hues = 0.645737 12:51:16.315 [planetinfo.record]: sun_color = 0.53809, 0.647092, 0.703043, 1 12:51:16.315 [planetinfo.record]: corona_shimmer = 0.433101 12:51:16.315 [planetinfo.record]: station_vector = -0.856 0.514 0.049 12:51:16.315 [planetinfo.record]: station = icosahedron 12:51:21.541 [PLANETINFO OVER]: Done 12:51:21.541 [PLANETINFO LOGGING]: 3 86 12:51:22.544 [planetinfo.record]: seed = 210 59 218 134 12:51:22.544 [planetinfo.record]: coordinates = 59 167 12:51:22.544 [planetinfo.record]: government = 2; 12:51:22.544 [planetinfo.record]: economy = 7; 12:51:22.544 [planetinfo.record]: techlevel = 4; 12:51:22.544 [planetinfo.record]: population = 26; 12:51:22.544 [planetinfo.record]: productivity = 3744; 12:51:22.544 [planetinfo.record]: name = "Biesin"; 12:51:22.544 [planetinfo.record]: inhabitant = "Fierce Black Fat Feline"; 12:51:22.544 [planetinfo.record]: inhabitants = "Fierce Black Fat Felines"; 12:51:22.545 [planetinfo.record]: description = "This planet is a tedious little planet."; 12:51:22.560 [planetinfo.record]: air_color = 0.479848, 0.991195, 0.823033, 1; 12:51:22.560 [planetinfo.record]: cloud_alpha = 1.000000; 12:51:22.560 [planetinfo.record]: cloud_color = 0.976562, 0.071317, 0.0495911, 1; 12:51:22.560 [planetinfo.record]: cloud_fraction = 0.510000; 12:51:22.560 [planetinfo.record]: land_color = 0.638952, 0.66, 0.518203, 1; 12:51:22.560 [planetinfo.record]: land_fraction = 0.260000; 12:51:22.560 [planetinfo.record]: polar_cloud_color = 0.939453, 0.395174, 0.382112, 1; 12:51:22.560 [planetinfo.record]: polar_land_color = 0.926553, 0.934, 0.883834, 1; 12:51:22.560 [planetinfo.record]: polar_sea_color = 0.86854, 0.880604, 0.916797, 1; 12:51:22.560 [planetinfo.record]: sea_color = 0.65685, 0.700645, 0.832031, 1; 12:51:22.560 [planetinfo.record]: rotation_speed = 0.003820 12:51:22.560 [planetinfo.record]: planet zpos = 529320.000000 12:51:22.560 [planetinfo.record]: sun_radius = 81925.494385 12:51:22.560 [planetinfo.record]: sun_vector = -0.069 0.991 0.111 12:51:22.560 [planetinfo.record]: sun_distance = 793980 12:51:22.560 [planetinfo.record]: corona_flare = 0.076854 12:51:22.560 [planetinfo.record]: corona_hues = 0.501907 12:51:22.560 [planetinfo.record]: sun_color = 0.686163, 0.413495, 0.224585, 1 12:51:22.560 [planetinfo.record]: corona_shimmer = 0.308809 12:51:22.560 [planetinfo.record]: station_vector = -0.332 -0.164 0.929 12:51:22.560 [planetinfo.record]: station = coriolis 12:51:27.530 [PLANETINFO OVER]: Done 12:51:27.531 [PLANETINFO LOGGING]: 3 87 12:51:28.533 [planetinfo.record]: seed = 34 29 106 179 12:51:28.533 [planetinfo.record]: coordinates = 29 44 12:51:28.533 [planetinfo.record]: government = 4; 12:51:28.533 [planetinfo.record]: economy = 4; 12:51:28.533 [planetinfo.record]: techlevel = 6; 12:51:28.533 [planetinfo.record]: population = 33; 12:51:28.533 [planetinfo.record]: productivity = 12672; 12:51:28.533 [planetinfo.record]: name = "Beisdiri"; 12:51:28.533 [planetinfo.record]: inhabitant = "Human Colonial"; 12:51:28.533 [planetinfo.record]: inhabitants = "Human Colonials"; 12:51:28.533 [planetinfo.record]: description = "This world is a tedious little planet."; 12:51:28.556 [planetinfo.record]: air_color = 0.665965, 0.72549, 0.993797, 1; 12:51:28.556 [planetinfo.record]: cloud_alpha = 1.000000; 12:51:28.556 [planetinfo.record]: cloud_color = 0.599854, 0.804131, 0.984375, 1; 12:51:28.556 [planetinfo.record]: cloud_fraction = 0.220000; 12:51:28.556 [planetinfo.record]: land_color = 0.66, 0.549221, 0.337734, 1; 12:51:28.556 [planetinfo.record]: land_fraction = 0.400000; 12:51:28.556 [planetinfo.record]: polar_cloud_color = 0.712752, 0.835055, 0.942969, 1; 12:51:28.556 [planetinfo.record]: polar_land_color = 0.934, 0.894808, 0.819986, 1; 12:51:28.556 [planetinfo.record]: polar_sea_color = 0.938477, 0.865158, 0.901245, 1; 12:51:28.556 [planetinfo.record]: sea_color = 0.615234, 0.422974, 0.517602, 1; 12:51:28.556 [planetinfo.record]: rotation_speed = 0.003836 12:51:28.556 [planetinfo.record]: planet zpos = 433560.000000 12:51:28.556 [planetinfo.record]: sun_radius = 109504.728699 12:51:28.556 [planetinfo.record]: sun_vector = 0.402 -0.883 0.241 12:51:28.556 [planetinfo.record]: sun_distance = 722600 12:51:28.556 [planetinfo.record]: corona_flare = 0.004013 12:51:28.556 [planetinfo.record]: corona_hues = 0.583435 12:51:28.556 [planetinfo.record]: sun_color = 0.481334, 0.583102, 0.753616, 1 12:51:28.556 [planetinfo.record]: corona_shimmer = 0.303298 12:51:28.556 [planetinfo.record]: station_vector = 0.186 0.304 0.934 12:51:28.556 [planetinfo.record]: station = coriolis 12:51:33.155 [PLANETINFO OVER]: Done 12:51:33.156 [PLANETINFO LOGGING]: 3 88 12:51:34.174 [planetinfo.record]: seed = 2 126 154 72 12:51:34.175 [planetinfo.record]: coordinates = 126 205 12:51:34.175 [planetinfo.record]: government = 0; 12:51:34.175 [planetinfo.record]: economy = 7; 12:51:34.175 [planetinfo.record]: techlevel = 2; 12:51:34.175 [planetinfo.record]: population = 16; 12:51:34.175 [planetinfo.record]: productivity = 1536; 12:51:34.175 [planetinfo.record]: name = "Usraqu"; 12:51:34.175 [planetinfo.record]: inhabitant = "Small Yellow Bony Lobster"; 12:51:34.175 [planetinfo.record]: inhabitants = "Small Yellow Bony Lobsters"; 12:51:34.175 [planetinfo.record]: description = "This planet is very notable for the Usraquian tree snake and Usraquian shrew cutlet."; 12:51:34.198 [planetinfo.record]: air_color = 0.592244, 0.941319, 0.952172, 1; 12:51:34.199 [planetinfo.record]: cloud_alpha = 1.000000; 12:51:34.199 [planetinfo.record]: cloud_color = 0.812666, 0.859375, 0.399475, 1; 12:51:34.199 [planetinfo.record]: cloud_fraction = 0.240000; 12:51:34.199 [planetinfo.record]: land_color = 0.589844, 0.479248, 0.487024, 1; 12:51:34.199 [planetinfo.record]: land_fraction = 0.300000; 12:51:34.199 [planetinfo.record]: polar_cloud_color = 0.856597, 0.886719, 0.590136, 1; 12:51:34.199 [planetinfo.record]: polar_land_color = 0.941016, 0.896905, 0.900007, 1; 12:51:34.199 [planetinfo.record]: polar_sea_color = 0.885888, 0.927148, 0.869111, 1; 12:51:34.199 [planetinfo.record]: sea_color = 0.598831, 0.728516, 0.546102, 1; 12:51:34.199 [planetinfo.record]: rotation_speed = 0.003177 12:51:34.199 [planetinfo.record]: planet zpos = 499000.000000 12:51:34.199 [planetinfo.record]: sun_radius = 108454.988098 12:51:34.199 [planetinfo.record]: sun_vector = -0.180 0.909 0.376 12:51:34.199 [planetinfo.record]: sun_distance = 1047900 12:51:34.199 [planetinfo.record]: corona_flare = 0.075298 12:51:34.199 [planetinfo.record]: corona_hues = 0.824142 12:51:34.200 [planetinfo.record]: sun_color = 0.749924, 0.737994, 0.736063, 1 12:51:34.200 [planetinfo.record]: corona_shimmer = 0.676438 12:51:34.200 [planetinfo.record]: station_vector = 0.176 -0.488 0.855 12:51:34.200 [planetinfo.record]: station = coriolis 12:51:38.951 [PLANETINFO OVER]: Done 12:51:38.951 [PLANETINFO LOGGING]: 3 89 12:51:39.959 [planetinfo.record]: seed = 146 55 138 38 12:51:39.959 [planetinfo.record]: coordinates = 55 90 12:51:39.959 [planetinfo.record]: government = 2; 12:51:39.959 [planetinfo.record]: economy = 2; 12:51:39.959 [planetinfo.record]: techlevel = 9; 12:51:39.959 [planetinfo.record]: population = 41; 12:51:39.959 [planetinfo.record]: productivity = 15744; 12:51:39.959 [planetinfo.record]: name = "Biedtive"; 12:51:39.959 [planetinfo.record]: inhabitant = "Fierce Red Furry Insect"; 12:51:39.959 [planetinfo.record]: inhabitants = "Fierce Red Furry Insects"; 12:51:39.959 [planetinfo.record]: description = "The planet Biedtive is scourged by evil disease."; 12:51:39.976 [planetinfo.record]: air_color = 0.492145, 0.55163, 0.887783, 1; 12:51:39.976 [planetinfo.record]: cloud_alpha = 1.000000; 12:51:39.976 [planetinfo.record]: cloud_color = 0.174309, 0.389431, 0.666016, 1; 12:51:39.976 [planetinfo.record]: cloud_fraction = 0.170000; 12:51:39.976 [planetinfo.record]: land_color = 0.66, 0.399448, 0.319688, 1; 12:51:39.976 [planetinfo.record]: land_fraction = 0.690000; 12:51:39.976 [planetinfo.record]: polar_cloud_color = 0.430702, 0.592142, 0.799707, 1; 12:51:39.976 [planetinfo.record]: polar_land_color = 0.934, 0.84182, 0.813602, 1; 12:51:39.976 [planetinfo.record]: polar_sea_color = 0.877378, 0.928711, 0.884196, 1; 12:51:39.976 [planetinfo.record]: sea_color = 0.555275, 0.712891, 0.576208, 1; 12:51:39.976 [planetinfo.record]: rotation_speed = 0.001900 12:51:39.976 [planetinfo.record]: planet zpos = 572910.000000 12:51:39.976 [planetinfo.record]: sun_radius = 96122.711334 12:51:39.976 [planetinfo.record]: sun_vector = 0.288 -0.773 0.566 12:51:39.976 [planetinfo.record]: sun_distance = 881400 12:51:39.976 [planetinfo.record]: corona_flare = 0.028584 12:51:39.976 [planetinfo.record]: corona_hues = 0.832596 12:51:39.976 [planetinfo.record]: sun_color = 0.70343, 0.649079, 0.393154, 1 12:51:39.976 [planetinfo.record]: corona_shimmer = 0.293268 12:51:39.976 [planetinfo.record]: station_vector = 0.553 0.744 0.375 12:51:39.976 [planetinfo.record]: station = coriolis 12:51:45.104 [PLANETINFO OVER]: Done 12:51:45.105 [PLANETINFO LOGGING]: 3 90 12:51:46.122 [planetinfo.record]: seed = 114 246 90 198 12:51:46.122 [planetinfo.record]: coordinates = 246 23 12:51:46.122 [planetinfo.record]: government = 6; 12:51:46.122 [planetinfo.record]: economy = 7; 12:51:46.122 [planetinfo.record]: techlevel = 5; 12:51:46.122 [planetinfo.record]: population = 34; 12:51:46.122 [planetinfo.record]: productivity = 8160; 12:51:46.122 [planetinfo.record]: name = "Bibeer"; 12:51:46.122 [planetinfo.record]: inhabitant = "Human Colonial"; 12:51:46.122 [planetinfo.record]: inhabitants = "Human Colonials"; 12:51:46.122 [planetinfo.record]: description = "This planet is most fabled for mud hockey but ravaged by killer disease."; 12:51:46.130 [planetinfo.record]: air_color = 0.556283, 0.934519, 0.991846, 1; 12:51:46.131 [planetinfo.record]: cloud_alpha = 1.000000; 12:51:46.131 [planetinfo.record]: cloud_color = 0.692797, 0.978516, 0.275208, 1; 12:51:46.131 [planetinfo.record]: cloud_fraction = 0.290000; 12:51:46.131 [planetinfo.record]: land_color = 0.350625, 0.66, 0.493228, 1; 12:51:46.131 [planetinfo.record]: land_fraction = 0.690000; 12:51:46.131 [planetinfo.record]: polar_cloud_color = 0.768726, 0.940332, 0.517917, 1; 12:51:46.131 [planetinfo.record]: polar_land_color = 0.824547, 0.934, 0.874998, 1; 12:51:46.131 [planetinfo.record]: polar_sea_color = 0.890829, 0.931836, 0.880876, 1; 12:51:46.131 [planetinfo.record]: sea_color = 0.561655, 0.681641, 0.532532, 1; 12:51:46.131 [planetinfo.record]: rotation_speed = 0.000127 12:51:46.131 [planetinfo.record]: planet zpos = 689700.000000 12:51:46.131 [planetinfo.record]: sun_radius = 141138.590393 12:51:46.131 [planetinfo.record]: sun_vector = -0.589 -0.481 0.649 12:51:46.131 [planetinfo.record]: sun_distance = 1057540 12:51:46.131 [planetinfo.record]: corona_flare = 0.052022 12:51:46.132 [planetinfo.record]: corona_hues = 0.744766 12:51:46.132 [planetinfo.record]: sun_color = 0.214634, 0.50049, 0.67861, 1 12:51:46.132 [planetinfo.record]: corona_shimmer = 0.670390 12:51:46.132 [planetinfo.record]: station_vector = -0.979 -0.188 0.084 12:51:46.132 [planetinfo.record]: station = coriolis 12:51:50.823 [PLANETINFO OVER]: Done 12:51:50.824 [PLANETINFO LOGGING]: 3 91 12:51:51.827 [planetinfo.record]: seed = 194 42 42 143 12:51:51.827 [planetinfo.record]: coordinates = 42 144 12:51:51.827 [planetinfo.record]: government = 0; 12:51:51.827 [planetinfo.record]: economy = 2; 12:51:51.827 [planetinfo.record]: techlevel = 7; 12:51:51.828 [planetinfo.record]: population = 31; 12:51:51.828 [planetinfo.record]: productivity = 7936; 12:51:51.828 [planetinfo.record]: name = "Aarza"; 12:51:51.828 [planetinfo.record]: inhabitant = "Human Colonial"; 12:51:51.828 [planetinfo.record]: inhabitants = "Human Colonials"; 12:51:51.828 [planetinfo.record]: description = "This planet is a tedious place."; 12:51:51.836 [planetinfo.record]: air_color = 0.737426, 0.748021, 0.954123, 1; 12:51:51.836 [planetinfo.record]: cloud_alpha = 1.000000; 12:51:51.836 [planetinfo.record]: cloud_color = 0.794258, 0.803685, 0.865234, 1; 12:51:51.843 [planetinfo.record]: cloud_fraction = 0.360000; 12:51:51.843 [planetinfo.record]: land_color = 0.447385, 0.66, 0.190781, 1; 12:51:51.844 [planetinfo.record]: land_fraction = 0.650000; 12:51:51.844 [planetinfo.record]: polar_cloud_color = 0.843759, 0.849814, 0.889355, 1; 12:51:51.844 [planetinfo.record]: polar_land_color = 0.858779, 0.934, 0.767996, 1; 12:51:51.844 [planetinfo.record]: polar_sea_color = 0.926756, 0.933789, 0.877525, 1; 12:51:51.844 [planetinfo.record]: sea_color = 0.642162, 0.662109, 0.502531, 1; 12:51:51.844 [planetinfo.record]: rotation_speed = 0.002571 12:51:51.844 [planetinfo.record]: planet zpos = 602820.000000 12:51:51.844 [planetinfo.record]: sun_radius = 162437.946777 12:51:51.844 [planetinfo.record]: sun_vector = 0.933 -0.350 -0.082 12:51:51.844 [planetinfo.record]: sun_distance = 1138660 12:51:51.844 [planetinfo.record]: corona_flare = 0.005093 12:51:51.844 [planetinfo.record]: corona_hues = 0.720490 12:51:51.844 [planetinfo.record]: sun_color = 0.678693, 0.571396, 0.212702, 1 12:51:51.844 [planetinfo.record]: corona_shimmer = 0.306885 12:51:51.844 [planetinfo.record]: station_vector = -0.871 -0.442 0.214 12:51:51.844 [planetinfo.record]: station = coriolis 12:51:56.505 [PLANETINFO OVER]: Done 12:51:56.505 [PLANETINFO LOGGING]: 3 92 12:51:57.509 [planetinfo.record]: seed = 34 222 26 45 12:51:57.509 [planetinfo.record]: coordinates = 222 4 12:51:57.509 [planetinfo.record]: government = 4; 12:51:57.509 [planetinfo.record]: economy = 4; 12:51:57.509 [planetinfo.record]: techlevel = 7; 12:51:57.509 [planetinfo.record]: population = 37; 12:51:57.509 [planetinfo.record]: productivity = 14208; 12:51:57.509 [planetinfo.record]: name = "Diaquti"; 12:51:57.509 [planetinfo.record]: inhabitant = "Human Colonial"; 12:51:57.509 [planetinfo.record]: inhabitants = "Human Colonials"; 12:51:57.509 [planetinfo.record]: description = "Diaquti is a revolting dump."; 12:51:57.518 [planetinfo.record]: air_color = 0.706242, 0.572762, 0.891035, 1; 12:51:57.518 [planetinfo.record]: cloud_alpha = 1.000000; 12:51:57.518 [planetinfo.record]: cloud_color = 0.675781, 0.356369, 0.588442, 1; 12:51:57.518 [planetinfo.record]: cloud_fraction = 0.290000; 12:51:57.518 [planetinfo.record]: land_color = 0.497175, 0.66, 0.175313, 1; 12:51:57.518 [planetinfo.record]: land_fraction = 0.350000; 12:51:57.518 [planetinfo.record]: polar_cloud_color = 0.804102, 0.566562, 0.739149, 1; 12:51:57.518 [planetinfo.record]: polar_land_color = 0.876395, 0.934, 0.762523, 1; 12:51:57.518 [planetinfo.record]: polar_sea_color = 0.94043, 0.926216, 0.888449, 1; 12:51:57.519 [planetinfo.record]: sea_color = 0.595703, 0.55969, 0.463997, 1; 12:51:57.519 [planetinfo.record]: rotation_speed = 0.004429 12:51:57.519 [planetinfo.record]: planet zpos = 572940.000000 12:51:57.519 [planetinfo.record]: sun_radius = 127914.481201 12:51:57.519 [planetinfo.record]: sun_vector = 0.761 -0.495 -0.419 12:51:57.519 [planetinfo.record]: sun_distance = 1145880 12:51:57.519 [planetinfo.record]: corona_flare = 0.028419 12:51:57.519 [planetinfo.record]: corona_hues = 0.988503 12:51:57.519 [planetinfo.record]: sun_color = 0.262542, 0.713337, 0.741995, 1 12:51:57.519 [planetinfo.record]: corona_shimmer = 0.653478 12:51:57.519 [planetinfo.record]: station_vector = 0.081 0.982 0.172 12:51:57.519 [planetinfo.record]: station = coriolis 12:52:01.863 [PLANETINFO OVER]: Done 12:52:01.864 [PLANETINFO LOGGING]: 3 93 12:52:02.874 [planetinfo.record]: seed = 178 87 74 130 12:52:02.874 [planetinfo.record]: coordinates = 87 26 12:52:02.874 [planetinfo.record]: government = 6; 12:52:02.874 [planetinfo.record]: economy = 2; 12:52:02.874 [planetinfo.record]: techlevel = 11; 12:52:02.874 [planetinfo.record]: population = 53; 12:52:02.874 [planetinfo.record]: productivity = 33920; 12:52:02.874 [planetinfo.record]: name = "Xerarebi"; 12:52:02.874 [planetinfo.record]: inhabitant = "Human Colonial"; 12:52:02.874 [planetinfo.record]: inhabitants = "Human Colonials"; 12:52:02.874 [planetinfo.record]: description = "This world is very notable for the Xerarebiian edible arts graduate."; 12:52:02.900 [planetinfo.record]: air_color = 0.656858, 0.885614, 0.904693, 1; 12:52:02.900 [planetinfo.record]: cloud_alpha = 1.000000; 12:52:02.900 [planetinfo.record]: cloud_color = 0.678144, 0.716797, 0.557198, 1; 12:52:02.900 [planetinfo.record]: cloud_fraction = 0.410000; 12:52:02.900 [planetinfo.record]: land_color = 0.66, 0.568879, 0.211406, 1; 12:52:02.900 [planetinfo.record]: land_fraction = 0.490000; 12:52:02.900 [planetinfo.record]: polar_cloud_color = 0.794836, 0.822559, 0.708091, 1; 12:52:02.900 [planetinfo.record]: polar_land_color = 0.934, 0.901763, 0.775293, 1; 12:52:02.900 [planetinfo.record]: polar_sea_color = 0.94043, 0.937336, 0.890929, 1; 12:52:02.900 [planetinfo.record]: sea_color = 0.595703, 0.587864, 0.47028, 1; 12:52:02.900 [planetinfo.record]: rotation_speed = 0.000697 12:52:02.900 [planetinfo.record]: planet zpos = 375650.000000 12:52:02.900 [planetinfo.record]: sun_radius = 101390.628815 12:52:02.900 [planetinfo.record]: sun_vector = 0.053 -0.328 -0.943 12:52:02.900 [planetinfo.record]: sun_distance = 580550 12:52:02.900 [planetinfo.record]: corona_flare = 0.090530 12:52:02.900 [planetinfo.record]: corona_hues = 0.538170 12:52:02.900 [planetinfo.record]: sun_color = 0.769272, 0.538262, 0.375548, 1 12:52:02.900 [planetinfo.record]: corona_shimmer = 0.317482 12:52:02.900 [planetinfo.record]: station_vector = -0.132 -0.744 0.655 12:52:02.900 [planetinfo.record]: station = dodecahedron 12:52:07.915 [PLANETINFO OVER]: Done 12:52:07.916 [PLANETINFO LOGGING]: 3 94 12:52:08.918 [planetinfo.record]: seed = 18 70 218 9 12:52:08.918 [planetinfo.record]: coordinates = 70 206 12:52:08.918 [planetinfo.record]: government = 2; 12:52:08.918 [planetinfo.record]: economy = 6; 12:52:08.918 [planetinfo.record]: techlevel = 4; 12:52:08.918 [planetinfo.record]: population = 25; 12:52:08.918 [planetinfo.record]: productivity = 4800; 12:52:08.918 [planetinfo.record]: name = "Esrireti"; 12:52:08.918 [planetinfo.record]: inhabitant = "Small Green Slimy Frog"; 12:52:08.919 [planetinfo.record]: inhabitants = "Small Green Slimy Frogs"; 12:52:08.919 [planetinfo.record]: description = "The planet Esrireti is scourged by deadly disease."; 12:52:08.922 [planetinfo.record]: air_color = 0.704721, 0.549763, 0.912498, 1; 12:52:08.922 [planetinfo.record]: cloud_alpha = 1.000000; 12:52:08.922 [planetinfo.record]: cloud_color = 0.740234, 0.30072, 0.609754, 1; 12:52:08.922 [planetinfo.record]: cloud_fraction = 0.250000; 12:52:08.922 [planetinfo.record]: land_color = 0.66, 0.54906, 0.451172, 1; 12:52:08.922 [planetinfo.record]: land_fraction = 0.550000; 12:52:08.922 [planetinfo.record]: polar_cloud_color = 0.833105, 0.523945, 0.741324, 1; 12:52:08.922 [planetinfo.record]: polar_land_color = 0.934, 0.894751, 0.860119, 1; 12:52:08.922 [planetinfo.record]: polar_sea_color = 0.870808, 0.855445, 0.908594, 1; 12:52:08.923 [planetinfo.record]: sea_color = 0.76201, 0.700186, 0.914062, 1; 12:52:08.923 [planetinfo.record]: rotation_speed = 0.001265 12:52:08.923 [planetinfo.record]: planet zpos = 622800.000000 12:52:08.923 [planetinfo.record]: sun_radius = 139710.118103 12:52:08.923 [planetinfo.record]: sun_vector = 0.705 0.524 0.477 12:52:08.923 [planetinfo.record]: sun_distance = 1038000 12:52:08.923 [planetinfo.record]: corona_flare = 0.085162 12:52:08.923 [planetinfo.record]: corona_hues = 0.996002 12:52:08.923 [planetinfo.record]: sun_color = 0.743768, 0.680045, 0.252802, 1 12:52:08.923 [planetinfo.record]: corona_shimmer = 0.331616 12:52:08.923 [planetinfo.record]: station_vector = 0.285 0.939 0.191 12:52:08.923 [planetinfo.record]: station = coriolis 12:52:14.190 [PLANETINFO OVER]: Done 12:52:14.191 [PLANETINFO LOGGING]: 3 95 12:52:15.195 [planetinfo.record]: seed = 98 151 234 36 12:52:15.195 [planetinfo.record]: coordinates = 151 110 12:52:15.195 [planetinfo.record]: government = 4; 12:52:15.195 [planetinfo.record]: economy = 6; 12:52:15.195 [planetinfo.record]: techlevel = 6; 12:52:15.195 [planetinfo.record]: population = 35; 12:52:15.195 [planetinfo.record]: productivity = 8960; 12:52:15.196 [planetinfo.record]: name = "Zamaso"; 12:52:15.196 [planetinfo.record]: inhabitant = "Fierce Red Bug-Eyed Frog"; 12:52:15.196 [planetinfo.record]: inhabitants = "Fierce Red Bug-Eyed Frogs"; 12:52:15.196 [planetinfo.record]: description = "This planet is very notable for its inhabitants’ weird shyness and its inhabitants’ ingrained shyness."; 12:52:15.224 [planetinfo.record]: air_color = 0.584324, 0.952923, 0.955424, 1; 12:52:15.224 [planetinfo.record]: cloud_alpha = 1.000000; 12:52:15.224 [planetinfo.record]: cloud_color = 0.853757, 0.869141, 0.376854, 1; 12:52:15.224 [planetinfo.record]: cloud_fraction = 0.550000; 12:52:15.224 [planetinfo.record]: land_color = 0.0301067, 0.646484, 0.0252533, 1; 12:52:15.224 [planetinfo.record]: land_fraction = 0.680000; 12:52:15.224 [planetinfo.record]: polar_cloud_color = 0.881255, 0.891113, 0.575656, 1; 12:52:15.224 [planetinfo.record]: polar_land_color = 0.712403, 0.935352, 0.710648, 1; 12:52:15.224 [planetinfo.record]: polar_sea_color = 0.865179, 0.923242, 0.874251, 1; 12:52:15.224 [planetinfo.record]: sea_color = 0.574484, 0.767578, 0.604655, 1; 12:52:15.224 [planetinfo.record]: rotation_speed = 0.001289 12:52:15.224 [planetinfo.record]: planet zpos = 598650.000000 12:52:15.225 [planetinfo.record]: sun_radius = 112105.592041 12:52:15.225 [planetinfo.record]: sun_vector = 0.006 -0.389 -0.921 12:52:15.225 [planetinfo.record]: sun_distance = 718380 12:52:15.225 [planetinfo.record]: corona_flare = 0.098511 12:52:15.225 [planetinfo.record]: corona_hues = 0.771202 12:52:15.225 [planetinfo.record]: sun_color = 0.849997, 0.849037, 0.358294, 1 12:52:15.225 [planetinfo.record]: corona_shimmer = 0.451972 12:52:15.225 [planetinfo.record]: station_vector = -0.942 -0.083 0.326 12:52:15.225 [planetinfo.record]: station = coriolis 12:52:19.899 [PLANETINFO OVER]: Done 12:52:19.900 [PLANETINFO LOGGING]: 3 96 12:52:20.902 [planetinfo.record]: seed = 66 55 154 73 12:52:20.902 [planetinfo.record]: coordinates = 55 231 12:52:20.902 [planetinfo.record]: government = 0; 12:52:20.902 [planetinfo.record]: economy = 7; 12:52:20.902 [planetinfo.record]: techlevel = 3; 12:52:20.902 [planetinfo.record]: population = 20; 12:52:20.902 [planetinfo.record]: productivity = 1920; 12:52:20.902 [planetinfo.record]: name = "Esusesqu"; 12:52:20.902 [planetinfo.record]: inhabitant = "Small Yellow Slimy Frog"; 12:52:20.902 [planetinfo.record]: inhabitants = "Small Yellow Slimy Frogs"; 12:52:20.902 [planetinfo.record]: description = "The world Esusesqu is mildly noted for its ancient mountains but ravaged by frequent earthquakes."; 12:52:20.918 [planetinfo.record]: air_color = 0.509208, 0.558554, 0.875426, 1; 12:52:20.918 [planetinfo.record]: cloud_alpha = 1.000000; 12:52:20.918 [planetinfo.record]: cloud_color = 0.216187, 0.377405, 0.628906, 1; 12:52:20.918 [planetinfo.record]: cloud_fraction = 0.300000; 12:52:20.918 [planetinfo.record]: land_color = 0.131484, 0.176904, 0.66, 1; 12:52:20.919 [planetinfo.record]: land_fraction = 0.570000; 12:52:20.919 [planetinfo.record]: polar_cloud_color = 0.461852, 0.587304, 0.783008, 1; 12:52:20.919 [planetinfo.record]: polar_land_color = 0.747018, 0.763086, 0.934, 1; 12:52:20.919 [planetinfo.record]: polar_sea_color = 0.932918, 0.939062, 0.889908, 1; 12:52:20.919 [planetinfo.record]: sea_color = 0.593427, 0.609375, 0.481787, 1; 12:52:20.919 [planetinfo.record]: rotation_speed = 0.000667 12:52:20.919 [planetinfo.record]: planet zpos = 621000.000000 12:52:20.919 [planetinfo.record]: sun_radius = 153546.741486 12:52:20.919 [planetinfo.record]: sun_vector = -0.169 -0.866 -0.471 12:52:20.919 [planetinfo.record]: sun_distance = 1086750 12:52:20.919 [planetinfo.record]: corona_flare = 0.022000 12:52:20.919 [planetinfo.record]: corona_hues = 0.755608 12:52:20.919 [planetinfo.record]: sun_color = 0.676071, 0.732581, 0.759583, 1 12:52:20.919 [planetinfo.record]: corona_shimmer = 0.424858 12:52:20.919 [planetinfo.record]: station_vector = -0.638 -0.759 0.128 12:52:20.919 [planetinfo.record]: station = coriolis 12:52:26.008 [PLANETINFO OVER]: Done 12:52:26.008 [PLANETINFO LOGGING]: 3 97 12:52:27.012 [planetinfo.record]: seed = 210 154 10 236 12:52:27.012 [planetinfo.record]: coordinates = 154 233 12:52:27.012 [planetinfo.record]: government = 2; 12:52:27.012 [planetinfo.record]: economy = 1; 12:52:27.013 [planetinfo.record]: techlevel = 9; 12:52:27.013 [planetinfo.record]: population = 40; 12:52:27.013 [planetinfo.record]: productivity = 17280; 12:52:27.013 [planetinfo.record]: name = "Inave"; 12:52:27.013 [planetinfo.record]: inhabitant = "Human Colonial"; 12:52:27.013 [planetinfo.record]: inhabitants = "Human Colonials"; 12:52:27.013 [planetinfo.record]: description = "The world Inave is reasonably famous for its inhabitants’ exceptional loathing of casinos."; 12:52:27.031 [planetinfo.record]: air_color = 0.600214, 0.928107, 0.891931, 1; 12:52:27.031 [planetinfo.record]: cloud_alpha = 1.000000; 12:52:27.031 [planetinfo.record]: cloud_color = 0.787109, 0.670897, 0.424301, 1; 12:52:27.032 [planetinfo.record]: cloud_fraction = 0.160000; 12:52:27.033 [planetinfo.record]: land_color = 0.493832, 0.66, 0.273281, 1; 12:52:27.033 [planetinfo.record]: land_fraction = 0.620000; 12:52:27.033 [planetinfo.record]: polar_cloud_color = 0.854199, 0.775376, 0.608116, 1; 12:52:27.033 [planetinfo.record]: polar_land_color = 0.875212, 0.934, 0.797184, 1; 12:52:27.033 [planetinfo.record]: polar_sea_color = 0.917649, 0.931641, 0.873868, 1; 12:52:27.033 [planetinfo.record]: sea_color = 0.642528, 0.683594, 0.51403, 1; 12:52:27.033 [planetinfo.record]: rotation_speed = 0.004506 12:52:27.033 [planetinfo.record]: planet zpos = 845880.000000 12:52:27.033 [planetinfo.record]: sun_radius = 164619.054565 12:52:27.033 [planetinfo.record]: sun_vector = 0.307 -0.914 -0.264 12:52:27.033 [planetinfo.record]: sun_distance = 1147980 12:52:27.034 [planetinfo.record]: corona_flare = 0.046533 12:52:27.034 [planetinfo.record]: corona_hues = 0.890686 12:52:27.034 [planetinfo.record]: sun_color = 0.579251, 0.664446, 0.680051, 1 12:52:27.034 [planetinfo.record]: corona_shimmer = 0.567392 12:52:27.035 [planetinfo.record]: station_vector = -0.844 -0.362 0.396 12:52:27.035 [planetinfo.record]: station = coriolis 12:52:31.616 [PLANETINFO OVER]: Done 12:52:31.617 [PLANETINFO LOGGING]: 3 98 12:52:32.620 [planetinfo.record]: seed = 178 82 90 185 12:52:32.620 [planetinfo.record]: coordinates = 82 246 12:52:32.620 [planetinfo.record]: government = 6; 12:52:32.620 [planetinfo.record]: economy = 6; 12:52:32.620 [planetinfo.record]: techlevel = 6; 12:52:32.621 [planetinfo.record]: population = 37; 12:52:32.621 [planetinfo.record]: productivity = 11840; 12:52:32.621 [planetinfo.record]: name = "Orxerema"; 12:52:32.621 [planetinfo.record]: inhabitant = "Human Colonial"; 12:52:32.621 [planetinfo.record]: inhabitants = "Human Colonials"; 12:52:32.621 [planetinfo.record]: description = "Orxerema is most famous for its pink Us tulip plantations and its inhabitants’ ingrained silliness."; 12:52:32.636 [planetinfo.record]: air_color = 0.639558, 0.508677, 0.930709, 1; 12:52:32.636 [planetinfo.record]: cloud_alpha = 1.000000; 12:52:32.636 [planetinfo.record]: cloud_color = 0.761808, 0.189415, 0.794922, 1; 12:52:32.637 [planetinfo.record]: cloud_fraction = 0.440000; 12:52:32.637 [planetinfo.record]: land_color = 0.494839, 0.66, 0.402188, 1; 12:52:32.637 [planetinfo.record]: land_fraction = 0.350000; 12:52:32.637 [planetinfo.record]: polar_cloud_color = 0.835384, 0.449379, 0.857715, 1; 12:52:32.637 [planetinfo.record]: polar_land_color = 0.875568, 0.934, 0.842789, 1; 12:52:32.637 [planetinfo.record]: polar_sea_color = 0.935352, 0.905796, 0.874335, 1; 12:52:32.637 [planetinfo.record]: sea_color = 0.646484, 0.564774, 0.477792, 1; 12:52:32.637 [planetinfo.record]: rotation_speed = 0.002597 12:52:32.637 [planetinfo.record]: planet zpos = 624240.000000 12:52:32.637 [planetinfo.record]: sun_radius = 100488.707886 12:52:32.637 [planetinfo.record]: sun_vector = -0.269 0.364 -0.891 12:52:32.637 [planetinfo.record]: sun_distance = 1092420 12:52:32.637 [planetinfo.record]: corona_flare = 0.008321 12:52:32.637 [planetinfo.record]: corona_hues = 0.997910 12:52:32.637 [planetinfo.record]: sun_color = 0.671259, 0.430059, 0.358665, 1 12:52:32.637 [planetinfo.record]: corona_shimmer = 0.518038 12:52:32.637 [planetinfo.record]: station_vector = 0.878 -0.438 0.191 12:52:32.637 [planetinfo.record]: station = coriolis 12:52:37.209 [PLANETINFO OVER]: Done 12:52:37.209 [PLANETINFO LOGGING]: 3 99 12:52:38.213 [planetinfo.record]: seed = 2 203 170 220 12:52:38.213 [planetinfo.record]: coordinates = 203 14 12:52:38.213 [planetinfo.record]: government = 0; 12:52:38.213 [planetinfo.record]: economy = 6; 12:52:38.213 [planetinfo.record]: techlevel = 4; 12:52:38.213 [planetinfo.record]: population = 23; 12:52:38.213 [planetinfo.record]: productivity = 2944; 12:52:38.213 [planetinfo.record]: name = "Teveriat"; 12:52:38.213 [planetinfo.record]: inhabitant = "Furry Humanoid"; 12:52:38.214 [planetinfo.record]: inhabitants = "Furry Humanoids"; 12:52:38.214 [planetinfo.record]: description = "This planet is beset by lethal disease."; 12:52:38.240 [planetinfo.record]: air_color = 0.504884, 0.469708, 0.93266, 1; 12:52:38.240 [planetinfo.record]: cloud_alpha = 1.000000; 12:52:38.240 [planetinfo.record]: cloud_color = 0.260313, 0.0875854, 0.800781, 1; 12:52:38.240 [planetinfo.record]: cloud_fraction = 0.300000; 12:52:38.240 [planetinfo.record]: land_color = 0.580078, 0.66, 0.639395, 1; 12:52:38.240 [planetinfo.record]: land_fraction = 0.470000; 12:52:38.240 [planetinfo.record]: polar_cloud_color = 0.49743, 0.381445, 0.860352, 1; 12:52:38.240 [planetinfo.record]: polar_land_color = 0.905725, 0.934, 0.92671, 1; 12:52:38.240 [planetinfo.record]: polar_sea_color = 0.878736, 0.92207, 0.859038, 1; 12:52:38.240 [planetinfo.record]: sea_color = 0.632798, 0.779297, 0.566208, 1; 12:52:38.240 [planetinfo.record]: rotation_speed = 0.000131 12:52:38.240 [planetinfo.record]: planet zpos = 730920.000000 12:52:38.240 [planetinfo.record]: sun_radius = 122840.495300 12:52:38.240 [planetinfo.record]: sun_vector = -0.930 -0.127 -0.344 12:52:38.240 [planetinfo.record]: sun_distance = 1157290 12:52:38.240 [planetinfo.record]: corona_flare = 0.040401 12:52:38.240 [planetinfo.record]: corona_hues = 0.662094 12:52:38.240 [planetinfo.record]: sun_color = 0.741324, 0.577662, 0.274784, 1 12:52:38.241 [planetinfo.record]: corona_shimmer = 0.657794 12:52:38.241 [planetinfo.record]: station_vector = -0.981 -0.084 0.175 12:52:38.241 [planetinfo.record]: station = coriolis 12:52:42.818 [PLANETINFO OVER]: Done 12:52:42.819 [PLANETINFO LOGGING]: 3 100 12:52:43.823 [planetinfo.record]: seed = 98 241 26 6 12:52:43.823 [planetinfo.record]: coordinates = 241 94 12:52:43.823 [planetinfo.record]: government = 4; 12:52:43.823 [planetinfo.record]: economy = 6; 12:52:43.823 [planetinfo.record]: techlevel = 4; 12:52:43.823 [planetinfo.record]: population = 27; 12:52:43.824 [planetinfo.record]: productivity = 6912; 12:52:43.824 [planetinfo.record]: name = "Biladi"; 12:52:43.824 [planetinfo.record]: inhabitant = "Human Colonial"; 12:52:43.824 [planetinfo.record]: inhabitants = "Human Colonials"; 12:52:43.824 [planetinfo.record]: description = "This world is a revolting little planet."; 12:52:43.848 [planetinfo.record]: air_color = 0.532777, 0.573878, 0.856564, 1; 12:52:43.848 [planetinfo.record]: cloud_alpha = 1.000000; 12:52:43.848 [planetinfo.record]: cloud_color = 0.266014, 0.378466, 0.572266, 1; 12:52:43.848 [planetinfo.record]: cloud_fraction = 0.490000; 12:52:43.848 [planetinfo.record]: land_color = 0.66, 0.0383093, 0.00773438, 1; 12:52:43.848 [planetinfo.record]: land_fraction = 0.310000; 12:52:43.848 [planetinfo.record]: polar_cloud_color = 0.50415, 0.597184, 0.75752, 1; 12:52:43.848 [planetinfo.record]: polar_land_color = 0.934, 0.714053, 0.703236, 1; 12:52:43.848 [planetinfo.record]: polar_sea_color = 0.897823, 0.934766, 0.88748, 1; 12:52:43.848 [planetinfo.record]: sea_color = 0.549221, 0.652344, 0.520346, 1; 12:52:43.848 [planetinfo.record]: rotation_speed = 0.001914 12:52:43.848 [planetinfo.record]: planet zpos = 459300.000000 12:52:43.848 [planetinfo.record]: sun_radius = 127503.827362 12:52:43.848 [planetinfo.record]: sun_vector = -0.157 0.078 0.985 12:52:43.848 [planetinfo.record]: sun_distance = 1102320 12:52:43.848 [planetinfo.record]: corona_flare = 0.052003 12:52:43.848 [planetinfo.record]: corona_hues = 0.787407 12:52:43.848 [planetinfo.record]: sun_color = 0.214258, 0.677377, 0.802789, 1 12:52:43.849 [planetinfo.record]: corona_shimmer = 0.476625 12:52:43.849 [planetinfo.record]: station_vector = -0.980 0.197 0.024 12:52:43.849 [planetinfo.record]: station = coriolis 12:52:48.360 [PLANETINFO OVER]: Done 12:52:48.361 [PLANETINFO LOGGING]: 3 101 12:52:49.364 [planetinfo.record]: seed = 242 168 202 75 12:52:49.364 [planetinfo.record]: coordinates = 168 77 12:52:49.364 [planetinfo.record]: government = 6; 12:52:49.364 [planetinfo.record]: economy = 5; 12:52:49.364 [planetinfo.record]: techlevel = 5; 12:52:49.365 [planetinfo.record]: population = 32; 12:52:49.365 [planetinfo.record]: productivity = 12800; 12:52:49.365 [planetinfo.record]: name = "Maleve"; 12:52:49.365 [planetinfo.record]: inhabitant = "Small Yellow Furry Rodent"; 12:52:49.365 [planetinfo.record]: inhabitants = "Small Yellow Furry Rodents"; 12:52:49.365 [planetinfo.record]: description = "This world is most fabled for Maleveian lethal brandy but scourged by deadly tree ants."; 12:52:49.386 [planetinfo.record]: air_color = 0.705598, 0.723222, 0.978838, 1; 12:52:49.386 [planetinfo.record]: cloud_alpha = 1.000000; 12:52:49.386 [planetinfo.record]: cloud_color = 0.715599, 0.755823, 0.939453, 1; 12:52:49.387 [planetinfo.record]: cloud_fraction = 0.160000; 12:52:49.387 [planetinfo.record]: land_color = 0.451172, 0.464224, 0.66, 1; 12:52:49.387 [planetinfo.record]: land_fraction = 0.280000; 12:52:49.387 [planetinfo.record]: polar_cloud_color = 0.785332, 0.810025, 0.922754, 1; 12:52:49.387 [planetinfo.record]: polar_land_color = 0.860119, 0.864737, 0.934, 1; 12:52:49.387 [planetinfo.record]: polar_sea_color = 0.93689, 0.938477, 0.887703, 1; 12:52:49.387 [planetinfo.record]: sea_color = 0.611074, 0.615234, 0.482094, 1; 12:52:49.387 [planetinfo.record]: rotation_speed = 0.003184 12:52:49.387 [planetinfo.record]: planet zpos = 638000.000000 12:52:49.387 [planetinfo.record]: sun_radius = 157075.073242 12:52:49.387 [planetinfo.record]: sun_vector = -0.182 -0.890 0.417 12:52:49.387 [planetinfo.record]: sun_distance = 1044000 12:52:49.388 [planetinfo.record]: corona_flare = 0.028578 12:52:49.388 [planetinfo.record]: corona_hues = 0.888298 12:52:49.388 [planetinfo.record]: sun_color = 0.40326, 0.517302, 0.787387, 1 12:52:49.388 [planetinfo.record]: corona_shimmer = 0.366352 12:52:49.388 [planetinfo.record]: station_vector = 0.692 -0.255 0.675 12:52:49.388 [planetinfo.record]: station = coriolis 12:52:54.139 [PLANETINFO OVER]: Done 12:52:54.139 [PLANETINFO LOGGING]: 3 102 12:52:55.143 [planetinfo.record]: seed = 82 196 218 60 12:52:55.143 [planetinfo.record]: coordinates = 196 54 12:52:55.143 [planetinfo.record]: government = 2; 12:52:55.143 [planetinfo.record]: economy = 6; 12:52:55.143 [planetinfo.record]: techlevel = 2; 12:52:55.143 [planetinfo.record]: population = 17; 12:52:55.143 [planetinfo.record]: productivity = 3264; 12:52:55.143 [planetinfo.record]: name = "Tetied"; 12:52:55.143 [planetinfo.record]: inhabitant = "Red Horned Lizard"; 12:52:55.144 [planetinfo.record]: inhabitants = "Red Horned Lizards"; 12:52:55.144 [planetinfo.record]: description = "Tetied is reasonably well known for the Tetiedian spotted leopard but plagued by vicious disease."; 12:52:55.160 [planetinfo.record]: air_color = 0.432311, 0.80902, 0.847459, 1; 12:52:55.160 [planetinfo.record]: cloud_alpha = 1.000000; 12:52:55.160 [planetinfo.record]: cloud_color = 0.40771, 0.544922, 0.0702438, 1; 12:52:55.160 [planetinfo.record]: cloud_fraction = 0.110000; 12:52:55.160 [planetinfo.record]: land_color = 0.525937, 0.66, 0.575164, 1; 12:52:55.160 [planetinfo.record]: land_fraction = 0.250000; 12:52:55.160 [planetinfo.record]: polar_cloud_color = 0.627936, 0.745215, 0.339495, 1; 12:52:55.160 [planetinfo.record]: polar_land_color = 0.88657, 0.934, 0.903986, 1; 12:52:55.160 [planetinfo.record]: polar_sea_color = 0.829237, 0.949023, 0.774789, 1; 12:52:55.160 [planetinfo.record]: sea_color = 0.252394, 0.509766, 0.135406, 1; 12:52:55.160 [planetinfo.record]: rotation_speed = 0.003807 12:52:55.160 [planetinfo.record]: planet zpos = 608400.000000 12:52:55.160 [planetinfo.record]: sun_radius = 141496.446533 12:52:55.160 [planetinfo.record]: sun_vector = -0.476 0.197 0.857 12:52:55.160 [planetinfo.record]: sun_distance = 1216800 12:52:55.160 [planetinfo.record]: corona_flare = 0.083830 12:52:55.160 [planetinfo.record]: corona_hues = 0.733261 12:52:55.160 [planetinfo.record]: sun_color = 0.754275, 0.755305, 0.766922, 1 12:52:55.160 [planetinfo.record]: corona_shimmer = 0.529729 12:52:55.161 [planetinfo.record]: station_vector = 0.318 -0.220 0.922 12:52:55.161 [planetinfo.record]: station = coriolis 12:53:00.127 [PLANETINFO OVER]: Done 12:53:00.128 [PLANETINFO LOGGING]: 3 103 12:53:01.130 [planetinfo.record]: seed = 162 173 106 30 12:53:01.130 [planetinfo.record]: coordinates = 173 56 12:53:01.130 [planetinfo.record]: government = 4; 12:53:01.130 [planetinfo.record]: economy = 0; 12:53:01.130 [planetinfo.record]: techlevel = 10; 12:53:01.130 [planetinfo.record]: population = 45; 12:53:01.130 [planetinfo.record]: productivity = 28800; 12:53:01.130 [planetinfo.record]: name = "Riceatra"; 12:53:01.130 [planetinfo.record]: inhabitant = "Human Colonial"; 12:53:01.130 [planetinfo.record]: inhabitants = "Human Colonials"; 12:53:01.130 [planetinfo.record]: description = "This planet is plagued by deadly earthquakes."; 12:53:01.134 [planetinfo.record]: air_color = 0.643197, 0.794938, 0.976887, 1; 12:53:01.134 [planetinfo.record]: cloud_alpha = 1.000000; 12:53:01.134 [planetinfo.record]: cloud_color = 0.536087, 0.933594, 0.793845, 1; 12:53:01.134 [planetinfo.record]: cloud_fraction = 0.440000; 12:53:01.134 [planetinfo.record]: land_color = 0.631459, 0.66, 0.626484, 1; 12:53:01.134 [planetinfo.record]: land_fraction = 0.350000; 12:53:01.134 [planetinfo.record]: polar_cloud_color = 0.675262, 0.920117, 0.834035, 1; 12:53:01.134 [planetinfo.record]: polar_land_color = 0.923903, 0.934, 0.922143, 1; 12:53:01.134 [planetinfo.record]: polar_sea_color = 0.756438, 0.940039, 0.849673, 1; 12:53:01.134 [planetinfo.record]: sea_color = 0.131165, 0.599609, 0.369047, 1; 12:53:01.134 [planetinfo.record]: rotation_speed = 0.003789 12:53:01.134 [planetinfo.record]: planet zpos = 788760.000000 12:53:01.134 [planetinfo.record]: sun_radius = 167660.845642 12:53:01.134 [planetinfo.record]: sun_vector = 0.985 -0.154 0.079 12:53:01.134 [planetinfo.record]: sun_distance = 1248870 12:53:01.134 [planetinfo.record]: corona_flare = 0.071161 12:53:01.135 [planetinfo.record]: corona_hues = 0.796242 12:53:01.135 [planetinfo.record]: sun_color = 0.795246, 0.80986, 0.82435, 1 12:53:01.135 [planetinfo.record]: corona_shimmer = 0.308413 12:53:01.135 [planetinfo.record]: station_vector = 0.631 0.633 0.447 12:53:01.135 [planetinfo.record]: station = coriolis 12:53:05.605 [PLANETINFO OVER]: Done 12:53:05.606 [PLANETINFO LOGGING]: 3 104 12:53:06.611 [planetinfo.record]: seed = 130 244 154 202 12:53:06.611 [planetinfo.record]: coordinates = 244 209 12:53:06.611 [planetinfo.record]: government = 0; 12:53:06.611 [planetinfo.record]: economy = 3; 12:53:06.611 [planetinfo.record]: techlevel = 4; 12:53:06.611 [planetinfo.record]: population = 20; 12:53:06.611 [planetinfo.record]: productivity = 4480; 12:53:06.611 [planetinfo.record]: name = "Arera"; 12:53:06.611 [planetinfo.record]: inhabitant = "Small Furry Insect"; 12:53:06.611 [planetinfo.record]: inhabitants = "Small Furry Insects"; 12:53:06.611 [planetinfo.record]: description = "This planet is most fabled for its inhabitants’ ingrained silliness but beset by deadly tree grubs."; 12:53:06.615 [planetinfo.record]: air_color = 0.465879, 0.552277, 0.904043, 1; 12:53:06.615 [planetinfo.record]: cloud_alpha = 1.000000; 12:53:06.616 [planetinfo.record]: cloud_color = 0.103317, 0.456856, 0.714844, 1; 12:53:06.616 [planetinfo.record]: cloud_fraction = 0.150000; 12:53:06.616 [planetinfo.record]: land_color = 0.559292, 0.66, 0.531094, 1; 12:53:06.616 [planetinfo.record]: land_fraction = 0.700000; 12:53:06.616 [planetinfo.record]: polar_cloud_color = 0.382354, 0.636339, 0.82168, 1; 12:53:06.616 [planetinfo.record]: polar_land_color = 0.898371, 0.934, 0.888395, 1; 12:53:06.616 [planetinfo.record]: polar_sea_color = 0.948047, 0.821715, 0.720293, 1; 12:53:06.616 [planetinfo.record]: sea_color = 0.519531, 0.242611, 0.0202942, 1; 12:53:06.616 [planetinfo.record]: rotation_speed = 0.003254 12:53:06.616 [planetinfo.record]: planet zpos = 674400.000000 12:53:06.616 [planetinfo.record]: sun_radius = 140748.687744 12:53:06.616 [planetinfo.record]: sun_vector = 0.436 -0.887 -0.153 12:53:06.616 [planetinfo.record]: sun_distance = 1124000 12:53:06.616 [planetinfo.record]: corona_flare = 0.026376 12:53:06.616 [planetinfo.record]: corona_hues = 0.634727 12:53:06.617 [planetinfo.record]: sun_color = 0.726672, 0.480331, 0.377487, 1 12:53:06.617 [planetinfo.record]: corona_shimmer = 0.623737 12:53:06.617 [planetinfo.record]: station_vector = -0.187 0.974 0.128 12:53:06.617 [planetinfo.record]: station = coriolis 12:53:11.050 [PLANETINFO OVER]: Done 12:53:11.050 [PLANETINFO LOGGING]: 3 105 12:53:12.053 [planetinfo.record]: seed = 18 170 138 137 12:53:12.053 [planetinfo.record]: coordinates = 170 79 12:53:12.053 [planetinfo.record]: government = 2; 12:53:12.053 [planetinfo.record]: economy = 7; 12:53:12.053 [planetinfo.record]: techlevel = 3; 12:53:12.053 [planetinfo.record]: population = 22; 12:53:12.053 [planetinfo.record]: productivity = 3168; 12:53:12.053 [planetinfo.record]: name = "Esxevexe"; 12:53:12.053 [planetinfo.record]: inhabitant = "Small Black Furry Feline"; 12:53:12.053 [planetinfo.record]: inhabitants = "Small Black Furry Felines"; 12:53:12.053 [planetinfo.record]: description = "The planet Esxevexe is an unremarkable dump."; 12:53:12.064 [planetinfo.record]: air_color = 0.652615, 0.855914, 0.798577, 1; 12:53:12.064 [planetinfo.record]: cloud_alpha = 1.000000; 12:53:12.064 [planetinfo.record]: cloud_color = 0.570312, 0.516306, 0.505707, 1; 12:53:12.064 [planetinfo.record]: cloud_fraction = 0.400000; 12:53:12.064 [planetinfo.record]: land_color = 0.495649, 0.374359, 0.550781, 1; 12:53:12.064 [planetinfo.record]: land_fraction = 0.270000; 12:53:12.064 [planetinfo.record]: polar_cloud_color = 0.756641, 0.711859, 0.70307, 1; 12:53:12.064 [planetinfo.record]: polar_land_color = 0.921276, 0.869254, 0.944922, 1; 12:53:12.064 [planetinfo.record]: polar_sea_color = 0.862976, 0.920508, 0.865223, 1; 12:53:12.064 [planetinfo.record]: sea_color = 0.596191, 0.794922, 0.603954, 1; 12:53:12.064 [planetinfo.record]: rotation_speed = 0.001963 12:53:12.064 [planetinfo.record]: planet zpos = 740600.000000 12:53:12.064 [planetinfo.record]: sun_radius = 134145.281982 12:53:12.071 [planetinfo.record]: sun_vector = -0.951 -0.294 0.099 12:53:12.071 [planetinfo.record]: sun_distance = 1005100 12:53:12.072 [planetinfo.record]: corona_flare = 0.036049 12:53:12.072 [planetinfo.record]: corona_hues = 0.605927 12:53:12.072 [planetinfo.record]: sun_color = 0.472946, 0.495478, 0.775616, 1 12:53:12.072 [planetinfo.record]: corona_shimmer = 0.265386 12:53:12.072 [planetinfo.record]: station_vector = -0.471 -0.002 0.882 12:53:12.072 [planetinfo.record]: station = coriolis 12:53:16.621 [PLANETINFO OVER]: Done 12:53:16.622 [PLANETINFO LOGGING]: 3 106 12:53:17.641 [planetinfo.record]: seed = 242 194 90 252 12:53:17.641 [planetinfo.record]: coordinates = 194 182 12:53:17.641 [planetinfo.record]: government = 6; 12:53:17.641 [planetinfo.record]: economy = 6; 12:53:17.641 [planetinfo.record]: techlevel = 6; 12:53:17.641 [planetinfo.record]: population = 37; 12:53:17.641 [planetinfo.record]: productivity = 11840; 12:53:17.641 [planetinfo.record]: name = "Telala"; 12:53:17.641 [planetinfo.record]: inhabitant = "Human Colonial"; 12:53:17.641 [planetinfo.record]: inhabitants = "Human Colonials"; 12:53:17.641 [planetinfo.record]: description = "This planet is plagued by frequent earthquakes."; 12:53:17.645 [planetinfo.record]: air_color = 0.614015, 0.501063, 0.930059, 1; 12:53:17.645 [planetinfo.record]: cloud_alpha = 1.000000; 12:53:17.645 [planetinfo.record]: cloud_color = 0.671366, 0.170364, 0.792969, 1; 12:53:17.646 [planetinfo.record]: cloud_fraction = 0.340000; 12:53:17.646 [planetinfo.record]: land_color = 0.66, 0.590894, 0.471797, 1; 12:53:17.646 [planetinfo.record]: land_fraction = 0.710000; 12:53:17.646 [planetinfo.record]: polar_cloud_color = 0.774713, 0.436367, 0.856836, 1; 12:53:17.646 [planetinfo.record]: polar_land_color = 0.934, 0.909551, 0.867416, 1; 12:53:17.646 [planetinfo.record]: polar_sea_color = 0.865259, 0.862515, 0.912695, 1; 12:53:17.646 [planetinfo.record]: sea_color = 0.691545, 0.681045, 0.873047, 1; 12:53:17.646 [planetinfo.record]: rotation_speed = 0.000144 12:53:17.646 [planetinfo.record]: planet zpos = 912300.000000 12:53:17.646 [planetinfo.record]: sun_radius = 155854.962158 12:53:17.646 [planetinfo.record]: sun_vector = 0.209 0.726 0.655 12:53:17.646 [planetinfo.record]: sun_distance = 1216400 12:53:17.646 [planetinfo.record]: corona_flare = 0.067154 12:53:17.646 [planetinfo.record]: corona_hues = 0.503555 12:53:17.646 [planetinfo.record]: sun_color = 0.687598, 0.684706, 0.675456, 1 12:53:17.646 [planetinfo.record]: corona_shimmer = 0.352272 12:53:17.647 [planetinfo.record]: station_vector = 0.346 -0.416 0.841 12:53:17.647 [planetinfo.record]: station = coriolis 12:53:22.296 [PLANETINFO OVER]: Done 12:53:22.298 [PLANETINFO LOGGING]: 3 107 12:53:23.301 [planetinfo.record]: seed = 66 167 42 82 12:53:23.301 [planetinfo.record]: coordinates = 167 53 12:53:23.301 [planetinfo.record]: government = 0; 12:53:23.301 [planetinfo.record]: economy = 7; 12:53:23.301 [planetinfo.record]: techlevel = 3; 12:53:23.301 [planetinfo.record]: population = 20; 12:53:23.301 [planetinfo.record]: productivity = 1920; 12:53:23.301 [planetinfo.record]: name = "Enreso"; 12:53:23.301 [planetinfo.record]: inhabitant = "Human Colonial"; 12:53:23.301 [planetinfo.record]: inhabitants = "Human Colonials"; 12:53:23.301 [planetinfo.record]: description = "This world is reasonably notable for its fabulous vicious Nore brew but ravaged by lethal vicious ants."; 12:53:23.328 [planetinfo.record]: air_color = 0.418943, 0.804615, 0.863068, 1; 12:53:23.328 [planetinfo.record]: cloud_alpha = 1.000000; 12:53:23.328 [planetinfo.record]: cloud_color = 0.364527, 0.591797, 0.0323639, 1; 12:53:23.328 [planetinfo.record]: cloud_fraction = 0.460000; 12:53:23.328 [planetinfo.record]: land_color = 0.66, 0.0747656, 0.637139, 1; 12:53:23.328 [planetinfo.record]: land_fraction = 0.330000; 12:53:23.328 [planetinfo.record]: polar_cloud_color = 0.582379, 0.766309, 0.313558, 1; 12:53:23.328 [planetinfo.record]: polar_land_color = 0.934, 0.726951, 0.925912, 1; 12:53:23.328 [planetinfo.record]: polar_sea_color = 0.947461, 0.911889, 0.899533, 1; 12:53:23.328 [planetinfo.record]: sea_color = 0.525391, 0.446489, 0.419081, 1; 12:53:23.328 [planetinfo.record]: rotation_speed = 0.002562 12:53:23.328 [planetinfo.record]: planet zpos = 384450.000000 12:53:23.328 [planetinfo.record]: sun_radius = 66110.408020 12:53:23.328 [planetinfo.record]: sun_vector = -0.401 0.834 -0.380 12:53:23.328 [planetinfo.record]: sun_distance = 733950 12:53:23.328 [planetinfo.record]: corona_flare = 0.037051 12:53:23.328 [planetinfo.record]: corona_hues = 0.552917 12:53:23.328 [planetinfo.record]: sun_color = 0.735816, 0.737367, 0.739081, 1 12:53:23.328 [planetinfo.record]: corona_shimmer = 0.908859 12:53:23.329 [planetinfo.record]: station_vector = 0.116 -0.922 0.369 12:53:23.329 [planetinfo.record]: station = coriolis 12:53:28.718 [PLANETINFO OVER]: Done 12:53:28.718 [PLANETINFO LOGGING]: 3 108 12:53:29.722 [planetinfo.record]: seed = 162 168 26 255 12:53:29.722 [planetinfo.record]: coordinates = 168 39 12:53:29.722 [planetinfo.record]: government = 4; 12:53:29.722 [planetinfo.record]: economy = 7; 12:53:29.722 [planetinfo.record]: techlevel = 2; 12:53:29.722 [planetinfo.record]: population = 20; 12:53:29.722 [planetinfo.record]: productivity = 3840; 12:53:29.722 [planetinfo.record]: name = "Onatibi"; 12:53:29.722 [planetinfo.record]: inhabitant = "Human Colonial"; 12:53:29.722 [planetinfo.record]: inhabitants = "Human Colonials"; 12:53:29.722 [planetinfo.record]: description = "This planet is notable for the Onatibiian edible arts graduate and its weird tropical forests."; 12:53:29.726 [planetinfo.record]: air_color = 0.653557, 0.889136, 0.906645, 1; 12:53:29.727 [planetinfo.record]: cloud_alpha = 1.000000; 12:53:29.727 [planetinfo.record]: cloud_color = 0.684988, 0.722656, 0.550461, 1; 12:53:29.727 [planetinfo.record]: cloud_fraction = 0.200000; 12:53:29.727 [planetinfo.record]: land_color = 0.390787, 0.239766, 0.66, 1; 12:53:29.727 [planetinfo.record]: land_fraction = 0.470000; 12:53:29.727 [planetinfo.record]: polar_cloud_color = 0.798312, 0.825195, 0.702302, 1; 12:53:29.727 [planetinfo.record]: polar_land_color = 0.838756, 0.785326, 0.934, 1; 12:53:29.727 [planetinfo.record]: polar_sea_color = 0.930469, 0.84646, 0.842329, 1; 12:53:29.727 [planetinfo.record]: sea_color = 0.695312, 0.444204, 0.431854, 1; 12:53:29.727 [planetinfo.record]: rotation_speed = 0.004517 12:53:29.727 [planetinfo.record]: planet zpos = 750640.000000 12:53:29.727 [planetinfo.record]: sun_radius = 163047.742920 12:53:29.728 [planetinfo.record]: sun_vector = -0.078 0.909 0.408 12:53:29.728 [planetinfo.record]: sun_distance = 1228320 12:53:29.728 [planetinfo.record]: corona_flare = 0.006000 12:53:29.728 [planetinfo.record]: corona_hues = 0.708992 12:53:29.728 [planetinfo.record]: sun_color = 0.634023, 0.717638, 0.828806, 1 12:53:29.728 [planetinfo.record]: corona_shimmer = 0.443716 12:53:29.728 [planetinfo.record]: station_vector = 0.126 0.800 0.587 12:53:29.728 [planetinfo.record]: station = coriolis 12:53:34.074 [PLANETINFO OVER]: Done 12:53:34.075 [PLANETINFO LOGGING]: 3 109 12:53:35.078 [planetinfo.record]: seed = 50 70 74 141 12:53:35.078 [planetinfo.record]: coordinates = 70 119 12:53:35.078 [planetinfo.record]: government = 6; 12:53:35.078 [planetinfo.record]: economy = 7; 12:53:35.078 [planetinfo.record]: techlevel = 5; 12:53:35.079 [planetinfo.record]: population = 34; 12:53:35.079 [planetinfo.record]: productivity = 8160; 12:53:35.079 [planetinfo.record]: name = "Diarrive"; 12:53:35.079 [planetinfo.record]: inhabitant = "Human Colonial"; 12:53:35.079 [planetinfo.record]: inhabitants = "Human Colonials"; 12:53:35.079 [planetinfo.record]: description = "This world is fabled for its exciting lethal Lesooues water."; 12:53:35.081 [planetinfo.record]: air_color = 0.428723, 0.778258, 0.862418, 1; 12:53:35.081 [planetinfo.record]: cloud_alpha = 1.000000; 12:53:35.081 [planetinfo.record]: cloud_color = 0.271089, 0.589844, 0.0529938, 1; 12:53:35.081 [planetinfo.record]: cloud_fraction = 0.420000; 12:53:35.081 [planetinfo.record]: land_color = 0.659053, 0.66, 0.538828, 1; 12:53:35.081 [planetinfo.record]: land_fraction = 0.430000; 12:53:35.081 [planetinfo.record]: polar_cloud_color = 0.506903, 0.76543, 0.330017, 1; 12:53:35.081 [planetinfo.record]: polar_land_color = 0.933665, 0.934, 0.891131, 1; 12:53:35.081 [planetinfo.record]: polar_sea_color = 0.718299, 0.873594, 0.927539, 1; 12:53:35.081 [planetinfo.record]: sea_color = 0.0707626, 0.55604, 0.724609, 1; 12:53:35.081 [planetinfo.record]: rotation_speed = 0.000700 12:53:35.081 [planetinfo.record]: planet zpos = 683540.000000 12:53:35.081 [planetinfo.record]: sun_radius = 153525.699463 12:53:35.082 [planetinfo.record]: sun_vector = -0.414 -0.324 -0.850 12:53:35.082 [planetinfo.record]: sun_distance = 1242800 12:53:35.082 [planetinfo.record]: corona_flare = 0.081805 12:53:35.082 [planetinfo.record]: corona_hues = 0.543716 12:53:35.082 [planetinfo.record]: sun_color = 0.454062, 0.663112, 0.671808, 1 12:53:35.082 [planetinfo.record]: corona_shimmer = 0.374723 12:53:35.082 [planetinfo.record]: station_vector = -0.437 -0.861 0.259 12:53:35.082 [planetinfo.record]: station = coriolis 12:53:39.690 [PLANETINFO OVER]: Done 12:53:39.690 [PLANETINFO LOGGING]: 3 110 12:53:40.693 [planetinfo.record]: seed = 146 246 218 95 12:53:40.693 [planetinfo.record]: coordinates = 246 30 12:53:40.693 [planetinfo.record]: government = 2; 12:53:40.693 [planetinfo.record]: economy = 6; 12:53:40.693 [planetinfo.record]: techlevel = 4; 12:53:40.693 [planetinfo.record]: population = 25; 12:53:40.693 [planetinfo.record]: productivity = 4800; 12:53:40.693 [planetinfo.record]: name = "Onramaon"; 12:53:40.693 [planetinfo.record]: inhabitant = "Yellow Slimy Lobster"; 12:53:40.693 [planetinfo.record]: inhabitants = "Yellow Slimy Lobsters"; 12:53:40.693 [planetinfo.record]: description = "The planet Onramaon is reasonably noted for its inhabitants’ eccentric shyness and its inhabitants’ ingrained silliness."; 12:53:40.708 [planetinfo.record]: air_color = 0.58803, 0.916516, 0.969732, 1; 12:53:40.708 [planetinfo.record]: cloud_alpha = 1.000000; 12:53:40.708 [planetinfo.record]: cloud_color = 0.683999, 0.912109, 0.381233, 1; 12:53:40.708 [planetinfo.record]: cloud_fraction = 0.430000; 12:53:40.708 [planetinfo.record]: land_color = 0.330025, 0.309662, 0.570312, 1; 12:53:40.708 [planetinfo.record]: land_fraction = 0.610000; 12:53:40.708 [planetinfo.record]: polar_cloud_color = 0.768139, 0.910449, 0.579256, 1; 12:53:40.708 [planetinfo.record]: polar_land_color = 0.843644, 0.835227, 0.942969, 1; 12:53:40.708 [planetinfo.record]: polar_sea_color = 0.922852, 0.880311, 0.820112, 1; 12:53:40.708 [planetinfo.record]: sea_color = 0.771484, 0.629233, 0.427933, 1; 12:53:40.708 [planetinfo.record]: rotation_speed = 0.001331 12:53:40.708 [planetinfo.record]: planet zpos = 828240.000000 12:53:40.708 [planetinfo.record]: sun_radius = 208360.654602 12:53:40.708 [planetinfo.record]: sun_vector = 0.895 -0.297 0.333 12:53:40.708 [planetinfo.record]: sun_distance = 1380400 12:53:40.708 [planetinfo.record]: corona_flare = 0.075616 12:53:40.708 [planetinfo.record]: corona_hues = 0.503181 12:53:40.708 [planetinfo.record]: sun_color = 0.398052, 0.66133, 0.66181, 1 12:53:40.708 [planetinfo.record]: corona_shimmer = 0.369204 12:53:40.709 [planetinfo.record]: station_vector = -0.577 -0.807 0.128 12:53:40.709 [planetinfo.record]: station = coriolis 12:53:46.290 [PLANETINFO OVER]: Done 12:53:46.291 [PLANETINFO LOGGING]: 3 111 12:53:47.296 [planetinfo.record]: seed = 226 159 234 223 12:53:47.296 [planetinfo.record]: coordinates = 159 203 12:53:47.296 [planetinfo.record]: government = 4; 12:53:47.296 [planetinfo.record]: economy = 3; 12:53:47.296 [planetinfo.record]: techlevel = 9; 12:53:47.296 [planetinfo.record]: population = 44; 12:53:47.296 [planetinfo.record]: productivity = 19712; 12:53:47.296 [planetinfo.record]: name = "Onmaar"; 12:53:47.296 [planetinfo.record]: inhabitant = "Fat Insect"; 12:53:47.296 [planetinfo.record]: inhabitants = "Fat Insects"; 12:53:47.296 [planetinfo.record]: description = "The world Onmaar is reasonably famous for the Onmaarian evil talking treeoid."; 12:53:47.302 [planetinfo.record]: air_color = 0.593912, 0.937213, 0.910054, 1; 12:53:47.302 [planetinfo.record]: cloud_alpha = 1.000000; 12:53:47.302 [planetinfo.record]: cloud_color = 0.814453, 0.722191, 0.407227, 1; 12:53:47.303 [planetinfo.record]: cloud_fraction = 0.390000; 12:53:47.303 [planetinfo.record]: land_color = 0.437617, 0.66, 0.193359, 1; 12:53:47.303 [planetinfo.record]: land_fraction = 0.550000; 12:53:47.303 [planetinfo.record]: polar_cloud_color = 0.866504, 0.805155, 0.595721, 1; 12:53:47.303 [planetinfo.record]: polar_land_color = 0.855323, 0.934, 0.768908, 1; 12:53:47.303 [planetinfo.record]: polar_sea_color = 0.945703, 0.92155, 0.898141, 1; 12:53:47.303 [planetinfo.record]: sea_color = 0.542969, 0.4875, 0.433739, 1; 12:53:47.303 [planetinfo.record]: rotation_speed = 0.001376 12:53:47.303 [planetinfo.record]: planet zpos = 749650.000000 12:53:47.303 [planetinfo.record]: sun_radius = 215831.557465 12:53:47.303 [planetinfo.record]: sun_vector = 0.183 0.344 -0.921 12:53:47.303 [planetinfo.record]: sun_distance = 1431150 12:53:47.303 [planetinfo.record]: corona_flare = 0.039784 12:53:47.303 [planetinfo.record]: corona_hues = 0.925262 12:53:47.303 [planetinfo.record]: sun_color = 0.689481, 0.558874, 0.180837, 1 12:53:47.304 [planetinfo.record]: corona_shimmer = 0.380197 12:53:47.304 [planetinfo.record]: station_vector = 0.682 -0.708 0.185 12:53:47.304 [planetinfo.record]: station = coriolis 12:53:52.390 [PLANETINFO OVER]: Done 12:53:52.391 [PLANETINFO LOGGING]: 3 112 12:53:53.397 [planetinfo.record]: seed = 194 245 154 11 12:53:53.397 [planetinfo.record]: coordinates = 245 202 12:53:53.397 [planetinfo.record]: government = 0; 12:53:53.397 [planetinfo.record]: economy = 2; 12:53:53.397 [planetinfo.record]: techlevel = 6; 12:53:53.397 [planetinfo.record]: population = 27; 12:53:53.397 [planetinfo.record]: productivity = 6912; 12:53:53.397 [planetinfo.record]: name = "Maindice"; 12:53:53.397 [planetinfo.record]: inhabitant = "Small Green Lizard"; 12:53:53.397 [planetinfo.record]: inhabitants = "Small Green Lizards"; 12:53:53.397 [planetinfo.record]: description = "Maindice is a revolting dump."; 12:53:53.408 [planetinfo.record]: air_color = 0.578683, 0.951002, 0.962578, 1; 12:53:53.408 [planetinfo.record]: cloud_alpha = 1.000000; 12:53:53.408 [planetinfo.record]: cloud_color = 0.836565, 0.890625, 0.358337, 1; 12:53:53.408 [planetinfo.record]: cloud_fraction = 0.400000; 12:53:53.408 [planetinfo.record]: land_color = 0.633353, 0.172734, 0.66, 1; 12:53:53.408 [planetinfo.record]: land_fraction = 0.250000; 12:53:53.409 [planetinfo.record]: polar_cloud_color = 0.866608, 0.900781, 0.564308, 1; 12:53:53.409 [planetinfo.record]: polar_land_color = 0.924573, 0.761611, 0.934, 1; 12:53:53.409 [planetinfo.record]: polar_sea_color = 0.934961, 0.898254, 0.878352, 1; 12:53:53.409 [planetinfo.record]: sea_color = 0.650391, 0.548251, 0.492874, 1; 12:53:53.409 [planetinfo.record]: rotation_speed = 0.000667 12:53:53.409 [planetinfo.record]: planet zpos = 881550.000000 12:53:53.409 [planetinfo.record]: sun_radius = 140727.498322 12:53:53.409 [planetinfo.record]: sun_vector = 0.380 -0.187 -0.906 12:53:53.409 [planetinfo.record]: sun_distance = 999090 12:53:53.409 [planetinfo.record]: corona_flare = 0.099074 12:53:53.409 [planetinfo.record]: corona_hues = 0.525673 12:53:53.409 [planetinfo.record]: sun_color = 0.333594, 0.45997, 0.707361, 1 12:53:53.413 [planetinfo.record]: corona_shimmer = 0.560022 12:53:53.413 [planetinfo.record]: station_vector = 0.644 -0.765 0.015 12:53:53.413 [planetinfo.record]: station = coriolis 12:53:58.350 [PLANETINFO OVER]: Done 12:53:58.352 [PLANETINFO LOGGING]: 3 113 12:53:59.356 [planetinfo.record]: seed = 82 165 10 63 12:53:59.356 [planetinfo.record]: coordinates = 165 205 12:53:59.356 [planetinfo.record]: government = 2; 12:53:59.356 [planetinfo.record]: economy = 5; 12:53:59.356 [planetinfo.record]: techlevel = 4; 12:53:59.356 [planetinfo.record]: population = 24; 12:53:59.357 [planetinfo.record]: productivity = 5760; 12:53:59.357 [planetinfo.record]: name = "Onatve"; 12:53:59.357 [planetinfo.record]: inhabitant = "Human Colonial"; 12:53:59.357 [planetinfo.record]: inhabitants = "Human Colonials"; 12:53:59.357 [planetinfo.record]: description = "This world is ravaged by unpredictable solar activity."; 12:53:59.372 [planetinfo.record]: air_color = 0.521162, 0.594576, 0.853963, 1; 12:53:59.372 [planetinfo.record]: cloud_alpha = 1.000000; 12:53:59.372 [planetinfo.record]: cloud_color = 0.242538, 0.45128, 0.564453, 1; 12:53:59.372 [planetinfo.record]: cloud_fraction = 0.570000; 12:53:59.372 [planetinfo.record]: land_color = 0.621328, 0.623745, 0.66, 1; 12:53:59.372 [planetinfo.record]: land_fraction = 0.600000; 12:53:59.372 [planetinfo.record]: polar_cloud_color = 0.485243, 0.659518, 0.754004, 1; 12:53:59.372 [planetinfo.record]: polar_land_color = 0.920318, 0.921173, 0.934, 1; 12:53:59.372 [planetinfo.record]: polar_sea_color = 0.936694, 0.937109, 0.883939, 1; 12:53:59.372 [planetinfo.record]: sea_color = 0.627791, 0.628906, 0.486174, 1; 12:53:59.372 [planetinfo.record]: rotation_speed = 0.004428 12:53:59.372 [planetinfo.record]: planet zpos = 954940.000000 12:53:59.373 [planetinfo.record]: sun_radius = 159906.737976 12:53:59.373 [planetinfo.record]: sun_vector = 0.017 -0.683 -0.730 12:53:59.373 [planetinfo.record]: sun_distance = 1091360 12:53:59.373 [planetinfo.record]: corona_flare = 0.025615 12:53:59.373 [planetinfo.record]: corona_hues = 0.904907 12:53:59.373 [planetinfo.record]: sun_color = 0.666043, 0.655821, 0.537067, 1 12:53:59.373 [planetinfo.record]: corona_shimmer = 0.334183 12:53:59.373 [planetinfo.record]: station_vector = -0.967 -0.230 0.111 12:53:59.373 [planetinfo.record]: station = coriolis 12:54:03.783 [PLANETINFO OVER]: Done 12:54:03.784 [PLANETINFO LOGGING]: 3 114 12:54:04.790 [planetinfo.record]: seed = 50 135 90 207 12:54:04.790 [planetinfo.record]: coordinates = 135 150 12:54:04.790 [planetinfo.record]: government = 6; 12:54:04.790 [planetinfo.record]: economy = 6; 12:54:04.790 [planetinfo.record]: techlevel = 7; 12:54:04.790 [planetinfo.record]: population = 41; 12:54:04.790 [planetinfo.record]: productivity = 13120; 12:54:04.790 [planetinfo.record]: name = "Aingeon"; 12:54:04.790 [planetinfo.record]: inhabitant = "Human Colonial"; 12:54:04.790 [planetinfo.record]: inhabitants = "Human Colonials"; 12:54:04.790 [planetinfo.record]: description = "The planet Aingeon is a boring planet."; 12:54:04.804 [planetinfo.record]: air_color = 0.684545, 0.547808, 0.907295, 1; 12:54:04.804 [planetinfo.record]: cloud_alpha = 1.000000; 12:54:04.804 [planetinfo.record]: cloud_color = 0.724609, 0.297203, 0.657827, 1; 12:54:04.804 [planetinfo.record]: cloud_fraction = 0.370000; 12:54:04.804 [planetinfo.record]: land_color = 0.510469, 0.66, 0.652991, 1; 12:54:04.804 [planetinfo.record]: land_fraction = 0.620000; 12:54:04.804 [planetinfo.record]: polar_cloud_color = 0.826074, 0.52154, 0.778491, 1; 12:54:04.804 [planetinfo.record]: polar_land_color = 0.881098, 0.934, 0.93152, 1; 12:54:04.804 [planetinfo.record]: polar_sea_color = 0.930273, 0.909322, 0.864864, 1; 12:54:04.804 [planetinfo.record]: sea_color = 0.697266, 0.63445, 0.50116, 1; 12:54:04.804 [planetinfo.record]: rotation_speed = 0.002615 12:54:04.804 [planetinfo.record]: planet zpos = 679100.000000 12:54:04.804 [planetinfo.record]: sun_radius = 161730.790253 12:54:04.804 [planetinfo.record]: sun_vector = 0.512 0.711 -0.481 12:54:04.804 [planetinfo.record]: sun_distance = 1086560 12:54:04.804 [planetinfo.record]: corona_flare = 0.040720 12:54:04.804 [planetinfo.record]: corona_hues = 0.905602 12:54:04.804 [planetinfo.record]: sun_color = 0.667276, 0.667659, 0.67515, 1 12:54:04.804 [planetinfo.record]: corona_shimmer = 1.509768 12:54:04.805 [planetinfo.record]: station_vector = -0.393 -0.090 0.915 12:54:04.805 [planetinfo.record]: station = coriolis 12:54:09.502 [PLANETINFO OVER]: Done 12:54:09.503 [PLANETINFO LOGGING]: 3 115 12:54:10.520 [planetinfo.record]: seed = 130 255 170 47 12:54:10.520 [planetinfo.record]: coordinates = 255 67 12:54:10.520 [planetinfo.record]: government = 0; 12:54:10.520 [planetinfo.record]: economy = 3; 12:54:10.520 [planetinfo.record]: techlevel = 7; 12:54:10.520 [planetinfo.record]: population = 32; 12:54:10.520 [planetinfo.record]: productivity = 7168; 12:54:10.520 [planetinfo.record]: name = "Aenlege"; 12:54:10.520 [planetinfo.record]: inhabitant = "Red Fat Insect"; 12:54:10.520 [planetinfo.record]: inhabitants = "Red Fat Insects"; 12:54:10.520 [planetinfo.record]: description = "Aenlege is mildly well known for its hoopy night life."; 12:54:10.541 [planetinfo.record]: air_color = 0.504935, 0.648347, 0.843557, 1; 12:54:10.541 [planetinfo.record]: cloud_alpha = 1.000000; 12:54:10.541 [planetinfo.record]: cloud_color = 0.210365, 0.533203, 0.449971, 1; 12:54:10.541 [planetinfo.record]: cloud_fraction = 0.420000; 12:54:10.541 [planetinfo.record]: land_color = 0.30336, 0.324623, 0.550781, 1; 12:54:10.541 [planetinfo.record]: land_fraction = 0.700000; 12:54:10.541 [planetinfo.record]: polar_cloud_color = 0.459934, 0.739941, 0.667752, 1; 12:54:10.541 [planetinfo.record]: polar_land_color = 0.838803, 0.847922, 0.944922, 1; 12:54:10.541 [planetinfo.record]: polar_sea_color = 0.947656, 0.912283, 0.899996, 1; 12:54:10.541 [planetinfo.record]: sea_color = 0.523438, 0.445284, 0.418137, 1; 12:54:10.541 [planetinfo.record]: rotation_speed = 0.000060 12:54:10.541 [planetinfo.record]: planet zpos = 829320.000000 12:54:10.541 [planetinfo.record]: sun_radius = 180446.741486 12:54:10.541 [planetinfo.record]: sun_vector = 0.455 -0.781 -0.428 12:54:10.541 [planetinfo.record]: sun_distance = 1451310 12:54:10.541 [planetinfo.record]: corona_flare = 0.053123 12:54:10.541 [planetinfo.record]: corona_hues = 0.573837 12:54:10.541 [planetinfo.record]: sun_color = 0.806146, 0.344075, 0.261226, 1 12:54:10.541 [planetinfo.record]: corona_shimmer = 0.322431 12:54:10.542 [planetinfo.record]: station_vector = -0.600 -0.773 0.207 12:54:10.542 [planetinfo.record]: station = coriolis 12:54:15.744 [PLANETINFO OVER]: Done 12:54:15.744 [PLANETINFO LOGGING]: 3 116 12:54:16.748 [planetinfo.record]: seed = 226 67 26 88 12:54:16.748 [planetinfo.record]: coordinates = 67 161 12:54:16.748 [planetinfo.record]: government = 4; 12:54:16.748 [planetinfo.record]: economy = 1; 12:54:16.748 [planetinfo.record]: techlevel = 11; 12:54:16.748 [planetinfo.record]: population = 50; 12:54:16.748 [planetinfo.record]: productivity = 28800; 12:54:16.748 [planetinfo.record]: name = "Edisor"; 12:54:16.748 [planetinfo.record]: inhabitant = "Human Colonial"; 12:54:16.748 [planetinfo.record]: inhabitants = "Human Colonials"; 12:54:16.748 [planetinfo.record]: description = "This planet is a dull world."; 12:54:16.764 [planetinfo.record]: air_color = 0.767825, 0.653324, 0.839654, 1; 12:54:16.764 [planetinfo.record]: cloud_alpha = 1.000000; 12:54:16.764 [planetinfo.record]: cloud_color = 0.521484, 0.486855, 0.491724, 1; 12:54:16.764 [planetinfo.record]: cloud_fraction = 0.540000; 12:54:16.764 [planetinfo.record]: land_color = 0.66, 0.356829, 0.113438, 1; 12:54:16.764 [planetinfo.record]: land_fraction = 0.350000; 12:54:16.764 [planetinfo.record]: polar_cloud_color = 0.734668, 0.704176, 0.708464, 1; 12:54:16.764 [planetinfo.record]: polar_land_color = 0.934, 0.826742, 0.740633, 1; 12:54:16.764 [planetinfo.record]: polar_sea_color = 0.880197, 0.929102, 0.894715, 1; 12:54:16.764 [planetinfo.record]: sea_color = 0.55971, 0.708984, 0.604026, 1; 12:54:16.764 [planetinfo.record]: rotation_speed = 0.001925 12:54:16.764 [planetinfo.record]: planet zpos = 591720.000000 12:54:16.764 [planetinfo.record]: sun_radius = 163948.827820 12:54:16.764 [planetinfo.record]: sun_vector = 0.874 0.486 -0.008 12:54:16.764 [planetinfo.record]: sun_distance = 1134130 12:54:16.764 [planetinfo.record]: corona_flare = 0.049712 12:54:16.764 [planetinfo.record]: corona_hues = 0.765121 12:54:16.764 [planetinfo.record]: sun_color = 0.728082, 0.491434, 0.217683, 1 12:54:16.764 [planetinfo.record]: corona_shimmer = 0.381769 12:54:16.764 [planetinfo.record]: station_vector = 0.162 -0.856 0.491 12:54:16.764 [planetinfo.record]: station = icosahedron 12:54:21.713 [PLANETINFO OVER]: Done 12:54:21.714 [PLANETINFO LOGGING]: 3 117 12:54:22.717 [planetinfo.record]: seed = 114 111 202 134 12:54:22.717 [planetinfo.record]: coordinates = 111 217 12:54:22.717 [planetinfo.record]: government = 6; 12:54:22.717 [planetinfo.record]: economy = 1; 12:54:22.717 [planetinfo.record]: techlevel = 12; 12:54:22.718 [planetinfo.record]: population = 56; 12:54:22.718 [planetinfo.record]: productivity = 40320; 12:54:22.718 [planetinfo.record]: name = "Biabi"; 12:54:22.718 [planetinfo.record]: inhabitant = "Fierce Black Rodent"; 12:54:22.718 [planetinfo.record]: inhabitants = "Fierce Black Rodents"; 12:54:22.718 [planetinfo.record]: description = "This world is plagued by frequent earthquakes."; 12:54:22.720 [planetinfo.record]: air_color = 0.597196, 0.940465, 0.926704, 1; 12:54:22.720 [planetinfo.record]: cloud_alpha = 1.000000; 12:54:22.720 [planetinfo.record]: cloud_color = 0.824219, 0.779496, 0.415329, 1; 12:54:22.720 [planetinfo.record]: cloud_fraction = 0.200000; 12:54:22.720 [planetinfo.record]: land_color = 0.607269, 0.66, 0.353203, 1; 12:54:22.720 [planetinfo.record]: land_fraction = 0.300000; 12:54:22.720 [planetinfo.record]: polar_cloud_color = 0.870898, 0.841364, 0.600869, 1; 12:54:22.720 [planetinfo.record]: polar_land_color = 0.915345, 0.934, 0.825459, 1; 12:54:22.720 [planetinfo.record]: polar_sea_color = 0.930469, 0.848526, 0.842329, 1; 12:54:22.720 [planetinfo.record]: sea_color = 0.695312, 0.450379, 0.431854, 1; 12:54:22.720 [planetinfo.record]: rotation_speed = 0.003203 12:54:22.720 [planetinfo.record]: planet zpos = 535560.000000 12:54:22.721 [planetinfo.record]: sun_radius = 142806.329803 12:54:22.721 [planetinfo.record]: sun_vector = 0.778 0.427 0.460 12:54:22.721 [planetinfo.record]: sun_distance = 803340 12:54:22.721 [planetinfo.record]: corona_flare = 0.063824 12:54:22.721 [planetinfo.record]: corona_hues = 0.952660 12:54:22.721 [planetinfo.record]: sun_color = 0.484144, 0.590205, 0.697537, 1 12:54:22.721 [planetinfo.record]: corona_shimmer = 0.328554 12:54:22.721 [planetinfo.record]: station_vector = -0.757 -0.007 0.653 12:54:22.721 [planetinfo.record]: station = dodecahedron 12:54:28.006 [PLANETINFO OVER]: Done 12:54:28.007 [PLANETINFO LOGGING]: 3 118 12:54:29.012 [planetinfo.record]: seed = 210 28 218 178 12:54:29.012 [planetinfo.record]: coordinates = 28 198 12:54:29.012 [planetinfo.record]: government = 2; 12:54:29.012 [planetinfo.record]: economy = 6; 12:54:29.012 [planetinfo.record]: techlevel = 2; 12:54:29.013 [planetinfo.record]: population = 17; 12:54:29.013 [planetinfo.record]: productivity = 3264; 12:54:29.013 [planetinfo.record]: name = "Enlace"; 12:54:29.013 [planetinfo.record]: inhabitant = "Harmless Horned Bird"; 12:54:29.013 [planetinfo.record]: inhabitants = "Harmless Horned Birds"; 12:54:29.013 [planetinfo.record]: description = "This world is a tedious little planet."; 12:54:29.030 [planetinfo.record]: air_color = 0.768242, 0.556816, 0.924205, 1; 12:54:29.030 [planetinfo.record]: cloud_alpha = 1.000000; 12:54:29.030 [planetinfo.record]: cloud_color = 0.775391, 0.315002, 0.433696, 1; 12:54:29.030 [planetinfo.record]: cloud_fraction = 0.360000; 12:54:29.033 [planetinfo.record]: land_color = 0.128181, 0.619141, 0.458045, 1; 12:54:29.033 [planetinfo.record]: land_fraction = 0.390000; 12:54:29.033 [planetinfo.record]: polar_cloud_color = 0.848926, 0.533895, 0.615114, 1; 12:54:29.033 [planetinfo.record]: polar_land_color = 0.752118, 0.938086, 0.877065, 1; 12:54:29.033 [planetinfo.record]: polar_sea_color = 0.93418, 0.88307, 0.869407, 1; 12:54:29.033 [planetinfo.record]: sea_color = 0.658203, 0.514161, 0.475655, 1; 12:54:29.033 [planetinfo.record]: rotation_speed = 0.003836 12:54:29.033 [planetinfo.record]: planet zpos = 369160.000000 12:54:29.033 [planetinfo.record]: sun_radius = 61900.317993 12:54:29.033 [planetinfo.record]: sun_vector = 0.288 0.878 0.382 12:54:29.033 [planetinfo.record]: sun_distance = 637640 12:54:29.033 [planetinfo.record]: corona_flare = 0.000806 12:54:29.033 [planetinfo.record]: corona_hues = 0.971413 12:54:29.033 [planetinfo.record]: sun_color = 0.821121, 0.690751, 0.596872, 1 12:54:29.034 [planetinfo.record]: corona_shimmer = 0.520226 12:54:29.034 [planetinfo.record]: station_vector = -0.711 -0.701 0.045 12:54:29.034 [planetinfo.record]: station = coriolis 12:54:34.711 [PLANETINFO OVER]: Done 12:54:34.712 [PLANETINFO LOGGING]: 3 119 12:54:35.714 [planetinfo.record]: seed = 34 174 106 169 12:54:35.714 [planetinfo.record]: coordinates = 174 101 12:54:35.715 [planetinfo.record]: government = 4; 12:54:35.715 [planetinfo.record]: economy = 5; 12:54:35.715 [planetinfo.record]: techlevel = 6; 12:54:35.715 [planetinfo.record]: population = 34; 12:54:35.715 [planetinfo.record]: productivity = 10880; 12:54:35.715 [planetinfo.record]: name = "Esisraan"; 12:54:35.715 [planetinfo.record]: inhabitant = "Human Colonial"; 12:54:35.715 [planetinfo.record]: inhabitants = "Human Colonials"; 12:54:35.715 [planetinfo.record]: description = "The planet Esisraan is mildly notable for vicious Cein gargle blasters."; 12:54:35.748 [planetinfo.record]: air_color = 0.534174, 0.557908, 0.862418, 1; 12:54:35.748 [planetinfo.record]: cloud_alpha = 1.000000; 12:54:35.748 [planetinfo.record]: cloud_color = 0.269577, 0.334631, 0.589844, 1; 12:54:35.748 [planetinfo.record]: cloud_fraction = 0.350000; 12:54:35.748 [planetinfo.record]: land_color = 0.66, 0.490549, 0.198516, 1; 12:54:35.748 [planetinfo.record]: land_fraction = 0.630000; 12:54:35.748 [planetinfo.record]: polar_cloud_color = 0.505677, 0.558439, 0.76543, 1; 12:54:35.748 [planetinfo.record]: polar_land_color = 0.934, 0.87405, 0.770732, 1; 12:54:35.748 [planetinfo.record]: polar_sea_color = 0.929241, 0.887868, 0.948047, 1; 12:54:35.748 [planetinfo.record]: sea_color = 0.478309, 0.387619, 0.519531, 1; 12:54:35.748 [planetinfo.record]: rotation_speed = 0.003854 12:54:35.748 [planetinfo.record]: planet zpos = 582340.000000 12:54:35.748 [planetinfo.record]: sun_radius = 156038.743591 12:54:35.748 [planetinfo.record]: sun_vector = -0.882 -0.465 -0.073 12:54:35.748 [planetinfo.record]: sun_distance = 1058800 12:54:35.748 [planetinfo.record]: corona_flare = 0.025165 12:54:35.748 [planetinfo.record]: corona_hues = 0.534309 12:54:35.748 [planetinfo.record]: sun_color = 0.669208, 0.242358, 0.184457, 1 12:54:35.748 [planetinfo.record]: corona_shimmer = 0.355332 12:54:35.748 [planetinfo.record]: station_vector = -0.550 -0.199 0.811 12:54:35.748 [planetinfo.record]: station = coriolis 12:54:40.337 [PLANETINFO OVER]: Done 12:54:40.338 [PLANETINFO LOGGING]: 3 120 12:54:41.341 [planetinfo.record]: seed = 2 123 154 76 12:54:41.341 [planetinfo.record]: coordinates = 123 20 12:54:41.341 [planetinfo.record]: government = 0; 12:54:41.341 [planetinfo.record]: economy = 6; 12:54:41.341 [planetinfo.record]: techlevel = 4; 12:54:41.341 [planetinfo.record]: population = 23; 12:54:41.341 [planetinfo.record]: productivity = 2944; 12:54:41.341 [planetinfo.record]: name = "Intege"; 12:54:41.341 [planetinfo.record]: inhabitant = "Yellow Insect"; 12:54:41.341 [planetinfo.record]: inhabitants = "Yellow Insects"; 12:54:41.341 [planetinfo.record]: description = "This planet is very notable for the Integeian tree ant and its inhabitants’ ancient loathing of sit coms."; 12:54:41.363 [planetinfo.record]: air_color = 0.794153, 0.587935, 0.907295, 1; 12:54:41.363 [planetinfo.record]: cloud_alpha = 1.000000; 12:54:41.363 [planetinfo.record]: cloud_color = 0.724609, 0.39344, 0.408964, 1; 12:54:41.363 [planetinfo.record]: cloud_fraction = 0.570000; 12:54:41.363 [planetinfo.record]: land_color = 0.190298, 0.0928125, 0.66, 1; 12:54:41.363 [planetinfo.record]: land_fraction = 0.310000; 12:54:41.363 [planetinfo.record]: polar_cloud_color = 0.826074, 0.590111, 0.601171, 1; 12:54:41.363 [planetinfo.record]: polar_land_color = 0.767825, 0.733336, 0.934, 1; 12:54:41.364 [planetinfo.record]: polar_sea_color = 0.936523, 0.901362, 0.875704, 1; 12:54:41.364 [planetinfo.record]: sea_color = 0.634766, 0.539438, 0.469875, 1; 12:54:41.364 [planetinfo.record]: rotation_speed = 0.003178 12:54:41.364 [planetinfo.record]: planet zpos = 781430.000000 12:54:41.364 [planetinfo.record]: sun_radius = 130582.591248 12:54:41.364 [planetinfo.record]: sun_vector = 0.169 -0.700 -0.694 12:54:41.364 [planetinfo.record]: sun_distance = 1081980 12:54:41.364 [planetinfo.record]: corona_flare = 0.024292 12:54:41.364 [planetinfo.record]: corona_hues = 0.767059 12:54:41.364 [planetinfo.record]: sun_color = 0.593179, 0.678776, 0.690033, 1 12:54:41.364 [planetinfo.record]: corona_shimmer = 0.429964 12:54:41.364 [planetinfo.record]: station_vector = 0.996 -0.084 0.029 12:54:41.364 [planetinfo.record]: station = coriolis 12:54:46.508 [PLANETINFO OVER]: Done 12:54:46.510 [PLANETINFO LOGGING]: 3 121 12:54:47.513 [planetinfo.record]: seed = 146 204 138 76 12:54:47.513 [planetinfo.record]: coordinates = 204 163 12:54:47.513 [planetinfo.record]: government = 2; 12:54:47.513 [planetinfo.record]: economy = 3; 12:54:47.513 [planetinfo.record]: techlevel = 5; 12:54:47.513 [planetinfo.record]: population = 26; 12:54:47.513 [planetinfo.record]: productivity = 8736; 12:54:47.513 [planetinfo.record]: name = "Inteveon"; 12:54:47.513 [planetinfo.record]: inhabitant = "Yellow Insect"; 12:54:47.513 [planetinfo.record]: inhabitants = "Yellow Insects"; 12:54:47.513 [planetinfo.record]: description = "Inteveon is a revolting dump."; 12:54:47.540 [planetinfo.record]: air_color = 0.620141, 0.897539, 0.838789, 1; 12:54:47.540 [planetinfo.record]: cloud_alpha = 1.000000; 12:54:47.540 [planetinfo.record]: cloud_color = 0.695312, 0.552719, 0.467163, 1; 12:54:47.540 [planetinfo.record]: cloud_fraction = 0.440000; 12:54:47.540 [planetinfo.record]: land_color = 0.66, 0.412621, 0.304219, 1; 12:54:47.540 [planetinfo.record]: land_fraction = 0.480000; 12:54:47.540 [planetinfo.record]: polar_cloud_color = 0.812891, 0.708699, 0.646185, 1; 12:54:47.540 [planetinfo.record]: polar_land_color = 0.934, 0.84648, 0.808129, 1; 12:54:47.540 [planetinfo.record]: polar_sea_color = 0.835568, 0.878895, 0.946484, 1; 12:54:47.540 [planetinfo.record]: sea_color = 0.284302, 0.382292, 0.535156, 1; 12:54:47.540 [planetinfo.record]: rotation_speed = 0.001940 12:54:47.540 [planetinfo.record]: planet zpos = 731040.000000 12:54:47.540 [planetinfo.record]: sun_radius = 125500.628662 12:54:47.540 [planetinfo.record]: sun_vector = 0.037 0.760 -0.648 12:54:47.540 [planetinfo.record]: sun_distance = 1157480 12:54:47.540 [planetinfo.record]: corona_flare = 0.065996 12:54:47.540 [planetinfo.record]: corona_hues = 0.637764 12:54:47.540 [planetinfo.record]: sun_color = 0.736771, 0.473668, 0.441367, 1 12:54:47.540 [planetinfo.record]: corona_shimmer = 0.429361 12:54:47.541 [planetinfo.record]: station_vector = 0.453 -0.600 0.660 12:54:47.541 [planetinfo.record]: station = coriolis 12:54:51.855 [PLANETINFO OVER]: Done 12:54:51.856 [PLANETINFO LOGGING]: 3 122 12:54:52.875 [planetinfo.record]: seed = 114 223 90 114 12:54:52.875 [planetinfo.record]: coordinates = 223 214 12:54:52.875 [planetinfo.record]: government = 6; 12:54:52.875 [planetinfo.record]: economy = 6; 12:54:52.875 [planetinfo.record]: techlevel = 7; 12:54:52.875 [planetinfo.record]: population = 41; 12:54:52.875 [planetinfo.record]: productivity = 13120; 12:54:52.875 [planetinfo.record]: name = "Ensoor"; 12:54:52.875 [planetinfo.record]: inhabitant = "Human Colonial"; 12:54:52.875 [planetinfo.record]: inhabitants = "Human Colonials"; 12:54:52.875 [planetinfo.record]: description = "The planet Ensoor is most famous for the Ensoorian spotted leopard."; 12:54:52.904 [planetinfo.record]: air_color = 0.713101, 0.80086, 0.924205, 1; 12:54:52.904 [planetinfo.record]: cloud_alpha = 1.000000; 12:54:52.904 [planetinfo.record]: cloud_color = 0.708755, 0.775391, 0.759773, 1; 12:54:52.904 [planetinfo.record]: cloud_fraction = 0.540000; 12:54:52.904 [planetinfo.record]: land_color = 0.66, 0.546724, 0.464063, 1; 12:54:52.904 [planetinfo.record]: land_fraction = 0.580000; 12:54:52.904 [planetinfo.record]: polar_cloud_color = 0.803329, 0.848926, 0.838239, 1; 12:54:52.904 [planetinfo.record]: polar_land_color = 0.934, 0.893924, 0.86468, 1; 12:54:52.904 [planetinfo.record]: polar_sea_color = 0.866615, 0.866236, 0.914648, 1; 12:54:52.904 [planetinfo.record]: sea_color = 0.674222, 0.67281, 0.853516, 1; 12:54:52.904 [planetinfo.record]: rotation_speed = 0.000086 12:54:52.904 [planetinfo.record]: planet zpos = 355100.000000 12:54:52.904 [planetinfo.record]: sun_radius = 76185.898743 12:54:52.904 [planetinfo.record]: sun_vector = -0.883 -0.400 0.247 12:54:52.904 [planetinfo.record]: sun_distance = 674690 12:54:52.904 [planetinfo.record]: corona_flare = 0.066072 12:54:52.904 [planetinfo.record]: corona_hues = 0.940353 12:54:52.904 [planetinfo.record]: sun_color = 0.50695, 0.539315, 0.738037, 1 12:54:52.905 [planetinfo.record]: corona_shimmer = 0.898812 12:54:52.905 [planetinfo.record]: station_vector = 0.570 0.024 0.821 12:54:52.905 [planetinfo.record]: station = coriolis 12:54:57.592 [PLANETINFO OVER]: Done 12:54:57.593 [PLANETINFO LOGGING]: 3 123 12:54:58.595 [planetinfo.record]: seed = 194 19 42 181 12:54:58.595 [planetinfo.record]: coordinates = 19 121 12:54:58.595 [planetinfo.record]: government = 0; 12:54:58.595 [planetinfo.record]: economy = 3; 12:54:58.595 [planetinfo.record]: techlevel = 7; 12:54:58.595 [planetinfo.record]: population = 32; 12:54:58.595 [planetinfo.record]: productivity = 7168; 12:54:58.595 [planetinfo.record]: name = "Laxema"; 12:54:58.595 [planetinfo.record]: inhabitant = "Human Colonial"; 12:54:58.595 [planetinfo.record]: inhabitants = "Human Colonials"; 12:54:58.595 [planetinfo.record]: description = "This planet is a tedious place."; 12:54:58.598 [planetinfo.record]: air_color = 0.605215, 0.933961, 0.923348, 1; 12:54:58.598 [planetinfo.record]: cloud_alpha = 1.000000; 12:54:58.598 [planetinfo.record]: cloud_color = 0.804688, 0.773082, 0.43692, 1; 12:54:58.598 [planetinfo.record]: cloud_fraction = 0.490000; 12:54:58.598 [planetinfo.record]: land_color = 0.411775, 0.66, 0.257812, 1; 12:54:58.598 [planetinfo.record]: land_fraction = 0.320000; 12:54:58.598 [planetinfo.record]: polar_cloud_color = 0.862109, 0.840947, 0.615853, 1; 12:54:58.598 [planetinfo.record]: polar_land_color = 0.846181, 0.934, 0.791711, 1; 12:54:58.598 [planetinfo.record]: polar_sea_color = 0.907327, 0.932031, 0.877511, 1; 12:54:58.598 [planetinfo.record]: sea_color = 0.607624, 0.679688, 0.520651, 1; 12:54:58.599 [planetinfo.record]: rotation_speed = 0.002585 12:54:58.599 [planetinfo.record]: planet zpos = 493800.000000 12:54:58.599 [planetinfo.record]: sun_radius = 99242.603302 12:54:58.599 [planetinfo.record]: sun_vector = 0.236 0.924 -0.302 12:54:58.599 [planetinfo.record]: sun_distance = 699550 12:54:58.599 [planetinfo.record]: corona_flare = 0.056744 12:54:58.599 [planetinfo.record]: corona_hues = 0.770416 12:54:58.599 [planetinfo.record]: sun_color = 0.782294, 0.759505, 0.268009, 1 12:54:58.599 [planetinfo.record]: corona_shimmer = 0.327343 12:54:58.599 [planetinfo.record]: station_vector = 0.753 0.644 0.133 12:54:58.599 [planetinfo.record]: station = coriolis 12:55:04.350 [PLANETINFO OVER]: Done 12:55:04.351 [PLANETINFO LOGGING]: 3 124 12:55:05.373 [planetinfo.record]: seed = 34 3 26 81 12:55:05.373 [planetinfo.record]: coordinates = 3 11 12:55:05.373 [planetinfo.record]: government = 4; 12:55:05.373 [planetinfo.record]: economy = 3; 12:55:05.373 [planetinfo.record]: techlevel = 9; 12:55:05.373 [planetinfo.record]: population = 44; 12:55:05.373 [planetinfo.record]: productivity = 19712; 12:55:05.374 [planetinfo.record]: name = "Atonbeza"; 12:55:05.374 [planetinfo.record]: inhabitant = "Human Colonial"; 12:55:05.374 [planetinfo.record]: inhabitants = "Human Colonials"; 12:55:05.374 [planetinfo.record]: description = "The planet Atonbeza is reasonably fabled for Zero-G hockey and its inhabitants’ ingrained shyness."; 12:55:05.376 [planetinfo.record]: air_color = 0.578239, 0.551953, 0.86567, 1; 12:55:05.376 [planetinfo.record]: cloud_alpha = 1.000000; 12:55:05.376 [planetinfo.record]: cloud_color = 0.384601, 0.306831, 0.599609, 1; 12:55:05.377 [planetinfo.record]: cloud_fraction = 0.510000; 12:55:05.377 [planetinfo.record]: land_color = 0.66, 0.144375, 0.651943, 1; 12:55:05.377 [planetinfo.record]: land_fraction = 0.600000; 12:55:05.377 [planetinfo.record]: polar_cloud_color = 0.597296, 0.534892, 0.769824, 1; 12:55:05.377 [planetinfo.record]: polar_land_color = 0.934, 0.751578, 0.93115, 1; 12:55:05.377 [planetinfo.record]: polar_sea_color = 0.9375, 0.935422, 0.884308, 1; 12:55:05.377 [planetinfo.record]: sea_color = 0.625, 0.619459, 0.483154, 1; 12:55:05.377 [planetinfo.record]: rotation_speed = 0.004412 12:55:05.377 [planetinfo.record]: planet zpos = 369000.000000 12:55:05.377 [planetinfo.record]: sun_radius = 65189.380646 12:55:05.377 [planetinfo.record]: sun_vector = -0.289 -0.759 -0.583 12:55:05.377 [planetinfo.record]: sun_distance = 676500 12:55:05.377 [planetinfo.record]: corona_flare = 0.080353 12:55:05.377 [planetinfo.record]: corona_hues = 0.720215 12:55:05.377 [planetinfo.record]: sun_color = 0.688306, 0.55964, 0.259228, 1 12:55:05.377 [planetinfo.record]: corona_shimmer = 0.413842 12:55:05.377 [planetinfo.record]: station_vector = -0.641 0.753 0.152 12:55:05.377 [planetinfo.record]: station = coriolis 12:55:10.963 [PLANETINFO OVER]: Done 12:55:10.965 [PLANETINFO LOGGING]: 3 125 12:55:11.966 [planetinfo.record]: seed = 178 100 74 120 12:55:11.966 [planetinfo.record]: coordinates = 100 179 12:55:11.966 [planetinfo.record]: government = 6; 12:55:11.966 [planetinfo.record]: economy = 3; 12:55:11.966 [planetinfo.record]: techlevel = 7; 12:55:11.966 [planetinfo.record]: population = 38; 12:55:11.966 [planetinfo.record]: productivity = 21280; 12:55:11.966 [planetinfo.record]: name = "Ederditi"; 12:55:11.966 [planetinfo.record]: inhabitant = "Human Colonial"; 12:55:11.966 [planetinfo.record]: inhabitants = "Human Colonials"; 12:55:11.966 [planetinfo.record]: description = "This world is very notable for its inhabitants’ weird shyness and its inhabitants’ ingrained shyness."; 12:55:11.992 [planetinfo.record]: air_color = 0.718415, 0.794568, 0.924855, 1; 12:55:11.992 [planetinfo.record]: cloud_alpha = 1.000000; 12:55:11.992 [planetinfo.record]: cloud_color = 0.722687, 0.777344, 0.77222, 1; 12:55:11.992 [planetinfo.record]: cloud_fraction = 0.350000; 12:55:11.995 [planetinfo.record]: land_color = 0.305341, 0.288544, 0.595703, 1; 12:55:11.995 [planetinfo.record]: land_fraction = 0.520000; 12:55:11.995 [planetinfo.record]: polar_cloud_color = 0.81246, 0.849805, 0.846304, 1; 12:55:11.995 [planetinfo.record]: polar_land_color = 0.825832, 0.819202, 0.94043, 1; 12:55:11.995 [planetinfo.record]: polar_sea_color = 0.912869, 0.93418, 0.881723, 1; 12:55:11.995 [planetinfo.record]: sea_color = 0.598144, 0.658203, 0.510365, 1; 12:55:11.995 [planetinfo.record]: rotation_speed = 0.000694 12:55:11.995 [planetinfo.record]: planet zpos = 694960.000000 12:55:11.996 [planetinfo.record]: sun_radius = 147339.967041 12:55:11.996 [planetinfo.record]: sun_vector = 0.797 0.315 0.515 12:55:11.996 [planetinfo.record]: sun_distance = 1141720 12:55:11.996 [planetinfo.record]: corona_flare = 0.070909 12:55:11.996 [planetinfo.record]: corona_hues = 0.612793 12:55:11.996 [planetinfo.record]: sun_color = 0.800708, 0.789804, 0.710046, 1 12:55:11.996 [planetinfo.record]: corona_shimmer = 0.357035 12:55:11.996 [planetinfo.record]: station_vector = 0.693 0.522 0.497 12:55:11.996 [planetinfo.record]: station = coriolis 12:55:16.133 [PLANETINFO OVER]: Done 12:55:16.134 [PLANETINFO LOGGING]: 3 126 12:55:17.151 [planetinfo.record]: seed = 18 119 218 117 12:55:17.151 [planetinfo.record]: coordinates = 119 109 12:55:17.151 [planetinfo.record]: government = 2; 12:55:17.151 [planetinfo.record]: economy = 5; 12:55:17.151 [planetinfo.record]: techlevel = 6; 12:55:17.152 [planetinfo.record]: population = 32; 12:55:17.152 [planetinfo.record]: productivity = 7680; 12:55:17.152 [planetinfo.record]: name = "Laqusoed"; 12:55:17.152 [planetinfo.record]: inhabitant = "Blue Horned Lobster"; 12:55:17.152 [planetinfo.record]: inhabitants = "Blue Horned Lobsters"; 12:55:17.152 [planetinfo.record]: description = "This world is mildly famous for its vast rain forests and the Laqusoedian tree wolf."; 12:55:17.158 [planetinfo.record]: air_color = 0.657501, 0.850711, 0.794711, 1; 12:55:17.158 [planetinfo.record]: cloud_alpha = 1.000000; 12:55:17.158 [planetinfo.record]: cloud_color = 0.554688, 0.515584, 0.509186, 1; 12:55:17.158 [planetinfo.record]: cloud_fraction = 0.330000; 12:55:17.158 [planetinfo.record]: land_color = 0.66, 0.603503, 0.234609, 1; 12:55:17.158 [planetinfo.record]: land_fraction = 0.680000; 12:55:17.159 [planetinfo.record]: polar_cloud_color = 0.749609, 0.716582, 0.711177, 1; 12:55:17.159 [planetinfo.record]: polar_land_color = 0.934, 0.914012, 0.783502, 1; 12:55:17.159 [planetinfo.record]: polar_sea_color = 0.934961, 0.902555, 0.881091, 1; 12:55:17.159 [planetinfo.record]: sea_color = 0.650391, 0.56022, 0.500496, 1; 12:55:17.159 [planetinfo.record]: rotation_speed = 0.001321 12:55:17.159 [planetinfo.record]: planet zpos = 463650.000000 12:55:17.159 [planetinfo.record]: sun_radius = 108837.762451 12:55:17.159 [planetinfo.record]: sun_vector = 0.829 -0.522 -0.202 12:55:17.159 [planetinfo.record]: sun_distance = 800850 12:55:17.159 [planetinfo.record]: corona_flare = 0.004591 12:55:17.159 [planetinfo.record]: corona_hues = 0.897804 12:55:17.159 [planetinfo.record]: sun_color = 0.355899, 0.472014, 0.828943, 1 12:55:17.159 [planetinfo.record]: corona_shimmer = 0.413560 12:55:17.160 [planetinfo.record]: station_vector = -0.654 -0.730 0.202 12:55:17.160 [planetinfo.record]: station = coriolis 12:55:22.032 [PLANETINFO OVER]: Done 12:55:22.033 [PLANETINFO LOGGING]: 3 127 12:55:23.036 [planetinfo.record]: seed = 98 24 234 186 12:55:23.036 [planetinfo.record]: coordinates = 24 71 12:55:23.036 [planetinfo.record]: government = 4; 12:55:23.036 [planetinfo.record]: economy = 7; 12:55:23.036 [planetinfo.record]: techlevel = 2; 12:55:23.036 [planetinfo.record]: population = 20; 12:55:23.036 [planetinfo.record]: productivity = 3840; 12:55:23.037 [planetinfo.record]: name = "Quanre"; 12:55:23.037 [planetinfo.record]: inhabitant = "Harmless Frog"; 12:55:23.037 [planetinfo.record]: inhabitants = "Harmless Frogs"; 12:55:23.037 [planetinfo.record]: description = "This world is a tedious little planet."; 12:55:23.057 [planetinfo.record]: air_color = 0.688585, 0.821083, 0.926807, 1; 12:55:23.057 [planetinfo.record]: cloud_alpha = 1.000000; 12:55:23.057 [planetinfo.record]: cloud_color = 0.64859, 0.783203, 0.694863, 1; 12:55:23.057 [planetinfo.record]: cloud_fraction = 0.450000; 12:55:23.057 [planetinfo.record]: land_color = 0.120605, 0.59375, 0.283249, 1; 12:55:23.057 [planetinfo.record]: land_fraction = 0.430000; 12:55:23.058 [planetinfo.record]: polar_cloud_color = 0.760871, 0.852441, 0.792348, 1; 12:55:23.058 [planetinfo.record]: polar_land_color = 0.753235, 0.940625, 0.81765, 1; 12:55:23.058 [planetinfo.record]: polar_sea_color = 0.860197, 0.921094, 0.869712, 1; 12:55:23.058 [planetinfo.record]: sea_color = 0.580392, 0.789062, 0.612997, 1; 12:55:23.058 [planetinfo.record]: rotation_speed = 0.001339 12:55:23.058 [planetinfo.record]: planet zpos = 540000.000000 12:55:23.058 [planetinfo.record]: sun_radius = 150041.656494 12:55:23.058 [planetinfo.record]: sun_vector = -0.706 -0.449 -0.547 12:55:23.058 [planetinfo.record]: sun_distance = 1026000 12:55:23.058 [planetinfo.record]: corona_flare = 0.099089 12:55:23.058 [planetinfo.record]: corona_hues = 0.698929 12:55:23.058 [planetinfo.record]: sun_color = 0.69333, 0.723281, 0.769348, 1 12:55:23.058 [planetinfo.record]: corona_shimmer = 0.471930 12:55:23.058 [planetinfo.record]: station_vector = 0.496 -0.753 0.432 12:55:23.058 [planetinfo.record]: station = coriolis 12:55:27.607 [PLANETINFO OVER]: Done 12:55:27.608 [PLANETINFO LOGGING]: 3 128 12:55:28.611 [planetinfo.record]: seed = 66 196 154 205 12:55:28.611 [planetinfo.record]: coordinates = 196 238 12:55:28.611 [planetinfo.record]: government = 0; 12:55:28.611 [planetinfo.record]: economy = 6; 12:55:28.611 [planetinfo.record]: techlevel = 1; 12:55:28.611 [planetinfo.record]: population = 11; 12:55:28.611 [planetinfo.record]: productivity = 1408; 12:55:28.611 [planetinfo.record]: name = "Dienon"; 12:55:28.611 [planetinfo.record]: inhabitant = "Horned Lobster"; 12:55:28.611 [planetinfo.record]: inhabitants = "Horned Lobsters"; 12:55:28.611 [planetinfo.record]: description = "The world Dienon is notable for its great volcanoes and its inhabitants’ eccentric love for poetry."; 12:55:28.618 [planetinfo.record]: air_color = 0.590034, 0.546864, 0.877377, 1; 12:55:28.618 [planetinfo.record]: cloud_alpha = 1.000000; 12:55:28.618 [planetinfo.record]: cloud_color = 0.434542, 0.297546, 0.634766, 1; 12:55:28.618 [planetinfo.record]: cloud_fraction = 0.330000; 12:55:28.618 [planetinfo.record]: land_color = 0.650252, 0.66, 0.348047, 1; 12:55:28.618 [planetinfo.record]: land_fraction = 0.260000; 12:55:28.618 [planetinfo.record]: polar_cloud_color = 0.63076, 0.524786, 0.785645, 1; 12:55:28.619 [planetinfo.record]: polar_land_color = 0.930551, 0.934, 0.823635, 1; 12:55:28.619 [planetinfo.record]: polar_sea_color = 0.948828, 0.890824, 0.935686, 1; 12:55:28.619 [planetinfo.record]: sea_color = 0.511719, 0.386588, 0.483369, 1; 12:55:28.619 [planetinfo.record]: rotation_speed = 0.000748 12:55:28.619 [planetinfo.record]: planet zpos = 697400.000000 12:55:28.619 [planetinfo.record]: sun_radius = 176391.229248 12:55:28.619 [planetinfo.record]: sun_vector = -0.091 0.925 -0.370 12:55:28.619 [planetinfo.record]: sun_distance = 1077800 12:55:28.619 [planetinfo.record]: corona_flare = 0.037903 12:55:28.619 [planetinfo.record]: corona_hues = 0.658257 12:55:28.619 [planetinfo.record]: sun_color = 0.821078, 0.438913, 0.24046, 1 12:55:28.619 [planetinfo.record]: corona_shimmer = 0.267699 12:55:28.619 [planetinfo.record]: station_vector = 0.344 0.437 0.831 12:55:28.620 [planetinfo.record]: station = coriolis 12:55:33.708 [PLANETINFO OVER]: Done 12:55:33.709 [PLANETINFO LOGGING]: 3 129 12:55:34.715 [planetinfo.record]: seed = 210 95 10 242 12:55:34.715 [planetinfo.record]: coordinates = 95 18 12:55:34.715 [planetinfo.record]: government = 2; 12:55:34.715 [planetinfo.record]: economy = 2; 12:55:34.715 [planetinfo.record]: techlevel = 9; 12:55:34.715 [planetinfo.record]: population = 41; 12:55:34.716 [planetinfo.record]: productivity = 15744; 12:55:34.716 [planetinfo.record]: name = "Engela"; 12:55:34.716 [planetinfo.record]: inhabitant = "Human Colonial"; 12:55:34.716 [planetinfo.record]: inhabitants = "Human Colonials"; 12:55:34.716 [planetinfo.record]: description = "The planet Engela is very famous for its fabulous monkey burgers but plagued by frequent solar activity."; 12:55:34.728 [planetinfo.record]: air_color = 0.532339, 0.589792, 0.84876, 1; 12:55:34.729 [planetinfo.record]: cloud_alpha = 1.000000; 12:55:34.729 [planetinfo.record]: cloud_color = 0.263695, 0.415172, 0.548828, 1; 12:55:34.729 [planetinfo.record]: cloud_fraction = 0.360000; 12:55:34.729 [planetinfo.record]: land_color = 0.380273, 0.66, 0.340313, 1; 12:55:34.729 [planetinfo.record]: land_fraction = 0.570000; 12:55:34.729 [planetinfo.record]: polar_cloud_color = 0.504425, 0.633279, 0.746973, 1; 12:55:34.729 [planetinfo.record]: polar_land_color = 0.835036, 0.934, 0.820898, 1; 12:55:34.729 [planetinfo.record]: polar_sea_color = 0.89605, 0.931836, 0.879784, 1; 12:55:34.729 [planetinfo.record]: sea_color = 0.576932, 0.681641, 0.529337, 1; 12:55:34.729 [planetinfo.record]: rotation_speed = 0.004500 12:55:34.729 [planetinfo.record]: planet zpos = 308070.000000 12:55:34.730 [planetinfo.record]: sun_radius = 94375.373383 12:55:34.731 [planetinfo.record]: sun_vector = 0.034 0.201 -0.979 12:55:34.731 [planetinfo.record]: sun_distance = 616140 12:55:34.731 [planetinfo.record]: corona_flare = 0.000490 12:55:34.731 [planetinfo.record]: corona_hues = 0.951157 12:55:34.731 [planetinfo.record]: sun_color = 0.492712, 0.743834, 0.838351, 1 12:55:34.731 [planetinfo.record]: corona_shimmer = 0.524666 12:55:34.731 [planetinfo.record]: station_vector = 0.716 -0.335 0.612 12:55:34.731 [planetinfo.record]: station = coriolis 12:55:39.387 [PLANETINFO OVER]: Done 12:55:39.388 [PLANETINFO LOGGING]: 3 130 12:55:40.406 [planetinfo.record]: seed = 178 11 90 37 12:55:40.406 [planetinfo.record]: coordinates = 11 181 12:55:40.406 [planetinfo.record]: government = 6; 12:55:40.407 [planetinfo.record]: economy = 5; 12:55:40.407 [planetinfo.record]: techlevel = 8; 12:55:40.407 [planetinfo.record]: population = 44; 12:55:40.407 [planetinfo.record]: productivity = 17600; 12:55:40.407 [planetinfo.record]: name = "Cebitiza"; 12:55:40.407 [planetinfo.record]: inhabitant = "Human Colonial"; 12:55:40.407 [planetinfo.record]: inhabitants = "Human Colonials"; 12:55:40.407 [planetinfo.record]: description = "Cebitiza is most famous for its vast oceans and its inhabitants’ exceptional love for tourists."; 12:55:40.432 [planetinfo.record]: air_color = 0.917257, 0.793168, 0.997699, 1; 12:55:40.432 [planetinfo.record]: cloud_alpha = 1.000000; 12:55:40.432 [planetinfo.record]: cloud_color = 0.996094, 0.98053, 0.983083, 1; 12:55:40.432 [planetinfo.record]: cloud_fraction = 0.220000; 12:55:40.432 [planetinfo.record]: land_color = 0.459858, 0.32373, 0.609375, 1; 12:55:40.432 [planetinfo.record]: land_fraction = 0.350000; 12:55:40.432 [planetinfo.record]: polar_cloud_color = 0.948242, 0.938982, 0.940501, 1; 12:55:40.432 [planetinfo.record]: polar_land_color = 0.88146, 0.829016, 0.939062, 1; 12:55:40.432 [planetinfo.record]: polar_sea_color = 0.87629, 0.928711, 0.877109, 1; 12:55:40.432 [planetinfo.record]: sea_color = 0.551933, 0.712891, 0.554448, 1; 12:55:40.432 [planetinfo.record]: rotation_speed = 0.002518 12:55:40.432 [planetinfo.record]: planet zpos = 616050.000000 12:55:40.433 [planetinfo.record]: sun_radius = 126683.052063 12:55:40.433 [planetinfo.record]: sun_vector = 0.268 -0.933 -0.242 12:55:40.433 [planetinfo.record]: sun_distance = 739260 12:55:40.433 [planetinfo.record]: corona_flare = 0.051187 12:55:40.433 [planetinfo.record]: corona_hues = 0.806511 12:55:40.433 [planetinfo.record]: sun_color = 0.735486, 0.729204, 0.726825, 1 12:55:40.433 [planetinfo.record]: corona_shimmer = 0.528618 12:55:40.433 [planetinfo.record]: station_vector = -0.698 -0.702 0.141 12:55:40.433 [planetinfo.record]: station = coriolis 12:55:45.176 [PLANETINFO OVER]: Done 12:55:45.177 [PLANETINFO LOGGING]: 3 131 12:55:46.181 [planetinfo.record]: seed = 2 36 170 34 12:55:46.181 [planetinfo.record]: coordinates = 36 23 12:55:46.181 [planetinfo.record]: government = 0; 12:55:46.181 [planetinfo.record]: economy = 7; 12:55:46.181 [planetinfo.record]: techlevel = 0; 12:55:46.181 [planetinfo.record]: population = 8; 12:55:46.181 [planetinfo.record]: productivity = 768; 12:55:46.181 [planetinfo.record]: name = "Xericebi"; 12:55:46.181 [planetinfo.record]: inhabitant = "Large Red Bony Humanoid"; 12:55:46.181 [planetinfo.record]: inhabitants = "Large Red Bony Humanoids"; 12:55:46.181 [planetinfo.record]: description = "The planet Xericebi is cursed by deadly civil war."; 12:55:46.190 [planetinfo.record]: air_color = 0.47017, 0.66481, 0.864369, 1; 12:55:46.191 [planetinfo.record]: cloud_alpha = 1.000000; 12:55:46.191 [planetinfo.record]: cloud_color = 0.137291, 0.595703, 0.380822, 1; 12:55:46.191 [planetinfo.record]: cloud_fraction = 0.230000; 12:55:46.191 [planetinfo.record]: land_color = 0.564609, 0.633171, 0.66, 1; 12:55:46.191 [planetinfo.record]: land_fraction = 0.460000; 12:55:46.191 [planetinfo.record]: polar_cloud_color = 0.398659, 0.768066, 0.594907, 1; 12:55:46.192 [planetinfo.record]: polar_land_color = 0.900252, 0.924508, 0.934, 1; 12:55:46.192 [planetinfo.record]: polar_sea_color = 0.936719, 0.87543, 0.87543, 1; 12:55:46.192 [planetinfo.record]: sea_color = 0.632812, 0.467194, 0.467194, 1; 12:55:46.192 [planetinfo.record]: rotation_speed = 0.000021 12:55:46.192 [planetinfo.record]: planet zpos = 370040.000000 12:55:46.192 [planetinfo.record]: sun_radius = 75943.059692 12:55:46.192 [planetinfo.record]: sun_vector = 0.460 -0.772 -0.439 12:55:46.192 [planetinfo.record]: sun_distance = 639160 12:55:46.192 [planetinfo.record]: corona_flare = 0.071425 12:55:46.192 [planetinfo.record]: corona_hues = 0.682632 12:55:46.192 [planetinfo.record]: sun_color = 0.249565, 0.743788, 0.798941, 1 12:55:46.193 [planetinfo.record]: corona_shimmer = 0.353679 12:55:46.193 [planetinfo.record]: station_vector = 0.447 0.329 0.832 12:55:46.193 [planetinfo.record]: station = coriolis 12:55:51.619 [PLANETINFO OVER]: Done 12:55:51.619 [PLANETINFO LOGGING]: 3 132 12:55:52.623 [planetinfo.record]: seed = 98 38 26 42 12:55:52.623 [planetinfo.record]: coordinates = 38 165 12:55:52.623 [planetinfo.record]: government = 4; 12:55:52.623 [planetinfo.record]: economy = 5; 12:55:52.623 [planetinfo.record]: techlevel = 6; 12:55:52.623 [planetinfo.record]: population = 34; 12:55:52.623 [planetinfo.record]: productivity = 10880; 12:55:52.623 [planetinfo.record]: name = "Arlabi"; 12:55:52.623 [planetinfo.record]: inhabitant = "Human Colonial"; 12:55:52.623 [planetinfo.record]: inhabitants = "Human Colonials"; 12:55:52.623 [planetinfo.record]: description = "The planet Arlabi is cursed by killer mountain monkeys."; 12:55:52.636 [planetinfo.record]: air_color = 0.719748, 0.713996, 0.983391, 1; 12:55:52.636 [planetinfo.record]: cloud_alpha = 1.000000; 12:55:52.636 [planetinfo.record]: cloud_color = 0.757485, 0.740906, 0.953125, 1; 12:55:52.636 [planetinfo.record]: cloud_fraction = 0.600000; 12:55:52.636 [planetinfo.record]: land_color = 0.306797, 0.66, 0.519271, 1; 12:55:52.636 [planetinfo.record]: land_fraction = 0.560000; 12:55:52.636 [planetinfo.record]: polar_cloud_color = 0.809738, 0.79964, 0.928906, 1; 12:55:52.636 [planetinfo.record]: polar_land_color = 0.809041, 0.934, 0.884212, 1; 12:55:52.636 [planetinfo.record]: polar_sea_color = 0.937305, 0.889629, 0.8833, 1; 12:55:52.636 [planetinfo.record]: sea_color = 0.626953, 0.499393, 0.48246, 1; 12:55:52.636 [planetinfo.record]: rotation_speed = 0.001899 12:55:52.636 [planetinfo.record]: planet zpos = 703820.000000 12:55:52.636 [planetinfo.record]: sun_radius = 155743.372192 12:55:52.636 [planetinfo.record]: sun_vector = 0.527 -0.060 -0.848 12:55:52.636 [planetinfo.record]: sun_distance = 1082800 12:55:52.636 [planetinfo.record]: corona_flare = 0.058289 12:55:52.636 [planetinfo.record]: corona_hues = 0.675095 12:55:52.636 [planetinfo.record]: sun_color = 0.661418, 0.671209, 0.688721, 1 12:55:52.636 [planetinfo.record]: corona_shimmer = 0.531361 12:55:52.636 [planetinfo.record]: station_vector = 0.124 0.612 0.781 12:55:52.637 [planetinfo.record]: station = coriolis 12:55:56.769 [PLANETINFO OVER]: Done 12:55:56.769 [PLANETINFO LOGGING]: 3 133 12:55:57.773 [planetinfo.record]: seed = 242 101 202 161 12:55:57.773 [planetinfo.record]: coordinates = 101 70 12:55:57.773 [planetinfo.record]: government = 6; 12:55:57.773 [planetinfo.record]: economy = 6; 12:55:57.773 [planetinfo.record]: techlevel = 5; 12:55:57.773 [planetinfo.record]: population = 33; 12:55:57.773 [planetinfo.record]: productivity = 10560; 12:55:57.773 [planetinfo.record]: name = "Ledila"; 12:55:57.773 [planetinfo.record]: inhabitant = "Large Harmless Bony Bird"; 12:55:57.773 [planetinfo.record]: inhabitants = "Large Harmless Bony Birds"; 12:55:57.773 [planetinfo.record]: description = "Ledila is ravaged by occasional solar activity."; 12:55:57.784 [planetinfo.record]: air_color = 0.460264, 0.735133, 0.846158, 1; 12:55:57.784 [planetinfo.record]: cloud_alpha = 1.000000; 12:55:57.784 [planetinfo.record]: cloud_color = 0.176728, 0.541016, 0.124687, 1; 12:55:57.784 [planetinfo.record]: cloud_fraction = 0.390000; 12:55:57.785 [planetinfo.record]: land_color = 0.461505, 0.66, 0.311953, 1; 12:55:57.785 [planetinfo.record]: land_fraction = 0.310000; 12:55:57.785 [planetinfo.record]: polar_cloud_color = 0.430582, 0.743457, 0.385886, 1; 12:55:57.785 [planetinfo.record]: polar_land_color = 0.863775, 0.934, 0.810865, 1; 12:55:57.785 [planetinfo.record]: polar_sea_color = 0.879962, 0.928516, 0.874473, 1; 12:55:57.785 [planetinfo.record]: sea_color = 0.565322, 0.714844, 0.548419, 1; 12:55:57.785 [planetinfo.record]: rotation_speed = 0.003214 12:55:57.785 [planetinfo.record]: planet zpos = 317300.000000 12:55:57.785 [planetinfo.record]: sun_radius = 85262.270966 12:55:57.785 [planetinfo.record]: sun_vector = -0.682 0.087 -0.726 12:55:57.785 [planetinfo.record]: sun_distance = 666330 12:55:57.785 [planetinfo.record]: corona_flare = 0.047035 12:55:57.785 [planetinfo.record]: corona_hues = 0.640572 12:55:57.785 [planetinfo.record]: sun_color = 0.437831, 0.528189, 0.696411, 1 12:55:57.785 [planetinfo.record]: corona_shimmer = 0.800958 12:55:57.785 [planetinfo.record]: station_vector = -0.033 -0.478 0.878 12:55:57.785 [planetinfo.record]: station = coriolis 12:56:02.585 [PLANETINFO OVER]: Done 12:56:02.585 [PLANETINFO LOGGING]: 3 134 12:56:03.589 [planetinfo.record]: seed = 82 69 218 232 12:56:03.589 [planetinfo.record]: coordinates = 69 85 12:56:03.589 [planetinfo.record]: government = 2; 12:56:03.589 [planetinfo.record]: economy = 5; 12:56:03.589 [planetinfo.record]: techlevel = 4; 12:56:03.589 [planetinfo.record]: population = 24; 12:56:03.589 [planetinfo.record]: productivity = 5760; 12:56:03.589 [planetinfo.record]: name = "Usgeat"; 12:56:03.589 [planetinfo.record]: inhabitant = "Small Slimy Rodent"; 12:56:03.589 [planetinfo.record]: inhabitants = "Small Slimy Rodents"; 12:56:03.589 [planetinfo.record]: description = "Usgeat is cursed by dreadful civil war."; 12:56:03.600 [planetinfo.record]: air_color = 0.570766, 0.936114, 0.976887, 1; 12:56:03.600 [planetinfo.record]: cloud_alpha = 1.000000; 12:56:03.600 [planetinfo.record]: cloud_color = 0.744413, 0.933594, 0.328217, 1; 12:56:03.600 [planetinfo.record]: cloud_fraction = 0.220000; 12:56:03.600 [planetinfo.record]: land_color = 0.541084, 0.66, 0.474375, 1; 12:56:03.600 [planetinfo.record]: land_fraction = 0.700000; 12:56:03.600 [planetinfo.record]: polar_cloud_color = 0.803586, 0.920117, 0.547218, 1; 12:56:03.600 [planetinfo.record]: polar_land_color = 0.891929, 0.934, 0.868328, 1; 12:56:03.600 [planetinfo.record]: polar_sea_color = 0.880253, 0.929102, 0.876114, 1; 12:56:03.600 [planetinfo.record]: sea_color = 0.559883, 0.708984, 0.547247, 1; 12:56:03.600 [planetinfo.record]: rotation_speed = 0.003867 12:56:03.600 [planetinfo.record]: planet zpos = 641290.000000 12:56:03.600 [planetinfo.record]: sun_radius = 112432.445831 12:56:03.600 [planetinfo.record]: sun_vector = 0.361 -0.847 -0.391 12:56:03.600 [planetinfo.record]: sun_distance = 986600 12:56:03.600 [planetinfo.record]: corona_flare = 0.064644 12:56:03.600 [planetinfo.record]: corona_hues = 0.999413 12:56:03.601 [planetinfo.record]: sun_color = 0.671054, 0.651213, 0.592799, 1 12:56:03.601 [planetinfo.record]: corona_shimmer = 0.592753 12:56:03.601 [planetinfo.record]: station_vector = 0.456 0.456 0.764 12:56:03.601 [planetinfo.record]: station = coriolis 12:56:08.687 [PLANETINFO OVER]: Done 12:56:08.687 [PLANETINFO LOGGING]: 3 135 12:56:09.692 [planetinfo.record]: seed = 162 30 106 84 12:56:09.692 [planetinfo.record]: coordinates = 30 177 12:56:09.692 [planetinfo.record]: government = 4; 12:56:09.692 [planetinfo.record]: economy = 1; 12:56:09.692 [planetinfo.record]: techlevel = 10; 12:56:09.692 [planetinfo.record]: population = 46; 12:56:09.692 [planetinfo.record]: productivity = 26496; 12:56:09.692 [planetinfo.record]: name = "Raceedat"; 12:56:09.692 [planetinfo.record]: inhabitant = "Human Colonial"; 12:56:09.692 [planetinfo.record]: inhabitants = "Human Colonials"; 12:56:09.692 [planetinfo.record]: description = "The world Raceedat is beset by evil disease."; 12:56:09.728 [planetinfo.record]: air_color = 0.467286, 0.73786, 0.839004, 1; 12:56:09.728 [planetinfo.record]: cloud_alpha = 1.000000; 12:56:09.728 [planetinfo.record]: cloud_color = 0.205257, 0.519531, 0.14003, 1; 12:56:09.728 [planetinfo.record]: cloud_fraction = 0.180000; 12:56:09.728 [planetinfo.record]: land_color = 0.322266, 0.66, 0.517518, 1; 12:56:09.728 [planetinfo.record]: land_fraction = 0.420000; 12:56:09.728 [planetinfo.record]: polar_cloud_color = 0.456362, 0.733789, 0.398783, 1; 12:56:09.728 [planetinfo.record]: polar_land_color = 0.814514, 0.934, 0.883592, 1; 12:56:09.728 [planetinfo.record]: polar_sea_color = 0.937109, 0.918935, 0.88037, 1; 12:56:09.728 [planetinfo.record]: sea_color = 0.628906, 0.580118, 0.476593, 1; 12:56:09.728 [planetinfo.record]: rotation_speed = 0.003792 12:56:09.728 [planetinfo.record]: planet zpos = 580500.000000 12:56:09.729 [planetinfo.record]: sun_radius = 100645.628357 12:56:09.729 [planetinfo.record]: sun_vector = 0.788 0.574 -0.221 12:56:09.729 [planetinfo.record]: sun_distance = 812700 12:56:09.729 [planetinfo.record]: corona_flare = 0.011275 12:56:09.729 [planetinfo.record]: corona_hues = 0.990250 12:56:09.729 [planetinfo.record]: sun_color = 0.572886, 0.767569, 0.841431, 1 12:56:09.729 [planetinfo.record]: corona_shimmer = 0.345847 12:56:09.729 [planetinfo.record]: station_vector = 0.713 -0.415 0.566 12:56:09.729 [planetinfo.record]: station = coriolis 12:56:15.016 [PLANETINFO OVER]: Done 12:56:15.016 [PLANETINFO LOGGING]: 3 136 12:56:16.020 [planetinfo.record]: seed = 130 17 154 206 12:56:16.020 [planetinfo.record]: coordinates = 17 152 12:56:16.020 [planetinfo.record]: government = 0; 12:56:16.020 [planetinfo.record]: economy = 2; 12:56:16.020 [planetinfo.record]: techlevel = 6; 12:56:16.020 [planetinfo.record]: population = 27; 12:56:16.020 [planetinfo.record]: productivity = 6912; 12:56:16.020 [planetinfo.record]: name = "Reeded"; 12:56:16.020 [planetinfo.record]: inhabitant = "Bug-Eyed Lobster"; 12:56:16.020 [planetinfo.record]: inhabitants = "Bug-Eyed Lobsters"; 12:56:16.020 [planetinfo.record]: description = "This world is a revolting little planet."; 12:56:16.029 [planetinfo.record]: air_color = 0.63231, 0.595624, 0.833801, 1; 12:56:16.030 [planetinfo.record]: cloud_alpha = 1.000000; 12:56:16.030 [planetinfo.record]: cloud_color = 0.435905, 0.373993, 0.503906, 1; 12:56:16.030 [planetinfo.record]: cloud_fraction = 0.300000; 12:56:16.030 [planetinfo.record]: land_color = 0.0876563, 0.217328, 0.66, 1; 12:56:16.030 [planetinfo.record]: land_fraction = 0.550000; 12:56:16.030 [planetinfo.record]: polar_cloud_color = 0.665461, 0.609653, 0.726758, 1; 12:56:16.030 [planetinfo.record]: polar_land_color = 0.731512, 0.777388, 0.934, 1; 12:56:16.030 [planetinfo.record]: polar_sea_color = 0.943555, 0.917913, 0.89306, 1; 12:56:16.030 [planetinfo.record]: sea_color = 0.564453, 0.503095, 0.443625, 1; 12:56:16.030 [planetinfo.record]: rotation_speed = 0.003222 12:56:16.030 [planetinfo.record]: planet zpos = 705870.000000 12:56:16.030 [planetinfo.record]: sun_radius = 105463.966827 12:56:16.030 [planetinfo.record]: sun_vector = -0.022 -0.965 0.262 12:56:16.030 [planetinfo.record]: sun_distance = 1540080 12:56:16.030 [planetinfo.record]: corona_flare = 0.053581 12:56:16.030 [planetinfo.record]: corona_hues = 0.649673 12:56:16.030 [planetinfo.record]: sun_color = 0.81619, 0.425906, 0.266984, 1 12:56:16.031 [planetinfo.record]: corona_shimmer = 0.524145 12:56:16.031 [planetinfo.record]: station_vector = -0.682 0.587 0.436 12:56:16.031 [planetinfo.record]: station = coriolis 12:56:20.574 [PLANETINFO OVER]: Done 12:56:20.574 [PLANETINFO LOGGING]: 3 137 12:56:21.581 [planetinfo.record]: seed = 18 159 138 111 12:56:21.581 [planetinfo.record]: coordinates = 159 88 12:56:21.581 [planetinfo.record]: government = 2; 12:56:21.581 [planetinfo.record]: economy = 0; 12:56:21.581 [planetinfo.record]: techlevel = 11; 12:56:21.581 [planetinfo.record]: population = 47; 12:56:21.581 [planetinfo.record]: productivity = 22560; 12:56:21.581 [planetinfo.record]: name = "Abilama"; 12:56:21.581 [planetinfo.record]: inhabitant = "Blue Lizard"; 12:56:21.581 [planetinfo.record]: inhabitants = "Blue Lizards"; 12:56:21.581 [planetinfo.record]: description = "Abilama is reasonably well known for its great dense forests but scourged by frequent civil war."; 12:56:21.599 [planetinfo.record]: air_color = 0.791758, 0.567236, 0.923555, 1; 12:56:21.600 [planetinfo.record]: cloud_alpha = 1.000000; 12:56:21.600 [planetinfo.record]: cloud_color = 0.773438, 0.3414, 0.381904, 1; 12:56:21.600 [planetinfo.record]: cloud_fraction = 0.480000; 12:56:21.600 [planetinfo.record]: land_color = 0.505859, 0.458435, 0.481777, 1; 12:56:21.600 [planetinfo.record]: land_fraction = 0.380000; 12:56:21.600 [planetinfo.record]: polar_cloud_color = 0.848047, 0.551976, 0.579732, 1; 12:56:21.601 [planetinfo.record]: polar_land_color = 0.949414, 0.927162, 0.938114, 1; 12:56:21.601 [planetinfo.record]: polar_sea_color = 0.938672, 0.918409, 0.883488, 1; 12:56:21.601 [planetinfo.record]: sea_color = 0.613281, 0.560327, 0.469064, 1; 12:56:21.601 [planetinfo.record]: rotation_speed = 0.001950 12:56:21.601 [planetinfo.record]: planet zpos = 613350.000000 12:56:21.601 [planetinfo.record]: sun_radius = 174583.420563 12:56:21.601 [planetinfo.record]: sun_vector = -0.662 0.043 0.748 12:56:21.601 [planetinfo.record]: sun_distance = 1363000 12:56:21.601 [planetinfo.record]: corona_flare = 0.065666 12:56:21.601 [planetinfo.record]: corona_hues = 0.976784 12:56:21.601 [planetinfo.record]: sun_color = 0.706204, 0.692827, 0.515787, 1 12:56:21.601 [planetinfo.record]: corona_shimmer = 0.286511 12:56:21.603 [planetinfo.record]: station_vector = -0.285 -0.956 0.072 12:56:21.603 [planetinfo.record]: station = dodecahedron 12:56:26.135 [PLANETINFO OVER]: Done 12:56:26.137 [PLANETINFO LOGGING]: 3 138 12:56:27.138 [planetinfo.record]: seed = 242 75 90 40 12:56:27.138 [planetinfo.record]: coordinates = 75 117 12:56:27.138 [planetinfo.record]: government = 6; 12:56:27.138 [planetinfo.record]: economy = 5; 12:56:27.138 [planetinfo.record]: techlevel = 8; 12:56:27.138 [planetinfo.record]: population = 44; 12:56:27.138 [planetinfo.record]: productivity = 17600; 12:56:27.138 [planetinfo.record]: name = "Usesri"; 12:56:27.138 [planetinfo.record]: inhabitant = "Human Colonial"; 12:56:27.139 [planetinfo.record]: inhabitants = "Human Colonials"; 12:56:27.139 [planetinfo.record]: description = "The world Usesri is mildly noted for its ancient mountains but ravaged by frequent earthquakes."; 12:56:27.152 [planetinfo.record]: air_color = 0.643215, 0.889884, 0.922254, 1; 12:56:27.152 [planetinfo.record]: cloud_alpha = 1.000000; 12:56:27.152 [planetinfo.record]: cloud_color = 0.68419, 0.769531, 0.532059, 1; 12:56:27.152 [planetinfo.record]: cloud_fraction = 0.410000; 12:56:27.152 [planetinfo.record]: land_color = 0.194115, 0.501953, 0.299934, 1; 12:56:27.152 [planetinfo.record]: land_fraction = 0.440000; 12:56:27.154 [planetinfo.record]: polar_cloud_color = 0.78763, 0.846289, 0.683064, 1; 12:56:27.155 [planetinfo.record]: polar_land_color = 0.80418, 0.949805, 0.854239, 1; 12:56:27.155 [planetinfo.record]: polar_sea_color = 0.943164, 0.917252, 0.892137, 1; 12:56:27.155 [planetinfo.record]: sea_color = 0.568359, 0.5059, 0.445363, 1; 12:56:27.155 [planetinfo.record]: rotation_speed = 0.000029 12:56:27.155 [planetinfo.record]: planet zpos = 493900.000000 12:56:27.155 [planetinfo.record]: sun_radius = 137910.813141 12:56:27.155 [planetinfo.record]: sun_vector = -0.123 0.842 0.525 12:56:27.155 [planetinfo.record]: sun_distance = 889020 12:56:27.155 [planetinfo.record]: corona_flare = 0.031190 12:56:27.155 [planetinfo.record]: corona_hues = 0.848419 12:56:27.155 [planetinfo.record]: sun_color = 0.763721, 0.762674, 0.714663, 1 12:56:27.155 [planetinfo.record]: corona_shimmer = 0.375586 12:56:27.155 [planetinfo.record]: station_vector = -0.401 0.916 0.025 12:56:27.156 [planetinfo.record]: station = coriolis 12:56:32.481 [PLANETINFO OVER]: Done 12:56:32.484 [PLANETINFO LOGGING]: 3 139 12:56:33.502 [planetinfo.record]: seed = 66 112 42 184 12:56:33.503 [planetinfo.record]: coordinates = 112 94 12:56:33.503 [planetinfo.record]: government = 0; 12:56:33.503 [planetinfo.record]: economy = 6; 12:56:33.503 [planetinfo.record]: techlevel = 1; 12:56:33.503 [planetinfo.record]: population = 11; 12:56:33.503 [planetinfo.record]: productivity = 1408; 12:56:33.503 [planetinfo.record]: name = "Edbire"; 12:56:33.503 [planetinfo.record]: inhabitant = "Human Colonial"; 12:56:33.503 [planetinfo.record]: inhabitants = "Human Colonials"; 12:56:33.503 [planetinfo.record]: description = "Edbire is cursed by killer edible talking treeoids."; 12:56:33.524 [planetinfo.record]: air_color = 0.62066, 0.903393, 0.856752, 1; 12:56:33.524 [planetinfo.record]: cloud_alpha = 1.000000; 12:56:33.524 [planetinfo.record]: cloud_color = 0.712891, 0.59554, 0.470619, 1; 12:56:33.524 [planetinfo.record]: cloud_fraction = 0.410000; 12:56:33.524 [planetinfo.record]: land_color = 0.513672, 0.495613, 0.49646, 1; 12:56:33.524 [planetinfo.record]: land_fraction = 0.640000; 12:56:33.524 [planetinfo.record]: polar_cloud_color = 0.820801, 0.736355, 0.646461, 1; 12:56:33.524 [planetinfo.record]: polar_land_color = 0.948633, 0.940295, 0.940686, 1; 12:56:33.524 [planetinfo.record]: polar_sea_color = 0.942187, 0.908776, 0.888729, 1; 12:56:33.524 [planetinfo.record]: sea_color = 0.578125, 0.49612, 0.446918, 1; 12:56:33.524 [planetinfo.record]: rotation_speed = 0.002601 12:56:33.524 [planetinfo.record]: planet zpos = 696640.000000 12:56:33.524 [planetinfo.record]: sun_radius = 92119.323730 12:56:33.524 [planetinfo.record]: sun_vector = -0.582 -0.704 -0.406 12:56:33.525 [planetinfo.record]: sun_distance = 995200 12:56:33.525 [planetinfo.record]: corona_flare = 0.078662 12:56:33.525 [planetinfo.record]: corona_hues = 0.540565 12:56:33.525 [planetinfo.record]: sun_color = 0.742206, 0.647941, 0.280836, 1 12:56:33.525 [planetinfo.record]: corona_shimmer = 0.342507 12:56:33.525 [planetinfo.record]: station_vector = -0.099 -0.028 0.995 12:56:33.525 [planetinfo.record]: station = coriolis 12:56:39.308 [PLANETINFO OVER]: Done 12:56:39.310 [PLANETINFO LOGGING]: 3 140 12:56:40.319 [planetinfo.record]: seed = 162 237 26 35 12:56:40.319 [planetinfo.record]: coordinates = 237 174 12:56:40.319 [planetinfo.record]: government = 4; 12:56:40.319 [planetinfo.record]: economy = 6; 12:56:40.319 [planetinfo.record]: techlevel = 4; 12:56:40.319 [planetinfo.record]: population = 27; 12:56:40.319 [planetinfo.record]: productivity = 6912; 12:56:40.319 [planetinfo.record]: name = "Geonerbe"; 12:56:40.319 [planetinfo.record]: inhabitant = "Human Colonial"; 12:56:40.319 [planetinfo.record]: inhabitants = "Human Colonials"; 12:56:40.319 [planetinfo.record]: description = "Geonerbe is an unremarkable planet."; 12:56:40.332 [planetinfo.record]: air_color = 0.602598, 0.873623, 0.974936, 1; 12:56:40.332 [planetinfo.record]: cloud_alpha = 1.000000; 12:56:40.332 [planetinfo.record]: cloud_color = 0.507581, 0.927734, 0.42038, 1; 12:56:40.332 [planetinfo.record]: cloud_fraction = 0.290000; 12:56:40.332 [planetinfo.record]: land_color = 0.66, 0.208828, 0.303997, 1; 12:56:40.332 [planetinfo.record]: land_fraction = 0.400000; 12:56:40.332 [planetinfo.record]: polar_cloud_color = 0.657787, 0.91748, 0.603889, 1; 12:56:40.332 [planetinfo.record]: polar_land_color = 0.934, 0.774381, 0.808051, 1; 12:56:40.332 [planetinfo.record]: polar_sea_color = 0.939453, 0.892833, 0.88037, 1; 12:56:40.332 [planetinfo.record]: sea_color = 0.605469, 0.485284, 0.453156, 1; 12:56:40.332 [planetinfo.record]: rotation_speed = 0.004426 12:56:40.332 [planetinfo.record]: planet zpos = 534940.000000 12:56:40.332 [planetinfo.record]: sun_radius = 95803.692322 12:56:40.332 [planetinfo.record]: sun_vector = 0.045 0.952 -0.302 12:56:40.332 [planetinfo.record]: sun_distance = 802410 12:56:40.332 [planetinfo.record]: corona_flare = 0.058220 12:56:40.333 [planetinfo.record]: corona_hues = 0.536209 12:56:40.333 [planetinfo.record]: sun_color = 0.821246, 0.585094, 0.516098, 1 12:56:40.333 [planetinfo.record]: corona_shimmer = 0.437315 12:56:40.333 [planetinfo.record]: station_vector = 0.417 -0.672 0.612 12:56:40.333 [planetinfo.record]: station = coriolis 12:56:44.957 [PLANETINFO OVER]: Done 12:56:44.958 [PLANETINFO LOGGING]: 3 141 12:56:45.963 [planetinfo.record]: seed = 50 179 74 67 12:56:45.963 [planetinfo.record]: coordinates = 179 208 12:56:45.963 [planetinfo.record]: government = 6; 12:56:45.963 [planetinfo.record]: economy = 0; 12:56:45.963 [planetinfo.record]: techlevel = 13; 12:56:45.963 [planetinfo.record]: population = 59; 12:56:45.963 [planetinfo.record]: productivity = 47200; 12:56:45.963 [planetinfo.record]: name = "Gebiisso"; 12:56:45.964 [planetinfo.record]: inhabitant = "Human Colonial"; 12:56:45.964 [planetinfo.record]: inhabitants = "Human Colonials"; 12:56:45.964 [planetinfo.record]: description = "This world is reasonably notable for its exotic cuisine but plagued by lethal disease."; 12:56:45.967 [planetinfo.record]: air_color = 0.43163, 0.65138, 0.89884, 1; 12:56:45.967 [planetinfo.record]: cloud_alpha = 1.000000; 12:56:45.967 [planetinfo.record]: cloud_color = 0.0273132, 0.699219, 0.431506, 1; 12:56:45.967 [planetinfo.record]: cloud_fraction = 0.290000; 12:56:45.968 [planetinfo.record]: land_color = 0.66, 0.152109, 0.652064, 1; 12:56:45.968 [planetinfo.record]: land_fraction = 0.300000; 12:56:45.968 [planetinfo.record]: polar_cloud_color = 0.325382, 0.814648, 0.619706, 1; 12:56:45.968 [planetinfo.record]: polar_land_color = 0.934, 0.754314, 0.931192, 1; 12:56:45.968 [planetinfo.record]: polar_sea_color = 0.947266, 0.903493, 0.897682, 1; 12:56:45.968 [planetinfo.record]: sea_color = 0.527344, 0.42987, 0.416931, 1; 12:56:45.968 [planetinfo.record]: rotation_speed = 0.000683 12:56:45.968 [planetinfo.record]: planet zpos = 526820.000000 12:56:45.968 [planetinfo.record]: sun_radius = 92443.731232 12:56:45.968 [planetinfo.record]: sun_vector = 0.107 -0.488 0.867 12:56:45.968 [planetinfo.record]: sun_distance = 752600 12:56:45.968 [planetinfo.record]: corona_flare = 0.029434 12:56:45.968 [planetinfo.record]: corona_hues = 0.800964 12:56:45.968 [planetinfo.record]: sun_color = 0.513511, 0.532004, 0.844043, 1 12:56:45.968 [planetinfo.record]: corona_shimmer = 0.702588 12:56:45.969 [planetinfo.record]: station_vector = 0.895 -0.363 0.259 12:56:45.969 [planetinfo.record]: station = dodecahedron 12:56:51.266 [PLANETINFO OVER]: Done 12:56:51.267 [PLANETINFO LOGGING]: 3 142 12:56:52.289 [planetinfo.record]: seed = 146 199 218 75 12:56:52.289 [planetinfo.record]: coordinates = 199 189 12:56:52.289 [planetinfo.record]: government = 2; 12:56:52.289 [planetinfo.record]: economy = 5; 12:56:52.289 [planetinfo.record]: techlevel = 6; 12:56:52.289 [planetinfo.record]: population = 32; 12:56:52.289 [planetinfo.record]: productivity = 7680; 12:56:52.289 [planetinfo.record]: name = "Maerza"; 12:56:52.289 [planetinfo.record]: inhabitant = "Small Yellow Horned Humanoid"; 12:56:52.289 [planetinfo.record]: inhabitants = "Small Yellow Horned Humanoids"; 12:56:52.289 [planetinfo.record]: description = "Maerza is a revolting little planet."; 12:56:52.305 [planetinfo.record]: air_color = 0.633575, 0.459366, 0.969732, 1; 12:56:52.306 [planetinfo.record]: cloud_alpha = 1.000000; 12:56:52.306 [planetinfo.record]: cloud_color = 0.912109, 0.0178146, 0.877176, 1; 12:56:52.306 [planetinfo.record]: cloud_fraction = 0.550000; 12:56:52.306 [planetinfo.record]: land_color = 0.471797, 0.66, 0.514437, 1; 12:56:52.306 [planetinfo.record]: land_fraction = 0.420000; 12:56:52.306 [planetinfo.record]: polar_cloud_color = 0.910449, 0.352532, 0.888656, 1; 12:56:52.306 [planetinfo.record]: polar_land_color = 0.867416, 0.934, 0.882501, 1; 12:56:52.306 [planetinfo.record]: polar_sea_color = 0.938086, 0.923081, 0.883212, 1; 12:56:52.306 [planetinfo.record]: sea_color = 0.619141, 0.579528, 0.474271, 1; 12:56:52.306 [planetinfo.record]: rotation_speed = 0.001314 12:56:52.306 [planetinfo.record]: planet zpos = 641410.000000 12:56:52.306 [planetinfo.record]: sun_radius = 178596.618500 12:56:52.306 [planetinfo.record]: sun_vector = -0.291 0.723 -0.627 12:56:52.306 [planetinfo.record]: sun_distance = 1049580 12:56:52.306 [planetinfo.record]: corona_flare = 0.035277 12:56:52.306 [planetinfo.record]: corona_hues = 0.778267 12:56:52.307 [planetinfo.record]: sun_color = 0.552846, 0.700646, 0.775064, 1 12:56:52.307 [planetinfo.record]: corona_shimmer = 0.428273 12:56:52.307 [planetinfo.record]: station_vector = 0.967 0.055 0.247 12:56:52.307 [planetinfo.record]: station = coriolis 12:56:57.949 [PLANETINFO OVER]: Done 12:56:57.950 [PLANETINFO LOGGING]: 3 143 12:56:58.956 [planetinfo.record]: seed = 226 0 234 181 12:56:58.956 [planetinfo.record]: coordinates = 0 228 12:56:58.956 [planetinfo.record]: government = 4; 12:56:58.956 [planetinfo.record]: economy = 4; 12:56:58.956 [planetinfo.record]: techlevel = 5; 12:56:58.956 [planetinfo.record]: population = 29; 12:56:58.956 [planetinfo.record]: productivity = 11136; 12:56:58.956 [planetinfo.record]: name = "Laanat"; 12:56:58.956 [planetinfo.record]: inhabitant = "Harmless Fat Humanoid"; 12:56:58.956 [planetinfo.record]: inhabitants = "Harmless Fat Humanoids"; 12:56:58.956 [planetinfo.record]: description = "This planet is most fabled for its inhabitants’ ingrained silliness but scourged by deadly edible arts graduates."; 12:56:58.973 [planetinfo.record]: air_color = 0.601724, 0.914393, 0.955424, 1; 12:56:58.973 [planetinfo.record]: cloud_alpha = 1.000000; 12:56:58.974 [planetinfo.record]: cloud_color = 0.709307, 0.869141, 0.424385, 1; 12:56:58.974 [planetinfo.record]: cloud_fraction = 0.210000; 12:56:58.974 [planetinfo.record]: land_color = 0.126968, 0.613281, 0.408118, 1; 12:56:58.974 [planetinfo.record]: land_fraction = 0.620000; 12:56:58.974 [planetinfo.record]: polar_cloud_color = 0.788692, 0.891113, 0.606114, 1; 12:56:58.974 [planetinfo.record]: polar_land_color = 0.752587, 0.938672, 0.860168, 1; 12:56:58.974 [planetinfo.record]: polar_sea_color = 0.936523, 0.88485, 0.877991, 1; 12:56:58.974 [planetinfo.record]: sea_color = 0.634766, 0.494671, 0.476074, 1; 12:56:58.974 [planetinfo.record]: rotation_speed = 0.001333 12:56:58.974 [planetinfo.record]: planet zpos = 573440.000000 12:56:58.974 [planetinfo.record]: sun_radius = 94095.000000 12:56:58.974 [planetinfo.record]: sun_vector = -0.481 0.839 0.255 12:56:58.974 [planetinfo.record]: sun_distance = 778240 12:56:58.975 [planetinfo.record]: corona_flare = 0.080029 12:56:58.975 [planetinfo.record]: corona_hues = 0.519272 12:56:58.975 [planetinfo.record]: sun_color = 0.600576, 0.706168, 0.715381, 1 12:56:58.975 [planetinfo.record]: corona_shimmer = 0.501423 12:56:58.976 [planetinfo.record]: station_vector = -0.202 -0.146 0.968 12:56:58.976 [planetinfo.record]: station = coriolis 12:57:03.660 [PLANETINFO OVER]: Done 12:57:03.661 [PLANETINFO LOGGING]: 3 144 12:57:04.664 [planetinfo.record]: seed = 194 162 154 143 12:57:04.664 [planetinfo.record]: coordinates = 162 81 12:57:04.664 [planetinfo.record]: government = 0; 12:57:04.664 [planetinfo.record]: economy = 3; 12:57:04.664 [planetinfo.record]: techlevel = 6; 12:57:04.664 [planetinfo.record]: population = 28; 12:57:04.664 [planetinfo.record]: productivity = 6272; 12:57:04.664 [planetinfo.record]: name = "Azavear"; 12:57:04.664 [planetinfo.record]: inhabitant = "Black Bony Feline"; 12:57:04.664 [planetinfo.record]: inhabitants = "Black Bony Felines"; 12:57:04.664 [planetinfo.record]: description = "Azavear is an unremarkable dump."; 12:57:04.680 [planetinfo.record]: air_color = 0.801675, 0.507879, 0.968432, 1; 12:57:04.680 [planetinfo.record]: cloud_alpha = 1.000000; 12:57:04.680 [planetinfo.record]: cloud_color = 0.908203, 0.156097, 0.20898, 1; 12:57:04.680 [planetinfo.record]: cloud_fraction = 0.390000; 12:57:04.680 [planetinfo.record]: land_color = 0.66, 0.389297, 0.440054, 1; 12:57:04.680 [planetinfo.record]: land_fraction = 0.260000; 12:57:04.680 [planetinfo.record]: polar_cloud_color = 0.908691, 0.438373, 0.471442, 1; 12:57:04.680 [planetinfo.record]: polar_land_color = 0.934, 0.838229, 0.856186, 1; 12:57:04.680 [planetinfo.record]: polar_sea_color = 0.935547, 0.932041, 0.879451, 1; 12:57:04.680 [planetinfo.record]: sea_color = 0.644531, 0.63487, 0.489944, 1; 12:57:04.680 [planetinfo.record]: rotation_speed = 0.000714 12:57:04.680 [planetinfo.record]: planet zpos = 681800.000000 12:57:04.680 [planetinfo.record]: sun_radius = 160208.851318 12:57:04.680 [planetinfo.record]: sun_vector = -0.014 -0.890 -0.456 12:57:04.680 [planetinfo.record]: sun_distance = 1363600 12:57:04.680 [planetinfo.record]: corona_flare = 0.062515 12:57:04.680 [planetinfo.record]: corona_hues = 0.538696 12:57:04.680 [planetinfo.record]: sun_color = 0.788278, 0.694826, 0.644166, 1 12:57:04.680 [planetinfo.record]: corona_shimmer = 0.377703 12:57:04.681 [planetinfo.record]: station_vector = -0.706 -0.511 0.490 12:57:04.681 [planetinfo.record]: station = coriolis 12:57:10.086 [PLANETINFO OVER]: Done 12:57:10.088 [PLANETINFO LOGGING]: 3 145 12:57:11.105 [planetinfo.record]: seed = 82 202 10 5 12:57:11.105 [planetinfo.record]: coordinates = 202 182 12:57:11.105 [planetinfo.record]: government = 2; 12:57:11.105 [planetinfo.record]: economy = 6; 12:57:11.105 [planetinfo.record]: techlevel = 4; 12:57:11.105 [planetinfo.record]: population = 25; 12:57:11.105 [planetinfo.record]: productivity = 4800; 12:57:11.105 [planetinfo.record]: name = "Cecela"; 12:57:11.105 [planetinfo.record]: inhabitant = "Human Colonial"; 12:57:11.105 [planetinfo.record]: inhabitants = "Human Colonials"; 12:57:11.105 [planetinfo.record]: description = "This world is most fabled for its inhabitants’ eccentric love for tourists but beset by lethal disease."; 12:57:11.116 [planetinfo.record]: air_color = 0.787814, 0.626238, 0.873475, 1; 12:57:11.117 [planetinfo.record]: cloud_alpha = 1.000000; 12:57:11.117 [planetinfo.record]: cloud_color = 0.623047, 0.467285, 0.470936, 1; 12:57:11.117 [planetinfo.record]: cloud_fraction = 0.190000; 12:57:11.117 [planetinfo.record]: land_color = 0.66, 0.193359, 0.598024, 1; 12:57:11.117 [planetinfo.record]: land_fraction = 0.390000; 12:57:11.117 [planetinfo.record]: polar_cloud_color = 0.780371, 0.658438, 0.661296, 1; 12:57:11.117 [planetinfo.record]: polar_land_color = 0.934, 0.768908, 0.912074, 1; 12:57:11.117 [planetinfo.record]: polar_sea_color = 0.932813, 0.924767, 0.872234, 1; 12:57:11.117 [planetinfo.record]: sea_color = 0.671875, 0.648695, 0.497345, 1; 12:57:11.117 [planetinfo.record]: rotation_speed = 0.004407 12:57:11.117 [planetinfo.record]: planet zpos = 386820.000000 12:57:11.117 [planetinfo.record]: sun_radius = 101590.223694 12:57:11.117 [planetinfo.record]: sun_vector = -0.766 -0.634 -0.102 12:57:11.117 [planetinfo.record]: sun_distance = 816620 12:57:11.117 [planetinfo.record]: corona_flare = 0.064827 12:57:11.117 [planetinfo.record]: corona_hues = 0.535973 12:57:11.117 [planetinfo.record]: sun_color = 0.556791, 0.59716, 0.727139, 1 12:57:11.118 [planetinfo.record]: corona_shimmer = 0.439132 12:57:11.118 [planetinfo.record]: station_vector = 0.936 -0.110 0.335 12:57:11.118 [planetinfo.record]: station = coriolis 12:57:15.734 [PLANETINFO OVER]: Done 12:57:15.736 [PLANETINFO LOGGING]: 3 146 12:57:16.737 [planetinfo.record]: seed = 50 224 90 187 12:57:16.737 [planetinfo.record]: coordinates = 224 85 12:57:16.737 [planetinfo.record]: government = 6; 12:57:16.737 [planetinfo.record]: economy = 5; 12:57:16.737 [planetinfo.record]: techlevel = 5; 12:57:16.737 [planetinfo.record]: population = 32; 12:57:16.737 [planetinfo.record]: productivity = 12800; 12:57:16.737 [planetinfo.record]: name = "Anerined"; 12:57:16.737 [planetinfo.record]: inhabitant = "Human Colonial"; 12:57:16.737 [planetinfo.record]: inhabitants = "Human Colonials"; 12:57:16.737 [planetinfo.record]: description = "This planet is beset by lethal disease."; 12:57:16.756 [planetinfo.record]: air_color = 0.734792, 0.708349, 0.997049, 1; 12:57:16.756 [planetinfo.record]: cloud_alpha = 1.000000; 12:57:16.756 [planetinfo.record]: cloud_color = 0.803644, 0.726189, 0.994141, 1; 12:57:16.756 [planetinfo.record]: cloud_fraction = 0.550000; 12:57:16.756 [planetinfo.record]: land_color = 0.128906, 0.373707, 0.66, 1; 12:57:16.756 [planetinfo.record]: land_fraction = 0.350000; 12:57:16.756 [planetinfo.record]: polar_cloud_color = 0.833905, 0.787773, 0.947363, 1; 12:57:16.756 [planetinfo.record]: polar_land_color = 0.746105, 0.832713, 0.934, 1; 12:57:16.756 [planetinfo.record]: polar_sea_color = 0.944531, 0.928297, 0.897305, 1; 12:57:16.756 [planetinfo.record]: sea_color = 0.554688, 0.516553, 0.44375, 1; 12:57:16.756 [planetinfo.record]: rotation_speed = 0.002619 12:57:16.756 [planetinfo.record]: planet zpos = 761280.000000 12:57:16.756 [planetinfo.record]: sun_radius = 137060.566406 12:57:16.756 [planetinfo.record]: sun_vector = -0.965 -0.051 -0.258 12:57:16.756 [planetinfo.record]: sun_distance = 1112640 12:57:16.756 [planetinfo.record]: corona_flare = 0.082314 12:57:16.756 [planetinfo.record]: corona_hues = 0.586830 12:57:16.756 [planetinfo.record]: sun_color = 0.77988, 0.536273, 0.519796, 1 12:57:16.757 [planetinfo.record]: corona_shimmer = 0.855964 12:57:16.757 [planetinfo.record]: station_vector = -0.774 -0.602 0.196 12:57:16.757 [planetinfo.record]: station = coriolis 12:57:21.611 [PLANETINFO OVER]: Done 12:57:21.612 [PLANETINFO LOGGING]: 3 147 12:57:22.615 [planetinfo.record]: seed = 130 56 170 181 12:57:22.615 [planetinfo.record]: coordinates = 56 140 12:57:22.615 [planetinfo.record]: government = 0; 12:57:22.615 [planetinfo.record]: economy = 6; 12:57:22.615 [planetinfo.record]: techlevel = 1; 12:57:22.615 [planetinfo.record]: population = 11; 12:57:22.615 [planetinfo.record]: productivity = 1408; 12:57:22.615 [planetinfo.record]: name = "Laquused"; 12:57:22.615 [planetinfo.record]: inhabitant = "Harmless Fat Humanoid"; 12:57:22.615 [planetinfo.record]: inhabitants = "Harmless Fat Humanoids"; 12:57:22.615 [planetinfo.record]: description = "This world is very notable for the Laquusedian edible moth."; 12:57:22.632 [planetinfo.record]: air_color = 0.687811, 0.852634, 0.888434, 1; 12:57:22.632 [planetinfo.record]: cloud_alpha = 1.000000; 12:57:22.632 [planetinfo.record]: cloud_color = 0.63943, 0.667969, 0.615784, 1; 12:57:22.632 [planetinfo.record]: cloud_fraction = 0.430000; 12:57:22.632 [planetinfo.record]: land_color = 0.66, 0.183047, 0.563119, 1; 12:57:22.639 [planetinfo.record]: land_fraction = 0.690000; 12:57:22.639 [planetinfo.record]: polar_cloud_color = 0.779208, 0.800586, 0.761495, 1; 12:57:22.639 [planetinfo.record]: polar_land_color = 0.934, 0.76526, 0.899725, 1; 12:57:22.639 [planetinfo.record]: polar_sea_color = 0.941992, 0.932089, 0.893237, 1; 12:57:22.639 [planetinfo.record]: sea_color = 0.580078, 0.555684, 0.459984, 1; 12:57:22.639 [planetinfo.record]: rotation_speed = 0.000091 12:57:22.639 [planetinfo.record]: planet zpos = 622800.000000 12:57:22.639 [planetinfo.record]: sun_radius = 109500.637207 12:57:22.639 [planetinfo.record]: sun_vector = -0.391 -0.790 -0.473 12:57:22.640 [planetinfo.record]: sun_distance = 830400 12:57:22.640 [planetinfo.record]: corona_flare = 0.017378 12:57:22.640 [planetinfo.record]: corona_hues = 0.603218 12:57:22.640 [planetinfo.record]: sun_color = 0.832498, 0.644745, 0.363894, 1 12:57:22.640 [planetinfo.record]: corona_shimmer = 0.978383 12:57:22.640 [planetinfo.record]: station_vector = -0.750 0.289 0.595 12:57:22.640 [planetinfo.record]: station = coriolis 12:57:27.802 [PLANETINFO OVER]: Done 12:57:27.803 [PLANETINFO LOGGING]: 3 148 12:57:28.808 [planetinfo.record]: seed = 226 152 26 124 12:57:28.808 [planetinfo.record]: coordinates = 152 104 12:57:28.808 [planetinfo.record]: government = 4; 12:57:28.808 [planetinfo.record]: economy = 0; 12:57:28.808 [planetinfo.record]: techlevel = 9; 12:57:28.808 [planetinfo.record]: population = 41; 12:57:28.808 [planetinfo.record]: productivity = 26240; 12:57:28.808 [planetinfo.record]: name = "Teisen"; 12:57:28.808 [planetinfo.record]: inhabitant = "Human Colonial"; 12:57:28.808 [planetinfo.record]: inhabitants = "Human Colonials"; 12:57:28.808 [planetinfo.record]: description = "Teisen is reasonably well known for its vast oceans but ravaged by lethal spotted craboids."; 12:57:28.824 [planetinfo.record]: air_color = 0.631728, 0.569699, 0.866971, 1; 12:57:28.824 [planetinfo.record]: cloud_alpha = 1.000000; 12:57:28.824 [planetinfo.record]: cloud_color = 0.510321, 0.344193, 0.603516, 1; 12:57:28.825 [planetinfo.record]: cloud_fraction = 0.220000; 12:57:28.825 [planetinfo.record]: land_color = 0.66, 0.598125, 0.4125, 1; 12:57:28.825 [planetinfo.record]: land_fraction = 0.300000; 12:57:28.825 [planetinfo.record]: polar_cloud_color = 0.697115, 0.56437, 0.771582, 1; 12:57:28.825 [planetinfo.record]: polar_land_color = 0.934, 0.912109, 0.846438, 1; 12:57:28.825 [planetinfo.record]: polar_sea_color = 0.936719, 0.847072, 0.912206, 1; 12:57:28.825 [planetinfo.record]: sea_color = 0.632812, 0.390564, 0.566573, 1; 12:57:28.825 [planetinfo.record]: rotation_speed = 0.001954 12:57:28.825 [planetinfo.record]: planet zpos = 664400.000000 12:57:28.825 [planetinfo.record]: sun_radius = 196017.059326 12:57:28.825 [planetinfo.record]: sun_vector = -0.258 -0.336 -0.906 12:57:28.825 [planetinfo.record]: sun_distance = 1208000 12:57:28.825 [planetinfo.record]: corona_flare = 0.012305 12:57:28.825 [planetinfo.record]: corona_hues = 0.715782 12:57:28.825 [planetinfo.record]: sun_color = 0.690469, 0.736179, 0.814694, 1 12:57:28.826 [planetinfo.record]: corona_shimmer = 0.397029 12:57:28.826 [planetinfo.record]: station_vector = -0.246 -0.968 0.050 12:57:28.826 [planetinfo.record]: station = coriolis 12:57:33.115 [PLANETINFO OVER]: Done 12:57:33.115 [PLANETINFO LOGGING]: 3 149 12:57:34.118 [planetinfo.record]: seed = 114 140 202 156 12:57:34.118 [planetinfo.record]: coordinates = 140 146 12:57:34.118 [planetinfo.record]: government = 6; 12:57:34.118 [planetinfo.record]: economy = 2; 12:57:34.118 [planetinfo.record]: techlevel = 8; 12:57:34.118 [planetinfo.record]: population = 41; 12:57:34.118 [planetinfo.record]: productivity = 26240; 12:57:34.118 [planetinfo.record]: name = "Teance"; 12:57:34.118 [planetinfo.record]: inhabitant = "Black Feline"; 12:57:34.118 [planetinfo.record]: inhabitants = "Black Felines"; 12:57:34.118 [planetinfo.record]: description = "Teance is well known for its inhabitants’ ancient mating traditions but ravaged by dreadful civil war."; 12:57:34.143 [planetinfo.record]: air_color = 0.559194, 0.50631, 0.911197, 1; 12:57:34.143 [planetinfo.record]: cloud_alpha = 1.000000; 12:57:34.143 [planetinfo.record]: cloud_color = 0.415263, 0.195587, 0.736328, 1; 12:57:34.143 [planetinfo.record]: cloud_fraction = 0.130000; 12:57:34.143 [planetinfo.record]: land_color = 0.66, 0.378984, 0.504124, 1; 12:57:34.144 [planetinfo.record]: land_fraction = 0.650000; 12:57:34.144 [planetinfo.record]: polar_cloud_color = 0.604787, 0.449772, 0.831348, 1; 12:57:34.144 [planetinfo.record]: polar_land_color = 0.934, 0.83458, 0.878853, 1; 12:57:34.144 [planetinfo.record]: polar_sea_color = 0.945508, 0.900834, 0.893523, 1; 12:57:34.144 [planetinfo.record]: sea_color = 0.544922, 0.441934, 0.425082, 1; 12:57:34.144 [planetinfo.record]: rotation_speed = 0.003218 12:57:34.144 [planetinfo.record]: planet zpos = 663080.000000 12:57:34.144 [planetinfo.record]: sun_radius = 190817.988281 12:57:34.145 [planetinfo.record]: sun_vector = 0.090 -0.004 -0.996 12:57:34.145 [planetinfo.record]: sun_distance = 1205600 12:57:34.145 [planetinfo.record]: corona_flare = 0.079803 12:57:34.145 [planetinfo.record]: corona_hues = 0.500633 12:57:34.145 [planetinfo.record]: sun_color = 0.759619, 0.74275, 0.494634, 1 12:57:34.145 [planetinfo.record]: corona_shimmer = 0.372292 12:57:34.146 [planetinfo.record]: station_vector = -0.518 0.640 0.567 12:57:34.146 [planetinfo.record]: station = coriolis 12:57:38.782 [PLANETINFO OVER]: Done 12:57:38.782 [PLANETINFO LOGGING]: 3 150 12:57:39.786 [planetinfo.record]: seed = 210 61 218 222 12:57:39.786 [planetinfo.record]: coordinates = 61 229 12:57:39.786 [planetinfo.record]: government = 2; 12:57:39.786 [planetinfo.record]: economy = 5; 12:57:39.786 [planetinfo.record]: techlevel = 4; 12:57:39.786 [planetinfo.record]: population = 24; 12:57:39.786 [planetinfo.record]: productivity = 5760; 12:57:39.786 [planetinfo.record]: name = "Rileri"; 12:57:39.786 [planetinfo.record]: inhabitant = "Slimy Lizard"; 12:57:39.786 [planetinfo.record]: inhabitants = "Slimy Lizards"; 12:57:39.786 [planetinfo.record]: description = "Rileri is most noted for the Rileriian deadly Ardigenuoid and the Rileriian mountain slug."; 12:57:39.806 [planetinfo.record]: air_color = 0.666432, 0.874853, 0.900141, 1; 12:57:39.807 [planetinfo.record]: cloud_alpha = 1.000000; 12:57:39.807 [planetinfo.record]: cloud_color = 0.660682, 0.703125, 0.576782, 1; 12:57:39.807 [planetinfo.record]: cloud_fraction = 0.320000; 12:57:39.807 [planetinfo.record]: land_color = 0.66, 0.415078, 0.489703, 1; 12:57:39.807 [planetinfo.record]: land_fraction = 0.520000; 12:57:39.807 [planetinfo.record]: polar_cloud_color = 0.785605, 0.816406, 0.72472, 1; 12:57:39.807 [planetinfo.record]: polar_land_color = 0.934, 0.84735, 0.873751, 1; 12:57:39.807 [planetinfo.record]: polar_sea_color = 0.933398, 0.906024, 0.876884, 1; 12:57:39.807 [planetinfo.record]: sea_color = 0.666016, 0.587886, 0.504715, 1; 12:57:39.807 [planetinfo.record]: rotation_speed = 0.003861 12:57:39.807 [planetinfo.record]: planet zpos = 904540.000000 12:57:39.807 [planetinfo.record]: sun_radius = 115754.982605 12:57:39.807 [planetinfo.record]: sun_vector = 0.980 -0.197 -0.038 12:57:39.807 [planetinfo.record]: sun_distance = 1162980 12:57:39.807 [planetinfo.record]: corona_flare = 0.029480 12:57:39.807 [planetinfo.record]: corona_hues = 0.536804 12:57:39.808 [planetinfo.record]: sun_color = 0.765347, 0.76041, 0.38289, 1 12:57:39.808 [planetinfo.record]: corona_shimmer = 0.443608 12:57:39.808 [planetinfo.record]: station_vector = 0.966 0.045 0.254 12:57:39.808 [planetinfo.record]: station = coriolis 12:57:44.337 [PLANETINFO OVER]: Done 12:57:44.338 [PLANETINFO LOGGING]: 3 151 12:57:45.347 [planetinfo.record]: seed = 34 255 106 31 12:57:45.347 [planetinfo.record]: coordinates = 255 30 12:57:45.347 [planetinfo.record]: government = 4; 12:57:45.347 [planetinfo.record]: economy = 6; 12:57:45.347 [planetinfo.record]: techlevel = 6; 12:57:45.347 [planetinfo.record]: population = 35; 12:57:45.347 [planetinfo.record]: productivity = 8960; 12:57:45.347 [planetinfo.record]: name = "Onisaned"; 12:57:45.347 [planetinfo.record]: inhabitant = "Human Colonial"; 12:57:45.347 [planetinfo.record]: inhabitants = "Human Colonials"; 12:57:45.348 [planetinfo.record]: description = "Onisaned is mildly notable for its inhabitants’ unusual mating traditions."; 12:57:45.368 [planetinfo.record]: air_color = 0.72422, 0.548092, 0.919652, 1; 12:57:45.368 [planetinfo.record]: cloud_alpha = 1.000000; 12:57:45.368 [planetinfo.record]: cloud_color = 0.761719, 0.294571, 0.557342, 1; 12:57:45.368 [planetinfo.record]: cloud_fraction = 0.420000; 12:57:45.369 [planetinfo.record]: land_color = 0.389297, 0.66, 0.418905, 1; 12:57:45.369 [planetinfo.record]: land_fraction = 0.540000; 12:57:45.369 [planetinfo.record]: polar_cloud_color = 0.842773, 0.519738, 0.701445, 1; 12:57:45.369 [planetinfo.record]: polar_land_color = 0.838229, 0.934, 0.848704, 1; 12:57:45.369 [planetinfo.record]: polar_sea_color = 0.935742, 0.903168, 0.874426, 1; 12:57:45.369 [planetinfo.record]: sea_color = 0.642578, 0.553102, 0.474152, 1; 12:57:45.369 [planetinfo.record]: rotation_speed = 0.003803 12:57:45.369 [planetinfo.record]: planet zpos = 691100.000000 12:57:45.369 [planetinfo.record]: sun_radius = 207182.365112 12:57:45.369 [planetinfo.record]: sun_vector = -0.634 -0.246 0.733 12:57:45.370 [planetinfo.record]: sun_distance = 1451310 12:57:45.370 [planetinfo.record]: corona_flare = 0.044991 12:57:45.370 [planetinfo.record]: corona_hues = 0.521225 12:57:45.370 [planetinfo.record]: sun_color = 0.470563, 0.731847, 0.781522, 1 12:57:45.370 [planetinfo.record]: corona_shimmer = 0.454045 12:57:45.371 [planetinfo.record]: station_vector = -0.402 -0.915 0.010 12:57:45.371 [planetinfo.record]: station = coriolis 12:57:50.691 [PLANETINFO OVER]: Done 12:57:50.692 [PLANETINFO LOGGING]: 3 152 12:57:51.696 [planetinfo.record]: seed = 2 184 154 80 12:57:51.696 [planetinfo.record]: coordinates = 184 91 12:57:51.696 [planetinfo.record]: government = 0; 12:57:51.696 [planetinfo.record]: economy = 3; 12:57:51.696 [planetinfo.record]: techlevel = 4; 12:57:51.696 [planetinfo.record]: population = 20; 12:57:51.696 [planetinfo.record]: productivity = 4480; 12:57:51.696 [planetinfo.record]: name = "Erzain"; 12:57:51.696 [planetinfo.record]: inhabitant = "Yellow Bony Lobster"; 12:57:51.696 [planetinfo.record]: inhabitants = "Yellow Bony Lobsters"; 12:57:51.696 [planetinfo.record]: description = "Erzain is reasonably well known for the Erzainian spotted shrew but plagued by evil tree snakes."; 12:57:51.698 [planetinfo.record]: air_color = 0.66839, 0.459907, 0.976236, 1; 12:57:51.698 [planetinfo.record]: cloud_alpha = 1.000000; 12:57:51.698 [planetinfo.record]: cloud_color = 0.931641, 0.0109177, 0.723039, 1; 12:57:51.698 [planetinfo.record]: cloud_fraction = 0.460000; 12:57:51.698 [planetinfo.record]: land_color = 0.27832, 0.305428, 0.59375, 1; 12:57:51.698 [planetinfo.record]: land_fraction = 0.320000; 12:57:51.698 [planetinfo.record]: polar_cloud_color = 0.919238, 0.351447, 0.790598, 1; 12:57:51.698 [planetinfo.record]: polar_land_color = 0.815698, 0.826434, 0.940625, 1; 12:57:51.698 [planetinfo.record]: polar_sea_color = 0.933789, 0.909567, 0.87178, 1; 12:57:51.699 [planetinfo.record]: sea_color = 0.662109, 0.593409, 0.486237, 1; 12:57:51.699 [planetinfo.record]: rotation_speed = 0.003189 12:57:51.699 [planetinfo.record]: planet zpos = 360000.000000 12:57:51.699 [planetinfo.record]: sun_radius = 63939.056396 12:57:51.699 [planetinfo.record]: sun_vector = -0.263 -0.481 -0.836 12:57:51.699 [planetinfo.record]: sun_distance = 540000 12:57:51.699 [planetinfo.record]: corona_flare = 0.010126 12:57:51.699 [planetinfo.record]: corona_hues = 0.644661 12:57:51.699 [planetinfo.record]: sun_color = 0.398253, 0.6782, 0.82059, 1 12:57:51.699 [planetinfo.record]: corona_shimmer = 0.363685 12:57:51.699 [planetinfo.record]: station_vector = -0.328 -0.937 0.123 12:57:51.699 [planetinfo.record]: station = coriolis 12:57:57.031 [PLANETINFO OVER]: Done 12:57:57.032 [PLANETINFO LOGGING]: 3 153 12:57:58.036 [planetinfo.record]: seed = 146 33 138 242 12:57:58.036 [planetinfo.record]: coordinates = 33 108 12:57:58.036 [planetinfo.record]: government = 2; 12:57:58.036 [planetinfo.record]: economy = 4; 12:57:58.036 [planetinfo.record]: techlevel = 5; 12:57:58.036 [planetinfo.record]: population = 27; 12:57:58.036 [planetinfo.record]: productivity = 7776; 12:57:58.036 [planetinfo.record]: name = "Enlaus"; 12:57:58.036 [planetinfo.record]: inhabitant = "Furry Insect"; 12:57:58.036 [planetinfo.record]: inhabitants = "Furry Insects"; 12:57:58.036 [planetinfo.record]: description = "Enlaus is mildly well known for Enlausian shrew cutlet and the Enlausian edible moth."; 12:57:58.059 [planetinfo.record]: air_color = 0.536088, 0.471435, 0.938514, 1; 12:57:58.060 [planetinfo.record]: cloud_alpha = 1.000000; 12:57:58.060 [planetinfo.record]: cloud_color = 0.400863, 0.0863113, 0.818359, 1; 12:57:58.060 [planetinfo.record]: cloud_fraction = 0.580000; 12:57:58.060 [planetinfo.record]: land_color = 0.623906, 0.66, 0.61875, 1; 12:57:58.060 [planetinfo.record]: land_fraction = 0.430000; 12:57:58.060 [planetinfo.record]: polar_cloud_color = 0.591415, 0.382832, 0.868262, 1; 12:57:58.060 [planetinfo.record]: polar_land_color = 0.92123, 0.934, 0.919406, 1; 12:57:58.060 [planetinfo.record]: polar_sea_color = 0.743548, 0.936523, 0.873204, 1; 12:57:58.060 [planetinfo.record]: sea_color = 0.11158, 0.634766, 0.463095, 1; 12:57:58.060 [planetinfo.record]: rotation_speed = 0.001990 12:57:58.060 [planetinfo.record]: planet zpos = 369710.000000 12:57:58.060 [planetinfo.record]: sun_radius = 68748.799591 12:57:58.060 [planetinfo.record]: sun_vector = 0.491 -0.234 0.839 12:57:58.060 [planetinfo.record]: sun_distance = 638590 12:57:58.060 [planetinfo.record]: corona_flare = 0.002771 12:57:58.060 [planetinfo.record]: corona_hues = 0.602768 12:57:58.060 [planetinfo.record]: sun_color = 0.514248, 0.638969, 0.720569, 1 12:57:58.060 [planetinfo.record]: corona_shimmer = 0.260751 12:57:58.061 [planetinfo.record]: station_vector = -0.098 0.975 0.202 12:57:58.061 [planetinfo.record]: station = coriolis 12:58:02.372 [PLANETINFO OVER]: Done 12:58:02.373 [PLANETINFO LOGGING]: 3 154 12:58:03.375 [planetinfo.record]: seed = 114 8 90 30 12:58:03.375 [planetinfo.record]: coordinates = 8 149 12:58:03.375 [planetinfo.record]: government = 6; 12:58:03.375 [planetinfo.record]: economy = 5; 12:58:03.375 [planetinfo.record]: techlevel = 5; 12:58:03.375 [planetinfo.record]: population = 32; 12:58:03.376 [planetinfo.record]: productivity = 12800; 12:58:03.376 [planetinfo.record]: name = "Rianxe"; 12:58:03.376 [planetinfo.record]: inhabitant = "Human Colonial"; 12:58:03.376 [planetinfo.record]: inhabitants = "Human Colonials"; 12:58:03.376 [planetinfo.record]: description = "Rianxe is mildly well known for its hoopy night life."; 12:58:03.400 [planetinfo.record]: air_color = 0.729376, 0.731716, 0.96648, 1; 12:58:03.400 [planetinfo.record]: cloud_alpha = 1.000000; 12:58:03.400 [planetinfo.record]: cloud_color = 0.778976, 0.780904, 0.902344, 1; 12:58:03.400 [planetinfo.record]: cloud_fraction = 0.150000; 12:58:03.400 [planetinfo.record]: land_color = 0.552734, 0.483643, 0.491739, 1; 12:58:03.400 [planetinfo.record]: land_fraction = 0.630000; 12:58:03.400 [planetinfo.record]: polar_cloud_color = 0.828633, 0.829843, 0.906055, 1; 12:58:03.400 [planetinfo.record]: polar_land_color = 0.944727, 0.915204, 0.918664, 1; 12:58:03.400 [planetinfo.record]: polar_sea_color = 0.888936, 0.921094, 0.841937, 1; 12:58:03.400 [planetinfo.record]: sea_color = 0.678871, 0.789062, 0.517822, 1; 12:58:03.400 [planetinfo.record]: rotation_speed = 0.000014 12:58:03.401 [planetinfo.record]: planet zpos = 833040.000000 12:58:03.401 [planetinfo.record]: sun_radius = 150043.765869 12:58:03.401 [planetinfo.record]: sun_vector = -0.831 -0.079 0.551 12:58:03.401 [planetinfo.record]: sun_distance = 1025280 12:58:03.401 [planetinfo.record]: corona_flare = 0.087265 12:58:03.401 [planetinfo.record]: corona_hues = 0.550545 12:58:03.401 [planetinfo.record]: sun_color = 0.763339, 0.43614, 0.404947, 1 12:58:03.401 [planetinfo.record]: corona_shimmer = 0.497148 12:58:03.401 [planetinfo.record]: station_vector = 0.809 -0.280 0.517 12:58:03.401 [planetinfo.record]: station = coriolis 12:58:08.567 [PLANETINFO OVER]: Done 12:58:08.568 [PLANETINFO LOGGING]: 3 155 12:58:09.572 [planetinfo.record]: seed = 194 188 42 91 12:58:09.572 [planetinfo.record]: coordinates = 188 226 12:58:09.572 [planetinfo.record]: government = 0; 12:58:09.572 [planetinfo.record]: economy = 2; 12:58:09.572 [planetinfo.record]: techlevel = 5; 12:58:09.572 [planetinfo.record]: population = 23; 12:58:09.572 [planetinfo.record]: productivity = 5888; 12:58:09.572 [planetinfo.record]: name = "Anquen"; 12:58:09.572 [planetinfo.record]: inhabitant = "Human Colonial"; 12:58:09.572 [planetinfo.record]: inhabitants = "Human Colonials"; 12:58:09.572 [planetinfo.record]: description = "Anquen is a revolting little planet."; 12:58:09.604 [planetinfo.record]: air_color = 0.630595, 0.909246, 0.893726, 1; 12:58:09.604 [planetinfo.record]: cloud_alpha = 1.000000; 12:58:09.604 [planetinfo.record]: cloud_color = 0.730469, 0.69391, 0.49649, 1; 12:58:09.611 [planetinfo.record]: cloud_fraction = 0.430000; 12:58:09.612 [planetinfo.record]: land_color = 0.654844, 0.65855, 0.66, 1; 12:58:09.612 [planetinfo.record]: land_fraction = 0.470000; 12:58:09.612 [planetinfo.record]: polar_cloud_color = 0.828711, 0.802788, 0.662807, 1; 12:58:09.612 [planetinfo.record]: polar_land_color = 0.932176, 0.933487, 0.934, 1; 12:58:09.612 [planetinfo.record]: polar_sea_color = 0.916427, 0.928125, 0.874649, 1; 12:58:09.612 [planetinfo.record]: sea_color = 0.682514, 0.71875, 0.553101, 1; 12:58:09.612 [planetinfo.record]: rotation_speed = 0.002570 12:58:09.612 [planetinfo.record]: planet zpos = 640200.000000 12:58:09.612 [planetinfo.record]: sun_radius = 138045.611572 12:58:09.612 [planetinfo.record]: sun_vector = 0.049 -0.690 -0.723 12:58:09.612 [planetinfo.record]: sun_distance = 1164000 12:58:09.612 [planetinfo.record]: corona_flare = 0.089359 12:58:09.612 [planetinfo.record]: corona_hues = 0.761108 12:58:09.612 [planetinfo.record]: sun_color = 0.703088, 0.637552, 0.620663, 1 12:58:09.612 [planetinfo.record]: corona_shimmer = 0.324118 12:58:09.612 [planetinfo.record]: station_vector = 0.533 -0.841 0.088 12:58:09.612 [planetinfo.record]: station = coriolis 12:58:14.865 [PLANETINFO OVER]: Done 12:58:14.866 [PLANETINFO LOGGING]: 3 156 12:58:15.882 [planetinfo.record]: seed = 34 104 26 117 12:58:15.882 [planetinfo.record]: coordinates = 104 18 12:58:15.882 [planetinfo.record]: government = 4; 12:58:15.882 [planetinfo.record]: economy = 2; 12:58:15.882 [planetinfo.record]: techlevel = 7; 12:58:15.882 [planetinfo.record]: population = 35; 12:58:15.882 [planetinfo.record]: productivity = 17920; 12:58:15.883 [planetinfo.record]: name = "Laainat"; 12:58:15.883 [planetinfo.record]: inhabitant = "Human Colonial"; 12:58:15.883 [planetinfo.record]: inhabitants = "Human Colonials"; 12:58:15.883 [planetinfo.record]: description = "This world is very well known for its inhabitants’ unusual mating traditions and its great parking meters."; 12:58:15.900 [planetinfo.record]: air_color = 0.610066, 0.587573, 0.83315, 1; 12:58:15.900 [planetinfo.record]: cloud_alpha = 1.000000; 12:58:15.900 [planetinfo.record]: cloud_color = 0.400193, 0.358818, 0.501953, 1; 12:58:15.900 [planetinfo.record]: cloud_fraction = 0.260000; 12:58:15.900 [planetinfo.record]: land_color = 0.220188, 0.113438, 0.66, 1; 12:58:15.900 [planetinfo.record]: land_fraction = 0.530000; 12:58:15.900 [planetinfo.record]: polar_cloud_color = 0.633906, 0.596511, 0.725879, 1; 12:58:15.900 [planetinfo.record]: polar_land_color = 0.7784, 0.740633, 0.934, 1; 12:58:15.900 [planetinfo.record]: polar_sea_color = 0.934265, 0.939258, 0.890094, 1; 12:58:15.900 [planetinfo.record]: sea_color = 0.594505, 0.607422, 0.480243, 1; 12:58:15.900 [planetinfo.record]: rotation_speed = 0.004444 12:58:15.900 [planetinfo.record]: planet zpos = 462000.000000 12:58:15.900 [planetinfo.record]: sun_radius = 85277.893066 12:58:15.900 [planetinfo.record]: sun_vector = 0.320 0.722 0.613 12:58:15.900 [planetinfo.record]: sun_distance = 882000 12:58:15.900 [planetinfo.record]: corona_flare = 0.041222 12:58:15.900 [planetinfo.record]: corona_hues = 0.570908 12:58:15.900 [planetinfo.record]: sun_color = 0.722427, 0.663738, 0.553092, 1 12:58:15.900 [planetinfo.record]: corona_shimmer = 0.830768 12:58:15.901 [planetinfo.record]: station_vector = -0.047 0.993 0.112 12:58:15.901 [planetinfo.record]: station = coriolis 12:58:20.041 [PLANETINFO OVER]: Done 12:58:20.042 [PLANETINFO LOGGING]: 3 157 12:58:21.044 [planetinfo.record]: seed = 178 49 74 238 12:58:21.044 [planetinfo.record]: coordinates = 49 204 12:58:21.044 [planetinfo.record]: government = 6; 12:58:21.044 [planetinfo.record]: economy = 4; 12:58:21.044 [planetinfo.record]: techlevel = 7; 12:58:21.044 [planetinfo.record]: population = 39; 12:58:21.044 [planetinfo.record]: productivity = 18720; 12:58:21.044 [planetinfo.record]: name = "Reininus"; 12:58:21.044 [planetinfo.record]: inhabitant = "Human Colonial"; 12:58:21.044 [planetinfo.record]: inhabitants = "Human Colonials"; 12:58:21.044 [planetinfo.record]: description = "Reininus is most famous for its pink Ceerle tulip plantations and the Reininusian edible arts graduate."; 12:58:21.047 [planetinfo.record]: air_color = 0.468654, 0.582304, 0.893637, 1; 12:58:21.047 [planetinfo.record]: cloud_alpha = 1.000000; 12:58:21.047 [planetinfo.record]: cloud_color = 0.117493, 0.564182, 0.683594, 1; 12:58:21.047 [planetinfo.record]: cloud_fraction = 0.260000; 12:58:21.047 [planetinfo.record]: land_color = 0.0979688, 0.66, 0.554619, 1; 12:58:21.047 [planetinfo.record]: land_fraction = 0.550000; 12:58:21.047 [planetinfo.record]: polar_cloud_color = 0.389612, 0.719444, 0.807617, 1; 12:58:21.047 [planetinfo.record]: polar_land_color = 0.73516, 0.934, 0.896718, 1; 12:58:21.047 [planetinfo.record]: polar_sea_color = 0.883389, 0.929883, 0.90155, 1; 12:58:21.048 [planetinfo.record]: sea_color = 0.560938, 0.701172, 0.615717, 1; 12:58:21.048 [planetinfo.record]: rotation_speed = 0.000741 12:58:21.048 [planetinfo.record]: planet zpos = 580410.000000 12:58:21.048 [planetinfo.record]: sun_radius = 187901.321564 12:58:21.048 [planetinfo.record]: sun_vector = 0.189 -0.914 -0.359 12:58:21.048 [planetinfo.record]: sun_distance = 1096330 12:58:21.048 [planetinfo.record]: corona_flare = 0.002184 12:58:21.048 [planetinfo.record]: corona_hues = 0.716064 12:58:21.048 [planetinfo.record]: sun_color = 0.729431, 0.675593, 0.658993, 1 12:58:21.048 [planetinfo.record]: corona_shimmer = 0.349179 12:58:21.048 [planetinfo.record]: station_vector = -0.836 0.549 0.015 12:58:21.048 [planetinfo.record]: station = coriolis 12:58:26.110 [PLANETINFO OVER]: Done 12:58:26.113 [PLANETINFO LOGGING]: 3 158 12:58:27.119 [planetinfo.record]: seed = 18 232 218 225 12:58:27.119 [planetinfo.record]: coordinates = 232 12 12:58:27.119 [planetinfo.record]: government = 2; 12:58:27.119 [planetinfo.record]: economy = 4; 12:58:27.119 [planetinfo.record]: techlevel = 4; 12:58:27.119 [planetinfo.record]: population = 23; 12:58:27.119 [planetinfo.record]: productivity = 6624; 12:58:27.119 [planetinfo.record]: name = "Leveor"; 12:58:27.119 [planetinfo.record]: inhabitant = "Large Fat Humanoid"; 12:58:27.119 [planetinfo.record]: inhabitants = "Large Fat Humanoids"; 12:58:27.119 [planetinfo.record]: description = "This planet is most fabled for Leveorian A’erin brandy but scourged by killer edible A’erinoids."; 12:58:27.138 [planetinfo.record]: air_color = 0.433611, 0.835102, 0.834674, 1; 12:58:27.139 [planetinfo.record]: cloud_alpha = 1.000000; 12:58:27.139 [planetinfo.record]: cloud_color = 0.504465, 0.507812, 0.0793457, 1; 12:58:27.139 [planetinfo.record]: cloud_fraction = 0.460000; 12:58:27.139 [planetinfo.record]: land_color = 0.0180469, 0.66, 0.509542, 1; 12:58:27.139 [planetinfo.record]: land_fraction = 0.330000; 12:58:27.139 [planetinfo.record]: polar_cloud_color = 0.725514, 0.728516, 0.344337, 1; 12:58:27.139 [planetinfo.record]: polar_land_color = 0.706885, 0.934, 0.88077, 1; 12:58:27.139 [planetinfo.record]: polar_sea_color = 0.939062, 0.913362, 0.883306, 1; 12:58:27.139 [planetinfo.record]: sea_color = 0.609375, 0.542665, 0.464648, 1; 12:58:27.139 [planetinfo.record]: rotation_speed = 0.001388 12:58:27.139 [planetinfo.record]: planet zpos = 462560.000000 12:58:27.139 [planetinfo.record]: sun_radius = 80365.605469 12:58:27.139 [planetinfo.record]: sun_vector = -0.994 -0.106 -0.026 12:58:27.139 [planetinfo.record]: sun_distance = 627760 12:58:27.139 [planetinfo.record]: corona_flare = 0.076625 12:58:27.139 [planetinfo.record]: corona_hues = 0.840515 12:58:27.139 [planetinfo.record]: sun_color = 0.232639, 0.576977, 0.837888, 1 12:58:27.140 [planetinfo.record]: corona_shimmer = 0.757151 12:58:27.140 [planetinfo.record]: station_vector = -0.727 -0.526 0.442 12:58:27.140 [planetinfo.record]: station = coriolis 12:58:31.825 [PLANETINFO OVER]: Done 12:58:31.826 [PLANETINFO LOGGING]: 3 159 12:58:32.830 [planetinfo.record]: seed = 98 89 234 208 12:58:32.830 [planetinfo.record]: coordinates = 89 160 12:58:32.830 [planetinfo.record]: government = 4; 12:58:32.830 [planetinfo.record]: economy = 0; 12:58:32.830 [planetinfo.record]: techlevel = 10; 12:58:32.830 [planetinfo.record]: population = 45; 12:58:32.830 [planetinfo.record]: productivity = 28800; 12:58:32.830 [planetinfo.record]: name = "Ermala"; 12:58:32.830 [planetinfo.record]: inhabitant = "Bug-Eyed Frog"; 12:58:32.831 [planetinfo.record]: inhabitants = "Bug-Eyed Frogs"; 12:58:32.831 [planetinfo.record]: description = "Ermala is cursed by deadly civil war."; 12:58:32.856 [planetinfo.record]: air_color = 0.802374, 0.537641, 0.947619, 1; 12:58:32.857 [planetinfo.record]: cloud_alpha = 1.000000; 12:58:32.857 [planetinfo.record]: cloud_color = 0.845703, 0.254372, 0.28209, 1; 12:58:32.857 [planetinfo.record]: cloud_fraction = 0.200000; 12:58:32.858 [planetinfo.record]: land_color = 0.589464, 0.572344, 0.66, 1; 12:58:32.858 [planetinfo.record]: land_fraction = 0.660000; 12:58:32.858 [planetinfo.record]: polar_cloud_color = 0.880566, 0.495749, 0.513787, 1; 12:58:32.859 [planetinfo.record]: polar_land_color = 0.909045, 0.902988, 0.934, 1; 12:58:32.859 [planetinfo.record]: polar_sea_color = 0.943945, 0.918714, 0.894259, 1; 12:58:32.859 [planetinfo.record]: sea_color = 0.560547, 0.500614, 0.442526, 1; 12:58:32.859 [planetinfo.record]: rotation_speed = 0.001359 12:58:32.859 [planetinfo.record]: planet zpos = 377650.000000 12:58:32.859 [planetinfo.record]: sun_radius = 79067.454529 12:58:32.859 [planetinfo.record]: sun_vector = -0.227 0.602 -0.765 12:58:32.859 [planetinfo.record]: sun_distance = 522900 12:58:32.860 [planetinfo.record]: corona_flare = 0.022687 12:58:32.860 [planetinfo.record]: corona_hues = 0.538124 12:58:32.860 [planetinfo.record]: sun_color = 0.77887, 0.762966, 0.715878, 1 12:58:32.860 [planetinfo.record]: corona_shimmer = 0.438069 12:58:32.862 [planetinfo.record]: station_vector = -0.814 -0.540 0.216 12:58:32.862 [planetinfo.record]: station = coriolis 12:58:37.215 [PLANETINFO OVER]: Done 12:58:37.216 [PLANETINFO LOGGING]: 3 160 12:58:38.232 [planetinfo.record]: seed = 66 145 154 81 12:58:38.232 [planetinfo.record]: coordinates = 145 245 12:58:38.232 [planetinfo.record]: government = 0; 12:58:38.232 [planetinfo.record]: economy = 7; 12:58:38.233 [planetinfo.record]: techlevel = 1; 12:58:38.233 [planetinfo.record]: population = 12; 12:58:38.233 [planetinfo.record]: productivity = 1152; 12:58:38.233 [planetinfo.record]: name = "Atedanza"; 12:58:38.233 [planetinfo.record]: inhabitant = "Yellow Fat Humanoid"; 12:58:38.233 [planetinfo.record]: inhabitants = "Yellow Fat Humanoids"; 12:58:38.233 [planetinfo.record]: description = "This world is plagued by frequent earthquakes."; 12:58:38.256 [planetinfo.record]: air_color = 0.463752, 0.705552, 0.854613, 1; 12:58:38.256 [planetinfo.record]: cloud_alpha = 1.000000; 12:58:38.256 [planetinfo.record]: cloud_color = 0.128326, 0.566406, 0.196776, 1; 12:58:38.256 [planetinfo.record]: cloud_fraction = 0.380000; 12:58:38.256 [planetinfo.record]: land_color = 0.544922, 0.466164, 0.493852, 1; 12:58:38.256 [planetinfo.record]: land_fraction = 0.590000; 12:58:38.256 [planetinfo.record]: polar_cloud_color = 0.389974, 0.754883, 0.446991, 1; 12:58:38.257 [planetinfo.record]: polar_land_color = 0.945508, 0.911344, 0.923355, 1; 12:58:38.257 [planetinfo.record]: polar_sea_color = 0.888429, 0.921289, 0.844815, 1; 12:58:38.257 [planetinfo.record]: sea_color = 0.674813, 0.787109, 0.525764, 1; 12:58:38.257 [planetinfo.record]: rotation_speed = 0.000684 12:58:38.257 [planetinfo.record]: planet zpos = 321700.000000 12:58:38.259 [planetinfo.record]: sun_radius = 93451.356354 12:58:38.260 [planetinfo.record]: sun_vector = -0.012 -0.969 -0.245 12:58:38.260 [planetinfo.record]: sun_distance = 546890 12:58:38.260 [planetinfo.record]: corona_flare = 0.076363 12:58:38.260 [planetinfo.record]: corona_hues = 0.652847 12:58:38.260 [planetinfo.record]: sun_color = 0.694305, 0.584221, 0.357495, 1 12:58:38.260 [planetinfo.record]: corona_shimmer = 0.711265 12:58:38.260 [planetinfo.record]: station_vector = -0.834 0.100 0.543 12:58:38.260 [planetinfo.record]: station = coriolis 12:58:42.967 [PLANETINFO OVER]: Done 12:58:42.968 [PLANETINFO LOGGING]: 3 161 12:58:43.972 [planetinfo.record]: seed = 210 228 10 120 12:58:43.972 [planetinfo.record]: coordinates = 228 187 12:58:43.972 [planetinfo.record]: government = 2; 12:58:43.972 [planetinfo.record]: economy = 3; 12:58:43.972 [planetinfo.record]: techlevel = 5; 12:58:43.972 [planetinfo.record]: population = 26; 12:58:43.972 [planetinfo.record]: productivity = 8736; 12:58:43.972 [planetinfo.record]: name = "Edtira"; 12:58:43.972 [planetinfo.record]: inhabitant = "Human Colonial"; 12:58:43.972 [planetinfo.record]: inhabitants = "Human Colonials"; 12:58:43.972 [planetinfo.record]: description = "The planet Edtira is reasonably noted for its inhabitants’ exceptional love for tourists and its weird volcanoes."; 12:58:43.977 [planetinfo.record]: air_color = 0.735014, 0.772936, 0.935262, 1; 12:58:43.978 [planetinfo.record]: cloud_alpha = 1.000000; 12:58:43.978 [planetinfo.record]: cloud_color = 0.773849, 0.793122, 0.808594, 1; 12:58:43.978 [planetinfo.record]: cloud_fraction = 0.410000; 12:58:43.978 [planetinfo.record]: land_color = 0.0773438, 0.18204, 0.66, 1; 12:58:43.978 [planetinfo.record]: land_fraction = 0.680000; 12:58:43.978 [planetinfo.record]: polar_cloud_color = 0.840668, 0.853536, 0.863867, 1; 12:58:43.978 [planetinfo.record]: polar_land_color = 0.727863, 0.764903, 0.934, 1; 12:58:43.978 [planetinfo.record]: polar_sea_color = 0.933203, 0.923819, 0.873146, 1; 12:58:43.978 [planetinfo.record]: sea_color = 0.667969, 0.641102, 0.496019, 1; 12:58:43.978 [planetinfo.record]: rotation_speed = 0.004464 12:58:43.978 [planetinfo.record]: planet zpos = 611040.000000 12:58:43.979 [planetinfo.record]: sun_radius = 140704.416504 12:58:43.979 [planetinfo.record]: sun_vector = -0.849 -0.525 0.063 12:58:43.979 [planetinfo.record]: sun_distance = 1018400 12:58:43.979 [planetinfo.record]: corona_flare = 0.038304 12:58:43.979 [planetinfo.record]: corona_hues = 0.527626 12:58:43.979 [planetinfo.record]: sun_color = 0.786948, 0.617858, 0.236345, 1 12:58:43.979 [planetinfo.record]: corona_shimmer = 0.431749 12:58:43.979 [planetinfo.record]: station_vector = 0.073 0.591 0.803 12:58:43.979 [planetinfo.record]: station = coriolis 12:58:48.476 [PLANETINFO OVER]: Done 12:58:48.477 [PLANETINFO LOGGING]: 3 162 12:58:49.486 [planetinfo.record]: seed = 178 4 90 145 12:58:49.486 [planetinfo.record]: coordinates = 4 116 12:58:49.486 [planetinfo.record]: government = 6; 12:58:49.486 [planetinfo.record]: economy = 4; 12:58:49.486 [planetinfo.record]: techlevel = 6; 12:58:49.486 [planetinfo.record]: population = 35; 12:58:49.486 [planetinfo.record]: productivity = 16800; 12:58:49.486 [planetinfo.record]: name = "Ataris"; 12:58:49.486 [planetinfo.record]: inhabitant = "Human Colonial"; 12:58:49.486 [planetinfo.record]: inhabitants = "Human Colonials"; 12:58:49.486 [planetinfo.record]: description = "The world Ataris is very famous for its unusual sit coms but ravaged by lethal spotted snails."; 12:58:49.508 [planetinfo.record]: air_color = 0.442128, 0.473978, 0.935912, 1; 12:58:49.508 [planetinfo.record]: cloud_alpha = 1.000000; 12:58:49.508 [planetinfo.record]: cloud_color = 0.0126648, 0.156034, 0.810547, 1; 12:58:49.508 [planetinfo.record]: cloud_fraction = 0.580000; 12:58:49.508 [planetinfo.record]: land_color = 0.230988, 0.509766, 0.457495, 1; 12:58:49.508 [planetinfo.record]: land_fraction = 0.290000; 12:58:49.508 [planetinfo.record]: polar_cloud_color = 0.332725, 0.428322, 0.864746, 1; 12:58:49.508 [planetinfo.record]: polar_land_color = 0.819274, 0.949023, 0.924695, 1; 12:58:49.508 [planetinfo.record]: polar_sea_color = 0.864051, 0.920313, 0.900533, 1; 12:58:49.508 [planetinfo.record]: sea_color = 0.602014, 0.796875, 0.728369, 1; 12:58:49.508 [planetinfo.record]: rotation_speed = 0.002566 12:58:49.509 [planetinfo.record]: planet zpos = 307600.000000 12:58:49.509 [planetinfo.record]: sun_radius = 92582.737427 12:58:49.509 [planetinfo.record]: sun_vector = -0.816 0.169 0.553 12:58:49.509 [planetinfo.record]: sun_distance = 615200 12:58:49.509 [planetinfo.record]: corona_flare = 0.051364 12:58:49.509 [planetinfo.record]: corona_hues = 0.721962 12:58:49.509 [planetinfo.record]: sun_color = 0.654126, 0.471891, 0.190069, 1 12:58:49.509 [planetinfo.record]: corona_shimmer = 0.257859 12:58:49.509 [planetinfo.record]: station_vector = -0.879 -0.449 0.161 12:58:49.509 [planetinfo.record]: station = coriolis 12:58:54.543 [PLANETINFO OVER]: Done 12:58:54.544 [PLANETINFO LOGGING]: 3 163 12:58:55.556 [planetinfo.record]: seed = 2 61 170 232 12:58:55.556 [planetinfo.record]: coordinates = 61 160 12:58:55.556 [planetinfo.record]: government = 0; 12:58:55.556 [planetinfo.record]: economy = 2; 12:58:55.556 [planetinfo.record]: techlevel = 6; 12:58:55.556 [planetinfo.record]: population = 27; 12:58:55.556 [planetinfo.record]: productivity = 6912; 12:58:55.556 [planetinfo.record]: name = "Usbiinan"; 12:58:55.556 [planetinfo.record]: inhabitant = "Small Furry Humanoid"; 12:58:55.556 [planetinfo.record]: inhabitants = "Small Furry Humanoids"; 12:58:55.556 [planetinfo.record]: description = "Usbiinan is most noted for its inhabitants’ exceptional love for food blenders and its weird rock formations."; 12:58:55.573 [planetinfo.record]: air_color = 0.665896, 0.808532, 0.954773, 1; 12:58:55.573 [planetinfo.record]: cloud_alpha = 1.000000; 12:58:55.573 [planetinfo.record]: cloud_color = 0.599579, 0.867188, 0.741746, 1; 12:58:55.573 [planetinfo.record]: cloud_fraction = 0.070000; 12:58:55.573 [planetinfo.record]: land_color = 0.66, 0.219141, 0.518787, 1; 12:58:55.573 [planetinfo.record]: land_fraction = 0.610000; 12:58:55.573 [planetinfo.record]: polar_cloud_color = 0.718534, 0.890234, 0.80975, 1; 12:58:55.573 [planetinfo.record]: polar_land_color = 0.934, 0.778029, 0.884041, 1; 12:58:55.573 [planetinfo.record]: polar_sea_color = 0.930859, 0.848548, 0.8445, 1; 12:58:55.573 [planetinfo.record]: sea_color = 0.691406, 0.446857, 0.43483, 1; 12:58:55.573 [planetinfo.record]: rotation_speed = 0.000115 12:58:55.573 [planetinfo.record]: planet zpos = 689500.000000 12:58:55.574 [planetinfo.record]: sun_radius = 103267.486572 12:58:55.574 [planetinfo.record]: sun_vector = -0.301 -0.797 0.524 12:58:55.574 [planetinfo.record]: sun_distance = 935750 12:58:55.574 [planetinfo.record]: corona_flare = 0.026862 12:58:55.574 [planetinfo.record]: corona_hues = 0.828606 12:58:55.574 [planetinfo.record]: sun_color = 0.710014, 0.71987, 0.729337, 1 12:58:55.574 [planetinfo.record]: corona_shimmer = 0.652035 12:58:55.574 [planetinfo.record]: station_vector = -0.486 -0.728 0.483 12:58:55.574 [planetinfo.record]: station = coriolis 12:59:00.721 [PLANETINFO OVER]: Done 12:59:00.721 [PLANETINFO LOGGING]: 3 164 12:59:01.726 [planetinfo.record]: seed = 98 155 26 78 12:59:01.726 [planetinfo.record]: coordinates = 155 236 12:59:01.726 [planetinfo.record]: government = 4; 12:59:01.726 [planetinfo.record]: economy = 4; 12:59:01.726 [planetinfo.record]: techlevel = 8; 12:59:01.726 [planetinfo.record]: population = 41; 12:59:01.726 [planetinfo.record]: productivity = 15744; 12:59:01.726 [planetinfo.record]: name = "Relaon"; 12:59:01.726 [planetinfo.record]: inhabitant = "Human Colonial"; 12:59:01.726 [planetinfo.record]: inhabitants = "Human Colonials"; 12:59:01.726 [planetinfo.record]: description = "This world is ravaged by occasional solar activity."; 12:59:01.744 [planetinfo.record]: air_color = 0.642701, 0.81217, 0.970383, 1; 12:59:01.745 [planetinfo.record]: cloud_alpha = 1.000000; 12:59:01.745 [planetinfo.record]: cloud_color = 0.535583, 0.914062, 0.710039, 1; 12:59:01.745 [planetinfo.record]: cloud_fraction = 0.430000; 12:59:01.745 [planetinfo.record]: land_color = 0.595703, 0.578905, 0.546837, 1; 12:59:01.745 [planetinfo.record]: land_fraction = 0.650000; 12:59:01.745 [planetinfo.record]: polar_cloud_color = 0.675486, 0.911328, 0.784195, 1; 12:59:01.745 [planetinfo.record]: polar_land_color = 0.94043, 0.9338, 0.921144, 1; 12:59:01.745 [planetinfo.record]: polar_sea_color = 0.844928, 0.912891, 0.806803, 1; 12:59:01.745 [planetinfo.record]: sea_color = 0.61169, 0.871094, 0.466171, 1; 12:59:01.745 [planetinfo.record]: rotation_speed = 0.001934 12:59:01.745 [planetinfo.record]: planet zpos = 786600.000000 12:59:01.745 [planetinfo.record]: sun_radius = 182039.879608 12:59:01.745 [planetinfo.record]: sun_vector = 0.663 0.078 -0.745 12:59:01.745 [planetinfo.record]: sun_distance = 1376550 12:59:01.745 [planetinfo.record]: corona_flare = 0.099594 12:59:01.745 [planetinfo.record]: corona_hues = 0.824371 12:59:01.745 [planetinfo.record]: sun_color = 0.673813, 0.439203, 0.434797, 1 12:59:01.746 [planetinfo.record]: corona_shimmer = 0.640275 12:59:01.746 [planetinfo.record]: station_vector = 0.757 0.539 0.368 12:59:01.746 [planetinfo.record]: station = coriolis 12:59:06.528 [PLANETINFO OVER]: Done 12:59:06.529 [PLANETINFO LOGGING]: 3 165 12:59:07.533 [planetinfo.record]: seed = 242 226 202 119 12:59:07.533 [planetinfo.record]: coordinates = 226 191 12:59:07.533 [planetinfo.record]: government = 6; 12:59:07.533 [planetinfo.record]: economy = 7; 12:59:07.533 [planetinfo.record]: techlevel = 5; 12:59:07.533 [planetinfo.record]: population = 34; 12:59:07.533 [planetinfo.record]: productivity = 8160; 12:59:07.533 [planetinfo.record]: name = "Tiorra"; 12:59:07.534 [planetinfo.record]: inhabitant = "Blue Furry Rodent"; 12:59:07.534 [planetinfo.record]: inhabitants = "Blue Furry Rodents"; 12:59:07.534 [planetinfo.record]: description = "The planet Tiorra is most noted for its inhabitants’ exceptional loathing of sit coms and Tiorraian wolf meat."; 12:59:07.551 [planetinfo.record]: air_color = 0.396304, 0.843557, 0.755814, 1; 12:59:07.551 [planetinfo.record]: cloud_alpha = 1.000000; 12:59:07.551 [planetinfo.record]: cloud_color = 0.533203, 0.227353, 0.00416565, 1; 12:59:07.551 [planetinfo.record]: cloud_fraction = 0.200000; 12:59:07.552 [planetinfo.record]: land_color = 0.66, 0.613312, 0.502734, 1; 12:59:07.552 [planetinfo.record]: land_fraction = 0.660000; 12:59:07.552 [planetinfo.record]: polar_cloud_color = 0.739941, 0.474669, 0.281091, 1; 12:59:07.552 [planetinfo.record]: polar_land_color = 0.934, 0.917482, 0.878361, 1; 12:59:07.552 [planetinfo.record]: polar_sea_color = 0.871545, 0.91933, 0.92168, 1; 12:59:07.552 [planetinfo.record]: sea_color = 0.612795, 0.775215, 0.783203, 1; 12:59:07.552 [planetinfo.record]: rotation_speed = 0.003213 12:59:07.552 [planetinfo.record]: planet zpos = 628420.000000 12:59:07.552 [planetinfo.record]: sun_radius = 127602.080688 12:59:07.552 [planetinfo.record]: sun_vector = -0.188 0.901 0.390 12:59:07.552 [planetinfo.record]: sun_distance = 1063480 12:59:07.552 [planetinfo.record]: corona_flare = 0.049043 12:59:07.552 [planetinfo.record]: corona_hues = 0.592415 12:59:07.552 [planetinfo.record]: sun_color = 0.812469, 0.69054, 0.221611, 1 12:59:07.552 [planetinfo.record]: corona_shimmer = 0.496974 12:59:07.553 [planetinfo.record]: station_vector = -0.954 0.300 0.019 12:59:07.553 [planetinfo.record]: station = coriolis 12:59:12.545 [PLANETINFO OVER]: Done 12:59:12.547 [PLANETINFO LOGGING]: 3 166 12:59:13.574 [planetinfo.record]: seed = 82 6 218 148 12:59:13.574 [planetinfo.record]: coordinates = 6 116 12:59:13.574 [planetinfo.record]: government = 2; 12:59:13.574 [planetinfo.record]: economy = 4; 12:59:13.574 [planetinfo.record]: techlevel = 6; 12:59:13.574 [planetinfo.record]: population = 31; 12:59:13.574 [planetinfo.record]: productivity = 8928; 12:59:13.574 [planetinfo.record]: name = "Raaar"; 12:59:13.574 [planetinfo.record]: inhabitant = "Black Horned Lizard"; 12:59:13.574 [planetinfo.record]: inhabitants = "Black Horned Lizards"; 12:59:13.575 [planetinfo.record]: description = "This planet is mildly noted for its pink Inresoxe tulip plantations but plagued by vicious disease."; 12:59:13.599 [planetinfo.record]: air_color = 0.437698, 0.750167, 0.862418, 1; 12:59:13.599 [planetinfo.record]: cloud_alpha = 1.000000; 12:59:13.599 [planetinfo.record]: cloud_color = 0.17268, 0.589844, 0.0714264, 1; 12:59:13.599 [planetinfo.record]: cloud_fraction = 0.380000; 12:59:13.599 [planetinfo.record]: land_color = 0.422813, 0.66, 0.521023, 1; 12:59:13.599 [planetinfo.record]: land_fraction = 0.350000; 12:59:13.600 [planetinfo.record]: polar_cloud_color = 0.427088, 0.76543, 0.344967, 1; 12:59:13.600 [planetinfo.record]: polar_land_color = 0.850086, 0.934, 0.884832, 1; 12:59:13.600 [planetinfo.record]: polar_sea_color = 0.885554, 0.931055, 0.88041, 1; 12:59:13.600 [planetinfo.record]: sea_color = 0.554679, 0.689453, 0.539443, 1; 12:59:13.600 [planetinfo.record]: rotation_speed = 0.003818 12:59:13.600 [planetinfo.record]: planet zpos = 461520.000000 12:59:13.600 [planetinfo.record]: sun_radius = 91539.682617 12:59:13.602 [planetinfo.record]: sun_vector = 0.986 -0.135 0.095 12:59:13.602 [planetinfo.record]: sun_distance = 615360 12:59:13.603 [planetinfo.record]: corona_flare = 0.031654 12:59:13.603 [planetinfo.record]: corona_hues = 0.696754 12:59:13.603 [planetinfo.record]: sun_color = 0.547786, 0.627537, 0.703809, 1 12:59:13.603 [planetinfo.record]: corona_shimmer = 0.477090 12:59:13.604 [planetinfo.record]: station_vector = 0.329 -0.163 0.930 12:59:13.604 [planetinfo.record]: station = coriolis 12:59:19.438 [PLANETINFO OVER]: Done 12:59:19.439 [PLANETINFO LOGGING]: 3 167 12:59:20.445 [planetinfo.record]: seed = 162 79 106 10 12:59:20.445 [planetinfo.record]: coordinates = 79 170 12:59:20.445 [planetinfo.record]: government = 4; 12:59:20.445 [planetinfo.record]: economy = 2; 12:59:20.445 [planetinfo.record]: techlevel = 10; 12:59:20.445 [planetinfo.record]: population = 47; 12:59:20.445 [planetinfo.record]: productivity = 24064; 12:59:20.446 [planetinfo.record]: name = "Arceonre"; 12:59:20.446 [planetinfo.record]: inhabitant = "Human Colonial"; 12:59:20.446 [planetinfo.record]: inhabitants = "Human Colonials"; 12:59:20.446 [planetinfo.record]: description = "The planet Arceonre is very famous for Arceonreian wolf cutlet but plagued by deadly earthquakes."; 12:59:20.462 [planetinfo.record]: air_color = 0.435395, 0.543225, 0.924205, 1; 12:59:20.462 [planetinfo.record]: cloud_alpha = 1.000000; 12:59:20.462 [planetinfo.record]: cloud_color = 0.00908661, 0.505987, 0.775391, 1; 12:59:20.462 [planetinfo.record]: cloud_fraction = 0.390000; 12:59:20.462 [planetinfo.record]: land_color = 0.579712, 0.3368, 0.638672, 1; 12:59:20.462 [planetinfo.record]: land_fraction = 0.650000; 12:59:20.462 [planetinfo.record]: polar_cloud_color = 0.324565, 0.66458, 0.848926, 1; 12:59:20.462 [planetinfo.record]: polar_land_color = 0.914528, 0.825516, 0.936133, 1; 12:59:20.463 [planetinfo.record]: polar_sea_color = 0.926953, 0.83665, 0.824662, 1; 12:59:20.463 [planetinfo.record]: sea_color = 0.730469, 0.44582, 0.408035, 1; 12:59:20.463 [planetinfo.record]: rotation_speed = 0.003766 12:59:20.463 [planetinfo.record]: planet zpos = 545500.000000 12:59:20.467 [planetinfo.record]: sun_radius = 143150.466919 12:59:20.468 [planetinfo.record]: sun_vector = 0.782 -0.174 -0.598 12:59:20.468 [planetinfo.record]: sun_distance = 1145550 12:59:20.468 [planetinfo.record]: corona_flare = 0.094116 12:59:20.468 [planetinfo.record]: corona_hues = 0.617851 12:59:20.468 [planetinfo.record]: sun_color = 0.780203, 0.752628, 0.366473, 1 12:59:20.468 [planetinfo.record]: corona_shimmer = 0.358799 12:59:20.468 [planetinfo.record]: station_vector = -0.651 0.737 0.182 12:59:20.468 [planetinfo.record]: station = coriolis 12:59:25.285 [PLANETINFO OVER]: Done 12:59:25.287 [PLANETINFO LOGGING]: 3 168 12:59:26.296 [planetinfo.record]: seed = 130 110 154 210 12:59:26.296 [planetinfo.record]: coordinates = 110 95 12:59:26.296 [planetinfo.record]: government = 0; 12:59:26.296 [planetinfo.record]: economy = 7; 12:59:26.296 [planetinfo.record]: techlevel = 2; 12:59:26.296 [planetinfo.record]: population = 16; 12:59:26.296 [planetinfo.record]: productivity = 1536; 12:59:26.296 [planetinfo.record]: name = "Enle"; 12:59:26.296 [planetinfo.record]: inhabitant = "Bug-Eyed Lobster"; 12:59:26.296 [planetinfo.record]: inhabitants = "Bug-Eyed Lobsters"; 12:59:26.296 [planetinfo.record]: description = "The planet Enle is famous for its inhabitants’ ancient loathing of casinos but ravaged by unpredictable civil war."; 12:59:26.302 [planetinfo.record]: air_color = 0.555514, 0.959326, 0.899016, 1; 12:59:26.302 [planetinfo.record]: cloud_alpha = 1.000000; 12:59:26.302 [planetinfo.record]: cloud_color = 0.880859, 0.624946, 0.295914, 1; 12:59:26.302 [planetinfo.record]: cloud_fraction = 0.380000; 12:59:26.302 [planetinfo.record]: land_color = 0.565496, 0.66, 0.304219, 1; 12:59:26.302 [planetinfo.record]: land_fraction = 0.400000; 12:59:26.302 [planetinfo.record]: polar_cloud_color = 0.896387, 0.733621, 0.524351, 1; 12:59:26.303 [planetinfo.record]: polar_land_color = 0.900566, 0.934, 0.808129, 1; 12:59:26.303 [planetinfo.record]: polar_sea_color = 0.894982, 0.932813, 0.882892, 1; 12:59:26.303 [planetinfo.record]: sea_color = 0.562884, 0.671875, 0.528052, 1; 12:59:26.303 [planetinfo.record]: rotation_speed = 0.003238 12:59:26.303 [planetinfo.record]: planet zpos = 343800.000000 12:59:26.303 [planetinfo.record]: sun_radius = 88782.824707 12:59:26.303 [planetinfo.record]: sun_vector = -0.391 0.915 -0.097 12:59:26.303 [planetinfo.record]: sun_distance = 618840 12:59:26.303 [planetinfo.record]: corona_flare = 0.086865 12:59:26.303 [planetinfo.record]: corona_hues = 0.686150 12:59:26.303 [planetinfo.record]: sun_color = 0.831659, 0.469475, 0.212274, 1 12:59:26.303 [planetinfo.record]: corona_shimmer = 0.241490 12:59:26.303 [planetinfo.record]: station_vector = 0.223 -0.507 0.833 12:59:26.304 [planetinfo.record]: station = coriolis 12:59:31.456 [PLANETINFO OVER]: Done 12:59:31.457 [PLANETINFO LOGGING]: 3 169 12:59:32.462 [planetinfo.record]: seed = 18 84 138 213 12:59:32.462 [planetinfo.record]: coordinates = 84 225 12:59:32.462 [planetinfo.record]: government = 2; 12:59:32.463 [planetinfo.record]: economy = 1; 12:59:32.463 [planetinfo.record]: techlevel = 7; 12:59:32.463 [planetinfo.record]: population = 32; 12:59:32.463 [planetinfo.record]: productivity = 13824; 12:59:32.463 [planetinfo.record]: name = "Laarrara"; 12:59:32.463 [planetinfo.record]: inhabitant = "Furry Feline"; 12:59:32.463 [planetinfo.record]: inhabitants = "Furry Felines"; 12:59:32.463 [planetinfo.record]: description = "Laarrara is cursed by dreadful civil war."; 12:59:32.476 [planetinfo.record]: air_color = 0.75622, 0.610102, 0.874775, 1; 12:59:32.476 [planetinfo.record]: cloud_alpha = 1.000000; 12:59:32.476 [planetinfo.record]: cloud_color = 0.626953, 0.433479, 0.496963, 1; 12:59:32.476 [planetinfo.record]: cloud_fraction = 0.100000; 12:59:32.476 [planetinfo.record]: land_color = 0.459309, 0.66, 0.417656, 1; 12:59:32.476 [planetinfo.record]: land_fraction = 0.650000; 12:59:32.476 [planetinfo.record]: polar_cloud_color = 0.782129, 0.631279, 0.680777, 1; 12:59:32.476 [planetinfo.record]: polar_land_color = 0.862998, 0.934, 0.848262, 1; 12:59:32.476 [planetinfo.record]: polar_sea_color = 0.877137, 0.925586, 0.91196, 1; 12:59:32.476 [planetinfo.record]: sea_color = 0.588336, 0.744141, 0.700321, 1; 12:59:32.476 [planetinfo.record]: rotation_speed = 0.001985 12:59:32.476 [planetinfo.record]: planet zpos = 501600.000000 12:59:32.476 [planetinfo.record]: sun_radius = 105919.781494 12:59:32.476 [planetinfo.record]: sun_vector = -0.321 -0.794 0.516 12:59:32.476 [planetinfo.record]: sun_distance = 794200 12:59:32.476 [planetinfo.record]: corona_flare = 0.083514 12:59:32.476 [planetinfo.record]: corona_hues = 0.626785 12:59:32.477 [planetinfo.record]: sun_color = 0.724625, 0.632332, 0.607474, 1 12:59:32.477 [planetinfo.record]: corona_shimmer = 0.470723 12:59:32.477 [planetinfo.record]: station_vector = -0.702 0.680 0.210 12:59:32.477 [planetinfo.record]: station = coriolis 12:59:36.942 [PLANETINFO OVER]: Done 12:59:36.943 [PLANETINFO LOGGING]: 3 170 12:59:37.946 [planetinfo.record]: seed = 242 20 90 84 12:59:37.946 [planetinfo.record]: coordinates = 20 52 12:59:37.946 [planetinfo.record]: government = 6; 12:59:37.946 [planetinfo.record]: economy = 4; 12:59:37.946 [planetinfo.record]: techlevel = 6; 12:59:37.946 [planetinfo.record]: population = 35; 12:59:37.946 [planetinfo.record]: productivity = 16800; 12:59:37.946 [planetinfo.record]: name = "Raisso"; 12:59:37.946 [planetinfo.record]: inhabitant = "Human Colonial"; 12:59:37.946 [planetinfo.record]: inhabitants = "Human Colonials"; 12:59:37.946 [planetinfo.record]: description = "The world Raisso is scourged by deadly disease."; 12:59:37.947 [planetinfo.record]: air_color = 0.602789, 0.854767, 0.981439, 1; 12:59:37.947 [planetinfo.record]: cloud_alpha = 1.000000; 12:59:37.948 [planetinfo.record]: cloud_color = 0.418129, 0.947266, 0.426397, 1; 12:59:37.948 [planetinfo.record]: cloud_fraction = 0.580000; 12:59:37.948 [planetinfo.record]: land_color = 0.66, 0.0386719, 0.315357, 1; 12:59:37.948 [planetinfo.record]: land_fraction = 0.330000; 12:59:37.948 [planetinfo.record]: polar_cloud_color = 0.602889, 0.92627, 0.607942, 1; 12:59:37.948 [planetinfo.record]: polar_land_color = 0.934, 0.714182, 0.81207, 1; 12:59:37.948 [planetinfo.record]: polar_sea_color = 0.939062, 0.904528, 0.881655, 1; 12:59:37.948 [planetinfo.record]: sea_color = 0.609375, 0.519735, 0.460364, 1; 12:59:37.948 [planetinfo.record]: rotation_speed = 0.000041 12:59:37.948 [planetinfo.record]: planet zpos = 501800.000000 12:59:37.948 [planetinfo.record]: sun_radius = 107866.314697 12:59:37.948 [planetinfo.record]: sun_vector = 0.303 0.892 0.335 12:59:37.948 [planetinfo.record]: sun_distance = 810600 12:59:37.948 [planetinfo.record]: corona_flare = 0.035617 12:59:37.948 [planetinfo.record]: corona_hues = 0.804939 12:59:37.948 [planetinfo.record]: sun_color = 0.762393, 0.645017, 0.488117, 1 12:59:37.948 [planetinfo.record]: corona_shimmer = 0.332907 12:59:37.948 [planetinfo.record]: station_vector = 0.173 -0.030 0.984 12:59:37.948 [planetinfo.record]: station = coriolis 12:59:42.239 [PLANETINFO OVER]: Done 12:59:42.240 [PLANETINFO LOGGING]: 3 171 12:59:43.243 [planetinfo.record]: seed = 66 249 42 158 12:59:43.243 [planetinfo.record]: coordinates = 249 7 12:59:43.243 [planetinfo.record]: government = 0; 12:59:43.243 [planetinfo.record]: economy = 7; 12:59:43.243 [planetinfo.record]: techlevel = 1; 12:59:43.243 [planetinfo.record]: population = 12; 12:59:43.243 [planetinfo.record]: productivity = 1152; 12:59:43.243 [planetinfo.record]: name = "Ririla"; 12:59:43.243 [planetinfo.record]: inhabitant = "Human Colonial"; 12:59:43.243 [planetinfo.record]: inhabitants = "Human Colonials"; 12:59:43.243 [planetinfo.record]: description = "The world Ririla is beset by deadly earthquakes."; 12:59:43.245 [planetinfo.record]: air_color = 0.703953, 0.498232, 0.954773, 1; 12:59:43.245 [planetinfo.record]: cloud_alpha = 1.000000; 12:59:43.245 [planetinfo.record]: cloud_color = 0.867188, 0.142273, 0.601008, 1; 12:59:43.245 [planetinfo.record]: cloud_fraction = 0.320000; 12:59:43.245 [planetinfo.record]: land_color = 0.170563, 0.539062, 0.305871, 1; 12:59:43.245 [planetinfo.record]: land_fraction = 0.310000; 12:59:43.245 [planetinfo.record]: polar_cloud_color = 0.890234, 0.425122, 0.719451, 1; 12:59:43.246 [planetinfo.record]: polar_land_color = 0.784408, 0.946094, 0.843777, 1; 12:59:43.246 [planetinfo.record]: polar_sea_color = 0.875227, 0.925, 0.908668, 1; 12:59:43.246 [planetinfo.record]: sea_color = 0.588574, 0.75, 0.697032, 1; 12:59:43.246 [planetinfo.record]: rotation_speed = 0.002610 12:59:43.246 [planetinfo.record]: planet zpos = 598410.000000 12:59:43.246 [planetinfo.record]: sun_radius = 118658.515015 12:59:43.246 [planetinfo.record]: sun_vector = 0.324 -0.831 0.453 12:59:43.246 [planetinfo.record]: sun_distance = 1462780 12:59:43.246 [planetinfo.record]: corona_flare = 0.050215 12:59:43.246 [planetinfo.record]: corona_hues = 0.887962 12:59:43.246 [planetinfo.record]: sun_color = 0.795874, 0.683059, 0.655218, 1 12:59:43.246 [planetinfo.record]: corona_shimmer = 0.538328 12:59:43.247 [planetinfo.record]: station_vector = -0.837 -0.233 0.496 12:59:43.247 [planetinfo.record]: station = coriolis 12:59:48.549 [PLANETINFO OVER]: Done 12:59:48.551 [PLANETINFO LOGGING]: 3 172 12:59:49.570 [planetinfo.record]: seed = 162 114 26 71 12:59:49.570 [planetinfo.record]: coordinates = 114 53 12:59:49.570 [planetinfo.record]: government = 4; 12:59:49.570 [planetinfo.record]: economy = 5; 12:59:49.570 [planetinfo.record]: techlevel = 6; 12:59:49.570 [planetinfo.record]: population = 34; 12:59:49.570 [planetinfo.record]: productivity = 10880; 12:59:49.570 [planetinfo.record]: name = "Soaes"; 12:59:49.570 [planetinfo.record]: inhabitant = "Human Colonial"; 12:59:49.570 [planetinfo.record]: inhabitants = "Human Colonials"; 12:59:49.570 [planetinfo.record]: description = "This planet is fabled for its fabulous Lole juice."; 12:59:49.582 [planetinfo.record]: air_color = 0.492184, 0.666962, 0.846158, 1; 12:59:49.582 [planetinfo.record]: cloud_alpha = 1.000000; 12:59:49.582 [planetinfo.record]: cloud_color = 0.185974, 0.541016, 0.37459, 1; 12:59:49.582 [planetinfo.record]: cloud_fraction = 0.320000; 12:59:49.582 [planetinfo.record]: land_color = 0.160034, 0.59375, 0.471767, 1; 12:59:49.582 [planetinfo.record]: land_fraction = 0.650000; 12:59:49.582 [planetinfo.record]: polar_cloud_color = 0.438523, 0.743457, 0.600519, 1; 12:59:49.582 [planetinfo.record]: polar_land_color = 0.768851, 0.940625, 0.892313, 1; 12:59:49.583 [planetinfo.record]: polar_sea_color = 0.932031, 0.928248, 0.871504, 1; 12:59:49.583 [planetinfo.record]: sea_color = 0.679688, 0.668653, 0.503128, 1; 12:59:49.583 [planetinfo.record]: rotation_speed = 0.004424 12:59:49.583 [planetinfo.record]: planet zpos = 424980.000000 12:59:49.583 [planetinfo.record]: sun_radius = 120782.932434 12:59:49.583 [planetinfo.record]: sun_vector = -0.919 -0.391 0.061 12:59:49.583 [planetinfo.record]: sun_distance = 802740 12:59:49.583 [planetinfo.record]: corona_flare = 0.031439 12:59:49.583 [planetinfo.record]: corona_hues = 0.889458 12:59:49.583 [planetinfo.record]: sun_color = 0.69256, 0.74413, 0.771649, 1 12:59:49.583 [planetinfo.record]: corona_shimmer = 0.393019 12:59:49.584 [planetinfo.record]: station_vector = 0.599 0.423 0.680 12:59:49.584 [planetinfo.record]: station = coriolis 12:59:53.983 [PLANETINFO OVER]: Done 12:59:53.985 [PLANETINFO LOGGING]: 3 173 12:59:55.002 [planetinfo.record]: seed = 50 224 74 121 12:59:55.002 [planetinfo.record]: coordinates = 224 169 12:59:55.002 [planetinfo.record]: government = 6; 12:59:55.002 [planetinfo.record]: economy = 1; 12:59:55.002 [planetinfo.record]: techlevel = 9; 12:59:55.002 [planetinfo.record]: population = 44; 12:59:55.002 [planetinfo.record]: productivity = 31680; 12:59:55.002 [planetinfo.record]: name = "Orxeteed"; 12:59:55.002 [planetinfo.record]: inhabitant = "Human Colonial"; 12:59:55.002 [planetinfo.record]: inhabitants = "Human Colonials"; 12:59:55.002 [planetinfo.record]: description = "The planet Orxeteed is most noted for the Orxeteedian evil arts graduate and its exotic night life."; 12:59:55.006 [planetinfo.record]: air_color = 0.571861, 0.914786, 0.985342, 1; 12:59:55.006 [planetinfo.record]: cloud_alpha = 1.000000; 12:59:55.006 [planetinfo.record]: cloud_color = 0.627607, 0.958984, 0.325905, 1; 12:59:55.006 [planetinfo.record]: cloud_fraction = 0.430000; 12:59:55.006 [planetinfo.record]: land_color = 0.390928, 0.0953906, 0.66, 1; 12:59:55.006 [planetinfo.record]: land_fraction = 0.650000; 12:59:55.006 [planetinfo.record]: polar_cloud_color = 0.730358, 0.931543, 0.547191, 1; 12:59:55.006 [planetinfo.record]: polar_land_color = 0.838806, 0.734248, 0.934, 1; 12:59:55.006 [planetinfo.record]: polar_sea_color = 0.935352, 0.908896, 0.874883, 1; 12:59:55.006 [planetinfo.record]: sea_color = 0.646484, 0.573345, 0.479308, 1; 12:59:55.006 [planetinfo.record]: rotation_speed = 0.000714 12:59:55.007 [planetinfo.record]: planet zpos = 694720.000000 12:59:55.007 [planetinfo.record]: sun_radius = 181105.302734 12:59:55.007 [planetinfo.record]: sun_vector = 0.900 -0.192 -0.390 12:59:55.007 [planetinfo.record]: sun_distance = 961920 12:59:55.007 [planetinfo.record]: corona_flare = 0.017130 12:59:55.007 [planetinfo.record]: corona_hues = 0.714996 12:59:55.007 [planetinfo.record]: sun_color = 0.268362, 0.488105, 0.748804, 1 12:59:55.007 [planetinfo.record]: corona_shimmer = 0.322879 12:59:55.007 [planetinfo.record]: station_vector = -0.899 -0.410 0.152 12:59:55.007 [planetinfo.record]: station = coriolis 12:59:59.257 [PLANETINFO OVER]: Done 12:59:59.259 [PLANETINFO LOGGING]: 3 174 13:00:00.261 [planetinfo.record]: seed = 146 216 218 55 13:00:00.261 [planetinfo.record]: coordinates = 216 92 13:00:00.261 [planetinfo.record]: government = 2; 13:00:00.261 [planetinfo.record]: economy = 4; 13:00:00.261 [planetinfo.record]: techlevel = 4; 13:00:00.261 [planetinfo.record]: population = 23; 13:00:00.261 [planetinfo.record]: productivity = 6624; 13:00:00.261 [planetinfo.record]: name = "Tiinisle"; 13:00:00.261 [planetinfo.record]: inhabitant = "Red Fat Insect"; 13:00:00.261 [planetinfo.record]: inhabitants = "Red Fat Insects"; 13:00:00.261 [planetinfo.record]: description = "Tiinisle is a revolting dump."; 13:00:00.293 [planetinfo.record]: air_color = 0.705326, 0.795274, 0.934611, 1; 13:00:00.293 [planetinfo.record]: cloud_alpha = 1.000000; 13:00:00.293 [planetinfo.record]: cloud_color = 0.696358, 0.806641, 0.788547, 1; 13:00:00.293 [planetinfo.record]: cloud_fraction = 0.330000; 13:00:00.293 [planetinfo.record]: land_color = 0.59458, 0.66, 0.585234, 1; 13:00:00.293 [planetinfo.record]: land_fraction = 0.390000; 13:00:00.293 [planetinfo.record]: polar_cloud_color = 0.789247, 0.862988, 0.85089, 1; 13:00:00.293 [planetinfo.record]: polar_land_color = 0.910855, 0.934, 0.907549, 1; 13:00:00.293 [planetinfo.record]: polar_sea_color = 0.873635, 0.919028, 0.922461, 1; 13:00:00.293 [planetinfo.record]: sea_color = 0.611226, 0.763848, 0.775391, 1; 13:00:00.293 [planetinfo.record]: rotation_speed = 0.001308 13:00:00.293 [planetinfo.record]: planet zpos = 675360.000000 13:00:00.293 [planetinfo.record]: sun_radius = 147946.992188 13:00:00.293 [planetinfo.record]: sun_vector = 0.385 0.005 -0.923 13:00:00.293 [planetinfo.record]: sun_distance = 1061280 13:00:00.293 [planetinfo.record]: corona_flare = 0.061705 13:00:00.293 [planetinfo.record]: corona_hues = 0.935593 13:00:00.293 [planetinfo.record]: sun_color = 0.195656, 0.412072, 0.731741, 1 13:00:00.294 [planetinfo.record]: corona_shimmer = 0.685537 13:00:00.294 [planetinfo.record]: station_vector = 0.996 -0.010 0.092 13:00:00.294 [planetinfo.record]: station = coriolis 13:00:05.553 [PLANETINFO OVER]: Done 13:00:05.555 [PLANETINFO LOGGING]: 3 175 13:00:06.575 [planetinfo.record]: seed = 226 33 234 11 13:00:06.575 [planetinfo.record]: coordinates = 33 125 13:00:06.575 [planetinfo.record]: government = 4; 13:00:06.575 [planetinfo.record]: economy = 5; 13:00:06.575 [planetinfo.record]: techlevel = 5; 13:00:06.575 [planetinfo.record]: population = 30; 13:00:06.575 [planetinfo.record]: productivity = 9600; 13:00:06.575 [planetinfo.record]: name = "Mamaed"; 13:00:06.575 [planetinfo.record]: inhabitant = "Small Green Fat Insect"; 13:00:06.575 [planetinfo.record]: inhabitants = "Small Green Fat Insects"; 13:00:06.575 [planetinfo.record]: description = "Mamaed is most famous for the Mamaedian deadly lobstoid and the Mamaedian spotted shrew."; 13:00:06.580 [planetinfo.record]: air_color = 0.637402, 0.885832, 0.840972, 1; 13:00:06.580 [planetinfo.record]: cloud_alpha = 1.000000; 13:00:06.580 [planetinfo.record]: cloud_color = 0.660156, 0.575219, 0.500275, 1; 13:00:06.581 [planetinfo.record]: cloud_fraction = 0.440000; 13:00:06.581 [planetinfo.record]: land_color = 0.623047, 0.386971, 0.580627, 1; 13:00:06.581 [planetinfo.record]: land_fraction = 0.690000; 13:00:06.581 [planetinfo.record]: polar_cloud_color = 0.79707, 0.732975, 0.67642, 1; 13:00:06.581 [planetinfo.record]: polar_land_color = 0.937695, 0.848871, 0.921735, 1; 13:00:06.581 [planetinfo.record]: polar_sea_color = 0.92168, 0.911244, 0.826271, 1; 13:00:06.581 [planetinfo.record]: sea_color = 0.783203, 0.747733, 0.458908, 1; 13:00:06.581 [planetinfo.record]: rotation_speed = 0.001260 13:00:06.581 [planetinfo.record]: planet zpos = 736450.000000 13:00:06.582 [planetinfo.record]: sun_radius = 136022.756195 13:00:06.583 [planetinfo.record]: sun_vector = 0.158 -0.602 0.783 13:00:06.583 [planetinfo.record]: sun_distance = 1133000 13:00:06.583 [planetinfo.record]: corona_flare = 0.072437 13:00:06.583 [planetinfo.record]: corona_hues = 0.644226 13:00:06.583 [planetinfo.record]: sun_color = 0.547943, 0.616172, 0.82439, 1 13:00:06.583 [planetinfo.record]: corona_shimmer = 0.282712 13:00:06.584 [planetinfo.record]: station_vector = 0.530 0.584 0.615 13:00:06.584 [planetinfo.record]: station = coriolis 13:00:11.035 [PLANETINFO OVER]: Done 13:00:11.037 [PLANETINFO LOGGING]: 3 176 13:00:12.040 [planetinfo.record]: seed = 194 143 154 19 13:00:12.040 [planetinfo.record]: coordinates = 143 216 13:00:12.040 [planetinfo.record]: government = 0; 13:00:12.040 [planetinfo.record]: economy = 2; 13:00:12.040 [planetinfo.record]: techlevel = 8; 13:00:12.041 [planetinfo.record]: population = 35; 13:00:12.041 [planetinfo.record]: productivity = 8960; 13:00:12.041 [planetinfo.record]: name = "Beteona"; 13:00:12.041 [planetinfo.record]: inhabitant = "Green Lizard"; 13:00:12.041 [planetinfo.record]: inhabitants = "Green Lizards"; 13:00:12.041 [planetinfo.record]: description = "This planet is fabled for its exciting sit coms."; 13:00:12.057 [planetinfo.record]: air_color = 0.450984, 0.750328, 0.84941, 1; 13:00:12.057 [planetinfo.record]: cloud_alpha = 1.000000; 13:00:12.057 [planetinfo.record]: cloud_color = 0.213283, 0.550781, 0.105423, 1; 13:00:12.057 [planetinfo.record]: cloud_fraction = 0.380000; 13:00:12.057 [planetinfo.record]: land_color = 0.605859, 0.66, 0.615588, 1; 13:00:12.057 [planetinfo.record]: land_fraction = 0.430000; 13:00:12.057 [planetinfo.record]: polar_cloud_color = 0.461442, 0.747852, 0.369909, 1; 13:00:12.058 [planetinfo.record]: polar_land_color = 0.914846, 0.934, 0.918288, 1; 13:00:12.058 [planetinfo.record]: polar_sea_color = 0.875828, 0.922512, 0.923633, 1; 13:00:12.058 [planetinfo.record]: sea_color = 0.605568, 0.759966, 0.763672, 1; 13:00:12.058 [planetinfo.record]: rotation_speed = 0.000655 13:00:12.058 [planetinfo.record]: planet zpos = 484510.000000 13:00:12.058 [planetinfo.record]: sun_radius = 91404.652252 13:00:12.058 [planetinfo.record]: sun_vector = 0.819 0.564 -0.106 13:00:12.058 [planetinfo.record]: sun_distance = 708130 13:00:12.058 [planetinfo.record]: corona_flare = 0.074715 13:00:12.058 [planetinfo.record]: corona_hues = 0.570068 13:00:12.058 [planetinfo.record]: sun_color = 0.77038, 0.555443, 0.394109, 1 13:00:12.058 [planetinfo.record]: corona_shimmer = 0.473157 13:00:12.061 [planetinfo.record]: station_vector = 0.934 0.054 0.352 13:00:12.061 [planetinfo.record]: station = coriolis 13:00:17.157 [PLANETINFO OVER]: Done 13:00:17.158 [PLANETINFO LOGGING]: 3 177 13:00:18.162 [planetinfo.record]: seed = 82 175 10 75 13:00:18.162 [planetinfo.record]: coordinates = 175 31 13:00:18.162 [planetinfo.record]: government = 2; 13:00:18.162 [planetinfo.record]: economy = 7; 13:00:18.162 [planetinfo.record]: techlevel = 4; 13:00:18.162 [planetinfo.record]: population = 26; 13:00:18.162 [planetinfo.record]: productivity = 3744; 13:00:18.162 [planetinfo.record]: name = "Maorra"; 13:00:18.162 [planetinfo.record]: inhabitant = "Human Colonial"; 13:00:18.163 [planetinfo.record]: inhabitants = "Human Colonials"; 13:00:18.163 [planetinfo.record]: description = "This world is reasonably notable for its exotic night life but ravaged by frequent earthquakes."; 13:00:18.184 [planetinfo.record]: air_color = 0.692831, 0.617415, 0.838354, 1; 13:00:18.188 [planetinfo.record]: cloud_alpha = 1.000000; 13:00:18.188 [planetinfo.record]: cloud_color = 0.517578, 0.41851, 0.513708, 1; 13:00:18.188 [planetinfo.record]: cloud_fraction = 0.370000; 13:00:18.188 [planetinfo.record]: land_color = 0.1697, 0.582031, 0.0841217, 1; 13:00:18.188 [planetinfo.record]: land_fraction = 0.340000; 13:00:18.188 [planetinfo.record]: polar_cloud_color = 0.73291, 0.645233, 0.729485, 1; 13:00:18.188 [planetinfo.record]: polar_land_color = 0.774996, 0.941797, 0.740377, 1; 13:00:18.188 [planetinfo.record]: polar_sea_color = 0.939453, 0.886322, 0.879269, 1; 13:00:18.188 [planetinfo.record]: sea_color = 0.605469, 0.468499, 0.450317, 1; 13:00:18.188 [planetinfo.record]: rotation_speed = 0.004435 13:00:18.188 [planetinfo.record]: planet zpos = 696840.000000 13:00:18.188 [planetinfo.record]: sun_radius = 135261.560669 13:00:18.188 [planetinfo.record]: sun_vector = -0.776 0.619 0.119 13:00:18.188 [planetinfo.record]: sun_distance = 1161400 13:00:18.188 [planetinfo.record]: corona_flare = 0.032430 13:00:18.188 [planetinfo.record]: corona_hues = 0.788460 13:00:18.189 [planetinfo.record]: sun_color = 0.680441, 0.620841, 0.613216, 1 13:00:18.189 [planetinfo.record]: corona_shimmer = 0.404393 13:00:18.189 [planetinfo.record]: station_vector = 0.815 0.526 0.245 13:00:18.189 [planetinfo.record]: station = coriolis 13:00:22.902 [PLANETINFO OVER]: Done 13:00:22.903 [PLANETINFO LOGGING]: 3 178 13:00:23.908 [planetinfo.record]: seed = 50 121 90 167 13:00:23.908 [planetinfo.record]: coordinates = 121 20 13:00:23.908 [planetinfo.record]: government = 6; 13:00:23.908 [planetinfo.record]: economy = 4; 13:00:23.908 [planetinfo.record]: techlevel = 7; 13:00:23.908 [planetinfo.record]: population = 39; 13:00:23.908 [planetinfo.record]: productivity = 18720; 13:00:23.908 [planetinfo.record]: name = "Soralaat"; 13:00:23.908 [planetinfo.record]: inhabitant = "Human Colonial"; 13:00:23.908 [planetinfo.record]: inhabitants = "Human Colonials"; 13:00:23.908 [planetinfo.record]: description = "Soralaat is reasonably notable for its unusual oceans but plagued by unpredictable civil war."; 13:00:23.912 [planetinfo.record]: air_color = 0.658608, 0.890859, 0.894287, 1; 13:00:23.913 [planetinfo.record]: cloud_alpha = 1.000000; 13:00:23.913 [planetinfo.record]: cloud_color = 0.678371, 0.685547, 0.554329, 1; 13:00:23.913 [planetinfo.record]: cloud_fraction = 0.370000; 13:00:23.913 [planetinfo.record]: land_color = 0.66, 0.407344, 0.50209, 1; 13:00:23.913 [planetinfo.record]: land_fraction = 0.560000; 13:00:23.913 [planetinfo.record]: polar_cloud_color = 0.803207, 0.808496, 0.711777, 1; 13:00:23.913 [planetinfo.record]: polar_land_color = 0.934, 0.844613, 0.878133, 1; 13:00:23.913 [planetinfo.record]: polar_sea_color = 0.902061, 0.932227, 0.879334, 1; 13:00:23.913 [planetinfo.record]: sea_color = 0.590012, 0.677734, 0.52392, 1; 13:00:23.914 [planetinfo.record]: rotation_speed = 0.002595 13:00:23.914 [planetinfo.record]: planet zpos = 567480.000000 13:00:23.914 [planetinfo.record]: sun_radius = 114422.952118 13:00:23.914 [planetinfo.record]: sun_vector = 0.738 0.674 -0.046 13:00:23.914 [planetinfo.record]: sun_distance = 1087670 13:00:23.914 [planetinfo.record]: corona_flare = 0.062700 13:00:23.914 [planetinfo.record]: corona_hues = 0.648598 13:00:23.914 [planetinfo.record]: sun_color = 0.797354, 0.737243, 0.710001, 1 13:00:23.914 [planetinfo.record]: corona_shimmer = 0.387141 13:00:23.915 [planetinfo.record]: station_vector = 0.376 0.879 0.295 13:00:23.915 [planetinfo.record]: station = coriolis 13:00:28.452 [PLANETINFO OVER]: Done 13:00:28.453 [PLANETINFO LOGGING]: 3 179 13:00:29.458 [planetinfo.record]: seed = 130 49 170 187 13:00:29.458 [planetinfo.record]: coordinates = 49 85 13:00:29.458 [planetinfo.record]: government = 0; 13:00:29.458 [planetinfo.record]: economy = 7; 13:00:29.458 [planetinfo.record]: techlevel = 1; 13:00:29.458 [planetinfo.record]: population = 12; 13:00:29.458 [planetinfo.record]: productivity = 1152; 13:00:29.458 [planetinfo.record]: name = "Anxeadi"; 13:00:29.458 [planetinfo.record]: inhabitant = "Harmless Fat Insect"; 13:00:29.458 [planetinfo.record]: inhabitants = "Harmless Fat Insects"; 13:00:29.458 [planetinfo.record]: description = "This world is very fabled for its unusual oceans."; 13:00:29.488 [planetinfo.record]: air_color = 0.481479, 0.650861, 0.860467, 1; 13:00:29.488 [planetinfo.record]: cloud_alpha = 1.000000; 13:00:29.488 [planetinfo.record]: cloud_color = 0.161964, 0.583984, 0.445509, 1; 13:00:29.488 [planetinfo.record]: cloud_fraction = 0.180000; 13:00:29.488 [planetinfo.record]: land_color = 0.335136, 0.66, 0.332578, 1; 13:00:29.488 [planetinfo.record]: land_fraction = 0.680000; 13:00:29.488 [planetinfo.record]: polar_cloud_color = 0.41827, 0.762793, 0.649746, 1; 13:00:29.488 [planetinfo.record]: polar_land_color = 0.819067, 0.934, 0.818162, 1; 13:00:29.488 [planetinfo.record]: polar_sea_color = 0.931445, 0.901257, 0.865953, 1; 13:00:29.488 [planetinfo.record]: sea_color = 0.685547, 0.596673, 0.492737, 1; 13:00:29.488 [planetinfo.record]: rotation_speed = 0.000093 13:00:29.488 [planetinfo.record]: planet zpos = 568100.000000 13:00:29.488 [planetinfo.record]: sun_radius = 149824.065704 13:00:29.488 [planetinfo.record]: sun_vector = 0.969 0.066 0.238 13:00:29.488 [planetinfo.record]: sun_distance = 1022580 13:00:29.488 [planetinfo.record]: corona_flare = 0.068877 13:00:29.488 [planetinfo.record]: corona_hues = 0.510872 13:00:29.488 [planetinfo.record]: sun_color = 0.705579, 0.682206, 0.672884, 1 13:00:29.488 [planetinfo.record]: corona_shimmer = 0.385960 13:00:29.489 [planetinfo.record]: station_vector = -0.900 -0.336 0.278 13:00:29.489 [planetinfo.record]: station = coriolis 13:00:34.264 [PLANETINFO OVER]: Done 13:00:34.265 [PLANETINFO LOGGING]: 3 180 13:00:35.286 [planetinfo.record]: seed = 226 45 26 160 13:00:35.286 [planetinfo.record]: coordinates = 45 47 13:00:35.286 [planetinfo.record]: government = 4; 13:00:35.286 [planetinfo.record]: economy = 7; 13:00:35.286 [planetinfo.record]: techlevel = 3; 13:00:35.286 [planetinfo.record]: population = 24; 13:00:35.286 [planetinfo.record]: productivity = 4608; 13:00:35.286 [planetinfo.record]: name = "Isma"; 13:00:35.286 [planetinfo.record]: inhabitant = "Human Colonial"; 13:00:35.286 [planetinfo.record]: inhabitants = "Human Colonials"; 13:00:35.286 [planetinfo.record]: description = "This world is a tedious little planet."; 13:00:35.289 [planetinfo.record]: air_color = 0.453747, 0.672164, 0.876076, 1; 13:00:35.289 [planetinfo.record]: cloud_alpha = 1.000000; 13:00:35.289 [planetinfo.record]: cloud_color = 0.0961075, 0.630859, 0.342595, 1; 13:00:35.290 [planetinfo.record]: cloud_fraction = 0.530000; 13:00:35.290 [planetinfo.record]: land_color = 0.0515625, 0.66, 0.389055, 1; 13:00:35.290 [planetinfo.record]: land_fraction = 0.370000; 13:00:35.290 [planetinfo.record]: polar_cloud_color = 0.368595, 0.783887, 0.560019, 1; 13:00:35.290 [planetinfo.record]: polar_land_color = 0.718742, 0.934, 0.838143, 1; 13:00:35.290 [planetinfo.record]: polar_sea_color = 0.873306, 0.923828, 0.911987, 1; 13:00:35.290 [planetinfo.record]: sea_color = 0.595093, 0.761719, 0.722666, 1; 13:00:35.290 [planetinfo.record]: rotation_speed = 0.001954 13:00:35.290 [planetinfo.record]: planet zpos = 286100.000000 13:00:35.290 [planetinfo.record]: sun_radius = 94009.274902 13:00:35.290 [planetinfo.record]: sun_vector = -0.329 -0.829 -0.453 13:00:35.290 [planetinfo.record]: sun_distance = 658030 13:00:35.290 [planetinfo.record]: corona_flare = 0.063397 13:00:35.290 [planetinfo.record]: corona_hues = 0.783775 13:00:35.290 [planetinfo.record]: sun_color = 0.473941, 0.606049, 0.720822, 1 13:00:35.290 [planetinfo.record]: corona_shimmer = 0.272914 13:00:35.290 [planetinfo.record]: station_vector = 0.568 -0.142 0.810 13:00:35.290 [planetinfo.record]: station = coriolis 13:00:39.570 [PLANETINFO OVER]: Done 13:00:39.571 [PLANETINFO LOGGING]: 3 181 13:00:40.574 [planetinfo.record]: seed = 114 105 202 50 13:00:40.574 [planetinfo.record]: coordinates = 105 203 13:00:40.574 [planetinfo.record]: government = 6; 13:00:40.574 [planetinfo.record]: economy = 3; 13:00:40.574 [planetinfo.record]: techlevel = 8; 13:00:40.574 [planetinfo.record]: population = 42; 13:00:40.574 [planetinfo.record]: productivity = 23520; 13:00:40.574 [planetinfo.record]: name = "Ensoza"; 13:00:40.574 [planetinfo.record]: inhabitant = "Red Horned Bird"; 13:00:40.574 [planetinfo.record]: inhabitants = "Red Horned Birds"; 13:00:40.574 [planetinfo.record]: description = "This planet is reasonably famous for the Ensozaian spotted cat."; 13:00:40.596 [planetinfo.record]: air_color = 0.690425, 0.476386, 0.968432, 1; 13:00:40.596 [planetinfo.record]: cloud_alpha = 1.000000; 13:00:40.596 [planetinfo.record]: cloud_color = 0.908203, 0.0674057, 0.638885, 1; 13:00:40.596 [planetinfo.record]: cloud_fraction = 0.470000; 13:00:40.596 [planetinfo.record]: land_color = 0.66, 0.0747656, 0.40396, 1; 13:00:40.596 [planetinfo.record]: land_fraction = 0.360000; 13:00:40.596 [planetinfo.record]: polar_cloud_color = 0.908691, 0.38291, 0.740277, 1; 13:00:40.596 [planetinfo.record]: polar_land_color = 0.934, 0.726951, 0.843416, 1; 13:00:40.596 [planetinfo.record]: polar_sea_color = 0.938281, 0.924628, 0.88367, 1; 13:00:40.596 [planetinfo.record]: sea_color = 0.617188, 0.581265, 0.473499, 1; 13:00:40.596 [planetinfo.record]: rotation_speed = 0.003164 13:00:40.596 [planetinfo.record]: planet zpos = 480620.000000 13:00:40.596 [planetinfo.record]: sun_radius = 112089.524384 13:00:40.596 [planetinfo.record]: sun_vector = 0.180 -0.878 -0.443 13:00:40.596 [planetinfo.record]: sun_distance = 617940 13:00:40.596 [planetinfo.record]: corona_flare = 0.030565 13:00:40.596 [planetinfo.record]: corona_hues = 0.691254 13:00:40.596 [planetinfo.record]: sun_color = 0.80517, 0.556424, 0.249401, 1 13:00:40.597 [planetinfo.record]: corona_shimmer = 0.330980 13:00:40.597 [planetinfo.record]: station_vector = 0.660 0.603 0.447 13:00:40.597 [planetinfo.record]: station = coriolis 13:00:45.377 [PLANETINFO OVER]: Done 13:00:45.380 [PLANETINFO LOGGING]: 3 182 13:00:46.391 [planetinfo.record]: seed = 210 158 218 10 13:00:46.392 [planetinfo.record]: coordinates = 158 4 13:00:46.392 [planetinfo.record]: government = 2; 13:00:46.393 [planetinfo.record]: economy = 4; 13:00:46.393 [planetinfo.record]: techlevel = 6; 13:00:46.393 [planetinfo.record]: population = 31; 13:00:46.393 [planetinfo.record]: productivity = 8928; 13:00:46.393 [planetinfo.record]: name = "Arditi"; 13:00:46.393 [planetinfo.record]: inhabitant = "Small Green Horned Bird"; 13:00:46.393 [planetinfo.record]: inhabitants = "Small Green Horned Birds"; 13:00:46.393 [planetinfo.record]: description = "This planet is notable for its great volcanoes and its inhabitants’ eccentric shyness."; 13:00:46.407 [planetinfo.record]: air_color = 0.566361, 0.958676, 0.927639, 1; 13:00:46.407 [planetinfo.record]: cloud_alpha = 1.000000; 13:00:46.407 [planetinfo.record]: cloud_color = 0.878906, 0.753674, 0.326157, 1; 13:00:46.407 [planetinfo.record]: cloud_fraction = 0.190000; 13:00:46.408 [planetinfo.record]: land_color = 0.170559, 0.0335156, 0.66, 1; 13:00:46.408 [planetinfo.record]: land_fraction = 0.490000; 13:00:46.408 [planetinfo.record]: polar_cloud_color = 0.895508, 0.815759, 0.543514, 1; 13:00:46.408 [planetinfo.record]: polar_land_color = 0.760842, 0.712357, 0.934, 1; 13:00:46.408 [planetinfo.record]: polar_sea_color = 0.942969, 0.933336, 0.895544, 1; 13:00:46.408 [planetinfo.record]: sea_color = 0.570312, 0.547008, 0.455582, 1; 13:00:46.408 [planetinfo.record]: rotation_speed = 0.003778 13:00:46.408 [planetinfo.record]: planet zpos = 719420.000000 13:00:46.408 [planetinfo.record]: sun_radius = 104381.460876 13:00:46.409 [planetinfo.record]: sun_vector = -0.510 -0.575 -0.640 13:00:46.409 [planetinfo.record]: sun_distance = 996120 13:00:46.409 [planetinfo.record]: corona_flare = 0.051352 13:00:46.409 [planetinfo.record]: corona_hues = 0.702988 13:00:46.409 [planetinfo.record]: sun_color = 0.693045, 0.647307, 0.284894, 1 13:00:46.409 [planetinfo.record]: corona_shimmer = 0.279536 13:00:46.409 [planetinfo.record]: station_vector = -0.915 0.400 0.049 13:00:46.409 [planetinfo.record]: station = coriolis 13:00:51.297 [PLANETINFO OVER]: Done 13:00:51.297 [PLANETINFO LOGGING]: 3 183 13:00:52.299 [planetinfo.record]: seed = 34 16 106 21 13:00:52.299 [planetinfo.record]: coordinates = 16 87 13:00:52.299 [planetinfo.record]: government = 4; 13:00:52.300 [planetinfo.record]: economy = 7; 13:00:52.300 [planetinfo.record]: techlevel = 2; 13:00:52.300 [planetinfo.record]: population = 20; 13:00:52.300 [planetinfo.record]: productivity = 3840; 13:00:52.300 [planetinfo.record]: name = "Laisxela"; 13:00:52.300 [planetinfo.record]: inhabitant = "Human Colonial"; 13:00:52.300 [planetinfo.record]: inhabitants = "Human Colonials"; 13:00:52.300 [planetinfo.record]: description = "Laisxela is mildly well known for its hoopy night life and its exotic cuisine."; 13:00:52.340 [planetinfo.record]: air_color = 0.676713, 0.719262, 0.992496, 1; 13:00:52.340 [planetinfo.record]: cloud_alpha = 1.000000; 13:00:52.340 [planetinfo.record]: cloud_color = 0.631943, 0.768086, 0.980469, 1; 13:00:52.340 [planetinfo.record]: cloud_fraction = 0.380000; 13:00:52.340 [planetinfo.record]: land_color = 0.66, 0.250078, 0.624772, 1; 13:00:52.340 [planetinfo.record]: land_fraction = 0.610000; 13:00:52.340 [planetinfo.record]: polar_cloud_color = 0.732104, 0.813786, 0.941211, 1; 13:00:52.340 [planetinfo.record]: polar_land_color = 0.934, 0.788975, 0.921537, 1; 13:00:52.340 [planetinfo.record]: polar_sea_color = 0.933203, 0.904952, 0.874878, 1; 13:00:52.340 [planetinfo.record]: sea_color = 0.667969, 0.587082, 0.500977, 1; 13:00:52.340 [planetinfo.record]: rotation_speed = 0.003762 13:00:52.340 [planetinfo.record]: planet zpos = 534560.000000 13:00:52.340 [planetinfo.record]: sun_radius = 88601.628418 13:00:52.341 [planetinfo.record]: sun_vector = -0.323 -0.195 -0.926 13:00:52.341 [planetinfo.record]: sun_distance = 740160 13:00:52.341 [planetinfo.record]: corona_flare = 0.058189 13:00:52.341 [planetinfo.record]: corona_hues = 0.683807 13:00:52.341 [planetinfo.record]: sun_color = 0.765253, 0.648595, 0.564957, 1 13:00:52.341 [planetinfo.record]: corona_shimmer = 0.642976 13:00:52.341 [planetinfo.record]: station_vector = 0.939 -0.339 0.056 13:00:52.341 [planetinfo.record]: station = coriolis 13:00:56.994 [PLANETINFO OVER]: Done 13:00:56.995 [PLANETINFO LOGGING]: 3 184 13:00:57.998 [planetinfo.record]: seed = 2 53 154 84 13:00:57.998 [planetinfo.record]: coordinates = 53 162 13:00:57.998 [planetinfo.record]: government = 0; 13:00:57.998 [planetinfo.record]: economy = 2; 13:00:57.998 [planetinfo.record]: techlevel = 6; 13:00:57.998 [planetinfo.record]: population = 27; 13:00:57.998 [planetinfo.record]: productivity = 6912; 13:00:57.998 [planetinfo.record]: name = "Rainla"; 13:00:57.998 [planetinfo.record]: inhabitant = "Yellow Insect"; 13:00:57.998 [planetinfo.record]: inhabitants = "Yellow Insects"; 13:00:57.998 [planetinfo.record]: description = "The world Rainla is mildly fabled for the Rainlaian mountain slug but ravaged by killer disease."; 13:00:58.012 [planetinfo.record]: air_color = 0.45711, 0.764372, 0.838354, 1; 13:00:58.012 [planetinfo.record]: cloud_alpha = 1.000000; 13:00:58.012 [planetinfo.record]: cloud_color = 0.282292, 0.517578, 0.121307, 1; 13:00:58.012 [planetinfo.record]: cloud_fraction = 0.430000; 13:00:58.012 [planetinfo.record]: land_color = 0.533203, 0.424896, 0.51628, 1; 13:00:58.013 [planetinfo.record]: land_fraction = 0.650000; 13:00:58.013 [planetinfo.record]: polar_cloud_color = 0.524677, 0.73291, 0.382201, 1; 13:00:58.013 [planetinfo.record]: polar_land_color = 0.94668, 0.898606, 0.939168, 1; 13:00:58.013 [planetinfo.record]: polar_sea_color = 0.869298, 0.919336, 0.841228, 1; 13:00:58.013 [planetinfo.record]: sea_color = 0.631025, 0.806641, 0.532509, 1; 13:00:58.013 [planetinfo.record]: rotation_speed = 0.003171 13:00:58.013 [planetinfo.record]: planet zpos = 428230.000000 13:00:58.013 [planetinfo.record]: sun_radius = 86048.035889 13:00:58.013 [planetinfo.record]: sun_vector = 0.988 0.151 -0.025 13:00:58.013 [planetinfo.record]: sun_distance = 739670 13:00:58.013 [planetinfo.record]: corona_flare = 0.041353 13:00:58.013 [planetinfo.record]: corona_hues = 0.934464 13:00:58.013 [planetinfo.record]: sun_color = 0.69749, 0.736481, 0.849676, 1 13:00:58.013 [planetinfo.record]: corona_shimmer = 0.410791 13:00:58.014 [planetinfo.record]: station_vector = 0.140 0.747 0.650 13:00:58.014 [planetinfo.record]: station = coriolis 13:01:02.881 [PLANETINFO OVER]: Done 13:01:02.883 [PLANETINFO LOGGING]: 3 185 13:01:03.900 [planetinfo.record]: seed = 146 54 138 24 13:01:03.900 [planetinfo.record]: coordinates = 54 181 13:01:03.900 [planetinfo.record]: government = 2; 13:01:03.900 [planetinfo.record]: economy = 5; 13:01:03.900 [planetinfo.record]: techlevel = 5; 13:01:03.900 [planetinfo.record]: population = 28; 13:01:03.901 [planetinfo.record]: productivity = 6720; 13:01:03.901 [planetinfo.record]: name = "Edzaraat"; 13:01:03.901 [planetinfo.record]: inhabitant = "Green Bony Lobster"; 13:01:03.901 [planetinfo.record]: inhabitants = "Green Bony Lobsters"; 13:01:03.901 [planetinfo.record]: description = "The planet Edzaraat is scourged by killer edible poets."; 13:01:03.909 [planetinfo.record]: air_color = 0.54009, 0.954123, 0.837352, 1; 13:01:03.909 [planetinfo.record]: cloud_alpha = 1.000000; 13:01:03.909 [planetinfo.record]: cloud_color = 0.865234, 0.356677, 0.256866, 1; 13:01:03.909 [planetinfo.record]: cloud_fraction = 0.250000; 13:01:03.910 [planetinfo.record]: land_color = 0.656898, 0.603281, 0.66, 1; 13:01:03.910 [planetinfo.record]: land_fraction = 0.540000; 13:01:03.910 [planetinfo.record]: polar_cloud_color = 0.889355, 0.562646, 0.498525, 1; 13:01:03.910 [planetinfo.record]: polar_land_color = 0.932903, 0.913934, 0.934, 1; 13:01:03.910 [planetinfo.record]: polar_sea_color = 0.947461, 0.837211, 0.719848, 1; 13:01:03.910 [planetinfo.record]: sea_color = 0.525391, 0.280845, 0.0205231, 1; 13:01:03.910 [planetinfo.record]: rotation_speed = 0.001894 13:01:03.910 [planetinfo.record]: planet zpos = 688520.000000 13:01:03.910 [planetinfo.record]: sun_radius = 107803.376465 13:01:03.910 [planetinfo.record]: sun_vector = -0.072 0.377 0.923 13:01:03.910 [planetinfo.record]: sun_distance = 983600 13:01:03.910 [planetinfo.record]: corona_flare = 0.078802 13:01:03.910 [planetinfo.record]: corona_hues = 0.846306 13:01:03.910 [planetinfo.record]: sun_color = 0.744803, 0.685161, 0.322331, 1 13:01:03.910 [planetinfo.record]: corona_shimmer = 0.382732 13:01:03.910 [planetinfo.record]: station_vector = -0.203 -0.720 0.664 13:01:03.911 [planetinfo.record]: station = coriolis 13:01:08.339 [PLANETINFO OVER]: Done 13:01:08.340 [PLANETINFO LOGGING]: 3 186 13:01:09.345 [planetinfo.record]: seed = 114 113 90 202 13:01:09.345 [planetinfo.record]: coordinates = 113 84 13:01:09.345 [planetinfo.record]: government = 6; 13:01:09.345 [planetinfo.record]: economy = 4; 13:01:09.345 [planetinfo.record]: techlevel = 7; 13:01:09.345 [planetinfo.record]: population = 39; 13:01:09.345 [planetinfo.record]: productivity = 18720; 13:01:09.345 [planetinfo.record]: name = "Arama"; 13:01:09.345 [planetinfo.record]: inhabitant = "Human Colonial"; 13:01:09.345 [planetinfo.record]: inhabitants = "Human Colonials"; 13:01:09.345 [planetinfo.record]: description = "The planet Arama is famous for its inhabitants’ ancient loathing of discos but ravaged by lethal spotted craboids."; 13:01:09.356 [planetinfo.record]: air_color = 0.69943, 0.578511, 0.883881, 1; 13:01:09.356 [planetinfo.record]: cloud_alpha = 1.000000; 13:01:09.356 [planetinfo.record]: cloud_color = 0.654297, 0.368042, 0.596151, 1; 13:01:09.356 [planetinfo.record]: cloud_fraction = 0.400000; 13:01:09.357 [planetinfo.record]: land_color = 0.546875, 0.465431, 0.0256348, 1; 13:01:09.357 [planetinfo.record]: land_fraction = 0.360000; 13:01:09.357 [planetinfo.record]: polar_cloud_color = 0.794434, 0.577206, 0.750309, 1; 13:01:09.357 [planetinfo.record]: polar_land_color = 0.945312, 0.910117, 0.720062, 1; 13:01:09.357 [planetinfo.record]: polar_sea_color = 0.940039, 0.900081, 0.882572, 1; 13:01:09.357 [planetinfo.record]: sea_color = 0.599609, 0.49766, 0.452986, 1; 13:01:09.357 [planetinfo.record]: rotation_speed = 0.000109 13:01:09.357 [planetinfo.record]: planet zpos = 658680.000000 13:01:09.357 [planetinfo.record]: sun_radius = 118004.789429 13:01:09.357 [planetinfo.record]: sun_vector = -0.751 -0.421 0.508 13:01:09.357 [planetinfo.record]: sun_distance = 1097800 13:01:09.358 [planetinfo.record]: corona_flare = 0.059944 13:01:09.358 [planetinfo.record]: corona_hues = 0.885307 13:01:09.358 [planetinfo.record]: sun_color = 0.593595, 0.646464, 0.721634, 1 13:01:09.358 [planetinfo.record]: corona_shimmer = 0.556626 13:01:09.358 [planetinfo.record]: station_vector = 0.303 0.897 0.322 13:01:09.358 [planetinfo.record]: station = coriolis 13:01:13.898 [PLANETINFO OVER]: Done 13:01:13.899 [PLANETINFO LOGGING]: 3 187 13:01:14.902 [planetinfo.record]: seed = 194 37 42 129 13:01:14.903 [planetinfo.record]: coordinates = 37 203 13:01:14.903 [planetinfo.record]: government = 0; 13:01:14.903 [planetinfo.record]: economy = 3; 13:01:14.903 [planetinfo.record]: techlevel = 5; 13:01:14.903 [planetinfo.record]: population = 24; 13:01:14.903 [planetinfo.record]: productivity = 5376; 13:01:14.903 [planetinfo.record]: name = "Leenor"; 13:01:14.903 [planetinfo.record]: inhabitant = "Human Colonial"; 13:01:14.903 [planetinfo.record]: inhabitants = "Human Colonials"; 13:01:14.903 [planetinfo.record]: description = "This planet is most notable for Leenorian evil juice but cursed by dreadful solar activity."; 13:01:14.921 [planetinfo.record]: air_color = 0.547244, 0.601635, 0.834451, 1; 13:01:14.921 [planetinfo.record]: cloud_alpha = 1.000000; 13:01:14.921 [planetinfo.record]: cloud_color = 0.286522, 0.408186, 0.505859, 1; 13:01:14.921 [planetinfo.record]: cloud_fraction = 0.330000; 13:01:14.921 [planetinfo.record]: land_color = 0.66, 0.328228, 0.061875, 1; 13:01:14.921 [planetinfo.record]: land_fraction = 0.460000; 13:01:14.921 [planetinfo.record]: polar_cloud_color = 0.53045, 0.639827, 0.727637, 1; 13:01:14.921 [planetinfo.record]: polar_land_color = 0.934, 0.816623, 0.722391, 1; 13:01:14.921 [planetinfo.record]: polar_sea_color = 0.928524, 0.936328, 0.883751, 1; 13:01:14.921 [planetinfo.record]: sea_color = 0.61549, 0.636719, 0.493706, 1; 13:01:14.921 [planetinfo.record]: rotation_speed = 0.002564 13:01:14.921 [planetinfo.record]: planet zpos = 435260.000000 13:01:14.921 [planetinfo.record]: sun_radius = 75814.608307 13:01:14.921 [planetinfo.record]: sun_vector = -0.073 0.515 -0.854 13:01:14.921 [planetinfo.record]: sun_distance = 746160 13:01:14.921 [planetinfo.record]: corona_flare = 0.066734 13:01:14.921 [planetinfo.record]: corona_hues = 0.978462 13:01:14.921 [planetinfo.record]: sun_color = 0.313191, 0.717501, 0.733478, 1 13:01:14.921 [planetinfo.record]: corona_shimmer = 0.575534 13:01:14.922 [planetinfo.record]: station_vector = -0.802 0.561 0.204 13:01:14.922 [planetinfo.record]: station = coriolis 13:01:20.413 [PLANETINFO OVER]: Done 13:01:20.414 [PLANETINFO LOGGING]: 3 188 13:01:21.417 [planetinfo.record]: seed = 34 13 26 153 13:01:21.418 [planetinfo.record]: coordinates = 13 25 13:01:21.418 [planetinfo.record]: government = 4; 13:01:21.418 [planetinfo.record]: economy = 1; 13:01:21.418 [planetinfo.record]: techlevel = 9; 13:01:21.418 [planetinfo.record]: population = 42; 13:01:21.418 [planetinfo.record]: productivity = 24192; 13:01:21.418 [planetinfo.record]: name = "Oronceri"; 13:01:21.418 [planetinfo.record]: inhabitant = "Human Colonial"; 13:01:21.418 [planetinfo.record]: inhabitants = "Human Colonials"; 13:01:21.418 [planetinfo.record]: description = "This planet is mildly fabled for its inhabitants’ eccentric love for tourists but plagued by deadly earthquakes."; 13:01:21.430 [planetinfo.record]: air_color = 0.676566, 0.845984, 0.917051, 1; 13:01:21.430 [planetinfo.record]: cloud_alpha = 1.000000; 13:01:21.430 [planetinfo.record]: cloud_color = 0.626905, 0.753906, 0.612549, 1; 13:01:21.430 [planetinfo.record]: cloud_fraction = 0.300000; 13:01:21.430 [planetinfo.record]: land_color = 0.587813, 0.0825, 0.66, 1; 13:01:21.430 [planetinfo.record]: land_fraction = 0.460000; 13:01:21.430 [planetinfo.record]: polar_cloud_color = 0.750896, 0.839258, 0.740907, 1; 13:01:21.430 [planetinfo.record]: polar_land_color = 0.908461, 0.729688, 0.934, 1; 13:01:21.430 [planetinfo.record]: polar_sea_color = 0.938672, 0.88268, 0.876888, 1; 13:01:21.430 [planetinfo.record]: sea_color = 0.613281, 0.466953, 0.451816, 1; 13:01:21.431 [planetinfo.record]: rotation_speed = 0.004446 13:01:21.431 [planetinfo.record]: planet zpos = 718620.000000 13:01:21.431 [planetinfo.record]: sun_radius = 105790.584869 13:01:21.431 [planetinfo.record]: sun_vector = -0.766 0.639 -0.073 13:01:21.431 [planetinfo.record]: sun_distance = 1077930 13:01:21.431 [planetinfo.record]: corona_flare = 0.039719 13:01:21.431 [planetinfo.record]: corona_hues = 0.572151 13:01:21.431 [planetinfo.record]: sun_color = 0.622751, 0.665644, 0.717935, 1 13:01:21.431 [planetinfo.record]: corona_shimmer = 0.307565 13:01:21.431 [planetinfo.record]: station_vector = -0.873 0.468 0.140 13:01:21.431 [planetinfo.record]: station = coriolis 13:01:25.699 [PLANETINFO OVER]: Done 13:01:25.700 [PLANETINFO LOGGING]: 3 189 13:01:26.703 [planetinfo.record]: seed = 178 190 74 228 13:01:26.704 [planetinfo.record]: coordinates = 190 101 13:01:26.704 [planetinfo.record]: government = 6; 13:01:26.704 [planetinfo.record]: economy = 5; 13:01:26.704 [planetinfo.record]: techlevel = 7; 13:01:26.704 [planetinfo.record]: population = 40; 13:01:26.704 [planetinfo.record]: productivity = 16000; 13:01:26.704 [planetinfo.record]: name = "Zausmaor"; 13:01:26.704 [planetinfo.record]: inhabitant = "Human Colonial"; 13:01:26.704 [planetinfo.record]: inhabitants = "Human Colonials"; 13:01:26.704 [planetinfo.record]: description = "This planet is very notable for its inhabitants’ ingrained shyness and its fabulous cuisine."; 13:01:26.716 [planetinfo.record]: air_color = 0.694364, 0.838631, 0.901441, 1; 13:01:26.716 [planetinfo.record]: cloud_alpha = 1.000000; 13:01:26.716 [planetinfo.record]: cloud_color = 0.648472, 0.707031, 0.643509, 1; 13:01:26.716 [planetinfo.record]: cloud_fraction = 0.320000; 13:01:26.716 [planetinfo.record]: land_color = 0.187378, 0.570624, 0.599609, 1; 13:01:26.716 [planetinfo.record]: land_fraction = 0.420000; 13:01:26.716 [planetinfo.record]: polar_cloud_color = 0.775811, 0.818164, 0.772222, 1; 13:01:26.716 [planetinfo.record]: polar_land_color = 0.77847, 0.928679, 0.940039, 1; 13:01:26.716 [planetinfo.record]: polar_sea_color = 0.945117, 0.914025, 0.895369, 1; 13:01:26.716 [planetinfo.record]: sea_color = 0.548828, 0.476607, 0.433274, 1; 13:01:26.716 [planetinfo.record]: rotation_speed = 0.000758 13:01:26.716 [planetinfo.record]: planet zpos = 483600.000000 13:01:26.716 [planetinfo.record]: sun_radius = 73859.761047 13:01:26.716 [planetinfo.record]: sun_vector = -0.119 -0.949 0.293 13:01:26.716 [planetinfo.record]: sun_distance = 765700 13:01:26.716 [planetinfo.record]: corona_flare = 0.042493 13:01:26.716 [planetinfo.record]: corona_hues = 0.912506 13:01:26.716 [planetinfo.record]: sun_color = 0.685571, 0.629233, 0.564794, 1 13:01:26.716 [planetinfo.record]: corona_shimmer = 0.462956 13:01:26.717 [planetinfo.record]: station_vector = 0.611 -0.781 0.126 13:01:26.717 [planetinfo.record]: station = coriolis 13:01:31.658 [PLANETINFO OVER]: Done 13:01:31.659 [PLANETINFO LOGGING]: 3 190 13:01:32.664 [planetinfo.record]: seed = 18 153 218 77 13:01:32.664 [planetinfo.record]: coordinates = 153 171 13:01:32.664 [planetinfo.record]: government = 2; 13:01:32.664 [planetinfo.record]: economy = 3; 13:01:32.664 [planetinfo.record]: techlevel = 6; 13:01:32.664 [planetinfo.record]: population = 30; 13:01:32.664 [planetinfo.record]: productivity = 10080; 13:01:32.664 [planetinfo.record]: name = "Dienorqu"; 13:01:32.664 [planetinfo.record]: inhabitant = "Yellow Horned Lobster"; 13:01:32.664 [planetinfo.record]: inhabitants = "Yellow Horned Lobsters"; 13:01:32.664 [planetinfo.record]: description = "This world is ravaged by occasional solar activity."; 13:01:32.680 [planetinfo.record]: air_color = 0.738596, 0.488051, 0.969082, 1; 13:01:32.680 [planetinfo.record]: cloud_alpha = 1.000000; 13:01:32.680 [planetinfo.record]: cloud_color = 0.910156, 0.0995483, 0.441524, 1; 13:01:32.680 [planetinfo.record]: cloud_fraction = 0.350000; 13:01:32.680 [planetinfo.record]: land_color = 0.66, 0.494053, 0.461484, 1; 13:01:32.680 [planetinfo.record]: land_fraction = 0.620000; 13:01:32.680 [planetinfo.record]: polar_cloud_color = 0.90957, 0.403267, 0.616863, 1; 13:01:32.680 [planetinfo.record]: polar_land_color = 0.934, 0.87529, 0.863768, 1; 13:01:32.680 [planetinfo.record]: polar_sea_color = 0.857197, 0.892616, 0.913867, 1; 13:01:32.680 [planetinfo.record]: sea_color = 0.647678, 0.781209, 0.861328, 1; 13:01:32.680 [planetinfo.record]: rotation_speed = 0.001308 13:01:32.680 [planetinfo.record]: planet zpos = 818610.000000 13:01:32.680 [planetinfo.record]: sun_radius = 163103.599548 13:01:32.680 [planetinfo.record]: sun_vector = -0.158 0.241 -0.958 13:01:32.680 [planetinfo.record]: sun_distance = 1070490 13:01:32.680 [planetinfo.record]: corona_flare = 0.078430 13:01:32.680 [planetinfo.record]: corona_hues = 0.712616 13:01:32.680 [planetinfo.record]: sun_color = 0.807617, 0.407549, 0.364026, 1 13:01:32.680 [planetinfo.record]: corona_shimmer = 0.374834 13:01:32.681 [planetinfo.record]: station_vector = 0.959 0.163 0.232 13:01:32.681 [planetinfo.record]: station = coriolis 13:01:37.596 [PLANETINFO OVER]: Done 13:01:37.597 [PLANETINFO LOGGING]: 3 191 13:01:38.606 [planetinfo.record]: seed = 98 90 234 102 13:01:38.606 [planetinfo.record]: coordinates = 90 121 13:01:38.606 [planetinfo.record]: government = 4; 13:01:38.606 [planetinfo.record]: economy = 1; 13:01:38.606 [planetinfo.record]: techlevel = 10; 13:01:38.606 [planetinfo.record]: population = 46; 13:01:38.606 [planetinfo.record]: productivity = 26496; 13:01:38.606 [planetinfo.record]: name = "Biante"; 13:01:38.606 [planetinfo.record]: inhabitant = "Fierce Blue Bony Humanoid"; 13:01:38.606 [planetinfo.record]: inhabitants = "Fierce Blue Bony Humanoids"; 13:01:38.606 [planetinfo.record]: description = "This planet is noted for its exciting sit coms."; 13:01:38.625 [planetinfo.record]: air_color = 0.54903, 0.956725, 0.870378, 1; 13:01:38.625 [planetinfo.record]: cloud_alpha = 1.000000; 13:01:38.625 [planetinfo.record]: cloud_color = 0.873047, 0.502172, 0.279648, 1; 13:01:38.625 [planetinfo.record]: cloud_fraction = 0.390000; 13:01:38.625 [planetinfo.record]: land_color = 0.288374, 0.613281, 0.019165, 1; 13:01:38.625 [planetinfo.record]: land_fraction = 0.250000; 13:01:38.625 [planetinfo.record]: polar_cloud_color = 0.892871, 0.655811, 0.513575, 1; 13:01:38.625 [planetinfo.record]: polar_land_color = 0.814348, 0.938672, 0.711337, 1; 13:01:38.625 [planetinfo.record]: polar_sea_color = 0.864881, 0.921484, 0.892299, 1; 13:01:38.625 [planetinfo.record]: sea_color = 0.592241, 0.785156, 0.685684, 1; 13:01:38.625 [planetinfo.record]: rotation_speed = 0.001310 13:01:38.625 [planetinfo.record]: planet zpos = 533040.000000 13:01:38.625 [planetinfo.record]: sun_radius = 124321.911926 13:01:38.625 [planetinfo.record]: sun_vector = 0.434 0.695 0.573 13:01:38.625 [planetinfo.record]: sun_distance = 799560 13:01:38.625 [planetinfo.record]: corona_flare = 0.065680 13:01:38.625 [planetinfo.record]: corona_hues = 0.813210 13:01:38.626 [planetinfo.record]: sun_color = 0.623076, 0.633451, 0.710483, 1 13:01:38.626 [planetinfo.record]: corona_shimmer = 0.229094 13:01:38.626 [planetinfo.record]: station_vector = -0.008 0.620 0.785 13:01:38.626 [planetinfo.record]: station = coriolis 13:01:43.510 [PLANETINFO OVER]: Done 13:01:43.511 [PLANETINFO LOGGING]: 3 192 13:01:44.524 [planetinfo.record]: seed = 66 158 154 213 13:01:44.524 [planetinfo.record]: coordinates = 158 252 13:01:44.524 [planetinfo.record]: government = 0; 13:01:44.524 [planetinfo.record]: economy = 6; 13:01:44.524 [planetinfo.record]: techlevel = 3; 13:01:44.524 [planetinfo.record]: population = 19; 13:01:44.524 [planetinfo.record]: productivity = 2432; 13:01:44.524 [planetinfo.record]: name = "Laerzaes"; 13:01:44.524 [planetinfo.record]: inhabitant = "Horned Lobster"; 13:01:44.524 [planetinfo.record]: inhabitants = "Horned Lobsters"; 13:01:44.524 [planetinfo.record]: description = "The world Laerzaes is most well known for its vast oceans."; 13:01:44.536 [planetinfo.record]: air_color = 0.748195, 0.733794, 0.972334, 1; 13:01:44.536 [planetinfo.record]: cloud_alpha = 1.000000; 13:01:44.536 [planetinfo.record]: cloud_color = 0.818716, 0.794151, 0.919922, 1; 13:01:44.536 [planetinfo.record]: cloud_fraction = 0.560000; 13:01:44.536 [planetinfo.record]: land_color = 0.66, 0.121172, 0.24746, 1; 13:01:44.536 [planetinfo.record]: land_fraction = 0.200000; 13:01:44.536 [planetinfo.record]: polar_cloud_color = 0.851121, 0.835867, 0.913965, 1; 13:01:44.536 [planetinfo.record]: polar_land_color = 0.934, 0.743369, 0.788048, 1; 13:01:44.536 [planetinfo.record]: polar_sea_color = 0.938477, 0.915176, 0.882205, 1; 13:01:44.536 [planetinfo.record]: sea_color = 0.615234, 0.554135, 0.467674, 1; 13:01:44.536 [planetinfo.record]: rotation_speed = 0.000746 13:01:44.536 [planetinfo.record]: planet zpos = 553020.000000 13:01:44.536 [planetinfo.record]: sun_radius = 119116.673584 13:01:44.536 [planetinfo.record]: sun_vector = -0.817 -0.304 0.490 13:01:44.536 [planetinfo.record]: sun_distance = 723180 13:01:44.536 [planetinfo.record]: corona_flare = 0.092081 13:01:44.536 [planetinfo.record]: corona_hues = 0.883987 13:01:44.536 [planetinfo.record]: sun_color = 0.796326, 0.772448, 0.624213, 1 13:01:44.536 [planetinfo.record]: corona_shimmer = 0.609717 13:01:44.536 [planetinfo.record]: station_vector = -0.963 0.269 0.014 13:01:44.536 [planetinfo.record]: station = coriolis 13:01:49.217 [PLANETINFO OVER]: Done 13:01:49.217 [PLANETINFO LOGGING]: 3 193 13:01:50.221 [planetinfo.record]: seed = 210 41 10 126 13:01:50.221 [planetinfo.record]: coordinates = 41 228 13:01:50.221 [planetinfo.record]: government = 2; 13:01:50.221 [planetinfo.record]: economy = 4; 13:01:50.221 [planetinfo.record]: techlevel = 5; 13:01:50.221 [planetinfo.record]: population = 27; 13:01:50.221 [planetinfo.record]: productivity = 7776; 13:01:50.221 [planetinfo.record]: name = "Rimabe"; 13:01:50.221 [planetinfo.record]: inhabitant = "Human Colonial"; 13:01:50.221 [planetinfo.record]: inhabitants = "Human Colonials"; 13:01:50.221 [planetinfo.record]: description = "This planet is a dull world."; 13:01:50.240 [planetinfo.record]: air_color = 0.549076, 0.584004, 0.842256, 1; 13:01:50.240 [planetinfo.record]: cloud_alpha = 1.000000; 13:01:50.240 [planetinfo.record]: cloud_color = 0.293594, 0.374617, 0.529297, 1; 13:01:50.240 [planetinfo.record]: cloud_fraction = 0.290000; 13:01:50.240 [planetinfo.record]: land_color = 0.523037, 0.66, 0.144375, 1; 13:01:50.240 [planetinfo.record]: land_fraction = 0.470000; 13:01:50.240 [planetinfo.record]: polar_cloud_color = 0.532732, 0.603356, 0.738184, 1; 13:01:50.240 [planetinfo.record]: polar_land_color = 0.885544, 0.934, 0.751578, 1; 13:01:50.240 [planetinfo.record]: polar_sea_color = 0.938477, 0.88753, 0.877531, 1; 13:01:50.241 [planetinfo.record]: sea_color = 0.615234, 0.481638, 0.455418, 1; 13:01:50.241 [planetinfo.record]: rotation_speed = 0.004438 13:01:50.241 [planetinfo.record]: planet zpos = 708510.000000 13:01:50.241 [planetinfo.record]: sun_radius = 185232.805023 13:01:50.241 [planetinfo.record]: sun_vector = 0.224 0.727 -0.649 13:01:50.241 [planetinfo.record]: sun_distance = 1159380 13:01:50.241 [planetinfo.record]: corona_flare = 0.078224 13:01:50.241 [planetinfo.record]: corona_hues = 0.593620 13:01:50.241 [planetinfo.record]: sun_color = 0.524845, 0.763743, 0.765326, 1 13:01:50.241 [planetinfo.record]: corona_shimmer = 0.739684 13:01:50.241 [planetinfo.record]: station_vector = -0.308 0.928 0.211 13:01:50.241 [planetinfo.record]: station = coriolis 13:01:55.287 [PLANETINFO OVER]: Done 13:01:55.288 [PLANETINFO LOGGING]: 3 194 13:01:56.307 [planetinfo.record]: seed = 178 61 90 253 13:01:56.307 [planetinfo.record]: coordinates = 61 51 13:01:56.308 [planetinfo.record]: government = 6; 13:01:56.308 [planetinfo.record]: economy = 3; 13:01:56.308 [planetinfo.record]: techlevel = 8; 13:01:56.308 [planetinfo.record]: population = 42; 13:01:56.308 [planetinfo.record]: productivity = 23520; 13:01:56.308 [planetinfo.record]: name = "Isreesve"; 13:01:56.308 [planetinfo.record]: inhabitant = "Human Colonial"; 13:01:56.308 [planetinfo.record]: inhabitants = "Human Colonials"; 13:01:56.308 [planetinfo.record]: description = "The planet Isreesve is cursed by deadly civil war."; 13:01:56.324 [planetinfo.record]: air_color = 0.501153, 0.995098, 0.90976, 1; 13:01:56.324 [planetinfo.record]: cloud_alpha = 1.000000; 13:01:56.324 [planetinfo.record]: cloud_color = 0.988281, 0.541311, 0.108093, 1; 13:01:56.324 [planetinfo.record]: cloud_fraction = 0.500000; 13:01:56.324 [planetinfo.record]: land_color = 0.200684, 0.535156, 0.417568, 1; 13:01:56.324 [planetinfo.record]: land_fraction = 0.340000; 13:01:56.324 [planetinfo.record]: polar_cloud_color = 0.944727, 0.677682, 0.418853, 1; 13:01:56.324 [planetinfo.record]: polar_land_color = 0.798596, 0.946484, 0.894492, 1; 13:01:56.324 [planetinfo.record]: polar_sea_color = 0.940039, 0.936929, 0.890283, 1; 13:01:56.324 [planetinfo.record]: sea_color = 0.599609, 0.591675, 0.472661, 1; 13:01:56.324 [planetinfo.record]: rotation_speed = 0.002624 13:01:56.324 [planetinfo.record]: planet zpos = 806650.000000 13:01:56.324 [planetinfo.record]: sun_radius = 117588.802338 13:01:56.324 [planetinfo.record]: sun_vector = -0.575 -0.810 -0.117 13:01:56.324 [planetinfo.record]: sun_distance = 1241000 13:01:56.324 [planetinfo.record]: corona_flare = 0.011087 13:01:56.324 [planetinfo.record]: corona_hues = 0.844315 13:01:56.324 [planetinfo.record]: sun_color = 0.400987, 0.608193, 0.771518, 1 13:01:56.324 [planetinfo.record]: corona_shimmer = 0.361546 13:01:56.324 [planetinfo.record]: station_vector = -0.997 0.054 0.052 13:01:56.324 [planetinfo.record]: station = coriolis 13:02:01.261 [PLANETINFO OVER]: Done 13:02:01.263 [PLANETINFO LOGGING]: 3 195 13:02:02.268 [planetinfo.record]: seed = 2 22 170 46 13:02:02.268 [planetinfo.record]: coordinates = 22 169 13:02:02.268 [planetinfo.record]: government = 0; 13:02:02.268 [planetinfo.record]: economy = 3; 13:02:02.268 [planetinfo.record]: techlevel = 6; 13:02:02.268 [planetinfo.record]: population = 28; 13:02:02.268 [planetinfo.record]: productivity = 6272; 13:02:02.268 [planetinfo.record]: name = "Rerebeer"; 13:02:02.268 [planetinfo.record]: inhabitant = "Red Frog"; 13:02:02.268 [planetinfo.record]: inhabitants = "Red Frogs"; 13:02:02.268 [planetinfo.record]: description = "The planet Rerebeer is reasonably noted for its fabulous goat soup and mud tennis."; 13:02:02.278 [planetinfo.record]: air_color = 0.706507, 0.758332, 0.959326, 1; 13:02:02.278 [planetinfo.record]: cloud_alpha = 1.000000; 13:02:02.278 [planetinfo.record]: cloud_color = 0.712257, 0.813682, 0.880859, 1; 13:02:02.278 [planetinfo.record]: cloud_fraction = 0.270000; 13:02:02.278 [planetinfo.record]: land_color = 0.180817, 0.585938, 0.528967, 1; 13:02:02.278 [planetinfo.record]: land_fraction = 0.440000; 13:02:02.278 [planetinfo.record]: polar_cloud_color = 0.789153, 0.853661, 0.896387, 1; 13:02:02.278 [planetinfo.record]: polar_land_color = 0.778683, 0.941406, 0.918523, 1; 13:02:02.278 [planetinfo.record]: polar_sea_color = 0.890137, 0.926172, 0.865482, 1; 13:02:02.278 [planetinfo.record]: sea_color = 0.623384, 0.738281, 0.544771, 1; 13:02:02.278 [planetinfo.record]: rotation_speed = 0.000024 13:02:02.278 [planetinfo.record]: planet zpos = 577980.000000 13:02:02.278 [planetinfo.record]: sun_radius = 144976.140442 13:02:02.279 [planetinfo.record]: sun_vector = 0.089 -0.615 0.784 13:02:02.279 [planetinfo.record]: sun_distance = 1284400 13:02:02.279 [planetinfo.record]: corona_flare = 0.081020 13:02:02.279 [planetinfo.record]: corona_hues = 0.508286 13:02:02.279 [planetinfo.record]: sun_color = 0.244544, 0.475801, 0.658218, 1 13:02:02.279 [planetinfo.record]: corona_shimmer = 0.508891 13:02:02.285 [planetinfo.record]: station_vector = -0.335 0.891 0.306 13:02:02.285 [planetinfo.record]: station = coriolis 13:02:07.866 [PLANETINFO OVER]: Done 13:02:07.867 [PLANETINFO LOGGING]: 3 196 13:02:08.871 [planetinfo.record]: seed = 98 80 26 114 13:02:08.871 [planetinfo.record]: coordinates = 80 51 13:02:08.871 [planetinfo.record]: government = 4; 13:02:08.871 [planetinfo.record]: economy = 3; 13:02:08.871 [planetinfo.record]: techlevel = 6; 13:02:08.871 [planetinfo.record]: population = 32; 13:02:08.871 [planetinfo.record]: productivity = 14336; 13:02:08.872 [planetinfo.record]: name = "Enlaed"; 13:02:08.872 [planetinfo.record]: inhabitant = "Human Colonial"; 13:02:08.872 [planetinfo.record]: inhabitants = "Human Colonials"; 13:02:08.872 [planetinfo.record]: description = "This planet is noted for mud hockey."; 13:02:08.873 [planetinfo.record]: air_color = 0.573265, 0.883131, 0.994447, 1; 13:02:08.874 [planetinfo.record]: cloud_alpha = 1.000000; 13:02:08.874 [planetinfo.record]: cloud_color = 0.45307, 0.986328, 0.323639, 1; 13:02:08.874 [planetinfo.record]: cloud_fraction = 0.180000; 13:02:08.874 [planetinfo.record]: land_color = 0.66, 0.332477, 0.244922, 1; 13:02:08.874 [planetinfo.record]: land_fraction = 0.260000; 13:02:08.874 [planetinfo.record]: polar_cloud_color = 0.624916, 0.943848, 0.547505, 1; 13:02:08.874 [planetinfo.record]: polar_land_color = 0.934, 0.818126, 0.78715, 1; 13:02:08.874 [planetinfo.record]: polar_sea_color = 0.884703, 0.923828, 0.868795, 1; 13:02:08.874 [planetinfo.record]: sea_color = 0.632681, 0.761719, 0.580215, 1; 13:02:08.874 [planetinfo.record]: rotation_speed = 0.001939 13:02:08.874 [planetinfo.record]: planet zpos = 374880.000000 13:02:08.874 [planetinfo.record]: sun_radius = 95344.541016 13:02:08.874 [planetinfo.record]: sun_vector = -0.386 -0.903 -0.188 13:02:08.874 [planetinfo.record]: sun_distance = 613440 13:02:08.874 [planetinfo.record]: corona_flare = 0.071765 13:02:08.874 [planetinfo.record]: corona_hues = 0.753929 13:02:08.875 [planetinfo.record]: sun_color = 0.51209, 0.710464, 0.721573, 1 13:02:08.875 [planetinfo.record]: corona_shimmer = 0.399002 13:02:08.875 [planetinfo.record]: station_vector = 0.512 -0.533 0.673 13:02:08.875 [planetinfo.record]: station = coriolis 13:02:13.305 [PLANETINFO OVER]: Done 13:02:13.307 [PLANETINFO LOGGING]: 3 197 13:02:14.325 [planetinfo.record]: seed = 242 31 202 205 13:02:14.325 [planetinfo.record]: coordinates = 31 184 13:02:14.325 [planetinfo.record]: government = 6; 13:02:14.325 [planetinfo.record]: economy = 0; 13:02:14.325 [planetinfo.record]: techlevel = 13; 13:02:14.325 [planetinfo.record]: population = 59; 13:02:14.325 [planetinfo.record]: productivity = 47200; 13:02:14.325 [planetinfo.record]: name = "Dicebe"; 13:02:14.325 [planetinfo.record]: inhabitant = "Rodent"; 13:02:14.325 [planetinfo.record]: inhabitants = "Rodents"; 13:02:14.326 [planetinfo.record]: description = "This world is fabled for its ancient Erouat plant plantations."; 13:02:14.344 [planetinfo.record]: air_color = 0.720747, 0.571105, 0.897539, 1; 13:02:14.344 [planetinfo.record]: cloud_alpha = 1.000000; 13:02:14.344 [planetinfo.record]: cloud_color = 0.695312, 0.353088, 0.561631, 1; 13:02:14.344 [planetinfo.record]: cloud_fraction = 0.190000; 13:02:14.344 [planetinfo.record]: land_color = 0.5604, 0.66, 0.363516, 1; 13:02:14.344 [planetinfo.record]: land_fraction = 0.690000; 13:02:14.344 [planetinfo.record]: polar_cloud_color = 0.812891, 0.562832, 0.715211, 1; 13:02:14.344 [planetinfo.record]: polar_land_color = 0.898763, 0.934, 0.829107, 1; 13:02:14.344 [planetinfo.record]: polar_sea_color = 0.930469, 0.848526, 0.842329, 1; 13:02:14.344 [planetinfo.record]: sea_color = 0.695312, 0.450379, 0.431854, 1; 13:02:14.344 [planetinfo.record]: rotation_speed = 0.003223 13:02:14.344 [planetinfo.record]: planet zpos = 741000.000000 13:02:14.344 [planetinfo.record]: sun_radius = 166643.779755 13:02:14.344 [planetinfo.record]: sun_vector = 0.441 0.718 0.538 13:02:14.344 [planetinfo.record]: sun_distance = 1173250 13:02:14.344 [planetinfo.record]: corona_flare = 0.045850 13:02:14.344 [planetinfo.record]: corona_hues = 0.696983 13:02:14.344 [planetinfo.record]: sun_color = 0.307441, 0.37835, 0.678891, 1 13:02:14.345 [planetinfo.record]: corona_shimmer = 0.296331 13:02:14.345 [planetinfo.record]: station_vector = -0.325 -0.798 0.508 13:02:14.345 [planetinfo.record]: station = dodecahedron 13:02:19.061 [PLANETINFO OVER]: Done 13:02:19.062 [PLANETINFO LOGGING]: 3 198 13:02:20.068 [planetinfo.record]: seed = 82 7 218 64 13:02:20.068 [planetinfo.record]: coordinates = 7 147 13:02:20.068 [planetinfo.record]: government = 2; 13:02:20.068 [planetinfo.record]: economy = 3; 13:02:20.068 [planetinfo.record]: techlevel = 8; 13:02:20.068 [planetinfo.record]: population = 38; 13:02:20.068 [planetinfo.record]: productivity = 12768; 13:02:20.068 [planetinfo.record]: name = "Ange"; 13:02:20.069 [planetinfo.record]: inhabitant = "Large Yellow Fat Bird"; 13:02:20.069 [planetinfo.record]: inhabitants = "Large Yellow Fat Birds"; 13:02:20.069 [planetinfo.record]: description = "The planet Ange is reasonably noted for its inhabitants’ eccentric love for tourists and the Angeian tree grub."; 13:02:20.074 [planetinfo.record]: air_color = 0.527555, 0.630525, 0.8325, 1; 13:02:20.074 [planetinfo.record]: cloud_alpha = 1.000000; 13:02:20.074 [planetinfo.record]: cloud_color = 0.25, 0.5, 0.5, 1; 13:02:20.074 [planetinfo.record]: cloud_fraction = 0.400000; 13:02:20.074 [planetinfo.record]: land_color = 0.526062, 0.342773, 0.609375, 1; 13:02:20.074 [planetinfo.record]: land_fraction = 0.480000; 13:02:20.075 [planetinfo.record]: polar_cloud_color = 0.498438, 0.725, 0.725, 1; 13:02:20.075 [planetinfo.record]: polar_land_color = 0.906966, 0.836353, 0.939062, 1; 13:02:20.075 [planetinfo.record]: polar_sea_color = 0.941992, 0.914038, 0.889373, 1; 13:02:20.075 [planetinfo.record]: sea_color = 0.580078, 0.511222, 0.450467, 1; 13:02:20.075 [planetinfo.record]: rotation_speed = 0.003780 13:02:20.075 [planetinfo.record]: planet zpos = 423450.000000 13:02:20.075 [planetinfo.record]: sun_radius = 68910.129547 13:02:20.075 [planetinfo.record]: sun_vector = 0.581 0.108 -0.807 13:02:20.075 [planetinfo.record]: sun_distance = 564600 13:02:20.075 [planetinfo.record]: corona_flare = 0.086830 13:02:20.075 [planetinfo.record]: corona_hues = 0.846237 13:02:20.075 [planetinfo.record]: sun_color = 0.757858, 0.736351, 0.724328, 1 13:02:20.076 [planetinfo.record]: corona_shimmer = 0.482485 13:02:20.076 [planetinfo.record]: station_vector = -0.716 0.233 0.658 13:02:20.076 [planetinfo.record]: station = coriolis 13:02:25.316 [PLANETINFO OVER]: Done 13:02:25.317 [PLANETINFO LOGGING]: 3 199 13:02:26.321 [planetinfo.record]: seed = 162 64 106 64 13:02:26.321 [planetinfo.record]: coordinates = 64 35 13:02:26.321 [planetinfo.record]: government = 4; 13:02:26.321 [planetinfo.record]: economy = 3; 13:02:26.321 [planetinfo.record]: techlevel = 6; 13:02:26.321 [planetinfo.record]: population = 32; 13:02:26.321 [planetinfo.record]: productivity = 14336; 13:02:26.321 [planetinfo.record]: name = "Cebima"; 13:02:26.321 [planetinfo.record]: inhabitant = "Human Colonial"; 13:02:26.321 [planetinfo.record]: inhabitants = "Human Colonials"; 13:02:26.321 [planetinfo.record]: description = "The world Cebima is mildly fabled for the Cebimaian mountain lobstoid but plagued by occasional civil war."; 13:02:26.336 [planetinfo.record]: air_color = 0.465122, 0.48038, 0.923555, 1; 13:02:26.336 [planetinfo.record]: cloud_alpha = 1.000000; 13:02:26.336 [planetinfo.record]: cloud_color = 0.0845947, 0.143792, 0.773438, 1; 13:02:26.336 [planetinfo.record]: cloud_fraction = 0.120000; 13:02:26.336 [planetinfo.record]: land_color = 0.0438281, 0.66, 0.371169, 1; 13:02:26.336 [planetinfo.record]: land_fraction = 0.560000; 13:02:26.336 [planetinfo.record]: polar_cloud_color = 0.37599, 0.416557, 0.848047, 1; 13:02:26.336 [planetinfo.record]: polar_land_color = 0.716006, 0.934, 0.831815, 1; 13:02:26.336 [planetinfo.record]: polar_sea_color = 0.875717, 0.926953, 0.89333, 1; 13:02:26.336 [planetinfo.record]: sea_color = 0.568967, 0.730469, 0.624483, 1; 13:02:26.337 [planetinfo.record]: rotation_speed = 0.003788 13:02:26.337 [planetinfo.record]: planet zpos = 374400.000000 13:02:26.337 [planetinfo.record]: sun_radius = 74708.349609 13:02:26.337 [planetinfo.record]: sun_vector = -0.136 0.937 -0.322 13:02:26.337 [planetinfo.record]: sun_distance = 547200 13:02:26.337 [planetinfo.record]: corona_flare = 0.021735 13:02:26.337 [planetinfo.record]: corona_hues = 0.868996 13:02:26.337 [planetinfo.record]: sun_color = 0.565823, 0.732885, 0.74165, 1 13:02:26.338 [planetinfo.record]: corona_shimmer = 0.278381 13:02:26.338 [planetinfo.record]: station_vector = -0.351 0.785 0.510 13:02:26.338 [planetinfo.record]: station = coriolis 13:02:31.855 [PLANETINFO OVER]: Done 13:02:31.856 [PLANETINFO LOGGING]: 3 200 13:02:32.858 [planetinfo.record]: seed = 130 11 154 214 13:02:32.858 [planetinfo.record]: coordinates = 11 38 13:02:32.858 [planetinfo.record]: government = 0; 13:02:32.858 [planetinfo.record]: economy = 6; 13:02:32.858 [planetinfo.record]: techlevel = 4; 13:02:32.858 [planetinfo.record]: population = 23; 13:02:32.858 [planetinfo.record]: productivity = 2944; 13:02:32.858 [planetinfo.record]: name = "Veusar"; 13:02:32.858 [planetinfo.record]: inhabitant = "Furry Insect"; 13:02:32.858 [planetinfo.record]: inhabitants = "Furry Insects"; 13:02:32.858 [planetinfo.record]: description = "This world is a revolting little planet."; 13:02:32.872 [planetinfo.record]: air_color = 0.544805, 0.978188, 0.954049, 1; 13:02:32.872 [planetinfo.record]: cloud_alpha = 1.000000; 13:02:32.872 [planetinfo.record]: cloud_color = 0.9375, 0.830498, 0.252686, 1; 13:02:32.872 [planetinfo.record]: cloud_fraction = 0.190000; 13:02:32.872 [planetinfo.record]: land_color = 0.626953, 0.5288, 0.494705, 1; 13:02:32.872 [planetinfo.record]: land_fraction = 0.570000; 13:02:32.872 [planetinfo.record]: polar_cloud_color = 0.921875, 0.856113, 0.500999, 1; 13:02:32.872 [planetinfo.record]: polar_land_color = 0.937305, 0.90062, 0.887877, 1; 13:02:32.872 [planetinfo.record]: polar_sea_color = 0.76929, 0.940039, 0.908024, 1; 13:02:32.872 [planetinfo.record]: sea_color = 0.163956, 0.599609, 0.517924, 1; 13:02:32.872 [planetinfo.record]: rotation_speed = 0.003224 13:02:32.872 [planetinfo.record]: planet zpos = 567190.000000 13:02:32.872 [planetinfo.record]: sun_radius = 71614.421539 13:02:32.872 [planetinfo.record]: sun_vector = -0.374 -0.450 0.811 13:02:32.872 [planetinfo.record]: sun_distance = 785340 13:02:32.872 [planetinfo.record]: corona_flare = 0.015186 13:02:32.872 [planetinfo.record]: corona_hues = 0.536118 13:02:32.872 [planetinfo.record]: sun_color = 0.815665, 0.399675, 0.222464, 1 13:02:32.872 [planetinfo.record]: corona_shimmer = 0.623772 13:02:32.873 [planetinfo.record]: station_vector = 0.113 -0.982 0.149 13:02:32.873 [planetinfo.record]: station = coriolis 13:02:38.116 [PLANETINFO OVER]: Done 13:02:38.117 [PLANETINFO LOGGING]: 3 201 13:02:39.121 [planetinfo.record]: seed = 18 201 138 187 13:02:39.121 [planetinfo.record]: coordinates = 201 234 13:02:39.121 [planetinfo.record]: government = 2; 13:02:39.121 [planetinfo.record]: economy = 2; 13:02:39.121 [planetinfo.record]: techlevel = 7; 13:02:39.121 [planetinfo.record]: population = 33; 13:02:39.121 [planetinfo.record]: productivity = 12672; 13:02:39.121 [planetinfo.record]: name = "Anrebeis"; 13:02:39.121 [planetinfo.record]: inhabitant = "Harmless Bony Feline"; 13:02:39.121 [planetinfo.record]: inhabitants = "Harmless Bony Felines"; 13:02:39.121 [planetinfo.record]: description = "The world Anrebeis is fabled for its fabulous Bith juice and its unusual dense forests."; 13:02:39.136 [planetinfo.record]: air_color = 0.689799, 0.765234, 0.963879, 1; 13:02:39.136 [planetinfo.record]: cloud_alpha = 1.000000; 13:02:39.136 [planetinfo.record]: cloud_color = 0.667404, 0.851945, 0.894531, 1; 13:02:39.136 [planetinfo.record]: cloud_fraction = 0.260000; 13:02:39.136 [planetinfo.record]: land_color = 0.541016, 0.464935, 0.491682, 1; 13:02:39.136 [planetinfo.record]: land_fraction = 0.600000; 13:02:39.136 [planetinfo.record]: polar_cloud_color = 0.759314, 0.875684, 0.902539, 1; 13:02:39.136 [planetinfo.record]: polar_land_color = 0.945898, 0.912644, 0.924335, 1; 13:02:39.136 [planetinfo.record]: polar_sea_color = 0.899808, 0.923437, 0.853097, 1; 13:02:39.136 [planetinfo.record]: sea_color = 0.687259, 0.765625, 0.532349, 1; 13:02:39.136 [planetinfo.record]: rotation_speed = 0.001991 13:02:39.136 [planetinfo.record]: planet zpos = 641630.000000 13:02:39.136 [planetinfo.record]: sun_radius = 144647.470245 13:02:39.136 [planetinfo.record]: sun_vector = 0.201 -0.366 0.908 13:02:39.136 [planetinfo.record]: sun_distance = 1224930 13:02:39.137 [planetinfo.record]: corona_flare = 0.047586 13:02:39.137 [planetinfo.record]: corona_hues = 0.737244 13:02:39.137 [planetinfo.record]: sun_color = 0.694826, 0.708871, 0.836243, 1 13:02:39.137 [planetinfo.record]: corona_shimmer = 0.334562 13:02:39.137 [planetinfo.record]: station_vector = -0.937 -0.305 0.170 13:02:39.137 [planetinfo.record]: station = coriolis 13:02:43.948 [PLANETINFO OVER]: Done 13:02:43.949 [PLANETINFO LOGGING]: 3 202 13:02:44.953 [planetinfo.record]: seed = 242 29 90 128 13:02:44.953 [planetinfo.record]: coordinates = 29 243 13:02:44.953 [planetinfo.record]: government = 6; 13:02:44.953 [planetinfo.record]: economy = 3; 13:02:44.953 [planetinfo.record]: techlevel = 8; 13:02:44.953 [planetinfo.record]: population = 42; 13:02:44.953 [planetinfo.record]: productivity = 23520; 13:02:44.953 [planetinfo.record]: name = "Ater"; 13:02:44.953 [planetinfo.record]: inhabitant = "Human Colonial"; 13:02:44.953 [planetinfo.record]: inhabitants = "Human Colonials"; 13:02:44.953 [planetinfo.record]: description = "This planet is mildly fabled for its inhabitants’ eccentric shyness but ravaged by killer disease."; 13:02:44.966 [planetinfo.record]: air_color = 0.611977, 0.907945, 0.849882, 1; 13:02:44.968 [planetinfo.record]: cloud_alpha = 1.000000; 13:02:44.968 [planetinfo.record]: cloud_color = 0.726562, 0.567405, 0.451263, 1; 13:02:44.968 [planetinfo.record]: cloud_fraction = 0.510000; 13:02:44.968 [planetinfo.record]: land_color = 0.529362, 0.482109, 0.66, 1; 13:02:44.968 [planetinfo.record]: land_fraction = 0.380000; 13:02:44.968 [planetinfo.record]: polar_cloud_color = 0.826953, 0.713735, 0.631117, 1; 13:02:44.968 [planetinfo.record]: polar_land_color = 0.887782, 0.871064, 0.934, 1; 13:02:44.968 [planetinfo.record]: polar_sea_color = 0.941992, 0.928359, 0.892133, 1; 13:02:44.968 [planetinfo.record]: sea_color = 0.580078, 0.546496, 0.457265, 1; 13:02:44.968 [planetinfo.record]: rotation_speed = 0.000062 13:02:44.968 [planetinfo.record]: planet zpos = 341400.000000 13:02:44.968 [planetinfo.record]: sun_radius = 78425.470734 13:02:44.968 [planetinfo.record]: sun_vector = -0.945 0.256 0.203 13:02:44.968 [planetinfo.record]: sun_distance = 625900 13:02:44.968 [planetinfo.record]: corona_flare = 0.098813 13:02:44.968 [planetinfo.record]: corona_hues = 0.665886 13:02:44.969 [planetinfo.record]: sun_color = 0.784232, 0.757902, 0.714722, 1 13:02:44.969 [planetinfo.record]: corona_shimmer = 1.079564 13:02:44.969 [planetinfo.record]: station_vector = 0.074 0.443 0.894 13:02:44.970 [planetinfo.record]: station = coriolis 13:02:49.204 [PLANETINFO OVER]: Done 13:02:49.205 [PLANETINFO LOGGING]: 3 203 13:02:50.207 [planetinfo.record]: seed = 66 66 42 4 13:02:50.207 [planetinfo.record]: coordinates = 66 48 13:02:50.207 [planetinfo.record]: government = 0; 13:02:50.207 [planetinfo.record]: economy = 2; 13:02:50.207 [planetinfo.record]: techlevel = 7; 13:02:50.207 [planetinfo.record]: population = 31; 13:02:50.207 [planetinfo.record]: productivity = 7936; 13:02:50.207 [planetinfo.record]: name = "Zavete"; 13:02:50.207 [planetinfo.record]: inhabitant = "Human Colonial"; 13:02:50.207 [planetinfo.record]: inhabitants = "Human Colonials"; 13:02:50.207 [planetinfo.record]: description = "The planet Zavete is scourged by killer edible poets."; 13:02:50.236 [planetinfo.record]: air_color = 0.53534, 0.956725, 0.831303, 1; 13:02:50.236 [planetinfo.record]: cloud_alpha = 1.000000; 13:02:50.236 [planetinfo.record]: cloud_color = 0.873047, 0.316069, 0.242134, 1; 13:02:50.236 [planetinfo.record]: cloud_fraction = 0.230000; 13:02:50.236 [planetinfo.record]: land_color = 0.66, 0.265305, 0.110859, 1; 13:02:50.236 [planetinfo.record]: land_fraction = 0.460000; 13:02:50.236 [planetinfo.record]: polar_cloud_color = 0.892871, 0.536856, 0.489597, 1; 13:02:50.236 [planetinfo.record]: polar_land_color = 0.934, 0.794362, 0.739721, 1; 13:02:50.236 [planetinfo.record]: polar_sea_color = 0.945117, 0.88374, 0.92258, 1; 13:02:50.236 [planetinfo.record]: sea_color = 0.548828, 0.406261, 0.496479, 1; 13:02:50.236 [planetinfo.record]: rotation_speed = 0.002511 13:02:50.236 [planetinfo.record]: planet zpos = 468720.000000 13:02:50.236 [planetinfo.record]: sun_radius = 78584.290466 13:02:50.236 [planetinfo.record]: sun_vector = 0.490 -0.798 -0.350 13:02:50.236 [planetinfo.record]: sun_distance = 703080 13:02:50.236 [planetinfo.record]: corona_flare = 0.046573 13:02:50.236 [planetinfo.record]: corona_hues = 0.731094 13:02:50.236 [planetinfo.record]: sun_color = 0.694781, 0.700098, 0.399224, 1 13:02:50.237 [planetinfo.record]: corona_shimmer = 0.509366 13:02:50.237 [planetinfo.record]: station_vector = 0.701 -0.707 0.096 13:02:50.237 [planetinfo.record]: station = coriolis 13:02:54.991 [PLANETINFO OVER]: Done 13:02:54.992 [PLANETINFO LOGGING]: 3 204 13:02:55.995 [planetinfo.record]: seed = 162 55 26 107 13:02:55.995 [planetinfo.record]: coordinates = 55 188 13:02:55.995 [planetinfo.record]: government = 4; 13:02:55.995 [planetinfo.record]: economy = 4; 13:02:55.995 [planetinfo.record]: techlevel = 8; 13:02:55.995 [planetinfo.record]: population = 41; 13:02:55.995 [planetinfo.record]: productivity = 15744; 13:02:55.995 [planetinfo.record]: name = "Maonxedi"; 13:02:55.996 [planetinfo.record]: inhabitant = "Human Colonial"; 13:02:55.996 [planetinfo.record]: inhabitants = "Human Colonials"; 13:02:55.996 [planetinfo.record]: description = "This planet is a dull world."; 13:02:56.024 [planetinfo.record]: air_color = 0.492081, 0.997699, 0.898506, 1; 13:02:56.024 [planetinfo.record]: cloud_alpha = 1.000000; 13:02:56.024 [planetinfo.record]: cloud_color = 0.996094, 0.465217, 0.0778198, 1; 13:02:56.024 [planetinfo.record]: cloud_fraction = 0.260000; 13:02:56.024 [planetinfo.record]: land_color = 0.583845, 0.66, 0.146953, 1; 13:02:56.024 [planetinfo.record]: land_fraction = 0.420000; 13:02:56.024 [planetinfo.record]: polar_cloud_color = 0.948242, 0.632383, 0.401892, 1; 13:02:56.024 [planetinfo.record]: polar_land_color = 0.907057, 0.934, 0.75249, 1; 13:02:56.024 [planetinfo.record]: polar_sea_color = 0.949219, 0.891747, 0.936198, 1; 13:02:56.025 [planetinfo.record]: sea_color = 0.507812, 0.384827, 0.479949, 1; 13:02:56.025 [planetinfo.record]: rotation_speed = 0.004432 13:02:56.025 [planetinfo.record]: planet zpos = 682440.000000 13:02:56.025 [planetinfo.record]: sun_radius = 146066.068726 13:02:56.025 [planetinfo.record]: sun_vector = 0.920 0.391 0.028 13:02:56.025 [planetinfo.record]: sun_distance = 1194270 13:02:56.025 [planetinfo.record]: corona_flare = 0.086819 13:02:56.025 [planetinfo.record]: corona_hues = 0.771973 13:02:56.025 [planetinfo.record]: sun_color = 0.753888, 0.794882, 0.815674, 1 13:02:56.025 [planetinfo.record]: corona_shimmer = 0.420076 13:02:56.025 [planetinfo.record]: station_vector = 0.469 0.028 0.883 13:02:56.025 [planetinfo.record]: station = coriolis 13:03:00.749 [PLANETINFO OVER]: Done 13:03:00.751 [PLANETINFO LOGGING]: 3 205 13:03:01.755 [planetinfo.record]: seed = 50 205 74 47 13:03:01.756 [planetinfo.record]: coordinates = 205 2 13:03:01.756 [planetinfo.record]: government = 6; 13:03:01.756 [planetinfo.record]: economy = 2; 13:03:01.756 [planetinfo.record]: techlevel = 9; 13:03:01.756 [planetinfo.record]: population = 45; 13:03:01.756 [planetinfo.record]: productivity = 28800; 13:03:01.756 [planetinfo.record]: name = "Arianes"; 13:03:01.756 [planetinfo.record]: inhabitant = "Human Colonial"; 13:03:01.756 [planetinfo.record]: inhabitants = "Human Colonials"; 13:03:01.756 [planetinfo.record]: description = "This world is reasonably well known for the Arianesian spotted leopard but plagued by vicious disease."; 13:03:01.767 [planetinfo.record]: air_color = 0.503772, 0.624977, 0.854613, 1; 13:03:01.767 [planetinfo.record]: cloud_alpha = 1.000000; 13:03:01.767 [planetinfo.record]: cloud_color = 0.207977, 0.566406, 0.558006, 1; 13:03:01.768 [planetinfo.record]: cloud_fraction = 0.310000; 13:03:01.768 [planetinfo.record]: land_color = 0.66, 0.368672, 0.505232, 1; 13:03:01.768 [planetinfo.record]: land_fraction = 0.360000; 13:03:01.768 [planetinfo.record]: polar_cloud_color = 0.456321, 0.754883, 0.747885, 1; 13:03:01.768 [planetinfo.record]: polar_land_color = 0.934, 0.830932, 0.879245, 1; 13:03:01.768 [planetinfo.record]: polar_sea_color = 0.925413, 0.931445, 0.872047, 1; 13:03:01.768 [planetinfo.record]: sea_color = 0.667787, 0.685547, 0.510679, 1; 13:03:01.768 [planetinfo.record]: rotation_speed = 0.000677 13:03:01.769 [planetinfo.record]: planet zpos = 823320.000000 13:03:01.769 [planetinfo.record]: sun_radius = 168439.769440 13:03:01.769 [planetinfo.record]: sun_vector = -0.134 -0.575 -0.807 13:03:01.769 [planetinfo.record]: sun_distance = 1166370 13:03:01.769 [planetinfo.record]: corona_flare = 0.065274 13:03:01.769 [planetinfo.record]: corona_hues = 0.697433 13:03:01.769 [planetinfo.record]: sun_color = 0.707532, 0.654538, 0.588748, 1 13:03:01.769 [planetinfo.record]: corona_shimmer = 0.373625 13:03:01.770 [planetinfo.record]: station_vector = 0.829 0.462 0.316 13:03:01.770 [planetinfo.record]: station = coriolis 13:03:07.000 [PLANETINFO OVER]: Done 13:03:07.001 [PLANETINFO LOGGING]: 3 206 13:03:08.006 [planetinfo.record]: seed = 146 41 218 35 13:03:08.006 [planetinfo.record]: coordinates = 41 251 13:03:08.006 [planetinfo.record]: government = 2; 13:03:08.006 [planetinfo.record]: economy = 3; 13:03:08.007 [planetinfo.record]: techlevel = 6; 13:03:08.007 [planetinfo.record]: population = 30; 13:03:08.007 [planetinfo.record]: productivity = 10080; 13:03:08.007 [planetinfo.record]: name = "Geusvexe"; 13:03:08.007 [planetinfo.record]: inhabitant = "Large Red Horned Humanoid"; 13:03:08.007 [planetinfo.record]: inhabitants = "Large Red Horned Humanoids"; 13:03:08.007 [planetinfo.record]: description = "The planet Geusvexe is cursed by vicious mountain monkeys."; 13:03:08.028 [planetinfo.record]: air_color = 0.626562, 0.888434, 0.824796, 1; 13:03:08.028 [planetinfo.record]: cloud_alpha = 1.000000; 13:03:08.028 [planetinfo.record]: cloud_color = 0.667969, 0.531065, 0.477493, 1; 13:03:08.028 [planetinfo.record]: cloud_fraction = 0.290000; 13:03:08.028 [planetinfo.record]: land_color = 0.0386719, 0.645438, 0.66, 1; 13:03:08.028 [planetinfo.record]: land_fraction = 0.520000; 13:03:08.028 [planetinfo.record]: polar_cloud_color = 0.800586, 0.698033, 0.657903, 1; 13:03:08.028 [planetinfo.record]: polar_land_color = 0.714182, 0.928848, 0.934, 1; 13:03:08.028 [planetinfo.record]: polar_sea_color = 0.926938, 0.932813, 0.874967, 1; 13:03:08.028 [planetinfo.record]: sea_color = 0.654949, 0.671875, 0.505219, 1; 13:03:08.028 [planetinfo.record]: rotation_speed = 0.001272 13:03:08.028 [planetinfo.record]: planet zpos = 471250.000000 13:03:08.028 [planetinfo.record]: sun_radius = 79424.648285 13:03:08.028 [planetinfo.record]: sun_vector = 0.388 0.272 0.881 13:03:08.029 [planetinfo.record]: sun_distance = 797500 13:03:08.029 [planetinfo.record]: corona_flare = 0.051393 13:03:08.029 [planetinfo.record]: corona_hues = 0.706055 13:03:08.029 [planetinfo.record]: sun_color = 0.408332, 0.603294, 0.81123, 1 13:03:08.029 [planetinfo.record]: corona_shimmer = 0.543515 13:03:08.029 [planetinfo.record]: station_vector = -0.607 -0.207 0.767 13:03:08.029 [planetinfo.record]: station = coriolis 13:03:12.484 [PLANETINFO OVER]: Done 13:03:12.485 [PLANETINFO LOGGING]: 3 207 13:03:13.489 [planetinfo.record]: seed = 226 2 234 225 13:03:13.489 [planetinfo.record]: coordinates = 2 150 13:03:13.489 [planetinfo.record]: government = 4; 13:03:13.489 [planetinfo.record]: economy = 6; 13:03:13.489 [planetinfo.record]: techlevel = 5; 13:03:13.489 [planetinfo.record]: population = 31; 13:03:13.489 [planetinfo.record]: productivity = 7936; 13:03:13.489 [planetinfo.record]: name = "Leanon"; 13:03:13.489 [planetinfo.record]: inhabitant = "Large Fat Humanoid"; 13:03:13.489 [planetinfo.record]: inhabitants = "Large Fat Humanoids"; 13:03:13.489 [planetinfo.record]: description = "This world is a tedious little planet."; 13:03:13.509 [planetinfo.record]: air_color = 0.878948, 0.774272, 0.995098, 1; 13:03:13.510 [planetinfo.record]: cloud_alpha = 1.000000; 13:03:13.510 [planetinfo.record]: cloud_color = 0.988281, 0.922653, 0.959569, 1; 13:03:13.510 [planetinfo.record]: cloud_fraction = 0.480000; 13:03:13.510 [planetinfo.record]: land_color = 0.271759, 0.441422, 0.507812, 1; 13:03:13.510 [planetinfo.record]: land_fraction = 0.600000; 13:03:13.510 [planetinfo.record]: polar_cloud_color = 0.944727, 0.905517, 0.927572, 1; 13:03:13.510 [planetinfo.record]: polar_land_color = 0.838909, 0.918194, 0.949219, 1; 13:03:13.510 [planetinfo.record]: polar_sea_color = 0.929924, 0.939258, 0.891469, 1; 13:03:13.510 [planetinfo.record]: sea_color = 0.583277, 0.607422, 0.483802, 1; 13:03:13.510 [planetinfo.record]: rotation_speed = 0.001353 13:03:13.510 [planetinfo.record]: planet zpos = 368880.000000 13:03:13.510 [planetinfo.record]: sun_radius = 69588.087769 13:03:13.513 [planetinfo.record]: sun_vector = 0.844 0.033 -0.535 13:03:13.513 [planetinfo.record]: sun_distance = 645540 13:03:13.513 [planetinfo.record]: corona_flare = 0.033801 13:03:13.513 [planetinfo.record]: corona_hues = 0.902550 13:03:13.513 [planetinfo.record]: sun_color = 0.730609, 0.683139, 0.226398, 1 13:03:13.514 [planetinfo.record]: corona_shimmer = 0.868210 13:03:13.514 [planetinfo.record]: station_vector = -0.512 -0.670 0.538 13:03:13.514 [planetinfo.record]: station = coriolis 13:03:18.224 [PLANETINFO OVER]: Done 13:03:18.226 [PLANETINFO LOGGING]: 3 208 13:03:19.228 [planetinfo.record]: seed = 194 188 154 151 13:03:19.228 [planetinfo.record]: coordinates = 188 95 13:03:19.228 [planetinfo.record]: government = 0; 13:03:19.228 [planetinfo.record]: economy = 7; 13:03:19.228 [planetinfo.record]: techlevel = 0; 13:03:19.228 [planetinfo.record]: population = 8; 13:03:19.228 [planetinfo.record]: productivity = 768; 13:03:19.228 [planetinfo.record]: name = "Tirausra"; 13:03:19.228 [planetinfo.record]: inhabitant = "Black Bony Feline"; 13:03:19.228 [planetinfo.record]: inhabitants = "Black Bony Felines"; 13:03:19.228 [planetinfo.record]: description = "The planet Tirausra is mildly well known for its exotic cuisine."; 13:03:19.240 [planetinfo.record]: air_color = 0.645096, 0.868271, 0.812296, 1; 13:03:19.240 [planetinfo.record]: cloud_alpha = 1.000000; 13:03:19.240 [planetinfo.record]: cloud_color = 0.607422, 0.529937, 0.503021, 1; 13:03:19.240 [planetinfo.record]: cloud_fraction = 0.190000; 13:03:19.240 [planetinfo.record]: land_color = 0.66, 0.185746, 0.162422, 1; 13:03:19.240 [planetinfo.record]: land_fraction = 0.600000; 13:03:19.240 [planetinfo.record]: polar_cloud_color = 0.77334, 0.711684, 0.690266, 1; 13:03:19.240 [planetinfo.record]: polar_land_color = 0.934, 0.766215, 0.757963, 1; 13:03:19.240 [planetinfo.record]: polar_sea_color = 0.870134, 0.923047, 0.904445, 1; 13:03:19.240 [planetinfo.record]: sea_color = 0.59308, 0.769531, 0.707498, 1; 13:03:19.240 [planetinfo.record]: rotation_speed = 0.000722 13:03:19.240 [planetinfo.record]: planet zpos = 575520.000000 13:03:19.240 [planetinfo.record]: sun_radius = 111634.920654 13:03:19.240 [planetinfo.record]: sun_vector = 0.495 0.383 0.780 13:03:19.240 [planetinfo.record]: sun_distance = 1103080 13:03:19.240 [planetinfo.record]: corona_flare = 0.054890 13:03:19.240 [planetinfo.record]: corona_hues = 0.886642 13:03:19.240 [planetinfo.record]: sun_color = 0.403796, 0.51173, 0.65155, 1 13:03:19.240 [planetinfo.record]: corona_shimmer = 0.242037 13:03:19.241 [planetinfo.record]: station_vector = -0.832 -0.492 0.256 13:03:19.241 [planetinfo.record]: station = coriolis 13:03:24.192 [PLANETINFO OVER]: Done 13:03:24.192 [PLANETINFO LOGGING]: 3 209 13:03:25.200 [planetinfo.record]: seed = 82 84 10 17 13:03:25.200 [planetinfo.record]: coordinates = 84 8 13:03:25.200 [planetinfo.record]: government = 2; 13:03:25.200 [planetinfo.record]: economy = 0; 13:03:25.200 [planetinfo.record]: techlevel = 8; 13:03:25.200 [planetinfo.record]: population = 35; 13:03:25.200 [planetinfo.record]: productivity = 16800; 13:03:25.200 [planetinfo.record]: name = "Atdibe"; 13:03:25.200 [planetinfo.record]: inhabitant = "Human Colonial"; 13:03:25.200 [planetinfo.record]: inhabitants = "Human Colonials"; 13:03:25.200 [planetinfo.record]: description = "The planet Atdibe is very noted for its exciting Zero-G hockey but ravaged by vicious disease."; 13:03:25.216 [planetinfo.record]: air_color = 0.666089, 0.846158, 0.800994, 1; 13:03:25.216 [planetinfo.record]: cloud_alpha = 1.000000; 13:03:25.216 [planetinfo.record]: cloud_color = 0.541016, 0.525331, 0.519882, 1; 13:03:25.216 [planetinfo.record]: cloud_fraction = 0.210000; 13:03:25.216 [planetinfo.record]: land_color = 0.66, 0.335217, 0.327422, 1; 13:03:25.216 [planetinfo.record]: land_fraction = 0.290000; 13:03:25.216 [planetinfo.record]: polar_cloud_color = 0.743457, 0.729986, 0.725306, 1; 13:03:25.216 [planetinfo.record]: polar_land_color = 0.934, 0.819096, 0.816338, 1; 13:03:25.216 [planetinfo.record]: polar_sea_color = 0.876921, 0.928516, 0.883774, 1; 13:03:25.216 [planetinfo.record]: sea_color = 0.555959, 0.714844, 0.57706, 1; 13:03:25.216 [planetinfo.record]: rotation_speed = 0.004395 13:03:25.216 [planetinfo.record]: planet zpos = 347160.000000 13:03:25.216 [planetinfo.record]: sun_radius = 76649.635620 13:03:25.216 [planetinfo.record]: sun_vector = -0.583 -0.704 -0.406 13:03:25.216 [planetinfo.record]: sun_distance = 694320 13:03:25.216 [planetinfo.record]: corona_flare = 0.085764 13:03:25.216 [planetinfo.record]: corona_hues = 0.562737 13:03:25.216 [planetinfo.record]: sun_color = 0.682697, 0.596935, 0.54712, 1 13:03:25.216 [planetinfo.record]: corona_shimmer = 0.385951 13:03:25.216 [planetinfo.record]: station_vector = -0.860 -0.051 0.508 13:03:25.217 [planetinfo.record]: station = coriolis 13:03:29.878 [PLANETINFO OVER]: Done 13:03:29.879 [PLANETINFO LOGGING]: 3 210 13:03:30.882 [planetinfo.record]: seed = 50 82 90 147 13:03:30.882 [planetinfo.record]: coordinates = 82 211 13:03:30.882 [planetinfo.record]: government = 6; 13:03:30.882 [planetinfo.record]: economy = 3; 13:03:30.882 [planetinfo.record]: techlevel = 9; 13:03:30.882 [planetinfo.record]: population = 46; 13:03:30.882 [planetinfo.record]: productivity = 25760; 13:03:30.882 [planetinfo.record]: name = "Beedriar"; 13:03:30.882 [planetinfo.record]: inhabitant = "Human Colonial"; 13:03:30.882 [planetinfo.record]: inhabitants = "Human Colonials"; 13:03:30.882 [planetinfo.record]: description = "This world is very notable for its inhabitants’ ingrained silliness and its weird rock formations."; 13:03:30.884 [planetinfo.record]: air_color = 0.573807, 0.888863, 0.993146, 1; 13:03:30.884 [planetinfo.record]: cloud_alpha = 1.000000; 13:03:30.884 [planetinfo.record]: cloud_color = 0.485125, 0.982422, 0.326195, 1; 13:03:30.884 [planetinfo.record]: cloud_fraction = 0.500000; 13:03:30.884 [planetinfo.record]: land_color = 0.0257813, 0.526219, 0.66, 1; 13:03:30.884 [planetinfo.record]: land_fraction = 0.610000; 13:03:30.884 [planetinfo.record]: polar_cloud_color = 0.644039, 0.94209, 0.548786, 1; 13:03:30.884 [planetinfo.record]: polar_land_color = 0.709621, 0.88667, 0.934, 1; 13:03:30.884 [planetinfo.record]: polar_sea_color = 0.93302, 0.937109, 0.884763, 1; 13:03:30.884 [planetinfo.record]: sea_color = 0.617928, 0.628906, 0.488385, 1; 13:03:30.884 [planetinfo.record]: rotation_speed = 0.002579 13:03:30.885 [planetinfo.record]: planet zpos = 403260.000000 13:03:30.885 [planetinfo.record]: sun_radius = 126794.620056 13:03:30.885 [planetinfo.record]: sun_vector = 0.023 -0.140 -0.990 13:03:30.885 [planetinfo.record]: sun_distance = 733200 13:03:30.885 [planetinfo.record]: corona_flare = 0.061989 13:03:30.885 [planetinfo.record]: corona_hues = 0.580925 13:03:30.885 [planetinfo.record]: sun_color = 0.400453, 0.662238, 0.69328, 1 13:03:30.885 [planetinfo.record]: corona_shimmer = 0.282334 13:03:30.885 [planetinfo.record]: station_vector = -0.851 -0.262 0.455 13:03:30.885 [planetinfo.record]: station = coriolis 13:03:35.944 [PLANETINFO OVER]: Done 13:03:35.945 [PLANETINFO LOGGING]: 3 211 13:03:36.949 [planetinfo.record]: seed = 130 234 170 65 13:03:36.949 [planetinfo.record]: coordinates = 234 158 13:03:36.949 [planetinfo.record]: government = 0; 13:03:36.949 [planetinfo.record]: economy = 6; 13:03:36.949 [planetinfo.record]: techlevel = 3; 13:03:36.949 [planetinfo.record]: population = 19; 13:03:36.949 [planetinfo.record]: productivity = 2432; 13:03:36.949 [planetinfo.record]: name = "Learvexe"; 13:03:36.949 [planetinfo.record]: inhabitant = "Large Yellow Fat Humanoid"; 13:03:36.949 [planetinfo.record]: inhabitants = "Large Yellow Fat Humanoids"; 13:03:36.949 [planetinfo.record]: description = "Learvexe is mildly notable for its inhabitants’ unusual mating traditions."; 13:03:36.974 [planetinfo.record]: air_color = 0.603467, 0.904043, 0.819271, 1; 13:03:36.974 [planetinfo.record]: cloud_alpha = 1.000000; 13:03:36.974 [planetinfo.record]: cloud_color = 0.714844, 0.476752, 0.430023, 1; 13:03:36.974 [planetinfo.record]: cloud_fraction = 0.250000; 13:03:36.974 [planetinfo.record]: land_color = 0.66, 0.294954, 0.152109, 1; 13:03:36.974 [planetinfo.record]: land_fraction = 0.670000; 13:03:36.974 [planetinfo.record]: polar_cloud_color = 0.82168, 0.650632, 0.617062, 1; 13:03:36.974 [planetinfo.record]: polar_land_color = 0.934, 0.804851, 0.754314, 1; 13:03:36.974 [planetinfo.record]: polar_sea_color = 0.921887, 0.937305, 0.887968, 1; 13:03:36.974 [planetinfo.record]: sea_color = 0.585702, 0.626953, 0.49495, 1; 13:03:36.974 [planetinfo.record]: rotation_speed = 0.000065 13:03:36.974 [planetinfo.record]: planet zpos = 429780.000000 13:03:36.974 [planetinfo.record]: sun_radius = 86318.400879 13:03:36.974 [planetinfo.record]: sun_vector = 0.276 0.961 0.030 13:03:36.974 [planetinfo.record]: sun_distance = 661200 13:03:36.974 [planetinfo.record]: corona_flare = 0.048801 13:03:36.974 [planetinfo.record]: corona_hues = 0.628777 13:03:36.974 [planetinfo.record]: sun_color = 0.828058, 0.451135, 0.209393, 1 13:03:36.974 [planetinfo.record]: corona_shimmer = 0.553028 13:03:36.974 [planetinfo.record]: station_vector = 0.569 0.735 0.370 13:03:36.974 [planetinfo.record]: station = coriolis 13:03:42.742 [PLANETINFO OVER]: Done 13:03:42.743 [PLANETINFO LOGGING]: 3 212 13:03:43.747 [planetinfo.record]: seed = 226 2 26 196 13:03:43.747 [planetinfo.record]: coordinates = 2 246 13:03:43.747 [planetinfo.record]: government = 4; 13:03:43.747 [planetinfo.record]: economy = 6; 13:03:43.747 [planetinfo.record]: techlevel = 5; 13:03:43.747 [planetinfo.record]: population = 31; 13:03:43.747 [planetinfo.record]: productivity = 7936; 13:03:43.747 [planetinfo.record]: name = "Zaisza"; 13:03:43.747 [planetinfo.record]: inhabitant = "Human Colonial"; 13:03:43.747 [planetinfo.record]: inhabitants = "Human Colonials"; 13:03:43.747 [planetinfo.record]: description = "This planet is most notable for its exotic cuisine but ravaged by vicious disease."; 13:03:43.764 [planetinfo.record]: air_color = 0.808899, 0.48019, 0.989244, 1; 13:03:43.764 [planetinfo.record]: cloud_alpha = 1.000000; 13:03:43.764 [planetinfo.record]: cloud_color = 0.970703, 0.0530853, 0.0960987, 1; 13:03:43.764 [planetinfo.record]: cloud_fraction = 0.380000; 13:03:43.764 [planetinfo.record]: land_color = 0.66, 0.350625, 0.394131, 1; 13:03:43.764 [planetinfo.record]: land_fraction = 0.300000; 13:03:43.764 [planetinfo.record]: polar_cloud_color = 0.936816, 0.383326, 0.409271, 1; 13:03:43.764 [planetinfo.record]: polar_land_color = 0.934, 0.824547, 0.839939, 1; 13:03:43.764 [planetinfo.record]: polar_sea_color = 0.927148, 0.83203, 0.824836, 1; 13:03:43.764 [planetinfo.record]: sea_color = 0.728516, 0.429555, 0.406944, 1; 13:03:43.764 [planetinfo.record]: rotation_speed = 0.001964 13:03:43.764 [planetinfo.record]: planet zpos = 499460.000000 13:03:43.764 [planetinfo.record]: sun_radius = 126264.361267 13:03:43.764 [planetinfo.record]: sun_vector = -0.361 0.933 -0.002 13:03:43.764 [planetinfo.record]: sun_distance = 729980 13:03:43.764 [planetinfo.record]: corona_flare = 0.000867 13:03:43.764 [planetinfo.record]: corona_hues = 0.803787 13:03:43.764 [planetinfo.record]: sun_color = 0.699774, 0.45683, 0.32469, 1 13:03:43.764 [planetinfo.record]: corona_shimmer = 0.417342 13:03:43.765 [planetinfo.record]: station_vector = 0.999 -0.022 0.028 13:03:43.765 [planetinfo.record]: station = coriolis 13:03:48.127 [PLANETINFO OVER]: Done 13:03:48.127 [PLANETINFO LOGGING]: 3 213 13:03:49.130 [planetinfo.record]: seed = 114 6 202 72 13:03:49.130 [planetinfo.record]: coordinates = 6 132 13:03:49.130 [planetinfo.record]: government = 6; 13:03:49.130 [planetinfo.record]: economy = 4; 13:03:49.130 [planetinfo.record]: techlevel = 8; 13:03:49.130 [planetinfo.record]: population = 43; 13:03:49.130 [planetinfo.record]: productivity = 20640; 13:03:49.130 [planetinfo.record]: name = "Usbege"; 13:03:49.130 [planetinfo.record]: inhabitant = "Small Yellow Horned Lizard"; 13:03:49.130 [planetinfo.record]: inhabitants = "Small Yellow Horned Lizards"; 13:03:49.130 [planetinfo.record]: description = "The planet Usbege is reasonably noted for mud tennis and its ancient A’ce tulip plantations."; 13:03:49.140 [planetinfo.record]: air_color = 0.473172, 0.566355, 0.895588, 1; 13:03:49.141 [planetinfo.record]: cloud_alpha = 1.000000; 13:03:49.141 [planetinfo.record]: cloud_color = 0.126579, 0.491568, 0.689453, 1; 13:03:49.141 [planetinfo.record]: cloud_fraction = 0.300000; 13:03:49.141 [planetinfo.record]: land_color = 0.66, 0.559473, 0.100547, 1; 13:03:49.141 [planetinfo.record]: land_fraction = 0.550000; 13:03:49.141 [planetinfo.record]: polar_cloud_color = 0.396819, 0.664906, 0.810254, 1; 13:03:49.141 [planetinfo.record]: polar_land_color = 0.934, 0.898435, 0.736072, 1; 13:03:49.141 [planetinfo.record]: polar_sea_color = 0.903596, 0.934766, 0.88611, 1; 13:03:49.141 [planetinfo.record]: sea_color = 0.565334, 0.652344, 0.516524, 1; 13:03:49.141 [planetinfo.record]: rotation_speed = 0.003159 13:03:49.141 [planetinfo.record]: planet zpos = 633100.000000 13:03:49.141 [planetinfo.record]: sun_radius = 161239.981079 13:03:49.141 [planetinfo.record]: sun_vector = -0.036 -0.435 -0.900 13:03:49.141 [planetinfo.record]: sun_distance = 925300 13:03:49.141 [planetinfo.record]: corona_flare = 0.056873 13:03:49.141 [planetinfo.record]: corona_hues = 0.814125 13:03:49.142 [planetinfo.record]: sun_color = 0.774844, 0.596339, 0.591786, 1 13:03:49.142 [planetinfo.record]: corona_shimmer = 0.644646 13:03:49.142 [planetinfo.record]: station_vector = 0.896 -0.436 0.090 13:03:49.142 [planetinfo.record]: station = coriolis 13:03:53.679 [PLANETINFO OVER]: Done 13:03:53.680 [PLANETINFO LOGGING]: 3 214 13:03:54.683 [planetinfo.record]: seed = 210 63 218 54 13:03:54.683 [planetinfo.record]: coordinates = 63 35 13:03:54.683 [planetinfo.record]: government = 2; 13:03:54.683 [planetinfo.record]: economy = 3; 13:03:54.683 [planetinfo.record]: techlevel = 8; 13:03:54.683 [planetinfo.record]: population = 38; 13:03:54.683 [planetinfo.record]: productivity = 12768; 13:03:54.683 [planetinfo.record]: name = "Veorer"; 13:03:54.683 [planetinfo.record]: inhabitant = "Red Fat Feline"; 13:03:54.683 [planetinfo.record]: inhabitants = "Red Fat Felines"; 13:03:54.683 [planetinfo.record]: description = "The world Veorer is very noted for its pink Veorerian Cenureit Cenureitweed plantations but scourged by deadly solar activity."; 13:03:54.692 [planetinfo.record]: air_color = 0.575243, 0.934611, 0.852891, 1; 13:03:54.692 [planetinfo.record]: cloud_alpha = 1.000000; 13:03:54.692 [planetinfo.record]: cloud_color = 0.806641, 0.506021, 0.359207, 1; 13:03:54.692 [planetinfo.record]: cloud_fraction = 0.220000; 13:03:54.692 [planetinfo.record]: land_color = 0.370542, 0.291718, 0.617188, 1; 13:03:54.692 [planetinfo.record]: land_fraction = 0.460000; 13:03:54.692 [planetinfo.record]: polar_cloud_color = 0.862988, 0.661976, 0.563808, 1; 13:03:54.692 [planetinfo.record]: polar_land_color = 0.84454, 0.814582, 0.938281, 1; 13:03:54.692 [planetinfo.record]: polar_sea_color = 0.919353, 0.933789, 0.87944, 1; 13:03:54.692 [planetinfo.record]: sea_color = 0.621164, 0.662109, 0.507962, 1; 13:03:54.692 [planetinfo.record]: rotation_speed = 0.003784 13:03:54.692 [planetinfo.record]: planet zpos = 529800.000000 13:03:54.692 [planetinfo.record]: sun_radius = 84476.217651 13:03:54.692 [planetinfo.record]: sun_vector = -0.403 -0.691 -0.600 13:03:54.692 [planetinfo.record]: sun_distance = 927150 13:03:54.692 [planetinfo.record]: corona_flare = 0.055219 13:03:54.692 [planetinfo.record]: corona_hues = 0.501419 13:03:54.693 [planetinfo.record]: sun_color = 0.690737, 0.7745, 0.823248, 1 13:03:54.693 [planetinfo.record]: corona_shimmer = 0.465580 13:03:54.693 [planetinfo.record]: station_vector = -0.430 0.893 0.130 13:03:54.693 [planetinfo.record]: station = coriolis 13:03:59.821 [PLANETINFO OVER]: Done 13:03:59.822 [PLANETINFO LOGGING]: 3 215 13:04:00.827 [planetinfo.record]: seed = 34 225 106 139 13:04:00.827 [planetinfo.record]: coordinates = 225 16 13:04:00.827 [planetinfo.record]: government = 4; 13:04:00.827 [planetinfo.record]: economy = 0; 13:04:00.827 [planetinfo.record]: techlevel = 10; 13:04:00.827 [planetinfo.record]: population = 45; 13:04:00.828 [planetinfo.record]: productivity = 28800; 13:04:00.828 [planetinfo.record]: name = "Maisesen"; 13:04:00.828 [planetinfo.record]: inhabitant = "Human Colonial"; 13:04:00.828 [planetinfo.record]: inhabitants = "Human Colonials"; 13:04:00.828 [planetinfo.record]: description = "The world Maisesen is reasonably fabled for Zero-G cricket and its fabulous vicious Th brew."; 13:04:00.840 [planetinfo.record]: air_color = 0.72152, 0.772121, 0.943066, 1; 13:04:00.840 [planetinfo.record]: cloud_alpha = 1.000000; 13:04:00.840 [planetinfo.record]: cloud_color = 0.744278, 0.803237, 0.832031, 1; 13:04:00.840 [planetinfo.record]: cloud_fraction = 0.220000; 13:04:00.840 [planetinfo.record]: land_color = 0.440859, 0.536733, 0.66, 1; 13:04:00.840 [planetinfo.record]: land_fraction = 0.360000; 13:04:00.840 [planetinfo.record]: polar_cloud_color = 0.816774, 0.855501, 0.874414, 1; 13:04:00.840 [planetinfo.record]: polar_land_color = 0.856471, 0.89039, 0.934, 1; 13:04:00.840 [planetinfo.record]: polar_sea_color = 0.934235, 0.935938, 0.881463, 1; 13:04:00.840 [planetinfo.record]: sea_color = 0.635964, 0.640625, 0.491479, 1; 13:04:00.840 [planetinfo.record]: rotation_speed = 0.003848 13:04:00.841 [planetinfo.record]: planet zpos = 702840.000000 13:04:00.841 [planetinfo.record]: sun_radius = 172051.162415 13:04:00.841 [planetinfo.record]: sun_vector = -0.680 0.712 -0.175 13:04:00.841 [planetinfo.record]: sun_distance = 995690 13:04:00.841 [planetinfo.record]: corona_flare = 0.094881 13:04:00.841 [planetinfo.record]: corona_hues = 0.673355 13:04:00.841 [planetinfo.record]: sun_color = 0.273279, 0.559363, 0.786484, 1 13:04:00.841 [planetinfo.record]: corona_shimmer = 0.685652 13:04:00.841 [planetinfo.record]: station_vector = 0.923 0.147 0.354 13:04:00.841 [planetinfo.record]: station = coriolis 13:04:05.830 [PLANETINFO OVER]: Done 13:04:05.831 [PLANETINFO LOGGING]: 3 216 13:04:06.835 [planetinfo.record]: seed = 2 242 154 88 13:04:06.835 [planetinfo.record]: coordinates = 242 233 13:04:06.835 [planetinfo.record]: government = 0; 13:04:06.835 [planetinfo.record]: economy = 3; 13:04:06.835 [planetinfo.record]: techlevel = 6; 13:04:06.835 [planetinfo.record]: population = 28; 13:04:06.835 [planetinfo.record]: productivity = 6272; 13:04:06.835 [planetinfo.record]: name = "Edrari"; 13:04:06.835 [planetinfo.record]: inhabitant = "Yellow Bony Lobster"; 13:04:06.835 [planetinfo.record]: inhabitants = "Yellow Bony Lobsters"; 13:04:06.835 [planetinfo.record]: description = "The planet Edrari is most famous for the Edrariian deadly goat and its pink oceans."; 13:04:06.844 [planetinfo.record]: air_color = 0.622916, 0.863928, 0.961277, 1; 13:04:06.844 [planetinfo.record]: cloud_alpha = 1.000000; 13:04:06.844 [planetinfo.record]: cloud_color = 0.532118, 0.886719, 0.481461, 1; 13:04:06.844 [planetinfo.record]: cloud_fraction = 0.560000; 13:04:06.844 [planetinfo.record]: land_color = 0.127819, 0.597656, 0.074707, 1; 13:04:06.844 [planetinfo.record]: land_fraction = 0.500000; 13:04:06.844 [planetinfo.record]: polar_cloud_color = 0.674322, 0.899023, 0.642222, 1; 13:04:06.844 [planetinfo.record]: polar_land_color = 0.755447, 0.940234, 0.734558, 1; 13:04:06.844 [planetinfo.record]: polar_sea_color = 0.905, 0.85975, 0.89233, 1; 13:04:06.844 [planetinfo.record]: sea_color = 0.95, 0.76, 0.8968, 1; 13:04:06.844 [planetinfo.record]: rotation_speed = 0.003202 13:04:06.845 [planetinfo.record]: planet zpos = 510600.000000 13:04:06.845 [planetinfo.record]: sun_radius = 106672.361755 13:04:06.845 [planetinfo.record]: sun_vector = -0.058 -0.986 -0.153 13:04:06.845 [planetinfo.record]: sun_distance = 1021200 13:04:06.845 [planetinfo.record]: corona_flare = 0.005463 13:04:06.845 [planetinfo.record]: corona_hues = 0.549133 13:04:06.845 [planetinfo.record]: sun_color = 0.362394, 0.461562, 0.760165, 1 13:04:06.845 [planetinfo.record]: corona_shimmer = 0.493628 13:04:06.845 [planetinfo.record]: station_vector = -0.477 -0.046 0.878 13:04:06.845 [planetinfo.record]: station = coriolis 13:04:11.715 [PLANETINFO OVER]: Done 13:04:11.716 [PLANETINFO LOGGING]: 3 217 13:04:12.719 [planetinfo.record]: seed = 146 11 138 190 13:04:12.719 [planetinfo.record]: coordinates = 11 126 13:04:12.719 [planetinfo.record]: government = 2; 13:04:12.719 [planetinfo.record]: economy = 6; 13:04:12.720 [planetinfo.record]: techlevel = 5; 13:04:12.720 [planetinfo.record]: population = 29; 13:04:12.720 [planetinfo.record]: productivity = 5568; 13:04:12.720 [planetinfo.record]: name = "Riusbequ"; 13:04:12.720 [planetinfo.record]: inhabitant = "Harmless Furry Insect"; 13:04:12.720 [planetinfo.record]: inhabitants = "Harmless Furry Insects"; 13:04:12.720 [planetinfo.record]: description = "This planet is a tedious place."; 13:04:12.736 [planetinfo.record]: air_color = 0.769406, 0.522527, 0.951522, 1; 13:04:12.736 [planetinfo.record]: cloud_alpha = 1.000000; 13:04:12.736 [planetinfo.record]: cloud_color = 0.857422, 0.211006, 0.37766, 1; 13:04:12.736 [planetinfo.record]: cloud_fraction = 0.520000; 13:04:12.736 [planetinfo.record]: land_color = 0.234222, 0.411207, 0.599609, 1; 13:04:12.736 [planetinfo.record]: land_fraction = 0.330000; 13:04:12.736 [planetinfo.record]: polar_cloud_color = 0.88584, 0.46844, 0.576051, 1; 13:04:12.736 [planetinfo.record]: polar_land_color = 0.79683, 0.866197, 0.940039, 1; 13:04:12.736 [planetinfo.record]: polar_sea_color = 0.936914, 0.897282, 0.875795, 1; 13:04:12.736 [planetinfo.record]: sea_color = 0.630859, 0.524117, 0.466244, 1; 13:04:12.736 [planetinfo.record]: rotation_speed = 0.001963 13:04:12.736 [planetinfo.record]: planet zpos = 833430.000000 13:04:12.736 [planetinfo.record]: sun_radius = 134460.199127 13:04:12.736 [planetinfo.record]: sun_vector = -0.460 -0.626 -0.629 13:04:12.736 [planetinfo.record]: sun_distance = 1025760 13:04:12.736 [planetinfo.record]: corona_flare = 0.021890 13:04:12.736 [planetinfo.record]: corona_hues = 0.550385 13:04:12.736 [planetinfo.record]: sun_color = 0.213545, 0.499991, 0.787125, 1 13:04:12.736 [planetinfo.record]: corona_shimmer = 0.780265 13:04:12.737 [planetinfo.record]: station_vector = 0.920 -0.322 0.225 13:04:12.737 [planetinfo.record]: station = coriolis 13:04:17.581 [PLANETINFO OVER]: Done 13:04:17.582 [PLANETINFO LOGGING]: 3 218 13:04:18.586 [planetinfo.record]: seed = 114 26 90 118 13:04:18.586 [planetinfo.record]: coordinates = 26 19 13:04:18.586 [planetinfo.record]: government = 6; 13:04:18.586 [planetinfo.record]: economy = 3; 13:04:18.586 [planetinfo.record]: techlevel = 9; 13:04:18.586 [planetinfo.record]: population = 46; 13:04:18.586 [planetinfo.record]: productivity = 25760; 13:04:18.586 [planetinfo.record]: name = "Vegera"; 13:04:18.586 [planetinfo.record]: inhabitant = "Human Colonial"; 13:04:18.586 [planetinfo.record]: inhabitants = "Human Colonials"; 13:04:18.586 [planetinfo.record]: description = "The world Vegera is mildly noted for its ancient mountains but plagued by vicious disease."; 13:04:18.589 [planetinfo.record]: air_color = 0.665528, 0.793933, 0.963229, 1; 13:04:18.589 [planetinfo.record]: cloud_alpha = 1.000000; 13:04:18.589 [planetinfo.record]: cloud_color = 0.599701, 0.892578, 0.810206, 1; 13:04:18.589 [planetinfo.record]: cloud_fraction = 0.090000; 13:04:18.589 [planetinfo.record]: land_color = 0.144375, 0.502896, 0.66, 1; 13:04:18.589 [planetinfo.record]: land_fraction = 0.250000; 13:04:18.589 [planetinfo.record]: polar_cloud_color = 0.716749, 0.90166, 0.849654, 1; 13:04:18.589 [planetinfo.record]: polar_land_color = 0.751578, 0.878418, 0.934, 1; 13:04:18.589 [planetinfo.record]: polar_sea_color = 0.884502, 0.931055, 0.897231, 1; 13:04:18.590 [planetinfo.record]: sea_color = 0.551562, 0.689453, 0.589267, 1; 13:04:18.590 [planetinfo.record]: rotation_speed = 0.000057 13:04:18.590 [planetinfo.record]: planet zpos = 656700.000000 13:04:18.590 [planetinfo.record]: sun_radius = 99196.410828 13:04:18.590 [planetinfo.record]: sun_vector = 0.727 -0.516 0.453 13:04:18.590 [planetinfo.record]: sun_distance = 1006940 13:04:18.590 [planetinfo.record]: corona_flare = 0.079636 13:04:18.590 [planetinfo.record]: corona_hues = 0.961128 13:04:18.590 [planetinfo.record]: sun_color = 0.366178, 0.400214, 0.793277, 1 13:04:18.590 [planetinfo.record]: corona_shimmer = 0.537242 13:04:18.590 [planetinfo.record]: station_vector = 0.016 -0.327 0.945 13:04:18.590 [planetinfo.record]: station = coriolis 13:04:23.691 [PLANETINFO OVER]: Done 13:04:23.691 [PLANETINFO LOGGING]: 3 219 13:04:24.704 [planetinfo.record]: seed = 194 78 42 39 13:04:24.704 [planetinfo.record]: coordinates = 78 52 13:04:24.704 [planetinfo.record]: government = 0; 13:04:24.704 [planetinfo.record]: economy = 6; 13:04:24.704 [planetinfo.record]: techlevel = 3; 13:04:24.704 [planetinfo.record]: population = 19; 13:04:24.704 [planetinfo.record]: productivity = 2432; 13:04:24.704 [planetinfo.record]: name = "Soar"; 13:04:24.704 [planetinfo.record]: inhabitant = "Human Colonial"; 13:04:24.704 [planetinfo.record]: inhabitants = "Human Colonials"; 13:04:24.704 [planetinfo.record]: description = "The world Soar is mildly noted for the Soarian mountain lobstoid but beset by dreadful earthquakes."; 13:04:24.717 [planetinfo.record]: air_color = 0.669379, 0.727812, 0.991195, 1; 13:04:24.717 [planetinfo.record]: cloud_alpha = 1.000000; 13:04:24.717 [planetinfo.record]: cloud_color = 0.610352, 0.804901, 0.976562, 1; 13:04:24.717 [planetinfo.record]: cloud_fraction = 0.330000; 13:04:24.717 [planetinfo.record]: land_color = 0.578125, 0.401978, 0.558859, 1; 13:04:24.717 [planetinfo.record]: land_fraction = 0.450000; 13:04:24.718 [planetinfo.record]: polar_cloud_color = 0.719269, 0.836242, 0.939453, 1; 13:04:24.718 [planetinfo.record]: polar_land_color = 0.942187, 0.870419, 0.934338, 1; 13:04:24.718 [planetinfo.record]: polar_sea_color = 0.932422, 0.895046, 0.867772, 1; 13:04:24.718 [planetinfo.record]: sea_color = 0.675781, 0.567427, 0.488358, 1; 13:04:24.718 [planetinfo.record]: rotation_speed = 0.002529 13:04:24.718 [planetinfo.record]: planet zpos = 421740.000000 13:04:24.718 [planetinfo.record]: sun_radius = 116157.542725 13:04:24.718 [planetinfo.record]: sun_vector = 0.966 -0.244 0.087 13:04:24.718 [planetinfo.record]: sun_distance = 984060 13:04:24.718 [planetinfo.record]: corona_flare = 0.096002 13:04:24.718 [planetinfo.record]: corona_hues = 0.574036 13:04:24.718 [planetinfo.record]: sun_color = 0.724211, 0.788365, 0.793558, 1 13:04:24.718 [planetinfo.record]: corona_shimmer = 0.381085 13:04:24.718 [planetinfo.record]: station_vector = -0.990 0.064 0.127 13:04:24.718 [planetinfo.record]: station = coriolis 13:04:29.603 [PLANETINFO OVER]: Done 13:04:29.604 [PLANETINFO LOGGING]: 3 220 13:04:30.624 [planetinfo.record]: seed = 34 242 26 189 13:04:30.624 [planetinfo.record]: coordinates = 242 32 13:04:30.624 [planetinfo.record]: government = 4; 13:04:30.624 [planetinfo.record]: economy = 0; 13:04:30.624 [planetinfo.record]: techlevel = 11; 13:04:30.624 [planetinfo.record]: population = 49; 13:04:30.624 [planetinfo.record]: productivity = 31360; 13:04:30.624 [planetinfo.record]: name = "Isarima"; 13:04:30.624 [planetinfo.record]: inhabitant = "Human Colonial"; 13:04:30.624 [planetinfo.record]: inhabitants = "Human Colonials"; 13:04:30.624 [planetinfo.record]: description = "This planet is mildly noted for the Isarimaian mountain lobstoid but plagued by frequent solar activity."; 13:04:30.627 [planetinfo.record]: air_color = 0.63231, 0.86311, 0.952822, 1; 13:04:30.627 [planetinfo.record]: cloud_alpha = 1.000000; 13:04:30.627 [planetinfo.record]: cloud_color = 0.560489, 0.861328, 0.508049, 1; 13:04:30.627 [planetinfo.record]: cloud_fraction = 0.270000; 13:04:30.627 [planetinfo.record]: land_color = 0.523014, 0.550781, 0.0430298, 1; 13:04:30.627 [planetinfo.record]: land_fraction = 0.550000; 13:04:30.627 [planetinfo.record]: polar_cloud_color = 0.693839, 0.887598, 0.660064, 1; 13:04:30.627 [planetinfo.record]: polar_land_color = 0.933012, 0.944922, 0.727147, 1; 13:04:30.628 [planetinfo.record]: polar_sea_color = 0.942383, 0.894297, 0.886429, 1; 13:04:30.628 [planetinfo.record]: sea_color = 0.576172, 0.458574, 0.439331, 1; 13:04:30.628 [planetinfo.record]: rotation_speed = 0.004498 13:04:30.628 [planetinfo.record]: planet zpos = 574740.000000 13:04:30.628 [planetinfo.record]: sun_radius = 184642.084961 13:04:30.628 [planetinfo.record]: sun_vector = -0.116 -0.939 0.325 13:04:30.628 [planetinfo.record]: sun_distance = 1341060 13:04:30.628 [planetinfo.record]: corona_flare = 0.090588 13:04:30.628 [planetinfo.record]: corona_hues = 0.766563 13:04:30.628 [planetinfo.record]: sun_color = 0.552102, 0.614985, 0.699988, 1 13:04:30.628 [planetinfo.record]: corona_shimmer = 0.310818 13:04:30.628 [planetinfo.record]: station_vector = -0.691 -0.475 0.545 13:04:30.628 [planetinfo.record]: station = dodecahedron 13:04:35.775 [PLANETINFO OVER]: Done 13:04:35.776 [PLANETINFO LOGGING]: 3 221 13:04:36.780 [planetinfo.record]: seed = 178 11 74 90 13:04:36.780 [planetinfo.record]: coordinates = 11 126 13:04:36.780 [planetinfo.record]: government = 6; 13:04:36.780 [planetinfo.record]: economy = 6; 13:04:36.780 [planetinfo.record]: techlevel = 7; 13:04:36.780 [planetinfo.record]: population = 41; 13:04:36.780 [planetinfo.record]: productivity = 13120; 13:04:36.780 [planetinfo.record]: name = "Quzaarar"; 13:04:36.780 [planetinfo.record]: inhabitant = "Human Colonial"; 13:04:36.780 [planetinfo.record]: inhabitants = "Human Colonials"; 13:04:36.780 [planetinfo.record]: description = "The planet Quzaarar is scourged by killer mountain Iloids."; 13:04:36.789 [planetinfo.record]: air_color = 0.406563, 0.855914, 0.848422, 1; 13:04:36.789 [planetinfo.record]: cloud_alpha = 1.000000; 13:04:36.790 [planetinfo.record]: cloud_color = 0.570312, 0.548557, 0.0133667, 1; 13:04:36.790 [planetinfo.record]: cloud_fraction = 0.400000; 13:04:36.790 [planetinfo.record]: land_color = 0.657341, 0.644531, 0.66, 1; 13:04:36.790 [planetinfo.record]: land_fraction = 0.290000; 13:04:36.790 [planetinfo.record]: polar_cloud_color = 0.756641, 0.738601, 0.294824, 1; 13:04:36.790 [planetinfo.record]: polar_land_color = 0.933059, 0.928527, 0.934, 1; 13:04:36.790 [planetinfo.record]: polar_sea_color = 0.926367, 0.856143, 0.828664, 1; 13:04:36.790 [planetinfo.record]: sea_color = 0.736328, 0.513057, 0.42569, 1; 13:04:36.790 [planetinfo.record]: rotation_speed = 0.000667 13:04:36.790 [planetinfo.record]: planet zpos = 592570.000000 13:04:36.790 [planetinfo.record]: sun_radius = 164078.438873 13:04:36.790 [planetinfo.record]: sun_vector = 0.080 0.870 -0.487 13:04:36.790 [planetinfo.record]: sun_distance = 1077400 13:04:36.791 [planetinfo.record]: corona_flare = 0.043649 13:04:36.791 [planetinfo.record]: corona_hues = 0.742592 13:04:36.791 [planetinfo.record]: sun_color = 0.70856, 0.441227, 0.271348, 1 13:04:36.791 [planetinfo.record]: corona_shimmer = 0.397125 13:04:36.791 [planetinfo.record]: station_vector = 0.807 0.487 0.334 13:04:36.791 [planetinfo.record]: station = coriolis 13:04:42.370 [PLANETINFO OVER]: Done 13:04:42.371 [PLANETINFO LOGGING]: 3 222 13:04:43.376 [planetinfo.record]: seed = 18 138 218 185 13:04:43.376 [planetinfo.record]: coordinates = 138 74 13:04:43.376 [planetinfo.record]: government = 2; 13:04:43.376 [planetinfo.record]: economy = 2; 13:04:43.376 [planetinfo.record]: techlevel = 8; 13:04:43.376 [planetinfo.record]: population = 37; 13:04:43.376 [planetinfo.record]: productivity = 14208; 13:04:43.376 [planetinfo.record]: name = "Orreenan"; 13:04:43.376 [planetinfo.record]: inhabitant = "Harmless Slimy Frog"; 13:04:43.377 [planetinfo.record]: inhabitants = "Harmless Slimy Frogs"; 13:04:43.377 [planetinfo.record]: description = "The planet Orreenan is an unremarkable dump."; 13:04:43.388 [planetinfo.record]: air_color = 0.422947, 0.7856, 0.864369, 1; 13:04:43.388 [planetinfo.record]: cloud_alpha = 1.000000; 13:04:43.388 [planetinfo.record]: cloud_color = 0.291561, 0.595703, 0.0395584, 1; 13:04:43.388 [planetinfo.record]: cloud_fraction = 0.420000; 13:04:43.388 [planetinfo.record]: land_color = 0.66, 0.244922, 0.634058, 1; 13:04:43.388 [planetinfo.record]: land_fraction = 0.430000; 13:04:43.388 [planetinfo.record]: polar_cloud_color = 0.522977, 0.768066, 0.319903, 1; 13:04:43.388 [planetinfo.record]: polar_land_color = 0.934, 0.78715, 0.924822, 1; 13:04:43.388 [planetinfo.record]: polar_sea_color = 0.888578, 0.933203, 0.885996, 1; 13:04:43.388 [planetinfo.record]: sea_color = 0.540201, 0.667969, 0.532809, 1; 13:04:43.388 [planetinfo.record]: rotation_speed = 0.001356 13:04:43.389 [planetinfo.record]: planet zpos = 630960.000000 13:04:43.389 [planetinfo.record]: sun_radius = 132525.893860 13:04:43.389 [planetinfo.record]: sun_vector = -0.526 0.283 -0.802 13:04:43.392 [planetinfo.record]: sun_distance = 1209340 13:04:43.392 [planetinfo.record]: corona_flare = 0.069948 13:04:43.392 [planetinfo.record]: corona_hues = 0.617516 13:04:43.392 [planetinfo.record]: sun_color = 0.533275, 0.568622, 0.652936, 1 13:04:43.392 [planetinfo.record]: corona_shimmer = 0.400685 13:04:43.392 [planetinfo.record]: station_vector = -0.858 -0.210 0.469 13:04:43.392 [planetinfo.record]: station = coriolis 13:04:48.193 [PLANETINFO OVER]: Done 13:04:48.194 [PLANETINFO LOGGING]: 3 223 13:04:49.199 [planetinfo.record]: seed = 98 27 234 124 13:04:49.199 [planetinfo.record]: coordinates = 27 210 13:04:49.199 [planetinfo.record]: government = 4; 13:04:49.199 [planetinfo.record]: economy = 2; 13:04:49.199 [planetinfo.record]: techlevel = 10; 13:04:49.199 [planetinfo.record]: population = 47; 13:04:49.199 [planetinfo.record]: productivity = 24064; 13:04:49.199 [planetinfo.record]: name = "Temage"; 13:04:49.199 [planetinfo.record]: inhabitant = "Blue Bug-Eyed Frog"; 13:04:49.199 [planetinfo.record]: inhabitants = "Blue Bug-Eyed Frogs"; 13:04:49.199 [planetinfo.record]: description = "The world Temage is mildly fabled for the Temageian mountain slug but ravaged by vicious disease."; 13:04:49.224 [planetinfo.record]: air_color = 0.757299, 0.64489, 0.842906, 1; 13:04:49.224 [planetinfo.record]: cloud_alpha = 1.000000; 13:04:49.224 [planetinfo.record]: cloud_color = 0.53125, 0.47522, 0.490978, 1; 13:04:49.224 [planetinfo.record]: cloud_fraction = 0.150000; 13:04:49.224 [planetinfo.record]: land_color = 0.66, 0.53903, 0.458906, 1; 13:04:49.224 [planetinfo.record]: land_fraction = 0.320000; 13:04:49.224 [planetinfo.record]: polar_cloud_color = 0.739062, 0.690345, 0.704047, 1; 13:04:49.224 [planetinfo.record]: polar_land_color = 0.934, 0.891202, 0.862855, 1; 13:04:49.225 [planetinfo.record]: polar_sea_color = 0.868164, 0.864324, 0.913477, 1; 13:04:49.225 [planetinfo.record]: sea_color = 0.693555, 0.679006, 0.865234, 1; 13:04:49.225 [planetinfo.record]: rotation_speed = 0.001311 13:04:49.225 [planetinfo.record]: planet zpos = 887250.000000 13:04:49.225 [planetinfo.record]: sun_radius = 166926.181030 13:04:49.225 [planetinfo.record]: sun_vector = -0.877 -0.404 0.260 13:04:49.225 [planetinfo.record]: sun_distance = 1242150 13:04:49.225 [planetinfo.record]: corona_flare = 0.058919 13:04:49.225 [planetinfo.record]: corona_hues = 0.865662 13:04:49.225 [planetinfo.record]: sun_color = 0.505718, 0.655313, 0.754379, 1 13:04:49.225 [planetinfo.record]: corona_shimmer = 0.734445 13:04:49.226 [planetinfo.record]: station_vector = 0.556 -0.458 0.694 13:04:49.226 [planetinfo.record]: station = coriolis 13:04:53.984 [PLANETINFO OVER]: Done 13:04:53.985 [PLANETINFO LOGGING]: 3 224 13:04:55.005 [planetinfo.record]: seed = 66 235 154 89 13:04:55.005 [planetinfo.record]: coordinates = 235 3 13:04:55.005 [planetinfo.record]: government = 0; 13:04:55.005 [planetinfo.record]: economy = 3; 13:04:55.005 [planetinfo.record]: techlevel = 7; 13:04:55.005 [planetinfo.record]: population = 32; 13:04:55.005 [planetinfo.record]: productivity = 7168; 13:04:55.005 [planetinfo.record]: name = "Orusdire"; 13:04:55.005 [planetinfo.record]: inhabitant = "Yellow Slimy Frog"; 13:04:55.005 [planetinfo.record]: inhabitants = "Yellow Slimy Frogs"; 13:04:55.005 [planetinfo.record]: description = "The planet Orusdire is most famous for its vast oceans."; 13:04:55.016 [planetinfo.record]: air_color = 0.52894, 0.620762, 0.836402, 1; 13:04:55.017 [planetinfo.record]: cloud_alpha = 1.000000; 13:04:55.017 [planetinfo.record]: cloud_color = 0.25386, 0.481501, 0.511719, 1; 13:04:55.017 [planetinfo.record]: cloud_fraction = 0.050000; 13:04:55.017 [planetinfo.record]: land_color = 0.536633, 0.66, 0.337734, 1; 13:04:55.017 [planetinfo.record]: land_fraction = 0.200000; 13:04:55.017 [planetinfo.record]: polar_cloud_color = 0.50028, 0.703321, 0.730273, 1; 13:04:55.017 [planetinfo.record]: polar_land_color = 0.890354, 0.934, 0.819986, 1; 13:04:55.017 [planetinfo.record]: polar_sea_color = 0.931641, 0.907829, 0.870684, 1; 13:04:55.017 [planetinfo.record]: sea_color = 0.683594, 0.613707, 0.504684, 1; 13:04:55.017 [planetinfo.record]: rotation_speed = 0.000701 13:04:55.017 [planetinfo.record]: planet zpos = 642600.000000 13:04:55.017 [planetinfo.record]: sun_radius = 152229.700470 13:04:55.017 [planetinfo.record]: sun_vector = 0.110 0.029 -0.993 13:04:55.017 [planetinfo.record]: sun_distance = 963900 13:04:55.017 [planetinfo.record]: corona_flare = 0.075194 13:04:55.017 [planetinfo.record]: corona_hues = 0.966652 13:04:55.017 [planetinfo.record]: sun_color = 0.714386, 0.595454, 0.364017, 1 13:04:55.017 [planetinfo.record]: corona_shimmer = 0.404398 13:04:55.018 [planetinfo.record]: station_vector = -0.128 0.989 0.073 13:04:55.018 [planetinfo.record]: station = coriolis 13:05:00.738 [PLANETINFO OVER]: Done 13:05:00.739 [PLANETINFO LOGGING]: 3 225 13:05:01.743 [planetinfo.record]: seed = 210 46 10 4 13:05:01.743 [planetinfo.record]: coordinates = 46 141 13:05:01.743 [planetinfo.record]: government = 2; 13:05:01.743 [planetinfo.record]: economy = 5; 13:05:01.743 [planetinfo.record]: techlevel = 5; 13:05:01.743 [planetinfo.record]: population = 28; 13:05:01.743 [planetinfo.record]: productivity = 6720; 13:05:01.743 [planetinfo.record]: name = "Zaonen"; 13:05:01.743 [planetinfo.record]: inhabitant = "Human Colonial"; 13:05:01.743 [planetinfo.record]: inhabitants = "Human Colonials"; 13:05:01.743 [planetinfo.record]: description = "Zaonen is most noted for its inhabitants’ eccentric shyness and its inhabitants’ eccentric shyness."; 13:05:01.760 [planetinfo.record]: air_color = 0.457352, 0.585753, 0.89884, 1; 13:05:01.760 [planetinfo.record]: cloud_alpha = 1.000000; 13:05:01.760 [planetinfo.record]: cloud_color = 0.0874023, 0.613182, 0.699219, 1; 13:05:01.760 [planetinfo.record]: cloud_fraction = 0.350000; 13:05:01.760 [planetinfo.record]: land_color = 0.607422, 0.521299, 0.498276, 1; 13:05:01.760 [planetinfo.record]: land_fraction = 0.260000; 13:05:01.760 [planetinfo.record]: polar_cloud_color = 0.369138, 0.751998, 0.814648, 1; 13:05:01.760 [planetinfo.record]: polar_land_color = 0.939258, 0.905965, 0.897065, 1; 13:05:01.760 [planetinfo.record]: polar_sea_color = 0.804942, 0.949609, 0.858062, 1; 13:05:01.760 [planetinfo.record]: sea_color = 0.196838, 0.503906, 0.30959, 1; 13:05:01.760 [planetinfo.record]: rotation_speed = 0.004383 13:05:01.760 [planetinfo.record]: planet zpos = 544040.000000 13:05:01.760 [planetinfo.record]: sun_radius = 76245.910034 13:05:01.760 [planetinfo.record]: sun_vector = 0.473 -0.020 0.881 13:05:01.760 [planetinfo.record]: sun_distance = 738340 13:05:01.760 [planetinfo.record]: corona_flare = 0.069926 13:05:01.760 [planetinfo.record]: corona_hues = 0.502090 13:05:01.760 [planetinfo.record]: sun_color = 0.79473, 0.72469, 0.685727, 1 13:05:01.760 [planetinfo.record]: corona_shimmer = 0.385103 13:05:01.761 [planetinfo.record]: station_vector = 0.232 -0.927 0.295 13:05:01.761 [planetinfo.record]: station = coriolis 13:05:06.421 [PLANETINFO OVER]: Done 13:05:06.422 [PLANETINFO LOGGING]: 3 226 13:05:07.425 [planetinfo.record]: seed = 178 182 90 105 13:05:07.425 [planetinfo.record]: coordinates = 182 242 13:05:07.425 [planetinfo.record]: government = 6; 13:05:07.425 [planetinfo.record]: economy = 2; 13:05:07.425 [planetinfo.record]: techlevel = 10; 13:05:07.425 [planetinfo.record]: population = 49; 13:05:07.425 [planetinfo.record]: productivity = 31360; 13:05:07.425 [planetinfo.record]: name = "Esenena"; 13:05:07.425 [planetinfo.record]: inhabitant = "Human Colonial"; 13:05:07.425 [planetinfo.record]: inhabitants = "Human Colonials"; 13:05:07.425 [planetinfo.record]: description = "Esenena is a revolting little planet."; 13:05:07.440 [planetinfo.record]: air_color = 0.4169, 0.826149, 0.856564, 1; 13:05:07.440 [planetinfo.record]: cloud_alpha = 1.000000; 13:05:07.440 [planetinfo.record]: cloud_color = 0.454417, 0.572266, 0.0335312, 1; 13:05:07.440 [planetinfo.record]: cloud_fraction = 0.580000; 13:05:07.440 [planetinfo.record]: land_color = 0.195938, 0.203188, 0.66, 1; 13:05:07.440 [planetinfo.record]: land_fraction = 0.550000; 13:05:07.440 [planetinfo.record]: polar_cloud_color = 0.660021, 0.75752, 0.311811, 1; 13:05:07.440 [planetinfo.record]: polar_land_color = 0.76982, 0.772386, 0.934, 1; 13:05:07.440 [planetinfo.record]: polar_sea_color = 0.927579, 0.936523, 0.884484, 1; 13:05:07.440 [planetinfo.record]: sea_color = 0.610516, 0.634766, 0.493679, 1; 13:05:07.440 [planetinfo.record]: rotation_speed = 0.002575 13:05:07.440 [planetinfo.record]: planet zpos = 636240.000000 13:05:07.440 [planetinfo.record]: sun_radius = 102210.094604 13:05:07.440 [planetinfo.record]: sun_vector = 0.523 -0.640 0.563 13:05:07.440 [planetinfo.record]: sun_distance = 1113420 13:05:07.440 [planetinfo.record]: corona_flare = 0.002301 13:05:07.440 [planetinfo.record]: corona_hues = 0.928879 13:05:07.440 [planetinfo.record]: sun_color = 0.665646, 0.501607, 0.345291, 1 13:05:07.440 [planetinfo.record]: corona_shimmer = 0.450599 13:05:07.440 [planetinfo.record]: station_vector = -0.088 0.978 0.188 13:05:07.441 [planetinfo.record]: station = coriolis 13:05:11.837 [PLANETINFO OVER]: Done 13:05:11.837 [PLANETINFO LOGGING]: 3 227 13:05:12.840 [planetinfo.record]: seed = 2 175 170 244 13:05:12.840 [planetinfo.record]: coordinates = 175 50 13:05:12.840 [planetinfo.record]: government = 0; 13:05:12.840 [planetinfo.record]: economy = 2; 13:05:12.840 [planetinfo.record]: techlevel = 8; 13:05:12.840 [planetinfo.record]: population = 35; 13:05:12.840 [planetinfo.record]: productivity = 8960; 13:05:12.840 [planetinfo.record]: name = "Ravequce"; 13:05:12.840 [planetinfo.record]: inhabitant = "Furry Humanoid"; 13:05:12.840 [planetinfo.record]: inhabitants = "Furry Humanoids"; 13:05:12.840 [planetinfo.record]: description = "This planet is notable for the Ravequceian edible arts graduate and its great volcanoes."; 13:05:12.854 [planetinfo.record]: air_color = 0.571087, 0.509043, 0.911848, 1; 13:05:12.854 [planetinfo.record]: cloud_alpha = 1.000000; 13:05:12.854 [planetinfo.record]: cloud_color = 0.457505, 0.201874, 0.738281, 1; 13:05:12.854 [planetinfo.record]: cloud_fraction = 0.530000; 13:05:12.854 [planetinfo.record]: land_color = 0.532604, 0.66, 0.518203, 1; 13:05:12.854 [planetinfo.record]: land_fraction = 0.430000; 13:05:12.854 [planetinfo.record]: polar_cloud_color = 0.634412, 0.454311, 0.832227, 1; 13:05:12.854 [planetinfo.record]: polar_land_color = 0.888929, 0.934, 0.883834, 1; 13:05:12.855 [planetinfo.record]: polar_sea_color = 0.914254, 0.880462, 0.948047, 1; 13:05:12.855 [planetinfo.record]: sea_color = 0.445457, 0.371384, 0.519531, 1; 13:05:12.855 [planetinfo.record]: rotation_speed = 0.000138 13:05:12.855 [planetinfo.record]: planet zpos = 481800.000000 13:05:12.855 [planetinfo.record]: sun_radius = 80970.228577 13:05:12.855 [planetinfo.record]: sun_vector = -0.382 0.554 0.740 13:05:12.855 [planetinfo.record]: sun_distance = 843150 13:05:12.855 [planetinfo.record]: corona_flare = 0.049655 13:05:12.855 [planetinfo.record]: corona_hues = 0.699776 13:05:12.855 [planetinfo.record]: sun_color = 0.363499, 0.447374, 0.691653, 1 13:05:12.855 [planetinfo.record]: corona_shimmer = 0.556910 13:05:12.855 [planetinfo.record]: station_vector = -0.410 0.670 0.619 13:05:12.855 [planetinfo.record]: station = coriolis 13:05:17.922 [PLANETINFO OVER]: Done 13:05:17.923 [PLANETINFO LOGGING]: 3 228 13:05:18.928 [planetinfo.record]: seed = 98 69 26 150 13:05:18.928 [planetinfo.record]: coordinates = 69 122 13:05:18.928 [planetinfo.record]: government = 4; 13:05:18.928 [planetinfo.record]: economy = 2; 13:05:18.928 [planetinfo.record]: techlevel = 8; 13:05:18.928 [planetinfo.record]: population = 39; 13:05:18.928 [planetinfo.record]: productivity = 19968; 13:05:18.928 [planetinfo.record]: name = "Velaat"; 13:05:18.928 [planetinfo.record]: inhabitant = "Human Colonial"; 13:05:18.928 [planetinfo.record]: inhabitants = "Human Colonials"; 13:05:18.928 [planetinfo.record]: description = "This world is fabled for its weird volcanoes."; 13:05:18.944 [planetinfo.record]: air_color = 0.557557, 0.50841, 0.908596, 1; 13:05:18.944 [planetinfo.record]: cloud_alpha = 1.000000; 13:05:18.944 [planetinfo.record]: cloud_color = 0.403587, 0.202049, 0.728516, 1; 13:05:18.944 [planetinfo.record]: cloud_fraction = 0.200000; 13:05:18.944 [planetinfo.record]: land_color = 0.629183, 0.66, 0.543984, 1; 13:05:18.944 [planetinfo.record]: land_fraction = 0.510000; 13:05:18.944 [planetinfo.record]: polar_cloud_color = 0.597066, 0.453933, 0.827832, 1; 13:05:18.944 [planetinfo.record]: polar_land_color = 0.923097, 0.934, 0.892955, 1; 13:05:18.944 [planetinfo.record]: polar_sea_color = 0.700183, 0.927539, 0.879581, 1; 13:05:18.944 [planetinfo.record]: sea_color = 0.0141525, 0.724609, 0.574747, 1; 13:05:18.944 [planetinfo.record]: rotation_speed = 0.001953 13:05:18.944 [planetinfo.record]: planet zpos = 442100.000000 13:05:18.944 [planetinfo.record]: sun_radius = 122823.469696 13:05:18.944 [planetinfo.record]: sun_vector = 0.955 0.244 0.170 13:05:18.944 [planetinfo.record]: sun_distance = 795780 13:05:18.944 [planetinfo.record]: corona_flare = 0.065511 13:05:18.944 [planetinfo.record]: corona_hues = 0.784927 13:05:18.944 [planetinfo.record]: sun_color = 0.209035, 0.478246, 0.689343, 1 13:05:18.944 [planetinfo.record]: corona_shimmer = 0.329887 13:05:18.944 [planetinfo.record]: station_vector = 0.544 0.320 0.776 13:05:18.944 [planetinfo.record]: station = coriolis 13:05:23.835 [PLANETINFO OVER]: Done 13:05:23.836 [PLANETINFO LOGGING]: 3 229 13:05:24.838 [planetinfo.record]: seed = 242 28 202 163 13:05:24.839 [planetinfo.record]: coordinates = 28 49 13:05:24.839 [planetinfo.record]: government = 6; 13:05:24.839 [planetinfo.record]: economy = 1; 13:05:24.839 [planetinfo.record]: techlevel = 9; 13:05:24.839 [planetinfo.record]: population = 44; 13:05:24.839 [planetinfo.record]: productivity = 31680; 13:05:24.839 [planetinfo.record]: name = "Geaten"; 13:05:24.839 [planetinfo.record]: inhabitant = "Large Harmless Furry Rodent"; 13:05:24.839 [planetinfo.record]: inhabitants = "Large Harmless Furry Rodents"; 13:05:24.839 [planetinfo.record]: description = "This planet is a tedious place."; 13:05:24.868 [planetinfo.record]: air_color = 0.568427, 0.927457, 0.812188, 1; 13:05:24.868 [planetinfo.record]: cloud_alpha = 1.000000; 13:05:24.868 [planetinfo.record]: cloud_color = 0.785156, 0.364208, 0.343506, 1; 13:05:24.868 [planetinfo.record]: cloud_fraction = 0.320000; 13:05:24.868 [planetinfo.record]: land_color = 0.554297, 0.610452, 0.66, 1; 13:05:24.868 [planetinfo.record]: land_fraction = 0.240000; 13:05:24.868 [planetinfo.record]: polar_cloud_color = 0.85332, 0.567387, 0.553325, 1; 13:05:24.868 [planetinfo.record]: polar_land_color = 0.896604, 0.91647, 0.934, 1; 13:05:24.868 [planetinfo.record]: polar_sea_color = 0.927344, 0.836245, 0.826821, 1; 13:05:24.868 [planetinfo.record]: sea_color = 0.726562, 0.441064, 0.41153, 1; 13:05:24.868 [planetinfo.record]: rotation_speed = 0.003203 13:05:24.868 [planetinfo.record]: planet zpos = 397320.000000 13:05:24.868 [planetinfo.record]: sun_radius = 98655.395508 13:05:24.868 [planetinfo.record]: sun_vector = 0.144 -0.563 0.814 13:05:24.868 [planetinfo.record]: sun_distance = 686280 13:05:24.868 [planetinfo.record]: corona_flare = 0.010655 13:05:24.868 [planetinfo.record]: corona_hues = 0.976509 13:05:24.868 [planetinfo.record]: sun_color = 0.206048, 0.249923, 0.696338, 1 13:05:24.868 [planetinfo.record]: corona_shimmer = 0.335226 13:05:24.869 [planetinfo.record]: station_vector = -0.874 -0.169 0.456 13:05:24.869 [planetinfo.record]: station = coriolis 13:05:29.684 [PLANETINFO OVER]: Done 13:05:29.684 [PLANETINFO LOGGING]: 3 230 13:05:30.689 [planetinfo.record]: seed = 82 72 218 236 13:05:30.689 [planetinfo.record]: coordinates = 72 178 13:05:30.689 [planetinfo.record]: government = 2; 13:05:30.689 [planetinfo.record]: economy = 2; 13:05:30.689 [planetinfo.record]: techlevel = 6; 13:05:30.689 [planetinfo.record]: population = 29; 13:05:30.689 [planetinfo.record]: productivity = 11136; 13:05:30.689 [planetinfo.record]: name = "Insote"; 13:05:30.689 [planetinfo.record]: inhabitant = "Horned Lizard"; 13:05:30.689 [planetinfo.record]: inhabitants = "Horned Lizards"; 13:05:30.689 [planetinfo.record]: description = "The planet Insote is cursed by deadly civil war."; 13:05:30.696 [planetinfo.record]: air_color = 0.491118, 0.995748, 0.884933, 1; 13:05:30.696 [planetinfo.record]: cloud_alpha = 1.000000; 13:05:30.696 [planetinfo.record]: cloud_color = 0.990234, 0.398294, 0.0773621, 1; 13:05:30.696 [planetinfo.record]: cloud_fraction = 0.510000; 13:05:30.696 [planetinfo.record]: land_color = 0.319688, 0.66, 0.47655, 1; 13:05:30.696 [planetinfo.record]: land_fraction = 0.290000; 13:05:30.697 [planetinfo.record]: polar_cloud_color = 0.945605, 0.592316, 0.400774, 1; 13:05:30.697 [planetinfo.record]: polar_land_color = 0.813602, 0.934, 0.869098, 1; 13:05:30.697 [planetinfo.record]: polar_sea_color = 0.93095, 0.939258, 0.890919, 1; 13:05:30.697 [planetinfo.record]: sea_color = 0.58593, 0.607422, 0.482378, 1; 13:05:30.697 [planetinfo.record]: rotation_speed = 0.003869 13:05:30.697 [planetinfo.record]: planet zpos = 596000.000000 13:05:30.697 [planetinfo.record]: sun_radius = 135553.259277 13:05:30.697 [planetinfo.record]: sun_vector = 0.219 0.863 0.456 13:05:30.697 [planetinfo.record]: sun_distance = 1132400 13:05:30.697 [planetinfo.record]: corona_flare = 0.025931 13:05:30.697 [planetinfo.record]: corona_hues = 0.688644 13:05:30.697 [planetinfo.record]: sun_color = 0.692465, 0.478197, 0.422774, 1 13:05:30.697 [planetinfo.record]: corona_shimmer = 0.381653 13:05:30.697 [planetinfo.record]: station_vector = 0.229 0.790 0.568 13:05:30.697 [planetinfo.record]: station = coriolis 13:05:36.397 [PLANETINFO OVER]: Done 13:05:36.398 [PLANETINFO LOGGING]: 3 231 13:05:37.401 [planetinfo.record]: seed = 162 241 106 246 13:05:37.401 [planetinfo.record]: coordinates = 241 28 13:05:37.401 [planetinfo.record]: government = 4; 13:05:37.401 [planetinfo.record]: economy = 4; 13:05:37.401 [planetinfo.record]: techlevel = 6; 13:05:37.401 [planetinfo.record]: population = 33; 13:05:37.401 [planetinfo.record]: productivity = 12672; 13:05:37.401 [planetinfo.record]: name = "Vecedius"; 13:05:37.401 [planetinfo.record]: inhabitant = "Human Colonial"; 13:05:37.401 [planetinfo.record]: inhabitants = "Human Colonials"; 13:05:37.401 [planetinfo.record]: description = "This world is a tedious place."; 13:05:37.428 [planetinfo.record]: air_color = 0.609556, 0.881295, 0.963879, 1; 13:05:37.428 [planetinfo.record]: cloud_alpha = 1.000000; 13:05:37.428 [planetinfo.record]: cloud_color = 0.574069, 0.894531, 0.443771, 1; 13:05:37.428 [planetinfo.record]: cloud_fraction = 0.160000; 13:05:37.428 [planetinfo.record]: land_color = 0.191884, 0.595703, 0.0628281, 1; 13:05:37.428 [planetinfo.record]: land_fraction = 0.630000; 13:05:37.428 [planetinfo.record]: polar_cloud_color = 0.700457, 0.902539, 0.618292, 1; 13:05:37.428 [planetinfo.record]: polar_land_color = 0.781053, 0.94043, 0.730119, 1; 13:05:37.428 [planetinfo.record]: polar_sea_color = 0.931445, 0.901257, 0.865953, 1; 13:05:37.428 [planetinfo.record]: sea_color = 0.685547, 0.596673, 0.492737, 1; 13:05:37.428 [planetinfo.record]: rotation_speed = 0.003899 13:05:37.428 [planetinfo.record]: planet zpos = 551160.000000 13:05:37.428 [planetinfo.record]: sun_radius = 107935.780334 13:05:37.428 [planetinfo.record]: sun_vector = 0.734 0.121 -0.669 13:05:37.428 [planetinfo.record]: sun_distance = 780810 13:05:37.428 [planetinfo.record]: corona_flare = 0.049838 13:05:37.428 [planetinfo.record]: corona_hues = 0.877769 13:05:37.428 [planetinfo.record]: sun_color = 0.642151, 0.665509, 0.671515, 1 13:05:37.428 [planetinfo.record]: corona_shimmer = 0.612658 13:05:37.429 [planetinfo.record]: station_vector = -0.927 -0.351 0.131 13:05:37.429 [planetinfo.record]: station = coriolis 13:05:42.233 [PLANETINFO OVER]: Done 13:05:42.234 [PLANETINFO LOGGING]: 3 232 13:05:43.237 [planetinfo.record]: seed = 130 232 154 218 13:05:43.237 [planetinfo.record]: coordinates = 232 237 13:05:43.237 [planetinfo.record]: government = 0; 13:05:43.237 [planetinfo.record]: economy = 7; 13:05:43.237 [planetinfo.record]: techlevel = 0; 13:05:43.238 [planetinfo.record]: population = 8; 13:05:43.238 [planetinfo.record]: productivity = 768; 13:05:43.238 [planetinfo.record]: name = "Querbe"; 13:05:43.238 [planetinfo.record]: inhabitant = "Furry Insect"; 13:05:43.238 [planetinfo.record]: inhabitants = "Furry Insects"; 13:05:43.238 [planetinfo.record]: description = "This planet is most fabled for its inhabitants’ ingrained shyness but cursed by unpredictable solar activity."; 13:05:43.250 [planetinfo.record]: air_color = 0.682556, 0.833346, 0.920303, 1; 13:05:43.251 [planetinfo.record]: cloud_alpha = 1.000000; 13:05:43.251 [planetinfo.record]: cloud_color = 0.629433, 0.763672, 0.644115, 1; 13:05:43.251 [planetinfo.record]: cloud_fraction = 0.380000; 13:05:43.251 [planetinfo.record]: land_color = 0.503906, 0.392693, 0.0590515, 1; 13:05:43.251 [planetinfo.record]: land_fraction = 0.260000; 13:05:43.251 [planetinfo.record]: polar_cloud_color = 0.750966, 0.843652, 0.761104, 1; 13:05:43.251 [planetinfo.record]: polar_land_color = 0.949609, 0.897214, 0.740028, 1; 13:05:43.251 [planetinfo.record]: polar_sea_color = 0.893004, 0.864405, 0.910742, 1; 13:05:43.251 [planetinfo.record]: sea_color = 0.823039, 0.710925, 0.892578, 1; 13:05:43.251 [planetinfo.record]: rotation_speed = 0.003260 13:05:43.251 [planetinfo.record]: planet zpos = 672960.000000 13:05:43.251 [planetinfo.record]: sun_radius = 140211.979980 13:05:43.251 [planetinfo.record]: sun_vector = 0.638 0.755 -0.155 13:05:43.251 [planetinfo.record]: sun_distance = 1009440 13:05:43.251 [planetinfo.record]: corona_flare = 0.056151 13:05:43.251 [planetinfo.record]: corona_hues = 0.873123 13:05:43.251 [planetinfo.record]: sun_color = 0.81705, 0.761191, 0.740321, 1 13:05:43.251 [planetinfo.record]: corona_shimmer = 0.334098 13:05:43.252 [planetinfo.record]: station_vector = 0.961 -0.170 0.217 13:05:43.252 [planetinfo.record]: station = coriolis 13:05:47.951 [PLANETINFO OVER]: Done 13:05:47.952 [PLANETINFO LOGGING]: 3 233 13:05:48.956 [planetinfo.record]: seed = 18 254 138 33 13:05:48.956 [planetinfo.record]: coordinates = 254 115 13:05:48.956 [planetinfo.record]: government = 2; 13:05:48.956 [planetinfo.record]: economy = 3; 13:05:48.956 [planetinfo.record]: techlevel = 7; 13:05:48.956 [planetinfo.record]: population = 34; 13:05:48.956 [planetinfo.record]: productivity = 11424; 13:05:48.956 [planetinfo.record]: name = "Leenenbi"; 13:05:48.956 [planetinfo.record]: inhabitant = "Large Red Furry Feline"; 13:05:48.956 [planetinfo.record]: inhabitants = "Large Red Furry Felines"; 13:05:48.956 [planetinfo.record]: description = "Leenenbi is mildly notable for its inhabitants’ unusual mating traditions."; 13:05:48.980 [planetinfo.record]: air_color = 0.62663, 0.890995, 0.942416, 1; 13:05:48.980 [planetinfo.record]: cloud_alpha = 1.000000; 13:05:48.980 [planetinfo.record]: cloud_color = 0.661469, 0.830078, 0.492859, 1; 13:05:48.980 [planetinfo.record]: cloud_fraction = 0.510000; 13:05:48.980 [planetinfo.record]: land_color = 0.613057, 0.363922, 0.621094, 1; 13:05:48.980 [planetinfo.record]: land_fraction = 0.550000; 13:05:48.980 [planetinfo.record]: polar_cloud_color = 0.762637, 0.873535, 0.651739, 1; 13:05:48.980 [planetinfo.record]: polar_land_color = 0.934857, 0.840804, 0.937891, 1; 13:05:48.980 [planetinfo.record]: polar_sea_color = 0.901142, 0.934961, 0.887391, 1; 13:05:48.980 [planetinfo.record]: sea_color = 0.556288, 0.650391, 0.518026, 1; 13:05:48.980 [planetinfo.record]: rotation_speed = 0.001929 13:05:48.980 [planetinfo.record]: planet zpos = 465640.000000 13:05:48.980 [planetinfo.record]: sun_radius = 85127.248535 13:05:48.980 [planetinfo.record]: sun_vector = 0.094 0.880 0.465 13:05:48.980 [planetinfo.record]: sun_distance = 631940 13:05:48.980 [planetinfo.record]: corona_flare = 0.037384 13:05:48.980 [planetinfo.record]: corona_hues = 0.907562 13:05:48.980 [planetinfo.record]: sun_color = 0.779877, 0.684914, 0.23, 1 13:05:48.981 [planetinfo.record]: corona_shimmer = 0.553626 13:05:48.981 [planetinfo.record]: station_vector = -0.766 0.621 0.168 13:05:48.981 [planetinfo.record]: station = coriolis 13:05:53.271 [PLANETINFO OVER]: Done 13:05:53.271 [PLANETINFO LOGGING]: 3 234 13:05:54.274 [planetinfo.record]: seed = 242 102 90 172 13:05:54.275 [planetinfo.record]: coordinates = 102 178 13:05:54.275 [planetinfo.record]: government = 6; 13:05:54.275 [planetinfo.record]: economy = 2; 13:05:54.275 [planetinfo.record]: techlevel = 10; 13:05:54.275 [planetinfo.record]: population = 49; 13:05:54.275 [planetinfo.record]: productivity = 31360; 13:05:54.275 [planetinfo.record]: name = "Inceor"; 13:05:54.275 [planetinfo.record]: inhabitant = "Human Colonial"; 13:05:54.275 [planetinfo.record]: inhabitants = "Human Colonials"; 13:05:54.275 [planetinfo.record]: description = "Inceor is ravaged by unpredictable civil war."; 13:05:54.292 [planetinfo.record]: air_color = 0.805381, 0.51768, 0.963229, 1; 13:05:54.292 [planetinfo.record]: cloud_alpha = 1.000000; 13:05:54.292 [planetinfo.record]: cloud_color = 0.892578, 0.188278, 0.221292, 1; 13:05:54.292 [planetinfo.record]: cloud_fraction = 0.240000; 13:05:54.292 [planetinfo.record]: land_color = 0.453528, 0.265547, 0.66, 1; 13:05:54.292 [planetinfo.record]: land_fraction = 0.430000; 13:05:54.292 [planetinfo.record]: polar_cloud_color = 0.90166, 0.456994, 0.477838, 1; 13:05:54.292 [planetinfo.record]: polar_land_color = 0.860953, 0.794447, 0.934, 1; 13:05:54.292 [planetinfo.record]: polar_sea_color = 0.935156, 0.875567, 0.869403, 1; 13:05:54.292 [planetinfo.record]: sea_color = 0.648438, 0.483162, 0.466064, 1; 13:05:54.292 [planetinfo.record]: rotation_speed = 0.000094 13:05:54.292 [planetinfo.record]: planet zpos = 898500.000000 13:05:54.292 [planetinfo.record]: sun_radius = 160454.785156 13:05:54.292 [planetinfo.record]: sun_vector = -0.353 -0.911 0.212 13:05:54.292 [planetinfo.record]: sun_distance = 1257900 13:05:54.292 [planetinfo.record]: corona_flare = 0.027440 13:05:54.292 [planetinfo.record]: corona_hues = 0.805717 13:05:54.292 [planetinfo.record]: sun_color = 0.802731, 0.779001, 0.763468, 1 13:05:54.293 [planetinfo.record]: corona_shimmer = 0.419184 13:05:54.293 [planetinfo.record]: station_vector = -0.262 -0.914 0.310 13:05:54.293 [planetinfo.record]: station = coriolis 13:05:59.243 [PLANETINFO OVER]: Done 13:05:59.244 [PLANETINFO LOGGING]: 3 235 13:06:00.263 [planetinfo.record]: seed = 66 75 42 234 13:06:00.263 [planetinfo.record]: coordinates = 75 217 13:06:00.263 [planetinfo.record]: government = 0; 13:06:00.263 [planetinfo.record]: economy = 3; 13:06:00.263 [planetinfo.record]: techlevel = 7; 13:06:00.263 [planetinfo.record]: population = 32; 13:06:00.263 [planetinfo.record]: productivity = 7168; 13:06:00.263 [planetinfo.record]: name = "Arrege"; 13:06:00.263 [planetinfo.record]: inhabitant = "Human Colonial"; 13:06:00.263 [planetinfo.record]: inhabitants = "Human Colonials"; 13:06:00.263 [planetinfo.record]: description = "The planet Arrege is cursed by vicious killer cats."; 13:06:00.267 [planetinfo.record]: air_color = 0.40486, 0.84799, 0.861768, 1; 13:06:00.268 [planetinfo.record]: cloud_alpha = 1.000000; 13:06:00.268 [planetinfo.record]: cloud_color = 0.528649, 0.587891, 0.0045929, 1; 13:06:00.268 [planetinfo.record]: cloud_fraction = 0.160000; 13:06:00.268 [planetinfo.record]: land_color = 0.66, 0.342891, 0.43951, 1; 13:06:00.268 [planetinfo.record]: land_fraction = 0.290000; 13:06:00.268 [planetinfo.record]: polar_cloud_color = 0.716399, 0.764551, 0.29044, 1; 13:06:00.268 [planetinfo.record]: polar_land_color = 0.934, 0.821811, 0.855993, 1; 13:06:00.268 [planetinfo.record]: polar_sea_color = 0.943359, 0.92911, 0.895362, 1; 13:06:00.268 [planetinfo.record]: sea_color = 0.566406, 0.532185, 0.451134, 1; 13:06:00.268 [planetinfo.record]: rotation_speed = 0.002618 13:06:00.268 [planetinfo.record]: planet zpos = 599610.000000 13:06:00.268 [planetinfo.record]: sun_radius = 100178.427429 13:06:00.268 [planetinfo.record]: sun_vector = -0.140 0.895 -0.423 13:06:00.268 [planetinfo.record]: sun_distance = 1144710 13:06:00.268 [planetinfo.record]: corona_flare = 0.085884 13:06:00.268 [planetinfo.record]: corona_hues = 0.565506 13:06:00.268 [planetinfo.record]: sun_color = 0.757123, 0.693196, 0.670555, 1 13:06:00.268 [planetinfo.record]: corona_shimmer = 0.436249 13:06:00.268 [planetinfo.record]: station_vector = -0.637 0.761 0.125 13:06:00.268 [planetinfo.record]: station = coriolis 13:06:05.483 [PLANETINFO OVER]: Done 13:06:05.485 [PLANETINFO LOGGING]: 3 236 13:06:06.505 [planetinfo.record]: seed = 162 60 26 143 13:06:06.505 [planetinfo.record]: coordinates = 60 67 13:06:06.505 [planetinfo.record]: government = 4; 13:06:06.505 [planetinfo.record]: economy = 3; 13:06:06.505 [planetinfo.record]: techlevel = 6; 13:06:06.505 [planetinfo.record]: population = 32; 13:06:06.505 [planetinfo.record]: productivity = 14336; 13:06:06.505 [planetinfo.record]: name = "Aaanqu"; 13:06:06.505 [planetinfo.record]: inhabitant = "Human Colonial"; 13:06:06.505 [planetinfo.record]: inhabitants = "Human Colonials"; 13:06:06.505 [planetinfo.record]: description = "This planet is noted for its exotic goat soup."; 13:06:06.536 [planetinfo.record]: air_color = 0.658246, 0.889084, 0.883433, 1; 13:06:06.536 [planetinfo.record]: cloud_alpha = 1.000000; 13:06:06.536 [planetinfo.record]: cloud_color = 0.669922, 0.662398, 0.549545, 1; 13:06:06.536 [planetinfo.record]: cloud_fraction = 0.140000; 13:06:06.536 [planetinfo.record]: land_color = 0.471459, 0.503906, 0.0885773, 1; 13:06:06.536 [planetinfo.record]: land_fraction = 0.350000; 13:06:06.536 [planetinfo.record]: polar_cloud_color = 0.801465, 0.795839, 0.711457, 1; 13:06:06.536 [planetinfo.record]: polar_land_color = 0.934323, 0.949609, 0.753938, 1; 13:06:06.536 [planetinfo.record]: polar_sea_color = 0.85882, 0.899022, 0.911328, 1; 13:06:06.536 [planetinfo.record]: sea_color = 0.682358, 0.838822, 0.886719, 1; 13:06:06.536 [planetinfo.record]: rotation_speed = 0.004449 13:06:06.536 [planetinfo.record]: planet zpos = 738760.000000 13:06:06.536 [planetinfo.record]: sun_radius = 170514.214478 13:06:06.536 [planetinfo.record]: sun_vector = 0.242 -0.098 -0.965 13:06:06.536 [planetinfo.record]: sun_distance = 1208880 13:06:06.536 [planetinfo.record]: corona_flare = 0.068959 13:06:06.536 [planetinfo.record]: corona_hues = 0.651787 13:06:06.536 [planetinfo.record]: sun_color = 0.479882, 0.823474, 0.837494, 1 13:06:06.536 [planetinfo.record]: corona_shimmer = 0.263237 13:06:06.536 [planetinfo.record]: station_vector = 0.690 -0.268 0.673 13:06:06.536 [planetinfo.record]: station = coriolis 13:06:11.417 [PLANETINFO OVER]: Done 13:06:11.419 [PLANETINFO LOGGING]: 3 237 13:06:12.440 [planetinfo.record]: seed = 50 122 74 101 13:06:12.440 [planetinfo.record]: coordinates = 122 219 13:06:12.440 [planetinfo.record]: government = 6; 13:06:12.440 [planetinfo.record]: economy = 3; 13:06:12.440 [planetinfo.record]: techlevel = 9; 13:06:12.440 [planetinfo.record]: population = 46; 13:06:12.440 [planetinfo.record]: productivity = 25760; 13:06:12.440 [planetinfo.record]: name = "Cequququ"; 13:06:12.440 [planetinfo.record]: inhabitant = "Human Colonial"; 13:06:12.440 [planetinfo.record]: inhabitants = "Human Colonials"; 13:06:12.440 [planetinfo.record]: description = "This world is mildly well known for Cequququian shrew steak and Cequququian wolf meat."; 13:06:12.459 [planetinfo.record]: air_color = 0.455158, 0.581471, 0.901441, 1; 13:06:12.460 [planetinfo.record]: cloud_alpha = 1.000000; 13:06:12.460 [planetinfo.record]: cloud_color = 0.0800934, 0.604174, 0.707031, 1; 13:06:12.460 [planetinfo.record]: cloud_fraction = 0.410000; 13:06:12.460 [planetinfo.record]: land_color = 0.311953, 0.66, 0.325549, 1; 13:06:12.460 [planetinfo.record]: land_fraction = 0.550000; 13:06:12.460 [planetinfo.record]: polar_cloud_color = 0.364738, 0.743774, 0.818164, 1; 13:06:12.460 [planetinfo.record]: polar_land_color = 0.810865, 0.934, 0.815675, 1; 13:06:12.463 [planetinfo.record]: polar_sea_color = 0.940234, 0.939465, 0.891019, 1; 13:06:12.463 [planetinfo.record]: sea_color = 0.597656, 0.595701, 0.472522, 1; 13:06:12.463 [planetinfo.record]: rotation_speed = 0.000689 13:06:12.463 [planetinfo.record]: planet zpos = 463980.000000 13:06:12.463 [planetinfo.record]: sun_radius = 104075.236816 13:06:12.464 [planetinfo.record]: sun_vector = 0.808 -0.391 -0.440 13:06:12.464 [planetinfo.record]: sun_distance = 1012320 13:06:12.464 [planetinfo.record]: corona_flare = 0.092680 13:06:12.464 [planetinfo.record]: corona_hues = 0.637093 13:06:12.464 [planetinfo.record]: sun_color = 0.813766, 0.763861, 0.678684, 1 13:06:12.465 [planetinfo.record]: corona_shimmer = 0.851338 13:06:12.465 [planetinfo.record]: station_vector = 0.035 -0.824 0.566 13:06:12.465 [planetinfo.record]: station = coriolis 13:06:18.262 [PLANETINFO OVER]: Done 13:06:18.263 [PLANETINFO LOGGING]: 3 238 13:06:19.270 [planetinfo.record]: seed = 146 186 218 15 13:06:19.270 [planetinfo.record]: coordinates = 186 154 13:06:19.270 [planetinfo.record]: government = 2; 13:06:19.270 [planetinfo.record]: economy = 2; 13:06:19.270 [planetinfo.record]: techlevel = 8; 13:06:19.270 [planetinfo.record]: population = 37; 13:06:19.270 [planetinfo.record]: productivity = 14208; 13:06:19.270 [planetinfo.record]: name = "Azaage"; 13:06:19.270 [planetinfo.record]: inhabitant = "Green Slimy Lobster"; 13:06:19.270 [planetinfo.record]: inhabitants = "Green Slimy Lobsters"; 13:06:19.270 [planetinfo.record]: description = "This planet is plagued by frequent solar activity."; 13:06:19.289 [planetinfo.record]: air_color = 0.594477, 0.844219, 0.993146, 1; 13:06:19.289 [planetinfo.record]: cloud_alpha = 1.000000; 13:06:19.289 [planetinfo.record]: cloud_color = 0.387596, 0.982422, 0.466596, 1; 13:06:19.289 [planetinfo.record]: cloud_fraction = 0.200000; 13:06:19.289 [planetinfo.record]: land_color = 0.66, 0.152109, 0.318761, 1; 13:06:19.289 [planetinfo.record]: land_fraction = 0.330000; 13:06:19.289 [planetinfo.record]: polar_cloud_color = 0.585586, 0.94209, 0.632934, 1; 13:06:19.289 [planetinfo.record]: polar_land_color = 0.934, 0.754314, 0.813274, 1; 13:06:19.289 [planetinfo.record]: polar_sea_color = 0.94082, 0.922945, 0.888818, 1; 13:06:19.289 [planetinfo.record]: sea_color = 0.591797, 0.54682, 0.460954, 1; 13:06:19.289 [planetinfo.record]: rotation_speed = 0.001285 13:06:19.289 [planetinfo.record]: planet zpos = 821040.000000 13:06:19.289 [planetinfo.record]: sun_radius = 213125.543823 13:06:19.290 [planetinfo.record]: sun_vector = -0.048 0.775 0.630 13:06:19.290 [planetinfo.record]: sun_distance = 1505240 13:06:19.290 [planetinfo.record]: corona_flare = 0.045117 13:06:19.290 [planetinfo.record]: corona_hues = 0.549675 13:06:19.290 [planetinfo.record]: sun_color = 0.820547, 0.621697, 0.613752, 1 13:06:19.290 [planetinfo.record]: corona_shimmer = 0.470217 13:06:19.290 [planetinfo.record]: station_vector = -0.993 0.016 0.115 13:06:19.290 [planetinfo.record]: station = coriolis 13:06:24.565 [PLANETINFO OVER]: Done 13:06:24.566 [PLANETINFO LOGGING]: 3 239 13:06:25.583 [planetinfo.record]: seed = 226 163 234 55 13:06:25.583 [planetinfo.record]: coordinates = 163 47 13:06:25.583 [planetinfo.record]: government = 4; 13:06:25.583 [planetinfo.record]: economy = 7; 13:06:25.583 [planetinfo.record]: techlevel = 5; 13:06:25.583 [planetinfo.record]: population = 32; 13:06:25.583 [planetinfo.record]: productivity = 6144; 13:06:25.583 [planetinfo.record]: name = "Timabi"; 13:06:25.583 [planetinfo.record]: inhabitant = "Red Fat Insect"; 13:06:25.583 [planetinfo.record]: inhabitants = "Red Fat Insects"; 13:06:25.583 [planetinfo.record]: description = "This world is plagued by occasional solar activity."; 13:06:25.613 [planetinfo.record]: air_color = 0.59618, 0.882211, 0.976887, 1; 13:06:25.613 [planetinfo.record]: cloud_alpha = 1.000000; 13:06:25.613 [planetinfo.record]: cloud_color = 0.530104, 0.933594, 0.401154, 1; 13:06:25.613 [planetinfo.record]: cloud_fraction = 0.250000; 13:06:25.613 [planetinfo.record]: land_color = 0.626484, 0.66, 0.656072, 1; 13:06:25.613 [planetinfo.record]: land_fraction = 0.510000; 13:06:25.613 [planetinfo.record]: polar_cloud_color = 0.671576, 0.920117, 0.592146, 1; 13:06:25.613 [planetinfo.record]: polar_land_color = 0.922143, 0.934, 0.93261, 1; 13:06:25.613 [planetinfo.record]: polar_sea_color = 0.942773, 0.93303, 0.894806, 1; 13:06:25.614 [planetinfo.record]: sea_color = 0.572266, 0.548609, 0.455801, 1; 13:06:25.614 [planetinfo.record]: rotation_speed = 0.001300 13:06:25.614 [planetinfo.record]: planet zpos = 524810.000000 13:06:25.614 [planetinfo.record]: sun_radius = 109139.100189 13:06:25.614 [planetinfo.record]: sun_vector = 0.306 -0.243 -0.921 13:06:25.614 [planetinfo.record]: sun_distance = 1097330 13:06:25.614 [planetinfo.record]: corona_flare = 0.049585 13:06:25.614 [planetinfo.record]: corona_hues = 0.931946 13:06:25.614 [planetinfo.record]: sun_color = 0.754426, 0.756882, 0.34218, 1 13:06:25.614 [planetinfo.record]: corona_shimmer = 0.514008 13:06:25.614 [planetinfo.record]: station_vector = 0.253 -0.555 0.792 13:06:25.614 [planetinfo.record]: station = coriolis 13:06:30.236 [PLANETINFO OVER]: Done 13:06:30.236 [PLANETINFO LOGGING]: 3 240 13:06:31.242 [planetinfo.record]: seed = 194 41 154 27 13:06:31.242 [planetinfo.record]: coordinates = 41 230 13:06:31.242 [planetinfo.record]: government = 0; 13:06:31.242 [planetinfo.record]: economy = 6; 13:06:31.242 [planetinfo.record]: techlevel = 2; 13:06:31.242 [planetinfo.record]: population = 15; 13:06:31.242 [planetinfo.record]: productivity = 1920; 13:06:31.242 [planetinfo.record]: name = "Aninator"; 13:06:31.242 [planetinfo.record]: inhabitant = "Green Lizard"; 13:06:31.242 [planetinfo.record]: inhabitants = "Green Lizards"; 13:06:31.242 [planetinfo.record]: description = "The planet Aninator is cursed by deadly civil war."; 13:06:31.277 [planetinfo.record]: air_color = 0.601697, 0.939474, 0.941766, 1; 13:06:31.277 [planetinfo.record]: cloud_alpha = 1.000000; 13:06:31.277 [planetinfo.record]: cloud_color = 0.81559, 0.828125, 0.427002, 1; 13:06:31.277 [planetinfo.record]: cloud_fraction = 0.300000; 13:06:31.277 [planetinfo.record]: land_color = 0.66, 0.378984, 0.543642, 1; 13:06:31.277 [planetinfo.record]: land_fraction = 0.450000; 13:06:31.277 [planetinfo.record]: polar_cloud_color = 0.864401, 0.872656, 0.608473, 1; 13:06:31.277 [planetinfo.record]: polar_land_color = 0.934, 0.83458, 0.892834, 1; 13:06:31.278 [planetinfo.record]: polar_sea_color = 0.927386, 0.936328, 0.8843, 1; 13:06:31.278 [planetinfo.record]: sea_color = 0.612395, 0.636719, 0.495198, 1; 13:06:31.278 [planetinfo.record]: rotation_speed = 0.000643 13:06:31.278 [planetinfo.record]: planet zpos = 850950.000000 13:06:31.278 [planetinfo.record]: sun_radius = 142417.957306 13:06:31.278 [planetinfo.record]: sun_vector = -0.078 0.926 -0.369 13:06:31.278 [planetinfo.record]: sun_distance = 1191330 13:06:31.279 [planetinfo.record]: corona_flare = 0.051060 13:06:31.279 [planetinfo.record]: corona_hues = 0.775925 13:06:31.279 [planetinfo.record]: sun_color = 0.791452, 0.82846, 0.828873, 1 13:06:31.279 [planetinfo.record]: corona_shimmer = 0.410724 13:06:31.279 [planetinfo.record]: station_vector = 0.983 0.182 0.015 13:06:31.280 [planetinfo.record]: station = coriolis 13:06:38.793 [PLANETINFO OVER]: Done 13:06:38.794 [PLANETINFO LOGGING]: 3 241 13:06:39.812 [planetinfo.record]: seed = 82 185 10 87 13:06:39.812 [planetinfo.record]: coordinates = 185 113 13:06:39.812 [planetinfo.record]: government = 2; 13:06:39.812 [planetinfo.record]: economy = 1; 13:06:39.812 [planetinfo.record]: techlevel = 8; 13:06:39.812 [planetinfo.record]: population = 36; 13:06:39.812 [planetinfo.record]: productivity = 15552; 13:06:39.812 [planetinfo.record]: name = "Tileen"; 13:06:39.812 [planetinfo.record]: inhabitant = "Human Colonial"; 13:06:39.812 [planetinfo.record]: inhabitants = "Human Colonials"; 13:06:39.812 [planetinfo.record]: description = "Tileen is famous for its inhabitants’ ancient loathing of discos but ravaged by lethal spotted craboids."; 13:06:39.828 [planetinfo.record]: air_color = 0.741421, 0.741865, 0.956074, 1; 13:06:39.828 [planetinfo.record]: cloud_alpha = 1.000000; 13:06:39.828 [planetinfo.record]: cloud_color = 0.806947, 0.806442, 0.871094, 1; 13:06:39.828 [planetinfo.record]: cloud_fraction = 0.280000; 13:06:39.828 [planetinfo.record]: land_color = 0.66, 0.229453, 0.350544, 1; 13:06:39.828 [planetinfo.record]: land_fraction = 0.400000; 13:06:39.828 [planetinfo.record]: polar_cloud_color = 0.850939, 0.850616, 0.891992, 1; 13:06:39.828 [planetinfo.record]: polar_land_color = 0.934, 0.781678, 0.824518, 1; 13:06:39.828 [planetinfo.record]: polar_sea_color = 0.941211, 0.888566, 0.883121, 1; 13:06:39.828 [planetinfo.record]: sea_color = 0.587891, 0.456362, 0.442755, 1; 13:06:39.828 [planetinfo.record]: rotation_speed = 0.004442 13:06:39.828 [planetinfo.record]: planet zpos = 671020.000000 13:06:39.828 [planetinfo.record]: sun_radius = 110921.499329 13:06:39.828 [planetinfo.record]: sun_vector = -0.151 -0.988 -0.016 13:06:39.828 [planetinfo.record]: sun_distance = 910670 13:06:39.828 [planetinfo.record]: corona_flare = 0.021376 13:06:39.828 [planetinfo.record]: corona_hues = 0.948341 13:06:39.828 [planetinfo.record]: sun_color = 0.670612, 0.644993, 0.634664, 1 13:06:39.829 [planetinfo.record]: corona_shimmer = 0.512504 13:06:39.829 [planetinfo.record]: station_vector = -0.402 0.813 0.421 13:06:39.829 [planetinfo.record]: station = coriolis 13:06:44.457 [PLANETINFO OVER]: Done 13:06:44.458 [PLANETINFO LOGGING]: 3 242 13:06:45.465 [planetinfo.record]: seed = 50 107 90 127 13:06:45.465 [planetinfo.record]: coordinates = 107 146 13:06:45.465 [planetinfo.record]: government = 6; 13:06:45.465 [planetinfo.record]: economy = 2; 13:06:45.465 [planetinfo.record]: techlevel = 11; 13:06:45.465 [planetinfo.record]: population = 53; 13:06:45.465 [planetinfo.record]: productivity = 33920; 13:06:45.465 [planetinfo.record]: name = "Ontesoge"; 13:06:45.465 [planetinfo.record]: inhabitant = "Human Colonial"; 13:06:45.465 [planetinfo.record]: inhabitants = "Human Colonials"; 13:06:45.465 [planetinfo.record]: description = "This world is mildly well known for its hoopy night life and Ontesogeian lethal brandy."; 13:06:45.476 [planetinfo.record]: air_color = 0.728505, 0.783979, 0.930059, 1; 13:06:45.476 [planetinfo.record]: cloud_alpha = 1.000000; 13:06:45.476 [planetinfo.record]: cloud_color = 0.752701, 0.785419, 0.792969, 1; 13:06:45.476 [planetinfo.record]: cloud_fraction = 0.130000; 13:06:45.476 [planetinfo.record]: land_color = 0.304219, 0.66, 0.409841, 1; 13:06:45.476 [planetinfo.record]: land_fraction = 0.660000; 13:06:45.476 [planetinfo.record]: polar_cloud_color = 0.829641, 0.851737, 0.856836, 1; 13:06:45.476 [planetinfo.record]: polar_land_color = 0.808129, 0.934, 0.845497, 1; 13:06:45.476 [planetinfo.record]: polar_sea_color = 0.923995, 0.935547, 0.88274, 1; 13:06:45.476 [planetinfo.record]: sea_color = 0.612698, 0.644531, 0.499008, 1; 13:06:45.476 [planetinfo.record]: rotation_speed = 0.002574 13:06:45.476 [planetinfo.record]: planet zpos = 676300.000000 13:06:45.476 [planetinfo.record]: sun_radius = 233842.365417 13:06:45.476 [planetinfo.record]: sun_vector = -0.324 -0.435 -0.840 13:06:45.476 [planetinfo.record]: sun_distance = 1352600 13:06:45.476 [planetinfo.record]: corona_flare = 0.051576 13:06:45.476 [planetinfo.record]: corona_hues = 0.562042 13:06:45.476 [planetinfo.record]: sun_color = 0.752582, 0.745979, 0.371663, 1 13:06:45.477 [planetinfo.record]: corona_shimmer = 0.473061 13:06:45.479 [planetinfo.record]: station_vector = -0.182 0.978 0.098 13:06:45.479 [planetinfo.record]: station = dodecahedron 13:06:50.207 [PLANETINFO OVER]: Done 13:06:50.208 [PLANETINFO LOGGING]: 3 243 13:06:51.217 [planetinfo.record]: seed = 130 99 170 71 13:06:51.217 [planetinfo.record]: coordinates = 99 103 13:06:51.217 [planetinfo.record]: government = 0; 13:06:51.217 [planetinfo.record]: economy = 7; 13:06:51.217 [planetinfo.record]: techlevel = 3; 13:06:51.217 [planetinfo.record]: population = 20; 13:06:51.217 [planetinfo.record]: productivity = 1920; 13:06:51.217 [planetinfo.record]: name = "Soenisti"; 13:06:51.217 [planetinfo.record]: inhabitant = "Fierce Yellow Fat Insect"; 13:06:51.217 [planetinfo.record]: inhabitants = "Fierce Yellow Fat Insects"; 13:06:51.217 [planetinfo.record]: description = "This planet is reasonably famous for its inhabitants’ exceptional loathing of food blenders."; 13:06:51.228 [planetinfo.record]: air_color = 0.590854, 0.859077, 0.989244, 1; 13:06:51.228 [planetinfo.record]: cloud_alpha = 1.000000; 13:06:51.228 [planetinfo.record]: cloud_color = 0.383802, 0.970703, 0.379181, 1; 13:06:51.228 [planetinfo.record]: cloud_fraction = 0.110000; 13:06:51.228 [planetinfo.record]: land_color = 0.66, 0.326838, 0.301641, 1; 13:06:51.228 [planetinfo.record]: land_fraction = 0.500000; 13:06:51.228 [planetinfo.record]: polar_cloud_color = 0.582809, 0.936816, 0.580021, 1; 13:06:51.228 [planetinfo.record]: polar_land_color = 0.934, 0.816131, 0.807217, 1; 13:06:51.228 [planetinfo.record]: polar_sea_color = 0.861711, 0.920117, 0.891371, 1; 13:06:51.228 [planetinfo.record]: sea_color = 0.596001, 0.798828, 0.698999, 1; 13:06:51.228 [planetinfo.record]: rotation_speed = 0.000047 13:06:51.228 [planetinfo.record]: planet zpos = 564840.000000 13:06:51.228 [planetinfo.record]: sun_radius = 79596.536407 13:06:51.228 [planetinfo.record]: sun_vector = -0.780 -0.073 -0.622 13:06:51.228 [planetinfo.record]: sun_distance = 1035540 13:06:51.235 [planetinfo.record]: corona_flare = 0.083041 13:06:51.235 [planetinfo.record]: corona_hues = 0.831833 13:06:51.236 [planetinfo.record]: sun_color = 0.559214, 0.594979, 0.728757, 1 13:06:51.236 [planetinfo.record]: corona_shimmer = 0.454102 13:06:51.236 [planetinfo.record]: station_vector = 0.697 -0.714 0.070 13:06:51.236 [planetinfo.record]: station = coriolis 13:06:56.888 [PLANETINFO OVER]: Done 13:06:56.889 [PLANETINFO LOGGING]: 3 244 13:06:57.914 [planetinfo.record]: seed = 226 23 26 232 13:06:57.914 [planetinfo.record]: coordinates = 23 189 13:06:57.914 [planetinfo.record]: government = 4; 13:06:57.914 [planetinfo.record]: economy = 5; 13:06:57.914 [planetinfo.record]: techlevel = 7; 13:06:57.914 [planetinfo.record]: population = 38; 13:06:57.914 [planetinfo.record]: productivity = 12160; 13:06:57.914 [planetinfo.record]: name = "Usisis"; 13:06:57.914 [planetinfo.record]: inhabitant = "Human Colonial"; 13:06:57.914 [planetinfo.record]: inhabitants = "Human Colonials"; 13:06:57.914 [planetinfo.record]: description = "This world is very fabled for the Usisisian edible poet."; 13:06:57.930 [planetinfo.record]: air_color = 0.455211, 0.701552, 0.863719, 1; 13:06:57.930 [planetinfo.record]: cloud_alpha = 1.000000; 13:06:57.930 [planetinfo.record]: cloud_color = 0.106689, 0.59375, 0.205624, 1; 13:06:57.930 [planetinfo.record]: cloud_fraction = 0.600000; 13:06:57.930 [planetinfo.record]: land_color = 0.656536, 0.649688, 0.66, 1; 13:06:57.931 [planetinfo.record]: land_fraction = 0.390000; 13:06:57.931 [planetinfo.record]: polar_cloud_color = 0.373854, 0.767187, 0.45375, 1; 13:06:57.931 [planetinfo.record]: polar_land_color = 0.932774, 0.930352, 0.934, 1; 13:06:57.931 [planetinfo.record]: polar_sea_color = 0.924414, 0.841474, 0.816084, 1; 13:06:57.931 [planetinfo.record]: sea_color = 0.755859, 0.484591, 0.40155, 1; 13:06:57.931 [planetinfo.record]: rotation_speed = 0.001984 13:06:57.931 [planetinfo.record]: planet zpos = 586440.000000 13:06:57.931 [planetinfo.record]: sun_radius = 158676.869202 13:06:57.931 [planetinfo.record]: sun_vector = 0.178 0.515 0.839 13:06:57.931 [planetinfo.record]: sun_distance = 1124010 13:06:57.931 [planetinfo.record]: corona_flare = 0.042708 13:06:57.932 [planetinfo.record]: corona_hues = 0.688210 13:06:57.932 [planetinfo.record]: sun_color = 0.821756, 0.746318, 0.566863, 1 13:06:57.933 [planetinfo.record]: corona_shimmer = 0.368855 13:06:57.933 [planetinfo.record]: station_vector = -0.768 0.301 0.565 13:06:57.933 [planetinfo.record]: station = coriolis 13:07:03.188 [PLANETINFO OVER]: Done 13:07:03.189 [PLANETINFO LOGGING]: 3 245 13:07:04.193 [planetinfo.record]: seed = 114 99 202 222 13:07:04.193 [planetinfo.record]: coordinates = 99 189 13:07:04.193 [planetinfo.record]: government = 6; 13:07:04.193 [planetinfo.record]: economy = 5; 13:07:04.193 [planetinfo.record]: techlevel = 8; 13:07:04.193 [planetinfo.record]: population = 44; 13:07:04.193 [planetinfo.record]: productivity = 17600; 13:07:04.193 [planetinfo.record]: name = "Rionxe"; 13:07:04.193 [planetinfo.record]: inhabitant = "Rodent"; 13:07:04.193 [planetinfo.record]: inhabitants = "Rodents"; 13:07:04.193 [planetinfo.record]: description = "The planet Rionxe is well known for its inhabitants’ ancient mating traditions but ravaged by unpredictable civil war."; 13:07:04.204 [planetinfo.record]: air_color = 0.536327, 0.985992, 0.967966, 1; 13:07:04.204 [planetinfo.record]: cloud_alpha = 1.000000; 13:07:04.204 [planetinfo.record]: cloud_color = 0.960938, 0.880058, 0.221466, 1; 13:07:04.204 [planetinfo.record]: cloud_fraction = 0.110000; 13:07:04.204 [planetinfo.record]: land_color = 0.456328, 0.516793, 0.66, 1; 13:07:04.204 [planetinfo.record]: land_fraction = 0.420000; 13:07:04.204 [planetinfo.record]: polar_cloud_color = 0.932422, 0.883372, 0.483967, 1; 13:07:04.204 [planetinfo.record]: polar_land_color = 0.861943, 0.883335, 0.934, 1; 13:07:04.204 [planetinfo.record]: polar_sea_color = 0.930859, 0.842682, 0.842682, 1; 13:07:04.204 [planetinfo.record]: sea_color = 0.691406, 0.429428, 0.429428, 1; 13:07:04.204 [planetinfo.record]: rotation_speed = 0.003242 13:07:04.204 [planetinfo.record]: planet zpos = 779880.000000 13:07:04.204 [planetinfo.record]: sun_radius = 204253.010712 13:07:04.205 [planetinfo.record]: sun_vector = 0.684 -0.436 0.585 13:07:04.205 [planetinfo.record]: sun_distance = 1299800 13:07:04.205 [planetinfo.record]: corona_flare = 0.064272 13:07:04.205 [planetinfo.record]: corona_hues = 0.777420 13:07:04.205 [planetinfo.record]: sun_color = 0.652188, 0.611418, 0.53671, 1 13:07:04.205 [planetinfo.record]: corona_shimmer = 0.406889 13:07:04.205 [planetinfo.record]: station_vector = -0.452 0.844 0.289 13:07:04.205 [planetinfo.record]: station = coriolis 13:07:09.884 [PLANETINFO OVER]: Done 13:07:09.885 [PLANETINFO LOGGING]: 3 246 13:07:10.906 [planetinfo.record]: seed = 210 32 218 98 13:07:10.906 [planetinfo.record]: coordinates = 32 66 13:07:10.906 [planetinfo.record]: government = 2; 13:07:10.906 [planetinfo.record]: economy = 2; 13:07:10.906 [planetinfo.record]: techlevel = 6; 13:07:10.906 [planetinfo.record]: population = 29; 13:07:10.906 [planetinfo.record]: productivity = 11136; 13:07:10.906 [planetinfo.record]: name = "Xecees"; 13:07:10.906 [planetinfo.record]: inhabitant = "Large Blue Horned Bird"; 13:07:10.906 [planetinfo.record]: inhabitants = "Large Blue Horned Birds"; 13:07:10.906 [planetinfo.record]: description = "The world Xecees is very noted for its unusual sit coms but ravaged by dreadful civil war."; 13:07:10.940 [planetinfo.record]: air_color = 0.599348, 0.522274, 0.905994, 1; 13:07:10.940 [planetinfo.record]: cloud_alpha = 1.000000; 13:07:10.940 [planetinfo.record]: cloud_color = 0.535337, 0.236481, 0.720703, 1; 13:07:10.940 [planetinfo.record]: cloud_fraction = 0.400000; 13:07:10.940 [planetinfo.record]: land_color = 0.533203, 0.476967, 0.490147, 1; 13:07:10.940 [planetinfo.record]: land_fraction = 0.590000; 13:07:10.940 [planetinfo.record]: polar_cloud_color = 0.691806, 0.478168, 0.824316, 1; 13:07:10.940 [planetinfo.record]: polar_land_color = 0.94668, 0.921718, 0.927569, 1; 13:07:10.940 [planetinfo.record]: polar_sea_color = 0.925586, 0.928711, 0.871573, 1; 13:07:10.940 [planetinfo.record]: sea_color = 0.703296, 0.712891, 0.537453, 1; 13:07:10.940 [planetinfo.record]: rotation_speed = 0.003800 13:07:10.940 [planetinfo.record]: planet zpos = 369600.000000 13:07:10.940 [planetinfo.record]: sun_radius = 63858.764648 13:07:10.940 [planetinfo.record]: sun_vector = -0.523 -0.714 0.465 13:07:10.940 [planetinfo.record]: sun_distance = 739200 13:07:10.940 [planetinfo.record]: corona_flare = 0.031018 13:07:10.940 [planetinfo.record]: corona_hues = 0.679977 13:07:10.940 [planetinfo.record]: sun_color = 0.266484, 0.337626, 0.657468, 1 13:07:10.940 [planetinfo.record]: corona_shimmer = 0.353138 13:07:10.941 [planetinfo.record]: station_vector = -0.227 0.834 0.502 13:07:10.941 [planetinfo.record]: station = coriolis 13:07:15.506 [PLANETINFO OVER]: Done 13:07:15.508 [PLANETINFO LOGGING]: 3 247 13:07:16.510 [planetinfo.record]: seed = 34 114 106 129 13:07:16.510 [planetinfo.record]: coordinates = 114 73 13:07:16.510 [planetinfo.record]: government = 4; 13:07:16.510 [planetinfo.record]: economy = 1; 13:07:16.510 [planetinfo.record]: techlevel = 10; 13:07:16.510 [planetinfo.record]: population = 46; 13:07:16.510 [planetinfo.record]: productivity = 26496; 13:07:16.510 [planetinfo.record]: name = "Leisera"; 13:07:16.510 [planetinfo.record]: inhabitant = "Human Colonial"; 13:07:16.510 [planetinfo.record]: inhabitants = "Human Colonials"; 13:07:16.510 [planetinfo.record]: description = "This world is mildly famous for its pink oceans and mud tennis."; 13:07:16.520 [planetinfo.record]: air_color = 0.393418, 0.835102, 0.707085, 1; 13:07:16.520 [planetinfo.record]: cloud_alpha = 1.000000; 13:07:16.520 [planetinfo.record]: cloud_color = 0.507812, 0.0765252, 0.00595093, 1; 13:07:16.520 [planetinfo.record]: cloud_fraction = 0.320000; 13:07:16.520 [planetinfo.record]: land_color = 0.259758, 0.402004, 0.568359, 1; 13:07:16.520 [planetinfo.record]: land_fraction = 0.270000; 13:07:16.520 [planetinfo.record]: polar_cloud_color = 0.728516, 0.341808, 0.278529, 1; 13:07:16.520 [planetinfo.record]: polar_land_color = 0.815137, 0.874149, 0.943164, 1; 13:07:16.520 [planetinfo.record]: polar_sea_color = 0.915, 0.852597, 0.844088, 1; 13:07:16.520 [planetinfo.record]: sea_color = 0.85, 0.61812, 0.5865, 1; 13:07:16.520 [planetinfo.record]: rotation_speed = 0.003826 13:07:16.520 [planetinfo.record]: planet zpos = 350460.000000 13:07:16.520 [planetinfo.record]: sun_radius = 96035.517883 13:07:16.520 [planetinfo.record]: sun_vector = 0.004 -0.832 -0.555 13:07:16.520 [planetinfo.record]: sun_distance = 637200 13:07:16.520 [planetinfo.record]: corona_flare = 0.031099 13:07:16.520 [planetinfo.record]: corona_hues = 0.604767 13:07:16.520 [planetinfo.record]: sun_color = 0.197932, 0.696979, 0.773422, 1 13:07:16.521 [planetinfo.record]: corona_shimmer = 0.328156 13:07:16.521 [planetinfo.record]: station_vector = -0.486 -0.148 0.861 13:07:16.521 [planetinfo.record]: station = coriolis 13:07:21.524 [PLANETINFO OVER]: Done 13:07:21.524 [PLANETINFO LOGGING]: 3 248 13:07:22.528 [planetinfo.record]: seed = 2 239 154 92 13:07:22.528 [planetinfo.record]: coordinates = 239 48 13:07:22.528 [planetinfo.record]: government = 0; 13:07:22.528 [planetinfo.record]: economy = 2; 13:07:22.528 [planetinfo.record]: techlevel = 8; 13:07:22.528 [planetinfo.record]: population = 35; 13:07:22.528 [planetinfo.record]: productivity = 8960; 13:07:22.528 [planetinfo.record]: name = "Teteso"; 13:07:22.528 [planetinfo.record]: inhabitant = "Yellow Insect"; 13:07:22.528 [planetinfo.record]: inhabitants = "Yellow Insects"; 13:07:22.528 [planetinfo.record]: description = "The planet Teteso is most famous for the Tetesoian deadly goat and its hoopy casinos."; 13:07:22.548 [planetinfo.record]: air_color = 0.652466, 0.851361, 0.789057, 1; 13:07:22.548 [planetinfo.record]: cloud_alpha = 1.000000; 13:07:22.548 [planetinfo.record]: cloud_color = 0.556641, 0.504082, 0.500107, 1; 13:07:22.548 [planetinfo.record]: cloud_fraction = 0.050000; 13:07:22.548 [planetinfo.record]: land_color = 0.66, 0.349819, 0.299063, 1; 13:07:22.548 [planetinfo.record]: land_fraction = 0.510000; 13:07:22.548 [planetinfo.record]: polar_cloud_color = 0.750488, 0.706199, 0.70285, 1; 13:07:22.548 [planetinfo.record]: polar_land_color = 0.934, 0.824262, 0.806305, 1; 13:07:22.548 [planetinfo.record]: polar_sea_color = 0.906072, 0.93125, 0.875684, 1; 13:07:22.548 [planetinfo.record]: sea_color = 0.613148, 0.6875, 0.523413, 1; 13:07:22.548 [planetinfo.record]: rotation_speed = 0.003203 13:07:22.548 [planetinfo.record]: planet zpos = 796510.000000 13:07:22.548 [planetinfo.record]: sun_radius = 127938.147278 13:07:22.548 [planetinfo.record]: sun_vector = 0.088 0.590 -0.803 13:07:22.548 [planetinfo.record]: sun_distance = 1347940 13:07:22.548 [planetinfo.record]: corona_flare = 0.076270 13:07:22.548 [planetinfo.record]: corona_hues = 0.708275 13:07:22.548 [planetinfo.record]: sun_color = 0.657373, 0.630113, 0.447247, 1 13:07:22.549 [planetinfo.record]: corona_shimmer = 0.348037 13:07:22.549 [planetinfo.record]: station_vector = 0.081 -0.284 0.955 13:07:22.549 [planetinfo.record]: station = coriolis 13:07:27.282 [PLANETINFO OVER]: Done 13:07:27.283 [PLANETINFO LOGGING]: 3 249 13:07:28.287 [planetinfo.record]: seed = 146 160 138 228 13:07:28.287 [planetinfo.record]: coordinates = 160 199 13:07:28.287 [planetinfo.record]: government = 2; 13:07:28.287 [planetinfo.record]: economy = 7; 13:07:28.288 [planetinfo.record]: techlevel = 1; 13:07:28.288 [planetinfo.record]: population = 14; 13:07:28.288 [planetinfo.record]: productivity = 2016; 13:07:28.288 [planetinfo.record]: name = "Zainenge"; 13:07:28.288 [planetinfo.record]: inhabitant = "Fierce Insect"; 13:07:28.288 [planetinfo.record]: inhabitants = "Fierce Insects"; 13:07:28.288 [planetinfo.record]: description = "The world Zainenge is reasonably noted for its inhabitants’ exceptional loathing of sit coms."; 13:07:28.300 [planetinfo.record]: air_color = 0.749586, 0.629542, 0.853312, 1; 13:07:28.300 [planetinfo.record]: cloud_alpha = 1.000000; 13:07:28.300 [planetinfo.record]: cloud_color = 0.5625, 0.457031, 0.496582, 1; 13:07:28.300 [planetinfo.record]: cloud_fraction = 0.560000; 13:07:28.300 [planetinfo.record]: land_color = 0.226624, 0.388341, 0.617188, 1; 13:07:28.300 [planetinfo.record]: land_fraction = 0.600000; 13:07:28.300 [planetinfo.record]: polar_cloud_color = 0.753125, 0.664868, 0.697964, 1; 13:07:28.300 [planetinfo.record]: polar_land_color = 0.789842, 0.851305, 0.938281, 1; 13:07:28.301 [planetinfo.record]: polar_sea_color = 0.942578, 0.91761, 0.891031, 1; 13:07:28.301 [planetinfo.record]: sea_color = 0.574219, 0.513376, 0.448608, 1; 13:07:28.301 [planetinfo.record]: rotation_speed = 0.002003 13:07:28.301 [planetinfo.record]: planet zpos = 480000.000000 13:07:28.301 [planetinfo.record]: sun_radius = 119051.513672 13:07:28.301 [planetinfo.record]: sun_vector = -0.381 -0.922 -0.062 13:07:28.301 [planetinfo.record]: sun_distance = 800000 13:07:28.301 [planetinfo.record]: corona_flare = 0.064754 13:07:28.301 [planetinfo.record]: corona_hues = 0.925453 13:07:28.301 [planetinfo.record]: sun_color = 0.233819, 0.271343, 0.815909, 1 13:07:28.301 [planetinfo.record]: corona_shimmer = 0.451916 13:07:28.301 [planetinfo.record]: station_vector = -0.684 -0.081 0.725 13:07:28.301 [planetinfo.record]: station = coriolis 13:07:32.561 [PLANETINFO OVER]: Done 13:07:32.562 [PLANETINFO LOGGING]: 3 250 13:07:33.563 [planetinfo.record]: seed = 114 3 90 34 13:07:33.563 [planetinfo.record]: coordinates = 3 210 13:07:33.563 [planetinfo.record]: government = 6; 13:07:33.563 [planetinfo.record]: economy = 2; 13:07:33.563 [planetinfo.record]: techlevel = 11; 13:07:33.564 [planetinfo.record]: population = 53; 13:07:33.564 [planetinfo.record]: productivity = 33920; 13:07:33.564 [planetinfo.record]: name = "Xetiis"; 13:07:33.564 [planetinfo.record]: inhabitant = "Human Colonial"; 13:07:33.564 [planetinfo.record]: inhabitants = "Human Colonials"; 13:07:33.564 [planetinfo.record]: description = "Xetiis is mildly well known for its exotic cuisine."; 13:07:33.574 [planetinfo.record]: air_color = 0.504108, 0.537725, 0.886482, 1; 13:07:33.575 [planetinfo.record]: cloud_alpha = 1.000000; 13:07:33.575 [planetinfo.record]: cloud_color = 0.201736, 0.31683, 0.662109, 1; 13:07:33.575 [planetinfo.record]: cloud_fraction = 0.290000; 13:07:33.575 [planetinfo.record]: land_color = 0.553169, 0.66, 0.257812, 1; 13:07:33.575 [planetinfo.record]: land_fraction = 0.620000; 13:07:33.575 [planetinfo.record]: polar_cloud_color = 0.451184, 0.537875, 0.797949, 1; 13:07:33.575 [planetinfo.record]: polar_land_color = 0.896204, 0.934, 0.791711, 1; 13:07:33.575 [planetinfo.record]: polar_sea_color = 0.89285, 0.929688, 0.874215, 1; 13:07:33.575 [planetinfo.record]: sea_color = 0.591685, 0.703125, 0.535309, 1; 13:07:33.575 [planetinfo.record]: rotation_speed = 0.000015 13:07:33.575 [planetinfo.record]: planet zpos = 333100.000000 13:07:33.575 [planetinfo.record]: sun_radius = 78002.204285 13:07:33.575 [planetinfo.record]: sun_vector = 0.903 0.037 0.428 13:07:33.576 [planetinfo.record]: sun_distance = 632890 13:07:33.576 [planetinfo.record]: corona_flare = 0.057559 13:07:33.576 [planetinfo.record]: corona_hues = 0.734467 13:07:33.576 [planetinfo.record]: sun_color = 0.82731, 0.678218, 0.420393, 1 13:07:33.576 [planetinfo.record]: corona_shimmer = 0.285936 13:07:33.577 [planetinfo.record]: station_vector = -0.726 -0.684 0.076 13:07:33.577 [planetinfo.record]: station = dodecahedron 13:07:38.309 [PLANETINFO OVER]: Done 13:07:38.309 [PLANETINFO LOGGING]: 3 251 13:07:39.312 [planetinfo.record]: seed = 194 55 42 77 13:07:39.312 [planetinfo.record]: coordinates = 55 29 13:07:39.312 [planetinfo.record]: government = 0; 13:07:39.312 [planetinfo.record]: economy = 7; 13:07:39.312 [planetinfo.record]: techlevel = 3; 13:07:39.312 [planetinfo.record]: population = 20; 13:07:39.312 [planetinfo.record]: productivity = 1920; 13:07:39.312 [planetinfo.record]: name = "Dixeso"; 13:07:39.312 [planetinfo.record]: inhabitant = "Human Colonial"; 13:07:39.312 [planetinfo.record]: inhabitants = "Human Colonials"; 13:07:39.312 [planetinfo.record]: description = "The world Dixeso is mildly noted for the Dixesoian mountain lobstoid but beset by deadly solar activity."; 13:07:39.328 [planetinfo.record]: air_color = 0.62376, 0.891035, 0.828171, 1; 13:07:39.328 [planetinfo.record]: cloud_alpha = 1.000000; 13:07:39.328 [planetinfo.record]: cloud_color = 0.675781, 0.53445, 0.472519, 1; 13:07:39.328 [planetinfo.record]: cloud_fraction = 0.340000; 13:07:39.328 [planetinfo.record]: land_color = 0.541016, 0.431122, 0.510967, 1; 13:07:39.328 [planetinfo.record]: land_fraction = 0.600000; 13:07:39.328 [planetinfo.record]: polar_cloud_color = 0.804102, 0.698997, 0.65294, 1; 13:07:39.328 [planetinfo.record]: polar_land_color = 0.945898, 0.897865, 0.932764, 1; 13:07:39.328 [planetinfo.record]: polar_sea_color = 0.854335, 0.916797, 0.831742, 1; 13:07:39.328 [planetinfo.record]: sea_color = 0.605284, 0.832031, 0.52327, 1; 13:07:39.328 [planetinfo.record]: rotation_speed = 0.002543 13:07:39.328 [planetinfo.record]: planet zpos = 743880.000000 13:07:39.328 [planetinfo.record]: sun_radius = 152828.770294 13:07:39.328 [planetinfo.record]: sun_vector = 0.001 -0.632 0.775 13:07:39.328 [planetinfo.record]: sun_distance = 1301790 13:07:39.328 [planetinfo.record]: corona_flare = 0.013008 13:07:39.328 [planetinfo.record]: corona_hues = 0.842522 13:07:39.328 [planetinfo.record]: sun_color = 0.324916, 0.359721, 0.687393, 1 13:07:39.328 [planetinfo.record]: corona_shimmer = 0.413164 13:07:39.328 [planetinfo.record]: station_vector = -0.186 0.821 0.540 13:07:39.328 [planetinfo.record]: station = coriolis 13:07:44.420 [PLANETINFO OVER]: Done 13:07:44.420 [PLANETINFO LOGGING]: 3 252 13:07:45.425 [planetinfo.record]: seed = 34 23 26 225 13:07:45.425 [planetinfo.record]: coordinates = 23 39 13:07:45.425 [planetinfo.record]: government = 4; 13:07:45.425 [planetinfo.record]: economy = 7; 13:07:45.425 [planetinfo.record]: techlevel = 5; 13:07:45.425 [planetinfo.record]: population = 32; 13:07:45.425 [planetinfo.record]: productivity = 6144; 13:07:45.425 [planetinfo.record]: name = "Leontied"; 13:07:45.425 [planetinfo.record]: inhabitant = "Human Colonial"; 13:07:45.425 [planetinfo.record]: inhabitants = "Human Colonials"; 13:07:45.425 [planetinfo.record]: description = "This world is very fabled for its weird volcanoes."; 13:07:45.438 [planetinfo.record]: air_color = 0.699681, 0.481388, 0.96583, 1; 13:07:45.438 [planetinfo.record]: cloud_alpha = 1.000000; 13:07:45.438 [planetinfo.record]: cloud_color = 0.900391, 0.0844116, 0.600773, 1; 13:07:45.438 [planetinfo.record]: cloud_fraction = 0.200000; 13:07:45.438 [planetinfo.record]: land_color = 0.490166, 0.020625, 0.66, 1; 13:07:45.438 [planetinfo.record]: land_fraction = 0.320000; 13:07:45.438 [planetinfo.record]: polar_cloud_color = 0.905176, 0.392479, 0.71692, 1; 13:07:45.438 [planetinfo.record]: polar_land_color = 0.873915, 0.707797, 0.934, 1; 13:07:45.438 [planetinfo.record]: polar_sea_color = 0.940039, 0.914947, 0.885601, 1; 13:07:45.439 [planetinfo.record]: sea_color = 0.599609, 0.535588, 0.460715, 1; 13:07:45.439 [planetinfo.record]: rotation_speed = 0.004481 13:07:45.439 [planetinfo.record]: planet zpos = 371400.000000 13:07:45.439 [planetinfo.record]: sun_radius = 61961.865997 13:07:45.439 [planetinfo.record]: sun_vector = -0.431 -0.058 0.901 13:07:45.439 [planetinfo.record]: sun_distance = 619000 13:07:45.439 [planetinfo.record]: corona_flare = 0.059480 13:07:45.439 [planetinfo.record]: corona_hues = 0.510345 13:07:45.439 [planetinfo.record]: sun_color = 0.699533, 0.224582, 0.17582, 1 13:07:45.439 [planetinfo.record]: corona_shimmer = 0.479443 13:07:45.439 [planetinfo.record]: station_vector = -0.477 -0.876 0.067 13:07:45.439 [planetinfo.record]: station = coriolis 13:07:50.547 [PLANETINFO OVER]: Done 13:07:50.547 [PLANETINFO LOGGING]: 3 253 13:07:51.551 [planetinfo.record]: seed = 178 24 74 80 13:07:51.552 [planetinfo.record]: coordinates = 24 23 13:07:51.552 [planetinfo.record]: government = 6; 13:07:51.552 [planetinfo.record]: economy = 7; 13:07:51.552 [planetinfo.record]: techlevel = 3; 13:07:51.552 [planetinfo.record]: population = 26; 13:07:51.552 [planetinfo.record]: productivity = 6240; 13:07:51.552 [planetinfo.record]: name = "Eresan"; 13:07:51.552 [planetinfo.record]: inhabitant = "Human Colonial"; 13:07:51.552 [planetinfo.record]: inhabitants = "Human Colonials"; 13:07:51.552 [planetinfo.record]: description = "The world Eresan is scourged by deadly disease."; 13:07:51.564 [planetinfo.record]: air_color = 0.718027, 0.623922, 0.844207, 1; 13:07:51.564 [planetinfo.record]: cloud_alpha = 1.000000; 13:07:51.564 [planetinfo.record]: cloud_color = 0.535156, 0.436905, 0.505988, 1; 13:07:51.565 [planetinfo.record]: cloud_fraction = 0.540000; 13:07:51.565 [planetinfo.record]: land_color = 0.0848694, 0.603516, 0.129441, 1; 13:07:51.565 [planetinfo.record]: land_fraction = 0.320000; 13:07:51.565 [planetinfo.record]: polar_cloud_color = 0.74082, 0.655814, 0.715584, 1; 13:07:51.565 [planetinfo.record]: polar_land_color = 0.737771, 0.939648, 0.75512, 1; 13:07:51.565 [planetinfo.record]: polar_sea_color = 0.933008, 0.89464, 0.869228, 1; 13:07:51.565 [planetinfo.record]: sea_color = 0.669922, 0.559727, 0.48674, 1; 13:07:51.565 [planetinfo.record]: rotation_speed = 0.000665 13:07:51.565 [planetinfo.record]: planet zpos = 397600.000000 13:07:51.565 [planetinfo.record]: sun_radius = 86478.381348 13:07:51.565 [planetinfo.record]: sun_vector = 0.835 0.469 -0.288 13:07:51.565 [planetinfo.record]: sun_distance = 596400 13:07:51.565 [planetinfo.record]: corona_flare = 0.009428 13:07:51.565 [planetinfo.record]: corona_hues = 0.628647 13:07:51.565 [planetinfo.record]: sun_color = 0.797009, 0.721648, 0.203111, 1 13:07:51.565 [planetinfo.record]: corona_shimmer = 0.241282 13:07:51.566 [planetinfo.record]: station_vector = -0.103 -0.099 0.990 13:07:51.566 [planetinfo.record]: station = coriolis 13:07:56.453 [PLANETINFO OVER]: Done 13:07:56.454 [PLANETINFO LOGGING]: 3 254 13:07:57.457 [planetinfo.record]: seed = 18 187 218 37 13:07:57.457 [planetinfo.record]: coordinates = 187 233 13:07:57.457 [planetinfo.record]: government = 2; 13:07:57.457 [planetinfo.record]: economy = 1; 13:07:57.457 [planetinfo.record]: techlevel = 10; 13:07:57.457 [planetinfo.record]: population = 44; 13:07:57.457 [planetinfo.record]: productivity = 19008; 13:07:57.457 [planetinfo.record]: name = "Cearmate"; 13:07:57.457 [planetinfo.record]: inhabitant = "Fierce Red Horned Lobster"; 13:07:57.457 [planetinfo.record]: inhabitants = "Fierce Red Horned Lobsters"; 13:07:57.457 [planetinfo.record]: description = "This world is most fabled for its inhabitants’ ingrained silliness but scourged by deadly edible poets."; 13:07:57.472 [planetinfo.record]: air_color = 0.465865, 0.661732, 0.868922, 1; 13:07:57.472 [planetinfo.record]: cloud_alpha = 1.000000; 13:07:57.472 [planetinfo.record]: cloud_color = 0.12616, 0.609375, 0.394193, 1; 13:07:57.472 [planetinfo.record]: cloud_fraction = 0.230000; 13:07:57.472 [planetinfo.record]: land_color = 0.66, 0.0283594, 0.368853, 1; 13:07:57.472 [planetinfo.record]: land_fraction = 0.560000; 13:07:57.472 [planetinfo.record]: polar_cloud_color = 0.390512, 0.774219, 0.603349, 1; 13:07:57.472 [planetinfo.record]: polar_land_color = 0.934, 0.710533, 0.830996, 1; 13:07:57.472 [planetinfo.record]: polar_sea_color = 0.936678, 0.938281, 0.886969, 1; 13:07:57.472 [planetinfo.record]: sea_color = 0.612968, 0.617188, 0.482178, 1; 13:07:57.472 [planetinfo.record]: rotation_speed = 0.001295 13:07:57.472 [planetinfo.record]: planet zpos = 471130.000000 13:07:57.472 [planetinfo.record]: sun_radius = 111281.144409 13:07:57.472 [planetinfo.record]: sun_vector = -0.218 0.835 -0.505 13:07:57.472 [planetinfo.record]: sun_distance = 856600 13:07:57.473 [planetinfo.record]: corona_flare = 0.042070 13:07:57.473 [planetinfo.record]: corona_hues = 0.556999 13:07:57.473 [planetinfo.record]: sun_color = 0.841864, 0.818615, 0.800427, 1 13:07:57.473 [planetinfo.record]: corona_shimmer = 0.343045 13:07:57.473 [planetinfo.record]: station_vector = -0.931 -0.208 0.299 13:07:57.473 [planetinfo.record]: station = coriolis 13:08:03.265 [PLANETINFO OVER]: Done 13:08:03.266 [PLANETINFO LOGGING]: 3 255 13:08:04.270 [planetinfo.record]: seed = 98 156 234 18 13:08:04.271 [planetinfo.record]: coordinates = 156 171 13:08:04.271 [planetinfo.record]: government = 4; 13:08:04.271 [planetinfo.record]: economy = 3; 13:08:04.271 [planetinfo.record]: techlevel = 6; 13:08:04.271 [planetinfo.record]: population = 32; 13:08:04.271 [planetinfo.record]: productivity = 14336; 13:08:04.271 [planetinfo.record]: name = "Enanar"; 13:08:04.271 [planetinfo.record]: inhabitant = "Green Frog"; 13:08:04.271 [planetinfo.record]: inhabitants = "Green Frogs"; 13:08:04.271 [planetinfo.record]: description = "This planet is notable for the Enanarian edible moth and its great dense forests."; 13:08:04.285 [planetinfo.record]: air_color = 0.662527, 0.850061, 0.801561, 1; 13:08:04.286 [planetinfo.record]: cloud_alpha = 1.000000; 13:08:04.286 [planetinfo.record]: cloud_color = 0.552734, 0.526285, 0.518188, 1; 13:08:04.286 [planetinfo.record]: cloud_fraction = 0.320000; 13:08:04.286 [planetinfo.record]: land_color = 0.632812, 0.610565, 0.543823, 1; 13:08:04.286 [planetinfo.record]: land_fraction = 0.550000; 13:08:04.286 [planetinfo.record]: polar_cloud_color = 0.74873, 0.726338, 0.719483, 1; 13:08:04.286 [planetinfo.record]: polar_land_color = 0.936719, 0.928486, 0.903787, 1; 13:08:04.286 [planetinfo.record]: polar_sea_color = 0.785931, 0.945703, 0.859576, 1; 13:08:04.286 [planetinfo.record]: sea_color = 0.176041, 0.542969, 0.345172, 1; 13:08:04.286 [planetinfo.record]: rotation_speed = 0.001282 13:08:04.286 [planetinfo.record]: planet zpos = 348400.000000 13:08:04.286 [planetinfo.record]: sun_radius = 98214.500122 13:08:04.286 [planetinfo.record]: sun_vector = 0.259 0.605 -0.753 13:08:04.286 [planetinfo.record]: sun_distance = 766480 13:08:04.286 [planetinfo.record]: corona_flare = 0.089650 13:08:04.286 [planetinfo.record]: corona_hues = 0.996384 13:08:04.286 [planetinfo.record]: sun_color = 0.239445, 0.303538, 0.786185, 1 13:08:04.286 [planetinfo.record]: corona_shimmer = 0.382920 13:08:04.286 [planetinfo.record]: station_vector = 0.627 0.683 0.374 13:08:04.286 [planetinfo.record]: station = coriolis 13:08:08.953 [PLANETINFO OVER]: Done 13:09:28.422 [planetinfo.record]: seed = 64 151 219 247 13:09:28.423 [planetinfo.record]: coordinates = 151 178 13:09:28.423 [planetinfo.record]: government = 0; 13:09:28.423 [planetinfo.record]: economy = 2; 13:09:28.423 [planetinfo.record]: techlevel = 8; 13:09:28.423 [planetinfo.record]: population = 35; 13:09:28.423 [planetinfo.record]: productivity = 8960; 13:09:28.423 [planetinfo.record]: name = "Tileer"; 13:09:28.423 [planetinfo.record]: inhabitant = "Furry Rodent"; 13:09:28.423 [planetinfo.record]: inhabitants = "Furry Rodents"; 13:09:28.423 [planetinfo.record]: description = "The world Tileer is reasonably noted for its inhabitants’ eccentric shyness."; 13:09:28.440 [planetinfo.record]: air_color = 0.710054, 0.57508, 0.891035, 1; 13:09:28.440 [planetinfo.record]: cloud_alpha = 1.000000; 13:09:28.440 [planetinfo.record]: cloud_color = 0.675781, 0.361649, 0.582523, 1; 13:09:28.440 [planetinfo.record]: cloud_fraction = 0.060000; 13:09:28.440 [planetinfo.record]: land_color = 0.66, 0.488796, 0.221719, 1; 13:09:28.440 [planetinfo.record]: land_fraction = 0.710000; 13:09:28.440 [planetinfo.record]: polar_cloud_color = 0.804102, 0.570488, 0.734748, 1; 13:09:28.440 [planetinfo.record]: polar_land_color = 0.934, 0.87343, 0.778941, 1; 13:09:28.440 [planetinfo.record]: polar_sea_color = 0.860937, 0.885291, 0.95, 1; 13:09:28.440 [planetinfo.record]: sea_color = 0.3125, 0.36377, 0.5, 1; 13:09:28.440 [planetinfo.record]: rotation_speed = 0.003300 13:09:28.440 [planetinfo.record]: planet zpos = 523490.000000 13:09:28.440 [planetinfo.record]: sun_radius = 138593.094482 13:09:28.440 [planetinfo.record]: sun_vector = -0.228 0.139 -0.964 13:09:28.440 [planetinfo.record]: sun_distance = 951800 13:09:28.441 [planetinfo.record]: corona_flare = 0.054324 13:09:28.441 [planetinfo.record]: corona_hues = 0.902267 13:09:28.441 [planetinfo.record]: sun_color = 0.360711, 0.665672, 0.703836, 1 13:09:28.441 [planetinfo.record]: corona_shimmer = 0.283377 13:09:28.441 [planetinfo.record]: station_vector = 0.848 0.345 0.402 13:09:28.441 [planetinfo.record]: station = coriolis 13:09:33.086 [PLANETINFO OVER]: Done 13:09:34.605 [PLANETINFO LOGGING]: 4 0 13:09:35.393 [planetinfo.record]: seed = 132 32 53 123 13:09:35.393 [planetinfo.record]: coordinates = 32 165 13:09:35.393 [planetinfo.record]: government = 0; 13:09:35.393 [planetinfo.record]: economy = 7; 13:09:35.393 [planetinfo.record]: techlevel = 0; 13:09:35.393 [planetinfo.record]: population = 8; 13:09:35.393 [planetinfo.record]: productivity = 768; 13:09:35.393 [planetinfo.record]: name = "Anleis"; 13:09:35.393 [planetinfo.record]: inhabitant = "Human Colonial"; 13:09:35.393 [planetinfo.record]: inhabitants = "Human Colonials"; 13:09:35.393 [planetinfo.record]: description = "This planet is mildly fabled for its inhabitants’ eccentric love for tourists but plagued by lethal disease."; 13:09:35.395 [planetinfo.record]: air_color = 0.56493, 0.956793, 0.975586, 1; 13:09:35.395 [planetinfo.record]: cloud_alpha = 1.000000; 13:09:35.395 [planetinfo.record]: cloud_color = 0.838047, 0.929688, 0.312317, 1; 13:09:35.395 [planetinfo.record]: cloud_fraction = 0.250000; 13:09:35.395 [planetinfo.record]: land_color = 0.66, 0.314531, 0.355016, 1; 13:09:35.395 [planetinfo.record]: land_fraction = 0.350000; 13:09:35.395 [planetinfo.record]: polar_cloud_color = 0.861782, 0.918359, 0.537204, 1; 13:09:35.395 [planetinfo.record]: polar_land_color = 0.934, 0.811777, 0.8261, 1; 13:09:35.395 [planetinfo.record]: polar_sea_color = 0.943164, 0.927616, 0.894624, 1; 13:09:35.395 [planetinfo.record]: sea_color = 0.568359, 0.530882, 0.451357, 1; 13:09:35.395 [planetinfo.record]: rotation_speed = 0.001428 13:09:35.395 [planetinfo.record]: planet zpos = 566400.000000 13:09:35.396 [planetinfo.record]: sun_radius = 131019.755859 13:09:35.396 [planetinfo.record]: sun_vector = 0.510 -0.537 -0.672 13:09:35.396 [planetinfo.record]: sun_distance = 906240 13:09:35.396 [planetinfo.record]: corona_flare = 0.042007 13:09:35.396 [planetinfo.record]: corona_hues = 0.823219 13:09:35.396 [planetinfo.record]: sun_color = 0.388173, 0.445679, 0.838184, 1 13:09:35.396 [planetinfo.record]: corona_shimmer = 0.528857 13:09:35.396 [planetinfo.record]: station_vector = 0.672 0.521 0.527 13:09:35.396 [planetinfo.record]: station = coriolis 13:09:40.126 [PLANETINFO OVER]: Done 13:09:40.127 [PLANETINFO LOGGING]: 4 1 13:09:41.138 [planetinfo.record]: seed = 168 153 27 184 13:09:41.138 [planetinfo.record]: coordinates = 153 221 13:09:41.138 [planetinfo.record]: government = 5; 13:09:41.138 [planetinfo.record]: economy = 5; 13:09:41.138 [planetinfo.record]: techlevel = 6; 13:09:41.139 [planetinfo.record]: population = 35; 13:09:41.139 [planetinfo.record]: productivity = 12600; 13:09:41.139 [planetinfo.record]: name = "Edre"; 13:09:41.139 [planetinfo.record]: inhabitant = "Human Colonial"; 13:09:41.139 [planetinfo.record]: inhabitants = "Human Colonials"; 13:09:41.139 [planetinfo.record]: description = "The planet Edre is a boring planet."; 13:09:41.176 [planetinfo.record]: air_color = 0.693851, 0.752141, 0.967781, 1; 13:09:41.176 [planetinfo.record]: cloud_alpha = 1.000000; 13:09:41.176 [planetinfo.record]: cloud_color = 0.679688, 0.821289, 0.90625, 1; 13:09:41.176 [planetinfo.record]: cloud_fraction = 0.320000; 13:09:41.176 [planetinfo.record]: land_color = 0.66, 0.126328, 0.276423, 1; 13:09:41.176 [planetinfo.record]: land_fraction = 0.570000; 13:09:41.176 [planetinfo.record]: polar_cloud_color = 0.765967, 0.85462, 0.907812, 1; 13:09:41.176 [planetinfo.record]: polar_land_color = 0.934, 0.745193, 0.798295, 1; 13:09:41.176 [planetinfo.record]: polar_sea_color = 0.948438, 0.908563, 0.900738, 1; 13:09:41.176 [planetinfo.record]: sea_color = 0.515625, 0.428914, 0.411896, 1; 13:09:41.176 [planetinfo.record]: rotation_speed = 0.004833 13:09:41.176 [planetinfo.record]: planet zpos = 501700.000000 13:09:41.176 [planetinfo.record]: sun_radius = 115504.452057 13:09:41.176 [planetinfo.record]: sun_vector = -0.374 -0.295 0.879 13:09:41.176 [planetinfo.record]: sun_distance = 1003400 13:09:41.176 [planetinfo.record]: corona_flare = 0.000294 13:09:41.176 [planetinfo.record]: corona_hues = 0.619072 13:09:41.176 [planetinfo.record]: sun_color = 0.847885, 0.83137, 0.826431, 1 13:09:41.176 [planetinfo.record]: corona_shimmer = 0.569868 13:09:41.177 [planetinfo.record]: station_vector = -0.494 -0.338 0.801 13:09:41.177 [planetinfo.record]: station = coriolis 13:09:45.825 [PLANETINFO OVER]: Done 13:09:45.825 [PLANETINFO LOGGING]: 4 2 13:09:46.834 [planetinfo.record]: seed = 144 103 5 23 13:09:46.834 [planetinfo.record]: coordinates = 103 128 13:09:46.834 [planetinfo.record]: government = 2; 13:09:46.834 [planetinfo.record]: economy = 0; 13:09:46.834 [planetinfo.record]: techlevel = 11; 13:09:46.834 [planetinfo.record]: population = 47; 13:09:46.834 [planetinfo.record]: productivity = 22560; 13:09:46.834 [planetinfo.record]: name = "Tionis"; 13:09:46.834 [planetinfo.record]: inhabitant = "Human Colonial"; 13:09:46.834 [planetinfo.record]: inhabitants = "Human Colonials"; 13:09:46.835 [planetinfo.record]: description = "The world Tionis is a boring world."; 13:09:46.850 [planetinfo.record]: air_color = 0.591456, 0.94892, 0.94575, 1; 13:09:46.850 [planetinfo.record]: cloud_alpha = 1.000000; 13:09:46.850 [planetinfo.record]: cloud_color = 0.849609, 0.842557, 0.398254, 1; 13:09:46.850 [planetinfo.record]: cloud_fraction = 0.520000; 13:09:46.850 [planetinfo.record]: land_color = 0.5245, 0.384033, 0.558594, 1; 13:09:46.850 [planetinfo.record]: land_fraction = 0.540000; 13:09:46.850 [planetinfo.record]: polar_cloud_color = 0.882324, 0.877747, 0.589365, 1; 13:09:46.850 [planetinfo.record]: polar_land_color = 0.929734, 0.87038, 0.944141, 1; 13:09:46.850 [planetinfo.record]: polar_sea_color = 0.931445, 0.861874, 0.85049, 1; 13:09:46.850 [planetinfo.record]: sea_color = 0.685547, 0.480728, 0.447212, 1; 13:09:46.850 [planetinfo.record]: rotation_speed = 0.001236 13:09:46.850 [planetinfo.record]: planet zpos = 565320.000000 13:09:46.851 [planetinfo.record]: sun_radius = 114018.334045 13:09:46.851 [planetinfo.record]: sun_vector = -0.133 -0.102 -0.986 13:09:46.851 [planetinfo.record]: sun_distance = 1036420 13:09:46.851 [planetinfo.record]: corona_flare = 0.021129 13:09:46.851 [planetinfo.record]: corona_hues = 0.677261 13:09:46.851 [planetinfo.record]: sun_color = 0.534308, 0.752967, 0.771707, 1 13:09:46.851 [planetinfo.record]: corona_shimmer = 0.471367 13:09:46.851 [planetinfo.record]: station_vector = -0.329 -0.942 0.068 13:09:46.851 [planetinfo.record]: station = dodecahedron 13:09:52.114 [PLANETINFO OVER]: Done 13:09:52.115 [PLANETINFO LOGGING]: 4 3 13:09:53.119 [planetinfo.record]: seed = 252 147 243 16 13:09:53.119 [planetinfo.record]: coordinates = 147 125 13:09:53.119 [planetinfo.record]: government = 7; 13:09:53.119 [planetinfo.record]: economy = 5; 13:09:53.119 [planetinfo.record]: techlevel = 9; 13:09:53.119 [planetinfo.record]: population = 49; 13:09:53.119 [planetinfo.record]: productivity = 21560; 13:09:53.119 [planetinfo.record]: name = "Erxesoan"; 13:09:53.119 [planetinfo.record]: inhabitant = "Green Feline"; 13:09:53.119 [planetinfo.record]: inhabitants = "Green Felines"; 13:09:53.119 [planetinfo.record]: description = "This planet is fabled for its fabulous Sebese juice and its ancient mountains."; 13:09:53.132 [planetinfo.record]: air_color = 0.656842, 0.888339, 0.901441, 1; 13:09:53.132 [planetinfo.record]: cloud_alpha = 1.000000; 13:09:53.132 [planetinfo.record]: cloud_color = 0.680923, 0.707031, 0.55513, 1; 13:09:53.132 [planetinfo.record]: cloud_fraction = 0.150000; 13:09:53.132 [planetinfo.record]: land_color = 0.628857, 0.634766, 0.622368, 1; 13:09:53.132 [planetinfo.record]: land_fraction = 0.440000; 13:09:53.133 [planetinfo.record]: polar_cloud_color = 0.799282, 0.818164, 0.708303, 1; 13:09:53.133 [planetinfo.record]: polar_land_color = 0.934344, 0.936523, 0.931951, 1; 13:09:53.133 [planetinfo.record]: polar_sea_color = 0.814362, 0.907422, 0.780702, 1; 13:09:53.133 [planetinfo.record]: sea_color = 0.54601, 0.925781, 0.408646, 1; 13:09:53.133 [planetinfo.record]: rotation_speed = 0.003349 13:09:53.133 [planetinfo.record]: planet zpos = 325930.000000 13:09:53.133 [planetinfo.record]: sun_radius = 73345.281677 13:09:53.133 [planetinfo.record]: sun_vector = 0.367 -0.481 -0.796 13:09:53.133 [planetinfo.record]: sun_distance = 592600 13:09:53.133 [planetinfo.record]: corona_flare = 0.083109 13:09:53.133 [planetinfo.record]: corona_hues = 0.833298 13:09:53.133 [planetinfo.record]: sun_color = 0.192399, 0.439216, 0.651422, 1 13:09:53.133 [planetinfo.record]: corona_shimmer = 0.564576 13:09:53.133 [planetinfo.record]: station_vector = -0.968 -0.167 0.186 13:09:53.134 [planetinfo.record]: station = coriolis 13:09:57.931 [PLANETINFO OVER]: Done 13:09:57.931 [PLANETINFO LOGGING]: 4 4 13:09:58.933 [planetinfo.record]: seed = 76 251 165 229 13:09:58.933 [planetinfo.record]: coordinates = 251 199 13:09:58.934 [planetinfo.record]: government = 1; 13:09:58.934 [planetinfo.record]: economy = 7; 13:09:58.934 [planetinfo.record]: techlevel = 4; 13:09:58.934 [planetinfo.record]: population = 25; 13:09:58.934 [planetinfo.record]: productivity = 3000; 13:09:58.934 [planetinfo.record]: name = "Ceuses"; 13:09:58.934 [planetinfo.record]: inhabitant = "Fierce Fat Humanoid"; 13:09:58.934 [planetinfo.record]: inhabitants = "Fierce Fat Humanoids"; 13:09:58.934 [planetinfo.record]: description = "This world is a tedious little planet."; 13:09:58.960 [planetinfo.record]: air_color = 0.678789, 0.863719, 0.854862, 1; 13:09:58.960 [planetinfo.record]: cloud_alpha = 1.000000; 13:09:58.960 [planetinfo.record]: cloud_color = 0.59375, 0.590362, 0.568237, 1; 13:09:58.960 [planetinfo.record]: cloud_fraction = 0.320000; 13:09:58.960 [planetinfo.record]: land_color = 0.629183, 0.265547, 0.66, 1; 13:09:58.960 [planetinfo.record]: land_fraction = 0.570000; 13:09:58.960 [planetinfo.record]: polar_cloud_color = 0.767187, 0.764451, 0.746584, 1; 13:09:58.960 [planetinfo.record]: polar_land_color = 0.923097, 0.794447, 0.934, 1; 13:09:58.960 [planetinfo.record]: polar_sea_color = 0.927344, 0.836245, 0.826821, 1; 13:09:58.960 [planetinfo.record]: sea_color = 0.726562, 0.441064, 0.41153, 1; 13:09:58.960 [planetinfo.record]: rotation_speed = 0.002448 13:09:58.960 [planetinfo.record]: planet zpos = 521640.000000 13:09:58.960 [planetinfo.record]: sun_radius = 89814.740295 13:09:58.960 [planetinfo.record]: sun_vector = -0.140 0.957 -0.256 13:09:58.960 [planetinfo.record]: sun_distance = 912870 13:09:58.960 [planetinfo.record]: corona_flare = 0.081154 13:09:58.961 [planetinfo.record]: corona_hues = 0.909752 13:09:58.961 [planetinfo.record]: sun_color = 0.684747, 0.71976, 0.826297, 1 13:09:58.961 [planetinfo.record]: corona_shimmer = 0.424472 13:09:58.961 [planetinfo.record]: station_vector = 0.728 0.235 0.644 13:09:58.961 [planetinfo.record]: station = coriolis 13:10:03.633 [PLANETINFO OVER]: Done 13:10:03.633 [PLANETINFO LOGGING]: 4 5 13:10:04.639 [planetinfo.record]: seed = 192 23 219 73 13:10:04.639 [planetinfo.record]: coordinates = 23 137 13:10:04.639 [planetinfo.record]: government = 0; 13:10:04.639 [planetinfo.record]: economy = 3; 13:10:04.639 [planetinfo.record]: techlevel = 7; 13:10:04.639 [planetinfo.record]: population = 32; 13:10:04.639 [planetinfo.record]: productivity = 7168; 13:10:04.640 [planetinfo.record]: name = "Esmain"; 13:10:04.640 [planetinfo.record]: inhabitant = "Small Yellow Insect"; 13:10:04.640 [planetinfo.record]: inhabitants = "Small Yellow Insects"; 13:10:04.640 [planetinfo.record]: description = "The planet Esmain is scourged by killer edible arts graduates."; 13:10:04.655 [planetinfo.record]: air_color = 0.645091, 0.513777, 0.926807, 1; 13:10:04.655 [planetinfo.record]: cloud_alpha = 1.000000; 13:10:04.655 [planetinfo.record]: cloud_color = 0.765134, 0.204979, 0.783203, 1; 13:10:04.655 [planetinfo.record]: cloud_fraction = 0.410000; 13:10:04.656 [planetinfo.record]: land_color = 0.401838, 0.595703, 0.0186157, 1; 13:10:04.656 [planetinfo.record]: land_fraction = 0.250000; 13:10:04.656 [planetinfo.record]: polar_cloud_color = 0.84015, 0.459103, 0.852441, 1; 13:10:04.656 [planetinfo.record]: polar_land_color = 0.863916, 0.94043, 0.712669, 1; 13:10:04.656 [planetinfo.record]: polar_sea_color = 0.932617, 0.915114, 0.877972, 1; 13:10:04.656 [planetinfo.record]: sea_color = 0.673828, 0.623242, 0.5159, 1; 13:10:04.656 [planetinfo.record]: rotation_speed = 0.003201 13:10:04.656 [planetinfo.record]: planet zpos = 617160.000000 13:10:04.656 [planetinfo.record]: sun_radius = 134737.716522 13:10:04.656 [planetinfo.record]: sun_vector = 0.457 0.882 -0.114 13:10:04.656 [planetinfo.record]: sun_distance = 1028600 13:10:04.656 [planetinfo.record]: corona_flare = 0.081531 13:10:04.656 [planetinfo.record]: corona_hues = 0.985588 13:10:04.656 [planetinfo.record]: sun_color = 0.327044, 0.450661, 0.702093, 1 13:10:04.657 [planetinfo.record]: corona_shimmer = 0.255942 13:10:04.657 [planetinfo.record]: station_vector = -0.051 0.820 0.570 13:10:04.657 [planetinfo.record]: station = coriolis 13:10:09.274 [PLANETINFO OVER]: Done 13:10:09.275 [PLANETINFO LOGGING]: 4 6 13:10:10.296 [planetinfo.record]: seed = 184 129 149 185 13:10:10.296 [planetinfo.record]: coordinates = 129 76 13:10:10.296 [planetinfo.record]: government = 7; 13:10:10.296 [planetinfo.record]: economy = 4; 13:10:10.296 [planetinfo.record]: techlevel = 8; 13:10:10.296 [planetinfo.record]: population = 44; 13:10:10.296 [planetinfo.record]: productivity = 23232; 13:10:10.296 [planetinfo.record]: name = "Orusge"; 13:10:10.296 [planetinfo.record]: inhabitant = "Harmless Furry Feline"; 13:10:10.296 [planetinfo.record]: inhabitants = "Harmless Furry Felines"; 13:10:10.296 [planetinfo.record]: description = "The planet Orusge is famous for Orusgeian shrew steak but ravaged by unpredictable solar activity."; 13:10:10.316 [planetinfo.record]: air_color = 0.631393, 0.893637, 0.850376, 1; 13:10:10.317 [planetinfo.record]: cloud_alpha = 1.000000; 13:10:10.317 [planetinfo.record]: cloud_color = 0.683594, 0.590467, 0.491333, 1; 13:10:10.317 [planetinfo.record]: cloud_fraction = 0.230000; 13:10:10.317 [planetinfo.record]: land_color = 0.48221, 0.66, 0.425391, 1; 13:10:10.317 [planetinfo.record]: land_fraction = 0.350000; 13:10:10.317 [planetinfo.record]: polar_cloud_color = 0.807617, 0.738853, 0.665653, 1; 13:10:10.317 [planetinfo.record]: polar_land_color = 0.8711, 0.934, 0.850998, 1; 13:10:10.317 [planetinfo.record]: polar_sea_color = 0.899638, 0.934766, 0.886932, 1; 13:10:10.317 [planetinfo.record]: sea_color = 0.554285, 0.652344, 0.518817, 1; 13:10:10.318 [planetinfo.record]: rotation_speed = 0.004752 13:10:10.318 [planetinfo.record]: planet zpos = 629880.000000 13:10:10.318 [planetinfo.record]: sun_radius = 98617.381592 13:10:10.318 [planetinfo.record]: sun_vector = 0.734 0.424 -0.531 13:10:10.318 [planetinfo.record]: sun_distance = 997310 13:10:10.318 [planetinfo.record]: corona_flare = 0.089696 13:10:10.318 [planetinfo.record]: corona_hues = 0.714920 13:10:10.318 [planetinfo.record]: sun_color = 0.699689, 0.65218, 0.578114, 1 13:10:10.318 [planetinfo.record]: corona_shimmer = 0.460186 13:10:10.319 [planetinfo.record]: station_vector = 0.260 0.798 0.543 13:10:10.319 [planetinfo.record]: station = coriolis 13:10:14.732 [PLANETINFO OVER]: Done 13:10:14.733 [PLANETINFO LOGGING]: 4 7 13:10:15.734 [planetinfo.record]: seed = 244 4 83 80 13:10:15.734 [planetinfo.record]: coordinates = 4 195 13:10:15.734 [planetinfo.record]: government = 6; 13:10:15.734 [planetinfo.record]: economy = 3; 13:10:15.735 [planetinfo.record]: techlevel = 7; 13:10:15.735 [planetinfo.record]: population = 38; 13:10:15.735 [planetinfo.record]: productivity = 21280; 13:10:15.735 [planetinfo.record]: name = "Ereddive"; 13:10:15.735 [planetinfo.record]: inhabitant = "Human Colonial"; 13:10:15.735 [planetinfo.record]: inhabitants = "Human Colonials"; 13:10:15.735 [planetinfo.record]: description = "The planet Ereddive is reasonably fabled for Zero-G cricket and vicious Soa’xenu gargle blasters."; 13:10:15.745 [planetinfo.record]: air_color = 0.616891, 0.499689, 0.93201, 1; 13:10:15.745 [planetinfo.record]: cloud_alpha = 1.000000; 13:10:15.745 [planetinfo.record]: cloud_color = 0.689955, 0.165382, 0.798828, 1; 13:10:15.745 [planetinfo.record]: cloud_fraction = 0.180000; 13:10:15.745 [planetinfo.record]: land_color = 0.585234, 0.66, 0.642477, 1; 13:10:15.745 [planetinfo.record]: land_fraction = 0.610000; 13:10:15.745 [planetinfo.record]: polar_cloud_color = 0.786261, 0.433513, 0.859473, 1; 13:10:15.745 [planetinfo.record]: polar_land_color = 0.907549, 0.934, 0.927801, 1; 13:10:15.745 [planetinfo.record]: polar_sea_color = 0.916103, 0.931445, 0.873685, 1; 13:10:15.745 [planetinfo.record]: sea_color = 0.640378, 0.685547, 0.515499, 1; 13:10:15.745 [planetinfo.record]: rotation_speed = 0.004294 13:10:15.745 [planetinfo.record]: planet zpos = 394800.000000 13:10:15.745 [planetinfo.record]: sun_radius = 78951.480103 13:10:15.745 [planetinfo.record]: sun_vector = 0.463 0.532 -0.709 13:10:15.746 [planetinfo.record]: sun_distance = 507600 13:10:15.746 [planetinfo.record]: corona_flare = 0.000710 13:10:15.746 [planetinfo.record]: corona_hues = 0.874123 13:10:15.746 [planetinfo.record]: sun_color = 0.281983, 0.66572, 0.781967, 1 13:10:15.746 [planetinfo.record]: corona_shimmer = 0.476799 13:10:15.746 [planetinfo.record]: station_vector = -0.212 0.705 0.677 13:10:15.746 [planetinfo.record]: station = coriolis 13:10:20.483 [PLANETINFO OVER]: Done 13:10:20.484 [PLANETINFO LOGGING]: 4 8 13:10:21.503 [planetinfo.record]: seed = 212 214 85 93 13:10:21.503 [planetinfo.record]: coordinates = 214 109 13:10:21.503 [planetinfo.record]: government = 2; 13:10:21.503 [planetinfo.record]: economy = 5; 13:10:21.503 [planetinfo.record]: techlevel = 5; 13:10:21.503 [planetinfo.record]: population = 28; 13:10:21.503 [planetinfo.record]: productivity = 6720; 13:10:21.503 [planetinfo.record]: name = "Isxevela"; 13:10:21.503 [planetinfo.record]: inhabitant = "Human Colonial"; 13:10:21.503 [planetinfo.record]: inhabitants = "Human Colonials"; 13:10:21.503 [planetinfo.record]: description = "The planet Isxevela is mildly notable for its inhabitants’ weird shyness."; 13:10:21.517 [planetinfo.record]: air_color = 0.469044, 0.563855, 0.89884, 1; 13:10:21.517 [planetinfo.record]: cloud_alpha = 1.000000; 13:10:21.517 [planetinfo.record]: cloud_color = 0.114716, 0.493729, 0.699219, 1; 13:10:21.517 [planetinfo.record]: cloud_fraction = 0.450000; 13:10:21.517 [planetinfo.record]: land_color = 0.489844, 0.644048, 0.66, 1; 13:10:21.517 [planetinfo.record]: land_fraction = 0.270000; 13:10:21.517 [planetinfo.record]: polar_cloud_color = 0.389026, 0.665016, 0.814648, 1; 13:10:21.517 [planetinfo.record]: polar_land_color = 0.873801, 0.928356, 0.934, 1; 13:10:21.517 [planetinfo.record]: polar_sea_color = 0.937089, 0.9375, 0.884857, 1; 13:10:21.517 [planetinfo.record]: sea_color = 0.623903, 0.625, 0.484619, 1; 13:10:21.517 [planetinfo.record]: rotation_speed = 0.003317 13:10:21.517 [planetinfo.record]: planet zpos = 762960.000000 13:10:21.517 [planetinfo.record]: sun_radius = 153146.539917 13:10:21.517 [planetinfo.record]: sun_vector = -0.263 -0.212 -0.941 13:10:21.517 [planetinfo.record]: sun_distance = 1144440 13:10:21.517 [planetinfo.record]: corona_flare = 0.003198 13:10:21.517 [planetinfo.record]: corona_hues = 0.606079 13:10:21.517 [planetinfo.record]: sun_color = 0.651852, 0.615562, 0.207476, 1 13:10:21.518 [planetinfo.record]: corona_shimmer = 0.901656 13:10:21.519 [planetinfo.record]: station_vector = -0.401 -0.569 0.718 13:10:21.519 [planetinfo.record]: station = coriolis 13:10:26.874 [PLANETINFO OVER]: Done 13:10:26.875 [PLANETINFO LOGGING]: 4 9 13:10:27.881 [planetinfo.record]: seed = 152 213 219 77 13:10:27.881 [planetinfo.record]: coordinates = 213 214 13:10:27.881 [planetinfo.record]: government = 3; 13:10:27.881 [planetinfo.record]: economy = 6; 13:10:27.881 [planetinfo.record]: techlevel = 4; 13:10:27.881 [planetinfo.record]: population = 26; 13:10:27.881 [planetinfo.record]: productivity = 5824; 13:10:27.881 [planetinfo.record]: name = "Dioris"; 13:10:27.881 [planetinfo.record]: inhabitant = "Yellow Bony Bird"; 13:10:27.881 [planetinfo.record]: inhabitants = "Yellow Bony Birds"; 13:10:27.881 [planetinfo.record]: description = "Dioris is an unremarkable planet."; 13:10:27.932 [planetinfo.record]: air_color = 0.691688, 0.852608, 0.878678, 1; 13:10:27.932 [planetinfo.record]: cloud_alpha = 1.000000; 13:10:27.932 [planetinfo.record]: cloud_color = 0.627952, 0.638672, 0.613724, 1; 13:10:27.932 [planetinfo.record]: cloud_fraction = 0.090000; 13:10:27.932 [planetinfo.record]: land_color = 0.0303955, 0.648438, 0.0690231, 1; 13:10:27.932 [planetinfo.record]: land_fraction = 0.590000; 13:10:27.932 [planetinfo.record]: polar_cloud_color = 0.779142, 0.787402, 0.768179, 1; 13:10:27.932 [planetinfo.record]: polar_land_color = 0.712326, 0.935156, 0.726253, 1; 13:10:27.932 [planetinfo.record]: polar_sea_color = 0.86909, 0.925391, 0.872609, 1; 13:10:27.932 [planetinfo.record]: sea_color = 0.564525, 0.746094, 0.575873, 1; 13:10:27.932 [planetinfo.record]: rotation_speed = 0.001669 13:10:27.932 [planetinfo.record]: planet zpos = 826410.000000 13:10:27.933 [planetinfo.record]: sun_radius = 121051.302338 13:10:27.933 [planetinfo.record]: sun_vector = -0.222 0.862 -0.455 13:10:27.933 [planetinfo.record]: sun_distance = 1080690 13:10:27.933 [planetinfo.record]: corona_flare = 0.004919 13:10:27.933 [planetinfo.record]: corona_hues = 0.622787 13:10:27.933 [planetinfo.record]: sun_color = 0.600503, 0.605007, 0.752283, 1 13:10:27.933 [planetinfo.record]: corona_shimmer = 0.449246 13:10:27.933 [planetinfo.record]: station_vector = 0.756 0.034 0.653 13:10:27.933 [planetinfo.record]: station = coriolis 13:10:33.020 [PLANETINFO OVER]: Done 13:10:33.020 [PLANETINFO LOGGING]: 4 10 13:10:34.026 [planetinfo.record]: seed = 160 100 101 123 13:10:34.026 [planetinfo.record]: coordinates = 100 29 13:10:34.026 [planetinfo.record]: government = 4; 13:10:34.026 [planetinfo.record]: economy = 5; 13:10:34.026 [planetinfo.record]: techlevel = 4; 13:10:34.026 [planetinfo.record]: population = 26; 13:10:34.026 [planetinfo.record]: productivity = 8320; 13:10:34.026 [planetinfo.record]: name = "Anisis"; 13:10:34.026 [planetinfo.record]: inhabitant = "Human Colonial"; 13:10:34.026 [planetinfo.record]: inhabitants = "Human Colonials"; 13:10:34.026 [planetinfo.record]: description = "This world is very notable for its inhabitants’ unusual silliness and its exotic cuisine."; 13:10:34.058 [planetinfo.record]: air_color = 0.456143, 0.572258, 0.903393, 1; 13:10:34.059 [planetinfo.record]: cloud_alpha = 1.000000; 13:10:34.059 [planetinfo.record]: cloud_color = 0.0807571, 0.564734, 0.712891, 1; 13:10:34.059 [planetinfo.record]: cloud_fraction = 0.490000; 13:10:34.059 [planetinfo.record]: land_color = 0.66, 0.655005, 0.340313, 1; 13:10:34.059 [planetinfo.record]: land_fraction = 0.550000; 13:10:34.059 [planetinfo.record]: polar_cloud_color = 0.365914, 0.714187, 0.820801, 1; 13:10:34.059 [planetinfo.record]: polar_land_color = 0.934, 0.932233, 0.820898, 1; 13:10:34.059 [planetinfo.record]: polar_sea_color = 0.937305, 0.89479, 0.87616, 1; 13:10:34.059 [planetinfo.record]: sea_color = 0.626953, 0.513203, 0.463358, 1; 13:10:34.059 [planetinfo.record]: rotation_speed = 0.003157 13:10:34.059 [planetinfo.record]: planet zpos = 745160.000000 13:10:34.059 [planetinfo.record]: sun_radius = 159701.132812 13:10:34.059 [planetinfo.record]: sun_vector = -0.334 -0.694 -0.638 13:10:34.059 [planetinfo.record]: sun_distance = 1146400 13:10:34.059 [planetinfo.record]: corona_flare = 0.079971 13:10:34.059 [planetinfo.record]: corona_hues = 0.503883 13:10:34.059 [planetinfo.record]: sun_color = 0.484835, 0.728976, 0.734973, 1 13:10:34.059 [planetinfo.record]: corona_shimmer = 0.324436 13:10:34.060 [planetinfo.record]: station_vector = -0.919 -0.373 0.128 13:10:34.060 [planetinfo.record]: station = coriolis 13:10:39.492 [PLANETINFO OVER]: Done 13:10:39.492 [PLANETINFO LOGGING]: 4 11 13:10:40.499 [planetinfo.record]: seed = 172 85 243 47 13:10:40.499 [planetinfo.record]: coordinates = 85 221 13:10:40.499 [planetinfo.record]: government = 5; 13:10:40.499 [planetinfo.record]: economy = 5; 13:10:40.499 [planetinfo.record]: techlevel = 6; 13:10:40.499 [planetinfo.record]: population = 35; 13:10:40.499 [planetinfo.record]: productivity = 12600; 13:10:40.499 [planetinfo.record]: name = "Axeus"; 13:10:40.499 [planetinfo.record]: inhabitant = "Red Slimy Lobster"; 13:10:40.500 [planetinfo.record]: inhabitants = "Red Slimy Lobsters"; 13:10:40.500 [planetinfo.record]: description = "The world Axeus is a boring world."; 13:10:40.509 [planetinfo.record]: air_color = 0.558224, 0.56556, 0.846158, 1; 13:10:40.509 [planetinfo.record]: cloud_alpha = 1.000000; 13:10:40.509 [planetinfo.record]: cloud_color = 0.312775, 0.32704, 0.541016, 1; 13:10:40.509 [planetinfo.record]: cloud_fraction = 0.110000; 13:10:40.509 [planetinfo.record]: land_color = 0.207336, 0.453887, 0.617188, 1; 13:10:40.510 [planetinfo.record]: land_fraction = 0.610000; 13:10:40.510 [planetinfo.record]: polar_cloud_color = 0.547428, 0.55968, 0.743457, 1; 13:10:40.510 [planetinfo.record]: polar_land_color = 0.782512, 0.876217, 0.938281, 1; 13:10:40.510 [planetinfo.record]: polar_sea_color = 0.925839, 0.929883, 0.878122, 1; 13:10:40.510 [planetinfo.record]: sea_color = 0.688975, 0.701172, 0.545052, 1; 13:10:40.510 [planetinfo.record]: rotation_speed = 0.000230 13:10:40.510 [planetinfo.record]: planet zpos = 876330.000000 13:10:40.510 [planetinfo.record]: sun_radius = 155254.067688 13:10:40.510 [planetinfo.record]: sun_vector = 0.995 0.017 0.101 13:10:40.510 [planetinfo.record]: sun_distance = 1213380 13:10:40.511 [planetinfo.record]: corona_flare = 0.082964 13:10:40.511 [planetinfo.record]: corona_hues = 0.666786 13:10:40.511 [planetinfo.record]: sun_color = 0.463135, 0.668645, 0.77533, 1 13:10:40.511 [planetinfo.record]: corona_shimmer = 0.900838 13:10:40.511 [planetinfo.record]: station_vector = -0.279 0.671 0.687 13:10:40.511 [planetinfo.record]: station = coriolis 13:10:45.142 [PLANETINFO OVER]: Done 13:10:45.143 [PLANETINFO LOGGING]: 4 12 13:10:46.147 [planetinfo.record]: seed = 28 123 69 198 13:10:46.147 [planetinfo.record]: coordinates = 123 232 13:10:46.147 [planetinfo.record]: government = 3; 13:10:46.147 [planetinfo.record]: economy = 0; 13:10:46.147 [planetinfo.record]: techlevel = 12; 13:10:46.147 [planetinfo.record]: population = 52; 13:10:46.147 [planetinfo.record]: productivity = 29120; 13:10:46.147 [planetinfo.record]: name = "Biesmaan"; 13:10:46.147 [planetinfo.record]: inhabitant = "Human Colonial"; 13:10:46.147 [planetinfo.record]: inhabitants = "Human Colonials"; 13:10:46.147 [planetinfo.record]: description = "The planet Biesmaan is famous for its exotic goat soup but cursed by killer disease."; 13:10:46.152 [planetinfo.record]: air_color = 0.704089, 0.781472, 0.944367, 1; 13:10:46.153 [planetinfo.record]: cloud_alpha = 1.000000; 13:10:46.153 [planetinfo.record]: cloud_color = 0.698792, 0.829509, 0.835938, 1; 13:10:46.153 [planetinfo.record]: cloud_fraction = 0.230000; 13:10:46.153 [planetinfo.record]: land_color = 0.0747375, 0.617188, 0.146782, 1; 13:10:46.153 [planetinfo.record]: land_fraction = 0.410000; 13:10:46.153 [planetinfo.record]: polar_cloud_color = 0.78633, 0.871961, 0.876172, 1; 13:10:46.153 [planetinfo.record]: polar_land_color = 0.732116, 0.938281, 0.759497, 1; 13:10:46.153 [planetinfo.record]: polar_sea_color = 0.926953, 0.901147, 0.846388, 1; 13:10:46.153 [planetinfo.record]: sea_color = 0.730469, 0.649125, 0.476517, 1; 13:10:46.153 [planetinfo.record]: rotation_speed = 0.004289 13:10:46.153 [planetinfo.record]: planet zpos = 492250.000000 13:10:46.153 [planetinfo.record]: sun_radius = 127838.218689 13:10:46.153 [planetinfo.record]: sun_vector = 0.005 0.999 0.038 13:10:46.153 [planetinfo.record]: sun_distance = 716000 13:10:46.154 [planetinfo.record]: corona_flare = 0.025887 13:10:46.154 [planetinfo.record]: corona_hues = 0.616287 13:10:46.154 [planetinfo.record]: sun_color = 0.738425, 0.745611, 0.811719, 1 13:10:46.154 [planetinfo.record]: corona_shimmer = 0.268397 13:10:46.154 [planetinfo.record]: station_vector = 0.803 0.593 0.055 13:10:46.154 [planetinfo.record]: station = dodecahedron 13:10:51.576 [PLANETINFO OVER]: Done 13:10:51.578 [PLANETINFO LOGGING]: 4 13 13:10:52.579 [planetinfo.record]: seed = 48 91 27 240 13:10:52.579 [planetinfo.record]: coordinates = 91 107 13:10:52.579 [planetinfo.record]: government = 6; 13:10:52.579 [planetinfo.record]: economy = 3; 13:10:52.579 [planetinfo.record]: techlevel = 10; 13:10:52.579 [planetinfo.record]: population = 50; 13:10:52.579 [planetinfo.record]: productivity = 28000; 13:10:52.580 [planetinfo.record]: name = "Ervele"; 13:10:52.580 [planetinfo.record]: inhabitant = "Human Colonial"; 13:10:52.580 [planetinfo.record]: inhabitants = "Human Colonials"; 13:10:52.580 [planetinfo.record]: description = "The planet Ervele is famous for its inhabitants’ ancient loathing of casinos but ravaged by unpredictable civil war."; 13:10:52.590 [planetinfo.record]: air_color = 0.620335, 0.559618, 0.874125, 1; 13:10:52.591 [planetinfo.record]: cloud_alpha = 1.000000; 13:10:52.591 [planetinfo.record]: cloud_color = 0.503006, 0.324707, 0.625, 1; 13:10:52.591 [planetinfo.record]: cloud_fraction = 0.410000; 13:10:52.591 [planetinfo.record]: land_color = 0.66, 0.281016, 0.618549, 1; 13:10:52.591 [planetinfo.record]: land_fraction = 0.510000; 13:10:52.591 [planetinfo.record]: polar_cloud_color = 0.685942, 0.546646, 0.78125, 1; 13:10:52.591 [planetinfo.record]: polar_land_color = 0.934, 0.79992, 0.919335, 1; 13:10:52.591 [planetinfo.record]: polar_sea_color = 0.928516, 0.829679, 0.829679, 1; 13:10:52.591 [planetinfo.record]: sea_color = 0.714844, 0.410477, 0.410477, 1; 13:10:52.591 [planetinfo.record]: rotation_speed = 0.000162 13:10:52.591 [planetinfo.record]: planet zpos = 319770.000000 13:10:52.591 [planetinfo.record]: sun_radius = 73220.151215 13:10:52.591 [planetinfo.record]: sun_vector = -0.186 0.698 0.692 13:10:52.591 [planetinfo.record]: sun_distance = 668610 13:10:52.591 [planetinfo.record]: corona_flare = 0.014291 13:10:52.591 [planetinfo.record]: corona_hues = 0.899330 13:10:52.591 [planetinfo.record]: sun_color = 0.546478, 0.559739, 0.771707, 1 13:10:52.592 [planetinfo.record]: corona_shimmer = 0.350677 13:10:52.592 [planetinfo.record]: station_vector = -0.695 -0.338 0.635 13:10:52.592 [planetinfo.record]: station = coriolis 13:10:57.577 [PLANETINFO OVER]: Done 13:10:57.577 [PLANETINFO LOGGING]: 4 14 13:10:58.579 [planetinfo.record]: seed = 72 168 117 96 13:10:58.580 [planetinfo.record]: coordinates = 168 1 13:10:58.580 [planetinfo.record]: government = 1; 13:10:58.580 [planetinfo.record]: economy = 3; 13:10:58.580 [planetinfo.record]: techlevel = 5; 13:10:58.580 [planetinfo.record]: population = 25; 13:10:58.580 [planetinfo.record]: productivity = 7000; 13:10:58.580 [planetinfo.record]: name = "Arbe"; 13:10:58.580 [planetinfo.record]: inhabitant = "Human Colonial"; 13:10:58.580 [planetinfo.record]: inhabitants = "Human Colonials"; 13:10:58.580 [planetinfo.record]: description = "Arbe is reasonably well known for its great dense forests but scourged by deadly edible moths."; 13:10:58.598 [planetinfo.record]: air_color = 0.860902, 0.781473, 0.978188, 1; 13:10:58.598 [planetinfo.record]: cloud_alpha = 1.000000; 13:10:58.598 [planetinfo.record]: cloud_color = 0.9375, 0.933838, 0.93667, 1; 13:10:58.598 [planetinfo.record]: cloud_fraction = 0.160000; 13:10:58.598 [planetinfo.record]: land_color = 0.0876563, 0.217328, 0.66, 1; 13:10:58.599 [planetinfo.record]: land_fraction = 0.330000; 13:10:58.599 [planetinfo.record]: polar_cloud_color = 0.921875, 0.919624, 0.921365, 1; 13:10:58.599 [planetinfo.record]: polar_land_color = 0.731512, 0.777388, 0.934, 1; 13:10:58.599 [planetinfo.record]: polar_sea_color = 0.939258, 0.883979, 0.878261, 1; 13:10:58.599 [planetinfo.record]: sea_color = 0.607422, 0.464427, 0.449635, 1; 13:10:58.599 [planetinfo.record]: rotation_speed = 0.001593 13:10:58.599 [planetinfo.record]: planet zpos = 358080.000000 13:10:58.599 [planetinfo.record]: sun_radius = 85229.042969 13:10:58.600 [planetinfo.record]: sun_vector = -0.361 -0.914 0.186 13:10:58.600 [planetinfo.record]: sun_distance = 566960 13:10:58.600 [planetinfo.record]: corona_flare = 0.060927 13:10:58.600 [planetinfo.record]: corona_hues = 0.948967 13:10:58.600 [planetinfo.record]: sun_color = 0.230129, 0.49301, 0.72095, 1 13:10:58.600 [planetinfo.record]: corona_shimmer = 0.336507 13:10:58.600 [planetinfo.record]: station_vector = 0.712 0.029 0.702 13:10:58.600 [planetinfo.record]: station = coriolis 13:11:03.611 [PLANETINFO OVER]: Done 13:11:03.611 [PLANETINFO LOGGING]: 4 15 13:11:04.614 [planetinfo.record]: seed = 36 126 211 155 13:11:04.615 [planetinfo.record]: coordinates = 126 19 13:11:04.615 [planetinfo.record]: government = 4; 13:11:04.615 [planetinfo.record]: economy = 3; 13:11:04.615 [planetinfo.record]: techlevel = 8; 13:11:04.615 [planetinfo.record]: population = 40; 13:11:04.615 [planetinfo.record]: productivity = 17920; 13:11:04.615 [planetinfo.record]: name = "Andiso"; 13:11:04.615 [planetinfo.record]: inhabitant = "Black Furry Rodent"; 13:11:04.615 [planetinfo.record]: inhabitants = "Black Furry Rodents"; 13:11:04.615 [planetinfo.record]: description = "Andiso is cursed by killer mountain goats."; 13:11:04.626 [planetinfo.record]: air_color = 0.446497, 0.560206, 0.912498, 1; 13:11:04.626 [planetinfo.record]: cloud_alpha = 1.000000; 13:11:04.626 [planetinfo.record]: cloud_color = 0.0491562, 0.545869, 0.740234, 1; 13:11:04.626 [planetinfo.record]: cloud_fraction = 0.230000; 13:11:04.628 [planetinfo.record]: land_color = 0.66, 0.301641, 0.494819, 1; 13:11:04.628 [planetinfo.record]: land_fraction = 0.500000; 13:11:04.628 [planetinfo.record]: polar_cloud_color = 0.346992, 0.696386, 0.833105, 1; 13:11:04.628 [planetinfo.record]: polar_land_color = 0.934, 0.807217, 0.875561, 1; 13:11:04.628 [planetinfo.record]: polar_sea_color = 0.926758, 0.822466, 0.819963, 1; 13:11:04.628 [planetinfo.record]: sea_color = 0.732422, 0.402734, 0.394821, 1; 13:11:04.628 [planetinfo.record]: rotation_speed = 0.001223 13:11:04.628 [planetinfo.record]: planet zpos = 690960.000000 13:11:04.628 [planetinfo.record]: sun_radius = 113987.067566 13:11:04.628 [planetinfo.record]: sun_vector = -0.461 0.876 -0.142 13:11:04.628 [planetinfo.record]: sun_distance = 1094020 13:11:04.628 [planetinfo.record]: corona_flare = 0.065134 13:11:04.628 [planetinfo.record]: corona_hues = 0.835777 13:11:04.628 [planetinfo.record]: sun_color = 0.721193, 0.806628, 0.847638, 1 13:11:04.629 [planetinfo.record]: corona_shimmer = 0.673823 13:11:04.629 [planetinfo.record]: station_vector = 0.576 0.763 0.294 13:11:04.629 [planetinfo.record]: station = coriolis 13:11:09.416 [PLANETINFO OVER]: Done 13:11:09.417 [PLANETINFO LOGGING]: 4 16 13:11:10.420 [planetinfo.record]: seed = 36 16 117 132 13:11:10.421 [planetinfo.record]: coordinates = 16 71 13:11:10.421 [planetinfo.record]: government = 4; 13:11:10.421 [planetinfo.record]: economy = 7; 13:11:10.421 [planetinfo.record]: techlevel = 2; 13:11:10.421 [planetinfo.record]: population = 20; 13:11:10.421 [planetinfo.record]: productivity = 3840; 13:11:10.421 [planetinfo.record]: name = "Zaaner"; 13:11:10.421 [planetinfo.record]: inhabitant = "Human Colonial"; 13:11:10.421 [planetinfo.record]: inhabitants = "Human Colonials"; 13:11:10.421 [planetinfo.record]: description = "The world Zaaner is mildly noted for its pink Zaanerian Alusabus plant plantations but beset by frequent civil war."; 13:11:10.432 [planetinfo.record]: air_color = 0.667532, 0.881375, 0.884531, 1; 13:11:10.437 [planetinfo.record]: cloud_alpha = 1.000000; 13:11:10.438 [planetinfo.record]: cloud_color = 0.651343, 0.65625, 0.566528, 1; 13:11:10.438 [planetinfo.record]: cloud_fraction = 0.290000; 13:11:10.438 [planetinfo.record]: land_color = 0.525937, 0.66, 0.391875, 1; 13:11:10.438 [planetinfo.record]: land_fraction = 0.280000; 13:11:10.438 [planetinfo.record]: polar_cloud_color = 0.791596, 0.795313, 0.727354, 1; 13:11:10.438 [planetinfo.record]: polar_land_color = 0.88657, 0.934, 0.839141, 1; 13:11:10.438 [planetinfo.record]: polar_sea_color = 0.949414, 0.899718, 0.905542, 1; 13:11:10.438 [planetinfo.record]: sea_color = 0.505859, 0.399945, 0.412357, 1; 13:11:10.438 [planetinfo.record]: rotation_speed = 0.000180 13:11:10.438 [planetinfo.record]: planet zpos = 462720.000000 13:11:10.438 [planetinfo.record]: sun_radius = 103747.675781 13:11:10.438 [planetinfo.record]: sun_vector = 0.016 0.896 -0.444 13:11:10.438 [planetinfo.record]: sun_distance = 694080 13:11:10.438 [planetinfo.record]: corona_flare = 0.059259 13:11:10.438 [planetinfo.record]: corona_hues = 0.860184 13:11:10.438 [planetinfo.record]: sun_color = 0.580385, 0.695007, 0.804495, 1 13:11:10.439 [planetinfo.record]: corona_shimmer = 0.449591 13:11:10.439 [planetinfo.record]: station_vector = 0.892 -0.031 0.452 13:11:10.439 [planetinfo.record]: station = coriolis 13:11:16.095 [PLANETINFO OVER]: Done 13:11:16.097 [PLANETINFO LOGGING]: 4 17 13:11:17.113 [planetinfo.record]: seed = 136 208 155 28 13:11:17.114 [planetinfo.record]: coordinates = 208 112 13:11:17.114 [planetinfo.record]: government = 1; 13:11:17.114 [planetinfo.record]: economy = 2; 13:11:17.114 [planetinfo.record]: techlevel = 6; 13:11:17.114 [planetinfo.record]: population = 28; 13:11:17.114 [planetinfo.record]: productivity = 8960; 13:11:17.114 [planetinfo.record]: name = "Teisarza"; 13:11:17.114 [planetinfo.record]: inhabitant = "Green Slimy Rodent"; 13:11:17.114 [planetinfo.record]: inhabitants = "Green Slimy Rodents"; 13:11:17.114 [planetinfo.record]: description = "This world is reasonably well known for its great volcanoes but ravaged by vicious disease."; 13:11:17.131 [planetinfo.record]: air_color = 0.748113, 0.754688, 0.945668, 1; 13:11:17.133 [planetinfo.record]: cloud_alpha = 1.000000; 13:11:17.133 [planetinfo.record]: cloud_color = 0.816879, 0.818853, 0.839844, 1; 13:11:17.133 [planetinfo.record]: cloud_fraction = 0.440000; 13:11:17.133 [planetinfo.record]: land_color = 0.66, 0.563925, 0.427969, 1; 13:11:17.133 [planetinfo.record]: land_fraction = 0.680000; 13:11:17.133 [planetinfo.record]: polar_cloud_color = 0.862926, 0.864215, 0.87793, 1; 13:11:17.133 [planetinfo.record]: polar_land_color = 0.934, 0.90001, 0.85191, 1; 13:11:17.133 [planetinfo.record]: polar_sea_color = 0.935895, 0.888245, 0.947461, 1; 13:11:17.133 [planetinfo.record]: sea_color = 0.499737, 0.394043, 0.525391, 1; 13:11:17.133 [planetinfo.record]: rotation_speed = 0.003521 13:11:17.133 [planetinfo.record]: planet zpos = 731520.000000 13:11:17.133 [planetinfo.record]: sun_radius = 152762.768555 13:11:17.133 [planetinfo.record]: sun_vector = 0.158 0.353 0.922 13:11:17.133 [planetinfo.record]: sun_distance = 1341120 13:11:17.133 [planetinfo.record]: corona_flare = 0.020883 13:11:17.133 [planetinfo.record]: corona_hues = 0.999939 13:11:17.133 [planetinfo.record]: sun_color = 0.660037, 0.712656, 0.724301, 1 13:11:17.133 [planetinfo.record]: corona_shimmer = 0.439768 13:11:17.133 [planetinfo.record]: station_vector = -0.004 -0.729 0.684 13:11:17.134 [planetinfo.record]: station = coriolis 13:11:22.046 [PLANETINFO OVER]: Done 13:11:22.049 [PLANETINFO LOGGING]: 4 18 13:11:23.051 [planetinfo.record]: seed = 176 196 197 108 13:11:23.051 [planetinfo.record]: coordinates = 196 74 13:11:23.051 [planetinfo.record]: government = 6; 13:11:23.052 [planetinfo.record]: economy = 2; 13:11:23.052 [planetinfo.record]: techlevel = 8; 13:11:23.052 [planetinfo.record]: population = 41; 13:11:23.052 [planetinfo.record]: productivity = 26240; 13:11:23.052 [planetinfo.record]: name = "Intedi"; 13:11:23.052 [planetinfo.record]: inhabitant = "Blue Feline"; 13:11:23.052 [planetinfo.record]: inhabitants = "Blue Felines"; 13:11:23.052 [planetinfo.record]: description = "Intedi is an unremarkable planet."; 13:11:23.069 [planetinfo.record]: air_color = 0.563107, 0.572333, 0.840305, 1; 13:11:23.069 [planetinfo.record]: cloud_alpha = 1.000000; 13:11:23.069 [planetinfo.record]: cloud_color = 0.31897, 0.336541, 0.523438, 1; 13:11:23.069 [planetinfo.record]: cloud_fraction = 0.310000; 13:11:23.069 [planetinfo.record]: land_color = 0.316025, 0.542969, 0.0890808, 1; 13:11:23.070 [planetinfo.record]: land_fraction = 0.250000; 13:11:23.072 [planetinfo.record]: polar_cloud_color = 0.55597, 0.571402, 0.735547, 1; 13:11:23.072 [planetinfo.record]: polar_land_color = 0.846885, 0.945703, 0.748066, 1; 13:11:23.072 [planetinfo.record]: polar_sea_color = 0.90293, 0.856725, 0.877301, 1; 13:11:23.072 [planetinfo.record]: sea_color = 0.970703, 0.772012, 0.860492, 1; 13:11:23.072 [planetinfo.record]: rotation_speed = 0.000040 13:11:23.072 [planetinfo.record]: planet zpos = 669240.000000 13:11:23.072 [planetinfo.record]: sun_radius = 132235.279541 13:11:23.072 [planetinfo.record]: sun_vector = -0.791 -0.427 -0.438 13:11:23.072 [planetinfo.record]: sun_distance = 1095120 13:11:23.072 [planetinfo.record]: corona_flare = 0.037889 13:11:23.072 [planetinfo.record]: corona_hues = 0.715874 13:11:23.073 [planetinfo.record]: sun_color = 0.67305, 0.660749, 0.455402, 1 13:11:23.073 [planetinfo.record]: corona_shimmer = 0.431954 13:11:23.073 [planetinfo.record]: station_vector = 0.413 0.863 0.290 13:11:23.073 [planetinfo.record]: station = coriolis 13:11:27.922 [PLANETINFO OVER]: Done 13:11:27.923 [PLANETINFO LOGGING]: 4 19 13:11:28.927 [planetinfo.record]: seed = 92 150 243 191 13:11:28.927 [planetinfo.record]: coordinates = 150 173 13:11:28.927 [planetinfo.record]: government = 3; 13:11:28.927 [planetinfo.record]: economy = 5; 13:11:28.927 [planetinfo.record]: techlevel = 6; 13:11:28.927 [planetinfo.record]: population = 33; 13:11:28.928 [planetinfo.record]: productivity = 9240; 13:11:28.928 [planetinfo.record]: name = "Ongequ"; 13:11:28.928 [planetinfo.record]: inhabitant = "Harmless Bony Feline"; 13:11:28.928 [planetinfo.record]: inhabitants = "Harmless Bony Felines"; 13:11:28.928 [planetinfo.record]: description = "The planet Ongequ is a boring planet."; 13:11:28.940 [planetinfo.record]: air_color = 0.641038, 0.592899, 0.842256, 1; 13:11:28.940 [planetinfo.record]: cloud_alpha = 1.000000; 13:11:28.940 [planetinfo.record]: cloud_color = 0.467141, 0.376297, 0.529297, 1; 13:11:28.940 [planetinfo.record]: cloud_fraction = 0.380000; 13:11:28.940 [planetinfo.record]: land_color = 0.187035, 0.146953, 0.66, 1; 13:11:28.940 [planetinfo.record]: land_fraction = 0.620000; 13:11:28.940 [planetinfo.record]: polar_cloud_color = 0.684005, 0.60482, 0.738184, 1; 13:11:28.940 [planetinfo.record]: polar_land_color = 0.766671, 0.75249, 0.934, 1; 13:11:28.940 [planetinfo.record]: polar_sea_color = 0.900127, 0.929883, 0.873037, 1; 13:11:28.940 [planetinfo.record]: sea_color = 0.611424, 0.701172, 0.529713, 1; 13:11:28.940 [planetinfo.record]: rotation_speed = 0.002180 13:11:28.940 [planetinfo.record]: planet zpos = 816720.000000 13:11:28.940 [planetinfo.record]: sun_radius = 134042.973633 13:11:28.940 [planetinfo.record]: sun_vector = 0.281 -0.936 0.214 13:11:28.940 [planetinfo.record]: sun_distance = 1497320 13:11:28.941 [planetinfo.record]: corona_flare = 0.011227 13:11:28.941 [planetinfo.record]: corona_hues = 0.627892 13:11:28.941 [planetinfo.record]: sun_color = 0.702408, 0.620634, 0.484309, 1 13:11:28.941 [planetinfo.record]: corona_shimmer = 0.404398 13:11:28.947 [planetinfo.record]: station_vector = 0.692 0.718 0.076 13:11:28.947 [planetinfo.record]: station = coriolis 13:11:34.161 [PLANETINFO OVER]: Done 13:11:34.161 [PLANETINFO LOGGING]: 4 20 13:11:35.169 [planetinfo.record]: seed = 236 29 229 123 13:11:35.169 [planetinfo.record]: coordinates = 29 90 13:11:35.169 [planetinfo.record]: government = 5; 13:11:35.169 [planetinfo.record]: economy = 2; 13:11:35.169 [planetinfo.record]: techlevel = 9; 13:11:35.169 [planetinfo.record]: population = 44; 13:11:35.169 [planetinfo.record]: productivity = 25344; 13:11:35.169 [planetinfo.record]: name = "Anbedi"; 13:11:35.170 [planetinfo.record]: inhabitant = "Blue Lizard"; 13:11:35.170 [planetinfo.record]: inhabitants = "Blue Lizards"; 13:11:35.170 [planetinfo.record]: description = "This planet is mildly fabled for the Anbediian mountain poet but scourged by deadly civil war."; 13:11:35.186 [planetinfo.record]: air_color = 0.613711, 0.568783, 0.860467, 1; 13:11:35.187 [planetinfo.record]: cloud_alpha = 1.000000; 13:11:35.187 [planetinfo.record]: cloud_color = 0.45622, 0.339897, 0.583984, 1; 13:11:35.187 [planetinfo.record]: cloud_fraction = 0.350000; 13:11:35.187 [planetinfo.record]: land_color = 0.66, 0.565254, 0.281016, 1; 13:11:35.187 [planetinfo.record]: land_fraction = 0.330000; 13:11:35.187 [planetinfo.record]: polar_cloud_color = 0.65849, 0.563528, 0.762793, 1; 13:11:35.187 [planetinfo.record]: polar_land_color = 0.934, 0.90048, 0.79992, 1; 13:11:35.187 [planetinfo.record]: polar_sea_color = 0.903056, 0.93457, 0.885378, 1; 13:11:35.187 [planetinfo.record]: sea_color = 0.566044, 0.654297, 0.516537, 1; 13:11:35.187 [planetinfo.record]: rotation_speed = 0.001115 13:11:35.187 [planetinfo.record]: planet zpos = 509490.000000 13:11:35.187 [planetinfo.record]: sun_radius = 150490.380707 13:11:35.187 [planetinfo.record]: sun_vector = 0.096 -0.502 -0.859 13:11:35.187 [planetinfo.record]: sun_distance = 1132200 13:11:35.187 [planetinfo.record]: corona_flare = 0.094736 13:11:35.187 [planetinfo.record]: corona_hues = 0.880722 13:11:35.187 [planetinfo.record]: sun_color = 0.667888, 0.696425, 0.697577, 1 13:11:35.188 [planetinfo.record]: corona_shimmer = 0.575807 13:11:35.188 [planetinfo.record]: station_vector = -0.691 0.603 0.397 13:11:35.188 [planetinfo.record]: station = coriolis 13:11:40.821 [PLANETINFO OVER]: Done 13:11:40.823 [PLANETINFO LOGGING]: 4 21 13:11:41.825 [planetinfo.record]: seed = 160 253 91 127 13:11:41.825 [planetinfo.record]: coordinates = 253 141 13:11:41.825 [planetinfo.record]: government = 4; 13:11:41.825 [planetinfo.record]: economy = 5; 13:11:41.825 [planetinfo.record]: techlevel = 5; 13:11:41.825 [planetinfo.record]: population = 30; 13:11:41.825 [planetinfo.record]: productivity = 9600; 13:11:41.825 [planetinfo.record]: name = "Onarsoat"; 13:11:41.825 [planetinfo.record]: inhabitant = "Human Colonial"; 13:11:41.825 [planetinfo.record]: inhabitants = "Human Colonials"; 13:11:41.825 [planetinfo.record]: description = "The world Onarsoat is scourged by evil disease."; 13:11:41.841 [planetinfo.record]: air_color = 0.427219, 0.742975, 0.875426, 1; 13:11:41.841 [planetinfo.record]: cloud_alpha = 1.000000; 13:11:41.841 [planetinfo.record]: cloud_color = 0.0991879, 0.628906, 0.0393066, 1; 13:11:41.841 [planetinfo.record]: cloud_fraction = 0.490000; 13:11:41.841 [planetinfo.record]: land_color = 0.655247, 0.66, 0.507891, 1; 13:11:41.841 [planetinfo.record]: land_fraction = 0.520000; 13:11:41.841 [planetinfo.record]: polar_cloud_color = 0.37081, 0.783008, 0.324214, 1; 13:11:41.842 [planetinfo.record]: polar_land_color = 0.932318, 0.934, 0.880186, 1; 13:11:41.842 [planetinfo.record]: polar_sea_color = 0.866172, 0.873868, 0.91543, 1; 13:11:41.842 [planetinfo.record]: sea_color = 0.663679, 0.69212, 0.845703, 1; 13:11:41.842 [planetinfo.record]: rotation_speed = 0.002010 13:11:41.842 [planetinfo.record]: planet zpos = 829080.000000 13:11:41.842 [planetinfo.record]: sun_radius = 177043.125000 13:11:41.842 [planetinfo.record]: sun_vector = 0.335 -0.619 -0.710 13:11:41.842 [planetinfo.record]: sun_distance = 1312710 13:11:41.842 [planetinfo.record]: corona_flare = 0.050232 13:11:41.842 [planetinfo.record]: corona_hues = 0.599068 13:11:41.842 [planetinfo.record]: sun_color = 0.71889, 0.576606, 0.479549, 1 13:11:41.842 [planetinfo.record]: corona_shimmer = 0.431891 13:11:41.842 [planetinfo.record]: station_vector = 0.473 0.235 0.849 13:11:41.843 [planetinfo.record]: station = coriolis 13:11:47.198 [PLANETINFO OVER]: Done 13:11:47.199 [PLANETINFO LOGGING]: 4 22 13:11:48.219 [planetinfo.record]: seed = 216 17 85 164 13:11:48.219 [planetinfo.record]: coordinates = 17 135 13:11:48.220 [planetinfo.record]: government = 3; 13:11:48.220 [planetinfo.record]: economy = 7; 13:11:48.220 [planetinfo.record]: techlevel = 3; 13:11:48.220 [planetinfo.record]: population = 23; 13:11:48.220 [planetinfo.record]: productivity = 3864; 13:11:48.220 [planetinfo.record]: name = "Zaisra"; 13:11:48.220 [planetinfo.record]: inhabitant = "Human Colonial"; 13:11:48.220 [planetinfo.record]: inhabitants = "Human Colonials"; 13:11:48.220 [planetinfo.record]: description = "This planet is most notable for Zaisraian vicious water but plagued by occasional solar activity."; 13:11:48.239 [planetinfo.record]: air_color = 0.550272, 0.593194, 0.835752, 1; 13:11:48.239 [planetinfo.record]: cloud_alpha = 1.000000; 13:11:48.239 [planetinfo.record]: cloud_color = 0.292717, 0.387676, 0.509766, 1; 13:11:48.239 [planetinfo.record]: cloud_fraction = 0.050000; 13:11:48.239 [planetinfo.record]: land_color = 0.433125, 0.66, 0.617461, 1; 13:11:48.239 [planetinfo.record]: land_fraction = 0.620000; 13:11:48.239 [planetinfo.record]: polar_cloud_color = 0.535293, 0.620212, 0.729395, 1; 13:11:48.239 [planetinfo.record]: polar_land_color = 0.853734, 0.934, 0.91895, 1; 13:11:48.239 [planetinfo.record]: polar_sea_color = 0.931445, 0.913893, 0.87232, 1; 13:11:48.239 [planetinfo.record]: sea_color = 0.685547, 0.633871, 0.511482, 1; 13:11:48.240 [planetinfo.record]: rotation_speed = 0.003475 13:11:48.240 [planetinfo.record]: planet zpos = 501410.000000 13:11:48.240 [planetinfo.record]: sun_radius = 109513.940430 13:11:48.240 [planetinfo.record]: sun_vector = 0.159 0.985 0.060 13:11:48.240 [planetinfo.record]: sun_distance = 848540 13:11:48.240 [planetinfo.record]: corona_flare = 0.031242 13:11:48.240 [planetinfo.record]: corona_hues = 0.785568 13:11:48.240 [planetinfo.record]: sun_color = 0.698624, 0.635018, 0.267375, 1 13:11:48.240 [planetinfo.record]: corona_shimmer = 0.291124 13:11:48.240 [planetinfo.record]: station_vector = -0.696 0.718 0.019 13:11:48.240 [planetinfo.record]: station = coriolis 13:11:53.599 [PLANETINFO OVER]: Done 13:11:53.600 [PLANETINFO LOGGING]: 4 23 13:11:54.604 [planetinfo.record]: seed = 84 214 83 8 13:11:54.604 [planetinfo.record]: coordinates = 214 244 13:11:54.604 [planetinfo.record]: government = 2; 13:11:54.604 [planetinfo.record]: economy = 4; 13:11:54.604 [planetinfo.record]: techlevel = 6; 13:11:54.604 [planetinfo.record]: population = 31; 13:11:54.604 [planetinfo.record]: productivity = 8928; 13:11:54.604 [planetinfo.record]: name = "Usenat"; 13:11:54.604 [planetinfo.record]: inhabitant = "Human Colonial"; 13:11:54.604 [planetinfo.record]: inhabitants = "Human Colonials"; 13:11:54.604 [planetinfo.record]: description = "This world is most notable for its fabulous cuisine but beset by occasional civil war."; 13:11:54.618 [planetinfo.record]: air_color = 0.488472, 0.572473, 0.883231, 1; 13:11:54.618 [planetinfo.record]: cloud_alpha = 1.000000; 13:11:54.618 [planetinfo.record]: cloud_color = 0.168182, 0.470783, 0.652344, 1; 13:11:54.618 [planetinfo.record]: cloud_fraction = 0.470000; 13:11:54.618 [planetinfo.record]: land_color = 0.66, 0.270703, 0.444062, 1; 13:11:54.618 [planetinfo.record]: land_fraction = 0.540000; 13:11:54.618 [planetinfo.record]: polar_cloud_color = 0.425451, 0.655516, 0.793555, 1; 13:11:54.618 [planetinfo.record]: polar_land_color = 0.934, 0.796272, 0.857604, 1; 13:11:54.618 [planetinfo.record]: polar_sea_color = 0.90133, 0.935156, 0.887577, 1; 13:11:54.618 [planetinfo.record]: sea_color = 0.554617, 0.648438, 0.51647, 1; 13:11:54.618 [planetinfo.record]: rotation_speed = 0.003043 13:11:54.618 [planetinfo.record]: planet zpos = 558580.000000 13:11:54.619 [planetinfo.record]: sun_radius = 169908.751831 13:11:54.619 [planetinfo.record]: sun_vector = 0.969 -0.177 -0.173 13:11:54.619 [planetinfo.record]: sun_distance = 1066380 13:11:54.619 [planetinfo.record]: corona_flare = 0.012679 13:11:54.619 [planetinfo.record]: corona_hues = 0.980125 13:11:54.619 [planetinfo.record]: sun_color = 0.256044, 0.331128, 0.745923, 1 13:11:54.619 [planetinfo.record]: corona_shimmer = 0.262587 13:11:54.619 [planetinfo.record]: station_vector = 0.824 -0.240 0.514 13:11:54.619 [planetinfo.record]: station = coriolis 13:11:59.694 [PLANETINFO OVER]: Done 13:11:59.695 [PLANETINFO LOGGING]: 4 24 13:12:00.709 [planetinfo.record]: seed = 116 140 149 16 13:12:00.709 [planetinfo.record]: coordinates = 140 177 13:12:00.709 [planetinfo.record]: government = 6; 13:12:00.709 [planetinfo.record]: economy = 1; 13:12:00.709 [planetinfo.record]: techlevel = 9; 13:12:00.709 [planetinfo.record]: population = 44; 13:12:00.709 [planetinfo.record]: productivity = 31680; 13:12:00.710 [planetinfo.record]: name = "Erremaar"; 13:12:00.710 [planetinfo.record]: inhabitant = "Green Furry Humanoid"; 13:12:00.710 [planetinfo.record]: inhabitants = "Green Furry Humanoids"; 13:12:00.710 [planetinfo.record]: description = "This planet is reasonably noted for Zero-G hockey."; 13:12:00.722 [planetinfo.record]: air_color = 0.554792, 0.929164, 0.993797, 1; 13:12:00.722 [planetinfo.record]: cloud_alpha = 1.000000; 13:12:00.722 [planetinfo.record]: cloud_color = 0.660295, 0.984375, 0.269165, 1; 13:12:00.722 [planetinfo.record]: cloud_fraction = 0.390000; 13:12:00.722 [planetinfo.record]: land_color = 0.647109, 0.66, 0.649728, 1; 13:12:00.727 [planetinfo.record]: land_fraction = 0.370000; 13:12:00.727 [planetinfo.record]: polar_cloud_color = 0.748939, 0.942969, 0.514765, 1; 13:12:00.727 [planetinfo.record]: polar_land_color = 0.929439, 0.934, 0.930366, 1; 13:12:00.727 [planetinfo.record]: polar_sea_color = 0.750149, 0.939062, 0.837227, 1; 13:12:00.727 [planetinfo.record]: sea_color = 0.119019, 0.609375, 0.345042, 1; 13:12:00.727 [planetinfo.record]: rotation_speed = 0.002021 13:12:00.727 [planetinfo.record]: planet zpos = 354720.000000 13:12:00.727 [planetinfo.record]: sun_radius = 55732.615967 13:12:00.727 [planetinfo.record]: sun_vector = -0.552 0.724 -0.414 13:12:00.727 [planetinfo.record]: sun_distance = 679880 13:12:00.727 [planetinfo.record]: corona_flare = 0.099461 13:12:00.727 [planetinfo.record]: corona_hues = 0.834930 13:12:00.727 [planetinfo.record]: sun_color = 0.189727, 0.420733, 0.677637, 1 13:12:00.727 [planetinfo.record]: corona_shimmer = 0.312878 13:12:00.728 [planetinfo.record]: station_vector = -0.401 -0.765 0.504 13:12:00.728 [planetinfo.record]: station = coriolis 13:12:05.408 [PLANETINFO OVER]: Done 13:12:05.409 [PLANETINFO LOGGING]: 4 25 13:12:06.411 [planetinfo.record]: seed = 120 74 91 132 13:12:06.411 [planetinfo.record]: coordinates = 74 235 13:12:06.411 [planetinfo.record]: government = 7; 13:12:06.411 [planetinfo.record]: economy = 3; 13:12:06.411 [planetinfo.record]: techlevel = 10; 13:12:06.411 [planetinfo.record]: population = 51; 13:12:06.411 [planetinfo.record]: productivity = 31416; 13:12:06.411 [planetinfo.record]: name = "Zaquesso"; 13:12:06.411 [planetinfo.record]: inhabitant = "Human Colonial"; 13:12:06.411 [planetinfo.record]: inhabitants = "Human Colonials"; 13:12:06.411 [planetinfo.record]: description = "This world is fabled for its exciting Zaquessoian evil juice."; 13:12:06.435 [planetinfo.record]: air_color = 0.680329, 0.845701, 0.912498, 1; 13:12:06.435 [planetinfo.record]: cloud_alpha = 1.000000; 13:12:06.435 [planetinfo.record]: cloud_color = 0.63397, 0.740234, 0.61879, 1; 13:12:06.435 [planetinfo.record]: cloud_fraction = 0.540000; 13:12:06.435 [planetinfo.record]: land_color = 0.134062, 0.339507, 0.66, 1; 13:12:06.435 [planetinfo.record]: land_fraction = 0.390000; 13:12:06.435 [planetinfo.record]: polar_cloud_color = 0.758358, 0.833105, 0.74768, 1; 13:12:06.435 [planetinfo.record]: polar_land_color = 0.74793, 0.820613, 0.934, 1; 13:12:06.435 [planetinfo.record]: polar_sea_color = 0.942773, 0.912647, 0.890663, 1; 13:12:06.435 [planetinfo.record]: sea_color = 0.572266, 0.499119, 0.445741, 1; 13:12:06.435 [planetinfo.record]: rotation_speed = 0.000423 13:12:06.435 [planetinfo.record]: planet zpos = 587100.000000 13:12:06.436 [planetinfo.record]: sun_radius = 81296.603699 13:12:06.436 [planetinfo.record]: sun_vector = -0.770 -0.271 -0.578 13:12:06.436 [planetinfo.record]: sun_distance = 821940 13:12:06.436 [planetinfo.record]: corona_flare = 0.007967 13:12:06.436 [planetinfo.record]: corona_hues = 0.629501 13:12:06.436 [planetinfo.record]: sun_color = 0.184493, 0.200809, 0.671683, 1 13:12:06.436 [planetinfo.record]: corona_shimmer = 0.285655 13:12:06.437 [planetinfo.record]: station_vector = -0.038 0.900 0.435 13:12:06.438 [planetinfo.record]: station = coriolis 13:12:11.261 [PLANETINFO OVER]: Done 13:12:11.262 [PLANETINFO LOGGING]: 4 26 13:12:12.280 [planetinfo.record]: seed = 192 199 37 11 13:12:12.280 [planetinfo.record]: coordinates = 199 137 13:12:12.281 [planetinfo.record]: government = 0; 13:12:12.281 [planetinfo.record]: economy = 3; 13:12:12.281 [planetinfo.record]: techlevel = 7; 13:12:12.281 [planetinfo.record]: population = 32; 13:12:12.281 [planetinfo.record]: productivity = 7168; 13:12:12.281 [planetinfo.record]: name = "Matere"; 13:12:12.281 [planetinfo.record]: inhabitant = "Human Colonial"; 13:12:12.281 [planetinfo.record]: inhabitants = "Human Colonials"; 13:12:12.281 [planetinfo.record]: description = "This world is most fabled for its inhabitants’ eccentric love for tourists but plagued by deadly earthquakes."; 13:12:12.283 [planetinfo.record]: air_color = 0.713775, 0.790642, 0.931359, 1; 13:12:12.283 [planetinfo.record]: cloud_alpha = 1.000000; 13:12:12.284 [planetinfo.record]: cloud_color = 0.715942, 0.796875, 0.793081, 1; 13:12:12.284 [planetinfo.record]: cloud_fraction = 0.330000; 13:12:12.284 [planetinfo.record]: land_color = 0.649446, 0.322266, 0.66, 1; 13:12:12.284 [planetinfo.record]: land_fraction = 0.420000; 13:12:12.284 [planetinfo.record]: polar_cloud_color = 0.804093, 0.858594, 0.856039, 1; 13:12:12.284 [planetinfo.record]: polar_land_color = 0.930266, 0.814514, 0.934, 1; 13:12:12.284 [planetinfo.record]: polar_sea_color = 0.930273, 0.875318, 0.851237, 1; 13:12:12.284 [planetinfo.record]: sea_color = 0.697266, 0.532503, 0.460304, 1; 13:12:12.284 [planetinfo.record]: rotation_speed = 0.001871 13:12:12.284 [planetinfo.record]: planet zpos = 699720.000000 13:12:12.284 [planetinfo.record]: sun_radius = 156355.787964 13:12:12.284 [planetinfo.record]: sun_vector = 0.101 -0.792 0.603 13:12:12.284 [planetinfo.record]: sun_distance = 1049580 13:12:12.284 [planetinfo.record]: corona_flare = 0.040405 13:12:12.284 [planetinfo.record]: corona_hues = 0.653175 13:12:12.284 [planetinfo.record]: sun_color = 0.801773, 0.79661, 0.479546, 1 13:12:12.284 [planetinfo.record]: corona_shimmer = 0.315098 13:12:12.284 [planetinfo.record]: station_vector = 0.020 -0.985 0.171 13:12:12.284 [planetinfo.record]: station = coriolis 13:12:17.534 [PLANETINFO OVER]: Done 13:12:17.535 [PLANETINFO LOGGING]: 4 27 13:12:18.541 [planetinfo.record]: seed = 12 150 243 32 13:12:18.541 [planetinfo.record]: coordinates = 150 46 13:12:18.541 [planetinfo.record]: government = 1; 13:12:18.541 [planetinfo.record]: economy = 6; 13:12:18.541 [planetinfo.record]: techlevel = 4; 13:12:18.541 [planetinfo.record]: population = 24; 13:12:18.541 [planetinfo.record]: productivity = 3840; 13:12:18.541 [planetinfo.record]: name = "Cetege"; 13:12:18.541 [planetinfo.record]: inhabitant = "Large Red Slimy Rodent"; 13:12:18.541 [planetinfo.record]: inhabitants = "Large Red Slimy Rodents"; 13:12:18.541 [planetinfo.record]: description = "This planet is very notable for the Cetegeian tree snake and Cetegeian shrew steak."; 13:12:18.556 [planetinfo.record]: air_color = 0.513707, 0.991846, 0.935361, 1; 13:12:18.556 [planetinfo.record]: cloud_alpha = 1.000000; 13:12:18.556 [planetinfo.record]: cloud_color = 0.978516, 0.693394, 0.149071, 1; 13:12:18.556 [planetinfo.record]: cloud_fraction = 0.300000; 13:12:18.556 [planetinfo.record]: land_color = 0.384181, 0.66, 0.229453, 1; 13:12:18.556 [planetinfo.record]: land_fraction = 0.630000; 13:12:18.556 [planetinfo.record]: polar_cloud_color = 0.940332, 0.769085, 0.442158, 1; 13:12:18.556 [planetinfo.record]: polar_land_color = 0.836419, 0.934, 0.781678, 1; 13:12:18.557 [planetinfo.record]: polar_sea_color = 0.932709, 0.937891, 0.886874, 1; 13:12:18.557 [planetinfo.record]: sea_color = 0.607369, 0.621094, 0.485957, 1; 13:12:18.557 [planetinfo.record]: rotation_speed = 0.003983 13:12:18.557 [planetinfo.record]: planet zpos = 355920.000000 13:12:18.557 [planetinfo.record]: sun_radius = 85967.203674 13:12:18.557 [planetinfo.record]: sun_vector = -0.091 -0.664 -0.742 13:12:18.557 [planetinfo.record]: sun_distance = 652520 13:12:18.557 [planetinfo.record]: corona_flare = 0.072127 13:12:18.557 [planetinfo.record]: corona_hues = 0.606461 13:12:18.557 [planetinfo.record]: sun_color = 0.503626, 0.601212, 0.752621, 1 13:12:18.557 [planetinfo.record]: corona_shimmer = 0.350606 13:12:18.558 [planetinfo.record]: station_vector = 0.951 -0.267 0.159 13:12:18.558 [planetinfo.record]: station = coriolis 13:12:23.021 [PLANETINFO OVER]: Done 13:12:23.022 [PLANETINFO LOGGING]: 4 28 13:12:24.026 [planetinfo.record]: seed = 188 163 133 38 13:12:24.026 [planetinfo.record]: coordinates = 163 156 13:12:24.026 [planetinfo.record]: government = 7; 13:12:24.026 [planetinfo.record]: economy = 4; 13:12:24.026 [planetinfo.record]: techlevel = 10; 13:12:24.026 [planetinfo.record]: population = 52; 13:12:24.026 [planetinfo.record]: productivity = 27456; 13:12:24.026 [planetinfo.record]: name = "Bisoaton"; 13:12:24.026 [planetinfo.record]: inhabitant = "Fierce Red Frog"; 13:12:24.026 [planetinfo.record]: inhabitants = "Fierce Red Frogs"; 13:12:24.026 [planetinfo.record]: description = "This planet is plagued by deadly earthquakes."; 13:12:24.048 [planetinfo.record]: air_color = 0.451694, 0.722851, 0.859166, 1; 13:12:24.048 [planetinfo.record]: cloud_alpha = 1.000000; 13:12:24.048 [planetinfo.record]: cloud_color = 0.101967, 0.580078, 0.109437, 1; 13:12:24.048 [planetinfo.record]: cloud_fraction = 0.510000; 13:12:24.048 [planetinfo.record]: land_color = 0.66, 0.556029, 0.201094, 1; 13:12:24.048 [planetinfo.record]: land_fraction = 0.330000; 13:12:24.048 [planetinfo.record]: polar_cloud_color = 0.368998, 0.761035, 0.375124, 1; 13:12:24.048 [planetinfo.record]: polar_land_color = 0.934, 0.897216, 0.771645, 1; 13:12:24.049 [planetinfo.record]: polar_sea_color = 0.909962, 0.932227, 0.877422, 1; 13:12:24.049 [planetinfo.record]: sea_color = 0.612989, 0.677734, 0.518361, 1; 13:12:24.049 [planetinfo.record]: rotation_speed = 0.002971 13:12:24.049 [planetinfo.record]: planet zpos = 451500.000000 13:12:24.049 [planetinfo.record]: sun_radius = 104954.322052 13:12:24.049 [planetinfo.record]: sun_vector = -0.450 0.551 -0.702 13:12:24.049 [planetinfo.record]: sun_distance = 948150 13:12:24.049 [planetinfo.record]: corona_flare = 0.013358 13:12:24.049 [planetinfo.record]: corona_hues = 0.764076 13:12:24.049 [planetinfo.record]: sun_color = 0.753268, 0.690562, 0.654624, 1 13:12:24.049 [planetinfo.record]: corona_shimmer = 0.381831 13:12:24.050 [planetinfo.record]: station_vector = -0.831 -0.553 0.066 13:12:24.050 [planetinfo.record]: station = coriolis 13:12:28.660 [PLANETINFO OVER]: Done 13:12:28.661 [PLANETINFO LOGGING]: 4 29 13:12:29.663 [planetinfo.record]: seed = 16 191 155 87 13:12:29.663 [planetinfo.record]: coordinates = 191 49 13:12:29.663 [planetinfo.record]: government = 2; 13:12:29.663 [planetinfo.record]: economy = 1; 13:12:29.663 [planetinfo.record]: techlevel = 10; 13:12:29.663 [planetinfo.record]: population = 44; 13:12:29.663 [planetinfo.record]: productivity = 19008; 13:12:29.663 [planetinfo.record]: name = "Tiusriri"; 13:12:29.663 [planetinfo.record]: inhabitant = "Yellow Frog"; 13:12:29.663 [planetinfo.record]: inhabitants = "Yellow Frogs"; 13:12:29.663 [planetinfo.record]: description = "Tiusriri is a revolting little planet."; 13:12:29.665 [planetinfo.record]: air_color = 0.458964, 0.470917, 0.928107, 1; 13:12:29.665 [planetinfo.record]: cloud_alpha = 1.000000; 13:12:29.665 [planetinfo.record]: cloud_color = 0.0645676, 0.109726, 0.787109, 1; 13:12:29.665 [planetinfo.record]: cloud_fraction = 0.410000; 13:12:29.665 [planetinfo.record]: land_color = 0.0154688, 0.66, 0.206814, 1; 13:12:29.665 [planetinfo.record]: land_fraction = 0.460000; 13:12:29.665 [planetinfo.record]: polar_cloud_color = 0.364119, 0.394749, 0.854199, 1; 13:12:29.665 [planetinfo.record]: polar_land_color = 0.705973, 0.934, 0.773668, 1; 13:12:29.665 [planetinfo.record]: polar_sea_color = 0.882374, 0.929102, 0.905008, 1; 13:12:29.665 [planetinfo.record]: sea_color = 0.566357, 0.708984, 0.635442, 1; 13:12:29.665 [planetinfo.record]: rotation_speed = 0.003858 13:12:29.665 [planetinfo.record]: planet zpos = 431910.000000 13:12:29.666 [planetinfo.record]: sun_radius = 129194.270325 13:12:29.666 [planetinfo.record]: sun_vector = 0.200 -0.945 0.258 13:12:29.666 [planetinfo.record]: sun_distance = 911810 13:12:29.666 [planetinfo.record]: corona_flare = 0.089351 13:12:29.666 [planetinfo.record]: corona_hues = 0.648636 13:12:29.666 [planetinfo.record]: sun_color = 0.729954, 0.730303, 0.730573, 1 13:12:29.666 [planetinfo.record]: corona_shimmer = 0.509659 13:12:29.667 [planetinfo.record]: station_vector = 0.659 -0.644 0.388 13:12:29.667 [planetinfo.record]: station = coriolis 13:12:35.046 [PLANETINFO OVER]: Done 13:12:35.047 [PLANETINFO LOGGING]: 4 30 13:12:36.054 [planetinfo.record]: seed = 104 254 53 165 13:12:36.054 [planetinfo.record]: coordinates = 254 94 13:12:36.054 [planetinfo.record]: government = 5; 13:12:36.054 [planetinfo.record]: economy = 6; 13:12:36.054 [planetinfo.record]: techlevel = 6; 13:12:36.054 [planetinfo.record]: population = 36; 13:12:36.054 [planetinfo.record]: productivity = 10368; 13:12:36.054 [planetinfo.record]: name = "Cexece"; 13:12:36.054 [planetinfo.record]: inhabitant = "Human Colonial"; 13:12:36.054 [planetinfo.record]: inhabitants = "Human Colonials"; 13:12:36.054 [planetinfo.record]: description = "This planet is mildly noted for its ancient mountains but plagued by frequent earthquakes."; 13:12:36.072 [planetinfo.record]: air_color = 0.64599, 0.52297, 0.919652, 1; 13:12:36.072 [planetinfo.record]: cloud_alpha = 1.000000; 13:12:36.073 [planetinfo.record]: cloud_color = 0.732754, 0.232086, 0.761719, 1; 13:12:36.073 [planetinfo.record]: cloud_fraction = 0.320000; 13:12:36.073 [planetinfo.record]: land_color = 0.66, 0.240934, 0.128906, 1; 13:12:36.073 [planetinfo.record]: land_fraction = 0.440000; 13:12:36.073 [planetinfo.record]: polar_cloud_color = 0.822744, 0.476529, 0.842773, 1; 13:12:36.073 [planetinfo.record]: polar_land_color = 0.934, 0.785739, 0.746105, 1; 13:12:36.073 [planetinfo.record]: polar_sea_color = 0.944531, 0.901888, 0.892047, 1; 13:12:36.073 [planetinfo.record]: sea_color = 0.554688, 0.454516, 0.4314, 1; 13:12:36.073 [planetinfo.record]: rotation_speed = 0.000387 13:12:36.073 [planetinfo.record]: planet zpos = 522000.000000 13:12:36.073 [planetinfo.record]: sun_radius = 114315.605164 13:12:36.073 [planetinfo.record]: sun_vector = 0.110 0.977 -0.184 13:12:36.073 [planetinfo.record]: sun_distance = 1044000 13:12:36.073 [planetinfo.record]: corona_flare = 0.076582 13:12:36.073 [planetinfo.record]: corona_hues = 0.574562 13:12:36.073 [planetinfo.record]: sun_color = 0.801065, 0.555995, 0.292647, 1 13:12:36.073 [planetinfo.record]: corona_shimmer = 0.387636 13:12:36.074 [planetinfo.record]: station_vector = -0.036 0.181 0.983 13:12:36.074 [planetinfo.record]: station = coriolis 13:12:41.074 [PLANETINFO OVER]: Done 13:12:41.075 [PLANETINFO LOGGING]: 4 31 13:12:42.077 [planetinfo.record]: seed = 132 77 211 245 13:12:42.077 [planetinfo.record]: coordinates = 77 165 13:12:42.077 [planetinfo.record]: government = 0; 13:12:42.077 [planetinfo.record]: economy = 7; 13:12:42.077 [planetinfo.record]: techlevel = 1; 13:12:42.078 [planetinfo.record]: population = 12; 13:12:42.078 [planetinfo.record]: productivity = 1152; 13:12:42.078 [planetinfo.record]: name = "Laesinma"; 13:12:42.078 [planetinfo.record]: inhabitant = "Slimy Frog"; 13:12:42.078 [planetinfo.record]: inhabitants = "Slimy Frogs"; 13:12:42.078 [planetinfo.record]: description = "The planet Laesinma is scourged by deadly edible arts graduates."; 13:12:42.100 [planetinfo.record]: air_color = 0.780915, 0.648036, 0.851361, 1; 13:12:42.100 [planetinfo.record]: cloud_alpha = 1.000000; 13:12:42.100 [planetinfo.record]: cloud_color = 0.556641, 0.491409, 0.492938, 1; 13:12:42.100 [planetinfo.record]: cloud_fraction = 0.120000; 13:12:42.100 [planetinfo.record]: land_color = 0.645015, 0.644531, 0.66, 1; 13:12:42.100 [planetinfo.record]: land_fraction = 0.420000; 13:12:42.100 [planetinfo.record]: polar_cloud_color = 0.750488, 0.695521, 0.696809, 1; 13:12:42.100 [planetinfo.record]: polar_land_color = 0.928698, 0.928527, 0.934, 1; 13:12:42.100 [planetinfo.record]: polar_sea_color = 0.788647, 0.944531, 0.897035, 1; 13:12:42.101 [planetinfo.record]: sea_color = 0.188507, 0.554688, 0.443117, 1; 13:12:42.101 [planetinfo.record]: rotation_speed = 0.000006 13:12:42.101 [planetinfo.record]: planet zpos = 542490.000000 13:12:42.101 [planetinfo.record]: sun_radius = 107220.935669 13:12:42.101 [planetinfo.record]: sun_vector = 0.112 0.837 0.535 13:12:42.101 [planetinfo.record]: sun_distance = 876330 13:12:42.101 [planetinfo.record]: corona_flare = 0.036389 13:12:42.101 [planetinfo.record]: corona_hues = 0.559486 13:12:42.101 [planetinfo.record]: sun_color = 0.800934, 0.779244, 0.649265, 1 13:12:42.101 [planetinfo.record]: corona_shimmer = 1.099782 13:12:42.108 [planetinfo.record]: station_vector = -0.874 -0.481 0.064 13:12:42.108 [planetinfo.record]: station = coriolis 13:12:47.670 [PLANETINFO OVER]: Done 13:12:47.672 [PLANETINFO LOGGING]: 4 32 13:12:48.676 [planetinfo.record]: seed = 196 11 181 33 13:12:48.677 [planetinfo.record]: coordinates = 11 44 13:12:48.677 [planetinfo.record]: government = 0; 13:12:48.677 [planetinfo.record]: economy = 6; 13:12:48.677 [planetinfo.record]: techlevel = 4; 13:12:48.677 [planetinfo.record]: population = 23; 13:12:48.677 [planetinfo.record]: productivity = 2944; 13:12:48.677 [planetinfo.record]: name = "Lequso"; 13:12:48.677 [planetinfo.record]: inhabitant = "Large Red Rodent"; 13:12:48.677 [planetinfo.record]: inhabitants = "Large Red Rodents"; 13:12:48.677 [planetinfo.record]: description = "Lequso is most famous for its vast dense forests and its exotic night life."; 13:12:48.696 [planetinfo.record]: air_color = 0.552999, 0.96583, 0.923504, 1; 13:12:48.696 [planetinfo.record]: cloud_alpha = 1.000000; 13:12:48.696 [planetinfo.record]: cloud_color = 0.900391, 0.717664, 0.284889, 1; 13:12:48.696 [planetinfo.record]: cloud_fraction = 0.270000; 13:12:48.696 [planetinfo.record]: land_color = 0.144375, 0.418301, 0.66, 1; 13:12:48.696 [planetinfo.record]: land_fraction = 0.690000; 13:12:48.696 [planetinfo.record]: polar_cloud_color = 0.905176, 0.790365, 0.518443, 1; 13:12:48.696 [planetinfo.record]: polar_land_color = 0.751578, 0.84849, 0.934, 1; 13:12:48.697 [planetinfo.record]: polar_sea_color = 0.936523, 0.876683, 0.875247, 1; 13:12:48.698 [planetinfo.record]: sea_color = 0.634766, 0.472529, 0.468636, 1; 13:12:48.698 [planetinfo.record]: rotation_speed = 0.003883 13:12:48.698 [planetinfo.record]: planet zpos = 277470.000000 13:12:48.698 [planetinfo.record]: sun_radius = 66035.455170 13:12:48.698 [planetinfo.record]: sun_vector = 0.289 -0.650 0.703 13:12:48.698 [planetinfo.record]: sun_distance = 739920 13:12:48.698 [planetinfo.record]: corona_flare = 0.041760 13:12:48.698 [planetinfo.record]: corona_hues = 0.615417 13:12:48.698 [planetinfo.record]: sun_color = 0.817657, 0.780019, 0.71613, 1 13:12:48.699 [planetinfo.record]: corona_shimmer = 0.478484 13:12:48.699 [planetinfo.record]: station_vector = 0.947 0.276 0.166 13:12:48.699 [planetinfo.record]: station = coriolis 13:12:53.147 [PLANETINFO OVER]: Done 13:12:53.149 [PLANETINFO LOGGING]: 4 33 13:12:54.151 [planetinfo.record]: seed = 104 3 27 229 13:12:54.151 [planetinfo.record]: coordinates = 3 135 13:12:54.151 [planetinfo.record]: government = 5; 13:12:54.151 [planetinfo.record]: economy = 7; 13:12:54.151 [planetinfo.record]: techlevel = 6; 13:12:54.151 [planetinfo.record]: population = 37; 13:12:54.151 [planetinfo.record]: productivity = 7992; 13:12:54.151 [planetinfo.record]: name = "Ceered"; 13:12:54.151 [planetinfo.record]: inhabitant = "Human Colonial"; 13:12:54.151 [planetinfo.record]: inhabitants = "Human Colonials"; 13:12:54.151 [planetinfo.record]: description = "This world is a revolting little planet."; 13:12:54.173 [planetinfo.record]: air_color = 0.558094, 0.527944, 0.887783, 1; 13:12:54.173 [planetinfo.record]: cloud_alpha = 1.000000; 13:12:54.173 [planetinfo.record]: cloud_color = 0.364146, 0.254959, 0.666016, 1; 13:12:54.173 [planetinfo.record]: cloud_fraction = 0.280000; 13:12:54.174 [planetinfo.record]: land_color = 0.655408, 0.66, 0.513047, 1; 13:12:54.174 [planetinfo.record]: land_fraction = 0.640000; 13:12:54.174 [planetinfo.record]: polar_cloud_color = 0.573166, 0.491226, 0.799707, 1; 13:12:54.174 [planetinfo.record]: polar_land_color = 0.932375, 0.934, 0.88201, 1; 13:12:54.174 [planetinfo.record]: polar_sea_color = 0.87054, 0.883456, 0.917773, 1; 13:12:54.174 [planetinfo.record]: sea_color = 0.652995, 0.69928, 0.822266, 1; 13:12:54.174 [planetinfo.record]: rotation_speed = 0.002331 13:12:54.174 [planetinfo.record]: planet zpos = 491880.000000 13:12:54.174 [planetinfo.record]: sun_radius = 107113.394775 13:12:54.174 [planetinfo.record]: sun_vector = -0.655 0.726 0.209 13:12:54.174 [planetinfo.record]: sun_distance = 737820 13:12:54.174 [planetinfo.record]: corona_flare = 0.085794 13:12:54.174 [planetinfo.record]: corona_hues = 0.862770 13:12:54.174 [planetinfo.record]: sun_color = 0.732169, 0.708187, 0.702725, 1 13:12:54.174 [planetinfo.record]: corona_shimmer = 0.342489 13:12:54.174 [planetinfo.record]: station_vector = -0.443 0.823 0.355 13:12:54.175 [planetinfo.record]: station = coriolis 13:12:58.709 [PLANETINFO OVER]: Done 13:12:58.709 [PLANETINFO LOGGING]: 4 34 13:12:59.711 [planetinfo.record]: seed = 208 173 133 118 13:12:59.711 [planetinfo.record]: coordinates = 173 88 13:12:59.711 [planetinfo.record]: government = 2; 13:12:59.711 [planetinfo.record]: economy = 0; 13:12:59.711 [planetinfo.record]: techlevel = 9; 13:12:59.711 [planetinfo.record]: population = 39; 13:12:59.712 [planetinfo.record]: productivity = 18720; 13:12:59.712 [planetinfo.record]: name = "Vetele"; 13:12:59.712 [planetinfo.record]: inhabitant = "Blue Furry Insect"; 13:12:59.712 [planetinfo.record]: inhabitants = "Blue Furry Insects"; 13:12:59.712 [planetinfo.record]: description = "Vetele is a revolting little planet."; 13:12:59.736 [planetinfo.record]: air_color = 0.508377, 0.535529, 0.883881, 1; 13:12:59.736 [planetinfo.record]: cloud_alpha = 1.000000; 13:12:59.736 [planetinfo.record]: cloud_color = 0.212135, 0.301949, 0.654297, 1; 13:12:59.736 [planetinfo.record]: cloud_fraction = 0.330000; 13:12:59.736 [planetinfo.record]: land_color = 0.270159, 0.0283594, 0.66, 1; 13:12:59.736 [planetinfo.record]: land_fraction = 0.600000; 13:12:59.736 [planetinfo.record]: polar_cloud_color = 0.458894, 0.52705, 0.794434, 1; 13:12:59.736 [planetinfo.record]: polar_land_color = 0.796079, 0.710533, 0.934, 1; 13:12:59.736 [planetinfo.record]: polar_sea_color = 0.938867, 0.876521, 0.876521, 1; 13:12:59.736 [planetinfo.record]: sea_color = 0.611328, 0.448944, 0.448944, 1; 13:12:59.736 [planetinfo.record]: rotation_speed = 0.003791 13:12:59.736 [planetinfo.record]: planet zpos = 543000.000000 13:12:59.736 [planetinfo.record]: sun_radius = 93060.916901 13:12:59.736 [planetinfo.record]: sun_vector = -0.316 -0.703 0.638 13:12:59.736 [planetinfo.record]: sun_distance = 995500 13:12:59.736 [planetinfo.record]: corona_flare = 0.049939 13:12:59.736 [planetinfo.record]: corona_hues = 0.597176 13:12:59.737 [planetinfo.record]: sun_color = 0.835596, 0.74529, 0.355762, 1 13:12:59.737 [planetinfo.record]: corona_shimmer = 0.278969 13:12:59.737 [planetinfo.record]: station_vector = 0.978 -0.181 0.106 13:12:59.737 [planetinfo.record]: station = coriolis 13:13:05.194 [PLANETINFO OVER]: Done 13:13:05.195 [PLANETINFO LOGGING]: 4 35 13:13:06.217 [planetinfo.record]: seed = 188 148 243 178 13:13:06.218 [planetinfo.record]: coordinates = 148 161 13:13:06.218 [planetinfo.record]: government = 7; 13:13:06.218 [planetinfo.record]: economy = 1; 13:13:06.218 [planetinfo.record]: techlevel = 10; 13:13:06.218 [planetinfo.record]: population = 49; 13:13:06.218 [planetinfo.record]: productivity = 38808; 13:13:06.218 [planetinfo.record]: name = "Enuserin"; 13:13:06.218 [planetinfo.record]: inhabitant = "Harmless Furry Insect"; 13:13:06.218 [planetinfo.record]: inhabitants = "Harmless Furry Insects"; 13:13:06.218 [planetinfo.record]: description = "The planet Enuserin is an unremarkable planet."; 13:13:06.251 [planetinfo.record]: air_color = 0.780444, 0.525003, 0.951522, 1; 13:13:06.252 [planetinfo.record]: cloud_alpha = 1.000000; 13:13:06.252 [planetinfo.record]: cloud_color = 0.857422, 0.217705, 0.337652, 1; 13:13:06.252 [planetinfo.record]: cloud_fraction = 0.320000; 13:13:06.252 [planetinfo.record]: land_color = 0.500381, 0.390137, 0.53125, 1; 13:13:06.252 [planetinfo.record]: land_fraction = 0.470000; 13:13:06.252 [planetinfo.record]: polar_cloud_color = 0.88584, 0.472765, 0.550217, 1; 13:13:06.252 [planetinfo.record]: polar_land_color = 0.93312, 0.883997, 0.946875, 1; 13:13:06.252 [planetinfo.record]: polar_sea_color = 0.928564, 0.936328, 0.884025, 1; 13:13:06.252 [planetinfo.record]: sea_color = 0.615601, 0.636719, 0.494452, 1; 13:13:06.252 [planetinfo.record]: rotation_speed = 0.000924 13:13:06.252 [planetinfo.record]: planet zpos = 347600.000000 13:13:06.252 [planetinfo.record]: sun_radius = 90559.198608 13:13:06.252 [planetinfo.record]: sun_vector = -0.643 0.270 0.717 13:13:06.252 [planetinfo.record]: sun_distance = 660440 13:13:06.253 [planetinfo.record]: corona_flare = 0.043361 13:13:06.253 [planetinfo.record]: corona_hues = 0.919525 13:13:06.253 [planetinfo.record]: sun_color = 0.673566, 0.670832, 0.657571, 1 13:13:06.253 [planetinfo.record]: corona_shimmer = 0.634436 13:13:06.253 [planetinfo.record]: station_vector = 0.942 0.132 0.308 13:13:06.253 [planetinfo.record]: station = coriolis 13:13:11.468 [PLANETINFO OVER]: Done 13:13:11.470 [PLANETINFO LOGGING]: 4 36 13:13:12.480 [planetinfo.record]: seed = 140 204 37 230 13:13:12.480 [planetinfo.record]: coordinates = 204 48 13:13:12.481 [planetinfo.record]: government = 1; 13:13:12.481 [planetinfo.record]: economy = 2; 13:13:12.481 [planetinfo.record]: techlevel = 6; 13:13:12.481 [planetinfo.record]: population = 28; 13:13:12.481 [planetinfo.record]: productivity = 8960; 13:13:12.481 [planetinfo.record]: name = "Bigeve"; 13:13:12.481 [planetinfo.record]: inhabitant = "Human Colonial"; 13:13:12.481 [planetinfo.record]: inhabitants = "Human Colonials"; 13:13:12.481 [planetinfo.record]: description = "This planet is beset by deadly earthquakes."; 13:13:12.499 [planetinfo.record]: air_color = 0.544283, 0.959326, 0.868185, 1; 13:13:12.499 [planetinfo.record]: cloud_alpha = 1.000000; 13:13:12.499 [planetinfo.record]: cloud_color = 0.880859, 0.481478, 0.264946, 1; 13:13:12.500 [planetinfo.record]: cloud_fraction = 0.530000; 13:13:12.500 [planetinfo.record]: land_color = 0.0953906, 0.461505, 0.66, 1; 13:13:12.500 [planetinfo.record]: land_fraction = 0.580000; 13:13:12.500 [planetinfo.record]: polar_cloud_color = 0.896387, 0.642373, 0.504655, 1; 13:13:12.500 [planetinfo.record]: polar_land_color = 0.734248, 0.863775, 0.934, 1; 13:13:12.500 [planetinfo.record]: polar_sea_color = 0.935352, 0.875058, 0.870498, 1; 13:13:12.500 [planetinfo.record]: sea_color = 0.646484, 0.479793, 0.467186, 1; 13:13:12.500 [planetinfo.record]: rotation_speed = 0.004940 13:13:12.500 [planetinfo.record]: planet zpos = 501160.000000 13:13:12.502 [planetinfo.record]: sun_radius = 129665.528564 13:13:12.503 [planetinfo.record]: sun_vector = -0.080 -0.143 -0.987 13:13:12.503 [planetinfo.record]: sun_distance = 865640 13:13:12.503 [planetinfo.record]: corona_flare = 0.058005 13:13:12.503 [planetinfo.record]: corona_hues = 0.869720 13:13:12.503 [planetinfo.record]: sun_color = 0.779599, 0.684195, 0.524525, 1 13:13:12.503 [planetinfo.record]: corona_shimmer = 0.359519 13:13:12.503 [planetinfo.record]: station_vector = -0.446 0.253 0.859 13:13:12.503 [planetinfo.record]: station = coriolis 13:13:18.017 [PLANETINFO OVER]: Done 13:13:18.018 [PLANETINFO LOGGING]: 4 37 13:13:19.021 [planetinfo.record]: seed = 128 95 219 216 13:13:19.022 [planetinfo.record]: coordinates = 95 150 13:13:19.022 [planetinfo.record]: government = 0; 13:13:19.022 [planetinfo.record]: economy = 6; 13:13:19.022 [planetinfo.record]: techlevel = 4; 13:13:19.022 [planetinfo.record]: population = 23; 13:13:19.022 [planetinfo.record]: productivity = 2944; 13:13:19.022 [planetinfo.record]: name = "Edrebi"; 13:13:19.022 [planetinfo.record]: inhabitant = "Bug-Eyed Frog"; 13:13:19.022 [planetinfo.record]: inhabitants = "Bug-Eyed Frogs"; 13:13:19.022 [planetinfo.record]: description = "The planet Edrebi is scourged by killer edible arts graduates."; 13:13:19.051 [planetinfo.record]: air_color = 0.582279, 0.9151, 0.805648, 1; 13:13:19.051 [planetinfo.record]: cloud_alpha = 1.000000; 13:13:19.051 [planetinfo.record]: cloud_color = 0.748047, 0.388497, 0.379868, 1; 13:13:19.051 [planetinfo.record]: cloud_fraction = 0.270000; 13:13:19.052 [planetinfo.record]: land_color = 0.126328, 0.534921, 0.66, 1; 13:13:19.052 [planetinfo.record]: land_fraction = 0.310000; 13:13:19.052 [planetinfo.record]: polar_cloud_color = 0.836621, 0.585294, 0.579262, 1; 13:13:19.052 [planetinfo.record]: polar_land_color = 0.745193, 0.889748, 0.934, 1; 13:13:19.052 [planetinfo.record]: polar_sea_color = 0.882981, 0.926953, 0.868928, 1; 13:13:19.052 [planetinfo.record]: sea_color = 0.591863, 0.730469, 0.547566, 1; 13:13:19.052 [planetinfo.record]: rotation_speed = 0.000777 13:13:19.052 [planetinfo.record]: planet zpos = 743850.000000 13:13:19.052 [planetinfo.record]: sun_radius = 134884.103851 13:13:19.052 [planetinfo.record]: sun_vector = 0.622 0.541 -0.566 13:13:19.052 [planetinfo.record]: sun_distance = 843030 13:13:19.052 [planetinfo.record]: corona_flare = 0.010011 13:13:19.052 [planetinfo.record]: corona_hues = 0.565369 13:13:19.052 [planetinfo.record]: sun_color = 0.814099, 0.6902, 0.601326, 1 13:13:19.052 [planetinfo.record]: corona_shimmer = 0.380086 13:13:19.053 [planetinfo.record]: station_vector = -0.462 -0.766 0.447 13:13:19.053 [planetinfo.record]: station = coriolis 13:13:24.067 [PLANETINFO OVER]: Done 13:13:24.068 [PLANETINFO LOGGING]: 4 38 13:13:25.073 [planetinfo.record]: seed = 248 173 21 131 13:13:25.073 [planetinfo.record]: coordinates = 173 6 13:13:25.073 [planetinfo.record]: government = 7; 13:13:25.073 [planetinfo.record]: economy = 6; 13:13:25.073 [planetinfo.record]: techlevel = 6; 13:13:25.074 [planetinfo.record]: population = 38; 13:13:25.074 [planetinfo.record]: productivity = 13376; 13:13:25.074 [planetinfo.record]: name = "Getius"; 13:13:25.074 [planetinfo.record]: inhabitant = "Human Colonial"; 13:13:25.074 [planetinfo.record]: inhabitants = "Human Colonials"; 13:13:25.074 [planetinfo.record]: description = "Getius is an unremarkable dump."; 13:13:25.080 [planetinfo.record]: air_color = 0.613734, 0.893917, 0.954773, 1; 13:13:25.080 [planetinfo.record]: cloud_alpha = 1.000000; 13:13:25.080 [planetinfo.record]: cloud_color = 0.643034, 0.867188, 0.457306, 1; 13:13:25.081 [planetinfo.record]: cloud_fraction = 0.290000; 13:13:25.081 [planetinfo.record]: land_color = 0.212131, 0.134062, 0.66, 1; 13:13:25.081 [planetinfo.record]: land_fraction = 0.250000; 13:13:25.081 [planetinfo.record]: polar_cloud_color = 0.746415, 0.890234, 0.62725, 1; 13:13:25.081 [planetinfo.record]: polar_land_color = 0.77555, 0.74793, 0.934, 1; 13:13:25.081 [planetinfo.record]: polar_sea_color = 0.942578, 0.93165, 0.894345, 1; 13:13:25.081 [planetinfo.record]: sea_color = 0.574219, 0.54759, 0.456683, 1; 13:13:25.081 [planetinfo.record]: rotation_speed = 0.002234 13:13:25.081 [planetinfo.record]: planet zpos = 525980.000000 13:13:25.081 [planetinfo.record]: sun_radius = 99226.052704 13:13:25.081 [planetinfo.record]: sun_vector = 0.138 0.989 0.050 13:13:25.081 [planetinfo.record]: sun_distance = 788970 13:13:25.081 [planetinfo.record]: corona_flare = 0.081055 13:13:25.081 [planetinfo.record]: corona_hues = 0.661522 13:13:25.081 [planetinfo.record]: sun_color = 0.74706, 0.763098, 0.832108, 1 13:13:25.081 [planetinfo.record]: corona_shimmer = 0.502669 13:13:25.081 [planetinfo.record]: station_vector = 0.136 -0.976 0.171 13:13:25.081 [planetinfo.record]: station = coriolis 13:13:29.432 [PLANETINFO OVER]: Done 13:13:29.433 [PLANETINFO LOGGING]: 4 39 13:13:30.435 [planetinfo.record]: seed = 180 35 83 196 13:13:30.435 [planetinfo.record]: coordinates = 35 104 13:13:30.435 [planetinfo.record]: government = 6; 13:13:30.435 [planetinfo.record]: economy = 0; 13:13:30.435 [planetinfo.record]: techlevel = 13; 13:13:30.436 [planetinfo.record]: population = 59; 13:13:30.436 [planetinfo.record]: productivity = 47200; 13:13:30.436 [planetinfo.record]: name = "Zaeredre"; 13:13:30.436 [planetinfo.record]: inhabitant = "Human Colonial"; 13:13:30.436 [planetinfo.record]: inhabitants = "Human Colonials"; 13:13:30.436 [planetinfo.record]: description = "This world is noted for its exciting vacuum karate."; 13:13:30.455 [planetinfo.record]: air_color = 0.574818, 0.569012, 0.840955, 1; 13:13:30.455 [planetinfo.record]: cloud_alpha = 1.000000; 13:13:30.455 [planetinfo.record]: cloud_color = 0.345653, 0.330421, 0.525391, 1; 13:13:30.455 [planetinfo.record]: cloud_fraction = 0.570000; 13:13:30.456 [planetinfo.record]: land_color = 0.541016, 0.321377, 0.0105667, 1; 13:13:30.456 [planetinfo.record]: land_fraction = 0.630000; 13:13:30.456 [planetinfo.record]: polar_cloud_color = 0.578968, 0.565624, 0.736426, 1; 13:13:30.456 [planetinfo.record]: polar_land_color = 0.945898, 0.849896, 0.714042, 1; 13:13:30.456 [planetinfo.record]: polar_sea_color = 0.939062, 0.904528, 0.881655, 1; 13:13:30.456 [planetinfo.record]: sea_color = 0.609375, 0.519735, 0.460364, 1; 13:13:30.456 [planetinfo.record]: rotation_speed = 0.001852 13:13:30.456 [planetinfo.record]: planet zpos = 426250.000000 13:13:30.456 [planetinfo.record]: sun_radius = 75382.041931 13:13:30.456 [planetinfo.record]: sun_vector = -0.393 -0.707 0.589 13:13:30.456 [planetinfo.record]: sun_distance = 775000 13:13:30.456 [planetinfo.record]: corona_flare = 0.045050 13:13:30.456 [planetinfo.record]: corona_hues = 0.581741 13:13:30.456 [planetinfo.record]: sun_color = 0.320785, 0.555321, 0.729199, 1 13:13:30.457 [planetinfo.record]: corona_shimmer = 1.013360 13:13:30.457 [planetinfo.record]: station_vector = 0.708 0.079 0.702 13:13:30.457 [planetinfo.record]: station = icosahedron 13:13:34.933 [PLANETINFO OVER]: Done 13:13:34.934 [PLANETINFO LOGGING]: 4 40 13:13:35.951 [planetinfo.record]: seed = 20 78 213 215 13:13:35.951 [planetinfo.record]: coordinates = 78 56 13:13:35.951 [planetinfo.record]: government = 2; 13:13:35.951 [planetinfo.record]: economy = 0; 13:13:35.951 [planetinfo.record]: techlevel = 10; 13:13:35.951 [planetinfo.record]: population = 43; 13:13:35.951 [planetinfo.record]: productivity = 20640; 13:13:35.951 [planetinfo.record]: name = "Tirizaan"; 13:13:35.951 [planetinfo.record]: inhabitant = "Frog"; 13:13:35.951 [planetinfo.record]: inhabitants = "Frogs"; 13:13:35.951 [planetinfo.record]: description = "Tirizaan is cursed by dreadful civil war."; 13:13:35.968 [planetinfo.record]: air_color = 0.676377, 0.85631, 0.904043, 1; 13:13:35.968 [planetinfo.record]: cloud_alpha = 1.000000; 13:13:35.968 [planetinfo.record]: cloud_color = 0.64329, 0.714844, 0.603149, 1; 13:13:35.968 [planetinfo.record]: cloud_fraction = 0.490000; 13:13:35.968 [planetinfo.record]: land_color = 0.633799, 0.638672, 0.576302, 1; 13:13:35.968 [planetinfo.record]: land_fraction = 0.610000; 13:13:35.968 [planetinfo.record]: polar_cloud_color = 0.770275, 0.82168, 0.741438, 1; 13:13:35.968 [planetinfo.record]: polar_land_color = 0.934347, 0.936133, 0.913278, 1; 13:13:35.969 [planetinfo.record]: polar_sea_color = 0.792671, 0.948242, 0.831564, 1; 13:13:35.969 [planetinfo.record]: sea_color = 0.177917, 0.517578, 0.262833, 1; 13:13:35.969 [planetinfo.record]: rotation_speed = 0.000852 13:13:35.969 [planetinfo.record]: planet zpos = 562320.000000 13:13:35.969 [planetinfo.record]: sun_radius = 100899.584656 13:13:35.969 [planetinfo.record]: sun_vector = -0.901 -0.373 -0.221 13:13:35.969 [planetinfo.record]: sun_distance = 796620 13:13:35.969 [planetinfo.record]: corona_flare = 0.034128 13:13:35.969 [planetinfo.record]: corona_hues = 0.867859 13:13:35.969 [planetinfo.record]: sun_color = 0.818436, 0.367828, 0.361681, 1 13:13:35.969 [planetinfo.record]: corona_shimmer = 0.325677 13:13:35.969 [planetinfo.record]: station_vector = 0.446 0.010 0.895 13:13:35.969 [planetinfo.record]: station = coriolis 13:13:41.925 [PLANETINFO OVER]: Done 13:13:41.925 [PLANETINFO LOGGING]: 4 41 13:13:42.935 [planetinfo.record]: seed = 88 187 219 158 13:13:42.935 [planetinfo.record]: coordinates = 187 132 13:13:42.935 [planetinfo.record]: government = 3; 13:13:42.935 [planetinfo.record]: economy = 4; 13:13:42.935 [planetinfo.record]: techlevel = 8; 13:13:42.935 [planetinfo.record]: population = 40; 13:13:42.935 [planetinfo.record]: productivity = 13440; 13:13:42.935 [planetinfo.record]: name = "Ririor"; 13:13:42.935 [planetinfo.record]: inhabitant = "Black Frog"; 13:13:42.935 [planetinfo.record]: inhabitants = "Black Frogs"; 13:13:42.935 [planetinfo.record]: description = "The planet Ririor is most well known for its hoopy casinos."; 13:13:42.953 [planetinfo.record]: air_color = 0.541246, 0.59788, 0.840305, 1; 13:13:42.953 [planetinfo.record]: cloud_alpha = 1.000000; 13:13:42.953 [planetinfo.record]: cloud_color = 0.278076, 0.414175, 0.523438, 1; 13:13:42.954 [planetinfo.record]: cloud_fraction = 0.420000; 13:13:42.954 [planetinfo.record]: land_color = 0.171875, 0.5, 0.192383, 1; 13:13:42.954 [planetinfo.record]: land_fraction = 0.660000; 13:13:42.954 [planetinfo.record]: polar_cloud_color = 0.520055, 0.639585, 0.735547, 1; 13:13:42.954 [planetinfo.record]: polar_land_color = 0.794141, 0.95, 0.803882, 1; 13:13:42.954 [planetinfo.record]: polar_sea_color = 0.936914, 0.905174, 0.877168, 1; 13:13:42.954 [planetinfo.record]: sea_color = 0.630859, 0.545371, 0.469941, 1; 13:13:42.954 [planetinfo.record]: rotation_speed = 0.004202 13:13:42.955 [planetinfo.record]: planet zpos = 790440.000000 13:13:42.955 [planetinfo.record]: sun_radius = 204934.137726 13:13:42.955 [planetinfo.record]: sun_vector = 0.836 -0.451 0.311 13:13:42.955 [planetinfo.record]: sun_distance = 1383270 13:13:42.955 [planetinfo.record]: corona_flare = 0.011687 13:13:42.955 [planetinfo.record]: corona_hues = 0.552971 13:13:42.955 [planetinfo.record]: sun_color = 0.288528, 0.393287, 0.762051, 1 13:13:42.955 [planetinfo.record]: corona_shimmer = 0.351023 13:13:42.956 [planetinfo.record]: station_vector = -0.181 -0.942 0.282 13:13:42.956 [planetinfo.record]: station = coriolis 13:13:47.951 [PLANETINFO OVER]: Done 13:13:47.952 [PLANETINFO LOGGING]: 4 42 13:13:48.956 [planetinfo.record]: seed = 224 182 229 206 13:13:48.956 [planetinfo.record]: coordinates = 182 57 13:13:48.956 [planetinfo.record]: government = 4; 13:13:48.956 [planetinfo.record]: economy = 1; 13:13:48.956 [planetinfo.record]: techlevel = 10; 13:13:48.956 [planetinfo.record]: population = 46; 13:13:48.956 [planetinfo.record]: productivity = 26496; 13:13:48.956 [planetinfo.record]: name = "Reriza"; 13:13:48.956 [planetinfo.record]: inhabitant = "Frog"; 13:13:48.956 [planetinfo.record]: inhabitants = "Frogs"; 13:13:48.956 [planetinfo.record]: description = "The world Reriza is reasonably noted for Zero-G cricket and Rerizaian evil brandy."; 13:13:48.985 [planetinfo.record]: air_color = 0.60868, 0.521784, 0.909246, 1; 13:13:48.986 [planetinfo.record]: cloud_alpha = 1.000000; 13:13:48.986 [planetinfo.record]: cloud_color = 0.575315, 0.233978, 0.730469, 1; 13:13:48.986 [planetinfo.record]: cloud_fraction = 0.380000; 13:13:48.986 [planetinfo.record]: land_color = 0.66, 0.0386719, 0.475543, 1; 13:13:48.987 [planetinfo.record]: land_fraction = 0.610000; 13:13:48.987 [planetinfo.record]: polar_cloud_color = 0.718698, 0.476671, 0.828711, 1; 13:13:48.987 [planetinfo.record]: polar_land_color = 0.934, 0.714182, 0.868741, 1; 13:13:48.987 [planetinfo.record]: polar_sea_color = 0.936076, 0.937695, 0.885866, 1; 13:13:48.987 [planetinfo.record]: sea_color = 0.618742, 0.623047, 0.485295, 1; 13:13:48.987 [planetinfo.record]: rotation_speed = 0.000708 13:13:48.987 [planetinfo.record]: planet zpos = 789840.000000 13:13:48.987 [planetinfo.record]: sun_radius = 225295.102844 13:13:48.987 [planetinfo.record]: sun_vector = -0.332 0.852 0.405 13:13:48.987 [planetinfo.record]: sun_distance = 1250580 13:13:48.987 [planetinfo.record]: corona_flare = 0.016338 13:13:48.988 [planetinfo.record]: corona_hues = 0.562614 13:13:48.988 [planetinfo.record]: sun_color = 0.768308, 0.789627, 0.814801, 1 13:13:48.988 [planetinfo.record]: corona_shimmer = 0.425459 13:13:48.988 [planetinfo.record]: station_vector = -0.542 -0.821 0.182 13:13:48.988 [planetinfo.record]: station = coriolis 13:13:53.874 [PLANETINFO OVER]: Done 13:13:53.875 [PLANETINFO LOGGING]: 4 43 13:13:54.879 [planetinfo.record]: seed = 108 210 243 213 13:13:54.879 [planetinfo.record]: coordinates = 210 68 13:13:54.879 [planetinfo.record]: government = 5; 13:13:54.879 [planetinfo.record]: economy = 4; 13:13:54.879 [planetinfo.record]: techlevel = 8; 13:13:54.879 [planetinfo.record]: population = 42; 13:13:54.879 [planetinfo.record]: productivity = 18144; 13:13:54.879 [planetinfo.record]: name = "Ladila"; 13:13:54.879 [planetinfo.record]: inhabitant = "Insect"; 13:13:54.879 [planetinfo.record]: inhabitants = "Insects"; 13:13:54.880 [planetinfo.record]: description = "This planet is beset by dreadful earthquakes."; 13:13:54.916 [planetinfo.record]: air_color = 0.5259, 0.606382, 0.845508, 1; 13:13:54.916 [planetinfo.record]: cloud_alpha = 1.000000; 13:13:54.916 [planetinfo.record]: cloud_color = 0.25058, 0.464688, 0.539062, 1; 13:13:54.916 [planetinfo.record]: cloud_fraction = 0.480000; 13:13:54.916 [planetinfo.record]: land_color = 0.617783, 0.66, 0.322266, 1; 13:13:54.916 [planetinfo.record]: land_fraction = 0.480000; 13:13:54.916 [planetinfo.record]: polar_cloud_color = 0.494206, 0.678545, 0.742578, 1; 13:13:54.916 [planetinfo.record]: polar_land_color = 0.919064, 0.934, 0.814514, 1; 13:13:54.916 [planetinfo.record]: polar_sea_color = 0.940039, 0.894042, 0.881746, 1; 13:13:54.916 [planetinfo.record]: sea_color = 0.599609, 0.482251, 0.450878, 1; 13:13:54.916 [planetinfo.record]: rotation_speed = 0.002825 13:13:54.916 [planetinfo.record]: planet zpos = 516720.000000 13:13:54.916 [planetinfo.record]: sun_radius = 101334.498291 13:13:54.916 [planetinfo.record]: sun_vector = 0.142 -0.855 0.499 13:13:54.916 [planetinfo.record]: sun_distance = 818140 13:13:54.916 [planetinfo.record]: corona_flare = 0.020859 13:13:54.916 [planetinfo.record]: corona_hues = 0.597198 13:13:54.916 [planetinfo.record]: sun_color = 0.607569, 0.69797, 0.7021, 1 13:13:54.917 [planetinfo.record]: corona_shimmer = 0.422878 13:13:54.917 [planetinfo.record]: station_vector = -0.215 0.969 0.123 13:13:54.917 [planetinfo.record]: station = coriolis 13:13:59.393 [PLANETINFO OVER]: Done 13:13:59.395 [PLANETINFO LOGGING]: 4 44 13:14:00.398 [planetinfo.record]: seed = 92 88 197 218 13:14:00.398 [planetinfo.record]: coordinates = 88 149 13:14:00.398 [planetinfo.record]: government = 3; 13:14:00.398 [planetinfo.record]: economy = 5; 13:14:00.398 [planetinfo.record]: techlevel = 4; 13:14:00.398 [planetinfo.record]: population = 25; 13:14:00.398 [planetinfo.record]: productivity = 7000; 13:14:00.398 [planetinfo.record]: name = "Quusanri"; 13:14:00.398 [planetinfo.record]: inhabitant = "Furry Insect"; 13:14:00.398 [planetinfo.record]: inhabitants = "Furry Insects"; 13:14:00.398 [planetinfo.record]: description = "The planet Quusanri is cursed by killer mountain lobstoids."; 13:14:00.404 [planetinfo.record]: air_color = 0.569991, 0.458182, 0.956725, 1; 13:14:00.404 [planetinfo.record]: cloud_alpha = 1.000000; 13:14:00.404 [planetinfo.record]: cloud_color = 0.609811, 0.0306931, 0.873047, 1; 13:14:00.404 [planetinfo.record]: cloud_fraction = 0.390000; 13:14:00.404 [planetinfo.record]: land_color = 0.616172, 0.619938, 0.66, 1; 13:14:00.404 [planetinfo.record]: land_fraction = 0.270000; 13:14:00.405 [planetinfo.record]: polar_cloud_color = 0.724613, 0.354445, 0.892871, 1; 13:14:00.405 [planetinfo.record]: polar_land_color = 0.918494, 0.919827, 0.934, 1; 13:14:00.405 [planetinfo.record]: polar_sea_color = 0.926758, 0.84587, 0.827204, 1; 13:14:00.405 [planetinfo.record]: sea_color = 0.732422, 0.476718, 0.417709, 1; 13:14:00.405 [planetinfo.record]: rotation_speed = 0.001793 13:14:00.405 [planetinfo.record]: planet zpos = 601040.000000 13:14:00.405 [planetinfo.record]: sun_radius = 142806.362305 13:14:00.405 [planetinfo.record]: sun_vector = -0.384 -0.852 -0.356 13:14:00.405 [planetinfo.record]: sun_distance = 1038160 13:14:00.405 [planetinfo.record]: corona_flare = 0.066513 13:14:00.405 [planetinfo.record]: corona_hues = 0.713516 13:14:00.405 [planetinfo.record]: sun_color = 0.207977, 0.726483, 0.783389, 1 13:14:00.405 [planetinfo.record]: corona_shimmer = 0.424417 13:14:00.405 [planetinfo.record]: station_vector = 0.481 0.782 0.397 13:14:00.405 [planetinfo.record]: station = coriolis 13:14:05.263 [PLANETINFO OVER]: Done 13:14:05.265 [PLANETINFO LOGGING]: 4 45 13:14:06.266 [planetinfo.record]: seed = 240 158 27 99 13:14:06.266 [planetinfo.record]: coordinates = 158 251 13:14:06.266 [planetinfo.record]: government = 6; 13:14:06.266 [planetinfo.record]: economy = 3; 13:14:06.266 [planetinfo.record]: techlevel = 9; 13:14:06.266 [planetinfo.record]: population = 46; 13:14:06.266 [planetinfo.record]: productivity = 25760; 13:14:06.266 [planetinfo.record]: name = "Geison"; 13:14:06.266 [planetinfo.record]: inhabitant = "Human Colonial"; 13:14:06.266 [planetinfo.record]: inhabitants = "Human Colonials"; 13:14:06.266 [planetinfo.record]: description = "Geison is reasonably notable for its fabulous cuisine but plagued by evil tree fishs."; 13:14:06.300 [planetinfo.record]: air_color = 0.758092, 0.636475, 0.850711, 1; 13:14:06.300 [planetinfo.record]: cloud_alpha = 1.000000; 13:14:06.300 [planetinfo.record]: cloud_color = 0.554688, 0.468018, 0.492393, 1; 13:14:06.300 [planetinfo.record]: cloud_fraction = 0.210000; 13:14:06.300 [planetinfo.record]: land_color = 0.186119, 0.560547, 0.446463, 1; 13:14:06.300 [planetinfo.record]: land_fraction = 0.250000; 13:14:06.300 [planetinfo.record]: polar_cloud_color = 0.749609, 0.676405, 0.696994, 1; 13:14:06.300 [planetinfo.record]: polar_land_color = 0.786314, 0.943945, 0.895917, 1; 13:14:06.300 [planetinfo.record]: polar_sea_color = 0.883875, 0.923633, 0.86771, 1; 13:14:06.300 [planetinfo.record]: sea_color = 0.632183, 0.763672, 0.57872, 1; 13:14:06.300 [planetinfo.record]: rotation_speed = 0.002608 13:14:06.300 [planetinfo.record]: planet zpos = 336780.000000 13:14:06.300 [planetinfo.record]: sun_radius = 68484.378357 13:14:06.300 [planetinfo.record]: sun_vector = -0.246 -0.915 0.320 13:14:06.300 [planetinfo.record]: sun_distance = 748400 13:14:06.300 [planetinfo.record]: corona_flare = 0.014630 13:14:06.300 [planetinfo.record]: corona_hues = 0.819115 13:14:06.300 [planetinfo.record]: sun_color = 0.734366, 0.719496, 0.584722, 1 13:14:06.300 [planetinfo.record]: corona_shimmer = 0.468885 13:14:06.301 [planetinfo.record]: station_vector = -0.814 0.530 0.238 13:14:06.301 [planetinfo.record]: station = coriolis 13:14:11.128 [PLANETINFO OVER]: Done 13:14:11.131 [PLANETINFO LOGGING]: 4 46 13:14:12.131 [planetinfo.record]: seed = 136 96 245 93 13:14:12.131 [planetinfo.record]: coordinates = 96 255 13:14:12.131 [planetinfo.record]: government = 1; 13:14:12.131 [planetinfo.record]: economy = 7; 13:14:12.132 [planetinfo.record]: techlevel = 1; 13:14:12.132 [planetinfo.record]: population = 13; 13:14:12.132 [planetinfo.record]: productivity = 1560; 13:14:12.132 [planetinfo.record]: name = "Isrite"; 13:14:12.132 [planetinfo.record]: inhabitant = "Yellow Rodent"; 13:14:12.132 [planetinfo.record]: inhabitants = "Yellow Rodents"; 13:14:12.132 [planetinfo.record]: description = "This planet is noted for its exciting sit coms."; 13:14:12.143 [planetinfo.record]: air_color = 0.601803, 0.510285, 0.918352, 1; 13:14:12.143 [planetinfo.record]: cloud_alpha = 1.000000; 13:14:12.143 [planetinfo.record]: cloud_color = 0.5839, 0.201294, 0.757812, 1; 13:14:12.143 [planetinfo.record]: cloud_fraction = 0.580000; 13:14:12.143 [planetinfo.record]: land_color = 0.609375, 0.399902, 0.557007, 1; 13:14:12.143 [planetinfo.record]: land_fraction = 0.700000; 13:14:12.143 [planetinfo.record]: polar_cloud_color = 0.720387, 0.455003, 0.841016, 1; 13:14:12.143 [planetinfo.record]: polar_land_color = 0.939062, 0.858362, 0.918887, 1; 13:14:12.144 [planetinfo.record]: polar_sea_color = 0.918945, 0.896342, 0.807667, 1; 13:14:12.144 [planetinfo.record]: sea_color = 0.810547, 0.730798, 0.417938, 1; 13:14:12.144 [planetinfo.record]: rotation_speed = 0.004080 13:14:12.144 [planetinfo.record]: planet zpos = 873600.000000 13:14:12.144 [planetinfo.record]: sun_radius = 165557.666016 13:14:12.144 [planetinfo.record]: sun_vector = -0.581 -0.349 -0.735 13:14:12.144 [planetinfo.record]: sun_distance = 1248000 13:14:12.144 [planetinfo.record]: corona_flare = 0.014023 13:14:12.145 [planetinfo.record]: corona_hues = 0.590775 13:14:12.145 [planetinfo.record]: sun_color = 0.826895, 0.790198, 0.763284, 1 13:14:12.145 [planetinfo.record]: corona_shimmer = 0.369442 13:14:12.145 [planetinfo.record]: station_vector = 0.994 0.112 0.003 13:14:12.145 [planetinfo.record]: station = coriolis 13:14:16.798 [PLANETINFO OVER]: Done 13:14:16.800 [PLANETINFO LOGGING]: 4 47 13:14:17.801 [planetinfo.record]: seed = 228 152 211 211 13:14:17.801 [planetinfo.record]: coordinates = 152 124 13:14:17.801 [planetinfo.record]: government = 4; 13:14:17.801 [planetinfo.record]: economy = 4; 13:14:17.801 [planetinfo.record]: techlevel = 5; 13:14:17.802 [planetinfo.record]: population = 29; 13:14:17.802 [planetinfo.record]: productivity = 11136; 13:14:17.802 [planetinfo.record]: name = "Beesve"; 13:14:17.802 [planetinfo.record]: inhabitant = "Fat Insect"; 13:14:17.802 [planetinfo.record]: inhabitants = "Fat Insects"; 13:14:17.802 [planetinfo.record]: description = "The world Beesve is most well known for its exotic night life."; 13:14:17.816 [planetinfo.record]: air_color = 0.444255, 0.708176, 0.870873, 1; 13:14:17.816 [planetinfo.record]: cloud_alpha = 1.000000; 13:14:17.816 [planetinfo.record]: cloud_color = 0.0793076, 0.615234, 0.163046, 1; 13:14:17.816 [planetinfo.record]: cloud_fraction = 0.510000; 13:14:17.816 [planetinfo.record]: land_color = 0.66, 0.550994, 0.342891, 1; 13:14:17.816 [planetinfo.record]: land_fraction = 0.360000; 13:14:17.816 [planetinfo.record]: polar_cloud_color = 0.353909, 0.776855, 0.419995, 1; 13:14:17.816 [planetinfo.record]: polar_land_color = 0.934, 0.895435, 0.821811, 1; 13:14:17.816 [planetinfo.record]: polar_sea_color = 0.858537, 0.918164, 0.895804, 1; 13:14:17.816 [planetinfo.record]: sea_color = 0.605778, 0.818359, 0.738641, 1; 13:14:17.816 [planetinfo.record]: rotation_speed = 0.003752 13:14:17.817 [planetinfo.record]: planet zpos = 410960.000000 13:14:17.817 [planetinfo.record]: sun_radius = 118553.696289 13:14:17.817 [planetinfo.record]: sun_vector = -0.644 -0.761 -0.071 13:14:17.817 [planetinfo.record]: sun_distance = 747200 13:14:17.817 [planetinfo.record]: corona_flare = 0.089828 13:14:17.817 [planetinfo.record]: corona_hues = 0.881310 13:14:17.817 [planetinfo.record]: sun_color = 0.27288, 0.356707, 0.760223, 1 13:14:17.817 [planetinfo.record]: corona_shimmer = 0.404239 13:14:17.817 [planetinfo.record]: station_vector = 0.431 -0.809 0.400 13:14:17.817 [planetinfo.record]: station = coriolis 13:14:21.973 [PLANETINFO OVER]: Done 13:14:21.974 [PLANETINFO LOGGING]: 4 48 13:14:22.993 [planetinfo.record]: seed = 100 19 245 82 13:14:22.993 [planetinfo.record]: coordinates = 19 86 13:14:22.993 [planetinfo.record]: government = 4; 13:14:22.993 [planetinfo.record]: economy = 6; 13:14:22.993 [planetinfo.record]: techlevel = 6; 13:14:22.993 [planetinfo.record]: population = 35; 13:14:22.993 [planetinfo.record]: productivity = 8960; 13:14:22.993 [planetinfo.record]: name = "Entexe"; 13:14:22.993 [planetinfo.record]: inhabitant = "Yellow Furry Insect"; 13:14:22.993 [planetinfo.record]: inhabitants = "Yellow Furry Insects"; 13:14:22.993 [planetinfo.record]: description = "The world Entexe is scourged by deadly edible grubs."; 13:14:23.012 [planetinfo.record]: air_color = 0.591169, 0.514606, 0.911197, 1; 13:14:23.012 [planetinfo.record]: cloud_alpha = 1.000000; 13:14:23.012 [planetinfo.record]: cloud_color = 0.524832, 0.215721, 0.736328, 1; 13:14:23.012 [planetinfo.record]: cloud_fraction = 0.350000; 13:14:23.012 [planetinfo.record]: land_color = 0.541406, 0.573834, 0.66, 1; 13:14:23.012 [planetinfo.record]: land_fraction = 0.300000; 13:14:23.012 [planetinfo.record]: polar_cloud_color = 0.682104, 0.46398, 0.831348, 1; 13:14:23.013 [planetinfo.record]: polar_land_color = 0.892043, 0.903516, 0.934, 1; 13:14:23.013 [planetinfo.record]: polar_sea_color = 0.825201, 0.948047, 0.769362, 1; 13:14:23.013 [planetinfo.record]: sea_color = 0.250253, 0.519531, 0.127853, 1; 13:14:23.013 [planetinfo.record]: rotation_speed = 0.002657 13:14:23.013 [planetinfo.record]: planet zpos = 502050.000000 13:14:23.013 [planetinfo.record]: sun_radius = 82838.964996 13:14:23.013 [planetinfo.record]: sun_vector = -0.745 0.543 -0.388 13:14:23.013 [planetinfo.record]: sun_distance = 669400 13:14:23.013 [planetinfo.record]: corona_flare = 0.068263 13:14:23.013 [planetinfo.record]: corona_hues = 0.846733 13:14:23.013 [planetinfo.record]: sun_color = 0.738017, 0.745749, 0.746985, 1 13:14:23.013 [planetinfo.record]: corona_shimmer = 0.467493 13:14:23.013 [planetinfo.record]: station_vector = -0.280 0.719 0.636 13:14:23.013 [planetinfo.record]: station = coriolis 13:14:28.205 [PLANETINFO OVER]: Done 13:14:28.206 [PLANETINFO LOGGING]: 4 49 13:14:29.210 [planetinfo.record]: seed = 72 50 155 17 13:14:29.210 [planetinfo.record]: coordinates = 50 34 13:14:29.210 [planetinfo.record]: government = 1; 13:14:29.210 [planetinfo.record]: economy = 2; 13:14:29.210 [planetinfo.record]: techlevel = 8; 13:14:29.210 [planetinfo.record]: population = 36; 13:14:29.210 [planetinfo.record]: productivity = 11520; 13:14:29.210 [planetinfo.record]: name = "Atbiarxe"; 13:14:29.210 [planetinfo.record]: inhabitant = "Green Slimy Frog"; 13:14:29.210 [planetinfo.record]: inhabitants = "Green Slimy Frogs"; 13:14:29.210 [planetinfo.record]: description = "The planet Atbiarxe is cursed by deadly civil war."; 13:14:29.228 [planetinfo.record]: air_color = 0.641815, 0.497226, 0.941115, 1; 13:14:29.228 [planetinfo.record]: cloud_alpha = 1.000000; 13:14:29.228 [planetinfo.record]: cloud_color = 0.820902, 0.15168, 0.826172, 1; 13:14:29.228 [planetinfo.record]: cloud_fraction = 0.060000; 13:14:29.228 [planetinfo.record]: land_color = 0.656918, 0.66, 0.265547, 1; 13:14:29.228 [planetinfo.record]: land_fraction = 0.440000; 13:14:29.228 [planetinfo.record]: polar_cloud_color = 0.868302, 0.42695, 0.871777, 1; 13:14:29.228 [planetinfo.record]: polar_land_color = 0.93291, 0.934, 0.794447, 1; 13:14:29.228 [planetinfo.record]: polar_sea_color = 0.86001, 0.884616, 0.95, 1; 13:14:29.228 [planetinfo.record]: sea_color = 0.310547, 0.36235, 0.5, 1; 13:14:29.228 [planetinfo.record]: rotation_speed = 0.000992 13:14:29.228 [planetinfo.record]: planet zpos = 405860.000000 13:14:29.228 [planetinfo.record]: sun_radius = 58156.396484 13:14:29.228 [planetinfo.record]: sun_vector = 0.549 -0.344 0.762 13:14:29.228 [planetinfo.record]: sun_distance = 499520 13:14:29.228 [planetinfo.record]: corona_flare = 0.079878 13:14:29.228 [planetinfo.record]: corona_hues = 0.503059 13:14:29.228 [planetinfo.record]: sun_color = 0.650146, 0.567162, 0.31452, 1 13:14:29.229 [planetinfo.record]: corona_shimmer = 0.303057 13:14:29.229 [planetinfo.record]: station_vector = -0.938 -0.346 0.025 13:14:29.229 [planetinfo.record]: station = coriolis 13:14:34.250 [PLANETINFO OVER]: Done 13:14:34.252 [PLANETINFO LOGGING]: 4 50 13:14:35.253 [planetinfo.record]: seed = 240 34 69 52 13:14:35.253 [planetinfo.record]: coordinates = 34 170 13:14:35.253 [planetinfo.record]: government = 6; 13:14:35.253 [planetinfo.record]: economy = 2; 13:14:35.253 [planetinfo.record]: techlevel = 10; 13:14:35.253 [planetinfo.record]: population = 49; 13:14:35.253 [planetinfo.record]: productivity = 31360; 13:14:35.253 [planetinfo.record]: name = "Raleor"; 13:14:35.253 [planetinfo.record]: inhabitant = "Human Colonial"; 13:14:35.253 [planetinfo.record]: inhabitants = "Human Colonials"; 13:14:35.253 [planetinfo.record]: description = "The planet Raleor is scourged by killer edible Leilcearoids."; 13:14:35.255 [planetinfo.record]: air_color = 0.914987, 0.79343, 0.996398, 1; 13:14:35.256 [planetinfo.record]: cloud_alpha = 1.000000; 13:14:35.256 [planetinfo.record]: cloud_color = 0.992188, 0.98056, 0.98274, 1; 13:14:35.256 [planetinfo.record]: cloud_fraction = 0.270000; 13:14:35.256 [planetinfo.record]: land_color = 0.542969, 0.529249, 0.52388, 1; 13:14:35.256 [planetinfo.record]: land_fraction = 0.630000; 13:14:35.256 [planetinfo.record]: polar_cloud_color = 0.946484, 0.939552, 0.940852, 1; 13:14:35.256 [planetinfo.record]: polar_land_color = 0.945703, 0.939729, 0.937391, 1; 13:14:35.256 [planetinfo.record]: polar_sea_color = 0.932813, 0.917395, 0.871141, 1; 13:14:35.256 [planetinfo.record]: sea_color = 0.671875, 0.627455, 0.494196, 1; 13:14:35.256 [planetinfo.record]: rotation_speed = 0.002489 13:14:35.257 [planetinfo.record]: planet zpos = 464880.000000 13:14:35.257 [planetinfo.record]: sun_radius = 80079.178772 13:14:35.257 [planetinfo.record]: sun_vector = -0.254 0.786 0.563 13:14:35.257 [planetinfo.record]: sun_distance = 891020 13:14:35.257 [planetinfo.record]: corona_flare = 0.060760 13:14:35.257 [planetinfo.record]: corona_hues = 0.585564 13:14:35.257 [planetinfo.record]: sun_color = 0.553082, 0.687878, 0.799643, 1 13:14:35.257 [planetinfo.record]: corona_shimmer = 0.320156 13:14:35.257 [planetinfo.record]: station_vector = -0.494 0.639 0.590 13:14:35.257 [planetinfo.record]: station = coriolis 13:14:40.491 [PLANETINFO OVER]: Done 13:14:40.492 [PLANETINFO LOGGING]: 4 51 13:14:41.508 [planetinfo.record]: seed = 28 143 243 233 13:14:41.508 [planetinfo.record]: coordinates = 143 89 13:14:41.508 [planetinfo.record]: government = 3; 13:14:41.508 [planetinfo.record]: economy = 1; 13:14:41.508 [planetinfo.record]: techlevel = 11; 13:14:41.508 [planetinfo.record]: population = 49; 13:14:41.508 [planetinfo.record]: productivity = 24696; 13:14:41.508 [planetinfo.record]: name = "Esenma"; 13:14:41.508 [planetinfo.record]: inhabitant = "Small Insect"; 13:14:41.508 [planetinfo.record]: inhabitants = "Small Insects"; 13:14:41.508 [planetinfo.record]: description = "The world Esenma is most well known for its great dense forests."; 13:14:41.511 [planetinfo.record]: air_color = 0.533012, 0.536353, 0.871523, 1; 13:14:41.511 [planetinfo.record]: cloud_alpha = 1.000000; 13:14:41.512 [planetinfo.record]: cloud_color = 0.267609, 0.273071, 0.617188, 1; 13:14:41.512 [planetinfo.record]: cloud_fraction = 0.310000; 13:14:41.512 [planetinfo.record]: land_color = 0.634219, 0.66, 0.646102, 1; 13:14:41.512 [planetinfo.record]: land_fraction = 0.490000; 13:14:41.512 [planetinfo.record]: polar_cloud_color = 0.502413, 0.506715, 0.777734, 1; 13:14:41.512 [planetinfo.record]: polar_land_color = 0.924879, 0.934, 0.929083, 1; 13:14:41.512 [planetinfo.record]: polar_sea_color = 0.707579, 0.927734, 0.922574, 1; 13:14:41.512 [planetinfo.record]: sea_color = 0.0366974, 0.722656, 0.706579, 1; 13:14:41.512 [planetinfo.record]: rotation_speed = 0.004699 13:14:41.512 [planetinfo.record]: planet zpos = 578930.000000 13:14:41.512 [planetinfo.record]: sun_radius = 168052.048645 13:14:41.512 [planetinfo.record]: sun_vector = 0.516 -0.593 -0.618 13:14:41.512 [planetinfo.record]: sun_distance = 1052600 13:14:41.512 [planetinfo.record]: corona_flare = 0.051274 13:14:41.513 [planetinfo.record]: corona_hues = 0.906250 13:14:41.513 [planetinfo.record]: sun_color = 0.300071, 0.589616, 0.839508, 1 13:14:41.513 [planetinfo.record]: corona_shimmer = 0.408009 13:14:41.513 [planetinfo.record]: station_vector = -0.277 -0.936 0.220 13:14:41.513 [planetinfo.record]: station = dodecahedron 13:14:47.260 [PLANETINFO OVER]: Done 13:14:47.262 [PLANETINFO LOGGING]: 4 52 13:14:48.265 [planetinfo.record]: seed = 44 7 101 36 13:14:48.265 [planetinfo.record]: coordinates = 7 75 13:14:48.265 [planetinfo.record]: government = 5; 13:14:48.265 [planetinfo.record]: economy = 3; 13:14:48.265 [planetinfo.record]: techlevel = 10; 13:14:48.265 [planetinfo.record]: population = 49; 13:14:48.265 [planetinfo.record]: productivity = 24696; 13:14:48.265 [planetinfo.record]: name = "Zavexe"; 13:14:48.265 [planetinfo.record]: inhabitant = "Human Colonial"; 13:14:48.265 [planetinfo.record]: inhabitants = "Human Colonials"; 13:14:48.266 [planetinfo.record]: description = "Zavexe is mildly well known for its hoopy casinos."; 13:14:48.286 [planetinfo.record]: air_color = 0.529557, 0.546181, 0.869572, 1; 13:14:48.286 [planetinfo.record]: cloud_alpha = 1.000000; 13:14:48.286 [planetinfo.record]: cloud_color = 0.260292, 0.306914, 0.611328, 1; 13:14:48.286 [planetinfo.record]: cloud_fraction = 0.440000; 13:14:48.287 [planetinfo.record]: land_color = 0.53125, 0.442017, 0.498485, 1; 13:14:48.287 [planetinfo.record]: land_fraction = 0.670000; 13:14:48.287 [planetinfo.record]: polar_cloud_color = 0.496925, 0.53387, 0.775098, 1; 13:14:48.287 [planetinfo.record]: polar_land_color = 0.946875, 0.907114, 0.932275, 1; 13:14:48.287 [planetinfo.record]: polar_sea_color = 0.897926, 0.923828, 0.856165, 1; 13:14:48.287 [planetinfo.record]: sea_color = 0.67629, 0.761719, 0.538559, 1; 13:14:48.287 [planetinfo.record]: rotation_speed = 0.003572 13:14:48.287 [planetinfo.record]: planet zpos = 538580.000000 13:14:48.287 [planetinfo.record]: sun_radius = 95609.713593 13:14:48.287 [planetinfo.record]: sun_vector = -0.166 -0.477 -0.863 13:14:48.287 [planetinfo.record]: sun_distance = 884810 13:14:48.287 [planetinfo.record]: corona_flare = 0.001791 13:14:48.287 [planetinfo.record]: corona_hues = 0.930351 13:14:48.287 [planetinfo.record]: sun_color = 0.705756, 0.558258, 0.222727, 1 13:14:48.289 [planetinfo.record]: corona_shimmer = 0.847901 13:14:48.289 [planetinfo.record]: station_vector = 0.862 0.337 0.378 13:14:48.289 [planetinfo.record]: station = coriolis 13:14:53.755 [PLANETINFO OVER]: Done 13:14:53.756 [PLANETINFO LOGGING]: 4 53 13:14:54.760 [planetinfo.record]: seed = 96 61 91 86 13:14:54.760 [planetinfo.record]: coordinates = 61 162 13:14:54.760 [planetinfo.record]: government = 4; 13:14:54.760 [planetinfo.record]: economy = 2; 13:14:54.760 [planetinfo.record]: techlevel = 8; 13:14:54.760 [planetinfo.record]: population = 39; 13:14:54.761 [planetinfo.record]: productivity = 19968; 13:14:54.761 [planetinfo.record]: name = "Veveesve"; 13:14:54.761 [planetinfo.record]: inhabitant = "Human Colonial"; 13:14:54.761 [planetinfo.record]: inhabitants = "Human Colonials"; 13:14:54.761 [planetinfo.record]: description = "The world Veveesve is a dull world."; 13:14:54.764 [planetinfo.record]: air_color = 0.674434, 0.759302, 0.974285, 1; 13:14:54.764 [planetinfo.record]: cloud_alpha = 1.000000; 13:14:54.765 [planetinfo.record]: cloud_color = 0.625626, 0.876537, 0.925781, 1; 13:14:54.765 [planetinfo.record]: cloud_fraction = 0.130000; 13:14:54.765 [planetinfo.record]: land_color = 0.23204, 0.528052, 0.535156, 1; 13:14:54.765 [planetinfo.record]: land_fraction = 0.280000; 13:14:54.765 [planetinfo.record]: polar_cloud_color = 0.730864, 0.886129, 0.916602, 1; 13:14:54.765 [planetinfo.record]: polar_land_color = 0.812461, 0.943343, 0.946484, 1; 13:14:54.765 [planetinfo.record]: polar_sea_color = 0.925977, 0.865503, 0.829219, 1; 13:14:54.765 [planetinfo.record]: sea_color = 0.740234, 0.546863, 0.43084, 1; 13:14:54.765 [planetinfo.record]: rotation_speed = 0.004462 13:14:54.765 [planetinfo.record]: planet zpos = 573690.000000 13:14:54.765 [planetinfo.record]: sun_radius = 86612.262726 13:14:54.765 [planetinfo.record]: sun_vector = -0.082 0.582 -0.809 13:14:54.765 [planetinfo.record]: sun_distance = 970860 13:14:54.765 [planetinfo.record]: corona_flare = 0.016859 13:14:54.765 [planetinfo.record]: corona_hues = 0.948723 13:14:54.765 [planetinfo.record]: sun_color = 0.714908, 0.611955, 0.600285, 1 13:14:54.766 [planetinfo.record]: corona_shimmer = 0.564323 13:14:54.766 [planetinfo.record]: station_vector = 0.175 0.551 0.816 13:14:54.766 [planetinfo.record]: station = coriolis 13:15:00.167 [PLANETINFO OVER]: Done 13:15:00.168 [PLANETINFO LOGGING]: 4 54 13:15:01.170 [planetinfo.record]: seed = 24 86 213 85 13:15:01.171 [planetinfo.record]: coordinates = 86 201 13:15:01.171 [planetinfo.record]: government = 3; 13:15:01.171 [planetinfo.record]: economy = 1; 13:15:01.171 [planetinfo.record]: techlevel = 10; 13:15:01.171 [planetinfo.record]: population = 45; 13:15:01.171 [planetinfo.record]: productivity = 22680; 13:15:01.171 [planetinfo.record]: name = "Lalale"; 13:15:01.171 [planetinfo.record]: inhabitant = "Yellow Rodent"; 13:15:01.171 [planetinfo.record]: inhabitants = "Yellow Rodents"; 13:15:01.171 [planetinfo.record]: description = "This planet is reasonably famous for the Lalaleian evil Reoid."; 13:15:01.183 [planetinfo.record]: air_color = 0.603571, 0.879162, 0.970383, 1; 13:15:01.183 [planetinfo.record]: cloud_alpha = 1.000000; 13:15:01.184 [planetinfo.record]: cloud_color = 0.543366, 0.914062, 0.424896, 1; 13:15:01.184 [planetinfo.record]: cloud_fraction = 0.350000; 13:15:01.184 [planetinfo.record]: land_color = 0.66, 0.354452, 0.131484, 1; 13:15:01.184 [planetinfo.record]: land_fraction = 0.520000; 13:15:01.184 [planetinfo.record]: polar_cloud_color = 0.680336, 0.911328, 0.606514, 1; 13:15:01.184 [planetinfo.record]: polar_land_color = 0.934, 0.825901, 0.747018, 1; 13:15:01.184 [planetinfo.record]: polar_sea_color = 0.92679, 0.933789, 0.877798, 1; 13:15:01.184 [planetinfo.record]: sea_color = 0.642259, 0.662109, 0.503307, 1; 13:15:01.184 [planetinfo.record]: rotation_speed = 0.000950 13:15:01.184 [planetinfo.record]: planet zpos = 501840.000000 13:15:01.184 [planetinfo.record]: sun_radius = 108849.669800 13:15:01.184 [planetinfo.record]: sun_vector = -0.454 0.855 0.251 13:15:01.184 [planetinfo.record]: sun_distance = 794580 13:15:01.184 [planetinfo.record]: corona_flare = 0.079901 13:15:01.184 [planetinfo.record]: corona_hues = 0.773026 13:15:01.184 [planetinfo.record]: sun_color = 0.542775, 0.75717, 0.849753, 1 13:15:01.185 [planetinfo.record]: corona_shimmer = 0.449924 13:15:01.185 [planetinfo.record]: station_vector = 0.028 0.999 0.024 13:15:01.185 [planetinfo.record]: station = coriolis 13:15:06.033 [PLANETINFO OVER]: Done 13:15:06.034 [PLANETINFO LOGGING]: 4 55 13:15:07.036 [planetinfo.record]: seed = 20 237 83 132 13:15:07.036 [planetinfo.record]: coordinates = 237 33 13:15:07.036 [planetinfo.record]: government = 2; 13:15:07.036 [planetinfo.record]: economy = 1; 13:15:07.036 [planetinfo.record]: techlevel = 8; 13:15:07.036 [planetinfo.record]: population = 36; 13:15:07.036 [planetinfo.record]: productivity = 15552; 13:15:07.036 [planetinfo.record]: name = "Zaenza"; 13:15:07.036 [planetinfo.record]: inhabitant = "Human Colonial"; 13:15:07.036 [planetinfo.record]: inhabitants = "Human Colonials"; 13:15:07.036 [planetinfo.record]: description = "The world Zaenza is fabled for its ancient Zaenzaian Us banana plantations and its hoopy casinos."; 13:15:07.047 [planetinfo.record]: air_color = 0.638507, 0.850125, 0.952822, 1; 13:15:07.047 [planetinfo.record]: cloud_alpha = 1.000000; 13:15:07.047 [planetinfo.record]: cloud_color = 0.5275, 0.861328, 0.524872, 1; 13:15:07.047 [planetinfo.record]: cloud_fraction = 0.330000; 13:15:07.047 [planetinfo.record]: land_color = 0.528516, 0.66, 0.601448, 1; 13:15:07.047 [planetinfo.record]: land_fraction = 0.410000; 13:15:07.047 [planetinfo.record]: polar_cloud_color = 0.672592, 0.887598, 0.670899, 1; 13:15:07.047 [planetinfo.record]: polar_land_color = 0.887482, 0.934, 0.913285, 1; 13:15:07.047 [planetinfo.record]: polar_sea_color = 0.919922, 0.85265, 0.798643, 1; 13:15:07.048 [planetinfo.record]: sea_color = 0.800781, 0.566544, 0.378494, 1; 13:15:07.048 [planetinfo.record]: rotation_speed = 0.000604 13:15:07.048 [planetinfo.record]: planet zpos = 489240.000000 13:15:07.048 [planetinfo.record]: sun_radius = 101515.035553 13:15:07.048 [planetinfo.record]: sun_vector = -0.118 -0.520 -0.846 13:15:07.048 [planetinfo.record]: sun_distance = 733860 13:15:07.048 [planetinfo.record]: corona_flare = 0.035492 13:15:07.048 [planetinfo.record]: corona_hues = 0.636612 13:15:07.048 [planetinfo.record]: sun_color = 0.721344, 0.703661, 0.688456, 1 13:15:07.049 [planetinfo.record]: corona_shimmer = 0.311981 13:15:07.049 [planetinfo.record]: station_vector = -0.879 0.435 0.194 13:15:07.049 [planetinfo.record]: station = coriolis 13:15:11.320 [PLANETINFO OVER]: Done 13:15:11.321 [PLANETINFO LOGGING]: 4 56 13:15:12.323 [planetinfo.record]: seed = 180 27 21 179 13:15:12.323 [planetinfo.record]: coordinates = 27 4 13:15:12.323 [planetinfo.record]: government = 6; 13:15:12.323 [planetinfo.record]: economy = 4; 13:15:12.323 [planetinfo.record]: techlevel = 9; 13:15:12.323 [planetinfo.record]: population = 47; 13:15:12.323 [planetinfo.record]: productivity = 22560; 13:15:12.323 [planetinfo.record]: name = "Bebeleus"; 13:15:12.323 [planetinfo.record]: inhabitant = "Human Colonial"; 13:15:12.323 [planetinfo.record]: inhabitants = "Human Colonials"; 13:15:12.323 [planetinfo.record]: description = "This world is a tedious little planet."; 13:15:12.330 [planetinfo.record]: air_color = 0.73795, 0.740122, 0.958025, 1; 13:15:12.331 [planetinfo.record]: cloud_alpha = 1.000000; 13:15:12.331 [planetinfo.record]: cloud_color = 0.798164, 0.799395, 0.876953, 1; 13:15:12.331 [planetinfo.record]: cloud_fraction = 0.190000; 13:15:12.331 [planetinfo.record]: land_color = 0.191895, 0.511719, 0.316826, 1; 13:15:12.331 [planetinfo.record]: land_fraction = 0.380000; 13:15:12.331 [planetinfo.record]: polar_cloud_color = 0.844393, 0.845178, 0.894629, 1; 13:15:12.331 [planetinfo.record]: polar_land_color = 0.800574, 0.948828, 0.858486, 1; 13:15:12.331 [planetinfo.record]: polar_sea_color = 0.869089, 0.918097, 0.920508, 1; 13:15:12.331 [planetinfo.record]: sea_color = 0.617307, 0.786596, 0.794922, 1; 13:15:12.331 [planetinfo.record]: rotation_speed = 0.004577 13:15:12.331 [planetinfo.record]: planet zpos = 469430.000000 13:15:12.331 [planetinfo.record]: sun_radius = 93450.934448 13:15:12.331 [planetinfo.record]: sun_vector = -0.242 -0.752 -0.613 13:15:12.331 [planetinfo.record]: sun_distance = 686090 13:15:12.332 [planetinfo.record]: corona_flare = 0.080156 13:15:12.332 [planetinfo.record]: corona_hues = 0.973717 13:15:12.332 [planetinfo.record]: sun_color = 0.654553, 0.456733, 0.294772, 1 13:15:12.332 [planetinfo.record]: corona_shimmer = 0.278171 13:15:12.332 [planetinfo.record]: station_vector = 0.750 0.537 0.386 13:15:12.332 [planetinfo.record]: station = coriolis 13:15:16.384 [PLANETINFO OVER]: Done 13:15:16.386 [PLANETINFO LOGGING]: 4 57 13:15:17.387 [planetinfo.record]: seed = 56 40 91 157 13:15:17.387 [planetinfo.record]: coordinates = 40 161 13:15:17.387 [planetinfo.record]: government = 7; 13:15:17.387 [planetinfo.record]: economy = 1; 13:15:17.387 [planetinfo.record]: techlevel = 10; 13:15:17.387 [planetinfo.record]: population = 49; 13:15:17.388 [planetinfo.record]: productivity = 38808; 13:15:17.388 [planetinfo.record]: name = "Issodien"; 13:15:17.388 [planetinfo.record]: inhabitant = "Human Colonial"; 13:15:17.388 [planetinfo.record]: inhabitants = "Human Colonials"; 13:15:17.388 [planetinfo.record]: description = "This planet is a tedious little planet."; 13:15:17.389 [planetinfo.record]: air_color = 0.52717, 0.959977, 0.821021, 1; 13:15:17.390 [planetinfo.record]: cloud_alpha = 1.000000; 13:15:17.390 [planetinfo.record]: cloud_color = 0.882812, 0.248453, 0.217255, 1; 13:15:17.390 [planetinfo.record]: cloud_fraction = 0.300000; 13:15:17.390 [planetinfo.record]: land_color = 0.613281, 0.462357, 0.480043, 1; 13:15:17.390 [planetinfo.record]: land_fraction = 0.290000; 13:15:17.390 [planetinfo.record]: polar_cloud_color = 0.897266, 0.4943, 0.474482, 1; 13:15:17.390 [planetinfo.record]: polar_land_color = 0.938672, 0.880922, 0.887689, 1; 13:15:17.390 [planetinfo.record]: polar_sea_color = 0.897021, 0.934375, 0.886561, 1; 13:15:17.390 [planetinfo.record]: sea_color = 0.551308, 0.65625, 0.521924, 1; 13:15:17.390 [planetinfo.record]: rotation_speed = 0.002929 13:15:17.390 [planetinfo.record]: planet zpos = 803920.000000 13:15:17.391 [planetinfo.record]: sun_radius = 144036.358643 13:15:17.391 [planetinfo.record]: sun_vector = 0.649 0.271 -0.711 13:15:17.391 [planetinfo.record]: sun_distance = 1298640 13:15:17.391 [planetinfo.record]: corona_flare = 0.099406 13:15:17.391 [planetinfo.record]: corona_hues = 0.627647 13:15:17.391 [planetinfo.record]: sun_color = 0.681586, 0.685254, 0.715527, 1 13:15:17.391 [planetinfo.record]: corona_shimmer = 1.176378 13:15:17.391 [planetinfo.record]: station_vector = 0.553 0.708 0.439 13:15:17.391 [planetinfo.record]: station = coriolis 13:15:22.344 [PLANETINFO OVER]: Done 13:15:22.346 [PLANETINFO LOGGING]: 4 58 13:15:23.349 [planetinfo.record]: seed = 0 50 165 198 13:15:23.349 [planetinfo.record]: coordinates = 50 45 13:15:23.349 [planetinfo.record]: government = 0; 13:15:23.349 [planetinfo.record]: economy = 7; 13:15:23.349 [planetinfo.record]: techlevel = 2; 13:15:23.350 [planetinfo.record]: population = 16; 13:15:23.350 [planetinfo.record]: productivity = 1536; 13:15:23.350 [planetinfo.record]: name = "Biceri"; 13:15:23.350 [planetinfo.record]: inhabitant = "Fierce Frog"; 13:15:23.350 [planetinfo.record]: inhabitants = "Fierce Frogs"; 13:15:23.350 [planetinfo.record]: description = "This world is very notable for its inhabitants’ ingrained shyness."; 13:15:23.354 [planetinfo.record]: air_color = 0.620501, 0.900047, 0.942416, 1; 13:15:23.354 [planetinfo.record]: cloud_alpha = 1.000000; 13:15:23.354 [planetinfo.record]: cloud_color = 0.686496, 0.830078, 0.476646, 1; 13:15:23.354 [planetinfo.record]: cloud_fraction = 0.410000; 13:15:23.354 [planetinfo.record]: land_color = 0.637244, 0.65625, 0.622925, 1; 13:15:23.354 [planetinfo.record]: land_fraction = 0.320000; 13:15:23.354 [planetinfo.record]: polar_cloud_color = 0.779099, 0.873535, 0.641076, 1; 13:15:23.354 [planetinfo.record]: polar_land_color = 0.92761, 0.934375, 0.922513, 1; 13:15:23.355 [planetinfo.record]: polar_sea_color = 0.785541, 0.947461, 0.818431, 1; 13:15:23.355 [planetinfo.record]: sea_color = 0.166237, 0.525391, 0.23919, 1; 13:15:23.355 [planetinfo.record]: rotation_speed = 0.004434 13:15:23.355 [planetinfo.record]: planet zpos = 440200.000000 13:15:23.355 [planetinfo.record]: sun_radius = 105053.955688 13:15:23.355 [planetinfo.record]: sun_vector = 0.169 -0.071 -0.983 13:15:23.355 [planetinfo.record]: sun_distance = 748340 13:15:23.355 [planetinfo.record]: corona_flare = 0.090375 13:15:23.355 [planetinfo.record]: corona_hues = 0.587433 13:15:23.355 [planetinfo.record]: sun_color = 0.733347, 0.583989, 0.545363, 1 13:15:23.355 [planetinfo.record]: corona_shimmer = 0.464834 13:15:23.355 [planetinfo.record]: station_vector = 0.525 0.608 0.596 13:15:23.355 [planetinfo.record]: station = coriolis 13:15:29.480 [PLANETINFO OVER]: Done 13:15:29.481 [PLANETINFO LOGGING]: 4 59 13:15:30.485 [planetinfo.record]: seed = 204 10 243 78 13:15:30.485 [planetinfo.record]: coordinates = 10 30 13:15:30.485 [planetinfo.record]: government = 1; 13:15:30.485 [planetinfo.record]: economy = 6; 13:15:30.485 [planetinfo.record]: techlevel = 4; 13:15:30.485 [planetinfo.record]: population = 24; 13:15:30.485 [planetinfo.record]: productivity = 3840; 13:15:30.485 [planetinfo.record]: name = "Reedated"; 13:15:30.486 [planetinfo.record]: inhabitant = "Yellow Fat Feline"; 13:15:30.486 [planetinfo.record]: inhabitants = "Yellow Fat Felines"; 13:15:30.486 [planetinfo.record]: description = "The planet Reedated is reasonably fabled for its exciting Reedatedian evil brandy and its fabulous lethal Ildi brew."; 13:15:30.500 [planetinfo.record]: air_color = 0.479311, 0.624472, 0.872824, 1; 13:15:30.500 [planetinfo.record]: cloud_alpha = 1.000000; 13:15:30.500 [planetinfo.record]: cloud_color = 0.152847, 0.621094, 0.577196, 1; 13:15:30.500 [planetinfo.record]: cloud_fraction = 0.170000; 13:15:30.500 [planetinfo.record]: land_color = 0.655952, 0.141797, 0.66, 1; 13:15:30.500 [planetinfo.record]: land_fraction = 0.650000; 13:15:30.500 [planetinfo.record]: polar_cloud_color = 0.412202, 0.779492, 0.745059, 1; 13:15:30.500 [planetinfo.record]: polar_land_color = 0.932568, 0.750666, 0.934, 1; 13:15:30.501 [planetinfo.record]: polar_sea_color = 0.943164, 0.886611, 0.886611, 1; 13:15:30.501 [planetinfo.record]: sea_color = 0.568359, 0.432042, 0.432042, 1; 13:15:30.501 [planetinfo.record]: rotation_speed = 0.001482 13:15:30.501 [planetinfo.record]: planet zpos = 769200.000000 13:15:30.501 [planetinfo.record]: sun_radius = 205270.429993 13:15:30.502 [planetinfo.record]: sun_vector = -0.646 0.613 -0.456 13:15:30.502 [planetinfo.record]: sun_distance = 1474300 13:15:30.502 [planetinfo.record]: corona_flare = 0.075438 13:15:30.502 [planetinfo.record]: corona_hues = 0.757744 13:15:30.502 [planetinfo.record]: sun_color = 0.704868, 0.589346, 0.428378, 1 13:15:30.502 [planetinfo.record]: corona_shimmer = 0.417400 13:15:30.502 [planetinfo.record]: station_vector = -0.206 0.978 0.019 13:15:30.502 [planetinfo.record]: station = coriolis 13:15:34.822 [PLANETINFO OVER]: Done 13:15:34.823 [PLANETINFO LOGGING]: 4 60 13:15:35.826 [planetinfo.record]: seed = 252 152 5 227 13:15:35.826 [planetinfo.record]: coordinates = 152 209 13:15:35.826 [planetinfo.record]: government = 7; 13:15:35.826 [planetinfo.record]: economy = 1; 13:15:35.826 [planetinfo.record]: techlevel = 10; 13:15:35.826 [planetinfo.record]: population = 49; 13:15:35.826 [planetinfo.record]: productivity = 38808; 13:15:35.827 [planetinfo.record]: name = "Gediesqu"; 13:15:35.827 [planetinfo.record]: inhabitant = "Human Colonial"; 13:15:35.827 [planetinfo.record]: inhabitants = "Human Colonials"; 13:15:35.827 [planetinfo.record]: description = "The world Gediesqu is most famous for the Gediesquian spotted wolf."; 13:15:35.861 [planetinfo.record]: air_color = 0.444789, 0.498669, 0.928758, 1; 13:15:35.861 [planetinfo.record]: cloud_alpha = 1.000000; 13:15:35.861 [planetinfo.record]: cloud_color = 0.0277405, 0.271601, 0.789062, 1; 13:15:35.861 [planetinfo.record]: cloud_fraction = 0.420000; 13:15:35.862 [planetinfo.record]: land_color = 0.31456, 0.519531, 0.109589, 1; 13:15:35.862 [planetinfo.record]: land_fraction = 0.510000; 13:15:35.862 [planetinfo.record]: polar_cloud_color = 0.339443, 0.504607, 0.855078, 1; 13:15:35.862 [planetinfo.record]: polar_land_color = 0.854538, 0.948047, 0.76103, 1; 13:15:35.862 [planetinfo.record]: polar_sea_color = 0.932043, 0.937305, 0.885497, 1; 13:15:35.862 [planetinfo.record]: sea_color = 0.612875, 0.626953, 0.488338, 1; 13:15:35.862 [planetinfo.record]: rotation_speed = 0.000556 13:15:35.862 [planetinfo.record]: planet zpos = 410960.000000 13:15:35.862 [planetinfo.record]: sun_radius = 109962.196045 13:15:35.862 [planetinfo.record]: sun_vector = -0.375 -0.860 -0.346 13:15:35.862 [planetinfo.record]: sun_distance = 709840 13:15:35.862 [planetinfo.record]: corona_flare = 0.024310 13:15:35.862 [planetinfo.record]: corona_hues = 0.669083 13:15:35.862 [planetinfo.record]: sun_color = 0.200769, 0.641142, 0.740408, 1 13:15:35.863 [planetinfo.record]: corona_shimmer = 0.382290 13:15:35.863 [planetinfo.record]: station_vector = -0.153 0.982 0.108 13:15:35.863 [planetinfo.record]: station = coriolis 13:15:40.772 [PLANETINFO OVER]: Done 13:15:40.772 [PLANETINFO LOGGING]: 4 61 13:15:41.776 [planetinfo.record]: seed = 208 250 155 18 13:15:41.776 [planetinfo.record]: coordinates = 250 201 13:15:41.776 [planetinfo.record]: government = 2; 13:15:41.776 [planetinfo.record]: economy = 1; 13:15:41.776 [planetinfo.record]: techlevel = 9; 13:15:41.776 [planetinfo.record]: population = 40; 13:15:41.776 [planetinfo.record]: productivity = 17280; 13:15:41.776 [planetinfo.record]: name = "Entizare"; 13:15:41.776 [planetinfo.record]: inhabitant = "Green Bony Humanoid"; 13:15:41.776 [planetinfo.record]: inhabitants = "Green Bony Humanoids"; 13:15:41.776 [planetinfo.record]: description = "Entizare is very fabled for its unusual oceans."; 13:15:41.778 [planetinfo.record]: air_color = 0.670686, 0.842256, 0.800563, 1; 13:15:41.778 [planetinfo.record]: cloud_alpha = 1.000000; 13:15:41.778 [planetinfo.record]: cloud_color = 0.529297, 0.524839, 0.523094, 1; 13:15:41.778 [planetinfo.record]: cloud_fraction = 0.390000; 13:15:41.778 [planetinfo.record]: land_color = 0.448594, 0.640181, 0.66, 1; 13:15:41.778 [planetinfo.record]: land_fraction = 0.370000; 13:15:41.778 [planetinfo.record]: polar_cloud_color = 0.738184, 0.734298, 0.732777, 1; 13:15:41.778 [planetinfo.record]: polar_land_color = 0.859207, 0.926988, 0.934, 1; 13:15:41.779 [planetinfo.record]: polar_sea_color = 0.941602, 0.940862, 0.894246, 1; 13:15:41.779 [planetinfo.record]: sea_color = 0.583984, 0.582149, 0.466503, 1; 13:15:41.779 [planetinfo.record]: rotation_speed = 0.001335 13:15:41.779 [planetinfo.record]: planet zpos = 500920.000000 13:15:41.779 [planetinfo.record]: sun_radius = 105787.837219 13:15:41.779 [planetinfo.record]: sun_vector = -0.480 0.860 -0.172 13:15:41.779 [planetinfo.record]: sun_distance = 715600 13:15:41.779 [planetinfo.record]: corona_flare = 0.012402 13:15:41.779 [planetinfo.record]: corona_hues = 0.555679 13:15:41.779 [planetinfo.record]: sun_color = 0.749039, 0.620885, 0.44341, 1 13:15:41.779 [planetinfo.record]: corona_shimmer = 0.523058 13:15:41.779 [planetinfo.record]: station_vector = -0.714 -0.620 0.326 13:15:41.779 [planetinfo.record]: station = coriolis 13:15:46.077 [PLANETINFO OVER]: Done 13:15:46.078 [PLANETINFO LOGGING]: 4 62 13:15:47.081 [planetinfo.record]: seed = 168 206 181 138 13:15:47.081 [planetinfo.record]: coordinates = 206 228 13:15:47.081 [planetinfo.record]: government = 5; 13:15:47.081 [planetinfo.record]: economy = 4; 13:15:47.081 [planetinfo.record]: techlevel = 8; 13:15:47.081 [planetinfo.record]: population = 42; 13:15:47.081 [planetinfo.record]: productivity = 18144; 13:15:47.081 [planetinfo.record]: name = "Arriti"; 13:15:47.081 [planetinfo.record]: inhabitant = "Small Black Horned Bird"; 13:15:47.081 [planetinfo.record]: inhabitants = "Small Black Horned Birds"; 13:15:47.081 [planetinfo.record]: description = "The planet Arriti is most famous for its pink oceans."; 13:15:47.090 [planetinfo.record]: air_color = 0.558094, 0.527944, 0.887783, 1; 13:15:47.091 [planetinfo.record]: cloud_alpha = 1.000000; 13:15:47.091 [planetinfo.record]: cloud_color = 0.364146, 0.254959, 0.666016, 1; 13:15:47.091 [planetinfo.record]: cloud_fraction = 0.290000; 13:15:47.091 [planetinfo.record]: land_color = 0.0309375, 0.468333, 0.66, 1; 13:15:47.091 [planetinfo.record]: land_fraction = 0.650000; 13:15:47.091 [planetinfo.record]: polar_cloud_color = 0.573166, 0.491226, 0.799707, 1; 13:15:47.091 [planetinfo.record]: polar_land_color = 0.711445, 0.86619, 0.934, 1; 13:15:47.091 [planetinfo.record]: polar_sea_color = 0.91, 0.8645, 0.8918, 1; 13:15:47.091 [planetinfo.record]: sea_color = 0.9, 0.72, 0.828, 1; 13:15:47.091 [planetinfo.record]: rotation_speed = 0.002868 13:15:47.091 [planetinfo.record]: planet zpos = 725660.000000 13:15:47.091 [planetinfo.record]: sun_radius = 135447.993164 13:15:47.091 [planetinfo.record]: sun_vector = 0.691 -0.485 -0.535 13:15:47.092 [planetinfo.record]: sun_distance = 1228040 13:15:47.092 [planetinfo.record]: corona_flare = 0.084772 13:15:47.092 [planetinfo.record]: corona_hues = 0.697678 13:15:47.098 [planetinfo.record]: sun_color = 0.727646, 0.295966, 0.253767, 1 13:15:47.098 [planetinfo.record]: corona_shimmer = 0.356868 13:15:47.098 [planetinfo.record]: station_vector = -0.609 -0.660 0.440 13:15:47.098 [planetinfo.record]: station = coriolis 13:15:52.056 [PLANETINFO OVER]: Done 13:15:52.058 [PLANETINFO LOGGING]: 4 63 13:15:53.063 [planetinfo.record]: seed = 68 96 211 53 13:15:53.063 [planetinfo.record]: coordinates = 96 151 13:15:53.063 [planetinfo.record]: government = 0; 13:15:53.063 [planetinfo.record]: economy = 7; 13:15:53.063 [planetinfo.record]: techlevel = 0; 13:15:53.064 [planetinfo.record]: population = 8; 13:15:53.064 [planetinfo.record]: productivity = 768; 13:15:53.064 [planetinfo.record]: name = "Ladigeso"; 13:15:53.064 [planetinfo.record]: inhabitant = "Red Rodent"; 13:15:53.064 [planetinfo.record]: inhabitants = "Red Rodents"; 13:15:53.064 [planetinfo.record]: description = "The world Ladigeso is a dull place."; 13:15:53.083 [planetinfo.record]: air_color = 0.751605, 0.753533, 0.946969, 1; 13:15:53.083 [planetinfo.record]: cloud_alpha = 1.000000; 13:15:53.083 [planetinfo.record]: cloud_color = 0.827271, 0.827528, 0.84375, 1; 13:15:53.083 [planetinfo.record]: cloud_fraction = 0.350000; 13:15:53.083 [planetinfo.record]: land_color = 0.66, 0.180308, 0.0696094, 1; 13:15:53.083 [planetinfo.record]: land_fraction = 0.300000; 13:15:53.083 [planetinfo.record]: polar_cloud_color = 0.868949, 0.869117, 0.879687, 1; 13:15:53.083 [planetinfo.record]: polar_land_color = 0.934, 0.764291, 0.725127, 1; 13:15:53.083 [planetinfo.record]: polar_sea_color = 0.947852, 0.887685, 0.938451, 1; 13:15:53.083 [planetinfo.record]: sea_color = 0.521484, 0.389076, 0.500796, 1; 13:15:53.083 [planetinfo.record]: rotation_speed = 0.002421 13:15:53.083 [planetinfo.record]: planet zpos = 586880.000000 13:15:53.083 [planetinfo.record]: sun_radius = 81664.555664 13:15:53.083 [planetinfo.record]: sun_vector = 0.309 -0.545 -0.780 13:15:53.083 [planetinfo.record]: sun_distance = 796480 13:15:53.083 [planetinfo.record]: corona_flare = 0.091736 13:15:53.083 [planetinfo.record]: corona_hues = 0.677582 13:15:53.083 [planetinfo.record]: sun_color = 0.713892, 0.712978, 0.69555, 1 13:15:53.084 [planetinfo.record]: corona_shimmer = 0.298885 13:15:53.084 [planetinfo.record]: station_vector = 0.781 0.047 0.623 13:15:53.084 [planetinfo.record]: station = coriolis 13:15:58.504 [PLANETINFO OVER]: Done 13:15:58.505 [PLANETINFO LOGGING]: 4 64 13:15:59.512 [planetinfo.record]: seed = 4 39 53 24 13:15:59.512 [planetinfo.record]: coordinates = 39 195 13:15:59.512 [planetinfo.record]: government = 0; 13:15:59.512 [planetinfo.record]: economy = 3; 13:15:59.512 [planetinfo.record]: techlevel = 7; 13:15:59.512 [planetinfo.record]: population = 32; 13:15:59.512 [planetinfo.record]: productivity = 7168; 13:15:59.512 [planetinfo.record]: name = "Edxexe"; 13:15:59.512 [planetinfo.record]: inhabitant = "Human Colonial"; 13:15:59.512 [planetinfo.record]: inhabitants = "Human Colonials"; 13:15:59.512 [planetinfo.record]: description = "The planet Edxexe is cursed by dreadful civil war."; 13:15:59.520 [planetinfo.record]: air_color = 0.78847, 0.521296, 0.956074, 1; 13:15:59.520 [planetinfo.record]: cloud_alpha = 1.000000; 13:15:59.520 [planetinfo.record]: cloud_color = 0.871094, 0.204163, 0.29795, 1; 13:15:59.520 [planetinfo.record]: cloud_fraction = 0.460000; 13:15:59.520 [planetinfo.record]: land_color = 0.109456, 0.576172, 0.105782, 1; 13:15:59.520 [planetinfo.record]: land_fraction = 0.390000; 13:15:59.520 [planetinfo.record]: polar_cloud_color = 0.891992, 0.46516, 0.525183, 1; 13:15:59.521 [planetinfo.record]: polar_land_color = 0.751544, 0.942383, 0.750041, 1; 13:15:59.521 [planetinfo.record]: polar_sea_color = 0.945117, 0.889001, 0.899523, 1; 13:15:59.521 [planetinfo.record]: sea_color = 0.548828, 0.418481, 0.442921, 1; 13:15:59.521 [planetinfo.record]: rotation_speed = 0.001384 13:15:59.521 [planetinfo.record]: planet zpos = 539330.000000 13:15:59.521 [planetinfo.record]: sun_radius = 92199.831696 13:15:59.522 [planetinfo.record]: sun_vector = 0.938 -0.238 -0.252 13:15:59.522 [planetinfo.record]: sun_distance = 1127690 13:15:59.522 [planetinfo.record]: corona_flare = 0.040137 13:15:59.522 [planetinfo.record]: corona_hues = 0.526764 13:15:59.522 [planetinfo.record]: sun_color = 0.666315, 0.661583, 0.634486, 1 13:15:59.522 [planetinfo.record]: corona_shimmer = 0.277446 13:15:59.522 [planetinfo.record]: station_vector = -0.069 -0.382 0.922 13:15:59.522 [planetinfo.record]: station = coriolis 13:16:04.638 [PLANETINFO OVER]: Done 13:16:04.639 [PLANETINFO LOGGING]: 4 65 13:16:05.659 [planetinfo.record]: seed = 40 93 27 162 13:16:05.659 [planetinfo.record]: coordinates = 93 66 13:16:05.659 [planetinfo.record]: government = 5; 13:16:05.659 [planetinfo.record]: economy = 2; 13:16:05.659 [planetinfo.record]: techlevel = 9; 13:16:05.659 [planetinfo.record]: population = 44; 13:16:05.659 [planetinfo.record]: productivity = 25344; 13:16:05.659 [planetinfo.record]: name = "Xele"; 13:16:05.659 [planetinfo.record]: inhabitant = "Human Colonial"; 13:16:05.659 [planetinfo.record]: inhabitants = "Human Colonials"; 13:16:05.659 [planetinfo.record]: description = "This world is mildly famous for its pink oceans and mud hockey."; 13:16:05.676 [planetinfo.record]: air_color = 0.52934, 0.583323, 0.854613, 1; 13:16:05.676 [planetinfo.record]: cloud_alpha = 1.000000; 13:16:05.676 [planetinfo.record]: cloud_color = 0.258865, 0.40783, 0.566406, 1; 13:16:05.676 [planetinfo.record]: cloud_fraction = 0.290000; 13:16:05.676 [planetinfo.record]: land_color = 0.613281, 0.485415, 0.479126, 1; 13:16:05.676 [planetinfo.record]: land_fraction = 0.390000; 13:16:05.676 [planetinfo.record]: polar_cloud_color = 0.498709, 0.622793, 0.754883, 1; 13:16:05.676 [planetinfo.record]: polar_land_color = 0.938672, 0.889745, 0.887338, 1; 13:16:05.676 [planetinfo.record]: polar_sea_color = 0.915, 0.852597, 0.844088, 1; 13:16:05.676 [planetinfo.record]: sea_color = 0.85, 0.61812, 0.5865, 1; 13:16:05.676 [planetinfo.record]: rotation_speed = 0.004814 13:16:05.676 [planetinfo.record]: planet zpos = 342100.000000 13:16:05.676 [planetinfo.record]: sun_radius = 63490.202026 13:16:05.676 [planetinfo.record]: sun_vector = -0.090 0.935 -0.342 13:16:05.676 [planetinfo.record]: sun_distance = 615780 13:16:05.676 [planetinfo.record]: corona_flare = 0.022076 13:16:05.676 [planetinfo.record]: corona_hues = 0.758736 13:16:05.676 [planetinfo.record]: sun_color = 0.197009, 0.44066, 0.779721, 1 13:16:05.676 [planetinfo.record]: corona_shimmer = 0.316455 13:16:05.676 [planetinfo.record]: station_vector = -0.208 0.857 0.472 13:16:05.676 [planetinfo.record]: station = coriolis 13:16:09.725 [PLANETINFO OVER]: Done 13:16:09.726 [PLANETINFO LOGGING]: 4 66 13:16:10.729 [planetinfo.record]: seed = 16 36 5 166 13:16:10.729 [planetinfo.record]: coordinates = 36 64 13:16:10.729 [planetinfo.record]: government = 2; 13:16:10.729 [planetinfo.record]: economy = 0; 13:16:10.729 [planetinfo.record]: techlevel = 8; 13:16:10.729 [planetinfo.record]: population = 35; 13:16:10.729 [planetinfo.record]: productivity = 16800; 13:16:10.729 [planetinfo.record]: name = "Biarra"; 13:16:10.730 [planetinfo.record]: inhabitant = "Human Colonial"; 13:16:10.730 [planetinfo.record]: inhabitants = "Human Colonials"; 13:16:10.730 [planetinfo.record]: description = "This planet is a tedious little planet."; 13:16:10.739 [planetinfo.record]: air_color = 0.712066, 0.79272, 0.930709, 1; 13:16:10.746 [planetinfo.record]: cloud_alpha = 1.000000; 13:16:10.746 [planetinfo.record]: cloud_color = 0.711082, 0.794922, 0.787062, 1; 13:16:10.746 [planetinfo.record]: cloud_fraction = 0.500000; 13:16:10.746 [planetinfo.record]: land_color = 0.391875, 0.66, 0.421201, 1; 13:16:10.746 [planetinfo.record]: land_fraction = 0.340000; 13:16:10.746 [planetinfo.record]: polar_cloud_color = 0.801176, 0.857715, 0.852414, 1; 13:16:10.746 [planetinfo.record]: polar_land_color = 0.839141, 0.934, 0.849516, 1; 13:16:10.747 [planetinfo.record]: polar_sea_color = 0.939844, 0.898152, 0.881838, 1; 13:16:10.747 [planetinfo.record]: sea_color = 0.601562, 0.49482, 0.453052, 1; 13:16:10.747 [planetinfo.record]: rotation_speed = 0.001292 13:16:10.747 [planetinfo.record]: planet zpos = 482680.000000 13:16:10.747 [planetinfo.record]: sun_radius = 81503.671875 13:16:10.747 [planetinfo.record]: sun_vector = -0.535 0.729 0.426 13:16:10.747 [planetinfo.record]: sun_distance = 921480 13:16:10.747 [planetinfo.record]: corona_flare = 0.059027 13:16:10.747 [planetinfo.record]: corona_hues = 0.557106 13:16:10.747 [planetinfo.record]: sun_color = 0.637911, 0.69209, 0.781927, 1 13:16:10.747 [planetinfo.record]: corona_shimmer = 0.507213 13:16:10.747 [planetinfo.record]: station_vector = -0.045 -0.229 0.972 13:16:10.747 [planetinfo.record]: station = coriolis 13:16:15.916 [PLANETINFO OVER]: Done 13:16:15.917 [PLANETINFO LOGGING]: 4 67 13:16:16.919 [planetinfo.record]: seed = 124 133 243 100 13:16:16.919 [planetinfo.record]: coordinates = 133 212 13:16:16.919 [planetinfo.record]: government = 7; 13:16:16.919 [planetinfo.record]: economy = 4; 13:16:16.919 [planetinfo.record]: techlevel = 8; 13:16:16.919 [planetinfo.record]: population = 44; 13:16:16.919 [planetinfo.record]: productivity = 23232; 13:16:16.919 [planetinfo.record]: name = "Zaonesdi"; 13:16:16.919 [planetinfo.record]: inhabitant = "Fierce Blue Bug-Eyed Frog"; 13:16:16.920 [planetinfo.record]: inhabitants = "Fierce Blue Bug-Eyed Frogs"; 13:16:16.920 [planetinfo.record]: description = "This world is plagued by frequent solar activity."; 13:16:16.944 [planetinfo.record]: air_color = 0.525949, 0.468097, 0.939164, 1; 13:16:16.944 [planetinfo.record]: cloud_alpha = 1.000000; 13:16:16.944 [planetinfo.record]: cloud_color = 0.36149, 0.0769043, 0.820312, 1; 13:16:16.944 [planetinfo.record]: cloud_fraction = 0.280000; 13:16:16.944 [planetinfo.record]: land_color = 0.543984, 0.66, 0.613775, 1; 13:16:16.944 [planetinfo.record]: land_fraction = 0.660000; 13:16:16.944 [planetinfo.record]: polar_cloud_color = 0.565307, 0.376854, 0.869141, 1; 13:16:16.944 [planetinfo.record]: polar_land_color = 0.892955, 0.934, 0.917646, 1; 13:16:16.944 [planetinfo.record]: polar_sea_color = 0.921875, 0.885977, 0.817444, 1; 13:16:16.944 [planetinfo.record]: sea_color = 0.78125, 0.659561, 0.427246, 1; 13:16:16.944 [planetinfo.record]: rotation_speed = 0.003386 13:16:16.944 [planetinfo.record]: planet zpos = 516490.000000 13:16:16.944 [planetinfo.record]: sun_radius = 76351.243896 13:16:16.945 [planetinfo.record]: sun_vector = -0.055 0.765 -0.642 13:16:16.945 [planetinfo.record]: sun_distance = 834330 13:16:16.945 [planetinfo.record]: corona_flare = 0.024104 13:16:16.945 [planetinfo.record]: corona_hues = 0.835739 13:16:16.945 [planetinfo.record]: sun_color = 0.196818, 0.387293, 0.771521, 1 13:16:16.945 [planetinfo.record]: corona_shimmer = 0.297399 13:16:16.945 [planetinfo.record]: station_vector = -0.902 -0.353 0.249 13:16:16.945 [planetinfo.record]: station = coriolis 13:16:21.944 [PLANETINFO OVER]: Done 13:16:21.946 [PLANETINFO LOGGING]: 4 68 13:16:22.947 [planetinfo.record]: seed = 204 205 165 54 13:16:22.947 [planetinfo.record]: coordinates = 205 169 13:16:22.947 [planetinfo.record]: government = 1; 13:16:22.947 [planetinfo.record]: economy = 3; 13:16:22.947 [planetinfo.record]: techlevel = 6; 13:16:22.947 [planetinfo.record]: population = 29; 13:16:22.947 [planetinfo.record]: productivity = 8120; 13:16:22.947 [planetinfo.record]: name = "Vereen"; 13:16:22.947 [planetinfo.record]: inhabitant = "Red Fat Feline"; 13:16:22.947 [planetinfo.record]: inhabitants = "Red Fat Felines"; 13:16:22.947 [planetinfo.record]: description = "Vereen is a revolting little planet."; 13:16:22.960 [planetinfo.record]: air_color = 0.620835, 0.866454, 0.961928, 1; 13:16:22.961 [planetinfo.record]: cloud_alpha = 1.000000; 13:16:22.961 [planetinfo.record]: cloud_color = 0.536897, 0.888672, 0.475578, 1; 13:16:22.961 [planetinfo.record]: cloud_fraction = 0.100000; 13:16:22.961 [planetinfo.record]: land_color = 0.0587769, 0.626953, 0.107605, 1; 13:16:22.967 [planetinfo.record]: land_fraction = 0.280000; 13:16:22.967 [planetinfo.record]: polar_cloud_color = 0.677265, 0.899902, 0.638456, 1; 13:16:22.968 [planetinfo.record]: polar_land_color = 0.724947, 0.937305, 0.743196, 1; 13:16:22.968 [planetinfo.record]: polar_sea_color = 0.888258, 0.924219, 0.870065, 1; 13:16:22.968 [planetinfo.record]: sea_color = 0.639867, 0.757812, 0.5802, 1; 13:16:22.968 [planetinfo.record]: rotation_speed = 0.002360 13:16:22.968 [planetinfo.record]: planet zpos = 501270.000000 13:16:22.968 [planetinfo.record]: sun_radius = 124133.330841 13:16:22.968 [planetinfo.record]: sun_vector = 0.359 -0.763 0.537 13:16:22.968 [planetinfo.record]: sun_distance = 1093680 13:16:22.968 [planetinfo.record]: corona_flare = 0.084407 13:16:22.968 [planetinfo.record]: corona_hues = 0.750168 13:16:22.968 [planetinfo.record]: sun_color = 0.717783, 0.734847, 0.746985, 1 13:16:22.968 [planetinfo.record]: corona_shimmer = 0.690376 13:16:22.968 [planetinfo.record]: station_vector = -0.614 -0.630 0.474 13:16:22.968 [planetinfo.record]: station = coriolis 13:16:28.192 [PLANETINFO OVER]: Done 13:16:28.194 [PLANETINFO LOGGING]: 4 69 13:16:29.209 [planetinfo.record]: seed = 64 151 219 247 13:16:29.209 [planetinfo.record]: coordinates = 151 178 13:16:29.209 [planetinfo.record]: government = 0; 13:16:29.209 [planetinfo.record]: economy = 2; 13:16:29.209 [planetinfo.record]: techlevel = 8; 13:16:29.209 [planetinfo.record]: population = 35; 13:16:29.209 [planetinfo.record]: productivity = 8960; 13:16:29.209 [planetinfo.record]: name = "Tileer"; 13:16:29.209 [planetinfo.record]: inhabitant = "Furry Rodent"; 13:16:29.209 [planetinfo.record]: inhabitants = "Furry Rodents"; 13:16:29.209 [planetinfo.record]: description = "The world Tileer is reasonably noted for its inhabitants’ eccentric shyness."; 13:16:29.211 [planetinfo.record]: air_color = 0.710054, 0.57508, 0.891035, 1; 13:16:29.211 [planetinfo.record]: cloud_alpha = 1.000000; 13:16:29.211 [planetinfo.record]: cloud_color = 0.675781, 0.361649, 0.582523, 1; 13:16:29.211 [planetinfo.record]: cloud_fraction = 0.060000; 13:16:29.211 [planetinfo.record]: land_color = 0.66, 0.488796, 0.221719, 1; 13:16:29.211 [planetinfo.record]: land_fraction = 0.710000; 13:16:29.211 [planetinfo.record]: polar_cloud_color = 0.804102, 0.570488, 0.734748, 1; 13:16:29.211 [planetinfo.record]: polar_land_color = 0.934, 0.87343, 0.778941, 1; 13:16:29.212 [planetinfo.record]: polar_sea_color = 0.860937, 0.885291, 0.95, 1; 13:16:29.212 [planetinfo.record]: sea_color = 0.3125, 0.36377, 0.5, 1; 13:16:29.212 [planetinfo.record]: rotation_speed = 0.003300 13:16:29.212 [planetinfo.record]: planet zpos = 523490.000000 13:16:29.212 [planetinfo.record]: sun_radius = 138593.094482 13:16:29.212 [planetinfo.record]: sun_vector = -0.228 0.139 -0.964 13:16:29.212 [planetinfo.record]: sun_distance = 951800 13:16:29.212 [planetinfo.record]: corona_flare = 0.054324 13:16:29.212 [planetinfo.record]: corona_hues = 0.902267 13:16:29.212 [planetinfo.record]: sun_color = 0.360711, 0.665672, 0.703836, 1 13:16:29.212 [planetinfo.record]: corona_shimmer = 0.283377 13:16:29.212 [planetinfo.record]: station_vector = 0.848 0.345 0.402 13:16:29.212 [planetinfo.record]: station = coriolis 13:16:34.219 [PLANETINFO OVER]: Done 13:16:34.220 [PLANETINFO LOGGING]: 4 70 13:16:35.223 [planetinfo.record]: seed = 56 10 149 28 13:16:35.224 [planetinfo.record]: coordinates = 10 208 13:16:35.224 [planetinfo.record]: government = 7; 13:16:35.224 [planetinfo.record]: economy = 0; 13:16:35.224 [planetinfo.record]: techlevel = 13; 13:16:35.224 [planetinfo.record]: population = 60; 13:16:35.224 [planetinfo.record]: productivity = 52800; 13:16:35.224 [planetinfo.record]: name = "Tetiri"; 13:16:35.224 [planetinfo.record]: inhabitant = "Green Horned Lizard"; 13:16:35.224 [planetinfo.record]: inhabitants = "Green Horned Lizards"; 13:16:35.224 [planetinfo.record]: description = "Tetiri is mildly well known for its hoopy night life."; 13:16:35.240 [planetinfo.record]: air_color = 0.599518, 0.910547, 0.832536, 1; 13:16:35.240 [planetinfo.record]: cloud_alpha = 1.000000; 13:16:35.240 [planetinfo.record]: cloud_color = 0.734375, 0.502306, 0.421692, 1; 13:16:35.240 [planetinfo.record]: cloud_fraction = 0.480000; 13:16:35.240 [planetinfo.record]: land_color = 0.596574, 0.66, 0.538828, 1; 13:16:35.240 [planetinfo.record]: land_fraction = 0.630000; 13:16:35.240 [planetinfo.record]: polar_cloud_color = 0.830469, 0.666446, 0.60947, 1; 13:16:35.240 [planetinfo.record]: polar_land_color = 0.911561, 0.934, 0.891131, 1; 13:16:35.240 [planetinfo.record]: polar_sea_color = 0.865736, 0.879431, 0.91582, 1; 13:16:35.241 [planetinfo.record]: sea_color = 0.657654, 0.708005, 0.841797, 1; 13:16:35.241 [planetinfo.record]: rotation_speed = 0.004662 13:16:35.241 [planetinfo.record]: planet zpos = 589800.000000 13:16:35.241 [planetinfo.record]: sun_radius = 92439.738464 13:16:35.241 [planetinfo.record]: sun_vector = 0.507 0.824 -0.253 13:16:35.241 [planetinfo.record]: sun_distance = 1002660 13:16:35.241 [planetinfo.record]: corona_flare = 0.018417 13:16:35.241 [planetinfo.record]: corona_hues = 0.837303 13:16:35.241 [planetinfo.record]: sun_color = 0.205036, 0.463173, 0.720825, 1 13:16:35.241 [planetinfo.record]: corona_shimmer = 0.369325 13:16:35.241 [planetinfo.record]: station_vector = -0.083 -0.819 0.568 13:16:35.241 [planetinfo.record]: station = icosahedron 13:16:40.671 [PLANETINFO OVER]: Done 13:16:40.672 [PLANETINFO LOGGING]: 4 71 13:16:41.690 [planetinfo.record]: seed = 116 50 83 72 13:16:41.690 [planetinfo.record]: coordinates = 50 30 13:16:41.690 [planetinfo.record]: government = 6; 13:16:41.690 [planetinfo.record]: economy = 6; 13:16:41.690 [planetinfo.record]: techlevel = 6; 13:16:41.690 [planetinfo.record]: population = 37; 13:16:41.690 [planetinfo.record]: productivity = 11840; 13:16:41.690 [planetinfo.record]: name = "Usorbela"; 13:16:41.690 [planetinfo.record]: inhabitant = "Human Colonial"; 13:16:41.690 [planetinfo.record]: inhabitants = "Human Colonials"; 13:16:41.690 [planetinfo.record]: description = "The world Usorbela is very noted for its pink oceans but cursed by occasional earthquakes."; 13:16:41.716 [planetinfo.record]: air_color = 0.75041, 0.721966, 0.987293, 1; 13:16:41.716 [planetinfo.record]: cloud_alpha = 1.000000; 13:16:41.716 [planetinfo.record]: cloud_color = 0.832195, 0.765091, 0.964844, 1; 13:16:41.716 [planetinfo.record]: cloud_fraction = 0.400000; 13:16:41.716 [planetinfo.record]: land_color = 0.642275, 0.66, 0.634219, 1; 13:16:41.716 [planetinfo.record]: land_fraction = 0.510000; 13:16:41.716 [planetinfo.record]: polar_cloud_color = 0.853909, 0.813302, 0.93418, 1; 13:16:41.716 [planetinfo.record]: polar_land_color = 0.927729, 0.934, 0.924879, 1; 13:16:41.716 [planetinfo.record]: polar_sea_color = 0.91, 0.8645, 0.8918, 1; 13:16:41.716 [planetinfo.record]: sea_color = 0.9, 0.72, 0.828, 1; 13:16:41.716 [planetinfo.record]: rotation_speed = 0.004298 13:16:41.716 [planetinfo.record]: planet zpos = 589680.000000 13:16:41.716 [planetinfo.record]: sun_radius = 111880.178833 13:16:41.716 [planetinfo.record]: sun_vector = 0.602 -0.756 -0.258 13:16:41.716 [planetinfo.record]: sun_distance = 1031940 13:16:41.716 [planetinfo.record]: corona_flare = 0.046022 13:16:41.716 [planetinfo.record]: corona_hues = 0.704071 13:16:41.716 [planetinfo.record]: sun_color = 0.826398, 0.824462, 0.820241, 1 13:16:41.716 [planetinfo.record]: corona_shimmer = 0.266093 13:16:41.716 [planetinfo.record]: station_vector = 0.945 0.306 0.114 13:16:41.716 [planetinfo.record]: station = coriolis 13:16:47.394 [PLANETINFO OVER]: Done 13:16:47.396 [PLANETINFO LOGGING]: 4 72 13:16:48.413 [planetinfo.record]: seed = 84 245 85 162 13:16:48.413 [planetinfo.record]: coordinates = 245 19 13:16:48.413 [planetinfo.record]: government = 2; 13:16:48.413 [planetinfo.record]: economy = 3; 13:16:48.413 [planetinfo.record]: techlevel = 6; 13:16:48.413 [planetinfo.record]: population = 30; 13:16:48.413 [planetinfo.record]: productivity = 10080; 13:16:48.413 [planetinfo.record]: name = "Xemageat"; 13:16:48.413 [planetinfo.record]: inhabitant = "Human Colonial"; 13:16:48.413 [planetinfo.record]: inhabitants = "Human Colonials"; 13:16:48.413 [planetinfo.record]: description = "The world Xemageat is beset by deadly earthquakes."; 13:16:48.414 [planetinfo.record]: air_color = 0.735025, 0.591593, 0.884531, 1; 13:16:48.415 [planetinfo.record]: cloud_alpha = 1.000000; 13:16:48.415 [planetinfo.record]: cloud_color = 0.65625, 0.397339, 0.53084, 1; 13:16:48.415 [planetinfo.record]: cloud_fraction = 0.320000; 13:16:48.415 [planetinfo.record]: land_color = 0.136841, 0.59375, 0.358156, 1; 13:16:48.415 [planetinfo.record]: land_fraction = 0.310000; 13:16:48.415 [planetinfo.record]: polar_cloud_color = 0.795313, 0.599203, 0.700322, 1; 13:16:48.415 [planetinfo.record]: polar_land_color = 0.759665, 0.940625, 0.847317, 1; 13:16:48.415 [planetinfo.record]: polar_sea_color = 0.876455, 0.927734, 0.888073, 1; 13:16:48.415 [planetinfo.record]: sea_color = 0.562881, 0.722656, 0.59908, 1; 13:16:48.415 [planetinfo.record]: rotation_speed = 0.003353 13:16:48.415 [planetinfo.record]: planet zpos = 428760.000000 13:16:48.415 [planetinfo.record]: sun_radius = 101397.286835 13:16:48.416 [planetinfo.record]: sun_vector = 0.061 0.952 0.301 13:16:48.416 [planetinfo.record]: sun_distance = 643140 13:16:48.416 [planetinfo.record]: corona_flare = 0.055101 13:16:48.416 [planetinfo.record]: corona_hues = 0.552376 13:16:48.416 [planetinfo.record]: sun_color = 0.697086, 0.548652, 0.454777, 1 13:16:48.416 [planetinfo.record]: corona_shimmer = 0.842603 13:16:48.416 [planetinfo.record]: station_vector = 0.858 0.479 0.186 13:16:48.416 [planetinfo.record]: station = coriolis 13:16:52.916 [PLANETINFO OVER]: Done 13:16:52.917 [PLANETINFO LOGGING]: 4 73 13:16:53.920 [planetinfo.record]: seed = 24 145 219 127 13:16:53.920 [planetinfo.record]: coordinates = 145 67 13:16:53.920 [planetinfo.record]: government = 3; 13:16:53.921 [planetinfo.record]: economy = 3; 13:16:53.921 [planetinfo.record]: techlevel = 7; 13:16:53.921 [planetinfo.record]: population = 35; 13:16:53.921 [planetinfo.record]: productivity = 13720; 13:16:53.921 [planetinfo.record]: name = "Onrace"; 13:16:53.921 [planetinfo.record]: inhabitant = "Blue Horned Humanoid"; 13:16:53.921 [planetinfo.record]: inhabitants = "Blue Horned Humanoids"; 13:16:53.921 [planetinfo.record]: description = "Onrace is reasonably notable for its unusual tropical forests but scourged by frequent civil war."; 13:16:53.952 [planetinfo.record]: air_color = 0.430075, 0.712727, 0.881279, 1; 13:16:53.952 [planetinfo.record]: cloud_alpha = 1.000000; 13:16:53.952 [planetinfo.record]: cloud_color = 0.0404053, 0.646484, 0.1209, 1; 13:16:53.952 [planetinfo.record]: cloud_fraction = 0.320000; 13:16:53.952 [planetinfo.record]: land_color = 0.630859, 0.377037, 0.5912, 1; 13:16:53.952 [planetinfo.record]: land_fraction = 0.410000; 13:16:53.952 [planetinfo.record]: polar_cloud_color = 0.327489, 0.790918, 0.389039, 1; 13:16:53.952 [planetinfo.record]: polar_land_color = 0.936914, 0.842674, 0.922189, 1; 13:16:53.952 [planetinfo.record]: polar_sea_color = 0.941406, 0.883858, 0.882476, 1; 13:16:53.952 [planetinfo.record]: sea_color = 0.585938, 0.442663, 0.439224, 1; 13:16:53.952 [planetinfo.record]: rotation_speed = 0.001682 13:16:53.952 [planetinfo.record]: planet zpos = 884130.000000 13:16:53.953 [planetinfo.record]: sun_radius = 163912.651062 13:16:53.953 [planetinfo.record]: sun_vector = -0.136 -0.872 -0.470 13:16:53.953 [planetinfo.record]: sun_distance = 1224180 13:16:53.953 [planetinfo.record]: corona_flare = 0.053279 13:16:53.953 [planetinfo.record]: corona_hues = 0.983200 13:16:53.953 [planetinfo.record]: sun_color = 0.201943, 0.535356, 0.725, 1 13:16:53.953 [planetinfo.record]: corona_shimmer = 0.456258 13:16:53.953 [planetinfo.record]: station_vector = 0.505 -0.863 0.025 13:16:53.953 [planetinfo.record]: station = coriolis 13:16:59.360 [PLANETINFO OVER]: Done 13:16:59.362 [PLANETINFO LOGGING]: 4 74 13:17:00.368 [planetinfo.record]: seed = 32 57 101 242 13:17:00.368 [planetinfo.record]: coordinates = 57 101 13:17:00.368 [planetinfo.record]: government = 4; 13:17:00.368 [planetinfo.record]: economy = 5; 13:17:00.369 [planetinfo.record]: techlevel = 5; 13:17:00.369 [planetinfo.record]: population = 30; 13:17:00.369 [planetinfo.record]: productivity = 9600; 13:17:00.369 [planetinfo.record]: name = "Enerte"; 13:17:00.369 [planetinfo.record]: inhabitant = "Human Colonial"; 13:17:00.369 [planetinfo.record]: inhabitants = "Human Colonials"; 13:17:00.369 [planetinfo.record]: description = "Enerte is most noted for its inhabitants’ eccentric love for poetry and its inhabitants’ weird shyness."; 13:17:00.392 [planetinfo.record]: air_color = 0.532789, 0.979488, 0.930204, 1; 13:17:00.392 [planetinfo.record]: cloud_alpha = 1.000000; 13:17:00.392 [planetinfo.record]: cloud_color = 0.941406, 0.709359, 0.216965, 1; 13:17:00.392 [planetinfo.record]: cloud_fraction = 0.340000; 13:17:00.392 [planetinfo.record]: land_color = 0.654864, 0.00257813, 0.66, 1; 13:17:00.392 [planetinfo.record]: land_fraction = 0.670000; 13:17:00.392 [planetinfo.record]: polar_cloud_color = 0.923633, 0.781341, 0.479405, 1; 13:17:00.392 [planetinfo.record]: polar_land_color = 0.932183, 0.701412, 0.934, 1; 13:17:00.392 [planetinfo.record]: polar_sea_color = 0.941797, 0.941061, 0.894707, 1; 13:17:00.392 [planetinfo.record]: sea_color = 0.582031, 0.580212, 0.465625, 1; 13:17:00.392 [planetinfo.record]: rotation_speed = 0.003205 13:17:00.393 [planetinfo.record]: planet zpos = 406200.000000 13:17:00.393 [planetinfo.record]: sun_radius = 109166.973114 13:17:00.393 [planetinfo.record]: sun_vector = -0.617 0.040 -0.786 13:17:00.393 [planetinfo.record]: sun_distance = 609300 13:17:00.393 [planetinfo.record]: corona_flare = 0.042743 13:17:00.393 [planetinfo.record]: corona_hues = 0.974960 13:17:00.393 [planetinfo.record]: sun_color = 0.691074, 0.510462, 0.393208, 1 13:17:00.393 [planetinfo.record]: corona_shimmer = 0.430303 13:17:00.394 [planetinfo.record]: station_vector = -0.506 -0.627 0.592 13:17:00.394 [planetinfo.record]: station = coriolis 13:17:06.081 [PLANETINFO OVER]: Done 13:17:06.082 [PLANETINFO LOGGING]: 4 75 13:17:07.084 [planetinfo.record]: seed = 44 63 243 139 13:17:07.084 [planetinfo.record]: coordinates = 63 188 13:17:07.084 [planetinfo.record]: government = 5; 13:17:07.084 [planetinfo.record]: economy = 4; 13:17:07.085 [planetinfo.record]: techlevel = 9; 13:17:07.085 [planetinfo.record]: population = 46; 13:17:07.085 [planetinfo.record]: productivity = 19872; 13:17:07.085 [planetinfo.record]: name = "Masoen"; 13:17:07.085 [planetinfo.record]: inhabitant = "Small Black Bony Feline"; 13:17:07.085 [planetinfo.record]: inhabitants = "Small Black Bony Felines"; 13:17:07.085 [planetinfo.record]: description = "This planet is noted for mud tennis."; 13:17:07.100 [planetinfo.record]: air_color = 0.5972, 0.518606, 0.909896, 1; 13:17:07.102 [planetinfo.record]: cloud_alpha = 1.000000; 13:17:07.102 [planetinfo.record]: cloud_color = 0.538565, 0.226021, 0.732422, 1; 13:17:07.102 [planetinfo.record]: cloud_fraction = 0.350000; 13:17:07.102 [planetinfo.record]: land_color = 0.652507, 0.66, 0.652266, 1; 13:17:07.102 [planetinfo.record]: land_fraction = 0.350000; 13:17:07.102 [planetinfo.record]: polar_cloud_color = 0.692356, 0.4711, 0.82959, 1; 13:17:07.102 [planetinfo.record]: polar_land_color = 0.931349, 0.934, 0.931264, 1; 13:17:07.102 [planetinfo.record]: polar_sea_color = 0.779186, 0.946484, 0.805326, 1; 13:17:07.102 [planetinfo.record]: sea_color = 0.156784, 0.535156, 0.215905, 1; 13:17:07.102 [planetinfo.record]: rotation_speed = 0.000268 13:17:07.102 [planetinfo.record]: planet zpos = 569500.000000 13:17:07.102 [planetinfo.record]: sun_radius = 99630.347443 13:17:07.102 [planetinfo.record]: sun_vector = -0.396 -0.508 0.765 13:17:07.102 [planetinfo.record]: sun_distance = 1082050 13:17:07.102 [planetinfo.record]: corona_flare = 0.084970 13:17:07.103 [planetinfo.record]: corona_hues = 0.691292 13:17:07.103 [planetinfo.record]: sun_color = 0.242169, 0.467201, 0.825528, 1 13:17:07.103 [planetinfo.record]: corona_shimmer = 0.728874 13:17:07.103 [planetinfo.record]: station_vector = 0.517 0.486 0.705 13:17:07.103 [planetinfo.record]: station = coriolis 13:17:11.445 [PLANETINFO OVER]: Done 13:17:11.446 [PLANETINFO LOGGING]: 4 76 13:17:12.464 [planetinfo.record]: seed = 156 101 69 63 13:17:12.465 [planetinfo.record]: coordinates = 101 82 13:17:12.465 [planetinfo.record]: government = 3; 13:17:12.465 [planetinfo.record]: economy = 2; 13:17:12.465 [planetinfo.record]: techlevel = 8; 13:17:12.465 [planetinfo.record]: population = 38; 13:17:12.465 [planetinfo.record]: productivity = 17024; 13:17:12.465 [planetinfo.record]: name = "Ontiteen"; 13:17:12.465 [planetinfo.record]: inhabitant = "Human Colonial"; 13:17:12.465 [planetinfo.record]: inhabitants = "Human Colonials"; 13:17:12.465 [planetinfo.record]: description = "This planet is reasonably famous for its inhabitants’ ancient mating traditions."; 13:17:12.488 [planetinfo.record]: air_color = 0.688105, 0.547804, 0.909246, 1; 13:17:12.488 [planetinfo.record]: cloud_alpha = 1.000000; 13:17:12.488 [planetinfo.record]: cloud_color = 0.730469, 0.296753, 0.652535, 1; 13:17:12.488 [planetinfo.record]: cloud_fraction = 0.300000; 13:17:12.488 [planetinfo.record]: land_color = 0.487266, 0.66, 0.542595, 1; 13:17:12.488 [planetinfo.record]: land_fraction = 0.440000; 13:17:12.488 [planetinfo.record]: polar_cloud_color = 0.828711, 0.521181, 0.773452, 1; 13:17:12.488 [planetinfo.record]: polar_land_color = 0.872889, 0.934, 0.892463, 1; 13:17:12.488 [planetinfo.record]: polar_sea_color = 0.773336, 0.943945, 0.75405, 1; 13:17:12.488 [planetinfo.record]: sea_color = 0.155293, 0.560547, 0.109482, 1; 13:17:12.488 [planetinfo.record]: rotation_speed = 0.004223 13:17:12.488 [planetinfo.record]: planet zpos = 878410.000000 13:17:12.488 [planetinfo.record]: sun_radius = 166912.416992 13:17:12.488 [planetinfo.record]: sun_vector = 0.697 0.698 0.163 13:17:12.488 [planetinfo.record]: sun_distance = 1418970 13:17:12.488 [planetinfo.record]: corona_flare = 0.053322 13:17:12.488 [planetinfo.record]: corona_hues = 0.825386 13:17:12.488 [planetinfo.record]: sun_color = 0.240378, 0.435814, 0.802118, 1 13:17:12.488 [planetinfo.record]: corona_shimmer = 0.362940 13:17:12.489 [planetinfo.record]: station_vector = 0.994 0.108 0.005 13:17:12.489 [planetinfo.record]: station = coriolis 13:17:17.540 [PLANETINFO OVER]: Done 13:17:17.542 [PLANETINFO LOGGING]: 4 77 13:17:18.570 [planetinfo.record]: seed = 176 210 27 102 13:17:18.570 [planetinfo.record]: coordinates = 210 156 13:17:18.570 [planetinfo.record]: government = 6; 13:17:18.570 [planetinfo.record]: economy = 4; 13:17:18.570 [planetinfo.record]: techlevel = 8; 13:17:18.570 [planetinfo.record]: population = 43; 13:17:18.570 [planetinfo.record]: productivity = 20640; 13:17:18.570 [planetinfo.record]: name = "Biradi"; 13:17:18.570 [planetinfo.record]: inhabitant = "Human Colonial"; 13:17:18.570 [planetinfo.record]: inhabitants = "Human Colonials"; 13:17:18.570 [planetinfo.record]: description = "The planet Biradi is mildly notable for its inhabitants’ unusual silliness."; 13:17:18.596 [planetinfo.record]: air_color = 0.444417, 0.750369, 0.855914, 1; 13:17:18.596 [planetinfo.record]: cloud_alpha = 1.000000; 13:17:18.596 [planetinfo.record]: cloud_color = 0.194374, 0.570312, 0.0891113, 1; 13:17:18.596 [planetinfo.record]: cloud_fraction = 0.340000; 13:17:18.596 [planetinfo.record]: land_color = 0.556875, 0.66, 0.560903, 1; 13:17:18.596 [planetinfo.record]: land_fraction = 0.320000; 13:17:18.596 [planetinfo.record]: polar_cloud_color = 0.444914, 0.756641, 0.357631, 1; 13:17:18.596 [planetinfo.record]: polar_land_color = 0.897516, 0.934, 0.898941, 1; 13:17:18.596 [planetinfo.record]: polar_sea_color = 0.92034, 0.93457, 0.880997, 1; 13:17:18.596 [planetinfo.record]: sea_color = 0.614446, 0.654297, 0.504269, 1; 13:17:18.596 [planetinfo.record]: rotation_speed = 0.000117 13:17:18.596 [planetinfo.record]: planet zpos = 593060.000000 13:17:18.596 [planetinfo.record]: sun_radius = 138933.699646 13:17:18.596 [planetinfo.record]: sun_vector = -0.193 -0.886 -0.421 13:17:18.596 [planetinfo.record]: sun_distance = 866780 13:17:18.596 [planetinfo.record]: corona_flare = 0.052516 13:17:18.596 [planetinfo.record]: corona_hues = 0.811981 13:17:18.596 [planetinfo.record]: sun_color = 0.295616, 0.574613, 0.72821, 1 13:17:18.597 [planetinfo.record]: corona_shimmer = 0.706405 13:17:18.597 [planetinfo.record]: station_vector = -0.514 -0.823 0.241 13:17:18.597 [planetinfo.record]: station = coriolis 13:17:23.413 [PLANETINFO OVER]: Done 13:17:23.414 [PLANETINFO LOGGING]: 4 78 13:17:24.432 [planetinfo.record]: seed = 200 72 117 43 13:17:24.433 [planetinfo.record]: coordinates = 72 13 13:17:24.433 [planetinfo.record]: government = 1; 13:17:24.433 [planetinfo.record]: economy = 7; 13:17:24.433 [planetinfo.record]: techlevel = 1; 13:17:24.433 [planetinfo.record]: population = 13; 13:17:24.433 [planetinfo.record]: productivity = 1560; 13:17:24.433 [planetinfo.record]: name = "Maleve"; 13:17:24.433 [planetinfo.record]: inhabitant = "Human Colonial"; 13:17:24.433 [planetinfo.record]: inhabitants = "Human Colonials"; 13:17:24.433 [planetinfo.record]: description = "The world Maleve is very noted for its ancient Biso tulip plantations but ravaged by occasional solar activity."; 13:17:24.458 [planetinfo.record]: air_color = 0.798354, 0.750935, 0.978188, 1; 13:17:24.459 [planetinfo.record]: cloud_alpha = 1.000000; 13:17:24.459 [planetinfo.record]: cloud_color = 0.904598, 0.845947, 0.9375, 1; 13:17:24.459 [planetinfo.record]: cloud_fraction = 0.410000; 13:17:24.459 [planetinfo.record]: land_color = 0.274666, 0.435418, 0.505859, 1; 13:17:24.459 [planetinfo.record]: land_fraction = 0.440000; 13:17:24.459 [planetinfo.record]: polar_cloud_color = 0.901654, 0.865608, 0.921875, 1; 13:17:24.459 [planetinfo.record]: polar_land_color = 0.840936, 0.916362, 0.949414, 1; 13:17:24.459 [planetinfo.record]: polar_sea_color = 0.90537, 0.932227, 0.878514, 1; 13:17:24.459 [planetinfo.record]: sea_color = 0.599636, 0.677734, 0.521538, 1; 13:17:24.459 [planetinfo.record]: rotation_speed = 0.001553 13:17:24.459 [planetinfo.record]: planet zpos = 741520.000000 13:17:24.459 [planetinfo.record]: sun_radius = 140089.877930 13:17:24.475 [planetinfo.record]: sun_vector = -0.289 -0.670 -0.684 13:17:24.475 [planetinfo.record]: sun_distance = 1197840 13:17:24.475 [planetinfo.record]: corona_flare = 0.095180 13:17:24.475 [planetinfo.record]: corona_hues = 0.749191 13:17:24.475 [planetinfo.record]: sun_color = 0.693985, 0.719686, 0.848447, 1 13:17:24.475 [planetinfo.record]: corona_shimmer = 0.479920 13:17:24.475 [planetinfo.record]: station_vector = -0.283 0.958 0.038 13:17:24.475 [planetinfo.record]: station = coriolis 13:17:29.337 [PLANETINFO OVER]: Done 13:17:29.338 [PLANETINFO LOGGING]: 4 79 13:17:30.341 [planetinfo.record]: seed = 164 163 211 27 13:17:30.341 [planetinfo.record]: coordinates = 163 246 13:17:30.341 [planetinfo.record]: government = 4; 13:17:30.341 [planetinfo.record]: economy = 6; 13:17:30.341 [planetinfo.record]: techlevel = 6; 13:17:30.341 [planetinfo.record]: population = 35; 13:17:30.341 [planetinfo.record]: productivity = 8960; 13:17:30.341 [planetinfo.record]: name = "Anlala"; 13:17:30.341 [planetinfo.record]: inhabitant = "Green Furry Rodent"; 13:17:30.341 [planetinfo.record]: inhabitants = "Green Furry Rodents"; 13:17:30.341 [planetinfo.record]: description = "This planet is very well known for vicious Lolees brew and its exotic goat meat."; 13:17:30.346 [planetinfo.record]: air_color = 0.708212, 0.794261, 0.93201, 1; 13:17:30.347 [planetinfo.record]: cloud_alpha = 1.000000; 13:17:30.347 [planetinfo.record]: cloud_color = 0.702095, 0.798828, 0.785225, 1; 13:17:30.347 [planetinfo.record]: cloud_fraction = 0.510000; 13:17:30.347 [planetinfo.record]: land_color = 0.610049, 0.260391, 0.66, 1; 13:17:30.347 [planetinfo.record]: land_fraction = 0.710000; 13:17:30.347 [planetinfo.record]: polar_cloud_color = 0.794425, 0.859473, 0.850325, 1; 13:17:30.347 [planetinfo.record]: polar_land_color = 0.916328, 0.792623, 0.934, 1; 13:17:30.347 [planetinfo.record]: polar_sea_color = 0.935156, 0.928933, 0.878262, 1; 13:17:30.347 [planetinfo.record]: sea_color = 0.648438, 0.631178, 0.490634, 1; 13:17:30.348 [planetinfo.record]: rotation_speed = 0.001170 13:17:30.348 [planetinfo.record]: planet zpos = 637450.000000 13:17:30.348 [planetinfo.record]: sun_radius = 147786.824799 13:17:30.348 [planetinfo.record]: sun_vector = -0.354 0.266 0.897 13:17:30.348 [planetinfo.record]: sun_distance = 1159000 13:17:30.348 [planetinfo.record]: corona_flare = 0.011298 13:17:30.348 [planetinfo.record]: corona_hues = 0.751953 13:17:30.348 [planetinfo.record]: sun_color = 0.757214, 0.742675, 0.477626, 1 13:17:30.348 [planetinfo.record]: corona_shimmer = 0.493241 13:17:30.348 [planetinfo.record]: station_vector = -0.799 0.254 0.545 13:17:30.348 [planetinfo.record]: station = coriolis 13:17:35.047 [PLANETINFO OVER]: Done 13:17:35.048 [PLANETINFO LOGGING]: 4 80 13:17:36.050 [planetinfo.record]: seed = 164 70 117 113 13:17:36.050 [planetinfo.record]: coordinates = 70 117 13:17:36.051 [planetinfo.record]: government = 4; 13:17:36.051 [planetinfo.record]: economy = 5; 13:17:36.051 [planetinfo.record]: techlevel = 6; 13:17:36.051 [planetinfo.record]: population = 34; 13:17:36.051 [planetinfo.record]: productivity = 10880; 13:17:36.051 [planetinfo.record]: name = "Atdice"; 13:17:36.051 [planetinfo.record]: inhabitant = "Human Colonial"; 13:17:36.051 [planetinfo.record]: inhabitants = "Human Colonials"; 13:17:36.051 [planetinfo.record]: description = "This planet is a tedious little planet."; 13:17:36.078 [planetinfo.record]: air_color = 0.68328, 0.861016, 0.88258, 1; 13:17:36.079 [planetinfo.record]: cloud_alpha = 1.000000; 13:17:36.079 [planetinfo.record]: cloud_color = 0.633321, 0.650391, 0.599579, 1; 13:17:36.079 [planetinfo.record]: cloud_fraction = 0.500000; 13:17:36.079 [planetinfo.record]: land_color = 0.611328, 0.506667, 0.48954, 1; 13:17:36.079 [planetinfo.record]: land_fraction = 0.630000; 13:17:36.079 [planetinfo.record]: polar_cloud_color = 0.779673, 0.792676, 0.753971, 1; 13:17:36.079 [planetinfo.record]: polar_land_color = 0.938867, 0.898683, 0.892107, 1; 13:17:36.079 [planetinfo.record]: polar_sea_color = 0.791636, 0.945898, 0.877203, 1; 13:17:36.079 [planetinfo.record]: sea_color = 0.188087, 0.541016, 0.383852, 1; 13:17:36.079 [planetinfo.record]: rotation_speed = 0.000179 13:17:36.079 [planetinfo.record]: planet zpos = 314200.000000 13:17:36.079 [planetinfo.record]: sun_radius = 68229.765015 13:17:36.079 [planetinfo.record]: sun_vector = -0.405 0.787 0.466 13:17:36.079 [planetinfo.record]: sun_distance = 691240 13:17:36.080 [planetinfo.record]: corona_flare = 0.025978 13:17:36.080 [planetinfo.record]: corona_hues = 0.818214 13:17:36.080 [planetinfo.record]: sun_color = 0.810477, 0.757883, 0.264577, 1 13:17:36.080 [planetinfo.record]: corona_shimmer = 1.366358 13:17:36.080 [planetinfo.record]: station_vector = -0.885 0.438 0.157 13:17:36.080 [planetinfo.record]: station = coriolis 13:17:40.889 [PLANETINFO OVER]: Done 13:17:40.890 [PLANETINFO LOGGING]: 4 81 13:17:41.894 [planetinfo.record]: seed = 8 132 155 150 13:17:41.894 [planetinfo.record]: coordinates = 132 229 13:17:41.894 [planetinfo.record]: government = 1; 13:17:41.894 [planetinfo.record]: economy = 7; 13:17:41.894 [planetinfo.record]: techlevel = 1; 13:17:41.894 [planetinfo.record]: population = 13; 13:17:41.894 [planetinfo.record]: productivity = 1560; 13:17:41.894 [planetinfo.record]: name = "Veonquat"; 13:17:41.894 [planetinfo.record]: inhabitant = "Black Bug-Eyed Lobster"; 13:17:41.894 [planetinfo.record]: inhabitants = "Black Bug-Eyed Lobsters"; 13:17:41.894 [planetinfo.record]: description = "Veonquat is reasonably notable for its great volcanoes but plagued by lethal spotted yaks."; 13:17:41.915 [planetinfo.record]: air_color = 0.489835, 0.993146, 0.866909, 1; 13:17:41.915 [planetinfo.record]: cloud_alpha = 1.000000; 13:17:41.915 [planetinfo.record]: cloud_color = 0.982422, 0.310245, 0.0767517, 1; 13:17:41.915 [planetinfo.record]: cloud_fraction = 0.480000; 13:17:41.915 [planetinfo.record]: land_color = 0.658872, 0.654844, 0.66, 1; 13:17:41.915 [planetinfo.record]: land_fraction = 0.500000; 13:17:41.915 [planetinfo.record]: polar_cloud_color = 0.94209, 0.539226, 0.399284, 1; 13:17:41.915 [planetinfo.record]: polar_land_color = 0.933601, 0.932176, 0.934, 1; 13:17:41.915 [planetinfo.record]: polar_sea_color = 0.920508, 0.851908, 0.801849, 1; 13:17:41.915 [planetinfo.record]: sea_color = 0.794922, 0.557959, 0.38504, 1; 13:17:41.916 [planetinfo.record]: rotation_speed = 0.003565 13:17:41.916 [planetinfo.record]: planet zpos = 538080.000000 13:17:41.916 [planetinfo.record]: sun_radius = 132911.436157 13:17:41.916 [planetinfo.record]: sun_vector = 0.423 -0.556 -0.715 13:17:41.916 [planetinfo.record]: sun_distance = 941640 13:17:41.916 [planetinfo.record]: corona_flare = 0.048694 13:17:41.916 [planetinfo.record]: corona_hues = 0.882782 13:17:41.916 [planetinfo.record]: sun_color = 0.830707, 0.291116, 0.226899, 1 13:17:41.916 [planetinfo.record]: corona_shimmer = 1.223280 13:17:41.916 [planetinfo.record]: station_vector = 0.958 -0.272 0.095 13:17:41.916 [planetinfo.record]: station = coriolis 13:17:46.731 [PLANETINFO OVER]: Done 13:17:46.732 [PLANETINFO LOGGING]: 4 82 13:17:47.736 [planetinfo.record]: seed = 48 177 197 203 13:17:47.736 [planetinfo.record]: coordinates = 177 26 13:17:47.736 [planetinfo.record]: government = 6; 13:17:47.736 [planetinfo.record]: economy = 2; 13:17:47.736 [planetinfo.record]: techlevel = 9; 13:17:47.736 [planetinfo.record]: population = 45; 13:17:47.736 [planetinfo.record]: productivity = 28800; 13:17:47.736 [planetinfo.record]: name = "Matira"; 13:17:47.736 [planetinfo.record]: inhabitant = "Small Bony Feline"; 13:17:47.736 [planetinfo.record]: inhabitants = "Small Bony Felines"; 13:17:47.736 [planetinfo.record]: description = "The world Matira is reasonably noted for its inhabitants’ exceptional love for tourists."; 13:17:47.753 [planetinfo.record]: air_color = 0.724065, 0.780544, 0.935262, 1; 13:17:47.753 [planetinfo.record]: cloud_alpha = 1.000000; 13:17:47.753 [planetinfo.record]: cloud_color = 0.745422, 0.795269, 0.808594, 1; 13:17:47.753 [planetinfo.record]: cloud_fraction = 0.330000; 13:17:47.753 [planetinfo.record]: land_color = 0.66, 0.459712, 0.0773438, 1; 13:17:47.753 [planetinfo.record]: land_fraction = 0.530000; 13:17:47.753 [planetinfo.record]: polar_cloud_color = 0.821686, 0.85497, 0.863867, 1; 13:17:47.754 [planetinfo.record]: polar_land_color = 0.934, 0.863141, 0.727863, 1; 13:17:47.754 [planetinfo.record]: polar_sea_color = 0.903341, 0.932422, 0.879245, 1; 13:17:47.754 [planetinfo.record]: sea_color = 0.591474, 0.675781, 0.521619, 1; 13:17:47.754 [planetinfo.record]: rotation_speed = 0.000081 13:17:47.754 [planetinfo.record]: planet zpos = 755170.000000 13:17:47.754 [planetinfo.record]: sun_radius = 151947.329102 13:17:47.755 [planetinfo.record]: sun_vector = 0.672 0.197 -0.714 13:17:47.755 [planetinfo.record]: sun_distance = 1219890 13:17:47.755 [planetinfo.record]: corona_flare = 0.006004 13:17:47.755 [planetinfo.record]: corona_hues = 0.789383 13:17:47.755 [planetinfo.record]: sun_color = 0.834781, 0.634973, 0.45091, 1 13:17:47.755 [planetinfo.record]: corona_shimmer = 1.195825 13:17:47.755 [planetinfo.record]: station_vector = 0.339 -0.790 0.510 13:17:47.756 [planetinfo.record]: station = coriolis 13:17:53.110 [PLANETINFO OVER]: Done 13:17:53.111 [PLANETINFO LOGGING]: 4 83 13:17:54.118 [planetinfo.record]: seed = 220 119 243 35 13:17:54.119 [planetinfo.record]: coordinates = 119 20 13:17:54.119 [planetinfo.record]: government = 3; 13:17:54.119 [planetinfo.record]: economy = 4; 13:17:54.119 [planetinfo.record]: techlevel = 8; 13:17:54.119 [planetinfo.record]: population = 40; 13:17:54.119 [planetinfo.record]: productivity = 13440; 13:17:54.119 [planetinfo.record]: name = "Geerin"; 13:17:54.119 [planetinfo.record]: inhabitant = "Large Red Bony Feline"; 13:17:54.119 [planetinfo.record]: inhabitants = "Large Red Bony Felines"; 13:17:54.119 [planetinfo.record]: description = "This planet is reasonably famous for its inhabitants’ exceptional loathing of casinos."; 13:17:54.144 [planetinfo.record]: air_color = 0.703546, 0.496424, 0.956074, 1; 13:17:54.144 [planetinfo.record]: cloud_alpha = 1.000000; 13:17:54.144 [planetinfo.record]: cloud_color = 0.871094, 0.136108, 0.601216, 1; 13:17:54.144 [planetinfo.record]: cloud_fraction = 0.330000; 13:17:54.144 [planetinfo.record]: land_color = 0.291328, 0.66, 0.374855, 1; 13:17:54.144 [planetinfo.record]: land_fraction = 0.350000; 13:17:54.144 [planetinfo.record]: polar_cloud_color = 0.891992, 0.421606, 0.719272, 1; 13:17:54.144 [planetinfo.record]: polar_land_color = 0.803568, 0.934, 0.833119, 1; 13:17:54.144 [planetinfo.record]: polar_sea_color = 0.9375, 0.901536, 0.877716, 1; 13:17:54.144 [planetinfo.record]: sea_color = 0.625, 0.529097, 0.465576, 1; 13:17:54.144 [planetinfo.record]: rotation_speed = 0.002104 13:17:54.144 [planetinfo.record]: planet zpos = 407330.000000 13:17:54.144 [planetinfo.record]: sun_radius = 96394.622803 13:17:54.144 [planetinfo.record]: sun_vector = 0.815 0.064 -0.577 13:17:54.144 [planetinfo.record]: sun_distance = 777630 13:17:54.144 [planetinfo.record]: corona_flare = 0.090764 13:17:54.145 [planetinfo.record]: corona_hues = 0.914040 13:17:54.145 [planetinfo.record]: sun_color = 0.602839, 0.699831, 0.732434, 1 13:17:54.145 [planetinfo.record]: corona_shimmer = 0.401465 13:17:54.145 [planetinfo.record]: station_vector = -0.348 -0.298 0.889 13:17:54.145 [planetinfo.record]: station = coriolis 13:17:59.723 [PLANETINFO OVER]: Done 13:17:59.724 [PLANETINFO LOGGING]: 4 84 13:18:00.728 [planetinfo.record]: seed = 108 32 229 28 13:18:00.728 [planetinfo.record]: coordinates = 32 76 13:18:00.728 [planetinfo.record]: government = 5; 13:18:00.728 [planetinfo.record]: economy = 4; 13:18:00.728 [planetinfo.record]: techlevel = 6; 13:18:00.728 [planetinfo.record]: population = 34; 13:18:00.728 [planetinfo.record]: productivity = 14688; 13:18:00.728 [planetinfo.record]: name = "Teesbi"; 13:18:00.728 [planetinfo.record]: inhabitant = "Green Fat Bird"; 13:18:00.729 [planetinfo.record]: inhabitants = "Green Fat Birds"; 13:18:00.729 [planetinfo.record]: description = "The planet Teesbi is reasonably noted for its inhabitants’ eccentric shyness and Teesbiian evil juice."; 13:18:00.767 [planetinfo.record]: air_color = 0.733316, 0.730079, 0.969082, 1; 13:18:00.767 [planetinfo.record]: cloud_alpha = 1.000000; 13:18:00.767 [planetinfo.record]: cloud_color = 0.789165, 0.782166, 0.910156, 1; 13:18:00.767 [planetinfo.record]: cloud_fraction = 0.240000; 13:18:00.767 [planetinfo.record]: land_color = 0.66, 0.497155, 0.389297, 1; 13:18:00.767 [planetinfo.record]: land_fraction = 0.370000; 13:18:00.767 [planetinfo.record]: polar_cloud_color = 0.834, 0.829628, 0.90957, 1; 13:18:00.767 [planetinfo.record]: polar_land_color = 0.934, 0.876387, 0.838229, 1; 13:18:00.767 [planetinfo.record]: polar_sea_color = 0.936328, 0.847633, 0.90376, 1; 13:18:00.767 [planetinfo.record]: sea_color = 0.636719, 0.395462, 0.548132, 1; 13:18:00.767 [planetinfo.record]: rotation_speed = 0.001072 13:18:00.767 [planetinfo.record]: planet zpos = 592000.000000 13:18:00.767 [planetinfo.record]: sun_radius = 190933.007812 13:18:00.767 [planetinfo.record]: sun_vector = 0.195 -0.977 0.086 13:18:00.767 [planetinfo.record]: sun_distance = 1006400 13:18:00.767 [planetinfo.record]: corona_flare = 0.030748 13:18:00.767 [planetinfo.record]: corona_hues = 0.523254 13:18:00.767 [planetinfo.record]: sun_color = 0.816202, 0.770467, 0.743307, 1 13:18:00.768 [planetinfo.record]: corona_shimmer = 0.278306 13:18:00.768 [planetinfo.record]: station_vector = 0.225 0.925 0.306 13:18:00.768 [planetinfo.record]: station = coriolis 13:18:05.068 [PLANETINFO OVER]: Done 13:18:05.068 [PLANETINFO LOGGING]: 4 85 13:18:06.087 [planetinfo.record]: seed = 32 109 91 189 13:18:06.087 [planetinfo.record]: coordinates = 109 198 13:18:06.087 [planetinfo.record]: government = 4; 13:18:06.087 [planetinfo.record]: economy = 6; 13:18:06.087 [planetinfo.record]: techlevel = 4; 13:18:06.087 [planetinfo.record]: population = 27; 13:18:06.087 [planetinfo.record]: productivity = 6912; 13:18:06.087 [planetinfo.record]: name = "Isatanar"; 13:18:06.088 [planetinfo.record]: inhabitant = "Human Colonial"; 13:18:06.088 [planetinfo.record]: inhabitants = "Human Colonials"; 13:18:06.088 [planetinfo.record]: description = "Isatanar is cursed by killer edible talking treeoids."; 13:18:06.119 [planetinfo.record]: air_color = 0.768391, 0.754802, 0.954123, 1; 13:18:06.119 [planetinfo.record]: cloud_alpha = 1.000000; 13:18:06.119 [planetinfo.record]: cloud_color = 0.846751, 0.841576, 0.865234, 1; 13:18:06.119 [planetinfo.record]: cloud_fraction = 0.320000; 13:18:06.119 [planetinfo.record]: land_color = 0.265547, 0.66, 0.280955, 1; 13:18:06.119 [planetinfo.record]: land_fraction = 0.660000; 13:18:06.119 [planetinfo.record]: polar_cloud_color = 0.877481, 0.874157, 0.889355, 1; 13:18:06.119 [planetinfo.record]: polar_land_color = 0.794447, 0.934, 0.799899, 1; 13:18:06.119 [planetinfo.record]: polar_sea_color = 0.94668, 0.911861, 0.898236, 1; 13:18:06.119 [planetinfo.record]: sea_color = 0.533203, 0.454759, 0.424063, 1; 13:18:06.119 [planetinfo.record]: rotation_speed = 0.002017 13:18:06.119 [planetinfo.record]: planet zpos = 750360.000000 13:18:06.119 [planetinfo.record]: sun_radius = 194238.392334 13:18:06.119 [planetinfo.record]: sun_vector = 0.364 -0.220 -0.905 13:18:06.120 [planetinfo.record]: sun_distance = 1000480 13:18:06.120 [planetinfo.record]: corona_flare = 0.002728 13:18:06.120 [planetinfo.record]: corona_hues = 0.989761 13:18:06.120 [planetinfo.record]: sun_color = 0.535581, 0.559453, 0.718799, 1 13:18:06.120 [planetinfo.record]: corona_shimmer = 0.287502 13:18:06.120 [planetinfo.record]: station_vector = -0.237 0.761 0.603 13:18:06.120 [planetinfo.record]: station = coriolis 13:18:10.994 [PLANETINFO OVER]: Done 13:18:10.995 [PLANETINFO LOGGING]: 4 86 13:18:11.997 [planetinfo.record]: seed = 88 202 85 215 13:18:11.997 [planetinfo.record]: coordinates = 202 27 13:18:11.997 [planetinfo.record]: government = 3; 13:18:11.997 [planetinfo.record]: economy = 3; 13:18:11.997 [planetinfo.record]: techlevel = 8; 13:18:11.998 [planetinfo.record]: population = 39; 13:18:11.998 [planetinfo.record]: productivity = 15288; 13:18:11.998 [planetinfo.record]: name = "Tiison"; 13:18:11.998 [planetinfo.record]: inhabitant = "Human Colonial"; 13:18:11.998 [planetinfo.record]: inhabitants = "Human Colonials"; 13:18:11.998 [planetinfo.record]: description = "This planet is beset by evil disease."; 13:18:12.024 [planetinfo.record]: air_color = 0.673271, 0.79022, 0.959977, 1; 13:18:12.024 [planetinfo.record]: cloud_alpha = 1.000000; 13:18:12.024 [planetinfo.record]: cloud_color = 0.620728, 0.882812, 0.827529, 1; 13:18:12.024 [planetinfo.record]: cloud_fraction = 0.480000; 13:18:12.024 [planetinfo.record]: land_color = 0.66, 0.144375, 0.144375, 1; 13:18:12.024 [planetinfo.record]: land_fraction = 0.420000; 13:18:12.024 [planetinfo.record]: polar_cloud_color = 0.730781, 0.897266, 0.862148, 1; 13:18:12.024 [planetinfo.record]: polar_land_color = 0.934, 0.751578, 0.751578, 1; 13:18:12.024 [planetinfo.record]: polar_sea_color = 0.894492, 0.92793, 0.8693, 1; 13:18:12.024 [planetinfo.record]: sea_color = 0.616823, 0.720703, 0.538557, 1; 13:18:12.024 [planetinfo.record]: rotation_speed = 0.003527 13:18:12.024 [planetinfo.record]: planet zpos = 577200.000000 13:18:12.024 [planetinfo.record]: sun_radius = 153258.566284 13:18:12.024 [planetinfo.record]: sun_vector = -0.730 0.683 0.019 13:18:12.025 [planetinfo.record]: sun_distance = 1010100 13:18:12.025 [planetinfo.record]: corona_flare = 0.015985 13:18:12.025 [planetinfo.record]: corona_hues = 0.998444 13:18:12.025 [planetinfo.record]: sun_color = 0.678625, 0.607612, 0.489502, 1 13:18:12.025 [planetinfo.record]: corona_shimmer = 1.943996 13:18:12.025 [planetinfo.record]: station_vector = -0.078 0.147 0.986 13:18:12.025 [planetinfo.record]: station = coriolis 13:18:17.150 [PLANETINFO OVER]: Done 13:18:17.153 [PLANETINFO LOGGING]: 4 87 13:18:18.155 [planetinfo.record]: seed = 212 243 83 16 13:18:18.155 [planetinfo.record]: coordinates = 243 95 13:18:18.155 [planetinfo.record]: government = 2; 13:18:18.155 [planetinfo.record]: economy = 7; 13:18:18.155 [planetinfo.record]: techlevel = 4; 13:18:18.155 [planetinfo.record]: population = 26; 13:18:18.155 [planetinfo.record]: productivity = 3744; 13:18:18.155 [planetinfo.record]: name = "Ergeso"; 13:18:18.155 [planetinfo.record]: inhabitant = "Human Colonial"; 13:18:18.155 [planetinfo.record]: inhabitants = "Human Colonials"; 13:18:18.155 [planetinfo.record]: description = "Ergeso is a revolting dump."; 13:18:18.177 [planetinfo.record]: air_color = 0.653797, 0.887942, 0.905344, 1; 13:18:18.177 [planetinfo.record]: cloud_alpha = 1.000000; 13:18:18.178 [planetinfo.record]: cloud_color = 0.6819, 0.71875, 0.550293, 1; 13:18:18.178 [planetinfo.record]: cloud_fraction = 0.110000; 13:18:18.178 [planetinfo.record]: land_color = 0.595194, 0.595703, 0.586395, 1; 13:18:18.178 [planetinfo.record]: land_fraction = 0.270000; 13:18:18.178 [planetinfo.record]: polar_cloud_color = 0.797052, 0.823438, 0.702817, 1; 13:18:18.178 [planetinfo.record]: polar_land_color = 0.940229, 0.94043, 0.936756, 1; 13:18:18.178 [planetinfo.record]: polar_sea_color = 0.89694, 0.919336, 0.826864, 1; 13:18:18.178 [planetinfo.record]: sea_color = 0.728039, 0.806641, 0.482094, 1; 13:18:18.178 [planetinfo.record]: rotation_speed = 0.003051 13:18:18.178 [planetinfo.record]: planet zpos = 336490.000000 13:18:18.178 [planetinfo.record]: sun_radius = 55736.103973 13:18:18.178 [planetinfo.record]: sun_vector = -0.516 -0.269 0.813 13:18:18.179 [planetinfo.record]: sun_distance = 734160 13:18:18.179 [planetinfo.record]: corona_flare = 0.025394 13:18:18.179 [planetinfo.record]: corona_hues = 0.627426 13:18:18.179 [planetinfo.record]: sun_color = 0.25268, 0.415787, 0.692596, 1 13:18:18.179 [planetinfo.record]: corona_shimmer = 1.563583 13:18:18.179 [planetinfo.record]: station_vector = -0.498 0.267 0.825 13:18:18.179 [planetinfo.record]: station = coriolis 13:18:22.430 [PLANETINFO OVER]: Done 13:18:22.431 [PLANETINFO LOGGING]: 4 88 13:18:23.434 [planetinfo.record]: seed = 244 218 149 165 13:18:23.434 [planetinfo.record]: coordinates = 218 103 13:18:23.434 [planetinfo.record]: government = 6; 13:18:23.434 [planetinfo.record]: economy = 7; 13:18:23.434 [planetinfo.record]: techlevel = 5; 13:18:23.435 [planetinfo.record]: population = 34; 13:18:23.435 [planetinfo.record]: productivity = 8160; 13:18:23.435 [planetinfo.record]: name = "Cesousla"; 13:18:23.435 [planetinfo.record]: inhabitant = "Fierce Harmless Furry Feline"; 13:18:23.435 [planetinfo.record]: inhabitants = "Fierce Harmless Furry Felines"; 13:18:23.435 [planetinfo.record]: description = "The world Cesousla is notable for its weird tropical forests and Cesouslaian evil juice."; 13:18:23.456 [planetinfo.record]: air_color = 0.55126, 0.954123, 0.865656, 1; 13:18:23.457 [planetinfo.record]: cloud_alpha = 1.000000; 13:18:23.457 [planetinfo.record]: cloud_color = 0.865234, 0.49047, 0.287285, 1; 13:18:23.457 [planetinfo.record]: cloud_fraction = 0.130000; 13:18:23.457 [planetinfo.record]: land_color = 0.522255, 0.347778, 0.601562, 1; 13:18:23.457 [planetinfo.record]: land_fraction = 0.560000; 13:18:23.457 [planetinfo.record]: polar_cloud_color = 0.889355, 0.648598, 0.518067, 1; 13:18:23.457 [planetinfo.record]: polar_land_color = 0.908867, 0.84072, 0.939844, 1; 13:18:23.457 [planetinfo.record]: polar_sea_color = 0.875446, 0.926953, 0.891944, 1; 13:18:23.457 [planetinfo.record]: sea_color = 0.568111, 0.730469, 0.620116, 1; 13:18:23.457 [planetinfo.record]: rotation_speed = 0.002100 13:18:23.457 [planetinfo.record]: planet zpos = 431400.000000 13:18:23.457 [planetinfo.record]: sun_radius = 96130.264893 13:18:23.457 [planetinfo.record]: sun_vector = 0.632 -0.209 -0.746 13:18:23.457 [planetinfo.record]: sun_distance = 690240 13:18:23.457 [planetinfo.record]: corona_flare = 0.003831 13:18:23.458 [planetinfo.record]: corona_hues = 0.891479 13:18:23.458 [planetinfo.record]: sun_color = 0.789077, 0.796793, 0.812643, 1 13:18:23.458 [planetinfo.record]: corona_shimmer = 0.290162 13:18:23.458 [planetinfo.record]: station_vector = 0.594 0.493 0.636 13:18:23.458 [planetinfo.record]: station = coriolis 13:18:27.611 [PLANETINFO OVER]: Done 13:18:27.612 [PLANETINFO LOGGING]: 4 89 13:18:28.630 [planetinfo.record]: seed = 248 245 91 70 13:18:28.631 [planetinfo.record]: coordinates = 245 104 13:18:28.631 [planetinfo.record]: government = 7; 13:18:28.631 [planetinfo.record]: economy = 0; 13:18:28.631 [planetinfo.record]: techlevel = 12; 13:18:28.631 [planetinfo.record]: population = 56; 13:18:28.631 [planetinfo.record]: productivity = 49280; 13:18:28.631 [planetinfo.record]: name = "Bizalein"; 13:18:28.631 [planetinfo.record]: inhabitant = "Human Colonial"; 13:18:28.631 [planetinfo.record]: inhabitants = "Human Colonials"; 13:18:28.631 [planetinfo.record]: description = "The planet Bizalein is most famous for its vast rain forests."; 13:18:28.640 [planetinfo.record]: air_color = 0.584292, 0.885118, 0.984691, 1; 13:18:28.640 [planetinfo.record]: cloud_alpha = 1.000000; 13:18:28.640 [planetinfo.record]: cloud_color = 0.506583, 0.957031, 0.362625, 1; 13:18:28.640 [planetinfo.record]: cloud_fraction = 0.320000; 13:18:28.640 [planetinfo.record]: land_color = 0.487266, 0.66, 0.595225, 1; 13:18:28.640 [planetinfo.record]: land_fraction = 0.360000; 13:18:28.640 [planetinfo.record]: polar_cloud_color = 0.65689, 0.930664, 0.569396, 1; 13:18:28.640 [planetinfo.record]: polar_land_color = 0.872889, 0.934, 0.911083, 1; 13:18:28.640 [planetinfo.record]: polar_sea_color = 0.92832, 0.850264, 0.834944, 1; 13:18:28.640 [planetinfo.record]: sea_color = 0.716797, 0.475714, 0.428398, 1; 13:18:28.640 [planetinfo.record]: rotation_speed = 0.000420 13:18:28.640 [planetinfo.record]: planet zpos = 551640.000000 13:18:28.640 [planetinfo.record]: sun_radius = 115638.371124 13:18:28.640 [planetinfo.record]: sun_vector = -0.835 -0.209 0.510 13:18:28.640 [planetinfo.record]: sun_distance = 1011340 13:18:28.640 [planetinfo.record]: corona_flare = 0.033864 13:18:28.641 [planetinfo.record]: corona_hues = 0.832855 13:18:28.641 [planetinfo.record]: sun_color = 0.773672, 0.660519, 0.447253, 1 13:18:28.641 [planetinfo.record]: corona_shimmer = 0.341579 13:18:28.641 [planetinfo.record]: station_vector = -0.767 0.507 0.393 13:18:28.641 [planetinfo.record]: station = dodecahedron 13:18:34.302 [PLANETINFO OVER]: Done 13:18:34.303 [PLANETINFO LOGGING]: 4 90 13:18:35.322 [planetinfo.record]: seed = 64 204 37 82 13:18:35.322 [planetinfo.record]: coordinates = 204 225 13:18:35.322 [planetinfo.record]: government = 0; 13:18:35.322 [planetinfo.record]: economy = 3; 13:18:35.322 [planetinfo.record]: techlevel = 4; 13:18:35.322 [planetinfo.record]: population = 20; 13:18:35.322 [planetinfo.record]: productivity = 4480; 13:18:35.322 [planetinfo.record]: name = "Enonis"; 13:18:35.323 [planetinfo.record]: inhabitant = "Human Colonial"; 13:18:35.323 [planetinfo.record]: inhabitants = "Human Colonials"; 13:18:35.323 [planetinfo.record]: description = "Enonis is an unremarkable dump."; 13:18:35.341 [planetinfo.record]: air_color = 0.617064, 0.811768, 0.987943, 1; 13:18:35.342 [planetinfo.record]: cloud_alpha = 1.000000; 13:18:35.342 [planetinfo.record]: cloud_color = 0.456963, 0.966797, 0.680015, 1; 13:18:35.342 [planetinfo.record]: cloud_fraction = 0.450000; 13:18:35.343 [planetinfo.record]: land_color = 0.552734, 0.455574, 0.498841, 1; 13:18:35.343 [planetinfo.record]: land_fraction = 0.540000; 13:18:35.343 [planetinfo.record]: polar_cloud_color = 0.626873, 0.935059, 0.761704, 1; 13:18:35.343 [planetinfo.record]: polar_land_color = 0.944727, 0.90321, 0.921698, 1; 13:18:35.343 [planetinfo.record]: polar_sea_color = 0.855678, 0.916602, 0.827985, 1; 13:18:35.343 [planetinfo.record]: sea_color = 0.612254, 0.833984, 0.511467, 1; 13:18:35.344 [planetinfo.record]: rotation_speed = 0.001904 13:18:35.344 [planetinfo.record]: planet zpos = 494480.000000 13:18:35.344 [planetinfo.record]: sun_radius = 75099.193115 13:18:35.344 [planetinfo.record]: sun_vector = 0.026 0.787 0.617 13:18:35.344 [planetinfo.record]: sun_distance = 741720 13:18:35.344 [planetinfo.record]: corona_flare = 0.095686 13:18:35.344 [planetinfo.record]: corona_hues = 0.519447 13:18:35.344 [planetinfo.record]: sun_color = 0.838641, 0.658928, 0.638524, 1 13:18:35.344 [planetinfo.record]: corona_shimmer = 0.377951 13:18:35.344 [planetinfo.record]: station_vector = 0.531 0.824 0.197 13:18:35.344 [planetinfo.record]: station = coriolis 13:18:41.077 [PLANETINFO OVER]: Done 13:18:41.079 [PLANETINFO LOGGING]: 4 91 13:18:42.084 [planetinfo.record]: seed = 140 111 243 140 13:18:42.084 [planetinfo.record]: coordinates = 111 29 13:18:42.084 [planetinfo.record]: government = 1; 13:18:42.084 [planetinfo.record]: economy = 7; 13:18:42.084 [planetinfo.record]: techlevel = 4; 13:18:42.084 [planetinfo.record]: population = 25; 13:18:42.084 [planetinfo.record]: productivity = 3000; 13:18:42.084 [planetinfo.record]: name = "Inquveri"; 13:18:42.084 [planetinfo.record]: inhabitant = "Black Horned Lizard"; 13:18:42.084 [planetinfo.record]: inhabitants = "Black Horned Lizards"; 13:18:42.084 [planetinfo.record]: description = "This world is ravaged by occasional solar activity."; 13:18:42.097 [planetinfo.record]: air_color = 0.659978, 0.54113, 0.905994, 1; 13:18:42.097 [planetinfo.record]: cloud_alpha = 1.000000; 13:18:42.097 [planetinfo.record]: cloud_color = 0.717272, 0.281525, 0.720703, 1; 13:18:42.098 [planetinfo.record]: cloud_fraction = 0.180000; 13:18:42.098 [planetinfo.record]: land_color = 0.264221, 0.293883, 0.609375, 1; 13:18:42.098 [planetinfo.record]: land_fraction = 0.360000; 13:18:42.098 [planetinfo.record]: polar_cloud_color = 0.821864, 0.510368, 0.824316, 1; 13:18:42.098 [planetinfo.record]: polar_land_color = 0.80609, 0.817517, 0.939062, 1; 13:18:42.098 [planetinfo.record]: polar_sea_color = 0.926367, 0.840156, 0.823236, 1; 13:18:42.098 [planetinfo.record]: sea_color = 0.736328, 0.462227, 0.408432, 1; 13:18:42.098 [planetinfo.record]: rotation_speed = 0.004026 13:18:42.098 [planetinfo.record]: planet zpos = 899850.000000 13:18:42.098 [planetinfo.record]: sun_radius = 140600.647125 13:18:42.098 [planetinfo.record]: sun_vector = 0.042 -0.051 -0.998 13:18:42.098 [planetinfo.record]: sun_distance = 1079820 13:18:42.098 [planetinfo.record]: corona_flare = 0.021577 13:18:42.098 [planetinfo.record]: corona_hues = 0.693794 13:18:42.098 [planetinfo.record]: sun_color = 0.737302, 0.353528, 0.279529, 1 13:18:42.099 [planetinfo.record]: corona_shimmer = 0.461820 13:18:42.099 [planetinfo.record]: station_vector = 0.424 0.739 0.524 13:18:42.099 [planetinfo.record]: station = coriolis 13:18:47.220 [PLANETINFO OVER]: Done 13:18:47.222 [PLANETINFO LOGGING]: 4 92 13:18:48.243 [planetinfo.record]: seed = 60 190 133 239 13:18:48.243 [planetinfo.record]: coordinates = 190 22 13:18:48.243 [planetinfo.record]: government = 7; 13:18:48.243 [planetinfo.record]: economy = 6; 13:18:48.243 [planetinfo.record]: techlevel = 7; 13:18:48.244 [planetinfo.record]: population = 42; 13:18:48.244 [planetinfo.record]: productivity = 14784; 13:18:48.244 [planetinfo.record]: name = "Azaenbi"; 13:18:48.244 [planetinfo.record]: inhabitant = "Slimy Lobster"; 13:18:48.244 [planetinfo.record]: inhabitants = "Slimy Lobsters"; 13:18:48.244 [planetinfo.record]: description = "This planet is beset by lethal disease."; 13:18:48.258 [planetinfo.record]: air_color = 0.585029, 0.920229, 0.971033, 1; 13:18:48.258 [planetinfo.record]: cloud_alpha = 1.000000; 13:18:48.259 [planetinfo.record]: cloud_color = 0.695063, 0.916016, 0.372131, 1; 13:18:48.259 [planetinfo.record]: cloud_fraction = 0.200000; 13:18:48.259 [planetinfo.record]: land_color = 0.0558929, 0.650391, 0.246318, 1; 13:18:48.259 [planetinfo.record]: land_fraction = 0.530000; 13:18:48.259 [planetinfo.record]: polar_cloud_color = 0.774686, 0.912207, 0.573693, 1; 13:18:48.259 [planetinfo.record]: polar_land_color = 0.721308, 0.934961, 0.789744, 1; 13:18:48.259 [planetinfo.record]: polar_sea_color = 0.909851, 0.936719, 0.889608, 1; 13:18:48.259 [planetinfo.record]: sea_color = 0.560209, 0.632812, 0.505508, 1; 13:18:48.259 [planetinfo.record]: rotation_speed = 0.003068 13:18:48.259 [planetinfo.record]: planet zpos = 958440.000000 13:18:48.260 [planetinfo.record]: sun_radius = 182072.512207 13:18:48.260 [planetinfo.record]: sun_vector = -0.117 -0.861 0.495 13:18:48.260 [planetinfo.record]: sun_distance = 1506120 13:18:48.260 [planetinfo.record]: corona_flare = 0.042224 13:18:48.260 [planetinfo.record]: corona_hues = 0.667931 13:18:48.260 [planetinfo.record]: sun_color = 0.240853, 0.416519, 0.755981, 1 13:18:48.260 [planetinfo.record]: corona_shimmer = 0.306940 13:18:48.261 [planetinfo.record]: station_vector = 0.914 0.307 0.266 13:18:48.261 [planetinfo.record]: station = coriolis 13:18:53.325 [PLANETINFO OVER]: Done 13:18:53.327 [PLANETINFO LOGGING]: 4 93 13:18:54.330 [planetinfo.record]: seed = 144 38 155 93 13:18:54.331 [planetinfo.record]: coordinates = 38 114 13:18:54.331 [planetinfo.record]: government = 2; 13:18:54.331 [planetinfo.record]: economy = 2; 13:18:54.331 [planetinfo.record]: techlevel = 8; 13:18:54.331 [planetinfo.record]: population = 37; 13:18:54.331 [planetinfo.record]: productivity = 14208; 13:18:54.331 [planetinfo.record]: name = "Isvequre"; 13:18:54.331 [planetinfo.record]: inhabitant = "Yellow Fat Humanoid"; 13:18:54.331 [planetinfo.record]: inhabitants = "Yellow Fat Humanoids"; 13:18:54.331 [planetinfo.record]: description = "The world Isvequre is very noted for its pink oceans but cursed by deadly disease."; 13:18:54.342 [planetinfo.record]: air_color = 0.771611, 0.60086, 0.889734, 1; 13:18:54.342 [planetinfo.record]: cloud_alpha = 1.000000; 13:18:54.342 [planetinfo.record]: cloud_color = 0.671875, 0.419922, 0.473068, 1; 13:18:54.342 [planetinfo.record]: cloud_fraction = 0.450000; 13:18:54.342 [planetinfo.record]: land_color = 0.66, 0.436952, 0.407344, 1; 13:18:54.342 [planetinfo.record]: land_fraction = 0.590000; 13:18:54.342 [planetinfo.record]: polar_cloud_color = 0.802344, 0.614294, 0.653961, 1; 13:18:54.342 [planetinfo.record]: polar_land_color = 0.934, 0.855088, 0.844613, 1; 13:18:54.342 [planetinfo.record]: polar_sea_color = 0.91, 0.8645, 0.8918, 1; 13:18:54.342 [planetinfo.record]: sea_color = 0.9, 0.72, 0.828, 1; 13:18:54.342 [planetinfo.record]: rotation_speed = 0.003837 13:18:54.342 [planetinfo.record]: planet zpos = 865480.000000 13:18:54.342 [planetinfo.record]: sun_radius = 141162.709961 13:18:54.342 [planetinfo.record]: sun_vector = 0.706 -0.181 0.684 13:18:54.343 [planetinfo.record]: sun_distance = 1112760 13:18:54.343 [planetinfo.record]: corona_flare = 0.012604 13:18:54.343 [planetinfo.record]: corona_hues = 0.843391 13:18:54.344 [planetinfo.record]: sun_color = 0.183562, 0.696709, 0.719879, 1 13:18:54.344 [planetinfo.record]: corona_shimmer = 0.250644 13:18:54.344 [planetinfo.record]: station_vector = -0.409 0.909 0.084 13:18:54.344 [planetinfo.record]: station = coriolis 13:18:59.772 [PLANETINFO OVER]: Done 13:18:59.774 [PLANETINFO LOGGING]: 4 94 13:19:00.775 [planetinfo.record]: seed = 232 206 53 64 13:19:00.775 [planetinfo.record]: coordinates = 206 122 13:19:00.775 [planetinfo.record]: government = 5; 13:19:00.775 [planetinfo.record]: economy = 2; 13:19:00.775 [planetinfo.record]: techlevel = 10; 13:19:00.775 [planetinfo.record]: population = 48; 13:19:00.775 [planetinfo.record]: productivity = 27648; 13:19:00.775 [planetinfo.record]: name = "Esed"; 13:19:00.775 [planetinfo.record]: inhabitant = "Human Colonial"; 13:19:00.775 [planetinfo.record]: inhabitants = "Human Colonials"; 13:19:00.775 [planetinfo.record]: description = "Esed is a revolting little planet."; 13:19:00.787 [planetinfo.record]: air_color = 0.525028, 0.960627, 0.820775, 1; 13:19:00.788 [planetinfo.record]: cloud_alpha = 1.000000; 13:19:00.788 [planetinfo.record]: cloud_color = 0.884766, 0.242414, 0.210823, 1; 13:19:00.788 [planetinfo.record]: cloud_fraction = 0.530000; 13:19:00.788 [planetinfo.record]: land_color = 0.205101, 0.509766, 0.359814, 1; 13:19:00.788 [planetinfo.record]: land_fraction = 0.390000; 13:19:00.788 [planetinfo.record]: polar_cloud_color = 0.898145, 0.490604, 0.470561, 1; 13:19:00.788 [planetinfo.record]: polar_land_color = 0.807226, 0.949023, 0.879232, 1; 13:19:00.788 [planetinfo.record]: polar_sea_color = 0.947656, 0.889446, 0.926282, 1; 13:19:00.788 [planetinfo.record]: sea_color = 0.523438, 0.394827, 0.476213, 1; 13:19:00.788 [planetinfo.record]: rotation_speed = 0.000333 13:19:00.788 [planetinfo.record]: planet zpos = 362640.000000 13:19:00.788 [planetinfo.record]: sun_radius = 97669.955444 13:19:00.789 [planetinfo.record]: sun_vector = 0.621 0.257 0.740 13:19:00.789 [planetinfo.record]: sun_distance = 574180 13:19:00.789 [planetinfo.record]: corona_flare = 0.046208 13:19:00.789 [planetinfo.record]: corona_hues = 0.658310 13:19:00.789 [planetinfo.record]: sun_color = 0.68343, 0.795466, 0.809219, 1 13:19:00.789 [planetinfo.record]: corona_shimmer = 0.356806 13:19:00.789 [planetinfo.record]: station_vector = -0.238 -0.762 0.602 13:19:00.789 [planetinfo.record]: station = coriolis 13:19:05.312 [PLANETINFO OVER]: Done 13:19:05.313 [PLANETINFO LOGGING]: 4 95 13:19:06.315 [planetinfo.record]: seed = 4 99 211 133 13:19:06.315 [planetinfo.record]: coordinates = 99 152 13:19:06.315 [planetinfo.record]: government = 0; 13:19:06.315 [planetinfo.record]: economy = 2; 13:19:06.315 [planetinfo.record]: techlevel = 8; 13:19:06.315 [planetinfo.record]: population = 35; 13:19:06.315 [planetinfo.record]: productivity = 8960; 13:19:06.315 [planetinfo.record]: name = "Celearen"; 13:19:06.315 [planetinfo.record]: inhabitant = "Fierce Black Bony Bird"; 13:19:06.315 [planetinfo.record]: inhabitants = "Fierce Black Bony Birds"; 13:19:06.315 [planetinfo.record]: description = "This world is mildly well known for Celearenian wolf meat and its ancient Celearenian Beitse banana plantations."; 13:19:06.332 [planetinfo.record]: air_color = 0.721833, 0.735704, 0.96648, 1; 13:19:06.332 [planetinfo.record]: cloud_alpha = 1.000000; 13:19:06.332 [planetinfo.record]: cloud_color = 0.757828, 0.780408, 0.902344, 1; 13:19:06.332 [planetinfo.record]: cloud_fraction = 0.350000; 13:19:06.332 [planetinfo.record]: land_color = 0.646484, 0.49674, 0.472237, 1; 13:19:06.332 [planetinfo.record]: land_fraction = 0.640000; 13:19:06.332 [planetinfo.record]: polar_cloud_color = 0.815361, 0.829532, 0.906055, 1; 13:19:06.332 [planetinfo.record]: polar_land_color = 0.935352, 0.881188, 0.872325, 1; 13:19:06.332 [planetinfo.record]: polar_sea_color = 0.729183, 0.849212, 0.928711, 1; 13:19:06.332 [planetinfo.record]: sea_color = 0.10025, 0.468792, 0.712891, 1; 13:19:06.332 [planetinfo.record]: rotation_speed = 0.004959 13:19:06.332 [planetinfo.record]: planet zpos = 503400.000000 13:19:06.332 [planetinfo.record]: sun_radius = 131625.678253 13:19:06.332 [planetinfo.record]: sun_vector = -0.558 0.605 -0.569 13:19:06.332 [planetinfo.record]: sun_distance = 797050 13:19:06.332 [planetinfo.record]: corona_flare = 0.094226 13:19:06.333 [planetinfo.record]: corona_hues = 0.845299 13:19:06.333 [planetinfo.record]: sun_color = 0.659036, 0.448699, 0.363938, 1 13:19:06.333 [planetinfo.record]: corona_shimmer = 1.112552 13:19:06.333 [planetinfo.record]: station_vector = 0.642 0.766 0.038 13:19:06.333 [planetinfo.record]: station = coriolis 13:19:11.956 [PLANETINFO OVER]: Done 13:19:11.958 [PLANETINFO LOGGING]: 4 96 13:19:12.976 [planetinfo.record]: seed = 68 114 181 94 13:19:12.977 [planetinfo.record]: coordinates = 114 106 13:19:12.977 [planetinfo.record]: government = 0; 13:19:12.977 [planetinfo.record]: economy = 2; 13:19:12.977 [planetinfo.record]: techlevel = 7; 13:19:12.977 [planetinfo.record]: population = 31; 13:19:12.977 [planetinfo.record]: productivity = 7936; 13:19:12.977 [planetinfo.record]: name = "Rianin"; 13:19:12.977 [planetinfo.record]: inhabitant = "Yellow Slimy Lizard"; 13:19:12.977 [planetinfo.record]: inhabitants = "Yellow Slimy Lizards"; 13:19:12.977 [planetinfo.record]: description = "This world is very notable for its inhabitants’ ingrained silliness and its weird rock formations."; 13:19:13.002 [planetinfo.record]: air_color = 0.632121, 0.873585, 0.946969, 1; 13:19:13.002 [planetinfo.record]: cloud_alpha = 1.000000; 13:19:13.002 [planetinfo.record]: cloud_color = 0.604746, 0.84375, 0.507568, 1; 13:19:13.002 [planetinfo.record]: cloud_fraction = 0.370000; 13:19:13.003 [planetinfo.record]: land_color = 0.464445, 0.66, 0.317109, 1; 13:19:13.003 [planetinfo.record]: land_fraction = 0.410000; 13:19:13.003 [planetinfo.record]: polar_cloud_color = 0.723948, 0.879687, 0.660625, 1; 13:19:13.003 [planetinfo.record]: polar_land_color = 0.864815, 0.934, 0.812689, 1; 13:19:13.003 [planetinfo.record]: polar_sea_color = 0.886377, 0.930078, 0.877307, 1; 13:19:13.003 [planetinfo.record]: sea_color = 0.567804, 0.699219, 0.540529, 1; 13:19:13.003 [planetinfo.record]: rotation_speed = 0.003927 13:19:13.003 [planetinfo.record]: planet zpos = 846820.000000 13:19:13.004 [planetinfo.record]: sun_radius = 164927.371216 13:19:13.004 [planetinfo.record]: sun_vector = -0.136 0.965 -0.225 13:19:13.004 [planetinfo.record]: sun_distance = 1433080 13:19:13.004 [planetinfo.record]: corona_flare = 0.018771 13:19:13.004 [planetinfo.record]: corona_hues = 0.530792 13:19:13.004 [planetinfo.record]: sun_color = 0.773901, 0.683046, 0.333038, 1 13:19:13.005 [planetinfo.record]: corona_shimmer = 0.758255 13:19:13.005 [planetinfo.record]: station_vector = -0.857 0.168 0.486 13:19:13.005 [planetinfo.record]: station = coriolis 13:19:18.425 [PLANETINFO OVER]: Done 13:19:18.426 [PLANETINFO LOGGING]: 4 97 13:19:19.431 [planetinfo.record]: seed = 232 166 27 239 13:19:19.431 [planetinfo.record]: coordinates = 166 12 13:19:19.431 [planetinfo.record]: government = 5; 13:19:19.431 [planetinfo.record]: economy = 4; 13:19:19.431 [planetinfo.record]: techlevel = 8; 13:19:19.431 [planetinfo.record]: population = 42; 13:19:19.431 [planetinfo.record]: productivity = 18144; 13:19:19.431 [planetinfo.record]: name = "Axeed"; 13:19:19.431 [planetinfo.record]: inhabitant = "Human Colonial"; 13:19:19.431 [planetinfo.record]: inhabitants = "Human Colonials"; 13:19:19.432 [planetinfo.record]: description = "The world Axeed is fabled for its exciting Zero-G cricket and its fabulous vicious Alab gargle blasters."; 13:19:19.465 [planetinfo.record]: air_color = 0.70191, 0.82537, 0.906645, 1; 13:19:19.465 [planetinfo.record]: cloud_alpha = 1.000000; 13:19:19.466 [planetinfo.record]: cloud_color = 0.666199, 0.722656, 0.677667, 1; 13:19:19.466 [planetinfo.record]: cloud_fraction = 0.270000; 13:19:19.466 [planetinfo.record]: land_color = 0.66, 0.0464063, 0.132693, 1; 13:19:19.466 [planetinfo.record]: land_fraction = 0.290000; 13:19:19.470 [planetinfo.record]: polar_cloud_color = 0.784903, 0.825195, 0.793087, 1; 13:19:19.470 [planetinfo.record]: polar_land_color = 0.934, 0.716918, 0.747445, 1; 13:19:19.470 [planetinfo.record]: polar_sea_color = 0.941602, 0.927898, 0.891487, 1; 13:19:19.470 [planetinfo.record]: sea_color = 0.583984, 0.549989, 0.45966, 1; 13:19:19.470 [planetinfo.record]: rotation_speed = 0.002361 13:19:19.470 [planetinfo.record]: planet zpos = 613980.000000 13:19:19.470 [planetinfo.record]: sun_radius = 204924.402466 13:19:19.470 [planetinfo.record]: sun_vector = -0.909 0.328 0.258 13:19:19.470 [planetinfo.record]: sun_distance = 1500840 13:19:19.470 [planetinfo.record]: corona_flare = 0.058731 13:19:19.470 [planetinfo.record]: corona_hues = 0.968369 13:19:19.470 [planetinfo.record]: sun_color = 0.5856, 0.593479, 0.75918, 1 13:19:19.471 [planetinfo.record]: corona_shimmer = 0.482102 13:19:19.471 [planetinfo.record]: station_vector = -0.796 0.537 0.279 13:19:19.471 [planetinfo.record]: station = coriolis 13:19:24.541 [PLANETINFO OVER]: Done 13:19:24.541 [PLANETINFO LOGGING]: 4 98 13:19:25.560 [planetinfo.record]: seed = 80 202 133 165 13:19:25.560 [planetinfo.record]: coordinates = 202 56 13:19:25.561 [planetinfo.record]: government = 2; 13:19:25.561 [planetinfo.record]: economy = 0; 13:19:25.561 [planetinfo.record]: techlevel = 10; 13:19:25.561 [planetinfo.record]: population = 43; 13:19:25.561 [planetinfo.record]: productivity = 20640; 13:19:25.561 [planetinfo.record]: name = "Ceused"; 13:19:25.561 [planetinfo.record]: inhabitant = "Fierce Harmless Horned Lobster"; 13:19:25.561 [planetinfo.record]: inhabitants = "Fierce Harmless Horned Lobsters"; 13:19:25.561 [planetinfo.record]: description = "The world Ceused is reasonably noted for its inhabitants’ eccentric shyness and its inhabitants’ eccentric shyness."; 13:19:25.563 [planetinfo.record]: air_color = 0.667333, 0.843557, 0.796606, 1; 13:19:25.564 [planetinfo.record]: cloud_alpha = 1.000000; 13:19:25.564 [planetinfo.record]: cloud_color = 0.533203, 0.521699, 0.518623, 1; 13:19:25.564 [planetinfo.record]: cloud_fraction = 0.440000; 13:19:25.564 [planetinfo.record]: land_color = 0.66, 0.0438281, 0.477074, 1; 13:19:25.564 [planetinfo.record]: land_fraction = 0.400000; 13:19:25.564 [planetinfo.record]: polar_cloud_color = 0.739941, 0.729963, 0.727296, 1; 13:19:25.564 [planetinfo.record]: polar_land_color = 0.934, 0.716006, 0.869283, 1; 13:19:25.564 [planetinfo.record]: polar_sea_color = 0.938672, 0.922453, 0.884038, 1; 13:19:25.564 [planetinfo.record]: sea_color = 0.613281, 0.570894, 0.470502, 1; 13:19:25.564 [planetinfo.record]: rotation_speed = 0.003817 13:19:25.564 [planetinfo.record]: planet zpos = 601720.000000 13:19:25.564 [planetinfo.record]: sun_radius = 107583.787842 13:19:25.564 [planetinfo.record]: sun_vector = 0.735 -0.607 -0.303 13:19:25.564 [planetinfo.record]: sun_distance = 816620 13:19:25.564 [planetinfo.record]: corona_flare = 0.003677 13:19:25.564 [planetinfo.record]: corona_hues = 0.506500 13:19:25.565 [planetinfo.record]: sun_color = 0.655225, 0.466278, 0.36214, 1 13:19:25.565 [planetinfo.record]: corona_shimmer = 0.426967 13:19:25.565 [planetinfo.record]: station_vector = 0.647 0.226 0.728 13:19:25.565 [planetinfo.record]: station = coriolis 13:19:30.853 [PLANETINFO OVER]: Done 13:19:30.854 [PLANETINFO LOGGING]: 4 99 13:19:31.871 [planetinfo.record]: seed = 60 102 243 38 13:19:31.871 [planetinfo.record]: coordinates = 102 24 13:19:31.871 [planetinfo.record]: government = 7; 13:19:31.871 [planetinfo.record]: economy = 0; 13:19:31.871 [planetinfo.record]: techlevel = 13; 13:19:31.871 [planetinfo.record]: population = 60; 13:19:31.872 [planetinfo.record]: productivity = 52800; 13:19:31.872 [planetinfo.record]: name = "Biceenon"; 13:19:31.872 [planetinfo.record]: inhabitant = "Fierce Red Rodent"; 13:19:31.872 [planetinfo.record]: inhabitants = "Fierce Red Rodents"; 13:19:31.872 [planetinfo.record]: description = "The world Biceenon is a dull place."; 13:19:31.881 [planetinfo.record]: air_color = 0.664782, 0.834524, 0.939164, 1; 13:19:31.882 [planetinfo.record]: cloud_alpha = 1.000000; 13:19:31.882 [planetinfo.record]: cloud_color = 0.592804, 0.820312, 0.628352, 1; 13:19:31.882 [planetinfo.record]: cloud_fraction = 0.280000; 13:19:31.882 [planetinfo.record]: land_color = 0.66, 0.397031, 0.477155, 1; 13:19:31.882 [planetinfo.record]: land_fraction = 0.690000; 13:19:31.882 [planetinfo.record]: polar_cloud_color = 0.718484, 0.869141, 0.742024, 1; 13:19:31.882 [planetinfo.record]: polar_land_color = 0.934, 0.840965, 0.869312, 1; 13:19:31.882 [planetinfo.record]: polar_sea_color = 0.914034, 0.935547, 0.885481, 1; 13:19:31.882 [planetinfo.record]: sea_color = 0.585247, 0.644531, 0.506561, 1; 13:19:31.887 [planetinfo.record]: rotation_speed = 0.000853 13:19:31.887 [planetinfo.record]: planet zpos = 489940.000000 13:19:31.888 [planetinfo.record]: sun_radius = 99614.210205 13:19:31.888 [planetinfo.record]: sun_vector = 0.344 0.848 -0.403 13:19:31.888 [planetinfo.record]: sun_distance = 801720 13:19:31.888 [planetinfo.record]: corona_flare = 0.095166 13:19:31.888 [planetinfo.record]: corona_hues = 0.517082 13:19:31.888 [planetinfo.record]: sun_color = 0.589048, 0.622097, 0.842111, 1 13:19:31.888 [planetinfo.record]: corona_shimmer = 0.541039 13:19:31.888 [planetinfo.record]: station_vector = -0.763 0.332 0.554 13:19:31.888 [planetinfo.record]: station = dodecahedron 13:19:36.892 [PLANETINFO OVER]: Done 13:19:36.892 [PLANETINFO LOGGING]: 4 100 13:19:37.897 [planetinfo.record]: seed = 12 255 37 215 13:19:37.897 [planetinfo.record]: coordinates = 255 50 13:19:37.897 [planetinfo.record]: government = 1; 13:19:37.897 [planetinfo.record]: economy = 2; 13:19:37.897 [planetinfo.record]: techlevel = 9; 13:19:37.897 [planetinfo.record]: population = 40; 13:19:37.897 [planetinfo.record]: productivity = 12800; 13:19:37.897 [planetinfo.record]: name = "Tiuson"; 13:19:37.897 [planetinfo.record]: inhabitant = "Human Colonial"; 13:19:37.897 [planetinfo.record]: inhabitants = "Human Colonials"; 13:19:37.897 [planetinfo.record]: description = "This world is a tedious place."; 13:19:37.928 [planetinfo.record]: air_color = 0.498708, 0.541951, 0.887133, 1; 13:19:37.928 [planetinfo.record]: cloud_alpha = 1.000000; 13:19:37.928 [planetinfo.record]: cloud_color = 0.189362, 0.341414, 0.664062, 1; 13:19:37.928 [planetinfo.record]: cloud_fraction = 0.570000; 13:19:37.928 [planetinfo.record]: land_color = 0.464063, 0.66, 0.545193, 1; 13:19:37.928 [planetinfo.record]: land_fraction = 0.460000; 13:19:37.928 [planetinfo.record]: polar_cloud_color = 0.44193, 0.556249, 0.798828, 1; 13:19:37.928 [planetinfo.record]: polar_land_color = 0.86468, 0.934, 0.893383, 1; 13:19:37.928 [planetinfo.record]: polar_sea_color = 0.949023, 0.824618, 0.724742, 1; 13:19:37.928 [planetinfo.record]: sea_color = 0.509766, 0.242468, 0.0278778, 1; 13:19:37.928 [planetinfo.record]: rotation_speed = 0.004942 13:19:37.928 [planetinfo.record]: planet zpos = 583560.000000 13:19:37.928 [planetinfo.record]: sun_radius = 161658.983917 13:19:37.928 [planetinfo.record]: sun_vector = -0.922 -0.211 0.325 13:19:37.929 [planetinfo.record]: sun_distance = 826710 13:19:37.929 [planetinfo.record]: corona_flare = 0.007896 13:19:37.929 [planetinfo.record]: corona_hues = 0.781479 13:19:37.929 [planetinfo.record]: sun_color = 0.823856, 0.804394, 0.753521, 1 13:19:37.929 [planetinfo.record]: corona_shimmer = 0.312544 13:19:37.929 [planetinfo.record]: station_vector = -0.729 -0.629 0.270 13:19:37.929 [planetinfo.record]: station = coriolis 13:19:43.469 [PLANETINFO OVER]: Done 13:19:43.470 [PLANETINFO LOGGING]: 4 101 13:19:44.474 [planetinfo.record]: seed = 0 191 219 166 13:19:44.474 [planetinfo.record]: coordinates = 191 223 13:19:44.474 [planetinfo.record]: government = 0; 13:19:44.474 [planetinfo.record]: economy = 7; 13:19:44.474 [planetinfo.record]: techlevel = 3; 13:19:44.474 [planetinfo.record]: population = 20; 13:19:44.474 [planetinfo.record]: productivity = 1920; 13:19:44.474 [planetinfo.record]: name = "Bizaar"; 13:19:44.474 [planetinfo.record]: inhabitant = "Fierce Harmless Slimy Lizard"; 13:19:44.474 [planetinfo.record]: inhabitants = "Fierce Harmless Slimy Lizards"; 13:19:44.474 [planetinfo.record]: description = "The planet Bizaar is most famous for the Bizaarian spotted shrew."; 13:19:44.497 [planetinfo.record]: air_color = 0.691329, 0.79428, 0.943717, 1; 13:19:44.497 [planetinfo.record]: cloud_alpha = 1.000000; 13:19:44.497 [planetinfo.record]: cloud_color = 0.664581, 0.833984, 0.798251, 1; 13:19:44.497 [planetinfo.record]: cloud_fraction = 0.110000; 13:19:44.497 [planetinfo.record]: land_color = 0.2005, 0.570312, 0.54431, 1; 13:19:44.497 [planetinfo.record]: land_fraction = 0.290000; 13:19:44.497 [planetinfo.record]: polar_cloud_color = 0.764172, 0.875293, 0.851853, 1; 13:19:44.498 [planetinfo.record]: polar_land_color = 0.790105, 0.942969, 0.93222, 1; 13:19:44.498 [planetinfo.record]: polar_sea_color = 0.88064, 0.926367, 0.867836, 1; 13:19:44.498 [planetinfo.record]: sea_color = 0.590941, 0.736328, 0.550233, 1; 13:19:44.498 [planetinfo.record]: rotation_speed = 0.000769 13:19:44.498 [planetinfo.record]: planet zpos = 681450.000000 13:19:44.498 [planetinfo.record]: sun_radius = 99788.503418 13:19:44.498 [planetinfo.record]: sun_vector = -0.175 -0.963 0.206 13:19:44.498 [planetinfo.record]: sun_distance = 954030 13:19:44.498 [planetinfo.record]: corona_flare = 0.057594 13:19:44.498 [planetinfo.record]: corona_hues = 0.644150 13:19:44.498 [planetinfo.record]: sun_color = 0.549397, 0.63543, 0.743427, 1 13:19:44.498 [planetinfo.record]: corona_shimmer = 0.431572 13:19:44.499 [planetinfo.record]: station_vector = 0.663 0.342 0.666 13:19:44.499 [planetinfo.record]: station = coriolis 13:19:49.576 [PLANETINFO OVER]: Done 13:19:49.577 [PLANETINFO LOGGING]: 4 102 13:19:50.596 [planetinfo.record]: seed = 120 150 21 134 13:19:50.597 [planetinfo.record]: coordinates = 150 170 13:19:50.597 [planetinfo.record]: government = 7; 13:19:50.597 [planetinfo.record]: economy = 2; 13:19:50.597 [planetinfo.record]: techlevel = 11; 13:19:50.597 [planetinfo.record]: population = 54; 13:19:50.597 [planetinfo.record]: productivity = 38016; 13:19:50.597 [planetinfo.record]: name = "Bisoge"; 13:19:50.597 [planetinfo.record]: inhabitant = "Human Colonial"; 13:19:50.597 [planetinfo.record]: inhabitants = "Human Colonials"; 13:19:50.597 [planetinfo.record]: description = "Bisoge is well known for the Bisogeian spotted shrew but plagued by evil tree leopards."; 13:19:50.620 [planetinfo.record]: air_color = 0.688024, 0.793915, 0.947619, 1; 13:19:50.620 [planetinfo.record]: cloud_alpha = 1.000000; 13:19:50.620 [planetinfo.record]: cloud_color = 0.657402, 0.845703, 0.805983, 1; 13:19:50.620 [planetinfo.record]: cloud_fraction = 0.400000; 13:19:50.620 [planetinfo.record]: land_color = 0.234609, 0.66, 0.251226, 1; 13:19:50.620 [planetinfo.record]: land_fraction = 0.530000; 13:19:50.620 [planetinfo.record]: polar_cloud_color = 0.758027, 0.880566, 0.854718, 1; 13:19:50.620 [planetinfo.record]: polar_land_color = 0.783502, 0.934, 0.789381, 1; 13:19:50.620 [planetinfo.record]: polar_sea_color = 0.941992, 0.890305, 0.884957, 1; 13:19:50.620 [planetinfo.record]: sea_color = 0.580078, 0.452761, 0.43959, 1; 13:19:50.620 [planetinfo.record]: rotation_speed = 0.002232 13:19:50.620 [planetinfo.record]: planet zpos = 585260.000000 13:19:50.620 [planetinfo.record]: sun_radius = 96940.419617 13:19:50.620 [planetinfo.record]: sun_vector = -0.589 0.335 0.735 13:19:50.620 [planetinfo.record]: sun_distance = 810360 13:19:50.620 [planetinfo.record]: corona_flare = 0.016490 13:19:50.620 [planetinfo.record]: corona_hues = 0.778969 13:19:50.620 [planetinfo.record]: sun_color = 0.651694, 0.606855, 0.600942, 1 13:19:50.621 [planetinfo.record]: corona_shimmer = 0.371355 13:19:50.621 [planetinfo.record]: station_vector = 0.207 -0.971 0.120 13:19:50.621 [planetinfo.record]: station = dodecahedron 13:19:56.401 [PLANETINFO OVER]: Done 13:19:56.402 [PLANETINFO LOGGING]: 4 103 13:19:57.407 [planetinfo.record]: seed = 52 49 83 220 13:19:57.407 [planetinfo.record]: coordinates = 49 227 13:19:57.407 [planetinfo.record]: government = 6; 13:19:57.407 [planetinfo.record]: economy = 3; 13:19:57.407 [planetinfo.record]: techlevel = 8; 13:19:57.407 [planetinfo.record]: population = 42; 13:19:57.407 [planetinfo.record]: productivity = 23520; 13:19:57.407 [planetinfo.record]: name = "Teatriin"; 13:19:57.407 [planetinfo.record]: inhabitant = "Human Colonial"; 13:19:57.407 [planetinfo.record]: inhabitants = "Human Colonials"; 13:19:57.407 [planetinfo.record]: description = "Teatriin is mildly well known for its exotic night life and Teatriinian Oubeab brandy."; 13:19:57.410 [planetinfo.record]: air_color = 0.468481, 0.612214, 0.884531, 1; 13:19:57.411 [planetinfo.record]: cloud_alpha = 1.000000; 13:19:57.411 [planetinfo.record]: cloud_color = 0.123047, 0.65625, 0.643753, 1; 13:19:57.411 [planetinfo.record]: cloud_fraction = 0.300000; 13:19:57.411 [planetinfo.record]: land_color = 0.492422, 0.66, 0.656072, 1; 13:19:57.411 [planetinfo.record]: land_fraction = 0.370000; 13:19:57.411 [planetinfo.record]: polar_cloud_color = 0.391443, 0.795313, 0.785847, 1; 13:19:57.411 [planetinfo.record]: polar_land_color = 0.874713, 0.934, 0.93261, 1; 13:19:57.411 [planetinfo.record]: polar_sea_color = 0.894436, 0.947266, 0.748377, 1; 13:19:57.411 [planetinfo.record]: sea_color = 0.409702, 0.527344, 0.0844574, 1; 13:19:57.411 [planetinfo.record]: rotation_speed = 0.001866 13:19:57.411 [planetinfo.record]: planet zpos = 712440.000000 13:19:57.411 [planetinfo.record]: sun_radius = 143562.052002 13:19:57.411 [planetinfo.record]: sun_vector = 0.899 -0.098 0.427 13:19:57.411 [planetinfo.record]: sun_distance = 949920 13:19:57.411 [planetinfo.record]: corona_flare = 0.040001 13:19:57.412 [planetinfo.record]: corona_hues = 0.803406 13:19:57.412 [planetinfo.record]: sun_color = 0.54525, 0.651374, 0.656821, 1 13:19:57.412 [planetinfo.record]: corona_shimmer = 0.333853 13:19:57.412 [planetinfo.record]: station_vector = 0.324 -0.082 0.943 13:19:57.412 [planetinfo.record]: station = coriolis 13:20:02.894 [PLANETINFO OVER]: Done 13:20:02.896 [PLANETINFO LOGGING]: 4 104 13:20:03.897 [planetinfo.record]: seed = 148 204 213 188 13:20:03.897 [planetinfo.record]: coordinates = 204 254 13:20:03.897 [planetinfo.record]: government = 2; 13:20:03.897 [planetinfo.record]: economy = 6; 13:20:03.897 [planetinfo.record]: techlevel = 2; 13:20:03.897 [planetinfo.record]: population = 17; 13:20:03.897 [planetinfo.record]: productivity = 3264; 13:20:03.897 [planetinfo.record]: name = "Teusatve"; 13:20:03.898 [planetinfo.record]: inhabitant = "Harmless Horned Lizard"; 13:20:03.898 [planetinfo.record]: inhabitants = "Harmless Horned Lizards"; 13:20:03.898 [planetinfo.record]: description = "The world Teusatve is notable for its weird exuberant forests and its inhabitants’ eccentric shyness."; 13:20:03.914 [planetinfo.record]: air_color = 0.747173, 0.630317, 0.851361, 1; 13:20:03.914 [planetinfo.record]: cloud_alpha = 1.000000; 13:20:03.914 [planetinfo.record]: cloud_color = 0.556641, 0.456619, 0.496472, 1; 13:20:03.914 [planetinfo.record]: cloud_fraction = 0.380000; 13:20:03.914 [planetinfo.record]: land_color = 0.48926, 0.66, 0.213984, 1; 13:20:03.914 [planetinfo.record]: land_fraction = 0.640000; 13:20:03.914 [planetinfo.record]: polar_cloud_color = 0.750488, 0.666205, 0.699787, 1; 13:20:03.914 [planetinfo.record]: polar_land_color = 0.873594, 0.934, 0.776205, 1; 13:20:03.914 [planetinfo.record]: polar_sea_color = 0.902096, 0.935547, 0.888495, 1; 13:20:03.914 [planetinfo.record]: sea_color = 0.55235, 0.644531, 0.51487, 1; 13:20:03.914 [planetinfo.record]: rotation_speed = 0.000859 13:20:03.914 [planetinfo.record]: planet zpos = 609200.000000 13:20:03.914 [planetinfo.record]: sun_radius = 158037.277832 13:20:03.915 [planetinfo.record]: sun_vector = 0.406 -0.738 -0.539 13:20:03.915 [planetinfo.record]: sun_distance = 1096560 13:20:03.915 [planetinfo.record]: corona_flare = 0.014960 13:20:03.915 [planetinfo.record]: corona_hues = 0.533142 13:20:03.915 [planetinfo.record]: sun_color = 0.718137, 0.670313, 0.65635, 1 13:20:03.915 [planetinfo.record]: corona_shimmer = 0.320178 13:20:03.915 [planetinfo.record]: station_vector = -0.716 0.425 0.554 13:20:03.915 [planetinfo.record]: station = coriolis 13:20:09.071 [PLANETINFO OVER]: Done 13:20:09.072 [PLANETINFO LOGGING]: 4 105 13:20:10.078 [planetinfo.record]: seed = 216 86 219 240 13:20:10.078 [planetinfo.record]: coordinates = 86 17 13:20:10.078 [planetinfo.record]: government = 3; 13:20:10.078 [planetinfo.record]: economy = 1; 13:20:10.078 [planetinfo.record]: techlevel = 10; 13:20:10.078 [planetinfo.record]: population = 45; 13:20:10.078 [planetinfo.record]: productivity = 22680; 13:20:10.078 [planetinfo.record]: name = "Erorle"; 13:20:10.078 [planetinfo.record]: inhabitant = "Insect"; 13:20:10.078 [planetinfo.record]: inhabitants = "Insects"; 13:20:10.078 [planetinfo.record]: description = "The planet Erorle is well known for its inhabitants’ weird silliness but ravaged by occasional earthquakes."; 13:20:10.082 [planetinfo.record]: air_color = 0.609594, 0.462238, 0.962578, 1; 13:20:10.082 [planetinfo.record]: cloud_alpha = 1.000000; 13:20:10.082 [planetinfo.record]: cloud_color = 0.803704, 0.03479, 0.890625, 1; 13:20:10.082 [planetinfo.record]: cloud_fraction = 0.170000; 13:20:10.082 [planetinfo.record]: land_color = 0.569685, 0.507891, 0.66, 1; 13:20:10.082 [planetinfo.record]: land_fraction = 0.470000; 13:20:10.082 [planetinfo.record]: polar_cloud_color = 0.845836, 0.359785, 0.900781, 1; 13:20:10.082 [planetinfo.record]: polar_land_color = 0.902048, 0.880186, 0.934, 1; 13:20:10.082 [planetinfo.record]: polar_sea_color = 0.931836, 0.890779, 0.863586, 1; 13:20:10.082 [planetinfo.record]: sea_color = 0.681641, 0.561509, 0.481941, 1; 13:20:10.082 [planetinfo.record]: rotation_speed = 0.004224 13:20:10.083 [planetinfo.record]: planet zpos = 377260.000000 13:20:10.083 [planetinfo.record]: sun_radius = 75867.975769 13:20:10.083 [planetinfo.record]: sun_vector = 0.132 0.955 0.267 13:20:10.083 [planetinfo.record]: sun_distance = 609420 13:20:10.083 [planetinfo.record]: corona_flare = 0.059872 13:20:10.083 [planetinfo.record]: corona_hues = 0.784340 13:20:10.083 [planetinfo.record]: sun_color = 0.704172, 0.603449, 0.517196, 1 13:20:10.083 [planetinfo.record]: corona_shimmer = 0.901557 13:20:10.083 [planetinfo.record]: station_vector = -0.416 -0.905 0.089 13:20:10.083 [planetinfo.record]: station = coriolis 13:20:14.818 [PLANETINFO OVER]: Done 13:20:14.820 [PLANETINFO LOGGING]: 4 106 13:20:15.837 [planetinfo.record]: seed = 96 235 229 229 13:20:15.837 [planetinfo.record]: coordinates = 235 161 13:20:15.837 [planetinfo.record]: government = 4; 13:20:15.837 [planetinfo.record]: economy = 1; 13:20:15.837 [planetinfo.record]: techlevel = 11; 13:20:15.837 [planetinfo.record]: population = 50; 13:20:15.837 [planetinfo.record]: productivity = 28800; 13:20:15.838 [planetinfo.record]: name = "Ceenge"; 13:20:15.838 [planetinfo.record]: inhabitant = "Fierce Horned Lobster"; 13:20:15.838 [planetinfo.record]: inhabitants = "Fierce Horned Lobsters"; 13:20:15.838 [planetinfo.record]: description = "This world is most notable for its fabulous cuisine but beset by occasional civil war."; 13:20:15.847 [planetinfo.record]: air_color = 0.694905, 0.718173, 0.985342, 1; 13:20:15.847 [planetinfo.record]: cloud_alpha = 1.000000; 13:20:15.847 [planetinfo.record]: cloud_color = 0.685524, 0.74748, 0.958984, 1; 13:20:15.847 [planetinfo.record]: cloud_fraction = 0.420000; 13:20:15.847 [planetinfo.record]: land_color = 0.66, 0.622939, 0.45375, 1; 13:20:15.848 [planetinfo.record]: land_fraction = 0.410000; 13:20:15.848 [planetinfo.record]: polar_cloud_color = 0.765521, 0.803135, 0.931543, 1; 13:20:15.848 [planetinfo.record]: polar_land_color = 0.934, 0.920888, 0.861031, 1; 13:20:15.848 [planetinfo.record]: polar_sea_color = 0.913194, 0.929297, 0.868675, 1; 13:20:15.848 [planetinfo.record]: sea_color = 0.658026, 0.707031, 0.52254, 1; 13:20:15.848 [planetinfo.record]: rotation_speed = 0.000726 13:20:15.848 [planetinfo.record]: planet zpos = 476410.000000 13:20:15.848 [planetinfo.record]: sun_radius = 124424.390717 13:20:15.849 [planetinfo.record]: sun_vector = -0.045 -0.883 -0.467 13:20:15.849 [planetinfo.record]: sun_distance = 779580 13:20:15.849 [planetinfo.record]: corona_flare = 0.013062 13:20:15.849 [planetinfo.record]: corona_hues = 0.969788 13:20:15.849 [planetinfo.record]: sun_color = 0.56775, 0.63618, 0.719202, 1 13:20:15.849 [planetinfo.record]: corona_shimmer = 0.790257 13:20:15.849 [planetinfo.record]: station_vector = 0.360 -0.710 0.606 13:20:15.850 [planetinfo.record]: station = dodecahedron 13:20:20.972 [PLANETINFO OVER]: Done 13:20:20.972 [PLANETINFO LOGGING]: 4 107 13:20:21.989 [planetinfo.record]: seed = 236 155 243 81 13:20:21.989 [planetinfo.record]: coordinates = 155 67 13:20:21.989 [planetinfo.record]: government = 5; 13:20:21.989 [planetinfo.record]: economy = 3; 13:20:21.989 [planetinfo.record]: techlevel = 10; 13:20:21.989 [planetinfo.record]: population = 49; 13:20:21.989 [planetinfo.record]: productivity = 24696; 13:20:21.989 [planetinfo.record]: name = "Ataton"; 13:20:21.989 [planetinfo.record]: inhabitant = "Yellow Slimy Frog"; 13:20:21.989 [planetinfo.record]: inhabitants = "Yellow Slimy Frogs"; 13:20:21.989 [planetinfo.record]: description = "This world is ravaged by unpredictable civil war."; 13:20:21.992 [planetinfo.record]: air_color = 0.564601, 0.54786, 0.86632, 1; 13:20:21.992 [planetinfo.record]: cloud_alpha = 1.000000; 13:20:21.992 [planetinfo.record]: cloud_color = 0.350532, 0.298431, 0.601562, 1; 13:20:21.992 [planetinfo.record]: cloud_fraction = 0.350000; 13:20:21.992 [planetinfo.record]: land_color = 0.466898, 0.378891, 0.521484, 1; 13:20:21.992 [planetinfo.record]: land_fraction = 0.690000; 13:20:21.992 [planetinfo.record]: polar_cloud_color = 0.569695, 0.527977, 0.770703, 1; 13:20:21.992 [planetinfo.record]: polar_land_color = 0.923047, 0.883057, 0.947852, 1; 13:20:21.993 [planetinfo.record]: polar_sea_color = 0.842255, 0.916211, 0.833895, 1; 13:20:21.993 [planetinfo.record]: sea_color = 0.567356, 0.837891, 0.536774, 1; 13:20:21.993 [planetinfo.record]: rotation_speed = 0.002756 13:20:21.993 [planetinfo.record]: planet zpos = 354970.000000 13:20:21.993 [planetinfo.record]: sun_radius = 96307.750854 13:20:21.993 [planetinfo.record]: sun_vector = -0.318 -0.654 0.687 13:20:21.993 [planetinfo.record]: sun_distance = 580860 13:20:21.993 [planetinfo.record]: corona_flare = 0.033188 13:20:21.993 [planetinfo.record]: corona_hues = 0.905914 13:20:21.993 [planetinfo.record]: sun_color = 0.407578, 0.470032, 0.835236, 1 13:20:21.993 [planetinfo.record]: corona_shimmer = 0.770051 13:20:21.993 [planetinfo.record]: station_vector = 0.738 0.410 0.536 13:20:21.993 [planetinfo.record]: station = coriolis 13:20:27.404 [PLANETINFO OVER]: Done 13:20:27.405 [PLANETINFO LOGGING]: 4 108 13:20:28.419 [planetinfo.record]: seed = 220 162 197 243 13:20:28.419 [planetinfo.record]: coordinates = 162 31 13:20:28.419 [planetinfo.record]: government = 3; 13:20:28.419 [planetinfo.record]: economy = 7; 13:20:28.419 [planetinfo.record]: techlevel = 4; 13:20:28.419 [planetinfo.record]: population = 27; 13:20:28.419 [planetinfo.record]: productivity = 4536; 13:20:28.419 [planetinfo.record]: name = "Beveinve"; 13:20:28.419 [planetinfo.record]: inhabitant = "Furry Rodent"; 13:20:28.419 [planetinfo.record]: inhabitants = "Furry Rodents"; 13:20:28.419 [planetinfo.record]: description = "The world Beveinve is fabled for its exciting sit coms and its fabulous monkey steak."; 13:20:28.420 [planetinfo.record]: air_color = 0.643431, 0.858516, 0.786104, 1; 13:20:28.421 [planetinfo.record]: cloud_alpha = 1.000000; 13:20:28.421 [planetinfo.record]: cloud_color = 0.578125, 0.490051, 0.490051, 1; 13:20:28.421 [planetinfo.record]: cloud_fraction = 0.190000; 13:20:28.421 [planetinfo.record]: land_color = 0.634766, 0.409126, 0.525472, 1; 13:20:28.421 [planetinfo.record]: land_fraction = 0.300000; 13:20:28.421 [planetinfo.record]: polar_cloud_color = 0.760156, 0.687778, 0.687778, 1; 13:20:28.421 [planetinfo.record]: polar_land_color = 0.936523, 0.853297, 0.896211, 1; 13:20:28.421 [planetinfo.record]: polar_sea_color = 0.932422, 0.874942, 0.859576, 1; 13:20:28.421 [planetinfo.record]: sea_color = 0.675781, 0.509146, 0.4646, 1; 13:20:28.421 [planetinfo.record]: rotation_speed = 0.001815 13:20:28.421 [planetinfo.record]: planet zpos = 486980.000000 13:20:28.421 [planetinfo.record]: sun_radius = 76786.826782 13:20:28.421 [planetinfo.record]: sun_vector = -0.046 0.982 -0.185 13:20:28.421 [planetinfo.record]: sun_distance = 786660 13:20:28.422 [planetinfo.record]: corona_flare = 0.019711 13:20:28.422 [planetinfo.record]: corona_hues = 0.927711 13:20:28.422 [planetinfo.record]: sun_color = 0.725577, 0.697156, 0.213313, 1 13:20:28.422 [planetinfo.record]: corona_shimmer = 0.441564 13:20:28.422 [planetinfo.record]: station_vector = -0.123 -0.576 0.808 13:20:28.422 [planetinfo.record]: station = coriolis 13:20:33.596 [PLANETINFO OVER]: Done 13:20:33.598 [PLANETINFO LOGGING]: 4 109 13:20:34.602 [planetinfo.record]: seed = 112 246 27 249 13:20:34.602 [planetinfo.record]: coordinates = 246 76 13:20:34.602 [planetinfo.record]: government = 6; 13:20:34.602 [planetinfo.record]: economy = 4; 13:20:34.602 [planetinfo.record]: techlevel = 8; 13:20:34.602 [planetinfo.record]: population = 43; 13:20:34.602 [planetinfo.record]: productivity = 20640; 13:20:34.602 [planetinfo.record]: name = "Ortema"; 13:20:34.602 [planetinfo.record]: inhabitant = "Human Colonial"; 13:20:34.602 [planetinfo.record]: inhabitants = "Human Colonials"; 13:20:34.602 [planetinfo.record]: description = "This world is a revolting little planet."; 13:20:34.626 [planetinfo.record]: air_color = 0.791832, 0.528716, 0.951522, 1; 13:20:34.626 [planetinfo.record]: cloud_alpha = 1.000000; 13:20:34.626 [planetinfo.record]: cloud_color = 0.857422, 0.227753, 0.301542, 1; 13:20:34.626 [planetinfo.record]: cloud_fraction = 0.410000; 13:20:34.626 [planetinfo.record]: land_color = 0.66, 0.0696094, 0.484728, 1; 13:20:34.628 [planetinfo.record]: land_fraction = 0.710000; 13:20:34.629 [planetinfo.record]: polar_cloud_color = 0.88584, 0.479253, 0.5269, 1; 13:20:34.629 [planetinfo.record]: polar_land_color = 0.934, 0.725127, 0.871991, 1; 13:20:34.629 [planetinfo.record]: polar_sea_color = 0.941406, 0.922401, 0.889647, 1; 13:20:34.629 [planetinfo.record]: sea_color = 0.585938, 0.538622, 0.457077, 1; 13:20:34.629 [planetinfo.record]: rotation_speed = 0.002690 13:20:34.629 [planetinfo.record]: planet zpos = 482940.000000 13:20:34.629 [planetinfo.record]: sun_radius = 116185.821533 13:20:34.629 [planetinfo.record]: sun_vector = 0.268 -0.817 0.510 13:20:34.629 [planetinfo.record]: sun_distance = 1234180 13:20:34.629 [planetinfo.record]: corona_flare = 0.005724 13:20:34.629 [planetinfo.record]: corona_hues = 0.567802 13:20:34.629 [planetinfo.record]: sun_color = 0.680542, 0.675685, 0.670534, 1 13:20:34.630 [planetinfo.record]: corona_shimmer = 0.384587 13:20:34.630 [planetinfo.record]: station_vector = -0.225 0.337 0.914 13:20:34.631 [planetinfo.record]: station = coriolis 13:20:39.705 [PLANETINFO OVER]: Done 13:20:39.706 [PLANETINFO LOGGING]: 4 110 13:20:40.709 [planetinfo.record]: seed = 8 97 245 200 13:20:40.710 [planetinfo.record]: coordinates = 97 43 13:20:40.710 [planetinfo.record]: government = 1; 13:20:40.710 [planetinfo.record]: economy = 3; 13:20:40.710 [planetinfo.record]: techlevel = 6; 13:20:40.710 [planetinfo.record]: population = 29; 13:20:40.710 [planetinfo.record]: productivity = 8120; 13:20:40.710 [planetinfo.record]: name = "Uslaon"; 13:20:40.710 [planetinfo.record]: inhabitant = "Small Horned Lizard"; 13:20:40.710 [planetinfo.record]: inhabitants = "Small Horned Lizards"; 13:20:40.710 [planetinfo.record]: description = "Uslaon is cursed by vicious mountain beasts."; 13:20:40.724 [planetinfo.record]: air_color = 0.734103, 0.482422, 0.972984, 1; 13:20:40.724 [planetinfo.record]: cloud_alpha = 1.000000; 13:20:40.724 [planetinfo.record]: cloud_color = 0.921875, 0.0792236, 0.454467, 1; 13:20:40.724 [planetinfo.record]: cloud_fraction = 0.560000; 13:20:40.724 [planetinfo.record]: land_color = 0.0464063, 0.127899, 0.66, 1; 13:20:40.724 [planetinfo.record]: land_fraction = 0.660000; 13:20:40.724 [planetinfo.record]: polar_cloud_color = 0.914844, 0.392204, 0.624942, 1; 13:20:40.724 [planetinfo.record]: polar_land_color = 0.716918, 0.745749, 0.934, 1; 13:20:40.724 [planetinfo.record]: polar_sea_color = 0.938086, 0.921713, 0.882937, 1; 13:20:40.724 [planetinfo.record]: sea_color = 0.619141, 0.575917, 0.473546, 1; 13:20:40.724 [planetinfo.record]: rotation_speed = 0.004130 13:20:40.724 [planetinfo.record]: planet zpos = 644930.000000 13:20:40.725 [planetinfo.record]: sun_radius = 102898.964233 13:20:40.725 [planetinfo.record]: sun_vector = -0.367 0.859 0.357 13:20:40.725 [planetinfo.record]: sun_distance = 892980 13:20:40.725 [planetinfo.record]: corona_flare = 0.080315 13:20:40.725 [planetinfo.record]: corona_hues = 0.980263 13:20:40.725 [planetinfo.record]: sun_color = 0.388715, 0.420189, 0.809085, 1 13:20:40.725 [planetinfo.record]: corona_shimmer = 0.355544 13:20:40.726 [planetinfo.record]: station_vector = -0.234 0.186 0.954 13:20:40.726 [planetinfo.record]: station = coriolis 13:20:45.853 [PLANETINFO OVER]: Done 13:20:45.854 [PLANETINFO LOGGING]: 4 111 13:20:46.864 [planetinfo.record]: seed = 100 158 211 115 13:20:46.864 [planetinfo.record]: coordinates = 158 127 13:20:46.864 [planetinfo.record]: government = 4; 13:20:46.864 [planetinfo.record]: economy = 7; 13:20:46.864 [planetinfo.record]: techlevel = 4; 13:20:46.864 [planetinfo.record]: population = 28; 13:20:46.864 [planetinfo.record]: productivity = 5376; 13:20:46.864 [planetinfo.record]: name = "Beatza"; 13:20:46.864 [planetinfo.record]: inhabitant = "Blue Bug-Eyed Bird"; 13:20:46.864 [planetinfo.record]: inhabitants = "Blue Bug-Eyed Birds"; 13:20:46.864 [planetinfo.record]: description = "Beatza is reasonably well known for the Beatzaian tree grub but ravaged by unpredictable civil war."; 13:20:46.883 [planetinfo.record]: air_color = 0.639837, 0.49335, 0.943066, 1; 13:20:46.883 [planetinfo.record]: cloud_alpha = 1.000000; 13:20:46.883 [planetinfo.record]: cloud_color = 0.826623, 0.139755, 0.832031, 1; 13:20:46.883 [planetinfo.record]: cloud_fraction = 0.300000; 13:20:46.883 [planetinfo.record]: land_color = 0.580078, 0.66, 0.605678, 1; 13:20:46.883 [planetinfo.record]: land_fraction = 0.420000; 13:20:46.883 [planetinfo.record]: polar_cloud_color = 0.870862, 0.419702, 0.874414, 1; 13:20:46.883 [planetinfo.record]: polar_land_color = 0.905725, 0.934, 0.914782, 1; 13:20:46.883 [planetinfo.record]: polar_sea_color = 0.746997, 0.937695, 0.719754, 1; 13:20:46.883 [planetinfo.record]: sea_color = 0.116213, 0.623047, 0.043808, 1; 13:20:46.883 [planetinfo.record]: rotation_speed = 0.003709 13:20:46.883 [planetinfo.record]: planet zpos = 486460.000000 13:20:46.883 [planetinfo.record]: sun_radius = 102492.749634 13:20:46.883 [planetinfo.record]: sun_vector = 0.085 0.463 -0.882 13:20:46.883 [planetinfo.record]: sun_distance = 785820 13:20:46.883 [planetinfo.record]: corona_flare = 0.057152 13:20:46.883 [planetinfo.record]: corona_hues = 0.867287 13:20:46.883 [planetinfo.record]: sun_color = 0.615815, 0.630328, 0.716479, 1 13:20:46.883 [planetinfo.record]: corona_shimmer = 0.591843 13:20:46.883 [planetinfo.record]: station_vector = -0.769 0.120 0.627 13:20:46.884 [planetinfo.record]: station = coriolis 13:20:51.465 [PLANETINFO OVER]: Done 13:20:51.465 [PLANETINFO LOGGING]: 4 112 13:20:52.469 [planetinfo.record]: seed = 228 169 245 223 13:20:52.469 [planetinfo.record]: coordinates = 169 164 13:20:52.469 [planetinfo.record]: government = 4; 13:20:52.469 [planetinfo.record]: economy = 4; 13:20:52.469 [planetinfo.record]: techlevel = 6; 13:20:52.469 [planetinfo.record]: population = 33; 13:20:52.469 [planetinfo.record]: productivity = 12672; 13:20:52.469 [planetinfo.record]: name = "Onditi"; 13:20:52.470 [planetinfo.record]: inhabitant = "Furry Rodent"; 13:20:52.470 [planetinfo.record]: inhabitants = "Furry Rodents"; 13:20:52.470 [planetinfo.record]: description = "The world Onditi is reasonably noted for mud tennis and its ancient mountains."; 13:20:52.484 [planetinfo.record]: air_color = 0.480784, 0.585435, 0.883881, 1; 13:20:52.485 [planetinfo.record]: cloud_alpha = 1.000000; 13:20:52.485 [planetinfo.record]: cloud_color = 0.150795, 0.536289, 0.654297, 1; 13:20:52.485 [planetinfo.record]: cloud_fraction = 0.400000; 13:20:52.485 [planetinfo.record]: land_color = 0.554186, 0.317574, 0.650391, 1; 13:20:52.485 [planetinfo.record]: land_fraction = 0.650000; 13:20:52.486 [planetinfo.record]: polar_cloud_color = 0.412345, 0.704882, 0.794434, 1; 13:20:52.486 [planetinfo.record]: polar_land_color = 0.900386, 0.815352, 0.934961, 1; 13:20:52.486 [planetinfo.record]: polar_sea_color = 0.942578, 0.903742, 0.888546, 1; 13:20:52.486 [planetinfo.record]: sea_color = 0.574219, 0.479583, 0.442552, 1; 13:20:52.486 [planetinfo.record]: rotation_speed = 0.002745 13:20:52.486 [planetinfo.record]: planet zpos = 887250.000000 13:20:52.487 [planetinfo.record]: sun_radius = 189593.284607 13:20:52.487 [planetinfo.record]: sun_vector = -0.766 -0.634 0.103 13:20:52.487 [planetinfo.record]: sun_distance = 1365000 13:20:52.487 [planetinfo.record]: corona_flare = 0.077214 13:20:52.487 [planetinfo.record]: corona_hues = 0.658501 13:20:52.487 [planetinfo.record]: sun_color = 0.807562, 0.622847, 0.621524, 1 13:20:52.487 [planetinfo.record]: corona_shimmer = 0.349686 13:20:52.488 [planetinfo.record]: station_vector = 0.963 -0.259 0.080 13:20:52.488 [planetinfo.record]: station = coriolis 13:20:57.869 [PLANETINFO OVER]: Done 13:20:57.870 [PLANETINFO LOGGING]: 4 113 13:20:58.874 [planetinfo.record]: seed = 200 197 155 171 13:20:58.874 [planetinfo.record]: coordinates = 197 183 13:20:58.874 [planetinfo.record]: government = 1; 13:20:58.874 [planetinfo.record]: economy = 7; 13:20:58.874 [planetinfo.record]: techlevel = 2; 13:20:58.874 [planetinfo.record]: population = 17; 13:20:58.874 [planetinfo.record]: productivity = 2040; 13:20:58.874 [planetinfo.record]: name = "Maesqua"; 13:20:58.874 [planetinfo.record]: inhabitant = "Small Harmless Horned Humanoid"; 13:20:58.874 [planetinfo.record]: inhabitants = "Small Harmless Horned Humanoids"; 13:20:58.874 [planetinfo.record]: description = "The world Maesqua is reasonably noted for its inhabitants’ eccentric love for tourists and the Maesquaian edible grub."; 13:20:58.888 [planetinfo.record]: air_color = 0.633959, 0.460959, 0.967781, 1; 13:20:58.888 [planetinfo.record]: cloud_alpha = 1.000000; 13:20:58.888 [planetinfo.record]: cloud_color = 0.90625, 0.0247803, 0.871818, 1; 13:20:58.888 [planetinfo.record]: cloud_fraction = 0.320000; 13:20:58.888 [planetinfo.record]: land_color = 0.655347, 0.0644531, 0.66, 1; 13:20:58.888 [planetinfo.record]: land_fraction = 0.410000; 13:20:58.888 [planetinfo.record]: polar_cloud_color = 0.907812, 0.355944, 0.886255, 1; 13:20:58.888 [planetinfo.record]: polar_land_color = 0.932354, 0.723303, 0.934, 1; 13:20:58.888 [planetinfo.record]: polar_sea_color = 0.929564, 0.939844, 0.892852, 1; 13:20:58.889 [planetinfo.record]: sea_color = 0.575244, 0.601562, 0.48125, 1; 13:20:58.889 [planetinfo.record]: rotation_speed = 0.001084 13:20:58.889 [planetinfo.record]: planet zpos = 874350.000000 13:20:58.889 [planetinfo.record]: sun_radius = 125845.242462 13:20:58.889 [planetinfo.record]: sun_vector = 0.228 0.466 -0.855 13:20:58.889 [planetinfo.record]: sun_distance = 1049220 13:20:58.889 [planetinfo.record]: corona_flare = 0.082526 13:20:58.889 [planetinfo.record]: corona_hues = 0.747131 13:20:58.889 [planetinfo.record]: sun_color = 0.202105, 0.62481, 0.807162, 1 13:20:58.889 [planetinfo.record]: corona_shimmer = 0.610409 13:20:58.889 [planetinfo.record]: station_vector = 0.771 0.347 0.534 13:20:58.889 [planetinfo.record]: station = coriolis 13:21:03.769 [PLANETINFO OVER]: Done 13:21:03.773 [PLANETINFO LOGGING]: 4 114 13:21:04.787 [planetinfo.record]: seed = 112 111 69 51 13:21:04.787 [planetinfo.record]: coordinates = 111 154 13:21:04.787 [planetinfo.record]: government = 6; 13:21:04.787 [planetinfo.record]: economy = 2; 13:21:04.787 [planetinfo.record]: techlevel = 11; 13:21:04.788 [planetinfo.record]: population = 53; 13:21:04.788 [planetinfo.record]: productivity = 33920; 13:21:04.788 [planetinfo.record]: name = "Beis"; 13:21:04.788 [planetinfo.record]: inhabitant = "Human Colonial"; 13:21:04.788 [planetinfo.record]: inhabitants = "Human Colonials"; 13:21:04.788 [planetinfo.record]: description = "This planet is reasonably famous for its inhabitants’ exceptional loathing of food blenders."; 13:21:04.792 [planetinfo.record]: air_color = 0.684923, 0.862984, 0.871523, 1; 13:21:04.792 [planetinfo.record]: cloud_alpha = 1.000000; 13:21:04.792 [planetinfo.record]: cloud_color = 0.613251, 0.617188, 0.590668, 1; 13:21:04.792 [planetinfo.record]: cloud_fraction = 0.150000; 13:21:04.792 [planetinfo.record]: land_color = 0.66, 0.314531, 0.614117, 1; 13:21:04.792 [planetinfo.record]: land_fraction = 0.430000; 13:21:04.793 [planetinfo.record]: polar_cloud_color = 0.774634, 0.777734, 0.756848, 1; 13:21:04.793 [planetinfo.record]: polar_land_color = 0.934, 0.811777, 0.917767, 1; 13:21:04.793 [planetinfo.record]: polar_sea_color = 0.941602, 0.916125, 0.889004, 1; 13:21:04.793 [planetinfo.record]: sea_color = 0.583984, 0.520781, 0.4535, 1; 13:21:04.793 [planetinfo.record]: rotation_speed = 0.002500 13:21:04.793 [planetinfo.record]: planet zpos = 406450.000000 13:21:04.793 [planetinfo.record]: sun_radius = 93100.626373 13:21:04.793 [planetinfo.record]: sun_vector = -0.034 -0.739 0.673 13:21:04.793 [planetinfo.record]: sun_distance = 812900 13:21:04.793 [planetinfo.record]: corona_flare = 0.087077 13:21:04.793 [planetinfo.record]: corona_hues = 0.548584 13:21:04.793 [planetinfo.record]: sun_color = 0.789462, 0.409093, 0.388512, 1 13:21:04.793 [planetinfo.record]: corona_shimmer = 0.336849 13:21:04.793 [planetinfo.record]: station_vector = 0.663 0.743 0.086 13:21:04.793 [planetinfo.record]: station = dodecahedron 13:21:09.917 [PLANETINFO OVER]: Done 13:21:09.918 [PLANETINFO LOGGING]: 4 115 13:21:10.921 [planetinfo.record]: seed = 156 80 243 109 13:21:10.921 [planetinfo.record]: coordinates = 80 224 13:21:10.921 [planetinfo.record]: government = 3; 13:21:10.921 [planetinfo.record]: economy = 0; 13:21:10.921 [planetinfo.record]: techlevel = 9; 13:21:10.921 [planetinfo.record]: population = 40; 13:21:10.921 [planetinfo.record]: productivity = 22400; 13:21:10.921 [planetinfo.record]: name = "Diriis"; 13:21:10.921 [planetinfo.record]: inhabitant = "Blue Slimy Frog"; 13:21:10.921 [planetinfo.record]: inhabitants = "Blue Slimy Frogs"; 13:21:10.921 [planetinfo.record]: description = "This planet is noted for its fabulous goat soup."; 13:21:10.939 [planetinfo.record]: air_color = 0.505209, 0.669693, 0.83315, 1; 13:21:10.939 [planetinfo.record]: cloud_alpha = 1.000000; 13:21:10.939 [planetinfo.record]: cloud_color = 0.209801, 0.501953, 0.358159, 1; 13:21:10.939 [planetinfo.record]: cloud_fraction = 0.130000; 13:21:10.939 [planetinfo.record]: land_color = 0.66, 0.353203, 0.597682, 1; 13:21:10.939 [planetinfo.record]: land_fraction = 0.700000; 13:21:10.939 [planetinfo.record]: polar_cloud_color = 0.461826, 0.725879, 0.595915, 1; 13:21:10.939 [planetinfo.record]: polar_land_color = 0.934, 0.825459, 0.911953, 1; 13:21:10.939 [planetinfo.record]: polar_sea_color = 0.944141, 0.920203, 0.894721, 1; 13:21:10.939 [planetinfo.record]: sea_color = 0.558594, 0.501943, 0.441638, 1; 13:21:10.939 [planetinfo.record]: rotation_speed = 0.004632 13:21:10.940 [planetinfo.record]: planet zpos = 871360.000000 13:21:10.940 [planetinfo.record]: sun_radius = 175724.291992 13:21:10.940 [planetinfo.record]: sun_vector = -0.965 0.049 -0.259 13:21:10.940 [planetinfo.record]: sun_distance = 1493760 13:21:10.940 [planetinfo.record]: corona_flare = 0.085112 13:21:10.940 [planetinfo.record]: corona_hues = 0.693253 13:21:10.940 [planetinfo.record]: sun_color = 0.832489, 0.452089, 0.319865, 1 13:21:10.940 [planetinfo.record]: corona_shimmer = 0.269907 13:21:10.940 [planetinfo.record]: station_vector = 0.623 -0.737 0.262 13:21:10.940 [planetinfo.record]: station = coriolis 13:21:16.977 [PLANETINFO OVER]: Done 13:21:16.978 [PLANETINFO LOGGING]: 4 116 13:21:17.983 [planetinfo.record]: seed = 172 105 101 101 13:21:17.983 [planetinfo.record]: coordinates = 105 93 13:21:17.983 [planetinfo.record]: government = 5; 13:21:17.983 [planetinfo.record]: economy = 5; 13:21:17.984 [planetinfo.record]: techlevel = 6; 13:21:17.984 [planetinfo.record]: population = 35; 13:21:17.984 [planetinfo.record]: productivity = 12600; 13:21:17.984 [planetinfo.record]: name = "Ceinan"; 13:21:17.984 [planetinfo.record]: inhabitant = "Human Colonial"; 13:21:17.984 [planetinfo.record]: inhabitants = "Human Colonials"; 13:21:17.984 [planetinfo.record]: description = "This world is very notable for the Ceinanian edible arts graduate and its unusual dense forests."; 13:21:17.1000 [planetinfo.record]: air_color = 0.604877, 0.880858, 0.968432, 1; 13:21:17.1000 [planetinfo.record]: cloud_alpha = 1.000000; 13:21:17.1000 [planetinfo.record]: cloud_color = 0.556485, 0.908203, 0.429268, 1; 13:21:17.1000 [planetinfo.record]: cloud_fraction = 0.340000; 13:21:17.1000 [planetinfo.record]: land_color = 0.600784, 0.66, 0.389297, 1; 13:21:17.1000 [planetinfo.record]: land_fraction = 0.380000; 13:21:17.1000 [planetinfo.record]: polar_cloud_color = 0.688749, 0.908691, 0.609196, 1; 13:21:17.1000 [planetinfo.record]: polar_land_color = 0.91305, 0.934, 0.838229, 1; 13:21:17.1000 [planetinfo.record]: polar_sea_color = 0.889208, 0.933594, 0.886641, 1; 13:21:18.000 [planetinfo.record]: sea_color = 0.537778, 0.664062, 0.530472, 1; 13:21:18.000 [planetinfo.record]: rotation_speed = 0.003616 13:21:18.000 [planetinfo.record]: planet zpos = 546130.000000 13:21:18.000 [planetinfo.record]: sun_radius = 120875.544281 13:21:18.000 [planetinfo.record]: sun_vector = -0.670 0.586 -0.456 13:21:18.000 [planetinfo.record]: sun_distance = 798190 13:21:18.000 [planetinfo.record]: corona_flare = 0.099867 13:21:18.000 [planetinfo.record]: corona_hues = 0.965569 13:21:18.000 [planetinfo.record]: sun_color = 0.737787, 0.749231, 0.842935, 1 13:21:18.001 [planetinfo.record]: corona_shimmer = 0.258614 13:21:18.001 [planetinfo.record]: station_vector = 0.345 -0.694 0.632 13:21:18.001 [planetinfo.record]: station = coriolis 13:21:22.952 [PLANETINFO OVER]: Done 13:21:22.953 [PLANETINFO LOGGING]: 4 117 13:21:23.956 [planetinfo.record]: seed = 224 140 91 180 13:21:23.956 [planetinfo.record]: coordinates = 140 251 13:21:23.956 [planetinfo.record]: government = 4; 13:21:23.956 [planetinfo.record]: economy = 3; 13:21:23.956 [planetinfo.record]: techlevel = 6; 13:21:23.957 [planetinfo.record]: population = 32; 13:21:23.957 [planetinfo.record]: productivity = 14336; 13:21:23.957 [planetinfo.record]: name = "Rateisre"; 13:21:23.957 [planetinfo.record]: inhabitant = "Human Colonial"; 13:21:23.957 [planetinfo.record]: inhabitants = "Human Colonials"; 13:21:23.957 [planetinfo.record]: description = "The planet Rateisre is an unremarkable dump."; 13:21:23.965 [planetinfo.record]: air_color = 0.641183, 0.857063, 0.947619, 1; 13:21:23.965 [planetinfo.record]: cloud_alpha = 1.000000; 13:21:23.965 [planetinfo.record]: cloud_color = 0.563742, 0.845703, 0.531868, 1; 13:21:23.965 [planetinfo.record]: cloud_fraction = 0.320000; 13:21:23.965 [planetinfo.record]: land_color = 0.536935, 0.66, 0.492422, 1; 13:21:23.966 [planetinfo.record]: land_fraction = 0.410000; 13:21:23.966 [planetinfo.record]: polar_cloud_color = 0.697076, 0.880566, 0.676333, 1; 13:21:23.966 [planetinfo.record]: polar_land_color = 0.890461, 0.934, 0.874713, 1; 13:21:23.966 [planetinfo.record]: polar_sea_color = 0.925443, 0.862853, 0.942969, 1; 13:21:23.966 [planetinfo.record]: sea_color = 0.527915, 0.376495, 0.570312, 1; 13:21:23.966 [planetinfo.record]: rotation_speed = 0.004517 13:21:23.966 [planetinfo.record]: planet zpos = 557200.000000 13:21:23.967 [planetinfo.record]: sun_radius = 93307.363892 13:21:23.967 [planetinfo.record]: sun_vector = 0.200 -0.671 -0.714 13:21:23.967 [planetinfo.record]: sun_distance = 835800 13:21:23.967 [planetinfo.record]: corona_flare = 0.097328 13:21:23.967 [planetinfo.record]: corona_hues = 0.785774 13:21:23.967 [planetinfo.record]: sun_color = 0.744586, 0.75904, 0.796194, 1 13:21:23.967 [planetinfo.record]: corona_shimmer = 0.296726 13:21:23.968 [planetinfo.record]: station_vector = 0.223 -0.896 0.383 13:21:23.968 [planetinfo.record]: station = coriolis 13:21:28.236 [PLANETINFO OVER]: Done 13:21:28.237 [PLANETINFO LOGGING]: 4 118 13:21:29.239 [planetinfo.record]: seed = 152 110 213 40 13:21:29.239 [planetinfo.record]: coordinates = 110 125 13:21:29.239 [planetinfo.record]: government = 3; 13:21:29.239 [planetinfo.record]: economy = 5; 13:21:29.239 [planetinfo.record]: techlevel = 6; 13:21:29.239 [planetinfo.record]: population = 33; 13:21:29.240 [planetinfo.record]: productivity = 9240; 13:21:29.240 [planetinfo.record]: name = "Uslain"; 13:21:29.240 [planetinfo.record]: inhabitant = "Small Red Bony Lobster"; 13:21:29.240 [planetinfo.record]: inhabitants = "Small Red Bony Lobsters"; 13:21:29.240 [planetinfo.record]: description = "The world Uslain is reasonably famous for the Uslainian spotted wolf."; 13:21:29.256 [planetinfo.record]: air_color = 0.748144, 0.614141, 0.867621, 1; 13:21:29.256 [planetinfo.record]: cloud_alpha = 1.000000; 13:21:29.256 [planetinfo.record]: cloud_color = 0.605469, 0.437546, 0.504453, 1; 13:21:29.256 [planetinfo.record]: cloud_fraction = 0.310000; 13:21:29.256 [planetinfo.record]: land_color = 0.507891, 0.66, 0.545918, 1; 13:21:29.256 [planetinfo.record]: land_fraction = 0.310000; 13:21:29.256 [planetinfo.record]: polar_cloud_color = 0.772461, 0.638563, 0.691913, 1; 13:21:29.256 [planetinfo.record]: polar_land_color = 0.880186, 0.934, 0.893639, 1; 13:21:29.256 [planetinfo.record]: polar_sea_color = 0.87567, 0.924609, 0.914286, 1; 13:21:29.256 [planetinfo.record]: sea_color = 0.59429, 0.753906, 0.720237, 1; 13:21:29.256 [planetinfo.record]: rotation_speed = 0.000933 13:21:29.256 [planetinfo.record]: planet zpos = 596880.000000 13:21:29.256 [planetinfo.record]: sun_radius = 105487.264709 13:21:29.256 [planetinfo.record]: sun_vector = -0.381 0.911 0.161 13:21:29.256 [planetinfo.record]: sun_distance = 994800 13:21:29.256 [planetinfo.record]: corona_flare = 0.084547 13:21:29.256 [planetinfo.record]: corona_hues = 0.986847 13:21:29.257 [planetinfo.record]: sun_color = 0.458369, 0.537177, 0.682294, 1 13:21:29.257 [planetinfo.record]: corona_shimmer = 0.296906 13:21:29.257 [planetinfo.record]: station_vector = 0.126 0.980 0.153 13:21:29.257 [planetinfo.record]: station = coriolis 13:21:34.045 [PLANETINFO OVER]: Done 13:21:34.046 [PLANETINFO LOGGING]: 4 119 13:21:35.058 [planetinfo.record]: seed = 148 234 83 172 13:21:35.058 [planetinfo.record]: coordinates = 234 172 13:21:35.058 [planetinfo.record]: government = 2; 13:21:35.058 [planetinfo.record]: economy = 4; 13:21:35.058 [planetinfo.record]: techlevel = 6; 13:21:35.058 [planetinfo.record]: population = 31; 13:21:35.058 [planetinfo.record]: productivity = 8928; 13:21:35.058 [planetinfo.record]: name = "Ingequ"; 13:21:35.058 [planetinfo.record]: inhabitant = "Human Colonial"; 13:21:35.058 [planetinfo.record]: inhabitants = "Human Colonials"; 13:21:35.058 [planetinfo.record]: description = "The world Ingequ is beset by dreadful earthquakes."; 13:21:35.072 [planetinfo.record]: air_color = 0.646266, 0.902951, 0.904693, 1; 13:21:35.072 [planetinfo.record]: cloud_alpha = 1.000000; 13:21:35.072 [planetinfo.record]: cloud_color = 0.711022, 0.716797, 0.531998, 1; 13:21:35.072 [planetinfo.record]: cloud_fraction = 0.280000; 13:21:35.072 [planetinfo.record]: land_color = 0.440798, 0.546875, 0.0619507, 1; 13:21:35.072 [planetinfo.record]: land_fraction = 0.300000; 13:21:35.072 [planetinfo.record]: polar_cloud_color = 0.818417, 0.822559, 0.690017, 1; 13:21:35.073 [planetinfo.record]: polar_land_color = 0.899472, 0.945312, 0.735756, 1; 13:21:35.073 [planetinfo.record]: polar_sea_color = 0.946875, 0.893983, 0.898942, 1; 13:21:35.073 [planetinfo.record]: sea_color = 0.53125, 0.412549, 0.423677, 1; 13:21:35.073 [planetinfo.record]: rotation_speed = 0.000621 13:21:35.074 [planetinfo.record]: planet zpos = 673420.000000 13:21:35.074 [planetinfo.record]: sun_radius = 181418.056641 13:21:35.074 [planetinfo.record]: sun_vector = -0.042 -0.983 -0.181 13:21:35.074 [planetinfo.record]: sun_distance = 1285620 13:21:35.074 [planetinfo.record]: corona_flare = 0.080408 13:21:35.074 [planetinfo.record]: corona_hues = 0.726685 13:21:35.074 [planetinfo.record]: sun_color = 0.699493, 0.556176, 0.370741, 1 13:21:35.074 [planetinfo.record]: corona_shimmer = 0.281713 13:21:35.074 [planetinfo.record]: station_vector = -0.075 0.995 0.072 13:21:35.074 [planetinfo.record]: station = coriolis 13:21:39.663 [PLANETINFO OVER]: Done 13:21:39.664 [PLANETINFO LOGGING]: 4 120 13:21:40.678 [planetinfo.record]: seed = 52 202 21 232 13:21:40.678 [planetinfo.record]: coordinates = 202 218 13:21:40.678 [planetinfo.record]: government = 6; 13:21:40.678 [planetinfo.record]: economy = 2; 13:21:40.678 [planetinfo.record]: techlevel = 10; 13:21:40.678 [planetinfo.record]: population = 49; 13:21:40.678 [planetinfo.record]: productivity = 31360; 13:21:40.678 [planetinfo.record]: name = "Usinribe"; 13:21:40.678 [planetinfo.record]: inhabitant = "Human Colonial"; 13:21:40.678 [planetinfo.record]: inhabitants = "Human Colonials"; 13:21:40.678 [planetinfo.record]: description = "This planet is beset by deadly earthquakes."; 13:21:40.695 [planetinfo.record]: air_color = 0.519696, 0.971684, 0.851263, 1; 13:21:40.695 [planetinfo.record]: cloud_alpha = 1.000000; 13:21:40.695 [planetinfo.record]: cloud_color = 0.917969, 0.340765, 0.186462, 1; 13:21:40.695 [planetinfo.record]: cloud_fraction = 0.550000; 13:21:40.695 [planetinfo.record]: land_color = 0.570974, 0.66, 0.440859, 1; 13:21:40.695 [planetinfo.record]: land_fraction = 0.580000; 13:21:40.695 [planetinfo.record]: polar_cloud_color = 0.913086, 0.554252, 0.458326, 1; 13:21:40.695 [planetinfo.record]: polar_land_color = 0.902504, 0.934, 0.856471, 1; 13:21:40.695 [planetinfo.record]: polar_sea_color = 0.944531, 0.88642, 0.934089, 1; 13:21:40.695 [planetinfo.record]: sea_color = 0.554688, 0.418182, 0.530159, 1; 13:21:40.695 [planetinfo.record]: rotation_speed = 0.004628 13:21:40.695 [planetinfo.record]: planet zpos = 658580.000000 13:21:40.696 [planetinfo.record]: sun_radius = 98258.879395 13:21:40.696 [planetinfo.record]: sun_vector = 0.551 0.405 0.730 13:21:40.696 [planetinfo.record]: sun_distance = 1063860 13:21:40.696 [planetinfo.record]: corona_flare = 0.061351 13:21:40.697 [planetinfo.record]: corona_hues = 0.975311 13:21:40.697 [planetinfo.record]: sun_color = 0.294771, 0.602264, 0.768323, 1 13:21:40.697 [planetinfo.record]: corona_shimmer = 0.552198 13:21:40.697 [planetinfo.record]: station_vector = -0.985 0.086 0.152 13:21:40.697 [planetinfo.record]: station = coriolis 13:21:45.381 [PLANETINFO OVER]: Done 13:21:45.381 [PLANETINFO LOGGING]: 4 121 13:21:46.394 [planetinfo.record]: seed = 184 179 91 127 13:21:46.394 [planetinfo.record]: coordinates = 179 62 13:21:46.394 [planetinfo.record]: government = 7; 13:21:46.394 [planetinfo.record]: economy = 6; 13:21:46.394 [planetinfo.record]: techlevel = 8; 13:21:46.394 [planetinfo.record]: population = 46; 13:21:46.394 [planetinfo.record]: productivity = 16192; 13:21:46.394 [planetinfo.record]: name = "Onenceve"; 13:21:46.394 [planetinfo.record]: inhabitant = "Human Colonial"; 13:21:46.394 [planetinfo.record]: inhabitants = "Human Colonials"; 13:21:46.394 [planetinfo.record]: description = "Onenceve is an unremarkable dump."; 13:21:46.398 [planetinfo.record]: air_color = 0.629794, 0.889734, 0.834681, 1; 13:21:46.398 [planetinfo.record]: cloud_alpha = 1.000000; 13:21:46.399 [planetinfo.record]: cloud_color = 0.671875, 0.555412, 0.485535, 1; 13:21:46.399 [planetinfo.record]: cloud_fraction = 0.310000; 13:21:46.399 [planetinfo.record]: land_color = 0.519814, 0.211406, 0.66, 1; 13:21:46.399 [planetinfo.record]: land_fraction = 0.260000; 13:21:46.399 [planetinfo.record]: polar_cloud_color = 0.802344, 0.71542, 0.663266, 1; 13:21:46.399 [planetinfo.record]: polar_land_color = 0.884404, 0.775293, 0.934, 1; 13:21:46.399 [planetinfo.record]: polar_sea_color = 0.898867, 0.932227, 0.880153, 1; 13:21:46.399 [planetinfo.record]: sea_color = 0.580724, 0.677734, 0.526303, 1; 13:21:46.399 [planetinfo.record]: rotation_speed = 0.002936 13:21:46.400 [planetinfo.record]: planet zpos = 1025250.000000 13:21:46.400 [planetinfo.record]: sun_radius = 188995.008850 13:21:46.400 [planetinfo.record]: sun_vector = 0.028 0.722 0.692 13:21:46.400 [planetinfo.record]: sun_distance = 1640400 13:21:46.400 [planetinfo.record]: corona_flare = 0.082416 13:21:46.400 [planetinfo.record]: corona_hues = 0.727440 13:21:46.400 [planetinfo.record]: sun_color = 0.720575, 0.644181, 0.489134, 1 13:21:46.400 [planetinfo.record]: corona_shimmer = 1.207191 13:21:46.401 [planetinfo.record]: station_vector = 0.177 0.933 0.313 13:21:46.401 [planetinfo.record]: station = coriolis 13:21:51.521 [PLANETINFO OVER]: Done 13:21:51.522 [PLANETINFO LOGGING]: 4 122 13:21:52.530 [planetinfo.record]: seed = 128 150 165 173 13:21:52.530 [planetinfo.record]: coordinates = 150 165 13:21:52.530 [planetinfo.record]: government = 0; 13:21:52.530 [planetinfo.record]: economy = 7; 13:21:52.530 [planetinfo.record]: techlevel = 2; 13:21:52.530 [planetinfo.record]: population = 16; 13:21:52.531 [planetinfo.record]: productivity = 1536; 13:21:52.531 [planetinfo.record]: name = "Diesdi"; 13:21:52.531 [planetinfo.record]: inhabitant = "Harmless Bony Bird"; 13:21:52.531 [planetinfo.record]: inhabitants = "Harmless Bony Birds"; 13:21:52.531 [planetinfo.record]: description = "The planet Diesdi is an unremarkable dump."; 13:21:52.558 [planetinfo.record]: air_color = 0.556326, 0.952172, 0.871425, 1; 13:21:52.558 [planetinfo.record]: cloud_alpha = 1.000000; 13:21:52.558 [planetinfo.record]: cloud_color = 0.859375, 0.524154, 0.302124, 1; 13:21:52.558 [planetinfo.record]: cloud_fraction = 0.410000; 13:21:52.558 [planetinfo.record]: land_color = 0.491002, 0.293922, 0.654297, 1; 13:21:52.558 [planetinfo.record]: land_fraction = 0.430000; 13:21:52.560 [planetinfo.record]: polar_cloud_color = 0.886719, 0.670539, 0.527355, 1; 13:21:52.560 [planetinfo.record]: polar_land_color = 0.87626, 0.805884, 0.93457, 1; 13:21:52.560 [planetinfo.record]: polar_sea_color = 0.940039, 0.900081, 0.882572, 1; 13:21:52.560 [planetinfo.record]: sea_color = 0.599609, 0.49766, 0.452986, 1; 13:21:52.560 [planetinfo.record]: rotation_speed = 0.004438 13:21:52.560 [planetinfo.record]: planet zpos = 881160.000000 13:21:52.560 [planetinfo.record]: sun_radius = 178892.467346 13:21:52.560 [planetinfo.record]: sun_vector = -0.527 0.395 -0.753 13:21:52.560 [planetinfo.record]: sun_distance = 1258800 13:21:52.560 [planetinfo.record]: corona_flare = 0.014439 13:21:52.562 [planetinfo.record]: corona_hues = 0.542313 13:21:52.562 [planetinfo.record]: sun_color = 0.779797, 0.485296, 0.239623, 1 13:21:52.562 [planetinfo.record]: corona_shimmer = 0.350565 13:21:52.563 [planetinfo.record]: station_vector = 0.947 -0.305 0.097 13:21:52.563 [planetinfo.record]: station = coriolis 13:21:58.137 [PLANETINFO OVER]: Done 13:21:58.139 [PLANETINFO LOGGING]: 4 123 13:21:59.144 [planetinfo.record]: seed = 76 196 243 218 13:21:59.144 [planetinfo.record]: coordinates = 196 45 13:21:59.144 [planetinfo.record]: government = 1; 13:21:59.144 [planetinfo.record]: economy = 7; 13:21:59.144 [planetinfo.record]: techlevel = 1; 13:21:59.144 [planetinfo.record]: population = 13; 13:21:59.144 [planetinfo.record]: productivity = 1560; 13:21:59.144 [planetinfo.record]: name = "Quinmabe"; 13:21:59.144 [planetinfo.record]: inhabitant = "Bug-Eyed Lobster"; 13:21:59.144 [planetinfo.record]: inhabitants = "Bug-Eyed Lobsters"; 13:21:59.144 [planetinfo.record]: description = "This planet is very well known for its inhabitants’ weird shyness and the Quinmabeian mountain Nusealoid."; 13:21:59.167 [planetinfo.record]: air_color = 0.604228, 0.562644, 0.863068, 1; 13:21:59.167 [planetinfo.record]: cloud_alpha = 1.000000; 13:21:59.167 [planetinfo.record]: cloud_color = 0.4415, 0.328262, 0.591797, 1; 13:21:59.167 [planetinfo.record]: cloud_fraction = 0.320000; 13:21:59.167 [planetinfo.record]: land_color = 0.66, 0.4096, 0.0876563, 1; 13:21:59.167 [planetinfo.record]: land_fraction = 0.390000; 13:21:59.167 [planetinfo.record]: polar_cloud_color = 0.644673, 0.553029, 0.766309, 1; 13:21:59.167 [planetinfo.record]: polar_land_color = 0.934, 0.845411, 0.731512, 1; 13:21:59.167 [planetinfo.record]: polar_sea_color = 0.934289, 0.940234, 0.892672, 1; 13:21:59.167 [planetinfo.record]: sea_color = 0.58254, 0.597656, 0.476724, 1; 13:21:59.167 [planetinfo.record]: rotation_speed = 0.001575 13:21:59.167 [planetinfo.record]: planet zpos = 724360.000000 13:21:59.167 [planetinfo.record]: sun_radius = 138372.410278 13:21:59.168 [planetinfo.record]: sun_vector = -0.297 0.881 -0.368 13:21:59.168 [planetinfo.record]: sun_distance = 1170120 13:21:59.168 [planetinfo.record]: corona_flare = 0.065736 13:21:59.168 [planetinfo.record]: corona_hues = 0.870636 13:21:59.168 [planetinfo.record]: sun_color = 0.840466, 0.457366, 0.364886, 1 13:21:59.168 [planetinfo.record]: corona_shimmer = 0.907349 13:21:59.171 [planetinfo.record]: station_vector = -0.428 -0.889 0.162 13:21:59.171 [planetinfo.record]: station = coriolis 13:22:04.028 [PLANETINFO OVER]: Done 13:22:04.029 [PLANETINFO LOGGING]: 4 124 13:22:05.034 [planetinfo.record]: seed = 124 19 5 76 13:22:05.034 [planetinfo.record]: coordinates = 19 107 13:22:05.034 [planetinfo.record]: government = 7; 13:22:05.034 [planetinfo.record]: economy = 3; 13:22:05.034 [planetinfo.record]: techlevel = 11; 13:22:05.034 [planetinfo.record]: population = 55; 13:22:05.034 [planetinfo.record]: productivity = 33880; 13:22:05.034 [planetinfo.record]: name = "Inmaarxe"; 13:22:05.034 [planetinfo.record]: inhabitant = "Human Colonial"; 13:22:05.034 [planetinfo.record]: inhabitants = "Human Colonials"; 13:22:05.035 [planetinfo.record]: description = "The planet Inmaarxe is scourged by killer mountain Abzazaaboids."; 13:22:05.047 [planetinfo.record]: air_color = 0.684039, 0.611201, 0.840305, 1; 13:22:05.048 [planetinfo.record]: cloud_alpha = 1.000000; 13:22:05.048 [planetinfo.record]: cloud_color = 0.519859, 0.408936, 0.523438, 1; 13:22:05.048 [planetinfo.record]: cloud_fraction = 0.190000; 13:22:05.048 [planetinfo.record]: land_color = 0.586382, 0.66, 0.549141, 1; 13:22:05.048 [planetinfo.record]: land_fraction = 0.710000; 13:22:05.048 [planetinfo.record]: polar_cloud_color = 0.732404, 0.634984, 0.735547, 1; 13:22:05.048 [planetinfo.record]: polar_land_color = 0.907955, 0.934, 0.894779, 1; 13:22:05.048 [planetinfo.record]: polar_sea_color = 0.865468, 0.880416, 0.91582, 1; 13:22:05.048 [planetinfo.record]: sea_color = 0.656667, 0.711628, 0.841797, 1; 13:22:05.048 [planetinfo.record]: rotation_speed = 0.000467 13:22:05.048 [planetinfo.record]: planet zpos = 708840.000000 13:22:05.049 [planetinfo.record]: sun_radius = 155773.509979 13:22:05.049 [planetinfo.record]: sun_vector = -0.415 -0.718 -0.558 13:22:05.049 [planetinfo.record]: sun_distance = 1358610 13:22:05.049 [planetinfo.record]: corona_flare = 0.064812 13:22:05.049 [planetinfo.record]: corona_hues = 0.819160 13:22:05.049 [planetinfo.record]: sun_color = 0.377093, 0.682037, 0.80293, 1 13:22:05.049 [planetinfo.record]: corona_shimmer = 0.322777 13:22:05.050 [planetinfo.record]: station_vector = -0.479 -0.380 0.791 13:22:05.050 [planetinfo.record]: station = icosahedron 13:22:10.295 [PLANETINFO OVER]: Done 13:22:10.296 [PLANETINFO LOGGING]: 4 125 13:22:11.316 [planetinfo.record]: seed = 80 66 155 56 13:22:11.316 [planetinfo.record]: coordinates = 66 42 13:22:11.316 [planetinfo.record]: government = 2; 13:22:11.316 [planetinfo.record]: economy = 2; 13:22:11.316 [planetinfo.record]: techlevel = 8; 13:22:11.316 [planetinfo.record]: population = 37; 13:22:11.316 [planetinfo.record]: productivity = 14208; 13:22:11.316 [planetinfo.record]: name = "Edceon"; 13:22:11.316 [planetinfo.record]: inhabitant = "Red Slimy Rodent"; 13:22:11.316 [planetinfo.record]: inhabitants = "Red Slimy Rodents"; 13:22:11.316 [planetinfo.record]: description = "The world Edceon is mildly noted for its ancient mountains but ravaged by occasional solar activity."; 13:22:11.322 [planetinfo.record]: air_color = 0.416554, 0.835752, 0.78623, 1; 13:22:11.322 [planetinfo.record]: cloud_alpha = 1.000000; 13:22:11.322 [planetinfo.record]: cloud_color = 0.509766, 0.350962, 0.0477905, 1; 13:22:11.322 [planetinfo.record]: cloud_fraction = 0.100000; 13:22:11.322 [planetinfo.record]: land_color = 0.641953, 0.5775, 0.66, 1; 13:22:11.322 [planetinfo.record]: land_fraction = 0.510000; 13:22:11.322 [planetinfo.record]: polar_cloud_color = 0.729395, 0.58738, 0.316261, 1; 13:22:11.322 [planetinfo.record]: polar_land_color = 0.927615, 0.904813, 0.934, 1; 13:22:11.322 [planetinfo.record]: polar_sea_color = 0.948828, 0.788405, 0.718107, 1; 13:22:11.323 [planetinfo.record]: sea_color = 0.511719, 0.165643, 0.0139923, 1; 13:22:11.323 [planetinfo.record]: rotation_speed = 0.001324 13:22:11.323 [planetinfo.record]: planet zpos = 443700.000000 13:22:11.323 [planetinfo.record]: sun_radius = 125369.111633 13:22:11.323 [planetinfo.record]: sun_vector = 0.467 0.051 -0.883 13:22:11.323 [planetinfo.record]: sun_distance = 887400 13:22:11.323 [planetinfo.record]: corona_flare = 0.055559 13:22:11.323 [planetinfo.record]: corona_hues = 0.829292 13:22:11.323 [planetinfo.record]: sun_color = 0.79119, 0.654592, 0.489224, 1 13:22:11.323 [planetinfo.record]: corona_shimmer = 0.339291 13:22:11.323 [planetinfo.record]: station_vector = -0.726 -0.077 0.684 13:22:11.323 [planetinfo.record]: station = coriolis 13:22:17.077 [PLANETINFO OVER]: Done 13:22:17.079 [PLANETINFO LOGGING]: 4 126 13:22:18.091 [planetinfo.record]: seed = 40 255 181 197 13:22:18.091 [planetinfo.record]: coordinates = 255 32 13:22:18.091 [planetinfo.record]: government = 5; 13:22:18.091 [planetinfo.record]: economy = 0; 13:22:18.091 [planetinfo.record]: techlevel = 13; 13:22:18.091 [planetinfo.record]: population = 58; 13:22:18.091 [planetinfo.record]: productivity = 41760; 13:22:18.091 [planetinfo.record]: name = "Cecear"; 13:22:18.091 [planetinfo.record]: inhabitant = "Fierce Rodent"; 13:22:18.091 [planetinfo.record]: inhabitants = "Fierce Rodents"; 13:22:18.091 [planetinfo.record]: description = "This planet is most notable for its fabulous Soinalet brandy but ravaged by frequent earthquakes."; 13:22:18.100 [planetinfo.record]: air_color = 0.656121, 0.87819, 0.911848, 1; 13:22:18.102 [planetinfo.record]: cloud_alpha = 1.000000; 13:22:18.102 [planetinfo.record]: cloud_color = 0.665643, 0.738281, 0.559479, 1; 13:22:18.102 [planetinfo.record]: cloud_fraction = 0.260000; 13:22:18.102 [planetinfo.record]: land_color = 0.318646, 0.261841, 0.644531, 1; 13:22:18.102 [planetinfo.record]: land_fraction = 0.290000; 13:22:18.102 [planetinfo.record]: polar_cloud_color = 0.781051, 0.832227, 0.706255, 1; 13:22:18.102 [planetinfo.record]: polar_land_color = 0.81729, 0.796677, 0.935547, 1; 13:22:18.102 [planetinfo.record]: polar_sea_color = 0.941602, 0.88266, 0.88266, 1; 13:22:18.102 [planetinfo.record]: sea_color = 0.583984, 0.43776, 0.43776, 1; 13:22:18.102 [planetinfo.record]: rotation_speed = 0.002902 13:22:18.102 [planetinfo.record]: planet zpos = 522120.000000 13:22:18.102 [planetinfo.record]: sun_radius = 124117.958069 13:22:18.102 [planetinfo.record]: sun_vector = 0.040 0.806 0.591 13:22:18.102 [planetinfo.record]: sun_distance = 870200 13:22:18.102 [planetinfo.record]: corona_flare = 0.030035 13:22:18.103 [planetinfo.record]: corona_hues = 0.512695 13:22:18.103 [planetinfo.record]: sun_color = 0.343619, 0.521365, 0.752051, 1 13:22:18.103 [planetinfo.record]: corona_shimmer = 0.326316 13:22:18.103 [planetinfo.record]: station_vector = 0.336 -0.495 0.801 13:22:18.103 [planetinfo.record]: station = dodecahedron 13:22:23.290 [PLANETINFO OVER]: Done 13:22:23.292 [PLANETINFO LOGGING]: 4 127 13:22:24.293 [planetinfo.record]: seed = 196 85 211 229 13:22:24.293 [planetinfo.record]: coordinates = 85 170 13:22:24.293 [planetinfo.record]: government = 0; 13:22:24.293 [planetinfo.record]: economy = 2; 13:22:24.293 [planetinfo.record]: techlevel = 6; 13:22:24.293 [planetinfo.record]: population = 27; 13:22:24.293 [planetinfo.record]: productivity = 6912; 13:22:24.293 [planetinfo.record]: name = "Cebiledi"; 13:22:24.293 [planetinfo.record]: inhabitant = "Fierce Rodent"; 13:22:24.293 [planetinfo.record]: inhabitants = "Fierce Rodents"; 13:22:24.293 [planetinfo.record]: description = "The planet Cebiledi is scourged by killer edible arts graduates."; 13:22:24.309 [planetinfo.record]: air_color = 0.713886, 0.764314, 0.950871, 1; 13:22:24.309 [planetinfo.record]: cloud_alpha = 1.000000; 13:22:24.309 [planetinfo.record]: cloud_color = 0.728485, 0.80785, 0.855469, 1; 13:22:24.309 [planetinfo.record]: cloud_fraction = 0.260000; 13:22:24.309 [planetinfo.record]: land_color = 0.66, 0.237188, 0.564207, 1; 13:22:24.309 [planetinfo.record]: land_fraction = 0.350000; 13:22:24.309 [planetinfo.record]: polar_cloud_color = 0.80286, 0.854173, 0.884961, 1; 13:22:24.309 [planetinfo.record]: polar_land_color = 0.934, 0.784414, 0.900109, 1; 13:22:24.309 [planetinfo.record]: polar_sea_color = 0.934766, 0.906406, 0.873239, 1; 13:22:24.309 [planetinfo.record]: sea_color = 0.652344, 0.573178, 0.480594, 1; 13:22:24.309 [planetinfo.record]: rotation_speed = 0.002500 13:22:24.309 [planetinfo.record]: planet zpos = 418100.000000 13:22:24.310 [planetinfo.record]: sun_radius = 97256.608124 13:22:24.310 [planetinfo.record]: sun_vector = 0.211 -0.969 -0.129 13:22:24.310 [planetinfo.record]: sun_distance = 961630 13:22:24.310 [planetinfo.record]: corona_flare = 0.027458 13:22:24.310 [planetinfo.record]: corona_hues = 0.856842 13:22:24.310 [planetinfo.record]: sun_color = 0.405006, 0.418802, 0.747913, 1 13:22:24.310 [planetinfo.record]: corona_shimmer = 0.458255 13:22:24.310 [planetinfo.record]: station_vector = 0.596 0.691 0.409 13:22:24.310 [planetinfo.record]: station = coriolis 13:22:29.179 [PLANETINFO OVER]: Done 13:22:29.182 [PLANETINFO LOGGING]: 4 128 13:22:30.199 [planetinfo.record]: seed = 132 237 53 245 13:22:30.199 [planetinfo.record]: coordinates = 237 33 13:22:30.199 [planetinfo.record]: government = 0; 13:22:30.199 [planetinfo.record]: economy = 3; 13:22:30.199 [planetinfo.record]: techlevel = 5; 13:22:30.199 [planetinfo.record]: population = 24; 13:22:30.199 [planetinfo.record]: productivity = 5376; 13:22:30.199 [planetinfo.record]: name = "Lazaso"; 13:22:30.200 [planetinfo.record]: inhabitant = "Human Colonial"; 13:22:30.200 [planetinfo.record]: inhabitants = "Human Colonials"; 13:22:30.200 [planetinfo.record]: description = "This world is a tedious place."; 13:22:30.211 [planetinfo.record]: air_color = 0.636764, 0.860036, 0.950221, 1; 13:22:30.211 [planetinfo.record]: cloud_alpha = 1.000000; 13:22:30.211 [planetinfo.record]: cloud_color = 0.561787, 0.853516, 0.520111, 1; 13:22:30.211 [planetinfo.record]: cloud_fraction = 0.200000; 13:22:30.211 [planetinfo.record]: land_color = 0.435217, 0.505859, 0.0948486, 1; 13:22:30.211 [planetinfo.record]: land_fraction = 0.580000; 13:22:30.211 [planetinfo.record]: polar_cloud_color = 0.695222, 0.884082, 0.668242, 1; 13:22:30.211 [planetinfo.record]: polar_land_color = 0.916268, 0.949414, 0.756564, 1; 13:22:30.211 [planetinfo.record]: polar_sea_color = 0.854519, 0.906912, 0.913867, 1; 13:22:30.211 [planetinfo.record]: sea_color = 0.637585, 0.835108, 0.861328, 1; 13:22:30.211 [planetinfo.record]: rotation_speed = 0.001515 13:22:30.212 [planetinfo.record]: planet zpos = 563290.000000 13:22:30.212 [planetinfo.record]: sun_radius = 90802.188263 13:22:30.212 [planetinfo.record]: sun_vector = 0.119 0.327 0.937 13:22:30.212 [planetinfo.record]: sun_distance = 866600 13:22:30.212 [planetinfo.record]: corona_flare = 0.030928 13:22:30.212 [planetinfo.record]: corona_hues = 0.886620 13:22:30.212 [planetinfo.record]: sun_color = 0.781155, 0.398584, 0.285579, 1 13:22:30.212 [planetinfo.record]: corona_shimmer = 0.275505 13:22:30.212 [planetinfo.record]: station_vector = -0.870 0.084 0.485 13:22:30.212 [planetinfo.record]: station = coriolis 13:22:34.784 [PLANETINFO OVER]: Done 13:22:34.785 [PLANETINFO LOGGING]: 4 129 13:22:35.789 [planetinfo.record]: seed = 168 224 27 204 13:22:35.789 [planetinfo.record]: coordinates = 224 231 13:22:35.789 [planetinfo.record]: government = 5; 13:22:35.789 [planetinfo.record]: economy = 7; 13:22:35.789 [planetinfo.record]: techlevel = 3; 13:22:35.789 [planetinfo.record]: population = 25; 13:22:35.789 [planetinfo.record]: productivity = 5400; 13:22:35.789 [planetinfo.record]: name = "Inbe"; 13:22:35.789 [planetinfo.record]: inhabitant = "Human Colonial"; 13:22:35.789 [planetinfo.record]: inhabitants = "Human Colonials"; 13:22:35.789 [planetinfo.record]: description = "This planet is plagued by frequent earthquakes."; 13:22:35.800 [planetinfo.record]: air_color = 0.434921, 0.682554, 0.886482, 1; 13:22:35.800 [planetinfo.record]: cloud_alpha = 1.000000; 13:22:35.800 [planetinfo.record]: cloud_color = 0.0465546, 0.662109, 0.272579, 1; 13:22:35.800 [planetinfo.record]: cloud_fraction = 0.290000; 13:22:35.800 [planetinfo.record]: land_color = 0.605469, 0.465927, 0.48228, 1; 13:22:35.800 [planetinfo.record]: land_fraction = 0.520000; 13:22:35.800 [planetinfo.record]: polar_cloud_color = 0.334297, 0.797949, 0.504544, 1; 13:22:35.800 [planetinfo.record]: polar_land_color = 0.939453, 0.885324, 0.891668, 1; 13:22:35.800 [planetinfo.record]: polar_sea_color = 0.925586, 0.852173, 0.823446, 1; 13:22:35.800 [planetinfo.record]: sea_color = 0.744141, 0.508054, 0.415672, 1; 13:22:35.800 [planetinfo.record]: rotation_speed = 0.004853 13:22:35.800 [planetinfo.record]: planet zpos = 672320.000000 13:22:35.800 [planetinfo.record]: sun_radius = 137104.052734 13:22:35.800 [planetinfo.record]: sun_vector = -0.824 -0.132 0.551 13:22:35.800 [planetinfo.record]: sun_distance = 1039040 13:22:35.800 [planetinfo.record]: corona_flare = 0.014613 13:22:35.800 [planetinfo.record]: corona_hues = 0.966125 13:22:35.800 [planetinfo.record]: sun_color = 0.724143, 0.745939, 0.759894, 1 13:22:35.801 [planetinfo.record]: corona_shimmer = 0.391451 13:22:35.801 [planetinfo.record]: station_vector = -0.429 -0.083 0.900 13:22:35.801 [planetinfo.record]: station = coriolis 13:22:39.958 [PLANETINFO OVER]: Done 13:22:39.959 [PLANETINFO LOGGING]: 4 130 13:22:40.961 [planetinfo.record]: seed = 144 160 5 117 13:22:40.961 [planetinfo.record]: coordinates = 160 64 13:22:40.961 [planetinfo.record]: government = 2; 13:22:40.961 [planetinfo.record]: economy = 0; 13:22:40.961 [planetinfo.record]: techlevel = 8; 13:22:40.962 [planetinfo.record]: population = 35; 13:22:40.962 [planetinfo.record]: productivity = 16800; 13:22:40.962 [planetinfo.record]: name = "Lavema"; 13:22:40.962 [planetinfo.record]: inhabitant = "Human Colonial"; 13:22:40.962 [planetinfo.record]: inhabitants = "Human Colonials"; 13:22:40.962 [planetinfo.record]: description = "Lavema is reasonably well known for the Lavemaian spotted wolf but beset by deadly solar activity."; 13:22:40.977 [planetinfo.record]: air_color = 0.658668, 0.886608, 0.897539, 1; 13:22:40.977 [planetinfo.record]: cloud_alpha = 1.000000; 13:22:40.977 [planetinfo.record]: cloud_color = 0.674751, 0.695312, 0.556793, 1; 13:22:40.977 [planetinfo.record]: cloud_fraction = 0.390000; 13:22:40.977 [planetinfo.record]: land_color = 0.652104, 0.623906, 0.66, 1; 13:22:40.977 [planetinfo.record]: land_fraction = 0.450000; 13:22:40.977 [planetinfo.record]: polar_cloud_color = 0.797867, 0.812891, 0.711676, 1; 13:22:40.977 [planetinfo.record]: polar_land_color = 0.931207, 0.92123, 0.934, 1; 13:22:40.978 [planetinfo.record]: polar_sea_color = 0.860991, 0.939258, 0.706278, 1; 13:22:40.978 [planetinfo.record]: sea_color = 0.40496, 0.607422, 0.00474548, 1; 13:22:40.978 [planetinfo.record]: rotation_speed = 0.001288 13:22:40.978 [planetinfo.record]: planet zpos = 468160.000000 13:22:40.978 [planetinfo.record]: sun_radius = 98558.325195 13:22:40.978 [planetinfo.record]: sun_vector = -0.542 0.718 -0.437 13:22:40.978 [planetinfo.record]: sun_distance = 808640 13:22:40.978 [planetinfo.record]: corona_flare = 0.026385 13:22:40.978 [planetinfo.record]: corona_hues = 0.517555 13:22:40.978 [planetinfo.record]: sun_color = 0.610948, 0.639042, 0.669046, 1 13:22:40.978 [planetinfo.record]: corona_shimmer = 1.190741 13:22:40.979 [planetinfo.record]: station_vector = 0.162 -0.819 0.550 13:22:40.979 [planetinfo.record]: station = coriolis 13:22:45.395 [PLANETINFO OVER]: Done 13:22:45.396 [PLANETINFO LOGGING]: 4 131 13:22:46.415 [planetinfo.record]: seed = 252 54 243 248 13:22:46.415 [planetinfo.record]: coordinates = 54 107 13:22:46.415 [planetinfo.record]: government = 7; 13:22:46.415 [planetinfo.record]: economy = 3; 13:22:46.416 [planetinfo.record]: techlevel = 10; 13:22:46.416 [planetinfo.record]: population = 51; 13:22:46.416 [planetinfo.record]: productivity = 31416; 13:22:46.416 [planetinfo.record]: name = "Edanma"; 13:22:46.416 [planetinfo.record]: inhabitant = "Furry Humanoid"; 13:22:46.416 [planetinfo.record]: inhabitants = "Furry Humanoids"; 13:22:46.416 [planetinfo.record]: description = "Edanma is ravaged by unpredictable solar activity."; 13:22:46.452 [planetinfo.record]: air_color = 0.583426, 0.573593, 0.839654, 1; 13:22:46.452 [planetinfo.record]: cloud_alpha = 1.000000; 13:22:46.452 [planetinfo.record]: cloud_color = 0.361067, 0.33815, 0.521484, 1; 13:22:46.452 [planetinfo.record]: cloud_fraction = 0.250000; 13:22:46.452 [planetinfo.record]: land_color = 0.238373, 0.461395, 0.570312, 1; 13:22:46.452 [planetinfo.record]: land_fraction = 0.710000; 13:22:46.452 [planetinfo.record]: polar_cloud_color = 0.59342, 0.573242, 0.734668, 1; 13:22:46.452 [planetinfo.record]: polar_land_color = 0.805759, 0.897947, 0.942969, 1; 13:22:46.452 [planetinfo.record]: polar_sea_color = 0.875769, 0.925, 0.911154, 1; 13:22:46.452 [planetinfo.record]: sea_color = 0.590332, 0.75, 0.705093, 1; 13:22:46.452 [planetinfo.record]: rotation_speed = 0.003441 13:22:46.452 [planetinfo.record]: planet zpos = 590160.000000 13:22:46.452 [planetinfo.record]: sun_radius = 116243.431702 13:22:46.452 [planetinfo.record]: sun_vector = -0.346 0.846 -0.406 13:22:46.452 [planetinfo.record]: sun_distance = 1131140 13:22:46.452 [planetinfo.record]: corona_flare = 0.094205 13:22:46.452 [planetinfo.record]: corona_hues = 0.836899 13:22:46.452 [planetinfo.record]: sun_color = 0.530517, 0.535496, 0.652512, 1 13:22:46.453 [planetinfo.record]: corona_shimmer = 0.480307 13:22:46.453 [planetinfo.record]: station_vector = -0.394 0.861 0.322 13:22:46.453 [planetinfo.record]: station = coriolis 13:22:51.069 [PLANETINFO OVER]: Done 13:22:51.069 [PLANETINFO LOGGING]: 4 132 13:22:52.072 [planetinfo.record]: seed = 76 96 165 199 13:22:52.072 [planetinfo.record]: coordinates = 96 203 13:22:52.072 [planetinfo.record]: government = 1; 13:22:52.072 [planetinfo.record]: economy = 3; 13:22:52.072 [planetinfo.record]: techlevel = 5; 13:22:52.072 [planetinfo.record]: population = 25; 13:22:52.072 [planetinfo.record]: productivity = 7000; 13:22:52.072 [planetinfo.record]: name = "Sobean"; 13:22:52.072 [planetinfo.record]: inhabitant = "Fierce Bony Feline"; 13:22:52.072 [planetinfo.record]: inhabitants = "Fierce Bony Felines"; 13:22:52.072 [planetinfo.record]: description = "Sobean is cursed by vicious killer cats."; 13:22:52.083 [planetinfo.record]: air_color = 0.510245, 0.644614, 0.839654, 1; 13:22:52.083 [planetinfo.record]: cloud_alpha = 1.000000; 13:22:52.084 [planetinfo.record]: cloud_color = 0.220001, 0.521484, 0.45789, 1; 13:22:52.084 [planetinfo.record]: cloud_fraction = 0.530000; 13:22:52.084 [planetinfo.record]: land_color = 0.394439, 0.570312, 0.0467834, 1; 13:22:52.084 [planetinfo.record]: land_fraction = 0.640000; 13:22:52.084 [planetinfo.record]: polar_cloud_color = 0.469212, 0.734668, 0.678673, 1; 13:22:52.084 [planetinfo.record]: polar_land_color = 0.870271, 0.942969, 0.726565, 1; 13:22:52.084 [planetinfo.record]: polar_sea_color = 0.936914, 0.89746, 0.87607, 1; 13:22:52.084 [planetinfo.record]: sea_color = 0.630859, 0.524596, 0.466984, 1; 13:22:52.084 [planetinfo.record]: rotation_speed = 0.002410 13:22:52.084 [planetinfo.record]: planet zpos = 517440.000000 13:22:52.084 [planetinfo.record]: sun_radius = 149826.591797 13:22:52.084 [planetinfo.record]: sun_vector = -0.835 -0.458 0.306 13:22:52.084 [planetinfo.record]: sun_distance = 987840 13:22:52.084 [planetinfo.record]: corona_flare = 0.093478 13:22:52.084 [planetinfo.record]: corona_hues = 0.612373 13:22:52.084 [planetinfo.record]: sun_color = 0.776651, 0.755459, 0.705378, 1 13:22:52.085 [planetinfo.record]: corona_shimmer = 0.321428 13:22:52.085 [planetinfo.record]: station_vector = -0.096 0.907 0.409 13:22:52.085 [planetinfo.record]: station = coriolis 13:22:57.648 [PLANETINFO OVER]: Done 13:22:57.648 [PLANETINFO LOGGING]: 4 133 13:22:58.657 [planetinfo.record]: seed = 192 214 219 229 13:22:58.657 [planetinfo.record]: coordinates = 214 27 13:22:58.657 [planetinfo.record]: government = 0; 13:22:58.657 [planetinfo.record]: economy = 3; 13:22:58.657 [planetinfo.record]: techlevel = 6; 13:22:58.657 [planetinfo.record]: population = 28; 13:22:58.657 [planetinfo.record]: productivity = 6272; 13:22:58.657 [planetinfo.record]: name = "Ceedra"; 13:22:58.657 [planetinfo.record]: inhabitant = "Fierce Furry Feline"; 13:22:58.657 [planetinfo.record]: inhabitants = "Fierce Furry Felines"; 13:22:58.657 [planetinfo.record]: description = "This planet is plagued by occasional solar activity."; 13:22:58.659 [planetinfo.record]: air_color = 0.561497, 0.960493, 0.976236, 1; 13:22:58.659 [planetinfo.record]: cloud_alpha = 1.000000; 13:22:58.659 [planetinfo.record]: cloud_color = 0.852942, 0.931641, 0.302055, 1; 13:22:58.659 [planetinfo.record]: cloud_fraction = 0.240000; 13:22:58.659 [planetinfo.record]: land_color = 0.613571, 0.617188, 0.588257, 1; 13:22:58.659 [planetinfo.record]: land_fraction = 0.680000; 13:22:58.659 [planetinfo.record]: polar_cloud_color = 0.870707, 0.919238, 0.530986, 1; 13:22:58.659 [planetinfo.record]: polar_land_color = 0.936907, 0.938281, 0.927286, 1; 13:22:58.660 [planetinfo.record]: polar_sea_color = 0.828529, 0.909766, 0.791603, 1; 13:22:58.660 [planetinfo.record]: sea_color = 0.580047, 0.902344, 0.433548, 1; 13:22:58.660 [planetinfo.record]: rotation_speed = 0.003301 13:22:58.660 [planetinfo.record]: planet zpos = 560300.000000 13:22:58.660 [planetinfo.record]: sun_radius = 145939.956665 13:22:58.660 [planetinfo.record]: sun_vector = 0.577 0.579 -0.576 13:22:58.660 [planetinfo.record]: sun_distance = 818900 13:22:58.660 [planetinfo.record]: corona_flare = 0.009619 13:22:58.660 [planetinfo.record]: corona_hues = 0.994377 13:22:58.660 [planetinfo.record]: sun_color = 0.620561, 0.783987, 0.790823, 1 13:22:58.660 [planetinfo.record]: corona_shimmer = 0.492989 13:22:58.660 [planetinfo.record]: station_vector = -0.737 -0.269 0.620 13:22:58.660 [planetinfo.record]: station = coriolis 13:23:03.230 [PLANETINFO OVER]: Done 13:23:03.231 [PLANETINFO LOGGING]: 4 134 13:23:04.233 [planetinfo.record]: seed = 184 82 149 191 13:23:04.233 [planetinfo.record]: coordinates = 82 148 13:23:04.233 [planetinfo.record]: government = 7; 13:23:04.233 [planetinfo.record]: economy = 4; 13:23:04.233 [planetinfo.record]: techlevel = 9; 13:23:04.233 [planetinfo.record]: population = 48; 13:23:04.233 [planetinfo.record]: productivity = 25344; 13:23:04.233 [planetinfo.record]: name = "Onsoor"; 13:23:04.233 [planetinfo.record]: inhabitant = "Harmless Frog"; 13:23:04.233 [planetinfo.record]: inhabitants = "Harmless Frogs"; 13:23:04.234 [planetinfo.record]: description = "Onsoor is most noted for the Onsoorian evil arts graduate and its fabulous Zaleen juice."; 13:23:04.260 [planetinfo.record]: air_color = 0.614077, 0.930513, 0.93266, 1; 13:23:04.260 [planetinfo.record]: cloud_alpha = 1.000000; 13:23:04.260 [planetinfo.record]: cloud_color = 0.790126, 0.800781, 0.459824, 1; 13:23:04.260 [planetinfo.record]: cloud_fraction = 0.130000; 13:23:04.260 [planetinfo.record]: land_color = 0.570128, 0.66, 0.409922, 1; 13:23:04.260 [planetinfo.record]: land_fraction = 0.420000; 13:23:04.260 [planetinfo.record]: polar_cloud_color = 0.853197, 0.860352, 0.631401, 1; 13:23:04.260 [planetinfo.record]: polar_land_color = 0.902204, 0.934, 0.845525, 1; 13:23:04.260 [planetinfo.record]: polar_sea_color = 0.926172, 0.854663, 0.826681, 1; 13:23:04.260 [planetinfo.record]: sea_color = 0.738281, 0.510272, 0.421051, 1; 13:23:04.260 [planetinfo.record]: rotation_speed = 0.004748 13:23:04.260 [planetinfo.record]: planet zpos = 741180.000000 13:23:04.260 [planetinfo.record]: sun_radius = 128277.594910 13:23:04.260 [planetinfo.record]: sun_vector = 0.084 -0.054 -0.995 13:23:04.260 [planetinfo.record]: sun_distance = 1482360 13:23:04.260 [planetinfo.record]: corona_flare = 0.019600 13:23:04.260 [planetinfo.record]: corona_hues = 0.836044 13:23:04.260 [planetinfo.record]: sun_color = 0.653311, 0.188914, 0.186378, 1 13:23:04.260 [planetinfo.record]: corona_shimmer = 0.399079 13:23:04.261 [planetinfo.record]: station_vector = 0.765 -0.428 0.481 13:23:04.261 [planetinfo.record]: station = coriolis 13:23:08.694 [PLANETINFO OVER]: Done 13:23:08.695 [PLANETINFO LOGGING]: 4 135 13:23:09.715 [planetinfo.record]: seed = 244 31 83 128 13:23:09.715 [planetinfo.record]: coordinates = 31 185 13:23:09.715 [planetinfo.record]: government = 6; 13:23:09.715 [planetinfo.record]: economy = 1; 13:23:09.715 [planetinfo.record]: techlevel = 12; 13:23:09.715 [planetinfo.record]: population = 56; 13:23:09.715 [planetinfo.record]: productivity = 40320; 13:23:09.715 [planetinfo.record]: name = "Ororbe"; 13:23:09.715 [planetinfo.record]: inhabitant = "Human Colonial"; 13:23:09.715 [planetinfo.record]: inhabitants = "Human Colonials"; 13:23:09.715 [planetinfo.record]: description = "This planet is mildly fabled for its inhabitants’ ingrained shyness but scourged by dreadful solar activity."; 13:23:09.732 [planetinfo.record]: air_color = 0.489933, 0.60687, 0.871523, 1; 13:23:09.732 [planetinfo.record]: cloud_alpha = 1.000000; 13:23:09.732 [planetinfo.record]: cloud_color = 0.175995, 0.575826, 0.617188, 1; 13:23:09.732 [planetinfo.record]: cloud_fraction = 0.450000; 13:23:09.732 [planetinfo.record]: land_color = 0.519531, 0.476913, 0.487901, 1; 13:23:09.732 [planetinfo.record]: land_fraction = 0.400000; 13:23:09.732 [planetinfo.record]: polar_cloud_color = 0.43026, 0.745159, 0.777734, 1; 13:23:09.732 [planetinfo.record]: polar_land_color = 0.948047, 0.928604, 0.933617, 1; 13:23:09.732 [planetinfo.record]: polar_sea_color = 0.935938, 0.920067, 0.877898, 1; 13:23:09.732 [planetinfo.record]: sea_color = 0.640625, 0.597174, 0.48172, 1; 13:23:09.732 [planetinfo.record]: rotation_speed = 0.004320 13:23:09.732 [planetinfo.record]: planet zpos = 313170.000000 13:23:09.732 [planetinfo.record]: sun_radius = 78225.599670 13:23:09.732 [planetinfo.record]: sun_vector = -0.867 -0.420 -0.267 13:23:09.732 [planetinfo.record]: sun_distance = 512460 13:23:09.732 [planetinfo.record]: corona_flare = 0.044429 13:23:09.732 [planetinfo.record]: corona_hues = 0.735107 13:23:09.732 [planetinfo.record]: sun_color = 0.680832, 0.563874, 0.534375, 1 13:23:09.732 [planetinfo.record]: corona_shimmer = 0.854936 13:23:09.733 [planetinfo.record]: station_vector = -0.289 -0.929 0.230 13:23:09.733 [planetinfo.record]: station = icosahedron 13:23:14.602 [PLANETINFO OVER]: Done 13:23:14.603 [PLANETINFO LOGGING]: 4 136 13:23:15.605 [planetinfo.record]: seed = 212 211 85 39 13:23:15.605 [planetinfo.record]: coordinates = 211 249 13:23:15.605 [planetinfo.record]: government = 2; 13:23:15.605 [planetinfo.record]: economy = 1; 13:23:15.605 [planetinfo.record]: techlevel = 10; 13:23:15.605 [planetinfo.record]: population = 44; 13:23:15.605 [planetinfo.record]: productivity = 19008; 13:23:15.605 [planetinfo.record]: name = "Solaerin"; 13:23:15.605 [planetinfo.record]: inhabitant = "Human Colonial"; 13:23:15.605 [planetinfo.record]: inhabitants = "Human Colonials"; 13:23:15.605 [planetinfo.record]: description = "This world is a tedious place."; 13:23:15.615 [planetinfo.record]: air_color = 0.598722, 0.87694, 0.976887, 1; 13:23:15.615 [planetinfo.record]: cloud_alpha = 1.000000; 13:23:15.615 [planetinfo.record]: cloud_color = 0.511015, 0.933594, 0.408447, 1; 13:23:15.616 [planetinfo.record]: cloud_fraction = 0.400000; 13:23:15.616 [planetinfo.record]: land_color = 0.587813, 0.614319, 0.66, 1; 13:23:15.616 [planetinfo.record]: land_fraction = 0.340000; 13:23:15.616 [planetinfo.record]: polar_cloud_color = 0.659818, 0.920117, 0.596639, 1; 13:23:15.616 [planetinfo.record]: polar_land_color = 0.908461, 0.917839, 0.934, 1; 13:23:15.616 [planetinfo.record]: polar_sea_color = 0.927148, 0.87793, 0.838417, 1; 13:23:15.616 [planetinfo.record]: sea_color = 0.728516, 0.573822, 0.449631, 1; 13:23:15.616 [planetinfo.record]: rotation_speed = 0.003291 13:23:15.616 [planetinfo.record]: planet zpos = 530090.000000 13:23:15.616 [planetinfo.record]: sun_radius = 118075.647430 13:23:15.616 [planetinfo.record]: sun_vector = 0.704 -0.474 0.529 13:23:15.616 [planetinfo.record]: sun_distance = 963800 13:23:15.616 [planetinfo.record]: corona_flare = 0.095158 13:23:15.616 [planetinfo.record]: corona_hues = 0.956779 13:23:15.616 [planetinfo.record]: sun_color = 0.565896, 0.691001, 0.760992, 1 13:23:15.616 [planetinfo.record]: corona_shimmer = 0.343484 13:23:15.616 [planetinfo.record]: station_vector = -0.853 -0.416 0.315 13:23:15.616 [planetinfo.record]: station = coriolis 13:23:20.911 [PLANETINFO OVER]: Done 13:23:20.912 [PLANETINFO LOGGING]: 4 137 13:23:21.916 [planetinfo.record]: seed = 152 12 219 241 13:23:21.916 [planetinfo.record]: coordinates = 12 240 13:23:21.916 [planetinfo.record]: government = 3; 13:23:21.916 [planetinfo.record]: economy = 0; 13:23:21.916 [planetinfo.record]: techlevel = 9; 13:23:21.916 [planetinfo.record]: population = 40; 13:23:21.916 [planetinfo.record]: productivity = 22400; 13:23:21.916 [planetinfo.record]: name = "Atredi"; 13:23:21.916 [planetinfo.record]: inhabitant = "Fat Humanoid"; 13:23:21.916 [planetinfo.record]: inhabitants = "Fat Humanoids"; 13:23:21.916 [planetinfo.record]: description = "This world is very fabled for its ancient mountains."; 13:23:21.936 [planetinfo.record]: air_color = 0.544579, 0.947511, 0.996398, 1; 13:23:21.936 [planetinfo.record]: cloud_alpha = 1.000000; 13:23:21.936 [planetinfo.record]: cloud_color = 0.738297, 0.992188, 0.23642, 1; 13:23:21.936 [planetinfo.record]: cloud_fraction = 0.390000; 13:23:21.936 [planetinfo.record]: land_color = 0.595225, 0.53625, 0.66, 1; 13:23:21.936 [planetinfo.record]: land_fraction = 0.380000; 13:23:21.936 [planetinfo.record]: polar_cloud_color = 0.795112, 0.946484, 0.495888, 1; 13:23:21.936 [planetinfo.record]: polar_land_color = 0.911083, 0.890219, 0.934, 1; 13:23:21.936 [planetinfo.record]: polar_sea_color = 0.941211, 0.927287, 0.89029, 1; 13:23:21.936 [planetinfo.record]: sea_color = 0.587891, 0.553103, 0.460667, 1; 13:23:21.936 [planetinfo.record]: rotation_speed = 0.001713 13:23:21.936 [planetinfo.record]: planet zpos = 431760.000000 13:23:21.936 [planetinfo.record]: sun_radius = 89739.807129 13:23:21.936 [planetinfo.record]: sun_vector = 0.025 -0.982 0.188 13:23:21.936 [planetinfo.record]: sun_distance = 555120 13:23:21.936 [planetinfo.record]: corona_flare = 0.063680 13:23:21.936 [planetinfo.record]: corona_hues = 0.995773 13:23:21.937 [planetinfo.record]: sun_color = 0.448607, 0.566093, 0.727792, 1 13:23:21.937 [planetinfo.record]: corona_shimmer = 0.258176 13:23:21.937 [planetinfo.record]: station_vector = -0.162 0.969 0.186 13:23:21.937 [planetinfo.record]: station = coriolis 13:23:26.712 [PLANETINFO OVER]: Done 13:23:26.713 [PLANETINFO LOGGING]: 4 138 13:23:27.716 [planetinfo.record]: seed = 160 205 101 169 13:23:27.716 [planetinfo.record]: coordinates = 205 237 13:23:27.717 [planetinfo.record]: government = 4; 13:23:27.717 [planetinfo.record]: economy = 5; 13:23:27.717 [planetinfo.record]: techlevel = 5; 13:23:27.717 [planetinfo.record]: population = 30; 13:23:27.717 [planetinfo.record]: productivity = 9600; 13:23:27.717 [planetinfo.record]: name = "Eszaan"; 13:23:27.717 [planetinfo.record]: inhabitant = "Human Colonial"; 13:23:27.717 [planetinfo.record]: inhabitants = "Human Colonials"; 13:23:27.717 [planetinfo.record]: description = "The world Eszaan is reasonably noted for Zero-G hockey and its inhabitants’ ingrained shyness."; 13:23:27.738 [planetinfo.record]: air_color = 0.75468, 0.557662, 0.919652, 1; 13:23:27.739 [planetinfo.record]: cloud_alpha = 1.000000; 13:23:27.739 [planetinfo.record]: cloud_color = 0.761719, 0.318375, 0.474238, 1; 13:23:27.739 [planetinfo.record]: cloud_fraction = 0.250000; 13:23:27.739 [planetinfo.record]: land_color = 0.469219, 0.619757, 0.66, 1; 13:23:27.739 [planetinfo.record]: land_fraction = 0.460000; 13:23:27.739 [planetinfo.record]: polar_cloud_color = 0.842773, 0.536198, 0.643979, 1; 13:23:27.740 [planetinfo.record]: polar_land_color = 0.866504, 0.919763, 0.934, 1; 13:23:27.740 [planetinfo.record]: polar_sea_color = 0.94668, 0.894908, 0.894908, 1; 13:23:27.740 [planetinfo.record]: sea_color = 0.533203, 0.416565, 0.416565, 1; 13:23:27.740 [planetinfo.record]: rotation_speed = 0.003194 13:23:27.740 [planetinfo.record]: planet zpos = 639000.000000 13:23:27.740 [planetinfo.record]: sun_radius = 143069.561005 13:23:27.740 [planetinfo.record]: sun_vector = -0.074 0.967 -0.245 13:23:27.740 [planetinfo.record]: sun_distance = 905250 13:23:27.740 [planetinfo.record]: corona_flare = 0.052020 13:23:27.740 [planetinfo.record]: corona_hues = 0.815773 13:23:27.740 [planetinfo.record]: sun_color = 0.745844, 0.698345, 0.687384, 1 13:23:27.741 [planetinfo.record]: corona_shimmer = 0.548887 13:23:27.741 [planetinfo.record]: station_vector = 0.230 -0.522 0.821 13:23:27.741 [planetinfo.record]: station = coriolis 13:23:32.777 [PLANETINFO OVER]: Done 13:23:32.778 [PLANETINFO LOGGING]: 4 139 13:23:33.788 [planetinfo.record]: seed = 172 232 243 39 13:23:33.788 [planetinfo.record]: coordinates = 232 219 13:23:33.788 [planetinfo.record]: government = 5; 13:23:33.788 [planetinfo.record]: economy = 3; 13:23:33.788 [planetinfo.record]: techlevel = 7; 13:23:33.788 [planetinfo.record]: population = 37; 13:23:33.788 [planetinfo.record]: productivity = 18648; 13:23:33.788 [planetinfo.record]: name = "Somate"; 13:23:33.788 [planetinfo.record]: inhabitant = "Fierce Red Bony Feline"; 13:23:33.788 [planetinfo.record]: inhabitants = "Fierce Red Bony Felines"; 13:23:33.788 [planetinfo.record]: description = "Somate is an unremarkable planet."; 13:23:33.798 [planetinfo.record]: air_color = 0.439716, 0.680178, 0.884531, 1; 13:23:33.798 [planetinfo.record]: cloud_alpha = 1.000000; 13:23:33.798 [planetinfo.record]: cloud_color = 0.05896, 0.65625, 0.292276, 1; 13:23:33.798 [planetinfo.record]: cloud_fraction = 0.360000; 13:23:33.798 [planetinfo.record]: land_color = 0.472237, 0.297989, 0.646484, 1; 13:23:33.798 [planetinfo.record]: land_fraction = 0.400000; 13:23:33.798 [planetinfo.record]: polar_cloud_color = 0.342901, 0.795313, 0.519624, 1; 13:23:33.799 [planetinfo.record]: polar_land_color = 0.872325, 0.809298, 0.935352, 1; 13:23:33.799 [planetinfo.record]: polar_sea_color = 0.941797, 0.920357, 0.890016, 1; 13:23:33.799 [planetinfo.record]: sea_color = 0.582031, 0.529031, 0.45403, 1; 13:23:33.799 [planetinfo.record]: rotation_speed = 0.000248 13:23:33.799 [planetinfo.record]: planet zpos = 677600.000000 13:23:33.799 [planetinfo.record]: sun_radius = 107304.046631 13:23:33.799 [planetinfo.record]: sun_vector = 0.017 -0.993 -0.120 13:23:33.799 [planetinfo.record]: sun_distance = 1016400 13:23:33.799 [planetinfo.record]: corona_flare = 0.005736 13:23:33.799 [planetinfo.record]: corona_hues = 0.557938 13:23:33.799 [planetinfo.record]: sun_color = 0.699924, 0.5534, 0.503326, 1 13:23:33.799 [planetinfo.record]: corona_shimmer = 0.699125 13:23:33.800 [planetinfo.record]: station_vector = -0.688 -0.717 0.109 13:23:33.801 [planetinfo.record]: station = coriolis 13:23:38.205 [PLANETINFO OVER]: Done 13:23:38.206 [PLANETINFO LOGGING]: 4 140 13:23:39.208 [planetinfo.record]: seed = 28 16 69 248 13:23:39.208 [planetinfo.record]: coordinates = 16 252 13:23:39.208 [planetinfo.record]: government = 3; 13:23:39.208 [planetinfo.record]: economy = 4; 13:23:39.208 [planetinfo.record]: techlevel = 5; 13:23:39.208 [planetinfo.record]: population = 28; 13:23:39.208 [planetinfo.record]: productivity = 9408; 13:23:39.209 [planetinfo.record]: name = "Edzadiar"; 13:23:39.209 [planetinfo.record]: inhabitant = "Human Colonial"; 13:23:39.209 [planetinfo.record]: inhabitants = "Human Colonials"; 13:23:39.209 [planetinfo.record]: description = "Edzadiar is very notable for the Edzadiarian tree grub."; 13:23:39.233 [planetinfo.record]: air_color = 0.496784, 0.978838, 0.816547, 1; 13:23:39.233 [planetinfo.record]: cloud_alpha = 1.000000; 13:23:39.233 [planetinfo.record]: cloud_color = 0.939453, 0.113762, 0.113762, 1; 13:23:39.233 [planetinfo.record]: cloud_fraction = 0.350000; 13:23:39.233 [planetinfo.record]: land_color = 0.157266, 0.259384, 0.66, 1; 13:23:39.233 [planetinfo.record]: land_fraction = 0.320000; 13:23:39.233 [planetinfo.record]: polar_cloud_color = 0.922754, 0.41587, 0.41587, 1; 13:23:39.233 [planetinfo.record]: polar_land_color = 0.756139, 0.792267, 0.934, 1; 13:23:39.234 [planetinfo.record]: polar_sea_color = 0.948438, 0.905837, 0.900182, 1; 13:23:39.234 [planetinfo.record]: sea_color = 0.515625, 0.422985, 0.410687, 1; 13:23:39.235 [planetinfo.record]: rotation_speed = 0.004295 13:23:39.235 [planetinfo.record]: planet zpos = 683200.000000 13:23:39.235 [planetinfo.record]: sun_radius = 140887.512207 13:23:39.235 [planetinfo.record]: sun_vector = -0.859 0.426 0.282 13:23:39.235 [planetinfo.record]: sun_distance = 1073600 13:23:39.235 [planetinfo.record]: corona_flare = 0.057690 13:23:39.235 [planetinfo.record]: corona_hues = 0.531868 13:23:39.235 [planetinfo.record]: sun_color = 0.495526, 0.618685, 0.731732, 1 13:23:39.235 [planetinfo.record]: corona_shimmer = 0.333816 13:23:39.236 [planetinfo.record]: station_vector = -0.095 0.949 0.300 13:23:39.236 [planetinfo.record]: station = coriolis 13:23:43.714 [PLANETINFO OVER]: Done 13:23:43.715 [PLANETINFO LOGGING]: 4 141 13:23:44.719 [planetinfo.record]: seed = 48 10 27 28 13:23:44.719 [planetinfo.record]: coordinates = 10 13 13:23:44.719 [planetinfo.record]: government = 6; 13:23:44.719 [planetinfo.record]: economy = 5; 13:23:44.719 [planetinfo.record]: techlevel = 7; 13:23:44.719 [planetinfo.record]: population = 40; 13:23:44.719 [planetinfo.record]: productivity = 16000; 13:23:44.719 [planetinfo.record]: name = "Tebeor"; 13:23:44.719 [planetinfo.record]: inhabitant = "Human Colonial"; 13:23:44.719 [planetinfo.record]: inhabitants = "Human Colonials"; 13:23:44.719 [planetinfo.record]: description = "Tebeor is mildly well known for its hoopy night life."; 13:23:44.725 [planetinfo.record]: air_color = 0.646066, 0.88193, 0.846703, 1; 13:23:44.725 [planetinfo.record]: cloud_alpha = 1.000000; 13:23:44.725 [planetinfo.record]: cloud_color = 0.648438, 0.590813, 0.516724, 1; 13:23:44.725 [planetinfo.record]: cloud_fraction = 0.370000; 13:23:44.726 [planetinfo.record]: land_color = 0.61446, 0.66, 0.353203, 1; 13:23:44.726 [planetinfo.record]: land_fraction = 0.620000; 13:23:44.726 [planetinfo.record]: polar_cloud_color = 0.791797, 0.747819, 0.691276, 1; 13:23:44.726 [planetinfo.record]: polar_land_color = 0.917888, 0.934, 0.825459, 1; 13:23:44.726 [planetinfo.record]: polar_sea_color = 0.94043, 0.920938, 0.887347, 1; 13:23:44.726 [planetinfo.record]: sea_color = 0.595703, 0.546317, 0.461205, 1; 13:23:44.726 [planetinfo.record]: rotation_speed = 0.000053 13:23:44.726 [planetinfo.record]: planet zpos = 707760.000000 13:23:44.726 [planetinfo.record]: sun_radius = 160702.860718 13:23:44.726 [planetinfo.record]: sun_vector = 0.851 0.431 0.300 13:23:44.727 [planetinfo.record]: sun_distance = 1002660 13:23:44.727 [planetinfo.record]: corona_flare = 0.064122 13:23:44.727 [planetinfo.record]: corona_hues = 0.883080 13:23:44.727 [planetinfo.record]: sun_color = 0.554732, 0.63212, 0.750018, 1 13:23:44.727 [planetinfo.record]: corona_shimmer = 0.397250 13:23:44.727 [planetinfo.record]: station_vector = -0.977 -0.157 0.142 13:23:44.727 [planetinfo.record]: station = coriolis 13:23:49.259 [PLANETINFO OVER]: Done 13:23:49.260 [PLANETINFO LOGGING]: 4 142 13:23:50.272 [planetinfo.record]: seed = 72 169 117 54 13:23:50.272 [planetinfo.record]: coordinates = 169 89 13:23:50.272 [planetinfo.record]: government = 1; 13:23:50.272 [planetinfo.record]: economy = 3; 13:23:50.272 [planetinfo.record]: techlevel = 6; 13:23:50.272 [planetinfo.record]: population = 29; 13:23:50.272 [planetinfo.record]: productivity = 8120; 13:23:50.272 [planetinfo.record]: name = "Veoror"; 13:23:50.272 [planetinfo.record]: inhabitant = "Human Colonial"; 13:23:50.273 [planetinfo.record]: inhabitants = "Human Colonials"; 13:23:50.273 [planetinfo.record]: description = "This world is ravaged by occasional solar activity."; 13:23:50.296 [planetinfo.record]: air_color = 0.506938, 0.573137, 0.871523, 1; 13:23:50.296 [planetinfo.record]: cloud_alpha = 1.000000; 13:23:50.296 [planetinfo.record]: cloud_color = 0.212158, 0.42733, 0.617188, 1; 13:23:50.296 [planetinfo.record]: cloud_fraction = 0.280000; 13:23:50.296 [planetinfo.record]: land_color = 0.66, 0.0309375, 0.266836, 1; 13:23:50.296 [planetinfo.record]: land_fraction = 0.560000; 13:23:50.296 [planetinfo.record]: polar_cloud_color = 0.458742, 0.628207, 0.777734, 1; 13:23:50.296 [planetinfo.record]: polar_land_color = 0.934, 0.711445, 0.794903, 1; 13:23:50.296 [planetinfo.record]: polar_sea_color = 0.942383, 0.915932, 0.890294, 1; 13:23:50.296 [planetinfo.record]: sea_color = 0.576172, 0.511483, 0.448784, 1; 13:23:50.296 [planetinfo.record]: rotation_speed = 0.001574 13:23:50.296 [planetinfo.record]: planet zpos = 497310.000000 13:23:50.296 [planetinfo.record]: sun_radius = 85281.308441 13:23:50.296 [planetinfo.record]: sun_vector = -0.113 -0.014 -0.994 13:23:50.296 [planetinfo.record]: sun_distance = 994620 13:23:50.296 [planetinfo.record]: corona_flare = 0.046431 13:23:50.297 [planetinfo.record]: corona_hues = 0.774254 13:23:50.297 [planetinfo.record]: sun_color = 0.814413, 0.725737, 0.674694, 1 13:23:50.297 [planetinfo.record]: corona_shimmer = 0.303669 13:23:50.297 [planetinfo.record]: station_vector = 0.628 -0.688 0.364 13:23:50.297 [planetinfo.record]: station = coriolis 13:23:55.288 [PLANETINFO OVER]: Done 13:23:55.290 [PLANETINFO LOGGING]: 4 143 13:23:56.293 [planetinfo.record]: seed = 36 137 211 219 13:23:56.293 [planetinfo.record]: coordinates = 137 25 13:23:56.293 [planetinfo.record]: government = 4; 13:23:56.293 [planetinfo.record]: economy = 1; 13:23:56.293 [planetinfo.record]: techlevel = 9; 13:23:56.293 [planetinfo.record]: population = 42; 13:23:56.293 [planetinfo.record]: productivity = 24192; 13:23:56.293 [planetinfo.record]: name = "Anrige"; 13:23:56.293 [planetinfo.record]: inhabitant = "Slimy Lobster"; 13:23:56.293 [planetinfo.record]: inhabitants = "Slimy Lobsters"; 13:23:56.293 [planetinfo.record]: description = "The planet Anrige is a boring world."; 13:23:56.296 [planetinfo.record]: air_color = 0.61125, 0.898836, 0.954773, 1; 13:23:56.296 [planetinfo.record]: cloud_alpha = 1.000000; 13:23:56.296 [planetinfo.record]: cloud_color = 0.658859, 0.867188, 0.450531, 1; 13:23:56.296 [planetinfo.record]: cloud_fraction = 0.330000; 13:23:56.296 [planetinfo.record]: land_color = 0.240374, 0.554688, 0.0975037, 1; 13:23:56.296 [planetinfo.record]: land_fraction = 0.290000; 13:23:56.296 [planetinfo.record]: polar_cloud_color = 0.756569, 0.890234, 0.622903, 1; 13:23:56.296 [planetinfo.record]: polar_land_color = 0.810727, 0.944531, 0.749906, 1; 13:23:56.296 [planetinfo.record]: polar_sea_color = 0.946875, 0.908204, 0.897867, 1; 13:23:56.296 [planetinfo.record]: sea_color = 0.53125, 0.444465, 0.421265, 1; 13:23:56.296 [planetinfo.record]: rotation_speed = 0.001254 13:23:56.296 [planetinfo.record]: planet zpos = 749970.000000 13:23:56.296 [planetinfo.record]: sun_radius = 168793.601990 13:23:56.296 [planetinfo.record]: sun_vector = 0.898 -0.438 0.030 13:23:56.296 [planetinfo.record]: sun_distance = 1096110 13:23:56.296 [planetinfo.record]: corona_flare = 0.031720 13:23:56.296 [planetinfo.record]: corona_hues = 0.606956 13:23:56.296 [planetinfo.record]: sun_color = 0.834274, 0.505907, 0.239474, 1 13:23:56.297 [planetinfo.record]: corona_shimmer = 0.309295 13:23:56.297 [planetinfo.record]: station_vector = 0.450 -0.886 0.114 13:23:56.297 [planetinfo.record]: station = coriolis 13:24:00.926 [PLANETINFO OVER]: Done 13:24:00.927 [PLANETINFO LOGGING]: 4 144 13:24:01.929 [planetinfo.record]: seed = 36 61 117 158 13:24:01.929 [planetinfo.record]: coordinates = 61 227 13:24:01.930 [planetinfo.record]: government = 4; 13:24:01.930 [planetinfo.record]: economy = 3; 13:24:01.930 [planetinfo.record]: techlevel = 7; 13:24:01.930 [planetinfo.record]: population = 36; 13:24:01.930 [planetinfo.record]: productivity = 16128; 13:24:01.930 [planetinfo.record]: name = "Ririqu"; 13:24:01.930 [planetinfo.record]: inhabitant = "Human Colonial"; 13:24:01.930 [planetinfo.record]: inhabitants = "Human Colonials"; 13:24:01.930 [planetinfo.record]: description = "This world is fabled for its ancient Za plant plantations."; 13:24:01.937 [planetinfo.record]: air_color = 0.698213, 0.790446, 0.943066, 1; 13:24:01.937 [planetinfo.record]: cloud_alpha = 1.000000; 13:24:01.937 [planetinfo.record]: cloud_color = 0.682526, 0.832031, 0.814511, 1; 13:24:01.937 [planetinfo.record]: cloud_fraction = 0.250000; 13:24:01.937 [planetinfo.record]: land_color = 0.394849, 0.517578, 0.0930023, 1; 13:24:01.937 [planetinfo.record]: land_fraction = 0.510000; 13:24:01.937 [planetinfo.record]: polar_cloud_color = 0.776213, 0.874414, 0.862906, 1; 13:24:01.937 [planetinfo.record]: polar_land_color = 0.89203, 0.948242, 0.753778, 1; 13:24:01.937 [planetinfo.record]: polar_sea_color = 0.85216, 0.862583, 0.947461, 1; 13:24:01.937 [planetinfo.record]: sea_color = 0.314003, 0.337124, 0.525391, 1; 13:24:01.937 [planetinfo.record]: rotation_speed = 0.000199 13:24:01.937 [planetinfo.record]: planet zpos = 710710.000000 13:24:01.937 [planetinfo.record]: sun_radius = 170787.252045 13:24:01.937 [planetinfo.record]: sun_vector = -0.028 0.996 -0.090 13:24:01.937 [planetinfo.record]: sun_distance = 1292200 13:24:01.938 [planetinfo.record]: corona_flare = 0.071802 13:24:01.938 [planetinfo.record]: corona_hues = 0.687279 13:24:01.938 [planetinfo.record]: sun_color = 0.341344, 0.454306, 0.683054, 1 13:24:01.938 [planetinfo.record]: corona_shimmer = 0.273692 13:24:01.938 [planetinfo.record]: station_vector = -0.684 0.720 0.118 13:24:01.938 [planetinfo.record]: station = coriolis 13:24:06.384 [PLANETINFO OVER]: Done 13:24:06.386 [PLANETINFO LOGGING]: 4 145 13:24:07.404 [planetinfo.record]: seed = 136 247 155 80 13:24:07.404 [planetinfo.record]: coordinates = 247 154 13:24:07.404 [planetinfo.record]: government = 1; 13:24:07.404 [planetinfo.record]: economy = 2; 13:24:07.404 [planetinfo.record]: techlevel = 9; 13:24:07.404 [planetinfo.record]: population = 40; 13:24:07.404 [planetinfo.record]: productivity = 12800; 13:24:07.404 [planetinfo.record]: name = "Erxearis"; 13:24:07.404 [planetinfo.record]: inhabitant = "Yellow Furry Humanoid"; 13:24:07.404 [planetinfo.record]: inhabitants = "Yellow Furry Humanoids"; 13:24:07.405 [planetinfo.record]: description = "The planet Erxearis is most famous for the Erxearisian deadly lobstoid and the Erxearisian spotted shrew."; 13:24:07.419 [planetinfo.record]: air_color = 0.524686, 0.614937, 0.843557, 1; 13:24:07.419 [planetinfo.record]: cloud_alpha = 1.000000; 13:24:07.419 [planetinfo.record]: cloud_color = 0.247856, 0.486388, 0.533203, 1; 13:24:07.419 [planetinfo.record]: cloud_fraction = 0.450000; 13:24:07.419 [planetinfo.record]: land_color = 0.636719, 0.383026, 0.573296, 1; 13:24:07.420 [planetinfo.record]: land_fraction = 0.470000; 13:24:07.420 [planetinfo.record]: polar_cloud_color = 0.492451, 0.699338, 0.739941, 1; 13:24:07.420 [planetinfo.record]: polar_land_color = 0.936328, 0.843061, 0.913011, 1; 13:24:07.420 [planetinfo.record]: polar_sea_color = 0.944531, 0.891115, 0.889833, 1; 13:24:07.420 [planetinfo.record]: sea_color = 0.554688, 0.429211, 0.426199, 1; 13:24:07.420 [planetinfo.record]: rotation_speed = 0.003551 13:24:07.420 [planetinfo.record]: planet zpos = 398190.000000 13:24:07.420 [planetinfo.record]: sun_radius = 74843.369293 13:24:07.420 [planetinfo.record]: sun_vector = 0.596 -0.364 -0.716 13:24:07.420 [planetinfo.record]: sun_distance = 581970 13:24:07.420 [planetinfo.record]: corona_flare = 0.043971 13:24:07.420 [planetinfo.record]: corona_hues = 0.834221 13:24:07.421 [planetinfo.record]: sun_color = 0.342125, 0.642548, 0.657339, 1 13:24:07.422 [planetinfo.record]: corona_shimmer = 0.228650 13:24:07.422 [planetinfo.record]: station_vector = -0.213 -0.935 0.282 13:24:07.422 [planetinfo.record]: station = coriolis 13:24:12.343 [PLANETINFO OVER]: Done 13:24:12.345 [PLANETINFO LOGGING]: 4 146 13:24:13.348 [planetinfo.record]: seed = 176 93 197 106 13:24:13.348 [planetinfo.record]: coordinates = 93 42 13:24:13.348 [planetinfo.record]: government = 6; 13:24:13.348 [planetinfo.record]: economy = 2; 13:24:13.349 [planetinfo.record]: techlevel = 9; 13:24:13.349 [planetinfo.record]: population = 45; 13:24:13.349 [planetinfo.record]: productivity = 28800; 13:24:13.349 [planetinfo.record]: name = "Arbean"; 13:24:13.349 [planetinfo.record]: inhabitant = "Small Blue Frog"; 13:24:13.349 [planetinfo.record]: inhabitants = "Small Blue Frogs"; 13:24:13.349 [planetinfo.record]: description = "This planet is fabled for its fabulous cuisine."; 13:24:13.371 [planetinfo.record]: air_color = 0.459869, 0.680647, 0.868271, 1; 13:24:13.371 [planetinfo.record]: cloud_alpha = 1.000000; 13:24:13.371 [planetinfo.record]: cloud_color = 0.113892, 0.607422, 0.306677, 1; 13:24:13.371 [planetinfo.record]: cloud_fraction = 0.100000; 13:24:13.371 [planetinfo.record]: land_color = 0.66, 0.195938, 0.206814, 1; 13:24:13.371 [planetinfo.record]: land_fraction = 0.320000; 13:24:13.371 [planetinfo.record]: polar_cloud_color = 0.380628, 0.77334, 0.534031, 1; 13:24:13.371 [planetinfo.record]: polar_land_color = 0.934, 0.76982, 0.773668, 1; 13:24:13.371 [planetinfo.record]: polar_sea_color = 0.942773, 0.900973, 0.888177, 1; 13:24:13.371 [planetinfo.record]: sea_color = 0.572266, 0.470774, 0.439706, 1; 13:24:13.371 [planetinfo.record]: rotation_speed = 0.000023 13:24:13.371 [planetinfo.record]: planet zpos = 765660.000000 13:24:13.371 [planetinfo.record]: sun_radius = 122472.520294 13:24:13.371 [planetinfo.record]: sun_vector = -0.481 -0.667 0.568 13:24:13.372 [planetinfo.record]: sun_distance = 984420 13:24:13.372 [planetinfo.record]: corona_flare = 0.087973 13:24:13.372 [planetinfo.record]: corona_hues = 0.900497 13:24:13.374 [planetinfo.record]: sun_color = 0.685094, 0.689127, 0.705829, 1 13:24:13.374 [planetinfo.record]: corona_shimmer = 0.366131 13:24:13.374 [planetinfo.record]: station_vector = 0.301 -0.526 0.796 13:24:13.374 [planetinfo.record]: station = coriolis 13:24:18.075 [PLANETINFO OVER]: Done 13:24:18.076 [PLANETINFO LOGGING]: 4 147 13:24:19.080 [planetinfo.record]: seed = 92 25 243 199 13:24:19.081 [planetinfo.record]: coordinates = 25 187 13:24:19.081 [planetinfo.record]: government = 3; 13:24:19.081 [planetinfo.record]: economy = 3; 13:24:19.081 [planetinfo.record]: techlevel = 7; 13:24:19.081 [planetinfo.record]: population = 35; 13:24:19.081 [planetinfo.record]: productivity = 13720; 13:24:19.081 [planetinfo.record]: name = "Soteri"; 13:24:19.081 [planetinfo.record]: inhabitant = "Fierce Horned Humanoid"; 13:24:19.081 [planetinfo.record]: inhabitants = "Fierce Horned Humanoids"; 13:24:19.081 [planetinfo.record]: description = "This world is a revolting dump."; 13:24:19.087 [planetinfo.record]: air_color = 0.570132, 0.945721, 0.973635, 1; 13:24:19.088 [planetinfo.record]: cloud_alpha = 1.000000; 13:24:19.088 [planetinfo.record]: cloud_color = 0.793576, 0.923828, 0.328392, 1; 13:24:19.088 [planetinfo.record]: cloud_fraction = 0.250000; 13:24:19.088 [planetinfo.record]: land_color = 0.296827, 0.0232031, 0.66, 1; 13:24:19.088 [planetinfo.record]: land_fraction = 0.570000; 13:24:19.088 [planetinfo.record]: polar_cloud_color = 0.83503, 0.915723, 0.54684, 1; 13:24:19.088 [planetinfo.record]: polar_land_color = 0.805514, 0.708709, 0.934, 1; 13:24:19.088 [planetinfo.record]: polar_sea_color = 0.931582, 0.937891, 0.887424, 1; 13:24:19.088 [planetinfo.record]: sea_color = 0.604384, 0.621094, 0.487413, 1; 13:24:19.088 [planetinfo.record]: rotation_speed = 0.002165 13:24:19.088 [planetinfo.record]: planet zpos = 602290.000000 13:24:19.088 [planetinfo.record]: sun_radius = 94597.721710 13:24:19.088 [planetinfo.record]: sun_vector = 0.216 0.901 -0.377 13:24:19.088 [planetinfo.record]: sun_distance = 833940 13:24:19.088 [planetinfo.record]: corona_flare = 0.012468 13:24:19.088 [planetinfo.record]: corona_hues = 0.501984 13:24:19.089 [planetinfo.record]: sun_color = 0.343603, 0.385629, 0.849823, 1 13:24:19.089 [planetinfo.record]: corona_shimmer = 0.319124 13:24:19.089 [planetinfo.record]: station_vector = 0.854 0.326 0.406 13:24:19.089 [planetinfo.record]: station = coriolis 13:24:24.268 [PLANETINFO OVER]: Done 13:24:24.272 [PLANETINFO LOGGING]: 4 148 13:24:25.288 [planetinfo.record]: seed = 236 226 229 253 13:24:25.288 [planetinfo.record]: coordinates = 226 126 13:24:25.288 [planetinfo.record]: government = 5; 13:24:25.288 [planetinfo.record]: economy = 6; 13:24:25.288 [planetinfo.record]: techlevel = 6; 13:24:25.288 [planetinfo.record]: population = 36; 13:24:25.288 [planetinfo.record]: productivity = 10368; 13:24:25.288 [planetinfo.record]: name = "Isrion"; 13:24:25.288 [planetinfo.record]: inhabitant = "Fat Humanoid"; 13:24:25.288 [planetinfo.record]: inhabitants = "Fat Humanoids"; 13:24:25.289 [planetinfo.record]: description = "This world is reasonably well known for its great volcanoes but ravaged by vicious disease."; 13:24:25.308 [planetinfo.record]: air_color = 0.671756, 0.562667, 0.889734, 1; 13:24:25.308 [planetinfo.record]: cloud_alpha = 1.000000; 13:24:25.308 [planetinfo.record]: cloud_color = 0.671875, 0.333313, 0.666585, 1; 13:24:25.308 [planetinfo.record]: cloud_fraction = 0.260000; 13:24:25.308 [planetinfo.record]: land_color = 0.640027, 0.648438, 0.635773, 1; 13:24:25.308 [planetinfo.record]: land_fraction = 0.560000; 13:24:25.308 [planetinfo.record]: polar_cloud_color = 0.802344, 0.549652, 0.798395, 1; 13:24:25.308 [planetinfo.record]: polar_land_color = 0.932124, 0.935156, 0.93059, 1; 13:24:25.308 [planetinfo.record]: polar_sea_color = 0.773515, 0.902734, 0.762564, 1; 13:24:25.308 [planetinfo.record]: sea_color = 0.415742, 0.972656, 0.368546, 1; 13:24:25.308 [planetinfo.record]: rotation_speed = 0.001204 13:24:25.308 [planetinfo.record]: planet zpos = 764400.000000 13:24:25.309 [planetinfo.record]: sun_radius = 155421.351624 13:24:25.309 [planetinfo.record]: sun_vector = 0.515 0.445 0.733 13:24:25.309 [planetinfo.record]: sun_distance = 1210300 13:24:25.309 [planetinfo.record]: corona_flare = 0.017653 13:24:25.309 [planetinfo.record]: corona_hues = 0.969643 13:24:25.309 [planetinfo.record]: sun_color = 0.835944, 0.502459, 0.448123, 1 13:24:25.309 [planetinfo.record]: corona_shimmer = 0.258382 13:24:25.309 [planetinfo.record]: station_vector = 0.740 -0.311 0.597 13:24:25.309 [planetinfo.record]: station = coriolis 13:24:30.557 [PLANETINFO OVER]: Done 13:24:30.558 [PLANETINFO LOGGING]: 4 149 13:24:31.563 [planetinfo.record]: seed = 160 156 91 59 13:24:31.563 [planetinfo.record]: coordinates = 156 63 13:24:31.563 [planetinfo.record]: government = 4; 13:24:31.563 [planetinfo.record]: economy = 7; 13:24:31.563 [planetinfo.record]: techlevel = 2; 13:24:31.563 [planetinfo.record]: population = 20; 13:24:31.564 [planetinfo.record]: productivity = 3840; 13:24:31.564 [planetinfo.record]: name = "Antiaxe"; 13:24:31.564 [planetinfo.record]: inhabitant = "Human Colonial"; 13:24:31.564 [planetinfo.record]: inhabitants = "Human Colonials"; 13:24:31.564 [planetinfo.record]: description = "This world is plagued by frequent earthquakes."; 13:24:31.592 [planetinfo.record]: air_color = 0.54839, 0.958747, 0.989244, 1; 13:24:31.592 [planetinfo.record]: cloud_alpha = 1.000000; 13:24:31.592 [planetinfo.record]: cloud_color = 0.813936, 0.970703, 0.254051, 1; 13:24:31.592 [planetinfo.record]: cloud_fraction = 0.150000; 13:24:31.592 [planetinfo.record]: land_color = 0.66, 0.0438281, 0.462632, 1; 13:24:31.592 [planetinfo.record]: land_fraction = 0.470000; 13:24:31.592 [planetinfo.record]: polar_cloud_color = 0.842257, 0.936816, 0.504545, 1; 13:24:31.592 [planetinfo.record]: polar_land_color = 0.934, 0.716006, 0.864174, 1; 13:24:31.592 [planetinfo.record]: polar_sea_color = 0.938867, 0.936862, 0.887523, 1; 13:24:31.592 [planetinfo.record]: sea_color = 0.611328, 0.606104, 0.4776, 1; 13:24:31.592 [planetinfo.record]: rotation_speed = 0.001964 13:24:31.592 [planetinfo.record]: planet zpos = 752440.000000 13:24:31.592 [planetinfo.record]: sun_radius = 154712.597046 13:24:31.592 [planetinfo.record]: sun_vector = 0.137 -0.957 -0.256 13:24:31.592 [planetinfo.record]: sun_distance = 1099720 13:24:31.592 [planetinfo.record]: corona_flare = 0.099489 13:24:31.592 [planetinfo.record]: corona_hues = 0.833412 13:24:31.592 [planetinfo.record]: sun_color = 0.684434, 0.697154, 0.70849, 1 13:24:31.592 [planetinfo.record]: corona_shimmer = 0.319654 13:24:31.593 [planetinfo.record]: station_vector = 0.847 0.325 0.420 13:24:31.593 [planetinfo.record]: station = coriolis 13:24:36.953 [PLANETINFO OVER]: Done 13:24:36.954 [PLANETINFO LOGGING]: 4 150 13:24:37.962 [planetinfo.record]: seed = 216 66 85 74 13:24:37.962 [planetinfo.record]: coordinates = 66 239 13:24:37.962 [planetinfo.record]: government = 3; 13:24:37.962 [planetinfo.record]: economy = 7; 13:24:37.962 [planetinfo.record]: techlevel = 4; 13:24:37.962 [planetinfo.record]: population = 27; 13:24:37.962 [planetinfo.record]: productivity = 4536; 13:24:37.962 [planetinfo.record]: name = "Artear"; 13:24:37.962 [planetinfo.record]: inhabitant = "Human Colonial"; 13:24:37.962 [planetinfo.record]: inhabitants = "Human Colonials"; 13:24:37.962 [planetinfo.record]: description = "The world Artear is a boring planet."; 13:24:37.977 [planetinfo.record]: air_color = 0.679639, 0.869142, 0.873475, 1; 13:24:37.978 [planetinfo.record]: cloud_alpha = 1.000000; 13:24:37.978 [planetinfo.record]: cloud_color = 0.619815, 0.623047, 0.581673, 1; 13:24:37.978 [planetinfo.record]: cloud_fraction = 0.270000; 13:24:37.978 [planetinfo.record]: land_color = 0.66, 0.632084, 0.335156, 1; 13:24:37.978 [planetinfo.record]: land_fraction = 0.690000; 13:24:37.978 [planetinfo.record]: polar_cloud_color = 0.777841, 0.780371, 0.747983, 1; 13:24:37.978 [planetinfo.record]: polar_land_color = 0.934, 0.924124, 0.819074, 1; 13:24:37.978 [planetinfo.record]: polar_sea_color = 0.941992, 0.871159, 0.932585, 1; 13:24:37.978 [planetinfo.record]: sea_color = 0.580078, 0.405602, 0.556905, 1; 13:24:37.978 [planetinfo.record]: rotation_speed = 0.003442 13:24:37.978 [planetinfo.record]: planet zpos = 653040.000000 13:24:37.979 [planetinfo.record]: sun_radius = 156440.061951 13:24:37.979 [planetinfo.record]: sun_vector = 0.028 0.311 -0.950 13:24:37.979 [planetinfo.record]: sun_distance = 870720 13:24:37.979 [planetinfo.record]: corona_flare = 0.073053 13:24:37.979 [planetinfo.record]: corona_hues = 0.960365 13:24:37.979 [planetinfo.record]: sun_color = 0.760132, 0.653041, 0.472873, 1 13:24:37.979 [planetinfo.record]: corona_shimmer = 0.859331 13:24:37.979 [planetinfo.record]: station_vector = -0.007 0.997 0.081 13:24:37.979 [planetinfo.record]: station = coriolis 13:24:43.795 [PLANETINFO OVER]: Done 13:24:43.797 [PLANETINFO LOGGING]: 4 151 13:24:44.813 [planetinfo.record]: seed = 84 209 83 88 13:24:44.813 [planetinfo.record]: coordinates = 209 10 13:24:44.813 [planetinfo.record]: government = 2; 13:24:44.813 [planetinfo.record]: economy = 2; 13:24:44.813 [planetinfo.record]: techlevel = 7; 13:24:44.813 [planetinfo.record]: population = 33; 13:24:44.813 [planetinfo.record]: productivity = 12672; 13:24:44.813 [planetinfo.record]: name = "Edbeis"; 13:24:44.813 [planetinfo.record]: inhabitant = "Human Colonial"; 13:24:44.814 [planetinfo.record]: inhabitants = "Human Colonials"; 13:24:44.814 [planetinfo.record]: description = "Edbeis is an unremarkable planet."; 13:24:44.852 [planetinfo.record]: air_color = 0.788825, 0.563619, 0.925506, 1; 13:24:44.852 [planetinfo.record]: cloud_alpha = 1.000000; 13:24:44.852 [planetinfo.record]: cloud_color = 0.779297, 0.33181, 0.38425, 1; 13:24:44.852 [planetinfo.record]: cloud_fraction = 0.340000; 13:24:44.852 [planetinfo.record]: land_color = 0.656213, 0.66, 0.538828, 1; 13:24:44.852 [planetinfo.record]: land_fraction = 0.650000; 13:24:44.852 [planetinfo.record]: polar_cloud_color = 0.850684, 0.545385, 0.581162, 1; 13:24:44.852 [planetinfo.record]: polar_land_color = 0.93266, 0.934, 0.891131, 1; 13:24:44.852 [planetinfo.record]: polar_sea_color = 0.719508, 0.879081, 0.92793, 1; 13:24:44.852 [planetinfo.record]: sea_color = 0.0731964, 0.568944, 0.720703, 1; 13:24:44.852 [planetinfo.record]: rotation_speed = 0.003079 13:24:44.852 [planetinfo.record]: planet zpos = 608760.000000 13:24:44.852 [planetinfo.record]: sun_radius = 116196.903992 13:24:44.852 [planetinfo.record]: sun_vector = 0.677 0.651 -0.343 13:24:44.852 [planetinfo.record]: sun_distance = 1065330 13:24:44.852 [planetinfo.record]: corona_flare = 0.094978 13:24:44.852 [planetinfo.record]: corona_hues = 0.711426 13:24:44.852 [planetinfo.record]: sun_color = 0.845877, 0.831994, 0.791784, 1 13:24:44.853 [planetinfo.record]: corona_shimmer = 0.445417 13:24:44.853 [planetinfo.record]: station_vector = 0.987 -0.026 0.158 13:24:44.853 [planetinfo.record]: station = coriolis 13:24:49.303 [PLANETINFO OVER]: Done 13:24:49.304 [PLANETINFO LOGGING]: 4 152 13:24:50.306 [planetinfo.record]: seed = 116 233 149 122 13:24:50.307 [planetinfo.record]: coordinates = 233 93 13:24:50.307 [planetinfo.record]: government = 6; 13:24:50.307 [planetinfo.record]: economy = 5; 13:24:50.307 [planetinfo.record]: techlevel = 6; 13:24:50.307 [planetinfo.record]: population = 36; 13:24:50.307 [planetinfo.record]: productivity = 14400; 13:24:50.307 [planetinfo.record]: name = "Qulecele"; 13:24:50.307 [planetinfo.record]: inhabitant = "Blue Fat Feline"; 13:24:50.307 [planetinfo.record]: inhabitants = "Blue Fat Felines"; 13:24:50.307 [planetinfo.record]: description = "The planet Qulecele is reasonably fabled for Zero-G hockey and its inhabitants’ ingrained silliness."; 13:24:50.325 [planetinfo.record]: air_color = 0.44859, 0.64517, 0.888434, 1; 13:24:50.325 [planetinfo.record]: cloud_alpha = 1.000000; 13:24:50.325 [planetinfo.record]: cloud_color = 0.0756683, 0.667969, 0.47362, 1; 13:24:50.325 [planetinfo.record]: cloud_fraction = 0.450000; 13:24:50.325 [planetinfo.record]: land_color = 0.457923, 0.603516, 0.00235748, 1; 13:24:50.325 [planetinfo.record]: land_fraction = 0.280000; 13:24:50.325 [planetinfo.record]: polar_cloud_color = 0.356902, 0.800586, 0.655002, 1; 13:24:50.325 [planetinfo.record]: polar_land_color = 0.882978, 0.939648, 0.705654, 1; 13:24:50.325 [planetinfo.record]: polar_sea_color = 0.880304, 0.930078, 0.885748, 1; 13:24:50.325 [planetinfo.record]: sea_color = 0.549542, 0.699219, 0.565913, 1; 13:24:50.325 [planetinfo.record]: rotation_speed = 0.002083 13:24:50.325 [planetinfo.record]: planet zpos = 616990.000000 13:24:50.325 [planetinfo.record]: sun_radius = 154224.391632 13:24:50.326 [planetinfo.record]: sun_vector = -0.751 0.585 -0.307 13:24:50.327 [planetinfo.record]: sun_distance = 1177890 13:24:50.327 [planetinfo.record]: corona_flare = 0.094545 13:24:50.327 [planetinfo.record]: corona_hues = 0.683083 13:24:50.327 [planetinfo.record]: sun_color = 0.403215, 0.593056, 0.747464, 1 13:24:50.328 [planetinfo.record]: corona_shimmer = 0.319778 13:24:50.328 [planetinfo.record]: station_vector = 0.164 -0.955 0.248 13:24:50.328 [planetinfo.record]: station = coriolis 13:24:55.481 [PLANETINFO OVER]: Done 13:24:55.481 [PLANETINFO LOGGING]: 4 153 13:24:56.524 [planetinfo.record]: seed = 120 97 91 72 13:24:56.524 [planetinfo.record]: coordinates = 97 37 13:24:56.524 [planetinfo.record]: government = 7; 13:24:56.524 [planetinfo.record]: economy = 5; 13:24:56.524 [planetinfo.record]: techlevel = 7; 13:24:56.524 [planetinfo.record]: population = 41; 13:24:56.524 [planetinfo.record]: productivity = 18040; 13:24:56.524 [planetinfo.record]: name = "Usaorer"; 13:24:56.524 [planetinfo.record]: inhabitant = "Human Colonial"; 13:24:56.524 [planetinfo.record]: inhabitants = "Human Colonials"; 13:24:56.524 [planetinfo.record]: description = "This planet is reasonably famous for its inhabitants’ exceptional loathing of sit coms."; 13:24:56.560 [planetinfo.record]: air_color = 0.75616, 0.650794, 0.836402, 1; 13:24:56.561 [planetinfo.record]: cloud_alpha = 1.000000; 13:24:56.561 [planetinfo.record]: cloud_color = 0.511719, 0.477737, 0.487295, 1; 13:24:56.561 [planetinfo.record]: cloud_fraction = 0.330000; 13:24:56.561 [planetinfo.record]: land_color = 0.453589, 0.226875, 0.66, 1; 13:24:56.561 [planetinfo.record]: land_fraction = 0.500000; 13:24:56.561 [planetinfo.record]: polar_cloud_color = 0.730273, 0.699964, 0.708489, 1; 13:24:56.561 [planetinfo.record]: polar_land_color = 0.860974, 0.780766, 0.934, 1; 13:24:56.561 [planetinfo.record]: polar_sea_color = 0.946484, 0.912808, 0.898051, 1; 13:24:56.561 [planetinfo.record]: sea_color = 0.535156, 0.458992, 0.425616, 1; 13:24:56.562 [planetinfo.record]: rotation_speed = 0.000399 13:24:56.562 [planetinfo.record]: planet zpos = 595320.000000 13:24:56.563 [planetinfo.record]: sun_radius = 104509.835815 13:24:56.563 [planetinfo.record]: sun_vector = 0.734 -0.644 0.219 13:24:56.563 [planetinfo.record]: sun_distance = 843370 13:24:56.563 [planetinfo.record]: corona_flare = 0.007445 13:24:56.563 [planetinfo.record]: corona_hues = 0.661636 13:24:56.563 [planetinfo.record]: sun_color = 0.713196, 0.678742, 0.305616, 1 13:24:56.563 [planetinfo.record]: corona_shimmer = 0.553168 13:24:56.564 [planetinfo.record]: station_vector = -0.719 -0.435 0.542 13:24:56.564 [planetinfo.record]: station = coriolis 13:25:01.783 [PLANETINFO OVER]: Done 13:25:01.783 [PLANETINFO LOGGING]: 4 154 13:25:02.797 [planetinfo.record]: seed = 192 144 37 217 13:25:02.797 [planetinfo.record]: coordinates = 144 121 13:25:02.797 [planetinfo.record]: government = 0; 13:25:02.797 [planetinfo.record]: economy = 3; 13:25:02.797 [planetinfo.record]: techlevel = 4; 13:25:02.797 [planetinfo.record]: population = 20; 13:25:02.797 [planetinfo.record]: productivity = 4480; 13:25:02.797 [planetinfo.record]: name = "Orgein"; 13:25:02.797 [planetinfo.record]: inhabitant = "Human Colonial"; 13:25:02.797 [planetinfo.record]: inhabitants = "Human Colonials"; 13:25:02.797 [planetinfo.record]: description = "The world Orgein is a boring world."; 13:25:02.808 [planetinfo.record]: air_color = 0.526189, 0.608753, 0.844207, 1; 13:25:02.808 [planetinfo.record]: cloud_alpha = 1.000000; 13:25:02.808 [planetinfo.record]: cloud_color = 0.250854, 0.468523, 0.535156, 1; 13:25:02.808 [planetinfo.record]: cloud_fraction = 0.380000; 13:25:02.808 [planetinfo.record]: land_color = 0.66, 0.368672, 0.457436, 1; 13:25:02.808 [planetinfo.record]: land_fraction = 0.330000; 13:25:02.808 [planetinfo.record]: polar_cloud_color = 0.494845, 0.68317, 0.74082, 1; 13:25:02.808 [planetinfo.record]: polar_land_color = 0.934, 0.830932, 0.862335, 1; 13:25:02.809 [planetinfo.record]: polar_sea_color = 0.925781, 0.834102, 0.819099, 1; 13:25:02.809 [planetinfo.record]: sea_color = 0.742188, 0.448194, 0.400085, 1; 13:25:02.809 [planetinfo.record]: rotation_speed = 0.001956 13:25:02.809 [planetinfo.record]: planet zpos = 579040.000000 13:25:02.809 [planetinfo.record]: sun_radius = 135020.925293 13:25:02.810 [planetinfo.record]: sun_vector = 0.083 0.505 0.859 13:25:02.810 [planetinfo.record]: sun_distance = 1052800 13:25:02.810 [planetinfo.record]: corona_flare = 0.079227 13:25:02.811 [planetinfo.record]: corona_hues = 0.780579 13:25:02.811 [planetinfo.record]: sun_color = 0.818585, 0.634192, 0.364764, 1 13:25:02.811 [planetinfo.record]: corona_shimmer = 0.388619 13:25:02.811 [planetinfo.record]: station_vector = 0.258 0.894 0.367 13:25:02.811 [planetinfo.record]: station = coriolis 13:25:09.038 [PLANETINFO OVER]: Done 13:25:09.041 [PLANETINFO LOGGING]: 4 155 13:25:10.060 [planetinfo.record]: seed = 12 9 243 56 13:25:10.060 [planetinfo.record]: coordinates = 9 76 13:25:10.060 [planetinfo.record]: government = 1; 13:25:10.060 [planetinfo.record]: economy = 6; 13:25:10.060 [planetinfo.record]: techlevel = 3; 13:25:10.060 [planetinfo.record]: population = 20; 13:25:10.060 [planetinfo.record]: productivity = 3200; 13:25:10.060 [planetinfo.record]: name = "Edreered"; 13:25:10.060 [planetinfo.record]: inhabitant = "Red Furry Humanoid"; 13:25:10.060 [planetinfo.record]: inhabitants = "Red Furry Humanoids"; 13:25:10.060 [planetinfo.record]: description = "The planet Edreered is most noted for its inhabitants’ exceptional loathing of casinos and its inhabitants’ ancient mating traditions."; 13:25:10.088 [planetinfo.record]: air_color = 0.656809, 0.890911, 0.898189, 1; 13:25:10.088 [planetinfo.record]: cloud_alpha = 1.000000; 13:25:10.088 [planetinfo.record]: cloud_color = 0.682604, 0.697266, 0.55291, 1; 13:25:10.088 [planetinfo.record]: cloud_fraction = 0.240000; 13:25:10.088 [planetinfo.record]: land_color = 0.635649, 0.66, 0.559453, 1; 13:25:10.088 [planetinfo.record]: land_fraction = 0.420000; 13:25:10.088 [planetinfo.record]: polar_cloud_color = 0.803075, 0.81377, 0.708472, 1; 13:25:10.088 [planetinfo.record]: polar_land_color = 0.925385, 0.934, 0.898428, 1; 13:25:10.088 [planetinfo.record]: polar_sea_color = 0.725715, 0.910919, 0.930078, 1; 13:25:10.088 [planetinfo.record]: sea_color = 0.084671, 0.641605, 0.699219, 1; 13:25:10.088 [planetinfo.record]: rotation_speed = 0.003972 13:25:10.088 [planetinfo.record]: planet zpos = 633490.000000 13:25:10.088 [planetinfo.record]: sun_radius = 95952.058716 13:25:10.088 [planetinfo.record]: sun_vector = 0.473 0.764 -0.438 13:25:10.088 [planetinfo.record]: sun_distance = 877140 13:25:10.088 [planetinfo.record]: corona_flare = 0.049223 13:25:10.088 [planetinfo.record]: corona_hues = 0.869965 13:25:10.088 [planetinfo.record]: sun_color = 0.665781, 0.649507, 0.601306, 1 13:25:10.088 [planetinfo.record]: corona_shimmer = 0.239714 13:25:10.089 [planetinfo.record]: station_vector = 0.996 0.090 0.030 13:25:10.089 [planetinfo.record]: station = coriolis 13:25:14.822 [PLANETINFO OVER]: Done 13:25:14.823 [PLANETINFO LOGGING]: 4 156 13:25:15.827 [planetinfo.record]: seed = 188 152 133 248 13:25:15.828 [planetinfo.record]: coordinates = 152 208 13:25:15.828 [planetinfo.record]: government = 7; 13:25:15.828 [planetinfo.record]: economy = 0; 13:25:15.828 [planetinfo.record]: techlevel = 11; 13:25:15.828 [planetinfo.record]: population = 52; 13:25:15.828 [planetinfo.record]: productivity = 45760; 13:25:15.828 [planetinfo.record]: name = "Edxebere"; 13:25:15.828 [planetinfo.record]: inhabitant = "Slimy Rodent"; 13:25:15.829 [planetinfo.record]: inhabitants = "Slimy Rodents"; 13:25:15.829 [planetinfo.record]: description = "The world Edxebere is reasonably noted for its fabulous goat burgers."; 13:25:15.896 [planetinfo.record]: air_color = 0.71252, 0.57508, 0.891035, 1; 13:25:15.896 [planetinfo.record]: cloud_alpha = 1.000000; 13:25:15.896 [planetinfo.record]: cloud_color = 0.675781, 0.361649, 0.575161, 1; 13:25:15.903 [planetinfo.record]: cloud_fraction = 0.500000; 13:25:15.903 [planetinfo.record]: land_color = 0.66, 0.630996, 0.195938, 1; 13:25:15.904 [planetinfo.record]: land_fraction = 0.240000; 13:25:15.904 [planetinfo.record]: polar_cloud_color = 0.804102, 0.570488, 0.729272, 1; 13:25:15.904 [planetinfo.record]: polar_land_color = 0.934, 0.923739, 0.76982, 1; 13:25:15.904 [planetinfo.record]: polar_sea_color = 0.924775, 0.933203, 0.876427, 1; 13:25:15.904 [planetinfo.record]: sea_color = 0.643839, 0.667969, 0.505412, 1; 13:25:15.904 [planetinfo.record]: rotation_speed = 0.003066 13:25:15.904 [planetinfo.record]: planet zpos = 652080.000000 13:25:15.904 [planetinfo.record]: sun_radius = 109230.563965 13:25:15.904 [planetinfo.record]: sun_vector = -0.607 -0.393 0.691 13:25:15.904 [planetinfo.record]: sun_distance = 1203840 13:25:15.904 [planetinfo.record]: corona_flare = 0.044051 13:25:15.904 [planetinfo.record]: corona_hues = 0.734604 13:25:15.904 [planetinfo.record]: sun_color = 0.84624, 0.839318, 0.4448, 1 13:25:15.904 [planetinfo.record]: corona_shimmer = 0.347737 13:25:15.904 [planetinfo.record]: station_vector = -0.660 0.741 0.120 13:25:15.905 [planetinfo.record]: station = icosahedron 13:25:20.523 [PLANETINFO OVER]: Done 13:25:20.524 [PLANETINFO LOGGING]: 4 157 13:25:21.539 [planetinfo.record]: seed = 16 78 155 163 13:25:21.539 [planetinfo.record]: coordinates = 78 243 13:25:21.539 [planetinfo.record]: government = 2; 13:25:21.540 [planetinfo.record]: economy = 3; 13:25:21.540 [planetinfo.record]: techlevel = 7; 13:25:21.540 [planetinfo.record]: population = 34; 13:25:21.540 [planetinfo.record]: productivity = 11424; 13:25:21.540 [planetinfo.record]: name = "Geceveon"; 13:25:21.540 [planetinfo.record]: inhabitant = "Large Harmless Furry Rodent"; 13:25:21.540 [planetinfo.record]: inhabitants = "Large Harmless Furry Rodents"; 13:25:21.540 [planetinfo.record]: description = "This world is very well known for Geceveonian lethal water and the Geceveonian tree grub."; 13:25:21.556 [planetinfo.record]: air_color = 0.555348, 0.487597, 0.927457, 1; 13:25:21.556 [planetinfo.record]: cloud_alpha = 1.000000; 13:25:21.556 [planetinfo.record]: cloud_color = 0.446419, 0.138016, 0.785156, 1; 13:25:21.556 [planetinfo.record]: cloud_fraction = 0.330000; 13:25:21.556 [planetinfo.record]: land_color = 0.335156, 0.66, 0.606705, 1; 13:25:21.556 [planetinfo.record]: land_fraction = 0.250000; 13:25:21.556 [planetinfo.record]: polar_cloud_color = 0.623229, 0.413744, 0.85332, 1; 13:25:21.556 [planetinfo.record]: polar_land_color = 0.819074, 0.934, 0.915145, 1; 13:25:21.557 [planetinfo.record]: polar_sea_color = 0.936719, 0.93588, 0.883022, 1; 13:25:21.557 [planetinfo.record]: sea_color = 0.632812, 0.630545, 0.487711, 1; 13:25:21.557 [planetinfo.record]: rotation_speed = 0.003875 13:25:21.557 [planetinfo.record]: planet zpos = 512680.000000 13:25:21.557 [planetinfo.record]: sun_radius = 99135.395813 13:25:21.557 [planetinfo.record]: sun_vector = -0.483 -0.860 -0.167 13:25:21.557 [planetinfo.record]: sun_distance = 732400 13:25:21.557 [planetinfo.record]: corona_flare = 0.073611 13:25:21.557 [planetinfo.record]: corona_hues = 0.500389 13:25:21.557 [planetinfo.record]: sun_color = 0.714153, 0.715668, 0.605592, 1 13:25:21.558 [planetinfo.record]: corona_shimmer = 0.254361 13:25:21.558 [planetinfo.record]: station_vector = 0.153 0.076 0.985 13:25:21.567 [planetinfo.record]: station = coriolis 13:25:27.108 [PLANETINFO OVER]: Done 13:25:27.109 [PLANETINFO LOGGING]: 4 158 13:25:28.121 [planetinfo.record]: seed = 104 95 53 27 13:25:28.121 [planetinfo.record]: coordinates = 95 214 13:25:28.121 [planetinfo.record]: government = 5; 13:25:28.121 [planetinfo.record]: economy = 6; 13:25:28.121 [planetinfo.record]: techlevel = 7; 13:25:28.121 [planetinfo.record]: population = 40; 13:25:28.121 [planetinfo.record]: productivity = 11520; 13:25:28.121 [planetinfo.record]: name = "Anatma"; 13:25:28.121 [planetinfo.record]: inhabitant = "Human Colonial"; 13:25:28.121 [planetinfo.record]: inhabitants = "Human Colonials"; 13:25:28.121 [planetinfo.record]: description = "The world Anatma is a boring planet."; 13:25:28.123 [planetinfo.record]: air_color = 0.656146, 0.791403, 0.969732, 1; 13:25:28.124 [planetinfo.record]: cloud_alpha = 1.000000; 13:25:28.124 [planetinfo.record]: cloud_color = 0.573631, 0.912109, 0.816912, 1; 13:25:28.124 [planetinfo.record]: cloud_fraction = 0.100000; 13:25:28.124 [planetinfo.record]: land_color = 0.556641, 0.552835, 0.547943, 1; 13:25:28.124 [planetinfo.record]: land_fraction = 0.510000; 13:25:28.124 [planetinfo.record]: polar_cloud_color = 0.699285, 0.910449, 0.851059, 1; 13:25:28.124 [planetinfo.record]: polar_land_color = 0.944336, 0.942722, 0.940647, 1; 13:25:28.124 [planetinfo.record]: polar_sea_color = 0.930859, 0.919042, 0.872681, 1; 13:25:28.124 [planetinfo.record]: sea_color = 0.691406, 0.656296, 0.518555, 1; 13:25:28.124 [planetinfo.record]: rotation_speed = 0.000300 13:25:28.124 [planetinfo.record]: planet zpos = 629970.000000 13:25:28.124 [planetinfo.record]: sun_radius = 161987.691650 13:25:28.124 [planetinfo.record]: sun_vector = -0.418 0.849 -0.322 13:25:28.124 [planetinfo.record]: sun_distance = 1145400 13:25:28.124 [planetinfo.record]: corona_flare = 0.061737 13:25:28.124 [planetinfo.record]: corona_hues = 0.896530 13:25:28.124 [planetinfo.record]: sun_color = 0.623795, 0.647983, 0.725677, 1 13:25:28.124 [planetinfo.record]: corona_shimmer = 0.385370 13:25:28.124 [planetinfo.record]: station_vector = -0.401 0.886 0.232 13:25:28.124 [planetinfo.record]: station = coriolis 13:25:33.399 [PLANETINFO OVER]: Done 13:25:33.400 [PLANETINFO LOGGING]: 4 159 13:25:34.404 [planetinfo.record]: seed = 132 56 211 85 13:25:34.404 [planetinfo.record]: coordinates = 56 203 13:25:34.404 [planetinfo.record]: government = 0; 13:25:34.404 [planetinfo.record]: economy = 3; 13:25:34.404 [planetinfo.record]: techlevel = 4; 13:25:34.404 [planetinfo.record]: population = 20; 13:25:34.404 [planetinfo.record]: productivity = 4480; 13:25:34.404 [planetinfo.record]: name = "Laquused"; 13:25:34.404 [planetinfo.record]: inhabitant = "Yellow Bony Bird"; 13:25:34.404 [planetinfo.record]: inhabitants = "Yellow Bony Birds"; 13:25:34.404 [planetinfo.record]: description = "The world Laquused is mildly fabled for its inhabitants’ eccentric love for poetry but ravaged by occasional solar activity."; 13:25:34.410 [planetinfo.record]: air_color = 0.682178, 0.761142, 0.969082, 1; 13:25:34.410 [planetinfo.record]: cloud_alpha = 1.000000; 13:25:34.410 [planetinfo.record]: cloud_color = 0.647064, 0.860826, 0.910156, 1; 13:25:34.410 [planetinfo.record]: cloud_fraction = 0.530000; 13:25:34.410 [planetinfo.record]: land_color = 0.511395, 0.0464063, 0.66, 1; 13:25:34.410 [planetinfo.record]: land_fraction = 0.690000; 13:25:34.410 [planetinfo.record]: polar_cloud_color = 0.745244, 0.878759, 0.90957, 1; 13:25:34.410 [planetinfo.record]: polar_land_color = 0.881425, 0.716918, 0.934, 1; 13:25:34.411 [planetinfo.record]: polar_sea_color = 0.945508, 0.917376, 0.896847, 1; 13:25:34.411 [planetinfo.record]: sea_color = 0.544922, 0.480069, 0.432745, 1; 13:25:34.411 [planetinfo.record]: rotation_speed = 0.004930 13:25:34.411 [planetinfo.record]: planet zpos = 622800.000000 13:25:34.411 [planetinfo.record]: sun_radius = 112093.736572 13:25:34.411 [planetinfo.record]: sun_vector = -0.673 0.100 -0.733 13:25:34.411 [planetinfo.record]: sun_distance = 830400 13:25:34.411 [planetinfo.record]: corona_flare = 0.061813 13:25:34.411 [planetinfo.record]: corona_hues = 0.569214 13:25:34.411 [planetinfo.record]: sun_color = 0.776685, 0.77454, 0.771298, 1 13:25:34.411 [planetinfo.record]: corona_shimmer = 0.360230 13:25:34.411 [planetinfo.record]: station_vector = 0.225 -0.013 0.974 13:25:34.411 [planetinfo.record]: station = coriolis 13:25:39.741 [PLANETINFO OVER]: Done 13:25:39.743 [PLANETINFO LOGGING]: 4 160 13:25:40.747 [planetinfo.record]: seed = 196 152 181 219 13:25:40.747 [planetinfo.record]: coordinates = 152 232 13:25:40.747 [planetinfo.record]: government = 0; 13:25:40.747 [planetinfo.record]: economy = 2; 13:25:40.747 [planetinfo.record]: techlevel = 5; 13:25:40.747 [planetinfo.record]: population = 23; 13:25:40.747 [planetinfo.record]: productivity = 5888; 13:25:40.747 [planetinfo.record]: name = "Anisat"; 13:25:40.747 [planetinfo.record]: inhabitant = "Slimy Lobster"; 13:25:40.747 [planetinfo.record]: inhabitants = "Slimy Lobsters"; 13:25:40.747 [planetinfo.record]: description = "The world Anisat is most well known for its vast dense forests."; 13:25:40.776 [planetinfo.record]: air_color = 0.461794, 0.53966, 0.909896, 1; 13:25:40.776 [planetinfo.record]: cloud_alpha = 1.000000; 13:25:40.776 [planetinfo.record]: cloud_color = 0.0886917, 0.415586, 0.732422, 1; 13:25:40.776 [planetinfo.record]: cloud_fraction = 0.300000; 13:25:40.776 [planetinfo.record]: land_color = 0.499794, 0.66, 0.448594, 1; 13:25:40.776 [planetinfo.record]: land_fraction = 0.440000; 13:25:40.776 [planetinfo.record]: polar_cloud_color = 0.373883, 0.605296, 0.82959, 1; 13:25:40.776 [planetinfo.record]: polar_land_color = 0.877321, 0.934, 0.859207, 1; 13:25:40.776 [planetinfo.record]: polar_sea_color = 0.936523, 0.896552, 0.874881, 1; 13:25:40.776 [planetinfo.record]: sea_color = 0.634766, 0.526398, 0.467644, 1; 13:25:40.776 [planetinfo.record]: rotation_speed = 0.003990 13:25:40.776 [planetinfo.record]: planet zpos = 694080.000000 13:25:40.776 [planetinfo.record]: sun_radius = 110907.070312 13:25:40.776 [planetinfo.record]: sun_vector = -0.088 -0.664 -0.743 13:25:40.776 [planetinfo.record]: sun_distance = 1041120 13:25:40.776 [planetinfo.record]: corona_flare = 0.075694 13:25:40.776 [planetinfo.record]: corona_hues = 0.657852 13:25:40.776 [planetinfo.record]: sun_color = 0.442275, 0.671656, 0.730099, 1 13:25:40.776 [planetinfo.record]: corona_shimmer = 0.398861 13:25:40.776 [planetinfo.record]: station_vector = 0.571 -0.754 0.325 13:25:40.776 [planetinfo.record]: station = coriolis 13:25:45.446 [PLANETINFO OVER]: Done 13:25:45.448 [PLANETINFO LOGGING]: 4 161 13:25:46.460 [planetinfo.record]: seed = 104 10 27 57 13:25:46.461 [planetinfo.record]: coordinates = 10 209 13:25:46.461 [planetinfo.record]: government = 5; 13:25:46.461 [planetinfo.record]: economy = 1; 13:25:46.461 [planetinfo.record]: techlevel = 11; 13:25:46.461 [planetinfo.record]: population = 51; 13:25:46.461 [planetinfo.record]: productivity = 33048; 13:25:46.461 [planetinfo.record]: name = "Orlaed"; 13:25:46.461 [planetinfo.record]: inhabitant = "Human Colonial"; 13:25:46.461 [planetinfo.record]: inhabitants = "Human Colonials"; 13:25:46.461 [planetinfo.record]: description = "The planet Orlaed is most noted for its inhabitants’ exceptional loathing of food blenders and its exciting Orlaedian evil juice."; 13:25:46.476 [planetinfo.record]: air_color = 0.682804, 0.830936, 0.922254, 1; 13:25:46.476 [planetinfo.record]: cloud_alpha = 1.000000; 13:25:46.476 [planetinfo.record]: cloud_color = 0.631256, 0.769531, 0.652862, 1; 13:25:46.476 [planetinfo.record]: cloud_fraction = 0.410000; 13:25:46.476 [planetinfo.record]: land_color = 0.18492, 0.0696094, 0.66, 1; 13:25:46.476 [planetinfo.record]: land_fraction = 0.430000; 13:25:46.476 [planetinfo.record]: polar_cloud_color = 0.751247, 0.846289, 0.766097, 1; 13:25:46.476 [planetinfo.record]: polar_land_color = 0.765922, 0.725127, 0.934, 1; 13:25:46.476 [planetinfo.record]: polar_sea_color = 0.94043, 0.893047, 0.882112, 1; 13:25:46.476 [planetinfo.record]: sea_color = 0.595703, 0.475646, 0.447941, 1; 13:25:46.476 [planetinfo.record]: rotation_speed = 0.002253 13:25:46.476 [planetinfo.record]: planet zpos = 461700.000000 13:25:46.477 [planetinfo.record]: sun_radius = 140293.006897 13:25:46.477 [planetinfo.record]: sun_vector = -0.145 0.184 -0.972 13:25:46.477 [planetinfo.record]: sun_distance = 923400 13:25:46.477 [planetinfo.record]: corona_flare = 0.042654 13:25:46.477 [planetinfo.record]: corona_hues = 0.859520 13:25:46.477 [planetinfo.record]: sun_color = 0.278487, 0.546211, 0.653851, 1 13:25:46.477 [planetinfo.record]: corona_shimmer = 0.387127 13:25:46.477 [planetinfo.record]: station_vector = 0.304 -0.924 0.231 13:25:46.477 [planetinfo.record]: station = dodecahedron 13:25:51.562 [PLANETINFO OVER]: Done 13:25:51.563 [PLANETINFO LOGGING]: 4 162 13:25:52.566 [planetinfo.record]: seed = 208 166 133 20 13:25:52.566 [planetinfo.record]: coordinates = 166 88 13:25:52.566 [planetinfo.record]: government = 2; 13:25:52.566 [planetinfo.record]: economy = 0; 13:25:52.566 [planetinfo.record]: techlevel = 10; 13:25:52.566 [planetinfo.record]: population = 43; 13:25:52.566 [planetinfo.record]: productivity = 20640; 13:25:52.566 [planetinfo.record]: name = "Rabea"; 13:25:52.566 [planetinfo.record]: inhabitant = "Green Feline"; 13:25:52.566 [planetinfo.record]: inhabitants = "Green Felines"; 13:25:52.566 [planetinfo.record]: description = "This planet is very well known for Rabeaian vicious water and the Rabeaian spotted cat."; 13:25:52.577 [planetinfo.record]: air_color = 0.720118, 0.56389, 0.904693, 1; 13:25:52.578 [planetinfo.record]: cloud_alpha = 1.000000; 13:25:52.578 [planetinfo.record]: cloud_color = 0.716797, 0.335999, 0.568048, 1; 13:25:52.578 [planetinfo.record]: cloud_fraction = 0.330000; 13:25:52.578 [planetinfo.record]: land_color = 0.388633, 0.589844, 0.029953, 1; 13:25:52.578 [planetinfo.record]: land_fraction = 0.670000; 13:25:52.578 [planetinfo.record]: polar_cloud_color = 0.822559, 0.549443, 0.715873, 1; 13:25:52.578 [planetinfo.record]: polar_land_color = 0.860765, 0.941016, 0.717708, 1; 13:25:52.578 [planetinfo.record]: polar_sea_color = 0.927344, 0.912444, 0.853989, 1; 13:25:52.578 [planetinfo.record]: sea_color = 0.726562, 0.679866, 0.496674, 1; 13:25:52.578 [planetinfo.record]: rotation_speed = 0.003745 13:25:52.578 [planetinfo.record]: planet zpos = 600900.000000 13:25:52.578 [planetinfo.record]: sun_radius = 85438.023682 13:25:52.578 [planetinfo.record]: sun_vector = -0.170 -0.485 0.858 13:25:52.578 [planetinfo.record]: sun_distance = 881320 13:25:52.578 [planetinfo.record]: corona_flare = 0.022562 13:25:52.578 [planetinfo.record]: corona_hues = 0.553017 13:25:52.578 [planetinfo.record]: sun_color = 0.739435, 0.541432, 0.51039, 1 13:25:52.578 [planetinfo.record]: corona_shimmer = 0.520681 13:25:52.579 [planetinfo.record]: station_vector = -0.971 -0.185 0.153 13:25:52.579 [planetinfo.record]: station = coriolis 13:25:57.717 [PLANETINFO OVER]: Done 13:25:57.718 [PLANETINFO LOGGING]: 4 163 13:25:58.721 [planetinfo.record]: seed = 188 247 243 218 13:25:58.722 [planetinfo.record]: coordinates = 247 207 13:25:58.722 [planetinfo.record]: government = 7; 13:25:58.722 [planetinfo.record]: economy = 7; 13:25:58.722 [planetinfo.record]: techlevel = 7; 13:25:58.722 [planetinfo.record]: population = 43; 13:25:58.722 [planetinfo.record]: productivity = 11352; 13:25:58.722 [planetinfo.record]: name = "Quleraat"; 13:25:58.722 [planetinfo.record]: inhabitant = "Slimy Lizard"; 13:25:58.722 [planetinfo.record]: inhabitants = "Slimy Lizards"; 13:25:58.722 [planetinfo.record]: description = "This world is reasonably well known for its great volcanoes but plagued by evil tree leopards."; 13:25:58.748 [planetinfo.record]: air_color = 0.443504, 0.68322, 0.880629, 1; 13:25:58.748 [planetinfo.record]: cloud_alpha = 1.000000; 13:25:58.748 [planetinfo.record]: cloud_color = 0.0704956, 0.644531, 0.281274, 1; 13:25:58.748 [planetinfo.record]: cloud_fraction = 0.190000; 13:25:58.748 [planetinfo.record]: land_color = 0.608438, 0.627371, 0.66, 1; 13:25:58.748 [planetinfo.record]: land_fraction = 0.420000; 13:25:58.748 [planetinfo.record]: polar_cloud_color = 0.350271, 0.790039, 0.511748, 1; 13:25:58.748 [planetinfo.record]: polar_land_color = 0.915758, 0.922456, 0.934, 1; 13:25:58.748 [planetinfo.record]: polar_sea_color = 0.93153, 0.935742, 0.881827, 1; 13:25:58.748 [planetinfo.record]: sea_color = 0.631008, 0.642578, 0.494484, 1; 13:25:58.748 [planetinfo.record]: rotation_speed = 0.000958 13:25:58.748 [planetinfo.record]: planet zpos = 843450.000000 13:25:58.748 [planetinfo.record]: sun_radius = 141365.219574 13:25:58.748 [planetinfo.record]: sun_vector = -0.452 -0.506 -0.735 13:25:58.748 [planetinfo.record]: sun_distance = 1124600 13:25:58.748 [planetinfo.record]: corona_flare = 0.080276 13:25:58.748 [planetinfo.record]: corona_hues = 0.847504 13:25:58.748 [planetinfo.record]: sun_color = 0.588166, 0.686195, 0.819205, 1 13:25:58.748 [planetinfo.record]: corona_shimmer = 0.262697 13:25:58.749 [planetinfo.record]: station_vector = -0.129 -0.867 0.481 13:25:58.749 [planetinfo.record]: station = coriolis 13:26:03.213 [PLANETINFO OVER]: Done 13:26:03.214 [PLANETINFO LOGGING]: 4 164 13:26:04.218 [planetinfo.record]: seed = 140 241 37 8 13:26:04.218 [planetinfo.record]: coordinates = 241 116 13:26:04.218 [planetinfo.record]: government = 1; 13:26:04.219 [planetinfo.record]: economy = 6; 13:26:04.219 [planetinfo.record]: techlevel = 3; 13:26:04.219 [planetinfo.record]: population = 20; 13:26:04.219 [planetinfo.record]: productivity = 3200; 13:26:04.219 [planetinfo.record]: name = "Usreus"; 13:26:04.219 [planetinfo.record]: inhabitant = "Human Colonial"; 13:26:04.219 [planetinfo.record]: inhabitants = "Human Colonials"; 13:26:04.219 [planetinfo.record]: description = "This world is a revolting dump."; 13:26:04.237 [planetinfo.record]: air_color = 0.609384, 0.803497, 0.996398, 1; 13:26:04.237 [planetinfo.record]: cloud_alpha = 1.000000; 13:26:04.237 [planetinfo.record]: cloud_color = 0.430206, 0.992188, 0.715587, 1; 13:26:04.237 [planetinfo.record]: cloud_fraction = 0.190000; 13:26:04.237 [planetinfo.record]: land_color = 0.378984, 0.66, 0.594137, 1; 13:26:04.237 [planetinfo.record]: land_fraction = 0.330000; 13:26:04.237 [planetinfo.record]: polar_cloud_color = 0.611425, 0.946484, 0.781572, 1; 13:26:04.237 [planetinfo.record]: polar_land_color = 0.83458, 0.934, 0.910698, 1; 13:26:04.237 [planetinfo.record]: polar_sea_color = 0.895647, 0.93457, 0.887568, 1; 13:26:04.237 [planetinfo.record]: sea_color = 0.545294, 0.654297, 0.522671, 1; 13:26:04.237 [planetinfo.record]: rotation_speed = 0.004844 13:26:04.237 [planetinfo.record]: planet zpos = 510500.000000 13:26:04.237 [planetinfo.record]: sun_radius = 152409.207916 13:26:04.237 [planetinfo.record]: sun_vector = 0.631 -0.163 -0.759 13:26:04.237 [planetinfo.record]: sun_distance = 969950 13:26:04.237 [planetinfo.record]: corona_flare = 0.088493 13:26:04.237 [planetinfo.record]: corona_hues = 0.874496 13:26:04.237 [planetinfo.record]: sun_color = 0.758273, 0.669606, 0.597822, 1 13:26:04.239 [planetinfo.record]: corona_shimmer = 0.280445 13:26:04.239 [planetinfo.record]: station_vector = -0.666 -0.667 0.334 13:26:04.239 [planetinfo.record]: station = coriolis 13:26:09.144 [PLANETINFO OVER]: Done 13:26:09.146 [PLANETINFO LOGGING]: 4 165 13:26:10.165 [planetinfo.record]: seed = 128 222 219 180 13:26:10.165 [planetinfo.record]: coordinates = 222 104 13:26:10.165 [planetinfo.record]: government = 0; 13:26:10.165 [planetinfo.record]: economy = 2; 13:26:10.165 [planetinfo.record]: techlevel = 7; 13:26:10.165 [planetinfo.record]: population = 31; 13:26:10.165 [planetinfo.record]: productivity = 7936; 13:26:10.165 [planetinfo.record]: name = "Raanre"; 13:26:10.165 [planetinfo.record]: inhabitant = "Harmless Feline"; 13:26:10.165 [planetinfo.record]: inhabitants = "Harmless Felines"; 13:26:10.165 [planetinfo.record]: description = "The world Raanre is beset by evil disease."; 13:26:10.180 [planetinfo.record]: air_color = 0.741313, 0.47418, 0.979488, 1; 13:26:10.181 [planetinfo.record]: cloud_alpha = 1.000000; 13:26:10.181 [planetinfo.record]: cloud_color = 0.941406, 0.0478058, 0.40385, 1; 13:26:10.181 [planetinfo.record]: cloud_fraction = 0.230000; 13:26:10.181 [planetinfo.record]: land_color = 0.570312, 0.494168, 0.49234, 1; 13:26:10.181 [planetinfo.record]: land_fraction = 0.260000; 13:26:10.181 [planetinfo.record]: polar_cloud_color = 0.923633, 0.375677, 0.594003, 1; 13:26:10.181 [planetinfo.record]: polar_land_color = 0.942969, 0.911494, 0.910738, 1; 13:26:10.181 [planetinfo.record]: polar_sea_color = 0.85077, 0.914844, 0.818356, 1; 13:26:10.181 [planetinfo.record]: sea_color = 0.612996, 0.851562, 0.49231, 1; 13:26:10.181 [planetinfo.record]: rotation_speed = 0.000780 13:26:10.181 [planetinfo.record]: planet zpos = 487440.000000 13:26:10.181 [planetinfo.record]: sun_radius = 108329.503784 13:26:10.182 [planetinfo.record]: sun_vector = -0.928 0.332 -0.172 13:26:10.182 [planetinfo.record]: sun_distance = 771780 13:26:10.182 [planetinfo.record]: corona_flare = 0.030072 13:26:10.182 [planetinfo.record]: corona_hues = 0.554688 13:26:10.182 [planetinfo.record]: sun_color = 0.753149, 0.665948, 0.3807, 1 13:26:10.182 [planetinfo.record]: corona_shimmer = 0.302316 13:26:10.182 [planetinfo.record]: station_vector = 0.684 -0.718 0.128 13:26:10.182 [planetinfo.record]: station = coriolis 13:26:15.536 [PLANETINFO OVER]: Done 13:26:15.537 [PLANETINFO LOGGING]: 4 166 13:26:16.557 [planetinfo.record]: seed = 248 62 21 201 13:26:16.557 [planetinfo.record]: coordinates = 62 142 13:26:16.557 [planetinfo.record]: government = 7; 13:26:16.557 [planetinfo.record]: economy = 6; 13:26:16.557 [planetinfo.record]: techlevel = 7; 13:26:16.557 [planetinfo.record]: population = 42; 13:26:16.557 [planetinfo.record]: productivity = 14784; 13:26:16.557 [planetinfo.record]: name = "Esveri"; 13:26:16.557 [planetinfo.record]: inhabitant = "Human Colonial"; 13:26:16.557 [planetinfo.record]: inhabitants = "Human Colonials"; 13:26:16.557 [planetinfo.record]: description = "Esveri is mildly well known for its hoopy night life and its exotic cuisine."; 13:26:16.559 [planetinfo.record]: air_color = 0.796655, 0.518429, 0.959977, 1; 13:26:16.559 [planetinfo.record]: cloud_alpha = 1.000000; 13:26:16.559 [planetinfo.record]: cloud_color = 0.882812, 0.193115, 0.257774, 1; 13:26:16.559 [planetinfo.record]: cloud_fraction = 0.430000; 13:26:16.559 [planetinfo.record]: land_color = 0.599609, 0.589069, 0.557449, 1; 13:26:16.559 [planetinfo.record]: land_fraction = 0.320000; 13:26:16.559 [planetinfo.record]: polar_cloud_color = 0.897266, 0.459148, 0.500221, 1; 13:26:16.559 [planetinfo.record]: polar_land_color = 0.940039, 0.935908, 0.923515, 1; 13:26:16.559 [planetinfo.record]: polar_sea_color = 0.847045, 0.913086, 0.806084, 1; 13:26:16.559 [planetinfo.record]: sea_color = 0.617692, 0.869141, 0.461731, 1; 13:26:16.559 [planetinfo.record]: rotation_speed = 0.002249 13:26:16.559 [planetinfo.record]: planet zpos = 466380.000000 13:26:16.559 [planetinfo.record]: sun_radius = 85983.435669 13:26:16.560 [planetinfo.record]: sun_vector = 0.323 -0.849 -0.418 13:26:16.560 [planetinfo.record]: sun_distance = 984580 13:26:16.560 [planetinfo.record]: corona_flare = 0.076434 13:26:16.560 [planetinfo.record]: corona_hues = 0.791389 13:26:16.560 [planetinfo.record]: sun_color = 0.796921, 0.441409, 0.205249, 1 13:26:16.560 [planetinfo.record]: corona_shimmer = 0.337064 13:26:16.560 [planetinfo.record]: station_vector = -0.166 0.623 0.765 13:26:16.560 [planetinfo.record]: station = coriolis 13:26:21.145 [PLANETINFO OVER]: Done 13:26:21.146 [PLANETINFO LOGGING]: 4 167 13:26:22.149 [planetinfo.record]: seed = 180 254 83 52 13:26:22.149 [planetinfo.record]: coordinates = 254 158 13:26:22.149 [planetinfo.record]: government = 6; 13:26:22.149 [planetinfo.record]: economy = 6; 13:26:22.149 [planetinfo.record]: techlevel = 6; 13:26:22.149 [planetinfo.record]: population = 37; 13:26:22.149 [planetinfo.record]: productivity = 11840; 13:26:22.149 [planetinfo.record]: name = "Raatzama"; 13:26:22.149 [planetinfo.record]: inhabitant = "Human Colonial"; 13:26:22.149 [planetinfo.record]: inhabitants = "Human Colonials"; 13:26:22.149 [planetinfo.record]: description = "Raatzama is mildly well known for its hoopy casinos."; 13:26:22.172 [planetinfo.record]: air_color = 0.698837, 0.843542, 0.88193, 1; 13:26:22.172 [planetinfo.record]: cloud_alpha = 1.000000; 13:26:22.172 [planetinfo.record]: cloud_color = 0.638701, 0.648438, 0.63324, 1; 13:26:22.172 [planetinfo.record]: cloud_fraction = 0.360000; 13:26:22.172 [planetinfo.record]: land_color = 0.648438, 0.591921, 0.519257, 1; 13:26:22.172 [planetinfo.record]: land_fraction = 0.260000; 13:26:22.172 [planetinfo.record]: polar_cloud_color = 0.784367, 0.791797, 0.780198, 1; 13:26:22.172 [planetinfo.record]: polar_land_color = 0.935156, 0.91478, 0.888581, 1; 13:26:22.172 [planetinfo.record]: polar_sea_color = 0.748699, 0.934961, 0.934961, 1; 13:26:22.172 [planetinfo.record]: sea_color = 0.132111, 0.650391, 0.650391, 1; 13:26:22.172 [planetinfo.record]: rotation_speed = 0.001820 13:26:22.172 [planetinfo.record]: planet zpos = 491280.000000 13:26:22.172 [planetinfo.record]: sun_radius = 78351.098938 13:26:22.173 [planetinfo.record]: sun_vector = -0.641 0.675 0.367 13:26:22.173 [planetinfo.record]: sun_distance = 818800 13:26:22.173 [planetinfo.record]: corona_flare = 0.002019 13:26:22.173 [planetinfo.record]: corona_hues = 0.925720 13:26:22.173 [planetinfo.record]: sun_color = 0.452207, 0.677317, 0.845187, 1 13:26:22.173 [planetinfo.record]: corona_shimmer = 0.485625 13:26:22.173 [planetinfo.record]: station_vector = -0.114 -0.962 0.248 13:26:22.173 [planetinfo.record]: station = coriolis 13:26:26.732 [PLANETINFO OVER]: Done 13:26:26.733 [PLANETINFO LOGGING]: 4 168 13:26:27.752 [planetinfo.record]: seed = 20 11 213 225 13:26:27.752 [planetinfo.record]: coordinates = 11 4 13:26:27.752 [planetinfo.record]: government = 2; 13:26:27.752 [planetinfo.record]: economy = 4; 13:26:27.752 [planetinfo.record]: techlevel = 7; 13:26:27.752 [planetinfo.record]: population = 35; 13:26:27.752 [planetinfo.record]: productivity = 10080; 13:26:27.753 [planetinfo.record]: name = "Leatrien"; 13:26:27.753 [planetinfo.record]: inhabitant = "Large Rodent"; 13:26:27.753 [planetinfo.record]: inhabitants = "Large Rodents"; 13:26:27.753 [planetinfo.record]: description = "This world is a revolting dump."; 13:26:27.758 [planetinfo.record]: air_color = 0.737248, 0.735951, 0.961928, 1; 13:26:27.759 [planetinfo.record]: cloud_alpha = 1.000000; 13:26:27.759 [planetinfo.record]: cloud_color = 0.797874, 0.794945, 0.888672, 1; 13:26:27.759 [planetinfo.record]: cloud_fraction = 0.100000; 13:26:27.759 [planetinfo.record]: land_color = 0.609666, 0.528516, 0.66, 1; 13:26:27.759 [planetinfo.record]: land_fraction = 0.680000; 13:26:27.759 [planetinfo.record]: polar_cloud_color = 0.842436, 0.840583, 0.899902, 1; 13:26:27.760 [planetinfo.record]: polar_land_color = 0.916193, 0.887482, 0.934, 1; 13:26:27.760 [planetinfo.record]: polar_sea_color = 0.937109, 0.882158, 0.879455, 1; 13:26:27.760 [planetinfo.record]: sea_color = 0.628906, 0.481391, 0.474136, 1; 13:26:27.760 [planetinfo.record]: rotation_speed = 0.000847 13:26:27.760 [planetinfo.record]: planet zpos = 339130.000000 13:26:27.760 [planetinfo.record]: sun_radius = 67450.033569 13:26:27.760 [planetinfo.record]: sun_vector = -0.220 -0.975 0.004 13:26:27.760 [planetinfo.record]: sun_distance = 709090 13:26:27.760 [planetinfo.record]: corona_flare = 0.057109 13:26:27.760 [planetinfo.record]: corona_hues = 0.731133 13:26:27.761 [planetinfo.record]: sun_color = 0.215055, 0.525549, 0.652124, 1 13:26:27.761 [planetinfo.record]: corona_shimmer = 1.463837 13:26:27.761 [planetinfo.record]: station_vector = -0.677 -0.733 0.071 13:26:27.761 [planetinfo.record]: station = coriolis 13:26:32.079 [PLANETINFO OVER]: Done 13:26:32.080 [PLANETINFO LOGGING]: 4 169 13:26:33.082 [planetinfo.record]: seed = 88 178 219 130 13:26:33.082 [planetinfo.record]: coordinates = 178 222 13:26:33.082 [planetinfo.record]: government = 3; 13:26:33.082 [planetinfo.record]: economy = 6; 13:26:33.082 [planetinfo.record]: techlevel = 5; 13:26:33.082 [planetinfo.record]: population = 30; 13:26:33.082 [planetinfo.record]: productivity = 6720; 13:26:33.082 [planetinfo.record]: name = "Xebees"; 13:26:33.082 [planetinfo.record]: inhabitant = "Large Black Fat Feline"; 13:26:33.082 [planetinfo.record]: inhabitants = "Large Black Fat Felines"; 13:26:33.082 [planetinfo.record]: description = "The planet Xebees is an unremarkable planet."; 13:26:33.099 [planetinfo.record]: air_color = 0.649755, 0.876076, 0.838741, 1; 13:26:33.100 [planetinfo.record]: cloud_alpha = 1.000000; 13:26:33.100 [planetinfo.record]: cloud_color = 0.630859, 0.577145, 0.519966, 1; 13:26:33.100 [planetinfo.record]: cloud_fraction = 0.530000; 13:26:33.100 [planetinfo.record]: land_color = 0.615769, 0.66, 0.567188, 1; 13:26:33.100 [planetinfo.record]: land_fraction = 0.290000; 13:26:33.100 [planetinfo.record]: polar_cloud_color = 0.783887, 0.742172, 0.697766, 1; 13:26:33.100 [planetinfo.record]: polar_land_color = 0.918352, 0.934, 0.901164, 1; 13:26:33.100 [planetinfo.record]: polar_sea_color = 0.714526, 0.88744, 0.927344, 1; 13:26:33.100 [planetinfo.record]: sea_color = 0.0596008, 0.601507, 0.726562, 1; 13:26:33.100 [planetinfo.record]: rotation_speed = 0.004188 13:26:33.100 [planetinfo.record]: planet zpos = 455780.000000 13:26:33.100 [planetinfo.record]: sun_radius = 109994.758301 13:26:33.100 [planetinfo.record]: sun_vector = -0.015 -0.924 -0.381 13:26:33.100 [planetinfo.record]: sun_distance = 771320 13:26:33.100 [planetinfo.record]: corona_flare = 0.049432 13:26:33.100 [planetinfo.record]: corona_hues = 0.846413 13:26:33.100 [planetinfo.record]: sun_color = 0.7397, 0.547457, 0.288192, 1 13:26:33.100 [planetinfo.record]: corona_shimmer = 0.402066 13:26:33.101 [planetinfo.record]: station_vector = -0.982 0.185 0.030 13:26:33.101 [planetinfo.record]: station = coriolis 13:26:37.088 [PLANETINFO OVER]: Done 13:26:37.089 [PLANETINFO LOGGING]: 4 170 13:26:38.091 [planetinfo.record]: seed = 224 223 229 60 13:26:38.091 [planetinfo.record]: coordinates = 223 73 13:26:38.091 [planetinfo.record]: government = 4; 13:26:38.091 [planetinfo.record]: economy = 1; 13:26:38.091 [planetinfo.record]: techlevel = 11; 13:26:38.091 [planetinfo.record]: population = 50; 13:26:38.091 [planetinfo.record]: productivity = 28800; 13:26:38.091 [planetinfo.record]: name = "Tecexe"; 13:26:38.091 [planetinfo.record]: inhabitant = "Red Feline"; 13:26:38.091 [planetinfo.record]: inhabitants = "Red Felines"; 13:26:38.091 [planetinfo.record]: description = "Tecexe is an unremarkable planet."; 13:26:38.104 [planetinfo.record]: air_color = 0.52406, 0.470455, 0.936563, 1; 13:26:38.104 [planetinfo.record]: cloud_alpha = 1.000000; 13:26:38.104 [planetinfo.record]: cloud_color = 0.346889, 0.0856934, 0.8125, 1; 13:26:38.104 [planetinfo.record]: cloud_fraction = 0.310000; 13:26:38.104 [planetinfo.record]: land_color = 0.346697, 0.00257813, 0.66, 1; 13:26:38.104 [planetinfo.record]: land_fraction = 0.520000; 13:26:38.104 [planetinfo.record]: polar_cloud_color = 0.555591, 0.38167, 0.865625, 1; 13:26:38.104 [planetinfo.record]: polar_land_color = 0.823157, 0.701412, 0.934, 1; 13:26:38.104 [planetinfo.record]: polar_sea_color = 0.938672, 0.906689, 0.881013, 1; 13:26:38.104 [planetinfo.record]: sea_color = 0.613281, 0.529698, 0.462596, 1; 13:26:38.104 [planetinfo.record]: rotation_speed = 0.000647 13:26:38.104 [planetinfo.record]: planet zpos = 916650.000000 13:26:38.104 [planetinfo.record]: sun_radius = 153030.495300 13:26:38.104 [planetinfo.record]: sun_vector = 0.480 -0.795 0.371 13:26:38.104 [planetinfo.record]: sun_distance = 1161090 13:26:38.104 [planetinfo.record]: corona_flare = 0.098663 13:26:38.104 [planetinfo.record]: corona_hues = 0.601280 13:26:38.104 [planetinfo.record]: sun_color = 0.782922, 0.723233, 0.710652, 1 13:26:38.104 [planetinfo.record]: corona_shimmer = 0.362378 13:26:38.104 [planetinfo.record]: station_vector = -0.038 0.716 0.697 13:26:38.104 [planetinfo.record]: station = icosahedron 13:26:43.341 [PLANETINFO OVER]: Done 13:26:43.341 [PLANETINFO LOGGING]: 4 171 13:26:44.345 [planetinfo.record]: seed = 108 37 243 13 13:26:44.345 [planetinfo.record]: coordinates = 37 130 13:26:44.345 [planetinfo.record]: government = 5; 13:26:44.345 [planetinfo.record]: economy = 2; 13:26:44.345 [planetinfo.record]: techlevel = 9; 13:26:44.346 [planetinfo.record]: population = 44; 13:26:44.346 [planetinfo.record]: productivity = 25344; 13:26:44.346 [planetinfo.record]: name = "Divees"; 13:26:44.346 [planetinfo.record]: inhabitant = "Green Rodent"; 13:26:44.346 [planetinfo.record]: inhabitants = "Green Rodents"; 13:26:44.346 [planetinfo.record]: description = "The planet Divees is most noted for its inhabitants’ exceptional love for food blenders and its weird rock formations."; 13:26:44.356 [planetinfo.record]: air_color = 0.704053, 0.732096, 0.974936, 1; 13:26:44.356 [planetinfo.record]: cloud_alpha = 1.000000; 13:26:44.356 [planetinfo.record]: cloud_color = 0.710297, 0.774848, 0.927734, 1; 13:26:44.356 [planetinfo.record]: cloud_fraction = 0.120000; 13:26:44.356 [planetinfo.record]: land_color = 0.66, 0.239766, 0.564791, 1; 13:26:44.356 [planetinfo.record]: land_fraction = 0.270000; 13:26:44.356 [planetinfo.record]: polar_cloud_color = 0.783084, 0.822983, 0.91748, 1; 13:26:44.356 [planetinfo.record]: polar_land_color = 0.934, 0.785326, 0.900316, 1; 13:26:44.356 [planetinfo.record]: polar_sea_color = 0.93125, 0.909992, 0.869409, 1; 13:26:44.356 [planetinfo.record]: sea_color = 0.6875, 0.624725, 0.504883, 1; 13:26:44.356 [planetinfo.record]: rotation_speed = 0.002707 13:26:44.356 [planetinfo.record]: planet zpos = 803530.000000 13:26:44.356 [planetinfo.record]: sun_radius = 161203.415070 13:26:44.356 [planetinfo.record]: sun_vector = 0.865 -0.040 -0.499 13:26:44.356 [planetinfo.record]: sun_distance = 1112580 13:26:44.356 [planetinfo.record]: corona_flare = 0.036020 13:26:44.357 [planetinfo.record]: corona_hues = 0.799355 13:26:44.357 [planetinfo.record]: sun_color = 0.567632, 0.590563, 0.688528, 1 13:26:44.357 [planetinfo.record]: corona_shimmer = 0.488491 13:26:44.357 [planetinfo.record]: station_vector = -0.303 -0.938 0.168 13:26:44.357 [planetinfo.record]: station = coriolis 13:26:49.481 [PLANETINFO OVER]: Done 13:26:49.482 [PLANETINFO LOGGING]: 4 172 13:26:50.486 [planetinfo.record]: seed = 92 173 197 76 13:26:50.486 [planetinfo.record]: coordinates = 173 233 13:26:50.486 [planetinfo.record]: government = 3; 13:26:50.487 [planetinfo.record]: economy = 1; 13:26:50.487 [planetinfo.record]: techlevel = 9; 13:26:50.487 [planetinfo.record]: population = 41; 13:26:50.487 [planetinfo.record]: productivity = 20664; 13:26:50.487 [planetinfo.record]: name = "Ingeisdi"; 13:26:50.487 [planetinfo.record]: inhabitant = "Yellow Fat Bird"; 13:26:50.487 [planetinfo.record]: inhabitants = "Yellow Fat Birds"; 13:26:50.487 [planetinfo.record]: description = "This world is reasonably notable for its exotic cuisine but ravaged by vicious disease."; 13:26:50.516 [planetinfo.record]: air_color = 0.756095, 0.496798, 0.96648, 1; 13:26:50.516 [planetinfo.record]: cloud_alpha = 1.000000; 13:26:50.516 [planetinfo.record]: cloud_color = 0.902344, 0.126892, 0.381337, 1; 13:26:50.516 [planetinfo.record]: cloud_fraction = 0.480000; 13:26:50.516 [planetinfo.record]: land_color = 0.528516, 0.66, 0.60453, 1; 13:26:50.516 [planetinfo.record]: land_fraction = 0.340000; 13:26:50.516 [planetinfo.record]: polar_cloud_color = 0.906055, 0.419404, 0.579086, 1; 13:26:50.516 [planetinfo.record]: polar_land_color = 0.887482, 0.934, 0.914375, 1; 13:26:50.523 [planetinfo.record]: polar_sea_color = 0.920703, 0.848766, 0.80112, 1; 13:26:50.523 [planetinfo.record]: sea_color = 0.792969, 0.545142, 0.380997, 1; 13:26:50.524 [planetinfo.record]: rotation_speed = 0.001741 13:26:50.524 [planetinfo.record]: planet zpos = 848540.000000 13:26:50.524 [planetinfo.record]: sun_radius = 161490.099335 13:26:50.524 [planetinfo.record]: sun_vector = -0.037 -0.085 -0.996 13:26:50.524 [planetinfo.record]: sun_distance = 1212200 13:26:50.524 [planetinfo.record]: corona_flare = 0.057069 13:26:50.524 [planetinfo.record]: corona_hues = 0.601921 13:26:50.524 [planetinfo.record]: sun_color = 0.460377, 0.473526, 0.684152, 1 13:26:50.524 [planetinfo.record]: corona_shimmer = 0.275656 13:26:50.524 [planetinfo.record]: station_vector = 0.948 0.315 0.043 13:26:50.524 [planetinfo.record]: station = coriolis 13:26:55.535 [PLANETINFO OVER]: Done 13:26:55.536 [PLANETINFO LOGGING]: 4 173 13:26:56.538 [planetinfo.record]: seed = 240 13 27 207 13:26:56.538 [planetinfo.record]: coordinates = 13 221 13:26:56.538 [planetinfo.record]: government = 6; 13:26:56.538 [planetinfo.record]: economy = 5; 13:26:56.538 [planetinfo.record]: techlevel = 6; 13:26:56.538 [planetinfo.record]: population = 36; 13:26:56.539 [planetinfo.record]: productivity = 14400; 13:26:56.539 [planetinfo.record]: name = "Aquti"; 13:26:56.539 [planetinfo.record]: inhabitant = "Human Colonial"; 13:26:56.539 [planetinfo.record]: inhabitants = "Human Colonials"; 13:26:56.539 [planetinfo.record]: description = "This world is a tedious little planet."; 13:26:56.548 [planetinfo.record]: air_color = 0.506545, 0.588867, 0.866971, 1; 13:26:56.548 [planetinfo.record]: cloud_alpha = 1.000000; 13:26:56.548 [planetinfo.record]: cloud_color = 0.212173, 0.475106, 0.603516, 1; 13:26:56.548 [planetinfo.record]: cloud_fraction = 0.210000; 13:26:56.548 [planetinfo.record]: land_color = 0.223721, 0.550781, 0.105423, 1; 13:26:56.548 [planetinfo.record]: land_fraction = 0.520000; 13:26:56.550 [planetinfo.record]: polar_cloud_color = 0.45888, 0.668977, 0.771582, 1; 13:26:56.550 [planetinfo.record]: polar_land_color = 0.804646, 0.944922, 0.753907, 1; 13:26:56.550 [planetinfo.record]: polar_sea_color = 0.8929, 0.86568, 0.911523, 1; 13:26:56.550 [planetinfo.record]: sea_color = 0.812457, 0.706776, 0.884766, 1; 13:26:56.550 [planetinfo.record]: rotation_speed = 0.002635 13:26:56.550 [planetinfo.record]: planet zpos = 933660.000000 13:26:56.550 [planetinfo.record]: sun_radius = 122827.398376 13:26:56.550 [planetinfo.record]: sun_vector = 0.322 -0.739 0.592 13:26:56.550 [planetinfo.record]: sun_distance = 1400490 13:26:56.550 [planetinfo.record]: corona_flare = 0.057013 13:26:56.550 [planetinfo.record]: corona_hues = 0.620232 13:26:56.550 [planetinfo.record]: sun_color = 0.262416, 0.462853, 0.655838, 1 13:26:56.550 [planetinfo.record]: corona_shimmer = 0.697355 13:26:56.551 [planetinfo.record]: station_vector = -0.343 0.938 0.053 13:26:56.551 [planetinfo.record]: station = coriolis 13:27:01.303 [PLANETINFO OVER]: Done 13:27:01.305 [PLANETINFO LOGGING]: 4 174 13:27:02.306 [planetinfo.record]: seed = 136 33 245 115 13:27:02.306 [planetinfo.record]: coordinates = 33 151 13:27:02.306 [planetinfo.record]: government = 1; 13:27:02.306 [planetinfo.record]: economy = 7; 13:27:02.306 [planetinfo.record]: techlevel = 2; 13:27:02.306 [planetinfo.record]: population = 17; 13:27:02.306 [planetinfo.record]: productivity = 2040; 13:27:02.306 [planetinfo.record]: name = "Bedixe"; 13:27:02.306 [planetinfo.record]: inhabitant = "Blue Frog"; 13:27:02.306 [planetinfo.record]: inhabitants = "Blue Frogs"; 13:27:02.306 [planetinfo.record]: description = "The world Bedixe is mildly fabled for the Bedixeian mountain slug but ravaged by vicious disease."; 13:27:02.308 [planetinfo.record]: air_color = 0.477681, 0.650213, 0.863719, 1; 13:27:02.308 [planetinfo.record]: cloud_alpha = 1.000000; 13:27:02.308 [planetinfo.record]: cloud_color = 0.153076, 0.59375, 0.449154, 1; 13:27:02.308 [planetinfo.record]: cloud_fraction = 0.350000; 13:27:02.308 [planetinfo.record]: land_color = 0.256967, 0.211406, 0.66, 1; 13:27:02.308 [planetinfo.record]: land_fraction = 0.290000; 13:27:02.308 [planetinfo.record]: polar_cloud_color = 0.411314, 0.767187, 0.650417, 1; 13:27:02.308 [planetinfo.record]: polar_land_color = 0.791412, 0.775293, 0.934, 1; 13:27:02.308 [planetinfo.record]: polar_sea_color = 0.931836, 0.929472, 0.871321, 1; 13:27:02.308 [planetinfo.record]: sea_color = 0.681641, 0.674724, 0.504574, 1; 13:27:02.308 [planetinfo.record]: rotation_speed = 0.004081 13:27:02.308 [planetinfo.record]: planet zpos = 470210.000000 13:27:02.308 [planetinfo.record]: sun_radius = 96807.843781 13:27:02.309 [planetinfo.record]: sun_vector = 0.122 0.600 0.790 13:27:02.309 [planetinfo.record]: sun_distance = 651060 13:27:02.309 [planetinfo.record]: corona_flare = 0.030661 13:27:02.309 [planetinfo.record]: corona_hues = 0.629227 13:27:02.309 [planetinfo.record]: sun_color = 0.552447, 0.636136, 0.845609, 1 13:27:02.309 [planetinfo.record]: corona_shimmer = 0.269969 13:27:02.309 [planetinfo.record]: station_vector = 0.104 0.701 0.706 13:27:02.309 [planetinfo.record]: station = coriolis 13:27:07.909 [PLANETINFO OVER]: Done 13:27:07.910 [PLANETINFO LOGGING]: 4 175 13:27:08.914 [planetinfo.record]: seed = 228 99 211 83 13:27:08.914 [planetinfo.record]: coordinates = 99 194 13:27:08.914 [planetinfo.record]: government = 4; 13:27:08.914 [planetinfo.record]: economy = 2; 13:27:08.914 [planetinfo.record]: techlevel = 10; 13:27:08.914 [planetinfo.record]: population = 47; 13:27:08.914 [planetinfo.record]: productivity = 24064; 13:27:08.914 [planetinfo.record]: name = "Bequen"; 13:27:08.914 [planetinfo.record]: inhabitant = "Yellow Bug-Eyed Bird"; 13:27:08.914 [planetinfo.record]: inhabitants = "Yellow Bug-Eyed Birds"; 13:27:08.914 [planetinfo.record]: description = "This planet is noted for Zero-G hockey."; 13:27:08.940 [planetinfo.record]: air_color = 0.501237, 0.5721, 0.875426, 1; 13:27:08.940 [planetinfo.record]: cloud_alpha = 1.000000; 13:27:08.940 [planetinfo.record]: cloud_color = 0.19899, 0.437459, 0.628906, 1; 13:27:08.940 [planetinfo.record]: cloud_fraction = 0.330000; 13:27:08.940 [planetinfo.record]: land_color = 0.66, 0.652266, 0.653172, 1; 13:27:08.940 [planetinfo.record]: land_fraction = 0.630000; 13:27:08.940 [planetinfo.record]: polar_cloud_color = 0.448471, 0.634034, 0.783008, 1; 13:27:08.940 [planetinfo.record]: polar_land_color = 0.934, 0.931264, 0.931584, 1; 13:27:08.940 [planetinfo.record]: polar_sea_color = 0.948633, 0.903761, 0.900367, 1; 13:27:08.940 [planetinfo.record]: sea_color = 0.513672, 0.416482, 0.409132, 1; 13:27:08.940 [planetinfo.record]: rotation_speed = 0.003685 13:27:08.940 [planetinfo.record]: planet zpos = 441960.000000 13:27:08.940 [planetinfo.record]: sun_radius = 121545.856171 13:27:08.940 [planetinfo.record]: sun_vector = 0.310 -0.735 -0.603 13:27:08.940 [planetinfo.record]: sun_distance = 847090 13:27:08.940 [planetinfo.record]: corona_flare = 0.058543 13:27:08.940 [planetinfo.record]: corona_hues = 0.930664 13:27:08.941 [planetinfo.record]: sun_color = 0.440895, 0.606155, 0.765173, 1 13:27:08.941 [planetinfo.record]: corona_shimmer = 0.256933 13:27:08.941 [planetinfo.record]: station_vector = -0.210 0.940 0.267 13:27:08.941 [planetinfo.record]: station = coriolis 13:27:13.437 [PLANETINFO OVER]: Done 13:27:13.437 [PLANETINFO LOGGING]: 4 176 13:27:14.439 [planetinfo.record]: seed = 100 0 245 172 13:27:14.439 [planetinfo.record]: coordinates = 0 50 13:27:14.439 [planetinfo.record]: government = 4; 13:27:14.439 [planetinfo.record]: economy = 2; 13:27:14.439 [planetinfo.record]: techlevel = 7; 13:27:14.440 [planetinfo.record]: population = 35; 13:27:14.440 [planetinfo.record]: productivity = 17920; 13:27:14.440 [planetinfo.record]: name = "Inonin"; 13:27:14.440 [planetinfo.record]: inhabitant = "Harmless Horned Lizard"; 13:27:14.440 [planetinfo.record]: inhabitants = "Harmless Horned Lizards"; 13:27:14.440 [planetinfo.record]: description = "This planet is mildly fabled for its inhabitants’ eccentric love for tourists but beset by evil spotted craboids."; 13:27:14.442 [planetinfo.record]: air_color = 0.614379, 0.841875, 0.977537, 1; 13:27:14.442 [planetinfo.record]: cloud_alpha = 1.000000; 13:27:14.442 [planetinfo.record]: cloud_color = 0.453156, 0.935547, 0.517223, 1; 13:27:14.442 [planetinfo.record]: cloud_fraction = 0.440000; 13:27:14.442 [planetinfo.record]: land_color = 0.66, 0.390264, 0.103125, 1; 13:27:14.442 [planetinfo.record]: land_fraction = 0.530000; 13:27:14.442 [planetinfo.record]: polar_cloud_color = 0.624191, 0.920996, 0.66361, 1; 13:27:14.442 [planetinfo.record]: polar_land_color = 0.934, 0.838571, 0.736984, 1; 13:27:14.442 [planetinfo.record]: polar_sea_color = 0.945312, 0.889185, 0.899709, 1; 13:27:14.442 [planetinfo.record]: sea_color = 0.546875, 0.416992, 0.441345, 1; 13:27:14.442 [planetinfo.record]: rotation_speed = 0.002696 13:27:14.442 [planetinfo.record]: planet zpos = 588800.000000 13:27:14.442 [planetinfo.record]: sun_radius = 142547.890625 13:27:14.442 [planetinfo.record]: sun_vector = 0.394 -0.338 0.855 13:27:14.442 [planetinfo.record]: sun_distance = 1000960 13:27:14.442 [planetinfo.record]: corona_flare = 0.019849 13:27:14.442 [planetinfo.record]: corona_hues = 0.645477 13:27:14.443 [planetinfo.record]: sun_color = 0.240273, 0.628111, 0.827621, 1 13:27:14.443 [planetinfo.record]: corona_shimmer = 0.319944 13:27:14.443 [planetinfo.record]: station_vector = -0.154 -0.853 0.499 13:27:14.443 [planetinfo.record]: station = coriolis 13:27:19.062 [PLANETINFO OVER]: Done 13:27:19.063 [PLANETINFO LOGGING]: 4 177 13:27:20.065 [planetinfo.record]: seed = 72 25 155 133 13:27:20.065 [planetinfo.record]: coordinates = 25 140 13:27:20.065 [planetinfo.record]: government = 1; 13:27:20.065 [planetinfo.record]: economy = 6; 13:27:20.065 [planetinfo.record]: techlevel = 3; 13:27:20.065 [planetinfo.record]: population = 20; 13:27:20.065 [planetinfo.record]: productivity = 3200; 13:27:20.065 [planetinfo.record]: name = "Cemaaran"; 13:27:20.065 [planetinfo.record]: inhabitant = "Fierce Black Furry Feline"; 13:27:20.065 [planetinfo.record]: inhabitants = "Fierce Black Furry Felines"; 13:27:20.065 [planetinfo.record]: description = "This planet is mildly fabled for its inhabitants’ eccentric love for tourists but plagued by deadly earthquakes."; 13:27:20.068 [planetinfo.record]: air_color = 0.649669, 0.48844, 0.950221, 1; 13:27:20.068 [planetinfo.record]: cloud_alpha = 1.000000; 13:27:20.068 [planetinfo.record]: cloud_color = 0.853516, 0.120026, 0.807673, 1; 13:27:20.068 [planetinfo.record]: cloud_fraction = 0.400000; 13:27:20.068 [planetinfo.record]: land_color = 0.0696094, 0.66, 0.466278, 1; 13:27:20.068 [planetinfo.record]: land_fraction = 0.390000; 13:27:20.068 [planetinfo.record]: polar_cloud_color = 0.884082, 0.409233, 0.854404, 1; 13:27:20.068 [planetinfo.record]: polar_land_color = 0.725127, 0.934, 0.865464, 1; 13:27:20.069 [planetinfo.record]: polar_sea_color = 0.895751, 0.932813, 0.882346, 1; 13:27:20.069 [planetinfo.record]: sea_color = 0.565098, 0.671875, 0.526477, 1; 13:27:20.069 [planetinfo.record]: rotation_speed = 0.001041 13:27:20.069 [planetinfo.record]: planet zpos = 412100.000000 13:27:20.069 [planetinfo.record]: sun_radius = 73908.993073 13:27:20.069 [planetinfo.record]: sun_vector = 0.480 0.788 0.386 13:27:20.069 [planetinfo.record]: sun_distance = 947830 13:27:20.069 [planetinfo.record]: corona_flare = 0.069038 13:27:20.069 [planetinfo.record]: corona_hues = 0.702568 13:27:20.069 [planetinfo.record]: sun_color = 0.665589, 0.745466, 0.77626, 1 13:27:20.069 [planetinfo.record]: corona_shimmer = 0.477174 13:27:20.069 [planetinfo.record]: station_vector = 0.276 -0.904 0.327 13:27:20.069 [planetinfo.record]: station = coriolis 13:27:25.294 [PLANETINFO OVER]: Done 13:27:25.294 [PLANETINFO LOGGING]: 4 178 13:27:26.299 [planetinfo.record]: seed = 240 123 69 114 13:27:26.299 [planetinfo.record]: coordinates = 123 202 13:27:26.299 [planetinfo.record]: government = 6; 13:27:26.299 [planetinfo.record]: economy = 2; 13:27:26.299 [planetinfo.record]: techlevel = 11; 13:27:26.299 [planetinfo.record]: population = 53; 13:27:26.299 [planetinfo.record]: productivity = 33920; 13:27:26.299 [planetinfo.record]: name = "Enedso"; 13:27:26.299 [planetinfo.record]: inhabitant = "Human Colonial"; 13:27:26.299 [planetinfo.record]: inhabitants = "Human Colonials"; 13:27:26.299 [planetinfo.record]: description = "This world is plagued by frequent earthquakes."; 13:27:26.328 [planetinfo.record]: air_color = 0.633698, 0.469433, 0.961928, 1; 13:27:26.328 [planetinfo.record]: cloud_alpha = 1.000000; 13:27:26.328 [planetinfo.record]: cloud_color = 0.888672, 0.055542, 0.875654, 1; 13:27:26.328 [planetinfo.record]: cloud_fraction = 0.360000; 13:27:26.328 [planetinfo.record]: land_color = 0.583804, 0.66, 0.559453, 1; 13:27:26.328 [planetinfo.record]: land_fraction = 0.700000; 13:27:26.328 [planetinfo.record]: polar_cloud_color = 0.899902, 0.372616, 0.891663, 1; 13:27:26.328 [planetinfo.record]: polar_land_color = 0.907043, 0.934, 0.898428, 1; 13:27:26.328 [planetinfo.record]: polar_sea_color = 0.866743, 0.885051, 0.916602, 1; 13:27:26.328 [planetinfo.record]: sea_color = 0.652528, 0.719156, 0.833984, 1; 13:27:26.328 [planetinfo.record]: rotation_speed = 0.002531 13:27:26.328 [planetinfo.record]: planet zpos = 379610.000000 13:27:26.328 [planetinfo.record]: sun_radius = 102356.777954 13:27:26.328 [planetinfo.record]: sun_vector = 0.920 0.142 0.364 13:27:26.328 [planetinfo.record]: sun_distance = 690200 13:27:26.328 [planetinfo.record]: corona_flare = 0.001549 13:27:26.328 [planetinfo.record]: corona_hues = 0.699509 13:27:26.328 [planetinfo.record]: sun_color = 0.583475, 0.666215, 0.694235, 1 13:27:26.329 [planetinfo.record]: corona_shimmer = 0.393690 13:27:26.329 [planetinfo.record]: station_vector = 0.394 0.717 0.576 13:27:26.329 [planetinfo.record]: station = dodecahedron 13:27:31.096 [PLANETINFO OVER]: Done 13:27:31.097 [PLANETINFO LOGGING]: 4 179 13:27:32.099 [planetinfo.record]: seed = 28 210 243 49 13:27:32.099 [planetinfo.record]: coordinates = 210 167 13:27:32.099 [planetinfo.record]: government = 3; 13:27:32.099 [planetinfo.record]: economy = 7; 13:27:32.099 [planetinfo.record]: techlevel = 4; 13:27:32.099 [planetinfo.record]: population = 27; 13:27:32.099 [planetinfo.record]: productivity = 4536; 13:27:32.099 [planetinfo.record]: name = "Atmaa"; 13:27:32.099 [planetinfo.record]: inhabitant = "Red Furry Feline"; 13:27:32.099 [planetinfo.record]: inhabitants = "Red Furry Felines"; 13:27:32.099 [planetinfo.record]: description = "Atmaa is a revolting little planet."; 13:27:32.119 [planetinfo.record]: air_color = 0.578129, 0.567918, 0.844207, 1; 13:27:32.119 [planetinfo.record]: cloud_alpha = 1.000000; 13:27:32.119 [planetinfo.record]: cloud_color = 0.3559, 0.330292, 0.535156, 1; 13:27:32.119 [planetinfo.record]: cloud_fraction = 0.500000; 13:27:32.119 [planetinfo.record]: land_color = 0.66, 0.345469, 0.404443, 1; 13:27:32.119 [planetinfo.record]: land_fraction = 0.280000; 13:27:32.119 [planetinfo.record]: polar_cloud_color = 0.585729, 0.563573, 0.74082, 1; 13:27:32.119 [planetinfo.record]: polar_land_color = 0.934, 0.822723, 0.843587, 1; 13:27:32.119 [planetinfo.record]: polar_sea_color = 0.927148, 0.862305, 0.83389, 1; 13:27:32.119 [planetinfo.record]: sea_color = 0.728516, 0.52471, 0.435402, 1; 13:27:32.119 [planetinfo.record]: rotation_speed = 0.004625 13:27:32.123 [planetinfo.record]: planet zpos = 393840.000000 13:27:32.123 [planetinfo.record]: sun_radius = 107844.368591 13:27:32.123 [planetinfo.record]: sun_vector = -0.066 -0.805 0.590 13:27:32.123 [planetinfo.record]: sun_distance = 689220 13:27:32.123 [planetinfo.record]: corona_flare = 0.040112 13:27:32.123 [planetinfo.record]: corona_hues = 0.507591 13:27:32.123 [planetinfo.record]: sun_color = 0.246058, 0.6676, 0.703485, 1 13:27:32.124 [planetinfo.record]: corona_shimmer = 0.319274 13:27:32.124 [planetinfo.record]: station_vector = -0.732 -0.576 0.363 13:27:32.124 [planetinfo.record]: station = coriolis 13:27:36.813 [PLANETINFO OVER]: Done 13:27:36.813 [PLANETINFO LOGGING]: 4 180 13:27:37.815 [planetinfo.record]: seed = 44 140 101 230 13:27:37.816 [planetinfo.record]: coordinates = 140 175 13:27:37.816 [planetinfo.record]: government = 5; 13:27:37.816 [planetinfo.record]: economy = 7; 13:27:37.816 [planetinfo.record]: techlevel = 3; 13:27:37.816 [planetinfo.record]: population = 25; 13:27:37.816 [planetinfo.record]: productivity = 5400; 13:27:37.816 [planetinfo.record]: name = "Bilera"; 13:27:37.816 [planetinfo.record]: inhabitant = "Human Colonial"; 13:27:37.816 [planetinfo.record]: inhabitants = "Human Colonials"; 13:27:37.816 [planetinfo.record]: description = "The world Bilera is a dull place."; 13:27:37.839 [planetinfo.record]: air_color = 0.661676, 0.857941, 0.922904, 1; 13:27:37.839 [planetinfo.record]: cloud_alpha = 1.000000; 13:27:37.839 [planetinfo.record]: cloud_color = 0.625324, 0.771484, 0.578613, 1; 13:27:37.839 [planetinfo.record]: cloud_fraction = 0.260000; 13:27:37.839 [planetinfo.record]: land_color = 0.0902344, 0.499753, 0.66, 1; 13:27:37.839 [planetinfo.record]: land_fraction = 0.420000; 13:27:37.839 [planetinfo.record]: polar_cloud_color = 0.746856, 0.847168, 0.714798, 1; 13:27:37.839 [planetinfo.record]: polar_land_color = 0.732424, 0.877307, 0.934, 1; 13:27:37.839 [planetinfo.record]: polar_sea_color = 0.94668, 0.914505, 0.898791, 1; 13:27:37.839 [planetinfo.record]: sea_color = 0.533203, 0.460714, 0.425313, 1; 13:27:37.839 [planetinfo.record]: rotation_speed = 0.003681 13:27:37.839 [planetinfo.record]: planet zpos = 404280.000000 13:27:37.839 [planetinfo.record]: sun_radius = 146439.638672 13:27:37.839 [planetinfo.record]: sun_vector = -0.022 0.176 -0.984 13:27:37.839 [planetinfo.record]: sun_distance = 988240 13:27:37.839 [planetinfo.record]: corona_flare = 0.066380 13:27:37.839 [planetinfo.record]: corona_hues = 0.789101 13:27:37.839 [planetinfo.record]: sun_color = 0.6547, 0.647331, 0.622789, 1 13:27:37.839 [planetinfo.record]: corona_shimmer = 0.518998 13:27:37.840 [planetinfo.record]: station_vector = -0.602 -0.729 0.325 13:27:37.840 [planetinfo.record]: station = coriolis 13:27:42.723 [PLANETINFO OVER]: Done 13:27:42.723 [PLANETINFO LOGGING]: 4 181 13:27:43.727 [planetinfo.record]: seed = 96 156 91 82 13:27:43.727 [planetinfo.record]: coordinates = 156 148 13:27:43.727 [planetinfo.record]: government = 4; 13:27:43.727 [planetinfo.record]: economy = 4; 13:27:43.727 [planetinfo.record]: techlevel = 5; 13:27:43.727 [planetinfo.record]: population = 29; 13:27:43.727 [planetinfo.record]: productivity = 11136; 13:27:43.727 [planetinfo.record]: name = "Engeatso"; 13:27:43.727 [planetinfo.record]: inhabitant = "Human Colonial"; 13:27:43.727 [planetinfo.record]: inhabitants = "Human Colonials"; 13:27:43.727 [planetinfo.record]: description = "This world is ravaged by unpredictable civil war."; 13:27:43.752 [planetinfo.record]: air_color = 0.435057, 0.775294, 0.857215, 1; 13:27:43.752 [planetinfo.record]: cloud_alpha = 1.000000; 13:27:43.752 [planetinfo.record]: cloud_color = 0.274562, 0.574219, 0.0695343, 1; 13:27:43.752 [planetinfo.record]: cloud_fraction = 0.430000; 13:27:43.752 [planetinfo.record]: land_color = 0.397997, 0.297478, 0.619141, 1; 13:27:43.752 [planetinfo.record]: land_fraction = 0.710000; 13:27:43.752 [planetinfo.record]: polar_cloud_color = 0.511042, 0.758398, 0.341798, 1; 13:27:43.752 [planetinfo.record]: polar_land_color = 0.85432, 0.816245, 0.938086, 1; 13:27:43.752 [planetinfo.record]: polar_sea_color = 0.946875, 0.900953, 0.896202, 1; 13:27:43.752 [planetinfo.record]: sea_color = 0.53125, 0.428191, 0.417529, 1; 13:27:43.752 [planetinfo.record]: rotation_speed = 0.004475 13:27:43.752 [planetinfo.record]: planet zpos = 487760.000000 13:27:43.752 [planetinfo.record]: sun_radius = 66410.560303 13:27:43.752 [planetinfo.record]: sun_vector = 0.710 0.653 0.263 13:27:43.752 [planetinfo.record]: sun_distance = 661960 13:27:43.752 [planetinfo.record]: corona_flare = 0.014084 13:27:43.752 [planetinfo.record]: corona_hues = 0.587082 13:27:43.752 [planetinfo.record]: sun_color = 0.747198, 0.7527, 0.75405, 1 13:27:43.752 [planetinfo.record]: corona_shimmer = 0.639139 13:27:43.753 [planetinfo.record]: station_vector = 0.243 -0.481 0.842 13:27:43.753 [planetinfo.record]: station = coriolis 13:27:48.849 [PLANETINFO OVER]: Done 13:27:48.852 [PLANETINFO LOGGING]: 4 182 13:27:49.867 [planetinfo.record]: seed = 24 71 213 59 13:27:49.868 [planetinfo.record]: coordinates = 71 113 13:27:49.868 [planetinfo.record]: government = 3; 13:27:49.868 [planetinfo.record]: economy = 1; 13:27:49.868 [planetinfo.record]: techlevel = 11; 13:27:49.868 [planetinfo.record]: population = 49; 13:27:49.868 [planetinfo.record]: productivity = 24696; 13:27:49.868 [planetinfo.record]: name = "Anrati"; 13:27:49.868 [planetinfo.record]: inhabitant = "Red Frog"; 13:27:49.868 [planetinfo.record]: inhabitants = "Red Frogs"; 13:27:49.868 [planetinfo.record]: description = "The world Anrati is mildly fabled for its inhabitants’ eccentric love for tourists but plagued by deadly earthquakes."; 13:27:49.890 [planetinfo.record]: air_color = 0.487093, 0.466304, 0.930709, 1; 13:27:49.890 [planetinfo.record]: cloud_alpha = 1.000000; 13:27:49.890 [planetinfo.record]: cloud_color = 0.186746, 0.0807343, 0.794922, 1; 13:27:49.890 [planetinfo.record]: cloud_fraction = 0.190000; 13:27:49.890 [planetinfo.record]: land_color = 0.591176, 0.66, 0.528516, 1; 13:27:49.890 [planetinfo.record]: land_fraction = 0.590000; 13:27:49.890 [planetinfo.record]: polar_cloud_color = 0.447579, 0.376088, 0.857715, 1; 13:27:49.890 [planetinfo.record]: polar_land_color = 0.909651, 0.934, 0.887482, 1; 13:27:49.890 [planetinfo.record]: polar_sea_color = 0.88798, 0.95, 0.765381, 1; 13:27:49.890 [planetinfo.record]: sea_color = 0.369431, 0.5, 0.111328, 1; 13:27:49.890 [planetinfo.record]: rotation_speed = 0.000936 13:27:49.890 [planetinfo.record]: planet zpos = 627330.000000 13:27:49.891 [planetinfo.record]: sun_radius = 150059.665375 13:27:49.891 [planetinfo.record]: sun_vector = -0.963 0.214 -0.166 13:27:49.891 [planetinfo.record]: sun_distance = 1254660 13:27:49.891 [planetinfo.record]: corona_flare = 0.039932 13:27:49.891 [planetinfo.record]: corona_hues = 0.880577 13:27:49.891 [planetinfo.record]: sun_color = 0.696145, 0.703787, 0.711343, 1 13:27:49.891 [planetinfo.record]: corona_shimmer = 0.363402 13:27:49.891 [planetinfo.record]: station_vector = 0.790 -0.252 0.559 13:27:49.891 [planetinfo.record]: station = dodecahedron 13:27:55.037 [PLANETINFO OVER]: Done 13:27:55.037 [PLANETINFO LOGGING]: 4 183 13:27:56.041 [planetinfo.record]: seed = 20 168 83 20 13:27:56.042 [planetinfo.record]: coordinates = 168 119 13:27:56.042 [planetinfo.record]: government = 2; 13:27:56.042 [planetinfo.record]: economy = 7; 13:27:56.042 [planetinfo.record]: techlevel = 1; 13:27:56.042 [planetinfo.record]: population = 14; 13:27:56.042 [planetinfo.record]: productivity = 2016; 13:27:56.042 [planetinfo.record]: name = "Rabeer"; 13:27:56.042 [planetinfo.record]: inhabitant = "Human Colonial"; 13:27:56.042 [planetinfo.record]: inhabitants = "Human Colonials"; 13:27:56.042 [planetinfo.record]: description = "This planet is very well known for killer Ongearet gargle blasters and its exciting sit coms."; 13:27:56.068 [planetinfo.record]: air_color = 0.704201, 0.829842, 0.897539, 1; 13:27:56.068 [planetinfo.record]: cloud_alpha = 1.000000; 13:27:56.068 [planetinfo.record]: cloud_color = 0.66272, 0.695312, 0.664757, 1; 13:27:56.068 [planetinfo.record]: cloud_fraction = 0.560000; 13:27:56.068 [planetinfo.record]: land_color = 0.608557, 0.609375, 0.583191, 1; 13:27:56.069 [planetinfo.record]: land_fraction = 0.680000; 13:27:56.069 [planetinfo.record]: polar_cloud_color = 0.789075, 0.812891, 0.790564, 1; 13:27:56.069 [planetinfo.record]: polar_land_color = 0.938747, 0.939062, 0.928975, 1; 13:27:56.069 [planetinfo.record]: polar_sea_color = 0.850172, 0.912695, 0.803065, 1; 13:27:56.071 [planetinfo.record]: sea_color = 0.633817, 0.873047, 0.453575, 1; 13:27:56.071 [planetinfo.record]: rotation_speed = 0.000542 13:27:56.071 [planetinfo.record]: planet zpos = 521040.000000 13:27:56.071 [planetinfo.record]: sun_radius = 104814.924316 13:27:56.071 [planetinfo.record]: sun_vector = -0.622 -0.782 0.024 13:27:56.071 [planetinfo.record]: sun_distance = 841680 13:27:56.071 [planetinfo.record]: corona_flare = 0.040344 13:27:56.071 [planetinfo.record]: corona_hues = 0.616638 13:27:56.072 [planetinfo.record]: sun_color = 0.672388, 0.533848, 0.289391, 1 13:27:56.072 [planetinfo.record]: corona_shimmer = 0.303065 13:27:56.072 [planetinfo.record]: station_vector = 0.692 -0.038 0.721 13:27:56.073 [planetinfo.record]: station = coriolis 13:28:01.121 [PLANETINFO OVER]: Done 13:28:01.121 [PLANETINFO LOGGING]: 4 184 13:28:02.128 [planetinfo.record]: seed = 180 56 21 93 13:28:02.128 [planetinfo.record]: coordinates = 56 240 13:28:02.128 [planetinfo.record]: government = 6; 13:28:02.128 [planetinfo.record]: economy = 0; 13:28:02.128 [planetinfo.record]: techlevel = 10; 13:28:02.128 [planetinfo.record]: population = 47; 13:28:02.128 [planetinfo.record]: productivity = 37600; 13:28:02.128 [planetinfo.record]: name = "Isbianon"; 13:28:02.128 [planetinfo.record]: inhabitant = "Human Colonial"; 13:28:02.128 [planetinfo.record]: inhabitants = "Human Colonials"; 13:28:02.128 [planetinfo.record]: description = "The world Isbianon is a boring world."; 13:28:02.140 [planetinfo.record]: air_color = 0.439546, 0.597708, 0.907945, 1; 13:28:02.140 [planetinfo.record]: cloud_alpha = 1.000000; 13:28:02.140 [planetinfo.record]: cloud_color = 0.0368958, 0.726562, 0.726562, 1; 13:28:02.140 [planetinfo.record]: cloud_fraction = 0.210000; 13:28:02.140 [planetinfo.record]: land_color = 0.339467, 0.0979688, 0.66, 1; 13:28:02.140 [planetinfo.record]: land_fraction = 0.290000; 13:28:02.140 [planetinfo.record]: polar_cloud_color = 0.336353, 0.826953, 0.826953, 1; 13:28:02.140 [planetinfo.record]: polar_land_color = 0.820599, 0.73516, 0.934, 1; 13:28:02.140 [planetinfo.record]: polar_sea_color = 0.941602, 0.91322, 0.888177, 1; 13:28:02.140 [planetinfo.record]: sea_color = 0.583984, 0.513574, 0.451447, 1; 13:28:02.140 [planetinfo.record]: rotation_speed = 0.004543 13:28:02.140 [planetinfo.record]: planet zpos = 744000.000000 13:28:02.140 [planetinfo.record]: sun_radius = 163233.428955 13:28:02.140 [planetinfo.record]: sun_vector = -0.457 0.889 0.019 13:28:02.140 [planetinfo.record]: sun_distance = 1240000 13:28:02.140 [planetinfo.record]: corona_flare = 0.070082 13:28:02.140 [planetinfo.record]: corona_hues = 0.841530 13:28:02.140 [planetinfo.record]: sun_color = 0.514912, 0.679281, 0.77323, 1 13:28:02.140 [planetinfo.record]: corona_shimmer = 0.312624 13:28:02.141 [planetinfo.record]: station_vector = 0.709 0.363 0.604 13:28:02.147 [planetinfo.record]: station = coriolis 13:28:07.138 [PLANETINFO OVER]: Done 13:28:07.138 [PLANETINFO LOGGING]: 4 185 13:28:08.141 [planetinfo.record]: seed = 56 255 91 161 13:28:08.141 [planetinfo.record]: coordinates = 255 27 13:28:08.141 [planetinfo.record]: government = 7; 13:28:08.141 [planetinfo.record]: economy = 3; 13:28:08.141 [planetinfo.record]: techlevel = 11; 13:28:08.141 [planetinfo.record]: population = 55; 13:28:08.141 [planetinfo.record]: productivity = 33880; 13:28:08.141 [planetinfo.record]: name = "Leteisan"; 13:28:08.141 [planetinfo.record]: inhabitant = "Human Colonial"; 13:28:08.141 [planetinfo.record]: inhabitants = "Human Colonials"; 13:28:08.141 [planetinfo.record]: description = "This planet is mildly noted for its ancient Eserce tulip plantations but ravaged by frequent earthquakes."; 13:28:08.152 [planetinfo.record]: air_color = 0.416561, 0.8159, 0.860467, 1; 13:28:08.152 [planetinfo.record]: cloud_alpha = 1.000000; 13:28:08.152 [planetinfo.record]: cloud_color = 0.410757, 0.583984, 0.0296555, 1; 13:28:08.152 [planetinfo.record]: cloud_fraction = 0.460000; 13:28:08.152 [planetinfo.record]: land_color = 0.425391, 0.473046, 0.66, 1; 13:28:08.152 [planetinfo.record]: land_fraction = 0.400000; 13:28:08.152 [planetinfo.record]: polar_cloud_color = 0.621376, 0.762793, 0.310257, 1; 13:28:08.152 [planetinfo.record]: polar_land_color = 0.850998, 0.867858, 0.934, 1; 13:28:08.152 [planetinfo.record]: polar_sea_color = 0.935352, 0.929127, 0.878445, 1; 13:28:08.152 [planetinfo.record]: sea_color = 0.646484, 0.629277, 0.489156, 1; 13:28:08.152 [planetinfo.record]: rotation_speed = 0.002964 13:28:08.152 [planetinfo.record]: planet zpos = 332700.000000 13:28:08.152 [planetinfo.record]: sun_radius = 106189.254456 13:28:08.152 [planetinfo.record]: sun_vector = -0.244 -0.883 0.400 13:28:08.152 [planetinfo.record]: sun_distance = 632130 13:28:08.152 [planetinfo.record]: corona_flare = 0.059464 13:28:08.152 [planetinfo.record]: corona_hues = 0.946571 13:28:08.152 [planetinfo.record]: sun_color = 0.728862, 0.752316, 0.814224, 1 13:28:08.152 [planetinfo.record]: corona_shimmer = 0.463499 13:28:08.152 [planetinfo.record]: station_vector = 0.269 0.562 0.782 13:28:08.152 [planetinfo.record]: station = dodecahedron 13:28:13.031 [PLANETINFO OVER]: Done 13:28:13.033 [PLANETINFO LOGGING]: 4 186 13:28:14.034 [planetinfo.record]: seed = 0 187 165 212 13:28:14.034 [planetinfo.record]: coordinates = 187 93 13:28:14.034 [planetinfo.record]: government = 0; 13:28:14.034 [planetinfo.record]: economy = 7; 13:28:14.034 [planetinfo.record]: techlevel = 3; 13:28:14.034 [planetinfo.record]: population = 20; 13:28:14.034 [planetinfo.record]: productivity = 1920; 13:28:14.034 [planetinfo.record]: name = "Rainte"; 13:28:14.034 [planetinfo.record]: inhabitant = "Feline"; 13:28:14.034 [planetinfo.record]: inhabitants = "Felines"; 13:28:14.034 [planetinfo.record]: description = "The world Rainte is fabled for its fabulous cuisine and the Rainteian edible poet."; 13:28:14.048 [planetinfo.record]: air_color = 0.571341, 0.514025, 0.905994, 1; 13:28:14.048 [planetinfo.record]: cloud_alpha = 1.000000; 13:28:14.048 [planetinfo.record]: cloud_color = 0.445117, 0.216774, 0.720703, 1; 13:28:14.048 [planetinfo.record]: cloud_fraction = 0.530000; 13:28:14.048 [planetinfo.record]: land_color = 0.556641, 0.428352, 0.518555, 1; 13:28:14.048 [planetinfo.record]: land_fraction = 0.710000; 13:28:14.048 [planetinfo.record]: polar_cloud_color = 0.627312, 0.46408, 0.824316, 1; 13:28:14.048 [planetinfo.record]: polar_land_color = 0.944336, 0.889926, 0.928183, 1; 13:28:14.048 [planetinfo.record]: polar_sea_color = 0.895981, 0.93125, 0.87814, 1; 13:28:14.048 [planetinfo.record]: sea_color = 0.583351, 0.6875, 0.530664, 1; 13:28:14.048 [planetinfo.record]: rotation_speed = 0.004461 13:28:14.048 [planetinfo.record]: planet zpos = 523510.000000 13:28:14.048 [planetinfo.record]: sun_radius = 92240.150604 13:28:14.048 [planetinfo.record]: sun_vector = 0.324 -0.501 -0.802 13:28:14.048 [planetinfo.record]: sun_distance = 966480 13:28:14.048 [planetinfo.record]: corona_flare = 0.018851 13:28:14.048 [planetinfo.record]: corona_hues = 0.621307 13:28:14.048 [planetinfo.record]: sun_color = 0.240173, 0.44836, 0.809973, 1 13:28:14.049 [planetinfo.record]: corona_shimmer = 0.225844 13:28:14.049 [planetinfo.record]: station_vector = 0.209 0.882 0.423 13:28:14.049 [planetinfo.record]: station = coriolis 13:28:18.623 [PLANETINFO OVER]: Done 13:28:18.625 [PLANETINFO LOGGING]: 4 187 13:28:19.628 [planetinfo.record]: seed = 204 61 243 166 13:28:19.628 [planetinfo.record]: coordinates = 61 124 13:28:19.628 [planetinfo.record]: government = 1; 13:28:19.628 [planetinfo.record]: economy = 6; 13:28:19.628 [planetinfo.record]: techlevel = 3; 13:28:19.628 [planetinfo.record]: population = 20; 13:28:19.628 [planetinfo.record]: productivity = 3200; 13:28:19.628 [planetinfo.record]: name = "Bilecedi"; 13:28:19.628 [planetinfo.record]: inhabitant = "Fierce Harmless Bug-Eyed Lobster"; 13:28:19.628 [planetinfo.record]: inhabitants = "Fierce Harmless Bug-Eyed Lobsters"; 13:28:19.628 [planetinfo.record]: description = "This world is very fabled for its unusual oceans."; 13:28:19.632 [planetinfo.record]: air_color = 0.786717, 0.751255, 0.970383, 1; 13:28:19.632 [planetinfo.record]: cloud_alpha = 1.000000; 13:28:19.632 [planetinfo.record]: cloud_color = 0.878357, 0.842651, 0.914062, 1; 13:28:19.632 [planetinfo.record]: cloud_fraction = 0.150000; 13:28:19.632 [planetinfo.record]: land_color = 0.450286, 0.66, 0.306797, 1; 13:28:19.632 [planetinfo.record]: land_fraction = 0.600000; 13:28:19.632 [planetinfo.record]: polar_cloud_color = 0.889079, 0.86683, 0.911328, 1; 13:28:19.632 [planetinfo.record]: polar_land_color = 0.859806, 0.934, 0.809041, 1; 13:28:19.632 [planetinfo.record]: polar_sea_color = 0.912125, 0.93125, 0.87432, 1; 13:28:19.632 [planetinfo.record]: sea_color = 0.631024, 0.6875, 0.519385, 1; 13:28:19.632 [planetinfo.record]: rotation_speed = 0.001530 13:28:19.632 [planetinfo.record]: planet zpos = 573690.000000 13:28:19.632 [planetinfo.record]: sun_radius = 137067.230530 13:28:19.632 [planetinfo.record]: sun_vector = -1.000 0.026 -0.005 13:28:19.632 [planetinfo.record]: sun_distance = 706080 13:28:19.632 [planetinfo.record]: corona_flare = 0.054329 13:28:19.632 [planetinfo.record]: corona_hues = 0.811340 13:28:19.632 [planetinfo.record]: sun_color = 0.770541, 0.700222, 0.578121, 1 13:28:19.632 [planetinfo.record]: corona_shimmer = 1.390714 13:28:19.633 [planetinfo.record]: station_vector = -0.641 -0.767 0.032 13:28:19.633 [planetinfo.record]: station = coriolis 13:28:25.079 [PLANETINFO OVER]: Done 13:28:25.080 [PLANETINFO LOGGING]: 4 188 13:28:26.085 [planetinfo.record]: seed = 252 77 5 245 13:28:26.085 [planetinfo.record]: coordinates = 77 69 13:28:26.085 [planetinfo.record]: government = 7; 13:28:26.085 [planetinfo.record]: economy = 5; 13:28:26.085 [planetinfo.record]: techlevel = 7; 13:28:26.085 [planetinfo.record]: population = 41; 13:28:26.085 [planetinfo.record]: productivity = 18040; 13:28:26.085 [planetinfo.record]: name = "Lausmaes"; 13:28:26.085 [planetinfo.record]: inhabitant = "Human Colonial"; 13:28:26.085 [planetinfo.record]: inhabitants = "Human Colonials"; 13:28:26.085 [planetinfo.record]: description = "The planet Lausmaes is scourged by deadly tree grubs."; 13:28:26.112 [planetinfo.record]: air_color = 0.519574, 0.605712, 0.851361, 1; 13:28:26.112 [planetinfo.record]: cloud_alpha = 1.000000; 13:28:26.112 [planetinfo.record]: cloud_color = 0.239182, 0.482236, 0.556641, 1; 13:28:26.112 [planetinfo.record]: cloud_fraction = 0.230000; 13:28:26.112 [planetinfo.record]: land_color = 0.649607, 0.629063, 0.66, 1; 13:28:26.112 [planetinfo.record]: land_fraction = 0.420000; 13:28:26.112 [planetinfo.record]: polar_cloud_color = 0.48298, 0.687791, 0.750488, 1; 13:28:26.113 [planetinfo.record]: polar_land_color = 0.930323, 0.923055, 0.934, 1; 13:28:26.113 [planetinfo.record]: polar_sea_color = 0.797134, 0.942383, 0.738077, 1; 13:28:26.113 [planetinfo.record]: sea_color = 0.220953, 0.576172, 0.0765228, 1; 13:28:26.113 [planetinfo.record]: rotation_speed = 0.000553 13:28:26.113 [planetinfo.record]: planet zpos = 584220.000000 13:28:26.113 [planetinfo.record]: sun_radius = 82502.329102 13:28:26.113 [planetinfo.record]: sun_vector = 0.376 0.816 -0.438 13:28:26.113 [planetinfo.record]: sun_distance = 834600 13:28:26.113 [planetinfo.record]: corona_flare = 0.090251 13:28:26.113 [planetinfo.record]: corona_hues = 0.672615 13:28:26.113 [planetinfo.record]: sun_color = 0.70567, 0.608038, 0.192238, 1 13:28:26.113 [planetinfo.record]: corona_shimmer = 0.393941 13:28:26.113 [planetinfo.record]: station_vector = -0.474 -0.650 0.594 13:28:26.113 [planetinfo.record]: station = coriolis 13:28:30.924 [PLANETINFO OVER]: Done 13:28:30.925 [PLANETINFO LOGGING]: 4 189 13:28:31.928 [planetinfo.record]: seed = 208 73 155 158 13:28:31.928 [planetinfo.record]: coordinates = 73 203 13:28:31.928 [planetinfo.record]: government = 2; 13:28:31.928 [planetinfo.record]: economy = 3; 13:28:31.928 [planetinfo.record]: techlevel = 6; 13:28:31.928 [planetinfo.record]: population = 30; 13:28:31.928 [planetinfo.record]: productivity = 10080; 13:28:31.929 [planetinfo.record]: name = "Riratea"; 13:28:31.929 [planetinfo.record]: inhabitant = "Black Horned Bird"; 13:28:31.929 [planetinfo.record]: inhabitants = "Black Horned Birds"; 13:28:31.929 [planetinfo.record]: description = "This world is very notable for its inhabitants’ ingrained shyness."; 13:28:31.952 [planetinfo.record]: air_color = 0.751461, 0.562833, 0.914449, 1; 13:28:31.952 [planetinfo.record]: cloud_alpha = 1.000000; 13:28:31.952 [planetinfo.record]: cloud_color = 0.746094, 0.332245, 0.487438, 1; 13:28:31.952 [planetinfo.record]: cloud_fraction = 0.380000; 13:28:31.952 [planetinfo.record]: land_color = 0.597656, 0.578797, 0.546295, 1; 13:28:31.952 [planetinfo.record]: land_fraction = 0.640000; 13:28:31.952 [planetinfo.record]: polar_cloud_color = 0.835742, 0.546007, 0.654658, 1; 13:28:31.952 [planetinfo.record]: polar_land_color = 0.940234, 0.932817, 0.920034, 1; 13:28:31.953 [planetinfo.record]: polar_sea_color = 0.833862, 0.911719, 0.802206, 1; 13:28:31.953 [planetinfo.record]: sea_color = 0.581259, 0.882812, 0.458649, 1; 13:28:31.953 [planetinfo.record]: rotation_speed = 0.001372 13:28:31.953 [planetinfo.record]: planet zpos = 712030.000000 13:28:31.953 [planetinfo.record]: sun_radius = 191912.360535 13:28:31.953 [planetinfo.record]: sun_vector = -0.220 -0.862 0.456 13:28:31.953 [planetinfo.record]: sun_distance = 1359330 13:28:31.953 [planetinfo.record]: corona_flare = 0.007695 13:28:31.953 [planetinfo.record]: corona_hues = 0.514595 13:28:31.953 [planetinfo.record]: sun_color = 0.493926, 0.514526, 0.816074, 1 13:28:31.953 [planetinfo.record]: corona_shimmer = 0.470203 13:28:31.954 [planetinfo.record]: station_vector = 0.768 -0.618 0.167 13:28:31.954 [planetinfo.record]: station = coriolis 13:28:37.600 [PLANETINFO OVER]: Done 13:28:37.601 [PLANETINFO LOGGING]: 4 190 13:28:38.607 [planetinfo.record]: seed = 168 239 181 64 13:28:38.607 [planetinfo.record]: coordinates = 239 156 13:28:38.607 [planetinfo.record]: government = 5; 13:28:38.607 [planetinfo.record]: economy = 4; 13:28:38.608 [planetinfo.record]: techlevel = 9; 13:28:38.608 [planetinfo.record]: population = 46; 13:28:38.608 [planetinfo.record]: productivity = 19872; 13:28:38.608 [planetinfo.record]: name = "Diis"; 13:28:38.608 [planetinfo.record]: inhabitant = "Large Yellow Bony Lobster"; 13:28:38.608 [planetinfo.record]: inhabitants = "Large Yellow Bony Lobsters"; 13:28:38.608 [planetinfo.record]: description = "Diis is mildly well known for killer Ar juice."; 13:28:38.612 [planetinfo.record]: air_color = 0.725123, 0.792346, 0.924205, 1; 13:28:38.612 [planetinfo.record]: cloud_alpha = 1.000000; 13:28:38.612 [planetinfo.record]: cloud_color = 0.739044, 0.775391, 0.775391, 1; 13:28:38.612 [planetinfo.record]: cloud_fraction = 0.510000; 13:28:38.612 [planetinfo.record]: land_color = 0.66, 0.56606, 0.386719, 1; 13:28:38.612 [planetinfo.record]: land_fraction = 0.240000; 13:28:38.613 [planetinfo.record]: polar_cloud_color = 0.824055, 0.848926, 0.848926, 1; 13:28:38.613 [planetinfo.record]: polar_land_color = 0.934, 0.900765, 0.837316, 1; 13:28:38.613 [planetinfo.record]: polar_sea_color = 0.94043, 0.868795, 0.917484, 1; 13:28:38.613 [planetinfo.record]: sea_color = 0.595703, 0.4142, 0.537565, 1; 13:28:38.613 [planetinfo.record]: rotation_speed = 0.002839 13:28:38.613 [planetinfo.record]: planet zpos = 366600.000000 13:28:38.613 [planetinfo.record]: sun_radius = 75155.069733 13:28:38.613 [planetinfo.record]: sun_vector = 0.229 -0.684 0.693 13:28:38.613 [planetinfo.record]: sun_distance = 611000 13:28:38.613 [planetinfo.record]: corona_flare = 0.057025 13:28:38.613 [planetinfo.record]: corona_hues = 0.705482 13:28:38.613 [planetinfo.record]: sun_color = 0.781461, 0.744662, 0.715684, 1 13:28:38.613 [planetinfo.record]: corona_shimmer = 0.554253 13:28:38.614 [planetinfo.record]: station_vector = 0.570 -0.668 0.478 13:28:38.614 [planetinfo.record]: station = coriolis 13:28:43.951 [PLANETINFO OVER]: Done 13:28:43.952 [PLANETINFO LOGGING]: 4 191 13:28:44.955 [planetinfo.record]: seed = 68 11 211 213 13:28:44.955 [planetinfo.record]: coordinates = 11 253 13:28:44.955 [planetinfo.record]: government = 0; 13:28:44.955 [planetinfo.record]: economy = 7; 13:28:44.955 [planetinfo.record]: techlevel = 3; 13:28:44.955 [planetinfo.record]: population = 20; 13:28:44.955 [planetinfo.record]: productivity = 1920; 13:28:44.955 [planetinfo.record]: name = "Larionra"; 13:28:44.955 [planetinfo.record]: inhabitant = "Insect"; 13:28:44.955 [planetinfo.record]: inhabitants = "Insects"; 13:28:44.955 [planetinfo.record]: description = "This world is a revolting little planet."; 13:28:44.980 [planetinfo.record]: air_color = 0.598855, 0.913148, 0.839225, 1; 13:28:44.980 [planetinfo.record]: cloud_alpha = 1.000000; 13:28:44.980 [planetinfo.record]: cloud_color = 0.742188, 0.51843, 0.42038, 1; 13:28:44.980 [planetinfo.record]: cloud_fraction = 0.600000; 13:28:44.980 [planetinfo.record]: land_color = 0.626484, 0.629365, 0.66, 1; 13:28:44.980 [planetinfo.record]: land_fraction = 0.570000; 13:28:44.980 [planetinfo.record]: polar_cloud_color = 0.833984, 0.676839, 0.607978, 1; 13:28:44.980 [planetinfo.record]: polar_land_color = 0.922143, 0.923162, 0.934, 1; 13:28:44.980 [planetinfo.record]: polar_sea_color = 0.905409, 0.931641, 0.876597, 1; 13:28:44.980 [planetinfo.record]: sea_color = 0.606604, 0.683594, 0.522041, 1; 13:28:44.980 [planetinfo.record]: rotation_speed = 0.002482 13:28:44.980 [planetinfo.record]: planet zpos = 451770.000000 13:28:44.980 [planetinfo.record]: sun_radius = 77379.750366 13:28:44.980 [planetinfo.record]: sun_vector = 0.098 0.663 -0.743 13:28:44.980 [planetinfo.record]: sun_distance = 780330 13:28:44.980 [planetinfo.record]: corona_flare = 0.041412 13:28:44.980 [planetinfo.record]: corona_hues = 0.808098 13:28:44.980 [planetinfo.record]: sun_color = 0.455732, 0.58639, 0.703064, 1 13:28:44.981 [planetinfo.record]: corona_shimmer = 0.324635 13:28:44.981 [planetinfo.record]: station_vector = -0.852 -0.003 0.523 13:28:44.981 [planetinfo.record]: station = coriolis 13:28:49.550 [PLANETINFO OVER]: Done 13:28:49.551 [PLANETINFO LOGGING]: 4 192 13:28:50.554 [planetinfo.record]: seed = 4 116 53 18 13:28:50.554 [planetinfo.record]: coordinates = 116 191 13:28:50.554 [planetinfo.record]: government = 0; 13:28:50.554 [planetinfo.record]: economy = 7; 13:28:50.554 [planetinfo.record]: techlevel = 0; 13:28:50.554 [planetinfo.record]: population = 8; 13:28:50.554 [planetinfo.record]: productivity = 768; 13:28:50.554 [planetinfo.record]: name = "Encein"; 13:28:50.554 [planetinfo.record]: inhabitant = "Human Colonial"; 13:28:50.554 [planetinfo.record]: inhabitants = "Human Colonials"; 13:28:50.554 [planetinfo.record]: description = "The world Encein is a dull place."; 13:28:50.580 [planetinfo.record]: air_color = 0.633146, 0.868922, 0.789544, 1; 13:28:50.580 [planetinfo.record]: cloud_alpha = 1.000000; 13:28:50.580 [planetinfo.record]: cloud_color = 0.609375, 0.478455, 0.478455, 1; 13:28:50.580 [planetinfo.record]: cloud_fraction = 0.300000; 13:28:50.580 [planetinfo.record]: land_color = 0.228088, 0.479677, 0.578125, 1; 13:28:50.580 [planetinfo.record]: land_fraction = 0.620000; 13:28:50.580 [planetinfo.record]: polar_cloud_color = 0.774219, 0.670259, 0.670259, 1; 13:28:50.580 [planetinfo.record]: polar_land_color = 0.799571, 0.902077, 0.942187, 1; 13:28:50.580 [planetinfo.record]: polar_sea_color = 0.920301, 0.933594, 0.878709, 1; 13:28:50.580 [planetinfo.record]: sea_color = 0.626243, 0.664062, 0.507904, 1; 13:28:50.580 [planetinfo.record]: rotation_speed = 0.001393 13:28:50.580 [planetinfo.record]: planet zpos = 482160.000000 13:28:50.581 [planetinfo.record]: sun_radius = 63255.963135 13:28:50.581 [planetinfo.record]: sun_vector = 0.346 -0.859 0.379 13:28:50.581 [planetinfo.record]: sun_distance = 688800 13:28:50.581 [planetinfo.record]: corona_flare = 0.028903 13:28:50.581 [planetinfo.record]: corona_hues = 0.946739 13:28:50.581 [planetinfo.record]: sun_color = 0.324483, 0.351182, 0.669, 1 13:28:50.581 [planetinfo.record]: corona_shimmer = 0.530295 13:28:50.581 [planetinfo.record]: station_vector = -0.614 0.362 0.701 13:28:50.581 [planetinfo.record]: station = coriolis 13:28:55.339 [PLANETINFO OVER]: Done 13:28:55.340 [PLANETINFO LOGGING]: 4 193 13:28:56.358 [planetinfo.record]: seed = 40 36 27 54 13:28:56.358 [planetinfo.record]: coordinates = 36 204 13:28:56.358 [planetinfo.record]: government = 5; 13:28:56.358 [planetinfo.record]: economy = 4; 13:28:56.358 [planetinfo.record]: techlevel = 6; 13:28:56.358 [planetinfo.record]: population = 34; 13:28:56.358 [planetinfo.record]: productivity = 14688; 13:28:56.358 [planetinfo.record]: name = "Vebi"; 13:28:56.358 [planetinfo.record]: inhabitant = "Human Colonial"; 13:28:56.358 [planetinfo.record]: inhabitants = "Human Colonials"; 13:28:56.358 [planetinfo.record]: description = "The planet Vebi is scourged by deadly edible arts graduates."; 13:28:56.379 [planetinfo.record]: air_color = 0.655357, 0.885182, 0.868794, 1; 13:28:56.379 [planetinfo.record]: cloud_alpha = 1.000000; 13:28:56.379 [planetinfo.record]: cloud_color = 0.658203, 0.634179, 0.539932, 1; 13:28:56.379 [planetinfo.record]: cloud_fraction = 0.200000; 13:28:56.379 [planetinfo.record]: land_color = 0.587891, 0.522657, 0.505219, 1; 13:28:56.379 [planetinfo.record]: land_fraction = 0.660000; 13:28:56.379 [planetinfo.record]: polar_cloud_color = 0.796191, 0.778029, 0.706775, 1; 13:28:56.379 [planetinfo.record]: polar_land_color = 0.941211, 0.915101, 0.908121, 1; 13:28:56.379 [planetinfo.record]: polar_sea_color = 0.807872, 0.90918, 0.79642, 1; 13:28:56.379 [planetinfo.record]: sea_color = 0.503409, 0.908203, 0.457649, 1; 13:28:56.379 [planetinfo.record]: rotation_speed = 0.004756 13:28:56.379 [planetinfo.record]: planet zpos = 482680.000000 13:28:56.379 [planetinfo.record]: sun_radius = 130353.114014 13:28:56.379 [planetinfo.record]: sun_vector = -0.832 0.227 0.505 13:28:56.379 [planetinfo.record]: sun_distance = 789840 13:28:56.379 [planetinfo.record]: corona_flare = 0.035431 13:28:56.379 [planetinfo.record]: corona_hues = 0.982758 13:28:56.379 [planetinfo.record]: sun_color = 0.661173, 0.350537, 0.223313, 1 13:28:56.379 [planetinfo.record]: corona_shimmer = 0.441053 13:28:56.380 [planetinfo.record]: station_vector = 0.212 0.899 0.384 13:28:56.380 [planetinfo.record]: station = coriolis 13:29:01.405 [PLANETINFO OVER]: Done 13:29:01.406 [PLANETINFO LOGGING]: 4 194 13:29:02.411 [planetinfo.record]: seed = 16 221 5 132 13:29:02.411 [planetinfo.record]: coordinates = 221 128 13:29:02.411 [planetinfo.record]: government = 2; 13:29:02.411 [planetinfo.record]: economy = 0; 13:29:02.411 [planetinfo.record]: techlevel = 9; 13:29:02.411 [planetinfo.record]: population = 39; 13:29:02.411 [planetinfo.record]: productivity = 18720; 13:29:02.411 [planetinfo.record]: name = "Zalexe"; 13:29:02.411 [planetinfo.record]: inhabitant = "Human Colonial"; 13:29:02.411 [planetinfo.record]: inhabitants = "Human Colonials"; 13:29:02.412 [planetinfo.record]: description = "The planet Zalexe is reasonably noted for its inhabitants’ exceptional love for tourists and its unusual oceans."; 13:29:02.417 [planetinfo.record]: air_color = 0.703237, 0.828534, 0.900791, 1; 13:29:02.417 [planetinfo.record]: cloud_alpha = 1.000000; 13:29:02.417 [planetinfo.record]: cloud_color = 0.663765, 0.705078, 0.668284, 1; 13:29:02.418 [planetinfo.record]: cloud_fraction = 0.280000; 13:29:02.418 [planetinfo.record]: land_color = 0.175781, 0.5, 0.203644, 1; 13:29:02.418 [planetinfo.record]: land_fraction = 0.250000; 13:29:02.418 [planetinfo.record]: polar_cloud_color = 0.787355, 0.817285, 0.790629, 1; 13:29:02.418 [planetinfo.record]: polar_land_color = 0.795996, 0.95, 0.809231, 1; 13:29:02.418 [planetinfo.record]: polar_sea_color = 0.932031, 0.874889, 0.857396, 1; 13:29:02.418 [planetinfo.record]: sea_color = 0.679688, 0.513001, 0.461975, 1; 13:29:02.418 [planetinfo.record]: rotation_speed = 0.001304 13:29:02.418 [planetinfo.record]: planet zpos = 406100.000000 13:29:02.418 [planetinfo.record]: sun_radius = 71741.069794 13:29:02.418 [planetinfo.record]: sun_vector = 0.459 -0.741 -0.491 13:29:02.418 [planetinfo.record]: sun_distance = 934030 13:29:02.418 [planetinfo.record]: corona_flare = 0.020721 13:29:02.418 [planetinfo.record]: corona_hues = 0.632660 13:29:02.418 [planetinfo.record]: sun_color = 0.741919, 0.737579, 0.737003, 1 13:29:02.419 [planetinfo.record]: corona_shimmer = 0.727919 13:29:02.419 [planetinfo.record]: station_vector = 0.947 0.318 0.043 13:29:02.419 [planetinfo.record]: station = coriolis 13:29:07.252 [PLANETINFO OVER]: Done 13:29:07.253 [PLANETINFO LOGGING]: 4 195 13:29:08.259 [planetinfo.record]: seed = 124 168 243 204 13:29:08.259 [planetinfo.record]: coordinates = 168 66 13:29:08.259 [planetinfo.record]: government = 7; 13:29:08.259 [planetinfo.record]: economy = 2; 13:29:08.259 [planetinfo.record]: techlevel = 9; 13:29:08.259 [planetinfo.record]: population = 46; 13:29:08.259 [planetinfo.record]: productivity = 32384; 13:29:08.259 [planetinfo.record]: name = "Ineddien"; 13:29:08.259 [planetinfo.record]: inhabitant = "Horned Lizard"; 13:29:08.259 [planetinfo.record]: inhabitants = "Horned Lizards"; 13:29:08.259 [planetinfo.record]: description = "The world Ineddien is most famous for the Ineddienian spotted cat."; 13:29:08.275 [planetinfo.record]: air_color = 0.642624, 0.885507, 0.924855, 1; 13:29:08.275 [planetinfo.record]: cloud_alpha = 1.000000; 13:29:08.275 [planetinfo.record]: cloud_color = 0.671659, 0.777344, 0.531387, 1; 13:29:08.276 [planetinfo.record]: cloud_fraction = 0.310000; 13:29:08.276 [planetinfo.record]: land_color = 0.573995, 0.502734, 0.66, 1; 13:29:08.276 [planetinfo.record]: land_fraction = 0.450000; 13:29:08.276 [planetinfo.record]: polar_cloud_color = 0.777595, 0.849805, 0.681753, 1; 13:29:08.276 [planetinfo.record]: polar_land_color = 0.903573, 0.878361, 0.934, 1; 13:29:08.276 [planetinfo.record]: polar_sea_color = 0.93457, 0.881921, 0.869771, 1; 13:29:08.276 [planetinfo.record]: sea_color = 0.654297, 0.506856, 0.472832, 1; 13:29:08.276 [planetinfo.record]: rotation_speed = 0.003439 13:29:08.276 [planetinfo.record]: planet zpos = 605600.000000 13:29:08.276 [planetinfo.record]: sun_radius = 110442.344971 13:29:08.276 [planetinfo.record]: sun_vector = -0.156 -0.116 0.981 13:29:08.276 [planetinfo.record]: sun_distance = 1150640 13:29:08.276 [planetinfo.record]: corona_flare = 0.074471 13:29:08.276 [planetinfo.record]: corona_hues = 0.943169 13:29:08.276 [planetinfo.record]: sun_color = 0.758536, 0.351554, 0.297848, 1 13:29:08.277 [planetinfo.record]: corona_shimmer = 0.431584 13:29:08.277 [planetinfo.record]: station_vector = 0.782 0.368 0.503 13:29:08.277 [planetinfo.record]: station = coriolis 13:29:13.102 [PLANETINFO OVER]: Done 13:29:13.102 [PLANETINFO LOGGING]: 4 196 13:29:14.122 [planetinfo.record]: seed = 204 178 165 152 13:29:14.122 [planetinfo.record]: coordinates = 178 45 13:29:14.122 [planetinfo.record]: government = 1; 13:29:14.122 [planetinfo.record]: economy = 7; 13:29:14.122 [planetinfo.record]: techlevel = 3; 13:29:14.122 [planetinfo.record]: population = 21; 13:29:14.122 [planetinfo.record]: productivity = 2520; 13:29:14.122 [planetinfo.record]: name = "Edorza"; 13:29:14.122 [planetinfo.record]: inhabitant = "Black Insect"; 13:29:14.122 [planetinfo.record]: inhabitants = "Black Insects"; 13:29:14.122 [planetinfo.record]: description = "The planet Edorza is a boring world."; 13:29:14.136 [planetinfo.record]: air_color = 0.608854, 0.922904, 0.893157, 1; 13:29:14.136 [planetinfo.record]: cloud_alpha = 1.000000; 13:29:14.136 [planetinfo.record]: cloud_color = 0.771484, 0.682489, 0.446014, 1; 13:29:14.136 [planetinfo.record]: cloud_fraction = 0.510000; 13:29:14.136 [planetinfo.record]: land_color = 0.420234, 0.180469, 0.66, 1; 13:29:14.136 [planetinfo.record]: land_fraction = 0.510000; 13:29:14.136 [planetinfo.record]: polar_cloud_color = 0.847168, 0.786089, 0.623794, 1; 13:29:14.136 [planetinfo.record]: polar_land_color = 0.849174, 0.764348, 0.934, 1; 13:29:14.136 [planetinfo.record]: polar_sea_color = 0.93125, 0.927385, 0.869409, 1; 13:29:14.136 [planetinfo.record]: sea_color = 0.6875, 0.676086, 0.504883, 1; 13:29:14.136 [planetinfo.record]: rotation_speed = 0.002401 13:29:14.136 [planetinfo.record]: planet zpos = 705880.000000 13:29:14.136 [planetinfo.record]: sun_radius = 134577.454834 13:29:14.136 [planetinfo.record]: sun_vector = 0.039 0.894 -0.446 13:29:14.136 [planetinfo.record]: sun_distance = 1058820 13:29:14.136 [planetinfo.record]: corona_flare = 0.020906 13:29:14.136 [planetinfo.record]: corona_hues = 0.527473 13:29:14.136 [planetinfo.record]: sun_color = 0.346602, 0.660906, 0.843463, 1 13:29:14.136 [planetinfo.record]: corona_shimmer = 0.699424 13:29:14.136 [planetinfo.record]: station_vector = -0.767 0.531 0.361 13:29:14.136 [planetinfo.record]: station = coriolis 13:29:19.683 [PLANETINFO OVER]: Done 13:29:19.684 [PLANETINFO LOGGING]: 4 197 13:29:20.704 [planetinfo.record]: seed = 64 214 219 19 13:29:20.704 [planetinfo.record]: coordinates = 214 196 13:29:20.704 [planetinfo.record]: government = 0; 13:29:20.704 [planetinfo.record]: economy = 6; 13:29:20.704 [planetinfo.record]: techlevel = 3; 13:29:20.704 [planetinfo.record]: population = 19; 13:29:20.704 [planetinfo.record]: productivity = 2432; 13:29:20.704 [planetinfo.record]: name = "Bereed"; 13:29:20.704 [planetinfo.record]: inhabitant = "Green Horned Humanoid"; 13:29:20.704 [planetinfo.record]: inhabitants = "Green Horned Humanoids"; 13:29:20.704 [planetinfo.record]: description = "This world is reasonably well known for its great tropical forests but cursed by dreadful solar activity."; 13:29:20.720 [planetinfo.record]: air_color = 0.570038, 0.576775, 0.834451, 1; 13:29:20.720 [planetinfo.record]: cloud_alpha = 1.000000; 13:29:20.720 [planetinfo.record]: cloud_color = 0.328018, 0.339133, 0.505859, 1; 13:29:20.720 [planetinfo.record]: cloud_fraction = 0.330000; 13:29:20.720 [planetinfo.record]: land_color = 0.654297, 0.558972, 0.495834, 1; 13:29:20.720 [planetinfo.record]: land_fraction = 0.660000; 13:29:20.720 [planetinfo.record]: polar_cloud_color = 0.567756, 0.577748, 0.727637, 1; 13:29:20.721 [planetinfo.record]: polar_land_color = 0.93457, 0.900531, 0.877985, 1; 13:29:20.721 [planetinfo.record]: polar_sea_color = 0.728429, 0.863125, 0.928906, 1; 13:29:20.721 [planetinfo.record]: sea_color = 0.0971985, 0.509554, 0.710938, 1; 13:29:20.721 [planetinfo.record]: rotation_speed = 0.003205 13:29:20.721 [planetinfo.record]: planet zpos = 455760.000000 13:29:20.721 [planetinfo.record]: sun_radius = 115451.990662 13:29:20.721 [planetinfo.record]: sun_vector = -0.769 0.227 0.597 13:29:20.721 [planetinfo.record]: sun_distance = 759600 13:29:20.721 [planetinfo.record]: corona_flare = 0.046429 13:29:20.721 [planetinfo.record]: corona_hues = 0.637604 13:29:20.721 [planetinfo.record]: sun_color = 0.807324, 0.739995, 0.663351, 1 13:29:20.721 [planetinfo.record]: corona_shimmer = 0.272144 13:29:20.721 [planetinfo.record]: station_vector = -0.921 0.354 0.163 13:29:20.721 [planetinfo.record]: station = coriolis 13:29:25.703 [PLANETINFO OVER]: Done 13:29:25.705 [PLANETINFO LOGGING]: 4 198 13:29:26.713 [planetinfo.record]: seed = 56 91 149 162 13:29:26.713 [planetinfo.record]: coordinates = 91 152 13:29:26.713 [planetinfo.record]: government = 7; 13:29:26.713 [planetinfo.record]: economy = 0; 13:29:26.713 [planetinfo.record]: techlevel = 14; 13:29:26.713 [planetinfo.record]: population = 64; 13:29:26.713 [planetinfo.record]: productivity = 56320; 13:29:26.713 [planetinfo.record]: name = "Xevera"; 13:29:26.713 [planetinfo.record]: inhabitant = "Large Harmless Bony Humanoid"; 13:29:26.713 [planetinfo.record]: inhabitants = "Large Harmless Bony Humanoids"; 13:29:26.713 [planetinfo.record]: description = "This world is mildly famous for its vast rain forests and the Xeveraian tree wolf."; 13:29:26.728 [planetinfo.record]: air_color = 0.672751, 0.804175, 0.952172, 1; 13:29:26.728 [planetinfo.record]: cloud_alpha = 1.000000; 13:29:26.728 [planetinfo.record]: cloud_color = 0.617676, 0.859375, 0.763073, 1; 13:29:26.728 [planetinfo.record]: cloud_fraction = 0.500000; 13:29:26.728 [planetinfo.record]: land_color = 0.60167, 0.66, 0.193359, 1; 13:29:26.728 [planetinfo.record]: land_fraction = 0.380000; 13:29:26.728 [planetinfo.record]: polar_cloud_color = 0.73085, 0.886719, 0.824615, 1; 13:29:26.728 [planetinfo.record]: polar_land_color = 0.913364, 0.934, 0.768908, 1; 13:29:26.728 [planetinfo.record]: polar_sea_color = 0.940039, 0.935739, 0.890008, 1; 13:29:26.728 [planetinfo.record]: sea_color = 0.599609, 0.588639, 0.471958, 1; 13:29:26.728 [planetinfo.record]: rotation_speed = 0.004736 13:29:26.728 [planetinfo.record]: planet zpos = 444470.000000 13:29:26.728 [planetinfo.record]: sun_radius = 82688.089294 13:29:26.728 [planetinfo.record]: sun_vector = -0.187 0.633 -0.751 13:29:26.728 [planetinfo.record]: sun_distance = 581230 13:29:26.728 [planetinfo.record]: corona_flare = 0.050504 13:29:26.729 [planetinfo.record]: corona_hues = 0.597870 13:29:26.729 [planetinfo.record]: sun_color = 0.745322, 0.640157, 0.456708, 1 13:29:26.729 [planetinfo.record]: corona_shimmer = 0.370641 13:29:26.729 [planetinfo.record]: station_vector = 0.929 -0.365 0.059 13:29:26.729 [planetinfo.record]: station = dodecahedron 13:29:31.633 [PLANETINFO OVER]: Done 13:29:31.633 [PLANETINFO LOGGING]: 4 199 13:29:32.638 [planetinfo.record]: seed = 116 205 83 248 13:29:32.638 [planetinfo.record]: coordinates = 205 148 13:29:32.638 [planetinfo.record]: government = 6; 13:29:32.638 [planetinfo.record]: economy = 4; 13:29:32.638 [planetinfo.record]: techlevel = 7; 13:29:32.638 [planetinfo.record]: population = 39; 13:29:32.638 [planetinfo.record]: productivity = 18720; 13:29:32.638 [planetinfo.record]: name = "Edquonen"; 13:29:32.638 [planetinfo.record]: inhabitant = "Human Colonial"; 13:29:32.638 [planetinfo.record]: inhabitants = "Human Colonials"; 13:29:32.638 [planetinfo.record]: description = "This planet is plagued by occasional solar activity."; 13:29:32.650 [planetinfo.record]: air_color = 0.450479, 0.723744, 0.861117, 1; 13:29:32.650 [planetinfo.record]: cloud_alpha = 1.000000; 13:29:32.650 [planetinfo.record]: cloud_color = 0.0984192, 0.585938, 0.106037, 1; 13:29:32.650 [planetinfo.record]: cloud_fraction = 0.340000; 13:29:32.650 [planetinfo.record]: land_color = 0.532959, 0.316269, 0.642578, 1; 13:29:32.650 [planetinfo.record]: land_fraction = 0.300000; 13:29:32.650 [planetinfo.record]: polar_cloud_color = 0.366548, 0.763672, 0.372753, 1; 13:29:32.650 [planetinfo.record]: polar_land_color = 0.895834, 0.816947, 0.935742, 1; 13:29:32.650 [planetinfo.record]: polar_sea_color = 0.923242, 0.878669, 0.821361, 1; 13:29:32.650 [planetinfo.record]: sea_color = 0.767578, 0.619347, 0.428764, 1; 13:29:32.650 [planetinfo.record]: rotation_speed = 0.004402 13:29:32.651 [planetinfo.record]: planet zpos = 456210.000000 13:29:32.651 [planetinfo.record]: sun_radius = 154649.515839 13:29:32.651 [planetinfo.record]: sun_vector = 0.202 0.961 -0.191 13:29:32.651 [planetinfo.record]: sun_distance = 1165870 13:29:32.651 [planetinfo.record]: corona_flare = 0.006636 13:29:32.651 [planetinfo.record]: corona_hues = 0.515198 13:29:32.651 [planetinfo.record]: sun_color = 0.768982, 0.621021, 0.363429, 1 13:29:32.651 [planetinfo.record]: corona_shimmer = 0.401692 13:29:32.651 [planetinfo.record]: station_vector = 0.934 0.293 0.204 13:29:32.651 [planetinfo.record]: station = coriolis 13:29:37.446 [PLANETINFO OVER]: Done 13:29:37.447 [PLANETINFO LOGGING]: 4 200 13:29:38.449 [planetinfo.record]: seed = 84 114 85 236 13:29:38.449 [planetinfo.record]: coordinates = 114 31 13:29:38.449 [planetinfo.record]: government = 2; 13:29:38.449 [planetinfo.record]: economy = 7; 13:29:38.449 [planetinfo.record]: techlevel = 3; 13:29:38.449 [planetinfo.record]: population = 22; 13:29:38.449 [planetinfo.record]: productivity = 3168; 13:29:38.449 [planetinfo.record]: name = "Inriisus"; 13:29:38.449 [planetinfo.record]: inhabitant = "Human Colonial"; 13:29:38.449 [planetinfo.record]: inhabitants = "Human Colonials"; 13:29:38.449 [planetinfo.record]: description = "The planet Inriisus is an unremarkable planet."; 13:29:38.451 [planetinfo.record]: air_color = 0.610214, 0.560389, 0.868271, 1; 13:29:38.452 [planetinfo.record]: cloud_alpha = 1.000000; 13:29:38.452 [planetinfo.record]: cloud_color = 0.466244, 0.325066, 0.607422, 1; 13:29:38.452 [planetinfo.record]: cloud_fraction = 0.360000; 13:29:38.452 [planetinfo.record]: land_color = 0.580078, 0.658127, 0.66, 1; 13:29:38.452 [planetinfo.record]: land_fraction = 0.700000; 13:29:38.452 [planetinfo.record]: polar_cloud_color = 0.661002, 0.548663, 0.77334, 1; 13:29:38.452 [planetinfo.record]: polar_land_color = 0.905725, 0.933337, 0.934, 1; 13:29:38.452 [planetinfo.record]: polar_sea_color = 0.777498, 0.94668, 0.796002, 1; 13:29:38.452 [planetinfo.record]: sea_color = 0.152046, 0.533203, 0.193735, 1; 13:29:38.452 [planetinfo.record]: rotation_speed = 0.003367 13:29:38.452 [planetinfo.record]: planet zpos = 660220.000000 13:29:38.452 [planetinfo.record]: sun_radius = 111975.178833 13:29:38.452 [planetinfo.record]: sun_vector = -0.261 -0.309 0.914 13:29:38.452 [planetinfo.record]: sun_distance = 1140380 13:29:38.452 [planetinfo.record]: corona_flare = 0.054033 13:29:38.453 [planetinfo.record]: corona_hues = 0.907570 13:29:38.453 [planetinfo.record]: sun_color = 0.75777, 0.632452, 0.576343, 1 13:29:38.453 [planetinfo.record]: corona_shimmer = 0.694635 13:29:38.453 [planetinfo.record]: station_vector = 0.360 -0.907 0.218 13:29:38.453 [planetinfo.record]: station = coriolis 13:29:43.057 [PLANETINFO OVER]: Done 13:29:43.058 [PLANETINFO LOGGING]: 4 201 13:29:44.062 [planetinfo.record]: seed = 24 72 219 163 13:29:44.062 [planetinfo.record]: coordinates = 72 221 13:29:44.062 [planetinfo.record]: government = 3; 13:29:44.062 [planetinfo.record]: economy = 5; 13:29:44.062 [planetinfo.record]: techlevel = 4; 13:29:44.062 [planetinfo.record]: population = 25; 13:29:44.062 [planetinfo.record]: productivity = 7000; 13:29:44.062 [planetinfo.record]: name = "Geesla"; 13:29:44.062 [planetinfo.record]: inhabitant = "Large Harmless Furry Rodent"; 13:29:44.062 [planetinfo.record]: inhabitants = "Large Harmless Furry Rodents"; 13:29:44.062 [planetinfo.record]: description = "This world is very notable for its inhabitants’ ingrained silliness and its weird volcanoes."; 13:29:44.072 [planetinfo.record]: air_color = 0.655631, 0.849158, 0.936563, 1; 13:29:44.072 [planetinfo.record]: cloud_alpha = 1.000000; 13:29:44.072 [planetinfo.record]: cloud_color = 0.58148, 0.8125, 0.568115, 1; 13:29:44.072 [planetinfo.record]: cloud_fraction = 0.180000; 13:29:44.072 [planetinfo.record]: land_color = 0.608538, 0.66, 0.569766, 1; 13:29:44.072 [planetinfo.record]: land_fraction = 0.680000; 13:29:44.072 [planetinfo.record]: polar_cloud_color = 0.711797, 0.865625, 0.702898, 1; 13:29:44.072 [planetinfo.record]: polar_land_color = 0.915793, 0.934, 0.902076, 1; 13:29:44.072 [planetinfo.record]: polar_sea_color = 0.7091, 0.880383, 0.926172, 1; 13:29:44.072 [planetinfo.record]: sea_color = 0.0461426, 0.592283, 0.738281, 1; 13:29:44.072 [planetinfo.record]: rotation_speed = 0.001687 13:29:44.072 [planetinfo.record]: planet zpos = 511840.000000 13:29:44.072 [planetinfo.record]: sun_radius = 88819.891357 13:29:44.072 [planetinfo.record]: sun_vector = 0.631 0.751 -0.197 13:29:44.072 [planetinfo.record]: sun_distance = 694640 13:29:44.072 [planetinfo.record]: corona_flare = 0.022256 13:29:44.072 [planetinfo.record]: corona_hues = 0.765694 13:29:44.073 [planetinfo.record]: sun_color = 0.467256, 0.603555, 0.790421, 1 13:29:44.073 [planetinfo.record]: corona_shimmer = 0.400802 13:29:44.073 [planetinfo.record]: station_vector = -0.158 0.954 0.256 13:29:44.073 [planetinfo.record]: station = coriolis 13:29:49.251 [PLANETINFO OVER]: Done 13:29:49.251 [PLANETINFO LOGGING]: 4 202 13:29:50.255 [planetinfo.record]: seed = 32 34 101 160 13:29:50.255 [planetinfo.record]: coordinates = 34 181 13:29:50.255 [planetinfo.record]: government = 4; 13:29:50.255 [planetinfo.record]: economy = 5; 13:29:50.255 [planetinfo.record]: techlevel = 6; 13:29:50.255 [planetinfo.record]: population = 34; 13:29:50.255 [planetinfo.record]: productivity = 10880; 13:29:50.255 [planetinfo.record]: name = "Tiqu"; 13:29:50.255 [planetinfo.record]: inhabitant = "Human Colonial"; 13:29:50.255 [planetinfo.record]: inhabitants = "Human Colonials"; 13:29:50.255 [planetinfo.record]: description = "This planet is a tedious place."; 13:29:50.267 [planetinfo.record]: air_color = 0.473784, 0.687472, 0.852662, 1; 13:29:50.267 [planetinfo.record]: cloud_alpha = 1.000000; 13:29:50.267 [planetinfo.record]: cloud_color = 0.148895, 0.560547, 0.280752, 1; 13:29:50.267 [planetinfo.record]: cloud_fraction = 0.400000; 13:29:50.267 [planetinfo.record]: land_color = 0.616575, 0.66, 0.461484, 1; 13:29:50.267 [planetinfo.record]: land_fraction = 0.260000; 13:29:50.267 [planetinfo.record]: polar_cloud_color = 0.406977, 0.752246, 0.517571, 1; 13:29:50.267 [planetinfo.record]: polar_land_color = 0.918637, 0.934, 0.863768, 1; 13:29:50.267 [planetinfo.record]: polar_sea_color = 0.932476, 0.868918, 0.943555, 1; 13:29:50.268 [planetinfo.record]: sea_color = 0.537943, 0.385857, 0.564453, 1; 13:29:50.268 [planetinfo.record]: rotation_speed = 0.003164 13:29:50.268 [planetinfo.record]: planet zpos = 313500.000000 13:29:50.268 [planetinfo.record]: sun_radius = 94034.866333 13:29:50.268 [planetinfo.record]: sun_vector = 0.495 0.804 0.331 13:29:50.268 [planetinfo.record]: sun_distance = 513000 13:29:50.268 [planetinfo.record]: corona_flare = 0.028304 13:29:50.268 [planetinfo.record]: corona_hues = 0.890831 13:29:50.268 [planetinfo.record]: sun_color = 0.783459, 0.620417, 0.486237, 1 13:29:50.269 [planetinfo.record]: corona_shimmer = 0.360623 13:29:50.269 [planetinfo.record]: station_vector = -0.822 -0.479 0.308 13:29:50.269 [planetinfo.record]: station = coriolis 13:29:55.641 [PLANETINFO OVER]: Done 13:29:55.642 [PLANETINFO LOGGING]: 4 203 13:29:56.644 [planetinfo.record]: seed = 44 82 243 3 13:29:56.645 [planetinfo.record]: coordinates = 82 58 13:29:56.645 [planetinfo.record]: government = 5; 13:29:56.645 [planetinfo.record]: economy = 2; 13:29:56.645 [planetinfo.record]: techlevel = 10; 13:29:56.645 [planetinfo.record]: population = 48; 13:29:56.645 [planetinfo.record]: productivity = 27648; 13:29:56.645 [planetinfo.record]: name = "Geerbi"; 13:29:56.645 [planetinfo.record]: inhabitant = "Large Green Slimy Lobster"; 13:29:56.645 [planetinfo.record]: inhabitants = "Large Green Slimy Lobsters"; 13:29:56.645 [planetinfo.record]: description = "The planet Geerbi is famous for Geerbiian wolf cutlet but plagued by vicious disease."; 13:29:56.649 [planetinfo.record]: air_color = 0.734008, 0.62791, 0.848109, 1; 13:29:56.650 [planetinfo.record]: cloud_alpha = 1.000000; 13:29:56.650 [planetinfo.record]: cloud_color = 0.546875, 0.448608, 0.50158, 1; 13:29:56.650 [planetinfo.record]: cloud_fraction = 0.110000; 13:29:56.650 [planetinfo.record]: land_color = 0.404766, 0.642054, 0.66, 1; 13:29:56.650 [planetinfo.record]: land_fraction = 0.620000; 13:29:56.650 [planetinfo.record]: polar_cloud_color = 0.746094, 0.662304, 0.707472, 1; 13:29:56.650 [planetinfo.record]: polar_land_color = 0.843701, 0.927651, 0.934, 1; 13:29:56.650 [planetinfo.record]: polar_sea_color = 0.942578, 0.902066, 0.887993, 1; 13:29:56.650 [planetinfo.record]: sea_color = 0.574219, 0.475499, 0.441206, 1; 13:29:56.650 [planetinfo.record]: rotation_speed = 0.000209 13:29:56.650 [planetinfo.record]: planet zpos = 549900.000000 13:29:56.650 [planetinfo.record]: sun_radius = 67350.219727 13:29:56.650 [planetinfo.record]: sun_vector = -0.019 0.977 -0.211 13:29:56.650 [planetinfo.record]: sun_distance = 733200 13:29:56.650 [planetinfo.record]: corona_flare = 0.099931 13:29:56.650 [planetinfo.record]: corona_hues = 0.661041 13:29:56.650 [planetinfo.record]: sun_color = 0.366062, 0.396586, 0.66012, 1 13:29:56.651 [planetinfo.record]: corona_shimmer = 0.491916 13:29:56.651 [planetinfo.record]: station_vector = 0.973 -0.195 0.120 13:29:56.651 [planetinfo.record]: station = coriolis 13:30:01.133 [PLANETINFO OVER]: Done 13:30:01.134 [PLANETINFO LOGGING]: 4 204 13:30:02.153 [planetinfo.record]: seed = 156 122 69 241 13:30:02.153 [planetinfo.record]: coordinates = 122 230 13:30:02.153 [planetinfo.record]: government = 3; 13:30:02.153 [planetinfo.record]: economy = 6; 13:30:02.153 [planetinfo.record]: techlevel = 5; 13:30:02.153 [planetinfo.record]: population = 30; 13:30:02.153 [planetinfo.record]: productivity = 6720; 13:30:02.153 [planetinfo.record]: name = "Atenrile"; 13:30:02.153 [planetinfo.record]: inhabitant = "Human Colonial"; 13:30:02.153 [planetinfo.record]: inhabitants = "Human Colonials"; 13:30:02.153 [planetinfo.record]: description = "The world Atenrile is a boring world."; 13:30:02.196 [planetinfo.record]: air_color = 0.738371, 0.636834, 0.840955, 1; 13:30:02.196 [planetinfo.record]: cloud_alpha = 1.000000; 13:30:02.196 [planetinfo.record]: cloud_color = 0.525391, 0.457664, 0.490998, 1; 13:30:02.196 [planetinfo.record]: cloud_fraction = 0.500000; 13:30:02.196 [planetinfo.record]: land_color = 0.232582, 0.239171, 0.654297, 1; 13:30:02.196 [planetinfo.record]: land_fraction = 0.350000; 13:30:02.196 [planetinfo.record]: polar_cloud_color = 0.736426, 0.677095, 0.706297, 1; 13:30:02.196 [planetinfo.record]: polar_land_color = 0.78398, 0.786333, 0.93457, 1; 13:30:02.196 [planetinfo.record]: polar_sea_color = 0.914651, 0.935938, 0.886399, 1; 13:30:02.196 [planetinfo.record]: sea_color = 0.582345, 0.640625, 0.504993, 1; 13:30:02.196 [planetinfo.record]: rotation_speed = 0.004308 13:30:02.197 [planetinfo.record]: planet zpos = 383280.000000 13:30:02.197 [planetinfo.record]: sun_radius = 74314.987488 13:30:02.197 [planetinfo.record]: sun_vector = 0.830 -0.339 -0.444 13:30:02.197 [planetinfo.record]: sun_distance = 606860 13:30:02.197 [planetinfo.record]: corona_flare = 0.016010 13:30:02.197 [planetinfo.record]: corona_hues = 0.978989 13:30:02.197 [planetinfo.record]: sun_color = 0.756741, 0.477593, 0.310757, 1 13:30:02.197 [planetinfo.record]: corona_shimmer = 0.272085 13:30:02.198 [planetinfo.record]: station_vector = -0.685 0.502 0.528 13:30:02.198 [planetinfo.record]: station = coriolis 13:30:06.823 [PLANETINFO OVER]: Done 13:30:06.823 [PLANETINFO LOGGING]: 4 205 13:30:07.826 [planetinfo.record]: seed = 176 1 27 18 13:30:07.826 [planetinfo.record]: coordinates = 1 190 13:30:07.826 [planetinfo.record]: government = 6; 13:30:07.826 [planetinfo.record]: economy = 6; 13:30:07.826 [planetinfo.record]: techlevel = 5; 13:30:07.826 [planetinfo.record]: population = 33; 13:30:07.826 [planetinfo.record]: productivity = 10560; 13:30:07.826 [planetinfo.record]: name = "Enatce"; 13:30:07.826 [planetinfo.record]: inhabitant = "Human Colonial"; 13:30:07.826 [planetinfo.record]: inhabitants = "Human Colonials"; 13:30:07.826 [planetinfo.record]: description = "Enatce is very notable for its inhabitants’ ingrained shyness."; 13:30:07.844 [planetinfo.record]: air_color = 0.517003, 0.983391, 0.902814, 1; 13:30:07.844 [planetinfo.record]: cloud_alpha = 1.000000; 13:30:07.844 [planetinfo.record]: cloud_color = 0.953125, 0.554196, 0.167542, 1; 13:30:07.844 [planetinfo.record]: cloud_fraction = 0.550000; 13:30:07.844 [planetinfo.record]: land_color = 0.305064, 0.278778, 0.615234, 1; 13:30:07.844 [planetinfo.record]: land_fraction = 0.430000; 13:30:07.844 [planetinfo.record]: polar_cloud_color = 0.928906, 0.685911, 0.450393, 1; 13:30:07.844 [planetinfo.record]: polar_land_color = 0.820193, 0.810169, 0.938477, 1; 13:30:07.844 [planetinfo.record]: polar_sea_color = 0.926467, 0.936523, 0.885033, 1; 13:30:07.844 [planetinfo.record]: sea_color = 0.6075, 0.634766, 0.495167, 1; 13:30:07.844 [planetinfo.record]: rotation_speed = 0.000047 13:30:07.844 [planetinfo.record]: planet zpos = 332900.000000 13:30:07.844 [planetinfo.record]: sun_radius = 74494.096069 13:30:07.844 [planetinfo.record]: sun_vector = 0.154 0.965 0.214 13:30:07.844 [planetinfo.record]: sun_distance = 599220 13:30:07.844 [planetinfo.record]: corona_flare = 0.098817 13:30:07.844 [planetinfo.record]: corona_hues = 0.520645 13:30:07.844 [planetinfo.record]: sun_color = 0.610486, 0.668608, 0.800781, 1 13:30:07.844 [planetinfo.record]: corona_shimmer = 0.418295 13:30:07.845 [planetinfo.record]: station_vector = 0.704 -0.572 0.421 13:30:07.845 [planetinfo.record]: station = coriolis 13:30:12.400 [PLANETINFO OVER]: Done 13:30:12.401 [PLANETINFO LOGGING]: 4 206 13:30:13.403 [planetinfo.record]: seed = 200 201 117 129 13:30:13.403 [planetinfo.record]: coordinates = 201 229 13:30:13.403 [planetinfo.record]: government = 1; 13:30:13.403 [planetinfo.record]: economy = 7; 13:30:13.403 [planetinfo.record]: techlevel = 2; 13:30:13.403 [planetinfo.record]: population = 17; 13:30:13.403 [planetinfo.record]: productivity = 2040; 13:30:13.403 [planetinfo.record]: name = "Leerte"; 13:30:13.403 [planetinfo.record]: inhabitant = "Human Colonial"; 13:30:13.403 [planetinfo.record]: inhabitants = "Human Colonials"; 13:30:13.403 [planetinfo.record]: description = "The planet Leerte is mildly well known for its exotic cuisine."; 13:30:13.436 [planetinfo.record]: air_color = 0.673681, 0.841475, 0.922904, 1; 13:30:13.436 [planetinfo.record]: cloud_alpha = 1.000000; 13:30:13.436 [planetinfo.record]: cloud_color = 0.610021, 0.771484, 0.608749, 1; 13:30:13.436 [planetinfo.record]: cloud_fraction = 0.650000; 13:30:13.436 [planetinfo.record]: land_color = 0.66, 0.631641, 0.655569, 1; 13:30:13.436 [planetinfo.record]: land_fraction = 0.510000; 13:30:13.436 [planetinfo.record]: polar_cloud_color = 0.736353, 0.847168, 0.735481, 1; 13:30:13.436 [planetinfo.record]: polar_land_color = 0.934, 0.923967, 0.932432, 1; 13:30:13.436 [planetinfo.record]: polar_sea_color = 0.9375, 0.925013, 0.882385, 1; 13:30:13.436 [planetinfo.record]: sea_color = 0.625, 0.591702, 0.478027, 1; 13:30:13.436 [planetinfo.record]: rotation_speed = 0.001612 13:30:13.436 [planetinfo.record]: planet zpos = 392760.000000 13:30:13.436 [planetinfo.record]: sun_radius = 75681.632538 13:30:13.436 [planetinfo.record]: sun_vector = 0.099 0.835 -0.542 13:30:13.436 [planetinfo.record]: sun_distance = 687330 13:30:13.436 [planetinfo.record]: corona_flare = 0.037889 13:30:13.436 [planetinfo.record]: corona_hues = 0.890144 13:30:13.436 [planetinfo.record]: sun_color = 0.409016, 0.72386, 0.770776, 1 13:30:13.436 [planetinfo.record]: corona_shimmer = 0.460309 13:30:13.437 [planetinfo.record]: station_vector = -0.007 0.542 0.840 13:30:13.437 [planetinfo.record]: station = coriolis 13:30:18.750 [PLANETINFO OVER]: Done 13:30:18.751 [PLANETINFO LOGGING]: 4 207 13:30:19.753 [planetinfo.record]: seed = 164 46 211 219 13:30:19.753 [planetinfo.record]: coordinates = 46 124 13:30:19.753 [planetinfo.record]: government = 4; 13:30:19.753 [planetinfo.record]: economy = 4; 13:30:19.753 [planetinfo.record]: techlevel = 7; 13:30:19.754 [planetinfo.record]: population = 37; 13:30:19.754 [planetinfo.record]: productivity = 14208; 13:30:19.754 [planetinfo.record]: name = "Anbiat"; 13:30:19.754 [planetinfo.record]: inhabitant = "Horned Humanoid"; 13:30:19.754 [planetinfo.record]: inhabitants = "Horned Humanoids"; 13:30:19.754 [planetinfo.record]: description = "Anbiat is mildly well known for Anbiatian wolf meat and its unusual dense forests."; 13:30:19.788 [planetinfo.record]: air_color = 0.712658, 0.521488, 0.938514, 1; 13:30:19.788 [planetinfo.record]: cloud_alpha = 1.000000; 13:30:19.788 [planetinfo.record]: cloud_color = 0.818359, 0.217377, 0.583601, 1; 13:30:19.788 [planetinfo.record]: cloud_fraction = 0.170000; 13:30:19.788 [planetinfo.record]: land_color = 0.634078, 0.66, 0.404766, 1; 13:30:19.788 [planetinfo.record]: land_fraction = 0.340000; 13:30:19.788 [planetinfo.record]: polar_cloud_color = 0.868262, 0.469743, 0.71259, 1; 13:30:19.788 [planetinfo.record]: polar_land_color = 0.924829, 0.934, 0.843701, 1; 13:30:19.788 [planetinfo.record]: polar_sea_color = 0.924023, 0.826314, 0.810325, 1; 13:30:19.788 [planetinfo.record]: sea_color = 0.759766, 0.438405, 0.385818, 1; 13:30:19.788 [planetinfo.record]: rotation_speed = 0.001240 13:30:19.788 [planetinfo.record]: planet zpos = 681360.000000 13:30:19.788 [planetinfo.record]: sun_radius = 140974.440308 13:30:19.788 [planetinfo.record]: sun_vector = -0.763 0.509 0.399 13:30:19.788 [planetinfo.record]: sun_distance = 1135600 13:30:19.788 [planetinfo.record]: corona_flare = 0.060477 13:30:19.788 [planetinfo.record]: corona_hues = 0.505981 13:30:19.788 [planetinfo.record]: sun_color = 0.303429, 0.713682, 0.717889, 1 13:30:19.788 [planetinfo.record]: corona_shimmer = 0.323267 13:30:19.788 [planetinfo.record]: station_vector = 0.632 0.499 0.593 13:30:19.788 [planetinfo.record]: station = coriolis 13:30:24.150 [PLANETINFO OVER]: Done 13:30:24.151 [PLANETINFO LOGGING]: 4 208 13:30:25.156 [planetinfo.record]: seed = 164 243 117 11 13:30:25.156 [planetinfo.record]: coordinates = 243 145 13:30:25.156 [planetinfo.record]: government = 4; 13:30:25.156 [planetinfo.record]: economy = 1; 13:30:25.156 [planetinfo.record]: techlevel = 11; 13:30:25.156 [planetinfo.record]: population = 50; 13:30:25.156 [planetinfo.record]: productivity = 28800; 13:30:25.156 [planetinfo.record]: name = "Maera"; 13:30:25.156 [planetinfo.record]: inhabitant = "Human Colonial"; 13:30:25.156 [planetinfo.record]: inhabitants = "Human Colonials"; 13:30:25.156 [planetinfo.record]: description = "This world is a revolting dump."; 13:30:25.158 [planetinfo.record]: air_color = 0.698646, 0.717013, 0.983391, 1; 13:30:25.158 [planetinfo.record]: cloud_alpha = 1.000000; 13:30:25.158 [planetinfo.record]: cloud_color = 0.696228, 0.742389, 0.953125, 1; 13:30:25.158 [planetinfo.record]: cloud_fraction = 0.150000; 13:30:25.158 [planetinfo.record]: land_color = 0.550781, 0.488388, 0.492775, 1; 13:30:25.158 [planetinfo.record]: land_fraction = 0.380000; 13:30:25.159 [planetinfo.record]: polar_cloud_color = 0.772425, 0.800543, 0.928906, 1; 13:30:25.159 [planetinfo.record]: polar_land_color = 0.944922, 0.918161, 0.920043, 1; 13:30:25.159 [planetinfo.record]: polar_sea_color = 0.899286, 0.922656, 0.847871, 1; 13:30:25.159 [planetinfo.record]: sea_color = 0.695074, 0.773438, 0.522675, 1; 13:30:25.159 [planetinfo.record]: rotation_speed = 0.000159 13:30:25.159 [planetinfo.record]: planet zpos = 763750.000000 13:30:25.159 [planetinfo.record]: sun_radius = 125608.425140 13:30:25.159 [planetinfo.record]: sun_vector = -0.064 0.549 -0.834 13:30:25.159 [planetinfo.record]: sun_distance = 1116250 13:30:25.159 [planetinfo.record]: corona_flare = 0.055096 13:30:25.159 [planetinfo.record]: corona_hues = 0.800621 13:30:25.160 [planetinfo.record]: sun_color = 0.721888, 0.808674, 0.835675, 1 13:30:25.160 [planetinfo.record]: corona_shimmer = 0.352651 13:30:25.160 [planetinfo.record]: station_vector = 0.458 0.840 0.290 13:30:25.160 [planetinfo.record]: station = dodecahedron 13:30:30.751 [PLANETINFO OVER]: Done 13:30:30.752 [PLANETINFO LOGGING]: 4 209 13:30:31.754 [planetinfo.record]: seed = 8 43 155 74 13:30:31.754 [planetinfo.record]: coordinates = 43 143 13:30:31.754 [planetinfo.record]: government = 1; 13:30:31.754 [planetinfo.record]: economy = 7; 13:30:31.754 [planetinfo.record]: techlevel = 4; 13:30:31.754 [planetinfo.record]: population = 25; 13:30:31.755 [planetinfo.record]: productivity = 3000; 13:30:31.755 [planetinfo.record]: name = "Arzaquar"; 13:30:31.755 [planetinfo.record]: inhabitant = "Small Yellow Fat Feline"; 13:30:31.755 [planetinfo.record]: inhabitants = "Small Yellow Fat Felines"; 13:30:31.755 [planetinfo.record]: description = "The world Arzaquar is very famous for its hoopy night life but scourged by frequent civil war."; 13:30:31.767 [planetinfo.record]: air_color = 0.682359, 0.585674, 0.868922, 1; 13:30:31.767 [planetinfo.record]: cloud_alpha = 1.000000; 13:30:31.767 [planetinfo.record]: cloud_color = 0.609375, 0.378479, 0.600356, 1; 13:30:31.767 [planetinfo.record]: cloud_fraction = 0.300000; 13:30:31.767 [planetinfo.record]: land_color = 0.252228, 0.426499, 0.566406, 1; 13:30:31.767 [planetinfo.record]: land_fraction = 0.450000; 13:30:31.767 [planetinfo.record]: polar_cloud_color = 0.774219, 0.590871, 0.767057, 1; 13:30:31.767 [planetinfo.record]: polar_land_color = 0.812542, 0.885105, 0.943359, 1; 13:30:31.767 [planetinfo.record]: polar_sea_color = 0.938477, 0.885865, 0.877256, 1; 13:30:31.767 [planetinfo.record]: sea_color = 0.615234, 0.477272, 0.454697, 1; 13:30:31.767 [planetinfo.record]: rotation_speed = 0.003517 13:30:31.767 [planetinfo.record]: planet zpos = 704470.000000 13:30:31.767 [planetinfo.record]: sun_radius = 166659.717712 13:30:31.767 [planetinfo.record]: sun_vector = -0.346 0.934 -0.088 13:30:31.767 [planetinfo.record]: sun_distance = 1137990 13:30:31.767 [planetinfo.record]: corona_flare = 0.087862 13:30:31.767 [planetinfo.record]: corona_hues = 0.927742 13:30:31.767 [planetinfo.record]: sun_color = 0.683313, 0.335275, 0.331496, 1 13:30:31.768 [planetinfo.record]: corona_shimmer = 0.775060 13:30:31.768 [planetinfo.record]: station_vector = 0.176 -0.917 0.357 13:30:31.768 [planetinfo.record]: station = coriolis 13:30:37.122 [PLANETINFO OVER]: Done 13:30:37.123 [PLANETINFO LOGGING]: 4 210 13:30:38.127 [planetinfo.record]: seed = 48 202 197 73 13:30:38.127 [planetinfo.record]: coordinates = 202 122 13:30:38.127 [planetinfo.record]: government = 6; 13:30:38.128 [planetinfo.record]: economy = 2; 13:30:38.128 [planetinfo.record]: techlevel = 10; 13:30:38.128 [planetinfo.record]: population = 49; 13:30:38.128 [planetinfo.record]: productivity = 31360; 13:30:38.128 [planetinfo.record]: name = "Esrexe"; 13:30:38.128 [planetinfo.record]: inhabitant = "Small Yellow Slimy Frog"; 13:30:38.128 [planetinfo.record]: inhabitants = "Small Yellow Slimy Frogs"; 13:30:38.128 [planetinfo.record]: description = "Esrexe is a revolting dump."; 13:30:38.130 [planetinfo.record]: air_color = 0.676025, 0.589143, 0.862418, 1; 13:30:38.130 [planetinfo.record]: cloud_alpha = 1.000000; 13:30:38.130 [planetinfo.record]: cloud_color = 0.583364, 0.382477, 0.589844, 1; 13:30:38.130 [planetinfo.record]: cloud_fraction = 0.470000; 13:30:38.130 [planetinfo.record]: land_color = 0.554688, 0.416016, 0.53627, 1; 13:30:38.130 [planetinfo.record]: land_fraction = 0.440000; 13:30:38.130 [planetinfo.record]: polar_cloud_color = 0.760174, 0.597244, 0.76543, 1; 13:30:38.130 [planetinfo.record]: polar_land_color = 0.944531, 0.885498, 0.936691, 1; 13:30:38.130 [planetinfo.record]: polar_sea_color = 0.885232, 0.928516, 0.873113, 1; 13:30:38.130 [planetinfo.record]: sea_color = 0.581552, 0.714844, 0.544231, 1; 13:30:38.131 [planetinfo.record]: rotation_speed = 0.000025 13:30:38.131 [planetinfo.record]: planet zpos = 638640.000000 13:30:38.131 [planetinfo.record]: sun_radius = 143443.719177 13:30:38.131 [planetinfo.record]: sun_vector = -0.059 0.107 -0.993 13:30:38.131 [planetinfo.record]: sun_distance = 1117620 13:30:38.131 [planetinfo.record]: corona_flare = 0.037041 13:30:38.131 [planetinfo.record]: corona_hues = 0.537933 13:30:38.131 [planetinfo.record]: sun_color = 0.735828, 0.596298, 0.565734, 1 13:30:38.131 [planetinfo.record]: corona_shimmer = 0.515228 13:30:38.132 [planetinfo.record]: station_vector = -0.699 -0.484 0.527 13:30:38.132 [planetinfo.record]: station = coriolis 13:30:42.769 [PLANETINFO OVER]: Done 13:30:42.771 [PLANETINFO LOGGING]: 4 211 13:30:43.889 [planetinfo.record]: seed = 220 122 243 171 13:30:43.889 [planetinfo.record]: coordinates = 122 162 13:30:43.889 [planetinfo.record]: government = 3; 13:30:43.889 [planetinfo.record]: economy = 2; 13:30:43.889 [planetinfo.record]: techlevel = 9; 13:30:43.889 [planetinfo.record]: population = 42; 13:30:43.889 [planetinfo.record]: productivity = 18816; 13:30:43.889 [planetinfo.record]: name = "Maeser"; 13:30:43.889 [planetinfo.record]: inhabitant = "Small Harmless Slimy Lobster"; 13:30:43.889 [planetinfo.record]: inhabitants = "Small Harmless Slimy Lobsters"; 13:30:43.889 [planetinfo.record]: description = "Maeser is reasonably well known for its vast oceans but ravaged by lethal spotted craboids."; 13:30:43.897 [planetinfo.record]: air_color = 0.501658, 0.596331, 0.86632, 1; 13:30:43.898 [planetinfo.record]: cloud_alpha = 1.000000; 13:30:43.898 [planetinfo.record]: cloud_color = 0.202087, 0.507936, 0.601562, 1; 13:30:43.898 [planetinfo.record]: cloud_fraction = 0.330000; 13:30:43.898 [planetinfo.record]: land_color = 0.497578, 0.66, 0.591478, 1; 13:30:43.898 [planetinfo.record]: land_fraction = 0.200000; 13:30:43.898 [planetinfo.record]: polar_cloud_color = 0.450831, 0.695733, 0.770703, 1; 13:30:43.898 [planetinfo.record]: polar_land_color = 0.876537, 0.934, 0.909758, 1; 13:30:43.898 [planetinfo.record]: polar_sea_color = 0.91222, 0.933008, 0.878704, 1; 13:30:43.898 [planetinfo.record]: sea_color = 0.610216, 0.669922, 0.513956, 1; 13:30:43.898 [planetinfo.record]: rotation_speed = 0.002167 13:30:43.898 [planetinfo.record]: planet zpos = 690480.000000 13:30:43.898 [planetinfo.record]: sun_radius = 143945.700989 13:30:43.899 [planetinfo.record]: sun_vector = 0.436 -0.716 0.545 13:30:43.899 [planetinfo.record]: sun_distance = 1093260 13:30:43.899 [planetinfo.record]: corona_flare = 0.089566 13:30:43.899 [planetinfo.record]: corona_hues = 0.803764 13:30:43.899 [planetinfo.record]: sun_color = 0.755246, 0.355004, 0.335468, 1 13:30:43.899 [planetinfo.record]: corona_shimmer = 0.355907 13:30:43.899 [planetinfo.record]: station_vector = 0.396 -0.709 0.583 13:30:43.899 [planetinfo.record]: station = coriolis 13:30:48.595 [PLANETINFO OVER]: Done 13:30:48.596 [PLANETINFO LOGGING]: 4 212 13:30:49.599 [planetinfo.record]: seed = 108 101 229 30 13:30:49.599 [planetinfo.record]: coordinates = 101 240 13:30:49.599 [planetinfo.record]: government = 5; 13:30:49.599 [planetinfo.record]: economy = 0; 13:30:49.599 [planetinfo.record]: techlevel = 11; 13:30:49.600 [planetinfo.record]: population = 50; 13:30:49.600 [planetinfo.record]: productivity = 36000; 13:30:49.600 [planetinfo.record]: name = "Riraed"; 13:30:49.600 [planetinfo.record]: inhabitant = "Green Furry Insect"; 13:30:49.600 [planetinfo.record]: inhabitants = "Green Furry Insects"; 13:30:49.600 [planetinfo.record]: description = "The world Riraed is a dull world."; 13:30:49.616 [planetinfo.record]: air_color = 0.499016, 0.592328, 0.869572, 1; 13:30:49.616 [planetinfo.record]: cloud_alpha = 1.000000; 13:30:49.616 [planetinfo.record]: cloud_color = 0.195816, 0.504204, 0.611328, 1; 13:30:49.616 [planetinfo.record]: cloud_fraction = 0.290000; 13:30:49.616 [planetinfo.record]: land_color = 0.586685, 0.66, 0.425391, 1; 13:30:49.616 [planetinfo.record]: land_fraction = 0.600000; 13:30:49.616 [planetinfo.record]: polar_cloud_color = 0.445833, 0.690209, 0.775098, 1; 13:30:49.616 [planetinfo.record]: polar_land_color = 0.908062, 0.934, 0.850998, 1; 13:30:49.616 [planetinfo.record]: polar_sea_color = 0.943164, 0.877769, 0.932946, 1; 13:30:49.616 [planetinfo.record]: sea_color = 0.568359, 0.410728, 0.54373, 1; 13:30:49.616 [planetinfo.record]: rotation_speed = 0.001084 13:30:49.616 [planetinfo.record]: planet zpos = 845130.000000 13:30:49.616 [planetinfo.record]: sun_radius = 206770.010834 13:30:49.616 [planetinfo.record]: sun_vector = -0.438 0.099 -0.894 13:30:49.616 [planetinfo.record]: sun_distance = 1235190 13:30:49.616 [planetinfo.record]: corona_flare = 0.071829 13:30:49.616 [planetinfo.record]: corona_hues = 0.697433 13:30:49.617 [planetinfo.record]: sun_color = 0.20992, 0.685824, 0.834637, 1 13:30:49.617 [planetinfo.record]: corona_shimmer = 0.494246 13:30:49.617 [planetinfo.record]: station_vector = -0.713 0.697 0.081 13:30:49.617 [planetinfo.record]: station = dodecahedron 13:30:54.020 [PLANETINFO OVER]: Done 13:30:54.021 [PLANETINFO LOGGING]: 4 213 13:30:55.024 [planetinfo.record]: seed = 32 140 91 249 13:30:55.024 [planetinfo.record]: coordinates = 140 248 13:30:55.024 [planetinfo.record]: government = 4; 13:30:55.024 [planetinfo.record]: economy = 0; 13:30:55.024 [planetinfo.record]: techlevel = 9; 13:30:55.025 [planetinfo.record]: population = 41; 13:30:55.025 [planetinfo.record]: productivity = 26240; 13:30:55.025 [planetinfo.record]: name = "Orrigean"; 13:30:55.025 [planetinfo.record]: inhabitant = "Human Colonial"; 13:30:55.025 [planetinfo.record]: inhabitants = "Human Colonials"; 13:30:55.025 [planetinfo.record]: description = "The world Orrigean is most famous for the Orrigeanian spotted shrew."; 13:30:55.031 [planetinfo.record]: air_color = 0.881405, 0.780525, 0.989895, 1; 13:30:55.032 [planetinfo.record]: cloud_alpha = 1.000000; 13:30:55.032 [planetinfo.record]: cloud_color = 0.972656, 0.938461, 0.956895, 1; 13:30:55.032 [planetinfo.record]: cloud_fraction = 0.600000; 13:30:55.032 [planetinfo.record]: land_color = 0.66, 0.304904, 0.131484, 1; 13:30:55.032 [planetinfo.record]: land_fraction = 0.610000; 13:30:55.032 [planetinfo.record]: polar_cloud_color = 0.937695, 0.917092, 0.928198, 1; 13:30:55.032 [planetinfo.record]: polar_land_color = 0.934, 0.808371, 0.747018, 1; 13:30:55.032 [planetinfo.record]: polar_sea_color = 0.943945, 0.887714, 0.92857, 1; 13:30:55.032 [planetinfo.record]: sea_color = 0.560547, 0.426979, 0.524024, 1; 13:30:55.032 [planetinfo.record]: rotation_speed = 0.002049 13:30:55.032 [planetinfo.record]: planet zpos = 683800.000000 13:30:55.033 [planetinfo.record]: sun_radius = 160060.958862 13:30:55.033 [planetinfo.record]: sun_vector = 0.770 -0.116 -0.628 13:30:55.033 [planetinfo.record]: sun_distance = 1209800 13:30:55.033 [planetinfo.record]: corona_flare = 0.087390 13:30:55.033 [planetinfo.record]: corona_hues = 0.582886 13:30:55.033 [planetinfo.record]: sun_color = 0.284902, 0.661503, 0.688312, 1 13:30:55.033 [planetinfo.record]: corona_shimmer = 0.253281 13:30:55.033 [planetinfo.record]: station_vector = -0.888 0.384 0.251 13:30:55.033 [planetinfo.record]: station = coriolis 13:30:59.565 [PLANETINFO OVER]: Done 13:30:59.566 [PLANETINFO LOGGING]: 4 214 13:31:00.568 [planetinfo.record]: seed = 88 123 85 253 13:31:00.568 [planetinfo.record]: coordinates = 123 3 13:31:00.568 [planetinfo.record]: government = 3; 13:31:00.568 [planetinfo.record]: economy = 3; 13:31:00.568 [planetinfo.record]: techlevel = 9; 13:31:00.568 [planetinfo.record]: population = 43; 13:31:00.568 [planetinfo.record]: productivity = 16856; 13:31:00.568 [planetinfo.record]: name = "Istela"; 13:31:00.568 [planetinfo.record]: inhabitant = "Human Colonial"; 13:31:00.568 [planetinfo.record]: inhabitants = "Human Colonials"; 13:31:00.568 [planetinfo.record]: description = "The world Istela is a dull world."; 13:31:00.571 [planetinfo.record]: air_color = 0.563934, 0.965753, 0.971684, 1; 13:31:00.572 [planetinfo.record]: cloud_alpha = 1.000000; 13:31:00.572 [planetinfo.record]: cloud_color = 0.884828, 0.917969, 0.311966, 1; 13:31:00.572 [planetinfo.record]: cloud_fraction = 0.120000; 13:31:00.572 [planetinfo.record]: land_color = 0.341751, 0.317459, 0.556641, 1; 13:31:00.572 [planetinfo.record]: land_fraction = 0.490000; 13:31:00.572 [planetinfo.record]: polar_cloud_color = 0.892483, 0.913086, 0.536349, 1; 13:31:00.572 [planetinfo.record]: polar_land_color = 0.853196, 0.842894, 0.944336, 1; 13:31:00.572 [planetinfo.record]: polar_sea_color = 0.884316, 0.930859, 0.897043, 1; 13:31:00.572 [planetinfo.record]: sea_color = 0.553125, 0.691406, 0.590936, 1; 13:31:00.572 [planetinfo.record]: rotation_speed = 0.003532 13:31:00.572 [planetinfo.record]: planet zpos = 940050.000000 13:31:00.572 [planetinfo.record]: sun_radius = 138365.330658 13:31:00.572 [planetinfo.record]: sun_vector = -0.849 -0.065 0.524 13:31:00.572 [planetinfo.record]: sun_distance = 1504080 13:31:00.572 [planetinfo.record]: corona_flare = 0.025427 13:31:00.572 [planetinfo.record]: corona_hues = 0.505318 13:31:00.572 [planetinfo.record]: sun_color = 0.538595, 0.603906, 0.815619, 1 13:31:00.573 [planetinfo.record]: corona_shimmer = 0.461957 13:31:00.573 [planetinfo.record]: station_vector = 0.367 -0.504 0.782 13:31:00.573 [planetinfo.record]: station = coriolis 13:31:05.330 [PLANETINFO OVER]: Done 13:31:05.330 [PLANETINFO LOGGING]: 4 215 13:31:06.335 [planetinfo.record]: seed = 212 110 83 224 13:31:06.335 [planetinfo.record]: coordinates = 110 245 13:31:06.335 [planetinfo.record]: government = 2; 13:31:06.335 [planetinfo.record]: economy = 5; 13:31:06.335 [planetinfo.record]: techlevel = 5; 13:31:06.335 [planetinfo.record]: population = 28; 13:31:06.335 [planetinfo.record]: productivity = 6720; 13:31:06.335 [planetinfo.record]: name = "Zabe"; 13:31:06.335 [planetinfo.record]: inhabitant = "Human Colonial"; 13:31:06.335 [planetinfo.record]: inhabitants = "Human Colonials"; 13:31:06.335 [planetinfo.record]: description = "The planet Zabe is very famous for its unusual casinos but beset by lethal disease."; 13:31:06.344 [planetinfo.record]: air_color = 0.583788, 0.56923, 0.846158, 1; 13:31:06.344 [planetinfo.record]: cloud_alpha = 1.000000; 13:31:06.344 [planetinfo.record]: cloud_color = 0.369505, 0.333908, 0.541016, 1; 13:31:06.344 [planetinfo.record]: cloud_fraction = 0.370000; 13:31:06.344 [planetinfo.record]: land_color = 0.587813, 0.66, 0.626162, 1; 13:31:06.344 [planetinfo.record]: land_fraction = 0.540000; 13:31:06.344 [planetinfo.record]: polar_cloud_color = 0.596152, 0.565579, 0.743457, 1; 13:31:06.344 [planetinfo.record]: polar_land_color = 0.908461, 0.934, 0.922029, 1; 13:31:06.344 [planetinfo.record]: polar_sea_color = 0.908594, 0.721374, 0.721374, 1; 13:31:06.344 [planetinfo.record]: sea_color = 0.914062, 0.160675, 0.160675, 1; 13:31:06.344 [planetinfo.record]: rotation_speed = 0.003126 13:31:06.344 [planetinfo.record]: planet zpos = 351120.000000 13:31:06.344 [planetinfo.record]: sun_radius = 51289.829712 13:31:06.344 [planetinfo.record]: sun_vector = 0.903 0.225 0.366 13:31:06.344 [planetinfo.record]: sun_distance = 702240 13:31:06.345 [planetinfo.record]: corona_flare = 0.011981 13:31:06.345 [planetinfo.record]: corona_hues = 0.533813 13:31:06.345 [planetinfo.record]: sun_color = 0.598528, 0.646396, 0.727399, 1 13:31:06.345 [planetinfo.record]: corona_shimmer = 0.421426 13:31:06.345 [planetinfo.record]: station_vector = 0.096 -0.995 0.020 13:31:06.345 [planetinfo.record]: station = coriolis 13:31:11.172 [PLANETINFO OVER]: Done 13:31:11.173 [PLANETINFO LOGGING]: 4 216 13:31:12.178 [planetinfo.record]: seed = 244 183 149 143 13:31:12.178 [planetinfo.record]: coordinates = 183 147 13:31:12.178 [planetinfo.record]: government = 6; 13:31:12.178 [planetinfo.record]: economy = 3; 13:31:12.178 [planetinfo.record]: techlevel = 10; 13:31:12.178 [planetinfo.record]: population = 50; 13:31:12.178 [planetinfo.record]: productivity = 28000; 13:31:12.178 [planetinfo.record]: name = "Aquxein"; 13:31:12.178 [planetinfo.record]: inhabitant = "Black Fat Insect"; 13:31:12.178 [planetinfo.record]: inhabitants = "Black Fat Insects"; 13:31:12.179 [planetinfo.record]: description = "The planet Aquxein is a boring world."; 13:31:12.198 [planetinfo.record]: air_color = 0.6022, 0.548817, 0.878678, 1; 13:31:12.198 [planetinfo.record]: cloud_alpha = 1.000000; 13:31:12.198 [planetinfo.record]: cloud_color = 0.470272, 0.301872, 0.638672, 1; 13:31:12.198 [planetinfo.record]: cloud_fraction = 0.240000; 13:31:12.198 [planetinfo.record]: land_color = 0.0670312, 0.229171, 0.66, 1; 13:31:12.199 [planetinfo.record]: land_fraction = 0.470000; 13:31:12.199 [planetinfo.record]: polar_cloud_color = 0.657642, 0.527883, 0.787402, 1; 13:31:12.199 [planetinfo.record]: polar_land_color = 0.724215, 0.781578, 0.934, 1; 13:31:12.199 [planetinfo.record]: polar_sea_color = 0.92355, 0.935156, 0.882097, 1; 13:31:12.199 [planetinfo.record]: sea_color = 0.616245, 0.648438, 0.501273, 1; 13:31:12.199 [planetinfo.record]: rotation_speed = 0.002085 13:31:12.199 [planetinfo.record]: planet zpos = 889070.000000 13:31:12.199 [planetinfo.record]: sun_radius = 154995.140533 13:31:12.199 [planetinfo.record]: sun_vector = -0.173 -0.863 -0.475 13:31:12.200 [planetinfo.record]: sun_distance = 1504580 13:31:12.200 [planetinfo.record]: corona_flare = 0.000880 13:31:12.200 [planetinfo.record]: corona_hues = 0.759346 13:31:12.200 [planetinfo.record]: sun_color = 0.487329, 0.530973, 0.775171, 1 13:31:12.200 [planetinfo.record]: corona_shimmer = 0.583611 13:31:12.200 [planetinfo.record]: station_vector = 0.149 -0.411 0.900 13:31:12.200 [planetinfo.record]: station = coriolis 13:31:16.622 [PLANETINFO OVER]: Done 13:31:16.623 [PLANETINFO LOGGING]: 4 217 13:31:17.626 [planetinfo.record]: seed = 248 140 91 138 13:31:17.626 [planetinfo.record]: coordinates = 140 34 13:31:17.626 [planetinfo.record]: government = 7; 13:31:17.626 [planetinfo.record]: economy = 2; 13:31:17.626 [planetinfo.record]: techlevel = 9; 13:31:17.626 [planetinfo.record]: population = 46; 13:31:17.626 [planetinfo.record]: productivity = 32384; 13:31:17.626 [planetinfo.record]: name = "Aroratla"; 13:31:17.626 [planetinfo.record]: inhabitant = "Human Colonial"; 13:31:17.627 [planetinfo.record]: inhabitants = "Human Colonials"; 13:31:17.627 [planetinfo.record]: description = "Aroratla is reasonably notable for its great parking meters but cursed by occasional earthquakes."; 13:31:17.640 [planetinfo.record]: air_color = 0.808746, 0.482129, 0.987943, 1; 13:31:17.640 [planetinfo.record]: cloud_alpha = 1.000000; 13:31:17.640 [planetinfo.record]: cloud_color = 0.966797, 0.0604248, 0.102911, 1; 13:31:17.640 [planetinfo.record]: cloud_fraction = 0.460000; 13:31:17.640 [planetinfo.record]: land_color = 0.230156, 0.404052, 0.607422, 1; 13:31:17.640 [planetinfo.record]: land_fraction = 0.470000; 13:31:17.640 [planetinfo.record]: polar_cloud_color = 0.935059, 0.387173, 0.412855, 1; 13:31:17.640 [planetinfo.record]: polar_land_color = 0.793416, 0.86064, 0.939258, 1; 13:31:17.640 [planetinfo.record]: polar_sea_color = 0.927148, 0.867851, 0.835701, 1; 13:31:17.640 [planetinfo.record]: sea_color = 0.728516, 0.54214, 0.441093, 1; 13:31:17.640 [planetinfo.record]: rotation_speed = 0.000435 13:31:17.640 [planetinfo.record]: planet zpos = 717080.000000 13:31:17.640 [planetinfo.record]: sun_radius = 139632.166748 13:31:17.641 [planetinfo.record]: sun_vector = 0.046 0.995 -0.084 13:31:17.641 [planetinfo.record]: sun_distance = 1268680 13:31:17.641 [planetinfo.record]: corona_flare = 0.090584 13:31:17.641 [planetinfo.record]: corona_hues = 0.518578 13:31:17.641 [planetinfo.record]: sun_color = 0.708221, 0.687078, 0.646745, 1 13:31:17.641 [planetinfo.record]: corona_shimmer = 0.389798 13:31:17.641 [planetinfo.record]: station_vector = -0.771 0.194 0.607 13:31:17.641 [planetinfo.record]: station = coriolis 13:31:22.406 [PLANETINFO OVER]: Done 13:31:22.407 [PLANETINFO LOGGING]: 4 218 13:31:23.409 [planetinfo.record]: seed = 64 21 37 160 13:31:23.409 [planetinfo.record]: coordinates = 21 81 13:31:23.409 [planetinfo.record]: government = 0; 13:31:23.410 [planetinfo.record]: economy = 3; 13:31:23.410 [planetinfo.record]: techlevel = 5; 13:31:23.410 [planetinfo.record]: population = 24; 13:31:23.410 [planetinfo.record]: productivity = 5376; 13:31:23.410 [planetinfo.record]: name = "Bian"; 13:31:23.410 [planetinfo.record]: inhabitant = "Human Colonial"; 13:31:23.410 [planetinfo.record]: inhabitants = "Human Colonials"; 13:31:23.410 [planetinfo.record]: description = "This planet is most notable for vicious Be gargle blasters but scourged by unpredictable earthquakes."; 13:31:23.416 [planetinfo.record]: air_color = 0.853781, 0.762419, 0.993146, 1; 13:31:23.416 [planetinfo.record]: cloud_alpha = 1.000000; 13:31:23.416 [planetinfo.record]: cloud_color = 0.982422, 0.886482, 0.962934, 1; 13:31:23.417 [planetinfo.record]: cloud_fraction = 0.360000; 13:31:23.417 [planetinfo.record]: land_color = 0.66, 0.372781, 0.260391, 1; 13:31:23.417 [planetinfo.record]: land_fraction = 0.610000; 13:31:23.417 [planetinfo.record]: polar_cloud_color = 0.94209, 0.884589, 0.93041, 1; 13:31:23.417 [planetinfo.record]: polar_land_color = 0.934, 0.832385, 0.792623, 1; 13:31:23.417 [planetinfo.record]: polar_sea_color = 0.930469, 0.904558, 0.864137, 1; 13:31:23.417 [planetinfo.record]: sea_color = 0.695312, 0.617862, 0.49704, 1; 13:31:23.417 [planetinfo.record]: rotation_speed = 0.001912 13:31:23.417 [planetinfo.record]: planet zpos = 255330.000000 13:31:23.417 [planetinfo.record]: sun_radius = 61948.987579 13:31:23.417 [planetinfo.record]: sun_vector = 0.245 0.086 -0.966 13:31:23.417 [planetinfo.record]: sun_distance = 595770 13:31:23.417 [planetinfo.record]: corona_flare = 0.081337 13:31:23.417 [planetinfo.record]: corona_hues = 0.646393 13:31:23.417 [planetinfo.record]: sun_color = 0.782907, 0.538498, 0.308171, 1 13:31:23.418 [planetinfo.record]: corona_shimmer = 0.418013 13:31:23.418 [planetinfo.record]: station_vector = -0.822 -0.545 0.166 13:31:23.418 [planetinfo.record]: station = coriolis 13:31:28.549 [PLANETINFO OVER]: Done 13:31:28.549 [PLANETINFO LOGGING]: 4 219 13:31:29.553 [planetinfo.record]: seed = 140 98 243 36 13:31:29.553 [planetinfo.record]: coordinates = 98 187 13:31:29.553 [planetinfo.record]: government = 1; 13:31:29.553 [planetinfo.record]: economy = 3; 13:31:29.553 [planetinfo.record]: techlevel = 7; 13:31:29.553 [planetinfo.record]: population = 33; 13:31:29.553 [planetinfo.record]: productivity = 9240; 13:31:29.553 [planetinfo.record]: name = "Zagearbe"; 13:31:29.553 [planetinfo.record]: inhabitant = "Fierce Red Bug-Eyed Frog"; 13:31:29.553 [planetinfo.record]: inhabitants = "Fierce Red Bug-Eyed Frogs"; 13:31:29.553 [planetinfo.record]: description = "The world Zagearbe is a dull world."; 13:31:29.564 [planetinfo.record]: air_color = 0.633271, 0.851917, 0.958025, 1; 13:31:29.564 [planetinfo.record]: cloud_alpha = 1.000000; 13:31:29.564 [planetinfo.record]: cloud_color = 0.513278, 0.876953, 0.510414, 1; 13:31:29.564 [planetinfo.record]: cloud_fraction = 0.210000; 13:31:29.564 [planetinfo.record]: land_color = 0.249825, 0.407899, 0.576172, 1; 13:31:29.564 [planetinfo.record]: land_fraction = 0.630000; 13:31:29.565 [planetinfo.record]: polar_cloud_color = 0.66275, 0.894629, 0.660925, 1; 13:31:29.565 [planetinfo.record]: polar_land_color = 0.80894, 0.873576, 0.942383, 1; 13:31:29.565 [planetinfo.record]: polar_sea_color = 0.870748, 0.922266, 0.915021, 1; 13:31:29.565 [planetinfo.record]: sea_color = 0.603656, 0.777344, 0.752919, 1; 13:31:29.565 [planetinfo.record]: rotation_speed = 0.003977 13:31:29.565 [planetinfo.record]: planet zpos = 472560.000000 13:31:29.565 [planetinfo.record]: sun_radius = 95616.197510 13:31:29.565 [planetinfo.record]: sun_vector = 0.401 0.017 0.916 13:31:29.565 [planetinfo.record]: sun_distance = 945120 13:31:29.565 [planetinfo.record]: corona_flare = 0.072365 13:31:29.565 [planetinfo.record]: corona_hues = 0.941856 13:31:29.565 [planetinfo.record]: sun_color = 0.650626, 0.684582, 0.685736, 1 13:31:29.565 [planetinfo.record]: corona_shimmer = 0.455219 13:31:29.565 [planetinfo.record]: station_vector = -0.519 0.638 0.569 13:31:29.566 [planetinfo.record]: station = coriolis 13:31:33.891 [PLANETINFO OVER]: Done 13:31:33.892 [PLANETINFO LOGGING]: 4 220 13:31:34.900 [planetinfo.record]: seed = 60 51 133 65 13:31:34.900 [planetinfo.record]: coordinates = 51 202 13:31:34.901 [planetinfo.record]: government = 7; 13:31:34.901 [planetinfo.record]: economy = 2; 13:31:34.901 [planetinfo.record]: techlevel = 12; 13:31:34.901 [planetinfo.record]: population = 58; 13:31:34.901 [planetinfo.record]: productivity = 40832; 13:31:34.901 [planetinfo.record]: name = "Leonrala"; 13:31:34.901 [planetinfo.record]: inhabitant = "Large Yellow Bug-Eyed Lizard"; 13:31:34.901 [planetinfo.record]: inhabitants = "Large Yellow Bug-Eyed Lizards"; 13:31:34.901 [planetinfo.record]: description = "The world Leonrala is very famous for its unusual casinos but plagued by frequent earthquakes."; 13:31:34.918 [planetinfo.record]: air_color = 0.782148, 0.630413, 0.868271, 1; 13:31:34.919 [planetinfo.record]: cloud_alpha = 1.000000; 13:31:34.919 [planetinfo.record]: cloud_color = 0.607422, 0.472176, 0.481685, 1; 13:31:34.919 [planetinfo.record]: cloud_fraction = 0.440000; 13:31:34.919 [planetinfo.record]: land_color = 0.381563, 0.66, 0.516431, 1; 13:31:34.919 [planetinfo.record]: land_fraction = 0.440000; 13:31:34.919 [planetinfo.record]: polar_cloud_color = 0.77334, 0.665722, 0.673289, 1; 13:31:34.919 [planetinfo.record]: polar_land_color = 0.835492, 0.934, 0.883207, 1; 13:31:34.919 [planetinfo.record]: polar_sea_color = 0.902813, 0.932031, 0.878603, 1; 13:31:34.920 [planetinfo.record]: sea_color = 0.594457, 0.679688, 0.523837, 1; 13:31:34.920 [planetinfo.record]: rotation_speed = 0.002967 13:31:34.920 [planetinfo.record]: planet zpos = 281070.000000 13:31:34.920 [planetinfo.record]: sun_radius = 58523.369293 13:31:34.920 [planetinfo.record]: sun_vector = 0.259 -0.562 -0.786 13:31:34.920 [planetinfo.record]: sun_distance = 655830 13:31:34.920 [planetinfo.record]: corona_flare = 0.080237 13:31:34.920 [planetinfo.record]: corona_hues = 0.939804 13:31:34.920 [planetinfo.record]: sun_color = 0.285802, 0.533037, 0.794928, 1 13:31:34.920 [planetinfo.record]: corona_shimmer = 0.347852 13:31:34.921 [planetinfo.record]: station_vector = 0.953 0.000 0.304 13:31:34.921 [planetinfo.record]: station = dodecahedron 13:31:39.351 [PLANETINFO OVER]: Done 13:31:39.351 [PLANETINFO LOGGING]: 4 221 13:31:40.354 [planetinfo.record]: seed = 144 53 155 41 13:31:40.354 [planetinfo.record]: coordinates = 53 180 13:31:40.354 [planetinfo.record]: government = 2; 13:31:40.354 [planetinfo.record]: economy = 4; 13:31:40.354 [planetinfo.record]: techlevel = 5; 13:31:40.354 [planetinfo.record]: population = 27; 13:31:40.354 [planetinfo.record]: productivity = 7776; 13:31:40.354 [planetinfo.record]: name = "Esbeena"; 13:31:40.354 [planetinfo.record]: inhabitant = "Small Red Bug-Eyed Lizard"; 13:31:40.355 [planetinfo.record]: inhabitants = "Small Red Bug-Eyed Lizards"; 13:31:40.355 [planetinfo.record]: description = "The planet Esbeena is scourged by evil disease."; 13:31:40.372 [planetinfo.record]: air_color = 0.55577, 0.961928, 0.910776, 1; 13:31:40.372 [planetinfo.record]: cloud_alpha = 1.000000; 13:31:40.372 [planetinfo.record]: cloud_color = 0.888672, 0.670708, 0.295067, 1; 13:31:40.372 [planetinfo.record]: cloud_fraction = 0.120000; 13:31:40.372 [planetinfo.record]: land_color = 0.66, 0.569605, 0.397031, 1; 13:31:40.372 [planetinfo.record]: land_fraction = 0.700000; 13:31:40.372 [planetinfo.record]: polar_cloud_color = 0.899902, 0.761953, 0.524211, 1; 13:31:40.372 [planetinfo.record]: polar_land_color = 0.934, 0.902019, 0.840965, 1; 13:31:40.372 [planetinfo.record]: polar_sea_color = 0.932617, 0.832434, 0.884091, 1; 13:31:40.372 [planetinfo.record]: sea_color = 0.673828, 0.384293, 0.533584, 1; 13:31:40.372 [planetinfo.record]: rotation_speed = 0.003816 13:31:40.372 [planetinfo.record]: planet zpos = 569030.000000 13:31:40.372 [planetinfo.record]: sun_radius = 119607.470245 13:31:40.372 [planetinfo.record]: sun_vector = -0.577 -0.439 0.688 13:31:40.372 [planetinfo.record]: sun_distance = 1241520 13:31:40.373 [planetinfo.record]: corona_flare = 0.046825 13:31:40.373 [planetinfo.record]: corona_hues = 0.761604 13:31:40.373 [planetinfo.record]: sun_color = 0.770007, 0.513951, 0.479369, 1 13:31:40.373 [planetinfo.record]: corona_shimmer = 0.380360 13:31:40.373 [planetinfo.record]: station_vector = 0.106 0.991 0.078 13:31:40.373 [planetinfo.record]: station = coriolis 13:31:45.451 [PLANETINFO OVER]: Done 13:31:45.452 [PLANETINFO LOGGING]: 4 222 13:31:46.455 [planetinfo.record]: seed = 232 175 53 54 13:31:46.455 [planetinfo.record]: coordinates = 175 114 13:31:46.455 [planetinfo.record]: government = 5; 13:31:46.455 [planetinfo.record]: economy = 2; 13:31:46.455 [planetinfo.record]: techlevel = 11; 13:31:46.455 [planetinfo.record]: population = 52; 13:31:46.455 [planetinfo.record]: productivity = 29952; 13:31:46.455 [planetinfo.record]: name = "Veedri"; 13:31:46.455 [planetinfo.record]: inhabitant = "Human Colonial"; 13:31:46.455 [planetinfo.record]: inhabitants = "Human Colonials"; 13:31:46.455 [planetinfo.record]: description = "This world is ravaged by unpredictable civil war."; 13:31:46.458 [planetinfo.record]: air_color = 0.514698, 0.472687, 0.931359, 1; 13:31:46.458 [planetinfo.record]: cloud_alpha = 1.000000; 13:31:46.458 [planetinfo.record]: cloud_color = 0.29895, 0.0964966, 0.796875, 1; 13:31:46.458 [planetinfo.record]: cloud_fraction = 0.280000; 13:31:46.459 [planetinfo.record]: land_color = 0.456328, 0.66, 0.631359, 1; 13:31:46.459 [planetinfo.record]: land_fraction = 0.620000; 13:31:46.459 [planetinfo.record]: polar_cloud_color = 0.523287, 0.386954, 0.858594, 1; 13:31:46.459 [planetinfo.record]: polar_land_color = 0.861943, 0.934, 0.923867, 1; 13:31:46.459 [planetinfo.record]: polar_sea_color = 0.942578, 0.913526, 0.890202, 1; 13:31:46.459 [planetinfo.record]: sea_color = 0.574219, 0.503424, 0.44659, 1; 13:31:46.459 [planetinfo.record]: rotation_speed = 0.000324 13:31:46.459 [planetinfo.record]: planet zpos = 497970.000000 13:31:46.459 [planetinfo.record]: sun_radius = 147520.545502 13:31:46.459 [planetinfo.record]: sun_vector = 0.169 0.098 -0.981 13:31:46.459 [planetinfo.record]: sun_distance = 995940 13:31:46.459 [planetinfo.record]: corona_flare = 0.027000 13:31:46.459 [planetinfo.record]: corona_hues = 0.616180 13:31:46.459 [planetinfo.record]: sun_color = 0.324991, 0.379762, 0.801135, 1 13:31:46.459 [planetinfo.record]: corona_shimmer = 0.405219 13:31:46.460 [planetinfo.record]: station_vector = 0.998 0.015 0.055 13:31:46.460 [planetinfo.record]: station = dodecahedron 13:31:51.592 [PLANETINFO OVER]: Done 13:31:51.594 [PLANETINFO LOGGING]: 4 223 13:31:52.597 [planetinfo.record]: seed = 4 206 211 101 13:31:52.597 [planetinfo.record]: coordinates = 206 62 13:31:52.597 [planetinfo.record]: government = 0; 13:31:52.597 [planetinfo.record]: economy = 6; 13:31:52.597 [planetinfo.record]: techlevel = 3; 13:31:52.597 [planetinfo.record]: population = 19; 13:31:52.597 [planetinfo.record]: productivity = 2432; 13:31:52.598 [planetinfo.record]: name = "Ceenbion"; 13:31:52.598 [planetinfo.record]: inhabitant = "Fierce Blue Slimy Frog"; 13:31:52.598 [planetinfo.record]: inhabitants = "Fierce Blue Slimy Frogs"; 13:31:52.598 [planetinfo.record]: description = "The planet Ceenbion is mildly notable for its inhabitants’ ingrained shyness."; 13:31:52.604 [planetinfo.record]: air_color = 0.677865, 0.861117, 0.84805, 1; 13:31:52.604 [planetinfo.record]: cloud_alpha = 1.000000; 13:31:52.604 [planetinfo.record]: cloud_color = 0.585938, 0.581288, 0.563049, 1; 13:31:52.604 [planetinfo.record]: cloud_fraction = 0.360000; 13:31:52.604 [planetinfo.record]: land_color = 0.66, 0.600764, 0.213984, 1; 13:31:52.604 [planetinfo.record]: land_fraction = 0.270000; 13:31:52.604 [planetinfo.record]: polar_cloud_color = 0.763672, 0.759885, 0.745028, 1; 13:31:52.604 [planetinfo.record]: polar_land_color = 0.934, 0.913043, 0.776205, 1; 13:31:52.604 [planetinfo.record]: polar_sea_color = 0.937305, 0.902908, 0.877808, 1; 13:31:52.604 [planetinfo.record]: sea_color = 0.626953, 0.534923, 0.467766, 1; 13:31:52.604 [planetinfo.record]: rotation_speed = 0.004960 13:31:52.605 [planetinfo.record]: planet zpos = 559260.000000 13:31:52.605 [planetinfo.record]: sun_radius = 133111.505127 13:31:52.605 [planetinfo.record]: sun_vector = 0.230 -0.953 -0.195 13:31:52.605 [planetinfo.record]: sun_distance = 817380 13:31:52.605 [planetinfo.record]: corona_flare = 0.028120 13:31:52.605 [planetinfo.record]: corona_hues = 0.876312 13:31:52.605 [planetinfo.record]: sun_color = 0.799551, 0.456017, 0.261432, 1 13:31:52.605 [planetinfo.record]: corona_shimmer = 1.568297 13:31:52.605 [planetinfo.record]: station_vector = -0.096 -0.990 0.104 13:31:52.605 [planetinfo.record]: station = coriolis 13:31:57.869 [PLANETINFO OVER]: Done 13:31:57.869 [PLANETINFO LOGGING]: 4 224 13:31:58.873 [planetinfo.record]: seed = 68 127 181 152 13:31:58.873 [planetinfo.record]: coordinates = 127 166 13:31:58.873 [planetinfo.record]: government = 0; 13:31:58.873 [planetinfo.record]: economy = 6; 13:31:58.873 [planetinfo.record]: techlevel = 4; 13:31:58.873 [planetinfo.record]: population = 23; 13:31:58.873 [planetinfo.record]: productivity = 2944; 13:31:58.873 [planetinfo.record]: name = "Edrive"; 13:31:58.873 [planetinfo.record]: inhabitant = "Black Bug-Eyed Frog"; 13:31:58.874 [planetinfo.record]: inhabitants = "Black Bug-Eyed Frogs"; 13:31:58.874 [planetinfo.record]: description = "Edrive is cursed by vicious mountain monkeys."; 13:31:58.891 [planetinfo.record]: air_color = 0.523397, 0.964529, 0.829787, 1; 13:31:58.891 [planetinfo.record]: cloud_alpha = 1.000000; 13:31:58.891 [planetinfo.record]: cloud_color = 0.896484, 0.268114, 0.20311, 1; 13:31:58.891 [planetinfo.record]: cloud_fraction = 0.290000; 13:31:58.891 [planetinfo.record]: land_color = 0.66, 0.291328, 0.343173, 1; 13:31:58.891 [planetinfo.record]: land_fraction = 0.480000; 13:31:58.891 [planetinfo.record]: polar_cloud_color = 0.903418, 0.507649, 0.466707, 1; 13:31:58.891 [planetinfo.record]: polar_land_color = 0.934, 0.803568, 0.82191, 1; 13:31:58.891 [planetinfo.record]: polar_sea_color = 0.944922, 0.896586, 0.891585, 1; 13:31:58.891 [planetinfo.record]: sea_color = 0.550781, 0.438084, 0.426425, 1; 13:31:58.891 [planetinfo.record]: rotation_speed = 0.003956 13:31:58.891 [planetinfo.record]: planet zpos = 598920.000000 13:31:58.892 [planetinfo.record]: sun_radius = 123840.558319 13:31:58.892 [planetinfo.record]: sun_vector = -0.017 0.850 0.526 13:31:58.892 [planetinfo.record]: sun_distance = 948290 13:31:58.892 [planetinfo.record]: corona_flare = 0.075179 13:31:58.892 [planetinfo.record]: corona_hues = 0.736366 13:31:58.892 [planetinfo.record]: sun_color = 0.837088, 0.556527, 0.504078, 1 13:31:58.892 [planetinfo.record]: corona_shimmer = 0.480110 13:31:58.892 [planetinfo.record]: station_vector = -0.035 0.876 0.481 13:31:58.892 [planetinfo.record]: station = coriolis 13:32:03.744 [PLANETINFO OVER]: Done 13:32:03.745 [PLANETINFO LOGGING]: 4 225 13:32:04.752 [planetinfo.record]: seed = 232 45 27 195 13:32:04.752 [planetinfo.record]: coordinates = 45 214 13:32:04.752 [planetinfo.record]: government = 5; 13:32:04.752 [planetinfo.record]: economy = 6; 13:32:04.752 [planetinfo.record]: techlevel = 5; 13:32:04.752 [planetinfo.record]: population = 32; 13:32:04.752 [planetinfo.record]: productivity = 9216; 13:32:04.752 [planetinfo.record]: name = "Gesoed"; 13:32:04.752 [planetinfo.record]: inhabitant = "Human Colonial"; 13:32:04.752 [planetinfo.record]: inhabitants = "Human Colonials"; 13:32:04.752 [planetinfo.record]: description = "This world is very fabled for the Gesoedian edible poet."; 13:32:04.772 [planetinfo.record]: air_color = 0.591942, 0.510393, 0.9164, 1; 13:32:04.772 [planetinfo.record]: cloud_alpha = 1.000000; 13:32:04.772 [planetinfo.record]: cloud_color = 0.541683, 0.202675, 0.751953, 1; 13:32:04.772 [planetinfo.record]: cloud_fraction = 0.500000; 13:32:04.772 [planetinfo.record]: land_color = 0.592969, 0.658429, 0.66, 1; 13:32:04.772 [planetinfo.record]: land_fraction = 0.240000; 13:32:04.772 [planetinfo.record]: polar_cloud_color = 0.691855, 0.455623, 0.838379, 1; 13:32:04.772 [planetinfo.record]: polar_land_color = 0.910285, 0.933444, 0.934, 1; 13:32:04.772 [planetinfo.record]: polar_sea_color = 0.925798, 0.943359, 0.718575, 1; 13:32:04.772 [planetinfo.record]: sea_color = 0.52423, 0.566406, 0.0265503, 1; 13:32:04.772 [planetinfo.record]: rotation_speed = 0.002322 13:32:04.772 [planetinfo.record]: planet zpos = 508060.000000 13:32:04.772 [planetinfo.record]: sun_radius = 76637.263641 13:32:04.772 [planetinfo.record]: sun_vector = 0.054 -0.697 0.715 13:32:04.772 [planetinfo.record]: sun_distance = 798380 13:32:04.772 [planetinfo.record]: corona_flare = 0.024718 13:32:04.772 [planetinfo.record]: corona_hues = 0.633934 13:32:04.772 [planetinfo.record]: sun_color = 0.303622, 0.440307, 0.8017, 1 13:32:04.773 [planetinfo.record]: corona_shimmer = 0.533742 13:32:04.773 [planetinfo.record]: station_vector = 0.884 -0.064 0.464 13:32:04.773 [planetinfo.record]: station = coriolis 13:32:09.561 [PLANETINFO OVER]: Done 13:32:09.562 [PLANETINFO LOGGING]: 4 226 13:32:10.564 [planetinfo.record]: seed = 80 67 133 195 13:32:10.564 [planetinfo.record]: coordinates = 67 184 13:32:10.564 [planetinfo.record]: government = 2; 13:32:10.564 [planetinfo.record]: economy = 0; 13:32:10.564 [planetinfo.record]: techlevel = 11; 13:32:10.564 [planetinfo.record]: population = 47; 13:32:10.564 [planetinfo.record]: productivity = 22560; 13:32:10.564 [planetinfo.record]: name = "Geonbi"; 13:32:10.564 [planetinfo.record]: inhabitant = "Large Bony Feline"; 13:32:10.564 [planetinfo.record]: inhabitants = "Large Bony Felines"; 13:32:10.564 [planetinfo.record]: description = "Geonbi is mildly famous for its hoopy casinos and its great parking meters."; 13:32:10.567 [planetinfo.record]: air_color = 0.481064, 0.478547, 0.917051, 1; 13:32:10.567 [planetinfo.record]: cloud_alpha = 1.000000; 13:32:10.567 [planetinfo.record]: cloud_color = 0.143382, 0.123688, 0.753906, 1; 13:32:10.567 [planetinfo.record]: cloud_fraction = 0.280000; 13:32:10.567 [planetinfo.record]: land_color = 0.378984, 0.66, 0.455825, 1; 13:32:10.567 [planetinfo.record]: land_fraction = 0.310000; 13:32:10.567 [planetinfo.record]: polar_cloud_color = 0.414481, 0.400778, 0.839258, 1; 13:32:10.567 [planetinfo.record]: polar_land_color = 0.83458, 0.934, 0.861765, 1; 13:32:10.567 [planetinfo.record]: polar_sea_color = 0.913916, 0.935547, 0.885206, 1; 13:32:10.567 [planetinfo.record]: sea_color = 0.584923, 0.644531, 0.505806, 1; 13:32:10.567 [planetinfo.record]: rotation_speed = 0.003810 13:32:10.567 [planetinfo.record]: planet zpos = 328590.000000 13:32:10.568 [planetinfo.record]: sun_radius = 93669.965973 13:32:10.568 [planetinfo.record]: sun_vector = 0.049 0.176 -0.983 13:32:10.568 [planetinfo.record]: sun_distance = 693690 13:32:10.568 [planetinfo.record]: corona_flare = 0.059779 13:32:10.568 [planetinfo.record]: corona_hues = 0.503616 13:32:10.568 [planetinfo.record]: sun_color = 0.543427, 0.688569, 0.704788, 1 13:32:10.568 [planetinfo.record]: corona_shimmer = 0.478295 13:32:10.568 [planetinfo.record]: station_vector = -0.870 0.240 0.431 13:32:10.568 [planetinfo.record]: station = dodecahedron 13:32:15.916 [PLANETINFO OVER]: Done 13:32:15.917 [PLANETINFO LOGGING]: 4 227 13:32:16.919 [planetinfo.record]: seed = 60 73 243 206 13:32:16.919 [planetinfo.record]: coordinates = 73 198 13:32:16.919 [planetinfo.record]: government = 7; 13:32:16.919 [planetinfo.record]: economy = 6; 13:32:16.919 [planetinfo.record]: techlevel = 6; 13:32:16.919 [planetinfo.record]: population = 38; 13:32:16.919 [planetinfo.record]: productivity = 13376; 13:32:16.919 [planetinfo.record]: name = "Reriveza"; 13:32:16.919 [planetinfo.record]: inhabitant = "Frog"; 13:32:16.920 [planetinfo.record]: inhabitants = "Frogs"; 13:32:16.920 [planetinfo.record]: description = "Reriveza is most noted for the Rerivezaian mountain lobstoid and the Rerivezaian evil poet."; 13:32:16.948 [planetinfo.record]: air_color = 0.762789, 0.596951, 0.889084, 1; 13:32:16.949 [planetinfo.record]: cloud_alpha = 1.000000; 13:32:16.949 [planetinfo.record]: cloud_color = 0.669922, 0.410851, 0.483714, 1; 13:32:16.949 [planetinfo.record]: cloud_fraction = 0.300000; 13:32:16.949 [planetinfo.record]: land_color = 0.447385, 0.190781, 0.66, 1; 13:32:16.949 [planetinfo.record]: land_fraction = 0.480000; 13:32:16.949 [planetinfo.record]: polar_cloud_color = 0.801465, 0.607751, 0.662233, 1; 13:32:16.949 [planetinfo.record]: polar_land_color = 0.858779, 0.767996, 0.934, 1; 13:32:16.949 [planetinfo.record]: polar_sea_color = 0.940039, 0.929597, 0.888631, 1; 13:32:16.949 [planetinfo.record]: sea_color = 0.599609, 0.572967, 0.468445, 1; 13:32:16.949 [planetinfo.record]: rotation_speed = 0.000926 13:32:16.949 [planetinfo.record]: planet zpos = 776760.000000 13:32:16.949 [planetinfo.record]: sun_radius = 137978.924713 13:32:16.949 [planetinfo.record]: sun_vector = 0.407 0.281 -0.869 13:32:16.949 [planetinfo.record]: sun_distance = 1359330 13:32:16.949 [planetinfo.record]: corona_flare = 0.033012 13:32:16.950 [planetinfo.record]: corona_hues = 0.681435 13:32:16.950 [planetinfo.record]: sun_color = 0.356443, 0.414, 0.672821, 1 13:32:16.950 [planetinfo.record]: corona_shimmer = 0.671888 13:32:16.950 [planetinfo.record]: station_vector = -0.336 -0.798 0.500 13:32:16.950 [planetinfo.record]: station = coriolis 13:32:21.761 [PLANETINFO OVER]: Done 13:32:21.762 [PLANETINFO LOGGING]: 4 228 13:32:22.765 [planetinfo.record]: seed = 12 164 37 121 13:32:22.765 [planetinfo.record]: coordinates = 164 246 13:32:22.765 [planetinfo.record]: government = 1; 13:32:22.765 [planetinfo.record]: economy = 6; 13:32:22.765 [planetinfo.record]: techlevel = 2; 13:32:22.765 [planetinfo.record]: population = 16; 13:32:22.765 [planetinfo.record]: productivity = 2560; 13:32:22.765 [planetinfo.record]: name = "Orbeat"; 13:32:22.765 [planetinfo.record]: inhabitant = "Human Colonial"; 13:32:22.766 [planetinfo.record]: inhabitants = "Human Colonials"; 13:32:22.766 [planetinfo.record]: description = "Orbeat is famous for its inhabitants’ ancient loathing of discos but plagued by occasional civil war."; 13:32:22.782 [planetinfo.record]: air_color = 0.614999, 0.559802, 0.870873, 1; 13:32:22.783 [planetinfo.record]: cloud_alpha = 1.000000; 13:32:22.783 [planetinfo.record]: cloud_color = 0.483468, 0.32444, 0.615234, 1; 13:32:22.783 [planetinfo.record]: cloud_fraction = 0.130000; 13:32:22.783 [planetinfo.record]: land_color = 0.459309, 0.66, 0.417656, 1; 13:32:22.783 [planetinfo.record]: land_fraction = 0.530000; 13:32:22.783 [planetinfo.record]: polar_cloud_color = 0.672867, 0.547364, 0.776855, 1; 13:32:22.783 [planetinfo.record]: polar_land_color = 0.862998, 0.934, 0.848262, 1; 13:32:22.783 [planetinfo.record]: polar_sea_color = 0.936133, 0.899265, 0.88311, 1; 13:32:22.783 [planetinfo.record]: sea_color = 0.638672, 0.538061, 0.493973, 1; 13:32:22.783 [planetinfo.record]: rotation_speed = 0.004885 13:32:22.783 [planetinfo.record]: planet zpos = 581240.000000 13:32:22.783 [planetinfo.record]: sun_radius = 129460.257568 13:32:22.783 [planetinfo.record]: sun_vector = -0.232 0.501 -0.834 13:32:22.783 [planetinfo.record]: sun_distance = 898280 13:32:22.783 [planetinfo.record]: corona_flare = 0.077367 13:32:22.783 [planetinfo.record]: corona_hues = 0.583138 13:32:22.783 [planetinfo.record]: sun_color = 0.556679, 0.626088, 0.651468, 1 13:32:22.783 [planetinfo.record]: corona_shimmer = 0.769997 13:32:22.783 [planetinfo.record]: station_vector = 0.277 -0.947 0.163 13:32:22.783 [planetinfo.record]: station = coriolis 13:32:27.879 [PLANETINFO OVER]: Done 13:32:27.880 [PLANETINFO LOGGING]: 4 229 13:32:28.884 [planetinfo.record]: seed = 0 190 219 2 13:32:28.884 [planetinfo.record]: coordinates = 190 49 13:32:28.884 [planetinfo.record]: government = 0; 13:32:28.884 [planetinfo.record]: economy = 3; 13:32:28.884 [planetinfo.record]: techlevel = 6; 13:32:28.884 [planetinfo.record]: population = 28; 13:32:28.884 [planetinfo.record]: productivity = 6272; 13:32:28.884 [planetinfo.record]: name = "Xeaten"; 13:32:28.884 [planetinfo.record]: inhabitant = "Large Green Frog"; 13:32:28.884 [planetinfo.record]: inhabitants = "Large Green Frogs"; 13:32:28.884 [planetinfo.record]: description = "This planet is plagued by deadly earthquakes."; 13:32:28.907 [planetinfo.record]: air_color = 0.606867, 0.838448, 0.985992, 1; 13:32:28.908 [planetinfo.record]: cloud_alpha = 1.000000; 13:32:28.908 [planetinfo.record]: cloud_color = 0.427917, 0.960938, 0.523695, 1; 13:32:28.908 [planetinfo.record]: cloud_fraction = 0.450000; 13:32:28.908 [planetinfo.record]: land_color = 0.66, 0.345469, 0.433931, 1; 13:32:28.908 [planetinfo.record]: land_fraction = 0.240000; 13:32:28.908 [planetinfo.record]: polar_cloud_color = 0.60917, 0.932422, 0.667254, 1; 13:32:28.908 [planetinfo.record]: polar_land_color = 0.934, 0.822723, 0.854019, 1; 13:32:28.908 [planetinfo.record]: polar_sea_color = 0.930469, 0.894016, 0.858685, 1; 13:32:28.908 [planetinfo.record]: sea_color = 0.695312, 0.586352, 0.480743, 1; 13:32:28.908 [planetinfo.record]: rotation_speed = 0.000694 13:32:28.908 [planetinfo.record]: planet zpos = 422160.000000 13:32:28.908 [planetinfo.record]: sun_radius = 81521.769714 13:32:28.908 [planetinfo.record]: sun_vector = 0.757 0.000 0.653 13:32:28.908 [planetinfo.record]: sun_distance = 738780 13:32:28.908 [planetinfo.record]: corona_flare = 0.029312 13:32:28.908 [planetinfo.record]: corona_hues = 0.848595 13:32:28.908 [planetinfo.record]: sun_color = 0.38724, 0.621754, 0.705502, 1 13:32:28.908 [planetinfo.record]: corona_shimmer = 0.328570 13:32:28.908 [planetinfo.record]: station_vector = 0.796 0.508 0.329 13:32:28.908 [planetinfo.record]: station = coriolis 13:32:33.730 [PLANETINFO OVER]: Done 13:32:33.731 [PLANETINFO LOGGING]: 4 230 13:32:34.736 [planetinfo.record]: seed = 120 167 21 76 13:32:34.736 [planetinfo.record]: coordinates = 167 178 13:32:34.736 [planetinfo.record]: government = 7; 13:32:34.736 [planetinfo.record]: economy = 2; 13:32:34.736 [planetinfo.record]: techlevel = 12; 13:32:34.736 [planetinfo.record]: population = 58; 13:32:34.736 [planetinfo.record]: productivity = 40832; 13:32:34.736 [planetinfo.record]: name = "Inbior"; 13:32:34.736 [planetinfo.record]: inhabitant = "Human Colonial"; 13:32:34.736 [planetinfo.record]: inhabitants = "Human Colonials"; 13:32:34.736 [planetinfo.record]: description = "This world is most fabled for Inbiorian evil juice but cursed by dreadful solar activity."; 13:32:34.760 [planetinfo.record]: air_color = 0.431693, 0.834966, 0.837703, 1; 13:32:34.760 [planetinfo.record]: cloud_alpha = 1.000000; 13:32:34.760 [planetinfo.record]: cloud_color = 0.501841, 0.515625, 0.0745239, 1; 13:32:34.760 [planetinfo.record]: cloud_fraction = 0.390000; 13:32:34.760 [planetinfo.record]: land_color = 0.506224, 0.346718, 0.595703, 1; 13:32:34.760 [planetinfo.record]: land_fraction = 0.280000; 13:32:34.760 [planetinfo.record]: polar_cloud_color = 0.7198, 0.732031, 0.340638, 1; 13:32:34.760 [planetinfo.record]: polar_land_color = 0.905115, 0.842162, 0.94043, 1; 13:32:34.760 [planetinfo.record]: polar_sea_color = 0.879, 0.928125, 0.897038, 1; 13:32:34.760 [planetinfo.record]: sea_color = 0.566577, 0.71875, 0.622453, 1; 13:32:34.760 [planetinfo.record]: rotation_speed = 0.002208 13:32:34.760 [planetinfo.record]: planet zpos = 726600.000000 13:32:34.760 [planetinfo.record]: sun_radius = 132308.993530 13:32:34.760 [planetinfo.record]: sun_vector = -0.452 -0.891 -0.045 13:32:34.760 [planetinfo.record]: sun_distance = 1271550 13:32:34.760 [planetinfo.record]: corona_flare = 0.030186 13:32:34.760 [planetinfo.record]: corona_hues = 0.846230 13:32:34.761 [planetinfo.record]: sun_color = 0.204908, 0.358695, 0.680072, 1 13:32:34.761 [planetinfo.record]: corona_shimmer = 0.424549 13:32:34.761 [planetinfo.record]: station_vector = -0.180 -0.979 0.099 13:32:34.761 [planetinfo.record]: station = icosahedron 13:32:39.318 [PLANETINFO OVER]: Done 13:32:39.318 [PLANETINFO LOGGING]: 4 231 13:32:40.323 [planetinfo.record]: seed = 52 140 83 204 13:32:40.323 [planetinfo.record]: coordinates = 140 153 13:32:40.323 [planetinfo.record]: government = 6; 13:32:40.323 [planetinfo.record]: economy = 1; 13:32:40.323 [planetinfo.record]: techlevel = 9; 13:32:40.323 [planetinfo.record]: population = 44; 13:32:40.323 [planetinfo.record]: productivity = 31680; 13:32:40.323 [planetinfo.record]: name = "Inenares"; 13:32:40.323 [planetinfo.record]: inhabitant = "Human Colonial"; 13:32:40.323 [planetinfo.record]: inhabitants = "Human Colonials"; 13:32:40.323 [planetinfo.record]: description = "The planet Inenares is a boring planet."; 13:32:40.343 [planetinfo.record]: air_color = 0.595602, 0.554441, 0.869572, 1; 13:32:40.343 [planetinfo.record]: cloud_alpha = 1.000000; 13:32:40.343 [planetinfo.record]: cloud_color = 0.434094, 0.312828, 0.611328, 1; 13:32:40.344 [planetinfo.record]: cloud_fraction = 0.390000; 13:32:40.344 [planetinfo.record]: land_color = 0.0180469, 0.66, 0.629908, 1; 13:32:40.344 [planetinfo.record]: land_fraction = 0.640000; 13:32:40.344 [planetinfo.record]: polar_cloud_color = 0.634651, 0.538557, 0.775098, 1; 13:32:40.344 [planetinfo.record]: polar_land_color = 0.706885, 0.934, 0.923354, 1; 13:32:40.344 [planetinfo.record]: polar_sea_color = 0.88839, 0.929102, 0.873664, 1; 13:32:40.344 [planetinfo.record]: sea_color = 0.584717, 0.708984, 0.53977, 1; 13:32:40.344 [planetinfo.record]: rotation_speed = 0.001872 13:32:40.344 [planetinfo.record]: planet zpos = 783640.000000 13:32:40.344 [planetinfo.record]: sun_radius = 143037.147827 13:32:40.344 [planetinfo.record]: sun_vector = -0.765 -0.066 0.641 13:32:40.344 [planetinfo.record]: sun_distance = 964480 13:32:40.344 [planetinfo.record]: corona_flare = 0.026723 13:32:40.344 [planetinfo.record]: corona_hues = 0.658592 13:32:40.344 [planetinfo.record]: sun_color = 0.226412, 0.284694, 0.690588, 1 13:32:40.344 [planetinfo.record]: corona_shimmer = 0.293688 13:32:40.344 [planetinfo.record]: station_vector = -0.988 0.135 0.079 13:32:40.344 [planetinfo.record]: station = coriolis 13:32:45.758 [PLANETINFO OVER]: Done 13:32:45.758 [PLANETINFO LOGGING]: 4 232 13:32:46.766 [planetinfo.record]: seed = 148 9 213 70 13:32:46.766 [planetinfo.record]: coordinates = 9 74 13:32:46.766 [planetinfo.record]: government = 2; 13:32:46.766 [planetinfo.record]: economy = 2; 13:32:46.766 [planetinfo.record]: techlevel = 7; 13:32:46.766 [planetinfo.record]: population = 33; 13:32:46.766 [planetinfo.record]: productivity = 12672; 13:32:46.766 [planetinfo.record]: name = "Bianmadi"; 13:32:46.766 [planetinfo.record]: inhabitant = "Fierce Yellow Bony Humanoid"; 13:32:46.766 [planetinfo.record]: inhabitants = "Fierce Yellow Bony Humanoids"; 13:32:46.766 [planetinfo.record]: description = "The planet Bianmadi is reasonably fabled for mud hockey and the Bianmadiian edible moth."; 13:32:46.772 [planetinfo.record]: air_color = 0.44382, 0.691359, 0.876727, 1; 13:32:46.772 [planetinfo.record]: cloud_alpha = 1.000000; 13:32:46.772 [planetinfo.record]: cloud_color = 0.0741577, 0.632812, 0.240008, 1; 13:32:46.772 [planetinfo.record]: cloud_fraction = 0.310000; 13:32:46.772 [planetinfo.record]: land_color = 0.538847, 0.398026, 0.550781, 1; 13:32:46.772 [planetinfo.record]: land_fraction = 0.550000; 13:32:46.772 [planetinfo.record]: polar_cloud_color = 0.351765, 0.784766, 0.480312, 1; 13:32:46.772 [planetinfo.record]: polar_land_color = 0.939803, 0.879405, 0.944922, 1; 13:32:46.772 [planetinfo.record]: polar_sea_color = 0.867471, 0.920508, 0.875758, 1; 13:32:46.772 [planetinfo.record]: sea_color = 0.611717, 0.794922, 0.640343, 1; 13:32:46.772 [planetinfo.record]: rotation_speed = 0.000775 13:32:46.772 [planetinfo.record]: planet zpos = 566930.000000 13:32:46.772 [planetinfo.record]: sun_radius = 78611.257172 13:32:46.772 [planetinfo.record]: sun_vector = 0.045 -0.954 0.297 13:32:46.773 [planetinfo.record]: sun_distance = 959420 13:32:46.773 [planetinfo.record]: corona_flare = 0.015117 13:32:46.773 [planetinfo.record]: corona_hues = 0.826859 13:32:46.773 [planetinfo.record]: sun_color = 0.842859, 0.835262, 0.708378, 1 13:32:46.773 [planetinfo.record]: corona_shimmer = 0.326879 13:32:46.773 [planetinfo.record]: station_vector = 0.967 -0.164 0.196 13:32:46.773 [planetinfo.record]: station = coriolis 13:32:52.391 [PLANETINFO OVER]: Done 13:32:52.392 [PLANETINFO LOGGING]: 4 233 13:32:53.397 [planetinfo.record]: seed = 216 205 219 84 13:32:53.398 [planetinfo.record]: coordinates = 205 235 13:32:53.398 [planetinfo.record]: government = 3; 13:32:53.398 [planetinfo.record]: economy = 3; 13:32:53.398 [planetinfo.record]: techlevel = 7; 13:32:53.398 [planetinfo.record]: population = 35; 13:32:53.398 [planetinfo.record]: productivity = 13720; 13:32:53.398 [planetinfo.record]: name = "Rareat"; 13:32:53.398 [planetinfo.record]: inhabitant = "Yellow Feline"; 13:32:53.398 [planetinfo.record]: inhabitants = "Yellow Felines"; 13:32:53.398 [planetinfo.record]: description = "Rareat is an unremarkable planet."; 13:32:53.410 [planetinfo.record]: air_color = 0.703483, 0.807299, 0.924205, 1; 13:32:53.410 [planetinfo.record]: cloud_alpha = 1.000000; 13:32:53.410 [planetinfo.record]: cloud_color = 0.684525, 0.775391, 0.739186, 1; 13:32:53.410 [planetinfo.record]: cloud_fraction = 0.320000; 13:32:53.410 [planetinfo.record]: land_color = 0.518203, 0.543682, 0.66, 1; 13:32:53.411 [planetinfo.record]: land_fraction = 0.580000; 13:32:53.411 [planetinfo.record]: polar_cloud_color = 0.786749, 0.848926, 0.824152, 1; 13:32:53.411 [planetinfo.record]: polar_land_color = 0.883834, 0.892848, 0.934, 1; 13:32:53.411 [planetinfo.record]: polar_sea_color = 0.95, 0.82235, 0.729199, 1; 13:32:53.411 [planetinfo.record]: sea_color = 0.5, 0.231262, 0.0351562, 1; 13:32:53.411 [planetinfo.record]: rotation_speed = 0.004171 13:32:53.411 [planetinfo.record]: planet zpos = 566300.000000 13:32:53.411 [planetinfo.record]: sun_radius = 107360.136414 13:32:53.411 [planetinfo.record]: sun_vector = 0.226 -0.222 -0.948 13:32:53.411 [planetinfo.record]: sun_distance = 687650 13:32:53.412 [planetinfo.record]: corona_flare = 0.078506 13:32:53.412 [planetinfo.record]: corona_hues = 0.754204 13:32:53.412 [planetinfo.record]: sun_color = 0.662485, 0.381114, 0.248328, 1 13:32:53.412 [planetinfo.record]: corona_shimmer = 0.292157 13:32:53.412 [planetinfo.record]: station_vector = 0.673 0.676 0.299 13:32:53.412 [planetinfo.record]: station = coriolis 13:32:58.490 [PLANETINFO OVER]: Done 13:32:58.490 [PLANETINFO LOGGING]: 4 234 13:32:59.493 [planetinfo.record]: seed = 96 148 229 211 13:32:59.493 [planetinfo.record]: coordinates = 148 49 13:32:59.493 [planetinfo.record]: government = 4; 13:32:59.493 [planetinfo.record]: economy = 1; 13:32:59.493 [planetinfo.record]: techlevel = 8; 13:32:59.493 [planetinfo.record]: population = 38; 13:32:59.493 [planetinfo.record]: productivity = 21888; 13:32:59.493 [planetinfo.record]: name = "Beorle"; 13:32:59.493 [planetinfo.record]: inhabitant = "Furry Rodent"; 13:32:59.493 [planetinfo.record]: inhabitants = "Furry Rodents"; 13:32:59.493 [planetinfo.record]: description = "The world Beorle is a boring world."; 13:32:59.508 [planetinfo.record]: air_color = 0.627184, 0.77959, 0.993797, 1; 13:32:59.508 [planetinfo.record]: cloud_alpha = 1.000000; 13:32:59.508 [planetinfo.record]: cloud_color = 0.484497, 0.984375, 0.867216, 1; 13:32:59.508 [planetinfo.record]: cloud_fraction = 0.190000; 13:32:59.508 [planetinfo.record]: land_color = 0.66, 0.114485, 0.0876563, 1; 13:32:59.508 [planetinfo.record]: land_fraction = 0.320000; 13:32:59.508 [planetinfo.record]: polar_cloud_color = 0.643687, 0.942969, 0.872824, 1; 13:32:59.508 [planetinfo.record]: polar_land_color = 0.934, 0.741003, 0.731512, 1; 13:32:59.508 [planetinfo.record]: polar_sea_color = 0.94375, 0.922259, 0.894627, 1; 13:32:59.508 [planetinfo.record]: sea_color = 0.5625, 0.511262, 0.445386, 1; 13:32:59.508 [planetinfo.record]: rotation_speed = 0.000705 13:32:59.508 [planetinfo.record]: planet zpos = 522480.000000 13:32:59.508 [planetinfo.record]: sun_radius = 109739.113770 13:32:59.508 [planetinfo.record]: sun_vector = -0.014 1.000 0.019 13:32:59.508 [planetinfo.record]: sun_distance = 671760 13:32:59.508 [planetinfo.record]: corona_flare = 0.028908 13:32:59.508 [planetinfo.record]: corona_hues = 0.751236 13:32:59.508 [planetinfo.record]: sun_color = 0.710477, 0.636367, 0.281579, 1 13:32:59.508 [planetinfo.record]: corona_shimmer = 0.984882 13:32:59.508 [planetinfo.record]: station_vector = -0.099 -0.824 0.558 13:32:59.508 [planetinfo.record]: station = coriolis 13:33:04.260 [PLANETINFO OVER]: Done 13:33:04.260 [PLANETINFO LOGGING]: 4 235 13:33:05.269 [planetinfo.record]: seed = 236 110 243 9 13:33:05.269 [planetinfo.record]: coordinates = 110 1 13:33:05.269 [planetinfo.record]: government = 5; 13:33:05.269 [planetinfo.record]: economy = 1; 13:33:05.269 [planetinfo.record]: techlevel = 11; 13:33:05.269 [planetinfo.record]: population = 51; 13:33:05.269 [planetinfo.record]: productivity = 33048; 13:33:05.269 [planetinfo.record]: name = "Esqube"; 13:33:05.269 [planetinfo.record]: inhabitant = "Small Green Rodent"; 13:33:05.269 [planetinfo.record]: inhabitants = "Small Green Rodents"; 13:33:05.269 [planetinfo.record]: description = "The world Esqube is a boring world."; 13:33:05.285 [planetinfo.record]: air_color = 0.437793, 0.65868, 0.892336, 1; 13:33:05.286 [planetinfo.record]: cloud_alpha = 1.000000; 13:33:05.286 [planetinfo.record]: cloud_color = 0.0477905, 0.679688, 0.398296, 1; 13:33:05.286 [planetinfo.record]: cloud_fraction = 0.250000; 13:33:05.286 [planetinfo.record]: land_color = 0.0670312, 0.66, 0.493228, 1; 13:33:05.286 [planetinfo.record]: land_fraction = 0.480000; 13:33:05.286 [planetinfo.record]: polar_cloud_color = 0.337611, 0.805859, 0.597343, 1; 13:33:05.286 [planetinfo.record]: polar_land_color = 0.724215, 0.934, 0.874998, 1; 13:33:05.286 [planetinfo.record]: polar_sea_color = 0.882495, 0.930664, 0.89115, 1; 13:33:05.286 [planetinfo.record]: sea_color = 0.549812, 0.693359, 0.575606, 1; 13:33:05.286 [planetinfo.record]: rotation_speed = 0.002716 13:33:05.286 [planetinfo.record]: planet zpos = 627600.000000 13:33:05.286 [planetinfo.record]: sun_radius = 160316.386414 13:33:05.286 [planetinfo.record]: sun_vector = -0.198 0.123 0.972 13:33:05.286 [planetinfo.record]: sun_distance = 1255200 13:33:05.286 [planetinfo.record]: corona_flare = 0.080963 13:33:05.286 [planetinfo.record]: corona_hues = 0.668800 13:33:05.286 [planetinfo.record]: sun_color = 0.812241, 0.688007, 0.530089, 1 13:33:05.286 [planetinfo.record]: corona_shimmer = 0.380302 13:33:05.286 [planetinfo.record]: station_vector = 0.757 -0.193 0.624 13:33:05.286 [planetinfo.record]: station = dodecahedron 13:33:10.428 [PLANETINFO OVER]: Done 13:33:10.429 [PLANETINFO LOGGING]: 4 236 13:33:11.447 [planetinfo.record]: seed = 220 119 197 229 13:33:11.447 [planetinfo.record]: coordinates = 119 243 13:33:11.447 [planetinfo.record]: government = 3; 13:33:11.447 [planetinfo.record]: economy = 3; 13:33:11.447 [planetinfo.record]: techlevel = 9; 13:33:11.447 [planetinfo.record]: population = 43; 13:33:11.447 [planetinfo.record]: productivity = 16856; 13:33:11.447 [planetinfo.record]: name = "Ceatrece"; 13:33:11.447 [planetinfo.record]: inhabitant = "Fierce Fat Humanoid"; 13:33:11.447 [planetinfo.record]: inhabitants = "Fierce Fat Humanoids"; 13:33:11.447 [planetinfo.record]: description = "The planet Ceatrece is a boring planet."; 13:33:11.450 [planetinfo.record]: air_color = 0.547103, 0.965559, 0.989244, 1; 13:33:11.450 [planetinfo.record]: cloud_alpha = 1.000000; 13:33:11.450 [planetinfo.record]: cloud_color = 0.846877, 0.970703, 0.250259, 1; 13:33:11.450 [planetinfo.record]: cloud_fraction = 0.540000; 13:33:11.450 [planetinfo.record]: land_color = 0.104555, 0.00773438, 0.66, 1; 13:33:11.451 [planetinfo.record]: land_fraction = 0.690000; 13:33:11.451 [planetinfo.record]: polar_cloud_color = 0.862127, 0.936816, 0.502258, 1; 13:33:11.451 [planetinfo.record]: polar_land_color = 0.73749, 0.703236, 0.934, 1; 13:33:11.451 [planetinfo.record]: polar_sea_color = 0.946094, 0.906362, 0.89574, 1; 13:33:11.451 [planetinfo.record]: sea_color = 0.539062, 0.448509, 0.424301, 1; 13:33:11.451 [planetinfo.record]: rotation_speed = 0.001802 13:33:11.451 [planetinfo.record]: planet zpos = 505800.000000 13:33:11.451 [planetinfo.record]: sun_radius = 87966.643524 13:33:11.451 [planetinfo.record]: sun_vector = 0.289 -0.059 -0.955 13:33:11.451 [planetinfo.record]: sun_distance = 927300 13:33:11.451 [planetinfo.record]: corona_flare = 0.060480 13:33:11.451 [planetinfo.record]: corona_hues = 0.631302 13:33:11.451 [planetinfo.record]: sun_color = 0.652391, 0.7065, 0.845694, 1 13:33:11.451 [planetinfo.record]: corona_shimmer = 0.348755 13:33:11.452 [planetinfo.record]: station_vector = 0.717 0.693 0.074 13:33:11.452 [planetinfo.record]: station = coriolis 13:33:16.852 [PLANETINFO OVER]: Done 13:33:16.853 [PLANETINFO LOGGING]: 4 237 13:33:17.855 [planetinfo.record]: seed = 112 229 27 229 13:33:17.855 [planetinfo.record]: coordinates = 229 174 13:33:17.855 [planetinfo.record]: government = 6; 13:33:17.855 [planetinfo.record]: economy = 6; 13:33:17.855 [planetinfo.record]: techlevel = 5; 13:33:17.855 [planetinfo.record]: population = 33; 13:33:17.855 [planetinfo.record]: productivity = 10560; 13:33:17.855 [planetinfo.record]: name = "Ceorge"; 13:33:17.855 [planetinfo.record]: inhabitant = "Human Colonial"; 13:33:17.855 [planetinfo.record]: inhabitants = "Human Colonials"; 13:33:17.855 [planetinfo.record]: description = "This planet is most fabled for Ceorgeian Sous brandy but cursed by deadly civil war."; 13:33:17.883 [planetinfo.record]: air_color = 0.781264, 0.496945, 0.971684, 1; 13:33:17.883 [planetinfo.record]: cloud_alpha = 1.000000; 13:33:17.883 [planetinfo.record]: cloud_color = 0.917969, 0.121918, 0.271177, 1; 13:33:17.883 [planetinfo.record]: cloud_fraction = 0.400000; 13:33:17.883 [planetinfo.record]: land_color = 0.406666, 0.293251, 0.630859, 1; 13:33:17.883 [planetinfo.record]: land_fraction = 0.340000; 13:33:17.883 [planetinfo.record]: polar_cloud_color = 0.913086, 0.4182, 0.510992, 1; 13:33:17.883 [planetinfo.record]: polar_land_color = 0.853675, 0.811565, 0.936914, 1; 13:33:17.883 [planetinfo.record]: polar_sea_color = 0.941406, 0.899921, 0.88551, 1; 13:33:17.883 [planetinfo.record]: sea_color = 0.585938, 0.482655, 0.446777, 1; 13:33:17.883 [planetinfo.record]: rotation_speed = 0.002678 13:33:17.883 [planetinfo.record]: planet zpos = 605500.000000 13:33:17.883 [planetinfo.record]: sun_radius = 94739.383698 13:33:17.884 [planetinfo.record]: sun_vector = 0.362 0.684 -0.634 13:33:17.884 [planetinfo.record]: sun_distance = 821750 13:33:17.884 [planetinfo.record]: corona_flare = 0.031168 13:33:17.884 [planetinfo.record]: corona_hues = 0.873146 13:33:17.884 [planetinfo.record]: sun_color = 0.330725, 0.47755, 0.667798, 1 13:33:17.885 [planetinfo.record]: corona_shimmer = 0.388878 13:33:17.885 [planetinfo.record]: station_vector = 0.340 -0.856 0.390 13:33:17.885 [planetinfo.record]: station = coriolis 13:33:22.634 [PLANETINFO OVER]: Done 13:33:22.635 [PLANETINFO LOGGING]: 4 238 13:33:23.639 [planetinfo.record]: seed = 8 162 245 94 13:33:23.639 [planetinfo.record]: coordinates = 162 67 13:33:23.639 [planetinfo.record]: government = 1; 13:33:23.639 [planetinfo.record]: economy = 3; 13:33:23.639 [planetinfo.record]: techlevel = 7; 13:33:23.639 [planetinfo.record]: population = 33; 13:33:23.639 [planetinfo.record]: productivity = 9240; 13:33:23.639 [planetinfo.record]: name = "Rizace"; 13:33:23.639 [planetinfo.record]: inhabitant = "Yellow Bug-Eyed Lobster"; 13:33:23.639 [planetinfo.record]: inhabitants = "Yellow Bug-Eyed Lobsters"; 13:33:23.639 [planetinfo.record]: description = "Rizace is reasonably notable for its exotic cuisine but plagued by lethal disease."; 13:33:23.642 [planetinfo.record]: air_color = 0.700381, 0.766764, 0.956074, 1; 13:33:23.643 [planetinfo.record]: cloud_alpha = 1.000000; 13:33:23.643 [planetinfo.record]: cloud_color = 0.694153, 0.829623, 0.871094, 1; 13:33:23.643 [planetinfo.record]: cloud_fraction = 0.480000; 13:33:23.643 [planetinfo.record]: land_color = 0.142822, 0.609375, 0.478157, 1; 13:33:23.643 [planetinfo.record]: land_fraction = 0.250000; 13:33:23.643 [planetinfo.record]: polar_cloud_color = 0.778751, 0.865451, 0.891992, 1; 13:33:23.643 [planetinfo.record]: polar_land_color = 0.75932, 0.939062, 0.88851, 1; 13:33:23.644 [planetinfo.record]: polar_sea_color = 0.937695, 0.887325, 0.875701, 1; 13:33:23.644 [planetinfo.record]: sea_color = 0.623047, 0.489174, 0.45828, 1; 13:33:23.644 [planetinfo.record]: rotation_speed = 0.004091 13:33:23.644 [planetinfo.record]: planet zpos = 787440.000000 13:33:23.644 [planetinfo.record]: sun_radius = 138532.334900 13:33:23.644 [planetinfo.record]: sun_vector = 0.359 -0.609 0.707 13:33:23.644 [planetinfo.record]: sun_distance = 1378020 13:33:23.644 [planetinfo.record]: corona_flare = 0.009218 13:33:23.644 [planetinfo.record]: corona_hues = 0.522545 13:33:23.644 [planetinfo.record]: sun_color = 0.653286, 0.653619, 0.35844, 1 13:33:23.645 [planetinfo.record]: corona_shimmer = 0.402714 13:33:23.645 [planetinfo.record]: station_vector = -0.941 -0.160 0.299 13:33:23.645 [planetinfo.record]: station = coriolis 13:33:28.580 [PLANETINFO OVER]: Done 13:33:28.580 [PLANETINFO LOGGING]: 4 239 13:33:29.601 [planetinfo.record]: seed = 100 233 211 115 13:33:29.601 [planetinfo.record]: coordinates = 233 69 13:33:29.601 [planetinfo.record]: government = 4; 13:33:29.601 [planetinfo.record]: economy = 5; 13:33:29.601 [planetinfo.record]: techlevel = 5; 13:33:29.601 [planetinfo.record]: population = 30; 13:33:29.601 [planetinfo.record]: productivity = 9600; 13:33:29.601 [planetinfo.record]: name = "Bexe"; 13:33:29.601 [planetinfo.record]: inhabitant = "Blue Fat Insect"; 13:33:29.601 [planetinfo.record]: inhabitants = "Blue Fat Insects"; 13:33:29.601 [planetinfo.record]: description = "The planet Bexe is reasonably noted for its inhabitants’ eccentric shyness and its inhabitants’ eccentric shyness."; 13:33:29.618 [planetinfo.record]: air_color = 0.571861, 0.914786, 0.985342, 1; 13:33:29.619 [planetinfo.record]: cloud_alpha = 1.000000; 13:33:29.619 [planetinfo.record]: cloud_color = 0.627607, 0.958984, 0.325905, 1; 13:33:29.619 [planetinfo.record]: cloud_fraction = 0.180000; 13:33:29.619 [planetinfo.record]: land_color = 0.605013, 0.66, 0.559453, 1; 13:33:29.619 [planetinfo.record]: land_fraction = 0.690000; 13:33:29.619 [planetinfo.record]: polar_cloud_color = 0.730358, 0.931543, 0.547191, 1; 13:33:29.619 [planetinfo.record]: polar_land_color = 0.914546, 0.934, 0.898428, 1; 13:33:29.619 [planetinfo.record]: polar_sea_color = 0.869259, 0.912965, 0.920117, 1; 13:33:29.619 [planetinfo.record]: sea_color = 0.622212, 0.773991, 0.798828, 1; 13:33:29.619 [planetinfo.record]: rotation_speed = 0.003721 13:33:29.619 [planetinfo.record]: planet zpos = 381700.000000 13:33:29.619 [planetinfo.record]: sun_radius = 102756.020966 13:33:29.619 [planetinfo.record]: sun_vector = -0.815 0.225 0.534 13:33:29.619 [planetinfo.record]: sun_distance = 801570 13:33:29.619 [planetinfo.record]: corona_flare = 0.042380 13:33:29.619 [planetinfo.record]: corona_hues = 0.823799 13:33:29.619 [planetinfo.record]: sun_color = 0.436737, 0.711781, 0.732385, 1 13:33:29.620 [planetinfo.record]: corona_shimmer = 0.361981 13:33:29.620 [planetinfo.record]: station_vector = 0.356 0.259 0.898 13:33:29.620 [planetinfo.record]: station = coriolis 13:33:33.968 [PLANETINFO OVER]: Done 13:33:33.969 [PLANETINFO LOGGING]: 4 240 13:33:34.972 [planetinfo.record]: seed = 228 22 245 185 13:33:34.972 [planetinfo.record]: coordinates = 22 0 13:33:34.972 [planetinfo.record]: government = 4; 13:33:34.972 [planetinfo.record]: economy = 0; 13:33:34.972 [planetinfo.record]: techlevel = 11; 13:33:34.972 [planetinfo.record]: population = 49; 13:33:34.972 [planetinfo.record]: productivity = 31360; 13:33:34.972 [planetinfo.record]: name = "Orerle"; 13:33:34.972 [planetinfo.record]: inhabitant = "Harmless Insect"; 13:33:34.972 [planetinfo.record]: inhabitants = "Harmless Insects"; 13:33:34.972 [planetinfo.record]: description = "This world is a tedious little planet."; 13:33:35.004 [planetinfo.record]: air_color = 0.479048, 0.500177, 0.911197, 1; 13:33:35.004 [planetinfo.record]: cloud_alpha = 1.000000; 13:33:35.004 [planetinfo.record]: cloud_color = 0.129433, 0.210036, 0.736328, 1; 13:33:35.004 [planetinfo.record]: cloud_fraction = 0.490000; 13:33:35.004 [planetinfo.record]: land_color = 0.436992, 0.66, 0.213984, 1; 13:33:35.004 [planetinfo.record]: land_fraction = 0.400000; 13:33:35.004 [planetinfo.record]: polar_cloud_color = 0.40309, 0.459968, 0.831348, 1; 13:33:35.004 [planetinfo.record]: polar_land_color = 0.855103, 0.934, 0.776205, 1; 13:33:35.004 [planetinfo.record]: polar_sea_color = 0.891179, 0.933203, 0.885176, 1; 13:33:35.004 [planetinfo.record]: sea_color = 0.54765, 0.667969, 0.530461, 1; 13:33:35.004 [planetinfo.record]: rotation_speed = 0.002705 13:33:35.004 [planetinfo.record]: planet zpos = 617040.000000 13:33:35.004 [planetinfo.record]: sun_radius = 97580.779724 13:33:35.004 [planetinfo.record]: sun_vector = 0.461 -0.570 -0.680 13:33:35.004 [planetinfo.record]: sun_distance = 1028400 13:33:35.004 [planetinfo.record]: corona_flare = 0.074236 13:33:35.004 [planetinfo.record]: corona_hues = 0.529839 13:33:35.005 [planetinfo.record]: sun_color = 0.250303, 0.538528, 0.830353, 1 13:33:35.006 [planetinfo.record]: corona_shimmer = 0.304219 13:33:35.006 [planetinfo.record]: station_vector = 0.815 0.563 0.138 13:33:35.006 [planetinfo.record]: station = dodecahedron 13:33:39.602 [PLANETINFO OVER]: Done 13:33:39.604 [PLANETINFO LOGGING]: 4 241 13:33:40.606 [planetinfo.record]: seed = 200 44 155 159 13:33:40.606 [planetinfo.record]: coordinates = 44 161 13:33:40.606 [planetinfo.record]: government = 1; 13:33:40.606 [planetinfo.record]: economy = 3; 13:33:40.606 [planetinfo.record]: techlevel = 5; 13:33:40.606 [planetinfo.record]: population = 25; 13:33:40.606 [planetinfo.record]: productivity = 7000; 13:33:40.606 [planetinfo.record]: name = "Onrequus"; 13:33:40.606 [planetinfo.record]: inhabitant = "Black Furry Rodent"; 13:33:40.606 [planetinfo.record]: inhabitants = "Black Furry Rodents"; 13:33:40.606 [planetinfo.record]: description = "This world is a tedious little planet."; 13:33:40.636 [planetinfo.record]: air_color = 0.579489, 0.926762, 0.972334, 1; 13:33:40.636 [planetinfo.record]: cloud_alpha = 1.000000; 13:33:40.636 [planetinfo.record]: cloud_color = 0.717173, 0.919922, 0.355751, 1; 13:33:40.636 [planetinfo.record]: cloud_fraction = 0.170000; 13:33:40.636 [planetinfo.record]: land_color = 0.66, 0.501365, 0.198516, 1; 13:33:40.636 [planetinfo.record]: land_fraction = 0.360000; 13:33:40.636 [planetinfo.record]: polar_cloud_color = 0.788067, 0.913965, 0.563641, 1; 13:33:40.636 [planetinfo.record]: polar_land_color = 0.934, 0.877877, 0.770732, 1; 13:33:40.636 [planetinfo.record]: polar_sea_color = 0.874197, 0.865827, 0.948242, 1; 13:33:40.636 [planetinfo.record]: sea_color = 0.355914, 0.337639, 0.517578, 1; 13:33:40.636 [planetinfo.record]: rotation_speed = 0.001055 13:33:40.636 [planetinfo.record]: planet zpos = 804000.000000 13:33:40.636 [planetinfo.record]: sun_radius = 151651.702881 13:33:40.636 [planetinfo.record]: sun_vector = 0.054 -0.939 0.339 13:33:40.636 [planetinfo.record]: sun_distance = 1206000 13:33:40.637 [planetinfo.record]: corona_flare = 0.010493 13:33:40.637 [planetinfo.record]: corona_hues = 0.627769 13:33:40.637 [planetinfo.record]: sun_color = 0.800635, 0.696768, 0.508515, 1 13:33:40.637 [planetinfo.record]: corona_shimmer = 1.432105 13:33:40.637 [planetinfo.record]: station_vector = -0.736 0.023 0.677 13:33:40.637 [planetinfo.record]: station = coriolis 13:33:45.736 [PLANETINFO OVER]: Done 13:33:45.737 [PLANETINFO LOGGING]: 4 242 13:33:46.744 [planetinfo.record]: seed = 112 72 69 241 13:33:46.744 [planetinfo.record]: coordinates = 72 58 13:33:46.744 [planetinfo.record]: government = 6; 13:33:46.744 [planetinfo.record]: economy = 2; 13:33:46.744 [planetinfo.record]: techlevel = 8; 13:33:46.744 [planetinfo.record]: population = 41; 13:33:46.744 [planetinfo.record]: productivity = 26240; 13:33:46.744 [planetinfo.record]: name = "Atrare"; 13:33:46.744 [planetinfo.record]: inhabitant = "Human Colonial"; 13:33:46.744 [planetinfo.record]: inhabitants = "Human Colonials"; 13:33:46.745 [planetinfo.record]: description = "The planet Atrare is cursed by vicious mountain monkeys."; 13:33:46.760 [planetinfo.record]: air_color = 0.694505, 0.846692, 0.885182, 1; 13:33:46.760 [planetinfo.record]: cloud_alpha = 1.000000; 13:33:46.760 [planetinfo.record]: cloud_color = 0.639161, 0.658203, 0.62735, 1; 13:33:46.760 [planetinfo.record]: cloud_fraction = 0.300000; 13:33:46.760 [planetinfo.record]: land_color = 0.623047, 0.588822, 0.535431, 1; 13:33:46.760 [planetinfo.record]: land_fraction = 0.340000; 13:33:46.760 [planetinfo.record]: polar_cloud_color = 0.781795, 0.796191, 0.772865, 1; 13:33:46.760 [planetinfo.record]: polar_land_color = 0.937695, 0.924818, 0.904729, 1; 13:33:46.760 [planetinfo.record]: polar_sea_color = 0.79763, 0.948633, 0.845998, 1; 13:33:46.760 [planetinfo.record]: sea_color = 0.186607, 0.513672, 0.29137, 1; 13:33:46.760 [planetinfo.record]: rotation_speed = 0.002581 13:33:46.760 [planetinfo.record]: planet zpos = 314400.000000 13:33:46.760 [planetinfo.record]: sun_radius = 75609.323730 13:33:46.760 [planetinfo.record]: sun_vector = -0.066 -0.952 -0.299 13:33:46.760 [planetinfo.record]: sun_distance = 597360 13:33:46.760 [planetinfo.record]: corona_flare = 0.013243 13:33:46.760 [planetinfo.record]: corona_hues = 0.557648 13:33:46.760 [planetinfo.record]: sun_color = 0.775281, 0.774427, 0.773977, 1 13:33:46.761 [planetinfo.record]: corona_shimmer = 0.587093 13:33:46.761 [planetinfo.record]: station_vector = 0.876 -0.454 0.163 13:33:46.761 [planetinfo.record]: station = coriolis 13:33:51.490 [PLANETINFO OVER]: Done 13:33:51.491 [PLANETINFO LOGGING]: 4 243 13:33:52.493 [planetinfo.record]: seed = 156 19 243 53 13:33:52.493 [planetinfo.record]: coordinates = 19 174 13:33:52.493 [planetinfo.record]: government = 3; 13:33:52.493 [planetinfo.record]: economy = 6; 13:33:52.493 [planetinfo.record]: techlevel = 6; 13:33:52.493 [planetinfo.record]: population = 34; 13:33:52.493 [planetinfo.record]: productivity = 7616; 13:33:52.493 [planetinfo.record]: name = "Latile"; 13:33:52.493 [planetinfo.record]: inhabitant = "Red Furry Feline"; 13:33:52.493 [planetinfo.record]: inhabitants = "Red Furry Felines"; 13:33:52.493 [planetinfo.record]: description = "The planet Latile is reasonably fabled for mud tennis and the Latileian mountain slug."; 13:33:52.496 [planetinfo.record]: air_color = 0.552975, 0.514588, 0.900791, 1; 13:33:52.496 [planetinfo.record]: cloud_alpha = 1.000000; 13:33:52.496 [planetinfo.record]: cloud_color = 0.371819, 0.220337, 0.705078, 1; 13:33:52.496 [planetinfo.record]: cloud_fraction = 0.200000; 13:33:52.496 [planetinfo.record]: land_color = 0.394453, 0.66, 0.541749, 1; 13:33:52.496 [planetinfo.record]: land_fraction = 0.490000; 13:33:52.496 [planetinfo.record]: polar_cloud_color = 0.575851, 0.466108, 0.817285, 1; 13:33:52.496 [planetinfo.record]: polar_land_color = 0.840053, 0.934, 0.892164, 1; 13:33:52.496 [planetinfo.record]: polar_sea_color = 0.912374, 0.93457, 0.882913, 1; 13:33:52.496 [planetinfo.record]: sea_color = 0.592138, 0.654297, 0.509636, 1; 13:33:52.496 [planetinfo.record]: rotation_speed = 0.004597 13:33:52.496 [planetinfo.record]: planet zpos = 452650.000000 13:33:52.496 [planetinfo.record]: sun_radius = 119438.352203 13:33:52.496 [planetinfo.record]: sun_vector = 0.742 -0.595 -0.309 13:33:52.496 [planetinfo.record]: sun_distance = 823000 13:33:52.496 [planetinfo.record]: corona_flare = 0.078708 13:33:52.496 [planetinfo.record]: corona_hues = 0.810295 13:33:52.496 [planetinfo.record]: sun_color = 0.680981, 0.51957, 0.441925, 1 13:33:52.497 [planetinfo.record]: corona_shimmer = 0.589331 13:33:52.497 [planetinfo.record]: station_vector = 0.363 0.909 0.205 13:33:52.497 [planetinfo.record]: station = coriolis 13:33:56.572 [PLANETINFO OVER]: Done 13:33:56.573 [PLANETINFO LOGGING]: 4 244 13:33:57.590 [planetinfo.record]: seed = 172 110 101 167 13:33:57.590 [planetinfo.record]: coordinates = 110 65 13:33:57.590 [planetinfo.record]: government = 5; 13:33:57.590 [planetinfo.record]: economy = 1; 13:33:57.590 [planetinfo.record]: techlevel = 11; 13:33:57.590 [planetinfo.record]: population = 51; 13:33:57.590 [planetinfo.record]: productivity = 33048; 13:33:57.590 [planetinfo.record]: name = "Sotidi"; 13:33:57.590 [planetinfo.record]: inhabitant = "Human Colonial"; 13:33:57.590 [planetinfo.record]: inhabitants = "Human Colonials"; 13:33:57.590 [planetinfo.record]: description = "Sotidi is cursed by dreadful civil war."; 13:33:57.597 [planetinfo.record]: air_color = 0.45314, 0.587216, 0.902092, 1; 13:33:57.598 [planetinfo.record]: cloud_alpha = 1.000000; 13:33:57.598 [planetinfo.record]: cloud_color = 0.0747757, 0.634663, 0.708984, 1; 13:33:57.598 [planetinfo.record]: cloud_fraction = 0.300000; 13:33:57.598 [planetinfo.record]: land_color = 0.66, 0.260391, 0.606927, 1; 13:33:57.598 [planetinfo.record]: land_fraction = 0.450000; 13:33:57.598 [planetinfo.record]: polar_cloud_color = 0.361131, 0.765381, 0.819043, 1; 13:33:57.598 [planetinfo.record]: polar_land_color = 0.934, 0.792623, 0.915223, 1; 13:33:57.598 [planetinfo.record]: polar_sea_color = 0.943555, 0.917913, 0.89306, 1; 13:33:57.598 [planetinfo.record]: sea_color = 0.564453, 0.503095, 0.443625, 1; 13:33:57.598 [planetinfo.record]: rotation_speed = 0.003647 13:33:57.598 [planetinfo.record]: planet zpos = 566160.000000 13:33:57.598 [planetinfo.record]: sun_radius = 133322.951050 13:33:57.599 [planetinfo.record]: sun_vector = 0.131 -0.910 0.393 13:33:57.599 [planetinfo.record]: sun_distance = 990780 13:33:57.599 [planetinfo.record]: corona_flare = 0.038828 13:33:57.599 [planetinfo.record]: corona_hues = 0.607834 13:33:57.599 [planetinfo.record]: sun_color = 0.381998, 0.704212, 0.720499, 1 13:33:57.599 [planetinfo.record]: corona_shimmer = 0.337613 13:33:57.599 [planetinfo.record]: station_vector = -0.265 -0.938 0.224 13:33:57.599 [planetinfo.record]: station = dodecahedron 13:34:02.354 [PLANETINFO OVER]: Done 13:34:02.355 [PLANETINFO LOGGING]: 4 245 13:34:03.358 [planetinfo.record]: seed = 224 107 91 48 13:34:03.358 [planetinfo.record]: coordinates = 107 109 13:34:03.358 [planetinfo.record]: government = 4; 13:34:03.358 [planetinfo.record]: economy = 5; 13:34:03.358 [planetinfo.record]: techlevel = 7; 13:34:03.358 [planetinfo.record]: population = 38; 13:34:03.358 [planetinfo.record]: productivity = 12160; 13:34:03.358 [planetinfo.record]: name = "Eresceon"; 13:34:03.358 [planetinfo.record]: inhabitant = "Human Colonial"; 13:34:03.358 [planetinfo.record]: inhabitants = "Human Colonials"; 13:34:03.358 [planetinfo.record]: description = "This planet is reasonably famous for the Eresceonian spotted cat."; 13:34:03.359 [planetinfo.record]: air_color = 0.621643, 0.841737, 0.972984, 1; 13:34:03.360 [planetinfo.record]: cloud_alpha = 1.000000; 13:34:03.360 [planetinfo.record]: cloud_color = 0.475342, 0.921875, 0.534647, 1; 13:34:03.360 [planetinfo.record]: cloud_fraction = 0.360000; 13:34:03.360 [planetinfo.record]: land_color = 0.177383, 0.595436, 0.605469, 1; 13:34:03.360 [planetinfo.record]: land_fraction = 0.360000; 13:34:03.360 [planetinfo.record]: polar_cloud_color = 0.637889, 0.914844, 0.674672, 1; 13:34:03.360 [planetinfo.record]: polar_land_color = 0.773397, 0.935561, 0.939453, 1; 13:34:03.360 [planetinfo.record]: polar_sea_color = 0.923092, 0.928711, 0.873387, 1; 13:34:03.360 [planetinfo.record]: sea_color = 0.695638, 0.712891, 0.543022, 1; 13:34:03.360 [planetinfo.record]: rotation_speed = 0.004452 13:34:03.360 [planetinfo.record]: planet zpos = 321530.000000 13:34:03.360 [planetinfo.record]: sun_radius = 71956.395874 13:34:03.360 [planetinfo.record]: sun_vector = 0.035 0.359 0.933 13:34:03.361 [planetinfo.record]: sun_distance = 613830 13:34:03.361 [planetinfo.record]: corona_flare = 0.055493 13:34:03.361 [planetinfo.record]: corona_hues = 0.889496 13:34:03.361 [planetinfo.record]: sun_color = 0.761777, 0.559873, 0.514783, 1 13:34:03.361 [planetinfo.record]: corona_shimmer = 0.659051 13:34:03.361 [planetinfo.record]: station_vector = -0.934 -0.356 0.033 13:34:03.361 [planetinfo.record]: station = coriolis 13:34:09.031 [PLANETINFO OVER]: Done 13:34:09.032 [PLANETINFO LOGGING]: 4 246 13:34:10.034 [planetinfo.record]: seed = 152 223 213 142 13:34:10.034 [planetinfo.record]: coordinates = 223 165 13:34:10.034 [planetinfo.record]: government = 3; 13:34:10.034 [planetinfo.record]: economy = 5; 13:34:10.034 [planetinfo.record]: techlevel = 7; 13:34:10.034 [planetinfo.record]: population = 37; 13:34:10.034 [planetinfo.record]: productivity = 10360; 13:34:10.034 [planetinfo.record]: name = "Reraxe"; 13:34:10.034 [planetinfo.record]: inhabitant = "Black Horned Bird"; 13:34:10.034 [planetinfo.record]: inhabitants = "Black Horned Birds"; 13:34:10.034 [planetinfo.record]: description = "The world Reraxe is reasonably fabled for Zero-G cricket and its exotic cuisine."; 13:34:10.037 [planetinfo.record]: air_color = 0.582859, 0.451883, 0.964529, 1; 13:34:10.037 [planetinfo.record]: cloud_alpha = 1.000000; 13:34:10.037 [planetinfo.record]: cloud_color = 0.701144, 0.00350189, 0.896484, 1; 13:34:10.038 [planetinfo.record]: cloud_fraction = 0.180000; 13:34:10.038 [planetinfo.record]: land_color = 0.574922, 0.63009, 0.66, 1; 13:34:10.038 [planetinfo.record]: land_fraction = 0.380000; 13:34:10.038 [planetinfo.record]: polar_cloud_color = 0.780386, 0.340987, 0.903418, 1; 13:34:10.038 [planetinfo.record]: polar_land_color = 0.9039, 0.923418, 0.934, 1; 13:34:10.038 [planetinfo.record]: polar_sea_color = 0.936719, 0.928029, 0.881101, 1; 13:34:10.038 [planetinfo.record]: sea_color = 0.632812, 0.609329, 0.48252, 1; 13:34:10.038 [planetinfo.record]: rotation_speed = 0.000997 13:34:10.038 [planetinfo.record]: planet zpos = 993450.000000 13:34:10.038 [planetinfo.record]: sun_radius = 197058.908386 13:34:10.038 [planetinfo.record]: sun_vector = 0.521 -0.403 -0.753 13:34:10.038 [planetinfo.record]: sun_distance = 1324600 13:34:10.038 [planetinfo.record]: corona_flare = 0.002516 13:34:10.038 [planetinfo.record]: corona_hues = 0.690781 13:34:10.038 [planetinfo.record]: sun_color = 0.661362, 0.620206, 0.549898, 1 13:34:10.039 [planetinfo.record]: corona_shimmer = 0.253677 13:34:10.039 [planetinfo.record]: station_vector = -0.688 0.717 0.114 13:34:10.039 [planetinfo.record]: station = coriolis 13:34:14.148 [PLANETINFO OVER]: Done 13:34:14.149 [PLANETINFO LOGGING]: 4 247 13:34:15.152 [planetinfo.record]: seed = 148 37 83 188 13:34:15.152 [planetinfo.record]: coordinates = 37 130 13:34:15.152 [planetinfo.record]: government = 2; 13:34:15.152 [planetinfo.record]: economy = 2; 13:34:15.152 [planetinfo.record]: techlevel = 7; 13:34:15.152 [planetinfo.record]: population = 33; 13:34:15.152 [planetinfo.record]: productivity = 12672; 13:34:15.152 [planetinfo.record]: name = "Tezabi"; 13:34:15.152 [planetinfo.record]: inhabitant = "Human Colonial"; 13:34:15.152 [planetinfo.record]: inhabitants = "Human Colonials"; 13:34:15.152 [planetinfo.record]: description = "This world is noted for its unusual sit coms."; 13:34:15.157 [planetinfo.record]: air_color = 0.780441, 0.624171, 0.872174, 1; 13:34:15.157 [planetinfo.record]: cloud_alpha = 1.000000; 13:34:15.157 [planetinfo.record]: cloud_color = 0.619141, 0.461937, 0.476675, 1; 13:34:15.157 [planetinfo.record]: cloud_fraction = 0.160000; 13:34:15.158 [planetinfo.record]: land_color = 0.591797, 0.455406, 0.496963, 1; 13:34:15.158 [planetinfo.record]: land_fraction = 0.570000; 13:34:15.158 [planetinfo.record]: polar_cloud_color = 0.778613, 0.655054, 0.666638, 1; 13:34:15.158 [planetinfo.record]: polar_land_color = 0.94082, 0.886613, 0.903129, 1; 13:34:15.158 [planetinfo.record]: polar_sea_color = 0.910937, 0.799767, 0.745474, 1; 13:34:15.158 [planetinfo.record]: sea_color = 0.890625, 0.455858, 0.24353, 1; 13:34:15.158 [planetinfo.record]: rotation_speed = 0.000599 13:34:15.158 [planetinfo.record]: planet zpos = 711000.000000 13:34:15.158 [planetinfo.record]: sun_radius = 122938.144684 13:34:15.158 [planetinfo.record]: sun_vector = 0.112 0.384 0.917 13:34:15.158 [planetinfo.record]: sun_distance = 1125750 13:34:15.158 [planetinfo.record]: corona_flare = 0.066063 13:34:15.158 [planetinfo.record]: corona_hues = 0.583305 13:34:15.158 [planetinfo.record]: sun_color = 0.758081, 0.752515, 0.75213, 1 13:34:15.158 [planetinfo.record]: corona_shimmer = 0.247214 13:34:15.159 [planetinfo.record]: station_vector = 0.018 -1.000 0.025 13:34:15.159 [planetinfo.record]: station = coriolis 13:34:20.193 [PLANETINFO OVER]: Done 13:34:20.194 [PLANETINFO LOGGING]: 4 248 13:34:21.198 [planetinfo.record]: seed = 52 103 21 18 13:34:21.198 [planetinfo.record]: coordinates = 103 70 13:34:21.198 [planetinfo.record]: government = 6; 13:34:21.198 [planetinfo.record]: economy = 6; 13:34:21.198 [planetinfo.record]: techlevel = 7; 13:34:21.198 [planetinfo.record]: population = 41; 13:34:21.198 [planetinfo.record]: productivity = 13120; 13:34:21.198 [planetinfo.record]: name = "Enonedar"; 13:34:21.198 [planetinfo.record]: inhabitant = "Human Colonial"; 13:34:21.198 [planetinfo.record]: inhabitants = "Human Colonials"; 13:34:21.198 [planetinfo.record]: description = "The world Enonedar is a boring world."; 13:34:21.199 [planetinfo.record]: air_color = 0.668685, 0.608977, 0.835752, 1; 13:34:21.200 [planetinfo.record]: cloud_alpha = 1.000000; 13:34:21.200 [planetinfo.record]: cloud_color = 0.488375, 0.400246, 0.509766, 1; 13:34:21.200 [planetinfo.record]: cloud_fraction = 0.400000; 13:34:21.200 [planetinfo.record]: land_color = 0.621328, 0.642779, 0.66, 1; 13:34:21.200 [planetinfo.record]: land_fraction = 0.490000; 13:34:21.200 [planetinfo.record]: polar_cloud_color = 0.710265, 0.631453, 0.729395, 1; 13:34:21.200 [planetinfo.record]: polar_land_color = 0.920318, 0.927907, 0.934, 1; 13:34:21.200 [planetinfo.record]: polar_sea_color = 0.8956, 0.94082, 0.70929, 1; 13:34:21.200 [planetinfo.record]: sea_color = 0.478018, 0.591797, 0.00924683, 1; 13:34:21.200 [planetinfo.record]: rotation_speed = 0.004515 13:34:21.200 [planetinfo.record]: planet zpos = 411720.000000 13:34:21.200 [planetinfo.record]: sun_radius = 74049.519806 13:34:21.200 [planetinfo.record]: sun_vector = -0.685 -0.581 0.440 13:34:21.200 [planetinfo.record]: sun_distance = 686200 13:34:21.200 [planetinfo.record]: corona_flare = 0.088940 13:34:21.201 [planetinfo.record]: corona_hues = 0.953300 13:34:21.201 [planetinfo.record]: sun_color = 0.398302, 0.566069, 0.729123, 1 13:34:21.201 [planetinfo.record]: corona_shimmer = 0.563751 13:34:21.201 [planetinfo.record]: station_vector = -0.239 0.757 0.608 13:34:21.201 [planetinfo.record]: station = coriolis 13:34:25.811 [PLANETINFO OVER]: Done 13:34:25.812 [PLANETINFO LOGGING]: 4 249 13:34:26.814 [planetinfo.record]: seed = 184 10 91 3 13:34:26.814 [planetinfo.record]: coordinates = 10 56 13:34:26.814 [planetinfo.record]: government = 7; 13:34:26.814 [planetinfo.record]: economy = 0; 13:34:26.814 [planetinfo.record]: techlevel = 13; 13:34:26.815 [planetinfo.record]: population = 60; 13:34:26.815 [planetinfo.record]: productivity = 52800; 13:34:26.815 [planetinfo.record]: name = "Gesolaon"; 13:34:26.815 [planetinfo.record]: inhabitant = "Human Colonial"; 13:34:26.815 [planetinfo.record]: inhabitants = "Human Colonials"; 13:34:26.815 [planetinfo.record]: description = "Gesolaon is very notable for the Gesolaonian tree snake."; 13:34:26.821 [planetinfo.record]: air_color = 0.707415, 0.625168, 0.837053, 1; 13:34:26.821 [planetinfo.record]: cloud_alpha = 1.000000; 13:34:26.821 [planetinfo.record]: cloud_color = 0.513672, 0.431404, 0.498889, 1; 13:34:26.821 [planetinfo.record]: cloud_fraction = 0.330000; 13:34:26.822 [planetinfo.record]: land_color = 0.469219, 0.66, 0.583986, 1; 13:34:26.822 [planetinfo.record]: land_fraction = 0.370000; 13:34:26.822 [planetinfo.record]: polar_cloud_color = 0.731152, 0.657966, 0.718002, 1; 13:34:26.822 [planetinfo.record]: polar_land_color = 0.866504, 0.934, 0.907107, 1; 13:34:26.822 [planetinfo.record]: polar_sea_color = 0.884425, 0.931836, 0.889611, 1; 13:34:26.822 [planetinfo.record]: sea_color = 0.542916, 0.681641, 0.558089, 1; 13:34:26.822 [planetinfo.record]: rotation_speed = 0.002854 13:34:26.822 [planetinfo.record]: planet zpos = 431280.000000 13:34:26.822 [planetinfo.record]: sun_radius = 106441.868591 13:34:26.822 [planetinfo.record]: sun_vector = -0.018 -0.972 -0.232 13:34:26.822 [planetinfo.record]: sun_distance = 646920 13:34:26.822 [planetinfo.record]: corona_flare = 0.075769 13:34:26.822 [planetinfo.record]: corona_hues = 0.615456 13:34:26.823 [planetinfo.record]: sun_color = 0.349633, 0.458981, 0.659824, 1 13:34:26.823 [planetinfo.record]: corona_shimmer = 0.241191 13:34:26.823 [planetinfo.record]: station_vector = 0.280 -0.952 0.125 13:34:26.823 [planetinfo.record]: station = dodecahedron 13:34:31.281 [PLANETINFO OVER]: Done 13:34:31.282 [PLANETINFO LOGGING]: 4 250 13:34:32.284 [planetinfo.record]: seed = 128 159 165 59 13:34:32.284 [planetinfo.record]: coordinates = 159 85 13:34:32.284 [planetinfo.record]: government = 0; 13:34:32.284 [planetinfo.record]: economy = 7; 13:34:32.284 [planetinfo.record]: techlevel = 3; 13:34:32.284 [planetinfo.record]: population = 20; 13:34:32.284 [planetinfo.record]: productivity = 1920; 13:34:32.284 [planetinfo.record]: name = "Anerma"; 13:34:32.284 [planetinfo.record]: inhabitant = "Red Horned Humanoid"; 13:34:32.284 [planetinfo.record]: inhabitants = "Red Horned Humanoids"; 13:34:32.284 [planetinfo.record]: description = "This world is plagued by frequent solar activity."; 13:34:32.289 [planetinfo.record]: air_color = 0.731426, 0.463221, 0.985992, 1; 13:34:32.290 [planetinfo.record]: cloud_alpha = 1.000000; 13:34:32.290 [planetinfo.record]: cloud_color = 0.960938, 0.00750732, 0.432082, 1; 13:34:32.290 [planetinfo.record]: cloud_fraction = 0.520000; 13:34:32.290 [planetinfo.record]: land_color = 0.507891, 0.66, 0.549483, 1; 13:34:32.290 [planetinfo.record]: land_fraction = 0.500000; 13:34:32.290 [planetinfo.record]: polar_cloud_color = 0.932422, 0.354211, 0.611696, 1; 13:34:32.290 [planetinfo.record]: polar_land_color = 0.880186, 0.934, 0.8949, 1; 13:34:32.290 [planetinfo.record]: polar_sea_color = 0.872604, 0.924805, 0.899112, 1; 13:34:32.290 [planetinfo.record]: sea_color = 0.582176, 0.751953, 0.668391, 1; 13:34:32.290 [planetinfo.record]: rotation_speed = 0.004387 13:34:32.290 [planetinfo.record]: planet zpos = 521190.000000 13:34:32.290 [planetinfo.record]: sun_radius = 111195.046692 13:34:32.290 [planetinfo.record]: sun_vector = 0.071 -0.285 0.956 13:34:32.290 [planetinfo.record]: sun_distance = 1158200 13:34:32.290 [planetinfo.record]: corona_flare = 0.099852 13:34:32.290 [planetinfo.record]: corona_hues = 0.713852 13:34:32.291 [planetinfo.record]: sun_color = 0.7546, 0.757364, 0.777429, 1 13:34:32.291 [planetinfo.record]: corona_shimmer = 0.385810 13:34:32.291 [planetinfo.record]: station_vector = -0.314 0.622 0.717 13:34:32.291 [planetinfo.record]: station = coriolis 13:34:37.742 [PLANETINFO OVER]: Done 13:34:37.742 [PLANETINFO LOGGING]: 4 251 13:34:38.747 [planetinfo.record]: seed = 76 119 243 178 13:34:38.747 [planetinfo.record]: coordinates = 119 11 13:34:38.747 [planetinfo.record]: government = 1; 13:34:38.747 [planetinfo.record]: economy = 3; 13:34:38.747 [planetinfo.record]: techlevel = 8; 13:34:38.747 [planetinfo.record]: population = 37; 13:34:38.747 [planetinfo.record]: productivity = 10360; 13:34:38.747 [planetinfo.record]: name = "Enlaonus"; 13:34:38.747 [planetinfo.record]: inhabitant = "Harmless Fat Feline"; 13:34:38.747 [planetinfo.record]: inhabitants = "Harmless Fat Felines"; 13:34:38.747 [planetinfo.record]: description = "Enlaonus is reasonably well known for the Enlaonusian tree grub but ravaged by occasional solar activity."; 13:34:38.756 [planetinfo.record]: air_color = 0.626108, 0.480116, 0.950871, 1; 13:34:38.756 [planetinfo.record]: cloud_alpha = 1.000000; 13:34:38.756 [planetinfo.record]: cloud_color = 0.813985, 0.0969086, 0.855469, 1; 13:34:38.756 [planetinfo.record]: cloud_fraction = 0.610000; 13:34:38.756 [planetinfo.record]: land_color = 0.582656, 0.647311, 0.66, 1; 13:34:38.756 [planetinfo.record]: land_fraction = 0.660000; 13:34:38.756 [planetinfo.record]: polar_cloud_color = 0.85814, 0.394516, 0.884961, 1; 13:34:38.756 [planetinfo.record]: polar_land_color = 0.906637, 0.929511, 0.934, 1; 13:34:38.756 [planetinfo.record]: polar_sea_color = 0.918945, 0.89353, 0.806769, 1; 13:34:38.756 [planetinfo.record]: sea_color = 0.810547, 0.720879, 0.414772, 1; 13:34:38.756 [planetinfo.record]: rotation_speed = 0.001545 13:34:38.757 [planetinfo.record]: planet zpos = 482580.000000 13:34:38.757 [planetinfo.record]: sun_radius = 88272.044220 13:34:38.757 [planetinfo.record]: sun_vector = 0.377 -0.499 -0.780 13:34:38.757 [planetinfo.record]: sun_distance = 585990 13:34:38.757 [planetinfo.record]: corona_flare = 0.029884 13:34:38.757 [planetinfo.record]: corona_hues = 0.767609 13:34:38.757 [planetinfo.record]: sun_color = 0.711423, 0.6891, 0.68826, 1 13:34:38.757 [planetinfo.record]: corona_shimmer = 0.268914 13:34:38.757 [planetinfo.record]: station_vector = -0.761 0.648 0.019 13:34:38.757 [planetinfo.record]: station = coriolis 13:34:43.425 [PLANETINFO OVER]: Done 13:34:43.426 [PLANETINFO LOGGING]: 4 252 13:34:44.432 [planetinfo.record]: seed = 124 72 5 222 13:34:44.432 [planetinfo.record]: coordinates = 72 95 13:34:44.432 [planetinfo.record]: government = 7; 13:34:44.432 [planetinfo.record]: economy = 7; 13:34:44.432 [planetinfo.record]: techlevel = 4; 13:34:44.432 [planetinfo.record]: population = 31; 13:34:44.432 [planetinfo.record]: productivity = 8184; 13:34:44.432 [planetinfo.record]: name = "Ribiinat"; 13:34:44.432 [planetinfo.record]: inhabitant = "Human Colonial"; 13:34:44.432 [planetinfo.record]: inhabitants = "Human Colonials"; 13:34:44.432 [planetinfo.record]: description = "Ribiinat is ravaged by dreadful civil war."; 13:34:44.440 [planetinfo.record]: air_color = 0.524579, 0.587326, 0.855914, 1; 13:34:44.440 [planetinfo.record]: cloud_alpha = 1.000000; 13:34:44.447 [planetinfo.record]: cloud_color = 0.249512, 0.427456, 0.570312, 1; 13:34:44.447 [planetinfo.record]: cloud_fraction = 0.580000; 13:34:44.448 [planetinfo.record]: land_color = 0.554982, 0.0489844, 0.66, 1; 13:34:44.448 [planetinfo.record]: land_fraction = 0.620000; 13:34:44.448 [planetinfo.record]: polar_cloud_color = 0.490634, 0.638185, 0.756641, 1; 13:34:44.448 [planetinfo.record]: polar_land_color = 0.896846, 0.71783, 0.934, 1; 13:34:44.448 [planetinfo.record]: polar_sea_color = 0.943555, 0.896835, 0.88919, 1; 13:34:44.448 [planetinfo.record]: sea_color = 0.564453, 0.452658, 0.434364, 1; 13:34:44.448 [planetinfo.record]: rotation_speed = 0.000542 13:34:44.448 [planetinfo.record]: planet zpos = 711920.000000 13:34:44.448 [planetinfo.record]: sun_radius = 161606.440430 13:34:44.448 [planetinfo.record]: sun_vector = -0.618 0.283 -0.734 13:34:44.448 [planetinfo.record]: sun_distance = 1035520 13:34:44.448 [planetinfo.record]: corona_flare = 0.062839 13:34:44.448 [planetinfo.record]: corona_hues = 0.937469 13:34:44.448 [planetinfo.record]: sun_color = 0.598069, 0.648216, 0.774564, 1 13:34:44.448 [planetinfo.record]: corona_shimmer = 1.452160 13:34:44.448 [planetinfo.record]: station_vector = -0.733 -0.388 0.559 13:34:44.448 [planetinfo.record]: station = coriolis 13:34:48.603 [PLANETINFO OVER]: Done 13:34:48.604 [PLANETINFO LOGGING]: 4 253 13:34:49.606 [planetinfo.record]: seed = 80 17 155 68 13:34:49.606 [planetinfo.record]: coordinates = 17 172 13:34:49.606 [planetinfo.record]: government = 2; 13:34:49.606 [planetinfo.record]: economy = 4; 13:34:49.606 [planetinfo.record]: techlevel = 5; 13:34:49.606 [planetinfo.record]: population = 27; 13:34:49.606 [planetinfo.record]: productivity = 7776; 13:34:49.606 [planetinfo.record]: name = "Zaxeed"; 13:34:49.606 [planetinfo.record]: inhabitant = "Fierce Yellow Furry Humanoid"; 13:34:49.606 [planetinfo.record]: inhabitants = "Fierce Yellow Furry Humanoids"; 13:34:49.606 [planetinfo.record]: description = "The planet Zaxeed is cursed by deadly civil war."; 13:34:49.628 [planetinfo.record]: air_color = 0.451132, 0.677189, 0.875426, 1; 13:34:49.628 [planetinfo.record]: cloud_alpha = 1.000000; 13:34:49.628 [planetinfo.record]: cloud_color = 0.0908966, 0.628906, 0.313666, 1; 13:34:49.628 [planetinfo.record]: cloud_fraction = 0.300000; 13:34:49.628 [planetinfo.record]: land_color = 0.402188, 0.66, 0.484768, 1; 13:34:49.628 [planetinfo.record]: land_fraction = 0.620000; 13:34:49.628 [planetinfo.record]: polar_cloud_color = 0.364359, 0.783008, 0.537706, 1; 13:34:49.628 [planetinfo.record]: polar_land_color = 0.842789, 0.934, 0.872005, 1; 13:34:49.628 [planetinfo.record]: polar_sea_color = 0.936133, 0.902411, 0.875339, 1; 13:34:49.628 [planetinfo.record]: sea_color = 0.638672, 0.546646, 0.472767, 1; 13:34:49.628 [planetinfo.record]: rotation_speed = 0.001322 13:34:49.628 [planetinfo.record]: planet zpos = 539980.000000 13:34:49.628 [planetinfo.record]: sun_radius = 98926.258850 13:34:49.628 [planetinfo.record]: sun_vector = -0.547 -0.830 0.109 13:34:49.628 [planetinfo.record]: sun_distance = 732830 13:34:49.628 [planetinfo.record]: corona_flare = 0.096465 13:34:49.628 [planetinfo.record]: corona_hues = 0.922974 13:34:49.628 [planetinfo.record]: sun_color = 0.802808, 0.640746, 0.293072, 1 13:34:49.629 [planetinfo.record]: corona_shimmer = 0.499132 13:34:49.629 [planetinfo.record]: station_vector = -0.022 -0.993 0.119 13:34:49.629 [planetinfo.record]: station = coriolis 13:34:54.586 [PLANETINFO OVER]: Done 13:34:54.587 [PLANETINFO LOGGING]: 4 254 13:34:55.591 [planetinfo.record]: seed = 40 160 181 251 13:34:55.591 [planetinfo.record]: coordinates = 160 88 13:34:55.591 [planetinfo.record]: government = 5; 13:34:55.591 [planetinfo.record]: economy = 0; 13:34:55.591 [planetinfo.record]: techlevel = 10; 13:34:55.591 [planetinfo.record]: population = 46; 13:34:55.591 [planetinfo.record]: productivity = 33120; 13:34:55.591 [planetinfo.record]: name = "Anraer"; 13:34:55.591 [planetinfo.record]: inhabitant = "Slimy Lobster"; 13:34:55.591 [planetinfo.record]: inhabitants = "Slimy Lobsters"; 13:34:55.591 [planetinfo.record]: description = "This planet is fabled for its fabulous vicious On brew and Anraerian wolf cutlet."; 13:34:55.594 [planetinfo.record]: air_color = 0.721383, 0.506783, 0.952172, 1; 13:34:55.595 [planetinfo.record]: cloud_alpha = 1.000000; 13:34:55.595 [planetinfo.record]: cloud_color = 0.859375, 0.167847, 0.540624, 1; 13:34:55.595 [planetinfo.record]: cloud_fraction = 0.150000; 13:34:55.595 [planetinfo.record]: land_color = 0.476953, 0.66, 0.293906, 1; 13:34:55.595 [planetinfo.record]: land_fraction = 0.360000; 13:34:55.595 [planetinfo.record]: polar_cloud_color = 0.886719, 0.440762, 0.68116, 1; 13:34:55.595 [planetinfo.record]: polar_land_color = 0.86924, 0.934, 0.80448, 1; 13:34:55.595 [planetinfo.record]: polar_sea_color = 0.919196, 0.931836, 0.874051, 1; 13:34:55.595 [planetinfo.record]: sea_color = 0.644655, 0.681641, 0.512562, 1; 13:34:55.595 [planetinfo.record]: rotation_speed = 0.002913 13:34:55.595 [planetinfo.record]: planet zpos = 868800.000000 13:34:55.602 [planetinfo.record]: sun_radius = 166375.942383 13:34:55.602 [planetinfo.record]: sun_vector = -0.300 0.886 -0.353 13:34:55.602 [planetinfo.record]: sun_distance = 1332160 13:34:55.602 [planetinfo.record]: corona_flare = 0.004002 13:34:55.602 [planetinfo.record]: corona_hues = 0.579498 13:34:55.602 [planetinfo.record]: sun_color = 0.290066, 0.703246, 0.824799, 1 13:34:55.602 [planetinfo.record]: corona_shimmer = 0.294721 13:34:55.602 [planetinfo.record]: station_vector = 0.366 0.663 0.653 13:34:55.603 [planetinfo.record]: station = coriolis 13:35:00.963 [PLANETINFO OVER]: Done 13:35:00.964 [PLANETINFO LOGGING]: 4 255 13:35:01.982 [planetinfo.record]: seed = 196 128 211 5 13:35:01.982 [planetinfo.record]: coordinates = 128 144 13:35:01.982 [planetinfo.record]: government = 0; 13:35:01.982 [planetinfo.record]: economy = 2; 13:35:01.982 [planetinfo.record]: techlevel = 5; 13:35:01.982 [planetinfo.record]: population = 23; 13:35:01.982 [planetinfo.record]: productivity = 5888; 13:35:01.982 [planetinfo.record]: name = "Cetiisqu"; 13:35:01.982 [planetinfo.record]: inhabitant = "Fierce Green Slimy Frog"; 13:35:01.982 [planetinfo.record]: inhabitants = "Fierce Green Slimy Frogs"; 13:35:01.982 [planetinfo.record]: description = "The world Cetiisqu is most well known for its vast oceans."; 13:35:02.029 [planetinfo.record]: air_color = 0.579124, 0.536365, 0.884531, 1; 13:35:02.030 [planetinfo.record]: cloud_alpha = 1.000000; 13:35:02.030 [planetinfo.record]: cloud_color = 0.42051, 0.274292, 0.65625, 1; 13:35:02.030 [planetinfo.record]: cloud_fraction = 0.480000; 13:35:02.030 [planetinfo.record]: land_color = 0.464063, 0.66, 0.650815, 1; 13:35:02.030 [planetinfo.record]: land_fraction = 0.200000; 13:35:02.030 [planetinfo.record]: polar_cloud_color = 0.616754, 0.506002, 0.795313, 1; 13:35:02.030 [planetinfo.record]: polar_land_color = 0.86468, 0.934, 0.930751, 1; 13:35:02.030 [planetinfo.record]: polar_sea_color = 0.888994, 0.933008, 0.885264, 1; 13:35:02.031 [planetinfo.record]: sea_color = 0.54351, 0.669922, 0.532797, 1; 13:35:02.031 [planetinfo.record]: rotation_speed = 0.002404 13:35:02.031 [planetinfo.record]: planet zpos = 633600.000000 13:35:02.031 [planetinfo.record]: sun_radius = 104050.546875 13:35:02.031 [planetinfo.record]: sun_vector = 0.654 0.468 -0.594 13:35:02.031 [planetinfo.record]: sun_distance = 887040 13:35:02.031 [planetinfo.record]: corona_flare = 0.041888 13:35:02.031 [planetinfo.record]: corona_hues = 0.858437 13:35:02.031 [planetinfo.record]: sun_color = 0.664005, 0.463497, 0.248062, 1 13:35:02.032 [planetinfo.record]: corona_shimmer = 0.278041 13:35:02.032 [planetinfo.record]: station_vector = 0.646 -0.520 0.559 13:35:02.032 [planetinfo.record]: station = coriolis 13:35:06.570 [PLANETINFO OVER]: Done 13:35:41.885 [planetinfo.record]: seed = 61 133 160 229 13:35:41.885 [planetinfo.record]: coordinates = 133 138 13:35:41.885 [planetinfo.record]: government = 7; 13:35:41.886 [planetinfo.record]: economy = 2; 13:35:41.886 [planetinfo.record]: techlevel = 10; 13:35:41.886 [planetinfo.record]: population = 50; 13:35:41.886 [planetinfo.record]: productivity = 35200; 13:35:41.886 [planetinfo.record]: name = "Celaan"; 13:35:41.886 [planetinfo.record]: inhabitant = "Fierce Rodent"; 13:35:41.886 [planetinfo.record]: inhabitants = "Fierce Rodents"; 13:35:41.886 [planetinfo.record]: description = "The world Celaan is a boring world."; 13:35:41.908 [planetinfo.record]: air_color = 0.661094, 0.846809, 0.794431, 1; 13:35:41.908 [planetinfo.record]: cloud_alpha = 1.000000; 13:35:41.908 [planetinfo.record]: cloud_color = 0.542969, 0.516374, 0.511154, 1; 13:35:41.908 [planetinfo.record]: cloud_fraction = 0.530000; 13:35:41.908 [planetinfo.record]: land_color = 0.649688, 0.65855, 0.66, 1; 13:35:41.908 [planetinfo.record]: land_fraction = 0.350000; 13:35:41.908 [planetinfo.record]: polar_cloud_color = 0.744336, 0.72155, 0.717078, 1; 13:35:41.908 [planetinfo.record]: polar_land_color = 0.930352, 0.933487, 0.934, 1; 13:35:41.908 [planetinfo.record]: polar_sea_color = 0.810506, 0.949609, 0.884405, 1; 13:35:41.908 [planetinfo.record]: sea_color = 0.208649, 0.503906, 0.365504, 1; 13:35:41.908 [planetinfo.record]: rotation_speed = 0.001258 13:35:41.908 [planetinfo.record]: planet zpos = 422900.000000 13:35:41.908 [planetinfo.record]: sun_radius = 104870.630493 13:35:41.908 [planetinfo.record]: sun_vector = 0.630 -0.305 0.714 13:35:41.908 [planetinfo.record]: sun_distance = 888090 13:35:41.909 [planetinfo.record]: corona_flare = 0.006793 13:35:41.909 [planetinfo.record]: corona_hues = 0.761414 13:35:41.909 [planetinfo.record]: sun_color = 0.609244, 0.770479, 0.847928, 1 13:35:41.909 [planetinfo.record]: corona_shimmer = 0.399668 13:35:41.909 [planetinfo.record]: station_vector = -0.334 0.767 0.547 13:35:41.909 [planetinfo.record]: station = coriolis 13:35:46.081 [PLANETINFO OVER]: Done 13:35:49.815 [PLANETINFO LOGGING]: 5 0 13:35:50.586 [planetinfo.record]: seed = 9 64 106 246 13:35:50.586 [planetinfo.record]: coordinates = 64 75 13:35:50.586 [planetinfo.record]: government = 1; 13:35:50.586 [planetinfo.record]: economy = 3; 13:35:50.586 [planetinfo.record]: techlevel = 5; 13:35:50.586 [planetinfo.record]: population = 25; 13:35:50.587 [planetinfo.record]: productivity = 7000; 13:35:50.587 [planetinfo.record]: name = "Veleeder"; 13:35:50.587 [planetinfo.record]: inhabitant = "Human Colonial"; 13:35:50.587 [planetinfo.record]: inhabitants = "Human Colonials"; 13:35:50.587 [planetinfo.record]: description = "The planet Veleeder is cursed by deadly civil war."; 13:35:50.625 [planetinfo.record]: air_color = 0.430097, 0.621286, 0.907945, 1; 13:35:50.625 [planetinfo.record]: cloud_alpha = 1.000000; 13:35:50.625 [planetinfo.record]: cloud_color = 0.0141907, 0.726562, 0.592993, 1; 13:35:50.625 [planetinfo.record]: cloud_fraction = 0.470000; 13:35:50.625 [planetinfo.record]: land_color = 0.631461, 0.632812, 0.630341, 1; 13:35:50.625 [planetinfo.record]: land_fraction = 0.300000; 13:35:50.625 [planetinfo.record]: polar_cloud_color = 0.320202, 0.826953, 0.731937, 1; 13:35:50.625 [planetinfo.record]: polar_land_color = 0.936219, 0.936719, 0.935804, 1; 13:35:50.625 [planetinfo.record]: polar_sea_color = 0.829939, 0.908984, 0.785597, 1; 13:35:50.625 [planetinfo.record]: sea_color = 0.593568, 0.910156, 0.41597, 1; 13:35:50.625 [planetinfo.record]: rotation_speed = 0.002896 13:35:50.625 [planetinfo.record]: planet zpos = 397440.000000 13:35:50.625 [planetinfo.record]: sun_radius = 130881.679688 13:35:50.625 [planetinfo.record]: sun_vector = 0.177 -0.495 -0.851 13:35:50.626 [planetinfo.record]: sun_distance = 794880 13:35:50.626 [planetinfo.record]: corona_flare = 0.087440 13:35:50.626 [planetinfo.record]: corona_hues = 0.502205 13:35:50.626 [planetinfo.record]: sun_color = 0.662045, 0.385545, 0.304267, 1 13:35:50.626 [planetinfo.record]: corona_shimmer = 0.539844 13:35:50.626 [planetinfo.record]: station_vector = 0.411 -0.832 0.373 13:35:50.626 [planetinfo.record]: station = coriolis 13:35:55.823 [PLANETINFO OVER]: Done 13:35:55.824 [PLANETINFO LOGGING]: 5 1 13:35:56.844 [planetinfo.record]: seed = 85 48 64 106 13:35:56.844 [planetinfo.record]: coordinates = 48 184 13:35:56.844 [planetinfo.record]: government = 2; 13:35:56.844 [planetinfo.record]: economy = 0; 13:35:56.844 [planetinfo.record]: techlevel = 8; 13:35:56.844 [planetinfo.record]: population = 35; 13:35:56.844 [planetinfo.record]: productivity = 16800; 13:35:56.844 [planetinfo.record]: name = "Arendi"; 13:35:56.844 [planetinfo.record]: inhabitant = "Human Colonial"; 13:35:56.844 [planetinfo.record]: inhabitants = "Human Colonials"; 13:35:56.844 [planetinfo.record]: description = "This planet is most fabled for its inhabitants’ ingrained silliness but beset by lethal disease."; 13:35:56.860 [planetinfo.record]: air_color = 0.579946, 0.55094, 0.867621, 1; 13:35:56.860 [planetinfo.record]: cloud_alpha = 1.000000; 13:35:56.860 [planetinfo.record]: cloud_color = 0.391925, 0.305099, 0.605469, 1; 13:35:56.860 [planetinfo.record]: cloud_fraction = 0.460000; 13:35:56.860 [planetinfo.record]: land_color = 0.239525, 0.430592, 0.583984, 1; 13:35:56.860 [planetinfo.record]: land_fraction = 0.340000; 13:35:56.860 [planetinfo.record]: polar_cloud_color = 0.602186, 0.532953, 0.772461, 1; 13:35:56.860 [planetinfo.record]: polar_land_color = 0.802752, 0.87977, 0.941602, 1; 13:35:56.860 [planetinfo.record]: polar_sea_color = 0.878087, 0.922461, 0.863906, 1; 13:35:56.860 [planetinfo.record]: sea_color = 0.626195, 0.775391, 0.578514, 1; 13:35:56.860 [planetinfo.record]: rotation_speed = 0.000875 13:35:56.861 [planetinfo.record]: planet zpos = 705120.000000 13:35:56.861 [planetinfo.record]: sun_radius = 150810.307617 13:35:56.861 [planetinfo.record]: sun_vector = 0.198 0.556 -0.807 13:35:56.861 [planetinfo.record]: sun_distance = 1301760 13:35:56.861 [planetinfo.record]: corona_flare = 0.069310 13:35:56.861 [planetinfo.record]: corona_hues = 0.954346 13:35:56.861 [planetinfo.record]: sun_color = 0.300431, 0.67251, 0.678201, 1 13:35:56.861 [planetinfo.record]: corona_shimmer = 0.320394 13:35:56.862 [planetinfo.record]: station_vector = -0.227 -0.241 0.944 13:35:56.862 [planetinfo.record]: station = coriolis 13:36:01.575 [PLANETINFO OVER]: Done 13:36:01.575 [PLANETINFO LOGGING]: 5 2 13:36:02.580 [planetinfo.record]: seed = 93 170 122 234 13:36:02.580 [planetinfo.record]: coordinates = 170 237 13:36:02.580 [planetinfo.record]: government = 3; 13:36:02.580 [planetinfo.record]: economy = 5; 13:36:02.580 [planetinfo.record]: techlevel = 6; 13:36:02.580 [planetinfo.record]: population = 33; 13:36:02.580 [planetinfo.record]: productivity = 9240; 13:36:02.580 [planetinfo.record]: name = "Arxetige"; 13:36:02.580 [planetinfo.record]: inhabitant = "Human Colonial"; 13:36:02.580 [planetinfo.record]: inhabitants = "Human Colonials"; 13:36:02.580 [planetinfo.record]: description = "The world Arxetige is fabled for its exciting lethal Es water and the Arxetigeian spotted shrew."; 13:36:02.587 [planetinfo.record]: air_color = 0.68819, 0.849977, 0.894938, 1; 13:36:02.587 [planetinfo.record]: cloud_alpha = 1.000000; 13:36:02.587 [planetinfo.record]: cloud_color = 0.644699, 0.6875, 0.623047, 1; 13:36:02.587 [planetinfo.record]: cloud_fraction = 0.430000; 13:36:02.587 [planetinfo.record]: land_color = 0.66, 0.653232, 0.551719, 1; 13:36:02.587 [planetinfo.record]: land_fraction = 0.290000; 13:36:02.587 [planetinfo.record]: polar_cloud_color = 0.777882, 0.809375, 0.761951, 1; 13:36:02.587 [planetinfo.record]: polar_land_color = 0.934, 0.931606, 0.895691, 1; 13:36:02.587 [planetinfo.record]: polar_sea_color = 0.745047, 0.934961, 0.921608, 1; 13:36:02.587 [planetinfo.record]: sea_color = 0.121948, 0.650391, 0.613235, 1; 13:36:02.587 [planetinfo.record]: rotation_speed = 0.003062 13:36:02.587 [planetinfo.record]: planet zpos = 610060.000000 13:36:02.587 [planetinfo.record]: sun_radius = 136052.005005 13:36:02.587 [planetinfo.record]: sun_vector = 0.133 0.975 -0.177 13:36:02.588 [planetinfo.record]: sun_distance = 1164660 13:36:02.588 [planetinfo.record]: corona_flare = 0.040518 13:36:02.588 [planetinfo.record]: corona_hues = 0.822678 13:36:02.588 [planetinfo.record]: sun_color = 0.578801, 0.707528, 0.810345, 1 13:36:02.588 [planetinfo.record]: corona_shimmer = 0.346465 13:36:02.588 [planetinfo.record]: station_vector = 0.965 -0.229 0.130 13:36:02.588 [planetinfo.record]: station = coriolis 13:36:07.283 [PLANETINFO OVER]: Done 13:36:07.284 [PLANETINFO LOGGING]: 5 3 13:36:08.287 [planetinfo.record]: seed = 177 131 232 28 13:36:08.287 [planetinfo.record]: coordinates = 131 23 13:36:08.287 [planetinfo.record]: government = 6; 13:36:08.287 [planetinfo.record]: economy = 7; 13:36:08.288 [planetinfo.record]: techlevel = 6; 13:36:08.288 [planetinfo.record]: population = 38; 13:36:08.288 [planetinfo.record]: productivity = 9120; 13:36:08.288 [planetinfo.record]: name = "Tetied"; 13:36:08.288 [planetinfo.record]: inhabitant = "Green Fat Bird"; 13:36:08.288 [planetinfo.record]: inhabitants = "Green Fat Birds"; 13:36:08.288 [planetinfo.record]: description = "This planet is reasonably noted for its exotic fish meat."; 13:36:08.301 [planetinfo.record]: air_color = 0.741232, 0.766275, 0.938514, 1; 13:36:08.302 [planetinfo.record]: cloud_alpha = 1.000000; 13:36:08.302 [planetinfo.record]: cloud_color = 0.792786, 0.802176, 0.818359, 1; 13:36:08.302 [planetinfo.record]: cloud_fraction = 0.380000; 13:36:08.302 [planetinfo.record]: land_color = 0.518203, 0.653353, 0.66, 1; 13:36:08.302 [planetinfo.record]: land_fraction = 0.400000; 13:36:08.302 [planetinfo.record]: polar_cloud_color = 0.851303, 0.85753, 0.868262, 1; 13:36:08.302 [planetinfo.record]: polar_land_color = 0.883834, 0.931648, 0.934, 1; 13:36:08.302 [planetinfo.record]: polar_sea_color = 0.930469, 0.902627, 0.863228, 1; 13:36:08.302 [planetinfo.record]: sea_color = 0.695312, 0.612091, 0.494324, 1; 13:36:08.302 [planetinfo.record]: rotation_speed = 0.004134 13:36:08.302 [planetinfo.record]: planet zpos = 722280.000000 13:36:08.302 [planetinfo.record]: sun_radius = 153486.520538 13:36:08.302 [planetinfo.record]: sun_vector = -0.513 -0.005 0.858 13:36:08.302 [planetinfo.record]: sun_distance = 1263990 13:36:08.302 [planetinfo.record]: corona_flare = 0.062762 13:36:08.302 [planetinfo.record]: corona_hues = 0.875282 13:36:08.303 [planetinfo.record]: sun_color = 0.678253, 0.679956, 0.307877, 1 13:36:08.303 [planetinfo.record]: corona_shimmer = 0.314853 13:36:08.304 [planetinfo.record]: station_vector = -0.033 0.986 0.161 13:36:08.304 [planetinfo.record]: station = coriolis 13:36:13.267 [PLANETINFO OVER]: Done 13:36:13.268 [PLANETINFO LOGGING]: 5 4 13:36:14.274 [planetinfo.record]: seed = 193 44 154 60 13:36:14.274 [planetinfo.record]: coordinates = 44 88 13:36:14.274 [planetinfo.record]: government = 0; 13:36:14.274 [planetinfo.record]: economy = 2; 13:36:14.274 [planetinfo.record]: techlevel = 5; 13:36:14.274 [planetinfo.record]: population = 23; 13:36:14.274 [planetinfo.record]: productivity = 5888; 13:36:14.274 [planetinfo.record]: name = "Telear"; 13:36:14.274 [planetinfo.record]: inhabitant = "Red Fat Bird"; 13:36:14.274 [planetinfo.record]: inhabitants = "Red Fat Birds"; 13:36:14.274 [planetinfo.record]: description = "The world Telear is scourged by evil disease."; 13:36:14.286 [planetinfo.record]: air_color = 0.622597, 0.5632, 0.870873, 1; 13:36:14.287 [planetinfo.record]: cloud_alpha = 1.000000; 13:36:14.287 [planetinfo.record]: cloud_color = 0.500028, 0.33165, 0.615234, 1; 13:36:14.287 [planetinfo.record]: cloud_fraction = 0.180000; 13:36:14.287 [planetinfo.record]: land_color = 0.440475, 0.344124, 0.568359, 1; 13:36:14.287 [planetinfo.record]: land_fraction = 0.330000; 13:36:14.287 [planetinfo.record]: polar_cloud_color = 0.685936, 0.553054, 0.776855, 1; 13:36:14.287 [planetinfo.record]: polar_land_color = 0.89011, 0.850137, 0.943164, 1; 13:36:14.287 [planetinfo.record]: polar_sea_color = 0.941406, 0.91704, 0.888544, 1; 13:36:14.287 [planetinfo.record]: sea_color = 0.585938, 0.525275, 0.45433, 1; 13:36:14.287 [planetinfo.record]: rotation_speed = 0.000619 13:36:14.287 [planetinfo.record]: planet zpos = 711840.000000 13:36:14.287 [planetinfo.record]: sun_radius = 156875.404053 13:36:14.287 [planetinfo.record]: sun_vector = -0.078 -0.912 0.403 13:36:14.287 [planetinfo.record]: sun_distance = 1245720 13:36:14.287 [planetinfo.record]: corona_flare = 0.068378 13:36:14.287 [planetinfo.record]: corona_hues = 0.854149 13:36:14.288 [planetinfo.record]: sun_color = 0.615631, 0.772283, 0.810791, 1 13:36:14.288 [planetinfo.record]: corona_shimmer = 0.501231 13:36:14.288 [planetinfo.record]: station_vector = -0.518 0.780 0.350 13:36:14.288 [planetinfo.record]: station = coriolis 13:36:19.001 [PLANETINFO OVER]: Done 13:36:19.001 [PLANETINFO LOGGING]: 5 5 13:36:20.005 [planetinfo.record]: seed = 29 41 160 21 13:36:20.006 [planetinfo.record]: coordinates = 41 42 13:36:20.006 [planetinfo.record]: government = 3; 13:36:20.006 [planetinfo.record]: economy = 2; 13:36:20.006 [planetinfo.record]: techlevel = 8; 13:36:20.006 [planetinfo.record]: population = 38; 13:36:20.006 [planetinfo.record]: productivity = 17024; 13:36:20.006 [planetinfo.record]: name = "Laesusso"; 13:36:20.006 [planetinfo.record]: inhabitant = "Green Bony Bird"; 13:36:20.006 [planetinfo.record]: inhabitants = "Green Bony Birds"; 13:36:20.006 [planetinfo.record]: description = "The planet Laesusso is cursed by dreadful civil war."; 13:36:20.023 [planetinfo.record]: air_color = 0.601, 0.927457, 0.888891, 1; 13:36:20.023 [planetinfo.record]: cloud_alpha = 1.000000; 13:36:20.023 [planetinfo.record]: cloud_color = 0.785156, 0.661805, 0.426315, 1; 13:36:20.023 [planetinfo.record]: cloud_fraction = 0.250000; 13:36:20.023 [planetinfo.record]: land_color = 0.381563, 0.555586, 0.66, 1; 13:36:20.023 [planetinfo.record]: land_fraction = 0.380000; 13:36:20.023 [planetinfo.record]: polar_cloud_color = 0.85332, 0.769533, 0.609574, 1; 13:36:20.023 [planetinfo.record]: polar_land_color = 0.835492, 0.89706, 0.934, 1; 13:36:20.024 [planetinfo.record]: polar_sea_color = 0.935938, 0.933788, 0.880915, 1; 13:36:20.024 [planetinfo.record]: sea_color = 0.640625, 0.63474, 0.489978, 1; 13:36:20.024 [planetinfo.record]: rotation_speed = 0.004898 13:36:20.024 [planetinfo.record]: planet zpos = 455070.000000 13:36:20.024 [planetinfo.record]: sun_radius = 75075.288391 13:36:20.024 [planetinfo.record]: sun_vector = 0.440 -0.143 0.887 13:36:20.024 [planetinfo.record]: sun_distance = 868770 13:36:20.024 [planetinfo.record]: corona_flare = 0.040347 13:36:20.024 [planetinfo.record]: corona_hues = 0.942245 13:36:20.024 [planetinfo.record]: sun_color = 0.31244, 0.63973, 0.807202, 1 13:36:20.024 [planetinfo.record]: corona_shimmer = 0.750854 13:36:20.024 [planetinfo.record]: station_vector = -0.829 0.555 0.066 13:36:20.024 [planetinfo.record]: station = coriolis 13:36:23.985 [PLANETINFO OVER]: Done 13:36:23.986 [PLANETINFO LOGGING]: 5 6 13:36:24.988 [planetinfo.record]: seed = 181 39 202 57 13:36:24.988 [planetinfo.record]: coordinates = 39 168 13:36:24.988 [planetinfo.record]: government = 6; 13:36:24.988 [planetinfo.record]: economy = 0; 13:36:24.989 [planetinfo.record]: techlevel = 13; 13:36:24.989 [planetinfo.record]: population = 59; 13:36:24.989 [planetinfo.record]: productivity = 47200; 13:36:24.989 [planetinfo.record]: name = "Oresmaa"; 13:36:24.989 [planetinfo.record]: inhabitant = "Red Rodent"; 13:36:24.989 [planetinfo.record]: inhabitants = "Red Rodents"; 13:36:24.989 [planetinfo.record]: description = "The planet Oresmaa is scourged by deadly disease."; 13:36:25.028 [planetinfo.record]: air_color = 0.720191, 0.803289, 0.9164, 1; 13:36:25.028 [planetinfo.record]: cloud_alpha = 1.000000; 13:36:25.028 [planetinfo.record]: cloud_color = 0.719643, 0.751953, 0.743623, 1; 13:36:25.028 [planetinfo.record]: cloud_fraction = 0.390000; 13:36:25.028 [planetinfo.record]: land_color = 0.66, 0.190781, 0.44372, 1; 13:36:25.028 [planetinfo.record]: land_fraction = 0.250000; 13:36:25.028 [planetinfo.record]: polar_cloud_color = 0.815864, 0.838379, 0.832574, 1; 13:36:25.028 [planetinfo.record]: polar_land_color = 0.934, 0.767996, 0.857483, 1; 13:36:25.028 [planetinfo.record]: polar_sea_color = 0.940234, 0.921991, 0.887163, 1; 13:36:25.028 [planetinfo.record]: sea_color = 0.597656, 0.551271, 0.462717, 1; 13:36:25.028 [planetinfo.record]: rotation_speed = 0.000774 13:36:25.028 [planetinfo.record]: planet zpos = 619080.000000 13:36:25.029 [planetinfo.record]: sun_radius = 132626.037903 13:36:25.029 [planetinfo.record]: sun_vector = -0.343 -0.540 -0.769 13:36:25.029 [planetinfo.record]: sun_distance = 877030 13:36:25.029 [planetinfo.record]: corona_flare = 0.011858 13:36:25.029 [planetinfo.record]: corona_hues = 0.574677 13:36:25.029 [planetinfo.record]: sun_color = 0.406135, 0.460658, 0.775702, 1 13:36:25.029 [planetinfo.record]: corona_shimmer = 0.314895 13:36:25.029 [planetinfo.record]: station_vector = 0.927 -0.367 0.076 13:36:25.029 [planetinfo.record]: station = dodecahedron 13:36:29.671 [PLANETINFO OVER]: Done 13:36:29.672 [PLANETINFO LOGGING]: 5 7 13:36:30.689 [planetinfo.record]: seed = 25 175 104 36 13:36:30.689 [planetinfo.record]: coordinates = 175 107 13:36:30.689 [planetinfo.record]: government = 3; 13:36:30.689 [planetinfo.record]: economy = 3; 13:36:30.689 [planetinfo.record]: techlevel = 9; 13:36:30.689 [planetinfo.record]: population = 43; 13:36:30.689 [planetinfo.record]: productivity = 16856; 13:36:30.689 [planetinfo.record]: name = "Zarienla"; 13:36:30.689 [planetinfo.record]: inhabitant = "Human Colonial"; 13:36:30.689 [planetinfo.record]: inhabitants = "Human Colonials"; 13:36:30.689 [planetinfo.record]: description = "This world is plagued by frequent solar activity."; 13:36:30.708 [planetinfo.record]: air_color = 0.736959, 0.764643, 0.942416, 1; 13:36:30.708 [planetinfo.record]: cloud_alpha = 1.000000; 13:36:30.708 [planetinfo.record]: cloud_color = 0.784683, 0.802416, 0.830078, 1; 13:36:30.708 [planetinfo.record]: cloud_fraction = 0.340000; 13:36:30.708 [planetinfo.record]: land_color = 0.523359, 0.66, 0.650392, 1; 13:36:30.708 [planetinfo.record]: land_fraction = 0.430000; 13:36:30.708 [planetinfo.record]: polar_cloud_color = 0.843678, 0.855341, 0.873535, 1; 13:36:30.708 [planetinfo.record]: polar_land_color = 0.885658, 0.934, 0.930601, 1; 13:36:30.708 [planetinfo.record]: polar_sea_color = 0.893518, 0.933984, 0.886464, 1; 13:36:30.708 [planetinfo.record]: sea_color = 0.545747, 0.660156, 0.525804, 1; 13:36:30.708 [planetinfo.record]: rotation_speed = 0.003207 13:36:30.709 [planetinfo.record]: planet zpos = 562100.000000 13:36:30.709 [planetinfo.record]: sun_radius = 114855.980682 13:36:30.709 [planetinfo.record]: sun_vector = -0.552 -0.208 -0.807 13:36:30.709 [planetinfo.record]: sun_distance = 762850 13:36:30.709 [planetinfo.record]: corona_flare = 0.068808 13:36:30.709 [planetinfo.record]: corona_hues = 0.605377 13:36:30.709 [planetinfo.record]: sun_color = 0.635995, 0.755718, 0.805032, 1 13:36:30.709 [planetinfo.record]: corona_shimmer = 0.397549 13:36:30.709 [planetinfo.record]: station_vector = -0.866 0.202 0.458 13:36:30.709 [planetinfo.record]: station = coriolis 13:36:35.959 [PLANETINFO OVER]: Done 13:36:35.960 [PLANETINFO LOGGING]: 5 8 13:36:36.964 [planetinfo.record]: seed = 185 117 10 199 13:36:36.964 [planetinfo.record]: coordinates = 117 18 13:36:36.964 [planetinfo.record]: government = 7; 13:36:36.964 [planetinfo.record]: economy = 2; 13:36:36.964 [planetinfo.record]: techlevel = 10; 13:36:36.964 [planetinfo.record]: population = 50; 13:36:36.964 [planetinfo.record]: productivity = 35200; 13:36:36.964 [planetinfo.record]: name = "Soamaxe"; 13:36:36.964 [planetinfo.record]: inhabitant = "Human Colonial"; 13:36:36.964 [planetinfo.record]: inhabitants = "Human Colonials"; 13:36:36.964 [planetinfo.record]: description = "The planet Soamaxe is famous for its inhabitants’ exceptional loathing of sit coms but cursed by deadly disease."; 13:36:36.988 [planetinfo.record]: air_color = 0.493025, 0.984041, 0.826397, 1; 13:36:36.988 [planetinfo.record]: cloud_alpha = 1.000000; 13:36:36.988 [planetinfo.record]: cloud_color = 0.955078, 0.137223, 0.0970001, 1; 13:36:36.988 [planetinfo.record]: cloud_fraction = 0.270000; 13:36:36.988 [planetinfo.record]: land_color = 0.66, 0.180469, 0.495161, 1; 13:36:36.988 [planetinfo.record]: land_fraction = 0.360000; 13:36:36.988 [planetinfo.record]: polar_cloud_color = 0.929785, 0.432162, 0.407689, 1; 13:36:36.988 [planetinfo.record]: polar_land_color = 0.934, 0.764348, 0.875682, 1; 13:36:36.988 [planetinfo.record]: polar_sea_color = 0.915711, 0.93457, 0.882092, 1; 13:36:36.988 [planetinfo.record]: sea_color = 0.601483, 0.654297, 0.507336, 1; 13:36:36.988 [planetinfo.record]: rotation_speed = 0.003507 13:36:36.988 [planetinfo.record]: planet zpos = 519750.000000 13:36:36.988 [planetinfo.record]: sun_radius = 131295.101166 13:36:36.988 [planetinfo.record]: sun_vector = 0.774 0.630 -0.057 13:36:36.988 [planetinfo.record]: sun_distance = 1039500 13:36:36.988 [planetinfo.record]: corona_flare = 0.004015 13:36:36.988 [planetinfo.record]: corona_hues = 0.840340 13:36:36.988 [planetinfo.record]: sun_color = 0.223555, 0.293574, 0.735535, 1 13:36:36.989 [planetinfo.record]: corona_shimmer = 0.491252 13:36:36.989 [planetinfo.record]: station_vector = -0.111 -0.786 0.609 13:36:36.989 [planetinfo.record]: station = coriolis 13:36:41.280 [PLANETINFO OVER]: Done 13:36:41.280 [PLANETINFO LOGGING]: 5 9 13:36:42.283 [planetinfo.record]: seed = 37 162 64 125 13:36:42.283 [planetinfo.record]: coordinates = 162 139 13:36:42.283 [planetinfo.record]: government = 4; 13:36:42.283 [planetinfo.record]: economy = 3; 13:36:42.283 [planetinfo.record]: techlevel = 8; 13:36:42.283 [planetinfo.record]: population = 40; 13:36:42.283 [planetinfo.record]: productivity = 17920; 13:36:42.283 [planetinfo.record]: name = "Ismaarbe"; 13:36:42.283 [planetinfo.record]: inhabitant = "Human Colonial"; 13:36:42.283 [planetinfo.record]: inhabitants = "Human Colonials"; 13:36:42.283 [planetinfo.record]: description = "Ismaarbe is famous for its inhabitants’ ancient loathing of casinos but plagued by frequent earthquakes."; 13:36:42.293 [planetinfo.record]: air_color = 0.572882, 0.950871, 0.912118, 1; 13:36:42.293 [planetinfo.record]: cloud_alpha = 1.000000; 13:36:42.293 [planetinfo.record]: cloud_color = 0.855469, 0.704676, 0.347534, 1; 13:36:42.293 [planetinfo.record]: cloud_fraction = 0.150000; 13:36:42.293 [planetinfo.record]: land_color = 0.446016, 0.524588, 0.66, 1; 13:36:42.293 [planetinfo.record]: land_fraction = 0.550000; 13:36:42.293 [planetinfo.record]: polar_cloud_color = 0.884961, 0.787466, 0.556557, 1; 13:36:42.293 [planetinfo.record]: polar_land_color = 0.858295, 0.886093, 0.934, 1; 13:36:42.293 [planetinfo.record]: polar_sea_color = 0.935156, 0.908467, 0.874152, 1; 13:36:42.293 [planetinfo.record]: sea_color = 0.648438, 0.574412, 0.479236, 1; 13:36:42.293 [planetinfo.record]: rotation_speed = 0.004027 13:36:42.293 [planetinfo.record]: planet zpos = 756720.000000 13:36:42.293 [planetinfo.record]: sun_radius = 150201.460876 13:36:42.294 [planetinfo.record]: sun_vector = -0.427 -0.240 -0.872 13:36:42.294 [planetinfo.record]: sun_distance = 1198140 13:36:42.294 [planetinfo.record]: corona_flare = 0.016101 13:36:42.294 [planetinfo.record]: corona_hues = 0.992073 13:36:42.294 [planetinfo.record]: sun_color = 0.510017, 0.572479, 0.833954, 1 13:36:42.294 [planetinfo.record]: corona_shimmer = 0.670912 13:36:42.294 [planetinfo.record]: station_vector = -0.229 0.950 0.211 13:36:42.294 [planetinfo.record]: station = coriolis 13:36:47.199 [PLANETINFO OVER]: Done 13:36:47.200 [PLANETINFO LOGGING]: 5 10 13:36:48.209 [planetinfo.record]: seed = 77 243 90 105 13:36:48.209 [planetinfo.record]: coordinates = 243 202 13:36:48.209 [planetinfo.record]: government = 1; 13:36:48.209 [planetinfo.record]: economy = 2; 13:36:48.209 [planetinfo.record]: techlevel = 9; 13:36:48.209 [planetinfo.record]: population = 40; 13:36:48.210 [planetinfo.record]: productivity = 12800; 13:36:48.210 [planetinfo.record]: name = "Essoza"; 13:36:48.210 [planetinfo.record]: inhabitant = "Human Colonial"; 13:36:48.210 [planetinfo.record]: inhabitants = "Human Colonials"; 13:36:48.210 [planetinfo.record]: description = "The planet Essoza is reasonably noted for mud tennis and the Essozaian mountain lobstoid."; 13:36:48.217 [planetinfo.record]: air_color = 0.711207, 0.828492, 0.891686, 1; 13:36:48.217 [planetinfo.record]: cloud_alpha = 1.000000; 13:36:48.217 [planetinfo.record]: cloud_color = 0.67244, 0.677734, 0.672771, 1; 13:36:48.217 [planetinfo.record]: cloud_fraction = 0.370000; 13:36:48.218 [planetinfo.record]: land_color = 0.66, 0.583825, 0.502734, 1; 13:36:48.218 [planetinfo.record]: land_fraction = 0.680000; 13:36:48.218 [planetinfo.record]: polar_cloud_color = 0.80105, 0.80498, 0.801296, 1; 13:36:48.218 [planetinfo.record]: polar_land_color = 0.934, 0.90705, 0.878361, 1; 13:36:48.218 [planetinfo.record]: polar_sea_color = 0.721922, 0.850433, 0.927539, 1; 13:36:48.218 [planetinfo.record]: sea_color = 0.0820847, 0.483663, 0.724609, 1; 13:36:48.218 [planetinfo.record]: rotation_speed = 0.003640 13:36:48.218 [planetinfo.record]: planet zpos = 536300.000000 13:36:48.218 [planetinfo.record]: sun_radius = 110717.439423 13:36:48.218 [planetinfo.record]: sun_vector = 0.686 -0.538 0.490 13:36:48.218 [planetinfo.record]: sun_distance = 1126230 13:36:48.218 [planetinfo.record]: corona_flare = 0.049605 13:36:48.218 [planetinfo.record]: corona_hues = 0.697136 13:36:48.218 [planetinfo.record]: sun_color = 0.433782, 0.604795, 0.738974, 1 13:36:48.219 [planetinfo.record]: corona_shimmer = 0.301623 13:36:48.219 [planetinfo.record]: station_vector = -0.996 -0.086 0.038 13:36:48.219 [planetinfo.record]: station = coriolis 13:36:54.116 [PLANETINFO OVER]: Done 13:36:54.117 [PLANETINFO LOGGING]: 5 11 13:36:55.136 [planetinfo.record]: seed = 193 20 40 192 13:36:55.136 [planetinfo.record]: coordinates = 20 132 13:36:55.136 [planetinfo.record]: government = 0; 13:36:55.136 [planetinfo.record]: economy = 6; 13:36:55.136 [planetinfo.record]: techlevel = 1; 13:36:55.136 [planetinfo.record]: population = 11; 13:36:55.136 [planetinfo.record]: productivity = 1408; 13:36:55.136 [planetinfo.record]: name = "Eddi"; 13:36:55.136 [planetinfo.record]: inhabitant = "Human Colonial"; 13:36:55.136 [planetinfo.record]: inhabitants = "Human Colonials"; 13:36:55.136 [planetinfo.record]: description = "This world is a tedious little planet."; 13:36:55.147 [planetinfo.record]: air_color = 0.450232, 0.630305, 0.891686, 1; 13:36:55.147 [planetinfo.record]: cloud_alpha = 1.000000; 13:36:55.147 [planetinfo.record]: cloud_color = 0.0767746, 0.677734, 0.550969, 1; 13:36:55.148 [planetinfo.record]: cloud_fraction = 0.430000; 13:36:55.148 [planetinfo.record]: land_color = 0.479531, 0.49504, 0.66, 1; 13:36:55.148 [planetinfo.record]: land_fraction = 0.440000; 13:36:55.148 [planetinfo.record]: polar_cloud_color = 0.358861, 0.80498, 0.710877, 1; 13:36:55.148 [planetinfo.record]: polar_land_color = 0.870152, 0.875639, 0.934, 1; 13:36:55.148 [planetinfo.record]: polar_sea_color = 0.947656, 0.912283, 0.899996, 1; 13:36:55.148 [planetinfo.record]: sea_color = 0.523438, 0.445284, 0.418137, 1; 13:36:55.148 [planetinfo.record]: rotation_speed = 0.002317 13:36:55.148 [planetinfo.record]: planet zpos = 311960.000000 13:36:55.148 [planetinfo.record]: sun_radius = 83327.838745 13:36:55.148 [planetinfo.record]: sun_vector = -0.709 -0.109 0.697 13:36:55.148 [planetinfo.record]: sun_distance = 567200 13:36:55.148 [planetinfo.record]: corona_flare = 0.053775 13:36:55.148 [planetinfo.record]: corona_hues = 0.557152 13:36:55.148 [planetinfo.record]: sun_color = 0.828412, 0.813305, 0.808579, 1 13:36:55.149 [planetinfo.record]: corona_shimmer = 0.298670 13:36:55.149 [planetinfo.record]: station_vector = 0.556 0.694 0.458 13:36:55.149 [planetinfo.record]: station = coriolis 13:37:00.094 [PLANETINFO OVER]: Done 13:37:00.095 [PLANETINFO LOGGING]: 5 12 13:37:01.099 [planetinfo.record]: seed = 241 70 186 205 13:37:01.099 [planetinfo.record]: coordinates = 70 45 13:37:01.099 [planetinfo.record]: government = 6; 13:37:01.099 [planetinfo.record]: economy = 5; 13:37:01.099 [planetinfo.record]: techlevel = 7; 13:37:01.099 [planetinfo.record]: population = 40; 13:37:01.099 [planetinfo.record]: productivity = 16000; 13:37:01.099 [planetinfo.record]: name = "Dixetiso"; 13:37:01.099 [planetinfo.record]: inhabitant = "Bony Bird"; 13:37:01.099 [planetinfo.record]: inhabitants = "Bony Birds"; 13:37:01.099 [planetinfo.record]: description = "Dixetiso is most famous for the Dixetisoian deadly monkey and its exotic cuisine."; 13:37:01.120 [planetinfo.record]: air_color = 0.485592, 0.997699, 0.873252, 1; 13:37:01.120 [planetinfo.record]: cloud_alpha = 1.000000; 13:37:01.120 [planetinfo.record]: cloud_color = 0.996094, 0.322101, 0.0583649, 1; 13:37:01.120 [planetinfo.record]: cloud_fraction = 0.430000; 13:37:01.120 [planetinfo.record]: land_color = 0.66, 0.613594, 0.648398, 1; 13:37:01.120 [planetinfo.record]: land_fraction = 0.440000; 13:37:01.120 [planetinfo.record]: polar_cloud_color = 0.948242, 0.547233, 0.390316, 1; 13:37:01.120 [planetinfo.record]: polar_land_color = 0.934, 0.917582, 0.929896, 1; 13:37:01.120 [planetinfo.record]: polar_sea_color = 0.93125, 0.874553, 0.854858, 1; 13:37:01.120 [planetinfo.record]: sea_color = 0.6875, 0.520073, 0.461914, 1; 13:37:01.120 [planetinfo.record]: rotation_speed = 0.001315 13:37:01.120 [planetinfo.record]: planet zpos = 807820.000000 13:37:01.120 [planetinfo.record]: sun_radius = 176134.130859 13:37:01.120 [planetinfo.record]: sun_vector = 0.547 0.611 0.572 13:37:01.120 [planetinfo.record]: sun_distance = 1242800 13:37:01.120 [planetinfo.record]: corona_flare = 0.073256 13:37:01.120 [planetinfo.record]: corona_hues = 0.841660 13:37:01.121 [planetinfo.record]: sun_color = 0.715948, 0.709415, 0.649672, 1 13:37:01.121 [planetinfo.record]: corona_shimmer = 0.267317 13:37:01.121 [planetinfo.record]: station_vector = -0.298 0.782 0.548 13:37:01.121 [planetinfo.record]: station = coriolis 13:37:05.290 [PLANETINFO OVER]: Done 13:37:05.292 [PLANETINFO LOGGING]: 5 13 13:37:06.310 [planetinfo.record]: seed = 109 103 32 1 13:37:06.310 [planetinfo.record]: coordinates = 103 87 13:37:06.310 [planetinfo.record]: government = 5; 13:37:06.310 [planetinfo.record]: economy = 7; 13:37:06.310 [planetinfo.record]: techlevel = 6; 13:37:06.310 [planetinfo.record]: population = 37; 13:37:06.310 [planetinfo.record]: productivity = 7992; 13:37:06.310 [planetinfo.record]: name = "Leonus"; 13:37:06.311 [planetinfo.record]: inhabitant = "Human Colonial"; 13:37:06.311 [planetinfo.record]: inhabitants = "Human Colonials"; 13:37:06.311 [planetinfo.record]: description = "The world Leonus is a boring planet."; 13:37:06.328 [planetinfo.record]: air_color = 0.426945, 0.694806, 0.889084, 1; 13:37:06.328 [planetinfo.record]: cloud_alpha = 1.000000; 13:37:06.328 [planetinfo.record]: cloud_color = 0.0261688, 0.669922, 0.202195, 1; 13:37:06.328 [planetinfo.record]: cloud_fraction = 0.290000; 13:37:06.328 [planetinfo.record]: land_color = 0.325851, 0.0489844, 0.66, 1; 13:37:06.328 [planetinfo.record]: land_fraction = 0.320000; 13:37:06.328 [planetinfo.record]: polar_cloud_color = 0.320116, 0.801465, 0.451735, 1; 13:37:06.328 [planetinfo.record]: polar_land_color = 0.815782, 0.71783, 0.934, 1; 13:37:06.328 [planetinfo.record]: polar_sea_color = 0.941016, 0.936806, 0.892035, 1; 13:37:06.328 [planetinfo.record]: sea_color = 0.589844, 0.57929, 0.467036, 1; 13:37:06.328 [planetinfo.record]: rotation_speed = 0.003023 13:37:06.328 [planetinfo.record]: planet zpos = 381000.000000 13:37:06.329 [planetinfo.record]: sun_radius = 94592.578888 13:37:06.329 [planetinfo.record]: sun_vector = 0.264 0.629 -0.731 13:37:06.329 [planetinfo.record]: sun_distance = 635000 13:37:06.329 [planetinfo.record]: corona_flare = 0.038770 13:37:06.329 [planetinfo.record]: corona_hues = 0.890556 13:37:06.329 [planetinfo.record]: sun_color = 0.799802, 0.765491, 0.693618, 1 13:37:06.329 [planetinfo.record]: corona_shimmer = 0.505110 13:37:06.329 [planetinfo.record]: station_vector = -0.792 0.568 0.223 13:37:06.329 [planetinfo.record]: station = coriolis 13:37:11.209 [PLANETINFO OVER]: Done 13:37:11.209 [PLANETINFO LOGGING]: 5 14 13:37:12.213 [planetinfo.record]: seed = 37 233 42 209 13:37:12.213 [planetinfo.record]: coordinates = 233 40 13:37:12.213 [planetinfo.record]: government = 4; 13:37:12.213 [planetinfo.record]: economy = 0; 13:37:12.213 [planetinfo.record]: techlevel = 10; 13:37:12.213 [planetinfo.record]: population = 45; 13:37:12.213 [planetinfo.record]: productivity = 28800; 13:37:12.213 [planetinfo.record]: name = "Atxeteer"; 13:37:12.213 [planetinfo.record]: inhabitant = "Human Colonial"; 13:37:12.213 [planetinfo.record]: inhabitants = "Human Colonials"; 13:37:12.213 [planetinfo.record]: description = "This planet is mildly fabled for the Atxeteerian edible poet but scourged by frequent civil war."; 13:37:12.216 [planetinfo.record]: air_color = 0.538112, 0.968432, 0.890728, 1; 13:37:12.216 [planetinfo.record]: cloud_alpha = 1.000000; 13:37:12.216 [planetinfo.record]: cloud_color = 0.908203, 0.55388, 0.241241, 1; 13:37:12.217 [planetinfo.record]: cloud_fraction = 0.390000; 13:37:12.217 [planetinfo.record]: land_color = 0.462189, 0.66, 0.244922, 1; 13:37:12.217 [planetinfo.record]: land_fraction = 0.660000; 13:37:12.217 [planetinfo.record]: polar_cloud_color = 0.908691, 0.68712, 0.491616, 1; 13:37:12.217 [planetinfo.record]: polar_land_color = 0.864017, 0.934, 0.78715, 1; 13:37:12.217 [planetinfo.record]: polar_sea_color = 0.882288, 0.929297, 0.901752, 1; 13:37:12.217 [planetinfo.record]: sea_color = 0.563968, 0.707031, 0.623205, 1; 13:37:12.217 [planetinfo.record]: rotation_speed = 0.001498 13:37:12.217 [planetinfo.record]: planet zpos = 429650.000000 13:37:12.217 [planetinfo.record]: sun_radius = 79352.577972 13:37:12.217 [planetinfo.record]: sun_vector = 0.016 0.937 0.349 13:37:12.217 [planetinfo.record]: sun_distance = 727100 13:37:12.217 [planetinfo.record]: corona_flare = 0.069249 13:37:12.217 [planetinfo.record]: corona_hues = 0.838951 13:37:12.217 [planetinfo.record]: sun_color = 0.736539, 0.631054, 0.573985, 1 13:37:12.217 [planetinfo.record]: corona_shimmer = 0.362791 13:37:12.217 [planetinfo.record]: station_vector = 0.317 -0.878 0.359 13:37:12.218 [planetinfo.record]: station = coriolis 13:37:16.697 [PLANETINFO OVER]: Done 13:37:16.698 [PLANETINFO LOGGING]: 5 15 13:37:17.702 [planetinfo.record]: seed = 169 80 40 208 13:37:17.702 [planetinfo.record]: coordinates = 80 156 13:37:17.702 [planetinfo.record]: government = 5; 13:37:17.702 [planetinfo.record]: economy = 4; 13:37:17.702 [planetinfo.record]: techlevel = 6; 13:37:17.702 [planetinfo.record]: population = 34; 13:37:17.702 [planetinfo.record]: productivity = 14688; 13:37:17.702 [planetinfo.record]: name = "Erisriin"; 13:37:17.702 [planetinfo.record]: inhabitant = "Human Colonial"; 13:37:17.702 [planetinfo.record]: inhabitants = "Human Colonials"; 13:37:17.702 [planetinfo.record]: description = "Erisriin is ravaged by unpredictable civil war."; 13:37:17.720 [planetinfo.record]: air_color = 0.706292, 0.525881, 0.933311, 1; 13:37:17.720 [planetinfo.record]: cloud_alpha = 1.000000; 13:37:17.720 [planetinfo.record]: cloud_color = 0.802734, 0.23204, 0.606558, 1; 13:37:17.720 [planetinfo.record]: cloud_fraction = 0.370000; 13:37:17.720 [planetinfo.record]: land_color = 0.446826, 0.523438, 0.0776978, 1; 13:37:17.720 [planetinfo.record]: land_fraction = 0.560000; 13:37:17.720 [planetinfo.record]: polar_cloud_color = 0.86123, 0.478555, 0.729686, 1; 13:37:17.720 [planetinfo.record]: polar_land_color = 0.912981, 0.947656, 0.745909, 1; 13:37:17.720 [planetinfo.record]: polar_sea_color = 0.882072, 0.930859, 0.880498, 1; 13:37:17.720 [planetinfo.record]: sea_color = 0.546457, 0.691406, 0.541782, 1; 13:37:17.720 [planetinfo.record]: rotation_speed = 0.001396 13:37:17.720 [planetinfo.record]: planet zpos = 405440.000000 13:37:17.720 [planetinfo.record]: sun_radius = 79138.007812 13:37:17.720 [planetinfo.record]: sun_vector = -0.690 -0.305 -0.656 13:37:17.720 [planetinfo.record]: sun_distance = 637120 13:37:17.720 [planetinfo.record]: corona_flare = 0.035100 13:37:17.720 [planetinfo.record]: corona_hues = 0.884743 13:37:17.720 [planetinfo.record]: sun_color = 0.768033, 0.731906, 0.59496, 1 13:37:17.720 [planetinfo.record]: corona_shimmer = 0.298978 13:37:17.720 [planetinfo.record]: station_vector = -0.839 -0.200 0.506 13:37:17.720 [planetinfo.record]: station = coriolis 13:37:22.929 [PLANETINFO OVER]: Done 13:37:22.929 [PLANETINFO LOGGING]: 5 16 13:37:23.932 [planetinfo.record]: seed = 105 108 170 8 13:37:23.932 [planetinfo.record]: coordinates = 108 222 13:37:23.932 [planetinfo.record]: government = 5; 13:37:23.932 [planetinfo.record]: economy = 6; 13:37:23.932 [planetinfo.record]: techlevel = 4; 13:37:23.932 [planetinfo.record]: population = 28; 13:37:23.932 [planetinfo.record]: productivity = 8064; 13:37:23.932 [planetinfo.record]: name = "Usbeus"; 13:37:23.932 [planetinfo.record]: inhabitant = "Small Green Horned Lizard"; 13:37:23.932 [planetinfo.record]: inhabitants = "Small Green Horned Lizards"; 13:37:23.932 [planetinfo.record]: description = "The world Usbeus is a boring world."; 13:37:23.934 [planetinfo.record]: air_color = 0.742942, 0.591025, 0.887133, 1; 13:37:23.934 [planetinfo.record]: cloud_alpha = 1.000000; 13:37:23.934 [planetinfo.record]: cloud_color = 0.664062, 0.396881, 0.51586, 1; 13:37:23.934 [planetinfo.record]: cloud_fraction = 0.110000; 13:37:23.934 [planetinfo.record]: land_color = 0.66, 0.322266, 0.504326, 1; 13:37:23.934 [planetinfo.record]: land_fraction = 0.450000; 13:37:23.934 [planetinfo.record]: polar_cloud_color = 0.798828, 0.597951, 0.687404, 1; 13:37:23.934 [planetinfo.record]: polar_land_color = 0.934, 0.814514, 0.878924, 1; 13:37:23.934 [planetinfo.record]: polar_sea_color = 0.943359, 0.93261, 0.895915, 1; 13:37:23.934 [planetinfo.record]: sea_color = 0.566406, 0.540591, 0.452461, 1; 13:37:23.934 [planetinfo.record]: rotation_speed = 0.004043 13:37:23.934 [planetinfo.record]: planet zpos = 596640.000000 13:37:23.934 [planetinfo.record]: sun_radius = 136008.507690 13:37:23.935 [planetinfo.record]: sun_vector = -0.081 0.811 0.580 13:37:23.935 [planetinfo.record]: sun_distance = 1143560 13:37:23.935 [planetinfo.record]: corona_flare = 0.001404 13:37:23.935 [planetinfo.record]: corona_hues = 0.665375 13:37:23.935 [planetinfo.record]: sun_color = 0.767014, 0.588188, 0.57902, 1 13:37:23.935 [planetinfo.record]: corona_shimmer = 1.225843 13:37:23.935 [planetinfo.record]: station_vector = -0.520 0.803 0.290 13:37:23.935 [planetinfo.record]: station = coriolis 13:37:29.041 [PLANETINFO OVER]: Done 13:37:29.042 [PLANETINFO LOGGING]: 5 17 13:37:30.048 [planetinfo.record]: seed = 245 36 64 65 13:37:30.048 [planetinfo.record]: coordinates = 36 200 13:37:30.048 [planetinfo.record]: government = 6; 13:37:30.048 [planetinfo.record]: economy = 0; 13:37:30.048 [planetinfo.record]: techlevel = 10; 13:37:30.048 [planetinfo.record]: population = 47; 13:37:30.048 [planetinfo.record]: productivity = 37600; 13:37:30.048 [planetinfo.record]: name = "Lerela"; 13:37:30.048 [planetinfo.record]: inhabitant = "Human Colonial"; 13:37:30.048 [planetinfo.record]: inhabitants = "Human Colonials"; 13:37:30.048 [planetinfo.record]: description = "The world Lerela is scourged by evil disease."; 13:37:30.068 [planetinfo.record]: air_color = 0.702149, 0.579956, 0.88258, 1; 13:37:30.068 [planetinfo.record]: cloud_alpha = 1.000000; 13:37:30.068 [planetinfo.record]: cloud_color = 0.650391, 0.370926, 0.587074, 1; 13:37:30.068 [planetinfo.record]: cloud_fraction = 0.400000; 13:37:30.068 [planetinfo.record]: land_color = 0.605799, 0.66, 0.126328, 1; 13:37:30.068 [planetinfo.record]: land_fraction = 0.300000; 13:37:30.068 [planetinfo.record]: polar_cloud_color = 0.792676, 0.579799, 0.744446, 1; 13:37:30.068 [planetinfo.record]: polar_land_color = 0.914824, 0.934, 0.745193, 1; 13:37:30.068 [planetinfo.record]: polar_sea_color = 0.919387, 0.935547, 0.883836, 1; 13:37:30.068 [planetinfo.record]: sea_color = 0.599999, 0.644531, 0.502029, 1; 13:37:30.068 [planetinfo.record]: rotation_speed = 0.002105 13:37:30.068 [planetinfo.record]: planet zpos = 310800.000000 13:37:30.068 [planetinfo.record]: sun_radius = 68159.650269 13:37:30.068 [planetinfo.record]: sun_vector = 0.639 0.163 0.752 13:37:30.068 [planetinfo.record]: sun_distance = 714840 13:37:30.068 [planetinfo.record]: corona_flare = 0.086215 13:37:30.068 [planetinfo.record]: corona_hues = 0.521561 13:37:30.069 [planetinfo.record]: sun_color = 0.331048, 0.418197, 0.661209, 1 13:37:30.069 [planetinfo.record]: corona_shimmer = 0.393123 13:37:30.069 [planetinfo.record]: station_vector = 0.782 -0.533 0.322 13:37:30.069 [planetinfo.record]: station = coriolis 13:37:34.911 [PLANETINFO OVER]: Done 13:37:34.912 [PLANETINFO LOGGING]: 5 18 13:37:35.915 [planetinfo.record]: seed = 61 5 58 201 13:37:35.915 [planetinfo.record]: coordinates = 5 149 13:37:35.915 [planetinfo.record]: government = 7; 13:37:35.915 [planetinfo.record]: economy = 5; 13:37:35.915 [planetinfo.record]: techlevel = 7; 13:37:35.915 [planetinfo.record]: population = 41; 13:37:35.915 [planetinfo.record]: productivity = 18040; 13:37:35.915 [planetinfo.record]: name = "Esgeen"; 13:37:35.915 [planetinfo.record]: inhabitant = "Human Colonial"; 13:37:35.915 [planetinfo.record]: inhabitants = "Human Colonials"; 13:37:35.915 [planetinfo.record]: description = "This world is a tedious place."; 13:37:35.928 [planetinfo.record]: air_color = 0.687186, 0.847363, 0.898189, 1; 13:37:35.928 [planetinfo.record]: cloud_alpha = 1.000000; 13:37:35.928 [planetinfo.record]: cloud_color = 0.64326, 0.697266, 0.623726, 1; 13:37:35.928 [planetinfo.record]: cloud_fraction = 0.210000; 13:37:35.928 [planetinfo.record]: land_color = 0.555727, 0.229453, 0.66, 1; 13:37:35.928 [planetinfo.record]: land_fraction = 0.380000; 13:37:35.928 [planetinfo.record]: polar_cloud_color = 0.774376, 0.81377, 0.760127, 1; 13:37:35.928 [planetinfo.record]: polar_land_color = 0.897109, 0.781678, 0.934, 1; 13:37:35.928 [planetinfo.record]: polar_sea_color = 0.949023, 0.905769, 0.901294, 1; 13:37:35.928 [planetinfo.record]: sea_color = 0.509766, 0.416829, 0.407215, 1; 13:37:35.928 [planetinfo.record]: rotation_speed = 0.004272 13:37:35.928 [planetinfo.record]: planet zpos = 666250.000000 13:37:35.928 [planetinfo.record]: sun_radius = 138709.545135 13:37:35.928 [planetinfo.record]: sun_vector = -0.335 -0.276 0.901 13:37:35.928 [planetinfo.record]: sun_distance = 1127500 13:37:35.928 [planetinfo.record]: corona_flare = 0.006332 13:37:35.928 [planetinfo.record]: corona_hues = 0.903549 13:37:35.928 [planetinfo.record]: sun_color = 0.553819, 0.626431, 0.67478, 1 13:37:35.928 [planetinfo.record]: corona_shimmer = 1.056964 13:37:35.929 [planetinfo.record]: station_vector = 0.868 -0.323 0.376 13:37:35.929 [planetinfo.record]: station = coriolis 13:37:40.441 [PLANETINFO OVER]: Done 13:37:40.442 [PLANETINFO LOGGING]: 5 19 13:37:41.446 [planetinfo.record]: seed = 209 94 104 244 13:37:41.446 [planetinfo.record]: coordinates = 94 50 13:37:41.446 [planetinfo.record]: government = 2; 13:37:41.446 [planetinfo.record]: economy = 2; 13:37:41.446 [planetinfo.record]: techlevel = 8; 13:37:41.446 [planetinfo.record]: population = 37; 13:37:41.446 [planetinfo.record]: productivity = 14208; 13:37:41.446 [planetinfo.record]: name = "Raceed"; 13:37:41.446 [planetinfo.record]: inhabitant = "Human Colonial"; 13:37:41.446 [planetinfo.record]: inhabitants = "Human Colonials"; 13:37:41.446 [planetinfo.record]: description = "The planet Raceed is very famous for its fabulous goat burgers but plagued by unpredictable civil war."; 13:37:41.479 [planetinfo.record]: air_color = 0.698517, 0.80063, 0.935262, 1; 13:37:41.480 [planetinfo.record]: cloud_alpha = 1.000000; 13:37:41.480 [planetinfo.record]: cloud_color = 0.679092, 0.808594, 0.772171, 1; 13:37:41.480 [planetinfo.record]: cloud_fraction = 0.260000; 13:37:41.480 [planetinfo.record]: land_color = 0.66, 0.139883, 0.100547, 1; 13:37:41.480 [planetinfo.record]: land_fraction = 0.590000; 13:37:41.480 [planetinfo.record]: polar_cloud_color = 0.777396, 0.863867, 0.839547, 1; 13:37:41.481 [planetinfo.record]: polar_land_color = 0.934, 0.749989, 0.736072, 1; 13:37:41.481 [planetinfo.record]: polar_sea_color = 0.949414, 0.891374, 0.938985, 1; 13:37:41.481 [planetinfo.record]: sea_color = 0.505859, 0.382161, 0.483632, 1; 13:37:41.481 [planetinfo.record]: rotation_speed = 0.000477 13:37:41.481 [planetinfo.record]: planet zpos = 590100.000000 13:37:41.481 [planetinfo.record]: sun_radius = 90132.756653 13:37:41.481 [planetinfo.record]: sun_vector = 0.205 -0.939 0.276 13:37:41.481 [planetinfo.record]: sun_distance = 747460 13:37:41.481 [planetinfo.record]: corona_flare = 0.078705 13:37:41.481 [planetinfo.record]: corona_hues = 0.931496 13:37:41.482 [planetinfo.record]: sun_color = 0.664374, 0.576446, 0.396629, 1 13:37:41.482 [planetinfo.record]: corona_shimmer = 0.407360 13:37:41.482 [planetinfo.record]: station_vector = -0.989 0.123 0.079 13:37:41.482 [planetinfo.record]: station = coriolis 13:37:46.382 [PLANETINFO OVER]: Done 13:37:46.383 [PLANETINFO LOGGING]: 5 20 13:37:47.388 [planetinfo.record]: seed = 33 82 218 175 13:37:47.388 [planetinfo.record]: coordinates = 82 216 13:37:47.388 [planetinfo.record]: government = 4; 13:37:47.388 [planetinfo.record]: economy = 0; 13:37:47.388 [planetinfo.record]: techlevel = 11; 13:37:47.388 [planetinfo.record]: population = 49; 13:37:47.388 [planetinfo.record]: productivity = 31360; 13:37:47.388 [planetinfo.record]: name = "Aqutebi"; 13:37:47.388 [planetinfo.record]: inhabitant = "Harmless Horned Humanoid"; 13:37:47.388 [planetinfo.record]: inhabitants = "Harmless Horned Humanoids"; 13:37:47.388 [planetinfo.record]: description = "Aqutebi is mildly famous for its vast dense forests and its great parking meters."; 13:37:47.401 [planetinfo.record]: air_color = 0.63156, 0.560867, 0.876076, 1; 13:37:47.401 [planetinfo.record]: cloud_alpha = 1.000000; 13:37:47.401 [planetinfo.record]: cloud_color = 0.536138, 0.327751, 0.630859, 1; 13:37:47.401 [planetinfo.record]: cloud_fraction = 0.540000; 13:37:47.401 [planetinfo.record]: land_color = 0.422813, 0.66, 0.185625, 1; 13:37:47.401 [planetinfo.record]: land_fraction = 0.260000; 13:37:47.401 [planetinfo.record]: polar_cloud_color = 0.710326, 0.548491, 0.783887, 1; 13:37:47.401 [planetinfo.record]: polar_land_color = 0.850086, 0.934, 0.766172, 1; 13:37:47.401 [planetinfo.record]: polar_sea_color = 0.9228, 0.936328, 0.885397, 1; 13:37:47.401 [planetinfo.record]: sea_color = 0.59992, 0.636719, 0.498183, 1; 13:37:47.402 [planetinfo.record]: rotation_speed = 0.001928 13:37:47.402 [planetinfo.record]: planet zpos = 808560.000000 13:37:47.402 [planetinfo.record]: sun_radius = 145571.890869 13:37:47.402 [planetinfo.record]: sun_vector = 0.108 -0.976 -0.188 13:37:47.402 [planetinfo.record]: sun_distance = 1145460 13:37:47.402 [planetinfo.record]: corona_flare = 0.034265 13:37:47.402 [planetinfo.record]: corona_hues = 0.909065 13:37:47.402 [planetinfo.record]: sun_color = 0.66207, 0.653558, 0.646196, 1 13:37:47.402 [planetinfo.record]: corona_shimmer = 0.527258 13:37:47.402 [planetinfo.record]: station_vector = -0.056 -0.912 0.406 13:37:47.402 [planetinfo.record]: station = dodecahedron 13:37:52.442 [PLANETINFO OVER]: Done 13:37:52.443 [PLANETINFO LOGGING]: 5 21 13:37:53.447 [planetinfo.record]: seed = 189 102 160 29 13:37:53.447 [planetinfo.record]: coordinates = 102 220 13:37:53.447 [planetinfo.record]: government = 7; 13:37:53.447 [planetinfo.record]: economy = 4; 13:37:53.447 [planetinfo.record]: techlevel = 9; 13:37:53.447 [planetinfo.record]: population = 48; 13:37:53.447 [planetinfo.record]: productivity = 25344; 13:37:53.447 [planetinfo.record]: name = "Iscege"; 13:37:53.448 [planetinfo.record]: inhabitant = "Green Horned Lobster"; 13:37:53.448 [planetinfo.record]: inhabitants = "Green Horned Lobsters"; 13:37:53.448 [planetinfo.record]: description = "The world Iscege is a dull world."; 13:37:53.481 [planetinfo.record]: air_color = 0.622649, 0.878027, 0.79205, 1; 13:37:53.481 [planetinfo.record]: cloud_alpha = 1.000000; 13:37:53.481 [planetinfo.record]: cloud_color = 0.636719, 0.462616, 0.462616, 1; 13:37:53.481 [planetinfo.record]: cloud_fraction = 0.450000; 13:37:53.481 [planetinfo.record]: land_color = 0.626444, 0.66, 0.572344, 1; 13:37:53.481 [planetinfo.record]: land_fraction = 0.600000; 13:37:53.481 [planetinfo.record]: polar_cloud_color = 0.786523, 0.652108, 0.652108, 1; 13:37:53.482 [planetinfo.record]: polar_land_color = 0.922128, 0.934, 0.902988, 1; 13:37:53.482 [planetinfo.record]: polar_sea_color = 0.731943, 0.932227, 0.927532, 1; 13:37:53.482 [planetinfo.record]: sea_color = 0.0953064, 0.677734, 0.664084, 1; 13:37:53.482 [planetinfo.record]: rotation_speed = 0.001161 13:37:53.482 [planetinfo.record]: planet zpos = 624600.000000 13:37:53.482 [planetinfo.record]: sun_radius = 196537.038574 13:37:53.482 [planetinfo.record]: sun_vector = -0.354 -0.132 -0.926 13:37:53.482 [planetinfo.record]: sun_distance = 1186740 13:37:53.483 [planetinfo.record]: corona_flare = 0.021313 13:37:53.483 [planetinfo.record]: corona_hues = 0.781898 13:37:53.483 [planetinfo.record]: sun_color = 0.67695, 0.618376, 0.517376, 1 13:37:53.483 [planetinfo.record]: corona_shimmer = 0.586441 13:37:53.483 [planetinfo.record]: station_vector = -0.908 0.394 0.145 13:37:53.483 [planetinfo.record]: station = coriolis 13:37:58.609 [PLANETINFO OVER]: Done 13:37:58.610 [PLANETINFO LOGGING]: 5 22 13:37:59.614 [planetinfo.record]: seed = 149 99 138 169 13:37:59.614 [planetinfo.record]: coordinates = 99 229 13:37:59.614 [planetinfo.record]: government = 2; 13:37:59.614 [planetinfo.record]: economy = 5; 13:37:59.614 [planetinfo.record]: techlevel = 6; 13:37:59.614 [planetinfo.record]: population = 32; 13:37:59.614 [planetinfo.record]: productivity = 7680; 13:37:59.614 [planetinfo.record]: name = "Esenon"; 13:37:59.614 [planetinfo.record]: inhabitant = "Small Harmless Insect"; 13:37:59.614 [planetinfo.record]: inhabitants = "Small Harmless Insects"; 13:37:59.614 [planetinfo.record]: description = "Esenon is ravaged by occasional solar activity."; 13:37:59.628 [planetinfo.record]: air_color = 0.596717, 0.919002, 0.850745, 1; 13:37:59.628 [planetinfo.record]: cloud_alpha = 1.000000; 13:37:59.628 [planetinfo.record]: cloud_color = 0.759766, 0.544598, 0.415497, 1; 13:37:59.628 [planetinfo.record]: cloud_fraction = 0.500000; 13:37:59.628 [planetinfo.record]: land_color = 0.66, 0.100547, 0.218557, 1; 13:37:59.628 [planetinfo.record]: land_fraction = 0.360000; 13:37:59.628 [planetinfo.record]: polar_cloud_color = 0.841895, 0.692878, 0.603467, 1; 13:37:59.628 [planetinfo.record]: polar_land_color = 0.934, 0.736072, 0.777823, 1; 13:37:59.628 [planetinfo.record]: polar_sea_color = 0.941797, 0.886395, 0.88367, 1; 13:37:59.628 [planetinfo.record]: sea_color = 0.582031, 0.445078, 0.438342, 1; 13:37:59.628 [planetinfo.record]: rotation_speed = 0.002084 13:37:59.628 [planetinfo.record]: planet zpos = 678470.000000 13:37:59.628 [planetinfo.record]: sun_radius = 134503.766022 13:37:59.628 [planetinfo.record]: sun_vector = 0.126 -0.987 0.098 13:37:59.628 [planetinfo.record]: sun_distance = 835040 13:37:59.628 [planetinfo.record]: corona_flare = 0.007433 13:37:59.628 [planetinfo.record]: corona_hues = 0.599937 13:37:59.628 [planetinfo.record]: sun_color = 0.267066, 0.759061, 0.759521, 1 13:37:59.628 [planetinfo.record]: corona_shimmer = 1.029922 13:37:59.629 [planetinfo.record]: station_vector = -0.133 0.248 0.959 13:37:59.629 [planetinfo.record]: station = coriolis 13:38:04.774 [PLANETINFO OVER]: Done 13:38:04.774 [PLANETINFO LOGGING]: 5 23 13:38:05.782 [planetinfo.record]: seed = 57 155 232 140 13:38:05.782 [planetinfo.record]: coordinates = 155 255 13:38:05.782 [planetinfo.record]: government = 7; 13:38:05.782 [planetinfo.record]: economy = 7; 13:38:05.782 [planetinfo.record]: techlevel = 7; 13:38:05.782 [planetinfo.record]: population = 43; 13:38:05.782 [planetinfo.record]: productivity = 11352; 13:38:05.782 [planetinfo.record]: name = "Insoaza"; 13:38:05.782 [planetinfo.record]: inhabitant = "Black Fat Bird"; 13:38:05.782 [planetinfo.record]: inhabitants = "Black Fat Birds"; 13:38:05.782 [planetinfo.record]: description = "Insoaza is famous for Insoazaian wolf cutlet but beset by evil disease."; 13:38:05.790 [planetinfo.record]: air_color = 0.628745, 0.501601, 0.933311, 1; 13:38:05.791 [planetinfo.record]: cloud_alpha = 1.000000; 13:38:05.791 [planetinfo.record]: cloud_color = 0.738404, 0.169327, 0.802734, 1; 13:38:05.791 [planetinfo.record]: cloud_fraction = 0.200000; 13:38:05.791 [planetinfo.record]: land_color = 0.232605, 0.464751, 0.578125, 1; 13:38:05.791 [planetinfo.record]: land_fraction = 0.640000; 13:38:05.791 [planetinfo.record]: polar_cloud_color = 0.818094, 0.436503, 0.86123, 1; 13:38:05.791 [planetinfo.record]: polar_land_color = 0.801411, 0.895995, 0.942187, 1; 13:38:05.791 [planetinfo.record]: polar_sea_color = 0.934961, 0.908277, 0.873969, 1; 13:38:05.791 [planetinfo.record]: sea_color = 0.650391, 0.576142, 0.480679, 1; 13:38:05.791 [planetinfo.record]: rotation_speed = 0.004502 13:38:05.791 [planetinfo.record]: planet zpos = 906450.000000 13:38:05.791 [planetinfo.record]: sun_radius = 161449.419098 13:38:05.792 [planetinfo.record]: sun_vector = -0.599 0.643 -0.477 13:38:05.792 [planetinfo.record]: sun_distance = 1269030 13:38:05.792 [planetinfo.record]: corona_flare = 0.003203 13:38:05.792 [planetinfo.record]: corona_hues = 0.958221 13:38:05.792 [planetinfo.record]: sun_color = 0.599889, 0.792431, 0.819543, 1 13:38:05.792 [planetinfo.record]: corona_shimmer = 0.279114 13:38:05.792 [planetinfo.record]: station_vector = 0.980 -0.126 0.153 13:38:05.792 [planetinfo.record]: station = coriolis 13:38:10.068 [PLANETINFO OVER]: Done 13:38:10.069 [PLANETINFO LOGGING]: 5 24 13:38:11.072 [planetinfo.record]: seed = 25 4 74 123 13:38:11.072 [planetinfo.record]: coordinates = 4 79 13:38:11.072 [planetinfo.record]: government = 3; 13:38:11.072 [planetinfo.record]: economy = 7; 13:38:11.072 [planetinfo.record]: techlevel = 2; 13:38:11.072 [planetinfo.record]: population = 19; 13:38:11.072 [planetinfo.record]: productivity = 3192; 13:38:11.072 [planetinfo.record]: name = "Anare"; 13:38:11.072 [planetinfo.record]: inhabitant = "Human Colonial"; 13:38:11.072 [planetinfo.record]: inhabitants = "Human Colonials"; 13:38:11.072 [planetinfo.record]: description = "The world Anare is beset by evil disease."; 13:38:11.092 [planetinfo.record]: air_color = 0.686996, 0.749941, 0.972334, 1; 13:38:11.092 [planetinfo.record]: cloud_alpha = 1.000000; 13:38:11.092 [planetinfo.record]: cloud_color = 0.661194, 0.828963, 0.919922, 1; 13:38:11.092 [planetinfo.record]: cloud_fraction = 0.420000; 13:38:11.092 [planetinfo.record]: land_color = 0.582656, 0.66, 0.60743, 1; 13:38:11.092 [planetinfo.record]: land_fraction = 0.550000; 13:38:11.092 [planetinfo.record]: polar_cloud_color = 0.753307, 0.857484, 0.913965, 1; 13:38:11.092 [planetinfo.record]: polar_land_color = 0.906637, 0.934, 0.915402, 1; 13:38:11.092 [planetinfo.record]: polar_sea_color = 0.864766, 0.947461, 0.755008, 1; 13:38:11.092 [planetinfo.record]: sea_color = 0.341966, 0.525391, 0.0985107, 1; 13:38:11.092 [planetinfo.record]: rotation_speed = 0.004705 13:38:11.092 [planetinfo.record]: planet zpos = 563600.000000 13:38:11.092 [planetinfo.record]: sun_radius = 142538.272095 13:38:11.092 [planetinfo.record]: sun_vector = 0.071 0.345 -0.936 13:38:11.092 [planetinfo.record]: sun_distance = 1239920 13:38:11.092 [planetinfo.record]: corona_flare = 0.016977 13:38:11.092 [planetinfo.record]: corona_hues = 0.593781 13:38:11.092 [planetinfo.record]: sun_color = 0.280511, 0.50707, 0.725418, 1 13:38:11.092 [planetinfo.record]: corona_shimmer = 0.408798 13:38:11.093 [planetinfo.record]: station_vector = 0.955 -0.226 0.193 13:38:11.093 [planetinfo.record]: station = coriolis 13:38:15.382 [PLANETINFO OVER]: Done 13:38:15.382 [PLANETINFO LOGGING]: 5 25 13:38:16.402 [planetinfo.record]: seed = 197 152 64 182 13:38:16.402 [planetinfo.record]: coordinates = 152 78 13:38:16.403 [planetinfo.record]: government = 0; 13:38:16.403 [planetinfo.record]: economy = 6; 13:38:16.403 [planetinfo.record]: techlevel = 1; 13:38:16.403 [planetinfo.record]: population = 11; 13:38:16.403 [planetinfo.record]: productivity = 1408; 13:38:16.403 [planetinfo.record]: name = "Veisin"; 13:38:16.403 [planetinfo.record]: inhabitant = "Human Colonial"; 13:38:16.403 [planetinfo.record]: inhabitants = "Human Colonials"; 13:38:16.403 [planetinfo.record]: description = "The planet Veisin is a boring planet."; 13:38:16.405 [planetinfo.record]: air_color = 0.620654, 0.889809, 0.94827, 1; 13:38:16.405 [planetinfo.record]: cloud_alpha = 1.000000; 13:38:16.405 [planetinfo.record]: cloud_color = 0.644848, 0.847656, 0.476807, 1; 13:38:16.405 [planetinfo.record]: cloud_fraction = 0.410000; 13:38:16.406 [planetinfo.record]: land_color = 0.495, 0.66, 0.555586, 1; 13:38:16.406 [planetinfo.record]: land_fraction = 0.540000; 13:38:16.406 [planetinfo.record]: polar_cloud_color = 0.749637, 0.881445, 0.640425, 1; 13:38:16.406 [planetinfo.record]: polar_land_color = 0.875625, 0.934, 0.89706, 1; 13:38:16.406 [planetinfo.record]: polar_sea_color = 0.942069, 0.885733, 0.948438, 1; 13:38:16.406 [planetinfo.record]: sea_color = 0.501776, 0.379266, 0.515625, 1; 13:38:16.406 [planetinfo.record]: rotation_speed = 0.000301 13:38:16.406 [planetinfo.record]: planet zpos = 630560.000000 13:38:16.406 [planetinfo.record]: sun_radius = 122611.256104 13:38:16.406 [planetinfo.record]: sun_vector = 0.783 -0.092 0.615 13:38:16.406 [planetinfo.record]: sun_distance = 810720 13:38:16.406 [planetinfo.record]: corona_flare = 0.058557 13:38:16.406 [planetinfo.record]: corona_hues = 0.516792 13:38:16.406 [planetinfo.record]: sun_color = 0.82305, 0.621392, 0.61276, 1 13:38:16.406 [planetinfo.record]: corona_shimmer = 1.039034 13:38:16.406 [planetinfo.record]: station_vector = -0.308 -0.900 0.309 13:38:16.406 [planetinfo.record]: station = coriolis 13:38:20.878 [PLANETINFO OVER]: Done 13:38:20.879 [PLANETINFO LOGGING]: 5 26 13:38:21.885 [planetinfo.record]: seed = 45 64 26 202 13:38:21.885 [planetinfo.record]: coordinates = 64 236 13:38:21.885 [planetinfo.record]: government = 5; 13:38:21.885 [planetinfo.record]: economy = 4; 13:38:21.885 [planetinfo.record]: techlevel = 6; 13:38:21.885 [planetinfo.record]: population = 34; 13:38:21.885 [planetinfo.record]: productivity = 14688; 13:38:21.885 [planetinfo.record]: name = "Arvelele"; 13:38:21.885 [planetinfo.record]: inhabitant = "Human Colonial"; 13:38:21.885 [planetinfo.record]: inhabitants = "Human Colonials"; 13:38:21.885 [planetinfo.record]: description = "Arvelele is mildly famous for its unusual casinos and its vast oceans."; 13:38:21.896 [planetinfo.record]: air_color = 0.546587, 0.976172, 0.985992, 1; 13:38:21.896 [planetinfo.record]: cloud_alpha = 1.000000; 13:38:21.896 [planetinfo.record]: cloud_color = 0.905512, 0.960938, 0.251495, 1; 13:38:21.896 [planetinfo.record]: cloud_fraction = 0.580000; 13:38:21.896 [planetinfo.record]: land_color = 0.404167, 0.357056, 0.507812, 1; 13:38:21.897 [planetinfo.record]: land_fraction = 0.200000; 13:38:21.897 [planetinfo.record]: polar_cloud_color = 0.898809, 0.932422, 0.502178, 1; 13:38:21.897 [planetinfo.record]: polar_land_color = 0.900784, 0.878769, 0.949219, 1; 13:38:21.897 [planetinfo.record]: polar_sea_color = 0.852079, 0.917969, 0.842667, 1; 13:38:21.897 [planetinfo.record]: sea_color = 0.584793, 0.820312, 0.551147, 1; 13:38:21.897 [planetinfo.record]: rotation_speed = 0.004906 13:38:21.897 [planetinfo.record]: planet zpos = 598400.000000 13:38:21.897 [planetinfo.record]: sun_radius = 121392.285156 13:38:21.897 [planetinfo.record]: sun_vector = -0.704 0.059 0.708 13:38:21.897 [planetinfo.record]: sun_distance = 1142400 13:38:21.897 [planetinfo.record]: corona_flare = 0.096190 13:38:21.897 [planetinfo.record]: corona_hues = 0.628029 13:38:21.898 [planetinfo.record]: sun_color = 0.245824, 0.306326, 0.836847, 1 13:38:21.898 [planetinfo.record]: corona_shimmer = 0.353744 13:38:21.898 [planetinfo.record]: station_vector = -0.944 -0.216 0.250 13:38:21.898 [planetinfo.record]: station = coriolis 13:38:26.228 [PLANETINFO OVER]: Done 13:38:26.230 [PLANETINFO LOGGING]: 5 27 13:38:27.249 [planetinfo.record]: seed = 225 193 168 185 13:38:27.249 [planetinfo.record]: coordinates = 193 1 13:38:27.249 [planetinfo.record]: government = 4; 13:38:27.250 [planetinfo.record]: economy = 1; 13:38:27.250 [planetinfo.record]: techlevel = 9; 13:38:27.250 [planetinfo.record]: population = 42; 13:38:27.250 [planetinfo.record]: productivity = 24192; 13:38:27.250 [planetinfo.record]: name = "Orteed"; 13:38:27.250 [planetinfo.record]: inhabitant = "Harmless Slimy Frog"; 13:38:27.250 [planetinfo.record]: inhabitants = "Harmless Slimy Frogs"; 13:38:27.250 [planetinfo.record]: description = "The world Orteed is reasonably noted for mud tennis and the Orteedian mountain Seoid."; 13:38:27.256 [planetinfo.record]: air_color = 0.463614, 0.610377, 0.888434, 1; 13:38:27.256 [planetinfo.record]: cloud_alpha = 1.000000; 13:38:27.256 [planetinfo.record]: cloud_color = 0.109589, 0.667969, 0.654882, 1; 13:38:27.256 [planetinfo.record]: cloud_fraction = 0.240000; 13:38:27.256 [planetinfo.record]: land_color = 0.66, 0.226875, 0.318237, 1; 13:38:27.256 [planetinfo.record]: land_fraction = 0.510000; 13:38:27.256 [planetinfo.record]: polar_cloud_color = 0.382311, 0.800586, 0.790783, 1; 13:38:27.257 [planetinfo.record]: polar_land_color = 0.934, 0.780766, 0.813089, 1; 13:38:27.257 [planetinfo.record]: polar_sea_color = 0.941016, 0.940263, 0.892862, 1; 13:38:27.257 [planetinfo.record]: sea_color = 0.589844, 0.587957, 0.46911, 1; 13:38:27.257 [planetinfo.record]: rotation_speed = 0.003590 13:38:27.257 [planetinfo.record]: planet zpos = 796950.000000 13:38:27.257 [planetinfo.record]: sun_radius = 90839.685516 13:38:27.257 [planetinfo.record]: sun_vector = -0.761 -0.649 0.017 13:38:27.257 [planetinfo.record]: sun_distance = 956340 13:38:27.257 [planetinfo.record]: corona_flare = 0.035382 13:38:27.257 [planetinfo.record]: corona_hues = 0.906868 13:38:27.257 [planetinfo.record]: sun_color = 0.849887, 0.813243, 0.802735, 1 13:38:27.257 [planetinfo.record]: corona_shimmer = 0.595252 13:38:27.258 [planetinfo.record]: station_vector = 0.681 -0.503 0.533 13:38:27.258 [planetinfo.record]: station = coriolis 13:38:31.848 [PLANETINFO OVER]: Done 13:38:31.849 [PLANETINFO LOGGING]: 5 28 13:38:32.853 [planetinfo.record]: seed = 81 46 250 162 13:38:32.853 [planetinfo.record]: coordinates = 46 248 13:38:32.853 [planetinfo.record]: government = 2; 13:38:32.853 [planetinfo.record]: economy = 0; 13:38:32.853 [planetinfo.record]: techlevel = 10; 13:38:32.853 [planetinfo.record]: population = 43; 13:38:32.853 [planetinfo.record]: productivity = 20640; 13:38:32.853 [planetinfo.record]: name = "Xeesqu"; 13:38:32.853 [planetinfo.record]: inhabitant = "Large Harmless Rodent"; 13:38:32.853 [planetinfo.record]: inhabitants = "Large Harmless Rodents"; 13:38:32.853 [planetinfo.record]: description = "This world is a revolting dump."; 13:38:32.888 [planetinfo.record]: air_color = 0.545854, 0.577077, 0.847459, 1; 13:38:32.888 [planetinfo.record]: cloud_alpha = 1.000000; 13:38:32.888 [planetinfo.record]: cloud_color = 0.28949, 0.365321, 0.544922, 1; 13:38:32.888 [planetinfo.record]: cloud_fraction = 0.380000; 13:38:32.888 [planetinfo.record]: land_color = 0.66, 0.250078, 0.432621, 1; 13:38:32.888 [planetinfo.record]: land_fraction = 0.410000; 13:38:32.888 [planetinfo.record]: polar_cloud_color = 0.52689, 0.591705, 0.745215, 1; 13:38:32.888 [planetinfo.record]: polar_land_color = 0.934, 0.788975, 0.853556, 1; 13:38:32.888 [planetinfo.record]: polar_sea_color = 0.935938, 0.925755, 0.879269, 1; 13:38:32.888 [planetinfo.record]: sea_color = 0.640625, 0.612746, 0.485474, 1; 13:38:32.888 [planetinfo.record]: rotation_speed = 0.002542 13:38:32.888 [planetinfo.record]: planet zpos = 371140.000000 13:38:32.888 [planetinfo.record]: sun_radius = 84516.290588 13:38:32.888 [planetinfo.record]: sun_vector = 0.769 -0.355 -0.532 13:38:32.888 [planetinfo.record]: sun_distance = 539840 13:38:32.888 [planetinfo.record]: corona_flare = 0.085939 13:38:32.888 [planetinfo.record]: corona_hues = 0.644630 13:38:32.888 [planetinfo.record]: sun_color = 0.22044, 0.384887, 0.686365, 1 13:38:32.888 [planetinfo.record]: corona_shimmer = 0.348055 13:38:32.889 [planetinfo.record]: station_vector = -0.353 0.151 0.923 13:38:32.889 [planetinfo.record]: station = coriolis 13:38:37.346 [PLANETINFO OVER]: Done 13:38:37.347 [PLANETINFO LOGGING]: 5 29 13:38:38.350 [planetinfo.record]: seed = 13 7 32 107 13:38:38.350 [planetinfo.record]: coordinates = 7 154 13:38:38.350 [planetinfo.record]: government = 1; 13:38:38.350 [planetinfo.record]: economy = 2; 13:38:38.350 [planetinfo.record]: techlevel = 9; 13:38:38.350 [planetinfo.record]: population = 40; 13:38:38.350 [planetinfo.record]: productivity = 12800; 13:38:38.350 [planetinfo.record]: name = "Mainon"; 13:38:38.351 [planetinfo.record]: inhabitant = "Human Colonial"; 13:38:38.351 [planetinfo.record]: inhabitants = "Human Colonials"; 13:38:38.351 [planetinfo.record]: description = "The world Mainon is scourged by deadly tree snakes."; 13:38:38.353 [planetinfo.record]: air_color = 0.624604, 0.83661, 0.971684, 1; 13:38:38.353 [planetinfo.record]: cloud_alpha = 1.000000; 13:38:38.353 [planetinfo.record]: cloud_color = 0.484085, 0.917969, 0.562049, 1; 13:38:38.353 [planetinfo.record]: cloud_fraction = 0.390000; 13:38:38.354 [planetinfo.record]: land_color = 0.332578, 0.66, 0.491173, 1; 13:38:38.354 [planetinfo.record]: land_fraction = 0.420000; 13:38:38.354 [planetinfo.record]: polar_cloud_color = 0.643351, 0.913086, 0.691819, 1; 13:38:38.354 [planetinfo.record]: polar_land_color = 0.818162, 0.934, 0.874271, 1; 13:38:38.354 [planetinfo.record]: polar_sea_color = 0.935742, 0.928254, 0.87936, 1; 13:38:38.354 [planetinfo.record]: sea_color = 0.642578, 0.622009, 0.487707, 1; 13:38:38.354 [planetinfo.record]: rotation_speed = 0.004307 13:38:38.354 [planetinfo.record]: planet zpos = 676680.000000 13:38:38.354 [planetinfo.record]: sun_radius = 131719.213409 13:38:38.355 [planetinfo.record]: sun_vector = -0.500 -0.860 0.099 13:38:38.355 [planetinfo.record]: sun_distance = 958630 13:38:38.355 [planetinfo.record]: corona_flare = 0.014818 13:38:38.355 [planetinfo.record]: corona_hues = 0.840225 13:38:38.355 [planetinfo.record]: sun_color = 0.819699, 0.509257, 0.426328, 1 13:38:38.355 [planetinfo.record]: corona_shimmer = 0.271591 13:38:38.355 [planetinfo.record]: station_vector = -0.658 0.049 0.752 13:38:38.356 [planetinfo.record]: station = coriolis 13:38:43.556 [PLANETINFO OVER]: Done 13:38:43.556 [PLANETINFO LOGGING]: 5 30 13:38:44.560 [planetinfo.record]: seed = 5 247 234 130 13:38:44.560 [planetinfo.record]: coordinates = 247 127 13:38:44.560 [planetinfo.record]: government = 0; 13:38:44.560 [planetinfo.record]: economy = 7; 13:38:44.560 [planetinfo.record]: techlevel = 3; 13:38:44.560 [planetinfo.record]: population = 20; 13:38:44.560 [planetinfo.record]: productivity = 1920; 13:38:44.560 [planetinfo.record]: name = "Xeeden"; 13:38:44.560 [planetinfo.record]: inhabitant = "Large Black Slimy Lizard"; 13:38:44.560 [planetinfo.record]: inhabitants = "Large Black Slimy Lizards"; 13:38:44.560 [planetinfo.record]: description = "The world Xeeden is scourged by deadly tree grubs."; 13:38:44.576 [planetinfo.record]: air_color = 0.738056, 0.724696, 0.978838, 1; 13:38:44.576 [planetinfo.record]: cloud_alpha = 1.000000; 13:38:44.576 [planetinfo.record]: cloud_color = 0.799659, 0.770645, 0.939453, 1; 13:38:44.576 [planetinfo.record]: cloud_fraction = 0.480000; 13:38:44.576 [planetinfo.record]: land_color = 0.411767, 0.328598, 0.576172, 1; 13:38:44.576 [planetinfo.record]: land_fraction = 0.490000; 13:38:44.576 [planetinfo.record]: polar_cloud_color = 0.836936, 0.819124, 0.922754, 1; 13:38:44.576 [planetinfo.record]: polar_land_color = 0.875158, 0.84115, 0.942383, 1; 13:38:44.576 [planetinfo.record]: polar_sea_color = 0.926953, 0.871953, 0.835525, 1; 13:38:44.576 [planetinfo.record]: sea_color = 0.730469, 0.557103, 0.442276, 1; 13:38:44.576 [planetinfo.record]: rotation_speed = 0.002714 13:38:44.576 [planetinfo.record]: planet zpos = 536250.000000 13:38:44.576 [planetinfo.record]: sun_radius = 92040.321350 13:38:44.576 [planetinfo.record]: sun_vector = 0.561 0.804 0.199 13:38:44.576 [planetinfo.record]: sun_distance = 679250 13:38:44.576 [planetinfo.record]: corona_flare = 0.088864 13:38:44.576 [planetinfo.record]: corona_hues = 0.538620 13:38:44.576 [planetinfo.record]: sun_color = 0.714645, 0.727336, 0.740729, 1 13:38:44.576 [planetinfo.record]: corona_shimmer = 0.354878 13:38:44.577 [planetinfo.record]: station_vector = -0.074 -0.117 0.990 13:38:44.577 [planetinfo.record]: station = coriolis 13:38:49.664 [PLANETINFO OVER]: Done 13:38:49.664 [PLANETINFO LOGGING]: 5 31 13:38:50.668 [planetinfo.record]: seed = 201 238 168 90 13:38:50.668 [planetinfo.record]: coordinates = 238 114 13:38:50.668 [planetinfo.record]: government = 1; 13:38:50.668 [planetinfo.record]: economy = 2; 13:38:50.668 [planetinfo.record]: techlevel = 8; 13:38:50.668 [planetinfo.record]: population = 36; 13:38:50.668 [planetinfo.record]: productivity = 11520; 13:38:50.668 [planetinfo.record]: name = "Qutecete"; 13:38:50.668 [planetinfo.record]: inhabitant = "Yellow Fat Feline"; 13:38:50.668 [planetinfo.record]: inhabitants = "Yellow Fat Felines"; 13:38:50.668 [planetinfo.record]: description = "The planet Qutecete is most famous for the Quteceteian spotted shrew and the Quteceteian edible poet."; 13:38:50.676 [planetinfo.record]: air_color = 0.689126, 0.839761, 0.905344, 1; 13:38:50.676 [planetinfo.record]: cloud_alpha = 1.000000; 13:38:50.676 [planetinfo.record]: cloud_color = 0.641102, 0.71875, 0.634521, 1; 13:38:50.676 [planetinfo.record]: cloud_fraction = 0.210000; 13:38:50.676 [planetinfo.record]: land_color = 0.625256, 0.66, 0.482109, 1; 13:38:50.676 [planetinfo.record]: land_fraction = 0.490000; 13:38:50.677 [planetinfo.record]: polar_cloud_color = 0.767839, 0.823438, 0.763127, 1; 13:38:50.677 [planetinfo.record]: polar_land_color = 0.921708, 0.934, 0.871064, 1; 13:38:50.677 [planetinfo.record]: polar_sea_color = 0.921617, 0.931641, 0.873322, 1; 13:38:50.677 [planetinfo.record]: sea_color = 0.654175, 0.683594, 0.512428, 1; 13:38:50.677 [planetinfo.record]: rotation_speed = 0.002616 13:38:50.677 [planetinfo.record]: planet zpos = 785960.000000 13:38:50.677 [planetinfo.record]: sun_radius = 89540.798645 13:38:50.677 [planetinfo.record]: sun_vector = -0.639 -0.618 0.458 13:38:50.677 [planetinfo.record]: sun_distance = 954380 13:38:50.677 [planetinfo.record]: corona_flare = 0.089856 13:38:50.677 [planetinfo.record]: corona_hues = 0.899887 13:38:50.677 [planetinfo.record]: sun_color = 0.297949, 0.360631, 0.694061, 1 13:38:50.677 [planetinfo.record]: corona_shimmer = 0.515204 13:38:50.677 [planetinfo.record]: station_vector = 0.861 -0.188 0.473 13:38:50.678 [planetinfo.record]: station = coriolis 13:38:55.304 [PLANETINFO OVER]: Done 13:38:55.305 [PLANETINFO LOGGING]: 5 32 13:38:56.324 [planetinfo.record]: seed = 201 28 234 222 13:38:56.324 [planetinfo.record]: coordinates = 28 5 13:38:56.324 [planetinfo.record]: government = 1; 13:38:56.324 [planetinfo.record]: economy = 7; 13:38:56.324 [planetinfo.record]: techlevel = 1; 13:38:56.324 [planetinfo.record]: population = 13; 13:38:56.324 [planetinfo.record]: productivity = 1560; 13:38:56.324 [planetinfo.record]: name = "Rileisis"; 13:38:56.324 [planetinfo.record]: inhabitant = "Bug-Eyed Lobster"; 13:38:56.324 [planetinfo.record]: inhabitants = "Bug-Eyed Lobsters"; 13:38:56.324 [planetinfo.record]: description = "This world is very fabled for the Rileisisian edible poet."; 13:38:56.344 [planetinfo.record]: air_color = 0.493906, 0.573179, 0.880629, 1; 13:38:56.344 [planetinfo.record]: cloud_alpha = 1.000000; 13:38:56.344 [planetinfo.record]: cloud_color = 0.181274, 0.459952, 0.644531, 1; 13:38:56.344 [planetinfo.record]: cloud_fraction = 0.460000; 13:38:56.344 [planetinfo.record]: land_color = 0.492422, 0.66, 0.612869, 1; 13:38:56.345 [planetinfo.record]: land_fraction = 0.350000; 13:38:56.345 [planetinfo.record]: polar_cloud_color = 0.435139, 0.648633, 0.790039, 1; 13:38:56.345 [planetinfo.record]: polar_land_color = 0.874713, 0.934, 0.917325, 1; 13:38:56.345 [planetinfo.record]: polar_sea_color = 0.886172, 0.932813, 0.889087, 1; 13:38:56.345 [planetinfo.record]: sea_color = 0.5375, 0.671875, 0.545898, 1; 13:38:56.345 [planetinfo.record]: rotation_speed = 0.000379 13:38:56.345 [planetinfo.record]: planet zpos = 642800.000000 13:38:56.345 [planetinfo.record]: sun_radius = 144861.477051 13:38:56.345 [planetinfo.record]: sun_vector = -0.134 0.955 0.263 13:38:56.345 [planetinfo.record]: sun_distance = 1285600 13:38:56.345 [planetinfo.record]: corona_flare = 0.004646 13:38:56.345 [planetinfo.record]: corona_hues = 0.512505 13:38:56.345 [planetinfo.record]: sun_color = 0.845337, 0.814615, 0.776883, 1 13:38:56.346 [planetinfo.record]: corona_shimmer = 0.348370 13:38:56.346 [planetinfo.record]: station_vector = 0.929 0.337 0.151 13:38:56.346 [planetinfo.record]: station = coriolis 13:39:00.919 [PLANETINFO OVER]: Done 13:39:00.920 [PLANETINFO LOGGING]: 5 33 13:39:01.923 [planetinfo.record]: seed = 149 221 64 220 13:39:01.923 [planetinfo.record]: coordinates = 221 253 13:39:01.923 [planetinfo.record]: government = 2; 13:39:01.923 [planetinfo.record]: economy = 5; 13:39:01.923 [planetinfo.record]: techlevel = 4; 13:39:01.923 [planetinfo.record]: population = 24; 13:39:01.924 [planetinfo.record]: productivity = 5760; 13:39:01.924 [planetinfo.record]: name = "Tetier"; 13:39:01.924 [planetinfo.record]: inhabitant = "Human Colonial"; 13:39:01.924 [planetinfo.record]: inhabitants = "Human Colonials"; 13:39:01.924 [planetinfo.record]: description = "This planet is plagued by occasional solar activity."; 13:39:01.925 [planetinfo.record]: air_color = 0.46094, 0.539067, 0.910547, 1; 13:39:01.925 [planetinfo.record]: cloud_alpha = 1.000000; 13:39:01.925 [planetinfo.record]: cloud_color = 0.0860596, 0.415282, 0.734375, 1; 13:39:01.926 [planetinfo.record]: cloud_fraction = 0.480000; 13:39:01.926 [planetinfo.record]: land_color = 0.607422, 0.476921, 0.476921, 1; 13:39:01.926 [planetinfo.record]: land_fraction = 0.650000; 13:39:01.926 [planetinfo.record]: polar_cloud_color = 0.372251, 0.60494, 0.830469, 1; 13:39:01.926 [planetinfo.record]: polar_land_color = 0.939258, 0.888809, 0.888809, 1; 13:39:01.926 [planetinfo.record]: polar_sea_color = 0.792559, 0.945898, 0.884802, 1; 13:39:01.926 [planetinfo.record]: sea_color = 0.190201, 0.541016, 0.401238, 1; 13:39:01.926 [planetinfo.record]: rotation_speed = 0.003454 13:39:01.926 [planetinfo.record]: planet zpos = 549810.000000 13:39:01.926 [planetinfo.record]: sun_radius = 142873.939209 13:39:01.926 [planetinfo.record]: sun_vector = 0.170 0.103 0.980 13:39:01.926 [planetinfo.record]: sun_distance = 1405070 13:39:01.926 [planetinfo.record]: corona_flare = 0.050723 13:39:01.926 [planetinfo.record]: corona_hues = 0.688637 13:39:01.926 [planetinfo.record]: sun_color = 0.776956, 0.728925, 0.692406, 1 13:39:01.926 [planetinfo.record]: corona_shimmer = 0.361048 13:39:01.926 [planetinfo.record]: station_vector = 0.692 0.420 0.588 13:39:01.926 [planetinfo.record]: station = coriolis 13:39:06.036 [PLANETINFO OVER]: Done 13:39:06.037 [PLANETINFO LOGGING]: 5 34 13:39:07.042 [planetinfo.record]: seed = 29 4 250 43 13:39:07.043 [planetinfo.record]: coordinates = 4 112 13:39:07.043 [planetinfo.record]: government = 3; 13:39:07.043 [planetinfo.record]: economy = 0; 13:39:07.043 [planetinfo.record]: techlevel = 9; 13:39:07.043 [planetinfo.record]: population = 40; 13:39:07.043 [planetinfo.record]: productivity = 22400; 13:39:07.043 [planetinfo.record]: name = "Maatis"; 13:39:07.043 [planetinfo.record]: inhabitant = "Small Red Fat Insect"; 13:39:07.043 [planetinfo.record]: inhabitants = "Small Red Fat Insects"; 13:39:07.043 [planetinfo.record]: description = "Maatis is mildly well known for its hoopy casinos and its vast oceans."; 13:39:07.081 [planetinfo.record]: air_color = 0.642147, 0.847591, 0.950871, 1; 13:39:07.082 [planetinfo.record]: cloud_alpha = 1.000000; 13:39:07.082 [planetinfo.record]: cloud_color = 0.534668, 0.855469, 0.53968, 1; 13:39:07.082 [planetinfo.record]: cloud_fraction = 0.260000; 13:39:07.082 [planetinfo.record]: land_color = 0.66, 0.376406, 0.46946, 1; 13:39:07.082 [planetinfo.record]: land_fraction = 0.200000; 13:39:07.082 [planetinfo.record]: polar_cloud_color = 0.677548, 0.884961, 0.680789, 1; 13:39:07.082 [planetinfo.record]: polar_land_color = 0.934, 0.833668, 0.866589, 1; 13:39:07.082 [planetinfo.record]: polar_sea_color = 0.931386, 0.933203, 0.87506, 1; 13:39:07.082 [planetinfo.record]: sea_color = 0.662767, 0.667969, 0.501498, 1; 13:39:07.083 [planetinfo.record]: rotation_speed = 0.000450 13:39:07.083 [planetinfo.record]: planet zpos = 732680.000000 13:39:07.083 [planetinfo.record]: sun_radius = 166064.891357 13:39:07.083 [planetinfo.record]: sun_vector = -0.494 0.726 0.478 13:39:07.083 [planetinfo.record]: sun_distance = 1239920 13:39:07.083 [planetinfo.record]: corona_flare = 0.032524 13:39:07.083 [planetinfo.record]: corona_hues = 0.793274 13:39:07.083 [planetinfo.record]: sun_color = 0.636128, 0.726996, 0.751886, 1 13:39:07.083 [planetinfo.record]: corona_shimmer = 0.510707 13:39:07.084 [planetinfo.record]: station_vector = -0.153 -0.975 0.160 13:39:07.084 [planetinfo.record]: station = coriolis 13:39:11.860 [PLANETINFO OVER]: Done 13:39:11.861 [PLANETINFO LOGGING]: 5 35 13:39:12.872 [planetinfo.record]: seed = 241 157 232 15 13:39:12.872 [planetinfo.record]: coordinates = 157 209 13:39:12.872 [planetinfo.record]: government = 6; 13:39:12.872 [planetinfo.record]: economy = 1; 13:39:12.872 [planetinfo.record]: techlevel = 10; 13:39:12.872 [planetinfo.record]: population = 48; 13:39:12.872 [planetinfo.record]: productivity = 34560; 13:39:12.872 [planetinfo.record]: name = "Ariin"; 13:39:12.872 [planetinfo.record]: inhabitant = "Green Fat Insect"; 13:39:12.872 [planetinfo.record]: inhabitants = "Green Fat Insects"; 13:39:12.872 [planetinfo.record]: description = "This planet is notable for the Ariinian edible arts graduate and its weird volcanoes."; 13:39:12.874 [planetinfo.record]: air_color = 0.729734, 0.634863, 0.838354, 1; 13:39:12.874 [planetinfo.record]: cloud_alpha = 1.000000; 13:39:12.874 [planetinfo.record]: cloud_color = 0.517578, 0.450859, 0.489952, 1; 13:39:12.874 [planetinfo.record]: cloud_fraction = 0.350000; 13:39:12.874 [planetinfo.record]: land_color = 0.617188, 0.419495, 0.526063, 1; 13:39:12.874 [planetinfo.record]: land_fraction = 0.530000; 13:39:12.874 [planetinfo.record]: polar_cloud_color = 0.73291, 0.673862, 0.708461, 1; 13:39:12.874 [planetinfo.record]: polar_land_color = 0.938281, 0.863145, 0.903648, 1; 13:39:12.874 [planetinfo.record]: polar_sea_color = 0.918555, 0.901035, 0.806426, 1; 13:39:12.874 [planetinfo.record]: sea_color = 0.814453, 0.752315, 0.416771, 1; 13:39:12.874 [planetinfo.record]: rotation_speed = 0.001632 13:39:12.874 [planetinfo.record]: planet zpos = 613170.000000 13:39:12.874 [planetinfo.record]: sun_radius = 156296.889954 13:39:12.874 [planetinfo.record]: sun_vector = 0.944 0.264 0.197 13:39:12.874 [planetinfo.record]: sun_distance = 1294470 13:39:12.874 [planetinfo.record]: corona_flare = 0.003886 13:39:12.874 [planetinfo.record]: corona_hues = 0.644707 13:39:12.874 [planetinfo.record]: sun_color = 0.505916, 0.741924, 0.836642, 1 13:39:12.874 [planetinfo.record]: corona_shimmer = 0.291199 13:39:12.875 [planetinfo.record]: station_vector = 0.153 -0.051 0.987 13:39:12.875 [planetinfo.record]: station = coriolis 13:39:17.949 [PLANETINFO OVER]: Done 13:39:17.950 [PLANETINFO LOGGING]: 5 36 13:39:18.964 [planetinfo.record]: seed = 129 187 26 103 13:39:18.964 [planetinfo.record]: coordinates = 187 44 13:39:18.964 [planetinfo.record]: government = 0; 13:39:18.964 [planetinfo.record]: economy = 6; 13:39:18.964 [planetinfo.record]: techlevel = 4; 13:39:18.964 [planetinfo.record]: population = 23; 13:39:18.964 [planetinfo.record]: productivity = 2944; 13:39:18.964 [planetinfo.record]: name = "Soaat"; 13:39:18.964 [planetinfo.record]: inhabitant = "Human Colonial"; 13:39:18.964 [planetinfo.record]: inhabitants = "Human Colonials"; 13:39:18.964 [planetinfo.record]: description = "Soaat is a revolting dump."; 13:39:18.972 [planetinfo.record]: air_color = 0.901419, 0.784357, 0.996398, 1; 13:39:18.972 [planetinfo.record]: cloud_alpha = 1.000000; 13:39:18.972 [planetinfo.record]: cloud_color = 0.992188, 0.95343, 0.966147, 1; 13:39:18.979 [planetinfo.record]: cloud_fraction = 0.290000; 13:39:18.979 [planetinfo.record]: land_color = 0.223753, 0.198516, 0.66, 1; 13:39:18.979 [planetinfo.record]: land_fraction = 0.580000; 13:39:18.979 [planetinfo.record]: polar_cloud_color = 0.946484, 0.923377, 0.930959, 1; 13:39:18.979 [planetinfo.record]: polar_land_color = 0.779661, 0.770732, 0.934, 1; 13:39:18.980 [planetinfo.record]: polar_sea_color = 0.936328, 0.924813, 0.879636, 1; 13:39:18.980 [planetinfo.record]: sea_color = 0.636719, 0.605396, 0.482513, 1; 13:39:18.980 [planetinfo.record]: rotation_speed = 0.003161 13:39:18.980 [planetinfo.record]: planet zpos = 575400.000000 13:39:18.980 [planetinfo.record]: sun_radius = 133009.741211 13:39:18.980 [planetinfo.record]: sun_vector = 0.012 -0.727 0.687 13:39:18.980 [planetinfo.record]: sun_distance = 911050 13:39:18.980 [planetinfo.record]: corona_flare = 0.018846 13:39:18.980 [planetinfo.record]: corona_hues = 0.908707 13:39:18.980 [planetinfo.record]: sun_color = 0.805197, 0.579498, 0.250589, 1 13:39:18.980 [planetinfo.record]: corona_shimmer = 0.914541 13:39:18.980 [planetinfo.record]: station_vector = -0.861 0.435 0.264 13:39:18.980 [planetinfo.record]: station = coriolis 13:39:24.277 [PLANETINFO OVER]: Done 13:39:24.278 [PLANETINFO LOGGING]: 5 37 13:39:25.299 [planetinfo.record]: seed = 93 40 160 233 13:39:25.299 [planetinfo.record]: coordinates = 40 113 13:39:25.299 [planetinfo.record]: government = 3; 13:39:25.299 [planetinfo.record]: economy = 1; 13:39:25.299 [planetinfo.record]: techlevel = 8; 13:39:25.299 [planetinfo.record]: population = 37; 13:39:25.299 [planetinfo.record]: productivity = 18648; 13:39:25.299 [planetinfo.record]: name = "Esgelage"; 13:39:25.299 [planetinfo.record]: inhabitant = "Small Bug-Eyed Lizard"; 13:39:25.299 [planetinfo.record]: inhabitants = "Small Bug-Eyed Lizards"; 13:39:25.299 [planetinfo.record]: description = "Esgelage is mildly well known for its exotic night life and Esgelageian Oubeouab brandy."; 13:39:25.302 [planetinfo.record]: air_color = 0.512222, 0.49706, 0.907295, 1; 13:39:25.302 [planetinfo.record]: cloud_alpha = 1.000000; 13:39:25.302 [planetinfo.record]: cloud_color = 0.244131, 0.175491, 0.724609, 1; 13:39:25.302 [planetinfo.record]: cloud_fraction = 0.520000; 13:39:25.302 [planetinfo.record]: land_color = 0.484688, 0.655891, 0.66, 1; 13:39:25.303 [planetinfo.record]: land_fraction = 0.420000; 13:39:25.303 [planetinfo.record]: polar_cloud_color = 0.483725, 0.434818, 0.826074, 1; 13:39:25.303 [planetinfo.record]: polar_land_color = 0.871977, 0.932546, 0.934, 1; 13:39:25.303 [planetinfo.record]: polar_sea_color = 0.947852, 0.90943, 0.73403, 1; 13:39:25.303 [planetinfo.record]: sea_color = 0.521484, 0.436931, 0.0509262, 1; 13:39:25.303 [planetinfo.record]: rotation_speed = 0.002495 13:39:25.303 [planetinfo.record]: planet zpos = 619200.000000 13:39:25.303 [planetinfo.record]: sun_radius = 124362.487793 13:39:25.303 [planetinfo.record]: sun_vector = -0.168 -0.584 0.794 13:39:25.303 [planetinfo.record]: sun_distance = 1032000 13:39:25.303 [planetinfo.record]: corona_flare = 0.013043 13:39:25.303 [planetinfo.record]: corona_hues = 0.892662 13:39:25.303 [planetinfo.record]: sun_color = 0.732025, 0.581131, 0.324366, 1 13:39:25.303 [planetinfo.record]: corona_shimmer = 0.293694 13:39:25.304 [planetinfo.record]: station_vector = -0.211 -0.971 0.112 13:39:25.304 [planetinfo.record]: station = coriolis 13:39:29.568 [PLANETINFO OVER]: Done 13:39:29.569 [PLANETINFO LOGGING]: 5 38 13:39:30.589 [planetinfo.record]: seed = 117 3 74 29 13:39:30.589 [planetinfo.record]: coordinates = 3 149 13:39:30.589 [planetinfo.record]: government = 6; 13:39:30.590 [planetinfo.record]: economy = 5; 13:39:30.590 [planetinfo.record]: techlevel = 8; 13:39:30.590 [planetinfo.record]: population = 44; 13:39:30.590 [planetinfo.record]: productivity = 17600; 13:39:30.590 [planetinfo.record]: name = "Isvetima"; 13:39:30.590 [planetinfo.record]: inhabitant = "Human Colonial"; 13:39:30.590 [planetinfo.record]: inhabitants = "Human Colonials"; 13:39:30.590 [planetinfo.record]: description = "Isvetima is mildly well known for vicious Erma brew."; 13:39:30.597 [planetinfo.record]: air_color = 0.761911, 0.533951, 0.941115, 1; 13:39:30.597 [planetinfo.record]: cloud_alpha = 1.000000; 13:39:30.598 [planetinfo.record]: cloud_color = 0.826172, 0.248497, 0.424507, 1; 13:39:30.598 [planetinfo.record]: cloud_fraction = 0.490000; 13:39:30.598 [planetinfo.record]: land_color = 0.0924683, 0.591797, 0.111973, 1; 13:39:30.598 [planetinfo.record]: land_fraction = 0.570000; 13:39:30.598 [planetinfo.record]: polar_cloud_color = 0.871777, 0.4908, 0.606879, 1; 13:39:30.598 [planetinfo.record]: polar_land_color = 0.742366, 0.94082, 0.750118, 1; 13:39:30.598 [planetinfo.record]: polar_sea_color = 0.937695, 0.885872, 0.875701, 1; 13:39:30.598 [planetinfo.record]: sea_color = 0.623047, 0.485312, 0.45828, 1; 13:39:30.599 [planetinfo.record]: rotation_speed = 0.003255 13:39:30.599 [planetinfo.record]: planet zpos = 676170.000000 13:39:30.599 [planetinfo.record]: sun_radius = 176112.825623 13:39:30.599 [planetinfo.record]: sun_vector = 0.458 0.049 -0.888 13:39:30.599 [planetinfo.record]: sun_distance = 1352340 13:39:30.599 [planetinfo.record]: corona_flare = 0.090549 13:39:30.599 [planetinfo.record]: corona_hues = 0.517906 13:39:30.599 [planetinfo.record]: sun_color = 0.656491, 0.653805, 0.59937, 1 13:39:30.599 [planetinfo.record]: corona_shimmer = 0.464287 13:39:30.600 [planetinfo.record]: station_vector = -0.741 0.185 0.645 13:39:30.600 [planetinfo.record]: station = coriolis 13:39:34.944 [PLANETINFO OVER]: Done 13:39:34.945 [PLANETINFO LOGGING]: 5 39 13:39:35.949 [planetinfo.record]: seed = 89 171 104 57 13:39:35.949 [planetinfo.record]: coordinates = 171 215 13:39:35.949 [planetinfo.record]: government = 3; 13:39:35.949 [planetinfo.record]: economy = 7; 13:39:35.949 [planetinfo.record]: techlevel = 5; 13:39:35.949 [planetinfo.record]: population = 31; 13:39:35.949 [planetinfo.record]: productivity = 5208; 13:39:35.949 [planetinfo.record]: name = "Orteve"; 13:39:35.949 [planetinfo.record]: inhabitant = "Human Colonial"; 13:39:35.949 [planetinfo.record]: inhabitants = "Human Colonials"; 13:39:35.949 [planetinfo.record]: description = "This world is ravaged by unpredictable civil war."; 13:39:35.955 [planetinfo.record]: air_color = 0.658447, 0.859166, 0.815089, 1; 13:39:35.956 [planetinfo.record]: cloud_alpha = 1.000000; 13:39:35.956 [planetinfo.record]: cloud_color = 0.580078, 0.541876, 0.521164, 1; 13:39:35.956 [planetinfo.record]: cloud_fraction = 0.610000; 13:39:35.956 [planetinfo.record]: land_color = 0.66, 0.404222, 0.265547, 1; 13:39:35.956 [planetinfo.record]: land_fraction = 0.610000; 13:39:35.956 [planetinfo.record]: polar_cloud_color = 0.761035, 0.729711, 0.712727, 1; 13:39:35.956 [planetinfo.record]: polar_land_color = 0.934, 0.843509, 0.794447, 1; 13:39:35.956 [planetinfo.record]: polar_sea_color = 0.932227, 0.904112, 0.871231, 1; 13:39:35.956 [planetinfo.record]: sea_color = 0.677734, 0.595975, 0.500359, 1; 13:39:35.956 [planetinfo.record]: rotation_speed = 0.000716 13:39:35.956 [planetinfo.record]: planet zpos = 529100.000000 13:39:35.956 [planetinfo.record]: sun_radius = 137449.258270 13:39:35.956 [planetinfo.record]: sun_vector = -0.283 -0.280 -0.917 13:39:35.956 [planetinfo.record]: sun_distance = 1005290 13:39:35.956 [planetinfo.record]: corona_flare = 0.042662 13:39:35.956 [planetinfo.record]: corona_hues = 0.577835 13:39:35.956 [planetinfo.record]: sun_color = 0.638659, 0.657262, 0.699683, 1 13:39:35.957 [planetinfo.record]: corona_shimmer = 0.346037 13:39:35.957 [planetinfo.record]: station_vector = 0.313 -0.125 0.942 13:39:35.957 [planetinfo.record]: station = coriolis 13:39:40.810 [PLANETINFO OVER]: Done 13:39:40.811 [PLANETINFO LOGGING]: 5 40 13:39:41.813 [planetinfo.record]: seed = 121 150 138 243 13:39:41.813 [planetinfo.record]: coordinates = 150 160 13:39:41.814 [planetinfo.record]: government = 7; 13:39:41.814 [planetinfo.record]: economy = 0; 13:39:41.814 [planetinfo.record]: techlevel = 13; 13:39:41.814 [planetinfo.record]: population = 60; 13:39:41.814 [planetinfo.record]: productivity = 52800; 13:39:41.814 [planetinfo.record]: name = "Bearrabe"; 13:39:41.814 [planetinfo.record]: inhabitant = "Frog"; 13:39:41.814 [planetinfo.record]: inhabitants = "Frogs"; 13:39:41.814 [planetinfo.record]: description = "The world Bearrabe is reasonably noted for its inhabitants’ exceptional loathing of food blenders."; 13:39:41.828 [planetinfo.record]: air_color = 0.672458, 0.540296, 0.910547, 1; 13:39:41.828 [planetinfo.record]: cloud_alpha = 1.000000; 13:39:41.828 [planetinfo.record]: cloud_color = 0.734375, 0.278259, 0.695178, 1; 13:39:41.835 [planetinfo.record]: cloud_fraction = 0.500000; 13:39:41.835 [planetinfo.record]: land_color = 0.145683, 0.556641, 0.238791, 1; 13:39:41.836 [planetinfo.record]: land_fraction = 0.660000; 13:39:41.836 [planetinfo.record]: polar_cloud_color = 0.830469, 0.508094, 0.802765, 1; 13:39:41.836 [planetinfo.record]: polar_land_color = 0.77004, 0.944336, 0.809529, 1; 13:39:41.836 [planetinfo.record]: polar_sea_color = 0.925977, 0.896446, 0.840071, 1; 13:39:41.836 [planetinfo.record]: sea_color = 0.740234, 0.645808, 0.465538, 1; 13:39:41.836 [planetinfo.record]: rotation_speed = 0.001032 13:39:41.836 [planetinfo.record]: planet zpos = 522760.000000 13:39:41.836 [planetinfo.record]: sun_radius = 73732.483826 13:39:41.836 [planetinfo.record]: sun_vector = -0.312 0.931 -0.190 13:39:41.836 [planetinfo.record]: sun_distance = 746800 13:39:41.836 [planetinfo.record]: corona_flare = 0.099857 13:39:41.836 [planetinfo.record]: corona_hues = 0.552574 13:39:41.836 [planetinfo.record]: sun_color = 0.810665, 0.819977, 0.844672, 1 13:39:41.836 [planetinfo.record]: corona_shimmer = 1.425005 13:39:41.836 [planetinfo.record]: station_vector = 0.282 -0.860 0.426 13:39:41.836 [planetinfo.record]: station = dodecahedron 13:39:46.427 [PLANETINFO OVER]: Done 13:39:46.427 [PLANETINFO LOGGING]: 5 41 13:39:47.432 [planetinfo.record]: seed = 101 211 64 179 13:39:47.432 [planetinfo.record]: coordinates = 211 180 13:39:47.432 [planetinfo.record]: government = 4; 13:39:47.432 [planetinfo.record]: economy = 4; 13:39:47.432 [planetinfo.record]: techlevel = 8; 13:39:47.432 [planetinfo.record]: population = 41; 13:39:47.432 [planetinfo.record]: productivity = 15744; 13:39:47.432 [planetinfo.record]: name = "Beanxeat"; 13:39:47.432 [planetinfo.record]: inhabitant = "Human Colonial"; 13:39:47.432 [planetinfo.record]: inhabitants = "Human Colonials"; 13:39:47.432 [planetinfo.record]: description = "The world Beanxeat is notable for its unusual oceans and the Beanxeatian evil poet."; 13:39:47.447 [planetinfo.record]: air_color = 0.602909, 0.902923, 0.961277, 1; 13:39:47.447 [planetinfo.record]: cloud_alpha = 1.000000; 13:39:47.447 [planetinfo.record]: cloud_color = 0.65638, 0.886719, 0.426041, 1; 13:39:47.447 [planetinfo.record]: cloud_fraction = 0.340000; 13:39:47.448 [planetinfo.record]: land_color = 0.285301, 0.373203, 0.541016, 1; 13:39:47.448 [planetinfo.record]: land_fraction = 0.630000; 13:39:47.448 [planetinfo.record]: polar_cloud_color = 0.753064, 0.899023, 0.607104, 1; 13:39:47.448 [planetinfo.record]: polar_land_color = 0.834127, 0.872549, 0.945898, 1; 13:39:47.448 [planetinfo.record]: polar_sea_color = 0.929883, 0.915687, 0.867225, 1; 13:39:47.448 [planetinfo.record]: sea_color = 0.701172, 0.658354, 0.512184, 1; 13:39:47.448 [planetinfo.record]: rotation_speed = 0.001559 13:39:47.448 [planetinfo.record]: planet zpos = 417450.000000 13:39:47.450 [planetinfo.record]: sun_radius = 78273.033142 13:39:47.450 [planetinfo.record]: sun_vector = -0.571 0.658 0.490 13:39:47.450 [planetinfo.record]: sun_distance = 645150 13:39:47.450 [planetinfo.record]: corona_flare = 0.093341 13:39:47.450 [planetinfo.record]: corona_hues = 0.835587 13:39:47.450 [planetinfo.record]: sun_color = 0.721698, 0.565223, 0.357666, 1 13:39:47.451 [planetinfo.record]: corona_shimmer = 0.700321 13:39:47.451 [planetinfo.record]: station_vector = -0.395 0.913 0.096 13:39:47.451 [planetinfo.record]: station = coriolis 13:39:52.076 [PLANETINFO OVER]: Done 13:39:52.076 [PLANETINFO LOGGING]: 5 42 13:39:53.078 [planetinfo.record]: seed = 13 177 218 174 13:39:53.078 [planetinfo.record]: coordinates = 177 194 13:39:53.078 [planetinfo.record]: government = 1; 13:39:53.078 [planetinfo.record]: economy = 2; 13:39:53.078 [planetinfo.record]: techlevel = 7; 13:39:53.078 [planetinfo.record]: population = 32; 13:39:53.078 [planetinfo.record]: productivity = 10240; 13:39:53.078 [planetinfo.record]: name = "Rexexe"; 13:39:53.078 [planetinfo.record]: inhabitant = "Harmless Bony Humanoid"; 13:39:53.078 [planetinfo.record]: inhabitants = "Harmless Bony Humanoids"; 13:39:53.078 [planetinfo.record]: description = "The planet Rexexe is most well known for its hoopy casinos."; 13:39:53.092 [planetinfo.record]: air_color = 0.620324, 0.541118, 0.894287, 1; 13:39:53.092 [planetinfo.record]: cloud_alpha = 1.000000; 13:39:53.092 [planetinfo.record]: cloud_color = 0.560019, 0.283859, 0.685547, 1; 13:39:53.092 [planetinfo.record]: cloud_fraction = 0.440000; 13:39:53.092 [planetinfo.record]: land_color = 0.194214, 0.578125, 0.560129, 1; 13:39:53.092 [planetinfo.record]: land_fraction = 0.710000; 13:39:53.092 [planetinfo.record]: polar_cloud_color = 0.715971, 0.512416, 0.808496, 1; 13:39:53.092 [planetinfo.record]: polar_land_color = 0.78577, 0.942187, 0.934855, 1; 13:39:53.092 [planetinfo.record]: polar_sea_color = 0.928125, 0.839414, 0.830237, 1; 13:39:53.092 [planetinfo.record]: sea_color = 0.71875, 0.443954, 0.415527, 1; 13:39:53.092 [planetinfo.record]: rotation_speed = 0.001161 13:39:53.092 [planetinfo.record]: planet zpos = 855010.000000 13:39:53.092 [planetinfo.record]: sun_radius = 151593.346863 13:39:53.092 [planetinfo.record]: sun_vector = -0.018 0.838 0.546 13:39:53.092 [planetinfo.record]: sun_distance = 1249630 13:39:53.092 [planetinfo.record]: corona_flare = 0.070140 13:39:53.092 [planetinfo.record]: corona_hues = 0.975410 13:39:53.092 [planetinfo.record]: sun_color = 0.262691, 0.425397, 0.761227, 1 13:39:53.092 [planetinfo.record]: corona_shimmer = 0.284499 13:39:53.093 [planetinfo.record]: station_vector = -0.026 -0.645 0.764 13:39:53.093 [planetinfo.record]: station = coriolis 13:39:57.837 [PLANETINFO OVER]: Done 13:39:57.838 [PLANETINFO LOGGING]: 5 43 13:39:58.842 [planetinfo.record]: seed = 1 83 40 247 13:39:58.842 [planetinfo.record]: coordinates = 83 130 13:39:58.842 [planetinfo.record]: government = 0; 13:39:58.842 [planetinfo.record]: economy = 2; 13:39:58.843 [planetinfo.record]: techlevel = 8; 13:39:58.843 [planetinfo.record]: population = 35; 13:39:58.843 [planetinfo.record]: productivity = 8960; 13:39:58.843 [planetinfo.record]: name = "Tiinve"; 13:39:58.843 [planetinfo.record]: inhabitant = "Human Colonial"; 13:39:58.843 [planetinfo.record]: inhabitants = "Human Colonials"; 13:39:58.843 [planetinfo.record]: description = "The planet Tiinve is well known for its inhabitants’ weird silliness but cursed by dreadful civil war."; 13:39:58.846 [planetinfo.record]: air_color = 0.577434, 0.950221, 0.91782, 1; 13:39:58.846 [planetinfo.record]: cloud_alpha = 1.000000; 13:39:58.846 [planetinfo.record]: cloud_color = 0.853516, 0.730156, 0.360077, 1; 13:39:58.846 [planetinfo.record]: cloud_fraction = 0.130000; 13:39:58.846 [planetinfo.record]: land_color = 0.66, 0.219141, 0.353465, 1; 13:39:58.846 [planetinfo.record]: land_fraction = 0.500000; 13:39:58.846 [planetinfo.record]: polar_cloud_color = 0.884082, 0.804221, 0.564638, 1; 13:39:58.846 [planetinfo.record]: polar_land_color = 0.934, 0.778029, 0.825552, 1; 13:39:58.846 [planetinfo.record]: polar_sea_color = 0.927345, 0.938086, 0.888983, 1; 13:39:58.846 [planetinfo.record]: sea_color = 0.590783, 0.619141, 0.489508, 1; 13:39:58.847 [planetinfo.record]: rotation_speed = 0.004852 13:39:58.847 [planetinfo.record]: planet zpos = 562920.000000 13:39:58.847 [planetinfo.record]: sun_radius = 122324.897003 13:39:58.847 [planetinfo.record]: sun_vector = -0.125 0.533 -0.837 13:39:58.847 [planetinfo.record]: sun_distance = 750560 13:39:58.847 [planetinfo.record]: corona_flare = 0.025885 13:39:58.847 [planetinfo.record]: corona_hues = 0.879166 13:39:58.847 [planetinfo.record]: sun_color = 0.57355, 0.695298, 0.785132, 1 13:39:58.847 [planetinfo.record]: corona_shimmer = 0.379268 13:39:58.848 [planetinfo.record]: station_vector = 0.681 0.630 0.372 13:39:58.848 [planetinfo.record]: station = coriolis 13:40:04.021 [PLANETINFO OVER]: Done 13:40:04.021 [PLANETINFO LOGGING]: 5 44 13:40:05.027 [planetinfo.record]: seed = 177 217 58 188 13:40:05.027 [planetinfo.record]: coordinates = 217 22 13:40:05.027 [planetinfo.record]: government = 6; 13:40:05.028 [planetinfo.record]: economy = 6; 13:40:05.028 [planetinfo.record]: techlevel = 5; 13:40:05.028 [planetinfo.record]: population = 33; 13:40:05.028 [planetinfo.record]: productivity = 10560; 13:40:05.028 [planetinfo.record]: name = "Teinxear"; 13:40:05.028 [planetinfo.record]: inhabitant = "Human Colonial"; 13:40:05.028 [planetinfo.record]: inhabitants = "Human Colonials"; 13:40:05.028 [planetinfo.record]: description = "The world Teinxear is beset by evil disease."; 13:40:05.031 [planetinfo.record]: air_color = 0.728692, 0.79891, 0.9151, 1; 13:40:05.031 [planetinfo.record]: cloud_alpha = 1.000000; 13:40:05.031 [planetinfo.record]: cloud_color = 0.739281, 0.748047, 0.74702, 1; 13:40:05.031 [planetinfo.record]: cloud_fraction = 0.130000; 13:40:05.031 [planetinfo.record]: land_color = 0.66, 0.583462, 0.170156, 1; 13:40:05.031 [planetinfo.record]: land_fraction = 0.290000; 13:40:05.031 [planetinfo.record]: polar_cloud_color = 0.830494, 0.836621, 0.835903, 1; 13:40:05.031 [planetinfo.record]: polar_land_color = 0.934, 0.906922, 0.760699, 1; 13:40:05.031 [planetinfo.record]: polar_sea_color = 0.926272, 0.932227, 0.873598, 1; 13:40:05.031 [planetinfo.record]: sea_color = 0.660419, 0.677734, 0.507242, 1; 13:40:05.031 [planetinfo.record]: rotation_speed = 0.003829 13:40:05.032 [planetinfo.record]: planet zpos = 610500.000000 13:40:05.032 [planetinfo.record]: sun_radius = 183478.836823 13:40:05.032 [planetinfo.record]: sun_vector = -0.304 0.677 0.670 13:40:05.032 [planetinfo.record]: sun_distance = 1343100 13:40:05.032 [planetinfo.record]: corona_flare = 0.040761 13:40:05.032 [planetinfo.record]: corona_hues = 0.777054 13:40:05.032 [planetinfo.record]: sun_color = 0.84433, 0.56009, 0.233258, 1 13:40:05.032 [planetinfo.record]: corona_shimmer = 0.760508 13:40:05.032 [planetinfo.record]: station_vector = 0.150 0.070 0.986 13:40:05.032 [planetinfo.record]: station = coriolis 13:40:09.614 [PLANETINFO OVER]: Done 13:40:09.614 [PLANETINFO LOGGING]: 5 45 13:40:10.634 [planetinfo.record]: seed = 173 170 32 153 13:40:10.634 [planetinfo.record]: coordinates = 170 66 13:40:10.634 [planetinfo.record]: government = 5; 13:40:10.634 [planetinfo.record]: economy = 2; 13:40:10.634 [planetinfo.record]: techlevel = 10; 13:40:10.634 [planetinfo.record]: population = 48; 13:40:10.634 [planetinfo.record]: productivity = 27648; 13:40:10.634 [planetinfo.record]: name = "Orcees"; 13:40:10.634 [planetinfo.record]: inhabitant = "Human Colonial"; 13:40:10.634 [planetinfo.record]: inhabitants = "Human Colonials"; 13:40:10.634 [planetinfo.record]: description = "The planet Orcees is an unremarkable planet."; 13:40:10.656 [planetinfo.record]: air_color = 0.49147, 0.449815, 0.946969, 1; 13:40:10.656 [planetinfo.record]: cloud_alpha = 1.000000; 13:40:10.656 [planetinfo.record]: cloud_color = 0.238644, 0.0197754, 0.84375, 1; 13:40:10.656 [planetinfo.record]: cloud_fraction = 0.280000; 13:40:10.656 [planetinfo.record]: land_color = 0.571498, 0.318527, 0.652344, 1; 13:40:10.656 [planetinfo.record]: land_fraction = 0.430000; 13:40:10.656 [planetinfo.record]: polar_cloud_color = 0.485388, 0.342769, 0.879687, 1; 13:40:10.656 [planetinfo.record]: polar_land_color = 0.905804, 0.815181, 0.934766, 1; 13:40:10.656 [planetinfo.record]: polar_sea_color = 0.946484, 0.916733, 0.898883, 1; 13:40:10.656 [planetinfo.record]: sea_color = 0.535156, 0.46787, 0.427498, 1; 13:40:10.656 [planetinfo.record]: rotation_speed = 0.000603 13:40:10.656 [planetinfo.record]: planet zpos = 476100.000000 13:40:10.656 [planetinfo.record]: sun_radius = 136274.649048 13:40:10.656 [planetinfo.record]: sun_vector = -0.198 0.857 0.476 13:40:10.656 [planetinfo.record]: sun_distance = 1058000 13:40:10.656 [planetinfo.record]: corona_flare = 0.050133 13:40:10.656 [planetinfo.record]: corona_hues = 0.926315 13:40:10.656 [planetinfo.record]: sun_color = 0.663693, 0.447413, 0.360114, 1 13:40:10.657 [planetinfo.record]: corona_shimmer = 0.979729 13:40:10.657 [planetinfo.record]: station_vector = 0.040 0.648 0.761 13:40:10.657 [planetinfo.record]: station = coriolis 13:40:15.264 [PLANETINFO OVER]: Done 13:40:15.264 [PLANETINFO LOGGING]: 5 46 13:40:16.267 [planetinfo.record]: seed = 229 232 170 56 13:40:16.267 [planetinfo.record]: coordinates = 232 201 13:40:16.267 [planetinfo.record]: government = 4; 13:40:16.267 [planetinfo.record]: economy = 1; 13:40:16.267 [planetinfo.record]: techlevel = 8; 13:40:16.267 [planetinfo.record]: population = 38; 13:40:16.267 [planetinfo.record]: productivity = 21888; 13:40:16.267 [planetinfo.record]: name = "Edmainer"; 13:40:16.267 [planetinfo.record]: inhabitant = "Red Bug-Eyed Frog"; 13:40:16.267 [planetinfo.record]: inhabitants = "Red Bug-Eyed Frogs"; 13:40:16.267 [planetinfo.record]: description = "Edmainer is very notable for the Edmainerian tree grub."; 13:40:16.284 [planetinfo.record]: air_color = 0.686208, 0.542641, 0.912498, 1; 13:40:16.284 [planetinfo.record]: cloud_alpha = 1.000000; 13:40:16.284 [planetinfo.record]: cloud_color = 0.740234, 0.283371, 0.658142, 1; 13:40:16.284 [planetinfo.record]: cloud_fraction = 0.400000; 13:40:16.284 [planetinfo.record]: land_color = 0.619141, 0.445007, 0.489901, 1; 13:40:16.284 [planetinfo.record]: land_fraction = 0.570000; 13:40:16.284 [planetinfo.record]: polar_cloud_color = 0.833105, 0.511742, 0.77536, 1; 13:40:16.284 [planetinfo.record]: polar_land_color = 0.938086, 0.872127, 0.889132, 1; 13:40:16.284 [planetinfo.record]: polar_sea_color = 0.929883, 0.876192, 0.849971, 1; 13:40:16.284 [planetinfo.record]: sea_color = 0.701172, 0.539231, 0.460144, 1; 13:40:16.284 [planetinfo.record]: rotation_speed = 0.003928 13:40:16.284 [planetinfo.record]: planet zpos = 611520.000000 13:40:16.284 [planetinfo.record]: sun_radius = 140865.489502 13:40:16.284 [planetinfo.record]: sun_vector = 0.596 -0.627 0.502 13:40:16.284 [planetinfo.record]: sun_distance = 1172080 13:40:16.284 [planetinfo.record]: corona_flare = 0.005261 13:40:16.284 [planetinfo.record]: corona_hues = 0.779228 13:40:16.284 [planetinfo.record]: sun_color = 0.766791, 0.698432, 0.538293, 1 13:40:16.284 [planetinfo.record]: corona_shimmer = 0.462048 13:40:16.285 [planetinfo.record]: station_vector = -0.179 -0.952 0.250 13:40:16.285 [planetinfo.record]: station = coriolis 13:40:20.722 [PLANETINFO OVER]: Done 13:40:20.723 [PLANETINFO LOGGING]: 5 47 13:40:21.725 [planetinfo.record]: seed = 233 48 40 41 13:40:21.725 [planetinfo.record]: coordinates = 48 12 13:40:21.726 [planetinfo.record]: government = 5; 13:40:21.726 [planetinfo.record]: economy = 4; 13:40:21.726 [planetinfo.record]: techlevel = 6; 13:40:21.726 [planetinfo.record]: population = 34; 13:40:21.726 [planetinfo.record]: productivity = 14688; 13:40:21.726 [planetinfo.record]: name = "Esbileat"; 13:40:21.726 [planetinfo.record]: inhabitant = "Human Colonial"; 13:40:21.726 [planetinfo.record]: inhabitants = "Human Colonials"; 13:40:21.726 [planetinfo.record]: description = "The planet Esbileat is scourged by deadly disease."; 13:40:21.740 [planetinfo.record]: air_color = 0.510164, 0.970383, 0.819035, 1; 13:40:21.740 [planetinfo.record]: cloud_alpha = 1.000000; 13:40:21.747 [planetinfo.record]: cloud_color = 0.914062, 0.178333, 0.160675, 1; 13:40:21.747 [planetinfo.record]: cloud_fraction = 0.210000; 13:40:21.747 [planetinfo.record]: land_color = 0.617461, 0.574922, 0.66, 1; 13:40:21.748 [planetinfo.record]: land_fraction = 0.650000; 13:40:21.748 [planetinfo.record]: polar_cloud_color = 0.911328, 0.452872, 0.44187, 1; 13:40:21.748 [planetinfo.record]: polar_land_color = 0.91895, 0.9039, 0.934, 1; 13:40:21.748 [planetinfo.record]: polar_sea_color = 0.864841, 0.945898, 0.746373, 1; 13:40:21.748 [planetinfo.record]: sea_color = 0.35557, 0.541016, 0.0845337, 1; 13:40:21.748 [planetinfo.record]: rotation_speed = 0.003814 13:40:21.748 [planetinfo.record]: planet zpos = 568480.000000 13:40:21.748 [planetinfo.record]: sun_radius = 135661.577148 13:40:21.748 [planetinfo.record]: sun_vector = 0.067 -0.668 -0.741 13:40:21.748 [planetinfo.record]: sun_distance = 1085280 13:40:21.748 [planetinfo.record]: corona_flare = 0.006497 13:40:21.748 [planetinfo.record]: corona_hues = 0.682808 13:40:21.748 [planetinfo.record]: sun_color = 0.849976, 0.724189, 0.448281, 1 13:40:21.748 [planetinfo.record]: corona_shimmer = 0.529353 13:40:21.749 [planetinfo.record]: station_vector = -0.948 0.172 0.269 13:40:21.749 [planetinfo.record]: station = coriolis 13:40:26.449 [PLANETINFO OVER]: Done 13:40:26.450 [PLANETINFO LOGGING]: 5 48 13:40:27.452 [planetinfo.record]: seed = 41 81 42 121 13:40:27.452 [planetinfo.record]: coordinates = 81 193 13:40:27.452 [planetinfo.record]: government = 5; 13:40:27.452 [planetinfo.record]: economy = 1; 13:40:27.452 [planetinfo.record]: techlevel = 10; 13:40:27.452 [planetinfo.record]: population = 47; 13:40:27.452 [planetinfo.record]: productivity = 30456; 13:40:27.452 [planetinfo.record]: name = "Ormala"; 13:40:27.453 [planetinfo.record]: inhabitant = "Human Colonial"; 13:40:27.453 [planetinfo.record]: inhabitants = "Human Colonials"; 13:40:27.453 [planetinfo.record]: description = "This planet is fabled for its exciting vacuum karate."; 13:40:27.464 [planetinfo.record]: air_color = 0.547336, 0.973635, 0.93991, 1; 13:40:27.464 [planetinfo.record]: cloud_alpha = 1.000000; 13:40:27.464 [planetinfo.record]: cloud_color = 0.923828, 0.774208, 0.263435, 1; 13:40:27.464 [planetinfo.record]: cloud_fraction = 0.560000; 13:40:27.464 [planetinfo.record]: land_color = 0.613594, 0.649124, 0.66, 1; 13:40:27.464 [planetinfo.record]: land_fraction = 0.340000; 13:40:27.464 [planetinfo.record]: polar_cloud_color = 0.915723, 0.82303, 0.506599, 1; 13:40:27.464 [planetinfo.record]: polar_land_color = 0.917582, 0.930152, 0.934, 1; 13:40:27.464 [planetinfo.record]: polar_sea_color = 0.733796, 0.938086, 0.775292, 1; 13:40:27.464 [planetinfo.record]: sea_color = 0.0798111, 0.619141, 0.189362, 1; 13:40:27.464 [planetinfo.record]: rotation_speed = 0.001591 13:40:27.464 [planetinfo.record]: planet zpos = 676130.000000 13:40:27.464 [planetinfo.record]: sun_radius = 99853.549500 13:40:27.464 [planetinfo.record]: sun_vector = -0.260 0.738 -0.622 13:40:27.464 [planetinfo.record]: sun_distance = 1040200 13:40:27.464 [planetinfo.record]: corona_flare = 0.064577 13:40:27.464 [planetinfo.record]: corona_hues = 0.912849 13:40:27.464 [planetinfo.record]: sun_color = 0.848285, 0.723637, 0.240117, 1 13:40:27.464 [planetinfo.record]: corona_shimmer = 0.333860 13:40:27.464 [planetinfo.record]: station_vector = 0.604 0.000 0.797 13:40:27.465 [planetinfo.record]: station = coriolis 13:40:32.232 [PLANETINFO OVER]: Done 13:40:32.232 [PLANETINFO LOGGING]: 5 49 13:40:33.236 [planetinfo.record]: seed = 53 90 64 59 13:40:33.236 [planetinfo.record]: coordinates = 90 85 13:40:33.236 [planetinfo.record]: government = 6; 13:40:33.236 [planetinfo.record]: economy = 5; 13:40:33.236 [planetinfo.record]: techlevel = 7; 13:40:33.236 [planetinfo.record]: population = 40; 13:40:33.236 [planetinfo.record]: productivity = 16000; 13:40:33.236 [planetinfo.record]: name = "Anma"; 13:40:33.236 [planetinfo.record]: inhabitant = "Human Colonial"; 13:40:33.236 [planetinfo.record]: inhabitants = "Human Colonials"; 13:40:33.236 [planetinfo.record]: description = "The world Anma is a dull place."; 13:40:33.237 [planetinfo.record]: air_color = 0.477482, 0.577758, 0.888434, 1; 13:40:33.237 [planetinfo.record]: cloud_alpha = 1.000000; 13:40:33.238 [planetinfo.record]: cloud_color = 0.1409, 0.519731, 0.667969, 1; 13:40:33.238 [planetinfo.record]: cloud_fraction = 0.320000; 13:40:33.238 [planetinfo.record]: land_color = 0.623906, 0.66, 0.66, 1; 13:40:33.238 [planetinfo.record]: land_fraction = 0.290000; 13:40:33.238 [planetinfo.record]: polar_cloud_color = 0.405766, 0.689543, 0.800586, 1; 13:40:33.238 [planetinfo.record]: polar_land_color = 0.92123, 0.934, 0.934, 1; 13:40:33.238 [planetinfo.record]: polar_sea_color = 0.91582, 0.908274, 0.795082, 1; 13:40:33.238 [planetinfo.record]: sea_color = 0.841797, 0.814052, 0.397881, 1; 13:40:33.238 [planetinfo.record]: rotation_speed = 0.004611 13:40:33.238 [planetinfo.record]: planet zpos = 686640.000000 13:40:33.238 [planetinfo.record]: sun_radius = 166668.442078 13:40:33.238 [planetinfo.record]: sun_vector = -0.402 0.665 -0.630 13:40:33.238 [planetinfo.record]: sun_distance = 1029960 13:40:33.238 [planetinfo.record]: corona_flare = 0.053227 13:40:33.238 [planetinfo.record]: corona_hues = 0.537468 13:40:33.238 [planetinfo.record]: sun_color = 0.8289, 0.74483, 0.698316, 1 13:40:33.251 [planetinfo.record]: corona_shimmer = 0.547557 13:40:33.252 [planetinfo.record]: station_vector = -0.455 0.696 0.555 13:40:33.252 [planetinfo.record]: station = coriolis 13:40:38.015 [PLANETINFO OVER]: Done 13:40:38.016 [PLANETINFO LOGGING]: 5 50 13:40:39.033 [planetinfo.record]: seed = 253 166 186 18 13:40:39.033 [planetinfo.record]: coordinates = 166 128 13:40:39.033 [planetinfo.record]: government = 7; 13:40:39.033 [planetinfo.record]: economy = 0; 13:40:39.033 [planetinfo.record]: techlevel = 13; 13:40:39.033 [planetinfo.record]: population = 60; 13:40:39.033 [planetinfo.record]: productivity = 52800; 13:40:39.033 [planetinfo.record]: name = "Enqura"; 13:40:39.033 [planetinfo.record]: inhabitant = "Green Rodent"; 13:40:39.033 [planetinfo.record]: inhabitants = "Green Rodents"; 13:40:39.033 [planetinfo.record]: description = "This planet is very well known for its inhabitants’ ancient loathing of discos and the Enquraian evil Beabbeaboid."; 13:40:39.048 [planetinfo.record]: air_color = 0.702832, 0.803515, 0.928107, 1; 13:40:39.048 [planetinfo.record]: cloud_alpha = 1.000000; 13:40:39.048 [planetinfo.record]: cloud_color = 0.685646, 0.787109, 0.753817, 1; 13:40:39.048 [planetinfo.record]: cloud_fraction = 0.210000; 13:40:39.048 [planetinfo.record]: land_color = 0.66, 0.340252, 0.332578, 1; 13:40:39.048 [planetinfo.record]: land_fraction = 0.650000; 13:40:39.048 [planetinfo.record]: polar_cloud_color = 0.785379, 0.854199, 0.831618, 1; 13:40:39.048 [planetinfo.record]: polar_land_color = 0.934, 0.820877, 0.818162, 1; 13:40:39.048 [planetinfo.record]: polar_sea_color = 0.822505, 0.85267, 0.943164, 1; 13:40:39.048 [planetinfo.record]: sea_color = 0.277519, 0.350229, 0.568359, 1; 13:40:39.048 [planetinfo.record]: rotation_speed = 0.001713 13:40:39.048 [planetinfo.record]: planet zpos = 314460.000000 13:40:39.048 [planetinfo.record]: sun_radius = 104452.665100 13:40:39.048 [planetinfo.record]: sun_vector = -0.955 -0.232 -0.183 13:40:39.048 [planetinfo.record]: sun_distance = 698800 13:40:39.048 [planetinfo.record]: corona_flare = 0.031436 13:40:39.048 [planetinfo.record]: corona_hues = 0.901703 13:40:39.048 [planetinfo.record]: sun_color = 0.815683, 0.74083, 0.534884, 1 13:40:39.049 [planetinfo.record]: corona_shimmer = 0.512665 13:40:39.049 [planetinfo.record]: station_vector = -0.819 -0.569 0.073 13:40:39.049 [planetinfo.record]: station = dodecahedron 13:40:44.718 [PLANETINFO OVER]: Done 13:40:44.719 [PLANETINFO LOGGING]: 5 51 13:40:45.722 [planetinfo.record]: seed = 17 65 104 111 13:40:45.722 [planetinfo.record]: coordinates = 65 244 13:40:45.722 [planetinfo.record]: government = 2; 13:40:45.722 [planetinfo.record]: economy = 4; 13:40:45.722 [planetinfo.record]: techlevel = 5; 13:40:45.722 [planetinfo.record]: population = 27; 13:40:45.722 [planetinfo.record]: productivity = 7776; 13:40:45.722 [planetinfo.record]: name = "Azara"; 13:40:45.722 [planetinfo.record]: inhabitant = "Human Colonial"; 13:40:45.722 [planetinfo.record]: inhabitants = "Human Colonials"; 13:40:45.723 [planetinfo.record]: description = "This planet is a tedious place."; 13:40:45.732 [planetinfo.record]: air_color = 0.544245, 0.970383, 0.920041, 1; 13:40:45.732 [planetinfo.record]: cloud_alpha = 1.000000; 13:40:45.732 [planetinfo.record]: cloud_color = 0.914062, 0.688225, 0.25708, 1; 13:40:45.732 [planetinfo.record]: cloud_fraction = 0.240000; 13:40:45.732 [planetinfo.record]: land_color = 0.380506, 0.505859, 0.104729, 1; 13:40:45.732 [planetinfo.record]: land_fraction = 0.560000; 13:40:45.732 [planetinfo.record]: polar_cloud_color = 0.911328, 0.770602, 0.501942, 1; 13:40:45.732 [planetinfo.record]: polar_land_color = 0.890597, 0.949414, 0.7612, 1; 13:40:45.732 [planetinfo.record]: polar_sea_color = 0.870306, 0.922656, 0.90916, 1; 13:40:45.732 [planetinfo.record]: sea_color = 0.597903, 0.773438, 0.728183, 1; 13:40:45.732 [planetinfo.record]: rotation_speed = 0.002912 13:40:45.732 [planetinfo.record]: planet zpos = 806520.000000 13:40:45.732 [planetinfo.record]: sun_radius = 212101.821747 13:40:45.732 [planetinfo.record]: sun_vector = 0.982 -0.185 -0.036 13:40:45.733 [planetinfo.record]: sun_distance = 1344200 13:40:45.733 [planetinfo.record]: corona_flare = 0.084383 13:40:45.733 [planetinfo.record]: corona_hues = 0.924568 13:40:45.733 [planetinfo.record]: sun_color = 0.669864, 0.490308, 0.391359, 1 13:40:45.733 [planetinfo.record]: corona_shimmer = 0.302950 13:40:45.733 [planetinfo.record]: station_vector = 0.199 0.580 0.790 13:40:45.739 [planetinfo.record]: station = coriolis 13:40:50.984 [PLANETINFO OVER]: Done 13:40:50.984 [PLANETINFO LOGGING]: 5 52 13:40:51.988 [planetinfo.record]: seed = 225 104 90 98 13:40:51.988 [planetinfo.record]: coordinates = 104 84 13:40:51.988 [planetinfo.record]: government = 4; 13:40:51.988 [planetinfo.record]: economy = 4; 13:40:51.988 [planetinfo.record]: techlevel = 5; 13:40:51.988 [planetinfo.record]: population = 29; 13:40:51.988 [planetinfo.record]: productivity = 11136; 13:40:51.989 [planetinfo.record]: name = "Xemadi"; 13:40:51.989 [planetinfo.record]: inhabitant = "Human Colonial"; 13:40:51.989 [planetinfo.record]: inhabitants = "Human Colonials"; 13:40:51.989 [planetinfo.record]: description = "This planet is notable for the Xemadiian edible poet and mud tennis."; 13:40:52.006 [planetinfo.record]: air_color = 0.428723, 0.778258, 0.862418, 1; 13:40:52.007 [planetinfo.record]: cloud_alpha = 1.000000; 13:40:52.007 [planetinfo.record]: cloud_color = 0.271089, 0.589844, 0.0529938, 1; 13:40:52.007 [planetinfo.record]: cloud_fraction = 0.370000; 13:40:52.007 [planetinfo.record]: land_color = 0.536049, 0.193359, 0.66, 1; 13:40:52.007 [planetinfo.record]: land_fraction = 0.350000; 13:40:52.007 [planetinfo.record]: polar_cloud_color = 0.506903, 0.76543, 0.330017, 1; 13:40:52.007 [planetinfo.record]: polar_land_color = 0.890148, 0.768908, 0.934, 1; 13:40:52.007 [planetinfo.record]: polar_sea_color = 0.93457, 0.928232, 0.876616, 1; 13:40:52.007 [planetinfo.record]: sea_color = 0.654297, 0.636546, 0.492001, 1; 13:40:52.007 [planetinfo.record]: rotation_speed = 0.004396 13:40:52.007 [planetinfo.record]: planet zpos = 480480.000000 13:40:52.008 [planetinfo.record]: sun_radius = 84232.620850 13:40:52.008 [planetinfo.record]: sun_vector = -0.256 -0.957 -0.139 13:40:52.008 [planetinfo.record]: sun_distance = 686400 13:40:52.008 [planetinfo.record]: corona_flare = 0.091556 13:40:52.008 [planetinfo.record]: corona_hues = 0.835190 13:40:52.008 [planetinfo.record]: sun_color = 0.746274, 0.603458, 0.58726, 1 13:40:52.008 [planetinfo.record]: corona_shimmer = 1.064518 13:40:52.008 [planetinfo.record]: station_vector = -0.565 -0.754 0.335 13:40:52.008 [planetinfo.record]: station = coriolis 13:40:56.802 [PLANETINFO OVER]: Done 13:40:56.803 [PLANETINFO LOGGING]: 5 53 13:40:57.807 [planetinfo.record]: seed = 253 109 160 121 13:40:57.808 [planetinfo.record]: coordinates = 109 235 13:40:57.808 [planetinfo.record]: government = 7; 13:40:57.808 [planetinfo.record]: economy = 3; 13:40:57.808 [planetinfo.record]: techlevel = 9; 13:40:57.808 [planetinfo.record]: population = 47; 13:40:57.808 [planetinfo.record]: productivity = 28952; 13:40:57.808 [planetinfo.record]: name = "Orbequso"; 13:40:57.808 [planetinfo.record]: inhabitant = "Blue Insect"; 13:40:57.808 [planetinfo.record]: inhabitants = "Blue Insects"; 13:40:57.808 [planetinfo.record]: description = "This world is mildly well known for its exotic cuisine and its inhabitants’ ingrained silliness."; 13:40:57.810 [planetinfo.record]: air_color = 0.502741, 0.46289, 0.938514, 1; 13:40:57.811 [planetinfo.record]: cloud_alpha = 1.000000; 13:40:57.811 [planetinfo.record]: cloud_color = 0.264328, 0.0639343, 0.818359, 1; 13:40:57.811 [planetinfo.record]: cloud_fraction = 0.600000; 13:40:57.811 [planetinfo.record]: land_color = 0.639762, 0.652344, 0.631958, 1; 13:40:57.811 [planetinfo.record]: land_fraction = 0.630000; 13:40:57.811 [planetinfo.record]: polar_cloud_color = 0.500877, 0.367994, 0.868262, 1; 13:40:57.811 [planetinfo.record]: polar_land_color = 0.930258, 0.934766, 0.927463, 1; 13:40:57.811 [planetinfo.record]: polar_sea_color = 0.756844, 0.901172, 0.759099, 1; 13:40:57.811 [planetinfo.record]: sea_color = 0.355164, 0.988281, 0.365056, 1; 13:40:57.811 [planetinfo.record]: rotation_speed = 0.003704 13:40:57.811 [planetinfo.record]: planet zpos = 784350.000000 13:40:57.811 [planetinfo.record]: sun_radius = 147711.111603 13:40:57.811 [planetinfo.record]: sun_vector = -0.536 0.840 -0.090 13:40:57.811 [planetinfo.record]: sun_distance = 993510 13:40:57.811 [planetinfo.record]: corona_flare = 0.050665 13:40:57.812 [planetinfo.record]: corona_hues = 0.802437 13:40:57.812 [planetinfo.record]: sun_color = 0.684149, 0.703541, 0.791788, 1 13:40:57.812 [planetinfo.record]: corona_shimmer = 0.340203 13:40:57.812 [planetinfo.record]: station_vector = 0.707 -0.653 0.271 13:40:57.812 [planetinfo.record]: station = coriolis 13:41:03.002 [PLANETINFO OVER]: Done 13:41:03.003 [PLANETINFO LOGGING]: 5 54 13:41:04.008 [planetinfo.record]: seed = 85 7 10 149 13:41:04.008 [planetinfo.record]: coordinates = 7 186 13:41:04.008 [planetinfo.record]: government = 2; 13:41:04.008 [planetinfo.record]: economy = 2; 13:41:04.008 [planetinfo.record]: techlevel = 9; 13:41:04.008 [planetinfo.record]: population = 41; 13:41:04.008 [planetinfo.record]: productivity = 15744; 13:41:04.008 [planetinfo.record]: name = "Latibe"; 13:41:04.008 [planetinfo.record]: inhabitant = "Human Colonial"; 13:41:04.008 [planetinfo.record]: inhabitants = "Human Colonials"; 13:41:04.008 [planetinfo.record]: description = "This planet is mildly noted for its ancient Latibeian Zainnu tulip plantations but ravaged by unpredictable solar activity."; 13:41:04.020 [planetinfo.record]: air_color = 0.798355, 0.504191, 0.971033, 1; 13:41:04.020 [planetinfo.record]: cloud_alpha = 1.000000; 13:41:04.020 [planetinfo.record]: cloud_color = 0.916016, 0.143127, 0.215586, 1; 13:41:04.020 [planetinfo.record]: cloud_fraction = 0.180000; 13:41:04.021 [planetinfo.record]: land_color = 0.633615, 0.322266, 0.66, 1; 13:41:04.021 [planetinfo.record]: land_fraction = 0.360000; 13:41:04.021 [planetinfo.record]: polar_cloud_color = 0.912207, 0.43116, 0.476258, 1; 13:41:04.021 [planetinfo.record]: polar_land_color = 0.924665, 0.814514, 0.934, 1; 13:41:04.021 [planetinfo.record]: polar_sea_color = 0.90997, 0.928906, 0.86831, 1; 13:41:04.021 [planetinfo.record]: sea_color = 0.652966, 0.710938, 0.525427, 1; 13:41:04.021 [planetinfo.record]: rotation_speed = 0.004561 13:41:04.021 [planetinfo.record]: planet zpos = 492360.000000 13:41:04.021 [planetinfo.record]: sun_radius = 118975.480347 13:41:04.021 [planetinfo.record]: sun_vector = -0.614 0.151 -0.775 13:41:04.021 [planetinfo.record]: sun_distance = 943690 13:41:04.021 [planetinfo.record]: corona_flare = 0.023108 13:41:04.021 [planetinfo.record]: corona_hues = 0.904762 13:41:04.021 [planetinfo.record]: sun_color = 0.513181, 0.796583, 0.808185, 1 13:41:04.021 [planetinfo.record]: corona_shimmer = 0.397376 13:41:04.021 [planetinfo.record]: station_vector = -0.196 0.954 0.229 13:41:04.021 [planetinfo.record]: station = coriolis 13:41:09.461 [PLANETINFO OVER]: Done 13:41:09.462 [PLANETINFO LOGGING]: 5 55 13:41:10.482 [planetinfo.record]: seed = 121 223 232 41 13:41:10.482 [planetinfo.record]: coordinates = 223 243 13:41:10.483 [planetinfo.record]: government = 7; 13:41:10.483 [planetinfo.record]: economy = 3; 13:41:10.483 [planetinfo.record]: techlevel = 11; 13:41:10.483 [planetinfo.record]: population = 55; 13:41:10.483 [planetinfo.record]: productivity = 33880; 13:41:10.483 [planetinfo.record]: name = "Estebiin"; 13:41:10.483 [planetinfo.record]: inhabitant = "Small Red Fat Humanoid"; 13:41:10.483 [planetinfo.record]: inhabitants = "Small Red Fat Humanoids"; 13:41:10.483 [planetinfo.record]: description = "Estebiin is a revolting dump."; 13:41:10.496 [planetinfo.record]: air_color = 0.574668, 0.945668, 0.893153, 1; 13:41:10.496 [planetinfo.record]: cloud_alpha = 1.000000; 13:41:10.496 [planetinfo.record]: cloud_color = 0.839844, 0.638802, 0.354309, 1; 13:41:10.496 [planetinfo.record]: cloud_fraction = 0.320000; 13:41:10.496 [planetinfo.record]: land_color = 0.00773438, 0.66, 0.4154, 1; 13:41:10.496 [planetinfo.record]: land_fraction = 0.330000; 13:41:10.496 [planetinfo.record]: polar_cloud_color = 0.87793, 0.74658, 0.560709, 1; 13:41:10.496 [planetinfo.record]: polar_land_color = 0.703236, 0.934, 0.847464, 1; 13:41:10.496 [planetinfo.record]: polar_sea_color = 0.941797, 0.921786, 0.890568, 1; 13:41:10.496 [planetinfo.record]: sea_color = 0.582031, 0.532564, 0.455394, 1; 13:41:10.496 [planetinfo.record]: rotation_speed = 0.001967 13:41:10.496 [planetinfo.record]: planet zpos = 587730.000000 13:41:10.496 [planetinfo.record]: sun_radius = 130992.202148 13:41:10.496 [planetinfo.record]: sun_vector = -1.000 -0.015 -0.023 13:41:10.496 [planetinfo.record]: sun_distance = 1068600 13:41:10.496 [planetinfo.record]: corona_flare = 0.020494 13:41:10.496 [planetinfo.record]: corona_hues = 0.606773 13:41:10.497 [planetinfo.record]: sun_color = 0.388261, 0.417273, 0.668753, 1 13:41:10.497 [planetinfo.record]: corona_shimmer = 0.455687 13:41:10.497 [planetinfo.record]: station_vector = 0.895 -0.091 0.437 13:41:10.497 [planetinfo.record]: station = dodecahedron 13:41:15.414 [PLANETINFO OVER]: Done 13:41:15.415 [PLANETINFO LOGGING]: 5 56 13:41:16.417 [planetinfo.record]: seed = 217 44 202 47 13:41:16.417 [planetinfo.record]: coordinates = 44 6 13:41:16.417 [planetinfo.record]: government = 3; 13:41:16.417 [planetinfo.record]: economy = 6; 13:41:16.417 [planetinfo.record]: techlevel = 3; 13:41:16.417 [planetinfo.record]: population = 22; 13:41:16.417 [planetinfo.record]: productivity = 4928; 13:41:16.417 [planetinfo.record]: name = "Axeon"; 13:41:16.417 [planetinfo.record]: inhabitant = "Red Horned Humanoid"; 13:41:16.417 [planetinfo.record]: inhabitants = "Red Horned Humanoids"; 13:41:16.417 [planetinfo.record]: description = "The planet Axeon is scourged by deadly disease."; 13:41:16.419 [planetinfo.record]: air_color = 0.625182, 0.901441, 0.860181, 1; 13:41:16.419 [planetinfo.record]: cloud_alpha = 1.000000; 13:41:16.420 [planetinfo.record]: cloud_color = 0.707031, 0.60795, 0.48056, 1; 13:41:16.420 [planetinfo.record]: cloud_fraction = 0.180000; 13:41:16.420 [planetinfo.record]: land_color = 0.231911, 0.525391, 0.504755, 1; 13:41:16.420 [planetinfo.record]: land_fraction = 0.680000; 13:41:16.420 [planetinfo.record]: polar_cloud_color = 0.818164, 0.746505, 0.654371, 1; 13:41:16.420 [planetinfo.record]: polar_land_color = 0.815149, 0.947461, 0.938158, 1; 13:41:16.420 [planetinfo.record]: polar_sea_color = 0.938281, 0.900017, 0.879272, 1; 13:41:16.420 [planetinfo.record]: sea_color = 0.617188, 0.51651, 0.461926, 1; 13:41:16.420 [planetinfo.record]: rotation_speed = 0.002176 13:41:16.420 [planetinfo.record]: planet zpos = 737000.000000 13:41:16.420 [planetinfo.record]: sun_radius = 188282.104492 13:41:16.420 [planetinfo.record]: sun_vector = -0.047 -0.178 0.983 13:41:16.420 [planetinfo.record]: sun_distance = 1474000 13:41:16.420 [planetinfo.record]: corona_flare = 0.095537 13:41:16.420 [planetinfo.record]: corona_hues = 0.757805 13:41:16.421 [planetinfo.record]: sun_color = 0.684058, 0.789885, 0.831726, 1 13:41:16.421 [planetinfo.record]: corona_shimmer = 0.445902 13:41:16.421 [planetinfo.record]: station_vector = 0.463 0.879 0.117 13:41:16.421 [planetinfo.record]: station = coriolis 13:41:21.278 [PLANETINFO OVER]: Done 13:41:21.279 [PLANETINFO LOGGING]: 5 57 13:41:22.283 [planetinfo.record]: seed = 5 82 64 116 13:41:22.283 [planetinfo.record]: coordinates = 82 191 13:41:22.283 [planetinfo.record]: government = 0; 13:41:22.283 [planetinfo.record]: economy = 7; 13:41:22.283 [planetinfo.record]: techlevel = 2; 13:41:22.283 [planetinfo.record]: population = 16; 13:41:22.283 [planetinfo.record]: productivity = 1536; 13:41:22.283 [planetinfo.record]: name = "Racemace"; 13:41:22.283 [planetinfo.record]: inhabitant = "Human Colonial"; 13:41:22.283 [planetinfo.record]: inhabitants = "Human Colonials"; 13:41:22.283 [planetinfo.record]: description = "This planet is noted for its exciting sit coms."; 13:41:22.297 [planetinfo.record]: air_color = 0.649667, 0.887866, 0.914449, 1; 13:41:22.297 [planetinfo.record]: cloud_alpha = 1.000000; 13:41:22.298 [planetinfo.record]: cloud_color = 0.683251, 0.746094, 0.544998, 1; 13:41:22.298 [planetinfo.record]: cloud_fraction = 0.360000; 13:41:22.298 [planetinfo.record]: land_color = 0.190781, 0.12375, 0.66, 1; 13:41:22.298 [planetinfo.record]: land_fraction = 0.300000; 13:41:22.298 [planetinfo.record]: polar_cloud_color = 0.791746, 0.835742, 0.694956, 1; 13:41:22.299 [planetinfo.record]: polar_land_color = 0.767996, 0.744281, 0.934, 1; 13:41:22.299 [planetinfo.record]: polar_sea_color = 0.931836, 0.926589, 0.870775, 1; 13:41:22.299 [planetinfo.record]: sea_color = 0.681641, 0.666287, 0.502976, 1; 13:41:22.299 [planetinfo.record]: rotation_speed = 0.002761 13:41:22.299 [planetinfo.record]: planet zpos = 470640.000000 13:41:22.299 [planetinfo.record]: sun_radius = 100541.944580 13:41:22.299 [planetinfo.record]: sun_vector = 0.406 0.475 -0.781 13:41:22.299 [planetinfo.record]: sun_distance = 784400 13:41:22.299 [planetinfo.record]: corona_flare = 0.000192 13:41:22.299 [planetinfo.record]: corona_hues = 0.757927 13:41:22.299 [planetinfo.record]: sun_color = 0.686522, 0.744393, 0.744769, 1 13:41:22.300 [planetinfo.record]: corona_shimmer = 0.412493 13:41:22.300 [planetinfo.record]: station_vector = 0.701 0.074 0.709 13:41:22.300 [planetinfo.record]: station = coriolis 13:41:27.436 [PLANETINFO OVER]: Done 13:41:27.437 [PLANETINFO LOGGING]: 5 58 13:41:28.442 [planetinfo.record]: seed = 237 69 154 23 13:41:28.442 [planetinfo.record]: coordinates = 69 75 13:41:28.442 [planetinfo.record]: government = 5; 13:41:28.442 [planetinfo.record]: economy = 3; 13:41:28.442 [planetinfo.record]: techlevel = 8; 13:41:28.442 [planetinfo.record]: population = 41; 13:41:28.442 [planetinfo.record]: productivity = 20664; 13:41:28.442 [planetinfo.record]: name = "Tiessous"; 13:41:28.442 [planetinfo.record]: inhabitant = "Green Frog"; 13:41:28.442 [planetinfo.record]: inhabitants = "Green Frogs"; 13:41:28.442 [planetinfo.record]: description = "The planet Tiessous is very famous for Tiessousian wolf cutlet but plagued by lethal disease."; 13:41:28.470 [planetinfo.record]: air_color = 0.588355, 0.886259, 0.980789, 1; 13:41:28.470 [planetinfo.record]: cloud_alpha = 1.000000; 13:41:28.470 [planetinfo.record]: cloud_color = 0.527699, 0.945312, 0.376648, 1; 13:41:28.470 [planetinfo.record]: cloud_fraction = 0.590000; 13:41:28.470 [planetinfo.record]: land_color = 0.640625, 0.619081, 0.545532, 1; 13:41:28.470 [planetinfo.record]: land_fraction = 0.690000; 13:41:28.470 [planetinfo.record]: polar_cloud_color = 0.669883, 0.925391, 0.577465, 1; 13:41:28.470 [planetinfo.record]: polar_land_color = 0.935938, 0.928069, 0.901205, 1; 13:41:28.470 [planetinfo.record]: polar_sea_color = 0.775371, 0.942969, 0.872263, 1; 13:41:28.470 [planetinfo.record]: sea_color = 0.164856, 0.570312, 0.399261, 1; 13:41:28.470 [planetinfo.record]: rotation_speed = 0.002325 13:41:28.470 [planetinfo.record]: planet zpos = 701550.000000 13:41:28.471 [planetinfo.record]: sun_radius = 122029.763947 13:41:28.471 [planetinfo.record]: sun_vector = 0.267 -0.820 0.506 13:41:28.471 [planetinfo.record]: sun_distance = 935400 13:41:28.471 [planetinfo.record]: corona_flare = 0.029869 13:41:28.471 [planetinfo.record]: corona_hues = 0.916573 13:41:28.471 [planetinfo.record]: sun_color = 0.798398, 0.556329, 0.396528, 1 13:41:28.471 [planetinfo.record]: corona_shimmer = 0.480504 13:41:28.471 [planetinfo.record]: station_vector = 0.806 -0.551 0.217 13:41:28.475 [planetinfo.record]: station = coriolis 13:41:32.695 [PLANETINFO OVER]: Done 13:41:32.696 [PLANETINFO LOGGING]: 5 59 13:41:33.700 [planetinfo.record]: seed = 33 200 168 120 13:41:33.700 [planetinfo.record]: coordinates = 200 7 13:41:33.700 [planetinfo.record]: government = 4; 13:41:33.701 [planetinfo.record]: economy = 7; 13:41:33.701 [planetinfo.record]: techlevel = 2; 13:41:33.701 [planetinfo.record]: population = 20; 13:41:33.701 [planetinfo.record]: productivity = 3840; 13:41:33.701 [planetinfo.record]: name = "Edsous"; 13:41:33.701 [planetinfo.record]: inhabitant = "Blue Insect"; 13:41:33.701 [planetinfo.record]: inhabitants = "Blue Insects"; 13:41:33.701 [planetinfo.record]: description = "The planet Edsous is mildly notable for Edsousian Lo water."; 13:41:33.736 [planetinfo.record]: air_color = 0.787233, 0.62824, 0.871523, 1; 13:41:33.736 [planetinfo.record]: cloud_alpha = 1.000000; 13:41:33.736 [planetinfo.record]: cloud_color = 0.617188, 0.470123, 0.47357, 1; 13:41:33.736 [planetinfo.record]: cloud_fraction = 0.530000; 13:41:33.736 [planetinfo.record]: land_color = 0.66, 0.190781, 0.56469, 1; 13:41:33.736 [planetinfo.record]: land_fraction = 0.400000; 13:41:33.736 [planetinfo.record]: polar_cloud_color = 0.777734, 0.66191, 0.664624, 1; 13:41:33.736 [planetinfo.record]: polar_land_color = 0.934, 0.767996, 0.90028, 1; 13:41:33.736 [planetinfo.record]: polar_sea_color = 0.937109, 0.891331, 0.875428, 1; 13:41:33.736 [planetinfo.record]: sea_color = 0.628906, 0.506015, 0.463327, 1; 13:41:33.736 [planetinfo.record]: rotation_speed = 0.001062 13:41:33.736 [planetinfo.record]: planet zpos = 557040.000000 13:41:33.737 [planetinfo.record]: sun_radius = 126706.633301 13:41:33.737 [planetinfo.record]: sun_vector = -0.010 0.213 -0.977 13:41:33.737 [planetinfo.record]: sun_distance = 1063440 13:41:33.737 [planetinfo.record]: corona_flare = 0.099609 13:41:33.737 [planetinfo.record]: corona_hues = 0.595650 13:41:33.737 [planetinfo.record]: sun_color = 0.732285, 0.675247, 0.564871, 1 13:41:33.737 [planetinfo.record]: corona_shimmer = 0.434324 13:41:33.737 [planetinfo.record]: station_vector = 0.745 0.668 0.006 13:41:33.737 [planetinfo.record]: station = coriolis 13:41:38.679 [PLANETINFO OVER]: Done 13:41:38.680 [PLANETINFO LOGGING]: 5 60 13:41:39.683 [planetinfo.record]: seed = 17 73 122 25 13:41:39.683 [planetinfo.record]: coordinates = 73 136 13:41:39.683 [planetinfo.record]: government = 2; 13:41:39.683 [planetinfo.record]: economy = 0; 13:41:39.683 [planetinfo.record]: techlevel = 9; 13:41:39.683 [planetinfo.record]: population = 39; 13:41:39.683 [planetinfo.record]: productivity = 18720; 13:41:39.683 [planetinfo.record]: name = "Ormadi"; 13:41:39.683 [planetinfo.record]: inhabitant = "Human Colonial"; 13:41:39.683 [planetinfo.record]: inhabitants = "Human Colonials"; 13:41:39.683 [planetinfo.record]: description = "The planet Ormadi is very famous for its exotic goat meat but scourged by dreadful solar activity."; 13:41:39.708 [planetinfo.record]: air_color = 0.639388, 0.799047, 0.978838, 1; 13:41:39.708 [planetinfo.record]: cloud_alpha = 1.000000; 13:41:39.708 [planetinfo.record]: cloud_color = 0.524773, 0.939453, 0.774229, 1; 13:41:39.709 [planetinfo.record]: cloud_fraction = 0.220000; 13:41:39.709 [planetinfo.record]: land_color = 0.335156, 0.66, 0.553411, 1; 13:41:39.709 [planetinfo.record]: land_fraction = 0.260000; 13:41:39.709 [planetinfo.record]: polar_cloud_color = 0.668186, 0.922754, 0.821324, 1; 13:41:39.709 [planetinfo.record]: polar_land_color = 0.819074, 0.934, 0.89629, 1; 13:41:39.709 [planetinfo.record]: polar_sea_color = 0.934961, 0.905153, 0.873421, 1; 13:41:39.709 [planetinfo.record]: sea_color = 0.650391, 0.567448, 0.479155, 1; 13:41:39.709 [planetinfo.record]: rotation_speed = 0.004983 13:41:39.709 [planetinfo.record]: planet zpos = 623160.000000 13:41:39.709 [planetinfo.record]: sun_radius = 95486.826324 13:41:39.709 [planetinfo.record]: sun_vector = 0.541 0.310 -0.782 13:41:39.709 [planetinfo.record]: sun_distance = 1194390 13:41:39.709 [planetinfo.record]: corona_flare = 0.010576 13:41:39.709 [planetinfo.record]: corona_hues = 0.623909 13:41:39.709 [planetinfo.record]: sun_color = 0.668803, 0.676768, 0.684164, 1 13:41:39.710 [planetinfo.record]: corona_shimmer = 0.419672 13:41:39.710 [planetinfo.record]: station_vector = -0.710 -0.704 0.013 13:41:39.710 [planetinfo.record]: station = coriolis 13:41:44.884 [PLANETINFO OVER]: Done 13:41:44.884 [PLANETINFO LOGGING]: 5 61 13:41:45.888 [planetinfo.record]: seed = 77 82 32 139 13:41:45.888 [planetinfo.record]: coordinates = 82 77 13:41:45.888 [planetinfo.record]: government = 1; 13:41:45.888 [planetinfo.record]: economy = 7; 13:41:45.888 [planetinfo.record]: techlevel = 3; 13:41:45.888 [planetinfo.record]: population = 21; 13:41:45.888 [planetinfo.record]: productivity = 2520; 13:41:45.888 [planetinfo.record]: name = "Mamaus"; 13:41:45.888 [planetinfo.record]: inhabitant = "Human Colonial"; 13:41:45.888 [planetinfo.record]: inhabitants = "Human Colonials"; 13:41:45.888 [planetinfo.record]: description = "This world is very notable for the Mamausian edible moth."; 13:41:45.920 [planetinfo.record]: air_color = 0.481069, 0.70921, 0.836402, 1; 13:41:45.920 [planetinfo.record]: cloud_alpha = 1.000000; 13:41:45.920 [planetinfo.record]: cloud_color = 0.165909, 0.511719, 0.195627, 1; 13:41:45.920 [planetinfo.record]: cloud_fraction = 0.150000; 13:41:45.920 [planetinfo.record]: land_color = 0.607422, 0.583898, 0.543358, 1; 13:41:45.920 [planetinfo.record]: land_fraction = 0.530000; 13:41:45.920 [planetinfo.record]: polar_cloud_color = 0.421833, 0.730273, 0.448339, 1; 13:41:45.920 [planetinfo.record]: polar_land_color = 0.939258, 0.930164, 0.914492, 1; 13:41:45.920 [planetinfo.record]: polar_sea_color = 0.791309, 0.906836, 0.784626, 1; 13:41:45.920 [planetinfo.record]: sea_color = 0.456893, 0.931641, 0.429428, 1; 13:41:45.920 [planetinfo.record]: rotation_speed = 0.001833 13:41:45.920 [planetinfo.record]: planet zpos = 685680.000000 13:41:45.920 [planetinfo.record]: sun_radius = 172352.919312 13:41:45.920 [planetinfo.record]: sun_vector = -0.322 -0.930 -0.178 13:41:45.920 [planetinfo.record]: sun_distance = 1028520 13:41:45.920 [planetinfo.record]: corona_flare = 0.022545 13:41:45.920 [planetinfo.record]: corona_hues = 0.608963 13:41:45.920 [planetinfo.record]: sun_color = 0.745691, 0.64136, 0.309906, 1 13:41:45.921 [planetinfo.record]: corona_shimmer = 0.591961 13:41:45.921 [planetinfo.record]: station_vector = -0.556 0.815 0.163 13:41:45.921 [planetinfo.record]: station = coriolis 13:41:51.174 [PLANETINFO OVER]: Done 13:41:51.174 [PLANETINFO LOGGING]: 5 62 13:41:52.179 [planetinfo.record]: seed = 197 190 106 242 13:41:52.179 [planetinfo.record]: coordinates = 190 8 13:41:52.179 [planetinfo.record]: government = 0; 13:41:52.179 [planetinfo.record]: economy = 2; 13:41:52.179 [planetinfo.record]: techlevel = 7; 13:41:52.179 [planetinfo.record]: population = 31; 13:41:52.179 [planetinfo.record]: productivity = 7936; 13:41:52.180 [planetinfo.record]: name = "Enorar"; 13:41:52.180 [planetinfo.record]: inhabitant = "Human Colonial"; 13:41:52.180 [planetinfo.record]: inhabitants = "Human Colonials"; 13:41:52.180 [planetinfo.record]: description = "This planet is beset by lethal disease."; 13:41:52.185 [planetinfo.record]: air_color = 0.568016, 0.928758, 0.815755, 1; 13:41:52.186 [planetinfo.record]: cloud_alpha = 1.000000; 13:41:52.186 [planetinfo.record]: cloud_color = 0.789062, 0.373557, 0.342133, 1; 13:41:52.186 [planetinfo.record]: cloud_fraction = 0.250000; 13:41:52.186 [planetinfo.record]: land_color = 0.160217, 0.546875, 0.275006, 1; 13:41:52.186 [planetinfo.record]: land_fraction = 0.560000; 13:41:52.186 [planetinfo.record]: polar_cloud_color = 0.855078, 0.573661, 0.552377, 1; 13:41:52.186 [planetinfo.record]: polar_land_color = 0.778221, 0.945312, 0.827826, 1; 13:41:52.186 [planetinfo.record]: polar_sea_color = 0.883688, 0.923437, 0.867526, 1; 13:41:52.186 [planetinfo.record]: sea_color = 0.6338, 0.765625, 0.5802, 1; 13:41:52.186 [planetinfo.record]: rotation_speed = 0.000256 13:41:52.186 [planetinfo.record]: planet zpos = 527700.000000 13:41:52.186 [planetinfo.record]: sun_radius = 97779.421692 13:41:52.186 [planetinfo.record]: sun_vector = -0.095 0.627 -0.773 13:41:52.186 [planetinfo.record]: sun_distance = 598060 13:41:52.187 [planetinfo.record]: corona_flare = 0.016658 13:41:52.187 [planetinfo.record]: corona_hues = 0.678391 13:41:52.187 [planetinfo.record]: sun_color = 0.709155, 0.446708, 0.356442, 1 13:41:52.187 [planetinfo.record]: corona_shimmer = 0.383328 13:41:52.187 [planetinfo.record]: station_vector = 0.278 0.907 0.316 13:41:52.187 [planetinfo.record]: station = coriolis 13:41:56.615 [PLANETINFO OVER]: Done 13:41:56.616 [PLANETINFO LOGGING]: 5 63 13:41:57.620 [planetinfo.record]: seed = 9 23 168 59 13:41:57.620 [planetinfo.record]: coordinates = 23 106 13:41:57.620 [planetinfo.record]: government = 1; 13:41:57.620 [planetinfo.record]: economy = 2; 13:41:57.620 [planetinfo.record]: techlevel = 9; 13:41:57.620 [planetinfo.record]: population = 40; 13:41:57.620 [planetinfo.record]: productivity = 12800; 13:41:57.620 [planetinfo.record]: name = "Aniseres"; 13:41:57.620 [planetinfo.record]: inhabitant = "Red Furry Rodent"; 13:41:57.620 [planetinfo.record]: inhabitants = "Red Furry Rodents"; 13:41:57.621 [planetinfo.record]: description = "The planet Aniseres is cursed by deadly civil war."; 13:41:57.624 [planetinfo.record]: air_color = 0.642048, 0.587541, 0.84876, 1; 13:41:57.624 [planetinfo.record]: cloud_alpha = 1.000000; 13:41:57.624 [planetinfo.record]: cloud_color = 0.484881, 0.370888, 0.548828, 1; 13:41:57.624 [planetinfo.record]: cloud_fraction = 0.550000; 13:41:57.624 [planetinfo.record]: land_color = 0.66, 0.629647, 0.306797, 1; 13:41:57.624 [planetinfo.record]: land_fraction = 0.580000; 13:41:57.624 [planetinfo.record]: polar_cloud_color = 0.692576, 0.595609, 0.746973, 1; 13:41:57.624 [planetinfo.record]: polar_land_color = 0.934, 0.923261, 0.809041, 1; 13:41:57.624 [planetinfo.record]: polar_sea_color = 0.936719, 0.897095, 0.875612, 1; 13:41:57.624 [planetinfo.record]: sea_color = 0.632812, 0.52574, 0.467688, 1; 13:41:57.624 [planetinfo.record]: rotation_speed = 0.000069 13:41:57.624 [planetinfo.record]: planet zpos = 678600.000000 13:41:57.624 [planetinfo.record]: sun_radius = 141446.619415 13:41:57.624 [planetinfo.record]: sun_vector = 0.015 -0.406 -0.914 13:41:57.624 [planetinfo.record]: sun_distance = 1017900 13:41:57.624 [planetinfo.record]: corona_flare = 0.007597 13:41:57.624 [planetinfo.record]: corona_hues = 0.744339 13:41:57.625 [planetinfo.record]: sun_color = 0.771606, 0.75709, 0.749751, 1 13:41:57.625 [planetinfo.record]: corona_shimmer = 0.453141 13:41:57.625 [planetinfo.record]: station_vector = 0.649 -0.748 0.135 13:41:57.625 [planetinfo.record]: station = coriolis 13:42:01.493 [PLANETINFO OVER]: Done 13:42:01.494 [PLANETINFO LOGGING]: 5 64 13:42:02.510 [planetinfo.record]: seed = 137 9 106 215 13:42:02.510 [planetinfo.record]: coordinates = 9 16 13:42:02.510 [planetinfo.record]: government = 1; 13:42:02.510 [planetinfo.record]: economy = 2; 13:42:02.510 [planetinfo.record]: techlevel = 7; 13:42:02.510 [planetinfo.record]: population = 32; 13:42:02.510 [planetinfo.record]: productivity = 10240; 13:42:02.510 [planetinfo.record]: name = "Tiatenqu"; 13:42:02.510 [planetinfo.record]: inhabitant = "Human Colonial"; 13:42:02.510 [planetinfo.record]: inhabitants = "Human Colonials"; 13:42:02.510 [planetinfo.record]: description = "This world is a tedious little planet."; 13:42:02.518 [planetinfo.record]: air_color = 0.662205, 0.862161, 0.920303, 1; 13:42:02.518 [planetinfo.record]: cloud_alpha = 1.000000; 13:42:02.518 [planetinfo.record]: cloud_color = 0.636518, 0.763672, 0.57872, 1; 13:42:02.518 [planetinfo.record]: cloud_fraction = 0.250000; 13:42:02.518 [planetinfo.record]: land_color = 0.66, 0.381563, 0.394614, 1; 13:42:02.518 [planetinfo.record]: land_fraction = 0.560000; 13:42:02.518 [planetinfo.record]: polar_cloud_color = 0.755858, 0.843652, 0.715951, 1; 13:42:02.519 [planetinfo.record]: polar_land_color = 0.934, 0.835492, 0.84011, 1; 13:42:02.519 [planetinfo.record]: polar_sea_color = 0.920943, 0.934961, 0.882187, 1; 13:42:02.519 [planetinfo.record]: sea_color = 0.611385, 0.650391, 0.503545, 1; 13:42:02.519 [planetinfo.record]: rotation_speed = 0.002873 13:42:02.519 [planetinfo.record]: planet zpos = 554040.000000 13:42:02.519 [planetinfo.record]: sun_radius = 116430.319061 13:42:02.519 [planetinfo.record]: sun_vector = 0.947 -0.099 -0.305 13:42:02.519 [planetinfo.record]: sun_distance = 923400 13:42:02.519 [planetinfo.record]: corona_flare = 0.099452 13:42:02.519 [planetinfo.record]: corona_hues = 0.586052 13:42:02.519 [planetinfo.record]: sun_color = 0.45472, 0.483807, 0.733136, 1 13:42:02.519 [planetinfo.record]: corona_shimmer = 0.808149 13:42:02.519 [planetinfo.record]: station_vector = -0.886 0.236 0.398 13:42:02.519 [planetinfo.record]: station = coriolis 13:42:06.634 [PLANETINFO OVER]: Done 13:42:06.635 [PLANETINFO LOGGING]: 5 65 13:42:07.639 [planetinfo.record]: seed = 213 154 64 94 13:42:07.639 [planetinfo.record]: coordinates = 154 210 13:42:07.639 [planetinfo.record]: government = 2; 13:42:07.639 [planetinfo.record]: economy = 2; 13:42:07.639 [planetinfo.record]: techlevel = 8; 13:42:07.639 [planetinfo.record]: population = 37; 13:42:07.639 [planetinfo.record]: productivity = 14208; 13:42:07.639 [planetinfo.record]: name = "Rimaza"; 13:42:07.639 [planetinfo.record]: inhabitant = "Human Colonial"; 13:42:07.639 [planetinfo.record]: inhabitants = "Human Colonials"; 13:42:07.639 [planetinfo.record]: description = "This world is ravaged by dreadful civil war."; 13:42:07.668 [planetinfo.record]: air_color = 0.625682, 0.534894, 0.901441, 1; 13:42:07.668 [planetinfo.record]: cloud_alpha = 1.000000; 13:42:07.668 [planetinfo.record]: cloud_color = 0.600679, 0.267899, 0.707031, 1; 13:42:07.668 [planetinfo.record]: cloud_fraction = 0.630000; 13:42:07.668 [planetinfo.record]: land_color = 0.66, 0.503137, 0.203672, 1; 13:42:07.668 [planetinfo.record]: land_fraction = 0.320000; 13:42:07.668 [planetinfo.record]: polar_cloud_color = 0.741246, 0.500566, 0.818164, 1; 13:42:07.668 [planetinfo.record]: polar_land_color = 0.934, 0.878504, 0.772557, 1; 13:42:07.668 [planetinfo.record]: polar_sea_color = 0.944141, 0.888082, 0.892024, 1; 13:42:07.668 [planetinfo.record]: sea_color = 0.558594, 0.425928, 0.435256, 1; 13:42:07.668 [planetinfo.record]: rotation_speed = 0.000886 13:42:07.668 [planetinfo.record]: planet zpos = 786480.000000 13:42:07.668 [planetinfo.record]: sun_radius = 145693.891907 13:42:07.668 [planetinfo.record]: sun_vector = 0.011 -0.938 0.347 13:42:07.668 [planetinfo.record]: sun_distance = 1179720 13:42:07.668 [planetinfo.record]: corona_flare = 0.057619 13:42:07.668 [planetinfo.record]: corona_hues = 0.546974 13:42:07.669 [planetinfo.record]: sun_color = 0.791245, 0.640089, 0.309288, 1 13:42:07.669 [planetinfo.record]: corona_shimmer = 0.537083 13:42:07.669 [planetinfo.record]: station_vector = -0.476 0.506 0.719 13:42:07.669 [planetinfo.record]: station = coriolis 13:42:12.391 [PLANETINFO OVER]: Done 13:42:12.392 [PLANETINFO LOGGING]: 5 66 13:42:13.405 [planetinfo.record]: seed = 221 237 122 125 13:42:13.405 [planetinfo.record]: coordinates = 237 196 13:42:13.405 [planetinfo.record]: government = 3; 13:42:13.405 [planetinfo.record]: economy = 4; 13:42:13.405 [planetinfo.record]: techlevel = 6; 13:42:13.405 [planetinfo.record]: population = 32; 13:42:13.405 [planetinfo.record]: productivity = 10752; 13:42:13.405 [planetinfo.record]: name = "Isaanus"; 13:42:13.405 [planetinfo.record]: inhabitant = "Human Colonial"; 13:42:13.405 [planetinfo.record]: inhabitants = "Human Colonials"; 13:42:13.405 [planetinfo.record]: description = "The world Isaanus is reasonably fabled for its exciting Isaanusian lethal brandy and its exotic night life."; 13:42:13.421 [planetinfo.record]: air_color = 0.803085, 0.561127, 0.931359, 1; 13:42:13.421 [planetinfo.record]: cloud_alpha = 1.000000; 13:42:13.421 [planetinfo.record]: cloud_color = 0.796875, 0.32373, 0.33482, 1; 13:42:13.421 [planetinfo.record]: cloud_fraction = 0.290000; 13:42:13.421 [planetinfo.record]: land_color = 0.66, 0.219141, 0.229473, 1; 13:42:13.421 [planetinfo.record]: land_fraction = 0.350000; 13:42:13.421 [planetinfo.record]: polar_cloud_color = 0.858594, 0.539975, 0.547443, 1; 13:42:13.421 [planetinfo.record]: polar_land_color = 0.934, 0.778029, 0.781685, 1; 13:42:13.421 [planetinfo.record]: polar_sea_color = 0.942383, 0.892498, 0.885877, 1; 13:42:13.421 [planetinfo.record]: sea_color = 0.576172, 0.454175, 0.437981, 1; 13:42:13.421 [planetinfo.record]: rotation_speed = 0.003022 13:42:13.421 [planetinfo.record]: planet zpos = 765720.000000 13:42:13.422 [planetinfo.record]: sun_radius = 190914.932098 13:42:13.422 [planetinfo.record]: sun_vector = 0.325 0.854 -0.406 13:42:13.422 [planetinfo.record]: sun_distance = 1020960 13:42:13.422 [planetinfo.record]: corona_flare = 0.018102 13:42:13.422 [planetinfo.record]: corona_hues = 0.563835 13:42:13.422 [planetinfo.record]: sun_color = 0.418951, 0.599329, 0.775565, 1 13:42:13.422 [planetinfo.record]: corona_shimmer = 0.827788 13:42:13.422 [planetinfo.record]: station_vector = 0.209 0.480 0.852 13:42:13.422 [planetinfo.record]: station = coriolis 13:42:18.925 [PLANETINFO OVER]: Done 13:42:18.925 [PLANETINFO LOGGING]: 5 67 13:42:19.928 [planetinfo.record]: seed = 49 72 232 18 13:42:19.928 [planetinfo.record]: coordinates = 72 155 13:42:19.928 [planetinfo.record]: government = 6; 13:42:19.928 [planetinfo.record]: economy = 3; 13:42:19.928 [planetinfo.record]: techlevel = 7; 13:42:19.928 [planetinfo.record]: population = 38; 13:42:19.928 [planetinfo.record]: productivity = 21280; 13:42:19.928 [planetinfo.record]: name = "Enveat"; 13:42:19.928 [planetinfo.record]: inhabitant = "Green Bony Humanoid"; 13:42:19.928 [planetinfo.record]: inhabitants = "Green Bony Humanoids"; 13:42:19.928 [planetinfo.record]: description = "The planet Enveat is famous for Enveatian wolf meat but beset by deadly edible moths."; 13:42:19.939 [planetinfo.record]: air_color = 0.651007, 0.855264, 0.792874, 1; 13:42:19.939 [planetinfo.record]: cloud_alpha = 1.000000; 13:42:19.939 [planetinfo.record]: cloud_color = 0.568359, 0.507999, 0.501755, 1; 13:42:19.939 [planetinfo.record]: cloud_fraction = 0.330000; 13:42:19.939 [planetinfo.record]: land_color = 0.20285, 0.564453, 0.539028, 1; 13:42:19.939 [planetinfo.record]: land_fraction = 0.670000; 13:42:19.939 [planetinfo.record]: polar_cloud_color = 0.755762, 0.705598, 0.700408, 1; 13:42:19.940 [planetinfo.record]: polar_land_color = 0.792439, 0.943555, 0.932929, 1; 13:42:19.940 [planetinfo.record]: polar_sea_color = 0.861809, 0.919922, 0.85524, 1; 13:42:19.940 [planetinfo.record]: sea_color = 0.598435, 0.800781, 0.575562, 1; 13:42:19.940 [planetinfo.record]: rotation_speed = 0.004121 13:42:19.940 [planetinfo.record]: planet zpos = 340000.000000 13:42:19.940 [planetinfo.record]: sun_radius = 71202.545166 13:42:19.940 [planetinfo.record]: sun_vector = -0.692 -0.590 0.415 13:42:19.940 [planetinfo.record]: sun_distance = 646000 13:42:19.940 [planetinfo.record]: corona_flare = 0.013538 13:42:19.940 [planetinfo.record]: corona_hues = 0.607330 13:42:19.940 [planetinfo.record]: sun_color = 0.513088, 0.78497, 0.831354, 1 13:42:19.940 [planetinfo.record]: corona_shimmer = 0.385492 13:42:19.940 [planetinfo.record]: station_vector = -0.101 0.805 0.585 13:42:19.941 [planetinfo.record]: station = coriolis 13:42:24.688 [PLANETINFO OVER]: Done 13:42:24.689 [PLANETINFO LOGGING]: 5 68 13:42:25.708 [planetinfo.record]: seed = 65 90 154 161 13:42:25.708 [planetinfo.record]: coordinates = 90 81 13:42:25.708 [planetinfo.record]: government = 0; 13:42:25.708 [planetinfo.record]: economy = 3; 13:42:25.708 [planetinfo.record]: techlevel = 6; 13:42:25.708 [planetinfo.record]: population = 28; 13:42:25.708 [planetinfo.record]: productivity = 6272; 13:42:25.708 [planetinfo.record]: name = "Ledius"; 13:42:25.708 [planetinfo.record]: inhabitant = "Large Harmless Bony Bird"; 13:42:25.708 [planetinfo.record]: inhabitants = "Large Harmless Bony Birds"; 13:42:25.708 [planetinfo.record]: description = "This world is mildly famous for its unusual sit coms and the Lediusian spotted shrew."; 13:42:25.720 [planetinfo.record]: air_color = 0.647898, 0.858516, 0.794183, 1; 13:42:25.720 [planetinfo.record]: cloud_alpha = 1.000000; 13:42:25.720 [planetinfo.record]: cloud_color = 0.578125, 0.506495, 0.499084, 1; 13:42:25.720 [planetinfo.record]: cloud_fraction = 0.350000; 13:42:25.720 [planetinfo.record]: land_color = 0.621328, 0.622839, 0.66, 1; 13:42:25.720 [planetinfo.record]: land_fraction = 0.360000; 13:42:25.720 [planetinfo.record]: polar_cloud_color = 0.760156, 0.701291, 0.695202, 1; 13:42:25.720 [planetinfo.record]: polar_land_color = 0.920318, 0.920853, 0.934, 1; 13:42:25.720 [planetinfo.record]: polar_sea_color = 0.90432, 0.933594, 0.882264, 1; 13:42:25.720 [planetinfo.record]: sea_color = 0.580773, 0.664062, 0.518021, 1; 13:42:25.720 [planetinfo.record]: rotation_speed = 0.000673 13:42:25.720 [planetinfo.record]: planet zpos = 411060.000000 13:42:25.720 [planetinfo.record]: sun_radius = 96153.054199 13:42:25.720 [planetinfo.record]: sun_vector = 0.824 0.157 0.544 13:42:25.720 [planetinfo.record]: sun_distance = 632400 13:42:25.720 [planetinfo.record]: corona_flare = 0.007065 13:42:25.720 [planetinfo.record]: corona_hues = 0.627281 13:42:25.720 [planetinfo.record]: sun_color = 0.304877, 0.386035, 0.758594, 1 13:42:25.720 [planetinfo.record]: corona_shimmer = 0.289383 13:42:25.720 [planetinfo.record]: station_vector = -0.535 -0.834 0.131 13:42:25.720 [planetinfo.record]: station = coriolis 13:42:31.301 [PLANETINFO OVER]: Done 13:42:31.302 [PLANETINFO LOGGING]: 5 69 13:42:32.307 [planetinfo.record]: seed = 157 55 160 205 13:42:32.307 [planetinfo.record]: coordinates = 55 72 13:42:32.307 [planetinfo.record]: government = 3; 13:42:32.307 [planetinfo.record]: economy = 0; 13:42:32.307 [planetinfo.record]: techlevel = 12; 13:42:32.308 [planetinfo.record]: population = 52; 13:42:32.308 [planetinfo.record]: productivity = 29120; 13:42:32.308 [planetinfo.record]: name = "Direbea"; 13:42:32.308 [planetinfo.record]: inhabitant = "Rodent"; 13:42:32.308 [planetinfo.record]: inhabitants = "Rodents"; 13:42:32.308 [planetinfo.record]: description = "Direbea is mildly well known for Direbeaian wolf meat and its unusual oceans."; 13:42:32.332 [planetinfo.record]: air_color = 0.755105, 0.752435, 0.94957, 1; 13:42:32.332 [planetinfo.record]: cloud_alpha = 1.000000; 13:42:32.332 [planetinfo.record]: cloud_color = 0.832695, 0.831604, 0.851562, 1; 13:42:32.332 [planetinfo.record]: cloud_fraction = 0.550000; 13:42:32.332 [planetinfo.record]: land_color = 0.14502, 0.580078, 0.345554, 1; 13:42:32.332 [planetinfo.record]: land_fraction = 0.280000; 13:42:32.332 [planetinfo.record]: polar_cloud_color = 0.870973, 0.870266, 0.883203, 1; 13:42:32.332 [planetinfo.record]: polar_land_color = 0.765369, 0.941992, 0.846781, 1; 13:42:32.332 [planetinfo.record]: polar_sea_color = 0.942773, 0.913868, 0.890663, 1; 13:42:32.332 [planetinfo.record]: sea_color = 0.572266, 0.502084, 0.445741, 1; 13:42:32.332 [planetinfo.record]: rotation_speed = 0.004985 13:42:32.332 [planetinfo.record]: planet zpos = 557910.000000 13:42:32.332 [planetinfo.record]: sun_radius = 134593.857880 13:42:32.332 [planetinfo.record]: sun_vector = 0.290 -0.117 -0.950 13:42:32.332 [planetinfo.record]: sun_distance = 1239800 13:42:32.332 [planetinfo.record]: corona_flare = 0.029672 13:42:32.332 [planetinfo.record]: corona_hues = 0.671761 13:42:32.333 [planetinfo.record]: sun_color = 0.786884, 0.671326, 0.381075, 1 13:42:32.333 [planetinfo.record]: corona_shimmer = 0.600081 13:42:32.333 [planetinfo.record]: station_vector = 0.365 0.326 0.872 13:42:32.333 [planetinfo.record]: station = dodecahedron 13:42:37.501 [PLANETINFO OVER]: Done 13:42:37.502 [PLANETINFO LOGGING]: 5 70 13:42:38.506 [planetinfo.record]: seed = 53 111 202 16 13:42:38.506 [planetinfo.record]: coordinates = 111 83 13:42:38.506 [planetinfo.record]: government = 6; 13:42:38.506 [planetinfo.record]: economy = 3; 13:42:38.506 [planetinfo.record]: techlevel = 10; 13:42:38.506 [planetinfo.record]: population = 50; 13:42:38.506 [planetinfo.record]: productivity = 28000; 13:42:38.506 [planetinfo.record]: name = "Erbebeti"; 13:42:38.506 [planetinfo.record]: inhabitant = "Green Fat Bird"; 13:42:38.506 [planetinfo.record]: inhabitants = "Green Fat Birds"; 13:42:38.506 [planetinfo.record]: description = "The world Erbebeti is a dull world."; 13:42:38.523 [planetinfo.record]: air_color = 0.537185, 0.477867, 0.93201, 1; 13:42:38.523 [planetinfo.record]: cloud_alpha = 1.000000; 13:42:38.523 [planetinfo.record]: cloud_color = 0.38937, 0.109215, 0.798828, 1; 13:42:38.523 [planetinfo.record]: cloud_fraction = 0.260000; 13:42:38.523 [planetinfo.record]: land_color = 0.634219, 0.646707, 0.66, 1; 13:42:38.523 [planetinfo.record]: land_fraction = 0.560000; 13:42:38.523 [planetinfo.record]: polar_cloud_color = 0.584133, 0.395743, 0.859473, 1; 13:42:38.523 [planetinfo.record]: polar_land_color = 0.924879, 0.929297, 0.934, 1; 13:42:38.523 [planetinfo.record]: polar_sea_color = 0.885298, 0.923633, 0.865906, 1; 13:42:38.523 [planetinfo.record]: sea_color = 0.63689, 0.763672, 0.572754, 1; 13:42:38.523 [planetinfo.record]: rotation_speed = 0.000766 13:42:38.523 [planetinfo.record]: planet zpos = 439050.000000 13:42:38.523 [planetinfo.record]: sun_radius = 89439.733734 13:42:38.523 [planetinfo.record]: sun_vector = -0.518 -0.225 -0.825 13:42:38.523 [planetinfo.record]: sun_distance = 585400 13:42:38.523 [planetinfo.record]: corona_flare = 0.044577 13:42:38.523 [planetinfo.record]: corona_hues = 0.867783 13:42:38.523 [planetinfo.record]: sun_color = 0.396952, 0.45368, 0.829608, 1 13:42:38.523 [planetinfo.record]: corona_shimmer = 0.277197 13:42:38.523 [planetinfo.record]: station_vector = -0.819 0.556 0.143 13:42:38.523 [planetinfo.record]: station = coriolis 13:42:43.549 [PLANETINFO OVER]: Done 13:42:43.551 [PLANETINFO LOGGING]: 5 71 13:42:44.566 [planetinfo.record]: seed = 153 55 104 94 13:42:44.566 [planetinfo.record]: coordinates = 55 83 13:42:44.566 [planetinfo.record]: government = 3; 13:42:44.566 [planetinfo.record]: economy = 3; 13:42:44.567 [planetinfo.record]: techlevel = 9; 13:42:44.567 [planetinfo.record]: population = 43; 13:42:44.567 [planetinfo.record]: productivity = 16856; 13:42:44.567 [planetinfo.record]: name = "Riesonso"; 13:42:44.567 [planetinfo.record]: inhabitant = "Human Colonial"; 13:42:44.567 [planetinfo.record]: inhabitants = "Human Colonials"; 13:42:44.567 [planetinfo.record]: description = "The world Riesonso is a boring planet."; 13:42:44.591 [planetinfo.record]: air_color = 0.599946, 0.911197, 0.833131, 1; 13:42:44.591 [planetinfo.record]: cloud_alpha = 1.000000; 13:42:44.591 [planetinfo.record]: cloud_color = 0.736328, 0.503641, 0.422813, 1; 13:42:44.591 [planetinfo.record]: cloud_fraction = 0.240000; 13:42:44.592 [planetinfo.record]: land_color = 0.635024, 0.634219, 0.66, 1; 13:42:44.592 [planetinfo.record]: land_fraction = 0.290000; 13:42:44.592 [planetinfo.record]: polar_cloud_color = 0.831348, 0.667152, 0.610115, 1; 13:42:44.592 [planetinfo.record]: polar_land_color = 0.925164, 0.924879, 0.934, 1; 13:42:44.592 [planetinfo.record]: polar_sea_color = 0.92793, 0.828816, 0.826437, 1; 13:42:44.592 [planetinfo.record]: sea_color = 0.720703, 0.412786, 0.405396, 1; 13:42:44.592 [planetinfo.record]: rotation_speed = 0.003215 13:42:44.592 [planetinfo.record]: planet zpos = 645500.000000 13:42:44.592 [planetinfo.record]: sun_radius = 154540.398407 13:42:44.592 [planetinfo.record]: sun_vector = 0.530 0.458 -0.714 13:42:44.592 [planetinfo.record]: sun_distance = 1226450 13:42:44.592 [planetinfo.record]: corona_flare = 0.048296 13:42:44.592 [planetinfo.record]: corona_hues = 0.844963 13:42:44.592 [planetinfo.record]: sun_color = 0.667133, 0.782714, 0.785876, 1 13:42:44.592 [planetinfo.record]: corona_shimmer = 0.454833 13:42:44.593 [planetinfo.record]: station_vector = 0.021 -0.442 0.897 13:42:44.593 [planetinfo.record]: station = coriolis 13:42:49.669 [PLANETINFO OVER]: Done 13:42:49.670 [PLANETINFO LOGGING]: 5 72 13:42:50.673 [planetinfo.record]: seed = 57 199 10 48 13:42:50.673 [planetinfo.record]: coordinates = 199 127 13:42:50.673 [planetinfo.record]: government = 7; 13:42:50.673 [planetinfo.record]: economy = 7; 13:42:50.673 [planetinfo.record]: techlevel = 7; 13:42:50.673 [planetinfo.record]: population = 43; 13:42:50.673 [planetinfo.record]: productivity = 11352; 13:42:50.673 [planetinfo.record]: name = "Ervedira"; 13:42:50.673 [planetinfo.record]: inhabitant = "Human Colonial"; 13:42:50.673 [planetinfo.record]: inhabitants = "Human Colonials"; 13:42:50.673 [planetinfo.record]: description = "This world is reasonably well known for its vast oceans but ravaged by vicious killer cats."; 13:42:50.696 [planetinfo.record]: air_color = 0.581581, 0.94892, 0.922726, 1; 13:42:50.696 [planetinfo.record]: cloud_alpha = 1.000000; 13:42:50.696 [planetinfo.record]: cloud_color = 0.849609, 0.752535, 0.371704, 1; 13:42:50.696 [planetinfo.record]: cloud_fraction = 0.400000; 13:42:50.696 [planetinfo.record]: land_color = 0.592969, 0.594016, 0.66, 1; 13:42:50.696 [planetinfo.record]: land_fraction = 0.200000; 13:42:50.696 [planetinfo.record]: polar_cloud_color = 0.882324, 0.819316, 0.572132, 1; 13:42:50.696 [planetinfo.record]: polar_land_color = 0.910285, 0.910656, 0.934, 1; 13:42:50.696 [planetinfo.record]: polar_sea_color = 0.94082, 0.932996, 0.890747, 1; 13:42:50.696 [planetinfo.record]: sea_color = 0.591797, 0.572111, 0.465809, 1; 13:42:50.696 [planetinfo.record]: rotation_speed = 0.003450 13:42:50.696 [planetinfo.record]: planet zpos = 361800.000000 13:42:50.696 [planetinfo.record]: sun_radius = 70489.242554 13:42:50.696 [planetinfo.record]: sun_vector = -0.053 -0.773 0.632 13:42:50.696 [planetinfo.record]: sun_distance = 603000 13:42:50.696 [planetinfo.record]: corona_flare = 0.081709 13:42:50.696 [planetinfo.record]: corona_hues = 0.898056 13:42:50.696 [planetinfo.record]: sun_color = 0.677481, 0.688171, 0.738611, 1 13:42:50.696 [planetinfo.record]: corona_shimmer = 0.279288 13:42:50.697 [planetinfo.record]: station_vector = -0.295 -0.887 0.356 13:42:50.697 [planetinfo.record]: station = coriolis 13:42:55.520 [PLANETINFO OVER]: Done 13:42:55.521 [PLANETINFO LOGGING]: 5 73 13:42:56.525 [planetinfo.record]: seed = 165 20 64 249 13:42:56.525 [planetinfo.record]: coordinates = 20 109 13:42:56.525 [planetinfo.record]: government = 4; 13:42:56.525 [planetinfo.record]: economy = 5; 13:42:56.525 [planetinfo.record]: techlevel = 4; 13:42:56.525 [planetinfo.record]: population = 26; 13:42:56.525 [planetinfo.record]: productivity = 8320; 13:42:56.525 [planetinfo.record]: name = "Oranesri"; 13:42:56.525 [planetinfo.record]: inhabitant = "Human Colonial"; 13:42:56.525 [planetinfo.record]: inhabitants = "Human Colonials"; 13:42:56.525 [planetinfo.record]: description = "Oranesri is very notable for its inhabitants’ weird shyness."; 13:42:56.543 [planetinfo.record]: air_color = 0.835074, 0.763109, 0.984041, 1; 13:42:56.543 [planetinfo.record]: cloud_alpha = 1.000000; 13:42:56.543 [planetinfo.record]: cloud_color = 0.954524, 0.884193, 0.955078, 1; 13:42:56.543 [planetinfo.record]: cloud_fraction = 0.360000; 13:42:56.543 [planetinfo.record]: land_color = 0.492662, 0.342064, 0.595703, 1; 13:42:56.543 [planetinfo.record]: land_fraction = 0.370000; 13:42:56.543 [planetinfo.record]: polar_cloud_color = 0.929448, 0.886655, 0.929785, 1; 13:42:56.543 [planetinfo.record]: polar_land_color = 0.899762, 0.840325, 0.94043, 1; 13:42:56.543 [planetinfo.record]: polar_sea_color = 0.928711, 0.878517, 0.845272, 1; 13:42:56.543 [planetinfo.record]: sea_color = 0.712891, 0.558773, 0.456696, 1; 13:42:56.543 [planetinfo.record]: rotation_speed = 0.004062 13:42:56.543 [planetinfo.record]: planet zpos = 514000.000000 13:42:56.543 [planetinfo.record]: sun_radius = 148070.681763 13:42:56.543 [planetinfo.record]: sun_vector = -0.148 -0.864 0.482 13:42:56.543 [planetinfo.record]: sun_distance = 873800 13:42:56.544 [planetinfo.record]: corona_flare = 0.026733 13:42:56.544 [planetinfo.record]: corona_hues = 0.931160 13:42:56.544 [planetinfo.record]: sun_color = 0.834738, 0.524383, 0.499845, 1 13:42:56.544 [planetinfo.record]: corona_shimmer = 0.503449 13:42:56.544 [planetinfo.record]: station_vector = -0.860 0.319 0.399 13:42:56.544 [planetinfo.record]: station = coriolis 13:43:01.998 [PLANETINFO OVER]: Done 13:43:01.998 [PLANETINFO LOGGING]: 5 74 13:43:03.009 [planetinfo.record]: seed = 205 254 90 4 13:43:03.009 [planetinfo.record]: coordinates = 254 137 13:43:03.009 [planetinfo.record]: government = 1; 13:43:03.009 [planetinfo.record]: economy = 3; 13:43:03.009 [planetinfo.record]: techlevel = 7; 13:43:03.009 [planetinfo.record]: population = 33; 13:43:03.009 [planetinfo.record]: productivity = 9240; 13:43:03.009 [planetinfo.record]: name = "Zainer"; 13:43:03.009 [planetinfo.record]: inhabitant = "Human Colonial"; 13:43:03.009 [planetinfo.record]: inhabitants = "Human Colonials"; 13:43:03.009 [planetinfo.record]: description = "Zainer is very fabled for its ancient Thinsea’ tulip plantations."; 13:43:03.022 [planetinfo.record]: air_color = 0.674099, 0.850711, 0.820198, 1; 13:43:03.022 [planetinfo.record]: cloud_alpha = 1.000000; 13:43:03.022 [planetinfo.record]: cloud_color = 0.554688, 0.548086, 0.541687, 1; 13:43:03.022 [planetinfo.record]: cloud_fraction = 0.570000; 13:43:03.022 [planetinfo.record]: land_color = 0.381563, 0.66, 0.562112, 1; 13:43:03.022 [planetinfo.record]: land_fraction = 0.260000; 13:43:03.023 [planetinfo.record]: polar_cloud_color = 0.749609, 0.744033, 0.738629, 1; 13:43:03.023 [planetinfo.record]: polar_land_color = 0.835492, 0.934, 0.899368, 1; 13:43:03.023 [planetinfo.record]: polar_sea_color = 0.944727, 0.925053, 0.897213, 1; 13:43:03.023 [planetinfo.record]: sea_color = 0.552734, 0.506693, 0.44154, 1; 13:43:03.023 [planetinfo.record]: rotation_speed = 0.003595 13:43:03.023 [planetinfo.record]: planet zpos = 491280.000000 13:43:03.023 [planetinfo.record]: sun_radius = 107773.600464 13:43:03.023 [planetinfo.record]: sun_vector = -0.743 0.042 -0.668 13:43:03.023 [planetinfo.record]: sun_distance = 859740 13:43:03.023 [planetinfo.record]: corona_flare = 0.045728 13:43:03.023 [planetinfo.record]: corona_hues = 0.595650 13:43:03.023 [planetinfo.record]: sun_color = 0.690433, 0.51062, 0.39643, 1 13:43:03.023 [planetinfo.record]: corona_shimmer = 0.371389 13:43:03.023 [planetinfo.record]: station_vector = 0.936 -0.292 0.195 13:43:03.023 [planetinfo.record]: station = coriolis 13:43:08.521 [PLANETINFO OVER]: Done 13:43:08.522 [PLANETINFO LOGGING]: 5 75 13:43:09.531 [planetinfo.record]: seed = 65 33 40 62 13:43:09.531 [planetinfo.record]: coordinates = 33 144 13:43:09.531 [planetinfo.record]: government = 0; 13:43:09.531 [planetinfo.record]: economy = 2; 13:43:09.531 [planetinfo.record]: techlevel = 6; 13:43:09.531 [planetinfo.record]: population = 27; 13:43:09.531 [planetinfo.record]: productivity = 6912; 13:43:09.531 [planetinfo.record]: name = "Riare"; 13:43:09.531 [planetinfo.record]: inhabitant = "Human Colonial"; 13:43:09.531 [planetinfo.record]: inhabitants = "Human Colonials"; 13:43:09.531 [planetinfo.record]: description = "The planet Riare is scourged by deadly tree grubs."; 13:43:09.544 [planetinfo.record]: air_color = 0.5216, 0.637246, 0.835102, 1; 13:43:09.544 [planetinfo.record]: cloud_alpha = 1.000000; 13:43:09.544 [planetinfo.record]: cloud_color = 0.240021, 0.507812, 0.482707, 1; 13:43:09.544 [planetinfo.record]: cloud_fraction = 0.380000; 13:43:09.544 [planetinfo.record]: land_color = 0.613594, 0.66, 0.28875, 1; 13:43:09.544 [planetinfo.record]: land_fraction = 0.710000; 13:43:09.544 [planetinfo.record]: polar_cloud_color = 0.488404, 0.728516, 0.706005, 1; 13:43:09.544 [planetinfo.record]: polar_land_color = 0.917582, 0.934, 0.802656, 1; 13:43:09.544 [planetinfo.record]: polar_sea_color = 0.886688, 0.93125, 0.880322, 1; 13:43:09.544 [planetinfo.record]: sea_color = 0.555908, 0.6875, 0.537109, 1; 13:43:09.544 [planetinfo.record]: rotation_speed = 0.002259 13:43:09.544 [planetinfo.record]: planet zpos = 578970.000000 13:43:09.544 [planetinfo.record]: sun_radius = 162447.581329 13:43:09.544 [planetinfo.record]: sun_vector = 0.169 0.978 0.125 13:43:09.544 [planetinfo.record]: sun_distance = 1415260 13:43:09.544 [planetinfo.record]: corona_flare = 0.026877 13:43:09.544 [planetinfo.record]: corona_hues = 0.980591 13:43:09.544 [planetinfo.record]: sun_color = 0.828964, 0.822864, 0.794489, 1 13:43:09.544 [planetinfo.record]: corona_shimmer = 0.330353 13:43:09.545 [planetinfo.record]: station_vector = -0.954 -0.025 0.300 13:43:09.545 [planetinfo.record]: station = coriolis 13:43:13.955 [PLANETINFO OVER]: Done 13:43:13.956 [PLANETINFO LOGGING]: 5 76 13:43:14.960 [planetinfo.record]: seed = 113 124 186 186 13:43:14.960 [planetinfo.record]: coordinates = 124 78 13:43:14.960 [planetinfo.record]: government = 6; 13:43:14.960 [planetinfo.record]: economy = 6; 13:43:14.960 [planetinfo.record]: techlevel = 4; 13:43:14.960 [planetinfo.record]: population = 29; 13:43:14.960 [planetinfo.record]: productivity = 9280; 13:43:14.960 [planetinfo.record]: name = "Qubiisis"; 13:43:14.960 [planetinfo.record]: inhabitant = "Harmless Horned Bird"; 13:43:14.960 [planetinfo.record]: inhabitants = "Harmless Horned Birds"; 13:43:14.960 [planetinfo.record]: description = "The planet Qubiisis is famous for its inhabitants’ ancient loathing of sit coms but ravaged by dreadful civil war."; 13:43:14.967 [planetinfo.record]: air_color = 0.439851, 0.760436, 0.857865, 1; 13:43:14.967 [planetinfo.record]: cloud_alpha = 1.000000; 13:43:14.967 [planetinfo.record]: cloud_color = 0.222553, 0.576172, 0.0787735, 1; 13:43:14.967 [planetinfo.record]: cloud_fraction = 0.540000; 13:43:14.967 [planetinfo.record]: land_color = 0.188644, 0.508153, 0.619141, 1; 13:43:14.967 [planetinfo.record]: land_fraction = 0.310000; 13:43:14.967 [planetinfo.record]: polar_cloud_color = 0.468029, 0.759277, 0.349609, 1; 13:43:14.967 [planetinfo.record]: polar_land_color = 0.77502, 0.896046, 0.938086, 1; 13:43:14.967 [planetinfo.record]: polar_sea_color = 0.878075, 0.927148, 0.902995, 1; 13:43:14.967 [planetinfo.record]: sea_color = 0.574275, 0.728516, 0.6526, 1; 13:43:14.967 [planetinfo.record]: rotation_speed = 0.001315 13:43:14.967 [planetinfo.record]: planet zpos = 605000.000000 13:43:14.968 [planetinfo.record]: sun_radius = 182256.317139 13:43:14.968 [planetinfo.record]: sun_vector = 0.239 0.965 -0.105 13:43:14.968 [planetinfo.record]: sun_distance = 1155000 13:43:14.968 [planetinfo.record]: corona_flare = 0.089957 13:43:14.968 [planetinfo.record]: corona_hues = 0.815109 13:43:14.968 [planetinfo.record]: sun_color = 0.478023, 0.671351, 0.722916, 1 13:43:14.968 [planetinfo.record]: corona_shimmer = 0.753183 13:43:14.968 [planetinfo.record]: station_vector = 0.810 0.530 0.249 13:43:14.968 [planetinfo.record]: station = coriolis 13:43:19.698 [PLANETINFO OVER]: Done 13:43:19.699 [PLANETINFO LOGGING]: 5 77 13:43:20.702 [planetinfo.record]: seed = 237 253 32 65 13:43:20.702 [planetinfo.record]: coordinates = 253 189 13:43:20.702 [planetinfo.record]: government = 5; 13:43:20.702 [planetinfo.record]: economy = 5; 13:43:20.702 [planetinfo.record]: techlevel = 6; 13:43:20.702 [planetinfo.record]: population = 35; 13:43:20.702 [planetinfo.record]: productivity = 12600; 13:43:20.702 [planetinfo.record]: name = "Letean"; 13:43:20.702 [planetinfo.record]: inhabitant = "Human Colonial"; 13:43:20.702 [planetinfo.record]: inhabitants = "Human Colonials"; 13:43:20.702 [planetinfo.record]: description = "The planet Letean is most famous for its pink Noge plant plantations and its unusual oceans."; 13:43:20.712 [planetinfo.record]: air_color = 0.502407, 0.596717, 0.86567, 1; 13:43:20.712 [planetinfo.record]: cloud_alpha = 1.000000; 13:43:20.712 [planetinfo.record]: cloud_color = 0.203773, 0.506835, 0.599609, 1; 13:43:20.712 [planetinfo.record]: cloud_fraction = 0.160000; 13:43:20.712 [planetinfo.record]: land_color = 0.358359, 0.66, 0.4267, 1; 13:43:20.712 [planetinfo.record]: land_fraction = 0.380000; 13:43:20.712 [planetinfo.record]: polar_cloud_color = 0.452197, 0.69538, 0.769824, 1; 13:43:20.713 [planetinfo.record]: polar_land_color = 0.827283, 0.934, 0.851461, 1; 13:43:20.713 [planetinfo.record]: polar_sea_color = 0.891399, 0.930078, 0.875672, 1; 13:43:20.713 [planetinfo.record]: sea_color = 0.582905, 0.699219, 0.535613, 1; 13:43:20.713 [planetinfo.record]: rotation_speed = 0.003075 13:43:20.713 [planetinfo.record]: planet zpos = 465500.000000 13:43:20.713 [planetinfo.record]: sun_radius = 77758.201599 13:43:20.714 [planetinfo.record]: sun_vector = -0.198 0.343 0.918 13:43:20.714 [planetinfo.record]: sun_distance = 698250 13:43:20.714 [planetinfo.record]: corona_flare = 0.069252 13:43:20.714 [planetinfo.record]: corona_hues = 0.789932 13:43:20.714 [planetinfo.record]: sun_color = 0.681451, 0.422745, 0.31682, 1 13:43:20.714 [planetinfo.record]: corona_shimmer = 0.238010 13:43:20.714 [planetinfo.record]: station_vector = 0.964 0.265 0.000 13:43:20.714 [planetinfo.record]: station = coriolis 13:43:25.916 [PLANETINFO OVER]: Done 13:43:25.917 [PLANETINFO LOGGING]: 5 78 13:43:26.937 [planetinfo.record]: seed = 165 120 42 176 13:43:26.937 [planetinfo.record]: coordinates = 120 59 13:43:26.937 [planetinfo.record]: government = 4; 13:43:26.937 [planetinfo.record]: economy = 3; 13:43:26.937 [planetinfo.record]: techlevel = 6; 13:43:26.937 [planetinfo.record]: population = 32; 13:43:26.937 [planetinfo.record]: productivity = 14336; 13:43:26.937 [planetinfo.record]: name = "Erzainle"; 13:43:26.937 [planetinfo.record]: inhabitant = "Human Colonial"; 13:43:26.937 [planetinfo.record]: inhabitants = "Human Colonials"; 13:43:26.937 [planetinfo.record]: description = "Erzainle is reasonably well known for the Erzainleian tree grub but plagued by frequent solar activity."; 13:43:26.956 [planetinfo.record]: air_color = 0.487968, 0.495391, 0.907945, 1; 13:43:26.956 [planetinfo.record]: cloud_alpha = 1.000000; 13:43:26.956 [planetinfo.record]: cloud_color = 0.153259, 0.175654, 0.726562, 1; 13:43:26.956 [planetinfo.record]: cloud_fraction = 0.040000; 13:43:26.956 [planetinfo.record]: land_color = 0.66, 0.657422, 0.657603, 1; 13:43:26.956 [planetinfo.record]: land_fraction = 0.640000; 13:43:26.956 [planetinfo.record]: polar_cloud_color = 0.41913, 0.43506, 0.826953, 1; 13:43:26.956 [planetinfo.record]: polar_land_color = 0.934, 0.933088, 0.933152, 1; 13:43:26.956 [planetinfo.record]: polar_sea_color = 0.95, 0.713428, 0.713428, 1; 13:43:26.956 [planetinfo.record]: sea_color = 0.5, 0.00195312, 0.00195312, 1; 13:43:26.956 [planetinfo.record]: rotation_speed = 0.001465 13:43:26.956 [planetinfo.record]: planet zpos = 411040.000000 13:43:26.956 [planetinfo.record]: sun_radius = 87983.232422 13:43:26.956 [planetinfo.record]: sun_vector = 0.784 -0.020 -0.621 13:43:26.956 [planetinfo.record]: sun_distance = 557840 13:43:26.956 [planetinfo.record]: corona_flare = 0.082181 13:43:26.956 [planetinfo.record]: corona_hues = 0.641121 13:43:26.956 [planetinfo.record]: sun_color = 0.756497, 0.49052, 0.305896, 1 13:43:26.957 [planetinfo.record]: corona_shimmer = 0.675559 13:43:26.957 [planetinfo.record]: station_vector = 0.132 0.902 0.411 13:43:26.957 [planetinfo.record]: station = coriolis 13:43:32.047 [PLANETINFO OVER]: Done 13:43:32.048 [PLANETINFO LOGGING]: 5 79 13:43:33.050 [planetinfo.record]: seed = 41 161 40 146 13:43:33.050 [planetinfo.record]: coordinates = 161 140 13:43:33.050 [planetinfo.record]: government = 5; 13:43:33.050 [planetinfo.record]: economy = 4; 13:43:33.050 [planetinfo.record]: techlevel = 7; 13:43:33.050 [planetinfo.record]: population = 38; 13:43:33.050 [planetinfo.record]: productivity = 16416; 13:43:33.050 [planetinfo.record]: name = "Enbece"; 13:43:33.050 [planetinfo.record]: inhabitant = "Human Colonial"; 13:43:33.050 [planetinfo.record]: inhabitants = "Human Colonials"; 13:43:33.051 [planetinfo.record]: description = "The planet Enbece is an unremarkable dump."; 13:43:33.066 [planetinfo.record]: air_color = 0.522427, 0.585952, 0.857865, 1; 13:43:33.066 [planetinfo.record]: cloud_alpha = 1.000000; 13:43:33.066 [planetinfo.record]: cloud_color = 0.245323, 0.428841, 0.576172, 1; 13:43:33.066 [planetinfo.record]: cloud_fraction = 0.450000; 13:43:33.066 [planetinfo.record]: land_color = 0.270703, 0.66, 0.441021, 1; 13:43:33.066 [planetinfo.record]: land_fraction = 0.270000; 13:43:33.066 [planetinfo.record]: polar_cloud_color = 0.486783, 0.637932, 0.759277, 1; 13:43:33.066 [planetinfo.record]: polar_land_color = 0.796272, 0.934, 0.856528, 1; 13:43:33.066 [planetinfo.record]: polar_sea_color = 0.946289, 0.910887, 0.897034, 1; 13:43:33.066 [planetinfo.record]: sea_color = 0.537109, 0.456733, 0.425282, 1; 13:43:33.066 [planetinfo.record]: rotation_speed = 0.001380 13:43:33.066 [planetinfo.record]: planet zpos = 314010.000000 13:43:33.066 [planetinfo.record]: sun_radius = 112603.514099 13:43:33.067 [planetinfo.record]: sun_vector = 0.764 -0.570 -0.302 13:43:33.067 [planetinfo.record]: sun_distance = 593130 13:43:33.067 [planetinfo.record]: corona_flare = 0.018779 13:43:33.067 [planetinfo.record]: corona_hues = 0.729286 13:43:33.067 [planetinfo.record]: sun_color = 0.258197, 0.802573, 0.842184, 1 13:43:33.067 [planetinfo.record]: corona_shimmer = 0.797488 13:43:33.067 [planetinfo.record]: station_vector = -0.938 0.326 0.121 13:43:33.067 [planetinfo.record]: station = coriolis 13:43:37.879 [PLANETINFO OVER]: Done 13:43:37.880 [PLANETINFO LOGGING]: 5 80 13:43:38.899 [planetinfo.record]: seed = 233 69 170 249 13:43:38.899 [planetinfo.record]: coordinates = 69 243 13:43:38.899 [planetinfo.record]: government = 5; 13:43:38.899 [planetinfo.record]: economy = 3; 13:43:38.899 [planetinfo.record]: techlevel = 8; 13:43:38.899 [planetinfo.record]: population = 41; 13:43:38.899 [planetinfo.record]: productivity = 20664; 13:43:38.899 [planetinfo.record]: name = "Orbeen"; 13:43:38.899 [planetinfo.record]: inhabitant = "Insect"; 13:43:38.899 [planetinfo.record]: inhabitants = "Insects"; 13:43:38.899 [planetinfo.record]: description = "The planet Orbeen is cursed by killer mountain lobstoids."; 13:43:38.912 [planetinfo.record]: air_color = 0.59633, 0.891075, 0.972984, 1; 13:43:38.912 [planetinfo.record]: cloud_alpha = 1.000000; 13:43:38.912 [planetinfo.record]: cloud_color = 0.577522, 0.921875, 0.40332, 1; 13:43:38.912 [planetinfo.record]: cloud_fraction = 0.450000; 13:43:38.912 [planetinfo.record]: land_color = 0.135956, 0.632812, 0.562942, 1; 13:43:38.912 [planetinfo.record]: land_fraction = 0.390000; 13:43:38.912 [planetinfo.record]: polar_cloud_color = 0.701265, 0.914844, 0.593219, 1; 13:43:38.912 [planetinfo.record]: polar_land_color = 0.752851, 0.936719, 0.910862, 1; 13:43:38.912 [planetinfo.record]: polar_sea_color = 0.931055, 0.880962, 0.856498, 1; 13:43:38.912 [planetinfo.record]: sea_color = 0.689453, 0.541076, 0.468613, 1; 13:43:38.912 [planetinfo.record]: rotation_speed = 0.004147 13:43:38.912 [planetinfo.record]: planet zpos = 570790.000000 13:43:38.912 [planetinfo.record]: sun_radius = 108146.658783 13:43:38.912 [planetinfo.record]: sun_vector = 0.532 0.572 -0.625 13:43:38.913 [planetinfo.record]: sun_distance = 1037800 13:43:38.913 [planetinfo.record]: corona_flare = 0.088980 13:43:38.913 [planetinfo.record]: corona_hues = 0.592369 13:43:38.913 [planetinfo.record]: sun_color = 0.628952, 0.640624, 0.702515, 1 13:43:38.913 [planetinfo.record]: corona_shimmer = 0.390328 13:43:38.913 [planetinfo.record]: station_vector = 0.986 -0.118 0.118 13:43:38.913 [planetinfo.record]: station = coriolis 13:43:43.974 [PLANETINFO OVER]: Done 13:43:43.975 [PLANETINFO LOGGING]: 5 81 13:43:44.979 [planetinfo.record]: seed = 117 159 64 69 13:43:44.979 [planetinfo.record]: coordinates = 159 114 13:43:44.979 [planetinfo.record]: government = 6; 13:43:44.979 [planetinfo.record]: economy = 2; 13:43:44.979 [planetinfo.record]: techlevel = 11; 13:43:44.980 [planetinfo.record]: population = 53; 13:43:44.980 [planetinfo.record]: productivity = 33920; 13:43:44.980 [planetinfo.record]: name = "Cetite"; 13:43:44.980 [planetinfo.record]: inhabitant = "Human Colonial"; 13:43:44.980 [planetinfo.record]: inhabitants = "Human Colonials"; 13:43:44.980 [planetinfo.record]: description = "This world is ravaged by occasional solar activity."; 13:43:44.993 [planetinfo.record]: air_color = 0.838284, 0.766966, 0.980789, 1; 13:43:44.993 [planetinfo.record]: cloud_alpha = 1.000000; 13:43:44.993 [planetinfo.record]: cloud_color = 0.945312, 0.893616, 0.944505, 1; 13:43:44.993 [planetinfo.record]: cloud_fraction = 0.360000; 13:43:44.993 [planetinfo.record]: land_color = 0.220413, 0.527344, 0.476988, 1; 13:43:44.994 [planetinfo.record]: land_fraction = 0.610000; 13:43:44.994 [planetinfo.record]: polar_cloud_color = 0.925391, 0.893761, 0.924896, 1; 13:43:44.994 [planetinfo.record]: polar_land_color = 0.809431, 0.947266, 0.924652, 1; 13:43:44.994 [planetinfo.record]: polar_sea_color = 0.930664, 0.860768, 0.84705, 1; 13:43:44.994 [planetinfo.record]: sea_color = 0.693359, 0.485064, 0.444183, 1; 13:43:44.994 [planetinfo.record]: rotation_speed = 0.002125 13:43:44.994 [planetinfo.record]: planet zpos = 468050.000000 13:43:44.994 [planetinfo.record]: sun_radius = 111189.273834 13:43:44.994 [planetinfo.record]: sun_vector = -0.262 0.960 -0.101 13:43:44.994 [planetinfo.record]: sun_distance = 808450 13:43:44.994 [planetinfo.record]: corona_flare = 0.049409 13:43:44.994 [planetinfo.record]: corona_hues = 0.849297 13:43:44.994 [planetinfo.record]: sun_color = 0.801688, 0.781622, 0.778678, 1 13:43:44.994 [planetinfo.record]: corona_shimmer = 0.738667 13:43:44.995 [planetinfo.record]: station_vector = -0.640 0.326 0.696 13:43:44.995 [planetinfo.record]: station = dodecahedron 13:43:49.937 [PLANETINFO OVER]: Done 13:43:49.937 [PLANETINFO LOGGING]: 5 82 13:43:50.940 [planetinfo.record]: seed = 189 216 58 108 13:43:50.940 [planetinfo.record]: coordinates = 216 60 13:43:50.940 [planetinfo.record]: government = 7; 13:43:50.940 [planetinfo.record]: economy = 4; 13:43:50.940 [planetinfo.record]: techlevel = 7; 13:43:50.940 [planetinfo.record]: population = 40; 13:43:50.940 [planetinfo.record]: productivity = 21120; 13:43:50.940 [planetinfo.record]: name = "Inlebi"; 13:43:50.940 [planetinfo.record]: inhabitant = "Human Colonial"; 13:43:50.940 [planetinfo.record]: inhabitants = "Human Colonials"; 13:43:50.940 [planetinfo.record]: description = "The planet Inlebi is mildly well known for its exotic night life."; 13:43:50.948 [planetinfo.record]: air_color = 0.544617, 0.980139, 0.96268, 1; 13:43:50.948 [planetinfo.record]: cloud_alpha = 1.000000; 13:43:50.948 [planetinfo.record]: cloud_color = 0.943359, 0.867587, 0.25058, 1; 13:43:50.948 [planetinfo.record]: cloud_fraction = 0.280000; 13:43:50.948 [planetinfo.record]: land_color = 0.293678, 0.364787, 0.533203, 1; 13:43:50.948 [planetinfo.record]: land_fraction = 0.440000; 13:43:50.948 [planetinfo.record]: polar_cloud_color = 0.924512, 0.8781, 0.500175, 1; 13:43:50.948 [planetinfo.record]: polar_land_color = 0.840363, 0.871926, 0.94668, 1; 13:43:50.948 [planetinfo.record]: polar_sea_color = 0.923047, 0.845328, 0.811272, 1; 13:43:50.948 [planetinfo.record]: sea_color = 0.769531, 0.510359, 0.39679, 1; 13:43:50.949 [planetinfo.record]: rotation_speed = 0.004261 13:43:50.949 [planetinfo.record]: planet zpos = 671440.000000 13:43:50.949 [planetinfo.record]: sun_radius = 191831.351318 13:43:50.949 [planetinfo.record]: sun_vector = -0.575 -0.600 0.555 13:43:50.949 [planetinfo.record]: sun_distance = 1037680 13:43:50.949 [planetinfo.record]: corona_flare = 0.028008 13:43:50.949 [planetinfo.record]: corona_hues = 0.507149 13:43:50.949 [planetinfo.record]: sun_color = 0.595343, 0.684416, 0.731216, 1 13:43:50.949 [planetinfo.record]: corona_shimmer = 1.504707 13:43:50.949 [planetinfo.record]: station_vector = 0.132 -0.972 0.195 13:43:50.949 [planetinfo.record]: station = coriolis 13:43:56.149 [PLANETINFO OVER]: Done 13:43:56.150 [PLANETINFO LOGGING]: 5 83 13:43:57.163 [planetinfo.record]: seed = 81 179 104 250 13:43:57.163 [planetinfo.record]: coordinates = 179 198 13:43:57.163 [planetinfo.record]: government = 2; 13:43:57.163 [planetinfo.record]: economy = 6; 13:43:57.163 [planetinfo.record]: techlevel = 5; 13:43:57.163 [planetinfo.record]: population = 29; 13:43:57.163 [planetinfo.record]: productivity = 5568; 13:43:57.163 [planetinfo.record]: name = "Qubele"; 13:43:57.163 [planetinfo.record]: inhabitant = "Human Colonial"; 13:43:57.163 [planetinfo.record]: inhabitants = "Human Colonials"; 13:43:57.163 [planetinfo.record]: description = "This planet is very well known for its inhabitants’ unusual silliness and its fabulous cuisine."; 13:43:57.172 [planetinfo.record]: air_color = 0.654249, 0.885636, 0.909246, 1; 13:43:57.172 [planetinfo.record]: cloud_alpha = 1.000000; 13:43:57.179 [planetinfo.record]: cloud_color = 0.679331, 0.730469, 0.553558, 1; 13:43:57.179 [planetinfo.record]: cloud_fraction = 0.240000; 13:43:57.180 [planetinfo.record]: land_color = 0.66, 0.369961, 0.195938, 1; 13:43:57.180 [planetinfo.record]: land_fraction = 0.540000; 13:43:57.180 [planetinfo.record]: polar_cloud_color = 0.792451, 0.828711, 0.703271, 1; 13:43:57.180 [planetinfo.record]: polar_land_color = 0.934, 0.831388, 0.76982, 1; 13:43:57.180 [planetinfo.record]: polar_sea_color = 0.86942, 0.923437, 0.895585, 1; 13:43:57.180 [planetinfo.record]: sea_color = 0.586481, 0.765625, 0.673254, 1; 13:43:57.180 [planetinfo.record]: rotation_speed = 0.000493 13:43:57.180 [planetinfo.record]: planet zpos = 722150.000000 13:43:57.180 [planetinfo.record]: sun_radius = 151797.901917 13:43:57.180 [planetinfo.record]: sun_vector = -0.760 0.639 0.122 13:43:57.180 [planetinfo.record]: sun_distance = 888800 13:43:57.180 [planetinfo.record]: corona_flare = 0.043840 13:43:57.180 [planetinfo.record]: corona_hues = 0.920250 13:43:57.180 [planetinfo.record]: sun_color = 0.787988, 0.784381, 0.623828, 1 13:43:57.180 [planetinfo.record]: corona_shimmer = 0.579288 13:43:57.180 [planetinfo.record]: station_vector = -0.802 0.169 0.573 13:43:57.180 [planetinfo.record]: station = coriolis 13:44:02.326 [PLANETINFO OVER]: Done 13:44:02.327 [PLANETINFO LOGGING]: 5 84 13:44:03.331 [planetinfo.record]: seed = 161 143 218 36 13:44:03.331 [planetinfo.record]: coordinates = 143 33 13:44:03.331 [planetinfo.record]: government = 4; 13:44:03.331 [planetinfo.record]: economy = 1; 13:44:03.331 [planetinfo.record]: techlevel = 11; 13:44:03.331 [planetinfo.record]: population = 50; 13:44:03.331 [planetinfo.record]: productivity = 28800; 13:44:03.331 [planetinfo.record]: name = "Zalaarce"; 13:44:03.331 [planetinfo.record]: inhabitant = "Fierce Red Feline"; 13:44:03.331 [planetinfo.record]: inhabitants = "Fierce Red Felines"; 13:44:03.331 [planetinfo.record]: description = "This planet is notable for the Zalaarceian edible poet and Zalaarceian evil juice."; 13:44:03.356 [planetinfo.record]: air_color = 0.652171, 0.86567, 0.818787, 1; 13:44:03.356 [planetinfo.record]: cloud_alpha = 1.000000; 13:44:03.356 [planetinfo.record]: cloud_color = 0.599609, 0.544933, 0.515289, 1; 13:44:03.356 [planetinfo.record]: cloud_fraction = 0.100000; 13:44:03.357 [planetinfo.record]: land_color = 0.66, 0.441363, 0.229453, 1; 13:44:03.357 [planetinfo.record]: land_fraction = 0.600000; 13:44:03.357 [planetinfo.record]: polar_cloud_color = 0.769824, 0.725951, 0.702164, 1; 13:44:03.357 [planetinfo.record]: polar_land_color = 0.934, 0.856649, 0.781678, 1; 13:44:03.357 [planetinfo.record]: polar_sea_color = 0.869982, 0.924609, 0.88748, 1; 13:44:03.357 [planetinfo.record]: sea_color = 0.575737, 0.753906, 0.632807, 1; 13:44:03.357 [planetinfo.record]: rotation_speed = 0.001874 13:44:03.357 [planetinfo.record]: planet zpos = 597450.000000 13:44:03.357 [planetinfo.record]: sun_radius = 68671.743164 13:44:03.357 [planetinfo.record]: sun_vector = -0.361 -0.337 0.869 13:44:03.357 [planetinfo.record]: sun_distance = 756770 13:44:03.357 [planetinfo.record]: corona_flare = 0.015685 13:44:03.358 [planetinfo.record]: corona_hues = 0.672997 13:44:03.358 [planetinfo.record]: sun_color = 0.503707, 0.559769, 0.76102, 1 13:44:03.358 [planetinfo.record]: corona_shimmer = 0.348533 13:44:03.358 [planetinfo.record]: station_vector = -0.279 0.206 0.938 13:44:03.358 [planetinfo.record]: station = icosahedron 13:44:08.471 [PLANETINFO OVER]: Done 13:44:08.472 [PLANETINFO LOGGING]: 5 85 13:44:09.474 [planetinfo.record]: seed = 61 133 160 229 13:44:09.474 [planetinfo.record]: coordinates = 133 138 13:44:09.474 [planetinfo.record]: government = 7; 13:44:09.474 [planetinfo.record]: economy = 2; 13:44:09.474 [planetinfo.record]: techlevel = 10; 13:44:09.475 [planetinfo.record]: population = 50; 13:44:09.475 [planetinfo.record]: productivity = 35200; 13:44:09.475 [planetinfo.record]: name = "Celaan"; 13:44:09.475 [planetinfo.record]: inhabitant = "Fierce Rodent"; 13:44:09.475 [planetinfo.record]: inhabitants = "Fierce Rodents"; 13:44:09.475 [planetinfo.record]: description = "The world Celaan is a boring world."; 13:44:09.491 [planetinfo.record]: air_color = 0.661094, 0.846809, 0.794431, 1; 13:44:09.491 [planetinfo.record]: cloud_alpha = 1.000000; 13:44:09.491 [planetinfo.record]: cloud_color = 0.542969, 0.516374, 0.511154, 1; 13:44:09.491 [planetinfo.record]: cloud_fraction = 0.530000; 13:44:09.491 [planetinfo.record]: land_color = 0.649688, 0.65855, 0.66, 1; 13:44:09.491 [planetinfo.record]: land_fraction = 0.350000; 13:44:09.491 [planetinfo.record]: polar_cloud_color = 0.744336, 0.72155, 0.717078, 1; 13:44:09.491 [planetinfo.record]: polar_land_color = 0.930352, 0.933487, 0.934, 1; 13:44:09.491 [planetinfo.record]: polar_sea_color = 0.810506, 0.949609, 0.884405, 1; 13:44:09.491 [planetinfo.record]: sea_color = 0.208649, 0.503906, 0.365504, 1; 13:44:09.491 [planetinfo.record]: rotation_speed = 0.001258 13:44:09.491 [planetinfo.record]: planet zpos = 422900.000000 13:44:09.492 [planetinfo.record]: sun_radius = 104870.630493 13:44:09.492 [planetinfo.record]: sun_vector = 0.630 -0.305 0.714 13:44:09.492 [planetinfo.record]: sun_distance = 888090 13:44:09.492 [planetinfo.record]: corona_flare = 0.006793 13:44:09.492 [planetinfo.record]: corona_hues = 0.761414 13:44:09.492 [planetinfo.record]: sun_color = 0.609244, 0.770479, 0.847928, 1 13:44:09.492 [planetinfo.record]: corona_shimmer = 0.399668 13:44:09.492 [planetinfo.record]: station_vector = -0.334 0.767 0.547 13:44:09.492 [planetinfo.record]: station = coriolis 13:44:14.081 [PLANETINFO OVER]: Done 13:44:14.082 [PLANETINFO LOGGING]: 5 86 13:44:15.085 [planetinfo.record]: seed = 21 59 138 144 13:44:15.085 [planetinfo.record]: coordinates = 59 96 13:44:15.085 [planetinfo.record]: government = 2; 13:44:15.085 [planetinfo.record]: economy = 0; 13:44:15.085 [planetinfo.record]: techlevel = 11; 13:44:15.086 [planetinfo.record]: population = 47; 13:44:15.086 [planetinfo.record]: productivity = 22560; 13:44:15.086 [planetinfo.record]: name = "Ermati"; 13:44:15.086 [planetinfo.record]: inhabitant = "Black Bony Lobster"; 13:44:15.086 [planetinfo.record]: inhabitants = "Black Bony Lobsters"; 13:44:15.086 [planetinfo.record]: description = "This planet is noted for its fabulous goat soup."; 13:44:15.101 [planetinfo.record]: air_color = 0.695447, 0.575485, 0.884531, 1; 13:44:15.101 [planetinfo.record]: cloud_alpha = 1.000000; 13:44:15.101 [planetinfo.record]: cloud_color = 0.65625, 0.36145, 0.603278, 1; 13:44:15.101 [planetinfo.record]: cloud_fraction = 0.210000; 13:44:15.102 [planetinfo.record]: land_color = 0.528516, 0.66, 0.632265, 1; 13:44:15.102 [planetinfo.record]: land_fraction = 0.350000; 13:44:15.102 [planetinfo.record]: polar_cloud_color = 0.795313, 0.572019, 0.755189, 1; 13:44:15.102 [planetinfo.record]: polar_land_color = 0.887482, 0.934, 0.924188, 1; 13:44:15.102 [planetinfo.record]: polar_sea_color = 0.876176, 0.927734, 0.873194, 1; 13:44:15.102 [planetinfo.record]: sea_color = 0.562013, 0.722656, 0.552719, 1; 13:44:15.102 [planetinfo.record]: rotation_speed = 0.002067 13:44:15.102 [planetinfo.record]: planet zpos = 373750.000000 13:44:15.102 [planetinfo.record]: sun_radius = 89690.647125 13:44:15.102 [planetinfo.record]: sun_vector = -0.890 -0.441 0.114 13:44:15.102 [planetinfo.record]: sun_distance = 488750 13:44:15.102 [planetinfo.record]: corona_flare = 0.023715 13:44:15.102 [planetinfo.record]: corona_hues = 0.695885 13:44:15.102 [planetinfo.record]: sun_color = 0.380093, 0.580479, 0.701788, 1 13:44:15.102 [planetinfo.record]: corona_shimmer = 0.453035 13:44:15.102 [planetinfo.record]: station_vector = -0.598 -0.237 0.765 13:44:15.102 [planetinfo.record]: station = icosahedron 13:44:20.232 [PLANETINFO OVER]: Done 13:44:20.234 [PLANETINFO LOGGING]: 5 87 13:44:21.235 [planetinfo.record]: seed = 185 179 232 214 13:44:21.236 [planetinfo.record]: coordinates = 179 247 13:44:21.236 [planetinfo.record]: government = 7; 13:44:21.236 [planetinfo.record]: economy = 7; 13:44:21.236 [planetinfo.record]: techlevel = 7; 13:44:21.236 [planetinfo.record]: population = 43; 13:44:21.236 [planetinfo.record]: productivity = 11352; 13:44:21.236 [planetinfo.record]: name = "Vexeince"; 13:44:21.236 [planetinfo.record]: inhabitant = "Fat Feline"; 13:44:21.236 [planetinfo.record]: inhabitants = "Fat Felines"; 13:44:21.236 [planetinfo.record]: description = "The world Vexeince is reasonably fabled for its exciting sit coms and its inhabitants’ ancient loathing of casinos."; 13:44:21.251 [planetinfo.record]: air_color = 0.534008, 0.513946, 0.895588, 1; 13:44:21.251 [planetinfo.record]: cloud_alpha = 1.000000; 13:44:21.251 [planetinfo.record]: cloud_color = 0.301383, 0.22084, 0.689453, 1; 13:44:21.251 [planetinfo.record]: cloud_fraction = 0.580000; 13:44:21.251 [planetinfo.record]: land_color = 0.619141, 0.509967, 0.488541, 1; 13:44:21.252 [planetinfo.record]: land_fraction = 0.660000; 13:44:21.252 [planetinfo.record]: polar_cloud_color = 0.525214, 0.466054, 0.810254, 1; 13:44:21.252 [planetinfo.record]: polar_land_color = 0.938086, 0.896733, 0.888617, 1; 13:44:21.252 [planetinfo.record]: polar_sea_color = 0.778732, 0.942578, 0.896496, 1; 13:44:21.252 [planetinfo.record]: sea_color = 0.174957, 0.574219, 0.461926, 1; 13:44:21.252 [planetinfo.record]: rotation_speed = 0.004539 13:44:21.252 [planetinfo.record]: planet zpos = 543720.000000 13:44:21.252 [planetinfo.record]: sun_radius = 140576.044922 13:44:21.252 [planetinfo.record]: sun_vector = -0.622 0.633 0.461 13:44:21.252 [planetinfo.record]: sun_distance = 951510 13:44:21.252 [planetinfo.record]: corona_flare = 0.054659 13:44:21.252 [planetinfo.record]: corona_hues = 0.729126 13:44:21.252 [planetinfo.record]: sun_color = 0.729285, 0.583617, 0.542384, 1 13:44:21.252 [planetinfo.record]: corona_shimmer = 0.387640 13:44:21.252 [planetinfo.record]: station_vector = 0.081 -0.285 0.955 13:44:21.252 [planetinfo.record]: station = coriolis 13:44:26.188 [PLANETINFO OVER]: Done 13:44:26.189 [PLANETINFO LOGGING]: 5 88 13:44:27.192 [planetinfo.record]: seed = 153 101 74 244 13:44:27.192 [planetinfo.record]: coordinates = 101 12 13:44:27.192 [planetinfo.record]: government = 3; 13:44:27.192 [planetinfo.record]: economy = 4; 13:44:27.192 [planetinfo.record]: techlevel = 6; 13:44:27.192 [planetinfo.record]: population = 32; 13:44:27.192 [planetinfo.record]: productivity = 10752; 13:44:27.192 [planetinfo.record]: name = "Rabi"; 13:44:27.192 [planetinfo.record]: inhabitant = "Human Colonial"; 13:44:27.192 [planetinfo.record]: inhabitants = "Human Colonials"; 13:44:27.192 [planetinfo.record]: description = "The planet Rabi is very noted for its unusual sit coms but ravaged by vicious vicious Alzaats."; 13:44:27.204 [planetinfo.record]: air_color = 0.499485, 0.675335, 0.834451, 1; 13:44:27.204 [planetinfo.record]: cloud_alpha = 1.000000; 13:44:27.204 [planetinfo.record]: cloud_color = 0.199577, 0.505859, 0.333576, 1; 13:44:27.204 [planetinfo.record]: cloud_fraction = 0.470000; 13:44:27.204 [planetinfo.record]: land_color = 0.4125, 0.51498, 0.66, 1; 13:44:27.204 [planetinfo.record]: land_fraction = 0.650000; 13:44:27.204 [planetinfo.record]: polar_cloud_color = 0.452286, 0.727637, 0.572752, 1; 13:44:27.204 [planetinfo.record]: polar_land_color = 0.846438, 0.882694, 0.934, 1; 13:44:27.204 [planetinfo.record]: polar_sea_color = 0.93418, 0.926413, 0.875702, 1; 13:44:27.205 [planetinfo.record]: sea_color = 0.658203, 0.636315, 0.493395, 1; 13:44:27.205 [planetinfo.record]: rotation_speed = 0.004775 13:44:27.205 [planetinfo.record]: planet zpos = 354690.000000 13:44:27.205 [planetinfo.record]: sun_radius = 113525.046387 13:44:27.206 [planetinfo.record]: sun_vector = 0.437 0.897 0.070 13:44:27.206 [planetinfo.record]: sun_distance = 867020 13:44:27.206 [planetinfo.record]: corona_flare = 0.022052 13:44:27.206 [planetinfo.record]: corona_hues = 0.970154 13:44:27.206 [planetinfo.record]: sun_color = 0.464829, 0.716712, 0.832095, 1 13:44:27.206 [planetinfo.record]: corona_shimmer = 0.285809 13:44:27.206 [planetinfo.record]: station_vector = 0.993 -0.067 0.101 13:44:27.206 [planetinfo.record]: station = coriolis 13:44:32.253 [PLANETINFO OVER]: Done 13:44:32.253 [PLANETINFO LOGGING]: 5 89 13:44:33.259 [planetinfo.record]: seed = 69 27 64 66 13:44:33.259 [planetinfo.record]: coordinates = 27 192 13:44:33.259 [planetinfo.record]: government = 0; 13:44:33.259 [planetinfo.record]: economy = 2; 13:44:33.259 [planetinfo.record]: techlevel = 8; 13:44:33.259 [planetinfo.record]: population = 35; 13:44:33.259 [planetinfo.record]: productivity = 8960; 13:44:33.259 [planetinfo.record]: name = "Xeisanan"; 13:44:33.259 [planetinfo.record]: inhabitant = "Human Colonial"; 13:44:33.259 [planetinfo.record]: inhabitants = "Human Colonials"; 13:44:33.259 [planetinfo.record]: description = "The planet Xeisanan is scourged by killer edible poets."; 13:44:33.276 [planetinfo.record]: air_color = 0.689861, 0.600085, 0.857215, 1; 13:44:33.277 [planetinfo.record]: cloud_alpha = 1.000000; 13:44:33.277 [planetinfo.record]: cloud_color = 0.574219, 0.401505, 0.563424, 1; 13:44:33.278 [planetinfo.record]: cloud_fraction = 0.420000; 13:44:33.278 [planetinfo.record]: land_color = 0.611016, 0.66, 0.656556, 1; 13:44:33.279 [planetinfo.record]: land_fraction = 0.690000; 13:44:33.279 [planetinfo.record]: polar_cloud_color = 0.758398, 0.615828, 0.749488, 1; 13:44:33.279 [planetinfo.record]: polar_land_color = 0.91667, 0.934, 0.932782, 1; 13:44:33.279 [planetinfo.record]: polar_sea_color = 0.949414, 0.8265, 0.727822, 1; 13:44:33.279 [planetinfo.record]: sea_color = 0.505859, 0.243899, 0.0335922, 1; 13:44:33.279 [planetinfo.record]: rotation_speed = 0.000229 13:44:33.279 [planetinfo.record]: planet zpos = 335500.000000 13:44:33.279 [planetinfo.record]: sun_radius = 80028.852081 13:44:33.279 [planetinfo.record]: sun_vector = 0.009 0.113 0.994 13:44:33.279 [planetinfo.record]: sun_distance = 738100 13:44:33.279 [planetinfo.record]: corona_flare = 0.076213 13:44:33.279 [planetinfo.record]: corona_hues = 0.522476 13:44:33.279 [planetinfo.record]: sun_color = 0.481593, 0.589978, 0.710425, 1 13:44:33.279 [planetinfo.record]: corona_shimmer = 1.151047 13:44:33.280 [planetinfo.record]: station_vector = 0.248 -0.945 0.213 13:44:33.280 [planetinfo.record]: station = coriolis 13:44:38.045 [PLANETINFO OVER]: Done 13:44:38.045 [PLANETINFO LOGGING]: 5 90 13:44:39.059 [planetinfo.record]: seed = 173 219 26 117 13:44:39.059 [planetinfo.record]: coordinates = 219 123 13:44:39.059 [planetinfo.record]: government = 5; 13:44:39.059 [planetinfo.record]: economy = 3; 13:44:39.060 [planetinfo.record]: techlevel = 10; 13:44:39.060 [planetinfo.record]: population = 49; 13:44:39.060 [planetinfo.record]: productivity = 24696; 13:44:39.060 [planetinfo.record]: name = "Lainisri"; 13:44:39.060 [planetinfo.record]: inhabitant = "Human Colonial"; 13:44:39.060 [planetinfo.record]: inhabitants = "Human Colonials"; 13:44:39.060 [planetinfo.record]: description = "The planet Lainisri is most famous for its pink oceans."; 13:44:39.068 [planetinfo.record]: air_color = 0.704108, 0.517826, 0.938514, 1; 13:44:39.068 [planetinfo.record]: cloud_alpha = 1.000000; 13:44:39.068 [planetinfo.record]: cloud_color = 0.818359, 0.207787, 0.608475, 1; 13:44:39.068 [planetinfo.record]: cloud_fraction = 0.300000; 13:44:39.068 [planetinfo.record]: land_color = 0.599736, 0.66, 0.572344, 1; 13:44:39.068 [planetinfo.record]: land_fraction = 0.560000; 13:44:39.068 [planetinfo.record]: polar_cloud_color = 0.868262, 0.463384, 0.729085, 1; 13:44:39.068 [planetinfo.record]: polar_land_color = 0.912679, 0.934, 0.902988, 1; 13:44:39.068 [planetinfo.record]: polar_sea_color = 0.91, 0.8645, 0.8918, 1; 13:44:39.071 [planetinfo.record]: sea_color = 0.9, 0.72, 0.828, 1; 13:44:39.071 [planetinfo.record]: rotation_speed = 0.004891 13:44:39.071 [planetinfo.record]: planet zpos = 560950.000000 13:44:39.071 [planetinfo.record]: sun_radius = 116368.576050 13:44:39.071 [planetinfo.record]: sun_vector = 0.502 -0.034 0.864 13:44:39.071 [planetinfo.record]: sun_distance = 863000 13:44:39.071 [planetinfo.record]: corona_flare = 0.049109 13:44:39.071 [planetinfo.record]: corona_hues = 0.503609 13:44:39.071 [planetinfo.record]: sun_color = 0.842279, 0.666335, 0.656851, 1 13:44:39.071 [planetinfo.record]: corona_shimmer = 0.447387 13:44:39.072 [planetinfo.record]: station_vector = -0.061 0.980 0.190 13:44:39.072 [planetinfo.record]: station = coriolis 13:44:43.754 [PLANETINFO OVER]: Done 13:44:43.756 [PLANETINFO LOGGING]: 5 91 13:44:44.758 [planetinfo.record]: seed = 97 94 168 71 13:44:44.758 [planetinfo.record]: coordinates = 94 29 13:44:44.758 [planetinfo.record]: government = 4; 13:44:44.758 [planetinfo.record]: economy = 5; 13:44:44.758 [planetinfo.record]: techlevel = 6; 13:44:44.758 [planetinfo.record]: population = 34; 13:44:44.758 [planetinfo.record]: productivity = 10880; 13:44:44.758 [planetinfo.record]: name = "Sogees"; 13:44:44.758 [planetinfo.record]: inhabitant = "Fierce Yellow Bony Feline"; 13:44:44.758 [planetinfo.record]: inhabitants = "Fierce Yellow Bony Felines"; 13:44:44.758 [planetinfo.record]: description = "This planet is reasonably famous for the Sogeesian evil Stoid."; 13:44:44.764 [planetinfo.record]: air_color = 0.468726, 0.469665, 0.923555, 1; 13:44:44.764 [planetinfo.record]: cloud_alpha = 1.000000; 13:44:44.764 [planetinfo.record]: cloud_color = 0.0989692, 0.0936584, 0.773438, 1; 13:44:44.764 [planetinfo.record]: cloud_fraction = 0.320000; 13:44:44.764 [planetinfo.record]: land_color = 0.605859, 0.66, 0.623201, 1; 13:44:44.764 [planetinfo.record]: land_fraction = 0.460000; 13:44:44.764 [planetinfo.record]: polar_cloud_color = 0.38584, 0.382201, 0.848047, 1; 13:44:44.765 [planetinfo.record]: polar_land_color = 0.914846, 0.934, 0.920981, 1; 13:44:44.765 [planetinfo.record]: polar_sea_color = 0.95, 0.828039, 0.730127, 1; 13:44:44.765 [planetinfo.record]: sea_color = 0.5, 0.24324, 0.0371094, 1; 13:44:44.765 [planetinfo.record]: rotation_speed = 0.003523 13:44:44.765 [planetinfo.record]: planet zpos = 658280.000000 13:44:44.765 [planetinfo.record]: sun_radius = 110701.047974 13:44:44.765 [planetinfo.record]: sun_vector = 0.067 0.975 -0.213 13:44:44.765 [planetinfo.record]: sun_distance = 846360 13:44:44.765 [planetinfo.record]: corona_flare = 0.095645 13:44:44.765 [planetinfo.record]: corona_hues = 0.856117 13:44:44.765 [planetinfo.record]: sun_color = 0.469122, 0.696842, 0.836047, 1 13:44:44.765 [planetinfo.record]: corona_shimmer = 0.232803 13:44:44.765 [planetinfo.record]: station_vector = 0.090 0.751 0.654 13:44:44.765 [planetinfo.record]: station = coriolis 13:44:49.564 [PLANETINFO OVER]: Done 13:44:49.566 [PLANETINFO LOGGING]: 5 92 13:44:50.572 [planetinfo.record]: seed = 209 115 250 159 13:44:50.572 [planetinfo.record]: coordinates = 115 105 13:44:50.572 [planetinfo.record]: government = 2; 13:44:50.572 [planetinfo.record]: economy = 1; 13:44:50.572 [planetinfo.record]: techlevel = 10; 13:44:50.572 [planetinfo.record]: population = 44; 13:44:50.572 [planetinfo.record]: productivity = 19008; 13:44:50.573 [planetinfo.record]: name = "Onteer"; 13:44:50.573 [planetinfo.record]: inhabitant = "Black Horned Humanoid"; 13:44:50.573 [planetinfo.record]: inhabitants = "Black Horned Humanoids"; 13:44:50.573 [planetinfo.record]: description = "Onteer is cursed by dreadful civil war."; 13:44:50.575 [planetinfo.record]: air_color = 0.67805, 0.858516, 0.84283, 1; 13:44:50.575 [planetinfo.record]: cloud_alpha = 1.000000; 13:44:50.575 [planetinfo.record]: cloud_color = 0.578125, 0.573608, 0.560059, 1; 13:44:50.575 [planetinfo.record]: cloud_fraction = 0.150000; 13:44:50.575 [planetinfo.record]: land_color = 0.66, 0.301641, 0.503218, 1; 13:44:50.575 [planetinfo.record]: land_fraction = 0.430000; 13:44:50.575 [planetinfo.record]: polar_cloud_color = 0.760156, 0.756445, 0.745309, 1; 13:44:50.575 [planetinfo.record]: polar_land_color = 0.934, 0.807217, 0.878532, 1; 13:44:50.576 [planetinfo.record]: polar_sea_color = 0.926953, 0.821042, 0.821042, 1; 13:44:50.576 [planetinfo.record]: sea_color = 0.730469, 0.396622, 0.396622, 1; 13:44:50.576 [planetinfo.record]: rotation_speed = 0.002551 13:44:50.576 [planetinfo.record]: planet zpos = 744810.000000 13:44:50.576 [planetinfo.record]: sun_radius = 132984.812164 13:44:50.576 [planetinfo.record]: sun_vector = 0.480 0.047 -0.876 13:44:50.576 [planetinfo.record]: sun_distance = 1489620 13:44:50.576 [planetinfo.record]: corona_flare = 0.054478 13:44:50.576 [planetinfo.record]: corona_hues = 0.850319 13:44:50.576 [planetinfo.record]: sun_color = 0.847333, 0.728453, 0.382092, 1 13:44:50.576 [planetinfo.record]: corona_shimmer = 0.337133 13:44:50.577 [planetinfo.record]: station_vector = -0.607 -0.224 0.763 13:44:50.577 [planetinfo.record]: station = coriolis 13:44:55.430 [PLANETINFO OVER]: Done 13:44:55.430 [PLANETINFO LOGGING]: 5 93 13:44:56.450 [planetinfo.record]: seed = 141 173 32 187 13:44:56.451 [planetinfo.record]: coordinates = 173 144 13:44:56.451 [planetinfo.record]: government = 1; 13:44:56.451 [planetinfo.record]: economy = 2; 13:44:56.451 [planetinfo.record]: techlevel = 7; 13:44:56.451 [planetinfo.record]: population = 32; 13:44:56.451 [planetinfo.record]: productivity = 10240; 13:44:56.451 [planetinfo.record]: name = "Anorxe"; 13:44:56.451 [planetinfo.record]: inhabitant = "Human Colonial"; 13:44:56.451 [planetinfo.record]: inhabitants = "Human Colonials"; 13:44:56.451 [planetinfo.record]: description = "The world Anorxe is most well known for its vast dense forests."; 13:44:56.455 [planetinfo.record]: air_color = 0.53624, 0.498368, 0.911848, 1; 13:44:56.455 [planetinfo.record]: cloud_alpha = 1.000000; 13:44:56.455 [planetinfo.record]: cloud_color = 0.338477, 0.175919, 0.738281, 1; 13:44:56.455 [planetinfo.record]: cloud_fraction = 0.370000; 13:44:56.455 [planetinfo.record]: land_color = 0.66, 0.0773438, 0.50068, 1; 13:44:56.455 [planetinfo.record]: land_fraction = 0.320000; 13:44:56.455 [planetinfo.record]: polar_cloud_color = 0.550552, 0.436025, 0.832227, 1; 13:44:56.455 [planetinfo.record]: polar_land_color = 0.934, 0.727863, 0.877635, 1; 13:44:56.455 [planetinfo.record]: polar_sea_color = 0.948047, 0.910424, 0.900367, 1; 13:44:56.455 [planetinfo.record]: sea_color = 0.519531, 0.437062, 0.415016, 1; 13:44:56.455 [planetinfo.record]: rotation_speed = 0.004369 13:44:56.455 [planetinfo.record]: planet zpos = 812700.000000 13:44:56.456 [planetinfo.record]: sun_radius = 155241.410065 13:44:56.456 [planetinfo.record]: sun_vector = -0.477 0.235 0.847 13:44:56.456 [planetinfo.record]: sun_distance = 1335150 13:44:56.456 [planetinfo.record]: corona_flare = 0.093660 13:44:56.456 [planetinfo.record]: corona_hues = 0.972046 13:44:56.456 [planetinfo.record]: sun_color = 0.799182, 0.776784, 0.773665, 1 13:44:56.456 [planetinfo.record]: corona_shimmer = 0.497306 13:44:56.456 [planetinfo.record]: station_vector = -0.351 0.918 0.182 13:44:56.456 [planetinfo.record]: station = coriolis 13:45:01.524 [PLANETINFO OVER]: Done 13:45:01.525 [PLANETINFO LOGGING]: 5 94 13:45:02.529 [planetinfo.record]: seed = 133 22 234 113 13:45:02.529 [planetinfo.record]: coordinates = 22 98 13:45:02.529 [planetinfo.record]: government = 0; 13:45:02.529 [planetinfo.record]: economy = 2; 13:45:02.529 [planetinfo.record]: techlevel = 7; 13:45:02.529 [planetinfo.record]: population = 31; 13:45:02.529 [planetinfo.record]: productivity = 7936; 13:45:02.529 [planetinfo.record]: name = "Ataren"; 13:45:02.529 [planetinfo.record]: inhabitant = "Blue Fat Humanoid"; 13:45:02.529 [planetinfo.record]: inhabitants = "Blue Fat Humanoids"; 13:45:02.529 [planetinfo.record]: description = "The world Ataren is very famous for its unusual casinos but plagued by frequent solar activity."; 13:45:02.550 [planetinfo.record]: air_color = 0.669693, 0.837278, 0.930709, 1; 13:45:02.550 [planetinfo.record]: cloud_alpha = 1.000000; 13:45:02.550 [planetinfo.record]: cloud_color = 0.602402, 0.794922, 0.618946, 1; 13:45:02.550 [planetinfo.record]: cloud_fraction = 0.250000; 13:45:02.550 [planetinfo.record]: land_color = 0.645337, 0.190781, 0.66, 1; 13:45:02.550 [planetinfo.record]: land_fraction = 0.630000; 13:45:02.550 [planetinfo.record]: polar_cloud_color = 0.727885, 0.857715, 0.739042, 1; 13:45:02.550 [planetinfo.record]: polar_land_color = 0.928812, 0.767996, 0.934, 1; 13:45:02.550 [planetinfo.record]: polar_sea_color = 0.938281, 0.893465, 0.877898, 1; 13:45:02.550 [planetinfo.record]: sea_color = 0.617188, 0.499271, 0.45831, 1; 13:45:02.550 [planetinfo.record]: rotation_speed = 0.002672 13:45:02.552 [planetinfo.record]: planet zpos = 371280.000000 13:45:02.552 [planetinfo.record]: sun_radius = 68927.612305 13:45:02.552 [planetinfo.record]: sun_vector = 0.363 0.628 0.688 13:45:02.552 [planetinfo.record]: sun_distance = 556920 13:45:02.552 [planetinfo.record]: corona_flare = 0.082693 13:45:02.552 [planetinfo.record]: corona_hues = 0.759476 13:45:02.552 [planetinfo.record]: sun_color = 0.681311, 0.571149, 0.429055, 1 13:45:02.553 [planetinfo.record]: corona_shimmer = 0.860821 13:45:02.553 [planetinfo.record]: station_vector = -0.824 0.227 0.520 13:45:02.553 [planetinfo.record]: station = coriolis 13:45:07.556 [PLANETINFO OVER]: Done 13:45:07.557 [PLANETINFO LOGGING]: 5 95 13:45:08.562 [planetinfo.record]: seed = 73 207 168 44 13:45:08.562 [planetinfo.record]: coordinates = 207 114 13:45:08.562 [planetinfo.record]: government = 1; 13:45:08.562 [planetinfo.record]: economy = 2; 13:45:08.562 [planetinfo.record]: techlevel = 9; 13:45:08.562 [planetinfo.record]: population = 40; 13:45:08.562 [planetinfo.record]: productivity = 12800; 13:45:08.562 [planetinfo.record]: name = "Inrearbi"; 13:45:08.562 [planetinfo.record]: inhabitant = "Red Furry Humanoid"; 13:45:08.562 [planetinfo.record]: inhabitants = "Red Furry Humanoids"; 13:45:08.562 [planetinfo.record]: description = "This world is a tedious place."; 13:45:08.575 [planetinfo.record]: air_color = 0.581502, 0.91575, 0.80322, 1; 13:45:08.575 [planetinfo.record]: cloud_alpha = 1.000000; 13:45:08.575 [planetinfo.record]: cloud_color = 0.75, 0.37793, 0.37793, 1; 13:45:08.575 [planetinfo.record]: cloud_fraction = 0.150000; 13:45:08.575 [planetinfo.record]: land_color = 0.417656, 0.66, 0.569121, 1; 13:45:08.575 [planetinfo.record]: land_fraction = 0.360000; 13:45:08.575 [planetinfo.record]: polar_cloud_color = 0.8375, 0.577826, 0.577826, 1; 13:45:08.575 [planetinfo.record]: polar_land_color = 0.848262, 0.934, 0.901848, 1; 13:45:08.575 [planetinfo.record]: polar_sea_color = 0.932988, 0.935938, 0.882011, 1; 13:45:08.576 [planetinfo.record]: sea_color = 0.632551, 0.640625, 0.492981, 1; 13:45:08.576 [planetinfo.record]: rotation_speed = 0.002590 13:45:08.576 [planetinfo.record]: planet zpos = 731400.000000 13:45:08.576 [planetinfo.record]: sun_radius = 130867.283630 13:45:08.576 [planetinfo.record]: sun_vector = -0.171 0.271 -0.947 13:45:08.576 [planetinfo.record]: sun_distance = 1097100 13:45:08.576 [planetinfo.record]: corona_flare = 0.026721 13:45:08.576 [planetinfo.record]: corona_hues = 0.747253 13:45:08.576 [planetinfo.record]: sun_color = 0.641004, 0.652836, 0.671585, 1 13:45:08.584 [planetinfo.record]: corona_shimmer = 0.364680 13:45:08.584 [planetinfo.record]: station_vector = 0.008 -0.797 0.604 13:45:08.584 [planetinfo.record]: station = coriolis 13:45:13.478 [PLANETINFO OVER]: Done 13:45:13.478 [PLANETINFO LOGGING]: 5 96 13:45:14.562 [planetinfo.record]: seed = 73 6 234 223 13:45:14.562 [planetinfo.record]: coordinates = 6 106 13:45:14.562 [planetinfo.record]: government = 1; 13:45:14.562 [planetinfo.record]: economy = 2; 13:45:14.562 [planetinfo.record]: techlevel = 8; 13:45:14.562 [planetinfo.record]: population = 36; 13:45:14.562 [planetinfo.record]: productivity = 11520; 13:45:14.562 [planetinfo.record]: name = "Onertius"; 13:45:14.562 [planetinfo.record]: inhabitant = "Fat Insect"; 13:45:14.562 [planetinfo.record]: inhabitants = "Fat Insects"; 13:45:14.562 [planetinfo.record]: description = "This world is a revolting little planet."; 13:45:14.583 [planetinfo.record]: air_color = 0.555529, 0.942416, 0.833301, 1; 13:45:14.583 [planetinfo.record]: cloud_alpha = 1.000000; 13:45:14.583 [planetinfo.record]: cloud_color = 0.830078, 0.390974, 0.304794, 1; 13:45:14.583 [planetinfo.record]: cloud_fraction = 0.520000; 13:45:14.583 [planetinfo.record]: land_color = 0.379533, 0.313568, 0.585938, 1; 13:45:14.583 [planetinfo.record]: land_fraction = 0.620000; 13:45:14.583 [planetinfo.record]: polar_cloud_color = 0.873535, 0.584727, 0.528045, 1; 13:45:14.583 [planetinfo.record]: polar_land_color = 0.8585, 0.832005, 0.941406, 1; 13:45:14.583 [planetinfo.record]: polar_sea_color = 0.937305, 0.888408, 0.875336, 1; 13:45:14.583 [planetinfo.record]: sea_color = 0.626953, 0.496127, 0.461153, 1; 13:45:14.583 [planetinfo.record]: rotation_speed = 0.000376 13:45:14.583 [planetinfo.record]: planet zpos = 999300.000000 13:45:14.583 [planetinfo.record]: sun_radius = 184307.946472 13:45:14.583 [planetinfo.record]: sun_vector = 0.312 0.859 -0.407 13:45:14.584 [planetinfo.record]: sun_distance = 1265780 13:45:14.584 [planetinfo.record]: corona_flare = 0.081313 13:45:14.584 [planetinfo.record]: corona_hues = 0.954926 13:45:14.584 [planetinfo.record]: sun_color = 0.691568, 0.614853, 0.595203, 1 13:45:14.584 [planetinfo.record]: corona_shimmer = 0.719796 13:45:14.584 [planetinfo.record]: station_vector = 0.848 -0.062 0.527 13:45:14.584 [planetinfo.record]: station = coriolis 13:45:19.933 [PLANETINFO OVER]: Done 13:45:19.934 [PLANETINFO LOGGING]: 5 97 13:45:20.939 [planetinfo.record]: seed = 21 104 64 240 13:45:20.939 [planetinfo.record]: coordinates = 104 55 13:45:20.939 [planetinfo.record]: government = 2; 13:45:20.939 [planetinfo.record]: economy = 7; 13:45:20.939 [planetinfo.record]: techlevel = 1; 13:45:20.939 [planetinfo.record]: population = 14; 13:45:20.939 [planetinfo.record]: productivity = 2016; 13:45:20.939 [planetinfo.record]: name = "Eraso"; 13:45:20.939 [planetinfo.record]: inhabitant = "Human Colonial"; 13:45:20.939 [planetinfo.record]: inhabitants = "Human Colonials"; 13:45:20.940 [planetinfo.record]: description = "The planet Eraso is very noted for its exciting sit coms but ravaged by vicious killer cats."; 13:45:20.956 [planetinfo.record]: air_color = 0.666318, 0.826724, 0.943066, 1; 13:45:20.956 [planetinfo.record]: cloud_alpha = 1.000000; 13:45:20.956 [planetinfo.record]: cloud_color = 0.598022, 0.832031, 0.662009, 1; 13:45:20.956 [planetinfo.record]: cloud_fraction = 0.320000; 13:45:20.956 [planetinfo.record]: land_color = 0.640625, 0.372864, 0.592512, 1; 13:45:20.956 [planetinfo.record]: land_fraction = 0.640000; 13:45:20.956 [planetinfo.record]: polar_cloud_color = 0.720708, 0.874414, 0.762737, 1; 13:45:20.956 [planetinfo.record]: polar_land_color = 0.935938, 0.838139, 0.918364, 1; 13:45:20.956 [planetinfo.record]: polar_sea_color = 0.941992, 0.896324, 0.885785, 1; 13:45:20.956 [planetinfo.record]: sea_color = 0.580078, 0.467589, 0.44163, 1; 13:45:20.957 [planetinfo.record]: rotation_speed = 0.003446 13:45:20.957 [planetinfo.record]: planet zpos = 408800.000000 13:45:20.957 [planetinfo.record]: sun_radius = 55376.007080 13:45:20.957 [planetinfo.record]: sun_vector = -0.411 0.479 0.776 13:45:20.957 [planetinfo.record]: sun_distance = 584000 13:45:20.957 [planetinfo.record]: corona_flare = 0.063597 13:45:20.957 [planetinfo.record]: corona_hues = 0.755646 13:45:20.957 [planetinfo.record]: sun_color = 0.715891, 0.577194, 0.387911, 1 13:45:20.957 [planetinfo.record]: corona_shimmer = 0.878640 13:45:20.958 [planetinfo.record]: station_vector = -0.165 -0.552 0.817 13:45:20.958 [planetinfo.record]: station = coriolis 13:45:25.806 [PLANETINFO OVER]: Done 13:45:25.807 [PLANETINFO LOGGING]: 5 98 13:45:26.813 [planetinfo.record]: seed = 157 103 250 222 13:45:26.813 [planetinfo.record]: coordinates = 103 231 13:45:26.813 [planetinfo.record]: government = 3; 13:45:26.813 [planetinfo.record]: economy = 7; 13:45:26.813 [planetinfo.record]: techlevel = 5; 13:45:26.813 [planetinfo.record]: population = 31; 13:45:26.813 [planetinfo.record]: productivity = 5208; 13:45:26.813 [planetinfo.record]: name = "Rirelaxe"; 13:45:26.813 [planetinfo.record]: inhabitant = "Slimy Lizard"; 13:45:26.813 [planetinfo.record]: inhabitants = "Slimy Lizards"; 13:45:26.813 [planetinfo.record]: description = "The planet Rirelaxe is famous for its inhabitants’ exceptional loathing of sit coms but cursed by deadly disease."; 13:45:26.829 [planetinfo.record]: air_color = 0.611767, 0.566012, 0.863068, 1; 13:45:26.830 [planetinfo.record]: cloud_alpha = 1.000000; 13:45:26.830 [planetinfo.record]: cloud_color = 0.457483, 0.335197, 0.591797, 1; 13:45:26.830 [planetinfo.record]: cloud_fraction = 0.320000; 13:45:26.831 [planetinfo.record]: land_color = 0.383289, 0.324142, 0.568359, 1; 13:45:26.831 [planetinfo.record]: land_fraction = 0.460000; 13:45:26.831 [planetinfo.record]: polar_cloud_color = 0.657608, 0.558642, 0.766309, 1; 13:45:26.831 [planetinfo.record]: polar_land_color = 0.866385, 0.841848, 0.943164, 1; 13:45:26.831 [planetinfo.record]: polar_sea_color = 0.90572, 0.930273, 0.873131, 1; 13:45:26.831 [planetinfo.record]: sea_color = 0.623651, 0.697266, 0.525945, 1; 13:45:26.831 [planetinfo.record]: rotation_speed = 0.000547 13:45:26.831 [planetinfo.record]: planet zpos = 910420.000000 13:45:26.831 [planetinfo.record]: sun_radius = 211573.739624 13:45:26.831 [planetinfo.record]: sun_vector = -0.909 -0.293 0.295 13:45:26.831 [planetinfo.record]: sun_distance = 1365630 13:45:26.831 [planetinfo.record]: corona_flare = 0.017670 13:45:26.831 [planetinfo.record]: corona_hues = 0.901161 13:45:26.831 [planetinfo.record]: sun_color = 0.375407, 0.576698, 0.766199, 1 13:45:26.832 [planetinfo.record]: corona_shimmer = 0.386324 13:45:26.832 [planetinfo.record]: station_vector = -0.187 0.741 0.646 13:45:26.836 [planetinfo.record]: station = coriolis 13:45:31.868 [PLANETINFO OVER]: Done 13:45:31.870 [PLANETINFO LOGGING]: 5 99 13:45:32.876 [planetinfo.record]: seed = 113 130 232 37 13:45:32.876 [planetinfo.record]: coordinates = 130 117 13:45:32.876 [planetinfo.record]: government = 6; 13:45:32.876 [planetinfo.record]: economy = 5; 13:45:32.876 [planetinfo.record]: techlevel = 7; 13:45:32.876 [planetinfo.record]: population = 40; 13:45:32.876 [planetinfo.record]: productivity = 16000; 13:45:32.876 [planetinfo.record]: name = "Ceisce"; 13:45:32.876 [planetinfo.record]: inhabitant = "Fierce Red Rodent"; 13:45:32.876 [planetinfo.record]: inhabitants = "Fierce Red Rodents"; 13:45:32.876 [planetinfo.record]: description = "This planet is noted for its exciting sit coms."; 13:45:32.880 [planetinfo.record]: air_color = 0.549513, 0.961928, 0.897113, 1; 13:45:32.880 [planetinfo.record]: cloud_alpha = 1.000000; 13:45:32.880 [planetinfo.record]: cloud_color = 0.888672, 0.607057, 0.27771, 1; 13:45:32.880 [planetinfo.record]: cloud_fraction = 0.240000; 13:45:32.880 [planetinfo.record]: land_color = 0.66, 0.438966, 0.203672, 1; 13:45:32.881 [planetinfo.record]: land_fraction = 0.480000; 13:45:32.881 [planetinfo.record]: polar_cloud_color = 0.899902, 0.721668, 0.513226, 1; 13:45:32.881 [planetinfo.record]: polar_land_color = 0.934, 0.855801, 0.772557, 1; 13:45:32.881 [planetinfo.record]: polar_sea_color = 0.908973, 0.883767, 0.949609, 1; 13:45:32.881 [planetinfo.record]: sea_color = 0.417651, 0.364151, 0.503906, 1; 13:45:32.881 [planetinfo.record]: rotation_speed = 0.001638 13:45:32.881 [planetinfo.record]: planet zpos = 507120.000000 13:45:32.881 [planetinfo.record]: sun_radius = 118075.353088 13:45:32.881 [planetinfo.record]: sun_vector = 0.003 0.937 -0.350 13:45:32.881 [planetinfo.record]: sun_distance = 845200 13:45:32.881 [planetinfo.record]: corona_flare = 0.017409 13:45:32.881 [planetinfo.record]: corona_hues = 0.773987 13:45:32.881 [planetinfo.record]: sun_color = 0.755429, 0.722182, 0.698509, 1 13:45:32.881 [planetinfo.record]: corona_shimmer = 0.720255 13:45:32.882 [planetinfo.record]: station_vector = -0.411 -0.836 0.363 13:45:32.882 [planetinfo.record]: station = coriolis 13:45:38.299 [PLANETINFO OVER]: Done 13:45:38.299 [PLANETINFO LOGGING]: 5 100 13:45:39.314 [planetinfo.record]: seed = 1 9 26 236 13:45:39.314 [planetinfo.record]: coordinates = 9 197 13:45:39.314 [planetinfo.record]: government = 0; 13:45:39.314 [planetinfo.record]: economy = 7; 13:45:39.314 [planetinfo.record]: techlevel = 1; 13:45:39.314 [planetinfo.record]: population = 12; 13:45:39.314 [planetinfo.record]: productivity = 1152; 13:45:39.314 [planetinfo.record]: name = "Inqua"; 13:45:39.314 [planetinfo.record]: inhabitant = "Human Colonial"; 13:45:39.314 [planetinfo.record]: inhabitants = "Human Colonials"; 13:45:39.314 [planetinfo.record]: description = "This world is a revolting dump."; 13:45:39.341 [planetinfo.record]: air_color = 0.413185, 0.761701, 0.88193, 1; 13:45:39.342 [planetinfo.record]: cloud_alpha = 1.000000; 13:45:39.342 [planetinfo.record]: cloud_color = 0.143825, 0.648438, 0.00253296, 1; 13:45:39.342 [planetinfo.record]: cloud_fraction = 0.150000; 13:45:39.342 [planetinfo.record]: land_color = 0.0438281, 0.428936, 0.66, 1; 13:45:39.342 [planetinfo.record]: land_fraction = 0.290000; 13:45:39.342 [planetinfo.record]: polar_cloud_color = 0.406688, 0.791797, 0.298857, 1; 13:45:39.342 [planetinfo.record]: polar_land_color = 0.716006, 0.852252, 0.934, 1; 13:45:39.342 [planetinfo.record]: polar_sea_color = 0.936914, 0.925559, 0.88101, 1; 13:45:39.342 [planetinfo.record]: sea_color = 0.630859, 0.600275, 0.480291, 1; 13:45:39.342 [planetinfo.record]: rotation_speed = 0.003195 13:45:39.342 [planetinfo.record]: planet zpos = 589700.000000 13:45:39.342 [planetinfo.record]: sun_radius = 134865.440979 13:45:39.342 [planetinfo.record]: sun_vector = -0.096 -0.099 -0.990 13:45:39.342 [planetinfo.record]: sun_distance = 1179400 13:45:39.343 [planetinfo.record]: corona_flare = 0.072516 13:45:39.343 [planetinfo.record]: corona_hues = 0.757401 13:45:39.343 [planetinfo.record]: sun_color = 0.621639, 0.655144, 0.678662, 1 13:45:39.344 [planetinfo.record]: corona_shimmer = 0.402877 13:45:39.344 [planetinfo.record]: station_vector = -0.587 -0.392 0.709 13:45:39.344 [planetinfo.record]: station = coriolis 13:45:44.805 [PLANETINFO OVER]: Done 13:45:44.806 [PLANETINFO LOGGING]: 5 101 13:45:45.816 [planetinfo.record]: seed = 221 86 160 193 13:45:45.816 [planetinfo.record]: coordinates = 86 175 13:45:45.816 [planetinfo.record]: government = 3; 13:45:45.816 [planetinfo.record]: economy = 7; 13:45:45.816 [planetinfo.record]: techlevel = 4; 13:45:45.816 [planetinfo.record]: population = 27; 13:45:45.816 [planetinfo.record]: productivity = 4536; 13:45:45.817 [planetinfo.record]: name = "Leusar"; 13:45:45.817 [planetinfo.record]: inhabitant = "Large Bug-Eyed Lizard"; 13:45:45.817 [planetinfo.record]: inhabitants = "Large Bug-Eyed Lizards"; 13:45:45.817 [planetinfo.record]: description = "Leusar is ravaged by unpredictable solar activity."; 13:45:45.832 [planetinfo.record]: air_color = 0.605652, 0.505122, 0.924205, 1; 13:45:45.832 [planetinfo.record]: cloud_alpha = 1.000000; 13:45:45.832 [planetinfo.record]: cloud_color = 0.618505, 0.184761, 0.775391, 1; 13:45:45.832 [planetinfo.record]: cloud_fraction = 0.300000; 13:45:45.832 [planetinfo.record]: land_color = 0.45375, 0.66, 0.548818, 1; 13:45:45.832 [planetinfo.record]: land_fraction = 0.480000; 13:45:45.832 [planetinfo.record]: polar_cloud_color = 0.741573, 0.444774, 0.848926, 1; 13:45:45.833 [planetinfo.record]: polar_land_color = 0.861031, 0.934, 0.894665, 1; 13:45:45.833 [planetinfo.record]: polar_sea_color = 0.925977, 0.879597, 0.834645, 1; 13:45:45.833 [planetinfo.record]: sea_color = 0.740234, 0.59193, 0.448189, 1; 13:45:45.833 [planetinfo.record]: rotation_speed = 0.002484 13:45:45.833 [planetinfo.record]: planet zpos = 315800.000000 13:45:45.833 [planetinfo.record]: sun_radius = 91945.139160 13:45:45.833 [planetinfo.record]: sun_vector = 0.570 0.486 0.663 13:45:45.839 [planetinfo.record]: sun_distance = 505280 13:45:45.840 [planetinfo.record]: corona_flare = 0.058205 13:45:45.840 [planetinfo.record]: corona_hues = 0.564362 13:45:45.840 [planetinfo.record]: sun_color = 0.524867, 0.709619, 0.738528, 1 13:45:45.840 [planetinfo.record]: corona_shimmer = 0.302176 13:45:45.840 [planetinfo.record]: station_vector = 0.094 -0.947 0.307 13:45:45.840 [planetinfo.record]: station = coriolis 13:45:50.661 [PLANETINFO OVER]: Done 13:45:50.662 [PLANETINFO LOGGING]: 5 102 13:45:51.664 [planetinfo.record]: seed = 245 106 74 20 13:45:51.665 [planetinfo.record]: coordinates = 106 224 13:45:51.665 [planetinfo.record]: government = 6; 13:45:51.665 [planetinfo.record]: economy = 0; 13:45:51.665 [planetinfo.record]: techlevel = 12; 13:45:51.665 [planetinfo.record]: population = 55; 13:45:51.665 [planetinfo.record]: productivity = 44000; 13:45:51.665 [planetinfo.record]: name = "Raonbe"; 13:45:51.665 [planetinfo.record]: inhabitant = "Human Colonial"; 13:45:51.665 [planetinfo.record]: inhabitants = "Human Colonials"; 13:45:51.665 [planetinfo.record]: description = "The world Raonbe is a dull world."; 13:45:51.680 [planetinfo.record]: air_color = 0.828389, 0.762633, 0.980139, 1; 13:45:51.680 [planetinfo.record]: cloud_alpha = 1.000000; 13:45:51.680 [planetinfo.record]: cloud_color = 0.938465, 0.880714, 0.943359, 1; 13:45:51.680 [planetinfo.record]: cloud_fraction = 0.380000; 13:45:51.680 [planetinfo.record]: land_color = 0.04125, 0.485977, 0.66, 1; 13:45:51.680 [planetinfo.record]: land_fraction = 0.550000; 13:45:51.680 [planetinfo.record]: polar_cloud_color = 0.921514, 0.886141, 0.924512, 1; 13:45:51.680 [planetinfo.record]: polar_land_color = 0.715094, 0.872433, 0.934, 1; 13:45:51.682 [planetinfo.record]: polar_sea_color = 0.942578, 0.9079, 0.889098, 1; 13:45:51.682 [planetinfo.record]: sea_color = 0.574219, 0.489714, 0.443898, 1; 13:45:51.682 [planetinfo.record]: rotation_speed = 0.003267 13:45:51.682 [planetinfo.record]: planet zpos = 473520.000000 13:45:51.682 [planetinfo.record]: sun_radius = 91041.715088 13:45:51.682 [planetinfo.record]: sun_vector = 0.388 0.921 -0.038 13:45:51.682 [planetinfo.record]: sun_distance = 907580 13:45:51.682 [planetinfo.record]: corona_flare = 0.041824 13:45:51.682 [planetinfo.record]: corona_hues = 0.977196 13:45:51.682 [planetinfo.record]: sun_color = 0.819821, 0.807429, 0.802764, 1 13:45:51.682 [planetinfo.record]: corona_shimmer = 0.282099 13:45:51.682 [planetinfo.record]: station_vector = -0.601 0.587 0.542 13:45:51.682 [planetinfo.record]: station = icosahedron 13:45:57.125 [PLANETINFO OVER]: Done 13:45:57.126 [PLANETINFO LOGGING]: 5 103 13:45:58.130 [planetinfo.record]: seed = 217 83 104 147 13:45:58.131 [planetinfo.record]: coordinates = 83 223 13:45:58.131 [planetinfo.record]: government = 3; 13:45:58.131 [planetinfo.record]: economy = 7; 13:45:58.131 [planetinfo.record]: techlevel = 5; 13:45:58.131 [planetinfo.record]: population = 31; 13:45:58.131 [planetinfo.record]: productivity = 5208; 13:45:58.131 [planetinfo.record]: name = "Bebidiso"; 13:45:58.131 [planetinfo.record]: inhabitant = "Human Colonial"; 13:45:58.131 [planetinfo.record]: inhabitants = "Human Colonials"; 13:45:58.131 [planetinfo.record]: description = "This world is very notable for its inhabitants’ weird silliness and its inhabitants’ ingrained silliness."; 13:45:58.142 [planetinfo.record]: air_color = 0.737899, 0.606927, 0.870223, 1; 13:45:58.142 [planetinfo.record]: cloud_alpha = 1.000000; 13:45:58.142 [planetinfo.record]: cloud_color = 0.613281, 0.424026, 0.517175, 1; 13:45:58.142 [planetinfo.record]: cloud_fraction = 0.200000; 13:45:58.143 [planetinfo.record]: land_color = 0.244217, 0.446806, 0.568359, 1; 13:45:58.143 [planetinfo.record]: land_fraction = 0.630000; 13:45:58.143 [planetinfo.record]: polar_cloud_color = 0.775977, 0.626313, 0.699976, 1; 13:45:58.143 [planetinfo.record]: polar_land_color = 0.808689, 0.892736, 0.943164, 1; 13:45:58.143 [planetinfo.record]: polar_sea_color = 0.920898, 0.883493, 0.812081, 1; 13:45:58.143 [planetinfo.record]: sea_color = 0.791016, 0.662495, 0.417137, 1; 13:45:58.143 [planetinfo.record]: rotation_speed = 0.000743 13:45:58.143 [planetinfo.record]: planet zpos = 513380.000000 13:45:58.143 [planetinfo.record]: sun_radius = 76559.032440 13:45:58.143 [planetinfo.record]: sun_vector = 0.288 0.826 0.485 13:45:58.143 [planetinfo.record]: sun_distance = 623390 13:45:58.143 [planetinfo.record]: corona_flare = 0.037552 13:45:58.143 [planetinfo.record]: corona_hues = 0.603447 13:45:58.143 [planetinfo.record]: sun_color = 0.807596, 0.561044, 0.531513, 1 13:45:58.143 [planetinfo.record]: corona_shimmer = 0.682056 13:45:58.144 [planetinfo.record]: station_vector = 0.854 -0.401 0.332 13:45:58.144 [planetinfo.record]: station = coriolis 13:46:03.868 [PLANETINFO OVER]: Done 13:46:03.868 [PLANETINFO LOGGING]: 5 104 13:46:04.886 [planetinfo.record]: seed = 249 7 138 124 13:46:04.886 [planetinfo.record]: coordinates = 7 173 13:46:04.886 [planetinfo.record]: government = 7; 13:46:04.886 [planetinfo.record]: economy = 5; 13:46:04.886 [planetinfo.record]: techlevel = 9; 13:46:04.886 [planetinfo.record]: population = 49; 13:46:04.886 [planetinfo.record]: productivity = 21560; 13:46:04.886 [planetinfo.record]: name = "Teenvece"; 13:46:04.886 [planetinfo.record]: inhabitant = "Blue Horned Lizard"; 13:46:04.886 [planetinfo.record]: inhabitants = "Blue Horned Lizards"; 13:46:04.886 [planetinfo.record]: description = "The world Teenvece is beset by dreadful earthquakes."; 13:46:04.904 [planetinfo.record]: air_color = 0.788603, 0.587513, 0.906645, 1; 13:46:04.904 [planetinfo.record]: cloud_alpha = 1.000000; 13:46:04.904 [planetinfo.record]: cloud_color = 0.722656, 0.39238, 0.423343, 1; 13:46:04.904 [planetinfo.record]: cloud_fraction = 0.270000; 13:46:04.904 [planetinfo.record]: land_color = 0.649688, 0.659033, 0.66, 1; 13:46:04.904 [planetinfo.record]: land_fraction = 0.600000; 13:46:04.904 [planetinfo.record]: polar_cloud_color = 0.825195, 0.589483, 0.611581, 1; 13:46:04.904 [planetinfo.record]: polar_land_color = 0.930352, 0.933658, 0.934, 1; 13:46:04.904 [planetinfo.record]: polar_sea_color = 0.888346, 0.866964, 0.947461, 1; 13:46:04.904 [planetinfo.record]: sea_color = 0.394267, 0.34684, 0.525391, 1; 13:46:04.904 [planetinfo.record]: rotation_speed = 0.000956 13:46:04.904 [planetinfo.record]: planet zpos = 766350.000000 13:46:04.904 [planetinfo.record]: sun_radius = 156341.271973 13:46:04.904 [planetinfo.record]: sun_vector = -0.002 0.867 -0.499 13:46:04.904 [planetinfo.record]: sun_distance = 1179000 13:46:04.905 [planetinfo.record]: corona_flare = 0.074175 13:46:04.905 [planetinfo.record]: corona_hues = 0.903252 13:46:04.905 [planetinfo.record]: sun_color = 0.762137, 0.445974, 0.198785, 1 13:46:04.905 [planetinfo.record]: corona_shimmer = 0.343642 13:46:04.905 [planetinfo.record]: station_vector = -0.949 0.259 0.182 13:46:04.905 [planetinfo.record]: station = coriolis 13:46:10.388 [PLANETINFO OVER]: Done 13:46:10.389 [PLANETINFO LOGGING]: 5 105 13:46:11.394 [planetinfo.record]: seed = 229 101 64 79 13:46:11.394 [planetinfo.record]: coordinates = 101 182 13:46:11.394 [planetinfo.record]: government = 4; 13:46:11.394 [planetinfo.record]: economy = 6; 13:46:11.394 [planetinfo.record]: techlevel = 4; 13:46:11.394 [planetinfo.record]: population = 27; 13:46:11.395 [planetinfo.record]: productivity = 6912; 13:46:11.395 [planetinfo.record]: name = "Ainlete"; 13:46:11.395 [planetinfo.record]: inhabitant = "Human Colonial"; 13:46:11.395 [planetinfo.record]: inhabitants = "Human Colonials"; 13:46:11.395 [planetinfo.record]: description = "This planet is noted for mud hockey."; 13:46:11.430 [planetinfo.record]: air_color = 0.432525, 0.835102, 0.831531, 1; 13:46:11.430 [planetinfo.record]: cloud_alpha = 1.000000; 13:46:11.430 [planetinfo.record]: cloud_color = 0.507812, 0.501087, 0.0773621, 1; 13:46:11.430 [planetinfo.record]: cloud_fraction = 0.220000; 13:46:11.430 [planetinfo.record]: land_color = 0.337694, 0.66, 0.335156, 1; 13:46:11.430 [planetinfo.record]: land_fraction = 0.610000; 13:46:11.430 [planetinfo.record]: polar_cloud_color = 0.728516, 0.722485, 0.342559, 1; 13:46:11.430 [planetinfo.record]: polar_land_color = 0.819972, 0.934, 0.819074, 1; 13:46:11.431 [planetinfo.record]: polar_sea_color = 0.94707, 0.912437, 0.898884, 1; 13:46:11.431 [planetinfo.record]: sea_color = 0.529297, 0.451873, 0.421577, 1; 13:46:11.431 [planetinfo.record]: rotation_speed = 0.001496 13:46:11.431 [planetinfo.record]: planet zpos = 878410.000000 13:46:11.431 [planetinfo.record]: sun_radius = 115180.166779 13:46:11.431 [planetinfo.record]: sun_vector = 0.278 -0.709 -0.648 13:46:11.431 [planetinfo.record]: sun_distance = 1351400 13:46:11.431 [planetinfo.record]: corona_flare = 0.091750 13:46:11.431 [planetinfo.record]: corona_hues = 0.643791 13:46:11.431 [planetinfo.record]: sun_color = 0.213063, 0.68088, 0.701489, 1 13:46:11.431 [planetinfo.record]: corona_shimmer = 0.249027 13:46:11.431 [planetinfo.record]: station_vector = -0.260 0.946 0.192 13:46:11.432 [planetinfo.record]: station = coriolis 13:46:16.324 [PLANETINFO OVER]: Done 13:46:16.325 [PLANETINFO LOGGING]: 5 106 13:46:17.331 [planetinfo.record]: seed = 141 220 218 105 13:46:17.331 [planetinfo.record]: coordinates = 220 33 13:46:17.331 [planetinfo.record]: government = 1; 13:46:17.331 [planetinfo.record]: economy = 3; 13:46:17.331 [planetinfo.record]: techlevel = 5; 13:46:17.331 [planetinfo.record]: population = 25; 13:46:17.331 [planetinfo.record]: productivity = 7000; 13:46:17.331 [planetinfo.record]: name = "Essore"; 13:46:17.331 [planetinfo.record]: inhabitant = "Small Blue Furry Feline"; 13:46:17.331 [planetinfo.record]: inhabitants = "Small Blue Furry Felines"; 13:46:17.331 [planetinfo.record]: description = "The planet Essore is most well known for its hoopy casinos."; 13:46:17.352 [planetinfo.record]: air_color = 0.682062, 0.86502, 0.861969, 1; 13:46:17.352 [planetinfo.record]: cloud_alpha = 1.000000; 13:46:17.352 [planetinfo.record]: cloud_color = 0.597656, 0.596835, 0.576645, 1; 13:46:17.352 [planetinfo.record]: cloud_fraction = 0.350000; 13:46:17.352 [planetinfo.record]: land_color = 0.567188, 0.581689, 0.66, 1; 13:46:17.352 [planetinfo.record]: land_fraction = 0.450000; 13:46:17.352 [planetinfo.record]: polar_cloud_color = 0.768945, 0.768285, 0.75205, 1; 13:46:17.352 [planetinfo.record]: polar_land_color = 0.901164, 0.906295, 0.934, 1; 13:46:17.352 [planetinfo.record]: polar_sea_color = 0.784337, 0.948242, 0.794581, 1; 13:46:17.352 [planetinfo.record]: sea_color = 0.159721, 0.517578, 0.182087, 1; 13:46:17.352 [planetinfo.record]: rotation_speed = 0.001136 13:46:17.352 [planetinfo.record]: planet zpos = 747600.000000 13:46:17.352 [planetinfo.record]: sun_radius = 151044.689941 13:46:17.352 [planetinfo.record]: sun_vector = 0.319 -0.763 0.563 13:46:17.352 [planetinfo.record]: sun_distance = 1068000 13:46:17.352 [planetinfo.record]: corona_flare = 0.016597 13:46:17.352 [planetinfo.record]: corona_hues = 0.616821 13:46:17.352 [planetinfo.record]: sun_color = 0.800558, 0.68409, 0.442172, 1 13:46:17.352 [planetinfo.record]: corona_shimmer = 0.369333 13:46:17.352 [planetinfo.record]: station_vector = -0.456 -0.862 0.220 13:46:17.352 [planetinfo.record]: station = coriolis 13:46:22.692 [PLANETINFO OVER]: Done 13:46:22.693 [PLANETINFO LOGGING]: 5 107 13:46:23.707 [planetinfo.record]: seed = 129 127 40 149 13:46:23.707 [planetinfo.record]: coordinates = 127 174 13:46:23.707 [planetinfo.record]: government = 0; 13:46:23.707 [planetinfo.record]: economy = 6; 13:46:23.707 [planetinfo.record]: techlevel = 4; 13:46:23.707 [planetinfo.record]: population = 23; 13:46:23.707 [planetinfo.record]: productivity = 2944; 13:46:23.707 [planetinfo.record]: name = "Laxeti"; 13:46:23.707 [planetinfo.record]: inhabitant = "Human Colonial"; 13:46:23.707 [planetinfo.record]: inhabitants = "Human Colonials"; 13:46:23.707 [planetinfo.record]: description = "Laxeti is cursed by vicious killer shrews."; 13:46:23.710 [planetinfo.record]: air_color = 0.557736, 0.961429, 0.980789, 1; 13:46:23.711 [planetinfo.record]: cloud_alpha = 1.000000; 13:46:23.711 [planetinfo.record]: cloud_color = 0.847746, 0.945312, 0.288025, 1; 13:46:23.711 [planetinfo.record]: cloud_fraction = 0.260000; 13:46:23.711 [planetinfo.record]: land_color = 0.144579, 0.628906, 0.0270233, 1; 13:46:23.711 [planetinfo.record]: land_fraction = 0.440000; 13:46:23.711 [planetinfo.record]: polar_cloud_color = 0.865697, 0.925391, 0.523243, 1; 13:46:23.711 [planetinfo.record]: polar_land_color = 0.75669, 0.937109, 0.712899, 1; 13:46:23.711 [planetinfo.record]: polar_sea_color = 0.930206, 0.937695, 0.887239, 1; 13:46:23.711 [planetinfo.record]: sea_color = 0.603141, 0.623047, 0.488946, 1; 13:46:23.711 [planetinfo.record]: rotation_speed = 0.004813 13:46:23.711 [planetinfo.record]: planet zpos = 506760.000000 13:46:23.711 [planetinfo.record]: sun_radius = 90910.874786 13:46:23.711 [planetinfo.record]: sun_vector = 0.093 0.114 -0.989 13:46:23.711 [planetinfo.record]: sun_distance = 929060 13:46:23.711 [planetinfo.record]: corona_flare = 0.077461 13:46:23.711 [planetinfo.record]: corona_hues = 0.559715 13:46:23.711 [planetinfo.record]: sun_color = 0.823795, 0.434364, 0.375494, 1 13:46:23.711 [planetinfo.record]: corona_shimmer = 0.800332 13:46:23.712 [planetinfo.record]: station_vector = -0.332 -0.918 0.216 13:46:23.712 [planetinfo.record]: station = coriolis 13:46:28.299 [PLANETINFO OVER]: Done 13:46:28.300 [PLANETINFO LOGGING]: 5 108 13:46:29.304 [planetinfo.record]: seed = 49 47 58 201 13:46:29.304 [planetinfo.record]: coordinates = 47 215 13:46:29.304 [planetinfo.record]: government = 6; 13:46:29.304 [planetinfo.record]: economy = 7; 13:46:29.304 [planetinfo.record]: techlevel = 6; 13:46:29.304 [planetinfo.record]: population = 38; 13:46:29.304 [planetinfo.record]: productivity = 9120; 13:46:29.304 [planetinfo.record]: name = "Esausle"; 13:46:29.304 [planetinfo.record]: inhabitant = "Human Colonial"; 13:46:29.304 [planetinfo.record]: inhabitants = "Human Colonials"; 13:46:29.304 [planetinfo.record]: description = "This world is very notable for its inhabitants’ ingrained shyness."; 13:46:29.320 [planetinfo.record]: air_color = 0.72032, 0.763735, 0.94957, 1; 13:46:29.320 [planetinfo.record]: cloud_alpha = 1.000000; 13:46:29.320 [planetinfo.record]: cloud_color = 0.745117, 0.804161, 0.851562, 1; 13:46:29.320 [planetinfo.record]: cloud_fraction = 0.250000; 13:46:29.320 [planetinfo.record]: land_color = 0.66, 0.587813, 0.638569, 1; 13:46:29.320 [planetinfo.record]: land_fraction = 0.320000; 13:46:29.320 [planetinfo.record]: polar_cloud_color = 0.814203, 0.852476, 0.883203, 1; 13:46:29.320 [planetinfo.record]: polar_land_color = 0.934, 0.908461, 0.926418, 1; 13:46:29.320 [planetinfo.record]: polar_sea_color = 0.939453, 0.919578, 0.885324, 1; 13:46:29.320 [planetinfo.record]: sea_color = 0.605469, 0.554231, 0.465927, 1; 13:46:29.320 [planetinfo.record]: rotation_speed = 0.003810 13:46:29.320 [planetinfo.record]: planet zpos = 775050.000000 13:46:29.320 [planetinfo.record]: sun_radius = 134409.331207 13:46:29.320 [planetinfo.record]: sun_vector = -0.731 0.079 -0.678 13:46:29.320 [planetinfo.record]: sun_distance = 1188410 13:46:29.320 [planetinfo.record]: corona_flare = 0.015036 13:46:29.320 [planetinfo.record]: corona_hues = 0.967941 13:46:29.320 [planetinfo.record]: sun_color = 0.701918, 0.70466, 0.775824, 1 13:46:29.320 [planetinfo.record]: corona_shimmer = 0.253438 13:46:29.320 [planetinfo.record]: station_vector = 0.057 0.640 0.767 13:46:29.320 [planetinfo.record]: station = coriolis 13:46:33.757 [PLANETINFO OVER]: Done 13:46:33.757 [PLANETINFO LOGGING]: 5 109 13:46:34.761 [planetinfo.record]: seed = 45 97 32 249 13:46:34.761 [planetinfo.record]: coordinates = 97 200 13:46:34.761 [planetinfo.record]: government = 5; 13:46:34.761 [planetinfo.record]: economy = 0; 13:46:34.761 [planetinfo.record]: techlevel = 11; 13:46:34.761 [planetinfo.record]: population = 50; 13:46:34.761 [planetinfo.record]: productivity = 36000; 13:46:34.761 [planetinfo.record]: name = "Orxete"; 13:46:34.761 [planetinfo.record]: inhabitant = "Human Colonial"; 13:46:34.761 [planetinfo.record]: inhabitants = "Human Colonials"; 13:46:34.762 [planetinfo.record]: description = "The planet Orxete is very noted for its unusual sit coms but plagued by unpredictable civil war."; 13:46:34.767 [planetinfo.record]: air_color = 0.712553, 0.740751, 0.96583, 1; 13:46:34.767 [planetinfo.record]: cloud_alpha = 1.000000; 13:46:34.767 [planetinfo.record]: cloud_color = 0.731567, 0.785644, 0.900391, 1; 13:46:34.767 [planetinfo.record]: cloud_fraction = 0.230000; 13:46:34.767 [planetinfo.record]: land_color = 0.66, 0.236664, 0.180469, 1; 13:46:34.767 [planetinfo.record]: land_fraction = 0.660000; 13:46:34.768 [planetinfo.record]: polar_cloud_color = 0.799101, 0.833078, 0.905176, 1; 13:46:34.768 [planetinfo.record]: polar_land_color = 0.934, 0.784229, 0.764348, 1; 13:46:34.768 [planetinfo.record]: polar_sea_color = 0.94082, 0.865481, 0.929049, 1; 13:46:34.768 [planetinfo.record]: sea_color = 0.591797, 0.402237, 0.562178, 1; 13:46:34.768 [planetinfo.record]: rotation_speed = 0.000636 13:46:34.768 [planetinfo.record]: planet zpos = 678210.000000 13:46:34.768 [planetinfo.record]: sun_radius = 107019.507751 13:46:34.768 [planetinfo.record]: sun_vector = 0.032 -0.147 -0.989 13:46:34.768 [planetinfo.record]: sun_distance = 886890 13:46:34.768 [planetinfo.record]: corona_flare = 0.096556 13:46:34.768 [planetinfo.record]: corona_hues = 0.894257 13:46:34.768 [planetinfo.record]: sun_color = 0.722217, 0.70441, 0.560675, 1 13:46:34.768 [planetinfo.record]: corona_shimmer = 0.661098 13:46:34.768 [planetinfo.record]: station_vector = -0.144 -0.377 0.915 13:46:34.768 [planetinfo.record]: station = dodecahedron 13:46:40.116 [PLANETINFO OVER]: Done 13:46:40.117 [PLANETINFO LOGGING]: 5 110 13:46:41.121 [planetinfo.record]: seed = 101 152 170 55 13:46:41.121 [planetinfo.record]: coordinates = 152 124 13:46:41.121 [planetinfo.record]: government = 4; 13:46:41.121 [planetinfo.record]: economy = 4; 13:46:41.121 [planetinfo.record]: techlevel = 5; 13:46:41.121 [planetinfo.record]: population = 29; 13:46:41.121 [planetinfo.record]: productivity = 11136; 13:46:41.121 [planetinfo.record]: name = "Tiintele"; 13:46:41.121 [planetinfo.record]: inhabitant = "Red Fat Insect"; 13:46:41.121 [planetinfo.record]: inhabitants = "Red Fat Insects"; 13:46:41.121 [planetinfo.record]: description = "This world is plagued by deadly earthquakes."; 13:46:41.147 [planetinfo.record]: air_color = 0.628963, 0.900141, 0.863872, 1; 13:46:41.147 [planetinfo.record]: cloud_alpha = 1.000000; 13:46:41.147 [planetinfo.record]: cloud_color = 0.703125, 0.61944, 0.488892, 1; 13:46:41.147 [planetinfo.record]: cloud_fraction = 0.200000; 13:46:41.147 [planetinfo.record]: land_color = 0.66, 0.355781, 0.384302, 1; 13:46:41.147 [planetinfo.record]: land_fraction = 0.400000; 13:46:41.147 [planetinfo.record]: polar_cloud_color = 0.816406, 0.755677, 0.660938, 1; 13:46:41.147 [planetinfo.record]: polar_land_color = 0.934, 0.826371, 0.836461, 1; 13:46:41.147 [planetinfo.record]: polar_sea_color = 0.902037, 0.932813, 0.880979, 1; 13:46:41.147 [planetinfo.record]: sea_color = 0.583207, 0.671875, 0.52254, 1; 13:46:41.147 [planetinfo.record]: rotation_speed = 0.003915 13:46:41.148 [planetinfo.record]: planet zpos = 571200.000000 13:46:41.148 [planetinfo.record]: sun_radius = 110154.168701 13:46:41.148 [planetinfo.record]: sun_vector = -0.531 -0.656 -0.536 13:46:41.148 [planetinfo.record]: sun_distance = 809200 13:46:41.148 [planetinfo.record]: corona_flare = 0.068076 13:46:41.148 [planetinfo.record]: corona_hues = 0.798294 13:46:41.148 [planetinfo.record]: sun_color = 0.276007, 0.744365, 0.786026, 1 13:46:41.148 [planetinfo.record]: corona_shimmer = 0.675016 13:46:41.148 [planetinfo.record]: station_vector = -0.030 -0.901 0.433 13:46:41.148 [planetinfo.record]: station = coriolis 13:46:46.391 [PLANETINFO OVER]: Done 13:46:46.392 [PLANETINFO LOGGING]: 5 111 13:46:47.397 [planetinfo.record]: seed = 105 161 40 11 13:46:47.397 [planetinfo.record]: coordinates = 161 28 13:46:47.397 [planetinfo.record]: government = 5; 13:46:47.397 [planetinfo.record]: economy = 4; 13:46:47.397 [planetinfo.record]: techlevel = 7; 13:46:47.397 [planetinfo.record]: population = 38; 13:46:47.397 [planetinfo.record]: productivity = 16416; 13:46:47.397 [planetinfo.record]: name = "Maesvear"; 13:46:47.397 [planetinfo.record]: inhabitant = "Human Colonial"; 13:46:47.397 [planetinfo.record]: inhabitants = "Human Colonials"; 13:46:47.397 [planetinfo.record]: description = "This planet is notable for the Maesvearian edible moth and its great dense forests."; 13:46:47.428 [planetinfo.record]: air_color = 0.570097, 0.516446, 0.904043, 1; 13:46:47.428 [planetinfo.record]: cloud_alpha = 1.000000; 13:46:47.428 [planetinfo.record]: cloud_color = 0.434561, 0.223389, 0.714844, 1; 13:46:47.428 [planetinfo.record]: cloud_fraction = 0.490000; 13:46:47.428 [planetinfo.record]: land_color = 0.591797, 0.399925, 0.561817, 1; 13:46:47.428 [planetinfo.record]: land_fraction = 0.520000; 13:46:47.428 [planetinfo.record]: polar_cloud_color = 0.620322, 0.468614, 0.82168, 1; 13:46:47.428 [planetinfo.record]: polar_land_color = 0.94082, 0.864562, 0.928905, 1; 13:46:47.429 [planetinfo.record]: polar_sea_color = 0.93418, 0.926377, 0.875428, 1; 13:46:47.429 [planetinfo.record]: sea_color = 0.658203, 0.636212, 0.492624, 1; 13:46:47.429 [planetinfo.record]: rotation_speed = 0.003818 13:46:47.429 [planetinfo.record]: planet zpos = 811020.000000 13:46:47.429 [planetinfo.record]: sun_radius = 120279.708252 13:46:47.429 [planetinfo.record]: sun_vector = 0.312 -0.582 -0.750 13:46:47.429 [planetinfo.record]: sun_distance = 1100670 13:46:47.429 [planetinfo.record]: corona_flare = 0.051833 13:46:47.429 [planetinfo.record]: corona_hues = 0.702217 13:46:47.429 [planetinfo.record]: sun_color = 0.705737, 0.573907, 0.468425, 1 13:46:47.429 [planetinfo.record]: corona_shimmer = 0.297456 13:46:47.430 [planetinfo.record]: station_vector = -0.591 0.650 0.478 13:46:47.430 [planetinfo.record]: station = coriolis 13:46:52.345 [PLANETINFO OVER]: Done 13:46:52.346 [PLANETINFO LOGGING]: 5 112 13:46:53.349 [planetinfo.record]: seed = 169 74 42 138 13:46:53.349 [planetinfo.record]: coordinates = 74 118 13:46:53.349 [planetinfo.record]: government = 5; 13:46:53.349 [planetinfo.record]: economy = 6; 13:46:53.350 [planetinfo.record]: techlevel = 6; 13:46:53.350 [planetinfo.record]: population = 36; 13:46:53.350 [planetinfo.record]: productivity = 10368; 13:46:53.350 [planetinfo.record]: name = "Araron"; 13:46:53.350 [planetinfo.record]: inhabitant = "Human Colonial"; 13:46:53.350 [planetinfo.record]: inhabitants = "Human Colonials"; 13:46:53.350 [planetinfo.record]: description = "This world is fabled for its ancient Araronian Re tulip plantations."; 13:46:53.360 [planetinfo.record]: air_color = 0.666103, 0.576245, 0.873475, 1; 13:46:53.360 [planetinfo.record]: cloud_alpha = 1.000000; 13:46:53.360 [planetinfo.record]: cloud_color = 0.602512, 0.360199, 0.623047, 1; 13:46:53.360 [planetinfo.record]: cloud_fraction = 0.370000; 13:46:53.360 [planetinfo.record]: land_color = 0.518203, 0.540359, 0.66, 1; 13:46:53.360 [planetinfo.record]: land_fraction = 0.440000; 13:46:53.360 [planetinfo.record]: polar_cloud_color = 0.764296, 0.574609, 0.780371, 1; 13:46:53.360 [planetinfo.record]: polar_land_color = 0.883834, 0.891672, 0.934, 1; 13:46:53.360 [planetinfo.record]: polar_sea_color = 0.949805, 0.827354, 0.729049, 1; 13:46:53.360 [planetinfo.record]: sea_color = 0.501953, 0.243103, 0.0352936, 1; 13:46:53.360 [planetinfo.record]: rotation_speed = 0.001597 13:46:53.360 [planetinfo.record]: planet zpos = 654000.000000 13:46:53.360 [planetinfo.record]: sun_radius = 131490.730286 13:46:53.360 [planetinfo.record]: sun_vector = -0.544 0.814 -0.204 13:46:53.360 [planetinfo.record]: sun_distance = 1035500 13:46:53.360 [planetinfo.record]: corona_flare = 0.029446 13:46:53.360 [planetinfo.record]: corona_hues = 0.831406 13:46:53.360 [planetinfo.record]: sun_color = 0.785997, 0.822162, 0.830225, 1 13:46:53.360 [planetinfo.record]: corona_shimmer = 0.504915 13:46:53.361 [planetinfo.record]: station_vector = 0.927 -0.370 0.065 13:46:53.361 [planetinfo.record]: station = coriolis 13:46:58.528 [PLANETINFO OVER]: Done 13:46:58.529 [PLANETINFO LOGGING]: 5 113 13:46:59.533 [planetinfo.record]: seed = 181 244 64 95 13:46:59.533 [planetinfo.record]: coordinates = 244 31 13:46:59.533 [planetinfo.record]: government = 6; 13:46:59.533 [planetinfo.record]: economy = 7; 13:46:59.533 [planetinfo.record]: techlevel = 3; 13:46:59.533 [planetinfo.record]: population = 26; 13:46:59.533 [planetinfo.record]: productivity = 6240; 13:46:59.533 [planetinfo.record]: name = "Onbeso"; 13:46:59.533 [planetinfo.record]: inhabitant = "Human Colonial"; 13:46:59.533 [planetinfo.record]: inhabitants = "Human Colonials"; 13:46:59.533 [planetinfo.record]: description = "The planet Onbeso is reasonably noted for its inhabitants’ exceptional love for tourists and its unusual dense forests."; 13:46:59.552 [planetinfo.record]: air_color = 0.458183, 0.655655, 0.878027, 1; 13:46:59.552 [planetinfo.record]: cloud_alpha = 1.000000; 13:46:59.552 [planetinfo.record]: cloud_color = 0.104462, 0.636719, 0.424648, 1; 13:46:59.552 [planetinfo.record]: cloud_fraction = 0.250000; 13:46:59.552 [planetinfo.record]: land_color = 0.578024, 0.66, 0.376406, 1; 13:46:59.552 [planetinfo.record]: land_fraction = 0.590000; 13:46:59.552 [planetinfo.record]: polar_cloud_color = 0.375596, 0.786523, 0.622794, 1; 13:46:59.552 [planetinfo.record]: polar_land_color = 0.904998, 0.934, 0.833668, 1; 13:46:59.552 [planetinfo.record]: polar_sea_color = 0.91524, 0.933398, 0.879345, 1; 13:46:59.552 [planetinfo.record]: sea_color = 0.614188, 0.666016, 0.511739, 1; 13:46:59.552 [planetinfo.record]: rotation_speed = 0.004650 13:46:59.552 [planetinfo.record]: planet zpos = 759000.000000 13:46:59.552 [planetinfo.record]: sun_radius = 227076.919556 13:46:59.552 [planetinfo.record]: sun_vector = -0.923 -0.024 0.383 13:46:59.552 [planetinfo.record]: sun_distance = 1242000 13:46:59.552 [planetinfo.record]: corona_flare = 0.028790 13:46:59.552 [planetinfo.record]: corona_hues = 0.929810 13:46:59.552 [planetinfo.record]: sun_color = 0.657758, 0.734091, 0.748788, 1 13:46:59.552 [planetinfo.record]: corona_shimmer = 0.271121 13:46:59.552 [planetinfo.record]: station_vector = 0.841 0.463 0.279 13:46:59.552 [planetinfo.record]: station = coriolis 13:47:04.626 [PLANETINFO OVER]: Done 13:47:04.627 [PLANETINFO LOGGING]: 5 114 13:47:05.631 [planetinfo.record]: seed = 125 154 186 213 13:47:05.631 [planetinfo.record]: coordinates = 154 199 13:47:05.631 [planetinfo.record]: government = 7; 13:47:05.631 [planetinfo.record]: economy = 7; 13:47:05.631 [planetinfo.record]: techlevel = 6; 13:47:05.631 [planetinfo.record]: population = 39; 13:47:05.631 [planetinfo.record]: productivity = 10296; 13:47:05.631 [planetinfo.record]: name = "Latius"; 13:47:05.631 [planetinfo.record]: inhabitant = "Furry Feline"; 13:47:05.631 [planetinfo.record]: inhabitants = "Furry Felines"; 13:47:05.631 [planetinfo.record]: description = "The world Latius is most well known for its great parking meters."; 13:47:05.656 [planetinfo.record]: air_color = 0.573265, 0.883131, 0.994447, 1; 13:47:05.656 [planetinfo.record]: cloud_alpha = 1.000000; 13:47:05.656 [planetinfo.record]: cloud_color = 0.45307, 0.986328, 0.323639, 1; 13:47:05.656 [planetinfo.record]: cloud_fraction = 0.320000; 13:47:05.656 [planetinfo.record]: land_color = 0.510469, 0.586403, 0.66, 1; 13:47:05.656 [planetinfo.record]: land_fraction = 0.400000; 13:47:05.657 [planetinfo.record]: polar_cloud_color = 0.624916, 0.943848, 0.547505, 1; 13:47:05.657 [planetinfo.record]: polar_land_color = 0.881098, 0.907962, 0.934, 1; 13:47:05.657 [planetinfo.record]: polar_sea_color = 0.930469, 0.875502, 0.851415, 1; 13:47:05.657 [planetinfo.record]: sea_color = 0.695312, 0.531012, 0.459015, 1; 13:47:05.657 [planetinfo.record]: rotation_speed = 0.001800 13:47:05.657 [planetinfo.record]: planet zpos = 552500.000000 13:47:05.657 [planetinfo.record]: sun_radius = 142515.983582 13:47:05.657 [planetinfo.record]: sun_vector = 0.698 -0.026 0.715 13:47:05.657 [planetinfo.record]: sun_distance = 935000 13:47:05.657 [planetinfo.record]: corona_flare = 0.000160 13:47:05.657 [planetinfo.record]: corona_hues = 0.575676 13:47:05.657 [planetinfo.record]: sun_color = 0.768158, 0.651721, 0.577853, 1 13:47:05.657 [planetinfo.record]: corona_shimmer = 0.321952 13:47:05.658 [planetinfo.record]: station_vector = 0.546 0.774 0.320 13:47:05.658 [planetinfo.record]: station = coriolis 13:47:10.347 [PLANETINFO OVER]: Done 13:47:10.348 [PLANETINFO LOGGING]: 5 115 13:47:11.350 [planetinfo.record]: seed = 145 181 104 149 13:47:11.350 [planetinfo.record]: coordinates = 181 168 13:47:11.350 [planetinfo.record]: government = 2; 13:47:11.350 [planetinfo.record]: economy = 0; 13:47:11.350 [planetinfo.record]: techlevel = 9; 13:47:11.350 [planetinfo.record]: population = 39; 13:47:11.350 [planetinfo.record]: productivity = 18720; 13:47:11.350 [planetinfo.record]: name = "Labeis"; 13:47:11.350 [planetinfo.record]: inhabitant = "Human Colonial"; 13:47:11.350 [planetinfo.record]: inhabitants = "Human Colonials"; 13:47:11.350 [planetinfo.record]: description = "The planet Labeis is a boring world."; 13:47:11.364 [planetinfo.record]: air_color = 0.648685, 0.792236, 0.975586, 1; 13:47:11.364 [planetinfo.record]: cloud_alpha = 1.000000; 13:47:11.364 [planetinfo.record]: cloud_color = 0.552002, 0.929688, 0.814611, 1; 13:47:11.364 [planetinfo.record]: cloud_fraction = 0.090000; 13:47:11.364 [planetinfo.record]: land_color = 0.25795, 0.263746, 0.628906, 1; 13:47:11.364 [planetinfo.record]: land_fraction = 0.500000; 13:47:11.364 [planetinfo.record]: polar_cloud_color = 0.685182, 0.918359, 0.847313, 1; 13:47:11.364 [planetinfo.record]: polar_land_color = 0.798922, 0.801081, 0.937109, 1; 13:47:11.364 [planetinfo.record]: polar_sea_color = 0.944531, 0.916954, 0.894814, 1; 13:47:11.364 [planetinfo.record]: sea_color = 0.554688, 0.489907, 0.4379, 1; 13:47:11.364 [planetinfo.record]: rotation_speed = 0.002947 13:47:11.364 [planetinfo.record]: planet zpos = 513240.000000 13:47:11.364 [planetinfo.record]: sun_radius = 66560.551453 13:47:11.364 [planetinfo.record]: sun_vector = 0.589 -0.618 -0.522 13:47:11.364 [planetinfo.record]: sun_distance = 727090 13:47:11.364 [planetinfo.record]: corona_flare = 0.045596 13:47:11.364 [planetinfo.record]: corona_hues = 0.684570 13:47:11.364 [planetinfo.record]: sun_color = 0.622992, 0.833896, 0.846527, 1 13:47:11.364 [planetinfo.record]: corona_shimmer = 0.304549 13:47:11.364 [planetinfo.record]: station_vector = 0.692 -0.720 0.057 13:47:11.364 [planetinfo.record]: station = coriolis 13:47:16.136 [PLANETINFO OVER]: Done 13:47:16.137 [PLANETINFO LOGGING]: 5 116 13:47:17.140 [planetinfo.record]: seed = 97 198 90 247 13:47:17.141 [planetinfo.record]: coordinates = 198 61 13:47:17.141 [planetinfo.record]: government = 4; 13:47:17.141 [planetinfo.record]: economy = 5; 13:47:17.141 [planetinfo.record]: techlevel = 6; 13:47:17.141 [planetinfo.record]: population = 34; 13:47:17.141 [planetinfo.record]: productivity = 10880; 13:47:17.141 [planetinfo.record]: name = "Tianorin"; 13:47:17.141 [planetinfo.record]: inhabitant = "Human Colonial"; 13:47:17.141 [planetinfo.record]: inhabitants = "Human Colonials"; 13:47:17.141 [planetinfo.record]: description = "This planet is plagued by frequent earthquakes."; 13:47:17.143 [planetinfo.record]: air_color = 0.648363, 0.820319, 0.961928, 1; 13:47:17.143 [planetinfo.record]: cloud_alpha = 1.000000; 13:47:17.143 [planetinfo.record]: cloud_color = 0.551949, 0.888672, 0.675589, 1; 13:47:17.143 [planetinfo.record]: cloud_fraction = 0.390000; 13:47:17.143 [planetinfo.record]: land_color = 0.375097, 0.66, 0.342891, 1; 13:47:17.143 [planetinfo.record]: land_fraction = 0.700000; 13:47:17.143 [planetinfo.record]: polar_cloud_color = 0.686791, 0.899902, 0.765043, 1; 13:47:17.143 [planetinfo.record]: polar_land_color = 0.833205, 0.934, 0.821811, 1; 13:47:17.143 [planetinfo.record]: polar_sea_color = 0.895675, 0.931641, 0.879327, 1; 13:47:17.143 [planetinfo.record]: sea_color = 0.578034, 0.683594, 0.530052, 1; 13:47:17.143 [planetinfo.record]: rotation_speed = 0.004479 13:47:17.143 [planetinfo.record]: planet zpos = 576720.000000 13:47:17.144 [planetinfo.record]: sun_radius = 85854.009705 13:47:17.144 [planetinfo.record]: sun_vector = -0.098 0.523 0.847 13:47:17.144 [planetinfo.record]: sun_distance = 768960 13:47:17.144 [planetinfo.record]: corona_flare = 0.033430 13:47:17.144 [planetinfo.record]: corona_hues = 0.800613 13:47:17.144 [planetinfo.record]: sun_color = 0.469376, 0.538364, 0.734381, 1 13:47:17.144 [planetinfo.record]: corona_shimmer = 0.400034 13:47:17.144 [planetinfo.record]: station_vector = 0.140 -0.983 0.116 13:47:17.144 [planetinfo.record]: station = coriolis 13:47:22.006 [PLANETINFO OVER]: Done 13:47:22.007 [PLANETINFO LOGGING]: 5 117 13:47:23.010 [planetinfo.record]: seed = 125 172 160 97 13:47:23.010 [planetinfo.record]: coordinates = 172 185 13:47:23.010 [planetinfo.record]: government = 7; 13:47:23.010 [planetinfo.record]: economy = 1; 13:47:23.010 [planetinfo.record]: techlevel = 10; 13:47:23.010 [planetinfo.record]: population = 49; 13:47:23.010 [planetinfo.record]: productivity = 38808; 13:47:23.010 [planetinfo.record]: name = "Lesolari"; 13:47:23.010 [planetinfo.record]: inhabitant = "Large Blue Furry Feline"; 13:47:23.010 [planetinfo.record]: inhabitants = "Large Blue Furry Felines"; 13:47:23.010 [planetinfo.record]: description = "Lesolari is reasonably well known for the Lesolariian spotted wolf but beset by frequent civil war."; 13:47:23.044 [planetinfo.record]: air_color = 0.645389, 0.549623, 0.892986, 1; 13:47:23.044 [planetinfo.record]: cloud_alpha = 1.000000; 13:47:23.044 [planetinfo.record]: cloud_color = 0.625517, 0.303543, 0.681641, 1; 13:47:23.044 [planetinfo.record]: cloud_fraction = 0.420000; 13:47:23.044 [planetinfo.record]: land_color = 0.108281, 0.608276, 0.66, 1; 13:47:23.044 [planetinfo.record]: land_fraction = 0.380000; 13:47:23.044 [planetinfo.record]: polar_cloud_color = 0.765223, 0.527058, 0.806738, 1; 13:47:23.044 [planetinfo.record]: polar_land_color = 0.738809, 0.915701, 0.934, 1; 13:47:23.044 [planetinfo.record]: polar_sea_color = 0.898082, 0.933984, 0.885096, 1; 13:47:23.044 [planetinfo.record]: sea_color = 0.558651, 0.660156, 0.521936, 1; 13:47:23.044 [planetinfo.record]: rotation_speed = 0.003703 13:47:23.044 [planetinfo.record]: planet zpos = 389280.000000 13:47:23.044 [planetinfo.record]: sun_radius = 74681.893311 13:47:23.044 [planetinfo.record]: sun_vector = 0.145 -0.857 -0.495 13:47:23.044 [planetinfo.record]: sun_distance = 583920 13:47:23.044 [planetinfo.record]: corona_flare = 0.002188 13:47:23.044 [planetinfo.record]: corona_hues = 0.500450 13:47:23.044 [planetinfo.record]: sun_color = 0.215284, 0.311072, 0.730148, 1 13:47:23.044 [planetinfo.record]: corona_shimmer = 0.331579 13:47:23.045 [planetinfo.record]: station_vector = -0.272 0.746 0.608 13:47:23.045 [planetinfo.record]: station = coriolis 13:47:27.483 [PLANETINFO OVER]: Done 13:47:27.483 [PLANETINFO LOGGING]: 5 118 13:47:28.486 [planetinfo.record]: seed = 213 254 10 156 13:47:28.486 [planetinfo.record]: coordinates = 254 213 13:47:28.486 [planetinfo.record]: government = 2; 13:47:28.486 [planetinfo.record]: economy = 5; 13:47:28.487 [planetinfo.record]: techlevel = 5; 13:47:28.487 [planetinfo.record]: population = 28; 13:47:28.487 [planetinfo.record]: productivity = 6720; 13:47:28.487 [planetinfo.record]: name = "Teerma"; 13:47:28.487 [planetinfo.record]: inhabitant = "Human Colonial"; 13:47:28.487 [planetinfo.record]: inhabitants = "Human Colonials"; 13:47:28.487 [planetinfo.record]: description = "This planet is mildly noted for its exciting its exotic fish cutlet but plagued by frequent earthquakes."; 13:47:28.504 [planetinfo.record]: air_color = 0.529329, 0.987293, 0.954637, 1; 13:47:28.504 [planetinfo.record]: cloud_alpha = 1.000000; 13:47:28.504 [planetinfo.record]: cloud_color = 0.964844, 0.809435, 0.199753, 1; 13:47:28.504 [planetinfo.record]: cloud_fraction = 0.330000; 13:47:28.504 [planetinfo.record]: land_color = 0.66, 0.151384, 0.0515625, 1; 13:47:28.504 [planetinfo.record]: land_fraction = 0.340000; 13:47:28.504 [planetinfo.record]: polar_cloud_color = 0.93418, 0.840136, 0.471195, 1; 13:47:28.504 [planetinfo.record]: polar_land_color = 0.934, 0.754058, 0.718742, 1; 13:47:28.504 [planetinfo.record]: polar_sea_color = 0.948828, 0.899997, 0.899997, 1; 13:47:28.504 [planetinfo.record]: sea_color = 0.511719, 0.406377, 0.406377, 1; 13:47:28.504 [planetinfo.record]: rotation_speed = 0.004602 13:47:28.504 [planetinfo.record]: planet zpos = 859880.000000 13:47:28.504 [planetinfo.record]: sun_radius = 199009.572144 13:47:28.504 [planetinfo.record]: sun_vector = -0.427 0.779 0.458 13:47:28.504 [planetinfo.record]: sun_distance = 1228400 13:47:28.504 [planetinfo.record]: corona_flare = 0.096646 13:47:28.504 [planetinfo.record]: corona_hues = 0.870872 13:47:28.504 [planetinfo.record]: sun_color = 0.776205, 0.533216, 0.262841, 1 13:47:28.504 [planetinfo.record]: corona_shimmer = 0.332397 13:47:28.504 [planetinfo.record]: station_vector = 0.343 0.294 0.892 13:47:28.504 [planetinfo.record]: station = coriolis 13:47:33.187 [PLANETINFO OVER]: Done 13:47:33.187 [PLANETINFO LOGGING]: 5 119 13:47:34.191 [planetinfo.record]: seed = 249 23 232 147 13:47:34.191 [planetinfo.record]: coordinates = 23 11 13:47:34.191 [planetinfo.record]: government = 7; 13:47:34.191 [planetinfo.record]: economy = 3; 13:47:34.191 [planetinfo.record]: techlevel = 11; 13:47:34.191 [planetinfo.record]: population = 55; 13:47:34.191 [planetinfo.record]: productivity = 33880; 13:47:34.191 [planetinfo.record]: name = "Betigere"; 13:47:34.191 [planetinfo.record]: inhabitant = "Black Fat Insect"; 13:47:34.191 [planetinfo.record]: inhabitants = "Black Fat Insects"; 13:47:34.191 [planetinfo.record]: description = "This planet is most fabled for Betigereian Es brandy but cursed by deadly civil war."; 13:47:34.196 [planetinfo.record]: air_color = 0.659032, 0.572836, 0.873475, 1; 13:47:34.197 [planetinfo.record]: cloud_alpha = 1.000000; 13:47:34.197 [planetinfo.record]: cloud_color = 0.589278, 0.352898, 0.623047, 1; 13:47:34.197 [planetinfo.record]: cloud_fraction = 0.300000; 13:47:34.197 [planetinfo.record]: land_color = 0.257672, 0.0541406, 0.66, 1; 13:47:34.197 [planetinfo.record]: land_fraction = 0.510000; 13:47:34.197 [planetinfo.record]: polar_cloud_color = 0.753936, 0.568894, 0.780371, 1; 13:47:34.197 [planetinfo.record]: polar_land_color = 0.791661, 0.719654, 0.934, 1; 13:47:34.197 [planetinfo.record]: polar_sea_color = 0.945117, 0.915524, 0.895923, 1; 13:47:34.197 [planetinfo.record]: sea_color = 0.548828, 0.480089, 0.43456, 1; 13:47:34.197 [planetinfo.record]: rotation_speed = 0.001984 13:47:34.197 [planetinfo.record]: planet zpos = 396770.000000 13:47:34.197 [planetinfo.record]: sun_radius = 72139.449615 13:47:34.197 [planetinfo.record]: sun_vector = -0.662 0.706 -0.252 13:47:34.197 [planetinfo.record]: sun_distance = 649260 13:47:34.197 [planetinfo.record]: corona_flare = 0.033670 13:47:34.197 [planetinfo.record]: corona_hues = 0.875687 13:47:34.197 [planetinfo.record]: sun_color = 0.677136, 0.64507, 0.248434, 1 13:47:34.197 [planetinfo.record]: corona_shimmer = 0.466648 13:47:34.197 [planetinfo.record]: station_vector = -0.533 -0.241 0.811 13:47:34.197 [planetinfo.record]: station = dodecahedron 13:47:38.423 [PLANETINFO OVER]: Done 13:47:38.424 [PLANETINFO LOGGING]: 5 120 13:47:39.427 [planetinfo.record]: seed = 89 174 202 200 13:47:39.427 [planetinfo.record]: coordinates = 174 99 13:47:39.427 [planetinfo.record]: government = 3; 13:47:39.427 [planetinfo.record]: economy = 3; 13:47:39.427 [planetinfo.record]: techlevel = 8; 13:47:39.427 [planetinfo.record]: population = 39; 13:47:39.427 [planetinfo.record]: productivity = 15288; 13:47:39.427 [planetinfo.record]: name = "Usquat"; 13:47:39.427 [planetinfo.record]: inhabitant = "Small Furry Humanoid"; 13:47:39.427 [planetinfo.record]: inhabitants = "Small Furry Humanoids"; 13:47:39.427 [planetinfo.record]: description = "The world Usquat is reasonably famous for the Usquatian evil arts graduate."; 13:47:39.437 [planetinfo.record]: air_color = 0.629412, 0.909246, 0.891476, 1; 13:47:39.437 [planetinfo.record]: cloud_alpha = 1.000000; 13:47:39.437 [planetinfo.record]: cloud_color = 0.730469, 0.687913, 0.493637, 1; 13:47:39.437 [planetinfo.record]: cloud_fraction = 0.560000; 13:47:39.437 [planetinfo.record]: land_color = 0.45375, 0.66, 0.558486, 1; 13:47:39.437 [planetinfo.record]: land_fraction = 0.470000; 13:47:39.437 [planetinfo.record]: polar_cloud_color = 0.828711, 0.798536, 0.660784, 1; 13:47:39.437 [planetinfo.record]: polar_land_color = 0.861031, 0.934, 0.898086, 1; 13:47:39.437 [planetinfo.record]: polar_sea_color = 0.917839, 0.935938, 0.885576, 1; 13:47:39.437 [planetinfo.record]: sea_color = 0.591073, 0.640625, 0.50274, 1; 13:47:39.437 [planetinfo.record]: rotation_speed = 0.002266 13:47:39.437 [planetinfo.record]: planet zpos = 654940.000000 13:47:39.437 [planetinfo.record]: sun_radius = 156980.869751 13:47:39.437 [planetinfo.record]: sun_vector = 0.470 -0.428 -0.772 13:47:39.437 [planetinfo.record]: sun_distance = 957220 13:47:39.437 [planetinfo.record]: corona_flare = 0.098544 13:47:39.437 [planetinfo.record]: corona_hues = 0.886597 13:47:39.437 [planetinfo.record]: sun_color = 0.7853, 0.730587, 0.671614, 1 13:47:39.438 [planetinfo.record]: corona_shimmer = 0.517744 13:47:39.438 [planetinfo.record]: station_vector = -0.389 0.862 0.325 13:47:39.438 [planetinfo.record]: station = coriolis 13:47:43.788 [PLANETINFO OVER]: Done 13:47:43.791 [PLANETINFO LOGGING]: 5 121 13:47:44.796 [planetinfo.record]: seed = 133 244 64 32 13:47:44.796 [planetinfo.record]: coordinates = 244 81 13:47:44.796 [planetinfo.record]: government = 0; 13:47:44.796 [planetinfo.record]: economy = 3; 13:47:44.796 [planetinfo.record]: techlevel = 4; 13:47:44.796 [planetinfo.record]: population = 20; 13:47:44.796 [planetinfo.record]: productivity = 4480; 13:47:44.796 [planetinfo.record]: name = "Biqule"; 13:47:44.796 [planetinfo.record]: inhabitant = "Human Colonial"; 13:47:44.796 [planetinfo.record]: inhabitants = "Human Colonials"; 13:47:44.797 [planetinfo.record]: description = "Biqule is very fabled for the Biquleian mountain poet."; 13:47:44.810 [planetinfo.record]: air_color = 0.535332, 0.48016, 0.929408, 1; 13:47:44.811 [planetinfo.record]: cloud_alpha = 1.000000; 13:47:44.811 [planetinfo.record]: cloud_color = 0.375279, 0.117416, 0.791016, 1; 13:47:44.811 [planetinfo.record]: cloud_fraction = 0.320000; 13:47:44.811 [planetinfo.record]: land_color = 0.608438, 0.66, 0.656375, 1; 13:47:44.811 [planetinfo.record]: land_fraction = 0.440000; 13:47:44.811 [planetinfo.record]: polar_cloud_color = 0.574789, 0.400394, 0.855957, 1; 13:47:44.811 [planetinfo.record]: polar_land_color = 0.915758, 0.934, 0.932717, 1; 13:47:44.811 [planetinfo.record]: polar_sea_color = 0.945508, 0.860069, 0.712824, 1; 13:47:44.812 [planetinfo.record]: sea_color = 0.544922, 0.34796, 0.0085144, 1; 13:47:44.812 [planetinfo.record]: rotation_speed = 0.002746 13:47:44.812 [planetinfo.record]: planet zpos = 428400.000000 13:47:44.812 [planetinfo.record]: sun_radius = 92551.739502 13:47:44.812 [planetinfo.record]: sun_vector = 0.531 -0.019 -0.847 13:47:44.812 [planetinfo.record]: sun_distance = 612000 13:47:44.812 [planetinfo.record]: corona_flare = 0.037949 13:47:44.812 [planetinfo.record]: corona_hues = 0.695526 13:47:44.812 [planetinfo.record]: sun_color = 0.34285, 0.508223, 0.814951, 1 13:47:44.813 [planetinfo.record]: corona_shimmer = 0.610785 13:47:44.813 [planetinfo.record]: station_vector = -0.977 0.211 0.041 13:47:44.813 [planetinfo.record]: station = coriolis 13:47:49.775 [PLANETINFO OVER]: Done 13:47:49.776 [PLANETINFO LOGGING]: 5 122 13:47:50.780 [planetinfo.record]: seed = 109 1 154 226 13:47:50.780 [planetinfo.record]: coordinates = 1 122 13:47:50.780 [planetinfo.record]: government = 5; 13:47:50.780 [planetinfo.record]: economy = 2; 13:47:50.780 [planetinfo.record]: techlevel = 9; 13:47:50.780 [planetinfo.record]: population = 44; 13:47:50.780 [planetinfo.record]: productivity = 25344; 13:47:50.780 [planetinfo.record]: name = "Xeongeza"; 13:47:50.780 [planetinfo.record]: inhabitant = "Large Bony Humanoid"; 13:47:50.780 [planetinfo.record]: inhabitants = "Large Bony Humanoids"; 13:47:50.780 [planetinfo.record]: description = "This world is a tedious little planet."; 13:47:50.792 [planetinfo.record]: air_color = 0.635078, 0.596709, 0.833801, 1; 13:47:50.793 [planetinfo.record]: cloud_alpha = 1.000000; 13:47:50.793 [planetinfo.record]: cloud_color = 0.439934, 0.375961, 0.503906, 1; 13:47:50.793 [planetinfo.record]: cloud_fraction = 0.510000; 13:47:50.793 [planetinfo.record]: land_color = 0.617188, 0.491671, 0.482178, 1; 13:47:50.793 [planetinfo.record]: land_fraction = 0.600000; 13:47:50.793 [planetinfo.record]: polar_cloud_color = 0.669093, 0.611428, 0.726758, 1; 13:47:50.793 [planetinfo.record]: polar_land_color = 0.938281, 0.890577, 0.886969, 1; 13:47:50.793 [planetinfo.record]: polar_sea_color = 0.780734, 0.942773, 0.8972, 1; 13:47:50.793 [planetinfo.record]: sea_color = 0.178833, 0.572266, 0.461613, 1; 13:47:50.793 [planetinfo.record]: rotation_speed = 0.002408 13:47:50.793 [planetinfo.record]: planet zpos = 366190.000000 13:47:50.793 [planetinfo.record]: sun_radius = 66651.623077 13:47:50.793 [planetinfo.record]: sun_vector = 0.074 0.582 0.810 13:47:50.793 [planetinfo.record]: sun_distance = 699090 13:47:50.793 [planetinfo.record]: corona_flare = 0.058072 13:47:50.793 [planetinfo.record]: corona_hues = 0.650543 13:47:50.793 [planetinfo.record]: sun_color = 0.835248, 0.299617, 0.278179, 1 13:47:50.793 [planetinfo.record]: corona_shimmer = 0.339646 13:47:50.793 [planetinfo.record]: station_vector = 0.556 -0.830 0.030 13:47:50.793 [planetinfo.record]: station = coriolis 13:47:55.109 [PLANETINFO OVER]: Done 13:47:55.109 [PLANETINFO LOGGING]: 5 123 13:47:56.113 [planetinfo.record]: seed = 161 132 168 38 13:47:56.113 [planetinfo.record]: coordinates = 132 67 13:47:56.113 [planetinfo.record]: government = 4; 13:47:56.113 [planetinfo.record]: economy = 3; 13:47:56.113 [planetinfo.record]: techlevel = 6; 13:47:56.113 [planetinfo.record]: population = 32; 13:47:56.113 [planetinfo.record]: productivity = 14336; 13:47:56.113 [planetinfo.record]: name = "Bireor"; 13:47:56.113 [planetinfo.record]: inhabitant = "Fierce Red Frog"; 13:47:56.113 [planetinfo.record]: inhabitants = "Fierce Red Frogs"; 13:47:56.113 [planetinfo.record]: description = "This planet is fabled for its exciting Zero-G cricket."; 13:47:56.132 [planetinfo.record]: air_color = 0.617459, 0.886684, 0.954773, 1; 13:47:56.132 [planetinfo.record]: cloud_alpha = 1.000000; 13:47:56.132 [planetinfo.record]: cloud_color = 0.620486, 0.867188, 0.467468, 1; 13:47:56.132 [planetinfo.record]: cloud_fraction = 0.370000; 13:47:56.132 [planetinfo.record]: land_color = 0.66, 0.108281, 0.37983, 1; 13:47:56.132 [planetinfo.record]: land_fraction = 0.500000; 13:47:56.132 [planetinfo.record]: polar_cloud_color = 0.731948, 0.890234, 0.63377, 1; 13:47:56.132 [planetinfo.record]: polar_land_color = 0.934, 0.738809, 0.834879, 1; 13:47:56.132 [planetinfo.record]: polar_sea_color = 0.939062, 0.918892, 0.884131, 1; 13:47:56.132 [planetinfo.record]: sea_color = 0.609375, 0.55702, 0.466791, 1; 13:47:56.132 [planetinfo.record]: rotation_speed = 0.001014 13:47:56.132 [planetinfo.record]: planet zpos = 493240.000000 13:47:56.132 [planetinfo.record]: sun_radius = 94375.692749 13:47:56.132 [planetinfo.record]: sun_vector = -0.357 0.844 -0.399 13:47:56.132 [planetinfo.record]: sun_distance = 1076160 13:47:56.132 [planetinfo.record]: corona_flare = 0.025981 13:47:56.132 [planetinfo.record]: corona_hues = 0.837395 13:47:56.132 [planetinfo.record]: sun_color = 0.795935, 0.405796, 0.336225, 1 13:47:56.133 [planetinfo.record]: corona_shimmer = 0.399707 13:47:56.133 [planetinfo.record]: station_vector = -0.574 -0.680 0.456 13:47:56.133 [planetinfo.record]: station = coriolis 13:48:00.414 [PLANETINFO OVER]: Done 13:48:00.415 [PLANETINFO LOGGING]: 5 124 13:48:01.419 [planetinfo.record]: seed = 145 174 122 54 13:48:01.419 [planetinfo.record]: coordinates = 174 153 13:48:01.419 [planetinfo.record]: government = 2; 13:48:01.419 [planetinfo.record]: economy = 1; 13:48:01.419 [planetinfo.record]: techlevel = 9; 13:48:01.419 [planetinfo.record]: population = 40; 13:48:01.419 [planetinfo.record]: productivity = 17280; 13:48:01.419 [planetinfo.record]: name = "Verige"; 13:48:01.419 [planetinfo.record]: inhabitant = "Human Colonial"; 13:48:01.419 [planetinfo.record]: inhabitants = "Human Colonials"; 13:48:01.419 [planetinfo.record]: description = "This world is ravaged by unpredictable civil war."; 13:48:01.448 [planetinfo.record]: air_color = 0.452944, 0.577099, 0.904043, 1; 13:48:01.448 [planetinfo.record]: cloud_alpha = 1.000000; 13:48:01.448 [planetinfo.record]: cloud_color = 0.0726013, 0.594423, 0.714844, 1; 13:48:01.448 [planetinfo.record]: cloud_fraction = 0.550000; 13:48:01.448 [planetinfo.record]: land_color = 0.476429, 0.66, 0.180469, 1; 13:48:01.448 [planetinfo.record]: land_fraction = 0.600000; 13:48:01.449 [planetinfo.record]: polar_cloud_color = 0.360287, 0.735169, 0.82168, 1; 13:48:01.449 [planetinfo.record]: polar_land_color = 0.869055, 0.934, 0.764348, 1; 13:48:01.449 [planetinfo.record]: polar_sea_color = 0.937305, 0.888408, 0.875336, 1; 13:48:01.449 [planetinfo.record]: sea_color = 0.626953, 0.496127, 0.461153, 1; 13:48:01.449 [planetinfo.record]: rotation_speed = 0.000012 13:48:01.449 [planetinfo.record]: planet zpos = 633640.000000 13:48:01.450 [planetinfo.record]: sun_radius = 102068.427124 13:48:01.450 [planetinfo.record]: sun_vector = 0.883 0.431 0.188 13:48:01.450 [planetinfo.record]: sun_distance = 950460 13:48:01.450 [planetinfo.record]: corona_flare = 0.076904 13:48:01.450 [planetinfo.record]: corona_hues = 0.922127 13:48:01.450 [planetinfo.record]: sun_color = 0.541229, 0.547628, 0.745935, 1 13:48:01.451 [planetinfo.record]: corona_shimmer = 0.551223 13:48:01.451 [planetinfo.record]: station_vector = 0.923 -0.304 0.236 13:48:01.451 [planetinfo.record]: station = coriolis 13:48:05.823 [PLANETINFO OVER]: Done 13:48:05.824 [PLANETINFO LOGGING]: 5 125 13:48:06.826 [planetinfo.record]: seed = 205 24 32 251 13:48:06.827 [planetinfo.record]: coordinates = 24 99 13:48:06.827 [planetinfo.record]: government = 1; 13:48:06.827 [planetinfo.record]: economy = 3; 13:48:06.827 [planetinfo.record]: techlevel = 5; 13:48:06.827 [planetinfo.record]: population = 25; 13:48:06.827 [planetinfo.record]: productivity = 7000; 13:48:06.827 [planetinfo.record]: name = "Antima"; 13:48:06.827 [planetinfo.record]: inhabitant = "Human Colonial"; 13:48:06.827 [planetinfo.record]: inhabitants = "Human Colonials"; 13:48:06.827 [planetinfo.record]: description = "Antima is mildly well known for Antimaian vicious brew."; 13:48:06.844 [planetinfo.record]: air_color = 0.743096, 0.600251, 0.878678, 1; 13:48:06.844 [planetinfo.record]: cloud_alpha = 1.000000; 13:48:06.844 [planetinfo.record]: cloud_color = 0.638672, 0.414139, 0.514126, 1; 13:48:06.844 [planetinfo.record]: cloud_fraction = 0.310000; 13:48:06.844 [planetinfo.record]: land_color = 0.477215, 0.466641, 0.66, 1; 13:48:06.844 [planetinfo.record]: land_fraction = 0.430000; 13:48:06.844 [planetinfo.record]: polar_cloud_color = 0.787402, 0.614389, 0.691434, 1; 13:48:06.844 [planetinfo.record]: polar_land_color = 0.869333, 0.865592, 0.934, 1; 13:48:06.844 [planetinfo.record]: polar_sea_color = 0.938477, 0.885865, 0.877256, 1; 13:48:06.844 [planetinfo.record]: sea_color = 0.615234, 0.477272, 0.454697, 1; 13:48:06.844 [planetinfo.record]: rotation_speed = 0.001876 13:48:06.844 [planetinfo.record]: planet zpos = 622160.000000 13:48:06.844 [planetinfo.record]: sun_radius = 139740.379639 13:48:06.844 [planetinfo.record]: sun_vector = -0.070 -0.059 -0.996 13:48:06.844 [planetinfo.record]: sun_distance = 1131200 13:48:06.844 [planetinfo.record]: corona_flare = 0.074037 13:48:06.844 [planetinfo.record]: corona_hues = 0.849564 13:48:06.844 [planetinfo.record]: sun_color = 0.653372, 0.29979, 0.288923, 1 13:48:06.845 [planetinfo.record]: corona_shimmer = 0.425813 13:48:06.845 [planetinfo.record]: station_vector = -0.389 -0.850 0.355 13:48:06.845 [planetinfo.record]: station = coriolis 13:48:11.590 [PLANETINFO OVER]: Done 13:48:11.591 [PLANETINFO LOGGING]: 5 126 13:48:12.595 [planetinfo.record]: seed = 69 254 106 1 13:48:12.595 [planetinfo.record]: coordinates = 254 139 13:48:12.595 [planetinfo.record]: government = 0; 13:48:12.595 [planetinfo.record]: economy = 3; 13:48:12.595 [planetinfo.record]: techlevel = 6; 13:48:12.595 [planetinfo.record]: population = 28; 13:48:12.595 [planetinfo.record]: productivity = 6272; 13:48:12.595 [planetinfo.record]: name = "Lemaar"; 13:48:12.595 [planetinfo.record]: inhabitant = "Human Colonial"; 13:48:12.595 [planetinfo.record]: inhabitants = "Human Colonials"; 13:48:12.595 [planetinfo.record]: description = "This world is a revolting dump."; 13:48:12.596 [planetinfo.record]: air_color = 0.610297, 0.891686, 0.801344, 1; 13:48:12.596 [planetinfo.record]: cloud_alpha = 1.000000; 13:48:12.596 [planetinfo.record]: cloud_color = 0.677734, 0.45316, 0.442116, 1; 13:48:12.597 [planetinfo.record]: cloud_fraction = 0.310000; 13:48:12.597 [planetinfo.record]: land_color = 0.500156, 0.66, 0.603805, 1; 13:48:12.597 [planetinfo.record]: land_fraction = 0.390000; 13:48:12.597 [planetinfo.record]: polar_cloud_color = 0.80498, 0.638269, 0.63007, 1; 13:48:12.597 [planetinfo.record]: polar_land_color = 0.877449, 0.934, 0.914119, 1; 13:48:12.597 [planetinfo.record]: polar_sea_color = 0.882212, 0.930078, 0.8953, 1; 13:48:12.597 [planetinfo.record]: sea_color = 0.555278, 0.699219, 0.594637, 1; 13:48:12.597 [planetinfo.record]: rotation_speed = 0.000156 13:48:12.597 [planetinfo.record]: planet zpos = 465640.000000 13:48:12.597 [planetinfo.record]: sun_radius = 80321.661682 13:48:12.597 [planetinfo.record]: sun_vector = -0.544 0.710 -0.447 13:48:12.597 [planetinfo.record]: sun_distance = 598680 13:48:12.597 [planetinfo.record]: corona_flare = 0.035832 13:48:12.597 [planetinfo.record]: corona_hues = 0.885056 13:48:12.597 [planetinfo.record]: sun_color = 0.494422, 0.672447, 0.721289, 1 13:48:12.598 [planetinfo.record]: corona_shimmer = 0.395448 13:48:12.598 [planetinfo.record]: station_vector = 0.690 0.436 0.578 13:48:12.598 [planetinfo.record]: station = coriolis 13:48:17.437 [PLANETINFO OVER]: Done 13:48:17.437 [PLANETINFO LOGGING]: 5 127 13:48:18.441 [planetinfo.record]: seed = 137 23 168 45 13:48:18.441 [planetinfo.record]: coordinates = 23 138 13:48:18.441 [planetinfo.record]: government = 1; 13:48:18.441 [planetinfo.record]: economy = 2; 13:48:18.441 [planetinfo.record]: techlevel = 9; 13:48:18.441 [planetinfo.record]: population = 40; 13:48:18.441 [planetinfo.record]: productivity = 12800; 13:48:18.441 [planetinfo.record]: name = "Dierlabe"; 13:48:18.441 [planetinfo.record]: inhabitant = "Red Furry Feline"; 13:48:18.441 [planetinfo.record]: inhabitants = "Red Furry Felines"; 13:48:18.441 [planetinfo.record]: description = "The planet Dierlabe is reasonably noted for its fabulous goat soup and mud tennis."; 13:48:18.460 [planetinfo.record]: air_color = 0.425837, 0.786842, 0.861117, 1; 13:48:18.460 [planetinfo.record]: cloud_alpha = 1.000000; 13:48:18.460 [planetinfo.record]: cloud_color = 0.304395, 0.585938, 0.0480652, 1; 13:48:18.460 [planetinfo.record]: cloud_fraction = 0.240000; 13:48:18.460 [planetinfo.record]: land_color = 0.163063, 0.601485, 0.623047, 1; 13:48:18.460 [planetinfo.record]: land_fraction = 0.440000; 13:48:18.460 [planetinfo.record]: polar_cloud_color = 0.534332, 0.763672, 0.32553, 1; 13:48:18.460 [planetinfo.record]: polar_land_color = 0.764625, 0.929583, 0.937695, 1; 13:48:18.460 [planetinfo.record]: polar_sea_color = 0.928711, 0.846243, 0.835296, 1; 13:48:18.460 [planetinfo.record]: sea_color = 0.712891, 0.459676, 0.426064, 1; 13:48:18.460 [planetinfo.record]: rotation_speed = 0.000063 13:48:18.460 [planetinfo.record]: planet zpos = 740040.000000 13:48:18.460 [planetinfo.record]: sun_radius = 124051.403198 13:48:18.460 [planetinfo.record]: sun_vector = -0.525 0.850 0.037 13:48:18.460 [planetinfo.record]: sun_distance = 1418410 13:48:18.460 [planetinfo.record]: corona_flare = 0.057549 13:48:18.460 [planetinfo.record]: corona_hues = 0.855949 13:48:18.461 [planetinfo.record]: sun_color = 0.725952, 0.658542, 0.520549, 1 13:48:18.461 [planetinfo.record]: corona_shimmer = 0.542840 13:48:18.461 [planetinfo.record]: station_vector = 0.230 0.008 0.973 13:48:18.461 [planetinfo.record]: station = coriolis 13:48:22.626 [PLANETINFO OVER]: Done 13:48:22.627 [PLANETINFO LOGGING]: 5 128 13:48:23.631 [planetinfo.record]: seed = 9 19 106 248 13:48:23.631 [planetinfo.record]: coordinates = 19 21 13:48:23.631 [planetinfo.record]: government = 1; 13:48:23.631 [planetinfo.record]: economy = 7; 13:48:23.631 [planetinfo.record]: techlevel = 4; 13:48:23.631 [planetinfo.record]: population = 25; 13:48:23.631 [planetinfo.record]: productivity = 3000; 13:48:23.631 [planetinfo.record]: name = "Edince"; 13:48:23.631 [planetinfo.record]: inhabitant = "Human Colonial"; 13:48:23.631 [planetinfo.record]: inhabitants = "Human Colonials"; 13:48:23.631 [planetinfo.record]: description = "Edince is very notable for its inhabitants’ ingrained silliness."; 13:48:23.636 [planetinfo.record]: air_color = 0.650024, 0.777452, 0.981439, 1; 13:48:23.636 [planetinfo.record]: cloud_alpha = 1.000000; 13:48:23.636 [planetinfo.record]: cloud_color = 0.555038, 0.947266, 0.892109, 1; 13:48:23.636 [planetinfo.record]: cloud_fraction = 0.500000; 13:48:23.636 [planetinfo.record]: land_color = 0.577681, 0.66, 0.487266, 1; 13:48:23.636 [planetinfo.record]: land_fraction = 0.350000; 13:48:23.636 [planetinfo.record]: polar_cloud_color = 0.686561, 0.92627, 0.892561, 1; 13:48:23.636 [planetinfo.record]: polar_land_color = 0.904877, 0.934, 0.872889, 1; 13:48:23.636 [planetinfo.record]: polar_sea_color = 0.923006, 0.876981, 0.946289, 1; 13:48:23.636 [planetinfo.record]: sea_color = 0.484248, 0.379753, 0.537109, 1; 13:48:23.636 [planetinfo.record]: rotation_speed = 0.002890 13:48:23.637 [planetinfo.record]: planet zpos = 683620.000000 13:48:23.637 [planetinfo.record]: sun_radius = 146025.811005 13:48:23.637 [planetinfo.record]: sun_vector = -0.626 -0.774 0.096 13:48:23.637 [planetinfo.record]: sun_distance = 1074260 13:48:23.637 [planetinfo.record]: corona_flare = 0.047388 13:48:23.637 [planetinfo.record]: corona_hues = 0.724800 13:48:23.637 [planetinfo.record]: sun_color = 0.776413, 0.665203, 0.635545, 1 13:48:23.638 [planetinfo.record]: corona_shimmer = 0.418215 13:48:23.638 [planetinfo.record]: station_vector = 0.075 0.384 0.920 13:48:23.638 [planetinfo.record]: station = coriolis 13:48:28.477 [PLANETINFO OVER]: Done 13:48:28.477 [PLANETINFO LOGGING]: 5 129 13:48:29.481 [planetinfo.record]: seed = 85 69 64 146 13:48:29.481 [planetinfo.record]: coordinates = 69 44 13:48:29.481 [planetinfo.record]: government = 2; 13:48:29.481 [planetinfo.record]: economy = 4; 13:48:29.481 [planetinfo.record]: techlevel = 5; 13:48:29.481 [planetinfo.record]: population = 27; 13:48:29.481 [planetinfo.record]: productivity = 7776; 13:48:29.481 [planetinfo.record]: name = "Engean"; 13:48:29.481 [planetinfo.record]: inhabitant = "Human Colonial"; 13:48:29.481 [planetinfo.record]: inhabitants = "Human Colonials"; 13:48:29.481 [planetinfo.record]: description = "This world is fabled for its ancient Engeanian On banana plantations."; 13:48:29.496 [planetinfo.record]: air_color = 0.525663, 0.971033, 0.869756, 1; 13:48:29.496 [planetinfo.record]: cloud_alpha = 1.000000; 13:48:29.496 [planetinfo.record]: cloud_color = 0.916016, 0.437601, 0.203957, 1; 13:48:29.496 [planetinfo.record]: cloud_fraction = 0.440000; 13:48:29.496 [planetinfo.record]: land_color = 0.647029, 0.600703, 0.66, 1; 13:48:29.496 [planetinfo.record]: land_fraction = 0.470000; 13:48:29.496 [planetinfo.record]: polar_cloud_color = 0.912207, 0.614441, 0.46902, 1; 13:48:29.496 [planetinfo.record]: polar_land_color = 0.929411, 0.913022, 0.934, 1; 13:48:29.496 [planetinfo.record]: polar_sea_color = 0.947852, 0.843746, 0.721996, 1; 13:48:29.496 [planetinfo.record]: sea_color = 0.521484, 0.29238, 0.0244446, 1; 13:48:29.496 [planetinfo.record]: rotation_speed = 0.000897 13:48:29.496 [planetinfo.record]: planet zpos = 407640.000000 13:48:29.496 [planetinfo.record]: sun_radius = 93009.565582 13:48:29.496 [planetinfo.record]: sun_vector = 0.270 -0.813 -0.516 13:48:29.496 [planetinfo.record]: sun_distance = 781310 13:48:29.496 [planetinfo.record]: corona_flare = 0.092929 13:48:29.496 [planetinfo.record]: corona_hues = 0.522964 13:48:29.496 [planetinfo.record]: sun_color = 0.604203, 0.746313, 0.831653, 1 13:48:29.497 [planetinfo.record]: corona_shimmer = 0.429142 13:48:29.497 [planetinfo.record]: station_vector = -0.981 0.191 0.027 13:48:29.497 [planetinfo.record]: station = coriolis 13:48:33.951 [PLANETINFO OVER]: Done 13:48:33.952 [PLANETINFO LOGGING]: 5 130 13:48:34.956 [planetinfo.record]: seed = 93 113 122 80 13:48:34.956 [planetinfo.record]: coordinates = 113 219 13:48:34.956 [planetinfo.record]: government = 3; 13:48:34.956 [planetinfo.record]: economy = 3; 13:48:34.956 [planetinfo.record]: techlevel = 7; 13:48:34.956 [planetinfo.record]: population = 35; 13:48:34.956 [planetinfo.record]: productivity = 13720; 13:48:34.956 [planetinfo.record]: name = "Erisonin"; 13:48:34.956 [planetinfo.record]: inhabitant = "Human Colonial"; 13:48:34.956 [planetinfo.record]: inhabitants = "Human Colonials"; 13:48:34.956 [planetinfo.record]: description = "This planet is fabled for its unusual tropical forests and its exciting sit coms."; 13:48:34.959 [planetinfo.record]: air_color = 0.568546, 0.926113, 0.984041, 1; 13:48:34.959 [planetinfo.record]: cloud_alpha = 1.000000; 13:48:34.959 [planetinfo.record]: cloud_color = 0.680954, 0.955078, 0.317116, 1; 13:48:34.959 [planetinfo.record]: cloud_fraction = 0.340000; 13:48:34.959 [planetinfo.record]: land_color = 0.659356, 0.5775, 0.66, 1; 13:48:34.959 [planetinfo.record]: land_fraction = 0.260000; 13:48:34.959 [planetinfo.record]: polar_cloud_color = 0.762995, 0.929785, 0.541618, 1; 13:48:34.959 [planetinfo.record]: polar_land_color = 0.933772, 0.904813, 0.934, 1; 13:48:34.959 [planetinfo.record]: polar_sea_color = 0.943359, 0.918906, 0.892875, 1; 13:48:34.959 [planetinfo.record]: sea_color = 0.566406, 0.507678, 0.44516, 1; 13:48:34.959 [planetinfo.record]: rotation_speed = 0.002983 13:48:34.959 [planetinfo.record]: planet zpos = 351480.000000 13:48:34.960 [planetinfo.record]: sun_radius = 76201.106415 13:48:34.960 [planetinfo.record]: sun_vector = 0.066 -0.998 -0.002 13:48:34.960 [planetinfo.record]: sun_distance = 556510 13:48:34.960 [planetinfo.record]: corona_flare = 0.099434 13:48:34.960 [planetinfo.record]: corona_hues = 0.929420 13:48:34.960 [planetinfo.record]: sun_color = 0.784753, 0.530028, 0.220014, 1 13:48:34.960 [planetinfo.record]: corona_shimmer = 0.358826 13:48:34.960 [planetinfo.record]: station_vector = 0.843 -0.492 0.219 13:48:34.960 [planetinfo.record]: station = coriolis 13:48:39.685 [PLANETINFO OVER]: Done 13:48:39.685 [PLANETINFO LOGGING]: 5 131 13:48:40.692 [planetinfo.record]: seed = 177 76 232 72 13:48:40.692 [planetinfo.record]: coordinates = 76 95 13:48:40.692 [planetinfo.record]: government = 6; 13:48:40.692 [planetinfo.record]: economy = 7; 13:48:40.692 [planetinfo.record]: techlevel = 3; 13:48:40.692 [planetinfo.record]: population = 26; 13:48:40.692 [planetinfo.record]: productivity = 6240; 13:48:40.692 [planetinfo.record]: name = "Usraar"; 13:48:40.692 [planetinfo.record]: inhabitant = "Small Yellow Bony Lobster"; 13:48:40.692 [planetinfo.record]: inhabitants = "Small Yellow Bony Lobsters"; 13:48:40.692 [planetinfo.record]: description = "The world Usraar is a dull world."; 13:48:40.699 [planetinfo.record]: air_color = 0.687329, 0.844853, 0.901441, 1; 13:48:40.699 [planetinfo.record]: cloud_alpha = 1.000000; 13:48:40.699 [planetinfo.record]: cloud_color = 0.642581, 0.707031, 0.626938, 1; 13:48:40.699 [planetinfo.record]: cloud_fraction = 0.410000; 13:48:40.699 [planetinfo.record]: land_color = 0.66, 0.39403, 0.301641, 1; 13:48:40.699 [planetinfo.record]: land_fraction = 0.290000; 13:48:40.700 [planetinfo.record]: polar_cloud_color = 0.771551, 0.818164, 0.760237, 1; 13:48:40.700 [planetinfo.record]: polar_land_color = 0.934, 0.839903, 0.807217, 1; 13:48:40.700 [planetinfo.record]: polar_sea_color = 0.841577, 0.893148, 0.948047, 1; 13:48:40.700 [planetinfo.record]: sea_color = 0.286148, 0.399193, 0.519531, 1; 13:48:40.700 [planetinfo.record]: rotation_speed = 0.004146 13:48:40.700 [planetinfo.record]: planet zpos = 543400.000000 13:48:40.700 [planetinfo.record]: sun_radius = 126079.449463 13:48:40.700 [planetinfo.record]: sun_vector = -0.564 0.687 0.459 13:48:40.700 [planetinfo.record]: sun_distance = 938600 13:48:40.700 [planetinfo.record]: corona_flare = 0.023392 13:48:40.700 [planetinfo.record]: corona_hues = 0.902618 13:48:40.700 [planetinfo.record]: sun_color = 0.833215, 0.81646, 0.664487, 1 13:48:40.700 [planetinfo.record]: corona_shimmer = 0.658799 13:48:40.701 [planetinfo.record]: station_vector = 0.993 0.092 0.077 13:48:40.701 [planetinfo.record]: station = coriolis 13:48:45.712 [PLANETINFO OVER]: Done 13:48:45.713 [PLANETINFO LOGGING]: 5 132 13:48:46.726 [planetinfo.record]: seed = 193 199 154 70 13:48:46.726 [planetinfo.record]: coordinates = 199 138 13:48:46.726 [planetinfo.record]: government = 0; 13:48:46.726 [planetinfo.record]: economy = 2; 13:48:46.726 [planetinfo.record]: techlevel = 8; 13:48:46.726 [planetinfo.record]: population = 35; 13:48:46.726 [planetinfo.record]: productivity = 8960; 13:48:46.726 [planetinfo.record]: name = "Biedbi"; 13:48:46.726 [planetinfo.record]: inhabitant = "Fierce Yellow Furry Insect"; 13:48:46.726 [planetinfo.record]: inhabitants = "Fierce Yellow Furry Insects"; 13:48:46.726 [planetinfo.record]: description = "Biedbi is a revolting little planet."; 13:48:46.730 [planetinfo.record]: air_color = 0.682964, 0.792245, 0.950871, 1; 13:48:46.730 [planetinfo.record]: cloud_alpha = 1.000000; 13:48:46.730 [planetinfo.record]: cloud_color = 0.644943, 0.855469, 0.811061, 1; 13:48:46.730 [planetinfo.record]: cloud_fraction = 0.110000; 13:48:46.730 [planetinfo.record]: land_color = 0.586805, 0.66, 0.456328, 1; 13:48:46.730 [planetinfo.record]: land_fraction = 0.380000; 13:48:46.731 [planetinfo.record]: polar_cloud_color = 0.748846, 0.884961, 0.856249, 1; 13:48:46.731 [planetinfo.record]: polar_land_color = 0.908105, 0.934, 0.861943, 1; 13:48:46.731 [planetinfo.record]: polar_sea_color = 0.937695, 0.846124, 0.92768, 1; 13:48:46.731 [planetinfo.record]: sea_color = 0.623047, 0.379669, 0.596427, 1; 13:48:46.731 [planetinfo.record]: rotation_speed = 0.000648 13:48:46.731 [planetinfo.record]: planet zpos = 591630.000000 13:48:46.731 [planetinfo.record]: sun_radius = 115607.594147 13:48:46.731 [planetinfo.record]: sun_vector = -0.207 0.869 -0.449 13:48:46.731 [planetinfo.record]: sun_distance = 955710 13:48:46.731 [planetinfo.record]: corona_flare = 0.036612 13:48:46.731 [planetinfo.record]: corona_hues = 0.771011 13:48:46.731 [planetinfo.record]: sun_color = 0.658787, 0.736427, 0.764288, 1 13:48:46.731 [planetinfo.record]: corona_shimmer = 0.360456 13:48:46.732 [planetinfo.record]: station_vector = 0.992 -0.124 0.036 13:48:46.732 [planetinfo.record]: station = coriolis 13:48:52.273 [PLANETINFO OVER]: Done 13:48:52.273 [PLANETINFO LOGGING]: 5 133 13:48:53.277 [planetinfo.record]: seed = 29 134 160 197 13:48:53.277 [planetinfo.record]: coordinates = 134 166 13:48:53.277 [planetinfo.record]: government = 3; 13:48:53.277 [planetinfo.record]: economy = 6; 13:48:53.277 [planetinfo.record]: techlevel = 5; 13:48:53.277 [planetinfo.record]: population = 30; 13:48:53.277 [planetinfo.record]: productivity = 6720; 13:48:53.277 [planetinfo.record]: name = "Ceenrive"; 13:48:53.277 [planetinfo.record]: inhabitant = "Fierce Slimy Frog"; 13:48:53.277 [planetinfo.record]: inhabitants = "Fierce Slimy Frogs"; 13:48:53.277 [planetinfo.record]: description = "The planet Ceenrive is an unremarkable dump."; 13:48:53.307 [planetinfo.record]: air_color = 0.544066, 0.577915, 0.848109, 1; 13:48:53.307 [planetinfo.record]: cloud_alpha = 1.000000; 13:48:53.307 [planetinfo.record]: cloud_color = 0.286255, 0.369735, 0.546875, 1; 13:48:53.307 [planetinfo.record]: cloud_fraction = 0.290000; 13:48:53.307 [planetinfo.record]: land_color = 0.623342, 0.66, 0.608438, 1; 13:48:53.307 [planetinfo.record]: land_fraction = 0.510000; 13:48:53.307 [planetinfo.record]: polar_cloud_color = 0.523869, 0.59505, 0.746094, 1; 13:48:53.307 [planetinfo.record]: polar_land_color = 0.921031, 0.934, 0.915758, 1; 13:48:53.307 [planetinfo.record]: polar_sea_color = 0.749057, 0.937695, 0.871377, 1; 13:48:53.307 [planetinfo.record]: sea_color = 0.121689, 0.623047, 0.446788, 1; 13:48:53.307 [planetinfo.record]: rotation_speed = 0.004993 13:48:53.307 [planetinfo.record]: planet zpos = 592200.000000 13:48:53.307 [planetinfo.record]: sun_radius = 111149.807739 13:48:53.307 [planetinfo.record]: sun_vector = -0.481 -0.852 -0.206 13:48:53.307 [planetinfo.record]: sun_distance = 930600 13:48:53.315 [planetinfo.record]: corona_flare = 0.020738 13:48:53.315 [planetinfo.record]: corona_hues = 0.960838 13:48:53.315 [planetinfo.record]: sun_color = 0.672018, 0.441011, 0.340713, 1 13:48:53.316 [planetinfo.record]: corona_shimmer = 0.265875 13:48:53.316 [planetinfo.record]: station_vector = -0.656 -0.095 0.749 13:48:53.316 [planetinfo.record]: station = coriolis 13:48:57.858 [PLANETINFO OVER]: Done 13:48:57.859 [PLANETINFO LOGGING]: 5 134 13:48:58.863 [planetinfo.record]: seed = 181 246 202 39 13:48:58.863 [planetinfo.record]: coordinates = 246 62 13:48:58.863 [planetinfo.record]: government = 6; 13:48:58.863 [planetinfo.record]: economy = 6; 13:48:58.863 [planetinfo.record]: techlevel = 6; 13:48:58.863 [planetinfo.record]: population = 37; 13:48:58.863 [planetinfo.record]: productivity = 11840; 13:48:58.863 [planetinfo.record]: name = "Sotean"; 13:48:58.863 [planetinfo.record]: inhabitant = "Fierce Red Slimy Lobster"; 13:48:58.863 [planetinfo.record]: inhabitants = "Fierce Red Slimy Lobsters"; 13:48:58.863 [planetinfo.record]: description = "Sotean is mildly notable for its inhabitants’ ingrained silliness."; 13:48:58.874 [planetinfo.record]: air_color = 0.577837, 0.885173, 0.991195, 1; 13:48:58.874 [planetinfo.record]: cloud_alpha = 1.000000; 13:48:58.874 [planetinfo.record]: cloud_color = 0.478864, 0.976562, 0.339508, 1; 13:48:58.874 [planetinfo.record]: cloud_fraction = 0.280000; 13:48:58.874 [planetinfo.record]: land_color = 0.66, 0.113438, 0.65146, 1; 13:48:58.874 [planetinfo.record]: land_fraction = 0.540000; 13:48:58.874 [planetinfo.record]: polar_cloud_color = 0.640212, 0.939453, 0.556424, 1; 13:48:58.874 [planetinfo.record]: polar_land_color = 0.934, 0.740633, 0.930979, 1; 13:48:58.874 [planetinfo.record]: polar_sea_color = 0.941406, 0.931341, 0.891854, 1; 13:48:58.875 [planetinfo.record]: sea_color = 0.585938, 0.560879, 0.46257, 1; 13:48:58.875 [planetinfo.record]: rotation_speed = 0.000797 13:48:58.875 [planetinfo.record]: planet zpos = 631020.000000 13:48:58.875 [planetinfo.record]: sun_radius = 119139.125061 13:48:58.875 [planetinfo.record]: sun_vector = -0.991 0.100 0.086 13:48:58.875 [planetinfo.record]: sun_distance = 970800 13:48:58.875 [planetinfo.record]: corona_flare = 0.047252 13:48:58.875 [planetinfo.record]: corona_hues = 0.589767 13:48:58.875 [planetinfo.record]: sun_color = 0.653824, 0.651724, 0.613613, 1 13:48:58.875 [planetinfo.record]: corona_shimmer = 0.319582 13:48:58.875 [planetinfo.record]: station_vector = -0.912 -0.225 0.343 13:48:58.875 [planetinfo.record]: station = coriolis 13:49:03.680 [PLANETINFO OVER]: Done 13:49:03.681 [PLANETINFO LOGGING]: 5 135 13:49:04.684 [planetinfo.record]: seed = 25 0 104 216 13:49:04.684 [planetinfo.record]: coordinates = 0 123 13:49:04.684 [planetinfo.record]: government = 3; 13:49:04.684 [planetinfo.record]: economy = 3; 13:49:04.684 [planetinfo.record]: techlevel = 6; 13:49:04.684 [planetinfo.record]: population = 31; 13:49:04.684 [planetinfo.record]: productivity = 12152; 13:49:04.684 [planetinfo.record]: name = "Edbeined"; 13:49:04.684 [planetinfo.record]: inhabitant = "Human Colonial"; 13:49:04.684 [planetinfo.record]: inhabitants = "Human Colonials"; 13:49:04.684 [planetinfo.record]: description = "This world is a tedious place."; 13:49:04.697 [planetinfo.record]: air_color = 0.799821, 0.762519, 0.963879, 1; 13:49:04.697 [planetinfo.record]: cloud_alpha = 1.000000; 13:49:04.697 [planetinfo.record]: cloud_color = 0.884021, 0.870071, 0.894531, 1; 13:49:04.697 [planetinfo.record]: cloud_fraction = 0.260000; 13:49:04.697 [planetinfo.record]: land_color = 0.496007, 0.203672, 0.66, 1; 13:49:04.697 [planetinfo.record]: land_fraction = 0.480000; 13:49:04.697 [planetinfo.record]: polar_cloud_color = 0.895911, 0.887115, 0.902539, 1; 13:49:04.697 [planetinfo.record]: polar_land_color = 0.875981, 0.772557, 0.934, 1; 13:49:04.697 [planetinfo.record]: polar_sea_color = 0.933984, 0.900237, 0.87561, 1; 13:49:04.697 [planetinfo.record]: sea_color = 0.660156, 0.564743, 0.495117, 1; 13:49:04.697 [planetinfo.record]: rotation_speed = 0.003263 13:49:04.697 [planetinfo.record]: planet zpos = 437760.000000 13:49:04.697 [planetinfo.record]: sun_radius = 138055.781250 13:49:04.697 [planetinfo.record]: sun_vector = -0.042 0.983 -0.179 13:49:04.697 [planetinfo.record]: sun_distance = 924160 13:49:04.698 [planetinfo.record]: corona_flare = 0.022299 13:49:04.698 [planetinfo.record]: corona_hues = 0.527756 13:49:04.698 [planetinfo.record]: sun_color = 0.828653, 0.791029, 0.784452, 1 13:49:04.698 [planetinfo.record]: corona_shimmer = 0.706420 13:49:04.698 [planetinfo.record]: station_vector = -0.755 0.636 0.160 13:49:04.698 [planetinfo.record]: station = coriolis 13:49:09.430 [PLANETINFO OVER]: Done 13:49:09.431 [PLANETINFO LOGGING]: 5 136 13:49:10.434 [planetinfo.record]: seed = 185 88 10 217 13:49:10.434 [planetinfo.record]: coordinates = 88 44 13:49:10.434 [planetinfo.record]: government = 7; 13:49:10.434 [planetinfo.record]: economy = 4; 13:49:10.434 [planetinfo.record]: techlevel = 7; 13:49:10.434 [planetinfo.record]: population = 40; 13:49:10.434 [planetinfo.record]: productivity = 21120; 13:49:10.434 [planetinfo.record]: name = "Orriaso"; 13:49:10.434 [planetinfo.record]: inhabitant = "Human Colonial"; 13:49:10.434 [planetinfo.record]: inhabitants = "Human Colonials"; 13:49:10.434 [planetinfo.record]: description = "Orriaso is cursed by killer mountain goats."; 13:49:10.436 [planetinfo.record]: air_color = 0.44246, 0.720021, 0.869572, 1; 13:49:10.436 [planetinfo.record]: cloud_alpha = 1.000000; 13:49:10.436 [planetinfo.record]: cloud_color = 0.076416, 0.611328, 0.109848, 1; 13:49:10.436 [planetinfo.record]: cloud_fraction = 0.250000; 13:49:10.437 [planetinfo.record]: land_color = 0.464063, 0.66, 0.517639, 1; 13:49:10.437 [planetinfo.record]: land_fraction = 0.250000; 13:49:10.437 [planetinfo.record]: polar_cloud_color = 0.351216, 0.775098, 0.377709, 1; 13:49:10.437 [planetinfo.record]: polar_land_color = 0.86468, 0.934, 0.883634, 1; 13:49:10.437 [planetinfo.record]: polar_sea_color = 0.926562, 0.908932, 0.848746, 1; 13:49:10.437 [planetinfo.record]: sea_color = 0.734375, 0.678481, 0.487671, 1; 13:49:10.437 [planetinfo.record]: rotation_speed = 0.003510 13:49:10.437 [planetinfo.record]: planet zpos = 624960.000000 13:49:10.437 [planetinfo.record]: sun_radius = 144974.648438 13:49:10.437 [planetinfo.record]: sun_vector = -0.331 -0.699 0.634 13:49:10.437 [planetinfo.record]: sun_distance = 1041600 13:49:10.437 [planetinfo.record]: corona_flare = 0.002504 13:49:10.437 [planetinfo.record]: corona_hues = 0.547905 13:49:10.438 [planetinfo.record]: sun_color = 0.24034, 0.386097, 0.737888, 1 13:49:10.438 [planetinfo.record]: corona_shimmer = 0.303537 13:49:10.438 [planetinfo.record]: station_vector = 0.857 0.450 0.250 13:49:10.438 [planetinfo.record]: station = coriolis 13:49:14.746 [PLANETINFO OVER]: Done 13:49:14.747 [PLANETINFO LOGGING]: 5 137 13:49:15.751 [planetinfo.record]: seed = 37 199 64 181 13:49:15.751 [planetinfo.record]: coordinates = 199 143 13:49:15.751 [planetinfo.record]: government = 4; 13:49:15.751 [planetinfo.record]: economy = 7; 13:49:15.751 [planetinfo.record]: techlevel = 5; 13:49:15.751 [planetinfo.record]: population = 32; 13:49:15.751 [planetinfo.record]: productivity = 6144; 13:49:15.751 [planetinfo.record]: name = "Lainusar"; 13:49:15.751 [planetinfo.record]: inhabitant = "Human Colonial"; 13:49:15.751 [planetinfo.record]: inhabitants = "Human Colonials"; 13:49:15.751 [planetinfo.record]: description = "The world Lainusar is reasonably fabled for Zero-G cricket and its fabulous cuisine."; 13:49:15.768 [planetinfo.record]: air_color = 0.581026, 0.865822, 0.994447, 1; 13:49:15.768 [planetinfo.record]: cloud_alpha = 1.000000; 13:49:15.768 [planetinfo.record]: cloud_color = 0.381733, 0.986328, 0.346756, 1; 13:49:15.768 [planetinfo.record]: cloud_fraction = 0.460000; 13:49:15.768 [planetinfo.record]: land_color = 0.572585, 0.66, 0.569766, 1; 13:49:15.768 [planetinfo.record]: land_fraction = 0.520000; 13:49:15.768 [planetinfo.record]: polar_cloud_color = 0.58225, 0.943848, 0.561331, 1; 13:49:15.768 [planetinfo.record]: polar_land_color = 0.903074, 0.934, 0.902076, 1; 13:49:15.768 [planetinfo.record]: polar_sea_color = 0.890288, 0.839756, 0.94082, 1; 13:49:15.768 [planetinfo.record]: sea_color = 0.464653, 0.337509, 0.591797, 1; 13:49:15.768 [planetinfo.record]: rotation_speed = 0.004058 13:49:15.768 [planetinfo.record]: planet zpos = 644250.000000 13:49:15.768 [planetinfo.record]: sun_radius = 99546.009827 13:49:15.768 [planetinfo.record]: sun_vector = 0.407 0.273 0.872 13:49:15.768 [planetinfo.record]: sun_distance = 859000 13:49:15.769 [planetinfo.record]: corona_flare = 0.085034 13:49:15.769 [planetinfo.record]: corona_hues = 0.742531 13:49:15.769 [planetinfo.record]: sun_color = 0.701147, 0.620692, 0.327566, 1 13:49:15.769 [planetinfo.record]: corona_shimmer = 0.868970 13:49:15.769 [planetinfo.record]: station_vector = 0.663 -0.731 0.159 13:49:15.769 [planetinfo.record]: station = coriolis 13:49:21.082 [PLANETINFO OVER]: Done 13:49:21.083 [PLANETINFO LOGGING]: 5 138 13:49:22.086 [planetinfo.record]: seed = 77 74 90 223 13:49:22.086 [planetinfo.record]: coordinates = 74 136 13:49:22.086 [planetinfo.record]: government = 1; 13:49:22.086 [planetinfo.record]: economy = 2; 13:49:22.086 [planetinfo.record]: techlevel = 8; 13:49:22.086 [planetinfo.record]: population = 36; 13:49:22.086 [planetinfo.record]: productivity = 11520; 13:49:22.086 [planetinfo.record]: name = "Onente"; 13:49:22.087 [planetinfo.record]: inhabitant = "Human Colonial"; 13:49:22.087 [planetinfo.record]: inhabitants = "Human Colonials"; 13:49:22.087 [planetinfo.record]: description = "Onente is cursed by dreadful civil war."; 13:49:22.096 [planetinfo.record]: air_color = 0.456375, 0.683326, 0.870223, 1; 13:49:22.096 [planetinfo.record]: cloud_alpha = 1.000000; 13:49:22.096 [planetinfo.record]: cloud_color = 0.105408, 0.613281, 0.291893, 1; 13:49:22.103 [planetinfo.record]: cloud_fraction = 0.390000; 13:49:22.103 [planetinfo.record]: land_color = 0.496718, 0.310898, 0.636719, 1; 13:49:22.103 [planetinfo.record]: land_fraction = 0.650000; 13:49:22.104 [planetinfo.record]: polar_cloud_color = 0.374348, 0.775977, 0.521821, 1; 13:49:22.104 [planetinfo.record]: polar_land_color = 0.884858, 0.816544, 0.936328, 1; 13:49:22.104 [planetinfo.record]: polar_sea_color = 0.939062, 0.938273, 0.888533, 1; 13:49:22.104 [planetinfo.record]: sea_color = 0.609375, 0.607326, 0.478217, 1; 13:49:22.104 [planetinfo.record]: rotation_speed = 0.003668 13:49:22.104 [planetinfo.record]: planet zpos = 740300.000000 13:49:22.104 [planetinfo.record]: sun_radius = 140237.771606 13:49:22.104 [planetinfo.record]: sun_vector = -0.497 0.821 -0.283 13:49:22.104 [planetinfo.record]: sun_distance = 1278700 13:49:22.104 [planetinfo.record]: corona_flare = 0.083873 13:49:22.104 [planetinfo.record]: corona_hues = 0.708565 13:49:22.104 [planetinfo.record]: sun_color = 0.649783, 0.684171, 0.751996, 1 13:49:22.104 [planetinfo.record]: corona_shimmer = 0.666301 13:49:22.104 [planetinfo.record]: station_vector = 0.839 0.053 0.541 13:49:22.104 [planetinfo.record]: station = coriolis 13:49:27.360 [PLANETINFO OVER]: Done 13:49:27.363 [PLANETINFO LOGGING]: 5 139 13:49:28.365 [planetinfo.record]: seed = 193 109 40 252 13:49:28.365 [planetinfo.record]: coordinates = 109 220 13:49:28.365 [planetinfo.record]: government = 0; 13:49:28.365 [planetinfo.record]: economy = 6; 13:49:28.365 [planetinfo.record]: techlevel = 2; 13:49:28.365 [planetinfo.record]: population = 15; 13:49:28.365 [planetinfo.record]: productivity = 1920; 13:49:28.365 [planetinfo.record]: name = "Tecea"; 13:49:28.365 [planetinfo.record]: inhabitant = "Human Colonial"; 13:49:28.365 [planetinfo.record]: inhabitants = "Human Colonials"; 13:49:28.365 [planetinfo.record]: description = "The world Tecea is a boring planet."; 13:49:28.385 [planetinfo.record]: air_color = 0.562135, 0.959977, 0.919188, 1; 13:49:28.385 [planetinfo.record]: cloud_alpha = 1.000000; 13:49:28.385 [planetinfo.record]: cloud_color = 0.882812, 0.713891, 0.313812, 1; 13:49:28.385 [planetinfo.record]: cloud_fraction = 0.200000; 13:49:28.385 [planetinfo.record]: land_color = 0.48785, 0.550781, 0.0473328, 1; 13:49:28.385 [planetinfo.record]: land_fraction = 0.330000; 13:49:28.385 [planetinfo.record]: polar_cloud_color = 0.897266, 0.789961, 0.535818, 1; 13:49:28.385 [planetinfo.record]: polar_land_color = 0.917931, 0.944922, 0.728992, 1; 13:49:28.385 [planetinfo.record]: polar_sea_color = 0.942773, 0.893356, 0.886796, 1; 13:49:28.385 [planetinfo.record]: sea_color = 0.572266, 0.45228, 0.436353, 1; 13:49:28.389 [planetinfo.record]: rotation_speed = 0.002358 13:49:28.389 [planetinfo.record]: planet zpos = 599700.000000 13:49:28.390 [planetinfo.record]: sun_radius = 170230.393982 13:49:28.390 [planetinfo.record]: sun_vector = -0.570 0.324 -0.756 13:49:28.390 [planetinfo.record]: sun_distance = 1079460 13:49:28.390 [planetinfo.record]: corona_flare = 0.054721 13:49:28.390 [planetinfo.record]: corona_hues = 0.721977 13:49:28.390 [planetinfo.record]: sun_color = 0.82319, 0.801922, 0.628465, 1 13:49:28.390 [planetinfo.record]: corona_shimmer = 0.302673 13:49:28.390 [planetinfo.record]: station_vector = 0.994 -0.104 0.030 13:49:28.390 [planetinfo.record]: station = coriolis 13:49:34.290 [PLANETINFO OVER]: Done 13:49:34.292 [PLANETINFO LOGGING]: 5 140 13:49:35.300 [planetinfo.record]: seed = 241 241 186 231 13:49:35.300 [planetinfo.record]: coordinates = 241 175 13:49:35.300 [planetinfo.record]: government = 6; 13:49:35.300 [planetinfo.record]: economy = 7; 13:49:35.300 [planetinfo.record]: techlevel = 4; 13:49:35.300 [planetinfo.record]: population = 30; 13:49:35.300 [planetinfo.record]: productivity = 7200; 13:49:35.300 [planetinfo.record]: name = "Soesgera"; 13:49:35.300 [planetinfo.record]: inhabitant = "Fierce Frog"; 13:49:35.300 [planetinfo.record]: inhabitants = "Fierce Frogs"; 13:49:35.300 [planetinfo.record]: description = "This world is reasonably well known for its great tropical forests but cursed by unpredictable solar activity."; 13:49:35.320 [planetinfo.record]: air_color = 0.690677, 0.851876, 0.883231, 1; 13:49:35.320 [planetinfo.record]: cloud_alpha = 1.000000; 13:49:35.320 [planetinfo.record]: cloud_color = 0.634506, 0.652344, 0.616669, 1; 13:49:35.320 [planetinfo.record]: cloud_fraction = 0.360000; 13:49:35.320 [planetinfo.record]: land_color = 0.580078, 0.407867, 0.549134, 1; 13:49:35.320 [planetinfo.record]: land_fraction = 0.490000; 13:49:35.320 [planetinfo.record]: polar_cloud_color = 0.779993, 0.793555, 0.766431, 1; 13:49:35.320 [planetinfo.record]: polar_land_color = 0.941992, 0.872079, 0.92943, 1; 13:49:35.320 [planetinfo.record]: polar_sea_color = 0.832562, 0.948242, 0.767669, 1; 13:49:35.320 [planetinfo.record]: sea_color = 0.265012, 0.517578, 0.123329, 1; 13:49:35.320 [planetinfo.record]: rotation_speed = 0.001353 13:49:35.320 [planetinfo.record]: planet zpos = 484900.000000 13:49:35.320 [planetinfo.record]: sun_radius = 131332.755890 13:49:35.321 [planetinfo.record]: sun_vector = -0.970 0.181 -0.160 13:49:35.321 [planetinfo.record]: sun_distance = 921310 13:49:35.321 [planetinfo.record]: corona_flare = 0.017505 13:49:35.321 [planetinfo.record]: corona_hues = 0.835060 13:49:35.321 [planetinfo.record]: sun_color = 0.478513, 0.614607, 0.793066, 1 13:49:35.321 [planetinfo.record]: corona_shimmer = 0.336520 13:49:35.321 [planetinfo.record]: station_vector = -0.908 0.361 0.213 13:49:35.321 [planetinfo.record]: station = coriolis 13:49:40.200 [PLANETINFO OVER]: Done 13:49:40.202 [PLANETINFO LOGGING]: 5 141 13:49:41.207 [planetinfo.record]: seed = 109 212 32 193 13:49:41.207 [planetinfo.record]: coordinates = 212 99 13:49:41.207 [planetinfo.record]: government = 5; 13:49:41.207 [planetinfo.record]: economy = 3; 13:49:41.207 [planetinfo.record]: techlevel = 7; 13:49:41.207 [planetinfo.record]: population = 37; 13:49:41.207 [planetinfo.record]: productivity = 18648; 13:49:41.207 [planetinfo.record]: name = "Leedre"; 13:49:41.207 [planetinfo.record]: inhabitant = "Human Colonial"; 13:49:41.207 [planetinfo.record]: inhabitants = "Human Colonials"; 13:49:41.207 [planetinfo.record]: description = "The world Leedre is beset by evil disease."; 13:49:41.219 [planetinfo.record]: air_color = 0.512166, 0.500437, 0.902742, 1; 13:49:41.219 [planetinfo.record]: cloud_alpha = 1.000000; 13:49:41.219 [planetinfo.record]: cloud_color = 0.239373, 0.186066, 0.710938, 1; 13:49:41.219 [planetinfo.record]: cloud_fraction = 0.290000; 13:49:41.219 [planetinfo.record]: land_color = 0.66, 0.0696094, 0.194145, 1; 13:49:41.219 [planetinfo.record]: land_fraction = 0.290000; 13:49:41.219 [planetinfo.record]: polar_cloud_color = 0.480013, 0.441589, 0.819922, 1; 13:49:41.219 [planetinfo.record]: polar_land_color = 0.934, 0.725127, 0.769186, 1; 13:49:41.219 [planetinfo.record]: polar_sea_color = 0.941016, 0.913763, 0.887348, 1; 13:49:41.219 [planetinfo.record]: sea_color = 0.589844, 0.521513, 0.455286, 1; 13:49:41.219 [planetinfo.record]: rotation_speed = 0.003128 13:49:41.219 [planetinfo.record]: planet zpos = 361240.000000 13:49:41.220 [planetinfo.record]: sun_radius = 90982.474365 13:49:41.220 [planetinfo.record]: sun_vector = -0.437 0.886 -0.152 13:49:41.220 [planetinfo.record]: sun_distance = 722480 13:49:41.220 [planetinfo.record]: corona_flare = 0.093945 13:49:41.220 [planetinfo.record]: corona_hues = 0.940269 13:49:41.220 [planetinfo.record]: sun_color = 0.800714, 0.629558, 0.52933, 1 13:49:41.220 [planetinfo.record]: corona_shimmer = 1.062585 13:49:41.220 [planetinfo.record]: station_vector = 0.893 -0.049 0.447 13:49:41.220 [planetinfo.record]: station = coriolis 13:49:46.208 [PLANETINFO OVER]: Done 13:49:46.211 [PLANETINFO LOGGING]: 5 142 13:49:47.239 [planetinfo.record]: seed = 37 72 42 207 13:49:47.240 [planetinfo.record]: coordinates = 72 142 13:49:47.240 [planetinfo.record]: government = 4; 13:49:47.240 [planetinfo.record]: economy = 6; 13:49:47.240 [planetinfo.record]: techlevel = 3; 13:49:47.240 [planetinfo.record]: population = 23; 13:49:47.240 [planetinfo.record]: productivity = 5888; 13:49:47.240 [planetinfo.record]: name = "Aceteat"; 13:49:47.240 [planetinfo.record]: inhabitant = "Human Colonial"; 13:49:47.241 [planetinfo.record]: inhabitants = "Human Colonials"; 13:49:47.241 [planetinfo.record]: description = "Aceteat is most noted for the Aceteatian evil poet and Aceteatian lethal brandy."; 13:49:47.242 [planetinfo.record]: air_color = 0.68938, 0.590441, 0.867621, 1; 13:49:47.242 [planetinfo.record]: cloud_alpha = 1.000000; 13:49:47.243 [planetinfo.record]: cloud_color = 0.605469, 0.387878, 0.58677, 1; 13:49:47.243 [planetinfo.record]: cloud_fraction = 0.360000; 13:49:47.243 [planetinfo.record]: land_color = 0.614108, 0.615234, 0.579185, 1; 13:49:47.243 [planetinfo.record]: land_fraction = 0.470000; 13:49:47.243 [planetinfo.record]: polar_cloud_color = 0.772461, 0.598959, 0.757551, 1; 13:49:47.243 [planetinfo.record]: polar_land_color = 0.938047, 0.938477, 0.924729, 1; 13:49:47.243 [planetinfo.record]: polar_sea_color = 0.821808, 0.90918, 0.790205, 1; 13:49:47.244 [planetinfo.record]: sea_color = 0.55909, 0.908203, 0.432816, 1; 13:49:47.244 [planetinfo.record]: rotation_speed = 0.001473 13:49:47.244 [planetinfo.record]: planet zpos = 672800.000000 13:49:47.244 [planetinfo.record]: sun_radius = 168413.535156 13:49:47.244 [planetinfo.record]: sun_vector = 0.354 -0.577 0.736 13:49:47.244 [planetinfo.record]: sun_distance = 1076480 13:49:47.244 [planetinfo.record]: corona_flare = 0.045346 13:49:47.244 [planetinfo.record]: corona_hues = 0.893929 13:49:47.244 [planetinfo.record]: sun_color = 0.648164, 0.655656, 0.786835, 1 13:49:47.244 [planetinfo.record]: corona_shimmer = 0.271584 13:49:47.244 [planetinfo.record]: station_vector = 0.279 -0.942 0.189 13:49:47.245 [planetinfo.record]: station = coriolis 13:49:52.720 [PLANETINFO OVER]: Done 13:49:52.722 [PLANETINFO LOGGING]: 5 143 13:49:53.725 [planetinfo.record]: seed = 169 49 40 148 13:49:53.725 [planetinfo.record]: coordinates = 49 188 13:49:53.725 [planetinfo.record]: government = 5; 13:49:53.725 [planetinfo.record]: economy = 4; 13:49:53.725 [planetinfo.record]: techlevel = 7; 13:49:53.725 [planetinfo.record]: population = 38; 13:49:53.725 [planetinfo.record]: productivity = 16416; 13:49:53.725 [planetinfo.record]: name = "Raxeuson"; 13:49:53.725 [planetinfo.record]: inhabitant = "Human Colonial"; 13:49:53.725 [planetinfo.record]: inhabitants = "Human Colonials"; 13:49:53.726 [planetinfo.record]: description = "This planet is a tedious little planet."; 13:49:53.760 [planetinfo.record]: air_color = 0.689471, 0.850349, 0.889084, 1; 13:49:53.760 [planetinfo.record]: cloud_alpha = 1.000000; 13:49:53.760 [planetinfo.record]: cloud_color = 0.6404, 0.669922, 0.620201, 1; 13:49:53.760 [planetinfo.record]: cloud_fraction = 0.340000; 13:49:53.760 [planetinfo.record]: land_color = 0.198441, 0.564453, 0.521561, 1; 13:49:53.760 [planetinfo.record]: land_fraction = 0.290000; 13:49:53.760 [planetinfo.record]: polar_cloud_color = 0.779391, 0.801465, 0.764288, 1; 13:49:53.760 [planetinfo.record]: polar_land_color = 0.790596, 0.943555, 0.92563, 1; 13:49:53.760 [planetinfo.record]: polar_sea_color = 0.91875, 0.933398, 0.878251, 1; 13:49:53.760 [planetinfo.record]: sea_color = 0.624207, 0.666016, 0.508617, 1; 13:49:53.760 [planetinfo.record]: rotation_speed = 0.001364 13:49:53.760 [planetinfo.record]: planet zpos = 350010.000000 13:49:53.761 [planetinfo.record]: sun_radius = 108736.644135 13:49:53.761 [planetinfo.record]: sun_vector = -0.568 -0.798 -0.201 13:49:53.761 [planetinfo.record]: sun_distance = 777800 13:49:53.761 [planetinfo.record]: corona_flare = 0.041785 13:49:53.761 [planetinfo.record]: corona_hues = 0.668243 13:49:53.761 [planetinfo.record]: sun_color = 0.792273, 0.589903, 0.348741, 1 13:49:53.761 [planetinfo.record]: corona_shimmer = 0.457302 13:49:53.761 [planetinfo.record]: station_vector = 0.707 0.409 0.577 13:49:53.761 [planetinfo.record]: station = coriolis 13:49:58.945 [PLANETINFO OVER]: Done 13:49:58.946 [PLANETINFO LOGGING]: 5 144 13:49:59.951 [planetinfo.record]: seed = 105 95 170 42 13:49:59.952 [planetinfo.record]: coordinates = 95 72 13:49:59.952 [planetinfo.record]: government = 5; 13:49:59.952 [planetinfo.record]: economy = 0; 13:49:59.952 [planetinfo.record]: techlevel = 13; 13:49:59.952 [planetinfo.record]: population = 58; 13:49:59.952 [planetinfo.record]: productivity = 41760; 13:49:59.952 [planetinfo.record]: name = "Arente"; 13:49:59.952 [planetinfo.record]: inhabitant = "Small Red Frog"; 13:49:59.952 [planetinfo.record]: inhabitants = "Small Red Frogs"; 13:49:59.952 [planetinfo.record]: description = "The world Arente is a dull world."; 13:49:59.968 [planetinfo.record]: air_color = 0.670791, 0.87348, 0.890385, 1; 13:49:59.968 [planetinfo.record]: cloud_alpha = 1.000000; 13:49:59.968 [planetinfo.record]: cloud_color = 0.650879, 0.673828, 0.579071, 1; 13:49:59.968 [planetinfo.record]: cloud_fraction = 0.520000; 13:49:59.968 [planetinfo.record]: land_color = 0.646484, 0.391426, 0.546852, 1; 13:49:59.968 [planetinfo.record]: land_fraction = 0.660000; 13:49:59.968 [planetinfo.record]: polar_cloud_color = 0.786125, 0.803223, 0.732627, 1; 13:49:59.968 [planetinfo.record]: polar_land_color = 0.935352, 0.843095, 0.899314, 1; 13:49:59.968 [planetinfo.record]: polar_sea_color = 0.926179, 0.938867, 0.891099, 1; 13:49:59.968 [planetinfo.record]: sea_color = 0.57828, 0.611328, 0.486913, 1; 13:49:59.968 [planetinfo.record]: rotation_speed = 0.004057 13:49:59.968 [planetinfo.record]: planet zpos = 711230.000000 13:49:59.969 [planetinfo.record]: sun_radius = 148741.142883 13:49:59.969 [planetinfo.record]: sun_vector = -0.760 0.193 -0.620 13:49:59.969 [planetinfo.record]: sun_distance = 1094200 13:49:59.969 [planetinfo.record]: corona_flare = 0.086040 13:49:59.969 [planetinfo.record]: corona_hues = 0.525047 13:49:59.969 [planetinfo.record]: sun_color = 0.242716, 0.680017, 0.71196, 1 13:49:59.969 [planetinfo.record]: corona_shimmer = 0.394710 13:49:59.969 [planetinfo.record]: station_vector = 0.572 -0.588 0.572 13:49:59.969 [planetinfo.record]: station = dodecahedron 13:50:05.468 [PLANETINFO OVER]: Done 13:50:05.470 [PLANETINFO LOGGING]: 5 145 13:50:06.492 [planetinfo.record]: seed = 245 89 64 137 13:50:06.492 [planetinfo.record]: coordinates = 89 92 13:50:06.492 [planetinfo.record]: government = 6; 13:50:06.492 [planetinfo.record]: economy = 4; 13:50:06.492 [planetinfo.record]: techlevel = 7; 13:50:06.492 [planetinfo.record]: population = 39; 13:50:06.492 [planetinfo.record]: productivity = 18720; 13:50:06.492 [planetinfo.record]: name = "Esonge"; 13:50:06.492 [planetinfo.record]: inhabitant = "Human Colonial"; 13:50:06.492 [planetinfo.record]: inhabitants = "Human Colonials"; 13:50:06.492 [planetinfo.record]: description = "This world is very notable for its inhabitants’ weird silliness and its inhabitants’ unusual silliness."; 13:50:06.501 [planetinfo.record]: air_color = 0.528732, 0.592092, 0.850061, 1; 13:50:06.501 [planetinfo.record]: cloud_alpha = 1.000000; 13:50:06.502 [planetinfo.record]: cloud_color = 0.256935, 0.427944, 0.552734, 1; 13:50:06.502 [planetinfo.record]: cloud_fraction = 0.490000; 13:50:06.502 [planetinfo.record]: land_color = 0.0438281, 0.66, 0.313403, 1; 13:50:06.502 [planetinfo.record]: land_fraction = 0.590000; 13:50:06.502 [planetinfo.record]: polar_cloud_color = 0.498301, 0.64308, 0.74873, 1; 13:50:06.502 [planetinfo.record]: polar_land_color = 0.716006, 0.934, 0.811378, 1; 13:50:06.503 [planetinfo.record]: polar_sea_color = 0.935742, 0.87149, 0.869948, 1; 13:50:06.503 [planetinfo.record]: sea_color = 0.642578, 0.466089, 0.461853, 1; 13:50:06.503 [planetinfo.record]: rotation_speed = 0.002146 13:50:06.503 [planetinfo.record]: planet zpos = 677170.000000 13:50:06.503 [planetinfo.record]: sun_radius = 109761.298523 13:50:06.503 [planetinfo.record]: sun_vector = -0.827 0.359 -0.432 13:50:06.503 [planetinfo.record]: sun_distance = 1198070 13:50:06.504 [planetinfo.record]: corona_flare = 0.072105 13:50:06.504 [planetinfo.record]: corona_hues = 0.602592 13:50:06.504 [planetinfo.record]: sun_color = 0.482674, 0.623929, 0.650821, 1 13:50:06.504 [planetinfo.record]: corona_shimmer = 0.375521 13:50:06.504 [planetinfo.record]: station_vector = -0.588 -0.697 0.411 13:50:06.504 [planetinfo.record]: station = coriolis 13:50:12.827 [PLANETINFO OVER]: Done 13:50:12.828 [PLANETINFO LOGGING]: 5 146 13:50:13.843 [planetinfo.record]: seed = 61 236 58 79 13:50:13.843 [planetinfo.record]: coordinates = 236 35 13:50:13.843 [planetinfo.record]: government = 7; 13:50:13.843 [planetinfo.record]: economy = 3; 13:50:13.843 [planetinfo.record]: techlevel = 8; 13:50:13.843 [planetinfo.record]: population = 43; 13:50:13.843 [planetinfo.record]: productivity = 26488; 13:50:13.843 [planetinfo.record]: name = "Ariqu"; 13:50:13.843 [planetinfo.record]: inhabitant = "Human Colonial"; 13:50:13.843 [planetinfo.record]: inhabitants = "Human Colonials"; 13:50:13.843 [planetinfo.record]: description = "The planet Ariqu is mildly well known for its hoopy casinos."; 13:50:13.868 [planetinfo.record]: air_color = 0.670227, 0.513241, 0.934611, 1; 13:50:13.868 [planetinfo.record]: cloud_alpha = 1.000000; 13:50:13.868 [planetinfo.record]: cloud_color = 0.806641, 0.198509, 0.725873, 1; 13:50:13.868 [planetinfo.record]: cloud_fraction = 0.350000; 13:50:13.868 [planetinfo.record]: land_color = 0.502734, 0.66, 0.630513, 1; 13:50:13.868 [planetinfo.record]: land_fraction = 0.350000; 13:50:13.868 [planetinfo.record]: polar_cloud_color = 0.862988, 0.456356, 0.808982, 1; 13:50:13.868 [planetinfo.record]: polar_land_color = 0.878361, 0.934, 0.923568, 1; 13:50:13.868 [planetinfo.record]: polar_sea_color = 0.934961, 0.900722, 0.880178, 1; 13:50:13.868 [planetinfo.record]: sea_color = 0.650391, 0.555119, 0.497955, 1; 13:50:13.868 [planetinfo.record]: rotation_speed = 0.004251 13:50:13.868 [planetinfo.record]: planet zpos = 689200.000000 13:50:13.868 [planetinfo.record]: sun_radius = 182732.226562 13:50:13.868 [planetinfo.record]: sun_vector = 0.467 0.067 -0.882 13:50:13.868 [planetinfo.record]: sun_distance = 1378400 13:50:13.868 [planetinfo.record]: corona_flare = 0.059953 13:50:13.869 [planetinfo.record]: corona_hues = 0.995544 13:50:13.869 [planetinfo.record]: sun_color = 0.665483, 0.712207, 0.712787, 1 13:50:13.869 [planetinfo.record]: corona_shimmer = 0.478274 13:50:13.869 [planetinfo.record]: station_vector = 0.575 -0.665 0.478 13:50:13.869 [planetinfo.record]: station = coriolis 13:50:18.811 [PLANETINFO OVER]: Done 13:50:18.811 [PLANETINFO LOGGING]: 5 147 13:50:19.817 [planetinfo.record]: seed = 209 71 104 64 13:50:19.817 [planetinfo.record]: coordinates = 71 154 13:50:19.817 [planetinfo.record]: government = 2; 13:50:19.817 [planetinfo.record]: economy = 2; 13:50:19.817 [planetinfo.record]: techlevel = 9; 13:50:19.817 [planetinfo.record]: population = 41; 13:50:19.817 [planetinfo.record]: productivity = 15744; 13:50:19.817 [planetinfo.record]: name = "Xear"; 13:50:19.817 [planetinfo.record]: inhabitant = "Human Colonial"; 13:50:19.817 [planetinfo.record]: inhabitants = "Human Colonials"; 13:50:19.817 [planetinfo.record]: description = "The world Xear is a boring planet."; 13:50:19.825 [planetinfo.record]: air_color = 0.481642, 0.451953, 0.943717, 1; 13:50:19.825 [planetinfo.record]: cloud_alpha = 1.000000; 13:50:19.825 [planetinfo.record]: cloud_color = 0.186481, 0.0293198, 0.833984, 1; 13:50:19.825 [planetinfo.record]: cloud_fraction = 0.360000; 13:50:19.825 [planetinfo.record]: land_color = 0.131484, 0.511355, 0.66, 1; 13:50:19.826 [planetinfo.record]: land_fraction = 0.640000; 13:50:19.826 [planetinfo.record]: polar_cloud_color = 0.450558, 0.347467, 0.875293, 1; 13:50:19.826 [planetinfo.record]: polar_land_color = 0.747018, 0.881411, 0.934, 1; 13:50:19.826 [planetinfo.record]: polar_sea_color = 0.882473, 0.929492, 0.901942, 1; 13:50:19.826 [planetinfo.record]: sea_color = 0.56241, 0.705078, 0.621484, 1; 13:50:19.826 [planetinfo.record]: rotation_speed = 0.000391 13:50:19.826 [planetinfo.record]: planet zpos = 288700.000000 13:50:19.826 [planetinfo.record]: sun_radius = 70366.219788 13:50:19.826 [planetinfo.record]: sun_vector = 0.255 -0.375 -0.892 13:50:19.826 [planetinfo.record]: sun_distance = 519660 13:50:19.826 [planetinfo.record]: corona_flare = 0.073033 13:50:19.826 [planetinfo.record]: corona_hues = 0.952881 13:50:19.827 [planetinfo.record]: sun_color = 0.849487, 0.722796, 0.709963, 1 13:50:19.827 [planetinfo.record]: corona_shimmer = 0.360778 13:50:19.827 [planetinfo.record]: station_vector = -0.240 0.969 0.051 13:50:19.827 [planetinfo.record]: station = coriolis 13:50:25.337 [PLANETINFO OVER]: Done 13:50:25.337 [PLANETINFO LOGGING]: 5 148 13:50:26.340 [planetinfo.record]: seed = 33 13 218 217 13:50:26.340 [planetinfo.record]: coordinates = 13 170 13:50:26.340 [planetinfo.record]: government = 4; 13:50:26.340 [planetinfo.record]: economy = 2; 13:50:26.340 [planetinfo.record]: techlevel = 8; 13:50:26.341 [planetinfo.record]: population = 39; 13:50:26.341 [planetinfo.record]: productivity = 19968; 13:50:26.341 [planetinfo.record]: name = "Oratedge"; 13:50:26.341 [planetinfo.record]: inhabitant = "Rodent"; 13:50:26.341 [planetinfo.record]: inhabitants = "Rodents"; 13:50:26.341 [planetinfo.record]: description = "This world is a revolting dump."; 13:50:26.380 [planetinfo.record]: air_color = 0.583176, 0.951522, 0.93963, 1; 13:50:26.380 [planetinfo.record]: cloud_alpha = 1.000000; 13:50:26.380 [planetinfo.record]: cloud_color = 0.857422, 0.815974, 0.375122, 1; 13:50:26.381 [planetinfo.record]: cloud_fraction = 0.320000; 13:50:26.381 [planetinfo.record]: land_color = 0.426483, 0.371094, 0.5, 1; 13:50:26.381 [planetinfo.record]: land_fraction = 0.630000; 13:50:26.381 [planetinfo.record]: polar_cloud_color = 0.88584, 0.859076, 0.574412, 1; 13:50:26.381 [planetinfo.record]: polar_land_color = 0.915079, 0.88877, 0.95, 1; 13:50:26.381 [planetinfo.record]: polar_sea_color = 0.889167, 0.923633, 0.8632, 1; 13:50:26.381 [planetinfo.record]: sea_color = 0.649685, 0.763672, 0.563805, 1; 13:50:26.381 [planetinfo.record]: rotation_speed = 0.001937 13:50:26.381 [planetinfo.record]: planet zpos = 667290.000000 13:50:26.381 [planetinfo.record]: sun_radius = 111533.253937 13:50:26.381 [planetinfo.record]: sun_vector = -0.600 0.693 0.399 13:50:26.381 [planetinfo.record]: sun_distance = 923940 13:50:26.381 [planetinfo.record]: corona_flare = 0.086572 13:50:26.381 [planetinfo.record]: corona_hues = 0.793571 13:50:26.381 [planetinfo.record]: sun_color = 0.663971, 0.593277, 0.548693, 1 13:50:26.382 [planetinfo.record]: corona_shimmer = 0.791230 13:50:26.382 [planetinfo.record]: station_vector = -0.022 -0.982 0.187 13:50:26.382 [planetinfo.record]: station = coriolis 13:50:31.655 [PLANETINFO OVER]: Done 13:50:31.655 [PLANETINFO LOGGING]: 5 149 13:50:32.663 [planetinfo.record]: seed = 189 227 160 237 13:50:32.663 [planetinfo.record]: coordinates = 227 120 13:50:32.663 [planetinfo.record]: government = 7; 13:50:32.663 [planetinfo.record]: economy = 0; 13:50:32.663 [planetinfo.record]: techlevel = 14; 13:50:32.663 [planetinfo.record]: population = 64; 13:50:32.663 [planetinfo.record]: productivity = 56320; 13:50:32.663 [planetinfo.record]: name = "Diesanen"; 13:50:32.663 [planetinfo.record]: inhabitant = "Bony Bird"; 13:50:32.663 [planetinfo.record]: inhabitants = "Bony Birds"; 13:50:32.663 [planetinfo.record]: description = "This world is most notable for Diesanenian lethal brandy but beset by evil disease."; 13:50:32.666 [planetinfo.record]: air_color = 0.47142, 0.543111, 0.903393, 1; 13:50:32.666 [planetinfo.record]: cloud_alpha = 1.000000; 13:50:32.666 [planetinfo.record]: cloud_color = 0.116959, 0.405613, 0.712891, 1; 13:50:32.666 [planetinfo.record]: cloud_fraction = 0.510000; 13:50:32.666 [planetinfo.record]: land_color = 0.66, 0.386719, 0.463579, 1; 13:50:32.666 [planetinfo.record]: land_fraction = 0.410000; 13:50:32.666 [planetinfo.record]: polar_cloud_color = 0.391964, 0.599682, 0.820801, 1; 13:50:32.666 [planetinfo.record]: polar_land_color = 0.934, 0.837316, 0.864509, 1; 13:50:32.666 [planetinfo.record]: polar_sea_color = 0.945312, 0.919761, 0.897216, 1; 13:50:32.666 [planetinfo.record]: sea_color = 0.546875, 0.487748, 0.435577, 1; 13:50:32.666 [planetinfo.record]: rotation_speed = 0.001275 13:50:32.666 [planetinfo.record]: planet zpos = 828230.000000 13:50:32.666 [planetinfo.record]: sun_radius = 122071.299744 13:50:32.666 [planetinfo.record]: sun_vector = -0.717 -0.599 -0.357 13:50:32.666 [planetinfo.record]: sun_distance = 1274200 13:50:32.667 [planetinfo.record]: corona_flare = 0.042632 13:50:32.667 [planetinfo.record]: corona_hues = 0.527161 13:50:32.667 [planetinfo.record]: sun_color = 0.664255, 0.751463, 0.760086, 1 13:50:32.667 [planetinfo.record]: corona_shimmer = 0.860414 13:50:32.667 [planetinfo.record]: station_vector = -0.480 -0.515 0.711 13:50:32.667 [planetinfo.record]: station = dodecahedron 13:50:37.996 [PLANETINFO OVER]: Done 13:50:37.997 [PLANETINFO LOGGING]: 5 150 13:50:39.002 [planetinfo.record]: seed = 149 82 138 183 13:50:39.002 [planetinfo.record]: coordinates = 82 27 13:50:39.002 [planetinfo.record]: government = 2; 13:50:39.002 [planetinfo.record]: economy = 3; 13:50:39.002 [planetinfo.record]: techlevel = 7; 13:50:39.002 [planetinfo.record]: population = 34; 13:50:39.002 [planetinfo.record]: productivity = 11424; 13:50:39.002 [planetinfo.record]: name = "Ticea"; 13:50:39.002 [planetinfo.record]: inhabitant = "Harmless Bug-Eyed Bird"; 13:50:39.002 [planetinfo.record]: inhabitants = "Harmless Bug-Eyed Birds"; 13:50:39.002 [planetinfo.record]: description = "Ticea is mildly famous for its pink Ticeaian Indi Indiweed plantations and its exciting sit coms."; 13:50:39.004 [planetinfo.record]: air_color = 0.778914, 0.745675, 0.972984, 1; 13:50:39.004 [planetinfo.record]: cloud_alpha = 1.000000; 13:50:39.004 [planetinfo.record]: cloud_color = 0.870672, 0.828247, 0.921875, 1; 13:50:39.004 [planetinfo.record]: cloud_fraction = 0.450000; 13:50:39.004 [planetinfo.record]: land_color = 0.66, 0.270703, 0.526179, 1; 13:50:39.004 [planetinfo.record]: land_fraction = 0.330000; 13:50:39.004 [planetinfo.record]: polar_cloud_color = 0.883086, 0.856773, 0.914844, 1; 13:50:39.005 [planetinfo.record]: polar_land_color = 0.934, 0.796272, 0.886656, 1; 13:50:39.005 [planetinfo.record]: polar_sea_color = 0.94707, 0.898499, 0.89611, 1; 13:50:39.005 [planetinfo.record]: sea_color = 0.529297, 0.420714, 0.415374, 1; 13:50:39.005 [planetinfo.record]: rotation_speed = 0.002088 13:50:39.005 [planetinfo.record]: planet zpos = 469000.000000 13:50:39.005 [planetinfo.record]: sun_radius = 120888.299561 13:50:39.005 [planetinfo.record]: sun_vector = 0.087 0.855 0.511 13:50:39.005 [planetinfo.record]: sun_distance = 938000 13:50:39.005 [planetinfo.record]: corona_flare = 0.071010 13:50:39.005 [planetinfo.record]: corona_hues = 0.978310 13:50:39.005 [planetinfo.record]: sun_color = 0.828281, 0.64257, 0.441417, 1 13:50:39.005 [planetinfo.record]: corona_shimmer = 0.284823 13:50:39.005 [planetinfo.record]: station_vector = 0.095 -0.631 0.770 13:50:39.005 [planetinfo.record]: station = coriolis 13:50:43.553 [PLANETINFO OVER]: Done 13:50:43.554 [PLANETINFO LOGGING]: 5 151 13:50:44.557 [planetinfo.record]: seed = 57 12 232 96 13:50:44.557 [planetinfo.record]: coordinates = 12 47 13:50:44.557 [planetinfo.record]: government = 7; 13:50:44.557 [planetinfo.record]: economy = 7; 13:50:44.557 [planetinfo.record]: techlevel = 4; 13:50:44.557 [planetinfo.record]: population = 31; 13:50:44.557 [planetinfo.record]: productivity = 8184; 13:50:44.557 [planetinfo.record]: name = "Teesso"; 13:50:44.557 [planetinfo.record]: inhabitant = "Large Blue Bony Lobster"; 13:50:44.557 [planetinfo.record]: inhabitants = "Large Blue Bony Lobsters"; 13:50:44.557 [planetinfo.record]: description = "The world Teesso is scourged by deadly tree ants."; 13:50:44.561 [planetinfo.record]: air_color = 0.576731, 0.945018, 0.895761, 1; 13:50:44.561 [planetinfo.record]: cloud_alpha = 1.000000; 13:50:44.561 [planetinfo.record]: cloud_color = 0.837891, 0.651227, 0.360031, 1; 13:50:44.561 [planetinfo.record]: cloud_fraction = 0.530000; 13:50:44.561 [planetinfo.record]: land_color = 0.66, 0.193359, 0.641772, 1; 13:50:44.561 [planetinfo.record]: land_fraction = 0.370000; 13:50:44.561 [planetinfo.record]: polar_cloud_color = 0.877051, 0.754933, 0.56443, 1; 13:50:44.561 [planetinfo.record]: polar_land_color = 0.934, 0.768908, 0.927551, 1; 13:50:44.561 [planetinfo.record]: polar_sea_color = 0.932031, 0.913956, 0.8756, 1; 13:50:44.561 [planetinfo.record]: sea_color = 0.679688, 0.62696, 0.515076, 1; 13:50:44.562 [planetinfo.record]: rotation_speed = 0.004460 13:50:44.562 [planetinfo.record]: planet zpos = 282800.000000 13:50:44.562 [planetinfo.record]: sun_radius = 78996.375732 13:50:44.562 [planetinfo.record]: sun_vector = -0.948 -0.307 -0.086 13:50:44.562 [planetinfo.record]: sun_distance = 537320 13:50:44.562 [planetinfo.record]: corona_flare = 0.013376 13:50:44.562 [planetinfo.record]: corona_hues = 0.980782 13:50:44.562 [planetinfo.record]: sun_color = 0.697992, 0.665181, 0.634992, 1 13:50:44.562 [planetinfo.record]: corona_shimmer = 0.414182 13:50:44.562 [planetinfo.record]: station_vector = 0.909 -0.405 0.097 13:50:44.562 [planetinfo.record]: station = coriolis 13:50:49.300 [PLANETINFO OVER]: Done 13:50:49.301 [PLANETINFO LOGGING]: 5 152 13:50:50.304 [planetinfo.record]: seed = 25 7 74 173 13:50:50.304 [planetinfo.record]: coordinates = 7 9 13:50:50.304 [planetinfo.record]: government = 3; 13:50:50.304 [planetinfo.record]: economy = 1; 13:50:50.304 [planetinfo.record]: techlevel = 11; 13:50:50.304 [planetinfo.record]: population = 49; 13:50:50.304 [planetinfo.record]: productivity = 24696; 13:50:50.304 [planetinfo.record]: name = "Dirien"; 13:50:50.304 [planetinfo.record]: inhabitant = "Human Colonial"; 13:50:50.304 [planetinfo.record]: inhabitants = "Human Colonials"; 13:50:50.304 [planetinfo.record]: description = "This planet is most fabled for Dirienian Bein brandy but cursed by unpredictable solar activity."; 13:50:50.308 [planetinfo.record]: air_color = 0.671881, 0.863348, 0.902742, 1; 13:50:50.309 [planetinfo.record]: cloud_alpha = 1.000000; 13:50:50.309 [planetinfo.record]: cloud_color = 0.648431, 0.710938, 0.591522, 1; 13:50:50.309 [planetinfo.record]: cloud_fraction = 0.490000; 13:50:50.309 [planetinfo.record]: land_color = 0.595703, 0.409546, 0.549164, 1; 13:50:50.309 [planetinfo.record]: land_fraction = 0.600000; 13:50:50.309 [planetinfo.record]: polar_cloud_color = 0.774867, 0.819922, 0.733846, 1; 13:50:50.309 [planetinfo.record]: polar_land_color = 0.94043, 0.866959, 0.922062, 1; 13:50:50.309 [planetinfo.record]: polar_sea_color = 0.935547, 0.923818, 0.877806, 1; 13:50:50.309 [planetinfo.record]: sea_color = 0.644531, 0.61221, 0.485413, 1; 13:50:50.309 [planetinfo.record]: rotation_speed = 0.004728 13:50:50.309 [planetinfo.record]: planet zpos = 922650.000000 13:50:50.309 [planetinfo.record]: sun_radius = 153194.964905 13:50:50.309 [planetinfo.record]: sun_vector = -0.052 -0.945 0.324 13:50:50.309 [planetinfo.record]: sun_distance = 1414730 13:50:50.309 [planetinfo.record]: corona_flare = 0.027112 13:50:50.309 [planetinfo.record]: corona_hues = 0.994370 13:50:50.310 [planetinfo.record]: sun_color = 0.334426, 0.433262, 0.774677, 1 13:50:50.310 [planetinfo.record]: corona_shimmer = 0.381920 13:50:50.310 [planetinfo.record]: station_vector = 0.797 -0.522 0.304 13:50:50.310 [planetinfo.record]: station = dodecahedron 13:50:55.039 [PLANETINFO OVER]: Done 13:50:55.040 [PLANETINFO LOGGING]: 5 153 13:50:56.043 [planetinfo.record]: seed = 197 221 64 14 13:50:56.043 [planetinfo.record]: coordinates = 221 114 13:50:56.043 [planetinfo.record]: government = 0; 13:50:56.043 [planetinfo.record]: economy = 2; 13:50:56.043 [planetinfo.record]: techlevel = 6; 13:50:56.043 [planetinfo.record]: population = 27; 13:50:56.043 [planetinfo.record]: productivity = 6912; 13:50:56.043 [planetinfo.record]: name = "Reriarti"; 13:50:56.043 [planetinfo.record]: inhabitant = "Human Colonial"; 13:50:56.043 [planetinfo.record]: inhabitants = "Human Colonials"; 13:50:56.043 [planetinfo.record]: description = "This world is reasonably well known for its vast oceans but ravaged by killer disease."; 13:50:56.060 [planetinfo.record]: air_color = 0.635903, 0.878468, 0.939814, 1; 13:50:56.060 [planetinfo.record]: cloud_alpha = 1.000000; 13:50:56.060 [planetinfo.record]: cloud_color = 0.633938, 0.822266, 0.517128, 1; 13:50:56.060 [planetinfo.record]: cloud_fraction = 0.440000; 13:50:56.060 [planetinfo.record]: land_color = 0.66, 0.525293, 0.20625, 1; 13:50:56.067 [planetinfo.record]: land_fraction = 0.200000; 13:50:56.067 [planetinfo.record]: polar_cloud_color = 0.745479, 0.87002, 0.668233, 1; 13:50:56.068 [planetinfo.record]: polar_land_color = 0.934, 0.886342, 0.773469, 1; 13:50:56.068 [planetinfo.record]: polar_sea_color = 0.939844, 0.901241, 0.882389, 1; 13:50:56.068 [planetinfo.record]: sea_color = 0.601562, 0.502729, 0.454462, 1; 13:50:56.068 [planetinfo.record]: rotation_speed = 0.000235 13:50:56.068 [planetinfo.record]: planet zpos = 595890.000000 13:50:56.068 [planetinfo.record]: sun_radius = 119320.652161 13:50:56.068 [planetinfo.record]: sun_vector = 0.085 0.182 -0.980 13:50:56.068 [planetinfo.record]: sun_distance = 1324200 13:50:56.068 [planetinfo.record]: corona_flare = 0.026994 13:50:56.068 [planetinfo.record]: corona_hues = 0.852432 13:50:56.068 [planetinfo.record]: sun_color = 0.256921, 0.433462, 0.843185, 1 13:50:56.068 [planetinfo.record]: corona_shimmer = 0.393912 13:50:56.068 [planetinfo.record]: station_vector = -0.138 0.779 0.611 13:50:56.068 [planetinfo.record]: station = coriolis 13:51:01.653 [PLANETINFO OVER]: Done 13:51:01.654 [PLANETINFO LOGGING]: 5 154 13:51:02.674 [planetinfo.record]: seed = 45 183 26 96 13:51:02.674 [planetinfo.record]: coordinates = 183 74 13:51:02.674 [planetinfo.record]: government = 5; 13:51:02.674 [planetinfo.record]: economy = 2; 13:51:02.674 [planetinfo.record]: techlevel = 11; 13:51:02.674 [planetinfo.record]: population = 52; 13:51:02.674 [planetinfo.record]: productivity = 29952; 13:51:02.674 [planetinfo.record]: name = "Leorqu"; 13:51:02.674 [planetinfo.record]: inhabitant = "Human Colonial"; 13:51:02.674 [planetinfo.record]: inhabitants = "Human Colonials"; 13:51:02.674 [planetinfo.record]: description = "Leorqu is a revolting little planet."; 13:51:02.697 [planetinfo.record]: air_color = 0.5324, 0.508729, 0.900791, 1; 13:51:02.697 [planetinfo.record]: cloud_alpha = 1.000000; 13:51:02.697 [planetinfo.record]: cloud_color = 0.303932, 0.206566, 0.705078, 1; 13:51:02.697 [planetinfo.record]: cloud_fraction = 0.320000; 13:51:02.697 [planetinfo.record]: land_color = 0.531094, 0.66, 0.578427, 1; 13:51:02.697 [planetinfo.record]: land_fraction = 0.470000; 13:51:02.698 [planetinfo.record]: polar_cloud_color = 0.526669, 0.456131, 0.817285, 1; 13:51:02.698 [planetinfo.record]: polar_land_color = 0.888395, 0.934, 0.90514, 1; 13:51:02.698 [planetinfo.record]: polar_sea_color = 0.880132, 0.927734, 0.871835, 1; 13:51:02.698 [planetinfo.record]: sea_color = 0.574338, 0.722656, 0.548485, 1; 13:51:02.698 [planetinfo.record]: rotation_speed = 0.004876 13:51:02.698 [planetinfo.record]: planet zpos = 359880.000000 13:51:02.698 [planetinfo.record]: sun_radius = 67017.600861 13:51:02.698 [planetinfo.record]: sun_vector = 0.887 0.095 0.451 13:51:02.698 [planetinfo.record]: sun_distance = 509830 13:51:02.698 [planetinfo.record]: corona_flare = 0.051675 13:51:02.698 [planetinfo.record]: corona_hues = 0.804405 13:51:02.698 [planetinfo.record]: sun_color = 0.807341, 0.808375, 0.81434, 1 13:51:02.698 [planetinfo.record]: corona_shimmer = 0.306143 13:51:02.698 [planetinfo.record]: station_vector = -0.408 -0.135 0.903 13:51:02.699 [planetinfo.record]: station = icosahedron 13:51:07.222 [PLANETINFO OVER]: Done 13:51:07.223 [PLANETINFO LOGGING]: 5 155 13:51:08.225 [planetinfo.record]: seed = 225 58 168 21 13:51:08.225 [planetinfo.record]: coordinates = 58 121 13:51:08.225 [planetinfo.record]: government = 4; 13:51:08.225 [planetinfo.record]: economy = 1; 13:51:08.225 [planetinfo.record]: techlevel = 10; 13:51:08.225 [planetinfo.record]: population = 46; 13:51:08.225 [planetinfo.record]: productivity = 26496; 13:51:08.225 [planetinfo.record]: name = "Laesqu"; 13:51:08.225 [planetinfo.record]: inhabitant = "Green Bony Bird"; 13:51:08.225 [planetinfo.record]: inhabitants = "Green Bony Birds"; 13:51:08.225 [planetinfo.record]: description = "The planet Laesqu is scourged by deadly disease."; 13:51:08.238 [planetinfo.record]: air_color = 0.472745, 0.711659, 0.844857, 1; 13:51:08.238 [planetinfo.record]: cloud_alpha = 1.000000; 13:51:08.239 [planetinfo.record]: cloud_color = 0.148964, 0.537109, 0.18232, 1; 13:51:08.239 [planetinfo.record]: cloud_fraction = 0.300000; 13:51:08.239 [planetinfo.record]: land_color = 0.0515625, 0.66, 0.66, 1; 13:51:08.239 [planetinfo.record]: land_fraction = 0.560000; 13:51:08.239 [planetinfo.record]: polar_cloud_color = 0.406703, 0.741699, 0.435492, 1; 13:51:08.239 [planetinfo.record]: polar_land_color = 0.718742, 0.934, 0.934, 1; 13:51:08.239 [planetinfo.record]: polar_sea_color = 0.872982, 0.927344, 0.872554, 1; 13:51:08.239 [planetinfo.record]: sea_color = 0.556197, 0.726562, 0.554855, 1; 13:51:08.239 [planetinfo.record]: rotation_speed = 0.003494 13:51:08.239 [planetinfo.record]: planet zpos = 415400.000000 13:51:08.239 [planetinfo.record]: sun_radius = 79521.565552 13:51:08.239 [planetinfo.record]: sun_vector = 0.731 0.584 -0.354 13:51:08.239 [planetinfo.record]: sun_distance = 830800 13:51:08.239 [planetinfo.record]: corona_flare = 0.098943 13:51:08.239 [planetinfo.record]: corona_hues = 0.820488 13:51:08.239 [planetinfo.record]: sun_color = 0.700436, 0.668545, 0.212559, 1 13:51:08.240 [planetinfo.record]: corona_shimmer = 0.803571 13:51:08.240 [planetinfo.record]: station_vector = 0.087 0.608 0.789 13:51:08.240 [planetinfo.record]: station = coriolis 13:51:12.899 [PLANETINFO OVER]: Done 13:51:12.899 [PLANETINFO LOGGING]: 5 156 13:51:13.903 [planetinfo.record]: seed = 81 249 250 220 13:51:13.903 [planetinfo.record]: coordinates = 249 26 13:51:13.903 [planetinfo.record]: government = 2; 13:51:13.903 [planetinfo.record]: economy = 2; 13:51:13.903 [planetinfo.record]: techlevel = 7; 13:51:13.903 [planetinfo.record]: population = 33; 13:51:13.903 [planetinfo.record]: productivity = 12672; 13:51:13.903 [planetinfo.record]: name = "Teerbi"; 13:51:13.903 [planetinfo.record]: inhabitant = "Bony Lobster"; 13:51:13.903 [planetinfo.record]: inhabitants = "Bony Lobsters"; 13:51:13.903 [planetinfo.record]: description = "This world is a tedious place."; 13:51:13.909 [planetinfo.record]: air_color = 0.603486, 0.582067, 0.837703, 1; 13:51:13.909 [planetinfo.record]: cloud_alpha = 1.000000; 13:51:13.909 [planetinfo.record]: cloud_color = 0.395814, 0.352478, 0.515625, 1; 13:51:13.909 [planetinfo.record]: cloud_fraction = 0.360000; 13:51:13.909 [planetinfo.record]: land_color = 0.66, 0.348047, 0.508898, 1; 13:51:13.909 [planetinfo.record]: land_fraction = 0.460000; 13:51:13.909 [planetinfo.record]: polar_cloud_color = 0.625722, 0.587269, 0.732031, 1; 13:51:13.909 [planetinfo.record]: polar_land_color = 0.934, 0.823635, 0.880542, 1; 13:51:13.909 [planetinfo.record]: polar_sea_color = 0.923437, 0.819146, 0.805302, 1; 13:51:13.910 [planetinfo.record]: sea_color = 0.765625, 0.419753, 0.37384, 1; 13:51:13.910 [planetinfo.record]: rotation_speed = 0.002599 13:51:13.910 [planetinfo.record]: planet zpos = 736440.000000 13:51:13.910 [planetinfo.record]: sun_radius = 143326.518555 13:51:13.910 [planetinfo.record]: sun_vector = -0.109 -0.604 -0.789 13:51:13.910 [planetinfo.record]: sun_distance = 1411510 13:51:13.910 [planetinfo.record]: corona_flare = 0.024164 13:51:13.910 [planetinfo.record]: corona_hues = 0.875931 13:51:13.910 [planetinfo.record]: sun_color = 0.789014, 0.720563, 0.215358, 1 13:51:13.910 [planetinfo.record]: corona_shimmer = 0.331395 13:51:13.910 [planetinfo.record]: station_vector = -0.778 -0.553 0.297 13:51:13.910 [planetinfo.record]: station = coriolis 13:51:18.501 [PLANETINFO OVER]: Done 13:51:18.502 [PLANETINFO LOGGING]: 5 157 13:51:19.523 [planetinfo.record]: seed = 13 148 32 75 13:51:19.523 [planetinfo.record]: coordinates = 148 198 13:51:19.523 [planetinfo.record]: government = 1; 13:51:19.523 [planetinfo.record]: economy = 6; 13:51:19.523 [planetinfo.record]: techlevel = 2; 13:51:19.523 [planetinfo.record]: population = 16; 13:51:19.523 [planetinfo.record]: productivity = 2560; 13:51:19.523 [planetinfo.record]: name = "Macece"; 13:51:19.523 [planetinfo.record]: inhabitant = "Human Colonial"; 13:51:19.523 [planetinfo.record]: inhabitants = "Human Colonials"; 13:51:19.523 [planetinfo.record]: description = "This world is plagued by frequent earthquakes."; 13:51:19.544 [planetinfo.record]: air_color = 0.390332, 0.83315, 0.687524, 1; 13:51:19.544 [planetinfo.record]: cloud_alpha = 1.000000; 13:51:19.544 [planetinfo.record]: cloud_color = 0.501953, 0.0136793, 0.00196075, 1; 13:51:19.544 [planetinfo.record]: cloud_fraction = 0.250000; 13:51:19.544 [planetinfo.record]: land_color = 0.600703, 0.611358, 0.66, 1; 13:51:19.544 [planetinfo.record]: land_fraction = 0.550000; 13:51:19.544 [planetinfo.record]: polar_cloud_color = 0.725879, 0.284568, 0.273977, 1; 13:51:19.544 [planetinfo.record]: polar_land_color = 0.913022, 0.916791, 0.934, 1; 13:51:19.544 [planetinfo.record]: polar_sea_color = 0.924805, 0.883317, 0.829976, 1; 13:51:19.544 [planetinfo.record]: sea_color = 0.751953, 0.61702, 0.443535, 1; 13:51:19.544 [planetinfo.record]: rotation_speed = 0.004314 13:51:19.545 [planetinfo.record]: planet zpos = 635800.000000 13:51:19.545 [planetinfo.record]: sun_radius = 131268.865967 13:51:19.545 [planetinfo.record]: sun_vector = -0.233 -0.876 0.422 13:51:19.545 [planetinfo.record]: sun_distance = 1156000 13:51:19.545 [planetinfo.record]: corona_flare = 0.010408 13:51:19.545 [planetinfo.record]: corona_hues = 0.903229 13:51:19.545 [planetinfo.record]: sun_color = 0.431186, 0.561919, 0.650912, 1 13:51:19.545 [planetinfo.record]: corona_shimmer = 0.388513 13:51:19.545 [planetinfo.record]: station_vector = -0.152 -0.484 0.862 13:51:19.545 [planetinfo.record]: station = coriolis 13:51:24.799 [PLANETINFO OVER]: Done 13:51:24.800 [PLANETINFO LOGGING]: 5 158 13:51:25.808 [planetinfo.record]: seed = 5 118 234 160 13:51:25.808 [planetinfo.record]: coordinates = 118 133 13:51:25.808 [planetinfo.record]: government = 0; 13:51:25.808 [planetinfo.record]: economy = 7; 13:51:25.808 [planetinfo.record]: techlevel = 2; 13:51:25.808 [planetinfo.record]: population = 16; 13:51:25.808 [planetinfo.record]: productivity = 1536; 13:51:25.808 [planetinfo.record]: name = "Anen"; 13:51:25.808 [planetinfo.record]: inhabitant = "Large Harmless Bony Lobster"; 13:51:25.809 [planetinfo.record]: inhabitants = "Large Harmless Bony Lobsters"; 13:51:25.809 [planetinfo.record]: description = "Anen is cursed by deadly civil war."; 13:51:25.836 [planetinfo.record]: air_color = 0.580904, 0.937863, 0.878978, 1; 13:51:25.836 [planetinfo.record]: cloud_alpha = 1.000000; 13:51:25.836 [planetinfo.record]: cloud_color = 0.816406, 0.601691, 0.373123, 1; 13:51:25.836 [planetinfo.record]: cloud_fraction = 0.250000; 13:51:25.836 [planetinfo.record]: land_color = 0.292053, 0.322061, 0.566406, 1; 13:51:25.836 [planetinfo.record]: land_fraction = 0.460000; 13:51:25.836 [planetinfo.record]: polar_cloud_color = 0.867383, 0.724806, 0.573032, 1; 13:51:25.836 [planetinfo.record]: polar_land_color = 0.829124, 0.841619, 0.943359, 1; 13:51:25.836 [planetinfo.record]: polar_sea_color = 0.917773, 0.893924, 0.800363, 1; 13:51:25.836 [planetinfo.record]: sea_color = 0.822266, 0.736797, 0.401497, 1; 13:51:25.836 [planetinfo.record]: rotation_speed = 0.002708 13:51:25.836 [planetinfo.record]: planet zpos = 352080.000000 13:51:25.836 [planetinfo.record]: sun_radius = 77260.597229 13:51:25.836 [planetinfo.record]: sun_vector = -0.826 0.436 -0.358 13:51:25.836 [planetinfo.record]: sun_distance = 616140 13:51:25.836 [planetinfo.record]: corona_flare = 0.059427 13:51:25.837 [planetinfo.record]: corona_hues = 0.965080 13:51:25.837 [planetinfo.record]: sun_color = 0.757306, 0.718596, 0.627609, 1 13:51:25.837 [planetinfo.record]: corona_shimmer = 0.442811 13:51:25.837 [planetinfo.record]: station_vector = -0.316 -0.946 0.076 13:51:25.837 [planetinfo.record]: station = coriolis 13:51:30.255 [PLANETINFO OVER]: Done 13:51:30.256 [PLANETINFO LOGGING]: 5 159 13:51:31.274 [planetinfo.record]: seed = 201 239 168 62 13:51:31.275 [planetinfo.record]: coordinates = 239 178 13:51:31.275 [planetinfo.record]: government = 1; 13:51:31.275 [planetinfo.record]: economy = 2; 13:51:31.275 [planetinfo.record]: techlevel = 9; 13:51:31.275 [planetinfo.record]: population = 40; 13:51:31.275 [planetinfo.record]: productivity = 12800; 13:51:31.275 [planetinfo.record]: name = "Rileaa"; 13:51:31.275 [planetinfo.record]: inhabitant = "Red Furry Insect"; 13:51:31.275 [planetinfo.record]: inhabitants = "Red Furry Insects"; 13:51:31.275 [planetinfo.record]: description = "Rileaa is mildly well known for vicious Thal gargle blasters."; 13:51:31.280 [planetinfo.record]: air_color = 0.678064, 0.864135, 0.892336, 1; 13:51:31.280 [planetinfo.record]: cloud_alpha = 1.000000; 13:51:31.280 [planetinfo.record]: cloud_color = 0.646251, 0.679688, 0.597382, 1; 13:51:31.280 [planetinfo.record]: cloud_fraction = 0.290000; 13:51:31.280 [planetinfo.record]: land_color = 0.182022, 0.589844, 0.55161, 1; 13:51:31.280 [planetinfo.record]: land_fraction = 0.700000; 13:51:31.281 [planetinfo.record]: polar_cloud_color = 0.781082, 0.805859, 0.744869, 1; 13:51:31.281 [planetinfo.record]: polar_land_color = 0.77836, 0.941016, 0.925767, 1; 13:51:31.281 [planetinfo.record]: polar_sea_color = 0.868966, 0.923242, 0.896528, 1; 13:51:31.281 [planetinfo.record]: sea_color = 0.587077, 0.767578, 0.678738, 1; 13:51:31.281 [planetinfo.record]: rotation_speed = 0.002603 13:51:31.281 [planetinfo.record]: planet zpos = 863070.000000 13:51:31.281 [planetinfo.record]: sun_radius = 107209.071350 13:51:31.281 [planetinfo.record]: sun_vector = 0.685 -0.544 -0.485 13:51:31.281 [planetinfo.record]: sun_distance = 1195020 13:51:31.281 [planetinfo.record]: corona_flare = 0.090175 13:51:31.281 [planetinfo.record]: corona_hues = 0.790627 13:51:31.281 [planetinfo.record]: sun_color = 0.841548, 0.845778, 0.846362, 1 13:51:31.281 [planetinfo.record]: corona_shimmer = 0.418509 13:51:31.281 [planetinfo.record]: station_vector = 0.652 -0.655 0.382 13:51:31.281 [planetinfo.record]: station = coriolis 13:51:35.697 [PLANETINFO OVER]: Done 13:51:35.701 [PLANETINFO LOGGING]: 5 160 13:51:36.713 [planetinfo.record]: seed = 201 47 234 32 13:51:36.713 [planetinfo.record]: coordinates = 47 15 13:51:36.713 [planetinfo.record]: government = 1; 13:51:36.713 [planetinfo.record]: economy = 7; 13:51:36.713 [planetinfo.record]: techlevel = 4; 13:51:36.713 [planetinfo.record]: population = 25; 13:51:36.713 [planetinfo.record]: productivity = 3000; 13:51:36.713 [planetinfo.record]: name = "Aten"; 13:51:36.713 [planetinfo.record]: inhabitant = "Large Red Slimy Rodent"; 13:51:36.713 [planetinfo.record]: inhabitants = "Large Red Slimy Rodents"; 13:51:36.713 [planetinfo.record]: description = "The planet Aten is scourged by killer edible talking treeoids."; 13:51:36.740 [planetinfo.record]: air_color = 0.475478, 0.449497, 0.943717, 1; 13:51:36.740 [planetinfo.record]: cloud_alpha = 1.000000; 13:51:36.740 [planetinfo.record]: cloud_color = 0.162226, 0.0228043, 0.833984, 1; 13:51:36.740 [planetinfo.record]: cloud_fraction = 0.360000; 13:51:36.740 [planetinfo.record]: land_color = 0.143806, 0.603516, 0.441899, 1; 13:51:36.740 [planetinfo.record]: land_fraction = 0.560000; 13:51:36.740 [planetinfo.record]: polar_cloud_color = 0.434648, 0.343193, 0.875293, 1; 13:51:36.740 [planetinfo.record]: polar_land_color = 0.760711, 0.939648, 0.876741, 1; 13:51:36.740 [planetinfo.record]: polar_sea_color = 0.869826, 0.921289, 0.920083, 1; 13:51:36.740 [planetinfo.record]: sea_color = 0.61124, 0.787109, 0.782987, 1; 13:51:36.740 [planetinfo.record]: rotation_speed = 0.000295 13:51:36.740 [planetinfo.record]: planet zpos = 429450.000000 13:51:36.740 [planetinfo.record]: sun_radius = 68164.440765 13:51:36.740 [planetinfo.record]: sun_vector = -0.502 0.637 0.585 13:51:36.740 [planetinfo.record]: sun_distance = 515340 13:51:36.740 [planetinfo.record]: corona_flare = 0.074409 13:51:36.740 [planetinfo.record]: corona_hues = 0.511543 13:51:36.740 [planetinfo.record]: sun_color = 0.769672, 0.761877, 0.695533, 1 13:51:36.836 [planetinfo.record]: corona_shimmer = 0.396549 13:51:36.836 [planetinfo.record]: station_vector = 0.978 -0.163 0.129 13:51:36.837 [planetinfo.record]: station = coriolis 13:51:41.911 [PLANETINFO OVER]: Done 13:51:41.912 [PLANETINFO LOGGING]: 5 161 13:51:42.915 [planetinfo.record]: seed = 149 50 64 68 13:51:42.915 [planetinfo.record]: coordinates = 50 177 13:51:42.915 [planetinfo.record]: government = 2; 13:51:42.915 [planetinfo.record]: economy = 1; 13:51:42.915 [planetinfo.record]: techlevel = 9; 13:51:42.915 [planetinfo.record]: population = 40; 13:51:42.915 [planetinfo.record]: productivity = 17280; 13:51:42.915 [planetinfo.record]: name = "Zausri"; 13:51:42.915 [planetinfo.record]: inhabitant = "Human Colonial"; 13:51:42.915 [planetinfo.record]: inhabitants = "Human Colonials"; 13:51:42.915 [planetinfo.record]: description = "The world Zausri is very famous for its unusual casinos but plagued by frequent solar activity."; 13:51:42.933 [planetinfo.record]: air_color = 0.769402, 0.518992, 0.954123, 1; 13:51:42.933 [planetinfo.record]: cloud_alpha = 1.000000; 13:51:42.933 [planetinfo.record]: cloud_color = 0.865234, 0.199409, 0.371067, 1; 13:51:42.933 [planetinfo.record]: cloud_fraction = 0.380000; 13:51:42.933 [planetinfo.record]: land_color = 0.401299, 0.59375, 0.020874, 1; 13:51:42.933 [planetinfo.record]: land_fraction = 0.460000; 13:51:42.933 [planetinfo.record]: polar_cloud_color = 0.889355, 0.461614, 0.571891, 1; 13:51:42.933 [planetinfo.record]: polar_land_color = 0.864404, 0.940625, 0.713736, 1; 13:51:42.933 [planetinfo.record]: polar_sea_color = 0.931641, 0.871338, 0.855217, 1; 13:51:42.933 [planetinfo.record]: sea_color = 0.683594, 0.506604, 0.45929, 1; 13:51:42.934 [planetinfo.record]: rotation_speed = 0.003358 13:51:42.934 [planetinfo.record]: planet zpos = 466800.000000 13:51:42.934 [planetinfo.record]: sun_radius = 99468.753052 13:51:42.934 [planetinfo.record]: sun_vector = 0.932 -0.332 -0.144 13:51:42.934 [planetinfo.record]: sun_distance = 739100 13:51:42.934 [planetinfo.record]: corona_flare = 0.000757 13:51:42.934 [planetinfo.record]: corona_hues = 0.589439 13:51:42.934 [planetinfo.record]: sun_color = 0.766562, 0.630554, 0.600347, 1 13:51:42.934 [planetinfo.record]: corona_shimmer = 0.367503 13:51:42.934 [planetinfo.record]: station_vector = -0.886 -0.351 0.303 13:51:42.934 [planetinfo.record]: station = coriolis 13:51:47.202 [PLANETINFO OVER]: Done 13:51:47.203 [PLANETINFO LOGGING]: 5 162 13:51:48.206 [planetinfo.record]: seed = 29 11 250 209 13:51:48.206 [planetinfo.record]: coordinates = 11 158 13:51:48.206 [planetinfo.record]: government = 3; 13:51:48.206 [planetinfo.record]: economy = 6; 13:51:48.206 [planetinfo.record]: techlevel = 6; 13:51:48.206 [planetinfo.record]: population = 34; 13:51:48.206 [planetinfo.record]: productivity = 7616; 13:51:48.206 [planetinfo.record]: name = "Atanorbi"; 13:51:48.206 [planetinfo.record]: inhabitant = "Furry Feline"; 13:51:48.206 [planetinfo.record]: inhabitants = "Furry Felines"; 13:51:48.206 [planetinfo.record]: description = "This world is a tedious little planet."; 13:51:48.207 [planetinfo.record]: air_color = 0.431915, 0.721823, 0.878027, 1; 13:51:48.207 [planetinfo.record]: cloud_alpha = 1.000000; 13:51:48.207 [planetinfo.record]: cloud_color = 0.0472565, 0.636719, 0.0840979, 1; 13:51:48.207 [planetinfo.record]: cloud_fraction = 0.550000; 13:51:48.207 [planetinfo.record]: land_color = 0.226715, 0.337223, 0.630859, 1; 13:51:48.208 [planetinfo.record]: land_fraction = 0.530000; 13:51:48.208 [planetinfo.record]: polar_cloud_color = 0.331431, 0.786523, 0.359874, 1; 13:51:48.208 [planetinfo.record]: polar_land_color = 0.786861, 0.827891, 0.936914, 1; 13:51:48.208 [planetinfo.record]: polar_sea_color = 0.936523, 0.90294, 0.875979, 1; 13:51:48.208 [planetinfo.record]: sea_color = 0.634766, 0.543716, 0.470619, 1; 13:51:48.208 [planetinfo.record]: rotation_speed = 0.000527 13:51:48.208 [planetinfo.record]: planet zpos = 308300.000000 13:51:48.208 [planetinfo.record]: sun_radius = 86957.290802 13:51:48.208 [planetinfo.record]: sun_vector = -0.672 0.678 0.297 13:51:48.208 [planetinfo.record]: sun_distance = 554940 13:51:48.208 [planetinfo.record]: corona_flare = 0.045598 13:51:48.208 [planetinfo.record]: corona_hues = 0.871933 13:51:48.208 [planetinfo.record]: sun_color = 0.60787, 0.668942, 0.782825, 1 13:51:48.208 [planetinfo.record]: corona_shimmer = 0.374111 13:51:48.208 [planetinfo.record]: station_vector = 0.978 -0.097 0.185 13:51:48.208 [planetinfo.record]: station = coriolis 13:51:54.140 [PLANETINFO OVER]: Done 13:51:54.140 [PLANETINFO LOGGING]: 5 163 13:51:55.143 [planetinfo.record]: seed = 241 166 232 123 13:51:55.143 [planetinfo.record]: coordinates = 166 89 13:51:55.143 [planetinfo.record]: government = 6; 13:51:55.143 [planetinfo.record]: economy = 1; 13:51:55.143 [planetinfo.record]: techlevel = 11; 13:51:55.143 [planetinfo.record]: population = 52; 13:51:55.143 [planetinfo.record]: productivity = 37440; 13:51:55.144 [planetinfo.record]: name = "Ananri"; 13:51:55.144 [planetinfo.record]: inhabitant = "Blue Lizard"; 13:51:55.144 [planetinfo.record]: inhabitants = "Blue Lizards"; 13:51:55.144 [planetinfo.record]: description = "Ananri is famous for Ananriian wolf cutlet but plagued by deadly earthquakes."; 13:51:55.155 [planetinfo.record]: air_color = 0.593202, 0.913878, 0.96583, 1; 13:51:55.155 [planetinfo.record]: cloud_alpha = 1.000000; 13:51:55.155 [planetinfo.record]: cloud_color = 0.684278, 0.900391, 0.397438, 1; 13:51:55.155 [planetinfo.record]: cloud_fraction = 0.350000; 13:51:55.155 [planetinfo.record]: land_color = 0.0670312, 0.66, 0.465432, 1; 13:51:55.155 [planetinfo.record]: land_fraction = 0.580000; 13:51:55.155 [planetinfo.record]: polar_cloud_color = 0.769388, 0.905176, 0.58916, 1; 13:51:55.155 [planetinfo.record]: polar_land_color = 0.724215, 0.934, 0.865164, 1; 13:51:55.155 [planetinfo.record]: polar_sea_color = 0.932031, 0.864763, 0.853755, 1; 13:51:55.155 [planetinfo.record]: sea_color = 0.679688, 0.483464, 0.451355, 1; 13:51:55.155 [planetinfo.record]: rotation_speed = 0.001683 13:51:55.155 [planetinfo.record]: planet zpos = 695760.000000 13:51:55.155 [planetinfo.record]: sun_radius = 186079.031677 13:51:55.155 [planetinfo.record]: sun_vector = -0.011 -0.994 -0.113 13:51:55.155 [planetinfo.record]: sun_distance = 1043640 13:51:55.155 [planetinfo.record]: corona_flare = 0.092377 13:51:55.155 [planetinfo.record]: corona_hues = 0.964005 13:51:55.155 [planetinfo.record]: sun_color = 0.272097, 0.422816, 0.684024, 1 13:51:55.156 [planetinfo.record]: corona_shimmer = 0.619515 13:51:55.156 [planetinfo.record]: station_vector = 0.242 0.276 0.930 13:51:55.156 [planetinfo.record]: station = dodecahedron 13:51:59.563 [PLANETINFO OVER]: Done 13:51:59.564 [PLANETINFO LOGGING]: 5 164 13:52:00.567 [planetinfo.record]: seed = 129 150 26 177 13:52:00.567 [planetinfo.record]: coordinates = 150 158 13:52:00.567 [planetinfo.record]: government = 0; 13:52:00.567 [planetinfo.record]: economy = 6; 13:52:00.567 [planetinfo.record]: techlevel = 3; 13:52:00.567 [planetinfo.record]: population = 19; 13:52:00.567 [planetinfo.record]: productivity = 2432; 13:52:00.567 [planetinfo.record]: name = "Atbidi"; 13:52:00.567 [planetinfo.record]: inhabitant = "Human Colonial"; 13:52:00.567 [planetinfo.record]: inhabitants = "Human Colonials"; 13:52:00.567 [planetinfo.record]: description = "The planet Atbidi is an unremarkable planet."; 13:52:00.578 [planetinfo.record]: air_color = 0.867498, 0.782036, 0.98209, 1; 13:52:00.578 [planetinfo.record]: cloud_alpha = 1.000000; 13:52:00.578 [planetinfo.record]: cloud_color = 0.949219, 0.938095, 0.945916, 1; 13:52:00.578 [planetinfo.record]: cloud_fraction = 0.580000; 13:52:00.578 [planetinfo.record]: land_color = 0.515625, 0.50867, 0.0704956, 1; 13:52:00.578 [planetinfo.record]: land_fraction = 0.470000; 13:52:00.579 [planetinfo.record]: polar_cloud_color = 0.927148, 0.920358, 0.925133, 1; 13:52:00.579 [planetinfo.record]: polar_land_color = 0.948438, 0.945239, 0.743745, 1; 13:52:00.579 [planetinfo.record]: polar_sea_color = 0.874541, 0.861305, 0.911133, 1; 13:52:00.579 [planetinfo.record]: sea_color = 0.745912, 0.694275, 0.888672, 1; 13:52:00.579 [planetinfo.record]: rotation_speed = 0.003189 13:52:00.579 [planetinfo.record]: planet zpos = 289980.000000 13:52:00.580 [planetinfo.record]: sun_radius = 88375.404968 13:52:00.580 [planetinfo.record]: sun_vector = 0.273 0.920 -0.282 13:52:00.580 [planetinfo.record]: sun_distance = 741060 13:52:00.580 [planetinfo.record]: corona_flare = 0.092719 13:52:00.580 [planetinfo.record]: corona_hues = 0.678673 13:52:00.580 [planetinfo.record]: sun_color = 0.666522, 0.63124, 0.605721, 1 13:52:00.580 [planetinfo.record]: corona_shimmer = 0.289140 13:52:00.581 [planetinfo.record]: station_vector = -1.000 -0.014 0.016 13:52:00.581 [planetinfo.record]: station = coriolis 13:52:06.066 [PLANETINFO OVER]: Done 13:52:06.066 [PLANETINFO LOGGING]: 5 165 13:52:07.069 [planetinfo.record]: seed = 93 197 160 217 13:52:07.069 [planetinfo.record]: coordinates = 197 45 13:52:07.069 [planetinfo.record]: government = 3; 13:52:07.069 [planetinfo.record]: economy = 5; 13:52:07.069 [planetinfo.record]: techlevel = 5; 13:52:07.069 [planetinfo.record]: population = 29; 13:52:07.069 [planetinfo.record]: productivity = 8120; 13:52:07.069 [planetinfo.record]: name = "Orinmaen"; 13:52:07.069 [planetinfo.record]: inhabitant = "Slimy Frog"; 13:52:07.069 [planetinfo.record]: inhabitants = "Slimy Frogs"; 13:52:07.070 [planetinfo.record]: description = "This planet is very well known for its inhabitants’ weird shyness and its inhabitants’ eccentric shyness."; 13:52:07.091 [planetinfo.record]: air_color = 0.725971, 0.765862, 0.945668, 1; 13:52:07.091 [planetinfo.record]: cloud_alpha = 1.000000; 13:52:07.091 [planetinfo.record]: cloud_color = 0.757828, 0.801399, 0.839844, 1; 13:52:07.091 [planetinfo.record]: cloud_fraction = 0.280000; 13:52:07.091 [planetinfo.record]: land_color = 0.619233, 0.66, 0.546562, 1; 13:52:07.091 [planetinfo.record]: land_fraction = 0.390000; 13:52:07.091 [planetinfo.record]: polar_cloud_color = 0.824345, 0.852812, 0.87793, 1; 13:52:07.091 [planetinfo.record]: polar_land_color = 0.919577, 0.934, 0.893867, 1; 13:52:07.091 [planetinfo.record]: polar_sea_color = 0.868351, 0.911298, 0.919727, 1; 13:52:07.091 [planetinfo.record]: sea_color = 0.623373, 0.773308, 0.802734, 1; 13:52:07.091 [planetinfo.record]: rotation_speed = 0.002512 13:52:07.091 [planetinfo.record]: planet zpos = 584870.000000 13:52:07.092 [planetinfo.record]: sun_radius = 123547.069855 13:52:07.092 [planetinfo.record]: sun_vector = 0.011 0.346 -0.938 13:52:07.092 [planetinfo.record]: sun_distance = 1063400 13:52:07.092 [planetinfo.record]: corona_flare = 0.094092 13:52:07.092 [planetinfo.record]: corona_hues = 0.996056 13:52:07.092 [planetinfo.record]: sun_color = 0.678604, 0.677611, 0.677121, 1 13:52:07.092 [planetinfo.record]: corona_shimmer = 0.362602 13:52:07.093 [planetinfo.record]: station_vector = -0.298 0.873 0.386 13:52:07.093 [planetinfo.record]: station = coriolis 13:52:11.604 [PLANETINFO OVER]: Done 13:52:11.605 [PLANETINFO LOGGING]: 5 166 13:52:12.609 [planetinfo.record]: seed = 117 18 74 75 13:52:12.609 [planetinfo.record]: coordinates = 18 107 13:52:12.609 [planetinfo.record]: government = 6; 13:52:12.609 [planetinfo.record]: economy = 3; 13:52:12.609 [planetinfo.record]: techlevel = 9; 13:52:12.609 [planetinfo.record]: population = 46; 13:52:12.609 [planetinfo.record]: productivity = 25760; 13:52:12.609 [planetinfo.record]: name = "Maessote"; 13:52:12.609 [planetinfo.record]: inhabitant = "Human Colonial"; 13:52:12.610 [planetinfo.record]: inhabitants = "Human Colonials"; 13:52:12.610 [planetinfo.record]: description = "The planet Maessote is scourged by killer mountain goats."; 13:52:12.613 [planetinfo.record]: air_color = 0.750432, 0.535023, 0.936563, 1; 13:52:12.613 [planetinfo.record]: cloud_alpha = 1.000000; 13:52:12.613 [planetinfo.record]: cloud_color = 0.8125, 0.253906, 0.463379, 1; 13:52:12.613 [planetinfo.record]: cloud_fraction = 0.200000; 13:52:12.613 [planetinfo.record]: land_color = 0.577339, 0.66, 0.520781, 1; 13:52:12.613 [planetinfo.record]: land_fraction = 0.700000; 13:52:12.613 [planetinfo.record]: polar_cloud_color = 0.865625, 0.493677, 0.633157, 1; 13:52:12.613 [planetinfo.record]: polar_land_color = 0.904756, 0.934, 0.884746, 1; 13:52:12.613 [planetinfo.record]: polar_sea_color = 0.712941, 0.934766, 0.742402, 1; 13:52:12.613 [planetinfo.record]: sea_color = 0.0331268, 0.652344, 0.115367, 1; 13:52:12.613 [planetinfo.record]: rotation_speed = 0.003279 13:52:12.613 [planetinfo.record]: planet zpos = 678000.000000 13:52:12.614 [planetinfo.record]: sun_radius = 159439.903259 13:52:12.614 [planetinfo.record]: sun_vector = -0.111 -0.135 -0.985 13:52:12.614 [planetinfo.record]: sun_distance = 1073500 13:52:12.614 [planetinfo.record]: corona_flare = 0.043295 13:52:12.614 [planetinfo.record]: corona_hues = 0.641853 13:52:12.614 [planetinfo.record]: sun_color = 0.275813, 0.390275, 0.811826, 1 13:52:12.614 [planetinfo.record]: corona_shimmer = 0.883949 13:52:12.614 [planetinfo.record]: station_vector = -0.817 -0.530 0.229 13:52:12.614 [planetinfo.record]: station = coriolis 13:52:17.657 [PLANETINFO OVER]: Done 13:52:17.658 [PLANETINFO LOGGING]: 5 167 13:52:18.670 [planetinfo.record]: seed = 89 60 104 45 13:52:18.670 [planetinfo.record]: coordinates = 60 39 13:52:18.670 [planetinfo.record]: government = 3; 13:52:18.670 [planetinfo.record]: economy = 7; 13:52:18.670 [planetinfo.record]: techlevel = 2; 13:52:18.671 [planetinfo.record]: population = 19; 13:52:18.671 [planetinfo.record]: productivity = 3192; 13:52:18.671 [planetinfo.record]: name = "Diatquor"; 13:52:18.671 [planetinfo.record]: inhabitant = "Human Colonial"; 13:52:18.671 [planetinfo.record]: inhabitants = "Human Colonials"; 13:52:18.671 [planetinfo.record]: description = "The world Diatquor is very famous for its exotic goat meat but scourged by dreadful solar activity."; 13:52:18.688 [planetinfo.record]: air_color = 0.452898, 0.725111, 0.857215, 1; 13:52:18.688 [planetinfo.record]: cloud_alpha = 1.000000; 13:52:18.688 [planetinfo.record]: cloud_color = 0.109085, 0.574219, 0.105423, 1; 13:52:18.688 [planetinfo.record]: cloud_fraction = 0.400000; 13:52:18.688 [planetinfo.record]: land_color = 0.440859, 0.66, 0.454556, 1; 13:52:18.688 [planetinfo.record]: land_fraction = 0.340000; 13:52:18.688 [planetinfo.record]: polar_cloud_color = 0.374446, 0.758398, 0.371423, 1; 13:52:18.688 [planetinfo.record]: polar_land_color = 0.856471, 0.934, 0.861316, 1; 13:52:18.688 [planetinfo.record]: polar_sea_color = 0.873254, 0.925781, 0.89131, 1; 13:52:18.688 [planetinfo.record]: sea_color = 0.573746, 0.742188, 0.631648, 1; 13:52:18.688 [planetinfo.record]: rotation_speed = 0.000694 13:52:18.688 [planetinfo.record]: planet zpos = 806520.000000 13:52:18.688 [planetinfo.record]: sun_radius = 103980.615234 13:52:18.688 [planetinfo.record]: sun_vector = -0.976 -0.133 0.171 13:52:18.688 [planetinfo.record]: sun_distance = 1116720 13:52:18.688 [planetinfo.record]: corona_flare = 0.032817 13:52:18.688 [planetinfo.record]: corona_hues = 0.873199 13:52:18.688 [planetinfo.record]: sun_color = 0.284624, 0.301208, 0.799518, 1 13:52:18.689 [planetinfo.record]: corona_shimmer = 0.379760 13:52:18.689 [planetinfo.record]: station_vector = 0.883 0.464 0.066 13:52:18.689 [planetinfo.record]: station = coriolis 13:52:24.139 [PLANETINFO OVER]: Done 13:52:24.140 [PLANETINFO LOGGING]: 5 168 13:52:25.143 [planetinfo.record]: seed = 121 185 138 69 13:52:25.143 [planetinfo.record]: coordinates = 185 250 13:52:25.143 [planetinfo.record]: government = 7; 13:52:25.143 [planetinfo.record]: economy = 2; 13:52:25.143 [planetinfo.record]: techlevel = 10; 13:52:25.143 [planetinfo.record]: population = 50; 13:52:25.144 [planetinfo.record]: productivity = 35200; 13:52:25.144 [planetinfo.record]: name = "Ceoreded"; 13:52:25.144 [planetinfo.record]: inhabitant = "Fierce Yellow Bony Bird"; 13:52:25.144 [planetinfo.record]: inhabitants = "Fierce Yellow Bony Birds"; 13:52:25.144 [planetinfo.record]: description = "This world is reasonably well known for its vast oceans but ravaged by lethal spotted yaks."; 13:52:25.148 [planetinfo.record]: air_color = 0.710864, 0.472291, 0.975586, 1; 13:52:25.149 [planetinfo.record]: cloud_alpha = 1.000000; 13:52:25.149 [planetinfo.record]: cloud_color = 0.929688, 0.0472107, 0.543604, 1; 13:52:25.149 [planetinfo.record]: cloud_fraction = 0.250000; 13:52:25.149 [planetinfo.record]: land_color = 0.66, 0.291328, 0.602395, 1; 13:52:25.149 [planetinfo.record]: land_fraction = 0.200000; 13:52:25.149 [planetinfo.record]: polar_cloud_color = 0.918359, 0.373532, 0.679997, 1; 13:52:25.149 [planetinfo.record]: polar_land_color = 0.934, 0.803568, 0.91362, 1; 13:52:25.149 [planetinfo.record]: polar_sea_color = 0.931311, 0.934375, 0.878349, 1; 13:52:25.149 [planetinfo.record]: sea_color = 0.647642, 0.65625, 0.498853, 1; 13:52:25.149 [planetinfo.record]: rotation_speed = 0.000958 13:52:25.149 [planetinfo.record]: planet zpos = 470910.000000 13:52:25.149 [planetinfo.record]: sun_radius = 89052.063904 13:52:25.149 [planetinfo.record]: sun_vector = -0.045 0.505 0.862 13:52:25.149 [planetinfo.record]: sun_distance = 727770 13:52:25.149 [planetinfo.record]: corona_flare = 0.084325 13:52:25.150 [planetinfo.record]: corona_hues = 0.624237 13:52:25.150 [planetinfo.record]: sun_color = 0.394009, 0.549723, 0.820337, 1 13:52:25.150 [planetinfo.record]: corona_shimmer = 0.356453 13:52:25.150 [planetinfo.record]: station_vector = 0.133 0.974 0.185 13:52:25.150 [planetinfo.record]: station = coriolis 13:52:30.262 [PLANETINFO OVER]: Done 13:52:30.263 [PLANETINFO LOGGING]: 5 169 13:52:31.265 [planetinfo.record]: seed = 101 56 64 43 13:52:31.265 [planetinfo.record]: coordinates = 56 248 13:52:31.265 [planetinfo.record]: government = 4; 13:52:31.265 [planetinfo.record]: economy = 0; 13:52:31.265 [planetinfo.record]: techlevel = 9; 13:52:31.265 [planetinfo.record]: population = 41; 13:52:31.265 [planetinfo.record]: productivity = 26240; 13:52:31.265 [planetinfo.record]: name = "Mateus"; 13:52:31.265 [planetinfo.record]: inhabitant = "Human Colonial"; 13:52:31.265 [planetinfo.record]: inhabitants = "Human Colonials"; 13:52:31.265 [planetinfo.record]: description = "The planet Mateus is very famous for Mateusian wolf meat but scourged by frequent civil war."; 13:52:31.280 [planetinfo.record]: air_color = 0.630662, 0.885717, 0.941115, 1; 13:52:31.280 [planetinfo.record]: cloud_alpha = 1.000000; 13:52:31.280 [planetinfo.record]: cloud_color = 0.649683, 0.826172, 0.503448, 1; 13:52:31.280 [planetinfo.record]: cloud_fraction = 0.340000; 13:52:31.280 [planetinfo.record]: land_color = 0.309556, 0.66, 0.306797, 1; 13:52:31.280 [planetinfo.record]: land_fraction = 0.280000; 13:52:31.280 [planetinfo.record]: polar_cloud_color = 0.755383, 0.871777, 0.658941, 1; 13:52:31.280 [planetinfo.record]: polar_land_color = 0.810017, 0.934, 0.809041, 1; 13:52:31.280 [planetinfo.record]: polar_sea_color = 0.948438, 0.907207, 0.90046, 1; 13:52:31.280 [planetinfo.record]: sea_color = 0.515625, 0.425963, 0.411292, 1; 13:52:31.280 [planetinfo.record]: rotation_speed = 0.001473 13:52:31.280 [planetinfo.record]: planet zpos = 568800.000000 13:52:31.280 [planetinfo.record]: sun_radius = 185379.016113 13:52:31.280 [planetinfo.record]: sun_vector = 0.595 0.010 0.804 13:52:31.280 [planetinfo.record]: sun_distance = 1137600 13:52:31.280 [planetinfo.record]: corona_flare = 0.084406 13:52:31.280 [planetinfo.record]: corona_hues = 0.514305 13:52:31.280 [planetinfo.record]: sun_color = 0.67995, 0.667105, 0.62576, 1 13:52:31.280 [planetinfo.record]: corona_shimmer = 0.278810 13:52:31.280 [planetinfo.record]: station_vector = 0.406 0.561 0.721 13:52:31.280 [planetinfo.record]: station = coriolis 13:52:35.612 [PLANETINFO OVER]: Done 13:52:35.612 [PLANETINFO LOGGING]: 5 170 13:52:36.627 [planetinfo.record]: seed = 13 72 218 100 13:52:36.627 [planetinfo.record]: coordinates = 72 192 13:52:36.627 [planetinfo.record]: government = 1; 13:52:36.627 [planetinfo.record]: economy = 2; 13:52:36.627 [planetinfo.record]: techlevel = 6; 13:52:36.627 [planetinfo.record]: population = 28; 13:52:36.627 [planetinfo.record]: productivity = 8960; 13:52:36.627 [planetinfo.record]: name = "Zadiqu"; 13:52:36.627 [planetinfo.record]: inhabitant = "Fierce Blue Slimy Rodent"; 13:52:36.627 [planetinfo.record]: inhabitants = "Fierce Blue Slimy Rodents"; 13:52:36.627 [planetinfo.record]: description = "This planet is a tedious place."; 13:52:36.644 [planetinfo.record]: air_color = 0.513832, 0.967781, 0.814952, 1; 13:52:36.644 [planetinfo.record]: cloud_alpha = 1.000000; 13:52:36.644 [planetinfo.record]: cloud_color = 0.90625, 0.173462, 0.173462, 1; 13:52:36.644 [planetinfo.record]: cloud_fraction = 0.500000; 13:52:36.644 [planetinfo.record]: land_color = 0.659033, 0.66, 0.53625, 1; 13:52:36.644 [planetinfo.record]: land_fraction = 0.520000; 13:52:36.644 [planetinfo.record]: polar_cloud_color = 0.907812, 0.44903, 0.44903, 1; 13:52:36.644 [planetinfo.record]: polar_land_color = 0.933658, 0.934, 0.890219, 1; 13:52:36.644 [planetinfo.record]: polar_sea_color = 0.717091, 0.86807, 0.927148, 1; 13:52:36.644 [planetinfo.record]: sea_color = 0.0682983, 0.54283, 0.728516, 1; 13:52:36.644 [planetinfo.record]: rotation_speed = 0.001111 13:52:36.644 [planetinfo.record]: planet zpos = 391200.000000 13:52:36.644 [planetinfo.record]: sun_radius = 94857.762451 13:52:36.644 [planetinfo.record]: sun_vector = 0.417 0.892 0.177 13:52:36.644 [planetinfo.record]: sun_distance = 821520 13:52:36.644 [planetinfo.record]: corona_flare = 0.047707 13:52:36.644 [planetinfo.record]: corona_hues = 0.625435 13:52:36.644 [planetinfo.record]: sun_color = 0.343777, 0.517255, 0.825827, 1 13:52:36.644 [planetinfo.record]: corona_shimmer = 0.390193 13:52:36.644 [planetinfo.record]: station_vector = -0.975 -0.164 0.152 13:52:36.644 [planetinfo.record]: station = coriolis 13:52:41.634 [PLANETINFO OVER]: Done 13:52:41.635 [PLANETINFO LOGGING]: 5 171 13:52:42.654 [planetinfo.record]: seed = 1 236 40 115 13:52:42.654 [planetinfo.record]: coordinates = 236 26 13:52:42.654 [planetinfo.record]: government = 0; 13:52:42.654 [planetinfo.record]: economy = 2; 13:52:42.654 [planetinfo.record]: techlevel = 5; 13:52:42.654 [planetinfo.record]: population = 23; 13:52:42.654 [planetinfo.record]: productivity = 5888; 13:52:42.654 [planetinfo.record]: name = "Beored"; 13:52:42.654 [planetinfo.record]: inhabitant = "Human Colonial"; 13:52:42.654 [planetinfo.record]: inhabitants = "Human Colonials"; 13:52:42.654 [planetinfo.record]: description = "The planet Beored is reasonably noted for its exotic fish meat and its exciting sit coms."; 13:52:42.656 [planetinfo.record]: air_color = 0.441275, 0.649909, 0.892336, 1; 13:52:42.656 [planetinfo.record]: cloud_alpha = 1.000000; 13:52:42.656 [planetinfo.record]: cloud_color = 0.0557556, 0.679688, 0.445713, 1; 13:52:42.656 [planetinfo.record]: cloud_fraction = 0.380000; 13:52:42.656 [planetinfo.record]: land_color = 0.231049, 0.523438, 0.496026, 1; 13:52:42.656 [planetinfo.record]: land_fraction = 0.710000; 13:52:42.656 [planetinfo.record]: polar_cloud_color = 0.343513, 0.805859, 0.63248, 1; 13:52:42.656 [planetinfo.record]: polar_land_color = 0.815318, 0.947656, 0.93525, 1; 13:52:42.656 [planetinfo.record]: polar_sea_color = 0.874057, 0.925195, 0.901224, 1; 13:52:42.656 [planetinfo.record]: sea_color = 0.582658, 0.748047, 0.670521, 1; 13:52:42.657 [planetinfo.record]: rotation_speed = 0.004815 13:52:42.657 [planetinfo.record]: planet zpos = 420200.000000 13:52:42.657 [planetinfo.record]: sun_radius = 99648.980713 13:52:42.657 [planetinfo.record]: sun_vector = 0.148 -0.348 -0.926 13:52:42.657 [planetinfo.record]: sun_distance = 878600 13:52:42.657 [planetinfo.record]: corona_flare = 0.091182 13:52:42.657 [planetinfo.record]: corona_hues = 0.509789 13:52:42.657 [planetinfo.record]: sun_color = 0.513452, 0.680674, 0.757907, 1 13:52:42.657 [planetinfo.record]: corona_shimmer = 0.697058 13:52:42.657 [planetinfo.record]: station_vector = -0.284 -0.634 0.719 13:52:42.657 [planetinfo.record]: station = coriolis 13:52:47.377 [PLANETINFO OVER]: Done 13:52:47.380 [PLANETINFO LOGGING]: 5 172 13:52:48.383 [planetinfo.record]: seed = 177 196 58 22 13:52:48.383 [planetinfo.record]: coordinates = 196 216 13:52:48.383 [planetinfo.record]: government = 6; 13:52:48.383 [planetinfo.record]: economy = 0; 13:52:48.383 [planetinfo.record]: techlevel = 10; 13:52:48.383 [planetinfo.record]: population = 47; 13:52:48.383 [planetinfo.record]: productivity = 37600; 13:52:48.383 [planetinfo.record]: name = "Vebereti"; 13:52:48.383 [planetinfo.record]: inhabitant = "Human Colonial"; 13:52:48.383 [planetinfo.record]: inhabitants = "Human Colonials"; 13:52:48.383 [planetinfo.record]: description = "This world is most fabled for its inhabitants’ ingrained silliness but scourged by deadly civil war."; 13:52:48.400 [planetinfo.record]: air_color = 0.526718, 0.564164, 0.863068, 1; 13:52:48.400 [planetinfo.record]: cloud_alpha = 1.000000; 13:52:48.400 [planetinfo.record]: cloud_color = 0.254288, 0.362396, 0.591797, 1; 13:52:48.400 [planetinfo.record]: cloud_fraction = 0.190000; 13:52:48.400 [planetinfo.record]: land_color = 0.66, 0.62729, 0.515625, 1; 13:52:48.400 [planetinfo.record]: land_fraction = 0.500000; 13:52:48.400 [planetinfo.record]: polar_cloud_color = 0.493161, 0.580654, 0.766309, 1; 13:52:48.400 [planetinfo.record]: polar_land_color = 0.934, 0.922428, 0.882922, 1; 13:52:48.400 [planetinfo.record]: polar_sea_color = 0.713171, 0.840952, 0.925586, 1; 13:52:48.400 [planetinfo.record]: sea_color = 0.0610428, 0.471969, 0.744141, 1; 13:52:48.401 [planetinfo.record]: rotation_speed = 0.003750 13:52:48.401 [planetinfo.record]: planet zpos = 500280.000000 13:52:48.401 [planetinfo.record]: sun_radius = 142812.030029 13:52:48.401 [planetinfo.record]: sun_vector = 0.409 0.657 -0.633 13:52:48.401 [planetinfo.record]: sun_distance = 818640 13:52:48.402 [planetinfo.record]: corona_flare = 0.069524 13:52:48.402 [planetinfo.record]: corona_hues = 0.960884 13:52:48.402 [planetinfo.record]: sun_color = 0.34343, 0.440205, 0.77597, 1 13:52:48.402 [planetinfo.record]: corona_shimmer = 0.326993 13:52:48.402 [planetinfo.record]: station_vector = -0.332 -0.673 0.661 13:52:48.402 [planetinfo.record]: station = coriolis 13:52:52.700 [PLANETINFO OVER]: Done 13:52:52.701 [PLANETINFO LOGGING]: 5 173 13:52:53.714 [planetinfo.record]: seed = 173 87 32 153 13:52:53.715 [planetinfo.record]: coordinates = 87 142 13:52:53.715 [planetinfo.record]: government = 5; 13:52:53.715 [planetinfo.record]: economy = 6; 13:52:53.715 [planetinfo.record]: techlevel = 7; 13:52:53.715 [planetinfo.record]: population = 40; 13:52:53.715 [planetinfo.record]: productivity = 11520; 13:52:53.715 [planetinfo.record]: name = "Orria"; 13:52:53.715 [planetinfo.record]: inhabitant = "Human Colonial"; 13:52:53.715 [planetinfo.record]: inhabitants = "Human Colonials"; 13:52:53.715 [planetinfo.record]: description = "This world is mildly well known for Orriaian wolf cutlet and its unusual oceans."; 13:52:53.730 [planetinfo.record]: air_color = 0.78636, 0.483424, 0.98274, 1; 13:52:53.730 [planetinfo.record]: cloud_alpha = 1.000000; 13:52:53.730 [planetinfo.record]: cloud_color = 0.951172, 0.0705948, 0.215064, 1; 13:52:53.730 [planetinfo.record]: cloud_fraction = 0.210000; 13:52:53.730 [planetinfo.record]: land_color = 0.66, 0.223088, 0.177891, 1; 13:52:53.730 [planetinfo.record]: land_fraction = 0.240000; 13:52:53.730 [planetinfo.record]: polar_cloud_color = 0.928027, 0.391058, 0.479155, 1; 13:52:53.730 [planetinfo.record]: polar_land_color = 0.934, 0.779426, 0.763436, 1; 13:52:53.730 [planetinfo.record]: polar_sea_color = 0.945703, 0.882903, 0.932947, 1; 13:52:53.730 [planetinfo.record]: sea_color = 0.542969, 0.398743, 0.513673, 1; 13:52:53.731 [planetinfo.record]: rotation_speed = 0.000590 13:52:53.731 [planetinfo.record]: planet zpos = 624840.000000 13:52:53.731 [planetinfo.record]: sun_radius = 84770.271454 13:52:53.731 [planetinfo.record]: sun_vector = -0.369 0.537 -0.759 13:52:53.731 [planetinfo.record]: sun_distance = 989330 13:52:53.731 [planetinfo.record]: corona_flare = 0.028175 13:52:53.731 [planetinfo.record]: corona_hues = 0.511795 13:52:53.731 [planetinfo.record]: sun_color = 0.766997, 0.769209, 0.780157, 1 13:52:53.732 [planetinfo.record]: corona_shimmer = 0.401549 13:52:53.732 [planetinfo.record]: station_vector = 0.977 -0.212 0.034 13:52:53.732 [planetinfo.record]: station = coriolis 13:52:59.698 [PLANETINFO OVER]: Done 13:52:59.700 [PLANETINFO LOGGING]: 5 174 13:53:00.723 [planetinfo.record]: seed = 229 135 170 118 13:53:00.723 [planetinfo.record]: coordinates = 135 111 13:53:00.723 [planetinfo.record]: government = 4; 13:53:00.723 [planetinfo.record]: economy = 7; 13:53:00.723 [planetinfo.record]: techlevel = 5; 13:53:00.723 [planetinfo.record]: population = 32; 13:53:00.723 [planetinfo.record]: productivity = 6144; 13:53:00.723 [planetinfo.record]: name = "Vereinat"; 13:53:00.723 [planetinfo.record]: inhabitant = "Blue Slimy Lizard"; 13:53:00.723 [planetinfo.record]: inhabitants = "Blue Slimy Lizards"; 13:53:00.723 [planetinfo.record]: description = "This world is ravaged by unpredictable civil war."; 13:53:00.755 [planetinfo.record]: air_color = 0.608156, 0.526232, 0.904693, 1; 13:53:00.755 [planetinfo.record]: cloud_alpha = 1.000000; 13:53:00.755 [planetinfo.record]: cloud_color = 0.558773, 0.246399, 0.716797, 1; 13:53:00.755 [planetinfo.record]: cloud_fraction = 0.330000; 13:53:00.756 [planetinfo.record]: land_color = 0.66, 0.0257813, 0.471716, 1; 13:53:00.756 [planetinfo.record]: land_fraction = 0.380000; 13:53:00.756 [planetinfo.record]: polar_cloud_color = 0.709221, 0.485181, 0.822559, 1; 13:53:00.756 [planetinfo.record]: polar_land_color = 0.934, 0.709621, 0.867388, 1; 13:53:00.756 [planetinfo.record]: polar_sea_color = 0.941211, 0.932259, 0.891393, 1; 13:53:00.756 [planetinfo.record]: sea_color = 0.587891, 0.565525, 0.463423, 1; 13:53:00.756 [planetinfo.record]: rotation_speed = 0.003941 13:53:00.756 [planetinfo.record]: planet zpos = 583310.000000 13:53:00.756 [planetinfo.record]: sun_radius = 124716.636047 13:53:00.756 [planetinfo.record]: sun_vector = 0.115 0.338 -0.934 13:53:00.756 [planetinfo.record]: sun_distance = 1032010 13:53:00.756 [planetinfo.record]: corona_flare = 0.088620 13:53:00.756 [planetinfo.record]: corona_hues = 0.755508 13:53:00.756 [planetinfo.record]: sun_color = 0.352716, 0.550642, 0.700912, 1 13:53:00.756 [planetinfo.record]: corona_shimmer = 0.343437 13:53:00.756 [planetinfo.record]: station_vector = -0.957 0.286 0.042 13:53:00.756 [planetinfo.record]: station = coriolis 13:53:06.010 [PLANETINFO OVER]: Done 13:53:06.011 [PLANETINFO LOGGING]: 5 175 13:53:07.023 [planetinfo.record]: seed = 233 81 40 45 13:53:07.023 [planetinfo.record]: coordinates = 81 108 13:53:07.023 [planetinfo.record]: government = 5; 13:53:07.023 [planetinfo.record]: economy = 4; 13:53:07.023 [planetinfo.record]: techlevel = 7; 13:53:07.023 [planetinfo.record]: population = 38; 13:53:07.023 [planetinfo.record]: productivity = 16416; 13:53:07.023 [planetinfo.record]: name = "Dimamaza"; 13:53:07.023 [planetinfo.record]: inhabitant = "Human Colonial"; 13:53:07.023 [planetinfo.record]: inhabitants = "Human Colonials"; 13:53:07.024 [planetinfo.record]: description = "The world Dimamaza is a boring planet."; 13:53:07.033 [planetinfo.record]: air_color = 0.706951, 0.831183, 0.893637, 1; 13:53:07.034 [planetinfo.record]: cloud_alpha = 1.000000; 13:53:07.035 [planetinfo.record]: cloud_color = 0.664902, 0.683594, 0.665194, 1; 13:53:07.035 [planetinfo.record]: cloud_fraction = 0.490000; 13:53:07.035 [planetinfo.record]: land_color = 0.605469, 0.521489, 0.499039, 1; 13:53:07.035 [planetinfo.record]: land_fraction = 0.540000; 13:53:07.035 [planetinfo.record]: polar_cloud_color = 0.793815, 0.807617, 0.794031, 1; 13:53:07.035 [planetinfo.record]: polar_land_color = 0.939453, 0.906877, 0.898169, 1; 13:53:07.035 [planetinfo.record]: polar_sea_color = 0.76498, 0.900391, 0.808354, 1; 13:53:07.035 [planetinfo.record]: sea_color = 0.396881, 0.996094, 0.588816, 1; 13:53:07.035 [planetinfo.record]: rotation_speed = 0.003821 13:53:07.035 [planetinfo.record]: planet zpos = 871500.000000 13:53:07.035 [planetinfo.record]: sun_radius = 161940.616608 13:53:07.035 [planetinfo.record]: sun_vector = -0.191 0.954 0.229 13:53:07.036 [planetinfo.record]: sun_distance = 1245000 13:53:07.036 [planetinfo.record]: corona_flare = 0.068774 13:53:07.036 [planetinfo.record]: corona_hues = 0.507515 13:53:07.036 [planetinfo.record]: sun_color = 0.669919, 0.657473, 0.632352, 1 13:53:07.037 [planetinfo.record]: corona_shimmer = 0.798927 13:53:07.037 [planetinfo.record]: station_vector = -0.864 -0.246 0.440 13:53:07.037 [planetinfo.record]: station = coriolis 13:53:12.331 [PLANETINFO OVER]: Done 13:53:12.332 [PLANETINFO LOGGING]: 5 176 13:53:13.348 [planetinfo.record]: seed = 41 132 42 219 13:53:13.348 [planetinfo.record]: coordinates = 132 107 13:53:13.348 [planetinfo.record]: government = 5; 13:53:13.348 [planetinfo.record]: economy = 3; 13:53:13.348 [planetinfo.record]: techlevel = 7; 13:53:13.348 [planetinfo.record]: population = 37; 13:53:13.349 [planetinfo.record]: productivity = 18648; 13:53:13.349 [planetinfo.record]: name = "Anares"; 13:53:13.349 [planetinfo.record]: inhabitant = "Human Colonial"; 13:53:13.349 [planetinfo.record]: inhabitants = "Human Colonials"; 13:53:13.349 [planetinfo.record]: description = "The planet Anares is a boring planet."; 13:53:13.355 [planetinfo.record]: air_color = 0.683818, 0.545438, 0.909246, 1; 13:53:13.356 [planetinfo.record]: cloud_alpha = 1.000000; 13:53:13.356 [planetinfo.record]: cloud_color = 0.730469, 0.291046, 0.661809, 1; 13:53:13.356 [planetinfo.record]: cloud_fraction = 0.260000; 13:53:13.356 [planetinfo.record]: land_color = 0.525937, 0.638005, 0.66, 1; 13:53:13.356 [planetinfo.record]: land_fraction = 0.710000; 13:53:13.356 [planetinfo.record]: polar_cloud_color = 0.828711, 0.517135, 0.780027, 1; 13:53:13.357 [planetinfo.record]: polar_land_color = 0.88657, 0.926219, 0.934, 1; 13:53:13.357 [planetinfo.record]: polar_sea_color = 0.929492, 0.903495, 0.858691, 1; 13:53:13.357 [planetinfo.record]: sea_color = 0.705078, 0.626196, 0.49025, 1; 13:53:13.357 [planetinfo.record]: rotation_speed = 0.001643 13:53:13.357 [planetinfo.record]: planet zpos = 576400.000000 13:53:13.357 [planetinfo.record]: sun_radius = 104686.222534 13:53:13.357 [planetinfo.record]: sun_vector = -0.270 0.607 -0.748 13:53:13.357 [planetinfo.record]: sun_distance = 1325720 13:53:13.357 [planetinfo.record]: corona_flare = 0.032251 13:53:13.357 [planetinfo.record]: corona_hues = 0.924767 13:53:13.358 [planetinfo.record]: sun_color = 0.556074, 0.722133, 0.827448, 1 13:53:13.358 [planetinfo.record]: corona_shimmer = 0.399449 13:53:13.358 [planetinfo.record]: station_vector = -0.842 -0.109 0.528 13:53:13.358 [planetinfo.record]: station = coriolis 13:53:18.298 [PLANETINFO OVER]: Done 13:53:18.299 [PLANETINFO LOGGING]: 5 177 13:53:19.302 [planetinfo.record]: seed = 53 207 64 195 13:53:19.302 [planetinfo.record]: coordinates = 207 41 13:53:19.302 [planetinfo.record]: government = 6; 13:53:19.302 [planetinfo.record]: economy = 1; 13:53:19.302 [planetinfo.record]: techlevel = 12; 13:53:19.302 [planetinfo.record]: population = 56; 13:53:19.302 [planetinfo.record]: productivity = 40320; 13:53:19.302 [planetinfo.record]: name = "Getere"; 13:53:19.302 [planetinfo.record]: inhabitant = "Human Colonial"; 13:53:19.302 [planetinfo.record]: inhabitants = "Human Colonials"; 13:53:19.302 [planetinfo.record]: description = "The world Getere is notable for the Getereian tree grub and its inhabitants’ weird silliness."; 13:53:19.304 [planetinfo.record]: air_color = 0.469408, 0.528585, 0.908596, 1; 13:53:19.304 [planetinfo.record]: cloud_alpha = 1.000000; 13:53:19.304 [planetinfo.record]: cloud_color = 0.108139, 0.350474, 0.728516, 1; 13:53:19.304 [planetinfo.record]: cloud_fraction = 0.320000; 13:53:19.304 [planetinfo.record]: land_color = 0.411767, 0.328598, 0.576172, 1; 13:53:19.304 [planetinfo.record]: land_fraction = 0.260000; 13:53:19.304 [planetinfo.record]: polar_cloud_color = 0.387238, 0.559345, 0.827832, 1; 13:53:19.304 [planetinfo.record]: polar_land_color = 0.875158, 0.84115, 0.942383, 1; 13:53:19.304 [planetinfo.record]: polar_sea_color = 0.926172, 0.872314, 0.833012, 1; 13:53:19.304 [planetinfo.record]: sea_color = 0.738281, 0.566553, 0.441238, 1; 13:53:19.305 [planetinfo.record]: rotation_speed = 0.004691 13:53:19.305 [planetinfo.record]: planet zpos = 568650.000000 13:53:19.305 [planetinfo.record]: sun_radius = 103871.872864 13:53:19.305 [planetinfo.record]: sun_vector = -0.298 0.702 0.647 13:53:19.305 [planetinfo.record]: sun_distance = 720290 13:53:19.305 [planetinfo.record]: corona_flare = 0.046947 13:53:19.305 [planetinfo.record]: corona_hues = 0.511192 13:53:19.305 [planetinfo.record]: sun_color = 0.240503, 0.497779, 0.722122, 1 13:53:19.305 [planetinfo.record]: corona_shimmer = 0.259501 13:53:19.305 [planetinfo.record]: station_vector = 0.901 -0.278 0.333 13:53:19.305 [planetinfo.record]: station = dodecahedron 13:53:24.539 [PLANETINFO OVER]: Done 13:53:24.539 [PLANETINFO LOGGING]: 5 178 13:53:25.543 [planetinfo.record]: seed = 253 205 186 216 13:53:25.543 [planetinfo.record]: coordinates = 205 78 13:53:25.543 [planetinfo.record]: government = 7; 13:53:25.543 [planetinfo.record]: economy = 6; 13:53:25.543 [planetinfo.record]: techlevel = 6; 13:53:25.543 [planetinfo.record]: population = 38; 13:53:25.543 [planetinfo.record]: productivity = 13376; 13:53:25.543 [planetinfo.record]: name = "Edlate"; 13:53:25.543 [planetinfo.record]: inhabitant = "Bony Lobster"; 13:53:25.543 [planetinfo.record]: inhabitants = "Bony Lobsters"; 13:53:25.543 [planetinfo.record]: description = "This planet is beset by lethal disease."; 13:53:25.545 [planetinfo.record]: air_color = 0.657881, 0.865797, 0.920953, 1; 13:53:25.545 [planetinfo.record]: cloud_alpha = 1.000000; 13:53:25.545 [planetinfo.record]: cloud_color = 0.639174, 0.765625, 0.568237, 1; 13:53:25.545 [planetinfo.record]: cloud_fraction = 0.500000; 13:53:25.545 [planetinfo.record]: land_color = 0.352376, 0.339203, 0.507812, 1; 13:53:25.545 [planetinfo.record]: land_fraction = 0.460000; 13:53:25.545 [planetinfo.record]: polar_cloud_color = 0.757354, 0.844531, 0.70845, 1; 13:53:25.545 [planetinfo.record]: polar_land_color = 0.876582, 0.870426, 0.949219, 1; 13:53:25.545 [planetinfo.record]: polar_sea_color = 0.912891, 0.894775, 0.776492, 1; 13:53:25.545 [planetinfo.record]: sea_color = 0.871094, 0.80195, 0.350479, 1; 13:53:25.545 [planetinfo.record]: rotation_speed = 0.001809 13:53:25.545 [planetinfo.record]: planet zpos = 709660.000000 13:53:25.545 [planetinfo.record]: sun_radius = 142949.265137 13:53:25.545 [planetinfo.record]: sun_vector = -0.661 0.669 0.340 13:53:25.545 [planetinfo.record]: sun_distance = 963110 13:53:25.545 [planetinfo.record]: corona_flare = 0.056859 13:53:25.545 [planetinfo.record]: corona_hues = 0.798111 13:53:25.545 [planetinfo.record]: sun_color = 0.201767, 0.505201, 0.782669, 1 13:53:25.545 [planetinfo.record]: corona_shimmer = 0.632905 13:53:25.546 [planetinfo.record]: station_vector = -0.977 -0.044 0.210 13:53:25.546 [planetinfo.record]: station = coriolis 13:53:30.962 [PLANETINFO OVER]: Done 13:53:30.963 [PLANETINFO LOGGING]: 5 179 13:53:31.966 [planetinfo.record]: seed = 17 106 104 251 13:53:31.966 [planetinfo.record]: coordinates = 106 156 13:53:31.966 [planetinfo.record]: government = 2; 13:53:31.966 [planetinfo.record]: economy = 4; 13:53:31.966 [planetinfo.record]: techlevel = 6; 13:53:31.966 [planetinfo.record]: population = 31; 13:53:31.966 [planetinfo.record]: productivity = 8928; 13:53:31.967 [planetinfo.record]: name = "Anlebi"; 13:53:31.967 [planetinfo.record]: inhabitant = "Human Colonial"; 13:53:31.967 [planetinfo.record]: inhabitants = "Human Colonials"; 13:53:31.967 [planetinfo.record]: description = "The planet Anlebi is a boring planet."; 13:53:31.984 [planetinfo.record]: air_color = 0.746045, 0.748066, 0.950871, 1; 13:53:31.984 [planetinfo.record]: cloud_alpha = 1.000000; 13:53:31.984 [planetinfo.record]: cloud_color = 0.815369, 0.815995, 0.855469, 1; 13:53:31.984 [planetinfo.record]: cloud_fraction = 0.350000; 13:53:31.984 [planetinfo.record]: land_color = 0.346697, 0.00257813, 0.66, 1; 13:53:31.984 [planetinfo.record]: land_fraction = 0.290000; 13:53:31.984 [planetinfo.record]: polar_cloud_color = 0.859034, 0.859439, 0.884961, 1; 13:53:31.984 [planetinfo.record]: polar_land_color = 0.823157, 0.701412, 0.934, 1; 13:53:31.984 [planetinfo.record]: polar_sea_color = 0.94707, 0.90423, 0.89722, 1; 13:53:31.984 [planetinfo.record]: sea_color = 0.529297, 0.433527, 0.417855, 1; 13:53:31.984 [planetinfo.record]: rotation_speed = 0.002982 13:53:31.984 [planetinfo.record]: planet zpos = 631180.000000 13:53:31.984 [planetinfo.record]: sun_radius = 116343.868713 13:53:31.985 [planetinfo.record]: sun_vector = 0.612 -0.534 -0.583 13:53:31.985 [planetinfo.record]: sun_distance = 975460 13:53:31.985 [planetinfo.record]: corona_flare = 0.085510 13:53:31.985 [planetinfo.record]: corona_hues = 0.642929 13:53:31.985 [planetinfo.record]: sun_color = 0.711571, 0.780371, 0.806738, 1 13:53:31.985 [planetinfo.record]: corona_shimmer = 0.413899 13:53:31.985 [planetinfo.record]: station_vector = -0.091 0.995 0.026 13:53:31.985 [planetinfo.record]: station = coriolis 13:53:36.771 [PLANETINFO OVER]: Done 13:53:36.772 [PLANETINFO LOGGING]: 5 180 13:53:37.774 [planetinfo.record]: seed = 225 99 90 204 13:53:37.774 [planetinfo.record]: coordinates = 99 102 13:53:37.774 [planetinfo.record]: government = 4; 13:53:37.774 [planetinfo.record]: economy = 6; 13:53:37.774 [planetinfo.record]: techlevel = 6; 13:53:37.774 [planetinfo.record]: population = 35; 13:53:37.774 [planetinfo.record]: productivity = 8960; 13:53:37.774 [planetinfo.record]: name = "Intisoar"; 13:53:37.774 [planetinfo.record]: inhabitant = "Human Colonial"; 13:53:37.774 [planetinfo.record]: inhabitants = "Human Colonials"; 13:53:37.774 [planetinfo.record]: description = "Intisoar is cursed by killer edible talking treeoids."; 13:53:37.792 [planetinfo.record]: air_color = 0.681497, 0.610108, 0.840305, 1; 13:53:37.792 [planetinfo.record]: cloud_alpha = 1.000000; 13:53:37.792 [planetinfo.record]: cloud_color = 0.517064, 0.406891, 0.523438, 1; 13:53:37.792 [planetinfo.record]: cloud_fraction = 0.470000; 13:53:37.792 [planetinfo.record]: land_color = 0.442571, 0.66, 0.332578, 1; 13:53:37.792 [planetinfo.record]: land_fraction = 0.240000; 13:53:37.792 [planetinfo.record]: polar_cloud_color = 0.729949, 0.633188, 0.735547, 1; 13:53:37.792 [planetinfo.record]: polar_land_color = 0.857076, 0.934, 0.818162, 1; 13:53:37.792 [planetinfo.record]: polar_sea_color = 0.903493, 0.93418, 0.88446, 1; 13:53:37.792 [planetinfo.record]: sea_color = 0.57172, 0.658203, 0.518078, 1; 13:53:37.792 [planetinfo.record]: rotation_speed = 0.004444 13:53:37.792 [planetinfo.record]: planet zpos = 658570.000000 13:53:37.792 [planetinfo.record]: sun_radius = 142479.929810 13:53:37.792 [planetinfo.record]: sun_vector = -0.012 0.844 -0.537 13:53:37.792 [planetinfo.record]: sun_distance = 1317140 13:53:37.792 [planetinfo.record]: corona_flare = 0.006915 13:53:37.792 [planetinfo.record]: corona_hues = 0.920357 13:53:37.792 [planetinfo.record]: sun_color = 0.410561, 0.653237, 0.71044, 1 13:53:37.792 [planetinfo.record]: corona_shimmer = 0.468241 13:53:37.793 [planetinfo.record]: station_vector = 0.966 -0.251 0.063 13:53:37.793 [planetinfo.record]: station = coriolis 13:53:42.379 [PLANETINFO OVER]: Done 13:53:42.380 [PLANETINFO LOGGING]: 5 181 13:53:43.385 [planetinfo.record]: seed = 253 42 160 137 13:53:43.385 [planetinfo.record]: coordinates = 42 199 13:53:43.385 [planetinfo.record]: government = 7; 13:53:43.385 [planetinfo.record]: economy = 7; 13:53:43.385 [planetinfo.record]: techlevel = 6; 13:53:43.385 [planetinfo.record]: population = 39; 13:53:43.385 [planetinfo.record]: productivity = 10296; 13:53:43.385 [planetinfo.record]: name = "Esteerve"; 13:53:43.385 [planetinfo.record]: inhabitant = "Small Black Furry Feline"; 13:53:43.385 [planetinfo.record]: inhabitants = "Small Black Furry Felines"; 13:53:43.385 [planetinfo.record]: description = "This planet is a dull place."; 13:53:43.390 [planetinfo.record]: air_color = 0.751635, 0.576005, 0.903393, 1; 13:53:43.390 [planetinfo.record]: cloud_alpha = 1.000000; 13:53:43.390 [planetinfo.record]: cloud_color = 0.712891, 0.364799, 0.495334, 1; 13:53:43.391 [planetinfo.record]: cloud_fraction = 0.080000; 13:53:43.391 [planetinfo.record]: land_color = 0.66, 0.28875, 0.306152, 1; 13:53:43.391 [planetinfo.record]: land_fraction = 0.600000; 13:53:43.391 [planetinfo.record]: polar_cloud_color = 0.820801, 0.570312, 0.664245, 1; 13:53:43.391 [planetinfo.record]: polar_land_color = 0.934, 0.802656, 0.808813, 1; 13:53:43.391 [planetinfo.record]: polar_sea_color = 0.937891, 0.914378, 0.881104, 1; 13:53:43.391 [planetinfo.record]: sea_color = 0.621094, 0.55881, 0.470673, 1; 13:53:43.391 [planetinfo.record]: rotation_speed = 0.003701 13:53:43.391 [planetinfo.record]: planet zpos = 516200.000000 13:53:43.391 [planetinfo.record]: sun_radius = 147377.242432 13:53:43.392 [planetinfo.record]: sun_vector = 0.397 -0.755 -0.522 13:53:43.392 [planetinfo.record]: sun_distance = 877540 13:53:43.392 [planetinfo.record]: corona_flare = 0.089586 13:53:43.392 [planetinfo.record]: corona_hues = 0.690834 13:53:43.392 [planetinfo.record]: sun_color = 0.513099, 0.783729, 0.819427, 1 13:53:43.392 [planetinfo.record]: corona_shimmer = 0.347067 13:53:43.392 [planetinfo.record]: station_vector = 0.221 0.207 0.953 13:53:43.392 [planetinfo.record]: station = coriolis 13:53:48.470 [PLANETINFO OVER]: Done 13:53:48.470 [PLANETINFO LOGGING]: 5 182 13:53:49.488 [planetinfo.record]: seed = 85 54 10 227 13:53:49.488 [planetinfo.record]: coordinates = 54 48 13:53:49.488 [planetinfo.record]: government = 2; 13:53:49.488 [planetinfo.record]: economy = 0; 13:53:49.488 [planetinfo.record]: techlevel = 10; 13:53:49.489 [planetinfo.record]: population = 43; 13:53:49.489 [planetinfo.record]: productivity = 20640; 13:53:49.489 [planetinfo.record]: name = "Gearge"; 13:53:49.489 [planetinfo.record]: inhabitant = "Human Colonial"; 13:53:49.489 [planetinfo.record]: inhabitants = "Human Colonials"; 13:53:49.489 [planetinfo.record]: description = "Gearge is most famous for its pink oceans and its exciting sit coms."; 13:53:49.510 [planetinfo.record]: air_color = 0.649273, 0.875426, 0.836354, 1; 13:53:49.510 [planetinfo.record]: cloud_alpha = 1.000000; 13:53:49.510 [planetinfo.record]: cloud_color = 0.628906, 0.572768, 0.518356, 1; 13:53:49.510 [planetinfo.record]: cloud_fraction = 0.260000; 13:53:49.510 [planetinfo.record]: land_color = 0.461586, 0.311028, 0.626953, 1; 13:53:49.510 [planetinfo.record]: land_fraction = 0.490000; 13:53:49.510 [planetinfo.record]: polar_cloud_color = 0.783008, 0.739324, 0.696984, 1; 13:53:49.510 [planetinfo.record]: polar_land_color = 0.875498, 0.819226, 0.937305, 1; 13:53:49.510 [planetinfo.record]: polar_sea_color = 0.91, 0.8645, 0.8918, 1; 13:53:49.510 [planetinfo.record]: sea_color = 0.9, 0.72, 0.828, 1; 13:53:49.510 [planetinfo.record]: rotation_speed = 0.004604 13:53:49.510 [planetinfo.record]: planet zpos = 327420.000000 13:53:49.511 [planetinfo.record]: sun_radius = 102343.730164 13:53:49.511 [planetinfo.record]: sun_vector = 0.408 -0.788 0.461 13:53:49.511 [planetinfo.record]: sun_distance = 836740 13:53:49.511 [planetinfo.record]: corona_flare = 0.091414 13:53:49.511 [planetinfo.record]: corona_hues = 0.634125 13:53:49.511 [planetinfo.record]: sun_color = 0.771445, 0.741927, 0.726137, 1 13:53:49.511 [planetinfo.record]: corona_shimmer = 0.346865 13:53:49.511 [planetinfo.record]: station_vector = 0.647 -0.753 0.120 13:53:49.511 [planetinfo.record]: station = coriolis 13:53:54.201 [PLANETINFO OVER]: Done 13:53:54.201 [PLANETINFO LOGGING]: 5 183 13:53:55.204 [planetinfo.record]: seed = 121 144 232 61 13:53:55.204 [planetinfo.record]: coordinates = 144 99 13:53:55.204 [planetinfo.record]: government = 7; 13:53:55.204 [planetinfo.record]: economy = 3; 13:53:55.204 [planetinfo.record]: techlevel = 8; 13:53:55.204 [planetinfo.record]: population = 43; 13:53:55.204 [planetinfo.record]: productivity = 26488; 13:53:55.204 [planetinfo.record]: name = "Isata"; 13:53:55.204 [planetinfo.record]: inhabitant = "Red Bony Bird"; 13:53:55.204 [planetinfo.record]: inhabitants = "Red Bony Birds"; 13:53:55.204 [planetinfo.record]: description = "This world is very well known for Isataian shrew cutlet and the Isataian tree grub."; 13:53:55.206 [planetinfo.record]: air_color = 0.631448, 0.872824, 0.797213, 1; 13:53:55.206 [planetinfo.record]: cloud_alpha = 1.000000; 13:53:55.206 [planetinfo.record]: cloud_color = 0.621094, 0.488016, 0.477951, 1; 13:53:55.206 [planetinfo.record]: cloud_fraction = 0.200000; 13:53:55.206 [planetinfo.record]: land_color = 0.413109, 0.35038, 0.537109, 1; 13:53:55.206 [planetinfo.record]: land_fraction = 0.380000; 13:53:55.206 [planetinfo.record]: polar_cloud_color = 0.779492, 0.675107, 0.667212, 1; 13:53:55.206 [planetinfo.record]: polar_land_color = 0.891673, 0.864043, 0.946289, 1; 13:53:55.206 [planetinfo.record]: polar_sea_color = 0.923047, 0.857434, 0.813976, 1; 13:53:55.206 [planetinfo.record]: sea_color = 0.769531, 0.550729, 0.405807, 1; 13:53:55.206 [planetinfo.record]: rotation_speed = 0.001964 13:53:55.206 [planetinfo.record]: planet zpos = 880320.000000 13:53:55.206 [planetinfo.record]: sun_radius = 156346.069336 13:53:55.206 [planetinfo.record]: sun_vector = -0.118 -0.723 -0.681 13:53:55.206 [planetinfo.record]: sun_distance = 1194720 13:53:55.206 [planetinfo.record]: corona_flare = 0.009735 13:53:55.206 [planetinfo.record]: corona_hues = 0.881485 13:53:55.207 [planetinfo.record]: sun_color = 0.773084, 0.458771, 0.245045, 1 13:53:55.207 [planetinfo.record]: corona_shimmer = 0.340129 13:53:55.207 [planetinfo.record]: station_vector = -0.190 0.257 0.948 13:53:55.207 [planetinfo.record]: station = coriolis 13:53:59.719 [PLANETINFO OVER]: Done 13:53:59.719 [PLANETINFO LOGGING]: 5 184 13:54:00.730 [planetinfo.record]: seed = 217 111 202 161 13:54:00.730 [planetinfo.record]: coordinates = 111 0 13:54:00.730 [planetinfo.record]: government = 3; 13:54:00.730 [planetinfo.record]: economy = 0; 13:54:00.730 [planetinfo.record]: techlevel = 12; 13:54:00.730 [planetinfo.record]: population = 52; 13:54:00.730 [planetinfo.record]: productivity = 29120; 13:54:00.730 [planetinfo.record]: name = "Leatge"; 13:54:00.730 [planetinfo.record]: inhabitant = "Large Harmless Rodent"; 13:54:00.730 [planetinfo.record]: inhabitants = "Large Harmless Rodents"; 13:54:00.730 [planetinfo.record]: description = "Leatge is ravaged by dreadful civil war."; 13:54:00.756 [planetinfo.record]: air_color = 0.785885, 0.626238, 0.873475, 1; 13:54:00.756 [planetinfo.record]: cloud_alpha = 1.000000; 13:54:00.756 [planetinfo.record]: cloud_color = 0.623047, 0.467285, 0.474586, 1; 13:54:00.756 [planetinfo.record]: cloud_fraction = 0.360000; 13:54:00.756 [planetinfo.record]: land_color = 0.66, 0.657422, 0.657784, 1; 13:54:00.756 [planetinfo.record]: land_fraction = 0.410000; 13:54:00.756 [planetinfo.record]: polar_cloud_color = 0.780371, 0.658438, 0.664154, 1; 13:54:00.756 [planetinfo.record]: polar_land_color = 0.934, 0.933088, 0.933216, 1; 13:54:00.756 [planetinfo.record]: polar_sea_color = 0.938281, 0.885444, 0.876798, 1; 13:54:00.756 [planetinfo.record]: sea_color = 0.617188, 0.478166, 0.455417, 1; 13:54:00.756 [planetinfo.record]: rotation_speed = 0.002238 13:54:00.756 [planetinfo.record]: planet zpos = 381960.000000 13:54:00.756 [planetinfo.record]: sun_radius = 85456.186981 13:54:00.756 [planetinfo.record]: sun_vector = -0.996 0.033 0.078 13:54:00.756 [planetinfo.record]: sun_distance = 604770 13:54:00.756 [planetinfo.record]: corona_flare = 0.092204 13:54:00.756 [planetinfo.record]: corona_hues = 0.942429 13:54:00.756 [planetinfo.record]: sun_color = 0.317447, 0.366024, 0.810812, 1 13:54:00.757 [planetinfo.record]: corona_shimmer = 0.429626 13:54:00.757 [planetinfo.record]: station_vector = -0.919 -0.199 0.340 13:54:00.757 [planetinfo.record]: station = dodecahedron 13:54:06.241 [PLANETINFO OVER]: Done 13:54:06.242 [PLANETINFO LOGGING]: 5 185 13:54:07.244 [planetinfo.record]: seed = 5 215 64 12 13:54:07.244 [planetinfo.record]: coordinates = 215 35 13:54:07.244 [planetinfo.record]: government = 0; 13:54:07.244 [planetinfo.record]: economy = 3; 13:54:07.244 [planetinfo.record]: techlevel = 7; 13:54:07.245 [planetinfo.record]: population = 32; 13:54:07.245 [planetinfo.record]: productivity = 7168; 13:54:07.245 [planetinfo.record]: name = "Inbieste"; 13:54:07.245 [planetinfo.record]: inhabitant = "Human Colonial"; 13:54:07.245 [planetinfo.record]: inhabitants = "Human Colonials"; 13:54:07.245 [planetinfo.record]: description = "This world is reasonably notable for its exotic night life but ravaged by occasional solar activity."; 13:54:07.260 [planetinfo.record]: air_color = 0.521774, 0.98274, 0.910296, 1; 13:54:07.260 [planetinfo.record]: cloud_alpha = 1.000000; 13:54:07.260 [planetinfo.record]: cloud_color = 0.951172, 0.596659, 0.18206, 1; 13:54:07.260 [planetinfo.record]: cloud_fraction = 0.360000; 13:54:07.260 [planetinfo.record]: land_color = 0.33, 0.66, 0.482109, 1; 13:54:07.260 [planetinfo.record]: land_fraction = 0.590000; 13:54:07.260 [planetinfo.record]: polar_cloud_color = 0.928027, 0.711848, 0.459029, 1; 13:54:07.260 [planetinfo.record]: polar_land_color = 0.81725, 0.934, 0.871064, 1; 13:54:07.260 [planetinfo.record]: polar_sea_color = 0.929566, 0.933984, 0.877435, 1; 13:54:07.260 [planetinfo.record]: sea_color = 0.647666, 0.660156, 0.500275, 1; 13:54:07.260 [planetinfo.record]: rotation_speed = 0.002733 13:54:07.260 [planetinfo.record]: planet zpos = 671330.000000 13:54:07.260 [planetinfo.record]: sun_radius = 156023.396149 13:54:07.260 [planetinfo.record]: sun_vector = 0.349 -0.848 -0.398 13:54:07.260 [planetinfo.record]: sun_distance = 1098540 13:54:07.260 [planetinfo.record]: corona_flare = 0.088170 13:54:07.260 [planetinfo.record]: corona_hues = 0.914314 13:54:07.260 [planetinfo.record]: sun_color = 0.338631, 0.715729, 0.749887, 1 13:54:07.260 [planetinfo.record]: corona_shimmer = 0.542131 13:54:07.261 [planetinfo.record]: station_vector = 0.816 -0.494 0.301 13:54:07.261 [planetinfo.record]: station = coriolis 13:54:12.530 [PLANETINFO OVER]: Done 13:54:12.531 [PLANETINFO LOGGING]: 5 186 13:54:13.543 [planetinfo.record]: seed = 237 252 154 237 13:54:13.543 [planetinfo.record]: coordinates = 252 233 13:54:13.543 [planetinfo.record]: government = 5; 13:54:13.543 [planetinfo.record]: economy = 1; 13:54:13.543 [planetinfo.record]: techlevel = 9; 13:54:13.543 [planetinfo.record]: population = 43; 13:54:13.543 [planetinfo.record]: productivity = 27864; 13:54:13.543 [planetinfo.record]: name = "Diraonle"; 13:54:13.543 [planetinfo.record]: inhabitant = "Furry Feline"; 13:54:13.543 [planetinfo.record]: inhabitants = "Furry Felines"; 13:54:13.543 [planetinfo.record]: description = "This world is a revolting little planet."; 13:54:13.551 [planetinfo.record]: air_color = 0.564632, 0.959977, 0.925615, 1; 13:54:13.551 [planetinfo.record]: cloud_alpha = 1.000000; 13:54:13.551 [planetinfo.record]: cloud_color = 0.882812, 0.742287, 0.320709, 1; 13:54:13.551 [planetinfo.record]: cloud_fraction = 0.320000; 13:54:13.551 [planetinfo.record]: land_color = 0.524531, 0.388641, 0.552734, 1; 13:54:13.551 [planetinfo.record]: land_fraction = 0.660000; 13:54:13.552 [planetinfo.record]: polar_cloud_color = 0.897266, 0.807999, 0.540199, 1; 13:54:13.552 [planetinfo.record]: polar_land_color = 0.932675, 0.87461, 0.944727, 1; 13:54:13.552 [planetinfo.record]: polar_sea_color = 0.924414, 0.884792, 0.828723, 1; 13:54:13.552 [planetinfo.record]: sea_color = 0.755859, 0.626269, 0.442886, 1; 13:54:13.552 [planetinfo.record]: rotation_speed = 0.002451 13:54:13.552 [planetinfo.record]: planet zpos = 767520.000000 13:54:13.552 [planetinfo.record]: sun_radius = 149533.436279 13:54:13.552 [planetinfo.record]: sun_vector = -0.997 -0.079 -0.001 13:54:13.552 [planetinfo.record]: sun_distance = 1215240 13:54:13.552 [planetinfo.record]: corona_flare = 0.099249 13:54:13.552 [planetinfo.record]: corona_hues = 0.596893 13:54:13.552 [planetinfo.record]: sun_color = 0.819745, 0.539555, 0.233943, 1 13:54:13.552 [planetinfo.record]: corona_shimmer = 0.762188 13:54:13.552 [planetinfo.record]: station_vector = -0.453 -0.791 0.411 13:54:13.552 [planetinfo.record]: station = coriolis 13:54:18.000 [PLANETINFO OVER]: Done 13:54:18.003 [PLANETINFO LOGGING]: 5 187 13:54:19.004 [planetinfo.record]: seed = 33 129 168 20 13:54:19.005 [planetinfo.record]: coordinates = 129 191 13:54:19.005 [planetinfo.record]: government = 4; 13:54:19.005 [planetinfo.record]: economy = 7; 13:54:19.005 [planetinfo.record]: techlevel = 3; 13:54:19.005 [planetinfo.record]: population = 24; 13:54:19.005 [planetinfo.record]: productivity = 4608; 13:54:19.005 [planetinfo.record]: name = "Raraar"; 13:54:19.005 [planetinfo.record]: inhabitant = "Green Feline"; 13:54:19.005 [planetinfo.record]: inhabitants = "Green Felines"; 13:54:19.005 [planetinfo.record]: description = "The world Raraar is reasonably famous for its inhabitants’ exceptional loathing of casinos."; 13:54:19.028 [planetinfo.record]: air_color = 0.666943, 0.550888, 0.89884, 1; 13:54:19.028 [planetinfo.record]: cloud_alpha = 1.000000; 13:54:19.028 [planetinfo.record]: cloud_color = 0.699219, 0.305908, 0.693073, 1; 13:54:19.028 [planetinfo.record]: cloud_fraction = 0.400000; 13:54:19.028 [planetinfo.record]: land_color = 0.510469, 0.66, 0.572384, 1; 13:54:19.028 [planetinfo.record]: land_fraction = 0.290000; 13:54:19.028 [planetinfo.record]: polar_cloud_color = 0.814648, 0.528249, 0.810173, 1; 13:54:19.028 [planetinfo.record]: polar_land_color = 0.881098, 0.934, 0.903003, 1; 13:54:19.028 [planetinfo.record]: polar_sea_color = 0.895917, 0.923828, 0.858871, 1; 13:54:19.028 [planetinfo.record]: sea_color = 0.669665, 0.761719, 0.547485, 1; 13:54:19.028 [planetinfo.record]: rotation_speed = 0.001005 13:54:19.028 [planetinfo.record]: planet zpos = 555660.000000 13:54:19.028 [planetinfo.record]: sun_radius = 103978.521881 13:54:19.028 [planetinfo.record]: sun_vector = -0.714 -0.063 0.698 13:54:19.028 [planetinfo.record]: sun_distance = 754110 13:54:19.028 [planetinfo.record]: corona_flare = 0.046490 13:54:19.029 [planetinfo.record]: corona_hues = 0.935570 13:54:19.029 [planetinfo.record]: sun_color = 0.314523, 0.716433, 0.75994, 1 13:54:19.029 [planetinfo.record]: corona_shimmer = 0.269438 13:54:19.029 [planetinfo.record]: station_vector = -0.888 -0.007 0.460 13:54:19.029 [planetinfo.record]: station = coriolis 13:54:23.270 [PLANETINFO OVER]: Done 13:54:23.272 [PLANETINFO LOGGING]: 5 188 13:54:24.272 [planetinfo.record]: seed = 17 84 122 147 13:54:24.272 [planetinfo.record]: coordinates = 84 234 13:54:24.272 [planetinfo.record]: government = 2; 13:54:24.272 [planetinfo.record]: economy = 2; 13:54:24.272 [planetinfo.record]: techlevel = 6; 13:54:24.272 [planetinfo.record]: population = 29; 13:54:24.272 [planetinfo.record]: productivity = 11136; 13:54:24.273 [planetinfo.record]: name = "Beenor"; 13:54:24.273 [planetinfo.record]: inhabitant = "Human Colonial"; 13:54:24.273 [planetinfo.record]: inhabitants = "Human Colonials"; 13:54:24.273 [planetinfo.record]: description = "This world is very notable for its inhabitants’ weird silliness and Beenorian Itusmaal brandy."; 13:54:24.300 [planetinfo.record]: air_color = 0.640593, 0.769685, 0.990545, 1; 13:54:24.300 [planetinfo.record]: cloud_alpha = 1.000000; 13:54:24.300 [planetinfo.record]: cloud_color = 0.525375, 0.974609, 0.932494, 1; 13:54:24.300 [planetinfo.record]: cloud_fraction = 0.200000; 13:54:24.300 [planetinfo.record]: land_color = 0.208679, 0.513672, 0.392151, 1; 13:54:24.300 [planetinfo.record]: land_fraction = 0.630000; 13:54:24.300 [planetinfo.record]: polar_cloud_color = 0.668184, 0.938574, 0.913225, 1; 13:54:24.300 [planetinfo.record]: polar_land_color = 0.80782, 0.948633, 0.892528, 1; 13:54:24.300 [planetinfo.record]: polar_sea_color = 0.944727, 0.884205, 0.913993, 1; 13:54:24.300 [planetinfo.record]: sea_color = 0.552734, 0.411096, 0.480809, 1; 13:54:24.300 [planetinfo.record]: rotation_speed = 0.000041 13:54:24.300 [planetinfo.record]: planet zpos = 440160.000000 13:54:24.300 [planetinfo.record]: sun_radius = 100594.631348 13:54:24.300 [planetinfo.record]: sun_vector = 0.061 0.053 -0.997 13:54:24.300 [planetinfo.record]: sun_distance = 660240 13:54:24.300 [planetinfo.record]: corona_flare = 0.027654 13:54:24.300 [planetinfo.record]: corona_hues = 0.923119 13:54:24.300 [planetinfo.record]: sun_color = 0.739636, 0.526294, 0.514786, 1 13:54:24.300 [planetinfo.record]: corona_shimmer = 0.234275 13:54:24.301 [planetinfo.record]: station_vector = -0.588 -0.809 0.003 13:54:24.301 [planetinfo.record]: station = coriolis 13:54:28.821 [PLANETINFO OVER]: Done 13:54:28.822 [PLANETINFO LOGGING]: 5 189 13:54:29.826 [planetinfo.record]: seed = 77 31 32 171 13:54:29.826 [planetinfo.record]: coordinates = 31 185 13:54:29.826 [planetinfo.record]: government = 1; 13:54:29.826 [planetinfo.record]: economy = 3; 13:54:29.826 [planetinfo.record]: techlevel = 8; 13:54:29.826 [planetinfo.record]: population = 37; 13:54:29.826 [planetinfo.record]: productivity = 10360; 13:54:29.826 [planetinfo.record]: name = "Mazare"; 13:54:29.826 [planetinfo.record]: inhabitant = "Human Colonial"; 13:54:29.826 [planetinfo.record]: inhabitants = "Human Colonials"; 13:54:29.826 [planetinfo.record]: description = "This planet is a tedious little planet."; 13:54:29.837 [planetinfo.record]: air_color = 0.71475, 0.810847, 0.912498, 1; 13:54:29.838 [planetinfo.record]: cloud_alpha = 1.000000; 13:54:29.838 [planetinfo.record]: cloud_color = 0.702644, 0.740234, 0.723495, 1; 13:54:29.838 [planetinfo.record]: cloud_fraction = 0.460000; 13:54:29.838 [planetinfo.record]: land_color = 0.0515347, 0.640625, 0.0325317, 1; 13:54:29.838 [planetinfo.record]: land_fraction = 0.340000; 13:54:29.838 [planetinfo.record]: polar_cloud_color = 0.806664, 0.833105, 0.821331, 1; 13:54:29.838 [planetinfo.record]: polar_land_color = 0.720776, 0.935938, 0.713835, 1; 13:54:29.838 [planetinfo.record]: polar_sea_color = 0.906959, 0.935547, 0.887399, 1; 13:54:29.838 [planetinfo.record]: sea_color = 0.565751, 0.644531, 0.511848, 1; 13:54:29.838 [planetinfo.record]: rotation_speed = 0.001840 13:54:29.838 [planetinfo.record]: planet zpos = 622930.000000 13:54:29.838 [planetinfo.record]: sun_radius = 171294.170990 13:54:29.838 [planetinfo.record]: sun_vector = -0.064 -0.287 -0.956 13:54:29.838 [planetinfo.record]: sun_distance = 1132600 13:54:29.838 [planetinfo.record]: corona_flare = 0.037672 13:54:29.838 [planetinfo.record]: corona_hues = 0.751518 13:54:29.838 [planetinfo.record]: sun_color = 0.83075, 0.445192, 0.342575, 1 13:54:29.838 [planetinfo.record]: corona_shimmer = 0.479836 13:54:29.839 [planetinfo.record]: station_vector = 0.575 -0.564 0.593 13:54:29.839 [planetinfo.record]: station = coriolis 13:54:34.651 [PLANETINFO OVER]: Done 13:54:34.652 [PLANETINFO LOGGING]: 5 190 13:54:35.657 [planetinfo.record]: seed = 197 125 106 80 13:54:35.657 [planetinfo.record]: coordinates = 125 78 13:54:35.657 [planetinfo.record]: government = 0; 13:54:35.657 [planetinfo.record]: economy = 6; 13:54:35.657 [planetinfo.record]: techlevel = 2; 13:54:35.658 [planetinfo.record]: population = 15; 13:54:35.658 [planetinfo.record]: productivity = 1920; 13:54:35.658 [planetinfo.record]: name = "Ertear"; 13:54:35.658 [planetinfo.record]: inhabitant = "Human Colonial"; 13:54:35.658 [planetinfo.record]: inhabitants = "Human Colonials"; 13:54:35.658 [planetinfo.record]: description = "This world is very notable for its inhabitants’ weird silliness and Ertearian Le water."; 13:54:35.661 [planetinfo.record]: air_color = 0.646921, 0.801895, 0.971033, 1; 13:54:35.661 [planetinfo.record]: cloud_alpha = 1.000000; 13:54:35.661 [planetinfo.record]: cloud_color = 0.547462, 0.916016, 0.760532, 1; 13:54:35.661 [planetinfo.record]: cloud_fraction = 0.370000; 13:54:35.661 [planetinfo.record]: land_color = 0.653414, 0.66, 0.652266, 1; 13:54:35.661 [planetinfo.record]: land_fraction = 0.370000; 13:54:35.661 [planetinfo.record]: polar_cloud_color = 0.682819, 0.912207, 0.815434, 1; 13:54:35.662 [planetinfo.record]: polar_land_color = 0.93167, 0.934, 0.931264, 1; 13:54:35.662 [planetinfo.record]: polar_sea_color = 0.790869, 0.949414, 0.797063, 1; 13:54:35.662 [planetinfo.record]: sea_color = 0.167961, 0.505859, 0.18116, 1; 13:54:35.662 [planetinfo.record]: rotation_speed = 0.000172 13:54:35.662 [planetinfo.record]: planet zpos = 352920.000000 13:54:35.662 [planetinfo.record]: sun_radius = 86427.775879 13:54:35.662 [planetinfo.record]: sun_vector = 0.137 -0.899 -0.416 13:54:35.662 [planetinfo.record]: sun_distance = 529380 13:54:35.662 [planetinfo.record]: corona_flare = 0.023190 13:54:35.662 [planetinfo.record]: corona_hues = 0.628677 13:54:35.662 [planetinfo.record]: sun_color = 0.654902, 0.678961, 0.698969, 1 13:54:35.662 [planetinfo.record]: corona_shimmer = 0.777617 13:54:35.662 [planetinfo.record]: station_vector = -0.472 -0.821 0.322 13:54:35.662 [planetinfo.record]: station = coriolis 13:54:40.005 [PLANETINFO OVER]: Done 13:54:40.006 [PLANETINFO LOGGING]: 5 191 13:54:41.008 [planetinfo.record]: seed = 9 88 168 95 13:54:41.008 [planetinfo.record]: coordinates = 88 234 13:54:41.008 [planetinfo.record]: government = 1; 13:54:41.008 [planetinfo.record]: economy = 2; 13:54:41.008 [planetinfo.record]: techlevel = 6; 13:54:41.008 [planetinfo.record]: population = 28; 13:54:41.008 [planetinfo.record]: productivity = 8960; 13:54:41.008 [planetinfo.record]: name = "Onxequte"; 13:54:41.009 [planetinfo.record]: inhabitant = "Yellow Horned Humanoid"; 13:54:41.009 [planetinfo.record]: inhabitants = "Yellow Horned Humanoids"; 13:54:41.009 [planetinfo.record]: description = "This planet is reasonably noted for its fabulous monkey steak."; 13:54:41.013 [planetinfo.record]: air_color = 0.657108, 0.602923, 0.836402, 1; 13:54:41.013 [planetinfo.record]: cloud_alpha = 1.000000; 13:54:41.013 [planetinfo.record]: cloud_color = 0.476472, 0.389786, 0.511719, 1; 13:54:41.013 [planetinfo.record]: cloud_fraction = 0.490000; 13:54:41.013 [planetinfo.record]: land_color = 0.523359, 0.66, 0.60876, 1; 13:54:41.013 [planetinfo.record]: land_fraction = 0.630000; 13:54:41.013 [planetinfo.record]: polar_cloud_color = 0.698836, 0.621517, 0.730273, 1; 13:54:41.013 [planetinfo.record]: polar_land_color = 0.885658, 0.934, 0.915872, 1; 13:54:41.013 [planetinfo.record]: polar_sea_color = 0.846903, 0.891495, 0.948828, 1; 13:54:41.013 [planetinfo.record]: sea_color = 0.29184, 0.388037, 0.511719, 1; 13:54:41.013 [planetinfo.record]: rotation_speed = 0.000096 13:54:41.013 [planetinfo.record]: planet zpos = 741840.000000 13:54:41.014 [planetinfo.record]: sun_radius = 164164.782715 13:54:41.014 [planetinfo.record]: sun_vector = -0.429 -0.903 0.014 13:54:41.014 [planetinfo.record]: sun_distance = 1348800 13:54:41.014 [planetinfo.record]: corona_flare = 0.072679 13:54:41.014 [planetinfo.record]: corona_hues = 0.946541 13:54:41.014 [planetinfo.record]: sun_color = 0.309061, 0.333044, 0.670322, 1 13:54:41.014 [planetinfo.record]: corona_shimmer = 0.402595 13:54:41.014 [planetinfo.record]: station_vector = -0.132 -0.137 0.982 13:54:41.014 [planetinfo.record]: station = coriolis 13:54:45.419 [PLANETINFO OVER]: Done 13:54:45.420 [PLANETINFO LOGGING]: 5 192 13:54:46.430 [planetinfo.record]: seed = 137 92 106 89 13:54:46.430 [planetinfo.record]: coordinates = 92 90 13:54:46.430 [planetinfo.record]: government = 1; 13:54:46.430 [planetinfo.record]: economy = 2; 13:54:46.430 [planetinfo.record]: techlevel = 6; 13:54:46.430 [planetinfo.record]: population = 28; 13:54:46.431 [planetinfo.record]: productivity = 8960; 13:54:46.431 [planetinfo.record]: name = "Orerbia"; 13:54:46.431 [planetinfo.record]: inhabitant = "Human Colonial"; 13:54:46.431 [planetinfo.record]: inhabitants = "Human Colonials"; 13:54:46.431 [planetinfo.record]: description = "This planet is reasonably noted for its fabulous goat burgers."; 13:54:46.443 [planetinfo.record]: air_color = 0.467628, 0.723663, 0.843557, 1; 13:54:46.443 [planetinfo.record]: cloud_alpha = 1.000000; 13:54:46.443 [planetinfo.record]: cloud_color = 0.151851, 0.533203, 0.139549, 1; 13:54:46.443 [planetinfo.record]: cloud_fraction = 0.240000; 13:54:46.443 [planetinfo.record]: land_color = 0.582031, 0.477448, 0.489703, 1; 13:54:46.443 [planetinfo.record]: land_fraction = 0.610000; 13:54:46.443 [planetinfo.record]: polar_cloud_color = 0.409183, 0.739941, 0.398513, 1; 13:54:46.443 [planetinfo.record]: polar_land_color = 0.941797, 0.89949, 0.904447, 1; 13:54:46.443 [planetinfo.record]: polar_sea_color = 0.906307, 0.913281, 0.785743, 1; 13:54:46.443 [planetinfo.record]: sea_color = 0.840697, 0.867188, 0.382782, 1; 13:54:46.443 [planetinfo.record]: rotation_speed = 0.002828 13:54:46.443 [planetinfo.record]: planet zpos = 677560.000000 13:54:46.443 [planetinfo.record]: sun_radius = 133538.413086 13:54:46.443 [planetinfo.record]: sun_vector = 0.236 -0.747 -0.622 13:54:46.443 [planetinfo.record]: sun_distance = 990280 13:54:46.443 [planetinfo.record]: corona_flare = 0.038826 13:54:46.443 [planetinfo.record]: corona_hues = 0.510025 13:54:46.443 [planetinfo.record]: sun_color = 0.550677, 0.691411, 0.81604, 1 13:54:46.444 [planetinfo.record]: corona_shimmer = 0.369121 13:54:46.444 [planetinfo.record]: station_vector = 0.692 0.013 0.722 13:54:46.444 [planetinfo.record]: station = coriolis 13:54:51.979 [PLANETINFO OVER]: Done 13:54:51.980 [PLANETINFO LOGGING]: 5 193 13:54:52.985 [planetinfo.record]: seed = 213 47 64 6 13:54:52.985 [planetinfo.record]: coordinates = 47 198 13:54:52.985 [planetinfo.record]: government = 2; 13:54:52.985 [planetinfo.record]: economy = 6; 13:54:52.985 [planetinfo.record]: techlevel = 5; 13:54:52.985 [planetinfo.record]: population = 29; 13:54:52.985 [planetinfo.record]: productivity = 5568; 13:54:52.985 [planetinfo.record]: name = "Biteen"; 13:54:52.985 [planetinfo.record]: inhabitant = "Human Colonial"; 13:54:52.985 [planetinfo.record]: inhabitants = "Human Colonials"; 13:54:52.985 [planetinfo.record]: description = "The planet Biteen is reasonably noted for mud tennis and its unusual sit coms."; 13:54:53.004 [planetinfo.record]: air_color = 0.672479, 0.498744, 0.946318, 1; 13:54:53.004 [planetinfo.record]: cloud_alpha = 1.000000; 13:54:53.004 [planetinfo.record]: cloud_color = 0.841797, 0.15126, 0.717716, 1; 13:54:53.004 [planetinfo.record]: cloud_fraction = 0.220000; 13:54:53.004 [planetinfo.record]: land_color = 0.160677, 0.511719, 0.157913, 1; 13:54:53.004 [planetinfo.record]: land_fraction = 0.290000; 13:54:53.004 [planetinfo.record]: polar_cloud_color = 0.878809, 0.428248, 0.797848, 1; 13:54:53.004 [planetinfo.record]: polar_land_color = 0.786103, 0.948828, 0.784822, 1; 13:54:53.004 [planetinfo.record]: polar_sea_color = 0.836775, 0.900991, 0.947852, 1; 13:54:53.004 [planetinfo.record]: sea_color = 0.277039, 0.418359, 0.521484, 1; 13:54:53.004 [planetinfo.record]: rotation_speed = 0.000829 13:54:53.005 [planetinfo.record]: planet zpos = 483890.000000 13:54:53.005 [planetinfo.record]: sun_radius = 103568.741455 13:54:53.005 [planetinfo.record]: sun_vector = -0.133 0.871 -0.473 13:54:53.005 [planetinfo.record]: sun_distance = 703840 13:54:53.005 [planetinfo.record]: corona_flare = 0.001628 13:54:53.005 [planetinfo.record]: corona_hues = 0.702240 13:54:53.005 [planetinfo.record]: sun_color = 0.308248, 0.652153, 0.697073, 1 13:54:53.005 [planetinfo.record]: corona_shimmer = 0.290471 13:54:53.005 [planetinfo.record]: station_vector = 0.059 0.925 0.375 13:54:53.005 [planetinfo.record]: station = coriolis 13:54:57.478 [PLANETINFO OVER]: Done 13:54:57.479 [PLANETINFO LOGGING]: 5 194 13:54:58.482 [planetinfo.record]: seed = 221 52 122 99 13:54:58.483 [planetinfo.record]: coordinates = 52 50 13:54:58.483 [planetinfo.record]: government = 3; 13:54:58.483 [planetinfo.record]: economy = 2; 13:54:58.483 [planetinfo.record]: techlevel = 7; 13:54:58.483 [planetinfo.record]: population = 34; 13:54:58.483 [planetinfo.record]: productivity = 15232; 13:54:58.483 [planetinfo.record]: name = "Geargeat"; 13:54:58.483 [planetinfo.record]: inhabitant = "Human Colonial"; 13:54:58.483 [planetinfo.record]: inhabitants = "Human Colonials"; 13:54:58.483 [planetinfo.record]: description = "The world Geargeat is a boring planet."; 13:54:58.504 [planetinfo.record]: air_color = 0.663302, 0.541519, 0.906645, 1; 13:54:58.504 [planetinfo.record]: cloud_alpha = 1.000000; 13:54:58.504 [planetinfo.record]: cloud_color = 0.722656, 0.282288, 0.715775, 1; 13:54:58.504 [planetinfo.record]: cloud_fraction = 0.470000; 13:54:58.504 [planetinfo.record]: land_color = 0.577581, 0.574922, 0.66, 1; 13:54:58.504 [planetinfo.record]: land_fraction = 0.320000; 13:54:58.504 [planetinfo.record]: polar_cloud_color = 0.825195, 0.510912, 0.820285, 1; 13:54:58.504 [planetinfo.record]: polar_land_color = 0.904841, 0.9039, 0.934, 1; 13:54:58.504 [planetinfo.record]: polar_sea_color = 0.933008, 0.900577, 0.871961, 1; 13:54:58.504 [planetinfo.record]: sea_color = 0.669922, 0.576777, 0.494591, 1; 13:54:58.504 [planetinfo.record]: rotation_speed = 0.002982 13:54:58.504 [planetinfo.record]: planet zpos = 472680.000000 13:54:58.504 [planetinfo.record]: sun_radius = 113687.138672 13:54:58.504 [planetinfo.record]: sun_vector = 0.608 0.361 0.707 13:54:58.504 [planetinfo.record]: sun_distance = 690840 13:54:58.504 [planetinfo.record]: corona_flare = 0.008731 13:54:58.504 [planetinfo.record]: corona_hues = 0.510033 13:54:58.504 [planetinfo.record]: sun_color = 0.293921, 0.313069, 0.74978, 1 13:54:58.504 [planetinfo.record]: corona_shimmer = 0.408085 13:54:58.505 [planetinfo.record]: station_vector = 0.916 0.098 0.390 13:54:58.505 [planetinfo.record]: station = coriolis 13:55:03.510 [PLANETINFO OVER]: Done 13:55:03.511 [PLANETINFO LOGGING]: 5 195 13:55:04.514 [planetinfo.record]: seed = 49 145 232 190 13:55:04.514 [planetinfo.record]: coordinates = 145 99 13:55:04.515 [planetinfo.record]: government = 6; 13:55:04.515 [planetinfo.record]: economy = 3; 13:55:04.515 [planetinfo.record]: techlevel = 8; 13:55:04.515 [planetinfo.record]: population = 42; 13:55:04.515 [planetinfo.record]: productivity = 23520; 13:55:04.515 [planetinfo.record]: name = "Ribege"; 13:55:04.515 [planetinfo.record]: inhabitant = "Harmless Horned Bird"; 13:55:04.515 [planetinfo.record]: inhabitants = "Harmless Horned Birds"; 13:55:04.515 [planetinfo.record]: description = "The planet Ribege is an unremarkable planet."; 13:55:04.526 [planetinfo.record]: air_color = 0.470811, 0.654352, 0.867621, 1; 13:55:04.527 [planetinfo.record]: cloud_alpha = 1.000000; 13:55:04.527 [planetinfo.record]: cloud_color = 0.137177, 0.605469, 0.429859, 1; 13:55:04.527 [planetinfo.record]: cloud_fraction = 0.150000; 13:55:04.527 [planetinfo.record]: land_color = 0.66, 0.394131, 0.350625, 1; 13:55:04.527 [planetinfo.record]: land_fraction = 0.560000; 13:55:04.527 [planetinfo.record]: polar_cloud_color = 0.399055, 0.772461, 0.632434, 1; 13:55:04.527 [planetinfo.record]: polar_land_color = 0.934, 0.839939, 0.824547, 1; 13:55:04.527 [planetinfo.record]: polar_sea_color = 0.929102, 0.823852, 0.84852, 1; 13:55:04.527 [planetinfo.record]: sea_color = 0.708984, 0.387726, 0.463021, 1; 13:55:04.527 [planetinfo.record]: rotation_speed = 0.004210 13:55:04.528 [planetinfo.record]: planet zpos = 850850.000000 13:55:04.528 [planetinfo.record]: sun_radius = 125830.661011 13:55:04.528 [planetinfo.record]: sun_vector = 0.511 0.768 0.387 13:55:04.528 [planetinfo.record]: sun_distance = 1243550 13:55:04.528 [planetinfo.record]: corona_flare = 0.049495 13:55:04.528 [planetinfo.record]: corona_hues = 0.792770 13:55:04.528 [planetinfo.record]: sun_color = 0.483306, 0.693151, 0.708075, 1 13:55:04.528 [planetinfo.record]: corona_shimmer = 0.386415 13:55:04.530 [planetinfo.record]: station_vector = 0.349 0.915 0.202 13:55:04.530 [planetinfo.record]: station = coriolis 13:55:09.713 [PLANETINFO OVER]: Done 13:55:09.714 [PLANETINFO LOGGING]: 5 196 13:55:10.718 [planetinfo.record]: seed = 65 117 154 43 13:55:10.718 [planetinfo.record]: coordinates = 117 3 13:55:10.718 [planetinfo.record]: government = 0; 13:55:10.718 [planetinfo.record]: economy = 3; 13:55:10.718 [planetinfo.record]: techlevel = 5; 13:55:10.718 [planetinfo.record]: population = 24; 13:55:10.718 [planetinfo.record]: productivity = 5376; 13:55:10.718 [planetinfo.record]: name = "Mazaza"; 13:55:10.718 [planetinfo.record]: inhabitant = "Small Red Frog"; 13:55:10.718 [planetinfo.record]: inhabitants = "Small Red Frogs"; 13:55:10.718 [planetinfo.record]: description = "This planet is reasonably noted for its inhabitants’ exceptional love for food blenders."; 13:55:10.735 [planetinfo.record]: air_color = 0.63128, 0.513537, 0.924205, 1; 13:55:10.735 [planetinfo.record]: cloud_alpha = 1.000000; 13:55:10.735 [planetinfo.record]: cloud_color = 0.704212, 0.205963, 0.775391, 1; 13:55:10.735 [planetinfo.record]: cloud_fraction = 0.290000; 13:55:10.735 [planetinfo.record]: land_color = 0.66, 0.62588, 0.348047, 1; 13:55:10.735 [planetinfo.record]: land_fraction = 0.410000; 13:55:10.735 [planetinfo.record]: polar_cloud_color = 0.80022, 0.459282, 0.848926, 1; 13:55:10.735 [planetinfo.record]: polar_land_color = 0.934, 0.921929, 0.823635, 1; 13:55:10.735 [planetinfo.record]: polar_sea_color = 0.936133, 0.928714, 0.880276, 1; 13:55:10.735 [planetinfo.record]: sea_color = 0.638672, 0.618427, 0.486239, 1; 13:55:10.735 [planetinfo.record]: rotation_speed = 0.000623 13:55:10.735 [planetinfo.record]: planet zpos = 574900.000000 13:55:10.736 [planetinfo.record]: sun_radius = 121328.497467 13:55:10.736 [planetinfo.record]: sun_vector = -0.319 0.224 0.921 13:55:10.736 [planetinfo.record]: sun_distance = 1034820 13:55:10.736 [planetinfo.record]: corona_flare = 0.088313 13:55:10.736 [planetinfo.record]: corona_hues = 0.883057 13:55:10.736 [planetinfo.record]: sun_color = 0.662451, 0.539825, 0.400985, 1 13:55:10.736 [planetinfo.record]: corona_shimmer = 0.391264 13:55:10.736 [planetinfo.record]: station_vector = -0.947 0.318 0.034 13:55:10.736 [planetinfo.record]: station = coriolis 13:55:16.141 [PLANETINFO OVER]: Done 13:55:16.142 [PLANETINFO LOGGING]: 5 197 13:55:17.146 [planetinfo.record]: seed = 157 20 160 253 13:55:17.146 [planetinfo.record]: coordinates = 20 68 13:55:17.146 [planetinfo.record]: government = 3; 13:55:17.146 [planetinfo.record]: economy = 4; 13:55:17.146 [planetinfo.record]: techlevel = 5; 13:55:17.146 [planetinfo.record]: population = 28; 13:55:17.146 [planetinfo.record]: productivity = 9408; 13:55:17.146 [planetinfo.record]: name = "Istiesri"; 13:55:17.146 [planetinfo.record]: inhabitant = "Slimy Frog"; 13:55:17.146 [planetinfo.record]: inhabitants = "Slimy Frogs"; 13:55:17.146 [planetinfo.record]: description = "Istiesri is mildly notable for its inhabitants’ unusual mating traditions."; 13:55:17.157 [planetinfo.record]: air_color = 0.651377, 0.857215, 0.797555, 1; 13:55:17.157 [planetinfo.record]: cloud_alpha = 1.000000; 13:55:17.157 [planetinfo.record]: cloud_color = 0.574219, 0.514463, 0.504684, 1; 13:55:17.157 [planetinfo.record]: cloud_fraction = 0.170000; 13:55:17.157 [planetinfo.record]: land_color = 0.519531, 0.423895, 0.0487061, 1; 13:55:17.157 [planetinfo.record]: land_fraction = 0.410000; 13:55:17.157 [planetinfo.record]: polar_cloud_color = 0.758398, 0.709072, 0.701, 1; 13:55:17.157 [planetinfo.record]: polar_land_color = 0.948047, 0.904417, 0.733255, 1; 13:55:17.157 [planetinfo.record]: polar_sea_color = 0.893199, 0.866133, 0.911719, 1; 13:55:17.158 [planetinfo.record]: sea_color = 0.811084, 0.70625, 0.882812, 1; 13:55:17.158 [planetinfo.record]: rotation_speed = 0.000001 13:55:17.158 [planetinfo.record]: planet zpos = 678040.000000 13:55:17.158 [planetinfo.record]: sun_radius = 133014.710693 13:55:17.158 [planetinfo.record]: sun_vector = 0.060 -0.283 -0.957 13:55:17.158 [planetinfo.record]: sun_distance = 1232800 13:55:17.158 [planetinfo.record]: corona_flare = 0.089275 13:55:17.158 [planetinfo.record]: corona_hues = 0.795578 13:55:17.158 [planetinfo.record]: sun_color = 0.382532, 0.470366, 0.838919, 1 13:55:17.158 [planetinfo.record]: corona_shimmer = 0.363794 13:55:17.158 [planetinfo.record]: station_vector = -0.902 0.345 0.258 13:55:17.158 [planetinfo.record]: station = coriolis 13:55:21.238 [PLANETINFO OVER]: Done 13:55:21.239 [PLANETINFO LOGGING]: 5 198 13:55:22.248 [planetinfo.record]: seed = 53 190 202 126 13:55:22.249 [planetinfo.record]: coordinates = 190 105 13:55:22.249 [planetinfo.record]: government = 6; 13:55:22.249 [planetinfo.record]: economy = 1; 13:55:22.249 [planetinfo.record]: techlevel = 11; 13:55:22.249 [planetinfo.record]: population = 52; 13:55:22.250 [planetinfo.record]: productivity = 37440; 13:55:22.250 [planetinfo.record]: name = "Ribigeus"; 13:55:22.250 [planetinfo.record]: inhabitant = "Blue Frog"; 13:55:22.250 [planetinfo.record]: inhabitants = "Blue Frogs"; 13:55:22.250 [planetinfo.record]: description = "The planet Ribigeus is a boring world."; 13:55:22.267 [planetinfo.record]: air_color = 0.781967, 0.47539, 0.987293, 1; 13:55:22.267 [planetinfo.record]: cloud_alpha = 1.000000; 13:55:22.267 [planetinfo.record]: cloud_color = 0.964844, 0.0414581, 0.214593, 1; 13:55:22.267 [planetinfo.record]: cloud_fraction = 0.330000; 13:55:22.267 [planetinfo.record]: land_color = 0.201416, 0.558901, 0.585938, 1; 13:55:22.267 [planetinfo.record]: land_fraction = 0.370000; 13:55:22.267 [planetinfo.record]: polar_cloud_color = 0.93418, 0.375405, 0.480175, 1; 13:55:22.267 [planetinfo.record]: polar_land_color = 0.786957, 0.930547, 0.941406, 1; 13:55:22.267 [planetinfo.record]: polar_sea_color = 0.89155, 0.929492, 0.874304, 1; 13:55:22.267 [planetinfo.record]: sea_color = 0.589952, 0.705078, 0.537622, 1; 13:55:22.268 [planetinfo.record]: rotation_speed = 0.000829 13:55:22.268 [planetinfo.record]: planet zpos = 790800.000000 13:55:22.268 [planetinfo.record]: sun_radius = 192802.951050 13:55:22.268 [planetinfo.record]: sun_vector = -0.144 0.938 0.315 13:55:22.269 [planetinfo.record]: sun_distance = 1186200 13:55:22.269 [planetinfo.record]: corona_flare = 0.076323 13:55:22.269 [planetinfo.record]: corona_hues = 0.512077 13:55:22.269 [planetinfo.record]: sun_color = 0.415733, 0.56057, 0.783316, 1 13:55:22.270 [planetinfo.record]: corona_shimmer = 0.384209 13:55:22.271 [planetinfo.record]: station_vector = -0.208 0.080 0.975 13:55:22.271 [planetinfo.record]: station = dodecahedron 13:55:27.139 [PLANETINFO OVER]: Done 13:55:27.140 [PLANETINFO LOGGING]: 5 199 13:55:28.143 [planetinfo.record]: seed = 153 8 104 146 13:55:28.143 [planetinfo.record]: coordinates = 8 227 13:55:28.143 [planetinfo.record]: government = 3; 13:55:28.143 [planetinfo.record]: economy = 3; 13:55:28.143 [planetinfo.record]: techlevel = 6; 13:55:28.143 [planetinfo.record]: population = 31; 13:55:28.143 [planetinfo.record]: productivity = 12152; 13:55:28.143 [planetinfo.record]: name = "Enriorar"; 13:55:28.143 [planetinfo.record]: inhabitant = "Human Colonial"; 13:55:28.143 [planetinfo.record]: inhabitants = "Human Colonials"; 13:55:28.143 [planetinfo.record]: description = "This planet is mildly noted for its pink Bi plant plantations but plagued by lethal disease."; 13:55:28.183 [planetinfo.record]: air_color = 0.587108, 0.513421, 0.911197, 1; 13:55:28.183 [planetinfo.record]: cloud_alpha = 1.000000; 13:55:28.183 [planetinfo.record]: cloud_color = 0.511394, 0.212845, 0.736328, 1; 13:55:28.183 [planetinfo.record]: cloud_fraction = 0.630000; 13:55:28.183 [planetinfo.record]: land_color = 0.66, 0.644531, 0.648519, 1; 13:55:28.183 [planetinfo.record]: land_fraction = 0.340000; 13:55:28.184 [planetinfo.record]: polar_cloud_color = 0.672622, 0.46195, 0.831348, 1; 13:55:28.184 [planetinfo.record]: polar_land_color = 0.934, 0.928527, 0.929938, 1; 13:55:28.184 [planetinfo.record]: polar_sea_color = 0.95, 0.726286, 0.715283, 1; 13:55:28.184 [planetinfo.record]: sea_color = 0.5, 0.0290222, 0.00585938, 1; 13:55:28.184 [planetinfo.record]: rotation_speed = 0.003231 13:55:28.184 [planetinfo.record]: planet zpos = 433680.000000 13:55:28.184 [planetinfo.record]: sun_radius = 79578.178711 13:55:28.184 [planetinfo.record]: sun_vector = 0.321 0.173 -0.931 13:55:28.184 [planetinfo.record]: sun_distance = 633840 13:55:28.184 [planetinfo.record]: corona_flare = 0.067908 13:55:28.184 [planetinfo.record]: corona_hues = 0.797226 13:55:28.185 [planetinfo.record]: sun_color = 0.355171, 0.592272, 0.810547, 1 13:55:28.185 [planetinfo.record]: corona_shimmer = 0.565619 13:55:28.185 [planetinfo.record]: station_vector = -0.936 -0.351 0.027 13:55:28.185 [planetinfo.record]: station = coriolis 13:55:32.627 [PLANETINFO OVER]: Done 13:55:32.627 [PLANETINFO LOGGING]: 5 200 13:55:33.630 [planetinfo.record]: seed = 57 42 10 194 13:55:33.630 [planetinfo.record]: coordinates = 42 25 13:55:33.630 [planetinfo.record]: government = 7; 13:55:33.630 [planetinfo.record]: economy = 1; 13:55:33.630 [planetinfo.record]: techlevel = 12; 13:55:33.630 [planetinfo.record]: population = 57; 13:55:33.630 [planetinfo.record]: productivity = 45144; 13:55:33.630 [planetinfo.record]: name = "Xeceator"; 13:55:33.630 [planetinfo.record]: inhabitant = "Human Colonial"; 13:55:33.630 [planetinfo.record]: inhabitants = "Human Colonials"; 13:55:33.630 [planetinfo.record]: description = "This world is very fabled for its weird tropical forests."; 13:55:33.648 [planetinfo.record]: air_color = 0.664374, 0.473531, 0.96518, 1; 13:55:33.648 [planetinfo.record]: cloud_alpha = 1.000000; 13:55:33.648 [planetinfo.record]: cloud_color = 0.898438, 0.0631714, 0.748351, 1; 13:55:33.648 [planetinfo.record]: cloud_fraction = 0.230000; 13:55:33.648 [planetinfo.record]: land_color = 0.66, 0.650252, 0.546562, 1; 13:55:33.648 [planetinfo.record]: land_fraction = 0.680000; 13:55:33.648 [planetinfo.record]: polar_cloud_color = 0.904297, 0.378851, 0.809881, 1; 13:55:33.648 [planetinfo.record]: polar_land_color = 0.934, 0.930551, 0.893867, 1; 13:55:33.648 [planetinfo.record]: polar_sea_color = 0.738024, 0.928438, 0.933008, 1; 13:55:33.648 [planetinfo.record]: sea_color = 0.109909, 0.656797, 0.669922, 1; 13:55:33.648 [planetinfo.record]: rotation_speed = 0.003493 13:55:33.648 [planetinfo.record]: planet zpos = 438100.000000 13:55:33.648 [planetinfo.record]: sun_radius = 78495.350647 13:55:33.648 [planetinfo.record]: sun_vector = 0.091 0.973 0.212 13:55:33.648 [planetinfo.record]: sun_distance = 606600 13:55:33.648 [planetinfo.record]: corona_flare = 0.057460 13:55:33.648 [planetinfo.record]: corona_hues = 0.693642 13:55:33.648 [planetinfo.record]: sun_color = 0.295752, 0.723459, 0.817374, 1 13:55:33.648 [planetinfo.record]: corona_shimmer = 0.461125 13:55:33.648 [planetinfo.record]: station_vector = 0.127 0.741 0.659 13:55:33.648 [planetinfo.record]: station = dodecahedron 13:55:37.914 [PLANETINFO OVER]: Done 13:55:37.914 [PLANETINFO LOGGING]: 5 201 13:55:38.917 [planetinfo.record]: seed = 165 185 64 177 13:55:38.917 [planetinfo.record]: coordinates = 185 241 13:55:38.917 [planetinfo.record]: government = 4; 13:55:38.917 [planetinfo.record]: economy = 1; 13:55:38.917 [planetinfo.record]: techlevel = 9; 13:55:38.917 [planetinfo.record]: population = 42; 13:55:38.917 [planetinfo.record]: productivity = 24192; 13:55:38.917 [planetinfo.record]: name = "Attesola"; 13:55:38.917 [planetinfo.record]: inhabitant = "Human Colonial"; 13:55:38.917 [planetinfo.record]: inhabitants = "Human Colonials"; 13:55:38.917 [planetinfo.record]: description = "The world Attesola is most famous for the Attesolaian spotted shrew."; 13:55:38.932 [planetinfo.record]: air_color = 0.474336, 0.602627, 0.88258, 1; 13:55:38.932 [planetinfo.record]: cloud_alpha = 1.000000; 13:55:38.932 [planetinfo.record]: cloud_color = 0.137192, 0.614306, 0.650391, 1; 13:55:38.932 [planetinfo.record]: cloud_fraction = 0.580000; 13:55:38.932 [planetinfo.record]: land_color = 0.160507, 0.613281, 0.581446, 1; 13:55:38.932 [planetinfo.record]: land_fraction = 0.340000; 13:55:38.932 [planetinfo.record]: polar_cloud_color = 0.401757, 0.765189, 0.792676, 1; 13:55:38.932 [planetinfo.record]: polar_land_color = 0.765421, 0.938672, 0.92649, 1; 13:55:38.932 [planetinfo.record]: polar_sea_color = 0.917026, 0.926562, 0.862318, 1; 13:55:38.932 [planetinfo.record]: sea_color = 0.704142, 0.734375, 0.530701, 1; 13:55:38.932 [planetinfo.record]: rotation_speed = 0.004054 13:55:38.932 [planetinfo.record]: planet zpos = 423410.000000 13:55:38.932 [planetinfo.record]: sun_radius = 92181.608276 13:55:38.932 [planetinfo.record]: sun_vector = -0.030 -0.792 0.610 13:55:38.932 [planetinfo.record]: sun_distance = 586260 13:55:38.932 [planetinfo.record]: corona_flare = 0.085872 13:55:38.932 [planetinfo.record]: corona_hues = 0.980812 13:55:38.932 [planetinfo.record]: sun_color = 0.734271, 0.483229, 0.3692, 1 13:55:38.932 [planetinfo.record]: corona_shimmer = 0.431885 13:55:38.932 [planetinfo.record]: station_vector = -0.479 0.825 0.300 13:55:38.932 [planetinfo.record]: station = coriolis 13:55:43.629 [PLANETINFO OVER]: Done 13:55:43.629 [PLANETINFO LOGGING]: 5 202 13:55:44.631 [planetinfo.record]: seed = 205 213 90 250 13:55:44.631 [planetinfo.record]: coordinates = 213 199 13:55:44.631 [planetinfo.record]: government = 1; 13:55:44.632 [planetinfo.record]: economy = 7; 13:55:44.632 [planetinfo.record]: techlevel = 2; 13:55:44.632 [planetinfo.record]: population = 17; 13:55:44.632 [planetinfo.record]: productivity = 2040; 13:55:44.632 [planetinfo.record]: name = "Qutius"; 13:55:44.632 [planetinfo.record]: inhabitant = "Human Colonial"; 13:55:44.632 [planetinfo.record]: inhabitants = "Human Colonials"; 13:55:44.632 [planetinfo.record]: description = "This world is most fabled for its inhabitants’ ingrained silliness but scourged by deadly civil war."; 13:55:44.640 [planetinfo.record]: air_color = 0.478177, 0.714194, 0.837053, 1; 13:55:44.640 [planetinfo.record]: cloud_alpha = 1.000000; 13:55:44.640 [planetinfo.record]: cloud_color = 0.160522, 0.513672, 0.174317, 1; 13:55:44.640 [planetinfo.record]: cloud_fraction = 0.300000; 13:55:44.640 [planetinfo.record]: land_color = 0.658203, 0.620038, 0.529648, 1; 13:55:44.640 [planetinfo.record]: land_fraction = 0.390000; 13:55:44.640 [planetinfo.record]: polar_cloud_color = 0.416985, 0.731152, 0.429257, 1; 13:55:44.640 [planetinfo.record]: polar_land_color = 0.93418, 0.920638, 0.888565, 1; 13:55:44.640 [planetinfo.record]: polar_sea_color = 0.738471, 0.914239, 0.932422, 1; 13:55:44.640 [planetinfo.record]: sea_color = 0.11351, 0.623068, 0.675781, 1; 13:55:44.647 [planetinfo.record]: rotation_speed = 0.003701 13:55:44.647 [planetinfo.record]: planet zpos = 726570.000000 13:55:44.648 [planetinfo.record]: sun_radius = 138492.684174 13:55:44.648 [planetinfo.record]: sun_vector = 0.912 -0.410 0.001 13:55:44.648 [planetinfo.record]: sun_distance = 1173690 13:55:44.648 [planetinfo.record]: corona_flare = 0.062259 13:55:44.648 [planetinfo.record]: corona_hues = 0.916199 13:55:44.648 [planetinfo.record]: sun_color = 0.797202, 0.581827, 0.321379, 1 13:55:44.648 [planetinfo.record]: corona_shimmer = 0.394966 13:55:44.648 [planetinfo.record]: station_vector = 0.814 -0.501 0.294 13:55:44.648 [planetinfo.record]: station = coriolis 13:55:49.133 [PLANETINFO OVER]: Done 13:55:49.133 [PLANETINFO LOGGING]: 5 203 13:55:50.135 [planetinfo.record]: seed = 65 250 40 250 13:55:50.135 [planetinfo.record]: coordinates = 250 104 13:55:50.135 [planetinfo.record]: government = 0; 13:55:50.135 [planetinfo.record]: economy = 2; 13:55:50.135 [planetinfo.record]: techlevel = 7; 13:55:50.135 [planetinfo.record]: population = 31; 13:55:50.136 [planetinfo.record]: productivity = 7936; 13:55:50.136 [planetinfo.record]: name = "Quteer"; 13:55:50.136 [planetinfo.record]: inhabitant = "Human Colonial"; 13:55:50.136 [planetinfo.record]: inhabitants = "Human Colonials"; 13:55:50.136 [planetinfo.record]: description = "This world is a revolting dump."; 13:55:50.137 [planetinfo.record]: air_color = 0.540174, 0.985992, 0.978559, 1; 13:55:50.137 [planetinfo.record]: cloud_alpha = 1.000000; 13:55:50.137 [planetinfo.record]: cloud_color = 0.960938, 0.932492, 0.232727, 1; 13:55:50.137 [planetinfo.record]: cloud_fraction = 0.230000; 13:55:50.137 [planetinfo.record]: land_color = 0.351834, 0.195938, 0.66, 1; 13:55:50.137 [planetinfo.record]: land_fraction = 0.280000; 13:55:50.137 [planetinfo.record]: polar_cloud_color = 0.932422, 0.915171, 0.490796, 1; 13:55:50.137 [planetinfo.record]: polar_land_color = 0.824974, 0.76982, 0.934, 1; 13:55:50.137 [planetinfo.record]: polar_sea_color = 0.930859, 0.905648, 0.866317, 1; 13:55:50.137 [planetinfo.record]: sea_color = 0.691406, 0.616501, 0.499649, 1; 13:55:50.137 [planetinfo.record]: rotation_speed = 0.002378 13:55:50.137 [planetinfo.record]: planet zpos = 675120.000000 13:55:50.137 [planetinfo.record]: sun_radius = 182944.581299 13:55:50.137 [planetinfo.record]: sun_vector = -0.357 -0.851 -0.385 13:55:50.138 [planetinfo.record]: sun_distance = 1237720 13:55:50.138 [planetinfo.record]: corona_flare = 0.041127 13:55:50.138 [planetinfo.record]: corona_hues = 0.857666 13:55:50.138 [planetinfo.record]: sun_color = 0.613924, 0.690921, 0.823019, 1 13:55:50.138 [planetinfo.record]: corona_shimmer = 0.369788 13:55:50.138 [planetinfo.record]: station_vector = 0.098 0.940 0.325 13:55:50.138 [planetinfo.record]: station = coriolis 13:55:54.334 [PLANETINFO OVER]: Done 13:55:54.335 [PLANETINFO LOGGING]: 5 204 13:55:55.339 [planetinfo.record]: seed = 113 167 186 84 13:55:55.339 [planetinfo.record]: coordinates = 167 80 13:55:55.339 [planetinfo.record]: government = 6; 13:55:55.339 [planetinfo.record]: economy = 0; 13:55:55.339 [planetinfo.record]: techlevel = 13; 13:55:55.339 [planetinfo.record]: population = 59; 13:55:55.339 [planetinfo.record]: productivity = 47200; 13:55:55.339 [planetinfo.record]: name = "Radiesar"; 13:55:55.339 [planetinfo.record]: inhabitant = "Yellow Insect"; 13:55:55.339 [planetinfo.record]: inhabitants = "Yellow Insects"; 13:55:55.339 [planetinfo.record]: description = "This world is reasonably notable for its fabulous cuisine but plagued by evil spotted cats."; 13:55:55.341 [planetinfo.record]: air_color = 0.646282, 0.894938, 0.879147, 1; 13:55:55.341 [planetinfo.record]: cloud_alpha = 1.000000; 13:55:55.341 [planetinfo.record]: cloud_color = 0.6875, 0.658546, 0.526367, 1; 13:55:55.341 [planetinfo.record]: cloud_fraction = 0.410000; 13:55:55.341 [planetinfo.record]: land_color = 0.381563, 0.607793, 0.66, 1; 13:55:55.341 [planetinfo.record]: land_fraction = 0.360000; 13:55:55.342 [planetinfo.record]: polar_cloud_color = 0.809375, 0.788071, 0.690814, 1; 13:55:55.342 [planetinfo.record]: polar_land_color = 0.835492, 0.91553, 0.934, 1; 13:55:55.342 [planetinfo.record]: polar_sea_color = 0.929057, 0.939453, 0.89193, 1; 13:55:55.342 [planetinfo.record]: sea_color = 0.578669, 0.605469, 0.482956, 1; 13:55:55.342 [planetinfo.record]: rotation_speed = 0.001275 13:55:55.342 [planetinfo.record]: planet zpos = 480840.000000 13:55:55.342 [planetinfo.record]: sun_radius = 94627.589264 13:55:55.342 [planetinfo.record]: sun_vector = 0.641 0.363 0.676 13:55:55.342 [planetinfo.record]: sun_distance = 921610 13:55:55.342 [planetinfo.record]: corona_flare = 0.093092 13:55:55.342 [planetinfo.record]: corona_hues = 0.998528 13:55:55.342 [planetinfo.record]: sun_color = 0.730051, 0.51687, 0.205889, 1 13:55:55.342 [planetinfo.record]: corona_shimmer = 0.346366 13:55:55.342 [planetinfo.record]: station_vector = -0.795 0.264 0.546 13:55:55.342 [planetinfo.record]: station = icosahedron 13:55:59.725 [PLANETINFO OVER]: Done 13:55:59.727 [PLANETINFO LOGGING]: 5 205 13:56:00.730 [planetinfo.record]: seed = 237 234 32 129 13:56:00.730 [planetinfo.record]: coordinates = 234 73 13:56:00.730 [planetinfo.record]: government = 5; 13:56:00.730 [planetinfo.record]: economy = 1; 13:56:00.730 [planetinfo.record]: techlevel = 11; 13:56:00.730 [planetinfo.record]: population = 51; 13:56:00.730 [planetinfo.record]: productivity = 33048; 13:56:00.730 [planetinfo.record]: name = "Lelale"; 13:56:00.730 [planetinfo.record]: inhabitant = "Human Colonial"; 13:56:00.730 [planetinfo.record]: inhabitants = "Human Colonials"; 13:56:00.730 [planetinfo.record]: description = "The world Lelale is reasonably fabled for its exciting Zero-G cricket and its fabulous cuisine."; 13:56:00.747 [planetinfo.record]: air_color = 0.751407, 0.560458, 0.9164, 1; 13:56:00.747 [planetinfo.record]: cloud_alpha = 1.000000; 13:56:00.747 [planetinfo.record]: cloud_color = 0.751953, 0.326042, 0.485759, 1; 13:56:00.747 [planetinfo.record]: cloud_fraction = 0.440000; 13:56:00.747 [planetinfo.record]: land_color = 0.0489844, 0.316304, 0.66, 1; 13:56:00.747 [planetinfo.record]: land_fraction = 0.350000; 13:56:00.747 [planetinfo.record]: polar_cloud_color = 0.838379, 0.541589, 0.652886, 1; 13:56:00.747 [planetinfo.record]: polar_land_color = 0.71783, 0.812404, 0.934, 1; 13:56:00.747 [planetinfo.record]: polar_sea_color = 0.946484, 0.907392, 0.896942, 1; 13:56:00.747 [planetinfo.record]: sea_color = 0.535156, 0.446743, 0.423108, 1; 13:56:00.747 [planetinfo.record]: rotation_speed = 0.003102 13:56:00.747 [planetinfo.record]: planet zpos = 297540.000000 13:56:00.747 [planetinfo.record]: sun_radius = 76157.152405 13:56:00.747 [planetinfo.record]: sun_vector = 0.062 0.850 0.523 13:56:00.747 [planetinfo.record]: sun_distance = 694260 13:56:00.747 [planetinfo.record]: corona_flare = 0.094104 13:56:00.748 [planetinfo.record]: corona_hues = 0.918083 13:56:00.748 [planetinfo.record]: sun_color = 0.397571, 0.517576, 0.659354, 1 13:56:00.748 [planetinfo.record]: corona_shimmer = 0.285170 13:56:00.748 [planetinfo.record]: station_vector = 0.188 0.974 0.123 13:56:00.748 [planetinfo.record]: station = dodecahedron 13:56:05.367 [PLANETINFO OVER]: Done 13:56:05.367 [PLANETINFO LOGGING]: 5 206 13:56:06.370 [planetinfo.record]: seed = 165 87 42 46 13:56:06.370 [planetinfo.record]: coordinates = 87 33 13:56:06.370 [planetinfo.record]: government = 4; 13:56:06.370 [planetinfo.record]: economy = 1; 13:56:06.370 [planetinfo.record]: techlevel = 11; 13:56:06.370 [planetinfo.record]: population = 50; 13:56:06.370 [planetinfo.record]: productivity = 28800; 13:56:06.370 [planetinfo.record]: name = "Resoinxe"; 13:56:06.370 [planetinfo.record]: inhabitant = "Human Colonial"; 13:56:06.370 [planetinfo.record]: inhabitants = "Human Colonials"; 13:56:06.370 [planetinfo.record]: description = "The world Resoinxe is a boring world."; 13:56:06.383 [planetinfo.record]: air_color = 0.700851, 0.714729, 0.984691, 1; 13:56:06.384 [planetinfo.record]: cloud_alpha = 1.000000; 13:56:06.384 [planetinfo.record]: cloud_color = 0.70282, 0.736582, 0.957031, 1; 13:56:06.384 [planetinfo.record]: cloud_fraction = 0.400000; 13:56:06.384 [planetinfo.record]: land_color = 0.66, 0.0438281, 0.361542, 1; 13:56:06.384 [planetinfo.record]: land_fraction = 0.610000; 13:56:06.384 [planetinfo.record]: polar_cloud_color = 0.776159, 0.796679, 0.930664, 1; 13:56:06.384 [planetinfo.record]: polar_land_color = 0.934, 0.716006, 0.828409, 1; 13:56:06.384 [planetinfo.record]: polar_sea_color = 0.942383, 0.907711, 0.888914, 1; 13:56:06.384 [planetinfo.record]: sea_color = 0.576172, 0.49138, 0.445408, 1; 13:56:06.384 [planetinfo.record]: rotation_speed = 0.001401 13:56:06.384 [planetinfo.record]: planet zpos = 713570.000000 13:56:06.384 [planetinfo.record]: sun_radius = 201912.032318 13:56:06.384 [planetinfo.record]: sun_vector = -0.379 0.669 0.639 13:56:06.384 [planetinfo.record]: sun_distance = 1167660 13:56:06.384 [planetinfo.record]: corona_flare = 0.065813 13:56:06.384 [planetinfo.record]: corona_hues = 0.695038 13:56:06.384 [planetinfo.record]: sun_color = 0.715622, 0.650204, 0.47949, 1 13:56:06.384 [planetinfo.record]: corona_shimmer = 0.325470 13:56:06.385 [planetinfo.record]: station_vector = -0.367 -0.647 0.668 13:56:06.385 [planetinfo.record]: station = dodecahedron 13:56:11.218 [PLANETINFO OVER]: Done 13:56:11.219 [PLANETINFO LOGGING]: 5 207 13:56:12.222 [planetinfo.record]: seed = 41 2 40 214 13:56:12.222 [planetinfo.record]: coordinates = 2 44 13:56:12.222 [planetinfo.record]: government = 5; 13:56:12.222 [planetinfo.record]: economy = 4; 13:56:12.222 [planetinfo.record]: techlevel = 8; 13:56:12.222 [planetinfo.record]: population = 42; 13:56:12.222 [planetinfo.record]: productivity = 18144; 13:56:12.222 [planetinfo.record]: name = "Veceised"; 13:56:12.222 [planetinfo.record]: inhabitant = "Human Colonial"; 13:56:12.222 [planetinfo.record]: inhabitants = "Human Colonials"; 13:56:12.222 [planetinfo.record]: description = "This world is a tedious place."; 13:56:12.230 [planetinfo.record]: air_color = 0.690893, 0.596765, 0.860467, 1; 13:56:12.231 [planetinfo.record]: cloud_alpha = 1.000000; 13:56:12.231 [planetinfo.record]: cloud_color = 0.583984, 0.396927, 0.567909, 1; 13:56:12.231 [planetinfo.record]: cloud_fraction = 0.300000; 13:56:12.231 [planetinfo.record]: land_color = 0.161636, 0.0747656, 0.66, 1; 13:56:12.231 [planetinfo.record]: land_fraction = 0.480000; 13:56:12.231 [planetinfo.record]: polar_cloud_color = 0.762793, 0.610085, 0.74967, 1; 13:56:12.231 [planetinfo.record]: polar_land_color = 0.757685, 0.726951, 0.934, 1; 13:56:12.231 [planetinfo.record]: polar_sea_color = 0.94668, 0.911861, 0.898236, 1; 13:56:12.231 [planetinfo.record]: sea_color = 0.533203, 0.454759, 0.424063, 1; 13:56:12.231 [planetinfo.record]: rotation_speed = 0.001387 13:56:12.231 [planetinfo.record]: planet zpos = 522480.000000 13:56:12.231 [planetinfo.record]: sun_radius = 142682.923889 13:56:12.231 [planetinfo.record]: sun_vector = 0.358 -0.155 -0.921 13:56:12.231 [planetinfo.record]: sun_distance = 870800 13:56:12.231 [planetinfo.record]: corona_flare = 0.053218 13:56:12.232 [planetinfo.record]: corona_hues = 0.644722 13:56:12.232 [planetinfo.record]: sun_color = 0.808807, 0.721279, 0.577711, 1 13:56:12.232 [planetinfo.record]: corona_shimmer = 0.472338 13:56:12.232 [planetinfo.record]: station_vector = -0.496 0.868 0.032 13:56:12.232 [planetinfo.record]: station = coriolis 13:56:16.587 [PLANETINFO OVER]: Done 13:56:16.588 [PLANETINFO LOGGING]: 5 208 13:56:17.604 [planetinfo.record]: seed = 233 184 170 155 13:56:17.604 [planetinfo.record]: coordinates = 184 221 13:56:17.604 [planetinfo.record]: government = 5; 13:56:17.604 [planetinfo.record]: economy = 5; 13:56:17.604 [planetinfo.record]: techlevel = 5; 13:56:17.604 [planetinfo.record]: population = 31; 13:56:17.604 [planetinfo.record]: productivity = 11160; 13:56:17.604 [planetinfo.record]: name = "Anenbi"; 13:56:17.604 [planetinfo.record]: inhabitant = "Black Furry Rodent"; 13:56:17.604 [planetinfo.record]: inhabitants = "Black Furry Rodents"; 13:56:17.604 [planetinfo.record]: description = "The planet Anenbi is mildly well known for its exotic cuisine."; 13:56:17.605 [planetinfo.record]: air_color = 0.694164, 0.842474, 0.893637, 1; 13:56:17.605 [planetinfo.record]: cloud_alpha = 1.000000; 13:56:17.605 [planetinfo.record]: cloud_color = 0.646043, 0.683594, 0.635529, 1; 13:56:17.606 [planetinfo.record]: cloud_fraction = 0.320000; 13:56:17.606 [planetinfo.record]: land_color = 0.549141, 0.66, 0.636616, 1; 13:56:17.606 [planetinfo.record]: land_fraction = 0.600000; 13:56:17.606 [planetinfo.record]: polar_cloud_color = 0.77989, 0.807617, 0.772126, 1; 13:56:17.606 [planetinfo.record]: polar_land_color = 0.894779, 0.934, 0.925727, 1; 13:56:17.606 [planetinfo.record]: polar_sea_color = 0.858113, 0.946484, 0.751457, 1; 13:56:17.606 [planetinfo.record]: sea_color = 0.335289, 0.535156, 0.0940704, 1; 13:56:17.606 [planetinfo.record]: rotation_speed = 0.004122 13:56:17.606 [planetinfo.record]: planet zpos = 697920.000000 13:56:17.606 [planetinfo.record]: sun_radius = 121037.690430 13:56:17.606 [planetinfo.record]: sun_vector = -0.260 -0.935 0.241 13:56:17.606 [planetinfo.record]: sun_distance = 1337680 13:56:17.606 [planetinfo.record]: corona_flare = 0.007372 13:56:17.606 [planetinfo.record]: corona_hues = 0.576729 13:56:17.606 [planetinfo.record]: sun_color = 0.415096, 0.540208, 0.769391, 1 13:56:17.607 [planetinfo.record]: corona_shimmer = 0.426939 13:56:17.607 [planetinfo.record]: station_vector = -0.529 0.207 0.823 13:56:17.607 [planetinfo.record]: station = coriolis 13:56:21.937 [PLANETINFO OVER]: Done 13:56:21.938 [PLANETINFO LOGGING]: 5 209 13:56:22.951 [planetinfo.record]: seed = 117 84 64 13 13:56:22.951 [planetinfo.record]: coordinates = 84 134 13:56:22.951 [planetinfo.record]: government = 6; 13:56:22.951 [planetinfo.record]: economy = 6; 13:56:22.951 [planetinfo.record]: techlevel = 4; 13:56:22.951 [planetinfo.record]: population = 29; 13:56:22.951 [planetinfo.record]: productivity = 9280; 13:56:22.951 [planetinfo.record]: name = "Diusar"; 13:56:22.951 [planetinfo.record]: inhabitant = "Human Colonial"; 13:56:22.951 [planetinfo.record]: inhabitants = "Human Colonials"; 13:56:22.951 [planetinfo.record]: description = "The planet Diusar is very noted for its unusual sit coms but plagued by unpredictable civil war."; 13:56:22.960 [planetinfo.record]: air_color = 0.70966, 0.827584, 0.895588, 1; 13:56:22.960 [planetinfo.record]: cloud_alpha = 1.000000; 13:56:22.960 [planetinfo.record]: cloud_color = 0.673294, 0.689453, 0.675061, 1; 13:56:22.960 [planetinfo.record]: cloud_fraction = 0.110000; 13:56:22.960 [planetinfo.record]: land_color = 0.546844, 0.523359, 0.66, 1; 13:56:22.960 [planetinfo.record]: land_fraction = 0.260000; 13:56:22.960 [planetinfo.record]: polar_cloud_color = 0.798385, 0.810254, 0.799683, 1; 13:56:22.960 [planetinfo.record]: polar_land_color = 0.893967, 0.885658, 0.934, 1; 13:56:22.960 [planetinfo.record]: polar_sea_color = 0.94375, 0.927149, 0.895457, 1; 13:56:22.960 [planetinfo.record]: sea_color = 0.5625, 0.522922, 0.447363, 1; 13:56:22.960 [planetinfo.record]: rotation_speed = 0.002088 13:56:22.961 [planetinfo.record]: planet zpos = 622800.000000 13:56:22.961 [planetinfo.record]: sun_radius = 168223.282471 13:56:22.961 [planetinfo.record]: sun_vector = 0.850 -0.075 0.521 13:56:22.961 [planetinfo.record]: sun_distance = 1370160 13:56:22.961 [planetinfo.record]: corona_flare = 0.043343 13:56:22.961 [planetinfo.record]: corona_hues = 0.830566 13:56:22.961 [planetinfo.record]: sun_color = 0.583054, 0.814348, 0.816052, 1 13:56:22.961 [planetinfo.record]: corona_shimmer = 0.546644 13:56:22.961 [planetinfo.record]: station_vector = 0.007 0.633 0.774 13:56:22.961 [planetinfo.record]: station = coriolis 13:56:28.205 [PLANETINFO OVER]: Done 13:56:28.205 [PLANETINFO LOGGING]: 5 210 13:56:29.220 [planetinfo.record]: seed = 189 63 58 114 13:56:29.220 [planetinfo.record]: coordinates = 63 74 13:56:29.220 [planetinfo.record]: government = 7; 13:56:29.220 [planetinfo.record]: economy = 2; 13:56:29.220 [planetinfo.record]: techlevel = 12; 13:56:29.220 [planetinfo.record]: population = 58; 13:56:29.220 [planetinfo.record]: productivity = 40832; 13:56:29.220 [planetinfo.record]: name = "Entere"; 13:56:29.220 [planetinfo.record]: inhabitant = "Human Colonial"; 13:56:29.220 [planetinfo.record]: inhabitants = "Human Colonials"; 13:56:29.220 [planetinfo.record]: description = "This planet is a tedious place."; 13:56:29.232 [planetinfo.record]: air_color = 0.673165, 0.732654, 0.987293, 1; 13:56:29.232 [planetinfo.record]: cloud_alpha = 1.000000; 13:56:29.232 [planetinfo.record]: cloud_color = 0.621872, 0.812114, 0.964844, 1; 13:56:29.232 [planetinfo.record]: cloud_fraction = 0.440000; 13:56:29.232 [planetinfo.record]: land_color = 0.392338, 0.66, 0.306797, 1; 13:56:29.232 [planetinfo.record]: land_fraction = 0.570000; 13:56:29.234 [planetinfo.record]: polar_cloud_color = 0.726635, 0.841757, 0.93418, 1; 13:56:29.234 [planetinfo.record]: polar_land_color = 0.839305, 0.934, 0.809041, 1; 13:56:29.234 [planetinfo.record]: polar_sea_color = 0.942773, 0.91939, 0.892044, 1; 13:56:29.234 [planetinfo.record]: sea_color = 0.572266, 0.515491, 0.449094, 1; 13:56:29.234 [planetinfo.record]: rotation_speed = 0.004241 13:56:29.234 [planetinfo.record]: planet zpos = 406920.000000 13:56:29.234 [planetinfo.record]: sun_radius = 109666.272888 13:56:29.234 [planetinfo.record]: sun_vector = -0.437 -0.504 -0.745 13:56:29.234 [planetinfo.record]: sun_distance = 610380 13:56:29.234 [planetinfo.record]: corona_flare = 0.086858 13:56:29.234 [planetinfo.record]: corona_hues = 0.531174 13:56:29.234 [planetinfo.record]: sun_color = 0.621228, 0.683875, 0.847028, 1 13:56:29.235 [planetinfo.record]: corona_shimmer = 0.355444 13:56:29.235 [planetinfo.record]: station_vector = -0.897 -0.428 0.113 13:56:29.235 [planetinfo.record]: station = dodecahedron 13:56:34.151 [PLANETINFO OVER]: Done 13:56:34.152 [PLANETINFO LOGGING]: 5 211 13:56:35.164 [planetinfo.record]: seed = 81 28 104 198 13:56:35.164 [planetinfo.record]: coordinates = 28 174 13:56:35.164 [planetinfo.record]: government = 2; 13:56:35.164 [planetinfo.record]: economy = 6; 13:56:35.165 [planetinfo.record]: techlevel = 2; 13:56:35.165 [planetinfo.record]: population = 17; 13:56:35.165 [planetinfo.record]: productivity = 3264; 13:56:35.165 [planetinfo.record]: name = "Bierbe"; 13:56:35.165 [planetinfo.record]: inhabitant = "Human Colonial"; 13:56:35.165 [planetinfo.record]: inhabitants = "Human Colonials"; 13:56:35.165 [planetinfo.record]: description = "This world is a revolting dump."; 13:56:35.186 [planetinfo.record]: air_color = 0.578024, 0.499609, 0.922904, 1; 13:56:35.187 [planetinfo.record]: cloud_alpha = 1.000000; 13:56:35.187 [planetinfo.record]: cloud_color = 0.513797, 0.171776, 0.771484, 1; 13:56:35.187 [planetinfo.record]: cloud_fraction = 0.290000; 13:56:35.187 [planetinfo.record]: land_color = 0.556875, 0.599575, 0.66, 1; 13:56:35.187 [planetinfo.record]: land_fraction = 0.590000; 13:56:35.187 [planetinfo.record]: polar_cloud_color = 0.670314, 0.43558, 0.847168, 1; 13:56:35.187 [planetinfo.record]: polar_land_color = 0.897516, 0.912622, 0.934, 1; 13:56:35.187 [planetinfo.record]: polar_sea_color = 0.935352, 0.900406, 0.881459, 1; 13:56:35.187 [planetinfo.record]: sea_color = 0.646484, 0.549871, 0.49749, 1; 13:56:35.187 [planetinfo.record]: rotation_speed = 0.000446 13:56:35.187 [planetinfo.record]: planet zpos = 525600.000000 13:56:35.188 [planetinfo.record]: sun_radius = 125495.260620 13:56:35.188 [planetinfo.record]: sun_vector = 0.405 0.485 0.775 13:56:35.188 [planetinfo.record]: sun_distance = 876000 13:56:35.189 [planetinfo.record]: corona_flare = 0.095998 13:56:35.189 [planetinfo.record]: corona_hues = 0.846649 13:56:35.189 [planetinfo.record]: sun_color = 0.530969, 0.584932, 0.668124, 1 13:56:35.189 [planetinfo.record]: corona_shimmer = 0.300114 13:56:35.190 [planetinfo.record]: station_vector = 0.761 -0.057 0.646 13:56:35.190 [planetinfo.record]: station = coriolis 13:56:40.129 [PLANETINFO OVER]: Done 13:56:40.130 [PLANETINFO LOGGING]: 5 212 13:56:41.135 [planetinfo.record]: seed = 161 202 218 206 13:56:41.135 [planetinfo.record]: coordinates = 202 115 13:56:41.135 [planetinfo.record]: government = 4; 13:56:41.135 [planetinfo.record]: economy = 3; 13:56:41.135 [planetinfo.record]: techlevel = 8; 13:56:41.136 [planetinfo.record]: population = 40; 13:56:41.136 [planetinfo.record]: productivity = 17920; 13:56:41.136 [planetinfo.record]: name = "Reinbixe"; 13:56:41.136 [planetinfo.record]: inhabitant = "Bug-Eyed Lobster"; 13:56:41.136 [planetinfo.record]: inhabitants = "Bug-Eyed Lobsters"; 13:56:41.136 [planetinfo.record]: description = "This planet is notable for the Reinbixeian tree wolf and its pink oceans."; 13:56:41.142 [planetinfo.record]: air_color = 0.653128, 0.803268, 0.967131, 1; 13:56:41.142 [planetinfo.record]: cloud_alpha = 1.000000; 13:56:41.142 [planetinfo.record]: cloud_color = 0.565186, 0.904297, 0.761234, 1; 13:56:41.142 [planetinfo.record]: cloud_fraction = 0.370000; 13:56:41.142 [planetinfo.record]: land_color = 0.194679, 0.446846, 0.630859, 1; 13:56:41.142 [planetinfo.record]: land_fraction = 0.330000; 13:56:41.142 [planetinfo.record]: polar_cloud_color = 0.694371, 0.906934, 0.817259, 1; 13:56:41.142 [planetinfo.record]: polar_land_color = 0.774967, 0.868593, 0.936914, 1; 13:56:41.142 [planetinfo.record]: polar_sea_color = 0.91, 0.8645, 0.8918, 1; 13:56:41.142 [planetinfo.record]: sea_color = 0.9, 0.72, 0.828, 1; 13:56:41.142 [planetinfo.record]: rotation_speed = 0.001961 13:56:41.143 [planetinfo.record]: planet zpos = 792240.000000 13:56:41.143 [planetinfo.record]: sun_radius = 169189.346008 13:56:41.143 [planetinfo.record]: sun_vector = -0.110 0.629 0.769 13:56:41.143 [planetinfo.record]: sun_distance = 1254380 13:56:41.143 [planetinfo.record]: corona_flare = 0.062196 13:56:41.143 [planetinfo.record]: corona_hues = 0.975693 13:56:41.143 [planetinfo.record]: sun_color = 0.670332, 0.678834, 0.692007, 1 13:56:41.143 [planetinfo.record]: corona_shimmer = 0.530879 13:56:41.144 [planetinfo.record]: station_vector = -0.258 -0.581 0.772 13:56:41.144 [planetinfo.record]: station = coriolis 13:56:45.857 [PLANETINFO OVER]: Done 13:56:45.858 [PLANETINFO LOGGING]: 5 213 13:56:46.862 [planetinfo.record]: seed = 61 130 160 53 13:56:46.862 [planetinfo.record]: coordinates = 130 166 13:56:46.862 [planetinfo.record]: government = 7; 13:56:46.862 [planetinfo.record]: economy = 6; 13:56:46.862 [planetinfo.record]: techlevel = 7; 13:56:46.862 [planetinfo.record]: population = 42; 13:56:46.862 [planetinfo.record]: productivity = 14784; 13:56:46.862 [planetinfo.record]: name = "Larivear"; 13:56:46.862 [planetinfo.record]: inhabitant = "Red Fat Humanoid"; 13:56:46.862 [planetinfo.record]: inhabitants = "Red Fat Humanoids"; 13:56:46.862 [planetinfo.record]: description = "This planet is fabled for its unusual tropical forests and its exciting Larivearian evil brandy."; 13:56:46.873 [planetinfo.record]: air_color = 0.692981, 0.74175, 0.973635, 1; 13:56:46.873 [planetinfo.record]: cloud_alpha = 1.000000; 13:56:46.873 [planetinfo.record]: cloud_color = 0.678436, 0.803049, 0.923828, 1; 13:56:46.873 [planetinfo.record]: cloud_fraction = 0.260000; 13:56:46.873 [planetinfo.record]: land_color = 0.324656, 0.523438, 0.106323, 1; 13:56:46.873 [planetinfo.record]: land_fraction = 0.640000; 13:56:46.873 [planetinfo.record]: polar_cloud_color = 0.763698, 0.840898, 0.915723, 1; 13:56:46.874 [planetinfo.record]: polar_land_color = 0.857686, 0.947656, 0.758865, 1; 13:56:46.874 [planetinfo.record]: polar_sea_color = 0.858022, 0.874339, 0.948828, 1; 13:56:46.874 [planetinfo.record]: sea_color = 0.315826, 0.351026, 0.511719, 1; 13:56:46.874 [planetinfo.record]: rotation_speed = 0.001177 13:56:46.874 [planetinfo.record]: planet zpos = 549380.000000 13:56:46.874 [planetinfo.record]: sun_radius = 110323.774414 13:56:46.874 [planetinfo.record]: sun_vector = -0.160 -0.967 0.200 13:56:46.874 [planetinfo.record]: sun_distance = 760680 13:56:46.874 [planetinfo.record]: corona_flare = 0.024480 13:56:46.874 [planetinfo.record]: corona_hues = 0.957756 13:56:46.874 [planetinfo.record]: sun_color = 0.84559, 0.734799, 0.681526, 1 13:56:46.875 [planetinfo.record]: corona_shimmer = 0.307779 13:56:46.883 [planetinfo.record]: station_vector = -0.999 -0.037 0.018 13:56:46.883 [planetinfo.record]: station = coriolis 13:56:52.088 [PLANETINFO OVER]: Done 13:56:52.089 [PLANETINFO LOGGING]: 5 214 13:56:53.109 [planetinfo.record]: seed = 21 170 138 30 13:56:53.109 [planetinfo.record]: coordinates = 170 22 13:56:53.109 [planetinfo.record]: government = 2; 13:56:53.109 [planetinfo.record]: economy = 6; 13:56:53.109 [planetinfo.record]: techlevel = 4; 13:56:53.109 [planetinfo.record]: population = 25; 13:56:53.109 [planetinfo.record]: productivity = 4800; 13:56:53.109 [planetinfo.record]: name = "Ririso"; 13:56:53.109 [planetinfo.record]: inhabitant = "Green Fat Feline"; 13:56:53.109 [planetinfo.record]: inhabitants = "Green Fat Felines"; 13:56:53.109 [planetinfo.record]: description = "This planet is plagued by deadly earthquakes."; 13:56:53.113 [planetinfo.record]: air_color = 0.456749, 0.453986, 0.935262, 1; 13:56:53.113 [planetinfo.record]: cloud_alpha = 1.000000; 13:56:53.113 [planetinfo.record]: cloud_color = 0.0681067, 0.04422, 0.808594, 1; 13:56:53.113 [planetinfo.record]: cloud_fraction = 0.090000; 13:56:53.113 [planetinfo.record]: land_color = 0.368672, 0.66, 0.468816, 1; 13:56:53.113 [planetinfo.record]: land_fraction = 0.320000; 13:56:53.113 [planetinfo.record]: polar_cloud_color = 0.369427, 0.353477, 0.863867, 1; 13:56:53.113 [planetinfo.record]: polar_land_color = 0.830932, 0.934, 0.866361, 1; 13:56:53.113 [planetinfo.record]: polar_sea_color = 0.94375, 0.928458, 0.89601, 1; 13:56:53.113 [planetinfo.record]: sea_color = 0.5625, 0.526043, 0.448682, 1; 13:56:53.113 [planetinfo.record]: rotation_speed = 0.002032 13:56:53.113 [planetinfo.record]: planet zpos = 657000.000000 13:56:53.113 [planetinfo.record]: sun_radius = 205885.931396 13:56:53.113 [planetinfo.record]: sun_vector = -0.132 -0.261 -0.956 13:56:53.113 [planetinfo.record]: sun_distance = 1379700 13:56:53.113 [planetinfo.record]: corona_flare = 0.046121 13:56:53.114 [planetinfo.record]: corona_hues = 0.580154 13:56:53.114 [planetinfo.record]: sun_color = 0.641188, 0.698122, 0.785437, 1 13:56:53.114 [planetinfo.record]: corona_shimmer = 0.292485 13:56:53.114 [planetinfo.record]: station_vector = -0.538 -0.232 0.811 13:56:53.114 [planetinfo.record]: station = coriolis 13:56:57.475 [PLANETINFO OVER]: Done 13:56:57.476 [PLANETINFO LOGGING]: 5 215 13:56:58.478 [planetinfo.record]: seed = 185 164 232 42 13:56:58.478 [planetinfo.record]: coordinates = 164 167 13:56:58.478 [planetinfo.record]: government = 7; 13:56:58.478 [planetinfo.record]: economy = 7; 13:56:58.478 [planetinfo.record]: techlevel = 4; 13:56:58.478 [planetinfo.record]: population = 31; 13:56:58.478 [planetinfo.record]: productivity = 8184; 13:56:58.478 [planetinfo.record]: name = "Artibius"; 13:56:58.478 [planetinfo.record]: inhabitant = "Small Red Bony Humanoid"; 13:56:58.478 [planetinfo.record]: inhabitants = "Small Red Bony Humanoids"; 13:56:58.478 [planetinfo.record]: description = "This world is plagued by deadly earthquakes."; 13:56:58.488 [planetinfo.record]: air_color = 0.622716, 0.920646, 0.927457, 1; 13:56:58.488 [planetinfo.record]: cloud_alpha = 1.000000; 13:56:58.488 [planetinfo.record]: cloud_color = 0.761435, 0.785156, 0.481522, 1; 13:56:58.488 [planetinfo.record]: cloud_fraction = 0.270000; 13:56:58.488 [planetinfo.record]: land_color = 0.159855, 0.566406, 0.101776, 1; 13:56:58.488 [planetinfo.record]: land_fraction = 0.390000; 13:56:58.488 [planetinfo.record]: polar_cloud_color = 0.837207, 0.85332, 0.647073, 1; 13:56:58.488 [planetinfo.record]: polar_land_color = 0.77408, 0.943359, 0.749897, 1; 13:56:58.489 [planetinfo.record]: polar_sea_color = 0.947461, 0.893426, 0.907357, 1; 13:56:58.489 [planetinfo.record]: sea_color = 0.525391, 0.405536, 0.436436, 1; 13:56:58.489 [planetinfo.record]: rotation_speed = 0.004458 13:56:58.489 [planetinfo.record]: planet zpos = 609400.000000 13:56:58.489 [planetinfo.record]: sun_radius = 179379.647827 13:56:58.489 [planetinfo.record]: sun_vector = 0.753 -0.551 0.359 13:56:58.489 [planetinfo.record]: sun_distance = 941800 13:56:58.489 [planetinfo.record]: corona_flare = 0.059142 13:56:58.489 [planetinfo.record]: corona_hues = 0.616829 13:56:58.489 [planetinfo.record]: sun_color = 0.664657, 0.674329, 0.67767, 1 13:56:58.489 [planetinfo.record]: corona_shimmer = 0.350291 13:56:58.489 [planetinfo.record]: station_vector = 0.774 -0.216 0.595 13:56:58.489 [planetinfo.record]: station = coriolis 13:57:03.514 [PLANETINFO OVER]: Done 13:57:03.515 [PLANETINFO LOGGING]: 5 216 13:57:04.520 [planetinfo.record]: seed = 153 232 74 166 13:57:04.520 [planetinfo.record]: coordinates = 232 70 13:57:04.520 [planetinfo.record]: government = 3; 13:57:04.520 [planetinfo.record]: economy = 6; 13:57:04.520 [planetinfo.record]: techlevel = 3; 13:57:04.520 [planetinfo.record]: population = 22; 13:57:04.520 [planetinfo.record]: productivity = 4928; 13:57:04.520 [planetinfo.record]: name = "Bilaza"; 13:57:04.520 [planetinfo.record]: inhabitant = "Human Colonial"; 13:57:04.520 [planetinfo.record]: inhabitants = "Human Colonials"; 13:57:04.520 [planetinfo.record]: description = "The world Bilaza is beset by evil disease."; 13:57:04.544 [planetinfo.record]: air_color = 0.70756, 0.827385, 0.89884, 1; 13:57:04.544 [planetinfo.record]: cloud_alpha = 1.000000; 13:57:04.544 [planetinfo.record]: cloud_color = 0.671906, 0.699219, 0.675533, 1; 13:57:04.545 [planetinfo.record]: cloud_fraction = 0.140000; 13:57:04.545 [planetinfo.record]: land_color = 0.580078, 0.560322, 0.539291, 1; 13:57:04.545 [planetinfo.record]: land_fraction = 0.700000; 13:57:04.545 [planetinfo.record]: polar_cloud_color = 0.79476, 0.814648, 0.797401, 1; 13:57:04.545 [planetinfo.record]: polar_land_color = 0.941992, 0.933972, 0.925434, 1; 13:57:04.545 [planetinfo.record]: polar_sea_color = 0.88406, 0.91875, 0.828131, 1; 13:57:04.545 [planetinfo.record]: sea_color = 0.689787, 0.8125, 0.491943, 1; 13:57:04.545 [planetinfo.record]: rotation_speed = 0.004759 13:57:04.545 [planetinfo.record]: planet zpos = 641760.000000 13:57:04.545 [planetinfo.record]: sun_radius = 130909.376221 13:57:04.545 [planetinfo.record]: sun_vector = 0.465 0.562 0.684 13:57:04.545 [planetinfo.record]: sun_distance = 825120 13:57:04.545 [planetinfo.record]: corona_flare = 0.093152 13:57:04.545 [planetinfo.record]: corona_hues = 0.910156 13:57:04.545 [planetinfo.record]: sun_color = 0.739004, 0.661885, 0.357964, 1 13:57:04.546 [planetinfo.record]: corona_shimmer = 0.428889 13:57:04.546 [planetinfo.record]: station_vector = 0.872 0.347 0.346 13:57:04.546 [planetinfo.record]: station = coriolis 13:57:09.778 [PLANETINFO OVER]: Done 13:57:09.778 [PLANETINFO LOGGING]: 5 217 13:57:10.782 [planetinfo.record]: seed = 69 224 64 26 13:57:10.782 [planetinfo.record]: coordinates = 224 100 13:57:10.782 [planetinfo.record]: government = 0; 13:57:10.783 [planetinfo.record]: economy = 6; 13:57:10.783 [planetinfo.record]: techlevel = 1; 13:57:10.783 [planetinfo.record]: population = 11; 13:57:10.783 [planetinfo.record]: productivity = 1408; 13:57:10.783 [planetinfo.record]: name = "Qurioren"; 13:57:10.783 [planetinfo.record]: inhabitant = "Human Colonial"; 13:57:10.783 [planetinfo.record]: inhabitants = "Human Colonials"; 13:57:10.783 [planetinfo.record]: description = "This world is a revolting little planet."; 13:57:10.804 [planetinfo.record]: air_color = 0.572286, 0.956074, 0.928708, 1; 13:57:10.804 [planetinfo.record]: cloud_alpha = 1.000000; 13:57:10.804 [planetinfo.record]: cloud_color = 0.871094, 0.763962, 0.343674, 1; 13:57:10.804 [planetinfo.record]: cloud_fraction = 0.260000; 13:57:10.804 [planetinfo.record]: land_color = 0.611372, 0.613281, 0.606094, 1; 13:57:10.804 [planetinfo.record]: land_fraction = 0.340000; 13:57:10.804 [planetinfo.record]: polar_cloud_color = 0.891992, 0.823428, 0.554446, 1; 13:57:10.804 [planetinfo.record]: polar_land_color = 0.937941, 0.938672, 0.935922, 1; 13:57:10.804 [planetinfo.record]: polar_sea_color = 0.86571, 0.914258, 0.807118, 1; 13:57:10.804 [planetinfo.record]: sea_color = 0.675303, 0.857422, 0.455505, 1; 13:57:10.804 [planetinfo.record]: rotation_speed = 0.000241 13:57:10.804 [planetinfo.record]: planet zpos = 728000.000000 13:57:10.804 [planetinfo.record]: sun_radius = 128319.091797 13:57:10.804 [planetinfo.record]: sun_vector = -0.773 0.245 0.585 13:57:10.804 [planetinfo.record]: sun_distance = 1176000 13:57:10.804 [planetinfo.record]: corona_flare = 0.035948 13:57:10.804 [planetinfo.record]: corona_hues = 0.859467 13:57:10.805 [planetinfo.record]: sun_color = 0.190009, 0.627077, 0.709833, 1 13:57:10.805 [planetinfo.record]: corona_shimmer = 0.471978 13:57:10.805 [planetinfo.record]: station_vector = -0.040 0.668 0.743 13:57:10.805 [planetinfo.record]: station = coriolis 13:57:15.746 [PLANETINFO OVER]: Done 13:57:15.747 [PLANETINFO LOGGING]: 5 218 13:57:16.765 [planetinfo.record]: seed = 173 210 26 139 13:57:16.765 [planetinfo.record]: coordinates = 210 89 13:57:16.765 [planetinfo.record]: government = 5; 13:57:16.765 [planetinfo.record]: economy = 1; 13:57:16.765 [planetinfo.record]: techlevel = 11; 13:57:16.765 [planetinfo.record]: population = 51; 13:57:16.765 [planetinfo.record]: productivity = 33048; 13:57:16.765 [planetinfo.record]: name = "Matilati"; 13:57:16.765 [planetinfo.record]: inhabitant = "Human Colonial"; 13:57:16.765 [planetinfo.record]: inhabitants = "Human Colonials"; 13:57:16.765 [planetinfo.record]: description = "The planet Matilati is most famous for the Matilatiian deadly lobstoid."; 13:57:16.774 [planetinfo.record]: air_color = 0.852208, 0.75683, 0.997699, 1; 13:57:16.774 [planetinfo.record]: cloud_alpha = 1.000000; 13:57:16.774 [planetinfo.record]: cloud_color = 0.996094, 0.871582, 0.970802, 1; 13:57:16.774 [planetinfo.record]: cloud_fraction = 0.590000; 13:57:16.774 [planetinfo.record]: land_color = 0.66, 0.309375, 0.399771, 1; 13:57:16.775 [planetinfo.record]: land_fraction = 0.690000; 13:57:16.775 [planetinfo.record]: polar_cloud_color = 0.948242, 0.874161, 0.933194, 1; 13:57:16.775 [planetinfo.record]: polar_land_color = 0.934, 0.809953, 0.841934, 1; 13:57:16.775 [planetinfo.record]: polar_sea_color = 0.922818, 0.932617, 0.875604, 1; 13:57:16.775 [planetinfo.record]: sea_color = 0.645508, 0.673828, 0.509056, 1; 13:57:16.775 [planetinfo.record]: rotation_speed = 0.004900 13:57:16.775 [planetinfo.record]: planet zpos = 584200.000000 13:57:16.775 [planetinfo.record]: sun_radius = 156936.893616 13:57:16.775 [planetinfo.record]: sun_vector = 0.213 0.805 -0.554 13:57:16.775 [planetinfo.record]: sun_distance = 934720 13:57:16.775 [planetinfo.record]: corona_flare = 0.041220 13:57:16.775 [planetinfo.record]: corona_hues = 0.960945 13:57:16.775 [planetinfo.record]: sun_color = 0.705014, 0.45941, 0.420382, 1 13:57:16.775 [planetinfo.record]: corona_shimmer = 0.499322 13:57:16.776 [planetinfo.record]: station_vector = -0.568 -0.812 0.138 13:57:16.776 [planetinfo.record]: station = dodecahedron 13:57:21.803 [PLANETINFO OVER]: Done 13:57:21.804 [PLANETINFO LOGGING]: 5 219 13:57:22.897 [planetinfo.record]: seed = 97 87 168 35 13:57:22.897 [planetinfo.record]: coordinates = 87 21 13:57:22.897 [planetinfo.record]: government = 4; 13:57:22.897 [planetinfo.record]: economy = 5; 13:57:22.897 [planetinfo.record]: techlevel = 7; 13:57:22.897 [planetinfo.record]: population = 38; 13:57:22.897 [planetinfo.record]: productivity = 12160; 13:57:22.897 [planetinfo.record]: name = "Geerma"; 13:57:22.897 [planetinfo.record]: inhabitant = "Large Red Horned Humanoid"; 13:57:22.897 [planetinfo.record]: inhabitants = "Large Red Horned Humanoids"; 13:57:22.897 [planetinfo.record]: description = "The world Geerma is a boring planet."; 13:57:22.904 [planetinfo.record]: air_color = 0.708816, 0.814155, 0.912498, 1; 13:57:22.904 [planetinfo.record]: cloud_alpha = 1.000000; 13:57:22.904 [planetinfo.record]: cloud_color = 0.688187, 0.740234, 0.712177, 1; 13:57:22.904 [planetinfo.record]: cloud_fraction = 0.530000; 13:57:22.905 [planetinfo.record]: land_color = 0.66, 0.100547, 0.362791, 1; 13:57:22.905 [planetinfo.record]: land_fraction = 0.510000; 13:57:22.905 [planetinfo.record]: polar_cloud_color = 0.796494, 0.833105, 0.81337, 1; 13:57:22.905 [planetinfo.record]: polar_land_color = 0.934, 0.736072, 0.828851, 1; 13:57:22.905 [planetinfo.record]: polar_sea_color = 0.938086, 0.884408, 0.88312, 1; 13:57:22.905 [planetinfo.record]: sea_color = 0.619141, 0.477431, 0.47403, 1; 13:57:22.905 [planetinfo.record]: rotation_speed = 0.003506 13:57:22.905 [planetinfo.record]: planet zpos = 330390.000000 13:57:22.905 [planetinfo.record]: sun_radius = 87556.509247 13:57:22.905 [planetinfo.record]: sun_vector = -0.071 0.983 -0.169 13:57:22.905 [planetinfo.record]: sun_distance = 807620 13:57:22.905 [planetinfo.record]: corona_flare = 0.001659 13:57:22.905 [planetinfo.record]: corona_hues = 0.927803 13:57:22.905 [planetinfo.record]: sun_color = 0.80448, 0.796491, 0.736002, 1 13:57:22.905 [planetinfo.record]: corona_shimmer = 0.645497 13:57:22.905 [planetinfo.record]: station_vector = 0.590 0.776 0.221 13:57:22.905 [planetinfo.record]: station = coriolis 13:57:28.121 [PLANETINFO OVER]: Done 13:57:28.122 [PLANETINFO LOGGING]: 5 220 13:57:29.125 [planetinfo.record]: seed = 209 190 250 89 13:57:29.125 [planetinfo.record]: coordinates = 190 11 13:57:29.125 [planetinfo.record]: government = 2; 13:57:29.125 [planetinfo.record]: economy = 3; 13:57:29.125 [planetinfo.record]: techlevel = 7; 13:57:29.125 [planetinfo.record]: population = 34; 13:57:29.125 [planetinfo.record]: productivity = 11424; 13:57:29.125 [planetinfo.record]: name = "Orgete"; 13:57:29.125 [planetinfo.record]: inhabitant = "Yellow Furry Feline"; 13:57:29.125 [planetinfo.record]: inhabitants = "Yellow Furry Felines"; 13:57:29.125 [planetinfo.record]: description = "Orgete is a revolting little planet."; 13:57:29.127 [planetinfo.record]: air_color = 0.738118, 0.597705, 0.879979, 1; 13:57:29.127 [planetinfo.record]: cloud_alpha = 1.000000; 13:57:29.127 [planetinfo.record]: cloud_color = 0.642578, 0.409142, 0.524036, 1; 13:57:29.127 [planetinfo.record]: cloud_fraction = 0.420000; 13:57:29.127 [planetinfo.record]: land_color = 0.66, 0.0696094, 0.443216, 1; 13:57:29.127 [planetinfo.record]: land_fraction = 0.480000; 13:57:29.127 [planetinfo.record]: polar_cloud_color = 0.78916, 0.609981, 0.698171, 1; 13:57:29.127 [planetinfo.record]: polar_land_color = 0.934, 0.725127, 0.857304, 1; 13:57:29.127 [planetinfo.record]: polar_sea_color = 0.945898, 0.893061, 0.893061, 1; 13:57:29.127 [planetinfo.record]: sea_color = 0.541016, 0.420132, 0.420132, 1; 13:57:29.128 [planetinfo.record]: rotation_speed = 0.002530 13:57:29.128 [planetinfo.record]: planet zpos = 637200.000000 13:57:29.128 [planetinfo.record]: sun_radius = 104521.179199 13:57:29.128 [planetinfo.record]: sun_vector = 0.750 -0.639 0.170 13:57:29.128 [planetinfo.record]: sun_distance = 1168200 13:57:29.128 [planetinfo.record]: corona_flare = 0.005684 13:57:29.128 [planetinfo.record]: corona_hues = 0.826759 13:57:29.128 [planetinfo.record]: sun_color = 0.654724, 0.485004, 0.366779, 1 13:57:29.128 [planetinfo.record]: corona_shimmer = 0.289278 13:57:29.128 [planetinfo.record]: station_vector = -0.109 0.883 0.457 13:57:29.128 [planetinfo.record]: station = coriolis 13:57:33.613 [PLANETINFO OVER]: Done 13:57:33.614 [PLANETINFO LOGGING]: 5 221 13:57:34.619 [planetinfo.record]: seed = 141 186 32 27 13:57:34.619 [planetinfo.record]: coordinates = 186 60 13:57:34.619 [planetinfo.record]: government = 1; 13:57:34.619 [planetinfo.record]: economy = 6; 13:57:34.619 [planetinfo.record]: techlevel = 4; 13:57:34.619 [planetinfo.record]: population = 24; 13:57:34.619 [planetinfo.record]: productivity = 3840; 13:57:34.619 [planetinfo.record]: name = "Anenus"; 13:57:34.619 [planetinfo.record]: inhabitant = "Human Colonial"; 13:57:34.620 [planetinfo.record]: inhabitants = "Human Colonials"; 13:57:34.620 [planetinfo.record]: description = "This world is plagued by occasional solar activity."; 13:57:34.640 [planetinfo.record]: air_color = 0.673076, 0.814054, 0.945668, 1; 13:57:34.640 [planetinfo.record]: cloud_alpha = 1.000000; 13:57:34.640 [planetinfo.record]: cloud_color = 0.61676, 0.839844, 0.719588, 1; 13:57:34.640 [planetinfo.record]: cloud_fraction = 0.410000; 13:57:34.640 [planetinfo.record]: land_color = 0.541016, 0.334635, 0.0126801, 1; 13:57:34.640 [planetinfo.record]: land_fraction = 0.450000; 13:57:34.640 [planetinfo.record]: polar_cloud_color = 0.73218, 0.87793, 0.799361, 1; 13:57:34.640 [planetinfo.record]: polar_land_color = 0.945898, 0.855691, 0.714966, 1; 13:57:34.640 [planetinfo.record]: polar_sea_color = 0.939844, 0.902945, 0.882939, 1; 13:57:34.640 [planetinfo.record]: sea_color = 0.601562, 0.507091, 0.455872, 1; 13:57:34.640 [planetinfo.record]: rotation_speed = 0.004298 13:57:34.640 [planetinfo.record]: planet zpos = 523620.000000 13:57:34.640 [planetinfo.record]: sun_radius = 161933.859863 13:57:34.640 [planetinfo.record]: sun_vector = 0.138 -0.980 -0.142 13:57:34.640 [planetinfo.record]: sun_distance = 1105420 13:57:34.640 [planetinfo.record]: corona_flare = 0.096111 13:57:34.640 [planetinfo.record]: corona_hues = 0.784897 13:57:34.641 [planetinfo.record]: sun_color = 0.342969, 0.703275, 0.736606, 1 13:57:34.641 [planetinfo.record]: corona_shimmer = 0.803511 13:57:34.641 [planetinfo.record]: station_vector = -0.004 0.828 0.561 13:57:34.641 [planetinfo.record]: station = coriolis 13:57:39.368 [PLANETINFO OVER]: Done 13:57:39.369 [PLANETINFO LOGGING]: 5 222 13:57:40.374 [planetinfo.record]: seed = 133 21 234 15 13:57:40.374 [planetinfo.record]: coordinates = 21 232 13:57:40.374 [planetinfo.record]: government = 0; 13:57:40.374 [planetinfo.record]: economy = 2; 13:57:40.374 [planetinfo.record]: techlevel = 6; 13:57:40.374 [planetinfo.record]: population = 27; 13:57:40.374 [planetinfo.record]: productivity = 6912; 13:57:40.374 [planetinfo.record]: name = "Adien"; 13:57:40.374 [planetinfo.record]: inhabitant = "Green Furry Rodent"; 13:57:40.374 [planetinfo.record]: inhabitants = "Green Furry Rodents"; 13:57:40.374 [planetinfo.record]: description = "Adien is mildly well known for its hoopy casinos and its vast dense forests."; 13:57:40.381 [planetinfo.record]: air_color = 0.574805, 0.471987, 0.946969, 1; 13:57:40.381 [planetinfo.record]: cloud_alpha = 1.000000; 13:57:40.381 [planetinfo.record]: cloud_color = 0.586876, 0.0791016, 0.84375, 1; 13:57:40.381 [planetinfo.record]: cloud_fraction = 0.230000; 13:57:40.381 [planetinfo.record]: land_color = 0.445236, 0.374359, 0.509766, 1; 13:57:40.381 [planetinfo.record]: land_fraction = 0.600000; 13:57:40.381 [planetinfo.record]: polar_cloud_color = 0.712303, 0.381427, 0.879687, 1; 13:57:40.381 [planetinfo.record]: polar_land_color = 0.91899, 0.886002, 0.949023, 1; 13:57:40.381 [planetinfo.record]: polar_sea_color = 0.866959, 0.919727, 0.847873, 1; 13:57:40.381 [planetinfo.record]: sea_color = 0.618513, 0.802734, 0.55188, 1; 13:57:40.381 [planetinfo.record]: rotation_speed = 0.002627 13:57:40.381 [planetinfo.record]: planet zpos = 868010.000000 13:57:40.381 [planetinfo.record]: sun_radius = 153583.429718 13:57:40.382 [planetinfo.record]: sun_vector = 0.565 -0.574 -0.593 13:57:40.382 [planetinfo.record]: sun_distance = 1335400 13:57:40.382 [planetinfo.record]: corona_flare = 0.018677 13:57:40.382 [planetinfo.record]: corona_hues = 0.771568 13:57:40.382 [planetinfo.record]: sun_color = 0.845963, 0.820159, 0.66074, 1 13:57:40.382 [planetinfo.record]: corona_shimmer = 0.598962 13:57:40.382 [planetinfo.record]: station_vector = 0.992 -0.036 0.120 13:57:40.382 [planetinfo.record]: station = coriolis 13:57:45.171 [PLANETINFO OVER]: Done 13:57:45.172 [PLANETINFO LOGGING]: 5 223 13:57:46.176 [planetinfo.record]: seed = 73 80 168 144 13:57:46.176 [planetinfo.record]: coordinates = 80 50 13:57:46.176 [planetinfo.record]: government = 1; 13:57:46.176 [planetinfo.record]: economy = 2; 13:57:46.176 [planetinfo.record]: techlevel = 6; 13:57:46.176 [planetinfo.record]: population = 28; 13:57:46.176 [planetinfo.record]: productivity = 8960; 13:57:46.177 [planetinfo.record]: name = "Erberaor"; 13:57:46.177 [planetinfo.record]: inhabitant = "Black Horned Lizard"; 13:57:46.177 [planetinfo.record]: inhabitants = "Black Horned Lizards"; 13:57:46.177 [planetinfo.record]: description = "This world is very notable for the Erberaorian tree grub."; 13:57:46.196 [planetinfo.record]: air_color = 0.706538, 0.835647, 0.885832, 1; 13:57:46.196 [planetinfo.record]: cloud_alpha = 1.000000; 13:57:46.196 [planetinfo.record]: cloud_color = 0.655764, 0.660156, 0.654999, 1; 13:57:46.196 [planetinfo.record]: cloud_fraction = 0.470000; 13:57:46.196 [planetinfo.record]: land_color = 0.410335, 0.361023, 0.507812, 1; 13:57:46.196 [planetinfo.record]: land_fraction = 0.570000; 13:57:46.197 [planetinfo.record]: polar_cloud_color = 0.793756, 0.79707, 0.793178, 1; 13:57:46.197 [planetinfo.record]: polar_land_color = 0.903667, 0.880623, 0.949219, 1; 13:57:46.197 [planetinfo.record]: polar_sea_color = 0.854971, 0.918359, 0.843922, 1; 13:57:46.197 [planetinfo.record]: sea_color = 0.591003, 0.816406, 0.551712, 1; 13:57:46.197 [planetinfo.record]: rotation_speed = 0.002617 13:57:46.197 [planetinfo.record]: planet zpos = 318560.000000 13:57:46.197 [planetinfo.record]: sun_radius = 62360.598145 13:57:46.197 [planetinfo.record]: sun_vector = -0.698 -0.615 0.366 13:57:46.197 [planetinfo.record]: sun_distance = 550240 13:57:46.197 [planetinfo.record]: corona_flare = 0.047951 13:57:46.197 [planetinfo.record]: corona_hues = 0.938103 13:57:46.197 [planetinfo.record]: sun_color = 0.758038, 0.480194, 0.429869, 1 13:57:46.197 [planetinfo.record]: corona_shimmer = 0.396952 13:57:46.197 [planetinfo.record]: station_vector = -0.379 0.877 0.296 13:57:46.197 [planetinfo.record]: station = coriolis 13:57:50.269 [PLANETINFO OVER]: Done 13:57:50.270 [PLANETINFO LOGGING]: 5 224 13:57:51.279 [planetinfo.record]: seed = 73 153 234 161 13:57:51.280 [planetinfo.record]: coordinates = 153 244 13:57:51.280 [planetinfo.record]: government = 1; 13:57:51.280 [planetinfo.record]: economy = 6; 13:57:51.280 [planetinfo.record]: techlevel = 3; 13:57:51.280 [planetinfo.record]: population = 20; 13:57:51.280 [planetinfo.record]: productivity = 3200; 13:57:51.280 [planetinfo.record]: name = "Leamais"; 13:57:51.280 [planetinfo.record]: inhabitant = "Large Harmless Furry Feline"; 13:57:51.280 [planetinfo.record]: inhabitants = "Large Harmless Furry Felines"; 13:57:51.280 [planetinfo.record]: description = "The planet Leamais is an unremarkable dump."; 13:57:51.284 [planetinfo.record]: air_color = 0.695651, 0.605123, 0.853312, 1; 13:57:51.285 [planetinfo.record]: cloud_alpha = 1.000000; 13:57:51.285 [planetinfo.record]: cloud_color = 0.5625, 0.408691, 0.545677, 1; 13:57:51.285 [planetinfo.record]: cloud_fraction = 0.550000; 13:57:51.285 [planetinfo.record]: land_color = 0.355402, 0.307961, 0.583984, 1; 13:57:51.285 [planetinfo.record]: land_fraction = 0.350000; 13:57:51.285 [planetinfo.record]: polar_cloud_color = 0.753125, 0.624417, 0.739048, 1; 13:57:51.285 [planetinfo.record]: polar_land_color = 0.849461, 0.830338, 0.941602, 1; 13:57:51.285 [planetinfo.record]: polar_sea_color = 0.931055, 0.92865, 0.8695, 1; 13:57:51.285 [planetinfo.record]: sea_color = 0.689453, 0.682331, 0.507125, 1; 13:57:51.285 [planetinfo.record]: rotation_speed = 0.000370 13:57:51.285 [planetinfo.record]: planet zpos = 387000.000000 13:57:51.285 [planetinfo.record]: sun_radius = 87720.039368 13:57:51.285 [planetinfo.record]: sun_vector = -0.452 -0.637 0.625 13:57:51.285 [planetinfo.record]: sun_distance = 612750 13:57:51.286 [planetinfo.record]: corona_flare = 0.038225 13:57:51.286 [planetinfo.record]: corona_hues = 0.725410 13:57:51.286 [planetinfo.record]: sun_color = 0.719925, 0.683957, 0.632263, 1 13:57:51.286 [planetinfo.record]: corona_shimmer = 1.470208 13:57:51.286 [planetinfo.record]: station_vector = 0.750 -0.566 0.343 13:57:51.286 [planetinfo.record]: station = coriolis 13:57:55.496 [PLANETINFO OVER]: Done 13:57:55.497 [PLANETINFO LOGGING]: 5 225 13:57:56.513 [planetinfo.record]: seed = 21 61 64 216 13:57:56.513 [planetinfo.record]: coordinates = 61 107 13:57:56.513 [planetinfo.record]: government = 2; 13:57:56.513 [planetinfo.record]: economy = 3; 13:57:56.513 [planetinfo.record]: techlevel = 6; 13:57:56.513 [planetinfo.record]: population = 30; 13:57:56.513 [planetinfo.record]: productivity = 10080; 13:57:56.513 [planetinfo.record]: name = "Edla"; 13:57:56.513 [planetinfo.record]: inhabitant = "Human Colonial"; 13:57:56.513 [planetinfo.record]: inhabitants = "Human Colonials"; 13:57:56.513 [planetinfo.record]: description = "Edla is most famous for its pink Edlaian Thno Thnoweed plantations and Zero-G hockey."; 13:57:56.547 [planetinfo.record]: air_color = 0.702889, 0.788908, 0.941115, 1; 13:57:56.547 [planetinfo.record]: cloud_alpha = 1.000000; 13:57:56.547 [planetinfo.record]: cloud_color = 0.693855, 0.826172, 0.816868, 1; 13:57:56.547 [planetinfo.record]: cloud_fraction = 0.470000; 13:57:56.547 [planetinfo.record]: land_color = 0.66, 0.446459, 0.219141, 1; 13:57:56.547 [planetinfo.record]: land_fraction = 0.450000; 13:57:56.547 [planetinfo.record]: polar_cloud_color = 0.784514, 0.871777, 0.865642, 1; 13:57:56.547 [planetinfo.record]: polar_land_color = 0.934, 0.858452, 0.778029, 1; 13:57:56.547 [planetinfo.record]: polar_sea_color = 0.938672, 0.880922, 0.887689, 1; 13:57:56.547 [planetinfo.record]: sea_color = 0.613281, 0.462357, 0.480043, 1; 13:57:56.547 [planetinfo.record]: rotation_speed = 0.003428 13:57:56.548 [planetinfo.record]: planet zpos = 640250.000000 13:57:56.548 [planetinfo.record]: sun_radius = 95590.209961 13:57:56.548 [planetinfo.record]: sun_vector = -0.336 -0.525 -0.782 13:57:56.549 [planetinfo.record]: sun_distance = 1034250 13:57:56.549 [planetinfo.record]: corona_flare = 0.063179 13:57:56.549 [planetinfo.record]: corona_hues = 0.914093 13:57:56.549 [planetinfo.record]: sun_color = 0.64035, 0.664399, 0.732742, 1 13:57:56.549 [planetinfo.record]: corona_shimmer = 0.704431 13:57:56.549 [planetinfo.record]: station_vector = -0.645 0.710 0.284 13:57:56.550 [planetinfo.record]: station = coriolis 13:58:01.253 [PLANETINFO OVER]: Done 13:58:01.254 [PLANETINFO LOGGING]: 5 226 13:58:02.257 [planetinfo.record]: seed = 157 238 250 4 13:58:02.258 [planetinfo.record]: coordinates = 238 149 13:58:02.258 [planetinfo.record]: government = 3; 13:58:02.258 [planetinfo.record]: economy = 5; 13:58:02.258 [planetinfo.record]: techlevel = 6; 13:58:02.258 [planetinfo.record]: population = 33; 13:58:02.258 [planetinfo.record]: productivity = 9240; 13:58:02.258 [planetinfo.record]: name = "Zaesisma"; 13:58:02.258 [planetinfo.record]: inhabitant = "Fierce Green Bony Lobster"; 13:58:02.258 [planetinfo.record]: inhabitants = "Fierce Green Bony Lobsters"; 13:58:02.258 [planetinfo.record]: description = "This world is a revolting little planet."; 13:58:02.272 [planetinfo.record]: air_color = 0.715367, 0.591636, 0.876076, 1; 13:58:02.272 [planetinfo.record]: cloud_alpha = 1.000000; 13:58:02.272 [planetinfo.record]: cloud_color = 0.630859, 0.394287, 0.555082, 1; 13:58:02.272 [planetinfo.record]: cloud_fraction = 0.300000; 13:58:02.272 [planetinfo.record]: land_color = 0.567188, 0.66, 0.594741, 1; 13:58:02.272 [planetinfo.record]: land_fraction = 0.270000; 13:58:02.272 [planetinfo.record]: polar_cloud_color = 0.783887, 0.600163, 0.725038, 1; 13:58:02.272 [planetinfo.record]: polar_land_color = 0.901164, 0.934, 0.910912, 1; 13:58:02.272 [planetinfo.record]: polar_sea_color = 0.880001, 0.92832, 0.90114, 1; 13:58:02.272 [planetinfo.record]: sea_color = 0.567558, 0.716797, 0.63285, 1; 13:58:02.272 [planetinfo.record]: rotation_speed = 0.000468 13:58:02.272 [planetinfo.record]: planet zpos = 448580.000000 13:58:02.272 [planetinfo.record]: sun_radius = 94963.960876 13:58:02.272 [planetinfo.record]: sun_vector = 0.020 0.857 0.516 13:58:02.272 [planetinfo.record]: sun_distance = 815600 13:58:02.272 [planetinfo.record]: corona_flare = 0.038914 13:58:02.272 [planetinfo.record]: corona_hues = 0.849556 13:58:02.272 [planetinfo.record]: sun_color = 0.585322, 0.625965, 0.730646, 1 13:58:02.273 [planetinfo.record]: corona_shimmer = 0.345829 13:58:02.273 [planetinfo.record]: station_vector = 0.713 -0.694 0.102 13:58:02.273 [planetinfo.record]: station = coriolis 13:58:06.904 [PLANETINFO OVER]: Done 13:58:06.905 [PLANETINFO LOGGING]: 5 227 13:58:07.908 [planetinfo.record]: seed = 113 11 232 17 13:58:07.908 [planetinfo.record]: coordinates = 11 125 13:58:07.909 [planetinfo.record]: government = 6; 13:58:07.909 [planetinfo.record]: economy = 5; 13:58:07.909 [planetinfo.record]: techlevel = 8; 13:58:07.909 [planetinfo.record]: population = 44; 13:58:07.909 [planetinfo.record]: productivity = 17600; 13:58:07.909 [planetinfo.record]: name = "Atquti"; 13:58:07.909 [planetinfo.record]: inhabitant = "Green Insect"; 13:58:07.909 [planetinfo.record]: inhabitants = "Green Insects"; 13:58:07.909 [planetinfo.record]: description = "Atquti is mildly well known for killer Xeones gargle blasters."; 13:58:07.924 [planetinfo.record]: air_color = 0.446409, 0.532337, 0.919652, 1; 13:58:07.924 [planetinfo.record]: cloud_alpha = 1.000000; 13:58:07.924 [planetinfo.record]: cloud_color = 0.0416565, 0.42419, 0.761719, 1; 13:58:07.924 [planetinfo.record]: cloud_fraction = 0.430000; 13:58:07.924 [planetinfo.record]: land_color = 0.152109, 0.290986, 0.66, 1; 13:58:07.924 [planetinfo.record]: land_fraction = 0.530000; 13:58:07.924 [planetinfo.record]: polar_cloud_color = 0.344846, 0.60937, 0.842773, 1; 13:58:07.924 [planetinfo.record]: polar_land_color = 0.754314, 0.803447, 0.934, 1; 13:58:07.924 [planetinfo.record]: polar_sea_color = 0.900374, 0.93418, 0.885008, 1; 13:58:07.924 [planetinfo.record]: sea_color = 0.562928, 0.658203, 0.519621, 1; 13:58:07.924 [planetinfo.record]: rotation_speed = 0.001611 13:58:07.925 [planetinfo.record]: planet zpos = 339130.000000 13:58:07.925 [planetinfo.record]: sun_radius = 88886.517944 13:58:07.925 [planetinfo.record]: sun_vector = -0.124 -0.974 -0.190 13:58:07.925 [planetinfo.record]: sun_distance = 678260 13:58:07.925 [planetinfo.record]: corona_flare = 0.034032 13:58:07.925 [planetinfo.record]: corona_hues = 0.513443 13:58:07.925 [planetinfo.record]: sun_color = 0.728326, 0.824182, 0.824942, 1 13:58:07.925 [planetinfo.record]: corona_shimmer = 0.523593 13:58:07.926 [planetinfo.record]: station_vector = 0.382 0.792 0.475 13:58:07.926 [planetinfo.record]: station = coriolis 13:58:13.171 [PLANETINFO OVER]: Done 13:58:13.172 [PLANETINFO LOGGING]: 5 228 13:58:14.176 [planetinfo.record]: seed = 1 100 26 182 13:58:14.176 [planetinfo.record]: coordinates = 100 183 13:58:14.176 [planetinfo.record]: government = 0; 13:58:14.176 [planetinfo.record]: economy = 7; 13:58:14.176 [planetinfo.record]: techlevel = 0; 13:58:14.176 [planetinfo.record]: population = 8; 13:58:14.176 [planetinfo.record]: productivity = 768; 13:58:14.176 [planetinfo.record]: name = "Veatma"; 13:58:14.176 [planetinfo.record]: inhabitant = "Human Colonial"; 13:58:14.176 [planetinfo.record]: inhabitants = "Human Colonials"; 13:58:14.176 [planetinfo.record]: description = "Veatma is ravaged by dreadful civil war."; 13:58:14.187 [planetinfo.record]: air_color = 0.43308, 0.808501, 0.846809, 1; 13:58:14.188 [planetinfo.record]: cloud_alpha = 1.000000; 13:58:14.188 [planetinfo.record]: cloud_color = 0.406862, 0.542969, 0.072113, 1; 13:58:14.190 [planetinfo.record]: cloud_fraction = 0.190000; 13:58:14.190 [planetinfo.record]: land_color = 0.66, 0.385732, 0.265547, 1; 13:58:14.190 [planetinfo.record]: land_fraction = 0.500000; 13:58:14.190 [planetinfo.record]: polar_cloud_color = 0.627721, 0.744336, 0.340912, 1; 13:58:14.190 [planetinfo.record]: polar_land_color = 0.934, 0.836967, 0.794447, 1; 13:58:14.191 [planetinfo.record]: polar_sea_color = 0.93418, 0.903312, 0.87853, 1; 13:58:14.191 [planetinfo.record]: sea_color = 0.658203, 0.571207, 0.501366, 1; 13:58:14.191 [planetinfo.record]: rotation_speed = 0.003184 13:58:14.191 [planetinfo.record]: planet zpos = 667800.000000 13:58:14.191 [planetinfo.record]: sun_radius = 101044.286499 13:58:14.191 [planetinfo.record]: sun_vector = 0.192 0.096 -0.977 13:58:14.191 [planetinfo.record]: sun_distance = 1068480 13:58:14.191 [planetinfo.record]: corona_flare = 0.092384 13:58:14.191 [planetinfo.record]: corona_hues = 0.612427 13:58:14.192 [planetinfo.record]: sun_color = 0.218195, 0.252158, 0.660635, 1 13:58:14.193 [planetinfo.record]: corona_shimmer = 0.292665 13:58:14.193 [planetinfo.record]: station_vector = 0.488 -0.556 0.672 13:58:14.193 [planetinfo.record]: station = coriolis 13:58:19.579 [PLANETINFO OVER]: Done 13:58:19.580 [PLANETINFO LOGGING]: 5 229 13:58:20.584 [planetinfo.record]: seed = 221 115 160 49 13:58:20.584 [planetinfo.record]: coordinates = 115 235 13:58:20.584 [planetinfo.record]: government = 3; 13:58:20.584 [planetinfo.record]: economy = 3; 13:58:20.584 [planetinfo.record]: techlevel = 9; 13:58:20.585 [planetinfo.record]: population = 43; 13:58:20.585 [planetinfo.record]: productivity = 16856; 13:58:20.585 [planetinfo.record]: name = "Atatveor"; 13:58:20.585 [planetinfo.record]: inhabitant = "Red Slimy Frog"; 13:58:20.585 [planetinfo.record]: inhabitants = "Red Slimy Frogs"; 13:58:20.585 [planetinfo.record]: description = "This planet is reasonably noted for its inhabitants’ eccentric shyness."; 13:58:20.620 [planetinfo.record]: air_color = 0.577717, 0.954773, 0.939658, 1; 13:58:20.620 [planetinfo.record]: cloud_alpha = 1.000000; 13:58:20.620 [planetinfo.record]: cloud_color = 0.867188, 0.811612, 0.35907, 1; 13:58:20.620 [planetinfo.record]: cloud_fraction = 0.430000; 13:58:20.620 [planetinfo.record]: land_color = 0.66, 0.563643, 0.185625, 1; 13:58:20.620 [planetinfo.record]: land_fraction = 0.450000; 13:58:20.620 [planetinfo.record]: polar_cloud_color = 0.890234, 0.854577, 0.564221, 1; 13:58:20.620 [planetinfo.record]: polar_land_color = 0.934, 0.89991, 0.766172, 1; 13:58:20.620 [planetinfo.record]: polar_sea_color = 0.890614, 0.929102, 0.87312, 1; 13:58:20.620 [planetinfo.record]: sea_color = 0.591507, 0.708984, 0.538108, 1; 13:58:20.620 [planetinfo.record]: rotation_speed = 0.002422 13:58:20.620 [planetinfo.record]: planet zpos = 414310.000000 13:58:20.620 [planetinfo.record]: sun_radius = 95572.555084 13:58:20.620 [planetinfo.record]: sun_vector = -0.045 -0.998 0.054 13:58:20.620 [planetinfo.record]: sun_distance = 509920 13:58:20.620 [planetinfo.record]: corona_flare = 0.035777 13:58:20.620 [planetinfo.record]: corona_hues = 0.512352 13:58:20.620 [planetinfo.record]: sun_color = 0.67529, 0.656422, 0.571224, 1 13:58:20.621 [planetinfo.record]: corona_shimmer = 0.332684 13:58:20.621 [planetinfo.record]: station_vector = -0.770 -0.367 0.523 13:58:20.621 [planetinfo.record]: station = coriolis 13:58:24.805 [PLANETINFO OVER]: Done 13:58:24.806 [PLANETINFO LOGGING]: 5 230 13:58:25.827 [planetinfo.record]: seed = 245 249 74 194 13:58:25.827 [planetinfo.record]: coordinates = 249 54 13:58:25.827 [planetinfo.record]: government = 6; 13:58:25.827 [planetinfo.record]: economy = 6; 13:58:25.827 [planetinfo.record]: techlevel = 5; 13:58:25.827 [planetinfo.record]: population = 33; 13:58:25.827 [planetinfo.record]: productivity = 10560; 13:58:25.827 [planetinfo.record]: name = "Xebeaza"; 13:58:25.827 [planetinfo.record]: inhabitant = "Human Colonial"; 13:58:25.827 [planetinfo.record]: inhabitants = "Human Colonials"; 13:58:25.827 [planetinfo.record]: description = "This planet is most fabled for mud hockey but ravaged by occasional earthquakes."; 13:58:25.832 [planetinfo.record]: air_color = 0.666818, 0.842906, 0.794618, 1; 13:58:25.832 [planetinfo.record]: cloud_alpha = 1.000000; 13:58:25.834 [planetinfo.record]: cloud_color = 0.53125, 0.519447, 0.516724, 1; 13:58:25.834 [planetinfo.record]: cloud_fraction = 0.440000; 13:58:25.834 [planetinfo.record]: land_color = 0.124741, 0.638672, 0.554355, 1; 13:58:25.834 [planetinfo.record]: land_fraction = 0.680000; 13:58:25.834 [planetinfo.record]: polar_cloud_color = 0.739062, 0.7288, 0.726432, 1; 13:58:25.834 [planetinfo.record]: polar_land_color = 0.747809, 0.936133, 0.905236, 1; 13:58:25.834 [planetinfo.record]: polar_sea_color = 0.944336, 0.895528, 0.890479, 1; 13:58:25.834 [planetinfo.record]: sea_color = 0.556641, 0.441562, 0.429657, 1; 13:58:25.834 [planetinfo.record]: rotation_speed = 0.003368 13:58:25.834 [planetinfo.record]: planet zpos = 321930.000000 13:58:25.834 [planetinfo.record]: sun_radius = 110539.539337 13:58:25.834 [planetinfo.record]: sun_vector = 0.054 -0.987 -0.154 13:58:25.835 [planetinfo.record]: sun_distance = 608090 13:58:25.835 [planetinfo.record]: corona_flare = 0.075888 13:58:25.835 [planetinfo.record]: corona_hues = 0.592964 13:58:25.835 [planetinfo.record]: sun_color = 0.648663, 0.750129, 0.756531, 1 13:58:25.835 [planetinfo.record]: corona_shimmer = 0.377068 13:58:25.835 [planetinfo.record]: station_vector = 0.671 -0.308 0.674 13:58:25.835 [planetinfo.record]: station = coriolis 13:58:30.397 [PLANETINFO OVER]: Done 13:58:30.398 [PLANETINFO LOGGING]: 5 231 13:58:31.400 [planetinfo.record]: seed = 217 100 104 7 13:58:31.400 [planetinfo.record]: coordinates = 100 175 13:58:31.400 [planetinfo.record]: government = 3; 13:58:31.400 [planetinfo.record]: economy = 7; 13:58:31.400 [planetinfo.record]: techlevel = 2; 13:58:31.401 [planetinfo.record]: population = 19; 13:58:31.401 [planetinfo.record]: productivity = 3192; 13:58:31.401 [planetinfo.record]: name = "Soansoar"; 13:58:31.401 [planetinfo.record]: inhabitant = "Human Colonial"; 13:58:31.401 [planetinfo.record]: inhabitants = "Human Colonials"; 13:58:31.401 [planetinfo.record]: description = "The world Soansoar is a boring planet."; 13:58:31.412 [planetinfo.record]: air_color = 0.660594, 0.767835, 0.978188, 1; 13:58:31.412 [planetinfo.record]: cloud_alpha = 1.000000; 13:58:31.412 [planetinfo.record]: cloud_color = 0.585938, 0.9375, 0.9375, 1; 13:58:31.415 [planetinfo.record]: cloud_fraction = 0.450000; 13:58:31.415 [planetinfo.record]: land_color = 0.66, 0.128906, 0.614359, 1; 13:58:31.415 [planetinfo.record]: land_fraction = 0.360000; 13:58:31.415 [planetinfo.record]: polar_cloud_color = 0.705811, 0.921875, 0.921875, 1; 13:58:31.415 [planetinfo.record]: polar_land_color = 0.934, 0.746105, 0.917853, 1; 13:58:31.416 [planetinfo.record]: polar_sea_color = 0.937695, 0.935628, 0.884767, 1; 13:58:31.416 [planetinfo.record]: sea_color = 0.623047, 0.617552, 0.482375, 1; 13:58:31.416 [planetinfo.record]: rotation_speed = 0.000682 13:58:31.416 [planetinfo.record]: planet zpos = 612040.000000 13:58:31.416 [planetinfo.record]: sun_radius = 102619.687500 13:58:31.416 [planetinfo.record]: sun_vector = -0.131 -0.329 0.935 13:58:31.416 [planetinfo.record]: sun_distance = 988680 13:58:31.416 [planetinfo.record]: corona_flare = 0.058424 13:58:31.416 [planetinfo.record]: corona_hues = 0.989151 13:58:31.416 [planetinfo.record]: sun_color = 0.723916, 0.793185, 0.797284, 1 13:58:31.416 [planetinfo.record]: corona_shimmer = 0.255581 13:58:31.416 [planetinfo.record]: station_vector = 0.251 0.386 0.887 13:58:31.416 [planetinfo.record]: station = coriolis 13:58:35.865 [PLANETINFO OVER]: Done 13:58:35.865 [PLANETINFO LOGGING]: 5 232 13:58:36.869 [planetinfo.record]: seed = 249 170 138 78 13:58:36.869 [planetinfo.record]: coordinates = 170 135 13:58:36.869 [planetinfo.record]: government = 7; 13:58:36.869 [planetinfo.record]: economy = 7; 13:58:36.869 [planetinfo.record]: techlevel = 6; 13:58:36.869 [planetinfo.record]: population = 39; 13:58:36.869 [planetinfo.record]: productivity = 10296; 13:58:36.869 [planetinfo.record]: name = "Relequar"; 13:58:36.869 [planetinfo.record]: inhabitant = "Yellow Furry Insect"; 13:58:36.869 [planetinfo.record]: inhabitants = "Yellow Furry Insects"; 13:58:36.869 [planetinfo.record]: description = "This world is reasonably notable for its fabulous cuisine but beset by evil tree snakes."; 13:58:36.888 [planetinfo.record]: air_color = 0.580424, 0.921604, 0.822717, 1; 13:58:36.888 [planetinfo.record]: cloud_alpha = 1.000000; 13:58:36.888 [planetinfo.record]: cloud_color = 0.767578, 0.430029, 0.374794, 1; 13:58:36.888 [planetinfo.record]: cloud_fraction = 0.350000; 13:58:36.888 [planetinfo.record]: land_color = 0.66, 0.443478, 0.438281, 1; 13:58:36.888 [planetinfo.record]: land_fraction = 0.330000; 13:58:36.888 [planetinfo.record]: polar_cloud_color = 0.84541, 0.61305, 0.575028, 1; 13:58:36.888 [planetinfo.record]: polar_land_color = 0.934, 0.857397, 0.855559, 1; 13:58:36.888 [planetinfo.record]: polar_sea_color = 0.870708, 0.868132, 0.915234, 1; 13:58:36.888 [planetinfo.record]: sea_color = 0.682701, 0.673158, 0.847656, 1; 13:58:36.888 [planetinfo.record]: rotation_speed = 0.000959 13:58:36.888 [planetinfo.record]: planet zpos = 919800.000000 13:58:36.888 [planetinfo.record]: sun_radius = 169756.745911 13:58:36.888 [planetinfo.record]: sun_vector = 0.167 -0.546 -0.821 13:58:36.888 [planetinfo.record]: sun_distance = 1248300 13:58:36.888 [planetinfo.record]: corona_flare = 0.088728 13:58:36.888 [planetinfo.record]: corona_hues = 0.863571 13:58:36.888 [planetinfo.record]: sun_color = 0.666287, 0.661871, 0.562404, 1 13:58:36.888 [planetinfo.record]: corona_shimmer = 0.376722 13:58:36.889 [planetinfo.record]: station_vector = -0.967 -0.117 0.225 13:58:36.889 [planetinfo.record]: station = coriolis 13:58:42.033 [PLANETINFO OVER]: Done 13:58:42.034 [PLANETINFO LOGGING]: 5 233 13:58:43.039 [planetinfo.record]: seed = 229 74 64 71 13:58:43.039 [planetinfo.record]: coordinates = 74 122 13:58:43.039 [planetinfo.record]: government = 4; 13:58:43.039 [planetinfo.record]: economy = 2; 13:58:43.039 [planetinfo.record]: techlevel = 9; 13:58:43.039 [planetinfo.record]: population = 43; 13:58:43.039 [planetinfo.record]: productivity = 22016; 13:58:43.039 [planetinfo.record]: name = "Sodionbe"; 13:58:43.039 [planetinfo.record]: inhabitant = "Human Colonial"; 13:58:43.039 [planetinfo.record]: inhabitants = "Human Colonials"; 13:58:43.039 [planetinfo.record]: description = "The world Sodionbe is a dull world."; 13:58:43.048 [planetinfo.record]: air_color = 0.553702, 0.941019, 0.991846, 1; 13:58:43.048 [planetinfo.record]: cloud_alpha = 1.000000; 13:58:43.048 [planetinfo.record]: cloud_color = 0.723017, 0.978516, 0.267563, 1; 13:58:43.049 [planetinfo.record]: cloud_fraction = 0.340000; 13:58:43.049 [planetinfo.record]: land_color = 0.554688, 0.534307, 0.524353, 1; 13:58:43.049 [planetinfo.record]: land_fraction = 0.260000; 13:58:43.049 [planetinfo.record]: polar_cloud_color = 0.786877, 0.940332, 0.513326, 1; 13:58:43.049 [planetinfo.record]: polar_land_color = 0.944531, 0.935855, 0.931618, 1; 13:58:43.049 [planetinfo.record]: polar_sea_color = 0.92793, 0.926939, 0.864497, 1; 13:58:43.049 [planetinfo.record]: sea_color = 0.720703, 0.717624, 0.523636, 1; 13:58:43.049 [planetinfo.record]: rotation_speed = 0.001488 13:58:43.054 [planetinfo.record]: planet zpos = 561840.000000 13:58:43.054 [planetinfo.record]: sun_radius = 127695.520325 13:58:43.054 [planetinfo.record]: sun_vector = 0.748 -0.404 0.526 13:58:43.054 [planetinfo.record]: sun_distance = 936400 13:58:43.054 [planetinfo.record]: corona_flare = 0.003441 13:58:43.054 [planetinfo.record]: corona_hues = 0.794914 13:58:43.054 [planetinfo.record]: sun_color = 0.198366, 0.426541, 0.737805, 1 13:58:43.054 [planetinfo.record]: corona_shimmer = 0.382455 13:58:43.054 [planetinfo.record]: station_vector = 0.916 -0.134 0.378 13:58:43.054 [planetinfo.record]: station = coriolis 13:58:49.149 [PLANETINFO OVER]: Done 13:58:49.150 [PLANETINFO LOGGING]: 5 234 13:58:50.155 [planetinfo.record]: seed = 141 243 218 159 13:58:50.155 [planetinfo.record]: coordinates = 243 159 13:58:50.155 [planetinfo.record]: government = 1; 13:58:50.155 [planetinfo.record]: economy = 7; 13:58:50.155 [planetinfo.record]: techlevel = 4; 13:58:50.155 [planetinfo.record]: population = 25; 13:58:50.155 [planetinfo.record]: productivity = 3000; 13:58:50.155 [planetinfo.record]: name = "Onenbi"; 13:58:50.155 [planetinfo.record]: inhabitant = "Black Fat Insect"; 13:58:50.155 [planetinfo.record]: inhabitants = "Black Fat Insects"; 13:58:50.155 [planetinfo.record]: description = "The world Onenbi is beset by dreadful earthquakes."; 13:58:50.158 [planetinfo.record]: air_color = 0.619465, 0.832597, 0.977537, 1; 13:58:50.158 [planetinfo.record]: cloud_alpha = 1.000000; 13:58:50.158 [planetinfo.record]: cloud_color = 0.467773, 0.935547, 0.573753, 1; 13:58:50.158 [planetinfo.record]: cloud_fraction = 0.140000; 13:58:50.158 [planetinfo.record]: land_color = 0.562716, 0.66, 0.551719, 1; 13:58:50.158 [planetinfo.record]: land_fraction = 0.260000; 13:58:50.158 [planetinfo.record]: polar_cloud_color = 0.633185, 0.920996, 0.698392, 1; 13:58:50.158 [planetinfo.record]: polar_land_color = 0.899582, 0.934, 0.895691, 1; 13:58:50.158 [planetinfo.record]: polar_sea_color = 0.713459, 0.935547, 0.71171, 1; 13:58:50.158 [planetinfo.record]: sea_color = 0.0325137, 0.644531, 0.0276947, 1; 13:58:50.158 [planetinfo.record]: rotation_speed = 0.001164 13:58:50.158 [planetinfo.record]: planet zpos = 758890.000000 13:58:50.158 [planetinfo.record]: sun_radius = 191420.511322 13:58:50.158 [planetinfo.record]: sun_vector = 0.506 -0.823 0.260 13:58:50.158 [planetinfo.record]: sun_distance = 1310810 13:58:50.158 [planetinfo.record]: corona_flare = 0.043619 13:58:50.158 [planetinfo.record]: corona_hues = 0.685921 13:58:50.158 [planetinfo.record]: sun_color = 0.78895, 0.769261, 0.655459, 1 13:58:50.158 [planetinfo.record]: corona_shimmer = 0.295473 13:58:50.159 [planetinfo.record]: station_vector = 0.030 0.898 0.439 13:58:50.159 [planetinfo.record]: station = coriolis 13:58:55.685 [PLANETINFO OVER]: Done 13:58:55.685 [PLANETINFO LOGGING]: 5 235 13:58:56.689 [planetinfo.record]: seed = 129 152 40 145 13:58:56.690 [planetinfo.record]: coordinates = 152 198 13:58:56.690 [planetinfo.record]: government = 0; 13:58:56.690 [planetinfo.record]: economy = 6; 13:58:56.690 [planetinfo.record]: techlevel = 1; 13:58:56.690 [planetinfo.record]: population = 11; 13:58:56.690 [planetinfo.record]: productivity = 1408; 13:58:56.690 [planetinfo.record]: name = "Ataor"; 13:58:56.690 [planetinfo.record]: inhabitant = "Human Colonial"; 13:58:56.690 [planetinfo.record]: inhabitants = "Human Colonials"; 13:58:56.690 [planetinfo.record]: description = "Ataor is famous for its inhabitants’ exceptional love for food blenders but scourged by frequent civil war."; 13:58:56.708 [planetinfo.record]: air_color = 0.662393, 0.870223, 0.844049, 1; 13:58:56.708 [planetinfo.record]: cloud_alpha = 1.000000; 13:58:56.708 [planetinfo.record]: cloud_color = 0.613281, 0.586892, 0.541412, 1; 13:58:56.708 [planetinfo.record]: cloud_fraction = 0.300000; 13:58:56.714 [planetinfo.record]: land_color = 0.591797, 0.386055, 0.588582, 1; 13:58:56.714 [planetinfo.record]: land_fraction = 0.650000; 13:58:56.714 [planetinfo.record]: polar_cloud_color = 0.775977, 0.755108, 0.719142, 1; 13:58:56.714 [planetinfo.record]: polar_land_color = 0.94082, 0.85905, 0.939543, 1; 13:58:56.714 [planetinfo.record]: polar_sea_color = 0.916992, 0.86697, 0.788936, 1; 13:58:56.715 [planetinfo.record]: sea_color = 0.830078, 0.648955, 0.366402, 1; 13:58:56.715 [planetinfo.record]: rotation_speed = 0.004815 13:58:56.715 [planetinfo.record]: planet zpos = 354640.000000 13:58:56.715 [planetinfo.record]: sun_radius = 68995.056152 13:58:56.715 [planetinfo.record]: sun_vector = -0.578 -0.220 -0.786 13:58:56.715 [planetinfo.record]: sun_distance = 612560 13:58:56.715 [planetinfo.record]: corona_flare = 0.046562 13:58:56.715 [planetinfo.record]: corona_hues = 0.510971 13:58:56.715 [planetinfo.record]: sun_color = 0.775421, 0.648664, 0.553838, 1 13:58:56.715 [planetinfo.record]: corona_shimmer = 0.295599 13:58:56.715 [planetinfo.record]: station_vector = 0.078 -0.995 0.062 13:58:56.715 [planetinfo.record]: station = coriolis 13:59:01.384 [PLANETINFO OVER]: Done 13:59:01.386 [PLANETINFO LOGGING]: 5 236 13:59:02.389 [planetinfo.record]: seed = 49 154 58 163 13:59:02.389 [planetinfo.record]: coordinates = 154 25 13:59:02.389 [planetinfo.record]: government = 6; 13:59:02.389 [planetinfo.record]: economy = 1; 13:59:02.389 [planetinfo.record]: techlevel = 11; 13:59:02.389 [planetinfo.record]: population = 52; 13:59:02.389 [planetinfo.record]: productivity = 37440; 13:59:02.389 [planetinfo.record]: name = "Geverare"; 13:59:02.389 [planetinfo.record]: inhabitant = "Human Colonial"; 13:59:02.389 [planetinfo.record]: inhabitants = "Human Colonials"; 13:59:02.389 [planetinfo.record]: description = "The planet Geverare is an unremarkable dump."; 13:59:02.395 [planetinfo.record]: air_color = 0.615367, 0.50313, 0.929408, 1; 13:59:02.395 [planetinfo.record]: cloud_alpha = 1.000000; 13:59:02.395 [planetinfo.record]: cloud_color = 0.67092, 0.176125, 0.791016, 1; 13:59:02.395 [planetinfo.record]: cloud_fraction = 0.200000; 13:59:02.395 [planetinfo.record]: land_color = 0.66, 0.603281, 0.64715, 1; 13:59:02.396 [planetinfo.record]: land_fraction = 0.370000; 13:59:02.396 [planetinfo.record]: polar_cloud_color = 0.774735, 0.440099, 0.855957, 1; 13:59:02.396 [planetinfo.record]: polar_land_color = 0.934, 0.913934, 0.929454, 1; 13:59:02.396 [planetinfo.record]: polar_sea_color = 0.94707, 0.912636, 0.899162, 1; 13:59:02.396 [planetinfo.record]: sea_color = 0.529297, 0.452319, 0.422197, 1; 13:59:02.396 [planetinfo.record]: rotation_speed = 0.003809 13:59:02.396 [planetinfo.record]: planet zpos = 448560.000000 13:59:02.396 [planetinfo.record]: sun_radius = 95774.842529 13:59:02.396 [planetinfo.record]: sun_vector = -0.559 -0.213 -0.802 13:59:02.396 [planetinfo.record]: sun_distance = 598080 13:59:02.396 [planetinfo.record]: corona_flare = 0.009210 13:59:02.396 [planetinfo.record]: corona_hues = 0.660027 13:59:02.396 [planetinfo.record]: sun_color = 0.712921, 0.396798, 0.384344, 1 13:59:02.396 [planetinfo.record]: corona_shimmer = 0.743359 13:59:02.397 [planetinfo.record]: station_vector = 0.614 -0.654 0.442 13:59:02.397 [planetinfo.record]: station = dodecahedron 13:59:06.516 [PLANETINFO OVER]: Done 13:59:06.517 [PLANETINFO LOGGING]: 5 237 13:59:07.519 [planetinfo.record]: seed = 45 142 32 121 13:59:07.520 [planetinfo.record]: coordinates = 142 148 13:59:07.520 [planetinfo.record]: government = 5; 13:59:07.520 [planetinfo.record]: economy = 4; 13:59:07.520 [planetinfo.record]: techlevel = 8; 13:59:07.520 [planetinfo.record]: population = 42; 13:59:07.520 [planetinfo.record]: productivity = 18144; 13:59:07.520 [planetinfo.record]: name = "Oranxe"; 13:59:07.520 [planetinfo.record]: inhabitant = "Human Colonial"; 13:59:07.520 [planetinfo.record]: inhabitants = "Human Colonials"; 13:59:07.520 [planetinfo.record]: description = "Oranxe is cursed by vicious vicious ants."; 13:59:07.546 [planetinfo.record]: air_color = 0.71228, 0.79407, 0.929408, 1; 13:59:07.546 [planetinfo.record]: cloud_alpha = 1.000000; 13:59:07.546 [planetinfo.record]: cloud_color = 0.710678, 0.791016, 0.781601, 1; 13:59:07.546 [planetinfo.record]: cloud_fraction = 0.050000; 13:59:07.547 [planetinfo.record]: land_color = 0.526824, 0.66, 0.366094, 1; 13:59:07.547 [planetinfo.record]: land_fraction = 0.310000; 13:59:07.547 [planetinfo.record]: polar_cloud_color = 0.801624, 0.855957, 0.84959, 1; 13:59:07.547 [planetinfo.record]: polar_land_color = 0.886884, 0.934, 0.83002, 1; 13:59:07.547 [planetinfo.record]: polar_sea_color = 0.943555, 0.885504, 0.926321, 1; 13:59:07.547 [planetinfo.record]: sea_color = 0.564453, 0.425545, 0.523215, 1; 13:59:07.547 [planetinfo.record]: rotation_speed = 0.000584 13:59:07.547 [planetinfo.record]: planet zpos = 631440.000000 13:59:07.547 [planetinfo.record]: sun_radius = 111394.362488 13:59:07.547 [planetinfo.record]: sun_vector = -0.281 0.941 -0.188 13:59:07.547 [planetinfo.record]: sun_distance = 1105020 13:59:07.547 [planetinfo.record]: corona_flare = 0.011597 13:59:07.547 [planetinfo.record]: corona_hues = 0.959282 13:59:07.547 [planetinfo.record]: sun_color = 0.823593, 0.788994, 0.764082, 1 13:59:07.547 [planetinfo.record]: corona_shimmer = 0.272746 13:59:07.548 [planetinfo.record]: station_vector = 0.880 -0.332 0.340 13:59:07.549 [planetinfo.record]: station = coriolis 13:59:12.776 [PLANETINFO OVER]: Done 13:59:12.778 [PLANETINFO LOGGING]: 5 238 13:59:13.782 [planetinfo.record]: seed = 101 183 170 245 13:59:13.782 [planetinfo.record]: coordinates = 183 162 13:59:13.782 [planetinfo.record]: government = 4; 13:59:13.782 [planetinfo.record]: economy = 2; 13:59:13.782 [planetinfo.record]: techlevel = 10; 13:59:13.782 [planetinfo.record]: population = 47; 13:59:13.782 [planetinfo.record]: productivity = 24064; 13:59:13.782 [planetinfo.record]: name = "Laatexe"; 13:59:13.782 [planetinfo.record]: inhabitant = "Furry Feline"; 13:59:13.782 [planetinfo.record]: inhabitants = "Furry Felines"; 13:59:13.782 [planetinfo.record]: description = "This planet is very well known for its inhabitants’ ancient mating traditions and its inhabitants’ ancient loathing of sit coms."; 13:59:13.804 [planetinfo.record]: air_color = 0.401571, 0.850061, 0.807579, 1; 13:59:13.804 [planetinfo.record]: cloud_alpha = 1.000000; 13:59:13.804 [planetinfo.record]: cloud_color = 0.552734, 0.403958, 0.00863647, 1; 13:59:13.804 [planetinfo.record]: cloud_fraction = 0.230000; 13:59:13.804 [planetinfo.record]: land_color = 0.66, 0.237188, 0.46511, 1; 13:59:13.804 [planetinfo.record]: land_fraction = 0.530000; 13:59:13.804 [planetinfo.record]: polar_cloud_color = 0.74873, 0.622773, 0.288086, 1; 13:59:13.804 [planetinfo.record]: polar_land_color = 0.934, 0.784414, 0.86505, 1; 13:59:13.805 [planetinfo.record]: polar_sea_color = 0.940039, 0.935716, 0.889732, 1; 13:59:13.805 [planetinfo.record]: sea_color = 0.599609, 0.588579, 0.471255, 1; 13:59:13.805 [planetinfo.record]: rotation_speed = 0.004007 13:59:13.805 [planetinfo.record]: planet zpos = 556270.000000 13:59:13.805 [planetinfo.record]: sun_radius = 135025.903015 13:59:13.805 [planetinfo.record]: sun_vector = -0.758 -0.651 0.039 13:59:13.805 [planetinfo.record]: sun_distance = 984170 13:59:13.805 [planetinfo.record]: corona_flare = 0.092641 13:59:13.805 [planetinfo.record]: corona_hues = 0.767044 13:59:13.805 [planetinfo.record]: sun_color = 0.798833, 0.807494, 0.812341, 1 13:59:13.806 [planetinfo.record]: corona_shimmer = 0.400039 13:59:13.806 [planetinfo.record]: station_vector = -0.120 -0.700 0.704 13:59:13.806 [planetinfo.record]: station = coriolis 13:59:19.060 [PLANETINFO OVER]: Done 13:59:19.061 [PLANETINFO LOGGING]: 5 239 13:59:20.070 [planetinfo.record]: seed = 105 66 40 143 13:59:20.070 [planetinfo.record]: coordinates = 66 252 13:59:20.070 [planetinfo.record]: government = 5; 13:59:20.071 [planetinfo.record]: economy = 4; 13:59:20.071 [planetinfo.record]: techlevel = 8; 13:59:20.071 [planetinfo.record]: population = 42; 13:59:20.071 [planetinfo.record]: productivity = 18144; 13:59:20.071 [planetinfo.record]: name = "Areis"; 13:59:20.071 [planetinfo.record]: inhabitant = "Human Colonial"; 13:59:20.071 [planetinfo.record]: inhabitants = "Human Colonials"; 13:59:20.071 [planetinfo.record]: description = "This world is noted for its exciting sit coms."; 13:59:20.072 [planetinfo.record]: air_color = 0.476667, 0.556001, 0.895588, 1; 13:59:20.072 [planetinfo.record]: cloud_alpha = 1.000000; 13:59:20.072 [planetinfo.record]: cloud_color = 0.134659, 0.442396, 0.689453, 1; 13:59:20.072 [planetinfo.record]: cloud_fraction = 0.070000; 13:59:20.073 [planetinfo.record]: land_color = 0.507812, 0.449063, 0.0654602, 1; 13:59:20.073 [planetinfo.record]: land_fraction = 0.410000; 13:59:20.073 [planetinfo.record]: polar_cloud_color = 0.402753, 0.628789, 0.810254, 1; 13:59:20.073 [planetinfo.record]: polar_land_color = 0.949219, 0.921764, 0.742504, 1; 13:59:20.073 [planetinfo.record]: polar_sea_color = 0.913928, 0.931641, 0.87496, 1; 13:59:20.073 [planetinfo.record]: sea_color = 0.631607, 0.683594, 0.517235, 1; 13:59:20.073 [planetinfo.record]: rotation_speed = 0.003864 13:59:20.073 [planetinfo.record]: planet zpos = 604980.000000 13:59:20.073 [planetinfo.record]: sun_radius = 137064.755554 13:59:20.073 [planetinfo.record]: sun_vector = 0.288 -0.093 -0.953 13:59:20.074 [planetinfo.record]: sun_distance = 1546060 13:59:20.074 [planetinfo.record]: corona_flare = 0.053267 13:59:20.074 [planetinfo.record]: corona_hues = 0.564316 13:59:20.074 [planetinfo.record]: sun_color = 0.657805, 0.696831, 0.709091, 1 13:59:20.074 [planetinfo.record]: corona_shimmer = 0.488346 13:59:20.075 [planetinfo.record]: station_vector = -0.538 0.797 0.273 13:59:20.075 [planetinfo.record]: station = coriolis 13:59:25.150 [PLANETINFO OVER]: Done 13:59:25.151 [PLANETINFO LOGGING]: 5 240 13:59:26.170 [planetinfo.record]: seed = 169 253 42 108 13:59:26.170 [planetinfo.record]: coordinates = 253 160 13:59:26.170 [planetinfo.record]: government = 5; 13:59:26.170 [planetinfo.record]: economy = 0; 13:59:26.170 [planetinfo.record]: techlevel = 11; 13:59:26.170 [planetinfo.record]: population = 50; 13:59:26.170 [planetinfo.record]: productivity = 36000; 13:59:26.170 [planetinfo.record]: name = "Inesbe"; 13:59:26.170 [planetinfo.record]: inhabitant = "Human Colonial"; 13:59:26.170 [planetinfo.record]: inhabitants = "Human Colonials"; 13:59:26.170 [planetinfo.record]: description = "The world Inesbe is scourged by killer mountain monkeys."; 13:59:26.184 [planetinfo.record]: air_color = 0.698249, 0.536446, 0.922254, 1; 13:59:26.184 [planetinfo.record]: cloud_alpha = 1.000000; 13:59:26.184 [planetinfo.record]: cloud_color = 0.769531, 0.264526, 0.631444, 1; 13:59:26.184 [planetinfo.record]: cloud_fraction = 0.600000; 13:59:26.184 [planetinfo.record]: land_color = 0.100547, 0.227298, 0.66, 1; 13:59:26.184 [planetinfo.record]: land_fraction = 0.330000; 13:59:26.184 [planetinfo.record]: polar_cloud_color = 0.846289, 0.499178, 0.751376, 1; 13:59:26.184 [planetinfo.record]: polar_land_color = 0.736072, 0.780915, 0.934, 1; 13:59:26.184 [planetinfo.record]: polar_sea_color = 0.938086, 0.917533, 0.882112, 1; 13:59:26.184 [planetinfo.record]: sea_color = 0.619141, 0.564881, 0.471369, 1; 13:59:26.184 [planetinfo.record]: rotation_speed = 0.001611 13:59:26.184 [planetinfo.record]: planet zpos = 552690.000000 13:59:26.184 [planetinfo.record]: sun_radius = 142634.695129 13:59:26.184 [planetinfo.record]: sun_vector = -0.221 -0.556 -0.801 13:59:26.184 [planetinfo.record]: sun_distance = 1351020 13:59:26.184 [planetinfo.record]: corona_flare = 0.041649 13:59:26.185 [planetinfo.record]: corona_hues = 0.700821 13:59:26.185 [planetinfo.record]: sun_color = 0.191227, 0.256809, 0.720233, 1 13:59:26.185 [planetinfo.record]: corona_shimmer = 0.455111 13:59:26.185 [planetinfo.record]: station_vector = 0.932 0.351 0.089 13:59:26.185 [planetinfo.record]: station = icosahedron 13:59:30.711 [PLANETINFO OVER]: Done 13:59:30.712 [PLANETINFO LOGGING]: 5 241 13:59:31.717 [planetinfo.record]: seed = 181 233 64 103 13:59:31.717 [planetinfo.record]: coordinates = 233 115 13:59:31.717 [planetinfo.record]: government = 6; 13:59:31.717 [planetinfo.record]: economy = 3; 13:59:31.717 [planetinfo.record]: techlevel = 8; 13:59:31.717 [planetinfo.record]: population = 42; 13:59:31.717 [planetinfo.record]: productivity = 23520; 13:59:31.717 [planetinfo.record]: name = "Sozala"; 13:59:31.717 [planetinfo.record]: inhabitant = "Human Colonial"; 13:59:31.717 [planetinfo.record]: inhabitants = "Human Colonials"; 13:59:31.717 [planetinfo.record]: description = "The planet Sozala is most famous for the Sozalaian deadly Inonbeoid and the Sozalaian deadly monkey."; 13:59:31.721 [planetinfo.record]: air_color = 0.667638, 0.836964, 0.934611, 1; 13:59:31.722 [planetinfo.record]: cloud_alpha = 1.000000; 13:59:31.722 [planetinfo.record]: cloud_color = 0.598679, 0.806641, 0.621424, 1; 13:59:31.722 [planetinfo.record]: cloud_fraction = 0.460000; 13:59:31.722 [planetinfo.record]: land_color = 0.458906, 0.66, 0.58459, 1; 13:59:31.722 [planetinfo.record]: land_fraction = 0.560000; 13:59:31.722 [planetinfo.record]: polar_cloud_color = 0.723933, 0.862988, 0.739142, 1; 13:59:31.722 [planetinfo.record]: polar_land_color = 0.862855, 0.934, 0.907321, 1; 13:59:31.722 [planetinfo.record]: polar_sea_color = 0.801974, 0.949414, 0.785306, 1; 13:59:31.722 [planetinfo.record]: sea_color = 0.191627, 0.505859, 0.156105, 1; 13:59:31.722 [planetinfo.record]: rotation_speed = 0.004652 13:59:31.722 [planetinfo.record]: planet zpos = 677740.000000 13:59:31.722 [planetinfo.record]: sun_radius = 159365.341797 13:59:31.723 [planetinfo.record]: sun_vector = -0.156 -0.591 -0.792 13:59:31.723 [planetinfo.record]: sun_distance = 822970 13:59:31.723 [planetinfo.record]: corona_flare = 0.018193 13:59:31.723 [planetinfo.record]: corona_hues = 0.853043 13:59:31.723 [planetinfo.record]: sun_color = 0.705016, 0.811901, 0.81842, 1 13:59:31.723 [planetinfo.record]: corona_shimmer = 0.505390 13:59:31.723 [planetinfo.record]: station_vector = -0.406 -0.090 0.909 13:59:31.723 [planetinfo.record]: station = coriolis 13:59:37.379 [PLANETINFO OVER]: Done 13:59:37.381 [PLANETINFO LOGGING]: 5 242 13:59:38.382 [planetinfo.record]: seed = 125 65 186 27 13:59:38.382 [planetinfo.record]: coordinates = 65 21 13:59:38.382 [planetinfo.record]: government = 7; 13:59:38.382 [planetinfo.record]: economy = 5; 13:59:38.382 [planetinfo.record]: techlevel = 7; 13:59:38.382 [planetinfo.record]: population = 41; 13:59:38.382 [planetinfo.record]: productivity = 18040; 13:59:38.382 [planetinfo.record]: name = "Anener"; 13:59:38.382 [planetinfo.record]: inhabitant = "Green Fat Insect"; 13:59:38.382 [planetinfo.record]: inhabitants = "Green Fat Insects"; 13:59:38.382 [planetinfo.record]: description = "The planet Anener is famous for Anenerian wolf meat but scourged by dreadful solar activity."; 13:59:38.390 [planetinfo.record]: air_color = 0.72862, 0.801409, 0.910547, 1; 13:59:38.390 [planetinfo.record]: cloud_alpha = 1.000000; 13:59:38.390 [planetinfo.record]: cloud_color = 0.734375, 0.734375, 0.734375, 1; 13:59:38.390 [planetinfo.record]: cloud_fraction = 0.510000; 13:59:38.390 [planetinfo.record]: land_color = 0.456973, 0.66, 0.28875, 1; 13:59:38.390 [planetinfo.record]: land_fraction = 0.690000; 13:59:38.390 [planetinfo.record]: polar_cloud_color = 0.830469, 0.830469, 0.830469, 1; 13:59:38.390 [planetinfo.record]: polar_land_color = 0.862171, 0.934, 0.802656, 1; 13:59:38.391 [planetinfo.record]: polar_sea_color = 0.935547, 0.868853, 0.868853, 1; 13:59:38.391 [planetinfo.record]: sea_color = 0.644531, 0.460739, 0.460739, 1; 13:59:38.391 [planetinfo.record]: rotation_speed = 0.001702 13:59:38.391 [planetinfo.record]: planet zpos = 569700.000000 13:59:38.391 [planetinfo.record]: sun_radius = 145001.585083 13:59:38.391 [planetinfo.record]: sun_vector = -0.702 -0.711 0.041 13:59:38.391 [planetinfo.record]: sun_distance = 1253340 13:59:38.391 [planetinfo.record]: corona_flare = 0.016275 13:59:38.391 [planetinfo.record]: corona_hues = 0.562355 13:59:38.391 [planetinfo.record]: sun_color = 0.201431, 0.410096, 0.665045, 1 13:59:38.391 [planetinfo.record]: corona_shimmer = 0.479155 13:59:38.392 [planetinfo.record]: station_vector = -0.813 -0.501 0.297 13:59:38.392 [planetinfo.record]: station = coriolis 13:59:43.170 [PLANETINFO OVER]: Done 13:59:43.171 [PLANETINFO LOGGING]: 5 243 13:59:44.174 [planetinfo.record]: seed = 145 94 104 161 13:59:44.174 [planetinfo.record]: coordinates = 94 208 13:59:44.174 [planetinfo.record]: government = 2; 13:59:44.174 [planetinfo.record]: economy = 0; 13:59:44.174 [planetinfo.record]: techlevel = 10; 13:59:44.174 [planetinfo.record]: population = 43; 13:59:44.174 [planetinfo.record]: productivity = 20640; 13:59:44.174 [planetinfo.record]: name = "Leera"; 13:59:44.174 [planetinfo.record]: inhabitant = "Human Colonial"; 13:59:44.174 [planetinfo.record]: inhabitants = "Human Colonials"; 13:59:44.174 [planetinfo.record]: description = "This world is mildly famous for its pink oceans and its exotic fish meat."; 13:59:44.192 [planetinfo.record]: air_color = 0.730414, 0.78363, 0.929408, 1; 13:59:44.192 [planetinfo.record]: cloud_alpha = 1.000000; 13:59:44.192 [planetinfo.record]: cloud_color = 0.757027, 0.783846, 0.791016, 1; 13:59:44.192 [planetinfo.record]: cloud_fraction = 0.240000; 13:59:44.192 [planetinfo.record]: land_color = 0.622235, 0.66, 0.466641, 1; 13:59:44.192 [planetinfo.record]: land_fraction = 0.390000; 13:59:44.192 [planetinfo.record]: polar_cloud_color = 0.83297, 0.851108, 0.855957, 1; 13:59:44.192 [planetinfo.record]: polar_land_color = 0.920639, 0.934, 0.865592, 1; 13:59:44.192 [planetinfo.record]: polar_sea_color = 0.91, 0.8645, 0.8918, 1; 13:59:44.192 [planetinfo.record]: sea_color = 0.9, 0.72, 0.828, 1; 13:59:44.192 [planetinfo.record]: rotation_speed = 0.002939 13:59:44.192 [planetinfo.record]: planet zpos = 348260.000000 13:59:44.192 [planetinfo.record]: sun_radius = 82376.096802 13:59:44.192 [planetinfo.record]: sun_vector = 0.946 0.285 0.152 13:59:44.192 [planetinfo.record]: sun_distance = 538220 13:59:44.192 [planetinfo.record]: corona_flare = 0.038245 13:59:44.192 [planetinfo.record]: corona_hues = 0.891968 13:59:44.192 [planetinfo.record]: sun_color = 0.658554, 0.653225, 0.621143, 1 13:59:44.193 [planetinfo.record]: corona_shimmer = 0.373254 13:59:44.193 [planetinfo.record]: station_vector = 0.883 -0.419 0.209 13:59:44.193 [planetinfo.record]: station = coriolis 13:59:49.781 [PLANETINFO OVER]: Done 13:59:49.782 [PLANETINFO LOGGING]: 5 244 13:59:50.786 [planetinfo.record]: seed = 97 65 90 225 13:59:50.786 [planetinfo.record]: coordinates = 65 207 13:59:50.786 [planetinfo.record]: government = 4; 13:59:50.786 [planetinfo.record]: economy = 7; 13:59:50.786 [planetinfo.record]: techlevel = 3; 13:59:50.787 [planetinfo.record]: population = 24; 13:59:50.787 [planetinfo.record]: productivity = 4608; 13:59:50.787 [planetinfo.record]: name = "Leenlaes"; 13:59:50.787 [planetinfo.record]: inhabitant = "Human Colonial"; 13:59:50.787 [planetinfo.record]: inhabitants = "Human Colonials"; 13:59:50.787 [planetinfo.record]: description = "Leenlaes is most noted for its inhabitants’ exceptional love for tourists and its unusual tropical forests."; 13:59:50.789 [planetinfo.record]: air_color = 0.708655, 0.817092, 0.909246, 1; 13:59:50.790 [planetinfo.record]: cloud_alpha = 1.000000; 13:59:50.790 [planetinfo.record]: cloud_color = 0.684814, 0.730469, 0.702648, 1; 13:59:50.790 [planetinfo.record]: cloud_fraction = 0.390000; 13:59:50.790 [planetinfo.record]: land_color = 0.572706, 0.152109, 0.66, 1; 13:59:50.790 [planetinfo.record]: land_fraction = 0.590000; 13:59:50.790 [planetinfo.record]: polar_cloud_color = 0.796339, 0.828711, 0.808985, 1; 13:59:50.790 [planetinfo.record]: polar_land_color = 0.903117, 0.754314, 0.934, 1; 13:59:50.790 [planetinfo.record]: polar_sea_color = 0.933398, 0.900534, 0.87415, 1; 13:59:50.790 [planetinfo.record]: sea_color = 0.666016, 0.572215, 0.49691, 1; 13:59:50.790 [planetinfo.record]: rotation_speed = 0.004448 13:59:50.790 [planetinfo.record]: planet zpos = 407810.000000 13:59:50.790 [planetinfo.record]: sun_radius = 59154.296417 13:59:50.790 [planetinfo.record]: sun_vector = -0.650 0.151 -0.745 13:59:50.790 [planetinfo.record]: sun_distance = 721510 13:59:50.790 [planetinfo.record]: corona_flare = 0.098759 13:59:50.790 [planetinfo.record]: corona_hues = 0.837914 13:59:50.791 [planetinfo.record]: sun_color = 0.776572, 0.474975, 0.469636, 1 13:59:50.791 [planetinfo.record]: corona_shimmer = 0.440412 13:59:50.791 [planetinfo.record]: station_vector = 0.018 -0.999 0.030 13:59:50.791 [planetinfo.record]: station = coriolis 13:59:55.871 [PLANETINFO OVER]: Done 13:59:55.871 [PLANETINFO LOGGING]: 5 245 13:59:56.875 [planetinfo.record]: seed = 125 233 160 241 13:59:56.875 [planetinfo.record]: coordinates = 233 21 13:59:56.875 [planetinfo.record]: government = 7; 13:59:56.875 [planetinfo.record]: economy = 5; 13:59:56.875 [planetinfo.record]: techlevel = 7; 13:59:56.875 [planetinfo.record]: population = 41; 13:59:56.875 [planetinfo.record]: productivity = 18040; 13:59:56.875 [planetinfo.record]: name = "Atermadi"; 13:59:56.875 [planetinfo.record]: inhabitant = "Fat Humanoid"; 13:59:56.875 [planetinfo.record]: inhabitants = "Fat Humanoids"; 13:59:56.875 [planetinfo.record]: description = "This world is reasonably notable for its great volcanoes but plagued by unpredictable civil war."; 13:59:56.890 [planetinfo.record]: air_color = 0.440669, 0.725223, 0.868271, 1; 13:59:56.891 [planetinfo.record]: cloud_alpha = 1.000000; 13:59:56.891 [planetinfo.record]: cloud_color = 0.073555, 0.607422, 0.0818967, 1; 13:59:56.891 [planetinfo.record]: cloud_fraction = 0.340000; 13:59:56.891 [planetinfo.record]: land_color = 0.346775, 0.552734, 0.07341, 1; 13:59:56.891 [planetinfo.record]: land_fraction = 0.510000; 13:59:56.891 [planetinfo.record]: polar_cloud_color = 0.348532, 0.77334, 0.355169, 1; 13:59:56.891 [planetinfo.record]: polar_land_color = 0.856721, 0.944727, 0.739913, 1; 13:59:56.891 [planetinfo.record]: polar_sea_color = 0.946875, 0.893428, 0.900944, 1; 13:59:56.891 [planetinfo.record]: sea_color = 0.53125, 0.411304, 0.428171, 1; 13:59:56.891 [planetinfo.record]: rotation_speed = 0.003778 13:59:56.891 [planetinfo.record]: planet zpos = 363550.000000 13:59:56.891 [planetinfo.record]: sun_radius = 71339.203644 13:59:56.891 [planetinfo.record]: sun_vector = -0.034 0.188 -0.982 13:59:56.892 [planetinfo.record]: sun_distance = 594900 13:59:56.892 [planetinfo.record]: corona_flare = 0.098970 13:59:56.892 [planetinfo.record]: corona_hues = 0.783134 13:59:56.892 [planetinfo.record]: sun_color = 0.675702, 0.429124, 0.263065, 1 13:59:56.892 [planetinfo.record]: corona_shimmer = 0.388302 13:59:56.892 [planetinfo.record]: station_vector = 0.816 -0.577 0.038 13:59:56.892 [planetinfo.record]: station = coriolis 14:00:01.157 [PLANETINFO OVER]: Done 14:00:01.158 [PLANETINFO LOGGING]: 5 246 14:00:02.178 [planetinfo.record]: seed = 213 173 10 106 14:00:02.178 [planetinfo.record]: coordinates = 173 203 14:00:02.178 [planetinfo.record]: government = 2; 14:00:02.178 [planetinfo.record]: economy = 3; 14:00:02.178 [planetinfo.record]: techlevel = 6; 14:00:02.178 [planetinfo.record]: population = 30; 14:00:02.178 [planetinfo.record]: productivity = 10080; 14:00:02.178 [planetinfo.record]: name = "Argean"; 14:00:02.178 [planetinfo.record]: inhabitant = "Human Colonial"; 14:00:02.178 [planetinfo.record]: inhabitants = "Human Colonials"; 14:00:02.178 [planetinfo.record]: description = "Argean is famous for its inhabitants’ ancient loathing of discos but plagued by unpredictable civil war."; 14:00:02.201 [planetinfo.record]: air_color = 0.603275, 0.523453, 0.905994, 1; 14:00:02.201 [planetinfo.record]: cloud_alpha = 1.000000; 14:00:02.201 [planetinfo.record]: cloud_color = 0.547697, 0.239296, 0.720703, 1; 14:00:02.201 [planetinfo.record]: cloud_fraction = 0.480000; 14:00:02.201 [planetinfo.record]: land_color = 0.66, 0.386719, 0.463579, 1; 14:00:02.201 [planetinfo.record]: land_fraction = 0.470000; 14:00:02.201 [planetinfo.record]: polar_cloud_color = 0.700643, 0.48018, 0.824316, 1; 14:00:02.202 [planetinfo.record]: polar_land_color = 0.934, 0.837316, 0.864509, 1; 14:00:02.202 [planetinfo.record]: polar_sea_color = 0.942578, 0.922874, 0.892136, 1; 14:00:02.202 [planetinfo.record]: sea_color = 0.574219, 0.526204, 0.4513, 1; 14:00:02.202 [planetinfo.record]: rotation_speed = 0.004566 14:00:02.202 [planetinfo.record]: planet zpos = 610390.000000 14:00:02.202 [planetinfo.record]: sun_radius = 129149.554291 14:00:02.202 [planetinfo.record]: sun_vector = 0.142 0.101 -0.985 14:00:02.202 [planetinfo.record]: sun_distance = 998820 14:00:02.202 [planetinfo.record]: corona_flare = 0.015349 14:00:02.202 [planetinfo.record]: corona_hues = 0.505264 14:00:02.202 [planetinfo.record]: sun_color = 0.738315, 0.475852, 0.332273, 1 14:00:02.202 [planetinfo.record]: corona_shimmer = 0.269691 14:00:02.202 [planetinfo.record]: station_vector = -0.863 -0.252 0.437 14:00:02.202 [planetinfo.record]: station = coriolis 14:00:07.242 [PLANETINFO OVER]: Done 14:00:07.244 [PLANETINFO LOGGING]: 5 247 14:00:08.247 [planetinfo.record]: seed = 249 72 232 39 14:00:08.247 [planetinfo.record]: coordinates = 72 251 14:00:08.247 [planetinfo.record]: government = 7; 14:00:08.247 [planetinfo.record]: economy = 3; 14:00:08.247 [planetinfo.record]: techlevel = 8; 14:00:08.247 [planetinfo.record]: population = 43; 14:00:08.247 [planetinfo.record]: productivity = 26488; 14:00:08.247 [planetinfo.record]: name = "Soinisat"; 14:00:08.247 [planetinfo.record]: inhabitant = "Fierce Red Bony Feline"; 14:00:08.247 [planetinfo.record]: inhabitants = "Fierce Red Bony Felines"; 14:00:08.247 [planetinfo.record]: description = "The world Soinisat is very noted for its pink oceans but scourged by deadly solar activity."; 14:00:08.251 [planetinfo.record]: air_color = 0.722689, 0.60755, 0.863068, 1; 14:00:08.252 [planetinfo.record]: cloud_alpha = 1.000000; 14:00:08.252 [planetinfo.record]: cloud_color = 0.591797, 0.420731, 0.528983, 1; 14:00:08.252 [planetinfo.record]: cloud_fraction = 0.230000; 14:00:08.252 [planetinfo.record]: land_color = 0.66, 0.171002, 0.146953, 1; 14:00:08.252 [planetinfo.record]: land_fraction = 0.400000; 14:00:08.252 [planetinfo.record]: polar_cloud_color = 0.766309, 0.627864, 0.715474, 1; 14:00:08.252 [planetinfo.record]: polar_land_color = 0.934, 0.760998, 0.75249, 1; 14:00:08.252 [planetinfo.record]: polar_sea_color = 0.91, 0.8645, 0.8918, 1; 14:00:08.252 [planetinfo.record]: sea_color = 0.9, 0.72, 0.828, 1; 14:00:08.252 [planetinfo.record]: rotation_speed = 0.001942 14:00:08.252 [planetinfo.record]: planet zpos = 655200.000000 14:00:08.252 [planetinfo.record]: sun_radius = 95866.589355 14:00:08.252 [planetinfo.record]: sun_vector = 0.678 0.345 -0.649 14:00:08.252 [planetinfo.record]: sun_distance = 936000 14:00:08.252 [planetinfo.record]: corona_flare = 0.036807 14:00:08.252 [planetinfo.record]: corona_hues = 0.872101 14:00:08.252 [planetinfo.record]: sun_color = 0.594022, 0.633628, 0.667038, 1 14:00:08.252 [planetinfo.record]: corona_shimmer = 0.679270 14:00:08.252 [planetinfo.record]: station_vector = 0.305 0.768 0.564 14:00:08.252 [planetinfo.record]: station = coriolis 14:00:13.032 [PLANETINFO OVER]: Done 14:00:13.034 [PLANETINFO LOGGING]: 5 248 14:00:14.039 [planetinfo.record]: seed = 89 113 202 186 14:00:14.040 [planetinfo.record]: coordinates = 113 221 14:00:14.040 [planetinfo.record]: government = 3; 14:00:14.040 [planetinfo.record]: economy = 5; 14:00:14.040 [planetinfo.record]: techlevel = 5; 14:00:14.040 [planetinfo.record]: population = 29; 14:00:14.040 [planetinfo.record]: productivity = 8120; 14:00:14.040 [planetinfo.record]: name = "Quesla"; 14:00:14.040 [planetinfo.record]: inhabitant = "Harmless Fat Feline"; 14:00:14.040 [planetinfo.record]: inhabitants = "Harmless Fat Felines"; 14:00:14.040 [planetinfo.record]: description = "Quesla is reasonably well known for the Queslaian tree grub but plagued by frequent earthquakes."; 14:00:14.049 [planetinfo.record]: air_color = 0.691437, 0.786419, 0.94892, 1; 14:00:14.049 [planetinfo.record]: cloud_alpha = 1.000000; 14:00:14.049 [planetinfo.record]: cloud_color = 0.667076, 0.849609, 0.832497, 1; 14:00:14.049 [planetinfo.record]: cloud_fraction = 0.300000; 14:00:14.049 [planetinfo.record]: land_color = 0.583984, 0.433426, 0.518115, 1; 14:00:14.049 [planetinfo.record]: land_fraction = 0.680000; 14:00:14.049 [planetinfo.record]: polar_cloud_color = 0.763848, 0.882324, 0.871217, 1; 14:00:14.049 [planetinfo.record]: polar_land_color = 0.941602, 0.880912, 0.91505, 1; 14:00:14.049 [planetinfo.record]: polar_sea_color = 0.877366, 0.920117, 0.841943, 1; 14:00:14.049 [planetinfo.record]: sea_color = 0.650364, 0.798828, 0.527351, 1; 14:00:14.049 [planetinfo.record]: rotation_speed = 0.002251 14:00:14.049 [planetinfo.record]: planet zpos = 768460.000000 14:00:14.050 [planetinfo.record]: sun_radius = 173691.304169 14:00:14.050 [planetinfo.record]: sun_vector = 0.203 0.796 -0.570 14:00:14.050 [planetinfo.record]: sun_distance = 1042910 14:00:14.050 [planetinfo.record]: corona_flare = 0.024202 14:00:14.050 [planetinfo.record]: corona_hues = 0.802132 14:00:14.050 [planetinfo.record]: sun_color = 0.571834, 0.590886, 0.792328, 1 14:00:14.050 [planetinfo.record]: corona_shimmer = 0.294094 14:00:14.050 [planetinfo.record]: station_vector = -0.938 0.091 0.335 14:00:14.050 [planetinfo.record]: station = coriolis 14:00:19.766 [PLANETINFO OVER]: Done 14:00:19.767 [PLANETINFO LOGGING]: 5 249 14:00:20.788 [planetinfo.record]: seed = 133 249 64 56 14:00:20.788 [planetinfo.record]: coordinates = 249 53 14:00:20.788 [planetinfo.record]: government = 0; 14:00:20.788 [planetinfo.record]: economy = 7; 14:00:20.788 [planetinfo.record]: techlevel = 1; 14:00:20.788 [planetinfo.record]: population = 12; 14:00:20.788 [planetinfo.record]: productivity = 1152; 14:00:20.788 [planetinfo.record]: name = "Edsoeded"; 14:00:20.788 [planetinfo.record]: inhabitant = "Human Colonial"; 14:00:20.788 [planetinfo.record]: inhabitants = "Human Colonials"; 14:00:20.788 [planetinfo.record]: description = "Edsoeded is mildly well known for its hoopy night life."; 14:00:20.817 [planetinfo.record]: air_color = 0.802985, 0.492018, 0.979488, 1; 14:00:20.818 [planetinfo.record]: cloud_alpha = 1.000000; 14:00:20.818 [planetinfo.record]: cloud_color = 0.941406, 0.0992889, 0.1585, 1; 14:00:20.818 [planetinfo.record]: cloud_fraction = 0.150000; 14:00:20.818 [planetinfo.record]: land_color = 0.613433, 0.66, 0.484688, 1; 14:00:20.818 [planetinfo.record]: land_fraction = 0.250000; 14:00:20.818 [planetinfo.record]: polar_cloud_color = 0.923633, 0.407246, 0.443555, 1; 14:00:20.818 [planetinfo.record]: polar_land_color = 0.917525, 0.934, 0.871977, 1; 14:00:20.818 [planetinfo.record]: polar_sea_color = 0.90997, 0.928906, 0.86831, 1; 14:00:20.818 [planetinfo.record]: sea_color = 0.652966, 0.710938, 0.525427, 1; 14:00:20.818 [planetinfo.record]: rotation_speed = 0.002758 14:00:20.818 [planetinfo.record]: planet zpos = 460170.000000 14:00:20.819 [planetinfo.record]: sun_radius = 153587.386017 14:00:20.819 [planetinfo.record]: sun_vector = 0.781 -0.211 0.588 14:00:20.819 [planetinfo.record]: sun_distance = 920340 14:00:20.819 [planetinfo.record]: corona_flare = 0.089575 14:00:20.819 [planetinfo.record]: corona_hues = 0.545128 14:00:20.819 [planetinfo.record]: sun_color = 0.229624, 0.554222, 0.782184, 1 14:00:20.819 [planetinfo.record]: corona_shimmer = 0.419511 14:00:20.819 [planetinfo.record]: station_vector = -0.823 0.539 0.180 14:00:20.820 [planetinfo.record]: station = coriolis 14:00:25.825 [PLANETINFO OVER]: Done 14:00:25.826 [PLANETINFO LOGGING]: 5 250 14:00:26.845 [planetinfo.record]: seed = 109 56 154 56 14:00:26.845 [planetinfo.record]: coordinates = 56 152 14:00:26.845 [planetinfo.record]: government = 5; 14:00:26.845 [planetinfo.record]: economy = 0; 14:00:26.845 [planetinfo.record]: techlevel = 10; 14:00:26.845 [planetinfo.record]: population = 46; 14:00:26.845 [planetinfo.record]: productivity = 33120; 14:00:26.845 [planetinfo.record]: name = "Edaranis"; 14:00:26.845 [planetinfo.record]: inhabitant = "Red Slimy Rodent"; 14:00:26.845 [planetinfo.record]: inhabitants = "Red Slimy Rodents"; 14:00:26.845 [planetinfo.record]: description = "The world Edaranis is very famous for its unusual casinos but plagued by frequent earthquakes."; 14:00:26.860 [planetinfo.record]: air_color = 0.413718, 0.845508, 0.824828, 1; 14:00:26.860 [planetinfo.record]: cloud_alpha = 1.000000; 14:00:26.860 [planetinfo.record]: cloud_color = 0.539062, 0.472223, 0.0357971, 1; 14:00:26.860 [planetinfo.record]: cloud_fraction = 0.320000; 14:00:26.860 [planetinfo.record]: land_color = 0.159199, 0.0876563, 0.66, 1; 14:00:26.860 [planetinfo.record]: land_fraction = 0.410000; 14:00:26.860 [planetinfo.record]: polar_cloud_color = 0.742578, 0.685032, 0.309287, 1; 14:00:26.860 [planetinfo.record]: polar_land_color = 0.756823, 0.731512, 0.934, 1; 14:00:26.860 [planetinfo.record]: polar_sea_color = 0.935742, 0.889595, 0.877258, 1; 14:00:26.860 [planetinfo.record]: sea_color = 0.642578, 0.51582, 0.481934, 1; 14:00:26.860 [planetinfo.record]: rotation_speed = 0.002339 14:00:26.860 [planetinfo.record]: planet zpos = 590400.000000 14:00:26.860 [planetinfo.record]: sun_radius = 102938.928223 14:00:26.860 [planetinfo.record]: sun_vector = -0.372 -0.676 0.636 14:00:26.860 [planetinfo.record]: sun_distance = 1033200 14:00:26.860 [planetinfo.record]: corona_flare = 0.096727 14:00:26.860 [planetinfo.record]: corona_hues = 0.637093 14:00:26.860 [planetinfo.record]: sun_color = 0.543528, 0.627331, 0.693353, 1 14:00:26.860 [planetinfo.record]: corona_shimmer = 0.642031 14:00:26.861 [planetinfo.record]: station_vector = 0.034 0.994 0.105 14:00:26.861 [planetinfo.record]: station = coriolis 14:00:31.692 [PLANETINFO OVER]: Done 14:00:31.692 [PLANETINFO LOGGING]: 5 251 14:00:32.695 [planetinfo.record]: seed = 161 189 168 66 14:00:32.695 [planetinfo.record]: coordinates = 189 123 14:00:32.695 [planetinfo.record]: government = 4; 14:00:32.695 [planetinfo.record]: economy = 3; 14:00:32.695 [planetinfo.record]: techlevel = 7; 14:00:32.695 [planetinfo.record]: population = 36; 14:00:32.695 [planetinfo.record]: productivity = 16128; 14:00:32.695 [planetinfo.record]: name = "Xeanan"; 14:00:32.695 [planetinfo.record]: inhabitant = "Large Yellow Rodent"; 14:00:32.695 [planetinfo.record]: inhabitants = "Large Yellow Rodents"; 14:00:32.695 [planetinfo.record]: description = "This world is reasonably well known for the Xeananian tree ant but ravaged by dreadful civil war."; 14:00:32.714 [planetinfo.record]: air_color = 0.522897, 0.534695, 0.877377, 1; 14:00:32.714 [planetinfo.record]: cloud_alpha = 1.000000; 14:00:32.714 [planetinfo.record]: cloud_color = 0.245476, 0.27893, 0.634766, 1; 14:00:32.714 [planetinfo.record]: cloud_fraction = 0.350000; 14:00:32.715 [planetinfo.record]: land_color = 0.0968323, 0.652344, 0.496106, 1; 14:00:32.715 [planetinfo.record]: land_fraction = 0.390000; 14:00:32.715 [planetinfo.record]: polar_cloud_color = 0.484506, 0.510385, 0.785645, 1; 14:00:32.715 [planetinfo.record]: polar_land_color = 0.735763, 0.934766, 0.878796, 1; 14:00:32.715 [planetinfo.record]: polar_sea_color = 0.938917, 0.94043, 0.892031, 1; 14:00:32.715 [planetinfo.record]: sea_color = 0.591871, 0.595703, 0.473072, 1; 14:00:32.715 [planetinfo.record]: rotation_speed = 0.001035 14:00:32.715 [planetinfo.record]: planet zpos = 492380.000000 14:00:32.715 [planetinfo.record]: sun_radius = 107217.625427 14:00:32.715 [planetinfo.record]: sun_vector = -0.823 -0.098 -0.559 14:00:32.715 [planetinfo.record]: sun_distance = 844080 14:00:32.715 [planetinfo.record]: corona_flare = 0.043886 14:00:32.715 [planetinfo.record]: corona_hues = 0.641266 14:00:32.715 [planetinfo.record]: sun_color = 0.468665, 0.678862, 0.692734, 1 14:00:32.715 [planetinfo.record]: corona_shimmer = 0.280911 14:00:32.715 [planetinfo.record]: station_vector = 0.715 0.671 0.197 14:00:32.715 [planetinfo.record]: station = coriolis 14:00:37.268 [PLANETINFO OVER]: Done 14:00:37.269 [PLANETINFO LOGGING]: 5 252 14:00:38.272 [planetinfo.record]: seed = 145 57 122 48 14:00:38.272 [planetinfo.record]: coordinates = 57 123 14:00:38.272 [planetinfo.record]: government = 2; 14:00:38.272 [planetinfo.record]: economy = 3; 14:00:38.273 [planetinfo.record]: techlevel = 6; 14:00:38.273 [planetinfo.record]: population = 30; 14:00:38.273 [planetinfo.record]: productivity = 10080; 14:00:38.273 [planetinfo.record]: name = "Ercea"; 14:00:38.273 [planetinfo.record]: inhabitant = "Human Colonial"; 14:00:38.273 [planetinfo.record]: inhabitants = "Human Colonials"; 14:00:38.273 [planetinfo.record]: description = "The world Ercea is very famous for its fabulous monkey burgers but plagued by frequent solar activity."; 14:00:38.286 [planetinfo.record]: air_color = 0.56573, 0.568446, 0.840955, 1; 14:00:38.286 [planetinfo.record]: cloud_alpha = 1.000000; 14:00:38.286 [planetinfo.record]: cloud_color = 0.324265, 0.327407, 0.525391, 1; 14:00:38.286 [planetinfo.record]: cloud_fraction = 0.440000; 14:00:38.286 [planetinfo.record]: land_color = 0.592324, 0.66, 0.443438, 1; 14:00:38.295 [planetinfo.record]: land_fraction = 0.330000; 14:00:38.295 [planetinfo.record]: polar_cloud_color = 0.56023, 0.562983, 0.736426, 1; 14:00:38.295 [planetinfo.record]: polar_land_color = 0.910057, 0.934, 0.857383, 1; 14:00:38.295 [planetinfo.record]: polar_sea_color = 0.945898, 0.883916, 0.930403, 1; 14:00:38.295 [planetinfo.record]: sea_color = 0.541016, 0.39921, 0.505564, 1; 14:00:38.295 [planetinfo.record]: rotation_speed = 0.004991 14:00:38.295 [planetinfo.record]: planet zpos = 344760.000000 14:00:38.296 [planetinfo.record]: sun_radius = 67020.738678 14:00:38.296 [planetinfo.record]: sun_vector = 0.601 -0.598 -0.531 14:00:38.296 [planetinfo.record]: sun_distance = 603330 14:00:38.296 [planetinfo.record]: corona_flare = 0.093974 14:00:38.296 [planetinfo.record]: corona_hues = 0.590073 14:00:38.296 [planetinfo.record]: sun_color = 0.843851, 0.774101, 0.450333, 1 14:00:38.296 [planetinfo.record]: corona_shimmer = 0.279638 14:00:38.296 [planetinfo.record]: station_vector = -0.304 0.490 0.817 14:00:38.296 [planetinfo.record]: station = coriolis 14:00:43.010 [PLANETINFO OVER]: Done 14:00:43.010 [PLANETINFO LOGGING]: 5 253 14:00:44.013 [planetinfo.record]: seed = 205 101 32 155 14:00:44.013 [planetinfo.record]: coordinates = 101 79 14:00:44.014 [planetinfo.record]: government = 1; 14:00:44.014 [planetinfo.record]: economy = 7; 14:00:44.014 [planetinfo.record]: techlevel = 2; 14:00:44.014 [planetinfo.record]: population = 17; 14:00:44.014 [planetinfo.record]: productivity = 2040; 14:00:44.014 [planetinfo.record]: name = "Anerat"; 14:00:44.014 [planetinfo.record]: inhabitant = "Human Colonial"; 14:00:44.014 [planetinfo.record]: inhabitants = "Human Colonials"; 14:00:44.014 [planetinfo.record]: description = "Anerat is ravaged by frequent solar activity."; 14:00:44.024 [planetinfo.record]: air_color = 0.444651, 0.742673, 0.858516, 1; 14:00:44.024 [planetinfo.record]: cloud_alpha = 1.000000; 14:00:44.024 [planetinfo.record]: cloud_color = 0.160816, 0.578125, 0.0880737, 1; 14:00:44.024 [planetinfo.record]: cloud_fraction = 0.270000; 14:00:44.024 [planetinfo.record]: land_color = 0.66, 0.239766, 0.495846, 1; 14:00:44.024 [planetinfo.record]: land_fraction = 0.240000; 14:00:44.025 [planetinfo.record]: polar_cloud_color = 0.417215, 0.760156, 0.357437, 1; 14:00:44.025 [planetinfo.record]: polar_land_color = 0.934, 0.785326, 0.875924, 1; 14:00:44.025 [planetinfo.record]: polar_sea_color = 0.939648, 0.915721, 0.884958, 1; 14:00:44.025 [planetinfo.record]: sea_color = 0.603516, 0.542044, 0.46301, 1; 14:00:44.025 [planetinfo.record]: rotation_speed = 0.001843 14:00:44.025 [planetinfo.record]: planet zpos = 802620.000000 14:00:44.025 [planetinfo.record]: sun_radius = 142912.100830 14:00:44.025 [planetinfo.record]: sun_vector = 0.664 -0.740 -0.107 14:00:44.025 [planetinfo.record]: sun_distance = 1261260 14:00:44.025 [planetinfo.record]: corona_flare = 0.006203 14:00:44.025 [planetinfo.record]: corona_hues = 0.857101 14:00:44.025 [planetinfo.record]: sun_color = 0.658502, 0.615802, 0.433441, 1 14:00:44.025 [planetinfo.record]: corona_shimmer = 0.293029 14:00:44.032 [planetinfo.record]: station_vector = -0.646 -0.760 0.076 14:00:44.032 [planetinfo.record]: station = coriolis 14:00:48.868 [PLANETINFO OVER]: Done 14:00:48.870 [PLANETINFO LOGGING]: 5 254 14:00:49.876 [planetinfo.record]: seed = 69 61 106 223 14:00:49.876 [planetinfo.record]: coordinates = 61 81 14:00:49.876 [planetinfo.record]: government = 0; 14:00:49.876 [planetinfo.record]: economy = 3; 14:00:49.876 [planetinfo.record]: techlevel = 5; 14:00:49.876 [planetinfo.record]: population = 24; 14:00:49.876 [planetinfo.record]: productivity = 5376; 14:00:49.876 [planetinfo.record]: name = "Onrear"; 14:00:49.876 [planetinfo.record]: inhabitant = "Human Colonial"; 14:00:49.876 [planetinfo.record]: inhabitants = "Human Colonials"; 14:00:49.876 [planetinfo.record]: description = "Onrear is most noted for the Onrearian deadly Gestesoid and its inhabitants’ exceptional loathing of sit coms."; 14:00:49.908 [planetinfo.record]: air_color = 0.534084, 0.577098, 0.853312, 1; 14:00:49.908 [planetinfo.record]: cloud_alpha = 1.000000; 14:00:49.908 [planetinfo.record]: cloud_color = 0.268066, 0.38308, 0.5625, 1; 14:00:49.908 [planetinfo.record]: cloud_fraction = 0.610000; 14:00:49.909 [planetinfo.record]: land_color = 0.629063, 0.66, 0.635347, 1; 14:00:49.909 [planetinfo.record]: land_fraction = 0.520000; 14:00:49.909 [planetinfo.record]: polar_cloud_color = 0.506741, 0.602985, 0.753125, 1; 14:00:49.909 [planetinfo.record]: polar_land_color = 0.923055, 0.934, 0.925278, 1; 14:00:49.909 [planetinfo.record]: polar_sea_color = 0.725571, 0.932227, 0.893479, 1; 14:00:49.909 [planetinfo.record]: sea_color = 0.0767746, 0.677734, 0.565054, 1; 14:00:49.909 [planetinfo.record]: rotation_speed = 0.000228 14:00:49.909 [planetinfo.record]: planet zpos = 738870.000000 14:00:49.909 [planetinfo.record]: sun_radius = 159401.658325 14:00:49.909 [planetinfo.record]: sun_vector = -0.583 0.704 0.405 14:00:49.910 [planetinfo.record]: sun_distance = 1410570 14:00:49.910 [planetinfo.record]: corona_flare = 0.013258 14:00:49.910 [planetinfo.record]: corona_hues = 0.707550 14:00:49.910 [planetinfo.record]: sun_color = 0.471298, 0.591521, 0.794168, 1 14:00:49.910 [planetinfo.record]: corona_shimmer = 0.492782 14:00:49.911 [planetinfo.record]: station_vector = 0.393 0.827 0.401 14:00:49.911 [planetinfo.record]: station = coriolis 14:00:55.036 [PLANETINFO OVER]: Done 14:00:55.036 [PLANETINFO LOGGING]: 5 255 14:00:56.043 [planetinfo.record]: seed = 137 216 168 209 14:00:56.043 [planetinfo.record]: coordinates = 216 138 14:00:56.043 [planetinfo.record]: government = 1; 14:00:56.043 [planetinfo.record]: economy = 2; 14:00:56.043 [planetinfo.record]: techlevel = 6; 14:00:56.043 [planetinfo.record]: population = 28; 14:00:56.043 [planetinfo.record]: productivity = 8960; 14:00:56.043 [planetinfo.record]: name = "Atlaonbi"; 14:00:56.043 [planetinfo.record]: inhabitant = "Horned Lobster"; 14:00:56.043 [planetinfo.record]: inhabitants = "Horned Lobsters"; 14:00:56.043 [planetinfo.record]: description = "This planet is beset by deadly earthquakes."; 14:00:56.047 [planetinfo.record]: air_color = 0.805234, 0.54402, 0.943717, 1; 14:00:56.047 [planetinfo.record]: cloud_alpha = 1.000000; 14:00:56.047 [planetinfo.record]: cloud_color = 0.833984, 0.273651, 0.286784, 1; 14:00:56.047 [planetinfo.record]: cloud_fraction = 0.160000; 14:00:56.047 [planetinfo.record]: land_color = 0.514517, 0.0592969, 0.66, 1; 14:00:56.047 [planetinfo.record]: land_fraction = 0.490000; 14:00:56.048 [planetinfo.record]: polar_cloud_color = 0.875293, 0.507738, 0.516353, 1; 14:00:56.048 [planetinfo.record]: polar_land_color = 0.88253, 0.721479, 0.934, 1; 14:00:56.048 [planetinfo.record]: polar_sea_color = 0.935938, 0.902155, 0.883839, 1; 14:00:56.048 [planetinfo.record]: sea_color = 0.640625, 0.548132, 0.497986, 1; 14:00:56.048 [planetinfo.record]: rotation_speed = 0.000167 14:00:56.048 [planetinfo.record]: planet zpos = 361680.000000 14:00:56.048 [planetinfo.record]: sun_radius = 91099.815674 14:00:56.048 [planetinfo.record]: sun_vector = -0.037 -0.995 -0.087 14:00:56.048 [planetinfo.record]: sun_distance = 558960 14:00:56.048 [planetinfo.record]: corona_flare = 0.073070 14:00:56.048 [planetinfo.record]: corona_hues = 0.693031 14:00:56.048 [planetinfo.record]: sun_color = 0.840155, 0.677879, 0.421324, 1 14:00:56.049 [planetinfo.record]: corona_shimmer = 0.538195 14:00:56.049 [planetinfo.record]: station_vector = 0.827 0.082 0.556 14:00:56.049 [planetinfo.record]: station = coriolis 14:01:00.560 [PLANETINFO OVER]: Done 14:07:32.548 [planetinfo.record]: seed = 202 218 128 45 14:07:32.548 [planetinfo.record]: coordinates = 218 139 14:07:32.548 [planetinfo.record]: government = 1; 14:07:32.548 [planetinfo.record]: economy = 3; 14:07:32.548 [planetinfo.record]: techlevel = 7; 14:07:32.548 [planetinfo.record]: population = 33; 14:07:32.548 [planetinfo.record]: productivity = 9240; 14:07:32.548 [planetinfo.record]: name = "Dirateri"; 14:07:32.548 [planetinfo.record]: inhabitant = "Red Bug-Eyed Lizard"; 14:07:32.548 [planetinfo.record]: inhabitants = "Red Bug-Eyed Lizards"; 14:07:32.548 [planetinfo.record]: description = "Dirateri is a revolting dump."; 14:07:32.580 [planetinfo.record]: air_color = 0.505418, 0.590818, 0.866971, 1; 14:07:32.580 [planetinfo.record]: cloud_alpha = 1.000000; 14:07:32.580 [planetinfo.record]: cloud_color = 0.209816, 0.48356, 0.603516, 1; 14:07:32.580 [planetinfo.record]: cloud_fraction = 0.500000; 14:07:32.580 [planetinfo.record]: land_color = 0.66, 0.33, 0.376406, 1; 14:07:32.580 [planetinfo.record]: land_fraction = 0.320000; 14:07:32.580 [planetinfo.record]: polar_cloud_color = 0.456997, 0.675732, 0.771582, 1; 14:07:32.581 [planetinfo.record]: polar_land_color = 0.934, 0.81725, 0.833668, 1; 14:07:32.581 [planetinfo.record]: polar_sea_color = 0.93418, 0.923524, 0.874881, 1; 14:07:32.581 [planetinfo.record]: sea_color = 0.658203, 0.628173, 0.491081, 1; 14:07:32.581 [planetinfo.record]: rotation_speed = 0.002944 14:07:32.581 [planetinfo.record]: planet zpos = 699820.000000 14:07:32.581 [planetinfo.record]: sun_radius = 189610.626526 14:07:32.581 [planetinfo.record]: sun_vector = -0.275 -0.833 0.481 14:07:32.581 [planetinfo.record]: sun_distance = 1336020 14:07:32.581 [planetinfo.record]: corona_flare = 0.035457 14:07:32.581 [planetinfo.record]: corona_hues = 0.835129 14:07:32.582 [planetinfo.record]: sun_color = 0.198762, 0.464353, 0.733655, 1 14:07:32.582 [planetinfo.record]: corona_shimmer = 0.386156 14:07:32.582 [planetinfo.record]: station_vector = -0.504 -0.830 0.236 14:07:32.582 [planetinfo.record]: station = coriolis 14:07:37.067 [PLANETINFO OVER]: Done 14:07:38.684 [PLANETINFO LOGGING]: 6 0 14:07:39.472 [planetinfo.record]: seed = 18 128 212 237 14:07:39.472 [planetinfo.record]: coordinates = 128 150 14:07:39.472 [planetinfo.record]: government = 2; 14:07:39.472 [planetinfo.record]: economy = 6; 14:07:39.472 [planetinfo.record]: techlevel = 2; 14:07:39.472 [planetinfo.record]: population = 17; 14:07:39.472 [planetinfo.record]: productivity = 3264; 14:07:39.473 [planetinfo.record]: name = "Dizaen"; 14:07:39.473 [planetinfo.record]: inhabitant = "Insect"; 14:07:39.473 [planetinfo.record]: inhabitants = "Insects"; 14:07:39.473 [planetinfo.record]: description = "The world Dizaen is a boring world."; 14:07:39.487 [planetinfo.record]: air_color = 0.639794, 0.874341, 0.936563, 1; 14:07:39.487 [planetinfo.record]: cloud_alpha = 1.000000; 14:07:39.487 [planetinfo.record]: cloud_color = 0.629509, 0.8125, 0.526855, 1; 14:07:39.487 [planetinfo.record]: cloud_fraction = 0.200000; 14:07:39.487 [planetinfo.record]: land_color = 0.563844, 0.100547, 0.66, 1; 14:07:39.487 [planetinfo.record]: land_fraction = 0.370000; 14:07:39.487 [planetinfo.record]: polar_cloud_color = 0.743778, 0.865625, 0.675424, 1; 14:07:39.487 [planetinfo.record]: polar_land_color = 0.899981, 0.736072, 0.934, 1; 14:07:39.487 [planetinfo.record]: polar_sea_color = 0.939453, 0.923629, 0.88615, 1; 14:07:39.487 [planetinfo.record]: sea_color = 0.605469, 0.564674, 0.468056, 1; 14:07:39.488 [planetinfo.record]: rotation_speed = 0.000675 14:07:39.488 [planetinfo.record]: planet zpos = 878080.000000 14:07:39.488 [planetinfo.record]: sun_radius = 101207.011719 14:07:39.488 [planetinfo.record]: sun_vector = 0.667 0.744 0.030 14:07:39.488 [planetinfo.record]: sun_distance = 1254400 14:07:39.488 [planetinfo.record]: corona_flare = 0.025926 14:07:39.488 [planetinfo.record]: corona_hues = 0.788986 14:07:39.488 [planetinfo.record]: sun_color = 0.362918, 0.439525, 0.713495, 1 14:07:39.488 [planetinfo.record]: corona_shimmer = 0.295836 14:07:39.488 [planetinfo.record]: station_vector = 0.018 -0.643 0.766 14:07:39.488 [planetinfo.record]: station = coriolis 14:07:44.541 [PLANETINFO OVER]: Done 14:07:44.542 [PLANETINFO LOGGING]: 6 1 14:07:45.555 [planetinfo.record]: seed = 170 100 128 219 14:07:45.556 [planetinfo.record]: coordinates = 100 114 14:07:45.556 [planetinfo.record]: government = 5; 14:07:45.556 [planetinfo.record]: economy = 2; 14:07:45.556 [planetinfo.record]: techlevel = 8; 14:07:45.556 [planetinfo.record]: population = 40; 14:07:45.556 [planetinfo.record]: productivity = 23040; 14:07:45.556 [planetinfo.record]: name = "Anenen"; 14:07:45.556 [planetinfo.record]: inhabitant = "Frog"; 14:07:45.556 [planetinfo.record]: inhabitants = "Frogs"; 14:07:45.556 [planetinfo.record]: description = "The planet Anenen is well known for its inhabitants’ weird silliness but cursed by dreadful solar activity."; 14:07:45.564 [planetinfo.record]: air_color = 0.727373, 0.771187, 0.941115, 1; 14:07:45.565 [planetinfo.record]: cloud_alpha = 1.000000; 14:07:45.565 [planetinfo.record]: cloud_color = 0.7584, 0.799169, 0.826172, 1; 14:07:45.565 [planetinfo.record]: cloud_fraction = 0.440000; 14:07:45.565 [planetinfo.record]: land_color = 0.624229, 0.66, 0.373828, 1; 14:07:45.565 [planetinfo.record]: land_fraction = 0.390000; 14:07:45.565 [planetinfo.record]: polar_cloud_color = 0.827082, 0.853969, 0.871777, 1; 14:07:45.565 [planetinfo.record]: polar_land_color = 0.921345, 0.934, 0.832756, 1; 14:07:45.565 [planetinfo.record]: polar_sea_color = 0.864428, 0.921289, 0.893303, 1; 14:07:45.565 [planetinfo.record]: sea_color = 0.592792, 0.787109, 0.691469, 1; 14:07:45.565 [planetinfo.record]: rotation_speed = 0.001755 14:07:45.565 [planetinfo.record]: planet zpos = 687840.000000 14:07:45.565 [planetinfo.record]: sun_radius = 117602.384644 14:07:45.566 [planetinfo.record]: sun_vector = 0.565 0.612 -0.553 14:07:45.566 [planetinfo.record]: sun_distance = 1089080 14:07:45.566 [planetinfo.record]: corona_flare = 0.062489 14:07:45.566 [planetinfo.record]: corona_hues = 0.763420 14:07:45.566 [planetinfo.record]: sun_color = 0.777484, 0.770083, 0.567688, 1 14:07:45.566 [planetinfo.record]: corona_shimmer = 0.420674 14:07:45.566 [planetinfo.record]: station_vector = -0.701 0.713 0.009 14:07:45.566 [planetinfo.record]: station = coriolis 14:07:50.329 [PLANETINFO OVER]: Done 14:07:50.330 [PLANETINFO LOGGING]: 6 2 14:07:51.339 [planetinfo.record]: seed = 186 128 244 37 14:07:51.339 [planetinfo.record]: coordinates = 128 242 14:07:51.339 [planetinfo.record]: government = 7; 14:07:51.339 [planetinfo.record]: economy = 2; 14:07:51.339 [planetinfo.record]: techlevel = 9; 14:07:51.340 [planetinfo.record]: population = 46; 14:07:51.340 [planetinfo.record]: productivity = 32384; 14:07:51.340 [planetinfo.record]: name = "Ceor"; 14:07:51.340 [planetinfo.record]: inhabitant = "Fierce Red Horned Lobster"; 14:07:51.340 [planetinfo.record]: inhabitants = "Fierce Red Horned Lobsters"; 14:07:51.340 [planetinfo.record]: description = "This planet is noted for Zero-G cricket."; 14:07:51.366 [planetinfo.record]: air_color = 0.649337, 0.899096, 0.900791, 1; 14:07:51.366 [planetinfo.record]: cloud_alpha = 1.000000; 14:07:51.366 [planetinfo.record]: cloud_color = 0.699828, 0.705078, 0.537071, 1; 14:07:51.366 [planetinfo.record]: cloud_fraction = 0.310000; 14:07:51.366 [planetinfo.record]: land_color = 0.66, 0.234609, 0.404101, 1; 14:07:51.366 [planetinfo.record]: land_fraction = 0.460000; 14:07:51.366 [planetinfo.record]: polar_cloud_color = 0.813482, 0.817285, 0.69557, 1; 14:07:51.366 [planetinfo.record]: polar_land_color = 0.934, 0.783502, 0.843466, 1; 14:07:51.366 [planetinfo.record]: polar_sea_color = 0.941797, 0.902002, 0.88643, 1; 14:07:51.366 [planetinfo.record]: sea_color = 0.582031, 0.483657, 0.445163, 1; 14:07:51.366 [planetinfo.record]: rotation_speed = 0.000896 14:07:51.366 [planetinfo.record]: planet zpos = 591360.000000 14:07:51.366 [planetinfo.record]: sun_radius = 120547.324219 14:07:51.366 [planetinfo.record]: sun_vector = 0.354 0.607 -0.711 14:07:51.366 [planetinfo.record]: sun_distance = 844800 14:07:51.366 [planetinfo.record]: corona_flare = 0.058720 14:07:51.366 [planetinfo.record]: corona_hues = 0.572716 14:07:51.366 [planetinfo.record]: sun_color = 0.314536, 0.711371, 0.841348, 1 14:07:51.367 [planetinfo.record]: corona_shimmer = 0.414643 14:07:51.367 [planetinfo.record]: station_vector = -0.205 -0.883 0.423 14:07:51.367 [planetinfo.record]: station = coriolis 14:07:55.385 [PLANETINFO OVER]: Done 14:07:55.386 [PLANETINFO LOGGING]: 6 3 14:07:56.388 [planetinfo.record]: seed = 98 255 208 216 14:07:56.388 [planetinfo.record]: coordinates = 255 64 14:07:56.388 [planetinfo.record]: government = 4; 14:07:56.388 [planetinfo.record]: economy = 0; 14:07:56.388 [planetinfo.record]: techlevel = 12; 14:07:56.388 [planetinfo.record]: population = 53; 14:07:56.388 [planetinfo.record]: productivity = 33920; 14:07:56.388 [planetinfo.record]: name = "Ededer"; 14:07:56.388 [planetinfo.record]: inhabitant = "Insect"; 14:07:56.388 [planetinfo.record]: inhabitants = "Insects"; 14:07:56.388 [planetinfo.record]: description = "This world is a tedious place."; 14:07:56.407 [planetinfo.record]: air_color = 0.71308, 0.731779, 0.971684, 1; 14:07:56.408 [planetinfo.record]: cloud_alpha = 1.000000; 14:07:56.408 [planetinfo.record]: cloud_color = 0.735092, 0.772239, 0.917969, 1; 14:07:56.408 [planetinfo.record]: cloud_fraction = 0.280000; 14:07:56.408 [planetinfo.record]: land_color = 0.505313, 0.536733, 0.66, 1; 14:07:56.408 [planetinfo.record]: land_fraction = 0.480000; 14:07:56.408 [planetinfo.record]: polar_cloud_color = 0.799396, 0.822489, 0.913086, 1; 14:07:56.408 [planetinfo.record]: polar_land_color = 0.879273, 0.89039, 0.934, 1; 14:07:56.408 [planetinfo.record]: polar_sea_color = 0.938867, 0.904175, 0.881197, 1; 14:07:56.408 [planetinfo.record]: sea_color = 0.611328, 0.52097, 0.461123, 1; 14:07:56.408 [planetinfo.record]: rotation_speed = 0.003341 14:07:56.408 [planetinfo.record]: planet zpos = 614280.000000 14:07:56.408 [planetinfo.record]: sun_radius = 127315.753784 14:07:56.408 [planetinfo.record]: sun_vector = 0.935 -0.344 -0.086 14:07:56.408 [planetinfo.record]: sun_distance = 972610 14:07:56.408 [planetinfo.record]: corona_flare = 0.077231 14:07:56.409 [planetinfo.record]: corona_hues = 0.734558 14:07:56.409 [planetinfo.record]: sun_color = 0.475259, 0.507731, 0.738547, 1 14:07:56.409 [planetinfo.record]: corona_shimmer = 0.721969 14:07:56.409 [planetinfo.record]: station_vector = -0.293 0.608 0.738 14:07:56.409 [planetinfo.record]: station = icosahedron 14:08:01.022 [PLANETINFO OVER]: Done 14:08:01.022 [PLANETINFO LOGGING]: 6 4 14:08:02.025 [planetinfo.record]: seed = 130 225 52 234 14:08:02.025 [planetinfo.record]: coordinates = 225 240 14:08:02.025 [planetinfo.record]: government = 0; 14:08:02.025 [planetinfo.record]: economy = 2; 14:08:02.025 [planetinfo.record]: techlevel = 6; 14:08:02.026 [planetinfo.record]: population = 27; 14:08:02.026 [planetinfo.record]: productivity = 6912; 14:08:02.026 [planetinfo.record]: name = "Artesore"; 14:08:02.026 [planetinfo.record]: inhabitant = "Human Colonial"; 14:08:02.026 [planetinfo.record]: inhabitants = "Human Colonials"; 14:08:02.026 [planetinfo.record]: description = "This planet is most fabled for mud hockey but ravaged by occasional earthquakes."; 14:08:02.055 [planetinfo.record]: air_color = 0.516987, 0.483662, 0.922254, 1; 14:08:02.056 [planetinfo.record]: cloud_alpha = 1.000000; 14:08:02.056 [planetinfo.record]: cloud_color = 0.286602, 0.132263, 0.769531, 1; 14:08:02.056 [planetinfo.record]: cloud_fraction = 0.400000; 14:08:02.056 [planetinfo.record]: land_color = 0.544922, 0.511097, 0.506607, 1; 14:08:02.056 [planetinfo.record]: land_fraction = 0.350000; 14:08:02.056 [planetinfo.record]: polar_cloud_color = 0.514351, 0.408268, 0.846289, 1; 14:08:02.056 [planetinfo.record]: polar_land_color = 0.945508, 0.930835, 0.928888, 1; 14:08:02.056 [planetinfo.record]: polar_sea_color = 0.929102, 0.92818, 0.870125, 1; 14:08:02.056 [planetinfo.record]: sea_color = 0.708984, 0.706172, 0.528969, 1; 14:08:02.057 [planetinfo.record]: rotation_speed = 0.001313 14:08:02.057 [planetinfo.record]: planet zpos = 504090.000000 14:08:02.057 [planetinfo.record]: sun_radius = 90678.665314 14:08:02.057 [planetinfo.record]: sun_vector = -0.229 -0.717 -0.659 14:08:02.057 [planetinfo.record]: sun_distance = 1176210 14:08:02.057 [planetinfo.record]: corona_flare = 0.040155 14:08:02.057 [planetinfo.record]: corona_hues = 0.827805 14:08:02.057 [planetinfo.record]: sun_color = 0.503209, 0.534597, 0.740836, 1 14:08:02.057 [planetinfo.record]: corona_shimmer = 0.532328 14:08:02.059 [planetinfo.record]: station_vector = 0.388 0.700 0.600 14:08:02.059 [planetinfo.record]: station = coriolis 14:08:07.542 [PLANETINFO OVER]: Done 14:08:07.543 [PLANETINFO LOGGING]: 6 5 14:08:08.548 [planetinfo.record]: seed = 58 46 64 114 14:08:08.548 [planetinfo.record]: coordinates = 46 135 14:08:08.548 [planetinfo.record]: government = 7; 14:08:08.548 [planetinfo.record]: economy = 7; 14:08:08.548 [planetinfo.record]: techlevel = 6; 14:08:08.549 [planetinfo.record]: population = 39; 14:08:08.549 [planetinfo.record]: productivity = 10296; 14:08:08.549 [planetinfo.record]: name = "Enususge"; 14:08:08.549 [planetinfo.record]: inhabitant = "Human Colonial"; 14:08:08.549 [planetinfo.record]: inhabitants = "Human Colonials"; 14:08:08.549 [planetinfo.record]: description = "This planet is a dull world."; 14:08:08.565 [planetinfo.record]: air_color = 0.759475, 0.610322, 0.876727, 1; 14:08:08.565 [planetinfo.record]: cloud_alpha = 1.000000; 14:08:08.565 [planetinfo.record]: cloud_color = 0.632812, 0.435059, 0.495312, 1; 14:08:08.565 [planetinfo.record]: cloud_fraction = 0.320000; 14:08:08.565 [planetinfo.record]: land_color = 0.66, 0.319688, 0.359568, 1; 14:08:08.565 [planetinfo.record]: land_fraction = 0.400000; 14:08:08.565 [planetinfo.record]: polar_cloud_color = 0.784766, 0.631491, 0.678192, 1; 14:08:08.566 [planetinfo.record]: polar_land_color = 0.934, 0.813602, 0.827711, 1; 14:08:08.566 [planetinfo.record]: polar_sea_color = 0.933984, 0.859193, 0.859193, 1; 14:08:08.566 [planetinfo.record]: sea_color = 0.660156, 0.4487, 0.4487, 1; 14:08:08.566 [planetinfo.record]: rotation_speed = 0.004824 14:08:08.566 [planetinfo.record]: planet zpos = 404880.000000 14:08:08.566 [planetinfo.record]: sun_radius = 70827.434692 14:08:08.566 [planetinfo.record]: sun_vector = 0.769 0.639 -0.012 14:08:08.566 [planetinfo.record]: sun_distance = 674800 14:08:08.566 [planetinfo.record]: corona_flare = 0.098206 14:08:08.566 [planetinfo.record]: corona_hues = 0.626144 14:08:08.566 [planetinfo.record]: sun_color = 0.708218, 0.580274, 0.542173, 1 14:08:08.566 [planetinfo.record]: corona_shimmer = 0.303045 14:08:08.566 [planetinfo.record]: station_vector = -0.129 0.915 0.383 14:08:08.566 [planetinfo.record]: station = coriolis 14:08:13.620 [PLANETINFO OVER]: Done 14:08:13.620 [PLANETINFO LOGGING]: 6 6 14:08:14.624 [planetinfo.record]: seed = 106 99 148 84 14:08:14.624 [planetinfo.record]: coordinates = 99 200 14:08:14.624 [planetinfo.record]: government = 5; 14:08:14.624 [planetinfo.record]: economy = 0; 14:08:14.624 [planetinfo.record]: techlevel = 13; 14:08:14.624 [planetinfo.record]: population = 58; 14:08:14.624 [planetinfo.record]: productivity = 41760; 14:08:14.624 [planetinfo.record]: name = "Raedre"; 14:08:14.624 [planetinfo.record]: inhabitant = "Yellow Bony Lobster"; 14:08:14.624 [planetinfo.record]: inhabitants = "Yellow Bony Lobsters"; 14:08:14.624 [planetinfo.record]: description = "This planet is noted for Zero-G cricket."; 14:08:14.637 [planetinfo.record]: air_color = 0.733499, 0.732161, 0.96518, 1; 14:08:14.638 [planetinfo.record]: cloud_alpha = 1.000000; 14:08:14.638 [planetinfo.record]: cloud_color = 0.789642, 0.786133, 0.898438, 1; 14:08:14.638 [planetinfo.record]: cloud_fraction = 0.440000; 14:08:14.638 [planetinfo.record]: land_color = 0.502291, 0.66, 0.311953, 1; 14:08:14.638 [planetinfo.record]: land_fraction = 0.640000; 14:08:14.638 [planetinfo.record]: polar_cloud_color = 0.835856, 0.833649, 0.904297, 1; 14:08:14.638 [planetinfo.record]: polar_land_color = 0.878205, 0.934, 0.810865, 1; 14:08:14.638 [planetinfo.record]: polar_sea_color = 0.88961, 0.93125, 0.879504, 1; 14:08:14.638 [planetinfo.record]: sea_color = 0.564538, 0.6875, 0.534692, 1; 14:08:14.638 [planetinfo.record]: rotation_speed = 0.001538 14:08:14.638 [planetinfo.record]: planet zpos = 512070.000000 14:08:14.638 [planetinfo.record]: sun_radius = 104395.881500 14:08:14.638 [planetinfo.record]: sun_vector = -0.539 0.817 -0.206 14:08:14.638 [planetinfo.record]: sun_distance = 866580 14:08:14.638 [planetinfo.record]: corona_flare = 0.095583 14:08:14.638 [planetinfo.record]: corona_hues = 0.820862 14:08:14.638 [planetinfo.record]: sun_color = 0.659213, 0.622819, 0.273265, 1 14:08:14.639 [planetinfo.record]: corona_shimmer = 0.438034 14:08:14.639 [planetinfo.record]: station_vector = -0.208 -0.958 0.196 14:08:14.639 [planetinfo.record]: station = icosahedron 14:08:19.294 [PLANETINFO OVER]: Done 14:08:19.294 [PLANETINFO LOGGING]: 6 7 14:08:20.298 [planetinfo.record]: seed = 50 14 208 199 14:08:20.298 [planetinfo.record]: coordinates = 14 56 14:08:20.298 [planetinfo.record]: government = 6; 14:08:20.298 [planetinfo.record]: economy = 0; 14:08:20.298 [planetinfo.record]: techlevel = 12; 14:08:20.298 [planetinfo.record]: population = 55; 14:08:20.298 [planetinfo.record]: productivity = 44000; 14:08:20.299 [planetinfo.record]: name = "Sorezaan"; 14:08:20.299 [planetinfo.record]: inhabitant = "Fierce Frog"; 14:08:20.299 [planetinfo.record]: inhabitants = "Fierce Frogs"; 14:08:20.299 [planetinfo.record]: description = "This world is a tedious little planet."; 14:08:20.310 [planetinfo.record]: air_color = 0.619245, 0.528546, 0.906645, 1; 14:08:20.311 [planetinfo.record]: cloud_alpha = 1.000000; 14:08:20.311 [planetinfo.record]: cloud_color = 0.597435, 0.251236, 0.722656, 1; 14:08:20.311 [planetinfo.record]: cloud_fraction = 0.150000; 14:08:20.312 [planetinfo.record]: land_color = 0.66, 0.381563, 0.472925, 1; 14:08:20.312 [planetinfo.record]: land_fraction = 0.460000; 14:08:20.312 [planetinfo.record]: polar_cloud_color = 0.735827, 0.488751, 0.825195, 1; 14:08:20.312 [planetinfo.record]: polar_land_color = 0.934, 0.835492, 0.867815, 1; 14:08:20.312 [planetinfo.record]: polar_sea_color = 0.939648, 0.925221, 0.886885, 1; 14:08:20.312 [planetinfo.record]: sea_color = 0.603516, 0.56645, 0.46796, 1; 14:08:20.312 [planetinfo.record]: rotation_speed = 0.001421 14:08:20.312 [planetinfo.record]: planet zpos = 554640.000000 14:08:20.312 [planetinfo.record]: sun_radius = 108052.367859 14:08:20.312 [planetinfo.record]: sun_vector = 0.119 -0.696 0.708 14:08:20.312 [planetinfo.record]: sun_distance = 831960 14:08:20.312 [planetinfo.record]: corona_flare = 0.035965 14:08:20.312 [planetinfo.record]: corona_hues = 0.574226 14:08:20.312 [planetinfo.record]: sun_color = 0.260403, 0.473681, 0.651327, 1 14:08:20.312 [planetinfo.record]: corona_shimmer = 0.405786 14:08:20.313 [planetinfo.record]: station_vector = -0.543 -0.816 0.197 14:08:20.314 [planetinfo.record]: station = dodecahedron 14:08:25.177 [PLANETINFO OVER]: Done 14:08:25.178 [PLANETINFO LOGGING]: 6 8 14:08:26.189 [planetinfo.record]: seed = 114 187 20 175 14:08:26.189 [planetinfo.record]: coordinates = 187 228 14:08:26.189 [planetinfo.record]: government = 6; 14:08:26.189 [planetinfo.record]: economy = 4; 14:08:26.189 [planetinfo.record]: techlevel = 9; 14:08:26.189 [planetinfo.record]: population = 47; 14:08:26.189 [planetinfo.record]: productivity = 22560; 14:08:26.189 [planetinfo.record]: name = "Aaored"; 14:08:26.189 [planetinfo.record]: inhabitant = "Human Colonial"; 14:08:26.189 [planetinfo.record]: inhabitants = "Human Colonials"; 14:08:26.189 [planetinfo.record]: description = "The world Aaored is most famous for the Aaoredian deadly monkey."; 14:08:26.216 [planetinfo.record]: air_color = 0.613057, 0.839744, 0.979488, 1; 14:08:26.216 [planetinfo.record]: cloud_alpha = 1.000000; 14:08:26.216 [planetinfo.record]: cloud_color = 0.448639, 0.941406, 0.525634, 1; 14:08:26.216 [planetinfo.record]: cloud_fraction = 0.240000; 14:08:26.216 [planetinfo.record]: land_color = 0.66, 0.165, 0.420234, 1; 14:08:26.216 [planetinfo.record]: land_fraction = 0.340000; 14:08:26.216 [planetinfo.record]: polar_cloud_color = 0.621468, 0.923633, 0.668681, 1; 14:08:26.216 [planetinfo.record]: polar_land_color = 0.934, 0.758875, 0.849174, 1; 14:08:26.216 [planetinfo.record]: polar_sea_color = 0.910788, 0.935547, 0.886029, 1; 14:08:26.216 [planetinfo.record]: sea_color = 0.576302, 0.644531, 0.508072, 1; 14:08:26.216 [planetinfo.record]: rotation_speed = 0.001905 14:08:26.216 [planetinfo.record]: planet zpos = 821160.000000 14:08:26.216 [planetinfo.record]: sun_radius = 153867.260742 14:08:26.216 [planetinfo.record]: sun_vector = 0.822 -0.486 0.297 14:08:26.216 [planetinfo.record]: sun_distance = 1505460 14:08:26.216 [planetinfo.record]: corona_flare = 0.068137 14:08:26.216 [planetinfo.record]: corona_hues = 0.905167 14:08:26.216 [planetinfo.record]: sun_color = 0.770654, 0.704081, 0.684779, 1 14:08:26.217 [planetinfo.record]: corona_shimmer = 0.374068 14:08:26.217 [planetinfo.record]: station_vector = 0.130 0.284 0.950 14:08:26.217 [planetinfo.record]: station = coriolis 14:08:31.506 [PLANETINFO OVER]: Done 14:08:31.506 [PLANETINFO LOGGING]: 6 9 14:08:32.511 [planetinfo.record]: seed = 74 184 128 193 14:08:32.512 [planetinfo.record]: coordinates = 184 185 14:08:32.512 [planetinfo.record]: government = 1; 14:08:32.512 [planetinfo.record]: economy = 3; 14:08:32.512 [planetinfo.record]: techlevel = 5; 14:08:32.512 [planetinfo.record]: population = 25; 14:08:32.512 [planetinfo.record]: productivity = 7000; 14:08:32.512 [planetinfo.record]: name = "Lebedixe"; 14:08:32.512 [planetinfo.record]: inhabitant = "Large Bug-Eyed Lizard"; 14:08:32.512 [planetinfo.record]: inhabitants = "Large Bug-Eyed Lizards"; 14:08:32.512 [planetinfo.record]: description = "The world Lebedixe is reasonably noted for its inhabitants’ eccentric love for tourists."; 14:08:32.536 [planetinfo.record]: air_color = 0.662377, 0.883711, 0.896238, 1; 14:08:32.536 [planetinfo.record]: cloud_alpha = 1.000000; 14:08:32.536 [planetinfo.record]: cloud_color = 0.669589, 0.691406, 0.564468, 1; 14:08:32.536 [planetinfo.record]: cloud_fraction = 0.570000; 14:08:32.536 [planetinfo.record]: land_color = 0.460155, 0.66, 0.448594, 1; 14:08:32.536 [planetinfo.record]: land_fraction = 0.500000; 14:08:32.536 [planetinfo.record]: polar_cloud_color = 0.795136, 0.811133, 0.718058, 1; 14:08:32.536 [planetinfo.record]: polar_land_color = 0.863297, 0.934, 0.859207, 1; 14:08:32.536 [planetinfo.record]: polar_sea_color = 0.892933, 0.948633, 0.755942, 1; 14:08:32.536 [planetinfo.record]: sea_color = 0.393029, 0.513672, 0.0963135, 1; 14:08:32.536 [planetinfo.record]: rotation_speed = 0.003007 14:08:32.536 [planetinfo.record]: planet zpos = 390720.000000 14:08:32.536 [planetinfo.record]: sun_radius = 77893.400879 14:08:32.536 [planetinfo.record]: sun_vector = -0.682 0.154 0.715 14:08:32.536 [planetinfo.record]: sun_distance = 618640 14:08:32.536 [planetinfo.record]: corona_flare = 0.011127 14:08:32.536 [planetinfo.record]: corona_hues = 0.989235 14:08:32.536 [planetinfo.record]: sun_color = 0.733505, 0.733492, 0.730559, 1 14:08:32.537 [planetinfo.record]: corona_shimmer = 0.647666 14:08:32.537 [planetinfo.record]: station_vector = 0.104 0.984 0.141 14:08:32.537 [planetinfo.record]: station = coriolis 14:08:37.719 [PLANETINFO OVER]: Done 14:08:37.720 [PLANETINFO LOGGING]: 6 10 14:08:38.722 [planetinfo.record]: seed = 154 162 180 131 14:08:38.722 [planetinfo.record]: coordinates = 162 173 14:08:38.722 [planetinfo.record]: government = 3; 14:08:38.722 [planetinfo.record]: economy = 5; 14:08:38.722 [planetinfo.record]: techlevel = 6; 14:08:38.723 [planetinfo.record]: population = 33; 14:08:38.723 [planetinfo.record]: productivity = 9240; 14:08:38.723 [planetinfo.record]: name = "Gebequat"; 14:08:38.723 [planetinfo.record]: inhabitant = "Large Black Lizard"; 14:08:38.723 [planetinfo.record]: inhabitants = "Large Black Lizards"; 14:08:38.723 [planetinfo.record]: description = "Gebequat is famous for Gebequatian wolf cutlet but plagued by frequent earthquakes."; 14:08:38.726 [planetinfo.record]: air_color = 0.669811, 0.875627, 0.889084, 1; 14:08:38.727 [planetinfo.record]: cloud_alpha = 1.000000; 14:08:38.727 [planetinfo.record]: cloud_color = 0.651522, 0.669922, 0.575714, 1; 14:08:38.727 [planetinfo.record]: cloud_fraction = 0.510000; 14:08:38.727 [planetinfo.record]: land_color = 0.353203, 0.66, 0.501808, 1; 14:08:38.727 [planetinfo.record]: land_fraction = 0.610000; 14:08:38.727 [planetinfo.record]: polar_cloud_color = 0.787707, 0.801465, 0.731024, 1; 14:08:38.727 [planetinfo.record]: polar_land_color = 0.825459, 0.934, 0.878034, 1; 14:08:38.727 [planetinfo.record]: polar_sea_color = 0.940234, 0.886396, 0.880827, 1; 14:08:38.727 [planetinfo.record]: sea_color = 0.597656, 0.460769, 0.446608, 1; 14:08:38.727 [planetinfo.record]: rotation_speed = 0.002194 14:08:38.727 [planetinfo.record]: planet zpos = 486980.000000 14:08:38.727 [planetinfo.record]: sun_radius = 108216.507568 14:08:38.727 [planetinfo.record]: sun_vector = -0.669 0.738 -0.088 14:08:38.727 [planetinfo.record]: sun_distance = 899040 14:08:38.727 [planetinfo.record]: corona_flare = 0.076440 14:08:38.727 [planetinfo.record]: corona_hues = 0.887047 14:08:38.728 [planetinfo.record]: sun_color = 0.82695, 0.675508, 0.667723, 1 14:08:38.728 [planetinfo.record]: corona_shimmer = 0.414465 14:08:38.728 [planetinfo.record]: station_vector = -0.744 -0.581 0.332 14:08:38.728 [planetinfo.record]: station = coriolis 14:08:42.979 [PLANETINFO OVER]: Done 14:08:42.980 [PLANETINFO LOGGING]: 6 11 14:08:43.984 [planetinfo.record]: seed = 130 81 80 31 14:08:43.984 [planetinfo.record]: coordinates = 81 250 14:08:43.984 [planetinfo.record]: government = 0; 14:08:43.984 [planetinfo.record]: economy = 2; 14:08:43.984 [planetinfo.record]: techlevel = 6; 14:08:43.984 [planetinfo.record]: population = 27; 14:08:43.984 [planetinfo.record]: productivity = 6912; 14:08:43.984 [planetinfo.record]: name = "Onaran"; 14:08:43.984 [planetinfo.record]: inhabitant = "Human Colonial"; 14:08:43.984 [planetinfo.record]: inhabitants = "Human Colonials"; 14:08:43.984 [planetinfo.record]: description = "The world Onaran is very noted for its ancient mountains but plagued by vicious disease."; 14:08:43.993 [planetinfo.record]: air_color = 0.752466, 0.738673, 0.967131, 1; 14:08:43.993 [planetinfo.record]: cloud_alpha = 1.000000; 14:08:43.993 [planetinfo.record]: cloud_color = 0.824707, 0.805389, 0.904297, 1; 14:08:43.993 [planetinfo.record]: cloud_fraction = 0.150000; 14:08:43.994 [planetinfo.record]: land_color = 0.576172, 0.411873, 0.546649, 1; 14:08:43.994 [planetinfo.record]: land_fraction = 0.400000; 14:08:43.994 [planetinfo.record]: polar_cloud_color = 0.857045, 0.844936, 0.906934, 1; 14:08:43.994 [planetinfo.record]: polar_land_color = 0.942383, 0.875201, 0.930311, 1; 14:08:43.994 [planetinfo.record]: polar_sea_color = 0.933203, 0.858445, 0.856651, 1; 14:08:43.994 [planetinfo.record]: sea_color = 0.667969, 0.453928, 0.448792, 1; 14:08:43.995 [planetinfo.record]: rotation_speed = 0.004479 14:08:43.995 [planetinfo.record]: planet zpos = 875810.000000 14:08:43.995 [planetinfo.record]: sun_radius = 175681.543427 14:08:43.995 [planetinfo.record]: sun_vector = -0.344 0.708 0.616 14:08:43.995 [planetinfo.record]: sun_distance = 1212660 14:08:43.995 [planetinfo.record]: corona_flare = 0.050220 14:08:43.995 [planetinfo.record]: corona_hues = 0.906593 14:08:43.995 [planetinfo.record]: sun_color = 0.305357, 0.432082, 0.757288, 1 14:08:43.995 [planetinfo.record]: corona_shimmer = 0.364782 14:08:43.996 [planetinfo.record]: station_vector = -0.037 -0.938 0.343 14:08:43.996 [planetinfo.record]: station = coriolis 14:08:48.646 [PLANETINFO OVER]: Done 14:08:48.648 [PLANETINFO LOGGING]: 6 12 14:08:49.667 [planetinfo.record]: seed = 226 101 116 172 14:08:49.667 [planetinfo.record]: coordinates = 101 219 14:08:49.667 [planetinfo.record]: government = 4; 14:08:49.667 [planetinfo.record]: economy = 3; 14:08:49.667 [planetinfo.record]: techlevel = 7; 14:08:49.667 [planetinfo.record]: population = 36; 14:08:49.667 [planetinfo.record]: productivity = 16128; 14:08:49.668 [planetinfo.record]: name = "Inre"; 14:08:49.668 [planetinfo.record]: inhabitant = "Human Colonial"; 14:08:49.668 [planetinfo.record]: inhabitants = "Human Colonials"; 14:08:49.668 [planetinfo.record]: description = "Inre is ravaged by unpredictable civil war."; 14:08:49.684 [planetinfo.record]: air_color = 0.553861, 0.933021, 0.994447, 1; 14:08:49.684 [planetinfo.record]: cloud_alpha = 1.000000; 14:08:49.684 [planetinfo.record]: cloud_color = 0.676746, 0.986328, 0.265846, 1; 14:08:49.684 [planetinfo.record]: cloud_fraction = 0.400000; 14:08:49.684 [planetinfo.record]: land_color = 0.658268, 0.66, 0.438281, 1; 14:08:49.684 [planetinfo.record]: land_fraction = 0.420000; 14:08:49.684 [planetinfo.record]: polar_cloud_color = 0.758692, 0.943848, 0.512941, 1; 14:08:49.685 [planetinfo.record]: polar_land_color = 0.933387, 0.934, 0.855559, 1; 14:08:49.685 [planetinfo.record]: polar_sea_color = 0.94043, 0.858693, 0.937237, 1; 14:08:49.685 [planetinfo.record]: sea_color = 0.595703, 0.388603, 0.587613, 1; 14:08:49.685 [planetinfo.record]: rotation_speed = 0.002515 14:08:49.685 [planetinfo.record]: planet zpos = 718680.000000 14:08:49.685 [planetinfo.record]: sun_radius = 141748.013153 14:08:49.685 [planetinfo.record]: sun_vector = 0.540 -0.229 0.810 14:08:49.685 [planetinfo.record]: sun_distance = 1137910 14:08:49.685 [planetinfo.record]: corona_flare = 0.008311 14:08:49.685 [planetinfo.record]: corona_hues = 0.605247 14:08:49.685 [planetinfo.record]: sun_color = 0.69487, 0.602552, 0.511008, 1 14:08:49.686 [planetinfo.record]: corona_shimmer = 0.439326 14:08:49.686 [planetinfo.record]: station_vector = -0.617 0.689 0.381 14:08:49.686 [planetinfo.record]: station = coriolis 14:08:54.406 [PLANETINFO OVER]: Done 14:08:54.407 [PLANETINFO LOGGING]: 6 13 14:08:55.410 [planetinfo.record]: seed = 218 154 64 137 14:08:55.410 [planetinfo.record]: coordinates = 154 0 14:08:55.410 [planetinfo.record]: government = 3; 14:08:55.410 [planetinfo.record]: economy = 0; 14:08:55.410 [planetinfo.record]: techlevel = 11; 14:08:55.410 [planetinfo.record]: population = 48; 14:08:55.410 [planetinfo.record]: productivity = 26880; 14:08:55.410 [planetinfo.record]: name = "Eszausve"; 14:08:55.410 [planetinfo.record]: inhabitant = "Human Colonial"; 14:08:55.410 [planetinfo.record]: inhabitants = "Human Colonials"; 14:08:55.410 [planetinfo.record]: description = "Eszausve is famous for its inhabitants’ ancient loathing of discos but ravaged by lethal spotted craboids."; 14:08:55.424 [planetinfo.record]: air_color = 0.697617, 0.84841, 0.876076, 1; 14:08:55.424 [planetinfo.record]: cloud_alpha = 1.000000; 14:08:55.424 [planetinfo.record]: cloud_color = 0.627336, 0.630859, 0.623466, 1; 14:08:55.424 [planetinfo.record]: cloud_fraction = 0.230000; 14:08:55.424 [planetinfo.record]: land_color = 0.532665, 0.66, 0.510469, 1; 14:08:55.424 [planetinfo.record]: land_fraction = 0.590000; 14:08:55.424 [planetinfo.record]: polar_cloud_color = 0.781151, 0.783887, 0.778145, 1; 14:08:55.424 [planetinfo.record]: polar_land_color = 0.88895, 0.934, 0.881098, 1; 14:08:55.424 [planetinfo.record]: polar_sea_color = 0.917196, 0.874389, 0.946484, 1; 14:08:55.424 [planetinfo.record]: sea_color = 0.468915, 0.372101, 0.535156, 1; 14:08:55.424 [planetinfo.record]: rotation_speed = 0.001101 14:08:55.424 [planetinfo.record]: planet zpos = 632880.000000 14:08:55.424 [planetinfo.record]: sun_radius = 126084.781494 14:08:55.424 [planetinfo.record]: sun_vector = 0.490 0.749 0.446 14:08:55.424 [planetinfo.record]: sun_distance = 896580 14:08:55.424 [planetinfo.record]: corona_flare = 0.041725 14:08:55.424 [planetinfo.record]: corona_hues = 0.829086 14:08:55.424 [planetinfo.record]: sun_color = 0.24855, 0.587987, 0.796509, 1 14:08:55.425 [planetinfo.record]: corona_shimmer = 0.394776 14:08:55.425 [planetinfo.record]: station_vector = 0.312 0.241 0.919 14:08:55.425 [planetinfo.record]: station = dodecahedron 14:09:00.324 [PLANETINFO OVER]: Done 14:09:00.325 [PLANETINFO LOGGING]: 6 14 14:09:01.336 [planetinfo.record]: seed = 74 246 84 99 14:09:01.336 [planetinfo.record]: coordinates = 246 72 14:09:01.336 [planetinfo.record]: government = 1; 14:09:01.336 [planetinfo.record]: economy = 2; 14:09:01.336 [planetinfo.record]: techlevel = 8; 14:09:01.336 [planetinfo.record]: population = 36; 14:09:01.336 [planetinfo.record]: productivity = 11520; 14:09:01.336 [planetinfo.record]: name = "Gexean"; 14:09:01.336 [planetinfo.record]: inhabitant = "Human Colonial"; 14:09:01.336 [planetinfo.record]: inhabitants = "Human Colonials"; 14:09:01.336 [planetinfo.record]: description = "The planet Gexean is reasonably noted for mud tennis and its ancient mountains."; 14:09:01.360 [planetinfo.record]: air_color = 0.606284, 0.857112, 0.978838, 1; 14:09:01.360 [planetinfo.record]: cloud_alpha = 1.000000; 14:09:01.360 [planetinfo.record]: cloud_color = 0.433345, 0.939453, 0.429359, 1; 14:09:01.360 [planetinfo.record]: cloud_fraction = 0.370000; 14:09:01.360 [planetinfo.record]: land_color = 0.020625, 0.66, 0.46519, 1; 14:09:01.360 [planetinfo.record]: land_fraction = 0.650000; 14:09:01.360 [planetinfo.record]: polar_cloud_color = 0.612059, 0.922754, 0.609612, 1; 14:09:01.360 [planetinfo.record]: polar_land_color = 0.707797, 0.934, 0.865079, 1; 14:09:01.360 [planetinfo.record]: polar_sea_color = 0.938477, 0.880793, 0.876431, 1; 14:09:01.361 [planetinfo.record]: sea_color = 0.615234, 0.463974, 0.452534, 1; 14:09:01.361 [planetinfo.record]: rotation_speed = 0.002817 14:09:01.361 [planetinfo.record]: planet zpos = 421300.000000 14:09:01.361 [planetinfo.record]: sun_radius = 102357.942200 14:09:01.361 [planetinfo.record]: sun_vector = -0.807 0.508 -0.300 14:09:01.361 [planetinfo.record]: sun_distance = 880900 14:09:01.361 [planetinfo.record]: corona_flare = 0.086966 14:09:01.361 [planetinfo.record]: corona_hues = 0.815720 14:09:01.361 [planetinfo.record]: sun_color = 0.735266, 0.706554, 0.660874, 1 14:09:01.361 [planetinfo.record]: corona_shimmer = 0.289021 14:09:01.361 [planetinfo.record]: station_vector = -0.569 -0.585 0.578 14:09:01.361 [planetinfo.record]: station = coriolis 14:09:06.258 [PLANETINFO OVER]: Done 14:09:06.258 [PLANETINFO LOGGING]: 6 15 14:09:07.262 [planetinfo.record]: seed = 82 1 80 159 14:09:07.262 [planetinfo.record]: coordinates = 1 251 14:09:07.263 [planetinfo.record]: government = 2; 14:09:07.263 [planetinfo.record]: economy = 3; 14:09:07.263 [planetinfo.record]: techlevel = 6; 14:09:07.263 [planetinfo.record]: population = 30; 14:09:07.263 [planetinfo.record]: productivity = 10080; 14:09:07.263 [planetinfo.record]: name = "Onteised"; 14:09:07.263 [planetinfo.record]: inhabitant = "Human Colonial"; 14:09:07.263 [planetinfo.record]: inhabitants = "Human Colonials"; 14:09:07.263 [planetinfo.record]: description = "This planet is mildly noted for its ancient mountains but plagued by lethal disease."; 14:09:07.280 [planetinfo.record]: air_color = 0.436843, 0.642742, 0.897539, 1; 14:09:07.280 [planetinfo.record]: cloud_alpha = 1.000000; 14:09:07.280 [planetinfo.record]: cloud_color = 0.040741, 0.695312, 0.480531, 1; 14:09:07.280 [planetinfo.record]: cloud_fraction = 0.460000; 14:09:07.280 [planetinfo.record]: land_color = 0.150757, 0.59375, 0.427628, 1; 14:09:07.280 [planetinfo.record]: land_fraction = 0.400000; 14:09:07.280 [planetinfo.record]: polar_cloud_color = 0.334603, 0.812891, 0.655953, 1; 14:09:07.280 [planetinfo.record]: polar_land_color = 0.765176, 0.940625, 0.874832, 1; 14:09:07.280 [planetinfo.record]: polar_sea_color = 0.916529, 0.926953, 0.866303, 1; 14:09:07.280 [planetinfo.record]: sea_color = 0.69761, 0.730469, 0.539291, 1; 14:09:07.280 [planetinfo.record]: rotation_speed = 0.002650 14:09:07.280 [planetinfo.record]: planet zpos = 798840.000000 14:09:07.280 [planetinfo.record]: sun_radius = 149878.998871 14:09:07.280 [planetinfo.record]: sun_vector = -0.259 -0.839 -0.479 14:09:07.280 [planetinfo.record]: sun_distance = 1464540 14:09:07.280 [planetinfo.record]: corona_flare = 0.006265 14:09:07.280 [planetinfo.record]: corona_hues = 0.592613 14:09:07.280 [planetinfo.record]: sun_color = 0.369868, 0.711987, 0.716757, 1 14:09:07.281 [planetinfo.record]: corona_shimmer = 0.432370 14:09:07.281 [planetinfo.record]: station_vector = -0.732 -0.641 0.231 14:09:07.281 [planetinfo.record]: station = coriolis 14:09:11.665 [PLANETINFO OVER]: Done 14:09:11.666 [PLANETINFO LOGGING]: 6 16 14:09:12.670 [planetinfo.record]: seed = 210 120 84 82 14:09:12.670 [planetinfo.record]: coordinates = 120 61 14:09:12.670 [planetinfo.record]: government = 2; 14:09:12.670 [planetinfo.record]: economy = 5; 14:09:12.670 [planetinfo.record]: techlevel = 3; 14:09:12.670 [planetinfo.record]: population = 20; 14:09:12.670 [planetinfo.record]: productivity = 4800; 14:09:12.670 [planetinfo.record]: name = "Enusbe"; 14:09:12.670 [planetinfo.record]: inhabitant = "Human Colonial"; 14:09:12.670 [planetinfo.record]: inhabitants = "Human Colonials"; 14:09:12.670 [planetinfo.record]: description = "This planet is very notable for the Enusbeian tree snake and its unusual casinos."; 14:09:12.712 [planetinfo.record]: air_color = 0.693087, 0.770141, 0.958025, 1; 14:09:12.712 [planetinfo.record]: cloud_alpha = 1.000000; 14:09:12.712 [planetinfo.record]: cloud_color = 0.674843, 0.848531, 0.876953, 1; 14:09:12.712 [planetinfo.record]: cloud_fraction = 0.380000; 14:09:12.712 [planetinfo.record]: land_color = 0.212708, 0.349775, 0.640625, 1; 14:09:12.712 [planetinfo.record]: land_fraction = 0.340000; 14:09:12.712 [planetinfo.record]: polar_cloud_color = 0.765764, 0.876507, 0.894629, 1; 14:09:12.712 [planetinfo.record]: polar_land_color = 0.779643, 0.829706, 0.935938, 1; 14:09:12.712 [planetinfo.record]: polar_sea_color = 0.938086, 0.933536, 0.885135, 1; 14:09:12.712 [planetinfo.record]: sea_color = 0.619141, 0.607127, 0.47935, 1; 14:09:12.713 [planetinfo.record]: rotation_speed = 0.003102 14:09:12.713 [planetinfo.record]: planet zpos = 482720.000000 14:09:12.713 [planetinfo.record]: sun_radius = 102870.208740 14:09:12.713 [planetinfo.record]: sun_vector = -0.550 0.397 -0.734 14:09:12.713 [planetinfo.record]: sun_distance = 620640 14:09:12.713 [planetinfo.record]: corona_flare = 0.051427 14:09:12.713 [planetinfo.record]: corona_hues = 0.763634 14:09:12.713 [planetinfo.record]: sun_color = 0.444839, 0.638175, 0.700217, 1 14:09:12.713 [planetinfo.record]: corona_shimmer = 0.348096 14:09:12.713 [planetinfo.record]: station_vector = -0.666 -0.688 0.289 14:09:12.713 [planetinfo.record]: station = coriolis 14:09:18.039 [PLANETINFO OVER]: Done 14:09:18.040 [PLANETINFO LOGGING]: 6 17 14:09:19.043 [planetinfo.record]: seed = 234 45 128 9 14:09:19.043 [planetinfo.record]: coordinates = 45 211 14:09:19.043 [planetinfo.record]: government = 5; 14:09:19.043 [planetinfo.record]: economy = 3; 14:09:19.043 [planetinfo.record]: techlevel = 8; 14:09:19.043 [planetinfo.record]: population = 41; 14:09:19.043 [planetinfo.record]: productivity = 20664; 14:09:19.043 [planetinfo.record]: name = "Esarxeve"; 14:09:19.043 [planetinfo.record]: inhabitant = "Small Green Insect"; 14:09:19.043 [planetinfo.record]: inhabitants = "Small Green Insects"; 14:09:19.043 [planetinfo.record]: description = "The planet Esarxeve is reasonably fabled for mud tennis and its ancient mountains."; 14:09:19.056 [planetinfo.record]: air_color = 0.721039, 0.507328, 0.950871, 1; 14:09:19.056 [planetinfo.record]: cloud_alpha = 1.000000; 14:09:19.056 [planetinfo.record]: cloud_color = 0.855469, 0.170425, 0.539707, 1; 14:09:19.056 [planetinfo.record]: cloud_fraction = 0.540000; 14:09:19.056 [planetinfo.record]: land_color = 0.66, 0.146953, 0.303272, 1; 14:09:19.056 [planetinfo.record]: land_fraction = 0.310000; 14:09:19.056 [planetinfo.record]: polar_cloud_color = 0.884961, 0.442048, 0.680806, 1; 14:09:19.056 [planetinfo.record]: polar_land_color = 0.934, 0.75249, 0.807794, 1; 14:09:19.056 [planetinfo.record]: polar_sea_color = 0.934037, 0.936914, 0.884304, 1; 14:09:19.056 [planetinfo.record]: sea_color = 0.62311, 0.630859, 0.489162, 1; 14:09:19.056 [planetinfo.record]: rotation_speed = 0.004151 14:09:19.056 [planetinfo.record]: planet zpos = 619800.000000 14:09:19.056 [planetinfo.record]: sun_radius = 154431.419373 14:09:19.056 [planetinfo.record]: sun_vector = 0.527 0.765 -0.370 14:09:19.056 [planetinfo.record]: sun_distance = 981350 14:09:19.056 [planetinfo.record]: corona_flare = 0.011739 14:09:19.056 [planetinfo.record]: corona_hues = 0.623985 14:09:19.056 [planetinfo.record]: sun_color = 0.671502, 0.680488, 0.751743, 1 14:09:19.056 [planetinfo.record]: corona_shimmer = 0.361881 14:09:19.057 [planetinfo.record]: station_vector = 0.431 0.722 0.541 14:09:19.057 [planetinfo.record]: station = coriolis 14:09:23.585 [PLANETINFO OVER]: Done 14:09:23.586 [PLANETINFO LOGGING]: 6 18 14:09:24.599 [planetinfo.record]: seed = 122 86 116 163 14:09:24.599 [planetinfo.record]: coordinates = 86 66 14:09:24.599 [planetinfo.record]: government = 7; 14:09:24.599 [planetinfo.record]: economy = 2; 14:09:24.599 [planetinfo.record]: techlevel = 11; 14:09:24.600 [planetinfo.record]: population = 54; 14:09:24.600 [planetinfo.record]: productivity = 38016; 14:09:24.600 [planetinfo.record]: name = "Geteve"; 14:09:24.600 [planetinfo.record]: inhabitant = "Human Colonial"; 14:09:24.600 [planetinfo.record]: inhabitants = "Human Colonials"; 14:09:24.600 [planetinfo.record]: description = "This world is mildly famous for its hoopy casinos and its hoopy casinos."; 14:09:24.601 [planetinfo.record]: air_color = 0.7351, 0.727956, 0.972984, 1; 14:09:24.601 [planetinfo.record]: cloud_alpha = 1.000000; 14:09:24.601 [planetinfo.record]: cloud_color = 0.792461, 0.777832, 0.921875, 1; 14:09:24.601 [planetinfo.record]: cloud_fraction = 0.350000; 14:09:24.601 [planetinfo.record]: land_color = 0.602959, 0.66, 0.598125, 1; 14:09:24.601 [planetinfo.record]: land_fraction = 0.340000; 14:09:24.601 [planetinfo.record]: polar_cloud_color = 0.834577, 0.825504, 0.914844, 1; 14:09:24.601 [planetinfo.record]: polar_land_color = 0.91382, 0.934, 0.912109, 1; 14:09:24.601 [planetinfo.record]: polar_sea_color = 0.709848, 0.906777, 0.927148, 1; 14:09:24.601 [planetinfo.record]: sea_color = 0.0455322, 0.664486, 0.728516, 1; 14:09:24.601 [planetinfo.record]: rotation_speed = 0.003446 14:09:24.601 [planetinfo.record]: planet zpos = 403700.000000 14:09:24.602 [planetinfo.record]: sun_radius = 111531.353760 14:09:24.602 [planetinfo.record]: sun_vector = 0.170 0.587 0.792 14:09:24.602 [planetinfo.record]: sun_distance = 697300 14:09:24.602 [planetinfo.record]: corona_flare = 0.035118 14:09:24.602 [planetinfo.record]: corona_hues = 0.862114 14:09:24.602 [planetinfo.record]: sun_color = 0.297827, 0.515369, 0.681192, 1 14:09:24.602 [planetinfo.record]: corona_shimmer = 0.290280 14:09:24.602 [planetinfo.record]: station_vector = 0.872 -0.486 0.063 14:09:24.602 [planetinfo.record]: station = dodecahedron 14:09:29.439 [PLANETINFO OVER]: Done 14:09:29.440 [PLANETINFO LOGGING]: 6 19 14:09:30.443 [planetinfo.record]: seed = 162 21 208 135 14:09:30.443 [planetinfo.record]: coordinates = 21 54 14:09:30.443 [planetinfo.record]: government = 4; 14:09:30.443 [planetinfo.record]: economy = 6; 14:09:30.443 [planetinfo.record]: techlevel = 4; 14:09:30.443 [planetinfo.record]: population = 27; 14:09:30.443 [planetinfo.record]: productivity = 6912; 14:09:30.443 [planetinfo.record]: name = "Sobeer"; 14:09:30.443 [planetinfo.record]: inhabitant = "Fierce Black Bony Feline"; 14:09:30.443 [planetinfo.record]: inhabitants = "Fierce Black Bony Felines"; 14:09:30.443 [planetinfo.record]: description = "This planet is mildly fabled for the Sobeerian mountain slug but cursed by occasional earthquakes."; 14:09:30.464 [planetinfo.record]: air_color = 0.400693, 0.843557, 0.770501, 1; 14:09:30.464 [planetinfo.record]: cloud_alpha = 1.000000; 14:09:30.464 [planetinfo.record]: cloud_color = 0.533203, 0.280986, 0.0124969, 1; 14:09:30.464 [planetinfo.record]: cloud_fraction = 0.640000; 14:09:30.464 [planetinfo.record]: land_color = 0.462693, 0.66, 0.340313, 1; 14:09:30.464 [planetinfo.record]: land_fraction = 0.370000; 14:09:30.464 [planetinfo.record]: polar_cloud_color = 0.739941, 0.521186, 0.288317, 1; 14:09:30.464 [planetinfo.record]: polar_land_color = 0.864195, 0.934, 0.820898, 1; 14:09:30.464 [planetinfo.record]: polar_sea_color = 0.916642, 0.937891, 0.890996, 1; 14:09:30.464 [planetinfo.record]: sea_color = 0.564807, 0.621094, 0.496875, 1; 14:09:30.464 [planetinfo.record]: rotation_speed = 0.000768 14:09:30.464 [planetinfo.record]: planet zpos = 601770.000000 14:09:30.464 [planetinfo.record]: sun_radius = 113077.677612 14:09:30.464 [planetinfo.record]: sun_vector = -0.432 0.123 -0.894 14:09:30.464 [planetinfo.record]: sun_distance = 925800 14:09:30.464 [planetinfo.record]: corona_flare = 0.066385 14:09:30.464 [planetinfo.record]: corona_hues = 0.713287 14:09:30.464 [planetinfo.record]: sun_color = 0.768594, 0.752676, 0.642567, 1 14:09:30.464 [planetinfo.record]: corona_shimmer = 0.593451 14:09:30.465 [planetinfo.record]: station_vector = -0.963 -0.252 0.091 14:09:30.465 [planetinfo.record]: station = coriolis 14:09:35.333 [PLANETINFO OVER]: Done 14:09:35.334 [PLANETINFO LOGGING]: 6 20 14:09:36.338 [planetinfo.record]: seed = 66 204 180 16 14:09:36.338 [planetinfo.record]: coordinates = 204 112 14:09:36.338 [planetinfo.record]: government = 0; 14:09:36.338 [planetinfo.record]: economy = 2; 14:09:36.338 [planetinfo.record]: techlevel = 5; 14:09:36.338 [planetinfo.record]: population = 23; 14:09:36.339 [planetinfo.record]: productivity = 5888; 14:09:36.339 [planetinfo.record]: name = "Erdiares"; 14:09:36.339 [planetinfo.record]: inhabitant = "Green Fat Bird"; 14:09:36.339 [planetinfo.record]: inhabitants = "Green Fat Birds"; 14:09:36.339 [planetinfo.record]: description = "This world is most fabled for Erdiaresian evil juice but cursed by dreadful solar activity."; 14:09:36.363 [planetinfo.record]: air_color = 0.655111, 0.869572, 0.830847, 1; 14:09:36.363 [planetinfo.record]: cloud_alpha = 1.000000; 14:09:36.363 [planetinfo.record]: cloud_color = 0.611328, 0.565658, 0.52536, 1; 14:09:36.363 [planetinfo.record]: cloud_fraction = 0.220000; 14:09:36.363 [planetinfo.record]: land_color = 0.66, 0.151425, 0.139219, 1; 14:09:36.364 [planetinfo.record]: land_fraction = 0.530000; 14:09:36.364 [planetinfo.record]: polar_cloud_color = 0.775098, 0.738907, 0.706974, 1; 14:09:36.364 [planetinfo.record]: polar_land_color = 0.934, 0.754072, 0.749754, 1; 14:09:36.364 [planetinfo.record]: polar_sea_color = 0.897486, 0.86956, 0.947266, 1; 14:09:36.364 [planetinfo.record]: sea_color = 0.416493, 0.354309, 0.527344, 1; 14:09:36.364 [planetinfo.record]: rotation_speed = 0.003710 14:09:36.364 [planetinfo.record]: planet zpos = 332200.000000 14:09:36.364 [planetinfo.record]: sun_radius = 91823.925781 14:09:36.364 [planetinfo.record]: sun_vector = 0.158 0.552 -0.818 14:09:36.364 [planetinfo.record]: sun_distance = 664400 14:09:36.364 [planetinfo.record]: corona_flare = 0.051366 14:09:36.364 [planetinfo.record]: corona_hues = 0.763840 14:09:36.364 [planetinfo.record]: sun_color = 0.275016, 0.285178, 0.662961, 1 14:09:36.365 [planetinfo.record]: corona_shimmer = 0.356008 14:09:36.365 [planetinfo.record]: station_vector = -0.060 -0.244 0.968 14:09:36.365 [planetinfo.record]: station = coriolis 14:09:41.143 [PLANETINFO OVER]: Done 14:09:41.144 [PLANETINFO LOGGING]: 6 21 14:09:42.146 [planetinfo.record]: seed = 122 137 64 2 14:09:42.146 [planetinfo.record]: coordinates = 137 42 14:09:42.146 [planetinfo.record]: government = 7; 14:09:42.146 [planetinfo.record]: economy = 2; 14:09:42.146 [planetinfo.record]: techlevel = 10; 14:09:42.147 [planetinfo.record]: population = 50; 14:09:42.147 [planetinfo.record]: productivity = 35200; 14:09:42.147 [planetinfo.record]: name = "Xevexean"; 14:09:42.147 [planetinfo.record]: inhabitant = "Human Colonial"; 14:09:42.147 [planetinfo.record]: inhabitants = "Human Colonials"; 14:09:42.147 [planetinfo.record]: description = "The world Xevexean is reasonably famous for the Xevexeanian evil talking treeoid."; 14:09:42.161 [planetinfo.record]: air_color = 0.76949, 0.593897, 0.894938, 1; 14:09:42.161 [planetinfo.record]: cloud_alpha = 1.000000; 14:09:42.161 [planetinfo.record]: cloud_color = 0.6875, 0.405518, 0.471607, 1; 14:09:42.161 [planetinfo.record]: cloud_fraction = 0.580000; 14:09:42.161 [planetinfo.record]: land_color = 0.219119, 0.537109, 0.117493, 1; 14:09:42.161 [planetinfo.record]: land_fraction = 0.680000; 14:09:42.161 [planetinfo.record]: polar_cloud_color = 0.809375, 0.601894, 0.650522, 1; 14:09:42.161 [planetinfo.record]: polar_land_color = 0.806229, 0.946289, 0.761467, 1; 14:09:42.161 [planetinfo.record]: polar_sea_color = 0.869381, 0.868751, 0.949414, 1; 14:09:42.161 [planetinfo.record]: sea_color = 0.335289, 0.333946, 0.505859, 1; 14:09:42.161 [planetinfo.record]: rotation_speed = 0.002287 14:09:42.161 [planetinfo.record]: planet zpos = 311850.000000 14:09:42.161 [planetinfo.record]: sun_radius = 99625.623322 14:09:42.161 [planetinfo.record]: sun_vector = 0.315 0.935 0.162 14:09:42.161 [planetinfo.record]: sun_distance = 727650 14:09:42.161 [planetinfo.record]: corona_flare = 0.046178 14:09:42.161 [planetinfo.record]: corona_hues = 0.609184 14:09:42.161 [planetinfo.record]: sun_color = 0.277971, 0.675444, 0.736328, 1 14:09:42.161 [planetinfo.record]: corona_shimmer = 0.645306 14:09:42.162 [planetinfo.record]: station_vector = 0.435 0.784 0.443 14:09:42.162 [planetinfo.record]: station = coriolis 14:09:46.327 [PLANETINFO OVER]: Done 14:09:46.327 [PLANETINFO LOGGING]: 6 22 14:09:47.329 [planetinfo.record]: seed = 42 251 20 244 14:09:47.329 [planetinfo.record]: coordinates = 251 66 14:09:47.329 [planetinfo.record]: government = 5; 14:09:47.329 [planetinfo.record]: economy = 2; 14:09:47.329 [planetinfo.record]: techlevel = 11; 14:09:47.329 [planetinfo.record]: population = 52; 14:09:47.329 [planetinfo.record]: productivity = 29952; 14:09:47.329 [planetinfo.record]: name = "Raatbi"; 14:09:47.329 [planetinfo.record]: inhabitant = "Human Colonial"; 14:09:47.329 [planetinfo.record]: inhabitants = "Human Colonials"; 14:09:47.329 [planetinfo.record]: description = "This world is a revolting little planet."; 14:09:47.344 [planetinfo.record]: air_color = 0.678973, 0.775396, 0.964529, 1; 14:09:47.344 [planetinfo.record]: cloud_alpha = 1.000000; 14:09:47.344 [planetinfo.record]: cloud_color = 0.637344, 0.896484, 0.896484, 1; 14:09:47.344 [planetinfo.record]: cloud_fraction = 0.530000; 14:09:47.344 [planetinfo.record]: land_color = 0.426055, 0.66, 0.399609, 1; 14:09:47.344 [planetinfo.record]: land_fraction = 0.710000; 14:09:47.344 [planetinfo.record]: polar_cloud_color = 0.740203, 0.903418, 0.903418, 1; 14:09:47.344 [planetinfo.record]: polar_land_color = 0.851233, 0.934, 0.841877, 1; 14:09:47.344 [planetinfo.record]: polar_sea_color = 0.941992, 0.928359, 0.892133, 1; 14:09:47.344 [planetinfo.record]: sea_color = 0.580078, 0.546496, 0.457265, 1; 14:09:47.344 [planetinfo.record]: rotation_speed = 0.004134 14:09:47.344 [planetinfo.record]: planet zpos = 490920.000000 14:09:47.344 [planetinfo.record]: sun_radius = 110014.915314 14:09:47.344 [planetinfo.record]: sun_vector = -0.728 0.353 0.588 14:09:47.344 [planetinfo.record]: sun_distance = 736380 14:09:47.344 [planetinfo.record]: corona_flare = 0.039697 14:09:47.344 [planetinfo.record]: corona_hues = 0.662918 14:09:47.344 [planetinfo.record]: sun_color = 0.790924, 0.6552, 0.427039, 1 14:09:47.345 [planetinfo.record]: corona_shimmer = 0.267894 14:09:47.345 [planetinfo.record]: station_vector = -0.326 -0.676 0.661 14:09:47.345 [planetinfo.record]: station = icosahedron 14:09:52.159 [PLANETINFO OVER]: Done 14:09:52.159 [PLANETINFO LOGGING]: 6 23 14:09:53.162 [planetinfo.record]: seed = 114 70 208 152 14:09:53.162 [planetinfo.record]: coordinates = 70 32 14:09:53.162 [planetinfo.record]: government = 6; 14:09:53.162 [planetinfo.record]: economy = 0; 14:09:53.162 [planetinfo.record]: techlevel = 12; 14:09:53.162 [planetinfo.record]: population = 55; 14:09:53.162 [planetinfo.record]: productivity = 44000; 14:09:53.162 [planetinfo.record]: name = "Edoned"; 14:09:53.162 [planetinfo.record]: inhabitant = "Black Feline"; 14:09:53.162 [planetinfo.record]: inhabitants = "Black Felines"; 14:09:53.162 [planetinfo.record]: description = "This world is very fabled for its unusual oceans."; 14:09:53.184 [planetinfo.record]: air_color = 0.681188, 0.863689, 0.885832, 1; 14:09:53.184 [planetinfo.record]: cloud_alpha = 1.000000; 14:09:53.184 [planetinfo.record]: cloud_color = 0.639365, 0.660156, 0.598267, 1; 14:09:53.184 [planetinfo.record]: cloud_fraction = 0.550000; 14:09:53.184 [planetinfo.record]: land_color = 0.390103, 0.144375, 0.66, 1; 14:09:53.184 [planetinfo.record]: land_fraction = 0.550000; 14:09:53.184 [planetinfo.record]: polar_cloud_color = 0.781381, 0.79707, 0.750367, 1; 14:09:53.184 [planetinfo.record]: polar_land_color = 0.838514, 0.751578, 0.934, 1; 14:09:53.184 [planetinfo.record]: polar_sea_color = 0.923058, 0.932813, 0.87606, 1; 14:09:53.184 [planetinfo.record]: sea_color = 0.643772, 0.671875, 0.508368, 1; 14:09:53.184 [planetinfo.record]: rotation_speed = 0.003908 14:09:53.184 [planetinfo.record]: planet zpos = 641420.000000 14:09:53.184 [planetinfo.record]: sun_radius = 152324.150085 14:09:53.184 [planetinfo.record]: sun_vector = 0.051 0.927 0.371 14:09:53.184 [planetinfo.record]: sun_distance = 937460 14:09:53.184 [planetinfo.record]: corona_flare = 0.045297 14:09:53.184 [planetinfo.record]: corona_hues = 0.744019 14:09:53.184 [planetinfo.record]: sun_color = 0.312848, 0.378332, 0.747623, 1 14:09:53.184 [planetinfo.record]: corona_shimmer = 0.481344 14:09:53.184 [planetinfo.record]: station_vector = 0.962 0.088 0.258 14:09:53.184 [planetinfo.record]: station = icosahedron 14:09:58.814 [PLANETINFO OVER]: Done 14:09:58.814 [PLANETINFO LOGGING]: 6 24 14:09:59.818 [planetinfo.record]: seed = 50 120 148 87 14:09:59.818 [planetinfo.record]: coordinates = 120 223 14:09:59.819 [planetinfo.record]: government = 6; 14:09:59.819 [planetinfo.record]: economy = 7; 14:09:59.819 [planetinfo.record]: techlevel = 3; 14:09:59.819 [planetinfo.record]: population = 26; 14:09:59.819 [planetinfo.record]: productivity = 6240; 14:09:59.819 [planetinfo.record]: name = "Tiarice"; 14:09:59.819 [planetinfo.record]: inhabitant = "Yellow Lizard"; 14:09:59.819 [planetinfo.record]: inhabitants = "Yellow Lizards"; 14:09:59.819 [planetinfo.record]: description = "This world is very notable for its inhabitants’ weird shyness and its inhabitants’ ingrained shyness."; 14:09:59.827 [planetinfo.record]: air_color = 0.602316, 0.911197, 0.838547, 1; 14:09:59.827 [planetinfo.record]: cloud_alpha = 1.000000; 14:09:59.827 [planetinfo.record]: cloud_color = 0.736328, 0.522337, 0.428566, 1; 14:09:59.827 [planetinfo.record]: cloud_fraction = 0.250000; 14:09:59.827 [planetinfo.record]: land_color = 0.533672, 0.66, 0.642235, 1; 14:09:59.827 [planetinfo.record]: land_fraction = 0.390000; 14:09:59.827 [planetinfo.record]: polar_cloud_color = 0.831348, 0.680344, 0.614174, 1; 14:09:59.827 [planetinfo.record]: polar_land_color = 0.889307, 0.934, 0.927715, 1; 14:09:59.827 [planetinfo.record]: polar_sea_color = 0.898536, 0.93457, 0.88702, 1; 14:09:59.827 [planetinfo.record]: sea_color = 0.553387, 0.654297, 0.521137, 1; 14:09:59.827 [planetinfo.record]: rotation_speed = 0.004355 14:09:59.827 [planetinfo.record]: planet zpos = 567360.000000 14:09:59.827 [planetinfo.record]: sun_radius = 123103.597412 14:09:59.827 [planetinfo.record]: sun_vector = 0.384 0.898 -0.216 14:09:59.827 [planetinfo.record]: sun_distance = 851040 14:09:59.827 [planetinfo.record]: corona_flare = 0.085245 14:09:59.828 [planetinfo.record]: corona_hues = 0.721985 14:09:59.828 [planetinfo.record]: sun_color = 0.214011, 0.720576, 0.829172, 1 14:09:59.828 [planetinfo.record]: corona_shimmer = 0.854758 14:09:59.828 [planetinfo.record]: station_vector = 0.525 -0.851 0.027 14:09:59.828 [planetinfo.record]: station = coriolis 14:10:05.159 [PLANETINFO OVER]: Done 14:10:05.160 [PLANETINFO LOGGING]: 6 25 14:10:06.163 [planetinfo.record]: seed = 138 133 128 179 14:10:06.163 [planetinfo.record]: coordinates = 133 126 14:10:06.163 [planetinfo.record]: government = 1; 14:10:06.163 [planetinfo.record]: economy = 6; 14:10:06.163 [planetinfo.record]: techlevel = 3; 14:10:06.163 [planetinfo.record]: population = 20; 14:10:06.163 [planetinfo.record]: productivity = 3200; 14:10:06.163 [planetinfo.record]: name = "Betierte"; 14:10:06.163 [planetinfo.record]: inhabitant = "Harmless Bony Feline"; 14:10:06.163 [planetinfo.record]: inhabitants = "Harmless Bony Felines"; 14:10:06.163 [planetinfo.record]: description = "The planet Betierte is famous for Betierteian shrew cutlet but plagued by frequent earthquakes."; 14:10:06.165 [planetinfo.record]: air_color = 0.659923, 0.875605, 0.910547, 1; 14:10:06.165 [planetinfo.record]: cloud_alpha = 1.000000; 14:10:06.165 [planetinfo.record]: cloud_color = 0.662883, 0.734375, 0.567993, 1; 14:10:06.165 [planetinfo.record]: cloud_fraction = 0.060000; 14:10:06.165 [planetinfo.record]: land_color = 0.301178, 0.344625, 0.542969, 1; 14:10:06.165 [planetinfo.record]: land_fraction = 0.320000; 14:10:06.165 [planetinfo.record]: polar_cloud_color = 0.779939, 0.830469, 0.712873, 1; 14:10:06.165 [planetinfo.record]: polar_land_color = 0.84042, 0.859338, 0.945703, 1; 14:10:06.165 [planetinfo.record]: polar_sea_color = 0.884687, 0.93125, 0.895237, 1; 14:10:06.165 [planetinfo.record]: sea_color = 0.55, 0.6875, 0.581152, 1; 14:10:06.165 [planetinfo.record]: rotation_speed = 0.000492 14:10:06.166 [planetinfo.record]: planet zpos = 557550.000000 14:10:06.166 [planetinfo.record]: sun_radius = 81229.404144 14:10:06.166 [planetinfo.record]: sun_vector = -0.619 0.446 -0.646 14:10:06.166 [planetinfo.record]: sun_distance = 817740 14:10:06.166 [planetinfo.record]: corona_flare = 0.025366 14:10:06.166 [planetinfo.record]: corona_hues = 0.849968 14:10:06.166 [planetinfo.record]: sun_color = 0.287562, 0.300051, 0.729343, 1 14:10:06.166 [planetinfo.record]: corona_shimmer = 0.256609 14:10:06.166 [planetinfo.record]: station_vector = 0.789 -0.498 0.361 14:10:06.166 [planetinfo.record]: station = coriolis 14:10:11.288 [PLANETINFO OVER]: Done 14:10:11.290 [PLANETINFO LOGGING]: 6 26 14:10:12.311 [planetinfo.record]: seed = 90 92 52 5 14:10:12.311 [planetinfo.record]: coordinates = 92 240 14:10:12.311 [planetinfo.record]: government = 3; 14:10:12.311 [planetinfo.record]: economy = 0; 14:10:12.311 [planetinfo.record]: techlevel = 9; 14:10:12.311 [planetinfo.record]: population = 40; 14:10:12.311 [planetinfo.record]: productivity = 22400; 14:10:12.311 [planetinfo.record]: name = "Ceenrama"; 14:10:12.311 [planetinfo.record]: inhabitant = "Human Colonial"; 14:10:12.311 [planetinfo.record]: inhabitants = "Human Colonials"; 14:10:12.311 [planetinfo.record]: description = "The planet Ceenrama is very noted for its exciting Zero-G cricket but cursed by deadly civil war."; 14:10:12.343 [planetinfo.record]: air_color = 0.617858, 0.888346, 0.953473, 1; 14:10:12.343 [planetinfo.record]: cloud_alpha = 1.000000; 14:10:12.343 [planetinfo.record]: cloud_color = 0.629019, 0.863281, 0.468735, 1; 14:10:12.343 [planetinfo.record]: cloud_fraction = 0.280000; 14:10:12.343 [planetinfo.record]: land_color = 0.471797, 0.57178, 0.66, 1; 14:10:12.343 [planetinfo.record]: land_fraction = 0.250000; 14:10:12.343 [planetinfo.record]: polar_cloud_color = 0.73779, 0.888477, 0.634688, 1; 14:10:12.344 [planetinfo.record]: polar_land_color = 0.867416, 0.902789, 0.934, 1; 14:10:12.344 [planetinfo.record]: polar_sea_color = 0.941016, 0.937972, 0.892311, 1; 14:10:12.344 [planetinfo.record]: sea_color = 0.589844, 0.582211, 0.467728, 1; 14:10:12.344 [planetinfo.record]: rotation_speed = 0.004625 14:10:12.344 [planetinfo.record]: planet zpos = 460680.000000 14:10:12.344 [planetinfo.record]: sun_radius = 95327.228394 14:10:12.344 [planetinfo.record]: sun_vector = 0.696 0.414 0.587 14:10:12.344 [planetinfo.record]: sun_distance = 837600 14:10:12.344 [planetinfo.record]: corona_flare = 0.011435 14:10:12.344 [planetinfo.record]: corona_hues = 0.803230 14:10:12.344 [planetinfo.record]: sun_color = 0.525241, 0.553379, 0.752603, 1 14:10:12.344 [planetinfo.record]: corona_shimmer = 0.329973 14:10:12.344 [planetinfo.record]: station_vector = -0.973 -0.068 0.221 14:10:12.345 [planetinfo.record]: station = coriolis 14:10:16.794 [PLANETINFO OVER]: Done 14:10:16.795 [PLANETINFO LOGGING]: 6 27 14:10:17.797 [planetinfo.record]: seed = 194 11 80 18 14:10:17.797 [planetinfo.record]: coordinates = 11 180 14:10:17.797 [planetinfo.record]: government = 0; 14:10:17.797 [planetinfo.record]: economy = 6; 14:10:17.797 [planetinfo.record]: techlevel = 4; 14:10:17.798 [planetinfo.record]: population = 23; 14:10:17.798 [planetinfo.record]: productivity = 2944; 14:10:17.798 [planetinfo.record]: name = "Enener"; 14:10:17.798 [planetinfo.record]: inhabitant = "Human Colonial"; 14:10:17.798 [planetinfo.record]: inhabitants = "Human Colonials"; 14:10:17.798 [planetinfo.record]: description = "Enener is mildly well known for killer Xeones gargle blasters."; 14:10:17.803 [planetinfo.record]: air_color = 0.438766, 0.501295, 0.931359, 1; 14:10:17.803 [planetinfo.record]: cloud_alpha = 1.000000; 14:10:17.803 [planetinfo.record]: cloud_color = 0.00933838, 0.298512, 0.796875, 1; 14:10:17.804 [planetinfo.record]: cloud_fraction = 0.410000; 14:10:17.804 [planetinfo.record]: land_color = 0.167578, 0.336848, 0.66, 1; 14:10:17.804 [planetinfo.record]: land_fraction = 0.530000; 14:10:17.804 [planetinfo.record]: polar_cloud_color = 0.328261, 0.522993, 0.858594, 1; 14:10:17.804 [planetinfo.record]: polar_land_color = 0.759787, 0.819673, 0.934, 1; 14:10:17.804 [planetinfo.record]: polar_sea_color = 0.88987, 0.933398, 0.886182, 1; 14:10:17.804 [planetinfo.record]: sea_color = 0.54178, 0.666016, 0.531251, 1; 14:10:17.804 [planetinfo.record]: rotation_speed = 0.001962 14:10:17.804 [planetinfo.record]: planet zpos = 367290.000000 14:10:17.804 [planetinfo.record]: sun_radius = 113895.177155 14:10:17.804 [planetinfo.record]: sun_vector = 0.217 0.974 -0.069 14:10:17.804 [planetinfo.record]: sun_distance = 801360 14:10:17.804 [planetinfo.record]: corona_flare = 0.055510 14:10:17.804 [planetinfo.record]: corona_hues = 0.828156 14:10:17.804 [planetinfo.record]: sun_color = 0.718436, 0.475559, 0.21003, 1 14:10:17.805 [planetinfo.record]: corona_shimmer = 0.749600 14:10:17.805 [planetinfo.record]: station_vector = 0.054 0.998 0.036 14:10:17.805 [planetinfo.record]: station = coriolis 14:10:22.176 [PLANETINFO OVER]: Done 14:10:22.177 [PLANETINFO LOGGING]: 6 28 14:10:23.180 [planetinfo.record]: seed = 162 212 244 150 14:10:23.180 [planetinfo.record]: coordinates = 212 240 14:10:23.180 [planetinfo.record]: government = 4; 14:10:23.180 [planetinfo.record]: economy = 0; 14:10:23.180 [planetinfo.record]: techlevel = 9; 14:10:23.180 [planetinfo.record]: population = 41; 14:10:23.180 [planetinfo.record]: productivity = 26240; 14:10:23.180 [planetinfo.record]: name = "Veanso"; 14:10:23.180 [planetinfo.record]: inhabitant = "Black Fat Feline"; 14:10:23.180 [planetinfo.record]: inhabitants = "Black Fat Felines"; 14:10:23.180 [planetinfo.record]: description = "The world Veanso is reasonably noted for its inhabitants’ eccentric shyness and its inhabitants’ ingrained silliness."; 14:10:23.201 [planetinfo.record]: air_color = 0.5135, 0.597529, 0.857865, 1; 14:10:23.201 [planetinfo.record]: cloud_alpha = 1.000000; 14:10:23.201 [planetinfo.record]: cloud_color = 0.227318, 0.478057, 0.576172, 1; 14:10:23.201 [planetinfo.record]: cloud_fraction = 0.270000; 14:10:23.201 [planetinfo.record]: land_color = 0.0360938, 0.279807, 0.66, 1; 14:10:23.201 [planetinfo.record]: land_fraction = 0.350000; 14:10:23.201 [planetinfo.record]: polar_cloud_color = 0.471953, 0.678467, 0.759277, 1; 14:10:23.201 [planetinfo.record]: polar_land_color = 0.71327, 0.799492, 0.934, 1; 14:10:23.201 [planetinfo.record]: polar_sea_color = 0.941406, 0.931341, 0.891854, 1; 14:10:23.201 [planetinfo.record]: sea_color = 0.585938, 0.560879, 0.46257, 1; 14:10:23.201 [planetinfo.record]: rotation_speed = 0.000023 14:10:23.201 [planetinfo.record]: planet zpos = 593320.000000 14:10:23.201 [planetinfo.record]: sun_radius = 116940.661011 14:10:23.201 [planetinfo.record]: sun_vector = 0.946 0.170 -0.275 14:10:23.201 [planetinfo.record]: sun_distance = 958440 14:10:23.201 [planetinfo.record]: corona_flare = 0.072847 14:10:23.202 [planetinfo.record]: corona_hues = 0.733482 14:10:23.202 [planetinfo.record]: sun_color = 0.66473, 0.668451, 0.742572, 1 14:10:23.202 [planetinfo.record]: corona_shimmer = 0.421285 14:10:23.202 [planetinfo.record]: station_vector = 0.889 -0.227 0.399 14:10:23.202 [planetinfo.record]: station = coriolis 14:10:27.459 [PLANETINFO OVER]: Done 14:10:27.460 [PLANETINFO LOGGING]: 6 29 14:10:28.474 [planetinfo.record]: seed = 26 186 64 221 14:10:28.474 [planetinfo.record]: coordinates = 186 199 14:10:28.474 [planetinfo.record]: government = 3; 14:10:28.474 [planetinfo.record]: economy = 7; 14:10:28.474 [planetinfo.record]: techlevel = 4; 14:10:28.474 [planetinfo.record]: population = 27; 14:10:28.474 [planetinfo.record]: productivity = 4536; 14:10:28.474 [planetinfo.record]: name = "Isriveen"; 14:10:28.474 [planetinfo.record]: inhabitant = "Human Colonial"; 14:10:28.474 [planetinfo.record]: inhabitants = "Human Colonials"; 14:10:28.474 [planetinfo.record]: description = "The world Isriveen is notable for its unusual oceans and its inhabitants’ eccentric love for tourists."; 14:10:28.492 [planetinfo.record]: air_color = 0.650408, 0.86632, 0.815537, 1; 14:10:28.492 [planetinfo.record]: cloud_alpha = 1.000000; 14:10:28.492 [planetinfo.record]: cloud_color = 0.601562, 0.539475, 0.512268, 1; 14:10:28.492 [planetinfo.record]: cloud_fraction = 0.490000; 14:10:28.492 [planetinfo.record]: land_color = 0.518203, 0.66, 0.570269, 1; 14:10:28.492 [planetinfo.record]: land_fraction = 0.310000; 14:10:28.492 [planetinfo.record]: polar_cloud_color = 0.770703, 0.720988, 0.699202, 1; 14:10:28.492 [planetinfo.record]: polar_land_color = 0.883834, 0.934, 0.902254, 1; 14:10:28.492 [planetinfo.record]: polar_sea_color = 0.894806, 0.925391, 0.87388, 1; 14:10:28.492 [planetinfo.record]: sea_color = 0.647459, 0.746094, 0.579971, 1; 14:10:28.492 [planetinfo.record]: rotation_speed = 0.003645 14:10:28.492 [planetinfo.record]: planet zpos = 759600.000000 14:10:28.492 [planetinfo.record]: sun_radius = 193589.666748 14:10:28.492 [planetinfo.record]: sun_vector = -0.281 -0.218 -0.935 14:10:28.492 [planetinfo.record]: sun_distance = 1202700 14:10:28.493 [planetinfo.record]: corona_flare = 0.066272 14:10:28.493 [planetinfo.record]: corona_hues = 0.611252 14:10:28.493 [planetinfo.record]: sun_color = 0.687042, 0.508412, 0.467338, 1 14:10:28.493 [planetinfo.record]: corona_shimmer = 0.274042 14:10:28.493 [planetinfo.record]: station_vector = 0.656 -0.753 0.058 14:10:28.493 [planetinfo.record]: station = coriolis 14:10:33.973 [PLANETINFO OVER]: Done 14:10:33.974 [PLANETINFO LOGGING]: 6 30 14:10:34.993 [planetinfo.record]: seed = 10 50 212 134 14:10:34.993 [planetinfo.record]: coordinates = 50 246 14:10:34.993 [planetinfo.record]: government = 1; 14:10:34.993 [planetinfo.record]: economy = 6; 14:10:34.994 [planetinfo.record]: techlevel = 4; 14:10:34.994 [planetinfo.record]: population = 24; 14:10:34.994 [planetinfo.record]: productivity = 3840; 14:10:34.994 [planetinfo.record]: name = "Bireso"; 14:10:34.994 [planetinfo.record]: inhabitant = "Fierce Black Fat Feline"; 14:10:34.994 [planetinfo.record]: inhabitants = "Fierce Black Fat Felines"; 14:10:34.994 [planetinfo.record]: description = "This planet is a tedious place."; 14:10:35.005 [planetinfo.record]: air_color = 0.630926, 0.913148, 0.904037, 1; 14:10:35.005 [planetinfo.record]: cloud_alpha = 1.000000; 14:10:35.005 [planetinfo.record]: cloud_color = 0.742188, 0.721259, 0.498657, 1; 14:10:35.006 [planetinfo.record]: cloud_fraction = 0.490000; 14:10:35.006 [planetinfo.record]: land_color = 0.636173, 0.66, 0.626484, 1; 14:10:35.006 [planetinfo.record]: land_fraction = 0.640000; 14:10:35.006 [planetinfo.record]: polar_cloud_color = 0.833984, 0.819286, 0.662952, 1; 14:10:35.006 [planetinfo.record]: polar_land_color = 0.92557, 0.934, 0.922143, 1; 14:10:35.006 [planetinfo.record]: polar_sea_color = 0.772646, 0.944141, 0.827578, 1; 14:10:35.006 [planetinfo.record]: sea_color = 0.15274, 0.558594, 0.28274, 1; 14:10:35.006 [planetinfo.record]: rotation_speed = 0.000303 14:10:35.006 [planetinfo.record]: planet zpos = 528240.000000 14:10:35.006 [planetinfo.record]: sun_radius = 88881.629944 14:10:35.006 [planetinfo.record]: sun_vector = -0.198 -0.823 0.532 14:10:35.006 [planetinfo.record]: sun_distance = 924420 14:10:35.006 [planetinfo.record]: corona_flare = 0.044102 14:10:35.006 [planetinfo.record]: corona_hues = 0.594780 14:10:35.007 [planetinfo.record]: sun_color = 0.752252, 0.697116, 0.41284, 1 14:10:35.007 [planetinfo.record]: corona_shimmer = 0.293876 14:10:35.007 [planetinfo.record]: station_vector = 0.376 -0.642 0.668 14:10:35.007 [planetinfo.record]: station = coriolis 14:10:40.583 [PLANETINFO OVER]: Done 14:10:40.584 [PLANETINFO LOGGING]: 6 31 14:10:41.605 [planetinfo.record]: seed = 146 157 80 180 14:10:41.605 [planetinfo.record]: coordinates = 157 103 14:10:41.605 [planetinfo.record]: government = 2; 14:10:41.605 [planetinfo.record]: economy = 7; 14:10:41.605 [planetinfo.record]: techlevel = 2; 14:10:41.605 [planetinfo.record]: population = 18; 14:10:41.605 [planetinfo.record]: productivity = 2592; 14:10:41.605 [planetinfo.record]: name = "Raormaor"; 14:10:41.605 [planetinfo.record]: inhabitant = "Human Colonial"; 14:10:41.605 [planetinfo.record]: inhabitants = "Human Colonials"; 14:10:41.605 [planetinfo.record]: description = "The planet Raormaor is a boring planet."; 14:10:41.627 [planetinfo.record]: air_color = 0.713214, 0.624317, 0.840305, 1; 14:10:41.627 [planetinfo.record]: cloud_alpha = 1.000000; 14:10:41.627 [planetinfo.record]: cloud_color = 0.523438, 0.433472, 0.500946, 1; 14:10:41.627 [planetinfo.record]: cloud_fraction = 0.610000; 14:10:41.627 [planetinfo.record]: land_color = 0.298848, 0.280884, 0.609375, 1; 14:10:41.627 [planetinfo.record]: land_fraction = 0.570000; 14:10:41.627 [planetinfo.record]: polar_cloud_color = 0.735547, 0.656533, 0.715793, 1; 14:10:41.627 [planetinfo.record]: polar_land_color = 0.81943, 0.812509, 0.939062, 1; 14:10:41.627 [planetinfo.record]: polar_sea_color = 0.924219, 0.850167, 0.817717, 1; 14:10:41.627 [planetinfo.record]: sea_color = 0.757812, 0.514937, 0.408508, 1; 14:10:41.627 [planetinfo.record]: rotation_speed = 0.000184 14:10:41.628 [planetinfo.record]: planet zpos = 479640.000000 14:10:41.628 [planetinfo.record]: sun_radius = 75483.506012 14:10:41.628 [planetinfo.record]: sun_vector = 0.996 0.083 0.042 14:10:41.628 [planetinfo.record]: sun_distance = 719460 14:10:41.629 [planetinfo.record]: corona_flare = 0.096436 14:10:41.629 [planetinfo.record]: corona_hues = 0.698532 14:10:41.629 [planetinfo.record]: sun_color = 0.683857, 0.718394, 0.804828, 1 14:10:41.629 [planetinfo.record]: corona_shimmer = 0.301556 14:10:41.629 [planetinfo.record]: station_vector = 0.334 0.832 0.443 14:10:41.629 [planetinfo.record]: station = coriolis 14:10:46.557 [PLANETINFO OVER]: Done 14:10:46.576 [PLANETINFO LOGGING]: 6 32 14:10:47.562 [planetinfo.record]: seed = 146 121 212 62 14:10:47.562 [planetinfo.record]: coordinates = 121 11 14:10:47.562 [planetinfo.record]: government = 2; 14:10:47.562 [planetinfo.record]: economy = 3; 14:10:47.562 [planetinfo.record]: techlevel = 6; 14:10:47.562 [planetinfo.record]: population = 30; 14:10:47.562 [planetinfo.record]: productivity = 10080; 14:10:47.562 [planetinfo.record]: name = "Rigete"; 14:10:47.562 [planetinfo.record]: inhabitant = "Red Horned Bird"; 14:10:47.562 [planetinfo.record]: inhabitants = "Red Horned Birds"; 14:10:47.562 [planetinfo.record]: description = "This planet is fabled for its ancient Rigeteian On banana plantations."; 14:10:47.566 [planetinfo.record]: air_color = 0.758006, 0.58924, 0.894938, 1; 14:10:47.566 [planetinfo.record]: cloud_alpha = 1.000000; 14:10:47.566 [planetinfo.record]: cloud_color = 0.6875, 0.394775, 0.490826, 1; 14:10:47.566 [planetinfo.record]: cloud_fraction = 0.450000; 14:10:47.566 [planetinfo.record]: land_color = 0.649526, 0.634219, 0.66, 1; 14:10:47.567 [planetinfo.record]: land_fraction = 0.630000; 14:10:47.567 [planetinfo.record]: polar_cloud_color = 0.809375, 0.59399, 0.664663, 1; 14:10:47.567 [planetinfo.record]: polar_land_color = 0.930295, 0.924879, 0.934, 1; 14:10:47.567 [planetinfo.record]: polar_sea_color = 0.839294, 0.946875, 0.75824, 1; 14:10:47.567 [planetinfo.record]: sea_color = 0.289814, 0.53125, 0.10791, 1; 14:10:47.567 [planetinfo.record]: rotation_speed = 0.000593 14:10:47.567 [planetinfo.record]: planet zpos = 847730.000000 14:10:47.567 [planetinfo.record]: sun_radius = 211685.733643 14:10:47.567 [planetinfo.record]: sun_vector = -0.197 -0.738 0.646 14:10:47.567 [planetinfo.record]: sun_distance = 1238990 14:10:47.568 [planetinfo.record]: corona_flare = 0.005794 14:10:47.568 [planetinfo.record]: corona_hues = 0.654282 14:10:47.568 [planetinfo.record]: sun_color = 0.206757, 0.26516, 0.719092, 1 14:10:47.568 [planetinfo.record]: corona_shimmer = 0.528329 14:10:47.568 [planetinfo.record]: station_vector = 0.219 0.286 0.933 14:10:47.568 [planetinfo.record]: station = coriolis 14:10:52.215 [PLANETINFO OVER]: Done 14:10:52.215 [PLANETINFO LOGGING]: 6 33 14:10:53.219 [planetinfo.record]: seed = 42 127 128 191 14:10:53.219 [planetinfo.record]: coordinates = 127 124 14:10:53.219 [planetinfo.record]: government = 5; 14:10:53.219 [planetinfo.record]: economy = 4; 14:10:53.219 [planetinfo.record]: techlevel = 9; 14:10:53.219 [planetinfo.record]: population = 46; 14:10:53.219 [planetinfo.record]: productivity = 19872; 14:10:53.219 [planetinfo.record]: name = "Onanorra"; 14:10:53.219 [planetinfo.record]: inhabitant = "Harmless Bony Feline"; 14:10:53.219 [planetinfo.record]: inhabitants = "Harmless Bony Felines"; 14:10:53.219 [planetinfo.record]: description = "The planet Onanorra is famous for its fabulous monkey burgers but plagued by unpredictable civil war."; 14:10:53.236 [planetinfo.record]: air_color = 0.68982, 0.575364, 0.88258, 1; 14:10:53.236 [planetinfo.record]: cloud_alpha = 1.000000; 14:10:53.236 [planetinfo.record]: cloud_color = 0.650391, 0.360764, 0.611925, 1; 14:10:53.236 [planetinfo.record]: cloud_fraction = 0.210000; 14:10:53.236 [planetinfo.record]: land_color = 0.0845642, 0.636719, 0.326132, 1; 14:10:53.236 [planetinfo.record]: land_fraction = 0.380000; 14:10:53.236 [planetinfo.record]: polar_cloud_color = 0.792676, 0.572058, 0.763375, 1; 14:10:53.236 [planetinfo.record]: polar_land_color = 0.733335, 0.936328, 0.822145, 1; 14:10:53.236 [planetinfo.record]: polar_sea_color = 0.876649, 0.928516, 0.882322, 1; 14:10:53.236 [planetinfo.record]: sea_color = 0.555121, 0.714844, 0.572591, 1; 14:10:53.236 [planetinfo.record]: rotation_speed = 0.001747 14:10:53.236 [planetinfo.record]: planet zpos = 610470.000000 14:10:53.236 [planetinfo.record]: sun_radius = 173267.893066 14:10:53.236 [planetinfo.record]: sun_vector = -0.866 -0.290 -0.407 14:10:53.236 [planetinfo.record]: sun_distance = 1220940 14:10:53.236 [planetinfo.record]: corona_flare = 0.067529 14:10:53.236 [planetinfo.record]: corona_hues = 0.906433 14:10:53.236 [planetinfo.record]: sun_color = 0.78204, 0.591379, 0.528145, 1 14:10:53.237 [planetinfo.record]: corona_shimmer = 0.944949 14:10:53.237 [planetinfo.record]: station_vector = -0.469 -0.429 0.772 14:10:53.237 [planetinfo.record]: station = coriolis 14:10:57.874 [PLANETINFO OVER]: Done 14:10:57.875 [PLANETINFO LOGGING]: 6 34 14:10:58.878 [planetinfo.record]: seed = 58 116 244 40 14:10:58.878 [planetinfo.record]: coordinates = 116 249 14:10:58.878 [planetinfo.record]: government = 7; 14:10:58.878 [planetinfo.record]: economy = 1; 14:10:58.878 [planetinfo.record]: techlevel = 10; 14:10:58.878 [planetinfo.record]: population = 49; 14:10:58.878 [planetinfo.record]: productivity = 38808; 14:10:58.878 [planetinfo.record]: name = "Usvera"; 14:10:58.878 [planetinfo.record]: inhabitant = "Small Red Furry Humanoid"; 14:10:58.878 [planetinfo.record]: inhabitants = "Small Red Furry Humanoids"; 14:10:58.878 [planetinfo.record]: description = "This planet is reasonably famous for its inhabitants’ exceptional loathing of casinos."; 14:10:58.903 [planetinfo.record]: air_color = 0.463179, 0.591926, 0.894287, 1; 14:10:58.903 [planetinfo.record]: cloud_alpha = 1.000000; 14:10:58.904 [planetinfo.record]: cloud_color = 0.104439, 0.617448, 0.685547, 1; 14:10:58.904 [planetinfo.record]: cloud_fraction = 0.570000; 14:10:58.904 [planetinfo.record]: land_color = 0.66, 0.546321, 0.446016, 1; 14:10:58.904 [planetinfo.record]: land_fraction = 0.370000; 14:10:58.904 [planetinfo.record]: polar_cloud_color = 0.380167, 0.758301, 0.808496, 1; 14:10:58.904 [planetinfo.record]: polar_land_color = 0.934, 0.893782, 0.858295, 1; 14:10:58.904 [planetinfo.record]: polar_sea_color = 0.87236, 0.852911, 0.907031, 1; 14:10:58.904 [planetinfo.record]: sea_color = 0.787539, 0.707797, 0.929688, 1; 14:10:58.904 [planetinfo.record]: rotation_speed = 0.000895 14:10:58.904 [planetinfo.record]: planet zpos = 647400.000000 14:10:58.904 [planetinfo.record]: sun_radius = 117483.197021 14:10:58.904 [planetinfo.record]: sun_vector = -0.852 0.406 -0.331 14:10:58.904 [planetinfo.record]: sun_distance = 896400 14:10:58.904 [planetinfo.record]: corona_flare = 0.070573 14:10:58.904 [planetinfo.record]: corona_hues = 0.538216 14:10:58.904 [planetinfo.record]: sun_color = 0.441366, 0.46423, 0.659647, 1 14:10:58.905 [planetinfo.record]: corona_shimmer = 0.315912 14:10:58.905 [planetinfo.record]: station_vector = 0.326 -0.655 0.682 14:10:58.905 [planetinfo.record]: station = coriolis 14:11:03.365 [PLANETINFO OVER]: Done 14:11:03.366 [PLANETINFO LOGGING]: 6 35 14:11:04.369 [planetinfo.record]: seed = 226 243 208 190 14:11:04.369 [planetinfo.record]: coordinates = 243 52 14:11:04.369 [planetinfo.record]: government = 4; 14:11:04.369 [planetinfo.record]: economy = 4; 14:11:04.369 [planetinfo.record]: techlevel = 8; 14:11:04.369 [planetinfo.record]: population = 41; 14:11:04.369 [planetinfo.record]: productivity = 15744; 14:11:04.370 [planetinfo.record]: name = "Ribior"; 14:11:04.370 [planetinfo.record]: inhabitant = "Harmless Frog"; 14:11:04.370 [planetinfo.record]: inhabitants = "Harmless Frogs"; 14:11:04.370 [planetinfo.record]: description = "This planet is mildly noted for the Ribiorian mountain slug but cursed by occasional earthquakes."; 14:11:04.384 [planetinfo.record]: air_color = 0.707072, 0.574661, 0.890385, 1; 14:11:04.384 [planetinfo.record]: cloud_alpha = 1.000000; 14:11:04.384 [planetinfo.record]: cloud_color = 0.673828, 0.360603, 0.588181, 1; 14:11:04.384 [planetinfo.record]: cloud_fraction = 0.140000; 14:11:04.384 [planetinfo.record]: land_color = 0.616172, 0.626102, 0.66, 1; 14:11:04.384 [planetinfo.record]: land_fraction = 0.580000; 14:11:04.384 [planetinfo.record]: polar_cloud_color = 0.803223, 0.569865, 0.739414, 1; 14:11:04.384 [planetinfo.record]: polar_land_color = 0.918494, 0.922007, 0.934, 1; 14:11:04.385 [planetinfo.record]: polar_sea_color = 0.936328, 0.935477, 0.881831, 1; 14:11:04.385 [planetinfo.record]: sea_color = 0.636719, 0.634403, 0.488483, 1; 14:11:04.385 [planetinfo.record]: rotation_speed = 0.003327 14:11:04.385 [planetinfo.record]: planet zpos = 797160.000000 14:11:04.385 [planetinfo.record]: sun_radius = 133824.986572 14:11:04.385 [planetinfo.record]: sun_vector = -0.204 -0.494 0.846 14:11:04.385 [planetinfo.record]: sun_distance = 1461460 14:11:04.385 [planetinfo.record]: corona_flare = 0.082930 14:11:04.385 [planetinfo.record]: corona_hues = 0.528122 14:11:04.385 [planetinfo.record]: sun_color = 0.820258, 0.743266, 0.729193, 1 14:11:04.385 [planetinfo.record]: corona_shimmer = 0.257244 14:11:04.385 [planetinfo.record]: station_vector = -0.807 0.141 0.573 14:11:04.385 [planetinfo.record]: station = coriolis 14:11:08.842 [PLANETINFO OVER]: Done 14:11:08.843 [PLANETINFO LOGGING]: 6 36 14:11:09.848 [planetinfo.record]: seed = 2 63 52 191 14:11:09.848 [planetinfo.record]: coordinates = 63 153 14:11:09.848 [planetinfo.record]: government = 0; 14:11:09.848 [planetinfo.record]: economy = 3; 14:11:09.848 [planetinfo.record]: techlevel = 7; 14:11:09.848 [planetinfo.record]: population = 32; 14:11:09.848 [planetinfo.record]: productivity = 7168; 14:11:09.848 [planetinfo.record]: name = "Ontilain"; 14:11:09.848 [planetinfo.record]: inhabitant = "Human Colonial"; 14:11:09.848 [planetinfo.record]: inhabitants = "Human Colonials"; 14:11:09.848 [planetinfo.record]: description = "This world is mildly well known for killer A’a’arbi gargle blasters and its ancient Ontilainian A’a’arbi banana plantations."; 14:11:09.851 [planetinfo.record]: air_color = 0.711699, 0.77261, 0.946318, 1; 14:11:09.851 [planetinfo.record]: cloud_alpha = 1.000000; 14:11:09.851 [planetinfo.record]: cloud_color = 0.720131, 0.813281, 0.841797, 1; 14:11:09.852 [planetinfo.record]: cloud_fraction = 0.420000; 14:11:09.852 [planetinfo.record]: land_color = 0.437832, 0.587891, 0.0229645, 1; 14:11:09.852 [planetinfo.record]: land_fraction = 0.380000; 14:11:09.852 [planetinfo.record]: polar_cloud_color = 0.799424, 0.860203, 0.878809, 1; 14:11:09.852 [planetinfo.record]: polar_land_color = 0.88115, 0.941211, 0.7151, 1; 14:11:09.852 [planetinfo.record]: polar_sea_color = 0.926953, 0.915355, 0.852724, 1; 14:11:09.852 [planetinfo.record]: sea_color = 0.730469, 0.69391, 0.49649, 1; 14:11:09.852 [planetinfo.record]: rotation_speed = 0.001269 14:11:09.852 [planetinfo.record]: planet zpos = 1007850.000000 14:11:09.853 [planetinfo.record]: sun_radius = 218433.115234 14:11:09.853 [planetinfo.record]: sun_vector = 0.286 -0.954 0.094 14:11:09.853 [planetinfo.record]: sun_distance = 1142230 14:11:09.853 [planetinfo.record]: corona_flare = 0.035580 14:11:09.853 [planetinfo.record]: corona_hues = 0.900108 14:11:09.853 [planetinfo.record]: sun_color = 0.782388, 0.727756, 0.633739, 1 14:11:09.853 [planetinfo.record]: corona_shimmer = 1.092485 14:11:09.853 [planetinfo.record]: station_vector = -0.942 0.318 0.112 14:11:09.854 [planetinfo.record]: station = coriolis 14:11:15.078 [PLANETINFO OVER]: Done 14:11:15.079 [PLANETINFO LOGGING]: 6 37 14:11:16.101 [planetinfo.record]: seed = 186 236 64 26 14:11:16.101 [planetinfo.record]: coordinates = 236 149 14:11:16.101 [planetinfo.record]: government = 7; 14:11:16.101 [planetinfo.record]: economy = 5; 14:11:16.101 [planetinfo.record]: techlevel = 6; 14:11:16.101 [planetinfo.record]: population = 37; 14:11:16.101 [planetinfo.record]: productivity = 16280; 14:11:16.101 [planetinfo.record]: name = "Qutegequ"; 14:11:16.101 [planetinfo.record]: inhabitant = "Human Colonial"; 14:11:16.101 [planetinfo.record]: inhabitants = "Human Colonials"; 14:11:16.101 [planetinfo.record]: description = "Qutegequ is a revolting dump."; 14:11:16.116 [planetinfo.record]: air_color = 0.505696, 0.996398, 0.9346, 1; 14:11:16.116 [planetinfo.record]: cloud_alpha = 1.000000; 14:11:16.116 [planetinfo.record]: cloud_color = 0.992188, 0.671985, 0.120148, 1; 14:11:16.116 [planetinfo.record]: cloud_fraction = 0.270000; 14:11:16.116 [planetinfo.record]: land_color = 0.0154688, 0.66, 0.0859644, 1; 14:11:16.116 [planetinfo.record]: land_fraction = 0.300000; 14:11:16.116 [planetinfo.record]: polar_cloud_color = 0.946484, 0.755576, 0.426565, 1; 14:11:16.116 [planetinfo.record]: polar_land_color = 0.705973, 0.934, 0.730913, 1; 14:11:16.116 [planetinfo.record]: polar_sea_color = 0.893219, 0.929883, 0.874671, 1; 14:11:16.116 [planetinfo.record]: sea_color = 0.590587, 0.701172, 0.534644, 1; 14:11:16.116 [planetinfo.record]: rotation_speed = 0.004813 14:11:16.117 [planetinfo.record]: planet zpos = 617320.000000 14:11:16.117 [planetinfo.record]: sun_radius = 142865.544434 14:11:16.117 [planetinfo.record]: sun_vector = -0.156 0.554 -0.818 14:11:16.117 [planetinfo.record]: sun_distance = 1346880 14:11:16.117 [planetinfo.record]: corona_flare = 0.004202 14:11:16.117 [planetinfo.record]: corona_hues = 0.539825 14:11:16.117 [planetinfo.record]: sun_color = 0.658333, 0.672586, 0.675598, 1 14:11:16.117 [planetinfo.record]: corona_shimmer = 0.499238 14:11:16.117 [planetinfo.record]: station_vector = 0.866 -0.487 0.112 14:11:16.118 [planetinfo.record]: station = coriolis 14:11:21.430 [PLANETINFO OVER]: Done 14:11:21.430 [PLANETINFO LOGGING]: 6 38 14:11:22.444 [planetinfo.record]: seed = 234 90 148 155 14:11:22.444 [planetinfo.record]: coordinates = 90 163 14:11:22.444 [planetinfo.record]: government = 5; 14:11:22.444 [planetinfo.record]: economy = 3; 14:11:22.444 [planetinfo.record]: techlevel = 9; 14:11:22.444 [planetinfo.record]: population = 45; 14:11:22.444 [planetinfo.record]: productivity = 22680; 14:11:22.444 [planetinfo.record]: name = "Anquerbi"; 14:11:22.444 [planetinfo.record]: inhabitant = "Black Bug-Eyed Bird"; 14:11:22.444 [planetinfo.record]: inhabitants = "Black Bug-Eyed Birds"; 14:11:22.444 [planetinfo.record]: description = "This world is mildly famous for its hoopy night life and its fabulous cuisine."; 14:11:22.452 [planetinfo.record]: air_color = 0.563102, 0.957375, 0.910798, 1; 14:11:22.453 [planetinfo.record]: cloud_alpha = 1.000000; 14:11:22.453 [planetinfo.record]: cloud_color = 0.875, 0.683487, 0.317871, 1; 14:11:22.453 [planetinfo.record]: cloud_fraction = 0.240000; 14:11:22.453 [planetinfo.record]: land_color = 0.630859, 0.474434, 0.47068, 1; 14:11:22.453 [planetinfo.record]: land_fraction = 0.300000; 14:11:22.453 [planetinfo.record]: polar_cloud_color = 0.89375, 0.771489, 0.538083, 1; 14:11:22.453 [planetinfo.record]: polar_land_color = 0.936914, 0.878836, 0.877442, 1; 14:11:22.453 [planetinfo.record]: polar_sea_color = 0.753422, 0.918119, 0.935156, 1; 14:11:22.453 [planetinfo.record]: sea_color = 0.144379, 0.601182, 0.648438, 1; 14:11:22.453 [planetinfo.record]: rotation_speed = 0.001568 14:11:22.453 [planetinfo.record]: planet zpos = 572200.000000 14:11:22.453 [planetinfo.record]: sun_radius = 120436.505127 14:11:22.453 [planetinfo.record]: sun_vector = -0.367 0.375 -0.852 14:11:22.453 [planetinfo.record]: sun_distance = 1144400 14:11:22.453 [planetinfo.record]: corona_flare = 0.058185 14:11:22.454 [planetinfo.record]: corona_hues = 0.820801 14:11:22.454 [planetinfo.record]: sun_color = 0.73739, 0.524641, 0.368262, 1 14:11:22.454 [planetinfo.record]: corona_shimmer = 0.336818 14:11:22.454 [planetinfo.record]: station_vector = 0.708 0.546 0.447 14:11:22.454 [planetinfo.record]: station = coriolis 14:11:27.567 [PLANETINFO OVER]: Done 14:11:27.567 [PLANETINFO LOGGING]: 6 39 14:11:28.576 [planetinfo.record]: seed = 178 198 208 241 14:11:28.576 [planetinfo.record]: coordinates = 198 144 14:11:28.576 [planetinfo.record]: government = 6; 14:11:28.576 [planetinfo.record]: economy = 0; 14:11:28.576 [planetinfo.record]: techlevel = 12; 14:11:28.576 [planetinfo.record]: population = 55; 14:11:28.576 [planetinfo.record]: productivity = 44000; 14:11:28.577 [planetinfo.record]: name = "Ateslete"; 14:11:28.577 [planetinfo.record]: inhabitant = "Insect"; 14:11:28.577 [planetinfo.record]: inhabitants = "Insects"; 14:11:28.577 [planetinfo.record]: description = "This planet is plagued by deadly earthquakes."; 14:11:28.604 [planetinfo.record]: air_color = 0.463262, 0.741611, 0.841605, 1; 14:11:28.604 [planetinfo.record]: cloud_alpha = 1.000000; 14:11:28.604 [planetinfo.record]: cloud_color = 0.209084, 0.527344, 0.131836, 1; 14:11:28.604 [planetinfo.record]: cloud_fraction = 0.300000; 14:11:28.604 [planetinfo.record]: land_color = 0.30434, 0.66, 0.242344, 1; 14:11:28.604 [planetinfo.record]: land_fraction = 0.640000; 14:11:28.604 [planetinfo.record]: polar_cloud_color = 0.459195, 0.737305, 0.391693, 1; 14:11:28.604 [planetinfo.record]: polar_land_color = 0.808172, 0.934, 0.786238, 1; 14:11:28.604 [planetinfo.record]: polar_sea_color = 0.947656, 0.897219, 0.897219, 1; 14:11:28.604 [planetinfo.record]: sea_color = 0.523438, 0.412003, 0.412003, 1; 14:11:28.604 [planetinfo.record]: rotation_speed = 0.001468 14:11:28.604 [planetinfo.record]: planet zpos = 425100.000000 14:11:28.604 [planetinfo.record]: sun_radius = 88027.944946 14:11:28.604 [planetinfo.record]: sun_vector = -0.602 0.430 0.673 14:11:28.604 [planetinfo.record]: sun_distance = 784800 14:11:28.605 [planetinfo.record]: corona_flare = 0.074066 14:11:28.605 [planetinfo.record]: corona_hues = 0.616547 14:11:28.605 [planetinfo.record]: sun_color = 0.770346, 0.678565, 0.488034, 1 14:11:28.605 [planetinfo.record]: corona_shimmer = 0.732983 14:11:28.605 [planetinfo.record]: station_vector = 0.818 0.411 0.403 14:11:28.605 [planetinfo.record]: station = dodecahedron 14:11:33.603 [PLANETINFO OVER]: Done 14:11:33.604 [PLANETINFO LOGGING]: 6 40 14:11:34.607 [planetinfo.record]: seed = 242 60 20 136 14:11:34.607 [planetinfo.record]: coordinates = 60 1 14:11:34.607 [planetinfo.record]: government = 6; 14:11:34.607 [planetinfo.record]: economy = 1; 14:11:34.607 [planetinfo.record]: techlevel = 9; 14:11:34.607 [planetinfo.record]: population = 44; 14:11:34.607 [planetinfo.record]: productivity = 31680; 14:11:34.607 [planetinfo.record]: name = "Usbimaqu"; 14:11:34.607 [planetinfo.record]: inhabitant = "Human Colonial"; 14:11:34.607 [planetinfo.record]: inhabitants = "Human Colonials"; 14:11:34.607 [planetinfo.record]: description = "This planet is reasonably famous for its inhabitants’ exceptional love for tourists."; 14:11:34.612 [planetinfo.record]: air_color = 0.527518, 0.581912, 0.855264, 1; 14:11:34.612 [planetinfo.record]: cloud_alpha = 1.000000; 14:11:34.612 [planetinfo.record]: cloud_color = 0.255318, 0.406947, 0.568359, 1; 14:11:34.612 [planetinfo.record]: cloud_fraction = 0.460000; 14:11:34.613 [planetinfo.record]: land_color = 0.0386719, 0.558063, 0.66, 1; 14:11:34.613 [planetinfo.record]: land_fraction = 0.280000; 14:11:34.613 [planetinfo.record]: polar_cloud_color = 0.4956, 0.621616, 0.755762, 1; 14:11:34.613 [planetinfo.record]: polar_land_color = 0.714182, 0.897936, 0.934, 1; 14:11:34.613 [planetinfo.record]: polar_sea_color = 0.944531, 0.891385, 0.89011, 1; 14:11:34.613 [planetinfo.record]: sea_color = 0.554688, 0.429846, 0.426849, 1; 14:11:34.613 [planetinfo.record]: rotation_speed = 0.001868 14:11:34.613 [planetinfo.record]: planet zpos = 541640.000000 14:11:34.613 [planetinfo.record]: sun_radius = 140657.377930 14:11:34.613 [planetinfo.record]: sun_vector = 0.156 0.878 0.452 14:11:34.613 [planetinfo.record]: sun_distance = 886320 14:11:34.613 [planetinfo.record]: corona_flare = 0.069356 14:11:34.613 [planetinfo.record]: corona_hues = 0.620537 14:11:34.613 [planetinfo.record]: sun_color = 0.763998, 0.642605, 0.45238, 1 14:11:34.613 [planetinfo.record]: corona_shimmer = 0.262551 14:11:34.614 [planetinfo.record]: station_vector = 0.537 0.488 0.688 14:11:34.614 [planetinfo.record]: station = coriolis 14:11:40.189 [PLANETINFO OVER]: Done 14:11:40.191 [PLANETINFO LOGGING]: 6 41 14:11:41.194 [planetinfo.record]: seed = 202 218 128 45 14:11:41.194 [planetinfo.record]: coordinates = 218 139 14:11:41.194 [planetinfo.record]: government = 1; 14:11:41.194 [planetinfo.record]: economy = 3; 14:11:41.194 [planetinfo.record]: techlevel = 7; 14:11:41.194 [planetinfo.record]: population = 33; 14:11:41.194 [planetinfo.record]: productivity = 9240; 14:11:41.194 [planetinfo.record]: name = "Dirateri"; 14:11:41.194 [planetinfo.record]: inhabitant = "Red Bug-Eyed Lizard"; 14:11:41.194 [planetinfo.record]: inhabitants = "Red Bug-Eyed Lizards"; 14:11:41.194 [planetinfo.record]: description = "Dirateri is a revolting dump."; 14:11:41.220 [planetinfo.record]: air_color = 0.505418, 0.590818, 0.866971, 1; 14:11:41.220 [planetinfo.record]: cloud_alpha = 1.000000; 14:11:41.220 [planetinfo.record]: cloud_color = 0.209816, 0.48356, 0.603516, 1; 14:11:41.220 [planetinfo.record]: cloud_fraction = 0.500000; 14:11:41.220 [planetinfo.record]: land_color = 0.66, 0.33, 0.376406, 1; 14:11:41.220 [planetinfo.record]: land_fraction = 0.320000; 14:11:41.220 [planetinfo.record]: polar_cloud_color = 0.456997, 0.675732, 0.771582, 1; 14:11:41.220 [planetinfo.record]: polar_land_color = 0.934, 0.81725, 0.833668, 1; 14:11:41.220 [planetinfo.record]: polar_sea_color = 0.93418, 0.923524, 0.874881, 1; 14:11:41.220 [planetinfo.record]: sea_color = 0.658203, 0.628173, 0.491081, 1; 14:11:41.220 [planetinfo.record]: rotation_speed = 0.002944 14:11:41.220 [planetinfo.record]: planet zpos = 699820.000000 14:11:41.220 [planetinfo.record]: sun_radius = 189610.626526 14:11:41.220 [planetinfo.record]: sun_vector = -0.275 -0.833 0.481 14:11:41.220 [planetinfo.record]: sun_distance = 1336020 14:11:41.220 [planetinfo.record]: corona_flare = 0.035457 14:11:41.220 [planetinfo.record]: corona_hues = 0.835129 14:11:41.221 [planetinfo.record]: sun_color = 0.198762, 0.464353, 0.733655, 1 14:11:41.221 [planetinfo.record]: corona_shimmer = 0.386156 14:11:41.221 [planetinfo.record]: station_vector = -0.504 -0.830 0.236 14:11:41.221 [planetinfo.record]: station = coriolis 14:11:47.237 [PLANETINFO OVER]: Done 14:11:47.238 [PLANETINFO LOGGING]: 6 42 14:11:48.242 [planetinfo.record]: seed = 26 94 180 142 14:11:48.243 [planetinfo.record]: coordinates = 94 156 14:11:48.243 [planetinfo.record]: government = 3; 14:11:48.243 [planetinfo.record]: economy = 4; 14:11:48.243 [planetinfo.record]: techlevel = 7; 14:11:48.243 [planetinfo.record]: population = 36; 14:11:48.243 [planetinfo.record]: productivity = 12096; 14:11:48.243 [planetinfo.record]: name = "Reesvere"; 14:11:48.243 [planetinfo.record]: inhabitant = "Black Horned Bird"; 14:11:48.243 [planetinfo.record]: inhabitants = "Black Horned Birds"; 14:11:48.243 [planetinfo.record]: description = "This world is mildly well known for Reesvereian wolf cutlet and its unusual oceans."; 14:11:48.254 [planetinfo.record]: air_color = 0.517003, 0.983391, 0.899174, 1; 14:11:48.254 [planetinfo.record]: cloud_alpha = 1.000000; 14:11:48.254 [planetinfo.record]: cloud_color = 0.953125, 0.535784, 0.167542, 1; 14:11:48.254 [planetinfo.record]: cloud_fraction = 0.260000; 14:11:48.254 [planetinfo.record]: land_color = 0.599024, 0.599609, 0.594925, 1; 14:11:48.254 [planetinfo.record]: land_fraction = 0.680000; 14:11:48.254 [planetinfo.record]: polar_cloud_color = 0.928906, 0.674696, 0.450393, 1; 14:11:48.254 [planetinfo.record]: polar_land_color = 0.93981, 0.940039, 0.938203, 1; 14:11:48.254 [planetinfo.record]: polar_sea_color = 0.893298, 0.918555, 0.82347, 1; 14:11:48.254 [planetinfo.record]: sea_color = 0.724875, 0.814453, 0.477219, 1; 14:11:48.254 [planetinfo.record]: rotation_speed = 0.002188 14:11:48.254 [planetinfo.record]: planet zpos = 844220.000000 14:11:48.254 [planetinfo.record]: sun_radius = 157370.698547 14:11:48.254 [planetinfo.record]: sun_vector = 0.209 -0.950 0.234 14:11:48.254 [planetinfo.record]: sun_distance = 1428680 14:11:48.254 [planetinfo.record]: corona_flare = 0.045592 14:11:48.254 [planetinfo.record]: corona_hues = 0.718643 14:11:48.255 [planetinfo.record]: sun_color = 0.55184, 0.639923, 0.774063, 1 14:11:48.256 [planetinfo.record]: corona_shimmer = 0.237097 14:11:48.256 [planetinfo.record]: station_vector = -0.801 -0.518 0.300 14:11:48.256 [planetinfo.record]: station = coriolis 14:11:53.051 [PLANETINFO OVER]: Done 14:11:53.052 [PLANETINFO LOGGING]: 6 43 14:11:54.057 [planetinfo.record]: seed = 2 142 80 141 14:11:54.057 [planetinfo.record]: coordinates = 142 118 14:11:54.057 [planetinfo.record]: government = 0; 14:11:54.057 [planetinfo.record]: economy = 6; 14:11:54.057 [planetinfo.record]: techlevel = 3; 14:11:54.057 [planetinfo.record]: population = 19; 14:11:54.057 [planetinfo.record]: productivity = 2432; 14:11:54.057 [planetinfo.record]: name = "Diatin"; 14:11:54.057 [planetinfo.record]: inhabitant = "Human Colonial"; 14:11:54.057 [planetinfo.record]: inhabitants = "Human Colonials"; 14:11:54.057 [planetinfo.record]: description = "Diatin is reasonably well known for the Diatinian tree grub but plagued by frequent earthquakes."; 14:11:54.092 [planetinfo.record]: air_color = 0.618472, 0.888434, 0.810188, 1; 14:11:54.093 [planetinfo.record]: cloud_alpha = 1.000000; 14:11:54.093 [planetinfo.record]: cloud_color = 0.667969, 0.488583, 0.459229, 1; 14:11:54.093 [planetinfo.record]: cloud_fraction = 0.380000; 14:11:54.093 [planetinfo.record]: land_color = 0.0326309, 0.642578, 0.0421613, 1; 14:11:54.093 [planetinfo.record]: land_fraction = 0.510000; 14:11:54.093 [planetinfo.record]: polar_cloud_color = 0.800586, 0.66621, 0.644221, 1; 14:11:54.093 [planetinfo.record]: polar_land_color = 0.713686, 0.935742, 0.717156, 1; 14:11:54.093 [planetinfo.record]: polar_sea_color = 0.890017, 0.931445, 0.879961, 1; 14:11:54.093 [planetinfo.record]: sea_color = 0.56358, 0.685547, 0.533977, 1; 14:11:54.093 [planetinfo.record]: rotation_speed = 0.004539 14:11:54.093 [planetinfo.record]: planet zpos = 754320.000000 14:11:54.093 [planetinfo.record]: sun_radius = 187833.767700 14:11:54.093 [planetinfo.record]: sun_vector = 0.246 -0.958 -0.147 14:11:54.093 [planetinfo.record]: sun_distance = 1320060 14:11:54.093 [planetinfo.record]: corona_flare = 0.092778 14:11:54.093 [planetinfo.record]: corona_hues = 0.726051 14:11:54.093 [planetinfo.record]: sun_color = 0.671652, 0.606151, 0.585395, 1 14:11:54.094 [planetinfo.record]: corona_shimmer = 0.442913 14:11:54.094 [planetinfo.record]: station_vector = -0.831 -0.548 0.091 14:11:54.094 [planetinfo.record]: station = coriolis 14:11:58.406 [PLANETINFO OVER]: Done 14:11:58.407 [PLANETINFO LOGGING]: 6 44 14:11:59.410 [planetinfo.record]: seed = 98 203 116 9 14:11:59.410 [planetinfo.record]: coordinates = 203 172 14:11:59.410 [planetinfo.record]: government = 4; 14:11:59.410 [planetinfo.record]: economy = 4; 14:11:59.410 [planetinfo.record]: techlevel = 8; 14:11:59.410 [planetinfo.record]: population = 41; 14:11:59.410 [planetinfo.record]: productivity = 15744; 14:11:59.410 [planetinfo.record]: name = "Esleve"; 14:11:59.410 [planetinfo.record]: inhabitant = "Human Colonial"; 14:11:59.411 [planetinfo.record]: inhabitants = "Human Colonials"; 14:11:59.411 [planetinfo.record]: description = "This world is most fabled for its inhabitants’ ingrained silliness but scourged by deadly civil war."; 14:11:59.423 [planetinfo.record]: air_color = 0.540286, 0.540957, 0.86502, 1; 14:11:59.423 [planetinfo.record]: cloud_alpha = 1.000000; 14:11:59.423 [planetinfo.record]: cloud_color = 0.284948, 0.282486, 0.597656, 1; 14:11:59.423 [planetinfo.record]: cloud_fraction = 0.400000; 14:11:59.423 [planetinfo.record]: land_color = 0.315545, 0.300354, 0.578125, 1; 14:11:59.423 [planetinfo.record]: land_fraction = 0.450000; 14:11:59.423 [planetinfo.record]: polar_cloud_color = 0.517489, 0.515509, 0.768945, 1; 14:11:59.423 [planetinfo.record]: polar_land_color = 0.835204, 0.829015, 0.942187, 1; 14:11:59.423 [planetinfo.record]: polar_sea_color = 0.936133, 0.920409, 0.87863, 1; 14:11:59.424 [planetinfo.record]: sea_color = 0.638672, 0.595763, 0.481748, 1; 14:11:59.424 [planetinfo.record]: rotation_speed = 0.002457 14:11:59.424 [planetinfo.record]: planet zpos = 532300.000000 14:11:59.424 [planetinfo.record]: sun_radius = 155512.725067 14:11:59.424 [planetinfo.record]: sun_vector = 0.276 -0.961 -0.005 14:11:59.424 [planetinfo.record]: sun_distance = 958140 14:11:59.424 [planetinfo.record]: corona_flare = 0.014522 14:11:59.424 [planetinfo.record]: corona_hues = 0.851830 14:11:59.424 [planetinfo.record]: sun_color = 0.76843, 0.335118, 0.208077, 1 14:11:59.424 [planetinfo.record]: corona_shimmer = 0.401347 14:11:59.424 [planetinfo.record]: station_vector = -0.041 0.366 0.930 14:11:59.424 [planetinfo.record]: station = coriolis 14:12:04.362 [PLANETINFO OVER]: Done 14:12:04.362 [PLANETINFO LOGGING]: 6 45 14:12:05.381 [planetinfo.record]: seed = 90 225 64 185 14:12:05.381 [planetinfo.record]: coordinates = 225 86 14:12:05.381 [planetinfo.record]: government = 3; 14:12:05.381 [planetinfo.record]: economy = 6; 14:12:05.381 [planetinfo.record]: techlevel = 4; 14:12:05.381 [planetinfo.record]: population = 26; 14:12:05.381 [planetinfo.record]: productivity = 5824; 14:12:05.381 [planetinfo.record]: name = "Orermala"; 14:12:05.382 [planetinfo.record]: inhabitant = "Human Colonial"; 14:12:05.382 [planetinfo.record]: inhabitants = "Human Colonials"; 14:12:05.382 [planetinfo.record]: description = "This planet is beset by lethal disease."; 14:12:05.408 [planetinfo.record]: air_color = 0.655192, 0.834213, 0.94827, 1; 14:12:05.408 [planetinfo.record]: cloud_alpha = 1.000000; 14:12:05.408 [planetinfo.record]: cloud_color = 0.569519, 0.847656, 0.619497, 1; 14:12:05.408 [planetinfo.record]: cloud_fraction = 0.410000; 14:12:05.408 [planetinfo.record]: land_color = 0.66, 0.190781, 0.432722, 1; 14:12:05.408 [planetinfo.record]: land_fraction = 0.340000; 14:12:05.408 [planetinfo.record]: polar_cloud_color = 0.70068, 0.881445, 0.733161, 1; 14:12:05.408 [planetinfo.record]: polar_land_color = 0.934, 0.767996, 0.853592, 1; 14:12:05.408 [planetinfo.record]: polar_sea_color = 0.937109, 0.900995, 0.877076, 1; 14:12:05.408 [planetinfo.record]: sea_color = 0.628906, 0.53196, 0.467749, 1; 14:12:05.408 [planetinfo.record]: rotation_speed = 0.001134 14:12:05.408 [planetinfo.record]: planet zpos = 694850.000000 14:12:05.408 [planetinfo.record]: sun_radius = 96803.091431 14:12:05.408 [planetinfo.record]: sun_vector = -0.510 -0.804 0.307 14:12:05.408 [planetinfo.record]: sun_distance = 1175900 14:12:05.408 [planetinfo.record]: corona_flare = 0.018619 14:12:05.408 [planetinfo.record]: corona_hues = 0.925430 14:12:05.408 [planetinfo.record]: sun_color = 0.765427, 0.780009, 0.783991, 1 14:12:05.409 [planetinfo.record]: corona_shimmer = 0.261004 14:12:05.409 [planetinfo.record]: station_vector = -0.872 0.195 0.448 14:12:05.410 [planetinfo.record]: station = coriolis 14:12:10.531 [PLANETINFO OVER]: Done 14:12:10.533 [PLANETINFO LOGGING]: 6 46 14:12:11.555 [planetinfo.record]: seed = 202 53 84 178 14:12:11.555 [planetinfo.record]: coordinates = 53 139 14:12:11.555 [planetinfo.record]: government = 1; 14:12:11.555 [planetinfo.record]: economy = 3; 14:12:11.555 [planetinfo.record]: techlevel = 6; 14:12:11.555 [planetinfo.record]: population = 29; 14:12:11.555 [planetinfo.record]: productivity = 8120; 14:12:11.555 [planetinfo.record]: name = "Enbean"; 14:12:11.555 [planetinfo.record]: inhabitant = "Human Colonial"; 14:12:11.555 [planetinfo.record]: inhabitants = "Human Colonials"; 14:12:11.555 [planetinfo.record]: description = "This world is very fabled for its unusual dense forests."; 14:12:11.576 [planetinfo.record]: air_color = 0.673004, 0.745858, 0.981439, 1; 14:12:11.576 [planetinfo.record]: cloud_alpha = 1.000000; 14:12:11.576 [planetinfo.record]: cloud_color = 0.621643, 0.848053, 0.947266, 1; 14:12:11.576 [planetinfo.record]: cloud_fraction = 0.370000; 14:12:11.576 [planetinfo.record]: land_color = 0.66, 0.510549, 0.113438, 1; 14:12:11.576 [planetinfo.record]: land_fraction = 0.630000; 14:12:11.576 [planetinfo.record]: polar_cloud_color = 0.727266, 0.865636, 0.92627, 1; 14:12:11.576 [planetinfo.record]: polar_land_color = 0.934, 0.881126, 0.740633, 1; 14:12:11.576 [planetinfo.record]: polar_sea_color = 0.879717, 0.927734, 0.904101, 1; 14:12:11.576 [planetinfo.record]: sea_color = 0.573044, 0.722656, 0.649019, 1; 14:12:11.576 [planetinfo.record]: rotation_speed = 0.002823 14:12:11.577 [planetinfo.record]: planet zpos = 304290.000000 14:12:11.577 [planetinfo.record]: sun_radius = 75610.253906 14:12:11.577 [planetinfo.record]: sun_vector = 0.731 0.537 0.421 14:12:11.577 [planetinfo.record]: sun_distance = 676200 14:12:11.577 [planetinfo.record]: corona_flare = 0.032487 14:12:11.577 [planetinfo.record]: corona_hues = 0.625381 14:12:11.577 [planetinfo.record]: sun_color = 0.487708, 0.518442, 0.656586, 1 14:12:11.577 [planetinfo.record]: corona_shimmer = 0.245205 14:12:11.577 [planetinfo.record]: station_vector = 0.355 -0.889 0.288 14:12:11.577 [planetinfo.record]: station = coriolis 14:12:16.527 [PLANETINFO OVER]: Done 14:12:16.528 [PLANETINFO LOGGING]: 6 47 14:12:17.537 [planetinfo.record]: seed = 210 129 80 81 14:12:17.537 [planetinfo.record]: coordinates = 129 91 14:12:17.537 [planetinfo.record]: government = 2; 14:12:17.537 [planetinfo.record]: economy = 3; 14:12:17.537 [planetinfo.record]: techlevel = 6; 14:12:17.537 [planetinfo.record]: population = 30; 14:12:17.537 [planetinfo.record]: productivity = 10080; 14:12:17.537 [planetinfo.record]: name = "Atrexexe"; 14:12:17.537 [planetinfo.record]: inhabitant = "Human Colonial"; 14:12:17.537 [planetinfo.record]: inhabitants = "Human Colonials"; 14:12:17.537 [planetinfo.record]: description = "This world is very well known for Atrexexeian vicious brew and its inhabitants’ ancient loathing of casinos."; 14:12:17.539 [planetinfo.record]: air_color = 0.662045, 0.545452, 0.903393, 1; 14:12:17.540 [planetinfo.record]: cloud_alpha = 1.000000; 14:12:17.540 [planetinfo.record]: cloud_color = 0.709606, 0.292397, 0.712891, 1; 14:12:17.540 [planetinfo.record]: cloud_fraction = 0.230000; 14:12:17.540 [planetinfo.record]: land_color = 0.226855, 0.00773438, 0.66, 1; 14:12:17.540 [planetinfo.record]: land_fraction = 0.420000; 14:12:17.540 [planetinfo.record]: polar_cloud_color = 0.818437, 0.518211, 0.820801, 1; 14:12:17.540 [planetinfo.record]: polar_land_color = 0.780759, 0.703236, 0.934, 1; 14:12:17.540 [planetinfo.record]: polar_sea_color = 0.919785, 0.934961, 0.882461, 1; 14:12:17.540 [planetinfo.record]: sea_color = 0.608163, 0.650391, 0.504307, 1; 14:12:17.540 [planetinfo.record]: rotation_speed = 0.002634 14:12:17.540 [planetinfo.record]: planet zpos = 416130.000000 14:12:17.540 [planetinfo.record]: sun_radius = 87341.738892 14:12:17.540 [planetinfo.record]: sun_vector = 0.897 -0.438 -0.064 14:12:17.540 [planetinfo.record]: sun_distance = 672210 14:12:17.540 [planetinfo.record]: corona_flare = 0.039639 14:12:17.540 [planetinfo.record]: corona_hues = 0.943069 14:12:17.540 [planetinfo.record]: sun_color = 0.181548, 0.241678, 0.665732, 1 14:12:17.541 [planetinfo.record]: corona_shimmer = 0.425741 14:12:17.541 [planetinfo.record]: station_vector = 0.285 0.901 0.328 14:12:17.541 [planetinfo.record]: station = coriolis 14:12:21.925 [PLANETINFO OVER]: Done 14:12:21.926 [PLANETINFO LOGGING]: 6 48 14:12:22.930 [planetinfo.record]: seed = 82 130 84 179 14:12:22.930 [planetinfo.record]: coordinates = 130 2 14:12:22.930 [planetinfo.record]: government = 2; 14:12:22.930 [planetinfo.record]: economy = 2; 14:12:22.930 [planetinfo.record]: techlevel = 8; 14:12:22.930 [planetinfo.record]: population = 37; 14:12:22.930 [planetinfo.record]: productivity = 14208; 14:12:22.930 [planetinfo.record]: name = "Betidi"; 14:12:22.930 [planetinfo.record]: inhabitant = "Human Colonial"; 14:12:22.930 [planetinfo.record]: inhabitants = "Human Colonials"; 14:12:22.930 [planetinfo.record]: description = "The planet Betidi is famous for its inhabitants’ exceptional love for food blenders but scourged by dreadful solar activity."; 14:12:22.947 [planetinfo.record]: air_color = 0.658046, 0.854858, 0.931359, 1; 14:12:22.947 [planetinfo.record]: cloud_alpha = 1.000000; 14:12:22.947 [planetinfo.record]: cloud_color = 0.606022, 0.796875, 0.572754, 1; 14:12:22.947 [planetinfo.record]: cloud_fraction = 0.300000; 14:12:22.947 [planetinfo.record]: land_color = 0.511758, 0.495, 0.66, 1; 14:12:22.947 [planetinfo.record]: land_fraction = 0.290000; 14:12:22.947 [planetinfo.record]: polar_cloud_color = 0.730072, 0.858594, 0.707669, 1; 14:12:22.947 [planetinfo.record]: polar_land_color = 0.881554, 0.875625, 0.934, 1; 14:12:22.947 [planetinfo.record]: polar_sea_color = 0.927018, 0.933984, 0.878255, 1; 14:12:22.947 [planetinfo.record]: sea_color = 0.640461, 0.660156, 0.502595, 1; 14:12:22.947 [planetinfo.record]: rotation_speed = 0.003148 14:12:22.947 [planetinfo.record]: planet zpos = 408540.000000 14:12:22.948 [planetinfo.record]: sun_radius = 89330.722046 14:12:22.948 [planetinfo.record]: sun_vector = -0.700 0.593 0.399 14:12:22.948 [planetinfo.record]: sun_distance = 817080 14:12:22.948 [planetinfo.record]: corona_flare = 0.084232 14:12:22.948 [planetinfo.record]: corona_hues = 0.544319 14:12:22.948 [planetinfo.record]: sun_color = 0.635592, 0.667271, 0.699991, 1 14:12:22.948 [planetinfo.record]: corona_shimmer = 0.348181 14:12:22.948 [planetinfo.record]: station_vector = -0.872 0.022 0.489 14:12:22.948 [planetinfo.record]: station = coriolis 14:12:27.543 [PLANETINFO OVER]: Done 14:12:27.544 [PLANETINFO LOGGING]: 6 49 14:12:28.547 [planetinfo.record]: seed = 106 88 128 253 14:12:28.547 [planetinfo.record]: coordinates = 88 109 14:12:28.547 [planetinfo.record]: government = 5; 14:12:28.547 [planetinfo.record]: economy = 5; 14:12:28.547 [planetinfo.record]: techlevel = 5; 14:12:28.547 [planetinfo.record]: population = 31; 14:12:28.547 [planetinfo.record]: productivity = 11160; 14:12:28.547 [planetinfo.record]: name = "Isgeoror"; 14:12:28.547 [planetinfo.record]: inhabitant = "Furry Feline"; 14:12:28.547 [planetinfo.record]: inhabitants = "Furry Felines"; 14:12:28.547 [planetinfo.record]: description = "The planet Isgeoror is very famous for its fabulous goat soup but ravaged by lethal spotted snails."; 14:12:28.556 [planetinfo.record]: air_color = 0.639746, 0.85053, 0.952822, 1; 14:12:28.564 [planetinfo.record]: cloud_alpha = 1.000000; 14:12:28.564 [planetinfo.record]: cloud_color = 0.530839, 0.861328, 0.528236, 1; 14:12:28.564 [planetinfo.record]: cloud_fraction = 0.270000; 14:12:28.564 [planetinfo.record]: land_color = 0.657422, 0.66, 0.657583, 1; 14:12:28.564 [planetinfo.record]: land_fraction = 0.620000; 14:12:28.564 [planetinfo.record]: polar_cloud_color = 0.674742, 0.887598, 0.673066, 1; 14:12:28.564 [planetinfo.record]: polar_land_color = 0.933088, 0.934, 0.933145, 1; 14:12:28.564 [planetinfo.record]: polar_sea_color = 0.780593, 0.94707, 0.802703, 1; 14:12:28.564 [planetinfo.record]: sea_color = 0.157135, 0.529297, 0.206563, 1; 14:12:28.564 [planetinfo.record]: rotation_speed = 0.004270 14:12:28.564 [planetinfo.record]: planet zpos = 810160.000000 14:12:28.564 [planetinfo.record]: sun_radius = 142307.286377 14:12:28.564 [planetinfo.record]: sun_vector = 0.151 0.948 -0.280 14:12:28.564 [planetinfo.record]: sun_distance = 1308720 14:12:28.564 [planetinfo.record]: corona_flare = 0.077153 14:12:28.564 [planetinfo.record]: corona_hues = 0.714882 14:12:28.564 [planetinfo.record]: sun_color = 0.600269, 0.653889, 0.699585, 1 14:12:28.564 [planetinfo.record]: corona_shimmer = 0.358964 14:12:28.565 [planetinfo.record]: station_vector = 0.901 -0.347 0.260 14:12:28.565 [planetinfo.record]: station = coriolis 14:12:33.533 [PLANETINFO OVER]: Done 14:12:33.535 [PLANETINFO LOGGING]: 6 50 14:12:34.539 [planetinfo.record]: seed = 250 217 116 182 14:12:34.539 [planetinfo.record]: coordinates = 217 25 14:12:34.539 [planetinfo.record]: government = 7; 14:12:34.539 [planetinfo.record]: economy = 1; 14:12:34.539 [planetinfo.record]: techlevel = 11; 14:12:34.539 [planetinfo.record]: population = 53; 14:12:34.540 [planetinfo.record]: productivity = 41976; 14:12:34.540 [planetinfo.record]: name = "Veesqu"; 14:12:34.540 [planetinfo.record]: inhabitant = "Human Colonial"; 14:12:34.540 [planetinfo.record]: inhabitants = "Human Colonials"; 14:12:34.540 [planetinfo.record]: description = "The world Veesqu is scourged by deadly disease."; 14:12:34.542 [planetinfo.record]: air_color = 0.691109, 0.605491, 0.850711, 1; 14:12:34.542 [planetinfo.record]: cloud_alpha = 1.000000; 14:12:34.548 [planetinfo.record]: cloud_color = 0.554688, 0.407349, 0.545479, 1; 14:12:34.548 [planetinfo.record]: cloud_fraction = 0.250000; 14:12:34.548 [planetinfo.record]: land_color = 0.537539, 0.66, 0.268125, 1; 14:12:34.548 [planetinfo.record]: land_fraction = 0.240000; 14:12:34.548 [planetinfo.record]: polar_cloud_color = 0.749609, 0.625162, 0.741831, 1; 14:12:34.548 [planetinfo.record]: polar_land_color = 0.890675, 0.934, 0.795359, 1; 14:12:34.548 [planetinfo.record]: polar_sea_color = 0.939258, 0.924686, 0.885966, 1; 14:12:34.549 [planetinfo.record]: sea_color = 0.607422, 0.569727, 0.469566, 1; 14:12:34.549 [planetinfo.record]: rotation_speed = 0.003475 14:12:34.549 [planetinfo.record]: planet zpos = 548280.000000 14:12:34.549 [planetinfo.record]: sun_radius = 111627.329407 14:12:34.549 [planetinfo.record]: sun_vector = -0.674 0.636 0.376 14:12:34.549 [planetinfo.record]: sun_distance = 1005180 14:12:34.549 [planetinfo.record]: corona_flare = 0.093411 14:12:34.549 [planetinfo.record]: corona_hues = 0.651131 14:12:34.549 [planetinfo.record]: sun_color = 0.565302, 0.608423, 0.670267, 1 14:12:34.549 [planetinfo.record]: corona_shimmer = 0.452085 14:12:34.559 [planetinfo.record]: station_vector = 0.261 -0.944 0.202 14:12:34.559 [planetinfo.record]: station = dodecahedron 14:12:39.128 [PLANETINFO OVER]: Done 14:12:39.129 [PLANETINFO LOGGING]: 6 51 14:12:40.132 [planetinfo.record]: seed = 34 154 208 125 14:12:40.132 [planetinfo.record]: coordinates = 154 58 14:12:40.132 [planetinfo.record]: government = 4; 14:12:40.132 [planetinfo.record]: economy = 2; 14:12:40.132 [planetinfo.record]: techlevel = 9; 14:12:40.132 [planetinfo.record]: population = 43; 14:12:40.132 [planetinfo.record]: productivity = 22016; 14:12:40.132 [planetinfo.record]: name = "Isenes"; 14:12:40.132 [planetinfo.record]: inhabitant = "Blue Slimy Frog"; 14:12:40.132 [planetinfo.record]: inhabitants = "Blue Slimy Frogs"; 14:12:40.132 [planetinfo.record]: description = "Isenes is reasonably well known for the Isenesian tree wolf but scourged by dreadful earthquakes."; 14:12:40.144 [planetinfo.record]: air_color = 0.695192, 0.835805, 0.904043, 1; 14:12:40.144 [planetinfo.record]: cloud_alpha = 1.000000; 14:12:40.144 [planetinfo.record]: cloud_color = 0.648351, 0.714844, 0.647827, 1; 14:12:40.145 [planetinfo.record]: cloud_fraction = 0.140000; 14:12:40.145 [planetinfo.record]: land_color = 0.205444, 0.515625, 0.377498, 1; 14:12:40.145 [planetinfo.record]: land_fraction = 0.480000; 14:12:40.145 [planetinfo.record]: polar_cloud_color = 0.773911, 0.82168, 0.773534, 1; 14:12:40.145 [planetinfo.record]: polar_land_color = 0.805801, 0.948438, 0.88492, 1; 14:12:40.145 [planetinfo.record]: polar_sea_color = 0.917969, 0.905614, 0.805016, 1; 14:12:40.145 [planetinfo.record]: sea_color = 0.820312, 0.776153, 0.416565, 1; 14:12:40.145 [planetinfo.record]: rotation_speed = 0.000784 14:12:40.145 [planetinfo.record]: planet zpos = 755760.000000 14:12:40.145 [planetinfo.record]: sun_radius = 180558.170776 14:12:40.145 [planetinfo.record]: sun_vector = -0.174 0.983 0.051 14:12:40.145 [planetinfo.record]: sun_distance = 1133640 14:12:40.145 [planetinfo.record]: corona_flare = 0.063715 14:12:40.145 [planetinfo.record]: corona_hues = 0.782265 14:12:40.145 [planetinfo.record]: sun_color = 0.546018, 0.675205, 0.681146, 1 14:12:40.145 [planetinfo.record]: corona_shimmer = 0.308873 14:12:40.145 [planetinfo.record]: station_vector = 0.934 0.063 0.351 14:12:40.145 [planetinfo.record]: station = coriolis 14:12:45.185 [PLANETINFO OVER]: Done 14:12:45.185 [PLANETINFO LOGGING]: 6 52 14:12:46.206 [planetinfo.record]: seed = 194 57 180 245 14:12:46.206 [planetinfo.record]: coordinates = 57 105 14:12:46.206 [planetinfo.record]: government = 0; 14:12:46.206 [planetinfo.record]: economy = 3; 14:12:46.206 [planetinfo.record]: techlevel = 5; 14:12:46.206 [planetinfo.record]: population = 24; 14:12:46.206 [planetinfo.record]: productivity = 5376; 14:12:46.206 [planetinfo.record]: name = "Laorusti"; 14:12:46.206 [planetinfo.record]: inhabitant = "Slimy Frog"; 14:12:46.206 [planetinfo.record]: inhabitants = "Slimy Frogs"; 14:12:46.206 [planetinfo.record]: description = "Laorusti is ravaged by dreadful civil war."; 14:12:46.221 [planetinfo.record]: air_color = 0.65898, 0.881195, 0.905994, 1; 14:12:46.221 [planetinfo.record]: cloud_alpha = 1.000000; 14:12:46.221 [planetinfo.record]: cloud_color = 0.671436, 0.720703, 0.563049, 1; 14:12:46.221 [planetinfo.record]: cloud_fraction = 0.520000; 14:12:46.221 [planetinfo.record]: land_color = 0.440859, 0.66, 0.454556, 1; 14:12:46.221 [planetinfo.record]: land_fraction = 0.710000; 14:12:46.221 [planetinfo.record]: polar_cloud_color = 0.789098, 0.824316, 0.711617, 1; 14:12:46.221 [planetinfo.record]: polar_land_color = 0.856471, 0.934, 0.861316, 1; 14:12:46.221 [planetinfo.record]: polar_sea_color = 0.948047, 0.889257, 0.931971, 1; 14:12:46.222 [planetinfo.record]: sea_color = 0.519531, 0.390663, 0.484294, 1; 14:12:46.222 [planetinfo.record]: rotation_speed = 0.003792 14:12:46.222 [planetinfo.record]: planet zpos = 456830.000000 14:12:46.222 [planetinfo.record]: sun_radius = 102399.180603 14:12:46.222 [planetinfo.record]: sun_vector = 0.274 0.853 -0.443 14:12:46.222 [planetinfo.record]: sun_distance = 872130 14:12:46.222 [planetinfo.record]: corona_flare = 0.070328 14:12:46.222 [planetinfo.record]: corona_hues = 0.984352 14:12:46.222 [planetinfo.record]: sun_color = 0.658102, 0.595262, 0.298803, 1 14:12:46.222 [planetinfo.record]: corona_shimmer = 0.311060 14:12:46.222 [planetinfo.record]: station_vector = -0.846 0.400 0.352 14:12:46.222 [planetinfo.record]: station = coriolis 14:12:51.927 [PLANETINFO OVER]: Done 14:12:51.927 [PLANETINFO LOGGING]: 6 53 14:12:52.931 [planetinfo.record]: seed = 250 87 64 186 14:12:52.931 [planetinfo.record]: coordinates = 87 200 14:12:52.931 [planetinfo.record]: government = 7; 14:12:52.931 [planetinfo.record]: economy = 0; 14:12:52.931 [planetinfo.record]: techlevel = 14; 14:12:52.931 [planetinfo.record]: population = 64; 14:12:52.931 [planetinfo.record]: productivity = 56320; 14:12:52.931 [planetinfo.record]: name = "Quandixe"; 14:12:52.931 [planetinfo.record]: inhabitant = "Human Colonial"; 14:12:52.931 [planetinfo.record]: inhabitants = "Human Colonials"; 14:12:52.931 [planetinfo.record]: description = "Quandixe is ravaged by occasional solar activity."; 14:12:52.948 [planetinfo.record]: air_color = 0.514919, 0.509709, 0.894287, 1; 14:12:52.948 [planetinfo.record]: cloud_alpha = 1.000000; 14:12:52.948 [planetinfo.record]: cloud_color = 0.237477, 0.211555, 0.685547, 1; 14:12:52.948 [planetinfo.record]: cloud_fraction = 0.330000; 14:12:52.948 [planetinfo.record]: land_color = 0.66, 0.611016, 0.648902, 1; 14:12:52.948 [planetinfo.record]: land_fraction = 0.420000; 14:12:52.948 [planetinfo.record]: polar_cloud_color = 0.478228, 0.459122, 0.808496, 1; 14:12:52.948 [planetinfo.record]: polar_land_color = 0.934, 0.91667, 0.930074, 1; 14:12:52.948 [planetinfo.record]: polar_sea_color = 0.947852, 0.872123, 0.72755, 1; 14:12:52.948 [planetinfo.record]: sea_color = 0.521484, 0.354828, 0.0366669, 1; 14:12:52.948 [planetinfo.record]: rotation_speed = 0.002363 14:12:52.948 [planetinfo.record]: planet zpos = 600930.000000 14:12:52.948 [planetinfo.record]: sun_radius = 178734.528809 14:12:52.948 [planetinfo.record]: sun_vector = 0.683 -0.497 -0.535 14:12:52.948 [planetinfo.record]: sun_distance = 928710 14:12:52.948 [planetinfo.record]: corona_flare = 0.044490 14:12:52.948 [planetinfo.record]: corona_hues = 0.669792 14:12:52.948 [planetinfo.record]: sun_color = 0.714315, 0.720281, 0.76131, 1 14:12:52.949 [planetinfo.record]: corona_shimmer = 0.429097 14:12:52.949 [planetinfo.record]: station_vector = 0.691 -0.251 0.678 14:12:52.949 [planetinfo.record]: station = dodecahedron 14:12:57.731 [PLANETINFO OVER]: Done 14:12:57.732 [PLANETINFO LOGGING]: 6 54 14:12:58.745 [planetinfo.record]: seed = 170 130 20 75 14:12:58.745 [planetinfo.record]: coordinates = 130 237 14:12:58.745 [planetinfo.record]: government = 5; 14:12:58.745 [planetinfo.record]: economy = 5; 14:12:58.745 [planetinfo.record]: techlevel = 7; 14:12:58.745 [planetinfo.record]: population = 39; 14:12:58.745 [planetinfo.record]: productivity = 14040; 14:12:58.745 [planetinfo.record]: name = "Maanusre"; 14:12:58.745 [planetinfo.record]: inhabitant = "Human Colonial"; 14:12:58.745 [planetinfo.record]: inhabitants = "Human Colonials"; 14:12:58.745 [planetinfo.record]: description = "This world is very well known for its inhabitants’ unusual mating traditions and its great parking meters."; 14:12:58.767 [planetinfo.record]: air_color = 0.758591, 0.756013, 0.946318, 1; 14:12:58.767 [planetinfo.record]: cloud_alpha = 1.000000; 14:12:58.768 [planetinfo.record]: cloud_color = 0.838688, 0.838509, 0.841797, 1; 14:12:58.768 [planetinfo.record]: cloud_fraction = 0.430000; 14:12:58.768 [planetinfo.record]: land_color = 0.257115, 0.517578, 0.125351, 1; 14:12:58.768 [planetinfo.record]: land_fraction = 0.370000; 14:12:58.768 [planetinfo.record]: polar_cloud_color = 0.87678, 0.876663, 0.878809, 1; 14:12:58.768 [planetinfo.record]: polar_land_color = 0.828945, 0.948242, 0.768595, 1; 14:12:58.768 [planetinfo.record]: polar_sea_color = 0.868845, 0.92168, 0.910535, 1; 14:12:58.768 [planetinfo.record]: sea_color = 0.603617, 0.783203, 0.745322, 1; 14:12:58.768 [planetinfo.record]: rotation_speed = 0.004038 14:12:58.769 [planetinfo.record]: planet zpos = 518580.000000 14:12:58.769 [planetinfo.record]: sun_radius = 137636.152649 14:12:58.769 [planetinfo.record]: sun_vector = -0.976 0.055 0.212 14:12:58.769 [planetinfo.record]: sun_distance = 1210020 14:12:58.769 [planetinfo.record]: corona_flare = 0.016171 14:12:58.769 [planetinfo.record]: corona_hues = 0.865013 14:12:58.771 [planetinfo.record]: sun_color = 0.655045, 0.366866, 0.186663, 1 14:12:58.771 [planetinfo.record]: corona_shimmer = 0.991258 14:12:58.772 [planetinfo.record]: station_vector = -0.896 -0.165 0.413 14:12:58.772 [planetinfo.record]: station = coriolis 14:13:04.139 [PLANETINFO OVER]: Done 14:13:04.140 [PLANETINFO LOGGING]: 6 55 14:13:05.157 [planetinfo.record]: seed = 242 142 208 210 14:13:05.157 [planetinfo.record]: coordinates = 142 136 14:13:05.158 [planetinfo.record]: government = 6; 14:13:05.158 [planetinfo.record]: economy = 0; 14:13:05.158 [planetinfo.record]: techlevel = 12; 14:13:05.158 [planetinfo.record]: population = 55; 14:13:05.158 [planetinfo.record]: productivity = 44000; 14:13:05.158 [planetinfo.record]: name = "Enarines"; 14:13:05.158 [planetinfo.record]: inhabitant = "Rodent"; 14:13:05.158 [planetinfo.record]: inhabitants = "Rodents"; 14:13:05.158 [planetinfo.record]: description = "The planet Enarines is a boring world."; 14:13:05.183 [planetinfo.record]: air_color = 0.649102, 0.900387, 0.902092, 1; 14:13:05.183 [planetinfo.record]: cloud_alpha = 1.000000; 14:13:05.183 [planetinfo.record]: cloud_color = 0.703619, 0.708984, 0.537277, 1; 14:13:05.183 [planetinfo.record]: cloud_fraction = 0.360000; 14:13:05.183 [planetinfo.record]: land_color = 0.509766, 0.477905, 0.486119, 1; 14:13:05.183 [planetinfo.record]: land_fraction = 0.250000; 14:13:05.184 [planetinfo.record]: polar_cloud_color = 0.815169, 0.819043, 0.695067, 1; 14:13:05.184 [planetinfo.record]: polar_land_color = 0.949023, 0.934195, 0.938018, 1; 14:13:05.184 [planetinfo.record]: polar_sea_color = 0.94082, 0.913293, 0.886613, 1; 14:13:05.184 [planetinfo.record]: sea_color = 0.591797, 0.522536, 0.455406, 1; 14:13:05.184 [planetinfo.record]: rotation_speed = 0.003945 14:13:05.184 [planetinfo.record]: planet zpos = 312300.000000 14:13:05.184 [planetinfo.record]: sun_radius = 86769.590759 14:13:05.184 [planetinfo.record]: sun_vector = -0.995 -0.007 -0.101 14:13:05.184 [planetinfo.record]: sun_distance = 624600 14:13:05.184 [planetinfo.record]: corona_flare = 0.068625 14:13:05.184 [planetinfo.record]: corona_hues = 0.987411 14:13:05.184 [planetinfo.record]: sun_color = 0.670813, 0.575448, 0.470639, 1 14:13:05.184 [planetinfo.record]: corona_shimmer = 0.391022 14:13:05.185 [planetinfo.record]: station_vector = 0.267 0.917 0.298 14:13:05.185 [planetinfo.record]: station = dodecahedron 14:13:09.496 [PLANETINFO OVER]: Done 14:13:09.497 [PLANETINFO LOGGING]: 6 56 14:13:10.499 [planetinfo.record]: seed = 178 9 148 64 14:13:10.499 [planetinfo.record]: coordinates = 9 76 14:13:10.499 [planetinfo.record]: government = 6; 14:13:10.499 [planetinfo.record]: economy = 4; 14:13:10.499 [planetinfo.record]: techlevel = 7; 14:13:10.499 [planetinfo.record]: population = 39; 14:13:10.499 [planetinfo.record]: productivity = 18720; 14:13:10.499 [planetinfo.record]: name = "Veed"; 14:13:10.499 [planetinfo.record]: inhabitant = "Large Yellow Furry Humanoid"; 14:13:10.500 [planetinfo.record]: inhabitants = "Large Yellow Furry Humanoids"; 14:13:10.500 [planetinfo.record]: description = "The planet Veed is reasonably noted for its inhabitants’ eccentric shyness and its inhabitants’ ingrained shyness."; 14:13:10.519 [planetinfo.record]: air_color = 0.65036, 0.835864, 0.950221, 1; 14:13:10.519 [planetinfo.record]: cloud_alpha = 1.000000; 14:13:10.520 [planetinfo.record]: cloud_color = 0.556786, 0.853516, 0.60315, 1; 14:13:10.520 [planetinfo.record]: cloud_fraction = 0.210000; 14:13:10.520 [planetinfo.record]: land_color = 0.548828, 0.441635, 0.506956, 1; 14:13:10.520 [planetinfo.record]: land_fraction = 0.490000; 14:13:10.520 [planetinfo.record]: polar_cloud_color = 0.691984, 0.884082, 0.721999, 1; 14:13:10.520 [planetinfo.record]: polar_land_color = 0.945117, 0.898969, 0.927091, 1; 14:13:10.520 [planetinfo.record]: polar_sea_color = 0.90625, 0.824255, 0.728363, 1; 14:13:10.520 [planetinfo.record]: sea_color = 0.9375, 0.598211, 0.201416, 1; 14:13:10.520 [planetinfo.record]: rotation_speed = 0.004327 14:13:10.520 [planetinfo.record]: planet zpos = 339000.000000 14:13:10.520 [planetinfo.record]: sun_radius = 61893.432617 14:13:10.520 [planetinfo.record]: sun_vector = -0.367 0.779 0.508 14:13:10.520 [planetinfo.record]: sun_distance = 565000 14:13:10.520 [planetinfo.record]: corona_flare = 0.043810 14:13:10.520 [planetinfo.record]: corona_hues = 0.981850 14:13:10.521 [planetinfo.record]: sun_color = 0.671933, 0.57964, 0.410046, 1 14:13:10.521 [planetinfo.record]: corona_shimmer = 0.243256 14:13:10.521 [planetinfo.record]: station_vector = -0.323 0.922 0.214 14:13:10.521 [planetinfo.record]: station = coriolis 14:13:16.018 [PLANETINFO OVER]: Done 14:13:16.020 [PLANETINFO LOGGING]: 6 57 14:13:17.035 [planetinfo.record]: seed = 10 184 128 47 14:13:17.035 [planetinfo.record]: coordinates = 184 224 14:13:17.035 [planetinfo.record]: government = 1; 14:13:17.035 [planetinfo.record]: economy = 2; 14:13:17.035 [planetinfo.record]: techlevel = 6; 14:13:17.035 [planetinfo.record]: population = 28; 14:13:17.035 [planetinfo.record]: productivity = 8960; 14:13:17.035 [planetinfo.record]: name = "Ausaso"; 14:13:17.035 [planetinfo.record]: inhabitant = "Red Slimy Lobster"; 14:13:17.035 [planetinfo.record]: inhabitants = "Red Slimy Lobsters"; 14:13:17.035 [planetinfo.record]: description = "This world is most fabled for Ausasoian Leth brandy but cursed by deadly civil war."; 14:13:17.042 [planetinfo.record]: air_color = 0.649192, 0.765547, 0.985992, 1; 14:13:17.042 [planetinfo.record]: cloud_alpha = 1.000000; 14:13:17.042 [planetinfo.record]: cloud_color = 0.551788, 0.960938, 0.951348, 1; 14:13:17.042 [planetinfo.record]: cloud_fraction = 0.310000; 14:13:17.042 [planetinfo.record]: land_color = 0.523359, 0.643987, 0.66, 1; 14:13:17.042 [planetinfo.record]: land_fraction = 0.630000; 14:13:17.042 [planetinfo.record]: polar_cloud_color = 0.684292, 0.932422, 0.926606, 1; 14:13:17.042 [planetinfo.record]: polar_land_color = 0.885658, 0.928335, 0.934, 1; 14:13:17.042 [planetinfo.record]: polar_sea_color = 0.929297, 0.903305, 0.858511, 1; 14:13:17.042 [planetinfo.record]: sea_color = 0.707031, 0.62793, 0.491608, 1; 14:13:17.042 [planetinfo.record]: rotation_speed = 0.000440 14:13:17.042 [planetinfo.record]: planet zpos = 820800.000000 14:13:17.042 [planetinfo.record]: sun_radius = 188049.902344 14:13:17.042 [planetinfo.record]: sun_vector = 0.237 0.740 0.629 14:13:17.042 [planetinfo.record]: sun_distance = 1436400 14:13:17.042 [planetinfo.record]: corona_flare = 0.021597 14:13:17.042 [planetinfo.record]: corona_hues = 0.666649 14:13:17.042 [planetinfo.record]: sun_color = 0.739273, 0.609113, 0.521132, 1 14:13:17.043 [planetinfo.record]: corona_shimmer = 0.570543 14:13:17.043 [planetinfo.record]: station_vector = 0.745 -0.571 0.345 14:13:17.043 [planetinfo.record]: station = coriolis 14:13:22.292 [PLANETINFO OVER]: Done 14:13:22.292 [PLANETINFO LOGGING]: 6 58 14:13:23.301 [planetinfo.record]: seed = 218 167 52 32 14:13:23.302 [planetinfo.record]: coordinates = 167 175 14:13:23.302 [planetinfo.record]: government = 3; 14:13:23.302 [planetinfo.record]: economy = 7; 14:13:23.302 [planetinfo.record]: techlevel = 5; 14:13:23.302 [planetinfo.record]: population = 31; 14:13:23.302 [planetinfo.record]: productivity = 5208; 14:13:23.302 [planetinfo.record]: name = "Eded"; 14:13:23.302 [planetinfo.record]: inhabitant = "Human Colonial"; 14:13:23.302 [planetinfo.record]: inhabitants = "Human Colonials"; 14:13:23.302 [planetinfo.record]: description = "This planet is plagued by deadly earthquakes."; 14:13:23.319 [planetinfo.record]: air_color = 0.552519, 0.960627, 0.896489, 1; 14:13:23.319 [planetinfo.record]: cloud_alpha = 1.000000; 14:13:23.319 [planetinfo.record]: cloud_color = 0.884766, 0.609167, 0.286858, 1; 14:13:23.319 [planetinfo.record]: cloud_fraction = 0.360000; 14:13:23.319 [planetinfo.record]: land_color = 0.650391, 0.411575, 0.512326, 1; 14:13:23.319 [planetinfo.record]: land_fraction = 0.310000; 14:13:23.319 [planetinfo.record]: polar_cloud_color = 0.898145, 0.723291, 0.518801, 1; 14:13:23.319 [planetinfo.record]: polar_land_color = 0.934961, 0.849134, 0.885343, 1; 14:13:23.319 [planetinfo.record]: polar_sea_color = 0.936076, 0.937695, 0.885866, 1; 14:13:23.319 [planetinfo.record]: sea_color = 0.618742, 0.623047, 0.485295, 1; 14:13:23.319 [planetinfo.record]: rotation_speed = 0.004649 14:13:23.319 [planetinfo.record]: planet zpos = 357960.000000 14:13:23.319 [planetinfo.record]: sun_radius = 80824.934845 14:13:23.319 [planetinfo.record]: sun_vector = 0.474 -0.395 0.787 14:13:23.319 [planetinfo.record]: sun_distance = 656260 14:13:23.319 [planetinfo.record]: corona_flare = 0.094672 14:13:23.319 [planetinfo.record]: corona_hues = 0.917610 14:13:23.319 [planetinfo.record]: sun_color = 0.835568, 0.823247, 0.81171, 1 14:13:23.320 [planetinfo.record]: corona_shimmer = 0.294068 14:13:23.320 [planetinfo.record]: station_vector = 0.577 -0.816 0.039 14:13:23.320 [planetinfo.record]: station = coriolis 14:13:28.693 [PLANETINFO OVER]: Done 14:13:28.694 [PLANETINFO LOGGING]: 6 59 14:13:29.697 [planetinfo.record]: seed = 66 216 80 144 14:13:29.697 [planetinfo.record]: coordinates = 216 64 14:13:29.697 [planetinfo.record]: government = 0; 14:13:29.697 [planetinfo.record]: economy = 2; 14:13:29.697 [planetinfo.record]: techlevel = 5; 14:13:29.697 [planetinfo.record]: population = 23; 14:13:29.697 [planetinfo.record]: productivity = 5888; 14:13:29.697 [planetinfo.record]: name = "Erusat"; 14:13:29.697 [planetinfo.record]: inhabitant = "Human Colonial"; 14:13:29.697 [planetinfo.record]: inhabitants = "Human Colonials"; 14:13:29.697 [planetinfo.record]: description = "The world Erusat is reasonably noted for its inhabitants’ eccentric shyness and its inhabitants’ ingrained silliness."; 14:13:29.725 [planetinfo.record]: air_color = 0.609946, 0.584881, 0.837053, 1; 14:13:29.725 [planetinfo.record]: cloud_alpha = 1.000000; 14:13:29.725 [planetinfo.record]: cloud_color = 0.406072, 0.357162, 0.513672, 1; 14:13:29.725 [planetinfo.record]: cloud_fraction = 0.140000; 14:13:29.725 [planetinfo.record]: land_color = 0.66, 0.575405, 0.118594, 1; 14:13:29.725 [planetinfo.record]: land_fraction = 0.320000; 14:13:29.725 [planetinfo.record]: polar_cloud_color = 0.63543, 0.591919, 0.731152, 1; 14:13:29.725 [planetinfo.record]: polar_land_color = 0.934, 0.904071, 0.742457, 1; 14:13:29.725 [planetinfo.record]: polar_sea_color = 0.948047, 0.894812, 0.908536, 1; 14:13:29.725 [planetinfo.record]: sea_color = 0.519531, 0.40284, 0.432924, 1; 14:13:29.725 [planetinfo.record]: rotation_speed = 0.002052 14:13:29.725 [planetinfo.record]: planet zpos = 394160.000000 14:13:29.725 [planetinfo.record]: sun_radius = 51228.382568 14:13:29.725 [planetinfo.record]: sun_vector = -0.267 -0.929 -0.257 14:13:29.725 [planetinfo.record]: sun_distance = 636720 14:13:29.725 [planetinfo.record]: corona_flare = 0.027866 14:13:29.725 [planetinfo.record]: corona_hues = 0.575531 14:13:29.725 [planetinfo.record]: sun_color = 0.344555, 0.530335, 0.829611, 1 14:13:29.725 [planetinfo.record]: corona_shimmer = 0.321217 14:13:29.726 [planetinfo.record]: station_vector = -0.738 -0.675 0.016 14:13:29.726 [planetinfo.record]: station = coriolis 14:13:34.506 [PLANETINFO OVER]: Done 14:13:34.506 [PLANETINFO LOGGING]: 6 60 14:13:35.511 [planetinfo.record]: seed = 34 74 244 3 14:13:35.512 [planetinfo.record]: coordinates = 74 17 14:13:35.512 [planetinfo.record]: government = 4; 14:13:35.512 [planetinfo.record]: economy = 1; 14:13:35.512 [planetinfo.record]: techlevel = 10; 14:13:35.512 [planetinfo.record]: population = 46; 14:13:35.512 [planetinfo.record]: productivity = 26496; 14:13:35.512 [planetinfo.record]: name = "Geondi"; 14:13:35.512 [planetinfo.record]: inhabitant = "Large Green Bony Feline"; 14:13:35.512 [planetinfo.record]: inhabitants = "Large Green Bony Felines"; 14:13:35.512 [planetinfo.record]: description = "The planet Geondi is scourged by deadly disease."; 14:13:35.533 [planetinfo.record]: air_color = 0.541694, 0.528662, 0.881279, 1; 14:13:35.533 [planetinfo.record]: cloud_alpha = 1.000000; 14:13:35.533 [planetinfo.record]: cloud_color = 0.306196, 0.257584, 0.646484, 1; 14:13:35.533 [planetinfo.record]: cloud_fraction = 0.130000; 14:13:35.533 [planetinfo.record]: land_color = 0.533672, 0.66, 0.642235, 1; 14:13:35.533 [planetinfo.record]: land_fraction = 0.540000; 14:13:35.533 [planetinfo.record]: polar_cloud_color = 0.530722, 0.493551, 0.790918, 1; 14:13:35.533 [planetinfo.record]: polar_land_color = 0.889307, 0.934, 0.927715, 1; 14:13:35.533 [planetinfo.record]: polar_sea_color = 0.943206, 0.944922, 0.725301, 1; 14:13:35.533 [planetinfo.record]: sea_color = 0.546781, 0.550781, 0.0387268, 1; 14:13:35.533 [planetinfo.record]: rotation_speed = 0.004934 14:13:35.533 [planetinfo.record]: planet zpos = 438960.000000 14:13:35.533 [planetinfo.record]: sun_radius = 82533.848267 14:13:35.533 [planetinfo.record]: sun_vector = 0.896 0.443 -0.033 14:13:35.534 [planetinfo.record]: sun_distance = 877920 14:13:35.534 [planetinfo.record]: corona_flare = 0.017276 14:13:35.534 [planetinfo.record]: corona_hues = 0.985260 14:13:35.534 [planetinfo.record]: sun_color = 0.812723, 0.597264, 0.568612, 1 14:13:35.534 [planetinfo.record]: corona_shimmer = 0.455941 14:13:35.535 [planetinfo.record]: station_vector = 0.844 0.499 0.195 14:13:35.535 [planetinfo.record]: station = coriolis 14:13:39.910 [PLANETINFO OVER]: Done 14:13:39.910 [PLANETINFO LOGGING]: 6 61 14:13:40.913 [planetinfo.record]: seed = 154 16 64 29 14:13:40.914 [planetinfo.record]: coordinates = 16 173 14:13:40.914 [planetinfo.record]: government = 3; 14:13:40.914 [planetinfo.record]: economy = 5; 14:13:40.914 [planetinfo.record]: techlevel = 4; 14:13:40.914 [planetinfo.record]: population = 25; 14:13:40.914 [planetinfo.record]: productivity = 7000; 14:13:40.914 [planetinfo.record]: name = "Isanesle"; 14:13:40.914 [planetinfo.record]: inhabitant = "Human Colonial"; 14:13:40.914 [planetinfo.record]: inhabitants = "Human Colonials"; 14:13:40.914 [planetinfo.record]: description = "Isanesle is most famous for its vast oceans and its inhabitants’ eccentric shyness."; 14:13:40.924 [planetinfo.record]: air_color = 0.690518, 0.847292, 0.894938, 1; 14:13:40.924 [planetinfo.record]: cloud_alpha = 1.000000; 14:13:40.924 [planetinfo.record]: cloud_color = 0.645496, 0.6875, 0.628418, 1; 14:13:40.924 [planetinfo.record]: cloud_fraction = 0.410000; 14:13:40.924 [planetinfo.record]: land_color = 0.66, 0.391875, 0.479854, 1; 14:13:40.924 [planetinfo.record]: land_fraction = 0.200000; 14:13:40.924 [planetinfo.record]: polar_cloud_color = 0.778469, 0.809375, 0.765903, 1; 14:13:40.924 [planetinfo.record]: polar_land_color = 0.934, 0.839141, 0.870266, 1; 14:13:40.924 [planetinfo.record]: polar_sea_color = 0.926562, 0.847311, 0.826125, 1; 14:13:40.924 [planetinfo.record]: sea_color = 0.734375, 0.483121, 0.415955, 1; 14:13:40.924 [planetinfo.record]: rotation_speed = 0.003531 14:13:40.924 [planetinfo.record]: planet zpos = 616000.000000 14:13:40.924 [planetinfo.record]: sun_radius = 172857.104492 14:13:40.924 [planetinfo.record]: sun_vector = 0.348 -0.379 -0.858 14:13:40.924 [planetinfo.record]: sun_distance = 1170400 14:13:40.924 [planetinfo.record]: corona_flare = 0.056911 14:13:40.924 [planetinfo.record]: corona_hues = 0.990700 14:13:40.924 [planetinfo.record]: sun_color = 0.702812, 0.711524, 0.729251, 1 14:13:40.924 [planetinfo.record]: corona_shimmer = 0.376518 14:13:40.925 [planetinfo.record]: station_vector = -0.683 0.317 0.658 14:13:40.925 [planetinfo.record]: station = coriolis 14:13:45.913 [PLANETINFO OVER]: Done 14:13:45.913 [PLANETINFO LOGGING]: 6 62 14:13:46.922 [planetinfo.record]: seed = 138 1 212 229 14:13:46.922 [planetinfo.record]: coordinates = 1 9 14:13:46.922 [planetinfo.record]: government = 1; 14:13:46.922 [planetinfo.record]: economy = 3; 14:13:46.922 [planetinfo.record]: techlevel = 6; 14:13:46.922 [planetinfo.record]: population = 29; 14:13:46.922 [planetinfo.record]: productivity = 8120; 14:13:46.922 [planetinfo.record]: name = "Ceerti"; 14:13:46.922 [planetinfo.record]: inhabitant = "Fierce Slimy Frog"; 14:13:46.922 [planetinfo.record]: inhabitants = "Fierce Slimy Frogs"; 14:13:46.922 [planetinfo.record]: description = "This world is a revolting little planet."; 14:13:46.981 [planetinfo.record]: air_color = 0.444645, 0.662165, 0.885182, 1; 14:13:46.981 [planetinfo.record]: cloud_alpha = 1.000000; 14:13:46.981 [planetinfo.record]: cloud_color = 0.0694199, 0.658203, 0.382211, 1; 14:13:46.981 [planetinfo.record]: cloud_fraction = 0.040000; 14:13:46.981 [planetinfo.record]: land_color = 0.564453, 0.458618, 0.498306, 1; 14:13:46.981 [planetinfo.record]: land_fraction = 0.620000; 14:13:46.981 [planetinfo.record]: polar_cloud_color = 0.351055, 0.796191, 0.587534, 1; 14:13:46.981 [planetinfo.record]: polar_land_color = 0.943555, 0.899326, 0.915911, 1; 14:13:46.981 [planetinfo.record]: polar_sea_color = 0.919623, 0.937891, 0.890172, 1; 14:13:46.981 [planetinfo.record]: sea_color = 0.572705, 0.621094, 0.494691, 1; 14:13:46.981 [planetinfo.record]: rotation_speed = 0.000339 14:13:46.981 [planetinfo.record]: planet zpos = 532610.000000 14:13:46.981 [planetinfo.record]: sun_radius = 101614.177094 14:13:46.981 [planetinfo.record]: sun_vector = 0.960 -0.022 -0.280 14:13:46.981 [planetinfo.record]: sun_distance = 860370 14:13:46.981 [planetinfo.record]: corona_flare = 0.043541 14:13:46.981 [planetinfo.record]: corona_hues = 0.776199 14:13:46.981 [planetinfo.record]: sun_color = 0.200388, 0.394391, 0.68201, 1 14:13:46.988 [planetinfo.record]: corona_shimmer = 0.396347 14:13:46.988 [planetinfo.record]: station_vector = 0.658 0.732 0.173 14:13:46.988 [planetinfo.record]: station = coriolis 14:13:51.829 [PLANETINFO OVER]: Done 14:13:51.830 [PLANETINFO LOGGING]: 6 63 14:13:52.833 [planetinfo.record]: seed = 18 174 80 118 14:13:52.833 [planetinfo.record]: coordinates = 174 215 14:13:52.833 [planetinfo.record]: government = 2; 14:13:52.833 [planetinfo.record]: economy = 7; 14:13:52.833 [planetinfo.record]: techlevel = 3; 14:13:52.833 [planetinfo.record]: population = 22; 14:13:52.833 [planetinfo.record]: productivity = 3168; 14:13:52.833 [planetinfo.record]: name = "Vetebe"; 14:13:52.833 [planetinfo.record]: inhabitant = "Human Colonial"; 14:13:52.833 [planetinfo.record]: inhabitants = "Human Colonials"; 14:13:52.833 [planetinfo.record]: description = "Vetebe is a revolting little planet."; 14:13:52.856 [planetinfo.record]: air_color = 0.774833, 0.628997, 0.86632, 1; 14:13:52.856 [planetinfo.record]: cloud_alpha = 1.000000; 14:13:52.856 [planetinfo.record]: cloud_color = 0.601562, 0.467621, 0.486456, 1; 14:13:52.856 [planetinfo.record]: cloud_fraction = 0.380000; 14:13:52.856 [planetinfo.record]: land_color = 0.161691, 0.542969, 0.129379, 1; 14:13:52.856 [planetinfo.record]: land_fraction = 0.600000; 14:13:52.856 [planetinfo.record]: polar_cloud_color = 0.770703, 0.663452, 0.678534, 1; 14:13:52.856 [planetinfo.record]: polar_land_color = 0.779683, 0.945703, 0.765613, 1; 14:13:52.856 [planetinfo.record]: polar_sea_color = 0.891809, 0.864138, 0.910742, 1; 14:13:52.856 [planetinfo.record]: sea_color = 0.818356, 0.709879, 0.892578, 1; 14:13:52.856 [planetinfo.record]: rotation_speed = 0.000158 14:13:52.856 [planetinfo.record]: planet zpos = 678900.000000 14:13:52.856 [planetinfo.record]: sun_radius = 154899.615173 14:13:52.856 [planetinfo.record]: sun_vector = 0.544 0.704 0.456 14:13:52.856 [planetinfo.record]: sun_distance = 859940 14:13:52.856 [planetinfo.record]: corona_flare = 0.089999 14:13:52.857 [planetinfo.record]: corona_hues = 0.861694 14:13:52.857 [planetinfo.record]: sun_color = 0.666, 0.707162, 0.841367, 1 14:13:52.857 [planetinfo.record]: corona_shimmer = 0.403209 14:13:52.857 [planetinfo.record]: station_vector = 0.414 0.145 0.899 14:13:52.857 [planetinfo.record]: station = coriolis 14:13:57.376 [PLANETINFO OVER]: Done 14:13:57.381 [PLANETINFO LOGGING]: 6 64 14:13:58.395 [planetinfo.record]: seed = 18 147 212 175 14:13:58.395 [planetinfo.record]: coordinates = 147 32 14:13:58.395 [planetinfo.record]: government = 2; 14:13:58.395 [planetinfo.record]: economy = 0; 14:13:58.395 [planetinfo.record]: techlevel = 11; 14:13:58.395 [planetinfo.record]: population = 47; 14:13:58.396 [planetinfo.record]: productivity = 22560; 14:13:58.396 [planetinfo.record]: name = "Agebi"; 14:13:58.396 [planetinfo.record]: inhabitant = "Harmless Bony Feline"; 14:13:58.396 [planetinfo.record]: inhabitants = "Harmless Bony Felines"; 14:13:58.396 [planetinfo.record]: description = "The planet Agebi is an unremarkable dump."; 14:13:58.412 [planetinfo.record]: air_color = 0.522873, 0.537358, 0.875426, 1; 14:13:58.412 [planetinfo.record]: cloud_alpha = 1.000000; 14:13:58.412 [planetinfo.record]: cloud_color = 0.245667, 0.287583, 0.628906, 1; 14:13:58.412 [planetinfo.record]: cloud_fraction = 0.250000; 14:13:58.412 [planetinfo.record]: land_color = 0.201961, 0.574219, 0.0829926, 1; 14:13:58.412 [planetinfo.record]: land_fraction = 0.420000; 14:13:58.412 [planetinfo.record]: polar_cloud_color = 0.484792, 0.517409, 0.783008, 1; 14:13:58.412 [planetinfo.record]: polar_land_color = 0.789813, 0.942578, 0.740992, 1; 14:13:58.412 [planetinfo.record]: polar_sea_color = 0.941602, 0.885948, 0.883211, 1; 14:13:58.412 [planetinfo.record]: sea_color = 0.583984, 0.445919, 0.439129, 1; 14:13:58.412 [planetinfo.record]: rotation_speed = 0.000649 14:13:58.412 [planetinfo.record]: planet zpos = 748330.000000 14:13:58.412 [planetinfo.record]: sun_radius = 112056.006470 14:13:58.412 [planetinfo.record]: sun_vector = -0.091 -0.883 -0.460 14:13:58.413 [planetinfo.record]: sun_distance = 1292570 14:13:58.413 [planetinfo.record]: corona_flare = 0.077243 14:13:58.413 [planetinfo.record]: corona_hues = 0.860710 14:13:58.413 [planetinfo.record]: sun_color = 0.826794, 0.798219, 0.748194, 1 14:13:58.413 [planetinfo.record]: corona_shimmer = 0.284060 14:13:58.413 [planetinfo.record]: station_vector = 0.918 0.150 0.367 14:13:58.413 [planetinfo.record]: station = dodecahedron 14:14:03.608 [PLANETINFO OVER]: Done 14:14:03.609 [PLANETINFO LOGGING]: 6 65 14:14:04.613 [planetinfo.record]: seed = 170 185 128 195 14:14:04.613 [planetinfo.record]: coordinates = 185 166 14:14:04.613 [planetinfo.record]: government = 5; 14:14:04.613 [planetinfo.record]: economy = 6; 14:14:04.613 [planetinfo.record]: techlevel = 5; 14:14:04.613 [planetinfo.record]: population = 32; 14:14:04.613 [planetinfo.record]: productivity = 9216; 14:14:04.613 [planetinfo.record]: name = "Gegeso"; 14:14:04.613 [planetinfo.record]: inhabitant = "Large Lizard"; 14:14:04.613 [planetinfo.record]: inhabitants = "Large Lizards"; 14:14:04.613 [planetinfo.record]: description = "The world Gegeso is reasonably noted for its fabulous goat burgers and the Gegesoian evil poet."; 14:14:04.628 [planetinfo.record]: air_color = 0.594262, 0.907945, 0.809683, 1; 14:14:04.628 [planetinfo.record]: cloud_alpha = 1.000000; 14:14:04.628 [planetinfo.record]: cloud_color = 0.726562, 0.431042, 0.408691, 1; 14:14:04.628 [planetinfo.record]: cloud_fraction = 0.110000; 14:14:04.628 [planetinfo.record]: land_color = 0.66, 0.208828, 0.452038, 1; 14:14:04.628 [planetinfo.record]: land_fraction = 0.520000; 14:14:04.628 [planetinfo.record]: polar_cloud_color = 0.826953, 0.616732, 0.600833, 1; 14:14:04.628 [planetinfo.record]: polar_land_color = 0.934, 0.774381, 0.860426, 1; 14:14:04.628 [planetinfo.record]: polar_sea_color = 0.939062, 0.890184, 0.878904, 1; 14:14:04.628 [planetinfo.record]: sea_color = 0.609375, 0.482501, 0.453223, 1; 14:14:04.628 [planetinfo.record]: rotation_speed = 0.001757 14:14:04.629 [planetinfo.record]: planet zpos = 452280.000000 14:14:04.629 [planetinfo.record]: sun_radius = 75988.459778 14:14:04.629 [planetinfo.record]: sun_vector = 0.137 0.981 0.135 14:14:04.629 [planetinfo.record]: sun_distance = 640730 14:14:04.630 [planetinfo.record]: corona_flare = 0.070932 14:14:04.630 [planetinfo.record]: corona_hues = 0.864227 14:14:04.630 [planetinfo.record]: sun_color = 0.799746, 0.814618, 0.816443, 1 14:14:04.630 [planetinfo.record]: corona_shimmer = 0.431692 14:14:04.630 [planetinfo.record]: station_vector = 0.526 -0.762 0.378 14:14:04.630 [planetinfo.record]: station = coriolis 14:14:09.653 [PLANETINFO OVER]: Done 14:14:09.653 [PLANETINFO LOGGING]: 6 66 14:14:10.656 [planetinfo.record]: seed = 186 135 244 75 14:14:10.657 [planetinfo.record]: coordinates = 135 160 14:14:10.657 [planetinfo.record]: government = 7; 14:14:10.657 [planetinfo.record]: economy = 0; 14:14:10.657 [planetinfo.record]: techlevel = 14; 14:14:10.657 [planetinfo.record]: population = 64; 14:14:10.657 [planetinfo.record]: productivity = 56320; 14:14:10.657 [planetinfo.record]: name = "Maraus"; 14:14:10.657 [planetinfo.record]: inhabitant = "Small Yellow Lizard"; 14:14:10.657 [planetinfo.record]: inhabitants = "Small Yellow Lizards"; 14:14:10.657 [planetinfo.record]: description = "This world is very well known for Marausian shrew cutlet and the Marausian edible grub."; 14:14:10.676 [planetinfo.record]: air_color = 0.455993, 0.538882, 0.912498, 1; 14:14:10.676 [planetinfo.record]: cloud_alpha = 1.000000; 14:14:10.676 [planetinfo.record]: cloud_color = 0.0722885, 0.427135, 0.740234, 1; 14:14:10.676 [planetinfo.record]: cloud_fraction = 0.290000; 14:14:10.676 [planetinfo.record]: land_color = 0.66, 0.579353, 0.425391, 1; 14:14:10.676 [planetinfo.record]: land_fraction = 0.430000; 14:14:10.676 [planetinfo.record]: polar_cloud_color = 0.363263, 0.612867, 0.833105, 1; 14:14:10.676 [planetinfo.record]: polar_land_color = 0.934, 0.905468, 0.850998, 1; 14:14:10.676 [planetinfo.record]: polar_sea_color = 0.939919, 0.873706, 0.94375, 1; 14:14:10.676 [planetinfo.record]: sea_color = 0.553368, 0.395508, 0.5625, 1; 14:14:10.676 [planetinfo.record]: rotation_speed = 0.000914 14:14:10.676 [planetinfo.record]: planet zpos = 634370.000000 14:14:10.677 [planetinfo.record]: sun_radius = 162724.859619 14:14:10.677 [planetinfo.record]: sun_vector = 0.417 0.857 -0.302 14:14:10.677 [planetinfo.record]: sun_distance = 1153400 14:14:10.677 [planetinfo.record]: corona_flare = 0.062825 14:14:10.677 [planetinfo.record]: corona_hues = 0.825844 14:14:10.677 [planetinfo.record]: sun_color = 0.830817, 0.724814, 0.648863, 1 14:14:10.677 [planetinfo.record]: corona_shimmer = 0.379501 14:14:10.677 [planetinfo.record]: station_vector = -0.836 -0.086 0.542 14:14:10.677 [planetinfo.record]: station = dodecahedron 14:14:15.540 [PLANETINFO OVER]: Done 14:14:15.541 [PLANETINFO LOGGING]: 6 67 14:14:16.563 [planetinfo.record]: seed = 98 8 208 196 14:14:16.563 [planetinfo.record]: coordinates = 8 72 14:14:16.563 [planetinfo.record]: government = 4; 14:14:16.563 [planetinfo.record]: economy = 0; 14:14:16.563 [planetinfo.record]: techlevel = 9; 14:14:16.563 [planetinfo.record]: population = 41; 14:14:16.563 [planetinfo.record]: productivity = 26240; 14:14:16.563 [planetinfo.record]: name = "Zalaxe"; 14:14:16.563 [planetinfo.record]: inhabitant = "Fierce Slimy Rodent"; 14:14:16.563 [planetinfo.record]: inhabitants = "Fierce Slimy Rodents"; 14:14:16.563 [planetinfo.record]: description = "This world is a tedious place."; 14:14:16.579 [planetinfo.record]: air_color = 0.664891, 0.498944, 0.944367, 1; 14:14:16.579 [planetinfo.record]: cloud_alpha = 1.000000; 14:14:16.579 [planetinfo.record]: cloud_color = 0.835938, 0.153473, 0.745298, 1; 14:14:16.579 [planetinfo.record]: cloud_fraction = 0.290000; 14:14:16.579 [planetinfo.record]: land_color = 0.455361, 0.45375, 0.66, 1; 14:14:16.579 [planetinfo.record]: land_fraction = 0.370000; 14:14:16.579 [planetinfo.record]: polar_cloud_color = 0.876172, 0.429102, 0.816795, 1; 14:14:16.579 [planetinfo.record]: polar_land_color = 0.861601, 0.861031, 0.934, 1; 14:14:16.579 [planetinfo.record]: polar_sea_color = 0.930206, 0.937695, 0.887239, 1; 14:14:16.580 [planetinfo.record]: sea_color = 0.603141, 0.623047, 0.488946, 1; 14:14:16.580 [planetinfo.record]: rotation_speed = 0.003294 14:14:16.580 [planetinfo.record]: planet zpos = 423280.000000 14:14:16.580 [planetinfo.record]: sun_radius = 102214.261475 14:14:16.580 [planetinfo.record]: sun_vector = -0.421 -0.728 0.541 14:14:16.580 [planetinfo.record]: sun_distance = 769600 14:14:16.580 [planetinfo.record]: corona_flare = 0.029742 14:14:16.580 [planetinfo.record]: corona_hues = 0.510918 14:14:16.580 [planetinfo.record]: sun_color = 0.309471, 0.695657, 0.726929, 1 14:14:16.580 [planetinfo.record]: corona_shimmer = 0.364583 14:14:16.580 [planetinfo.record]: station_vector = -0.805 0.484 0.343 14:14:16.580 [planetinfo.record]: station = coriolis 14:14:21.648 [PLANETINFO OVER]: Done 14:14:21.649 [PLANETINFO LOGGING]: 6 68 14:14:22.664 [planetinfo.record]: seed = 130 188 52 180 14:14:22.664 [planetinfo.record]: coordinates = 188 226 14:14:22.664 [planetinfo.record]: government = 0; 14:14:22.664 [planetinfo.record]: economy = 2; 14:14:22.664 [planetinfo.record]: techlevel = 5; 14:14:22.664 [planetinfo.record]: population = 23; 14:14:22.664 [planetinfo.record]: productivity = 5888; 14:14:22.664 [planetinfo.record]: name = "Rabegema"; 14:14:22.664 [planetinfo.record]: inhabitant = "Human Colonial"; 14:14:22.664 [planetinfo.record]: inhabitants = "Human Colonials"; 14:14:22.664 [planetinfo.record]: description = "The world Rabegema is reasonably famous for its inhabitants’ ancient loathing of casinos."; 14:14:22.680 [planetinfo.record]: air_color = 0.781206, 0.63125, 0.86632, 1; 14:14:22.680 [planetinfo.record]: cloud_alpha = 1.000000; 14:14:22.680 [planetinfo.record]: cloud_color = 0.601562, 0.472321, 0.481408, 1; 14:14:22.680 [planetinfo.record]: cloud_fraction = 0.500000; 14:14:22.680 [planetinfo.record]: land_color = 0.66, 0.198032, 0.175313, 1; 14:14:22.680 [planetinfo.record]: land_fraction = 0.400000; 14:14:22.680 [planetinfo.record]: polar_cloud_color = 0.770703, 0.667215, 0.674492, 1; 14:14:22.680 [planetinfo.record]: polar_land_color = 0.934, 0.770561, 0.762523, 1; 14:14:22.680 [planetinfo.record]: polar_sea_color = 0.940039, 0.936929, 0.890283, 1; 14:14:22.680 [planetinfo.record]: sea_color = 0.599609, 0.591675, 0.472661, 1; 14:14:22.680 [planetinfo.record]: rotation_speed = 0.001282 14:14:22.680 [planetinfo.record]: planet zpos = 563920.000000 14:14:22.680 [planetinfo.record]: sun_radius = 67737.713623 14:14:22.680 [planetinfo.record]: sun_vector = 0.496 0.798 -0.343 14:14:22.680 [planetinfo.record]: sun_distance = 765320 14:14:22.680 [planetinfo.record]: corona_flare = 0.066434 14:14:22.680 [planetinfo.record]: corona_hues = 0.913551 14:14:22.680 [planetinfo.record]: sun_color = 0.70246, 0.545499, 0.313082, 1 14:14:22.680 [planetinfo.record]: corona_shimmer = 0.272912 14:14:22.681 [planetinfo.record]: station_vector = -0.521 0.494 0.696 14:14:22.681 [planetinfo.record]: station = coriolis 14:14:27.969 [PLANETINFO OVER]: Done 14:14:27.969 [PLANETINFO LOGGING]: 6 69 14:14:28.974 [planetinfo.record]: seed = 58 203 64 226 14:14:28.974 [planetinfo.record]: coordinates = 203 195 14:14:28.974 [planetinfo.record]: government = 7; 14:14:28.974 [planetinfo.record]: economy = 3; 14:14:28.974 [planetinfo.record]: techlevel = 11; 14:14:28.974 [planetinfo.record]: population = 55; 14:14:28.974 [planetinfo.record]: productivity = 33880; 14:14:28.974 [planetinfo.record]: name = "Xeatrien"; 14:14:28.974 [planetinfo.record]: inhabitant = "Human Colonial"; 14:14:28.974 [planetinfo.record]: inhabitants = "Human Colonials"; 14:14:28.974 [planetinfo.record]: description = "This planet is beset by dreadful earthquakes."; 14:14:28.988 [planetinfo.record]: air_color = 0.501875, 0.482297, 0.919652, 1; 14:14:28.988 [planetinfo.record]: cloud_alpha = 1.000000; 14:14:28.988 [planetinfo.record]: cloud_color = 0.224555, 0.13092, 0.761719, 1; 14:14:28.988 [planetinfo.record]: cloud_fraction = 0.310000; 14:14:28.988 [planetinfo.record]: land_color = 0.450625, 0.564453, 0.0440979, 1; 14:14:28.988 [planetinfo.record]: land_fraction = 0.530000; 14:14:28.988 [planetinfo.record]: polar_cloud_color = 0.471321, 0.406572, 0.842773, 1; 14:14:28.988 [planetinfo.record]: polar_land_color = 0.895985, 0.943555, 0.726095, 1; 14:14:28.988 [planetinfo.record]: polar_sea_color = 0.937891, 0.897522, 0.877807, 1; 14:14:28.988 [planetinfo.record]: sea_color = 0.621094, 0.514161, 0.461938, 1; 14:14:28.988 [planetinfo.record]: rotation_speed = 0.004900 14:14:28.988 [planetinfo.record]: planet zpos = 388410.000000 14:14:28.988 [planetinfo.record]: sun_radius = 67758.497772 14:14:28.988 [planetinfo.record]: sun_vector = 0.416 -0.753 0.509 14:14:28.988 [planetinfo.record]: sun_distance = 635580 14:14:28.988 [planetinfo.record]: corona_flare = 0.073999 14:14:28.988 [planetinfo.record]: corona_hues = 0.543503 14:14:28.988 [planetinfo.record]: sun_color = 0.47958, 0.750103, 0.793842, 1 14:14:28.989 [planetinfo.record]: corona_shimmer = 0.318492 14:14:28.989 [planetinfo.record]: station_vector = -0.860 0.193 0.472 14:14:28.989 [planetinfo.record]: station = dodecahedron 14:14:33.738 [PLANETINFO OVER]: Done 14:14:33.739 [PLANETINFO LOGGING]: 6 70 14:14:34.748 [planetinfo.record]: seed = 106 114 148 2 14:14:34.749 [planetinfo.record]: coordinates = 114 30 14:14:34.749 [planetinfo.record]: government = 5; 14:14:34.749 [planetinfo.record]: economy = 6; 14:14:34.749 [planetinfo.record]: techlevel = 6; 14:14:34.749 [planetinfo.record]: population = 36; 14:14:34.749 [planetinfo.record]: productivity = 10368; 14:14:34.749 [planetinfo.record]: name = "Xebeuson"; 14:14:34.749 [planetinfo.record]: inhabitant = "Large Green Fat Feline"; 14:14:34.749 [planetinfo.record]: inhabitants = "Large Green Fat Felines"; 14:14:34.749 [planetinfo.record]: description = "The world Xebeuson is a boring world."; 14:14:34.750 [planetinfo.record]: air_color = 0.519591, 0.576487, 0.862418, 1; 14:14:34.750 [planetinfo.record]: cloud_alpha = 1.000000; 14:14:34.750 [planetinfo.record]: cloud_color = 0.239624, 0.409262, 0.589844, 1; 14:14:34.750 [planetinfo.record]: cloud_fraction = 0.210000; 14:14:34.750 [planetinfo.record]: land_color = 0.324481, 0.322838, 0.533203, 1; 14:14:34.750 [planetinfo.record]: land_fraction = 0.450000; 14:14:34.750 [planetinfo.record]: polar_cloud_color = 0.481384, 0.618968, 0.76543, 1; 14:14:34.750 [planetinfo.record]: polar_land_color = 0.854035, 0.853306, 0.94668, 1; 14:14:34.750 [planetinfo.record]: polar_sea_color = 0.875251, 0.926172, 0.898722, 1; 14:14:34.750 [planetinfo.record]: sea_color = 0.575917, 0.738281, 0.650757, 1; 14:14:34.750 [planetinfo.record]: rotation_speed = 0.001502 14:14:34.750 [planetinfo.record]: planet zpos = 344200.000000 14:14:34.750 [planetinfo.record]: sun_radius = 92968.033447 14:14:34.750 [planetinfo.record]: sun_vector = 0.809 -0.548 0.215 14:14:34.750 [planetinfo.record]: sun_distance = 619560 14:14:34.751 [planetinfo.record]: corona_flare = 0.081271 14:14:34.751 [planetinfo.record]: corona_hues = 0.934471 14:14:34.751 [planetinfo.record]: sun_color = 0.721259, 0.442988, 0.330515, 1 14:14:34.751 [planetinfo.record]: corona_shimmer = 0.307944 14:14:34.751 [planetinfo.record]: station_vector = 0.921 -0.383 0.073 14:14:34.751 [planetinfo.record]: station = coriolis 14:14:39.366 [PLANETINFO OVER]: Done 14:14:39.366 [PLANETINFO LOGGING]: 6 71 14:14:40.385 [planetinfo.record]: seed = 50 159 208 59 14:14:40.385 [planetinfo.record]: coordinates = 159 8 14:14:40.385 [planetinfo.record]: government = 6; 14:14:40.385 [planetinfo.record]: economy = 0; 14:14:40.385 [planetinfo.record]: techlevel = 13; 14:14:40.386 [planetinfo.record]: population = 59; 14:14:40.386 [planetinfo.record]: productivity = 47200; 14:14:40.386 [planetinfo.record]: name = "Angeriri"; 14:14:40.386 [planetinfo.record]: inhabitant = "Red Lizard"; 14:14:40.386 [planetinfo.record]: inhabitants = "Red Lizards"; 14:14:40.386 [planetinfo.record]: description = "This world is plagued by frequent solar activity."; 14:14:40.389 [planetinfo.record]: air_color = 0.667984, 0.829009, 0.938514, 1; 14:14:40.389 [planetinfo.record]: cloud_alpha = 1.000000; 14:14:40.389 [planetinfo.record]: cloud_color = 0.600983, 0.818359, 0.650232, 1; 14:14:40.389 [planetinfo.record]: cloud_fraction = 0.380000; 14:14:40.389 [planetinfo.record]: land_color = 0.392681, 0.226875, 0.66, 1; 14:14:40.389 [planetinfo.record]: land_fraction = 0.510000; 14:14:40.389 [planetinfo.record]: polar_cloud_color = 0.724117, 0.868262, 0.756775, 1; 14:14:40.389 [planetinfo.record]: polar_land_color = 0.839426, 0.780766, 0.934, 1; 14:14:40.390 [planetinfo.record]: polar_sea_color = 0.933203, 0.871332, 0.861208, 1; 14:14:40.390 [planetinfo.record]: sea_color = 0.667969, 0.490825, 0.461838, 1; 14:14:40.390 [planetinfo.record]: rotation_speed = 0.001379 14:14:40.390 [planetinfo.record]: planet zpos = 637010.000000 14:14:40.390 [planetinfo.record]: sun_radius = 136065.878906 14:14:40.390 [planetinfo.record]: sun_vector = -0.237 -0.221 -0.946 14:14:40.390 [planetinfo.record]: sun_distance = 1042380 14:14:40.390 [planetinfo.record]: corona_flare = 0.054416 14:14:40.390 [planetinfo.record]: corona_hues = 0.752342 14:14:40.390 [planetinfo.record]: sun_color = 0.253271, 0.385455, 0.742398, 1 14:14:40.390 [planetinfo.record]: corona_shimmer = 0.247464 14:14:40.391 [planetinfo.record]: station_vector = -0.182 -0.869 0.460 14:14:40.391 [planetinfo.record]: station = dodecahedron 14:14:45.512 [PLANETINFO OVER]: Done 14:14:45.558 [PLANETINFO LOGGING]: 6 72 14:14:46.519 [planetinfo.record]: seed = 114 222 20 129 14:14:46.519 [planetinfo.record]: coordinates = 222 190 14:14:46.519 [planetinfo.record]: government = 6; 14:14:46.519 [planetinfo.record]: economy = 6; 14:14:46.519 [planetinfo.record]: techlevel = 6; 14:14:46.520 [planetinfo.record]: population = 37; 14:14:46.520 [planetinfo.record]: productivity = 11840; 14:14:46.520 [planetinfo.record]: name = "Leriisis"; 14:14:46.520 [planetinfo.record]: inhabitant = "Human Colonial"; 14:14:46.520 [planetinfo.record]: inhabitants = "Human Colonials"; 14:14:46.520 [planetinfo.record]: description = "The planet Leriisis is reasonably noted for its inhabitants’ exceptional love for food blenders and its fabulous cuisine."; 14:14:46.527 [planetinfo.record]: air_color = 0.740481, 0.605335, 0.872824, 1; 14:14:46.527 [planetinfo.record]: cloud_alpha = 1.000000; 14:14:46.527 [planetinfo.record]: cloud_color = 0.621094, 0.42215, 0.515405, 1; 14:14:46.527 [planetinfo.record]: cloud_fraction = 0.350000; 14:14:46.527 [planetinfo.record]: land_color = 0.66, 0.613594, 0.640785, 1; 14:14:46.528 [planetinfo.record]: land_fraction = 0.710000; 14:14:46.528 [planetinfo.record]: polar_cloud_color = 0.779492, 0.623442, 0.69659, 1; 14:14:46.528 [planetinfo.record]: polar_land_color = 0.934, 0.917582, 0.927202, 1; 14:14:46.528 [planetinfo.record]: polar_sea_color = 0.94668, 0.911662, 0.897959, 1; 14:14:46.529 [planetinfo.record]: sea_color = 0.533203, 0.45431, 0.423438, 1; 14:14:46.529 [planetinfo.record]: rotation_speed = 0.001889 14:14:46.529 [planetinfo.record]: planet zpos = 296460.000000 14:14:46.529 [planetinfo.record]: sun_radius = 74455.276794 14:14:46.529 [planetinfo.record]: sun_vector = 0.377 0.808 -0.453 14:14:46.529 [planetinfo.record]: sun_distance = 658800 14:14:46.529 [planetinfo.record]: corona_flare = 0.040944 14:14:46.529 [planetinfo.record]: corona_hues = 0.870857 14:14:46.529 [planetinfo.record]: sun_color = 0.206915, 0.708248, 0.78692, 1 14:14:46.530 [planetinfo.record]: corona_shimmer = 0.306037 14:14:46.530 [planetinfo.record]: station_vector = -0.304 0.699 0.647 14:14:46.530 [planetinfo.record]: station = coriolis 14:14:51.438 [PLANETINFO OVER]: Done 14:14:51.439 [PLANETINFO LOGGING]: 6 73 14:14:52.441 [planetinfo.record]: seed = 74 29 128 185 14:14:52.441 [planetinfo.record]: coordinates = 29 125 14:14:52.441 [planetinfo.record]: government = 1; 14:14:52.441 [planetinfo.record]: economy = 7; 14:14:52.441 [planetinfo.record]: techlevel = 2; 14:14:52.441 [planetinfo.record]: population = 17; 14:14:52.441 [planetinfo.record]: productivity = 2040; 14:14:52.441 [planetinfo.record]: name = "Orramaor"; 14:14:52.441 [planetinfo.record]: inhabitant = "Harmless Slimy Frog"; 14:14:52.441 [planetinfo.record]: inhabitants = "Harmless Slimy Frogs"; 14:14:52.441 [planetinfo.record]: description = "This world is a tedious little planet."; 14:14:52.452 [planetinfo.record]: air_color = 0.80228, 0.765981, 0.961928, 1; 14:14:52.452 [planetinfo.record]: cloud_alpha = 1.000000; 14:14:52.452 [planetinfo.record]: cloud_color = 0.884197, 0.878258, 0.888672, 1; 14:14:52.452 [planetinfo.record]: cloud_fraction = 0.400000; 14:14:52.452 [planetinfo.record]: land_color = 0.0773438, 0.496128, 0.66, 1; 14:14:52.452 [planetinfo.record]: land_fraction = 0.470000; 14:14:52.452 [planetinfo.record]: polar_cloud_color = 0.89707, 0.893311, 0.899902, 1; 14:14:52.452 [planetinfo.record]: polar_land_color = 0.727863, 0.876024, 0.934, 1; 14:14:52.452 [planetinfo.record]: polar_sea_color = 0.913299, 0.935156, 0.884289, 1; 14:14:52.452 [planetinfo.record]: sea_color = 0.587815, 0.648438, 0.507352, 1; 14:14:52.452 [planetinfo.record]: rotation_speed = 0.002979 14:14:52.459 [planetinfo.record]: planet zpos = 566390.000000 14:14:52.460 [planetinfo.record]: sun_radius = 128475.155334 14:14:52.460 [planetinfo.record]: sun_vector = -0.995 0.101 0.001 14:14:52.460 [planetinfo.record]: sun_distance = 926820 14:14:52.460 [planetinfo.record]: corona_flare = 0.044537 14:14:52.460 [planetinfo.record]: corona_hues = 0.647957 14:14:52.460 [planetinfo.record]: sun_color = 0.672441, 0.713296, 0.795776, 1 14:14:52.460 [planetinfo.record]: corona_shimmer = 0.460820 14:14:52.460 [planetinfo.record]: station_vector = 0.044 -0.627 0.777 14:14:52.460 [planetinfo.record]: station = coriolis 14:14:57.235 [PLANETINFO OVER]: Done 14:14:57.236 [PLANETINFO LOGGING]: 6 74 14:14:58.239 [planetinfo.record]: seed = 154 57 180 185 14:14:58.239 [planetinfo.record]: coordinates = 57 43 14:14:58.239 [planetinfo.record]: government = 3; 14:14:58.239 [planetinfo.record]: economy = 3; 14:14:58.239 [planetinfo.record]: techlevel = 7; 14:14:58.239 [planetinfo.record]: population = 35; 14:14:58.239 [planetinfo.record]: productivity = 13720; 14:14:58.239 [planetinfo.record]: name = "Orrienar"; 14:14:58.240 [planetinfo.record]: inhabitant = "Harmless Horned Lobster"; 14:14:58.240 [planetinfo.record]: inhabitants = "Harmless Horned Lobsters"; 14:14:58.240 [planetinfo.record]: description = "This world is very notable for its inhabitants’ ingrained shyness."; 14:14:58.249 [planetinfo.record]: air_color = 0.499216, 0.996398, 0.914382, 1; 14:14:58.250 [planetinfo.record]: cloud_alpha = 1.000000; 14:14:58.250 [planetinfo.record]: cloud_color = 0.992188, 0.560407, 0.100769, 1; 14:14:58.250 [planetinfo.record]: cloud_fraction = 0.490000; 14:14:58.250 [planetinfo.record]: land_color = 0.276848, 0.398613, 0.541016, 1; 14:14:58.250 [planetinfo.record]: land_fraction = 0.260000; 14:14:58.250 [planetinfo.record]: polar_cloud_color = 0.946484, 0.689052, 0.415011, 1; 14:14:58.250 [planetinfo.record]: polar_land_color = 0.830432, 0.883655, 0.945898, 1; 14:14:58.250 [planetinfo.record]: polar_sea_color = 0.935547, 0.925221, 0.87808, 1; 14:14:58.250 [planetinfo.record]: sea_color = 0.644531, 0.616075, 0.486168, 1; 14:14:58.250 [planetinfo.record]: rotation_speed = 0.002203 14:14:58.250 [planetinfo.record]: planet zpos = 517700.000000 14:14:58.250 [planetinfo.record]: sun_radius = 150909.202423 14:14:58.250 [planetinfo.record]: sun_vector = -0.508 0.364 -0.781 14:14:58.250 [planetinfo.record]: sun_distance = 880090 14:14:58.250 [planetinfo.record]: corona_flare = 0.027232 14:14:58.250 [planetinfo.record]: corona_hues = 0.732422 14:14:58.250 [planetinfo.record]: sun_color = 0.652417, 0.588243, 0.586422, 1 14:14:58.250 [planetinfo.record]: corona_shimmer = 0.254333 14:14:58.250 [planetinfo.record]: station_vector = -0.876 -0.335 0.347 14:14:58.250 [planetinfo.record]: station = coriolis 14:15:02.622 [PLANETINFO OVER]: Done 14:15:02.623 [PLANETINFO LOGGING]: 6 75 14:15:03.644 [planetinfo.record]: seed = 130 234 80 27 14:15:03.644 [planetinfo.record]: coordinates = 234 18 14:15:03.644 [planetinfo.record]: government = 0; 14:15:03.644 [planetinfo.record]: economy = 2; 14:15:03.644 [planetinfo.record]: techlevel = 7; 14:15:03.644 [planetinfo.record]: population = 31; 14:15:03.644 [planetinfo.record]: productivity = 7936; 14:15:03.644 [planetinfo.record]: name = "Antiis"; 14:15:03.644 [planetinfo.record]: inhabitant = "Human Colonial"; 14:15:03.644 [planetinfo.record]: inhabitants = "Human Colonials"; 14:15:03.644 [planetinfo.record]: description = "Antiis is a revolting dump."; 14:15:03.655 [planetinfo.record]: air_color = 0.458645, 0.655288, 0.876727, 1; 14:15:03.655 [planetinfo.record]: cloud_alpha = 1.000000; 14:15:03.655 [planetinfo.record]: cloud_color = 0.106293, 0.632812, 0.423027, 1; 14:15:03.656 [planetinfo.record]: cloud_fraction = 0.360000; 14:15:03.656 [planetinfo.record]: land_color = 0.66, 0.213984, 0.485775, 1; 14:15:03.656 [planetinfo.record]: land_fraction = 0.290000; 14:15:03.656 [planetinfo.record]: polar_cloud_color = 0.376672, 0.784766, 0.622166, 1; 14:15:03.656 [planetinfo.record]: polar_land_color = 0.934, 0.776205, 0.872361, 1; 14:15:03.656 [planetinfo.record]: polar_sea_color = 0.948242, 0.90702, 0.900274, 1; 14:15:03.656 [planetinfo.record]: sea_color = 0.517578, 0.427577, 0.412849, 1; 14:15:03.656 [planetinfo.record]: rotation_speed = 0.004500 14:15:03.656 [planetinfo.record]: planet zpos = 703920.000000 14:15:03.656 [planetinfo.record]: sun_radius = 147525.388794 14:15:03.656 [planetinfo.record]: sun_vector = 0.156 0.832 -0.533 14:15:03.656 [planetinfo.record]: sun_distance = 1231860 14:15:03.656 [planetinfo.record]: corona_flare = 0.043723 14:15:03.656 [planetinfo.record]: corona_hues = 0.698311 14:15:03.656 [planetinfo.record]: sun_color = 0.545365, 0.708052, 0.711038, 1 14:15:03.657 [planetinfo.record]: corona_shimmer = 0.495142 14:15:03.657 [planetinfo.record]: station_vector = -0.607 -0.723 0.331 14:15:03.657 [planetinfo.record]: station = coriolis 14:15:08.531 [PLANETINFO OVER]: Done 14:15:08.532 [PLANETINFO LOGGING]: 6 76 14:15:09.538 [planetinfo.record]: seed = 226 80 116 134 14:15:09.538 [planetinfo.record]: coordinates = 80 29 14:15:09.538 [planetinfo.record]: government = 4; 14:15:09.538 [planetinfo.record]: economy = 5; 14:15:09.538 [planetinfo.record]: techlevel = 4; 14:15:09.538 [planetinfo.record]: population = 26; 14:15:09.538 [planetinfo.record]: productivity = 8320; 14:15:09.538 [planetinfo.record]: name = "Bilain"; 14:15:09.538 [planetinfo.record]: inhabitant = "Human Colonial"; 14:15:09.538 [planetinfo.record]: inhabitants = "Human Colonials"; 14:15:09.538 [planetinfo.record]: description = "This world is fabled for its unusual dense forests."; 14:15:09.568 [planetinfo.record]: air_color = 0.543813, 0.974551, 0.987943, 1; 14:15:09.568 [planetinfo.record]: cloud_alpha = 1.000000; 14:15:09.568 [planetinfo.record]: cloud_color = 0.893154, 0.966797, 0.241699, 1; 14:15:09.568 [planetinfo.record]: cloud_fraction = 0.160000; 14:15:09.568 [planetinfo.record]: land_color = 0.45375, 0.466641, 0.66, 1; 14:15:09.568 [planetinfo.record]: land_fraction = 0.470000; 14:15:09.568 [planetinfo.record]: polar_cloud_color = 0.890543, 0.935059, 0.49675, 1; 14:15:09.568 [planetinfo.record]: polar_land_color = 0.861031, 0.865592, 0.934, 1; 14:15:09.568 [planetinfo.record]: polar_sea_color = 0.933203, 0.86024, 0.856651, 1; 14:15:09.568 [planetinfo.record]: sea_color = 0.667969, 0.459065, 0.448792, 1; 14:15:09.568 [planetinfo.record]: rotation_speed = 0.002495 14:15:09.568 [planetinfo.record]: planet zpos = 576160.000000 14:15:09.568 [planetinfo.record]: sun_radius = 106712.626953 14:15:09.568 [planetinfo.record]: sun_vector = 0.506 -0.272 0.818 14:15:09.569 [planetinfo.record]: sun_distance = 930720 14:15:09.569 [planetinfo.record]: corona_flare = 0.080370 14:15:09.569 [planetinfo.record]: corona_hues = 0.771729 14:15:09.569 [planetinfo.record]: sun_color = 0.822998, 0.770408, 0.401559, 1 14:15:09.569 [planetinfo.record]: corona_shimmer = 0.397284 14:15:09.569 [planetinfo.record]: station_vector = 0.570 0.820 0.037 14:15:09.569 [planetinfo.record]: station = coriolis 14:15:14.118 [PLANETINFO OVER]: Done 14:15:14.119 [PLANETINFO LOGGING]: 6 77 14:15:15.122 [planetinfo.record]: seed = 218 71 64 9 14:15:15.122 [planetinfo.record]: coordinates = 71 204 14:15:15.122 [planetinfo.record]: government = 3; 14:15:15.122 [planetinfo.record]: economy = 4; 14:15:15.122 [planetinfo.record]: techlevel = 8; 14:15:15.122 [planetinfo.record]: population = 40; 14:15:15.122 [planetinfo.record]: productivity = 13440; 14:15:15.123 [planetinfo.record]: name = "Esisrela"; 14:15:15.123 [planetinfo.record]: inhabitant = "Human Colonial"; 14:15:15.123 [planetinfo.record]: inhabitants = "Human Colonials"; 14:15:15.123 [planetinfo.record]: description = "The planet Esisrela is scourged by evil disease."; 14:15:15.136 [planetinfo.record]: air_color = 0.5935, 0.936563, 0.904067, 1; 14:15:15.136 [planetinfo.record]: cloud_alpha = 1.000000; 14:15:15.136 [planetinfo.record]: cloud_color = 0.8125, 0.701416, 0.40625, 1; 14:15:15.136 [planetinfo.record]: cloud_fraction = 0.430000; 14:15:15.136 [planetinfo.record]: land_color = 0.66, 0.153076, 0.0360938, 1; 14:15:15.136 [planetinfo.record]: land_fraction = 0.560000; 14:15:15.136 [planetinfo.record]: polar_cloud_color = 0.865625, 0.791658, 0.595117, 1; 14:15:15.136 [planetinfo.record]: polar_land_color = 0.934, 0.754656, 0.71327, 1; 14:15:15.136 [planetinfo.record]: polar_sea_color = 0.944336, 0.881903, 0.921411, 1; 14:15:15.136 [planetinfo.record]: sea_color = 0.556641, 0.409435, 0.502589, 1; 14:15:15.136 [planetinfo.record]: rotation_speed = 0.001030 14:15:15.136 [planetinfo.record]: planet zpos = 571010.000000 14:15:15.136 [planetinfo.record]: sun_radius = 79752.535553 14:15:15.136 [planetinfo.record]: sun_vector = -0.430 -0.793 0.432 14:15:15.136 [planetinfo.record]: sun_distance = 1038200 14:15:15.136 [planetinfo.record]: corona_flare = 0.031465 14:15:15.136 [planetinfo.record]: corona_hues = 0.652718 14:15:15.136 [planetinfo.record]: sun_color = 0.302135, 0.462414, 0.677914, 1 14:15:15.136 [planetinfo.record]: corona_shimmer = 0.633345 14:15:15.137 [planetinfo.record]: station_vector = 0.088 -0.983 0.162 14:15:15.137 [planetinfo.record]: station = coriolis 14:15:19.740 [PLANETINFO OVER]: Done 14:15:19.741 [PLANETINFO LOGGING]: 6 78 14:15:20.753 [planetinfo.record]: seed = 74 149 84 33 14:15:20.753 [planetinfo.record]: coordinates = 149 110 14:15:20.753 [planetinfo.record]: government = 1; 14:15:20.753 [planetinfo.record]: economy = 6; 14:15:20.753 [planetinfo.record]: techlevel = 3; 14:15:20.753 [planetinfo.record]: population = 20; 14:15:20.753 [planetinfo.record]: productivity = 3200; 14:15:20.753 [planetinfo.record]: name = "Lecean"; 14:15:20.753 [planetinfo.record]: inhabitant = "Human Colonial"; 14:15:20.753 [planetinfo.record]: inhabitants = "Human Colonials"; 14:15:20.753 [planetinfo.record]: description = "This planet is very notable for the Leceanian tree ant and its inhabitants’ ancient loathing of sit coms."; 14:15:20.757 [planetinfo.record]: air_color = 0.782627, 0.736093, 0.987293, 1; 14:15:20.757 [planetinfo.record]: cloud_alpha = 1.000000; 14:15:20.757 [planetinfo.record]: cloud_color = 0.896827, 0.806549, 0.964844, 1; 14:15:20.757 [planetinfo.record]: cloud_fraction = 0.440000; 14:15:20.757 [planetinfo.record]: land_color = 0.511719, 0.43576, 0.492729, 1; 14:15:20.757 [planetinfo.record]: land_fraction = 0.620000; 14:15:20.757 [planetinfo.record]: polar_cloud_color = 0.89302, 0.83839, 0.93418, 1; 14:15:20.757 [planetinfo.record]: polar_land_color = 0.948828, 0.913618, 0.940026, 1; 14:15:20.758 [planetinfo.record]: polar_sea_color = 0.927943, 0.93125, 0.870773, 1; 14:15:20.758 [planetinfo.record]: sea_color = 0.677733, 0.6875, 0.508911, 1; 14:15:20.758 [planetinfo.record]: rotation_speed = 0.002772 14:15:20.758 [planetinfo.record]: planet zpos = 386520.000000 14:15:20.758 [planetinfo.record]: sun_radius = 57383.892212 14:15:20.758 [planetinfo.record]: sun_vector = 0.753 0.130 -0.645 14:15:20.758 [planetinfo.record]: sun_distance = 547570 14:15:20.758 [planetinfo.record]: corona_flare = 0.050674 14:15:20.758 [planetinfo.record]: corona_hues = 0.876442 14:15:20.758 [planetinfo.record]: sun_color = 0.672922, 0.582953, 0.306925, 1 14:15:20.758 [planetinfo.record]: corona_shimmer = 0.270131 14:15:20.758 [planetinfo.record]: station_vector = 0.376 -0.924 0.068 14:15:20.758 [planetinfo.record]: station = coriolis 14:15:25.271 [PLANETINFO OVER]: Done 14:15:25.272 [PLANETINFO LOGGING]: 6 79 14:15:26.275 [planetinfo.record]: seed = 82 34 80 35 14:15:26.275 [planetinfo.record]: coordinates = 34 219 14:15:26.275 [planetinfo.record]: government = 2; 14:15:26.275 [planetinfo.record]: economy = 3; 14:15:26.275 [planetinfo.record]: techlevel = 7; 14:15:26.275 [planetinfo.record]: population = 34; 14:15:26.275 [planetinfo.record]: productivity = 11424; 14:15:26.275 [planetinfo.record]: name = "Gelesoma"; 14:15:26.275 [planetinfo.record]: inhabitant = "Human Colonial"; 14:15:26.275 [planetinfo.record]: inhabitants = "Human Colonials"; 14:15:26.275 [planetinfo.record]: description = "The planet Gelesoma is cursed by dreadful civil war."; 14:15:26.296 [planetinfo.record]: air_color = 0.762692, 0.647679, 0.842256, 1; 14:15:26.296 [planetinfo.record]: cloud_alpha = 1.000000; 14:15:26.296 [planetinfo.record]: cloud_color = 0.529297, 0.479675, 0.490142, 1; 14:15:26.296 [planetinfo.record]: cloud_fraction = 0.410000; 14:15:26.296 [planetinfo.record]: land_color = 0.595043, 0.66, 0.466641, 1; 14:15:26.296 [planetinfo.record]: land_fraction = 0.450000; 14:15:26.296 [planetinfo.record]: polar_cloud_color = 0.738184, 0.694931, 0.704054, 1; 14:15:26.296 [planetinfo.record]: polar_land_color = 0.911019, 0.934, 0.865592, 1; 14:15:26.296 [planetinfo.record]: polar_sea_color = 0.934151, 0.863058, 0.942187, 1; 14:15:26.297 [planetinfo.record]: sea_color = 0.5584, 0.383911, 0.578125, 1; 14:15:26.297 [planetinfo.record]: rotation_speed = 0.002599 14:15:26.297 [planetinfo.record]: planet zpos = 397980.000000 14:15:26.297 [planetinfo.record]: sun_radius = 83996.383667 14:15:26.297 [planetinfo.record]: sun_vector = -0.575 -0.713 0.401 14:15:26.297 [planetinfo.record]: sun_distance = 723600 14:15:26.297 [planetinfo.record]: corona_flare = 0.023056 14:15:26.297 [planetinfo.record]: corona_hues = 0.559059 14:15:26.297 [planetinfo.record]: sun_color = 0.766632, 0.682744, 0.50908, 1 14:15:26.297 [planetinfo.record]: corona_shimmer = 0.314171 14:15:26.297 [planetinfo.record]: station_vector = 0.900 -0.432 0.058 14:15:26.297 [planetinfo.record]: station = coriolis 14:15:30.833 [PLANETINFO OVER]: Done 14:15:30.833 [PLANETINFO LOGGING]: 6 80 14:15:31.839 [planetinfo.record]: seed = 210 171 84 52 14:15:31.839 [planetinfo.record]: coordinates = 171 103 14:15:31.839 [planetinfo.record]: government = 2; 14:15:31.839 [planetinfo.record]: economy = 7; 14:15:31.839 [planetinfo.record]: techlevel = 4; 14:15:31.839 [planetinfo.record]: population = 26; 14:15:31.839 [planetinfo.record]: productivity = 3744; 14:15:31.839 [planetinfo.record]: name = "Rasoso"; 14:15:31.839 [planetinfo.record]: inhabitant = "Human Colonial"; 14:15:31.839 [planetinfo.record]: inhabitants = "Human Colonials"; 14:15:31.839 [planetinfo.record]: description = "This world is ravaged by occasional solar activity."; 14:15:31.856 [planetinfo.record]: air_color = 0.516168, 0.643775, 0.835102, 1; 14:15:31.856 [planetinfo.record]: cloud_alpha = 1.000000; 14:15:31.856 [planetinfo.record]: cloud_color = 0.230103, 0.507812, 0.455742, 1; 14:15:31.856 [planetinfo.record]: cloud_fraction = 0.220000; 14:15:31.856 [planetinfo.record]: land_color = 0.66, 0.582455, 0.461484, 1; 14:15:31.856 [planetinfo.record]: land_fraction = 0.550000; 14:15:31.856 [planetinfo.record]: polar_cloud_color = 0.479511, 0.728516, 0.681827, 1; 14:15:31.856 [planetinfo.record]: polar_land_color = 0.934, 0.906565, 0.863768, 1; 14:15:31.856 [planetinfo.record]: polar_sea_color = 0.86869, 0.855812, 0.908984, 1; 14:15:31.856 [planetinfo.record]: sea_color = 0.748771, 0.697194, 0.910156, 1; 14:15:31.856 [planetinfo.record]: rotation_speed = 0.003096 14:15:31.856 [planetinfo.record]: planet zpos = 441210.000000 14:15:31.856 [planetinfo.record]: sun_radius = 119268.739929 14:15:31.856 [planetinfo.record]: sun_vector = 0.539 0.490 -0.686 14:15:31.856 [planetinfo.record]: sun_distance = 802200 14:15:31.856 [planetinfo.record]: corona_flare = 0.026619 14:15:31.856 [planetinfo.record]: corona_hues = 0.560776 14:15:31.856 [planetinfo.record]: sun_color = 0.802319, 0.344379, 0.288211, 1 14:15:31.857 [planetinfo.record]: corona_shimmer = 0.520011 14:15:31.857 [planetinfo.record]: station_vector = -0.132 0.991 0.029 14:15:31.857 [planetinfo.record]: station = coriolis 14:15:36.221 [PLANETINFO OVER]: Done 14:15:36.221 [PLANETINFO LOGGING]: 6 81 14:15:37.224 [planetinfo.record]: seed = 234 162 128 17 14:15:37.224 [planetinfo.record]: coordinates = 162 39 14:15:37.224 [planetinfo.record]: government = 5; 14:15:37.224 [planetinfo.record]: economy = 7; 14:15:37.225 [planetinfo.record]: techlevel = 5; 14:15:37.225 [planetinfo.record]: population = 33; 14:15:37.225 [planetinfo.record]: productivity = 7128; 14:15:37.225 [planetinfo.record]: name = "Ataneris"; 14:15:37.225 [planetinfo.record]: inhabitant = "Green Furry Feline"; 14:15:37.225 [planetinfo.record]: inhabitants = "Green Furry Felines"; 14:15:37.225 [planetinfo.record]: description = "This planet is very notable for its inhabitants’ weird shyness and the Atanerisian edible moth."; 14:15:37.237 [planetinfo.record]: air_color = 0.605685, 0.902092, 0.820809, 1; 14:15:37.237 [planetinfo.record]: cloud_alpha = 1.000000; 14:15:37.237 [planetinfo.record]: cloud_color = 0.708984, 0.486215, 0.434807, 1; 14:15:37.237 [planetinfo.record]: cloud_fraction = 0.420000; 14:15:37.237 [planetinfo.record]: land_color = 0.242908, 0.229453, 0.66, 1; 14:15:37.237 [planetinfo.record]: land_fraction = 0.600000; 14:15:37.238 [planetinfo.record]: polar_cloud_color = 0.819043, 0.658199, 0.621081, 1; 14:15:37.238 [planetinfo.record]: polar_land_color = 0.786438, 0.781678, 0.934, 1; 14:15:37.238 [planetinfo.record]: polar_sea_color = 0.87989, 0.927344, 0.908436, 1; 14:15:37.238 [planetinfo.record]: sea_color = 0.577844, 0.726562, 0.667308, 1; 14:15:37.238 [planetinfo.record]: rotation_speed = 0.004172 14:15:37.238 [planetinfo.record]: planet zpos = 355740.000000 14:15:37.238 [planetinfo.record]: sun_radius = 94138.633118 14:15:37.238 [planetinfo.record]: sun_vector = -0.124 0.723 0.680 14:15:37.238 [planetinfo.record]: sun_distance = 614460 14:15:37.238 [planetinfo.record]: corona_flare = 0.082465 14:15:37.238 [planetinfo.record]: corona_hues = 0.783173 14:15:37.238 [planetinfo.record]: sun_color = 0.240919, 0.31502, 0.838339, 1 14:15:37.238 [planetinfo.record]: corona_shimmer = 0.451387 14:15:37.238 [planetinfo.record]: station_vector = 0.477 -0.222 0.851 14:15:37.239 [planetinfo.record]: station = coriolis 14:15:41.461 [PLANETINFO OVER]: Done 14:15:41.461 [PLANETINFO LOGGING]: 6 82 14:15:42.464 [planetinfo.record]: seed = 122 125 116 233 14:15:42.464 [planetinfo.record]: coordinates = 125 144 14:15:42.464 [planetinfo.record]: government = 7; 14:15:42.464 [planetinfo.record]: economy = 0; 14:15:42.464 [planetinfo.record]: techlevel = 12; 14:15:42.464 [planetinfo.record]: population = 56; 14:15:42.464 [planetinfo.record]: productivity = 49280; 14:15:42.464 [planetinfo.record]: name = "Estiri"; 14:15:42.464 [planetinfo.record]: inhabitant = "Human Colonial"; 14:15:42.464 [planetinfo.record]: inhabitants = "Human Colonials"; 14:15:42.464 [planetinfo.record]: description = "The world Estiri is a boring world."; 14:15:42.472 [planetinfo.record]: air_color = 0.746883, 0.756602, 0.945668, 1; 14:15:42.472 [planetinfo.record]: cloud_alpha = 1.000000; 14:15:42.472 [planetinfo.record]: cloud_color = 0.813599, 0.817084, 0.839844, 1; 14:15:42.472 [planetinfo.record]: cloud_fraction = 0.430000; 14:15:42.472 [planetinfo.record]: land_color = 0.376406, 0.66, 0.653353, 1; 14:15:42.472 [planetinfo.record]: land_fraction = 0.310000; 14:15:42.472 [planetinfo.record]: polar_cloud_color = 0.860783, 0.86306, 0.87793, 1; 14:15:42.472 [planetinfo.record]: polar_land_color = 0.833668, 0.934, 0.931648, 1; 14:15:42.472 [planetinfo.record]: polar_sea_color = 0.936719, 0.906524, 0.877259, 1; 14:15:42.473 [planetinfo.record]: sea_color = 0.632812, 0.55122, 0.472137, 1; 14:15:42.473 [planetinfo.record]: rotation_speed = 0.003484 14:15:42.473 [planetinfo.record]: planet zpos = 629400.000000 14:15:42.473 [planetinfo.record]: sun_radius = 155426.822662 14:15:42.473 [planetinfo.record]: sun_vector = 0.857 0.495 -0.146 14:15:42.473 [planetinfo.record]: sun_distance = 1153900 14:15:42.473 [planetinfo.record]: corona_flare = 0.090123 14:15:42.473 [planetinfo.record]: corona_hues = 0.656181 14:15:42.473 [planetinfo.record]: sun_color = 0.329055, 0.783467, 0.832333, 1 14:15:42.473 [planetinfo.record]: corona_shimmer = 1.718301 14:15:42.473 [planetinfo.record]: station_vector = 0.226 -0.967 0.114 14:15:42.473 [planetinfo.record]: station = dodecahedron 14:15:47.528 [PLANETINFO OVER]: Done 14:15:47.529 [PLANETINFO LOGGING]: 6 83 14:15:48.542 [planetinfo.record]: seed = 162 62 208 147 14:15:48.543 [planetinfo.record]: coordinates = 62 94 14:15:48.543 [planetinfo.record]: government = 4; 14:15:48.543 [planetinfo.record]: economy = 6; 14:15:48.543 [planetinfo.record]: techlevel = 5; 14:15:48.543 [planetinfo.record]: population = 31; 14:15:48.543 [planetinfo.record]: productivity = 7936; 14:15:48.543 [planetinfo.record]: name = "Beerxe"; 14:15:48.543 [planetinfo.record]: inhabitant = "Black Slimy Lobster"; 14:15:48.543 [planetinfo.record]: inhabitants = "Black Slimy Lobsters"; 14:15:48.543 [planetinfo.record]: description = "This world is noted for its unusual sit coms."; 14:15:48.560 [planetinfo.record]: air_color = 0.663921, 0.848881, 0.929408, 1; 14:15:48.560 [planetinfo.record]: cloud_alpha = 1.000000; 14:15:48.560 [planetinfo.record]: cloud_color = 0.603014, 0.791016, 0.587082, 1; 14:15:48.560 [planetinfo.record]: cloud_fraction = 0.210000; 14:15:48.560 [planetinfo.record]: land_color = 0.55752, 0.66, 0.53625, 1; 14:15:48.560 [planetinfo.record]: land_fraction = 0.420000; 14:15:48.560 [planetinfo.record]: polar_cloud_color = 0.728809, 0.855957, 0.718034, 1; 14:15:48.560 [planetinfo.record]: polar_land_color = 0.897744, 0.934, 0.890219, 1; 14:15:48.560 [planetinfo.record]: polar_sea_color = 0.95, 0.850363, 0.733838, 1; 14:15:48.560 [planetinfo.record]: sea_color = 0.5, 0.290237, 0.0449219, 1; 14:15:48.560 [planetinfo.record]: rotation_speed = 0.000780 14:15:48.560 [planetinfo.record]: planet zpos = 437520.000000 14:15:48.560 [planetinfo.record]: sun_radius = 87809.205627 14:15:48.560 [planetinfo.record]: sun_vector = -0.238 -0.787 -0.570 14:15:48.560 [planetinfo.record]: sun_distance = 583360 14:15:48.560 [planetinfo.record]: corona_flare = 0.073201 14:15:48.560 [planetinfo.record]: corona_hues = 0.550720 14:15:48.560 [planetinfo.record]: sun_color = 0.68292, 0.684015, 0.714883, 1 14:15:48.560 [planetinfo.record]: corona_shimmer = 0.402096 14:15:48.561 [planetinfo.record]: station_vector = -0.106 0.978 0.182 14:15:48.561 [planetinfo.record]: station = coriolis 14:15:54.021 [PLANETINFO OVER]: Done 14:15:54.022 [PLANETINFO LOGGING]: 6 84 14:15:55.024 [planetinfo.record]: seed = 66 199 180 250 14:15:55.024 [planetinfo.record]: coordinates = 199 2 14:15:55.024 [planetinfo.record]: government = 0; 14:15:55.024 [planetinfo.record]: economy = 2; 14:15:55.024 [planetinfo.record]: techlevel = 8; 14:15:55.024 [planetinfo.record]: population = 35; 14:15:55.024 [planetinfo.record]: productivity = 8960; 14:15:55.024 [planetinfo.record]: name = "Quzabibi"; 14:15:55.024 [planetinfo.record]: inhabitant = "Furry Insect"; 14:15:55.024 [planetinfo.record]: inhabitants = "Furry Insects"; 14:15:55.024 [planetinfo.record]: description = "This planet is plagued by frequent solar activity."; 14:15:55.040 [planetinfo.record]: air_color = 0.6345, 0.520667, 0.917701, 1; 14:15:55.040 [planetinfo.record]: cloud_alpha = 1.000000; 14:15:55.040 [planetinfo.record]: cloud_color = 0.689795, 0.227348, 0.755859, 1; 14:15:55.040 [planetinfo.record]: cloud_fraction = 0.480000; 14:15:55.040 [planetinfo.record]: land_color = 0.535156, 0.493347, 0.495307, 1; 14:15:55.040 [planetinfo.record]: land_fraction = 0.260000; 14:15:55.040 [planetinfo.record]: polar_cloud_color = 0.794243, 0.472987, 0.840137, 1; 14:15:55.040 [planetinfo.record]: polar_land_color = 0.946484, 0.927998, 0.928865, 1; 14:15:55.040 [planetinfo.record]: polar_sea_color = 0.931445, 0.92468, 0.869592, 1; 14:15:55.040 [planetinfo.record]: sea_color = 0.685547, 0.66563, 0.503448, 1; 14:15:55.040 [planetinfo.record]: rotation_speed = 0.003816 14:15:55.040 [planetinfo.record]: planet zpos = 669000.000000 14:15:55.040 [planetinfo.record]: sun_radius = 104378.128052 14:15:55.040 [planetinfo.record]: sun_vector = -0.794 -0.339 0.504 14:15:55.042 [planetinfo.record]: sun_distance = 1115000 14:15:55.042 [planetinfo.record]: corona_flare = 0.037256 14:15:55.042 [planetinfo.record]: corona_hues = 0.857002 14:15:55.042 [planetinfo.record]: sun_color = 0.826306, 0.711908, 0.558777, 1 14:15:55.043 [planetinfo.record]: corona_shimmer = 0.396266 14:15:55.043 [planetinfo.record]: station_vector = 0.936 0.152 0.318 14:15:55.043 [planetinfo.record]: station = coriolis 14:15:59.403 [PLANETINFO OVER]: Done 14:15:59.404 [PLANETINFO LOGGING]: 6 85 14:16:00.409 [planetinfo.record]: seed = 122 70 64 146 14:16:00.409 [planetinfo.record]: coordinates = 70 134 14:16:00.409 [planetinfo.record]: government = 7; 14:16:00.409 [planetinfo.record]: economy = 6; 14:16:00.409 [planetinfo.record]: techlevel = 7; 14:16:00.409 [planetinfo.record]: population = 42; 14:16:00.409 [planetinfo.record]: productivity = 14784; 14:16:00.410 [planetinfo.record]: name = "Enonedar"; 14:16:00.410 [planetinfo.record]: inhabitant = "Human Colonial"; 14:16:00.410 [planetinfo.record]: inhabitants = "Human Colonials"; 14:16:00.410 [planetinfo.record]: description = "This world is fabled for its ancient mountains."; 14:16:00.425 [planetinfo.record]: air_color = 0.601666, 0.906645, 0.823011, 1; 14:16:00.425 [planetinfo.record]: cloud_alpha = 1.000000; 14:16:00.425 [planetinfo.record]: cloud_color = 0.722656, 0.48183, 0.426254, 1; 14:16:00.425 [planetinfo.record]: cloud_fraction = 0.600000; 14:16:00.425 [planetinfo.record]: land_color = 0.12375, 0.609727, 0.66, 1; 14:16:00.425 [planetinfo.record]: land_fraction = 0.490000; 14:16:00.425 [planetinfo.record]: polar_cloud_color = 0.825195, 0.653322, 0.613658, 1; 14:16:00.425 [planetinfo.record]: polar_land_color = 0.744281, 0.916214, 0.934, 1; 14:16:00.426 [planetinfo.record]: polar_sea_color = 0.937891, 0.922814, 0.882753, 1; 14:16:00.426 [planetinfo.record]: sea_color = 0.621094, 0.581157, 0.47504, 1; 14:16:00.426 [planetinfo.record]: rotation_speed = 0.002342 14:16:00.426 [planetinfo.record]: planet zpos = 407760.000000 14:16:00.426 [planetinfo.record]: sun_radius = 95539.714355 14:16:00.426 [planetinfo.record]: sun_vector = 0.062 0.815 0.576 14:16:00.426 [planetinfo.record]: sun_distance = 679600 14:16:00.426 [planetinfo.record]: corona_flare = 0.042531 14:16:00.426 [planetinfo.record]: corona_hues = 0.514023 14:16:00.427 [planetinfo.record]: sun_color = 0.388988, 0.544312, 0.689789, 1 14:16:00.497 [planetinfo.record]: corona_shimmer = 0.505937 14:16:00.498 [planetinfo.record]: station_vector = 0.320 -0.838 0.442 14:16:00.498 [planetinfo.record]: station = coriolis 14:16:04.555 [PLANETINFO OVER]: Done 14:16:04.556 [PLANETINFO LOGGING]: 6 86 14:16:05.559 [planetinfo.record]: seed = 42 42 20 194 14:16:05.559 [planetinfo.record]: coordinates = 42 56 14:16:05.559 [planetinfo.record]: government = 5; 14:16:05.559 [planetinfo.record]: economy = 0; 14:16:05.559 [planetinfo.record]: techlevel = 12; 14:16:05.559 [planetinfo.record]: population = 54; 14:16:05.559 [planetinfo.record]: productivity = 38880; 14:16:05.559 [planetinfo.record]: name = "Xezaerti"; 14:16:05.559 [planetinfo.record]: inhabitant = "Human Colonial"; 14:16:05.559 [planetinfo.record]: inhabitants = "Human Colonials"; 14:16:05.559 [planetinfo.record]: description = "This world is very fabled for its weird volcanoes."; 14:16:05.561 [planetinfo.record]: air_color = 0.50501, 0.561095, 0.878027, 1; 14:16:05.561 [planetinfo.record]: cloud_alpha = 1.000000; 14:16:05.561 [planetinfo.record]: cloud_color = 0.206436, 0.394685, 0.636719, 1; 14:16:05.561 [planetinfo.record]: cloud_fraction = 0.350000; 14:16:05.561 [planetinfo.record]: land_color = 0.33995, 0.0747656, 0.66, 1; 14:16:05.561 [planetinfo.record]: land_fraction = 0.680000; 14:16:05.561 [planetinfo.record]: polar_cloud_color = 0.454325, 0.599662, 0.786523, 1; 14:16:05.561 [planetinfo.record]: polar_land_color = 0.82077, 0.726951, 0.934, 1; 14:16:05.561 [planetinfo.record]: polar_sea_color = 0.933633, 0.935352, 0.880363, 1; 14:16:05.561 [planetinfo.record]: sea_color = 0.641734, 0.646484, 0.49446, 1; 14:16:05.561 [planetinfo.record]: rotation_speed = 0.004079 14:16:05.561 [planetinfo.record]: planet zpos = 438100.000000 14:16:05.561 [planetinfo.record]: sun_radius = 62566.320801 14:16:05.561 [planetinfo.record]: sun_vector = -0.117 -0.870 0.479 14:16:05.561 [planetinfo.record]: sun_distance = 674000 14:16:05.562 [planetinfo.record]: corona_flare = 0.038734 14:16:05.562 [planetinfo.record]: corona_hues = 0.875320 14:16:05.562 [planetinfo.record]: sun_color = 0.677206, 0.65029, 0.539179, 1 14:16:05.562 [planetinfo.record]: corona_shimmer = 0.305310 14:16:05.562 [planetinfo.record]: station_vector = 0.778 -0.182 0.601 14:16:05.562 [planetinfo.record]: station = dodecahedron 14:16:10.204 [PLANETINFO OVER]: Done 14:16:10.205 [PLANETINFO LOGGING]: 6 87 14:16:11.207 [planetinfo.record]: seed = 114 247 208 44 14:16:11.207 [planetinfo.record]: coordinates = 247 16 14:16:11.207 [planetinfo.record]: government = 6; 14:16:11.207 [planetinfo.record]: economy = 0; 14:16:11.208 [planetinfo.record]: techlevel = 13; 14:16:11.208 [planetinfo.record]: population = 59; 14:16:11.208 [planetinfo.record]: productivity = 47200; 14:16:11.208 [planetinfo.record]: name = "Inlaoran"; 14:16:11.208 [planetinfo.record]: inhabitant = "Red Insect"; 14:16:11.208 [planetinfo.record]: inhabitants = "Red Insects"; 14:16:11.208 [planetinfo.record]: description = "Inlaoran is mildly notable for Inlaoranian evil juice."; 14:16:11.240 [planetinfo.record]: air_color = 0.769003, 0.734769, 0.98209, 1; 14:16:11.240 [planetinfo.record]: cloud_alpha = 1.000000; 14:16:11.240 [planetinfo.record]: cloud_color = 0.864633, 0.800903, 0.949219, 1; 14:16:11.240 [planetinfo.record]: cloud_fraction = 0.290000; 14:16:11.240 [planetinfo.record]: land_color = 0.210571, 0.515556, 0.585938, 1; 14:16:11.240 [planetinfo.record]: land_fraction = 0.600000; 14:16:11.240 [planetinfo.record]: polar_cloud_color = 0.875511, 0.836607, 0.927148, 1; 14:16:11.240 [planetinfo.record]: polar_land_color = 0.790634, 0.913136, 0.941406, 1; 14:16:11.240 [planetinfo.record]: polar_sea_color = 0.927701, 0.9375, 0.887329, 1; 14:16:11.241 [planetinfo.record]: sea_color = 0.598869, 0.625, 0.491211, 1; 14:16:11.241 [planetinfo.record]: rotation_speed = 0.003886 14:16:11.241 [planetinfo.record]: planet zpos = 736200.000000 14:16:11.241 [planetinfo.record]: sun_radius = 187461.245728 14:16:11.241 [planetinfo.record]: sun_vector = -0.827 -0.531 -0.183 14:16:11.241 [planetinfo.record]: sun_distance = 1349700 14:16:11.241 [planetinfo.record]: corona_flare = 0.019420 14:16:11.241 [planetinfo.record]: corona_hues = 0.845306 14:16:11.241 [planetinfo.record]: sun_color = 0.780234, 0.343881, 0.300984, 1 14:16:11.242 [planetinfo.record]: corona_shimmer = 1.767994 14:16:11.242 [planetinfo.record]: station_vector = 0.857 -0.455 0.243 14:16:11.242 [planetinfo.record]: station = icosahedron 14:16:15.597 [PLANETINFO OVER]: Done 14:16:15.597 [PLANETINFO LOGGING]: 6 88 14:16:16.600 [planetinfo.record]: seed = 50 187 148 73 14:16:16.600 [planetinfo.record]: coordinates = 187 89 14:16:16.600 [planetinfo.record]: government = 6; 14:16:16.600 [planetinfo.record]: economy = 1; 14:16:16.600 [planetinfo.record]: techlevel = 12; 14:16:16.600 [planetinfo.record]: population = 56; 14:16:16.600 [planetinfo.record]: productivity = 40320; 14:16:16.600 [planetinfo.record]: name = "Esrixear"; 14:16:16.600 [planetinfo.record]: inhabitant = "Small Yellow Horned Lobster"; 14:16:16.600 [planetinfo.record]: inhabitants = "Small Yellow Horned Lobsters"; 14:16:16.600 [planetinfo.record]: description = "Esrixear is reasonably well known for the Esrixearian spotted wolf but beset by deadly solar activity."; 14:16:16.625 [planetinfo.record]: air_color = 0.754642, 0.757972, 0.943066, 1; 14:16:16.625 [planetinfo.record]: cloud_alpha = 1.000000; 14:16:16.625 [planetinfo.record]: cloud_color = 0.832031, 0.832031, 0.832031, 1; 14:16:16.625 [planetinfo.record]: cloud_fraction = 0.460000; 14:16:16.625 [planetinfo.record]: land_color = 0.569766, 0.66, 0.640966, 1; 14:16:16.625 [planetinfo.record]: land_fraction = 0.280000; 14:16:16.626 [planetinfo.record]: polar_cloud_color = 0.874414, 0.874414, 0.874414, 1; 14:16:16.626 [planetinfo.record]: polar_land_color = 0.902076, 0.934, 0.927266, 1; 14:16:16.626 [planetinfo.record]: polar_sea_color = 0.915178, 0.934961, 0.883282, 1; 14:16:16.626 [planetinfo.record]: sea_color = 0.595343, 0.650391, 0.506593, 1; 14:16:16.626 [planetinfo.record]: rotation_speed = 0.004359 14:16:16.626 [planetinfo.record]: planet zpos = 477630.000000 14:16:16.626 [planetinfo.record]: sun_radius = 136498.799744 14:16:16.626 [planetinfo.record]: sun_vector = -0.228 -0.796 0.560 14:16:16.626 [planetinfo.record]: sun_distance = 1008330 14:16:16.626 [planetinfo.record]: corona_flare = 0.072038 14:16:16.626 [planetinfo.record]: corona_hues = 0.550072 14:16:16.626 [planetinfo.record]: sun_color = 0.614158, 0.638098, 0.781479, 1 14:16:16.626 [planetinfo.record]: corona_shimmer = 0.342278 14:16:16.627 [planetinfo.record]: station_vector = 0.146 -0.958 0.248 14:16:16.627 [planetinfo.record]: station = dodecahedron 14:16:21.263 [PLANETINFO OVER]: Done 14:16:21.263 [PLANETINFO LOGGING]: 6 89 14:16:22.266 [planetinfo.record]: seed = 138 10 128 203 14:16:22.266 [planetinfo.record]: coordinates = 10 98 14:16:22.266 [planetinfo.record]: government = 1; 14:16:22.266 [planetinfo.record]: economy = 2; 14:16:22.266 [planetinfo.record]: techlevel = 8; 14:16:22.266 [planetinfo.record]: population = 36; 14:16:22.266 [planetinfo.record]: productivity = 11520; 14:16:22.266 [planetinfo.record]: name = "Maedrebe"; 14:16:22.266 [planetinfo.record]: inhabitant = "Small Slimy Lobster"; 14:16:22.266 [planetinfo.record]: inhabitants = "Small Slimy Lobsters"; 14:16:22.266 [planetinfo.record]: description = "This world is a tedious place."; 14:16:22.269 [planetinfo.record]: air_color = 0.638554, 0.885832, 0.84118, 1; 14:16:22.269 [planetinfo.record]: cloud_alpha = 1.000000; 14:16:22.269 [planetinfo.record]: cloud_color = 0.660156, 0.576589, 0.502853, 1; 14:16:22.269 [planetinfo.record]: cloud_fraction = 0.390000; 14:16:22.269 [planetinfo.record]: land_color = 0.66, 0.242344, 0.594741, 1; 14:16:22.269 [planetinfo.record]: land_fraction = 0.450000; 14:16:22.269 [planetinfo.record]: polar_cloud_color = 0.79707, 0.734009, 0.678366, 1; 14:16:22.269 [planetinfo.record]: polar_land_color = 0.934, 0.786238, 0.910912, 1; 14:16:22.270 [planetinfo.record]: polar_sea_color = 0.934375, 0.928008, 0.876159, 1; 14:16:22.270 [planetinfo.record]: sea_color = 0.65625, 0.638362, 0.4927, 1; 14:16:22.270 [planetinfo.record]: rotation_speed = 0.000484 14:16:22.270 [planetinfo.record]: planet zpos = 564200.000000 14:16:22.270 [planetinfo.record]: sun_radius = 126647.128296 14:16:22.270 [planetinfo.record]: sun_vector = -0.382 -0.678 -0.628 14:16:22.270 [planetinfo.record]: sun_distance = 1071980 14:16:22.270 [planetinfo.record]: corona_flare = 0.097194 14:16:22.270 [planetinfo.record]: corona_hues = 0.516479 14:16:22.270 [planetinfo.record]: sun_color = 0.284462, 0.617059, 0.683978, 1 14:16:22.270 [planetinfo.record]: corona_shimmer = 0.287250 14:16:22.270 [planetinfo.record]: station_vector = -0.771 -0.609 0.188 14:16:22.270 [planetinfo.record]: station = coriolis 14:16:27.159 [PLANETINFO OVER]: Done 14:16:27.160 [PLANETINFO LOGGING]: 6 90 14:16:28.165 [planetinfo.record]: seed = 90 19 52 91 14:16:28.165 [planetinfo.record]: coordinates = 19 14 14:16:28.165 [planetinfo.record]: government = 3; 14:16:28.165 [planetinfo.record]: economy = 6; 14:16:28.165 [planetinfo.record]: techlevel = 6; 14:16:28.165 [planetinfo.record]: population = 34; 14:16:28.165 [planetinfo.record]: productivity = 7616; 14:16:28.165 [planetinfo.record]: name = "Anisinza"; 14:16:28.165 [planetinfo.record]: inhabitant = "Human Colonial"; 14:16:28.165 [planetinfo.record]: inhabitants = "Human Colonials"; 14:16:28.165 [planetinfo.record]: description = "The world Anisinza is scourged by deadly disease."; 14:16:28.169 [planetinfo.record]: air_color = 0.657769, 0.729964, 0.997049, 1; 14:16:28.169 [planetinfo.record]: cloud_alpha = 1.000000; 14:16:28.170 [planetinfo.record]: cloud_color = 0.574738, 0.836864, 0.994141, 1; 14:16:28.170 [planetinfo.record]: cloud_fraction = 0.090000; 14:16:28.170 [planetinfo.record]: land_color = 0.366356, 0.0438281, 0.66, 1; 14:16:28.170 [planetinfo.record]: land_fraction = 0.380000; 14:16:28.170 [planetinfo.record]: polar_cloud_color = 0.69757, 0.853691, 0.947363, 1; 14:16:28.170 [planetinfo.record]: polar_land_color = 0.830112, 0.716006, 0.934, 1; 14:16:28.170 [planetinfo.record]: polar_sea_color = 0.936914, 0.887642, 0.881102, 1; 14:16:28.170 [planetinfo.record]: sea_color = 0.630859, 0.498153, 0.480537, 1; 14:16:28.170 [planetinfo.record]: rotation_speed = 0.004653 14:16:28.170 [planetinfo.record]: planet zpos = 678120.000000 14:16:28.170 [planetinfo.record]: sun_radius = 127649.343567 14:16:28.170 [planetinfo.record]: sun_vector = -0.141 0.950 0.278 14:16:28.170 [planetinfo.record]: sun_distance = 1186710 14:16:28.170 [planetinfo.record]: corona_flare = 0.030711 14:16:28.170 [planetinfo.record]: corona_hues = 0.858635 14:16:28.170 [planetinfo.record]: sun_color = 0.465019, 0.505773, 0.791263, 1 14:16:28.171 [planetinfo.record]: corona_shimmer = 0.683037 14:16:28.171 [planetinfo.record]: station_vector = 0.062 0.913 0.403 14:16:28.171 [planetinfo.record]: station = coriolis 14:16:33.448 [PLANETINFO OVER]: Done 14:16:33.449 [PLANETINFO LOGGING]: 6 91 14:16:34.451 [planetinfo.record]: seed = 194 196 80 46 14:16:34.451 [planetinfo.record]: coordinates = 196 236 14:16:34.451 [planetinfo.record]: government = 0; 14:16:34.451 [planetinfo.record]: economy = 6; 14:16:34.451 [planetinfo.record]: techlevel = 1; 14:16:34.451 [planetinfo.record]: population = 11; 14:16:34.451 [planetinfo.record]: productivity = 1408; 14:16:34.451 [planetinfo.record]: name = "Reonen"; 14:16:34.451 [planetinfo.record]: inhabitant = "Human Colonial"; 14:16:34.451 [planetinfo.record]: inhabitants = "Human Colonials"; 14:16:34.451 [planetinfo.record]: description = "This world is reasonably notable for its great volcanoes but plagued by lethal spotted batoids."; 14:16:34.455 [planetinfo.record]: air_color = 0.673824, 0.874775, 0.871425, 1; 14:16:34.455 [planetinfo.record]: cloud_alpha = 1.000000; 14:16:34.456 [planetinfo.record]: cloud_color = 0.626953, 0.624753, 0.570625, 1; 14:16:34.456 [planetinfo.record]: cloud_fraction = 0.410000; 14:16:34.456 [planetinfo.record]: land_color = 0.157266, 0.224035, 0.66, 1; 14:16:34.456 [planetinfo.record]: land_fraction = 0.260000; 14:16:34.456 [planetinfo.record]: polar_cloud_color = 0.782129, 0.780413, 0.738211, 1; 14:16:34.456 [planetinfo.record]: polar_land_color = 0.756139, 0.779761, 0.934, 1; 14:16:34.456 [planetinfo.record]: polar_sea_color = 0.942578, 0.925428, 0.892688, 1; 14:16:34.456 [planetinfo.record]: sea_color = 0.574219, 0.532428, 0.452646, 1; 14:16:34.456 [planetinfo.record]: rotation_speed = 0.002003 14:16:34.456 [planetinfo.record]: planet zpos = 923440.000000 14:16:34.456 [planetinfo.record]: sun_radius = 150045.513306 14:16:34.456 [planetinfo.record]: sun_vector = -0.950 0.234 -0.208 14:16:34.456 [planetinfo.record]: sun_distance = 1319200 14:16:34.456 [planetinfo.record]: corona_flare = 0.035208 14:16:34.456 [planetinfo.record]: corona_hues = 0.987709 14:16:34.456 [planetinfo.record]: sun_color = 0.808939, 0.745104, 0.714567, 1 14:16:34.457 [planetinfo.record]: corona_shimmer = 0.294388 14:16:34.457 [planetinfo.record]: station_vector = 0.350 0.860 0.372 14:16:34.457 [planetinfo.record]: station = coriolis 14:16:39.359 [PLANETINFO OVER]: Done 14:16:39.360 [PLANETINFO LOGGING]: 6 92 14:16:40.364 [planetinfo.record]: seed = 162 223 244 144 14:16:40.364 [planetinfo.record]: coordinates = 223 210 14:16:40.364 [planetinfo.record]: government = 4; 14:16:40.364 [planetinfo.record]: economy = 2; 14:16:40.364 [planetinfo.record]: techlevel = 10; 14:16:40.364 [planetinfo.record]: population = 47; 14:16:40.364 [planetinfo.record]: productivity = 24064; 14:16:40.364 [planetinfo.record]: name = "Erxebe"; 14:16:40.364 [planetinfo.record]: inhabitant = "Black Furry Humanoid"; 14:16:40.364 [planetinfo.record]: inhabitants = "Black Furry Humanoids"; 14:16:40.364 [planetinfo.record]: description = "The world Erxebe is reasonably fabled for its exciting sit coms and its inhabitants’ exceptional loathing of food blenders."; 14:16:40.365 [planetinfo.record]: air_color = 0.677391, 0.489538, 0.954773, 1; 14:16:40.365 [planetinfo.record]: cloud_alpha = 1.000000; 14:16:40.366 [planetinfo.record]: cloud_color = 0.867188, 0.118561, 0.697577, 1; 14:16:40.366 [planetinfo.record]: cloud_fraction = 0.310000; 14:16:40.366 [planetinfo.record]: land_color = 0.66, 0.49363, 0.299063, 1; 14:16:40.366 [planetinfo.record]: land_fraction = 0.400000; 14:16:40.366 [planetinfo.record]: polar_cloud_color = 0.890234, 0.409908, 0.78141, 1; 14:16:40.366 [planetinfo.record]: polar_land_color = 0.934, 0.87514, 0.806305, 1; 14:16:40.366 [planetinfo.record]: polar_sea_color = 0.845037, 0.84897, 0.945703, 1; 14:16:40.366 [planetinfo.record]: sea_color = 0.311783, 0.320814, 0.542969, 1; 14:16:40.366 [planetinfo.record]: rotation_speed = 0.000022 14:16:40.366 [planetinfo.record]: planet zpos = 425460.000000 14:16:40.366 [planetinfo.record]: sun_radius = 77790.906372 14:16:40.366 [planetinfo.record]: sun_vector = -0.257 0.510 0.821 14:16:40.366 [planetinfo.record]: sun_distance = 638190 14:16:40.366 [planetinfo.record]: corona_flare = 0.086441 14:16:40.366 [planetinfo.record]: corona_hues = 0.525917 14:16:40.366 [planetinfo.record]: sun_color = 0.750778, 0.412129, 0.294905, 1 14:16:40.366 [planetinfo.record]: corona_shimmer = 0.283365 14:16:40.366 [planetinfo.record]: station_vector = 0.077 0.668 0.741 14:16:40.366 [planetinfo.record]: station = coriolis 14:16:44.415 [PLANETINFO OVER]: Done 14:16:44.415 [PLANETINFO LOGGING]: 6 93 14:16:45.418 [planetinfo.record]: seed = 26 135 64 125 14:16:45.418 [planetinfo.record]: coordinates = 135 179 14:16:45.418 [planetinfo.record]: government = 3; 14:16:45.418 [planetinfo.record]: economy = 3; 14:16:45.418 [planetinfo.record]: techlevel = 9; 14:16:45.418 [planetinfo.record]: population = 43; 14:16:45.418 [planetinfo.record]: productivity = 16856; 14:16:45.418 [planetinfo.record]: name = "Istiteat"; 14:16:45.418 [planetinfo.record]: inhabitant = "Human Colonial"; 14:16:45.418 [planetinfo.record]: inhabitants = "Human Colonials"; 14:16:45.418 [planetinfo.record]: description = "Istiteat is ravaged by dreadful civil war."; 14:16:45.437 [planetinfo.record]: air_color = 0.544494, 0.519044, 0.892336, 1; 14:16:45.438 [planetinfo.record]: cloud_alpha = 1.000000; 14:16:45.438 [planetinfo.record]: cloud_color = 0.331215, 0.233643, 0.679688, 1; 14:16:45.438 [planetinfo.record]: cloud_fraction = 0.140000; 14:16:45.438 [planetinfo.record]: land_color = 0.66, 0.550551, 0.291328, 1; 14:16:45.438 [planetinfo.record]: land_fraction = 0.280000; 14:16:45.438 [planetinfo.record]: polar_cloud_color = 0.547634, 0.475331, 0.805859, 1; 14:16:45.438 [planetinfo.record]: polar_land_color = 0.934, 0.895278, 0.803568, 1; 14:16:45.438 [planetinfo.record]: polar_sea_color = 0.935742, 0.898362, 0.873603, 1; 14:16:45.438 [planetinfo.record]: sea_color = 0.642578, 0.539901, 0.471893, 1; 14:16:45.438 [planetinfo.record]: rotation_speed = 0.003593 14:16:45.438 [planetinfo.record]: planet zpos = 941850.000000 14:16:45.438 [planetinfo.record]: sun_radius = 135658.247223 14:16:45.438 [planetinfo.record]: sun_vector = -0.689 0.472 -0.551 14:16:45.438 [planetinfo.record]: sun_distance = 1255800 14:16:45.439 [planetinfo.record]: corona_flare = 0.084013 14:16:45.439 [planetinfo.record]: corona_hues = 0.844261 14:16:45.439 [planetinfo.record]: sun_color = 0.682108, 0.26542, 0.255567, 1 14:16:45.439 [planetinfo.record]: corona_shimmer = 0.634105 14:16:45.439 [planetinfo.record]: station_vector = 0.206 0.715 0.668 14:16:45.439 [planetinfo.record]: station = coriolis 14:16:50.107 [PLANETINFO OVER]: Done 14:16:50.108 [PLANETINFO LOGGING]: 6 94 14:16:51.117 [planetinfo.record]: seed = 10 241 212 100 14:16:51.117 [planetinfo.record]: coordinates = 241 188 14:16:51.117 [planetinfo.record]: government = 1; 14:16:51.117 [planetinfo.record]: economy = 6; 14:16:51.118 [planetinfo.record]: techlevel = 3; 14:16:51.118 [planetinfo.record]: population = 20; 14:16:51.118 [planetinfo.record]: productivity = 3200; 14:16:51.118 [planetinfo.record]: name = "Zaatso"; 14:16:51.118 [planetinfo.record]: inhabitant = "Fierce Blue Furry Humanoid"; 14:16:51.118 [planetinfo.record]: inhabitants = "Fierce Blue Furry Humanoids"; 14:16:51.118 [planetinfo.record]: description = "The planet Zaatso is reasonably noted for its inhabitants’ exceptional love for tourists and its great volcanoes."; 14:16:51.120 [planetinfo.record]: air_color = 0.66357, 0.563656, 0.885832, 1; 14:16:51.120 [planetinfo.record]: cloud_alpha = 1.000000; 14:16:51.120 [planetinfo.record]: cloud_color = 0.642387, 0.335236, 0.660156, 1; 14:16:51.120 [planetinfo.record]: cloud_fraction = 0.480000; 14:16:51.120 [planetinfo.record]: land_color = 0.245365, 0.0953906, 0.66, 1; 14:16:51.120 [planetinfo.record]: land_fraction = 0.610000; 14:16:51.121 [planetinfo.record]: polar_cloud_color = 0.783661, 0.551878, 0.79707, 1; 14:16:51.121 [planetinfo.record]: polar_land_color = 0.787307, 0.734248, 0.934, 1; 14:16:51.121 [planetinfo.record]: polar_sea_color = 0.88918, 0.933203, 0.885449, 1; 14:16:51.121 [planetinfo.record]: sea_color = 0.541926, 0.667969, 0.531244, 1; 14:16:51.121 [planetinfo.record]: rotation_speed = 0.000317 14:16:51.121 [planetinfo.record]: planet zpos = 530530.000000 14:16:51.121 [planetinfo.record]: sun_radius = 78540.070496 14:16:51.121 [planetinfo.record]: sun_vector = -0.016 -0.321 0.947 14:16:51.121 [planetinfo.record]: sun_distance = 816200 14:16:51.121 [planetinfo.record]: corona_flare = 0.080563 14:16:51.122 [planetinfo.record]: corona_hues = 0.995689 14:16:51.122 [planetinfo.record]: sun_color = 0.759634, 0.657515, 0.587254, 1 14:16:51.122 [planetinfo.record]: corona_shimmer = 0.345665 14:16:51.122 [planetinfo.record]: station_vector = 0.961 0.241 0.135 14:16:51.122 [planetinfo.record]: station = coriolis 14:16:56.381 [PLANETINFO OVER]: Done 14:16:56.382 [PLANETINFO LOGGING]: 6 95 14:16:57.385 [planetinfo.record]: seed = 146 222 80 88 14:16:57.385 [planetinfo.record]: coordinates = 222 103 14:16:57.385 [planetinfo.record]: government = 2; 14:16:57.385 [planetinfo.record]: economy = 7; 14:16:57.385 [planetinfo.record]: techlevel = 3; 14:16:57.385 [planetinfo.record]: population = 22; 14:16:57.385 [planetinfo.record]: productivity = 3168; 14:16:57.385 [planetinfo.record]: name = "Edrilain"; 14:16:57.385 [planetinfo.record]: inhabitant = "Human Colonial"; 14:16:57.385 [planetinfo.record]: inhabitants = "Human Colonials"; 14:16:57.385 [planetinfo.record]: description = "The planet Edrilain is mildly notable for Edrilainian lethal water."; 14:16:57.400 [planetinfo.record]: air_color = 0.775991, 0.634659, 0.861768, 1; 14:16:57.400 [planetinfo.record]: cloud_alpha = 1.000000; 14:16:57.400 [planetinfo.record]: cloud_color = 0.587891, 0.475365, 0.488551, 1; 14:16:57.400 [planetinfo.record]: cloud_fraction = 0.170000; 14:16:57.400 [planetinfo.record]: land_color = 0.169937, 0.587891, 0.489933, 1; 14:16:57.400 [planetinfo.record]: land_fraction = 0.300000; 14:16:57.400 [planetinfo.record]: polar_cloud_color = 0.764551, 0.673088, 0.683807, 1; 14:16:57.400 [planetinfo.record]: polar_land_color = 0.773925, 0.941211, 0.902003, 1; 14:16:57.400 [planetinfo.record]: polar_sea_color = 0.944336, 0.914101, 0.894076, 1; 14:16:57.400 [planetinfo.record]: sea_color = 0.556641, 0.485353, 0.438137, 1; 14:16:57.400 [planetinfo.record]: rotation_speed = 0.000152 14:16:57.400 [planetinfo.record]: planet zpos = 559460.000000 14:16:57.400 [planetinfo.record]: sun_radius = 148268.975525 14:16:57.400 [planetinfo.record]: sun_vector = -0.137 -0.983 -0.119 14:16:57.400 [planetinfo.record]: sun_distance = 966340 14:16:57.401 [planetinfo.record]: corona_flare = 0.066283 14:16:57.401 [planetinfo.record]: corona_hues = 0.508804 14:16:57.401 [planetinfo.record]: sun_color = 0.786896, 0.780236, 0.628654, 1 14:16:57.401 [planetinfo.record]: corona_shimmer = 0.939665 14:16:57.401 [planetinfo.record]: station_vector = -0.306 0.615 0.727 14:16:57.401 [planetinfo.record]: station = coriolis 14:17:01.942 [PLANETINFO OVER]: Done 14:17:01.942 [PLANETINFO LOGGING]: 6 96 14:17:02.947 [planetinfo.record]: seed = 146 204 212 64 14:17:02.947 [planetinfo.record]: coordinates = 204 213 14:17:02.947 [planetinfo.record]: government = 2; 14:17:02.947 [planetinfo.record]: economy = 5; 14:17:02.947 [planetinfo.record]: techlevel = 3; 14:17:02.947 [planetinfo.record]: population = 20; 14:17:02.947 [planetinfo.record]: productivity = 4800; 14:17:02.947 [planetinfo.record]: name = "Xeer"; 14:17:02.947 [planetinfo.record]: inhabitant = "Large Yellow Bug-Eyed Frog"; 14:17:02.948 [planetinfo.record]: inhabitants = "Large Yellow Bug-Eyed Frogs"; 14:17:02.948 [planetinfo.record]: description = "Xeer is a revolting little planet."; 14:17:02.984 [planetinfo.record]: air_color = 0.615801, 0.901441, 0.838716, 1; 14:17:02.985 [planetinfo.record]: cloud_alpha = 1.000000; 14:17:02.985 [planetinfo.record]: cloud_color = 0.707031, 0.545852, 0.458466, 1; 14:17:02.985 [planetinfo.record]: cloud_fraction = 0.210000; 14:17:02.985 [planetinfo.record]: land_color = 0.557439, 0.66, 0.464063, 1; 14:17:02.985 [planetinfo.record]: land_fraction = 0.360000; 14:17:02.985 [planetinfo.record]: polar_cloud_color = 0.818164, 0.701593, 0.638392, 1; 14:17:02.985 [planetinfo.record]: polar_land_color = 0.897715, 0.934, 0.86468, 1; 14:17:02.985 [planetinfo.record]: polar_sea_color = 0.931836, 0.888611, 0.862676, 1; 14:17:02.985 [planetinfo.record]: sea_color = 0.681641, 0.555164, 0.479279, 1; 14:17:02.985 [planetinfo.record]: rotation_speed = 0.000607 14:17:02.986 [planetinfo.record]: planet zpos = 302000.000000 14:17:02.986 [planetinfo.record]: sun_radius = 96423.324585 14:17:02.986 [planetinfo.record]: sun_vector = -0.167 0.849 0.502 14:17:02.986 [planetinfo.record]: sun_distance = 573800 14:17:02.986 [planetinfo.record]: corona_flare = 0.009058 14:17:02.986 [planetinfo.record]: corona_hues = 0.568840 14:17:02.986 [planetinfo.record]: sun_color = 0.273937, 0.564163, 0.743054, 1 14:17:02.987 [planetinfo.record]: corona_shimmer = 0.258970 14:17:02.987 [planetinfo.record]: station_vector = 0.961 -0.252 0.115 14:17:02.987 [planetinfo.record]: station = coriolis 14:17:07.494 [PLANETINFO OVER]: Done 14:17:07.494 [PLANETINFO LOGGING]: 6 97 14:17:08.499 [planetinfo.record]: seed = 42 20 128 231 14:17:08.499 [planetinfo.record]: coordinates = 20 240 14:17:08.499 [planetinfo.record]: government = 5; 14:17:08.499 [planetinfo.record]: economy = 0; 14:17:08.499 [planetinfo.record]: techlevel = 10; 14:17:08.499 [planetinfo.record]: population = 46; 14:17:08.499 [planetinfo.record]: productivity = 33120; 14:17:08.499 [planetinfo.record]: name = "Soinsoan"; 14:17:08.499 [planetinfo.record]: inhabitant = "Fierce Fat Insect"; 14:17:08.499 [planetinfo.record]: inhabitants = "Fierce Fat Insects"; 14:17:08.499 [planetinfo.record]: description = "This world is very fabled for its unusual tropical forests."; 14:17:08.536 [planetinfo.record]: air_color = 0.638723, 0.490127, 0.946318, 1; 14:17:08.536 [planetinfo.record]: cloud_alpha = 1.000000; 14:17:08.536 [planetinfo.record]: cloud_color = 0.836222, 0.128242, 0.841797, 1; 14:17:08.536 [planetinfo.record]: cloud_fraction = 0.540000; 14:17:08.536 [planetinfo.record]: land_color = 0.66, 0.605094, 0.417656, 1; 14:17:08.536 [planetinfo.record]: land_fraction = 0.350000; 14:17:08.536 [planetinfo.record]: polar_cloud_color = 0.875171, 0.413229, 0.878809, 1; 14:17:08.536 [planetinfo.record]: polar_land_color = 0.934, 0.914575, 0.848262, 1; 14:17:08.536 [planetinfo.record]: polar_sea_color = 0.938281, 0.85215, 0.920786, 1; 14:17:08.536 [planetinfo.record]: sea_color = 0.617188, 0.390564, 0.571155, 1; 14:17:08.536 [planetinfo.record]: rotation_speed = 0.001749 14:17:08.536 [planetinfo.record]: planet zpos = 555360.000000 14:17:08.536 [planetinfo.record]: sun_radius = 119961.070557 14:17:08.536 [planetinfo.record]: sun_vector = -0.330 0.709 -0.623 14:17:08.536 [planetinfo.record]: sun_distance = 971880 14:17:08.536 [planetinfo.record]: corona_flare = 0.000113 14:17:08.536 [planetinfo.record]: corona_hues = 0.583031 14:17:08.536 [planetinfo.record]: sun_color = 0.746808, 0.444016, 0.322959, 1 14:17:08.536 [planetinfo.record]: corona_shimmer = 0.300046 14:17:08.537 [planetinfo.record]: station_vector = -0.904 0.364 0.224 14:17:08.537 [planetinfo.record]: station = coriolis 14:17:12.914 [PLANETINFO OVER]: Done 14:17:12.914 [PLANETINFO LOGGING]: 6 98 14:17:13.918 [planetinfo.record]: seed = 58 187 244 142 14:17:13.918 [planetinfo.record]: coordinates = 187 231 14:17:13.918 [planetinfo.record]: government = 7; 14:17:13.918 [planetinfo.record]: economy = 7; 14:17:13.918 [planetinfo.record]: techlevel = 7; 14:17:13.918 [planetinfo.record]: population = 43; 14:17:13.918 [planetinfo.record]: productivity = 11352; 14:17:13.918 [planetinfo.record]: name = "Reatte"; 14:17:13.918 [planetinfo.record]: inhabitant = "Black Fat Feline"; 14:17:13.918 [planetinfo.record]: inhabitants = "Black Fat Felines"; 14:17:13.918 [planetinfo.record]: description = "The planet Reatte is mildly notable for Reatteian lethal brandy."; 14:17:13.924 [planetinfo.record]: air_color = 0.443963, 0.615801, 0.900141, 1; 14:17:13.924 [planetinfo.record]: cloud_alpha = 1.000000; 14:17:13.924 [planetinfo.record]: cloud_color = 0.0549316, 0.703125, 0.627165, 1; 14:17:13.924 [planetinfo.record]: cloud_fraction = 0.480000; 14:17:13.924 [planetinfo.record]: land_color = 0.561487, 0.66, 0.471797, 1; 14:17:13.924 [planetinfo.record]: land_fraction = 0.500000; 14:17:13.924 [planetinfo.record]: polar_cloud_color = 0.346016, 0.816406, 0.761282, 1; 14:17:13.924 [planetinfo.record]: polar_land_color = 0.899147, 0.934, 0.867416, 1; 14:17:13.924 [planetinfo.record]: polar_sea_color = 0.935759, 0.85502, 0.94043, 1; 14:17:13.924 [planetinfo.record]: sea_color = 0.583868, 0.379295, 0.595703, 1; 14:17:13.924 [planetinfo.record]: rotation_speed = 0.000953 14:17:13.924 [planetinfo.record]: planet zpos = 922180.000000 14:17:13.925 [planetinfo.record]: sun_radius = 147547.151642 14:17:13.925 [planetinfo.record]: sun_vector = 0.792 -0.567 -0.228 14:17:13.925 [planetinfo.record]: sun_distance = 1580880 14:17:13.925 [planetinfo.record]: corona_flare = 0.062160 14:17:13.925 [planetinfo.record]: corona_hues = 0.852951 14:17:13.925 [planetinfo.record]: sun_color = 0.842151, 0.360242, 0.225139, 1 14:17:13.925 [planetinfo.record]: corona_shimmer = 0.310839 14:17:13.925 [planetinfo.record]: station_vector = -0.413 -0.401 0.818 14:17:13.925 [planetinfo.record]: station = coriolis 14:17:18.778 [PLANETINFO OVER]: Done 14:17:18.779 [PLANETINFO LOGGING]: 6 99 14:17:19.784 [planetinfo.record]: seed = 226 60 208 234 14:17:19.784 [planetinfo.record]: coordinates = 60 124 14:17:19.784 [planetinfo.record]: government = 4; 14:17:19.784 [planetinfo.record]: economy = 4; 14:17:19.784 [planetinfo.record]: techlevel = 5; 14:17:19.784 [planetinfo.record]: population = 29; 14:17:19.784 [planetinfo.record]: productivity = 11136; 14:17:19.784 [planetinfo.record]: name = "Argema"; 14:17:19.784 [planetinfo.record]: inhabitant = "Small Slimy Lizard"; 14:17:19.784 [planetinfo.record]: inhabitants = "Small Slimy Lizards"; 14:17:19.784 [planetinfo.record]: description = "Argema is most noted for its inhabitants’ eccentric love for tourists and its great tropical forests."; 14:17:19.799 [planetinfo.record]: air_color = 0.690627, 0.857868, 0.863068, 1; 14:17:19.799 [planetinfo.record]: cloud_alpha = 1.000000; 14:17:19.799 [planetinfo.record]: cloud_color = 0.591797, 0.591797, 0.591797, 1; 14:17:19.799 [planetinfo.record]: cloud_fraction = 0.420000; 14:17:19.799 [planetinfo.record]: land_color = 0.321655, 0.32493, 0.53125, 1; 14:17:19.799 [planetinfo.record]: land_fraction = 0.630000; 14:17:19.799 [planetinfo.record]: polar_cloud_color = 0.766309, 0.766309, 0.766309, 1; 14:17:19.799 [planetinfo.record]: polar_land_color = 0.853482, 0.854941, 0.946875, 1; 14:17:19.800 [planetinfo.record]: polar_sea_color = 0.917188, 0.892808, 0.797165, 1; 14:17:19.800 [planetinfo.record]: sea_color = 0.828125, 0.740076, 0.394653, 1; 14:17:19.800 [planetinfo.record]: rotation_speed = 0.003319 14:17:19.800 [planetinfo.record]: planet zpos = 597960.000000 14:17:19.800 [planetinfo.record]: sun_radius = 113981.314087 14:17:19.800 [planetinfo.record]: sun_vector = 0.838 0.094 -0.538 14:17:19.800 [planetinfo.record]: sun_distance = 1032840 14:17:19.800 [planetinfo.record]: corona_flare = 0.061041 14:17:19.800 [planetinfo.record]: corona_hues = 0.733238 14:17:19.800 [planetinfo.record]: sun_color = 0.676572, 0.66387, 0.616155, 1 14:17:19.801 [planetinfo.record]: corona_shimmer = 0.324805 14:17:19.801 [planetinfo.record]: station_vector = -0.605 -0.236 0.761 14:17:19.802 [planetinfo.record]: station = coriolis 14:17:25.071 [PLANETINFO OVER]: Done 14:17:25.071 [PLANETINFO LOGGING]: 6 100 14:17:26.075 [planetinfo.record]: seed = 2 90 52 201 14:17:26.075 [planetinfo.record]: coordinates = 90 203 14:17:26.075 [planetinfo.record]: government = 0; 14:17:26.075 [planetinfo.record]: economy = 3; 14:17:26.075 [planetinfo.record]: techlevel = 6; 14:17:26.075 [planetinfo.record]: population = 28; 14:17:26.075 [planetinfo.record]: productivity = 6272; 14:17:26.075 [planetinfo.record]: name = "Esreates"; 14:17:26.075 [planetinfo.record]: inhabitant = "Human Colonial"; 14:17:26.075 [planetinfo.record]: inhabitants = "Human Colonials"; 14:17:26.075 [planetinfo.record]: description = "Esreates is cursed by dreadful civil war."; 14:17:26.100 [planetinfo.record]: air_color = 0.530484, 0.503057, 0.905344, 1; 14:17:26.100 [planetinfo.record]: cloud_alpha = 1.000000; 14:17:26.100 [planetinfo.record]: cloud_color = 0.306381, 0.190918, 0.71875, 1; 14:17:26.100 [planetinfo.record]: cloud_fraction = 0.210000; 14:17:26.100 [planetinfo.record]: land_color = 0.446938, 0.539062, 0.0673828, 1; 14:17:26.100 [planetinfo.record]: land_fraction = 0.590000; 14:17:26.100 [planetinfo.record]: polar_cloud_color = 0.528168, 0.445493, 0.823438, 1; 14:17:26.100 [planetinfo.record]: polar_land_color = 0.905672, 0.946094, 0.739136, 1; 14:17:26.100 [planetinfo.record]: polar_sea_color = 0.900195, 0.852548, 0.864832, 1; 14:17:26.100 [planetinfo.record]: sea_color = 0.998047, 0.786742, 0.841219, 1; 14:17:26.100 [planetinfo.record]: rotation_speed = 0.001277 14:17:26.100 [planetinfo.record]: planet zpos = 625200.000000 14:17:26.100 [planetinfo.record]: sun_radius = 116013.446045 14:17:26.100 [planetinfo.record]: sun_vector = 0.393 -0.311 -0.865 14:17:26.100 [planetinfo.record]: sun_distance = 1094100 14:17:26.100 [planetinfo.record]: corona_flare = 0.003857 14:17:26.100 [planetinfo.record]: corona_hues = 0.827072 14:17:26.100 [planetinfo.record]: sun_color = 0.827249, 0.627965, 0.514787, 1 14:17:26.101 [planetinfo.record]: corona_shimmer = 0.390486 14:17:26.101 [planetinfo.record]: station_vector = -0.631 -0.753 0.188 14:17:26.101 [planetinfo.record]: station = coriolis 14:17:31.256 [PLANETINFO OVER]: Done 14:17:31.256 [PLANETINFO LOGGING]: 6 101 14:17:32.261 [planetinfo.record]: seed = 186 201 64 202 14:17:32.261 [planetinfo.record]: coordinates = 201 17 14:17:32.261 [planetinfo.record]: government = 7; 14:17:32.261 [planetinfo.record]: economy = 1; 14:17:32.261 [planetinfo.record]: techlevel = 11; 14:17:32.261 [planetinfo.record]: population = 53; 14:17:32.261 [planetinfo.record]: productivity = 41976; 14:17:32.261 [planetinfo.record]: name = "Arceores"; 14:17:32.261 [planetinfo.record]: inhabitant = "Human Colonial"; 14:17:32.262 [planetinfo.record]: inhabitants = "Human Colonials"; 14:17:32.262 [planetinfo.record]: description = "The world Arceores is notable for the Arceoresian edible arts graduate and its great volcanoes."; 14:17:32.284 [planetinfo.record]: air_color = 0.509917, 0.997049, 0.947105, 1; 14:17:32.284 [planetinfo.record]: cloud_alpha = 1.000000; 14:17:32.284 [planetinfo.record]: cloud_color = 0.994141, 0.738203, 0.132034, 1; 14:17:32.284 [planetinfo.record]: cloud_fraction = 0.320000; 14:17:32.284 [planetinfo.record]: land_color = 0.221603, 0.433445, 0.603516, 1; 14:17:32.284 [planetinfo.record]: land_fraction = 0.270000; 14:17:32.284 [planetinfo.record]: polar_cloud_color = 0.947363, 0.794929, 0.4339, 1; 14:17:32.284 [planetinfo.record]: polar_land_color = 0.790993, 0.87345, 0.939648, 1; 14:17:32.284 [planetinfo.record]: polar_sea_color = 0.879385, 0.923047, 0.867159, 1; 14:17:32.284 [planetinfo.record]: sea_color = 0.623929, 0.769531, 0.58316, 1; 14:17:32.284 [planetinfo.record]: rotation_speed = 0.004888 14:17:32.284 [planetinfo.record]: planet zpos = 557700.000000 14:17:32.284 [planetinfo.record]: sun_radius = 136006.602631 14:17:32.284 [planetinfo.record]: sun_vector = -0.020 -0.992 0.125 14:17:32.284 [planetinfo.record]: sun_distance = 1171170 14:17:32.284 [planetinfo.record]: corona_flare = 0.001338 14:17:32.284 [planetinfo.record]: corona_hues = 0.806122 14:17:32.284 [planetinfo.record]: sun_color = 0.775922, 0.649651, 0.208889, 1 14:17:32.284 [planetinfo.record]: corona_shimmer = 0.750458 14:17:32.284 [planetinfo.record]: station_vector = 0.828 0.547 0.128 14:17:32.284 [planetinfo.record]: station = dodecahedron 14:17:36.946 [PLANETINFO OVER]: Done 14:17:36.947 [PLANETINFO LOGGING]: 6 102 14:17:37.950 [planetinfo.record]: seed = 234 169 148 137 14:17:37.950 [planetinfo.record]: coordinates = 169 57 14:17:37.950 [planetinfo.record]: government = 5; 14:17:37.950 [planetinfo.record]: economy = 1; 14:17:37.950 [planetinfo.record]: techlevel = 10; 14:17:37.950 [planetinfo.record]: population = 47; 14:17:37.950 [planetinfo.record]: productivity = 30456; 14:17:37.950 [planetinfo.record]: name = "Esditi"; 14:17:37.950 [planetinfo.record]: inhabitant = "Small Black Slimy Frog"; 14:17:37.950 [planetinfo.record]: inhabitants = "Small Black Slimy Frogs"; 14:17:37.950 [planetinfo.record]: description = "The planet Esditi is an unremarkable dump."; 14:17:37.963 [planetinfo.record]: air_color = 0.453509, 0.554187, 0.909896, 1; 14:17:37.963 [planetinfo.record]: cloud_alpha = 1.000000; 14:17:37.963 [planetinfo.record]: cloud_color = 0.0686646, 0.49907, 0.732422, 1; 14:17:37.963 [planetinfo.record]: cloud_fraction = 0.240000; 14:17:37.963 [planetinfo.record]: land_color = 0.415078, 0.66, 0.556674, 1; 14:17:37.963 [planetinfo.record]: land_fraction = 0.270000; 14:17:37.963 [planetinfo.record]: polar_cloud_color = 0.359705, 0.664396, 0.82959, 1; 14:17:37.963 [planetinfo.record]: polar_land_color = 0.84735, 0.934, 0.897444, 1; 14:17:37.963 [planetinfo.record]: polar_sea_color = 0.88268, 0.930859, 0.890208, 1; 14:17:37.963 [planetinfo.record]: sea_color = 0.548264, 0.691406, 0.57063, 1; 14:17:37.963 [planetinfo.record]: rotation_speed = 0.001572 14:17:37.963 [planetinfo.record]: planet zpos = 793350.000000 14:17:37.963 [planetinfo.record]: sun_radius = 109406.018829 14:17:37.963 [planetinfo.record]: sun_vector = 0.220 0.603 0.766 14:17:37.964 [planetinfo.record]: sun_distance = 899130 14:17:37.964 [planetinfo.record]: corona_flare = 0.048773 14:17:37.964 [planetinfo.record]: corona_hues = 0.812325 14:17:37.964 [planetinfo.record]: sun_color = 0.79928, 0.720378, 0.337757, 1 14:17:37.964 [planetinfo.record]: corona_shimmer = 0.268777 14:17:37.964 [planetinfo.record]: station_vector = -0.659 -0.126 0.741 14:17:37.964 [planetinfo.record]: station = coriolis 14:17:42.202 [PLANETINFO OVER]: Done 14:17:42.203 [PLANETINFO LOGGING]: 6 103 14:17:43.207 [planetinfo.record]: seed = 178 151 208 165 14:17:43.207 [planetinfo.record]: coordinates = 151 160 14:17:43.207 [planetinfo.record]: government = 6; 14:17:43.207 [planetinfo.record]: economy = 0; 14:17:43.207 [planetinfo.record]: techlevel = 13; 14:17:43.207 [planetinfo.record]: population = 59; 14:17:43.207 [planetinfo.record]: productivity = 47200; 14:17:43.207 [planetinfo.record]: name = "Cerianon"; 14:17:43.207 [planetinfo.record]: inhabitant = "Fierce Harmless Rodent"; 14:17:43.207 [planetinfo.record]: inhabitants = "Fierce Harmless Rodents"; 14:17:43.207 [planetinfo.record]: description = "The planet Cerianon is an unremarkable dump."; 14:17:43.225 [planetinfo.record]: air_color = 0.667893, 0.859816, 0.829654, 1; 14:17:43.226 [planetinfo.record]: cloud_alpha = 1.000000; 14:17:43.226 [planetinfo.record]: cloud_color = 0.582031, 0.563168, 0.541107, 1; 14:17:43.226 [planetinfo.record]: cloud_fraction = 0.360000; 14:17:43.226 [planetinfo.record]: land_color = 0.605469, 0.574427, 0.534515, 1; 14:17:43.226 [planetinfo.record]: land_fraction = 0.370000; 14:17:43.226 [planetinfo.record]: polar_cloud_color = 0.761914, 0.746481, 0.728432, 1; 14:17:43.226 [planetinfo.record]: polar_land_color = 0.939453, 0.927412, 0.91193, 1; 14:17:43.226 [planetinfo.record]: polar_sea_color = 0.782979, 0.905859, 0.782011, 1; 14:17:43.226 [planetinfo.record]: sea_color = 0.430597, 0.941406, 0.426575, 1; 14:17:43.226 [planetinfo.record]: rotation_speed = 0.001426 14:17:43.226 [planetinfo.record]: planet zpos = 509640.000000 14:17:43.226 [planetinfo.record]: sun_radius = 75471.476288 14:17:43.226 [planetinfo.record]: sun_vector = -0.733 0.019 -0.680 14:17:43.226 [planetinfo.record]: sun_distance = 764460 14:17:43.226 [planetinfo.record]: corona_flare = 0.060414 14:17:43.227 [planetinfo.record]: corona_hues = 0.954483 14:17:43.227 [planetinfo.record]: sun_color = 0.654755, 0.418836, 0.383153, 1 14:17:43.227 [planetinfo.record]: corona_shimmer = 0.406864 14:17:43.227 [planetinfo.record]: station_vector = 0.357 0.658 0.663 14:17:43.227 [planetinfo.record]: station = dodecahedron 14:17:48.284 [PLANETINFO OVER]: Done 14:17:48.285 [PLANETINFO LOGGING]: 6 104 14:17:49.300 [planetinfo.record]: seed = 242 159 20 154 14:17:49.300 [planetinfo.record]: coordinates = 159 27 14:17:49.300 [planetinfo.record]: government = 6; 14:17:49.300 [planetinfo.record]: economy = 3; 14:17:49.300 [planetinfo.record]: techlevel = 10; 14:17:49.300 [planetinfo.record]: population = 50; 14:17:49.300 [planetinfo.record]: productivity = 28000; 14:17:49.300 [planetinfo.record]: name = "Qulaaon"; 14:17:49.300 [planetinfo.record]: inhabitant = "Human Colonial"; 14:17:49.300 [planetinfo.record]: inhabitants = "Human Colonials"; 14:17:49.300 [planetinfo.record]: description = "The planet Qulaaon is an unremarkable dump."; 14:17:49.316 [planetinfo.record]: air_color = 0.472681, 0.625126, 0.877377, 1; 14:17:49.316 [planetinfo.record]: cloud_alpha = 1.000000; 14:17:49.316 [planetinfo.record]: cloud_color = 0.136375, 0.634766, 0.576361, 1; 14:17:49.316 [planetinfo.record]: cloud_fraction = 0.340000; 14:17:49.316 [planetinfo.record]: land_color = 0.25177, 0.490889, 0.515625, 1; 14:17:49.316 [planetinfo.record]: land_fraction = 0.330000; 14:17:49.316 [planetinfo.record]: polar_cloud_color = 0.400111, 0.785645, 0.740465, 1; 14:17:49.316 [planetinfo.record]: polar_land_color = 0.827104, 0.937063, 0.948438, 1; 14:17:49.316 [planetinfo.record]: polar_sea_color = 0.866662, 0.920508, 0.912936, 1; 14:17:49.316 [planetinfo.record]: sea_color = 0.608923, 0.794922, 0.768766, 1; 14:17:49.316 [planetinfo.record]: rotation_speed = 0.001891 14:17:49.316 [planetinfo.record]: planet zpos = 664200.000000 14:17:49.316 [planetinfo.record]: sun_radius = 153953.166962 14:17:49.316 [planetinfo.record]: sun_vector = -0.227 0.800 -0.555 14:17:49.316 [planetinfo.record]: sun_distance = 1217700 14:17:49.318 [planetinfo.record]: corona_flare = 0.008511 14:17:49.318 [planetinfo.record]: corona_hues = 0.541702 14:17:49.318 [planetinfo.record]: sun_color = 0.771085, 0.522819, 0.338179, 1 14:17:49.318 [planetinfo.record]: corona_shimmer = 0.238005 14:17:49.318 [planetinfo.record]: station_vector = -0.062 -0.609 0.791 14:17:49.318 [planetinfo.record]: station = coriolis 14:17:54.271 [PLANETINFO OVER]: Done 14:17:54.272 [PLANETINFO LOGGING]: 6 105 14:17:55.276 [planetinfo.record]: seed = 202 127 128 101 14:17:55.276 [planetinfo.record]: coordinates = 127 143 14:17:55.276 [planetinfo.record]: government = 1; 14:17:55.276 [planetinfo.record]: economy = 7; 14:17:55.276 [planetinfo.record]: techlevel = 4; 14:17:55.276 [planetinfo.record]: population = 25; 14:17:55.276 [planetinfo.record]: productivity = 3000; 14:17:55.277 [planetinfo.record]: name = "Celaqula"; 14:17:55.277 [planetinfo.record]: inhabitant = "Fierce Blue Slimy Frog"; 14:17:55.277 [planetinfo.record]: inhabitants = "Fierce Blue Slimy Frogs"; 14:17:55.277 [planetinfo.record]: description = "This world is mildly famous for its unusual sit coms and the Celaqulaian spotted wolf."; 14:17:55.289 [planetinfo.record]: air_color = 0.67858, 0.822982, 0.934611, 1; 14:17:55.289 [planetinfo.record]: cloud_alpha = 1.000000; 14:17:55.289 [planetinfo.record]: cloud_color = 0.627037, 0.806641, 0.684566, 1; 14:17:55.289 [planetinfo.record]: cloud_fraction = 0.320000; 14:17:55.289 [planetinfo.record]: land_color = 0.619717, 0.66, 0.45375, 1; 14:17:55.289 [planetinfo.record]: land_fraction = 0.610000; 14:17:55.289 [planetinfo.record]: polar_cloud_color = 0.742895, 0.862988, 0.781362, 1; 14:17:55.289 [planetinfo.record]: polar_land_color = 0.919748, 0.934, 0.861031, 1; 14:17:55.289 [planetinfo.record]: polar_sea_color = 0.940377, 0.859228, 0.941016, 1; 14:17:55.289 [planetinfo.record]: sea_color = 0.588242, 0.384781, 0.589844, 1; 14:17:55.289 [planetinfo.record]: rotation_speed = 0.002956 14:17:55.289 [planetinfo.record]: planet zpos = 591220.000000 14:17:55.289 [planetinfo.record]: sun_radius = 126532.771606 14:17:55.289 [planetinfo.record]: sun_vector = -0.391 -0.764 0.514 14:17:55.289 [planetinfo.record]: sun_distance = 886830 14:17:55.289 [planetinfo.record]: corona_flare = 0.098915 14:17:55.290 [planetinfo.record]: corona_hues = 0.629509 14:17:55.290 [planetinfo.record]: sun_color = 0.718546, 0.714454, 0.68206, 1 14:17:55.290 [planetinfo.record]: corona_shimmer = 0.323874 14:17:55.292 [planetinfo.record]: station_vector = -0.394 -0.674 0.625 14:17:55.292 [planetinfo.record]: station = coriolis 14:18:00.858 [PLANETINFO OVER]: Done 14:18:00.858 [PLANETINFO LOGGING]: 6 106 14:18:01.863 [planetinfo.record]: seed = 26 53 180 4 14:18:01.863 [planetinfo.record]: coordinates = 53 90 14:18:01.863 [planetinfo.record]: government = 3; 14:18:01.863 [planetinfo.record]: economy = 2; 14:18:01.863 [planetinfo.record]: techlevel = 8; 14:18:01.863 [planetinfo.record]: population = 38; 14:18:01.863 [planetinfo.record]: productivity = 17024; 14:18:01.863 [planetinfo.record]: name = "Zarareso"; 14:18:01.863 [planetinfo.record]: inhabitant = "Fierce Green Insect"; 14:18:01.863 [planetinfo.record]: inhabitants = "Fierce Green Insects"; 14:18:01.863 [planetinfo.record]: description = "The planet Zarareso is cursed by dreadful civil war."; 14:18:01.888 [planetinfo.record]: air_color = 0.505842, 0.635015, 0.84876, 1; 14:18:01.888 [planetinfo.record]: cloud_alpha = 1.000000; 14:18:01.888 [planetinfo.record]: cloud_color = 0.212242, 0.548828, 0.509384, 1; 14:18:01.888 [planetinfo.record]: cloud_fraction = 0.090000; 14:18:01.888 [planetinfo.record]: land_color = 0.474375, 0.66, 0.50773, 1; 14:18:01.888 [planetinfo.record]: land_fraction = 0.330000; 14:18:01.888 [planetinfo.record]: polar_cloud_color = 0.460657, 0.746973, 0.71342, 1; 14:18:01.888 [planetinfo.record]: polar_land_color = 0.868328, 0.934, 0.880129, 1; 14:18:01.888 [planetinfo.record]: polar_sea_color = 0.869894, 0.924805, 0.884909, 1; 14:18:01.888 [planetinfo.record]: sea_color = 0.573364, 0.751953, 0.622197, 1; 14:18:01.888 [planetinfo.record]: rotation_speed = 0.002119 14:18:01.888 [planetinfo.record]: planet zpos = 545020.000000 14:18:01.888 [planetinfo.record]: sun_radius = 99273.400879 14:18:01.888 [planetinfo.record]: sun_vector = -0.235 0.735 -0.636 14:18:01.888 [planetinfo.record]: sun_distance = 739670 14:18:01.888 [planetinfo.record]: corona_flare = 0.054124 14:18:01.888 [planetinfo.record]: corona_hues = 0.642921 14:18:01.888 [planetinfo.record]: sun_color = 0.592136, 0.704324, 0.726532, 1 14:18:01.889 [planetinfo.record]: corona_shimmer = 0.262931 14:18:01.889 [planetinfo.record]: station_vector = 0.031 0.698 0.715 14:18:01.889 [planetinfo.record]: station = coriolis 14:18:06.156 [PLANETINFO OVER]: Done 14:18:06.157 [PLANETINFO LOGGING]: 6 107 14:18:07.159 [planetinfo.record]: seed = 2 103 80 201 14:18:07.159 [planetinfo.record]: coordinates = 103 206 14:18:07.159 [planetinfo.record]: government = 0; 14:18:07.159 [planetinfo.record]: economy = 6; 14:18:07.159 [planetinfo.record]: techlevel = 4; 14:18:07.159 [planetinfo.record]: population = 23; 14:18:07.160 [planetinfo.record]: productivity = 2944; 14:18:07.160 [planetinfo.record]: name = "Esrire"; 14:18:07.160 [planetinfo.record]: inhabitant = "Human Colonial"; 14:18:07.160 [planetinfo.record]: inhabitants = "Human Colonials"; 14:18:07.160 [planetinfo.record]: description = "Esrire is cursed by killer edible arts graduates."; 14:18:07.184 [planetinfo.record]: air_color = 0.407606, 0.846811, 0.860467, 1; 14:18:07.184 [planetinfo.record]: cloud_alpha = 1.000000; 14:18:07.184 [planetinfo.record]: cloud_color = 0.525832, 0.583984, 0.0114059, 1; 14:18:07.184 [planetinfo.record]: cloud_fraction = 0.240000; 14:18:07.184 [planetinfo.record]: land_color = 0.270703, 0.66, 0.358903, 1; 14:18:07.184 [planetinfo.record]: land_fraction = 0.240000; 14:18:07.184 [planetinfo.record]: polar_cloud_color = 0.715319, 0.762793, 0.295359, 1; 14:18:07.184 [planetinfo.record]: polar_land_color = 0.796272, 0.934, 0.827476, 1; 14:18:07.184 [planetinfo.record]: polar_sea_color = 0.89945, 0.93457, 0.886747, 1; 14:18:07.184 [planetinfo.record]: sea_color = 0.555945, 0.654297, 0.52037, 1; 14:18:07.184 [planetinfo.record]: rotation_speed = 0.004560 14:18:07.184 [planetinfo.record]: planet zpos = 783450.000000 14:18:07.184 [planetinfo.record]: sun_radius = 155049.045868 14:18:07.184 [planetinfo.record]: sun_vector = -0.391 -0.546 0.741 14:18:07.184 [planetinfo.record]: sun_distance = 940140 14:18:07.184 [planetinfo.record]: corona_flare = 0.018599 14:18:07.184 [planetinfo.record]: corona_hues = 0.804070 14:18:07.184 [planetinfo.record]: sun_color = 0.642118, 0.676192, 0.755228, 1 14:18:07.184 [planetinfo.record]: corona_shimmer = 0.327284 14:18:07.184 [planetinfo.record]: station_vector = 0.481 0.479 0.734 14:18:07.185 [planetinfo.record]: station = coriolis 14:18:12.189 [PLANETINFO OVER]: Done 14:18:12.190 [PLANETINFO LOGGING]: 6 108 14:18:13.197 [planetinfo.record]: seed = 98 246 116 35 14:18:13.197 [planetinfo.record]: coordinates = 246 46 14:18:13.197 [planetinfo.record]: government = 4; 14:18:13.197 [planetinfo.record]: economy = 6; 14:18:13.198 [planetinfo.record]: techlevel = 5; 14:18:13.198 [planetinfo.record]: population = 31; 14:18:13.198 [planetinfo.record]: productivity = 7936; 14:18:13.198 [planetinfo.record]: name = "Geusxe"; 14:18:13.198 [planetinfo.record]: inhabitant = "Human Colonial"; 14:18:13.198 [planetinfo.record]: inhabitants = "Human Colonials"; 14:18:13.198 [planetinfo.record]: description = "Geusxe is very notable for its inhabitants’ ingrained shyness."; 14:18:13.212 [planetinfo.record]: air_color = 0.616015, 0.894938, 0.820626, 1; 14:18:13.212 [planetinfo.record]: cloud_alpha = 1.000000; 14:18:13.212 [planetinfo.record]: cloud_color = 0.6875, 0.50526, 0.456543, 1; 14:18:13.212 [planetinfo.record]: cloud_fraction = 0.540000; 14:18:13.212 [planetinfo.record]: land_color = 0.66, 0.652709, 0.193359, 1; 14:18:13.212 [planetinfo.record]: land_fraction = 0.500000; 14:18:13.212 [planetinfo.record]: polar_cloud_color = 0.809375, 0.675284, 0.639438, 1; 14:18:13.212 [planetinfo.record]: polar_land_color = 0.934, 0.93142, 0.768908, 1; 14:18:13.212 [planetinfo.record]: polar_sea_color = 0.948438, 0.897403, 0.903384, 1; 14:18:13.212 [planetinfo.record]: sea_color = 0.515625, 0.404645, 0.41765, 1; 14:18:13.212 [planetinfo.record]: rotation_speed = 0.002475 14:18:13.212 [planetinfo.record]: planet zpos = 421300.000000 14:18:13.212 [planetinfo.record]: sun_radius = 110134.707642 14:18:13.212 [planetinfo.record]: sun_vector = -0.719 -0.268 0.642 14:18:13.212 [planetinfo.record]: sun_distance = 804300 14:18:13.212 [planetinfo.record]: corona_flare = 0.001361 14:18:13.212 [planetinfo.record]: corona_hues = 0.762146 14:18:13.212 [planetinfo.record]: sun_color = 0.755606, 0.721921, 0.715128, 1 14:18:13.213 [planetinfo.record]: corona_shimmer = 0.390556 14:18:13.213 [planetinfo.record]: station_vector = 0.608 -0.793 0.039 14:18:13.213 [planetinfo.record]: station = coriolis 14:18:17.599 [PLANETINFO OVER]: Done 14:18:17.600 [PLANETINFO LOGGING]: 6 109 14:18:18.619 [planetinfo.record]: seed = 90 206 64 121 14:18:18.619 [planetinfo.record]: coordinates = 206 98 14:18:18.619 [planetinfo.record]: government = 3; 14:18:18.619 [planetinfo.record]: economy = 2; 14:18:18.619 [planetinfo.record]: techlevel = 9; 14:18:18.619 [planetinfo.record]: population = 42; 14:18:18.619 [planetinfo.record]: productivity = 18816; 14:18:18.619 [planetinfo.record]: name = "Oresatra"; 14:18:18.619 [planetinfo.record]: inhabitant = "Human Colonial"; 14:18:18.619 [planetinfo.record]: inhabitants = "Human Colonials"; 14:18:18.619 [planetinfo.record]: description = "The planet Oresatra is mildly well known for its exotic cuisine."; 14:18:18.636 [planetinfo.record]: air_color = 0.589731, 0.891183, 0.978838, 1; 14:18:18.636 [planetinfo.record]: cloud_alpha = 1.000000; 14:18:18.636 [planetinfo.record]: cloud_color = 0.555965, 0.939453, 0.381653, 1; 14:18:18.636 [planetinfo.record]: cloud_fraction = 0.400000; 14:18:18.636 [planetinfo.record]: land_color = 0.211406, 0.292013, 0.66, 1; 14:18:18.636 [planetinfo.record]: land_fraction = 0.470000; 14:18:18.636 [planetinfo.record]: polar_cloud_color = 0.687335, 0.922754, 0.580326, 1; 14:18:18.636 [planetinfo.record]: polar_land_color = 0.775293, 0.803811, 0.934, 1; 14:18:18.636 [planetinfo.record]: polar_sea_color = 0.933594, 0.857922, 0.857922, 1; 14:18:18.636 [planetinfo.record]: sea_color = 0.664062, 0.448761, 0.448761, 1; 14:18:18.636 [planetinfo.record]: rotation_speed = 0.001102 14:18:18.637 [planetinfo.record]: planet zpos = 639120.000000 14:18:18.637 [planetinfo.record]: sun_radius = 99588.625793 14:18:18.637 [planetinfo.record]: sun_vector = -0.706 0.168 0.688 14:18:18.637 [planetinfo.record]: sun_distance = 1118460 14:18:18.637 [planetinfo.record]: corona_flare = 0.022025 14:18:18.637 [planetinfo.record]: corona_hues = 0.685219 14:18:18.637 [planetinfo.record]: sun_color = 0.741876, 0.620252, 0.225101, 1 14:18:18.637 [planetinfo.record]: corona_shimmer = 0.377694 14:18:18.638 [planetinfo.record]: station_vector = -0.786 0.202 0.585 14:18:18.638 [planetinfo.record]: station = coriolis 14:18:23.777 [PLANETINFO OVER]: Done 14:18:23.778 [PLANETINFO LOGGING]: 6 110 14:18:24.783 [planetinfo.record]: seed = 202 20 84 176 14:18:24.783 [planetinfo.record]: coordinates = 20 241 14:18:24.783 [planetinfo.record]: government = 1; 14:18:24.783 [planetinfo.record]: economy = 3; 14:18:24.783 [planetinfo.record]: techlevel = 5; 14:18:24.783 [planetinfo.record]: population = 25; 14:18:24.783 [planetinfo.record]: productivity = 7000; 14:18:24.783 [planetinfo.record]: name = "Ervean"; 14:18:24.783 [planetinfo.record]: inhabitant = "Human Colonial"; 14:18:24.783 [planetinfo.record]: inhabitants = "Human Colonials"; 14:18:24.783 [planetinfo.record]: description = "This planet is a tedious place."; 14:18:24.812 [planetinfo.record]: air_color = 0.600991, 0.902092, 0.810121, 1; 14:18:24.812 [planetinfo.record]: cloud_alpha = 1.000000; 14:18:24.812 [planetinfo.record]: cloud_color = 0.708984, 0.450472, 0.423729, 1; 14:18:24.812 [planetinfo.record]: cloud_fraction = 0.350000; 14:18:24.812 [planetinfo.record]: land_color = 0.489844, 0.612144, 0.66, 1; 14:18:24.812 [planetinfo.record]: land_fraction = 0.280000; 14:18:24.812 [planetinfo.record]: polar_cloud_color = 0.819043, 0.632391, 0.613082, 1; 14:18:24.812 [planetinfo.record]: polar_land_color = 0.873801, 0.917069, 0.934, 1; 14:18:24.812 [planetinfo.record]: polar_sea_color = 0.947266, 0.908203, 0.729876, 1; 14:18:24.812 [planetinfo.record]: sea_color = 0.527344, 0.44036, 0.0432587, 1; 14:18:24.812 [planetinfo.record]: rotation_speed = 0.002817 14:18:24.812 [planetinfo.record]: planet zpos = 397040.000000 14:18:24.812 [planetinfo.record]: sun_radius = 64049.304810 14:18:24.812 [planetinfo.record]: sun_vector = 0.987 -0.096 -0.125 14:18:24.812 [planetinfo.record]: sun_distance = 567200 14:18:24.812 [planetinfo.record]: corona_flare = 0.036465 14:18:24.813 [planetinfo.record]: corona_hues = 0.866402 14:18:24.813 [planetinfo.record]: sun_color = 0.705646, 0.354135, 0.291899, 1 14:18:24.813 [planetinfo.record]: corona_shimmer = 0.462720 14:18:24.813 [planetinfo.record]: station_vector = -0.414 -0.853 0.318 14:18:24.813 [planetinfo.record]: station = coriolis 14:18:29.473 [PLANETINFO OVER]: Done 14:18:29.474 [PLANETINFO LOGGING]: 6 111 14:18:30.476 [planetinfo.record]: seed = 210 226 80 21 14:18:30.476 [planetinfo.record]: coordinates = 226 123 14:18:30.476 [planetinfo.record]: government = 2; 14:18:30.476 [planetinfo.record]: economy = 3; 14:18:30.476 [planetinfo.record]: techlevel = 7; 14:18:30.477 [planetinfo.record]: population = 34; 14:18:30.477 [planetinfo.record]: productivity = 11424; 14:18:30.477 [planetinfo.record]: name = "Labeinla"; 14:18:30.477 [planetinfo.record]: inhabitant = "Human Colonial"; 14:18:30.477 [planetinfo.record]: inhabitants = "Human Colonials"; 14:18:30.477 [planetinfo.record]: description = "This world is a revolting little planet."; 14:18:30.478 [planetinfo.record]: air_color = 0.580916, 0.93201, 0.860391, 1; 14:18:30.478 [planetinfo.record]: cloud_alpha = 1.000000; 14:18:30.478 [planetinfo.record]: cloud_color = 0.798828, 0.543539, 0.374451, 1; 14:18:30.478 [planetinfo.record]: cloud_fraction = 0.180000; 14:18:30.478 [planetinfo.record]: land_color = 0.139313, 0.624572, 0.648438, 1; 14:18:30.478 [planetinfo.record]: land_fraction = 0.310000; 14:18:30.479 [planetinfo.record]: polar_cloud_color = 0.859473, 0.687804, 0.574101, 1; 14:18:30.479 [planetinfo.record]: polar_land_color = 0.751595, 0.926552, 0.935156, 1; 14:18:30.479 [planetinfo.record]: polar_sea_color = 0.918273, 0.92793, 0.871746, 1; 14:18:30.479 [planetinfo.record]: sea_color = 0.690703, 0.720703, 0.546158, 1; 14:18:30.479 [planetinfo.record]: rotation_speed = 0.002621 14:18:30.479 [planetinfo.record]: planet zpos = 518640.000000 14:18:30.479 [planetinfo.record]: sun_radius = 117205.628357 14:18:30.479 [planetinfo.record]: sun_vector = 0.587 -0.575 0.570 14:18:30.479 [planetinfo.record]: sun_distance = 864400 14:18:30.479 [planetinfo.record]: corona_flare = 0.087405 14:18:30.479 [planetinfo.record]: corona_hues = 0.846649 14:18:30.479 [planetinfo.record]: sun_color = 0.525315, 0.531865, 0.666858, 1 14:18:30.479 [planetinfo.record]: corona_shimmer = 0.328202 14:18:30.479 [planetinfo.record]: station_vector = 0.428 -0.782 0.453 14:18:30.479 [planetinfo.record]: station = coriolis 14:18:34.533 [PLANETINFO OVER]: Done 14:18:34.535 [PLANETINFO LOGGING]: 6 112 14:18:35.539 [planetinfo.record]: seed = 82 245 84 213 14:18:35.539 [planetinfo.record]: coordinates = 245 108 14:18:35.539 [planetinfo.record]: government = 2; 14:18:35.539 [planetinfo.record]: economy = 4; 14:18:35.539 [planetinfo.record]: techlevel = 5; 14:18:35.539 [planetinfo.record]: population = 27; 14:18:35.540 [planetinfo.record]: productivity = 7776; 14:18:35.540 [planetinfo.record]: name = "Lavele"; 14:18:35.540 [planetinfo.record]: inhabitant = "Human Colonial"; 14:18:35.540 [planetinfo.record]: inhabitants = "Human Colonials"; 14:18:35.540 [planetinfo.record]: description = "This planet is most notable for its fabulous Rearce juice but plagued by lethal disease."; 14:18:35.556 [planetinfo.record]: air_color = 0.752753, 0.501391, 0.963229, 1; 14:18:35.556 [planetinfo.record]: cloud_alpha = 1.000000; 14:18:35.556 [planetinfo.record]: cloud_color = 0.892578, 0.142952, 0.406492, 1; 14:18:35.556 [planetinfo.record]: cloud_fraction = 0.330000; 14:18:35.556 [planetinfo.record]: land_color = 0.651581, 0.654297, 0.567398, 1; 14:18:35.556 [planetinfo.record]: land_fraction = 0.340000; 14:18:35.556 [planetinfo.record]: polar_cloud_color = 0.90166, 0.428377, 0.594765, 1; 14:18:35.556 [planetinfo.record]: polar_land_color = 0.933601, 0.93457, 0.90354, 1; 14:18:35.556 [planetinfo.record]: polar_sea_color = 0.762864, 0.940039, 0.877751, 1; 14:18:35.556 [planetinfo.record]: sea_color = 0.14756, 0.599609, 0.440686, 1; 14:18:35.556 [planetinfo.record]: rotation_speed = 0.003181 14:18:35.556 [planetinfo.record]: planet zpos = 520920.000000 14:18:35.557 [planetinfo.record]: sun_radius = 100207.443695 14:18:35.557 [planetinfo.record]: sun_vector = -0.445 0.879 0.172 14:18:35.557 [planetinfo.record]: sun_distance = 868200 14:18:35.557 [planetinfo.record]: corona_flare = 0.015535 14:18:35.557 [planetinfo.record]: corona_hues = 0.816643 14:18:35.557 [planetinfo.record]: sun_color = 0.280049, 0.465107, 0.685034, 1 14:18:35.557 [planetinfo.record]: corona_shimmer = 0.577166 14:18:35.557 [planetinfo.record]: station_vector = -0.361 -0.065 0.930 14:18:35.557 [planetinfo.record]: station = coriolis 14:18:40.551 [PLANETINFO OVER]: Done 14:18:40.551 [PLANETINFO LOGGING]: 6 113 14:18:41.558 [planetinfo.record]: seed = 106 13 128 69 14:18:41.558 [planetinfo.record]: coordinates = 13 1 14:18:41.558 [planetinfo.record]: government = 5; 14:18:41.558 [planetinfo.record]: economy = 1; 14:18:41.558 [planetinfo.record]: techlevel = 10; 14:18:41.558 [planetinfo.record]: population = 47; 14:18:41.558 [planetinfo.record]: productivity = 30456; 14:18:41.558 [planetinfo.record]: name = "Ceraso"; 14:18:41.558 [planetinfo.record]: inhabitant = "Fierce Yellow Fat Humanoid"; 14:18:41.559 [planetinfo.record]: inhabitants = "Fierce Yellow Fat Humanoids"; 14:18:41.559 [planetinfo.record]: description = "The planet Ceraso is reasonably fabled for its exciting sit coms and its inhabitants’ exceptional love for food blenders."; 14:18:41.568 [planetinfo.record]: air_color = 0.479345, 0.551787, 0.896238, 1; 14:18:41.568 [planetinfo.record]: cloud_alpha = 1.000000; 14:18:41.568 [planetinfo.record]: cloud_color = 0.140442, 0.420228, 0.691406, 1; 14:18:41.568 [planetinfo.record]: cloud_fraction = 0.320000; 14:18:41.568 [planetinfo.record]: land_color = 0.597656, 0.551311, 0.520615, 1; 14:18:41.568 [planetinfo.record]: land_fraction = 0.590000; 14:18:41.568 [planetinfo.record]: polar_cloud_color = 0.407151, 0.612298, 0.811133, 1; 14:18:41.569 [planetinfo.record]: polar_land_color = 0.940234, 0.922007, 0.909934, 1; 14:18:41.569 [planetinfo.record]: polar_sea_color = 0.792388, 0.907422, 0.788677, 1; 14:18:41.569 [planetinfo.record]: sea_color = 0.456336, 0.925781, 0.441193, 1; 14:18:41.569 [planetinfo.record]: rotation_speed = 0.004174 14:18:41.569 [planetinfo.record]: planet zpos = 493080.000000 14:18:41.569 [planetinfo.record]: sun_radius = 101294.223328 14:18:41.569 [planetinfo.record]: sun_vector = -0.572 -0.589 0.571 14:18:41.569 [planetinfo.record]: sun_distance = 862890 14:18:41.569 [planetinfo.record]: corona_flare = 0.096420 14:18:41.569 [planetinfo.record]: corona_hues = 0.851921 14:18:41.569 [planetinfo.record]: sun_color = 0.807275, 0.42743, 0.256899, 1 14:18:41.569 [planetinfo.record]: corona_shimmer = 0.363265 14:18:41.569 [planetinfo.record]: station_vector = -1.000 0.016 0.019 14:18:41.569 [planetinfo.record]: station = coriolis 14:18:46.001 [PLANETINFO OVER]: Done 14:18:46.002 [PLANETINFO LOGGING]: 6 114 14:18:47.004 [planetinfo.record]: seed = 250 64 116 60 14:18:47.004 [planetinfo.record]: coordinates = 64 167 14:18:47.004 [planetinfo.record]: government = 7; 14:18:47.004 [planetinfo.record]: economy = 7; 14:18:47.004 [planetinfo.record]: techlevel = 4; 14:18:47.004 [planetinfo.record]: population = 31; 14:18:47.004 [planetinfo.record]: productivity = 8184; 14:18:47.004 [planetinfo.record]: name = "Tezaxe"; 14:18:47.004 [planetinfo.record]: inhabitant = "Human Colonial"; 14:18:47.004 [planetinfo.record]: inhabitants = "Human Colonials"; 14:18:47.004 [planetinfo.record]: description = "The world Tezaxe is mildly noted for the Tezaxeian mountain lobstoid but beset by dreadful earthquakes."; 14:18:47.017 [planetinfo.record]: air_color = 0.443387, 0.804946, 0.835102, 1; 14:18:47.017 [planetinfo.record]: cloud_alpha = 1.000000; 14:18:47.017 [planetinfo.record]: cloud_color = 0.408367, 0.507812, 0.0971985, 1; 14:18:47.017 [planetinfo.record]: cloud_fraction = 0.150000; 14:18:47.017 [planetinfo.record]: land_color = 0.541406, 0.66, 0.584952, 1; 14:18:47.017 [planetinfo.record]: land_fraction = 0.530000; 14:18:47.017 [planetinfo.record]: polar_cloud_color = 0.639349, 0.728516, 0.360345, 1; 14:18:47.017 [planetinfo.record]: polar_land_color = 0.892043, 0.934, 0.907449, 1; 14:18:47.017 [planetinfo.record]: polar_sea_color = 0.908733, 0.868126, 0.945703, 1; 14:18:47.017 [planetinfo.record]: sea_color = 0.458064, 0.364807, 0.542969, 1; 14:18:47.017 [planetinfo.record]: rotation_speed = 0.003396 14:18:47.017 [planetinfo.record]: planet zpos = 773760.000000 14:18:47.017 [planetinfo.record]: sun_radius = 156431.630859 14:18:47.017 [planetinfo.record]: sun_vector = -0.636 -0.476 0.608 14:18:47.017 [planetinfo.record]: sun_distance = 1309440 14:18:47.017 [planetinfo.record]: corona_flare = 0.020853 14:18:47.017 [planetinfo.record]: corona_hues = 0.967056 14:18:47.017 [planetinfo.record]: sun_color = 0.842847, 0.794198, 0.694073, 1 14:18:47.018 [planetinfo.record]: corona_shimmer = 0.257602 14:18:47.018 [planetinfo.record]: station_vector = -0.983 -0.181 0.043 14:18:47.018 [planetinfo.record]: station = coriolis 14:18:51.844 [PLANETINFO OVER]: Done 14:18:51.845 [PLANETINFO LOGGING]: 6 115 14:18:52.850 [planetinfo.record]: seed = 34 3 208 201 14:18:52.850 [planetinfo.record]: coordinates = 3 162 14:18:52.850 [planetinfo.record]: government = 4; 14:18:52.850 [planetinfo.record]: economy = 2; 14:18:52.850 [planetinfo.record]: techlevel = 10; 14:18:52.850 [planetinfo.record]: population = 47; 14:18:52.850 [planetinfo.record]: productivity = 24064; 14:18:52.850 [planetinfo.record]: name = "Esaan"; 14:18:52.850 [planetinfo.record]: inhabitant = "Small Bug-Eyed Lizard"; 14:18:52.850 [planetinfo.record]: inhabitants = "Small Bug-Eyed Lizards"; 14:18:52.850 [planetinfo.record]: description = "This world is a tedious place."; 14:18:52.852 [planetinfo.record]: air_color = 0.661703, 0.559195, 0.889734, 1; 14:18:52.852 [planetinfo.record]: cloud_alpha = 1.000000; 14:18:52.852 [planetinfo.record]: cloud_color = 0.652929, 0.325439, 0.671875, 1; 14:18:52.852 [planetinfo.record]: cloud_fraction = 0.300000; 14:18:52.852 [planetinfo.record]: land_color = 0.227005, 0.509766, 0.443494, 1; 14:18:52.852 [planetinfo.record]: land_fraction = 0.370000; 14:18:52.852 [planetinfo.record]: polar_cloud_color = 0.788203, 0.543776, 0.802344, 1; 14:18:52.852 [planetinfo.record]: polar_land_color = 0.817421, 0.949023, 0.918179, 1; 14:18:52.852 [planetinfo.record]: polar_sea_color = 0.859565, 0.905272, 0.91582, 1; 14:18:52.852 [planetinfo.record]: sea_color = 0.634965, 0.803016, 0.841797, 1; 14:18:52.852 [planetinfo.record]: rotation_speed = 0.000795 14:18:52.852 [planetinfo.record]: planet zpos = 563530.000000 14:18:52.852 [planetinfo.record]: sun_radius = 148859.827728 14:18:52.853 [planetinfo.record]: sun_vector = -0.210 0.675 0.707 14:18:52.853 [planetinfo.record]: sun_distance = 922140 14:18:52.853 [planetinfo.record]: corona_flare = 0.025220 14:18:52.853 [planetinfo.record]: corona_hues = 0.856895 14:18:52.853 [planetinfo.record]: sun_color = 0.648319, 0.755948, 0.831973, 1 14:18:52.853 [planetinfo.record]: corona_shimmer = 0.299815 14:18:52.853 [planetinfo.record]: station_vector = -0.618 0.732 0.286 14:18:52.853 [planetinfo.record]: station = coriolis 14:18:57.478 [PLANETINFO OVER]: Done 14:18:57.479 [PLANETINFO LOGGING]: 6 116 14:18:58.482 [planetinfo.record]: seed = 194 116 180 31 14:18:58.482 [planetinfo.record]: coordinates = 116 59 14:18:58.482 [planetinfo.record]: government = 0; 14:18:58.482 [planetinfo.record]: economy = 3; 14:18:58.482 [planetinfo.record]: techlevel = 4; 14:18:58.482 [planetinfo.record]: population = 20; 14:18:58.482 [planetinfo.record]: productivity = 4480; 14:18:58.482 [planetinfo.record]: name = "Onerzara"; 14:18:58.482 [planetinfo.record]: inhabitant = "Green Lizard"; 14:18:58.482 [planetinfo.record]: inhabitants = "Green Lizards"; 14:18:58.482 [planetinfo.record]: description = "The world Onerzara is reasonably famous for the Onerzaraian spotted shrew."; 14:18:58.496 [planetinfo.record]: air_color = 0.7579, 0.619981, 0.86632, 1; 14:18:58.496 [planetinfo.record]: cloud_alpha = 1.000000; 14:18:58.496 [planetinfo.record]: cloud_color = 0.601562, 0.448822, 0.49536, 1; 14:18:58.496 [planetinfo.record]: cloud_fraction = 0.150000; 14:18:58.496 [planetinfo.record]: land_color = 0.0819041, 0.603516, 0.0777969, 1; 14:18:58.496 [planetinfo.record]: land_fraction = 0.280000; 14:18:58.496 [planetinfo.record]: polar_cloud_color = 0.770703, 0.648399, 0.685664, 1; 14:18:58.497 [planetinfo.record]: polar_land_color = 0.736617, 0.939648, 0.735018, 1; 14:18:58.497 [planetinfo.record]: polar_sea_color = 0.931641, 0.896357, 0.865225, 1; 14:18:58.497 [planetinfo.record]: sea_color = 0.683594, 0.580037, 0.488663, 1; 14:18:58.497 [planetinfo.record]: rotation_speed = 0.003703 14:18:58.497 [planetinfo.record]: planet zpos = 812640.000000 14:18:58.497 [planetinfo.record]: sun_radius = 175090.754395 14:18:58.497 [planetinfo.record]: sun_vector = -0.141 0.144 -0.979 14:18:58.498 [planetinfo.record]: sun_distance = 1422120 14:18:58.498 [planetinfo.record]: corona_flare = 0.094699 14:18:58.498 [planetinfo.record]: corona_hues = 0.867546 14:18:58.498 [planetinfo.record]: sun_color = 0.225754, 0.403356, 0.837747, 1 14:18:58.498 [planetinfo.record]: corona_shimmer = 0.369125 14:18:58.499 [planetinfo.record]: station_vector = -0.600 0.730 0.327 14:18:58.500 [planetinfo.record]: station = coriolis 14:19:02.963 [PLANETINFO OVER]: Done 14:19:02.964 [PLANETINFO LOGGING]: 6 117 14:19:03.967 [planetinfo.record]: seed = 250 84 64 138 14:19:03.967 [planetinfo.record]: coordinates = 84 100 14:19:03.967 [planetinfo.record]: government = 7; 14:19:03.968 [planetinfo.record]: economy = 4; 14:19:03.968 [planetinfo.record]: techlevel = 7; 14:19:03.968 [planetinfo.record]: population = 40; 14:19:03.968 [planetinfo.record]: productivity = 21120; 14:19:03.968 [planetinfo.record]: name = "Arzageat"; 14:19:03.968 [planetinfo.record]: inhabitant = "Human Colonial"; 14:19:03.968 [planetinfo.record]: inhabitants = "Human Colonials"; 14:19:03.968 [planetinfo.record]: description = "This world is very notable for the Arzageatian tree snake and Arzageatian shrew cutlet."; 14:19:04.006 [planetinfo.record]: air_color = 0.757291, 0.655337, 0.8325, 1; 14:19:04.006 [planetinfo.record]: cloud_alpha = 1.000000; 14:19:04.006 [planetinfo.record]: cloud_color = 0.5, 0.480469, 0.485504, 1; 14:19:04.006 [planetinfo.record]: cloud_fraction = 0.260000; 14:19:04.006 [planetinfo.record]: land_color = 0.66, 0.557358, 0.425391, 1; 14:19:04.006 [planetinfo.record]: land_fraction = 0.550000; 14:19:04.006 [planetinfo.record]: polar_cloud_color = 0.725, 0.7073, 0.711863, 1; 14:19:04.006 [planetinfo.record]: polar_land_color = 0.934, 0.897687, 0.850998, 1; 14:19:04.006 [planetinfo.record]: polar_sea_color = 0.937623, 0.885663, 0.94668, 1; 14:19:04.007 [planetinfo.record]: sea_color = 0.512798, 0.395737, 0.533203, 1; 14:19:04.007 [planetinfo.record]: rotation_speed = 0.002341 14:19:04.007 [planetinfo.record]: planet zpos = 764400.000000 14:19:04.007 [planetinfo.record]: sun_radius = 180658.383179 14:19:04.007 [planetinfo.record]: sun_vector = 0.182 0.925 -0.333 14:19:04.007 [planetinfo.record]: sun_distance = 1092000 14:19:04.007 [planetinfo.record]: corona_flare = 0.092941 14:19:04.007 [planetinfo.record]: corona_hues = 0.599884 14:19:04.007 [planetinfo.record]: sun_color = 0.457311, 0.652216, 0.803333, 1 14:19:04.007 [planetinfo.record]: corona_shimmer = 0.325923 14:19:04.007 [planetinfo.record]: station_vector = -0.036 -0.978 0.207 14:19:04.007 [planetinfo.record]: station = coriolis 14:19:08.442 [PLANETINFO OVER]: Done 14:19:08.443 [PLANETINFO LOGGING]: 6 118 14:19:09.447 [planetinfo.record]: seed = 170 241 20 89 14:19:09.447 [planetinfo.record]: coordinates = 241 35 14:19:09.447 [planetinfo.record]: government = 5; 14:19:09.447 [planetinfo.record]: economy = 3; 14:19:09.447 [planetinfo.record]: techlevel = 8; 14:19:09.447 [planetinfo.record]: population = 41; 14:19:09.447 [planetinfo.record]: productivity = 20664; 14:19:09.447 [planetinfo.record]: name = "Orreedon"; 14:19:09.447 [planetinfo.record]: inhabitant = "Human Colonial"; 14:19:09.447 [planetinfo.record]: inhabitants = "Human Colonials"; 14:19:09.447 [planetinfo.record]: description = "The planet Orreedon is most famous for the Orreedonian deadly goat and its pink Orreedonian Re Reweed plantations."; 14:19:09.460 [planetinfo.record]: air_color = 0.547804, 0.587216, 0.840305, 1; 14:19:09.460 [planetinfo.record]: cloud_alpha = 1.000000; 14:19:09.460 [planetinfo.record]: cloud_color = 0.290344, 0.381396, 0.523438, 1; 14:19:09.460 [planetinfo.record]: cloud_fraction = 0.550000; 14:19:09.460 [planetinfo.record]: land_color = 0.366577, 0.300781, 0.601562, 1; 14:19:09.460 [planetinfo.record]: land_fraction = 0.500000; 14:19:09.460 [planetinfo.record]: polar_cloud_color = 0.530829, 0.610797, 0.735547, 1; 14:19:09.461 [planetinfo.record]: polar_land_color = 0.848062, 0.822363, 0.939844, 1; 14:19:09.461 [planetinfo.record]: polar_sea_color = 0.91291, 0.926367, 0.864851, 1; 14:19:09.461 [planetinfo.record]: sea_color = 0.693543, 0.736328, 0.540741, 1; 14:19:09.462 [planetinfo.record]: rotation_speed = 0.004061 14:19:09.462 [planetinfo.record]: planet zpos = 589710.000000 14:19:09.462 [planetinfo.record]: sun_radius = 123788.251648 14:19:09.462 [planetinfo.record]: sun_vector = -0.351 0.569 -0.743 14:19:09.462 [planetinfo.record]: sun_distance = 911370 14:19:09.462 [planetinfo.record]: corona_flare = 0.046172 14:19:09.462 [planetinfo.record]: corona_hues = 0.969391 14:19:09.462 [planetinfo.record]: sun_color = 0.772614, 0.768438, 0.726141, 1 14:19:09.462 [planetinfo.record]: corona_shimmer = 0.477341 14:19:09.462 [planetinfo.record]: station_vector = -0.272 0.747 0.606 14:19:09.462 [planetinfo.record]: station = coriolis 14:19:14.770 [PLANETINFO OVER]: Done 14:19:14.772 [PLANETINFO LOGGING]: 6 119 14:19:15.774 [planetinfo.record]: seed = 242 127 208 166 14:19:15.774 [planetinfo.record]: coordinates = 127 184 14:19:15.774 [planetinfo.record]: government = 6; 14:19:15.774 [planetinfo.record]: economy = 0; 14:19:15.774 [planetinfo.record]: techlevel = 13; 14:19:15.775 [planetinfo.record]: population = 59; 14:19:15.775 [planetinfo.record]: productivity = 47200; 14:19:15.775 [planetinfo.record]: name = "Bionbiin"; 14:19:15.775 [planetinfo.record]: inhabitant = "Fierce Harmless Frog"; 14:19:15.775 [planetinfo.record]: inhabitants = "Fierce Harmless Frogs"; 14:19:15.775 [planetinfo.record]: description = "Bionbiin is well known for the Bionbiinian tree ant but cursed by vicious mountain beasts."; 14:19:15.804 [planetinfo.record]: air_color = 0.411838, 0.850711, 0.839968, 1; 14:19:15.804 [planetinfo.record]: cloud_alpha = 1.000000; 14:19:15.804 [planetinfo.record]: cloud_color = 0.554688, 0.52178, 0.0281677, 1; 14:19:15.804 [planetinfo.record]: cloud_fraction = 0.370000; 14:19:15.804 [planetinfo.record]: land_color = 0.597964, 0.546562, 0.66, 1; 14:19:15.804 [planetinfo.record]: land_fraction = 0.620000; 14:19:15.804 [planetinfo.record]: polar_cloud_color = 0.749609, 0.721815, 0.304895, 1; 14:19:15.804 [planetinfo.record]: polar_land_color = 0.912052, 0.893867, 0.934, 1; 14:19:15.804 [planetinfo.record]: polar_sea_color = 0.945703, 0.922799, 0.898418, 1; 14:19:15.804 [planetinfo.record]: sea_color = 0.542969, 0.490369, 0.434375, 1; 14:19:15.804 [planetinfo.record]: rotation_speed = 0.003923 14:19:15.804 [planetinfo.record]: planet zpos = 537480.000000 14:19:15.804 [planetinfo.record]: sun_radius = 113864.031372 14:19:15.804 [planetinfo.record]: sun_vector = -0.441 0.896 0.050 14:19:15.805 [planetinfo.record]: sun_distance = 895800 14:19:15.805 [planetinfo.record]: corona_flare = 0.040836 14:19:15.811 [planetinfo.record]: corona_hues = 0.617363 14:19:15.811 [planetinfo.record]: sun_color = 0.668048, 0.664202, 0.204718, 1 14:19:15.812 [planetinfo.record]: corona_shimmer = 0.392126 14:19:15.812 [planetinfo.record]: station_vector = 0.757 0.653 0.017 14:19:15.812 [planetinfo.record]: station = dodecahedron 14:19:21.259 [PLANETINFO OVER]: Done 14:19:21.259 [PLANETINFO LOGGING]: 6 120 14:19:22.262 [planetinfo.record]: seed = 178 140 148 114 14:19:22.262 [planetinfo.record]: coordinates = 140 6 14:19:22.262 [planetinfo.record]: government = 6; 14:19:22.262 [planetinfo.record]: economy = 6; 14:19:22.262 [planetinfo.record]: techlevel = 4; 14:19:22.263 [planetinfo.record]: population = 29; 14:19:22.263 [planetinfo.record]: productivity = 9280; 14:19:22.263 [planetinfo.record]: name = "Encezais"; 14:19:22.263 [planetinfo.record]: inhabitant = "Blue Horned Bird"; 14:19:22.263 [planetinfo.record]: inhabitants = "Blue Horned Birds"; 14:19:22.263 [planetinfo.record]: description = "This world is ravaged by dreadful civil war."; 14:19:22.280 [planetinfo.record]: air_color = 0.712048, 0.498911, 0.956074, 1; 14:19:22.280 [planetinfo.record]: cloud_alpha = 1.000000; 14:19:22.280 [planetinfo.record]: cloud_color = 0.871094, 0.142914, 0.569582, 1; 14:19:22.280 [planetinfo.record]: cloud_fraction = 0.310000; 14:19:22.280 [planetinfo.record]: land_color = 0.640625, 0.390381, 0.560469, 1; 14:19:22.280 [planetinfo.record]: land_fraction = 0.380000; 14:19:22.280 [planetinfo.record]: polar_cloud_color = 0.891992, 0.425961, 0.699026, 1; 14:19:22.280 [planetinfo.record]: polar_land_color = 0.935938, 0.844537, 0.906661, 1; 14:19:22.280 [planetinfo.record]: polar_sea_color = 0.935352, 0.897915, 0.879632, 1; 14:19:22.280 [planetinfo.record]: sea_color = 0.646484, 0.542985, 0.492439, 1; 14:19:22.280 [planetinfo.record]: rotation_speed = 0.004370 14:19:22.280 [planetinfo.record]: planet zpos = 450840.000000 14:19:22.280 [planetinfo.record]: sun_radius = 71868.817749 14:19:22.280 [planetinfo.record]: sun_vector = 0.316 -0.921 -0.228 14:19:22.280 [planetinfo.record]: sun_distance = 589560 14:19:22.280 [planetinfo.record]: corona_flare = 0.066371 14:19:22.280 [planetinfo.record]: corona_hues = 0.849380 14:19:22.280 [planetinfo.record]: sun_color = 0.72565, 0.720542, 0.715344, 1 14:19:22.281 [planetinfo.record]: corona_shimmer = 0.698529 14:19:22.281 [planetinfo.record]: station_vector = 0.900 0.137 0.415 14:19:22.281 [planetinfo.record]: station = coriolis 14:19:26.921 [PLANETINFO OVER]: Done 14:19:26.923 [PLANETINFO LOGGING]: 6 121 14:19:27.925 [planetinfo.record]: seed = 10 125 128 135 14:19:27.926 [planetinfo.record]: coordinates = 125 4 14:19:27.926 [planetinfo.record]: government = 1; 14:19:27.926 [planetinfo.record]: economy = 6; 14:19:27.926 [planetinfo.record]: techlevel = 3; 14:19:27.926 [planetinfo.record]: population = 20; 14:19:27.926 [planetinfo.record]: productivity = 3200; 14:19:27.926 [planetinfo.record]: name = "Soesdiri"; 14:19:27.926 [planetinfo.record]: inhabitant = "Fierce Black Bug-Eyed Bird"; 14:19:27.926 [planetinfo.record]: inhabitants = "Fierce Black Bug-Eyed Birds"; 14:19:27.926 [planetinfo.record]: description = "Soesdiri is ravaged by unpredictable civil war."; 14:19:27.929 [planetinfo.record]: air_color = 0.638949, 0.807142, 0.974285, 1; 14:19:27.929 [planetinfo.record]: cloud_alpha = 1.000000; 14:19:27.929 [planetinfo.record]: cloud_color = 0.524368, 0.925781, 0.728211, 1; 14:19:27.929 [planetinfo.record]: cloud_fraction = 0.230000; 14:19:27.929 [planetinfo.record]: land_color = 0.427365, 0.234609, 0.66, 1; 14:19:27.930 [planetinfo.record]: land_fraction = 0.280000; 14:19:27.930 [planetinfo.record]: polar_cloud_color = 0.668206, 0.916602, 0.794344, 1; 14:19:27.930 [planetinfo.record]: polar_land_color = 0.851696, 0.783502, 0.934, 1; 14:19:27.930 [planetinfo.record]: polar_sea_color = 0.880469, 0.929102, 0.894907, 1; 14:19:27.930 [planetinfo.record]: sea_color = 0.560541, 0.708984, 0.60461, 1; 14:19:27.930 [planetinfo.record]: rotation_speed = 0.000471 14:19:27.930 [planetinfo.record]: planet zpos = 520630.000000 14:19:27.930 [planetinfo.record]: sun_radius = 128819.266205 14:19:27.930 [planetinfo.record]: sun_vector = 0.061 0.502 0.863 14:19:27.930 [planetinfo.record]: sun_distance = 899270 14:19:27.931 [planetinfo.record]: corona_flare = 0.048773 14:19:27.931 [planetinfo.record]: corona_hues = 0.740692 14:19:27.931 [planetinfo.record]: sun_color = 0.70907, 0.689819, 0.235831, 1 14:19:27.931 [planetinfo.record]: corona_shimmer = 0.321968 14:19:27.931 [planetinfo.record]: station_vector = 0.710 0.474 0.520 14:19:27.931 [planetinfo.record]: station = coriolis 14:19:33.250 [PLANETINFO OVER]: Done 14:19:33.251 [PLANETINFO LOGGING]: 6 122 14:19:34.255 [planetinfo.record]: seed = 218 158 52 182 14:19:34.255 [planetinfo.record]: coordinates = 158 13 14:19:34.255 [planetinfo.record]: government = 3; 14:19:34.255 [planetinfo.record]: economy = 5; 14:19:34.255 [planetinfo.record]: techlevel = 6; 14:19:34.255 [planetinfo.record]: population = 33; 14:19:34.255 [planetinfo.record]: productivity = 9240; 14:19:34.255 [planetinfo.record]: name = "Vegeedat"; 14:19:34.255 [planetinfo.record]: inhabitant = "Human Colonial"; 14:19:34.255 [planetinfo.record]: inhabitants = "Human Colonials"; 14:19:34.255 [planetinfo.record]: description = "The planet Vegeedat is a boring world."; 14:19:34.257 [planetinfo.record]: air_color = 0.517758, 0.515618, 0.888434, 1; 14:19:34.257 [planetinfo.record]: cloud_alpha = 1.000000; 14:19:34.257 [planetinfo.record]: cloud_color = 0.240785, 0.227005, 0.667969, 1; 14:19:34.257 [planetinfo.record]: cloud_fraction = 0.470000; 14:19:34.257 [planetinfo.record]: land_color = 0.66, 0.595627, 0.484688, 1; 14:19:34.257 [planetinfo.record]: land_fraction = 0.600000; 14:19:34.257 [planetinfo.record]: polar_cloud_color = 0.480589, 0.470266, 0.800586, 1; 14:19:34.257 [planetinfo.record]: polar_land_color = 0.934, 0.911226, 0.871977, 1; 14:19:34.257 [planetinfo.record]: polar_sea_color = 0.869429, 0.875694, 0.916602, 1; 14:19:34.257 [planetinfo.record]: sea_color = 0.662301, 0.685103, 0.833984, 1; 14:19:34.257 [planetinfo.record]: rotation_speed = 0.004716 14:19:34.257 [planetinfo.record]: planet zpos = 586300.000000 14:19:34.257 [planetinfo.record]: sun_radius = 117498.382568 14:19:34.257 [planetinfo.record]: sun_vector = -0.864 0.278 -0.420 14:19:34.257 [planetinfo.record]: sun_distance = 856900 14:19:34.257 [planetinfo.record]: corona_flare = 0.044896 14:19:34.257 [planetinfo.record]: corona_hues = 0.953445 14:19:34.257 [planetinfo.record]: sun_color = 0.585378, 0.636043, 0.682394, 1 14:19:34.259 [planetinfo.record]: corona_shimmer = 0.273158 14:19:34.260 [planetinfo.record]: station_vector = 0.991 -0.000 0.137 14:19:34.260 [planetinfo.record]: station = coriolis 14:19:39.342 [PLANETINFO OVER]: Done 14:19:39.342 [PLANETINFO LOGGING]: 6 123 14:19:40.346 [planetinfo.record]: seed = 66 209 80 236 14:19:40.346 [planetinfo.record]: coordinates = 209 184 14:19:40.346 [planetinfo.record]: government = 0; 14:19:40.346 [planetinfo.record]: economy = 2; 14:19:40.346 [planetinfo.record]: techlevel = 6; 14:19:40.346 [planetinfo.record]: population = 27; 14:19:40.346 [planetinfo.record]: productivity = 6912; 14:19:40.346 [planetinfo.record]: name = "Inlabe"; 14:19:40.346 [planetinfo.record]: inhabitant = "Human Colonial"; 14:19:40.346 [planetinfo.record]: inhabitants = "Human Colonials"; 14:19:40.346 [planetinfo.record]: description = "This planet is plagued by frequent solar activity."; 14:19:40.368 [planetinfo.record]: air_color = 0.672363, 0.72754, 0.989895, 1; 14:19:40.368 [planetinfo.record]: cloud_alpha = 1.000000; 14:19:40.368 [planetinfo.record]: cloud_color = 0.619308, 0.798743, 0.972656, 1; 14:19:40.368 [planetinfo.record]: cloud_fraction = 0.440000; 14:19:40.368 [planetinfo.record]: land_color = 0.629838, 0.632812, 0.57843, 1; 14:19:40.368 [planetinfo.record]: land_fraction = 0.690000; 14:19:40.368 [planetinfo.record]: polar_cloud_color = 0.724791, 0.832906, 0.937695, 1; 14:19:40.369 [planetinfo.record]: polar_land_color = 0.935618, 0.936719, 0.916594, 1; 14:19:40.369 [planetinfo.record]: polar_sea_color = 0.762289, 0.901367, 0.777501, 1; 14:19:40.369 [planetinfo.record]: sea_color = 0.377579, 0.986328, 0.444161, 1; 14:19:40.369 [planetinfo.record]: rotation_speed = 0.002093 14:19:40.369 [planetinfo.record]: planet zpos = 731640.000000 14:19:40.369 [planetinfo.record]: sun_radius = 160143.004150 14:19:40.369 [planetinfo.record]: sun_vector = 0.483 0.834 0.266 14:19:40.369 [planetinfo.record]: sun_distance = 1280370 14:19:40.369 [planetinfo.record]: corona_flare = 0.029524 14:19:40.369 [planetinfo.record]: corona_hues = 0.891716 14:19:40.369 [planetinfo.record]: sun_color = 0.532256, 0.63763, 0.652118, 1 14:19:40.369 [planetinfo.record]: corona_shimmer = 0.353988 14:19:40.369 [planetinfo.record]: station_vector = 0.162 -0.939 0.304 14:19:40.369 [planetinfo.record]: station = coriolis 14:19:45.479 [PLANETINFO OVER]: Done 14:19:45.480 [PLANETINFO LOGGING]: 6 124 14:19:46.494 [planetinfo.record]: seed = 34 149 244 61 14:19:46.494 [planetinfo.record]: coordinates = 149 51 14:19:46.494 [planetinfo.record]: government = 4; 14:19:46.494 [planetinfo.record]: economy = 3; 14:19:46.494 [planetinfo.record]: techlevel = 7; 14:19:46.494 [planetinfo.record]: population = 36; 14:19:46.494 [planetinfo.record]: productivity = 16128; 14:19:46.494 [planetinfo.record]: name = "Isbior"; 14:19:46.494 [planetinfo.record]: inhabitant = "Red Insect"; 14:19:46.494 [planetinfo.record]: inhabitants = "Red Insects"; 14:19:46.494 [planetinfo.record]: description = "This world is plagued by deadly earthquakes."; 14:19:46.512 [planetinfo.record]: air_color = 0.737297, 0.734177, 0.964529, 1; 14:19:46.512 [planetinfo.record]: cloud_alpha = 1.000000; 14:19:46.512 [planetinfo.record]: cloud_color = 0.797173, 0.791428, 0.896484, 1; 14:19:46.512 [planetinfo.record]: cloud_fraction = 0.520000; 14:19:46.512 [planetinfo.record]: land_color = 0.505859, 0.484123, 0.488708, 1; 14:19:46.512 [planetinfo.record]: land_fraction = 0.430000; 14:19:46.512 [planetinfo.record]: polar_cloud_color = 0.840868, 0.83725, 0.903418, 1; 14:19:46.512 [planetinfo.record]: polar_land_color = 0.949414, 0.939215, 0.941367, 1; 14:19:46.512 [planetinfo.record]: polar_sea_color = 0.943164, 0.909181, 0.890756, 1; 14:19:46.512 [planetinfo.record]: sea_color = 0.568359, 0.486444, 0.442033, 1; 14:19:46.512 [planetinfo.record]: rotation_speed = 0.004972 14:19:46.512 [planetinfo.record]: planet zpos = 818090.000000 14:19:46.512 [planetinfo.record]: sun_radius = 136360.176239 14:19:46.512 [planetinfo.record]: sun_vector = -0.197 -0.116 -0.974 14:19:46.512 [planetinfo.record]: sun_distance = 1258600 14:19:46.512 [planetinfo.record]: corona_flare = 0.046359 14:19:46.512 [planetinfo.record]: corona_hues = 0.757027 14:19:46.512 [planetinfo.record]: sun_color = 0.533099, 0.585685, 0.736264, 1 14:19:46.512 [planetinfo.record]: corona_shimmer = 0.315724 14:19:46.512 [planetinfo.record]: station_vector = 0.714 -0.688 0.130 14:19:46.512 [planetinfo.record]: station = coriolis 14:19:51.207 [PLANETINFO OVER]: Done 14:19:51.208 [PLANETINFO LOGGING]: 6 125 14:19:52.213 [planetinfo.record]: seed = 154 29 64 253 14:19:52.213 [planetinfo.record]: coordinates = 29 217 14:19:52.214 [planetinfo.record]: government = 3; 14:19:52.214 [planetinfo.record]: economy = 1; 14:19:52.214 [planetinfo.record]: techlevel = 9; 14:19:52.214 [planetinfo.record]: population = 41; 14:19:52.214 [planetinfo.record]: productivity = 20664; 14:19:52.214 [planetinfo.record]: name = "Israa"; 14:19:52.214 [planetinfo.record]: inhabitant = "Human Colonial"; 14:19:52.214 [planetinfo.record]: inhabitants = "Human Colonials"; 14:19:52.214 [planetinfo.record]: description = "Israa is mildly well known for its hoopy night life and Israaian lethal brandy."; 14:19:52.236 [planetinfo.record]: air_color = 0.725295, 0.583375, 0.887783, 1; 14:19:52.236 [planetinfo.record]: cloud_alpha = 1.000000; 14:19:52.236 [planetinfo.record]: cloud_color = 0.666016, 0.379837, 0.54752, 1; 14:19:52.236 [planetinfo.record]: cloud_fraction = 0.620000; 14:19:52.236 [planetinfo.record]: land_color = 0.66, 0.66, 0.66, 1; 14:19:52.236 [planetinfo.record]: land_fraction = 0.500000; 14:19:52.236 [planetinfo.record]: polar_cloud_color = 0.799707, 0.584942, 0.710781, 1; 14:19:52.236 [planetinfo.record]: polar_land_color = 0.934, 0.934, 0.934, 1; 14:19:52.236 [planetinfo.record]: polar_sea_color = 0.918945, 0.893327, 0.805872, 1; 14:19:52.236 [planetinfo.record]: sea_color = 0.810547, 0.720162, 0.411606, 1; 14:19:52.236 [planetinfo.record]: rotation_speed = 0.003635 14:19:52.236 [planetinfo.record]: planet zpos = 802490.000000 14:19:52.237 [planetinfo.record]: sun_radius = 162592.276154 14:19:52.237 [planetinfo.record]: sun_vector = 0.890 -0.154 -0.429 14:19:52.237 [planetinfo.record]: sun_distance = 1111140 14:19:52.237 [planetinfo.record]: corona_flare = 0.003461 14:19:52.237 [planetinfo.record]: corona_hues = 0.590088 14:19:52.237 [planetinfo.record]: sun_color = 0.564669, 0.696744, 0.737207, 1 14:19:52.237 [planetinfo.record]: corona_shimmer = 0.296895 14:19:52.237 [planetinfo.record]: station_vector = -0.861 0.044 0.507 14:19:52.237 [planetinfo.record]: station = coriolis 14:19:57.158 [PLANETINFO OVER]: Done 14:19:57.159 [PLANETINFO LOGGING]: 6 126 14:19:58.181 [planetinfo.record]: seed = 138 0 212 3 14:19:58.181 [planetinfo.record]: coordinates = 0 15 14:19:58.181 [planetinfo.record]: government = 1; 14:19:58.181 [planetinfo.record]: economy = 7; 14:19:58.181 [planetinfo.record]: techlevel = 1; 14:19:58.181 [planetinfo.record]: population = 13; 14:19:58.181 [planetinfo.record]: productivity = 1560; 14:19:58.181 [planetinfo.record]: name = "Gebeti"; 14:19:58.181 [planetinfo.record]: inhabitant = "Large Green Lizard"; 14:19:58.181 [planetinfo.record]: inhabitants = "Large Green Lizards"; 14:19:58.182 [planetinfo.record]: description = "Gebeti is very fabled for its ancient Gebetiian Et banana plantations."; 14:19:58.191 [planetinfo.record]: air_color = 0.435517, 0.732469, 0.871523, 1; 14:19:58.192 [planetinfo.record]: cloud_alpha = 1.000000; 14:19:58.192 [planetinfo.record]: cloud_color = 0.0776758, 0.617188, 0.0602722, 1; 14:19:58.192 [planetinfo.record]: cloud_fraction = 0.260000; 14:19:58.192 [planetinfo.record]: land_color = 0.500156, 0.66, 0.528878, 1; 14:19:58.192 [planetinfo.record]: land_fraction = 0.270000; 14:19:58.192 [planetinfo.record]: polar_cloud_color = 0.352826, 0.777734, 0.33912, 1; 14:19:58.192 [planetinfo.record]: polar_land_color = 0.877449, 0.934, 0.887611, 1; 14:19:58.192 [planetinfo.record]: polar_sea_color = 0.948047, 0.791293, 0.714738, 1; 14:19:58.192 [planetinfo.record]: sea_color = 0.519531, 0.175925, 0.00811768, 1; 14:19:58.192 [planetinfo.record]: rotation_speed = 0.000236 14:19:58.192 [planetinfo.record]: planet zpos = 501760.000000 14:19:58.192 [planetinfo.record]: sun_radius = 94845.625000 14:19:58.192 [planetinfo.record]: sun_vector = -0.901 0.053 0.430 14:19:58.192 [planetinfo.record]: sun_distance = 609280 14:19:58.192 [planetinfo.record]: corona_flare = 0.012398 14:19:58.192 [planetinfo.record]: corona_hues = 0.748749 14:19:58.192 [planetinfo.record]: sun_color = 0.373661, 0.508822, 0.700464, 1 14:19:58.193 [planetinfo.record]: corona_shimmer = 0.392192 14:19:58.193 [planetinfo.record]: station_vector = 0.626 -0.768 0.133 14:19:58.193 [planetinfo.record]: station = coriolis 14:20:03.016 [PLANETINFO OVER]: Done 14:20:03.017 [PLANETINFO LOGGING]: 6 127 14:20:04.019 [planetinfo.record]: seed = 18 47 80 90 14:20:04.019 [planetinfo.record]: coordinates = 47 23 14:20:04.019 [planetinfo.record]: government = 2; 14:20:04.019 [planetinfo.record]: economy = 7; 14:20:04.019 [planetinfo.record]: techlevel = 4; 14:20:04.019 [planetinfo.record]: population = 26; 14:20:04.019 [planetinfo.record]: productivity = 3744; 14:20:04.019 [planetinfo.record]: name = "Qulearbi"; 14:20:04.019 [planetinfo.record]: inhabitant = "Human Colonial"; 14:20:04.020 [planetinfo.record]: inhabitants = "Human Colonials"; 14:20:04.020 [planetinfo.record]: description = "The world Qulearbi is mildly noted for the Qulearbiian mountain lobstoid but beset by frequent solar activity."; 14:20:04.040 [planetinfo.record]: air_color = 0.64727, 0.882324, 0.922904, 1; 14:20:04.040 [planetinfo.record]: cloud_alpha = 1.000000; 14:20:04.040 [planetinfo.record]: cloud_color = 0.667703, 0.771484, 0.54245, 1; 14:20:04.040 [planetinfo.record]: cloud_fraction = 0.180000; 14:20:04.040 [planetinfo.record]: land_color = 0.646484, 0.602903, 0.527794, 1; 14:20:04.040 [planetinfo.record]: land_fraction = 0.650000; 14:20:04.040 [planetinfo.record]: polar_cloud_color = 0.775942, 0.847168, 0.689979, 1; 14:20:04.040 [planetinfo.record]: polar_land_color = 0.935352, 0.919588, 0.89242, 1; 14:20:04.040 [planetinfo.record]: polar_sea_color = 0.756068, 0.937305, 0.911818, 1; 14:20:04.040 [planetinfo.record]: sea_color = 0.142044, 0.626953, 0.558763, 1; 14:20:04.040 [planetinfo.record]: rotation_speed = 0.000126 14:20:04.040 [planetinfo.record]: planet zpos = 542300.000000 14:20:04.040 [planetinfo.record]: sun_radius = 136789.746704 14:20:04.040 [planetinfo.record]: sun_vector = -0.352 0.739 -0.575 14:20:04.040 [planetinfo.record]: sun_distance = 1030370 14:20:04.040 [planetinfo.record]: corona_flare = 0.007944 14:20:04.040 [planetinfo.record]: corona_hues = 0.930832 14:20:04.040 [planetinfo.record]: sun_color = 0.690994, 0.604447, 0.498945, 1 14:20:04.040 [planetinfo.record]: corona_shimmer = 0.266428 14:20:04.041 [planetinfo.record]: station_vector = -0.117 0.255 0.960 14:20:04.047 [planetinfo.record]: station = coriolis 14:20:08.425 [PLANETINFO OVER]: Done 14:20:08.426 [PLANETINFO LOGGING]: 6 128 14:20:09.428 [planetinfo.record]: seed = 18 38 212 241 14:20:09.428 [planetinfo.record]: coordinates = 38 42 14:20:09.428 [planetinfo.record]: government = 2; 14:20:09.429 [planetinfo.record]: economy = 2; 14:20:09.429 [planetinfo.record]: techlevel = 8; 14:20:09.429 [planetinfo.record]: population = 37; 14:20:09.429 [planetinfo.record]: productivity = 14208; 14:20:09.429 [planetinfo.record]: name = "Atxequ"; 14:20:09.429 [planetinfo.record]: inhabitant = "Fat Humanoid"; 14:20:09.429 [planetinfo.record]: inhabitants = "Fat Humanoids"; 14:20:09.429 [planetinfo.record]: description = "Atxequ is mildly well known for its hoopy casinos and its exotic night life."; 14:20:09.440 [planetinfo.record]: air_color = 0.482935, 0.640961, 0.863068, 1; 14:20:09.440 [planetinfo.record]: cloud_alpha = 1.000000; 14:20:09.440 [planetinfo.record]: cloud_color = 0.164131, 0.591797, 0.491563, 1; 14:20:09.440 [planetinfo.record]: cloud_fraction = 0.140000; 14:20:09.440 [planetinfo.record]: land_color = 0.507299, 0.364372, 0.572266, 1; 14:20:09.441 [planetinfo.record]: land_fraction = 0.470000; 14:20:09.441 [planetinfo.record]: polar_cloud_color = 0.420198, 0.766309, 0.685189, 1; 14:20:09.441 [planetinfo.record]: polar_land_color = 0.916016, 0.85715, 0.942773, 1; 14:20:09.441 [planetinfo.record]: polar_sea_color = 0.949023, 0.92652, 0.743278, 1; 14:20:09.442 [planetinfo.record]: sea_color = 0.509766, 0.461415, 0.0677032, 1; 14:20:09.442 [planetinfo.record]: rotation_speed = 0.000663 14:20:09.442 [planetinfo.record]: planet zpos = 373200.000000 14:20:09.442 [planetinfo.record]: sun_radius = 51843.931580 14:20:09.442 [planetinfo.record]: sun_vector = 0.103 0.690 -0.716 14:20:09.442 [planetinfo.record]: sun_distance = 715300 14:20:09.442 [planetinfo.record]: corona_flare = 0.020810 14:20:09.442 [planetinfo.record]: corona_hues = 0.866455 14:20:09.442 [planetinfo.record]: sun_color = 0.402496, 0.633999, 0.779953, 1 14:20:09.442 [planetinfo.record]: corona_shimmer = 0.334740 14:20:09.443 [planetinfo.record]: station_vector = 0.578 -0.779 0.245 14:20:09.443 [planetinfo.record]: station = coriolis 14:20:13.959 [PLANETINFO OVER]: Done 14:20:13.960 [PLANETINFO LOGGING]: 6 129 14:20:14.965 [planetinfo.record]: seed = 170 142 128 43 14:20:14.965 [planetinfo.record]: coordinates = 142 90 14:20:14.965 [planetinfo.record]: government = 5; 14:20:14.965 [planetinfo.record]: economy = 2; 14:20:14.965 [planetinfo.record]: techlevel = 10; 14:20:14.965 [planetinfo.record]: population = 48; 14:20:14.965 [planetinfo.record]: productivity = 27648; 14:20:14.965 [planetinfo.record]: name = "Mararere"; 14:20:14.966 [planetinfo.record]: inhabitant = "Small Red Fat Insect"; 14:20:14.966 [planetinfo.record]: inhabitants = "Small Red Fat Insects"; 14:20:14.966 [planetinfo.record]: description = "This planet is very notable for the Mararereian edible grub and the Mararereian tree wolf."; 14:20:14.979 [planetinfo.record]: air_color = 0.473775, 0.572642, 0.892336, 1; 14:20:14.979 [planetinfo.record]: cloud_alpha = 1.000000; 14:20:14.979 [planetinfo.record]: cloud_color = 0.130096, 0.512234, 0.679688, 1; 14:20:14.979 [planetinfo.record]: cloud_fraction = 0.300000; 14:20:14.979 [planetinfo.record]: land_color = 0.30201, 0.348971, 0.533203, 1; 14:20:14.979 [planetinfo.record]: land_fraction = 0.650000; 14:20:14.980 [planetinfo.record]: polar_cloud_color = 0.398601, 0.681773, 0.805859, 1; 14:20:14.980 [planetinfo.record]: polar_land_color = 0.844061, 0.864906, 0.94668, 1; 14:20:14.980 [planetinfo.record]: polar_sea_color = 0.923633, 0.871416, 0.820807, 1; 14:20:14.980 [planetinfo.record]: sea_color = 0.763672, 0.590979, 0.423599, 1; 14:20:14.980 [planetinfo.record]: rotation_speed = 0.001681 14:20:14.980 [planetinfo.record]: planet zpos = 577400.000000 14:20:14.980 [planetinfo.record]: sun_radius = 124389.101562 14:20:14.980 [planetinfo.record]: sun_vector = -0.008 -0.506 0.863 14:20:14.980 [planetinfo.record]: sun_distance = 1328020 14:20:14.980 [planetinfo.record]: corona_flare = 0.025389 14:20:14.981 [planetinfo.record]: corona_hues = 0.517670 14:20:14.981 [planetinfo.record]: sun_color = 0.419106, 0.749797, 0.759378, 1 14:20:14.981 [planetinfo.record]: corona_shimmer = 0.484605 14:20:14.981 [planetinfo.record]: station_vector = -0.946 -0.306 0.104 14:20:14.981 [planetinfo.record]: station = coriolis 14:20:19.593 [PLANETINFO OVER]: Done 14:20:19.594 [PLANETINFO LOGGING]: 6 130 14:20:20.596 [planetinfo.record]: seed = 186 14 244 241 14:20:20.597 [planetinfo.record]: coordinates = 14 206 14:20:20.597 [planetinfo.record]: government = 7; 14:20:20.597 [planetinfo.record]: economy = 6; 14:20:20.597 [planetinfo.record]: techlevel = 7; 14:20:20.597 [planetinfo.record]: population = 42; 14:20:20.597 [planetinfo.record]: productivity = 14784; 14:20:20.597 [planetinfo.record]: name = "Ataer"; 14:20:20.597 [planetinfo.record]: inhabitant = "Slimy Frog"; 14:20:20.597 [planetinfo.record]: inhabitants = "Slimy Frogs"; 14:20:20.597 [planetinfo.record]: description = "Ataer is very fabled for its unusual oceans."; 14:20:20.622 [planetinfo.record]: air_color = 0.646288, 0.901441, 0.899178, 1; 14:20:20.622 [planetinfo.record]: cloud_alpha = 1.000000; 14:20:20.622 [planetinfo.record]: cloud_color = 0.707031, 0.704269, 0.530273, 1; 14:20:20.622 [planetinfo.record]: cloud_fraction = 0.290000; 14:20:20.623 [planetinfo.record]: land_color = 0.394232, 0.66, 0.286172, 1; 14:20:20.623 [planetinfo.record]: land_fraction = 0.240000; 14:20:20.623 [planetinfo.record]: polar_cloud_color = 0.818164, 0.816167, 0.690326, 1; 14:20:20.623 [planetinfo.record]: polar_land_color = 0.839974, 0.934, 0.801744, 1; 14:20:20.623 [planetinfo.record]: polar_sea_color = 0.903829, 0.932617, 0.879975, 1; 14:20:20.623 [planetinfo.record]: sea_color = 0.590628, 0.673828, 0.52169, 1; 14:20:20.623 [planetinfo.record]: rotation_speed = 0.000972 14:20:20.623 [planetinfo.record]: planet zpos = 370320.000000 14:20:20.623 [planetinfo.record]: sun_radius = 85660.326843 14:20:20.623 [planetinfo.record]: sun_vector = 0.229 -0.912 0.339 14:20:20.623 [planetinfo.record]: sun_distance = 617200 14:20:20.623 [planetinfo.record]: corona_flare = 0.042912 14:20:20.623 [planetinfo.record]: corona_hues = 0.753937 14:20:20.623 [planetinfo.record]: sun_color = 0.679218, 0.791423, 0.817627, 1 14:20:20.623 [planetinfo.record]: corona_shimmer = 0.334507 14:20:20.623 [planetinfo.record]: station_vector = -0.755 -0.377 0.536 14:20:20.623 [planetinfo.record]: station = coriolis 14:20:25.684 [PLANETINFO OVER]: Done 14:20:25.685 [PLANETINFO LOGGING]: 6 131 14:20:26.687 [planetinfo.record]: seed = 98 145 208 48 14:20:26.687 [planetinfo.record]: coordinates = 145 208 14:20:26.687 [planetinfo.record]: government = 4; 14:20:26.687 [planetinfo.record]: economy = 0; 14:20:26.687 [planetinfo.record]: techlevel = 10; 14:20:26.687 [planetinfo.record]: population = 45; 14:20:26.687 [planetinfo.record]: productivity = 28800; 14:20:26.687 [planetinfo.record]: name = "Erenra"; 14:20:26.687 [planetinfo.record]: inhabitant = "Red Bug-Eyed Frog"; 14:20:26.687 [planetinfo.record]: inhabitants = "Red Bug-Eyed Frogs"; 14:20:26.687 [planetinfo.record]: description = "This planet is very well known for its inhabitants’ unusual silliness and vicious Mare brew."; 14:20:26.693 [planetinfo.record]: air_color = 0.696373, 0.528986, 0.928107, 1; 14:20:26.693 [planetinfo.record]: cloud_alpha = 1.000000; 14:20:26.693 [planetinfo.record]: cloud_color = 0.787109, 0.242897, 0.638301, 1; 14:20:26.693 [planetinfo.record]: cloud_fraction = 0.480000; 14:20:26.693 [planetinfo.record]: land_color = 0.200771, 0.0721875, 0.66, 1; 14:20:26.693 [planetinfo.record]: land_fraction = 0.260000; 14:20:26.693 [planetinfo.record]: polar_cloud_color = 0.854199, 0.485075, 0.753267, 1; 14:20:26.693 [planetinfo.record]: polar_land_color = 0.771531, 0.726039, 0.934, 1; 14:20:26.693 [planetinfo.record]: polar_sea_color = 0.9375, 0.936677, 0.884857, 1; 14:20:26.694 [planetinfo.record]: sea_color = 0.625, 0.622807, 0.484619, 1; 14:20:26.694 [planetinfo.record]: rotation_speed = 0.003247 14:20:26.694 [planetinfo.record]: planet zpos = 414540.000000 14:20:26.694 [planetinfo.record]: sun_radius = 79324.763489 14:20:26.694 [planetinfo.record]: sun_vector = 0.474 0.815 -0.333 14:20:26.694 [planetinfo.record]: sun_distance = 681030 14:20:26.694 [planetinfo.record]: corona_flare = 0.085150 14:20:26.694 [planetinfo.record]: corona_hues = 0.786217 14:20:26.694 [planetinfo.record]: sun_color = 0.783954, 0.768932, 0.76732, 1 14:20:26.694 [planetinfo.record]: corona_shimmer = 0.323465 14:20:26.694 [planetinfo.record]: station_vector = 0.505 0.057 0.861 14:20:26.694 [planetinfo.record]: station = coriolis 14:20:31.235 [PLANETINFO OVER]: Done 14:20:31.236 [PLANETINFO LOGGING]: 6 132 14:20:32.238 [planetinfo.record]: seed = 130 23 52 254 14:20:32.238 [planetinfo.record]: coordinates = 23 84 14:20:32.238 [planetinfo.record]: government = 0; 14:20:32.238 [planetinfo.record]: economy = 6; 14:20:32.238 [planetinfo.record]: techlevel = 4; 14:20:32.238 [planetinfo.record]: population = 23; 14:20:32.238 [planetinfo.record]: productivity = 2944; 14:20:32.238 [planetinfo.record]: name = "Riaronus"; 14:20:32.238 [planetinfo.record]: inhabitant = "Human Colonial"; 14:20:32.238 [planetinfo.record]: inhabitants = "Human Colonials"; 14:20:32.238 [planetinfo.record]: description = "Riaronus is mildly well known for vicious Iliton brew."; 14:20:32.256 [planetinfo.record]: air_color = 0.709142, 0.812117, 0.914449, 1; 14:20:32.256 [planetinfo.record]: cloud_alpha = 1.000000; 14:20:32.256 [planetinfo.record]: cloud_color = 0.69072, 0.746094, 0.718839, 1; 14:20:32.256 [planetinfo.record]: cloud_fraction = 0.230000; 14:20:32.256 [planetinfo.record]: land_color = 0.66, 0.0670312, 0.080929, 1; 14:20:32.256 [planetinfo.record]: land_fraction = 0.450000; 14:20:32.256 [planetinfo.record]: polar_cloud_color = 0.796975, 0.835742, 0.816661, 1; 14:20:32.256 [planetinfo.record]: polar_land_color = 0.934, 0.724215, 0.729132, 1; 14:20:32.256 [planetinfo.record]: polar_sea_color = 0.944531, 0.907604, 0.893154, 1; 14:20:32.256 [planetinfo.record]: sea_color = 0.554688, 0.467943, 0.434, 1; 14:20:32.256 [planetinfo.record]: rotation_speed = 0.001291 14:20:32.256 [planetinfo.record]: planet zpos = 706530.000000 14:20:32.256 [planetinfo.record]: sun_radius = 111162.708893 14:20:32.256 [planetinfo.record]: sun_vector = 0.948 -0.111 -0.297 14:20:32.256 [planetinfo.record]: sun_distance = 1091910 14:20:32.256 [planetinfo.record]: corona_flare = 0.017285 14:20:32.256 [planetinfo.record]: corona_hues = 0.563507 14:20:32.256 [planetinfo.record]: sun_color = 0.721542, 0.714758, 0.60006, 1 14:20:32.256 [planetinfo.record]: corona_shimmer = 0.359997 14:20:32.257 [planetinfo.record]: station_vector = 0.927 0.282 0.249 14:20:32.257 [planetinfo.record]: station = coriolis 14:20:37.388 [PLANETINFO OVER]: Done 14:20:37.389 [PLANETINFO LOGGING]: 6 133 14:20:38.394 [planetinfo.record]: seed = 58 232 64 210 14:20:38.394 [planetinfo.record]: coordinates = 232 127 14:20:38.394 [planetinfo.record]: government = 7; 14:20:38.394 [planetinfo.record]: economy = 7; 14:20:38.394 [planetinfo.record]: techlevel = 4; 14:20:38.394 [planetinfo.record]: population = 31; 14:20:38.394 [planetinfo.record]: productivity = 8184; 14:20:38.394 [planetinfo.record]: name = "Enqurale"; 14:20:38.394 [planetinfo.record]: inhabitant = "Human Colonial"; 14:20:38.394 [planetinfo.record]: inhabitants = "Human Colonials"; 14:20:38.394 [planetinfo.record]: description = "This planet is mildly fabled for the Enquraleian mountain poet but scourged by dreadful solar activity."; 14:20:38.396 [planetinfo.record]: air_color = 0.693273, 0.853977, 0.873475, 1; 14:20:38.396 [planetinfo.record]: cloud_alpha = 1.000000; 14:20:38.396 [planetinfo.record]: cloud_color = 0.618959, 0.623047, 0.610878, 1; 14:20:38.396 [planetinfo.record]: cloud_fraction = 0.440000; 14:20:38.397 [planetinfo.record]: land_color = 0.108093, 0.628906, 0.396982, 1; 14:20:38.397 [planetinfo.record]: land_fraction = 0.660000; 14:20:38.397 [planetinfo.record]: polar_cloud_color = 0.777171, 0.780371, 0.770845, 1; 14:20:38.397 [planetinfo.record]: polar_land_color = 0.743098, 0.937109, 0.850714, 1; 14:20:38.397 [planetinfo.record]: polar_sea_color = 0.92793, 0.890337, 0.846373, 1; 14:20:38.397 [planetinfo.record]: sea_color = 0.720703, 0.603914, 0.467331, 1; 14:20:38.397 [planetinfo.record]: rotation_speed = 0.004897 14:20:38.397 [planetinfo.record]: planet zpos = 498400.000000 14:20:38.397 [planetinfo.record]: sun_radius = 103686.303711 14:20:38.397 [planetinfo.record]: sun_vector = 0.200 -0.917 0.346 14:20:38.397 [planetinfo.record]: sun_distance = 676400 14:20:38.397 [planetinfo.record]: corona_flare = 0.001747 14:20:38.397 [planetinfo.record]: corona_hues = 0.606674 14:20:38.397 [planetinfo.record]: sun_color = 0.341089, 0.599458, 0.738614, 1 14:20:38.397 [planetinfo.record]: corona_shimmer = 0.745575 14:20:38.398 [planetinfo.record]: station_vector = 0.289 0.325 0.900 14:20:38.398 [planetinfo.record]: station = coriolis 14:20:43.600 [PLANETINFO OVER]: Done 14:20:43.601 [PLANETINFO LOGGING]: 6 134 14:20:44.605 [planetinfo.record]: seed = 106 1 148 48 14:20:44.605 [planetinfo.record]: coordinates = 1 244 14:20:44.605 [planetinfo.record]: government = 5; 14:20:44.605 [planetinfo.record]: economy = 4; 14:20:44.605 [planetinfo.record]: techlevel = 7; 14:20:44.605 [planetinfo.record]: population = 38; 14:20:44.605 [planetinfo.record]: productivity = 16416; 14:20:44.605 [planetinfo.record]: name = "Erbieder"; 14:20:44.605 [planetinfo.record]: inhabitant = "Red Furry Humanoid"; 14:20:44.606 [planetinfo.record]: inhabitants = "Red Furry Humanoids"; 14:20:44.606 [planetinfo.record]: description = "Erbieder is most famous for its vast dense forests and its fabulous cuisine."; 14:20:44.616 [planetinfo.record]: air_color = 0.678402, 0.865219, 0.889734, 1; 14:20:44.616 [planetinfo.record]: cloud_alpha = 1.000000; 14:20:44.616 [planetinfo.record]: cloud_color = 0.644523, 0.671875, 0.595764, 1; 14:20:44.616 [planetinfo.record]: cloud_fraction = 0.280000; 14:20:44.616 [planetinfo.record]: land_color = 0.66, 0.440054, 0.157266, 1; 14:20:44.616 [planetinfo.record]: land_fraction = 0.260000; 14:20:44.616 [planetinfo.record]: polar_cloud_color = 0.781929, 0.802344, 0.745537, 1; 14:20:44.616 [planetinfo.record]: polar_land_color = 0.934, 0.856186, 0.756139, 1; 14:20:44.616 [planetinfo.record]: polar_sea_color = 0.941016, 0.916405, 0.887624, 1; 14:20:44.616 [planetinfo.record]: sea_color = 0.589844, 0.528139, 0.455977, 1; 14:20:44.616 [planetinfo.record]: rotation_speed = 0.001506 14:20:44.616 [planetinfo.record]: planet zpos = 309870.000000 14:20:44.616 [planetinfo.record]: sun_radius = 48959.215851 14:20:44.616 [planetinfo.record]: sun_vector = -0.937 -0.040 -0.347 14:20:44.616 [planetinfo.record]: sun_distance = 535230 14:20:44.616 [planetinfo.record]: corona_flare = 0.099568 14:20:44.616 [planetinfo.record]: corona_hues = 0.766411 14:20:44.616 [planetinfo.record]: sun_color = 0.791522, 0.520582, 0.415949, 1 14:20:44.617 [planetinfo.record]: corona_shimmer = 0.654196 14:20:44.624 [planetinfo.record]: station_vector = -0.096 -0.007 0.995 14:20:44.624 [planetinfo.record]: station = coriolis 14:20:49.738 [PLANETINFO OVER]: Done 14:20:49.739 [PLANETINFO LOGGING]: 6 135 14:20:50.741 [planetinfo.record]: seed = 50 176 208 47 14:20:50.741 [planetinfo.record]: coordinates = 176 88 14:20:50.741 [planetinfo.record]: government = 6; 14:20:50.742 [planetinfo.record]: economy = 0; 14:20:50.742 [planetinfo.record]: techlevel = 10; 14:20:50.742 [planetinfo.record]: population = 47; 14:20:50.742 [planetinfo.record]: productivity = 37600; 14:20:50.742 [planetinfo.record]: name = "Aededle"; 14:20:50.742 [planetinfo.record]: inhabitant = "Red Slimy Lobster"; 14:20:50.742 [planetinfo.record]: inhabitants = "Red Slimy Lobsters"; 14:20:50.742 [planetinfo.record]: description = "This world is ravaged by unpredictable civil war."; 14:20:50.756 [planetinfo.record]: air_color = 0.718433, 0.631908, 0.834451, 1; 14:20:50.756 [planetinfo.record]: cloud_alpha = 1.000000; 14:20:50.756 [planetinfo.record]: cloud_color = 0.505859, 0.440651, 0.486501, 1; 14:20:50.756 [planetinfo.record]: cloud_fraction = 0.260000; 14:20:50.756 [planetinfo.record]: land_color = 0.402188, 0.63583, 0.66, 1; 14:20:50.756 [planetinfo.record]: land_fraction = 0.560000; 14:20:50.756 [planetinfo.record]: polar_cloud_color = 0.727637, 0.669014, 0.710233, 1; 14:20:50.756 [planetinfo.record]: polar_land_color = 0.842789, 0.925449, 0.934, 1; 14:20:50.756 [planetinfo.record]: polar_sea_color = 0.938867, 0.879967, 0.877071, 1; 14:20:50.756 [planetinfo.record]: sea_color = 0.611328, 0.457921, 0.450377, 1; 14:20:50.756 [planetinfo.record]: rotation_speed = 0.001376 14:20:50.756 [planetinfo.record]: planet zpos = 956480.000000 14:20:50.756 [planetinfo.record]: sun_radius = 160399.172363 14:20:50.756 [planetinfo.record]: sun_vector = -0.317 -0.909 0.272 14:20:50.756 [planetinfo.record]: sun_distance = 1229760 14:20:50.756 [planetinfo.record]: corona_flare = 0.060042 14:20:50.756 [planetinfo.record]: corona_hues = 0.830330 14:20:50.756 [planetinfo.record]: sun_color = 0.327884, 0.515054, 0.81817, 1 14:20:50.756 [planetinfo.record]: corona_shimmer = 0.339487 14:20:50.756 [planetinfo.record]: station_vector = 0.156 -0.988 0.002 14:20:50.756 [planetinfo.record]: station = coriolis 14:20:55.413 [PLANETINFO OVER]: Done 14:20:55.414 [PLANETINFO LOGGING]: 6 136 14:20:56.419 [planetinfo.record]: seed = 114 129 20 211 14:20:56.419 [planetinfo.record]: coordinates = 129 24 14:20:56.419 [planetinfo.record]: government = 6; 14:20:56.419 [planetinfo.record]: economy = 0; 14:20:56.419 [planetinfo.record]: techlevel = 11; 14:20:56.419 [planetinfo.record]: population = 51; 14:20:56.419 [planetinfo.record]: productivity = 40800; 14:20:56.419 [planetinfo.record]: name = "Bedilexe"; 14:20:56.419 [planetinfo.record]: inhabitant = "Human Colonial"; 14:20:56.419 [planetinfo.record]: inhabitants = "Human Colonials"; 14:20:56.419 [planetinfo.record]: description = "The planet Bedilexe is an unremarkable planet."; 14:20:56.422 [planetinfo.record]: air_color = 0.542083, 0.591793, 0.841605, 1; 14:20:56.422 [planetinfo.record]: cloud_alpha = 1.000000; 14:20:56.422 [planetinfo.record]: cloud_color = 0.280151, 0.399885, 0.527344, 1; 14:20:56.422 [planetinfo.record]: cloud_fraction = 0.330000; 14:20:56.422 [planetinfo.record]: land_color = 0.66, 0.0721875, 0.457939, 1; 14:20:56.422 [planetinfo.record]: land_fraction = 0.600000; 14:20:56.422 [planetinfo.record]: polar_cloud_color = 0.521297, 0.625926, 0.737305, 1; 14:20:56.423 [planetinfo.record]: polar_land_color = 0.934, 0.726039, 0.862513, 1; 14:20:56.423 [planetinfo.record]: polar_sea_color = 0.946289, 0.91766, 0.898697, 1; 14:20:56.423 [planetinfo.record]: sea_color = 0.537109, 0.47211, 0.429058, 1; 14:20:56.423 [planetinfo.record]: rotation_speed = 0.001912 14:20:56.423 [planetinfo.record]: planet zpos = 519820.000000 14:20:56.423 [planetinfo.record]: sun_radius = 83856.373596 14:20:56.423 [planetinfo.record]: sun_vector = -0.268 -0.963 0.030 14:20:56.423 [planetinfo.record]: sun_distance = 853990 14:20:56.423 [planetinfo.record]: corona_flare = 0.082829 14:20:56.423 [planetinfo.record]: corona_hues = 0.585007 14:20:56.423 [planetinfo.record]: sun_color = 0.543125, 0.769031, 0.833472, 1 14:20:56.423 [planetinfo.record]: corona_shimmer = 0.312860 14:20:56.424 [planetinfo.record]: station_vector = 0.580 -0.211 0.787 14:20:56.424 [planetinfo.record]: station = dodecahedron 14:21:01.505 [PLANETINFO OVER]: Done 14:21:01.506 [PLANETINFO LOGGING]: 6 137 14:21:02.522 [planetinfo.record]: seed = 74 2 128 49 14:21:02.522 [planetinfo.record]: coordinates = 2 193 14:21:02.522 [planetinfo.record]: government = 1; 14:21:02.522 [planetinfo.record]: economy = 3; 14:21:02.522 [planetinfo.record]: techlevel = 7; 14:21:02.522 [planetinfo.record]: population = 33; 14:21:02.522 [planetinfo.record]: productivity = 9240; 14:21:02.522 [planetinfo.record]: name = "Atlaeser"; 14:21:02.522 [planetinfo.record]: inhabitant = "Red Bony Bird"; 14:21:02.522 [planetinfo.record]: inhabitants = "Red Bony Birds"; 14:21:02.522 [planetinfo.record]: description = "The planet Atlaeser is most famous for its vast dense forests and its great parking meters."; 14:21:02.544 [planetinfo.record]: air_color = 0.526884, 0.63269, 0.83315, 1; 14:21:02.544 [planetinfo.record]: cloud_alpha = 1.000000; 14:21:02.544 [planetinfo.record]: cloud_color = 0.249016, 0.501953, 0.496025, 1; 14:21:02.544 [planetinfo.record]: cloud_fraction = 0.340000; 14:21:02.544 [planetinfo.record]: land_color = 0.421161, 0.0360938, 0.66, 1; 14:21:02.544 [planetinfo.record]: land_fraction = 0.280000; 14:21:02.544 [planetinfo.record]: polar_cloud_color = 0.49727, 0.725879, 0.720521, 1; 14:21:02.544 [planetinfo.record]: polar_land_color = 0.849502, 0.71327, 0.934, 1; 14:21:02.544 [planetinfo.record]: polar_sea_color = 0.922692, 0.937109, 0.887234, 1; 14:21:02.544 [planetinfo.record]: sea_color = 0.590204, 0.628906, 0.495018, 1; 14:21:02.544 [planetinfo.record]: rotation_speed = 0.002913 14:21:02.544 [planetinfo.record]: planet zpos = 430360.000000 14:21:02.544 [planetinfo.record]: sun_radius = 80282.076721 14:21:02.544 [planetinfo.record]: sun_vector = 0.746 0.645 0.167 14:21:02.544 [planetinfo.record]: sun_distance = 676280 14:21:02.544 [planetinfo.record]: corona_flare = 0.068999 14:21:02.544 [planetinfo.record]: corona_hues = 0.608543 14:21:02.544 [planetinfo.record]: sun_color = 0.45904, 0.758923, 0.759222, 1 14:21:02.544 [planetinfo.record]: corona_shimmer = 0.656581 14:21:02.544 [planetinfo.record]: station_vector = -0.108 -0.898 0.426 14:21:02.544 [planetinfo.record]: station = coriolis 14:21:08.036 [PLANETINFO OVER]: Done 14:21:08.037 [PLANETINFO LOGGING]: 6 138 14:21:09.040 [planetinfo.record]: seed = 154 80 180 111 14:21:09.040 [planetinfo.record]: coordinates = 80 41 14:21:09.040 [planetinfo.record]: government = 3; 14:21:09.040 [planetinfo.record]: economy = 1; 14:21:09.040 [planetinfo.record]: techlevel = 8; 14:21:09.040 [planetinfo.record]: population = 37; 14:21:09.040 [planetinfo.record]: productivity = 18648; 14:21:09.040 [planetinfo.record]: name = "Aesarge"; 14:21:09.040 [planetinfo.record]: inhabitant = "Blue Bug-Eyed Bird"; 14:21:09.040 [planetinfo.record]: inhabitants = "Blue Bug-Eyed Birds"; 14:21:09.040 [planetinfo.record]: description = "This planet is noted for its exotic fish meat."; 14:21:09.056 [planetinfo.record]: air_color = 0.469051, 0.617899, 0.881279, 1; 14:21:09.056 [planetinfo.record]: cloud_alpha = 1.000000; 14:21:09.056 [planetinfo.record]: cloud_color = 0.126266, 0.646484, 0.609907, 1; 14:21:09.056 [planetinfo.record]: cloud_fraction = 0.470000; 14:21:09.056 [planetinfo.record]: land_color = 0.656858, 0.652266, 0.66, 1; 14:21:09.056 [planetinfo.record]: land_fraction = 0.710000; 14:21:09.056 [planetinfo.record]: polar_cloud_color = 0.393142, 0.790918, 0.762949, 1; 14:21:09.056 [planetinfo.record]: polar_land_color = 0.932888, 0.931264, 0.934, 1; 14:21:09.056 [planetinfo.record]: polar_sea_color = 0.923242, 0.902202, 0.830377, 1; 14:21:09.056 [planetinfo.record]: sea_color = 0.767578, 0.697609, 0.458748, 1; 14:21:09.056 [planetinfo.record]: rotation_speed = 0.002172 14:21:09.056 [planetinfo.record]: planet zpos = 1010400.000000 14:21:09.056 [planetinfo.record]: sun_radius = 199035.561523 14:21:09.056 [planetinfo.record]: sun_vector = -0.694 0.707 0.137 14:21:09.056 [planetinfo.record]: sun_distance = 1347200 14:21:09.056 [planetinfo.record]: corona_flare = 0.040913 14:21:09.056 [planetinfo.record]: corona_hues = 0.667076 14:21:09.056 [planetinfo.record]: sun_color = 0.833173, 0.811037, 0.796006, 1 14:21:09.056 [planetinfo.record]: corona_shimmer = 0.472189 14:21:09.057 [planetinfo.record]: station_vector = 0.320 -0.733 0.600 14:21:09.057 [planetinfo.record]: station = coriolis 14:21:14.350 [PLANETINFO OVER]: Done 14:21:14.351 [PLANETINFO LOGGING]: 6 139 14:21:15.371 [planetinfo.record]: seed = 130 3 80 151 14:21:15.371 [planetinfo.record]: coordinates = 3 170 14:21:15.371 [planetinfo.record]: government = 0; 14:21:15.371 [planetinfo.record]: economy = 2; 14:21:15.371 [planetinfo.record]: techlevel = 8; 14:21:15.371 [planetinfo.record]: population = 35; 14:21:15.371 [planetinfo.record]: productivity = 8960; 14:21:15.371 [planetinfo.record]: name = "Tizaon"; 14:21:15.372 [planetinfo.record]: inhabitant = "Human Colonial"; 14:21:15.372 [planetinfo.record]: inhabitants = "Human Colonials"; 14:21:15.372 [planetinfo.record]: description = "This planet is mildly noted for its pink oceans but scourged by dreadful earthquakes."; 14:21:15.386 [planetinfo.record]: air_color = 0.745097, 0.745525, 0.952822, 1; 14:21:15.386 [planetinfo.record]: cloud_alpha = 1.000000; 14:21:15.386 [planetinfo.record]: cloud_color = 0.814592, 0.814224, 0.861328, 1; 14:21:15.386 [planetinfo.record]: cloud_fraction = 0.250000; 14:21:15.386 [planetinfo.record]: land_color = 0.66, 0.286172, 0.435119, 1; 14:21:15.387 [planetinfo.record]: land_fraction = 0.340000; 14:21:15.387 [planetinfo.record]: polar_cloud_color = 0.857497, 0.85726, 0.887598, 1; 14:21:15.387 [planetinfo.record]: polar_land_color = 0.934, 0.801744, 0.85444, 1; 14:21:15.387 [planetinfo.record]: polar_sea_color = 0.91, 0.8645, 0.8918, 1; 14:21:15.387 [planetinfo.record]: sea_color = 0.9, 0.72, 0.828, 1; 14:21:15.387 [planetinfo.record]: rotation_speed = 0.004522 14:21:15.387 [planetinfo.record]: planet zpos = 507210.000000 14:21:15.387 [planetinfo.record]: sun_radius = 118439.011688 14:21:15.387 [planetinfo.record]: sun_vector = 0.474 -0.485 0.735 14:21:15.387 [planetinfo.record]: sun_distance = 1014420 14:21:15.387 [planetinfo.record]: corona_flare = 0.014639 14:21:15.387 [planetinfo.record]: corona_hues = 0.803162 14:21:15.387 [planetinfo.record]: sun_color = 0.805456, 0.808282, 0.654417, 1 14:21:15.387 [planetinfo.record]: corona_shimmer = 0.427948 14:21:15.387 [planetinfo.record]: station_vector = -0.208 0.968 0.143 14:21:15.387 [planetinfo.record]: station = coriolis 14:21:21.815 [PLANETINFO OVER]: Done 14:21:21.816 [PLANETINFO LOGGING]: 6 140 14:21:22.841 [planetinfo.record]: seed = 226 187 116 224 14:21:22.841 [planetinfo.record]: coordinates = 187 223 14:21:22.841 [planetinfo.record]: government = 4; 14:21:22.841 [planetinfo.record]: economy = 7; 14:21:22.841 [planetinfo.record]: techlevel = 5; 14:21:22.841 [planetinfo.record]: population = 32; 14:21:22.841 [planetinfo.record]: productivity = 6144; 14:21:22.841 [planetinfo.record]: name = "Teed"; 14:21:22.841 [planetinfo.record]: inhabitant = "Human Colonial"; 14:21:22.841 [planetinfo.record]: inhabitants = "Human Colonials"; 14:21:22.841 [planetinfo.record]: description = "This planet is notable for the Teedian edible moth and the Teedian edible arts graduate."; 14:21:22.870 [planetinfo.record]: air_color = 0.405079, 0.839004, 0.767422, 1; 14:21:22.870 [planetinfo.record]: cloud_alpha = 1.000000; 14:21:22.870 [planetinfo.record]: cloud_color = 0.519531, 0.279679, 0.024353, 1; 14:21:22.870 [planetinfo.record]: cloud_fraction = 0.490000; 14:21:22.870 [planetinfo.record]: land_color = 0.66, 0.590834, 0.139219, 1; 14:21:22.870 [planetinfo.record]: land_fraction = 0.360000; 14:21:22.870 [planetinfo.record]: polar_cloud_color = 0.733789, 0.522059, 0.296669, 1; 14:21:22.870 [planetinfo.record]: polar_land_color = 0.934, 0.90953, 0.749754, 1; 14:21:22.870 [planetinfo.record]: polar_sea_color = 0.941602, 0.892371, 0.884315, 1; 14:21:22.877 [planetinfo.record]: sea_color = 0.583984, 0.461852, 0.441866, 1; 14:21:22.877 [planetinfo.record]: rotation_speed = 0.002552 14:21:22.877 [planetinfo.record]: planet zpos = 300300.000000 14:21:22.878 [planetinfo.record]: sun_radius = 68315.775604 14:21:22.878 [planetinfo.record]: sun_vector = 0.660 0.159 0.735 14:21:22.878 [planetinfo.record]: sun_distance = 600600 14:21:22.878 [planetinfo.record]: corona_flare = 0.068509 14:21:22.878 [planetinfo.record]: corona_hues = 0.725349 14:21:22.878 [planetinfo.record]: sun_color = 0.250298, 0.344524, 0.799558, 1 14:21:22.878 [planetinfo.record]: corona_shimmer = 0.415416 14:21:22.879 [planetinfo.record]: station_vector = -0.982 -0.140 0.129 14:21:22.879 [planetinfo.record]: station = coriolis 14:21:27.127 [PLANETINFO OVER]: Done 14:21:27.127 [PLANETINFO LOGGING]: 6 141 14:21:28.131 [planetinfo.record]: seed = 218 116 64 9 14:21:28.131 [planetinfo.record]: coordinates = 116 24 14:21:28.131 [planetinfo.record]: government = 3; 14:21:28.131 [planetinfo.record]: economy = 0; 14:21:28.131 [planetinfo.record]: techlevel = 9; 14:21:28.131 [planetinfo.record]: population = 40; 14:21:28.131 [planetinfo.record]: productivity = 22400; 14:21:28.131 [planetinfo.record]: name = "Esverara"; 14:21:28.131 [planetinfo.record]: inhabitant = "Human Colonial"; 14:21:28.131 [planetinfo.record]: inhabitants = "Human Colonials"; 14:21:28.131 [planetinfo.record]: description = "The world Esverara is a dull world."; 14:21:28.147 [planetinfo.record]: air_color = 0.622082, 0.795999, 0.991846, 1; 14:21:28.147 [planetinfo.record]: cloud_alpha = 1.000000; 14:21:28.147 [planetinfo.record]: cloud_color = 0.470146, 0.978516, 0.775962, 1; 14:21:28.148 [planetinfo.record]: cloud_fraction = 0.490000; 14:21:28.148 [planetinfo.record]: land_color = 0.608397, 0.551719, 0.66, 1; 14:21:28.148 [planetinfo.record]: land_fraction = 0.530000; 14:21:28.148 [planetinfo.record]: polar_cloud_color = 0.635, 0.940332, 0.818676, 1; 14:21:28.148 [planetinfo.record]: polar_land_color = 0.915744, 0.895691, 0.934, 1; 14:21:28.148 [planetinfo.record]: polar_sea_color = 0.945312, 0.92215, 0.897493, 1; 14:21:28.148 [planetinfo.record]: sea_color = 0.546875, 0.493276, 0.436218, 1; 14:21:28.148 [planetinfo.record]: rotation_speed = 0.001037 14:21:28.149 [planetinfo.record]: planet zpos = 523600.000000 14:21:28.149 [planetinfo.record]: sun_radius = 131329.835205 14:21:28.149 [planetinfo.record]: sun_vector = -0.148 -0.975 0.168 14:21:28.149 [planetinfo.record]: sun_distance = 1047200 14:21:28.149 [planetinfo.record]: corona_flare = 0.031682 14:21:28.149 [planetinfo.record]: corona_hues = 0.819771 14:21:28.149 [planetinfo.record]: sun_color = 0.275912, 0.421667, 0.766202, 1 14:21:28.149 [planetinfo.record]: corona_shimmer = 0.447442 14:21:28.149 [planetinfo.record]: station_vector = 0.073 0.034 0.997 14:21:28.150 [planetinfo.record]: station = coriolis 14:21:32.875 [PLANETINFO OVER]: Done 14:21:32.876 [PLANETINFO LOGGING]: 6 142 14:21:33.880 [planetinfo.record]: seed = 74 180 84 95 14:21:33.880 [planetinfo.record]: coordinates = 180 20 14:21:33.880 [planetinfo.record]: government = 1; 14:21:33.880 [planetinfo.record]: economy = 6; 14:21:33.880 [planetinfo.record]: techlevel = 2; 14:21:33.880 [planetinfo.record]: population = 16; 14:21:33.880 [planetinfo.record]: productivity = 2560; 14:21:33.880 [planetinfo.record]: name = "Onusan"; 14:21:33.880 [planetinfo.record]: inhabitant = "Human Colonial"; 14:21:33.880 [planetinfo.record]: inhabitants = "Human Colonials"; 14:21:33.880 [planetinfo.record]: description = "Onusan is famous for its inhabitants’ ancient loathing of casinos but plagued by frequent earthquakes."; 14:21:33.882 [planetinfo.record]: air_color = 0.75444, 0.531503, 0.941115, 1; 14:21:33.883 [planetinfo.record]: cloud_alpha = 1.000000; 14:21:33.883 [planetinfo.record]: cloud_color = 0.826172, 0.242043, 0.447401, 1; 14:21:33.883 [planetinfo.record]: cloud_fraction = 0.270000; 14:21:33.883 [planetinfo.record]: land_color = 0.188312, 0.564453, 0.0970154, 1; 14:21:33.883 [planetinfo.record]: land_fraction = 0.430000; 14:21:33.883 [planetinfo.record]: polar_cloud_color = 0.871777, 0.486544, 0.621977, 1; 14:21:33.883 [planetinfo.record]: polar_land_color = 0.786363, 0.943555, 0.748209, 1; 14:21:33.883 [planetinfo.record]: polar_sea_color = 0.947461, 0.893704, 0.906303, 1; 14:21:33.883 [planetinfo.record]: sea_color = 0.525391, 0.406152, 0.434098, 1; 14:21:33.883 [planetinfo.record]: rotation_speed = 0.002805 14:21:33.883 [planetinfo.record]: planet zpos = 888680.000000 14:21:33.883 [planetinfo.record]: sun_radius = 185717.105103 14:21:33.883 [planetinfo.record]: sun_vector = -0.212 -0.784 -0.584 14:21:33.883 [planetinfo.record]: sun_distance = 1572280 14:21:33.884 [planetinfo.record]: corona_flare = 0.006706 14:21:33.884 [planetinfo.record]: corona_hues = 0.612694 14:21:33.884 [planetinfo.record]: sun_color = 0.40223, 0.679056, 0.691547, 1 14:21:33.884 [planetinfo.record]: corona_shimmer = 0.614704 14:21:33.884 [planetinfo.record]: station_vector = 0.573 -0.623 0.532 14:21:33.884 [planetinfo.record]: station = coriolis 14:21:38.409 [PLANETINFO OVER]: Done 14:21:38.410 [PLANETINFO LOGGING]: 6 143 14:21:39.413 [planetinfo.record]: seed = 82 195 80 39 14:21:39.413 [planetinfo.record]: coordinates = 195 59 14:21:39.413 [planetinfo.record]: government = 2; 14:21:39.413 [planetinfo.record]: economy = 3; 14:21:39.413 [planetinfo.record]: techlevel = 8; 14:21:39.413 [planetinfo.record]: population = 38; 14:21:39.413 [planetinfo.record]: productivity = 12768; 14:21:39.413 [planetinfo.record]: name = "Sobiatri"; 14:21:39.413 [planetinfo.record]: inhabitant = "Human Colonial"; 14:21:39.413 [planetinfo.record]: inhabitants = "Human Colonials"; 14:21:39.413 [planetinfo.record]: description = "This world is reasonably notable for its fabulous cuisine but beset by evil tree snakes."; 14:21:39.440 [planetinfo.record]: air_color = 0.700744, 0.519186, 0.936563, 1; 14:21:39.440 [planetinfo.record]: cloud_alpha = 1.000000; 14:21:39.440 [planetinfo.record]: cloud_color = 0.8125, 0.212646, 0.620359, 1; 14:21:39.440 [planetinfo.record]: cloud_fraction = 0.260000; 14:21:39.440 [planetinfo.record]: land_color = 0.66, 0.446217, 0.435703, 1; 14:21:39.440 [planetinfo.record]: land_fraction = 0.660000; 14:21:39.440 [planetinfo.record]: polar_cloud_color = 0.865625, 0.466203, 0.737685, 1; 14:21:39.440 [planetinfo.record]: polar_land_color = 0.934, 0.858366, 0.854647, 1; 14:21:39.440 [planetinfo.record]: polar_sea_color = 0.872247, 0.863852, 0.912695, 1; 14:21:39.440 [planetinfo.record]: sea_color = 0.718281, 0.68616, 0.873047, 1; 14:21:39.440 [planetinfo.record]: rotation_speed = 0.002625 14:21:39.440 [planetinfo.record]: planet zpos = 672420.000000 14:21:39.440 [planetinfo.record]: sun_radius = 106529.185638 14:21:39.440 [planetinfo.record]: sun_vector = -0.635 -0.722 -0.276 14:21:39.440 [planetinfo.record]: sun_distance = 960600 14:21:39.440 [planetinfo.record]: corona_flare = 0.056448 14:21:39.440 [planetinfo.record]: corona_hues = 0.624283 14:21:39.440 [planetinfo.record]: sun_color = 0.729263, 0.635871, 0.578614, 1 14:21:39.440 [planetinfo.record]: corona_shimmer = 0.270240 14:21:39.448 [planetinfo.record]: station_vector = -0.907 0.405 0.117 14:21:39.448 [planetinfo.record]: station = coriolis 14:21:43.925 [PLANETINFO OVER]: Done 14:21:43.926 [PLANETINFO LOGGING]: 6 144 14:21:44.928 [planetinfo.record]: seed = 210 94 84 150 14:21:44.928 [planetinfo.record]: coordinates = 94 17 14:21:44.928 [planetinfo.record]: government = 2; 14:21:44.928 [planetinfo.record]: economy = 1; 14:21:44.928 [planetinfo.record]: techlevel = 9; 14:21:44.928 [planetinfo.record]: population = 40; 14:21:44.928 [planetinfo.record]: productivity = 17280; 14:21:44.928 [planetinfo.record]: name = "Vebian"; 14:21:44.928 [planetinfo.record]: inhabitant = "Human Colonial"; 14:21:44.928 [planetinfo.record]: inhabitants = "Human Colonials"; 14:21:44.928 [planetinfo.record]: description = "This world is mildly famous for its hoopy night life and its exotic cuisine."; 14:21:44.947 [planetinfo.record]: air_color = 0.549564, 0.588652, 0.839654, 1; 14:21:44.948 [planetinfo.record]: cloud_alpha = 1.000000; 14:21:44.948 [planetinfo.record]: cloud_color = 0.293335, 0.382456, 0.521484, 1; 14:21:44.948 [planetinfo.record]: cloud_fraction = 0.460000; 14:21:44.948 [planetinfo.record]: land_color = 0.289841, 0.392113, 0.511719, 1; 14:21:44.948 [planetinfo.record]: land_fraction = 0.280000; 14:21:44.948 [planetinfo.record]: polar_cloud_color = 0.533782, 0.612253, 0.734668, 1; 14:21:44.948 [planetinfo.record]: polar_land_color = 0.845977, 0.893385, 0.948828, 1; 14:21:44.948 [planetinfo.record]: polar_sea_color = 0.915283, 0.934961, 0.883556, 1; 14:21:44.948 [planetinfo.record]: sea_color = 0.595635, 0.650391, 0.507356, 1; 14:21:44.948 [planetinfo.record]: rotation_speed = 0.003129 14:21:44.948 [planetinfo.record]: planet zpos = 533520.000000 14:21:44.949 [planetinfo.record]: sun_radius = 131154.150696 14:21:44.949 [planetinfo.record]: sun_vector = 0.035 -0.708 -0.706 14:21:44.949 [planetinfo.record]: sun_distance = 755820 14:21:44.949 [planetinfo.record]: corona_flare = 0.073412 14:21:44.949 [planetinfo.record]: corona_hues = 0.902161 14:21:44.949 [planetinfo.record]: sun_color = 0.745712, 0.693646, 0.658367, 1 14:21:44.949 [planetinfo.record]: corona_shimmer = 0.433118 14:21:44.949 [planetinfo.record]: station_vector = -0.556 0.064 0.829 14:21:44.949 [planetinfo.record]: station = coriolis 14:21:50.095 [PLANETINFO OVER]: Done 14:21:50.095 [PLANETINFO LOGGING]: 6 145 14:21:51.098 [planetinfo.record]: seed = 234 151 128 153 14:21:51.098 [planetinfo.record]: coordinates = 151 251 14:21:51.098 [planetinfo.record]: government = 5; 14:21:51.098 [planetinfo.record]: economy = 3; 14:21:51.098 [planetinfo.record]: techlevel = 10; 14:21:51.098 [planetinfo.record]: population = 49; 14:21:51.098 [planetinfo.record]: productivity = 24696; 14:21:51.098 [planetinfo.record]: name = "Orinriza"; 14:21:51.098 [planetinfo.record]: inhabitant = "Black Fat Humanoid"; 14:21:51.098 [planetinfo.record]: inhabitants = "Black Fat Humanoids"; 14:21:51.098 [planetinfo.record]: description = "Orinriza is famous for its exotic fish meat but scourged by deadly solar activity."; 14:21:51.101 [planetinfo.record]: air_color = 0.639305, 0.768872, 0.990545, 1; 14:21:51.101 [planetinfo.record]: cloud_alpha = 1.000000; 14:21:51.101 [planetinfo.record]: cloud_color = 0.521568, 0.974609, 0.932137, 1; 14:21:51.101 [planetinfo.record]: cloud_fraction = 0.240000; 14:21:51.102 [planetinfo.record]: land_color = 0.641953, 0.66, 0.64858, 1; 14:21:51.102 [planetinfo.record]: land_fraction = 0.250000; 14:21:51.102 [planetinfo.record]: polar_cloud_color = 0.665893, 0.938574, 0.91301, 1; 14:21:51.102 [planetinfo.record]: polar_land_color = 0.927615, 0.934, 0.92996, 1; 14:21:51.102 [planetinfo.record]: polar_sea_color = 0.730587, 0.933984, 0.872012, 1; 14:21:51.102 [planetinfo.record]: sea_color = 0.0850983, 0.660156, 0.484943, 1; 14:21:51.102 [planetinfo.record]: rotation_speed = 0.004233 14:21:51.102 [planetinfo.record]: planet zpos = 737940.000000 14:21:51.102 [planetinfo.record]: sun_radius = 148546.071167 14:21:51.102 [planetinfo.record]: sun_vector = -0.465 -0.798 0.384 14:21:51.102 [planetinfo.record]: sun_distance = 843360 14:21:51.102 [planetinfo.record]: corona_flare = 0.030356 14:21:51.102 [planetinfo.record]: corona_hues = 0.860512 14:21:51.102 [planetinfo.record]: sun_color = 0.680034, 0.681574, 0.59887, 1 14:21:51.102 [planetinfo.record]: corona_shimmer = 0.333669 14:21:51.103 [planetinfo.record]: station_vector = 0.334 0.741 0.582 14:21:51.103 [planetinfo.record]: station = coriolis 14:21:55.344 [PLANETINFO OVER]: Done 14:21:55.345 [PLANETINFO LOGGING]: 6 146 14:21:56.348 [planetinfo.record]: seed = 122 36 116 175 14:21:56.348 [planetinfo.record]: coordinates = 36 94 14:21:56.348 [planetinfo.record]: government = 7; 14:21:56.348 [planetinfo.record]: economy = 6; 14:21:56.348 [planetinfo.record]: techlevel = 5; 14:21:56.348 [planetinfo.record]: population = 34; 14:21:56.348 [planetinfo.record]: productivity = 11968; 14:21:56.348 [planetinfo.record]: name = "Aenbi"; 14:21:56.348 [planetinfo.record]: inhabitant = "Human Colonial"; 14:21:56.348 [planetinfo.record]: inhabitants = "Human Colonials"; 14:21:56.348 [planetinfo.record]: description = "This world is a revolting little planet."; 14:21:56.352 [planetinfo.record]: air_color = 0.660365, 0.731593, 0.995098, 1; 14:21:56.352 [planetinfo.record]: cloud_alpha = 1.000000; 14:21:56.352 [planetinfo.record]: cloud_color = 0.582932, 0.836275, 0.988281, 1; 14:21:56.352 [planetinfo.record]: cloud_fraction = 0.240000; 14:21:56.352 [planetinfo.record]: land_color = 0.490166, 0.484688, 0.66, 1; 14:21:56.352 [planetinfo.record]: land_fraction = 0.440000; 14:21:56.352 [planetinfo.record]: polar_cloud_color = 0.702548, 0.85391, 0.944727, 1; 14:21:56.352 [planetinfo.record]: polar_land_color = 0.873915, 0.871977, 0.934, 1; 14:21:56.352 [planetinfo.record]: polar_sea_color = 0.939648, 0.894825, 0.881104, 1; 14:21:56.352 [planetinfo.record]: sea_color = 0.603516, 0.48836, 0.453108, 1; 14:21:56.353 [planetinfo.record]: rotation_speed = 0.003444 14:21:56.353 [planetinfo.record]: planet zpos = 869960.000000 14:21:56.353 [planetinfo.record]: sun_radius = 137955.106201 14:21:56.353 [planetinfo.record]: sun_vector = -0.134 0.721 -0.680 14:21:56.353 [planetinfo.record]: sun_distance = 1539160 14:21:56.353 [planetinfo.record]: corona_flare = 0.081529 14:21:56.353 [planetinfo.record]: corona_hues = 0.547615 14:21:56.353 [planetinfo.record]: sun_color = 0.614511, 0.760049, 0.772412, 1 14:21:56.353 [planetinfo.record]: corona_shimmer = 0.246663 14:21:56.353 [planetinfo.record]: station_vector = -0.759 -0.618 0.203 14:21:56.353 [planetinfo.record]: station = coriolis 14:22:01.012 [PLANETINFO OVER]: Done 14:22:01.013 [PLANETINFO LOGGING]: 6 147 14:22:02.030 [planetinfo.record]: seed = 162 231 208 31 14:22:02.030 [planetinfo.record]: coordinates = 231 6 14:22:02.030 [planetinfo.record]: government = 4; 14:22:02.030 [planetinfo.record]: economy = 6; 14:22:02.030 [planetinfo.record]: techlevel = 6; 14:22:02.030 [planetinfo.record]: population = 35; 14:22:02.030 [planetinfo.record]: productivity = 8960; 14:22:02.030 [planetinfo.record]: name = "Ondira"; 14:22:02.030 [planetinfo.record]: inhabitant = "Green Bug-Eyed Bird"; 14:22:02.030 [planetinfo.record]: inhabitants = "Green Bug-Eyed Birds"; 14:22:02.031 [planetinfo.record]: description = "Ondira is a revolting dump."; 14:22:02.044 [planetinfo.record]: air_color = 0.723976, 0.615228, 0.856564, 1; 14:22:02.044 [planetinfo.record]: cloud_alpha = 1.000000; 14:22:02.044 [planetinfo.record]: cloud_color = 0.572266, 0.431435, 0.520554, 1; 14:22:02.044 [planetinfo.record]: cloud_fraction = 0.210000; 14:22:02.044 [planetinfo.record]: land_color = 0.402188, 0.66, 0.412258, 1; 14:22:02.044 [planetinfo.record]: land_fraction = 0.310000; 14:22:02.044 [planetinfo.record]: polar_cloud_color = 0.75752, 0.641007, 0.714737, 1; 14:22:02.044 [planetinfo.record]: polar_land_color = 0.842789, 0.934, 0.846352, 1; 14:22:02.044 [planetinfo.record]: polar_sea_color = 0.929297, 0.876335, 0.84762, 1; 14:22:02.044 [planetinfo.record]: sea_color = 0.707031, 0.545852, 0.458466, 1; 14:22:02.044 [planetinfo.record]: rotation_speed = 0.000752 14:22:02.044 [planetinfo.record]: planet zpos = 757570.000000 14:22:02.045 [planetinfo.record]: sun_radius = 164434.270935 14:22:02.045 [planetinfo.record]: sun_vector = -0.763 0.351 -0.542 14:22:02.045 [planetinfo.record]: sun_distance = 1515140 14:22:02.045 [planetinfo.record]: corona_flare = 0.004120 14:22:02.045 [planetinfo.record]: corona_hues = 0.946556 14:22:02.045 [planetinfo.record]: sun_color = 0.678191, 0.736423, 0.760858, 1 14:22:02.045 [planetinfo.record]: corona_shimmer = 0.282712 14:22:02.045 [planetinfo.record]: station_vector = 0.223 -0.552 0.803 14:22:02.045 [planetinfo.record]: station = coriolis 14:22:07.364 [PLANETINFO OVER]: Done 14:22:07.364 [PLANETINFO LOGGING]: 6 148 14:22:08.369 [planetinfo.record]: seed = 66 66 180 100 14:22:08.369 [planetinfo.record]: coordinates = 66 20 14:22:08.369 [planetinfo.record]: government = 0; 14:22:08.369 [planetinfo.record]: economy = 6; 14:22:08.369 [planetinfo.record]: techlevel = 3; 14:22:08.369 [planetinfo.record]: population = 19; 14:22:08.369 [planetinfo.record]: productivity = 2432; 14:22:08.369 [planetinfo.record]: name = "Zaanxege"; 14:22:08.369 [planetinfo.record]: inhabitant = "Fierce Blue Feline"; 14:22:08.369 [planetinfo.record]: inhabitants = "Fierce Blue Felines"; 14:22:08.369 [planetinfo.record]: description = "This planet is a dull place."; 14:22:08.388 [planetinfo.record]: air_color = 0.441717, 0.5219, 0.924855, 1; 14:22:08.388 [planetinfo.record]: cloud_alpha = 1.000000; 14:22:08.388 [planetinfo.record]: cloud_color = 0.024292, 0.389051, 0.777344, 1; 14:22:08.388 [planetinfo.record]: cloud_fraction = 0.300000; 14:22:08.388 [planetinfo.record]: land_color = 0.66, 0.0876563, 0.154728, 1; 14:22:08.388 [planetinfo.record]: land_fraction = 0.470000; 14:22:08.388 [planetinfo.record]: polar_cloud_color = 0.335275, 0.5845, 0.849805, 1; 14:22:08.388 [planetinfo.record]: polar_land_color = 0.934, 0.731512, 0.755241, 1; 14:22:08.388 [planetinfo.record]: polar_sea_color = 0.942187, 0.919645, 0.890662, 1; 14:22:08.388 [planetinfo.record]: sea_color = 0.578125, 0.522797, 0.45166, 1; 14:22:08.388 [planetinfo.record]: rotation_speed = 0.003727 14:22:08.388 [planetinfo.record]: planet zpos = 507780.000000 14:22:08.388 [planetinfo.record]: sun_radius = 119732.107544 14:22:08.388 [planetinfo.record]: sun_vector = 0.608 0.683 -0.405 14:22:08.388 [planetinfo.record]: sun_distance = 742140 14:22:08.388 [planetinfo.record]: corona_flare = 0.089696 14:22:08.388 [planetinfo.record]: corona_hues = 0.596283 14:22:08.388 [planetinfo.record]: sun_color = 0.769626, 0.764832, 0.763998, 1 14:22:08.389 [planetinfo.record]: corona_shimmer = 0.651542 14:22:08.389 [planetinfo.record]: station_vector = -0.095 0.408 0.908 14:22:08.389 [planetinfo.record]: station = coriolis 14:22:13.653 [PLANETINFO OVER]: Done 14:22:13.654 [PLANETINFO LOGGING]: 6 149 14:22:14.660 [planetinfo.record]: seed = 122 131 64 162 14:22:14.660 [planetinfo.record]: coordinates = 131 98 14:22:14.660 [planetinfo.record]: government = 7; 14:22:14.660 [planetinfo.record]: economy = 2; 14:22:14.660 [planetinfo.record]: techlevel = 12; 14:22:14.660 [planetinfo.record]: population = 58; 14:22:14.660 [planetinfo.record]: productivity = 40832; 14:22:14.660 [planetinfo.record]: name = "Xeusreor"; 14:22:14.660 [planetinfo.record]: inhabitant = "Human Colonial"; 14:22:14.660 [planetinfo.record]: inhabitants = "Human Colonials"; 14:22:14.660 [planetinfo.record]: description = "Xeusreor is well known for the Xeusreorian tree wolf but scourged by deadly solar activity."; 14:22:14.692 [planetinfo.record]: air_color = 0.744147, 0.63962, 0.840305, 1; 14:22:14.692 [planetinfo.record]: cloud_alpha = 1.000000; 14:22:14.692 [planetinfo.record]: cloud_color = 0.523438, 0.462097, 0.487975, 1; 14:22:14.692 [planetinfo.record]: cloud_fraction = 0.490000; 14:22:14.692 [planetinfo.record]: land_color = 0.434952, 0.355042, 0.541016, 1; 14:22:14.692 [planetinfo.record]: land_fraction = 0.620000; 14:22:14.692 [planetinfo.record]: polar_cloud_color = 0.735547, 0.681674, 0.704401, 1; 14:22:14.692 [planetinfo.record]: polar_land_color = 0.899539, 0.86461, 0.945898, 1; 14:22:14.692 [planetinfo.record]: polar_sea_color = 0.886454, 0.927539, 0.869749, 1; 14:22:14.692 [planetinfo.record]: sea_color = 0.596224, 0.724609, 0.544023, 1; 14:22:14.692 [planetinfo.record]: rotation_speed = 0.002359 14:22:14.692 [planetinfo.record]: planet zpos = 380490.000000 14:22:14.692 [planetinfo.record]: sun_radius = 95529.434967 14:22:14.692 [planetinfo.record]: sun_vector = 0.596 -0.209 -0.775 14:22:14.692 [planetinfo.record]: sun_distance = 657210 14:22:14.692 [planetinfo.record]: corona_flare = 0.020901 14:22:14.692 [planetinfo.record]: corona_hues = 0.557808 14:22:14.692 [planetinfo.record]: sun_color = 0.708194, 0.607023, 0.573584, 1 14:22:14.693 [planetinfo.record]: corona_shimmer = 0.325648 14:22:14.693 [planetinfo.record]: station_vector = -0.127 0.404 0.906 14:22:14.693 [planetinfo.record]: station = dodecahedron 14:22:19.077 [PLANETINFO OVER]: Done 14:22:19.078 [PLANETINFO LOGGING]: 6 150 14:22:20.080 [planetinfo.record]: seed = 42 217 20 16 14:22:20.080 [planetinfo.record]: coordinates = 217 174 14:22:20.080 [planetinfo.record]: government = 5; 14:22:20.081 [planetinfo.record]: economy = 6; 14:22:20.081 [planetinfo.record]: techlevel = 5; 14:22:20.081 [planetinfo.record]: population = 32; 14:22:20.081 [planetinfo.record]: productivity = 9216; 14:22:20.081 [planetinfo.record]: name = "Ertius"; 14:22:20.081 [planetinfo.record]: inhabitant = "Human Colonial"; 14:22:20.081 [planetinfo.record]: inhabitants = "Human Colonials"; 14:22:20.081 [planetinfo.record]: description = "This world is reasonably well known for its great dense forests but scourged by frequent civil war."; 14:22:20.092 [planetinfo.record]: air_color = 0.736268, 0.788724, 0.921604, 1; 14:22:20.092 [planetinfo.record]: cloud_alpha = 1.000000; 14:22:20.092 [planetinfo.record]: cloud_color = 0.76458, 0.767086, 0.767578, 1; 14:22:20.092 [planetinfo.record]: cloud_fraction = 0.490000; 14:22:20.092 [planetinfo.record]: land_color = 0.521783, 0.525391, 0.0636215, 1; 14:22:20.092 [planetinfo.record]: land_fraction = 0.650000; 14:22:20.092 [planetinfo.record]: polar_cloud_color = 0.843346, 0.845072, 0.84541, 1; 14:22:20.092 [planetinfo.record]: polar_land_color = 0.945835, 0.947461, 0.739279, 1; 14:22:20.092 [planetinfo.record]: polar_sea_color = 0.851311, 0.896215, 0.909961, 1; 14:22:20.092 [planetinfo.record]: sea_color = 0.668259, 0.845985, 0.900391, 1; 14:22:20.092 [planetinfo.record]: rotation_speed = 0.004024 14:22:20.092 [planetinfo.record]: planet zpos = 303300.000000 14:22:20.092 [planetinfo.record]: sun_radius = 56943.260651 14:22:20.092 [planetinfo.record]: sun_vector = -0.748 -0.166 0.642 14:22:20.092 [planetinfo.record]: sun_distance = 576270 14:22:20.092 [planetinfo.record]: corona_flare = 0.012755 14:22:20.092 [planetinfo.record]: corona_hues = 0.600868 14:22:20.092 [planetinfo.record]: sun_color = 0.698047, 0.456434, 0.373186, 1 14:22:20.092 [planetinfo.record]: corona_shimmer = 0.548639 14:22:20.092 [planetinfo.record]: station_vector = 0.825 0.495 0.273 14:22:20.092 [planetinfo.record]: station = coriolis 14:22:24.792 [PLANETINFO OVER]: Done 14:22:24.793 [PLANETINFO LOGGING]: 6 151 14:22:25.796 [planetinfo.record]: seed = 114 40 208 64 14:22:25.797 [planetinfo.record]: coordinates = 40 128 14:22:25.797 [planetinfo.record]: government = 6; 14:22:25.797 [planetinfo.record]: economy = 0; 14:22:25.797 [planetinfo.record]: techlevel = 10; 14:22:25.797 [planetinfo.record]: population = 47; 14:22:25.797 [planetinfo.record]: productivity = 37600; 14:22:25.797 [planetinfo.record]: name = "Arberi"; 14:22:25.797 [planetinfo.record]: inhabitant = "Large Yellow Slimy Rodent"; 14:22:25.797 [planetinfo.record]: inhabitants = "Large Yellow Slimy Rodents"; 14:22:25.797 [planetinfo.record]: description = "The world Arberi is scourged by evil disease."; 14:22:25.799 [planetinfo.record]: air_color = 0.477705, 0.547382, 0.897539, 1; 14:22:25.799 [planetinfo.record]: cloud_alpha = 1.000000; 14:22:25.799 [planetinfo.record]: cloud_color = 0.135803, 0.406816, 0.695312, 1; 14:22:25.799 [planetinfo.record]: cloud_fraction = 0.370000; 14:22:25.799 [planetinfo.record]: land_color = 0.658203, 0.658203, 0.658203, 1; 14:22:25.799 [planetinfo.record]: land_fraction = 0.330000; 14:22:25.799 [planetinfo.record]: polar_cloud_color = 0.404064, 0.602089, 0.812891, 1; 14:22:25.799 [planetinfo.record]: polar_land_color = 0.93418, 0.93418, 0.93418, 1; 14:22:25.799 [planetinfo.record]: polar_sea_color = 0.774089, 0.902344, 0.75959, 1; 14:22:25.799 [planetinfo.record]: sea_color = 0.421345, 0.976562, 0.358582, 1; 14:22:25.799 [planetinfo.record]: rotation_speed = 0.003863 14:22:25.799 [planetinfo.record]: planet zpos = 314160.000000 14:22:25.799 [planetinfo.record]: sun_radius = 61988.221436 14:22:25.800 [planetinfo.record]: sun_vector = -0.943 0.332 0.033 14:22:25.800 [planetinfo.record]: sun_distance = 514080 14:22:25.800 [planetinfo.record]: corona_flare = 0.037714 14:22:25.800 [planetinfo.record]: corona_hues = 0.752228 14:22:25.800 [planetinfo.record]: sun_color = 0.794907, 0.581199, 0.534696, 1 14:22:25.800 [planetinfo.record]: corona_shimmer = 0.489403 14:22:25.800 [planetinfo.record]: station_vector = 0.257 0.965 0.053 14:22:25.800 [planetinfo.record]: station = coriolis 14:22:30.188 [PLANETINFO OVER]: Done 14:22:30.189 [PLANETINFO LOGGING]: 6 152 14:22:31.192 [planetinfo.record]: seed = 50 126 148 187 14:22:31.192 [planetinfo.record]: coordinates = 126 83 14:22:31.192 [planetinfo.record]: government = 6; 14:22:31.192 [planetinfo.record]: economy = 3; 14:22:31.192 [planetinfo.record]: techlevel = 9; 14:22:31.192 [planetinfo.record]: population = 46; 14:22:31.192 [planetinfo.record]: productivity = 25760; 14:22:31.192 [planetinfo.record]: name = "Andibia"; 14:22:31.192 [planetinfo.record]: inhabitant = "Harmless Furry Rodent"; 14:22:31.192 [planetinfo.record]: inhabitants = "Harmless Furry Rodents"; 14:22:31.192 [planetinfo.record]: description = "The planet Andibia is famous for Andibiaian wolf meat but beset by deadly edible moths."; 14:22:31.196 [planetinfo.record]: air_color = 0.690025, 0.504899, 0.946318, 1; 14:22:31.196 [planetinfo.record]: cloud_alpha = 1.000000; 14:22:31.196 [planetinfo.record]: cloud_color = 0.841797, 0.167702, 0.657474, 1; 14:22:31.196 [planetinfo.record]: cloud_fraction = 0.390000; 14:22:31.196 [planetinfo.record]: land_color = 0.216331, 0.517578, 0.432853, 1; 14:22:31.196 [planetinfo.record]: land_fraction = 0.330000; 14:22:31.196 [planetinfo.record]: polar_cloud_color = 0.878809, 0.438975, 0.758542, 1; 14:22:31.197 [planetinfo.record]: polar_land_color = 0.810266, 0.948242, 0.909436, 1; 14:22:31.197 [planetinfo.record]: polar_sea_color = 0.878214, 0.927344, 0.871196, 1; 14:22:31.197 [planetinfo.record]: sea_color = 0.572594, 0.726562, 0.550598, 1; 14:22:31.197 [planetinfo.record]: rotation_speed = 0.004401 14:22:31.197 [planetinfo.record]: planet zpos = 806120.000000 14:22:31.197 [planetinfo.record]: sun_radius = 145488.430481 14:22:31.197 [planetinfo.record]: sun_vector = 0.551 -0.312 -0.774 14:22:31.197 [planetinfo.record]: sun_distance = 1151600 14:22:31.197 [planetinfo.record]: corona_flare = 0.031767 14:22:31.197 [planetinfo.record]: corona_hues = 0.565567 14:22:31.198 [planetinfo.record]: sun_color = 0.600859, 0.746314, 0.749335, 1 14:22:31.198 [planetinfo.record]: corona_shimmer = 0.568553 14:22:31.198 [planetinfo.record]: station_vector = -0.652 -0.496 0.573 14:22:31.198 [planetinfo.record]: station = coriolis 14:22:35.997 [PLANETINFO OVER]: Done 14:22:35.998 [PLANETINFO LOGGING]: 6 153 14:22:37.001 [planetinfo.record]: seed = 138 15 128 99 14:22:37.001 [planetinfo.record]: coordinates = 15 198 14:22:37.001 [planetinfo.record]: government = 1; 14:22:37.001 [planetinfo.record]: economy = 6; 14:22:37.001 [planetinfo.record]: techlevel = 5; 14:22:37.001 [planetinfo.record]: population = 28; 14:22:37.001 [planetinfo.record]: productivity = 4480; 14:22:37.001 [planetinfo.record]: name = "Georinar"; 14:22:37.001 [planetinfo.record]: inhabitant = "Large Blue Bug-Eyed Bird"; 14:22:37.001 [planetinfo.record]: inhabitants = "Large Blue Bug-Eyed Birds"; 14:22:37.001 [planetinfo.record]: description = "The world Georinar is scourged by evil disease."; 14:22:37.003 [planetinfo.record]: air_color = 0.548739, 0.572618, 0.846809, 1; 14:22:37.003 [planetinfo.record]: cloud_alpha = 1.000000; 14:22:37.003 [planetinfo.record]: cloud_color = 0.294815, 0.351037, 0.542969, 1; 14:22:37.003 [planetinfo.record]: cloud_fraction = 0.120000; 14:22:37.003 [planetinfo.record]: land_color = 0.66, 0.552645, 0.448594, 1; 14:22:37.003 [planetinfo.record]: land_fraction = 0.420000; 14:22:37.003 [planetinfo.record]: polar_cloud_color = 0.53172, 0.579891, 0.744336, 1; 14:22:37.003 [planetinfo.record]: polar_land_color = 0.934, 0.896019, 0.859207, 1; 14:22:37.003 [planetinfo.record]: polar_sea_color = 0.872385, 0.851379, 0.90625, 1; 14:22:37.003 [planetinfo.record]: sea_color = 0.797367, 0.710449, 0.9375, 1; 14:22:37.003 [planetinfo.record]: rotation_speed = 0.000438 14:22:37.003 [planetinfo.record]: planet zpos = 467870.000000 14:22:37.003 [planetinfo.record]: sun_radius = 83416.336517 14:22:37.003 [planetinfo.record]: sun_vector = 0.625 -0.258 0.736 14:22:37.003 [planetinfo.record]: sun_distance = 755790 14:22:37.003 [planetinfo.record]: corona_flare = 0.004616 14:22:37.003 [planetinfo.record]: corona_hues = 0.613312 14:22:37.004 [planetinfo.record]: sun_color = 0.690265, 0.648291, 0.456433, 1 14:22:37.004 [planetinfo.record]: corona_shimmer = 0.575624 14:22:37.004 [planetinfo.record]: station_vector = -0.090 -0.825 0.558 14:22:37.004 [planetinfo.record]: station = coriolis 14:22:42.025 [PLANETINFO OVER]: Done 14:22:42.026 [PLANETINFO LOGGING]: 6 154 14:22:43.029 [planetinfo.record]: seed = 90 74 52 49 14:22:43.029 [planetinfo.record]: coordinates = 74 172 14:22:43.029 [planetinfo.record]: government = 3; 14:22:43.029 [planetinfo.record]: economy = 4; 14:22:43.029 [planetinfo.record]: techlevel = 7; 14:22:43.029 [planetinfo.record]: population = 36; 14:22:43.029 [planetinfo.record]: productivity = 12096; 14:22:43.029 [planetinfo.record]: name = "Atuszais"; 14:22:43.029 [planetinfo.record]: inhabitant = "Human Colonial"; 14:22:43.029 [planetinfo.record]: inhabitants = "Human Colonials"; 14:22:43.029 [planetinfo.record]: description = "The world Atuszais is mildly fabled for the Atuszaisian mountain slug but ravaged by vicious disease."; 14:22:43.040 [planetinfo.record]: air_color = 0.591263, 0.923555, 0.853178, 1; 14:22:43.040 [planetinfo.record]: cloud_alpha = 1.000000; 14:22:43.040 [planetinfo.record]: cloud_color = 0.773438, 0.54118, 0.401825, 1; 14:22:43.047 [planetinfo.record]: cloud_fraction = 0.420000; 14:22:43.048 [planetinfo.record]: land_color = 0.507812, 0.466156, 0.48373, 1; 14:22:43.048 [planetinfo.record]: land_fraction = 0.510000; 14:22:43.048 [planetinfo.record]: polar_cloud_color = 0.848047, 0.688883, 0.593384, 1; 14:22:43.048 [planetinfo.record]: polar_land_color = 0.949219, 0.929752, 0.937965, 1; 14:22:43.048 [planetinfo.record]: polar_sea_color = 0.939062, 0.917497, 0.883856, 1; 14:22:43.048 [planetinfo.record]: sea_color = 0.609375, 0.553399, 0.466077, 1; 14:22:43.048 [planetinfo.record]: rotation_speed = 0.004642 14:22:43.048 [planetinfo.record]: planet zpos = 283140.000000 14:22:43.048 [planetinfo.record]: sun_radius = 70949.654236 14:22:43.048 [planetinfo.record]: sun_vector = 0.456 0.879 -0.141 14:22:43.048 [planetinfo.record]: sun_distance = 534820 14:22:43.048 [planetinfo.record]: corona_flare = 0.056630 14:22:43.048 [planetinfo.record]: corona_hues = 0.938339 14:22:43.048 [planetinfo.record]: sun_color = 0.361727, 0.668405, 0.726102, 1 14:22:43.049 [planetinfo.record]: corona_shimmer = 0.389391 14:22:43.049 [planetinfo.record]: station_vector = 0.744 -0.183 0.643 14:22:43.049 [planetinfo.record]: station = coriolis 14:22:47.283 [PLANETINFO OVER]: Done 14:22:47.284 [PLANETINFO LOGGING]: 6 155 14:22:48.287 [planetinfo.record]: seed = 194 253 80 202 14:22:48.287 [planetinfo.record]: coordinates = 253 164 14:22:48.287 [planetinfo.record]: government = 0; 14:22:48.287 [planetinfo.record]: economy = 6; 14:22:48.287 [planetinfo.record]: techlevel = 2; 14:22:48.288 [planetinfo.record]: population = 15; 14:22:48.288 [planetinfo.record]: productivity = 1920; 14:22:48.288 [planetinfo.record]: name = "Arinra"; 14:22:48.288 [planetinfo.record]: inhabitant = "Human Colonial"; 14:22:48.288 [planetinfo.record]: inhabitants = "Human Colonials"; 14:22:48.288 [planetinfo.record]: description = "This planet is most notable for its fabulous cuisine but plagued by lethal spotted batoids."; 14:22:48.296 [planetinfo.record]: air_color = 0.791409, 0.762873, 0.958025, 1; 14:22:48.297 [planetinfo.record]: cloud_alpha = 1.000000; 14:22:48.297 [planetinfo.record]: cloud_color = 0.871333, 0.866676, 0.876953, 1; 14:22:48.297 [planetinfo.record]: cloud_fraction = 0.250000; 14:22:48.297 [planetinfo.record]: land_color = 0.491254, 0.476953, 0.66, 1; 14:22:48.297 [planetinfo.record]: land_fraction = 0.310000; 14:22:48.297 [planetinfo.record]: polar_cloud_color = 0.891046, 0.888076, 0.894629, 1; 14:22:48.297 [planetinfo.record]: polar_land_color = 0.8743, 0.86924, 0.934, 1; 14:22:48.297 [planetinfo.record]: polar_sea_color = 0.933789, 0.906845, 0.878163, 1; 14:22:48.297 [planetinfo.record]: sea_color = 0.662109, 0.58569, 0.504341, 1; 14:22:48.297 [planetinfo.record]: rotation_speed = 0.002083 14:22:48.297 [planetinfo.record]: planet zpos = 506610.000000 14:22:48.297 [planetinfo.record]: sun_radius = 119609.378662 14:22:48.297 [planetinfo.record]: sun_vector = 0.873 -0.432 -0.225 14:22:48.297 [planetinfo.record]: sun_distance = 1182090 14:22:48.297 [planetinfo.record]: corona_flare = 0.065077 14:22:48.298 [planetinfo.record]: corona_hues = 0.511772 14:22:48.298 [planetinfo.record]: sun_color = 0.820123, 0.701928, 0.596869, 1 14:22:48.298 [planetinfo.record]: corona_shimmer = 0.324458 14:22:48.298 [planetinfo.record]: station_vector = -0.386 0.658 0.647 14:22:48.298 [planetinfo.record]: station = coriolis 14:22:53.140 [PLANETINFO OVER]: Done 14:22:53.141 [PLANETINFO LOGGING]: 6 156 14:22:54.146 [planetinfo.record]: seed = 162 106 244 10 14:22:54.146 [planetinfo.record]: coordinates = 106 52 14:22:54.146 [planetinfo.record]: government = 4; 14:22:54.146 [planetinfo.record]: economy = 4; 14:22:54.146 [planetinfo.record]: techlevel = 7; 14:22:54.146 [planetinfo.record]: population = 37; 14:22:54.146 [planetinfo.record]: productivity = 14208; 14:22:54.146 [planetinfo.record]: name = "Areson"; 14:22:54.146 [planetinfo.record]: inhabitant = "Small Green Rodent"; 14:22:54.146 [planetinfo.record]: inhabitants = "Small Green Rodents"; 14:22:54.146 [planetinfo.record]: description = "The world Areson is a boring world."; 14:22:54.157 [planetinfo.record]: air_color = 0.599194, 0.904693, 0.811379, 1; 14:22:54.158 [planetinfo.record]: cloud_alpha = 1.000000; 14:22:54.158 [planetinfo.record]: cloud_color = 0.716797, 0.447823, 0.419998, 1; 14:22:54.158 [planetinfo.record]: cloud_fraction = 0.620000; 14:22:54.158 [planetinfo.record]: land_color = 0.216293, 0.428231, 0.615234, 1; 14:22:54.158 [planetinfo.record]: land_fraction = 0.450000; 14:22:54.158 [planetinfo.record]: polar_cloud_color = 0.822559, 0.629646, 0.609689, 1; 14:22:54.158 [planetinfo.record]: polar_land_color = 0.786341, 0.867163, 0.938477, 1; 14:22:54.158 [planetinfo.record]: polar_sea_color = 0.937891, 0.922739, 0.882478, 1; 14:22:54.158 [planetinfo.record]: sea_color = 0.621094, 0.580958, 0.474312, 1; 14:22:54.158 [planetinfo.record]: rotation_speed = 0.004942 14:22:54.158 [planetinfo.record]: planet zpos = 657840.000000 14:22:54.158 [planetinfo.record]: sun_radius = 149720.265808 14:22:54.158 [planetinfo.record]: sun_vector = 0.254 -0.521 0.815 14:22:54.158 [planetinfo.record]: sun_distance = 1096400 14:22:54.158 [planetinfo.record]: corona_flare = 0.002737 14:22:54.158 [planetinfo.record]: corona_hues = 0.942513 14:22:54.158 [planetinfo.record]: sun_color = 0.663525, 0.721514, 0.747543, 1 14:22:54.158 [planetinfo.record]: corona_shimmer = 0.353062 14:22:54.158 [planetinfo.record]: station_vector = -0.495 0.859 0.133 14:22:54.158 [planetinfo.record]: station = coriolis 14:22:58.785 [PLANETINFO OVER]: Done 14:22:58.786 [PLANETINFO LOGGING]: 6 157 14:22:59.789 [planetinfo.record]: seed = 26 212 64 157 14:22:59.789 [planetinfo.record]: coordinates = 212 31 14:22:59.789 [planetinfo.record]: government = 3; 14:22:59.789 [planetinfo.record]: economy = 7; 14:22:59.789 [planetinfo.record]: techlevel = 2; 14:22:59.789 [planetinfo.record]: population = 19; 14:22:59.789 [planetinfo.record]: productivity = 3192; 14:22:59.789 [planetinfo.record]: name = "Iserxeer"; 14:22:59.789 [planetinfo.record]: inhabitant = "Human Colonial"; 14:22:59.789 [planetinfo.record]: inhabitants = "Human Colonials"; 14:22:59.789 [planetinfo.record]: description = "The world Iserxeer is reasonably noted for Zero-G hockey and its inhabitants’ ingrained silliness."; 14:22:59.791 [planetinfo.record]: air_color = 0.679894, 0.829573, 0.928107, 1; 14:22:59.791 [planetinfo.record]: cloud_alpha = 1.000000; 14:22:59.791 [planetinfo.record]: cloud_color = 0.627228, 0.787109, 0.659704, 1; 14:22:59.791 [planetinfo.record]: cloud_fraction = 0.160000; 14:22:59.791 [planetinfo.record]: land_color = 0.621094, 0.371201, 0.611332, 1; 14:22:59.791 [planetinfo.record]: land_fraction = 0.410000; 14:22:59.792 [planetinfo.record]: polar_cloud_color = 0.745756, 0.854199, 0.767784, 1; 14:22:59.792 [planetinfo.record]: polar_land_color = 0.937891, 0.843552, 0.934206, 1; 14:22:59.792 [planetinfo.record]: polar_sea_color = 0.925977, 0.856814, 0.826506, 1; 14:22:59.792 [planetinfo.record]: sea_color = 0.740234, 0.519077, 0.422165, 1; 14:22:59.792 [planetinfo.record]: rotation_speed = 0.003619 14:22:59.792 [planetinfo.record]: planet zpos = 635600.000000 14:22:59.792 [planetinfo.record]: sun_radius = 196330.338135 14:22:59.792 [planetinfo.record]: sun_vector = 0.974 0.174 0.149 14:22:59.792 [planetinfo.record]: sun_distance = 1461880 14:22:59.792 [planetinfo.record]: corona_flare = 0.037523 14:22:59.792 [planetinfo.record]: corona_hues = 0.706963 14:22:59.792 [planetinfo.record]: sun_color = 0.680222, 0.71898, 0.820474, 1 14:22:59.792 [planetinfo.record]: corona_shimmer = 0.901731 14:22:59.792 [planetinfo.record]: station_vector = 0.994 -0.083 0.073 14:22:59.792 [planetinfo.record]: station = coriolis 14:23:04.896 [PLANETINFO OVER]: Done 14:23:04.899 [PLANETINFO LOGGING]: 6 158 14:23:05.902 [planetinfo.record]: seed = 10 48 212 194 14:23:05.902 [planetinfo.record]: coordinates = 48 2 14:23:05.902 [planetinfo.record]: government = 1; 14:23:05.902 [planetinfo.record]: economy = 2; 14:23:05.903 [planetinfo.record]: techlevel = 6; 14:23:05.903 [planetinfo.record]: population = 28; 14:23:05.903 [planetinfo.record]: productivity = 8960; 14:23:05.903 [planetinfo.record]: name = "Xeraso"; 14:23:05.903 [planetinfo.record]: inhabitant = "Large Horned Bird"; 14:23:05.903 [planetinfo.record]: inhabitants = "Large Horned Birds"; 14:23:05.903 [planetinfo.record]: description = "This world is very notable for the Xerasoian edible moth."; 14:23:05.906 [planetinfo.record]: air_color = 0.674025, 0.861203, 0.904043, 1; 14:23:05.907 [planetinfo.record]: cloud_alpha = 1.000000; 14:23:05.907 [planetinfo.record]: cloud_color = 0.647958, 0.714844, 0.597565, 1; 14:23:05.907 [planetinfo.record]: cloud_fraction = 0.470000; 14:23:05.907 [planetinfo.record]: land_color = 0.648438, 0.629777, 0.544586, 1; 14:23:05.907 [planetinfo.record]: land_fraction = 0.260000; 14:23:05.907 [planetinfo.record]: polar_cloud_color = 0.773628, 0.82168, 0.737425, 1; 14:23:05.907 [planetinfo.record]: polar_land_color = 0.935156, 0.928428, 0.897713, 1; 14:23:05.908 [planetinfo.record]: polar_sea_color = 0.761154, 0.939062, 0.893196, 1; 14:23:05.908 [planetinfo.record]: sea_color = 0.147583, 0.609375, 0.490319, 1; 14:23:05.908 [planetinfo.record]: rotation_speed = 0.000330 14:23:05.908 [planetinfo.record]: planet zpos = 337600.000000 14:23:05.908 [planetinfo.record]: sun_radius = 66724.113770 14:23:05.908 [planetinfo.record]: sun_vector = 0.673 0.592 -0.443 14:23:05.908 [planetinfo.record]: sun_distance = 742720 14:23:05.908 [planetinfo.record]: corona_flare = 0.037782 14:23:05.908 [planetinfo.record]: corona_hues = 0.603729 14:23:05.908 [planetinfo.record]: sun_color = 0.356224, 0.57042, 0.694537, 1 14:23:05.908 [planetinfo.record]: corona_shimmer = 0.716366 14:23:05.908 [planetinfo.record]: station_vector = 0.270 0.791 0.549 14:23:05.908 [planetinfo.record]: station = coriolis 14:23:11.186 [PLANETINFO OVER]: Done 14:23:11.187 [PLANETINFO LOGGING]: 6 159 14:23:12.207 [planetinfo.record]: seed = 146 159 80 124 14:23:12.207 [planetinfo.record]: coordinates = 159 231 14:23:12.207 [planetinfo.record]: government = 2; 14:23:12.207 [planetinfo.record]: economy = 7; 14:23:12.207 [planetinfo.record]: techlevel = 4; 14:23:12.207 [planetinfo.record]: population = 26; 14:23:12.207 [planetinfo.record]: productivity = 3744; 14:23:12.207 [planetinfo.record]: name = "Tegeonon"; 14:23:12.207 [planetinfo.record]: inhabitant = "Human Colonial"; 14:23:12.207 [planetinfo.record]: inhabitants = "Human Colonials"; 14:23:12.207 [planetinfo.record]: description = "Tegeonon is well known for its inhabitants’ weird silliness but cursed by deadly civil war."; 14:23:12.228 [planetinfo.record]: air_color = 0.587, 0.935912, 0.889247, 1; 14:23:12.228 [planetinfo.record]: cloud_alpha = 1.000000; 14:23:12.228 [planetinfo.record]: cloud_color = 0.810547, 0.646053, 0.389442, 1; 14:23:12.228 [planetinfo.record]: cloud_fraction = 0.480000; 14:23:12.228 [planetinfo.record]: land_color = 0.539594, 0.66, 0.407344, 1; 14:23:12.228 [planetinfo.record]: land_fraction = 0.510000; 14:23:12.228 [planetinfo.record]: polar_cloud_color = 0.864746, 0.755063, 0.583957, 1; 14:23:12.228 [planetinfo.record]: polar_land_color = 0.891402, 0.934, 0.844613, 1; 14:23:12.228 [planetinfo.record]: polar_sea_color = 0.823626, 0.894089, 0.945508, 1; 14:23:12.228 [planetinfo.record]: sea_color = 0.263947, 0.426385, 0.544922, 1; 14:23:12.228 [planetinfo.record]: rotation_speed = 0.000158 14:23:12.228 [planetinfo.record]: planet zpos = 604700.000000 14:23:12.228 [planetinfo.record]: sun_radius = 177073.314819 14:23:12.228 [planetinfo.record]: sun_vector = -0.581 -0.812 0.060 14:23:12.229 [planetinfo.record]: sun_distance = 1209400 14:23:12.229 [planetinfo.record]: corona_flare = 0.034605 14:23:12.229 [planetinfo.record]: corona_hues = 0.786034 14:23:12.229 [planetinfo.record]: sun_color = 0.844223, 0.72371, 0.358971, 1 14:23:12.229 [planetinfo.record]: corona_shimmer = 0.277122 14:23:12.229 [planetinfo.record]: station_vector = 0.559 0.540 0.629 14:23:12.229 [planetinfo.record]: station = coriolis 14:23:16.982 [PLANETINFO OVER]: Done 14:23:16.983 [PLANETINFO LOGGING]: 6 160 14:23:17.988 [planetinfo.record]: seed = 146 159 212 194 14:23:17.988 [planetinfo.record]: coordinates = 159 31 14:23:17.988 [planetinfo.record]: government = 2; 14:23:17.988 [planetinfo.record]: economy = 7; 14:23:17.988 [planetinfo.record]: techlevel = 4; 14:23:17.988 [planetinfo.record]: population = 26; 14:23:17.988 [planetinfo.record]: productivity = 3744; 14:23:17.988 [planetinfo.record]: name = "Xeleza"; 14:23:17.988 [planetinfo.record]: inhabitant = "Large Slimy Lizard"; 14:23:17.988 [planetinfo.record]: inhabitants = "Large Slimy Lizards"; 14:23:17.988 [planetinfo.record]: description = "The planet Xeleza is mildly notable for killer Nustza juice."; 14:23:18.004 [planetinfo.record]: air_color = 0.502365, 0.611905, 0.859816, 1; 14:23:18.004 [planetinfo.record]: cloud_alpha = 1.000000; 14:23:18.004 [planetinfo.record]: cloud_color = 0.20462, 0.546649, 0.582031, 1; 14:23:18.004 [planetinfo.record]: cloud_fraction = 0.560000; 14:23:18.004 [planetinfo.record]: land_color = 0.640261, 0.623906, 0.66, 1; 14:23:18.004 [planetinfo.record]: land_fraction = 0.250000; 14:23:18.004 [planetinfo.record]: polar_cloud_color = 0.453131, 0.732966, 0.761914, 1; 14:23:18.004 [planetinfo.record]: polar_land_color = 0.927017, 0.92123, 0.934, 1; 14:23:18.004 [planetinfo.record]: polar_sea_color = 0.948242, 0.867657, 0.728776, 1; 14:23:18.004 [planetinfo.record]: sea_color = 0.517578, 0.341635, 0.038414, 1; 14:23:18.004 [planetinfo.record]: rotation_speed = 0.000660 14:23:18.004 [planetinfo.record]: planet zpos = 523050.000000 14:23:18.004 [planetinfo.record]: sun_radius = 74124.821625 14:23:18.004 [planetinfo.record]: sun_vector = 0.200 -0.149 -0.968 14:23:18.004 [planetinfo.record]: sun_distance = 662530 14:23:18.004 [planetinfo.record]: corona_flare = 0.037418 14:23:18.004 [planetinfo.record]: corona_hues = 0.783661 14:23:18.004 [planetinfo.record]: sun_color = 0.781876, 0.611376, 0.603357, 1 14:23:18.004 [planetinfo.record]: corona_shimmer = 0.313554 14:23:18.005 [planetinfo.record]: station_vector = 0.950 0.013 0.311 14:23:18.005 [planetinfo.record]: station = coriolis 14:23:22.917 [PLANETINFO OVER]: Done 14:23:22.918 [PLANETINFO LOGGING]: 6 161 14:23:23.931 [planetinfo.record]: seed = 42 41 128 143 14:23:23.931 [planetinfo.record]: coordinates = 41 228 14:23:23.931 [planetinfo.record]: government = 5; 14:23:23.931 [planetinfo.record]: economy = 4; 14:23:23.931 [planetinfo.record]: techlevel = 7; 14:23:23.931 [planetinfo.record]: population = 38; 14:23:23.931 [planetinfo.record]: productivity = 16416; 14:23:23.931 [planetinfo.record]: name = "Aislaxe"; 14:23:23.931 [planetinfo.record]: inhabitant = "Black Furry Rodent"; 14:23:23.931 [planetinfo.record]: inhabitants = "Black Furry Rodents"; 14:23:23.931 [planetinfo.record]: description = "This planet is a tedious place."; 14:23:23.945 [planetinfo.record]: air_color = 0.404736, 0.833801, 0.742929, 1; 14:23:23.946 [planetinfo.record]: cloud_alpha = 1.000000; 14:23:23.946 [planetinfo.record]: cloud_color = 0.503906, 0.206188, 0.0275574, 1; 14:23:23.946 [planetinfo.record]: cloud_fraction = 0.190000; 14:23:23.946 [planetinfo.record]: land_color = 0.609375, 0.38562, 0.584902, 1; 14:23:23.946 [planetinfo.record]: land_fraction = 0.640000; 14:23:23.946 [planetinfo.record]: polar_cloud_color = 0.726758, 0.458393, 0.297375, 1; 14:23:23.946 [planetinfo.record]: polar_land_color = 0.939062, 0.852859, 0.929634, 1; 14:23:23.946 [planetinfo.record]: polar_sea_color = 0.933789, 0.910251, 0.879987, 1; 14:23:23.946 [planetinfo.record]: sea_color = 0.662109, 0.595349, 0.509514, 1; 14:23:23.946 [planetinfo.record]: rotation_speed = 0.001712 14:23:23.946 [planetinfo.record]: planet zpos = 736670.000000 14:23:23.946 [planetinfo.record]: sun_radius = 177027.616730 14:23:23.946 [planetinfo.record]: sun_vector = 0.901 -0.244 -0.359 14:23:23.946 [planetinfo.record]: sun_distance = 1138490 14:23:23.946 [planetinfo.record]: corona_flare = 0.023779 14:23:23.946 [planetinfo.record]: corona_hues = 0.540184 14:23:23.948 [planetinfo.record]: sun_color = 0.462125, 0.748214, 0.755701, 1 14:23:23.948 [planetinfo.record]: corona_shimmer = 0.303872 14:23:23.948 [planetinfo.record]: station_vector = 0.273 0.549 0.790 14:23:23.948 [planetinfo.record]: station = coriolis 14:23:28.486 [PLANETINFO OVER]: Done 14:23:28.487 [PLANETINFO LOGGING]: 6 162 14:23:29.490 [planetinfo.record]: seed = 58 130 244 116 14:23:29.490 [planetinfo.record]: coordinates = 130 85 14:23:29.490 [planetinfo.record]: government = 7; 14:23:29.490 [planetinfo.record]: economy = 5; 14:23:29.490 [planetinfo.record]: techlevel = 8; 14:23:29.491 [planetinfo.record]: population = 45; 14:23:29.491 [planetinfo.record]: productivity = 19800; 14:23:29.491 [planetinfo.record]: name = "Rainza"; 14:23:29.491 [planetinfo.record]: inhabitant = "Blue Insect"; 14:23:29.491 [planetinfo.record]: inhabitants = "Blue Insects"; 14:23:29.491 [planetinfo.record]: description = "This world is ravaged by occasional solar activity."; 14:23:29.503 [planetinfo.record]: air_color = 0.755288, 0.575307, 0.905994, 1; 14:23:29.503 [planetinfo.record]: cloud_alpha = 1.000000; 14:23:29.503 [planetinfo.record]: cloud_color = 0.720703, 0.363167, 0.488863, 1; 14:23:29.503 [planetinfo.record]: cloud_fraction = 0.390000; 14:23:29.503 [planetinfo.record]: land_color = 0.149269, 0.587891, 0.402847, 1; 14:23:29.503 [planetinfo.record]: land_fraction = 0.310000; 14:23:29.503 [planetinfo.record]: polar_cloud_color = 0.824316, 0.56873, 0.658585, 1; 14:23:29.503 [planetinfo.record]: polar_land_color = 0.765653, 0.941211, 0.867147, 1; 14:23:29.503 [planetinfo.record]: polar_sea_color = 0.948828, 0.894437, 0.919933, 1; 14:23:29.503 [planetinfo.record]: sea_color = 0.511719, 0.394383, 0.449384, 1; 14:23:29.503 [planetinfo.record]: rotation_speed = 0.000932 14:23:29.503 [planetinfo.record]: planet zpos = 436700.000000 14:23:29.503 [planetinfo.record]: sun_radius = 91098.706055 14:23:29.503 [planetinfo.record]: sun_vector = 0.206 -0.834 -0.512 14:23:29.503 [planetinfo.record]: sun_distance = 714600 14:23:29.503 [planetinfo.record]: corona_flare = 0.007472 14:23:29.504 [planetinfo.record]: corona_hues = 0.646385 14:23:29.504 [planetinfo.record]: sun_color = 0.421796, 0.639383, 0.846564, 1 14:23:29.504 [planetinfo.record]: corona_shimmer = 0.276456 14:23:29.504 [planetinfo.record]: station_vector = -0.746 -0.658 0.102 14:23:29.504 [planetinfo.record]: station = coriolis 14:23:34.294 [PLANETINFO OVER]: Done 14:23:34.295 [PLANETINFO LOGGING]: 6 163 14:23:35.316 [planetinfo.record]: seed = 226 5 208 150 14:23:35.316 [planetinfo.record]: coordinates = 5 68 14:23:35.316 [planetinfo.record]: government = 4; 14:23:35.316 [planetinfo.record]: economy = 4; 14:23:35.316 [planetinfo.record]: techlevel = 6; 14:23:35.316 [planetinfo.record]: population = 33; 14:23:35.317 [planetinfo.record]: productivity = 12672; 14:23:35.317 [planetinfo.record]: name = "Veis"; 14:23:35.317 [planetinfo.record]: inhabitant = "Black Bug-Eyed Lobster"; 14:23:35.317 [planetinfo.record]: inhabitants = "Black Bug-Eyed Lobsters"; 14:23:35.317 [planetinfo.record]: description = "This planet is mildly noted for its pink Arit tulip plantations but plagued by vicious disease."; 14:23:35.323 [planetinfo.record]: air_color = 0.69143, 0.696797, 0.995098, 1; 14:23:35.323 [planetinfo.record]: cloud_alpha = 1.000000; 14:23:35.323 [planetinfo.record]: cloud_color = 0.675583, 0.687798, 0.988281, 1; 14:23:35.324 [planetinfo.record]: cloud_fraction = 0.340000; 14:23:35.324 [planetinfo.record]: land_color = 0.541749, 0.513047, 0.66, 1; 14:23:35.324 [planetinfo.record]: land_fraction = 0.360000; 14:23:35.324 [planetinfo.record]: polar_cloud_color = 0.757903, 0.765201, 0.944727, 1; 14:23:35.324 [planetinfo.record]: polar_land_color = 0.892164, 0.88201, 0.934, 1; 14:23:35.324 [planetinfo.record]: polar_sea_color = 0.938281, 0.933753, 0.885595, 1; 14:23:35.324 [planetinfo.record]: sea_color = 0.617188, 0.605274, 0.478561, 1; 14:23:35.324 [planetinfo.record]: rotation_speed = 0.003272 14:23:35.324 [planetinfo.record]: planet zpos = 609980.000000 14:23:35.324 [planetinfo.record]: sun_radius = 95536.745300 14:23:35.324 [planetinfo.record]: sun_vector = -0.349 0.604 0.717 14:23:35.324 [planetinfo.record]: sun_distance = 697120 14:23:35.324 [planetinfo.record]: corona_flare = 0.077187 14:23:35.324 [planetinfo.record]: corona_hues = 0.940758 14:23:35.324 [planetinfo.record]: sun_color = 0.66759, 0.513887, 0.195563, 1 14:23:35.325 [planetinfo.record]: corona_shimmer = 1.070780 14:23:35.325 [planetinfo.record]: station_vector = -0.976 0.183 0.120 14:23:35.325 [planetinfo.record]: station = coriolis 14:23:41.022 [PLANETINFO OVER]: Done 14:23:41.023 [PLANETINFO LOGGING]: 6 164 14:23:42.026 [planetinfo.record]: seed = 2 245 52 83 14:23:42.026 [planetinfo.record]: coordinates = 245 125 14:23:42.026 [planetinfo.record]: government = 0; 14:23:42.026 [planetinfo.record]: economy = 7; 14:23:42.026 [planetinfo.record]: techlevel = 1; 14:23:42.026 [planetinfo.record]: population = 12; 14:23:42.026 [planetinfo.record]: productivity = 1152; 14:23:42.026 [planetinfo.record]: name = "Becedibi"; 14:23:42.026 [planetinfo.record]: inhabitant = "Human Colonial"; 14:23:42.026 [planetinfo.record]: inhabitants = "Human Colonials"; 14:23:42.026 [planetinfo.record]: description = "The planet Becedibi is most famous for the Becedibiian deadly lobstoid and its pink Inet plant plantations."; 14:23:42.029 [planetinfo.record]: air_color = 0.454429, 0.701634, 0.864369, 1; 14:23:42.029 [planetinfo.record]: cloud_alpha = 1.000000; 14:23:42.029 [planetinfo.record]: cloud_color = 0.104713, 0.595703, 0.204446, 1; 14:23:42.029 [planetinfo.record]: cloud_fraction = 0.320000; 14:23:42.029 [planetinfo.record]: land_color = 0.636152, 0.66, 0.469219, 1; 14:23:42.029 [planetinfo.record]: land_fraction = 0.480000; 14:23:42.029 [planetinfo.record]: polar_cloud_color = 0.372407, 0.768066, 0.452775, 1; 14:23:42.030 [planetinfo.record]: polar_land_color = 0.925563, 0.934, 0.866504, 1; 14:23:42.030 [planetinfo.record]: polar_sea_color = 0.926469, 0.887308, 0.948438, 1; 14:23:42.030 [planetinfo.record]: sea_color = 0.467852, 0.38269, 0.515625, 1; 14:23:42.030 [planetinfo.record]: rotation_speed = 0.001247 14:23:42.030 [planetinfo.record]: planet zpos = 497770.000000 14:23:42.030 [planetinfo.record]: sun_radius = 123148.950500 14:23:42.030 [planetinfo.record]: sun_vector = 0.667 0.564 0.487 14:23:42.030 [planetinfo.record]: sun_distance = 765800 14:23:42.030 [planetinfo.record]: corona_flare = 0.026086 14:23:42.030 [planetinfo.record]: corona_hues = 0.745850 14:23:42.030 [planetinfo.record]: sun_color = 0.721967, 0.567052, 0.486749, 1 14:23:42.030 [planetinfo.record]: corona_shimmer = 0.355803 14:23:42.030 [planetinfo.record]: station_vector = -0.863 -0.324 0.386 14:23:42.030 [planetinfo.record]: station = coriolis 14:23:46.862 [PLANETINFO OVER]: Done 14:23:46.863 [PLANETINFO LOGGING]: 6 165 14:23:47.867 [planetinfo.record]: seed = 186 38 64 250 14:23:47.867 [planetinfo.record]: coordinates = 38 13 14:23:47.867 [planetinfo.record]: government = 7; 14:23:47.867 [planetinfo.record]: economy = 5; 14:23:47.867 [planetinfo.record]: techlevel = 8; 14:23:47.867 [planetinfo.record]: population = 45; 14:23:47.867 [planetinfo.record]: productivity = 19800; 14:23:47.867 [planetinfo.record]: name = "Qureaed"; 14:23:47.867 [planetinfo.record]: inhabitant = "Human Colonial"; 14:23:47.868 [planetinfo.record]: inhabitants = "Human Colonials"; 14:23:47.868 [planetinfo.record]: description = "Qureaed is most famous for its vast oceans and its fabulous monkey burgers."; 14:23:47.893 [planetinfo.record]: air_color = 0.685101, 0.844942, 0.904693, 1; 14:23:47.893 [planetinfo.record]: cloud_alpha = 1.000000; 14:23:47.893 [planetinfo.record]: cloud_color = 0.640278, 0.716797, 0.624397, 1; 14:23:47.893 [planetinfo.record]: cloud_fraction = 0.320000; 14:23:47.893 [planetinfo.record]: land_color = 0.546079, 0.139219, 0.66, 1; 14:23:47.894 [planetinfo.record]: land_fraction = 0.200000; 14:23:47.894 [planetinfo.record]: polar_cloud_color = 0.767678, 0.822559, 0.756288, 1; 14:23:47.894 [planetinfo.record]: polar_land_color = 0.893696, 0.749754, 0.934, 1; 14:23:47.894 [planetinfo.record]: polar_sea_color = 0.933931, 0.934375, 0.877528, 1; 14:23:47.894 [planetinfo.record]: sea_color = 0.655002, 0.65625, 0.496545, 1; 14:23:47.894 [planetinfo.record]: rotation_speed = 0.004885 14:23:47.894 [planetinfo.record]: planet zpos = 703820.000000 14:23:47.894 [planetinfo.record]: sun_radius = 135649.052124 14:23:47.894 [planetinfo.record]: sun_vector = 0.528 -0.034 0.848 14:23:47.894 [planetinfo.record]: sun_distance = 1136940 14:23:47.894 [planetinfo.record]: corona_flare = 0.054512 14:23:47.894 [planetinfo.record]: corona_hues = 0.916229 14:23:47.894 [planetinfo.record]: sun_color = 0.661224, 0.4044, 0.248612, 1 14:23:47.894 [planetinfo.record]: corona_shimmer = 0.329151 14:23:47.895 [planetinfo.record]: station_vector = -0.006 -0.846 0.533 14:23:47.895 [planetinfo.record]: station = coriolis 14:23:52.319 [PLANETINFO OVER]: Done 14:23:52.319 [PLANETINFO LOGGING]: 6 166 14:23:53.322 [planetinfo.record]: seed = 234 120 148 247 14:23:53.322 [planetinfo.record]: coordinates = 120 79 14:23:53.322 [planetinfo.record]: government = 5; 14:23:53.322 [planetinfo.record]: economy = 7; 14:23:53.322 [planetinfo.record]: techlevel = 3; 14:23:53.322 [planetinfo.record]: population = 25; 14:23:53.322 [planetinfo.record]: productivity = 5400; 14:23:53.322 [planetinfo.record]: name = "Tierus"; 14:23:53.322 [planetinfo.record]: inhabitant = "Lizard"; 14:23:53.322 [planetinfo.record]: inhabitants = "Lizards"; 14:23:53.322 [planetinfo.record]: description = "The world Tierus is a boring world."; 14:23:53.330 [planetinfo.record]: air_color = 0.516374, 0.504849, 0.900141, 1; 14:23:53.331 [planetinfo.record]: cloud_alpha = 1.000000; 14:23:53.331 [planetinfo.record]: cloud_color = 0.249081, 0.197754, 0.703125, 1; 14:23:53.331 [planetinfo.record]: cloud_fraction = 0.440000; 14:23:53.331 [planetinfo.record]: land_color = 0.66, 0.00773438, 0.573371, 1; 14:23:53.331 [planetinfo.record]: land_fraction = 0.400000; 14:23:53.331 [planetinfo.record]: polar_cloud_color = 0.486909, 0.449661, 0.816406, 1; 14:23:53.331 [planetinfo.record]: polar_land_color = 0.934, 0.703236, 0.903352, 1; 14:23:53.331 [planetinfo.record]: polar_sea_color = 0.946289, 0.908366, 0.896757, 1; 14:23:53.331 [planetinfo.record]: sea_color = 0.537109, 0.451009, 0.424652, 1; 14:23:53.331 [planetinfo.record]: rotation_speed = 0.001615 14:23:53.331 [planetinfo.record]: planet zpos = 567360.000000 14:23:53.331 [planetinfo.record]: sun_radius = 95441.594238 14:23:53.331 [planetinfo.record]: sun_vector = 0.664 0.682 0.308 14:23:53.332 [planetinfo.record]: sun_distance = 1134720 14:23:53.332 [planetinfo.record]: corona_flare = 0.083434 14:23:53.332 [planetinfo.record]: corona_hues = 0.708496 14:23:53.332 [planetinfo.record]: sun_color = 0.718265, 0.477484, 0.261198, 1 14:23:53.332 [planetinfo.record]: corona_shimmer = 0.260427 14:23:53.332 [planetinfo.record]: station_vector = 0.773 -0.348 0.530 14:23:53.332 [planetinfo.record]: station = coriolis 14:23:57.839 [PLANETINFO OVER]: Done 14:23:57.840 [PLANETINFO LOGGING]: 6 167 14:23:58.843 [planetinfo.record]: seed = 178 232 208 217 14:23:58.843 [planetinfo.record]: coordinates = 232 48 14:23:58.843 [planetinfo.record]: government = 6; 14:23:58.843 [planetinfo.record]: economy = 0; 14:23:58.843 [planetinfo.record]: techlevel = 10; 14:23:58.843 [planetinfo.record]: population = 47; 14:23:58.843 [planetinfo.record]: productivity = 37600; 14:23:58.843 [planetinfo.record]: name = "Orbelaxe"; 14:23:58.843 [planetinfo.record]: inhabitant = "Slimy Frog"; 14:23:58.844 [planetinfo.record]: inhabitants = "Slimy Frogs"; 14:23:58.844 [planetinfo.record]: description = "This planet is most fabled for its inhabitants’ ingrained shyness but cursed by dreadful solar activity."; 14:23:58.850 [planetinfo.record]: air_color = 0.762005, 0.544092, 0.933311, 1; 14:23:58.850 [planetinfo.record]: cloud_alpha = 1.000000; 14:23:58.851 [planetinfo.record]: cloud_color = 0.802734, 0.279076, 0.438628, 1; 14:23:58.851 [planetinfo.record]: cloud_fraction = 0.370000; 14:23:58.851 [planetinfo.record]: land_color = 0.66, 0.592969, 0.641671, 1; 14:23:58.851 [planetinfo.record]: land_fraction = 0.260000; 14:23:58.851 [planetinfo.record]: polar_cloud_color = 0.86123, 0.510094, 0.617081, 1; 14:23:58.851 [planetinfo.record]: polar_land_color = 0.934, 0.910285, 0.927516, 1; 14:23:58.851 [planetinfo.record]: polar_sea_color = 0.941992, 0.916772, 0.889925, 1; 14:23:58.851 [planetinfo.record]: sea_color = 0.580078, 0.517956, 0.451826, 1; 14:23:58.851 [planetinfo.record]: rotation_speed = 0.001463 14:23:58.851 [planetinfo.record]: planet zpos = 802800.000000 14:23:58.851 [planetinfo.record]: sun_radius = 143877.465820 14:23:58.851 [planetinfo.record]: sun_vector = -0.018 0.182 -0.983 14:23:58.851 [planetinfo.record]: sun_distance = 1284480 14:23:58.851 [planetinfo.record]: corona_flare = 0.081700 14:23:58.851 [planetinfo.record]: corona_hues = 0.557724 14:23:58.851 [planetinfo.record]: sun_color = 0.292622, 0.474227, 0.839304, 1 14:23:58.851 [planetinfo.record]: corona_shimmer = 0.860730 14:23:58.851 [planetinfo.record]: station_vector = 0.568 -0.277 0.775 14:23:58.851 [planetinfo.record]: station = coriolis 14:24:04.439 [PLANETINFO OVER]: Done 14:24:04.439 [PLANETINFO LOGGING]: 6 168 14:24:05.443 [planetinfo.record]: seed = 242 130 20 44 14:24:05.443 [planetinfo.record]: coordinates = 130 181 14:24:05.443 [planetinfo.record]: government = 6; 14:24:05.443 [planetinfo.record]: economy = 5; 14:24:05.443 [planetinfo.record]: techlevel = 7; 14:24:05.443 [planetinfo.record]: population = 40; 14:24:05.443 [planetinfo.record]: productivity = 16000; 14:24:05.443 [planetinfo.record]: name = "Inzabeza"; 14:24:05.443 [planetinfo.record]: inhabitant = "Human Colonial"; 14:24:05.443 [planetinfo.record]: inhabitants = "Human Colonials"; 14:24:05.443 [planetinfo.record]: description = "This planet is fabled for its fabulous vicious Bi gargle blasters."; 14:24:05.460 [planetinfo.record]: air_color = 0.542359, 0.526385, 0.883231, 1; 14:24:05.460 [planetinfo.record]: cloud_alpha = 1.000000; 14:24:05.460 [planetinfo.record]: cloud_color = 0.311659, 0.252274, 0.652344, 1; 14:24:05.460 [planetinfo.record]: cloud_fraction = 0.290000; 14:24:05.460 [planetinfo.record]: land_color = 0.22776, 0.537488, 0.544922, 1; 14:24:05.460 [planetinfo.record]: land_fraction = 0.540000; 14:24:05.460 [planetinfo.record]: polar_cloud_color = 0.534535, 0.489385, 0.793555, 1; 14:24:05.460 [planetinfo.record]: polar_land_color = 0.807929, 0.942283, 0.945508, 1; 14:24:05.460 [planetinfo.record]: polar_sea_color = 0.949414, 0.891096, 0.940302, 1; 14:24:05.460 [planetinfo.record]: sea_color = 0.505859, 0.381568, 0.486439, 1; 14:24:05.460 [planetinfo.record]: rotation_speed = 0.001836 14:24:05.460 [planetinfo.record]: planet zpos = 782340.000000 14:24:05.460 [planetinfo.record]: sun_radius = 173327.876587 14:24:05.460 [planetinfo.record]: sun_vector = -0.312 -0.948 -0.066 14:24:05.460 [planetinfo.record]: sun_distance = 1143420 14:24:05.460 [planetinfo.record]: corona_flare = 0.090440 14:24:05.460 [planetinfo.record]: corona_hues = 0.882118 14:24:05.460 [planetinfo.record]: sun_color = 0.187785, 0.429806, 0.714789, 1 14:24:05.460 [planetinfo.record]: corona_shimmer = 0.546434 14:24:05.461 [planetinfo.record]: station_vector = 0.612 -0.761 0.214 14:24:05.461 [planetinfo.record]: station = coriolis 14:24:11.108 [PLANETINFO OVER]: Done 14:24:11.109 [PLANETINFO LOGGING]: 6 169 14:24:12.112 [planetinfo.record]: seed = 202 164 128 29 14:24:12.112 [planetinfo.record]: coordinates = 164 19 14:24:12.112 [planetinfo.record]: government = 1; 14:24:12.112 [planetinfo.record]: economy = 3; 14:24:12.112 [planetinfo.record]: techlevel = 5; 14:24:12.113 [planetinfo.record]: population = 25; 14:24:12.113 [planetinfo.record]: productivity = 7000; 14:24:12.113 [planetinfo.record]: name = "Isveedin"; 14:24:12.113 [planetinfo.record]: inhabitant = "Green Rodent"; 14:24:12.113 [planetinfo.record]: inhabitants = "Green Rodents"; 14:24:12.113 [planetinfo.record]: description = "This planet is beset by lethal disease."; 14:24:12.128 [planetinfo.record]: air_color = 0.449212, 0.739366, 0.856564, 1; 14:24:12.128 [planetinfo.record]: cloud_alpha = 1.000000; 14:24:12.128 [planetinfo.record]: cloud_color = 0.157597, 0.572266, 0.0983582, 1; 14:24:12.128 [planetinfo.record]: cloud_fraction = 0.310000; 14:24:12.128 [planetinfo.record]: land_color = 0.197021, 0.525391, 0.371468, 1; 14:24:12.128 [planetinfo.record]: land_fraction = 0.260000; 14:24:12.128 [planetinfo.record]: polar_cloud_color = 0.414453, 0.75752, 0.365444, 1; 14:24:12.128 [planetinfo.record]: polar_land_color = 0.79942, 0.947461, 0.878067, 1; 14:24:12.128 [planetinfo.record]: polar_sea_color = 0.923242, 0.919896, 0.83759, 1; 14:24:12.128 [planetinfo.record]: sea_color = 0.767578, 0.756451, 0.482735, 1; 14:24:12.128 [planetinfo.record]: rotation_speed = 0.002928 14:24:12.128 [planetinfo.record]: planet zpos = 820040.000000 14:24:12.128 [planetinfo.record]: sun_radius = 190871.478882 14:24:12.128 [planetinfo.record]: sun_vector = -0.042 0.844 -0.534 14:24:12.128 [planetinfo.record]: sun_distance = 1324680 14:24:12.128 [planetinfo.record]: corona_flare = 0.035803 14:24:12.129 [planetinfo.record]: corona_hues = 0.960213 14:24:12.129 [planetinfo.record]: sun_color = 0.271882, 0.299339, 0.795166, 1 14:24:12.129 [planetinfo.record]: corona_shimmer = 0.443506 14:24:12.129 [planetinfo.record]: station_vector = 0.998 0.062 0.014 14:24:12.129 [planetinfo.record]: station = coriolis 14:24:18.173 [PLANETINFO OVER]: Done 14:24:18.174 [PLANETINFO LOGGING]: 6 170 14:24:19.180 [planetinfo.record]: seed = 26 140 180 250 14:24:19.180 [planetinfo.record]: coordinates = 140 152 14:24:19.180 [planetinfo.record]: government = 3; 14:24:19.180 [planetinfo.record]: economy = 0; 14:24:19.180 [planetinfo.record]: techlevel = 9; 14:24:19.180 [planetinfo.record]: population = 40; 14:24:19.180 [planetinfo.record]: productivity = 22400; 14:24:19.180 [planetinfo.record]: name = "Quonbi"; 14:24:19.180 [planetinfo.record]: inhabitant = "Fat Feline"; 14:24:19.180 [planetinfo.record]: inhabitants = "Fat Felines"; 14:24:19.180 [planetinfo.record]: description = "The world Quonbi is reasonably famous for the Quonbiian spotted leopard."; 14:24:19.182 [planetinfo.record]: air_color = 0.602864, 0.862312, 0.979488, 1; 14:24:19.182 [planetinfo.record]: cloud_alpha = 1.000000; 14:24:19.182 [planetinfo.record]: cloud_color = 0.447777, 0.941406, 0.41922, 1; 14:24:19.182 [planetinfo.record]: cloud_fraction = 0.370000; 14:24:19.182 [planetinfo.record]: land_color = 0.623906, 0.651541, 0.66, 1; 14:24:19.183 [planetinfo.record]: land_fraction = 0.620000; 14:24:19.183 [planetinfo.record]: polar_cloud_color = 0.620939, 0.923633, 0.603428, 1; 14:24:19.183 [planetinfo.record]: polar_land_color = 0.92123, 0.931007, 0.934, 1; 14:24:19.183 [planetinfo.record]: polar_sea_color = 0.933408, 0.936328, 0.882928, 1; 14:24:19.183 [planetinfo.record]: sea_color = 0.628775, 0.636719, 0.491467, 1; 14:24:19.183 [planetinfo.record]: rotation_speed = 0.002245 14:24:19.183 [planetinfo.record]: planet zpos = 606760.000000 14:24:19.183 [planetinfo.record]: sun_radius = 127719.943237 14:24:19.183 [planetinfo.record]: sun_vector = -0.385 -0.256 -0.887 14:24:19.183 [planetinfo.record]: sun_distance = 1103200 14:24:19.183 [planetinfo.record]: corona_flare = 0.083302 14:24:19.183 [planetinfo.record]: corona_hues = 0.962761 14:24:19.183 [planetinfo.record]: sun_color = 0.486056, 0.571635, 0.788065, 1 14:24:19.183 [planetinfo.record]: corona_shimmer = 0.279648 14:24:19.183 [planetinfo.record]: station_vector = 0.124 -0.946 0.300 14:24:19.183 [planetinfo.record]: station = coriolis 14:24:23.403 [PLANETINFO OVER]: Done 14:24:23.404 [PLANETINFO LOGGING]: 6 171 14:24:24.409 [planetinfo.record]: seed = 2 192 80 133 14:24:24.409 [planetinfo.record]: coordinates = 192 166 14:24:24.409 [planetinfo.record]: government = 0; 14:24:24.409 [planetinfo.record]: economy = 6; 14:24:24.409 [planetinfo.record]: techlevel = 1; 14:24:24.409 [planetinfo.record]: population = 11; 14:24:24.409 [planetinfo.record]: productivity = 1408; 14:24:24.409 [planetinfo.record]: name = "Cemaer"; 14:24:24.409 [planetinfo.record]: inhabitant = "Human Colonial"; 14:24:24.409 [planetinfo.record]: inhabitants = "Human Colonials"; 14:24:24.409 [planetinfo.record]: description = "The planet Cemaer is mildly notable for Cemaerian Arusno water."; 14:24:24.432 [planetinfo.record]: air_color = 0.606533, 0.924855, 0.892219, 1; 14:24:24.432 [planetinfo.record]: cloud_alpha = 1.000000; 14:24:24.432 [planetinfo.record]: cloud_color = 0.777344, 0.677282, 0.440292, 1; 14:24:24.432 [planetinfo.record]: cloud_fraction = 0.250000; 14:24:24.432 [planetinfo.record]: land_color = 0.153191, 0.568359, 0.334827, 1; 14:24:24.433 [planetinfo.record]: land_fraction = 0.450000; 14:24:24.433 [planetinfo.record]: polar_cloud_color = 0.849805, 0.781436, 0.619511, 1; 14:24:24.433 [planetinfo.record]: polar_land_color = 0.770926, 0.943164, 0.84628, 1; 14:24:24.433 [planetinfo.record]: polar_sea_color = 0.859053, 0.916992, 0.91156, 1; 14:24:24.433 [planetinfo.record]: sea_color = 0.620289, 0.830078, 0.81041, 1; 14:24:24.433 [planetinfo.record]: rotation_speed = 0.004543 14:24:24.433 [planetinfo.record]: planet zpos = 428800.000000 14:24:24.433 [planetinfo.record]: sun_radius = 127040.898438 14:24:24.433 [planetinfo.record]: sun_vector = -0.167 0.976 -0.141 14:24:24.433 [planetinfo.record]: sun_distance = 857600 14:24:24.433 [planetinfo.record]: corona_flare = 0.089761 14:24:24.433 [planetinfo.record]: corona_hues = 0.671677 14:24:24.433 [planetinfo.record]: sun_color = 0.708044, 0.679274, 0.407515, 1 14:24:24.433 [planetinfo.record]: corona_shimmer = 0.396055 14:24:24.434 [planetinfo.record]: station_vector = -0.215 -0.971 0.106 14:24:24.434 [planetinfo.record]: station = coriolis 14:24:29.360 [PLANETINFO OVER]: Done 14:24:29.361 [PLANETINFO LOGGING]: 6 172 14:24:30.366 [planetinfo.record]: seed = 98 161 116 189 14:24:30.366 [planetinfo.record]: coordinates = 161 48 14:24:30.366 [planetinfo.record]: government = 4; 14:24:30.366 [planetinfo.record]: economy = 0; 14:24:30.366 [planetinfo.record]: techlevel = 10; 14:24:30.366 [planetinfo.record]: population = 45; 14:24:30.366 [planetinfo.record]: productivity = 28800; 14:24:30.366 [planetinfo.record]: name = "Isare"; 14:24:30.366 [planetinfo.record]: inhabitant = "Human Colonial"; 14:24:30.366 [planetinfo.record]: inhabitants = "Human Colonials"; 14:24:30.366 [planetinfo.record]: description = "The planet Isare is mildly notable for killer Re gargle blasters."; 14:24:30.382 [planetinfo.record]: air_color = 0.563972, 0.930059, 0.809667, 1; 14:24:30.382 [planetinfo.record]: cloud_alpha = 1.000000; 14:24:30.382 [planetinfo.record]: cloud_color = 0.792969, 0.342253, 0.331436, 1; 14:24:30.382 [planetinfo.record]: cloud_fraction = 0.550000; 14:24:30.383 [planetinfo.record]: land_color = 0.307541, 0.327773, 0.542969, 1; 14:24:30.383 [planetinfo.record]: land_fraction = 0.710000; 14:24:30.383 [planetinfo.record]: polar_cloud_color = 0.856836, 0.55245, 0.545145, 1; 14:24:30.383 [planetinfo.record]: polar_land_color = 0.84319, 0.852, 0.945703, 1; 14:24:30.383 [planetinfo.record]: polar_sea_color = 0.862434, 0.918555, 0.839616, 1; 14:24:30.383 [planetinfo.record]: sea_color = 0.615413, 0.814453, 0.534485, 1; 14:24:30.383 [planetinfo.record]: rotation_speed = 0.002533 14:24:30.383 [planetinfo.record]: planet zpos = 756600.000000 14:24:30.383 [planetinfo.record]: sun_radius = 177546.514130 14:24:30.383 [planetinfo.record]: sun_vector = 0.207 -0.808 0.551 14:24:30.383 [planetinfo.record]: sun_distance = 1197950 14:24:30.383 [planetinfo.record]: corona_flare = 0.039244 14:24:30.383 [planetinfo.record]: corona_hues = 0.733864 14:24:30.383 [planetinfo.record]: sun_color = 0.750229, 0.6551, 0.619907, 1 14:24:30.383 [planetinfo.record]: corona_shimmer = 0.511090 14:24:30.383 [planetinfo.record]: station_vector = 0.162 0.927 0.340 14:24:30.383 [planetinfo.record]: station = coriolis 14:24:34.650 [PLANETINFO OVER]: Done 14:24:34.651 [PLANETINFO LOGGING]: 6 173 14:24:35.656 [planetinfo.record]: seed = 90 59 64 185 14:24:35.656 [planetinfo.record]: coordinates = 59 238 14:24:35.656 [planetinfo.record]: government = 3; 14:24:35.656 [planetinfo.record]: economy = 6; 14:24:35.656 [planetinfo.record]: techlevel = 6; 14:24:35.656 [planetinfo.record]: population = 34; 14:24:35.656 [planetinfo.record]: productivity = 7616; 14:24:35.656 [planetinfo.record]: name = "Orxetibe"; 14:24:35.656 [planetinfo.record]: inhabitant = "Human Colonial"; 14:24:35.656 [planetinfo.record]: inhabitants = "Human Colonials"; 14:24:35.656 [planetinfo.record]: description = "This world is very notable for its inhabitants’ weird shyness."; 14:24:35.659 [planetinfo.record]: air_color = 0.626058, 0.556086, 0.879328, 1; 14:24:35.659 [planetinfo.record]: cloud_alpha = 1.000000; 14:24:35.659 [planetinfo.record]: cloud_color = 0.532179, 0.31781, 0.640625, 1; 14:24:35.659 [planetinfo.record]: cloud_fraction = 0.240000; 14:24:35.659 [planetinfo.record]: land_color = 0.562031, 0.66, 0.577339, 1; 14:24:35.659 [planetinfo.record]: land_fraction = 0.280000; 14:24:35.659 [planetinfo.record]: polar_cloud_color = 0.704881, 0.540019, 0.788281, 1; 14:24:35.659 [planetinfo.record]: polar_land_color = 0.89934, 0.934, 0.904756, 1; 14:24:35.660 [planetinfo.record]: polar_sea_color = 0.930273, 0.844551, 0.840335, 1; 14:24:35.660 [planetinfo.record]: sea_color = 0.697266, 0.44026, 0.42762, 1; 14:24:35.660 [planetinfo.record]: rotation_speed = 0.001109 14:24:35.660 [planetinfo.record]: planet zpos = 569690.000000 14:24:35.660 [planetinfo.record]: sun_radius = 99175.131531 14:24:35.660 [planetinfo.record]: sun_vector = -0.749 -0.347 -0.564 14:24:35.660 [planetinfo.record]: sun_distance = 1035800 14:24:35.660 [planetinfo.record]: corona_flare = 0.049448 14:24:35.660 [planetinfo.record]: corona_hues = 0.538269 14:24:35.660 [planetinfo.record]: sun_color = 0.667673, 0.666576, 0.614889, 1 14:24:35.660 [planetinfo.record]: corona_shimmer = 0.358007 14:24:35.660 [planetinfo.record]: station_vector = -0.402 -0.834 0.377 14:24:35.661 [planetinfo.record]: station = coriolis 14:24:40.233 [PLANETINFO OVER]: Done 14:24:40.235 [PLANETINFO LOGGING]: 6 174 14:24:41.238 [planetinfo.record]: seed = 202 115 84 46 14:24:41.238 [planetinfo.record]: coordinates = 115 215 14:24:41.238 [planetinfo.record]: government = 1; 14:24:41.238 [planetinfo.record]: economy = 7; 14:24:41.238 [planetinfo.record]: techlevel = 4; 14:24:41.238 [planetinfo.record]: population = 25; 14:24:41.238 [planetinfo.record]: productivity = 3000; 14:24:41.238 [planetinfo.record]: name = "Reoran"; 14:24:41.238 [planetinfo.record]: inhabitant = "Human Colonial"; 14:24:41.239 [planetinfo.record]: inhabitants = "Human Colonials"; 14:24:41.239 [planetinfo.record]: description = "This planet is reasonably noted for its inhabitants’ exceptional love for food blenders."; 14:24:41.288 [planetinfo.record]: air_color = 0.589213, 0.909404, 0.971684, 1; 14:24:41.288 [planetinfo.record]: cloud_alpha = 1.000000; 14:24:41.288 [planetinfo.record]: cloud_color = 0.650826, 0.917969, 0.383682, 1; 14:24:41.288 [planetinfo.record]: cloud_fraction = 0.200000; 14:24:41.288 [planetinfo.record]: land_color = 0.580078, 0.471313, 0.489158, 1; 14:24:41.288 [planetinfo.record]: land_fraction = 0.410000; 14:24:41.288 [planetinfo.record]: polar_cloud_color = 0.74701, 0.913086, 0.580933, 1; 14:24:41.288 [planetinfo.record]: polar_land_color = 0.941992, 0.897836, 0.905081, 1; 14:24:41.288 [planetinfo.record]: polar_sea_color = 0.883471, 0.949805, 0.765224, 1; 14:24:41.288 [planetinfo.record]: sea_color = 0.361729, 0.501953, 0.111763, 1; 14:24:41.288 [planetinfo.record]: rotation_speed = 0.002772 14:24:41.288 [planetinfo.record]: planet zpos = 716650.000000 14:24:41.289 [planetinfo.record]: sun_radius = 149468.431091 14:24:41.289 [planetinfo.record]: sun_vector = 0.086 -0.961 -0.264 14:24:41.289 [planetinfo.record]: sun_distance = 1303000 14:24:41.289 [planetinfo.record]: corona_flare = 0.083365 14:24:41.289 [planetinfo.record]: corona_hues = 0.884743 14:24:41.289 [planetinfo.record]: sun_color = 0.673135, 0.61456, 0.369767, 1 14:24:41.289 [planetinfo.record]: corona_shimmer = 0.343225 14:24:41.289 [planetinfo.record]: station_vector = -0.685 -0.477 0.550 14:24:41.289 [planetinfo.record]: station = coriolis 14:24:45.864 [PLANETINFO OVER]: Done 14:24:45.864 [PLANETINFO LOGGING]: 6 175 14:24:46.869 [planetinfo.record]: seed = 210 195 80 89 14:24:46.869 [planetinfo.record]: coordinates = 195 27 14:24:46.869 [planetinfo.record]: government = 2; 14:24:46.869 [planetinfo.record]: economy = 3; 14:24:46.869 [planetinfo.record]: techlevel = 8; 14:24:46.869 [planetinfo.record]: population = 38; 14:24:46.869 [planetinfo.record]: productivity = 12768; 14:24:46.869 [planetinfo.record]: name = "Oredveus"; 14:24:46.869 [planetinfo.record]: inhabitant = "Human Colonial"; 14:24:46.869 [planetinfo.record]: inhabitants = "Human Colonials"; 14:24:46.869 [planetinfo.record]: description = "Oredveus is a revolting dump."; 14:24:46.904 [planetinfo.record]: air_color = 0.741689, 0.554877, 0.919002, 1; 14:24:46.904 [planetinfo.record]: cloud_alpha = 1.000000; 14:24:46.904 [planetinfo.record]: cloud_color = 0.759766, 0.311623, 0.511186, 1; 14:24:46.904 [planetinfo.record]: cloud_fraction = 0.240000; 14:24:46.904 [planetinfo.record]: land_color = 0.618972, 0.66, 0.605859, 1; 14:24:46.904 [planetinfo.record]: land_fraction = 0.520000; 14:24:46.904 [planetinfo.record]: polar_cloud_color = 0.841895, 0.531528, 0.669738, 1; 14:24:46.904 [planetinfo.record]: polar_land_color = 0.919485, 0.934, 0.914846, 1; 14:24:46.904 [planetinfo.record]: polar_sea_color = 0.738656, 0.934961, 0.888952, 1; 14:24:46.904 [planetinfo.record]: sea_color = 0.104164, 0.650391, 0.522369, 1; 14:24:46.904 [planetinfo.record]: rotation_speed = 0.002648 14:24:46.904 [planetinfo.record]: planet zpos = 584650.000000 14:24:46.904 [planetinfo.record]: sun_radius = 142517.845154 14:24:46.904 [planetinfo.record]: sun_vector = -0.722 -0.359 -0.591 14:24:46.904 [planetinfo.record]: sun_distance = 1275600 14:24:46.904 [planetinfo.record]: corona_flare = 0.026379 14:24:46.904 [planetinfo.record]: corona_hues = 0.958054 14:24:46.904 [planetinfo.record]: sun_color = 0.256289, 0.393031, 0.701367, 1 14:24:46.904 [planetinfo.record]: corona_shimmer = 0.932446 14:24:46.904 [planetinfo.record]: station_vector = 0.550 -0.440 0.710 14:24:46.904 [planetinfo.record]: station = coriolis 14:24:51.947 [PLANETINFO OVER]: Done 14:24:51.947 [PLANETINFO LOGGING]: 6 176 14:24:52.951 [planetinfo.record]: seed = 82 232 84 119 14:24:52.951 [planetinfo.record]: coordinates = 232 86 14:24:52.951 [planetinfo.record]: government = 2; 14:24:52.951 [planetinfo.record]: economy = 6; 14:24:52.951 [planetinfo.record]: techlevel = 2; 14:24:52.951 [planetinfo.record]: population = 17; 14:24:52.951 [planetinfo.record]: productivity = 3264; 14:24:52.951 [planetinfo.record]: name = "Tilala"; 14:24:52.951 [planetinfo.record]: inhabitant = "Human Colonial"; 14:24:52.951 [planetinfo.record]: inhabitants = "Human Colonials"; 14:24:52.951 [planetinfo.record]: description = "The planet Tilala is reasonably noted for its inhabitants’ eccentric shyness and Tilalaian Etonle brandy."; 14:24:52.988 [planetinfo.record]: air_color = 0.682749, 0.492928, 0.954123, 1; 14:24:52.988 [planetinfo.record]: cloud_alpha = 1.000000; 14:24:52.988 [planetinfo.record]: cloud_color = 0.865234, 0.128433, 0.681034, 1; 14:24:52.988 [planetinfo.record]: cloud_fraction = 0.340000; 14:24:52.988 [planetinfo.record]: land_color = 0.285706, 0.297586, 0.589844, 1; 14:24:52.988 [planetinfo.record]: land_fraction = 0.710000; 14:24:52.988 [planetinfo.record]: polar_cloud_color = 0.889355, 0.416017, 0.771021, 1; 14:24:52.988 [planetinfo.record]: polar_land_color = 0.819713, 0.824451, 0.941016, 1; 14:24:52.988 [planetinfo.record]: polar_sea_color = 0.875399, 0.924609, 0.913076, 1; 14:24:52.988 [planetinfo.record]: sea_color = 0.593407, 0.753906, 0.716289, 1; 14:24:52.988 [planetinfo.record]: rotation_speed = 0.003136 14:24:52.988 [planetinfo.record]: planet zpos = 629200.000000 14:24:52.988 [planetinfo.record]: sun_radius = 115452.197266 14:24:52.988 [planetinfo.record]: sun_vector = -0.031 -0.999 0.021 14:24:52.988 [planetinfo.record]: sun_distance = 1016400 14:24:52.988 [planetinfo.record]: corona_flare = 0.046283 14:24:52.988 [planetinfo.record]: corona_hues = 0.950310 14:24:52.988 [planetinfo.record]: sun_color = 0.702298, 0.538582, 0.352649, 1 14:24:52.988 [planetinfo.record]: corona_shimmer = 0.338261 14:24:52.988 [planetinfo.record]: station_vector = 0.881 0.077 0.467 14:24:52.988 [planetinfo.record]: station = coriolis 14:24:57.935 [PLANETINFO OVER]: Done 14:24:57.938 [PLANETINFO LOGGING]: 6 177 14:24:58.940 [planetinfo.record]: seed = 106 66 128 13 14:24:58.941 [planetinfo.record]: coordinates = 66 21 14:24:58.941 [planetinfo.record]: government = 5; 14:24:58.941 [planetinfo.record]: economy = 5; 14:24:58.941 [planetinfo.record]: techlevel = 7; 14:24:58.941 [planetinfo.record]: population = 39; 14:24:58.941 [planetinfo.record]: productivity = 14040; 14:24:58.941 [planetinfo.record]: name = "Dicelaso"; 14:24:58.941 [planetinfo.record]: inhabitant = "Green Rodent"; 14:24:58.941 [planetinfo.record]: inhabitants = "Green Rodents"; 14:24:58.941 [planetinfo.record]: description = "The planet Dicelaso is scourged by deadly disease."; 14:24:58.946 [planetinfo.record]: air_color = 0.549514, 0.966638, 0.986643, 1; 14:24:58.946 [planetinfo.record]: cloud_alpha = 1.000000; 14:24:58.946 [planetinfo.record]: cloud_color = 0.858485, 0.962891, 0.259529, 1; 14:24:58.946 [planetinfo.record]: cloud_fraction = 0.200000; 14:24:58.946 [planetinfo.record]: land_color = 0.527831, 0.162422, 0.66, 1; 14:24:58.946 [planetinfo.record]: land_fraction = 0.560000; 14:24:58.946 [planetinfo.record]: polar_cloud_color = 0.870053, 0.933301, 0.507209, 1; 14:24:58.947 [planetinfo.record]: polar_land_color = 0.88724, 0.757963, 0.934, 1; 14:24:58.947 [planetinfo.record]: polar_sea_color = 0.932813, 0.866064, 0.857204, 1; 14:24:58.947 [planetinfo.record]: sea_color = 0.671875, 0.479568, 0.454041, 1; 14:24:58.947 [planetinfo.record]: rotation_speed = 0.004156 14:24:58.947 [planetinfo.record]: planet zpos = 683100.000000 14:24:58.947 [planetinfo.record]: sun_radius = 153575.642395 14:24:58.947 [planetinfo.record]: sun_vector = -0.728 -0.221 -0.649 14:24:58.947 [planetinfo.record]: sun_distance = 1242000 14:24:58.947 [planetinfo.record]: corona_flare = 0.025282 14:24:58.947 [planetinfo.record]: corona_hues = 0.673996 14:24:58.948 [planetinfo.record]: sun_color = 0.820926, 0.663729, 0.642567, 1 14:24:58.948 [planetinfo.record]: corona_shimmer = 0.420383 14:24:58.948 [planetinfo.record]: station_vector = 0.196 -0.304 0.932 14:24:58.948 [planetinfo.record]: station = coriolis 14:25:03.811 [PLANETINFO OVER]: Done 14:25:03.812 [PLANETINFO LOGGING]: 6 178 14:25:04.816 [planetinfo.record]: seed = 250 39 116 66 14:25:04.816 [planetinfo.record]: coordinates = 39 181 14:25:04.816 [planetinfo.record]: government = 7; 14:25:04.816 [planetinfo.record]: economy = 5; 14:25:04.816 [planetinfo.record]: techlevel = 9; 14:25:04.816 [planetinfo.record]: population = 49; 14:25:04.816 [planetinfo.record]: productivity = 21560; 14:25:04.816 [planetinfo.record]: name = "Xeonar"; 14:25:04.816 [planetinfo.record]: inhabitant = "Human Colonial"; 14:25:04.816 [planetinfo.record]: inhabitants = "Human Colonials"; 14:25:04.816 [planetinfo.record]: description = "The world Xeonar is scourged by evil disease."; 14:25:04.818 [planetinfo.record]: air_color = 0.703945, 0.838156, 0.88258, 1; 14:25:04.818 [planetinfo.record]: cloud_alpha = 1.000000; 14:25:04.818 [planetinfo.record]: cloud_color = 0.64654, 0.650391, 0.645309, 1; 14:25:04.818 [planetinfo.record]: cloud_fraction = 0.380000; 14:25:04.818 [planetinfo.record]: land_color = 0.484688, 0.58604, 0.66, 1; 14:25:04.818 [planetinfo.record]: land_fraction = 0.340000; 14:25:04.818 [planetinfo.record]: polar_cloud_color = 0.789743, 0.792676, 0.788805, 1; 14:25:04.819 [planetinfo.record]: polar_land_color = 0.871977, 0.907834, 0.934, 1; 14:25:04.819 [planetinfo.record]: polar_sea_color = 0.947852, 0.861073, 0.725699, 1; 14:25:04.819 [planetinfo.record]: sea_color = 0.521484, 0.330511, 0.0325928, 1; 14:25:04.819 [planetinfo.record]: rotation_speed = 0.003395 14:25:04.819 [planetinfo.record]: planet zpos = 336700.000000 14:25:04.819 [planetinfo.record]: sun_radius = 88870.797729 14:25:04.819 [planetinfo.record]: sun_vector = -0.766 0.612 -0.195 14:25:04.819 [planetinfo.record]: sun_distance = 639730 14:25:04.819 [planetinfo.record]: corona_flare = 0.016507 14:25:04.819 [planetinfo.record]: corona_hues = 0.780022 14:25:04.819 [planetinfo.record]: sun_color = 0.719232, 0.657111, 0.453594, 1 14:25:04.819 [planetinfo.record]: corona_shimmer = 0.313123 14:25:04.819 [planetinfo.record]: station_vector = -0.959 -0.272 0.081 14:25:04.819 [planetinfo.record]: station = coriolis 14:25:09.577 [PLANETINFO OVER]: Done 14:25:09.578 [PLANETINFO LOGGING]: 6 179 14:25:10.580 [planetinfo.record]: seed = 34 236 208 149 14:25:10.580 [planetinfo.record]: coordinates = 236 138 14:25:10.580 [planetinfo.record]: government = 4; 14:25:10.580 [planetinfo.record]: economy = 2; 14:25:10.580 [planetinfo.record]: techlevel = 7; 14:25:10.580 [planetinfo.record]: population = 35; 14:25:10.580 [planetinfo.record]: productivity = 17920; 14:25:10.580 [planetinfo.record]: name = "Laindi"; 14:25:10.580 [planetinfo.record]: inhabitant = "Black Insect"; 14:25:10.580 [planetinfo.record]: inhabitants = "Black Insects"; 14:25:10.580 [planetinfo.record]: description = "The world Laindi is scourged by deadly tree snakes."; 14:25:10.583 [planetinfo.record]: air_color = 0.655331, 0.862418, 0.816942, 1; 14:25:10.584 [planetinfo.record]: cloud_alpha = 1.000000; 14:25:10.584 [planetinfo.record]: cloud_color = 0.589844, 0.543528, 0.518417, 1; 14:25:10.584 [planetinfo.record]: cloud_fraction = 0.580000; 14:25:10.584 [planetinfo.record]: land_color = 0.654844, 0.66, 0.659879, 1; 14:25:10.584 [planetinfo.record]: land_fraction = 0.580000; 14:25:10.584 [planetinfo.record]: polar_cloud_color = 0.76543, 0.727865, 0.707499, 1; 14:25:10.584 [planetinfo.record]: polar_land_color = 0.932176, 0.934, 0.933957, 1; 14:25:10.584 [planetinfo.record]: polar_sea_color = 0.793587, 0.944922, 0.905906, 1; 14:25:10.584 [planetinfo.record]: sea_color = 0.197937, 0.550781, 0.459814, 1; 14:25:10.584 [planetinfo.record]: rotation_speed = 0.000807 14:25:10.584 [planetinfo.record]: planet zpos = 606480.000000 14:25:10.584 [planetinfo.record]: sun_radius = 121210.861816 14:25:10.584 [planetinfo.record]: sun_vector = -0.582 -0.612 -0.535 14:25:10.584 [planetinfo.record]: sun_distance = 866400 14:25:10.584 [planetinfo.record]: corona_flare = 0.027754 14:25:10.584 [planetinfo.record]: corona_hues = 0.785896 14:25:10.584 [planetinfo.record]: sun_color = 0.717593, 0.71183, 0.626315, 1 14:25:10.585 [planetinfo.record]: corona_shimmer = 0.412648 14:25:10.585 [planetinfo.record]: station_vector = 0.903 -0.406 0.142 14:25:10.585 [planetinfo.record]: station = coriolis 14:25:15.614 [PLANETINFO OVER]: Done 14:25:15.614 [PLANETINFO LOGGING]: 6 180 14:25:16.618 [planetinfo.record]: seed = 194 47 180 201 14:25:16.618 [planetinfo.record]: coordinates = 47 141 14:25:16.618 [planetinfo.record]: government = 0; 14:25:16.618 [planetinfo.record]: economy = 7; 14:25:16.618 [planetinfo.record]: techlevel = 3; 14:25:16.618 [planetinfo.record]: population = 20; 14:25:16.619 [planetinfo.record]: productivity = 1920; 14:25:16.619 [planetinfo.record]: name = "Essoat"; 14:25:16.619 [planetinfo.record]: inhabitant = "Small Horned Lobster"; 14:25:16.619 [planetinfo.record]: inhabitants = "Small Horned Lobsters"; 14:25:16.619 [planetinfo.record]: description = "This world is very notable for its inhabitants’ ingrained shyness."; 14:25:16.621 [planetinfo.record]: air_color = 0.843578, 0.75695, 0.994447, 1; 14:25:16.621 [planetinfo.record]: cloud_alpha = 1.000000; 14:25:16.621 [planetinfo.record]: cloud_color = 0.986328, 0.870743, 0.973686, 1; 14:25:16.621 [planetinfo.record]: cloud_fraction = 0.590000; 14:25:16.621 [planetinfo.record]: land_color = 0.0993361, 0.617188, 0.0554504, 1; 14:25:16.621 [planetinfo.record]: land_fraction = 0.330000; 14:25:16.621 [planetinfo.record]: polar_cloud_color = 0.943848, 0.874718, 0.936287, 1; 14:25:16.621 [planetinfo.record]: polar_land_color = 0.741465, 0.938281, 0.724786, 1; 14:25:16.621 [planetinfo.record]: polar_sea_color = 0.889413, 0.924805, 0.873326, 1; 14:25:16.621 [planetinfo.record]: sea_color = 0.636847, 0.751953, 0.584526, 1; 14:25:16.621 [planetinfo.record]: rotation_speed = 0.003770 14:25:16.621 [planetinfo.record]: planet zpos = 465030.000000 14:25:16.621 [planetinfo.record]: sun_radius = 129390.239105 14:25:16.621 [planetinfo.record]: sun_vector = -0.622 -0.452 -0.639 14:25:16.621 [planetinfo.record]: sun_distance = 930060 14:25:16.622 [planetinfo.record]: corona_flare = 0.057851 14:25:16.622 [planetinfo.record]: corona_hues = 0.902245 14:25:16.622 [planetinfo.record]: sun_color = 0.712549, 0.496085, 0.4944, 1 14:25:16.622 [planetinfo.record]: corona_shimmer = 0.389437 14:25:16.622 [planetinfo.record]: station_vector = 0.372 0.310 0.875 14:25:16.622 [planetinfo.record]: station = coriolis 14:25:22.148 [PLANETINFO OVER]: Done 14:25:22.149 [PLANETINFO LOGGING]: 6 181 14:25:23.156 [planetinfo.record]: seed = 250 209 64 218 14:25:23.156 [planetinfo.record]: coordinates = 209 128 14:25:23.156 [planetinfo.record]: government = 7; 14:25:23.156 [planetinfo.record]: economy = 0; 14:25:23.156 [planetinfo.record]: techlevel = 12; 14:25:23.156 [planetinfo.record]: population = 56; 14:25:23.156 [planetinfo.record]: productivity = 49280; 14:25:23.156 [planetinfo.record]: name = "Qudior"; 14:25:23.156 [planetinfo.record]: inhabitant = "Human Colonial"; 14:25:23.156 [planetinfo.record]: inhabitants = "Human Colonials"; 14:25:23.157 [planetinfo.record]: description = "This planet is beset by deadly earthquakes."; 14:25:23.172 [planetinfo.record]: air_color = 0.697138, 0.827892, 0.911197, 1; 14:25:23.172 [planetinfo.record]: cloud_alpha = 1.000000; 14:25:23.172 [planetinfo.record]: cloud_color = 0.658669, 0.736328, 0.672623, 1; 14:25:23.172 [planetinfo.record]: cloud_fraction = 0.390000; 14:25:23.172 [planetinfo.record]: land_color = 0.57893, 0.66, 0.569766, 1; 14:25:23.172 [planetinfo.record]: land_fraction = 0.520000; 14:25:23.172 [planetinfo.record]: polar_cloud_color = 0.776547, 0.831348, 0.786394, 1; 14:25:23.172 [planetinfo.record]: polar_land_color = 0.905318, 0.934, 0.902076, 1; 14:25:23.172 [planetinfo.record]: polar_sea_color = 0.868843, 0.890336, 0.917969, 1; 14:25:23.172 [planetinfo.record]: sea_color = 0.644714, 0.721539, 0.820312, 1; 14:25:23.172 [planetinfo.record]: rotation_speed = 0.002397 14:25:23.172 [planetinfo.record]: planet zpos = 502650.000000 14:25:23.172 [planetinfo.record]: sun_radius = 177154.331970 14:25:23.172 [planetinfo.record]: sun_vector = 0.868 -0.050 0.494 14:25:23.172 [planetinfo.record]: sun_distance = 1117000 14:25:23.172 [planetinfo.record]: corona_flare = 0.007460 14:25:23.173 [planetinfo.record]: corona_hues = 0.943794 14:25:23.173 [planetinfo.record]: sun_color = 0.526054, 0.640175, 0.708493, 1 14:25:23.173 [planetinfo.record]: corona_shimmer = 0.395290 14:25:23.173 [planetinfo.record]: station_vector = -0.010 -0.993 0.114 14:25:23.173 [planetinfo.record]: station = dodecahedron 14:25:27.486 [PLANETINFO OVER]: Done 14:25:27.487 [PLANETINFO LOGGING]: 6 182 14:25:28.505 [planetinfo.record]: seed = 170 224 20 231 14:25:28.505 [planetinfo.record]: coordinates = 224 217 14:25:28.505 [planetinfo.record]: government = 5; 14:25:28.505 [planetinfo.record]: economy = 1; 14:25:28.505 [planetinfo.record]: techlevel = 9; 14:25:28.506 [planetinfo.record]: population = 43; 14:25:28.506 [planetinfo.record]: productivity = 27864; 14:25:28.506 [planetinfo.record]: name = "Soleuser"; 14:25:28.506 [planetinfo.record]: inhabitant = "Human Colonial"; 14:25:28.506 [planetinfo.record]: inhabitants = "Human Colonials"; 14:25:28.506 [planetinfo.record]: description = "This planet is most fabled for its inhabitants’ ingrained shyness but cursed by dreadful solar activity."; 14:25:28.518 [planetinfo.record]: air_color = 0.816057, 0.745137, 0.992496, 1; 14:25:28.519 [planetinfo.record]: cloud_alpha = 1.000000; 14:25:28.519 [planetinfo.record]: cloud_color = 0.962276, 0.83493, 0.980469, 1; 14:25:28.519 [planetinfo.record]: cloud_fraction = 0.330000; 14:25:28.519 [planetinfo.record]: land_color = 0.66, 0.54902, 0.170156, 1; 14:25:28.519 [planetinfo.record]: land_fraction = 0.310000; 14:25:28.519 [planetinfo.record]: polar_cloud_color = 0.930296, 0.853892, 0.941211, 1; 14:25:28.519 [planetinfo.record]: polar_land_color = 0.934, 0.894737, 0.760699, 1; 14:25:28.519 [planetinfo.record]: polar_sea_color = 0.944727, 0.897895, 0.891678, 1; 14:25:28.519 [planetinfo.record]: sea_color = 0.552734, 0.443134, 0.428585, 1; 14:25:28.519 [planetinfo.record]: rotation_speed = 0.004124 14:25:28.519 [planetinfo.record]: planet zpos = 579840.000000 14:25:28.519 [planetinfo.record]: sun_radius = 107061.801758 14:25:28.519 [planetinfo.record]: sun_vector = -0.729 -0.083 -0.679 14:25:28.519 [planetinfo.record]: sun_distance = 821440 14:25:28.519 [planetinfo.record]: corona_flare = 0.047981 14:25:28.519 [planetinfo.record]: corona_hues = 0.851303 14:25:28.520 [planetinfo.record]: sun_color = 0.553866, 0.557925, 0.674509, 1 14:25:28.520 [planetinfo.record]: corona_shimmer = 0.394420 14:25:28.520 [planetinfo.record]: station_vector = -0.740 0.662 0.119 14:25:28.520 [planetinfo.record]: station = coriolis 14:25:33.016 [PLANETINFO OVER]: Done 14:25:33.017 [PLANETINFO LOGGING]: 6 183 14:25:34.020 [planetinfo.record]: seed = 242 240 208 250 14:25:34.020 [planetinfo.record]: coordinates = 240 104 14:25:34.020 [planetinfo.record]: government = 6; 14:25:34.020 [planetinfo.record]: economy = 0; 14:25:34.020 [planetinfo.record]: techlevel = 10; 14:25:34.020 [planetinfo.record]: population = 47; 14:25:34.020 [planetinfo.record]: productivity = 37600; 14:25:34.020 [planetinfo.record]: name = "Quraa"; 14:25:34.020 [planetinfo.record]: inhabitant = "Slimy Lizard"; 14:25:34.020 [planetinfo.record]: inhabitants = "Slimy Lizards"; 14:25:34.020 [planetinfo.record]: description = "This world is a tedious little planet."; 14:25:34.046 [planetinfo.record]: air_color = 0.700004, 0.556829, 0.904693, 1; 14:25:34.046 [planetinfo.record]: cloud_alpha = 1.000000; 14:25:34.046 [planetinfo.record]: cloud_color = 0.716797, 0.319199, 0.617397, 1; 14:25:34.046 [planetinfo.record]: cloud_fraction = 0.120000; 14:25:34.046 [planetinfo.record]: land_color = 0.143127, 0.570433, 0.654297, 1; 14:25:34.046 [planetinfo.record]: land_fraction = 0.670000; 14:25:34.046 [planetinfo.record]: polar_cloud_color = 0.822559, 0.537394, 0.751267, 1; 14:25:34.046 [planetinfo.record]: polar_land_color = 0.752037, 0.904623, 0.93457, 1; 14:25:34.046 [planetinfo.record]: polar_sea_color = 0.877494, 0.925391, 0.915287, 1; 14:25:34.046 [planetinfo.record]: sea_color = 0.591629, 0.746094, 0.713511, 1; 14:25:34.046 [planetinfo.record]: rotation_speed = 0.003979 14:25:34.046 [planetinfo.record]: planet zpos = 617760.000000 14:25:34.046 [planetinfo.record]: sun_radius = 135343.234863 14:25:34.055 [planetinfo.record]: sun_vector = -0.136 0.971 -0.197 14:25:34.056 [planetinfo.record]: sun_distance = 1067040 14:25:34.056 [planetinfo.record]: corona_flare = 0.094781 14:25:34.056 [planetinfo.record]: corona_hues = 0.658989 14:25:34.056 [planetinfo.record]: sun_color = 0.731851, 0.759137, 0.848355, 1 14:25:34.056 [planetinfo.record]: corona_shimmer = 0.455522 14:25:34.056 [planetinfo.record]: station_vector = 0.672 -0.554 0.491 14:25:34.056 [planetinfo.record]: station = coriolis 14:25:38.689 [PLANETINFO OVER]: Done 14:25:38.690 [PLANETINFO LOGGING]: 6 184 14:25:39.695 [planetinfo.record]: seed = 178 143 148 36 14:25:39.695 [planetinfo.record]: coordinates = 143 64 14:25:39.695 [planetinfo.record]: government = 6; 14:25:39.695 [planetinfo.record]: economy = 0; 14:25:39.695 [planetinfo.record]: techlevel = 13; 14:25:39.695 [planetinfo.record]: population = 59; 14:25:39.695 [planetinfo.record]: productivity = 47200; 14:25:39.695 [planetinfo.record]: name = "Zarausxe"; 14:25:39.695 [planetinfo.record]: inhabitant = "Fierce Red Insect"; 14:25:39.695 [planetinfo.record]: inhabitants = "Fierce Red Insects"; 14:25:39.695 [planetinfo.record]: description = "This planet is notable for its great tropical forests and Zero-G hockey."; 14:25:39.699 [planetinfo.record]: air_color = 0.556178, 0.935262, 0.807637, 1; 14:25:39.699 [planetinfo.record]: cloud_alpha = 1.000000; 14:25:39.699 [planetinfo.record]: cloud_color = 0.808594, 0.30954, 0.30954, 1; 14:25:39.699 [planetinfo.record]: cloud_fraction = 0.410000; 14:25:39.699 [planetinfo.record]: land_color = 0.66, 0.603281, 0.64582, 1; 14:25:39.700 [planetinfo.record]: land_fraction = 0.590000; 14:25:39.700 [planetinfo.record]: polar_cloud_color = 0.863867, 0.530637, 0.530637, 1; 14:25:39.700 [planetinfo.record]: polar_land_color = 0.934, 0.913934, 0.928983, 1; 14:25:39.700 [planetinfo.record]: polar_sea_color = 0.946289, 0.913568, 0.897588, 1; 14:25:39.700 [planetinfo.record]: sea_color = 0.537109, 0.462821, 0.42654, 1; 14:25:39.700 [planetinfo.record]: rotation_speed = 0.004334 14:25:39.700 [planetinfo.record]: planet zpos = 398300.000000 14:25:39.700 [planetinfo.record]: sun_radius = 84742.068787 14:25:39.700 [planetinfo.record]: sun_vector = 0.910 0.316 -0.267 14:25:39.700 [planetinfo.record]: sun_distance = 677110 14:25:39.700 [planetinfo.record]: corona_flare = 0.034470 14:25:39.700 [planetinfo.record]: corona_hues = 0.749687 14:25:39.701 [planetinfo.record]: sun_color = 0.309716, 0.648903, 0.67916, 1 14:25:39.701 [planetinfo.record]: corona_shimmer = 0.320463 14:25:39.701 [planetinfo.record]: station_vector = 0.082 -0.049 0.995 14:25:39.701 [planetinfo.record]: station = icosahedron 14:25:45.464 [PLANETINFO OVER]: Done 14:25:45.465 [PLANETINFO LOGGING]: 6 185 14:25:46.467 [planetinfo.record]: seed = 10 194 128 95 14:25:46.467 [planetinfo.record]: coordinates = 194 168 14:25:46.467 [planetinfo.record]: government = 1; 14:25:46.467 [planetinfo.record]: economy = 2; 14:25:46.467 [planetinfo.record]: techlevel = 8; 14:25:46.467 [planetinfo.record]: population = 36; 14:25:46.467 [planetinfo.record]: productivity = 11520; 14:25:46.467 [planetinfo.record]: name = "Onarmala"; 14:25:46.467 [planetinfo.record]: inhabitant = "Yellow Horned Humanoid"; 14:25:46.468 [planetinfo.record]: inhabitants = "Yellow Horned Humanoids"; 14:25:46.468 [planetinfo.record]: description = "Onarmala is an unremarkable dump."; 14:25:46.478 [planetinfo.record]: air_color = 0.552089, 0.969123, 0.98209, 1; 14:25:46.478 [planetinfo.record]: cloud_alpha = 1.000000; 14:25:46.478 [planetinfo.record]: cloud_color = 0.880304, 0.949219, 0.270676, 1; 14:25:46.478 [planetinfo.record]: cloud_fraction = 0.330000; 14:25:46.478 [planetinfo.record]: land_color = 0.66, 0.275859, 0.356889, 1; 14:25:46.478 [planetinfo.record]: land_fraction = 0.570000; 14:25:46.478 [planetinfo.record]: polar_cloud_color = 0.885078, 0.927148, 0.51292, 1; 14:25:46.478 [planetinfo.record]: polar_land_color = 0.934, 0.798096, 0.826763, 1; 14:25:46.478 [planetinfo.record]: polar_sea_color = 0.923409, 0.935938, 0.884205, 1; 14:25:46.478 [planetinfo.record]: sea_color = 0.606322, 0.640625, 0.498987, 1; 14:25:46.478 [planetinfo.record]: rotation_speed = 0.000463 14:25:46.478 [planetinfo.record]: planet zpos = 959000.000000 14:25:46.479 [planetinfo.record]: sun_radius = 185488.082886 14:25:46.479 [planetinfo.record]: sun_vector = 0.517 -0.855 -0.049 14:25:46.479 [planetinfo.record]: sun_distance = 1507000 14:25:46.479 [planetinfo.record]: corona_flare = 0.018913 14:25:46.479 [planetinfo.record]: corona_hues = 0.735176 14:25:46.479 [planetinfo.record]: sun_color = 0.844305, 0.847, 0.459633, 1 14:25:46.479 [planetinfo.record]: corona_shimmer = 0.800293 14:25:46.479 [planetinfo.record]: station_vector = -0.915 0.311 0.255 14:25:46.479 [planetinfo.record]: station = coriolis 14:25:51.416 [PLANETINFO OVER]: Done 14:25:51.417 [PLANETINFO LOGGING]: 6 186 14:25:52.422 [planetinfo.record]: seed = 218 21 52 204 14:25:52.422 [planetinfo.record]: coordinates = 21 235 14:25:52.422 [planetinfo.record]: government = 3; 14:25:52.422 [planetinfo.record]: economy = 3; 14:25:52.422 [planetinfo.record]: techlevel = 7; 14:25:52.422 [planetinfo.record]: population = 35; 14:25:52.422 [planetinfo.record]: productivity = 13720; 14:25:52.422 [planetinfo.record]: name = "Inreerar"; 14:25:52.422 [planetinfo.record]: inhabitant = "Human Colonial"; 14:25:52.422 [planetinfo.record]: inhabitants = "Human Colonials"; 14:25:52.422 [planetinfo.record]: description = "This world is a revolting dump."; 14:25:52.436 [planetinfo.record]: air_color = 0.726325, 0.791598, 0.924205, 1; 14:25:52.436 [planetinfo.record]: cloud_alpha = 1.000000; 14:25:52.436 [planetinfo.record]: cloud_color = 0.742073, 0.77461, 0.775391, 1; 14:25:52.436 [planetinfo.record]: cloud_fraction = 0.450000; 14:25:52.436 [planetinfo.record]: land_color = 0.358682, 0.66, 0.221719, 1; 14:25:52.436 [planetinfo.record]: land_fraction = 0.570000; 14:25:52.436 [planetinfo.record]: polar_cloud_color = 0.826127, 0.848391, 0.848926, 1; 14:25:52.436 [planetinfo.record]: polar_land_color = 0.827397, 0.934, 0.778941, 1; 14:25:52.436 [planetinfo.record]: polar_sea_color = 0.941406, 0.919861, 0.889372, 1; 14:25:52.436 [planetinfo.record]: sea_color = 0.585938, 0.532297, 0.45639, 1; 14:25:52.436 [planetinfo.record]: rotation_speed = 0.004705 14:25:52.436 [planetinfo.record]: planet zpos = 590900.000000 14:25:52.436 [planetinfo.record]: sun_radius = 158058.717194 14:25:52.436 [planetinfo.record]: sun_vector = -0.835 0.265 0.483 14:25:52.436 [planetinfo.record]: sun_distance = 1122710 14:25:52.437 [planetinfo.record]: corona_flare = 0.021019 14:25:52.437 [planetinfo.record]: corona_hues = 0.534470 14:25:52.437 [planetinfo.record]: sun_color = 0.664017, 0.75789, 0.836697, 1 14:25:52.437 [planetinfo.record]: corona_shimmer = 0.437817 14:25:52.437 [planetinfo.record]: station_vector = 0.563 0.753 0.342 14:25:52.437 [planetinfo.record]: station = coriolis 14:25:57.781 [PLANETINFO OVER]: Done 14:25:57.781 [PLANETINFO LOGGING]: 6 187 14:25:58.800 [planetinfo.record]: seed = 66 74 80 200 14:25:58.800 [planetinfo.record]: coordinates = 74 176 14:25:58.800 [planetinfo.record]: government = 0; 14:25:58.800 [planetinfo.record]: economy = 2; 14:25:58.800 [planetinfo.record]: techlevel = 7; 14:25:58.800 [planetinfo.record]: population = 31; 14:25:58.800 [planetinfo.record]: productivity = 7936; 14:25:58.800 [planetinfo.record]: name = "Usxela"; 14:25:58.800 [planetinfo.record]: inhabitant = "Human Colonial"; 14:25:58.800 [planetinfo.record]: inhabitants = "Human Colonials"; 14:25:58.800 [planetinfo.record]: description = "Usxela is most noted for the Usxelaian deadly goat and its hoopy casinos."; 14:25:58.816 [planetinfo.record]: air_color = 0.724977, 0.808705, 0.905994, 1; 14:25:58.816 [planetinfo.record]: cloud_alpha = 1.000000; 14:25:58.816 [planetinfo.record]: cloud_color = 0.720703, 0.720703, 0.720703, 1; 14:25:58.816 [planetinfo.record]: cloud_fraction = 0.240000; 14:25:58.816 [planetinfo.record]: land_color = 0.412595, 0.533203, 0.0791473, 1; 14:25:58.816 [planetinfo.record]: land_fraction = 0.420000; 14:25:58.816 [planetinfo.record]: polar_cloud_color = 0.824316, 0.824316, 0.824316, 1; 14:25:58.816 [planetinfo.record]: polar_land_color = 0.893146, 0.94668, 0.74514, 1; 14:25:58.816 [planetinfo.record]: polar_sea_color = 0.856699, 0.915625, 0.914244, 1; 14:25:58.816 [planetinfo.record]: sea_color = 0.62655, 0.84375, 0.838659, 1; 14:25:58.816 [planetinfo.record]: rotation_speed = 0.002056 14:25:58.816 [planetinfo.record]: planet zpos = 740700.000000 14:25:58.816 [planetinfo.record]: sun_radius = 85793.379822 14:25:58.816 [planetinfo.record]: sun_vector = 0.273 0.930 0.248 14:25:58.816 [planetinfo.record]: sun_distance = 938220 14:25:58.816 [planetinfo.record]: corona_flare = 0.041365 14:25:58.817 [planetinfo.record]: corona_hues = 0.856575 14:25:58.817 [planetinfo.record]: sun_color = 0.799789, 0.799997, 0.769785, 1 14:25:58.817 [planetinfo.record]: corona_shimmer = 0.347174 14:25:58.824 [planetinfo.record]: station_vector = -0.964 -0.249 0.097 14:25:58.824 [planetinfo.record]: station = coriolis 14:26:03.934 [PLANETINFO OVER]: Done 14:26:03.934 [PLANETINFO LOGGING]: 6 188 14:26:04.940 [planetinfo.record]: seed = 34 96 244 247 14:26:04.940 [planetinfo.record]: coordinates = 96 213 14:26:04.940 [planetinfo.record]: government = 4; 14:26:04.940 [planetinfo.record]: economy = 5; 14:26:04.940 [planetinfo.record]: techlevel = 4; 14:26:04.940 [planetinfo.record]: population = 26; 14:26:04.940 [planetinfo.record]: productivity = 8320; 14:26:04.940 [planetinfo.record]: name = "Tidice"; 14:26:04.940 [planetinfo.record]: inhabitant = "Furry Rodent"; 14:26:04.941 [planetinfo.record]: inhabitants = "Furry Rodents"; 14:26:04.941 [planetinfo.record]: description = "The planet Tidice is very famous for its unusual casinos but beset by dreadful earthquakes."; 14:26:04.983 [planetinfo.record]: air_color = 0.577368, 0.89713, 0.985992, 1; 14:26:04.983 [planetinfo.record]: cloud_alpha = 1.000000; 14:26:04.983 [planetinfo.record]: cloud_color = 0.549648, 0.960938, 0.341583, 1; 14:26:04.983 [planetinfo.record]: cloud_fraction = 0.470000; 14:26:04.983 [planetinfo.record]: land_color = 0.399609, 0.66, 0.586765, 1; 14:26:04.983 [planetinfo.record]: land_fraction = 0.640000; 14:26:04.983 [planetinfo.record]: polar_cloud_color = 0.682994, 0.932422, 0.556812, 1; 14:26:04.983 [planetinfo.record]: polar_land_color = 0.841877, 0.934, 0.90809, 1; 14:26:04.983 [planetinfo.record]: polar_sea_color = 0.905044, 0.932031, 0.878057, 1; 14:26:04.983 [planetinfo.record]: sea_color = 0.600966, 0.679688, 0.522244, 1; 14:26:04.983 [planetinfo.record]: rotation_speed = 0.000049 14:26:04.983 [planetinfo.record]: planet zpos = 611520.000000 14:26:04.983 [planetinfo.record]: sun_radius = 97078.857422 14:26:04.983 [planetinfo.record]: sun_vector = 0.451 0.196 0.871 14:26:04.983 [planetinfo.record]: sun_distance = 940800 14:26:04.983 [planetinfo.record]: corona_flare = 0.021603 14:26:04.983 [planetinfo.record]: corona_hues = 0.811386 14:26:04.984 [planetinfo.record]: sun_color = 0.701288, 0.703353, 0.708136, 1 14:26:04.984 [planetinfo.record]: corona_shimmer = 0.269573 14:26:04.984 [planetinfo.record]: station_vector = 0.447 0.848 0.284 14:26:04.984 [planetinfo.record]: station = coriolis 14:26:09.449 [PLANETINFO OVER]: Done 14:26:09.450 [PLANETINFO LOGGING]: 6 189 14:26:10.454 [planetinfo.record]: seed = 154 170 64 93 14:26:10.454 [planetinfo.record]: coordinates = 170 133 14:26:10.454 [planetinfo.record]: government = 3; 14:26:10.454 [planetinfo.record]: economy = 5; 14:26:10.454 [planetinfo.record]: techlevel = 6; 14:26:10.454 [planetinfo.record]: population = 33; 14:26:10.454 [planetinfo.record]: productivity = 9240; 14:26:10.454 [planetinfo.record]: name = "Isdilaon"; 14:26:10.455 [planetinfo.record]: inhabitant = "Human Colonial"; 14:26:10.455 [planetinfo.record]: inhabitants = "Human Colonials"; 14:26:10.455 [planetinfo.record]: description = "Isdilaon is reasonably well known for its great dense forests but beset by deadly tree snakes."; 14:26:10.460 [planetinfo.record]: air_color = 0.469257, 0.629362, 0.877377, 1; 14:26:10.460 [planetinfo.record]: cloud_alpha = 1.000000; 14:26:10.460 [planetinfo.record]: cloud_color = 0.128937, 0.634766, 0.551778, 1; 14:26:10.460 [planetinfo.record]: cloud_fraction = 0.540000; 14:26:10.460 [planetinfo.record]: land_color = 0.598125, 0.66, 0.619395, 1; 14:26:10.460 [planetinfo.record]: land_fraction = 0.310000; 14:26:10.460 [planetinfo.record]: polar_cloud_color = 0.394357, 0.785645, 0.721449, 1; 14:26:10.460 [planetinfo.record]: polar_land_color = 0.912109, 0.934, 0.919634, 1; 14:26:10.460 [planetinfo.record]: polar_sea_color = 0.885299, 0.855236, 0.944727, 1; 14:26:10.460 [planetinfo.record]: sea_color = 0.413657, 0.3433, 0.552734, 1; 14:26:10.460 [planetinfo.record]: rotation_speed = 0.003583 14:26:10.460 [planetinfo.record]: planet zpos = 757680.000000 14:26:10.461 [planetinfo.record]: sun_radius = 168135.684814 14:26:10.461 [planetinfo.record]: sun_vector = 0.607 -0.750 0.262 14:26:10.461 [planetinfo.record]: sun_distance = 1262800 14:26:10.461 [planetinfo.record]: corona_flare = 0.058325 14:26:10.461 [planetinfo.record]: corona_hues = 0.993500 14:26:10.461 [planetinfo.record]: sun_color = 0.587009, 0.727276, 0.784891, 1 14:26:10.461 [planetinfo.record]: corona_shimmer = 0.265463 14:26:10.461 [planetinfo.record]: station_vector = -0.706 0.159 0.690 14:26:10.461 [planetinfo.record]: station = coriolis 14:26:15.192 [PLANETINFO OVER]: Done 14:26:15.193 [PLANETINFO LOGGING]: 6 190 14:26:16.195 [planetinfo.record]: seed = 138 127 212 161 14:26:16.195 [planetinfo.record]: coordinates = 127 149 14:26:16.195 [planetinfo.record]: government = 1; 14:26:16.196 [planetinfo.record]: economy = 7; 14:26:16.196 [planetinfo.record]: techlevel = 4; 14:26:16.196 [planetinfo.record]: population = 25; 14:26:16.196 [planetinfo.record]: productivity = 3000; 14:26:16.196 [planetinfo.record]: name = "Leveti"; 14:26:16.196 [planetinfo.record]: inhabitant = "Large Harmless Horned Lobster"; 14:26:16.196 [planetinfo.record]: inhabitants = "Large Harmless Horned Lobsters"; 14:26:16.196 [planetinfo.record]: description = "Leveti is reasonably notable for its great tropical forests but cursed by dreadful solar activity."; 14:26:16.198 [planetinfo.record]: air_color = 0.420717, 0.821811, 0.855264, 1; 14:26:16.198 [planetinfo.record]: cloud_alpha = 1.000000; 14:26:16.198 [planetinfo.record]: cloud_color = 0.440926, 0.568359, 0.0421829, 1; 14:26:16.198 [planetinfo.record]: cloud_fraction = 0.340000; 14:26:16.198 [planetinfo.record]: land_color = 0.520298, 0.134062, 0.66, 1; 14:26:16.198 [planetinfo.record]: land_fraction = 0.560000; 14:26:16.198 [planetinfo.record]: polar_cloud_color = 0.649855, 0.755762, 0.318468, 1; 14:26:16.198 [planetinfo.record]: polar_land_color = 0.884575, 0.74793, 0.934, 1; 14:26:16.198 [planetinfo.record]: polar_sea_color = 0.935742, 0.930944, 0.879908, 1; 14:26:16.198 [planetinfo.record]: sea_color = 0.642578, 0.629398, 0.489213, 1; 14:26:16.198 [planetinfo.record]: rotation_speed = 0.000327 14:26:16.198 [planetinfo.record]: planet zpos = 351890.000000 14:26:16.198 [planetinfo.record]: sun_radius = 78413.476410 14:26:16.198 [planetinfo.record]: sun_vector = -0.122 0.094 -0.988 14:26:16.199 [planetinfo.record]: sun_distance = 543830 14:26:16.199 [planetinfo.record]: corona_flare = 0.069258 14:26:16.199 [planetinfo.record]: corona_hues = 0.948822 14:26:16.199 [planetinfo.record]: sun_color = 0.608698, 0.786512, 0.794998, 1 14:26:16.199 [planetinfo.record]: corona_shimmer = 0.276234 14:26:16.199 [planetinfo.record]: station_vector = 0.146 -0.761 0.632 14:26:16.199 [planetinfo.record]: station = coriolis 14:26:21.267 [PLANETINFO OVER]: Done 14:26:21.268 [PLANETINFO LOGGING]: 6 191 14:26:22.274 [planetinfo.record]: seed = 18 48 80 190 14:26:22.274 [planetinfo.record]: coordinates = 48 215 14:26:22.274 [planetinfo.record]: government = 2; 14:26:22.274 [planetinfo.record]: economy = 7; 14:26:22.274 [planetinfo.record]: techlevel = 1; 14:26:22.274 [planetinfo.record]: population = 14; 14:26:22.274 [planetinfo.record]: productivity = 2016; 14:26:22.274 [planetinfo.record]: name = "Ribiraor"; 14:26:22.274 [planetinfo.record]: inhabitant = "Human Colonial"; 14:26:22.274 [planetinfo.record]: inhabitants = "Human Colonials"; 14:26:22.274 [planetinfo.record]: description = "This world is very fabled for the Ribiraorian edible poet."; 14:26:22.279 [planetinfo.record]: air_color = 0.515438, 0.975586, 0.856583, 1; 14:26:22.280 [planetinfo.record]: cloud_alpha = 1.000000; 14:26:22.280 [planetinfo.record]: cloud_color = 0.929688, 0.348576, 0.170685, 1; 14:26:22.280 [planetinfo.record]: cloud_fraction = 0.350000; 14:26:22.280 [planetinfo.record]: land_color = 0.5, 0.447266, 0.481873, 1; 14:26:22.280 [planetinfo.record]: land_fraction = 0.700000; 14:26:22.280 [planetinfo.record]: polar_cloud_color = 0.918359, 0.55959, 0.449763, 1; 14:26:22.280 [planetinfo.record]: polar_land_color = 0.95, 0.924951, 0.941389, 1; 14:26:22.280 [planetinfo.record]: polar_sea_color = 0.939062, 0.918993, 0.884406, 1; 14:26:22.280 [planetinfo.record]: sea_color = 0.609375, 0.557282, 0.467505, 1; 14:26:22.280 [planetinfo.record]: rotation_speed = 0.000172 14:26:22.280 [planetinfo.record]: planet zpos = 838240.000000 14:26:22.280 [planetinfo.record]: sun_radius = 222361.350098 14:26:22.281 [planetinfo.record]: sun_vector = -0.302 -0.596 0.744 14:26:22.281 [planetinfo.record]: sun_distance = 1354080 14:26:22.281 [planetinfo.record]: corona_flare = 0.027641 14:26:22.281 [planetinfo.record]: corona_hues = 0.829895 14:26:22.281 [planetinfo.record]: sun_color = 0.764475, 0.362748, 0.320442, 1 14:26:22.281 [planetinfo.record]: corona_shimmer = 0.369217 14:26:22.281 [planetinfo.record]: station_vector = -0.122 -0.848 0.516 14:26:22.281 [planetinfo.record]: station = coriolis 14:26:26.930 [PLANETINFO OVER]: Done 14:26:26.930 [PLANETINFO LOGGING]: 6 192 14:26:27.933 [planetinfo.record]: seed = 18 57 212 179 14:26:27.934 [planetinfo.record]: coordinates = 57 180 14:26:27.934 [planetinfo.record]: government = 2; 14:26:27.934 [planetinfo.record]: economy = 4; 14:26:27.934 [planetinfo.record]: techlevel = 5; 14:26:27.934 [planetinfo.record]: population = 27; 14:26:27.934 [planetinfo.record]: productivity = 7776; 14:26:27.934 [planetinfo.record]: name = "Belere"; 14:26:27.934 [planetinfo.record]: inhabitant = "Harmless Furry Rodent"; 14:26:27.934 [planetinfo.record]: inhabitants = "Harmless Furry Rodents"; 14:26:27.934 [planetinfo.record]: description = "This world is very notable for the Belereian edible moth."; 14:26:27.944 [planetinfo.record]: air_color = 0.489026, 0.528067, 0.896889, 1; 14:26:27.944 [planetinfo.record]: cloud_alpha = 1.000000; 14:26:27.944 [planetinfo.record]: cloud_color = 0.162506, 0.307661, 0.693359, 1; 14:26:27.944 [planetinfo.record]: cloud_fraction = 0.340000; 14:26:27.944 [planetinfo.record]: land_color = 0.508837, 0.66, 0.415078, 1; 14:26:27.944 [planetinfo.record]: land_fraction = 0.680000; 14:26:27.944 [planetinfo.record]: polar_cloud_color = 0.423451, 0.529698, 0.812012, 1; 14:26:27.944 [planetinfo.record]: polar_land_color = 0.88052, 0.934, 0.84735, 1; 14:26:27.944 [planetinfo.record]: polar_sea_color = 0.884064, 0.932031, 0.885938, 1; 14:26:27.945 [planetinfo.record]: sea_color = 0.539767, 0.679688, 0.545233, 1; 14:26:27.945 [planetinfo.record]: rotation_speed = 0.000637 14:26:27.945 [planetinfo.record]: planet zpos = 473330.000000 14:26:27.945 [planetinfo.record]: sun_radius = 98326.889496 14:26:27.945 [planetinfo.record]: sun_vector = -0.423 -0.663 0.617 14:26:27.945 [planetinfo.record]: sun_distance = 618970 14:26:27.945 [planetinfo.record]: corona_flare = 0.031259 14:26:27.945 [planetinfo.record]: corona_hues = 0.577827 14:26:27.945 [planetinfo.record]: sun_color = 0.741357, 0.735424, 0.686711, 1 14:26:27.946 [planetinfo.record]: corona_shimmer = 0.323504 14:26:27.946 [planetinfo.record]: station_vector = 0.159 0.944 0.289 14:26:27.946 [planetinfo.record]: station = coriolis 14:26:33.021 [PLANETINFO OVER]: Done 14:26:33.023 [PLANETINFO LOGGING]: 6 193 14:26:34.039 [planetinfo.record]: seed = 170 227 128 19 14:26:34.039 [planetinfo.record]: coordinates = 227 142 14:26:34.039 [planetinfo.record]: government = 5; 14:26:34.039 [planetinfo.record]: economy = 6; 14:26:34.039 [planetinfo.record]: techlevel = 7; 14:26:34.039 [planetinfo.record]: population = 40; 14:26:34.039 [planetinfo.record]: productivity = 11520; 14:26:34.039 [planetinfo.record]: name = "Becetela"; 14:26:34.039 [planetinfo.record]: inhabitant = "Green Furry Rodent"; 14:26:34.039 [planetinfo.record]: inhabitants = "Green Furry Rodents"; 14:26:34.039 [planetinfo.record]: description = "This world is a revolting little planet."; 14:26:34.056 [planetinfo.record]: air_color = 0.479722, 0.596699, 0.88193, 1; 14:26:34.056 [planetinfo.record]: cloud_alpha = 1.000000; 14:26:34.056 [planetinfo.record]: cloud_color = 0.149445, 0.578267, 0.648438, 1; 14:26:34.056 [planetinfo.record]: cloud_fraction = 0.420000; 14:26:34.056 [planetinfo.record]: land_color = 0.164383, 0.519531, 0.211551, 1; 14:26:34.056 [planetinfo.record]: land_fraction = 0.300000; 14:26:34.056 [planetinfo.record]: polar_cloud_color = 0.410977, 0.738244, 0.791797, 1; 14:26:34.056 [planetinfo.record]: polar_land_color = 0.786027, 0.948047, 0.807545, 1; 14:26:34.056 [planetinfo.record]: polar_sea_color = 0.948438, 0.90165, 0.899348, 1; 14:26:34.056 [planetinfo.record]: sea_color = 0.515625, 0.413878, 0.408875, 1; 14:26:34.056 [planetinfo.record]: rotation_speed = 0.001683 14:26:34.056 [planetinfo.record]: planet zpos = 533540.000000 14:26:34.056 [planetinfo.record]: sun_radius = 80746.492920 14:26:34.056 [planetinfo.record]: sun_vector = 0.868 -0.415 -0.271 14:26:34.056 [planetinfo.record]: sun_distance = 724090 14:26:34.056 [planetinfo.record]: corona_flare = 0.027591 14:26:34.056 [planetinfo.record]: corona_hues = 0.829155 14:26:34.063 [planetinfo.record]: sun_color = 0.528039, 0.681108, 0.838202, 1 14:26:34.064 [planetinfo.record]: corona_shimmer = 0.576019 14:26:34.064 [planetinfo.record]: station_vector = 0.045 -0.993 0.108 14:26:34.064 [planetinfo.record]: station = coriolis 14:26:39.044 [PLANETINFO OVER]: Done 14:26:39.046 [PLANETINFO LOGGING]: 6 194 14:26:40.051 [planetinfo.record]: seed = 186 21 244 23 14:26:40.051 [planetinfo.record]: coordinates = 21 124 14:26:40.051 [planetinfo.record]: government = 7; 14:26:40.051 [planetinfo.record]: economy = 4; 14:26:40.051 [planetinfo.record]: techlevel = 8; 14:26:40.051 [planetinfo.record]: population = 44; 14:26:40.051 [planetinfo.record]: productivity = 23232; 14:26:40.051 [planetinfo.record]: name = "Tiared"; 14:26:40.051 [planetinfo.record]: inhabitant = "Green Bug-Eyed Bird"; 14:26:40.051 [planetinfo.record]: inhabitants = "Green Bug-Eyed Birds"; 14:26:40.051 [planetinfo.record]: description = "Tiared is most famous for its pink Tiaredian Loa’ Loa’weed plantations and Zero-G hockey."; 14:26:40.088 [planetinfo.record]: air_color = 0.736448, 0.534752, 0.933961, 1; 14:26:40.088 [planetinfo.record]: cloud_alpha = 1.000000; 14:26:40.088 [planetinfo.record]: cloud_color = 0.804688, 0.254608, 0.512458, 1; 14:26:40.088 [planetinfo.record]: cloud_fraction = 0.330000; 14:26:40.088 [planetinfo.record]: land_color = 0.476953, 0.66, 0.617098, 1; 14:26:40.088 [planetinfo.record]: land_fraction = 0.690000; 14:26:40.088 [planetinfo.record]: polar_cloud_color = 0.862109, 0.493777, 0.666433, 1; 14:26:40.089 [planetinfo.record]: polar_land_color = 0.86924, 0.934, 0.918822, 1; 14:26:40.089 [planetinfo.record]: polar_sea_color = 0.896918, 0.928711, 0.870576, 1; 14:26:40.089 [planetinfo.record]: sea_color = 0.615273, 0.712891, 0.534389, 1; 14:26:40.089 [planetinfo.record]: rotation_speed = 0.000873 14:26:40.089 [planetinfo.record]: planet zpos = 601770.000000 14:26:40.089 [planetinfo.record]: sun_radius = 135678.804016 14:26:40.089 [planetinfo.record]: sun_vector = 0.760 0.580 -0.292 14:26:40.089 [planetinfo.record]: sun_distance = 1064670 14:26:40.089 [planetinfo.record]: corona_flare = 0.069313 14:26:40.089 [planetinfo.record]: corona_hues = 0.551010 14:26:40.089 [planetinfo.record]: sun_color = 0.790265, 0.637756, 0.578295, 1 14:26:40.089 [planetinfo.record]: corona_shimmer = 0.287408 14:26:40.090 [planetinfo.record]: station_vector = -0.785 -0.535 0.311 14:26:40.090 [planetinfo.record]: station = coriolis 14:26:44.788 [PLANETINFO OVER]: Done 14:26:44.789 [PLANETINFO LOGGING]: 6 195 14:26:45.791 [planetinfo.record]: seed = 98 154 208 28 14:26:45.791 [planetinfo.record]: coordinates = 154 216 14:26:45.791 [planetinfo.record]: government = 4; 14:26:45.791 [planetinfo.record]: economy = 0; 14:26:45.791 [planetinfo.record]: techlevel = 11; 14:26:45.791 [planetinfo.record]: population = 49; 14:26:45.792 [planetinfo.record]: productivity = 31360; 14:26:45.792 [planetinfo.record]: name = "Teabi"; 14:26:45.792 [planetinfo.record]: inhabitant = "Green Horned Lizard"; 14:26:45.792 [planetinfo.record]: inhabitants = "Green Horned Lizards"; 14:26:45.792 [planetinfo.record]: description = "This planet is very notable for the Teabiian tree wolf and its pink oceans."; 14:26:45.808 [planetinfo.record]: air_color = 0.438421, 0.804864, 0.842256, 1; 14:26:45.808 [planetinfo.record]: cloud_alpha = 1.000000; 14:26:45.808 [planetinfo.record]: cloud_color = 0.400801, 0.529297, 0.0847702, 1; 14:26:45.808 [planetinfo.record]: cloud_fraction = 0.290000; 14:26:45.808 [planetinfo.record]: land_color = 0.295356, 0.020625, 0.66, 1; 14:26:45.808 [planetinfo.record]: land_fraction = 0.630000; 14:26:45.808 [planetinfo.record]: polar_cloud_color = 0.626179, 0.738184, 0.350709, 1; 14:26:45.808 [planetinfo.record]: polar_land_color = 0.804994, 0.707797, 0.934, 1; 14:26:45.808 [planetinfo.record]: polar_sea_color = 0.91, 0.8645, 0.8918, 1; 14:26:45.808 [planetinfo.record]: sea_color = 0.9, 0.72, 0.828, 1; 14:26:45.808 [planetinfo.record]: rotation_speed = 0.003239 14:26:45.808 [planetinfo.record]: planet zpos = 785460.000000 14:26:45.808 [planetinfo.record]: sun_radius = 162409.173889 14:26:45.808 [planetinfo.record]: sun_vector = 0.113 0.508 0.854 14:26:45.808 [planetinfo.record]: sun_distance = 1268820 14:26:45.808 [planetinfo.record]: corona_flare = 0.059877 14:26:45.808 [planetinfo.record]: corona_hues = 0.752487 14:26:45.808 [planetinfo.record]: sun_color = 0.680051, 0.636646, 0.287265, 1 14:26:45.808 [planetinfo.record]: corona_shimmer = 0.361239 14:26:45.808 [planetinfo.record]: station_vector = -0.955 0.289 0.068 14:26:45.808 [planetinfo.record]: station = icosahedron 14:26:50.442 [PLANETINFO OVER]: Done 14:26:50.443 [PLANETINFO LOGGING]: 6 196 14:26:51.459 [planetinfo.record]: seed = 130 242 52 200 14:26:51.460 [planetinfo.record]: coordinates = 242 70 14:26:51.460 [planetinfo.record]: government = 0; 14:26:51.460 [planetinfo.record]: economy = 6; 14:26:51.460 [planetinfo.record]: techlevel = 3; 14:26:51.460 [planetinfo.record]: population = 19; 14:26:51.460 [planetinfo.record]: productivity = 2432; 14:26:51.460 [planetinfo.record]: name = "Usleance"; 14:26:51.460 [planetinfo.record]: inhabitant = "Human Colonial"; 14:26:51.460 [planetinfo.record]: inhabitants = "Human Colonials"; 14:26:51.460 [planetinfo.record]: description = "This planet is most fabled for its inhabitants’ eccentric shyness but cursed by occasional earthquakes."; 14:26:51.472 [planetinfo.record]: air_color = 0.562245, 0.955291, 0.977537, 1; 14:26:51.472 [planetinfo.record]: cloud_alpha = 1.000000; 14:26:51.472 [planetinfo.record]: cloud_color = 0.826883, 0.935547, 0.303322, 1; 14:26:51.472 [planetinfo.record]: cloud_fraction = 0.350000; 14:26:51.472 [planetinfo.record]: land_color = 0.66, 0.28875, 0.323555, 1; 14:26:51.472 [planetinfo.record]: land_fraction = 0.660000; 14:26:51.472 [planetinfo.record]: polar_cloud_color = 0.854138, 0.920996, 0.532001, 1; 14:26:51.472 [planetinfo.record]: polar_land_color = 0.934, 0.802656, 0.81497, 1; 14:26:51.472 [planetinfo.record]: polar_sea_color = 0.931836, 0.90802, 0.870866, 1; 14:26:51.472 [planetinfo.record]: sea_color = 0.681641, 0.611954, 0.503242, 1; 14:26:51.472 [planetinfo.record]: rotation_speed = 0.001300 14:26:51.472 [planetinfo.record]: planet zpos = 612720.000000 14:26:51.472 [planetinfo.record]: sun_radius = 83393.221436 14:26:51.472 [planetinfo.record]: sun_vector = 0.216 -0.201 0.956 14:26:51.472 [planetinfo.record]: sun_distance = 868020 14:26:51.472 [planetinfo.record]: corona_flare = 0.012047 14:26:51.472 [planetinfo.record]: corona_hues = 0.612625 14:26:51.472 [planetinfo.record]: sun_color = 0.662778, 0.690008, 0.701404, 1 14:26:51.472 [planetinfo.record]: corona_shimmer = 0.346546 14:26:51.473 [planetinfo.record]: station_vector = -0.250 -0.889 0.383 14:26:51.473 [planetinfo.record]: station = coriolis 14:26:56.889 [PLANETINFO OVER]: Done 14:26:56.889 [PLANETINFO LOGGING]: 6 197 14:26:57.893 [planetinfo.record]: seed = 58 133 64 66 14:26:57.893 [planetinfo.record]: coordinates = 133 187 14:26:57.893 [planetinfo.record]: government = 7; 14:26:57.893 [planetinfo.record]: economy = 3; 14:26:57.893 [planetinfo.record]: techlevel = 9; 14:26:57.893 [planetinfo.record]: population = 47; 14:26:57.893 [planetinfo.record]: productivity = 28952; 14:26:57.893 [planetinfo.record]: name = "Xegearer"; 14:26:57.893 [planetinfo.record]: inhabitant = "Human Colonial"; 14:26:57.893 [planetinfo.record]: inhabitants = "Human Colonials"; 14:26:57.893 [planetinfo.record]: description = "This planet is very notable for its inhabitants’ weird shyness and its inhabitants’ eccentric love for poetry."; 14:26:57.901 [planetinfo.record]: air_color = 0.559963, 0.919768, 0.993797, 1; 14:26:57.902 [planetinfo.record]: cloud_alpha = 1.000000; 14:26:57.902 [planetinfo.record]: cloud_color = 0.618058, 0.984375, 0.284546, 1; 14:26:57.902 [planetinfo.record]: cloud_fraction = 0.250000; 14:26:57.902 [planetinfo.record]: land_color = 0.505313, 0.522231, 0.66, 1; 14:26:57.902 [planetinfo.record]: land_fraction = 0.310000; 14:26:57.902 [planetinfo.record]: polar_cloud_color = 0.723651, 0.942969, 0.523974, 1; 14:26:57.902 [planetinfo.record]: polar_land_color = 0.879273, 0.885259, 0.934, 1; 14:26:57.902 [planetinfo.record]: polar_sea_color = 0.942969, 0.930905, 0.894715, 1; 14:26:57.902 [planetinfo.record]: sea_color = 0.570312, 0.541129, 0.453577, 1; 14:26:57.902 [planetinfo.record]: rotation_speed = 0.004816 14:26:57.902 [planetinfo.record]: planet zpos = 449930.000000 14:26:57.902 [planetinfo.record]: sun_radius = 71986.750946 14:26:57.903 [planetinfo.record]: sun_vector = 0.631 0.457 -0.627 14:26:57.903 [planetinfo.record]: sun_distance = 622980 14:26:57.903 [planetinfo.record]: corona_flare = 0.061414 14:26:57.903 [planetinfo.record]: corona_hues = 0.675446 14:26:57.903 [planetinfo.record]: sun_color = 0.706964, 0.539867, 0.284346, 1 14:26:57.903 [planetinfo.record]: corona_shimmer = 0.539240 14:26:57.903 [planetinfo.record]: station_vector = 0.552 -0.219 0.804 14:26:57.903 [planetinfo.record]: station = coriolis 14:27:02.984 [PLANETINFO OVER]: Done 14:27:02.985 [PLANETINFO LOGGING]: 6 198 14:27:04.001 [planetinfo.record]: seed = 106 16 148 222 14:27:04.001 [planetinfo.record]: coordinates = 16 74 14:27:04.001 [planetinfo.record]: government = 5; 14:27:04.001 [planetinfo.record]: economy = 2; 14:27:04.001 [planetinfo.record]: techlevel = 8; 14:27:04.001 [planetinfo.record]: population = 40; 14:27:04.001 [planetinfo.record]: productivity = 23040; 14:27:04.001 [planetinfo.record]: name = "Riorusle"; 14:27:04.001 [planetinfo.record]: inhabitant = "Horned Bird"; 14:27:04.001 [planetinfo.record]: inhabitants = "Horned Birds"; 14:27:04.001 [planetinfo.record]: description = "This world is noted for its exciting sit coms."; 14:27:04.038 [planetinfo.record]: air_color = 0.559184, 0.587681, 0.834451, 1; 14:27:04.038 [planetinfo.record]: cloud_alpha = 1.000000; 14:27:04.038 [planetinfo.record]: cloud_color = 0.308258, 0.366921, 0.505859, 1; 14:27:04.038 [planetinfo.record]: cloud_fraction = 0.390000; 14:27:04.038 [planetinfo.record]: land_color = 0.213814, 0.533708, 0.576172, 1; 14:27:04.038 [planetinfo.record]: land_fraction = 0.710000; 14:27:04.038 [planetinfo.record]: polar_cloud_color = 0.549991, 0.60273, 0.727637, 1; 14:27:04.038 [planetinfo.record]: polar_land_color = 0.794215, 0.925019, 0.942383, 1; 14:27:04.038 [planetinfo.record]: polar_sea_color = 0.907656, 0.933594, 0.881717, 1; 14:27:04.038 [planetinfo.record]: sea_color = 0.590263, 0.664062, 0.516464, 1; 14:27:04.038 [planetinfo.record]: rotation_speed = 0.001588 14:27:04.038 [planetinfo.record]: planet zpos = 769920.000000 14:27:04.038 [planetinfo.record]: sun_radius = 166890.795898 14:27:04.038 [planetinfo.record]: sun_vector = 0.591 0.353 -0.725 14:27:04.038 [planetinfo.record]: sun_distance = 1411520 14:27:04.038 [planetinfo.record]: corona_flare = 0.072479 14:27:04.038 [planetinfo.record]: corona_hues = 0.567711 14:27:04.038 [planetinfo.record]: sun_color = 0.75276, 0.804171, 0.816354, 1 14:27:04.040 [planetinfo.record]: corona_shimmer = 0.286774 14:27:04.041 [planetinfo.record]: station_vector = 0.976 -0.004 0.219 14:27:04.041 [planetinfo.record]: station = coriolis 14:27:09.064 [PLANETINFO OVER]: Done 14:27:09.065 [PLANETINFO LOGGING]: 6 199 14:27:10.069 [planetinfo.record]: seed = 50 65 208 163 14:27:10.069 [planetinfo.record]: coordinates = 65 40 14:27:10.069 [planetinfo.record]: government = 6; 14:27:10.069 [planetinfo.record]: economy = 0; 14:27:10.069 [planetinfo.record]: techlevel = 11; 14:27:10.069 [planetinfo.record]: population = 51; 14:27:10.069 [planetinfo.record]: productivity = 40800; 14:27:10.069 [planetinfo.record]: name = "Gedienza"; 14:27:10.069 [planetinfo.record]: inhabitant = "Large Harmless Bug-Eyed Bird"; 14:27:10.069 [planetinfo.record]: inhabitants = "Large Harmless Bug-Eyed Birds"; 14:27:10.069 [planetinfo.record]: description = "This world is very fabled for the Gedienzaian edible poet."; 14:27:10.084 [planetinfo.record]: air_color = 0.609484, 0.811582, 0.994447, 1; 14:27:10.084 [planetinfo.record]: cloud_alpha = 1.000000; 14:27:10.084 [planetinfo.record]: cloud_color = 0.431519, 0.986328, 0.674248, 1; 14:27:10.084 [planetinfo.record]: cloud_fraction = 0.590000; 14:27:10.084 [planetinfo.record]: land_color = 0.328066, 0.66, 0.2475, 1; 14:27:10.084 [planetinfo.record]: land_fraction = 0.610000; 14:27:10.084 [planetinfo.record]: polar_cloud_color = 0.612026, 0.943848, 0.757198, 1; 14:27:10.084 [planetinfo.record]: polar_land_color = 0.816566, 0.934, 0.788063, 1; 14:27:10.084 [planetinfo.record]: polar_sea_color = 0.944336, 0.929212, 0.897119, 1; 14:27:10.084 [planetinfo.record]: sea_color = 0.556641, 0.520981, 0.445312, 1; 14:27:10.084 [planetinfo.record]: rotation_speed = 0.001412 14:27:10.084 [planetinfo.record]: planet zpos = 474370.000000 14:27:10.084 [planetinfo.record]: sun_radius = 85103.615417 14:27:10.084 [planetinfo.record]: sun_vector = -0.529 0.700 -0.480 14:27:10.085 [planetinfo.record]: sun_distance = 656820 14:27:10.085 [planetinfo.record]: corona_flare = 0.079541 14:27:10.085 [planetinfo.record]: corona_hues = 0.664101 14:27:10.085 [planetinfo.record]: sun_color = 0.20086, 0.639212, 0.705151, 1 14:27:10.085 [planetinfo.record]: corona_shimmer = 0.399971 14:27:10.085 [planetinfo.record]: station_vector = 0.239 -0.692 0.682 14:27:10.085 [planetinfo.record]: station = dodecahedron 14:27:15.791 [PLANETINFO OVER]: Done 14:27:15.792 [PLANETINFO LOGGING]: 6 200 14:27:16.795 [planetinfo.record]: seed = 114 164 20 165 14:27:16.795 [planetinfo.record]: coordinates = 164 242 14:27:16.795 [planetinfo.record]: government = 6; 14:27:16.795 [planetinfo.record]: economy = 2; 14:27:16.795 [planetinfo.record]: techlevel = 8; 14:27:16.796 [planetinfo.record]: population = 41; 14:27:16.796 [planetinfo.record]: productivity = 26240; 14:27:16.796 [planetinfo.record]: name = "Ceteceso"; 14:27:16.796 [planetinfo.record]: inhabitant = "Human Colonial"; 14:27:16.796 [planetinfo.record]: inhabitants = "Human Colonials"; 14:27:16.796 [planetinfo.record]: description = "The planet Ceteceso is a boring planet."; 14:27:16.828 [planetinfo.record]: air_color = 0.586313, 0.941635, 0.958676, 1; 14:27:16.828 [planetinfo.record]: cloud_alpha = 1.000000; 14:27:16.828 [planetinfo.record]: cloud_color = 0.805011, 0.878906, 0.381088, 1; 14:27:16.828 [planetinfo.record]: cloud_fraction = 0.530000; 14:27:16.828 [planetinfo.record]: land_color = 0.544922, 0.429977, 0.513492, 1; 14:27:16.828 [planetinfo.record]: land_fraction = 0.490000; 14:27:16.828 [planetinfo.record]: polar_cloud_color = 0.848451, 0.895508, 0.578495, 1; 14:27:16.828 [planetinfo.record]: polar_land_color = 0.945508, 0.895647, 0.931874, 1; 14:27:16.828 [planetinfo.record]: polar_sea_color = 0.904883, 0.829874, 0.723729, 1; 14:27:16.828 [planetinfo.record]: sea_color = 0.951172, 0.635789, 0.189491, 1; 14:27:16.828 [planetinfo.record]: rotation_speed = 0.001897 14:27:16.828 [planetinfo.record]: planet zpos = 468600.000000 14:27:16.828 [planetinfo.record]: sun_radius = 96712.582397 14:27:16.828 [planetinfo.record]: sun_vector = -0.237 -0.942 0.238 14:27:16.828 [planetinfo.record]: sun_distance = 681600 14:27:16.828 [planetinfo.record]: corona_flare = 0.014198 14:27:16.828 [planetinfo.record]: corona_hues = 0.863113 14:27:16.828 [planetinfo.record]: sun_color = 0.389578, 0.438475, 0.678931, 1 14:27:16.829 [planetinfo.record]: corona_shimmer = 0.263841 14:27:16.829 [planetinfo.record]: station_vector = -0.942 -0.168 0.290 14:27:16.829 [planetinfo.record]: station = coriolis 14:27:22.086 [PLANETINFO OVER]: Done 14:27:22.087 [PLANETINFO LOGGING]: 6 201 14:27:23.090 [planetinfo.record]: seed = 74 103 128 41 14:27:23.090 [planetinfo.record]: coordinates = 103 133 14:27:23.090 [planetinfo.record]: government = 1; 14:27:23.090 [planetinfo.record]: economy = 7; 14:27:23.090 [planetinfo.record]: techlevel = 4; 14:27:23.090 [planetinfo.record]: population = 25; 14:27:23.090 [planetinfo.record]: productivity = 3000; 14:27:23.090 [planetinfo.record]: name = "Esvesoso"; 14:27:23.090 [planetinfo.record]: inhabitant = "Small Red Horned Lobster"; 14:27:23.090 [planetinfo.record]: inhabitants = "Small Red Horned Lobsters"; 14:27:23.090 [planetinfo.record]: description = "The world Esvesoso is a dull place."; 14:27:23.093 [planetinfo.record]: air_color = 0.799626, 0.767132, 0.958676, 1; 14:27:23.093 [planetinfo.record]: cloud_alpha = 1.000000; 14:27:23.093 [planetinfo.record]: cloud_color = 0.878906, 0.878906, 0.878906, 1; 14:27:23.093 [planetinfo.record]: cloud_fraction = 0.530000; 14:27:23.093 [planetinfo.record]: land_color = 0.66, 0.324844, 0.340554, 1; 14:27:23.093 [planetinfo.record]: land_fraction = 0.250000; 14:27:23.093 [planetinfo.record]: polar_cloud_color = 0.895508, 0.895508, 0.895508, 1; 14:27:23.093 [planetinfo.record]: polar_land_color = 0.934, 0.815426, 0.820984, 1; 14:27:23.094 [planetinfo.record]: polar_sea_color = 0.940039, 0.892228, 0.881195, 1; 14:27:23.094 [planetinfo.record]: sea_color = 0.599609, 0.477623, 0.449473, 1; 14:27:23.094 [planetinfo.record]: rotation_speed = 0.002925 14:27:23.094 [planetinfo.record]: planet zpos = 470070.000000 14:27:23.094 [planetinfo.record]: sun_radius = 133412.200928 14:27:23.094 [planetinfo.record]: sun_vector = -0.673 0.310 0.671 14:27:23.094 [planetinfo.record]: sun_distance = 940140 14:27:23.094 [planetinfo.record]: corona_flare = 0.096994 14:27:23.094 [planetinfo.record]: corona_hues = 0.968704 14:27:23.094 [planetinfo.record]: sun_color = 0.811322, 0.684658, 0.365845, 1 14:27:23.094 [planetinfo.record]: corona_shimmer = 1.973857 14:27:23.094 [planetinfo.record]: station_vector = 0.643 -0.735 0.214 14:27:23.094 [planetinfo.record]: station = coriolis 14:27:27.754 [PLANETINFO OVER]: Done 14:27:27.756 [PLANETINFO LOGGING]: 6 202 14:27:28.772 [planetinfo.record]: seed = 154 231 180 165 14:27:28.772 [planetinfo.record]: coordinates = 231 167 14:27:28.772 [planetinfo.record]: government = 3; 14:27:28.772 [planetinfo.record]: economy = 7; 14:27:28.772 [planetinfo.record]: techlevel = 5; 14:27:28.772 [planetinfo.record]: population = 31; 14:27:28.772 [planetinfo.record]: productivity = 5208; 14:27:28.772 [planetinfo.record]: name = "Ceraxete"; 14:27:28.772 [planetinfo.record]: inhabitant = "Fierce Harmless Slimy Frog"; 14:27:28.772 [planetinfo.record]: inhabitants = "Fierce Harmless Slimy Frogs"; 14:27:28.773 [planetinfo.record]: description = "The world Ceraxete is beset by evil disease."; 14:27:28.788 [planetinfo.record]: air_color = 0.517703, 0.624975, 0.842906, 1; 14:27:28.789 [planetinfo.record]: cloud_alpha = 1.000000; 14:27:28.789 [planetinfo.record]: cloud_color = 0.234497, 0.524295, 0.53125, 1; 14:27:28.789 [planetinfo.record]: cloud_fraction = 0.190000; 14:27:28.789 [planetinfo.record]: land_color = 0.66, 0.636797, 0.644954, 1; 14:27:28.789 [planetinfo.record]: land_fraction = 0.680000; 14:27:28.789 [planetinfo.record]: polar_cloud_color = 0.48104, 0.733015, 0.739062, 1; 14:27:28.789 [planetinfo.record]: polar_land_color = 0.934, 0.925791, 0.928677, 1; 14:27:28.790 [planetinfo.record]: polar_sea_color = 0.949023, 0.906021, 0.901572, 1; 14:27:28.790 [planetinfo.record]: sea_color = 0.509766, 0.417371, 0.407813, 1; 14:27:28.790 [planetinfo.record]: rotation_speed = 0.002220 14:27:28.790 [planetinfo.record]: planet zpos = 519240.000000 14:27:28.790 [planetinfo.record]: sun_radius = 122055.389557 14:27:28.790 [planetinfo.record]: sun_vector = 0.065 -0.968 0.241 14:27:28.790 [planetinfo.record]: sun_distance = 865400 14:27:28.790 [planetinfo.record]: corona_flare = 0.041011 14:27:28.790 [planetinfo.record]: corona_hues = 0.709587 14:27:28.790 [planetinfo.record]: sun_color = 0.388952, 0.617405, 0.784753, 1 14:27:28.791 [planetinfo.record]: corona_shimmer = 0.508465 14:27:28.792 [planetinfo.record]: station_vector = -0.783 -0.243 0.573 14:27:28.792 [planetinfo.record]: station = coriolis 14:27:33.042 [PLANETINFO OVER]: Done 14:27:33.043 [PLANETINFO LOGGING]: 6 203 14:27:34.054 [planetinfo.record]: seed = 130 156 80 147 14:27:34.054 [planetinfo.record]: coordinates = 156 194 14:27:34.054 [planetinfo.record]: government = 0; 14:27:34.054 [planetinfo.record]: economy = 2; 14:27:34.054 [planetinfo.record]: techlevel = 5; 14:27:34.054 [planetinfo.record]: population = 23; 14:27:34.054 [planetinfo.record]: productivity = 5888; 14:27:34.054 [planetinfo.record]: name = "Beatle"; 14:27:34.054 [planetinfo.record]: inhabitant = "Human Colonial"; 14:27:34.054 [planetinfo.record]: inhabitants = "Human Colonials"; 14:27:34.054 [planetinfo.record]: description = "Beatle is famous for its exotic fish meat but scourged by deadly civil war."; 14:27:34.058 [planetinfo.record]: air_color = 0.681999, 0.549768, 0.904693, 1; 14:27:34.058 [planetinfo.record]: cloud_alpha = 1.000000; 14:27:34.058 [planetinfo.record]: cloud_color = 0.716797, 0.302399, 0.66176, 1; 14:27:34.058 [planetinfo.record]: cloud_fraction = 0.380000; 14:27:34.058 [planetinfo.record]: land_color = 0.66, 0.0283594, 0.146792, 1; 14:27:34.058 [planetinfo.record]: land_fraction = 0.710000; 14:27:34.058 [planetinfo.record]: polar_cloud_color = 0.822559, 0.525345, 0.783085, 1; 14:27:34.058 [planetinfo.record]: polar_land_color = 0.934, 0.710533, 0.752433, 1; 14:27:34.058 [planetinfo.record]: polar_sea_color = 0.939453, 0.887969, 0.879545, 1; 14:27:34.058 [planetinfo.record]: sea_color = 0.605469, 0.472745, 0.451027, 1; 14:27:34.058 [planetinfo.record]: rotation_speed = 0.004543 14:27:34.058 [planetinfo.record]: planet zpos = 374000.000000 14:27:34.058 [planetinfo.record]: sun_radius = 92596.044922 14:27:34.058 [planetinfo.record]: sun_vector = 0.162 -0.757 -0.632 14:27:34.058 [planetinfo.record]: sun_distance = 748000 14:27:34.058 [planetinfo.record]: corona_flare = 0.074994 14:27:34.058 [planetinfo.record]: corona_hues = 0.886551 14:27:34.058 [planetinfo.record]: sun_color = 0.667398, 0.597206, 0.503507, 1 14:27:34.058 [planetinfo.record]: corona_shimmer = 0.276596 14:27:34.059 [planetinfo.record]: station_vector = 0.486 -0.794 0.365 14:27:34.059 [planetinfo.record]: station = coriolis 14:27:38.528 [PLANETINFO OVER]: Done 14:27:38.529 [PLANETINFO LOGGING]: 6 204 14:27:39.537 [planetinfo.record]: seed = 226 166 116 186 14:27:39.537 [planetinfo.record]: coordinates = 166 33 14:27:39.537 [planetinfo.record]: government = 4; 14:27:39.537 [planetinfo.record]: economy = 1; 14:27:39.537 [planetinfo.record]: techlevel = 10; 14:27:39.537 [planetinfo.record]: population = 46; 14:27:39.537 [planetinfo.record]: productivity = 26496; 14:27:39.537 [planetinfo.record]: name = "Qugeza"; 14:27:39.537 [planetinfo.record]: inhabitant = "Human Colonial"; 14:27:39.537 [planetinfo.record]: inhabitants = "Human Colonials"; 14:27:39.537 [planetinfo.record]: description = "The planet Qugeza is most well known for its exotic cuisine."; 14:27:39.546 [planetinfo.record]: air_color = 0.598597, 0.472695, 0.950871, 1; 14:27:39.546 [planetinfo.record]: cloud_alpha = 1.000000; 14:27:39.546 [planetinfo.record]: cloud_color = 0.703396, 0.0768585, 0.855469, 1; 14:27:39.546 [planetinfo.record]: cloud_fraction = 0.250000; 14:27:39.546 [planetinfo.record]: land_color = 0.154688, 0.257329, 0.66, 1; 14:27:39.546 [planetinfo.record]: land_fraction = 0.250000; 14:27:39.546 [planetinfo.record]: polar_cloud_color = 0.786639, 0.381553, 0.884961, 1; 14:27:39.546 [planetinfo.record]: polar_land_color = 0.755227, 0.79154, 0.934, 1; 14:27:39.546 [planetinfo.record]: polar_sea_color = 0.893676, 0.933203, 0.884082, 1; 14:27:39.546 [planetinfo.record]: sea_color = 0.554799, 0.667969, 0.52733, 1; 14:27:39.546 [planetinfo.record]: rotation_speed = 0.002532 14:27:39.546 [planetinfo.record]: planet zpos = 831300.000000 14:27:39.546 [planetinfo.record]: sun_radius = 128346.482544 14:27:39.546 [planetinfo.record]: sun_vector = 0.703 0.325 0.632 14:27:39.546 [planetinfo.record]: sun_distance = 1219240 14:27:39.546 [planetinfo.record]: corona_flare = 0.009042 14:27:39.546 [planetinfo.record]: corona_hues = 0.699219 14:27:39.546 [planetinfo.record]: sun_color = 0.285348, 0.622827, 0.820731, 1 14:27:39.547 [planetinfo.record]: corona_shimmer = 0.377773 14:27:39.547 [planetinfo.record]: station_vector = -0.961 -0.006 0.275 14:27:39.547 [planetinfo.record]: station = coriolis 14:27:44.284 [PLANETINFO OVER]: Done 14:27:44.285 [PLANETINFO LOGGING]: 6 205 14:27:45.290 [planetinfo.record]: seed = 218 33 64 137 14:27:45.290 [planetinfo.record]: coordinates = 33 228 14:27:45.290 [planetinfo.record]: government = 3; 14:27:45.290 [planetinfo.record]: economy = 4; 14:27:45.290 [planetinfo.record]: techlevel = 6; 14:27:45.290 [planetinfo.record]: population = 32; 14:27:45.290 [planetinfo.record]: productivity = 10752; 14:27:45.290 [planetinfo.record]: name = "Esaqube"; 14:27:45.290 [planetinfo.record]: inhabitant = "Human Colonial"; 14:27:45.291 [planetinfo.record]: inhabitants = "Human Colonials"; 14:27:45.291 [planetinfo.record]: description = "This planet is a dull world."; 14:27:45.332 [planetinfo.record]: air_color = 0.67589, 0.781405, 0.961928, 1; 14:27:45.332 [planetinfo.record]: cloud_alpha = 1.000000; 14:27:45.332 [planetinfo.record]: cloud_color = 0.628319, 0.888672, 0.864264, 1; 14:27:45.332 [planetinfo.record]: cloud_fraction = 0.470000; 14:27:45.332 [planetinfo.record]: land_color = 0.0773438, 0.66, 0.578064, 1; 14:27:45.332 [planetinfo.record]: land_fraction = 0.500000; 14:27:45.333 [planetinfo.record]: polar_cloud_color = 0.735125, 0.899902, 0.884454, 1; 14:27:45.333 [planetinfo.record]: polar_land_color = 0.727863, 0.934, 0.905012, 1; 14:27:45.333 [planetinfo.record]: polar_sea_color = 0.881506, 0.927344, 0.870381, 1; 14:27:45.333 [planetinfo.record]: sea_color = 0.582911, 0.726562, 0.548044, 1; 14:27:45.333 [planetinfo.record]: rotation_speed = 0.001083 14:27:45.333 [planetinfo.record]: planet zpos = 669890.000000 14:27:45.333 [planetinfo.record]: sun_radius = 127092.813263 14:27:45.333 [planetinfo.record]: sun_vector = 0.008 -0.978 -0.207 14:27:45.333 [planetinfo.record]: sun_distance = 1030600 14:27:45.333 [planetinfo.record]: corona_flare = 0.012534 14:27:45.333 [planetinfo.record]: corona_hues = 0.718498 14:27:45.333 [planetinfo.record]: sun_color = 0.770923, 0.738168, 0.736409, 1 14:27:45.333 [planetinfo.record]: corona_shimmer = 0.453267 14:27:45.334 [planetinfo.record]: station_vector = 0.968 0.230 0.103 14:27:45.334 [planetinfo.record]: station = coriolis 14:27:50.395 [PLANETINFO OVER]: Done 14:27:50.396 [PLANETINFO LOGGING]: 6 206 14:27:51.400 [planetinfo.record]: seed = 74 83 84 29 14:27:51.400 [planetinfo.record]: coordinates = 83 58 14:27:51.400 [planetinfo.record]: government = 1; 14:27:51.400 [planetinfo.record]: economy = 2; 14:27:51.400 [planetinfo.record]: techlevel = 9; 14:27:51.400 [planetinfo.record]: population = 40; 14:27:51.400 [planetinfo.record]: productivity = 12800; 14:27:51.400 [planetinfo.record]: name = "Ismaan"; 14:27:51.400 [planetinfo.record]: inhabitant = "Human Colonial"; 14:27:51.400 [planetinfo.record]: inhabitants = "Human Colonials"; 14:27:51.400 [planetinfo.record]: description = "The world Ismaan is mildly noted for its ancient mountains but ravaged by occasional solar activity."; 14:27:51.403 [planetinfo.record]: air_color = 0.572941, 0.516011, 0.905344, 1; 14:27:51.403 [planetinfo.record]: cloud_alpha = 1.000000; 14:27:51.403 [planetinfo.record]: cloud_color = 0.446981, 0.221802, 0.71875, 1; 14:27:51.403 [planetinfo.record]: cloud_fraction = 0.100000; 14:27:51.403 [planetinfo.record]: land_color = 0.656697, 0.654844, 0.66, 1; 14:27:51.403 [planetinfo.record]: land_fraction = 0.400000; 14:27:51.403 [planetinfo.record]: polar_cloud_color = 0.628842, 0.467606, 0.823438, 1; 14:27:51.403 [planetinfo.record]: polar_land_color = 0.932831, 0.932176, 0.934, 1; 14:27:51.403 [planetinfo.record]: polar_sea_color = 0.943359, 0.933778, 0.896191, 1; 14:27:51.403 [planetinfo.record]: sea_color = 0.566406, 0.543396, 0.453125, 1; 14:27:51.403 [planetinfo.record]: rotation_speed = 0.002760 14:27:51.404 [planetinfo.record]: planet zpos = 871780.000000 14:27:51.404 [planetinfo.record]: sun_radius = 113691.018372 14:27:51.404 [planetinfo.record]: sun_vector = -0.496 -0.670 -0.552 14:27:51.404 [planetinfo.record]: sun_distance = 1058590 14:27:51.404 [planetinfo.record]: corona_flare = 0.038142 14:27:51.404 [planetinfo.record]: corona_hues = 0.502472 14:27:51.404 [planetinfo.record]: sun_color = 0.222967, 0.290501, 0.744846, 1 14:27:51.405 [planetinfo.record]: corona_shimmer = 0.534717 14:27:51.406 [planetinfo.record]: station_vector = 0.763 0.089 0.641 14:27:51.406 [planetinfo.record]: station = coriolis 14:27:57.140 [PLANETINFO OVER]: Done 14:27:57.141 [PLANETINFO LOGGING]: 6 207 14:27:58.146 [planetinfo.record]: seed = 82 228 80 171 14:27:58.146 [planetinfo.record]: coordinates = 228 27 14:27:58.146 [planetinfo.record]: government = 2; 14:27:58.146 [planetinfo.record]: economy = 3; 14:27:58.146 [planetinfo.record]: techlevel = 5; 14:27:58.146 [planetinfo.record]: population = 26; 14:27:58.146 [planetinfo.record]: productivity = 8736; 14:27:58.146 [planetinfo.record]: name = "Mamaanat"; 14:27:58.146 [planetinfo.record]: inhabitant = "Human Colonial"; 14:27:58.146 [planetinfo.record]: inhabitants = "Human Colonials"; 14:27:58.146 [planetinfo.record]: description = "The world Mamaanat is beset by evil disease."; 14:27:58.148 [planetinfo.record]: air_color = 0.699781, 0.736769, 0.974285, 1; 14:27:58.148 [planetinfo.record]: cloud_alpha = 1.000000; 14:27:58.148 [planetinfo.record]: cloud_color = 0.697952, 0.786948, 0.925781, 1; 14:27:58.148 [planetinfo.record]: cloud_fraction = 0.320000; 14:27:58.148 [planetinfo.record]: land_color = 0.518264, 0.0747656, 0.66, 1; 14:27:58.148 [planetinfo.record]: land_fraction = 0.710000; 14:27:58.148 [planetinfo.record]: polar_cloud_color = 0.77562, 0.830691, 0.916602, 1; 14:27:58.148 [planetinfo.record]: polar_land_color = 0.883855, 0.726951, 0.934, 1; 14:27:58.148 [planetinfo.record]: polar_sea_color = 0.928564, 0.936328, 0.884025, 1; 14:27:58.148 [planetinfo.record]: sea_color = 0.615601, 0.636719, 0.494452, 1; 14:27:58.148 [planetinfo.record]: rotation_speed = 0.002690 14:27:58.148 [planetinfo.record]: planet zpos = 527400.000000 14:27:58.148 [planetinfo.record]: sun_radius = 123097.912598 14:27:58.148 [planetinfo.record]: sun_vector = 0.253 -0.601 -0.758 14:27:58.148 [planetinfo.record]: sun_distance = 1172000 14:27:58.148 [planetinfo.record]: corona_flare = 0.094672 14:27:58.148 [planetinfo.record]: corona_hues = 0.757645 14:27:58.148 [planetinfo.record]: sun_color = 0.585311, 0.817464, 0.84303, 1 14:27:58.149 [planetinfo.record]: corona_shimmer = 0.281820 14:27:58.149 [planetinfo.record]: station_vector = -0.828 -0.334 0.451 14:27:58.149 [planetinfo.record]: station = coriolis 14:28:02.421 [PLANETINFO OVER]: Done 14:28:02.422 [PLANETINFO LOGGING]: 6 208 14:28:03.439 [planetinfo.record]: seed = 210 145 84 120 14:28:03.439 [planetinfo.record]: coordinates = 145 59 14:28:03.439 [planetinfo.record]: government = 2; 14:28:03.439 [planetinfo.record]: economy = 3; 14:28:03.439 [planetinfo.record]: techlevel = 6; 14:28:03.439 [planetinfo.record]: population = 30; 14:28:03.439 [planetinfo.record]: productivity = 10080; 14:28:03.439 [planetinfo.record]: name = "Edcea"; 14:28:03.439 [planetinfo.record]: inhabitant = "Human Colonial"; 14:28:03.439 [planetinfo.record]: inhabitants = "Human Colonials"; 14:28:03.439 [planetinfo.record]: description = "Edcea is reasonably notable for its fabulous cuisine but plagued by evil tree wolfs."; 14:28:03.456 [planetinfo.record]: air_color = 0.569907, 0.520489, 0.89884, 1; 14:28:03.456 [planetinfo.record]: cloud_alpha = 1.000000; 14:28:03.456 [planetinfo.record]: cloud_color = 0.423526, 0.234894, 0.699219, 1; 14:28:03.456 [planetinfo.record]: cloud_fraction = 0.280000; 14:28:03.456 [planetinfo.record]: land_color = 0.572266, 0.552077, 0.534264, 1; 14:28:03.456 [planetinfo.record]: land_fraction = 0.330000; 14:28:03.456 [planetinfo.record]: polar_cloud_color = 0.613895, 0.476538, 0.814648, 1; 14:28:03.456 [planetinfo.record]: polar_land_color = 0.942773, 0.934459, 0.927122, 1; 14:28:03.456 [planetinfo.record]: polar_sea_color = 0.896913, 0.921094, 0.83744, 1; 14:28:03.456 [planetinfo.record]: sea_color = 0.706202, 0.789062, 0.502411, 1; 14:28:03.456 [planetinfo.record]: rotation_speed = 0.003123 14:28:03.456 [planetinfo.record]: planet zpos = 450810.000000 14:28:03.456 [planetinfo.record]: sun_radius = 147264.722290 14:28:03.456 [planetinfo.record]: sun_vector = 0.423 -0.285 -0.860 14:28:03.456 [planetinfo.record]: sun_distance = 1202160 14:28:03.456 [planetinfo.record]: corona_flare = 0.004326 14:28:03.456 [planetinfo.record]: corona_hues = 0.691826 14:28:03.456 [planetinfo.record]: sun_color = 0.739719, 0.569691, 0.493976, 1 14:28:03.456 [planetinfo.record]: corona_shimmer = 0.734807 14:28:03.456 [planetinfo.record]: station_vector = -0.303 0.923 0.239 14:28:03.456 [planetinfo.record]: station = coriolis 14:28:07.553 [PLANETINFO OVER]: Done 14:28:07.554 [PLANETINFO LOGGING]: 6 209 14:28:08.557 [planetinfo.record]: seed = 234 12 128 161 14:28:08.558 [planetinfo.record]: coordinates = 12 79 14:28:08.558 [planetinfo.record]: government = 5; 14:28:08.558 [planetinfo.record]: economy = 7; 14:28:08.558 [planetinfo.record]: techlevel = 3; 14:28:08.558 [planetinfo.record]: population = 25; 14:28:08.558 [planetinfo.record]: productivity = 5400; 14:28:08.558 [planetinfo.record]: name = "Leisinma"; 14:28:08.558 [planetinfo.record]: inhabitant = "Large Harmless Bony Bird"; 14:28:08.558 [planetinfo.record]: inhabitants = "Large Harmless Bony Birds"; 14:28:08.558 [planetinfo.record]: description = "This planet is most fabled for its inhabitants’ ingrained silliness but beset by deadly edible grubs."; 14:28:08.586 [planetinfo.record]: air_color = 0.450733, 0.648589, 0.885832, 1; 14:28:08.586 [planetinfo.record]: cloud_alpha = 1.000000; 14:28:08.586 [planetinfo.record]: cloud_color = 0.0825195, 0.660156, 0.457081, 1; 14:28:08.586 [planetinfo.record]: cloud_fraction = 0.400000; 14:28:08.586 [planetinfo.record]: land_color = 0.509766, 0.373208, 0.0497818, 1; 14:28:08.586 [planetinfo.record]: land_fraction = 0.540000; 14:28:08.586 [planetinfo.record]: polar_cloud_color = 0.361172, 0.79707, 0.643825, 1; 14:28:08.586 [planetinfo.record]: polar_land_color = 0.949023, 0.885467, 0.734937, 1; 14:28:08.586 [planetinfo.record]: polar_sea_color = 0.856645, 0.917578, 0.890444, 1; 14:28:08.586 [planetinfo.record]: sea_color = 0.605286, 0.824219, 0.726725, 1; 14:28:08.586 [planetinfo.record]: rotation_speed = 0.004215 14:28:08.586 [planetinfo.record]: planet zpos = 400920.000000 14:28:08.587 [planetinfo.record]: sun_radius = 89413.694458 14:28:08.587 [planetinfo.record]: sun_vector = 0.069 0.987 0.147 14:28:08.587 [planetinfo.record]: sun_distance = 524280 14:28:08.587 [planetinfo.record]: corona_flare = 0.044228 14:28:08.587 [planetinfo.record]: corona_hues = 0.793716 14:28:08.587 [planetinfo.record]: sun_color = 0.67514, 0.504842, 0.366766, 1 14:28:08.587 [planetinfo.record]: corona_shimmer = 0.441385 14:28:08.588 [planetinfo.record]: station_vector = -0.896 -0.211 0.391 14:28:08.588 [planetinfo.record]: station = coriolis 14:28:12.979 [PLANETINFO OVER]: Done 14:28:12.980 [PLANETINFO LOGGING]: 6 210 14:28:13.982 [planetinfo.record]: seed = 122 75 116 245 14:28:13.982 [planetinfo.record]: coordinates = 75 172 14:28:13.982 [planetinfo.record]: government = 7; 14:28:13.982 [planetinfo.record]: economy = 4; 14:28:13.982 [planetinfo.record]: techlevel = 10; 14:28:13.982 [planetinfo.record]: population = 52; 14:28:13.982 [planetinfo.record]: productivity = 27456; 14:28:13.982 [planetinfo.record]: name = "Ladire"; 14:28:13.982 [planetinfo.record]: inhabitant = "Human Colonial"; 14:28:13.982 [planetinfo.record]: inhabitants = "Human Colonials"; 14:28:13.982 [planetinfo.record]: description = "The planet Ladire is scourged by killer edible arts graduates."; 14:28:13.998 [planetinfo.record]: air_color = 0.620838, 0.46003, 0.96583, 1; 14:28:13.998 [planetinfo.record]: cloud_alpha = 1.000000; 14:28:13.998 [planetinfo.record]: cloud_color = 0.873023, 0.0246201, 0.900391, 1; 14:28:13.998 [planetinfo.record]: cloud_fraction = 0.280000; 14:28:13.998 [planetinfo.record]: land_color = 0.66, 0.262969, 0.62588, 1; 14:28:13.998 [planetinfo.record]: land_fraction = 0.410000; 14:28:13.998 [planetinfo.record]: polar_cloud_color = 0.88798, 0.35491, 0.905176, 1; 14:28:13.998 [planetinfo.record]: polar_land_color = 0.934, 0.793535, 0.921929, 1; 14:28:13.998 [planetinfo.record]: polar_sea_color = 0.924127, 0.937305, 0.887693, 1; 14:28:13.998 [planetinfo.record]: sea_color = 0.591695, 0.626953, 0.494215, 1; 14:28:13.998 [planetinfo.record]: rotation_speed = 0.003482 14:28:13.999 [planetinfo.record]: planet zpos = 583940.000000 14:28:13.999 [planetinfo.record]: sun_radius = 124538.743439 14:28:13.999 [planetinfo.record]: sun_vector = 0.189 -0.959 0.214 14:28:13.999 [planetinfo.record]: sun_distance = 667360 14:28:13.999 [planetinfo.record]: corona_flare = 0.077086 14:28:13.999 [planetinfo.record]: corona_hues = 0.587135 14:28:13.999 [planetinfo.record]: sun_color = 0.680726, 0.707874, 0.723825, 1 14:28:13.999 [planetinfo.record]: corona_shimmer = 0.829696 14:28:13.999 [planetinfo.record]: station_vector = -0.635 0.250 0.731 14:28:13.999 [planetinfo.record]: station = coriolis 14:28:19.063 [PLANETINFO OVER]: Done 14:28:19.064 [PLANETINFO LOGGING]: 6 211 14:28:20.068 [planetinfo.record]: seed = 162 16 208 43 14:28:20.068 [planetinfo.record]: coordinates = 16 46 14:28:20.068 [planetinfo.record]: government = 4; 14:28:20.068 [planetinfo.record]: economy = 6; 14:28:20.068 [planetinfo.record]: techlevel = 3; 14:28:20.068 [planetinfo.record]: population = 23; 14:28:20.068 [planetinfo.record]: productivity = 5888; 14:28:20.068 [planetinfo.record]: name = "Maarbi"; 14:28:20.068 [planetinfo.record]: inhabitant = "Small Red Frog"; 14:28:20.068 [planetinfo.record]: inhabitants = "Small Red Frogs"; 14:28:20.068 [planetinfo.record]: description = "The planet Maarbi is most noted for the Maarbiian mountain lobstoid and the Maarbiian evil talking treeoid."; 14:28:20.078 [planetinfo.record]: air_color = 0.529026, 0.989601, 0.996398, 1; 14:28:20.079 [planetinfo.record]: cloud_alpha = 1.000000; 14:28:20.079 [planetinfo.record]: cloud_color = 0.948313, 0.992188, 0.189911, 1; 14:28:20.079 [planetinfo.record]: cloud_fraction = 0.300000; 14:28:20.079 [planetinfo.record]: land_color = 0.598125, 0.66, 0.613594, 1; 14:28:20.079 [planetinfo.record]: land_fraction = 0.360000; 14:28:20.079 [planetinfo.record]: polar_cloud_color = 0.920326, 0.946484, 0.468159, 1; 14:28:20.079 [planetinfo.record]: polar_land_color = 0.912109, 0.934, 0.917582, 1; 14:28:20.079 [planetinfo.record]: polar_sea_color = 0.938281, 0.937471, 0.886419, 1; 14:28:20.079 [planetinfo.record]: sea_color = 0.617188, 0.615055, 0.480731, 1; 14:28:20.079 [planetinfo.record]: rotation_speed = 0.000725 14:28:20.079 [planetinfo.record]: planet zpos = 790720.000000 14:28:20.079 [planetinfo.record]: sun_radius = 141952.365723 14:28:20.079 [planetinfo.record]: sun_vector = 0.658 -0.511 0.553 14:28:20.079 [planetinfo.record]: sun_distance = 903680 14:28:20.079 [planetinfo.record]: corona_flare = 0.028526 14:28:20.079 [planetinfo.record]: corona_hues = 0.802734 14:28:20.079 [planetinfo.record]: sun_color = 0.728131, 0.560274, 0.327465, 1 14:28:20.080 [planetinfo.record]: corona_shimmer = 0.262669 14:28:20.080 [planetinfo.record]: station_vector = 0.779 -0.407 0.476 14:28:20.080 [planetinfo.record]: station = coriolis 14:28:24.954 [PLANETINFO OVER]: Done 14:28:24.955 [PLANETINFO LOGGING]: 6 212 14:28:25.961 [planetinfo.record]: seed = 66 61 180 78 14:28:25.961 [planetinfo.record]: coordinates = 61 166 14:28:25.961 [planetinfo.record]: government = 0; 14:28:25.961 [planetinfo.record]: economy = 6; 14:28:25.961 [planetinfo.record]: techlevel = 2; 14:28:25.961 [planetinfo.record]: population = 15; 14:28:25.961 [planetinfo.record]: productivity = 1920; 14:28:25.961 [planetinfo.record]: name = "Reenri"; 14:28:25.961 [planetinfo.record]: inhabitant = "Yellow Bony Humanoid"; 14:28:25.961 [planetinfo.record]: inhabitants = "Yellow Bony Humanoids"; 14:28:25.961 [planetinfo.record]: description = "The world Reenri is mildly fabled for its inhabitants’ eccentric shyness but cursed by occasional earthquakes."; 14:28:25.969 [planetinfo.record]: air_color = 0.720478, 0.730719, 0.969732, 1; 14:28:25.969 [planetinfo.record]: cloud_alpha = 1.000000; 14:28:25.969 [planetinfo.record]: cloud_color = 0.755341, 0.772487, 0.912109, 1; 14:28:25.969 [planetinfo.record]: cloud_fraction = 0.320000; 14:28:25.969 [planetinfo.record]: land_color = 0.5625, 0.474609, 0.491089, 1; 14:28:25.969 [planetinfo.record]: land_fraction = 0.680000; 14:28:25.969 [planetinfo.record]: polar_cloud_color = 0.812647, 0.823344, 0.910449, 1; 14:28:25.969 [planetinfo.record]: polar_land_color = 0.94375, 0.906885, 0.913797, 1; 14:28:25.969 [planetinfo.record]: polar_sea_color = 0.905664, 0.831689, 0.727008, 1; 14:28:25.969 [planetinfo.record]: sea_color = 0.943359, 0.635144, 0.19899, 1; 14:28:25.969 [planetinfo.record]: rotation_speed = 0.003717 14:28:25.969 [planetinfo.record]: planet zpos = 904540.000000 14:28:25.969 [planetinfo.record]: sun_radius = 199294.679413 14:28:25.969 [planetinfo.record]: sun_vector = 0.833 -0.506 0.225 14:28:25.970 [planetinfo.record]: sun_distance = 1292200 14:28:25.970 [planetinfo.record]: corona_flare = 0.012219 14:28:25.970 [planetinfo.record]: corona_hues = 0.546562 14:28:25.970 [planetinfo.record]: sun_color = 0.841922, 0.491579, 0.343656, 1 14:28:25.970 [planetinfo.record]: corona_shimmer = 0.412622 14:28:25.970 [planetinfo.record]: station_vector = 0.849 0.144 0.509 14:28:25.970 [planetinfo.record]: station = coriolis 14:28:31.888 [PLANETINFO OVER]: Done 14:28:31.889 [PLANETINFO LOGGING]: 6 213 14:28:32.896 [planetinfo.record]: seed = 122 64 64 50 14:28:32.896 [planetinfo.record]: coordinates = 64 190 14:28:32.896 [planetinfo.record]: government = 7; 14:28:32.896 [planetinfo.record]: economy = 6; 14:28:32.896 [planetinfo.record]: techlevel = 5; 14:28:32.896 [planetinfo.record]: population = 34; 14:28:32.896 [planetinfo.record]: productivity = 11968; 14:28:32.896 [planetinfo.record]: name = "Enatzaus"; 14:28:32.896 [planetinfo.record]: inhabitant = "Human Colonial"; 14:28:32.896 [planetinfo.record]: inhabitants = "Human Colonials"; 14:28:32.896 [planetinfo.record]: description = "The world Enatzaus is very noted for its pink oceans but cursed by unpredictable earthquakes."; 14:28:32.916 [planetinfo.record]: air_color = 0.602453, 0.556704, 0.869572, 1; 14:28:32.916 [planetinfo.record]: cloud_alpha = 1.000000; 14:28:32.916 [planetinfo.record]: cloud_color = 0.450698, 0.317604, 0.611328, 1; 14:28:32.916 [planetinfo.record]: cloud_fraction = 0.260000; 14:28:32.916 [planetinfo.record]: land_color = 0.66, 0.4125, 0.470508, 1; 14:28:32.916 [planetinfo.record]: land_fraction = 0.430000; 14:28:32.916 [planetinfo.record]: polar_cloud_color = 0.647809, 0.542341, 0.775098, 1; 14:28:32.916 [planetinfo.record]: polar_land_color = 0.934, 0.846438, 0.86696, 1; 14:28:32.916 [planetinfo.record]: polar_sea_color = 0.91, 0.8645, 0.8918, 1; 14:28:32.916 [planetinfo.record]: sea_color = 0.9, 0.72, 0.828, 1; 14:28:32.917 [planetinfo.record]: rotation_speed = 0.002298 14:28:32.917 [planetinfo.record]: planet zpos = 474880.000000 14:28:32.917 [planetinfo.record]: sun_radius = 97883.339844 14:28:32.917 [planetinfo.record]: sun_vector = -0.385 0.248 -0.889 14:28:32.917 [planetinfo.record]: sun_distance = 712320 14:28:32.917 [planetinfo.record]: corona_flare = 0.080237 14:28:32.917 [planetinfo.record]: corona_hues = 0.672409 14:28:32.917 [planetinfo.record]: sun_color = 0.17638, 0.373519, 0.68334, 1 14:28:32.917 [planetinfo.record]: corona_shimmer = 0.396890 14:28:32.918 [planetinfo.record]: station_vector = -0.340 0.798 0.498 14:28:32.918 [planetinfo.record]: station = coriolis 14:28:37.380 [PLANETINFO OVER]: Done 14:28:37.381 [PLANETINFO LOGGING]: 6 214 14:28:38.383 [planetinfo.record]: seed = 42 8 20 222 14:28:38.383 [planetinfo.record]: coordinates = 8 164 14:28:38.383 [planetinfo.record]: government = 5; 14:28:38.384 [planetinfo.record]: economy = 4; 14:28:38.384 [planetinfo.record]: techlevel = 6; 14:28:38.384 [planetinfo.record]: population = 34; 14:28:38.384 [planetinfo.record]: productivity = 14688; 14:28:38.384 [planetinfo.record]: name = "Riareror"; 14:28:38.384 [planetinfo.record]: inhabitant = "Human Colonial"; 14:28:38.384 [planetinfo.record]: inhabitants = "Human Colonials"; 14:28:38.384 [planetinfo.record]: description = "This world is a revolting little planet."; 14:28:38.391 [planetinfo.record]: air_color = 0.584372, 0.953473, 0.950199, 1; 14:28:38.391 [planetinfo.record]: cloud_alpha = 1.000000; 14:28:38.391 [planetinfo.record]: cloud_color = 0.863281, 0.855694, 0.377686, 1; 14:28:38.391 [planetinfo.record]: cloud_fraction = 0.260000; 14:28:38.391 [planetinfo.record]: land_color = 0.415078, 0.522231, 0.66, 1; 14:28:38.391 [planetinfo.record]: land_fraction = 0.620000; 14:28:38.391 [planetinfo.record]: polar_cloud_color = 0.888477, 0.883596, 0.576122, 1; 14:28:38.391 [planetinfo.record]: polar_land_color = 0.84735, 0.885259, 0.934, 1; 14:28:38.392 [planetinfo.record]: polar_sea_color = 0.935156, 0.905062, 0.883102, 1; 14:28:38.392 [planetinfo.record]: sea_color = 0.648438, 0.564969, 0.504059, 1; 14:28:38.392 [planetinfo.record]: rotation_speed = 0.004086 14:28:38.392 [planetinfo.record]: planet zpos = 704880.000000 14:28:38.392 [planetinfo.record]: sun_radius = 119017.727051 14:28:38.392 [planetinfo.record]: sun_vector = 0.150 -0.978 0.144 14:28:38.392 [planetinfo.record]: sun_distance = 1153440 14:28:38.392 [planetinfo.record]: corona_flare = 0.082620 14:28:38.392 [planetinfo.record]: corona_hues = 0.506035 14:28:38.392 [planetinfo.record]: sun_color = 0.65668, 0.60189, 0.373766, 1 14:28:38.393 [planetinfo.record]: corona_shimmer = 0.424621 14:28:38.393 [planetinfo.record]: station_vector = 0.884 -0.221 0.412 14:28:38.393 [planetinfo.record]: station = coriolis 14:28:43.252 [PLANETINFO OVER]: Done 14:28:43.252 [PLANETINFO LOGGING]: 6 215 14:28:44.259 [planetinfo.record]: seed = 114 217 208 212 14:28:44.259 [planetinfo.record]: coordinates = 217 112 14:28:44.259 [planetinfo.record]: government = 6; 14:28:44.259 [planetinfo.record]: economy = 0; 14:28:44.259 [planetinfo.record]: techlevel = 11; 14:28:44.259 [planetinfo.record]: population = 51; 14:28:44.260 [planetinfo.record]: productivity = 40800; 14:28:44.260 [planetinfo.record]: name = "Raondile"; 14:28:44.260 [planetinfo.record]: inhabitant = "Bug-Eyed Frog"; 14:28:44.260 [planetinfo.record]: inhabitants = "Bug-Eyed Frogs"; 14:28:44.260 [planetinfo.record]: description = "This planet is plagued by deadly earthquakes."; 14:28:44.272 [planetinfo.record]: air_color = 0.575529, 0.56748, 0.843557, 1; 14:28:44.272 [planetinfo.record]: cloud_alpha = 1.000000; 14:28:44.272 [planetinfo.record]: cloud_color = 0.349817, 0.329086, 0.533203, 1; 14:28:44.272 [planetinfo.record]: cloud_fraction = 0.450000; 14:28:44.272 [planetinfo.record]: land_color = 0.66, 0.438442, 0.0928125, 1; 14:28:44.272 [planetinfo.record]: land_fraction = 0.540000; 14:28:44.272 [planetinfo.record]: polar_cloud_color = 0.580885, 0.562905, 0.739941, 1; 14:28:44.272 [planetinfo.record]: polar_land_color = 0.934, 0.855616, 0.733336, 1; 14:28:44.272 [planetinfo.record]: polar_sea_color = 0.946875, 0.891486, 0.909661, 1; 14:28:44.272 [planetinfo.record]: sea_color = 0.53125, 0.406946, 0.447733, 1; 14:28:44.272 [planetinfo.record]: rotation_speed = 0.003958 14:28:44.272 [planetinfo.record]: planet zpos = 567980.000000 14:28:44.272 [planetinfo.record]: sun_radius = 119735.233459 14:28:44.272 [planetinfo.record]: sun_vector = 0.234 0.057 -0.971 14:28:44.273 [planetinfo.record]: sun_distance = 730260 14:28:44.273 [planetinfo.record]: corona_flare = 0.055298 14:28:44.273 [planetinfo.record]: corona_hues = 0.593285 14:28:44.273 [planetinfo.record]: sun_color = 0.290865, 0.408483, 0.699817, 1 14:28:44.273 [planetinfo.record]: corona_shimmer = 0.291005 14:28:44.273 [planetinfo.record]: station_vector = 0.583 -0.127 0.802 14:28:44.273 [planetinfo.record]: station = icosahedron 14:28:49.373 [PLANETINFO OVER]: Done 14:28:49.373 [PLANETINFO LOGGING]: 6 216 14:28:50.392 [planetinfo.record]: seed = 50 193 148 173 14:28:50.392 [planetinfo.record]: coordinates = 193 205 14:28:50.392 [planetinfo.record]: government = 6; 14:28:50.392 [planetinfo.record]: economy = 5; 14:28:50.392 [planetinfo.record]: techlevel = 6; 14:28:50.392 [planetinfo.record]: population = 36; 14:28:50.392 [planetinfo.record]: productivity = 14400; 14:28:50.392 [planetinfo.record]: name = "Ditearra"; 14:28:50.392 [planetinfo.record]: inhabitant = "Harmless Fat Humanoid"; 14:28:50.392 [planetinfo.record]: inhabitants = "Harmless Fat Humanoids"; 14:28:50.392 [planetinfo.record]: description = "The world Ditearra is reasonably noted for its inhabitants’ exceptional loathing of casinos."; 14:28:50.408 [planetinfo.record]: air_color = 0.704695, 0.814183, 0.9164, 1; 14:28:50.408 [planetinfo.record]: cloud_alpha = 1.000000; 14:28:50.408 [planetinfo.record]: cloud_color = 0.681458, 0.751953, 0.713952, 1; 14:28:50.408 [planetinfo.record]: cloud_fraction = 0.300000; 14:28:50.408 [planetinfo.record]: land_color = 0.311953, 0.66, 0.53764, 1; 14:28:50.408 [planetinfo.record]: land_fraction = 0.380000; 14:28:50.408 [planetinfo.record]: polar_cloud_color = 0.789255, 0.838379, 0.811898, 1; 14:28:50.408 [planetinfo.record]: polar_land_color = 0.810865, 0.934, 0.89071, 1; 14:28:50.408 [planetinfo.record]: polar_sea_color = 0.911875, 0.93418, 0.882271, 1; 14:28:50.408 [planetinfo.record]: sea_color = 0.595342, 0.658203, 0.511907, 1; 14:28:50.408 [planetinfo.record]: rotation_speed = 0.004405 14:28:50.408 [planetinfo.record]: planet zpos = 697070.000000 14:28:50.408 [planetinfo.record]: sun_radius = 158111.708374 14:28:50.408 [planetinfo.record]: sun_vector = 0.249 0.899 0.359 14:28:50.408 [planetinfo.record]: sun_distance = 1330770 14:28:50.408 [planetinfo.record]: corona_flare = 0.066096 14:28:50.408 [planetinfo.record]: corona_hues = 0.908745 14:28:50.408 [planetinfo.record]: sun_color = 0.754282, 0.601074, 0.402102, 1 14:28:50.409 [planetinfo.record]: corona_shimmer = 0.789400 14:28:50.409 [planetinfo.record]: station_vector = -0.352 0.754 0.555 14:28:50.409 [planetinfo.record]: station = coriolis 14:28:56.216 [PLANETINFO OVER]: Done 14:28:56.217 [PLANETINFO LOGGING]: 6 217 14:28:57.221 [planetinfo.record]: seed = 138 148 128 123 14:28:57.221 [planetinfo.record]: coordinates = 148 170 14:28:57.221 [planetinfo.record]: government = 1; 14:28:57.221 [planetinfo.record]: economy = 2; 14:28:57.221 [planetinfo.record]: techlevel = 6; 14:28:57.221 [planetinfo.record]: population = 28; 14:28:57.221 [planetinfo.record]: productivity = 8960; 14:28:57.221 [planetinfo.record]: name = "Anquarle"; 14:28:57.221 [planetinfo.record]: inhabitant = "Blue Frog"; 14:28:57.221 [planetinfo.record]: inhabitants = "Blue Frogs"; 14:28:57.221 [planetinfo.record]: description = "Anquarle is reasonably notable for its weird tropical forests but scourged by frequent civil war."; 14:28:57.231 [planetinfo.record]: air_color = 0.603775, 0.932064, 0.945018, 1; 14:28:57.231 [planetinfo.record]: cloud_alpha = 1.000000; 14:28:57.231 [planetinfo.record]: cloud_color = 0.787159, 0.837891, 0.432037, 1; 14:28:57.231 [planetinfo.record]: cloud_fraction = 0.550000; 14:28:57.231 [planetinfo.record]: land_color = 0.382449, 0.66, 0.324844, 1; 14:28:57.231 [planetinfo.record]: land_fraction = 0.390000; 14:28:57.231 [planetinfo.record]: polar_cloud_color = 0.843862, 0.877051, 0.611537, 1; 14:28:57.231 [planetinfo.record]: polar_land_color = 0.835806, 0.934, 0.815426, 1; 14:28:57.231 [planetinfo.record]: polar_sea_color = 0.909661, 0.932031, 0.876965, 1; 14:28:57.231 [planetinfo.record]: sea_color = 0.614432, 0.679688, 0.519058, 1; 14:28:57.231 [planetinfo.record]: rotation_speed = 0.000469 14:28:57.231 [planetinfo.record]: planet zpos = 693600.000000 14:28:57.231 [planetinfo.record]: sun_radius = 128141.442871 14:28:57.231 [planetinfo.record]: sun_vector = 0.376 -0.857 -0.353 14:28:57.231 [planetinfo.record]: sun_distance = 1156000 14:28:57.232 [planetinfo.record]: corona_flare = 0.079135 14:28:57.232 [planetinfo.record]: corona_hues = 0.532021 14:28:57.232 [planetinfo.record]: sun_color = 0.667623, 0.754612, 0.762363, 1 14:28:57.232 [planetinfo.record]: corona_shimmer = 0.607265 14:28:57.232 [planetinfo.record]: station_vector = -0.481 0.674 0.561 14:28:57.232 [planetinfo.record]: station = coriolis 14:29:02.518 [PLANETINFO OVER]: Done 14:29:02.518 [PLANETINFO LOGGING]: 6 218 14:29:03.522 [planetinfo.record]: seed = 90 1 52 135 14:29:03.522 [planetinfo.record]: coordinates = 1 202 14:29:03.522 [planetinfo.record]: government = 3; 14:29:03.522 [planetinfo.record]: economy = 2; 14:29:03.522 [planetinfo.record]: techlevel = 8; 14:29:03.522 [planetinfo.record]: population = 38; 14:29:03.522 [planetinfo.record]: productivity = 17024; 14:29:03.522 [planetinfo.record]: name = "Sobeteve"; 14:29:03.522 [planetinfo.record]: inhabitant = "Human Colonial"; 14:29:03.522 [planetinfo.record]: inhabitants = "Human Colonials"; 14:29:03.522 [planetinfo.record]: description = "The world Sobeteve is beset by dreadful earthquakes."; 14:29:03.535 [planetinfo.record]: air_color = 0.457194, 0.595165, 0.896238, 1; 14:29:03.535 [planetinfo.record]: cloud_alpha = 1.000000; 14:29:03.535 [planetinfo.record]: cloud_color = 0.0891266, 0.649058, 0.691406, 1; 14:29:03.535 [planetinfo.record]: cloud_fraction = 0.320000; 14:29:03.535 [planetinfo.record]: land_color = 0.66, 0.482975, 0.177891, 1; 14:29:03.535 [planetinfo.record]: land_fraction = 0.640000; 14:29:03.535 [planetinfo.record]: polar_cloud_color = 0.369525, 0.780082, 0.811133, 1; 14:29:03.535 [planetinfo.record]: polar_land_color = 0.934, 0.871371, 0.763436, 1; 14:29:03.535 [planetinfo.record]: polar_sea_color = 0.925647, 0.938477, 0.890178, 1; 14:29:03.535 [planetinfo.record]: sea_color = 0.581593, 0.615234, 0.488583, 1; 14:29:03.536 [planetinfo.record]: rotation_speed = 0.004671 14:29:03.536 [planetinfo.record]: planet zpos = 645260.000000 14:29:03.536 [planetinfo.record]: sun_radius = 103145.504150 14:29:03.537 [planetinfo.record]: sun_vector = -0.924 0.217 0.315 14:29:03.537 [planetinfo.record]: sun_distance = 829620 14:29:03.537 [planetinfo.record]: corona_flare = 0.017053 14:29:03.537 [planetinfo.record]: corona_hues = 0.937096 14:29:03.537 [planetinfo.record]: sun_color = 0.757483, 0.656522, 0.574331, 1 14:29:03.538 [planetinfo.record]: corona_shimmer = 0.333272 14:29:03.538 [planetinfo.record]: station_vector = 0.276 0.590 0.759 14:29:03.538 [planetinfo.record]: station = coriolis 14:29:08.137 [PLANETINFO OVER]: Done 14:29:08.138 [PLANETINFO LOGGING]: 6 219 14:29:09.143 [planetinfo.record]: seed = 194 182 80 230 14:29:09.143 [planetinfo.record]: coordinates = 182 220 14:29:09.143 [planetinfo.record]: government = 0; 14:29:09.143 [planetinfo.record]: economy = 6; 14:29:09.143 [planetinfo.record]: techlevel = 3; 14:29:09.143 [planetinfo.record]: population = 19; 14:29:09.143 [planetinfo.record]: productivity = 2432; 14:29:09.143 [planetinfo.record]: name = "Biorve"; 14:29:09.143 [planetinfo.record]: inhabitant = "Human Colonial"; 14:29:09.143 [planetinfo.record]: inhabitants = "Human Colonials"; 14:29:09.143 [planetinfo.record]: description = "This planet is notable for the Biorveian edible poet and its exciting sit coms."; 14:29:09.144 [planetinfo.record]: air_color = 0.506043, 0.536468, 0.885832, 1; 14:29:09.144 [planetinfo.record]: cloud_alpha = 1.000000; 14:29:09.144 [planetinfo.record]: cloud_color = 0.206299, 0.309126, 0.660156, 1; 14:29:09.144 [planetinfo.record]: cloud_fraction = 0.450000; 14:29:09.144 [planetinfo.record]: land_color = 0.400498, 0.357658, 0.505859, 1; 14:29:09.144 [planetinfo.record]: land_fraction = 0.360000; 14:29:09.144 [planetinfo.record]: polar_cloud_color = 0.454579, 0.532175, 0.79707, 1; 14:29:09.144 [planetinfo.record]: polar_land_color = 0.899978, 0.879877, 0.949414, 1; 14:29:09.144 [planetinfo.record]: polar_sea_color = 0.855917, 0.918555, 0.844999, 1; 14:29:09.144 [planetinfo.record]: sea_color = 0.592298, 0.814453, 0.553574, 1; 14:29:09.144 [planetinfo.record]: rotation_speed = 0.002085 14:29:09.144 [planetinfo.record]: planet zpos = 544080.000000 14:29:09.144 [planetinfo.record]: sun_radius = 97426.068420 14:29:09.144 [planetinfo.record]: sun_vector = -0.212 0.355 -0.910 14:29:09.144 [planetinfo.record]: sun_distance = 906800 14:29:09.145 [planetinfo.record]: corona_flare = 0.094370 14:29:09.145 [planetinfo.record]: corona_hues = 0.645935 14:29:09.145 [planetinfo.record]: sun_color = 0.271017, 0.529888, 0.777133, 1 14:29:09.145 [planetinfo.record]: corona_shimmer = 1.033593 14:29:09.145 [planetinfo.record]: station_vector = 0.593 -0.798 0.108 14:29:09.145 [planetinfo.record]: station = coriolis 14:29:13.893 [PLANETINFO OVER]: Done 14:29:13.895 [PLANETINFO LOGGING]: 6 220 14:29:14.916 [planetinfo.record]: seed = 162 117 244 4 14:29:14.916 [planetinfo.record]: coordinates = 117 22 14:29:14.916 [planetinfo.record]: government = 4; 14:29:14.916 [planetinfo.record]: economy = 6; 14:29:14.916 [planetinfo.record]: techlevel = 4; 14:29:14.916 [planetinfo.record]: population = 27; 14:29:14.916 [planetinfo.record]: productivity = 6912; 14:29:14.916 [planetinfo.record]: name = "Zaerma"; 14:29:14.916 [planetinfo.record]: inhabitant = "Fierce Green Bony Lobster"; 14:29:14.917 [planetinfo.record]: inhabitants = "Fierce Green Bony Lobsters"; 14:29:14.917 [planetinfo.record]: description = "The world Zaerma is a dull world."; 14:29:14.936 [planetinfo.record]: air_color = 0.560927, 0.580893, 0.837053, 1; 14:29:14.936 [planetinfo.record]: cloud_alpha = 1.000000; 14:29:14.936 [planetinfo.record]: cloud_color = 0.313019, 0.353776, 0.513672, 1; 14:29:14.936 [planetinfo.record]: cloud_fraction = 0.280000; 14:29:14.936 [planetinfo.record]: land_color = 0.66, 0.560742, 0.495, 1; 14:29:14.936 [planetinfo.record]: land_fraction = 0.500000; 14:29:14.936 [planetinfo.record]: polar_cloud_color = 0.552648, 0.588907, 0.731152, 1; 14:29:14.936 [planetinfo.record]: polar_land_color = 0.934, 0.898884, 0.875625, 1; 14:29:14.936 [planetinfo.record]: polar_sea_color = 0.718296, 0.833711, 0.926367, 1; 14:29:14.937 [planetinfo.record]: sea_color = 0.0747833, 0.441734, 0.736328, 1; 14:29:14.937 [planetinfo.record]: rotation_speed = 0.004941 14:29:14.937 [planetinfo.record]: planet zpos = 514410.000000 14:29:14.937 [planetinfo.record]: sun_radius = 107972.193604 14:29:14.937 [planetinfo.record]: sun_vector = 0.109 0.289 -0.951 14:29:14.937 [planetinfo.record]: sun_distance = 791400 14:29:14.937 [planetinfo.record]: corona_flare = 0.027565 14:29:14.937 [planetinfo.record]: corona_hues = 0.585503 14:29:14.937 [planetinfo.record]: sun_color = 0.721317, 0.412504, 0.378445, 1 14:29:14.937 [planetinfo.record]: corona_shimmer = 0.250800 14:29:14.938 [planetinfo.record]: station_vector = -0.216 0.105 0.971 14:29:14.938 [planetinfo.record]: station = coriolis 14:29:19.027 [PLANETINFO OVER]: Done 14:29:19.028 [PLANETINFO LOGGING]: 6 221 14:29:20.032 [planetinfo.record]: seed = 26 161 64 61 14:29:20.033 [planetinfo.record]: coordinates = 161 11 14:29:20.033 [planetinfo.record]: government = 3; 14:29:20.033 [planetinfo.record]: economy = 3; 14:29:20.033 [planetinfo.record]: techlevel = 7; 14:29:20.033 [planetinfo.record]: population = 35; 14:29:20.033 [planetinfo.record]: productivity = 13720; 14:29:20.033 [planetinfo.record]: name = "Isesusa"; 14:29:20.033 [planetinfo.record]: inhabitant = "Human Colonial"; 14:29:20.033 [planetinfo.record]: inhabitants = "Human Colonials"; 14:29:20.033 [planetinfo.record]: description = "This world is plagued by occasional solar activity."; 14:29:20.055 [planetinfo.record]: air_color = 0.540987, 0.558916, 0.857215, 1; 14:29:20.055 [planetinfo.record]: cloud_alpha = 1.000000; 14:29:20.055 [planetinfo.record]: cloud_color = 0.282623, 0.328185, 0.574219, 1; 14:29:20.055 [planetinfo.record]: cloud_fraction = 0.380000; 14:29:20.055 [planetinfo.record]: land_color = 0.16613, 0.471234, 0.654297, 1; 14:29:20.055 [planetinfo.record]: land_fraction = 0.540000; 14:29:20.055 [planetinfo.record]: polar_cloud_color = 0.517696, 0.555306, 0.758398, 1; 14:29:20.055 [planetinfo.record]: polar_land_color = 0.760251, 0.869201, 0.93457, 1; 14:29:20.055 [planetinfo.record]: polar_sea_color = 0.928906, 0.871615, 0.843636, 1; 14:29:20.056 [planetinfo.record]: sea_color = 0.710938, 0.535546, 0.44989, 1; 14:29:20.056 [planetinfo.record]: rotation_speed = 0.003568 14:29:20.056 [planetinfo.record]: planet zpos = 819650.000000 14:29:20.056 [planetinfo.record]: sun_radius = 138149.884796 14:29:20.057 [planetinfo.record]: sun_vector = -0.220 -0.398 0.890 14:29:20.057 [planetinfo.record]: sun_distance = 1197950 14:29:20.057 [planetinfo.record]: corona_flare = 0.078247 14:29:20.057 [planetinfo.record]: corona_hues = 0.792076 14:29:20.057 [planetinfo.record]: sun_color = 0.728933, 0.72902, 0.7293, 1 14:29:20.057 [planetinfo.record]: corona_shimmer = 0.329254 14:29:20.057 [planetinfo.record]: station_vector = 0.601 0.338 0.725 14:29:20.064 [planetinfo.record]: station = coriolis 14:29:25.221 [PLANETINFO OVER]: Done 14:29:25.223 [PLANETINFO LOGGING]: 6 222 14:29:26.242 [planetinfo.record]: seed = 10 239 212 160 14:29:26.242 [planetinfo.record]: coordinates = 239 200 14:29:26.242 [planetinfo.record]: government = 1; 14:29:26.242 [planetinfo.record]: economy = 2; 14:29:26.242 [planetinfo.record]: techlevel = 9; 14:29:26.242 [planetinfo.record]: population = 40; 14:29:26.242 [planetinfo.record]: productivity = 12800; 14:29:26.242 [planetinfo.record]: name = "Tiso"; 14:29:26.243 [planetinfo.record]: inhabitant = "Large Harmless Insect"; 14:29:26.243 [planetinfo.record]: inhabitants = "Large Harmless Insects"; 14:29:26.243 [planetinfo.record]: description = "The world Tiso is beset by lethal disease."; 14:29:26.256 [planetinfo.record]: air_color = 0.653029, 0.882398, 0.912498, 1; 14:29:26.260 [planetinfo.record]: cloud_alpha = 1.000000; 14:29:26.260 [planetinfo.record]: cloud_color = 0.67269, 0.740234, 0.552284, 1; 14:29:26.260 [planetinfo.record]: cloud_fraction = 0.300000; 14:29:26.260 [planetinfo.record]: land_color = 0.0232031, 0.421201, 0.66, 1; 14:29:26.260 [planetinfo.record]: land_fraction = 0.710000; 14:29:26.260 [planetinfo.record]: polar_cloud_color = 0.785594, 0.833105, 0.700899, 1; 14:29:26.260 [planetinfo.record]: polar_land_color = 0.708709, 0.849516, 0.934, 1; 14:29:26.260 [planetinfo.record]: polar_sea_color = 0.942383, 0.934891, 0.894435, 1; 14:29:26.260 [planetinfo.record]: sea_color = 0.576172, 0.55785, 0.458912, 1; 14:29:26.267 [planetinfo.record]: rotation_speed = 0.000344 14:29:26.267 [planetinfo.record]: planet zpos = 458250.000000 14:29:26.268 [planetinfo.record]: sun_radius = 57490.087891 14:29:26.268 [planetinfo.record]: sun_vector = 0.403 0.056 0.914 14:29:26.268 [planetinfo.record]: sun_distance = 641550 14:29:26.268 [planetinfo.record]: corona_flare = 0.027649 14:29:26.268 [planetinfo.record]: corona_hues = 0.991463 14:29:26.268 [planetinfo.record]: sun_color = 0.812808, 0.789995, 0.525065, 1 14:29:26.268 [planetinfo.record]: corona_shimmer = 0.345891 14:29:26.268 [planetinfo.record]: station_vector = 0.973 0.172 0.151 14:29:26.268 [planetinfo.record]: station = coriolis 14:29:32.440 [PLANETINFO OVER]: Done 14:29:32.441 [PLANETINFO LOGGING]: 6 223 14:29:33.454 [planetinfo.record]: seed = 146 224 80 32 14:29:33.455 [planetinfo.record]: coordinates = 224 231 14:29:33.455 [planetinfo.record]: government = 2; 14:29:33.455 [planetinfo.record]: economy = 7; 14:29:33.455 [planetinfo.record]: techlevel = 1; 14:29:33.455 [planetinfo.record]: population = 14; 14:29:33.455 [planetinfo.record]: productivity = 2016; 14:29:33.455 [planetinfo.record]: name = "Usesen"; 14:29:33.455 [planetinfo.record]: inhabitant = "Human Colonial"; 14:29:33.455 [planetinfo.record]: inhabitants = "Human Colonials"; 14:29:33.455 [planetinfo.record]: description = "Usesen is a revolting little planet."; 14:29:33.476 [planetinfo.record]: air_color = 0.774393, 0.730633, 0.988594, 1; 14:29:33.478 [planetinfo.record]: cloud_alpha = 1.000000; 14:29:33.479 [planetinfo.record]: cloud_color = 0.88399, 0.790894, 0.96875, 1; 14:29:33.479 [planetinfo.record]: cloud_fraction = 0.150000; 14:29:33.479 [planetinfo.record]: land_color = 0.66, 0.362549, 0.293906, 1; 14:29:33.479 [planetinfo.record]: land_fraction = 0.240000; 14:29:33.479 [planetinfo.record]: polar_cloud_color = 0.884757, 0.828542, 0.935938, 1; 14:29:33.479 [planetinfo.record]: polar_land_color = 0.934, 0.828765, 0.80448, 1; 14:29:33.479 [planetinfo.record]: polar_sea_color = 0.823597, 0.856344, 0.943359, 1; 14:29:33.479 [planetinfo.record]: sea_color = 0.278778, 0.357426, 0.566406, 1; 14:29:33.479 [planetinfo.record]: rotation_speed = 0.000126 14:29:33.479 [planetinfo.record]: planet zpos = 395200.000000 14:29:33.479 [planetinfo.record]: sun_radius = 89832.055664 14:29:33.479 [planetinfo.record]: sun_vector = 0.884 0.251 0.394 14:29:33.479 [planetinfo.record]: sun_distance = 638400 14:29:33.479 [planetinfo.record]: corona_flare = 0.074794 14:29:33.479 [planetinfo.record]: corona_hues = 0.559952 14:29:33.479 [planetinfo.record]: sun_color = 0.350299, 0.492926, 0.782227, 1 14:29:33.480 [planetinfo.record]: corona_shimmer = 0.403805 14:29:33.480 [planetinfo.record]: station_vector = -0.252 0.966 0.058 14:29:33.480 [planetinfo.record]: station = coriolis 14:29:39.316 [PLANETINFO OVER]: Done 14:29:40.061 [PLANETINFO LOGGING]: 6 224 14:29:41.390 [planetinfo.record]: seed = 146 242 212 196 14:29:41.390 [planetinfo.record]: coordinates = 242 233 14:29:41.390 [planetinfo.record]: government = 2; 14:29:41.391 [planetinfo.record]: economy = 1; 14:29:41.391 [planetinfo.record]: techlevel = 9; 14:29:41.391 [planetinfo.record]: population = 40; 14:29:41.391 [planetinfo.record]: productivity = 17280; 14:29:41.391 [planetinfo.record]: name = "Zaed"; 14:29:41.391 [planetinfo.record]: inhabitant = "Fierce Bony Lobster"; 14:29:41.391 [planetinfo.record]: inhabitants = "Fierce Bony Lobsters"; 14:29:41.391 [planetinfo.record]: description = "This planet is mildly fabled for its inhabitants’ eccentric love for tourists but beset by lethal disease."; 14:29:42.002 [planetinfo.record]: air_color = 0.704346, 0.783091, 0.943066, 1; 14:29:42.002 [planetinfo.record]: cloud_alpha = 1.000000; 14:29:42.048 [planetinfo.record]: cloud_color = 0.698776, 0.828908, 0.832031, 1; 14:29:42.048 [planetinfo.record]: cloud_fraction = 0.450000; 14:29:42.048 [planetinfo.record]: land_color = 0.581045, 0.66, 0.45375, 1; 14:29:42.048 [planetinfo.record]: land_fraction = 0.620000; 14:29:42.048 [planetinfo.record]: polar_cloud_color = 0.786887, 0.872363, 0.874414, 1; 14:29:42.048 [planetinfo.record]: polar_land_color = 0.906067, 0.934, 0.861031, 1; 14:29:42.048 [planetinfo.record]: polar_sea_color = 0.874389, 0.923828, 0.916876, 1; 14:29:42.048 [planetinfo.record]: sea_color = 0.598663, 0.761719, 0.738789, 1; 14:29:42.048 [planetinfo.record]: rotation_speed = 0.000673 14:29:42.048 [planetinfo.record]: planet zpos = 489840.000000 14:29:42.048 [planetinfo.record]: sun_radius = 84593.619995 14:29:42.048 [planetinfo.record]: sun_vector = 0.324 0.923 -0.210 14:29:42.048 [planetinfo.record]: sun_distance = 775580 14:29:42.048 [planetinfo.record]: corona_flare = 0.093217 14:29:42.048 [planetinfo.record]: corona_hues = 0.949585 14:29:42.048 [planetinfo.record]: sun_color = 0.245806, 0.344184, 0.697653, 1 14:29:42.402 [planetinfo.record]: corona_shimmer = 0.317584 14:29:42.490 [planetinfo.record]: station_vector = -0.550 0.835 0.007 14:29:42.490 [planetinfo.record]: station = coriolis 14:29:48.297 [PLANETINFO OVER]: Done 14:29:48.298 [PLANETINFO LOGGING]: 6 225 14:29:49.315 [planetinfo.record]: seed = 42 190 128 183 14:29:49.315 [planetinfo.record]: coordinates = 190 88 14:29:49.315 [planetinfo.record]: government = 5; 14:29:49.315 [planetinfo.record]: economy = 0; 14:29:49.315 [planetinfo.record]: techlevel = 12; 14:29:49.315 [planetinfo.record]: population = 54; 14:29:49.315 [planetinfo.record]: productivity = 38880; 14:29:49.315 [planetinfo.record]: name = "Tiregees"; 14:29:49.315 [planetinfo.record]: inhabitant = "Harmless Frog"; 14:29:49.315 [planetinfo.record]: inhabitants = "Harmless Frogs"; 14:29:49.315 [planetinfo.record]: description = "The world Tiregees is reasonably noted for its inhabitants’ exceptional love for tourists and the Tiregeesian edible arts graduate."; 14:29:49.317 [planetinfo.record]: air_color = 0.41989, 0.796011, 0.86502, 1; 14:29:49.317 [planetinfo.record]: cloud_alpha = 1.000000; 14:29:49.317 [planetinfo.record]: cloud_color = 0.328412, 0.597656, 0.0326843, 1; 14:29:49.317 [planetinfo.record]: cloud_fraction = 0.460000; 14:29:49.317 [planetinfo.record]: land_color = 0.0876563, 0.405128, 0.66, 1; 14:29:49.317 [planetinfo.record]: land_fraction = 0.450000; 14:29:49.317 [planetinfo.record]: polar_cloud_color = 0.552439, 0.768945, 0.314637, 1; 14:29:49.317 [planetinfo.record]: polar_land_color = 0.731512, 0.843829, 0.934, 1; 14:29:49.317 [planetinfo.record]: polar_sea_color = 0.937695, 0.93437, 0.884492, 1; 14:29:49.317 [planetinfo.record]: sea_color = 0.623047, 0.614209, 0.481644, 1; 14:29:49.317 [planetinfo.record]: rotation_speed = 0.001753 14:29:49.317 [planetinfo.record]: planet zpos = 671720.000000 14:29:49.317 [planetinfo.record]: sun_radius = 120952.267761 14:29:49.317 [planetinfo.record]: sun_vector = -0.616 -0.593 -0.519 14:29:49.317 [planetinfo.record]: sun_distance = 863640 14:29:49.317 [planetinfo.record]: corona_flare = 0.019542 14:29:49.317 [planetinfo.record]: corona_hues = 0.623482 14:29:49.317 [planetinfo.record]: sun_color = 0.31102, 0.384144, 0.664883, 1 14:29:49.317 [planetinfo.record]: corona_shimmer = 0.458146 14:29:49.317 [planetinfo.record]: station_vector = -0.510 -0.853 0.112 14:29:49.317 [planetinfo.record]: station = dodecahedron 14:29:54.120 [PLANETINFO OVER]: Done 14:29:54.121 [PLANETINFO LOGGING]: 6 226 14:29:55.124 [planetinfo.record]: seed = 58 201 244 218 14:29:55.124 [planetinfo.record]: coordinates = 201 67 14:29:55.124 [planetinfo.record]: government = 7; 14:29:55.124 [planetinfo.record]: economy = 3; 14:29:55.124 [planetinfo.record]: techlevel = 9; 14:29:55.124 [planetinfo.record]: population = 47; 14:29:55.124 [planetinfo.record]: productivity = 28952; 14:29:55.124 [planetinfo.record]: name = "Qusoin"; 14:29:55.124 [planetinfo.record]: inhabitant = "Horned Bird"; 14:29:55.125 [planetinfo.record]: inhabitants = "Horned Birds"; 14:29:55.125 [planetinfo.record]: description = "This planet is beset by evil disease."; 14:29:55.126 [planetinfo.record]: air_color = 0.722162, 0.556488, 0.911848, 1; 14:29:55.126 [planetinfo.record]: cloud_alpha = 1.000000; 14:29:55.126 [planetinfo.record]: cloud_color = 0.738281, 0.31723, 0.56394, 1; 14:29:55.126 [planetinfo.record]: cloud_fraction = 0.300000; 14:29:55.126 [planetinfo.record]: land_color = 0.192261, 0.362632, 0.65625, 1; 14:29:55.126 [planetinfo.record]: land_fraction = 0.440000; 14:29:55.126 [planetinfo.record]: polar_cloud_color = 0.832227, 0.535583, 0.709398, 1; 14:29:55.126 [planetinfo.record]: polar_land_color = 0.769217, 0.829861, 0.934375, 1; 14:29:55.126 [planetinfo.record]: polar_sea_color = 0.932227, 0.863913, 0.854844, 1; 14:29:55.126 [planetinfo.record]: sea_color = 0.677734, 0.479076, 0.452705, 1; 14:29:55.126 [planetinfo.record]: rotation_speed = 0.000990 14:29:55.126 [planetinfo.record]: planet zpos = 669240.000000 14:29:55.126 [planetinfo.record]: sun_radius = 121330.555573 14:29:55.126 [planetinfo.record]: sun_vector = -0.057 0.998 0.012 14:29:55.127 [planetinfo.record]: sun_distance = 1338480 14:29:55.127 [planetinfo.record]: corona_flare = 0.065143 14:29:55.127 [planetinfo.record]: corona_hues = 0.844955 14:29:55.127 [planetinfo.record]: sun_color = 0.839893, 0.831126, 0.621109, 1 14:29:55.127 [planetinfo.record]: corona_shimmer = 0.599597 14:29:55.127 [planetinfo.record]: station_vector = -0.747 -0.491 0.448 14:29:55.127 [planetinfo.record]: station = coriolis 14:30:00.334 [PLANETINFO OVER]: Done 14:30:00.335 [PLANETINFO LOGGING]: 6 227 14:30:01.338 [planetinfo.record]: seed = 226 78 208 194 14:30:01.338 [planetinfo.record]: coordinates = 78 140 14:30:01.338 [planetinfo.record]: government = 4; 14:30:01.338 [planetinfo.record]: economy = 4; 14:30:01.338 [planetinfo.record]: techlevel = 7; 14:30:01.339 [planetinfo.record]: population = 37; 14:30:01.339 [planetinfo.record]: productivity = 14208; 14:30:01.339 [planetinfo.record]: name = "Xeisa"; 14:30:01.339 [planetinfo.record]: inhabitant = "Large Horned Bird"; 14:30:01.339 [planetinfo.record]: inhabitants = "Large Horned Birds"; 14:30:01.339 [planetinfo.record]: description = "Xeisa is most noted for the Xeisaian deadly lobstoid and its pink Xeisaian Enst Enstweed plantations."; 14:30:01.342 [planetinfo.record]: air_color = 0.475671, 0.711037, 0.842256, 1; 14:30:01.342 [planetinfo.record]: cloud_alpha = 1.000000; 14:30:01.342 [planetinfo.record]: cloud_color = 0.155067, 0.529297, 0.187228, 1; 14:30:01.342 [planetinfo.record]: cloud_fraction = 0.320000; 14:30:01.342 [planetinfo.record]: land_color = 0.433753, 0.289536, 0.644531, 1; 14:30:01.342 [planetinfo.record]: land_fraction = 0.410000; 14:30:01.342 [planetinfo.record]: polar_cloud_color = 0.411984, 0.738184, 0.440017, 1; 14:30:01.342 [planetinfo.record]: polar_land_color = 0.85906, 0.806726, 0.935547, 1; 14:30:01.342 [planetinfo.record]: polar_sea_color = 0.939062, 0.923, 0.884956, 1; 14:30:01.342 [planetinfo.record]: sea_color = 0.609375, 0.567681, 0.468933, 1; 14:30:01.342 [planetinfo.record]: rotation_speed = 0.003303 14:30:01.342 [planetinfo.record]: planet zpos = 306540.000000 14:30:01.342 [planetinfo.record]: sun_radius = 72030.850525 14:30:01.342 [planetinfo.record]: sun_vector = 0.321 -0.594 0.738 14:30:01.342 [planetinfo.record]: sun_distance = 681200 14:30:01.342 [planetinfo.record]: corona_flare = 0.085484 14:30:01.342 [planetinfo.record]: corona_hues = 0.990608 14:30:01.342 [planetinfo.record]: sun_color = 0.644904, 0.686369, 0.784, 1 14:30:01.342 [planetinfo.record]: corona_shimmer = 0.411059 14:30:01.343 [planetinfo.record]: station_vector = -0.033 0.780 0.625 14:30:01.343 [planetinfo.record]: station = coriolis 14:30:06.138 [PLANETINFO OVER]: Done 14:30:06.139 [PLANETINFO LOGGING]: 6 228 14:30:07.157 [planetinfo.record]: seed = 2 16 52 93 14:30:07.157 [planetinfo.record]: coordinates = 16 175 14:30:07.157 [planetinfo.record]: government = 0; 14:30:07.157 [planetinfo.record]: economy = 7; 14:30:07.157 [planetinfo.record]: techlevel = 0; 14:30:07.157 [planetinfo.record]: population = 8; 14:30:07.157 [planetinfo.record]: productivity = 768; 14:30:07.157 [planetinfo.record]: name = "Isteesge"; 14:30:07.157 [planetinfo.record]: inhabitant = "Human Colonial"; 14:30:07.157 [planetinfo.record]: inhabitants = "Human Colonials"; 14:30:07.158 [planetinfo.record]: description = "The world Isteesge is scourged by deadly disease."; 14:30:07.159 [planetinfo.record]: air_color = 0.658315, 0.833839, 0.945668, 1; 14:30:07.159 [planetinfo.record]: cloud_alpha = 1.000000; 14:30:07.159 [planetinfo.record]: cloud_color = 0.577393, 0.839844, 0.624552, 1; 14:30:07.159 [planetinfo.record]: cloud_fraction = 0.250000; 14:30:07.159 [planetinfo.record]: land_color = 0.652668, 0.66, 0.525937, 1; 14:30:07.159 [planetinfo.record]: land_fraction = 0.370000; 14:30:07.159 [planetinfo.record]: polar_cloud_color = 0.706459, 0.87793, 0.73727, 1; 14:30:07.159 [planetinfo.record]: polar_land_color = 0.931406, 0.934, 0.88657, 1; 14:30:07.159 [planetinfo.record]: polar_sea_color = 0.863352, 0.903163, 0.916992, 1; 14:30:07.159 [planetinfo.record]: sea_color = 0.635853, 0.780004, 0.830078, 1; 14:30:07.159 [planetinfo.record]: rotation_speed = 0.001217 14:30:07.160 [planetinfo.record]: planet zpos = 616000.000000 14:30:07.160 [planetinfo.record]: sun_radius = 206492.907715 14:30:07.160 [planetinfo.record]: sun_vector = 0.833 -0.150 -0.533 14:30:07.160 [planetinfo.record]: sun_distance = 1047200 14:30:07.160 [planetinfo.record]: corona_flare = 0.088759 14:30:07.160 [planetinfo.record]: corona_hues = 0.862984 14:30:07.160 [planetinfo.record]: sun_color = 0.693048, 0.502568, 0.281953, 1 14:30:07.160 [planetinfo.record]: corona_shimmer = 0.477918 14:30:07.160 [planetinfo.record]: station_vector = 0.556 -0.516 0.652 14:30:07.160 [planetinfo.record]: station = coriolis 14:30:12.166 [PLANETINFO OVER]: Done 14:30:12.167 [PLANETINFO LOGGING]: 6 229 14:30:13.179 [planetinfo.record]: seed = 186 3 64 170 14:30:13.179 [planetinfo.record]: coordinates = 3 137 14:30:13.179 [planetinfo.record]: government = 7; 14:30:13.179 [planetinfo.record]: economy = 1; 14:30:13.179 [planetinfo.record]: techlevel = 13; 14:30:13.179 [planetinfo.record]: population = 61; 14:30:13.179 [planetinfo.record]: productivity = 48312; 14:30:13.179 [planetinfo.record]: name = "Articeso"; 14:30:13.179 [planetinfo.record]: inhabitant = "Human Colonial"; 14:30:13.179 [planetinfo.record]: inhabitants = "Human Colonials"; 14:30:13.179 [planetinfo.record]: description = "This planet is mildly fabled for its inhabitants’ eccentric love for poetry but plagued by frequent earthquakes."; 14:30:13.185 [planetinfo.record]: air_color = 0.645726, 0.872174, 0.822447, 1; 14:30:13.185 [planetinfo.record]: cloud_alpha = 1.000000; 14:30:13.185 [planetinfo.record]: cloud_color = 0.619141, 0.547001, 0.507889, 1; 14:30:13.185 [planetinfo.record]: cloud_fraction = 0.310000; 14:30:13.185 [planetinfo.record]: land_color = 0.66, 0.400536, 0.366094, 1; 14:30:13.185 [planetinfo.record]: land_fraction = 0.530000; 14:30:13.185 [planetinfo.record]: polar_cloud_color = 0.778613, 0.721913, 0.691171, 1; 14:30:13.185 [planetinfo.record]: polar_land_color = 0.934, 0.842205, 0.83002, 1; 14:30:13.185 [planetinfo.record]: polar_sea_color = 0.935742, 0.848016, 0.895306, 1; 14:30:13.185 [planetinfo.record]: sea_color = 0.642578, 0.401611, 0.531507, 1; 14:30:13.185 [planetinfo.record]: rotation_speed = 0.004843 14:30:13.185 [planetinfo.record]: planet zpos = 645480.000000 14:30:13.185 [planetinfo.record]: sun_radius = 139101.681976 14:30:13.186 [planetinfo.record]: sun_vector = -0.248 -0.935 -0.254 14:30:13.186 [planetinfo.record]: sun_distance = 1129590 14:30:13.186 [planetinfo.record]: corona_flare = 0.064110 14:30:13.186 [planetinfo.record]: corona_hues = 0.778885 14:30:13.186 [planetinfo.record]: sun_color = 0.718582, 0.706257, 0.507682, 1 14:30:13.188 [planetinfo.record]: corona_shimmer = 0.294578 14:30:13.188 [planetinfo.record]: station_vector = 0.642 0.736 0.215 14:30:13.188 [planetinfo.record]: station = dodecahedron 14:30:18.175 [PLANETINFO OVER]: Done 14:30:18.175 [PLANETINFO LOGGING]: 6 230 14:30:19.184 [planetinfo.record]: seed = 234 199 148 229 14:30:19.184 [planetinfo.record]: coordinates = 199 229 14:30:19.184 [planetinfo.record]: government = 5; 14:30:19.184 [planetinfo.record]: economy = 5; 14:30:19.184 [planetinfo.record]: techlevel = 8; 14:30:19.184 [planetinfo.record]: population = 43; 14:30:19.184 [planetinfo.record]: productivity = 15480; 14:30:19.184 [planetinfo.record]: name = "Cebeor"; 14:30:19.184 [planetinfo.record]: inhabitant = "Fierce Horned Lobster"; 14:30:19.184 [planetinfo.record]: inhabitants = "Fierce Horned Lobsters"; 14:30:19.184 [planetinfo.record]: description = "This planet is beset by lethal disease."; 14:30:19.204 [planetinfo.record]: air_color = 0.714498, 0.801135, 0.922904, 1; 14:30:19.204 [planetinfo.record]: cloud_alpha = 1.000000; 14:30:19.204 [planetinfo.record]: cloud_color = 0.711212, 0.771484, 0.757358, 1; 14:30:19.204 [planetinfo.record]: cloud_fraction = 0.210000; 14:30:19.204 [planetinfo.record]: land_color = 0.612144, 0.66, 0.590391, 1; 14:30:19.204 [planetinfo.record]: land_fraction = 0.530000; 14:30:19.204 [planetinfo.record]: polar_cloud_color = 0.805802, 0.847168, 0.837473, 1; 14:30:19.204 [planetinfo.record]: polar_land_color = 0.917069, 0.934, 0.909373, 1; 14:30:19.204 [planetinfo.record]: polar_sea_color = 0.724355, 0.930664, 0.925829, 1; 14:30:19.204 [planetinfo.record]: sea_color = 0.0785446, 0.693359, 0.67895, 1; 14:30:19.204 [planetinfo.record]: rotation_speed = 0.001619 14:30:19.204 [planetinfo.record]: planet zpos = 558350.000000 14:30:19.204 [planetinfo.record]: sun_radius = 85144.364166 14:30:19.204 [planetinfo.record]: sun_vector = 0.151 -0.605 0.782 14:30:19.205 [planetinfo.record]: sun_distance = 901950 14:30:19.205 [planetinfo.record]: corona_flare = 0.013554 14:30:19.205 [planetinfo.record]: corona_hues = 0.706909 14:30:19.205 [planetinfo.record]: sun_color = 0.674514, 0.711527, 0.765152, 1 14:30:19.205 [planetinfo.record]: corona_shimmer = 0.380771 14:30:19.205 [planetinfo.record]: station_vector = 0.735 -0.678 0.012 14:30:19.211 [planetinfo.record]: station = coriolis 14:30:24.653 [PLANETINFO OVER]: Done 14:30:24.654 [PLANETINFO LOGGING]: 6 231 14:30:25.657 [planetinfo.record]: seed = 178 185 208 141 14:30:25.657 [planetinfo.record]: coordinates = 185 64 14:30:25.657 [planetinfo.record]: government = 6; 14:30:25.657 [planetinfo.record]: economy = 0; 14:30:25.657 [planetinfo.record]: techlevel = 11; 14:30:25.657 [planetinfo.record]: population = 51; 14:30:25.657 [planetinfo.record]: productivity = 40800; 14:30:25.657 [planetinfo.record]: name = "Diusace"; 14:30:25.657 [planetinfo.record]: inhabitant = "Black Bug-Eyed Lizard"; 14:30:25.657 [planetinfo.record]: inhabitants = "Black Bug-Eyed Lizards"; 14:30:25.657 [planetinfo.record]: description = "The planet Diusace is mildly notable for its inhabitants’ unusual silliness."; 14:30:25.661 [planetinfo.record]: air_color = 0.712289, 0.721118, 0.977537, 1; 14:30:25.661 [planetinfo.record]: cloud_alpha = 1.000000; 14:30:25.662 [planetinfo.record]: cloud_color = 0.73455, 0.751824, 0.935547, 1; 14:30:25.662 [planetinfo.record]: cloud_fraction = 0.140000; 14:30:25.662 [planetinfo.record]: land_color = 0.258789, 0.264511, 0.625, 1; 14:30:25.662 [planetinfo.record]: land_fraction = 0.470000; 14:30:25.662 [planetinfo.record]: polar_cloud_color = 0.797327, 0.807955, 0.920996, 1; 14:30:25.662 [planetinfo.record]: polar_land_color = 0.800171, 0.802317, 0.9375, 1; 14:30:25.662 [planetinfo.record]: polar_sea_color = 0.949609, 0.90297, 0.901851, 1; 14:30:25.662 [planetinfo.record]: sea_color = 0.503906, 0.40491, 0.402534, 1; 14:30:25.662 [planetinfo.record]: rotation_speed = 0.001421 14:30:25.662 [planetinfo.record]: planet zpos = 632900.000000 14:30:25.662 [planetinfo.record]: sun_radius = 112236.030731 14:30:25.662 [planetinfo.record]: sun_vector = 0.138 -0.465 -0.874 14:30:25.662 [planetinfo.record]: sun_distance = 1139220 14:30:25.662 [planetinfo.record]: corona_flare = 0.052153 14:30:25.662 [planetinfo.record]: corona_hues = 0.588142 14:30:25.662 [planetinfo.record]: sun_color = 0.485248, 0.577932, 0.717184, 1 14:30:25.663 [planetinfo.record]: corona_shimmer = 0.440904 14:30:25.663 [planetinfo.record]: station_vector = 0.663 0.729 0.171 14:30:25.663 [planetinfo.record]: station = dodecahedron 14:30:29.815 [PLANETINFO OVER]: Done 14:30:29.817 [PLANETINFO LOGGING]: 6 232 14:30:30.820 [planetinfo.record]: seed = 242 229 20 62 14:30:30.820 [planetinfo.record]: coordinates = 229 207 14:30:30.820 [planetinfo.record]: government = 6; 14:30:30.820 [planetinfo.record]: economy = 7; 14:30:30.820 [planetinfo.record]: techlevel = 4; 14:30:30.820 [planetinfo.record]: population = 30; 14:30:30.820 [planetinfo.record]: productivity = 7200; 14:30:30.820 [planetinfo.record]: name = "Ribeties"; 14:30:30.820 [planetinfo.record]: inhabitant = "Human Colonial"; 14:30:30.820 [planetinfo.record]: inhabitants = "Human Colonials"; 14:30:30.820 [planetinfo.record]: description = "Ribeties is very notable for the Ribetiesian tree ant."; 14:30:30.838 [planetinfo.record]: air_color = 0.781398, 0.607181, 0.887133, 1; 14:30:30.838 [planetinfo.record]: cloud_alpha = 1.000000; 14:30:30.838 [planetinfo.record]: cloud_color = 0.664062, 0.433197, 0.460252, 1; 14:30:30.838 [planetinfo.record]: cloud_fraction = 0.390000; 14:30:30.838 [planetinfo.record]: land_color = 0.559453, 0.66, 0.655287, 1; 14:30:30.838 [planetinfo.record]: land_fraction = 0.590000; 14:30:30.838 [planetinfo.record]: polar_cloud_color = 0.798828, 0.625255, 0.645595, 1; 14:30:30.838 [planetinfo.record]: polar_land_color = 0.898428, 0.934, 0.932333, 1; 14:30:30.838 [planetinfo.record]: polar_sea_color = 0.81456, 0.947852, 0.929107, 1; 14:30:30.838 [planetinfo.record]: sea_color = 0.228149, 0.521484, 0.480234, 1; 14:30:30.838 [planetinfo.record]: rotation_speed = 0.001859 14:30:30.838 [planetinfo.record]: planet zpos = 662900.000000 14:30:30.839 [planetinfo.record]: sun_radius = 185945.998993 14:30:30.839 [planetinfo.record]: sun_vector = -0.109 0.770 -0.628 14:30:30.839 [planetinfo.record]: sun_distance = 1193220 14:30:30.839 [planetinfo.record]: corona_flare = 0.013248 14:30:30.839 [planetinfo.record]: corona_hues = 0.918915 14:30:30.839 [planetinfo.record]: sun_color = 0.726804, 0.721122, 0.555045, 1 14:30:30.839 [planetinfo.record]: corona_shimmer = 0.295138 14:30:30.839 [planetinfo.record]: station_vector = 0.248 -0.921 0.302 14:30:30.839 [planetinfo.record]: station = coriolis 14:30:35.603 [PLANETINFO OVER]: Done 14:30:35.604 [PLANETINFO LOGGING]: 6 233 14:30:36.613 [planetinfo.record]: seed = 202 73 128 85 14:30:36.613 [planetinfo.record]: coordinates = 73 23 14:30:36.613 [planetinfo.record]: government = 1; 14:30:36.613 [planetinfo.record]: economy = 7; 14:30:36.613 [planetinfo.record]: techlevel = 2; 14:30:36.613 [planetinfo.record]: population = 17; 14:30:36.613 [planetinfo.record]: productivity = 2040; 14:30:36.613 [planetinfo.record]: name = "Lativege"; 14:30:36.613 [planetinfo.record]: inhabitant = "Yellow Insect"; 14:30:36.613 [planetinfo.record]: inhabitants = "Yellow Insects"; 14:30:36.613 [planetinfo.record]: description = "This planet is a dull place."; 14:30:36.636 [planetinfo.record]: air_color = 0.481907, 0.686979, 0.845508, 1; 14:30:36.636 [planetinfo.record]: cloud_alpha = 1.000000; 14:30:36.636 [planetinfo.record]: cloud_color = 0.166351, 0.539062, 0.285735, 1; 14:30:36.636 [planetinfo.record]: cloud_fraction = 0.130000; 14:30:36.636 [planetinfo.record]: land_color = 0.30542, 0.336969, 0.542969, 1; 14:30:36.636 [planetinfo.record]: land_fraction = 0.390000; 14:30:36.636 [planetinfo.record]: polar_cloud_color = 0.421689, 0.742578, 0.524474, 1; 14:30:36.636 [planetinfo.record]: polar_land_color = 0.842267, 0.856004, 0.945703, 1; 14:30:36.636 [planetinfo.record]: polar_sea_color = 0.919727, 0.841698, 0.794881, 1; 14:30:36.636 [planetinfo.record]: sea_color = 0.802734, 0.530322, 0.366875, 1; 14:30:36.636 [planetinfo.record]: rotation_speed = 0.002940 14:30:36.636 [planetinfo.record]: planet zpos = 500280.000000 14:30:36.636 [planetinfo.record]: sun_radius = 126811.748352 14:30:36.636 [planetinfo.record]: sun_vector = 0.236 0.970 -0.065 14:30:36.636 [planetinfo.record]: sun_distance = 875490 14:30:36.636 [planetinfo.record]: corona_flare = 0.067477 14:30:36.636 [planetinfo.record]: corona_hues = 0.870476 14:30:36.637 [planetinfo.record]: sun_color = 0.564356, 0.665147, 0.679208, 1 14:30:36.637 [planetinfo.record]: corona_shimmer = 1.329115 14:30:36.637 [planetinfo.record]: station_vector = -0.060 -0.874 0.482 14:30:36.637 [planetinfo.record]: station = coriolis 14:30:41.970 [PLANETINFO OVER]: Done 14:30:41.971 [PLANETINFO LOGGING]: 6 234 14:30:42.974 [planetinfo.record]: seed = 26 99 180 112 14:30:42.974 [planetinfo.record]: coordinates = 99 86 14:30:42.974 [planetinfo.record]: government = 3; 14:30:42.974 [planetinfo.record]: economy = 6; 14:30:42.974 [planetinfo.record]: techlevel = 6; 14:30:42.975 [planetinfo.record]: population = 34; 14:30:42.975 [planetinfo.record]: productivity = 7616; 14:30:42.975 [planetinfo.record]: name = "Erarrior"; 14:30:42.975 [planetinfo.record]: inhabitant = "Blue Furry Humanoid"; 14:30:42.975 [planetinfo.record]: inhabitants = "Blue Furry Humanoids"; 14:30:42.975 [planetinfo.record]: description = "This world is very notable for the Erarriorian edible grub and the Erarriorian tree grub."; 14:30:42.989 [planetinfo.record]: air_color = 0.669205, 0.829503, 0.938514, 1; 14:30:42.989 [planetinfo.record]: cloud_alpha = 1.000000; 14:30:42.989 [planetinfo.record]: cloud_color = 0.604179, 0.818359, 0.652705, 1; 14:30:42.989 [planetinfo.record]: cloud_fraction = 0.380000; 14:30:42.989 [planetinfo.record]: land_color = 0.401332, 0.351685, 0.523438, 1; 14:30:42.989 [planetinfo.record]: land_fraction = 0.430000; 14:30:42.989 [planetinfo.record]: polar_cloud_color = 0.726236, 0.868262, 0.758414, 1; 14:30:42.989 [planetinfo.record]: polar_land_color = 0.89239, 0.869919, 0.947656, 1; 14:30:42.989 [planetinfo.record]: polar_sea_color = 0.912041, 0.934961, 0.884378, 1; 14:30:42.989 [planetinfo.record]: sea_color = 0.586614, 0.650391, 0.509642, 1; 14:30:42.989 [planetinfo.record]: rotation_speed = 0.002176 14:30:42.990 [planetinfo.record]: planet zpos = 349800.000000 14:30:42.990 [planetinfo.record]: sun_radius = 71189.231873 14:30:42.990 [planetinfo.record]: sun_vector = 0.949 -0.237 -0.207 14:30:42.990 [planetinfo.record]: sun_distance = 641300 14:30:42.990 [planetinfo.record]: corona_flare = 0.024236 14:30:42.990 [planetinfo.record]: corona_hues = 0.596062 14:30:42.990 [planetinfo.record]: sun_color = 0.632002, 0.650181, 0.734491, 1 14:30:42.990 [planetinfo.record]: corona_shimmer = 0.514743 14:30:42.990 [planetinfo.record]: station_vector = 0.084 0.591 0.803 14:30:42.990 [planetinfo.record]: station = coriolis 14:30:47.560 [PLANETINFO OVER]: Done 14:30:47.560 [PLANETINFO LOGGING]: 6 235 14:30:48.579 [planetinfo.record]: seed = 2 153 80 193 14:30:48.579 [planetinfo.record]: coordinates = 153 254 14:30:48.579 [planetinfo.record]: government = 0; 14:30:48.579 [planetinfo.record]: economy = 6; 14:30:48.579 [planetinfo.record]: techlevel = 2; 14:30:48.579 [planetinfo.record]: population = 15; 14:30:48.579 [planetinfo.record]: productivity = 1920; 14:30:48.579 [planetinfo.record]: name = "Leeden"; 14:30:48.579 [planetinfo.record]: inhabitant = "Human Colonial"; 14:30:48.579 [planetinfo.record]: inhabitants = "Human Colonials"; 14:30:48.579 [planetinfo.record]: description = "The planet Leeden is a boring world."; 14:30:48.582 [planetinfo.record]: air_color = 0.646066, 0.88193, 0.844862, 1; 14:30:48.582 [planetinfo.record]: cloud_alpha = 1.000000; 14:30:48.582 [planetinfo.record]: cloud_color = 0.648438, 0.587726, 0.516724, 1; 14:30:48.582 [planetinfo.record]: cloud_fraction = 0.290000; 14:30:48.583 [planetinfo.record]: land_color = 0.415078, 0.66, 0.573895, 1; 14:30:48.583 [planetinfo.record]: land_fraction = 0.660000; 14:30:48.583 [planetinfo.record]: polar_cloud_color = 0.791797, 0.745463, 0.691276, 1; 14:30:48.583 [planetinfo.record]: polar_land_color = 0.84735, 0.934, 0.903537, 1; 14:30:48.583 [planetinfo.record]: polar_sea_color = 0.934323, 0.940234, 0.892947, 1; 14:30:48.583 [planetinfo.record]: sea_color = 0.582627, 0.597656, 0.477425, 1; 14:30:48.583 [planetinfo.record]: rotation_speed = 0.004564 14:30:48.583 [planetinfo.record]: planet zpos = 419250.000000 14:30:48.583 [planetinfo.record]: sun_radius = 94916.942596 14:30:48.583 [planetinfo.record]: sun_vector = -0.566 0.150 -0.810 14:30:48.583 [planetinfo.record]: sun_distance = 709500 14:30:48.583 [planetinfo.record]: corona_flare = 0.040013 14:30:48.583 [planetinfo.record]: corona_hues = 0.777069 14:30:48.583 [planetinfo.record]: sun_color = 0.361721, 0.472869, 0.696368, 1 14:30:48.583 [planetinfo.record]: corona_shimmer = 0.440241 14:30:48.583 [planetinfo.record]: station_vector = -0.622 0.700 0.352 14:30:48.584 [planetinfo.record]: station = coriolis 14:30:52.961 [PLANETINFO OVER]: Done 14:30:52.963 [PLANETINFO LOGGING]: 6 236 14:30:53.980 [planetinfo.record]: seed = 98 204 116 215 14:30:53.981 [planetinfo.record]: coordinates = 204 178 14:30:53.981 [planetinfo.record]: government = 4; 14:30:53.981 [planetinfo.record]: economy = 2; 14:30:53.981 [planetinfo.record]: techlevel = 7; 14:30:53.981 [planetinfo.record]: population = 35; 14:30:53.981 [planetinfo.record]: productivity = 17920; 14:30:53.981 [planetinfo.record]: name = "Tivequ"; 14:30:53.981 [planetinfo.record]: inhabitant = "Human Colonial"; 14:30:53.981 [planetinfo.record]: inhabitants = "Human Colonials"; 14:30:53.981 [planetinfo.record]: description = "This planet is beset by lethal disease."; 14:30:53.996 [planetinfo.record]: air_color = 0.658714, 0.872262, 0.913799, 1; 14:30:53.996 [planetinfo.record]: cloud_alpha = 1.000000; 14:30:53.996 [planetinfo.record]: cloud_color = 0.655483, 0.744141, 0.566826, 1; 14:30:53.996 [planetinfo.record]: cloud_fraction = 0.490000; 14:30:53.996 [planetinfo.record]: land_color = 0.66, 0.591921, 0.224297, 1; 14:30:53.996 [planetinfo.record]: land_fraction = 0.440000; 14:30:53.997 [planetinfo.record]: polar_cloud_color = 0.772697, 0.834863, 0.710531, 1; 14:30:53.997 [planetinfo.record]: polar_land_color = 0.934, 0.909915, 0.779854, 1; 14:30:53.997 [planetinfo.record]: polar_sea_color = 0.937305, 0.89316, 0.875886, 1; 14:30:53.997 [planetinfo.record]: sea_color = 0.626953, 0.508841, 0.462623, 1; 14:30:53.997 [planetinfo.record]: rotation_speed = 0.002551 14:30:53.997 [planetinfo.record]: planet zpos = 625560.000000 14:30:53.997 [planetinfo.record]: sun_radius = 133293.339844 14:30:53.997 [planetinfo.record]: sun_vector = 0.642 -0.761 -0.098 14:30:53.997 [planetinfo.record]: sun_distance = 1058640 14:30:53.997 [planetinfo.record]: corona_flare = 0.044374 14:30:53.997 [planetinfo.record]: corona_hues = 0.669769 14:30:53.997 [planetinfo.record]: sun_color = 0.656677, 0.601292, 0.290633, 1 14:30:53.997 [planetinfo.record]: corona_shimmer = 0.496876 14:30:53.997 [planetinfo.record]: station_vector = 0.397 0.917 0.033 14:30:53.998 [planetinfo.record]: station = coriolis 14:30:58.631 [PLANETINFO OVER]: Done 14:30:58.632 [PLANETINFO LOGGING]: 6 237 14:30:59.649 [planetinfo.record]: seed = 90 40 64 121 14:30:59.649 [planetinfo.record]: coordinates = 40 250 14:30:59.649 [planetinfo.record]: government = 3; 14:30:59.649 [planetinfo.record]: economy = 2; 14:30:59.649 [planetinfo.record]: techlevel = 7; 14:30:59.649 [planetinfo.record]: population = 34; 14:30:59.649 [planetinfo.record]: productivity = 15232; 14:30:59.649 [planetinfo.record]: name = "Oranisen"; 14:30:59.649 [planetinfo.record]: inhabitant = "Human Colonial"; 14:30:59.649 [planetinfo.record]: inhabitants = "Human Colonials"; 14:30:59.649 [planetinfo.record]: description = "This planet is most notable for Oranisenian Enstilth brandy but scourged by deadly civil war."; 14:30:59.673 [planetinfo.record]: air_color = 0.779156, 0.556753, 0.928107, 1; 14:30:59.674 [planetinfo.record]: cloud_alpha = 1.000000; 14:30:59.674 [planetinfo.record]: cloud_color = 0.787109, 0.313614, 0.402394, 1; 14:30:59.674 [planetinfo.record]: cloud_fraction = 0.340000; 14:30:59.674 [planetinfo.record]: land_color = 0.601562, 0.565764, 0.531067, 1; 14:30:59.674 [planetinfo.record]: land_fraction = 0.410000; 14:30:59.674 [planetinfo.record]: polar_cloud_color = 0.854199, 0.53304, 0.593258, 1; 14:30:59.674 [planetinfo.record]: polar_land_color = 0.939844, 0.925861, 0.912309, 1; 14:30:59.674 [planetinfo.record]: polar_sea_color = 0.797137, 0.907422, 0.787791, 1; 14:30:59.674 [planetinfo.record]: sea_color = 0.475717, 0.925781, 0.437576, 1; 14:30:59.674 [planetinfo.record]: rotation_speed = 0.001076 14:30:59.674 [planetinfo.record]: planet zpos = 516000.000000 14:30:59.674 [planetinfo.record]: sun_radius = 101843.389893 14:30:59.683 [planetinfo.record]: sun_vector = -0.182 -0.938 0.294 14:30:59.684 [planetinfo.record]: sun_distance = 980400 14:30:59.684 [planetinfo.record]: corona_flare = 0.089572 14:30:59.684 [planetinfo.record]: corona_hues = 0.708717 14:30:59.684 [planetinfo.record]: sun_color = 0.809933, 0.349846, 0.226379, 1 14:30:59.684 [planetinfo.record]: corona_shimmer = 0.284097 14:30:59.684 [planetinfo.record]: station_vector = -0.888 0.374 0.269 14:30:59.684 [planetinfo.record]: station = coriolis 14:31:05.157 [PLANETINFO OVER]: Done 14:31:05.158 [PLANETINFO LOGGING]: 6 238 14:31:06.160 [planetinfo.record]: seed = 202 82 84 44 14:31:06.160 [planetinfo.record]: coordinates = 82 61 14:31:06.160 [planetinfo.record]: government = 1; 14:31:06.160 [planetinfo.record]: economy = 7; 14:31:06.160 [planetinfo.record]: techlevel = 3; 14:31:06.160 [planetinfo.record]: population = 21; 14:31:06.161 [planetinfo.record]: productivity = 2520; 14:31:06.161 [planetinfo.record]: name = "Intean"; 14:31:06.161 [planetinfo.record]: inhabitant = "Human Colonial"; 14:31:06.161 [planetinfo.record]: inhabitants = "Human Colonials"; 14:31:06.161 [planetinfo.record]: description = "The world Intean is a boring planet."; 14:31:06.180 [planetinfo.record]: air_color = 0.713033, 0.739271, 0.96648, 1; 14:31:06.180 [planetinfo.record]: cloud_alpha = 1.000000; 14:31:06.180 [planetinfo.record]: cloud_color = 0.733154, 0.783382, 0.902344, 1; 14:31:06.180 [planetinfo.record]: cloud_fraction = 0.530000; 14:31:06.180 [planetinfo.record]: land_color = 0.194397, 0.507812, 0.316825, 1; 14:31:06.180 [planetinfo.record]: land_fraction = 0.540000; 14:31:06.180 [planetinfo.record]: polar_cloud_color = 0.799876, 0.831398, 0.906055, 1; 14:31:06.180 [planetinfo.record]: polar_land_color = 0.802757, 0.949219, 0.859969, 1; 14:31:06.180 [planetinfo.record]: polar_sea_color = 0.921094, 0.917334, 0.824847, 1; 14:31:06.180 [planetinfo.record]: sea_color = 0.789062, 0.77618, 0.459259, 1; 14:31:06.180 [planetinfo.record]: rotation_speed = 0.002766 14:31:06.180 [planetinfo.record]: planet zpos = 716400.000000 14:31:06.180 [planetinfo.record]: sun_radius = 138284.898376 14:31:06.180 [planetinfo.record]: sun_vector = -0.177 0.959 -0.222 14:31:06.180 [planetinfo.record]: sun_distance = 1194000 14:31:06.180 [planetinfo.record]: corona_flare = 0.097115 14:31:06.180 [planetinfo.record]: corona_hues = 0.770836 14:31:06.180 [planetinfo.record]: sun_color = 0.328223, 0.604348, 0.776361, 1 14:31:06.180 [planetinfo.record]: corona_shimmer = 0.333595 14:31:06.180 [planetinfo.record]: station_vector = 0.819 0.055 0.570 14:31:06.181 [planetinfo.record]: station = coriolis 14:31:11.131 [PLANETINFO OVER]: Done 14:31:11.132 [PLANETINFO LOGGING]: 6 239 14:31:12.153 [planetinfo.record]: seed = 210 36 80 29 14:31:12.153 [planetinfo.record]: coordinates = 36 59 14:31:12.153 [planetinfo.record]: government = 2; 14:31:12.153 [planetinfo.record]: economy = 3; 14:31:12.153 [planetinfo.record]: techlevel = 5; 14:31:12.153 [planetinfo.record]: population = 26; 14:31:12.153 [planetinfo.record]: productivity = 8736; 14:31:12.153 [planetinfo.record]: name = "Isisan"; 14:31:12.153 [planetinfo.record]: inhabitant = "Human Colonial"; 14:31:12.153 [planetinfo.record]: inhabitants = "Human Colonials"; 14:31:12.153 [planetinfo.record]: description = "The planet Isisan is cursed by dreadful civil war."; 14:31:12.165 [planetinfo.record]: air_color = 0.522002, 0.606666, 0.848109, 1; 14:31:12.166 [planetinfo.record]: cloud_alpha = 1.000000; 14:31:12.166 [planetinfo.record]: cloud_color = 0.24353, 0.475779, 0.546875, 1; 14:31:12.166 [planetinfo.record]: cloud_fraction = 0.300000; 14:31:12.166 [planetinfo.record]: land_color = 0.66, 0.351834, 0.0799219, 1; 14:31:12.166 [planetinfo.record]: land_fraction = 0.410000; 14:31:12.166 [planetinfo.record]: polar_cloud_color = 0.487438, 0.685471, 0.746094, 1; 14:31:12.166 [planetinfo.record]: polar_land_color = 0.934, 0.824974, 0.728775, 1; 14:31:12.166 [planetinfo.record]: polar_sea_color = 0.939453, 0.892833, 0.88037, 1; 14:31:12.166 [planetinfo.record]: sea_color = 0.605469, 0.485284, 0.453156, 1; 14:31:12.166 [planetinfo.record]: rotation_speed = 0.002596 14:31:12.166 [planetinfo.record]: planet zpos = 618000.000000 14:31:12.167 [planetinfo.record]: sun_radius = 174575.381470 14:31:12.167 [planetinfo.record]: sun_vector = -0.494 0.477 0.727 14:31:12.167 [planetinfo.record]: sun_distance = 1297800 14:31:12.167 [planetinfo.record]: corona_flare = 0.021973 14:31:12.167 [planetinfo.record]: corona_hues = 0.974457 14:31:12.167 [planetinfo.record]: sun_color = 0.803317, 0.787816, 0.74471, 1 14:31:12.167 [planetinfo.record]: corona_shimmer = 0.287476 14:31:12.167 [planetinfo.record]: station_vector = -0.325 0.946 0.024 14:31:12.167 [planetinfo.record]: station = coriolis 14:31:16.787 [PLANETINFO OVER]: Done 14:31:16.788 [PLANETINFO LOGGING]: 6 240 14:31:17.790 [planetinfo.record]: seed = 82 91 84 153 14:31:17.790 [planetinfo.record]: coordinates = 91 192 14:31:17.790 [planetinfo.record]: government = 2; 14:31:17.791 [planetinfo.record]: economy = 0; 14:31:17.791 [planetinfo.record]: techlevel = 11; 14:31:17.791 [planetinfo.record]: population = 47; 14:31:17.791 [planetinfo.record]: productivity = 22560; 14:31:17.791 [planetinfo.record]: name = "Orraes"; 14:31:17.791 [planetinfo.record]: inhabitant = "Human Colonial"; 14:31:17.791 [planetinfo.record]: inhabitants = "Human Colonials"; 14:31:17.791 [planetinfo.record]: description = "This world is mildly well known for its hoopy night life and Orraesian lethal water."; 14:31:17.814 [planetinfo.record]: air_color = 0.57343, 0.93776, 0.974936, 1; 14:31:17.814 [planetinfo.record]: cloud_alpha = 1.000000; 14:31:17.814 [planetinfo.record]: cloud_color = 0.756983, 0.927734, 0.337029, 1; 14:31:17.814 [planetinfo.record]: cloud_fraction = 0.310000; 14:31:17.814 [planetinfo.record]: land_color = 0.66, 0.631238, 0.397031, 1; 14:31:17.814 [planetinfo.record]: land_fraction = 0.280000; 14:31:17.814 [planetinfo.record]: polar_cloud_color = 0.811941, 0.91748, 0.55237, 1; 14:31:17.814 [planetinfo.record]: polar_land_color = 0.934, 0.923824, 0.840965, 1; 14:31:17.814 [planetinfo.record]: polar_sea_color = 0.944577, 0.894721, 0.947461, 1; 14:31:17.814 [planetinfo.record]: sea_color = 0.518993, 0.408409, 0.525391, 1; 14:31:17.814 [planetinfo.record]: rotation_speed = 0.003130 14:31:17.815 [planetinfo.record]: planet zpos = 729540.000000 14:31:17.815 [planetinfo.record]: sun_radius = 179711.754456 14:31:17.815 [planetinfo.record]: sun_vector = -0.634 -0.725 0.271 14:31:17.815 [planetinfo.record]: sun_distance = 1146420 14:31:17.815 [planetinfo.record]: corona_flare = 0.049991 14:31:17.815 [planetinfo.record]: corona_hues = 0.841637 14:31:17.815 [planetinfo.record]: sun_color = 0.833493, 0.821874, 0.781508, 1 14:31:17.815 [planetinfo.record]: corona_shimmer = 0.286146 14:31:17.815 [planetinfo.record]: station_vector = -0.716 0.216 0.664 14:31:17.815 [planetinfo.record]: station = dodecahedron 14:31:22.890 [PLANETINFO OVER]: Done 14:31:22.890 [PLANETINFO LOGGING]: 6 241 14:31:23.907 [planetinfo.record]: seed = 106 247 128 85 14:31:23.907 [planetinfo.record]: coordinates = 247 169 14:31:23.907 [planetinfo.record]: government = 5; 14:31:23.907 [planetinfo.record]: economy = 1; 14:31:23.907 [planetinfo.record]: techlevel = 12; 14:31:23.907 [planetinfo.record]: population = 55; 14:31:23.907 [planetinfo.record]: productivity = 35640; 14:31:23.907 [planetinfo.record]: name = "Lavegere"; 14:31:23.907 [planetinfo.record]: inhabitant = "Yellow Insect"; 14:31:23.907 [planetinfo.record]: inhabitants = "Yellow Insects"; 14:31:23.907 [planetinfo.record]: description = "The planet Lavegere is most noted for its inhabitants’ exceptional loathing of sit coms and Lavegereian wolf cutlet."; 14:31:23.909 [planetinfo.record]: air_color = 0.617699, 0.82815, 0.980789, 1; 14:31:23.909 [planetinfo.record]: cloud_alpha = 1.000000; 14:31:23.909 [planetinfo.record]: cloud_color = 0.461578, 0.945312, 0.593849, 1; 14:31:23.909 [planetinfo.record]: cloud_fraction = 0.320000; 14:31:23.909 [planetinfo.record]: land_color = 0.0799219, 0.619213, 0.66, 1; 14:31:23.909 [planetinfo.record]: land_fraction = 0.530000; 14:31:23.909 [planetinfo.record]: polar_cloud_color = 0.629428, 0.925391, 0.710355, 1; 14:31:23.909 [planetinfo.record]: polar_land_color = 0.728775, 0.91957, 0.934, 1; 14:31:23.909 [planetinfo.record]: polar_sea_color = 0.880804, 0.927734, 0.909036, 1; 14:31:23.910 [planetinfo.record]: sea_color = 0.576431, 0.722656, 0.664395, 1; 14:31:23.910 [planetinfo.record]: rotation_speed = 0.004217 14:31:23.910 [planetinfo.record]: planet zpos = 434300.000000 14:31:23.910 [planetinfo.record]: sun_radius = 100195.957642 14:31:23.910 [planetinfo.record]: sun_vector = 0.398 0.642 -0.655 14:31:23.910 [planetinfo.record]: sun_distance = 998890 14:31:23.910 [planetinfo.record]: corona_flare = 0.039444 14:31:23.910 [planetinfo.record]: corona_hues = 0.610115 14:31:23.910 [planetinfo.record]: sun_color = 0.517131, 0.712183, 0.79158, 1 14:31:23.910 [planetinfo.record]: corona_shimmer = 0.603029 14:31:23.910 [planetinfo.record]: station_vector = -0.056 0.955 0.292 14:31:23.910 [planetinfo.record]: station = dodecahedron 14:31:28.292 [PLANETINFO OVER]: Done 14:31:28.293 [PLANETINFO LOGGING]: 6 242 14:31:29.296 [planetinfo.record]: seed = 250 142 116 200 14:31:29.296 [planetinfo.record]: coordinates = 142 67 14:31:29.297 [planetinfo.record]: government = 7; 14:31:29.297 [planetinfo.record]: economy = 3; 14:31:29.297 [planetinfo.record]: techlevel = 10; 14:31:29.297 [planetinfo.record]: population = 51; 14:31:29.297 [planetinfo.record]: productivity = 31416; 14:31:29.297 [planetinfo.record]: name = "Usquen"; 14:31:29.297 [planetinfo.record]: inhabitant = "Human Colonial"; 14:31:29.297 [planetinfo.record]: inhabitants = "Human Colonials"; 14:31:29.297 [planetinfo.record]: description = "The planet Usquen is a boring planet."; 14:31:29.298 [planetinfo.record]: air_color = 0.455993, 0.538882, 0.912498, 1; 14:31:29.298 [planetinfo.record]: cloud_alpha = 1.000000; 14:31:29.298 [planetinfo.record]: cloud_color = 0.0722885, 0.427135, 0.740234, 1; 14:31:29.298 [planetinfo.record]: cloud_fraction = 0.160000; 14:31:29.298 [planetinfo.record]: land_color = 0.365812, 0.0979688, 0.66, 1; 14:31:29.298 [planetinfo.record]: land_fraction = 0.630000; 14:31:29.298 [planetinfo.record]: polar_cloud_color = 0.363263, 0.612867, 0.833105, 1; 14:31:29.298 [planetinfo.record]: polar_land_color = 0.82992, 0.73516, 0.934, 1; 14:31:29.298 [planetinfo.record]: polar_sea_color = 0.931746, 0.935938, 0.882286, 1; 14:31:29.298 [planetinfo.record]: sea_color = 0.629149, 0.640625, 0.493732, 1; 14:31:29.298 [planetinfo.record]: rotation_speed = 0.003472 14:31:29.298 [planetinfo.record]: planet zpos = 750900.000000 14:31:29.298 [planetinfo.record]: sun_radius = 123992.759705 14:31:29.298 [planetinfo.record]: sun_vector = -0.008 0.767 -0.641 14:31:29.298 [planetinfo.record]: sun_distance = 901080 14:31:29.298 [planetinfo.record]: corona_flare = 0.020428 14:31:29.298 [planetinfo.record]: corona_hues = 0.561043 14:31:29.298 [planetinfo.record]: sun_color = 0.581104, 0.702654, 0.829831, 1 14:31:29.298 [planetinfo.record]: corona_shimmer = 0.241561 14:31:29.299 [planetinfo.record]: station_vector = 0.088 0.973 0.212 14:31:29.299 [planetinfo.record]: station = coriolis 14:31:34.012 [PLANETINFO OVER]: Done 14:31:34.013 [PLANETINFO LOGGING]: 6 243 14:31:35.016 [planetinfo.record]: seed = 34 85 208 225 14:31:35.017 [planetinfo.record]: coordinates = 85 242 14:31:35.017 [planetinfo.record]: government = 4; 14:31:35.017 [planetinfo.record]: economy = 2; 14:31:35.017 [planetinfo.record]: techlevel = 8; 14:31:35.017 [planetinfo.record]: population = 39; 14:31:35.017 [planetinfo.record]: productivity = 19968; 14:31:35.017 [planetinfo.record]: name = "Leeson"; 14:31:35.017 [planetinfo.record]: inhabitant = "Large Rodent"; 14:31:35.017 [planetinfo.record]: inhabitants = "Large Rodents"; 14:31:35.017 [planetinfo.record]: description = "The planet Leeson is cursed by killer mountain A’onsethoids."; 14:31:35.023 [planetinfo.record]: air_color = 0.40442, 0.83315, 0.739003, 1; 14:31:35.023 [planetinfo.record]: cloud_alpha = 1.000000; 14:31:35.023 [planetinfo.record]: cloud_color = 0.501953, 0.194268, 0.0274506, 1; 14:31:35.023 [planetinfo.record]: cloud_fraction = 0.500000; 14:31:35.023 [planetinfo.record]: land_color = 0.0257813, 0.541084, 0.66, 1; 14:31:35.023 [planetinfo.record]: land_fraction = 0.310000; 14:31:35.024 [planetinfo.record]: polar_cloud_color = 0.725879, 0.447787, 0.297015, 1; 14:31:35.024 [planetinfo.record]: polar_land_color = 0.709621, 0.891929, 0.934, 1; 14:31:35.024 [planetinfo.record]: polar_sea_color = 0.945312, 0.921029, 0.897493, 1; 14:31:35.024 [planetinfo.record]: sea_color = 0.546875, 0.490682, 0.436218, 1; 14:31:35.024 [planetinfo.record]: rotation_speed = 0.000819 14:31:35.024 [planetinfo.record]: planet zpos = 410410.000000 14:31:35.024 [planetinfo.record]: sun_radius = 89558.968353 14:31:35.024 [planetinfo.record]: sun_vector = 0.147 -0.304 -0.941 14:31:35.024 [planetinfo.record]: sun_distance = 631400 14:31:35.024 [planetinfo.record]: corona_flare = 0.081107 14:31:35.024 [planetinfo.record]: corona_hues = 0.758736 14:31:35.024 [planetinfo.record]: sun_color = 0.703634, 0.733251, 0.787149, 1 14:31:35.024 [planetinfo.record]: corona_shimmer = 0.469341 14:31:35.024 [planetinfo.record]: station_vector = 0.836 -0.542 0.083 14:31:35.025 [planetinfo.record]: station = coriolis 14:31:39.997 [PLANETINFO OVER]: Done 14:31:39.998 [PLANETINFO LOGGING]: 6 244 14:31:41.013 [planetinfo.record]: seed = 194 106 180 243 14:31:41.013 [planetinfo.record]: coordinates = 106 95 14:31:41.013 [planetinfo.record]: government = 0; 14:31:41.013 [planetinfo.record]: economy = 7; 14:31:41.013 [planetinfo.record]: techlevel = 2; 14:31:41.013 [planetinfo.record]: population = 16; 14:31:41.013 [planetinfo.record]: productivity = 1536; 14:31:41.014 [planetinfo.record]: name = "Beritere"; 14:31:41.015 [planetinfo.record]: inhabitant = "Furry Rodent"; 14:31:41.016 [planetinfo.record]: inhabitants = "Furry Rodents"; 14:31:41.017 [planetinfo.record]: description = "The planet Beritere is very noted for its ancient Arceon tulip plantations but ravaged by occasional earthquakes."; 14:31:41.025 [planetinfo.record]: air_color = 0.654614, 0.601681, 0.837703, 1; 14:31:41.025 [planetinfo.record]: cloud_alpha = 1.000000; 14:31:41.025 [planetinfo.record]: cloud_color = 0.475971, 0.388733, 0.515625, 1; 14:31:41.025 [planetinfo.record]: cloud_fraction = 0.310000; 14:31:41.025 [planetinfo.record]: land_color = 0.407525, 0.66, 0.363516, 1; 14:31:41.025 [planetinfo.record]: land_fraction = 0.700000; 14:31:41.025 [planetinfo.record]: polar_cloud_color = 0.696846, 0.619439, 0.732031, 1; 14:31:41.026 [planetinfo.record]: polar_land_color = 0.844677, 0.934, 0.829107, 1; 14:31:41.026 [planetinfo.record]: polar_sea_color = 0.94082, 0.93776, 0.89185, 1; 14:31:41.026 [planetinfo.record]: sea_color = 0.591797, 0.584096, 0.468583, 1; 14:31:41.026 [planetinfo.record]: rotation_speed = 0.003799 14:31:41.026 [planetinfo.record]: planet zpos = 516600.000000 14:31:41.026 [planetinfo.record]: sun_radius = 89906.025696 14:31:41.026 [planetinfo.record]: sun_vector = 0.995 -0.063 -0.073 14:31:41.026 [planetinfo.record]: sun_distance = 738000 14:31:41.026 [planetinfo.record]: corona_flare = 0.076080 14:31:41.026 [planetinfo.record]: corona_hues = 0.887215 14:31:41.026 [planetinfo.record]: sun_color = 0.745873, 0.746738, 0.634695, 1 14:31:41.026 [planetinfo.record]: corona_shimmer = 0.320081 14:31:41.026 [planetinfo.record]: station_vector = -0.889 -0.003 0.458 14:31:41.027 [planetinfo.record]: station = coriolis 14:31:45.948 [PLANETINFO OVER]: Done 14:31:45.949 [PLANETINFO LOGGING]: 6 245 14:31:46.967 [planetinfo.record]: seed = 250 206 64 170 14:31:46.967 [planetinfo.record]: coordinates = 206 28 14:31:46.967 [planetinfo.record]: government = 7; 14:31:46.967 [planetinfo.record]: economy = 4; 14:31:46.967 [planetinfo.record]: techlevel = 9; 14:31:46.967 [planetinfo.record]: population = 48; 14:31:46.967 [planetinfo.record]: productivity = 25344; 14:31:46.967 [planetinfo.record]: name = "Arveaa"; 14:31:46.967 [planetinfo.record]: inhabitant = "Human Colonial"; 14:31:46.967 [planetinfo.record]: inhabitants = "Human Colonials"; 14:31:46.967 [planetinfo.record]: description = "The world Arveaa is reasonably fabled for its exciting sit coms and its inhabitants’ ancient loathing of discos."; 14:31:46.973 [planetinfo.record]: air_color = 0.638898, 0.593253, 0.839654, 1; 14:31:46.973 [planetinfo.record]: cloud_alpha = 1.000000; 14:31:46.973 [planetinfo.record]: cloud_color = 0.458463, 0.374817, 0.521484, 1; 14:31:46.973 [planetinfo.record]: cloud_fraction = 0.320000; 14:31:46.973 [planetinfo.record]: land_color = 0.384141, 0.595345, 0.66, 1; 14:31:46.973 [planetinfo.record]: land_fraction = 0.490000; 14:31:46.973 [planetinfo.record]: polar_cloud_color = 0.679178, 0.605527, 0.734668, 1; 14:31:46.973 [planetinfo.record]: polar_land_color = 0.836404, 0.911126, 0.934, 1; 14:31:46.973 [planetinfo.record]: polar_sea_color = 0.940625, 0.89681, 0.883398, 1; 14:31:46.973 [planetinfo.record]: sea_color = 0.59375, 0.483121, 0.449255, 1; 14:31:46.973 [planetinfo.record]: rotation_speed = 0.002374 14:31:46.974 [planetinfo.record]: planet zpos = 669840.000000 14:31:46.974 [planetinfo.record]: sun_radius = 179126.359558 14:31:46.974 [planetinfo.record]: sun_vector = 0.753 0.040 0.656 14:31:46.974 [planetinfo.record]: sun_distance = 1283860 14:31:46.974 [planetinfo.record]: corona_flare = 0.019133 14:31:46.974 [planetinfo.record]: corona_hues = 0.743187 14:31:46.974 [planetinfo.record]: sun_color = 0.735233, 0.547883, 0.467968, 1 14:31:46.974 [planetinfo.record]: corona_shimmer = 0.262948 14:31:46.974 [planetinfo.record]: station_vector = 0.992 -0.101 0.077 14:31:46.974 [planetinfo.record]: station = coriolis 14:31:51.303 [PLANETINFO OVER]: Done 14:31:51.305 [PLANETINFO LOGGING]: 6 246 14:31:52.321 [planetinfo.record]: seed = 170 79 20 245 14:31:52.321 [planetinfo.record]: coordinates = 79 15 14:31:52.321 [planetinfo.record]: government = 5; 14:31:52.321 [planetinfo.record]: economy = 7; 14:31:52.321 [planetinfo.record]: techlevel = 6; 14:31:52.322 [planetinfo.record]: population = 37; 14:31:52.322 [planetinfo.record]: productivity = 7992; 14:31:52.322 [planetinfo.record]: name = "Laraedle"; 14:31:52.322 [planetinfo.record]: inhabitant = "Human Colonial"; 14:31:52.322 [planetinfo.record]: inhabitants = "Human Colonials"; 14:31:52.322 [planetinfo.record]: description = "The planet Laraedle is scourged by deadly disease."; 14:31:52.324 [planetinfo.record]: air_color = 0.502392, 0.462569, 0.937863, 1; 14:31:52.325 [planetinfo.record]: cloud_alpha = 1.000000; 14:31:52.325 [planetinfo.record]: cloud_color = 0.263698, 0.0637817, 0.816406, 1; 14:31:52.325 [planetinfo.record]: cloud_fraction = 0.420000; 14:31:52.325 [planetinfo.record]: land_color = 0.267883, 0.436959, 0.519531, 1; 14:31:52.325 [planetinfo.record]: land_fraction = 0.440000; 14:31:52.325 [planetinfo.record]: polar_cloud_color = 0.50037, 0.367621, 0.867383, 1; 14:31:52.325 [planetinfo.record]: polar_land_color = 0.833244, 0.910377, 0.948047, 1; 14:31:52.325 [planetinfo.record]: polar_sea_color = 0.857076, 0.916602, 0.904045, 1; 14:31:52.325 [planetinfo.record]: sea_color = 0.617344, 0.833984, 0.788287, 1; 14:31:52.325 [planetinfo.record]: rotation_speed = 0.004108 14:31:52.325 [planetinfo.record]: planet zpos = 501000.000000 14:31:52.325 [planetinfo.record]: sun_radius = 95861.404419 14:31:52.325 [planetinfo.record]: sun_vector = 0.155 -0.910 -0.383 14:31:52.325 [planetinfo.record]: sun_distance = 1002000 14:31:52.326 [planetinfo.record]: corona_flare = 0.029614 14:31:52.326 [planetinfo.record]: corona_hues = 0.841080 14:31:52.326 [planetinfo.record]: sun_color = 0.194659, 0.544415, 0.678638, 1 14:31:52.326 [planetinfo.record]: corona_shimmer = 0.903970 14:31:52.326 [planetinfo.record]: station_vector = -0.511 -0.783 0.355 14:31:52.326 [planetinfo.record]: station = coriolis 14:31:57.674 [PLANETINFO OVER]: Done 14:31:57.675 [PLANETINFO LOGGING]: 6 247 14:31:58.695 [planetinfo.record]: seed = 242 225 208 206 14:31:58.696 [planetinfo.record]: coordinates = 225 152 14:31:58.696 [planetinfo.record]: government = 6; 14:31:58.696 [planetinfo.record]: economy = 0; 14:31:58.696 [planetinfo.record]: techlevel = 11; 14:31:58.696 [planetinfo.record]: population = 51; 14:31:58.696 [planetinfo.record]: productivity = 40800; 14:31:58.696 [planetinfo.record]: name = "Reesquen"; 14:31:58.696 [planetinfo.record]: inhabitant = "Bug-Eyed Lobster"; 14:31:58.696 [planetinfo.record]: inhabitants = "Bug-Eyed Lobsters"; 14:31:58.696 [planetinfo.record]: description = "This planet is plagued by frequent solar activity."; 14:31:58.724 [planetinfo.record]: air_color = 0.468863, 0.508037, 0.914449, 1; 14:31:58.724 [planetinfo.record]: cloud_alpha = 1.000000; 14:31:58.724 [planetinfo.record]: cloud_color = 0.102005, 0.263027, 0.746094, 1; 14:31:58.724 [planetinfo.record]: cloud_fraction = 0.390000; 14:31:58.724 [planetinfo.record]: land_color = 0.132767, 0.601562, 0.0657959, 1; 14:31:58.724 [planetinfo.record]: land_fraction = 0.560000; 14:31:58.724 [planetinfo.record]: polar_cloud_color = 0.384817, 0.497548, 0.835742, 1; 14:31:58.724 [planetinfo.record]: polar_land_color = 0.756739, 0.939844, 0.730582, 1; 14:31:58.724 [planetinfo.record]: polar_sea_color = 0.930469, 0.900256, 0.86141, 1; 14:31:58.724 [planetinfo.record]: sea_color = 0.695312, 0.605003, 0.488892, 1; 14:31:58.724 [planetinfo.record]: rotation_speed = 0.003956 14:31:58.725 [planetinfo.record]: planet zpos = 927500.000000 14:31:58.725 [planetinfo.record]: sun_radius = 162416.419983 14:31:58.725 [planetinfo.record]: sun_vector = 0.033 0.429 -0.903 14:31:58.725 [planetinfo.record]: sun_distance = 1391250 14:31:58.725 [planetinfo.record]: corona_flare = 0.068376 14:31:58.725 [planetinfo.record]: corona_hues = 0.538597 14:31:58.725 [planetinfo.record]: sun_color = 0.769174, 0.717265, 0.683737, 1 14:31:58.725 [planetinfo.record]: corona_shimmer = 0.457213 14:31:58.726 [planetinfo.record]: station_vector = -0.482 -0.774 0.411 14:31:58.726 [planetinfo.record]: station = dodecahedron 14:32:03.784 [PLANETINFO OVER]: Done 14:32:03.785 [PLANETINFO LOGGING]: 6 248 14:32:04.789 [planetinfo.record]: seed = 178 18 148 86 14:32:04.789 [planetinfo.record]: coordinates = 18 250 14:32:04.789 [planetinfo.record]: government = 6; 14:32:04.789 [planetinfo.record]: economy = 2; 14:32:04.789 [planetinfo.record]: techlevel = 10; 14:32:04.789 [planetinfo.record]: population = 49; 14:32:04.789 [planetinfo.record]: productivity = 31360; 14:32:04.789 [planetinfo.record]: name = "Vegeinso"; 14:32:04.789 [planetinfo.record]: inhabitant = "Yellow Slimy Lizard"; 14:32:04.789 [planetinfo.record]: inhabitants = "Yellow Slimy Lizards"; 14:32:04.789 [planetinfo.record]: description = "The world Vegeinso is scourged by deadly tree grubs."; 14:32:04.816 [planetinfo.record]: air_color = 0.7296, 0.630636, 0.842906, 1; 14:32:04.816 [planetinfo.record]: cloud_alpha = 1.000000; 14:32:04.816 [planetinfo.record]: cloud_color = 0.53125, 0.448242, 0.49688, 1; 14:32:04.816 [planetinfo.record]: cloud_fraction = 0.090000; 14:32:04.816 [planetinfo.record]: land_color = 0.574219, 0.41272, 0.545199, 1; 14:32:04.816 [planetinfo.record]: land_fraction = 0.320000; 14:32:04.816 [planetinfo.record]: polar_cloud_color = 0.739062, 0.666888, 0.709178, 1; 14:32:04.816 [planetinfo.record]: polar_land_color = 0.942578, 0.876303, 0.930669, 1; 14:32:04.816 [planetinfo.record]: polar_sea_color = 0.931055, 0.842859, 0.842859, 1; 14:32:04.816 [planetinfo.record]: sea_color = 0.689453, 0.428215, 0.428215, 1; 14:32:04.816 [planetinfo.record]: rotation_speed = 0.004338 14:32:04.816 [planetinfo.record]: planet zpos = 655500.000000 14:32:04.816 [planetinfo.record]: sun_radius = 94792.912292 14:32:04.816 [planetinfo.record]: sun_vector = 0.537 0.535 -0.652 14:32:04.816 [planetinfo.record]: sun_distance = 742900 14:32:04.816 [planetinfo.record]: corona_flare = 0.046298 14:32:04.816 [planetinfo.record]: corona_hues = 0.665855 14:32:04.816 [planetinfo.record]: sun_color = 0.815878, 0.73446, 0.569578, 1 14:32:04.817 [planetinfo.record]: corona_shimmer = 0.469073 14:32:04.817 [planetinfo.record]: station_vector = 0.996 0.066 0.060 14:32:04.817 [planetinfo.record]: station = coriolis 14:32:10.561 [PLANETINFO OVER]: Done 14:32:10.563 [PLANETINFO LOGGING]: 6 249 14:32:11.571 [planetinfo.record]: seed = 10 135 128 183 14:32:11.571 [planetinfo.record]: coordinates = 135 204 14:32:11.571 [planetinfo.record]: government = 1; 14:32:11.571 [planetinfo.record]: economy = 6; 14:32:11.571 [planetinfo.record]: techlevel = 5; 14:32:11.571 [planetinfo.record]: population = 28; 14:32:11.571 [planetinfo.record]: productivity = 4480; 14:32:11.571 [planetinfo.record]: name = "Timaesin"; 14:32:11.571 [planetinfo.record]: inhabitant = "Harmless Bony Feline"; 14:32:11.571 [planetinfo.record]: inhabitants = "Harmless Bony Felines"; 14:32:11.571 [planetinfo.record]: description = "The planet Timaesin is very famous for its exotic fish cutlet but plagued by deadly earthquakes."; 14:32:11.573 [planetinfo.record]: air_color = 0.643527, 0.88234, 0.926156, 1; 14:32:11.573 [planetinfo.record]: cloud_alpha = 1.000000; 14:32:11.573 [planetinfo.record]: cloud_color = 0.663447, 0.78125, 0.534058, 1; 14:32:11.573 [planetinfo.record]: cloud_fraction = 0.200000; 14:32:11.573 [planetinfo.record]: land_color = 0.603991, 0.605469, 0.593643, 1; 14:32:11.573 [planetinfo.record]: land_fraction = 0.380000; 14:32:11.573 [planetinfo.record]: polar_cloud_color = 0.771309, 0.851562, 0.683163, 1; 14:32:11.573 [planetinfo.record]: polar_land_color = 0.93888, 0.939453, 0.934866, 1; 14:32:11.573 [planetinfo.record]: polar_sea_color = 0.874587, 0.916016, 0.814037, 1; 14:32:11.573 [planetinfo.record]: sea_color = 0.687909, 0.839844, 0.465851, 1; 14:32:11.573 [planetinfo.record]: rotation_speed = 0.000495 14:32:11.573 [planetinfo.record]: planet zpos = 426870.000000 14:32:11.573 [planetinfo.record]: sun_radius = 127127.250824 14:32:11.573 [planetinfo.record]: sun_vector = 0.535 -0.326 0.780 14:32:11.573 [planetinfo.record]: sun_distance = 948600 14:32:11.573 [planetinfo.record]: corona_flare = 0.073763 14:32:11.574 [planetinfo.record]: corona_hues = 0.954910 14:32:11.574 [planetinfo.record]: sun_color = 0.653616, 0.641932, 0.638335, 1 14:32:11.574 [planetinfo.record]: corona_shimmer = 0.377355 14:32:11.574 [planetinfo.record]: station_vector = 0.703 0.529 0.475 14:32:11.574 [planetinfo.record]: station = coriolis 14:32:16.992 [PLANETINFO OVER]: Done 14:32:16.993 [PLANETINFO LOGGING]: 6 250 14:32:17.995 [planetinfo.record]: seed = 218 12 52 98 14:32:17.996 [planetinfo.record]: coordinates = 12 73 14:32:17.996 [planetinfo.record]: government = 3; 14:32:17.996 [planetinfo.record]: economy = 1; 14:32:17.996 [planetinfo.record]: techlevel = 8; 14:32:17.996 [planetinfo.record]: population = 37; 14:32:17.996 [planetinfo.record]: productivity = 18648; 14:32:17.996 [planetinfo.record]: name = "Xeorusge"; 14:32:17.996 [planetinfo.record]: inhabitant = "Human Colonial"; 14:32:17.996 [planetinfo.record]: inhabitants = "Human Colonials"; 14:32:17.996 [planetinfo.record]: description = "The world Xeorusge is scourged by deadly tree snakes."; 14:32:17.999 [planetinfo.record]: air_color = 0.627947, 0.589519, 0.839004, 1; 14:32:17.999 [planetinfo.record]: cloud_alpha = 1.000000; 14:32:17.999 [planetinfo.record]: cloud_color = 0.439861, 0.367325, 0.519531, 1; 14:32:17.999 [planetinfo.record]: cloud_fraction = 0.180000; 14:32:17.999 [planetinfo.record]: land_color = 0.223877, 0.511719, 0.43751, 1; 14:32:17.1000 [planetinfo.record]: land_fraction = 0.380000; 14:32:17.1000 [planetinfo.record]: polar_cloud_color = 0.66346, 0.599428, 0.733789, 1; 14:32:17.1000 [planetinfo.record]: polar_land_color = 0.815399, 0.948828, 0.914428, 1; 14:32:17.1000 [planetinfo.record]: polar_sea_color = 0.883586, 0.930664, 0.895355, 1; 14:32:17.1000 [planetinfo.record]: sea_color = 0.553062, 0.693359, 0.588137, 1; 14:32:17.1000 [planetinfo.record]: rotation_speed = 0.004655 14:32:17.1000 [planetinfo.record]: planet zpos = 367400.000000 14:32:17.1000 [planetinfo.record]: sun_radius = 92122.149658 14:32:17.1000 [planetinfo.record]: sun_vector = 0.936 0.027 -0.352 14:32:18.000 [planetinfo.record]: sun_distance = 634600 14:32:18.000 [planetinfo.record]: corona_flare = 0.025081 14:32:18.000 [planetinfo.record]: corona_hues = 0.570366 14:32:18.000 [planetinfo.record]: sun_color = 0.558289, 0.728177, 0.748309, 1 14:32:18.000 [planetinfo.record]: corona_shimmer = 0.324070 14:32:18.001 [planetinfo.record]: station_vector = 0.080 -0.155 0.985 14:32:18.001 [planetinfo.record]: station = coriolis 14:32:22.681 [PLANETINFO OVER]: Done 14:32:22.682 [PLANETINFO LOGGING]: 6 251 14:32:23.685 [planetinfo.record]: seed = 66 67 80 36 14:32:23.685 [planetinfo.record]: coordinates = 67 40 14:32:23.686 [planetinfo.record]: government = 0; 14:32:23.686 [planetinfo.record]: economy = 2; 14:32:23.686 [planetinfo.record]: techlevel = 8; 14:32:23.686 [planetinfo.record]: population = 35; 14:32:23.686 [planetinfo.record]: productivity = 8960; 14:32:23.686 [planetinfo.record]: name = "Zaati"; 14:32:23.686 [planetinfo.record]: inhabitant = "Human Colonial"; 14:32:23.686 [planetinfo.record]: inhabitants = "Human Colonials"; 14:32:23.686 [planetinfo.record]: description = "The world Zaati is very famous for its hoopy night life but scourged by deadly tree grubs."; 14:32:23.716 [planetinfo.record]: air_color = 0.699374, 0.826776, 0.907945, 1; 14:32:23.716 [planetinfo.record]: cloud_alpha = 1.000000; 14:32:23.716 [planetinfo.record]: cloud_color = 0.661285, 0.726562, 0.673015, 1; 14:32:23.716 [planetinfo.record]: cloud_fraction = 0.300000; 14:32:23.716 [planetinfo.record]: land_color = 0.623141, 0.66, 0.582656, 1; 14:32:23.716 [planetinfo.record]: land_fraction = 0.310000; 14:32:23.716 [planetinfo.record]: polar_cloud_color = 0.780518, 0.826953, 0.788862, 1; 14:32:23.716 [planetinfo.record]: polar_land_color = 0.92096, 0.934, 0.906637, 1; 14:32:23.716 [planetinfo.record]: polar_sea_color = 0.734379, 0.933008, 0.914386, 1; 14:32:23.716 [planetinfo.record]: sea_color = 0.0994415, 0.669922, 0.616439, 1; 14:32:23.716 [planetinfo.record]: rotation_speed = 0.001979 14:32:23.716 [planetinfo.record]: planet zpos = 390700.000000 14:32:23.716 [planetinfo.record]: sun_radius = 72744.743958 14:32:23.716 [planetinfo.record]: sun_vector = -0.786 0.379 -0.488 14:32:23.716 [planetinfo.record]: sun_distance = 859540 14:32:23.716 [planetinfo.record]: corona_flare = 0.033560 14:32:23.716 [planetinfo.record]: corona_hues = 0.540367 14:32:23.716 [planetinfo.record]: sun_color = 0.204831, 0.414021, 0.754337, 1 14:32:23.717 [planetinfo.record]: corona_shimmer = 0.277277 14:32:23.717 [planetinfo.record]: station_vector = 0.918 -0.166 0.361 14:32:23.717 [planetinfo.record]: station = coriolis 14:32:28.567 [PLANETINFO OVER]: Done 14:32:28.568 [PLANETINFO LOGGING]: 6 252 14:32:29.573 [planetinfo.record]: seed = 34 171 244 49 14:32:29.573 [planetinfo.record]: coordinates = 171 247 14:32:29.573 [planetinfo.record]: government = 4; 14:32:29.573 [planetinfo.record]: economy = 7; 14:32:29.573 [planetinfo.record]: techlevel = 5; 14:32:29.573 [planetinfo.record]: population = 32; 14:32:29.573 [planetinfo.record]: productivity = 6144; 14:32:29.573 [planetinfo.record]: name = "Atraat"; 14:32:29.573 [planetinfo.record]: inhabitant = "Red Fat Humanoid"; 14:32:29.573 [planetinfo.record]: inhabitants = "Red Fat Humanoids"; 14:32:29.573 [planetinfo.record]: description = "This world is plagued by occasional solar activity."; 14:32:29.594 [planetinfo.record]: air_color = 0.651811, 0.474117, 0.961277, 1; 14:32:29.595 [planetinfo.record]: cloud_alpha = 1.000000; 14:32:29.595 [planetinfo.record]: cloud_color = 0.886719, 0.0692749, 0.797311, 1; 14:32:29.595 [planetinfo.record]: cloud_fraction = 0.310000; 14:32:29.595 [planetinfo.record]: land_color = 0.595703, 0.449104, 0.500643, 1; 14:32:29.595 [planetinfo.record]: land_fraction = 0.530000; 14:32:29.596 [planetinfo.record]: polar_cloud_color = 0.899023, 0.381031, 0.842368, 1; 14:32:29.596 [planetinfo.record]: polar_land_color = 0.94043, 0.882571, 0.902912, 1; 14:32:29.596 [planetinfo.record]: polar_sea_color = 0.931055, 0.896426, 0.862862, 1; 14:32:29.596 [planetinfo.record]: sea_color = 0.689453, 0.586881, 0.487465, 1; 14:32:29.596 [planetinfo.record]: rotation_speed = 0.004970 14:32:29.596 [planetinfo.record]: planet zpos = 356730.000000 14:32:29.596 [planetinfo.record]: sun_radius = 70109.784393 14:32:29.596 [planetinfo.record]: sun_vector = -0.412 0.161 -0.897 14:32:29.596 [planetinfo.record]: sun_distance = 648600 14:32:29.597 [planetinfo.record]: corona_flare = 0.059561 14:32:29.597 [planetinfo.record]: corona_hues = 0.681694 14:32:29.597 [planetinfo.record]: sun_color = 0.829419, 0.62947, 0.362358, 1 14:32:29.597 [planetinfo.record]: corona_shimmer = 0.287603 14:32:29.598 [planetinfo.record]: station_vector = 0.723 0.206 0.659 14:32:29.598 [planetinfo.record]: station = coriolis 14:32:33.882 [PLANETINFO OVER]: Done 14:32:33.883 [PLANETINFO LOGGING]: 6 253 14:32:34.886 [planetinfo.record]: seed = 154 183 64 61 14:32:34.886 [planetinfo.record]: coordinates = 183 177 14:32:34.886 [planetinfo.record]: government = 3; 14:32:34.886 [planetinfo.record]: economy = 1; 14:32:34.886 [planetinfo.record]: techlevel = 11; 14:32:34.886 [planetinfo.record]: population = 49; 14:32:34.886 [planetinfo.record]: productivity = 24696; 14:32:34.886 [planetinfo.record]: name = "Isbianri"; 14:32:34.886 [planetinfo.record]: inhabitant = "Human Colonial"; 14:32:34.886 [planetinfo.record]: inhabitants = "Human Colonials"; 14:32:34.886 [planetinfo.record]: description = "This world is reasonably notable for its exotic cuisine but ravaged by vicious disease."; 14:32:34.902 [planetinfo.record]: air_color = 0.619035, 0.890588, 0.94957, 1; 14:32:34.903 [planetinfo.record]: cloud_alpha = 1.000000; 14:32:34.903 [planetinfo.record]: cloud_color = 0.644181, 0.851562, 0.472351, 1; 14:32:34.903 [planetinfo.record]: cloud_fraction = 0.460000; 14:32:34.903 [planetinfo.record]: land_color = 0.66, 0.514658, 0.20625, 1; 14:32:34.903 [planetinfo.record]: land_fraction = 0.280000; 14:32:34.903 [planetinfo.record]: polar_cloud_color = 0.748774, 0.883203, 0.63739, 1; 14:32:34.907 [planetinfo.record]: polar_land_color = 0.934, 0.88258, 0.773469, 1; 14:32:34.907 [planetinfo.record]: polar_sea_color = 0.944641, 0.888314, 0.949414, 1; 14:32:34.907 [planetinfo.record]: sea_color = 0.495686, 0.37564, 0.505859, 1; 14:32:34.908 [planetinfo.record]: rotation_speed = 0.003571 14:32:34.908 [planetinfo.record]: planet zpos = 695970.000000 14:32:34.908 [planetinfo.record]: sun_radius = 169450.182037 14:32:34.915 [planetinfo.record]: sun_vector = 0.377 0.923 0.080 14:32:34.915 [planetinfo.record]: sun_distance = 1391940 14:32:34.916 [planetinfo.record]: corona_flare = 0.021786 14:32:34.916 [planetinfo.record]: corona_hues = 0.552826 14:32:34.916 [planetinfo.record]: sun_color = 0.651151, 0.750641, 0.783603, 1 14:32:34.916 [planetinfo.record]: corona_shimmer = 0.285979 14:32:34.916 [planetinfo.record]: station_vector = 0.177 0.984 0.029 14:32:34.916 [planetinfo.record]: station = dodecahedron 14:32:40.016 [PLANETINFO OVER]: Done 14:32:40.016 [PLANETINFO LOGGING]: 6 254 14:32:41.020 [planetinfo.record]: seed = 138 126 212 191 14:32:41.020 [planetinfo.record]: coordinates = 126 155 14:32:41.020 [planetinfo.record]: government = 1; 14:32:41.020 [planetinfo.record]: economy = 3; 14:32:41.020 [planetinfo.record]: techlevel = 7; 14:32:41.020 [planetinfo.record]: population = 33; 14:32:41.020 [planetinfo.record]: productivity = 9240; 14:32:41.020 [planetinfo.record]: name = "Onorti"; 14:32:41.020 [planetinfo.record]: inhabitant = "Harmless Furry Rodent"; 14:32:41.020 [planetinfo.record]: inhabitants = "Harmless Furry Rodents"; 14:32:41.020 [planetinfo.record]: description = "The planet Onorti is famous for Onortiian shrew steak but ravaged by unpredictable solar activity."; 14:32:41.034 [planetinfo.record]: air_color = 0.726133, 0.718486, 0.980789, 1; 14:32:41.034 [planetinfo.record]: cloud_alpha = 1.000000; 14:32:41.034 [planetinfo.record]: cloud_color = 0.772798, 0.753296, 0.945312, 1; 14:32:41.034 [planetinfo.record]: cloud_fraction = 0.460000; 14:32:41.034 [planetinfo.record]: land_color = 0.403425, 0.535156, 0.0794373, 1; 14:32:41.034 [planetinfo.record]: land_fraction = 0.370000; 14:32:41.034 [planetinfo.record]: polar_cloud_color = 0.819841, 0.807909, 0.925391, 1; 14:32:41.034 [planetinfo.record]: polar_land_color = 0.888239, 0.946484, 0.744987, 1; 14:32:41.034 [planetinfo.record]: polar_sea_color = 0.903516, 0.85834, 0.878457, 1; 14:32:41.034 [planetinfo.record]: sea_color = 0.964844, 0.771875, 0.857806, 1; 14:32:41.034 [planetinfo.record]: rotation_speed = 0.000341 14:32:41.035 [planetinfo.record]: planet zpos = 813840.000000 14:32:41.035 [planetinfo.record]: sun_radius = 164791.754761 14:32:41.035 [planetinfo.record]: sun_vector = -0.461 -0.541 0.703 14:32:41.035 [planetinfo.record]: sun_distance = 1356400 14:32:41.035 [planetinfo.record]: corona_flare = 0.035158 14:32:41.035 [planetinfo.record]: corona_hues = 0.810577 14:32:41.035 [planetinfo.record]: sun_color = 0.686649, 0.625495, 0.279687, 1 14:32:41.035 [planetinfo.record]: corona_shimmer = 0.415347 14:32:41.035 [planetinfo.record]: station_vector = -0.272 0.547 0.792 14:32:41.035 [planetinfo.record]: station = coriolis 14:32:46.079 [PLANETINFO OVER]: Done 14:32:46.080 [PLANETINFO LOGGING]: 6 255 14:32:47.101 [planetinfo.record]: seed = 18 177 80 162 14:32:47.101 [planetinfo.record]: coordinates = 177 23 14:32:47.101 [planetinfo.record]: government = 2; 14:32:47.101 [planetinfo.record]: economy = 7; 14:32:47.101 [planetinfo.record]: techlevel = 2; 14:32:47.101 [planetinfo.record]: population = 18; 14:32:47.101 [planetinfo.record]: productivity = 2592; 14:32:47.101 [planetinfo.record]: name = "Xemariin"; 14:32:47.101 [planetinfo.record]: inhabitant = "Human Colonial"; 14:32:47.101 [planetinfo.record]: inhabitants = "Human Colonials"; 14:32:47.102 [planetinfo.record]: description = "The planet Xemariin is mildly notable for vicious Enetit brew."; 14:32:47.106 [planetinfo.record]: air_color = 0.498399, 0.680065, 0.834451, 1; 14:32:47.106 [planetinfo.record]: cloud_alpha = 1.000000; 14:32:47.106 [planetinfo.record]: cloud_color = 0.197601, 0.505859, 0.318015, 1; 14:32:47.106 [planetinfo.record]: cloud_fraction = 0.290000; 14:32:47.106 [planetinfo.record]: land_color = 0.30266, 0.285255, 0.603516, 1; 14:32:47.106 [planetinfo.record]: land_fraction = 0.590000; 14:32:47.107 [planetinfo.record]: polar_cloud_color = 0.450509, 0.727637, 0.558762, 1; 14:32:47.107 [planetinfo.record]: polar_land_color = 0.822544, 0.815769, 0.939648, 1; 14:32:47.107 [planetinfo.record]: polar_sea_color = 0.891405, 0.926172, 0.865211, 1; 14:32:47.107 [planetinfo.record]: sea_color = 0.627426, 0.738281, 0.543906, 1; 14:32:47.107 [planetinfo.record]: rotation_speed = 0.000179 14:32:47.107 [planetinfo.record]: planet zpos = 420600.000000 14:32:47.107 [planetinfo.record]: sun_radius = 118761.183167 14:32:47.107 [planetinfo.record]: sun_vector = 0.142 0.856 0.497 14:32:47.107 [planetinfo.record]: sun_distance = 630900 14:32:47.107 [planetinfo.record]: corona_flare = 0.030605 14:32:47.107 [planetinfo.record]: corona_hues = 0.674568 14:32:47.107 [planetinfo.record]: sun_color = 0.802582, 0.776591, 0.549485, 1 14:32:47.107 [planetinfo.record]: corona_shimmer = 0.473820 14:32:47.108 [planetinfo.record]: station_vector = -0.231 -0.168 0.958 14:32:47.108 [planetinfo.record]: station = coriolis 14:32:51.779 [PLANETINFO OVER]: Done 14:34:05.061 [planetinfo.record]: seed = 150 174 175 188 14:34:05.061 [planetinfo.record]: coordinates = 174 17 14:34:05.061 [planetinfo.record]: government = 2; 14:34:05.061 [planetinfo.record]: economy = 1; 14:34:05.061 [planetinfo.record]: techlevel = 9; 14:34:05.061 [planetinfo.record]: population = 40; 14:34:05.061 [planetinfo.record]: productivity = 17280; 14:34:05.061 [planetinfo.record]: name = "Teteus"; 14:34:05.061 [planetinfo.record]: inhabitant = "Harmless Insect"; 14:34:05.061 [planetinfo.record]: inhabitants = "Harmless Insects"; 14:34:05.061 [planetinfo.record]: description = "The world Teteus is most famous for its vast oceans."; 14:34:05.075 [planetinfo.record]: air_color = 0.603759, 0.584949, 0.8325, 1; 14:34:05.075 [planetinfo.record]: cloud_alpha = 1.000000; 14:34:05.075 [planetinfo.record]: cloud_color = 0.388992, 0.353516, 0.5, 1; 14:34:05.075 [planetinfo.record]: cloud_fraction = 0.470000; 14:34:05.075 [planetinfo.record]: land_color = 0.491495, 0.66, 0.430547, 1; 14:34:05.075 [planetinfo.record]: land_fraction = 0.200000; 14:34:05.075 [planetinfo.record]: polar_cloud_color = 0.624399, 0.592249, 0.725, 1; 14:34:05.075 [planetinfo.record]: polar_land_color = 0.874385, 0.934, 0.852822, 1; 14:34:05.075 [planetinfo.record]: polar_sea_color = 0.940234, 0.889655, 0.881378, 1; 14:34:05.075 [planetinfo.record]: sea_color = 0.597656, 0.469053, 0.448009, 1; 14:34:05.076 [planetinfo.record]: rotation_speed = 0.001480 14:34:05.076 [planetinfo.record]: planet zpos = 788060.000000 14:34:05.076 [planetinfo.record]: sun_radius = 146562.465820 14:34:05.076 [planetinfo.record]: sun_vector = 0.695 -0.627 0.351 14:34:05.077 [planetinfo.record]: sun_distance = 1273020 14:34:05.077 [planetinfo.record]: corona_flare = 0.070970 14:34:05.077 [planetinfo.record]: corona_hues = 0.935585 14:34:05.077 [planetinfo.record]: sun_color = 0.650986, 0.579158, 0.559068, 1 14:34:05.078 [planetinfo.record]: corona_shimmer = 0.270147 14:34:05.078 [planetinfo.record]: station_vector = -0.383 -0.140 0.913 14:34:05.078 [planetinfo.record]: station = coriolis 14:34:09.296 [PLANETINFO OVER]: Done 14:34:19.164 [PLANETINFO LOGGING]: 7 0 14:34:19.935 [planetinfo.record]: seed = 36 1 169 219 14:34:19.935 [planetinfo.record]: coordinates = 1 45 14:34:19.935 [planetinfo.record]: government = 4; 14:34:19.935 [planetinfo.record]: economy = 5; 14:34:19.935 [planetinfo.record]: techlevel = 5; 14:34:19.936 [planetinfo.record]: population = 30; 14:34:19.936 [planetinfo.record]: productivity = 9600; 14:34:19.936 [planetinfo.record]: name = "Anesbi"; 14:34:19.936 [planetinfo.record]: inhabitant = "Fat Insect"; 14:34:19.936 [planetinfo.record]: inhabitants = "Fat Insects"; 14:34:19.936 [planetinfo.record]: description = "This world is a tedious little planet."; 14:34:19.948 [planetinfo.record]: air_color = 0.703877, 0.708852, 0.985342, 1; 14:34:19.948 [planetinfo.record]: cloud_alpha = 1.000000; 14:34:19.948 [planetinfo.record]: cloud_color = 0.711746, 0.721404, 0.958984, 1; 14:34:19.948 [planetinfo.record]: cloud_fraction = 0.340000; 14:34:19.948 [planetinfo.record]: land_color = 0.66, 0.450628, 0.311953, 1; 14:34:19.948 [planetinfo.record]: land_fraction = 0.520000; 14:34:19.948 [planetinfo.record]: polar_cloud_color = 0.781441, 0.787304, 0.931543, 1; 14:34:19.948 [planetinfo.record]: polar_land_color = 0.934, 0.859927, 0.810865, 1; 14:34:19.948 [planetinfo.record]: polar_sea_color = 0.828375, 0.849071, 0.943555, 1; 14:34:19.948 [planetinfo.record]: sea_color = 0.288841, 0.338365, 0.564453, 1; 14:34:19.948 [planetinfo.record]: rotation_speed = 0.001311 14:34:19.948 [planetinfo.record]: planet zpos = 563300.000000 14:34:19.948 [planetinfo.record]: sun_radius = 162575.345612 14:34:19.949 [planetinfo.record]: sun_vector = 0.406 -0.293 -0.866 14:34:19.949 [planetinfo.record]: sun_distance = 957610 14:34:19.949 [planetinfo.record]: corona_flare = 0.092725 14:34:19.949 [planetinfo.record]: corona_hues = 0.566940 14:34:19.949 [planetinfo.record]: sun_color = 0.669214, 0.643871, 0.632644, 1 14:34:19.949 [planetinfo.record]: corona_shimmer = 0.443212 14:34:19.949 [planetinfo.record]: station_vector = -0.589 0.741 0.323 14:34:19.949 [planetinfo.record]: station = coriolis 14:34:25.192 [PLANETINFO OVER]: Done 14:34:25.193 [PLANETINFO LOGGING]: 7 1 14:34:26.200 [planetinfo.record]: seed = 90 204 11 189 14:34:26.200 [planetinfo.record]: coordinates = 204 230 14:34:26.200 [planetinfo.record]: government = 3; 14:34:26.200 [planetinfo.record]: economy = 6; 14:34:26.200 [planetinfo.record]: techlevel = 3; 14:34:26.200 [planetinfo.record]: population = 22; 14:34:26.200 [planetinfo.record]: productivity = 4928; 14:34:26.200 [planetinfo.record]: name = "Iseror"; 14:34:26.200 [planetinfo.record]: inhabitant = "Human Colonial"; 14:34:26.200 [planetinfo.record]: inhabitants = "Human Colonials"; 14:34:26.200 [planetinfo.record]: description = "The world Iseror is notable for its weird tropical forests and Iserorian Onenst brandy."; 14:34:26.223 [planetinfo.record]: air_color = 0.536042, 0.607603, 0.839004, 1; 14:34:26.223 [planetinfo.record]: cloud_alpha = 1.000000; 14:34:26.223 [planetinfo.record]: cloud_color = 0.267883, 0.442857, 0.519531, 1; 14:34:26.223 [planetinfo.record]: cloud_fraction = 0.260000; 14:34:26.223 [planetinfo.record]: land_color = 0.50491, 0.66, 0.299063, 1; 14:34:26.223 [planetinfo.record]: land_fraction = 0.650000; 14:34:26.223 [planetinfo.record]: polar_cloud_color = 0.511646, 0.666105, 0.733789, 1; 14:34:26.224 [planetinfo.record]: polar_land_color = 0.879131, 0.934, 0.806305, 1; 14:34:26.224 [planetinfo.record]: polar_sea_color = 0.90848, 0.929688, 0.870674, 1; 14:34:26.224 [planetinfo.record]: sea_color = 0.638967, 0.703125, 0.524597, 1; 14:34:26.224 [planetinfo.record]: rotation_speed = 0.004922 14:34:26.224 [planetinfo.record]: planet zpos = 761760.000000 14:34:26.224 [planetinfo.record]: sun_radius = 175138.584595 14:34:26.224 [planetinfo.record]: sun_vector = 0.028 0.801 0.598 14:34:26.224 [planetinfo.record]: sun_distance = 1142640 14:34:26.224 [planetinfo.record]: corona_flare = 0.056836 14:34:26.224 [planetinfo.record]: corona_hues = 0.524376 14:34:26.224 [planetinfo.record]: sun_color = 0.565305, 0.650283, 0.657941, 1 14:34:26.224 [planetinfo.record]: corona_shimmer = 0.635655 14:34:26.224 [planetinfo.record]: station_vector = -0.626 -0.352 0.696 14:34:26.224 [planetinfo.record]: station = coriolis 14:34:30.832 [PLANETINFO OVER]: Done 14:34:30.832 [PLANETINFO LOGGING]: 7 2 14:34:31.835 [planetinfo.record]: seed = 184 38 101 144 14:34:31.835 [planetinfo.record]: coordinates = 38 249 14:34:31.835 [planetinfo.record]: government = 7; 14:34:31.835 [planetinfo.record]: economy = 1; 14:34:31.836 [planetinfo.record]: techlevel = 12; 14:34:31.836 [planetinfo.record]: population = 57; 14:34:31.836 [planetinfo.record]: productivity = 45144; 14:34:31.836 [planetinfo.record]: name = "Ererso"; 14:34:31.836 [planetinfo.record]: inhabitant = "Human Colonial"; 14:34:31.836 [planetinfo.record]: inhabitants = "Human Colonials"; 14:34:31.836 [planetinfo.record]: description = "This planet is a dull place."; 14:34:31.839 [planetinfo.record]: air_color = 0.573782, 0.94827, 0.901107, 1; 14:34:31.839 [planetinfo.record]: cloud_alpha = 1.000000; 14:34:31.839 [planetinfo.record]: cloud_color = 0.847656, 0.665284, 0.350983, 1; 14:34:31.839 [planetinfo.record]: cloud_fraction = 0.360000; 14:34:31.839 [planetinfo.record]: land_color = 0.66, 0.118594, 0.397756, 1; 14:34:31.839 [planetinfo.record]: land_fraction = 0.620000; 14:34:31.839 [planetinfo.record]: polar_cloud_color = 0.881445, 0.762919, 0.55865, 1; 14:34:31.839 [planetinfo.record]: polar_land_color = 0.934, 0.742457, 0.841221, 1; 14:34:31.839 [planetinfo.record]: polar_sea_color = 0.937501, 0.939062, 0.889083, 1; 14:34:31.839 [planetinfo.record]: sea_color = 0.605321, 0.609375, 0.479645, 1; 14:34:31.839 [planetinfo.record]: rotation_speed = 0.004094 14:34:31.839 [planetinfo.record]: planet zpos = 342480.000000 14:34:31.839 [planetinfo.record]: sun_radius = 84645.382690 14:34:31.839 [planetinfo.record]: sun_vector = -0.783 -0.622 0.013 14:34:31.839 [planetinfo.record]: sun_distance = 599340 14:34:31.839 [planetinfo.record]: corona_flare = 0.042342 14:34:31.839 [planetinfo.record]: corona_hues = 0.575722 14:34:31.840 [planetinfo.record]: sun_color = 0.650943, 0.240349, 0.208744, 1 14:34:31.840 [planetinfo.record]: corona_shimmer = 1.083096 14:34:31.840 [planetinfo.record]: station_vector = -0.908 0.411 0.079 14:34:31.840 [planetinfo.record]: station = icosahedron 14:34:36.266 [PLANETINFO OVER]: Done 14:34:36.266 [PLANETINFO LOGGING]: 7 3 14:34:37.269 [planetinfo.record]: seed = 206 168 55 193 14:34:37.269 [planetinfo.record]: coordinates = 168 103 14:34:37.269 [planetinfo.record]: government = 1; 14:34:37.269 [planetinfo.record]: economy = 7; 14:34:37.269 [planetinfo.record]: techlevel = 1; 14:34:37.269 [planetinfo.record]: population = 13; 14:34:37.269 [planetinfo.record]: productivity = 1560; 14:34:37.269 [planetinfo.record]: name = "Leatanre"; 14:34:37.269 [planetinfo.record]: inhabitant = "Human Colonial"; 14:34:37.269 [planetinfo.record]: inhabitants = "Human Colonials"; 14:34:37.269 [planetinfo.record]: description = "The world Leatanre is most well known for its vast dense forests."; 14:34:37.308 [planetinfo.record]: air_color = 0.706952, 0.822726, 0.904043, 1; 14:34:37.308 [planetinfo.record]: cloud_alpha = 1.000000; 14:34:37.308 [planetinfo.record]: cloud_color = 0.675751, 0.714844, 0.685524, 1; 14:34:37.308 [planetinfo.record]: cloud_fraction = 0.200000; 14:34:37.308 [planetinfo.record]: land_color = 0.520781, 0.66, 0.552323, 1; 14:34:37.308 [planetinfo.record]: land_fraction = 0.330000; 14:34:37.308 [planetinfo.record]: polar_cloud_color = 0.793595, 0.82168, 0.800616, 1; 14:34:37.308 [planetinfo.record]: polar_land_color = 0.884746, 0.934, 0.895905, 1; 14:34:37.308 [planetinfo.record]: polar_sea_color = 0.922266, 0.884234, 0.818691, 1; 14:34:37.308 [planetinfo.record]: sea_color = 0.777344, 0.649123, 0.428146, 1; 14:34:37.308 [planetinfo.record]: rotation_speed = 0.004605 14:34:37.308 [planetinfo.record]: planet zpos = 356400.000000 14:34:37.308 [planetinfo.record]: sun_radius = 95591.271973 14:34:37.308 [planetinfo.record]: sun_vector = -0.237 0.970 -0.058 14:34:37.308 [planetinfo.record]: sun_distance = 583200 14:34:37.308 [planetinfo.record]: corona_flare = 0.007019 14:34:37.308 [planetinfo.record]: corona_hues = 0.899513 14:34:37.308 [planetinfo.record]: sun_color = 0.39995, 0.571799, 0.719714, 1 14:34:37.308 [planetinfo.record]: corona_shimmer = 0.279233 14:34:37.308 [planetinfo.record]: station_vector = 0.173 0.041 0.984 14:34:37.308 [planetinfo.record]: station = coriolis 14:34:42.168 [PLANETINFO OVER]: Done 14:34:42.169 [PLANETINFO LOGGING]: 7 4 14:34:43.184 [planetinfo.record]: seed = 204 206 97 220 14:34:43.185 [planetinfo.record]: coordinates = 206 59 14:34:43.185 [planetinfo.record]: government = 1; 14:34:43.185 [planetinfo.record]: economy = 3; 14:34:43.185 [planetinfo.record]: techlevel = 7; 14:34:43.185 [planetinfo.record]: population = 33; 14:34:43.185 [planetinfo.record]: productivity = 9240; 14:34:43.185 [planetinfo.record]: name = "Tebienla"; 14:34:43.185 [planetinfo.record]: inhabitant = "Human Colonial"; 14:34:43.185 [planetinfo.record]: inhabitants = "Human Colonials"; 14:34:43.185 [planetinfo.record]: description = "This planet is beset by dreadful earthquakes."; 14:34:43.190 [planetinfo.record]: air_color = 0.700936, 0.844515, 0.877377, 1; 14:34:43.191 [planetinfo.record]: cloud_alpha = 1.000000; 14:34:43.191 [planetinfo.record]: cloud_color = 0.633352, 0.634766, 0.632286, 1; 14:34:43.191 [planetinfo.record]: cloud_fraction = 0.650000; 14:34:43.191 [planetinfo.record]: land_color = 0.621449, 0.66, 0.574922, 1; 14:34:43.191 [planetinfo.record]: land_fraction = 0.500000; 14:34:43.191 [planetinfo.record]: polar_cloud_color = 0.784551, 0.785645, 0.783726, 1; 14:34:43.191 [planetinfo.record]: polar_land_color = 0.920361, 0.934, 0.9039, 1; 14:34:43.191 [planetinfo.record]: polar_sea_color = 0.728601, 0.931445, 0.931445, 1; 14:34:43.191 [planetinfo.record]: sea_color = 0.0883713, 0.685547, 0.685547, 1; 14:34:43.191 [planetinfo.record]: rotation_speed = 0.004467 14:34:43.191 [planetinfo.record]: planet zpos = 731280.000000 14:34:43.191 [planetinfo.record]: sun_radius = 143327.465515 14:34:43.191 [planetinfo.record]: sun_vector = 0.356 -0.280 0.892 14:34:43.191 [planetinfo.record]: sun_distance = 1462560 14:34:43.191 [planetinfo.record]: corona_flare = 0.088121 14:34:43.191 [planetinfo.record]: corona_hues = 0.766205 14:34:43.191 [planetinfo.record]: sun_color = 0.735809, 0.463877, 0.376971, 1 14:34:43.192 [planetinfo.record]: corona_shimmer = 0.371599 14:34:43.192 [planetinfo.record]: station_vector = 0.755 0.603 0.258 14:34:43.192 [planetinfo.record]: station = coriolis 14:34:47.790 [PLANETINFO OVER]: Done 14:34:47.791 [PLANETINFO LOGGING]: 7 5 14:34:48.796 [planetinfo.record]: seed = 130 85 163 206 14:34:48.796 [planetinfo.record]: coordinates = 85 146 14:34:48.796 [planetinfo.record]: government = 0; 14:34:48.797 [planetinfo.record]: economy = 2; 14:34:48.797 [planetinfo.record]: techlevel = 6; 14:34:48.797 [planetinfo.record]: population = 27; 14:34:48.797 [planetinfo.record]: productivity = 6912; 14:34:48.797 [planetinfo.record]: name = "Revequ"; 14:34:48.797 [planetinfo.record]: inhabitant = "Frog"; 14:34:48.797 [planetinfo.record]: inhabitants = "Frogs"; 14:34:48.797 [planetinfo.record]: description = "Revequ is cursed by dreadful civil war."; 14:34:48.816 [planetinfo.record]: air_color = 0.515483, 0.646124, 0.835752, 1; 14:34:48.816 [planetinfo.record]: cloud_alpha = 1.000000; 14:34:48.816 [planetinfo.record]: cloud_color = 0.228996, 0.509766, 0.450541, 1; 14:34:48.816 [planetinfo.record]: cloud_fraction = 0.110000; 14:34:48.816 [planetinfo.record]: land_color = 0.66, 0.366094, 0.441866, 1; 14:34:48.816 [planetinfo.record]: land_fraction = 0.590000; 14:34:48.816 [planetinfo.record]: polar_cloud_color = 0.478309, 0.729395, 0.676431, 1; 14:34:48.816 [planetinfo.record]: polar_land_color = 0.934, 0.83002, 0.856827, 1; 14:34:48.816 [planetinfo.record]: polar_sea_color = 0.936719, 0.88665, 0.880003, 1; 14:34:48.816 [planetinfo.record]: sea_color = 0.632812, 0.497513, 0.479553, 1; 14:34:48.816 [planetinfo.record]: rotation_speed = 0.004287 14:34:48.817 [planetinfo.record]: planet zpos = 713350.000000 14:34:48.817 [planetinfo.record]: sun_radius = 124087.371826 14:34:48.817 [planetinfo.record]: sun_vector = -0.698 0.711 0.084 14:34:48.817 [planetinfo.record]: sun_distance = 1037600 14:34:48.817 [planetinfo.record]: corona_flare = 0.015613 14:34:48.817 [planetinfo.record]: corona_hues = 0.926216 14:34:48.817 [planetinfo.record]: sun_color = 0.761752, 0.756369, 0.720736, 1 14:34:48.817 [planetinfo.record]: corona_shimmer = 0.902203 14:34:48.817 [planetinfo.record]: station_vector = 0.112 -0.945 0.307 14:34:48.818 [planetinfo.record]: station = coriolis 14:34:53.369 [PLANETINFO OVER]: Done 14:34:53.369 [PLANETINFO LOGGING]: 7 6 14:34:54.373 [planetinfo.record]: seed = 96 95 29 240 14:34:54.373 [planetinfo.record]: coordinates = 95 218 14:34:54.373 [planetinfo.record]: government = 4; 14:34:54.373 [planetinfo.record]: economy = 2; 14:34:54.373 [planetinfo.record]: techlevel = 10; 14:34:54.373 [planetinfo.record]: population = 47; 14:34:54.373 [planetinfo.record]: productivity = 24064; 14:34:54.373 [planetinfo.record]: name = "Eresorbe"; 14:34:54.373 [planetinfo.record]: inhabitant = "Human Colonial"; 14:34:54.373 [planetinfo.record]: inhabitants = "Human Colonials"; 14:34:54.373 [planetinfo.record]: description = "The planet Eresorbe is famous for Eresorbeian shrew steak but ravaged by occasional solar activity."; 14:34:54.396 [planetinfo.record]: air_color = 0.75237, 0.643992, 0.840305, 1; 14:34:54.396 [planetinfo.record]: cloud_alpha = 1.000000; 14:34:54.396 [planetinfo.record]: cloud_color = 0.523438, 0.470276, 0.48772, 1; 14:34:54.396 [planetinfo.record]: cloud_fraction = 0.420000; 14:34:54.396 [planetinfo.record]: land_color = 0.0386719, 0.66, 0.35419, 1; 14:34:54.396 [planetinfo.record]: land_fraction = 0.550000; 14:34:54.396 [planetinfo.record]: polar_cloud_color = 0.735547, 0.688857, 0.704177, 1; 14:34:54.396 [planetinfo.record]: polar_land_color = 0.714182, 0.934, 0.825808, 1; 14:34:54.396 [planetinfo.record]: polar_sea_color = 0.941602, 0.938607, 0.893694, 1; 14:34:54.396 [planetinfo.record]: sea_color = 0.583984, 0.576556, 0.465134, 1; 14:34:54.396 [planetinfo.record]: rotation_speed = 0.002272 14:34:54.397 [planetinfo.record]: planet zpos = 349320.000000 14:34:54.397 [planetinfo.record]: sun_radius = 71430.012817 14:34:54.397 [planetinfo.record]: sun_vector = 0.212 -0.141 -0.967 14:34:54.397 [planetinfo.record]: sun_distance = 582200 14:34:54.397 [planetinfo.record]: corona_flare = 0.034030 14:34:54.397 [planetinfo.record]: corona_hues = 0.750237 14:34:54.397 [planetinfo.record]: sun_color = 0.716061, 0.687592, 0.588774, 1 14:34:54.397 [planetinfo.record]: corona_shimmer = 0.471903 14:34:54.397 [planetinfo.record]: station_vector = -0.960 0.142 0.242 14:34:54.397 [planetinfo.record]: station = coriolis 14:34:58.761 [PLANETINFO OVER]: Done 14:34:58.762 [PLANETINFO LOGGING]: 7 7 14:34:59.764 [planetinfo.record]: seed = 118 147 207 54 14:34:59.764 [planetinfo.record]: coordinates = 147 121 14:34:59.764 [planetinfo.record]: government = 6; 14:34:59.764 [planetinfo.record]: economy = 1; 14:34:59.764 [planetinfo.record]: techlevel = 12; 14:34:59.764 [planetinfo.record]: population = 56; 14:34:59.765 [planetinfo.record]: productivity = 40320; 14:34:59.765 [planetinfo.record]: name = "Vegedius"; 14:34:59.765 [planetinfo.record]: inhabitant = "Red Horned Bird"; 14:34:59.765 [planetinfo.record]: inhabitants = "Red Horned Birds"; 14:34:59.765 [planetinfo.record]: description = "This planet is very well known for Vegediusian wolf meat and its weird volcanoes."; 14:34:59.774 [planetinfo.record]: air_color = 0.641585, 0.888434, 0.855419, 1; 14:34:59.774 [planetinfo.record]: cloud_alpha = 1.000000; 14:34:59.774 [planetinfo.record]: cloud_color = 0.667969, 0.606814, 0.511414, 1; 14:34:59.774 [planetinfo.record]: cloud_fraction = 0.490000; 14:34:59.775 [planetinfo.record]: land_color = 0.100547, 0.266635, 0.66, 1; 14:34:59.775 [planetinfo.record]: land_fraction = 0.340000; 14:34:59.775 [planetinfo.record]: polar_cloud_color = 0.800586, 0.754776, 0.683313, 1; 14:34:59.775 [planetinfo.record]: polar_land_color = 0.736072, 0.794832, 0.934, 1; 14:34:59.775 [planetinfo.record]: polar_sea_color = 0.937695, 0.887325, 0.875701, 1; 14:34:59.775 [planetinfo.record]: sea_color = 0.623047, 0.489174, 0.45828, 1; 14:34:59.775 [planetinfo.record]: rotation_speed = 0.003914 14:34:59.775 [planetinfo.record]: planet zpos = 539880.000000 14:34:59.775 [planetinfo.record]: sun_radius = 119571.277313 14:34:59.775 [planetinfo.record]: sun_vector = 0.358 0.932 -0.054 14:34:59.775 [planetinfo.record]: sun_distance = 944790 14:34:59.775 [planetinfo.record]: corona_flare = 0.022847 14:34:59.775 [planetinfo.record]: corona_hues = 0.992516 14:34:59.775 [planetinfo.record]: sun_color = 0.830185, 0.586358, 0.340319, 1 14:34:59.776 [planetinfo.record]: corona_shimmer = 0.533707 14:34:59.776 [planetinfo.record]: station_vector = 0.560 -0.158 0.814 14:34:59.776 [planetinfo.record]: station = dodecahedron 14:35:04.179 [PLANETINFO OVER]: Done 14:35:04.180 [PLANETINFO LOGGING]: 7 8 14:35:05.183 [planetinfo.record]: seed = 116 136 25 218 14:35:05.183 [planetinfo.record]: coordinates = 136 13 14:35:05.183 [planetinfo.record]: government = 6; 14:35:05.183 [planetinfo.record]: economy = 5; 14:35:05.183 [planetinfo.record]: techlevel = 5; 14:35:05.183 [planetinfo.record]: population = 32; 14:35:05.183 [planetinfo.record]: productivity = 12800; 14:35:05.183 [planetinfo.record]: name = "Querbeis"; 14:35:05.183 [planetinfo.record]: inhabitant = "Human Colonial"; 14:35:05.184 [planetinfo.record]: inhabitants = "Human Colonials"; 14:35:05.184 [planetinfo.record]: description = "The planet Querbeis is a boring world."; 14:35:05.200 [planetinfo.record]: air_color = 0.553756, 0.945668, 0.838195, 1; 14:35:05.200 [planetinfo.record]: cloud_alpha = 1.000000; 14:35:05.200 [planetinfo.record]: cloud_color = 0.839844, 0.400033, 0.298538, 1; 14:35:05.200 [planetinfo.record]: cloud_fraction = 0.520000; 14:35:05.200 [planetinfo.record]: land_color = 0.658832, 0.66, 0.510469, 1; 14:35:05.200 [planetinfo.record]: land_fraction = 0.270000; 14:35:05.200 [planetinfo.record]: polar_cloud_color = 0.87793, 0.590582, 0.524271, 1; 14:35:05.200 [planetinfo.record]: polar_land_color = 0.933587, 0.934, 0.881098, 1; 14:35:05.200 [planetinfo.record]: polar_sea_color = 0.868808, 0.879681, 0.916797, 1; 14:35:05.200 [planetinfo.record]: sea_color = 0.657825, 0.697293, 0.832031, 1; 14:35:05.200 [planetinfo.record]: rotation_speed = 0.002581 14:35:05.200 [planetinfo.record]: planet zpos = 606320.000000 14:35:05.200 [planetinfo.record]: sun_radius = 109751.341553 14:35:05.200 [planetinfo.record]: sun_vector = -0.568 0.466 -0.678 14:35:05.200 [planetinfo.record]: sun_distance = 992160 14:35:05.200 [planetinfo.record]: corona_flare = 0.021680 14:35:05.200 [planetinfo.record]: corona_hues = 0.630836 14:35:05.200 [planetinfo.record]: sun_color = 0.794128, 0.49255, 0.451035, 1 14:35:05.200 [planetinfo.record]: corona_shimmer = 0.400621 14:35:05.201 [planetinfo.record]: station_vector = 0.363 0.737 0.570 14:35:05.201 [planetinfo.record]: station = coriolis 14:35:10.452 [PLANETINFO OVER]: Done 14:35:10.453 [PLANETINFO LOGGING]: 7 9 14:35:11.457 [planetinfo.record]: seed = 170 29 59 97 14:35:11.457 [planetinfo.record]: coordinates = 29 211 14:35:11.457 [planetinfo.record]: government = 5; 14:35:11.457 [planetinfo.record]: economy = 3; 14:35:11.457 [planetinfo.record]: techlevel = 8; 14:35:11.457 [planetinfo.record]: population = 41; 14:35:11.457 [planetinfo.record]: productivity = 20664; 14:35:11.457 [planetinfo.record]: name = "Leater"; 14:35:11.457 [planetinfo.record]: inhabitant = "Human Colonial"; 14:35:11.457 [planetinfo.record]: inhabitants = "Human Colonials"; 14:35:11.457 [planetinfo.record]: description = "The world Leater is very famous for its hoopy night life but scourged by killer mountain monkeys."; 14:35:11.468 [planetinfo.record]: air_color = 0.595887, 0.846609, 0.991195, 1; 14:35:11.469 [planetinfo.record]: cloud_alpha = 1.000000; 14:35:11.469 [planetinfo.record]: cloud_color = 0.392914, 0.976562, 0.45675, 1; 14:35:11.469 [planetinfo.record]: cloud_fraction = 0.420000; 14:35:11.469 [planetinfo.record]: land_color = 0.020625, 0.66, 0.450205, 1; 14:35:11.469 [planetinfo.record]: land_fraction = 0.540000; 14:35:11.469 [planetinfo.record]: polar_cloud_color = 0.588534, 0.939453, 0.626916, 1; 14:35:11.469 [planetinfo.record]: polar_land_color = 0.707797, 0.934, 0.859777, 1; 14:35:11.469 [planetinfo.record]: polar_sea_color = 0.938281, 0.881815, 0.875974, 1; 14:35:11.470 [planetinfo.record]: sea_color = 0.617188, 0.468616, 0.453247, 1; 14:35:11.470 [planetinfo.record]: rotation_speed = 0.003603 14:35:11.470 [planetinfo.record]: planet zpos = 403130.000000 14:35:11.470 [planetinfo.record]: sun_radius = 65839.468994 14:35:11.470 [planetinfo.record]: sun_vector = 0.320 0.816 -0.482 14:35:11.470 [planetinfo.record]: sun_distance = 651210 14:35:11.470 [planetinfo.record]: corona_flare = 0.017810 14:35:11.470 [planetinfo.record]: corona_hues = 0.796928 14:35:11.470 [planetinfo.record]: sun_color = 0.663715, 0.302258, 0.212558, 1 14:35:11.470 [planetinfo.record]: corona_shimmer = 1.174986 14:35:11.470 [planetinfo.record]: station_vector = 0.934 -0.161 0.319 14:35:11.470 [planetinfo.record]: station = coriolis 14:35:16.351 [PLANETINFO OVER]: Done 14:35:16.351 [PLANETINFO LOGGING]: 7 10 14:35:17.354 [planetinfo.record]: seed = 8 132 213 166 14:35:17.354 [planetinfo.record]: coordinates = 132 208 14:35:17.354 [planetinfo.record]: government = 1; 14:35:17.354 [planetinfo.record]: economy = 2; 14:35:17.354 [planetinfo.record]: techlevel = 6; 14:35:17.354 [planetinfo.record]: population = 28; 14:35:17.354 [planetinfo.record]: productivity = 8960; 14:35:17.354 [planetinfo.record]: name = "Bianbies"; 14:35:17.354 [planetinfo.record]: inhabitant = "Fierce Harmless Fat Feline"; 14:35:17.354 [planetinfo.record]: inhabitants = "Fierce Harmless Fat Felines"; 14:35:17.354 [planetinfo.record]: description = "Bianbies is well known for the Bianbiesian spotted leopard but ravaged by frequent earthquakes."; 14:35:17.380 [planetinfo.record]: air_color = 0.455012, 0.524579, 0.917701, 1; 14:35:17.380 [planetinfo.record]: cloud_alpha = 1.000000; 14:35:17.380 [planetinfo.record]: cloud_color = 0.0649567, 0.367227, 0.755859, 1; 14:35:17.380 [planetinfo.record]: cloud_fraction = 0.420000; 14:35:17.380 [planetinfo.record]: land_color = 0.348194, 0.28006, 0.628906, 1; 14:35:17.380 [planetinfo.record]: land_fraction = 0.660000; 14:35:17.380 [planetinfo.record]: polar_cloud_color = 0.360176, 0.570159, 0.840137, 1; 14:35:17.380 [planetinfo.record]: polar_land_color = 0.83254, 0.807159, 0.937109, 1; 14:35:17.380 [planetinfo.record]: polar_sea_color = 0.933203, 0.86548, 0.858474, 1; 14:35:17.380 [planetinfo.record]: sea_color = 0.667969, 0.474069, 0.45401, 1; 14:35:17.380 [planetinfo.record]: rotation_speed = 0.000369 14:35:17.380 [planetinfo.record]: planet zpos = 448400.000000 14:35:17.380 [planetinfo.record]: sun_radius = 93141.388550 14:35:17.380 [planetinfo.record]: sun_vector = 0.098 -0.871 -0.482 14:35:17.380 [planetinfo.record]: sun_distance = 986480 14:35:17.380 [planetinfo.record]: corona_flare = 0.059160 14:35:17.380 [planetinfo.record]: corona_hues = 0.814636 14:35:17.380 [planetinfo.record]: sun_color = 0.44218, 0.62219, 0.705963, 1 14:35:17.380 [planetinfo.record]: corona_shimmer = 0.369930 14:35:17.381 [planetinfo.record]: station_vector = 0.901 -0.327 0.284 14:35:17.381 [planetinfo.record]: station = coriolis 14:35:22.297 [PLANETINFO OVER]: Done 14:35:22.298 [PLANETINFO LOGGING]: 7 11 14:35:23.301 [planetinfo.record]: seed = 30 201 103 235 14:35:23.301 [planetinfo.record]: coordinates = 201 38 14:35:23.301 [planetinfo.record]: government = 3; 14:35:23.301 [planetinfo.record]: economy = 6; 14:35:23.301 [planetinfo.record]: techlevel = 4; 14:35:23.301 [planetinfo.record]: population = 26; 14:35:23.301 [planetinfo.record]: productivity = 5824; 14:35:23.301 [planetinfo.record]: name = "Maana"; 14:35:23.301 [planetinfo.record]: inhabitant = "Human Colonial"; 14:35:23.301 [planetinfo.record]: inhabitants = "Human Colonials"; 14:35:23.302 [planetinfo.record]: description = "This planet is beset by deadly earthquakes."; 14:35:23.324 [planetinfo.record]: air_color = 0.458781, 0.448258, 0.941115, 1; 14:35:23.324 [planetinfo.record]: cloud_alpha = 1.000000; 14:35:23.324 [planetinfo.record]: cloud_color = 0.0853704, 0.0225906, 0.826172, 1; 14:35:23.324 [planetinfo.record]: cloud_fraction = 0.220000; 14:35:23.324 [planetinfo.record]: land_color = 0.322266, 0.66, 0.343374, 1; 14:35:23.324 [planetinfo.record]: land_fraction = 0.600000; 14:35:23.324 [planetinfo.record]: polar_cloud_color = 0.383218, 0.341815, 0.871777, 1; 14:35:23.324 [planetinfo.record]: polar_land_color = 0.814514, 0.934, 0.821982, 1; 14:35:23.324 [planetinfo.record]: polar_sea_color = 0.937891, 0.905325, 0.879181, 1; 14:35:23.324 [planetinfo.record]: sea_color = 0.621094, 0.534831, 0.465578, 1; 14:35:23.324 [planetinfo.record]: rotation_speed = 0.003380 14:35:23.324 [planetinfo.record]: planet zpos = 699960.000000 14:35:23.324 [planetinfo.record]: sun_radius = 121638.912659 14:35:23.324 [planetinfo.record]: sun_vector = -0.280 -0.498 -0.821 14:35:23.324 [planetinfo.record]: sun_distance = 991610 14:35:23.324 [planetinfo.record]: corona_flare = 0.062112 14:35:23.324 [planetinfo.record]: corona_hues = 0.671112 14:35:23.325 [planetinfo.record]: sun_color = 0.779825, 0.273433, 0.205478, 1 14:35:23.325 [planetinfo.record]: corona_shimmer = 0.374021 14:35:23.325 [planetinfo.record]: station_vector = 0.535 -0.814 0.228 14:35:23.325 [planetinfo.record]: station = coriolis 14:35:28.294 [PLANETINFO OVER]: Done 14:35:28.295 [PLANETINFO LOGGING]: 7 12 14:35:29.316 [planetinfo.record]: seed = 28 86 209 192 14:35:29.316 [planetinfo.record]: coordinates = 86 143 14:35:29.316 [planetinfo.record]: government = 3; 14:35:29.316 [planetinfo.record]: economy = 7; 14:35:29.316 [planetinfo.record]: techlevel = 4; 14:35:29.316 [planetinfo.record]: population = 27; 14:35:29.316 [planetinfo.record]: productivity = 4536; 14:35:29.316 [planetinfo.record]: name = "Biis"; 14:35:29.316 [planetinfo.record]: inhabitant = "Large Bug-Eyed Frog"; 14:35:29.316 [planetinfo.record]: inhabitants = "Large Bug-Eyed Frogs"; 14:35:29.316 [planetinfo.record]: description = "Biis is ravaged by unpredictable solar activity."; 14:35:29.322 [planetinfo.record]: air_color = 0.802208, 0.743799, 0.987293, 1; 14:35:29.322 [planetinfo.record]: cloud_alpha = 1.000000; 14:35:29.322 [planetinfo.record]: cloud_color = 0.928803, 0.829163, 0.964844, 1; 14:35:29.322 [planetinfo.record]: cloud_fraction = 0.290000; 14:35:29.322 [planetinfo.record]: land_color = 0.248016, 0.503906, 0.497909, 1; 14:35:29.322 [planetinfo.record]: land_fraction = 0.470000; 14:35:29.322 [planetinfo.record]: polar_cloud_color = 0.91237, 0.852074, 0.93418, 1; 14:35:29.323 [planetinfo.record]: polar_land_color = 0.829054, 0.949609, 0.946784, 1; 14:35:29.323 [planetinfo.record]: polar_sea_color = 0.821457, 0.932522, 0.947266, 1; 14:35:29.323 [planetinfo.record]: sea_color = 0.247192, 0.494514, 0.527344, 1; 14:35:29.323 [planetinfo.record]: rotation_speed = 0.000687 14:35:29.323 [planetinfo.record]: planet zpos = 348240.000000 14:35:29.323 [planetinfo.record]: sun_radius = 76433.444214 14:35:29.323 [planetinfo.record]: sun_vector = -0.847 -0.473 0.242 14:35:29.323 [planetinfo.record]: sun_distance = 522360 14:35:29.323 [planetinfo.record]: corona_flare = 0.009732 14:35:29.323 [planetinfo.record]: corona_hues = 0.598473 14:35:29.323 [planetinfo.record]: sun_color = 0.80575, 0.719665, 0.666133, 1 14:35:29.323 [planetinfo.record]: corona_shimmer = 0.411131 14:35:29.323 [planetinfo.record]: station_vector = 0.461 -0.874 0.156 14:35:29.323 [planetinfo.record]: station = coriolis 14:35:33.981 [PLANETINFO OVER]: Done 14:35:33.982 [PLANETINFO LOGGING]: 7 13 14:35:34.987 [planetinfo.record]: seed = 210 36 211 136 14:35:34.987 [planetinfo.record]: coordinates = 36 189 14:35:34.987 [planetinfo.record]: government = 2; 14:35:34.987 [planetinfo.record]: economy = 5; 14:35:34.987 [planetinfo.record]: techlevel = 3; 14:35:34.987 [planetinfo.record]: population = 20; 14:35:34.987 [planetinfo.record]: productivity = 4800; 14:35:34.987 [planetinfo.record]: name = "Usmaedin"; 14:35:34.987 [planetinfo.record]: inhabitant = "Small Black Bug-Eyed Frog"; 14:35:34.987 [planetinfo.record]: inhabitants = "Small Black Bug-Eyed Frogs"; 14:35:34.987 [planetinfo.record]: description = "This planet is a dull world."; 14:35:35.016 [planetinfo.record]: air_color = 0.696548, 0.717336, 0.984041, 1; 14:35:35.016 [planetinfo.record]: cloud_alpha = 1.000000; 14:35:35.016 [planetinfo.record]: cloud_color = 0.690193, 0.743998, 0.955078, 1; 14:35:35.016 [planetinfo.record]: cloud_fraction = 0.480000; 14:35:35.016 [planetinfo.record]: land_color = 0.66, 0.198113, 0.0567188, 1; 14:35:35.016 [planetinfo.record]: land_fraction = 0.530000; 14:35:35.016 [planetinfo.record]: polar_cloud_color = 0.768616, 0.801354, 0.929785, 1; 14:35:35.016 [planetinfo.record]: polar_land_color = 0.934, 0.77059, 0.720566, 1; 14:35:35.016 [planetinfo.record]: polar_sea_color = 0.885986, 0.932617, 0.889994, 1; 14:35:35.016 [planetinfo.record]: sea_color = 0.539062, 0.673828, 0.550644, 1; 14:35:35.016 [planetinfo.record]: rotation_speed = 0.002997 14:35:35.016 [planetinfo.record]: planet zpos = 539000.000000 14:35:35.016 [planetinfo.record]: sun_radius = 106954.223633 14:35:35.016 [planetinfo.record]: sun_vector = -0.490 0.140 -0.860 14:35:35.016 [planetinfo.record]: sun_distance = 1078000 14:35:35.016 [planetinfo.record]: corona_flare = 0.025333 14:35:35.017 [planetinfo.record]: corona_hues = 0.830887 14:35:35.017 [planetinfo.record]: sun_color = 0.848212, 0.535679, 0.511689, 1 14:35:35.017 [planetinfo.record]: corona_shimmer = 0.243712 14:35:35.017 [planetinfo.record]: station_vector = -0.764 0.174 0.621 14:35:35.017 [planetinfo.record]: station = coriolis 14:35:39.336 [PLANETINFO OVER]: Done 14:35:39.337 [PLANETINFO LOGGING]: 7 14 14:35:40.340 [planetinfo.record]: seed = 176 12 141 144 14:35:40.340 [planetinfo.record]: coordinates = 12 24 14:35:40.340 [planetinfo.record]: government = 6; 14:35:40.340 [planetinfo.record]: economy = 0; 14:35:40.340 [planetinfo.record]: techlevel = 10; 14:35:40.340 [planetinfo.record]: population = 47; 14:35:40.340 [planetinfo.record]: productivity = 37600; 14:35:40.340 [planetinfo.record]: name = "Erlabeor"; 14:35:40.340 [planetinfo.record]: inhabitant = "Black Fat Bird"; 14:35:40.340 [planetinfo.record]: inhabitants = "Black Fat Birds"; 14:35:40.340 [planetinfo.record]: description = "This planet is mildly noted for the Erlabeorian deadly Stoid but ravaged by lethal vicious ants."; 14:35:40.344 [planetinfo.record]: air_color = 0.690591, 0.856183, 0.871523, 1; 14:35:40.344 [planetinfo.record]: cloud_alpha = 1.000000; 14:35:40.344 [planetinfo.record]: cloud_color = 0.613345, 0.617188, 0.602722, 1; 14:35:40.344 [planetinfo.record]: cloud_fraction = 0.290000; 14:35:40.344 [planetinfo.record]: land_color = 0.357747, 0.264221, 0.650391, 1; 14:35:40.344 [planetinfo.record]: land_fraction = 0.360000; 14:35:40.344 [planetinfo.record]: polar_cloud_color = 0.774708, 0.777734, 0.766342, 1; 14:35:40.344 [planetinfo.record]: polar_land_color = 0.829789, 0.796178, 0.934961, 1; 14:35:40.344 [planetinfo.record]: polar_sea_color = 0.934961, 0.874557, 0.868308, 1; 14:35:40.344 [planetinfo.record]: sea_color = 0.650391, 0.482315, 0.464928, 1; 14:35:40.345 [planetinfo.record]: rotation_speed = 0.003465 14:35:40.345 [planetinfo.record]: planet zpos = 367640.000000 14:35:40.345 [planetinfo.record]: sun_radius = 78145.421143 14:35:40.345 [planetinfo.record]: sun_vector = 0.393 -0.867 -0.306 14:35:40.345 [planetinfo.record]: sun_distance = 565600 14:35:40.345 [planetinfo.record]: corona_flare = 0.001617 14:35:40.345 [planetinfo.record]: corona_hues = 0.665054 14:35:40.345 [planetinfo.record]: sun_color = 0.320423, 0.554916, 0.746136, 1 14:35:40.345 [planetinfo.record]: corona_shimmer = 0.283703 14:35:40.345 [planetinfo.record]: station_vector = -0.079 0.775 0.628 14:35:40.345 [planetinfo.record]: station = coriolis 14:35:44.720 [PLANETINFO OVER]: Done 14:35:44.721 [PLANETINFO LOGGING]: 7 15 14:35:45.724 [planetinfo.record]: seed = 198 153 255 162 14:35:45.724 [planetinfo.record]: coordinates = 153 83 14:35:45.724 [planetinfo.record]: government = 0; 14:35:45.724 [planetinfo.record]: economy = 3; 14:35:45.724 [planetinfo.record]: techlevel = 5; 14:35:45.725 [planetinfo.record]: population = 24; 14:35:45.725 [planetinfo.record]: productivity = 5376; 14:35:45.725 [planetinfo.record]: name = "Xeerin"; 14:35:45.725 [planetinfo.record]: inhabitant = "Large Harmless Horned Bird"; 14:35:45.725 [planetinfo.record]: inhabitants = "Large Harmless Horned Birds"; 14:35:45.725 [planetinfo.record]: description = "The planet Xeerin is an unremarkable dump."; 14:35:45.748 [planetinfo.record]: air_color = 0.514226, 0.597918, 0.857215, 1; 14:35:45.748 [planetinfo.record]: cloud_alpha = 1.000000; 14:35:45.748 [planetinfo.record]: cloud_color = 0.22879, 0.477067, 0.574219, 1; 14:35:45.748 [planetinfo.record]: cloud_fraction = 0.410000; 14:35:45.748 [planetinfo.record]: land_color = 0.646505, 0.66, 0.487266, 1; 14:35:45.748 [planetinfo.record]: land_fraction = 0.360000; 14:35:45.748 [planetinfo.record]: polar_cloud_color = 0.473258, 0.678203, 0.758398, 1; 14:35:45.748 [planetinfo.record]: polar_land_color = 0.929226, 0.934, 0.872889, 1; 14:35:45.748 [planetinfo.record]: polar_sea_color = 0.840066, 0.801245, 0.935547, 1; 14:35:45.748 [planetinfo.record]: sea_color = 0.381412, 0.274429, 0.644531, 1; 14:35:45.748 [planetinfo.record]: rotation_speed = 0.002714 14:35:45.748 [planetinfo.record]: planet zpos = 452530.000000 14:35:45.748 [planetinfo.record]: sun_radius = 61344.551392 14:35:45.748 [planetinfo.record]: sun_vector = -0.105 -0.938 0.330 14:35:45.748 [planetinfo.record]: sun_distance = 661390 14:35:45.748 [planetinfo.record]: corona_flare = 0.015749 14:35:45.748 [planetinfo.record]: corona_hues = 0.680061 14:35:45.748 [planetinfo.record]: sun_color = 0.245934, 0.747311, 0.784729, 1 14:35:45.748 [planetinfo.record]: corona_shimmer = 0.401392 14:35:45.749 [planetinfo.record]: station_vector = 0.097 0.820 0.564 14:35:45.749 [planetinfo.record]: station = coriolis 14:35:50.929 [PLANETINFO OVER]: Done 14:35:50.930 [PLANETINFO LOGGING]: 7 16 14:35:51.951 [planetinfo.record]: seed = 196 255 137 92 14:35:51.951 [planetinfo.record]: coordinates = 255 204 14:35:51.951 [planetinfo.record]: government = 0; 14:35:51.951 [planetinfo.record]: economy = 6; 14:35:51.951 [planetinfo.record]: techlevel = 4; 14:35:51.951 [planetinfo.record]: population = 23; 14:35:51.951 [planetinfo.record]: productivity = 2944; 14:35:51.951 [planetinfo.record]: name = "Teescear"; 14:35:51.951 [planetinfo.record]: inhabitant = "Yellow Bony Lobster"; 14:35:51.951 [planetinfo.record]: inhabitants = "Yellow Bony Lobsters"; 14:35:51.951 [planetinfo.record]: description = "The planet Teescear is reasonably fabled for Teescearian evil juice and Teescearian evil brandy."; 14:35:51.960 [planetinfo.record]: air_color = 0.725529, 0.611315, 0.860467, 1; 14:35:51.960 [planetinfo.record]: cloud_alpha = 1.000000; 14:35:51.960 [planetinfo.record]: cloud_color = 0.583984, 0.426582, 0.522499, 1; 14:35:51.961 [planetinfo.record]: cloud_fraction = 0.130000; 14:35:51.961 [planetinfo.record]: land_color = 0.66, 0.174023, 0.12375, 1; 14:35:51.961 [planetinfo.record]: land_fraction = 0.680000; 14:35:51.961 [planetinfo.record]: polar_cloud_color = 0.762793, 0.634295, 0.712599, 1; 14:35:51.961 [planetinfo.record]: polar_land_color = 0.934, 0.762067, 0.744281, 1; 14:35:51.961 [planetinfo.record]: polar_sea_color = 0.938672, 0.89445, 0.879088, 1; 14:35:51.961 [planetinfo.record]: sea_color = 0.613281, 0.497711, 0.457565, 1; 14:35:51.962 [planetinfo.record]: rotation_speed = 0.003791 14:35:51.962 [planetinfo.record]: planet zpos = 614300.000000 14:35:51.962 [planetinfo.record]: sun_radius = 134913.350372 14:35:51.962 [planetinfo.record]: sun_vector = 0.188 0.565 0.804 14:35:51.962 [planetinfo.record]: sun_distance = 1228600 14:35:51.962 [planetinfo.record]: corona_flare = 0.023927 14:35:51.962 [planetinfo.record]: corona_hues = 0.802063 14:35:51.962 [planetinfo.record]: sun_color = 0.72966, 0.725918, 0.688986, 1 14:35:51.962 [planetinfo.record]: corona_shimmer = 0.373242 14:35:51.962 [planetinfo.record]: station_vector = 0.877 -0.273 0.394 14:35:51.963 [planetinfo.record]: station = coriolis 14:35:56.175 [PLANETINFO OVER]: Done 14:35:56.175 [PLANETINFO LOGGING]: 7 17 14:35:57.179 [planetinfo.record]: seed = 250 10 107 185 14:35:57.179 [planetinfo.record]: coordinates = 10 133 14:35:57.179 [planetinfo.record]: government = 7; 14:35:57.179 [planetinfo.record]: economy = 5; 14:35:57.179 [planetinfo.record]: techlevel = 8; 14:35:57.179 [planetinfo.record]: population = 45; 14:35:57.179 [planetinfo.record]: productivity = 19800; 14:35:57.179 [planetinfo.record]: name = "Oresreat"; 14:35:57.179 [planetinfo.record]: inhabitant = "Human Colonial"; 14:35:57.179 [planetinfo.record]: inhabitants = "Human Colonials"; 14:35:57.179 [planetinfo.record]: description = "This planet is a dull world."; 14:35:57.187 [planetinfo.record]: air_color = 0.756328, 0.637094, 0.850061, 1; 14:35:57.188 [planetinfo.record]: cloud_alpha = 1.000000; 14:35:57.188 [planetinfo.record]: cloud_color = 0.552734, 0.468529, 0.494185, 1; 14:35:57.188 [planetinfo.record]: cloud_fraction = 0.460000; 14:35:57.188 [planetinfo.record]: land_color = 0.615608, 0.585234, 0.66, 1; 14:35:57.188 [planetinfo.record]: land_fraction = 0.280000; 14:35:57.188 [planetinfo.record]: polar_cloud_color = 0.74873, 0.67744, 0.699161, 1; 14:35:57.188 [planetinfo.record]: polar_land_color = 0.918295, 0.907549, 0.934, 1; 14:35:57.188 [planetinfo.record]: polar_sea_color = 0.91832, 0.936328, 0.88622, 1; 14:35:57.188 [planetinfo.record]: sea_color = 0.587737, 0.636719, 0.500421, 1; 14:35:57.188 [planetinfo.record]: rotation_speed = 0.002390 14:35:57.188 [planetinfo.record]: planet zpos = 564300.000000 14:35:57.188 [planetinfo.record]: sun_radius = 115833.609009 14:35:57.188 [planetinfo.record]: sun_vector = -0.179 -0.175 -0.968 14:35:57.188 [planetinfo.record]: sun_distance = 1077300 14:35:57.189 [planetinfo.record]: corona_flare = 0.009854 14:35:57.189 [planetinfo.record]: corona_hues = 0.646393 14:35:57.189 [planetinfo.record]: sun_color = 0.385916, 0.569152, 0.730032, 1 14:35:57.189 [planetinfo.record]: corona_shimmer = 0.402699 14:35:57.189 [planetinfo.record]: station_vector = -0.489 0.027 0.872 14:35:57.189 [planetinfo.record]: station = coriolis 14:36:00.999 [PLANETINFO OVER]: Done 14:36:01.000 [PLANETINFO LOGGING]: 7 18 14:36:02.003 [planetinfo.record]: seed = 88 17 69 105 14:36:02.003 [planetinfo.record]: coordinates = 17 14 14:36:02.003 [planetinfo.record]: government = 3; 14:36:02.003 [planetinfo.record]: economy = 6; 14:36:02.003 [planetinfo.record]: techlevel = 4; 14:36:02.003 [planetinfo.record]: population = 26; 14:36:02.003 [planetinfo.record]: productivity = 5824; 14:36:02.003 [planetinfo.record]: name = "Esusge"; 14:36:02.003 [planetinfo.record]: inhabitant = "Human Colonial"; 14:36:02.004 [planetinfo.record]: inhabitants = "Human Colonials"; 14:36:02.004 [planetinfo.record]: description = "The world Esusge is beset by lethal disease."; 14:36:02.014 [planetinfo.record]: air_color = 0.39495, 0.838354, 0.727142, 1; 14:36:02.014 [planetinfo.record]: cloud_alpha = 1.000000; 14:36:02.014 [planetinfo.record]: cloud_color = 0.517578, 0.13794, 0.00606537, 1; 14:36:02.014 [planetinfo.record]: cloud_fraction = 0.370000; 14:36:02.014 [planetinfo.record]: land_color = 0.66, 0.20625, 0.578467, 1; 14:36:02.014 [planetinfo.record]: land_fraction = 0.500000; 14:36:02.014 [planetinfo.record]: polar_cloud_color = 0.73291, 0.396921, 0.280209, 1; 14:36:02.014 [planetinfo.record]: polar_land_color = 0.934, 0.773469, 0.905155, 1; 14:36:02.014 [planetinfo.record]: polar_sea_color = 0.92833, 0.936133, 0.883567, 1; 14:36:02.014 [planetinfo.record]: sea_color = 0.617378, 0.638672, 0.49522, 1; 14:36:02.014 [planetinfo.record]: rotation_speed = 0.001573 14:36:02.014 [planetinfo.record]: planet zpos = 667810.000000 14:36:02.014 [planetinfo.record]: sun_radius = 122395.671997 14:36:02.014 [planetinfo.record]: sun_vector = 0.405 0.686 0.605 14:36:02.015 [planetinfo.record]: sun_distance = 1130140 14:36:02.015 [planetinfo.record]: corona_flare = 0.051927 14:36:02.015 [planetinfo.record]: corona_hues = 0.908371 14:36:02.015 [planetinfo.record]: sun_color = 0.762848, 0.601737, 0.483668, 1 14:36:02.024 [planetinfo.record]: corona_shimmer = 0.375793 14:36:02.024 [planetinfo.record]: station_vector = -0.822 -0.169 0.543 14:36:02.024 [planetinfo.record]: station = coriolis 14:36:07.390 [PLANETINFO OVER]: Done 14:36:07.391 [PLANETINFO LOGGING]: 7 19 14:36:08.414 [planetinfo.record]: seed = 110 245 151 129 14:36:08.414 [planetinfo.record]: coordinates = 245 3 14:36:08.414 [planetinfo.record]: government = 5; 14:36:08.414 [planetinfo.record]: economy = 3; 14:36:08.414 [planetinfo.record]: techlevel = 8; 14:36:08.414 [planetinfo.record]: population = 41; 14:36:08.415 [planetinfo.record]: productivity = 20664; 14:36:08.415 [planetinfo.record]: name = "Lequatdi"; 14:36:08.415 [planetinfo.record]: inhabitant = "Large Black Insect"; 14:36:08.415 [planetinfo.record]: inhabitants = "Large Black Insects"; 14:36:08.415 [planetinfo.record]: description = "The world Lequatdi is scourged by deadly edible arts graduates."; 14:36:08.425 [planetinfo.record]: air_color = 0.741281, 0.773259, 0.933961, 1; 14:36:08.426 [planetinfo.record]: cloud_alpha = 1.000000; 14:36:08.426 [planetinfo.record]: cloud_color = 0.788971, 0.796584, 0.804688, 1; 14:36:08.426 [planetinfo.record]: cloud_fraction = 0.120000; 14:36:08.426 [planetinfo.record]: land_color = 0.66, 0.265345, 0.159844, 1; 14:36:08.426 [planetinfo.record]: land_fraction = 0.470000; 14:36:08.426 [planetinfo.record]: polar_cloud_color = 0.851586, 0.856683, 0.862109, 1; 14:36:08.426 [planetinfo.record]: polar_land_color = 0.934, 0.794376, 0.757051, 1; 14:36:08.426 [planetinfo.record]: polar_sea_color = 0.936133, 0.899901, 0.884024, 1; 14:36:08.426 [planetinfo.record]: sea_color = 0.638672, 0.539795, 0.496468, 1; 14:36:08.426 [planetinfo.record]: rotation_speed = 0.002088 14:36:08.426 [planetinfo.record]: planet zpos = 364870.000000 14:36:08.426 [planetinfo.record]: sun_radius = 77050.302277 14:36:08.426 [planetinfo.record]: sun_vector = 0.996 -0.068 -0.056 14:36:08.427 [planetinfo.record]: sun_distance = 696570 14:36:08.427 [planetinfo.record]: corona_flare = 0.073053 14:36:08.427 [planetinfo.record]: corona_hues = 0.619110 14:36:08.427 [planetinfo.record]: sun_color = 0.328721, 0.637098, 0.682245, 1 14:36:08.427 [planetinfo.record]: corona_shimmer = 0.419635 14:36:08.427 [planetinfo.record]: station_vector = -0.050 0.513 0.857 14:36:08.427 [planetinfo.record]: station = coriolis 14:36:12.971 [PLANETINFO OVER]: Done 14:36:12.972 [PLANETINFO LOGGING]: 7 20 14:36:13.973 [planetinfo.record]: seed = 108 237 65 89 14:36:13.974 [planetinfo.record]: coordinates = 237 241 14:36:13.974 [planetinfo.record]: government = 5; 14:36:13.974 [planetinfo.record]: economy = 1; 14:36:13.974 [planetinfo.record]: techlevel = 10; 14:36:13.974 [planetinfo.record]: population = 47; 14:36:13.974 [planetinfo.record]: productivity = 30456; 14:36:13.974 [planetinfo.record]: name = "Oredrier"; 14:36:13.974 [planetinfo.record]: inhabitant = "Human Colonial"; 14:36:13.974 [planetinfo.record]: inhabitants = "Human Colonials"; 14:36:13.974 [planetinfo.record]: description = "The planet Oredrier is most famous for its pink Oredrierian Ce Ceweed plantations and its exciting sit coms."; 14:36:13.982 [planetinfo.record]: air_color = 0.50266, 0.62425, 0.854613, 1; 14:36:13.983 [planetinfo.record]: cloud_alpha = 1.000000; 14:36:13.983 [planetinfo.record]: cloud_color = 0.205765, 0.566406, 0.557954, 1; 14:36:13.983 [planetinfo.record]: cloud_fraction = 0.550000; 14:36:13.983 [planetinfo.record]: land_color = 0.397031, 0.66, 0.487427, 1; 14:36:13.983 [planetinfo.record]: land_fraction = 0.470000; 14:36:13.983 [planetinfo.record]: polar_cloud_color = 0.454478, 0.754883, 0.747842, 1; 14:36:13.983 [planetinfo.record]: polar_land_color = 0.840965, 0.934, 0.872946, 1; 14:36:13.983 [planetinfo.record]: polar_sea_color = 0.936694, 0.937109, 0.883939, 1; 14:36:13.983 [planetinfo.record]: sea_color = 0.627791, 0.628906, 0.486174, 1; 14:36:13.983 [planetinfo.record]: rotation_speed = 0.001912 14:36:13.983 [planetinfo.record]: planet zpos = 696410.000000 14:36:13.983 [planetinfo.record]: sun_radius = 150189.073029 14:36:13.983 [planetinfo.record]: sun_vector = 0.155 0.028 -0.988 14:36:13.983 [planetinfo.record]: sun_distance = 857120 14:36:13.983 [planetinfo.record]: corona_flare = 0.082220 14:36:13.984 [planetinfo.record]: corona_hues = 0.630737 14:36:13.984 [planetinfo.record]: sun_color = 0.615259, 0.738266, 0.795447, 1 14:36:13.984 [planetinfo.record]: corona_shimmer = 0.920088 14:36:13.984 [planetinfo.record]: station_vector = -0.989 -0.077 0.126 14:36:13.984 [planetinfo.record]: station = coriolis 14:36:18.198 [PLANETINFO OVER]: Done 14:36:18.199 [PLANETINFO LOGGING]: 7 21 14:36:19.221 [planetinfo.record]: seed = 34 16 3 199 14:36:19.221 [planetinfo.record]: coordinates = 16 126 14:36:19.221 [planetinfo.record]: government = 4; 14:36:19.221 [planetinfo.record]: economy = 6; 14:36:19.221 [planetinfo.record]: techlevel = 3; 14:36:19.221 [planetinfo.record]: population = 23; 14:36:19.221 [planetinfo.record]: productivity = 5888; 14:36:19.221 [planetinfo.record]: name = "Soladiar"; 14:36:19.221 [planetinfo.record]: inhabitant = "Human Colonial"; 14:36:19.221 [planetinfo.record]: inhabitants = "Human Colonials"; 14:36:19.221 [planetinfo.record]: description = "This world is a tedious little planet."; 14:36:19.239 [planetinfo.record]: air_color = 0.755806, 0.753154, 0.94892, 1; 14:36:19.239 [planetinfo.record]: cloud_alpha = 1.000000; 14:36:19.239 [planetinfo.record]: cloud_color = 0.833923, 0.833015, 0.849609, 1; 14:36:19.239 [planetinfo.record]: cloud_fraction = 0.250000; 14:36:19.239 [planetinfo.record]: land_color = 0.66, 0.208828, 0.219402, 1; 14:36:19.240 [planetinfo.record]: land_fraction = 0.470000; 14:36:19.240 [planetinfo.record]: polar_cloud_color = 0.872143, 0.871554, 0.882324, 1; 14:36:19.240 [planetinfo.record]: polar_land_color = 0.934, 0.774381, 0.778122, 1; 14:36:19.240 [planetinfo.record]: polar_sea_color = 0.935352, 0.869342, 0.867758, 1; 14:36:19.240 [planetinfo.record]: sea_color = 0.646484, 0.46399, 0.45961, 1; 14:36:19.240 [planetinfo.record]: rotation_speed = 0.001772 14:36:19.240 [planetinfo.record]: planet zpos = 508640.000000 14:36:19.240 [planetinfo.record]: sun_radius = 108106.179199 14:36:19.240 [planetinfo.record]: sun_vector = 0.466 -0.121 -0.876 14:36:19.240 [planetinfo.record]: sun_distance = 878560 14:36:19.240 [planetinfo.record]: corona_flare = 0.008940 14:36:19.240 [planetinfo.record]: corona_hues = 0.613823 14:36:19.240 [planetinfo.record]: sun_color = 0.292949, 0.496594, 0.688638, 1 14:36:19.240 [planetinfo.record]: corona_shimmer = 0.243483 14:36:19.241 [planetinfo.record]: station_vector = -0.895 0.002 0.446 14:36:19.241 [planetinfo.record]: station = coriolis 14:36:23.899 [PLANETINFO OVER]: Done 14:36:23.901 [PLANETINFO LOGGING]: 7 22 14:36:24.904 [planetinfo.record]: seed = 0 74 253 204 14:36:24.904 [planetinfo.record]: coordinates = 74 45 14:36:24.904 [planetinfo.record]: government = 0; 14:36:24.904 [planetinfo.record]: economy = 7; 14:36:24.904 [planetinfo.record]: techlevel = 2; 14:36:24.904 [planetinfo.record]: population = 16; 14:36:24.904 [planetinfo.record]: productivity = 1536; 14:36:24.905 [planetinfo.record]: name = "Inzaan"; 14:36:24.905 [planetinfo.record]: inhabitant = "Insect"; 14:36:24.905 [planetinfo.record]: inhabitants = "Insects"; 14:36:24.905 [planetinfo.record]: description = "Inzaan is most noted for the Inzaanian evil poet and Inzaanian Nu brandy."; 14:36:24.940 [planetinfo.record]: air_color = 0.604454, 0.496398, 0.93266, 1; 14:36:24.940 [planetinfo.record]: cloud_alpha = 1.000000; 14:36:24.941 [planetinfo.record]: cloud_color = 0.644721, 0.156403, 0.800781, 1; 14:36:24.941 [planetinfo.record]: cloud_fraction = 0.140000; 14:36:24.941 [planetinfo.record]: land_color = 0.428855, 0.66, 0.270703, 1; 14:36:24.941 [planetinfo.record]: land_fraction = 0.460000; 14:36:24.941 [planetinfo.record]: polar_cloud_color = 0.755558, 0.427655, 0.860352, 1; 14:36:24.941 [planetinfo.record]: polar_land_color = 0.852224, 0.934, 0.796272, 1; 14:36:24.941 [planetinfo.record]: polar_sea_color = 0.939258, 0.931103, 0.887067, 1; 14:36:24.941 [planetinfo.record]: sea_color = 0.607422, 0.586327, 0.472413, 1; 14:36:24.941 [planetinfo.record]: rotation_speed = 0.004753 14:36:24.941 [planetinfo.record]: planet zpos = 715440.000000 14:36:24.941 [planetinfo.record]: sun_radius = 170920.794983 14:36:24.942 [planetinfo.record]: sun_vector = -0.268 0.962 -0.045 14:36:24.942 [planetinfo.record]: sun_distance = 1132780 14:36:24.942 [planetinfo.record]: corona_flare = 0.032521 14:36:24.942 [planetinfo.record]: corona_hues = 0.600655 14:36:24.942 [planetinfo.record]: sun_color = 0.630387, 0.748389, 0.756345, 1 14:36:24.942 [planetinfo.record]: corona_shimmer = 0.270747 14:36:24.942 [planetinfo.record]: station_vector = -0.995 -0.018 0.093 14:36:24.942 [planetinfo.record]: station = coriolis 14:36:30.924 [PLANETINFO OVER]: Done 14:36:30.925 [PLANETINFO LOGGING]: 7 23 14:36:31.930 [planetinfo.record]: seed = 22 108 47 11 14:36:31.930 [planetinfo.record]: coordinates = 108 91 14:36:31.930 [planetinfo.record]: government = 2; 14:36:31.930 [planetinfo.record]: economy = 3; 14:36:31.930 [planetinfo.record]: techlevel = 5; 14:36:31.930 [planetinfo.record]: population = 26; 14:36:31.930 [planetinfo.record]: productivity = 8736; 14:36:31.930 [planetinfo.record]: name = "Maenes"; 14:36:31.930 [planetinfo.record]: inhabitant = "Human Colonial"; 14:36:31.930 [planetinfo.record]: inhabitants = "Human Colonials"; 14:36:31.930 [planetinfo.record]: description = "The world Maenes is a boring world."; 14:36:31.948 [planetinfo.record]: air_color = 0.743468, 0.590736, 0.888434, 1; 14:36:31.948 [planetinfo.record]: cloud_alpha = 1.000000; 14:36:31.948 [planetinfo.record]: cloud_color = 0.667969, 0.396606, 0.517447, 1; 14:36:31.948 [planetinfo.record]: cloud_fraction = 0.500000; 14:36:31.948 [planetinfo.record]: land_color = 0.66, 0.0592969, 0.228245, 1; 14:36:31.948 [planetinfo.record]: land_fraction = 0.470000; 14:36:31.948 [planetinfo.record]: polar_cloud_color = 0.800586, 0.597312, 0.687833, 1; 14:36:31.948 [planetinfo.record]: polar_land_color = 0.934, 0.721479, 0.78125, 1; 14:36:31.948 [planetinfo.record]: polar_sea_color = 0.925139, 0.938867, 0.891374, 1; 14:36:31.948 [planetinfo.record]: sea_color = 0.575572, 0.611328, 0.48763, 1; 14:36:31.948 [planetinfo.record]: rotation_speed = 0.001388 14:36:31.948 [planetinfo.record]: planet zpos = 574000.000000 14:36:31.948 [planetinfo.record]: sun_radius = 177310.610962 14:36:31.948 [planetinfo.record]: sun_vector = -0.927 0.306 -0.215 14:36:31.948 [planetinfo.record]: sun_distance = 1148000 14:36:31.948 [planetinfo.record]: corona_flare = 0.022821 14:36:31.948 [planetinfo.record]: corona_hues = 0.752075 14:36:31.948 [planetinfo.record]: sun_color = 0.619085, 0.737428, 0.847836, 1 14:36:31.948 [planetinfo.record]: corona_shimmer = 0.999329 14:36:31.949 [planetinfo.record]: station_vector = -0.306 -0.273 0.912 14:36:31.949 [planetinfo.record]: station = coriolis 14:36:36.903 [PLANETINFO OVER]: Done 14:36:36.904 [PLANETINFO LOGGING]: 7 24 14:36:37.908 [planetinfo.record]: seed = 20 39 249 66 14:36:37.908 [planetinfo.record]: coordinates = 39 73 14:36:37.908 [planetinfo.record]: government = 2; 14:36:37.908 [planetinfo.record]: economy = 1; 14:36:37.908 [planetinfo.record]: techlevel = 10; 14:36:37.908 [planetinfo.record]: population = 44; 14:36:37.908 [planetinfo.record]: productivity = 19008; 14:36:37.908 [planetinfo.record]: name = "Xebeis"; 14:36:37.908 [planetinfo.record]: inhabitant = "Large Yellow Rodent"; 14:36:37.908 [planetinfo.record]: inhabitants = "Large Yellow Rodents"; 14:36:37.908 [planetinfo.record]: description = "The world Xebeis is scourged by evil disease."; 14:36:37.923 [planetinfo.record]: air_color = 0.705373, 0.468796, 0.976236, 1; 14:36:37.923 [planetinfo.record]: cloud_alpha = 1.000000; 14:36:37.923 [planetinfo.record]: cloud_color = 0.931641, 0.0363922, 0.560952, 1; 14:36:37.923 [planetinfo.record]: cloud_fraction = 0.400000; 14:36:37.923 [planetinfo.record]: land_color = 0.338658, 0.564453, 0.0661469, 1; 14:36:37.924 [planetinfo.record]: land_fraction = 0.340000; 14:36:37.924 [planetinfo.record]: polar_cloud_color = 0.919238, 0.367157, 0.690642, 1; 14:36:37.924 [planetinfo.record]: polar_land_color = 0.849193, 0.943555, 0.735309, 1; 14:36:37.924 [planetinfo.record]: polar_sea_color = 0.941992, 0.890054, 0.884682, 1; 14:36:37.924 [planetinfo.record]: sea_color = 0.580078, 0.452145, 0.438911, 1; 14:36:37.924 [planetinfo.record]: rotation_speed = 0.004997 14:36:37.924 [planetinfo.record]: planet zpos = 505050.000000 14:36:37.924 [planetinfo.record]: sun_radius = 85410.601044 14:36:37.924 [planetinfo.record]: sun_vector = -0.954 -0.211 -0.212 14:36:37.924 [planetinfo.record]: sun_distance = 808080 14:36:37.924 [planetinfo.record]: corona_flare = 0.089079 14:36:37.924 [planetinfo.record]: corona_hues = 0.752769 14:36:37.924 [planetinfo.record]: sun_color = 0.840945, 0.692477, 0.391521, 1 14:36:37.924 [planetinfo.record]: corona_shimmer = 0.422415 14:36:37.924 [planetinfo.record]: station_vector = -0.284 0.763 0.580 14:36:37.924 [planetinfo.record]: station = coriolis 14:36:42.950 [PLANETINFO OVER]: Done 14:36:42.951 [PLANETINFO LOGGING]: 7 25 14:36:43.954 [planetinfo.record]: seed = 74 20 155 229 14:36:43.954 [planetinfo.record]: coordinates = 20 29 14:36:43.954 [planetinfo.record]: government = 1; 14:36:43.954 [planetinfo.record]: economy = 7; 14:36:43.954 [planetinfo.record]: techlevel = 1; 14:36:43.954 [planetinfo.record]: population = 13; 14:36:43.954 [planetinfo.record]: productivity = 1560; 14:36:43.954 [planetinfo.record]: name = "Cetiat"; 14:36:43.954 [planetinfo.record]: inhabitant = "Fierce Bug-Eyed Lizard"; 14:36:43.954 [planetinfo.record]: inhabitants = "Fierce Bug-Eyed Lizards"; 14:36:43.954 [planetinfo.record]: description = "This world is very fabled for its weird volcanoes."; 14:36:43.963 [planetinfo.record]: air_color = 0.469348, 0.700496, 0.852662, 1; 14:36:43.963 [planetinfo.record]: cloud_alpha = 1.000000; 14:36:43.964 [planetinfo.record]: cloud_color = 0.140137, 0.560547, 0.225533, 1; 14:36:43.964 [planetinfo.record]: cloud_fraction = 0.150000; 14:36:43.964 [planetinfo.record]: land_color = 0.66, 0.613594, 0.653837, 1; 14:36:43.964 [planetinfo.record]: land_fraction = 0.340000; 14:36:43.964 [planetinfo.record]: polar_cloud_color = 0.399631, 0.752246, 0.471256, 1; 14:36:43.964 [planetinfo.record]: polar_land_color = 0.934, 0.917582, 0.931819, 1; 14:36:43.964 [planetinfo.record]: polar_sea_color = 0.949414, 0.907228, 0.74173, 1; 14:36:43.964 [planetinfo.record]: sea_color = 0.505859, 0.415951, 0.0632324, 1; 14:36:43.964 [planetinfo.record]: rotation_speed = 0.001162 14:36:43.965 [planetinfo.record]: planet zpos = 452760.000000 14:36:43.965 [planetinfo.record]: sun_radius = 99029.945068 14:36:43.965 [planetinfo.record]: sun_vector = -0.069 0.904 0.421 14:36:43.965 [planetinfo.record]: sun_distance = 782040 14:36:43.965 [planetinfo.record]: corona_flare = 0.036917 14:36:43.965 [planetinfo.record]: corona_hues = 0.897064 14:36:43.965 [planetinfo.record]: sun_color = 0.265696, 0.639713, 0.73345, 1 14:36:43.965 [planetinfo.record]: corona_shimmer = 0.406813 14:36:43.966 [planetinfo.record]: station_vector = -0.535 0.025 0.844 14:36:43.966 [planetinfo.record]: station = coriolis 14:36:49.141 [PLANETINFO OVER]: Done 14:36:49.142 [PLANETINFO LOGGING]: 7 26 14:36:50.146 [planetinfo.record]: seed = 168 14 181 55 14:36:50.146 [planetinfo.record]: coordinates = 14 17 14:36:50.146 [planetinfo.record]: government = 5; 14:36:50.146 [planetinfo.record]: economy = 1; 14:36:50.146 [planetinfo.record]: techlevel = 11; 14:36:50.146 [planetinfo.record]: population = 51; 14:36:50.146 [planetinfo.record]: productivity = 33048; 14:36:50.146 [planetinfo.record]: name = "Titiridi"; 14:36:50.146 [planetinfo.record]: inhabitant = "Red Lizard"; 14:36:50.146 [planetinfo.record]: inhabitants = "Red Lizards"; 14:36:50.146 [planetinfo.record]: description = "The planet Titiridi is reasonably noted for its inhabitants’ eccentric shyness and its inhabitants’ eccentric love for poetry."; 14:36:50.160 [planetinfo.record]: air_color = 0.439182, 0.563246, 0.917051, 1; 14:36:50.160 [planetinfo.record]: cloud_alpha = 1.000000; 14:36:50.160 [planetinfo.record]: cloud_color = 0.0265045, 0.583421, 0.753906, 1; 14:36:50.160 [planetinfo.record]: cloud_fraction = 0.350000; 14:36:50.160 [planetinfo.record]: land_color = 0.634622, 0.543984, 0.66, 1; 14:36:50.160 [planetinfo.record]: land_fraction = 0.460000; 14:36:50.160 [planetinfo.record]: polar_cloud_color = 0.333162, 0.720642, 0.839258, 1; 14:36:50.160 [planetinfo.record]: polar_land_color = 0.925021, 0.892955, 0.934, 1; 14:36:50.160 [planetinfo.record]: polar_sea_color = 0.935742, 0.930944, 0.879908, 1; 14:36:50.160 [planetinfo.record]: sea_color = 0.642578, 0.629398, 0.489213, 1; 14:36:50.160 [planetinfo.record]: rotation_speed = 0.002801 14:36:50.160 [planetinfo.record]: planet zpos = 600860.000000 14:36:50.160 [planetinfo.record]: sun_radius = 120515.039062 14:36:50.160 [planetinfo.record]: sun_vector = 0.562 -0.521 0.642 14:36:50.160 [planetinfo.record]: sun_distance = 1016840 14:36:50.160 [planetinfo.record]: corona_flare = 0.069566 14:36:50.160 [planetinfo.record]: corona_hues = 0.537186 14:36:50.160 [planetinfo.record]: sun_color = 0.839044, 0.71882, 0.683375, 1 14:36:50.160 [planetinfo.record]: corona_shimmer = 0.477007 14:36:50.161 [planetinfo.record]: station_vector = 0.697 0.683 0.218 14:36:50.161 [planetinfo.record]: station = dodecahedron 14:36:55.331 [PLANETINFO OVER]: Done 14:36:55.333 [PLANETINFO LOGGING]: 7 27 14:36:56.349 [planetinfo.record]: seed = 190 45 199 35 14:36:56.349 [planetinfo.record]: coordinates = 45 158 14:36:56.349 [planetinfo.record]: government = 7; 14:36:56.349 [planetinfo.record]: economy = 6; 14:36:56.350 [planetinfo.record]: techlevel = 6; 14:36:56.350 [planetinfo.record]: population = 38; 14:36:56.350 [planetinfo.record]: productivity = 13376; 14:36:56.350 [planetinfo.record]: name = "Geale"; 14:36:56.350 [planetinfo.record]: inhabitant = "Large Red Bony Feline"; 14:36:56.350 [planetinfo.record]: inhabitants = "Large Red Bony Felines"; 14:36:56.350 [planetinfo.record]: description = "The planet Geale is scourged by killer edible Stleusoids."; 14:36:56.351 [planetinfo.record]: air_color = 0.610681, 0.519272, 0.913148, 1; 14:36:56.351 [planetinfo.record]: cloud_alpha = 1.000000; 14:36:56.351 [planetinfo.record]: cloud_color = 0.593016, 0.226135, 0.742188, 1; 14:36:56.351 [planetinfo.record]: cloud_fraction = 0.290000; 14:36:56.351 [planetinfo.record]: land_color = 0.146953, 0.359387, 0.66, 1; 14:36:56.351 [planetinfo.record]: land_fraction = 0.570000; 14:36:56.351 [planetinfo.record]: polar_cloud_color = 0.729221, 0.47156, 0.833984, 1; 14:36:56.351 [planetinfo.record]: polar_land_color = 0.75249, 0.827647, 0.934, 1; 14:36:56.351 [planetinfo.record]: polar_sea_color = 0.933594, 0.906214, 0.877068, 1; 14:36:56.351 [planetinfo.record]: sea_color = 0.664062, 0.586162, 0.503235, 1; 14:36:56.351 [planetinfo.record]: rotation_speed = 0.000765 14:36:56.351 [planetinfo.record]: planet zpos = 362900.000000 14:36:56.352 [planetinfo.record]: sun_radius = 61846.275635 14:36:56.352 [planetinfo.record]: sun_vector = 0.553 0.830 0.067 14:36:56.352 [planetinfo.record]: sun_distance = 834670 14:36:56.352 [planetinfo.record]: corona_flare = 0.019690 14:36:56.352 [planetinfo.record]: corona_hues = 0.811615 14:36:56.352 [planetinfo.record]: sun_color = 0.714722, 0.600118, 0.542939, 1 14:36:56.352 [planetinfo.record]: corona_shimmer = 0.914286 14:36:56.352 [planetinfo.record]: station_vector = 0.700 0.093 0.708 14:36:56.352 [planetinfo.record]: station = coriolis 14:37:01.168 [PLANETINFO OVER]: Done 14:37:01.170 [PLANETINFO LOGGING]: 7 28 14:37:02.188 [planetinfo.record]: seed = 188 84 177 133 14:37:02.188 [planetinfo.record]: coordinates = 84 65 14:37:02.188 [planetinfo.record]: government = 7; 14:37:02.188 [planetinfo.record]: economy = 1; 14:37:02.188 [planetinfo.record]: techlevel = 10; 14:37:02.188 [planetinfo.record]: population = 49; 14:37:02.188 [planetinfo.record]: productivity = 38808; 14:37:02.188 [planetinfo.record]: name = "Ceanve"; 14:37:02.188 [planetinfo.record]: inhabitant = "Fierce Black Furry Feline"; 14:37:02.188 [planetinfo.record]: inhabitants = "Fierce Black Furry Felines"; 14:37:02.188 [planetinfo.record]: description = "This world is very notable for the Ceanveian edible poet."; 14:37:02.194 [planetinfo.record]: air_color = 0.726881, 0.630876, 0.840305, 1; 14:37:02.194 [planetinfo.record]: cloud_alpha = 1.000000; 14:37:02.194 [planetinfo.record]: cloud_color = 0.523438, 0.44574, 0.493087, 1; 14:37:02.194 [planetinfo.record]: cloud_fraction = 0.360000; 14:37:02.194 [planetinfo.record]: land_color = 0.66, 0.257812, 0.257812, 1; 14:37:02.195 [planetinfo.record]: land_fraction = 0.500000; 14:37:02.195 [planetinfo.record]: polar_cloud_color = 0.735547, 0.667308, 0.708891, 1; 14:37:02.195 [planetinfo.record]: polar_land_color = 0.934, 0.791711, 0.791711, 1; 14:37:02.195 [planetinfo.record]: polar_sea_color = 0.944336, 0.880704, 0.94185, 1; 14:37:02.195 [planetinfo.record]: sea_color = 0.556641, 0.406609, 0.55078, 1; 14:37:02.195 [planetinfo.record]: rotation_speed = 0.003159 14:37:02.195 [planetinfo.record]: planet zpos = 501600.000000 14:37:02.195 [planetinfo.record]: sun_radius = 85868.078613 14:37:02.195 [planetinfo.record]: sun_vector = 0.290 0.756 0.587 14:37:02.195 [planetinfo.record]: sun_distance = 836000 14:37:02.195 [planetinfo.record]: corona_flare = 0.072624 14:37:02.195 [planetinfo.record]: corona_hues = 0.713036 14:37:02.195 [planetinfo.record]: sun_color = 0.771121, 0.769926, 0.769745, 1 14:37:02.195 [planetinfo.record]: corona_shimmer = 0.595923 14:37:02.196 [planetinfo.record]: station_vector = -0.418 -0.258 0.871 14:37:02.196 [planetinfo.record]: station = coriolis 14:37:06.819 [PLANETINFO OVER]: Done 14:37:06.820 [PLANETINFO LOGGING]: 7 29 14:37:07.823 [planetinfo.record]: seed = 114 151 51 169 14:37:07.823 [planetinfo.record]: coordinates = 151 246 14:37:07.823 [planetinfo.record]: government = 6; 14:37:07.823 [planetinfo.record]: economy = 6; 14:37:07.823 [planetinfo.record]: techlevel = 7; 14:37:07.824 [planetinfo.record]: population = 41; 14:37:07.824 [planetinfo.record]: productivity = 13120; 14:37:07.824 [planetinfo.record]: name = "Esveti"; 14:37:07.824 [planetinfo.record]: inhabitant = "Human Colonial"; 14:37:07.824 [planetinfo.record]: inhabitants = "Human Colonials"; 14:37:07.824 [planetinfo.record]: description = "The planet Esveti is an unremarkable dump."; 14:37:07.848 [planetinfo.record]: air_color = 0.725926, 0.770304, 0.942416, 1; 14:37:07.848 [planetinfo.record]: cloud_alpha = 1.000000; 14:37:07.848 [planetinfo.record]: cloud_color = 0.755501, 0.800364, 0.830078, 1; 14:37:07.848 [planetinfo.record]: cloud_fraction = 0.330000; 14:37:07.848 [planetinfo.record]: land_color = 0.636716, 0.572344, 0.66, 1; 14:37:07.848 [planetinfo.record]: land_fraction = 0.410000; 14:37:07.848 [planetinfo.record]: polar_cloud_color = 0.824484, 0.853991, 0.873535, 1; 14:37:07.848 [planetinfo.record]: polar_land_color = 0.925763, 0.902988, 0.934, 1; 14:37:07.848 [planetinfo.record]: polar_sea_color = 0.947656, 0.796433, 0.714444, 1; 14:37:07.848 [planetinfo.record]: sea_color = 0.523438, 0.189324, 0.00817871, 1; 14:37:07.848 [planetinfo.record]: rotation_speed = 0.000529 14:37:07.848 [planetinfo.record]: planet zpos = 579810.000000 14:37:07.848 [planetinfo.record]: sun_radius = 129818.964844 14:37:07.848 [planetinfo.record]: sun_vector = 0.505 0.611 0.610 14:37:07.849 [planetinfo.record]: sun_distance = 1212330 14:37:07.849 [planetinfo.record]: corona_flare = 0.019557 14:37:07.849 [planetinfo.record]: corona_hues = 0.824898 14:37:07.849 [planetinfo.record]: sun_color = 0.784482, 0.777946, 0.777003, 1 14:37:07.849 [planetinfo.record]: corona_shimmer = 0.420761 14:37:07.849 [planetinfo.record]: station_vector = 0.552 0.788 0.273 14:37:07.849 [planetinfo.record]: station = coriolis 14:37:12.411 [PLANETINFO OVER]: Done 14:37:12.413 [PLANETINFO LOGGING]: 7 30 14:37:13.422 [planetinfo.record]: seed = 80 87 109 5 14:37:13.422 [planetinfo.record]: coordinates = 87 119 14:37:13.422 [planetinfo.record]: government = 2; 14:37:13.422 [planetinfo.record]: economy = 7; 14:37:13.422 [planetinfo.record]: techlevel = 4; 14:37:13.422 [planetinfo.record]: population = 26; 14:37:13.422 [planetinfo.record]: productivity = 3744; 14:37:13.422 [planetinfo.record]: name = "Ceraerar"; 14:37:13.422 [planetinfo.record]: inhabitant = "Human Colonial"; 14:37:13.422 [planetinfo.record]: inhabitants = "Human Colonials"; 14:37:13.422 [planetinfo.record]: description = "The planet Ceraerar is very famous for its unusual casinos but beset by evil disease."; 14:37:13.474 [planetinfo.record]: air_color = 0.487818, 0.583509, 0.879979, 1; 14:37:13.474 [planetinfo.record]: cloud_alpha = 1.000000; 14:37:13.474 [planetinfo.record]: cloud_color = 0.168175, 0.509152, 0.642578, 1; 14:37:13.474 [planetinfo.record]: cloud_fraction = 0.280000; 14:37:13.474 [planetinfo.record]: land_color = 0.591797, 0.588727, 0.56868, 1; 14:37:13.474 [planetinfo.record]: land_fraction = 0.690000; 14:37:13.474 [planetinfo.record]: polar_cloud_color = 0.425021, 0.686746, 0.78916, 1; 14:37:13.474 [planetinfo.record]: polar_land_color = 0.94082, 0.9396, 0.931633, 1; 14:37:13.474 [planetinfo.record]: polar_sea_color = 0.887026, 0.918359, 0.825089, 1; 14:37:13.474 [planetinfo.record]: sea_color = 0.704988, 0.816406, 0.484741, 1; 14:37:13.474 [planetinfo.record]: rotation_speed = 0.000914 14:37:13.474 [planetinfo.record]: planet zpos = 376470.000000 14:37:13.474 [planetinfo.record]: sun_radius = 133533.543396 14:37:13.474 [planetinfo.record]: sun_vector = -0.920 -0.309 -0.241 14:37:13.474 [planetinfo.record]: sun_distance = 836600 14:37:13.474 [planetinfo.record]: corona_flare = 0.079666 14:37:13.474 [planetinfo.record]: corona_hues = 0.757492 14:37:13.475 [planetinfo.record]: sun_color = 0.713718, 0.512498, 0.221662, 1 14:37:13.475 [planetinfo.record]: corona_shimmer = 1.369556 14:37:13.475 [planetinfo.record]: station_vector = -0.713 0.476 0.515 14:37:13.475 [planetinfo.record]: station = coriolis 14:37:18.048 [PLANETINFO OVER]: Done 14:37:18.048 [PLANETINFO LOGGING]: 7 31 14:37:19.051 [planetinfo.record]: seed = 102 10 95 15 14:37:19.051 [planetinfo.record]: coordinates = 10 48 14:37:19.051 [planetinfo.record]: government = 4; 14:37:19.051 [planetinfo.record]: economy = 0; 14:37:19.051 [planetinfo.record]: techlevel = 11; 14:37:19.051 [planetinfo.record]: population = 49; 14:37:19.051 [planetinfo.record]: productivity = 31360; 14:37:19.051 [planetinfo.record]: name = "Aarzari"; 14:37:19.051 [planetinfo.record]: inhabitant = "Human Colonial"; 14:37:19.051 [planetinfo.record]: inhabitants = "Human Colonials"; 14:37:19.051 [planetinfo.record]: description = "Aarzari is mildly notable for Aarzariian lethal brandy."; 14:37:19.061 [planetinfo.record]: air_color = 0.68622, 0.541422, 0.914449, 1; 14:37:19.061 [planetinfo.record]: cloud_alpha = 1.000000; 14:37:19.062 [planetinfo.record]: cloud_color = 0.746094, 0.279785, 0.662304, 1; 14:37:19.062 [planetinfo.record]: cloud_fraction = 0.230000; 14:37:19.062 [planetinfo.record]: land_color = 0.394453, 0.56042, 0.66, 1; 14:37:19.062 [planetinfo.record]: land_fraction = 0.500000; 14:37:19.062 [planetinfo.record]: polar_cloud_color = 0.835742, 0.50928, 0.777081, 1; 14:37:19.062 [planetinfo.record]: polar_land_color = 0.840053, 0.89877, 0.934, 1; 14:37:19.062 [planetinfo.record]: polar_sea_color = 0.931776, 0.938086, 0.887609, 1; 14:37:19.062 [planetinfo.record]: sea_color = 0.602483, 0.619141, 0.48588, 1; 14:37:19.062 [planetinfo.record]: rotation_speed = 0.000125 14:37:19.062 [planetinfo.record]: planet zpos = 733260.000000 14:37:19.062 [planetinfo.record]: sun_radius = 155692.233582 14:37:19.062 [planetinfo.record]: sun_vector = 0.049 0.812 -0.582 14:37:19.062 [planetinfo.record]: sun_distance = 1199880 14:37:19.062 [planetinfo.record]: corona_flare = 0.007935 14:37:19.062 [planetinfo.record]: corona_hues = 0.773132 14:37:19.062 [planetinfo.record]: sun_color = 0.695224, 0.66407, 0.539012, 1 14:37:19.063 [planetinfo.record]: corona_shimmer = 0.652424 14:37:19.063 [planetinfo.record]: station_vector = -0.464 -0.865 0.192 14:37:19.063 [planetinfo.record]: station = dodecahedron 14:37:24.120 [PLANETINFO OVER]: Done 14:37:24.137 [PLANETINFO LOGGING]: 7 32 14:37:25.126 [planetinfo.record]: seed = 100 190 105 109 14:37:25.127 [planetinfo.record]: coordinates = 190 100 14:37:25.127 [planetinfo.record]: government = 4; 14:37:25.127 [planetinfo.record]: economy = 4; 14:37:25.127 [planetinfo.record]: techlevel = 7; 14:37:25.127 [planetinfo.record]: population = 37; 14:37:25.127 [planetinfo.record]: productivity = 14208; 14:37:25.127 [planetinfo.record]: name = "Dieranor"; 14:37:25.127 [planetinfo.record]: inhabitant = "Human Colonial"; 14:37:25.127 [planetinfo.record]: inhabitants = "Human Colonials"; 14:37:25.127 [planetinfo.record]: description = "Dieranor is an unremarkable planet."; 14:37:25.133 [planetinfo.record]: air_color = 0.594912, 0.912997, 0.964529, 1; 14:37:25.134 [planetinfo.record]: cloud_alpha = 1.000000; 14:37:25.134 [planetinfo.record]: cloud_color = 0.684319, 0.896484, 0.402718, 1; 14:37:25.134 [planetinfo.record]: cloud_fraction = 0.160000; 14:37:25.134 [planetinfo.record]: land_color = 0.647109, 0.66, 0.656979, 1; 14:37:25.134 [planetinfo.record]: land_fraction = 0.680000; 14:37:25.134 [planetinfo.record]: polar_cloud_color = 0.769789, 0.903418, 0.592427, 1; 14:37:25.134 [planetinfo.record]: polar_land_color = 0.929439, 0.934, 0.932931, 1; 14:37:25.134 [planetinfo.record]: polar_sea_color = 0.877652, 0.924414, 0.924414, 1; 14:37:25.134 [planetinfo.record]: sea_color = 0.602916, 0.755859, 0.755859, 1; 14:37:25.134 [planetinfo.record]: rotation_speed = 0.001290 14:37:25.134 [planetinfo.record]: planet zpos = 696740.000000 14:37:25.134 [planetinfo.record]: sun_radius = 164596.435852 14:37:25.134 [planetinfo.record]: sun_vector = -0.504 -0.767 -0.397 14:37:25.134 [planetinfo.record]: sun_distance = 1330140 14:37:25.134 [planetinfo.record]: corona_flare = 0.068837 14:37:25.134 [planetinfo.record]: corona_hues = 0.845871 14:37:25.134 [planetinfo.record]: sun_color = 0.298197, 0.561937, 0.676093, 1 14:37:25.134 [planetinfo.record]: corona_shimmer = 0.277897 14:37:25.135 [planetinfo.record]: station_vector = 0.982 -0.111 0.153 14:37:25.135 [planetinfo.record]: station = coriolis 14:37:31.061 [PLANETINFO OVER]: Done 14:37:31.063 [PLANETINFO LOGGING]: 7 33 14:37:32.077 [planetinfo.record]: seed = 154 185 203 5 14:37:32.078 [planetinfo.record]: coordinates = 185 187 14:37:32.078 [planetinfo.record]: government = 3; 14:37:32.078 [planetinfo.record]: economy = 3; 14:37:32.078 [planetinfo.record]: techlevel = 7; 14:37:32.078 [planetinfo.record]: population = 35; 14:37:32.078 [planetinfo.record]: productivity = 13720; 14:37:32.078 [planetinfo.record]: name = "Ceanquan"; 14:37:32.078 [planetinfo.record]: inhabitant = "Fierce Green Horned Lobster"; 14:37:32.078 [planetinfo.record]: inhabitants = "Fierce Green Horned Lobsters"; 14:37:32.078 [planetinfo.record]: description = "This planet is plagued by deadly earthquakes."; 14:37:32.108 [planetinfo.record]: air_color = 0.522549, 0.637125, 0.83315, 1; 14:37:32.108 [planetinfo.record]: cloud_alpha = 1.000000; 14:37:32.108 [planetinfo.record]: cloud_color = 0.241173, 0.501953, 0.477505, 1; 14:37:32.108 [planetinfo.record]: cloud_fraction = 0.180000; 14:37:32.108 [planetinfo.record]: land_color = 0.523438, 0.460979, 0.0531616, 1; 14:37:32.108 [planetinfo.record]: land_fraction = 0.710000; 14:37:32.108 [planetinfo.record]: polar_cloud_color = 0.490181, 0.725879, 0.703782, 1; 14:37:32.108 [planetinfo.record]: polar_land_color = 0.947656, 0.919387, 0.734804, 1; 14:37:32.108 [planetinfo.record]: polar_sea_color = 0.893199, 0.866133, 0.911719, 1; 14:37:32.108 [planetinfo.record]: sea_color = 0.811084, 0.70625, 0.882812, 1; 14:37:32.108 [planetinfo.record]: rotation_speed = 0.004835 14:37:32.108 [planetinfo.record]: planet zpos = 428100.000000 14:37:32.109 [planetinfo.record]: sun_radius = 113627.183075 14:37:32.109 [planetinfo.record]: sun_vector = 0.451 0.492 -0.745 14:37:32.109 [planetinfo.record]: sun_distance = 856200 14:37:32.109 [planetinfo.record]: corona_flare = 0.041391 14:37:32.109 [planetinfo.record]: corona_hues = 0.953255 14:37:32.109 [planetinfo.record]: sun_color = 0.440718, 0.468034, 0.671274, 1 14:37:32.109 [planetinfo.record]: corona_shimmer = 0.587324 14:37:32.109 [planetinfo.record]: station_vector = 0.988 0.048 0.150 14:37:32.109 [planetinfo.record]: station = coriolis 14:37:37.060 [PLANETINFO OVER]: Done 14:37:37.061 [PLANETINFO LOGGING]: 7 34 14:37:38.066 [planetinfo.record]: seed = 248 187 37 114 14:37:38.066 [planetinfo.record]: coordinates = 187 58 14:37:38.066 [planetinfo.record]: government = 7; 14:37:38.066 [planetinfo.record]: economy = 2; 14:37:38.066 [planetinfo.record]: techlevel = 12; 14:37:38.066 [planetinfo.record]: population = 58; 14:37:38.066 [planetinfo.record]: productivity = 40832; 14:37:38.066 [planetinfo.record]: name = "Enustien"; 14:37:38.066 [planetinfo.record]: inhabitant = "Human Colonial"; 14:37:38.066 [planetinfo.record]: inhabitants = "Human Colonials"; 14:37:38.066 [planetinfo.record]: description = "Enustien is an unremarkable planet."; 14:37:38.075 [planetinfo.record]: air_color = 0.533279, 0.98274, 0.943675, 1; 14:37:38.076 [planetinfo.record]: cloud_alpha = 1.000000; 14:37:38.076 [planetinfo.record]: cloud_color = 0.951172, 0.767254, 0.2155, 1; 14:37:38.076 [planetinfo.record]: cloud_fraction = 0.310000; 14:37:38.076 [planetinfo.record]: land_color = 0.450769, 0.66, 0.407344, 1; 14:37:38.076 [planetinfo.record]: land_fraction = 0.700000; 14:37:38.076 [planetinfo.record]: polar_cloud_color = 0.928027, 0.815876, 0.47942, 1; 14:37:38.076 [planetinfo.record]: polar_land_color = 0.859977, 0.934, 0.844613, 1; 14:37:38.076 [planetinfo.record]: polar_sea_color = 0.844528, 0.887472, 0.948242, 1; 14:37:38.076 [planetinfo.record]: sea_color = 0.291138, 0.384898, 0.517578, 1; 14:37:38.076 [planetinfo.record]: rotation_speed = 0.004103 14:37:38.076 [planetinfo.record]: planet zpos = 421800.000000 14:37:38.076 [planetinfo.record]: sun_radius = 92963.854980 14:37:38.076 [planetinfo.record]: sun_vector = -0.277 -0.958 0.074 14:37:38.076 [planetinfo.record]: sun_distance = 632700 14:37:38.076 [planetinfo.record]: corona_flare = 0.031015 14:37:38.077 [planetinfo.record]: corona_hues = 0.974869 14:37:38.077 [planetinfo.record]: sun_color = 0.752609, 0.746233, 0.313643, 1 14:37:38.077 [planetinfo.record]: corona_shimmer = 0.392201 14:37:38.077 [planetinfo.record]: station_vector = -0.915 0.393 0.096 14:37:38.077 [planetinfo.record]: station = dodecahedron 14:37:42.826 [PLANETINFO OVER]: Done 14:37:42.827 [PLANETINFO LOGGING]: 7 35 14:37:43.829 [planetinfo.record]: seed = 14 114 247 113 14:37:43.829 [planetinfo.record]: coordinates = 114 151 14:37:43.829 [planetinfo.record]: government = 1; 14:37:43.829 [planetinfo.record]: economy = 7; 14:37:43.829 [planetinfo.record]: techlevel = 3; 14:37:43.829 [planetinfo.record]: population = 21; 14:37:43.829 [planetinfo.record]: productivity = 2520; 14:37:43.829 [planetinfo.record]: name = "Atanon"; 14:37:43.829 [planetinfo.record]: inhabitant = "Blue Furry Feline"; 14:37:43.829 [planetinfo.record]: inhabitants = "Blue Furry Felines"; 14:37:43.829 [planetinfo.record]: description = "This world is mildly well known for Atanonian shrew steak and vicious Stlogein brew."; 14:37:43.844 [planetinfo.record]: air_color = 0.555687, 0.582806, 0.839004, 1; 14:37:43.844 [planetinfo.record]: cloud_alpha = 1.000000; 14:37:43.844 [planetinfo.record]: cloud_color = 0.304413, 0.363234, 0.519531, 1; 14:37:43.844 [planetinfo.record]: cloud_fraction = 0.270000; 14:37:43.844 [planetinfo.record]: land_color = 0.129067, 0.59375, 0.0765381, 1; 14:37:43.844 [planetinfo.record]: land_fraction = 0.600000; 14:37:43.844 [planetinfo.record]: polar_cloud_color = 0.543893, 0.595817, 0.733789, 1; 14:37:43.844 [planetinfo.record]: polar_land_color = 0.756586, 0.940625, 0.735782, 1; 14:37:43.844 [planetinfo.record]: polar_sea_color = 0.93418, 0.897118, 0.874881, 1; 14:37:43.844 [planetinfo.record]: sea_color = 0.658203, 0.553752, 0.491081, 1; 14:37:43.844 [planetinfo.record]: rotation_speed = 0.004561 14:37:43.844 [planetinfo.record]: planet zpos = 382320.000000 14:37:43.844 [planetinfo.record]: sun_radius = 57070.508423 14:37:43.844 [planetinfo.record]: sun_vector = 0.357 -0.922 -0.151 14:37:43.844 [planetinfo.record]: sun_distance = 541620 14:37:43.844 [planetinfo.record]: corona_flare = 0.080685 14:37:43.844 [planetinfo.record]: corona_hues = 0.930496 14:37:43.844 [planetinfo.record]: sun_color = 0.498307, 0.569167, 0.664203, 1 14:37:43.844 [planetinfo.record]: corona_shimmer = 0.695195 14:37:43.844 [planetinfo.record]: station_vector = 0.545 -0.249 0.801 14:37:43.844 [planetinfo.record]: station = coriolis 14:37:48.910 [PLANETINFO OVER]: Done 14:37:48.910 [PLANETINFO LOGGING]: 7 36 14:37:49.913 [planetinfo.record]: seed = 12 76 33 38 14:37:49.913 [planetinfo.record]: coordinates = 76 95 14:37:49.913 [planetinfo.record]: government = 1; 14:37:49.913 [planetinfo.record]: economy = 7; 14:37:49.913 [planetinfo.record]: techlevel = 1; 14:37:49.913 [planetinfo.record]: population = 13; 14:37:49.913 [planetinfo.record]: productivity = 1560; 14:37:49.913 [planetinfo.record]: name = "Biatge"; 14:37:49.913 [planetinfo.record]: inhabitant = "Human Colonial"; 14:37:49.913 [planetinfo.record]: inhabitants = "Human Colonials"; 14:37:49.913 [planetinfo.record]: description = "The world Biatge is very noted for its ancient Biatgeian Esbeusus tulip plantations but cursed by unpredictable solar activity."; 14:37:49.930 [planetinfo.record]: air_color = 0.766892, 0.589786, 0.897539, 1; 14:37:49.930 [planetinfo.record]: cloud_alpha = 1.000000; 14:37:49.930 [planetinfo.record]: cloud_color = 0.695312, 0.396545, 0.473571, 1; 14:37:49.930 [planetinfo.record]: cloud_fraction = 0.350000; 14:37:49.930 [planetinfo.record]: land_color = 0.66, 0.226875, 0.460356, 1; 14:37:49.930 [planetinfo.record]: land_fraction = 0.420000; 14:37:49.930 [planetinfo.record]: polar_cloud_color = 0.812891, 0.594585, 0.650867, 1; 14:37:49.930 [planetinfo.record]: polar_land_color = 0.934, 0.780766, 0.863369, 1; 14:37:49.930 [planetinfo.record]: polar_sea_color = 0.94707, 0.90423, 0.89722, 1; 14:37:49.930 [planetinfo.record]: sea_color = 0.529297, 0.433527, 0.417855, 1; 14:37:49.930 [planetinfo.record]: rotation_speed = 0.004365 14:37:49.930 [planetinfo.record]: planet zpos = 664200.000000 14:37:49.930 [planetinfo.record]: sun_radius = 102547.496338 14:37:49.930 [planetinfo.record]: sun_vector = 0.527 0.074 -0.847 14:37:49.930 [planetinfo.record]: sun_distance = 752760 14:37:49.931 [planetinfo.record]: corona_flare = 0.044250 14:37:49.931 [planetinfo.record]: corona_hues = 0.671562 14:37:49.931 [planetinfo.record]: sun_color = 0.835196, 0.568021, 0.485619, 1 14:37:49.931 [planetinfo.record]: corona_shimmer = 0.681928 14:37:49.931 [planetinfo.record]: station_vector = 0.395 -0.678 0.620 14:37:49.931 [planetinfo.record]: station = coriolis 14:37:54.399 [PLANETINFO OVER]: Done 14:37:54.400 [PLANETINFO LOGGING]: 7 37 14:37:55.403 [planetinfo.record]: seed = 194 58 99 79 14:37:55.403 [planetinfo.record]: coordinates = 58 67 14:37:55.403 [planetinfo.record]: government = 0; 14:37:55.403 [planetinfo.record]: economy = 3; 14:37:55.403 [planetinfo.record]: techlevel = 6; 14:37:55.404 [planetinfo.record]: population = 28; 14:37:55.404 [planetinfo.record]: productivity = 6272; 14:37:55.404 [planetinfo.record]: name = "Aditira"; 14:37:55.404 [planetinfo.record]: inhabitant = "Human Colonial"; 14:37:55.404 [planetinfo.record]: inhabitants = "Human Colonials"; 14:37:55.404 [planetinfo.record]: description = "The world Aditira is mildly fabled for the Aditiraian edible moth but beset by evil disease."; 14:37:55.410 [planetinfo.record]: air_color = 0.602729, 0.828266, 0.991846, 1; 14:37:55.410 [planetinfo.record]: cloud_alpha = 1.000000; 14:37:55.410 [planetinfo.record]: cloud_color = 0.412811, 0.978516, 0.567496, 1; 14:37:55.410 [planetinfo.record]: cloud_fraction = 0.080000; 14:37:55.410 [planetinfo.record]: land_color = 0.552734, 0.449097, 0.500106, 1; 14:37:55.410 [planetinfo.record]: land_fraction = 0.650000; 14:37:55.410 [planetinfo.record]: polar_cloud_color = 0.600564, 0.940332, 0.693469, 1; 14:37:55.410 [planetinfo.record]: polar_land_color = 0.944727, 0.900443, 0.922239, 1; 14:37:55.410 [planetinfo.record]: polar_sea_color = 0.868867, 0.95, 0.770947, 1; 14:37:55.410 [planetinfo.record]: sea_color = 0.329193, 0.5, 0.123047, 1; 14:37:55.410 [planetinfo.record]: rotation_speed = 0.004224 14:37:55.410 [planetinfo.record]: planet zpos = 805680.000000 14:37:55.410 [planetinfo.record]: sun_radius = 186272.111206 14:37:55.410 [planetinfo.record]: sun_vector = -0.682 0.050 -0.730 14:37:55.410 [planetinfo.record]: sun_distance = 1342800 14:37:55.410 [planetinfo.record]: corona_flare = 0.089526 14:37:55.410 [planetinfo.record]: corona_hues = 0.611015 14:37:55.410 [planetinfo.record]: sun_color = 0.745947, 0.746521, 0.542098, 1 14:37:55.411 [planetinfo.record]: corona_shimmer = 0.361901 14:37:55.411 [planetinfo.record]: station_vector = -0.110 0.509 0.854 14:37:55.411 [planetinfo.record]: station = coriolis 14:38:00.213 [PLANETINFO OVER]: Done 14:38:00.214 [PLANETINFO LOGGING]: 7 38 14:38:01.219 [planetinfo.record]: seed = 160 116 221 153 14:38:01.219 [planetinfo.record]: coordinates = 116 87 14:38:01.219 [planetinfo.record]: government = 4; 14:38:01.219 [planetinfo.record]: economy = 7; 14:38:01.219 [planetinfo.record]: techlevel = 2; 14:38:01.219 [planetinfo.record]: population = 20; 14:38:01.219 [planetinfo.record]: productivity = 3840; 14:38:01.219 [planetinfo.record]: name = "Orbira"; 14:38:01.219 [planetinfo.record]: inhabitant = "Black Bony Bird"; 14:38:01.219 [planetinfo.record]: inhabitants = "Black Bony Birds"; 14:38:01.219 [planetinfo.record]: description = "Orbira is ravaged by dreadful civil war."; 14:38:01.227 [planetinfo.record]: air_color = 0.461096, 0.751421, 0.839654, 1; 14:38:01.227 [planetinfo.record]: cloud_alpha = 1.000000; 14:38:01.227 [planetinfo.record]: cloud_color = 0.241979, 0.521484, 0.128334, 1; 14:38:01.227 [planetinfo.record]: cloud_fraction = 0.350000; 14:38:01.227 [planetinfo.record]: land_color = 0.529297, 0.425919, 0.513144, 1; 14:38:01.227 [planetinfo.record]: land_fraction = 0.380000; 14:38:01.227 [planetinfo.record]: polar_cloud_color = 0.488563, 0.734668, 0.388499, 1; 14:38:01.227 [planetinfo.record]: polar_land_color = 0.94707, 0.900827, 0.939845, 1; 14:38:01.227 [planetinfo.record]: polar_sea_color = 0.880757, 0.921094, 0.847334, 1; 14:38:01.227 [planetinfo.record]: sea_color = 0.650842, 0.789062, 0.536316, 1; 14:38:01.228 [planetinfo.record]: rotation_speed = 0.002236 14:38:01.228 [planetinfo.record]: planet zpos = 733040.000000 14:38:01.228 [planetinfo.record]: sun_radius = 117965.795288 14:38:01.228 [planetinfo.record]: sun_vector = -0.113 -0.544 -0.832 14:38:01.228 [planetinfo.record]: sun_distance = 1204280 14:38:01.228 [planetinfo.record]: corona_flare = 0.004735 14:38:01.228 [planetinfo.record]: corona_hues = 0.772041 14:38:01.228 [planetinfo.record]: sun_color = 0.737549, 0.767015, 0.771811, 1 14:38:01.228 [planetinfo.record]: corona_shimmer = 0.273664 14:38:01.228 [planetinfo.record]: station_vector = -0.458 -0.423 0.782 14:38:01.228 [planetinfo.record]: station = coriolis 14:38:05.625 [PLANETINFO OVER]: Done 14:38:05.626 [PLANETINFO LOGGING]: 7 39 14:38:06.632 [planetinfo.record]: seed = 182 116 143 79 14:38:06.632 [planetinfo.record]: coordinates = 116 116 14:38:06.632 [planetinfo.record]: government = 6; 14:38:06.632 [planetinfo.record]: economy = 4; 14:38:06.632 [planetinfo.record]: techlevel = 6; 14:38:06.632 [planetinfo.record]: population = 35; 14:38:06.632 [planetinfo.record]: productivity = 16800; 14:38:06.632 [planetinfo.record]: name = "Aedis"; 14:38:06.632 [planetinfo.record]: inhabitant = "Yellow Slimy Lobster"; 14:38:06.632 [planetinfo.record]: inhabitants = "Yellow Slimy Lobsters"; 14:38:06.632 [planetinfo.record]: description = "This planet is notable for its great tropical forests and mud tennis."; 14:38:06.648 [planetinfo.record]: air_color = 0.46583, 0.755623, 0.8325, 1; 14:38:06.648 [planetinfo.record]: cloud_alpha = 1.000000; 14:38:06.648 [planetinfo.record]: cloud_color = 0.268524, 0.5, 0.138672, 1; 14:38:06.648 [planetinfo.record]: cloud_fraction = 0.380000; 14:38:06.648 [planetinfo.record]: land_color = 0.66, 0.139219, 0.639657, 1; 14:38:06.648 [planetinfo.record]: land_fraction = 0.270000; 14:38:06.648 [planetinfo.record]: polar_cloud_color = 0.515225, 0.725, 0.397546, 1; 14:38:06.650 [planetinfo.record]: polar_land_color = 0.934, 0.749754, 0.926803, 1; 14:38:06.650 [planetinfo.record]: polar_sea_color = 0.941211, 0.927287, 0.89029, 1; 14:38:06.650 [planetinfo.record]: sea_color = 0.587891, 0.553103, 0.460667, 1; 14:38:06.650 [planetinfo.record]: rotation_speed = 0.003920 14:38:06.650 [planetinfo.record]: planet zpos = 880360.000000 14:38:06.650 [planetinfo.record]: sun_radius = 163196.148071 14:38:06.650 [planetinfo.record]: sun_vector = 0.249 -0.834 -0.493 14:38:06.650 [planetinfo.record]: sun_distance = 1151240 14:38:06.650 [planetinfo.record]: corona_flare = 0.032590 14:38:06.650 [planetinfo.record]: corona_hues = 0.543472 14:38:06.650 [planetinfo.record]: sun_color = 0.749893, 0.684305, 0.683272, 1 14:38:06.651 [planetinfo.record]: corona_shimmer = 0.457003 14:38:06.651 [planetinfo.record]: station_vector = -0.641 -0.597 0.483 14:38:06.651 [planetinfo.record]: station = coriolis 14:38:12.169 [PLANETINFO OVER]: Done 14:38:12.170 [PLANETINFO LOGGING]: 7 40 14:38:13.190 [planetinfo.record]: seed = 180 133 217 187 14:38:13.190 [planetinfo.record]: coordinates = 133 253 14:38:13.190 [planetinfo.record]: government = 6; 14:38:13.190 [planetinfo.record]: economy = 5; 14:38:13.190 [planetinfo.record]: techlevel = 6; 14:38:13.190 [planetinfo.record]: population = 36; 14:38:13.190 [planetinfo.record]: productivity = 14400; 14:38:13.191 [planetinfo.record]: name = "Anri"; 14:38:13.191 [planetinfo.record]: inhabitant = "Harmless Slimy Lobster"; 14:38:13.191 [planetinfo.record]: inhabitants = "Harmless Slimy Lobsters"; 14:38:13.191 [planetinfo.record]: description = "The planet Anri is very noted for its unusual casinos but beset by dreadful earthquakes."; 14:38:13.216 [planetinfo.record]: air_color = 0.671801, 0.590049, 0.860467, 1; 14:38:13.216 [planetinfo.record]: cloud_alpha = 1.000000; 14:38:13.216 [planetinfo.record]: cloud_color = 0.568301, 0.38324, 0.583984, 1; 14:38:13.216 [planetinfo.record]: cloud_fraction = 0.350000; 14:38:13.216 [planetinfo.record]: land_color = 0.630392, 0.66, 0.281016, 1; 14:38:13.216 [planetinfo.record]: land_fraction = 0.410000; 14:38:13.216 [planetinfo.record]: polar_cloud_color = 0.74999, 0.598912, 0.762793, 1; 14:38:13.216 [planetinfo.record]: polar_land_color = 0.923525, 0.934, 0.79992, 1; 14:38:13.216 [planetinfo.record]: polar_sea_color = 0.875105, 0.92832, 0.875937, 1; 14:38:13.216 [planetinfo.record]: sea_color = 0.552438, 0.716797, 0.555006, 1; 14:38:13.216 [planetinfo.record]: rotation_speed = 0.002567 14:38:13.216 [planetinfo.record]: planet zpos = 576500.000000 14:38:13.216 [planetinfo.record]: sun_radius = 159366.148376 14:38:13.216 [planetinfo.record]: sun_vector = -0.820 -0.178 0.543 14:38:13.216 [planetinfo.record]: sun_distance = 922400 14:38:13.216 [planetinfo.record]: corona_flare = 0.060143 14:38:13.216 [planetinfo.record]: corona_hues = 0.737625 14:38:13.216 [planetinfo.record]: sun_color = 0.656103, 0.483613, 0.226902, 1 14:38:13.217 [planetinfo.record]: corona_shimmer = 0.622161 14:38:13.217 [planetinfo.record]: station_vector = 0.965 -0.127 0.230 14:38:13.217 [planetinfo.record]: station = coriolis 14:38:18.460 [PLANETINFO OVER]: Done 14:38:18.461 [PLANETINFO LOGGING]: 7 41 14:38:19.475 [planetinfo.record]: seed = 234 122 251 57 14:38:19.476 [planetinfo.record]: coordinates = 122 128 14:38:19.476 [planetinfo.record]: government = 5; 14:38:19.476 [planetinfo.record]: economy = 0; 14:38:19.476 [planetinfo.record]: techlevel = 12; 14:38:19.476 [planetinfo.record]: population = 54; 14:38:19.476 [planetinfo.record]: productivity = 38880; 14:38:19.476 [planetinfo.record]: name = "Orlaaror"; 14:38:19.476 [planetinfo.record]: inhabitant = "Red Horned Lobster"; 14:38:19.476 [planetinfo.record]: inhabitants = "Red Horned Lobsters"; 14:38:19.476 [planetinfo.record]: description = "This planet is fabled for its fabulous cuisine."; 14:38:19.500 [planetinfo.record]: air_color = 0.674022, 0.852012, 0.82265, 1; 14:38:19.500 [planetinfo.record]: cloud_alpha = 1.000000; 14:38:19.500 [planetinfo.record]: cloud_color = 0.558594, 0.551195, 0.54332, 1; 14:38:19.500 [planetinfo.record]: cloud_fraction = 0.320000; 14:38:19.500 [planetinfo.record]: land_color = 0.366353, 0.338631, 0.525391, 1; 14:38:19.500 [planetinfo.record]: land_fraction = 0.600000; 14:38:19.500 [planetinfo.record]: polar_cloud_color = 0.751367, 0.745147, 0.738526, 1; 14:38:19.500 [planetinfo.record]: polar_land_color = 0.875761, 0.863263, 0.947461, 1; 14:38:19.500 [planetinfo.record]: polar_sea_color = 0.865525, 0.923633, 0.863651, 1; 14:38:19.500 [planetinfo.record]: sea_color = 0.571495, 0.763672, 0.565296, 1; 14:38:19.500 [planetinfo.record]: rotation_speed = 0.003599 14:38:19.500 [planetinfo.record]: planet zpos = 629040.000000 14:38:19.500 [planetinfo.record]: sun_radius = 149027.781982 14:38:19.500 [planetinfo.record]: sun_vector = -0.472 0.379 -0.796 14:38:19.500 [planetinfo.record]: sun_distance = 1100820 14:38:19.501 [planetinfo.record]: corona_flare = 0.002536 14:38:19.501 [planetinfo.record]: corona_hues = 0.534935 14:38:19.501 [planetinfo.record]: sun_color = 0.744177, 0.743995, 0.741239, 1 14:38:19.501 [planetinfo.record]: corona_shimmer = 0.354092 14:38:19.501 [planetinfo.record]: station_vector = 0.391 0.918 0.074 14:38:19.501 [planetinfo.record]: station = dodecahedron 14:38:25.147 [PLANETINFO OVER]: Done 14:38:25.148 [PLANETINFO LOGGING]: 7 42 14:38:26.152 [planetinfo.record]: seed = 72 89 149 120 14:38:26.152 [planetinfo.record]: coordinates = 89 234 14:38:26.152 [planetinfo.record]: government = 1; 14:38:26.152 [planetinfo.record]: economy = 2; 14:38:26.152 [planetinfo.record]: techlevel = 7; 14:38:26.152 [planetinfo.record]: population = 32; 14:38:26.152 [planetinfo.record]: productivity = 10240; 14:38:26.152 [planetinfo.record]: name = "Edandi"; 14:38:26.152 [planetinfo.record]: inhabitant = "Blue Bony Lobster"; 14:38:26.152 [planetinfo.record]: inhabitants = "Blue Bony Lobsters"; 14:38:26.152 [planetinfo.record]: description = "This world is fabled for its unusual dense forests."; 14:38:26.172 [planetinfo.record]: air_color = 0.401445, 0.847459, 0.794769, 1; 14:38:26.173 [planetinfo.record]: cloud_alpha = 1.000000; 14:38:26.173 [planetinfo.record]: cloud_color = 0.544922, 0.361264, 0.010643, 1; 14:38:26.173 [planetinfo.record]: cloud_fraction = 0.530000; 14:38:26.173 [planetinfo.record]: land_color = 0.621771, 0.283594, 0.66, 1; 14:38:26.173 [planetinfo.record]: land_fraction = 0.420000; 14:38:26.173 [planetinfo.record]: polar_cloud_color = 0.745215, 0.588237, 0.288552, 1; 14:38:26.173 [planetinfo.record]: polar_land_color = 0.920475, 0.800832, 0.934, 1; 14:38:26.173 [planetinfo.record]: polar_sea_color = 0.88544, 0.932617, 0.887283, 1; 14:38:26.173 [planetinfo.record]: sea_color = 0.537483, 0.673828, 0.542809, 1; 14:38:26.173 [planetinfo.record]: rotation_speed = 0.000342 14:38:26.173 [planetinfo.record]: planet zpos = 693420.000000 14:38:26.173 [planetinfo.record]: sun_radius = 143041.152649 14:38:26.173 [planetinfo.record]: sun_vector = -0.959 0.212 0.190 14:38:26.173 [planetinfo.record]: sun_distance = 1139190 14:38:26.173 [planetinfo.record]: corona_flare = 0.096996 14:38:26.173 [planetinfo.record]: corona_hues = 0.729492 14:38:26.173 [planetinfo.record]: sun_color = 0.692586, 0.694905, 0.701465, 1 14:38:26.173 [planetinfo.record]: corona_shimmer = 0.423490 14:38:26.174 [planetinfo.record]: station_vector = -0.088 -0.514 0.853 14:38:26.174 [planetinfo.record]: station = coriolis 14:38:31.327 [PLANETINFO OVER]: Done 14:38:31.328 [PLANETINFO LOGGING]: 7 43 14:38:32.333 [planetinfo.record]: seed = 94 194 39 12 14:38:32.333 [planetinfo.record]: coordinates = 194 141 14:38:32.333 [planetinfo.record]: government = 3; 14:38:32.333 [planetinfo.record]: economy = 5; 14:38:32.334 [planetinfo.record]: techlevel = 6; 14:38:32.334 [planetinfo.record]: population = 33; 14:38:32.334 [planetinfo.record]: productivity = 9240; 14:38:32.334 [planetinfo.record]: name = "Intearbe"; 14:38:32.334 [planetinfo.record]: inhabitant = "Human Colonial"; 14:38:32.334 [planetinfo.record]: inhabitants = "Human Colonials"; 14:38:32.334 [planetinfo.record]: description = "This world is plagued by occasional solar activity."; 14:38:32.356 [planetinfo.record]: air_color = 0.672665, 0.863068, 0.845033, 1; 14:38:32.357 [planetinfo.record]: cloud_alpha = 1.000000; 14:38:32.357 [planetinfo.record]: cloud_color = 0.591797, 0.581683, 0.55481, 1; 14:38:32.357 [planetinfo.record]: cloud_fraction = 0.240000; 14:38:32.357 [planetinfo.record]: land_color = 0.156502, 0.548828, 0.263779, 1; 14:38:32.357 [planetinfo.record]: land_fraction = 0.380000; 14:38:32.357 [planetinfo.record]: polar_cloud_color = 0.766309, 0.758124, 0.736375, 1; 14:38:32.357 [planetinfo.record]: polar_land_color = 0.776214, 0.945117, 0.822399, 1; 14:38:32.358 [planetinfo.record]: polar_sea_color = 0.859875, 0.871689, 0.948828, 1; 14:38:32.358 [planetinfo.record]: sea_color = 0.319824, 0.34531, 0.511719, 1; 14:38:32.358 [planetinfo.record]: rotation_speed = 0.003277 14:38:32.358 [planetinfo.record]: planet zpos = 790660.000000 14:38:32.358 [planetinfo.record]: sun_radius = 121892.426758 14:38:32.358 [planetinfo.record]: sun_vector = -0.254 0.964 -0.074 14:38:32.358 [planetinfo.record]: sun_distance = 1216400 14:38:32.358 [planetinfo.record]: corona_flare = 0.050002 14:38:32.358 [planetinfo.record]: corona_hues = 0.871796 14:38:32.358 [planetinfo.record]: sun_color = 0.516051, 0.586045, 0.670798, 1 14:38:32.359 [planetinfo.record]: corona_shimmer = 0.252326 14:38:32.359 [planetinfo.record]: station_vector = -0.188 0.481 0.856 14:38:32.359 [planetinfo.record]: station = coriolis 14:38:37.094 [PLANETINFO OVER]: Done 14:38:37.095 [PLANETINFO LOGGING]: 7 44 14:38:38.098 [planetinfo.record]: seed = 92 147 145 26 14:38:38.099 [planetinfo.record]: coordinates = 147 42 14:38:38.099 [planetinfo.record]: government = 3; 14:38:38.099 [planetinfo.record]: economy = 2; 14:38:38.099 [planetinfo.record]: techlevel = 10; 14:38:38.099 [planetinfo.record]: population = 46; 14:38:38.099 [planetinfo.record]: productivity = 20608; 14:38:38.099 [planetinfo.record]: name = "Quedbiqu"; 14:38:38.099 [planetinfo.record]: inhabitant = "Green Bug-Eyed Lobster"; 14:38:38.099 [planetinfo.record]: inhabitants = "Green Bug-Eyed Lobsters"; 14:38:38.099 [planetinfo.record]: description = "This planet is notable for its unusual oceans and the Quedbiquian evil poet."; 14:38:38.113 [planetinfo.record]: air_color = 0.440549, 0.611045, 0.902742, 1; 14:38:38.114 [planetinfo.record]: cloud_alpha = 1.000000; 14:38:38.114 [planetinfo.record]: cloud_color = 0.0444336, 0.710938, 0.648453, 1; 14:38:38.114 [planetinfo.record]: cloud_fraction = 0.510000; 14:38:38.114 [planetinfo.record]: land_color = 0.66, 0.0979688, 0.624873, 1; 14:38:38.114 [planetinfo.record]: land_fraction = 0.540000; 14:38:38.114 [planetinfo.record]: polar_cloud_color = 0.339499, 0.819922, 0.774882, 1; 14:38:38.115 [planetinfo.record]: polar_land_color = 0.934, 0.73516, 0.921573, 1; 14:38:38.115 [planetinfo.record]: polar_sea_color = 0.938086, 0.927051, 0.883761, 1; 14:38:38.115 [planetinfo.record]: sea_color = 0.619141, 0.590009, 0.475722, 1; 14:38:38.115 [planetinfo.record]: rotation_speed = 0.000620 14:38:38.115 [planetinfo.record]: planet zpos = 717990.000000 14:38:38.115 [planetinfo.record]: sun_radius = 136279.114838 14:38:38.115 [planetinfo.record]: sun_vector = 0.375 -0.215 0.902 14:38:38.115 [planetinfo.record]: sun_distance = 1270290 14:38:38.115 [planetinfo.record]: corona_flare = 0.073347 14:38:38.115 [planetinfo.record]: corona_hues = 0.727020 14:38:38.116 [planetinfo.record]: sun_color = 0.761035, 0.55536, 0.524907, 1 14:38:38.116 [planetinfo.record]: corona_shimmer = 0.286081 14:38:38.116 [planetinfo.record]: station_vector = 0.113 0.913 0.393 14:38:38.116 [planetinfo.record]: station = coriolis 14:38:42.573 [PLANETINFO OVER]: Done 14:38:42.574 [PLANETINFO LOGGING]: 7 45 14:38:43.576 [planetinfo.record]: seed = 18 122 147 217 14:38:43.576 [planetinfo.record]: coordinates = 122 134 14:38:43.576 [planetinfo.record]: government = 2; 14:38:43.576 [planetinfo.record]: economy = 6; 14:38:43.576 [planetinfo.record]: techlevel = 4; 14:38:43.576 [planetinfo.record]: population = 25; 14:38:43.576 [planetinfo.record]: productivity = 4800; 14:38:43.577 [planetinfo.record]: name = "Orqure"; 14:38:43.577 [planetinfo.record]: inhabitant = "Fat Humanoid"; 14:38:43.577 [planetinfo.record]: inhabitants = "Fat Humanoids"; 14:38:43.577 [planetinfo.record]: description = "The planet Orqure is an unremarkable dump."; 14:38:43.591 [planetinfo.record]: air_color = 0.539846, 0.449776, 0.957375, 1; 14:38:43.591 [planetinfo.record]: cloud_alpha = 1.000000; 14:38:43.591 [planetinfo.record]: cloud_color = 0.481613, 0.00683594, 0.875, 1; 14:38:43.591 [planetinfo.record]: cloud_fraction = 0.410000; 14:38:43.591 [planetinfo.record]: land_color = 0.607632, 0.66, 0.592969, 1; 14:38:43.591 [planetinfo.record]: land_fraction = 0.590000; 14:38:43.591 [planetinfo.record]: polar_cloud_color = 0.642615, 0.33952, 0.89375, 1; 14:38:43.591 [planetinfo.record]: polar_land_color = 0.915473, 0.934, 0.910285, 1; 14:38:43.591 [planetinfo.record]: polar_sea_color = 0.719963, 0.929688, 0.929688, 1; 14:38:43.591 [planetinfo.record]: sea_color = 0.0686646, 0.703125, 0.703125, 1; 14:38:43.591 [planetinfo.record]: rotation_speed = 0.003047 14:38:43.591 [planetinfo.record]: planet zpos = 733880.000000 14:38:43.591 [planetinfo.record]: sun_radius = 147811.986084 14:38:43.591 [planetinfo.record]: sun_vector = 0.542 0.403 -0.738 14:38:43.591 [planetinfo.record]: sun_distance = 995980 14:38:43.591 [planetinfo.record]: corona_flare = 0.032793 14:38:43.591 [planetinfo.record]: corona_hues = 0.959137 14:38:43.591 [planetinfo.record]: sun_color = 0.614913, 0.710124, 0.813931, 1 14:38:43.591 [planetinfo.record]: corona_shimmer = 0.607884 14:38:43.591 [planetinfo.record]: station_vector = -0.500 -0.828 0.255 14:38:43.591 [planetinfo.record]: station = coriolis 14:38:47.895 [PLANETINFO OVER]: Done 14:38:47.897 [PLANETINFO LOGGING]: 7 46 14:38:48.901 [planetinfo.record]: seed = 240 225 77 234 14:38:48.901 [planetinfo.record]: coordinates = 225 46 14:38:48.901 [planetinfo.record]: government = 6; 14:38:48.901 [planetinfo.record]: economy = 6; 14:38:48.901 [planetinfo.record]: techlevel = 5; 14:38:48.901 [planetinfo.record]: population = 33; 14:38:48.901 [planetinfo.record]: productivity = 10560; 14:38:48.901 [planetinfo.record]: name = "Arqubi"; 14:38:48.901 [planetinfo.record]: inhabitant = "Human Colonial"; 14:38:48.901 [planetinfo.record]: inhabitants = "Human Colonials"; 14:38:48.901 [planetinfo.record]: description = "This world is most fabled for Arqubiian Es brandy but cursed by dreadful solar activity."; 14:38:48.923 [planetinfo.record]: air_color = 0.697159, 0.752499, 0.967131, 1; 14:38:48.923 [planetinfo.record]: cloud_alpha = 1.000000; 14:38:48.924 [planetinfo.record]: cloud_color = 0.68882, 0.818443, 0.904297, 1; 14:38:48.924 [planetinfo.record]: cloud_fraction = 0.300000; 14:38:48.924 [planetinfo.record]: land_color = 0.278702, 0.377889, 0.548828, 1; 14:38:48.924 [planetinfo.record]: land_fraction = 0.360000; 14:38:48.924 [planetinfo.record]: polar_cloud_color = 0.771868, 0.853118, 0.906934, 1; 14:38:48.924 [planetinfo.record]: polar_land_color = 0.828823, 0.871525, 0.945117, 1; 14:38:48.924 [planetinfo.record]: polar_sea_color = 0.865258, 0.922461, 0.883581, 1; 14:38:48.924 [planetinfo.record]: sea_color = 0.583057, 0.775391, 0.644664, 1; 14:38:48.924 [planetinfo.record]: rotation_speed = 0.003539 14:38:48.924 [planetinfo.record]: planet zpos = 504090.000000 14:38:48.924 [planetinfo.record]: sun_radius = 129623.118439 14:38:48.924 [planetinfo.record]: sun_vector = 0.200 -0.976 -0.082 14:38:48.924 [planetinfo.record]: sun_distance = 1120200 14:38:48.924 [planetinfo.record]: corona_flare = 0.090233 14:38:48.924 [planetinfo.record]: corona_hues = 0.988258 14:38:48.924 [planetinfo.record]: sun_color = 0.213144, 0.284432, 0.692673, 1 14:38:48.924 [planetinfo.record]: corona_shimmer = 0.388486 14:38:48.925 [planetinfo.record]: station_vector = 0.583 0.357 0.730 14:38:48.925 [planetinfo.record]: station = coriolis 14:38:53.748 [PLANETINFO OVER]: Done 14:38:53.749 [PLANETINFO LOGGING]: 7 47 14:38:54.770 [planetinfo.record]: seed = 6 171 191 107 14:38:54.770 [planetinfo.record]: coordinates = 171 198 14:38:54.770 [planetinfo.record]: government = 0; 14:38:54.770 [planetinfo.record]: economy = 6; 14:38:54.770 [planetinfo.record]: techlevel = 4; 14:38:54.770 [planetinfo.record]: population = 23; 14:38:54.770 [planetinfo.record]: productivity = 2944; 14:38:54.770 [planetinfo.record]: name = "Maisrais"; 14:38:54.771 [planetinfo.record]: inhabitant = "Small Blue Furry Rodent"; 14:38:54.771 [planetinfo.record]: inhabitants = "Small Blue Furry Rodents"; 14:38:54.771 [planetinfo.record]: description = "Maisrais is famous for its inhabitants’ ancient mating traditions but ravaged by unpredictable solar activity."; 14:38:54.783 [planetinfo.record]: air_color = 0.521318, 0.462363, 0.942416, 1; 14:38:54.783 [planetinfo.record]: cloud_alpha = 1.000000; 14:38:54.783 [planetinfo.record]: cloud_color = 0.353786, 0.0583649, 0.830078, 1; 14:38:54.783 [planetinfo.record]: cloud_fraction = 0.300000; 14:38:54.783 [planetinfo.record]: land_color = 0.183047, 0.268749, 0.66, 1; 14:38:54.783 [planetinfo.record]: land_fraction = 0.460000; 14:38:54.783 [planetinfo.record]: polar_cloud_color = 0.560268, 0.365963, 0.873535, 1; 14:38:54.783 [planetinfo.record]: polar_land_color = 0.76526, 0.79558, 0.934, 1; 14:38:54.783 [planetinfo.record]: polar_sea_color = 0.931101, 0.93418, 0.877892, 1; 14:38:54.783 [planetinfo.record]: sea_color = 0.649528, 0.658203, 0.499566, 1; 14:38:54.783 [planetinfo.record]: rotation_speed = 0.002692 14:38:54.783 [planetinfo.record]: planet zpos = 696360.000000 14:38:54.783 [planetinfo.record]: sun_radius = 147476.387939 14:38:54.783 [planetinfo.record]: sun_vector = 0.504 -0.652 0.566 14:38:54.783 [planetinfo.record]: sun_distance = 986510 14:38:54.783 [planetinfo.record]: corona_flare = 0.049721 14:38:54.783 [planetinfo.record]: corona_hues = 0.787346 14:38:54.783 [planetinfo.record]: sun_color = 0.656857, 0.644923, 0.624879, 1 14:38:54.783 [planetinfo.record]: corona_shimmer = 0.483661 14:38:54.784 [planetinfo.record]: station_vector = -0.479 0.870 0.115 14:38:54.784 [planetinfo.record]: station = coriolis 14:38:59.922 [PLANETINFO OVER]: Done 14:38:59.922 [PLANETINFO LOGGING]: 7 48 14:39:00.927 [planetinfo.record]: seed = 4 61 73 14 14:39:00.927 [planetinfo.record]: coordinates = 61 244 14:39:00.927 [planetinfo.record]: government = 0; 14:39:00.927 [planetinfo.record]: economy = 6; 14:39:00.927 [planetinfo.record]: techlevel = 2; 14:39:00.927 [planetinfo.record]: population = 15; 14:39:00.927 [planetinfo.record]: productivity = 1920; 14:39:00.927 [planetinfo.record]: name = "Reonar"; 14:39:00.927 [planetinfo.record]: inhabitant = "Human Colonial"; 14:39:00.927 [planetinfo.record]: inhabitants = "Human Colonials"; 14:39:00.927 [planetinfo.record]: description = "The planet Reonar is scourged by killer edible arts graduates."; 14:39:00.934 [planetinfo.record]: air_color = 0.687783, 0.807048, 0.937213, 1; 14:39:00.934 [planetinfo.record]: cloud_alpha = 1.000000; 14:39:00.934 [planetinfo.record]: cloud_color = 0.652199, 0.814453, 0.746002, 1; 14:39:00.934 [planetinfo.record]: cloud_fraction = 0.380000; 14:39:00.935 [planetinfo.record]: land_color = 0.66, 0.103125, 0.58604, 1; 14:39:00.935 [planetinfo.record]: land_fraction = 0.510000; 14:39:00.935 [planetinfo.record]: polar_cloud_color = 0.758614, 0.866504, 0.820988, 1; 14:39:00.935 [planetinfo.record]: polar_land_color = 0.934, 0.736984, 0.907834, 1; 14:39:00.935 [planetinfo.record]: polar_sea_color = 0.940039, 0.92445, 0.887529, 1; 14:39:00.935 [planetinfo.record]: sea_color = 0.599609, 0.559835, 0.465634, 1; 14:39:00.935 [planetinfo.record]: rotation_speed = 0.003726 14:39:00.935 [planetinfo.record]: planet zpos = 839930.000000 14:39:00.935 [planetinfo.record]: sun_radius = 201843.154297 14:39:00.935 [planetinfo.record]: sun_vector = 0.854 -0.106 -0.509 14:39:00.935 [planetinfo.record]: sun_distance = 1292200 14:39:00.935 [planetinfo.record]: corona_flare = 0.044693 14:39:00.935 [planetinfo.record]: corona_hues = 0.815689 14:39:00.935 [planetinfo.record]: sun_color = 0.799135, 0.802626, 0.810742, 1 14:39:00.935 [planetinfo.record]: corona_shimmer = 0.924266 14:39:00.936 [planetinfo.record]: station_vector = 0.671 0.740 0.053 14:39:00.936 [planetinfo.record]: station = coriolis 14:39:05.080 [PLANETINFO OVER]: Done 14:39:05.080 [PLANETINFO LOGGING]: 7 49 14:39:06.084 [planetinfo.record]: seed = 58 216 43 162 14:39:06.084 [planetinfo.record]: coordinates = 216 138 14:39:06.084 [planetinfo.record]: government = 7; 14:39:06.085 [planetinfo.record]: economy = 2; 14:39:06.085 [planetinfo.record]: techlevel = 9; 14:39:06.085 [planetinfo.record]: population = 46; 14:39:06.085 [planetinfo.record]: productivity = 32384; 14:39:06.085 [planetinfo.record]: name = "Xeceon"; 14:39:06.085 [planetinfo.record]: inhabitant = "Human Colonial"; 14:39:06.085 [planetinfo.record]: inhabitants = "Human Colonials"; 14:39:06.085 [planetinfo.record]: description = "The world Xeceon is fabled for its fabulous vicious It gargle blasters and its exotic fish cutlet."; 14:39:06.092 [planetinfo.record]: air_color = 0.743673, 0.604862, 0.875426, 1; 14:39:06.092 [planetinfo.record]: cloud_alpha = 1.000000; 14:39:06.092 [planetinfo.record]: cloud_color = 0.628906, 0.422546, 0.514441, 1; 14:39:06.092 [planetinfo.record]: cloud_fraction = 0.460000; 14:39:06.092 [planetinfo.record]: land_color = 0.542776, 0.66, 0.505313, 1; 14:39:06.092 [planetinfo.record]: land_fraction = 0.500000; 14:39:06.092 [planetinfo.record]: polar_cloud_color = 0.783008, 0.62243, 0.693937, 1; 14:39:06.092 [planetinfo.record]: polar_land_color = 0.892528, 0.934, 0.879273, 1; 14:39:06.093 [planetinfo.record]: polar_sea_color = 0.918136, 0.876418, 0.94668, 1; 14:39:06.093 [planetinfo.record]: sea_color = 0.468896, 0.374908, 0.533203, 1; 14:39:06.093 [planetinfo.record]: rotation_speed = 0.002411 14:39:06.093 [planetinfo.record]: planet zpos = 496160.000000 14:39:06.093 [planetinfo.record]: sun_radius = 102637.346191 14:39:06.093 [planetinfo.record]: sun_vector = -0.670 0.727 -0.147 14:39:06.093 [planetinfo.record]: sun_distance = 602480 14:39:06.093 [planetinfo.record]: corona_flare = 0.007835 14:39:06.093 [planetinfo.record]: corona_hues = 0.535477 14:39:06.093 [planetinfo.record]: sun_color = 0.760214, 0.768856, 0.779126, 1 14:39:06.093 [planetinfo.record]: corona_shimmer = 0.385937 14:39:06.093 [planetinfo.record]: station_vector = -0.848 -0.525 0.070 14:39:06.093 [planetinfo.record]: station = coriolis 14:39:10.919 [PLANETINFO OVER]: Done 14:39:10.920 [PLANETINFO LOGGING]: 7 50 14:39:11.924 [planetinfo.record]: seed = 152 38 5 171 14:39:11.924 [planetinfo.record]: coordinates = 38 127 14:39:11.924 [planetinfo.record]: government = 3; 14:39:11.924 [planetinfo.record]: economy = 7; 14:39:11.924 [planetinfo.record]: techlevel = 4; 14:39:11.924 [planetinfo.record]: population = 27; 14:39:11.924 [planetinfo.record]: productivity = 4536; 14:39:11.924 [planetinfo.record]: name = "Maatxeri"; 14:39:11.924 [planetinfo.record]: inhabitant = "Human Colonial"; 14:39:11.924 [planetinfo.record]: inhabitants = "Human Colonials"; 14:39:11.924 [planetinfo.record]: description = "This world is a tedious little planet."; 14:39:11.945 [planetinfo.record]: air_color = 0.41759, 0.766129, 0.876727, 1; 14:39:11.945 [planetinfo.record]: cloud_alpha = 1.000000; 14:39:11.945 [planetinfo.record]: cloud_color = 0.180798, 0.632812, 0.0173035, 1; 14:39:11.945 [planetinfo.record]: cloud_fraction = 0.410000; 14:39:11.945 [planetinfo.record]: land_color = 0.144375, 0.249111, 0.66, 1; 14:39:11.945 [planetinfo.record]: land_fraction = 0.410000; 14:39:11.945 [planetinfo.record]: polar_cloud_color = 0.43442, 0.784766, 0.307699, 1; 14:39:11.945 [planetinfo.record]: polar_land_color = 0.751578, 0.788633, 0.934, 1; 14:39:11.945 [planetinfo.record]: polar_sea_color = 0.93418, 0.931927, 0.876523, 1; 14:39:11.945 [planetinfo.record]: sea_color = 0.658203, 0.651856, 0.495709, 1; 14:39:11.946 [planetinfo.record]: rotation_speed = 0.001607 14:39:11.946 [planetinfo.record]: planet zpos = 510300.000000 14:39:11.946 [planetinfo.record]: sun_radius = 174141.224670 14:39:11.946 [planetinfo.record]: sun_vector = -0.067 -0.709 0.702 14:39:11.946 [planetinfo.record]: sun_distance = 1190700 14:39:11.946 [planetinfo.record]: corona_flare = 0.002518 14:39:11.946 [planetinfo.record]: corona_hues = 0.798630 14:39:11.946 [planetinfo.record]: sun_color = 0.784042, 0.636966, 0.501745, 1 14:39:11.946 [planetinfo.record]: corona_shimmer = 0.299265 14:39:11.946 [planetinfo.record]: station_vector = -0.899 -0.370 0.232 14:39:11.946 [planetinfo.record]: station = coriolis 14:39:16.317 [PLANETINFO OVER]: Done 14:39:16.317 [PLANETINFO LOGGING]: 7 51 14:39:17.327 [planetinfo.record]: seed = 174 30 87 146 14:39:17.327 [planetinfo.record]: coordinates = 30 34 14:39:17.327 [planetinfo.record]: government = 5; 14:39:17.327 [planetinfo.record]: economy = 2; 14:39:17.327 [planetinfo.record]: techlevel = 10; 14:39:17.328 [planetinfo.record]: population = 48; 14:39:17.328 [planetinfo.record]: productivity = 27648; 14:39:17.328 [planetinfo.record]: name = "Enbeza"; 14:39:17.328 [planetinfo.record]: inhabitant = "Human Colonial"; 14:39:17.328 [planetinfo.record]: inhabitants = "Human Colonials"; 14:39:17.328 [planetinfo.record]: description = "This planet is a dull world."; 14:39:17.369 [planetinfo.record]: air_color = 0.777499, 0.505258, 0.96583, 1; 14:39:17.369 [planetinfo.record]: cloud_alpha = 1.000000; 14:39:17.369 [planetinfo.record]: cloud_color = 0.900391, 0.151237, 0.309262, 1; 14:39:17.369 [planetinfo.record]: cloud_fraction = 0.220000; 14:39:17.370 [planetinfo.record]: land_color = 0.482089, 0.66, 0.471797, 1; 14:39:17.370 [planetinfo.record]: land_fraction = 0.560000; 14:39:17.370 [planetinfo.record]: polar_cloud_color = 0.905176, 0.434467, 0.533757, 1; 14:39:17.370 [planetinfo.record]: polar_land_color = 0.871057, 0.934, 0.867416, 1; 14:39:17.370 [planetinfo.record]: polar_sea_color = 0.929688, 0.869653, 0.846161, 1; 14:39:17.370 [planetinfo.record]: sea_color = 0.703125, 0.521507, 0.450439, 1; 14:39:17.370 [planetinfo.record]: rotation_speed = 0.002063 14:39:17.370 [planetinfo.record]: planet zpos = 402960.000000 14:39:17.370 [planetinfo.record]: sun_radius = 73740.114136 14:39:17.370 [planetinfo.record]: sun_vector = 0.911 0.359 -0.201 14:39:17.370 [planetinfo.record]: sun_distance = 570860 14:39:17.370 [planetinfo.record]: corona_flare = 0.016002 14:39:17.370 [planetinfo.record]: corona_hues = 0.886887 14:39:17.370 [planetinfo.record]: sun_color = 0.789926, 0.476179, 0.2813, 1 14:39:17.370 [planetinfo.record]: corona_shimmer = 0.458672 14:39:17.370 [planetinfo.record]: station_vector = 0.806 -0.584 0.095 14:39:17.370 [planetinfo.record]: station = coriolis 14:39:22.015 [PLANETINFO OVER]: Done 14:39:22.019 [PLANETINFO LOGGING]: 7 52 14:39:23.019 [planetinfo.record]: seed = 172 234 1 67 14:39:23.020 [planetinfo.record]: coordinates = 234 132 14:39:23.020 [planetinfo.record]: government = 5; 14:39:23.020 [planetinfo.record]: economy = 4; 14:39:23.020 [planetinfo.record]: techlevel = 8; 14:39:23.020 [planetinfo.record]: population = 42; 14:39:23.020 [planetinfo.record]: productivity = 18144; 14:39:23.020 [planetinfo.record]: name = "Geen"; 14:39:23.020 [planetinfo.record]: inhabitant = "Human Colonial"; 14:39:23.020 [planetinfo.record]: inhabitants = "Human Colonials"; 14:39:23.021 [planetinfo.record]: description = "Geen is mildly notable for Geenian lethal brandy."; 14:39:23.023 [planetinfo.record]: air_color = 0.730919, 0.794998, 0.9164, 1; 14:39:23.024 [planetinfo.record]: cloud_alpha = 1.000000; 14:39:23.024 [planetinfo.record]: cloud_color = 0.746078, 0.751953, 0.751815, 1; 14:39:23.024 [planetinfo.record]: cloud_fraction = 0.420000; 14:39:23.024 [planetinfo.record]: land_color = 0.490286, 0.570312, 0.031189, 1; 14:39:23.024 [planetinfo.record]: land_fraction = 0.690000; 14:39:23.024 [planetinfo.record]: polar_cloud_color = 0.834285, 0.838379, 0.838283, 1; 14:39:23.025 [planetinfo.record]: polar_land_color = 0.909889, 0.942969, 0.720119, 1; 14:39:23.025 [planetinfo.record]: polar_sea_color = 0.93418, 0.90855, 0.881267, 1; 14:39:23.025 [planetinfo.record]: sea_color = 0.658203, 0.585971, 0.509079, 1; 14:39:23.025 [planetinfo.record]: rotation_speed = 0.001902 14:39:23.025 [planetinfo.record]: planet zpos = 381800.000000 14:39:23.025 [planetinfo.record]: sun_radius = 60009.876404 14:39:23.025 [planetinfo.record]: sun_vector = -0.542 0.590 0.599 14:39:23.025 [planetinfo.record]: sun_distance = 839960 14:39:23.025 [planetinfo.record]: corona_flare = 0.071344 14:39:23.025 [planetinfo.record]: corona_hues = 0.885292 14:39:23.026 [planetinfo.record]: sun_color = 0.664401, 0.357922, 0.210185, 1 14:39:23.026 [planetinfo.record]: corona_shimmer = 0.307430 14:39:23.026 [planetinfo.record]: station_vector = 0.498 0.830 0.249 14:39:23.026 [planetinfo.record]: station = coriolis 14:39:27.789 [PLANETINFO OVER]: Done 14:39:27.790 [PLANETINFO LOGGING]: 7 53 14:39:28.792 [planetinfo.record]: seed = 98 213 195 103 14:39:28.792 [planetinfo.record]: coordinates = 213 224 14:39:28.792 [planetinfo.record]: government = 4; 14:39:28.792 [planetinfo.record]: economy = 0; 14:39:28.793 [planetinfo.record]: techlevel = 10; 14:39:28.793 [planetinfo.record]: population = 45; 14:39:28.793 [planetinfo.record]: productivity = 28800; 14:39:28.793 [planetinfo.record]: name = "Soisqu"; 14:39:28.793 [planetinfo.record]: inhabitant = "Fierce Blue Furry Rodent"; 14:39:28.793 [planetinfo.record]: inhabitants = "Fierce Blue Furry Rodents"; 14:39:28.793 [planetinfo.record]: description = "The planet Soisqu is mildly notable for Soisquian vicious brew."; 14:39:28.812 [planetinfo.record]: air_color = 0.525913, 0.590342, 0.852662, 1; 14:39:28.812 [planetinfo.record]: cloud_alpha = 1.000000; 14:39:28.812 [planetinfo.record]: cloud_color = 0.251808, 0.430298, 0.560547, 1; 14:39:28.812 [planetinfo.record]: cloud_fraction = 0.320000; 14:39:28.812 [planetinfo.record]: land_color = 0.482109, 0.489058, 0.66, 1; 14:39:28.812 [planetinfo.record]: land_fraction = 0.370000; 14:39:28.812 [planetinfo.record]: polar_cloud_color = 0.493294, 0.643001, 0.752246, 1; 14:39:28.812 [planetinfo.record]: polar_land_color = 0.871064, 0.873523, 0.934, 1; 14:39:28.812 [planetinfo.record]: polar_sea_color = 0.931641, 0.882739, 0.858856, 1; 14:39:28.812 [planetinfo.record]: sea_color = 0.683594, 0.540066, 0.469971, 1; 14:39:28.812 [planetinfo.record]: rotation_speed = 0.001759 14:39:28.812 [planetinfo.record]: planet zpos = 482100.000000 14:39:28.812 [planetinfo.record]: sun_radius = 146253.527069 14:39:28.812 [planetinfo.record]: sun_vector = -0.486 0.479 0.730 14:39:28.812 [planetinfo.record]: sun_distance = 964200 14:39:28.812 [planetinfo.record]: corona_flare = 0.012360 14:39:28.812 [planetinfo.record]: corona_hues = 0.874176 14:39:28.812 [planetinfo.record]: sun_color = 0.565166, 0.63334, 0.701736, 1 14:39:28.812 [planetinfo.record]: corona_shimmer = 0.433227 14:39:28.812 [planetinfo.record]: station_vector = -0.451 0.831 0.325 14:39:28.812 [planetinfo.record]: station = coriolis 14:39:33.252 [PLANETINFO OVER]: Done 14:39:33.253 [PLANETINFO LOGGING]: 7 54 14:39:34.264 [planetinfo.record]: seed = 64 223 189 86 14:39:34.264 [planetinfo.record]: coordinates = 223 90 14:39:34.264 [planetinfo.record]: government = 0; 14:39:34.264 [planetinfo.record]: economy = 2; 14:39:34.264 [planetinfo.record]: techlevel = 8; 14:39:34.264 [planetinfo.record]: population = 35; 14:39:34.264 [planetinfo.record]: productivity = 8960; 14:39:34.264 [planetinfo.record]: name = "Veerbidi"; 14:39:34.264 [planetinfo.record]: inhabitant = "Yellow Furry Insect"; 14:39:34.264 [planetinfo.record]: inhabitants = "Yellow Furry Insects"; 14:39:34.264 [planetinfo.record]: description = "The planet Veerbidi is mildly notable for Veerbidiian vicious water."; 14:39:34.281 [planetinfo.record]: air_color = 0.710169, 0.82869, 0.890385, 1; 14:39:34.281 [planetinfo.record]: cloud_alpha = 1.000000; 14:39:34.281 [planetinfo.record]: cloud_color = 0.668564, 0.673828, 0.668769, 1; 14:39:34.281 [planetinfo.record]: cloud_fraction = 0.460000; 14:39:34.281 [planetinfo.record]: land_color = 0.296605, 0.180469, 0.66, 1; 14:39:34.281 [planetinfo.record]: land_fraction = 0.300000; 14:39:34.291 [planetinfo.record]: polar_cloud_color = 0.799301, 0.803223, 0.799454, 1; 14:39:34.292 [planetinfo.record]: polar_land_color = 0.805435, 0.764348, 0.934, 1; 14:39:34.292 [planetinfo.record]: polar_sea_color = 0.932422, 0.913015, 0.875967, 1; 14:39:34.292 [planetinfo.record]: sea_color = 0.675781, 0.619521, 0.512115, 1; 14:39:34.292 [planetinfo.record]: rotation_speed = 0.004722 14:39:34.292 [planetinfo.record]: planet zpos = 549000.000000 14:39:34.292 [planetinfo.record]: sun_radius = 119415.206909 14:39:34.292 [planetinfo.record]: sun_vector = -0.300 -0.847 0.439 14:39:34.292 [planetinfo.record]: sun_distance = 869250 14:39:34.292 [planetinfo.record]: corona_flare = 0.069437 14:39:34.292 [planetinfo.record]: corona_hues = 0.750732 14:39:34.292 [planetinfo.record]: sun_color = 0.261373, 0.684017, 0.808569, 1 14:39:34.292 [planetinfo.record]: corona_shimmer = 0.377390 14:39:34.292 [planetinfo.record]: station_vector = 0.285 0.891 0.354 14:39:34.292 [planetinfo.record]: station = coriolis 14:39:39.266 [PLANETINFO OVER]: Done 14:39:39.267 [PLANETINFO LOGGING]: 7 55 14:39:40.272 [planetinfo.record]: seed = 86 173 239 3 14:39:40.272 [planetinfo.record]: coordinates = 173 198 14:39:40.272 [planetinfo.record]: government = 2; 14:39:40.272 [planetinfo.record]: economy = 6; 14:39:40.272 [planetinfo.record]: techlevel = 3; 14:39:40.272 [planetinfo.record]: population = 21; 14:39:40.272 [planetinfo.record]: productivity = 4032; 14:39:40.272 [planetinfo.record]: name = "Getiusza"; 14:39:40.272 [planetinfo.record]: inhabitant = "Large Green Bony Feline"; 14:39:40.272 [planetinfo.record]: inhabitants = "Large Green Bony Felines"; 14:39:40.272 [planetinfo.record]: description = "This planet is very well known for its inhabitants’ unusual silliness and killer Re gargle blasters."; 14:39:40.296 [planetinfo.record]: air_color = 0.452218, 0.642915, 0.886482, 1; 14:39:40.296 [planetinfo.record]: cloud_alpha = 1.000000; 14:39:40.296 [planetinfo.record]: cloud_color = 0.08535, 0.662109, 0.486378, 1; 14:39:40.296 [planetinfo.record]: cloud_fraction = 0.290000; 14:39:40.296 [planetinfo.record]: land_color = 0.66, 0.0567188, 0.212252, 1; 14:39:40.296 [planetinfo.record]: land_fraction = 0.570000; 14:39:40.296 [planetinfo.record]: polar_cloud_color = 0.363519, 0.797949, 0.665584, 1; 14:39:40.296 [planetinfo.record]: polar_land_color = 0.934, 0.720566, 0.775592, 1; 14:39:40.296 [planetinfo.record]: polar_sea_color = 0.948828, 0.901413, 0.900275, 1; 14:39:40.296 [planetinfo.record]: sea_color = 0.511719, 0.409431, 0.406976, 1; 14:39:40.296 [planetinfo.record]: rotation_speed = 0.001395 14:39:40.296 [planetinfo.record]: planet zpos = 488410.000000 14:39:40.296 [planetinfo.record]: sun_radius = 67765.993195 14:39:40.296 [planetinfo.record]: sun_vector = -0.079 -0.461 0.884 14:39:40.296 [planetinfo.record]: sun_distance = 601120 14:39:40.296 [planetinfo.record]: corona_flare = 0.015118 14:39:40.296 [planetinfo.record]: corona_hues = 0.872368 14:39:40.296 [planetinfo.record]: sun_color = 0.616873, 0.683444, 0.68591, 1 14:39:40.296 [planetinfo.record]: corona_shimmer = 0.378891 14:39:40.297 [planetinfo.record]: station_vector = -0.125 -0.979 0.160 14:39:40.297 [planetinfo.record]: station = coriolis 14:39:45.367 [PLANETINFO OVER]: Done 14:39:45.368 [PLANETINFO LOGGING]: 7 56 14:39:46.373 [planetinfo.record]: seed = 84 164 185 68 14:39:46.373 [planetinfo.record]: coordinates = 164 40 14:39:46.373 [planetinfo.record]: government = 2; 14:39:46.373 [planetinfo.record]: economy = 0; 14:39:46.373 [planetinfo.record]: techlevel = 8; 14:39:46.373 [planetinfo.record]: population = 35; 14:39:46.373 [planetinfo.record]: productivity = 16800; 14:39:46.373 [planetinfo.record]: name = "Zaatquat"; 14:39:46.373 [planetinfo.record]: inhabitant = "Fierce Yellow Fat Bird"; 14:39:46.373 [planetinfo.record]: inhabitants = "Fierce Yellow Fat Birds"; 14:39:46.373 [planetinfo.record]: description = "This world is ravaged by unpredictable civil war."; 14:39:46.383 [planetinfo.record]: air_color = 0.568639, 0.897517, 0.993146, 1; 14:39:46.383 [planetinfo.record]: cloud_alpha = 1.000000; 14:39:46.383 [planetinfo.record]: cloud_color = 0.520712, 0.982422, 0.310844, 1; 14:39:46.383 [planetinfo.record]: cloud_fraction = 0.410000; 14:39:46.383 [planetinfo.record]: land_color = 0.62747, 0.636719, 0.594437, 1; 14:39:46.383 [planetinfo.record]: land_fraction = 0.650000; 14:39:46.383 [planetinfo.record]: polar_cloud_color = 0.665368, 0.94209, 0.539586, 1; 14:39:46.383 [planetinfo.record]: polar_land_color = 0.932928, 0.936328, 0.920784, 1; 14:39:46.383 [planetinfo.record]: polar_sea_color = 0.761574, 0.901563, 0.770323, 1; 14:39:46.383 [planetinfo.record]: sea_color = 0.372986, 0.984375, 0.411198, 1; 14:39:46.384 [planetinfo.record]: rotation_speed = 0.000017 14:39:46.384 [planetinfo.record]: planet zpos = 400400.000000 14:39:46.384 [planetinfo.record]: sun_radius = 88372.586060 14:39:46.384 [planetinfo.record]: sun_vector = 0.255 -0.874 -0.414 14:39:46.384 [planetinfo.record]: sun_distance = 800800 14:39:46.384 [planetinfo.record]: corona_flare = 0.054301 14:39:46.384 [planetinfo.record]: corona_hues = 0.652596 14:39:46.384 [planetinfo.record]: sun_color = 0.738001, 0.707044, 0.684015, 1 14:39:46.384 [planetinfo.record]: corona_shimmer = 0.255471 14:39:46.385 [planetinfo.record]: station_vector = 0.440 -0.482 0.758 14:39:46.385 [planetinfo.record]: station = coriolis 14:39:51.342 [PLANETINFO OVER]: Done 14:39:51.343 [PLANETINFO LOGGING]: 7 57 14:39:52.347 [planetinfo.record]: seed = 138 81 91 94 14:39:52.347 [planetinfo.record]: coordinates = 81 250 14:39:52.347 [planetinfo.record]: government = 1; 14:39:52.347 [planetinfo.record]: economy = 2; 14:39:52.347 [planetinfo.record]: techlevel = 7; 14:39:52.347 [planetinfo.record]: population = 32; 14:39:52.347 [planetinfo.record]: productivity = 10240; 14:39:52.347 [planetinfo.record]: name = "Riarquge"; 14:39:52.347 [planetinfo.record]: inhabitant = "Human Colonial"; 14:39:52.347 [planetinfo.record]: inhabitants = "Human Colonials"; 14:39:52.347 [planetinfo.record]: description = "This planet is reasonably famous for the Riarqugeian evil talking treeoid."; 14:39:52.361 [planetinfo.record]: air_color = 0.543772, 0.960627, 0.872341, 1; 14:39:52.361 [planetinfo.record]: cloud_alpha = 1.000000; 14:39:52.361 [planetinfo.record]: cloud_color = 0.884766, 0.495953, 0.262665, 1; 14:39:52.361 [planetinfo.record]: cloud_fraction = 0.230000; 14:39:52.361 [planetinfo.record]: land_color = 0.66, 0.549221, 0.337734, 1; 14:39:52.361 [planetinfo.record]: land_fraction = 0.550000; 14:39:52.361 [planetinfo.record]: polar_cloud_color = 0.898145, 0.651462, 0.503452, 1; 14:39:52.361 [planetinfo.record]: polar_land_color = 0.934, 0.894808, 0.819986, 1; 14:39:52.361 [planetinfo.record]: polar_sea_color = 0.858199, 0.874519, 0.949023, 1; 14:39:52.361 [planetinfo.record]: sea_color = 0.314621, 0.349686, 0.509766, 1; 14:39:52.361 [planetinfo.record]: rotation_speed = 0.001109 14:39:52.361 [planetinfo.record]: planet zpos = 777720.000000 14:39:52.361 [planetinfo.record]: sun_radius = 143729.940796 14:39:52.361 [planetinfo.record]: sun_vector = -0.359 -0.882 0.305 14:39:52.361 [planetinfo.record]: sun_distance = 1361010 14:39:52.361 [planetinfo.record]: corona_flare = 0.033035 14:39:52.362 [planetinfo.record]: corona_hues = 0.903091 14:39:52.362 [planetinfo.record]: sun_color = 0.327487, 0.355807, 0.816574, 1 14:39:52.362 [planetinfo.record]: corona_shimmer = 0.400920 14:39:52.362 [planetinfo.record]: station_vector = -0.684 0.215 0.697 14:39:52.362 [planetinfo.record]: station = coriolis 14:39:57.210 [PLANETINFO OVER]: Done 14:39:57.211 [PLANETINFO LOGGING]: 7 58 14:39:58.214 [planetinfo.record]: seed = 232 99 117 105 14:39:58.214 [planetinfo.record]: coordinates = 99 90 14:39:58.214 [planetinfo.record]: government = 5; 14:39:58.214 [planetinfo.record]: economy = 2; 14:39:58.214 [planetinfo.record]: techlevel = 11; 14:39:58.214 [planetinfo.record]: population = 52; 14:39:58.214 [planetinfo.record]: productivity = 29952; 14:39:58.214 [planetinfo.record]: name = "Esusla"; 14:39:58.214 [planetinfo.record]: inhabitant = "Human Colonial"; 14:39:58.214 [planetinfo.record]: inhabitants = "Human Colonials"; 14:39:58.215 [planetinfo.record]: description = "This world is fabled for its unusual oceans."; 14:39:58.228 [planetinfo.record]: air_color = 0.650013, 0.834183, 0.951522, 1; 14:39:58.228 [planetinfo.record]: cloud_alpha = 1.000000; 14:39:58.228 [planetinfo.record]: cloud_color = 0.555984, 0.857422, 0.610149, 1; 14:39:58.228 [planetinfo.record]: cloud_fraction = 0.220000; 14:39:58.228 [planetinfo.record]: land_color = 0.487266, 0.494013, 0.66, 1; 14:39:58.228 [planetinfo.record]: land_fraction = 0.370000; 14:39:58.228 [planetinfo.record]: polar_cloud_color = 0.691197, 0.88584, 0.726172, 1; 14:39:58.228 [planetinfo.record]: polar_land_color = 0.872889, 0.875276, 0.934, 1; 14:39:58.228 [planetinfo.record]: polar_sea_color = 0.925987, 0.93418, 0.878986, 1; 14:39:58.228 [planetinfo.record]: sea_color = 0.635113, 0.658203, 0.502651, 1; 14:39:58.228 [planetinfo.record]: rotation_speed = 0.002836 14:39:58.228 [planetinfo.record]: planet zpos = 730660.000000 14:39:58.228 [planetinfo.record]: sun_radius = 118402.239990 14:39:58.228 [planetinfo.record]: sun_vector = 0.220 -0.255 -0.942 14:39:58.228 [planetinfo.record]: sun_distance = 1043800 14:39:58.228 [planetinfo.record]: corona_flare = 0.061797 14:39:58.228 [planetinfo.record]: corona_hues = 0.926529 14:39:58.228 [planetinfo.record]: sun_color = 0.688235, 0.629198, 0.452201, 1 14:39:58.229 [planetinfo.record]: corona_shimmer = 0.291860 14:39:58.229 [planetinfo.record]: station_vector = 0.105 0.966 0.237 14:39:58.229 [planetinfo.record]: station = dodecahedron 14:40:03.169 [PLANETINFO OVER]: Done 14:40:03.170 [PLANETINFO LOGGING]: 7 59 14:40:04.192 [planetinfo.record]: seed = 254 134 135 164 14:40:04.192 [planetinfo.record]: coordinates = 134 245 14:40:04.192 [planetinfo.record]: government = 7; 14:40:04.192 [planetinfo.record]: economy = 5; 14:40:04.192 [planetinfo.record]: techlevel = 8; 14:40:04.192 [planetinfo.record]: population = 45; 14:40:04.192 [planetinfo.record]: productivity = 19800; 14:40:04.192 [planetinfo.record]: name = "Zainat"; 14:40:04.193 [planetinfo.record]: inhabitant = "Fierce Harmless Bony Lobster"; 14:40:04.193 [planetinfo.record]: inhabitants = "Fierce Harmless Bony Lobsters"; 14:40:04.193 [planetinfo.record]: description = "Zainat is well known for its inhabitants’ unusual silliness but cursed by deadly civil war."; 14:40:04.204 [planetinfo.record]: air_color = 0.59228, 0.84694, 0.993797, 1; 14:40:04.204 [planetinfo.record]: cloud_alpha = 1.000000; 14:40:04.204 [planetinfo.record]: cloud_color = 0.380676, 0.984375, 0.446706, 1; 14:40:04.204 [planetinfo.record]: cloud_fraction = 0.250000; 14:40:04.204 [planetinfo.record]: land_color = 0.623906, 0.625316, 0.66, 1; 14:40:04.204 [planetinfo.record]: land_fraction = 0.670000; 14:40:04.204 [planetinfo.record]: polar_cloud_color = 0.581528, 0.942969, 0.621061, 1; 14:40:04.205 [planetinfo.record]: polar_land_color = 0.92123, 0.921729, 0.934, 1; 14:40:04.205 [planetinfo.record]: polar_sea_color = 0.91875, 0.855875, 0.794934, 1; 14:40:04.205 [planetinfo.record]: sea_color = 0.8125, 0.590084, 0.374512, 1; 14:40:04.205 [planetinfo.record]: rotation_speed = 0.000837 14:40:04.205 [planetinfo.record]: planet zpos = 556360.000000 14:40:04.205 [planetinfo.record]: sun_radius = 91604.047241 14:40:04.205 [planetinfo.record]: sun_vector = 0.108 0.888 -0.447 14:40:04.205 [planetinfo.record]: sun_distance = 755060 14:40:04.205 [planetinfo.record]: corona_flare = 0.064153 14:40:04.205 [planetinfo.record]: corona_hues = 0.642433 14:40:04.205 [planetinfo.record]: sun_color = 0.772879, 0.812232, 0.848364, 1 14:40:04.206 [planetinfo.record]: corona_shimmer = 0.367828 14:40:04.206 [planetinfo.record]: station_vector = 0.923 -0.014 0.386 14:40:04.206 [planetinfo.record]: station = coriolis 14:40:09.245 [PLANETINFO OVER]: Done 14:40:09.246 [PLANETINFO LOGGING]: 7 60 14:40:10.250 [planetinfo.record]: seed = 252 17 113 127 14:40:10.250 [planetinfo.record]: coordinates = 17 76 14:40:10.250 [planetinfo.record]: government = 7; 14:40:10.250 [planetinfo.record]: economy = 4; 14:40:10.250 [planetinfo.record]: techlevel = 8; 14:40:10.250 [planetinfo.record]: population = 44; 14:40:10.250 [planetinfo.record]: productivity = 23232; 14:40:10.250 [planetinfo.record]: name = "Onisain"; 14:40:10.251 [planetinfo.record]: inhabitant = "Human Colonial"; 14:40:10.251 [planetinfo.record]: inhabitants = "Human Colonials"; 14:40:10.251 [planetinfo.record]: description = "The world Onisain is mildly noted for its pink oceans but scourged by dreadful solar activity."; 14:40:10.258 [planetinfo.record]: air_color = 0.48226, 0.496515, 0.910547, 1; 14:40:10.258 [planetinfo.record]: cloud_alpha = 1.000000; 14:40:10.258 [planetinfo.record]: cloud_color = 0.137695, 0.188972, 0.734375, 1; 14:40:10.258 [planetinfo.record]: cloud_fraction = 0.220000; 14:40:10.258 [planetinfo.record]: land_color = 0.633816, 0.626484, 0.66, 1; 14:40:10.258 [planetinfo.record]: land_fraction = 0.250000; 14:40:10.258 [planetinfo.record]: polar_cloud_color = 0.408746, 0.444988, 0.830469, 1; 14:40:10.258 [planetinfo.record]: polar_land_color = 0.924736, 0.922143, 0.934, 1; 14:40:10.258 [planetinfo.record]: polar_sea_color = 0.91, 0.8645, 0.8918, 1; 14:40:10.259 [planetinfo.record]: sea_color = 0.9, 0.72, 0.828, 1; 14:40:10.259 [planetinfo.record]: rotation_speed = 0.003146 14:40:10.259 [planetinfo.record]: planet zpos = 800760.000000 14:40:10.259 [planetinfo.record]: sun_radius = 123499.781799 14:40:10.259 [planetinfo.record]: sun_vector = -0.706 -0.671 0.225 14:40:10.259 [planetinfo.record]: sun_distance = 1334600 14:40:10.259 [planetinfo.record]: corona_flare = 0.010869 14:40:10.259 [planetinfo.record]: corona_hues = 0.522148 14:40:10.259 [planetinfo.record]: sun_color = 0.378023, 0.62914, 0.769705, 1 14:40:10.259 [planetinfo.record]: corona_shimmer = 0.507111 14:40:10.259 [planetinfo.record]: station_vector = -0.570 0.817 0.086 14:40:10.259 [planetinfo.record]: station = coriolis 14:40:15.613 [PLANETINFO OVER]: Done 14:40:15.613 [PLANETINFO LOGGING]: 7 61 14:40:16.619 [planetinfo.record]: seed = 178 204 243 25 14:40:16.619 [planetinfo.record]: coordinates = 204 111 14:40:16.619 [planetinfo.record]: government = 6; 14:40:16.619 [planetinfo.record]: economy = 7; 14:40:16.619 [planetinfo.record]: techlevel = 3; 14:40:16.619 [planetinfo.record]: population = 26; 14:40:16.619 [planetinfo.record]: productivity = 6240; 14:40:16.619 [planetinfo.record]: name = "Orlatein"; 14:40:16.619 [planetinfo.record]: inhabitant = "Green Bony Bird"; 14:40:16.619 [planetinfo.record]: inhabitants = "Green Bony Birds"; 14:40:16.619 [planetinfo.record]: description = "This world is reasonably notable for its exotic cuisine but plagued by lethal disease."; 14:40:16.641 [planetinfo.record]: air_color = 0.642803, 0.834953, 0.957375, 1; 14:40:16.642 [planetinfo.record]: cloud_alpha = 1.000000; 14:40:16.642 [planetinfo.record]: cloud_color = 0.536621, 0.875, 0.597424, 1; 14:40:16.642 [planetinfo.record]: cloud_fraction = 0.320000; 14:40:16.642 [planetinfo.record]: land_color = 0.299072, 0.337791, 0.546875, 1; 14:40:16.642 [planetinfo.record]: land_fraction = 0.620000; 14:40:16.642 [planetinfo.record]: polar_cloud_color = 0.677731, 0.89375, 0.716547, 1; 14:40:16.642 [planetinfo.record]: polar_land_color = 0.838226, 0.854959, 0.945312, 1; 14:40:16.642 [planetinfo.record]: polar_sea_color = 0.927145, 0.936133, 0.883841, 1; 14:40:16.642 [planetinfo.record]: sea_color = 0.614145, 0.638672, 0.495969, 1; 14:40:16.642 [planetinfo.record]: rotation_speed = 0.000472 14:40:16.642 [planetinfo.record]: planet zpos = 745360.000000 14:40:16.642 [planetinfo.record]: sun_radius = 121593.479004 14:40:16.642 [planetinfo.record]: sun_vector = -0.354 0.807 -0.473 14:40:16.642 [planetinfo.record]: sun_distance = 905080 14:40:16.643 [planetinfo.record]: corona_flare = 0.014500 14:40:16.643 [planetinfo.record]: corona_hues = 0.546906 14:40:16.643 [planetinfo.record]: sun_color = 0.737479, 0.485939, 0.415029, 1 14:40:16.643 [planetinfo.record]: corona_shimmer = 0.481606 14:40:16.643 [planetinfo.record]: station_vector = -0.484 -0.855 0.188 14:40:16.643 [planetinfo.record]: station = coriolis 14:40:22.670 [PLANETINFO OVER]: Done 14:40:22.671 [PLANETINFO LOGGING]: 7 62 14:40:23.677 [planetinfo.record]: seed = 144 172 45 63 14:40:23.677 [planetinfo.record]: coordinates = 172 60 14:40:23.677 [planetinfo.record]: government = 2; 14:40:23.677 [planetinfo.record]: economy = 4; 14:40:23.677 [planetinfo.record]: techlevel = 4; 14:40:23.678 [planetinfo.record]: population = 23; 14:40:23.678 [planetinfo.record]: productivity = 6624; 14:40:23.678 [planetinfo.record]: name = "Onusra"; 14:40:23.678 [planetinfo.record]: inhabitant = "Human Colonial"; 14:40:23.678 [planetinfo.record]: inhabitants = "Human Colonials"; 14:40:23.678 [planetinfo.record]: description = "This world is ravaged by dreadful civil war."; 14:40:23.699 [planetinfo.record]: air_color = 0.720238, 0.790648, 0.928758, 1; 14:40:23.699 [planetinfo.record]: cloud_alpha = 1.000000; 14:40:23.699 [planetinfo.record]: cloud_color = 0.730499, 0.789062, 0.789062, 1; 14:40:23.699 [planetinfo.record]: cloud_fraction = 0.480000; 14:40:23.699 [planetinfo.record]: land_color = 0.66, 0.342891, 0.446942, 1; 14:40:23.699 [planetinfo.record]: land_fraction = 0.670000; 14:40:23.699 [planetinfo.record]: polar_cloud_color = 0.815414, 0.855078, 0.855078, 1; 14:40:23.699 [planetinfo.record]: polar_land_color = 0.934, 0.821811, 0.858623, 1; 14:40:23.699 [planetinfo.record]: polar_sea_color = 0.93457, 0.896695, 0.87616, 1; 14:40:23.699 [planetinfo.record]: sea_color = 0.654297, 0.548229, 0.490723, 1; 14:40:23.699 [planetinfo.record]: rotation_speed = 0.000953 14:40:23.699 [planetinfo.record]: planet zpos = 682800.000000 14:40:23.699 [planetinfo.record]: sun_radius = 126161.094360 14:40:23.699 [planetinfo.record]: sun_vector = 0.553 0.102 0.827 14:40:23.699 [planetinfo.record]: sun_distance = 1502160 14:40:23.699 [planetinfo.record]: corona_flare = 0.083549 14:40:23.699 [planetinfo.record]: corona_hues = 0.925110 14:40:23.699 [planetinfo.record]: sun_color = 0.849384, 0.704, 0.638693, 1 14:40:23.700 [planetinfo.record]: corona_shimmer = 0.266947 14:40:23.700 [planetinfo.record]: station_vector = 0.519 0.124 0.846 14:40:23.700 [planetinfo.record]: station = coriolis 14:40:28.956 [PLANETINFO OVER]: Done 14:40:28.956 [PLANETINFO LOGGING]: 7 63 14:40:29.965 [planetinfo.record]: seed = 166 123 31 184 14:40:29.966 [planetinfo.record]: coordinates = 123 20 14:40:29.966 [planetinfo.record]: government = 4; 14:40:29.966 [planetinfo.record]: economy = 4; 14:40:29.966 [planetinfo.record]: techlevel = 8; 14:40:29.966 [planetinfo.record]: population = 41; 14:40:29.966 [planetinfo.record]: productivity = 15744; 14:40:29.966 [planetinfo.record]: name = "Edsoan"; 14:40:29.966 [planetinfo.record]: inhabitant = "Human Colonial"; 14:40:29.966 [planetinfo.record]: inhabitants = "Human Colonials"; 14:40:29.966 [planetinfo.record]: description = "The planet Edsoan is well known for its inhabitants’ ancient mating traditions but ravaged by unpredictable civil war."; 14:40:29.969 [planetinfo.record]: air_color = 0.669163, 0.852492, 0.918352, 1; 14:40:29.969 [planetinfo.record]: cloud_alpha = 1.000000; 14:40:29.969 [planetinfo.record]: cloud_color = 0.6268, 0.757812, 0.595001, 1; 14:40:29.969 [planetinfo.record]: cloud_fraction = 0.460000; 14:40:29.969 [planetinfo.record]: land_color = 0.515625, 0.445129, 0.491392, 1; 14:40:29.969 [planetinfo.record]: land_fraction = 0.270000; 14:40:29.969 [planetinfo.record]: polar_cloud_color = 0.750143, 0.841016, 0.728086, 1; 14:40:29.969 [planetinfo.record]: polar_land_color = 0.948438, 0.91602, 0.937294, 1; 14:40:29.970 [planetinfo.record]: polar_sea_color = 0.930775, 0.93125, 0.870501, 1; 14:40:29.970 [planetinfo.record]: sea_color = 0.686098, 0.6875, 0.508105, 1; 14:40:29.970 [planetinfo.record]: rotation_speed = 0.000219 14:40:29.970 [planetinfo.record]: planet zpos = 548570.000000 14:40:29.970 [planetinfo.record]: sun_radius = 144189.711761 14:40:29.970 [planetinfo.record]: sun_vector = 0.458 -0.234 0.857 14:40:29.970 [planetinfo.record]: sun_distance = 1147010 14:40:29.970 [planetinfo.record]: corona_flare = 0.071571 14:40:29.970 [planetinfo.record]: corona_hues = 0.805168 14:40:29.970 [planetinfo.record]: sun_color = 0.791168, 0.559367, 0.200617, 1 14:40:29.970 [planetinfo.record]: corona_shimmer = 1.167450 14:40:29.970 [planetinfo.record]: station_vector = -0.030 0.929 0.369 14:40:29.970 [planetinfo.record]: station = coriolis 14:40:34.614 [PLANETINFO OVER]: Done 14:40:34.615 [PLANETINFO LOGGING]: 7 64 14:40:35.619 [planetinfo.record]: seed = 164 123 41 63 14:40:35.619 [planetinfo.record]: coordinates = 123 123 14:40:35.619 [planetinfo.record]: government = 4; 14:40:35.619 [planetinfo.record]: economy = 3; 14:40:35.619 [planetinfo.record]: techlevel = 9; 14:40:35.619 [planetinfo.record]: population = 44; 14:40:35.619 [planetinfo.record]: productivity = 19712; 14:40:35.619 [planetinfo.record]: name = "Onveat"; 14:40:35.619 [planetinfo.record]: inhabitant = "Human Colonial"; 14:40:35.619 [planetinfo.record]: inhabitants = "Human Colonials"; 14:40:35.619 [planetinfo.record]: description = "This planet is fabled for its weird exuberant forests and its inhabitants’ eccentric shyness."; 14:40:35.648 [planetinfo.record]: air_color = 0.645798, 0.858923, 0.941766, 1; 14:40:35.648 [planetinfo.record]: cloud_alpha = 1.000000; 14:40:35.648 [planetinfo.record]: cloud_color = 0.585712, 0.828125, 0.543457, 1; 14:40:35.648 [planetinfo.record]: cloud_fraction = 0.060000; 14:40:35.648 [planetinfo.record]: land_color = 0.583984, 0.456238, 0.498155, 1; 14:40:35.648 [planetinfo.record]: land_fraction = 0.660000; 14:40:35.648 [planetinfo.record]: polar_cloud_color = 0.713001, 0.872656, 0.685171, 1; 14:40:35.648 [planetinfo.record]: polar_land_color = 0.941602, 0.890108, 0.907004, 1; 14:40:35.648 [planetinfo.record]: polar_sea_color = 0.92652, 0.938281, 0.889718, 1; 14:40:35.648 [planetinfo.record]: sea_color = 0.586241, 0.617188, 0.48941, 1; 14:40:35.648 [planetinfo.record]: rotation_speed = 0.001258 14:40:35.648 [planetinfo.record]: planet zpos = 745690.000000 14:40:35.648 [planetinfo.record]: sun_radius = 164407.507172 14:40:35.648 [planetinfo.record]: sun_vector = -0.378 -0.639 -0.670 14:40:35.648 [planetinfo.record]: sun_distance = 1355800 14:40:35.648 [planetinfo.record]: corona_flare = 0.049738 14:40:35.648 [planetinfo.record]: corona_hues = 0.787209 14:40:35.648 [planetinfo.record]: sun_color = 0.784305, 0.365256, 0.336868, 1 14:40:35.648 [planetinfo.record]: corona_shimmer = 0.391826 14:40:35.648 [planetinfo.record]: station_vector = -0.285 0.953 0.101 14:40:35.648 [planetinfo.record]: station = coriolis 14:40:40.755 [PLANETINFO OVER]: Done 14:40:40.756 [PLANETINFO LOGGING]: 7 65 14:40:41.761 [planetinfo.record]: seed = 218 102 139 142 14:40:41.761 [planetinfo.record]: coordinates = 102 241 14:40:41.761 [planetinfo.record]: government = 3; 14:40:41.761 [planetinfo.record]: economy = 1; 14:40:41.761 [planetinfo.record]: techlevel = 10; 14:40:41.762 [planetinfo.record]: population = 45; 14:40:41.762 [planetinfo.record]: productivity = 22680; 14:40:41.762 [planetinfo.record]: name = "Rebite"; 14:40:41.762 [planetinfo.record]: inhabitant = "Black Frog"; 14:40:41.762 [planetinfo.record]: inhabitants = "Black Frogs"; 14:40:41.762 [planetinfo.record]: description = "This world is mildly famous for its vast dense forests and its great volcanoes."; 14:40:41.791 [planetinfo.record]: air_color = 0.400342, 0.847459, 0.791149, 1; 14:40:41.791 [planetinfo.record]: cloud_alpha = 1.000000; 14:40:41.791 [planetinfo.record]: cloud_color = 0.544922, 0.34796, 0.0085144, 1; 14:40:41.791 [planetinfo.record]: cloud_fraction = 0.140000; 14:40:41.791 [planetinfo.record]: land_color = 0.66, 0.212776, 0.190781, 1; 14:40:41.791 [planetinfo.record]: land_fraction = 0.290000; 14:40:41.791 [planetinfo.record]: polar_cloud_color = 0.745215, 0.576866, 0.286733, 1; 14:40:41.791 [planetinfo.record]: polar_land_color = 0.934, 0.775778, 0.767996, 1; 14:40:41.791 [planetinfo.record]: polar_sea_color = 0.907429, 0.855486, 0.942969, 1; 14:40:41.791 [planetinfo.record]: sea_color = 0.484334, 0.358673, 0.570312, 1; 14:40:41.791 [planetinfo.record]: rotation_speed = 0.004885 14:40:41.791 [planetinfo.record]: planet zpos = 975300.000000 14:40:41.791 [planetinfo.record]: sun_radius = 153243.853760 14:40:41.791 [planetinfo.record]: sun_vector = -0.812 0.329 -0.483 14:40:41.791 [planetinfo.record]: sun_distance = 1365420 14:40:41.791 [planetinfo.record]: corona_flare = 0.090634 14:40:41.791 [planetinfo.record]: corona_hues = 0.978012 14:40:41.791 [planetinfo.record]: sun_color = 0.809937, 0.516357, 0.437082, 1 14:40:41.791 [planetinfo.record]: corona_shimmer = 0.693092 14:40:41.792 [planetinfo.record]: station_vector = -0.886 0.065 0.459 14:40:41.792 [planetinfo.record]: station = coriolis 14:40:46.881 [PLANETINFO OVER]: Done 14:40:46.882 [PLANETINFO LOGGING]: 7 66 14:40:47.886 [planetinfo.record]: seed = 56 81 229 19 14:40:47.886 [planetinfo.record]: coordinates = 81 220 14:40:47.886 [planetinfo.record]: government = 7; 14:40:47.886 [planetinfo.record]: economy = 4; 14:40:47.886 [planetinfo.record]: techlevel = 8; 14:40:47.886 [planetinfo.record]: population = 44; 14:40:47.886 [planetinfo.record]: productivity = 23232; 14:40:47.886 [planetinfo.record]: name = "Belebi"; 14:40:47.886 [planetinfo.record]: inhabitant = "Green Furry Rodent"; 14:40:47.886 [planetinfo.record]: inhabitants = "Green Furry Rodents"; 14:40:47.886 [planetinfo.record]: description = "The planet Belebi is very noted for its unusual sit coms but ravaged by dreadful civil war."; 14:40:47.906 [planetinfo.record]: air_color = 0.703546, 0.805366, 0.927457, 1; 14:40:47.907 [planetinfo.record]: cloud_alpha = 1.000000; 14:40:47.907 [planetinfo.record]: cloud_color = 0.687012, 0.785156, 0.750652, 1; 14:40:47.907 [planetinfo.record]: cloud_fraction = 0.500000; 14:40:47.907 [planetinfo.record]: land_color = 0.328248, 0.193359, 0.66, 1; 14:40:47.907 [planetinfo.record]: land_fraction = 0.290000; 14:40:47.907 [planetinfo.record]: polar_cloud_color = 0.786655, 0.85332, 0.829883, 1; 14:40:47.908 [planetinfo.record]: polar_land_color = 0.81663, 0.768908, 0.934, 1; 14:40:47.908 [planetinfo.record]: polar_sea_color = 0.929688, 0.907877, 0.861595, 1; 14:40:47.908 [planetinfo.record]: sea_color = 0.703125, 0.637143, 0.497131, 1; 14:40:47.908 [planetinfo.record]: rotation_speed = 0.004044 14:40:47.908 [planetinfo.record]: planet zpos = 476450.000000 14:40:47.908 [planetinfo.record]: sun_radius = 92697.052765 14:40:47.908 [planetinfo.record]: sun_vector = 0.704 -0.702 0.104 14:40:47.908 [planetinfo.record]: sun_distance = 733000 14:40:47.908 [planetinfo.record]: corona_flare = 0.063979 14:40:47.908 [planetinfo.record]: corona_hues = 0.518181 14:40:47.908 [planetinfo.record]: sun_color = 0.420806, 0.515643, 0.741666, 1 14:40:47.908 [planetinfo.record]: corona_shimmer = 0.397467 14:40:47.909 [planetinfo.record]: station_vector = 0.083 -0.996 0.042 14:40:47.909 [planetinfo.record]: station = coriolis 14:40:53.463 [PLANETINFO OVER]: Done 14:40:53.463 [PLANETINFO LOGGING]: 7 67 14:40:54.466 [planetinfo.record]: seed = 78 251 183 226 14:40:54.466 [planetinfo.record]: coordinates = 251 166 14:40:54.466 [planetinfo.record]: government = 1; 14:40:54.466 [planetinfo.record]: economy = 6; 14:40:54.466 [planetinfo.record]: techlevel = 5; 14:40:54.466 [planetinfo.record]: population = 28; 14:40:54.466 [planetinfo.record]: productivity = 4480; 14:40:54.466 [planetinfo.record]: name = "Xezaxees"; 14:40:54.466 [planetinfo.record]: inhabitant = "Large Furry Insect"; 14:40:54.466 [planetinfo.record]: inhabitants = "Large Furry Insects"; 14:40:54.466 [planetinfo.record]: description = "This world is a tedious place."; 14:40:54.469 [planetinfo.record]: air_color = 0.422543, 0.760846, 0.872824, 1; 14:40:54.469 [planetinfo.record]: cloud_alpha = 1.000000; 14:40:54.469 [planetinfo.record]: cloud_color = 0.174322, 0.621094, 0.0315399, 1; 14:40:54.469 [planetinfo.record]: cloud_fraction = 0.330000; 14:40:54.469 [planetinfo.record]: land_color = 0.541245, 0.487266, 0.66, 1; 14:40:54.469 [planetinfo.record]: land_fraction = 0.540000; 14:40:54.469 [planetinfo.record]: polar_cloud_color = 0.429047, 0.779492, 0.317049, 1; 14:40:54.469 [planetinfo.record]: polar_land_color = 0.891986, 0.872889, 0.934, 1; 14:40:54.469 [planetinfo.record]: polar_sea_color = 0.943164, 0.90177, 0.889098, 1; 14:40:54.469 [planetinfo.record]: sea_color = 0.568359, 0.468581, 0.438036, 1; 14:40:54.469 [planetinfo.record]: rotation_speed = 0.004634 14:40:54.469 [planetinfo.record]: planet zpos = 429480.000000 14:40:54.469 [planetinfo.record]: sun_radius = 84927.524872 14:40:54.470 [planetinfo.record]: sun_vector = -0.239 -0.262 -0.935 14:40:54.470 [planetinfo.record]: sun_distance = 608430 14:40:54.470 [planetinfo.record]: corona_flare = 0.085020 14:40:54.470 [planetinfo.record]: corona_hues = 0.757782 14:40:54.470 [planetinfo.record]: sun_color = 0.837347, 0.714579, 0.64211, 1 14:40:54.470 [planetinfo.record]: corona_shimmer = 0.842452 14:40:54.470 [planetinfo.record]: station_vector = 0.010 0.822 0.570 14:40:54.470 [planetinfo.record]: station = coriolis 14:40:59.464 [PLANETINFO OVER]: Done 14:40:59.465 [PLANETINFO LOGGING]: 7 68 14:41:00.469 [planetinfo.record]: seed = 76 201 225 175 14:41:00.469 [planetinfo.record]: coordinates = 201 98 14:41:00.469 [planetinfo.record]: government = 1; 14:41:00.469 [planetinfo.record]: economy = 2; 14:41:00.469 [planetinfo.record]: techlevel = 7; 14:41:00.469 [planetinfo.record]: population = 32; 14:41:00.469 [planetinfo.record]: productivity = 10240; 14:41:00.469 [planetinfo.record]: name = "Aanra"; 14:41:00.469 [planetinfo.record]: inhabitant = "Harmless Bony Feline"; 14:41:00.469 [planetinfo.record]: inhabitants = "Harmless Bony Felines"; 14:41:00.470 [planetinfo.record]: description = "The world Aanra is reasonably fabled for Zero-G cricket and Aanraian evil brandy."; 14:41:00.488 [planetinfo.record]: air_color = 0.519655, 0.60107, 0.853312, 1; 14:41:00.488 [planetinfo.record]: cloud_alpha = 1.000000; 14:41:00.488 [planetinfo.record]: cloud_color = 0.239502, 0.471657, 0.5625, 1; 14:41:00.488 [planetinfo.record]: cloud_fraction = 0.400000; 14:41:00.488 [planetinfo.record]: land_color = 0.655569, 0.0928125, 0.66, 1; 14:41:00.488 [planetinfo.record]: land_fraction = 0.490000; 14:41:00.488 [planetinfo.record]: polar_cloud_color = 0.482838, 0.677107, 0.753125, 1; 14:41:00.488 [planetinfo.record]: polar_land_color = 0.932432, 0.733336, 0.934, 1; 14:41:00.488 [planetinfo.record]: polar_sea_color = 0.939062, 0.917497, 0.883856, 1; 14:41:00.488 [planetinfo.record]: sea_color = 0.609375, 0.553399, 0.466077, 1; 14:41:00.488 [planetinfo.record]: rotation_speed = 0.004446 14:41:00.488 [planetinfo.record]: planet zpos = 891410.000000 14:41:00.488 [planetinfo.record]: sun_radius = 197947.536163 14:41:00.488 [planetinfo.record]: sun_vector = -0.239 0.929 -0.283 14:41:00.488 [planetinfo.record]: sun_distance = 1508540 14:41:00.488 [planetinfo.record]: corona_flare = 0.091368 14:41:00.488 [planetinfo.record]: corona_hues = 0.694237 14:41:00.488 [planetinfo.record]: sun_color = 0.69827, 0.646717, 0.644306, 1 14:41:00.489 [planetinfo.record]: corona_shimmer = 0.299612 14:41:00.489 [planetinfo.record]: station_vector = -0.510 0.792 0.336 14:41:00.489 [planetinfo.record]: station = coriolis 14:41:05.350 [PLANETINFO OVER]: Done 14:41:05.351 [PLANETINFO LOGGING]: 7 69 14:41:06.355 [planetinfo.record]: seed = 2 224 35 16 14:41:06.355 [planetinfo.record]: coordinates = 224 84 14:41:06.355 [planetinfo.record]: government = 0; 14:41:06.355 [planetinfo.record]: economy = 6; 14:41:06.355 [planetinfo.record]: techlevel = 1; 14:41:06.355 [planetinfo.record]: population = 11; 14:41:06.355 [planetinfo.record]: productivity = 1408; 14:41:06.355 [planetinfo.record]: name = "Erzara"; 14:41:06.355 [planetinfo.record]: inhabitant = "Human Colonial"; 14:41:06.355 [planetinfo.record]: inhabitants = "Human Colonials"; 14:41:06.355 [planetinfo.record]: description = "This world is a tedious place."; 14:41:06.362 [planetinfo.record]: air_color = 0.55208, 0.533959, 0.878678, 1; 14:41:06.362 [planetinfo.record]: cloud_alpha = 1.000000; 14:41:06.362 [planetinfo.record]: cloud_color = 0.332901, 0.26944, 0.638672, 1; 14:41:06.362 [planetinfo.record]: cloud_fraction = 0.200000; 14:41:06.362 [planetinfo.record]: land_color = 0.574922, 0.66, 0.656012, 1; 14:41:06.362 [planetinfo.record]: land_fraction = 0.240000; 14:41:06.362 [planetinfo.record]: polar_cloud_color = 0.551792, 0.502892, 0.787402, 1; 14:41:06.362 [planetinfo.record]: polar_land_color = 0.9039, 0.934, 0.932589, 1; 14:41:06.362 [planetinfo.record]: polar_sea_color = 0.775009, 0.945898, 0.797705, 1; 14:41:06.362 [planetinfo.record]: sea_color = 0.150047, 0.541016, 0.201973, 1; 14:41:06.363 [planetinfo.record]: rotation_speed = 0.004221 14:41:06.363 [planetinfo.record]: planet zpos = 456000.000000 14:41:06.363 [planetinfo.record]: sun_radius = 74966.040039 14:41:06.363 [planetinfo.record]: sun_vector = 0.385 0.714 -0.584 14:41:06.363 [planetinfo.record]: sun_distance = 699200 14:41:06.363 [planetinfo.record]: corona_flare = 0.052771 14:41:06.363 [planetinfo.record]: corona_hues = 0.967354 14:41:06.363 [planetinfo.record]: sun_color = 0.69134, 0.77322, 0.784219, 1 14:41:06.363 [planetinfo.record]: corona_shimmer = 0.333462 14:41:06.363 [planetinfo.record]: station_vector = 0.444 -0.775 0.449 14:41:06.363 [planetinfo.record]: station = coriolis 14:41:11.095 [PLANETINFO OVER]: Done 14:41:11.096 [PLANETINFO LOGGING]: 7 70 14:41:12.099 [planetinfo.record]: seed = 224 137 157 3 14:41:12.100 [planetinfo.record]: coordinates = 137 52 14:41:12.100 [planetinfo.record]: government = 4; 14:41:12.100 [planetinfo.record]: economy = 4; 14:41:12.100 [planetinfo.record]: techlevel = 6; 14:41:12.100 [planetinfo.record]: population = 33; 14:41:12.100 [planetinfo.record]: productivity = 12672; 14:41:12.100 [planetinfo.record]: name = "Gexeala"; 14:41:12.100 [planetinfo.record]: inhabitant = "Large Green Furry Rodent"; 14:41:12.100 [planetinfo.record]: inhabitants = "Large Green Furry Rodents"; 14:41:12.100 [planetinfo.record]: description = "The world Gexeala is reasonably famous for the Gexealaian evil poet."; 14:41:12.112 [planetinfo.record]: air_color = 0.650805, 0.834094, 0.950871, 1; 14:41:12.112 [planetinfo.record]: cloud_alpha = 1.000000; 14:41:12.112 [planetinfo.record]: cloud_color = 0.55806, 0.855469, 0.6115, 1; 14:41:12.112 [planetinfo.record]: cloud_fraction = 0.350000; 14:41:12.112 [planetinfo.record]: land_color = 0.650695, 0.605859, 0.66, 1; 14:41:12.112 [planetinfo.record]: land_fraction = 0.690000; 14:41:12.112 [planetinfo.record]: polar_cloud_color = 0.692672, 0.884961, 0.727224, 1; 14:41:12.112 [planetinfo.record]: polar_land_color = 0.930708, 0.914846, 0.934, 1; 14:41:12.112 [planetinfo.record]: polar_sea_color = 0.939453, 0.933864, 0.888352, 1; 14:41:12.112 [planetinfo.record]: sea_color = 0.605469, 0.59106, 0.473732, 1; 14:41:12.112 [planetinfo.record]: rotation_speed = 0.002170 14:41:12.112 [planetinfo.record]: planet zpos = 558150.000000 14:41:12.112 [planetinfo.record]: sun_radius = 78170.183868 14:41:12.112 [planetinfo.record]: sun_vector = -0.404 -0.411 0.817 14:41:12.112 [planetinfo.record]: sun_distance = 781410 14:41:12.112 [planetinfo.record]: corona_flare = 0.004636 14:41:12.112 [planetinfo.record]: corona_hues = 0.912941 14:41:12.113 [planetinfo.record]: sun_color = 0.723767, 0.651759, 0.503965, 1 14:41:12.113 [planetinfo.record]: corona_shimmer = 0.362061 14:41:12.113 [planetinfo.record]: station_vector = 0.880 0.201 0.430 14:41:12.113 [planetinfo.record]: station = coriolis 14:41:16.785 [PLANETINFO OVER]: Done 14:41:16.786 [PLANETINFO LOGGING]: 7 71 14:41:17.789 [planetinfo.record]: seed = 246 21 79 40 14:41:17.789 [planetinfo.record]: coordinates = 21 79 14:41:17.789 [planetinfo.record]: government = 6; 14:41:17.789 [planetinfo.record]: economy = 7; 14:41:17.789 [planetinfo.record]: techlevel = 4; 14:41:17.789 [planetinfo.record]: population = 30; 14:41:17.789 [planetinfo.record]: productivity = 7200; 14:41:17.789 [planetinfo.record]: name = "Usreinxe"; 14:41:17.789 [planetinfo.record]: inhabitant = "Human Colonial"; 14:41:17.789 [planetinfo.record]: inhabitants = "Human Colonials"; 14:41:17.789 [planetinfo.record]: description = "The planet Usreinxe is most noted for its inhabitants’ exceptional love for tourists and its great volcanoes."; 14:41:17.793 [planetinfo.record]: air_color = 0.626564, 0.88193, 0.809908, 1; 14:41:17.793 [planetinfo.record]: cloud_alpha = 1.000000; 14:41:17.793 [planetinfo.record]: cloud_color = 0.648438, 0.502337, 0.473663, 1; 14:41:17.793 [planetinfo.record]: cloud_fraction = 0.530000; 14:41:17.794 [planetinfo.record]: land_color = 0.244225, 0.499031, 0.525391, 1; 14:41:17.794 [planetinfo.record]: land_fraction = 0.380000; 14:41:17.794 [planetinfo.record]: polar_cloud_color = 0.791797, 0.680296, 0.658413, 1; 14:41:17.794 [planetinfo.record]: polar_land_color = 0.820701, 0.935577, 0.947461, 1; 14:41:17.794 [planetinfo.record]: polar_sea_color = 0.95, 0.757669, 0.719922, 1; 14:41:17.794 [planetinfo.record]: sea_color = 0.5, 0.0950928, 0.015625, 1; 14:41:17.794 [planetinfo.record]: rotation_speed = 0.003888 14:41:17.794 [planetinfo.record]: planet zpos = 439650.000000 14:41:17.794 [planetinfo.record]: sun_radius = 109817.089844 14:41:17.794 [planetinfo.record]: sun_vector = 0.139 -0.383 -0.913 14:41:17.794 [planetinfo.record]: sun_distance = 879300 14:41:17.794 [planetinfo.record]: corona_flare = 0.074187 14:41:17.794 [planetinfo.record]: corona_hues = 0.930756 14:41:17.794 [planetinfo.record]: sun_color = 0.669598, 0.636926, 0.313793, 1 14:41:17.795 [planetinfo.record]: corona_shimmer = 0.287461 14:41:17.795 [planetinfo.record]: station_vector = 0.655 0.552 0.515 14:41:17.795 [planetinfo.record]: station = coriolis 14:41:22.934 [PLANETINFO OVER]: Done 14:41:22.986 [PLANETINFO LOGGING]: 7 72 14:41:23.938 [planetinfo.record]: seed = 244 130 153 221 14:41:23.938 [planetinfo.record]: coordinates = 130 204 14:41:23.938 [planetinfo.record]: government = 6; 14:41:23.938 [planetinfo.record]: economy = 4; 14:41:23.938 [planetinfo.record]: techlevel = 8; 14:41:23.938 [planetinfo.record]: population = 43; 14:41:23.938 [planetinfo.record]: productivity = 20640; 14:41:23.938 [planetinfo.record]: name = "Isdidied"; 14:41:23.938 [planetinfo.record]: inhabitant = "Insect"; 14:41:23.938 [planetinfo.record]: inhabitants = "Insects"; 14:41:23.938 [planetinfo.record]: description = "The planet Isdidied is a boring planet."; 14:41:23.948 [planetinfo.record]: air_color = 0.597157, 0.903393, 0.802684, 1; 14:41:23.948 [planetinfo.record]: cloud_alpha = 1.000000; 14:41:23.948 [planetinfo.record]: cloud_color = 0.712891, 0.421908, 0.414925, 1; 14:41:23.948 [planetinfo.record]: cloud_fraction = 0.240000; 14:41:23.948 [planetinfo.record]: land_color = 0.324844, 0.66, 0.432198, 1; 14:41:23.948 [planetinfo.record]: land_fraction = 0.240000; 14:41:23.948 [planetinfo.record]: polar_cloud_color = 0.820801, 0.611408, 0.606383, 1; 14:41:23.948 [planetinfo.record]: polar_land_color = 0.815426, 0.934, 0.853407, 1; 14:41:23.948 [planetinfo.record]: polar_sea_color = 0.943164, 0.927528, 0.894348, 1; 14:41:23.948 [planetinfo.record]: sea_color = 0.568359, 0.530669, 0.450691, 1; 14:41:23.948 [planetinfo.record]: rotation_speed = 0.002581 14:41:23.948 [planetinfo.record]: planet zpos = 627400.000000 14:41:23.948 [planetinfo.record]: sun_radius = 156371.331787 14:41:23.948 [planetinfo.record]: sun_vector = 0.252 -0.162 -0.954 14:41:23.948 [planetinfo.record]: sun_distance = 1129320 14:41:23.948 [planetinfo.record]: corona_flare = 0.014601 14:41:23.948 [planetinfo.record]: corona_hues = 0.815659 14:41:23.948 [planetinfo.record]: sun_color = 0.784056, 0.78439, 0.785449, 1 14:41:23.949 [planetinfo.record]: corona_shimmer = 0.498183 14:41:23.949 [planetinfo.record]: station_vector = -0.263 -0.939 0.221 14:41:23.955 [planetinfo.record]: station = coriolis 14:41:28.752 [PLANETINFO OVER]: Done 14:41:28.754 [PLANETINFO LOGGING]: 7 73 14:41:29.758 [planetinfo.record]: seed = 42 152 187 82 14:41:29.758 [planetinfo.record]: coordinates = 152 141 14:41:29.758 [planetinfo.record]: government = 5; 14:41:29.758 [planetinfo.record]: economy = 5; 14:41:29.758 [planetinfo.record]: techlevel = 5; 14:41:29.758 [planetinfo.record]: population = 31; 14:41:29.758 [planetinfo.record]: productivity = 11160; 14:41:29.758 [planetinfo.record]: name = "Enedge"; 14:41:29.758 [planetinfo.record]: inhabitant = "Yellow Furry Insect"; 14:41:29.758 [planetinfo.record]: inhabitants = "Yellow Furry Insects"; 14:41:29.758 [planetinfo.record]: description = "This world is ravaged by occasional solar activity."; 14:41:29.780 [planetinfo.record]: air_color = 0.721996, 0.558448, 0.909246, 1; 14:41:29.780 [planetinfo.record]: cloud_alpha = 1.000000; 14:41:29.780 [planetinfo.record]: cloud_color = 0.730469, 0.322433, 0.561517, 1; 14:41:29.780 [planetinfo.record]: cloud_fraction = 0.520000; 14:41:29.780 [planetinfo.record]: land_color = 0.204468, 0.523438, 0.396348, 1; 14:41:29.780 [planetinfo.record]: land_fraction = 0.670000; 14:41:29.780 [planetinfo.record]: polar_cloud_color = 0.828711, 0.53939, 0.708914, 1; 14:41:29.780 [planetinfo.record]: polar_land_color = 0.803287, 0.947656, 0.890134, 1; 14:41:29.780 [planetinfo.record]: polar_sea_color = 0.924219, 0.867647, 0.82223, 1; 14:41:29.780 [planetinfo.record]: sea_color = 0.757812, 0.572268, 0.423309, 1; 14:41:29.780 [planetinfo.record]: rotation_speed = 0.003615 14:41:29.780 [planetinfo.record]: planet zpos = 382800.000000 14:41:29.780 [planetinfo.record]: sun_radius = 88947.729492 14:41:29.780 [planetinfo.record]: sun_vector = -0.082 0.952 -0.294 14:41:29.780 [planetinfo.record]: sun_distance = 696000 14:41:29.781 [planetinfo.record]: corona_flare = 0.064137 14:41:29.781 [planetinfo.record]: corona_hues = 0.871201 14:41:29.781 [planetinfo.record]: sun_color = 0.654535, 0.626986, 0.178667, 1 14:41:29.781 [planetinfo.record]: corona_shimmer = 0.421329 14:41:29.787 [planetinfo.record]: station_vector = 0.000 0.871 0.492 14:41:29.788 [planetinfo.record]: station = coriolis 14:41:34.821 [PLANETINFO OVER]: Done 14:41:34.821 [PLANETINFO LOGGING]: 7 74 14:41:35.825 [planetinfo.record]: seed = 136 46 85 10 14:41:35.825 [planetinfo.record]: coordinates = 46 99 14:41:35.825 [planetinfo.record]: government = 1; 14:41:35.825 [planetinfo.record]: economy = 3; 14:41:35.825 [planetinfo.record]: techlevel = 7; 14:41:35.826 [planetinfo.record]: population = 33; 14:41:35.826 [planetinfo.record]: productivity = 9240; 14:41:35.826 [planetinfo.record]: name = "Artelaan"; 14:41:35.826 [planetinfo.record]: inhabitant = "Human Colonial"; 14:41:35.826 [planetinfo.record]: inhabitants = "Human Colonials"; 14:41:35.826 [planetinfo.record]: description = "The planet Artelaan is cursed by dreadful civil war."; 14:41:35.852 [planetinfo.record]: air_color = 0.676329, 0.859166, 0.841848, 1; 14:41:35.852 [planetinfo.record]: cloud_alpha = 1.000000; 14:41:35.852 [planetinfo.record]: cloud_color = 0.580078, 0.573882, 0.557419, 1; 14:41:35.852 [planetinfo.record]: cloud_fraction = 0.250000; 14:41:35.852 [planetinfo.record]: land_color = 0.335479, 0.66, 0.268125, 1; 14:41:35.852 [planetinfo.record]: land_fraction = 0.320000; 14:41:35.852 [planetinfo.record]: polar_cloud_color = 0.761035, 0.755955, 0.742455, 1; 14:41:35.852 [planetinfo.record]: polar_land_color = 0.819188, 0.934, 0.795359, 1; 14:41:35.852 [planetinfo.record]: polar_sea_color = 0.913973, 0.933398, 0.879345, 1; 14:41:35.852 [planetinfo.record]: sea_color = 0.610573, 0.666016, 0.511739, 1; 14:41:35.852 [planetinfo.record]: rotation_speed = 0.000284 14:41:35.852 [planetinfo.record]: planet zpos = 596420.000000 14:41:35.852 [planetinfo.record]: sun_radius = 148915.541077 14:41:35.852 [planetinfo.record]: sun_vector = 0.438 -0.487 0.756 14:41:35.852 [planetinfo.record]: sun_distance = 1084400 14:41:35.852 [planetinfo.record]: corona_flare = 0.095966 14:41:35.853 [planetinfo.record]: corona_hues = 0.666718 14:41:35.853 [planetinfo.record]: sun_color = 0.230836, 0.332789, 0.716461, 1 14:41:35.853 [planetinfo.record]: corona_shimmer = 1.117790 14:41:35.853 [planetinfo.record]: station_vector = -0.959 0.198 0.201 14:41:35.853 [planetinfo.record]: station = coriolis 14:41:40.961 [PLANETINFO OVER]: Done 14:41:40.962 [PLANETINFO LOGGING]: 7 75 14:41:41.985 [planetinfo.record]: seed = 158 123 231 236 14:41:41.985 [planetinfo.record]: coordinates = 123 213 14:41:41.985 [planetinfo.record]: government = 3; 14:41:41.985 [planetinfo.record]: economy = 5; 14:41:41.985 [planetinfo.record]: techlevel = 7; 14:41:41.985 [planetinfo.record]: population = 37; 14:41:41.985 [planetinfo.record]: productivity = 10360; 14:41:41.985 [planetinfo.record]: name = "Inisbi"; 14:41:41.986 [planetinfo.record]: inhabitant = "Feline"; 14:41:41.986 [planetinfo.record]: inhabitants = "Felines"; 14:41:41.986 [planetinfo.record]: description = "The world Inisbi is a boring planet."; 14:41:42.002 [planetinfo.record]: air_color = 0.606251, 0.928107, 0.905157, 1; 14:41:42.002 [planetinfo.record]: cloud_alpha = 1.000000; 14:41:42.002 [planetinfo.record]: cloud_color = 0.787109, 0.716537, 0.439674, 1; 14:41:42.002 [planetinfo.record]: cloud_fraction = 0.360000; 14:41:42.002 [planetinfo.record]: land_color = 0.66, 0.363516, 0.453851, 1; 14:41:42.002 [planetinfo.record]: land_fraction = 0.320000; 14:41:42.002 [planetinfo.record]: polar_cloud_color = 0.854199, 0.806332, 0.618544, 1; 14:41:42.002 [planetinfo.record]: polar_land_color = 0.934, 0.829107, 0.861067, 1; 14:41:42.002 [planetinfo.record]: polar_sea_color = 0.936719, 0.935875, 0.882748, 1; 14:41:42.002 [planetinfo.record]: sea_color = 0.632812, 0.630534, 0.486969, 1; 14:41:42.002 [planetinfo.record]: rotation_speed = 0.003369 14:41:42.002 [planetinfo.record]: planet zpos = 901650.000000 14:41:42.003 [planetinfo.record]: sun_radius = 158276.703491 14:41:42.003 [planetinfo.record]: sun_vector = 0.081 -0.942 -0.326 14:41:42.003 [planetinfo.record]: sun_distance = 1442640 14:41:42.003 [planetinfo.record]: corona_flare = 0.065448 14:41:42.003 [planetinfo.record]: corona_hues = 0.723183 14:41:42.003 [planetinfo.record]: sun_color = 0.606277, 0.668358, 0.841064, 1 14:41:42.003 [planetinfo.record]: corona_shimmer = 0.628498 14:41:42.003 [planetinfo.record]: station_vector = 0.353 -0.712 0.607 14:41:42.003 [planetinfo.record]: station = coriolis 14:41:46.947 [PLANETINFO OVER]: Done 14:41:46.948 [PLANETINFO LOGGING]: 7 76 14:41:47.952 [planetinfo.record]: seed = 156 208 81 180 14:41:47.952 [planetinfo.record]: coordinates = 208 166 14:41:47.953 [planetinfo.record]: government = 3; 14:41:47.953 [planetinfo.record]: economy = 6; 14:41:47.953 [planetinfo.record]: techlevel = 3; 14:41:47.953 [planetinfo.record]: population = 22; 14:41:47.953 [planetinfo.record]: productivity = 4928; 14:41:47.953 [planetinfo.record]: name = "Ramaa"; 14:41:47.953 [planetinfo.record]: inhabitant = "Human Colonial"; 14:41:47.953 [planetinfo.record]: inhabitants = "Human Colonials"; 14:41:47.953 [planetinfo.record]: description = "The world Ramaa is fabled for its ancient mountains and its inhabitants’ ingrained shyness."; 14:41:47.959 [planetinfo.record]: air_color = 0.522204, 0.995748, 0.976765, 1; 14:41:47.959 [planetinfo.record]: cloud_alpha = 1.000000; 14:41:47.959 [planetinfo.record]: cloud_color = 0.990234, 0.900543, 0.170197, 1; 14:41:47.959 [planetinfo.record]: cloud_fraction = 0.440000; 14:41:47.959 [planetinfo.record]: land_color = 0.657422, 0.659335, 0.66, 1; 14:41:47.959 [planetinfo.record]: land_fraction = 0.600000; 14:41:47.959 [planetinfo.record]: polar_cloud_color = 0.945605, 0.892075, 0.456181, 1; 14:41:47.959 [planetinfo.record]: polar_land_color = 0.933088, 0.933765, 0.934, 1; 14:41:47.959 [planetinfo.record]: polar_sea_color = 0.922221, 0.933203, 0.876974, 1; 14:41:47.959 [planetinfo.record]: sea_color = 0.636525, 0.667969, 0.506978, 1; 14:41:47.959 [planetinfo.record]: rotation_speed = 0.000699 14:41:47.959 [planetinfo.record]: planet zpos = 526240.000000 14:41:47.959 [planetinfo.record]: sun_radius = 83693.215332 14:41:47.959 [planetinfo.record]: sun_vector = 0.765 -0.351 -0.540 14:41:47.959 [planetinfo.record]: sun_distance = 809600 14:41:47.959 [planetinfo.record]: corona_flare = 0.015701 14:41:47.959 [planetinfo.record]: corona_hues = 0.736588 14:41:47.959 [planetinfo.record]: sun_color = 0.734625, 0.664483, 0.454399, 1 14:41:47.960 [planetinfo.record]: corona_shimmer = 0.446577 14:41:47.960 [planetinfo.record]: station_vector = 0.315 -0.826 0.468 14:41:47.960 [planetinfo.record]: station = coriolis 14:41:52.704 [PLANETINFO OVER]: Done 14:41:52.705 [PLANETINFO LOGGING]: 7 77 14:41:53.708 [planetinfo.record]: seed = 82 143 83 106 14:41:53.709 [planetinfo.record]: coordinates = 143 175 14:41:53.709 [planetinfo.record]: government = 2; 14:41:53.709 [planetinfo.record]: economy = 7; 14:41:53.709 [planetinfo.record]: techlevel = 4; 14:41:53.709 [planetinfo.record]: population = 26; 14:41:53.709 [planetinfo.record]: productivity = 3744; 14:41:53.709 [planetinfo.record]: name = "Aresgeti"; 14:41:53.709 [planetinfo.record]: inhabitant = "Human Colonial"; 14:41:53.709 [planetinfo.record]: inhabitants = "Human Colonials"; 14:41:53.709 [planetinfo.record]: description = "This world is ravaged by unpredictable civil war."; 14:41:53.724 [planetinfo.record]: air_color = 0.85795, 0.768153, 0.990545, 1; 14:41:53.724 [planetinfo.record]: cloud_alpha = 1.000000; 14:41:53.724 [planetinfo.record]: cloud_color = 0.974609, 0.902275, 0.958221, 1; 14:41:53.724 [planetinfo.record]: cloud_fraction = 0.350000; 14:41:53.724 [planetinfo.record]: land_color = 0.648239, 0.65625, 0.645996, 1; 14:41:53.724 [planetinfo.record]: land_fraction = 0.330000; 14:41:53.724 [planetinfo.record]: polar_cloud_color = 0.938574, 0.895037, 0.92871, 1; 14:41:53.724 [planetinfo.record]: polar_land_color = 0.931524, 0.934375, 0.930725, 1; 14:41:53.724 [planetinfo.record]: polar_sea_color = 0.756061, 0.900977, 0.75492, 1; 14:41:53.724 [planetinfo.record]: sea_color = 0.353146, 0.990234, 0.348129, 1; 14:41:53.724 [planetinfo.record]: rotation_speed = 0.003000 14:41:53.724 [planetinfo.record]: planet zpos = 551900.000000 14:41:53.725 [planetinfo.record]: sun_radius = 145596.299744 14:41:53.725 [planetinfo.record]: sun_vector = 0.021 -0.655 0.756 14:41:53.725 [planetinfo.record]: sun_distance = 1103800 14:41:53.725 [planetinfo.record]: corona_flare = 0.099818 14:41:53.725 [planetinfo.record]: corona_hues = 0.962677 14:41:53.725 [planetinfo.record]: sun_color = 0.565517, 0.692147, 0.749036, 1 14:41:53.725 [planetinfo.record]: corona_shimmer = 0.514532 14:41:53.726 [planetinfo.record]: station_vector = -0.650 0.651 0.393 14:41:53.726 [planetinfo.record]: station = coriolis 14:41:58.192 [PLANETINFO OVER]: Done 14:41:58.337 [PLANETINFO LOGGING]: 7 78 14:41:59.218 [planetinfo.record]: seed = 48 183 13 4 14:41:59.218 [planetinfo.record]: coordinates = 183 163 14:41:59.219 [planetinfo.record]: government = 6; 14:41:59.219 [planetinfo.record]: economy = 3; 14:41:59.219 [planetinfo.record]: techlevel = 10; 14:41:59.219 [planetinfo.record]: population = 50; 14:41:59.219 [planetinfo.record]: productivity = 28000; 14:41:59.219 [planetinfo.record]: name = "Zariorte"; 14:41:59.219 [planetinfo.record]: inhabitant = "Human Colonial"; 14:41:59.219 [planetinfo.record]: inhabitants = "Human Colonials"; 14:41:59.219 [planetinfo.record]: description = "This planet is beset by deadly earthquakes."; 14:41:59.225 [planetinfo.record]: air_color = 0.480054, 0.701298, 0.842256, 1; 14:41:59.225 [planetinfo.record]: cloud_alpha = 1.000000; 14:41:59.225 [planetinfo.record]: cloud_color = 0.163338, 0.529297, 0.229096, 1; 14:41:59.225 [planetinfo.record]: cloud_fraction = 0.340000; 14:41:59.225 [planetinfo.record]: land_color = 0.66, 0.0567188, 0.594016, 1; 14:41:59.225 [planetinfo.record]: land_fraction = 0.670000; 14:41:59.225 [planetinfo.record]: polar_cloud_color = 0.419193, 0.738184, 0.476512, 1; 14:41:59.226 [planetinfo.record]: polar_land_color = 0.934, 0.720566, 0.910656, 1; 14:41:59.226 [planetinfo.record]: polar_sea_color = 0.938867, 0.886606, 0.886606, 1; 14:41:59.226 [planetinfo.record]: sea_color = 0.611328, 0.475212, 0.475212, 1; 14:41:59.226 [planetinfo.record]: rotation_speed = 0.003427 14:41:59.226 [planetinfo.record]: planet zpos = 482760.000000 14:41:59.226 [planetinfo.record]: sun_radius = 131113.975067 14:41:59.226 [planetinfo.record]: sun_vector = -0.241 0.917 0.318 14:41:59.226 [planetinfo.record]: sun_distance = 724140 14:41:59.226 [planetinfo.record]: corona_flare = 0.010239 14:41:59.226 [planetinfo.record]: corona_hues = 0.634407 14:41:59.226 [planetinfo.record]: sun_color = 0.651514, 0.650783, 0.191286, 1 14:41:59.226 [planetinfo.record]: corona_shimmer = 0.340617 14:41:59.226 [planetinfo.record]: station_vector = 0.917 -0.018 0.399 14:41:59.226 [planetinfo.record]: station = coriolis 14:42:03.866 [PLANETINFO OVER]: Done 14:42:03.867 [PLANETINFO LOGGING]: 7 79 14:42:04.869 [planetinfo.record]: seed = 70 124 127 244 14:42:04.869 [planetinfo.record]: coordinates = 124 25 14:42:04.869 [planetinfo.record]: government = 0; 14:42:04.869 [planetinfo.record]: economy = 3; 14:42:04.869 [planetinfo.record]: techlevel = 4; 14:42:04.869 [planetinfo.record]: population = 20; 14:42:04.869 [planetinfo.record]: productivity = 4480; 14:42:04.869 [planetinfo.record]: name = "Raaran"; 14:42:04.869 [planetinfo.record]: inhabitant = "Furry Humanoid"; 14:42:04.869 [planetinfo.record]: inhabitants = "Furry Humanoids"; 14:42:04.869 [planetinfo.record]: description = "The world Raaran is a boring world."; 14:42:04.872 [planetinfo.record]: air_color = 0.732921, 0.53522, 0.93266, 1; 14:42:04.872 [planetinfo.record]: cloud_alpha = 1.000000; 14:42:04.872 [planetinfo.record]: cloud_color = 0.800781, 0.2565, 0.524389, 1; 14:42:04.872 [planetinfo.record]: cloud_fraction = 0.330000; 14:42:04.872 [planetinfo.record]: land_color = 0.0254822, 0.652344, 0.064661, 1; 14:42:04.872 [planetinfo.record]: land_fraction = 0.410000; 14:42:04.872 [planetinfo.record]: polar_cloud_color = 0.860352, 0.49487, 0.674756, 1; 14:42:04.872 [planetinfo.record]: polar_land_color = 0.710203, 0.934766, 0.724238, 1; 14:42:04.872 [planetinfo.record]: polar_sea_color = 0.893518, 0.933984, 0.886464, 1; 14:42:04.872 [planetinfo.record]: sea_color = 0.545747, 0.660156, 0.525804, 1; 14:42:04.872 [planetinfo.record]: rotation_speed = 0.002747 14:42:04.872 [planetinfo.record]: planet zpos = 475680.000000 14:42:04.872 [planetinfo.record]: sun_radius = 88025.647583 14:42:04.872 [planetinfo.record]: sun_vector = -0.619 -0.088 0.780 14:42:04.872 [planetinfo.record]: sun_distance = 911720 14:42:04.872 [planetinfo.record]: corona_flare = 0.077911 14:42:04.872 [planetinfo.record]: corona_hues = 0.666183 14:42:04.872 [planetinfo.record]: sun_color = 0.805634, 0.436671, 0.211698, 1 14:42:04.872 [planetinfo.record]: corona_shimmer = 0.318538 14:42:04.873 [planetinfo.record]: station_vector = -0.934 -0.118 0.337 14:42:04.873 [planetinfo.record]: station = coriolis 14:42:09.973 [PLANETINFO OVER]: Done 14:42:09.973 [PLANETINFO LOGGING]: 7 80 14:42:10.980 [planetinfo.record]: seed = 68 122 9 0 14:42:10.980 [planetinfo.record]: coordinates = 122 251 14:42:10.980 [planetinfo.record]: government = 0; 14:42:10.980 [planetinfo.record]: economy = 3; 14:42:10.980 [planetinfo.record]: techlevel = 6; 14:42:10.980 [planetinfo.record]: population = 28; 14:42:10.980 [planetinfo.record]: productivity = 6272; 14:42:10.980 [planetinfo.record]: name = "Laace"; 14:42:10.980 [planetinfo.record]: inhabitant = "Human Colonial"; 14:42:10.980 [planetinfo.record]: inhabitants = "Human Colonials"; 14:42:10.980 [planetinfo.record]: description = "The world Laace is a dull world."; 14:42:10.992 [planetinfo.record]: air_color = 0.574322, 0.920947, 0.980789, 1; 14:42:10.992 [planetinfo.record]: cloud_alpha = 1.000000; 14:42:10.992 [planetinfo.record]: cloud_color = 0.669231, 0.945312, 0.336029, 1; 14:42:10.992 [planetinfo.record]: cloud_fraction = 0.130000; 14:42:10.992 [planetinfo.record]: land_color = 0.0464063, 0.257329, 0.66, 1; 14:42:10.992 [planetinfo.record]: land_fraction = 0.500000; 14:42:10.992 [planetinfo.record]: polar_cloud_color = 0.756476, 0.925391, 0.552614, 1; 14:42:10.992 [planetinfo.record]: polar_land_color = 0.716918, 0.79154, 0.934, 1; 14:42:10.992 [planetinfo.record]: polar_sea_color = 0.939258, 0.885652, 0.878536, 1; 14:42:10.992 [planetinfo.record]: sea_color = 0.607422, 0.468754, 0.450346, 1; 14:42:10.992 [planetinfo.record]: rotation_speed = 0.003729 14:42:10.992 [planetinfo.record]: planet zpos = 381940.000000 14:42:10.992 [planetinfo.record]: sun_radius = 54266.208496 14:42:10.992 [planetinfo.record]: sun_vector = 0.387 0.508 0.770 14:42:10.992 [planetinfo.record]: sun_distance = 587600 14:42:10.992 [planetinfo.record]: corona_flare = 0.093175 14:42:10.992 [planetinfo.record]: corona_hues = 0.917130 14:42:10.992 [planetinfo.record]: sun_color = 0.842993, 0.842504, 0.8409, 1 14:42:10.992 [planetinfo.record]: corona_shimmer = 0.330063 14:42:10.993 [planetinfo.record]: station_vector = -0.150 0.456 0.877 14:42:10.993 [planetinfo.record]: station = coriolis 14:42:15.547 [PLANETINFO OVER]: Done 14:42:15.548 [PLANETINFO LOGGING]: 7 81 14:42:16.552 [planetinfo.record]: seed = 122 101 235 202 14:42:16.552 [planetinfo.record]: coordinates = 101 239 14:42:16.552 [planetinfo.record]: government = 7; 14:42:16.552 [planetinfo.record]: economy = 7; 14:42:16.553 [planetinfo.record]: techlevel = 5; 14:42:16.553 [planetinfo.record]: population = 35; 14:42:16.553 [planetinfo.record]: productivity = 9240; 14:42:16.553 [planetinfo.record]: name = "Areran"; 14:42:16.553 [planetinfo.record]: inhabitant = "Small Horned Bird"; 14:42:16.553 [planetinfo.record]: inhabitants = "Small Horned Birds"; 14:42:16.553 [planetinfo.record]: description = "Areran is cursed by killer edible poets."; 14:42:16.592 [planetinfo.record]: air_color = 0.495967, 0.641466, 0.852662, 1; 14:42:16.592 [planetinfo.record]: cloud_alpha = 1.000000; 14:42:16.592 [planetinfo.record]: cloud_color = 0.192688, 0.560547, 0.482952, 1; 14:42:16.592 [planetinfo.record]: cloud_fraction = 0.470000; 14:42:16.592 [planetinfo.record]: land_color = 0.535399, 0.352478, 0.601562, 1; 14:42:16.592 [planetinfo.record]: land_fraction = 0.240000; 14:42:16.592 [planetinfo.record]: polar_cloud_color = 0.443708, 0.752246, 0.687164, 1; 14:42:16.592 [planetinfo.record]: polar_land_color = 0.914002, 0.842555, 0.939844, 1; 14:42:16.592 [planetinfo.record]: polar_sea_color = 0.928906, 0.855202, 0.838193, 1; 14:42:16.596 [planetinfo.record]: sea_color = 0.710938, 0.485298, 0.433228, 1; 14:42:16.596 [planetinfo.record]: rotation_speed = 0.002412 14:42:16.596 [planetinfo.record]: planet zpos = 712010.000000 14:42:16.596 [planetinfo.record]: sun_radius = 147189.360657 14:42:16.596 [planetinfo.record]: sun_vector = 0.464 0.034 0.885 14:42:16.596 [planetinfo.record]: sun_distance = 1150170 14:42:16.596 [planetinfo.record]: corona_flare = 0.052950 14:42:16.596 [planetinfo.record]: corona_hues = 0.934898 14:42:16.597 [planetinfo.record]: sun_color = 0.606929, 0.633538, 0.815958, 1 14:42:16.597 [planetinfo.record]: corona_shimmer = 0.370771 14:42:16.597 [planetinfo.record]: station_vector = -0.844 -0.535 0.029 14:42:16.597 [planetinfo.record]: station = coriolis 14:42:21.535 [PLANETINFO OVER]: Done 14:42:21.536 [PLANETINFO LOGGING]: 7 82 14:42:22.541 [planetinfo.record]: seed = 216 59 197 172 14:42:22.541 [planetinfo.record]: coordinates = 59 80 14:42:22.541 [planetinfo.record]: government = 3; 14:42:22.541 [planetinfo.record]: economy = 0; 14:42:22.541 [planetinfo.record]: techlevel = 12; 14:42:22.541 [planetinfo.record]: population = 52; 14:42:22.542 [planetinfo.record]: productivity = 29120; 14:42:22.542 [planetinfo.record]: name = "Inorle"; 14:42:22.542 [planetinfo.record]: inhabitant = "Harmless Bony Lobster"; 14:42:22.542 [planetinfo.record]: inhabitants = "Harmless Bony Lobsters"; 14:42:22.542 [planetinfo.record]: description = "This world is very fabled for the Inorleian edible poet."; 14:42:22.556 [planetinfo.record]: air_color = 0.675489, 0.494959, 0.950871, 1; 14:42:22.556 [planetinfo.record]: cloud_alpha = 1.000000; 14:42:22.556 [planetinfo.record]: cloud_color = 0.855469, 0.137009, 0.709532, 1; 14:42:22.556 [planetinfo.record]: cloud_fraction = 0.490000; 14:42:22.556 [planetinfo.record]: land_color = 0.66, 0.591458, 0.446016, 1; 14:42:22.556 [planetinfo.record]: land_fraction = 0.640000; 14:42:22.556 [planetinfo.record]: polar_cloud_color = 0.884961, 0.420443, 0.790606, 1; 14:42:22.556 [planetinfo.record]: polar_land_color = 0.934, 0.909751, 0.858295, 1; 14:42:22.556 [planetinfo.record]: polar_sea_color = 0.928919, 0.933398, 0.876064, 1; 14:42:22.556 [planetinfo.record]: sea_color = 0.653231, 0.666016, 0.502374, 1; 14:42:22.556 [planetinfo.record]: rotation_speed = 0.001611 14:42:22.556 [planetinfo.record]: planet zpos = 713640.000000 14:42:22.556 [planetinfo.record]: sun_radius = 107456.344452 14:42:22.556 [planetinfo.record]: sun_vector = -0.923 -0.223 -0.314 14:42:22.556 [planetinfo.record]: sun_distance = 1189400 14:42:22.556 [planetinfo.record]: corona_flare = 0.040323 14:42:22.556 [planetinfo.record]: corona_hues = 0.502617 14:42:22.556 [planetinfo.record]: sun_color = 0.839023, 0.536254, 0.374812, 1 14:42:22.557 [planetinfo.record]: corona_shimmer = 0.639738 14:42:22.557 [planetinfo.record]: station_vector = 0.451 -0.801 0.394 14:42:22.557 [planetinfo.record]: station = icosahedron 14:42:28.203 [PLANETINFO OVER]: Done 14:42:28.204 [PLANETINFO LOGGING]: 7 83 14:42:29.220 [planetinfo.record]: seed = 238 7 23 99 14:42:29.220 [planetinfo.record]: coordinates = 7 33 14:42:29.220 [planetinfo.record]: government = 5; 14:42:29.220 [planetinfo.record]: economy = 1; 14:42:29.220 [planetinfo.record]: techlevel = 12; 14:42:29.220 [planetinfo.record]: population = 55; 14:42:29.220 [planetinfo.record]: productivity = 35640; 14:42:29.220 [planetinfo.record]: name = "Geintiso"; 14:42:29.220 [planetinfo.record]: inhabitant = "Human Colonial"; 14:42:29.220 [planetinfo.record]: inhabitants = "Human Colonials"; 14:42:29.220 [planetinfo.record]: description = "The world Geintiso is scourged by killer edible poets."; 14:42:29.224 [planetinfo.record]: air_color = 0.466383, 0.552425, 0.902742, 1; 14:42:29.224 [planetinfo.record]: cloud_alpha = 1.000000; 14:42:29.224 [planetinfo.record]: cloud_color = 0.10553, 0.455531, 0.710938, 1; 14:42:29.224 [planetinfo.record]: cloud_fraction = 0.240000; 14:42:29.224 [planetinfo.record]: land_color = 0.507891, 0.635045, 0.66, 1; 14:42:29.224 [planetinfo.record]: land_fraction = 0.340000; 14:42:29.224 [planetinfo.record]: polar_cloud_color = 0.383538, 0.635822, 0.819922, 1; 14:42:29.224 [planetinfo.record]: polar_land_color = 0.880186, 0.925171, 0.934, 1; 14:42:29.224 [planetinfo.record]: polar_sea_color = 0.828797, 0.946289, 0.755922, 1; 14:42:29.224 [planetinfo.record]: sea_color = 0.270358, 0.537109, 0.104904, 1; 14:42:29.224 [planetinfo.record]: rotation_speed = 0.002038 14:42:29.224 [planetinfo.record]: planet zpos = 430920.000000 14:42:29.225 [planetinfo.record]: sun_radius = 71638.630829 14:42:29.225 [planetinfo.record]: sun_vector = -0.186 0.399 0.898 14:42:29.225 [planetinfo.record]: sun_distance = 754110 14:42:29.225 [planetinfo.record]: corona_flare = 0.001611 14:42:29.225 [planetinfo.record]: corona_hues = 0.537201 14:42:29.225 [planetinfo.record]: sun_color = 0.455576, 0.499284, 0.666867, 1 14:42:29.225 [planetinfo.record]: corona_shimmer = 0.380479 14:42:29.225 [planetinfo.record]: station_vector = 0.682 0.729 0.060 14:42:29.225 [planetinfo.record]: station = dodecahedron 14:42:33.985 [PLANETINFO OVER]: Done 14:42:33.986 [PLANETINFO LOGGING]: 7 84 14:42:34.988 [planetinfo.record]: seed = 236 231 193 108 14:42:34.988 [planetinfo.record]: coordinates = 231 247 14:42:34.988 [planetinfo.record]: government = 5; 14:42:34.988 [planetinfo.record]: economy = 7; 14:42:34.988 [planetinfo.record]: techlevel = 6; 14:42:34.988 [planetinfo.record]: population = 37; 14:42:34.988 [planetinfo.record]: productivity = 7992; 14:42:34.988 [planetinfo.record]: name = "Ininlequ"; 14:42:34.988 [planetinfo.record]: inhabitant = "Blue Slimy Rodent"; 14:42:34.989 [planetinfo.record]: inhabitants = "Blue Slimy Rodents"; 14:42:34.989 [planetinfo.record]: description = "The planet Ininlequ is most noted for the Ininlequian evil arts graduate and its fabulous cuisine."; 14:42:34.992 [planetinfo.record]: air_color = 0.629177, 0.872824, 0.792698, 1; 14:42:34.992 [planetinfo.record]: cloud_alpha = 1.000000; 14:42:34.992 [planetinfo.record]: cloud_color = 0.621094, 0.476567, 0.473099, 1; 14:42:34.992 [planetinfo.record]: cloud_fraction = 0.170000; 14:42:34.992 [planetinfo.record]: land_color = 0.634219, 0.639456, 0.66, 1; 14:42:34.992 [planetinfo.record]: land_fraction = 0.600000; 14:42:34.993 [planetinfo.record]: polar_cloud_color = 0.779492, 0.666126, 0.663406, 1; 14:42:34.993 [planetinfo.record]: polar_land_color = 0.924879, 0.926732, 0.934, 1; 14:42:34.993 [planetinfo.record]: polar_sea_color = 0.812374, 0.947461, 0.928464, 1; 14:42:34.993 [planetinfo.record]: sea_color = 0.225754, 0.525391, 0.483254, 1; 14:42:34.993 [planetinfo.record]: rotation_speed = 0.001920 14:42:34.993 [planetinfo.record]: planet zpos = 734280.000000 14:42:34.993 [planetinfo.record]: sun_radius = 140312.359924 14:42:34.993 [planetinfo.record]: sun_vector = 0.637 -0.595 -0.490 14:42:34.993 [planetinfo.record]: sun_distance = 1346180 14:42:34.993 [planetinfo.record]: corona_flare = 0.055774 14:42:34.993 [planetinfo.record]: corona_hues = 0.703323 14:42:34.993 [planetinfo.record]: sun_color = 0.734128, 0.679217, 0.596101, 1 14:42:34.993 [planetinfo.record]: corona_shimmer = 0.373297 14:42:34.993 [planetinfo.record]: station_vector = -0.936 -0.317 0.153 14:42:34.994 [planetinfo.record]: station = coriolis 14:42:40.060 [PLANETINFO OVER]: Done 14:42:40.061 [PLANETINFO LOGGING]: 7 85 14:42:41.071 [planetinfo.record]: seed = 162 90 131 72 14:42:41.071 [planetinfo.record]: coordinates = 90 161 14:42:41.071 [planetinfo.record]: government = 4; 14:42:41.071 [planetinfo.record]: economy = 1; 14:42:41.071 [planetinfo.record]: techlevel = 10; 14:42:41.071 [planetinfo.record]: population = 46; 14:42:41.071 [planetinfo.record]: productivity = 26496; 14:42:41.071 [planetinfo.record]: name = "Uszasora"; 14:42:41.071 [planetinfo.record]: inhabitant = "Small Yellow Bony Lobster"; 14:42:41.071 [planetinfo.record]: inhabitants = "Small Yellow Bony Lobsters"; 14:42:41.071 [planetinfo.record]: description = "This planet is a tedious little planet."; 14:42:41.077 [planetinfo.record]: air_color = 0.685092, 0.501006, 0.94827, 1; 14:42:41.077 [planetinfo.record]: cloud_alpha = 1.000000; 14:42:41.077 [planetinfo.record]: cloud_color = 0.847656, 0.155624, 0.674648, 1; 14:42:41.078 [planetinfo.record]: cloud_fraction = 0.410000; 14:42:41.078 [planetinfo.record]: land_color = 0.190186, 0.429482, 0.640625, 1; 14:42:41.078 [planetinfo.record]: land_fraction = 0.420000; 14:42:41.078 [planetinfo.record]: polar_cloud_color = 0.881445, 0.431684, 0.769005, 1; 14:42:41.078 [planetinfo.record]: polar_land_color = 0.771417, 0.858819, 0.935938, 1; 14:42:41.078 [planetinfo.record]: polar_sea_color = 0.926953, 0.880306, 0.839146, 1; 14:42:41.078 [planetinfo.record]: sea_color = 0.730469, 0.58343, 0.45369, 1; 14:42:41.078 [planetinfo.record]: rotation_speed = 0.001726 14:42:41.078 [planetinfo.record]: planet zpos = 693560.000000 14:42:41.078 [planetinfo.record]: sun_radius = 142890.879211 14:42:41.079 [planetinfo.record]: sun_vector = -0.971 -0.160 0.179 14:42:41.079 [planetinfo.record]: sun_distance = 941260 14:42:41.079 [planetinfo.record]: corona_flare = 0.070619 14:42:41.079 [planetinfo.record]: corona_hues = 0.988365 14:42:41.079 [planetinfo.record]: sun_color = 0.328692, 0.645362, 0.674115, 1 14:42:41.079 [planetinfo.record]: corona_shimmer = 0.253146 14:42:41.079 [planetinfo.record]: station_vector = 0.954 0.172 0.245 14:42:41.080 [planetinfo.record]: station = coriolis 14:42:45.992 [PLANETINFO OVER]: Done 14:42:45.992 [PLANETINFO LOGGING]: 7 86 14:42:46.995 [planetinfo.record]: seed = 128 116 125 160 14:42:46.995 [planetinfo.record]: coordinates = 116 231 14:42:46.995 [planetinfo.record]: government = 0; 14:42:46.995 [planetinfo.record]: economy = 7; 14:42:46.995 [planetinfo.record]: techlevel = 0; 14:42:46.995 [planetinfo.record]: population = 8; 14:42:46.996 [planetinfo.record]: productivity = 768; 14:42:46.996 [planetinfo.record]: name = "Teat"; 14:42:46.996 [planetinfo.record]: inhabitant = "Human Colonial"; 14:42:46.996 [planetinfo.record]: inhabitants = "Human Colonials"; 14:42:46.996 [planetinfo.record]: description = "Teat is cursed by dreadful civil war."; 14:42:47.005 [planetinfo.record]: air_color = 0.632411, 0.903393, 0.87984, 1; 14:42:47.005 [planetinfo.record]: cloud_alpha = 1.000000; 14:42:47.005 [planetinfo.record]: cloud_color = 0.712891, 0.659285, 0.498466, 1; 14:42:47.005 [planetinfo.record]: cloud_fraction = 0.270000; 14:42:47.005 [planetinfo.record]: land_color = 0.66, 0.348047, 0.538143, 1; 14:42:47.005 [planetinfo.record]: land_fraction = 0.440000; 14:42:47.006 [planetinfo.record]: polar_cloud_color = 0.820801, 0.782226, 0.6665, 1; 14:42:47.006 [planetinfo.record]: polar_land_color = 0.934, 0.823635, 0.890889, 1; 14:42:47.006 [planetinfo.record]: polar_sea_color = 0.938281, 0.89205, 0.877898, 1; 14:42:47.006 [planetinfo.record]: sea_color = 0.617188, 0.495547, 0.45831, 1; 14:42:47.006 [planetinfo.record]: rotation_speed = 0.004739 14:42:47.006 [planetinfo.record]: planet zpos = 381160.000000 14:42:47.006 [planetinfo.record]: sun_radius = 69605.025024 14:42:47.006 [planetinfo.record]: sun_vector = -0.023 0.183 -0.983 14:42:47.006 [planetinfo.record]: sun_distance = 615720 14:42:47.006 [planetinfo.record]: corona_flare = 0.043176 14:42:47.006 [planetinfo.record]: corona_hues = 0.876114 14:42:47.006 [planetinfo.record]: sun_color = 0.838666, 0.774745, 0.758812, 1 14:42:47.006 [planetinfo.record]: corona_shimmer = 0.400606 14:42:47.007 [planetinfo.record]: station_vector = -0.328 -0.753 0.570 14:42:47.007 [planetinfo.record]: station = coriolis 14:42:51.961 [PLANETINFO OVER]: Done 14:42:51.962 [PLANETINFO LOGGING]: 7 87 14:42:52.966 [planetinfo.record]: seed = 150 174 175 188 14:42:52.966 [planetinfo.record]: coordinates = 174 17 14:42:52.966 [planetinfo.record]: government = 2; 14:42:52.966 [planetinfo.record]: economy = 1; 14:42:52.966 [planetinfo.record]: techlevel = 9; 14:42:52.966 [planetinfo.record]: population = 40; 14:42:52.966 [planetinfo.record]: productivity = 17280; 14:42:52.966 [planetinfo.record]: name = "Teteus"; 14:42:52.966 [planetinfo.record]: inhabitant = "Harmless Insect"; 14:42:52.966 [planetinfo.record]: inhabitants = "Harmless Insects"; 14:42:52.966 [planetinfo.record]: description = "The world Teteus is most famous for its vast oceans."; 14:42:52.984 [planetinfo.record]: air_color = 0.603759, 0.584949, 0.8325, 1; 14:42:52.984 [planetinfo.record]: cloud_alpha = 1.000000; 14:42:52.984 [planetinfo.record]: cloud_color = 0.388992, 0.353516, 0.5, 1; 14:42:52.984 [planetinfo.record]: cloud_fraction = 0.470000; 14:42:52.984 [planetinfo.record]: land_color = 0.491495, 0.66, 0.430547, 1; 14:42:52.984 [planetinfo.record]: land_fraction = 0.200000; 14:42:52.984 [planetinfo.record]: polar_cloud_color = 0.624399, 0.592249, 0.725, 1; 14:42:52.984 [planetinfo.record]: polar_land_color = 0.874385, 0.934, 0.852822, 1; 14:42:52.984 [planetinfo.record]: polar_sea_color = 0.940234, 0.889655, 0.881378, 1; 14:42:52.984 [planetinfo.record]: sea_color = 0.597656, 0.469053, 0.448009, 1; 14:42:52.984 [planetinfo.record]: rotation_speed = 0.001480 14:42:52.984 [planetinfo.record]: planet zpos = 788060.000000 14:42:52.984 [planetinfo.record]: sun_radius = 146562.465820 14:42:52.984 [planetinfo.record]: sun_vector = 0.695 -0.627 0.351 14:42:52.984 [planetinfo.record]: sun_distance = 1273020 14:42:52.984 [planetinfo.record]: corona_flare = 0.070970 14:42:52.984 [planetinfo.record]: corona_hues = 0.935585 14:42:52.984 [planetinfo.record]: sun_color = 0.650986, 0.579158, 0.559068, 1 14:42:52.985 [planetinfo.record]: corona_shimmer = 0.270147 14:42:52.985 [planetinfo.record]: station_vector = -0.383 -0.140 0.913 14:42:52.985 [planetinfo.record]: station = coriolis 14:42:57.601 [PLANETINFO OVER]: Done 14:42:57.602 [PLANETINFO LOGGING]: 7 88 14:42:58.607 [planetinfo.record]: seed = 148 33 121 134 14:42:58.607 [planetinfo.record]: coordinates = 33 232 14:42:58.607 [planetinfo.record]: government = 2; 14:42:58.607 [planetinfo.record]: economy = 0; 14:42:58.607 [planetinfo.record]: techlevel = 9; 14:42:58.607 [planetinfo.record]: population = 39; 14:42:58.607 [planetinfo.record]: productivity = 18720; 14:42:58.607 [planetinfo.record]: name = "Biered"; 14:42:58.607 [planetinfo.record]: inhabitant = "Human Colonial"; 14:42:58.607 [planetinfo.record]: inhabitants = "Human Colonials"; 14:42:58.607 [planetinfo.record]: description = "This planet is most notable for Bieredian A’il water but scourged by evil disease."; 14:42:58.632 [planetinfo.record]: air_color = 0.636742, 0.892986, 0.858715, 1; 14:42:58.632 [planetinfo.record]: cloud_alpha = 1.000000; 14:42:58.632 [planetinfo.record]: cloud_color = 0.681641, 0.611954, 0.503242, 1; 14:42:58.632 [planetinfo.record]: cloud_fraction = 0.300000; 14:42:58.632 [planetinfo.record]: land_color = 0.394937, 0.66, 0.299063, 1; 14:42:58.632 [planetinfo.record]: land_fraction = 0.470000; 14:42:58.632 [planetinfo.record]: polar_cloud_color = 0.806738, 0.755191, 0.674777, 1; 14:42:58.632 [planetinfo.record]: polar_land_color = 0.840224, 0.934, 0.806305, 1; 14:42:58.632 [planetinfo.record]: polar_sea_color = 0.928044, 0.938672, 0.890088, 1; 14:42:58.632 [planetinfo.record]: sea_color = 0.585507, 0.613281, 0.486313, 1; 14:42:58.632 [planetinfo.record]: rotation_speed = 0.000027 14:42:58.632 [planetinfo.record]: planet zpos = 482350.000000 14:42:58.632 [planetinfo.record]: sun_radius = 87340.694427 14:42:58.632 [planetinfo.record]: sun_vector = -0.372 -0.662 -0.651 14:42:58.633 [planetinfo.record]: sun_distance = 789300 14:42:58.633 [planetinfo.record]: corona_flare = 0.000346 14:42:58.633 [planetinfo.record]: corona_hues = 0.625443 14:42:58.633 [planetinfo.record]: sun_color = 0.770908, 0.482175, 0.403995, 1 14:42:58.633 [planetinfo.record]: corona_shimmer = 0.278015 14:42:58.633 [planetinfo.record]: station_vector = 0.794 -0.285 0.536 14:42:58.633 [planetinfo.record]: station = coriolis 14:43:03.372 [PLANETINFO OVER]: Done 14:43:03.373 [PLANETINFO LOGGING]: 7 89 14:43:04.374 [planetinfo.record]: seed = 202 78 27 23 14:43:04.374 [planetinfo.record]: coordinates = 78 56 14:43:04.374 [planetinfo.record]: government = 1; 14:43:04.375 [planetinfo.record]: economy = 2; 14:43:04.375 [planetinfo.record]: techlevel = 8; 14:43:04.375 [planetinfo.record]: population = 36; 14:43:04.375 [planetinfo.record]: productivity = 11520; 14:43:04.375 [planetinfo.record]: name = "Tirige"; 14:43:04.375 [planetinfo.record]: inhabitant = "Human Colonial"; 14:43:04.375 [planetinfo.record]: inhabitants = "Human Colonials"; 14:43:04.375 [planetinfo.record]: description = "The planet Tirige is very noted for its unusual sit coms but plagued by frequent solar activity."; 14:43:04.390 [planetinfo.record]: air_color = 0.414381, 0.80443, 0.867621, 1; 14:43:04.390 [planetinfo.record]: cloud_alpha = 1.000000; 14:43:04.390 [planetinfo.record]: cloud_color = 0.353436, 0.605469, 0.0189209, 1; 14:43:04.390 [planetinfo.record]: cloud_fraction = 0.110000; 14:43:04.390 [planetinfo.record]: land_color = 0.457094, 0.0953906, 0.66, 1; 14:43:04.390 [planetinfo.record]: land_fraction = 0.290000; 14:43:04.390 [planetinfo.record]: polar_cloud_color = 0.571496, 0.772461, 0.30476, 1; 14:43:04.390 [planetinfo.record]: polar_land_color = 0.862214, 0.734248, 0.934, 1; 14:43:04.390 [planetinfo.record]: polar_sea_color = 0.936719, 0.930696, 0.88165, 1; 14:43:04.390 [planetinfo.record]: sea_color = 0.632812, 0.616536, 0.484003, 1; 14:43:04.390 [planetinfo.record]: rotation_speed = 0.001076 14:43:04.391 [planetinfo.record]: planet zpos = 421740.000000 14:43:04.391 [planetinfo.record]: sun_radius = 141602.488403 14:43:04.391 [planetinfo.record]: sun_vector = 0.092 0.880 -0.467 14:43:04.391 [planetinfo.record]: sun_distance = 937200 14:43:04.391 [planetinfo.record]: corona_flare = 0.033408 14:43:04.391 [planetinfo.record]: corona_hues = 0.531471 14:43:04.391 [planetinfo.record]: sun_color = 0.67428, 0.578971, 0.480996, 1 14:43:04.391 [planetinfo.record]: corona_shimmer = 1.229753 14:43:04.391 [planetinfo.record]: station_vector = -0.399 -0.480 0.781 14:43:04.391 [planetinfo.record]: station = coriolis 14:43:09.113 [PLANETINFO OVER]: Done 14:43:09.114 [PLANETINFO LOGGING]: 7 90 14:43:10.134 [planetinfo.record]: seed = 40 185 53 91 14:43:10.134 [planetinfo.record]: coordinates = 185 3 14:43:10.134 [planetinfo.record]: government = 5; 14:43:10.134 [planetinfo.record]: economy = 3; 14:43:10.134 [planetinfo.record]: techlevel = 8; 14:43:10.134 [planetinfo.record]: population = 41; 14:43:10.134 [planetinfo.record]: productivity = 20664; 14:43:10.134 [planetinfo.record]: name = "Anedin"; 14:43:10.134 [planetinfo.record]: inhabitant = "Human Colonial"; 14:43:10.134 [planetinfo.record]: inhabitants = "Human Colonials"; 14:43:10.134 [planetinfo.record]: description = "Anedin is a revolting little planet."; 14:43:10.136 [planetinfo.record]: air_color = 0.608749, 0.889598, 0.960627, 1; 14:43:10.136 [planetinfo.record]: cloud_alpha = 1.000000; 14:43:10.136 [planetinfo.record]: cloud_color = 0.611732, 0.884766, 0.442383, 1; 14:43:10.136 [planetinfo.record]: cloud_fraction = 0.610000; 14:43:10.136 [planetinfo.record]: land_color = 0.231105, 0.0567188, 0.66, 1; 14:43:10.136 [planetinfo.record]: land_fraction = 0.440000; 14:43:10.136 [planetinfo.record]: polar_cloud_color = 0.724918, 0.898145, 0.617474, 1; 14:43:10.136 [planetinfo.record]: polar_land_color = 0.782262, 0.720566, 0.934, 1; 14:43:10.137 [planetinfo.record]: polar_sea_color = 0.937891, 0.921359, 0.882203, 1; 14:43:10.137 [planetinfo.record]: sea_color = 0.621094, 0.577302, 0.473584, 1; 14:43:10.137 [planetinfo.record]: rotation_speed = 0.002843 14:43:10.137 [planetinfo.record]: planet zpos = 756210.000000 14:43:10.137 [planetinfo.record]: sun_radius = 114853.263702 14:43:10.137 [planetinfo.record]: sun_vector = 0.397 -0.703 0.590 14:43:10.137 [planetinfo.record]: sun_distance = 1221570 14:43:10.137 [planetinfo.record]: corona_flare = 0.020697 14:43:10.137 [planetinfo.record]: corona_hues = 0.571136 14:43:10.137 [planetinfo.record]: sun_color = 0.381159, 0.536898, 0.686353, 1 14:43:10.137 [planetinfo.record]: corona_shimmer = 0.368449 14:43:10.137 [planetinfo.record]: station_vector = -0.985 -0.080 0.156 14:43:10.137 [planetinfo.record]: station = coriolis 14:43:14.944 [PLANETINFO OVER]: Done 14:43:14.945 [PLANETINFO LOGGING]: 7 91 14:43:15.948 [planetinfo.record]: seed = 62 160 71 229 14:43:15.948 [planetinfo.record]: coordinates = 160 44 14:43:15.948 [planetinfo.record]: government = 7; 14:43:15.948 [planetinfo.record]: economy = 4; 14:43:15.948 [planetinfo.record]: techlevel = 7; 14:43:15.948 [planetinfo.record]: population = 40; 14:43:15.948 [planetinfo.record]: productivity = 21120; 14:43:15.948 [planetinfo.record]: name = "Ceenti"; 14:43:15.948 [planetinfo.record]: inhabitant = "Human Colonial"; 14:43:15.948 [planetinfo.record]: inhabitants = "Human Colonials"; 14:43:15.948 [planetinfo.record]: description = "The world Ceenti is reasonably noted for its inhabitants’ exceptional loathing of food blenders."; 14:43:15.966 [planetinfo.record]: air_color = 0.427426, 0.835752, 0.819383, 1; 14:43:15.966 [planetinfo.record]: cloud_alpha = 1.000000; 14:43:15.966 [planetinfo.record]: cloud_color = 0.509766, 0.461415, 0.0677032, 1; 14:43:15.966 [planetinfo.record]: cloud_fraction = 0.480000; 14:43:15.966 [planetinfo.record]: land_color = 0.145785, 0.0876563, 0.66, 1; 14:43:15.966 [planetinfo.record]: land_fraction = 0.610000; 14:43:15.966 [planetinfo.record]: polar_cloud_color = 0.729395, 0.686156, 0.334068, 1; 14:43:15.966 [planetinfo.record]: polar_land_color = 0.752077, 0.731512, 0.934, 1; 14:43:15.966 [planetinfo.record]: polar_sea_color = 0.941406, 0.938413, 0.893509, 1; 14:43:15.966 [planetinfo.record]: sea_color = 0.585938, 0.578485, 0.46669, 1; 14:43:15.967 [planetinfo.record]: rotation_speed = 0.000871 14:43:15.967 [planetinfo.record]: planet zpos = 553280.000000 14:43:15.967 [planetinfo.record]: sun_radius = 127276.713867 14:43:15.967 [planetinfo.record]: sun_vector = -0.772 0.246 0.587 14:43:15.967 [planetinfo.record]: sun_distance = 851200 14:43:15.967 [planetinfo.record]: corona_flare = 0.023552 14:43:15.967 [planetinfo.record]: corona_hues = 0.707726 14:43:15.967 [planetinfo.record]: sun_color = 0.791782, 0.515488, 0.343006, 1 14:43:15.967 [planetinfo.record]: corona_shimmer = 0.469321 14:43:15.967 [planetinfo.record]: station_vector = -0.218 0.525 0.823 14:43:15.967 [planetinfo.record]: station = coriolis 14:43:21.230 [PLANETINFO OVER]: Done 14:43:21.231 [PLANETINFO LOGGING]: 7 92 14:43:22.242 [planetinfo.record]: seed = 60 207 49 185 14:43:22.242 [planetinfo.record]: coordinates = 207 55 14:43:22.242 [planetinfo.record]: government = 7; 14:43:22.242 [planetinfo.record]: economy = 7; 14:43:22.242 [planetinfo.record]: techlevel = 7; 14:43:22.242 [planetinfo.record]: population = 43; 14:43:22.242 [planetinfo.record]: productivity = 11352; 14:43:22.242 [planetinfo.record]: name = "Orus"; 14:43:22.242 [planetinfo.record]: inhabitant = "Human Colonial"; 14:43:22.242 [planetinfo.record]: inhabitants = "Human Colonials"; 14:43:22.242 [planetinfo.record]: description = "The world Orus is notable for its unusual tropical forests and its exciting sit coms."; 14:43:22.260 [planetinfo.record]: air_color = 0.653659, 0.876727, 0.846892, 1; 14:43:22.260 [planetinfo.record]: cloud_alpha = 1.000000; 14:43:22.260 [planetinfo.record]: cloud_color = 0.632812, 0.592257, 0.528992, 1; 14:43:22.260 [planetinfo.record]: cloud_fraction = 0.310000; 14:43:22.260 [planetinfo.record]: land_color = 0.22194, 0.0438281, 0.66, 1; 14:43:22.260 [planetinfo.record]: land_fraction = 0.640000; 14:43:22.260 [planetinfo.record]: polar_cloud_color = 0.784766, 0.753332, 0.704296, 1; 14:43:22.260 [planetinfo.record]: polar_land_color = 0.77902, 0.716006, 0.934, 1; 14:43:22.260 [planetinfo.record]: polar_sea_color = 0.938281, 0.890201, 0.877348, 1; 14:43:22.260 [planetinfo.record]: sea_color = 0.617188, 0.490682, 0.456863, 1; 14:43:22.260 [planetinfo.record]: rotation_speed = 0.003201 14:43:22.260 [planetinfo.record]: planet zpos = 585970.000000 14:43:22.260 [planetinfo.record]: sun_radius = 129954.544983 14:43:22.260 [planetinfo.record]: sun_vector = -0.044 0.998 -0.044 14:43:22.260 [planetinfo.record]: sun_distance = 1012130 14:43:22.260 [planetinfo.record]: corona_flare = 0.056567 14:43:22.260 [planetinfo.record]: corona_hues = 0.705650 14:43:22.260 [planetinfo.record]: sun_color = 0.395172, 0.72878, 0.836066, 1 14:43:22.260 [planetinfo.record]: corona_shimmer = 0.583206 14:43:22.260 [planetinfo.record]: station_vector = 0.144 -0.937 0.319 14:43:22.261 [planetinfo.record]: station = coriolis 14:43:27.226 [PLANETINFO OVER]: Done 14:43:27.227 [PLANETINFO LOGGING]: 7 93 14:43:28.231 [planetinfo.record]: seed = 242 193 179 202 14:43:28.231 [planetinfo.record]: coordinates = 193 72 14:43:28.231 [planetinfo.record]: government = 6; 14:43:28.231 [planetinfo.record]: economy = 0; 14:43:28.231 [planetinfo.record]: techlevel = 11; 14:43:28.231 [planetinfo.record]: population = 51; 14:43:28.231 [planetinfo.record]: productivity = 40800; 14:43:28.231 [planetinfo.record]: name = "Arlale"; 14:43:28.231 [planetinfo.record]: inhabitant = "Small Bug-Eyed Lobster"; 14:43:28.231 [planetinfo.record]: inhabitants = "Small Bug-Eyed Lobsters"; 14:43:28.231 [planetinfo.record]: description = "The world Arlale is fabled for its ancient Arlaleian Enalno banana plantations and its hoopy night life."; 14:43:28.235 [planetinfo.record]: air_color = 0.546408, 0.958676, 0.871361, 1; 14:43:28.236 [planetinfo.record]: cloud_alpha = 1.000000; 14:43:28.236 [planetinfo.record]: cloud_color = 0.878906, 0.499105, 0.271225, 1; 14:43:28.236 [planetinfo.record]: cloud_fraction = 0.430000; 14:43:28.236 [planetinfo.record]: land_color = 0.373828, 0.66, 0.3783, 1; 14:43:28.236 [planetinfo.record]: land_fraction = 0.680000; 14:43:28.236 [planetinfo.record]: polar_cloud_color = 0.895508, 0.653649, 0.508533, 1; 14:43:28.236 [planetinfo.record]: polar_land_color = 0.832756, 0.934, 0.834338, 1; 14:43:28.236 [planetinfo.record]: polar_sea_color = 0.93457, 0.904196, 0.87981, 1; 14:43:28.236 [planetinfo.record]: sea_color = 0.654297, 0.569235, 0.500946, 1; 14:43:28.236 [planetinfo.record]: rotation_speed = 0.000551 14:43:28.236 [planetinfo.record]: planet zpos = 779660.000000 14:43:28.236 [planetinfo.record]: sun_radius = 106482.821808 14:43:28.236 [planetinfo.record]: sun_vector = -0.156 -0.900 -0.408 14:43:28.236 [planetinfo.record]: sun_distance = 1280870 14:43:28.237 [planetinfo.record]: corona_flare = 0.029364 14:43:28.237 [planetinfo.record]: corona_hues = 0.790359 14:43:28.237 [planetinfo.record]: sun_color = 0.601057, 0.619088, 0.707086, 1 14:43:28.237 [planetinfo.record]: corona_shimmer = 0.358101 14:43:28.237 [planetinfo.record]: station_vector = -0.309 -0.777 0.549 14:43:28.237 [planetinfo.record]: station = dodecahedron 14:43:33.363 [PLANETINFO OVER]: Done 14:43:33.364 [PLANETINFO LOGGING]: 7 94 14:43:34.367 [planetinfo.record]: seed = 208 1 237 56 14:43:34.367 [planetinfo.record]: coordinates = 1 97 14:43:34.367 [planetinfo.record]: government = 2; 14:43:34.367 [planetinfo.record]: economy = 1; 14:43:34.367 [planetinfo.record]: techlevel = 8; 14:43:34.368 [planetinfo.record]: population = 36; 14:43:34.368 [planetinfo.record]: productivity = 15552; 14:43:34.368 [planetinfo.record]: name = "Edtetiin"; 14:43:34.368 [planetinfo.record]: inhabitant = "Red Slimy Rodent"; 14:43:34.368 [planetinfo.record]: inhabitants = "Red Slimy Rodents"; 14:43:34.368 [planetinfo.record]: description = "The planet Edtetiin is most famous for its pink oceans and its unusual sit coms."; 14:43:34.376 [planetinfo.record]: air_color = 0.699725, 0.828533, 0.905344, 1; 14:43:34.376 [planetinfo.record]: cloud_alpha = 1.000000; 14:43:34.376 [planetinfo.record]: cloud_color = 0.65979, 0.71875, 0.667621, 1; 14:43:34.376 [planetinfo.record]: cloud_fraction = 0.210000; 14:43:34.376 [planetinfo.record]: land_color = 0.512412, 0.583984, 0.0114059, 1; 14:43:34.376 [planetinfo.record]: land_fraction = 0.340000; 14:43:34.376 [planetinfo.record]: polar_cloud_color = 0.78122, 0.823438, 0.786827, 1; 14:43:34.376 [planetinfo.record]: polar_land_color = 0.912751, 0.941602, 0.710799, 1; 14:43:34.377 [planetinfo.record]: polar_sea_color = 0.91, 0.8645, 0.8918, 1; 14:43:34.377 [planetinfo.record]: sea_color = 0.9, 0.72, 0.828, 1; 14:43:34.377 [planetinfo.record]: rotation_speed = 0.000924 14:43:34.377 [planetinfo.record]: planet zpos = 632450.000000 14:43:34.377 [planetinfo.record]: sun_radius = 131766.850281 14:43:34.377 [planetinfo.record]: sun_vector = 0.696 0.127 -0.707 14:43:34.377 [planetinfo.record]: sun_distance = 1070300 14:43:34.377 [planetinfo.record]: corona_flare = 0.081190 14:43:34.377 [planetinfo.record]: corona_hues = 0.696419 14:43:34.377 [planetinfo.record]: sun_color = 0.23534, 0.443219, 0.659424, 1 14:43:34.377 [planetinfo.record]: corona_shimmer = 0.569710 14:43:34.378 [planetinfo.record]: station_vector = 0.559 0.311 0.768 14:43:34.378 [planetinfo.record]: station = coriolis 14:43:39.872 [PLANETINFO OVER]: Done 14:43:39.872 [PLANETINFO LOGGING]: 7 95 14:43:40.877 [planetinfo.record]: seed = 230 172 223 32 14:43:40.877 [planetinfo.record]: coordinates = 172 215 14:43:40.877 [planetinfo.record]: government = 4; 14:43:40.877 [planetinfo.record]: economy = 7; 14:43:40.877 [planetinfo.record]: techlevel = 2; 14:43:40.877 [planetinfo.record]: population = 20; 14:43:40.877 [planetinfo.record]: productivity = 3840; 14:43:40.877 [planetinfo.record]: name = "Ceened"; 14:43:40.878 [planetinfo.record]: inhabitant = "Large Red Bony Lobster"; 14:43:40.878 [planetinfo.record]: inhabitants = "Large Red Bony Lobsters"; 14:43:40.878 [planetinfo.record]: description = "This world is plagued by frequent earthquakes."; 14:43:40.891 [planetinfo.record]: air_color = 0.63846, 0.821628, 0.967781, 1; 14:43:40.891 [planetinfo.record]: cloud_alpha = 1.000000; 14:43:40.891 [planetinfo.record]: cloud_color = 0.523926, 0.90625, 0.65535, 1; 14:43:40.891 [planetinfo.record]: cloud_fraction = 0.330000; 14:43:40.891 [planetinfo.record]: land_color = 0.617188, 0.412262, 0.537138, 1; 14:43:40.891 [planetinfo.record]: land_fraction = 0.370000; 14:43:40.891 [planetinfo.record]: polar_cloud_color = 0.668448, 0.907812, 0.750729, 1; 14:43:40.891 [planetinfo.record]: polar_land_color = 0.938281, 0.860397, 0.907858, 1; 14:43:40.891 [planetinfo.record]: polar_sea_color = 0.87273, 0.926953, 0.876119, 1; 14:43:40.891 [planetinfo.record]: sea_color = 0.55955, 0.730469, 0.570233, 1; 14:43:40.891 [planetinfo.record]: rotation_speed = 0.000157 14:43:40.891 [planetinfo.record]: planet zpos = 298800.000000 14:43:40.891 [planetinfo.record]: sun_radius = 81318.317871 14:43:40.891 [planetinfo.record]: sun_vector = -0.307 -0.892 0.332 14:43:40.891 [planetinfo.record]: sun_distance = 507960 14:43:40.891 [planetinfo.record]: corona_flare = 0.029512 14:43:40.891 [planetinfo.record]: corona_hues = 0.909775 14:43:40.891 [planetinfo.record]: sun_color = 0.5525, 0.630946, 0.663824, 1 14:43:40.891 [planetinfo.record]: corona_shimmer = 0.640025 14:43:40.893 [planetinfo.record]: station_vector = 0.274 -0.193 0.942 14:43:40.893 [planetinfo.record]: station = coriolis 14:43:45.363 [PLANETINFO OVER]: Done 14:43:45.363 [PLANETINFO LOGGING]: 7 96 14:43:46.366 [planetinfo.record]: seed = 228 56 233 80 14:43:46.366 [planetinfo.record]: coordinates = 56 114 14:43:46.366 [planetinfo.record]: government = 4; 14:43:46.366 [planetinfo.record]: economy = 2; 14:43:46.366 [planetinfo.record]: techlevel = 7; 14:43:46.366 [planetinfo.record]: population = 35; 14:43:46.366 [planetinfo.record]: productivity = 17920; 14:43:46.366 [planetinfo.record]: name = "Ertebira"; 14:43:46.366 [planetinfo.record]: inhabitant = "Yellow Horned Lizard"; 14:43:46.366 [planetinfo.record]: inhabitants = "Yellow Horned Lizards"; 14:43:46.366 [planetinfo.record]: description = "The world Ertebira is mildly fabled for the Ertebiraian edible poet but scourged by deadly civil war."; 14:43:46.380 [planetinfo.record]: air_color = 0.504029, 0.472906, 0.929408, 1; 14:43:46.380 [planetinfo.record]: cloud_alpha = 1.000000; 14:43:46.380 [planetinfo.record]: cloud_color = 0.250282, 0.098877, 0.791016, 1; 14:43:46.380 [planetinfo.record]: cloud_fraction = 0.510000; 14:43:46.380 [planetinfo.record]: land_color = 0.587891, 0.550591, 0.525887, 1; 14:43:46.380 [planetinfo.record]: land_fraction = 0.650000; 14:43:46.380 [planetinfo.record]: polar_cloud_color = 0.490253, 0.387856, 0.855957, 1; 14:43:46.380 [planetinfo.record]: polar_land_color = 0.941211, 0.926282, 0.916394, 1; 14:43:46.380 [planetinfo.record]: polar_sea_color = 0.83711, 0.9125, 0.806458, 1; 14:43:46.380 [planetinfo.record]: sea_color = 0.585835, 0.875, 0.468262, 1; 14:43:46.380 [planetinfo.record]: rotation_speed = 0.001256 14:43:46.380 [planetinfo.record]: planet zpos = 287200.000000 14:43:46.380 [planetinfo.record]: sun_radius = 63280.761719 14:43:46.380 [planetinfo.record]: sun_vector = 0.206 -0.877 -0.435 14:43:46.380 [planetinfo.record]: sun_distance = 603120 14:43:46.380 [planetinfo.record]: corona_flare = 0.025090 14:43:46.381 [planetinfo.record]: corona_hues = 0.825409 14:43:46.381 [planetinfo.record]: sun_color = 0.691864, 0.617551, 0.328202, 1 14:43:46.381 [planetinfo.record]: corona_shimmer = 0.366857 14:43:46.381 [planetinfo.record]: station_vector = 0.393 0.465 0.793 14:43:46.381 [planetinfo.record]: station = coriolis 14:43:50.763 [PLANETINFO OVER]: Done 14:43:50.763 [PLANETINFO LOGGING]: 7 97 14:43:51.766 [planetinfo.record]: seed = 26 212 75 87 14:43:51.766 [planetinfo.record]: coordinates = 212 134 14:43:51.766 [planetinfo.record]: government = 3; 14:43:51.766 [planetinfo.record]: economy = 6; 14:43:51.766 [planetinfo.record]: techlevel = 3; 14:43:51.766 [planetinfo.record]: population = 22; 14:43:51.766 [planetinfo.record]: productivity = 4928; 14:43:51.766 [planetinfo.record]: name = "Tiatisbi"; 14:43:51.766 [planetinfo.record]: inhabitant = "Human Colonial"; 14:43:51.766 [planetinfo.record]: inhabitants = "Human Colonials"; 14:43:51.766 [planetinfo.record]: description = "Tiatisbi is very fabled for the Tiatisbiian edible grub."; 14:43:51.792 [planetinfo.record]: air_color = 0.39389, 0.833801, 0.702865, 1; 14:43:51.792 [planetinfo.record]: cloud_alpha = 1.000000; 14:43:51.792 [planetinfo.record]: cloud_color = 0.503906, 0.0660024, 0.00787354, 1; 14:43:51.792 [planetinfo.record]: cloud_fraction = 0.160000; 14:43:51.792 [planetinfo.record]: land_color = 0.170398, 0.0515625, 0.66, 1; 14:43:51.792 [planetinfo.record]: land_fraction = 0.670000; 14:43:51.792 [planetinfo.record]: polar_cloud_color = 0.726758, 0.332029, 0.279631, 1; 14:43:51.792 [planetinfo.record]: polar_land_color = 0.760785, 0.718742, 0.934, 1; 14:43:51.792 [planetinfo.record]: polar_sea_color = 0.921199, 0.9375, 0.888977, 1; 14:43:51.792 [planetinfo.record]: sea_color = 0.581532, 0.625, 0.495605, 1; 14:43:51.792 [planetinfo.record]: rotation_speed = 0.004876 14:43:51.792 [planetinfo.record]: planet zpos = 482000.000000 14:43:51.792 [planetinfo.record]: sun_radius = 100142.089844 14:43:51.792 [planetinfo.record]: sun_vector = -0.836 -0.544 0.068 14:43:51.792 [planetinfo.record]: sun_distance = 867600 14:43:51.792 [planetinfo.record]: corona_flare = 0.038771 14:43:51.792 [planetinfo.record]: corona_hues = 0.726448 14:43:51.792 [planetinfo.record]: sun_color = 0.379895, 0.673523, 0.697751, 1 14:43:51.792 [planetinfo.record]: corona_shimmer = 0.710479 14:43:51.793 [planetinfo.record]: station_vector = -0.496 0.321 0.807 14:43:51.793 [planetinfo.record]: station = coriolis 14:43:56.221 [PLANETINFO OVER]: Done 14:43:56.221 [PLANETINFO LOGGING]: 7 98 14:43:57.230 [planetinfo.record]: seed = 120 230 165 117 14:43:57.230 [planetinfo.record]: coordinates = 230 221 14:43:57.230 [planetinfo.record]: government = 7; 14:43:57.230 [planetinfo.record]: economy = 5; 14:43:57.230 [planetinfo.record]: techlevel = 8; 14:43:57.230 [planetinfo.record]: population = 45; 14:43:57.230 [planetinfo.record]: productivity = 19800; 14:43:57.230 [planetinfo.record]: name = "Laorlaza"; 14:43:57.230 [planetinfo.record]: inhabitant = "Blue Bony Bird"; 14:43:57.230 [planetinfo.record]: inhabitants = "Blue Bony Birds"; 14:43:57.230 [planetinfo.record]: description = "The planet Laorlaza is reasonably noted for its inhabitants’ exceptional love for tourists and the Laorlazaian edible arts graduate."; 14:43:57.238 [planetinfo.record]: air_color = 0.608163, 0.925506, 0.9004, 1; 14:43:57.238 [planetinfo.record]: cloud_alpha = 1.000000; 14:43:57.238 [planetinfo.record]: cloud_color = 0.779297, 0.703431, 0.444443, 1; 14:43:57.238 [planetinfo.record]: cloud_fraction = 0.190000; 14:43:57.238 [planetinfo.record]: land_color = 0.423014, 0.66, 0.373828, 1; 14:43:57.238 [planetinfo.record]: land_fraction = 0.680000; 14:43:57.238 [planetinfo.record]: polar_cloud_color = 0.850684, 0.798924, 0.622229, 1; 14:43:57.238 [planetinfo.record]: polar_land_color = 0.850157, 0.934, 0.832756, 1; 14:43:57.238 [planetinfo.record]: polar_sea_color = 0.942773, 0.8936, 0.887073, 1; 14:43:57.238 [planetinfo.record]: sea_color = 0.572266, 0.452872, 0.437023, 1; 14:43:57.238 [planetinfo.record]: rotation_speed = 0.004112 14:43:57.238 [planetinfo.record]: planet zpos = 605640.000000 14:43:57.238 [planetinfo.record]: sun_radius = 134733.354492 14:43:57.239 [planetinfo.record]: sun_vector = 0.019 0.980 -0.198 14:43:57.239 [planetinfo.record]: sun_distance = 1038240 14:43:57.239 [planetinfo.record]: corona_flare = 0.026201 14:43:57.239 [planetinfo.record]: corona_hues = 0.861191 14:43:57.239 [planetinfo.record]: sun_color = 0.314455, 0.522777, 0.671585, 1 14:43:57.239 [planetinfo.record]: corona_shimmer = 0.437432 14:43:57.239 [planetinfo.record]: station_vector = 0.455 -0.888 0.066 14:43:57.239 [planetinfo.record]: station = coriolis 14:44:02.192 [PLANETINFO OVER]: Done 14:44:02.193 [PLANETINFO LOGGING]: 7 99 14:44:03.212 [planetinfo.record]: seed = 142 68 119 19 14:44:03.212 [planetinfo.record]: coordinates = 68 149 14:44:03.212 [planetinfo.record]: government = 1; 14:44:03.212 [planetinfo.record]: economy = 7; 14:44:03.212 [planetinfo.record]: techlevel = 1; 14:44:03.212 [planetinfo.record]: population = 13; 14:44:03.212 [planetinfo.record]: productivity = 1560; 14:44:03.212 [planetinfo.record]: name = "Bedice"; 14:44:03.212 [planetinfo.record]: inhabitant = "Human Colonial"; 14:44:03.212 [planetinfo.record]: inhabitants = "Human Colonials"; 14:44:03.212 [planetinfo.record]: description = "The planet Bedice is well known for its inhabitants’ ancient mating traditions but cursed by deadly civil war."; 14:44:03.234 [planetinfo.record]: air_color = 0.624109, 0.898828, 0.940465, 1; 14:44:03.234 [planetinfo.record]: cloud_alpha = 1.000000; 14:44:03.234 [planetinfo.record]: cloud_color = 0.686882, 0.824219, 0.48616, 1; 14:44:03.234 [planetinfo.record]: cloud_fraction = 0.420000; 14:44:03.234 [planetinfo.record]: land_color = 0.535156, 0.430634, 0.511475, 1; 14:44:03.234 [planetinfo.record]: land_fraction = 0.630000; 14:44:03.234 [planetinfo.record]: polar_cloud_color = 0.780202, 0.870898, 0.647646, 1; 14:44:03.234 [planetinfo.record]: polar_land_color = 0.946484, 0.900269, 0.936014, 1; 14:44:03.234 [planetinfo.record]: polar_sea_color = 0.870575, 0.919336, 0.840331, 1; 14:44:03.234 [planetinfo.record]: sea_color = 0.635505, 0.806641, 0.529358, 1; 14:44:03.235 [planetinfo.record]: rotation_speed = 0.004511 14:44:03.235 [planetinfo.record]: planet zpos = 474760.000000 14:44:03.236 [planetinfo.record]: sun_radius = 87287.235718 14:44:03.236 [planetinfo.record]: sun_vector = -0.542 0.322 0.776 14:44:03.236 [planetinfo.record]: sun_distance = 657360 14:44:03.236 [planetinfo.record]: corona_flare = 0.028120 14:44:03.236 [planetinfo.record]: corona_hues = 0.745468 14:44:03.236 [planetinfo.record]: sun_color = 0.75972, 0.753724, 0.351235, 1 14:44:03.237 [planetinfo.record]: corona_shimmer = 0.455846 14:44:03.237 [planetinfo.record]: station_vector = 0.719 -0.671 0.181 14:44:03.237 [planetinfo.record]: station = coriolis 14:44:08.016 [PLANETINFO OVER]: Done 14:44:08.028 [PLANETINFO LOGGING]: 7 100 14:44:09.019 [planetinfo.record]: seed = 140 70 161 121 14:44:09.019 [planetinfo.record]: coordinates = 70 69 14:44:09.019 [planetinfo.record]: government = 1; 14:44:09.019 [planetinfo.record]: economy = 7; 14:44:09.019 [planetinfo.record]: techlevel = 3; 14:44:09.019 [planetinfo.record]: population = 21; 14:44:09.019 [planetinfo.record]: productivity = 2520; 14:44:09.019 [planetinfo.record]: name = "Orcece"; 14:44:09.019 [planetinfo.record]: inhabitant = "Blue Bony Bird"; 14:44:09.020 [planetinfo.record]: inhabitants = "Blue Bony Birds"; 14:44:09.020 [planetinfo.record]: description = "This planet is reasonably famous for its inhabitants’ exceptional love for tourists."; 14:44:09.028 [planetinfo.record]: air_color = 0.739698, 0.74015, 0.958676, 1; 14:44:09.028 [planetinfo.record]: cloud_alpha = 1.000000; 14:44:09.028 [planetinfo.record]: cloud_color = 0.803965, 0.803375, 0.878906, 1; 14:44:09.028 [planetinfo.record]: cloud_fraction = 0.240000; 14:44:09.028 [planetinfo.record]: land_color = 0.39115, 0.66, 0.293906, 1; 14:44:09.028 [planetinfo.record]: land_fraction = 0.710000; 14:44:09.028 [planetinfo.record]: polar_cloud_color = 0.847785, 0.847409, 0.895508, 1; 14:44:09.028 [planetinfo.record]: polar_land_color = 0.838884, 0.934, 0.80448, 1; 14:44:09.028 [planetinfo.record]: polar_sea_color = 0.942187, 0.899991, 0.887073, 1; 14:44:09.028 [planetinfo.record]: sea_color = 0.578125, 0.474557, 0.442853, 1; 14:44:09.028 [planetinfo.record]: rotation_speed = 0.004402 14:44:09.028 [planetinfo.record]: planet zpos = 726600.000000 14:44:09.028 [planetinfo.record]: sun_radius = 143168.481445 14:44:09.028 [planetinfo.record]: sun_vector = -0.322 -0.942 -0.098 14:44:09.028 [planetinfo.record]: sun_distance = 882300 14:44:09.028 [planetinfo.record]: corona_flare = 0.042735 14:44:09.028 [planetinfo.record]: corona_hues = 0.536163 14:44:09.028 [planetinfo.record]: sun_color = 0.82576, 0.631667, 0.32295, 1 14:44:09.028 [planetinfo.record]: corona_shimmer = 0.382375 14:44:09.029 [planetinfo.record]: station_vector = -0.975 -0.111 0.195 14:44:09.029 [planetinfo.record]: station = coriolis 14:44:14.184 [PLANETINFO OVER]: Done 14:44:14.185 [PLANETINFO LOGGING]: 7 101 14:44:15.189 [planetinfo.record]: seed = 66 69 227 16 14:44:15.189 [planetinfo.record]: coordinates = 69 197 14:44:15.189 [planetinfo.record]: government = 0; 14:44:15.189 [planetinfo.record]: economy = 7; 14:44:15.189 [planetinfo.record]: techlevel = 1; 14:44:15.189 [planetinfo.record]: population = 12; 14:44:15.189 [planetinfo.record]: productivity = 1152; 14:44:15.189 [planetinfo.record]: name = "Erteenon"; 14:44:15.189 [planetinfo.record]: inhabitant = "Green Slimy Rodent"; 14:44:15.189 [planetinfo.record]: inhabitants = "Green Slimy Rodents"; 14:44:15.189 [planetinfo.record]: description = "The planet Erteenon is well known for its inhabitants’ ancient mating traditions but cursed by vicious killer monkeys."; 14:44:15.194 [planetinfo.record]: air_color = 0.723137, 0.778715, 0.937213, 1; 14:44:15.194 [planetinfo.record]: cloud_alpha = 1.000000; 14:44:15.194 [planetinfo.record]: cloud_color = 0.744461, 0.798049, 0.814453, 1; 14:44:15.194 [planetinfo.record]: cloud_fraction = 0.580000; 14:44:15.194 [planetinfo.record]: land_color = 0.617289, 0.619141, 0.614304, 1; 14:44:15.194 [planetinfo.record]: land_fraction = 0.620000; 14:44:15.194 [planetinfo.record]: polar_cloud_color = 0.819963, 0.855596, 0.866504, 1; 14:44:15.194 [planetinfo.record]: polar_land_color = 0.937385, 0.938086, 0.936254, 1; 14:44:15.194 [planetinfo.record]: polar_sea_color = 0.856726, 0.912891, 0.800562, 1; 14:44:15.194 [planetinfo.record]: sea_color = 0.656723, 0.871094, 0.442352, 1; 14:44:15.194 [planetinfo.record]: rotation_speed = 0.004197 14:44:15.195 [planetinfo.record]: planet zpos = 375050.000000 14:44:15.195 [planetinfo.record]: sun_radius = 95562.543488 14:44:15.195 [planetinfo.record]: sun_vector = -0.229 0.831 -0.507 14:44:15.195 [planetinfo.record]: sun_distance = 548150 14:44:15.195 [planetinfo.record]: corona_flare = 0.068773 14:44:15.195 [planetinfo.record]: corona_hues = 0.532120 14:44:15.195 [planetinfo.record]: sun_color = 0.251764, 0.359945, 0.738205, 1 14:44:15.195 [planetinfo.record]: corona_shimmer = 0.718864 14:44:15.195 [planetinfo.record]: station_vector = -0.799 0.278 0.533 14:44:15.195 [planetinfo.record]: station = coriolis 14:44:20.020 [PLANETINFO OVER]: Done 14:44:20.020 [PLANETINFO LOGGING]: 7 102 14:44:21.043 [planetinfo.record]: seed = 32 159 93 45 14:44:21.043 [planetinfo.record]: coordinates = 159 114 14:44:21.043 [planetinfo.record]: government = 4; 14:44:21.043 [planetinfo.record]: economy = 2; 14:44:21.043 [planetinfo.record]: techlevel = 10; 14:44:21.043 [planetinfo.record]: population = 47; 14:44:21.043 [planetinfo.record]: productivity = 24064; 14:44:21.043 [planetinfo.record]: name = "Dirima"; 14:44:21.043 [planetinfo.record]: inhabitant = "Human Colonial"; 14:44:21.043 [planetinfo.record]: inhabitants = "Human Colonials"; 14:44:21.043 [planetinfo.record]: description = "This world is plagued by deadly earthquakes."; 14:44:21.062 [planetinfo.record]: air_color = 0.660064, 0.859816, 0.817511, 1; 14:44:21.062 [planetinfo.record]: cloud_alpha = 1.000000; 14:44:21.062 [planetinfo.record]: cloud_color = 0.582031, 0.546507, 0.525192, 1; 14:44:21.062 [planetinfo.record]: cloud_fraction = 0.400000; 14:44:21.062 [planetinfo.record]: land_color = 0.28875, 0.66, 0.51208, 1; 14:44:21.062 [planetinfo.record]: land_fraction = 0.360000; 14:44:21.063 [planetinfo.record]: polar_cloud_color = 0.761914, 0.732849, 0.715411, 1; 14:44:21.063 [planetinfo.record]: polar_land_color = 0.802656, 0.934, 0.881668, 1; 14:44:21.063 [planetinfo.record]: polar_sea_color = 0.948242, 0.904268, 0.899719, 1; 14:44:21.063 [planetinfo.record]: sea_color = 0.517578, 0.421568, 0.411636, 1; 14:44:21.063 [planetinfo.record]: rotation_speed = 0.002193 14:44:21.063 [planetinfo.record]: planet zpos = 630300.000000 14:44:21.063 [planetinfo.record]: sun_radius = 176831.388245 14:44:21.063 [planetinfo.record]: sun_vector = -0.869 0.446 -0.212 14:44:21.063 [planetinfo.record]: sun_distance = 1386660 14:44:21.063 [planetinfo.record]: corona_flare = 0.073848 14:44:21.063 [planetinfo.record]: corona_hues = 0.770409 14:44:21.063 [planetinfo.record]: sun_color = 0.805197, 0.682193, 0.53374, 1 14:44:21.063 [planetinfo.record]: corona_shimmer = 0.291398 14:44:21.063 [planetinfo.record]: station_vector = 0.547 -0.619 0.564 14:44:21.063 [planetinfo.record]: station = coriolis 14:44:26.100 [PLANETINFO OVER]: Done 14:44:26.100 [PLANETINFO LOGGING]: 7 103 14:44:27.111 [planetinfo.record]: seed = 54 119 15 193 14:44:27.111 [planetinfo.record]: coordinates = 119 11 14:44:27.111 [planetinfo.record]: government = 6; 14:44:27.111 [planetinfo.record]: economy = 3; 14:44:27.111 [planetinfo.record]: techlevel = 10; 14:44:27.111 [planetinfo.record]: population = 50; 14:44:27.111 [planetinfo.record]: productivity = 28000; 14:44:27.111 [planetinfo.record]: name = "Legean"; 14:44:27.111 [planetinfo.record]: inhabitant = "Human Colonial"; 14:44:27.111 [planetinfo.record]: inhabitants = "Human Colonials"; 14:44:27.111 [planetinfo.record]: description = "The planet Legean is famous for its inhabitants’ ancient loathing of discos but plagued by unpredictable civil war."; 14:44:27.117 [planetinfo.record]: air_color = 0.771066, 0.575829, 0.910547, 1; 14:44:27.118 [planetinfo.record]: cloud_alpha = 1.000000; 14:44:27.118 [planetinfo.record]: cloud_color = 0.734375, 0.364319, 0.451051, 1; 14:44:27.118 [planetinfo.record]: cloud_fraction = 0.470000; 14:44:27.118 [planetinfo.record]: land_color = 0.66, 0.657422, 0.658389, 1; 14:44:27.118 [planetinfo.record]: land_fraction = 0.320000; 14:44:27.118 [planetinfo.record]: polar_cloud_color = 0.830469, 0.56892, 0.63022, 1; 14:44:27.118 [planetinfo.record]: polar_land_color = 0.934, 0.933088, 0.93343, 1; 14:44:27.118 [planetinfo.record]: polar_sea_color = 0.809882, 0.946289, 0.760543, 1; 14:44:27.118 [planetinfo.record]: sea_color = 0.227413, 0.537109, 0.115395, 1; 14:44:27.118 [planetinfo.record]: rotation_speed = 0.003973 14:44:27.118 [planetinfo.record]: planet zpos = 382920.000000 14:44:27.118 [planetinfo.record]: sun_radius = 90138.348999 14:44:27.118 [planetinfo.record]: sun_vector = -0.238 -0.952 0.193 14:44:27.118 [planetinfo.record]: sun_distance = 765840 14:44:27.118 [planetinfo.record]: corona_flare = 0.009729 14:44:27.118 [planetinfo.record]: corona_hues = 0.501411 14:44:27.118 [planetinfo.record]: sun_color = 0.709857, 0.598714, 0.500754, 1 14:44:27.118 [planetinfo.record]: corona_shimmer = 0.548097 14:44:27.119 [planetinfo.record]: station_vector = 0.824 0.103 0.557 14:44:27.119 [planetinfo.record]: station = coriolis 14:44:32.001 [PLANETINFO OVER]: Done 14:44:32.002 [PLANETINFO LOGGING]: 7 104 14:44:33.005 [planetinfo.record]: seed = 52 128 89 63 14:44:33.005 [planetinfo.record]: coordinates = 128 123 14:44:33.005 [planetinfo.record]: government = 6; 14:44:33.005 [planetinfo.record]: economy = 3; 14:44:33.005 [planetinfo.record]: techlevel = 7; 14:44:33.005 [planetinfo.record]: population = 38; 14:44:33.005 [planetinfo.record]: productivity = 21280; 14:44:33.005 [planetinfo.record]: name = "Onanqu"; 14:44:33.006 [planetinfo.record]: inhabitant = "Human Colonial"; 14:44:33.006 [planetinfo.record]: inhabitants = "Human Colonials"; 14:44:33.006 [planetinfo.record]: description = "This planet is notable for the Onanquian edible arts graduate and its fabulous cuisine."; 14:44:33.023 [planetinfo.record]: air_color = 0.677735, 0.833763, 0.926807, 1; 14:44:33.023 [planetinfo.record]: cloud_alpha = 1.000000; 14:44:33.023 [planetinfo.record]: cloud_color = 0.621056, 0.783203, 0.642591, 1; 14:44:33.023 [planetinfo.record]: cloud_fraction = 0.240000; 14:44:33.023 [planetinfo.record]: land_color = 0.655065, 0.0283594, 0.66, 1; 14:44:33.023 [planetinfo.record]: land_fraction = 0.710000; 14:44:33.023 [planetinfo.record]: polar_cloud_color = 0.74214, 0.852441, 0.75679, 1; 14:44:33.023 [planetinfo.record]: polar_land_color = 0.932254, 0.710533, 0.934, 1; 14:44:33.023 [planetinfo.record]: polar_sea_color = 0.939258, 0.924686, 0.885966, 1; 14:44:33.023 [planetinfo.record]: sea_color = 0.607422, 0.569727, 0.469566, 1; 14:44:33.024 [planetinfo.record]: rotation_speed = 0.002509 14:44:33.024 [planetinfo.record]: planet zpos = 881920.000000 14:44:33.024 [planetinfo.record]: sun_radius = 160006.171875 14:44:33.024 [planetinfo.record]: sun_vector = -0.128 0.897 -0.424 14:44:33.024 [planetinfo.record]: sun_distance = 1085440 14:44:33.024 [planetinfo.record]: corona_flare = 0.002260 14:44:33.024 [planetinfo.record]: corona_hues = 0.701057 14:44:33.024 [planetinfo.record]: sun_color = 0.276036, 0.303111, 0.730914, 1 14:44:33.024 [planetinfo.record]: corona_shimmer = 0.536767 14:44:33.024 [planetinfo.record]: station_vector = -0.516 -0.844 0.149 14:44:33.024 [planetinfo.record]: station = coriolis 14:44:37.678 [PLANETINFO OVER]: Done 14:44:37.679 [PLANETINFO LOGGING]: 7 105 14:44:38.699 [planetinfo.record]: seed = 106 117 123 171 14:44:38.699 [planetinfo.record]: coordinates = 117 250 14:44:38.699 [planetinfo.record]: government = 5; 14:44:38.700 [planetinfo.record]: economy = 2; 14:44:38.702 [planetinfo.record]: techlevel = 9; 14:44:38.702 [planetinfo.record]: population = 44; 14:44:38.702 [planetinfo.record]: productivity = 25344; 14:44:38.702 [planetinfo.record]: name = "Maantege"; 14:44:38.702 [planetinfo.record]: inhabitant = "Human Colonial"; 14:44:38.702 [planetinfo.record]: inhabitants = "Human Colonials"; 14:44:38.702 [planetinfo.record]: description = "Maantege is cursed by killer mountain goats."; 14:44:38.741 [planetinfo.record]: air_color = 0.792354, 0.58412, 0.910547, 1; 14:44:38.741 [planetinfo.record]: cloud_alpha = 1.000000; 14:44:38.741 [planetinfo.record]: cloud_color = 0.734375, 0.384399, 0.409007, 1; 14:44:38.741 [planetinfo.record]: cloud_fraction = 0.150000; 14:44:38.742 [planetinfo.record]: land_color = 0.652266, 0.66, 0.654381, 1; 14:44:38.742 [planetinfo.record]: land_fraction = 0.570000; 14:44:38.742 [planetinfo.record]: polar_cloud_color = 0.830469, 0.583112, 0.600505, 1; 14:44:38.742 [planetinfo.record]: polar_land_color = 0.931264, 0.934, 0.932012, 1; 14:44:38.751 [planetinfo.record]: polar_sea_color = 0.749077, 0.938867, 0.836558, 1; 14:44:38.752 [planetinfo.record]: sea_color = 0.117012, 0.611328, 0.344861, 1; 14:44:38.752 [planetinfo.record]: rotation_speed = 0.003651 14:44:38.752 [planetinfo.record]: planet zpos = 689880.000000 14:44:38.752 [planetinfo.record]: sun_radius = 130094.634705 14:44:38.752 [planetinfo.record]: sun_vector = -0.024 0.613 -0.790 14:44:38.752 [planetinfo.record]: sun_distance = 1034820 14:44:38.752 [planetinfo.record]: corona_flare = 0.053064 14:44:38.752 [planetinfo.record]: corona_hues = 0.635429 14:44:38.752 [planetinfo.record]: sun_color = 0.7121, 0.664777, 0.652732, 1 14:44:38.752 [planetinfo.record]: corona_shimmer = 0.423064 14:44:38.752 [planetinfo.record]: station_vector = 0.850 0.521 0.082 14:44:38.752 [planetinfo.record]: station = coriolis 14:44:44.170 [PLANETINFO OVER]: Done 14:44:44.171 [PLANETINFO LOGGING]: 7 106 14:44:45.175 [planetinfo.record]: seed = 200 3 21 92 14:44:45.175 [planetinfo.record]: coordinates = 3 60 14:44:45.175 [planetinfo.record]: government = 1; 14:44:45.175 [planetinfo.record]: economy = 6; 14:44:45.175 [planetinfo.record]: techlevel = 5; 14:44:45.175 [planetinfo.record]: population = 28; 14:44:45.175 [planetinfo.record]: productivity = 4480; 14:44:45.175 [planetinfo.record]: name = "Tetete"; 14:44:45.175 [planetinfo.record]: inhabitant = "Human Colonial"; 14:44:45.175 [planetinfo.record]: inhabitants = "Human Colonials"; 14:44:45.176 [planetinfo.record]: description = "The planet Tetete is reasonably fabled for its fabulous vicious Enetil gargle blasters and its exotic fish meat."; 14:44:45.196 [planetinfo.record]: air_color = 0.46469, 0.690998, 0.860467, 1; 14:44:45.197 [planetinfo.record]: cloud_alpha = 1.000000; 14:44:45.197 [planetinfo.record]: cloud_color = 0.127747, 0.583984, 0.263192, 1; 14:44:45.197 [planetinfo.record]: cloud_fraction = 0.470000; 14:44:45.197 [planetinfo.record]: land_color = 0.616495, 0.619141, 0.585281, 1; 14:44:45.197 [planetinfo.record]: land_fraction = 0.710000; 14:44:45.197 [planetinfo.record]: polar_cloud_color = 0.390335, 0.762793, 0.500909, 1; 14:44:45.197 [planetinfo.record]: polar_land_color = 0.937084, 0.938086, 0.925261, 1; 14:44:45.197 [planetinfo.record]: polar_sea_color = 0.817322, 0.908789, 0.78809, 1; 14:44:45.197 [planetinfo.record]: sea_color = 0.544905, 0.912109, 0.427551, 1; 14:44:45.197 [planetinfo.record]: rotation_speed = 0.000315 14:44:45.197 [planetinfo.record]: planet zpos = 706920.000000 14:44:45.197 [planetinfo.record]: sun_radius = 145126.640320 14:44:45.197 [planetinfo.record]: sun_vector = 0.370 0.929 -0.024 14:44:45.197 [planetinfo.record]: sun_distance = 1001470 14:44:45.198 [planetinfo.record]: corona_flare = 0.020100 14:44:45.198 [planetinfo.record]: corona_hues = 0.573631 14:44:45.198 [planetinfo.record]: sun_color = 0.2441, 0.353452, 0.690924, 1 14:44:45.199 [planetinfo.record]: corona_shimmer = 0.339220 14:44:45.199 [planetinfo.record]: station_vector = -0.495 -0.861 0.112 14:44:45.199 [planetinfo.record]: station = coriolis 14:44:50.772 [PLANETINFO OVER]: Done 14:44:50.772 [PLANETINFO LOGGING]: 7 107 14:44:51.784 [planetinfo.record]: seed = 222 244 167 141 14:44:51.785 [planetinfo.record]: coordinates = 244 252 14:44:51.785 [planetinfo.record]: government = 3; 14:44:51.785 [planetinfo.record]: economy = 4; 14:44:51.785 [planetinfo.record]: techlevel = 5; 14:44:51.785 [planetinfo.record]: population = 28; 14:44:51.785 [planetinfo.record]: productivity = 9408; 14:44:51.785 [planetinfo.record]: name = "Diriledi"; 14:44:51.785 [planetinfo.record]: inhabitant = "Black Slimy Frog"; 14:44:51.785 [planetinfo.record]: inhabitants = "Black Slimy Frogs"; 14:44:51.785 [planetinfo.record]: description = "The world Diriledi is scourged by evil disease."; 14:44:51.799 [planetinfo.record]: air_color = 0.773232, 0.488169, 0.976887, 1; 14:44:51.800 [planetinfo.record]: cloud_alpha = 1.000000; 14:44:51.800 [planetinfo.record]: cloud_color = 0.933594, 0.0911713, 0.288614, 1; 14:44:51.800 [planetinfo.record]: cloud_fraction = 0.390000; 14:44:51.800 [planetinfo.record]: land_color = 0.262436, 0.384226, 0.574219, 1; 14:44:51.800 [planetinfo.record]: land_fraction = 0.580000; 14:44:51.800 [planetinfo.record]: polar_cloud_color = 0.920117, 0.401203, 0.522824, 1; 14:44:51.800 [planetinfo.record]: polar_land_color = 0.814631, 0.86461, 0.942578, 1; 14:44:51.801 [planetinfo.record]: polar_sea_color = 0.892435, 0.928125, 0.870299, 1; 14:44:51.801 [planetinfo.record]: sea_color = 0.608196, 0.71875, 0.539624, 1; 14:44:51.801 [planetinfo.record]: rotation_speed = 0.003343 14:44:51.801 [planetinfo.record]: planet zpos = 766560.000000 14:44:51.801 [planetinfo.record]: sun_radius = 151910.921021 14:44:51.801 [planetinfo.record]: sun_vector = 0.671 0.739 0.058 14:44:51.801 [planetinfo.record]: sun_distance = 1533120 14:44:51.801 [planetinfo.record]: corona_flare = 0.044231 14:44:51.801 [planetinfo.record]: corona_hues = 0.921097 14:44:51.801 [planetinfo.record]: sun_color = 0.782492, 0.572226, 0.507442, 1 14:44:51.801 [planetinfo.record]: corona_shimmer = 0.381138 14:44:51.801 [planetinfo.record]: station_vector = -0.804 -0.554 0.218 14:44:51.801 [planetinfo.record]: station = coriolis 14:44:56.487 [PLANETINFO OVER]: Done 14:44:56.487 [PLANETINFO LOGGING]: 7 108 14:44:57.491 [planetinfo.record]: seed = 220 13 17 142 14:44:57.491 [planetinfo.record]: coordinates = 13 1 14:44:57.491 [planetinfo.record]: government = 3; 14:44:57.491 [planetinfo.record]: economy = 1; 14:44:57.491 [planetinfo.record]: techlevel = 9; 14:44:57.491 [planetinfo.record]: population = 41; 14:44:57.491 [planetinfo.record]: productivity = 20664; 14:44:57.491 [planetinfo.record]: name = "Reisorza"; 14:44:57.491 [planetinfo.record]: inhabitant = "Human Colonial"; 14:44:57.491 [planetinfo.record]: inhabitants = "Human Colonials"; 14:44:57.491 [planetinfo.record]: description = "This planet is mildly noted for the Reisorzaian deadly Loarenusoid but plagued by unpredictable civil war."; 14:44:57.494 [planetinfo.record]: air_color = 0.743316, 0.626055, 0.854613, 1; 14:44:57.494 [planetinfo.record]: cloud_alpha = 1.000000; 14:44:57.494 [planetinfo.record]: cloud_color = 0.566406, 0.451355, 0.502589, 1; 14:44:57.494 [planetinfo.record]: cloud_fraction = 0.330000; 14:44:57.494 [planetinfo.record]: land_color = 0.439899, 0.338699, 0.574219, 1; 14:44:57.494 [planetinfo.record]: land_fraction = 0.350000; 14:44:57.494 [planetinfo.record]: polar_cloud_color = 0.754883, 0.659048, 0.701724, 1; 14:44:57.494 [planetinfo.record]: polar_land_color = 0.887457, 0.845927, 0.942578, 1; 14:44:57.495 [planetinfo.record]: polar_sea_color = 0.924213, 0.933789, 0.878072, 1; 14:44:57.495 [planetinfo.record]: sea_color = 0.634948, 0.662109, 0.504083, 1; 14:44:57.495 [planetinfo.record]: rotation_speed = 0.000652 14:44:57.495 [planetinfo.record]: planet zpos = 641300.000000 14:44:57.495 [planetinfo.record]: sun_radius = 126177.653809 14:44:57.495 [planetinfo.record]: sun_vector = -0.359 -0.565 -0.743 14:44:57.495 [planetinfo.record]: sun_distance = 1154340 14:44:57.495 [planetinfo.record]: corona_flare = 0.009863 14:44:57.558 [planetinfo.record]: corona_hues = 0.900993 14:44:57.558 [planetinfo.record]: sun_color = 0.333911, 0.441344, 0.67995, 1 14:44:57.560 [planetinfo.record]: corona_shimmer = 0.474005 14:44:57.560 [planetinfo.record]: station_vector = -0.300 -0.900 0.316 14:44:57.560 [planetinfo.record]: station = coriolis 14:45:02.546 [PLANETINFO OVER]: Done 14:45:02.547 [PLANETINFO LOGGING]: 7 109 14:45:03.553 [planetinfo.record]: seed = 146 100 19 59 14:45:03.553 [planetinfo.record]: coordinates = 100 57 14:45:03.553 [planetinfo.record]: government = 2; 14:45:03.553 [planetinfo.record]: economy = 1; 14:45:03.553 [planetinfo.record]: techlevel = 7; 14:45:03.553 [planetinfo.record]: population = 32; 14:45:03.553 [planetinfo.record]: productivity = 13824; 14:45:03.553 [planetinfo.record]: name = "Aneded"; 14:45:03.553 [planetinfo.record]: inhabitant = "Human Colonial"; 14:45:03.553 [planetinfo.record]: inhabitants = "Human Colonials"; 14:45:03.553 [planetinfo.record]: description = "This planet is reasonably famous for the Anededian spotted leopard."; 14:45:03.588 [planetinfo.record]: air_color = 0.679701, 0.788377, 0.956725, 1; 14:45:03.588 [planetinfo.record]: cloud_alpha = 1.000000; 14:45:03.588 [planetinfo.record]: cloud_color = 0.637733, 0.873047, 0.834441, 1; 14:45:03.588 [planetinfo.record]: cloud_fraction = 0.430000; 14:45:03.588 [planetinfo.record]: land_color = 0.580078, 0.396538, 0.568607, 1; 14:45:03.588 [planetinfo.record]: land_fraction = 0.390000; 14:45:03.588 [planetinfo.record]: polar_cloud_color = 0.742461, 0.892871, 0.868194, 1; 14:45:03.588 [planetinfo.record]: polar_land_color = 0.941992, 0.867479, 0.937335, 1; 14:45:03.588 [planetinfo.record]: polar_sea_color = 0.947656, 0.860173, 0.723698, 1; 14:45:03.588 [planetinfo.record]: sea_color = 0.523438, 0.330152, 0.0286255, 1; 14:45:03.588 [planetinfo.record]: rotation_speed = 0.002971 14:45:03.588 [planetinfo.record]: planet zpos = 630520.000000 14:45:03.588 [planetinfo.record]: sun_radius = 140458.314819 14:45:03.588 [planetinfo.record]: sun_vector = -0.904 -0.145 0.401 14:45:03.588 [planetinfo.record]: sun_distance = 1146400 14:45:03.588 [planetinfo.record]: corona_flare = 0.088213 14:45:03.588 [planetinfo.record]: corona_hues = 0.761940 14:45:03.588 [planetinfo.record]: sun_color = 0.754269, 0.625116, 0.35941, 1 14:45:03.589 [planetinfo.record]: corona_shimmer = 0.381733 14:45:03.589 [planetinfo.record]: station_vector = -0.061 0.997 0.038 14:45:03.589 [planetinfo.record]: station = coriolis 14:45:08.841 [PLANETINFO OVER]: Done 14:45:08.842 [PLANETINFO LOGGING]: 7 110 14:45:09.850 [planetinfo.record]: seed = 112 140 205 221 14:45:09.850 [planetinfo.record]: coordinates = 140 120 14:45:09.850 [planetinfo.record]: government = 6; 14:45:09.850 [planetinfo.record]: economy = 0; 14:45:09.850 [planetinfo.record]: techlevel = 10; 14:45:09.850 [planetinfo.record]: population = 47; 14:45:09.850 [planetinfo.record]: productivity = 37600; 14:45:09.850 [planetinfo.record]: name = "Isxein"; 14:45:09.850 [planetinfo.record]: inhabitant = "Fat Humanoid"; 14:45:09.850 [planetinfo.record]: inhabitants = "Fat Humanoids"; 14:45:09.851 [planetinfo.record]: description = "The world Isxein is a boring world."; 14:45:09.880 [planetinfo.record]: air_color = 0.521465, 0.641306, 0.83315, 1; 14:45:09.881 [planetinfo.record]: cloud_alpha = 1.000000; 14:45:09.881 [planetinfo.record]: cloud_color = 0.239212, 0.501953, 0.465005, 1; 14:45:09.881 [planetinfo.record]: cloud_fraction = 0.330000; 14:45:09.881 [planetinfo.record]: land_color = 0.66, 0.0103125, 0.17781, 1; 14:45:09.881 [planetinfo.record]: land_fraction = 0.340000; 14:45:09.881 [planetinfo.record]: polar_cloud_color = 0.488409, 0.725879, 0.692485, 1; 14:45:09.882 [planetinfo.record]: polar_land_color = 0.934, 0.704148, 0.763407, 1; 14:45:09.882 [planetinfo.record]: polar_sea_color = 0.944922, 0.916327, 0.895461, 1; 14:45:09.882 [planetinfo.record]: sea_color = 0.550781, 0.484112, 0.435461, 1; 14:45:09.882 [planetinfo.record]: rotation_speed = 0.003521 14:45:09.882 [planetinfo.record]: planet zpos = 942600.000000 14:45:09.882 [planetinfo.record]: sun_radius = 117870.066528 14:45:09.882 [planetinfo.record]: sun_vector = 0.441 0.840 0.317 14:45:09.882 [planetinfo.record]: sun_distance = 1382480 14:45:09.882 [planetinfo.record]: corona_flare = 0.023759 14:45:09.882 [planetinfo.record]: corona_hues = 0.607857 14:45:09.882 [planetinfo.record]: sun_color = 0.420241, 0.715307, 0.758682, 1 14:45:09.882 [planetinfo.record]: corona_shimmer = 0.503434 14:45:09.883 [planetinfo.record]: station_vector = -0.569 0.664 0.485 14:45:09.884 [planetinfo.record]: station = coriolis 14:45:14.564 [PLANETINFO OVER]: Done 14:45:14.565 [PLANETINFO LOGGING]: 7 111 14:45:15.570 [planetinfo.record]: seed = 134 13 63 61 14:45:15.570 [planetinfo.record]: coordinates = 13 76 14:45:15.571 [planetinfo.record]: government = 0; 14:45:15.571 [planetinfo.record]: economy = 6; 14:45:15.571 [planetinfo.record]: techlevel = 2; 14:45:15.571 [planetinfo.record]: population = 15; 14:45:15.571 [planetinfo.record]: productivity = 1920; 14:45:15.571 [planetinfo.record]: name = "Istixeti"; 14:45:15.571 [planetinfo.record]: inhabitant = "Human Colonial"; 14:45:15.571 [planetinfo.record]: inhabitants = "Human Colonials"; 14:45:15.571 [planetinfo.record]: description = "The planet Istixeti is reasonably noted for its fabulous goat soup and mud tennis."; 14:45:15.579 [planetinfo.record]: air_color = 0.618211, 0.590363, 0.8325, 1; 14:45:15.580 [planetinfo.record]: cloud_alpha = 1.000000; 14:45:15.580 [planetinfo.record]: cloud_color = 0.412415, 0.363281, 0.5, 1; 14:45:15.580 [planetinfo.record]: cloud_fraction = 0.420000; 14:45:15.580 [planetinfo.record]: land_color = 0.113438, 0.455039, 0.66, 1; 14:45:15.580 [planetinfo.record]: land_fraction = 0.500000; 14:45:15.580 [planetinfo.record]: polar_cloud_color = 0.645626, 0.601099, 0.725, 1; 14:45:15.580 [planetinfo.record]: polar_land_color = 0.740633, 0.861487, 0.934, 1; 14:45:15.580 [planetinfo.record]: polar_sea_color = 0.941602, 0.939741, 0.89397, 1; 14:45:15.581 [planetinfo.record]: sea_color = 0.583984, 0.579369, 0.465819, 1; 14:45:15.581 [planetinfo.record]: rotation_speed = 0.002646 14:45:15.581 [planetinfo.record]: planet zpos = 800410.000000 14:45:15.582 [planetinfo.record]: sun_radius = 133926.211700 14:45:15.582 [planetinfo.record]: sun_vector = -0.583 -0.605 0.542 14:45:15.582 [planetinfo.record]: sun_distance = 1292970 14:45:15.582 [planetinfo.record]: corona_flare = 0.036310 14:45:15.583 [planetinfo.record]: corona_hues = 0.549309 14:45:15.583 [planetinfo.record]: sun_color = 0.849442, 0.758925, 0.457779, 1 14:45:15.583 [planetinfo.record]: corona_shimmer = 0.379681 14:45:15.583 [planetinfo.record]: station_vector = 0.929 0.281 0.239 14:45:15.583 [planetinfo.record]: station = coriolis 14:45:21.691 [PLANETINFO OVER]: Done 14:45:21.692 [PLANETINFO LOGGING]: 7 112 14:45:22.698 [planetinfo.record]: seed = 132 183 201 49 14:45:22.698 [planetinfo.record]: coordinates = 183 226 14:45:22.698 [planetinfo.record]: government = 0; 14:45:22.698 [planetinfo.record]: economy = 2; 14:45:22.698 [planetinfo.record]: techlevel = 8; 14:45:22.698 [planetinfo.record]: population = 35; 14:45:22.698 [planetinfo.record]: productivity = 8960; 14:45:22.698 [planetinfo.record]: name = "Atmala"; 14:45:22.698 [planetinfo.record]: inhabitant = "Red Furry Feline"; 14:45:22.699 [planetinfo.record]: inhabitants = "Red Furry Felines"; 14:45:22.699 [planetinfo.record]: description = "This world is most fabled for Atmalaian Re brandy but scourged by killer edible Reoids."; 14:45:22.702 [planetinfo.record]: air_color = 0.70574, 0.506287, 0.94892, 1; 14:45:22.702 [planetinfo.record]: cloud_alpha = 1.000000; 14:45:22.702 [planetinfo.record]: cloud_color = 0.849609, 0.169258, 0.599793, 1; 14:45:22.703 [planetinfo.record]: cloud_fraction = 0.420000; 14:45:22.703 [planetinfo.record]: land_color = 0.0283594, 0.66, 0.482351, 1; 14:45:22.703 [planetinfo.record]: land_fraction = 0.640000; 14:45:22.703 [planetinfo.record]: polar_cloud_color = 0.882324, 0.440731, 0.720177, 1; 14:45:22.703 [planetinfo.record]: polar_land_color = 0.710533, 0.934, 0.87115, 1; 14:45:22.703 [planetinfo.record]: polar_sea_color = 0.939453, 0.886079, 0.878994, 1; 14:45:22.703 [planetinfo.record]: sea_color = 0.605469, 0.467873, 0.449608, 1; 14:45:22.703 [planetinfo.record]: rotation_speed = 0.003761 14:45:22.703 [planetinfo.record]: planet zpos = 390600.000000 14:45:22.703 [planetinfo.record]: sun_radius = 82080.276489 14:45:22.703 [planetinfo.record]: sun_vector = -0.334 -0.882 0.332 14:45:22.703 [planetinfo.record]: sun_distance = 618450 14:45:22.703 [planetinfo.record]: corona_flare = 0.063608 14:45:22.703 [planetinfo.record]: corona_hues = 0.906052 14:45:22.703 [planetinfo.record]: sun_color = 0.269142, 0.56925, 0.75235, 1 14:45:22.703 [planetinfo.record]: corona_shimmer = 0.714460 14:45:22.703 [planetinfo.record]: station_vector = -0.938 0.013 0.347 14:45:22.703 [planetinfo.record]: station = coriolis 14:45:27.588 [PLANETINFO OVER]: Done 14:45:27.590 [PLANETINFO LOGGING]: 7 113 14:45:28.593 [planetinfo.record]: seed = 186 178 171 51 14:45:28.593 [planetinfo.record]: coordinates = 178 181 14:45:28.593 [planetinfo.record]: government = 7; 14:45:28.593 [planetinfo.record]: economy = 5; 14:45:28.593 [planetinfo.record]: techlevel = 8; 14:45:28.593 [planetinfo.record]: population = 45; 14:45:28.593 [planetinfo.record]: productivity = 19800; 14:45:28.593 [planetinfo.record]: name = "Beanle"; 14:45:28.594 [planetinfo.record]: inhabitant = "Red Lizard"; 14:45:28.594 [planetinfo.record]: inhabitants = "Red Lizards"; 14:45:28.594 [planetinfo.record]: description = "This world is ravaged by unpredictable civil war."; 14:45:28.608 [planetinfo.record]: air_color = 0.786391, 0.548043, 0.935912, 1; 14:45:28.608 [planetinfo.record]: cloud_alpha = 1.000000; 14:45:28.608 [planetinfo.record]: cloud_color = 0.810547, 0.288124, 0.36159, 1; 14:45:28.608 [planetinfo.record]: cloud_fraction = 0.430000; 14:45:28.608 [planetinfo.record]: land_color = 0.102386, 0.595703, 0.179467, 1; 14:45:28.608 [planetinfo.record]: land_fraction = 0.620000; 14:45:28.608 [planetinfo.record]: polar_cloud_color = 0.864746, 0.516399, 0.565385, 1; 14:45:28.608 [planetinfo.record]: polar_land_color = 0.745731, 0.94043, 0.776153, 1; 14:45:28.608 [planetinfo.record]: polar_sea_color = 0.938086, 0.883334, 0.876066, 1; 14:45:28.608 [planetinfo.record]: sea_color = 0.619141, 0.474595, 0.455407, 1; 14:45:28.608 [planetinfo.record]: rotation_speed = 0.002355 14:45:28.608 [planetinfo.record]: planet zpos = 526680.000000 14:45:28.609 [planetinfo.record]: sun_radius = 94080.997925 14:45:28.609 [planetinfo.record]: sun_vector = -0.621 -0.757 -0.203 14:45:28.609 [planetinfo.record]: sun_distance = 752400 14:45:28.609 [planetinfo.record]: corona_flare = 0.045151 14:45:28.609 [planetinfo.record]: corona_hues = 0.578461 14:45:28.609 [planetinfo.record]: sun_color = 0.693314, 0.298355, 0.178065, 1 14:45:28.609 [planetinfo.record]: corona_shimmer = 0.506829 14:45:28.609 [planetinfo.record]: station_vector = 0.627 -0.102 0.772 14:45:28.609 [planetinfo.record]: station = coriolis 14:45:33.254 [PLANETINFO OVER]: Done 14:45:33.255 [PLANETINFO LOGGING]: 7 114 14:45:34.258 [planetinfo.record]: seed = 24 81 133 110 14:45:34.258 [planetinfo.record]: coordinates = 81 129 14:45:34.258 [planetinfo.record]: government = 3; 14:45:34.258 [planetinfo.record]: economy = 1; 14:45:34.258 [planetinfo.record]: techlevel = 9; 14:45:34.259 [planetinfo.record]: population = 41; 14:45:34.259 [planetinfo.record]: productivity = 20664; 14:45:34.259 [planetinfo.record]: name = "Releleat"; 14:45:34.259 [planetinfo.record]: inhabitant = "Blue Slimy Lizard"; 14:45:34.259 [planetinfo.record]: inhabitants = "Blue Slimy Lizards"; 14:45:34.259 [planetinfo.record]: description = "This planet is noted for its exotic fish meat."; 14:45:34.264 [planetinfo.record]: air_color = 0.566545, 0.963229, 0.94423, 1; 14:45:34.264 [planetinfo.record]: cloud_alpha = 1.000000; 14:45:34.264 [planetinfo.record]: cloud_color = 0.892578, 0.817098, 0.324257, 1; 14:45:34.264 [planetinfo.record]: cloud_fraction = 0.450000; 14:45:34.264 [planetinfo.record]: land_color = 0.110537, 0.0928125, 0.66, 1; 14:45:34.264 [planetinfo.record]: land_fraction = 0.710000; 14:45:34.265 [planetinfo.record]: polar_cloud_color = 0.90166, 0.854005, 0.542845, 1; 14:45:34.265 [planetinfo.record]: polar_land_color = 0.739607, 0.733336, 0.934, 1; 14:45:34.265 [planetinfo.record]: polar_sea_color = 0.924445, 0.936719, 0.886041, 1; 14:45:34.265 [planetinfo.record]: sea_color = 0.599646, 0.632812, 0.495868, 1; 14:45:34.265 [planetinfo.record]: rotation_speed = 0.001586 14:45:34.265 [planetinfo.record]: planet zpos = 712910.000000 14:45:34.265 [planetinfo.record]: sun_radius = 167718.224640 14:45:34.265 [planetinfo.record]: sun_vector = -0.447 0.113 -0.888 14:45:34.265 [planetinfo.record]: sun_distance = 1231390 14:45:34.265 [planetinfo.record]: corona_flare = 0.016592 14:45:34.265 [planetinfo.record]: corona_hues = 0.586334 14:45:34.265 [planetinfo.record]: sun_color = 0.773328, 0.674207, 0.373182, 1 14:45:34.265 [planetinfo.record]: corona_shimmer = 0.387333 14:45:34.266 [planetinfo.record]: station_vector = 0.095 0.961 0.259 14:45:34.266 [planetinfo.record]: station = coriolis 14:45:39.642 [PLANETINFO OVER]: Done 14:45:39.643 [PLANETINFO LOGGING]: 7 115 14:45:40.647 [planetinfo.record]: seed = 46 177 215 243 14:45:40.647 [planetinfo.record]: coordinates = 177 1 14:45:40.647 [planetinfo.record]: government = 5; 14:45:40.647 [planetinfo.record]: economy = 1; 14:45:40.647 [planetinfo.record]: techlevel = 10; 14:45:40.647 [planetinfo.record]: population = 47; 14:45:40.647 [planetinfo.record]: productivity = 30456; 14:45:40.647 [planetinfo.record]: name = "Bebima"; 14:45:40.647 [planetinfo.record]: inhabitant = "Slimy Lobster"; 14:45:40.647 [planetinfo.record]: inhabitants = "Slimy Lobsters"; 14:45:40.647 [planetinfo.record]: description = "This planet is notable for the Bebimaian tree grub and its inhabitants’ ancient mating traditions."; 14:45:40.676 [planetinfo.record]: air_color = 0.731472, 0.74596, 0.956074, 1; 14:45:40.676 [planetinfo.record]: cloud_alpha = 1.000000; 14:45:40.676 [planetinfo.record]: cloud_color = 0.779221, 0.795729, 0.871094, 1; 14:45:40.677 [planetinfo.record]: cloud_fraction = 0.560000; 14:45:40.677 [planetinfo.record]: land_color = 0.402188, 0.581448, 0.66, 1; 14:45:40.677 [planetinfo.record]: land_fraction = 0.450000; 14:45:40.677 [planetinfo.record]: polar_cloud_color = 0.833194, 0.843759, 0.891992, 1; 14:45:40.677 [planetinfo.record]: polar_land_color = 0.842789, 0.906209, 0.934, 1; 14:45:40.677 [planetinfo.record]: polar_sea_color = 0.937695, 0.888733, 0.883668, 1; 14:45:40.677 [planetinfo.record]: sea_color = 0.623047, 0.492916, 0.479454, 1; 14:45:40.677 [planetinfo.record]: rotation_speed = 0.002130 14:45:40.677 [planetinfo.record]: planet zpos = 488930.000000 14:45:40.677 [planetinfo.record]: sun_radius = 95217.528992 14:45:40.677 [planetinfo.record]: sun_vector = -0.661 0.743 0.105 14:45:40.677 [planetinfo.record]: sun_distance = 676980 14:45:40.677 [planetinfo.record]: corona_flare = 0.017662 14:45:40.677 [planetinfo.record]: corona_hues = 0.659332 14:45:40.677 [planetinfo.record]: sun_color = 0.785945, 0.80257, 0.833282, 1 14:45:40.678 [planetinfo.record]: corona_shimmer = 1.064864 14:45:40.678 [planetinfo.record]: station_vector = -0.235 0.947 0.220 14:45:40.678 [planetinfo.record]: station = coriolis 14:45:45.859 [PLANETINFO OVER]: Done 14:45:45.860 [PLANETINFO LOGGING]: 7 116 14:45:46.864 [planetinfo.record]: seed = 44 229 129 214 14:45:46.864 [planetinfo.record]: coordinates = 229 75 14:45:46.864 [planetinfo.record]: government = 5; 14:45:46.864 [planetinfo.record]: economy = 3; 14:45:46.864 [planetinfo.record]: techlevel = 8; 14:45:46.864 [planetinfo.record]: population = 41; 14:45:46.864 [planetinfo.record]: productivity = 20664; 14:45:46.864 [planetinfo.record]: name = "Vebixe"; 14:45:46.864 [planetinfo.record]: inhabitant = "Rodent"; 14:45:46.864 [planetinfo.record]: inhabitants = "Rodents"; 14:45:46.864 [planetinfo.record]: description = "This planet is mildly noted for the Vebixeian mountain poet but cursed by unpredictable earthquakes."; 14:45:46.866 [planetinfo.record]: air_color = 0.824259, 0.762387, 0.978188, 1; 14:45:46.866 [planetinfo.record]: cloud_alpha = 1.000000; 14:45:46.866 [planetinfo.record]: cloud_color = 0.930176, 0.878906, 0.9375, 1; 14:45:46.866 [planetinfo.record]: cloud_fraction = 0.570000; 14:45:46.866 [planetinfo.record]: land_color = 0.574922, 0.65003, 0.66, 1; 14:45:46.866 [planetinfo.record]: land_fraction = 0.670000; 14:45:46.866 [planetinfo.record]: polar_cloud_color = 0.917374, 0.885864, 0.921875, 1; 14:45:46.867 [planetinfo.record]: polar_land_color = 0.9039, 0.930473, 0.934, 1; 14:45:46.867 [planetinfo.record]: polar_sea_color = 0.917578, 0.848076, 0.786752, 1; 14:45:46.867 [planetinfo.record]: sea_color = 0.824219, 0.574498, 0.354156, 1; 14:45:46.867 [planetinfo.record]: rotation_speed = 0.001969 14:45:46.867 [planetinfo.record]: planet zpos = 503910.000000 14:45:46.867 [planetinfo.record]: sun_radius = 135871.917572 14:45:46.867 [planetinfo.record]: sun_vector = 0.328 -0.363 -0.872 14:45:46.867 [planetinfo.record]: sun_distance = 1007820 14:45:46.867 [planetinfo.record]: corona_flare = 0.013686 14:45:46.867 [planetinfo.record]: corona_hues = 0.921959 14:45:46.867 [planetinfo.record]: sun_color = 0.515484, 0.565406, 0.748764, 1 14:45:46.867 [planetinfo.record]: corona_shimmer = 0.372859 14:45:46.868 [planetinfo.record]: station_vector = 0.414 -0.569 0.711 14:45:46.868 [planetinfo.record]: station = coriolis 14:45:51.542 [PLANETINFO OVER]: Done 14:45:51.542 [PLANETINFO LOGGING]: 7 117 14:45:52.547 [planetinfo.record]: seed = 226 159 67 105 14:45:52.547 [planetinfo.record]: coordinates = 159 194 14:45:52.547 [planetinfo.record]: government = 4; 14:45:52.547 [planetinfo.record]: economy = 2; 14:45:52.547 [planetinfo.record]: techlevel = 10; 14:45:52.547 [planetinfo.record]: population = 47; 14:45:52.548 [planetinfo.record]: productivity = 24064; 14:45:52.548 [planetinfo.record]: name = "Esmara"; 14:45:52.548 [planetinfo.record]: inhabitant = "Human Colonial"; 14:45:52.548 [planetinfo.record]: inhabitants = "Human Colonials"; 14:45:52.548 [planetinfo.record]: description = "Esmara is reasonably notable for its unusual oceans but plagued by occasional civil war."; 14:45:52.554 [planetinfo.record]: air_color = 0.552233, 0.44975, 0.959977, 1; 14:45:52.554 [planetinfo.record]: cloud_alpha = 1.000000; 14:45:52.554 [planetinfo.record]: cloud_color = 0.546181, 0.00344849, 0.882812, 1; 14:45:52.554 [planetinfo.record]: cloud_fraction = 0.160000; 14:45:52.554 [planetinfo.record]: land_color = 0.040968, 0.0360938, 0.66, 1; 14:45:52.554 [planetinfo.record]: land_fraction = 0.330000; 14:45:52.554 [planetinfo.record]: polar_cloud_color = 0.683426, 0.338665, 0.897266, 1; 14:45:52.554 [planetinfo.record]: polar_land_color = 0.714994, 0.71327, 0.934, 1; 14:45:52.554 [planetinfo.record]: polar_sea_color = 0.916695, 0.937305, 0.889341, 1; 14:45:52.554 [planetinfo.record]: sea_color = 0.571812, 0.626953, 0.498624, 1; 14:45:52.554 [planetinfo.record]: rotation_speed = 0.001751 14:45:52.554 [planetinfo.record]: planet zpos = 580690.000000 14:45:52.554 [planetinfo.record]: sun_radius = 135524.889069 14:45:52.554 [planetinfo.record]: sun_vector = -0.405 0.774 0.486 14:45:52.554 [planetinfo.record]: sun_distance = 1214170 14:45:52.554 [planetinfo.record]: corona_flare = 0.050153 14:45:52.554 [planetinfo.record]: corona_hues = 0.519882 14:45:52.554 [planetinfo.record]: sun_color = 0.633495, 0.750479, 0.788156, 1 14:45:52.555 [planetinfo.record]: corona_shimmer = 0.436621 14:45:52.555 [planetinfo.record]: station_vector = -0.908 0.178 0.380 14:45:52.555 [planetinfo.record]: station = coriolis 14:45:57.447 [PLANETINFO OVER]: Done 14:45:57.448 [PLANETINFO LOGGING]: 7 118 14:45:58.451 [planetinfo.record]: seed = 192 9 61 170 14:45:58.451 [planetinfo.record]: coordinates = 9 212 14:45:58.451 [planetinfo.record]: government = 0; 14:45:58.451 [planetinfo.record]: economy = 6; 14:45:58.451 [planetinfo.record]: techlevel = 2; 14:45:58.451 [planetinfo.record]: population = 15; 14:45:58.451 [planetinfo.record]: productivity = 1920; 14:45:58.451 [planetinfo.record]: name = "Arustea"; 14:45:58.451 [planetinfo.record]: inhabitant = "Human Colonial"; 14:45:58.451 [planetinfo.record]: inhabitants = "Human Colonials"; 14:45:58.451 [planetinfo.record]: description = "This planet is most fabled for Arusteaian No brandy but cursed by unpredictable solar activity."; 14:45:58.480 [planetinfo.record]: air_color = 0.540827, 0.955424, 0.84173, 1; 14:45:58.480 [planetinfo.record]: cloud_alpha = 1.000000; 14:45:58.480 [planetinfo.record]: cloud_color = 0.869141, 0.37261, 0.258026, 1; 14:45:58.480 [planetinfo.record]: cloud_fraction = 0.430000; 14:45:58.481 [planetinfo.record]: land_color = 0.627854, 0.66, 0.513047, 1; 14:45:58.481 [planetinfo.record]: land_fraction = 0.590000; 14:45:58.481 [planetinfo.record]: polar_cloud_color = 0.891113, 0.572936, 0.499511, 1; 14:45:58.481 [planetinfo.record]: polar_land_color = 0.922627, 0.934, 0.88201, 1; 14:45:58.481 [planetinfo.record]: polar_sea_color = 0.861362, 0.864583, 0.912891, 1; 14:45:58.481 [planetinfo.record]: sea_color = 0.674417, 0.686709, 0.871094, 1; 14:45:58.481 [planetinfo.record]: rotation_speed = 0.004727 14:45:58.481 [planetinfo.record]: planet zpos = 538500.000000 14:45:58.481 [planetinfo.record]: sun_radius = 117599.670410 14:45:58.481 [planetinfo.record]: sun_vector = -0.230 -0.499 -0.836 14:45:58.481 [planetinfo.record]: sun_distance = 1184700 14:45:58.481 [planetinfo.record]: corona_flare = 0.043861 14:45:58.481 [planetinfo.record]: corona_hues = 0.826317 14:45:58.487 [planetinfo.record]: sun_color = 0.236489, 0.39099, 0.779733, 1 14:45:58.488 [planetinfo.record]: corona_shimmer = 0.427262 14:45:58.489 [planetinfo.record]: station_vector = 0.305 0.840 0.448 14:45:58.489 [planetinfo.record]: station = coriolis 14:46:03.647 [PLANETINFO OVER]: Done 14:46:03.648 [PLANETINFO LOGGING]: 7 119 14:46:04.665 [planetinfo.record]: seed = 214 111 111 53 14:46:04.665 [planetinfo.record]: coordinates = 111 60 14:46:04.665 [planetinfo.record]: government = 2; 14:46:04.665 [planetinfo.record]: economy = 4; 14:46:04.665 [planetinfo.record]: techlevel = 7; 14:46:04.666 [planetinfo.record]: population = 35; 14:46:04.666 [planetinfo.record]: productivity = 10080; 14:46:04.666 [planetinfo.record]: name = "Laxesori"; 14:46:04.666 [planetinfo.record]: inhabitant = "Human Colonial"; 14:46:04.666 [planetinfo.record]: inhabitants = "Human Colonials"; 14:46:04.666 [planetinfo.record]: description = "This planet is reasonably noted for its inhabitants’ exceptional love for tourists."; 14:46:04.668 [planetinfo.record]: air_color = 0.49796, 0.492339, 0.907295, 1; 14:46:04.669 [planetinfo.record]: cloud_alpha = 1.000000; 14:46:04.669 [planetinfo.record]: cloud_color = 0.194818, 0.164169, 0.724609, 1; 14:46:04.669 [planetinfo.record]: cloud_fraction = 0.150000; 14:46:04.669 [planetinfo.record]: land_color = 0.113923, 0.638672, 0.0224533, 1; 14:46:04.669 [planetinfo.record]: land_fraction = 0.450000; 14:46:04.669 [planetinfo.record]: polar_cloud_color = 0.448589, 0.426751, 0.826074, 1; 14:46:04.669 [planetinfo.record]: polar_land_color = 0.743845, 0.936133, 0.710327, 1; 14:46:04.669 [planetinfo.record]: polar_sea_color = 0.895363, 0.932617, 0.881888, 1; 14:46:04.669 [planetinfo.record]: sea_color = 0.566161, 0.673828, 0.527218, 1; 14:46:04.669 [planetinfo.record]: rotation_speed = 0.001408 14:46:04.669 [planetinfo.record]: planet zpos = 588980.000000 14:46:04.669 [planetinfo.record]: sun_radius = 97058.987274 14:46:04.669 [planetinfo.record]: sun_vector = -0.583 0.452 -0.675 14:46:04.669 [planetinfo.record]: sun_distance = 757260 14:46:04.669 [planetinfo.record]: corona_flare = 0.067197 14:46:04.669 [planetinfo.record]: corona_hues = 0.889366 14:46:04.670 [planetinfo.record]: sun_color = 0.727304, 0.726239, 0.674909, 1 14:46:04.670 [planetinfo.record]: corona_shimmer = 0.397164 14:46:04.670 [planetinfo.record]: station_vector = 0.783 -0.377 0.495 14:46:04.670 [planetinfo.record]: station = coriolis 14:46:09.436 [PLANETINFO OVER]: Done 14:46:09.437 [PLANETINFO LOGGING]: 7 120 14:46:10.441 [planetinfo.record]: seed = 212 158 57 8 14:46:10.441 [planetinfo.record]: coordinates = 158 135 14:46:10.441 [planetinfo.record]: government = 2; 14:46:10.441 [planetinfo.record]: economy = 7; 14:46:10.441 [planetinfo.record]: techlevel = 3; 14:46:10.441 [planetinfo.record]: population = 22; 14:46:10.441 [planetinfo.record]: productivity = 3168; 14:46:10.441 [planetinfo.record]: name = "Usrelain"; 14:46:10.441 [planetinfo.record]: inhabitant = "Human Colonial"; 14:46:10.441 [planetinfo.record]: inhabitants = "Human Colonials"; 14:46:10.441 [planetinfo.record]: description = "This planet is fabled for its unusual tropical forests and its fabulous cuisine."; 14:46:10.455 [planetinfo.record]: air_color = 0.738374, 0.517919, 0.947619, 1; 14:46:10.455 [planetinfo.record]: cloud_alpha = 1.000000; 14:46:10.455 [planetinfo.record]: cloud_color = 0.845703, 0.201515, 0.48838, 1; 14:46:10.456 [planetinfo.record]: cloud_fraction = 0.520000; 14:46:10.456 [planetinfo.record]: land_color = 0.579071, 0.533672, 0.66, 1; 14:46:10.456 [planetinfo.record]: land_fraction = 0.460000; 14:46:10.456 [planetinfo.record]: polar_cloud_color = 0.880566, 0.461351, 0.648033, 1; 14:46:10.456 [planetinfo.record]: polar_land_color = 0.905368, 0.889307, 0.934, 1; 14:46:10.456 [planetinfo.record]: polar_sea_color = 0.941992, 0.92719, 0.892133, 1; 14:46:10.456 [planetinfo.record]: sea_color = 0.580078, 0.543618, 0.457265, 1; 14:46:10.456 [planetinfo.record]: rotation_speed = 0.004988 14:46:10.456 [planetinfo.record]: planet zpos = 652860.000000 14:46:10.456 [planetinfo.record]: sun_radius = 138414.583740 14:46:10.457 [planetinfo.record]: sun_vector = 0.093 0.945 -0.315 14:46:10.457 [planetinfo.record]: sun_distance = 853740 14:46:10.457 [planetinfo.record]: corona_flare = 0.042331 14:46:10.457 [planetinfo.record]: corona_hues = 0.557137 14:46:10.457 [planetinfo.record]: sun_color = 0.726306, 0.528136, 0.226527, 1 14:46:10.457 [planetinfo.record]: corona_shimmer = 1.094675 14:46:10.457 [planetinfo.record]: station_vector = 0.506 -0.771 0.387 14:46:10.457 [planetinfo.record]: station = coriolis 14:46:15.134 [PLANETINFO OVER]: Done 14:46:15.135 [PLANETINFO LOGGING]: 7 121 14:46:16.150 [planetinfo.record]: seed = 10 12 219 15 14:46:16.151 [planetinfo.record]: coordinates = 12 213 14:46:16.151 [planetinfo.record]: government = 1; 14:46:16.151 [planetinfo.record]: economy = 7; 14:46:16.151 [planetinfo.record]: techlevel = 1; 14:46:16.151 [planetinfo.record]: population = 13; 14:46:16.151 [planetinfo.record]: productivity = 1560; 14:46:16.151 [planetinfo.record]: name = "Aatdire"; 14:46:16.151 [planetinfo.record]: inhabitant = "Green Bug-Eyed Bird"; 14:46:16.151 [planetinfo.record]: inhabitants = "Green Bug-Eyed Birds"; 14:46:16.151 [planetinfo.record]: description = "Aatdire is mildly well known for Aatdireian vicious brew."; 14:46:16.155 [planetinfo.record]: air_color = 0.462393, 0.459682, 0.93201, 1; 14:46:16.155 [planetinfo.record]: cloud_alpha = 1.000000; 14:46:16.155 [planetinfo.record]: cloud_color = 0.0854216, 0.0624084, 0.798828, 1; 14:46:16.155 [planetinfo.record]: cloud_fraction = 0.130000; 14:46:16.155 [planetinfo.record]: land_color = 0.178528, 0.507812, 0.245414, 1; 14:46:16.155 [planetinfo.record]: land_fraction = 0.510000; 14:46:16.155 [planetinfo.record]: polar_cloud_color = 0.379744, 0.364269, 0.859473, 1; 14:46:16.155 [planetinfo.record]: polar_land_color = 0.795341, 0.949219, 0.826598, 1; 14:46:16.155 [planetinfo.record]: polar_sea_color = 0.930469, 0.857066, 0.845055, 1; 14:46:16.155 [planetinfo.record]: sea_color = 0.695312, 0.475905, 0.440002, 1; 14:46:16.155 [planetinfo.record]: rotation_speed = 0.001063 14:46:16.155 [planetinfo.record]: planet zpos = 933520.000000 14:46:16.155 [planetinfo.record]: sun_radius = 121193.260498 14:46:16.155 [planetinfo.record]: sun_vector = 0.627 0.728 0.276 14:46:16.155 [planetinfo.record]: sun_distance = 1266920 14:46:16.155 [planetinfo.record]: corona_flare = 0.037738 14:46:16.155 [planetinfo.record]: corona_hues = 0.857170 14:46:16.155 [planetinfo.record]: sun_color = 0.607113, 0.656671, 0.752399, 1 14:46:16.156 [planetinfo.record]: corona_shimmer = 0.398361 14:46:16.156 [planetinfo.record]: station_vector = -0.382 -0.793 0.474 14:46:16.156 [planetinfo.record]: station = coriolis 14:46:21.120 [PLANETINFO OVER]: Done 14:46:21.121 [PLANETINFO LOGGING]: 7 122 14:46:22.124 [planetinfo.record]: seed = 104 14 245 12 14:46:22.125 [planetinfo.record]: coordinates = 14 13 14:46:22.125 [planetinfo.record]: government = 5; 14:46:22.125 [planetinfo.record]: economy = 5; 14:46:22.125 [planetinfo.record]: techlevel = 7; 14:46:22.125 [planetinfo.record]: population = 39; 14:46:22.125 [planetinfo.record]: productivity = 14040; 14:46:22.125 [planetinfo.record]: name = "Inusge"; 14:46:22.125 [planetinfo.record]: inhabitant = "Green Bony Lobster"; 14:46:22.125 [planetinfo.record]: inhabitants = "Green Bony Lobsters"; 14:46:22.125 [planetinfo.record]: description = "Inusge is mildly well known for killer Soza gargle blasters."; 14:46:22.148 [planetinfo.record]: air_color = 0.617489, 0.879153, 0.958676, 1; 14:46:22.148 [planetinfo.record]: cloud_alpha = 1.000000; 14:46:22.148 [planetinfo.record]: cloud_color = 0.586009, 0.878906, 0.466919, 1; 14:46:22.148 [planetinfo.record]: cloud_fraction = 0.140000; 14:46:22.149 [planetinfo.record]: land_color = 0.66, 0.547489, 0.281016, 1; 14:46:22.149 [planetinfo.record]: land_fraction = 0.510000; 14:46:22.149 [planetinfo.record]: polar_cloud_color = 0.708989, 0.895508, 0.633152, 1; 14:46:22.149 [planetinfo.record]: polar_land_color = 0.934, 0.894195, 0.79992, 1; 14:46:22.149 [planetinfo.record]: polar_sea_color = 0.868811, 0.924805, 0.87756, 1; 14:46:22.149 [planetinfo.record]: sea_color = 0.569839, 0.751953, 0.598295, 1; 14:46:22.149 [planetinfo.record]: rotation_speed = 0.002781 14:46:22.149 [planetinfo.record]: planet zpos = 708240.000000 14:46:22.149 [planetinfo.record]: sun_radius = 170496.618652 14:46:22.149 [planetinfo.record]: sun_vector = -0.565 -0.777 0.279 14:46:22.149 [planetinfo.record]: sun_distance = 1357460 14:46:22.149 [planetinfo.record]: corona_flare = 0.031035 14:46:22.149 [planetinfo.record]: corona_hues = 0.625168 14:46:22.149 [planetinfo.record]: sun_color = 0.754324, 0.498952, 0.491938, 1 14:46:22.149 [planetinfo.record]: corona_shimmer = 0.230386 14:46:22.149 [planetinfo.record]: station_vector = 0.871 -0.457 0.181 14:46:22.149 [planetinfo.record]: station = coriolis 14:46:27.204 [PLANETINFO OVER]: Done 14:46:27.205 [PLANETINFO LOGGING]: 7 123 14:46:28.212 [planetinfo.record]: seed = 126 121 7 230 14:46:28.212 [planetinfo.record]: coordinates = 121 67 14:46:28.212 [planetinfo.record]: government = 7; 14:46:28.212 [planetinfo.record]: economy = 3; 14:46:28.212 [planetinfo.record]: techlevel = 9; 14:46:28.212 [planetinfo.record]: population = 47; 14:46:28.212 [planetinfo.record]: productivity = 28952; 14:46:28.212 [planetinfo.record]: name = "Bigexein"; 14:46:28.212 [planetinfo.record]: inhabitant = "Human Colonial"; 14:46:28.212 [planetinfo.record]: inhabitants = "Human Colonials"; 14:46:28.212 [planetinfo.record]: description = "The planet Bigexein is a boring planet."; 14:46:28.227 [planetinfo.record]: air_color = 0.458191, 0.671955, 0.871523, 1; 14:46:28.228 [planetinfo.record]: cloud_alpha = 1.000000; 14:46:28.228 [planetinfo.record]: cloud_color = 0.10849, 0.617188, 0.342968, 1; 14:46:28.228 [planetinfo.record]: cloud_fraction = 0.380000; 14:46:28.228 [planetinfo.record]: land_color = 0.66, 0.561548, 0.175313, 1; 14:46:28.228 [planetinfo.record]: land_fraction = 0.240000; 14:46:28.228 [planetinfo.record]: polar_cloud_color = 0.377095, 0.777734, 0.561765, 1; 14:46:28.228 [planetinfo.record]: polar_land_color = 0.934, 0.899169, 0.762523, 1; 14:46:28.228 [planetinfo.record]: polar_sea_color = 0.942383, 0.92897, 0.893331, 1; 14:46:28.228 [planetinfo.record]: sea_color = 0.576172, 0.54337, 0.456211, 1; 14:46:28.228 [planetinfo.record]: rotation_speed = 0.000865 14:46:28.228 [planetinfo.record]: planet zpos = 447300.000000 14:46:28.228 [planetinfo.record]: sun_radius = 123687.978058 14:46:28.228 [planetinfo.record]: sun_vector = 0.056 0.892 0.449 14:46:28.228 [planetinfo.record]: sun_distance = 849870 14:46:28.229 [planetinfo.record]: corona_flare = 0.024821 14:46:28.229 [planetinfo.record]: corona_hues = 0.922958 14:46:28.229 [planetinfo.record]: sun_color = 0.679343, 0.608909, 0.236782, 1 14:46:28.229 [planetinfo.record]: corona_shimmer = 0.468972 14:46:28.229 [planetinfo.record]: station_vector = 0.836 0.424 0.350 14:46:28.229 [planetinfo.record]: station = coriolis 14:46:32.928 [PLANETINFO OVER]: Done 14:46:32.929 [PLANETINFO LOGGING]: 7 124 14:46:33.931 [planetinfo.record]: seed = 124 140 241 50 14:46:33.931 [planetinfo.record]: coordinates = 140 2 14:46:33.931 [planetinfo.record]: government = 7; 14:46:33.931 [planetinfo.record]: economy = 2; 14:46:33.931 [planetinfo.record]: techlevel = 9; 14:46:33.931 [planetinfo.record]: population = 46; 14:46:33.931 [planetinfo.record]: productivity = 32384; 14:46:33.931 [planetinfo.record]: name = "Enxeleti"; 14:46:33.931 [planetinfo.record]: inhabitant = "Red Rodent"; 14:46:33.931 [planetinfo.record]: inhabitants = "Red Rodents"; 14:46:33.931 [planetinfo.record]: description = "This planet is very notable for its inhabitants’ ingrained shyness and its fabulous vicious Noenthnu gargle blasters."; 14:46:33.941 [planetinfo.record]: air_color = 0.43364, 0.608604, 0.907945, 1; 14:46:33.941 [planetinfo.record]: cloud_alpha = 1.000000; 14:46:33.941 [planetinfo.record]: cloud_color = 0.0227051, 0.726562, 0.660576, 1; 14:46:33.941 [planetinfo.record]: cloud_fraction = 0.330000; 14:46:33.941 [planetinfo.record]: land_color = 0.66, 0.172734, 0.652386, 1; 14:46:33.941 [planetinfo.record]: land_fraction = 0.710000; 14:46:33.941 [planetinfo.record]: polar_cloud_color = 0.326259, 0.826953, 0.780013, 1; 14:46:33.941 [planetinfo.record]: polar_land_color = 0.934, 0.761611, 0.931306, 1; 14:46:33.941 [planetinfo.record]: polar_sea_color = 0.936328, 0.89781, 0.874699, 1; 14:46:33.941 [planetinfo.record]: sea_color = 0.636719, 0.531946, 0.469083, 1; 14:46:33.941 [planetinfo.record]: rotation_speed = 0.003130 14:46:33.941 [planetinfo.record]: planet zpos = 346800.000000 14:46:33.941 [planetinfo.record]: sun_radius = 80770.067139 14:46:33.941 [planetinfo.record]: sun_vector = -0.712 0.192 -0.675 14:46:33.941 [planetinfo.record]: sun_distance = 728280 14:46:33.941 [planetinfo.record]: corona_flare = 0.009039 14:46:33.941 [planetinfo.record]: corona_hues = 0.694305 14:46:33.947 [planetinfo.record]: sun_color = 0.798935, 0.308028, 0.228589, 1 14:46:33.948 [planetinfo.record]: corona_shimmer = 0.500023 14:46:33.948 [planetinfo.record]: station_vector = -0.366 0.129 0.922 14:46:33.948 [planetinfo.record]: station = coriolis 14:46:38.638 [PLANETINFO OVER]: Done 14:46:38.639 [PLANETINFO LOGGING]: 7 125 14:46:39.641 [planetinfo.record]: seed = 50 119 115 187 14:46:39.642 [planetinfo.record]: coordinates = 119 129 14:46:39.642 [planetinfo.record]: government = 6; 14:46:39.642 [planetinfo.record]: economy = 1; 14:46:39.642 [planetinfo.record]: techlevel = 12; 14:46:39.642 [planetinfo.record]: population = 56; 14:46:39.642 [planetinfo.record]: productivity = 40320; 14:46:39.642 [planetinfo.record]: name = "Anrasoti"; 14:46:39.642 [planetinfo.record]: inhabitant = "Human Colonial"; 14:46:39.642 [planetinfo.record]: inhabitants = "Human Colonials"; 14:46:39.642 [planetinfo.record]: description = "Anrasoti is well known for the Anrasotiian spotted cat but ravaged by occasional earthquakes."; 14:46:39.654 [planetinfo.record]: air_color = 0.695639, 0.735545, 0.975586, 1; 14:46:39.655 [planetinfo.record]: cloud_alpha = 1.000000; 14:46:39.655 [planetinfo.record]: cloud_color = 0.686371, 0.787119, 0.929688, 1; 14:46:39.655 [planetinfo.record]: cloud_fraction = 0.380000; 14:46:39.655 [planetinfo.record]: land_color = 0.66, 0.337734, 0.541668, 1; 14:46:39.655 [planetinfo.record]: land_fraction = 0.260000; 14:46:39.655 [planetinfo.record]: polar_cloud_color = 0.768139, 0.83034, 0.918359, 1; 14:46:39.655 [planetinfo.record]: polar_land_color = 0.934, 0.819986, 0.892136, 1; 14:46:39.655 [planetinfo.record]: polar_sea_color = 0.933984, 0.888095, 0.870138, 1; 14:46:39.656 [planetinfo.record]: sea_color = 0.660156, 0.530414, 0.479645, 1; 14:46:39.656 [planetinfo.record]: rotation_speed = 0.000533 14:46:39.656 [planetinfo.record]: planet zpos = 517590.000000 14:46:39.656 [planetinfo.record]: sun_radius = 156349.169769 14:46:39.656 [planetinfo.record]: sun_vector = -0.828 0.462 0.319 14:46:39.656 [planetinfo.record]: sun_distance = 1380240 14:46:39.656 [planetinfo.record]: corona_flare = 0.027620 14:46:39.656 [planetinfo.record]: corona_hues = 0.556992 14:46:39.656 [planetinfo.record]: sun_color = 0.802625, 0.54285, 0.417889, 1 14:46:39.657 [planetinfo.record]: corona_shimmer = 0.416281 14:46:39.657 [planetinfo.record]: station_vector = 0.121 0.189 0.974 14:46:39.657 [planetinfo.record]: station = dodecahedron 14:46:44.922 [PLANETINFO OVER]: Done 14:46:44.923 [PLANETINFO LOGGING]: 7 126 14:46:45.931 [planetinfo.record]: seed = 16 87 173 242 14:46:45.931 [planetinfo.record]: coordinates = 87 231 14:46:45.931 [planetinfo.record]: government = 2; 14:46:45.931 [planetinfo.record]: economy = 7; 14:46:45.931 [planetinfo.record]: techlevel = 4; 14:46:45.931 [planetinfo.record]: population = 26; 14:46:45.931 [planetinfo.record]: productivity = 3744; 14:46:45.931 [planetinfo.record]: name = "Enerqu"; 14:46:45.931 [planetinfo.record]: inhabitant = "Slimy Lizard"; 14:46:45.931 [planetinfo.record]: inhabitants = "Slimy Lizards"; 14:46:45.931 [planetinfo.record]: description = "The planet Enerqu is well known for its inhabitants’ unusual mating traditions but scourged by deadly tree grubs."; 14:46:45.948 [planetinfo.record]: air_color = 0.666025, 0.836402, 0.783031, 1; 14:46:45.948 [planetinfo.record]: cloud_alpha = 1.000000; 14:46:45.948 [planetinfo.record]: cloud_color = 0.511719, 0.506144, 0.505722, 1; 14:46:45.948 [planetinfo.record]: cloud_fraction = 0.470000; 14:46:45.948 [planetinfo.record]: land_color = 0.642799, 0.623906, 0.66, 1; 14:46:45.948 [planetinfo.record]: land_fraction = 0.490000; 14:46:45.948 [planetinfo.record]: polar_cloud_color = 0.730273, 0.725301, 0.724925, 1; 14:46:45.948 [planetinfo.record]: polar_land_color = 0.927915, 0.92123, 0.934, 1; 14:46:45.948 [planetinfo.record]: polar_sea_color = 0.843501, 0.948047, 0.764733, 1; 14:46:45.948 [planetinfo.record]: sea_color = 0.290365, 0.519531, 0.117706, 1; 14:46:45.948 [planetinfo.record]: rotation_speed = 0.001022 14:46:45.948 [planetinfo.record]: planet zpos = 409800.000000 14:46:45.948 [planetinfo.record]: sun_radius = 111620.100403 14:46:45.948 [planetinfo.record]: sun_vector = -0.527 0.726 0.442 14:46:45.948 [planetinfo.record]: sun_distance = 785450 14:46:45.948 [planetinfo.record]: corona_flare = 0.087112 14:46:45.948 [planetinfo.record]: corona_hues = 0.830376 14:46:45.948 [planetinfo.record]: sun_color = 0.684741, 0.49684, 0.197123, 1 14:46:45.949 [planetinfo.record]: corona_shimmer = 0.263678 14:46:45.949 [planetinfo.record]: station_vector = 0.598 -0.638 0.484 14:46:45.949 [planetinfo.record]: station = coriolis 14:46:51.311 [PLANETINFO OVER]: Done 14:46:51.311 [PLANETINFO LOGGING]: 7 127 14:46:52.314 [planetinfo.record]: seed = 38 158 159 73 14:46:52.314 [planetinfo.record]: coordinates = 158 122 14:46:52.314 [planetinfo.record]: government = 4; 14:46:52.314 [planetinfo.record]: economy = 2; 14:46:52.314 [planetinfo.record]: techlevel = 9; 14:46:52.314 [planetinfo.record]: population = 43; 14:46:52.314 [planetinfo.record]: productivity = 22016; 14:46:52.314 [planetinfo.record]: name = "Esxear"; 14:46:52.314 [planetinfo.record]: inhabitant = "Small Yellow Fat Humanoid"; 14:46:52.314 [planetinfo.record]: inhabitants = "Small Yellow Fat Humanoids"; 14:46:52.314 [planetinfo.record]: description = "This world is ravaged by occasional solar activity."; 14:46:52.347 [planetinfo.record]: air_color = 0.712908, 0.55843, 0.907295, 1; 14:46:52.347 [planetinfo.record]: cloud_alpha = 1.000000; 14:46:52.347 [planetinfo.record]: cloud_color = 0.724609, 0.322678, 0.586445, 1; 14:46:52.347 [planetinfo.record]: cloud_fraction = 0.500000; 14:46:52.347 [planetinfo.record]: land_color = 0.529724, 0.66, 0.319688, 1; 14:46:52.347 [planetinfo.record]: land_fraction = 0.630000; 14:46:52.348 [planetinfo.record]: polar_cloud_color = 0.826074, 0.539691, 0.72763, 1; 14:46:52.348 [planetinfo.record]: polar_land_color = 0.88791, 0.934, 0.813602, 1; 14:46:52.348 [planetinfo.record]: polar_sea_color = 0.905472, 0.935352, 0.88694, 1; 14:46:52.348 [planetinfo.record]: sea_color = 0.563878, 0.646484, 0.512642, 1; 14:46:52.348 [planetinfo.record]: rotation_speed = 0.000174 14:46:52.348 [planetinfo.record]: planet zpos = 686140.000000 14:46:52.348 [planetinfo.record]: sun_radius = 129286.678162 14:46:52.348 [planetinfo.record]: sun_vector = 0.269 -0.538 -0.799 14:46:52.348 [planetinfo.record]: sun_distance = 1055600 14:46:52.348 [planetinfo.record]: corona_flare = 0.081541 14:46:52.348 [planetinfo.record]: corona_hues = 0.528839 14:46:52.348 [planetinfo.record]: sun_color = 0.631825, 0.705097, 0.715176, 1 14:46:52.348 [planetinfo.record]: corona_shimmer = 0.420286 14:46:52.348 [planetinfo.record]: station_vector = -0.556 -0.718 0.419 14:46:52.348 [planetinfo.record]: station = coriolis 14:46:57.174 [PLANETINFO OVER]: Done 14:46:57.175 [PLANETINFO LOGGING]: 7 128 14:46:58.181 [planetinfo.record]: seed = 36 246 169 162 14:46:58.181 [planetinfo.record]: coordinates = 246 74 14:46:58.181 [planetinfo.record]: government = 4; 14:46:58.181 [planetinfo.record]: economy = 2; 14:46:58.181 [planetinfo.record]: techlevel = 9; 14:46:58.181 [planetinfo.record]: population = 43; 14:46:58.181 [planetinfo.record]: productivity = 22016; 14:46:58.181 [planetinfo.record]: name = "Xexean"; 14:46:58.181 [planetinfo.record]: inhabitant = "Large Harmless Fat Feline"; 14:46:58.181 [planetinfo.record]: inhabitants = "Large Harmless Fat Felines"; 14:46:58.181 [planetinfo.record]: description = "The world Xexean is beset by deadly earthquakes."; 14:46:58.187 [planetinfo.record]: air_color = 0.584715, 0.559676, 0.858516, 1; 14:46:58.188 [planetinfo.record]: cloud_alpha = 1.000000; 14:46:58.188 [planetinfo.record]: cloud_color = 0.389063, 0.320679, 0.578125, 1; 14:46:58.188 [planetinfo.record]: cloud_fraction = 0.370000; 14:46:58.188 [planetinfo.record]: land_color = 0.11799, 0.0567188, 0.66, 1; 14:46:58.188 [planetinfo.record]: land_fraction = 0.320000; 14:46:58.188 [planetinfo.record]: polar_cloud_color = 0.604787, 0.548589, 0.760156, 1; 14:46:58.188 [planetinfo.record]: polar_land_color = 0.742243, 0.720566, 0.934, 1; 14:46:58.188 [planetinfo.record]: polar_sea_color = 0.915875, 0.935352, 0.884474, 1; 14:46:58.188 [planetinfo.record]: sea_color = 0.592638, 0.646484, 0.505824, 1; 14:46:58.188 [planetinfo.record]: rotation_speed = 0.001322 14:46:58.188 [planetinfo.record]: planet zpos = 321660.000000 14:46:58.188 [planetinfo.record]: sun_radius = 63423.557434 14:46:58.188 [planetinfo.record]: sun_vector = 0.166 0.776 0.609 14:46:58.188 [planetinfo.record]: sun_distance = 643320 14:46:58.188 [planetinfo.record]: corona_flare = 0.068474 14:46:58.188 [planetinfo.record]: corona_hues = 0.636650 14:46:58.188 [planetinfo.record]: sun_color = 0.669782, 0.653247, 0.652734, 1 14:46:58.189 [planetinfo.record]: corona_shimmer = 0.517883 14:46:58.189 [planetinfo.record]: station_vector = 0.653 0.725 0.219 14:46:58.189 [planetinfo.record]: station = coriolis 14:47:03.124 [PLANETINFO OVER]: Done 14:47:03.125 [PLANETINFO LOGGING]: 7 129 14:47:04.128 [planetinfo.record]: seed = 90 1 11 96 14:47:04.128 [planetinfo.record]: coordinates = 1 123 14:47:04.128 [planetinfo.record]: government = 3; 14:47:04.128 [planetinfo.record]: economy = 3; 14:47:04.128 [planetinfo.record]: techlevel = 7; 14:47:04.128 [planetinfo.record]: population = 35; 14:47:04.128 [planetinfo.record]: productivity = 13720; 14:47:04.128 [planetinfo.record]: name = "Isri"; 14:47:04.128 [planetinfo.record]: inhabitant = "Human Colonial"; 14:47:04.128 [planetinfo.record]: inhabitants = "Human Colonials"; 14:47:04.128 [planetinfo.record]: description = "The planet Isri is reasonably fabled for its fabulous cuisine and its ancient Isriian Ouetonle banana plantations."; 14:47:04.156 [planetinfo.record]: air_color = 0.65235, 0.88605, 0.909896, 1; 14:47:04.156 [planetinfo.record]: cloud_alpha = 1.000000; 14:47:04.156 [planetinfo.record]: cloud_color = 0.679493, 0.732422, 0.549316, 1; 14:47:04.156 [planetinfo.record]: cloud_fraction = 0.340000; 14:47:04.156 [planetinfo.record]: land_color = 0.460215, 0.66, 0.425391, 1; 14:47:04.156 [planetinfo.record]: land_fraction = 0.250000; 14:47:04.156 [planetinfo.record]: polar_cloud_color = 0.792121, 0.82959, 0.699966, 1; 14:47:04.156 [planetinfo.record]: polar_land_color = 0.863319, 0.934, 0.850998, 1; 14:47:04.156 [planetinfo.record]: polar_sea_color = 0.935742, 0.907984, 0.875522, 1; 14:47:04.157 [planetinfo.record]: sea_color = 0.642578, 0.566333, 0.477164, 1; 14:47:04.157 [planetinfo.record]: rotation_speed = 0.004848 14:47:04.157 [planetinfo.record]: planet zpos = 309870.000000 14:47:04.157 [planetinfo.record]: sun_radius = 55046.181335 14:47:04.157 [planetinfo.record]: sun_vector = 0.170 -0.958 -0.230 14:47:04.157 [planetinfo.record]: sun_distance = 591570 14:47:04.157 [planetinfo.record]: corona_flare = 0.071634 14:47:04.157 [planetinfo.record]: corona_hues = 0.745758 14:47:04.157 [planetinfo.record]: sun_color = 0.78093, 0.782566, 0.786484, 1 14:47:04.157 [planetinfo.record]: corona_shimmer = 0.471563 14:47:04.157 [planetinfo.record]: station_vector = -0.284 0.949 0.135 14:47:04.157 [planetinfo.record]: station = coriolis 14:47:08.608 [PLANETINFO OVER]: Done 14:47:08.609 [PLANETINFO LOGGING]: 7 130 14:47:09.629 [planetinfo.record]: seed = 184 123 101 151 14:47:09.629 [planetinfo.record]: coordinates = 123 62 14:47:09.629 [planetinfo.record]: government = 7; 14:47:09.629 [planetinfo.record]: economy = 6; 14:47:09.629 [planetinfo.record]: techlevel = 8; 14:47:09.629 [planetinfo.record]: population = 46; 14:47:09.629 [planetinfo.record]: productivity = 16192; 14:47:09.629 [planetinfo.record]: name = "Tiatza"; 14:47:09.629 [planetinfo.record]: inhabitant = "Human Colonial"; 14:47:09.629 [planetinfo.record]: inhabitants = "Human Colonials"; 14:47:09.629 [planetinfo.record]: description = "Tiatza is cursed by deadly civil war."; 14:47:09.645 [planetinfo.record]: air_color = 0.473485, 0.655188, 0.86632, 1; 14:47:09.646 [planetinfo.record]: cloud_alpha = 1.000000; 14:47:09.646 [planetinfo.record]: cloud_color = 0.143341, 0.601562, 0.429729, 1; 14:47:09.646 [planetinfo.record]: cloud_fraction = 0.420000; 14:47:09.646 [planetinfo.record]: land_color = 0.66, 0.641147, 0.358359, 1; 14:47:09.646 [planetinfo.record]: land_fraction = 0.420000; 14:47:09.646 [planetinfo.record]: polar_cloud_color = 0.403791, 0.770703, 0.633111, 1; 14:47:09.646 [planetinfo.record]: polar_land_color = 0.934, 0.92733, 0.827283, 1; 14:47:09.646 [planetinfo.record]: polar_sea_color = 0.87563, 0.927148, 0.890925, 1; 14:47:09.646 [planetinfo.record]: sea_color = 0.566592, 0.728516, 0.614663, 1; 14:47:09.646 [planetinfo.record]: rotation_speed = 0.004111 14:47:09.646 [planetinfo.record]: planet zpos = 709650.000000 14:47:09.647 [planetinfo.record]: sun_radius = 137556.048431 14:47:09.647 [planetinfo.record]: sun_vector = 0.512 -0.844 -0.161 14:47:09.647 [planetinfo.record]: sun_distance = 946200 14:47:09.647 [planetinfo.record]: corona_flare = 0.000148 14:47:09.647 [planetinfo.record]: corona_hues = 0.754066 14:47:09.647 [planetinfo.record]: sun_color = 0.52551, 0.644155, 0.671368, 1 14:47:09.647 [planetinfo.record]: corona_shimmer = 0.259027 14:47:09.647 [planetinfo.record]: station_vector = -0.383 0.181 0.906 14:47:09.647 [planetinfo.record]: station = coriolis 14:47:14.441 [PLANETINFO OVER]: Done 14:47:14.442 [PLANETINFO LOGGING]: 7 131 14:47:15.448 [planetinfo.record]: seed = 206 77 55 4 14:47:15.448 [planetinfo.record]: coordinates = 77 100 14:47:15.449 [planetinfo.record]: government = 1; 14:47:15.449 [planetinfo.record]: economy = 6; 14:47:15.449 [planetinfo.record]: techlevel = 3; 14:47:15.449 [planetinfo.record]: population = 20; 14:47:15.449 [planetinfo.record]: productivity = 3200; 14:47:15.449 [planetinfo.record]: name = "Zaveusge"; 14:47:15.449 [planetinfo.record]: inhabitant = "Human Colonial"; 14:47:15.449 [planetinfo.record]: inhabitants = "Human Colonials"; 14:47:15.449 [planetinfo.record]: description = "The planet Zaveusge is well known for its inhabitants’ ancient mating traditions but cursed by vicious killer cats."; 14:47:15.460 [planetinfo.record]: air_color = 0.721854, 0.813662, 0.902092, 1; 14:47:15.460 [planetinfo.record]: cloud_alpha = 1.000000; 14:47:15.460 [planetinfo.record]: cloud_color = 0.708984, 0.708984, 0.708984, 1; 14:47:15.460 [planetinfo.record]: cloud_fraction = 0.530000; 14:47:15.461 [planetinfo.record]: land_color = 0.66, 0.244579, 0.234609, 1; 14:47:15.461 [planetinfo.record]: land_fraction = 0.570000; 14:47:15.461 [planetinfo.record]: polar_cloud_color = 0.819043, 0.819043, 0.819043, 1; 14:47:15.461 [planetinfo.record]: polar_land_color = 0.934, 0.787029, 0.783502, 1; 14:47:15.461 [planetinfo.record]: polar_sea_color = 0.917839, 0.935938, 0.885576, 1; 14:47:15.461 [planetinfo.record]: sea_color = 0.591073, 0.640625, 0.50274, 1; 14:47:15.461 [planetinfo.record]: rotation_speed = 0.004505 14:47:15.461 [planetinfo.record]: planet zpos = 391700.000000 14:47:15.461 [planetinfo.record]: sun_radius = 123217.310944 14:47:15.461 [planetinfo.record]: sun_vector = 0.593 0.658 -0.465 14:47:15.462 [planetinfo.record]: sun_distance = 705060 14:47:15.462 [planetinfo.record]: corona_flare = 0.016666 14:47:15.462 [planetinfo.record]: corona_hues = 0.507866 14:47:15.462 [planetinfo.record]: sun_color = 0.768762, 0.483692, 0.343389, 1 14:47:15.462 [planetinfo.record]: corona_shimmer = 0.944649 14:47:15.462 [planetinfo.record]: station_vector = -0.015 0.496 0.868 14:47:15.462 [planetinfo.record]: station = coriolis 14:47:20.635 [PLANETINFO OVER]: Done 14:47:20.636 [PLANETINFO LOGGING]: 7 132 14:47:21.639 [planetinfo.record]: seed = 204 195 97 131 14:47:21.639 [planetinfo.record]: coordinates = 195 8 14:47:21.639 [planetinfo.record]: government = 1; 14:47:21.639 [planetinfo.record]: economy = 2; 14:47:21.639 [planetinfo.record]: techlevel = 9; 14:47:21.639 [planetinfo.record]: population = 40; 14:47:21.639 [planetinfo.record]: productivity = 12800; 14:47:21.640 [planetinfo.record]: name = "Geatiar"; 14:47:21.640 [planetinfo.record]: inhabitant = "Human Colonial"; 14:47:21.640 [planetinfo.record]: inhabitants = "Human Colonials"; 14:47:21.640 [planetinfo.record]: description = "The planet Geatiar is mildly notable for Geatiarian lethal water."; 14:47:21.643 [planetinfo.record]: air_color = 0.678437, 0.8552, 0.902092, 1; 14:47:21.643 [planetinfo.record]: cloud_alpha = 1.000000; 14:47:21.643 [planetinfo.record]: cloud_color = 0.643339, 0.708984, 0.606514, 1; 14:47:21.643 [planetinfo.record]: cloud_fraction = 0.390000; 14:47:21.643 [planetinfo.record]: land_color = 0.0721875, 0.66, 0.604893, 1; 14:47:21.643 [planetinfo.record]: land_fraction = 0.460000; 14:47:21.643 [planetinfo.record]: polar_cloud_color = 0.771646, 0.819043, 0.745057, 1; 14:47:21.643 [planetinfo.record]: polar_land_color = 0.726039, 0.934, 0.914504, 1; 14:47:21.643 [planetinfo.record]: polar_sea_color = 0.893426, 0.929297, 0.873303, 1; 14:47:21.644 [planetinfo.record]: sea_color = 0.597865, 0.707031, 0.536626, 1; 14:47:21.644 [planetinfo.record]: rotation_speed = 0.004425 14:47:21.644 [planetinfo.record]: planet zpos = 566850.000000 14:47:21.644 [planetinfo.record]: sun_radius = 91603.384399 14:47:21.644 [planetinfo.record]: sun_vector = -0.118 -0.926 0.358 14:47:21.644 [planetinfo.record]: sun_distance = 906960 14:47:21.644 [planetinfo.record]: corona_flare = 0.055545 14:47:21.644 [planetinfo.record]: corona_hues = 0.823235 14:47:21.644 [planetinfo.record]: sun_color = 0.827725, 0.828955, 0.729962, 1 14:47:21.644 [planetinfo.record]: corona_shimmer = 0.345946 14:47:21.644 [planetinfo.record]: station_vector = -0.234 0.257 0.938 14:47:21.644 [planetinfo.record]: station = coriolis 14:47:25.950 [PLANETINFO OVER]: Done 14:47:25.951 [PLANETINFO LOGGING]: 7 133 14:47:26.954 [planetinfo.record]: seed = 130 106 163 81 14:47:26.954 [planetinfo.record]: coordinates = 106 151 14:47:26.954 [planetinfo.record]: government = 0; 14:47:26.954 [planetinfo.record]: economy = 7; 14:47:26.954 [planetinfo.record]: techlevel = 2; 14:47:26.954 [planetinfo.record]: population = 16; 14:47:26.954 [planetinfo.record]: productivity = 1536; 14:47:26.954 [planetinfo.record]: name = "Atbea"; 14:47:26.954 [planetinfo.record]: inhabitant = "Yellow Furry Feline"; 14:47:26.954 [planetinfo.record]: inhabitants = "Yellow Furry Felines"; 14:47:26.954 [planetinfo.record]: description = "This planet is fabled for its exciting Zero-G cricket."; 14:47:26.972 [planetinfo.record]: air_color = 0.677223, 0.705341, 0.997049, 1; 14:47:26.972 [planetinfo.record]: cloud_alpha = 1.000000; 14:47:26.972 [planetinfo.record]: cloud_color = 0.632988, 0.723276, 0.994141, 1; 14:47:26.972 [planetinfo.record]: cloud_fraction = 0.370000; 14:47:26.972 [planetinfo.record]: land_color = 0.66, 0.313021, 0.244922, 1; 14:47:26.972 [planetinfo.record]: land_fraction = 0.670000; 14:47:26.972 [planetinfo.record]: polar_cloud_color = 0.732264, 0.786039, 0.947363, 1; 14:47:26.972 [planetinfo.record]: polar_land_color = 0.934, 0.811243, 0.78715, 1; 14:47:26.972 [planetinfo.record]: polar_sea_color = 0.869029, 0.921875, 0.909489, 1; 14:47:26.972 [planetinfo.record]: sea_color = 0.602112, 0.78125, 0.739264, 1; 14:47:26.972 [planetinfo.record]: rotation_speed = 0.004233 14:47:26.972 [planetinfo.record]: planet zpos = 444920.000000 14:47:26.972 [planetinfo.record]: sun_radius = 63301.535339 14:47:26.972 [planetinfo.record]: sun_vector = -0.020 -0.968 -0.250 14:47:26.972 [planetinfo.record]: sun_distance = 730940 14:47:26.972 [planetinfo.record]: corona_flare = 0.071323 14:47:26.972 [planetinfo.record]: corona_hues = 0.538429 14:47:26.972 [planetinfo.record]: sun_color = 0.686517, 0.498887, 0.393994, 1 14:47:26.972 [planetinfo.record]: corona_shimmer = 0.324317 14:47:26.973 [planetinfo.record]: station_vector = -0.209 -0.961 0.182 14:47:26.973 [planetinfo.record]: station = coriolis 14:47:31.815 [PLANETINFO OVER]: Done 14:47:31.816 [PLANETINFO LOGGING]: 7 134 14:47:32.820 [planetinfo.record]: seed = 96 180 29 23 14:47:32.820 [planetinfo.record]: coordinates = 180 15 14:47:32.820 [planetinfo.record]: government = 4; 14:47:32.820 [planetinfo.record]: economy = 7; 14:47:32.820 [planetinfo.record]: techlevel = 2; 14:47:32.821 [planetinfo.record]: population = 20; 14:47:32.821 [planetinfo.record]: productivity = 3840; 14:47:32.821 [planetinfo.record]: name = "Tiqubied"; 14:47:32.821 [planetinfo.record]: inhabitant = "Human Colonial"; 14:47:32.821 [planetinfo.record]: inhabitants = "Human Colonials"; 14:47:32.821 [planetinfo.record]: description = "This planet is plagued by frequent solar activity."; 14:47:32.852 [planetinfo.record]: air_color = 0.519729, 0.561252, 0.868271, 1; 14:47:32.852 [planetinfo.record]: cloud_alpha = 1.000000; 14:47:32.852 [planetinfo.record]: cloud_color = 0.239647, 0.36607, 0.607422, 1; 14:47:32.852 [planetinfo.record]: cloud_fraction = 0.330000; 14:47:32.852 [planetinfo.record]: land_color = 0.619141, 0.519906, 0.493378, 1; 14:47:32.852 [planetinfo.record]: land_fraction = 0.350000; 14:47:32.852 [planetinfo.record]: polar_cloud_color = 0.480694, 0.581291, 0.77334, 1; 14:47:32.852 [planetinfo.record]: polar_land_color = 0.938086, 0.900497, 0.890449, 1; 14:47:32.852 [planetinfo.record]: polar_sea_color = 0.783386, 0.94375, 0.887372, 1; 14:47:32.852 [planetinfo.record]: sea_color = 0.180176, 0.5625, 0.428089, 1; 14:47:32.852 [planetinfo.record]: rotation_speed = 0.002186 14:47:32.852 [planetinfo.record]: planet zpos = 526680.000000 14:47:32.852 [planetinfo.record]: sun_radius = 74671.495972 14:47:32.852 [planetinfo.record]: sun_vector = -0.964 0.227 -0.141 14:47:32.852 [planetinfo.record]: sun_distance = 1005480 14:47:32.852 [planetinfo.record]: corona_flare = 0.062199 14:47:32.852 [planetinfo.record]: corona_hues = 0.885002 14:47:32.852 [planetinfo.record]: sun_color = 0.688293, 0.469359, 0.456154, 1 14:47:32.852 [planetinfo.record]: corona_shimmer = 0.846653 14:47:32.853 [planetinfo.record]: station_vector = 0.441 -0.895 0.074 14:47:32.853 [planetinfo.record]: station = coriolis 14:47:37.632 [PLANETINFO OVER]: Done 14:47:37.633 [PLANETINFO LOGGING]: 7 135 14:47:38.636 [planetinfo.record]: seed = 118 152 207 25 14:47:38.637 [planetinfo.record]: coordinates = 152 166 14:47:38.637 [planetinfo.record]: government = 6; 14:47:38.637 [planetinfo.record]: economy = 6; 14:47:38.637 [planetinfo.record]: techlevel = 4; 14:47:38.637 [planetinfo.record]: population = 29; 14:47:38.637 [planetinfo.record]: productivity = 9280; 14:47:38.637 [planetinfo.record]: name = "Oredaris"; 14:47:38.637 [planetinfo.record]: inhabitant = "Green Insect"; 14:47:38.637 [planetinfo.record]: inhabitants = "Green Insects"; 14:47:38.637 [planetinfo.record]: description = "This planet is notable for the Oredarisian edible grub and the Oredarisian tree snake."; 14:47:38.661 [planetinfo.record]: air_color = 0.717272, 0.801675, 0.920303, 1; 14:47:38.661 [planetinfo.record]: cloud_alpha = 1.000000; 14:47:38.661 [planetinfo.record]: cloud_color = 0.715942, 0.763672, 0.752485, 1; 14:47:38.661 [planetinfo.record]: cloud_fraction = 0.350000; 14:47:38.661 [planetinfo.record]: land_color = 0.66, 0.391875, 0.391875, 1; 14:47:38.661 [planetinfo.record]: land_fraction = 0.580000; 14:47:38.661 [planetinfo.record]: polar_cloud_color = 0.810697, 0.843652, 0.835928, 1; 14:47:38.661 [planetinfo.record]: polar_land_color = 0.934, 0.839141, 0.839141, 1; 14:47:38.661 [planetinfo.record]: polar_sea_color = 0.943196, 0.892871, 0.947461, 1; 14:47:38.661 [planetinfo.record]: sea_color = 0.515931, 0.404305, 0.525391, 1; 14:47:38.662 [planetinfo.record]: rotation_speed = 0.003902 14:47:38.662 [planetinfo.record]: planet zpos = 790800.000000 14:47:38.662 [planetinfo.record]: sun_radius = 141070.405273 14:47:38.662 [planetinfo.record]: sun_vector = 0.206 -0.977 -0.062 14:47:38.662 [planetinfo.record]: sun_distance = 948960 14:47:38.662 [planetinfo.record]: corona_flare = 0.057979 14:47:38.662 [planetinfo.record]: corona_hues = 0.539932 14:47:38.662 [planetinfo.record]: sun_color = 0.591049, 0.632963, 0.698529, 1 14:47:38.664 [planetinfo.record]: corona_shimmer = 0.231390 14:47:38.664 [planetinfo.record]: station_vector = 0.067 0.581 0.811 14:47:38.664 [planetinfo.record]: station = coriolis 14:47:44.061 [PLANETINFO OVER]: Done 14:47:44.062 [PLANETINFO LOGGING]: 7 136 14:47:45.066 [planetinfo.record]: seed = 116 125 25 225 14:47:45.066 [planetinfo.record]: coordinates = 125 10 14:47:45.066 [planetinfo.record]: government = 6; 14:47:45.066 [planetinfo.record]: economy = 2; 14:47:45.067 [planetinfo.record]: techlevel = 9; 14:47:45.067 [planetinfo.record]: population = 45; 14:47:45.067 [planetinfo.record]: productivity = 28800; 14:47:45.067 [planetinfo.record]: name = "Leesusen"; 14:47:45.067 [planetinfo.record]: inhabitant = "Human Colonial"; 14:47:45.067 [planetinfo.record]: inhabitants = "Human Colonials"; 14:47:45.067 [planetinfo.record]: description = "The planet Leesusen is a boring planet."; 14:47:45.076 [planetinfo.record]: air_color = 0.631618, 0.88258, 0.821594, 1; 14:47:45.076 [planetinfo.record]: cloud_alpha = 1.000000; 14:47:45.076 [planetinfo.record]: cloud_color = 0.650391, 0.531698, 0.485252, 1; 14:47:45.076 [planetinfo.record]: cloud_fraction = 0.220000; 14:47:45.076 [planetinfo.record]: land_color = 0.0902344, 0.539815, 0.66, 1; 14:47:45.076 [planetinfo.record]: land_fraction = 0.710000; 14:47:45.076 [planetinfo.record]: polar_cloud_color = 0.792676, 0.702264, 0.666885, 1; 14:47:45.076 [planetinfo.record]: polar_land_color = 0.732424, 0.89148, 0.934, 1; 14:47:45.076 [planetinfo.record]: polar_sea_color = 0.912811, 0.933398, 0.879619, 1; 14:47:45.076 [planetinfo.record]: sea_color = 0.607256, 0.666016, 0.51252, 1; 14:47:45.077 [planetinfo.record]: rotation_speed = 0.002582 14:47:45.077 [planetinfo.record]: planet zpos = 447580.000000 14:47:45.077 [planetinfo.record]: sun_radius = 63712.674255 14:47:45.077 [planetinfo.record]: sun_vector = -0.275 0.962 0.006 14:47:45.077 [planetinfo.record]: sun_distance = 639400 14:47:45.077 [planetinfo.record]: corona_flare = 0.070743 14:47:45.077 [planetinfo.record]: corona_hues = 0.880280 14:47:45.077 [planetinfo.record]: sun_color = 0.323076, 0.61326, 0.807071, 1 14:47:45.077 [planetinfo.record]: corona_shimmer = 0.484383 14:47:45.084 [planetinfo.record]: station_vector = 0.141 -0.953 0.268 14:47:45.084 [planetinfo.record]: station = coriolis 14:47:49.881 [PLANETINFO OVER]: Done 14:47:49.881 [PLANETINFO LOGGING]: 7 137 14:47:50.884 [planetinfo.record]: seed = 170 18 59 68 14:47:50.884 [planetinfo.record]: coordinates = 18 200 14:47:50.884 [planetinfo.record]: government = 5; 14:47:50.884 [planetinfo.record]: economy = 0; 14:47:50.884 [planetinfo.record]: techlevel = 12; 14:47:50.884 [planetinfo.record]: population = 54; 14:47:50.884 [planetinfo.record]: productivity = 38880; 14:47:50.884 [planetinfo.record]: name = "Zarila"; 14:47:50.884 [planetinfo.record]: inhabitant = "Human Colonial"; 14:47:50.884 [planetinfo.record]: inhabitants = "Human Colonials"; 14:47:50.884 [planetinfo.record]: description = "The planet Zarila is cursed by deadly civil war."; 14:47:50.908 [planetinfo.record]: air_color = 0.486627, 0.687202, 0.842256, 1; 14:47:50.908 [planetinfo.record]: cloud_alpha = 1.000000; 14:47:50.908 [planetinfo.record]: cloud_color = 0.175743, 0.529297, 0.288991, 1; 14:47:50.908 [planetinfo.record]: cloud_fraction = 0.360000; 14:47:50.908 [planetinfo.record]: land_color = 0.523438, 0.510467, 0.509125, 1; 14:47:50.908 [planetinfo.record]: land_fraction = 0.620000; 14:47:50.908 [planetinfo.record]: polar_cloud_color = 0.430006, 0.738184, 0.528719, 1; 14:47:50.908 [planetinfo.record]: polar_land_color = 0.947656, 0.941785, 0.941178, 1; 14:47:50.908 [planetinfo.record]: polar_sea_color = 0.939648, 0.910009, 0.883857, 1; 14:47:50.908 [planetinfo.record]: sea_color = 0.603516, 0.527369, 0.460181, 1; 14:47:50.908 [planetinfo.record]: rotation_speed = 0.003588 14:47:50.908 [planetinfo.record]: planet zpos = 462960.000000 14:47:50.908 [planetinfo.record]: sun_radius = 82996.802673 14:47:50.908 [planetinfo.record]: sun_vector = -0.568 0.799 -0.198 14:47:50.908 [planetinfo.record]: sun_distance = 810180 14:47:50.908 [planetinfo.record]: corona_flare = 0.009550 14:47:50.908 [planetinfo.record]: corona_hues = 0.835251 14:47:50.908 [planetinfo.record]: sun_color = 0.319539, 0.606942, 0.667947, 1 14:47:50.909 [planetinfo.record]: corona_shimmer = 0.416043 14:47:50.909 [planetinfo.record]: station_vector = 0.139 0.745 0.652 14:47:50.909 [planetinfo.record]: station = icosahedron 14:47:55.898 [PLANETINFO OVER]: Done 14:47:55.900 [PLANETINFO LOGGING]: 7 138 14:47:56.916 [planetinfo.record]: seed = 8 217 213 109 14:47:56.916 [planetinfo.record]: coordinates = 217 117 14:47:56.916 [planetinfo.record]: government = 1; 14:47:56.916 [planetinfo.record]: economy = 7; 14:47:56.917 [planetinfo.record]: techlevel = 2; 14:47:56.917 [planetinfo.record]: population = 17; 14:47:56.917 [planetinfo.record]: productivity = 2040; 14:47:56.917 [planetinfo.record]: name = "Ditegere"; 14:47:56.917 [planetinfo.record]: inhabitant = "Blue Fat Humanoid"; 14:47:56.917 [planetinfo.record]: inhabitants = "Blue Fat Humanoids"; 14:47:56.917 [planetinfo.record]: description = "The planet Ditegere is most well known for its hoopy night life."; 14:47:56.932 [planetinfo.record]: air_color = 0.468891, 0.557424, 0.900791, 1; 14:47:56.932 [planetinfo.record]: cloud_alpha = 1.000000; 14:47:56.932 [planetinfo.record]: cloud_color = 0.112923, 0.469141, 0.705078, 1; 14:47:56.932 [planetinfo.record]: cloud_fraction = 0.230000; 14:47:56.932 [planetinfo.record]: land_color = 0.488937, 0.568359, 0.0333023, 1; 14:47:56.932 [planetinfo.record]: land_fraction = 0.460000; 14:47:56.932 [planetinfo.record]: polar_cloud_color = 0.38829, 0.646358, 0.817285, 1; 14:47:56.932 [planetinfo.record]: polar_land_color = 0.910215, 0.943164, 0.721189, 1; 14:47:56.932 [planetinfo.record]: polar_sea_color = 0.935156, 0.902311, 0.87333, 1; 14:47:56.932 [planetinfo.record]: sea_color = 0.648438, 0.557338, 0.476956, 1; 14:47:56.932 [planetinfo.record]: rotation_speed = 0.000356 14:47:56.932 [planetinfo.record]: planet zpos = 826930.000000 14:47:56.932 [planetinfo.record]: sun_radius = 131403.335876 14:47:56.932 [planetinfo.record]: sun_vector = -0.058 -0.336 -0.940 14:47:56.932 [planetinfo.record]: sun_distance = 1144980 14:47:56.932 [planetinfo.record]: corona_flare = 0.099940 14:47:56.932 [planetinfo.record]: corona_hues = 0.939102 14:47:56.932 [planetinfo.record]: sun_color = 0.723914, 0.775274, 0.83638, 1 14:47:56.932 [planetinfo.record]: corona_shimmer = 0.717951 14:47:56.933 [planetinfo.record]: station_vector = -0.306 -0.945 0.117 14:47:56.933 [planetinfo.record]: station = coriolis 14:48:02.557 [PLANETINFO OVER]: Done 14:48:02.558 [PLANETINFO LOGGING]: 7 139 14:48:03.569 [planetinfo.record]: seed = 30 46 103 238 14:48:03.569 [planetinfo.record]: coordinates = 46 3 14:48:03.569 [planetinfo.record]: government = 3; 14:48:03.569 [planetinfo.record]: economy = 3; 14:48:03.569 [planetinfo.record]: techlevel = 8; 14:48:03.570 [planetinfo.record]: population = 39; 14:48:03.570 [planetinfo.record]: productivity = 15288; 14:48:03.570 [planetinfo.record]: name = "Rete"; 14:48:03.570 [planetinfo.record]: inhabitant = "Human Colonial"; 14:48:03.570 [planetinfo.record]: inhabitants = "Human Colonials"; 14:48:03.570 [planetinfo.record]: description = "Rete is most famous for its vast oceans and its fabulous goat soup."; 14:48:03.596 [planetinfo.record]: air_color = 0.6616, 0.885832, 0.880343, 1; 14:48:03.596 [planetinfo.record]: cloud_alpha = 1.000000; 14:48:03.596 [planetinfo.record]: cloud_color = 0.660156, 0.653548, 0.554428, 1; 14:48:03.596 [planetinfo.record]: cloud_fraction = 0.400000; 14:48:03.596 [planetinfo.record]: land_color = 0.66, 0.201094, 0.265627, 1; 14:48:03.596 [planetinfo.record]: land_fraction = 0.200000; 14:48:03.596 [planetinfo.record]: polar_cloud_color = 0.79707, 0.792084, 0.717285, 1; 14:48:03.596 [planetinfo.record]: polar_land_color = 0.934, 0.771645, 0.794476, 1; 14:48:03.596 [planetinfo.record]: polar_sea_color = 0.944141, 0.902308, 0.891125, 1; 14:48:03.596 [planetinfo.record]: sea_color = 0.558594, 0.459594, 0.433128, 1; 14:48:03.596 [planetinfo.record]: rotation_speed = 0.003358 14:48:03.596 [planetinfo.record]: planet zpos = 709060.000000 14:48:03.596 [planetinfo.record]: sun_radius = 204992.163696 14:48:03.596 [planetinfo.record]: sun_vector = 0.233 -0.924 0.303 14:48:03.596 [planetinfo.record]: sun_distance = 1289200 14:48:03.596 [planetinfo.record]: corona_flare = 0.081386 14:48:03.596 [planetinfo.record]: corona_hues = 0.886894 14:48:03.597 [planetinfo.record]: sun_color = 0.265989, 0.385853, 0.671896, 1 14:48:03.597 [planetinfo.record]: corona_shimmer = 0.794318 14:48:03.597 [planetinfo.record]: station_vector = 0.533 -0.846 0.005 14:48:03.597 [planetinfo.record]: station = coriolis 14:48:08.448 [PLANETINFO OVER]: Done 14:48:08.453 [PLANETINFO LOGGING]: 7 140 14:48:09.467 [planetinfo.record]: seed = 28 75 209 167 14:48:09.467 [planetinfo.record]: coordinates = 75 60 14:48:09.467 [planetinfo.record]: government = 3; 14:48:09.467 [planetinfo.record]: economy = 4; 14:48:09.467 [planetinfo.record]: techlevel = 8; 14:48:09.467 [planetinfo.record]: population = 40; 14:48:09.467 [planetinfo.record]: productivity = 13440; 14:48:09.467 [planetinfo.record]: name = "Soaxe"; 14:48:09.467 [planetinfo.record]: inhabitant = "Fierce Harmless Lizard"; 14:48:09.467 [planetinfo.record]: inhabitants = "Fierce Harmless Lizards"; 14:48:09.467 [planetinfo.record]: description = "This world is very well known for Soaxeian lethal water and the Soaxeian tree grub."; 14:48:09.492 [planetinfo.record]: air_color = 0.617333, 0.834937, 0.978188, 1; 14:48:09.492 [planetinfo.record]: cloud_alpha = 1.000000; 14:48:09.492 [planetinfo.record]: cloud_color = 0.461426, 0.9375, 0.558128, 1; 14:48:09.492 [planetinfo.record]: cloud_fraction = 0.460000; 14:48:09.492 [planetinfo.record]: land_color = 0.24305, 0.505859, 0.487381, 1; 14:48:09.492 [planetinfo.record]: land_fraction = 0.270000; 14:48:09.492 [planetinfo.record]: polar_cloud_color = 0.629288, 0.921875, 0.68872, 1; 14:48:09.492 [planetinfo.record]: polar_land_color = 0.826101, 0.949414, 0.940744, 1; 14:48:09.492 [planetinfo.record]: polar_sea_color = 0.919922, 0.88132, 0.807627, 1; 14:48:09.492 [planetinfo.record]: sea_color = 0.800781, 0.666373, 0.409775, 1; 14:48:09.492 [planetinfo.record]: rotation_speed = 0.000674 14:48:09.492 [planetinfo.record]: planet zpos = 515130.000000 14:48:09.492 [planetinfo.record]: sun_radius = 124521.524506 14:48:09.492 [planetinfo.record]: sun_vector = 0.062 -0.998 -0.002 14:48:09.492 [planetinfo.record]: sun_distance = 983430 14:48:09.492 [planetinfo.record]: corona_flare = 0.012889 14:48:09.492 [planetinfo.record]: corona_hues = 0.567581 14:48:09.492 [planetinfo.record]: sun_color = 0.768494, 0.52418, 0.523139, 1 14:48:09.492 [planetinfo.record]: corona_shimmer = 0.346983 14:48:09.493 [planetinfo.record]: station_vector = -0.337 0.933 0.126 14:48:09.493 [planetinfo.record]: station = coriolis 14:48:14.483 [PLANETINFO OVER]: Done 14:48:14.483 [PLANETINFO LOGGING]: 7 141 14:48:15.500 [planetinfo.record]: seed = 210 249 211 75 14:48:15.500 [planetinfo.record]: coordinates = 249 34 14:48:15.500 [planetinfo.record]: government = 2; 14:48:15.500 [planetinfo.record]: economy = 2; 14:48:15.500 [planetinfo.record]: techlevel = 7; 14:48:15.500 [planetinfo.record]: population = 33; 14:48:15.500 [planetinfo.record]: productivity = 12672; 14:48:15.500 [planetinfo.record]: name = "Mausdile"; 14:48:15.500 [planetinfo.record]: inhabitant = "Small Yellow Bony Feline"; 14:48:15.500 [planetinfo.record]: inhabitants = "Small Yellow Bony Felines"; 14:48:15.500 [planetinfo.record]: description = "The planet Mausdile is most famous for the Mausdileian deadly goat and its pink Mausdileian Xeal Xealweed plantations."; 14:48:15.511 [planetinfo.record]: air_color = 0.416638, 0.840305, 0.810094, 1; 14:48:15.511 [planetinfo.record]: cloud_alpha = 1.000000; 14:48:15.511 [planetinfo.record]: cloud_color = 0.523438, 0.426251, 0.0449829, 1; 14:48:15.511 [planetinfo.record]: cloud_fraction = 0.340000; 14:48:15.511 [planetinfo.record]: land_color = 0.430547, 0.509421, 0.66, 1; 14:48:15.511 [planetinfo.record]: land_fraction = 0.450000; 14:48:15.511 [planetinfo.record]: polar_cloud_color = 0.735547, 0.650192, 0.315337, 1; 14:48:15.512 [planetinfo.record]: polar_land_color = 0.852822, 0.880727, 0.934, 1; 14:48:15.512 [planetinfo.record]: polar_sea_color = 0.940625, 0.938723, 0.89194, 1; 14:48:15.512 [planetinfo.record]: sea_color = 0.59375, 0.588948, 0.470825, 1; 14:48:15.512 [planetinfo.record]: rotation_speed = 0.003002 14:48:15.512 [planetinfo.record]: planet zpos = 588100.000000 14:48:15.512 [planetinfo.record]: sun_radius = 123157.666473 14:48:15.512 [planetinfo.record]: sun_vector = 0.568 0.628 0.531 14:48:15.512 [planetinfo.record]: sun_distance = 1235010 14:48:15.512 [planetinfo.record]: corona_flare = 0.091794 14:48:15.512 [planetinfo.record]: corona_hues = 0.771492 14:48:15.512 [planetinfo.record]: sun_color = 0.534084, 0.725047, 0.841119, 1 14:48:15.513 [planetinfo.record]: corona_shimmer = 0.325654 14:48:15.513 [planetinfo.record]: station_vector = 0.216 0.193 0.957 14:48:15.513 [planetinfo.record]: station = coriolis 14:48:20.307 [PLANETINFO OVER]: Done 14:48:20.308 [PLANETINFO LOGGING]: 7 142 14:48:21.312 [planetinfo.record]: seed = 176 97 141 119 14:48:21.312 [planetinfo.record]: coordinates = 97 173 14:48:21.312 [planetinfo.record]: government = 6; 14:48:21.312 [planetinfo.record]: economy = 5; 14:48:21.312 [planetinfo.record]: techlevel = 6; 14:48:21.312 [planetinfo.record]: population = 36; 14:48:21.312 [planetinfo.record]: productivity = 14400; 14:48:21.312 [planetinfo.record]: name = "Tibiri"; 14:48:21.312 [planetinfo.record]: inhabitant = "Blue Fat Insect"; 14:48:21.312 [planetinfo.record]: inhabitants = "Blue Fat Insects"; 14:48:21.312 [planetinfo.record]: description = "This world is very notable for the Tibiriian tree snake and its unusual casinos."; 14:48:21.317 [planetinfo.record]: air_color = 0.744042, 0.647179, 0.83315, 1; 14:48:21.317 [planetinfo.record]: cloud_alpha = 1.000000; 14:48:21.317 [planetinfo.record]: cloud_color = 0.501953, 0.46666, 0.481549, 1; 14:48:21.317 [planetinfo.record]: cloud_fraction = 0.470000; 14:48:21.317 [planetinfo.record]: land_color = 0.232712, 0.430808, 0.589844, 1; 14:48:21.317 [planetinfo.record]: land_fraction = 0.480000; 14:48:21.317 [planetinfo.record]: polar_cloud_color = 0.725879, 0.69398, 0.707437, 1; 14:48:21.317 [planetinfo.record]: polar_land_color = 0.798577, 0.877586, 0.941016, 1; 14:48:21.317 [planetinfo.record]: polar_sea_color = 0.930664, 0.88863, 0.857956, 1; 14:48:21.317 [planetinfo.record]: sea_color = 0.693359, 0.568094, 0.476685, 1; 14:48:21.317 [planetinfo.record]: rotation_speed = 0.003467 14:48:21.317 [planetinfo.record]: planet zpos = 564600.000000 14:48:21.317 [planetinfo.record]: sun_radius = 128371.634674 14:48:21.317 [planetinfo.record]: sun_vector = -0.436 0.668 0.603 14:48:21.317 [planetinfo.record]: sun_distance = 846900 14:48:21.317 [planetinfo.record]: corona_flare = 0.076372 14:48:21.317 [planetinfo.record]: corona_hues = 0.669701 14:48:21.318 [planetinfo.record]: sun_color = 0.352185, 0.37743, 0.788412, 1 14:48:21.318 [planetinfo.record]: corona_shimmer = 0.442757 14:48:21.318 [planetinfo.record]: station_vector = 0.940 0.293 0.173 14:48:21.318 [planetinfo.record]: station = coriolis 14:48:25.465 [PLANETINFO OVER]: Done 14:48:25.466 [PLANETINFO LOGGING]: 7 143 14:48:26.469 [planetinfo.record]: seed = 198 94 255 69 14:48:26.469 [planetinfo.record]: coordinates = 94 96 14:48:26.469 [planetinfo.record]: government = 0; 14:48:26.469 [planetinfo.record]: economy = 2; 14:48:26.469 [planetinfo.record]: techlevel = 7; 14:48:26.469 [planetinfo.record]: population = 31; 14:48:26.469 [planetinfo.record]: productivity = 7936; 14:48:26.469 [planetinfo.record]: name = "Cecees"; 14:48:26.469 [planetinfo.record]: inhabitant = "Fierce Yellow Insect"; 14:48:26.469 [planetinfo.record]: inhabitants = "Fierce Yellow Insects"; 14:48:26.470 [planetinfo.record]: description = "This planet is reasonably famous for the Ceceesian spotted cat."; 14:48:26.504 [planetinfo.record]: air_color = 0.669713, 0.741951, 0.985992, 1; 14:48:26.504 [planetinfo.record]: cloud_alpha = 1.000000; 14:48:26.504 [planetinfo.record]: cloud_color = 0.611847, 0.846392, 0.960938, 1; 14:48:26.504 [planetinfo.record]: cloud_fraction = 0.190000; 14:48:26.504 [planetinfo.record]: land_color = 0.243378, 0.445271, 0.566406, 1; 14:48:26.504 [planetinfo.record]: land_fraction = 0.440000; 14:48:26.504 [planetinfo.record]: polar_cloud_color = 0.720715, 0.862955, 0.932422, 1; 14:48:26.504 [planetinfo.record]: polar_land_color = 0.808857, 0.892921, 0.943359, 1; 14:48:26.504 [planetinfo.record]: polar_sea_color = 0.863087, 0.921875, 0.877784, 1; 14:48:26.504 [planetinfo.record]: sea_color = 0.58197, 0.78125, 0.63179, 1; 14:48:26.504 [planetinfo.record]: rotation_speed = 0.002663 14:48:26.505 [planetinfo.record]: planet zpos = 502800.000000 14:48:26.505 [planetinfo.record]: sun_radius = 120140.270996 14:48:26.505 [planetinfo.record]: sun_vector = 0.015 0.127 -0.992 14:48:26.505 [planetinfo.record]: sun_distance = 838000 14:48:26.505 [planetinfo.record]: corona_flare = 0.091785 14:48:26.505 [planetinfo.record]: corona_hues = 0.782768 14:48:26.505 [planetinfo.record]: sun_color = 0.467559, 0.686051, 0.739389, 1 14:48:26.505 [planetinfo.record]: corona_shimmer = 0.922029 14:48:26.505 [planetinfo.record]: station_vector = 0.882 -0.231 0.411 14:48:26.505 [planetinfo.record]: station = coriolis 14:48:31.706 [PLANETINFO OVER]: Done 14:48:31.707 [PLANETINFO LOGGING]: 7 144 14:48:32.712 [planetinfo.record]: seed = 196 244 137 163 14:48:32.712 [planetinfo.record]: coordinates = 244 169 14:48:32.712 [planetinfo.record]: government = 0; 14:48:32.712 [planetinfo.record]: economy = 3; 14:48:32.712 [planetinfo.record]: techlevel = 4; 14:48:32.712 [planetinfo.record]: population = 20; 14:48:32.712 [planetinfo.record]: productivity = 4480; 14:48:32.712 [planetinfo.record]: name = "Gexequon"; 14:48:32.712 [planetinfo.record]: inhabitant = "Large Harmless Furry Rodent"; 14:48:32.712 [planetinfo.record]: inhabitants = "Large Harmless Furry Rodents"; 14:48:32.712 [planetinfo.record]: description = "The world Gexequon is beset by deadly earthquakes."; 14:48:32.727 [planetinfo.record]: air_color = 0.600488, 0.938725, 0.943717, 1; 14:48:32.727 [planetinfo.record]: cloud_alpha = 1.000000; 14:48:32.727 [planetinfo.record]: cloud_color = 0.811536, 0.833984, 0.423508, 1; 14:48:32.727 [planetinfo.record]: cloud_fraction = 0.300000; 14:48:32.727 [planetinfo.record]: land_color = 0.66, 0.566261, 0.198516, 1; 14:48:32.727 [planetinfo.record]: land_fraction = 0.320000; 14:48:32.727 [planetinfo.record]: polar_cloud_color = 0.860568, 0.875293, 0.606038, 1; 14:48:32.727 [planetinfo.record]: polar_land_color = 0.934, 0.900836, 0.770732, 1; 14:48:32.727 [planetinfo.record]: polar_sea_color = 0.916436, 0.878992, 0.947461, 1; 14:48:32.728 [planetinfo.record]: sea_color = 0.456574, 0.37352, 0.525391, 1; 14:48:32.728 [planetinfo.record]: rotation_speed = 0.003822 14:48:32.728 [planetinfo.record]: planet zpos = 497640.000000 14:48:32.728 [planetinfo.record]: sun_radius = 120623.822021 14:48:32.728 [planetinfo.record]: sun_vector = 0.203 0.835 -0.511 14:48:32.728 [planetinfo.record]: sun_distance = 765600 14:48:32.728 [planetinfo.record]: corona_flare = 0.090782 14:48:32.728 [planetinfo.record]: corona_hues = 0.694183 14:48:32.728 [planetinfo.record]: sun_color = 0.765883, 0.771931, 0.785913, 1 14:48:32.728 [planetinfo.record]: corona_shimmer = 0.398376 14:48:32.729 [planetinfo.record]: station_vector = -0.379 0.807 0.452 14:48:32.729 [planetinfo.record]: station = coriolis 14:48:37.194 [PLANETINFO OVER]: Done 14:48:37.194 [PLANETINFO LOGGING]: 7 145 14:48:38.199 [planetinfo.record]: seed = 250 191 107 220 14:48:38.199 [planetinfo.record]: coordinates = 191 218 14:48:38.199 [planetinfo.record]: government = 7; 14:48:38.199 [planetinfo.record]: economy = 2; 14:48:38.199 [planetinfo.record]: techlevel = 12; 14:48:38.199 [planetinfo.record]: population = 58; 14:48:38.199 [planetinfo.record]: productivity = 40832; 14:48:38.199 [planetinfo.record]: name = "Tevebebi"; 14:48:38.199 [planetinfo.record]: inhabitant = "Human Colonial"; 14:48:38.199 [planetinfo.record]: inhabitants = "Human Colonials"; 14:48:38.199 [planetinfo.record]: description = "This planet is notable for the Tevebebiian tree grub and its inhabitants’ ancient loathing of discos."; 14:48:38.213 [planetinfo.record]: air_color = 0.784434, 0.571997, 0.917701, 1; 14:48:38.213 [planetinfo.record]: cloud_alpha = 1.000000; 14:48:38.213 [planetinfo.record]: cloud_color = 0.755859, 0.354309, 0.410777, 1; 14:48:38.213 [planetinfo.record]: cloud_fraction = 0.540000; 14:48:38.213 [planetinfo.record]: land_color = 0.570624, 0.355682, 0.615234, 1; 14:48:38.213 [planetinfo.record]: land_fraction = 0.360000; 14:48:38.213 [planetinfo.record]: polar_cloud_color = 0.840137, 0.561185, 0.600413, 1; 14:48:38.213 [planetinfo.record]: polar_land_color = 0.921464, 0.839497, 0.938477, 1; 14:48:38.213 [planetinfo.record]: polar_sea_color = 0.942969, 0.912836, 0.890848, 1; 14:48:38.213 [planetinfo.record]: sea_color = 0.570312, 0.497415, 0.44422, 1; 14:48:38.213 [planetinfo.record]: rotation_speed = 0.002434 14:48:38.213 [planetinfo.record]: planet zpos = 607900.000000 14:48:38.213 [planetinfo.record]: sun_radius = 128819.775848 14:48:38.214 [planetinfo.record]: sun_vector = 0.149 -0.514 0.845 14:48:38.214 [planetinfo.record]: sun_distance = 1094220 14:48:38.214 [planetinfo.record]: corona_flare = 0.095070 14:48:38.214 [planetinfo.record]: corona_hues = 0.865822 14:48:38.214 [planetinfo.record]: sun_color = 0.293574, 0.413932, 0.693426, 1 14:48:38.214 [planetinfo.record]: corona_shimmer = 0.330977 14:48:38.214 [planetinfo.record]: station_vector = 0.211 0.969 0.131 14:48:38.214 [planetinfo.record]: station = icosahedron 14:48:42.461 [PLANETINFO OVER]: Done 14:48:42.462 [PLANETINFO LOGGING]: 7 146 14:48:43.466 [planetinfo.record]: seed = 88 102 69 240 14:48:43.466 [planetinfo.record]: coordinates = 102 19 14:48:43.466 [planetinfo.record]: government = 3; 14:48:43.466 [planetinfo.record]: economy = 3; 14:48:43.467 [planetinfo.record]: techlevel = 8; 14:48:43.467 [planetinfo.record]: population = 39; 14:48:43.467 [planetinfo.record]: productivity = 15288; 14:48:43.467 [planetinfo.record]: name = "Eres"; 14:48:43.467 [planetinfo.record]: inhabitant = "Human Colonial"; 14:48:43.467 [planetinfo.record]: inhabitants = "Human Colonials"; 14:48:43.467 [planetinfo.record]: description = "The planet Eres is very noted for its unusual sit coms but plagued by occasional solar activity."; 14:48:43.472 [planetinfo.record]: air_color = 0.528392, 0.987943, 0.955174, 1; 14:48:43.473 [planetinfo.record]: cloud_alpha = 1.000000; 14:48:43.473 [planetinfo.record]: cloud_color = 0.966797, 0.810306, 0.196381, 1; 14:48:43.473 [planetinfo.record]: cloud_fraction = 0.210000; 14:48:43.473 [planetinfo.record]: land_color = 0.116016, 0.31576, 0.66, 1; 14:48:43.473 [planetinfo.record]: land_fraction = 0.620000; 14:48:43.473 [planetinfo.record]: polar_cloud_color = 0.935059, 0.840463, 0.469356, 1; 14:48:43.473 [planetinfo.record]: polar_land_color = 0.741545, 0.812212, 0.934, 1; 14:48:43.473 [planetinfo.record]: polar_sea_color = 0.918079, 0.933789, 0.87944, 1; 14:48:43.473 [planetinfo.record]: sea_color = 0.617551, 0.662109, 0.507962, 1; 14:48:43.473 [planetinfo.record]: rotation_speed = 0.001648 14:48:43.473 [planetinfo.record]: planet zpos = 350160.000000 14:48:43.473 [planetinfo.record]: sun_radius = 65088.194885 14:48:43.473 [planetinfo.record]: sun_vector = 0.254 -0.738 0.626 14:48:43.473 [planetinfo.record]: sun_distance = 641960 14:48:43.473 [planetinfo.record]: corona_flare = 0.087314 14:48:43.474 [planetinfo.record]: corona_hues = 0.990486 14:48:43.474 [planetinfo.record]: sun_color = 0.728116, 0.676829, 0.24699, 1 14:48:43.474 [planetinfo.record]: corona_shimmer = 0.479366 14:48:43.474 [planetinfo.record]: station_vector = 0.957 -0.283 0.063 14:48:43.474 [planetinfo.record]: station = coriolis 14:48:48.156 [PLANETINFO OVER]: Done 14:48:48.156 [PLANETINFO LOGGING]: 7 147 14:48:49.160 [planetinfo.record]: seed = 110 26 151 68 14:48:49.160 [planetinfo.record]: coordinates = 26 192 14:48:49.160 [planetinfo.record]: government = 5; 14:48:49.160 [planetinfo.record]: economy = 0; 14:48:49.160 [planetinfo.record]: techlevel = 12; 14:48:49.160 [planetinfo.record]: population = 54; 14:48:49.160 [planetinfo.record]: productivity = 38880; 14:48:49.160 [planetinfo.record]: name = "Zaonrixe"; 14:48:49.160 [planetinfo.record]: inhabitant = "Fierce Yellow Horned Lizard"; 14:48:49.160 [planetinfo.record]: inhabitants = "Fierce Yellow Horned Lizards"; 14:48:49.160 [planetinfo.record]: description = "The planet Zaonrixe is scourged by killer edible arts graduates."; 14:48:49.162 [planetinfo.record]: air_color = 0.750682, 0.623627, 0.860467, 1; 14:48:49.162 [planetinfo.record]: cloud_alpha = 1.000000; 14:48:49.162 [planetinfo.record]: cloud_color = 0.583984, 0.451675, 0.501291, 1; 14:48:49.162 [planetinfo.record]: cloud_fraction = 0.360000; 14:48:49.162 [planetinfo.record]: land_color = 0.207237, 0.203672, 0.66, 1; 14:48:49.162 [planetinfo.record]: land_fraction = 0.710000; 14:48:49.162 [planetinfo.record]: polar_cloud_color = 0.762793, 0.65478, 0.695285, 1; 14:48:49.162 [planetinfo.record]: polar_land_color = 0.773818, 0.772557, 0.934, 1; 14:48:49.162 [planetinfo.record]: polar_sea_color = 0.938867, 0.899565, 0.880371, 1; 14:48:49.162 [planetinfo.record]: sea_color = 0.611328, 0.508965, 0.458974, 1; 14:48:49.162 [planetinfo.record]: rotation_speed = 0.002027 14:48:49.162 [planetinfo.record]: planet zpos = 386600.000000 14:48:49.163 [planetinfo.record]: sun_radius = 96827.561340 14:48:49.163 [planetinfo.record]: sun_vector = -0.131 -0.975 0.178 14:48:49.163 [planetinfo.record]: sun_distance = 811860 14:48:49.163 [planetinfo.record]: corona_flare = 0.035895 14:48:49.163 [planetinfo.record]: corona_hues = 0.842934 14:48:49.163 [planetinfo.record]: sun_color = 0.544104, 0.757339, 0.767252, 1 14:48:49.163 [planetinfo.record]: corona_shimmer = 0.320022 14:48:49.163 [planetinfo.record]: station_vector = 0.037 0.511 0.858 14:48:49.163 [planetinfo.record]: station = icosahedron 14:48:53.517 [PLANETINFO OVER]: Done 14:48:53.518 [PLANETINFO LOGGING]: 7 148 14:48:54.537 [planetinfo.record]: seed = 108 226 65 128 14:48:54.537 [planetinfo.record]: coordinates = 226 126 14:48:54.537 [planetinfo.record]: government = 5; 14:48:54.537 [planetinfo.record]: economy = 6; 14:48:54.537 [planetinfo.record]: techlevel = 6; 14:48:54.537 [planetinfo.record]: population = 36; 14:48:54.537 [planetinfo.record]: productivity = 10368; 14:48:54.537 [planetinfo.record]: name = "Legece"; 14:48:54.537 [planetinfo.record]: inhabitant = "Human Colonial"; 14:48:54.537 [planetinfo.record]: inhabitants = "Human Colonials"; 14:48:54.537 [planetinfo.record]: description = "The planet Legece is reasonably noted for its inhabitants’ eccentric shyness and Zero-G cricket."; 14:48:54.556 [planetinfo.record]: air_color = 0.587635, 0.565842, 0.852662, 1; 14:48:54.556 [planetinfo.record]: cloud_alpha = 1.000000; 14:48:54.556 [planetinfo.record]: cloud_color = 0.386317, 0.330635, 0.560547, 1; 14:48:54.556 [planetinfo.record]: cloud_fraction = 0.350000; 14:48:54.556 [planetinfo.record]: land_color = 0.66, 0.571498, 0.224297, 1; 14:48:54.556 [planetinfo.record]: land_fraction = 0.270000; 14:48:54.556 [planetinfo.record]: polar_cloud_color = 0.606112, 0.55941, 0.752246, 1; 14:48:54.556 [planetinfo.record]: polar_land_color = 0.934, 0.902689, 0.779854, 1; 14:48:54.556 [planetinfo.record]: polar_sea_color = 0.940625, 0.89368, 0.882846, 1; 14:48:54.556 [planetinfo.record]: sea_color = 0.59375, 0.475217, 0.447864, 1; 14:48:54.556 [planetinfo.record]: rotation_speed = 0.001928 14:48:54.556 [planetinfo.record]: planet zpos = 365040.000000 14:48:54.556 [planetinfo.record]: sun_radius = 84626.048584 14:48:54.556 [planetinfo.record]: sun_vector = -0.072 0.510 -0.857 14:48:54.556 [planetinfo.record]: sun_distance = 669240 14:48:54.556 [planetinfo.record]: corona_flare = 0.021065 14:48:54.556 [planetinfo.record]: corona_hues = 0.548264 14:48:54.556 [planetinfo.record]: sun_color = 0.543944, 0.629, 0.745071, 1 14:48:54.556 [planetinfo.record]: corona_shimmer = 0.562889 14:48:54.556 [planetinfo.record]: station_vector = -0.832 0.531 0.163 14:48:54.557 [planetinfo.record]: station = coriolis 14:48:59.226 [PLANETINFO OVER]: Done 14:48:59.226 [PLANETINFO LOGGING]: 7 149 14:49:00.245 [planetinfo.record]: seed = 34 165 3 202 14:49:00.245 [planetinfo.record]: coordinates = 165 67 14:49:00.245 [planetinfo.record]: government = 4; 14:49:00.246 [planetinfo.record]: economy = 3; 14:49:00.246 [planetinfo.record]: techlevel = 7; 14:49:00.246 [planetinfo.record]: population = 36; 14:49:00.246 [planetinfo.record]: productivity = 16128; 14:49:00.246 [planetinfo.record]: name = "Arenxeon"; 14:49:00.246 [planetinfo.record]: inhabitant = "Human Colonial"; 14:49:00.246 [planetinfo.record]: inhabitants = "Human Colonials"; 14:49:00.246 [planetinfo.record]: description = "The world Arenxeon is most famous for its vast oceans."; 14:49:00.256 [planetinfo.record]: air_color = 0.738187, 0.552877, 0.919652, 1; 14:49:00.256 [planetinfo.record]: cloud_alpha = 1.000000; 14:49:00.256 [planetinfo.record]: cloud_color = 0.761719, 0.306473, 0.519869, 1; 14:49:00.256 [planetinfo.record]: cloud_fraction = 0.240000; 14:49:00.256 [planetinfo.record]: land_color = 0.66, 0.514215, 0.417656, 1; 14:49:00.259 [planetinfo.record]: land_fraction = 0.200000; 14:49:00.259 [planetinfo.record]: polar_cloud_color = 0.842773, 0.527968, 0.675533, 1; 14:49:00.259 [planetinfo.record]: polar_land_color = 0.934, 0.882423, 0.848262, 1; 14:49:00.259 [planetinfo.record]: polar_sea_color = 0.93739, 0.892681, 0.948242, 1; 14:49:00.259 [planetinfo.record]: sea_color = 0.493885, 0.396271, 0.517578, 1; 14:49:00.259 [planetinfo.record]: rotation_speed = 0.001797 14:49:00.260 [planetinfo.record]: planet zpos = 554100.000000 14:49:00.260 [planetinfo.record]: sun_radius = 124258.210144 14:49:00.260 [planetinfo.record]: sun_vector = -0.023 -0.449 -0.893 14:49:00.260 [planetinfo.record]: sun_distance = 997380 14:49:00.260 [planetinfo.record]: corona_flare = 0.024837 14:49:00.260 [planetinfo.record]: corona_hues = 0.873482 14:49:00.260 [planetinfo.record]: sun_color = 0.658964, 0.661892, 0.664838, 1 14:49:00.260 [planetinfo.record]: corona_shimmer = 0.577155 14:49:00.260 [planetinfo.record]: station_vector = 0.876 0.469 0.114 14:49:00.260 [planetinfo.record]: station = coriolis 14:49:05.439 [PLANETINFO OVER]: Done 14:49:05.440 [PLANETINFO LOGGING]: 7 150 14:49:06.444 [planetinfo.record]: seed = 0 159 253 115 14:49:06.444 [planetinfo.record]: coordinates = 159 34 14:49:06.444 [planetinfo.record]: government = 0; 14:49:06.444 [planetinfo.record]: economy = 2; 14:49:06.444 [planetinfo.record]: techlevel = 8; 14:49:06.444 [planetinfo.record]: population = 35; 14:49:06.444 [planetinfo.record]: productivity = 8960; 14:49:06.444 [planetinfo.record]: name = "Belaus"; 14:49:06.444 [planetinfo.record]: inhabitant = "Blue Furry Rodent"; 14:49:06.444 [planetinfo.record]: inhabitants = "Blue Furry Rodents"; 14:49:06.444 [planetinfo.record]: description = "Belaus is reasonably well known for the Belausian tree ant but ravaged by dreadful civil war."; 14:49:06.460 [planetinfo.record]: air_color = 0.747195, 0.736919, 0.96648, 1; 14:49:06.460 [planetinfo.record]: cloud_alpha = 1.000000; 14:49:06.460 [planetinfo.record]: cloud_color = 0.815298, 0.800125, 0.902344, 1; 14:49:06.460 [planetinfo.record]: cloud_fraction = 0.330000; 14:49:06.460 [planetinfo.record]: land_color = 0.427969, 0.66, 0.496853, 1; 14:49:06.460 [planetinfo.record]: land_fraction = 0.420000; 14:49:06.460 [planetinfo.record]: polar_cloud_color = 0.851427, 0.841905, 0.906055, 1; 14:49:06.460 [planetinfo.record]: polar_land_color = 0.85191, 0.934, 0.876281, 1; 14:49:06.460 [planetinfo.record]: polar_sea_color = 0.934766, 0.874374, 0.868127, 1; 14:49:06.460 [planetinfo.record]: sea_color = 0.652344, 0.483763, 0.466324, 1; 14:49:06.461 [planetinfo.record]: rotation_speed = 0.004725 14:49:06.461 [planetinfo.record]: planet zpos = 411730.000000 14:49:06.461 [planetinfo.record]: sun_radius = 107755.176392 14:49:06.461 [planetinfo.record]: sun_vector = 0.728 -0.686 0.012 14:49:06.461 [planetinfo.record]: sun_distance = 786030 14:49:06.461 [planetinfo.record]: corona_flare = 0.014952 14:49:06.461 [planetinfo.record]: corona_hues = 0.853081 14:49:06.461 [planetinfo.record]: sun_color = 0.741437, 0.733196, 0.727114, 1 14:49:06.461 [planetinfo.record]: corona_shimmer = 0.791976 14:49:06.461 [planetinfo.record]: station_vector = 0.592 0.364 0.719 14:49:06.461 [planetinfo.record]: station = coriolis 14:49:11.579 [PLANETINFO OVER]: Done 14:49:11.580 [PLANETINFO LOGGING]: 7 151 14:49:12.586 [planetinfo.record]: seed = 22 241 47 110 14:49:12.586 [planetinfo.record]: coordinates = 241 72 14:49:12.586 [planetinfo.record]: government = 2; 14:49:12.586 [planetinfo.record]: economy = 0; 14:49:12.586 [planetinfo.record]: techlevel = 9; 14:49:12.586 [planetinfo.record]: population = 39; 14:49:12.586 [planetinfo.record]: productivity = 18720; 14:49:12.586 [planetinfo.record]: name = "Resobi"; 14:49:12.586 [planetinfo.record]: inhabitant = "Human Colonial"; 14:49:12.586 [planetinfo.record]: inhabitants = "Human Colonials"; 14:49:12.586 [planetinfo.record]: description = "The planet Resobi is reasonably fabled for Zero-G hockey and its inhabitants’ ingrained shyness."; 14:49:12.612 [planetinfo.record]: air_color = 0.712889, 0.821738, 0.898189, 1; 14:49:12.612 [planetinfo.record]: cloud_alpha = 1.000000; 14:49:12.612 [planetinfo.record]: cloud_color = 0.683647, 0.697266, 0.687052, 1; 14:49:12.612 [planetinfo.record]: cloud_fraction = 0.230000; 14:49:12.612 [planetinfo.record]: land_color = 0.0204468, 0.654297, 0.0303507, 1; 14:49:12.612 [planetinfo.record]: land_fraction = 0.710000; 14:49:12.612 [planetinfo.record]: polar_cloud_color = 0.803836, 0.81377, 0.806319, 1; 14:49:12.612 [planetinfo.record]: polar_land_color = 0.708229, 0.93457, 0.711766, 1; 14:49:12.612 [planetinfo.record]: polar_sea_color = 0.928906, 0.918701, 0.863593, 1; 14:49:12.612 [planetinfo.record]: sea_color = 0.710938, 0.679695, 0.510986, 1; 14:49:12.612 [planetinfo.record]: rotation_speed = 0.001454 14:49:12.612 [planetinfo.record]: planet zpos = 863330.000000 14:49:12.612 [planetinfo.record]: sun_radius = 128390.707550 14:49:12.612 [planetinfo.record]: sun_vector = 0.756 0.629 -0.180 14:49:12.612 [planetinfo.record]: sun_distance = 1593840 14:49:12.612 [planetinfo.record]: corona_flare = 0.064700 14:49:12.612 [planetinfo.record]: corona_hues = 0.618362 14:49:12.612 [planetinfo.record]: sun_color = 0.753118, 0.764401, 0.764749, 1 14:49:12.612 [planetinfo.record]: corona_shimmer = 0.322374 14:49:12.613 [planetinfo.record]: station_vector = 0.964 -0.027 0.266 14:49:12.613 [planetinfo.record]: station = coriolis 14:49:16.569 [PLANETINFO OVER]: Done 14:49:16.571 [PLANETINFO LOGGING]: 7 152 14:49:17.572 [planetinfo.record]: seed = 20 28 249 201 14:49:17.573 [planetinfo.record]: coordinates = 28 6 14:49:17.573 [planetinfo.record]: government = 2; 14:49:17.573 [planetinfo.record]: economy = 6; 14:49:17.573 [planetinfo.record]: techlevel = 2; 14:49:17.573 [planetinfo.record]: population = 17; 14:49:17.573 [planetinfo.record]: productivity = 3264; 14:49:17.573 [planetinfo.record]: name = "Esinen"; 14:49:17.573 [planetinfo.record]: inhabitant = "Small Horned Lobster"; 14:49:17.573 [planetinfo.record]: inhabitants = "Small Horned Lobsters"; 14:49:17.573 [planetinfo.record]: description = "This world is noted for its unusual casinos."; 14:49:17.587 [planetinfo.record]: air_color = 0.780352, 0.596658, 0.895588, 1; 14:49:17.587 [planetinfo.record]: cloud_alpha = 1.000000; 14:49:17.587 [planetinfo.record]: cloud_color = 0.689453, 0.412056, 0.451065, 1; 14:49:17.588 [planetinfo.record]: cloud_fraction = 0.450000; 14:49:17.588 [planetinfo.record]: land_color = 0.566406, 0.562603, 0.555344, 1; 14:49:17.588 [planetinfo.record]: land_fraction = 0.620000; 14:49:17.588 [planetinfo.record]: polar_cloud_color = 0.810254, 0.606504, 0.635156, 1; 14:49:17.588 [planetinfo.record]: polar_land_color = 0.943359, 0.941776, 0.938753, 1; 14:49:17.588 [planetinfo.record]: polar_sea_color = 0.927734, 0.923544, 0.860691, 1; 14:49:17.588 [planetinfo.record]: sea_color = 0.722656, 0.7096, 0.513763, 1; 14:49:17.588 [planetinfo.record]: rotation_speed = 0.000057 14:49:17.588 [planetinfo.record]: planet zpos = 669240.000000 14:49:17.588 [planetinfo.record]: sun_radius = 125851.695557 14:49:17.588 [planetinfo.record]: sun_vector = -0.841 0.529 -0.114 14:49:17.588 [planetinfo.record]: sun_distance = 1081080 14:49:17.589 [planetinfo.record]: corona_flare = 0.085034 14:49:17.589 [planetinfo.record]: corona_hues = 0.691612 14:49:17.589 [planetinfo.record]: sun_color = 0.835529, 0.819358, 0.567242, 1 14:49:17.589 [planetinfo.record]: corona_shimmer = 0.376263 14:49:17.589 [planetinfo.record]: station_vector = -0.315 -0.250 0.916 14:49:17.590 [planetinfo.record]: station = coriolis 14:49:22.670 [PLANETINFO OVER]: Done 14:49:22.672 [PLANETINFO LOGGING]: 7 153 14:49:23.676 [planetinfo.record]: seed = 74 137 155 72 14:49:23.676 [planetinfo.record]: coordinates = 137 210 14:49:23.676 [planetinfo.record]: government = 1; 14:49:23.676 [planetinfo.record]: economy = 2; 14:49:23.676 [planetinfo.record]: techlevel = 7; 14:49:23.676 [planetinfo.record]: population = 32; 14:49:23.676 [planetinfo.record]: productivity = 10240; 14:49:23.676 [planetinfo.record]: name = "Uszave"; 14:49:23.676 [planetinfo.record]: inhabitant = "Small Yellow Bony Lobster"; 14:49:23.676 [planetinfo.record]: inhabitants = "Small Yellow Bony Lobsters"; 14:49:23.677 [planetinfo.record]: description = "This world is very well known for Uszaveian wolf cutlet and the Uszaveian edible arts graduate."; 14:49:23.720 [planetinfo.record]: air_color = 0.504738, 0.637012, 0.84876, 1; 14:49:23.720 [planetinfo.record]: cloud_alpha = 1.000000; 14:49:23.720 [planetinfo.record]: cloud_color = 0.210098, 0.548828, 0.501194, 1; 14:49:23.720 [planetinfo.record]: cloud_fraction = 0.360000; 14:49:23.720 [planetinfo.record]: land_color = 0.196419, 0.529297, 0.381062, 1; 14:49:23.720 [planetinfo.record]: land_fraction = 0.420000; 14:49:23.720 [planetinfo.record]: polar_cloud_color = 0.458834, 0.746973, 0.706453, 1; 14:49:23.720 [planetinfo.record]: polar_land_color = 0.798166, 0.94707, 0.880761, 1; 14:49:23.720 [planetinfo.record]: polar_sea_color = 0.861816, 0.916406, 0.893376, 1; 14:49:23.720 [planetinfo.record]: sea_color = 0.636749, 0.835938, 0.751905, 1; 14:49:23.720 [planetinfo.record]: rotation_speed = 0.001108 14:49:23.720 [planetinfo.record]: planet zpos = 700140.000000 14:49:23.720 [planetinfo.record]: sun_radius = 122412.935944 14:49:23.720 [planetinfo.record]: sun_vector = -0.018 -0.869 0.495 14:49:23.720 [planetinfo.record]: sun_distance = 950190 14:49:23.720 [planetinfo.record]: corona_flare = 0.065111 14:49:23.720 [planetinfo.record]: corona_hues = 0.560745 14:49:23.720 [planetinfo.record]: sun_color = 0.415879, 0.546844, 0.835321, 1 14:49:23.721 [planetinfo.record]: corona_shimmer = 0.339868 14:49:23.721 [planetinfo.record]: station_vector = 0.275 -0.478 0.834 14:49:23.724 [planetinfo.record]: station = coriolis 14:49:29.024 [PLANETINFO OVER]: Done 14:49:29.027 [PLANETINFO LOGGING]: 7 154 14:49:30.042 [planetinfo.record]: seed = 168 99 181 126 14:49:30.042 [planetinfo.record]: coordinates = 99 118 14:49:30.042 [planetinfo.record]: government = 5; 14:49:30.042 [planetinfo.record]: economy = 6; 14:49:30.042 [planetinfo.record]: techlevel = 7; 14:49:30.042 [planetinfo.record]: population = 40; 14:49:30.042 [planetinfo.record]: productivity = 11520; 14:49:30.042 [planetinfo.record]: name = "Riedanen"; 14:49:30.042 [planetinfo.record]: inhabitant = "Blue Furry Insect"; 14:49:30.042 [planetinfo.record]: inhabitants = "Blue Furry Insects"; 14:49:30.042 [planetinfo.record]: description = "This world is very well known for its inhabitants’ ancient mating traditions and Riedanenian vicious brew."; 14:49:30.046 [planetinfo.record]: air_color = 0.864621, 0.780241, 0.981439, 1; 14:49:30.046 [planetinfo.record]: cloud_alpha = 1.000000; 14:49:30.046 [planetinfo.record]: cloud_color = 0.947266, 0.932465, 0.943218, 1; 14:49:30.046 [planetinfo.record]: cloud_fraction = 0.520000; 14:49:30.046 [planetinfo.record]: land_color = 0.649043, 0.61875, 0.66, 1; 14:49:30.046 [planetinfo.record]: land_fraction = 0.580000; 14:49:30.047 [planetinfo.record]: polar_cloud_color = 0.92627, 0.917224, 0.923796, 1; 14:49:30.047 [planetinfo.record]: polar_land_color = 0.930124, 0.919406, 0.934, 1; 14:49:30.047 [planetinfo.record]: polar_sea_color = 0.924805, 0.840002, 0.817332, 1; 14:49:30.047 [planetinfo.record]: sea_color = 0.751953, 0.476144, 0.402412, 1; 14:49:30.047 [planetinfo.record]: rotation_speed = 0.002846 14:49:30.047 [planetinfo.record]: planet zpos = 779880.000000 14:49:30.047 [planetinfo.record]: sun_radius = 162620.775299 14:49:30.047 [planetinfo.record]: sun_vector = -0.569 0.558 0.603 14:49:30.047 [planetinfo.record]: sun_distance = 1234810 14:49:30.047 [planetinfo.record]: corona_flare = 0.086636 14:49:30.047 [planetinfo.record]: corona_hues = 0.878159 14:49:30.047 [planetinfo.record]: sun_color = 0.83707, 0.678486, 0.593722, 1 14:49:30.047 [planetinfo.record]: corona_shimmer = 0.304286 14:49:30.048 [planetinfo.record]: station_vector = -0.701 -0.281 0.656 14:49:30.048 [planetinfo.record]: station = coriolis 14:49:35.130 [PLANETINFO OVER]: Done 14:49:35.130 [PLANETINFO LOGGING]: 7 155 14:49:36.134 [planetinfo.record]: seed = 190 18 199 166 14:49:36.134 [planetinfo.record]: coordinates = 18 59 14:49:36.134 [planetinfo.record]: government = 7; 14:49:36.134 [planetinfo.record]: economy = 3; 14:49:36.134 [planetinfo.record]: techlevel = 10; 14:49:36.135 [planetinfo.record]: population = 51; 14:49:36.135 [planetinfo.record]: productivity = 31416; 14:49:36.135 [planetinfo.record]: name = "Birare"; 14:49:36.135 [planetinfo.record]: inhabitant = "Fierce Harmless Bug-Eyed Lobster"; 14:49:36.135 [planetinfo.record]: inhabitants = "Fierce Harmless Bug-Eyed Lobsters"; 14:49:36.135 [planetinfo.record]: description = "This planet is a dull world."; 14:49:36.152 [planetinfo.record]: air_color = 0.573932, 0.936626, 0.973635, 1; 14:49:36.152 [planetinfo.record]: cloud_alpha = 1.000000; 14:49:36.152 [planetinfo.record]: cloud_color = 0.754839, 0.923828, 0.339218, 1; 14:49:36.152 [planetinfo.record]: cloud_fraction = 0.570000; 14:49:36.152 [planetinfo.record]: land_color = 0.543541, 0.66, 0.355781, 1; 14:49:36.152 [planetinfo.record]: land_fraction = 0.650000; 14:49:36.152 [planetinfo.record]: polar_cloud_color = 0.811031, 0.915723, 0.553547, 1; 14:49:36.152 [planetinfo.record]: polar_land_color = 0.892798, 0.934, 0.826371, 1; 14:49:36.152 [planetinfo.record]: polar_sea_color = 0.937695, 0.929211, 0.883393, 1; 14:49:36.152 [planetinfo.record]: sea_color = 0.623047, 0.600496, 0.478724, 1; 14:49:36.152 [planetinfo.record]: rotation_speed = 0.000821 14:49:36.152 [planetinfo.record]: planet zpos = 568100.000000 14:49:36.152 [planetinfo.record]: sun_radius = 71019.834900 14:49:36.152 [planetinfo.record]: sun_vector = 0.108 0.213 -0.971 14:49:36.152 [planetinfo.record]: sun_distance = 699200 14:49:36.152 [planetinfo.record]: corona_flare = 0.072020 14:49:36.152 [planetinfo.record]: corona_hues = 0.583878 14:49:36.152 [planetinfo.record]: sun_color = 0.283593, 0.490767, 0.679495, 1 14:49:36.152 [planetinfo.record]: corona_shimmer = 0.731853 14:49:36.152 [planetinfo.record]: station_vector = 0.856 0.085 0.509 14:49:36.152 [planetinfo.record]: station = coriolis 14:49:41.989 [PLANETINFO OVER]: Done 14:49:41.990 [PLANETINFO LOGGING]: 7 156 14:49:42.995 [planetinfo.record]: seed = 188 73 177 236 14:49:42.995 [planetinfo.record]: coordinates = 73 174 14:49:42.996 [planetinfo.record]: government = 7; 14:49:42.996 [planetinfo.record]: economy = 6; 14:49:42.996 [planetinfo.record]: techlevel = 6; 14:49:42.996 [planetinfo.record]: population = 38; 14:49:42.996 [planetinfo.record]: productivity = 13376; 14:49:42.996 [planetinfo.record]: name = "Inzaan"; 14:49:42.996 [planetinfo.record]: inhabitant = "Insect"; 14:49:42.996 [planetinfo.record]: inhabitants = "Insects"; 14:49:42.996 [planetinfo.record]: description = "The planet Inzaan is cursed by vicious killer beasts."; 14:49:43.004 [planetinfo.record]: air_color = 0.441185, 0.749653, 0.860467, 1; 14:49:43.004 [planetinfo.record]: cloud_alpha = 1.000000; 14:49:43.004 [planetinfo.record]: cloud_color = 0.178307, 0.583984, 0.0798416, 1; 14:49:43.005 [planetinfo.record]: cloud_fraction = 0.080000; 14:49:43.005 [planetinfo.record]: land_color = 0.580078, 0.66, 0.656254, 1; 14:49:43.005 [planetinfo.record]: land_fraction = 0.300000; 14:49:43.005 [planetinfo.record]: polar_cloud_color = 0.431611, 0.762793, 0.351227, 1; 14:49:43.005 [planetinfo.record]: polar_land_color = 0.905725, 0.934, 0.932675, 1; 14:49:43.005 [planetinfo.record]: polar_sea_color = 0.832763, 0.941394, 0.949609, 1; 14:49:43.005 [planetinfo.record]: sea_color = 0.25589, 0.486468, 0.503906, 1; 14:49:43.005 [planetinfo.record]: rotation_speed = 0.003204 14:49:43.005 [planetinfo.record]: planet zpos = 655710.000000 14:49:43.005 [planetinfo.record]: sun_radius = 118367.726898 14:49:43.006 [planetinfo.record]: sun_vector = 0.679 0.495 0.542 14:49:43.006 [planetinfo.record]: sun_distance = 1132590 14:49:43.006 [planetinfo.record]: corona_flare = 0.035953 14:49:43.006 [planetinfo.record]: corona_hues = 0.668068 14:49:43.006 [planetinfo.record]: sun_color = 0.731506, 0.573076, 0.478179, 1 14:49:43.006 [planetinfo.record]: corona_shimmer = 0.382558 14:49:43.007 [planetinfo.record]: station_vector = 0.794 0.569 0.213 14:49:43.007 [planetinfo.record]: station = coriolis 14:49:48.806 [PLANETINFO OVER]: Done 14:49:48.807 [PLANETINFO LOGGING]: 7 157 14:49:49.810 [planetinfo.record]: seed = 114 236 51 236 14:49:49.810 [planetinfo.record]: coordinates = 236 27 14:49:49.810 [planetinfo.record]: government = 6; 14:49:49.810 [planetinfo.record]: economy = 3; 14:49:49.810 [planetinfo.record]: techlevel = 7; 14:49:49.810 [planetinfo.record]: population = 38; 14:49:49.810 [planetinfo.record]: productivity = 21280; 14:49:49.810 [planetinfo.record]: name = "Inbein"; 14:49:49.810 [planetinfo.record]: inhabitant = "Human Colonial"; 14:49:49.810 [planetinfo.record]: inhabitants = "Human Colonials"; 14:49:49.810 [planetinfo.record]: description = "This world is reasonably notable for its great volcanoes but plagued by unpredictable civil war."; 14:49:49.826 [planetinfo.record]: air_color = 0.554106, 0.541999, 0.869572, 1; 14:49:49.826 [planetinfo.record]: cloud_alpha = 1.000000; 14:49:49.826 [planetinfo.record]: cloud_color = 0.327156, 0.28656, 0.611328, 1; 14:49:49.826 [planetinfo.record]: cloud_fraction = 0.380000; 14:49:49.826 [planetinfo.record]: land_color = 0.616172, 0.66, 0.62302, 1; 14:49:49.826 [planetinfo.record]: land_fraction = 0.490000; 14:49:49.826 [planetinfo.record]: polar_cloud_color = 0.549911, 0.517741, 0.775098, 1; 14:49:49.826 [planetinfo.record]: polar_land_color = 0.918494, 0.934, 0.920917, 1; 14:49:49.827 [planetinfo.record]: polar_sea_color = 0.713765, 0.928711, 0.923673, 1; 14:49:49.827 [planetinfo.record]: sea_color = 0.0529099, 0.712891, 0.697422, 1; 14:49:49.827 [planetinfo.record]: rotation_speed = 0.000573 14:49:49.827 [planetinfo.record]: planet zpos = 857360.000000 14:49:49.827 [planetinfo.record]: sun_radius = 144565.684204 14:49:49.827 [planetinfo.record]: sun_vector = 0.568 0.090 -0.818 14:49:49.827 [planetinfo.record]: sun_distance = 1347280 14:49:49.827 [planetinfo.record]: corona_flare = 0.010678 14:49:49.827 [planetinfo.record]: corona_hues = 0.800346 14:49:49.827 [planetinfo.record]: sun_color = 0.76745, 0.764896, 0.730799, 1 14:49:49.828 [planetinfo.record]: corona_shimmer = 0.376129 14:49:49.828 [planetinfo.record]: station_vector = 0.822 -0.389 0.416 14:49:49.828 [planetinfo.record]: station = coriolis 14:49:54.506 [PLANETINFO OVER]: Done 14:49:54.507 [PLANETINFO LOGGING]: 7 158 14:49:55.512 [planetinfo.record]: seed = 80 172 109 108 14:49:55.512 [planetinfo.record]: coordinates = 172 204 14:49:55.512 [planetinfo.record]: government = 2; 14:49:55.512 [planetinfo.record]: economy = 4; 14:49:55.512 [planetinfo.record]: techlevel = 4; 14:49:55.512 [planetinfo.record]: population = 23; 14:49:55.512 [planetinfo.record]: productivity = 6624; 14:49:55.512 [planetinfo.record]: name = "Inceisa"; 14:49:55.512 [planetinfo.record]: inhabitant = "Human Colonial"; 14:49:55.512 [planetinfo.record]: inhabitants = "Human Colonials"; 14:49:55.513 [planetinfo.record]: description = "Inceisa is famous for its inhabitants’ ancient loathing of discos but ravaged by lethal spotted batoids."; 14:49:55.529 [planetinfo.record]: air_color = 0.626058, 0.462231, 0.96518, 1; 14:49:55.529 [planetinfo.record]: cloud_alpha = 1.000000; 14:49:55.529 [planetinfo.record]: cloud_color = 0.891665, 0.0315857, 0.898438, 1; 14:49:55.530 [planetinfo.record]: cloud_fraction = 0.300000; 14:49:55.530 [planetinfo.record]: land_color = 0.238647, 0.510676, 0.53125, 1; 14:49:55.530 [planetinfo.record]: land_fraction = 0.490000; 14:49:55.530 [planetinfo.record]: polar_cloud_color = 0.900037, 0.358981, 0.904297, 1; 14:49:55.530 [planetinfo.record]: polar_land_color = 0.816495, 0.937708, 0.946875, 1; 14:49:55.530 [planetinfo.record]: polar_sea_color = 0.931836, 0.874705, 0.857216, 1; 14:49:55.530 [planetinfo.record]: sea_color = 0.681641, 0.514476, 0.463303, 1; 14:49:55.530 [planetinfo.record]: rotation_speed = 0.000974 14:49:55.530 [planetinfo.record]: planet zpos = 727200.000000 14:49:55.530 [planetinfo.record]: sun_radius = 125057.775879 14:49:55.530 [planetinfo.record]: sun_vector = -0.275 -0.283 -0.919 14:49:55.530 [planetinfo.record]: sun_distance = 969600 14:49:55.530 [planetinfo.record]: corona_flare = 0.029897 14:49:55.531 [planetinfo.record]: corona_hues = 0.876678 14:49:55.531 [planetinfo.record]: sun_color = 0.762177, 0.522504, 0.45008, 1 14:49:55.531 [planetinfo.record]: corona_shimmer = 0.935427 14:49:55.531 [planetinfo.record]: station_vector = -0.527 -0.586 0.615 14:49:55.531 [planetinfo.record]: station = coriolis 14:50:00.840 [PLANETINFO OVER]: Done 14:50:00.841 [PLANETINFO LOGGING]: 7 159 14:50:01.845 [planetinfo.record]: seed = 102 79 95 50 14:50:01.845 [planetinfo.record]: coordinates = 79 253 14:50:01.845 [planetinfo.record]: government = 4; 14:50:01.845 [planetinfo.record]: economy = 5; 14:50:01.845 [planetinfo.record]: techlevel = 7; 14:50:01.845 [planetinfo.record]: population = 38; 14:50:01.845 [planetinfo.record]: productivity = 12160; 14:50:01.845 [planetinfo.record]: name = "Enonlebe"; 14:50:01.845 [planetinfo.record]: inhabitant = "Human Colonial"; 14:50:01.845 [planetinfo.record]: inhabitants = "Human Colonials"; 14:50:01.845 [planetinfo.record]: description = "The world Enonlebe is a boring planet."; 14:50:01.876 [planetinfo.record]: air_color = 0.497371, 0.498205, 0.901441, 1; 14:50:01.876 [planetinfo.record]: cloud_alpha = 1.000000; 14:50:01.876 [planetinfo.record]: cloud_color = 0.183641, 0.17952, 0.707031, 1; 14:50:01.876 [planetinfo.record]: cloud_fraction = 0.410000; 14:50:01.876 [planetinfo.record]: land_color = 0.578125, 0.456177, 0.496191, 1; 14:50:01.876 [planetinfo.record]: land_fraction = 0.580000; 14:50:01.876 [planetinfo.record]: polar_cloud_color = 0.439628, 0.436647, 0.818164, 1; 14:50:01.876 [planetinfo.record]: polar_land_color = 0.942187, 0.892502, 0.908805, 1; 14:50:01.876 [planetinfo.record]: polar_sea_color = 0.906055, 0.795342, 0.722012, 1; 14:50:01.876 [planetinfo.record]: sea_color = 0.939453, 0.480277, 0.176147, 1; 14:50:01.876 [planetinfo.record]: rotation_speed = 0.000151 14:50:01.876 [planetinfo.record]: planet zpos = 340700.000000 14:50:01.876 [planetinfo.record]: sun_radius = 77230.393372 14:50:01.876 [planetinfo.record]: sun_vector = -0.229 0.056 -0.972 14:50:01.877 [planetinfo.record]: sun_distance = 715470 14:50:01.877 [planetinfo.record]: corona_flare = 0.058649 14:50:01.877 [planetinfo.record]: corona_hues = 0.929405 14:50:01.877 [planetinfo.record]: sun_color = 0.286696, 0.472882, 0.769849, 1 14:50:01.877 [planetinfo.record]: corona_shimmer = 0.525566 14:50:01.877 [planetinfo.record]: station_vector = 0.175 -0.323 0.930 14:50:01.877 [planetinfo.record]: station = coriolis 14:50:06.677 [PLANETINFO OVER]: Done 14:50:06.678 [PLANETINFO LOGGING]: 7 160 14:50:07.680 [planetinfo.record]: seed = 100 179 105 52 14:50:07.680 [planetinfo.record]: coordinates = 179 1 14:50:07.680 [planetinfo.record]: government = 4; 14:50:07.680 [planetinfo.record]: economy = 1; 14:50:07.680 [planetinfo.record]: techlevel = 11; 14:50:07.680 [planetinfo.record]: population = 50; 14:50:07.681 [planetinfo.record]: productivity = 28800; 14:50:07.681 [planetinfo.record]: name = "Raeserre"; 14:50:07.681 [planetinfo.record]: inhabitant = "Human Colonial"; 14:50:07.681 [planetinfo.record]: inhabitants = "Human Colonials"; 14:50:07.681 [planetinfo.record]: description = "This world is most fabled for Zero-G hockey but cursed by occasional earthquakes."; 14:50:07.708 [planetinfo.record]: air_color = 0.591303, 0.572751, 0.844857, 1; 14:50:07.708 [planetinfo.record]: cloud_alpha = 1.000000; 14:50:07.708 [planetinfo.record]: cloud_color = 0.383031, 0.33989, 0.537109, 1; 14:50:07.708 [planetinfo.record]: cloud_fraction = 0.420000; 14:50:07.708 [planetinfo.record]: land_color = 0.66, 0.517559, 0.391875, 1; 14:50:07.708 [planetinfo.record]: land_fraction = 0.640000; 14:50:07.708 [planetinfo.record]: polar_cloud_color = 0.608719, 0.571485, 0.741699, 1; 14:50:07.708 [planetinfo.record]: polar_land_color = 0.934, 0.883606, 0.839141, 1; 14:50:07.708 [planetinfo.record]: polar_sea_color = 0.935547, 0.844185, 0.899859, 1; 14:50:07.708 [planetinfo.record]: sea_color = 0.644531, 0.392761, 0.546184, 1; 14:50:07.708 [planetinfo.record]: rotation_speed = 0.001262 14:50:07.708 [planetinfo.record]: planet zpos = 562660.000000 14:50:07.708 [planetinfo.record]: sun_radius = 106392.869568 14:50:07.708 [planetinfo.record]: sun_vector = 0.584 -0.345 0.735 14:50:07.708 [planetinfo.record]: sun_distance = 803800 14:50:07.708 [planetinfo.record]: corona_flare = 0.089650 14:50:07.708 [planetinfo.record]: corona_hues = 0.860580 14:50:07.708 [planetinfo.record]: sun_color = 0.760834, 0.344317, 0.230139, 1 14:50:07.708 [planetinfo.record]: corona_shimmer = 0.342876 14:50:07.709 [planetinfo.record]: station_vector = 0.400 -0.777 0.486 14:50:07.709 [planetinfo.record]: station = icosahedron 14:50:12.508 [PLANETINFO OVER]: Done 14:50:12.509 [PLANETINFO LOGGING]: 7 161 14:50:13.512 [planetinfo.record]: seed = 154 238 203 168 14:50:13.512 [planetinfo.record]: coordinates = 238 208 14:50:13.512 [planetinfo.record]: government = 3; 14:50:13.512 [planetinfo.record]: economy = 0; 14:50:13.512 [planetinfo.record]: techlevel = 11; 14:50:13.512 [planetinfo.record]: population = 48; 14:50:13.512 [planetinfo.record]: productivity = 26880; 14:50:13.512 [planetinfo.record]: name = "Ususoner"; 14:50:13.512 [planetinfo.record]: inhabitant = "Small Harmless Feline"; 14:50:13.512 [planetinfo.record]: inhabitants = "Small Harmless Felines"; 14:50:13.512 [planetinfo.record]: description = "The world Ususoner is beset by dreadful earthquakes."; 14:50:13.537 [planetinfo.record]: air_color = 0.675158, 0.547023, 0.905994, 1; 14:50:13.538 [planetinfo.record]: cloud_alpha = 1.000000; 14:50:13.538 [planetinfo.record]: cloud_color = 0.720703, 0.295601, 0.684171, 1; 14:50:13.538 [planetinfo.record]: cloud_fraction = 0.130000; 14:50:13.538 [planetinfo.record]: land_color = 0.525112, 0.66, 0.193359, 1; 14:50:13.538 [planetinfo.record]: land_fraction = 0.310000; 14:50:13.538 [planetinfo.record]: polar_cloud_color = 0.824316, 0.52043, 0.798201, 1; 14:50:13.538 [planetinfo.record]: polar_land_color = 0.886278, 0.934, 0.768908, 1; 14:50:13.538 [planetinfo.record]: polar_sea_color = 0.948438, 0.894347, 0.915899, 1; 14:50:13.538 [planetinfo.record]: sea_color = 0.515625, 0.397998, 0.444865, 1; 14:50:13.539 [planetinfo.record]: rotation_speed = 0.004918 14:50:13.539 [planetinfo.record]: planet zpos = 663260.000000 14:50:13.539 [planetinfo.record]: sun_radius = 127635.635376 14:50:13.539 [planetinfo.record]: sun_vector = -0.063 0.552 0.832 14:50:13.539 [planetinfo.record]: sun_distance = 1122440 14:50:13.539 [planetinfo.record]: corona_flare = 0.000798 14:50:13.539 [planetinfo.record]: corona_hues = 0.517029 14:50:13.539 [planetinfo.record]: sun_color = 0.846307, 0.695663, 0.376138, 1 14:50:13.539 [planetinfo.record]: corona_shimmer = 0.324989 14:50:13.540 [planetinfo.record]: station_vector = -0.264 0.269 0.926 14:50:13.540 [planetinfo.record]: station = icosahedron 14:50:18.342 [PLANETINFO OVER]: Done 14:50:18.343 [PLANETINFO LOGGING]: 7 162 14:50:19.346 [planetinfo.record]: seed = 248 16 37 121 14:50:19.346 [planetinfo.record]: coordinates = 16 255 14:50:19.346 [planetinfo.record]: government = 7; 14:50:19.346 [planetinfo.record]: economy = 7; 14:50:19.346 [planetinfo.record]: techlevel = 4; 14:50:19.346 [planetinfo.record]: population = 31; 14:50:19.346 [planetinfo.record]: productivity = 8184; 14:50:19.346 [planetinfo.record]: name = "Oresrati"; 14:50:19.346 [planetinfo.record]: inhabitant = "Human Colonial"; 14:50:19.346 [planetinfo.record]: inhabitants = "Human Colonials"; 14:50:19.346 [planetinfo.record]: description = "The world Oresrati is very famous for its unusual casinos but plagued by deadly earthquakes."; 14:50:19.355 [planetinfo.record]: air_color = 0.713628, 0.528785, 0.93201, 1; 14:50:19.355 [planetinfo.record]: cloud_alpha = 1.000000; 14:50:19.355 [planetinfo.record]: cloud_color = 0.798828, 0.240273, 0.580642, 1; 14:50:19.356 [planetinfo.record]: cloud_fraction = 0.220000; 14:50:19.356 [planetinfo.record]: land_color = 0.647109, 0.656375, 0.66, 1; 14:50:19.356 [planetinfo.record]: land_fraction = 0.660000; 14:50:19.356 [planetinfo.record]: polar_cloud_color = 0.859473, 0.483873, 0.712754, 1; 14:50:19.356 [planetinfo.record]: polar_land_color = 0.929439, 0.932717, 0.934, 1; 14:50:19.356 [planetinfo.record]: polar_sea_color = 0.753198, 0.938086, 0.72097, 1; 14:50:19.356 [planetinfo.record]: sea_color = 0.131035, 0.619141, 0.0459518, 1; 14:50:19.356 [planetinfo.record]: rotation_speed = 0.004081 14:50:19.356 [planetinfo.record]: planet zpos = 564960.000000 14:50:19.356 [planetinfo.record]: sun_radius = 89706.804199 14:50:19.356 [planetinfo.record]: sun_vector = 0.953 0.206 0.221 14:50:19.356 [planetinfo.record]: sun_distance = 1027200 14:50:19.356 [planetinfo.record]: corona_flare = 0.000627 14:50:19.356 [planetinfo.record]: corona_hues = 0.812035 14:50:19.356 [planetinfo.record]: sun_color = 0.27779, 0.744507, 0.767392, 1 14:50:19.356 [planetinfo.record]: corona_shimmer = 0.290641 14:50:19.356 [planetinfo.record]: station_vector = -0.808 -0.590 0.007 14:50:19.357 [planetinfo.record]: station = coriolis 14:50:23.801 [PLANETINFO OVER]: Done 14:50:23.802 [PLANETINFO LOGGING]: 7 163 14:50:24.804 [planetinfo.record]: seed = 14 23 247 180 14:50:24.804 [planetinfo.record]: coordinates = 23 20 14:50:24.804 [planetinfo.record]: government = 1; 14:50:24.804 [planetinfo.record]: economy = 6; 14:50:24.804 [planetinfo.record]: techlevel = 5; 14:50:24.804 [planetinfo.record]: population = 28; 14:50:24.804 [planetinfo.record]: productivity = 4480; 14:50:24.804 [planetinfo.record]: name = "Rain"; 14:50:24.804 [planetinfo.record]: inhabitant = "Harmless Bony Lobster"; 14:50:24.804 [planetinfo.record]: inhabitants = "Harmless Bony Lobsters"; 14:50:24.804 [planetinfo.record]: description = "This world is a tedious place."; 14:50:24.809 [planetinfo.record]: air_color = 0.48324, 0.592433, 0.879979, 1; 14:50:24.809 [planetinfo.record]: cloud_alpha = 1.000000; 14:50:24.809 [planetinfo.record]: cloud_color = 0.158134, 0.551745, 0.642578, 1; 14:50:24.809 [planetinfo.record]: cloud_fraction = 0.350000; 14:50:24.809 [planetinfo.record]: land_color = 0.626953, 0.435928, 0.494131, 1; 14:50:24.809 [planetinfo.record]: land_fraction = 0.360000; 14:50:24.809 [planetinfo.record]: polar_cloud_color = 0.417315, 0.719439, 0.78916, 1; 14:50:24.809 [planetinfo.record]: polar_land_color = 0.937305, 0.865908, 0.887662, 1; 14:50:24.809 [planetinfo.record]: polar_sea_color = 0.926025, 0.929102, 0.872847, 1; 14:50:24.809 [planetinfo.record]: sea_color = 0.699594, 0.708984, 0.537277, 1; 14:50:24.809 [planetinfo.record]: rotation_speed = 0.004578 14:50:24.809 [planetinfo.record]: planet zpos = 579450.000000 14:50:24.809 [planetinfo.record]: sun_radius = 69501.108856 14:50:24.809 [planetinfo.record]: sun_vector = 0.871 0.490 -0.001 14:50:24.809 [planetinfo.record]: sun_distance = 927120 14:50:24.809 [planetinfo.record]: corona_flare = 0.098819 14:50:24.809 [planetinfo.record]: corona_hues = 0.803078 14:50:24.809 [planetinfo.record]: sun_color = 0.75188, 0.545856, 0.291784, 1 14:50:24.809 [planetinfo.record]: corona_shimmer = 0.257754 14:50:24.810 [planetinfo.record]: station_vector = -0.320 0.304 0.897 14:50:24.810 [planetinfo.record]: station = coriolis 14:50:29.770 [PLANETINFO OVER]: Done 14:50:29.771 [PLANETINFO LOGGING]: 7 164 14:50:30.774 [planetinfo.record]: seed = 12 65 33 205 14:50:30.774 [planetinfo.record]: coordinates = 65 172 14:50:30.774 [planetinfo.record]: government = 1; 14:50:30.774 [planetinfo.record]: economy = 6; 14:50:30.774 [planetinfo.record]: techlevel = 3; 14:50:30.774 [planetinfo.record]: population = 20; 14:50:30.774 [planetinfo.record]: productivity = 3200; 14:50:30.774 [planetinfo.record]: name = "Diquus"; 14:50:30.774 [planetinfo.record]: inhabitant = "Human Colonial"; 14:50:30.774 [planetinfo.record]: inhabitants = "Human Colonials"; 14:50:30.774 [planetinfo.record]: description = "Diquus is mildly famous for its vast rain forests and the Diquusian tree grub."; 14:50:30.786 [planetinfo.record]: air_color = 0.748022, 0.6168, 0.86502, 1; 14:50:30.787 [planetinfo.record]: cloud_alpha = 1.000000; 14:50:30.787 [planetinfo.record]: cloud_color = 0.597656, 0.441238, 0.503561, 1; 14:50:30.787 [planetinfo.record]: cloud_fraction = 0.400000; 14:50:30.787 [planetinfo.record]: land_color = 0.66, 0.636293, 0.384141, 1; 14:50:30.787 [planetinfo.record]: land_fraction = 0.380000; 14:50:30.787 [planetinfo.record]: polar_cloud_color = 0.768945, 0.643166, 0.693281, 1; 14:50:30.787 [planetinfo.record]: polar_land_color = 0.934, 0.925613, 0.836404, 1; 14:50:30.787 [planetinfo.record]: polar_sea_color = 0.829746, 0.869405, 0.945117, 1; 14:50:30.787 [planetinfo.record]: sea_color = 0.280846, 0.372965, 0.548828, 1; 14:50:30.787 [planetinfo.record]: rotation_speed = 0.004439 14:50:30.787 [planetinfo.record]: planet zpos = 869260.000000 14:50:30.787 [planetinfo.record]: sun_radius = 136626.233063 14:50:30.787 [planetinfo.record]: sun_vector = 0.233 0.380 0.895 14:50:30.787 [planetinfo.record]: sun_distance = 1490160 14:50:30.787 [planetinfo.record]: corona_flare = 0.019678 14:50:30.787 [planetinfo.record]: corona_hues = 0.656441 14:50:30.788 [planetinfo.record]: sun_color = 0.682056, 0.557618, 0.528162, 1 14:50:30.788 [planetinfo.record]: corona_shimmer = 0.377012 14:50:30.788 [planetinfo.record]: station_vector = 0.897 0.321 0.304 14:50:30.788 [planetinfo.record]: station = coriolis 14:50:35.496 [PLANETINFO OVER]: Done 14:50:35.497 [PLANETINFO LOGGING]: 7 165 14:50:36.514 [planetinfo.record]: seed = 194 79 99 210 14:50:36.514 [planetinfo.record]: coordinates = 79 200 14:50:36.514 [planetinfo.record]: government = 0; 14:50:36.514 [planetinfo.record]: economy = 2; 14:50:36.514 [planetinfo.record]: techlevel = 8; 14:50:36.514 [planetinfo.record]: population = 35; 14:50:36.514 [planetinfo.record]: productivity = 8960; 14:50:36.514 [planetinfo.record]: name = "Enarines"; 14:50:36.514 [planetinfo.record]: inhabitant = "Human Colonial"; 14:50:36.514 [planetinfo.record]: inhabitants = "Human Colonials"; 14:50:36.514 [planetinfo.record]: description = "Enarines is ravaged by dreadful civil war."; 14:50:36.520 [planetinfo.record]: air_color = 0.660529, 0.485813, 0.954773, 1; 14:50:36.521 [planetinfo.record]: cloud_alpha = 1.000000; 14:50:36.521 [planetinfo.record]: cloud_color = 0.867188, 0.108398, 0.766411, 1; 14:50:36.521 [planetinfo.record]: cloud_fraction = 0.470000; 14:50:36.521 [planetinfo.record]: land_color = 0.66, 0.415078, 0.432299, 1; 14:50:36.521 [planetinfo.record]: land_fraction = 0.570000; 14:50:36.521 [planetinfo.record]: polar_cloud_color = 0.890234, 0.403387, 0.825575, 1; 14:50:36.521 [planetinfo.record]: polar_land_color = 0.934, 0.84735, 0.853442, 1; 14:50:36.521 [planetinfo.record]: polar_sea_color = 0.920803, 0.927148, 0.864675, 1; 14:50:36.521 [planetinfo.record]: sea_color = 0.708573, 0.728516, 0.532158, 1; 14:50:36.521 [planetinfo.record]: rotation_speed = 0.004287 14:50:36.521 [planetinfo.record]: planet zpos = 374770.000000 14:50:36.521 [planetinfo.record]: sun_radius = 90806.718597 14:50:36.521 [planetinfo.record]: sun_vector = 0.893 -0.424 -0.152 14:50:36.521 [planetinfo.record]: sun_distance = 647330 14:50:36.521 [planetinfo.record]: corona_flare = 0.091399 14:50:36.521 [planetinfo.record]: corona_hues = 0.582977 14:50:36.522 [planetinfo.record]: sun_color = 0.441033, 0.632845, 0.783942, 1 14:50:36.522 [planetinfo.record]: corona_shimmer = 0.269217 14:50:36.522 [planetinfo.record]: station_vector = 0.530 -0.819 0.219 14:50:36.522 [planetinfo.record]: station = coriolis 14:50:40.938 [PLANETINFO OVER]: Done 14:50:40.939 [PLANETINFO LOGGING]: 7 166 14:50:41.958 [planetinfo.record]: seed = 160 201 221 192 14:50:41.958 [planetinfo.record]: coordinates = 201 12 14:50:41.958 [planetinfo.record]: government = 4; 14:50:41.958 [planetinfo.record]: economy = 4; 14:50:41.958 [planetinfo.record]: techlevel = 6; 14:50:41.958 [planetinfo.record]: population = 33; 14:50:41.958 [planetinfo.record]: productivity = 12672; 14:50:41.958 [planetinfo.record]: name = "Tile"; 14:50:41.958 [planetinfo.record]: inhabitant = "Large Furry Humanoid"; 14:50:41.958 [planetinfo.record]: inhabitants = "Large Furry Humanoids"; 14:50:41.959 [planetinfo.record]: description = "The world Tile is fabled for its weird exuberant forests and its inhabitants’ eccentric shyness."; 14:50:41.972 [planetinfo.record]: air_color = 0.62542, 0.885182, 0.813948, 1; 14:50:41.972 [planetinfo.record]: cloud_alpha = 1.000000; 14:50:41.972 [planetinfo.record]: cloud_color = 0.658203, 0.507793, 0.473083, 1; 14:50:41.972 [planetinfo.record]: cloud_fraction = 0.410000; 14:50:41.972 [planetinfo.record]: land_color = 0.358359, 0.66, 0.617582, 1; 14:50:41.972 [planetinfo.record]: land_fraction = 0.660000; 14:50:41.972 [planetinfo.record]: polar_cloud_color = 0.796191, 0.682478, 0.656236, 1; 14:50:41.972 [planetinfo.record]: polar_land_color = 0.827283, 0.934, 0.918993, 1; 14:50:41.972 [planetinfo.record]: polar_sea_color = 0.934766, 0.899392, 0.878169, 1; 14:50:41.972 [planetinfo.record]: sea_color = 0.652344, 0.5536, 0.494354, 1; 14:50:41.972 [planetinfo.record]: rotation_speed = 0.002267 14:50:41.972 [planetinfo.record]: planet zpos = 392210.000000 14:50:41.972 [planetinfo.record]: sun_radius = 65506.133728 14:50:41.972 [planetinfo.record]: sun_vector = 0.926 -0.361 -0.112 14:50:41.972 [planetinfo.record]: sun_distance = 543060 14:50:41.972 [planetinfo.record]: corona_flare = 0.024237 14:50:41.972 [planetinfo.record]: corona_hues = 0.938408 14:50:41.972 [planetinfo.record]: sun_color = 0.755902, 0.759442, 0.782211, 1 14:50:41.973 [planetinfo.record]: corona_shimmer = 0.447589 14:50:41.973 [planetinfo.record]: station_vector = -0.734 0.037 0.678 14:50:41.973 [planetinfo.record]: station = coriolis 14:50:46.367 [PLANETINFO OVER]: Done 14:50:46.368 [PLANETINFO LOGGING]: 7 167 14:50:47.370 [planetinfo.record]: seed = 182 121 143 50 14:50:47.370 [planetinfo.record]: coordinates = 121 33 14:50:47.370 [planetinfo.record]: government = 6; 14:50:47.370 [planetinfo.record]: economy = 1; 14:50:47.370 [planetinfo.record]: techlevel = 10; 14:50:47.370 [planetinfo.record]: population = 48; 14:50:47.370 [planetinfo.record]: productivity = 34560; 14:50:47.370 [planetinfo.record]: name = "Endiqu"; 14:50:47.370 [planetinfo.record]: inhabitant = "Red Slimy Lizard"; 14:50:47.370 [planetinfo.record]: inhabitants = "Red Slimy Lizards"; 14:50:47.370 [planetinfo.record]: description = "This planet is noted for Zero-G cricket."; 14:50:47.392 [planetinfo.record]: air_color = 0.638867, 0.894388, 0.922904, 1; 14:50:47.392 [planetinfo.record]: cloud_alpha = 1.000000; 14:50:47.392 [planetinfo.record]: cloud_color = 0.693319, 0.771484, 0.521355, 1; 14:50:47.392 [planetinfo.record]: cloud_fraction = 0.150000; 14:50:47.392 [planetinfo.record]: land_color = 0.582918, 0.66, 0.430547, 1; 14:50:47.392 [planetinfo.record]: land_fraction = 0.510000; 14:50:47.392 [planetinfo.record]: polar_cloud_color = 0.793522, 0.847168, 0.675501, 1; 14:50:47.392 [planetinfo.record]: polar_land_color = 0.906729, 0.934, 0.852822, 1; 14:50:47.392 [planetinfo.record]: polar_sea_color = 0.768238, 0.944336, 0.758051, 1; 14:50:47.392 [planetinfo.record]: sea_color = 0.141436, 0.556641, 0.117416, 1; 14:50:47.392 [planetinfo.record]: rotation_speed = 0.003908 14:50:47.392 [planetinfo.record]: planet zpos = 413880.000000 14:50:47.392 [planetinfo.record]: sun_radius = 83740.979004 14:50:47.392 [planetinfo.record]: sun_vector = -0.389 -0.804 -0.450 14:50:47.392 [planetinfo.record]: sun_distance = 827760 14:50:47.392 [planetinfo.record]: corona_flare = 0.057074 14:50:47.392 [planetinfo.record]: corona_hues = 0.579407 14:50:47.392 [planetinfo.record]: sun_color = 0.679507, 0.577592, 0.562629, 1 14:50:47.392 [planetinfo.record]: corona_shimmer = 0.379193 14:50:47.392 [planetinfo.record]: station_vector = -0.811 0.560 0.167 14:50:47.392 [planetinfo.record]: station = coriolis 14:50:52.403 [PLANETINFO OVER]: Done 14:50:52.404 [PLANETINFO LOGGING]: 7 168 14:50:53.407 [planetinfo.record]: seed = 180 122 217 194 14:50:53.407 [planetinfo.record]: coordinates = 122 122 14:50:53.407 [planetinfo.record]: government = 6; 14:50:53.407 [planetinfo.record]: economy = 2; 14:50:53.407 [planetinfo.record]: techlevel = 10; 14:50:53.407 [planetinfo.record]: population = 49; 14:50:53.407 [planetinfo.record]: productivity = 31360; 14:50:53.408 [planetinfo.record]: name = "Xetila"; 14:50:53.408 [planetinfo.record]: inhabitant = "Large Slimy Lizard"; 14:50:53.408 [planetinfo.record]: inhabitants = "Large Slimy Lizards"; 14:50:53.408 [planetinfo.record]: description = "The planet Xetila is famous for Xetilaian wolf cutlet but plagued by lethal disease."; 14:50:53.416 [planetinfo.record]: air_color = 0.670913, 0.552456, 0.89949, 1; 14:50:53.417 [planetinfo.record]: cloud_alpha = 1.000000; 14:50:53.417 [planetinfo.record]: cloud_color = 0.701172, 0.309502, 0.685872, 1; 14:50:53.417 [planetinfo.record]: cloud_fraction = 0.540000; 14:50:53.417 [planetinfo.record]: land_color = 0.520781, 0.620845, 0.66, 1; 14:50:53.417 [planetinfo.record]: land_fraction = 0.370000; 14:50:53.417 [planetinfo.record]: polar_cloud_color = 0.815527, 0.53081, 0.804406, 1; 14:50:53.417 [planetinfo.record]: polar_land_color = 0.884746, 0.920147, 0.934, 1; 14:50:53.417 [planetinfo.record]: polar_sea_color = 0.929102, 0.914095, 0.862867, 1; 14:50:53.417 [planetinfo.record]: sea_color = 0.708984, 0.66318, 0.506813, 1; 14:50:53.417 [planetinfo.record]: rotation_speed = 0.002568 14:50:53.417 [planetinfo.record]: planet zpos = 448500.000000 14:50:53.417 [planetinfo.record]: sun_radius = 95431.434631 14:50:53.417 [planetinfo.record]: sun_vector = 0.857 -0.493 0.151 14:50:53.417 [planetinfo.record]: sun_distance = 793500 14:50:53.417 [planetinfo.record]: corona_flare = 0.008543 14:50:53.417 [planetinfo.record]: corona_hues = 0.575714 14:50:53.418 [planetinfo.record]: sun_color = 0.653705, 0.522221, 0.290978, 1 14:50:53.418 [planetinfo.record]: corona_shimmer = 0.314944 14:50:53.418 [planetinfo.record]: station_vector = 0.110 -0.990 0.083 14:50:53.418 [planetinfo.record]: station = coriolis 14:50:58.237 [PLANETINFO OVER]: Done 14:50:58.237 [PLANETINFO LOGGING]: 7 169 14:50:59.240 [planetinfo.record]: seed = 234 111 251 28 14:50:59.240 [planetinfo.record]: coordinates = 111 245 14:50:59.240 [planetinfo.record]: government = 5; 14:50:59.240 [planetinfo.record]: economy = 5; 14:50:59.240 [planetinfo.record]: techlevel = 8; 14:50:59.240 [planetinfo.record]: population = 43; 14:50:59.240 [planetinfo.record]: productivity = 15480; 14:50:59.240 [planetinfo.record]: name = "Texeare"; 14:50:59.240 [planetinfo.record]: inhabitant = "Green Horned Lizard"; 14:50:59.240 [planetinfo.record]: inhabitants = "Green Horned Lizards"; 14:50:59.240 [planetinfo.record]: description = "The world Texeare is a dull place."; 14:50:59.264 [planetinfo.record]: air_color = 0.566154, 0.954123, 0.90829, 1; 14:50:59.264 [planetinfo.record]: cloud_alpha = 1.000000; 14:50:59.264 [planetinfo.record]: cloud_color = 0.865234, 0.680506, 0.327843, 1; 14:50:59.264 [planetinfo.record]: cloud_fraction = 0.520000; 14:50:59.264 [planetinfo.record]: land_color = 0.504144, 0.66, 0.386719, 1; 14:50:59.264 [planetinfo.record]: land_fraction = 0.680000; 14:50:59.264 [planetinfo.record]: polar_cloud_color = 0.889355, 0.770682, 0.544122, 1; 14:50:59.264 [planetinfo.record]: polar_land_color = 0.87886, 0.934, 0.837316, 1; 14:50:59.264 [planetinfo.record]: polar_sea_color = 0.879062, 0.926758, 0.911108, 1; 14:50:59.264 [planetinfo.record]: sea_color = 0.581646, 0.732422, 0.682949, 1; 14:50:59.264 [planetinfo.record]: rotation_speed = 0.003584 14:50:59.264 [planetinfo.record]: planet zpos = 899850.000000 14:50:59.264 [planetinfo.record]: sun_radius = 172235.998688 14:50:59.264 [planetinfo.record]: sun_vector = 0.377 0.840 0.390 14:50:59.264 [planetinfo.record]: sun_distance = 1259790 14:50:59.264 [planetinfo.record]: corona_flare = 0.030321 14:50:59.264 [planetinfo.record]: corona_hues = 0.633591 14:50:59.264 [planetinfo.record]: sun_color = 0.651468, 0.546901, 0.362755, 1 14:50:59.265 [planetinfo.record]: corona_shimmer = 0.418167 14:50:59.265 [planetinfo.record]: station_vector = -0.344 -0.814 0.468 14:50:59.265 [planetinfo.record]: station = coriolis 14:51:04.213 [PLANETINFO OVER]: Done 14:51:04.213 [PLANETINFO LOGGING]: 7 170 14:51:05.218 [planetinfo.record]: seed = 72 174 149 63 14:51:05.218 [planetinfo.record]: coordinates = 174 15 14:51:05.218 [planetinfo.record]: government = 1; 14:51:05.218 [planetinfo.record]: economy = 7; 14:51:05.218 [planetinfo.record]: techlevel = 3; 14:51:05.218 [planetinfo.record]: population = 21; 14:51:05.218 [planetinfo.record]: productivity = 2520; 14:51:05.218 [planetinfo.record]: name = "Ontear"; 14:51:05.218 [planetinfo.record]: inhabitant = "Red Bug-Eyed Bird"; 14:51:05.218 [planetinfo.record]: inhabitants = "Red Bug-Eyed Birds"; 14:51:05.218 [planetinfo.record]: description = "This world is most fabled for Ontearian Onrese water but scourged by deadly tree ants."; 14:51:05.240 [planetinfo.record]: air_color = 0.651232, 0.864376, 0.93201, 1; 14:51:05.240 [planetinfo.record]: cloud_alpha = 1.000000; 14:51:05.240 [planetinfo.record]: cloud_color = 0.620086, 0.798828, 0.555435, 1; 14:51:05.240 [planetinfo.record]: cloud_fraction = 0.180000; 14:51:05.240 [planetinfo.record]: land_color = 0.117088, 0.632812, 0.0271912, 1; 14:51:05.240 [planetinfo.record]: land_fraction = 0.700000; 14:51:05.240 [planetinfo.record]: polar_cloud_color = 0.739278, 0.859473, 0.695804, 1; 14:51:05.240 [planetinfo.record]: polar_land_color = 0.745869, 0.936719, 0.712601, 1; 14:51:05.240 [planetinfo.record]: polar_sea_color = 0.935547, 0.901238, 0.873695, 1; 14:51:05.240 [planetinfo.record]: sea_color = 0.644531, 0.549986, 0.474083, 1; 14:51:05.240 [planetinfo.record]: rotation_speed = 0.000329 14:51:05.240 [planetinfo.record]: planet zpos = 887900.000000 14:51:05.240 [planetinfo.record]: sun_radius = 196467.759705 14:51:05.240 [planetinfo.record]: sun_vector = -0.601 0.798 -0.045 14:51:05.240 [planetinfo.record]: sun_distance = 1297700 14:51:05.240 [planetinfo.record]: corona_flare = 0.067192 14:51:05.240 [planetinfo.record]: corona_hues = 0.850586 14:51:05.240 [planetinfo.record]: sun_color = 0.179599, 0.231353, 0.691736, 1 14:51:05.241 [planetinfo.record]: corona_shimmer = 0.951176 14:51:05.241 [planetinfo.record]: station_vector = 0.361 0.930 0.065 14:51:05.241 [planetinfo.record]: station = coriolis 14:51:10.647 [PLANETINFO OVER]: Done 14:51:10.649 [PLANETINFO LOGGING]: 7 171 14:51:11.669 [planetinfo.record]: seed = 94 39 39 15 14:51:11.669 [planetinfo.record]: coordinates = 39 234 14:51:11.669 [planetinfo.record]: government = 3; 14:51:11.669 [planetinfo.record]: economy = 2; 14:51:11.669 [planetinfo.record]: techlevel = 10; 14:51:11.669 [planetinfo.record]: population = 46; 14:51:11.669 [planetinfo.record]: productivity = 20608; 14:51:11.669 [planetinfo.record]: name = "Aletius"; 14:51:11.669 [planetinfo.record]: inhabitant = "Human Colonial"; 14:51:11.669 [planetinfo.record]: inhabitants = "Human Colonials"; 14:51:11.669 [planetinfo.record]: description = "The planet Aletius is most noted for its inhabitants’ eccentric love for poetry and its inhabitants’ unusual silliness."; 14:51:11.684 [planetinfo.record]: air_color = 0.453185, 0.55735, 0.909246, 1; 14:51:11.684 [planetinfo.record]: cloud_alpha = 1.000000; 14:51:11.684 [planetinfo.record]: cloud_color = 0.0684814, 0.513254, 0.730469, 1; 14:51:11.684 [planetinfo.record]: cloud_fraction = 0.360000; 14:51:11.684 [planetinfo.record]: land_color = 0.66, 0.108724, 0.0670312, 1; 14:51:11.684 [planetinfo.record]: land_fraction = 0.300000; 14:51:11.684 [planetinfo.record]: polar_cloud_color = 0.359324, 0.674693, 0.828711, 1; 14:51:11.684 [planetinfo.record]: polar_land_color = 0.934, 0.738965, 0.724215, 1; 14:51:11.684 [planetinfo.record]: polar_sea_color = 0.948242, 0.908144, 0.900274, 1; 14:51:11.684 [planetinfo.record]: sea_color = 0.517578, 0.430031, 0.412849, 1; 14:51:11.684 [planetinfo.record]: rotation_speed = 0.003254 14:51:11.684 [planetinfo.record]: planet zpos = 803400.000000 14:51:11.684 [planetinfo.record]: sun_radius = 140522.876740 14:51:11.684 [planetinfo.record]: sun_vector = -0.455 -0.858 0.237 14:51:11.684 [planetinfo.record]: sun_distance = 1405950 14:51:11.684 [planetinfo.record]: corona_flare = 0.086472 14:51:11.684 [planetinfo.record]: corona_hues = 0.696686 14:51:11.685 [planetinfo.record]: sun_color = 0.719055, 0.495883, 0.398497, 1 14:51:11.685 [planetinfo.record]: corona_shimmer = 0.387581 14:51:11.685 [planetinfo.record]: station_vector = 0.125 -0.532 0.837 14:51:11.685 [planetinfo.record]: station = coriolis 14:51:16.807 [PLANETINFO OVER]: Done 14:51:16.808 [PLANETINFO LOGGING]: 7 172 14:51:17.811 [planetinfo.record]: seed = 92 136 145 1 14:51:17.811 [planetinfo.record]: coordinates = 136 87 14:51:17.811 [planetinfo.record]: government = 3; 14:51:17.811 [planetinfo.record]: economy = 7; 14:51:17.811 [planetinfo.record]: techlevel = 2; 14:51:17.811 [planetinfo.record]: population = 19; 14:51:17.811 [planetinfo.record]: productivity = 3192; 14:51:17.811 [planetinfo.record]: name = "Lelemaa"; 14:51:17.811 [planetinfo.record]: inhabitant = "Large Green Rodent"; 14:51:17.811 [planetinfo.record]: inhabitants = "Large Green Rodents"; 14:51:17.811 [planetinfo.record]: description = "The world Lelemaa is reasonably famous for the Lelemaaian deadly goat."; 14:51:17.813 [planetinfo.record]: air_color = 0.519107, 0.528318, 0.880629, 1; 14:51:17.814 [planetinfo.record]: cloud_alpha = 1.000000; 14:51:17.814 [planetinfo.record]: cloud_color = 0.236664, 0.262156, 0.644531, 1; 14:51:17.814 [planetinfo.record]: cloud_fraction = 0.450000; 14:51:17.814 [planetinfo.record]: land_color = 0.66, 0.0902344, 0.651097, 1; 14:51:17.814 [planetinfo.record]: land_fraction = 0.660000; 14:51:17.814 [planetinfo.record]: polar_cloud_color = 0.477572, 0.497102, 0.790039, 1; 14:51:17.814 [planetinfo.record]: polar_land_color = 0.934, 0.732424, 0.93085, 1; 14:51:17.814 [planetinfo.record]: polar_sea_color = 0.944141, 0.897867, 0.890295, 1; 14:51:17.814 [planetinfo.record]: sea_color = 0.558594, 0.449084, 0.431165, 1; 14:51:17.814 [planetinfo.record]: rotation_speed = 0.000607 14:51:17.814 [planetinfo.record]: planet zpos = 384960.000000 14:51:17.814 [planetinfo.record]: sun_radius = 79965.039062 14:51:17.814 [planetinfo.record]: sun_vector = 0.377 -0.222 0.899 14:51:17.814 [planetinfo.record]: sun_distance = 673680 14:51:17.814 [planetinfo.record]: corona_flare = 0.092281 14:51:17.814 [planetinfo.record]: corona_hues = 0.821106 14:51:17.815 [planetinfo.record]: sun_color = 0.187429, 0.541544, 0.683282, 1 14:51:17.815 [planetinfo.record]: corona_shimmer = 0.514153 14:51:17.815 [planetinfo.record]: station_vector = 0.669 0.245 0.702 14:51:17.815 [planetinfo.record]: station = coriolis 14:51:22.051 [PLANETINFO OVER]: Done 14:51:22.052 [PLANETINFO LOGGING]: 7 173 14:51:23.053 [planetinfo.record]: seed = 18 79 147 156 14:51:23.053 [planetinfo.record]: coordinates = 79 107 14:51:23.053 [planetinfo.record]: government = 2; 14:51:23.053 [planetinfo.record]: economy = 3; 14:51:23.053 [planetinfo.record]: techlevel = 8; 14:51:23.053 [planetinfo.record]: population = 38; 14:51:23.053 [planetinfo.record]: productivity = 12768; 14:51:23.053 [planetinfo.record]: name = "Tetige"; 14:51:23.053 [planetinfo.record]: inhabitant = "Black Fat Bird"; 14:51:23.054 [planetinfo.record]: inhabitants = "Black Fat Birds"; 14:51:23.054 [planetinfo.record]: description = "This world is very notable for its inhabitants’ unusual silliness and Tetigeian lethal water."; 14:51:23.068 [planetinfo.record]: air_color = 0.729765, 0.594976, 0.879328, 1; 14:51:23.068 [planetinfo.record]: cloud_alpha = 1.000000; 14:51:23.068 [planetinfo.record]: cloud_color = 0.640625, 0.402893, 0.536617, 1; 14:51:23.068 [planetinfo.record]: cloud_fraction = 0.500000; 14:51:23.068 [planetinfo.record]: land_color = 0.66, 0.306797, 0.588256, 1; 14:51:23.068 [planetinfo.record]: land_fraction = 0.670000; 14:51:23.068 [planetinfo.record]: polar_cloud_color = 0.788281, 0.605452, 0.708294, 1; 14:51:23.068 [planetinfo.record]: polar_land_color = 0.934, 0.809041, 0.908618, 1; 14:51:23.068 [planetinfo.record]: polar_sea_color = 0.93125, 0.87137, 0.85304, 1; 14:51:23.068 [planetinfo.record]: sea_color = 0.6875, 0.510674, 0.456543, 1; 14:51:23.068 [planetinfo.record]: rotation_speed = 0.003013 14:51:23.068 [planetinfo.record]: planet zpos = 775710.000000 14:51:23.068 [planetinfo.record]: sun_radius = 172526.386871 14:51:23.068 [planetinfo.record]: sun_vector = -0.979 -0.111 0.169 14:51:23.068 [planetinfo.record]: sun_distance = 1253070 14:51:23.068 [planetinfo.record]: corona_flare = 0.024641 14:51:23.068 [planetinfo.record]: corona_hues = 0.616592 14:51:23.068 [planetinfo.record]: sun_color = 0.776031, 0.683924, 0.622302, 1 14:51:23.069 [planetinfo.record]: corona_shimmer = 0.436470 14:51:23.069 [planetinfo.record]: station_vector = -0.308 -0.925 0.225 14:51:23.069 [planetinfo.record]: station = coriolis 14:51:27.840 [PLANETINFO OVER]: Done 14:51:27.841 [PLANETINFO LOGGING]: 7 174 14:51:28.845 [planetinfo.record]: seed = 240 54 77 209 14:51:28.846 [planetinfo.record]: coordinates = 54 67 14:51:28.846 [planetinfo.record]: government = 6; 14:51:28.846 [planetinfo.record]: economy = 3; 14:51:28.846 [planetinfo.record]: techlevel = 9; 14:51:28.846 [planetinfo.record]: population = 46; 14:51:28.846 [planetinfo.record]: productivity = 25760; 14:51:28.846 [planetinfo.record]: name = "Atmabe"; 14:51:28.846 [planetinfo.record]: inhabitant = "Human Colonial"; 14:51:28.846 [planetinfo.record]: inhabitants = "Human Colonials"; 14:51:28.846 [planetinfo.record]: description = "Atmabe is mildly well known for its exotic cuisine and its inhabitants’ ingrained shyness."; 14:51:28.884 [planetinfo.record]: air_color = 0.556261, 0.502643, 0.913148, 1; 14:51:28.884 [planetinfo.record]: cloud_alpha = 1.000000; 14:51:28.884 [planetinfo.record]: cloud_color = 0.411682, 0.185547, 0.742188, 1; 14:51:28.884 [planetinfo.record]: cloud_fraction = 0.410000; 14:51:28.884 [planetinfo.record]: land_color = 0.327422, 0.66, 0.605436, 1; 14:51:28.884 [planetinfo.record]: land_fraction = 0.320000; 14:51:28.884 [planetinfo.record]: polar_cloud_color = 0.60187, 0.443054, 0.833984, 1; 14:51:28.884 [planetinfo.record]: polar_land_color = 0.816338, 0.934, 0.914696, 1; 14:51:28.884 [planetinfo.record]: polar_sea_color = 0.94043, 0.894631, 0.882388, 1; 14:51:28.884 [planetinfo.record]: sea_color = 0.595703, 0.47966, 0.448639, 1; 14:51:28.884 [planetinfo.record]: rotation_speed = 0.003502 14:51:28.884 [planetinfo.record]: planet zpos = 375120.000000 14:51:28.884 [planetinfo.record]: sun_radius = 76261.120605 14:51:28.884 [planetinfo.record]: sun_vector = 0.450 0.868 -0.211 14:51:28.884 [planetinfo.record]: sun_distance = 718980 14:51:28.884 [planetinfo.record]: corona_flare = 0.041292 14:51:28.884 [planetinfo.record]: corona_hues = 0.692764 14:51:28.884 [planetinfo.record]: sun_color = 0.711945, 0.297254, 0.286797, 1 14:51:28.884 [planetinfo.record]: corona_shimmer = 0.484755 14:51:28.885 [planetinfo.record]: station_vector = -0.938 -0.318 0.139 14:51:28.885 [planetinfo.record]: station = coriolis 14:51:34.344 [PLANETINFO OVER]: Done 14:51:34.346 [PLANETINFO LOGGING]: 7 175 14:51:35.348 [planetinfo.record]: seed = 6 112 191 14 14:51:35.348 [planetinfo.record]: coordinates = 112 83 14:51:35.348 [planetinfo.record]: government = 0; 14:51:35.348 [planetinfo.record]: economy = 3; 14:51:35.348 [planetinfo.record]: techlevel = 4; 14:51:35.348 [planetinfo.record]: population = 20; 14:51:35.348 [planetinfo.record]: productivity = 4480; 14:51:35.348 [planetinfo.record]: name = "Reenaten"; 14:51:35.348 [planetinfo.record]: inhabitant = "Green Bony Humanoid"; 14:51:35.348 [planetinfo.record]: inhabitants = "Green Bony Humanoids"; 14:51:35.348 [planetinfo.record]: description = "The world Reenaten is a dull world."; 14:51:35.364 [planetinfo.record]: air_color = 0.796051, 0.750436, 0.977537, 1; 14:51:35.364 [planetinfo.record]: cloud_alpha = 1.000000; 14:51:35.364 [planetinfo.record]: cloud_color = 0.900572, 0.844185, 0.935547, 1; 14:51:35.364 [planetinfo.record]: cloud_fraction = 0.600000; 14:51:35.364 [planetinfo.record]: land_color = 0.527344, 0.512795, 0.510864, 1; 14:51:35.364 [planetinfo.record]: land_fraction = 0.540000; 14:51:35.364 [planetinfo.record]: polar_cloud_color = 0.899477, 0.864783, 0.920996, 1; 14:51:35.364 [planetinfo.record]: polar_land_color = 0.947266, 0.940732, 0.939865, 1; 14:51:35.364 [planetinfo.record]: polar_sea_color = 0.938477, 0.910954, 0.881655, 1; 14:51:35.364 [planetinfo.record]: sea_color = 0.615234, 0.543061, 0.466232, 1; 14:51:35.364 [planetinfo.record]: rotation_speed = 0.002640 14:51:35.364 [planetinfo.record]: planet zpos = 716320.000000 14:51:35.364 [planetinfo.record]: sun_radius = 172334.094238 14:51:35.364 [planetinfo.record]: sun_vector = 0.634 -0.732 0.250 14:51:35.364 [planetinfo.record]: sun_distance = 1497760 14:51:35.364 [planetinfo.record]: corona_flare = 0.048759 14:51:35.364 [planetinfo.record]: corona_hues = 0.985710 14:51:35.364 [planetinfo.record]: sun_color = 0.2963, 0.447931, 0.826892, 1 14:51:35.364 [planetinfo.record]: corona_shimmer = 0.371692 14:51:35.365 [planetinfo.record]: station_vector = -0.547 -0.543 0.637 14:51:35.365 [planetinfo.record]: station = coriolis 14:51:40.025 [PLANETINFO OVER]: Done 14:51:40.025 [PLANETINFO LOGGING]: 7 176 14:51:41.030 [planetinfo.record]: seed = 4 50 73 85 14:51:41.030 [planetinfo.record]: coordinates = 50 81 14:51:41.030 [planetinfo.record]: government = 0; 14:51:41.030 [planetinfo.record]: economy = 3; 14:51:41.030 [planetinfo.record]: techlevel = 6; 14:51:41.030 [planetinfo.record]: population = 28; 14:51:41.030 [planetinfo.record]: productivity = 6272; 14:51:41.030 [planetinfo.record]: name = "Laedon"; 14:51:41.030 [planetinfo.record]: inhabitant = "Human Colonial"; 14:51:41.030 [planetinfo.record]: inhabitants = "Human Colonials"; 14:51:41.030 [planetinfo.record]: description = "The world Laedon is mildly noted for the Laedonian mountain lobstoid but beset by deadly earthquakes."; 14:51:41.045 [planetinfo.record]: air_color = 0.571576, 0.563252, 0.84876, 1; 14:51:41.046 [planetinfo.record]: cloud_alpha = 1.000000; 14:51:41.046 [planetinfo.record]: cloud_color = 0.346585, 0.323723, 0.548828, 1; 14:51:41.046 [planetinfo.record]: cloud_fraction = 0.420000; 14:51:41.046 [planetinfo.record]: land_color = 0.66, 0.177891, 0.256987, 1; 14:51:41.046 [planetinfo.record]: land_fraction = 0.630000; 14:51:41.046 [planetinfo.record]: polar_cloud_color = 0.574936, 0.555488, 0.746973, 1; 14:51:41.046 [planetinfo.record]: polar_land_color = 0.934, 0.763436, 0.791419, 1; 14:51:41.046 [planetinfo.record]: polar_sea_color = 0.926783, 0.939258, 0.892295, 1; 14:51:41.046 [planetinfo.record]: sea_color = 0.575153, 0.607422, 0.485938, 1; 14:51:41.046 [planetinfo.record]: rotation_speed = 0.003757 14:51:41.046 [planetinfo.record]: planet zpos = 497520.000000 14:51:41.046 [planetinfo.record]: sun_radius = 127651.326599 14:51:41.046 [planetinfo.record]: sun_vector = 0.363 -0.460 -0.810 14:51:41.047 [planetinfo.record]: sun_distance = 829200 14:51:41.047 [planetinfo.record]: corona_flare = 0.086765 14:51:41.047 [planetinfo.record]: corona_hues = 0.539459 14:51:41.047 [planetinfo.record]: sun_color = 0.392266, 0.494773, 0.680331, 1 14:51:41.047 [planetinfo.record]: corona_shimmer = 1.119814 14:51:41.047 [planetinfo.record]: station_vector = 0.906 0.228 0.356 14:51:41.047 [planetinfo.record]: station = coriolis 14:51:45.567 [PLANETINFO OVER]: Done 14:51:45.568 [PLANETINFO LOGGING]: 7 177 14:51:46.573 [planetinfo.record]: seed = 58 141 43 197 14:51:46.573 [planetinfo.record]: coordinates = 141 95 14:51:46.573 [planetinfo.record]: government = 7; 14:51:46.573 [planetinfo.record]: economy = 7; 14:51:46.573 [planetinfo.record]: techlevel = 5; 14:51:46.573 [planetinfo.record]: population = 35; 14:51:46.573 [planetinfo.record]: productivity = 9240; 14:51:46.573 [planetinfo.record]: name = "Ceenza"; 14:51:46.573 [planetinfo.record]: inhabitant = "Human Colonial"; 14:51:46.573 [planetinfo.record]: inhabitants = "Human Colonials"; 14:51:46.573 [planetinfo.record]: description = "The planet Ceenza is an unremarkable planet."; 14:51:46.586 [planetinfo.record]: air_color = 0.535404, 0.493211, 0.917701, 1; 14:51:46.586 [planetinfo.record]: cloud_alpha = 1.000000; 14:51:46.586 [planetinfo.record]: cloud_color = 0.34582, 0.159439, 0.755859, 1; 14:51:46.586 [planetinfo.record]: cloud_fraction = 0.260000; 14:51:46.586 [planetinfo.record]: land_color = 0.301641, 0.66, 0.366033, 1; 14:51:46.586 [planetinfo.record]: land_fraction = 0.580000; 14:51:46.586 [planetinfo.record]: polar_cloud_color = 0.555288, 0.425811, 0.840137, 1; 14:51:46.586 [planetinfo.record]: polar_land_color = 0.807217, 0.934, 0.829998, 1; 14:51:46.586 [planetinfo.record]: polar_sea_color = 0.935626, 0.94043, 0.893133, 1; 14:51:46.587 [planetinfo.record]: sea_color = 0.583532, 0.595703, 0.475864, 1; 14:51:46.587 [planetinfo.record]: rotation_speed = 0.002416 14:51:46.587 [planetinfo.record]: planet zpos = 550810.000000 14:51:46.587 [planetinfo.record]: sun_radius = 123609.121857 14:51:46.587 [planetinfo.record]: sun_vector = 0.953 -0.237 -0.191 14:51:46.587 [planetinfo.record]: sun_distance = 720290 14:51:46.587 [planetinfo.record]: corona_flare = 0.073087 14:51:46.587 [planetinfo.record]: corona_hues = 0.814896 14:51:46.587 [planetinfo.record]: sun_color = 0.218566, 0.246895, 0.667145, 1 14:51:46.587 [planetinfo.record]: corona_shimmer = 0.356038 14:51:46.587 [planetinfo.record]: station_vector = 0.550 0.790 0.272 14:51:46.587 [planetinfo.record]: station = coriolis 14:51:51.167 [PLANETINFO OVER]: Done 14:51:51.168 [PLANETINFO LOGGING]: 7 178 14:51:52.173 [planetinfo.record]: seed = 152 123 5 50 14:51:52.173 [planetinfo.record]: coordinates = 123 4 14:51:52.173 [planetinfo.record]: government = 3; 14:51:52.173 [planetinfo.record]: economy = 4; 14:51:52.173 [planetinfo.record]: techlevel = 8; 14:51:52.173 [planetinfo.record]: population = 40; 14:51:52.173 [planetinfo.record]: productivity = 13440; 14:51:52.173 [planetinfo.record]: name = "Enenonge"; 14:51:52.173 [planetinfo.record]: inhabitant = "Human Colonial"; 14:51:52.173 [planetinfo.record]: inhabitants = "Human Colonials"; 14:51:52.173 [planetinfo.record]: description = "This planet is noted for its exciting sit coms."; 14:51:52.216 [planetinfo.record]: air_color = 0.407893, 0.84941, 0.821373, 1; 14:51:52.216 [planetinfo.record]: cloud_alpha = 1.000000; 14:51:52.217 [planetinfo.record]: cloud_color = 0.550781, 0.455679, 0.0215149, 1; 14:51:52.217 [planetinfo.record]: cloud_fraction = 0.300000; 14:51:52.217 [planetinfo.record]: land_color = 0.0531921, 0.648438, 0.202003, 1; 14:51:52.217 [planetinfo.record]: land_fraction = 0.530000; 14:51:52.217 [planetinfo.record]: polar_cloud_color = 0.747852, 0.667145, 0.298702, 1; 14:51:52.217 [planetinfo.record]: polar_land_color = 0.720545, 0.935156, 0.774198, 1; 14:51:52.217 [planetinfo.record]: polar_sea_color = 0.939258, 0.895621, 0.880462, 1; 14:51:52.217 [planetinfo.record]: sea_color = 0.607422, 0.494541, 0.455329, 1; 14:51:52.217 [planetinfo.record]: rotation_speed = 0.001564 14:51:52.217 [planetinfo.record]: planet zpos = 414120.000000 14:51:52.217 [planetinfo.record]: sun_radius = 107212.906189 14:51:52.217 [planetinfo.record]: sun_vector = -0.235 -0.946 0.226 14:51:52.217 [planetinfo.record]: sun_distance = 655690 14:51:52.217 [planetinfo.record]: corona_flare = 0.054968 14:51:52.217 [planetinfo.record]: corona_hues = 0.891762 14:51:52.217 [planetinfo.record]: sun_color = 0.463838, 0.585463, 0.677701, 1 14:51:52.218 [planetinfo.record]: corona_shimmer = 0.438591 14:51:52.219 [planetinfo.record]: station_vector = 0.538 -0.840 0.063 14:51:52.219 [planetinfo.record]: station = coriolis 14:51:57.152 [PLANETINFO OVER]: Done 14:51:57.152 [PLANETINFO LOGGING]: 7 179 14:51:58.164 [planetinfo.record]: seed = 174 67 87 85 14:51:58.164 [planetinfo.record]: coordinates = 67 95 14:51:58.164 [planetinfo.record]: government = 5; 14:51:58.164 [planetinfo.record]: economy = 7; 14:51:58.164 [planetinfo.record]: techlevel = 6; 14:51:58.164 [planetinfo.record]: population = 37; 14:51:58.164 [planetinfo.record]: productivity = 7992; 14:51:58.164 [planetinfo.record]: name = "Laedat"; 14:51:58.164 [planetinfo.record]: inhabitant = "Human Colonial"; 14:51:58.164 [planetinfo.record]: inhabitants = "Human Colonials"; 14:51:58.164 [planetinfo.record]: description = "The world Laedat is a dull world."; 14:51:58.169 [planetinfo.record]: air_color = 0.570832, 0.929408, 0.825479, 1; 14:51:58.169 [planetinfo.record]: cloud_alpha = 1.000000; 14:51:58.169 [planetinfo.record]: cloud_color = 0.791016, 0.411295, 0.349159, 1; 14:51:58.169 [planetinfo.record]: cloud_fraction = 0.480000; 14:51:58.169 [planetinfo.record]: land_color = 0.589844, 0.460815, 0.494081, 1; 14:51:58.169 [planetinfo.record]: land_fraction = 0.320000; 14:51:58.169 [planetinfo.record]: polar_cloud_color = 0.855957, 0.599148, 0.557124, 1; 14:51:58.169 [planetinfo.record]: polar_land_color = 0.941016, 0.889554, 0.902821, 1; 14:51:58.169 [planetinfo.record]: polar_sea_color = 0.947266, 0.914333, 0.899902, 1; 14:51:58.169 [planetinfo.record]: sea_color = 0.527344, 0.45401, 0.421875, 1; 14:51:58.169 [planetinfo.record]: rotation_speed = 0.002041 14:51:58.169 [planetinfo.record]: planet zpos = 624450.000000 14:51:58.169 [planetinfo.record]: sun_radius = 92313.838959 14:51:58.169 [planetinfo.record]: sun_vector = 0.492 -0.868 0.069 14:51:58.169 [planetinfo.record]: sun_distance = 790970 14:51:58.169 [planetinfo.record]: corona_flare = 0.069189 14:51:58.169 [planetinfo.record]: corona_hues = 0.809219 14:51:58.169 [planetinfo.record]: sun_color = 0.710663, 0.70263, 0.500436, 1 14:51:58.170 [planetinfo.record]: corona_shimmer = 0.303214 14:51:58.170 [planetinfo.record]: station_vector = -0.206 -0.131 0.970 14:51:58.170 [planetinfo.record]: station = coriolis 14:52:02.859 [PLANETINFO OVER]: Done 14:52:02.859 [PLANETINFO LOGGING]: 7 180 14:52:03.862 [planetinfo.record]: seed = 172 223 1 106 14:52:03.862 [planetinfo.record]: coordinates = 223 145 14:52:03.862 [planetinfo.record]: government = 5; 14:52:03.862 [planetinfo.record]: economy = 1; 14:52:03.862 [planetinfo.record]: techlevel = 12; 14:52:03.863 [planetinfo.record]: population = 55; 14:52:03.863 [planetinfo.record]: productivity = 35640; 14:52:03.863 [planetinfo.record]: name = "Arance"; 14:52:03.863 [planetinfo.record]: inhabitant = "Human Colonial"; 14:52:03.863 [planetinfo.record]: inhabitants = "Human Colonials"; 14:52:03.863 [planetinfo.record]: description = "The planet Arance is most famous for its vast dense forests."; 14:52:03.876 [planetinfo.record]: air_color = 0.783347, 0.751759, 0.967781, 1; 14:52:03.876 [planetinfo.record]: cloud_alpha = 1.000000; 14:52:03.876 [planetinfo.record]: cloud_color = 0.871403, 0.842529, 0.90625, 1; 14:52:03.876 [planetinfo.record]: cloud_fraction = 0.480000; 14:52:03.876 [planetinfo.record]: land_color = 0.572266, 0.453789, 0.500994, 1; 14:52:03.876 [planetinfo.record]: land_fraction = 0.490000; 14:52:03.876 [planetinfo.record]: polar_cloud_color = 0.885995, 0.867918, 0.907812, 1; 14:52:03.876 [planetinfo.record]: polar_land_color = 0.942773, 0.893978, 0.91342, 1; 14:52:03.876 [planetinfo.record]: polar_sea_color = 0.925411, 0.939062, 0.891834, 1; 14:52:03.876 [planetinfo.record]: sea_color = 0.573939, 0.609375, 0.486786, 1; 14:52:03.876 [planetinfo.record]: rotation_speed = 0.001918 14:52:03.876 [planetinfo.record]: planet zpos = 503910.000000 14:52:03.876 [planetinfo.record]: sun_radius = 86788.942566 14:52:03.876 [planetinfo.record]: sun_vector = 0.083 0.110 0.990 14:52:03.876 [planetinfo.record]: sun_distance = 1007820 14:52:03.876 [planetinfo.record]: corona_flare = 0.097403 14:52:03.876 [planetinfo.record]: corona_hues = 0.879639 14:52:03.876 [planetinfo.record]: sun_color = 0.703107, 0.667145, 0.20505, 1 14:52:03.876 [planetinfo.record]: corona_shimmer = 0.515455 14:52:03.877 [planetinfo.record]: station_vector = -0.477 -0.245 0.844 14:52:03.877 [planetinfo.record]: station = dodecahedron 14:52:09.363 [PLANETINFO OVER]: Done 14:52:09.364 [PLANETINFO LOGGING]: 7 181 14:52:10.381 [planetinfo.record]: seed = 98 106 195 106 14:52:10.381 [planetinfo.record]: coordinates = 106 37 14:52:10.381 [planetinfo.record]: government = 4; 14:52:10.381 [planetinfo.record]: economy = 5; 14:52:10.381 [planetinfo.record]: techlevel = 6; 14:52:10.381 [planetinfo.record]: population = 34; 14:52:10.381 [planetinfo.record]: productivity = 10880; 14:52:10.381 [planetinfo.record]: name = "Arqua"; 14:52:10.381 [planetinfo.record]: inhabitant = "Small Blue Frog"; 14:52:10.381 [planetinfo.record]: inhabitants = "Small Blue Frogs"; 14:52:10.381 [planetinfo.record]: description = "This world is very notable for its inhabitants’ ingrained shyness and its fabulous lethal At water."; 14:52:10.388 [planetinfo.record]: air_color = 0.746163, 0.581613, 0.897539, 1; 14:52:10.388 [planetinfo.record]: cloud_alpha = 1.000000; 14:52:10.388 [planetinfo.record]: cloud_color = 0.695312, 0.377533, 0.511596, 1; 14:52:10.388 [planetinfo.record]: cloud_fraction = 0.350000; 14:52:10.389 [planetinfo.record]: land_color = 0.271751, 0.0309375, 0.66, 1; 14:52:10.389 [planetinfo.record]: land_fraction = 0.450000; 14:52:10.389 [planetinfo.record]: polar_cloud_color = 0.812891, 0.580693, 0.678651, 1; 14:52:10.389 [planetinfo.record]: polar_land_color = 0.796642, 0.711445, 0.934, 1; 14:52:10.389 [planetinfo.record]: polar_sea_color = 0.938477, 0.931441, 0.885504, 1; 14:52:10.389 [planetinfo.record]: sea_color = 0.615234, 0.596786, 0.476326, 1; 14:52:10.389 [planetinfo.record]: rotation_speed = 0.001744 14:52:10.389 [planetinfo.record]: planet zpos = 712660.000000 14:52:10.389 [planetinfo.record]: sun_radius = 169855.339966 14:52:10.389 [planetinfo.record]: sun_vector = -0.022 -0.159 -0.987 14:52:10.389 [planetinfo.record]: sun_distance = 1206040 14:52:10.389 [planetinfo.record]: corona_flare = 0.064879 14:52:10.389 [planetinfo.record]: corona_hues = 0.808838 14:52:10.389 [planetinfo.record]: sun_color = 0.215478, 0.453379, 0.783279, 1 14:52:10.389 [planetinfo.record]: corona_shimmer = 0.276754 14:52:10.390 [planetinfo.record]: station_vector = 0.969 0.226 0.097 14:52:10.390 [planetinfo.record]: station = coriolis 14:52:15.497 [PLANETINFO OVER]: Done 14:52:15.498 [PLANETINFO LOGGING]: 7 182 14:52:16.501 [planetinfo.record]: seed = 64 52 189 253 14:52:16.501 [planetinfo.record]: coordinates = 52 207 14:52:16.501 [planetinfo.record]: government = 0; 14:52:16.501 [planetinfo.record]: economy = 7; 14:52:16.501 [planetinfo.record]: techlevel = 0; 14:52:16.501 [planetinfo.record]: population = 8; 14:52:16.501 [planetinfo.record]: productivity = 768; 14:52:16.501 [planetinfo.record]: name = "Islebeen"; 14:52:16.501 [planetinfo.record]: inhabitant = "Bony Bird"; 14:52:16.501 [planetinfo.record]: inhabitants = "Bony Birds"; 14:52:16.501 [planetinfo.record]: description = "Islebeen is cursed by dreadful civil war."; 14:52:16.524 [planetinfo.record]: air_color = 0.592613, 0.546864, 0.877377, 1; 14:52:16.524 [planetinfo.record]: cloud_alpha = 1.000000; 14:52:16.524 [planetinfo.record]: cloud_color = 0.442445, 0.297546, 0.634766, 1; 14:52:16.524 [planetinfo.record]: cloud_fraction = 0.450000; 14:52:16.524 [planetinfo.record]: land_color = 0.638726, 0.335815, 0.65625, 1; 14:52:16.524 [planetinfo.record]: land_fraction = 0.260000; 14:52:16.524 [planetinfo.record]: polar_cloud_color = 0.636874, 0.524786, 0.785645, 1; 14:52:16.524 [planetinfo.record]: polar_land_color = 0.928137, 0.820316, 0.934375, 1; 14:52:16.524 [planetinfo.record]: polar_sea_color = 0.91094, 0.926562, 0.867747, 1; 14:52:16.524 [planetinfo.record]: sea_color = 0.684846, 0.734375, 0.547913, 1; 14:52:16.524 [planetinfo.record]: rotation_speed = 0.004772 14:52:16.524 [planetinfo.record]: planet zpos = 929400.000000 14:52:16.524 [planetinfo.record]: sun_radius = 98814.930420 14:52:16.524 [planetinfo.record]: sun_vector = -0.298 0.698 -0.651 14:52:16.524 [planetinfo.record]: sun_distance = 1301160 14:52:16.524 [planetinfo.record]: corona_flare = 0.057544 14:52:16.524 [planetinfo.record]: corona_hues = 0.543365 14:52:16.524 [planetinfo.record]: sun_color = 0.538907, 0.848521, 0.84939, 1 14:52:16.524 [planetinfo.record]: corona_shimmer = 0.343433 14:52:16.524 [planetinfo.record]: station_vector = -0.885 -0.053 0.463 14:52:16.524 [planetinfo.record]: station = coriolis 14:52:21.861 [PLANETINFO OVER]: Done 14:52:21.862 [PLANETINFO LOGGING]: 7 183 14:52:22.870 [planetinfo.record]: seed = 86 50 239 102 14:52:22.870 [planetinfo.record]: coordinates = 50 51 14:52:22.870 [planetinfo.record]: government = 2; 14:52:22.870 [planetinfo.record]: economy = 3; 14:52:22.870 [planetinfo.record]: techlevel = 7; 14:52:22.870 [planetinfo.record]: population = 34; 14:52:22.870 [planetinfo.record]: productivity = 11424; 14:52:22.870 [planetinfo.record]: name = "Biinceor"; 14:52:22.870 [planetinfo.record]: inhabitant = "Fierce Blue Bug-Eyed Lobster"; 14:52:22.870 [planetinfo.record]: inhabitants = "Fierce Blue Bug-Eyed Lobsters"; 14:52:22.870 [planetinfo.record]: description = "This planet is mildly fabled for its inhabitants’ ingrained silliness but beset by lethal disease."; 14:52:22.912 [planetinfo.record]: air_color = 0.431181, 0.8254, 0.840955, 1; 14:52:22.912 [planetinfo.record]: cloud_alpha = 1.000000; 14:52:22.912 [planetinfo.record]: cloud_color = 0.468696, 0.525391, 0.0718307, 1; 14:52:22.912 [planetinfo.record]: cloud_fraction = 0.390000; 14:52:22.912 [planetinfo.record]: land_color = 0.0179443, 0.65625, 0.0428782, 1; 14:52:22.912 [planetinfo.record]: land_fraction = 0.330000; 14:52:22.912 [planetinfo.record]: polar_cloud_color = 0.686758, 0.736426, 0.339087, 1; 14:52:22.912 [planetinfo.record]: polar_land_color = 0.707169, 0.934375, 0.716044, 1; 14:52:22.912 [planetinfo.record]: polar_sea_color = 0.908145, 0.932617, 0.878609, 1; 14:52:22.912 [planetinfo.record]: sea_color = 0.603102, 0.673828, 0.517742, 1; 14:52:22.912 [planetinfo.record]: rotation_speed = 0.001421 14:52:22.912 [planetinfo.record]: planet zpos = 528240.000000 14:52:22.912 [planetinfo.record]: sun_radius = 79595.489502 14:52:22.912 [planetinfo.record]: sun_vector = 0.378 0.506 -0.775 14:52:22.912 [planetinfo.record]: sun_distance = 1012460 14:52:22.912 [planetinfo.record]: corona_flare = 0.085748 14:52:22.912 [planetinfo.record]: corona_hues = 0.673233 14:52:22.912 [planetinfo.record]: sun_color = 0.285711, 0.607534, 0.718393, 1 14:52:22.913 [planetinfo.record]: corona_shimmer = 0.413680 14:52:22.913 [planetinfo.record]: station_vector = 0.577 0.808 0.121 14:52:22.913 [planetinfo.record]: station = coriolis 14:52:28.047 [PLANETINFO OVER]: Done 14:52:28.091 [PLANETINFO LOGGING]: 7 184 14:52:29.065 [planetinfo.record]: seed = 84 153 185 203 14:52:29.065 [planetinfo.record]: coordinates = 153 101 14:52:29.065 [planetinfo.record]: government = 2; 14:52:29.065 [planetinfo.record]: economy = 5; 14:52:29.065 [planetinfo.record]: techlevel = 4; 14:52:29.065 [planetinfo.record]: population = 24; 14:52:29.065 [planetinfo.record]: productivity = 5760; 14:52:29.065 [planetinfo.record]: name = "Maarabi"; 14:52:29.065 [planetinfo.record]: inhabitant = "Small Fat Insect"; 14:52:29.065 [planetinfo.record]: inhabitants = "Small Fat Insects"; 14:52:29.065 [planetinfo.record]: description = "The planet Maarabi is a boring world."; 14:52:29.085 [planetinfo.record]: air_color = 0.692198, 0.830203, 0.912498, 1; 14:52:29.085 [planetinfo.record]: cloud_alpha = 1.000000; 14:52:29.085 [planetinfo.record]: cloud_color = 0.647705, 0.740234, 0.659994, 1; 14:52:29.085 [planetinfo.record]: cloud_fraction = 0.140000; 14:52:29.085 [planetinfo.record]: land_color = 0.638672, 0.563672, 0.508942, 1; 14:52:29.085 [planetinfo.record]: land_fraction = 0.290000; 14:52:29.085 [planetinfo.record]: polar_cloud_color = 0.768019, 0.833105, 0.776663, 1; 14:52:29.085 [planetinfo.record]: polar_land_color = 0.936133, 0.90865, 0.888595, 1; 14:52:29.085 [planetinfo.record]: polar_sea_color = 0.757899, 0.937305, 0.920485, 1; 14:52:29.085 [planetinfo.record]: sea_color = 0.146942, 0.626953, 0.581952, 1; 14:52:29.086 [planetinfo.record]: rotation_speed = 0.000077 14:52:29.086 [planetinfo.record]: planet zpos = 752050.000000 14:52:29.086 [planetinfo.record]: sun_radius = 122359.246826 14:52:29.086 [planetinfo.record]: sun_vector = 0.844 -0.282 -0.457 14:52:29.086 [planetinfo.record]: sun_distance = 1041300 14:52:29.086 [planetinfo.record]: corona_flare = 0.081236 14:52:29.086 [planetinfo.record]: corona_hues = 0.514313 14:52:29.086 [planetinfo.record]: sun_color = 0.39328, 0.528749, 0.728598, 1 14:52:29.087 [planetinfo.record]: corona_shimmer = 0.506461 14:52:29.087 [planetinfo.record]: station_vector = 0.127 -0.963 0.236 14:52:29.087 [planetinfo.record]: station = coriolis 14:52:33.914 [PLANETINFO OVER]: Done 14:52:33.914 [PLANETINFO LOGGING]: 7 185 14:52:34.918 [planetinfo.record]: seed = 138 198 91 193 14:52:34.918 [planetinfo.record]: coordinates = 198 47 14:52:34.918 [planetinfo.record]: government = 1; 14:52:34.918 [planetinfo.record]: economy = 7; 14:52:34.918 [planetinfo.record]: techlevel = 3; 14:52:34.918 [planetinfo.record]: population = 21; 14:52:34.918 [planetinfo.record]: productivity = 2520; 14:52:34.918 [planetinfo.record]: name = "Letioned"; 14:52:34.918 [planetinfo.record]: inhabitant = "Human Colonial"; 14:52:34.918 [planetinfo.record]: inhabitants = "Human Colonials"; 14:52:34.918 [planetinfo.record]: description = "The world Letioned is fabled for its fabulous cuisine and the Letionedian mountain lobstoid."; 14:52:34.936 [planetinfo.record]: air_color = 0.669176, 0.87283, 0.897539, 1; 14:52:34.936 [planetinfo.record]: cloud_alpha = 1.000000; 14:52:34.936 [planetinfo.record]: cloud_color = 0.656991, 0.695312, 0.581238, 1; 14:52:34.936 [planetinfo.record]: cloud_fraction = 0.310000; 14:52:34.936 [planetinfo.record]: land_color = 0.63148, 0.629063, 0.66, 1; 14:52:34.936 [planetinfo.record]: land_fraction = 0.630000; 14:52:34.936 [planetinfo.record]: polar_cloud_color = 0.784889, 0.812891, 0.729538, 1; 14:52:34.936 [planetinfo.record]: polar_land_color = 0.92391, 0.923055, 0.934, 1; 14:52:34.936 [planetinfo.record]: polar_sea_color = 0.933398, 0.929712, 0.874423, 1; 14:52:34.936 [planetinfo.record]: sea_color = 0.666016, 0.655495, 0.497691, 1; 14:52:34.936 [planetinfo.record]: rotation_speed = 0.001173 14:52:34.936 [planetinfo.record]: planet zpos = 359700.000000 14:52:34.936 [planetinfo.record]: sun_radius = 100454.603577 14:52:34.936 [planetinfo.record]: sun_vector = 0.543 0.180 -0.820 14:52:34.936 [planetinfo.record]: sun_distance = 588600 14:52:34.936 [planetinfo.record]: corona_flare = 0.000768 14:52:34.936 [planetinfo.record]: corona_hues = 0.849388 14:52:34.936 [planetinfo.record]: sun_color = 0.231544, 0.410647, 0.717844, 1 14:52:34.936 [planetinfo.record]: corona_shimmer = 0.582449 14:52:34.936 [planetinfo.record]: station_vector = 0.951 0.308 0.044 14:52:34.937 [planetinfo.record]: station = coriolis 14:52:40.343 [PLANETINFO OVER]: Done 14:52:40.344 [PLANETINFO LOGGING]: 7 186 14:52:41.347 [planetinfo.record]: seed = 232 184 117 176 14:52:41.347 [planetinfo.record]: coordinates = 184 63 14:52:41.347 [planetinfo.record]: government = 5; 14:52:41.347 [planetinfo.record]: economy = 7; 14:52:41.347 [planetinfo.record]: techlevel = 3; 14:52:41.347 [planetinfo.record]: population = 25; 14:52:41.347 [planetinfo.record]: productivity = 5400; 14:52:41.348 [planetinfo.record]: name = "Eresen"; 14:52:41.349 [planetinfo.record]: inhabitant = "Human Colonial"; 14:52:41.349 [planetinfo.record]: inhabitants = "Human Colonials"; 14:52:41.349 [planetinfo.record]: description = "The world Eresen is most famous for the Eresenian spotted shrew."; 14:52:41.360 [planetinfo.record]: air_color = 0.677623, 0.856564, 0.838218, 1; 14:52:41.360 [planetinfo.record]: cloud_alpha = 1.000000; 14:52:41.360 [planetinfo.record]: cloud_color = 0.572266, 0.56762, 0.556618, 1; 14:52:41.360 [planetinfo.record]: cloud_fraction = 0.530000; 14:52:41.360 [planetinfo.record]: land_color = 0.562031, 0.57045, 0.66, 1; 14:52:41.360 [planetinfo.record]: land_fraction = 0.330000; 14:52:41.360 [planetinfo.record]: polar_cloud_color = 0.75752, 0.753676, 0.744574, 1; 14:52:41.360 [planetinfo.record]: polar_land_color = 0.89934, 0.902318, 0.934, 1; 14:52:41.360 [planetinfo.record]: polar_sea_color = 0.929688, 0.90238, 0.859779, 1; 14:52:41.360 [planetinfo.record]: sea_color = 0.703125, 0.620513, 0.491638, 1; 14:52:41.360 [planetinfo.record]: rotation_speed = 0.002881 14:52:41.360 [planetinfo.record]: planet zpos = 390000.000000 14:52:41.360 [planetinfo.record]: sun_radius = 64904.937744 14:52:41.360 [planetinfo.record]: sun_vector = -0.695 -0.719 -0.020 14:52:41.360 [planetinfo.record]: sun_distance = 660000 14:52:41.360 [planetinfo.record]: corona_flare = 0.025977 14:52:41.360 [planetinfo.record]: corona_hues = 0.911171 14:52:41.360 [planetinfo.record]: sun_color = 0.738383, 0.777171, 0.785199, 1 14:52:41.361 [planetinfo.record]: corona_shimmer = 0.383247 14:52:41.361 [planetinfo.record]: station_vector = -0.576 -0.816 0.045 14:52:41.361 [planetinfo.record]: station = coriolis 14:52:49.656 [PLANETINFO OVER]: Done 14:52:50.629 [PLANETINFO LOGGING]: 7 187 14:52:50.665 [script.javaScript.timeLimit]: ***** ERROR: Script "planetinfo.dump" ran for 1.03557 seconds and has been terminated. 14:52:50.891 [script.javaScript.stackTrace]: this: [Script "planetinfo.dump" version (null)] 14:52:50.892 [script.javaScript.stackTrace]: this: [Script "planetinfo.dump" version (null)] 14:52:52.260 [planetinfo.record]: seed = 254 107 135 39 14:52:52.261 [planetinfo.record]: coordinates = 107 18 14:52:52.261 [planetinfo.record]: government = 7; 14:52:52.261 [planetinfo.record]: economy = 2; 14:52:52.261 [planetinfo.record]: techlevel = 12; 14:52:52.261 [planetinfo.record]: population = 58; 14:52:52.261 [planetinfo.record]: productivity = 40832; 14:52:52.261 [planetinfo.record]: name = "Soceorbi"; 14:52:52.261 [planetinfo.record]: inhabitant = "Fierce Red Bug-Eyed Bird"; 14:52:52.261 [planetinfo.record]: inhabitants = "Fierce Red Bug-Eyed Birds"; 14:52:52.261 [planetinfo.record]: description = "The world Soceorbi is a dull place."; 14:52:52.429 [planetinfo.record]: air_color = 0.784843, 0.514683, 0.959977, 1; 14:52:52.429 [planetinfo.record]: cloud_alpha = 1.000000; 14:52:52.429 [planetinfo.record]: cloud_color = 0.882812, 0.18277, 0.297621, 1; 14:52:52.429 [planetinfo.record]: cloud_fraction = 0.380000; 14:52:52.429 [planetinfo.record]: land_color = 0.228004, 0.517578, 0.13546, 1; 14:52:52.429 [planetinfo.record]: land_fraction = 0.270000; 14:52:52.429 [planetinfo.record]: polar_cloud_color = 0.897266, 0.452576, 0.525533, 1; 14:52:52.429 [planetinfo.record]: polar_land_color = 0.815612, 0.948242, 0.773225, 1; 14:52:52.429 [planetinfo.record]: polar_sea_color = 0.891809, 0.864138, 0.910742, 1; 14:52:52.429 [planetinfo.record]: sea_color = 0.818356, 0.709879, 0.892578, 1; 14:52:52.429 [planetinfo.record]: rotation_speed = 0.000776 14:52:52.430 [planetinfo.record]: planet zpos = 612950.000000 14:52:52.430 [planetinfo.record]: sun_radius = 113797.866058 14:52:52.430 [planetinfo.record]: sun_vector = -0.424 -0.153 0.893 14:52:52.430 [planetinfo.record]: sun_distance = 943000 14:52:52.430 [planetinfo.record]: corona_flare = 0.035123 14:52:52.430 [planetinfo.record]: corona_hues = 0.569824 14:52:52.430 [planetinfo.record]: sun_color = 0.807831, 0.740317, 0.723166, 1 14:52:52.438 [planetinfo.record]: corona_shimmer = 0.495944 14:52:52.439 [planetinfo.record]: station_vector = 0.107 -0.234 0.966 14:52:52.439 [planetinfo.record]: station = dodecahedron 14:53:08.725 [PLANETINFO OVER]: Done 14:53:08.726 [PLANETINFO LOGGING]: 7 188 14:53:09.740 [planetinfo.record]: seed = 252 6 113 230 14:53:09.740 [planetinfo.record]: coordinates = 6 57 14:53:09.740 [planetinfo.record]: government = 7; 14:53:09.740 [planetinfo.record]: economy = 1; 14:53:09.740 [planetinfo.record]: techlevel = 12; 14:53:09.740 [planetinfo.record]: population = 57; 14:53:09.740 [planetinfo.record]: productivity = 45144; 14:53:09.740 [planetinfo.record]: name = "Bibirale"; 14:53:09.740 [planetinfo.record]: inhabitant = "Human Colonial"; 14:53:09.740 [planetinfo.record]: inhabitants = "Human Colonials"; 14:53:09.740 [planetinfo.record]: description = "This world is a revolting dump."; 14:53:09.748 [planetinfo.record]: air_color = 0.510587, 0.514328, 0.889734, 1; 14:53:09.748 [planetinfo.record]: cloud_alpha = 1.000000; 14:53:09.748 [planetinfo.record]: cloud_color = 0.21521, 0.222345, 0.671875, 1; 14:53:09.748 [planetinfo.record]: cloud_fraction = 0.240000; 14:53:09.748 [planetinfo.record]: land_color = 0.596191, 0.66, 0.28875, 1; 14:53:09.748 [planetinfo.record]: land_fraction = 0.690000; 14:53:09.748 [planetinfo.record]: polar_cloud_color = 0.461504, 0.46683, 0.802344, 1; 14:53:09.748 [planetinfo.record]: polar_land_color = 0.911425, 0.934, 0.802656, 1; 14:53:09.748 [planetinfo.record]: polar_sea_color = 0.877849, 0.928906, 0.876202, 1; 14:53:09.748 [planetinfo.record]: sea_color = 0.55463, 0.710938, 0.549588, 1; 14:53:09.748 [planetinfo.record]: rotation_speed = 0.003191 14:53:09.748 [planetinfo.record]: planet zpos = 653700.000000 14:53:09.748 [planetinfo.record]: sun_radius = 121247.438660 14:53:09.748 [planetinfo.record]: sun_vector = 0.035 -0.596 0.802 14:53:09.748 [planetinfo.record]: sun_distance = 828020 14:53:09.749 [planetinfo.record]: corona_flare = 0.092502 14:53:09.749 [planetinfo.record]: corona_hues = 0.503738 14:53:09.749 [planetinfo.record]: sun_color = 0.723062, 0.559864, 0.215354, 1 14:53:09.749 [planetinfo.record]: corona_shimmer = 0.342357 14:53:09.749 [planetinfo.record]: station_vector = 0.782 0.623 0.014 14:53:09.749 [planetinfo.record]: station = dodecahedron 14:53:16.029 [PLANETINFO OVER]: Done 14:53:16.030 [PLANETINFO LOGGING]: 7 189 14:53:17.033 [planetinfo.record]: seed = 178 33 243 92 14:53:17.033 [planetinfo.record]: coordinates = 33 20 14:53:17.033 [planetinfo.record]: government = 6; 14:53:17.033 [planetinfo.record]: economy = 4; 14:53:17.033 [planetinfo.record]: techlevel = 7; 14:53:17.033 [planetinfo.record]: population = 39; 14:53:17.033 [planetinfo.record]: productivity = 18720; 14:53:17.033 [planetinfo.record]: name = "Teenatle"; 14:53:17.033 [planetinfo.record]: inhabitant = "Yellow Furry Humanoid"; 14:53:17.033 [planetinfo.record]: inhabitants = "Yellow Furry Humanoids"; 14:53:17.033 [planetinfo.record]: description = "The world Teenatle is very famous for its unusual casinos but beset by lethal disease."; 14:53:17.115 [planetinfo.record]: air_color = 0.541743, 0.515901, 0.894938, 1; 14:53:17.116 [planetinfo.record]: cloud_alpha = 1.000000; 14:53:17.116 [planetinfo.record]: cloud_color = 0.32663, 0.225586, 0.6875, 1; 14:53:17.116 [planetinfo.record]: cloud_fraction = 0.290000; 14:53:17.116 [planetinfo.record]: land_color = 0.592203, 0.66, 0.404766, 1; 14:53:17.116 [planetinfo.record]: land_fraction = 0.540000; 14:53:17.116 [planetinfo.record]: polar_cloud_color = 0.543848, 0.469501, 0.809375, 1; 14:53:17.116 [planetinfo.record]: polar_land_color = 0.910014, 0.934, 0.843701, 1; 14:53:17.116 [planetinfo.record]: polar_sea_color = 0.860558, 0.843292, 0.94375, 1; 14:53:17.116 [planetinfo.record]: sea_color = 0.364162, 0.322998, 0.5625, 1; 14:53:17.116 [planetinfo.record]: rotation_speed = 0.000477 14:53:17.116 [planetinfo.record]: planet zpos = 769730.000000 14:53:17.116 [planetinfo.record]: sun_radius = 138656.889496 14:53:17.116 [planetinfo.record]: sun_vector = 0.544 0.659 0.520 14:53:17.116 [planetinfo.record]: sun_distance = 1421040 14:53:17.116 [planetinfo.record]: corona_flare = 0.071416 14:53:17.116 [planetinfo.record]: corona_hues = 0.656769 14:53:17.117 [planetinfo.record]: sun_color = 0.828888, 0.699215, 0.552071, 1 14:53:17.117 [planetinfo.record]: corona_shimmer = 0.520515 14:53:17.118 [planetinfo.record]: station_vector = 0.303 -0.464 0.832 14:53:17.118 [planetinfo.record]: station = coriolis 14:53:25.568 [PLANETINFO OVER]: Done 14:53:25.568 [PLANETINFO LOGGING]: 7 190 14:53:26.571 [planetinfo.record]: seed = 144 1 45 166 14:53:26.572 [planetinfo.record]: coordinates = 1 17 14:53:26.572 [planetinfo.record]: government = 2; 14:53:26.572 [planetinfo.record]: economy = 1; 14:53:26.572 [planetinfo.record]: techlevel = 8; 14:53:26.572 [planetinfo.record]: population = 36; 14:53:26.572 [planetinfo.record]: productivity = 15552; 14:53:26.572 [planetinfo.record]: name = "Biorle"; 14:53:26.572 [planetinfo.record]: inhabitant = "Human Colonial"; 14:53:26.572 [planetinfo.record]: inhabitants = "Human Colonials"; 14:53:26.572 [planetinfo.record]: description = "This planet is mildly noted for the Biorleian mountain lobstoid but plagued by unpredictable civil war."; 14:53:26.591 [planetinfo.record]: air_color = 0.460409, 0.47591, 0.926156, 1; 14:53:26.591 [planetinfo.record]: cloud_alpha = 1.000000; 14:53:26.591 [planetinfo.record]: cloud_color = 0.0701904, 0.131297, 0.78125, 1; 14:53:26.591 [planetinfo.record]: cloud_fraction = 0.290000; 14:53:26.591 [planetinfo.record]: land_color = 0.0860023, 0.652344, 0.00509644, 1; 14:53:26.591 [planetinfo.record]: land_fraction = 0.470000; 14:53:26.591 [planetinfo.record]: polar_cloud_color = 0.367153, 0.408782, 0.851562, 1; 14:53:26.592 [planetinfo.record]: polar_land_color = 0.731883, 0.934766, 0.7029, 1; 14:53:26.592 [planetinfo.record]: polar_sea_color = 0.949414, 0.891096, 0.941669, 1; 14:53:26.592 [planetinfo.record]: sea_color = 0.505859, 0.381568, 0.489352, 1; 14:53:26.592 [planetinfo.record]: rotation_speed = 0.000974 14:53:26.592 [planetinfo.record]: planet zpos = 391770.000000 14:53:26.592 [planetinfo.record]: sun_radius = 81803.401794 14:53:26.592 [planetinfo.record]: sun_vector = -0.831 0.322 -0.453 14:53:26.592 [planetinfo.record]: sun_distance = 914130 14:53:26.592 [planetinfo.record]: corona_flare = 0.029799 14:53:26.592 [planetinfo.record]: corona_hues = 0.566750 14:53:26.592 [planetinfo.record]: sun_color = 0.488832, 0.573973, 0.826535, 1 14:53:26.593 [planetinfo.record]: corona_shimmer = 0.267999 14:53:26.593 [planetinfo.record]: station_vector = 0.406 0.272 0.873 14:53:26.593 [planetinfo.record]: station = coriolis 14:53:31.754 [PLANETINFO OVER]: Done 14:53:31.755 [PLANETINFO LOGGING]: 7 191 14:53:32.757 [planetinfo.record]: seed = 166 192 31 219 14:53:32.757 [planetinfo.record]: coordinates = 192 97 14:53:32.757 [planetinfo.record]: government = 4; 14:53:32.757 [planetinfo.record]: economy = 1; 14:53:32.757 [planetinfo.record]: techlevel = 8; 14:53:32.757 [planetinfo.record]: population = 38; 14:53:32.757 [planetinfo.record]: productivity = 21888; 14:53:32.757 [planetinfo.record]: name = "Anteed"; 14:53:32.757 [planetinfo.record]: inhabitant = "Human Colonial"; 14:53:32.757 [planetinfo.record]: inhabitants = "Human Colonials"; 14:53:32.757 [planetinfo.record]: description = "This planet is notable for the Anteedian tree grub and its inhabitants’ ancient mating traditions."; 14:53:32.764 [planetinfo.record]: air_color = 0.538453, 0.964529, 0.87429, 1; 14:53:32.764 [planetinfo.record]: cloud_alpha = 1.000000; 14:53:32.764 [planetinfo.record]: cloud_color = 0.896484, 0.489389, 0.245132, 1; 14:53:32.764 [planetinfo.record]: cloud_fraction = 0.350000; 14:53:32.764 [planetinfo.record]: land_color = 0.241113, 0.611328, 0.033432, 1; 14:53:32.764 [planetinfo.record]: land_fraction = 0.350000; 14:53:32.764 [planetinfo.record]: polar_cloud_color = 0.903418, 0.647016, 0.493174, 1; 14:53:32.764 [planetinfo.record]: polar_land_color = 0.796725, 0.938867, 0.716986, 1; 14:53:32.764 [planetinfo.record]: polar_sea_color = 0.91222, 0.933008, 0.878704, 1; 14:53:32.764 [planetinfo.record]: sea_color = 0.610216, 0.669922, 0.513956, 1; 14:53:32.764 [planetinfo.record]: rotation_speed = 0.000246 14:53:32.764 [planetinfo.record]: planet zpos = 582400.000000 14:53:32.764 [planetinfo.record]: sun_radius = 164382.968750 14:53:32.765 [planetinfo.record]: sun_vector = 0.483 -0.595 -0.642 14:53:32.765 [planetinfo.record]: sun_distance = 1223040 14:53:32.766 [planetinfo.record]: corona_flare = 0.041757 14:53:32.766 [planetinfo.record]: corona_hues = 0.535080 14:53:32.766 [planetinfo.record]: sun_color = 0.654312, 0.510605, 0.433461, 1 14:53:32.766 [planetinfo.record]: corona_shimmer = 0.882568 14:53:32.766 [planetinfo.record]: station_vector = 0.440 -0.823 0.359 14:53:32.766 [planetinfo.record]: station = coriolis 14:53:37.452 [PLANETINFO OVER]: Done 14:53:37.452 [PLANETINFO LOGGING]: 7 192 14:53:38.472 [planetinfo.record]: seed = 164 112 41 6 14:53:38.472 [planetinfo.record]: coordinates = 112 152 14:53:38.472 [planetinfo.record]: government = 4; 14:53:38.472 [planetinfo.record]: economy = 0; 14:53:38.472 [planetinfo.record]: techlevel = 9; 14:53:38.472 [planetinfo.record]: population = 41; 14:53:38.472 [planetinfo.record]: productivity = 26240; 14:53:38.472 [planetinfo.record]: name = "Biabi"; 14:53:38.472 [planetinfo.record]: inhabitant = "Human Colonial"; 14:53:38.472 [planetinfo.record]: inhabitants = "Human Colonials"; 14:53:38.472 [planetinfo.record]: description = "The world Biabi is a boring world."; 14:53:38.498 [planetinfo.record]: air_color = 0.664856, 0.859767, 0.919002, 1; 14:53:38.500 [planetinfo.record]: cloud_alpha = 1.000000; 14:53:38.500 [planetinfo.record]: cloud_color = 0.635279, 0.759766, 0.584663, 1; 14:53:38.500 [planetinfo.record]: cloud_fraction = 0.170000; 14:53:38.500 [planetinfo.record]: land_color = 0.231171, 0.4529, 0.585938, 1; 14:53:38.500 [planetinfo.record]: land_fraction = 0.460000; 14:53:38.500 [planetinfo.record]: polar_cloud_color = 0.75568, 0.841895, 0.720626, 1; 14:53:38.500 [planetinfo.record]: polar_land_color = 0.798908, 0.887969, 0.941406, 1; 14:53:38.500 [planetinfo.record]: polar_sea_color = 0.935547, 0.898174, 0.873421, 1; 14:53:38.500 [planetinfo.record]: sea_color = 0.644531, 0.541542, 0.473328, 1; 14:53:38.500 [planetinfo.record]: rotation_speed = 0.001230 14:53:38.501 [planetinfo.record]: planet zpos = 624960.000000 14:53:38.501 [planetinfo.record]: sun_radius = 110433.867188 14:53:38.501 [planetinfo.record]: sun_vector = -0.469 0.883 0.029 14:53:38.501 [planetinfo.record]: sun_distance = 848160 14:53:38.501 [planetinfo.record]: corona_flare = 0.003224 14:53:38.501 [planetinfo.record]: corona_hues = 0.530281 14:53:38.501 [planetinfo.record]: sun_color = 0.423448, 0.435087, 0.847504, 1 14:53:38.501 [planetinfo.record]: corona_shimmer = 0.534090 14:53:38.502 [planetinfo.record]: station_vector = -0.588 0.728 0.352 14:53:38.502 [planetinfo.record]: station = coriolis 14:53:43.320 [PLANETINFO OVER]: Done 14:53:43.321 [PLANETINFO LOGGING]: 7 193 14:53:44.324 [planetinfo.record]: seed = 218 155 139 49 14:53:44.324 [planetinfo.record]: coordinates = 155 134 14:53:44.324 [planetinfo.record]: government = 3; 14:53:44.324 [planetinfo.record]: economy = 6; 14:53:44.324 [planetinfo.record]: techlevel = 6; 14:53:44.324 [planetinfo.record]: population = 34; 14:53:44.324 [planetinfo.record]: productivity = 7616; 14:53:44.324 [planetinfo.record]: name = "Atbele"; 14:53:44.324 [planetinfo.record]: inhabitant = "Red Furry Feline"; 14:53:44.324 [planetinfo.record]: inhabitants = "Red Furry Felines"; 14:53:44.324 [planetinfo.record]: description = "This world is plagued by deadly earthquakes."; 14:53:44.334 [planetinfo.record]: air_color = 0.60402, 0.565688, 0.859166, 1; 14:53:44.334 [planetinfo.record]: cloud_alpha = 1.000000; 14:53:44.334 [planetinfo.record]: cloud_color = 0.43343, 0.333092, 0.580078, 1; 14:53:44.334 [planetinfo.record]: cloud_fraction = 0.350000; 14:53:44.334 [planetinfo.record]: land_color = 0.66, 0.477839, 0.317109, 1; 14:53:44.334 [planetinfo.record]: land_fraction = 0.370000; 14:53:44.334 [planetinfo.record]: polar_cloud_color = 0.640788, 0.558514, 0.761035, 1; 14:53:44.334 [planetinfo.record]: polar_land_color = 0.934, 0.869554, 0.812689, 1; 14:53:44.334 [planetinfo.record]: polar_sea_color = 0.842676, 0.89381, 0.948242, 1; 14:53:44.334 [planetinfo.record]: sea_color = 0.287094, 0.398735, 0.517578, 1; 14:53:44.334 [planetinfo.record]: rotation_speed = 0.004850 14:53:44.334 [planetinfo.record]: planet zpos = 451780.000000 14:53:44.334 [planetinfo.record]: sun_radius = 77184.368439 14:53:44.334 [planetinfo.record]: sun_vector = 0.142 0.986 -0.085 14:53:44.334 [planetinfo.record]: sun_distance = 677670 14:53:44.334 [planetinfo.record]: corona_flare = 0.095683 14:53:44.335 [planetinfo.record]: corona_hues = 0.504112 14:53:44.335 [planetinfo.record]: sun_color = 0.266914, 0.356131, 0.839319, 1 14:53:44.335 [planetinfo.record]: corona_shimmer = 0.428642 14:53:44.335 [planetinfo.record]: station_vector = -0.444 0.441 0.780 14:53:44.335 [planetinfo.record]: station = coriolis 14:53:50.215 [PLANETINFO OVER]: Done 14:53:50.216 [PLANETINFO LOGGING]: 7 194 14:53:51.220 [planetinfo.record]: seed = 56 166 229 26 14:53:51.220 [planetinfo.record]: coordinates = 166 33 14:53:51.220 [planetinfo.record]: government = 7; 14:53:51.220 [planetinfo.record]: economy = 1; 14:53:51.220 [planetinfo.record]: techlevel = 12; 14:53:51.220 [planetinfo.record]: population = 57; 14:53:51.220 [planetinfo.record]: productivity = 45144; 14:53:51.220 [planetinfo.record]: name = "Quxege"; 14:53:51.220 [planetinfo.record]: inhabitant = "Green Frog"; 14:53:51.220 [planetinfo.record]: inhabitants = "Green Frogs"; 14:53:51.220 [planetinfo.record]: description = "This planet is beset by lethal disease."; 14:53:51.234 [planetinfo.record]: air_color = 0.470336, 0.683211, 0.858516, 1; 14:53:51.234 [planetinfo.record]: cloud_alpha = 1.000000; 14:53:51.234 [planetinfo.record]: cloud_color = 0.140015, 0.578125, 0.300883, 1; 14:53:51.234 [planetinfo.record]: cloud_fraction = 0.300000; 14:53:51.234 [planetinfo.record]: land_color = 0.650391, 0.426819, 0.484458, 1; 14:53:51.234 [planetinfo.record]: land_fraction = 0.250000; 14:53:51.234 [planetinfo.record]: polar_cloud_color = 0.400121, 0.760156, 0.532322, 1; 14:53:51.234 [planetinfo.record]: polar_land_color = 0.934961, 0.854613, 0.875328, 1; 14:53:51.235 [planetinfo.record]: polar_sea_color = 0.929883, 0.889842, 0.854511, 1; 14:53:51.235 [planetinfo.record]: sea_color = 0.701172, 0.580401, 0.473839, 1; 14:53:51.235 [planetinfo.record]: rotation_speed = 0.004060 14:53:51.235 [planetinfo.record]: planet zpos = 665040.000000 14:53:51.235 [planetinfo.record]: sun_radius = 136939.897461 14:53:51.235 [planetinfo.record]: sun_vector = 0.773 0.615 0.154 14:53:51.235 [planetinfo.record]: sun_distance = 1052980 14:53:51.235 [planetinfo.record]: corona_flare = 0.069562 14:53:51.235 [planetinfo.record]: corona_hues = 0.836449 14:53:51.235 [planetinfo.record]: sun_color = 0.396228, 0.530979, 0.746323, 1 14:53:51.235 [planetinfo.record]: corona_shimmer = 0.289140 14:53:51.235 [planetinfo.record]: station_vector = -0.666 -0.604 0.438 14:53:51.235 [planetinfo.record]: station = dodecahedron 14:53:56.496 [PLANETINFO OVER]: Done 14:53:56.497 [PLANETINFO LOGGING]: 7 195 14:53:57.518 [planetinfo.record]: seed = 78 160 183 37 14:53:57.518 [planetinfo.record]: coordinates = 160 163 14:53:57.518 [planetinfo.record]: government = 1; 14:53:57.518 [planetinfo.record]: economy = 3; 14:53:57.518 [planetinfo.record]: techlevel = 5; 14:53:57.518 [planetinfo.record]: population = 25; 14:53:57.518 [planetinfo.record]: productivity = 7000; 14:53:57.518 [planetinfo.record]: name = "Ceesari"; 14:53:57.518 [planetinfo.record]: inhabitant = "Fierce Red Bony Bird"; 14:53:57.518 [planetinfo.record]: inhabitants = "Fierce Red Bony Birds"; 14:53:57.518 [planetinfo.record]: description = "This planet is beset by lethal disease."; 14:53:57.543 [planetinfo.record]: air_color = 0.56255, 0.967131, 0.95407, 1; 14:53:57.543 [planetinfo.record]: cloud_alpha = 1.000000; 14:53:57.543 [planetinfo.record]: cloud_color = 0.904297, 0.853298, 0.310852, 1; 14:53:57.543 [planetinfo.record]: cloud_fraction = 0.150000; 14:53:57.544 [planetinfo.record]: land_color = 0.16008, 0.525391, 0.200036, 1; 14:53:57.544 [planetinfo.record]: land_fraction = 0.300000; 14:53:57.544 [planetinfo.record]: polar_cloud_color = 0.906934, 0.874966, 0.534949, 1; 14:53:57.544 [planetinfo.record]: polar_land_color = 0.782766, 0.947461, 0.800779, 1; 14:53:57.544 [planetinfo.record]: polar_sea_color = 0.927344, 0.902107, 0.848556, 1; 14:53:57.544 [planetinfo.record]: sea_color = 0.726562, 0.647472, 0.479645, 1; 14:53:57.544 [planetinfo.record]: rotation_speed = 0.004533 14:53:57.544 [planetinfo.record]: planet zpos = 638400.000000 14:53:57.544 [planetinfo.record]: sun_radius = 109306.777344 14:53:57.544 [planetinfo.record]: sun_vector = -0.128 -0.028 -0.991 14:53:57.544 [planetinfo.record]: sun_distance = 723520 14:53:57.544 [planetinfo.record]: corona_flare = 0.003618 14:53:57.545 [planetinfo.record]: corona_hues = 0.646980 14:53:57.545 [planetinfo.record]: sun_color = 0.768814, 0.767355, 0.386349, 1 14:53:57.546 [planetinfo.record]: corona_shimmer = 0.375418 14:53:57.546 [planetinfo.record]: station_vector = -0.225 0.231 0.946 14:53:57.546 [planetinfo.record]: station = coriolis 14:54:02.806 [PLANETINFO OVER]: Done 14:54:02.807 [PLANETINFO LOGGING]: 7 196 14:54:03.811 [planetinfo.record]: seed = 76 190 225 86 14:54:03.811 [planetinfo.record]: coordinates = 190 47 14:54:03.811 [planetinfo.record]: government = 1; 14:54:03.811 [planetinfo.record]: economy = 7; 14:54:03.811 [planetinfo.record]: techlevel = 3; 14:54:03.811 [planetinfo.record]: population = 21; 14:54:03.811 [planetinfo.record]: productivity = 2520; 14:54:03.811 [planetinfo.record]: name = "Vezaorla"; 14:54:03.811 [planetinfo.record]: inhabitant = "Yellow Bug-Eyed Lobster"; 14:54:03.811 [planetinfo.record]: inhabitants = "Yellow Bug-Eyed Lobsters"; 14:54:03.811 [planetinfo.record]: description = "Vezaorla is a revolting little planet."; 14:54:03.813 [planetinfo.record]: air_color = 0.582546, 0.858382, 0.997049, 1; 14:54:03.813 [planetinfo.record]: cloud_alpha = 1.000000; 14:54:03.813 [planetinfo.record]: cloud_color = 0.349503, 0.994141, 0.359575, 1; 14:54:03.813 [planetinfo.record]: cloud_fraction = 0.480000; 14:54:03.813 [planetinfo.record]: land_color = 0.66, 0.432621, 0.250078, 1; 14:54:03.813 [planetinfo.record]: land_fraction = 0.450000; 14:54:03.813 [planetinfo.record]: polar_cloud_color = 0.563422, 0.947363, 0.569421, 1; 14:54:03.813 [planetinfo.record]: polar_land_color = 0.934, 0.853556, 0.788975, 1; 14:54:03.813 [planetinfo.record]: polar_sea_color = 0.890299, 0.933203, 0.885449, 1; 14:54:03.813 [planetinfo.record]: sea_color = 0.54513, 0.667969, 0.531244, 1; 14:54:03.813 [planetinfo.record]: rotation_speed = 0.004404 14:54:03.813 [planetinfo.record]: planet zpos = 545040.000000 14:54:03.813 [planetinfo.record]: sun_radius = 134391.525879 14:54:03.814 [planetinfo.record]: sun_vector = 0.387 0.007 -0.922 14:54:03.814 [planetinfo.record]: sun_distance = 999240 14:54:03.814 [planetinfo.record]: corona_flare = 0.067024 14:54:03.814 [planetinfo.record]: corona_hues = 0.715492 14:54:03.814 [planetinfo.record]: sun_color = 0.719124, 0.778667, 0.824167, 1 14:54:03.814 [planetinfo.record]: corona_shimmer = 0.283108 14:54:03.814 [planetinfo.record]: station_vector = 0.974 0.173 0.143 14:54:03.814 [planetinfo.record]: station = coriolis 14:54:08.480 [PLANETINFO OVER]: Done 14:54:08.481 [PLANETINFO LOGGING]: 7 197 14:54:09.485 [planetinfo.record]: seed = 2 245 35 147 14:54:09.485 [planetinfo.record]: coordinates = 245 89 14:54:09.485 [planetinfo.record]: government = 0; 14:54:09.485 [planetinfo.record]: economy = 3; 14:54:09.485 [planetinfo.record]: techlevel = 5; 14:54:09.485 [planetinfo.record]: population = 24; 14:54:09.485 [planetinfo.record]: productivity = 5376; 14:54:09.485 [planetinfo.record]: name = "Belees"; 14:54:09.485 [planetinfo.record]: inhabitant = "Human Colonial"; 14:54:09.485 [planetinfo.record]: inhabitants = "Human Colonials"; 14:54:09.485 [planetinfo.record]: description = "The world Belees is beset by evil disease."; 14:54:09.493 [planetinfo.record]: air_color = 0.432827, 0.781486, 0.857215, 1; 14:54:09.493 [planetinfo.record]: cloud_alpha = 1.000000; 14:54:09.493 [planetinfo.record]: cloud_color = 0.295766, 0.574219, 0.0650482, 1; 14:54:09.493 [planetinfo.record]: cloud_fraction = 0.570000; 14:54:09.493 [planetinfo.record]: land_color = 0.45375, 0.582656, 0.66, 1; 14:54:09.493 [planetinfo.record]: land_fraction = 0.640000; 14:54:09.494 [planetinfo.record]: polar_cloud_color = 0.528545, 0.758398, 0.338095, 1; 14:54:09.494 [planetinfo.record]: polar_land_color = 0.861031, 0.906637, 0.934, 1; 14:54:09.494 [planetinfo.record]: polar_sea_color = 0.927579, 0.936523, 0.884484, 1; 14:54:09.494 [planetinfo.record]: sea_color = 0.610516, 0.634766, 0.493679, 1; 14:54:09.494 [planetinfo.record]: rotation_speed = 0.004284 14:54:09.494 [planetinfo.record]: planet zpos = 536060.000000 14:54:09.494 [planetinfo.record]: sun_radius = 90245.818787 14:54:09.494 [planetinfo.record]: sun_vector = 0.794 -0.449 -0.411 14:54:09.494 [planetinfo.record]: sun_distance = 842380 14:54:09.494 [planetinfo.record]: corona_flare = 0.059062 14:54:09.494 [planetinfo.record]: corona_hues = 0.797356 14:54:09.494 [planetinfo.record]: sun_color = 0.336825, 0.483715, 0.69599, 1 14:54:09.494 [planetinfo.record]: corona_shimmer = 0.246844 14:54:09.494 [planetinfo.record]: station_vector = 0.492 0.685 0.538 14:54:09.494 [planetinfo.record]: station = coriolis 14:54:13.748 [PLANETINFO OVER]: Done 14:54:13.750 [PLANETINFO LOGGING]: 7 198 14:54:14.769 [planetinfo.record]: seed = 224 222 157 42 14:54:14.769 [planetinfo.record]: coordinates = 222 105 14:54:14.769 [planetinfo.record]: government = 4; 14:54:14.769 [planetinfo.record]: economy = 1; 14:54:14.769 [planetinfo.record]: techlevel = 10; 14:54:14.769 [planetinfo.record]: population = 46; 14:54:14.769 [planetinfo.record]: productivity = 26496; 14:54:14.769 [planetinfo.record]: name = "Arbetequ"; 14:54:14.769 [planetinfo.record]: inhabitant = "Small Red Frog"; 14:54:14.769 [planetinfo.record]: inhabitants = "Small Red Frogs"; 14:54:14.770 [planetinfo.record]: description = "Arbetequ is a revolting dump."; 14:54:14.797 [planetinfo.record]: air_color = 0.46211, 0.732209, 0.845508, 1; 14:54:14.797 [planetinfo.record]: cloud_alpha = 1.000000; 14:54:14.797 [planetinfo.record]: cloud_color = 0.170151, 0.539062, 0.128448, 1; 14:54:14.797 [planetinfo.record]: cloud_fraction = 0.380000; 14:54:14.797 [planetinfo.record]: land_color = 0.433125, 0.66, 0.553652, 1; 14:54:14.797 [planetinfo.record]: land_fraction = 0.330000; 14:54:14.797 [planetinfo.record]: polar_cloud_color = 0.42496, 0.742578, 0.389056, 1; 14:54:14.797 [planetinfo.record]: polar_land_color = 0.853734, 0.934, 0.896375, 1; 14:54:14.797 [planetinfo.record]: polar_sea_color = 0.933398, 0.863909, 0.858654, 1; 14:54:14.797 [planetinfo.record]: sea_color = 0.666016, 0.467682, 0.452682, 1; 14:54:14.797 [planetinfo.record]: rotation_speed = 0.002201 14:54:14.797 [planetinfo.record]: planet zpos = 783720.000000 14:54:14.797 [planetinfo.record]: sun_radius = 169006.025391 14:54:14.797 [planetinfo.record]: sun_vector = -0.979 0.183 -0.087 14:54:14.797 [planetinfo.record]: sun_distance = 1063620 14:54:14.797 [planetinfo.record]: corona_flare = 0.092795 14:54:14.797 [planetinfo.record]: corona_hues = 0.510109 14:54:14.797 [planetinfo.record]: sun_color = 0.676956, 0.539841, 0.33681, 1 14:54:14.798 [planetinfo.record]: corona_shimmer = 0.353149 14:54:14.799 [planetinfo.record]: station_vector = 0.095 0.354 0.931 14:54:14.799 [planetinfo.record]: station = coriolis 14:54:20.164 [PLANETINFO OVER]: Done 14:54:20.165 [PLANETINFO LOGGING]: 7 199 14:54:21.186 [planetinfo.record]: seed = 246 26 79 11 14:54:21.186 [planetinfo.record]: coordinates = 26 124 14:54:21.186 [planetinfo.record]: government = 6; 14:54:21.186 [planetinfo.record]: economy = 4; 14:54:21.186 [planetinfo.record]: techlevel = 8; 14:54:21.186 [planetinfo.record]: population = 43; 14:54:21.186 [planetinfo.record]: productivity = 20640; 14:54:21.186 [planetinfo.record]: name = "Mageesti"; 14:54:21.186 [planetinfo.record]: inhabitant = "Human Colonial"; 14:54:21.187 [planetinfo.record]: inhabitants = "Human Colonials"; 14:54:21.187 [planetinfo.record]: description = "Mageesti is most famous for its vast oceans and its inhabitants’ exceptional love for tourists."; 14:54:21.203 [planetinfo.record]: air_color = 0.897611, 0.790057, 0.990545, 1; 14:54:21.203 [planetinfo.record]: cloud_alpha = 1.000000; 14:54:21.203 [planetinfo.record]: cloud_color = 0.974609, 0.966995, 0.969851, 1; 14:54:21.203 [planetinfo.record]: cloud_fraction = 0.410000; 14:54:21.203 [planetinfo.record]: land_color = 0.66, 0.598125, 0.63583, 1; 14:54:21.203 [planetinfo.record]: land_fraction = 0.200000; 14:54:21.203 [planetinfo.record]: polar_cloud_color = 0.938574, 0.933991, 0.93571, 1; 14:54:21.203 [planetinfo.record]: polar_land_color = 0.934, 0.912109, 0.925449, 1; 14:54:21.203 [planetinfo.record]: polar_sea_color = 0.941797, 0.916315, 0.889189, 1; 14:54:21.203 [planetinfo.record]: sea_color = 0.582031, 0.519039, 0.451984, 1; 14:54:21.203 [planetinfo.record]: rotation_speed = 0.003875 14:54:21.203 [planetinfo.record]: planet zpos = 678960.000000 14:54:21.204 [planetinfo.record]: sun_radius = 128219.279480 14:54:21.204 [planetinfo.record]: sun_vector = -0.713 0.439 0.547 14:54:21.204 [planetinfo.record]: sun_distance = 1131600 14:54:21.204 [planetinfo.record]: corona_flare = 0.045384 14:54:21.204 [planetinfo.record]: corona_hues = 0.927437 14:54:21.204 [planetinfo.record]: sun_color = 0.826141, 0.76425, 0.489478, 1 14:54:21.204 [planetinfo.record]: corona_shimmer = 0.519715 14:54:21.204 [planetinfo.record]: station_vector = 0.339 0.941 0.016 14:54:21.204 [planetinfo.record]: station = coriolis 14:54:27.131 [PLANETINFO OVER]: Done 14:54:27.133 [PLANETINFO LOGGING]: 7 200 14:54:28.136 [planetinfo.record]: seed = 244 119 153 228 14:54:28.136 [planetinfo.record]: coordinates = 119 201 14:54:28.136 [planetinfo.record]: government = 6; 14:54:28.136 [planetinfo.record]: economy = 1; 14:54:28.136 [planetinfo.record]: techlevel = 12; 14:54:28.136 [planetinfo.record]: population = 56; 14:54:28.136 [planetinfo.record]: productivity = 40320; 14:54:28.136 [planetinfo.record]: name = "Zabixedi"; 14:54:28.136 [planetinfo.record]: inhabitant = "Fierce Feline"; 14:54:28.136 [planetinfo.record]: inhabitants = "Fierce Felines"; 14:54:28.136 [planetinfo.record]: description = "The planet Zabixedi is an unremarkable planet."; 14:54:28.161 [planetinfo.record]: air_color = 0.646155, 0.877377, 0.83382, 1; 14:54:28.162 [planetinfo.record]: cloud_alpha = 1.000000; 14:54:28.162 [planetinfo.record]: cloud_color = 0.634766, 0.567372, 0.513268, 1; 14:54:28.162 [planetinfo.record]: cloud_fraction = 0.350000; 14:54:28.162 [planetinfo.record]: land_color = 0.61454, 0.564609, 0.66, 1; 14:54:28.162 [planetinfo.record]: land_fraction = 0.680000; 14:54:28.162 [planetinfo.record]: polar_cloud_color = 0.785645, 0.733512, 0.691659, 1; 14:54:28.163 [planetinfo.record]: polar_land_color = 0.917917, 0.900252, 0.934, 1; 14:54:28.163 [planetinfo.record]: polar_sea_color = 0.89376, 0.94707, 0.746373, 1; 14:54:28.163 [planetinfo.record]: sea_color = 0.410121, 0.529297, 0.0806351, 1; 14:54:28.163 [planetinfo.record]: rotation_speed = 0.002583 14:54:28.163 [planetinfo.record]: planet zpos = 514670.000000 14:54:28.163 [planetinfo.record]: sun_radius = 98742.423248 14:54:28.163 [planetinfo.record]: sun_vector = 0.208 0.978 0.011 14:54:28.163 [planetinfo.record]: sun_distance = 791800 14:54:28.163 [planetinfo.record]: corona_flare = 0.021974 14:54:28.164 [planetinfo.record]: corona_hues = 0.719299 14:54:28.164 [planetinfo.record]: sun_color = 0.785532, 0.778106, 0.775958, 1 14:54:28.164 [planetinfo.record]: corona_shimmer = 0.279781 14:54:28.164 [planetinfo.record]: station_vector = 0.485 -0.865 0.126 14:54:28.165 [planetinfo.record]: station = icosahedron 14:54:32.648 [PLANETINFO OVER]: Done 14:54:32.650 [PLANETINFO LOGGING]: 7 201 14:54:33.653 [planetinfo.record]: seed = 42 141 187 53 14:54:33.653 [planetinfo.record]: coordinates = 141 130 14:54:33.653 [planetinfo.record]: government = 5; 14:54:33.653 [planetinfo.record]: economy = 2; 14:54:33.653 [planetinfo.record]: techlevel = 9; 14:54:33.653 [planetinfo.record]: population = 44; 14:54:33.654 [planetinfo.record]: productivity = 25344; 14:54:33.654 [planetinfo.record]: name = "Laceus"; 14:54:33.654 [planetinfo.record]: inhabitant = "Red Rodent"; 14:54:33.654 [planetinfo.record]: inhabitants = "Red Rodents"; 14:54:33.654 [planetinfo.record]: description = "This planet is very notable for its inhabitants’ weird silliness and its inhabitants’ ingrained silliness."; 14:54:33.656 [planetinfo.record]: air_color = 0.481915, 0.49616, 0.909896, 1; 14:54:33.656 [planetinfo.record]: cloud_alpha = 1.000000; 14:54:33.656 [planetinfo.record]: cloud_color = 0.137329, 0.18847, 0.732422, 1; 14:54:33.656 [planetinfo.record]: cloud_fraction = 0.120000; 14:54:33.657 [planetinfo.record]: land_color = 0.66, 0.229473, 0.219141, 1; 14:54:33.657 [planetinfo.record]: land_fraction = 0.270000; 14:54:33.657 [planetinfo.record]: polar_cloud_color = 0.408314, 0.444517, 0.82959, 1; 14:54:33.657 [planetinfo.record]: polar_land_color = 0.934, 0.781685, 0.778029, 1; 14:54:33.657 [planetinfo.record]: polar_sea_color = 0.883421, 0.931641, 0.886435, 1; 14:54:33.657 [planetinfo.record]: sea_color = 0.542068, 0.683594, 0.550914, 1; 14:54:33.657 [planetinfo.record]: rotation_speed = 0.003600 14:54:33.657 [planetinfo.record]: planet zpos = 423700.000000 14:54:33.657 [planetinfo.record]: sun_radius = 109487.943573 14:54:33.657 [planetinfo.record]: sun_vector = 0.422 -0.669 0.612 14:54:33.657 [planetinfo.record]: sun_distance = 847400 14:54:33.657 [planetinfo.record]: corona_flare = 0.092181 14:54:33.657 [planetinfo.record]: corona_hues = 0.558334 14:54:33.658 [planetinfo.record]: sun_color = 0.299959, 0.471776, 0.660117, 1 14:54:33.658 [planetinfo.record]: corona_shimmer = 0.517384 14:54:33.658 [planetinfo.record]: station_vector = -0.932 -0.332 0.147 14:54:33.658 [planetinfo.record]: station = coriolis 14:54:38.582 [PLANETINFO OVER]: Done 14:54:38.583 [PLANETINFO LOGGING]: 7 202 14:54:39.587 [planetinfo.record]: seed = 136 131 85 209 14:54:39.587 [planetinfo.record]: coordinates = 131 8 14:54:39.587 [planetinfo.record]: government = 1; 14:54:39.587 [planetinfo.record]: economy = 2; 14:54:39.587 [planetinfo.record]: techlevel = 9; 14:54:39.587 [planetinfo.record]: population = 40; 14:54:39.587 [planetinfo.record]: productivity = 12800; 14:54:39.588 [planetinfo.record]: name = "Atisen"; 14:54:39.588 [planetinfo.record]: inhabitant = "Human Colonial"; 14:54:39.588 [planetinfo.record]: inhabitants = "Human Colonials"; 14:54:39.588 [planetinfo.record]: description = "The planet Atisen is an unremarkable planet."; 14:54:39.595 [planetinfo.record]: air_color = 0.697614, 0.592456, 0.868922, 1; 14:54:39.595 [planetinfo.record]: cloud_alpha = 1.000000; 14:54:39.596 [planetinfo.record]: cloud_color = 0.609375, 0.392761, 0.575529, 1; 14:54:39.596 [planetinfo.record]: cloud_fraction = 0.530000; 14:54:39.596 [planetinfo.record]: land_color = 0.518445, 0.0128906, 0.66, 1; 14:54:39.596 [planetinfo.record]: land_fraction = 0.600000; 14:54:39.596 [planetinfo.record]: polar_cloud_color = 0.774219, 0.602212, 0.747343, 1; 14:54:39.596 [planetinfo.record]: polar_land_color = 0.88392, 0.705061, 0.934, 1; 14:54:39.596 [planetinfo.record]: polar_sea_color = 0.937891, 0.926802, 0.883303, 1; 14:54:39.596 [planetinfo.record]: sea_color = 0.621094, 0.591722, 0.476495, 1; 14:54:39.596 [planetinfo.record]: rotation_speed = 0.000388 14:54:39.596 [planetinfo.record]: planet zpos = 448420.000000 14:54:39.597 [planetinfo.record]: sun_radius = 81596.933289 14:54:39.597 [planetinfo.record]: sun_vector = 0.244 -0.540 0.806 14:54:39.597 [planetinfo.record]: sun_distance = 704660 14:54:39.597 [planetinfo.record]: corona_flare = 0.052336 14:54:39.597 [planetinfo.record]: corona_hues = 0.822556 14:54:39.597 [planetinfo.record]: sun_color = 0.810779, 0.645482, 0.460631, 1 14:54:39.597 [planetinfo.record]: corona_shimmer = 0.314771 14:54:39.598 [planetinfo.record]: station_vector = -0.376 0.227 0.898 14:54:39.598 [planetinfo.record]: station = coriolis 14:54:44.316 [PLANETINFO OVER]: Done 14:54:44.317 [PLANETINFO LOGGING]: 7 203 14:54:45.339 [planetinfo.record]: seed = 158 224 231 239 14:54:45.339 [planetinfo.record]: coordinates = 224 178 14:54:45.339 [planetinfo.record]: government = 3; 14:54:45.339 [planetinfo.record]: economy = 2; 14:54:45.339 [planetinfo.record]: techlevel = 7; 14:54:45.339 [planetinfo.record]: population = 34; 14:54:45.339 [planetinfo.record]: productivity = 15232; 14:54:45.339 [planetinfo.record]: name = "Axebe"; 14:54:45.339 [planetinfo.record]: inhabitant = "Horned Humanoid"; 14:54:45.339 [planetinfo.record]: inhabitants = "Horned Humanoids"; 14:54:45.339 [planetinfo.record]: description = "This world is most fabled for Axebeian Itit water but beset by evil disease."; 14:54:45.342 [planetinfo.record]: air_color = 0.753115, 0.506847, 0.959326, 1; 14:54:45.343 [planetinfo.record]: cloud_alpha = 1.000000; 14:54:45.343 [planetinfo.record]: cloud_color = 0.880859, 0.16172, 0.414543, 1; 14:54:45.343 [planetinfo.record]: cloud_fraction = 0.300000; 14:54:45.343 [planetinfo.record]: land_color = 0.223755, 0.419578, 0.609375, 1; 14:54:45.343 [planetinfo.record]: land_fraction = 0.400000; 14:54:45.343 [planetinfo.record]: polar_cloud_color = 0.896387, 0.439002, 0.599801, 1; 14:54:45.343 [planetinfo.record]: polar_land_color = 0.7905, 0.865942, 0.939062, 1; 14:54:45.343 [planetinfo.record]: polar_sea_color = 0.912546, 0.935352, 0.885022, 1; 14:54:45.343 [planetinfo.record]: sea_color = 0.583434, 0.646484, 0.507339, 1; 14:54:45.343 [planetinfo.record]: rotation_speed = 0.003386 14:54:45.344 [planetinfo.record]: planet zpos = 963200.000000 14:54:45.344 [planetinfo.record]: sun_radius = 176660.083008 14:54:45.344 [planetinfo.record]: sun_vector = -0.041 0.792 -0.609 14:54:45.344 [planetinfo.record]: sun_distance = 1169600 14:54:45.344 [planetinfo.record]: corona_flare = 0.037784 14:54:45.344 [planetinfo.record]: corona_hues = 0.960556 14:54:45.344 [planetinfo.record]: sun_color = 0.849042, 0.347724, 0.298718, 1 14:54:45.344 [planetinfo.record]: corona_shimmer = 0.394382 14:54:45.344 [planetinfo.record]: station_vector = -0.388 0.102 0.916 14:54:45.344 [planetinfo.record]: station = coriolis 14:54:49.968 [PLANETINFO OVER]: Done 14:54:49.970 [PLANETINFO LOGGING]: 7 204 14:54:50.990 [planetinfo.record]: seed = 156 197 81 155 14:54:50.991 [planetinfo.record]: coordinates = 197 83 14:54:50.991 [planetinfo.record]: government = 3; 14:54:50.991 [planetinfo.record]: economy = 3; 14:54:50.991 [planetinfo.record]: techlevel = 7; 14:54:50.991 [planetinfo.record]: population = 35; 14:54:50.991 [planetinfo.record]: productivity = 13720; 14:54:50.991 [planetinfo.record]: name = "Anrara"; 14:54:50.991 [planetinfo.record]: inhabitant = "Human Colonial"; 14:54:50.991 [planetinfo.record]: inhabitants = "Human Colonials"; 14:54:50.991 [planetinfo.record]: description = "The planet Anrara is most famous for its pink Anraraian Il Ilweed plantations."; 14:54:51.023 [planetinfo.record]: air_color = 0.606689, 0.903008, 0.957375, 1; 14:54:51.023 [planetinfo.record]: cloud_alpha = 1.000000; 14:54:51.023 [planetinfo.record]: cloud_color = 0.666504, 0.875, 0.4375, 1; 14:54:51.023 [planetinfo.record]: cloud_fraction = 0.190000; 14:54:51.024 [planetinfo.record]: land_color = 0.649688, 0.657825, 0.66, 1; 14:54:51.024 [planetinfo.record]: land_fraction = 0.240000; 14:54:51.024 [planetinfo.record]: polar_cloud_color = 0.760648, 0.89375, 0.614453, 1; 14:54:51.024 [planetinfo.record]: polar_land_color = 0.930352, 0.93323, 0.934, 1; 14:54:51.024 [planetinfo.record]: polar_sea_color = 0.778198, 0.94082, 0.929386, 1; 14:54:51.024 [planetinfo.record]: sea_color = 0.182625, 0.591797, 0.563027, 1; 14:54:51.024 [planetinfo.record]: rotation_speed = 0.000686 14:54:51.024 [planetinfo.record]: planet zpos = 699480.000000 14:54:51.024 [planetinfo.record]: sun_radius = 180275.095367 14:54:51.024 [planetinfo.record]: sun_vector = 0.758 0.097 0.645 14:54:51.024 [planetinfo.record]: sun_distance = 1340670 14:54:51.024 [planetinfo.record]: corona_flare = 0.016205 14:54:51.025 [planetinfo.record]: corona_hues = 0.966560 14:54:51.025 [planetinfo.record]: sun_color = 0.721509, 0.352772, 0.280097, 1 14:54:51.025 [planetinfo.record]: corona_shimmer = 0.377433 14:54:51.025 [planetinfo.record]: station_vector = 0.588 -0.362 0.723 14:54:51.025 [planetinfo.record]: station = coriolis 14:54:55.828 [PLANETINFO OVER]: Done 14:54:55.829 [PLANETINFO LOGGING]: 7 205 14:54:56.833 [planetinfo.record]: seed = 82 100 83 45 14:54:56.833 [planetinfo.record]: coordinates = 100 20 14:54:56.833 [planetinfo.record]: government = 2; 14:54:56.833 [planetinfo.record]: economy = 4; 14:54:56.833 [planetinfo.record]: techlevel = 4; 14:54:56.833 [planetinfo.record]: population = 23; 14:54:56.833 [planetinfo.record]: productivity = 6624; 14:54:56.833 [planetinfo.record]: name = "Dibiedin"; 14:54:56.833 [planetinfo.record]: inhabitant = "Human Colonial"; 14:54:56.833 [planetinfo.record]: inhabitants = "Human Colonials"; 14:54:56.833 [planetinfo.record]: description = "The world Dibiedin is a dull place."; 14:54:56.856 [planetinfo.record]: air_color = 0.574045, 0.911414, 0.984691, 1; 14:54:56.856 [planetinfo.record]: cloud_alpha = 1.000000; 14:54:56.856 [planetinfo.record]: cloud_color = 0.61561, 0.957031, 0.332718, 1; 14:54:56.856 [planetinfo.record]: cloud_fraction = 0.380000; 14:54:56.856 [planetinfo.record]: land_color = 0.658203, 0.362526, 0.598144, 1; 14:54:56.856 [planetinfo.record]: land_fraction = 0.250000; 14:54:56.856 [planetinfo.record]: polar_cloud_color = 0.723155, 0.930664, 0.551219, 1; 14:54:56.856 [planetinfo.record]: polar_land_color = 0.93418, 0.829267, 0.912869, 1; 14:54:56.856 [planetinfo.record]: polar_sea_color = 0.90556, 0.932422, 0.878698, 1; 14:54:56.856 [planetinfo.record]: sea_color = 0.597908, 0.675781, 0.520035, 1; 14:54:56.856 [planetinfo.record]: rotation_speed = 0.002965 14:54:56.856 [planetinfo.record]: planet zpos = 811720.000000 14:54:56.856 [planetinfo.record]: sun_radius = 169191.858521 14:54:56.856 [planetinfo.record]: sun_vector = 0.529 0.562 -0.636 14:54:56.863 [planetinfo.record]: sun_distance = 1373680 14:54:56.863 [planetinfo.record]: corona_flare = 0.041708 14:54:56.864 [planetinfo.record]: corona_hues = 0.798668 14:54:56.864 [planetinfo.record]: sun_color = 0.557668, 0.603204, 0.662537, 1 14:54:56.864 [planetinfo.record]: corona_shimmer = 0.387237 14:54:56.864 [planetinfo.record]: station_vector = 0.111 -0.040 0.993 14:54:56.864 [planetinfo.record]: station = coriolis 14:55:02.134 [PLANETINFO OVER]: Done 14:55:02.135 [PLANETINFO LOGGING]: 7 206 14:55:03.139 [planetinfo.record]: seed = 48 12 13 235 14:55:03.139 [planetinfo.record]: coordinates = 12 56 14:55:03.139 [planetinfo.record]: government = 6; 14:55:03.139 [planetinfo.record]: economy = 0; 14:55:03.139 [planetinfo.record]: techlevel = 10; 14:55:03.139 [planetinfo.record]: population = 47; 14:55:03.139 [planetinfo.record]: productivity = 37600; 14:55:03.139 [planetinfo.record]: name = "Maabile"; 14:55:03.139 [planetinfo.record]: inhabitant = "Human Colonial"; 14:55:03.139 [planetinfo.record]: inhabitants = "Human Colonials"; 14:55:03.139 [planetinfo.record]: description = "This world is fabled for its exciting vicious Dioureer gargle blasters."; 14:55:03.148 [planetinfo.record]: air_color = 0.59214, 0.926807, 0.868987, 1; 14:55:03.148 [planetinfo.record]: cloud_alpha = 1.000000; 14:55:03.148 [planetinfo.record]: cloud_color = 0.783203, 0.590557, 0.403839, 1; 14:55:03.149 [planetinfo.record]: cloud_fraction = 0.500000; 14:55:03.149 [planetinfo.record]: land_color = 0.239927, 0.0489844, 0.66, 1; 14:55:03.149 [planetinfo.record]: land_fraction = 0.310000; 14:55:03.149 [planetinfo.record]: polar_cloud_color = 0.852441, 0.721394, 0.594378, 1; 14:55:03.149 [planetinfo.record]: polar_land_color = 0.785383, 0.71783, 0.934, 1; 14:55:03.149 [planetinfo.record]: polar_sea_color = 0.931715, 0.934766, 0.87899, 1; 14:55:03.149 [planetinfo.record]: sea_color = 0.643829, 0.652344, 0.496648, 1; 14:55:03.149 [planetinfo.record]: rotation_speed = 0.003507 14:55:03.149 [planetinfo.record]: planet zpos = 620840.000000 14:55:03.149 [planetinfo.record]: sun_radius = 180433.002930 14:55:03.149 [planetinfo.record]: sun_vector = 0.023 0.806 0.591 14:55:03.149 [planetinfo.record]: sun_distance = 1015920 14:55:03.149 [planetinfo.record]: corona_flare = 0.031259 14:55:03.149 [planetinfo.record]: corona_hues = 0.939583 14:55:03.149 [planetinfo.record]: sun_color = 0.715796, 0.611758, 0.578332, 1 14:55:03.149 [planetinfo.record]: corona_shimmer = 0.293132 14:55:03.150 [planetinfo.record]: station_vector = -0.969 0.134 0.206 14:55:03.150 [planetinfo.record]: station = coriolis 14:55:08.107 [PLANETINFO OVER]: Done 14:55:08.107 [PLANETINFO LOGGING]: 7 207 14:55:09.125 [planetinfo.record]: seed = 70 65 127 151 14:55:09.125 [planetinfo.record]: coordinates = 65 38 14:55:09.125 [planetinfo.record]: government = 0; 14:55:09.126 [planetinfo.record]: economy = 6; 14:55:09.126 [planetinfo.record]: techlevel = 2; 14:55:09.126 [planetinfo.record]: population = 15; 14:55:09.126 [planetinfo.record]: productivity = 1920; 14:55:09.126 [planetinfo.record]: name = "Tioned"; 14:55:09.126 [planetinfo.record]: inhabitant = "Black Lizard"; 14:55:09.126 [planetinfo.record]: inhabitants = "Black Lizards"; 14:55:09.126 [planetinfo.record]: description = "This world is fabled for its ancient Senole plant plantations."; 14:55:09.160 [planetinfo.record]: air_color = 0.75572, 0.622614, 0.863719, 1; 14:55:09.160 [planetinfo.record]: cloud_alpha = 1.000000; 14:55:09.160 [planetinfo.record]: cloud_color = 0.59375, 0.452271, 0.498693, 1; 14:55:09.160 [planetinfo.record]: cloud_fraction = 0.420000; 14:55:09.160 [planetinfo.record]: land_color = 0.546039, 0.482109, 0.66, 1; 14:55:09.160 [planetinfo.record]: land_fraction = 0.490000; 14:55:09.160 [planetinfo.record]: polar_cloud_color = 0.767187, 0.652933, 0.690423, 1; 14:55:09.160 [planetinfo.record]: polar_land_color = 0.893682, 0.871064, 0.934, 1; 14:55:09.160 [planetinfo.record]: polar_sea_color = 0.941602, 0.918711, 0.88928, 1; 14:55:09.160 [planetinfo.record]: sea_color = 0.583984, 0.527197, 0.454185, 1; 14:55:09.160 [planetinfo.record]: rotation_speed = 0.002696 14:55:09.160 [planetinfo.record]: planet zpos = 514030.000000 14:55:09.160 [planetinfo.record]: sun_radius = 108677.768250 14:55:09.160 [planetinfo.record]: sun_vector = 0.640 0.656 -0.399 14:55:09.160 [planetinfo.record]: sun_distance = 887870 14:55:09.160 [planetinfo.record]: corona_flare = 0.089720 14:55:09.160 [planetinfo.record]: corona_hues = 0.686104 14:55:09.160 [planetinfo.record]: sun_color = 0.726398, 0.567445, 0.220762, 1 14:55:09.160 [planetinfo.record]: corona_shimmer = 0.266190 14:55:09.161 [planetinfo.record]: station_vector = -0.689 0.677 0.258 14:55:09.161 [planetinfo.record]: station = coriolis 14:55:14.788 [PLANETINFO OVER]: Done 14:55:14.789 [PLANETINFO LOGGING]: 7 208 14:55:15.793 [planetinfo.record]: seed = 68 111 9 71 14:55:15.793 [planetinfo.record]: coordinates = 111 216 14:55:15.794 [planetinfo.record]: government = 0; 14:55:15.794 [planetinfo.record]: economy = 2; 14:55:15.794 [planetinfo.record]: techlevel = 8; 14:55:15.794 [planetinfo.record]: population = 35; 14:55:15.794 [planetinfo.record]: productivity = 8960; 14:55:15.794 [planetinfo.record]: name = "Sorezaqu"; 14:55:15.794 [planetinfo.record]: inhabitant = "Human Colonial"; 14:55:15.794 [planetinfo.record]: inhabitants = "Human Colonials"; 14:55:15.794 [planetinfo.record]: description = "This planet is fabled for its exciting sit coms."; 14:55:15.808 [planetinfo.record]: air_color = 0.588074, 0.514606, 0.911197, 1; 14:55:15.808 [planetinfo.record]: cloud_alpha = 1.000000; 14:55:15.808 [planetinfo.record]: cloud_color = 0.51263, 0.215721, 0.736328, 1; 14:55:15.808 [planetinfo.record]: cloud_fraction = 0.460000; 14:55:15.808 [planetinfo.record]: land_color = 0.314813, 0.121172, 0.66, 1; 14:55:15.808 [planetinfo.record]: land_fraction = 0.620000; 14:55:15.808 [planetinfo.record]: polar_cloud_color = 0.673494, 0.46398, 0.831348, 1; 14:55:15.808 [planetinfo.record]: polar_land_color = 0.811877, 0.743369, 0.934, 1; 14:55:15.808 [planetinfo.record]: polar_sea_color = 0.937168, 0.939844, 0.890924, 1; 14:55:15.808 [planetinfo.record]: sea_color = 0.594713, 0.601562, 0.476315, 1; 14:55:15.808 [planetinfo.record]: rotation_speed = 0.003760 14:55:15.808 [planetinfo.record]: planet zpos = 566280.000000 14:55:15.808 [planetinfo.record]: sun_radius = 85032.871857 14:55:15.809 [planetinfo.record]: sun_vector = 0.260 0.952 -0.163 14:55:15.809 [planetinfo.record]: sun_distance = 943800 14:55:15.809 [planetinfo.record]: corona_flare = 0.011142 14:55:15.809 [planetinfo.record]: corona_hues = 0.875938 14:55:15.809 [planetinfo.record]: sun_color = 0.434925, 0.492619, 0.701895, 1 14:55:15.809 [planetinfo.record]: corona_shimmer = 0.351780 14:55:15.810 [planetinfo.record]: station_vector = 0.475 0.296 0.829 14:55:15.810 [planetinfo.record]: station = coriolis 14:55:20.712 [PLANETINFO OVER]: Done 14:55:20.712 [PLANETINFO LOGGING]: 7 209 14:55:21.717 [planetinfo.record]: seed = 122 26 235 237 14:55:21.717 [planetinfo.record]: coordinates = 26 68 14:55:21.717 [planetinfo.record]: government = 7; 14:55:21.717 [planetinfo.record]: economy = 4; 14:55:21.717 [planetinfo.record]: techlevel = 9; 14:55:21.717 [planetinfo.record]: population = 48; 14:55:21.717 [planetinfo.record]: productivity = 25344; 14:55:21.717 [planetinfo.record]: name = "Didilaer"; 14:55:21.717 [planetinfo.record]: inhabitant = "Insect"; 14:55:21.717 [planetinfo.record]: inhabitants = "Insects"; 14:55:21.717 [planetinfo.record]: description = "Didilaer is very notable for its inhabitants’ ingrained silliness."; 14:55:21.756 [planetinfo.record]: air_color = 0.649014, 0.505433, 0.935912, 1; 14:55:21.756 [planetinfo.record]: cloud_alpha = 1.000000; 14:55:21.756 [planetinfo.record]: cloud_color = 0.810547, 0.177307, 0.800653, 1; 14:55:21.756 [planetinfo.record]: cloud_fraction = 0.370000; 14:55:21.756 [planetinfo.record]: land_color = 0.66, 0.183047, 0.585476, 1; 14:55:21.756 [planetinfo.record]: land_fraction = 0.320000; 14:55:21.756 [planetinfo.record]: polar_cloud_color = 0.864746, 0.442507, 0.858149, 1; 14:55:21.756 [planetinfo.record]: polar_land_color = 0.934, 0.76526, 0.907634, 1; 14:55:21.756 [planetinfo.record]: polar_sea_color = 0.93457, 0.874948, 0.867033, 1; 14:55:21.756 [planetinfo.record]: sea_color = 0.654297, 0.487328, 0.465164, 1; 14:55:21.756 [planetinfo.record]: rotation_speed = 0.002417 14:55:21.756 [planetinfo.record]: planet zpos = 740400.000000 14:55:21.756 [planetinfo.record]: sun_radius = 167126.448059 14:55:21.756 [planetinfo.record]: sun_vector = -0.596 0.450 -0.665 14:55:21.757 [planetinfo.record]: sun_distance = 1295700 14:55:21.757 [planetinfo.record]: corona_flare = 0.061342 14:55:21.757 [planetinfo.record]: corona_hues = 0.686951 14:55:21.757 [planetinfo.record]: sun_color = 0.324606, 0.389458, 0.81152, 1 14:55:21.757 [planetinfo.record]: corona_shimmer = 0.342822 14:55:21.757 [planetinfo.record]: station_vector = -0.828 -0.551 0.105 14:55:21.757 [planetinfo.record]: station = coriolis 14:55:26.170 [PLANETINFO OVER]: Done 14:55:26.171 [PLANETINFO LOGGING]: 7 210 14:55:27.177 [planetinfo.record]: seed = 216 144 197 51 14:55:27.177 [planetinfo.record]: coordinates = 144 85 14:55:27.177 [planetinfo.record]: government = 3; 14:55:27.177 [planetinfo.record]: economy = 5; 14:55:27.177 [planetinfo.record]: techlevel = 4; 14:55:27.177 [planetinfo.record]: population = 25; 14:55:27.177 [planetinfo.record]: productivity = 7000; 14:55:27.177 [planetinfo.record]: name = "Bequri"; 14:55:27.177 [planetinfo.record]: inhabitant = "Red Furry Rodent"; 14:55:27.177 [planetinfo.record]: inhabitants = "Red Furry Rodents"; 14:55:27.177 [planetinfo.record]: description = "This planet is very well known for its inhabitants’ unusual mating traditions and its hoopy casinos."; 14:55:27.181 [planetinfo.record]: air_color = 0.624617, 0.895588, 0.842428, 1; 14:55:27.182 [planetinfo.record]: cloud_alpha = 1.000000; 14:55:27.182 [planetinfo.record]: cloud_color = 0.689453, 0.566451, 0.476692, 1; 14:55:27.182 [planetinfo.record]: cloud_fraction = 0.420000; 14:55:27.182 [planetinfo.record]: land_color = 0.658203, 0.354813, 0.617909, 1; 14:55:27.182 [planetinfo.record]: land_fraction = 0.280000; 14:55:27.182 [planetinfo.record]: polar_cloud_color = 0.810254, 0.719908, 0.653979, 1; 14:55:27.182 [planetinfo.record]: polar_land_color = 0.93418, 0.82653, 0.919882, 1; 14:55:27.183 [planetinfo.record]: polar_sea_color = 0.911372, 0.926758, 0.868835, 1; 14:55:27.183 [planetinfo.record]: sea_color = 0.683784, 0.732422, 0.549316, 1; 14:55:27.183 [planetinfo.record]: rotation_speed = 0.001568 14:55:27.183 [planetinfo.record]: planet zpos = 410080.000000 14:55:27.183 [planetinfo.record]: sun_radius = 68682.666016 14:55:27.183 [planetinfo.record]: sun_vector = 0.308 0.728 0.613 14:55:27.183 [planetinfo.record]: sun_distance = 671040 14:55:27.183 [planetinfo.record]: corona_flare = 0.031364 14:55:27.183 [planetinfo.record]: corona_hues = 0.697525 14:55:27.183 [planetinfo.record]: sun_color = 0.762305, 0.677957, 0.344352, 1 14:55:27.184 [planetinfo.record]: corona_shimmer = 0.497714 14:55:27.184 [planetinfo.record]: station_vector = -0.558 0.209 0.803 14:55:27.184 [planetinfo.record]: station = coriolis 14:55:32.229 [PLANETINFO OVER]: Done 14:55:32.230 [PLANETINFO LOGGING]: 7 211 14:55:33.233 [planetinfo.record]: seed = 238 44 23 38 14:55:33.233 [planetinfo.record]: coordinates = 44 222 14:55:33.233 [planetinfo.record]: government = 5; 14:55:33.233 [planetinfo.record]: economy = 6; 14:55:33.233 [planetinfo.record]: techlevel = 4; 14:55:33.233 [planetinfo.record]: population = 28; 14:55:33.233 [planetinfo.record]: productivity = 8064; 14:55:33.233 [planetinfo.record]: name = "Biatzate"; 14:55:33.233 [planetinfo.record]: inhabitant = "Human Colonial"; 14:55:33.233 [planetinfo.record]: inhabitants = "Human Colonials"; 14:55:33.233 [planetinfo.record]: description = "The planet Biatzate is scourged by killer edible poets."; 14:55:33.235 [planetinfo.record]: air_color = 0.619766, 0.902092, 0.848908, 1; 14:55:33.236 [planetinfo.record]: cloud_alpha = 1.000000; 14:55:33.236 [planetinfo.record]: cloud_color = 0.708984, 0.575336, 0.46804, 1; 14:55:33.236 [planetinfo.record]: cloud_fraction = 0.240000; 14:55:33.236 [planetinfo.record]: land_color = 0.66, 0.65577, 0.551719, 1; 14:55:33.236 [planetinfo.record]: land_fraction = 0.580000; 14:55:33.236 [planetinfo.record]: polar_cloud_color = 0.819043, 0.722546, 0.645076, 1; 14:55:33.236 [planetinfo.record]: polar_land_color = 0.934, 0.932504, 0.895691, 1; 14:55:33.236 [planetinfo.record]: polar_sea_color = 0.742755, 0.934375, 0.925393, 1; 14:55:33.236 [planetinfo.record]: sea_color = 0.11792, 0.65625, 0.631016, 1; 14:55:33.236 [planetinfo.record]: rotation_speed = 0.002016 14:55:33.237 [planetinfo.record]: planet zpos = 483560.000000 14:55:33.237 [planetinfo.record]: sun_radius = 88644.438477 14:55:33.237 [planetinfo.record]: sun_vector = 0.200 -0.941 0.274 14:55:33.237 [planetinfo.record]: sun_distance = 791280 14:55:33.237 [planetinfo.record]: corona_flare = 0.028580 14:55:33.237 [planetinfo.record]: corona_hues = 0.507187 14:55:33.237 [planetinfo.record]: sun_color = 0.722845, 0.664874, 0.49812, 1 14:55:33.237 [planetinfo.record]: corona_shimmer = 0.572757 14:55:33.237 [planetinfo.record]: station_vector = -0.963 -0.018 0.270 14:55:33.237 [planetinfo.record]: station = coriolis 14:55:37.354 [PLANETINFO OVER]: Done 14:55:37.356 [PLANETINFO LOGGING]: 7 212 14:55:38.359 [planetinfo.record]: seed = 236 220 193 147 14:55:38.359 [planetinfo.record]: coordinates = 220 132 14:55:38.359 [planetinfo.record]: government = 5; 14:55:38.359 [planetinfo.record]: economy = 4; 14:55:38.359 [planetinfo.record]: techlevel = 6; 14:55:38.359 [planetinfo.record]: population = 34; 14:55:38.359 [planetinfo.record]: productivity = 14688; 14:55:38.359 [planetinfo.record]: name = "Belabia"; 14:55:38.359 [planetinfo.record]: inhabitant = "Black Slimy Lobster"; 14:55:38.359 [planetinfo.record]: inhabitants = "Black Slimy Lobsters"; 14:55:38.359 [planetinfo.record]: description = "The world Belabia is reasonably fabled for Belabiaian evil juice and its inhabitants’ eccentric love for poetry."; 14:55:38.396 [planetinfo.record]: air_color = 0.658978, 0.845508, 0.787077, 1; 14:55:38.396 [planetinfo.record]: cloud_alpha = 1.000000; 14:55:38.396 [planetinfo.record]: cloud_color = 0.539062, 0.50774, 0.505371, 1; 14:55:38.396 [planetinfo.record]: cloud_fraction = 0.370000; 14:55:38.396 [planetinfo.record]: land_color = 0.461484, 0.66, 0.511113, 1; 14:55:38.396 [planetinfo.record]: land_fraction = 0.400000; 14:55:38.396 [planetinfo.record]: polar_cloud_color = 0.742578, 0.715611, 0.713571, 1; 14:55:38.396 [planetinfo.record]: polar_land_color = 0.863768, 0.934, 0.881326, 1; 14:55:38.396 [planetinfo.record]: polar_sea_color = 0.928906, 0.904272, 0.857243, 1; 14:55:38.396 [planetinfo.record]: sea_color = 0.710938, 0.635522, 0.491547, 1; 14:55:38.396 [planetinfo.record]: rotation_speed = 0.001936 14:55:38.396 [planetinfo.record]: planet zpos = 418440.000000 14:55:38.396 [planetinfo.record]: sun_radius = 86403.202515 14:55:38.396 [planetinfo.record]: sun_vector = -0.212 -0.391 0.895 14:55:38.396 [planetinfo.record]: sun_distance = 874920 14:55:38.397 [planetinfo.record]: corona_flare = 0.026491 14:55:38.397 [planetinfo.record]: corona_hues = 0.767738 14:55:38.397 [planetinfo.record]: sun_color = 0.291871, 0.450783, 0.786038, 1 14:55:38.397 [planetinfo.record]: corona_shimmer = 0.296825 14:55:38.397 [planetinfo.record]: station_vector = -0.672 -0.598 0.438 14:55:38.397 [planetinfo.record]: station = coriolis 14:55:43.595 [PLANETINFO OVER]: Done 14:55:43.595 [PLANETINFO LOGGING]: 7 213 14:55:44.599 [planetinfo.record]: seed = 162 239 131 75 14:55:44.599 [planetinfo.record]: coordinates = 239 102 14:55:44.599 [planetinfo.record]: government = 4; 14:55:44.599 [planetinfo.record]: economy = 6; 14:55:44.599 [planetinfo.record]: techlevel = 6; 14:55:44.599 [planetinfo.record]: population = 35; 14:55:44.599 [planetinfo.record]: productivity = 8960; 14:55:44.599 [planetinfo.record]: name = "Maletees"; 14:55:44.599 [planetinfo.record]: inhabitant = "Small Yellow Bug-Eyed Bird"; 14:55:44.600 [planetinfo.record]: inhabitants = "Small Yellow Bug-Eyed Birds"; 14:55:44.600 [planetinfo.record]: description = "The planet Maletees is mildly well known for its hoopy casinos."; 14:55:44.616 [planetinfo.record]: air_color = 0.457881, 0.769654, 0.833801, 1; 14:55:44.616 [planetinfo.record]: cloud_alpha = 1.000000; 14:55:44.616 [planetinfo.record]: cloud_color = 0.305053, 0.503906, 0.124008, 1; 14:55:44.616 [planetinfo.record]: cloud_fraction = 0.450000; 14:55:44.616 [planetinfo.record]: land_color = 0.317421, 0.339313, 0.517578, 1; 14:55:44.616 [planetinfo.record]: land_fraction = 0.340000; 14:55:44.616 [planetinfo.record]: polar_cloud_color = 0.547511, 0.726758, 0.384316, 1; 14:55:44.616 [planetinfo.record]: polar_land_color = 0.856566, 0.866593, 0.948242, 1; 14:55:44.616 [planetinfo.record]: polar_sea_color = 0.869291, 0.921484, 0.872553, 1; 14:55:44.616 [planetinfo.record]: sea_color = 0.607269, 0.785156, 0.618387, 1; 14:55:44.616 [planetinfo.record]: rotation_speed = 0.001750 14:55:44.616 [planetinfo.record]: planet zpos = 763230.000000 14:55:44.616 [planetinfo.record]: sun_radius = 163738.692627 14:55:44.616 [planetinfo.record]: sun_vector = -0.811 -0.505 0.296 14:55:44.616 [planetinfo.record]: sun_distance = 1232910 14:55:44.616 [planetinfo.record]: corona_flare = 0.056113 14:55:44.616 [planetinfo.record]: corona_hues = 0.566490 14:55:44.616 [planetinfo.record]: sun_color = 0.35424, 0.697596, 0.753479, 1 14:55:44.616 [planetinfo.record]: corona_shimmer = 0.635359 14:55:44.616 [planetinfo.record]: station_vector = -0.396 0.882 0.257 14:55:44.616 [planetinfo.record]: station = coriolis 14:55:49.715 [PLANETINFO OVER]: Done 14:55:49.716 [PLANETINFO LOGGING]: 7 214 14:55:50.720 [planetinfo.record]: seed = 128 201 125 71 14:55:50.720 [planetinfo.record]: coordinates = 201 220 14:55:50.720 [planetinfo.record]: government = 0; 14:55:50.720 [planetinfo.record]: economy = 6; 14:55:50.720 [planetinfo.record]: techlevel = 2; 14:55:50.720 [planetinfo.record]: population = 15; 14:55:50.720 [planetinfo.record]: productivity = 1920; 14:55:50.720 [planetinfo.record]: name = "Sodiri"; 14:55:50.720 [planetinfo.record]: inhabitant = "Human Colonial"; 14:55:50.720 [planetinfo.record]: inhabitants = "Human Colonials"; 14:55:50.720 [planetinfo.record]: description = "Sodiri is a revolting little planet."; 14:55:50.748 [planetinfo.record]: air_color = 0.603718, 0.474844, 0.950221, 1; 14:55:50.748 [planetinfo.record]: cloud_alpha = 1.000000; 14:55:50.748 [planetinfo.record]: cloud_color = 0.721144, 0.0833511, 0.853516, 1; 14:55:50.749 [planetinfo.record]: cloud_fraction = 0.430000; 14:55:50.749 [planetinfo.record]: land_color = 0.625, 0.492249, 0.478516, 1; 14:55:50.749 [planetinfo.record]: land_fraction = 0.400000; 14:55:50.749 [planetinfo.record]: polar_cloud_color = 0.798387, 0.385491, 0.884082, 1; 14:55:50.749 [planetinfo.record]: polar_land_color = 0.9375, 0.887718, 0.882568, 1; 14:55:50.749 [planetinfo.record]: polar_sea_color = 0.765421, 0.938672, 0.92243, 1; 14:55:50.749 [planetinfo.record]: sea_color = 0.160507, 0.613281, 0.570834, 1; 14:55:50.749 [planetinfo.record]: rotation_speed = 0.004711 14:55:50.749 [planetinfo.record]: planet zpos = 577080.000000 14:55:50.749 [planetinfo.record]: sun_radius = 114742.082520 14:55:50.749 [planetinfo.record]: sun_vector = -0.746 -0.638 0.193 14:55:50.749 [planetinfo.record]: sun_distance = 865620 14:55:50.749 [planetinfo.record]: corona_flare = 0.096172 14:55:50.750 [planetinfo.record]: corona_hues = 0.881561 14:55:50.750 [planetinfo.record]: sun_color = 0.677469, 0.490252, 0.250735, 1 14:55:50.750 [planetinfo.record]: corona_shimmer = 0.245869 14:55:50.750 [planetinfo.record]: station_vector = 0.474 0.160 0.866 14:55:50.750 [planetinfo.record]: station = coriolis 14:55:56.167 [PLANETINFO OVER]: Done 14:55:56.167 [PLANETINFO LOGGING]: 7 215 14:55:57.172 [planetinfo.record]: seed = 150 51 175 31 14:55:57.172 [planetinfo.record]: coordinates = 51 254 14:55:57.172 [planetinfo.record]: government = 2; 14:55:57.172 [planetinfo.record]: economy = 6; 14:55:57.172 [planetinfo.record]: techlevel = 5; 14:55:57.172 [planetinfo.record]: population = 29; 14:55:57.172 [planetinfo.record]: productivity = 5568; 14:55:57.172 [planetinfo.record]: name = "Onatce"; 14:55:57.172 [planetinfo.record]: inhabitant = "Green Furry Rodent"; 14:55:57.172 [planetinfo.record]: inhabitants = "Green Furry Rodents"; 14:55:57.172 [planetinfo.record]: description = "The planet Onatce is scourged by deadly edible arts graduates."; 14:55:57.178 [planetinfo.record]: air_color = 0.768213, 0.633381, 0.858516, 1; 14:55:57.178 [planetinfo.record]: cloud_alpha = 1.000000; 14:55:57.178 [planetinfo.record]: cloud_color = 0.578125, 0.469727, 0.490051, 1; 14:55:57.178 [planetinfo.record]: cloud_fraction = 0.560000; 14:55:57.178 [planetinfo.record]: land_color = 0.328096, 0.321228, 0.541016, 1; 14:55:57.178 [planetinfo.record]: land_fraction = 0.580000; 14:55:57.178 [planetinfo.record]: polar_cloud_color = 0.760156, 0.671075, 0.687778, 1; 14:55:57.178 [planetinfo.record]: polar_land_color = 0.852833, 0.849831, 0.945898, 1; 14:55:57.178 [planetinfo.record]: polar_sea_color = 0.926758, 0.863518, 0.832634, 1; 14:55:57.178 [planetinfo.record]: sea_color = 0.732422, 0.532508, 0.434875, 1; 14:55:57.179 [planetinfo.record]: rotation_speed = 0.001389 14:55:57.179 [planetinfo.record]: planet zpos = 804840.000000 14:55:57.179 [planetinfo.record]: sun_radius = 175030.225983 14:55:57.179 [planetinfo.record]: sun_vector = 0.218 -0.016 -0.976 14:55:57.179 [planetinfo.record]: sun_distance = 1408470 14:55:57.179 [planetinfo.record]: corona_flare = 0.048813 14:55:57.179 [planetinfo.record]: corona_hues = 0.765480 14:55:57.179 [planetinfo.record]: sun_color = 0.295821, 0.31842, 0.69798, 1 14:55:57.179 [planetinfo.record]: corona_shimmer = 0.401165 14:55:57.179 [planetinfo.record]: station_vector = 0.839 0.064 0.541 14:55:57.179 [planetinfo.record]: station = coriolis 14:56:01.498 [PLANETINFO OVER]: Done 14:56:01.499 [PLANETINFO LOGGING]: 7 216 14:56:02.503 [planetinfo.record]: seed = 148 22 121 13 14:56:02.503 [planetinfo.record]: coordinates = 22 165 14:56:02.503 [planetinfo.record]: government = 2; 14:56:02.503 [planetinfo.record]: economy = 5; 14:56:02.503 [planetinfo.record]: techlevel = 5; 14:56:02.503 [planetinfo.record]: population = 28; 14:56:02.503 [planetinfo.record]: productivity = 6720; 14:56:02.503 [planetinfo.record]: name = "Diesdi"; 14:56:02.503 [planetinfo.record]: inhabitant = "Human Colonial"; 14:56:02.503 [planetinfo.record]: inhabitants = "Human Colonials"; 14:56:02.503 [planetinfo.record]: description = "Diesdi is mildly well known for its hoopy casinos and its great parking meters."; 14:56:02.520 [planetinfo.record]: air_color = 0.559586, 0.948053, 0.984041, 1; 14:56:02.520 [planetinfo.record]: cloud_alpha = 1.000000; 14:56:02.520 [planetinfo.record]: cloud_color = 0.778682, 0.955078, 0.291, 1; 14:56:02.520 [planetinfo.record]: cloud_fraction = 0.360000; 14:56:02.520 [planetinfo.record]: land_color = 0.66, 0.631802, 0.208828, 1; 14:56:02.520 [planetinfo.record]: land_fraction = 0.590000; 14:56:02.520 [planetinfo.record]: polar_cloud_color = 0.822457, 0.929785, 0.525728, 1; 14:56:02.520 [planetinfo.record]: polar_land_color = 0.934, 0.924024, 0.774381, 1; 14:56:02.520 [planetinfo.record]: polar_sea_color = 0.944141, 0.883103, 0.911715, 1; 14:56:02.520 [planetinfo.record]: sea_color = 0.558594, 0.414145, 0.481855, 1; 14:56:02.520 [planetinfo.record]: rotation_speed = 0.004970 14:56:02.520 [planetinfo.record]: planet zpos = 678260.000000 14:56:02.520 [planetinfo.record]: sun_radius = 128705.465088 14:56:02.520 [planetinfo.record]: sun_vector = -0.080 0.977 0.197 14:56:02.520 [planetinfo.record]: sun_distance = 1233200 14:56:02.520 [planetinfo.record]: corona_flare = 0.060913 14:56:02.520 [planetinfo.record]: corona_hues = 0.574295 14:56:02.520 [planetinfo.record]: sun_color = 0.664444, 0.702823, 0.805759, 1 14:56:02.520 [planetinfo.record]: corona_shimmer = 0.409533 14:56:02.521 [planetinfo.record]: station_vector = -0.260 0.897 0.358 14:56:02.521 [planetinfo.record]: station = coriolis 14:56:07.741 [PLANETINFO OVER]: Done 14:56:07.742 [PLANETINFO LOGGING]: 7 217 14:56:08.746 [planetinfo.record]: seed = 202 195 27 122 14:56:08.746 [planetinfo.record]: coordinates = 195 237 14:56:08.746 [planetinfo.record]: government = 1; 14:56:08.746 [planetinfo.record]: economy = 7; 14:56:08.746 [planetinfo.record]: techlevel = 4; 14:56:08.746 [planetinfo.record]: population = 25; 14:56:08.746 [planetinfo.record]: productivity = 3000; 14:56:08.747 [planetinfo.record]: name = "Qumaus"; 14:56:08.747 [planetinfo.record]: inhabitant = "Human Colonial"; 14:56:08.747 [planetinfo.record]: inhabitants = "Human Colonials"; 14:56:08.747 [planetinfo.record]: description = "The planet Qumaus is mildly notable for its inhabitants’ ingrained silliness."; 14:56:08.800 [planetinfo.record]: air_color = 0.57822, 0.531419, 0.889734, 1; 14:56:08.800 [planetinfo.record]: cloud_alpha = 1.000000; 14:56:08.800 [planetinfo.record]: cloud_color = 0.42878, 0.262451, 0.671875, 1; 14:56:08.800 [planetinfo.record]: cloud_fraction = 0.260000; 14:56:08.800 [planetinfo.record]: land_color = 0.386719, 0.66, 0.403799, 1; 14:56:08.800 [planetinfo.record]: land_fraction = 0.370000; 14:56:08.800 [planetinfo.record]: polar_cloud_color = 0.620906, 0.496764, 0.802344, 1; 14:56:08.800 [planetinfo.record]: polar_land_color = 0.837316, 0.934, 0.843359, 1; 14:56:08.800 [planetinfo.record]: polar_sea_color = 0.939453, 0.897581, 0.881196, 1; 14:56:08.800 [planetinfo.record]: sea_color = 0.605469, 0.497524, 0.455284, 1; 14:56:08.800 [planetinfo.record]: rotation_speed = 0.001140 14:56:08.800 [planetinfo.record]: planet zpos = 668520.000000 14:56:08.801 [planetinfo.record]: sun_radius = 160228.304901 14:56:08.801 [planetinfo.record]: sun_vector = -0.392 0.916 -0.091 14:56:08.801 [planetinfo.record]: sun_distance = 947070 14:56:08.801 [planetinfo.record]: corona_flare = 0.094064 14:56:08.801 [planetinfo.record]: corona_hues = 0.594933 14:56:08.801 [planetinfo.record]: sun_color = 0.229726, 0.442481, 0.830548, 1 14:56:08.801 [planetinfo.record]: corona_shimmer = 0.494880 14:56:08.801 [planetinfo.record]: station_vector = 0.331 0.002 0.943 14:56:08.801 [planetinfo.record]: station = coriolis 14:56:13.701 [PLANETINFO OVER]: Done 14:56:13.702 [PLANETINFO LOGGING]: 7 218 14:56:14.705 [planetinfo.record]: seed = 40 14 53 162 14:56:14.705 [planetinfo.record]: coordinates = 14 104 14:56:14.705 [planetinfo.record]: government = 5; 14:56:14.705 [planetinfo.record]: economy = 0; 14:56:14.705 [planetinfo.record]: techlevel = 12; 14:56:14.705 [planetinfo.record]: population = 54; 14:56:14.706 [planetinfo.record]: productivity = 38880; 14:56:14.706 [planetinfo.record]: name = "Xeoresce"; 14:56:14.706 [planetinfo.record]: inhabitant = "Human Colonial"; 14:56:14.706 [planetinfo.record]: inhabitants = "Human Colonials"; 14:56:14.706 [planetinfo.record]: description = "This planet is most fabled for mud hockey but ravaged by occasional earthquakes."; 14:56:14.710 [planetinfo.record]: air_color = 0.648658, 0.868863, 0.930059, 1; 14:56:14.710 [planetinfo.record]: cloud_alpha = 1.000000; 14:56:14.710 [planetinfo.record]: cloud_color = 0.630469, 0.792969, 0.548264, 1; 14:56:14.710 [planetinfo.record]: cloud_fraction = 0.080000; 14:56:14.710 [planetinfo.record]: land_color = 0.259484, 0.131484, 0.66, 1; 14:56:14.711 [planetinfo.record]: land_fraction = 0.560000; 14:56:14.711 [planetinfo.record]: polar_cloud_color = 0.747094, 0.856836, 0.691577, 1; 14:56:14.711 [planetinfo.record]: polar_land_color = 0.792302, 0.747018, 0.934, 1; 14:56:14.711 [planetinfo.record]: polar_sea_color = 0.937695, 0.918274, 0.881195, 1; 14:56:14.711 [planetinfo.record]: sea_color = 0.623047, 0.571428, 0.472883, 1; 14:56:14.711 [planetinfo.record]: rotation_speed = 0.002849 14:56:14.711 [planetinfo.record]: planet zpos = 401040.000000 14:56:14.711 [planetinfo.record]: sun_radius = 67823.181152 14:56:14.711 [planetinfo.record]: sun_vector = 0.209 0.816 0.539 14:56:14.711 [planetinfo.record]: sun_distance = 701820 14:56:14.711 [planetinfo.record]: corona_flare = 0.058484 14:56:14.711 [planetinfo.record]: corona_hues = 0.597687 14:56:14.711 [planetinfo.record]: sun_color = 0.22482, 0.276017, 0.773358, 1 14:56:14.711 [planetinfo.record]: corona_shimmer = 0.278827 14:56:14.712 [planetinfo.record]: station_vector = -0.379 -0.226 0.897 14:56:14.712 [planetinfo.record]: station = dodecahedron 14:56:19.333 [PLANETINFO OVER]: Done 14:56:19.334 [PLANETINFO LOGGING]: 7 219 14:56:20.337 [planetinfo.record]: seed = 62 133 71 104 14:56:20.337 [planetinfo.record]: coordinates = 133 201 14:56:20.337 [planetinfo.record]: government = 7; 14:56:20.337 [planetinfo.record]: economy = 1; 14:56:20.337 [planetinfo.record]: techlevel = 11; 14:56:20.337 [planetinfo.record]: population = 53; 14:56:20.337 [planetinfo.record]: productivity = 41976; 14:56:20.337 [planetinfo.record]: name = "Ustiza"; 14:56:20.338 [planetinfo.record]: inhabitant = "Human Colonial"; 14:56:20.338 [planetinfo.record]: inhabitants = "Human Colonials"; 14:56:20.338 [planetinfo.record]: description = "This world is plagued by occasional solar activity."; 14:56:20.342 [planetinfo.record]: air_color = 0.687449, 0.803096, 0.941766, 1; 14:56:20.342 [planetinfo.record]: cloud_alpha = 1.000000; 14:56:20.342 [planetinfo.record]: cloud_color = 0.653442, 0.828125, 0.766713, 1; 14:56:20.342 [planetinfo.record]: cloud_fraction = 0.090000; 14:56:20.342 [planetinfo.record]: land_color = 0.333404, 0.66, 0.296484, 1; 14:56:20.342 [planetinfo.record]: land_fraction = 0.690000; 14:56:20.342 [planetinfo.record]: polar_cloud_color = 0.757609, 0.872656, 0.83221, 1; 14:56:20.342 [planetinfo.record]: polar_land_color = 0.818454, 0.934, 0.805393, 1; 14:56:20.342 [planetinfo.record]: polar_sea_color = 0.936328, 0.898867, 0.884208, 1; 14:56:20.342 [planetinfo.record]: sea_color = 0.636719, 0.534822, 0.494949, 1; 14:56:20.342 [planetinfo.record]: rotation_speed = 0.000810 14:56:20.342 [planetinfo.record]: planet zpos = 599640.000000 14:56:20.342 [planetinfo.record]: sun_radius = 104885.456238 14:56:20.342 [planetinfo.record]: sun_vector = 0.256 -0.394 -0.883 14:56:20.342 [planetinfo.record]: sun_distance = 799520 14:56:20.342 [planetinfo.record]: corona_flare = 0.070493 14:56:20.342 [planetinfo.record]: corona_hues = 0.543961 14:56:20.342 [planetinfo.record]: sun_color = 0.809189, 0.781995, 0.671421, 1 14:56:20.342 [planetinfo.record]: corona_shimmer = 0.293517 14:56:20.342 [planetinfo.record]: station_vector = -0.950 -0.096 0.298 14:56:20.342 [planetinfo.record]: station = icosahedron 14:56:25.403 [PLANETINFO OVER]: Done 14:56:25.404 [PLANETINFO LOGGING]: 7 220 14:56:26.409 [planetinfo.record]: seed = 60 196 49 32 14:56:26.409 [planetinfo.record]: coordinates = 196 164 14:56:26.409 [planetinfo.record]: government = 7; 14:56:26.409 [planetinfo.record]: economy = 4; 14:56:26.409 [planetinfo.record]: techlevel = 7; 14:56:26.409 [planetinfo.record]: population = 40; 14:56:26.409 [planetinfo.record]: productivity = 21120; 14:56:26.409 [planetinfo.record]: name = "Esdi"; 14:56:26.409 [planetinfo.record]: inhabitant = "Human Colonial"; 14:56:26.409 [planetinfo.record]: inhabitants = "Human Colonials"; 14:56:26.409 [planetinfo.record]: description = "This world is most notable for Esdiian lethal water but beset by lethal disease."; 14:56:26.412 [planetinfo.record]: air_color = 0.646518, 0.743173, 0.997699, 1; 14:56:26.412 [planetinfo.record]: cloud_alpha = 1.000000; 14:56:26.412 [planetinfo.record]: cloud_color = 0.540848, 0.910735, 0.996094, 1; 14:56:26.412 [planetinfo.record]: cloud_fraction = 0.260000; 14:56:26.412 [planetinfo.record]: land_color = 0.58882, 0.66, 0.366094, 1; 14:56:26.412 [planetinfo.record]: land_fraction = 0.600000; 14:56:26.412 [planetinfo.record]: polar_cloud_color = 0.677382, 0.897456, 0.948242, 1; 14:56:26.412 [planetinfo.record]: polar_land_color = 0.908817, 0.934, 0.83002, 1; 14:56:26.412 [planetinfo.record]: polar_sea_color = 0.833783, 0.86957, 0.945508, 1; 14:56:26.412 [planetinfo.record]: sea_color = 0.287361, 0.369861, 0.544922, 1; 14:56:26.412 [planetinfo.record]: rotation_speed = 0.003130 14:56:26.412 [planetinfo.record]: planet zpos = 421680.000000 14:56:26.412 [planetinfo.record]: sun_radius = 77061.626587 14:56:26.412 [planetinfo.record]: sun_vector = 0.992 0.126 -0.015 14:56:26.413 [planetinfo.record]: sun_distance = 632520 14:56:26.413 [planetinfo.record]: corona_flare = 0.069315 14:56:26.413 [planetinfo.record]: corona_hues = 0.591278 14:56:26.413 [planetinfo.record]: sun_color = 0.757291, 0.21544, 0.207436, 1 14:56:26.413 [planetinfo.record]: corona_shimmer = 0.272277 14:56:26.413 [planetinfo.record]: station_vector = -0.922 0.376 0.090 14:56:26.413 [planetinfo.record]: station = coriolis 14:56:31.559 [PLANETINFO OVER]: Done 14:56:31.560 [PLANETINFO LOGGING]: 7 221 14:56:32.564 [planetinfo.record]: seed = 242 22 179 13 14:56:32.565 [planetinfo.record]: coordinates = 22 109 14:56:32.565 [planetinfo.record]: government = 6; 14:56:32.565 [planetinfo.record]: economy = 5; 14:56:32.565 [planetinfo.record]: techlevel = 7; 14:56:32.565 [planetinfo.record]: population = 40; 14:56:32.565 [planetinfo.record]: productivity = 16000; 14:56:32.565 [planetinfo.record]: name = "Dienve"; 14:56:32.565 [planetinfo.record]: inhabitant = "Green Bony Bird"; 14:56:32.565 [planetinfo.record]: inhabitants = "Green Bony Birds"; 14:56:32.565 [planetinfo.record]: description = "Dienve is mildly famous for its hoopy casinos and its vast oceans."; 14:56:32.566 [planetinfo.record]: air_color = 0.636843, 0.804743, 0.976887, 1; 14:56:32.566 [planetinfo.record]: cloud_alpha = 1.000000; 14:56:32.566 [planetinfo.record]: cloud_color = 0.517853, 0.933594, 0.738715, 1; 14:56:32.566 [planetinfo.record]: cloud_fraction = 0.480000; 14:56:32.566 [planetinfo.record]: land_color = 0.638672, 0.40416, 0.536073, 1; 14:56:32.566 [planetinfo.record]: land_fraction = 0.200000; 14:56:32.566 [planetinfo.record]: polar_cloud_color = 0.66403, 0.920117, 0.800076, 1; 14:56:32.566 [planetinfo.record]: polar_land_color = 0.936133, 0.850199, 0.898537, 1; 14:56:32.566 [planetinfo.record]: polar_sea_color = 0.937089, 0.9375, 0.884857, 1; 14:56:32.566 [planetinfo.record]: sea_color = 0.623903, 0.625, 0.484619, 1; 14:56:32.566 [planetinfo.record]: rotation_speed = 0.000438 14:56:32.566 [planetinfo.record]: planet zpos = 801580.000000 14:56:32.567 [planetinfo.record]: sun_radius = 194690.584412 14:56:32.567 [planetinfo.record]: sun_vector = 0.693 -0.290 0.660 14:56:32.567 [planetinfo.record]: sun_distance = 1418180 14:56:32.567 [planetinfo.record]: corona_flare = 0.093279 14:56:32.567 [planetinfo.record]: corona_hues = 0.787773 14:56:32.567 [planetinfo.record]: sun_color = 0.498015, 0.519393, 0.697858, 1 14:56:32.567 [planetinfo.record]: corona_shimmer = 0.567805 14:56:32.567 [planetinfo.record]: station_vector = -0.334 -0.938 0.094 14:56:32.567 [planetinfo.record]: station = coriolis 14:56:37.536 [PLANETINFO OVER]: Done 14:56:37.537 [PLANETINFO LOGGING]: 7 222 14:56:38.540 [planetinfo.record]: seed = 208 86 237 159 14:56:38.541 [planetinfo.record]: coordinates = 86 182 14:56:38.541 [planetinfo.record]: government = 2; 14:56:38.541 [planetinfo.record]: economy = 6; 14:56:38.541 [planetinfo.record]: techlevel = 4; 14:56:38.541 [planetinfo.record]: population = 25; 14:56:38.541 [planetinfo.record]: productivity = 4800; 14:56:38.541 [planetinfo.record]: name = "Ondizaat"; 14:56:38.541 [planetinfo.record]: inhabitant = "Black Slimy Lobster"; 14:56:38.541 [planetinfo.record]: inhabitants = "Black Slimy Lobsters"; 14:56:38.541 [planetinfo.record]: description = "This world is mildly well known for its exotic cuisine and its inhabitants’ ingrained silliness."; 14:56:38.544 [planetinfo.record]: air_color = 0.574196, 0.932735, 0.976236, 1; 14:56:38.545 [planetinfo.record]: cloud_alpha = 1.000000; 14:56:38.545 [planetinfo.record]: cloud_color = 0.732365, 0.931641, 0.338448, 1; 14:56:38.545 [planetinfo.record]: cloud_fraction = 0.330000; 14:56:38.545 [planetinfo.record]: land_color = 0.265808, 0.438903, 0.523438, 1; 14:56:38.545 [planetinfo.record]: land_fraction = 0.300000; 14:56:38.545 [planetinfo.record]: polar_cloud_color = 0.796349, 0.919238, 0.553428, 1; 14:56:38.545 [planetinfo.record]: polar_land_color = 0.83105, 0.909395, 0.947656, 1; 14:56:38.545 [planetinfo.record]: polar_sea_color = 0.875313, 0.924805, 0.910885, 1; 14:56:38.545 [planetinfo.record]: sea_color = 0.590988, 0.751953, 0.706682, 1; 14:56:38.545 [planetinfo.record]: rotation_speed = 0.000984 14:56:38.545 [planetinfo.record]: planet zpos = 809040.000000 14:56:38.545 [planetinfo.record]: sun_radius = 173933.435974 14:56:38.545 [planetinfo.record]: sun_vector = 0.509 -0.426 -0.748 14:56:38.545 [planetinfo.record]: sun_distance = 1550660 14:56:38.546 [planetinfo.record]: corona_flare = 0.048672 14:56:38.546 [planetinfo.record]: corona_hues = 0.887466 14:56:38.546 [planetinfo.record]: sun_color = 0.656934, 0.648791, 0.433461, 1 14:56:38.546 [planetinfo.record]: corona_shimmer = 0.483168 14:56:38.546 [planetinfo.record]: station_vector = -0.357 -0.886 0.294 14:56:38.546 [planetinfo.record]: station = coriolis 14:56:43.678 [PLANETINFO OVER]: Done 14:56:43.678 [PLANETINFO LOGGING]: 7 223 14:56:44.700 [planetinfo.record]: seed = 230 241 223 67 14:56:44.700 [planetinfo.record]: coordinates = 241 164 14:56:44.700 [planetinfo.record]: government = 4; 14:56:44.700 [planetinfo.record]: economy = 4; 14:56:44.700 [planetinfo.record]: techlevel = 6; 14:56:44.700 [planetinfo.record]: population = 33; 14:56:44.700 [planetinfo.record]: productivity = 12672; 14:56:44.700 [planetinfo.record]: name = "Gequadi"; 14:56:44.700 [planetinfo.record]: inhabitant = "Large Yellow Furry Rodent"; 14:56:44.700 [planetinfo.record]: inhabitants = "Large Yellow Furry Rodents"; 14:56:44.700 [planetinfo.record]: description = "The planet Gequadi is mildly well known for its exotic night life."; 14:56:44.703 [planetinfo.record]: air_color = 0.746497, 0.736157, 0.967131, 1; 14:56:44.704 [planetinfo.record]: cloud_alpha = 1.000000; 14:56:44.704 [planetinfo.record]: cloud_color = 0.814055, 0.798325, 0.904297, 1; 14:56:44.704 [planetinfo.record]: cloud_fraction = 0.280000; 14:56:44.704 [planetinfo.record]: land_color = 0.596191, 0.66, 0.567188, 1; 14:56:44.704 [planetinfo.record]: land_fraction = 0.290000; 14:56:44.704 [planetinfo.record]: polar_cloud_color = 0.850368, 0.840508, 0.906934, 1; 14:56:44.704 [planetinfo.record]: polar_land_color = 0.911425, 0.934, 0.901164, 1; 14:56:44.704 [planetinfo.record]: polar_sea_color = 0.87541, 0.902047, 0.921484, 1; 14:56:44.704 [planetinfo.record]: sea_color = 0.628125, 0.718909, 0.785156, 1; 14:56:44.704 [planetinfo.record]: rotation_speed = 0.000184 14:56:44.704 [planetinfo.record]: planet zpos = 344250.000000 14:56:44.704 [planetinfo.record]: sun_radius = 101465.572357 14:56:44.705 [planetinfo.record]: sun_vector = -0.608 0.591 -0.530 14:56:44.705 [planetinfo.record]: sun_distance = 765000 14:56:44.705 [planetinfo.record]: corona_flare = 0.039166 14:56:44.705 [planetinfo.record]: corona_hues = 0.777405 14:56:44.705 [planetinfo.record]: sun_color = 0.718085, 0.565131, 0.547121, 1 14:56:44.705 [planetinfo.record]: corona_shimmer = 0.546716 14:56:44.705 [planetinfo.record]: station_vector = 0.927 -0.341 0.159 14:56:44.705 [planetinfo.record]: station = coriolis 14:56:49.254 [PLANETINFO OVER]: Done 14:56:49.254 [PLANETINFO LOGGING]: 7 224 14:56:50.257 [planetinfo.record]: seed = 228 45 233 23 14:56:50.257 [planetinfo.record]: coordinates = 45 15 14:56:50.257 [planetinfo.record]: government = 4; 14:56:50.257 [planetinfo.record]: economy = 7; 14:56:50.257 [planetinfo.record]: techlevel = 3; 14:56:50.257 [planetinfo.record]: population = 24; 14:56:50.257 [planetinfo.record]: productivity = 4608; 14:56:50.257 [planetinfo.record]: name = "Tilaanes"; 14:56:50.257 [planetinfo.record]: inhabitant = "Green Horned Humanoid"; 14:56:50.257 [planetinfo.record]: inhabitants = "Green Horned Humanoids"; 14:56:50.257 [planetinfo.record]: description = "The planet Tilaanes is cursed by deadly civil war."; 14:56:50.288 [planetinfo.record]: air_color = 0.638315, 0.756647, 0.997049, 1; 14:56:50.288 [planetinfo.record]: cloud_alpha = 1.000000; 14:56:50.288 [planetinfo.record]: cloud_color = 0.516487, 0.982946, 0.994141, 1; 14:56:50.288 [planetinfo.record]: cloud_fraction = 0.580000; 14:56:50.288 [planetinfo.record]: land_color = 0.329015, 0.29953, 0.589844, 1; 14:56:50.288 [planetinfo.record]: land_fraction = 0.450000; 14:56:50.288 [planetinfo.record]: polar_cloud_color = 0.662877, 0.940696, 0.947363, 1; 14:56:50.288 [planetinfo.record]: polar_land_color = 0.836986, 0.825227, 0.941016, 1; 14:56:50.288 [planetinfo.record]: polar_sea_color = 0.905539, 0.931641, 0.87687, 1; 14:56:50.288 [planetinfo.record]: sea_color = 0.606986, 0.683594, 0.522842, 1; 14:56:50.288 [planetinfo.record]: rotation_speed = 0.001228 14:56:50.288 [planetinfo.record]: planet zpos = 604890.000000 14:56:50.288 [planetinfo.record]: sun_radius = 104785.508881 14:56:50.288 [planetinfo.record]: sun_vector = -0.038 -0.833 -0.552 14:56:50.288 [planetinfo.record]: sun_distance = 930600 14:56:50.288 [planetinfo.record]: corona_flare = 0.066757 14:56:50.288 [planetinfo.record]: corona_hues = 0.793533 14:56:50.288 [planetinfo.record]: sun_color = 0.541383, 0.669795, 0.706711, 1 14:56:50.288 [planetinfo.record]: corona_shimmer = 0.489754 14:56:50.288 [planetinfo.record]: station_vector = -0.480 -0.859 0.178 14:56:50.288 [planetinfo.record]: station = coriolis 14:56:54.395 [PLANETINFO OVER]: Done 14:56:54.396 [PLANETINFO LOGGING]: 7 225 14:56:55.399 [planetinfo.record]: seed = 26 9 75 250 14:56:55.399 [planetinfo.record]: coordinates = 9 155 14:56:55.399 [planetinfo.record]: government = 3; 14:56:55.400 [planetinfo.record]: economy = 3; 14:56:55.400 [planetinfo.record]: techlevel = 7; 14:56:55.400 [planetinfo.record]: population = 35; 14:56:55.400 [planetinfo.record]: productivity = 13720; 14:56:55.400 [planetinfo.record]: name = "Qurixean"; 14:56:55.400 [planetinfo.record]: inhabitant = "Human Colonial"; 14:56:55.400 [planetinfo.record]: inhabitants = "Human Colonials"; 14:56:55.400 [planetinfo.record]: description = "Qurixean is very fabled for its unusual oceans."; 14:56:55.409 [planetinfo.record]: air_color = 0.445032, 0.593069, 0.904693, 1; 14:56:55.409 [planetinfo.record]: cloud_alpha = 1.000000; 14:56:55.409 [planetinfo.record]: cloud_color = 0.0531998, 0.685691, 0.716797, 1; 14:56:55.409 [planetinfo.record]: cloud_fraction = 0.610000; 14:56:55.409 [planetinfo.record]: land_color = 0.475443, 0.177891, 0.66, 1; 14:56:55.409 [planetinfo.record]: land_fraction = 0.270000; 14:56:55.410 [planetinfo.record]: polar_cloud_color = 0.346615, 0.800249, 0.822559, 1; 14:56:55.410 [planetinfo.record]: polar_land_color = 0.868706, 0.763436, 0.934, 1; 14:56:55.410 [planetinfo.record]: polar_sea_color = 0.93125, 0.85262, 0.846674, 1; 14:56:55.410 [planetinfo.record]: sea_color = 0.6875, 0.455305, 0.437744, 1; 14:56:55.410 [planetinfo.record]: rotation_speed = 0.004920 14:56:55.410 [planetinfo.record]: planet zpos = 700050.000000 14:56:55.411 [planetinfo.record]: sun_radius = 112290.758514 14:56:55.411 [planetinfo.record]: sun_vector = -0.321 0.417 0.850 14:56:55.411 [planetinfo.record]: sun_distance = 969300 14:56:55.411 [planetinfo.record]: corona_flare = 0.030766 14:56:55.411 [planetinfo.record]: corona_hues = 0.932114 14:56:55.411 [planetinfo.record]: sun_color = 0.778809, 0.773819, 0.630162, 1 14:56:55.411 [planetinfo.record]: corona_shimmer = 0.336894 14:56:55.412 [planetinfo.record]: station_vector = -0.684 0.464 0.563 14:56:55.412 [planetinfo.record]: station = coriolis 14:56:59.979 [PLANETINFO OVER]: Done 14:56:59.979 [PLANETINFO LOGGING]: 7 226 14:57:00.983 [planetinfo.record]: seed = 120 59 165 124 14:57:00.983 [planetinfo.record]: coordinates = 59 162 14:57:00.983 [planetinfo.record]: government = 7; 14:57:00.984 [planetinfo.record]: economy = 2; 14:57:00.984 [planetinfo.record]: techlevel = 12; 14:57:00.984 [planetinfo.record]: population = 58; 14:57:00.984 [planetinfo.record]: productivity = 40832; 14:57:00.984 [planetinfo.record]: name = "Tequenes"; 14:57:00.984 [planetinfo.record]: inhabitant = "Blue Bug-Eyed Frog"; 14:57:00.984 [planetinfo.record]: inhabitants = "Blue Bug-Eyed Frogs"; 14:57:00.984 [planetinfo.record]: description = "This planet is a tedious little planet."; 14:57:01.028 [planetinfo.record]: air_color = 0.672284, 0.821807, 0.941115, 1; 14:57:01.028 [planetinfo.record]: cloud_alpha = 1.000000; 14:57:01.028 [planetinfo.record]: cloud_color = 0.613174, 0.826172, 0.686392, 1; 14:57:01.028 [planetinfo.record]: cloud_fraction = 0.390000; 14:57:01.028 [planetinfo.record]: land_color = 0.613594, 0.646948, 0.66, 1; 14:57:01.028 [planetinfo.record]: land_fraction = 0.640000; 14:57:01.028 [planetinfo.record]: polar_cloud_color = 0.731305, 0.871777, 0.779593, 1; 14:57:01.028 [planetinfo.record]: polar_land_color = 0.917582, 0.929382, 0.934, 1; 14:57:01.028 [planetinfo.record]: polar_sea_color = 0.916016, 0.76576, 0.762154, 1; 14:57:01.028 [planetinfo.record]: sea_color = 0.839844, 0.288799, 0.275574, 1; 14:57:01.028 [planetinfo.record]: rotation_speed = 0.004089 14:57:01.028 [planetinfo.record]: planet zpos = 773110.000000 14:57:01.028 [planetinfo.record]: sun_radius = 131806.594391 14:57:01.028 [planetinfo.record]: sun_vector = -0.503 0.308 0.807 14:57:01.028 [planetinfo.record]: sun_distance = 1248870 14:57:01.028 [planetinfo.record]: corona_flare = 0.044136 14:57:01.028 [planetinfo.record]: corona_hues = 0.864769 14:57:01.028 [planetinfo.record]: sun_color = 0.595194, 0.682568, 0.686981, 1 14:57:01.028 [planetinfo.record]: corona_shimmer = 0.346605 14:57:01.028 [planetinfo.record]: station_vector = 0.940 -0.257 0.226 14:57:01.028 [planetinfo.record]: station = icosahedron 14:57:05.929 [PLANETINFO OVER]: Done 14:57:05.929 [PLANETINFO LOGGING]: 7 227 14:57:06.947 [planetinfo.record]: seed = 142 233 119 86 14:57:06.947 [planetinfo.record]: coordinates = 233 18 14:57:06.947 [planetinfo.record]: government = 1; 14:57:06.947 [planetinfo.record]: economy = 2; 14:57:06.947 [planetinfo.record]: techlevel = 7; 14:57:06.947 [planetinfo.record]: population = 32; 14:57:06.947 [planetinfo.record]: productivity = 10240; 14:57:06.947 [planetinfo.record]: name = "Veenen"; 14:57:06.947 [planetinfo.record]: inhabitant = "Human Colonial"; 14:57:06.947 [planetinfo.record]: inhabitants = "Human Colonials"; 14:57:06.948 [planetinfo.record]: description = "The planet Veenen is most well known for its hoopy casinos."; 14:57:06.955 [planetinfo.record]: air_color = 0.677105, 0.871629, 0.876076, 1; 14:57:06.955 [planetinfo.record]: cloud_alpha = 1.000000; 14:57:06.955 [planetinfo.record]: cloud_color = 0.626816, 0.630859, 0.579109, 1; 14:57:06.955 [planetinfo.record]: cloud_fraction = 0.250000; 14:57:06.955 [planetinfo.record]: land_color = 0.210915, 0.559982, 0.568359, 1; 14:57:06.955 [planetinfo.record]: land_fraction = 0.390000; 14:57:06.955 [planetinfo.record]: polar_cloud_color = 0.780747, 0.783887, 0.743697, 1; 14:57:06.956 [planetinfo.record]: polar_land_color = 0.794874, 0.939689, 0.943164, 1; 14:57:06.956 [planetinfo.record]: polar_sea_color = 0.933203, 0.913795, 0.880346, 1; 14:57:06.956 [planetinfo.record]: sea_color = 0.667969, 0.6124, 0.516632, 1; 14:57:06.956 [planetinfo.record]: rotation_speed = 0.004566 14:57:06.956 [planetinfo.record]: planet zpos = 550200.000000 14:57:06.956 [planetinfo.record]: sun_radius = 102604.206848 14:57:06.956 [planetinfo.record]: sun_vector = 0.050 0.998 0.029 14:57:06.956 [planetinfo.record]: sun_distance = 1008700 14:57:06.956 [planetinfo.record]: corona_flare = 0.037898 14:57:06.956 [planetinfo.record]: corona_hues = 0.841255 14:57:06.956 [planetinfo.record]: sun_color = 0.675269, 0.555601, 0.436254, 1 14:57:06.956 [planetinfo.record]: corona_shimmer = 0.337839 14:57:06.956 [planetinfo.record]: station_vector = 0.767 -0.574 0.286 14:57:06.956 [planetinfo.record]: station = coriolis 14:57:12.041 [PLANETINFO OVER]: Done 14:57:12.041 [PLANETINFO LOGGING]: 7 228 14:57:13.045 [planetinfo.record]: seed = 140 59 161 32 14:57:13.045 [planetinfo.record]: coordinates = 59 146 14:57:13.045 [planetinfo.record]: government = 1; 14:57:13.045 [planetinfo.record]: economy = 2; 14:57:13.045 [planetinfo.record]: techlevel = 9; 14:57:13.045 [planetinfo.record]: population = 40; 14:57:13.045 [planetinfo.record]: productivity = 12800; 14:57:13.046 [planetinfo.record]: name = "Rear"; 14:57:13.046 [planetinfo.record]: inhabitant = "Large Red Bug-Eyed Frog"; 14:57:13.046 [planetinfo.record]: inhabitants = "Large Red Bug-Eyed Frogs"; 14:57:13.046 [planetinfo.record]: description = "The planet Rear is scourged by evil disease."; 14:57:13.076 [planetinfo.record]: air_color = 0.635957, 0.911848, 0.909401, 1; 14:57:13.076 [planetinfo.record]: cloud_alpha = 1.000000; 14:57:13.076 [planetinfo.record]: cloud_color = 0.738281, 0.734721, 0.510452, 1; 14:57:13.083 [planetinfo.record]: cloud_fraction = 0.210000; 14:57:13.083 [planetinfo.record]: land_color = 0.451172, 0.620845, 0.66, 1; 14:57:13.083 [planetinfo.record]: land_fraction = 0.670000; 14:57:13.084 [planetinfo.record]: polar_cloud_color = 0.832227, 0.829719, 0.671714, 1; 14:57:13.084 [planetinfo.record]: polar_land_color = 0.860119, 0.920147, 0.934, 1; 14:57:13.084 [planetinfo.record]: polar_sea_color = 0.941016, 0.940263, 0.892862, 1; 14:57:13.084 [planetinfo.record]: sea_color = 0.589844, 0.587957, 0.46911, 1; 14:57:13.084 [planetinfo.record]: rotation_speed = 0.004359 14:57:13.084 [planetinfo.record]: planet zpos = 373750.000000 14:57:13.084 [planetinfo.record]: sun_radius = 81380.100250 14:57:13.084 [planetinfo.record]: sun_vector = 0.546 -0.126 -0.828 14:57:13.084 [planetinfo.record]: sun_distance = 488750 14:57:13.085 [planetinfo.record]: corona_flare = 0.087630 14:57:13.085 [planetinfo.record]: corona_hues = 0.788429 14:57:13.085 [planetinfo.record]: sun_color = 0.671713, 0.452228, 0.351667, 1 14:57:13.086 [planetinfo.record]: corona_shimmer = 0.355656 14:57:13.086 [planetinfo.record]: station_vector = 0.030 0.862 0.506 14:57:13.086 [planetinfo.record]: station = coriolis 14:57:17.704 [PLANETINFO OVER]: Done 14:57:17.705 [PLANETINFO LOGGING]: 7 229 14:57:18.708 [planetinfo.record]: seed = 66 90 227 147 14:57:18.708 [planetinfo.record]: coordinates = 90 74 14:57:18.708 [planetinfo.record]: government = 0; 14:57:18.708 [planetinfo.record]: economy = 2; 14:57:18.708 [planetinfo.record]: techlevel = 7; 14:57:18.708 [planetinfo.record]: population = 31; 14:57:18.708 [planetinfo.record]: productivity = 7936; 14:57:18.708 [planetinfo.record]: name = "Beorsora"; 14:57:18.708 [planetinfo.record]: inhabitant = "Black Slimy Lobster"; 14:57:18.709 [planetinfo.record]: inhabitants = "Black Slimy Lobsters"; 14:57:18.709 [planetinfo.record]: description = "This world is mildly well known for Beorsoraian wolf cutlet and its unusual oceans."; 14:57:18.712 [planetinfo.record]: air_color = 0.655631, 0.851351, 0.936563, 1; 14:57:18.712 [planetinfo.record]: cloud_alpha = 1.000000; 14:57:18.712 [planetinfo.record]: cloud_color = 0.587208, 0.8125, 0.568115, 1; 14:57:18.712 [planetinfo.record]: cloud_fraction = 0.250000; 14:57:18.712 [planetinfo.record]: land_color = 0.558345, 0.66, 0.533672, 1; 14:57:18.713 [planetinfo.record]: land_fraction = 0.700000; 14:57:18.713 [planetinfo.record]: polar_cloud_color = 0.715611, 0.865625, 0.702898, 1; 14:57:18.713 [planetinfo.record]: polar_land_color = 0.898036, 0.934, 0.889307, 1; 14:57:18.713 [planetinfo.record]: polar_sea_color = 0.940625, 0.87908, 0.906487, 1; 14:57:18.713 [planetinfo.record]: sea_color = 0.59375, 0.438354, 0.507554, 1; 14:57:18.713 [planetinfo.record]: rotation_speed = 0.004260 14:57:18.713 [planetinfo.record]: planet zpos = 440880.000000 14:57:18.713 [planetinfo.record]: sun_radius = 80948.979187 14:57:18.713 [planetinfo.record]: sun_vector = 0.556 -0.699 -0.450 14:57:18.713 [planetinfo.record]: sun_distance = 661320 14:57:18.713 [planetinfo.record]: corona_flare = 0.010463 14:57:18.713 [planetinfo.record]: corona_hues = 0.776657 14:57:18.713 [planetinfo.record]: sun_color = 0.819113, 0.674162, 0.389737, 1 14:57:18.713 [planetinfo.record]: corona_shimmer = 0.420302 14:57:18.714 [planetinfo.record]: station_vector = 0.386 -0.817 0.428 14:57:18.714 [planetinfo.record]: station = coriolis 14:57:23.639 [PLANETINFO OVER]: Done 14:57:23.640 [PLANETINFO LOGGING]: 7 230 14:57:24.644 [planetinfo.record]: seed = 32 244 93 84 14:57:24.644 [planetinfo.record]: coordinates = 244 39 14:57:24.644 [planetinfo.record]: government = 4; 14:57:24.644 [planetinfo.record]: economy = 7; 14:57:24.644 [planetinfo.record]: techlevel = 2; 14:57:24.644 [planetinfo.record]: population = 20; 14:57:24.644 [planetinfo.record]: productivity = 3840; 14:57:24.644 [planetinfo.record]: name = "Raaed"; 14:57:24.644 [planetinfo.record]: inhabitant = "Human Colonial"; 14:57:24.644 [planetinfo.record]: inhabitants = "Human Colonials"; 14:57:24.644 [planetinfo.record]: description = "The planet Raaed is most famous for the Raaedian deadly goat and its vast rain forests."; 14:57:24.646 [planetinfo.record]: air_color = 0.638797, 0.868922, 0.802223, 1; 14:57:24.646 [planetinfo.record]: cloud_alpha = 1.000000; 14:57:24.646 [planetinfo.record]: cloud_color = 0.609375, 0.507093, 0.490356, 1; 14:57:24.646 [planetinfo.record]: cloud_fraction = 0.140000; 14:57:24.646 [planetinfo.record]: land_color = 0.66, 0.281761, 0.116016, 1; 14:57:24.646 [planetinfo.record]: land_fraction = 0.480000; 14:57:24.646 [planetinfo.record]: polar_cloud_color = 0.774219, 0.693, 0.67971, 1; 14:57:24.646 [planetinfo.record]: polar_land_color = 0.934, 0.800184, 0.741545, 1; 14:57:24.646 [planetinfo.record]: polar_sea_color = 0.949805, 0.892297, 0.940819, 1; 14:57:24.646 [planetinfo.record]: sea_color = 0.501953, 0.380386, 0.482958, 1; 14:57:24.646 [planetinfo.record]: rotation_speed = 0.002224 14:57:24.647 [planetinfo.record]: planet zpos = 367560.000000 14:57:24.647 [planetinfo.record]: sun_radius = 111238.772583 14:57:24.647 [planetinfo.record]: sun_vector = -0.406 -0.902 -0.149 14:57:24.647 [planetinfo.record]: sun_distance = 898480 14:57:24.647 [planetinfo.record]: corona_flare = 0.043510 14:57:24.647 [planetinfo.record]: corona_hues = 0.552811 14:57:24.647 [planetinfo.record]: sun_color = 0.284652, 0.301963, 0.675769, 1 14:57:24.647 [planetinfo.record]: corona_shimmer = 0.279274 14:57:24.647 [planetinfo.record]: station_vector = -0.285 0.940 0.187 14:57:24.647 [planetinfo.record]: station = coriolis 14:57:28.681 [PLANETINFO OVER]: Done 14:57:28.683 [PLANETINFO LOGGING]: 7 231 14:57:29.686 [planetinfo.record]: seed = 54 124 15 164 14:57:29.686 [planetinfo.record]: coordinates = 124 184 14:57:29.686 [planetinfo.record]: government = 6; 14:57:29.686 [planetinfo.record]: economy = 0; 14:57:29.686 [planetinfo.record]: techlevel = 10; 14:57:29.686 [planetinfo.record]: population = 47; 14:57:29.686 [planetinfo.record]: productivity = 37600; 14:57:29.686 [planetinfo.record]: name = "Zaeded"; 14:57:29.686 [planetinfo.record]: inhabitant = "Human Colonial"; 14:57:29.686 [planetinfo.record]: inhabitants = "Human Colonials"; 14:57:29.686 [planetinfo.record]: description = "Zaeded is reasonably notable for its great parking meters but cursed by occasional earthquakes."; 14:57:29.688 [planetinfo.record]: air_color = 0.67776, 0.835572, 0.923555, 1; 14:57:29.688 [planetinfo.record]: cloud_alpha = 1.000000; 14:57:29.689 [planetinfo.record]: cloud_color = 0.619354, 0.773438, 0.632596, 1; 14:57:29.689 [planetinfo.record]: cloud_fraction = 0.130000; 14:57:29.689 [planetinfo.record]: land_color = 0.317754, 0.66, 0.28875, 1; 14:57:29.689 [planetinfo.record]: land_fraction = 0.560000; 14:57:29.689 [planetinfo.record]: polar_cloud_color = 0.742455, 0.848047, 0.751529, 1; 14:57:29.689 [planetinfo.record]: polar_land_color = 0.812917, 0.934, 0.802656, 1; 14:57:29.689 [planetinfo.record]: polar_sea_color = 0.944922, 0.90698, 0.8938, 1; 14:57:29.690 [planetinfo.record]: sea_color = 0.550781, 0.462318, 0.431589, 1; 14:57:29.690 [planetinfo.record]: rotation_speed = 0.003960 14:57:29.690 [planetinfo.record]: planet zpos = 436040.000000 14:57:29.690 [planetinfo.record]: sun_radius = 112691.773071 14:57:29.690 [planetinfo.record]: sun_vector = -0.882 -0.361 0.302 14:57:29.690 [planetinfo.record]: sun_distance = 832440 14:57:29.690 [planetinfo.record]: corona_flare = 0.055254 14:57:29.690 [planetinfo.record]: corona_hues = 0.965729 14:57:29.690 [planetinfo.record]: sun_color = 0.719772, 0.626413, 0.537732, 1 14:57:29.691 [planetinfo.record]: corona_shimmer = 0.439550 14:57:29.691 [planetinfo.record]: station_vector = 0.746 0.530 0.403 14:57:29.691 [planetinfo.record]: station = coriolis 14:57:34.321 [PLANETINFO OVER]: Done 14:57:34.322 [PLANETINFO LOGGING]: 7 232 14:57:35.324 [planetinfo.record]: seed = 52 117 89 70 14:57:35.324 [planetinfo.record]: coordinates = 117 248 14:57:35.324 [planetinfo.record]: government = 6; 14:57:35.324 [planetinfo.record]: economy = 0; 14:57:35.324 [planetinfo.record]: techlevel = 11; 14:57:35.324 [planetinfo.record]: population = 51; 14:57:35.324 [planetinfo.record]: productivity = 40800; 14:57:35.324 [planetinfo.record]: name = "Biraa"; 14:57:35.324 [planetinfo.record]: inhabitant = "Human Colonial"; 14:57:35.325 [planetinfo.record]: inhabitants = "Human Colonials"; 14:57:35.325 [planetinfo.record]: description = "This planet is fabled for its ancient Biraaian Geer banana plantations."; 14:57:35.344 [planetinfo.record]: air_color = 0.646378, 0.871523, 0.820325, 1; 14:57:35.344 [planetinfo.record]: cloud_alpha = 1.000000; 14:57:35.344 [planetinfo.record]: cloud_color = 0.617188, 0.544296, 0.508698, 1; 14:57:35.344 [planetinfo.record]: cloud_fraction = 0.400000; 14:57:35.344 [planetinfo.record]: land_color = 0.583462, 0.66, 0.268125, 1; 14:57:35.344 [planetinfo.record]: land_fraction = 0.670000; 14:57:35.344 [planetinfo.record]: polar_cloud_color = 0.777734, 0.720326, 0.69229, 1; 14:57:35.344 [planetinfo.record]: polar_land_color = 0.906922, 0.934, 0.795359, 1; 14:57:35.344 [planetinfo.record]: polar_sea_color = 0.879358, 0.92793, 0.900608, 1; 14:57:35.344 [planetinfo.record]: sea_color = 0.569806, 0.720703, 0.635823, 1; 14:57:35.344 [planetinfo.record]: rotation_speed = 0.002511 14:57:35.344 [planetinfo.record]: planet zpos = 536280.000000 14:57:35.344 [planetinfo.record]: sun_radius = 105483.429413 14:57:35.344 [planetinfo.record]: sun_vector = -0.254 -0.510 -0.822 14:57:35.344 [planetinfo.record]: sun_distance = 1027870 14:57:35.344 [planetinfo.record]: corona_flare = 0.097346 14:57:35.344 [planetinfo.record]: corona_hues = 0.981544 14:57:35.344 [planetinfo.record]: sun_color = 0.666649, 0.668311, 0.499262, 1 14:57:35.345 [planetinfo.record]: corona_shimmer = 0.291484 14:57:35.345 [planetinfo.record]: station_vector = 0.617 0.535 0.577 14:57:35.345 [planetinfo.record]: station = dodecahedron 14:57:40.843 [PLANETINFO OVER]: Done 14:57:40.843 [PLANETINFO LOGGING]: 7 233 14:57:41.854 [planetinfo.record]: seed = 106 106 123 142 14:57:41.854 [planetinfo.record]: coordinates = 106 111 14:57:41.854 [planetinfo.record]: government = 5; 14:57:41.854 [planetinfo.record]: economy = 7; 14:57:41.854 [planetinfo.record]: techlevel = 5; 14:57:41.854 [planetinfo.record]: population = 33; 14:57:41.854 [planetinfo.record]: productivity = 7128; 14:57:41.854 [planetinfo.record]: name = "Reusleed"; 14:57:41.854 [planetinfo.record]: inhabitant = "Human Colonial"; 14:57:41.854 [planetinfo.record]: inhabitants = "Human Colonials"; 14:57:41.854 [planetinfo.record]: description = "This world is mildly famous for its pink Reusleedian Th Thweed plantations and its unusual casinos."; 14:57:41.864 [planetinfo.record]: air_color = 0.691056, 0.854549, 0.879328, 1; 14:57:41.872 [planetinfo.record]: cloud_alpha = 1.000000; 14:57:41.872 [planetinfo.record]: cloud_color = 0.629442, 0.640625, 0.613098, 1; 14:57:41.872 [planetinfo.record]: cloud_fraction = 0.520000; 14:57:41.872 [planetinfo.record]: land_color = 0.511719, 0.475739, 0.486701, 1; 14:57:41.872 [planetinfo.record]: land_fraction = 0.330000; 14:57:41.872 [planetinfo.record]: polar_cloud_color = 0.779681, 0.788281, 0.767112, 1; 14:57:41.872 [planetinfo.record]: polar_land_color = 0.948828, 0.932149, 0.937231, 1; 14:57:41.872 [planetinfo.record]: polar_sea_color = 0.938672, 0.915595, 0.882938, 1; 14:57:41.872 [planetinfo.record]: sea_color = 0.613281, 0.552971, 0.467627, 1; 14:57:41.872 [planetinfo.record]: rotation_speed = 0.003636 14:57:41.872 [planetinfo.record]: planet zpos = 715660.000000 14:57:41.872 [planetinfo.record]: sun_radius = 149054.469299 14:57:41.872 [planetinfo.record]: sun_vector = -0.529 -0.827 -0.189 14:57:41.872 [planetinfo.record]: sun_distance = 1171080 14:57:41.872 [planetinfo.record]: corona_flare = 0.014200 14:57:41.872 [planetinfo.record]: corona_hues = 0.534508 14:57:41.872 [planetinfo.record]: sun_color = 0.544344, 0.617485, 0.758749, 1 14:57:41.873 [planetinfo.record]: corona_shimmer = 0.521343 14:57:41.873 [planetinfo.record]: station_vector = -0.596 -0.702 0.390 14:57:41.873 [planetinfo.record]: station = coriolis 14:57:47.783 [PLANETINFO OVER]: Done 14:57:47.784 [PLANETINFO LOGGING]: 7 234 14:57:48.790 [planetinfo.record]: seed = 200 88 21 35 14:57:48.790 [planetinfo.record]: coordinates = 88 97 14:57:48.790 [planetinfo.record]: government = 1; 14:57:48.790 [planetinfo.record]: economy = 3; 14:57:48.790 [planetinfo.record]: techlevel = 5; 14:57:48.790 [planetinfo.record]: population = 25; 14:57:48.790 [planetinfo.record]: productivity = 7000; 14:57:48.790 [planetinfo.record]: name = "Geisor"; 14:57:48.790 [planetinfo.record]: inhabitant = "Human Colonial"; 14:57:48.790 [planetinfo.record]: inhabitants = "Human Colonials"; 14:57:48.790 [planetinfo.record]: description = "The world Geisor is a boring planet."; 14:57:48.793 [planetinfo.record]: air_color = 0.421392, 0.72042, 0.887133, 1; 14:57:48.793 [planetinfo.record]: cloud_alpha = 1.000000; 14:57:48.793 [planetinfo.record]: cloud_color = 0.015564, 0.664062, 0.0712943, 1; 14:57:48.793 [planetinfo.record]: cloud_fraction = 0.300000; 14:57:48.793 [planetinfo.record]: land_color = 0.319732, 0.312027, 0.558594, 1; 14:57:48.793 [planetinfo.record]: land_fraction = 0.510000; 14:57:48.793 [planetinfo.record]: polar_cloud_color = 0.311262, 0.798828, 0.353162, 1; 14:57:48.793 [planetinfo.record]: polar_land_color = 0.843209, 0.839953, 0.944141, 1; 14:57:48.794 [planetinfo.record]: polar_sea_color = 0.904011, 0.924609, 0.858695, 1; 14:57:48.794 [planetinfo.record]: sea_color = 0.686725, 0.753906, 0.538925, 1; 14:57:48.794 [planetinfo.record]: rotation_speed = 0.000302 14:57:48.794 [planetinfo.record]: planet zpos = 403920.000000 14:57:48.794 [planetinfo.record]: sun_radius = 90041.209717 14:57:48.794 [planetinfo.record]: sun_vector = -0.183 -0.237 -0.954 14:57:48.794 [planetinfo.record]: sun_distance = 660960 14:57:48.794 [planetinfo.record]: corona_flare = 0.043684 14:57:48.794 [planetinfo.record]: corona_hues = 0.838463 14:57:48.794 [planetinfo.record]: sun_color = 0.240448, 0.575356, 0.692728, 1 14:57:48.794 [planetinfo.record]: corona_shimmer = 0.274568 14:57:48.795 [planetinfo.record]: station_vector = -0.137 0.976 0.168 14:57:48.795 [planetinfo.record]: station = coriolis 14:57:54.801 [PLANETINFO OVER]: Done 14:57:54.802 [PLANETINFO LOGGING]: 7 235 14:57:55.807 [planetinfo.record]: seed = 222 89 167 144 14:57:55.807 [planetinfo.record]: coordinates = 89 89 14:57:55.807 [planetinfo.record]: government = 3; 14:57:55.807 [planetinfo.record]: economy = 1; 14:57:55.807 [planetinfo.record]: techlevel = 9; 14:57:55.808 [planetinfo.record]: population = 41; 14:57:55.808 [planetinfo.record]: productivity = 20664; 14:57:55.808 [planetinfo.record]: name = "Ergerexe"; 14:57:55.808 [planetinfo.record]: inhabitant = "Black Slimy Rodent"; 14:57:55.808 [planetinfo.record]: inhabitants = "Black Slimy Rodents"; 14:57:55.808 [planetinfo.record]: description = "This world is very well known for Ergerexeian lethal water and the Ergerexeian tree snake."; 14:57:55.836 [planetinfo.record]: air_color = 0.496615, 0.682239, 0.835102, 1; 14:57:55.836 [planetinfo.record]: cloud_alpha = 1.000000; 14:57:55.836 [planetinfo.record]: cloud_color = 0.194397, 0.507812, 0.309479, 1; 14:57:55.836 [planetinfo.record]: cloud_fraction = 0.330000; 14:57:55.837 [planetinfo.record]: land_color = 0.46519, 0.66, 0.180469, 1; 14:57:55.837 [planetinfo.record]: land_fraction = 0.660000; 14:57:55.837 [planetinfo.record]: polar_cloud_color = 0.447496, 0.728516, 0.550683, 1; 14:57:55.837 [planetinfo.record]: polar_land_color = 0.865079, 0.934, 0.764348, 1; 14:57:55.837 [planetinfo.record]: polar_sea_color = 0.95, 0.898325, 0.916492, 1; 14:57:55.837 [planetinfo.record]: sea_color = 0.5, 0.391211, 0.429457, 1; 14:57:55.837 [planetinfo.record]: rotation_speed = 0.003321 14:57:55.837 [planetinfo.record]: planet zpos = 319550.000000 14:57:55.837 [planetinfo.record]: sun_radius = 71835.983276 14:57:55.837 [planetinfo.record]: sun_vector = -0.904 -0.401 -0.150 14:57:55.837 [planetinfo.record]: sun_distance = 581000 14:57:55.837 [planetinfo.record]: corona_flare = 0.073619 14:57:55.837 [planetinfo.record]: corona_hues = 0.692581 14:57:55.837 [planetinfo.record]: sun_color = 0.681696, 0.655809, 0.486965, 1 14:57:55.837 [planetinfo.record]: corona_shimmer = 0.308748 14:57:55.838 [planetinfo.record]: station_vector = 0.953 -0.243 0.182 14:57:55.838 [planetinfo.record]: station = coriolis 14:58:00.584 [PLANETINFO OVER]: Done 14:58:00.585 [PLANETINFO LOGGING]: 7 236 14:58:01.588 [planetinfo.record]: seed = 220 2 17 117 14:58:01.588 [planetinfo.record]: coordinates = 2 46 14:58:01.588 [planetinfo.record]: government = 3; 14:58:01.588 [planetinfo.record]: economy = 6; 14:58:01.588 [planetinfo.record]: techlevel = 5; 14:58:01.588 [planetinfo.record]: population = 30; 14:58:01.588 [planetinfo.record]: productivity = 6720; 14:58:01.588 [planetinfo.record]: name = "Labirior"; 14:58:01.589 [planetinfo.record]: inhabitant = "Human Colonial"; 14:58:01.589 [planetinfo.record]: inhabitants = "Human Colonials"; 14:58:01.589 [planetinfo.record]: description = "The world Labirior is scourged by deadly disease."; 14:58:01.608 [planetinfo.record]: air_color = 0.424844, 0.808112, 0.854613, 1; 14:58:01.608 [planetinfo.record]: cloud_alpha = 1.000000; 14:58:01.608 [planetinfo.record]: cloud_color = 0.393224, 0.566406, 0.0508881, 1; 14:58:01.608 [planetinfo.record]: cloud_fraction = 0.450000; 14:58:01.608 [planetinfo.record]: land_color = 0.426151, 0.333099, 0.576172, 1; 14:58:01.608 [planetinfo.record]: land_fraction = 0.470000; 14:58:01.608 [planetinfo.record]: polar_cloud_color = 0.610627, 0.754883, 0.325469, 1; 14:58:01.608 [planetinfo.record]: polar_land_color = 0.881039, 0.842991, 0.942383, 1; 14:58:01.608 [planetinfo.record]: polar_sea_color = 0.931445, 0.867886, 0.853218, 1; 14:58:01.608 [planetinfo.record]: sea_color = 0.685547, 0.498427, 0.455246, 1; 14:58:01.608 [planetinfo.record]: rotation_speed = 0.000639 14:58:01.608 [planetinfo.record]: planet zpos = 368820.000000 14:58:01.608 [planetinfo.record]: sun_radius = 81661.729431 14:58:01.608 [planetinfo.record]: sun_vector = 0.138 0.621 -0.772 14:58:01.608 [planetinfo.record]: sun_distance = 860580 14:58:01.608 [planetinfo.record]: corona_flare = 0.002269 14:58:01.608 [planetinfo.record]: corona_hues = 0.573143 14:58:01.608 [planetinfo.record]: sun_color = 0.28392, 0.351268, 0.69577, 1 14:58:01.608 [planetinfo.record]: corona_shimmer = 0.391493 14:58:01.608 [planetinfo.record]: station_vector = -0.662 -0.625 0.414 14:58:01.608 [planetinfo.record]: station = coriolis 14:58:06.163 [PLANETINFO OVER]: Done 14:58:06.163 [PLANETINFO LOGGING]: 7 237 14:58:07.179 [planetinfo.record]: seed = 146 57 19 254 14:58:07.179 [planetinfo.record]: coordinates = 57 30 14:58:07.179 [planetinfo.record]: government = 2; 14:58:07.179 [planetinfo.record]: economy = 6; 14:58:07.179 [planetinfo.record]: techlevel = 3; 14:58:07.179 [planetinfo.record]: population = 21; 14:58:07.179 [planetinfo.record]: productivity = 4032; 14:58:07.179 [planetinfo.record]: name = "Riladi"; 14:58:07.179 [planetinfo.record]: inhabitant = "Human Colonial"; 14:58:07.179 [planetinfo.record]: inhabitants = "Human Colonials"; 14:58:07.179 [planetinfo.record]: description = "The planet Riladi is cursed by dreadful civil war."; 14:58:07.181 [planetinfo.record]: air_color = 0.616305, 0.575824, 0.852662, 1; 14:58:07.181 [planetinfo.record]: cloud_alpha = 1.000000; 14:58:07.181 [planetinfo.record]: cloud_color = 0.445591, 0.350342, 0.560547, 1; 14:58:07.181 [planetinfo.record]: cloud_fraction = 0.530000; 14:58:07.181 [planetinfo.record]: land_color = 0.145508, 0.582031, 0.356949, 1; 14:58:07.181 [planetinfo.record]: land_fraction = 0.310000; 14:58:07.181 [planetinfo.record]: polar_cloud_color = 0.655828, 0.575938, 0.752246, 1; 14:58:07.182 [planetinfo.record]: polar_land_color = 0.76521, 0.941797, 0.850744, 1; 14:58:07.182 [planetinfo.record]: polar_sea_color = 0.949805, 0.896193, 0.92258, 1; 14:58:07.182 [planetinfo.record]: sea_color = 0.501953, 0.388622, 0.444402, 1; 14:58:07.182 [planetinfo.record]: rotation_speed = 0.003054 14:58:07.182 [planetinfo.record]: planet zpos = 645700.000000 14:58:07.182 [planetinfo.record]: sun_radius = 150738.869934 14:58:07.182 [planetinfo.record]: sun_vector = 0.913 0.396 0.095 14:58:07.182 [planetinfo.record]: sun_distance = 1226830 14:58:07.182 [planetinfo.record]: corona_flare = 0.001161 14:58:07.182 [planetinfo.record]: corona_hues = 0.987175 14:58:07.182 [planetinfo.record]: sun_color = 0.304653, 0.761946, 0.814413, 1 14:58:07.182 [planetinfo.record]: corona_shimmer = 0.444800 14:58:07.182 [planetinfo.record]: station_vector = 0.082 -0.931 0.356 14:58:07.182 [planetinfo.record]: station = coriolis 14:58:11.918 [PLANETINFO OVER]: Done 14:58:11.919 [PLANETINFO LOGGING]: 7 238 14:58:12.921 [planetinfo.record]: seed = 112 225 205 196 14:58:12.921 [planetinfo.record]: coordinates = 225 141 14:58:12.921 [planetinfo.record]: government = 6; 14:58:12.922 [planetinfo.record]: economy = 5; 14:58:12.922 [planetinfo.record]: techlevel = 6; 14:58:12.922 [planetinfo.record]: population = 36; 14:58:12.922 [planetinfo.record]: productivity = 14400; 14:58:12.922 [planetinfo.record]: name = "Zabeor"; 14:58:12.922 [planetinfo.record]: inhabitant = "Fierce Fat Bird"; 14:58:12.922 [planetinfo.record]: inhabitants = "Fierce Fat Birds"; 14:58:12.922 [planetinfo.record]: description = "This planet is beset by deadly earthquakes."; 14:58:12.948 [planetinfo.record]: air_color = 0.777243, 0.617623, 0.877377, 1; 14:58:12.948 [planetinfo.record]: cloud_alpha = 1.000000; 14:58:12.948 [planetinfo.record]: cloud_color = 0.634766, 0.451279, 0.477082, 1; 14:58:12.948 [planetinfo.record]: cloud_fraction = 0.410000; 14:58:12.948 [planetinfo.record]: land_color = 0.624047, 0.00257813, 0.66, 1; 14:58:12.948 [planetinfo.record]: land_fraction = 0.460000; 14:58:12.948 [planetinfo.record]: polar_cloud_color = 0.785645, 0.643707, 0.663667, 1; 14:58:12.948 [planetinfo.record]: polar_land_color = 0.92128, 0.701412, 0.934, 1; 14:58:12.948 [planetinfo.record]: polar_sea_color = 0.937378, 0.940039, 0.891385, 1; 14:58:12.948 [planetinfo.record]: sea_color = 0.592821, 0.599609, 0.475471, 1; 14:58:12.948 [planetinfo.record]: rotation_speed = 0.003522 14:58:12.948 [planetinfo.record]: planet zpos = 569100.000000 14:58:12.948 [planetinfo.record]: sun_radius = 74830.586243 14:58:12.948 [planetinfo.record]: sun_vector = 0.280 0.936 -0.214 14:58:12.948 [planetinfo.record]: sun_distance = 894300 14:58:12.948 [planetinfo.record]: corona_flare = 0.008174 14:58:12.948 [planetinfo.record]: corona_hues = 0.553780 14:58:12.948 [planetinfo.record]: sun_color = 0.724149, 0.665167, 0.224611, 1 14:58:12.948 [planetinfo.record]: corona_shimmer = 0.554619 14:58:12.948 [planetinfo.record]: station_vector = -0.179 -0.982 0.064 14:58:12.948 [planetinfo.record]: station = coriolis 14:58:17.811 [PLANETINFO OVER]: Done 14:58:17.811 [PLANETINFO LOGGING]: 7 239 14:58:18.815 [planetinfo.record]: seed = 134 210 63 224 14:58:18.815 [planetinfo.record]: coordinates = 210 217 14:58:18.815 [planetinfo.record]: government = 0; 14:58:18.815 [planetinfo.record]: economy = 3; 14:58:18.815 [planetinfo.record]: techlevel = 6; 14:58:18.815 [planetinfo.record]: population = 28; 14:58:18.815 [planetinfo.record]: productivity = 6272; 14:58:18.815 [planetinfo.record]: name = "Inonin"; 14:58:18.815 [planetinfo.record]: inhabitant = "Human Colonial"; 14:58:18.815 [planetinfo.record]: inhabitants = "Human Colonials"; 14:58:18.815 [planetinfo.record]: description = "This planet is plagued by deadly earthquakes."; 14:58:18.817 [planetinfo.record]: air_color = 0.674183, 0.78681, 0.961277, 1; 14:58:18.817 [planetinfo.record]: cloud_alpha = 1.000000; 14:58:18.818 [planetinfo.record]: cloud_color = 0.623474, 0.886719, 0.84353, 1; 14:58:18.818 [planetinfo.record]: cloud_fraction = 0.520000; 14:58:18.818 [planetinfo.record]: land_color = 0.541016, 0.467049, 0.491319, 1; 14:58:18.818 [planetinfo.record]: land_fraction = 0.580000; 14:58:18.818 [planetinfo.record]: polar_cloud_color = 0.732212, 0.899023, 0.871656, 1; 14:58:18.818 [planetinfo.record]: polar_land_color = 0.945898, 0.913568, 0.924176, 1; 14:58:18.818 [planetinfo.record]: polar_sea_color = 0.901647, 0.923633, 0.853278, 1; 14:58:18.818 [planetinfo.record]: sea_color = 0.690959, 0.763672, 0.530991, 1; 14:58:18.818 [planetinfo.record]: rotation_speed = 0.002751 14:58:18.818 [planetinfo.record]: planet zpos = 363120.000000 14:58:18.818 [planetinfo.record]: sun_radius = 88739.149170 14:58:18.818 [planetinfo.record]: sun_vector = -0.127 0.900 -0.417 14:58:18.818 [planetinfo.record]: sun_distance = 635460 14:58:18.818 [planetinfo.record]: corona_flare = 0.075533 14:58:18.818 [planetinfo.record]: corona_hues = 0.676376 14:58:18.818 [planetinfo.record]: sun_color = 0.802283, 0.801761, 0.696733, 1 14:58:18.818 [planetinfo.record]: corona_shimmer = 0.389979 14:58:18.818 [planetinfo.record]: station_vector = 0.578 -0.797 0.177 14:58:18.818 [planetinfo.record]: station = coriolis 14:58:23.173 [PLANETINFO OVER]: Done 14:58:23.173 [PLANETINFO LOGGING]: 7 240 14:58:24.176 [planetinfo.record]: seed = 132 172 201 120 14:58:24.176 [planetinfo.record]: coordinates = 172 63 14:58:24.176 [planetinfo.record]: government = 0; 14:58:24.176 [planetinfo.record]: economy = 7; 14:58:24.176 [planetinfo.record]: techlevel = 0; 14:58:24.177 [planetinfo.record]: population = 8; 14:58:24.177 [planetinfo.record]: productivity = 768; 14:58:24.177 [planetinfo.record]: name = "Edzaar"; 14:58:24.177 [planetinfo.record]: inhabitant = "Blue Bony Lobster"; 14:58:24.177 [planetinfo.record]: inhabitants = "Blue Bony Lobsters"; 14:58:24.177 [planetinfo.record]: description = "Edzaar is a revolting little planet."; 14:58:24.187 [planetinfo.record]: air_color = 0.696781, 0.740568, 0.973635, 1; 14:58:24.187 [planetinfo.record]: cloud_alpha = 1.000000; 14:58:24.188 [planetinfo.record]: cloud_color = 0.689262, 0.797383, 0.923828, 1; 14:58:24.188 [planetinfo.record]: cloud_fraction = 0.300000; 14:58:24.188 [planetinfo.record]: land_color = 0.103125, 0.425068, 0.66, 1; 14:58:24.188 [planetinfo.record]: land_fraction = 0.600000; 14:58:24.188 [planetinfo.record]: polar_cloud_color = 0.770405, 0.837388, 0.915723, 1; 14:58:24.188 [planetinfo.record]: polar_land_color = 0.736984, 0.850884, 0.934, 1; 14:58:24.188 [planetinfo.record]: polar_sea_color = 0.92572, 0.938477, 0.890453, 1; 14:58:24.188 [planetinfo.record]: sea_color = 0.581784, 0.615234, 0.489304, 1; 14:58:24.188 [planetinfo.record]: rotation_speed = 0.003792 14:58:24.188 [planetinfo.record]: planet zpos = 553960.000000 14:58:24.188 [planetinfo.record]: sun_radius = 124718.919067 14:58:24.188 [planetinfo.record]: sun_vector = -0.356 -0.220 0.908 14:58:24.188 [planetinfo.record]: sun_distance = 956840 14:58:24.188 [planetinfo.record]: corona_flare = 0.074933 14:58:24.188 [planetinfo.record]: corona_hues = 0.650665 14:58:24.188 [planetinfo.record]: sun_color = 0.78096, 0.472933, 0.376106, 1 14:58:24.189 [planetinfo.record]: corona_shimmer = 0.826702 14:58:24.189 [planetinfo.record]: station_vector = -0.527 0.565 0.635 14:58:24.189 [planetinfo.record]: station = coriolis 14:58:28.722 [PLANETINFO OVER]: Done 14:58:28.723 [PLANETINFO LOGGING]: 7 241 14:58:29.737 [planetinfo.record]: seed = 186 103 171 86 14:58:29.737 [planetinfo.record]: coordinates = 103 138 14:58:29.737 [planetinfo.record]: government = 7; 14:58:29.737 [planetinfo.record]: economy = 2; 14:58:29.737 [planetinfo.record]: techlevel = 12; 14:58:29.737 [planetinfo.record]: population = 58; 14:58:29.737 [planetinfo.record]: productivity = 40832; 14:58:29.737 [planetinfo.record]: name = "Veusbi"; 14:58:29.737 [planetinfo.record]: inhabitant = "Yellow Furry Insect"; 14:58:29.737 [planetinfo.record]: inhabitants = "Yellow Furry Insects"; 14:58:29.737 [planetinfo.record]: description = "This planet is fabled for its exciting Veusbiian evil brandy."; 14:58:29.752 [planetinfo.record]: air_color = 0.455583, 0.680084, 0.870873, 1; 14:58:29.752 [planetinfo.record]: cloud_alpha = 1.000000; 14:58:29.752 [planetinfo.record]: cloud_color = 0.10334, 0.615234, 0.303299, 1; 14:58:29.752 [planetinfo.record]: cloud_fraction = 0.250000; 14:58:29.752 [planetinfo.record]: land_color = 0.569766, 0.66, 0.621932, 1; 14:58:29.752 [planetinfo.record]: land_fraction = 0.700000; 14:58:29.752 [planetinfo.record]: polar_cloud_color = 0.372875, 0.776855, 0.53068, 1; 14:58:29.752 [planetinfo.record]: polar_land_color = 0.902076, 0.934, 0.920532, 1; 14:58:29.752 [planetinfo.record]: polar_sea_color = 0.880399, 0.922266, 0.85922, 1; 14:58:29.752 [planetinfo.record]: sea_color = 0.636194, 0.777344, 0.564789, 1; 14:58:29.752 [planetinfo.record]: rotation_speed = 0.002359 14:58:29.752 [planetinfo.record]: planet zpos = 579150.000000 14:58:29.752 [planetinfo.record]: sun_radius = 112359.999847 14:58:29.752 [planetinfo.record]: sun_vector = -0.109 -0.753 0.649 14:58:29.752 [planetinfo.record]: sun_distance = 891000 14:58:29.752 [planetinfo.record]: corona_flare = 0.094110 14:58:29.752 [planetinfo.record]: corona_hues = 0.846954 14:58:29.752 [planetinfo.record]: sun_color = 0.726257, 0.575954, 0.510603, 1 14:58:29.752 [planetinfo.record]: corona_shimmer = 0.455706 14:58:29.752 [planetinfo.record]: station_vector = -0.154 0.543 0.825 14:58:29.752 [planetinfo.record]: station = dodecahedron 14:58:34.511 [PLANETINFO OVER]: Done 14:58:34.512 [PLANETINFO LOGGING]: 7 242 14:58:35.515 [planetinfo.record]: seed = 24 166 133 245 14:58:35.515 [planetinfo.record]: coordinates = 166 6 14:58:35.515 [planetinfo.record]: government = 3; 14:58:35.515 [planetinfo.record]: economy = 6; 14:58:35.515 [planetinfo.record]: techlevel = 5; 14:58:35.515 [planetinfo.record]: population = 30; 14:58:35.515 [planetinfo.record]: productivity = 6720; 14:58:35.515 [planetinfo.record]: name = "Laxerive"; 14:58:35.516 [planetinfo.record]: inhabitant = "Slimy Frog"; 14:58:35.516 [planetinfo.record]: inhabitants = "Slimy Frogs"; 14:58:35.516 [planetinfo.record]: description = "This planet is fabled for its fabulous cuisine and its unusual tropical forests."; 14:58:35.534 [planetinfo.record]: air_color = 0.689742, 0.697555, 0.996398, 1; 14:58:35.535 [planetinfo.record]: cloud_alpha = 1.000000; 14:58:35.535 [planetinfo.record]: cloud_color = 0.670502, 0.690607, 0.992188, 1; 14:58:35.535 [planetinfo.record]: cloud_fraction = 0.380000; 14:58:35.535 [planetinfo.record]: land_color = 0.00257813, 0.66, 0.321017, 1; 14:58:35.535 [planetinfo.record]: land_fraction = 0.350000; 14:58:35.535 [planetinfo.record]: polar_cloud_color = 0.754692, 0.766679, 0.946484, 1; 14:58:35.535 [planetinfo.record]: polar_land_color = 0.701412, 0.934, 0.814072, 1; 14:58:35.535 [planetinfo.record]: polar_sea_color = 0.933398, 0.898618, 0.873238, 1; 14:58:35.535 [planetinfo.record]: sea_color = 0.666016, 0.566747, 0.494308, 1; 14:58:35.535 [planetinfo.record]: rotation_speed = 0.001661 14:58:35.535 [planetinfo.record]: planet zpos = 596680.000000 14:58:35.536 [planetinfo.record]: sun_radius = 103814.063416 14:58:35.536 [planetinfo.record]: sun_vector = 0.204 -0.630 0.749 14:58:35.536 [planetinfo.record]: sun_distance = 937640 14:58:35.536 [planetinfo.record]: corona_flare = 0.040408 14:58:35.536 [planetinfo.record]: corona_hues = 0.699394 14:58:35.536 [planetinfo.record]: sun_color = 0.302701, 0.372285, 0.74809, 1 14:58:35.536 [planetinfo.record]: corona_shimmer = 0.393383 14:58:35.537 [planetinfo.record]: station_vector = 0.156 0.920 0.359 14:58:35.537 [planetinfo.record]: station = coriolis 14:58:40.728 [PLANETINFO OVER]: Done 14:58:40.728 [PLANETINFO LOGGING]: 7 243 14:58:41.732 [planetinfo.record]: seed = 46 214 215 182 14:58:41.732 [planetinfo.record]: coordinates = 214 62 14:58:41.732 [planetinfo.record]: government = 5; 14:58:41.732 [planetinfo.record]: economy = 6; 14:58:41.732 [planetinfo.record]: techlevel = 6; 14:58:41.732 [planetinfo.record]: population = 36; 14:58:41.732 [planetinfo.record]: productivity = 10368; 14:58:41.732 [planetinfo.record]: name = "Vemaed"; 14:58:41.732 [planetinfo.record]: inhabitant = "Harmless Slimy Lizard"; 14:58:41.732 [planetinfo.record]: inhabitants = "Harmless Slimy Lizards"; 14:58:41.732 [planetinfo.record]: description = "The world Vemaed is notable for the Vemaedian tree grub and the Vemaedian spotted wolf."; 14:58:41.744 [planetinfo.record]: air_color = 0.412575, 0.767884, 0.880629, 1; 14:58:41.744 [planetinfo.record]: cloud_alpha = 1.000000; 14:58:41.744 [planetinfo.record]: cloud_color = 0.173053, 0.644531, 0.0025177, 1; 14:58:41.744 [planetinfo.record]: cloud_fraction = 0.310000; 14:58:41.744 [planetinfo.record]: land_color = 0.638267, 0.66, 0.446016, 1; 14:58:41.744 [planetinfo.record]: land_fraction = 0.690000; 14:58:41.744 [planetinfo.record]: polar_cloud_color = 0.42884, 0.790039, 0.298193, 1; 14:58:41.744 [planetinfo.record]: polar_land_color = 0.926311, 0.934, 0.858295, 1; 14:58:41.744 [planetinfo.record]: polar_sea_color = 0.940384, 0.860147, 0.941016, 1; 14:58:41.745 [planetinfo.record]: sea_color = 0.58826, 0.387085, 0.589844, 1; 14:58:41.745 [planetinfo.record]: rotation_speed = 0.002108 14:58:41.745 [planetinfo.record]: planet zpos = 547920.000000 14:58:41.745 [planetinfo.record]: sun_radius = 116580.843201 14:58:41.745 [planetinfo.record]: sun_vector = -0.011 0.952 0.306 14:58:41.745 [planetinfo.record]: sun_distance = 913200 14:58:41.745 [planetinfo.record]: corona_flare = 0.083400 14:58:41.745 [planetinfo.record]: corona_hues = 0.775902 14:58:41.745 [planetinfo.record]: sun_color = 0.657797, 0.381983, 0.301653, 1 14:58:41.745 [planetinfo.record]: corona_shimmer = 0.485985 14:58:41.745 [planetinfo.record]: station_vector = -0.842 0.512 0.171 14:58:41.745 [planetinfo.record]: station = coriolis 14:58:46.705 [PLANETINFO OVER]: Done 14:58:46.706 [PLANETINFO LOGGING]: 7 244 14:58:47.711 [planetinfo.record]: seed = 44 218 129 253 14:58:47.711 [planetinfo.record]: coordinates = 218 88 14:58:47.711 [planetinfo.record]: government = 5; 14:58:47.711 [planetinfo.record]: economy = 0; 14:58:47.711 [planetinfo.record]: techlevel = 12; 14:58:47.711 [planetinfo.record]: population = 54; 14:58:47.711 [planetinfo.record]: productivity = 38880; 14:58:47.711 [planetinfo.record]: name = "Isaso"; 14:58:47.711 [planetinfo.record]: inhabitant = "Horned Lobster"; 14:58:47.711 [planetinfo.record]: inhabitants = "Horned Lobsters"; 14:58:47.711 [planetinfo.record]: description = "This world is most notable for Isasoian lethal water but plagued by frequent solar activity."; 14:58:47.713 [planetinfo.record]: air_color = 0.654736, 0.59371, 0.848109, 1; 14:58:47.713 [planetinfo.record]: cloud_alpha = 1.000000; 14:58:47.713 [planetinfo.record]: cloud_color = 0.503182, 0.382385, 0.546875, 1; 14:58:47.713 [planetinfo.record]: cloud_fraction = 0.300000; 14:58:47.713 [planetinfo.record]: land_color = 0.512966, 0.66, 0.402188, 1; 14:58:47.713 [planetinfo.record]: land_fraction = 0.470000; 14:58:47.713 [planetinfo.record]: polar_cloud_color = 0.708838, 0.605837, 0.746094, 1; 14:58:47.713 [planetinfo.record]: polar_land_color = 0.881981, 0.934, 0.842789, 1; 14:58:47.713 [planetinfo.record]: polar_sea_color = 0.834752, 0.902671, 0.947656, 1; 14:58:47.713 [planetinfo.record]: sea_color = 0.273987, 0.424047, 0.523438, 1; 14:58:47.713 [planetinfo.record]: rotation_speed = 0.001985 14:58:47.713 [planetinfo.record]: planet zpos = 890680.000000 14:58:47.714 [planetinfo.record]: sun_radius = 187316.710815 14:58:47.714 [planetinfo.record]: sun_vector = 0.797 0.327 -0.508 14:58:47.714 [planetinfo.record]: sun_distance = 1463260 14:58:47.714 [planetinfo.record]: corona_flare = 0.053575 14:58:47.714 [planetinfo.record]: corona_hues = 0.970703 14:58:47.714 [planetinfo.record]: sun_color = 0.700473, 0.537485, 0.397105, 1 14:58:47.714 [planetinfo.record]: corona_shimmer = 0.296476 14:58:47.714 [planetinfo.record]: station_vector = 0.973 -0.168 0.161 14:58:47.714 [planetinfo.record]: station = dodecahedron 14:58:52.563 [PLANETINFO OVER]: Done 14:58:52.564 [PLANETINFO LOGGING]: 7 245 14:58:53.568 [planetinfo.record]: seed = 226 52 67 108 14:58:53.568 [planetinfo.record]: coordinates = 52 7 14:58:53.568 [planetinfo.record]: government = 4; 14:58:53.568 [planetinfo.record]: economy = 7; 14:58:53.568 [planetinfo.record]: techlevel = 2; 14:58:53.568 [planetinfo.record]: population = 20; 14:58:53.569 [planetinfo.record]: productivity = 3840; 14:58:53.569 [planetinfo.record]: name = "Inuses"; 14:58:53.569 [planetinfo.record]: inhabitant = "Human Colonial"; 14:58:53.569 [planetinfo.record]: inhabitants = "Human Colonials"; 14:58:53.569 [planetinfo.record]: description = "This planet is a dull world."; 14:58:53.572 [planetinfo.record]: air_color = 0.624491, 0.817453, 0.981439, 1; 14:58:53.572 [planetinfo.record]: cloud_alpha = 1.000000; 14:58:53.572 [planetinfo.record]: cloud_color = 0.481033, 0.947266, 0.663155, 1; 14:58:53.572 [planetinfo.record]: cloud_fraction = 0.150000; 14:58:53.572 [planetinfo.record]: land_color = 0.423276, 0.244922, 0.66, 1; 14:58:53.572 [planetinfo.record]: land_fraction = 0.410000; 14:58:53.573 [planetinfo.record]: polar_cloud_color = 0.641333, 0.92627, 0.752636, 1; 14:58:53.573 [planetinfo.record]: polar_land_color = 0.85025, 0.78715, 0.934, 1; 14:58:53.573 [planetinfo.record]: polar_sea_color = 0.930664, 0.844572, 0.842505, 1; 14:58:53.573 [planetinfo.record]: sea_color = 0.693359, 0.436799, 0.430641, 1; 14:58:53.573 [planetinfo.record]: rotation_speed = 0.001736 14:58:53.573 [planetinfo.record]: planet zpos = 831600.000000 14:58:53.573 [planetinfo.record]: sun_radius = 96940.118408 14:58:53.573 [planetinfo.record]: sun_vector = 0.475 -0.570 -0.670 14:58:53.573 [planetinfo.record]: sun_distance = 1188000 14:58:53.573 [planetinfo.record]: corona_flare = 0.056815 14:58:53.573 [planetinfo.record]: corona_hues = 0.941536 14:58:53.573 [planetinfo.record]: sun_color = 0.739095, 0.778572, 0.806781, 1 14:58:53.573 [planetinfo.record]: corona_shimmer = 0.276528 14:58:53.573 [planetinfo.record]: station_vector = 0.172 0.985 0.017 14:58:53.573 [planetinfo.record]: station = coriolis 14:58:59.106 [PLANETINFO OVER]: Done 14:58:59.107 [PLANETINFO LOGGING]: 7 246 14:59:00.112 [planetinfo.record]: seed = 192 94 61 81 14:59:00.112 [planetinfo.record]: coordinates = 94 73 14:59:00.112 [planetinfo.record]: government = 0; 14:59:00.112 [planetinfo.record]: economy = 3; 14:59:00.113 [planetinfo.record]: techlevel = 6; 14:59:00.113 [planetinfo.record]: population = 28; 14:59:00.113 [planetinfo.record]: productivity = 6272; 14:59:00.113 [planetinfo.record]: name = "Atoresra"; 14:59:00.113 [planetinfo.record]: inhabitant = "Human Colonial"; 14:59:00.113 [planetinfo.record]: inhabitants = "Human Colonials"; 14:59:00.113 [planetinfo.record]: description = "This planet is reasonably famous for its inhabitants’ exceptional love for tourists."; 14:59:00.133 [planetinfo.record]: air_color = 0.58721, 0.522748, 0.902742, 1; 14:59:00.133 [planetinfo.record]: cloud_alpha = 1.000000; 14:59:00.133 [planetinfo.record]: cloud_color = 0.485949, 0.238831, 0.710938, 1; 14:59:00.133 [planetinfo.record]: cloud_fraction = 0.520000; 14:59:00.133 [planetinfo.record]: land_color = 0.0605416, 0.654297, 0.0102234, 1; 14:59:00.133 [planetinfo.record]: land_fraction = 0.550000; 14:59:00.133 [planetinfo.record]: polar_cloud_color = 0.657748, 0.479622, 0.819922, 1; 14:59:00.133 [planetinfo.record]: polar_land_color = 0.722547, 0.93457, 0.704578, 1; 14:59:00.133 [planetinfo.record]: polar_sea_color = 0.941797, 0.883394, 0.883394, 1; 14:59:00.133 [planetinfo.record]: sea_color = 0.582031, 0.43766, 0.43766, 1; 14:59:00.134 [planetinfo.record]: rotation_speed = 0.004699 14:59:00.134 [planetinfo.record]: planet zpos = 284940.000000 14:59:00.134 [planetinfo.record]: sun_radius = 69520.502014 14:59:00.134 [planetinfo.record]: sun_vector = -0.477 0.440 0.761 14:59:00.134 [planetinfo.record]: sun_distance = 601540 14:59:00.134 [planetinfo.record]: corona_flare = 0.021695 14:59:00.134 [planetinfo.record]: corona_hues = 0.540611 14:59:00.134 [planetinfo.record]: sun_color = 0.680359, 0.630518, 0.330639, 1 14:59:00.134 [planetinfo.record]: corona_shimmer = 0.255754 14:59:00.134 [planetinfo.record]: station_vector = -0.920 0.193 0.342 14:59:00.134 [planetinfo.record]: station = coriolis 14:59:04.849 [PLANETINFO OVER]: Done 14:59:04.850 [PLANETINFO LOGGING]: 7 247 14:59:05.853 [planetinfo.record]: seed = 214 244 111 152 14:59:05.853 [planetinfo.record]: coordinates = 244 169 14:59:05.853 [planetinfo.record]: government = 2; 14:59:05.853 [planetinfo.record]: economy = 1; 14:59:05.853 [planetinfo.record]: techlevel = 7; 14:59:05.853 [planetinfo.record]: population = 32; 14:59:05.853 [planetinfo.record]: productivity = 13824; 14:59:05.853 [planetinfo.record]: name = "Edtizabe"; 14:59:05.853 [planetinfo.record]: inhabitant = "Human Colonial"; 14:59:05.853 [planetinfo.record]: inhabitants = "Human Colonials"; 14:59:05.853 [planetinfo.record]: description = "The world Edtizabe is beset by lethal disease."; 14:59:05.868 [planetinfo.record]: air_color = 0.682193, 0.794157, 0.951522, 1; 14:59:05.868 [planetinfo.record]: cloud_alpha = 1.000000; 14:59:05.868 [planetinfo.record]: cloud_color = 0.643066, 0.857422, 0.807182, 1; 14:59:05.868 [planetinfo.record]: cloud_fraction = 0.470000; 14:59:05.868 [planetinfo.record]: land_color = 0.646827, 0.644531, 0.66, 1; 14:59:05.868 [planetinfo.record]: land_fraction = 0.690000; 14:59:05.868 [planetinfo.record]: polar_cloud_color = 0.747427, 0.88584, 0.853399, 1; 14:59:05.868 [planetinfo.record]: polar_land_color = 0.92934, 0.928527, 0.934, 1; 14:59:05.868 [planetinfo.record]: polar_sea_color = 0.92168, 0.865003, 0.81007, 1; 14:59:05.868 [planetinfo.record]: sea_color = 0.783203, 0.590557, 0.403839, 1; 14:59:05.868 [planetinfo.record]: rotation_speed = 0.001473 14:59:05.868 [planetinfo.record]: planet zpos = 664040.000000 14:59:05.868 [planetinfo.record]: sun_radius = 160971.835938 14:59:05.868 [planetinfo.record]: sun_vector = -0.939 -0.061 0.338 14:59:05.868 [planetinfo.record]: sun_distance = 868360 14:59:05.869 [planetinfo.record]: corona_flare = 0.069016 14:59:05.869 [planetinfo.record]: corona_hues = 0.955963 14:59:05.869 [planetinfo.record]: sun_color = 0.74577, 0.632621, 0.574958, 1 14:59:05.869 [planetinfo.record]: corona_shimmer = 0.379738 14:59:05.869 [planetinfo.record]: station_vector = 0.010 -0.861 0.509 14:59:05.869 [planetinfo.record]: station = coriolis 14:59:09.932 [PLANETINFO OVER]: Done 14:59:09.933 [PLANETINFO LOGGING]: 7 248 14:59:10.936 [planetinfo.record]: seed = 212 147 57 143 14:59:10.937 [planetinfo.record]: coordinates = 147 196 14:59:10.937 [planetinfo.record]: government = 2; 14:59:10.937 [planetinfo.record]: economy = 4; 14:59:10.937 [planetinfo.record]: techlevel = 7; 14:59:10.937 [planetinfo.record]: population = 35; 14:59:10.937 [planetinfo.record]: productivity = 10080; 14:59:10.937 [planetinfo.record]: name = "Asoarle"; 14:59:10.937 [planetinfo.record]: inhabitant = "Human Colonial"; 14:59:10.937 [planetinfo.record]: inhabitants = "Human Colonials"; 14:59:10.937 [planetinfo.record]: description = "Asoarle is well known for its inhabitants’ weird silliness but cursed by deadly civil war."; 14:59:10.956 [planetinfo.record]: air_color = 0.71342, 0.772231, 0.946969, 1; 14:59:10.956 [planetinfo.record]: cloud_alpha = 1.000000; 14:59:10.956 [planetinfo.record]: cloud_color = 0.725098, 0.81316, 0.84375, 1; 14:59:10.956 [planetinfo.record]: cloud_fraction = 0.340000; 14:59:10.956 [planetinfo.record]: land_color = 0.443438, 0.538184, 0.66, 1; 14:59:10.956 [planetinfo.record]: land_fraction = 0.580000; 14:59:10.956 [planetinfo.record]: polar_cloud_color = 0.802371, 0.859754, 0.879687, 1; 14:59:10.956 [planetinfo.record]: polar_land_color = 0.857383, 0.890903, 0.934, 1; 14:59:10.956 [planetinfo.record]: polar_sea_color = 0.930666, 0.939844, 0.892852, 1; 14:59:10.956 [planetinfo.record]: sea_color = 0.578064, 0.601562, 0.48125, 1; 14:59:10.956 [planetinfo.record]: rotation_speed = 0.000048 14:59:10.956 [planetinfo.record]: planet zpos = 748330.000000 14:59:10.956 [planetinfo.record]: sun_radius = 181241.362152 14:59:10.956 [planetinfo.record]: sun_vector = 0.679 -0.078 0.730 14:59:10.956 [planetinfo.record]: sun_distance = 1292570 14:59:10.956 [planetinfo.record]: corona_flare = 0.053181 14:59:10.956 [planetinfo.record]: corona_hues = 0.978706 14:59:10.956 [planetinfo.record]: sun_color = 0.291211, 0.432246, 0.755753, 1 14:59:10.956 [planetinfo.record]: corona_shimmer = 0.311495 14:59:10.956 [planetinfo.record]: station_vector = -0.585 0.723 0.367 14:59:10.956 [planetinfo.record]: station = coriolis 14:59:15.653 [PLANETINFO OVER]: Done 14:59:15.654 [PLANETINFO LOGGING]: 7 249 14:59:16.674 [planetinfo.record]: seed = 10 129 219 114 14:59:16.674 [planetinfo.record]: coordinates = 129 10 14:59:16.674 [planetinfo.record]: government = 1; 14:59:16.674 [planetinfo.record]: economy = 2; 14:59:16.674 [planetinfo.record]: techlevel = 7; 14:59:16.674 [planetinfo.record]: population = 32; 14:59:16.674 [planetinfo.record]: productivity = 10240; 14:59:16.674 [planetinfo.record]: name = "Enrienge"; 14:59:16.675 [planetinfo.record]: inhabitant = "Blue Bony Humanoid"; 14:59:16.675 [planetinfo.record]: inhabitants = "Blue Bony Humanoids"; 14:59:16.675 [planetinfo.record]: description = "This world is ravaged by occasional solar activity."; 14:59:16.691 [planetinfo.record]: air_color = 0.568911, 0.562821, 0.848109, 1; 14:59:16.691 [planetinfo.record]: cloud_alpha = 1.000000; 14:59:16.691 [planetinfo.record]: cloud_color = 0.340095, 0.322571, 0.546875, 1; 14:59:16.691 [planetinfo.record]: cloud_fraction = 0.250000; 14:59:16.691 [planetinfo.record]: land_color = 0.572266, 0.453789, 0.498218, 1; 14:59:16.691 [planetinfo.record]: land_fraction = 0.270000; 14:59:16.691 [planetinfo.record]: polar_cloud_color = 0.569777, 0.554834, 0.746094, 1; 14:59:16.691 [planetinfo.record]: polar_land_color = 0.942773, 0.893978, 0.912276, 1; 14:59:16.691 [planetinfo.record]: polar_sea_color = 0.890058, 0.924023, 0.864467, 1; 14:59:16.691 [planetinfo.record]: sea_color = 0.648054, 0.759766, 0.563889, 1; 14:59:16.691 [planetinfo.record]: rotation_speed = 0.001126 14:59:16.692 [planetinfo.record]: planet zpos = 449410.000000 14:59:16.692 [planetinfo.record]: sun_radius = 92365.136261 14:59:16.692 [planetinfo.record]: sun_vector = -0.467 0.884 0.000 14:59:16.692 [planetinfo.record]: sun_distance = 725970 14:59:16.692 [planetinfo.record]: corona_flare = 0.060764 14:59:16.692 [planetinfo.record]: corona_hues = 0.536392 14:59:16.692 [planetinfo.record]: sun_color = 0.621198, 0.652981, 0.796759, 1 14:59:16.692 [planetinfo.record]: corona_shimmer = 0.577674 14:59:16.692 [planetinfo.record]: station_vector = -0.089 0.669 0.738 14:59:16.692 [planetinfo.record]: station = coriolis 14:59:21.931 [PLANETINFO OVER]: Done 14:59:21.932 [PLANETINFO LOGGING]: 7 250 14:59:22.934 [planetinfo.record]: seed = 104 99 245 83 14:59:22.934 [planetinfo.record]: coordinates = 99 242 14:59:22.934 [planetinfo.record]: government = 5; 14:59:22.934 [planetinfo.record]: economy = 2; 14:59:22.934 [planetinfo.record]: techlevel = 11; 14:59:22.935 [planetinfo.record]: population = 52; 14:59:22.935 [planetinfo.record]: productivity = 29952; 14:59:22.935 [planetinfo.record]: name = "Bees"; 14:59:22.935 [planetinfo.record]: inhabitant = "Yellow Bug-Eyed Bird"; 14:59:22.935 [planetinfo.record]: inhabitants = "Yellow Bug-Eyed Birds"; 14:59:22.935 [planetinfo.record]: description = "This planet is noted for Zero-G hockey."; 14:59:22.949 [planetinfo.record]: air_color = 0.644663, 0.819718, 0.963879, 1; 14:59:22.950 [planetinfo.record]: cloud_alpha = 1.000000; 14:59:22.950 [planetinfo.record]: cloud_color = 0.541611, 0.894531, 0.671199, 1; 14:59:22.950 [planetinfo.record]: cloud_fraction = 0.490000; 14:59:22.950 [planetinfo.record]: land_color = 0.66, 0.555425, 0.355781, 1; 14:59:22.950 [planetinfo.record]: land_fraction = 0.630000; 14:59:22.950 [planetinfo.record]: polar_cloud_color = 0.679989, 0.902539, 0.761707, 1; 14:59:22.950 [planetinfo.record]: polar_land_color = 0.934, 0.897003, 0.826371, 1; 14:59:22.950 [planetinfo.record]: polar_sea_color = 0.929883, 0.904875, 0.861776, 1; 14:59:22.950 [planetinfo.record]: sea_color = 0.701172, 0.625744, 0.49575, 1; 14:59:22.950 [planetinfo.record]: rotation_speed = 0.002826 14:59:22.950 [planetinfo.record]: planet zpos = 405130.000000 14:59:22.950 [planetinfo.record]: sun_radius = 102520.544586 14:59:22.951 [planetinfo.record]: sun_vector = 0.126 -0.992 -0.010 14:59:22.951 [planetinfo.record]: sun_distance = 736600 14:59:22.951 [planetinfo.record]: corona_flare = 0.015305 14:59:22.951 [planetinfo.record]: corona_hues = 0.959389 14:59:22.951 [planetinfo.record]: sun_color = 0.801944, 0.485829, 0.317949, 1 14:59:22.951 [planetinfo.record]: corona_shimmer = 0.655059 14:59:22.951 [planetinfo.record]: station_vector = -0.080 0.983 0.165 14:59:22.951 [planetinfo.record]: station = dodecahedron 14:59:27.886 [PLANETINFO OVER]: Done 14:59:27.887 [PLANETINFO LOGGING]: 7 251 14:59:28.890 [planetinfo.record]: seed = 126 94 7 105 14:59:28.890 [planetinfo.record]: coordinates = 94 96 14:59:28.890 [planetinfo.record]: government = 7; 14:59:28.890 [planetinfo.record]: economy = 0; 14:59:28.890 [planetinfo.record]: techlevel = 13; 14:59:28.890 [planetinfo.record]: population = 60; 14:59:28.890 [planetinfo.record]: productivity = 52800; 14:59:28.890 [planetinfo.record]: name = "Esusale"; 14:59:28.890 [planetinfo.record]: inhabitant = "Human Colonial"; 14:59:28.890 [planetinfo.record]: inhabitants = "Human Colonials"; 14:59:28.890 [planetinfo.record]: description = "This planet is fabled for its ancient Esusaleian A’nuet tulip plantations."; 14:59:28.912 [planetinfo.record]: air_color = 0.685135, 0.814494, 0.935262, 1; 14:59:28.912 [planetinfo.record]: cloud_alpha = 1.000000; 14:59:28.912 [planetinfo.record]: cloud_color = 0.644348, 0.808594, 0.720055, 1; 14:59:28.912 [planetinfo.record]: cloud_fraction = 0.160000; 14:59:28.912 [planetinfo.record]: land_color = 0.492931, 0.345581, 0.59375, 1; 14:59:28.912 [planetinfo.record]: land_fraction = 0.320000; 14:59:28.912 [planetinfo.record]: polar_cloud_color = 0.754197, 0.863867, 0.804748, 1; 14:59:28.912 [planetinfo.record]: polar_land_color = 0.900696, 0.842337, 0.940625, 1; 14:59:28.912 [planetinfo.record]: polar_sea_color = 0.949805, 0.871811, 0.737397, 1; 14:59:28.912 [planetinfo.record]: sea_color = 0.501953, 0.337081, 0.0529404, 1; 14:59:28.912 [planetinfo.record]: rotation_speed = 0.000804 14:59:28.912 [planetinfo.record]: planet zpos = 469260.000000 14:59:28.912 [planetinfo.record]: sun_radius = 149832.487793 14:59:28.912 [planetinfo.record]: sun_vector = -0.533 -0.256 -0.806 14:59:28.912 [planetinfo.record]: sun_distance = 1042800 14:59:28.912 [planetinfo.record]: corona_flare = 0.091373 14:59:28.912 [planetinfo.record]: corona_hues = 0.751854 14:59:28.912 [planetinfo.record]: sun_color = 0.388097, 0.467169, 0.829697, 1 14:59:28.912 [planetinfo.record]: corona_shimmer = 0.292718 14:59:28.913 [planetinfo.record]: station_vector = -0.991 -0.087 0.098 14:59:28.913 [planetinfo.record]: station = dodecahedron 14:59:33.853 [PLANETINFO OVER]: Done 14:59:33.854 [PLANETINFO LOGGING]: 7 252 14:59:34.858 [planetinfo.record]: seed = 124 129 241 153 14:59:34.858 [planetinfo.record]: coordinates = 129 239 14:59:34.858 [planetinfo.record]: government = 7; 14:59:34.858 [planetinfo.record]: economy = 7; 14:59:34.858 [planetinfo.record]: techlevel = 5; 14:59:34.858 [planetinfo.record]: population = 35; 14:59:34.858 [planetinfo.record]: productivity = 9240; 14:59:34.858 [planetinfo.record]: name = "Ormabiin"; 14:59:34.858 [planetinfo.record]: inhabitant = "Black Insect"; 14:59:34.858 [planetinfo.record]: inhabitants = "Black Insects"; 14:59:34.858 [planetinfo.record]: description = "Ormabiin is reasonably notable for its great volcanoes but plagued by lethal spotted batoids."; 14:59:34.876 [planetinfo.record]: air_color = 0.786514, 0.47278, 0.989895, 1; 14:59:34.876 [planetinfo.record]: cloud_alpha = 1.000000; 14:59:34.876 [planetinfo.record]: cloud_color = 0.972656, 0.0303955, 0.184985, 1; 14:59:34.876 [planetinfo.record]: cloud_fraction = 0.460000; 14:59:34.876 [planetinfo.record]: land_color = 0.500156, 0.66, 0.495, 1; 14:59:34.876 [planetinfo.record]: land_fraction = 0.510000; 14:59:34.876 [planetinfo.record]: polar_cloud_color = 0.937695, 0.36995, 0.463096, 1; 14:59:34.876 [planetinfo.record]: polar_land_color = 0.877449, 0.934, 0.875625, 1; 14:59:34.876 [planetinfo.record]: polar_sea_color = 0.928125, 0.836277, 0.82933, 1; 14:59:34.876 [planetinfo.record]: sea_color = 0.71875, 0.434237, 0.41272, 1; 14:59:34.876 [planetinfo.record]: rotation_speed = 0.003175 14:59:34.876 [planetinfo.record]: planet zpos = 682370.000000 14:59:34.876 [planetinfo.record]: sun_radius = 171141.139679 14:59:34.876 [planetinfo.record]: sun_vector = 0.037 -0.853 -0.521 14:59:34.876 [planetinfo.record]: sun_distance = 1049800 14:59:34.876 [planetinfo.record]: corona_flare = 0.038625 14:59:34.876 [planetinfo.record]: corona_hues = 0.988251 14:59:34.876 [planetinfo.record]: sun_color = 0.463443, 0.683214, 0.690717, 1 14:59:34.877 [planetinfo.record]: corona_shimmer = 0.338352 14:59:34.877 [planetinfo.record]: station_vector = -0.580 -0.590 0.562 14:59:34.877 [planetinfo.record]: station = coriolis 14:59:39.786 [PLANETINFO OVER]: Done 14:59:39.787 [PLANETINFO LOGGING]: 7 253 14:59:40.790 [planetinfo.record]: seed = 50 204 115 254 14:59:40.790 [planetinfo.record]: coordinates = 204 38 14:59:40.790 [planetinfo.record]: government = 6; 14:59:40.790 [planetinfo.record]: economy = 6; 14:59:40.790 [planetinfo.record]: techlevel = 4; 14:59:40.790 [planetinfo.record]: population = 29; 14:59:40.790 [planetinfo.record]: productivity = 9280; 14:59:40.790 [planetinfo.record]: name = "Riattein"; 14:59:40.790 [planetinfo.record]: inhabitant = "Human Colonial"; 14:59:40.790 [planetinfo.record]: inhabitants = "Human Colonials"; 14:59:40.790 [planetinfo.record]: description = "This planet is plagued by occasional solar activity."; 14:59:40.797 [planetinfo.record]: air_color = 0.717472, 0.619559, 0.84876, 1; 14:59:40.797 [planetinfo.record]: cloud_alpha = 1.000000; 14:59:40.797 [planetinfo.record]: cloud_color = 0.548828, 0.43306, 0.514459, 1; 14:59:40.797 [planetinfo.record]: cloud_fraction = 0.380000; 14:59:40.797 [planetinfo.record]: land_color = 0.381563, 0.66, 0.601267, 1; 14:59:40.797 [planetinfo.record]: land_fraction = 0.340000; 14:59:40.797 [planetinfo.record]: polar_cloud_color = 0.746973, 0.648495, 0.717737, 1; 14:59:40.798 [planetinfo.record]: polar_land_color = 0.835492, 0.934, 0.913221, 1; 14:59:40.798 [planetinfo.record]: polar_sea_color = 0.901715, 0.934375, 0.885193, 1; 14:59:40.798 [planetinfo.record]: sea_color = 0.564496, 0.65625, 0.518079, 1; 14:59:40.798 [planetinfo.record]: rotation_speed = 0.000577 14:59:40.798 [planetinfo.record]: planet zpos = 792480.000000 14:59:40.798 [planetinfo.record]: sun_radius = 172786.662598 14:59:40.798 [planetinfo.record]: sun_vector = 0.401 0.697 -0.595 14:59:40.798 [planetinfo.record]: sun_distance = 1188720 14:59:40.798 [planetinfo.record]: corona_flare = 0.014737 14:59:40.798 [planetinfo.record]: corona_hues = 0.586357 14:59:40.798 [planetinfo.record]: sun_color = 0.665342, 0.814596, 0.823056, 1 14:59:40.798 [planetinfo.record]: corona_shimmer = 0.371502 14:59:40.798 [planetinfo.record]: station_vector = -0.573 -0.813 0.105 14:59:40.798 [planetinfo.record]: station = coriolis 14:59:46.486 [PLANETINFO OVER]: Done 14:59:46.487 [PLANETINFO LOGGING]: 7 254 14:59:47.490 [planetinfo.record]: seed = 16 172 173 89 14:59:47.490 [planetinfo.record]: coordinates = 172 188 14:59:47.490 [planetinfo.record]: government = 2; 14:59:47.490 [planetinfo.record]: economy = 4; 14:59:47.490 [planetinfo.record]: techlevel = 4; 14:59:47.490 [planetinfo.record]: population = 23; 14:59:47.490 [planetinfo.record]: productivity = 6624; 14:59:47.490 [planetinfo.record]: name = "Orleso"; 14:59:47.490 [planetinfo.record]: inhabitant = "Yellow Slimy Frog"; 14:59:47.490 [planetinfo.record]: inhabitants = "Yellow Slimy Frogs"; 14:59:47.490 [planetinfo.record]: description = "Orleso is reasonably well known for its great tropical forests but scourged by deadly civil war."; 14:59:47.502 [planetinfo.record]: air_color = 0.703916, 0.768875, 0.954123, 1; 14:59:47.502 [planetinfo.record]: cloud_alpha = 1.000000; 14:59:47.502 [planetinfo.record]: cloud_color = 0.703003, 0.827211, 0.865234, 1; 14:59:47.502 [planetinfo.record]: cloud_fraction = 0.530000; 14:59:47.502 [planetinfo.record]: land_color = 0.66, 0.557781, 0.286172, 1; 14:59:47.502 [planetinfo.record]: land_fraction = 0.290000; 14:59:47.503 [planetinfo.record]: polar_cloud_color = 0.785134, 0.864929, 0.889355, 1; 14:59:47.503 [planetinfo.record]: polar_land_color = 0.934, 0.897836, 0.801744, 1; 14:59:47.503 [planetinfo.record]: polar_sea_color = 0.929492, 0.909551, 0.862322, 1; 14:59:47.503 [planetinfo.record]: sea_color = 0.705078, 0.644572, 0.501266, 1; 14:59:47.503 [planetinfo.record]: rotation_speed = 0.000965 14:59:47.503 [planetinfo.record]: planet zpos = 793800.000000 14:59:47.503 [planetinfo.record]: sun_radius = 123166.422729 14:59:47.503 [planetinfo.record]: sun_vector = 0.886 0.331 0.326 14:59:47.503 [planetinfo.record]: sun_distance = 899640 14:59:47.503 [planetinfo.record]: corona_flare = 0.000233 14:59:47.503 [planetinfo.record]: corona_hues = 0.565956 14:59:47.503 [planetinfo.record]: sun_color = 0.40605, 0.73816, 0.783035, 1 14:59:47.503 [planetinfo.record]: corona_shimmer = 0.909035 14:59:47.503 [planetinfo.record]: station_vector = -0.277 -0.400 0.874 14:59:47.503 [planetinfo.record]: station = coriolis 14:59:52.588 [PLANETINFO OVER]: Done 14:59:52.588 [PLANETINFO LOGGING]: 7 255 14:59:53.593 [planetinfo.record]: seed = 38 227 159 108 14:59:53.593 [planetinfo.record]: coordinates = 227 199 14:59:53.593 [planetinfo.record]: government = 4; 14:59:53.593 [planetinfo.record]: economy = 7; 14:59:53.593 [planetinfo.record]: techlevel = 5; 14:59:53.593 [planetinfo.record]: population = 32; 14:59:53.593 [planetinfo.record]: productivity = 6144; 14:59:53.593 [planetinfo.record]: name = "Intiso"; 14:59:53.594 [planetinfo.record]: inhabitant = "Blue Fat Bird"; 14:59:53.594 [planetinfo.record]: inhabitants = "Blue Fat Birds"; 14:59:53.594 [planetinfo.record]: description = "The planet Intiso is most famous for the Intisoian spotted shrew and the Intisoian edible poet."; 14:59:53.606 [planetinfo.record]: air_color = 0.78584, 0.483105, 0.98209, 1; 14:59:53.616 [planetinfo.record]: cloud_alpha = 1.000000; 14:59:53.616 [planetinfo.record]: cloud_color = 0.949219, 0.0704498, 0.214623, 1; 14:59:53.616 [planetinfo.record]: cloud_fraction = 0.430000; 14:59:53.616 [planetinfo.record]: land_color = 0.446016, 0.514558, 0.66, 1; 14:59:53.616 [planetinfo.record]: land_fraction = 0.550000; 14:59:53.616 [planetinfo.record]: polar_cloud_color = 0.927148, 0.390688, 0.478701, 1; 14:59:53.616 [planetinfo.record]: polar_land_color = 0.858295, 0.882544, 0.934, 1; 14:59:53.616 [planetinfo.record]: polar_sea_color = 0.941211, 0.902546, 0.885602, 1; 14:59:53.616 [planetinfo.record]: sea_color = 0.587891, 0.491287, 0.448956, 1; 14:59:53.616 [planetinfo.record]: rotation_speed = 0.000201 14:59:53.616 [planetinfo.record]: planet zpos = 733800.000000 14:59:53.616 [planetinfo.record]: sun_radius = 206732.086182 14:59:53.616 [planetinfo.record]: sun_vector = 0.275 0.943 0.187 14:59:53.616 [planetinfo.record]: sun_distance = 1100700 14:59:53.616 [planetinfo.record]: corona_flare = 0.042746 14:59:53.616 [planetinfo.record]: corona_hues = 0.733376 14:59:53.616 [planetinfo.record]: sun_color = 0.282424, 0.353239, 0.650851, 1 14:59:53.616 [planetinfo.record]: corona_shimmer = 0.375354 14:59:53.617 [planetinfo.record]: station_vector = 0.966 0.237 0.108 14:59:53.617 [planetinfo.record]: station = coriolis 14:59:58.321 [PLANETINFO OVER]: Done oolite-1.82/tools/planetinfo-source/README.md000066400000000000000000000014141256642440500210230ustar00rootroot00000000000000# Planetinfo creator * `1.80-planetinfo-data-extract.log` is the (relevant) log lines from running 26c815b3 in OO_DUMP_PLANETINFO mode for every system. * `radii.txt` separate file containing planet radii in km because I forgot to include that value in the log dump and don't want to spend four hours regenerating it * `seeds.txt` separate file containing system seed strings, same reason * `planetlog.pl` is a tool to convert this into a planetinfo.plist file The log is saved here because even with the debugging tools enabled by OO_DUMP_PLANETINFO it still takes about four hours to generate it. If we want to change the generation rules a bit later (e.g. modify distance ratios, or planet colour schemes) then it may be easier to modify planetlog.pl and regenerate. oolite-1.82/tools/planetinfo-source/planetlog.pl000077500000000000000000000056321256642440500220770ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; my $begin = 0; my @accum = ""; my @radii; open(my $RADIILIST, ") { chomp; push(@radii,$_); } close($RADIILIST); my @seeds; open(my $SEEDLIST, ") { chomp; push(@seeds,$_); } close($SEEDLIST); print ("{\n\n"); print (' "interstellar space" = { sky_color_1 = (0, 1, 0.5); sky_color_2 = (0, 1, 0); nebula_color_1 = (0, 1, 0.5); nebula_color_2 = (0, 1, 0); sky_n_stars = 2048; sky_n_blurs = 256; }; "universal" = { sky_color_1 = (0.75,0.8,1); sky_color_2 = (1.0,0.85,0.6); stations_require_docking_clearance = no; }; // Uncomment the desired behaviour for galactic hyperspace exit. Fixed coordinates will put the arrival // of an intergalactic jump on map coordinates specified by the key galactic_hyperspace_fixed_coords. "galactic_hyperspace_behaviour" = "BEHAVIOUR_STANDARD"; //"BEHAVIOUR_ALL_SYSTEMS_REACHABLE"; //"BEHAVIOUR_FIXED_COORDINATES"; // When using BEHAVIOUR_FIXED_COORDINATES, the key below is used to specify the // actual fixed coordinates for the intergalactic jump. "galactic_hyperspace_fixed_coords" = "96 96"; hyperspace_tunnel_color_1 = (1.0, 1.0, 1.0, 0.7); // R, G, B, A values 0.0 to 1.0 //hyperspace_tunnel_color_1 = (1.0, 0.0, 0.0, 0.5); // fallback value, same as docking tunnel //hyperspace_tunnel_color_2 = (0.0, 0.0, 1.0, 0.25); // fallback value, same as docking tunnel /* This setting controls the minimum charge level of the energy banks before the shields start charging. It\'s taken as a percentage value from 0.0 (0%) to 1.0 (100%). If the energy banks are less than this, energy is first used to charge the banks, and only then the shields. Default: 0.0 */ //shield_charge_energybank_threshold = 0.75; '); while (<>) { chomp; if (/PLANETINFO LOGGING/) { if ($begin == 0) { $begin = 1; } s/.*PLANETINFO LOGGING]: //; print ("\t".'"'.$_.'" = {'."\n"); @accum = (); } elsif ($begin && /planetinfo.record/) { s/.*planetinfo.record]: //; my $record = ""; my $line = $_; $line =~/([a-z_ ]+) = (.*)/; my $key = $1; my $val = $2; $val =~s/;$//; if ($key =~/color/) { $val =~s/,//g; } $key =~s/planet zpos/planet_distance/; $key =~s/seed/random_seed/; $record .= ("\t\t".$key." = "); if ($key eq "random_seed") { $val = shift(@seeds); $record .= ('"'.$val.'"'); } elsif ($val !~/"/ && $val =~/[^0-9.]/) { $record .= ('"'.$val.'"'); } else { $record .= ($val); } $record .= (";\n"); push(@accum,$record); } elsif ($begin && /PLANETINFO OVER/) { push(@accum,"\t\tsky_n_stars = ".(5000+int(rand(5000))).";\n"); push(@accum,"\t\tsky_n_blurs = ".(40+int(rand(160))).";\n"); push(@accum,"\t\tradius = ".shift(@radii).";\n"); push(@accum,"\t\tlayer = 0;\n"); @accum = sort(@accum); print (join("",@accum)); print ("\t};\n\n"); $begin = 0; } } print ("\n}\n"); oolite-1.82/tools/planetinfo-source/radii.txt000066400000000000000000000240001256642440500213710ustar00rootroot000000000000004610 5528 3149 4435 3508 4780 3909 4116 4076 6360 5892 6493 5364 6740 4290 4341 6578 3127 6102 6662 6575 4731 6074 6678 4114 4597 3076 5390 6820 3168 4972 3381 5326 5970 6092 6196 6549 5957 5146 6419 5540 3727 5743 5178 5185 4712 6403 3697 4949 5866 6701 6879 3432 3851 5222 3085 3746 4004 4934 6371 4810 3180 5253 5032 5705 6909 4859 4102 5415 3021 6559 4867 2828 5429 5514 4103 5183 2924 4852 3036 3497 4492 3380 6568 4562 2955 4547 3572 3298 3650 6713 3240 6816 3433 5454 6156 6261 3223 3545 6087 4457 4165 3796 3298 4387 3019 5205 3013 5357 4961 2964 6455 6061 2846 4331 6497 5612 6395 3792 4043 6865 3280 4572 3933 4646 3413 5831 3679 6737 3873 6248 3960 3419 5293 5048 5809 6123 4689 5071 6002 5195 3141 5349 5762 4449 5792 4945 6155 6582 5212 2957 4243 6257 2894 6191 4867 6747 3633 6127 5026 3293 4507 5031 5657 6652 6406 6221 3952 3427 6344 4858 4623 5208 5145 3558 5310 3012 4115 5736 6308 3375 4524 6393 4682 5825 6589 3889 5784 4673 3837 6344 6613 3608 5126 3734 3755 5454 3406 3474 6432 5003 3886 4565 3740 5142 3549 5783 4585 5592 2933 6447 5933 4441 3564 5398 5105 5313 6171 5860 6685 6871 4026 6481 3833 4100 5728 6452 5420 4432 4742 4743 4799 6498 5508 4192 6426 5252 5522 4087 3844 3996 5575 3238 6054 5427 6684 4835 5512 4720 5737 3399 3474 4637 3942 6794 5132 6660 5685 3783 5791 4336 4405 5279 5337 5289 3109 5317 5858 4032 4212 6057 5451 6148 5203 5625 6500 6278 4530 5544 6460 5781 3647 4068 3748 3009 5229 3101 3883 3804 2921 5124 5409 6387 6694 3721 6039 4184 6482 4827 3421 3995 4445 6506 4098 3211 6520 3047 6358 4153 6802 3905 3306 4852 2908 3497 5390 2893 5445 3727 6865 4627 4222 6818 5252 4183 5110 6354 6710 3176 5726 4176 5303 4055 4645 2956 2968 4211 3708 4404 5929 6221 4947 6459 3961 3764 6232 3023 3161 2872 5886 4192 5464 5803 5490 4255 5062 6170 3239 4732 3508 6359 4939 3622 3058 3954 5070 3341 5647 5306 4961 6114 5723 4288 3315 5012 4839 6867 5429 6484 4995 2948 6038 4754 4286 6818 6727 5885 4841 4158 5431 3205 4115 5287 4119 3163 4620 3950 5206 4079 4453 6242 6181 3567 5486 6292 5492 3406 5430 5715 4849 5946 6798 2863 6159 5155 2821 3834 4091 3258 4587 6337 3752 5871 4945 4310 3524 6897 6407 4041 5567 4400 3228 3177 3530 4957 5216 4295 3796 5991 6244 4658 3982 5503 4472 2875 6503 5653 5675 4785 5264 4824 4302 4261 6136 4280 5744 3174 6736 6374 4321 3973 5639 5074 6066 4305 4686 6442 4915 6235 5909 4577 2931 3698 3082 4901 6211 2982 6816 6246 4402 5833 6404 6229 3984 6376 4137 6882 6510 6229 6077 5692 6044 4000 5936 5587 4777 5272 6067 3928 4837 4174 4149 3002 3681 6673 6503 4394 4077 2930 3000 6680 3096 6408 5219 3389 4260 4005 3210 5846 6549 6438 4971 3829 3026 3136 3545 6255 6734 6759 3689 6360 6846 6350 6510 4482 3989 6762 5372 6532 3336 5614 4840 5040 5898 3022 6086 5018 4944 4415 6121 3223 5197 2869 5734 5306 3638 3683 6160 3800 5630 3133 4475 3716 3418 6904 5372 5907 6846 3208 6055 3992 4188 6048 3407 6632 4651 3509 6792 6550 5853 5114 4775 4855 4070 3427 6592 6686 4475 4261 5030 5344 4239 3636 5358 5329 4311 3139 4075 3395 5574 3783 6833 5453 5009 6515 6165 4033 3371 4027 3499 3891 6473 5588 3526 6392 6750 4018 3195 4131 5279 4616 3741 6793 3071 4170 6080 6845 4915 3757 6713 4884 4271 4133 3356 2977 5797 2917 4924 5433 6156 4578 4141 5487 3222 6095 6212 3736 5559 4384 3989 5831 6052 5258 6643 4305 5712 4737 6899 4170 5744 4152 5608 6532 2935 4772 4326 4502 6554 3448 3778 2898 5068 4904 4880 2892 4034 6633 4899 5337 4335 5039 4624 3084 6792 5677 5290 5818 4480 5335 3461 5750 6540 4834 4342 3989 5664 5186 4881 5930 3182 4010 6569 4362 3693 5519 5714 4552 4815 2852 3681 6841 2824 5469 5610 4848 3405 6383 4032 3138 3217 5710 4216 3139 3209 5293 2917 5445 4520 5633 5787 3423 3875 4605 5359 5987 6637 5909 6645 6117 5563 3454 6400 4154 5632 5932 6181 6261 4337 6866 6711 4715 6017 6372 5066 4783 3941 5783 5683 2966 3089 6367 6534 4915 4791 4975 3990 3419 5198 6160 5579 4263 5440 5586 5361 5057 6106 6429 4683 5031 3020 3673 5759 5635 6396 6766 6326 5884 6356 6084 3992 6457 4667 2884 5453 6628 3736 4180 4477 3236 5077 3293 2896 3232 2905 6471 6766 3970 3339 3450 6144 3952 4437 4830 3244 4628 3613 3675 3739 4757 3789 2905 5493 5530 6220 4033 2914 5453 5121 5369 5898 6229 6614 4330 4501 6361 4402 3580 4267 2947 5116 4375 3701 2937 6046 5795 4761 3685 5374 4744 5184 3694 5254 3886 2834 4411 3613 4990 4407 4598 6698 6366 3415 5190 3991 5175 6042 5202 6091 4593 5800 6084 6573 5620 5290 6082 3495 6824 6214 6902 6815 5877 6821 6791 6911 4931 4463 3356 5294 6011 6092 3551 4115 3075 4964 4215 5400 6340 3423 4107 3364 5414 3173 4933 3870 6417 6815 4939 4976 3821 3763 5831 4096 6818 4298 5856 4152 6040 6028 6461 6911 3000 3361 6408 5820 4200 6449 3304 2905 3217 5092 3076 4925 6555 4834 3846 5455 3438 4180 3860 6649 4722 5344 4824 5665 3727 5807 4729 5681 2861 3433 5534 4112 3893 4918 5489 3109 5133 4030 6297 4442 4254 6441 6205 6422 3408 6175 2823 2880 4363 5833 2845 3906 5687 6861 3625 3074 4796 3156 3666 3306 3842 4870 4415 5857 5106 6411 4378 4686 6386 5387 5258 5915 5355 3886 5302 4015 4421 3612 5960 4593 5608 3326 5990 5451 6716 4218 6842 4771 5673 4793 6763 4707 4887 6499 3360 3186 6127 4000 3331 6199 3095 2840 4283 3484 5664 5017 4711 2963 4347 5143 5249 2820 6358 6357 5732 6741 4475 2907 2984 5758 3856 6096 6084 6806 5661 6909 3857 5078 2956 3914 5831 2966 4515 4799 4350 4173 3083 4099 4525 3476 4556 4959 3757 3875 4686 6587 6582 4306 5464 3742 6240 3736 3347 3122 3874 5263 3847 4413 4182 4077 3611 6184 4402 6410 3736 3578 5582 4192 4903 3421 4388 3973 4557 4759 5898 4914 3573 6801 3385 5695 6757 4562 5704 5795 3142 4484 5809 3703 5920 6253 4810 3059 4314 4597 3532 5999 6846 6182 3022 4195 6514 6822 4298 4454 4863 4543 4502 5937 6092 2902 4331 3227 3746 5366 4961 3742 6825 5829 3695 6224 4201 3980 4974 6122 5066 6835 6294 5572 5907 4930 4351 4181 4333 6112 4256 4918 4704 4310 6738 2847 4819 3084 5325 4840 4880 5898 4521 5769 6461 3063 5469 4633 6370 5788 5442 5073 5609 4961 5264 4873 5016 3662 5727 4152 5784 5130 4006 5623 5105 4062 5182 4094 3083 3506 6111 6181 6061 6669 3617 3683 5888 4121 3451 3282 4492 3484 5703 4008 6200 3327 4027 4413 4173 6473 3055 4107 3444 4388 4061 6056 5042 3798 3419 5069 6002 3656 2850 3666 3194 3329 3273 5678 5875 5419 5322 5754 6501 5260 6267 2926 6839 5516 2837 3938 3123 5173 4527 4302 4991 3629 3651 6473 5284 3518 6055 6028 4361 4045 3732 5230 4215 4325 6562 3817 5142 6700 3144 4115 4718 2923 6623 5925 3431 3594 5791 3447 6472 3857 5792 4224 4416 5424 5546 6019 5932 4137 5159 4015 4725 6306 5363 2836 6214 3175 3305 2896 4972 3108 5125 3934 6738 6246 5219 6043 5636 4504 5440 5313 3374 5639 3575 5614 6428 6109 5636 6813 4795 5160 6147 5291 3734 3795 6577 4691 6105 5290 5096 5168 5201 5722 3494 6721 3432 5229 4103 5343 6700 3922 4677 5064 5193 5714 3518 5655 4617 6554 6381 3400 3162 6199 2927 6455 3015 5140 4094 6433 5500 3325 2936 3489 5189 4255 6104 5555 3983 4229 2875 4531 3941 3355 4315 4702 6771 5805 3094 6095 6662 2920 6503 4226 5897 3158 3946 3667 5895 6757 5340 4223 5167 5217 4760 5793 5450 6900 4250 4277 4806 3244 6142 3607 5038 3060 3329 4484 4526 5656 3326 6167 4883 3397 2929 4940 4551 4230 4854 4864 5208 4295 6730 5997 4849 3284 6728 3889 5471 5209 6892 2887 5133 6371 4690 2828 6151 6621 2999 4154 6137 5780 2934 6639 2863 3890 3083 5798 3222 5317 5650 6204 4281 5688 3912 3820 4548 5207 4487 6225 5764 3791 5069 5738 5987 5162 3638 6288 3183 6103 6396 3969 3668 5663 2941 6744 5212 4399 3636 6545 5749 6164 6590 3336 3370 3257 5589 5626 4007 3306 6487 4354 5816 6228 3391 4380 6602 4226 6570 5540 4584 5600 5842 3671 5310 5818 6677 2896 3225 4925 4078 3083 4452 3187 3577 4708 6570 4682 6899 3224 3738 5262 4279 6722 6141 4841 5697 3166 3137 3305 5549 4680 5489 5113 4920 3517 2873 5733 6717 3288 6272 5732 4224 5119 5601 3374 3939 4622 6843 3256 3746 6737 5989 5274 3830 6657 3448 5165 3670 4629 3020 3465 4091 4934 4728 3717 4188 3339 4564 6330 4402 3997 6521 6783 4980 6643 6719 5612 5722 3270 4924 6362 6494 6286 5323 5345 3381 3201 3714 6232 4569 6298 4153 5463 5762 3470 2825 6840 2983 3032 3658 6160 4097 4526 6803 3769 5767 3848 4028 3531 3442 5791 3294 5149 5177 5866 4432 5191 3221 3618 4011 3234 5245 3646 5575 3398 3370 6135 5307 5642 5651 6596 3039 6279 4081 5086 3020 4628 6587 5436 5210 5577 5289 4247 5535 4223 3893 5223 3830 5326 2836 4322 4341 4109 5952 5123 6772 5460 5361 4479 3468 4733 4510 6097 6293 6173 3584 5423 3110 5774 3086 2961 6423 3560 2817 6832 3713 3074 6736 4611 3003 5236 6836 4803 4446 5271 6692 6887 3906 3459 3033 2856 5758 3599 3146 5629 5482 6356 3376 6047 3487 6697 3970 4357 3829 5414 4728 5352 6018 6308 5516 4288 6305 5179 6515 5315 4840 6210 3367 4332 5167 5585 4832 5616 3983 6850 5909 4938 4704 6314 3199 6448 3641 3811 4629 6042 5106 3461 6416 3649 4260 5223 4327 3740 5542 5153 6227 5860 5009 3084 4171 5648 6461 3392 6408 4057 6337 5780 4609 4534 3957 6305 3055 3040 4082 4798 5577 3406 6160 5379 4295 6329 6629 4169 2915 3225 4812 5160 5970 6180 5211 4343 5006 3157 3690 5582 4175 6625 4370 4743 3340 3907 3243 6327 6782 3505 5633 6348 2854 3240 6094 6485 2911 4499 5512 3101 4484 5833 2902 4900 2828 3481 6143 5130 5137 3317 5357 4624 5962 5740 3367 4116 4622 3629 4180 5271 4183 6666 6334 4281 3515 3186 4428 6714 5236 6772 5765 5242 4953 6082 5523 5242 5601 5803 6461 3544 5670 3358 3818 4821 4575 3757 4004 6481 5219 3974 6673 5324 6828 4987 6779 6502 3665 3579 6857 3040 3721 4885 6274 3480 5422 6011 4048 5519 4023 3964 2938 5477 5947 3591 6119 4954 2932 6062 4385 4686 5817 4256 5327 5569 4865 2988 2872 4820 4326 3652 5190 2885 6303 3191 6784 5749 5891 6388 6413 5732 6284 6157 3255 3762 6481 3761 4581 5279 5385 4207 5022 6668 5902 4473 3468 5751 3415 5278 3574 2817 4731 3917 3779 3178 4788 5272 3197 3858 6361 6446 4683 5881 4705 4190 3828 6079 2918 3866 3042 5541 3743 6641 5148 5001 6499 4370 5961 6124 6060 3407 4019 5102 5136 3863 6209 3407 3017 3449 3450 5999 6830 6695 3208 5967 3126 6512 4146 4237 3451 4163 5599 5482 6196 4402 5785 3270 3000 4715 4358 5921 4353 5824 4464 3227 5542 4256 4542 3829 5598 5658 3959 4237 3203 6880 5829 6244 5644 4673 4719 6170 3728 4396 3804 5871 4809 6707 6166 5571 3342 4997 3012 6166 6742 3825 4653 5385 5947 4585 2875 3674 4084 3964 4469 6506 3672 2905 4098 6457 4065 3026 5036 4455 4262 4566 6362 5940 3166 5108 6803 3457 3683 5214 5249 6604 5292 6115 oolite-1.82/tools/planetinfo-source/seeds.txt000066400000000000000000001252561256642440500214230ustar00rootroot0000000000000074 90 72 2 83 183 128 205 184 152 29 122 42 243 156 77 27 33 152 208 148 83 13 134 218 131 32 180 51 226 128 176 224 172 141 119 90 249 212 69 27 20 56 173 156 20 29 21 170 0 184 236 83 196 192 98 200 216 61 237 202 238 204 4 91 28 24 49 100 93 109 158 186 40 16 244 179 73 64 164 112 84 45 95 122 11 132 194 219 37 56 220 236 245 253 197 10 84 40 178 83 158 0 245 216 55 93 209 106 7 252 214 155 220 152 110 52 6 205 47 154 218 0 175 51 46 0 149 0 123 205 199 154 26 52 186 155 172 56 232 60 22 221 127 106 20 152 18 83 165 64 132 232 245 125 70 10 253 44 4 219 193 24 137 4 14 45 90 122 89 240 164 179 239 192 130 144 96 109 209 186 230 228 108 91 8 56 209 140 53 189 98 202 1 8 206 83 57 128 16 248 82 157 108 170 143 92 204 27 44 152 128 212 52 141 61 90 101 224 149 51 238 128 109 32 69 13 156 218 47 148 26 27 153 56 151 220 19 157 142 42 220 120 164 83 186 192 153 8 143 189 99 74 127 140 111 91 123 24 85 164 58 237 249 58 190 208 65 179 137 64 85 176 104 173 71 250 181 68 3 219 190 56 58 44 113 125 35 138 99 232 85 83 136 0 32 24 234 221 75 234 139 188 45 155 15 152 6 116 223 77 175 26 36 192 104 51 34 0 58 64 11 77 244 26 57 244 102 155 217 56 186 124 13 93 65 234 87 88 162 83 3 64 163 40 164 253 68 138 117 236 70 219 72 24 149 68 227 173 125 250 86 176 202 179 23 192 27 208 108 237 193 58 121 164 133 91 73 56 23 204 168 61 8 74 121 200 73 83 139 128 35 56 253 29 111 42 252 28 251 27 135 152 0 20 6 13 133 218 22 160 39 51 202 128 250 96 205 141 208 90 54 84 159 27 110 56 81 28 3 29 152 170 135 56 12 83 128 192 160 72 53 61 234 202 223 76 138 91 42 24 73 228 7 109 229 186 35 144 63 179 153 64 214 240 108 45 64 122 48 4 244 219 167 56 104 108 220 253 16 10 67 168 169 83 66 0 27 88 140 93 214 106 224 124 52 155 146 152 110 180 168 205 190 154 61 128 210 51 230 0 175 128 139 205 48 154 39 180 195 155 86 56 92 188 244 221 146 106 107 24 226 83 49 64 146 104 66 125 83 10 190 172 57 219 31 24 113 132 168 45 49 122 36 112 160 179 15 192 132 16 105 109 194 186 219 100 78 91 218 56 45 12 12 189 61 202 192 136 117 83 173 128 6 120 151 157 129 170 56 220 217 27 50 152 80 84 199 141 92 90 152 96 105 51 118 128 87 160 69 13 21 218 12 20 212 27 147 56 219 92 226 157 49 42 3 248 35 83 22 192 119 136 203 189 128 74 16 12 85 91 41 24 13 36 197 237 96 58 89 80 237 179 121 64 39 48 97 173 72 250 122 196 148 219 224 56 102 172 55 125 142 138 242 104 173 83 204 0 230 152 30 221 112 234 4 60 235 155 101 152 166 244 97 77 94 26 39 64 236 51 122 0 244 192 251 77 125 26 230 116 208 155 35 56 206 252 203 93 116 234 78 216 209 83 47 64 81 168 208 253 113 138 214 108 220 219 70 24 29 196 93 173 116 250 193 48 38 179 215 192 189 80 85 237 210 58 14 36 199 91 187 56 19 76 95 61 3 74 216 72 81 83 159 128 185 184 33 29 164 42 69 156 104 27 45 152 112 148 120 13 196 218 233 32 91 51 242 128 132 224 173 141 105 90 179 212 184 27 8 56 53 156 177 29 91 170 78 184 235 83 124 192 30 200 81 61 39 202 16 204 207 91 120 24 161 100 114 109 108 186 94 16 75 179 41 64 72 112 69 45 97 122 149 132 229 219 105 56 52 236 130 253 155 10 114 40 97 83 38 0 129 216 160 93 27 106 249 252 81 155 136 152 174 52 11 205 141 154 224 0 182 51 222 0 9 0 92 205 217 154 116 52 141 155 64 56 16 60 147 221 229 106 2 152 113 83 253 64 224 232 78 125 160 10 191 44 47 219 189 24 153 4 3 45 72 122 47 240 91 179 111 192 198 144 49 109 243 186 16 228 239 91 236 56 201 140 162 189 88 202 191 8 221 83 97 128 60 248 155 157 214 170 33 92 167 27 120 152 96 212 25 141 187 90 11 224 252 51 62 128 129 32 6 13 206 218 41 148 77 27 205 56 95 220 112 157 20 42 106 120 99 83 178 192 149 8 200 189 221 74 225 140 250 91 23 24 5 164 15 237 7 58 52 208 88 179 169 64 57 176 25 173 137 250 127 68 230 219 66 56 210 44 190 125 57 138 193 232 196 83 80 0 236 24 19 221 213 234 189 188 104 155 251 152 134 116 164 77 77 26 106 192 47 51 18 0 238 64 172 77 70 26 211 244 249 155 173 56 34 124 74 93 231 234 133 88 193 83 155 64 63 40 189 253 222 138 119 236 49 219 132 24 229 68 152 173 171 250 108 176 65 179 215 192 159 208 253 237 35 58 227 164 200 91 109 56 79 204 213 61 62 74 119 200 24 83 243 128 143 56 6 29 25 42 206 28 150 27 19 152 32 20 171 13 67 218 252 160 78 51 90 128 78 96 78 141 66 90 112 84 146 27 226 56 89 28 32 29 94 170 85 56 139 83 184 192 220 72 46 61 164 202 129 76 213 91 6 24 57 228 156 109 51 186 217 144 22 179 249 64 250 240 221 45 194 122 58 4 151 219 107 56 64 108 233 253 102 10 225 168 216 83 74 0 39 88 117 93 160 106 82 124 47 155 190 152 46 180 45 205 156 154 195 128 89 51 22 0 163 128 236 205 194 154 1 180 22 155 106 56 4 188 241 221 120 106 217 24 193 83 9 64 110 104 27 125 45 10 0 172 228 219 155 24 1 132 29 45 159 122 122 112 215 179 15 192 72 16 186 109 100 186 133 100 81 91 62 56 165 12 249 189 179 202 254 136 4 83 85 128 178 120 96 157 107 170 74 220 52 27 254 152 176 84 44 141 90 90 190 96 80 51 70 128 235 160 134 13 199 218 134 20 135 27 71 56 35 92 191 157 55 42 17 248 98 83 142 192 243 136 132 189 122 74 242 12 96 91 69 24 61 36 26 237 238 58 79 80 132 179 25 64 139 48 146 173 10 250 196 196 247 219 228 56 126 172 4 125 36 138 208 104 156 83 20 0 50 152 199 221 122 234 182 60 166 155 209 152 166 244 166 77 124 26 237 64 51 51 234 0 40 192 28 77 79 26 0 116 227 155 119 56 182 252 136 93 154 234 252 216 112 83 71 64 109 168 105 253 139 138 88 108 71 219 2 24 237 196 146 173 34 250 87 48 29 179 23 192 193 80 102 237 180 58 248 36 138 91 95 56 203 76 12 61 185 148 180 144 4 166 111 0 157 112 53 58 251 84 254 56 199 54 147 48 179 40 159 26 171 180 71 64 240 102 53 0 147 192 53 26 54 180 106 168 159 54 9 112 188 56 217 58 169 84 193 112 169 166 169 128 103 144 37 122 161 148 245 152 197 182 233 48 84 200 226 218 219 116 209 32 192 102 164 128 154 224 116 90 69 244 14 8 169 182 12 112 122 216 75 250 138 20 40 80 4 166 125 0 44 176 83 186 41 212 38 248 249 54 202 48 175 104 100 154 254 52 117 0 134 102 13 0 156 0 178 154 86 52 173 104 168 54 250 112 242 120 60 186 126 212 104 48 149 166 171 128 234 208 63 250 211 20 18 88 228 182 244 48 196 8 164 90 83 244 178 224 193 102 48 128 151 32 109 218 169 116 197 200 29 182 145 112 36 24 43 122 196 148 3 16 220 166 243 0 163 240 105 58 224 84 55 184 4 54 41 48 147 168 33 26 26 180 10 192 243 102 205 0 141 64 38 26 127 180 215 40 137 54 147 112 16 184 151 58 156 84 120 240 88 166 21 128 85 16 82 122 142 148 22 24 219 182 39 48 28 72 93 218 146 116 252 160 155 102 164 128 124 96 93 90 22 244 99 136 106 182 190 112 182 88 2 250 69 20 71 208 139 166 209 0 2 48 120 186 30 212 47 120 231 54 176 48 95 232 214 154 253 52 8 128 57 102 117 0 102 128 146 154 175 52 234 232 65 54 212 112 22 248 234 186 1 212 239 176 244 166 231 128 168 80 92 250 208 20 3 216 169 182 130 48 92 136 14 90 154 244 173 96 77 102 0 128 73 160 69 218 138 116 234 72 143 182 147 112 48 152 209 122 15 148 242 144 19 166 23 0 73 112 126 58 229 84 16 56 162 54 95 48 19 40 132 26 169 180 109 64 87 102 5 0 39 192 246 26 232 180 228 168 210 54 189 112 4 56 54 58 175 84 207 112 104 166 33 128 227 144 94 122 155 148 215 152 80 182 5 48 132 200 183 218 105 116 199 32 215 102 68 128 254 224 37 90 7 244 88 8 140 182 16 112 146 216 152 250 32 20 6 80 115 166 197 0 120 176 124 186 51 212 216 248 52 54 54 48 175 104 41 154 28 52 59 0 77 102 125 0 208 0 83 154 40 52 199 104 59 54 78 112 218 120 121 186 164 212 22 48 180 166 195 128 6 208 88 250 237 20 148 88 207 182 176 48 148 8 89 90 1 244 72 224 56 102 112 128 155 32 254 218 139 116 175 200 96 182 53 112 220 24 88 122 122 148 129 16 171 166 219 0 143 240 114 58 10 84 137 184 159 54 53 48 51 168 198 26 88 180 112 192 26 102 221 0 97 64 167 26 113 180 145 40 124 54 135 112 152 184 180 58 226 84 198 240 215 166 205 128 17 16 75 122 200 148 56 24 38 182 131 48 140 72 242 218 96 116 50 160 114 102 132 128 32 96 206 90 24 244 237 136 13 182 2 112 14 88 15 250 27 20 101 208 186 166 89 0 142 48 97 186 104 212 33 120 226 54 92 48 159 232 91 154 91 52 14 128 192 102 37 0 218 128 243 154 193 52 68 232 148 54 104 112 62 248 231 186 103 212 221 176 211 166 63 128 4 80 53 250 42 20 197 216 84 182 126 48 108 136 131 90 136 244 131 96 132 102 128 128 141 160 150 218 172 116 20 72 146 182 119 112 40 152 190 122 5 148 176 144 162 166 63 0 117 112 71 58 79 84 162 56 253 54 171 48 243 40 233 26 39 180 19 64 62 102 85 0 59 192 55 26 26 180 222 168 133 54 241 112 204 56 19 58 53 84 93 112 167 166 25 128 223 144 23 122 21 148 57 152 91 182 161 48 52 200 12 218 119 116 61 32 110 102 100 128 226 224 86 90 73 244 34 8 239 182 148 112 42 216 101 250 54 20 100 80 98 166 141 0 68 176 37 186 189 212 10 248 239 54 34 48 47 104 110 154 186 52 129 0 148 102 109 0 132 0 116 154 122 52 97 104 78 54 34 112 66 120 54 186 74 212 68 48 83 166 91 128 162 208 241 250 135 20 150 88 58 182 236 48 228 8 142 90 47 244 94 224 47 102 48 128 31 32 15 218 237 116 25 200 35 182 89 112 20 24 5 122 176 148 127 16 250 166 67 0 251 240 251 58 180 84 91 184 186 54 193 48 83 168 235 26 22 180 86 192 193 102 109 0 181 64 168 26 227 180 203 40 239 54 251 112 160 184 81 58 168 84 148 240 214 166 5 128 77 16 196 122 130 148 218 24 241 182 95 48 124 72 7 218 174 116 232 160 201 102 228 128 68 96 191 90 154 244 247 136 48 182 198 112 230 88 156 250 113 20 3 208 105 166 97 0 154 48 202 186 50 212 147 120 93 54 136 48 95 232 96 154 57 52 148 128 199 102 85 0 206 128 212 154 83 52 30 232 103 54 124 112 230 248 100 186 77 212 75 176 50 166 23 128 224 80 142 250 4 20 7 216 127 182 250 48 252 136 120 90 246 244 217 96 59 102 128 128 81 160 103 218 78 116 190 72 21 182 219 112 160 152 43 122 123 148 238 144 177 166 231 0 33 112 144 58 57 84 180 56 216 54 119 48 83 40 206 26 37 180 57 64 165 102 37 0 207 192 248 26 204 180 88 168 184 54 165 112 20 56 112 58 59 84 107 112 102 166 145 128 91 144 80 122 15 148 27 152 230 182 189 48 100 200 225 218 5 116 51 32 133 102 4 128 70 224 7 90 11 244 108 8 210 182 152 112 66 216 178 250 204 20 66 80 209 166 213 0 144 176 78 186 199 212 188 248 42 54 142 48 47 104 51 154 216 52 71 0 91 102 221 0 184 0 21 154 76 52 123 104 225 54 118 112 42 120 115 186 112 212 242 48 114 166 115 128 190 208 10 250 161 20 24 88 37 182 168 48 180 8 67 90 221 244 244 224 166 102 112 128 35 32 160 218 207 116 3 200 102 182 253 112 204 24 50 122 102 148 253 16 201 166 43 0 231 240 4 58 222 84 173 184 85 54 205 48 243 168 144 26 84 180 188 192 232 102 125 0 137 64 41 26 213 180 133 40 226 54 239 112 40 184 110 58 238 84 226 240 85 166 189 128 9 16 189 122 188 148 252 24 60 182 187 48 236 72 156 218 124 116 30 160 160 102 196 128 232 96 48 90 156 244 129 136 211 182 10 112 62 88 169 250 71 20 33 208 152 166 233 0 38 48 179 186 124 212 133 120 88 54 52 48 159 232 229 154 151 52 154 128 78 102 5 0 66 128 53 154 101 52 120 232 186 54 16 112 14 248 97 186 179 212 57 176 17 166 111 128 60 80 103 250 94 20 201 216 42 182 246 48 12 136 237 90 228 244 175 96 114 102 0 128 149 160 184 218 112 116 232 72 24 182 191 112 152 152 24 122 113 41 105 33 8 77 222 5 54 233 99 133 233 225 208 217 61 45 146 237 112 1 164 213 181 153 36 49 165 141 148 21 152 249 138 101 129 209 176 169 214 237 43 253 181 81 149 181 254 9 222 193 38 205 78 37 201 137 107 69 136 193 176 249 245 173 99 13 100 33 210 149 64 121 137 209 64 13 65 53 141 153 217 37 114 177 36 201 111 109 109 29 223 113 78 117 47 233 218 97 103 77 159 69 232 41 105 5 51 161 32 25 216 45 125 45 203 65 190 85 255 89 70 113 206 141 157 85 30 57 110 229 62 145 120 233 130 237 198 61 12 145 149 53 100 201 255 1 106 205 111 101 179 201 252 197 137 129 192 57 132 173 126 77 198 97 8 21 146 57 251 17 238 13 74 117 107 217 232 165 135 113 76 9 176 109 216 93 93 177 10 245 60 169 236 161 206 77 96 133 74 105 198 133 44 97 48 89 154 45 8 109 117 129 80 213 152 25 72 177 63 141 230 149 148 121 233 101 236 81 64 41 151 237 65 125 242 209 77 181 89 137 65 65 53 205 16 165 205 9 102 69 187 65 16 121 186 173 185 141 248 161 54 149 179 249 204 81 99 13 19 181 185 25 16 37 13 49 244 73 216 109 163 157 235 241 254 117 90 105 158 225 61 77 33 197 92 169 123 5 214 33 0 153 132 45 51 173 111 193 90 85 130 217 41 241 248 141 111 213 250 185 252 229 137 17 8 105 19 237 156 189 104 17 190 53 223 73 163 129 136 205 49 229 23 73 167 197 28 1 160 185 152 173 20 205 250 225 92 21 165 185 254 145 160 13 156 245 119 89 79 165 2 241 27 137 232 109 206 221 137 49 43 245 135 41 240 33 181 77 226 5 31 233 136 133 47 225 143 217 150 45 254 237 185 1 221 213 187 153 235 49 250 141 56 21 81 249 167 101 23 209 207 169 247 237 215 253 110 81 230 181 244 9 37 193 99 205 210 37 146 137 192 69 174 193 111 249 30 173 143 13 205 33 123 149 102 121 144 209 165 13 229 53 166 153 166 37 104 177 195 201 224 109 89 29 56 113 143 117 197 233 225 97 52 77 163 69 145 41 238 5 57 161 223 25 209 45 105 45 84 65 215 85 69 89 141 113 67 141 65 85 151 57 235 229 148 145 151 233 67 237 242 61 5 145 198 53 154 201 198 1 199 205 243 101 60 201 177 197 111 129 127 57 77 173 42 77 111 97 145 21 248 57 130 17 115 13 238 117 68 217 21 165 61 113 235 9 193 109 68 93 246 177 43 245 18 169 115 161 187 77 100 133 179 105 171 133 242 97 239 89 51 45 116 109 62 129 73 213 30 25 15 177 212 141 138 149 205 121 198 101 2 81 95 41 248 237 237 125 43 209 94 181 207 137 136 65 178 205 148 165 22 9 123 69 97 65 207 121 35 173 229 141 225 161 159 149 89 249 211 81 8 13 183 181 82 25 157 37 131 49 147 73 137 109 143 157 196 241 255 117 112 105 165 225 74 77 37 197 133 169 192 5 92 33 191 153 189 45 31 173 120 193 51 85 72 217 112 241 173 141 19 213 243 185 57 229 95 17 39 105 20 237 200 189 225 17 175 53 149 73 106 129 37 205 181 229 32 73 28 197 130 1 95 185 161 173 192 205 35 225 165 21 139 185 133 145 101 13 64 245 208 89 60 165 56 241 186 137 57 109 58 221 162 49 12 245 221 41 119 33 226 77 230 5 8 233 45 133 117 225 78 217 111 45 106 237 2 1 150 213 193 153 178 49 207 141 220 21 10 249 68 101 173 209 238 169 152 237 131 253 39 81 183 181 234 9 108 193 32 205 86 37 91 137 149 69 212 193 46 249 199 173 187 13 54 33 164 149 140 121 151 209 138 13 137 53 191 153 243 37 94 177 98 201 209 109 69 29 145 113 80 117 91 233 232 97 129 77 167 69 58 41 243 5 63 161 158 25 74 45 85 45 221 65 112 85 139 89 212 113 56 141 229 85 16 57 232 229 234 145 182 233 132 237 30 61 254 145 119 53 208 201 141 1 164 205 119 101 197 201 230 197 85 129 62 57 150 173 214 77 24 97 154 21 94 57 9 17 120 13 146 117 29 217 194 165 243 113 138 9 82 109 176 93 143 177 204 245 232 169 250 161 40 77 104 133 28 105 16 133 184 97 174 89 76 45 224 109 7 129 194 213 164 25 214 177 233 141 46 149 6 121 35 101 24 81 126 41 217 237 153 125 100 209 239 181 69 137 207 65 175 205 24 165 95 9 16 69 7 65 142 121 12 173 17 141 202 161 136 149 255 249 218 81 45 13 91 181 235 25 170 37 249 49 50 73 186 109 123 157 157 241 128 117 134 105 172 225 215 77 41 197 174 169 133 5 226 33 126 153 118 45 11 173 129 193 140 85 14 217 183 241 226 141 183 213 236 185 246 229 53 17 70 105 149 237 244 189 90 17 32 53 75 73 49 129 66 205 57 229 41 73 17 197 232 1 30 185 42 173 108 205 76 225 110 21 113 185 12 145 170 13 228 245 41 89 169 165 110 241 89 137 10 109 166 221 187 49 109 245 51 41 254 33 143 77 234 5 241 233 82 133 187 225 13 217 200 45 214 237 75 1 207 213 199 153 121 49 36 141 128 21 195 249 97 101 67 209 13 169 185 237 47 253 224 81 8 181 224 9 179 193 93 205 218 37 36 137 234 69 250 193 237 249 240 173 231 13 159 33 77 149 178 121 158 209 239 13 45 53 216 153 192 37 84 177 1 201 66 109 49 29 234 113 145 117 241 233 239 97 78 77 171 69 227 41 120 5 69 161 93 25 67 45 65 45 102 65 137 85 209 89 27 113 173 141 137 85 137 57 101 229 64 145 213 233 69 237 74 61 247 145 168 53 6 201 84 1 1 205 251 101 78 201 155 197 59 129 253 57 95 173 130 77 193 97 35 21 196 57 144 17 253 13 54 117 246 217 239 165 169 113 41 9 99 109 28 93 40 177 237 245 190 169 129 161 21 77 108 133 133 105 245 133 126 97 109 89 229 45 76 109 208 129 187 213 42 25 157 177 126 141 210 149 63 121 0 101 46 81 157 41 58 237 69 125 157 209 0 181 187 137 22 65 44 205 156 165 168 9 37 69 173 65 77 121 117 173 61 141 179 161 241 149 165 249 225 81 210 13 255 181 132 25 55 37 111 49 209 73 107 109 103 157 118 241 129 117 156 105 179 225 228 77 45 197 215 169 202 5 104 33 61 153 175 45 247 173 138 193 101 85 212 217 254 241 151 141 91 213 229 185 51 229 11 17 101 105 150 237 32 189 211 17 17 53 1 73 248 129 223 205 189 229 50 73 134 197 78 1 221 185 51 173 24 205 117 225 183 21 87 185 147 145 111 13 136 245 130 89 150 165 164 241 248 137 91 109 18 221 212 49 78 245 137 82 210 66 16 154 189 10 110 210 203 10 218 194 185 178 167 90 117 218 243 2 64 170 10 50 137 98 210 26 154 42 98 242 241 202 73 162 217 82 193 218 56 250 205 162 218 106 124 18 124 130 29 154 190 74 52 18 75 138 215 130 121 242 167 90 120 26 186 66 204 42 32 242 146 162 89 26 147 106 108 50 127 74 107 98 65 146 3 218 155 58 128 226 252 234 93 210 53 194 110 154 127 138 18 82 182 10 237 66 89 50 252 90 11 90 104 130 212 170 157 178 76 226 196 26 236 170 174 114 152 202 68 34 105 210 57 218 46 122 58 34 59 106 71 146 63 2 68 154 64 202 72 146 77 138 90 2 153 114 228 90 110 154 62 194 152 42 195 114 246 34 84 26 229 234 104 178 125 74 22 226 144 18 164 218 49 186 60 98 213 234 120 82 217 66 221 154 65 10 23 210 80 10 96 194 120 178 160 90 225 218 124 2 89 170 208 50 208 98 71 26 190 42 219 242 110 202 31 162 248 82 130 218 228 250 198 162 11 106 50 18 67 130 122 154 194 74 189 18 0 138 61 130 56 242 112 90 164 26 99 66 85 42 6 242 25 162 222 26 183 106 69 50 172 74 161 98 224 146 20 218 135 58 25 226 29 234 179 210 188 194 91 154 3 138 123 82 155 10 51 66 24 50 149 90 247 90 49 130 205 170 163 178 19 226 89 26 16 170 231 114 117 202 218 34 136 210 154 218 90 122 115 34 76 106 61 146 134 2 193 154 68 202 145 146 98 138 128 2 88 114 77 90 26 154 39 194 1 42 233 114 253 34 249 26 9 234 1 178 10 74 12 226 47 18 85 218 157 186 21 98 214 234 14 82 224 66 234 154 197 10 64 210 149 10 102 194 55 178 217 90 77 218 133 2 50 170 22 50 23 98 252 26 226 42 212 242 171 202 117 162 23 82 131 218 144 250 63 162 252 106 104 18 10 130 23 154 198 74 198 18 117 138 35 130 247 242 121 90 208 26 140 66 158 42 108 242 160 162 163 26 219 106 158 50 153 74 87 98 127 146 101 218 115 58 50 226 254 234 137 210 67 194 136 154 135 138 100 82 64 10 249 66 215 50 110 90 227 90 122 130 134 170 41 178 218 226 46 26 52 170 160 114 18 202 240 34 167 210 59 218 134 122 44 34 29 106 179 146 205 2 126 154 72 202 90 146 55 138 38 2 23 114 246 90 198 154 144 194 42 42 143 114 4 34 222 26 45 234 26 178 87 74 130 226 206 18 70 218 9 186 110 98 151 234 36 82 231 66 55 154 73 10 233 210 154 10 236 194 246 178 82 90 185 218 14 2 203 170 220 50 94 98 241 26 6 42 77 242 168 202 75 162 54 82 196 218 60 250 56 162 173 106 30 18 209 130 244 154 202 74 79 18 170 138 137 130 182 242 194 90 252 26 53 66 167 42 82 242 39 162 168 26 255 106 119 50 70 74 141 98 30 146 246 218 95 58 203 226 159 234 223 210 202 194 245 154 11 138 205 82 165 10 63 66 150 50 135 90 207 90 67 130 255 170 47 178 161 226 67 26 88 170 217 114 111 202 134 34 198 210 28 218 178 122 101 34 174 106 169 146 20 2 123 154 76 202 163 146 204 138 76 2 214 114 223 90 114 154 121 194 19 42 181 114 11 34 3 26 81 234 179 178 100 74 120 226 109 18 119 218 117 186 71 98 24 234 186 82 238 66 196 154 205 10 18 210 95 10 242 194 181 178 11 90 37 218 23 2 36 170 34 50 165 98 38 26 42 42 70 242 101 202 161 162 85 82 69 218 232 250 177 162 30 106 84 18 152 130 17 154 206 74 88 18 159 138 111 130 117 242 75 90 40 26 94 66 112 42 184 242 174 162 237 26 35 106 208 50 179 74 67 98 189 146 199 218 75 58 228 226 0 234 181 210 81 194 162 154 143 138 182 82 202 10 5 66 85 50 224 90 187 90 140 130 56 170 181 178 104 226 152 26 124 170 146 114 140 202 156 34 229 210 61 218 222 122 30 34 255 106 31 146 91 2 184 154 80 202 108 146 33 138 242 2 149 114 8 90 30 154 226 194 188 42 91 114 18 34 104 26 117 234 204 178 49 74 238 226 12 18 232 218 225 186 160 98 89 234 208 82 245 66 145 154 81 10 187 210 228 10 120 194 116 178 4 90 145 218 160 2 61 170 232 50 236 98 155 26 78 42 191 242 226 202 119 162 116 82 6 218 148 250 170 162 79 106 10 18 95 130 110 154 210 74 225 18 84 138 213 130 52 242 20 90 84 26 7 66 249 42 158 242 53 162 114 26 71 106 169 50 224 74 121 98 92 146 216 218 55 58 125 226 33 234 11 210 216 194 143 154 19 138 31 82 175 10 75 66 20 50 121 90 167 90 85 130 49 170 187 178 47 226 45 26 160 170 203 114 105 202 50 34 4 210 158 218 10 122 87 34 16 106 21 146 162 2 53 154 84 202 181 146 54 138 24 2 84 114 113 90 202 154 203 194 37 42 129 114 25 34 13 26 153 234 101 178 190 74 228 226 171 18 153 218 77 186 121 98 90 234 102 82 252 66 158 154 213 10 228 210 41 10 126 194 51 178 61 90 253 218 169 2 22 170 46 50 51 98 80 26 114 42 184 242 31 202 205 162 147 82 7 218 64 250 35 162 64 106 64 18 38 130 11 154 214 74 234 18 201 138 187 130 243 242 29 90 128 26 48 66 66 42 4 242 188 162 55 26 107 106 2 50 205 74 47 98 251 146 41 218 35 58 150 226 2 234 225 210 95 194 188 154 151 138 8 82 84 10 17 66 211 50 82 90 147 90 158 130 234 170 65 178 246 226 2 26 196 170 132 114 6 202 72 34 35 210 63 218 54 122 16 34 225 106 139 146 233 2 242 154 88 202 126 146 11 138 190 2 19 114 26 90 118 154 52 194 78 42 39 114 32 34 242 26 189 234 126 178 11 74 90 226 74 18 138 218 185 186 210 98 27 234 124 82 3 66 235 154 89 10 141 210 46 10 4 194 242 178 182 90 105 218 50 2 175 170 244 50 122 98 69 26 150 42 49 242 28 202 163 162 178 82 72 218 236 250 28 162 241 106 246 18 237 130 232 154 218 74 115 18 254 138 33 130 178 242 102 90 172 26 217 66 75 42 234 242 67 162 60 26 143 106 219 50 122 74 101 98 154 146 186 218 15 58 47 226 163 234 55 210 230 194 41 154 27 138 113 82 185 10 87 66 146 50 107 90 127 90 103 130 99 170 71 178 189 226 23 26 232 170 189 114 99 202 222 34 66 210 32 218 98 122 73 34 114 106 129 146 48 2 239 154 92 202 199 146 160 138 228 2 210 114 3 90 34 154 29 194 55 42 77 114 39 34 23 26 225 234 23 178 24 74 80 226 233 18 187 218 37 186 171 98 156 234 18 164 165 132 32 53 123 22 221 168 153 27 184 156 128 144 103 5 23 198 125 252 147 243 16 164 199 76 251 165 229 134 137 192 23 219 73 188 76 184 129 149 185 86 195 244 4 83 80 228 109 212 214 85 93 54 214 152 213 219 77 28 29 160 100 101 123 38 221 172 85 243 47 100 232 28 123 69 198 38 107 48 91 27 240 188 1 72 168 117 96 54 19 36 126 211 155 36 71 36 16 117 132 86 112 136 208 155 28 156 74 176 196 197 108 134 173 92 150 243 191 36 90 236 29 229 123 198 141 160 253 91 127 188 135 216 17 85 164 22 244 84 214 83 8 100 177 116 140 149 16 118 235 120 74 91 132 28 137 192 199 37 11 230 46 12 150 243 32 228 156 188 163 133 38 102 49 16 191 155 87 188 94 104 254 53 165 246 165 132 77 211 245 164 44 196 11 181 33 150 135 104 3 27 229 156 88 208 173 133 118 70 161 188 148 243 178 164 48 140 204 37 230 6 150 128 95 219 216 188 6 248 173 21 131 214 104 180 35 83 196 228 56 20 78 213 215 182 132 88 187 219 158 28 57 224 182 229 206 166 68 108 210 243 213 100 149 92 88 197 218 166 251 240 158 27 99 188 255 136 96 245 93 182 124 228 152 211 211 36 86 100 19 245 82 214 34 72 50 155 17 156 170 240 34 69 52 6 89 28 143 243 233 36 75 44 7 101 36 70 162 96 61 91 86 188 201 24 86 213 85 150 33 20 237 83 132 100 4 180 27 21 179 246 161 56 40 91 157 28 45 0 50 165 198 102 30 204 10 243 78 228 209 252 152 5 227 230 201 208 250 155 18 188 228 168 206 181 138 118 151 68 96 211 53 164 195 4 39 53 24 22 66 40 93 27 162 156 64 16 36 5 166 198 212 124 133 243 100 164 169 204 205 165 54 134 178 64 151 219 247 188 208 56 10 149 28 86 30 116 50 83 72 228 19 84 245 85 162 54 67 24 145 219 127 28 101 32 57 101 242 38 188 44 63 243 139 100 82 156 101 69 63 38 156 176 210 27 102 188 13 200 72 117 43 54 246 164 163 211 27 36 117 164 70 117 113 86 229 8 132 155 150 156 26 48 177 197 203 134 20 220 119 243 35 36 76 108 32 229 28 198 198 32 109 91 189 188 27 88 202 85 215 22 95 212 243 83 16 100 103 244 218 149 165 118 104 248 245 91 70 28 225 64 204 37 82 230 29 140 111 243 140 228 22 60 190 133 239 102 114 144 38 155 93 188 122 232 206 53 64 246 152 4 99 211 133 164 106 68 114 181 94 150 12 232 166 27 239 156 56 80 202 133 165 70 24 60 102 243 38 164 50 12 255 37 215 6 223 0 191 219 166 188 170 120 150 21 134 214 227 52 49 83 220 228 254 148 204 213 188 182 17 216 86 219 240 28 161 96 235 229 229 166 67 236 155 243 81 100 31 220 162 197 243 166 76 112 246 27 249 188 43 8 97 245 200 182 127 100 158 211 115 36 164 228 169 245 223 214 183 200 197 155 171 156 154 112 111 69 51 6 224 156 80 243 109 36 93 172 105 101 101 70 251 224 140 91 180 188 125 152 110 213 40 150 172 148 234 83 172 100 218 52 202 21 232 246 62 184 179 91 127 28 165 128 150 165 173 102 45 76 196 243 218 228 107 124 19 5 76 230 42 80 66 155 56 188 32 40 255 181 197 118 170 196 85 211 229 164 33 132 237 53 245 22 231 168 224 27 204 156 64 144 160 5 117 198 107 252 54 243 248 164 203 76 96 165 199 134 27 192 214 219 229 188 148 184 82 149 191 86 185 244 31 83 128 228 249 212 211 85 39 54 240 152 12 219 241 28 237 160 205 101 169 38 219 172 232 243 39 100 252 28 16 69 248 38 13 48 10 27 28 188 89 72 169 117 54 54 25 36 137 211 219 36 227 36 61 117 158 86 154 136 247 155 80 156 42 176 93 197 106 134 187 92 25 243 199 36 126 236 226 229 253 198 63 160 156 91 59 188 239 216 66 85 74 22 10 84 209 83 88 100 93 116 233 149 122 118 37 120 97 91 72 28 121 192 144 37 217 230 76 12 9 243 56 228 208 188 152 133 248 102 243 16 78 155 163 188 214 104 95 53 27 246 203 132 56 211 85 164 232 196 152 181 219 150 209 104 10 27 57 156 88 208 166 133 20 70 207 188 247 243 218 164 116 140 241 37 8 6 104 128 222 219 180 188 142 248 62 21 201 214 158 180 254 83 52 228 4 20 11 213 225 182 222 88 178 219 130 28 73 224 223 229 60 166 130 108 37 243 13 100 233 92 173 197 76 166 221 240 13 27 207 188 151 136 33 245 115 182 194 228 99 211 83 36 50 100 0 245 172 214 140 72 25 155 133 156 202 240 123 69 114 6 167 28 210 243 49 36 175 44 140 101 230 70 148 96 156 91 82 188 113 24 71 213 59 150 119 20 168 83 20 100 240 180 56 21 93 246 27 56 255 91 161 28 93 0 187 165 212 102 124 204 61 243 166 228 69 252 77 5 245 230 203 208 73 155 158 188 156 168 239 181 64 118 253 68 11 211 213 164 191 4 116 53 18 22 204 40 36 27 54 156 128 16 221 5 132 198 66 124 168 243 204 164 45 204 178 165 152 134 196 64 214 219 19 188 152 56 91 149 162 86 148 116 205 83 248 228 31 84 114 85 236 54 221 24 72 219 163 28 181 32 34 101 160 38 58 44 82 243 3 100 230 156 122 69 241 38 190 176 1 27 18 188 229 200 201 117 129 54 124 164 46 211 219 36 145 164 243 117 11 86 143 8 43 155 74 156 122 48 202 197 73 134 162 220 122 243 171 36 240 108 101 229 30 198 248 32 140 91 249 188 3 88 123 85 253 22 245 212 110 83 224 100 147 244 183 149 143 118 34 248 140 91 138 28 81 64 21 37 160 230 187 140 98 243 36 228 202 60 51 133 65 102 180 144 53 155 41 188 114 232 175 53 54 246 62 4 206 211 101 164 166 68 127 181 152 150 214 232 45 27 195 156 184 80 67 133 195 70 198 60 73 243 206 164 246 12 164 37 121 6 49 0 190 219 2 188 178 120 167 21 76 214 153 52 140 83 204 228 74 148 9 213 70 182 235 216 205 219 84 28 49 96 148 229 211 166 1 236 110 243 9 100 243 220 119 197 229 166 174 112 229 27 229 188 67 8 162 245 94 182 69 100 233 211 115 36 0 228 22 245 185 214 161 200 44 155 159 156 58 112 72 69 241 6 174 156 19 243 53 36 65 172 110 101 167 70 109 224 107 91 48 188 165 152 223 213 142 150 130 148 37 83 188 100 70 52 103 21 18 246 56 184 10 91 3 28 85 128 159 165 59 102 11 76 119 243 178 228 95 124 72 5 222 230 172 80 17 155 68 188 88 40 160 181 251 118 144 196 128 211 5 73 75 9 64 106 246 47 184 85 48 64 106 89 237 93 170 122 234 7 23 177 131 232 28 57 88 193 44 154 60 239 42 29 41 160 21 105 168 181 39 202 57 103 107 25 175 104 36 105 18 185 117 10 199 239 139 37 162 64 125 185 202 77 243 90 105 7 132 193 20 40 192 217 45 241 70 186 205 47 87 109 103 32 1 73 40 37 233 42 209 231 156 169 80 40 208 137 222 105 108 170 8 175 200 245 36 64 65 25 149 61 5 58 201 7 50 209 94 104 244 121 216 33 82 218 175 111 220 189 102 160 29 41 229 149 99 138 169 103 255 57 155 232 140 169 79 25 4 74 123 111 78 197 152 64 182 121 236 45 64 26 202 7 1 225 193 168 185 25 248 81 46 250 162 175 154 13 7 32 107 9 127 5 247 234 130 231 114 201 238 168 90 201 5 201 28 234 222 47 253 149 221 64 220 217 112 29 4 250 43 7 209 241 157 232 15 185 44 129 187 26 103 239 113 93 40 160 233 233 149 117 3 74 29 103 215 89 171 104 57 233 160 121 150 138 243 239 180 101 211 64 179 57 194 13 177 218 174 7 130 1 83 40 247 89 22 177 217 58 188 47 66 173 170 32 153 201 201 229 232 170 56 231 12 233 48 40 41 9 193 41 81 42 121 175 85 53 90 64 59 153 128 253 166 186 18 7 244 17 65 104 111 249 84 225 104 90 98 111 235 253 109 160 121 169 186 85 7 10 149 103 243 121 223 232 41 41 6 217 44 202 47 111 191 5 82 64 116 249 75 237 69 154 23 7 7 33 200 168 120 153 136 17 73 122 25 175 77 77 82 32 139 137 8 197 190 106 242 231 106 9 23 168 59 73 16 137 9 106 215 47 210 213 154 64 94 89 196 221 237 122 125 7 155 49 72 232 18 57 81 65 90 154 161 239 72 157 55 160 205 105 83 53 111 202 16 103 83 153 55 104 94 105 127 57 199 10 48 239 109 165 20 64 249 185 137 205 254 90 4 7 144 65 33 40 62 217 78 113 124 186 186 47 189 237 253 32 65 73 59 165 120 42 176 231 140 41 161 40 146 137 243 233 69 170 249 175 114 117 159 64 69 25 60 189 216 58 108 7 198 81 179 104 250 121 33 161 143 218 36 111 138 61 133 160 229 41 96 21 59 138 144 103 247 185 179 232 214 169 12 153 101 74 244 111 192 69 27 64 66 121 123 173 219 26 117 7 29 97 94 168 71 25 105 209 115 250 159 175 144 141 173 32 187 9 98 133 22 234 113 231 114 73 207 168 44 201 106 73 6 234 223 47 55 21 104 64 240 217 231 157 103 250 222 7 117 113 130 232 37 185 197 1 9 26 236 239 175 221 86 160 193 233 224 245 106 74 20 103 223 217 83 104 147 233 173 249 7 138 124 239 182 229 101 64 79 57 33 141 220 218 105 7 174 129 127 40 149 89 215 49 47 58 201 47 200 45 97 32 249 201 124 101 152 170 55 231 28 105 161 40 11 9 118 169 74 42 138 175 31 181 244 64 95 153 199 125 154 186 213 7 168 145 181 104 149 249 61 97 198 90 247 111 185 125 172 160 97 169 213 213 254 10 156 103 11 249 23 232 147 41 99 89 174 202 200 111 81 133 244 64 32 249 122 109 1 154 226 7 67 161 132 168 38 153 153 145 174 122 54 175 99 205 24 32 251 137 139 69 254 106 1 231 138 137 23 168 45 73 21 9 19 106 248 47 44 85 69 64 146 89 219 93 113 122 80 7 95 177 76 232 72 57 138 193 199 154 70 239 166 29 134 160 197 105 62 181 246 202 39 103 123 25 0 104 216 105 44 185 88 10 217 239 143 37 199 64 181 185 136 77 74 90 223 7 220 193 109 40 252 217 175 241 241 186 231 47 99 109 212 32 193 73 142 37 72 42 207 231 188 169 49 40 148 137 72 105 95 170 42 175 92 245 89 64 137 25 35 61 236 58 79 7 154 209 71 104 64 121 170 33 13 218 217 111 120 189 227 160 237 41 27 149 82 138 183 103 47 57 12 232 96 169 9 25 7 74 173 111 114 197 221 64 14 121 74 45 183 26 96 7 121 225 58 168 21 25 26 81 249 250 220 175 198 13 148 32 75 9 133 5 118 234 160 231 178 201 239 168 62 201 15 201 47 234 32 47 177 149 50 64 68 217 158 29 11 250 209 7 89 241 166 232 123 185 158 129 150 26 177 239 45 93 197 160 217 233 107 117 18 74 75 103 39 89 60 104 45 233 250 121 185 138 69 239 248 101 56 64 43 57 192 13 72 218 100 7 26 1 236 40 115 89 216 177 196 58 22 47 142 173 87 32 153 201 111 229 135 170 118 231 108 233 81 40 45 9 107 41 132 42 219 175 41 53 207 64 195 153 78 253 205 186 216 7 156 17 106 104 251 249 102 225 99 90 204 111 199 253 42 160 137 169 48 85 54 10 227 103 99 121 144 232 61 41 0 217 111 202 161 111 35 5 215 64 12 249 233 237 252 154 237 7 191 33 129 168 20 153 234 17 84 122 147 175 185 77 31 32 171 137 78 197 125 106 80 231 234 9 88 168 95 73 90 137 92 106 89 47 198 213 47 64 6 89 50 221 52 122 99 7 99 49 145 232 190 57 3 65 117 154 43 239 68 157 20 160 253 105 105 53 190 202 126 103 227 153 8 104 146 105 25 57 42 10 194 239 241 165 185 64 177 185 199 205 213 90 250 7 104 65 250 40 250 217 80 113 167 186 84 47 73 237 234 32 129 73 33 165 87 42 46 231 44 41 2 40 214 137 221 233 184 170 155 175 134 117 84 64 13 25 74 189 63 58 114 7 174 81 28 104 198 121 115 161 202 218 206 111 166 61 130 160 53 41 22 21 170 138 30 103 167 185 164 232 42 169 70 153 232 74 166 111 100 69 224 64 26 121 89 173 210 26 139 7 21 97 87 168 35 25 11 209 190 250 89 175 60 141 186 32 27 9 232 133 21 234 15 231 50 73 80 168 144 201 244 73 153 234 161 47 107 21 61 64 216 217 149 157 238 250 4 7 125 113 11 232 17 185 183 1 100 26 182 239 235 221 115 160 49 233 54 245 249 74 194 103 175 217 100 104 7 233 135 249 170 138 78 239 122 229 74 64 71 57 159 141 243 218 159 7 198 129 152 40 145 89 25 49 154 58 163 47 148 45 142 32 121 201 162 101 183 170 245 231 252 105 66 40 143 9 160 169 253 42 108 175 115 181 233 64 103 153 21 125 65 186 27 7 208 145 94 104 161 249 207 97 65 90 225 111 21 125 233 160 241 169 203 213 173 10 106 103 251 249 72 232 39 41 221 89 113 202 186 111 53 133 249 64 56 249 152 109 56 154 56 7 123 161 189 168 66 153 123 145 57 122 48 175 79 205 101 32 155 137 81 69 61 106 223 231 138 137 216 168 209 146 150 18 128 212 237 94 114 170 100 128 219 178 242 186 128 244 37 14 64 98 255 208 216 114 240 130 225 52 234 222 135 58 46 64 114 210 200 106 99 148 84 206 56 50 14 208 199 210 228 114 187 20 175 222 185 74 184 128 193 114 173 154 162 180 131 14 250 130 81 80 31 178 219 226 101 116 172 94 0 218 154 64 137 146 72 74 246 84 99 206 251 82 1 80 159 18 61 210 120 84 82 94 211 234 45 128 9 50 66 122 86 116 163 14 54 162 21 208 135 242 112 66 204 180 16 222 42 122 137 64 2 82 66 42 251 20 244 206 32 114 70 208 152 82 223 50 120 148 87 222 126 138 133 128 179 242 240 90 92 52 5 14 180 194 11 80 18 50 240 162 212 244 150 94 199 26 186 64 221 18 246 10 50 212 134 206 103 146 157 80 180 146 11 146 121 212 62 94 124 42 127 128 191 178 249 58 116 244 40 14 52 226 243 208 190 114 153 2 63 52 191 222 149 186 236 64 26 210 163 234 90 148 155 206 144 178 198 208 241 210 1 242 60 20 136 222 139 202 218 128 45 114 156 26 94 180 142 14 118 2 142 80 141 178 172 98 203 116 9 94 86 90 225 64 185 146 139 202 53 84 178 206 91 210 129 80 81 18 2 82 130 84 179 94 109 106 88 128 253 50 25 250 217 116 182 14 58 34 154 208 125 242 105 194 57 180 245 222 200 250 87 64 186 82 237 170 130 20 75 206 136 242 142 208 210 82 76 178 9 148 64 222 224 10 184 128 47 242 175 218 167 52 32 14 64 66 216 80 144 50 17 34 74 244 3 94 173 154 16 64 29 18 9 138 1 212 229 206 215 18 174 80 118 146 32 18 147 212 175 94 166 170 185 128 195 178 160 186 135 244 75 14 72 98 8 208 196 114 226 130 188 52 180 222 195 58 203 64 226 210 30 106 114 148 2 206 8 50 159 208 59 210 190 114 222 20 129 222 125 74 29 128 185 114 43 154 57 180 185 14 18 130 234 80 27 178 29 226 80 116 134 94 204 218 71 64 9 146 110 74 149 84 33 206 219 82 34 80 35 18 103 210 171 84 52 94 39 234 162 128 17 50 144 122 125 116 233 14 94 162 62 208 147 242 2 66 199 180 250 222 134 122 70 64 146 82 56 42 42 20 194 206 16 114 247 208 44 82 89 50 187 148 73 222 98 138 10 128 203 242 14 90 19 52 91 14 236 194 196 80 46 50 210 162 223 244 144 94 179 26 135 64 125 18 188 10 241 212 100 206 103 146 222 80 88 146 213 146 204 212 64 94 240 42 20 128 231 178 231 58 187 244 142 14 124 226 60 208 234 114 203 2 90 52 201 222 17 186 201 64 202 210 57 234 169 148 137 206 160 178 151 208 165 210 27 242 159 20 154 222 143 202 127 128 101 114 90 26 53 180 4 14 206 2 103 80 201 178 46 98 246 116 35 94 98 90 206 64 121 146 241 202 20 84 176 206 123 210 226 80 21 18 108 82 245 84 213 94 1 106 13 128 69 50 167 250 64 116 60 14 162 34 3 208 201 242 59 194 116 180 31 222 100 250 84 64 138 82 35 170 241 20 89 206 184 242 127 208 166 82 6 178 140 148 114 222 4 10 125 128 135 242 13 218 158 52 182 14 184 66 209 80 236 50 51 34 149 244 61 94 217 154 29 64 253 18 15 138 0 212 3 206 23 18 47 80 90 146 42 18 38 212 241 94 90 170 142 128 43 178 206 186 14 244 241 14 208 98 145 208 48 114 84 130 23 52 254 222 127 58 232 64 210 210 244 106 1 148 48 206 88 50 176 208 47 210 24 114 129 20 211 222 193 74 2 128 49 114 41 154 80 180 111 14 170 130 3 80 151 178 223 226 187 116 224 94 24 218 116 64 9 146 20 74 180 84 95 206 59 82 195 80 39 18 17 210 94 84 150 94 251 234 151 128 153 50 94 122 36 116 175 14 6 162 231 208 31 242 20 66 66 180 100 222 98 122 131 64 162 82 174 42 217 20 16 206 128 114 40 208 64 82 83 50 126 148 187 222 198 138 15 128 99 242 172 90 74 52 49 14 164 194 253 80 202 50 52 162 106 244 10 94 31 26 212 64 157 18 2 10 48 212 194 206 231 146 159 80 124 146 31 146 159 212 194 94 228 42 41 128 143 178 85 58 130 244 116 14 68 226 5 208 150 114 125 2 245 52 83 222 13 186 38 64 250 210 79 234 120 148 247 206 48 178 232 208 217 210 181 242 130 20 44 222 19 202 164 128 29 114 152 26 140 180 250 14 166 2 192 80 133 178 48 98 161 116 189 94 238 90 59 64 185 146 215 202 115 84 46 206 27 210 195 80 89 18 86 82 232 84 119 94 21 106 66 128 13 50 181 250 39 116 66 14 138 34 236 208 149 242 141 194 47 180 201 222 128 250 209 64 218 82 217 170 224 20 231 206 104 242 240 208 250 82 64 178 143 148 36 222 168 10 194 128 95 242 235 218 21 52 204 14 176 66 74 80 200 50 213 34 96 244 247 94 133 154 170 64 93 18 149 138 127 212 161 206 215 18 48 80 190 146 180 18 57 212 179 94 142 170 227 128 19 178 124 186 21 244 23 14 216 98 154 208 28 114 70 130 242 52 200 222 187 58 133 64 66 210 74 106 16 148 222 206 40 50 65 208 163 210 242 114 164 20 165 222 133 74 103 128 41 114 167 154 231 180 165 14 194 130 156 80 147 178 33 226 166 116 186 94 228 218 33 64 137 146 58 74 83 84 29 206 27 82 228 80 171 18 59 210 145 84 120 94 79 234 12 128 161 50 172 122 75 116 245 14 46 162 16 208 43 242 166 66 61 180 78 222 190 122 64 64 50 82 164 42 8 20 222 206 112 114 217 208 212 82 205 50 193 148 173 222 170 138 148 128 123 242 202 90 1 52 135 14 220 194 182 80 230 50 22 162 117 244 4 94 11 26 161 64 61 18 200 10 239 212 160 206 231 146 224 80 32 146 233 146 242 212 196 94 88 42 190 128 183 178 67 58 201 244 218 14 140 226 78 208 194 114 175 2 16 52 93 222 137 186 3 64 170 210 229 234 199 148 229 206 64 178 185 208 141 210 207 242 229 20 62 222 23 202 73 128 85 114 86 26 99 180 112 14 254 2 153 80 193 178 178 98 204 116 215 94 250 90 40 64 121 146 61 202 82 84 44 206 59 210 36 80 29 18 192 82 91 84 153 94 169 106 247 128 85 50 67 250 142 116 200 14 242 34 85 208 225 242 95 194 106 180 243 222 28 250 206 64 170 82 15 170 79 20 245 206 152 242 225 208 206 82 250 178 18 148 86 222 204 10 135 128 183 242 73 218 12 52 98 14 40 66 67 80 36 50 247 34 171 244 49 94 177 154 183 64 61 18 155 138 126 212 191 206 23 18 177 80 162 37 45 36 1 169 219 191 230 90 204 11 189 137 249 184 38 101 144 195 103 206 168 55 193 205 59 204 206 97 220 39 146 130 85 163 206 113 218 96 95 29 240 107 121 118 147 207 54 245 13 116 136 25 218 15 211 170 29 59 97 217 208 8 132 213 166 147 38 30 201 103 235 157 143 28 86 209 192 119 189 210 36 211 136 193 24 176 12 141 144 59 83 198 153 255 162 197 204 196 255 137 92 95 133 250 10 107 185 41 14 88 17 69 105 99 3 110 245 151 129 109 241 108 237 65 89 199 126 34 16 3 199 17 45 0 74 253 204 11 91 22 108 47 11 149 73 20 39 249 66 175 29 74 20 155 229 121 17 168 14 181 55 51 158 190 45 199 35 61 65 188 84 177 133 23 246 114 151 51 169 97 119 80 87 109 5 219 48 102 10 95 15 101 100 100 190 105 109 255 187 154 185 203 5 201 58 248 187 37 114 3 151 14 114 247 113 13 95 12 76 33 38 103 67 194 58 99 79 177 87 160 116 221 153 171 116 182 116 143 79 53 253 180 133 217 187 79 128 234 122 251 57 25 234 72 89 149 120 211 141 94 194 39 12 221 42 92 147 145 26 183 134 18 122 147 217 1 46 240 225 77 234 123 198 6 171 191 107 5 244 4 61 73 14 159 138 58 216 43 162 105 127 152 38 5 171 163 34 174 30 87 146 173 132 172 234 1 67 7 224 98 213 195 103 81 90 64 223 189 86 75 198 86 173 239 3 213 40 84 164 185 68 239 250 138 81 91 94 185 90 232 99 117 105 115 245 254 134 135 164 125 76 252 17 113 127 87 111 178 204 243 25 161 60 144 172 45 63 27 20 166 123 31 184 165 123 164 123 41 63 63 241 218 102 139 142 9 220 56 81 229 19 67 166 78 251 183 226 77 98 76 201 225 175 167 84 2 224 35 16 241 52 224 137 157 3 235 79 246 21 79 40 117 204 244 130 153 221 143 141 42 152 187 82 89 99 136 46 85 10 19 213 158 123 231 236 29 166 156 208 81 180 247 175 82 143 83 106 65 163 48 183 13 4 187 25 70 124 127 244 69 251 68 122 9 0 223 239 122 101 235 202 169 80 216 59 197 172 227 33 238 7 23 99 237 247 236 231 193 108 71 161 162 90 131 72 145 231 128 116 125 160 139 17 150 174 175 188 21 232 148 33 121 134 47 56 202 78 27 23 249 3 40 185 53 91 179 44 62 160 71 229 189 55 60 207 49 185 151 72 242 193 179 202 225 97 208 1 237 56 91 215 230 172 223 32 229 114 228 56 233 80 127 134 26 212 75 87 73 221 120 230 165 117 131 149 142 68 119 19 141 69 140 70 161 121 231 197 66 69 227 16 49 114 32 159 93 45 43 11 54 119 15 193 181 123 52 128 89 63 207 250 106 117 123 171 153 60 200 3 21 92 83 252 222 244 167 141 93 1 220 13 17 142 55 57 146 100 19 59 129 120 112 140 205 221 251 76 134 13 63 61 133 226 132 183 201 49 31 181 186 178 171 51 233 129 24 81 133 110 35 1 46 177 215 243 45 75 44 229 129 214 135 194 226 159 67 105 209 212 192 9 61 170 203 60 214 111 111 53 85 135 212 158 57 8 111 213 10 12 219 15 57 13 104 14 245 12 243 67 126 121 7 230 253 2 124 140 241 50 215 129 50 119 115 187 33 231 16 87 173 242 155 122 38 158 159 73 37 74 36 246 169 162 191 123 90 1 11 96 137 62 184 123 101 151 195 100 206 77 55 4 205 8 204 195 97 131 39 151 130 106 163 81 113 15 96 180 29 23 107 166 118 152 207 25 245 10 116 125 25 225 15 200 170 18 59 68 217 117 8 217 213 109 147 3 30 46 103 238 157 60 28 75 209 167 119 34 210 249 211 75 193 173 176 97 141 119 59 96 198 94 255 69 197 169 196 244 137 163 95 218 250 191 107 220 41 19 88 102 69 240 99 192 110 26 151 68 109 126 108 226 65 128 199 67 34 165 3 202 17 34 0 159 253 115 11 72 22 241 47 110 149 6 20 28 249 201 175 210 74 137 155 72 121 118 168 99 181 126 51 59 190 18 199 166 61 174 188 73 177 236 23 27 114 236 51 236 97 204 80 172 109 108 219 253 102 79 95 50 101 1 100 179 105 52 255 208 154 238 203 168 201 255 248 16 37 121 3 20 14 23 247 180 13 172 12 65 33 205 103 200 194 79 99 210 177 12 160 201 221 192 171 33 182 121 143 50 53 122 180 122 217 194 79 245 234 111 251 28 25 15 72 174 149 63 211 234 94 39 39 15 221 87 92 136 145 1 183 107 18 79 147 156 1 67 240 54 77 209 123 83 6 112 191 14 5 81 4 50 73 85 159 95 58 141 43 197 105 4 152 123 5 50 163 95 174 67 87 85 173 145 172 223 1 106 7 37 98 106 195 106 81 207 64 52 189 253 75 51 86 50 239 102 213 101 84 153 185 203 239 47 138 198 91 193 185 63 232 184 117 176 115 18 254 107 135 39 125 57 252 6 113 230 87 20 178 33 243 92 161 17 144 1 45 166 27 97 166 192 31 219 165 152 164 112 41 6 63 134 218 155 139 49 9 33 56 166 229 26 67 163 78 160 183 37 77 47 76 190 225 86 167 89 2 245 35 147 241 105 224 222 157 42 235 124 246 26 79 11 117 201 244 119 153 228 143 130 42 141 187 53 89 8 136 131 85 209 19 178 158 224 231 239 29 83 156 197 81 155 247 20 82 100 83 45 65 56 48 12 13 235 187 38 70 65 127 151 69 216 68 111 9 71 223 68 122 26 235 237 169 85 216 144 197 51 227 222 238 44 23 38 237 132 236 220 193 147 71 102 162 239 131 75 145 220 128 201 125 71 139 254 150 51 175 31 21 165 148 22 121 13 47 237 202 195 27 122 249 104 40 14 53 162 179 201 62 133 71 104 189 164 60 196 49 32 151 109 242 22 179 13 225 182 208 86 237 159 91 164 230 241 223 67 229 15 228 45 233 23 127 155 26 9 75 250 73 162 120 59 165 124 131 18 142 233 119 86 141 146 140 59 161 32 231 74 66 90 227 147 49 39 32 244 93 84 43 184 54 124 15 164 181 248 52 117 89 70 207 111 106 106 123 142 153 97 200 88 21 35 83 89 222 89 167 144 93 46 220 2 17 117 55 30 146 57 19 254 129 141 112 225 205 196 251 217 134 210 63 224 133 63 132 172 201 120 31 138 186 103 171 86 233 6 24 166 133 245 35 62 46 214 215 182 45 88 44 218 129 253 135 7 226 52 67 108 209 73 192 94 61 81 203 169 214 244 111 152 85 196 212 147 57 143 111 10 10 129 219 114 57 242 104 99 245 83 243 96 126 94 7 105 253 239 124 129 241 153 215 38 50 204 115 254 33 188 16 172 173 89 155 199 38 227 159 108 oolite-1.82/tools/resource-management/000077500000000000000000000000001256642440500200505ustar00rootroot00000000000000oolite-1.82/tools/resource-management/copy-game-resources000077500000000000000000000023031256642440500236650ustar00rootroot00000000000000#! /bin/sh if [ $# -lt 2 ]; then echo "error: usage: copy_game_resources " exit 1 fi SOURCE_DIR=$1 DESTINATION_DIR=$2 if [ ! $COPY_HELPER ]; then COPY_HELPER="$(dirname $0)/copy-resources" fi function perform_copy_base { "$COPY_HELPER" "$SOURCE_DIR/$1" "$DESTINATION_DIR/$2" "$3" if [ $? -ne 0 ]; then exit $? fi } # Usage: perform_copy # Copies modified files from src/ to dst/ function perform_copy { perform_copy_base "$1" "$1" "$2" } # Usage: perform_copy_binary # Copies modified files from src/Binary/ to dst/ function perform_copy_binary { perform_copy_base "Binary/$1" "$1" "$2" } perform_copy AIs .plist perform_copy AIs .js perform_copy Config .plist perform_copy Scenarios .oolite-save perform_copy Scripts .js perform_copy Shaders .vertex perform_copy Shaders .fragment perform_copy_binary Images .png # N.b.: Images .bmp deliberately excluded, being Windows-specific perform_copy_binary Models .dat perform_copy_binary Music .ogg perform_copy_binary Sounds .ogg perform_copy_binary Textures .png oolite-1.82/tools/resource-management/copy-resources000077500000000000000000000027401256642440500227630ustar00rootroot00000000000000#! /bin/sh function fail { # Print an error and bail. error: prefix is used for the benefit of build tools. echo "error: $1" 1>&2 exit 1 } function verbose { # echo "$1" : } function debug { # Uncomment for debugging. # echo " DEBUG: $1" : } if [ $# -lt 3 ]; then fail "usage: copy_resources " fi pushd "$1" > /dev/null SOURCE_DIR=$(pwd) popd > /dev/null mkdir -p "$2" pushd "$2" > /dev/null DESTINATION_DIR=$(pwd) popd > /dev/null FILE_EXTENSION=$3 cd "$SOURCE_DIR" FILES=`ls *$FILE_EXTENSION 2> /dev/null` if [ "$FILES" = "" ]; then echo "warning: no files matching *$FILE_EXTENSION found in $SOURCE_DIR" exit 0 fi for FILE in $FILES; do DESTINATION_FILE="$DESTINATION_DIR/$FILE" SOURCE_FILE="$SOURCE_DIR/$FILE" # If the destination file exists, don't copy if it has the same timestamp COPY_THIS_FILE=1 if [ -e "$DESTINATION_FILE" ]; then SOURCE_MOD_DATE=$(stat -f "%m" "$SOURCE_FILE") DESTINATION_MOD_DATE=$(stat -f "%m" "$DESTINATION_FILE") if [ $SOURCE_MOD_DATE -eq $DESTINATION_MOD_DATE ]; then verbose "$FILE is up to date" COPY_THIS_FILE="" else debug "$FILE is out of date (src: $SOURCE_MOD_DATE dst: $DESTINATION_MOD_DATE)" fi else debug "$DESTINATION_FILE does not exist" fi if [ $COPY_THIS_FILE ]; then echo "Copying $FILE" cp -p "$SOURCE_FILE" "$DESTINATION_FILE" if [ $? -ne 0 ]; then fail "Failed to copy $FILE" fi else debug "Not copying $FILE" fi done oolite-1.82/tools/sysdesc_key_table.plist000066400000000000000000000036661256642440500206730ustar00rootroot00000000000000{ /* Keys beginning with tharg_ are used directly by thargoid_curses, but many are also used by system description blocks. Note that thargoid_curses also uses some weird combinations, like [tharg_creature_noun_2]-[tharg_creature_noun_4] ("arts graduate-fish") and [tharg_strange_adj] [tharg_tourist_attraction_2_noun] ("strange parking meters"). */ // system-description-string = "[planet_root] is [planet_attr_root]." 14 = "planet_root"; 22 = "planet_attr_root"; 9 = "main_dispatch"; 32 = "secondary_dispatch"; 0 = "famous_adj"; 1 = "degree_adj"; 15 = "boring_adj"; 16 = "planet_noun"; // planet, world, dump. 7 = "cursed_adj"; 26 = "dangerous_adj"; // Used for creatures, drinks and plantation plants. 12 = "exciting_adj"; // Used for [entertainment_noun] or creature-based foods. 21 = "disaster_frequency_adj"; 8 = "disaster_noun_phrase"; 24 = "tharg_creature_noun_root"; // Builds a creature name frome one of the following 30 = "creature_noun_1"; // Also used as food ingredient. 31 = "tharg_creature_noun_2"; // Also used as [exciting_adj] food ingredient. 17 = "creature_noun_3"; 6 = "-oid_prefix_creature_noun"; // Things always used with an -oid suffix by [tharg_creature_noun_root]. 18 = "tharg_creature_noun_4"; // Art graduate, atc. 23 = "creature_attr_adj"; 19 = "forest_adj"; 3 = "tourist_attraction_noun"; 27 = "tharg_tourist_attraction_2_noun"; 2 = "tourist_attraction_adj"; 29 = "plantation_1"; 28 = "plantation_2"; 25 = "behaviour_adj"; // used in conjunction with [tharg_behaviour_noun] 20 = "tharg_strange_adj"; // Subset of [behaviour_adj] 4 = "tharg_behaviour_noun"; // shyness, mating traditions, love/loathing of [tharg_object_of_emotion_noun] 5 = "tharg_object_of_emotion_noun"; 13 = "entertainment_noun"; 34 = "sport_adj"; // ice, mud, zero-G 35 = "sport_noun"; 33 = "tharg_food_type_noun"; 10 = "drink_type_noun"; 11 = "drink_attribute"; } oolite-1.82/tools/updateberlios-linux.pl000077500000000000000000000047611256642440500204560ustar00rootroot00000000000000#!/usr/bin/perl # This tool is designed to automate the incredibly tedious task of # updating the berlios.de download pages. # # Usage: tools/updateberlios.pl # # eg. # tools/updateberlios.pl src 1.65-dev1 # # It looks for the autopackage (which gets dropped by makeinstaller # into the root), and the tarballs in TarballInstallers directory # (this is where the source packages and tarball installer gets # deposited). Common functions that access the BerliOS website # are in UpdateBerlios.pm. This module can be re-used for, say, # updating the Windows side of berlios.de. # # Warning: this is rather quick and dirty. It also only updates x86 for # binary packages (since that's all we are hosting on berlios at present) # # Dylan Smith, 2006-03-16. # License is the same as Oolite. use lib "tools"; use UpdateBerlios; use strict; my %SRCPAGE=( 'dev' => 'https://developer.berlios.de/project/admin/editreleases.php?package_id=2804&release_id=7423&group_id=3577', 'stable' => 'https://developer.berlios.de/project/admin/editreleases.php?package_id=2786&release_id=8856&group_id=3577'); my %BINPAGE=( 'dev' => 'https://developer.berlios.de/project/admin/editreleases.php?package_id=2803&release_id=7424&group_id=3577', 'stable' => 'https://developer.berlios.de/project/admin/editreleases.php?package_id=2785&release_id=8855&group_id=3577'); my $berlios=new UpdateBerlios; my $arg=shift; my $version=shift; if(!$arg || !$version) { print("Usage: tools/updateberlios.pl < \n"); exit(255); } my $urlkey; if($version =~ /dev/) { $urlkey='dev'; } else { $urlkey='stable'; } # .updateberliosrc should contain username and passwd on separate # lines my @login=`cat ~/.berliosrc`; if(scalar(@login) < 2) { print(".berliosrc must contain a username and passwd on separate lines\n"); exit(255); } $berlios->connect(@login); if($arg eq "src" || $arg eq "all") { my $srcfile="oolite-$version-src.tar.gz"; my $datafile="oolite-$version-data.tar.gz"; $berlios->deleteFiles($SRCPAGE{$urlkey}); $berlios->addFiles($SRCPAGE{$urlkey}, $srcfile, $datafile); $berlios->setFileArchitectures($SRCPAGE{$urlkey}, 'any', 'srcgz'); } if($arg eq "bin" || $arg eq "all") { my $tarball="Oolite-Linux-$version-x86.tar.gz"; my $package="oolite-$version.x86.package"; $berlios->deleteFiles($BINPAGE{$urlkey}); $berlios->addFiles($BINPAGE{$urlkey}, $tarball, $package); $berlios->setFileArchitectures($BINPAGE{$urlkey}, 'x86', 'other'); }

    $latest_name ($latest_date)"; ?>

    vFhlƂƚ*f9>=5dCdNX4qΦ.00v!ƣ,U}6+7 d29tѮת'Clnҍ!r ֖ObmEZ[FuL$զ{ Va`W*^9D|Y[{D&%9r?x ]l>d)^ݶe ._ãCoVH- EQe}}Wt1gaP!/F\딢Y %'HXQmfsy0dR3DCȭx,MVj<:>6ث0"(4z4$BTWժf4* !O`N]_>t{slʲ,:ܩ z _7˗@xZ+wx].)F}[`gl*T7NJ"SVu3p;vkdlUx\26Mu)%`EQuYTƘfmU_Wo[Ucp4ڛ=܁!dٶ]c=99e9ؙ " 1 ;EuSخh죧O stO뚌׿:q(X$(˂#CsD F{@3PDWCԊG"RKށ8dlpsu ѸjU_Gf&ӊGF#h4Oxr}}}ѣЇz1>I̋żBf]pȰ`3y`@@3Y"8@)٦isTĈWO1TVfGG(RDZy_RSbE&Hatm7=~o{ r5͗UQs%B~ӓ㺪x2m{yy5ۧgֹ|~}y}{}۷֒H #"͐R/˲Er1w`2fi1uMChmnGi7l*|mYqs"Cz!2)ˮY Pj(;;q'A:h:`7qd|V%PSyc p> Xk|Qb>YrX7eX M H!uS){ٶ֛?~|UmONN9::޶Ѥ9?8<:<99ٳgr\Ŕ3bY׫R,3)Oψh%cnѹ_C *XQgQd5:gCj+at6>{6Oh6屾bLa$:ňLmԖBK߯Jعw_/_Xm9Iyjw}I][uI)qA8ѠQ[L0:_(+E CYD)QՊ,.I)7; ںk1W@DD@"$Ї/kISך'kyp], BH @ 1vZ.Gl`|,*0oP$۴v(v_W77uH-r+g~ֱc$I14eQOFc.dsvRB@ka/o=W ȨI իjVҚ ' uI۴13tK3vx#2cU ./߼b6? G )U9˪$23 e %UT`$W8ECP6n۬W([v{*ONߟ 1?%{nV_`:s3c, \~\[l>b IDATjsqv~ыP|'Ի] !hOT(Vsc&w޿?=3d}ջo6|[_y=\.JSjL1[ m[t;ǘ PPFcndSJ!SFiQ)D[Frq)ILF$єDLD6ƶՔ巯PI1ijw'][vW4~6eUU UI()SAU%sq lvǥBM=!8D 꼷 `y?3"lx{K1"rqǣQKBS 8ՠd\{󦮛d04z2'&c߈OyKv Eytx~pжq}sh2ebۦn[:_כvwt|ϿS(IB^"v`4<jArm{t|'OQ>څ7MRmѝ"-Nвy#\܇bW N8:Q~]WM"V""uYVh3D,†뛛rUEQ$ ) *`5 )E5C&Ihʀ@жw˫j0(rCYjFb,7U4AYY=dnC܉+SJ$/neGU5vK0ͭRr=vBb7̏r]oTb4GcSfZmV(QD h1e%U̦L1GF* {}QkQ7^@D%o#^:;[ɲN]tnT  "5;.Hz2sVX爑j%[CQx= .uQUYy_HJb+{ʪaԫf<?=ϟ8m>b&bZWp0#ji\lVkdFF0l^j$*0s^tԜS3+ddE%&)~{_^D@t')1/h]-;XɆmL8;m}f)ƛ붩_Wv.obS6A5E1$6y)ǶPe"S (U"ZQ9P%UUr]*4JΥ>p+d>[Y;]eg?QycNRY.{̧q'-=VA@!6 |Qq+[/6()O?ʖc\_EQ{c١Ad5E;.*Ÿoͺ(,8J@-JjeІ7Ie8:AsL>@f2pZ}Y)~#@L)nCIel\bY6.ދ&o ";ލǣtRo߼]L߮///$z&I IHM#U{QN&GGA?MƘ$]^6M۶mO+/Kx%3x<# :;s5nVd)I-ƣ%#?g/ HwoV`0H!_\6h諪(蒒9,"eUy PD޼zsvvzyyуAYy&Ť Mz O>w^wjZ\^/])EywbK4msFhU;K.O}x]**ؗ#vv!Fc"qJlvyq^TLB mV˛f++8?FGv3iAf$ J_߯Ʈ/d#g[4+3-4>;YQ-knО#c7~~EE +8;7y0q]Xx:G$cZrpo}!I46m۬n"2NjЕ'`lѠzG*AULL슂۶Y\ShEJ̕D<`ݔJLqz$vp>8;mً={t6 m{uuyt݅J )#ΙcL9b;L&f 1¾n*`(iYUj|/0l6G~[aAqaWHc4`4;:M) }3t/oj|NI$?^H5jD| sWɴER0SbS쉼^\n8Ua鋔R'(=;>>"rqq/./WTU;X,RL*жm~7?ŻwoNߟ7mB^iܩ]TQs)gZRbdcs%M.,Y@& 1(ܴ WJ$3G,+sJJL!b1W˛_?sTչJ$ wJeV9m{.\v L9쳌C@1"Iwd1L@ "&?he*Haf0LJzA=xp:?GЩz;+1!AAb6VRǶ,?E:%FUvVT[5e RZW}IYnB F6BTc0o,u!\*q0{ 8f|21]ٗe1NTfqެFܽW jY\-;GkQI8Ѫ\Q5 1ZXNU3;|z}P]hW_|iёAj-;8{:m7bn1(FRʕfѪ:7|E\)-#O]sM";Ǟ٭ne5Ȥ@@tśWN!UeE!zl6|M/^׾>f/^rjf뼿ss~Zummںnv8=~mC8͙3dLsM;5ث}6ݿAddM``І߽~Uaxl>$)"oL͟=ٞC"f a\DpPmpJK)a0˵shD1hg_22P^*0N@hVKM̭R"p4nVU&6Dإ >I复!AT%X[vmL&W`qwKEtöiۦI1sZv}hFQӺiQEǣ+J#wƄNX.R?zڶ!)v%Uo\LN_=X߉2$┙aY,"ޥ(k f2ރ@b[B.{*x4 ),XTo×?bqu%޾~}rΧ|zuu^m$AtuݜWr$QWx.f(fD I$BTP$HsQR†, $Ir 2HTG%:`{'mӨ 3}Y_&1SC:ϭD.Պ/~֧WGC}:7ڝޙ3&OOeƅQ А=i//;OYͳ#ʑ@$@zY (T3u'`._5idEQyTv,n6O&37{ PUTu(Ę,BnCN=KtÈ?c} cbU )Ê+v|y~\6z_[9/{:dQ 3Y'Cɋ*բ$H)罔ĐĎ`NOt~0?:9Bl:%"<&&" NUe`֔PӔ΢IT"#Fr< /_-"`zxzMݺ/*wx灧B$OXӿ(K"fcBh_\01($b&4Zr2{&4/$9H'_FCCȒ^KB6Z9vu]7u~kLz?*?OOO˟[zzŗA[v/W+f1tDqm -6+e 0o;< x e6X)&AJ;xBRm_l~ Cl,蜏QSZ\\گ˶%;)]R'c9EY(;?zqWNj=v"T5VSʆ  @,sԲ/ wx2.v ujtTU5ꚗ*[H1H0/D.I!DPefdcm̗e۴IPvY~W~7~fT_>{f댱NHu68Q9;bnJΩCo.yho6{V /屯CUgOugK `Yxݴm;=hTT%3HfBӬv5C[wz2Enח~y.6ãÃj! jP~u<;?-ٳT 켝zݦ߹brzHEٌ9wX */K}cU& ٬z;@fyos& lD6{uY2]Ye9Η7 ^ChxP31 cZG (6I!1A]:< ~P'Bn"q.z ۬V…Ĕ^߮g6OW׋{8cleQq4OfƟ}O>!\\^6m{tt\U%fs m}Qlf:LƓx]wۭ01!l[jvW>= :yWYyʤTʭYF@ 1JLp72I ۦ !TUiUdDC$>{-W͛/)]/~C8/.~!9;GT91USBs?|xk0'-oAb9EL` wŦٱ+|MkL\jmwGf1f#-ܮ4W13#۶,l)IS$SB{8p\F|YVxZ%Ѳ*Օ/O !:#O$1#ŕ?5%DM;MGb%dr j(Is )'RS!EA(fݓ~UUU*BBb}Kض! 5# {7~cG˟L m{v]UaF6GOb[7VhN"bkDZnB9vq>z3ҎJs94In5sdo(һfݛ7? BŽ;ZfC ?n=6ivjUOIjl{wM'\Wm"*H<%frUI/Z"ͤ-rcι S:Q&,Ӓp IDAT Mb &}lyZi❚LU!Cݩ$-i꺮/$=b>~ M vqu^.W嗾2dW}?WgW{DLdߍW Yrs] Գsw<%"228 kh>hLsVbQNv`vmwj~8/\B0) 8}ut릔&-Bv4eɎEh07JЎOWU5*/n6?wb+y|LM"gk7`v`W޻n/B;UTD\@;XTH1SJR2$$LGGGWWW;وvuazIb~Jxn׫(UfTjP8`lIƪEv',..\vh ؍SP`cKO#${ǀa* 8v(#N9ķ<{w2j۴"BeY ! (9Csh<:?;[֛z\=}d4umWu ONgmf4}x k(U٠>e]mjZ]âBHLE#)ܚXa-J)'SH= hȘߠm4s^Ub CsˋzZ]|[U~0߿sT'"rLhU]A#"5./]cENzKWPȱ!vD((Dѳ;nZhtaYRH9bkvsKJ%vbX۬׃p02'ف%Vc6>y^ڶU_eUIrUӧO˪jzqͲ -fZEM/R=tRt+}| Ll|$4JTQRDd2{a] ӷQ4qIbluml\h4nS)j") ؕUR\\E޾@x8fڿ\!9t3λfBIRn' cJ39순!o RĶlFroof@=J(c ;˛PbjFEsQV74nqԆx1"Wn]V?L'}lB|a-T6M]Ihҙ2ARWf;2~N3<^E^a,hݦ,Qk0ֺ@3 9ږ_ ɽ'oW_Waod+Y{L$VK D1FS#u&)o`S H`hIbP18wfNPz 16&]E2Ӝ ڦY,٬,ʔtB9 L$Ȗnyr rii$L[/O;|G$C$Wެ7퓧o޽}g=}ы_Ի1IHHش)%MkU@*:1 C)JX~mKcf_V#L6Q TS[.ۘBc6,RTe5\_W Wgb C{Y4t~yHC&i4n C}PRҍ }n (>'lVWh<(fyQzb]%=I4hBU@IM^ YCE2P5ń^K"$\ݐ&H@z=1X-:n3$|駏? _}s\.)VƼ#w4I$D.\6TED2^ z1C[4v|!D;A>L66ŤiWc/H*j͐"yڈ)2K͙ RL1(:TR/)ARTUJA$6˫7o~Ewm4GGGGEQ0s۷ߗ?|_iYVѨTFR -1rgʛI$Rgn`0!Cy{OJ ٣L! SL, !!2T&1c!%^ZE$ئc~&9Ŗ$n7/>ٟ\3YޯxGٞ%;̘#}nu{|ݶJQFI}AD_:.wrLNtL]u; 3@wxRZ\-&x2 !\o]я3cDizɴ*cmCJlC|Q6F{ongϞˋ7_:<:zIljz"wۘB2 *I ש]׆ڂQ hds!3L{M1Yۋ}Tع=NǓC[ש )^41$ѱM Q cg$ιs/)Sb٣eBw]ç{0|w$ƭˮlEFȷ{@DY6KKG޶o+.J (a\em9UrUbX?%}= <~7{`0vmQ~{~6ÇoWWzoS#vvOA$U$4ʘ:=@a,d21h⑑r5 [׊E QT*)I dhWc!=;)M5ZL);oﯗ~{#SsFet` ̨>vz6g5 9!0#Oh9+fi|Db-5K}9 r]m.Y q `FVg#"X$јjLRmZ6K1:z[EὊ}h:ݬV1DeDgGǓlZO^j I4e'Zw:fs̨λ 013<ū5 8Eb!$j@`$O1/=;7,)()Ȧڤ,Dh߯WK_ ŕ=<:UE\sҐL%v?|C6"!9sewd D"M"Hzk[l1hj~/"D#.hjT4+8{_32ş;\t2 Ljrn)DbnܟEaUɝj4TU noP\2Ɣ-eT 5 fPf v@%fB~VIhfeTvÎu[:;>ED$[vg @1RI${GuSE阻)`us_E#ay lۅvn\ 3R&UdHg$I=w4;G4,(ASJ }AQ00t}U#PnŢ(x*);1$TYr=(b(٢OԻ96RD6󣃪BӜOf2ԁVCH!ӳ}S?} xŋF~ !" G ̞ wTQ U em@M<tQ,֧vX;k\R>U4Ơ)w"2dAj%1s!A*KƎCu"TMȼ'`"VL2O#]Z V!j0#HS׻m`ػB5EJNi݌& @Ӗt=z(/[&ZƱйr1-`q!f̽Dh'"jVlXL)mdg7d#^0 qH,FAʲRrYPR3~Z.C/F;wˋІo|[λ;'G'I$ĐR$;oMՀ$0gd" JlgkM yұ,(";%&w*./춻˫fb_[QEEM߾Gz+Ba)g2< f3&mf0GRdj#d*MȌuLscn!JeaTQMp733;Ƕ d(.֛G9^,R`4,onʲ`@ Qn֓d0_O/x6?~(\/כuk6IRXxh"b=8Ԑ^3v'.j#0UgB{OݶdtcuUfB\)yQv;TEY2[EA9EY:sm]?_\]}SWcmDz9Fu=))>d"V-] A\ ( M%,37h@%j0t/on|vRvKz@Zp^-4S6!|]*)жIF%1"Cf?ʧϟWڧ mWj/xܶݛ`pCUm۶,׳uTR)Lh3ZnOJ@%s[}*Bt{#rEY0!1lSKvdt'xqzvsfކR9[ ev["r6?$ndSV;oM }>x؈D;,v t6[wK#E ^$7Օ );cԃ#Upqݼg/PҗZrFt'ntk'"I^Vx:JJ_~7֛z~mg H@"·⌒ 1:xݚ eAF1dYҾw"vP ;6cr]@1$s@CHȎSNGn wz*Ic$FB_PDRE,="xTف@R&i&}lJ3FTfڧckFVB"4RI0H"%~8Vc˛uR) *r0/.TJ}5"6TU92?r5Q'[P{Et^EҶ]7XP0i>A;w˜n20T])[,^oIv\ ҫރp`B z4"@t26G[I?O??~Wŏw muf"`4%CT"[WdUMA EaM̤I ќ gJ(!!*A {DPHbB{LDBF)EBvZDܵ1%D9s:=#*T+hz5N+"d@?l~ n-Qc[ct&,$e"u#n։(殣(r;"@IRHh} +@EZq~|"ׅE֚4+0*)]UbjzۍcS""5m *U46w> M%&D9sL77˳g>yqjnxFmӊ@g^_xO)jBDB&1\?P&΅&߼d&$kP_$&gt-JllJLjMSL|8 ɸ&b؄n3 742J-ͧ5Xcf66ǃP%+I1mf j )92o2$7{-Tcx̐!mVb46eQU"6mwYtf;O6]k} *n]4!,oUY޹s<ض!ns}vuo?@]g6c3t8_*m-o:,UBW&+g-tR6X-:`0+y#kk*9F;v~EQjNh_8; _{Gu:bvDyoP66wJDt]{%4  $eB>wɟҎy^vYATμ(#(ȱDK~0,$:hwrl^E# uɪ)y9*HJc۶~7N$f-ʲdX>Qzh-by:?;?sbJ?g.f']˭~Mz$˲3f>{ 9OUYC' hO lv7 PB(B҂ BuK=UfeFD1n{s8YrW(dfx="B9g)w|j|Z>s79 ̮i&/G (fU= ojsϞ=kf3RJi08T-淦2ͫקo"ߍumux%C;3\Mw;;ҶIT![Tߝ$;uccPr/iH8aĺwМSWsgo^~GF$3S`rqVM"v8/h^/ooolwi7x.9{ Lg̲|mSNSӗ dFU@+ 㹻vm^8Pxj@@@D)QW(QU0f6tx`#V|cUomӌGg''goO?.}FP =ht.&aWs sff1x±rKhҼb rEeZZכ.ڔ1)R1F0XUXDx>$@Sb3:VU?D6^o~}ĕF1T77ovw>c6o?ZD܊UEZ[@f98dtKC*Ɗ} c >lH"ٝDD9V=}dgow3UՔ,rztZ6fӤ?LrgndR ]iw~1ff"^_}M+,]ZX~%K?ZgZ~ě h`1F@t|ԍ#KR?u-r+-粎h.+h1ƪ[&zضh4zuvիģ7\><:C -g.R*wD8;S-(Z-k\?PLw )#`Ւ &IʴtUZW*ԦH4 b Tyq ҲΒsmYz~]P.GP|\hNwLvv7 b./8?C=wt6iS} r\GbݯQ@bq<)fg:xߤ|İnљsZ֛rhs*J479p;Z=aUuvuR?>[1O%tH@$VPK:Q|ɁNY\ˀeC$Kxw53Uu$/I}X8jݖ0qh?89=}?_&>=GP.U)J4usn>e΂Ӭ&ǵ->sh6@ wY`] b^wt|lRJ8T ?"gCy{1T?&zUdXlBYLM7^o`h^-C]巏?яlI.x S&Tr疝NHElKmPfШ\j%j=5wPPY]` \>[fbe[^`2ySʭ";q`4ݙ1QiD2!!Y]W)5~ghϞ|qqj) usG$r1U}}Y,5r13nva1ׇƐ˾ `ر ^`%"d!P*YY6/$~ESPQ*LwL[c矔cAAl5%x0zmfm̊@()A (8" *,$𑠏 Iء>;W@,j=QtRۗiHF?$đcP7f5C0aY&ƊFhV%1ƿwO~L<9?u'6ADT yLZm&I4eIm9AXյcT%bݻLLJҜ&㣋7r)9C7B!.7>dׯTdVvnwzLCUźy88P, ?F8YrjMnFtIs"ʧ"dQAd,dUM9B /Ni3 L~6Uz<{=S8y s||x!' PSdR`8FA?&KX vG+ax0i[^?w3 nd-*)S 0B{B٪]==$mF3I,o%loo9mM7Vqh)#eC``DP'fj!C ISv}#yVSPNeI" [[7;]TcBEx/lNEJޡcTslD&"ݩ}Hb5MC@ >E`8Pؐ5hMjRkaX,s\l_?cz.Cvl>`+v/{[S2OJҶ"Y$,Vb""e|ː9j&xnXNW9TqXHz}}=mpA,K?EsV&I6mc^}LڜR_%KΥܯڋ{\\ \u0Wo={ hmj>f2^!Q "-1oOOO%*n0;JR",*MiL ELYrisMImm<9?C"9F:{{vsu=F44)gU*t~{]hh秧HԶt6z" fA:IUUWM+cJsm #0ܳE~}pWI4P%Tn eSJH%s ԲbR@5\梦>Ft!2Z.C8󍔯U߫b/MG3lV_E޽Nu)$urʆ@}F!11Oѓ8GYi>_=8yѓ;~~rfnȞ\2^9dFcPrQEb&e 'IPNd2$),_pv|U=L6=FmwvvGө C:n}  m-76qd(AAQ ѲwE:d.9MU !79T iswÇf*֬-[%{{=2Vud^`8D"x=D~m[/ I;R&1iۘxκ)dRoc.jFF@ŋ'ĎDT0p)("f SSR5ʡ̄(%A32D#!kBH *ng ^s+!􂫩' DfcjXr6wx.ڶ%d"7:і "70x 4YYϕaTTsڹ @18邉(zo TB?S)vP~0\`..Fp8JM*`ɶD Ĝn+4Alr}&4-,T<#0l6fZUu12w>L~]7%Tld:j`T^q>y/>O&w]/^U{|Ν椂̎i6"@`FɚBJm ewYa**r!2rxwpgg7Dj֯JƁ͌ƓjX.~`FDvw&;3 K VyQmϔn6fOfuU_6`/;,uT_z6D@sHķmPi!vHUCBPWvG~ &k1 ;SA8~ߴ)"*w%W"/D B RGbQAEDxC?B6+FmAS f| pJԩJMH2!w2*2V,wA<1.@ *CUaݶ *60!T\~&ED?Xm+v7nSIV_?C3& 4XՄ(]m!dz 5,z1XiQdǣpI-:gpbX,b4ՖBd>P5Sm,j )YG#D\ݹTMsQ_lf$/o_gLDٳGE6O<ޜ=ypp0uۦ)|wo9'ݻzyg{^ Q s;dGxڜ `|,fq^|}r?wUqիrʒ%'QU@RUFU]Whٳ B&hWvv-|f"IEb4糽AD" $dQX )kYPM$bܗ&"#gFj>=8buyy1YJ1l/pKE1&/)G.`*[_%5t~{"!!ј8ĸX,޼{u?Ӈ{Gw{ߜ=y۳_sRRr׏Ul6gϞ Gûl:st4GwՋgY ίͩee3uWO~{o6M;ٴs۶L9"U^k I/oOOOO9Di $-\'q ;+EG'EFI*okVshf=H4+iJ'#x$}j Vfb$I"RM@S ʓ,rcb=E&Dp]*Wl^Xz 2QFQ 6J\ e3@Ur[WpZ $XU2 ԅ]]S,ށH@ 1VYoKXwH E ]r ,V5S0R;*'D!d͞ M9e"U99KɈB1UzY:@ )ELvv?>?䗙$+»z+ЀTYD wtKU6J~ܜhj p7(?MTqY/pxuy5 ө"sQ%uIt.dgl(LAoERJYS~WS[ 7յC 2Uj67ݿ;Rj_{m3՛7onoFӺkDdɗ_|ٶyKA|3";D4%3sjXWjipŪ"m+G R+1 XLq.BpSJDTU\ K{֝{HUv}-p9/nR )j~c3p[b'E!u)*93b rr`:rJj]zG?:I j0m%n M;*u_\]^Sxosz2^$VznnB(-(  ɶm30Fߒ[ţ㊔0u)Pg wQԗ DEֲVTPvpp[bS !fMcPrGgsYj\]?SD~BH̑c6ͩ\U*-Q%$8W 5<f]RPa%]"L[k!Pv1v 'cQ511$Ҹ521l&!`٤4~9 fj=@R}kS6Pב_z}vv}m˳)jH|GOf;fs?֫Gfp0ͯk9:s1kRbJ21_RUt'LGwZ/7}x<FٌMcx8/f=rN_7Mƣ<,m`T6Bf撪ZjzN1DB1VgW"vTQ%mG*y퀅֦Vs{[!$H8 UH&`ud]/ϟ}.!f!d38%3S4TUeo^źT`.8<:zk25)ֻrg+yWR#)lwrHѮhfD$*` 4{uHtMv 9څNF$0@ȧ(2BV2`p@XrʱnhXs$-T0)fY?'G?6Q-4K "`LL@ H`M=^äs9^,=xjf<̦˫KzSj-Ha7Lwf;mJNN~竝$Fb1?}{w ګ{dZ\_~vggp8@Y؇%*Y8N/ {u͑ ]Ɠt.j~,n4ۛu90SNP2%0戁?WC8r\N>~|Ν.l )o90DAh8\/W Qu̱! \#hJec{;e dU*T[!TM83DJѐ t{uvYW $Sf$ sK̰SamK~vjث:.+1%QeSD!I!疑ڦϾP-=CT%DɴLُܽϢ>%5.rb)G`a4`8ݻ8?K)v|'+?{ؠJk[9D2iߴM]W^?֕(_->/Rj˹H>U *yq;?===8:g?Azߋ1$9/(:- "i[$|Njr9;MjB{mzC=>>st&LbpptxȁR]f"iE q{^u{;g}HU]}7_7x2)_LrN"VՠɳdWo߼aewow00 >+3Oub#a %S8<>nW+-"fٗ~)|%g$ZZ B樚E4 Yr[FXfGkI]~DETPvӨy8yC!+ XΆb꺷Z&ө/g<3q r7pM=9FDV*"3:c璅*0){rS8:Zb{ts~vr݋U$&m""SB-d0$ \q rV&Z/ F\EU5,l7Ծj/HOBŪCC/$rW_߽{w8R0abǔegt0 \W)%0j6W߼yB^zu7ܻ07oA1 "^Ϊ-8m=z*VggGvv淧4'LC]'dd(K?ŗcDb]̙KmI ,[651mh|7z!9'9ѡuQ b٭$JHLU\0]!|(*l60*"Xw0hG,J=wt[IbI0W8??yy_UuTMN^{JJE\gijD`!UҞ.FtPD)jH9;c#'MQ IIT&T#ٌtMˆd+I?w8B&P17HDf V(%-e?=w߮@:T1{ } *!LD!kT"?bHC! ?ʕ(022on./?M~b[CLо\럪bg.opC\s΂ M ^П/988WPCd  k?hMr2YRl l2*&1# ^?8{*7-+3я>m$&_T͛۟fX = mdt7&]+)xVU-_?$ɦQ*߹suua7ѧ Bl[f`޶TĒ`j{g擏?7<̦ٙ,K)jJd_͛7;J^wOaCfF9f<PUN`wY|=I,Jf\hUB  x@D ̣WfPU Gxx|-br2N!jA0ָjݽOK1 ,|f)tqT[wA`4BTݦ ME41 ]Z!U *٫bi&fhȾdeo./AFPGčZ*WRd,ݖ"bĜfʩfsyq7P-=9jw](Q|'53:?^D:-((;+ I53q0,v2S]ި/G۱w{  & A A$6Mw1DBR'|`WN?mZ@uM̋j^⫟ϟG"ryqyyq@B`A sٖLR"`s64Ehыp3s,}flԌ >s;zt:R !,f2Q H{=dV xv~/iW[_[1?zr97z^0>y Dūr ǢB\41ؕ<̈́9"R"1q 1#0/n^iN~r$J%D !(1ƠҼXj[\$٪*?fd$&lL(:P K~G./>Y{]HF1$˚˹#V#&M[O A(BF4;x2f p2Z1#IY+u fP ;#vڶ%Ce"'[!!awns**&Us:JL X*ƾYD`Djn W!o_TC\nʍ[egV|K,X .eYn3ZU5"jVbrVw5+R$t6{:׀b@䏛BG'S@BZˍldCh㒼R(JC)mӯ~ӛr亊!&_'!o_~{"^_87MړKqxP} ،!P@IbESkVmOm2 02ͦlV1x5}z&fӬWY\1f1FBhs~u±=x?_.un)#4db\\.p4WÚǦm|&i(DP*F>I+fv>A0EF eɁ>tz*Lt5d , {J6MlfZOO|`<4 B}fڮW!8?_ĺ] !̣!xcۛhL|@}X6!8Ow3bO55U(X$A' )3c\XŘs$L= b6,f&b*IׅpU'@@^͐aw,p(1P# "޾_\VIJ/e eLWԋ#-fuWW(b ̤MDȡ*"3TL6M{ss= GC?,E%Q$˂[Q6 @Ԛ"4r̪&&,RP-&jS/Ͼzjq(O<ۓWAÏ>23{Pכ* SGG2\hsN(Pxa "l"mj1PUO>ӏ_~+9s4NRӾx޽Ϟ>w~ݯ,j& uf|śWvR[`%h"nͺ˗Mc$j+H{*n#]e(~jf٫X^\\ݻOhY0vjR/`*p`˾d!(JUq9#HVQ ݛ-r]gz3s{ݝ8 HD+,II8\r/Nv(Itpw˜Ř!?Av pY+s1ገR̺z "fg[oM3-wBoAdfs [h\b`=b~{RU*@ K H 8Wq0`Iz? S\Us܀,UG&v FJ081h8!yu7+4qU9BE^HĠJHh XV^o_?pC?@ "TM!xHU _37&^Qr8F&RJ^~:ZۛM!8ԠգNHYkASUrwwMj#ZY3TOUeZgv΍;<7^'Wϟ=/|3Ͽ||>+ZJ_ⲅ hVԐ-QUӠRD$v(!@;d@y4hA"G%WS*-ჇZ=7G_=zx8\JL!OSBOGAMgQ3RѮR 69/MmY5MfϾ{zqy~qoV#jW[WLX]LTbiԴDLgÃto6PaslKH.Ţ^NM9%V% sNDvmqxܶQNI9I:7XPJ) LRe&GsȥZ&NI]@2gP+nx/\DT!*xQA]-֔r\3 AJ,EkfOfF/:`p# ]/Su@!Z #inރ׋PxD$B ˋ__;RAU%LRrsL1l [O<&^Ϻm;n7-:f_ Q?҂!IvX.FILH.<0̤{m 蕚3u[7_m]_|ĻmbCU)er߆RJ6q4`7d a2s69FI:%lL7ddpq5-=QGn}:m~@ݙORzv2">t6]PTS┚޼WNg{7OONϘt>T$c* Ab /fѨ)"3_]_#nd L1@860P(qoQJnCDv뻕~m̢JMgbMX1"EJ$D8H8II1 +S@C bB3S a:Ff{nkjE-R1JD DbDeαB@cRbӳP^| 5*x!1"0 -s  |qPSfn:twGC3k̉\fc)fl1 BE^M,#樊MB*Dy(PepE 918DuPU 7{Lm! <2t"fvs{; 6$N^!V{? 1*֫ܶi Q_,L~ tcTe}doJzeY;:L?xϿNfNNΚvc۬38Ѿ#IܸSMZ!PJ91+}0y LADMKZ @!삉3=xon%^_?ǟ|9zEM pNL2c ջxX6@Yv럼Z]o E{\a@[)% Pix~׍Ɠb1ڶ. % ȤB=JuBQxR4$oعԄ!fHW?/jM^?|DI^[7mtK6FHC %Kh"p,N$G6!WN"0GLt2453Dw3rM)ULKɽQpZDrtm  . Zhͪ8Һ IDAT++znb]߶ouy}|>c>16FET,q`J у'RpZ=wD^,HQ&"f%q`B [+ n]o|XEg|d_<}zvÇ^&2Pks/N^%|gĬ{}'k3T\)2iN cOLۛM;?Ǒuo]&n.ȮIb`}݃£դ6DD_\.&YnDl0j'(!" e31U1e5T*\U\!] Sp1!-}a|n&DR*}`{{{'ghJ`N K^r  wW=()1y{|>zMj_쏚k[׉ps>88m/_m7k634\>'1Ӑ j٬1r#?|h<*ݶQW f24K!Ja񞌧 Ϟ\[U(Pb YS?#1⤑иfX!0)[s7nFW05xbGMŝ!bȔ2d61SD wx;(FeF%dJ[㛫+!+)17)_\|GOzb*h=|6뭚02%[ęSf${w-3rn!jR RJdȈ !32@}r@1; )Bс㣣㔸믟NrmWb*5BFE6/9ł9QJ1wl6Դ9@]^W:88>&)̉_>-^ beBT#cdRd`شJG8 m /?{#_P") -r!6Cӑ4Jju*6Y"$V4 3znGhruuLƉSJR)圛n=M HV?/i8e)b*C‰͊S1g@D`):HmeH57TivhȈZ8 5bZ4΂WAU){Rz@梒9)/?4iF]%#qgU77Zr̯ImnH4."~"4MMi릳WqݷJALXTkDx8h@Tp56CjDyH f^W۽;q!Rs\Aʕ"&J0btSۛˋ_]!qt/3`&&-$J#{+s*}ٓ M߳iڶmoo|qo**(p]C9DE.*n7"eooьFPT]9ԊMfwvr*"?O5 fb֢)'ງabjqjNX IU #$) 3(p 'rLa27Msv~vvvԘȗ}Ǡ1äfĘ4q8 P_&LUA\WhmrmzȊNblDLӠwDŽ+C),61ȜM>ͫҀ"sݭ7'\C  <^n8bI5NT!&J)Wcs8%℀M;ڛkS](;û!D%tdD`Rfۮ6S 1x9ɓwt}wg*sC)̜7z_]i4V]!f`ō(kLʼn1-i1+qk%fDȹ)!LbB*}P JG`n>=~l6 8z]ߊb49(n~҉D$@)%0րdu6iDŦ8%)2ٷ_V>I&ab)Fɗ(hÂqCN.Ea"Uzrbq*9JP]cžRR5)9xw|͋Դs2OEdݎcd@bR9PĮ2a8#Ve?fJ V͋q]+ppSBfp  dghҀ,Rib?F0%$*]Ĕȋ+0Ǣ^(!h@ b-"?OD??s9LP (EOh81sTMJ`a0@YQld2{=<>f&Nq ۈCQXqK)v{M3"b-L0ׁ0|L~s2\\RrPDq_nѮNNOjNE4[鬒&=5p3EsQ R Kvb6.mlpLh^& ؕ9&bXds?|y}yz~6]L<82վ7q,wۭL3g]D{EJGrn&7in֛m1&q]-R:aߞ|qBw)uH"XR^^< t:Cěg~[^TRjc81 V07)B9ĪM0l݁+XOj^?ŗEMk:FP@[@FJ )hh$YLRǓͶ1.J nP!.epWuWDuF'P~׭w{h4ff7'!mnnX Vk.R1%"@8 O>Z,J/tA5udꉳaD  U!Q\ ,R99W^S̊61"2cΩߩ;Zr` Vu"&ӎRf_L~nbifl6ۋxbjN^S{l:w7PNVt>ﵣf])8Qto4G./f{{GtwE:Dv73d;65Ĝ_}2KJ_@.ӊH(#CBH4Ġ$Y#w$z T)f#r !%gp+TV1 PU5Z 7_zi}Dϖe$;0ij.ܹyL"LFT#q .?TRqKH NJLRcOLNghDCBP$@($5H\..} `juw_3.#8Y%0cQ\G&43ȁ&k h2Yo9%)] :V[n cG.atǓͮt*e;qJZ;; `nfy4J}ĉJ%Bnwt]ǴAKDSb(IUll&΄(NMTH1V-SD̍;y495 nWPŒLa|:H 110j~Ӯ|^?pgE+U0MѫdZtAF="l~}pwv?8R٨i=Q^+u7z1|O2qSϻEl&lޚ_n2.DQYcfT ~j _s\2CjR5O'J @'Hf3' 9Ǯb|tn7NZ5;9"xP $40T~ @pubrN9CWTKE!qb禁wa|*V.j|u IMzJd45dNmz)ffH`fB+E`^\_'az؁o`rH(EH"H Ԍ1`/9vܮVã:<^"01aλ:Uwn]/f}H?xp]*ۭq ; 'S`4w} ٬mG`&W@5?<8{Ivvgnr<!SsFgGNL<˽gϐx4`mGw z x'/_x7qӴd&@É9HK )x۫v0Fj̺#bL \ڙ+1x#p+ju&Ȯ*ңE%5n{:"Sqπ {#?x;!P^upb +8$Ȣ%1''}ߛ鉙/eEJ̜(1%C 3Q»7UdRz?T-jP?iN1y !t}~ӽyjrh@IcZW>~\zY֪B̔87'_&eD)&zݥSqךF㻡5AN9jHLIRTȜBDSbȏĸ?}+]HgwDL(;w=|׷l6gG<|`?(ls6n.Z iVP١_\>16jc t"Bge{ (`jh&Ŋ'FKb0%lV( Pm;&f!~P(2GJT4\h1"Uk.Ĝc-^#Tw߈Zo6<@&א*,i\WebsD)8Wn?pO\vQ[~236GN []o, sN^z IDATqBގ'H_G4Olcj|V>Eͷխr9|䀥@7B_.Z*RU$B@mNhN#MG 7Uy<|M/Fgy ru?'BLwQ03@ZJ|n/.׿gp)`a3F !߿2[Ex$R./bw[UBT}8<A!f:0KJwǼapsɓ'KvL"a nn^|O~Ҷj|x:9R~Aw$uVW3 B Tc UD6Q̙9;xih OM3&/^\^Of-RnT,q.ҹs!|pqr:K]wGGŢ:(LX#c 0V%1OӆEGJ5u#E pqs-~3B NJ~ͥ|x~~7ˍ[o@DhisbD."ȫ9(`jF)b f3GGŪ隩9{ߣmu f&}!{DW[.O^ۜZԴ;}u-E9qulܢfn vGsfB aRVG*:Xnpd6XM.c;@LMPbpB* PZCJLRMԈ^A}^V_7?[I*uPѮ#}ܟcdx Wדg(ou:SUv7)nɉzqbCn[Ԭ><~\.vkTq*Ituyŋ}CTvE1Ձ:wbr뉘ЃЎxzɉ4VhJ)qDR)ՃfvCSu1gE̙/.OrJ7ߩ7%/ɫ@8˳n"r{O& vi`œ\ h^tݠH`#sܺ+fLIh].hx9A{$ۭn+r-(77DW77wח$c{\ L>?915{Dě/^>z} 6#aLf+b\ps :!01@0L1V@Jx9J'&ZfN)G)Eԗ !04ޞOg9gϿ_{ƣ_}ٛÐhb/}[oi^כ/Ed0 3,+D!sttn<<~0mdFF/OGbw{#ֻ&5La΍i('jڱiǯe&s_?x0_,nni+Rvei՚d1ybp3Ir )116KWR"$8Qb",=8t8[5c0B 9D4ysL+"GH-"м%ws`a@9J#x8`"چuAW-Ȣ~瀠гރMM<"49ʚ" xuhx @'8=9"0 )hM,5!O1DCBgDn77W{{KX)z+7tfXp{uuuzxH$&6<`t"jRXur:އRJߎFHXR Ua"bUȩQ8Xg#ڜPU@du R U5ס"rNm;淋|$z7Ow}s}qA$tFD^)nnooonR>z#ppxU=S%U@B?8oԜ ȪX OC[i_ R hؖGz k;)2=lΈtor0WY"Cc;j](j*tC 8jտşϮO_EXxCu$p`8) 1vv+b^7ۮ^Ti{Q1`*b3Az9?[짜kLL|ݤ'm_^66!LXl63Sv"!! :w?QSKPQwgkG!/i13Z"0;b#X"ģ?w~ Uwx.ttusɣfEԌlųv4wf4;9>>[bXa9mǓmf"LfjXRXDŽX+:RY_Mzk^=;?=ywYȶ;>:Cu j&DSb=Z=^1naBSO!Wu޿; V @F1sD/>|:"Z on׫bkiL6nܶ"wLJIU,؈`4SqC :wT32 2w՝Cj"G1Pc,rpnM66”<(ȏR/o,_ެI֛;3jN#,KB?_X%J;|#, GoI@N_{s" *Ռ97)o0wqjnZBW>:i?@銇WXxZuDðs7ե H&(rh}-iE0a?8_G(KBg!̬~ggfTmH#:B &S Tѿ9 dn̓bJ ͺμ YZ#1+)G 6eTߘD0D :z8­}"!JfI}<;=dWcD8MN ̆;/5>B:.3tHxyq{{{$K/ 5}k$6>n6rho:bEx*2"(j(C$p\n-s&jYN J$La5!yNBmb0rQ66ATGHJq #AO\k/ִ4|Hĵ"K1 | kl%LphDQSus!“w, qu ۼ(ks?#"68'is@6CgWWU\[bsZ'~FQb""K1t];pp{oYbbZѱg"ypQ<@" h 6^]]..ǤϞ>?8:\yaZ.3 -E_ )rphu-fRG(Ejmf n{@0C0SN#2 #?)q9HW}^wx&a)AjY`։.AȔCj0Nsrr2ץAу{'9 ܸU6F# 8FʂbF%噄#r=H A5" ̡)eLq&f(<~d;~|+r͙1F|;ޒxUU[[pr[q"XqM6 Zԝ$|¥ /J7\x&#6>|YR:Fb q%=F ????:~0oB#f sHE Q׼kZ {XhXAlSh;1#c#*2KV@nfΤ6>XUZJxoonW77W0ҰYU3Xlos'ffuus}~y9Ç=2 iJXq%lggwUJH鹔\]Mmmͧ`h]fHH>[`_: cJ=$9T,vf' 0Qk)ny1:[["ɓw sڕt M?@ !paF0oV(D"j_ݧB#G1,bs؜hr@LJ;:3r' p]5YuGLRc?'-,'$bn ̒SOÃ6zj.Oi)Ib`9Gf$ Wi$0DFi&|f6K q'0(>L-q@wܞ#k ƔۺǺڃ)ݹ8BWWgg?_1.<)@ s>8'+ '\^ayGG$}7WMUkE@fMs2œ7s)]9yrG2^bl^ܿ_,HJO=]?x8\_[&lTluj\1u\)9-v>\y2ܭkL]x縇DhbJDK""@rpM7R߁;1gu.)G@X w͋ f91igAjw5`&nƲ1!#"GD/}Yrʐj+ѽz5,i.N%sID&1WwG$ !b40$H'@pWvJLӫ\"PA$f!OF8 Yri\ ?}jg}܏wS`(֫ݽg"%Kk[HAu'{b2I˳ӗ)R w~#*ZG zs"Xd/Nڲ7Is""f]6&cA=(J̼ `Z[@0 ^ghEb α~ #Mu R҆4mB u~ů?p UmuLJ/[7뱎/_#`Z:Iסl.fZf󳋋j\[eIZ1 *:z } @;˳?ω0LuFᎴ”=9b Ukq b)d"I 'a^m( 'CEu4tZ̘|vQ0bE... &h~pxo: y`s'HFV]]#@hDhj ѼJ%æVg쓐am!3{jE$)ux {8!+Sc_<o SolZ)6}=1o"\\_GͰffi^$ۘ!&YH\OOO_<_D޵鐵hPڜY\Ƿ{Oݮ.]כ6S#Y,,MlyTGR^;~&B3M`L Hv-۴_5E~ 0zbH'}ׇzHa[wVzrz 1&B>3"_ߟmqZ Tbo9D}fu`gun4`K Vplջ"7߮;ݩikK挀vG(} fJE(]$>X=*yֱ21b49wf$Cx6\xbli!H۵anjbsf$j&"B̽ͅ;c+fUTPZMg<pӆcuz+Oٓvy~qss7???yPՈZoԝ +j\=3_V/f|TniEI2}͸j['a@d.뛛O.K5+u[v;bՑxݽӗ4!@f{@e Xō!\lVuXQ?SAUаY_| ko&p4Mۜ߮nK_Nt]UTJ%:־-Œ 'OP+Z*5p{^}$9 @ĥtf6t;tm@XG- qTDCխݰ.Bf.]Ha!tn.qϔb⤤w!Čů?|ݽcB݉7"z^u]g{BL<, |SuH*Zh>P=z~pxw7k <B c EXY5:a/sF b6a+P@ĔKMp"]Qдm!g_Ꭵhz,@A$9¬=c qxQv}~"75ZjبX <ꖗ]8Ww˺s;HN, 0dDv$ 躮X:$HR: $$5u:%,ǥZ59bVdMQsp4ӫ/?0~ xrS>Z[кYJ߹M?7wsZF  o"?)Nh5δ|p 1kUx:}å'%2DB!+E͠0%V7|~j9l_RP@UCQJ͈nfL 5 @dO [ @89y/w|`nC5db1/_˓Rbk;E&B!."Sjpap>ﻞN^~ܱ"r)'oH١MH 8`kF p `Z0c  x QSbN=$e<$UG7"u&6[Dz$! M+1m>䓽ã|Bvf7u-XGF˯c=::gv tG7Iщ&wZ0]N^"l6#~Ջ/~cD6,H!Xt5BI&C w RJ)]'޽cDlwiAa6X$YwDSi$Y(X/4 YZ]=/W)a#g# Fn* 1I:'gteX`ɩtTV˩љ{ ɮ@rqY_vggMs[#4bkQ3ɔ*@,#:39iՠR!O:A% Y? c4?bZIFȡ`UuHdu\]wnJn3 I.l6ZUzDFhߣ!͠u OCn6C./?}X4{l텚+4"LZwoz[t㢟n?~;sXk`ɫ$L|\_շ88:ZQW""@ӗYp3G@b5#L-9GR M OXcL?bZE)-?ɿ㰻3O'\ "h)%U//~;!W[A݀{nj4Zյ{-lD%k9תk.`ٌAu\ϟl>NGF%0aVxl\_G X+q䣏h>??y>9X.10 L(ԋ;fSY(uza*X~x||eT%A\o[.KR10wKpdj54< 勓'} ɪMbJ 2 A 0d4 1ҡfn=6 BAf>En6C&)GUaFK{RrD^+#0@uL0/0ua5~ORXK tg}BDJ6gc&(]fQJ` @GҪ:"xu,i^qUJhrG#bj@4T7!iĥ0d7Wח?n/܏%\ͫf/fڐgkG? @Ut #8K^@ݝ "N?}Ww H($Zg勾+gg秧g;̈Y_8|PVsdcc /;0I7w%V52HDs@ÐW|-yuo\mm (k3pn,>HH5ýgg:b5 plb"6!L4r&`NX$A ,(x` WE$bZ͟`YPiSP0,b{wWܮow@%nD@Xn6lkGB!&7O88" 1c c.0]q,."9lGSڶ3cf<ܐثC8;?2|;<9˦5BgjefeJ1F_\SnfZ0XK)Rӗ =Q=斚ikŨZ! Q)]ht{r8חp{]ߩjϺV]ߡˋ?w"ܬS0èjHZ5X((͝ rSo>04a!’ӢnDr+U-Z[w_,KFp7uk sj|}Ѽ_zv~QnJ阨Rm$ DxD攖ų|qa*W7o]+ ™d;h8t4\jfK)\t/[/MU1[54ܓ`S BJ5pad4x:Ĩ5n37DR;ۏ=J)|>oo4=K[9&M5e?^f)]Woejj#&P0f"$~@sLv;!.]yGK߭WJ‚C84[nC 8A`R3."mipؔ(D*D\Trb%FjnVxg$D`ԗ[O[sS9*1ukhuͶis{;:BqB"bwoo abQUru'+"J ,̠ qB35 IDAT lBmaH R:SmyB"rse~ 4[u ˋ[_)g1U*z}s{{xt4_U)SuTlMfb2/ButwwV(= 2<f}{OBJ}\^w1 7xX\z'DZn;"Ej" b""Ann\LPL?jI6ONtE0 $xf=uiH10K㰺^f77W/O_.[} ZG8/TrwI<~)  =+_ݬnB:u5†%6IBfU&9w y]_|ַ?O;[omoׄQbl@<1/"P1Z0ʭ@2ɦֱ$ͪ1RtAȑ#G$D$=p#!DLH211wl^Bc}|zX̷f"DVu1{`s@.|iX1!GFvfLNY%8BaX1H,Eʔj kJw4& jI $juq~vqv6dou]6U pf/puuut|Ъ0kXw)٭(Mr⮟rHnwX,b,N[8g}w?yZL5 $ˆ0zџ? dzv9@|+[[WZM X~ɧ˃6 E3 Y|:,M"S '`nU\4DhB` ZַB:9:8f}8Ib/wZNNwv,9GJI3Z\'/8,2Zӓ|qIɉ;8ru, ar~hPJDLDE¥?}W;ۻ0D9ipDAu!BF5[T-3ei, 48ظBh^Ä gVAn9J$ DViT +]BISa ؿw/sXD&)H{˃g/^Q.H@oT"s4#B9xN<\"ᆎ$حLU/.7([!"f3^^^moZ|Jbdl>wNuQ6.^W{ef< rFJ,$S8?pZnn9ou30~O7cfGF~HW_~vYlԪw o>/{olٕifs^\^I!L1A& 022"`ČX2!Ve4 3&XaTaXA]DfK.w9b}#&2B2{>kIl!DwW#2#I͈B,Yzⶮ5:ͨ ̟!Ě9T[gɉJ͠nf2 LfL:_sLM4㊈2Q +{ϾZ.ǪjC e\w^ѳbf;:^Y[]]_oSt~~zw-ڔz5!BPj[2o][ąwn&"@%XՃARǏ i{{{֭0U%)d䁕Ԑn!zS4" E&2RI&fԪI<_DYߵđ93ݼ!*B%DI3|'Q!*293@x8fl6LLc]p1[Ay6B,Ș¥Å"CjY1W(Jf fO2qd5I)k^!fLDAzS==>9?_e!͠|6ܼ2Y]˩2inZ|8|eTU/67b޾3%bU9zx:Sx/f3?%בWzO>wŵcy}Y!8di6wT5w~O]W T 1I5SepĸD L ^U4+Yc]1s;_W;1iq8~ZUuS31"h>&ˆ}j!fʕ="J9w]{xp$"++=@$rЙ|5;? 1 CR^*p;iN @ ,Cf N#l"bT~y7Qs>99z7s fj!T(`%ENXb5sH`h`V*j *X1_1J]Z 1sUjD"U )y!UHc9Pb ĜrAAR[hՊws\XV UpYU&h *xU6!pb2eY ǥ @3}J5+dqҮ2s= 3ge4`/___Kq4deOI,k|PȥTw@^X_h,* ?.y&\*oܿ?ZY9?95AX U?߽w_-`? |V7}|w[3ĮKq1<7_7'uWAFdS0Cd-G@3dbB3TMϦ㣃CZf]z0BT ْQ4D*f3sj+g''bcͦt:JR eTjKD'G]zmi11[w7g"IMP.a5 `H,""rz NN?UUe"myyF!7&) [y(E" 9z>$#c$ͪ"dRU YAd"2eȟE {90D`m0b+뇊(AM.| *Drj0]\Lׯ\ַFCP"}в?>h("T$#Q 0dAނAb4C@dZN)X7împLf 5>~>ϧ]|]&|zv6&q׵]emy25sQ "\| Zj3=><L 9g(BGeI$uoX]_;;>"0Db c_2׀ sh[~ 7>}׋ Ch}uX,9I0!z|RP_9ʼn.+h14T/4 ?ۺz{fsʚU49 kh6%CrA1HOX.$ʈu3fGUd2Y*g3L0<XJ~}s+V`Dxgh4~~1UĻ?u3ޏPJQ Fd >;=͒}4z!^_\\yW].`?y]ш9m<|[XsU"Kʎ Ա0~1>"DF䪪*fS߃iT?kVVLo[LοcjћjVvD׸dS_Vf<켌!t/b8\o~B^5$C6l:Mu'u6y0wm;Rb3Up+gRMk 矝]~΋׮aa83I"^n0ה *xH^SrPgqo!Ve&be$kH@c0/8e%rCZjʊ(GG fzbƂ2jBrꚫUDF3uju)}ׁhᖝL"~FN)eIoVxM]vy7Hj_rʺ8*EfTwJTF|^b-tS>|UBL 0w fbb&9[̮lm{{]S+!P1ᰮFw^w#Y/?U}õn0?>ݺ E}JPו=p{9vPP!rf\uh[1 Um*!eA}Y"QQrO*޸q]@̊$Xő)%2b**iߘx!@jŢ5-O/q 31\esY)x6 bU9}u PM< ]y9s(Y U4U)OGr{O&H,jK7דL5[z3`Z._F$Z)aP-1cF}WT0G dI^##3<3ѐ}U})#B`I"F$9):rJ˓'O$=U:˃(ȑ j.o9S@918ݺr9DcXrVTɮ7/ΧE C%bU &ЄYLyآ248q8?~WTKY>{D޷5t:K|c^^l8UB= 9fU!"b,]S}4Abs6KPX.~9)' Ff13^:  ,TuO/'']=Ī0_2V;oO.Ϸ]k}~e=;)_ϩĪ&@nG8pJ 1h"TB"UC)чYYYS>ϟ=m=+ *YIƖSR rbIU1xE >GI()C(}5O#3`!VN3[/`q1fJm9-s`+ TB%ED fbJ'+fh}+K^]YӪԫr@rb$BYd);7 >">u%]&rdBI1{1_4ˏ;u/"k6Lş 7US#Xų㳓uPМ2盒%ܛ7:>ַrӋ XUlfuǹ;߄!$~t>.b<"mTj|wo:f*98Rum*"EK{KakfJe8<=z@u7s6U/y:X9ںre8js\驈_d&Φ[oW!XWneu=ΗO7-4&8㓳m;7"~k㯽'_GW#d%"Y52d2G{{0ӳdusR8r D]*h4q٬tӳvvhj')yQbILeS(0"!6,'mݺs rߟ_{Pkc]˝b,&ԧe0_=.хik. "4*-Em3rkdbbdP$'׉޶$ԫԱBf;*Q0 J!/ M902sT3 8D#m-T$KmNTTV&k`Sr Ӗ"`.pd.~R&}mLK^k }׿!麌T>-& EPDpCB@ `4S>9:ܾzU`k~!vJfm+ãîk90139(PA]gH}& qkg=4 >|۷fpڵz| 1yOY<w*83`4"j(H aԷBbRO^q=Ƙsj6#umXDG@jUAEd_ЛJIE8dB!TMjeum0*"Ţ8hp0ǦJ׵{n*[dmD%UP]ʟj 6M>yrރ+k0C`"ڋdT,D1FI!29SUM(:NL1` ācUUM#w/)w}HUkUJL"2fI O1* fiS{CPfCdbc(M"&%d?b{ɒܩSm[km`.*+/@L35ZZ?z`%1UT\pQu#JZfw.`b3Yti>8>>ZH̓wEE0 IDAT-[גs[غr#@`2Vb0iYQekW^/Z0(e0C@z^•?_f''-,b?8 #]u7?#^Cĺ%yyоorJD"y"P͌9PDPQE_xaÝQEq4 B%e"bb}JK0P&k/z6r>ƺ.X^)?B14ƠVbF{wA_L @HN@"&a`*[-Hbܹ`8Jԧϟ㵍 "}Ģ&09FrOfs\~ L!pߔ9Phsْ%L~Sy+xWINeF\?U\HI_%e5)i&)-C2QR󣢚$;1I)b &bb]ms2.h!o\\o}q_ f`> C-5zئj.b XN'ā@r/)ڏGMZ@$Ray^0тw-3@gkkYR?2u7n߻j/=R @up|]sӗ`Vo"G}=hsV5ԕ~yuw߸{|p[Wnݾ2d0<@)i А9RdZs e@;Ty@M/}S?USLD$^J4RI]Rf**T]Ofxcc}vq1N9֖,e%$F40 bP2䬹1){k& #[ MBUUUUr}ccc}R۽x{<̔##U10ʊKtQ >ptE.>,IA>#qx\䄂z3$sf l&j;V0w,U>"( qdS3#erpV @dV7gWIzb0Bf:*9MI~ڃШ")BH9wM}egwzxYyLeﱪ_lnlTM?UDJrܸ{ѾD1Fr13z5Tz|8W"Ԛ9V_f, g뺹{ޟͻqO}kkFf8LEEc$g Ob[^ 18EJ* \Xx9YJ  CLL(AjemNfg'p0`AM/Me|h`zq| 7߸f3'zlnE`b "33SGFFHէ/=|DSߺs[yJmP@"%QP\|Հ]ZU|BsSx[Qۜ)\*|{̌nA9Vy6I}Bm2ĺZ3䤪Ė$U80jc H@ kϞ?N/guq>ec]w]?YY,Hyf.^cV#({F+А7!23!E0l>wo\i&ƀl"\=Yp4UzI}TfI Éb6hG, 3Xrʠw;Xik_pV _~Շ+j]aTgOK2oʢ]po<|0gϟ?I a<hQ I„͠>ՕC3QC b)eƍ;"T̀pńi{cm8o=lW{'+h`DnĪQP*ԣ'Oc],`4Xeʅ/TP, vw6ׯߺ3Y[5hG`ktzg9>BˮF+H+D CD?JsE9SWU|Wn?~'OTuememcE~=!F4QGR0S0G4o%#`9Ûx|[(Wp/ 2hIv38 zA#k/X [| 'w*K>NRBJ)9UJ(&0˩9b13v8zuH_;D#VW>;=~VJ!i0@MI ol={̝Oe@ģ膖U@I;]Ha>~w ??{XA"oOR6snK2gӜE$ڠ}*Vh41AB̈@D&HF4"bl2_䴘0 =h:PMFWlGQ8^_@mb k{Ya>GQlWra8<0!E.$&& GoAnbzj"m;3twG|_G|jje)1GFV01X{ͭW acsﻭWd1pb/3\&ԶV@q*r`So8I ly 2Fv#0!s?Z)`4KV%Q0uqd13 C.@A:`4'f%_Т8~pock+Tw/T𠮛XA=9v#bҹ F+y' *`<p2o?ƣ/Rv1?}[,n7$$+[_K]gO7o޺s;Fu]A.u]gBSTB*1 o޲ ?HAcEcl8Tu>D"m0ft6tvq||Y\?n$)M+ֽESSʅ0J<{eHNx/!5@*dmuԔ)d06#p|Ǣʵm8H ]sv.ݾZ !2zC8"UY̸Lz堈3DcSf5[ @ [Hk0dI_@5 u쏃g+g4(gDC1fj"MQ@!08c׻@ Q\kyQ= Dlg曗ݙLYrgǪ#}YRL'!jU5`xtf+kkUUFQ*ڶ-"Ǔx< )F tk$I"x?o<|gmAӿgkj! @ 9u?}pxtprb#mK,j24Sն+Wzt:8ֱU2WBA3 /8<8xQ rgiG7lkFhb0/޿WNZ&bj }]w|z|tt4Ʉ(0T/~.Yccv{㱉t9,&ZRZ=~滲r-&q~*# 5AUW/_9eS23kNUԃbz1MZ0 u]p0L)-Q).|k9x%3E$TbP*6jo[kΆqQv8g-cQO~Ow_hiwkt.PiBdm<Ɔ| <xyu{{uuDDuo}NBAONƣAOO>;y76BD ?_ۂ+YP>{CR^FI)3UM=>}Exe -vvmΫ[wﯬ%PժHRZKe ^g쫦~@lL u3`g||hT{!IHRDEH$bi _|ݩY_Jۉ}. 4%$5y ؍$  Fr HnSCL}N:ϒ!reާ&` 1Bl)IԏGdeַȨB񱈺rxt$W`~GIΘ0HU]*08=9><߃o_4eOF*6VUUgg'$D Tv||xrti<^}v/y6D%w~=фKرW&G/^<| a`EKzrwzx<^]&Kz8W.wno~{.k׶UsۋivcV1bPCQAº)hhۄbw߸ϟx䫺7777l?{VM OF^kv0)5/:ol>}%!Z;mnn61qD$21॔X KOD̉{{[W7 \Ƨtt9/A"7:Y:D)pUU|x!B*3 v~@5#嬝lI888jOA$&lDXY ?v(7]ÃjĒ3" 00KI=[Y$/Tuh.5炗L,TѬcPHH* *$3T%iecmgf0P1$"bCU}8^Q')EUvYg|]HLZM^|oz}<?5 yU`8N/v++qeu]w~~ԡϺuepu 9kz;Gb͘]D!Ա9;;{<7B|>^zG1ooֺܷ]'M3 ⃽oÓ/w:"xԟtDDT5m"еx}j4yKzOϮn_r劙NFÚdozz[ov"&[5AVaɢ]t]Pnn/.f"gYY[gf̾2/K$PE $ 1gY֝8^4;cuR!cK#esOH2P̒Mf8>z ruR/wcE,('v PJr,Y߃HvF$R<{ kDPpЛk%SXf7g$88zEr{(bUW>(y@ 4?5$}W B FWG$_Nfb9$97qm{֝;.\Rp>}֓'/|ӟ7ߺ~:#`[JIV˒PE@.D޾r͓'_<{ڵkW~kׁ@u\7+1b>;o>3ԅ$_A:xQY@Ѳycy28??>> }ߝl^Om}&!rgŬMov$"VvP1"w@i/τSV!K_F{7 }H}YEġtd"ټwg`CWxq0 l*!29ëYF*jY,VR(uf.UQ߬4VcdB@ /`(,lgԥ3#EB4!*9 * #ƨNpg==bfUb6%9#0xjoHx\yP(h ŏDJm۶_"sepBgm<%[P#S ()g3X,BUf٬wkes2ꮾh YP43r8'?LH؟`c_f҈FqG7Ѝw5++/眽Z~X; zH Av2rZ߯MM_z׶)U7:37hw7^qգ흪Ꙫh2v:D˪ HU%k~zr,~pqw:ݺ}~݋^q`AuIu$g18( s,HHBUmf0PUU ULǷnF7ͦSzi!>_rV*:iopbx^9A6Ӧm FUEcE||^o0&9<8Xܬ hB!1b#i_3uJO5O]}3(b1iej nqqd| ꤀}ՃGH^}7np`5s/+@L DbSedL40 풪j33!sLU%5`d e#4ϻLSa% eMs3$dQ)wLE٤;jP=1rL"c)bMäT1$kХP-&?,?mţ@Ė'C7(B6#D&Nft2~7Wr9H1 !(*9ߴx,g,$ DˁC/L7C5~ M un}W_˻1۶ˢuxW>V׻t`_s9r2KFfA-:TMkwn_]ƛo@MJ <J͓۷k[k8i]d/a2u]$Y|T>|hie/asK,3YM9tye}HsI-BE SDyr45 OeHD5#"S^Lӏ?>Rv EBWvσVf/˞31 T15ArDG03<.)љ篤3wDLPB䙉UQ Y?h(@̘)O"U%aB NBdu^Aa< 9  3WUiC$c`v}xpԵMu<o,/̚fyeK+?\G<[)w m;唺w"Q9=cc}a0X[_Óp8u988fp}umu`&#70Z).+&I͝Nqy" ч...^R7nlmjR"R|իnߺu픚ׯoXUq2y9A$^2:jU^/]8?7b]JKHfir=x<*Π5W^~N]ך)ac7QMg++˽EX1f(" UemnoEIMTQPT784L5wf*)}gs  CU>As ZJ ŔUeΤ T31"q1&D6UwbaA× jh' TPT$j!H衞dDxN+8] !~iRPQ}6yL=+!˳f(IDŤȡias)}|0pztum1$qeemqWNS/3`_ܶyOG{ uGLPXuǡJ*,.,G'h2oZi)^t~{@!,mPtY[^\"'1W_yAݛ43QL:S>k~K7_yg?|vw9Ç|orNMg&Uᮛ]tιQI$#3H]rig 1iV`:2Nt=u9!d4BƶM&~_U޼!(fL4w jvѡ5qJwlū;R23 fsH)"#xԄ18(`O9ݾh8q*ĶkRNαm~[W$sײ"RUyEE%uYбfPrzEPCg`p1ĴV' HLQ̌+f$#O8N]L *i!esJ?ogACbJcw2S*IЬ@U,`&X@ Irt5b@ ~c`9VUL1ƠD~3 btrLgEo%د9ħjii9ĘL%`7];F AsDWcwQ|X:/<Ϟ:. |x;0L$R3eɁ1j]Nu5>{ݻoܨ{Bm۶KԶsDw9P@v6{wWVWΟWi#q`'q?:.jeuͰTA >uÓt֨/h4:5]unCעrd2pC2f7t6^{q69{=L I>-@RA$2-wX34PR5l".+(BUe͇{O?}_BgY 9GZpD#c)a tB!$2|'P͖7a@# @>TS0@9Y^a >f90# bČ9%ffdj@d=(j r?0,)uġ)UU'# Bѩ2G35PÀUurJUTR"v kȤ9IJ$r+2TQTM{TU*^C=~ K+Y$KqS׫븵2:=n{8w-   _,X,yvQEڪc緖"``$*"BYDv6` \~'|O=xҹsЯ+dl3lE&X?ӯdID̸&n,-dGG9i\~2ܺl2kECnz>Փ{wndzpxhB$|Z̗|aYs 6d//6$Ʊ N|dhs1t- ۷>r 6|DTUtoQP*}$&f nFdE駕w%dK~VST@!2 Z`_X$lْ0Y4ae0 ۳x@,ϒ]sRMԳyF K[sao{s9ȡ:DR p U / FDhA Ć)I,D K^ C஝5|7x^A !$<{6BDsKy:,TC>Hc«7 rׁΡ %\ "A,`@D,"bZ4?o/go׿BDb"6 + S@(T]Ri>ZǏZ_]K K~v:n8nsу{ۿ-fmL-0Vܺ{vx}+g;58%LM^ymܵh]#'ƸtD"m=x`ucZX0pf %"!z4+"S1d@/ ʦ cfDXNhHM"&&r U>zŅŭmBnکHF qv:²ˇ7"Bbd@Y5Baa(@ z=fV&֡f]:T rΒ6p!젙8xyCHtnf*n+R.B gvl,Jsy7ОKGO;07Ћ`)d.kF'uFS.2̓g dQtJIzB`V͓t6|ϿF|xNVV׬#HÃ)(ɚq@6PztfƁBzS"3(A| 1TX֫{1~Zun󕗿rtZ 1ўYZ#ڶ9 "no''᧟~/ܸ~ŅŅA=?{y*"YL10w^&жWqß}pgʥ6ubsvX//?}8d<^]_[^^%Dv(a d/9dFt:ml=/by`S M)F5B)"!sMD4`Hf#sd4WT5k93e+5) n;EQo&墟cHP3蜱Y{ Ɵ6b &h`WҬ T@7ܔECyʁ+S,U S&&*YE:D %Tз܀0$;~c4C.&[p?)bVD T{Q*N̰\[̦ly%bw˚Ry Ch&hx|>/ʿJ!mpU.P2s`>99iWMӲYRN`_)M0 WJLdJHADHb) Awv...-K7_޽x(*qΩk  eI0$YY#t6u/zoWѓ'www/^"oVz}(mjU~<|p%^~:Ae֗3}Jx/nY^^,-O&N rJ::.O'`!wl6M)/-GL Q7;!R6vIAD5&d@t@ D;08o|g49!͙HУܡC{_x䔚YS9+I085l*B(R*;_#ePU%I"0\Q_9Ă,Ȍ5!SԲ0h3aI)Śā@M(mɢYrαW|̝HQA6,>'d&K"GBpe6M@i fU"";<̠z:>UשJc²$ chk=e0<>8\t%qnF{7_y/<7 "0 PLM}LSNϮr+ޕ f3݌J  "gB7,31M Q$ľZgbRBeL6&MB!Lc\2PŜ-JA%KZdBʒ̰nK3 EL5'6^]x6Y3 i*6Ѯ AI(bMdx|,*w]nѿ?XH&oS]RGmiι:wf䔺n}csk{{x8̹!0dB{D\("FgؘSk e po?ͭ,-$\^^rjwS0H!p7PM}!p`b̩1˦n{L\I>xW~W6nIʹ˒`/pGxiws}S;LNb?xQ۵9HN]I{ ~Y۵;U/bb %Uqbf/ݍs[ҵ9'$GeοBpCr]Hd`")jX@FRk\ŪE۟|z:>@,9I>/³ə'f+N1g ѥ{@fEI f?"sx~mGk:eik&@QpHRbUy{$@s=F Q: ܵѐݙ#@ZLЪ;ŎBPS,BEP{=D<[Z^Zs 1`l6,,,Mz_\ff0Ȁ5px2<~`_r;N:9"">Uk^=OǓ6%mכ[/^v=/FN,YdD I4L>Y,,^6ɦ*/^x+Ϟ?}hmmuq5 H唉PrƄb1b_5#2\zQ|GoԴM]bWk~|k+[yR<%ܓpw?h<6]R]3#!rkO=,t2 %$$bOՓR[aK++[;;sX̀BBaδY'kT>!g" D|UD̹KI ɛEƁԈؗ=䒦3|hh'`T7(Qb쐵RIȨوXD; 35uŪ$*, ?EDfh >*+=9dFNs%C&_A" Lk*c42˃P`JC!^[B+"3 ofZr6efBvp8T3)@D*Rd<wKTgc{ t8m¢xA7L--.@aFϧX&IBdu@B(*,2@,//oJ7HKR2 ~Km7&uΟ>?mwgv@UM5S B{SӐDi^\\_uN&Z8y[$ @`6}\1UǹW[w7X-LӜ8lr:>67չΨD >#,Y?_Bhtuꍗr3q|뛹ɘĜf  Z?64%wH U9xaR"jB&6;.(1O @ À>_2CUea7TMU լi@Ո,%C-Y+dCF `@"Miu}dxAf \pS.:$U!*7Hxt2<>:9מ_>~֏?(X=RVK^==6 "TÚ9?~} ̼MjsɆrLZNt޹s wiS0UUZL+ZΆ뫫/^c/~sBU=")SFԵ]J0_ŕ+WwsY3CZ4<=~_5X A26==is/Ov:eQ+1 BLH*v]wxpжq D?wSdy(fµkٝ`+"0";YJaY3 1TUH݋Rb.B31c% IDATfU@uNo`>+wܓ9ˌ==9:87ϫ/y7Mؼ?W^XźaJ`;Uf Z&/ݜƳ&`ԧ1{`>REƘ)6kׯɧm*Vfh 4V…W^~{յ/^ R&k눾+2 B]wVV^Ȫfbf`{o  JoӦi&]9 Р׽wBUH :8ؿ|jK\&ĥءiã׮3)!܀sݗ UU43k Lb*[|/Y9FbSC42UŠP& fαRSC"ҵT&Hh "<eU L1ܱjsdP:Ϣy"~'¢ ElM?wٲPd R> Т"!3ˮJ٥M]j82!!2xX efE[X{v36EiF˩ttMXv3ߔ`L?=/t+W淾'#UYY]RGhv ^|OOZWuT3cegk> 0EBQIͲu)'?9:8m[66֮_q.&rY1dM$3L9mť WVV<}.9ĨCurkx4]=&'))EϞ?y WÓɏS"hU>0cF";XX][,#%,T  *{4JӐX3C^q#umB4".KejIeD"44UjJo yHܿ=Pnn&6/Yy7R'8nۙqv7u<#t"9/\Q/8Od<03sǡ#;*sF.MU9SWs`?[FDh0lh0e jDTU5a@i[ fmFġWj3꺊d{?| ,ZZ `Zfb7|3S=f^MJ}4A@PМ) qf F@0TE EyP@%fRQ߾wJ%(~YP%ecp9R+B(oFIby*EP53܋T5iK^Ӗ9ZY{FF fZl Bmx C\cFtOfiiuwsJ@D]תzuyK}a ,-yoi"z0@Qוּr,1Ādj@CT%`8z9g H>+̾T>DH.3%8Shq(Qp\ rY!"X8 `L@1ث9eLΟTHИɟeWc'b0?~pm^SOPgO n&Ã=ߞ.R~t4*͗>STGUoks8b`TUٕ|sۑYt:/,UU{x8L]wxpgDD̩ E$DZ۟~<,*f~U5?Xn;LLUTe˗Fi߹v⮙5Ij<564tckK 苽K7)k1St=/ݿ "m3{ZUWTPg?(Pq8"f?(!2G';痖YjNRZ 9XɠpDחzV1yLDX?z G;GO=jnW@jͱEPnUf狈3 .Bb]0ͱVNg̗@{B򪋁ԭ TOZK"D&dߙQ3B*UaRgxck[=NHfQaADǏsN?\pyQ[ 'çO<~p:ONOmG͍u" ~$ L~CB?eV>&*9i6Y]`;<ǹKжDD8vf֊r joj N'];{'?o?|x~ݯo\)kjYYs63$I9z888XX">nLUl}}Y(mn>ukDl>a5X9湲ӛ9HAP3TRsEJoAd_.6`{rʠL=բ$ c05?W(͹) 5UUQP]fBLM$I!F,_C/z3Uk?&OFåu$rs'8Zp{?yyTJ[oeZM^=9mllϿ 0%B h;l$A ڶߛMg{{umsp"̞x>n|zSnG)uO=\'&lJ`5XPr;fz;=}ͯgfd,GbF[;>!}Ǘ\]\Zlgxt `K hykp`|:RIGGG99. $&!HNY fb^"aKWJڮ5{s \v@Z I05լLLD…[[KXWױ@꠩8怺oi:zz ƬKm5P- :9e\5 fy3 ]o,9 9Ӝ:&He9 =ZVUX:$+@qxؓ,Rg<d =,/WwlaT?ܿs'<#$r)oVp""mnnZ3_i.(5F7 )ZN=sLgӣy机)7mՈx|tt2f{tZ!urv50z^=>ϿQM"i$dVQuO>={!wf3͚nԐxiyu6>yv6?Սm"KHUV)C$ C$bN *]jƣf6c CTMJ7Q<¼{c !ͪ٧!ѥKW>B<>8lnm0E_CK[9i(#22rDP1~ي%_%@)?nQ˒D_qUwMZ=C$ PPv1TDS&&=/TEoD\M01-!":%[Pq5ޠ799ŕUz+(zto{dYv{o*]ݨ~TM@dʂf"ۚAڞjN 3IYMA5sJ-svzS_"mnEL f&QѓPVvK 3ih ~ ԍ|( b[.^]<n1Xy`iL@N S\rI>tt2|`Xp5勗=jn*]Qd\jsB"qoU[E$_fS/)=J@r;uN(yImD~j{zz]JA,kFW64gd.lI*? 'q8߆#tι8^IkN}TϪ^DwVVꪑ,Mmgsʅ_Z ;ou]x_tf+7O?\ҌO~ᵗ,6OOf&?_W!y?s`SKM)T-Vf0.v_}u緯_w27o'Sw4Vc̡N TH۶DB\vuosO<9<<\Z]aV0(WW"Ul67W.]&iڅHPṔ&g-p8D.4j;hqiAO?x8h 7o'++/^ iE*)@I\Զ-". E$싄nl03 01tn $VZ x{* :J1 D8 iU9g3G엨Ϊ08|U<΢nQ@!k꽐`֠9Y "2E3o` \$Y%ys.">)pPUh'={Ք[hwmml٥Wo5] u Ύ3pW^z彽Ôx*pS 㭭(H.O~޺][0f*ڶsO<@i|b|HDn|/=hэgoԁ7^z|tbBׂs8*T@bsD9e4Jqbٷ RR.>6{[["v49 US BӥfCaHo2bɩm뺮*x<۽vof\}|txqu ;*<<`$"@@%jy6xfHIqU(t)"!;][rw.`X+=ЮorjULUc]1 3U HMϒS@!FfY/Fgi͒ܔD,jLpW+FCBS! ' ^&ݔ|TL|NニL\,J "F =c,,67>me7W{o}7lyuWA'R˯r㹛h4"bNmW?壼uoj u>BU].t1` }[Ϟ}91I9,9qɰm]F@-&3 Rv7G 9ݝ,ru뇖#xDfơj92rx>]֌jy>׃!3z劘ڂ9Ma*tbTy`Fuڪ C%LcU#G{W?d</uu֥!r Emiw"T+q@uyzإ/(T-1*M|CL*)bJ! Cb6 iMD./4MAMC~)(&i9$P1@6QUU+XJϻU* س$'5C4T2`MUy|x'~S9|owEe]>fsٛoʗ+UU,>L||p//Y>JԢ۶mTDU^ IDATUahqɏ+,M~V%+ޱY \DrSA3j>pfO$ѥ ܵ=g݂!'V̑ U{KתHHoHHfUk3m, FLDrMfuU7}zi vw?|ۯ߸^ zx8${f=./=پB{cL@d "ܛYj?#+SOfL`Yh;‰;OېcCįͿHzO|(oݻw RJ9%5# [<1ZX\X\)UUKev!hHS8-u&"f&Ĝs;oC]yƪvH~W.^LغqY@FޝO[*}§`q!`qha~>V{_xz ̡::^~=6Mu@nID#s<Ly_duL=j#K.MNO{W_~rtҭ=T)gSY p`#t$}Dq7гվ.ދc +#aA1˒<7_IFKC>6}}3L 2"9 pBAXԤGQV*E9CS1o|P g%'yg}[[/E?E^- z[/_Gqu}ŒTkճk.] FK^jԠPJ D}e& jf`Pm;ޮY4 Gƞf)]X;ӣg_=%MӼ>I)Uuc140dvr0Pɭ2֘+֦䢬h ^~mww{:<31Xڅhwx{x0Ȁ*n`ɭhHD"҅^[ #Œ^?Tݭ-" K,Ȓ90 x\i;)P%Wu7 Y8z (B ,jQUI9w5"@틾Kڀsa(g[8Saw"E8SL|K7_~ s*}VE$J}AÅÃi5f<\\Y\YndmBAS5ӓo\~㍥ W]4lU9AUWV*E"fIM3|3G?"!*Xjg FȁhML̴`0b?8:cU擓GKKVVMużz0nۣ\.K(AD_񼗃/HS3]Ȼbgwb0t^"^UHʮ?6%/R@< 1)km"|23>/h{;fԷC?oA u@~p9vW7WV. dF  `,Y˾tWUuYX\\^F!TL+&V Mvgcy Djԍ=z9 iTM vVUbhޥ2! ȁ1BjH&u51w5f͒Ol $[$s8t#fTu#CE Դ7 <ͣ?t ''hO{L-wf!݂15:qJ9OOh{sT6"p\*=/$oME /~>ZV7"YF1 RQEU ˃$#@4?9"t|EVW_BɧLNʅ+WO&GOA 8Z32"xh-%^UOhݽTMMx@dVźc̮^6k2o+'l3`&nl`@_b ]0x9_u*^2};0Zɺ pń8c@DX!do mLt܎%= wvqCӀqs ZAxf{]/˅?]77k0Y DM"5~Id6ǺU--.攙d<¢!zE7&{}9̩i啥dzxѓᠩBx xI ro^,*%tTMG4@d _Ui!k &+ `s`^"2gfl b /Cԇw.nPytA@b@g#ԗ?ݕ]ׯ=E3Sb 4Kxl2J#̓}F<[{:sstvf;_7oâE &o\\qU}k90a n!Bmۙ/PsD K`?Ttrztx0ZȊEPMb \X][]b9ttrt[57RI8$UEP}0DkDh]Y}ܩOt]M*I`Cp~‹nnkJ̦ˆ!sV(b v} 陓=wIzw+2Y[23#b_"IDܯҦ }!nmnݳf/bח{%L<B'q='da6j<`0)6.]vi21MmJB ,hʕs6B% FhMf!0"v ;3bVq wCq6~ޏ._0R6 JHROݴ * 5v!hz3{YW2K*A"]峯€CP͈&1ԾAH"%1!r4z6r@FBvGMU JNNOsޔ$[v lS#M&&O ͺSeu].BD?>pmc&ZY^a#sdDL4kV5IAQ5;bf㱊-`8x2=<<:>>ZUU K,gD@ܶT7XYdfl**$h$FQ4 fWtO4f_0Ȇ]}Lxr'/~rJiyu@n[7*JUɚX؋rDЬ%/K "UNdP}=_db5CE9qo }JR f!ғ)&3 '\ ˋK''?= Ͱnz82eѬ( LCp2ZΚ46UC;SGyKh`h_c4|D#ż% 01W>oݮBhcS#3U7$0u)tT4PN#af]89 H!P@jĔy)!dyC|-0g2D)P qTdh0 7áL&$H001|zz:OckVwwI:M30L/4 v8l*$g7#u1@&)ܚ5jY  D#G5&V@?xG͠r4QiFT$D+R@}jΓ*Ŕ)pDR;x0$OƪBظ?vw! lom> qVYoE \(S""菿G^~yz:&&tA1`&-\O&f*HpH)OfKW8YDD͒T73ׯ]haqauzwхWC]em)C }#i(8!05(xHfb3 !D?PE1ĺbO?W0et ѧ7EX|>cH̞Y9R궝ft2MX$s ?[ݝn nnq;uibC>.*\(3bo~XP3 -2 "ax|ZY]C`f?q<>eifiJi0hA=W.\zҵk+˒怀J@B ƾ,HImMcȓ= )` r`ٽɧx)v͒`1 !"#AM2M8ϛ`gks|˹:ҩ/r€7aEQO֔ܢ? < PS0ʫQeB"&FFK6KBcUNOǧOgiԃUKKh4 iX!eۯ&ʾrw}D$YH D!D2"a|0ĈAWfZUR;wzZ q>yg)TU765UٶsBލGv臾%>&~ lIDATe;_qV~-B0=Knݪp0G 2''ǻˋ ˋ M{H%텋Ch&l"3Ճf:n_Օk׮#`=hB|'m ''{W9ˢgw`U]{߳X[3]+ Q''#XWM@EgQŪM)gU'[;f 1|:mSDwxw'| ᾈFw78Pt[o;;>${#`v_rdqag*x8XXVUuud2N)!AkP4UVrzD$DĘ|>{`۷Ք 2&c| L5!fk5 O*+ŻLsE5֑O 9p`0BK/f9YTڔE@$g#DUpളw<Z3þ`,W(\wwz޽Own˯ֶiᠩ[;;'mz0jAU..njX΄#!УkW]r 糉X"HJP5@Id`Yg (5O&M89?8yr8vwaw{,\u vv9A{ E;߹wi9u_t7_P+RȴUuH1DA3?::taƓ slXW1jX{/KAR! 1Eb "*HEZ5f)paITRj٘Cy<>9{vFsoDg'gnYnNB_:߻ϙEګ_~7HU!D怄Ą1D= m//-/#0k1:=>~L|6CE&J]Y2EV8==N'f0Msj%ѓC@::?U]>mk_;6;`^B忞0r{;uqKu3`f0cѐONOBA3@>r7oVUHөƺq*LONHmǪO7|bE ?6xRo¡ T= kkk._J3ú=x`f;[cPN}qL~Ihv[__n%ͽP(s]H[6<7Us=EYqOl͝ ɪmj߹w  E ;i X` 3muvYzBrϣ[+mM]=&AP(\_wyw= @6wΝ;wߨHpPt(;;ݍ\QBrP(ʠ%( ˅BP(\( E BPtP(. BrP(]. BBP(\( ˅BP(\( E BPtP(. BrP(]. BBP(\( ˅BP(\( E BPtP(. BrP(]. BBP(\( ˅BPtP( E BPtP(. BrP(]. BBP(\( ˅BPtP( E BPtP(. BrP(]. BBP(\( ˅BPtP( E BrP(. BrP(]. BBP(\( ˅BPtP( E BrP(. BrP(]. BBP(\( ˅BPtP( E BrP(. BBP(]. BBP(\( ˅BPtP( E BrP(. BBP(]. BBP(\( O_qs=qtIENDB`PKFK-Pictures/100000000000001A0000001B0EC27C29.pngPNG  IHDRt<sRGBgAMA a cHRMz&u0`:pQ< pHYs oytEXtSoftwarePaint.NET v3.36%IDATHK?HAPQ-MAB 54)548-B-4Ԟ8KZ[` AD-Ţ@Do˯'wQ޽ޝRLp͍|(e|HX︼d5?Ow{8[_Y&'s~nL'FF8d[^'˯uw2î16!vua}//nCrvZZ*DeU*AS!D_|{kr\\0Z6*T\~JC M-Pwiz)xhn|H㹬IENDB`PKFR-Pictures/10000000000000FF0000005ABA029AD1.pngPNG  IHDRZ@psRGBgAMA a cHRMz&u0`:pQ<PLTE  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz|||}}}~~~)+@ pHYs!!VytEXtSoftwarePaint.NET v3.36% IDATx^Ŝw\SO2eI\- $J@bZȣhA--ZgE#E}>wE GPч *N8 rz7 poN~ uZjWܾz1?Оk'΍57q{_zyMC=ccuw~-%A`W`lիؔ=w8~f ̊[lO}]UoW>7}e fM r0 Z~䵌`YS=7u4-NsҖs15515߬[˃zjש۵ߡ]bX Z~ &-H [*2i@bᝰ 5[X-HpGxC_iVZY m9&9 Op]ů4hrpIrMn6yCp~7g6M"5t\!o'؄Z@^  ~ <υy(H)R㿾XBuv.'{p"IkqfkuJ[.w+/(.,%_͍ + -#DM>lU~&A䊀 L:+CÓ޾%\Y6 )51eJ? ?L6\׿ҁs/χ Yj>qďh29D?L.^" #NXjsO TBldD9G(27Z .@BkIE,?y^"Ibfz۸?W 3 L%&y hרt!PCё ?Y_/6QC( ƍƋ5]ɏyM"i1~ܠIBOLDo9w@fLSJP5#(]ԞJ^s5vN[E芓eZSO}SQ_at+Z_yTRhF5Ji6l%Vx* ?dT驒=U&*Klon{,?k;\Jd*}Ó`Q;/lf?L)`(Wt\?ȇF5  o 9 _ppT2Ax@C}Ǧso`,a[#1}/m~"}KNo'9} z"?>^?ւH{ 4GQ g._Q^6S^o=?>XLqKA|YI1M uѣ |ESI TĂZ*N!Ϧ%Ά(,5z  B4!IUKRSc#1]XN:jE@݌^Ld,3eE&ζ,Ł-=nRv@ldw la|jS"^h %Ͻ[TU1d/Ej"6Hz?T41#O&Lu~Hm_7}IrSRH5?Y:ySm+OuqC̄Nի{ZH?nFU$޹u.rVfjOñI\>JGpY^1gYGOoq bt̀)/3{ uOxLOv9o=x3yHdnx‘30EQP!`'DYE8v^N <0C3[F?%~?6|{N"Pjg*a TmG 3^6KLjkB(҆rMFm@,ag*(Nuۉ6Kl xȵ Y,D?F,cgw^;ow懰fM2.<a!w yu\?'L )\Z#XJ Ǜ&̈FJIҸRd*h'iF4<~$K0>2ΐ5dQ?D/{65=! qA.f_)Pg!+ =}=!Dd ),H?ȳs(-z)! '#U=cO JGwrQ$"rUx*'RidrXA@ ;Ȥ4|bpK`Kr@40JӺAlN r7҂Ax) i~DPveq{f#\CxE,%+@. Oب1E w9Ƹ?p\S17㜑"Q}A_^8NNmzb2_ՈnPULv@A:P*#َ^qWrݲ h))6V-w3EnGJJ4z 4\oP?-A.RUdÐkz$hɄ_ኬTWegl[dzLB ])Jy!jsxڄ=IENDB`PKFq$@@-Pictures/100000000000001A0000001BCCF6F964.pngPNG  IHDRt<sRGBgAMA a cHRMz&u0`:pQ< pHYs oytEXtSoftwarePaint.NET v3.36%IDATHKAHbAOS+R=t .Qԡ آ[lc]<襓SI KѲlQ*PFPA=Y)v9f7T[K spG Bu5RZ;Sp7nq&AB**PRgiG]]:7(A+i\9?/PGG~%D7N-frx< 7f nhHWW;{v gW2=-䤚i2{'oxoO&S70>GG |y<ʹ3zz?g"%NX?n(8&~p`//jP]_&scb}6hTdUU ,'v;86ff`::ёd}Dyby9$h><`kKH,z%Ϯ.Y_ʲt|nnbi cc7oos;;қ7'sd'4_9_#VVd":fќ51FIENDB`PKF{##-Pictures/100002010000004D0000008339E69CAE.pngPNG  IHDRMtsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDATx{Wu?{ *+AM$"Ѵ5Z%0.(7-"Vbh=X~"Xt.#h#7tWc=i`k0B((W2n_~:6<X8F1`w'W2}39 m'6ǀ/\9{k듬SDFoǦi_z+׎C4h{cJ=H%2~OF"Z]2$'# ɓF*YUv,_ Wjtz}&  hU_ kN&h l yV,}MFAl*S͂l?({!Dw9ydnB+[Ұ$G_BE=0.ɱ{qAyvJ;#ޯԑ˫R"V(qP\Eh {0HUvJ'z I%59-l8Dw^φaNJ mgrVx Yu|$q`&*VsGH 0pdt/]̂Fɜ: 8mxw" h0r<>lnF선 eЄ#! de=~ q ;g|e6Zifxafk3'ِЛ! oq5mx(t/p1K=߁me'4aIu]O@!kT-R /EJ9_|g4" G]Qӡ^U5G&]IFY5rAx⡶<0󖭂}QAw{ }`f*\1K V¹NVxRh@W &Ne gfC7|.j3{I3] C x-݉={3,;u_{S T:s_",ۦ*`-i;#4<u 9G!q#Q6w.پ IC(ju"'/:rvIl<WTvL8߈ PT䫱>"shvkOBlfq@x=PLj p]oeBy_mC8@H"Ox*髑B!I ?7jg{f;4fAT>ɭvqY" , D޼}$AM<t- wOeB( +JE4"f97>vo(4qhd* $(B4޻l2J)f64 lZgp=Z(Q@# |2'!(pIp!6 qӕTR4loEˁs$! PC(;QA]?UpX8_L*kl}a-Z Ѭ#hzL+F5)MBC@TzbYt [ 4(RIi]xDvh7A\;x#.NH{tS8-6ϣ٦}C˳^ш|UhWEΓ)qfeF]bFN!yw"hX? )Hڐf $$y6Ґߥ2%"gCd=pLw=Xn6{ޝ^2Q|9*Zp T ΌvzAl;bɥIhL'k!/ lەBpRcP`+"} Z !ΎþFy*4H&uw^ShIh_z`@*ss]U&3ɟ/Ҟc0Hx}2ٍr5! 1(07.Vn! =>r+fprJEY<&uEV{I=O^@b">dcߋd\\OD RG (80JEэ}wpY g XXEo3bj7?Į§m9IۢφP~mb4 4s6D4g%D0E:O|L;"ː0ˮEB(93;}Cc($iYfɜR21/D3Y&vҒh:ыF ;&."8̘N=$' qYZօ黜g#50+P!dE 7́᥎B>hPh{v{9 wiHs8⾤1:* uvRIA;ߎ. ,QaWF4"ѐkG-o.ԋ/^`fNv 9\_~C x4[OoD-Sd]{--DMzkýPD8}?N!eLFl $φz"y'݄2PZ)硝[kՑidуbϙ[/#g}=Џntiͭ@f])iMHGU!ZpجC?79(DTD`R39yF yp)m7QWHL5Krs Qmd_єȧHԽ} mw]C!z1}|âZ>,B97!kH)D5V_afKGLDC3œXd5H\HEc6hyvMA1QqNU|+T&A.$;$2DnDȶk8\!=]єqp7W3E> ߛ`ny, $C<]7̗ \dMCeڎ|$ \RSp](ZҎBcF֙h䴐PN Hu!߂Ѭ j><;MF4%0iͫQhJwEhFs8m9mQb$;<мaSH 7R5hcx&lwK Z\C5hi/p%arm -AQPwbEƾ Za{AHc3d+Rkxe4F0i=G.i;vkdfE󓯠 (~* Bidѻ-#ㇽjvʵ~u. FݿY-ecH'Z"%gaGؒ_ڳ0$i%JF{n_2,4[W Քئw~IENDB`PKFk,55-Pictures/100000000000001A0000001B5E8A504C.pngPNG  IHDRt<sRGBgAMA a cHRMz&u0`:pQ< pHYs oytEXtSoftwarePaint.NET v3.36%zIDATHKO(QyaVcʆ J i(,Xx+BXؐiRR$6(ĂI;yxMt/.DNQ# ?eq+111A,,4ioWԔAJNGUvvbgJ]4# Pd؄DZ܂?Bt^82ee!5OOBQQ16,.̯aMiW$#FT>|oq,\KKW\sP듓 CY(Unk/߂ "?ڪyV)L\ i@oCg 2T\Y3) GYn`w7[q̚8ϴt{}Ƴޚ@Mًq'fx~6͸ (Qdf KKpٝ#<`^g<&'82r) {{Z%77I 孛&13AX]ƓX2/e͋sdeBn.xn^]%kk47'4scRP4ؔ07Ԅ ب9:U/"h+HIENDB`PKFK-Pictures/10000000000000250000001BA0A56BFA.pngPNG  IHDR%tщsRGB pHYs oytIMETIDATHǥV[H[Y]$EB5-JGMpᇷ?B"(XU:"- ?m (cǪQ!TbИM̚}ܳ9`([^f^/kk GTӰKpu*^F e^/JK̠bw.,`L 0QYZ0; "! f7oB˗Fp8 r@B vV{h*iGEYŷo((8prr!|> J7|x*`0 66Lf)9VBFa@Z{˗Z`SSbS [0YQd~NgJDC!I;ed5\;0fdRF޸dsfy,aaՌk{RfRTںl6ܜ`>U!bcϝ_>|uuu$~NNyyy~l Eo}}#pMsto "++ɤ^IVb!`Xl6ެ}>>{ƌ *E Pϟ `~~  x($EHM2 $B~q~q? ;H 4iP#!RYg`6LOwOwWWWW~ܟ43zνx o?PBgzB (2#c<}=rߣ`y9rųg_|9Yj8LEL @ k! r~,G@6ĈX P HL?DC<>18{Ϟ}/?#"Ii/M`1 02Yժ,g%RUDDDl0lg7[@wh@d H@ Do)=A?= :8ν쳯<˄4ᰜa.Y)ES*fH(FHz3!!1#ZZ](aWDD@b50Ccl!h-r\dI /@@!~ {c@== g͹s_ƗW%&F`CTAR?]1QPUU5U5f HHL" HZ @"R`RQj j&E,EM " !x@jI5M"1#{DՏ~aP}G~j#= 8c=B_Ͼ~~Ğ~InmcƦ(0"H{w3"1aJB(P=3-EL mke|f&P @+s" !"2Q1±ItFa[]Pr:QBLM (Qh):8DB"DhLk:8:;YIc׆1<| Aݹ΁/ȓ<.) 2cbpzmALr_b2f:4B5YR$*dќ({|佷7FDhey_9xA *bf;#8u{S[]f[$-u0͒IT8h`y90բAUEMJ-%*$[8^oDiiю7G1}S}#?y9gg8gLDDT M|wA<ah9B!2P@BdQ;XEb(EUM3߈gȭh;U%'}^wof1=ghfŽlO|`G>}׉JY5]$rZ ^/#\ռC"439jn>@@"ȫH4n2;'v52j\ sQ#=rc/zų⹗1tcPa6k \@XK*2ZkqqK>6ŋgz@@T - PTj)Чry?M{0(@-DUPM)0S Z[o?єe&}^嵋$I`؄~cFsj d(jP`HL'ú+JDG+cV>gGxn=QK_gf?>sOC/KϾ,E"Bf\J`h$-{rp%brQ(+Xbӣ۶FBotu?_;,XVCD53Z" ";˺uf֎* -}ڿm:Xϝ}칗=9 "Q@@P#+/d" DĄ!RQ""2jz|=ۏ?N{O[:{}S1E]g6TDMEoLӵta) ԉefbV3끵E5FB}{~miCIeU\J>X7>ڈV {7f>}}gHE V7HzNOA!]Z"Ũn[cmPT|?vsh=s/=ҹ^y.C$[Zz-SU("9LZQD)p*4C(EK)E`q2Z?}j|F)Ί PugB`交Fma. iyk;y9ZpXs!5@B3fD†Γ%>eUќk!BEbH72\wk;;Eٛެ\_CRn"$AS4h;FLj#3,6IK{izz?,{CSkF$ɥ a@53۶<-.{.ˍi~w/KFsa^\]=,KNj7 MD5P$ hj[)%,HxlY#./J)JHjBUSu>[fyrd܈?ǟ~`ǿ;Ǯ<ۀfd&2,b'R*W$" ̼/3R@.lt4niIQrag[þ!v!@ D鍝o_C؛MgC*Lt(Ao=][!|fbEULj4PJ] W֝ǷM6&P$ `jyr c>CÇNZ{߱ p?x`gO j,r}kY9&t1w{&4#0]Q_"#Ytlolz+~ޛӲ7+r`f)Lm-;6 P1~b}<'/~Z]ˣm]H:,;%g٣V6 ?[hϬ8F6`Ӱ=\u)0E k߸! iMƺ9A }Y2a2aѢ$g* )0 K;{|lc9+b6`B0|ljcG,:O6e ;\BAP٪RlV:>}{8Or}ᢼpa<۲RKS$ !Zɒ\~%mN"(~<_)o|>6q1hfBAb>\Yf03TS"9RTE-wzj|lH uʖeA ]W1%ۓxr:ցv-b,pcfn6-'Zk3 L0,j @HHHhjb"%!C6bH$)( `611bD QBjf-3f`"2~-}}?z:YQ{o1x}Xki!yE@[?nk赋5ǚcNk ^ORd& MJ֒Ev.ՓhiZlB/|_}g[O~D-UD%;MG %M5aH%B!J.R]#a#0YzQc vc^e!{嫗 pysT ]Jctؽ6c|I\vgru*o\;a`R Rtc{<4L LA4C4@0B3RG!֑c'EQ yq[ IVEZ{N10 AD!jQ@_^bS+O\-Eˏ_q.<+2sƣm$ |,WcxfKz(RJ b `@ƒ%r^I'n=<}8^GDE o~ Ïqd[__|9, A]r9D!\ Y&`1a &"Df&ވ6?|K|y(6]?O;ח8MUULELI3 D[>>^wsXrc{K`׍F "(#*d1Ȫ&D W3z#jJG3.t5#G %ƆC9"ɐM!27uYf<Qͳa6OՇ#ݲ}:vnL9$ \]9*fDش1%keF+z黷Ba E`RI3ou:<O닿K/~ۡ iYm۴Z+ɊdЄ+SSUf&Y6" Cn@Ly!L8[ A[oCwoܺpJ/@tb=_c֥s;ʍ^mܭUmI7Y%4ݨ~ "˜ Qk9VFP?B$p !2"(i#@Ձ$[:3dhɤ>:̎I !r0]e.!$Mwgv5F7'Ou,#x};smE+wAu$)ֆ 7m*ȳ\ 'nN8kXS<>OksA=y//})R Gknܶ@ ,t/`ڶ1ĀD0Z5չ aM@ᨃ{Mh` ms=~L:\k  a~=ʅZ ]!1&j#Y+ZTW.$ & Mxi}լ1f)"Z XR"fqk !#eF$d"Sf{;!=@&D P2d5F}!CX.wgϺZJ\ ?<3[Muu1ެ_kb̀d"9 M }F3@]wnB[EV~9Eg|t3yOsO|>wGpv2 T_ iD+"BSO0\$LWV<=޾T*/O_u@mU =[U)@CpvН-t Eŀqqb'(Ee[ͭ"PY[&"`u\5q`&7]-j@#L4QW*R!@%T>qa31CEU5@G8` d&$@ B&ZĠXGczEd1~loV] Wfp|=.\ca4BĎ00#R @,yIF  u6H)mwl*@׺/zɟ='|3sO~«(ć76Nmomm0!&4f"?ןN{/_^ߘ74Z$vK.h6@lgGs弩"ņ Ls<|{wfk=nEU'7Oo%Z_m!*PT|#lo,\wY:tYvbY$ʣQ\u]ˁ4IĆǓ30dm #準V`jb"IELE%"BEM(1"rLC PŊTʐWK&ЧxN)vDMc=Ra(ALX]M68DRY.W}Olu*HSU^M (X_=|R懇}ҽ?pxRKů?qgR;jOMv$ iפ$B m&vQr*Rd2j'f@)Y}ff"Mtk-jZM0̓jQc` qu9d(6#S ^!QR mZRHrA&D@n<D&1؅fDԌP45,R}+CY G& Ԑ֛ۗX[Fh3i#wL<=*g&t3ݽOŔn/W콱D F@*Y~iԝ;= l[lZ1X.O:'sO~+4byjM:TUT(4!4VHPt>γF#D,K&@2M{{9}|чol5#nM{̱}^$DDW'rD Gmjz`` LjZ72 bfJ^] ``bā`j@Hu—i1)ʁ1(0Z4Պ04lf9nb:\GG 6na3S0X?V+C}XZ1GœQή-1>AL0KC5@""umudIlڱ`Iau{>@5a $C곭fBD PKTJ 4nz0+@H I2w -bP(3'v/ިdթ$1bCSQ1;7Fօ/=z#M 6AI% dmh18#S- ɻ6b4ciLF9} ? ?i̷?څotMsk qPIn@iRʒ% aD];6]dfSA @>^ʦFxێ7w1% eFS"W[ 9 mG=PUSk"nm>rkwSQT/J.mlGM(K/r z: Ea!fE^7԰!&CC3Dl[6b+b0/2wm0ADu}ܭÐY3h'm*D&u9N*IC56sՌa2k槮ZjgK1q$nj! szxxfe;[H|T`Юu$j j|BӒ-K#oK$͖pUj**n!Z{t?ݶo8Xlb=[VW֪O?OK}Ry?],Ɔ:#ۺњ`P,PGiDyݳO|_xK[יw.a,0TʐIKR,r?/2"6M:ur3FBS,,rK*C%U3 m(*MQ86'v=.(Kɫ*$ 93ccR.Z!Bۆ֚Аeh ERD @#X.ia(BI5lfd!FBFPD"F,&RDDPTĈDeJR7,ִ>pdsHHJI/AA`ˎ-%y b $RDRe;DAV,&)dMX]ᜳ[Kc wW=]ܪ߽_uZS!( ?LM!d`!!&@Êՠ!L@qw7-O6H0UմKNl*:yh~|w[8y]êzMܷY'!kh" ܣ[D w]ֱvObzq}vZ<">ÚiHA){7c4v1׹UW@\+puNT"xV>ƒE.E5۫/\] ӣW]Ȍ$jwX=}`c6m7pjf}H,]yV^5BΎ2-4QF`j<К1" J%fku:DԚʢ00A%գ$B"ĉ;ɜoz@:ml:[müw=YҐ1UkS5Wl@;\˲9DP"ND,y0:!L}hMuV`$"J%שm.vQXV $ҩBuWB@Lb M{Ft+b_("ŪwOț|䓥>gRfuZPDQ}?߇77#w?u)i|<^xȐi3$t[t)܅u[睖ev6tay?^#T[S DB3K f _ze߾o;U}x5o&׷V kJDj' H]oe܃"0dApp4U?~osSHyWٯl}rvtIofWgfB1kY.Hզm{_ If3^n Â9 Bb[ D(^'}}mo^6Z|WS_}ؠ'v v2{(S8I¥Yw/@5ܫ;k[$ZC&a xoAOF r„djV-6GspfI{{ji򐈹7[idSs녪,S2D֭eQr܍DнGy`ug8x@.B9qgaЪITE$VĄ=5#F D\hA@%A 0Wh˅ZNqF u§擴V{ZZ3"x[:-DV"UfZ]C\${I"!8*um "2eUqpw0W\- ,<:e dNw{ȫ\ 2ϛZ,˵0 pBWkv2Kǘk+}994 :?=4lj 2ﯦr4y3)32kC.bS⒨M-4޷.y#y|KhӾ{Qw盙$8`6S5D{^jCFysuntǞ՟>}t/]vNAI&` $f&EG5t:UgLČyȒG-$a%(̀0<\͓JY:- ={[=[ > _|W? 0QrwU J*nI@t2*ӭto_x6YT =beiҪCզAYH᜘S6:N͑8 Lx"fcӹ 03=Afw31EN[% C A~> i3OJ^ ;#k7#`va BBIIDG`צkmk6TB{'_mצ[+^Jw/l4UY*d!%EfDG>D^e& yʏEqsn?S~3GgE)zUO3=fcȭֺ<tky kD9iwGǫR2Ocԍnp0f63oHq;1>>s˽n[GML} QZ,X^S'"' j{}m3!ZL(JNuS.jxxu)] X8/a -zY2"fi?A0BZV%FfB @:;eʋdڬ]([*yH9 /`Un]uw5z5) wb=02f긙'L"d!&LY@(ɻ4S8!%=@ ",U?,/HY*$0w ܚk$,yȈdnHĝ6g IDATkyi,23Zu*H®X#9-JTa(HqmHHxkx% ,xk_xuZ vkUHvGH"F; \4Ȉfg{G~=no_z3/|JGپoh,3K IHQR2UNENoALLHH#ڤ>OWt7$X+n$Kۣ4$vt@5xpes/rwyq}pY;BB D$ +U}u3,BDBÛ֖Tdɭb]<\'eX-%>65u!uP &ju@ ֥1ЦVúPJIx` cXx-"Q@ #=cB:f-E$@jH,9$ >,V# #uF@Ȝ͍ eێ.ĎnM}?)B|8U{}=50K՞`iX ]ݚiv:ۋ{=N}Y.d1Ȣ "_r8O翼Fny72䶛|>o~sYd0T|8*\8̼JLA2GPhSvjSfRUr"L=/zoL@sBƣU*4dD{ߺ~7/ՋemRa>7s H(Œ_րzJݣ `չnP3kjUYL}-=BզLU[:{!v.꾶$$ =OD@_ӫZcU@9KP~Zr9 "KJr7#2M(أܴ*#xk}EgvazXuzӋ{vx_? Ͼ?'SAZ2 a3pkڝwq_9$'ljϻ9_ǘ՟} Y/qLPJ-qXs\5"/E?@@Sk\MH1Q-e)yY#{8 urkjPтo˷ix4}>J<"zaSm*:iXp7^#/: Kܴdc5 LZ`1M`j!Q< 3QHhLlZ[+fncJdv\$ -T;Q3YLȵ6[*Q% K/iڴIf)IJ]y [¢1"#1 M% xV5٩ A tR֚E5quYw [Mab#$:[nj;D00Es W}~!bW )/uA +Io%w.mWݣ^'%oίD2,sK=qsԼ}c?+?;i3&YT Ȍa,EUQ YU"yQL ZLDBmgyR:RRb//kOl%w䩓wVIQ-,تlqX*Й d9]Rh~R/vzds`BP,"ʐ6UwR ‡eA@B @ X/txcUE@ʒ4Ҵo怀Q:?l햧C/(1$PY˪Esө9F$ I$z{Z8>'^9tA@{x67SCaQUp@pu@b40hsƄp% I#91 AX_y>%Z`b8s/Wۊ! !V/[3hN@N=գ[ >Wq?ΜZi3NPR(}ndڻdˢ~_gnj>}яl}mЪ_ @dfat0cX3u!=;"rA7DuG[ 9K0nʵ@. tVr7{_9T p'6zB+a{4Nk3oQ¢MC6ɢ&<c8GKD^uғkbѹJ %Q2SJYa~:"Y)P2ZZ3H攒sF tp@+1Y"qP 4AǦݜRN!bd,^.z;kiHVfM0ss O\! z"3UmV̌gˣuHq@\bkyH n$22D-?'9fxt1ě9.w8Mwv{^MvԦ!ܦ6i ־п=O~snjT~c{ ,7Oۓ( \"πݼ;"aiHy1p$"D:poކ,a v:Wq92bP1rEѶ=NCc{;dFk݇Kv4 r$ |v58yԵ"Ŷ !` JJE #" "Gxt 3!3jmf0׺Q) gy7A2"ښzBKE#?ǻ${3KfuFVA 1'HCZu (bz{4An$s.€HKLPZD@B Cpġ%djJ88}Ͷ2CtKF2G81 |Ga -~۳w=JgG2P.ڽK}p $lj6]qzN`qoSKzaK{Y/|(j7W?~oև_'5p ȫӒ5LD},܇u!>4&@@1 *!Uf0a=s?;?__* ˑ2.rI+3.gDj8Z;Vj7/n=J>MOhjWg˓;Pw&0|x@qA^h7-ގ0a `# 8,$o?>gׯvcƃQz$^^\%!"p5%$ʹ?]n;@6XVgϳOg7sΛws>מ˔ co/rwEUɽc#aSI9q_{8 A;9gf:Sv*G !zXjf?_Oứy7?_\vJÐqY^.F">RC)Sd$!ޥ'S1YSZ FBpwv;)TRY DZUw$ҪvF7!",чZ, _|mqU.Y' (]HL]txQ.E!!8mjTyvmѬM`@*GsӜߌ9 $@pu3[+)DHմUY.QB!vWec={#sp d%< o;##@NmUC'EՀ BR&ʜ (8eq7fps YOo$9a;!!"Ap /ң'>[?;O9L#K re?voj0|~iX E0BK)E roW[&EQhaaӾ2U9 ;Zyh:j"$&ws`zp:,DHHT]$"'IV5X))  Ա]oZ.:ujWZNmw׹yۼ:AY|B\! Oڦ:u=:\ ̽!%IY$rH,\G9ُ6 ϻ9_y_$0E Dɿ};^x޽XOnvU-22)3{.c֋{٢JqqцSYhO,"%YPbG6oyR0`Zv xm,7M t wȉq_G4e9 Tbjn>5tH N'D73"M|+" }HB tex-WXfdhljd,DDy=VH ewGfMLG\&"Ҫh3pрB'ujul,V2sV:축"5ճ?eP?ki4-,Ryjo7o4^~3u~mLH n/„ 썊޿lG4ϮM᠉$n_ԯ1?4j˝/^sqRfoOǷK3Cz>Eg65%)J ۫} 4|758L[G?y*Eh>Y煙@ t` Qo' IFGpΔ2E]M͋$~|muߴi xx$HR0 M뼳ݹ# zPx,LI Ms<">; 6@,9w(N*Wug  H\ɕdj:Vȋݓ@"DD\H7 :/EJ G@`^,ޅ6=s9B" $=/>i9d1- P583#48N& M,s{N9ܩY eYHF@a(%L$I"D~yq%>Z`_o~^ɭ7U. iޙi>_a$Pwsaʃض1@ 1NjWǓA'D<]$I& +tН{3#yIDBHī?[Ͽo*Mͻ9ÿ?g:f4o{y詓w=~< Y}1փ!ZaYfHw0{ݪ0jG[a6f/q+R,aDN@JZ/9̼Yn{ٞ8qY,STCg,Wν\X5; @bjTS@A8;3Fjb!A"( h8J#{ODU]0A:q;E` {g&UcgbeF~8&!ܑ"1!Pycfj$T{Fq9N͛#aGt䊫;"A "8K&"N $a:iiT#HՊSx086׾qvu8F"mo@ 0 2Fy:}} 0 cQ( [˫iH 7uA@Ghj/5` vx: ;Dh43#+Vh긤w_L ?njKO~~/>6nP 従 IDAT(pƇ_]wR1wE=*F=RJ!.NMYfkOy!y!Dtͷ0@Yu']MwO?c|ứy7 }|_yngGFWwps_鯬u޷SgIHId 8B'V ;d+pH @80| $(R$mYVlDDi;HJ$lp|zZ+[[!`t'S^{sܭnfB*͌K+:0ni7ֆI͊jьL,ce;VQ{f]fU3wC Bl1:r>bɓYE\5F%:k.H Otv65GD2[Π erb2tˎ@;!A'"N)={aj$ākH/3#!2 KM2H(UδuBh#34!US3%NI a!0 =psv}sJ:DE$8Ƀh~!3#3|[KQ+S\NfAϜnP;%`A() O#F!М`53 * b030ej^Q\1sUTY2*X)("6mDŪYoaH#Z5/7,eH*p QTR i"ѓ'CEzLEB:ClnTaS"إN܊ 咿Eofm~ >ݝ! [1SG[ӊz!uX4\4tIH Ĝ"P%*hn"H feHD@\N@P(A¡E ~(w/bޅ=~MM\M0p¡FHНx+8 87 Y 8t'@X2) >X&̕lP9v*$]yz]\>]! ~g۟:BkX@!Rp"r!|˿fv Q(hd5;V{\Fdi۶ohr6حUH󱄈Nn"`lճ<)VC]ηt@FQBhCwIPʐS9|]V]긒1+R3 ij6RS$l拝ݾA^OL;LD&1VG<4s;8pe)`pȚl J@n3`6o{ ߮CDHDehݪ P58Ki67"iJ7YHՁбKLx6ds;3#30$ff$B ,R@fs4'sC?vSS[]h&}\ Va!V տ,Y򤋃pyg`u:#xӥ<;/gS"&UkĘK$$84;~wg1mg^]M;O147.dQ0UfTҰ E N,H$8\g/׏9_OG ߣg/~ÿ_yNefLl a9b#/^6('z7:Vٵ#pJYo^/㕁B2gK@t``EJ ,"ftuB$WBw֮ gΟaV]aXth?/6{wKmկlv?y]"qf 担0ᢡ&TgW^.X:2#"(;yE)1y\E.*fn:9:0IM8BH=sڴߵr(m-NמÓvJys뷻bs!#UIK5n5 C@M/lҘNcĢgG>?WG5ߴ?ǟfacR9ӵU괊.\ŷ4dJs]NB9BxC?;lopJZ+c!*B̰DTwNQjy- W8X1KR.aK/z(M?{mC{fi?#AOSV@_"cu78b`cr "t%6hc[8 ;@gZw}ǛI/~>KAHYP˱8R 3 NHnfSF꾨JDB p}!1i5{`tڃ$XTS 9#!墇g98}rN JwO\;ÌV%G!ɣWnO6Xr_̠ ScðQGvtPI zyuX]B+Z$6JAGm5[ (|IbuB@wԏԮbIt_Pn=op=7Ztq*V1Ռ΄P1!$,Yr,äfaQH*ȌLtt0|m?ws!LE}*~vbH> sjcZ݉Бiu5QKADMZRIcFU+IkvAnZ̀)Tg^$jAzvտX5Fd4惣EEKVשOU'L\GPԨng_]eT>qvR3dN² A(՛$lA>9ppS?^Tdz~YvƒS4ij\yէSe^G|_GExGo|o mjzQ3q7l+G͢98=vS HJQ01Wf.c14c+6 fFD$a{s߼qt㩛O?:0yWw}/p[hf%["JE_=~<0&fOF>0tV>Vs.ONknb6SS%_~xq<ӧ;~XحEKsv]ܡd=6!M:M}Ã&z/o_[?-jޟ?O~lǒm웓FOM#`DCpG SwF"%`p=Omν1M:Gͻ:SCNf!e?fxyPF$pXъyqPϗy%B RV{ԴX\;X ˘Kfԛnqcg჻'я<)Kw{[}, }{~NfjI|v#TC)>hm'˖iyJ.c.a$h"cA?;۞I]VsIaV9,pPjT_AB"tI *$0 HD°$`<ZNjŪc&86DXk \_խ-؅e? c1ҮĝmfjNw_?^R:aRrt"^ÎV~?~DlyT/4ZI:nFKPر5@d!nSϭ,n~<˟ӤW :kf6pUYC6"bbyLIQUϠ  n> i B!FF,7MCۆ:K9yg~7<&OW.kkyMZ IjTU1T(+0aQcX4^RL EBb*SȤ@ :h7Fol~wWnvy>3~b2MA]f˔}a$&@Ф ݪ-)s-VrVY6\'ȑBeABw83Cu$@u0'&p0󦋸J:ekDr O\11KˋUˁ/QۚY%#%)JA\Tq8Y$ njY!$;YRI728NIsG+I31Ъa@'/l~t^F~E0"1uY-eۢcklr W^w{]G7&H} AJU+He3ÿ??w}t>yWw_G?S_|1R)::AЋE! vq6 axwZ3u0N_e>Ё0.c+@Br PxҐJqYWZܝ9PM92fK2- v(!=qDF?[^pvg ,`jUπHLNB)0 bpS-+o 064+wsQv1pw"1q2l'*M$AWufy\FDSm-a _s7W󒋩/*՛H$̲Ԍ#ջ,F$H ^9fNGh]J-ZJɚ\Tpbijj;WCUS("Ӭ+kp!!2l,m**Q1v~-B?X@t^Ԡ&d LSώ/;PɪšPn[Zlvw٧Z߽ C fخc?Iĕx s.ZA2ՋQ5@9eZ],rNwI-[t `)NäŁMvXwrW|{պ`5ʠmG_vuB'O.'OЏiLugW&ݪ6G7 Q-!![f9[v}h͉:L'>'?79ռ?y}C^l4at d֔UI+U7z)V=R!Bt؄~K[7^}2WŮ_NB7hU{WahtɁ + j)'Cf q IDAT WV9%ajV3 wR"! p QLnMC$"t11vos>j@#p! H9S ptdjx.9kuhQ o'UVLڠ B2LmCjv7zlsY($%tQXu,5 qˁ84nLu:7SvA50*댭&!}RS5S53WG,zQEDRM>OD҇aA0Զ<@Ux}f̕|Cu `^#܊!i*9w드xu~BIZ,^A\2LOR\ [ŴBml_=_srNC{f_Yi T+LE6|3w_o~{vï_/WxWz/0TzI "32G7eN(E~r Ku!21q  xor3c&dq;LnfnVmhl ML=6^ őf~D7(w35D@EM a2 R}}|W\E8ۯ`0$w:@ U[A@f^ "ڃq-6i^chλI&b9IsF82k*L5[pBc.e!H &ϰLer)\/TMjq;N)wnyصX3ۯLZ+9DaBB`B"k'Wo %+:h(XDHL48IQb al9O n}w"}ѩg2y!ꝫ)0F%Hӈ0,Z|q;W_>Ӫ@it75ZO*zSEW$ǝk.Z#QJ-00li!hFE Z1DUZ9{hCN*1(5 HDT}"fͻ\c~PNr>zE@='% h>i2U+˚l> q0d#g堓+==w2( j^NT刵6(Y" ] Oj->l +ra֧st{%0_Mtyi})K؄d6Xh7E.M IjσcyyHiRKVpC炰n"u҄nFFFQ0\Գi(SV3(ŐQB]j Z̲Յt?@o?mw?Xvt9 lZXΑNVa ,\T9)ABw#bwzi, 319i mdcB&N t iWQyTxՅEw'K%SMC!::fbaS@`."M!P0Z~}gS0fc+3c{"#BSqͥ8EpjEWM|b'x-9.?~(ԯQ9.4LQa3Z^PPx'MZ]>)Kf>zJ#0ͭ"ƶm4N^f~$Fs>!\?hD*@YT粀5C2Im'+jLDM^o;@͋C)^"Pg_{/BS M`z= :פt#34kY *Bfm_yWoXOG\Ji9p"1I:ȵu;ªa"S#DFSi4풛1b2IC`ZkGܧ8Pl$y8 ,5CiӤ8LIN,ReHnw+VGXAx]oWdJbW^yv3IunuZ-^BHBZQŶ 8wf断~]MJM$r ^8 桄sh̄\=4n&0'4n2& fF/~g|e>ҰQ?>?@>n| bMWrh!;c 6Ss /ߟwۏ|/}TB{]A{2ɣvh*Zׇj%pk[.6 6"p`f ܪ$x¼ܜfh.0sEuZlR?=|`P3a1ppm=TXhfJISaK|mRq d F2咊9;"6#Shfɂ]$I`;0ꠝC&!5050!c4ЭZ-vr LAR& p'G:9U$FsC3rUp"^&wNH d& ZBb.Y9G wXEڏF Pr,O $0D3G`*23Bq{j923p"t"[u `DBT<0[+N[ vЧS&+ܫ'fbNCS  dW\ Y˻qy܀b,*xxxwX盥gЛ-z gcȏ#'ã>O}<Ԅ*8k1+ 0B Oe| Ot>Wp՘^/r%rYi+^GZJCD{xy=~,72f@5F@sR(iz4 ybSw sFb]3Ip僝?:KʋM|VKѢnjsSL,XwMHnFpq(21ZvhL j*Lj0H0Jɴ-n~ww}_}TP"pibNN B@JɥQADjp. 8~7b "QET8 *ʹ(4&0oPp6B2܃9g QW+R BrKU9oH%RzQ!8YS͵tzrk: fBu uwY!'ꄎ{()i&b][>kw9⪵V3 +9N'?׿m( SlEK,$BԌhO`T +"Y)*G/bkBnM&n(o?Zy=,]g?-S65@TDv|00  b@(hnn-X8/˯]h?!ǛWg>K/[[}Jk1s G0pn>qEӘȭX4l#Apo:8]5M-'ۜ i,ffB6gUq`_][mlK1J'e*g9":::vyGݲ%S\ KR];V-/_;bjYk?9!1#)CQfD&`#h!枊 Ӻ%rPwF&W;Bt v@$f؅!"A\}7A8011jUqŬ.:3VjƕចS] j0#Eh"yVg˔B}tE3pppt?G@,tв wLK͉IU 97H0NZb/:!ĆW (4ǩb%k*39Ճ ȡS9kqRNZMF *# }ڐv댤 9|@m^)>Bh9 ϲwl RUb"$-%n%Nb8nK)/ŏ5˲5CZ!Y(jЪ8@ (;5e0QIe\,chD%7N-C67y=LDt"Zwr$FAۇ|^h^kٽBh]݊UkLԩ\'9B07+gl=g#Z5ml.ISgj{[Sns3wC:w6g?_{^ 1ĪAU _9(tW(ۋ_̏m7ߴ m~,:`4\-gN5\]~N[!YinyK@UѓCLp3Zź%g1;1@rՏ[ڎi޾?7xzbH0r{j<$3 90Ѫ msr7Ow^X|>M6ypj 1Lي9!20HMq̪vv :37]D,}KBawt[V*R+ EY͞yg\٨ٹMl©m_վrtC.shhxB "wTsS>n~"AMVR] B@"0]˷7 *lO7vhsfs5ܪnGX+hXDq4<~";rՄ]#^F K]@FHY5ͱaۀ nZel|9x:M=³TU  Tɛj\ !PUIesgiٲPytNks0vbABdUu 4u5۫%}SY5yu@^}? -?)sd3/$C#Bg.>i173nU:DHHr{Chn٭06NZ]؏'ԙ*JJ%Or]T@fM0pBWu5Sj4G~@PszG gK7}~qRb9U8n~"BF"fOϮnV2K.YE^P3H^̌*l 9!vݿz>lM$,~7/ox{~_ҧbnԼqUGhn$B( ^hkww˶/H?RoCk.jJdYt_/66nTݺ9] =\Z۷ ;+'gUa9۰{i%,B\ߍ8( :YsQd# Do{X0IDitٝt}]/w's%02;p0vp}q҃h10)c杗; wfGœp`U;lG3o ,X5gA`D 32:5V򬈸ff+%!2ܲ Lj0\-il#RQ}t/No>i~Bbb.]gբ^CV@]M͋d IDATg`&ź[i2 tiT}iKd K\W8[bP j_'nVikv#j/YXڤ0) ȩ]ÔJ2oh槫|whVԉjZ ; AwPa.B.@'\]_blKy3n^s7TB^ytd*y %zRQB'} BCi"WpmG;y޶cP#\"Oۡ)]UwOU)yC¦漱vB=ak{kg7~l_iKX⣯i>ω Bh慓i%rd} "İ pxgns\vP 4tunw FS+îH 7wvB"9e3 3 ɋUf6}Z&kFd+6vم&)!-E=mlOvb5Үjx j."tO F]֭WU ^* ImjE(A318 hd01Ӕua!WI&7_G,&"&`U"`@a Q6BDvESCɄOlhQ;nTQ'B&DH؉DmV5Ŗ' DYAHBT,"”O|iͯE;V]t!?o`wZ\)eS.b9;6a3*'^}qgadrTl80̄d6j[Wy>_VMͅiab|EY]4͋b-%&7KD_<6rS[զ>;42{Vr!"b fԦCpuͳ#ʓ;JN!HJWnKo.{~V=9r`Tq0  Bw_`SQ=wDMàWxϿS鳕緸H].k4+QfFBTt|m۟n5Pn{=>Awsv4?9E$HL,LMa~?ky5da%DY>؄e8]I~; yJ'cI1u\vaD`l"Iוڰ=0 EƉh5>w6Tw0U߫WB.7N:r-5˅N}i75w-nIiPbك6q_je؆Xih5r;n煅(zdD 2.?~vg?t2uEC(Hj}ߛ/wsGg?ǁ S!UVIe"DR5 0_WoM]ڵ_܄\o_mn?okg?ꥳ(pnM-'5 RϊHegN7s43fh.۾8ujokDn 7+ fԜ*0: )iln8I#M7@hYgKdofVUS'uΊSXT7>giWOG㷆-bJ:<"zh#"z9Ѕ2Z!":piciB'F@X́^ F80 ɜDw 3>ZWDz.0厄ה"]? ;"f`S0f5ww1̐ED5# 2a !pbt'.Q>}~sh{7iӖ޾0uY 7zl ,ލ|oN\pQP]j*V2ת>xGsϤ#@h}~CkWӢ,4e{\o24]nlĭS2򰟺ݴsKy*V 䡡4 et 12E2RUjj]Bٗ@! ލsԏsmg33!r:DVϽ*Pf8ѲM'OGY-1 ‹"[O{O[瞧Zs4LwWg/vMHڒi݊/ݱddyM{N3ݡNxV ak/3HX (Ѳ-y8̜Zv%rdvqubs.B-AQSQ+tͽ{uWm)鰟nZ 8\6+խ,׃͏AZui6KtvLXRpu,jxrYU #EP!)0 y9 ,B10B`ZBpRΗʗIlʦE psDm b4'E MXżC (Ef Gn~`}bSo^턀!r4] mA$pUu$3C6/:ln& i ȳ9g8;kxC`7t)QTu}j)6͢=a;01!Oaapעta2AARlk~|g_$ !a17GgFPX ;\X4x֦_n{B~\ߡ"{18[l˨h1_ -Z9{dy(:n,y͏k9Ec~<{9s\ AU>5NDsmaCk ri_hyO6@D&>2; 2Wo3o~^53Cذ7i?lU @OyrA?V?{o?9 Jj*M`qQH0ZʵAr@)hf)\NAl"b)R4"Qv'C`j"}{L@Eq/I8"v^{oG= %ZwDjPIBޞ+Zfr8 7r6Ro<EHIjo?;/ o_OhW uL0K@k( DF)vI'i6y QWwޕeVug2#t?ՇOo[AN9N%%bȴMc⸾!;/^NW] Ŝp}Ԭ"1/]<:`7cB+`DtpA@[Z\I&PVB Q^M.yڟ~\͛.΅׽ÊW>?z>3T_"9AµUYAMC͒eWfVw[Uu+؋ݓmyn+53"+ W/rS<-FEXkptRZ/H>5%+T 0)2Hع qVQ% FIPhQa#k vIdBBG-:l{ `RswsK@tӇ5ƴ9n͝6TDV-\=G/ng?dk60utmU\:3!@Vϯlj(01ᲡeSj8/ Js>cq5+f -3R3B`FTs_zW?ُv;ŵAGP0!cЈHTӢ"3ЍH+% ``C`Bc`D)ٔr_o=W@T_/Gb&a naݧOaO)@ 41Po.WZgLȌO{ՇƛmUacL%>1m[F Mt^m:"Zi؄vۡˤeP(YMPsUzAz1W"L<۩y?"1[iG}|R7;Zʹx/_տ2=7ߣ쓛ڧ?x$~]c8 .O9Mu1 ~T'ˇrFO/4 )[~]9Q1:5aҒMOtR͌K6Ih6f0X񒊫I}AĤI]jV͑ZlHR ~#%pwvOvX)˝Vݦ3484kO˃ƣ7;?|xZ^ke^@V-檵V>hcU'"#aM!b ]P 0*;Yʐ*hF^LE͢&e𪓓U8VHm+;}I j?6:cK5J3Ԁ% Dx"Čm 4j1e'4LSv3n3 .C6TDGl׷V|šjP8ԧͰC٨ H&Kهr ȕ'q%9h1Ulsҏ~ɿ}N_=|B>t]ӭ98Db!S3ՒaON(4uˑ$f#QEogX2n6mtH -DjGA!b)Y6yE3!"0 bV70a?n09R9eQk[ r.!0B">w1WxHswY{c+o~yw/˳\Sh]/7a7TLղ\4o޻YY-9 h%b \F-V1~̱$MS"a-.KR\?)UwC#H]@Z_ԉ12 pޞtfs#WWYގwQPukWg sK&eٴ7_N6:yάufNz/nwTб ˳P#VAr*I i nb!7cdr{: !75u"Zt褋V/Aq3'[_ۉUǫFD Mc Ds 3L3aR>DB%@yK+A(3NJx#jضa>KH4{iVK>eKS.z* רF0sMf0 &w;xॢj3݇'᷿zѴ> F/ j Zi֋շag⣪EmYvD@AvOl0RH7KwqYi`MiH@! yЮ#4űen%WH92ϝdlc!y?L xsL@u Эڿ_<|?#5"`ذ,2&DpFB 'w>LV+[&ajOVB5^|؊'1(f&lZ 0J 9x"qn eC:ɞet[Y#%l|dLݡQ.`چUhmGn"V[SR<C,YӐ>oa7Iݐɉ cqW))*-Fiʅc{0w7kV@},?sӯeA=/x{d9}xAr2i M\{'yʇP["xz]5LpdU6MmaiaYY4];qn~VR 1aI b%\@S@>LQfG⬎@k_!_AD&ޗby/??Ҿϼ黗RTUsGp!`¶W/ ۷r_.B\聀 6o|mb^Xbw}/lZpI-ԏIɸjtZN.VEYg:"͟KT@c^Ђ gώ@mM,i$.BsLvđ5 ˶2Q-i4d S22O*BU{_-+W[B#<7 $pĀ, ͺ_0{:Gʝf*4f39!\aY1hg[fJd@!{r<Qal" Tʃe LYbbRdH$DAؐ(jT%<2E54|b.L1p$LLh^^i2'ԙd.1!D)#ɬbY}tSZDFE] BV+R.S*a86!4Ϩ^%zy(: s/;1fMcP"0cEU%Hĥ1ݺdخS(T9}KptbJF'.tM{fٜ? #2 BݗU^RrrwݭbhfӘ'e ףe!:W"3ךS)k)πc榊Eջ.B>]yX_8]OEˑJm@zq>x~}םy? _rD б" Hui !(M>&{:OjWʲuW?_rل Н,^7fF0]E>&x.u]95P_ߑHhuRz.Z>=r9S>g@i1aMlBюZY4Ĵ/:_&bUeܟe/XEjpg&@l%0Ry*plDVn1).? s⸛(0qfi{Ddc'k7[Q̡:+}DTMwj 082R*~v!edfPɠP5ssW~.%f$n KD(abHNH0ןp؄ MmElеDFY`#4;+, }9vOiR5J%Is@4G5(j)iOS6fL,XH5V^rV/-9ӆsWɕ^|,7)MjJQoO0iQf-aiJHp$TsdR>_t>*9&wheVcЊCyXki]Rs"I,$pxZ@a8$ CNK's=hP!—??sKw~,:"wHD L,` gMEhPf,%~?/ͫ~/|jv;4V@$>o.RsO4Y9#&'i9y4e<å,]MK A@ON3m@7#Z,.w,@SetP7X0p^%tq!W'˸tI;zh`ͩ`1Yeu?=\}}i6C"xtv8LfƦ'O߾7kn{Oa{K2+PFhp 2fX̬2 0!dG X!"HXԼՈ$!U1HhäY p\<x .LuƪW{-@FLƁ8 3R6%f:.^-Xjb(ϛ`D"lJV u^7&Υc]l9&-IeFLTP&@q;bNlr1CuRsJma7uw5PH-Ѯ޶\x}k'E!pʚi)9ø:_nm.߹ 㛜KLL,UAʤu'$F$S$@UpzZp;s`ji(20#>>F(EE c"a(TL5n;A*qW4|YMzkG\vk5`JM yϿ߇N~O3~Id h'`w@h<}yVu' y"~7~[Ͻ~Xߩw>xq@t"ܧG)DNC`iL$^yx^n6M:lvhϚA{_]k|qZPqb!nKECLnbA"$@ qbEM>UuUÜs|U`kj_Ti9<mF~ QLnUDPHQaB"$V  1!/NEd / Ȁ<5 ua\Y4+JcHqpG!fI=D0$&6ӓǧwW7]Z+*t0[6EY bvn Ot;J|g1>l J)58ŀfEL <0ԁh 9x2jH=!b2yb^ATe g’e3+#Dsg ?ȓ "y4#Ęp̈))IcAL vDI3XyccD0򎝳h61r@ETLLTEU LmO(&/o_^hxx 0FTqC!f~\=QEE)vwo]_8#&$dDH݆.HLh*b$BU#sv2æ75 }bG\dػBR8$IVVQDCpgWtČ>dS,%?R?.Ju1/dTD1Տma_[w>?~ϼ#HUAT *)A#(LU`VIGfol O>fў}9!3$ } 1@[;ںr}%fBAGQ?X4W `.70 LL+\Òq&m 0  @QŚUkg 6yFq3x 2~l$|h얘a8l@#S 6˦ 7\Ŭrj{;үNڲ &1]7WCvErv#f2ey曵a|5cF B1HbQ,,U5J a# &)( QCR8Q;1YZ:)-EmbOq5B4S<&" 6O2mBMDdKfG "ԒY؎&Rj@R}%j*HRW#"PEMMU-%͌(+\԰^<#bt(}F Z6ŘC0]U=!I#0 >qq6(`x= Cb *f9ݦfIȿ`ۙXb9/Lo$~ ɌTS8:U3<f0GDp3>:|?nILi ?/%sQr~??rg?O2y?{&"Qp,㺌'O/ҐЬYUEZ&!)DWRD`J60 r"i` )y*rGw+2tw/+;j7z}*g/ @ɶpbu1/AE]uٴdC `qY*Q:z((6!s@H9U&G545 t#d[2U]AbQB5jZQ Qr194$s; ObI4A,E$Ø":"$sŤ!HUa_z)ØĜ+;CMbRĤj VtvWujbSYz檦h1U!Z6~=~~8{~ڴ53 ^+q݋[3[^ f7{ha{s]U٢B)._-C_~@&_p&O)o:3(j".Nc*O>M瀄ǞÄT9?|gøDm4MeK}|~I9~RPE.UAfdFQpںyrɢE~Q޹ CoVz՞pќ2, $@jDljԮrw,@IcLB0j4`dF3LфKMcyZD6H]|| ;~#Oh5 ^Wլ]- I@Uf#iMeSfS1fUyڽw:m (M[7f֮ft1-eٔ(~?fBUȤInP~sEdF"T$̹"7sl cA4FCD%4!Qe KGv7F̝5 9ۅN&ɦM" Qf4J&($S(bqJ QA@#YAL(Z 1FI ,M[>!`x4=1j&&Q13@%&. aMQCF_1c`Aś/800f.TR, BHd]i"x?w}Η"Z6e a9̖ 2Qzunv{t:{J"`fbȘU MUi~#ɥ&)Cie/;ݝJ ~QWݜ}񑅴;RH crY9\3H!wawoWÿKgϙwo|4oM,8_LAm޸'!(#_XyTk͠l!w߿8±? HZ.ٕ:A$4Qxkf)>8V[WULĵX]L]ʛ!0P3 -h \ل/zYs2[- #޿L$*"`>YaYUS Qy\W>( `GTɌe'0$'U[x1JL)?&9N Y驕@#;IO&Q5(C(FG q4Y"k>llIfF;2d-=$Q<jdBLf̮h(r:!Sr%ǽU5gf4Ljm 8 11Q|E۫3Eb¢?kԞf^K2DL `jf"ITc=Yr #CUSL]wϖFTۋ& Ɣ gz4*7t ?ef+ b/1]J4N fǖ0Oadxf˧_pth zKDӯ<]>ZQbJ9o b iݦ<̱wæk|]Zz,G#3n;ǢEWLiQ`N(j+‡xqhYV1CJ%i2.iYgKs#Wy5x<$c-Mf !оǿ7_Sd!9~7/w ̒*P[s@Sajfd:3gu—7>>ַѐX"C%#DZs낎]PsV%JˮvnMӋ[ PQNQWg7ڥDKmu ӭ1FS]796*"UAuw=LoLCﻢz^0#gU>զ Xѐ7zp\L)j j^rPԐʏq1#!9F$RbgrtrVqr_32$a{7[=weɎ9;IjhJkj8YWrL&mJiJ5q<ʖY˿Y>@ َ{* o %Y%5?/T!eOf?XHSb(I ,<9t!7YJ1OY~5%KI>vC߇qqJdF @2[d44& )Ky [?=9킊5,9òÅp؏CESԕs\8~ەMAǪd@1C{uwW}CDˋ󯼳8[ҧ!1MU` qLi840tC;/Ϟ,f'&$b+tawnJ_{>UըVXR^Qڰ`f>=O AcHze,?3eOU { ³Kܞ|3a죙(TP9/d:')dwU4^~O?ݳx-o|UêFĠZGd`B=?-yݐlzAs@$L hG"Se$-*n1?ڌw~;q-7SCC&t(<@6P0c#24S8훝ֳh =Ҥqy]4f jsd}o_m7syX[̟~i8e"]7EՔڦn 8wP8pʂO':ЍLS2;./n1nC*]sm88PBlԢUW%c%ɧk1Q,DIj@TSR̜g!vsŃ 04~ n9Vߧꪜ׈xz>;{rJgvbx{@N􋩩J&Q\y~֗5Iwg|yR3 t9;(1t}i.m"o IDAT,]?tayִ&Id1&cȨ[Pno6٬1->1LdTuc R6 M E KD 5̾\L2ˆpV'5E Bh~zh~]ɡdp <2B) c˿gޟ_?EU:Wx1#VbgrUF@ɇo{R$GT z[ƌhJ\{ߎHXmwoO۲-T'#skjn+޷އ/v ۇB&$X @bI Y,.a *2`9}|Feͺ:R:Wౚ6oa'|hIΒ1RqmR@|NfmMj6OͲ-ʢ(tY\Qo_nҘ/{Gn_~#@@N3 R~׿ rX`Ae?! 2%I$Ѝ8;if뺚#f*%m3ltnTmav 0m @ F]w۷a\\ґ&.I8@Or \pQ-/?-[z~Z[?~{ɯfJ&jj/].߿i G\Γ2k8_򧬓OW{Y H@ήA|^P;}Scۦd٢h1iP$2R6k{5?@E4})̐ eDG ccc=/r>wdU ư=3|aN@(bHE۔lL>FCKD JD*Nъ;f_ppZ8W uBQg3YP\K9iC4+f'椲!#".o[UaG!C >Qj\Kavr,5ϛeK8mws Ę[ѹ0A\[WF+4gn]0vAc U…V'nׁ;Tp=S9 dh`v|{SHnB2dDH)R5`X 1"b|aZkTr935"XəU%rw>>@9k _䑒,Ζcż]FXnh}ޅzhd,%۾{S*jyl˦x?sf AĈrveLC7њu{Y1:`HI'*Mw``Do7$9y|;^M2N:dfFPlOlvǎ_8r~``(A"19l=04U^6~Uc|V'w^Y3܋` q VKnԙaݯ+zXe缣W1%,<7%2j Z}PE1#bEț3{td͟U{`ǵoǢ'spiYT EInt!Y۪(!8UAF3z"ϤvϦ$ I^4)n%.Y@50vx=:7Z:}aX=`I cU[abLU ;##@twss5@4&WkdLQ8`F ;>S?vqm';yc=`x#fo٭jY4S0vp~^׭S vtj&n7#8ҖA.ˉ:Ma&蔹)itlc5U-6$05fO":9]Y:|X@D&Щ$0îVWIٿWw;-S ,CDvrx8߼5sM#!$%4Uevڴ'i;5~a;GNVێ]X\^ . A%c}7.;n&ı iqH FǂSpst˳`}ʏ??71M[fvAO.۰;C.' WrNgdmd U'_Ϗ1S@#B&@΋74 4`܂fQ$PQfE2 !ThIU. 66ALaqں8іm[ E CRJPX. Q(bcN"o\(>X7I kOgͪeporq39HA6X.q#~g7d OU|<މ°͐ƨbu]McI!|:faa=T0nIŽ3vu٢5QŗY69vg;zޭ/V.UXwf qs{0qc0J%FK &dF?9W|me|UD_)6w) y_xMV;XG$j`TqcQsZixCI#FٗKg4d4$5֗KHL/e=YK5qCv7qţ/Oլ2FBi͒[ӻ9~RГGˋ֋Z3o4l ?2,%mW-x0}>MvDf)ÁQ س,{7#_9y }gG' Զ/nO!ZS7D5dV mBOT+ݸd??ں~tO+"ey~f 1p~H{C~Gj9/YY{$o2Znwbva&fX!j]T2nߌ`PKA}!LGOq ND7mQy2$`-@H1!#b,H6Hib\F60S щj:O& M! vC0r(Gx A!h+ 7jѻD(:!8 9fp^.WG UYUyf/Jj5Zvrfգ|+ri>SICvv( zmr},'7/e)ٖ|h""I`*BMz*[~1v!t4]5\T4i]0TդB8uwa\oS 'U- !|䬙yN t`%- 弼Dt H]1gUI`45t2Vu^;'_ ϋ{\~uKwso!7oUz9{3_HdO`I =={ bT>cUy_S$,qԟw?zӭfnH ja xH+h"JM)3`0lr{q]fTUKoIY(HNz^fQrCP]k83g-̛z @ tx4Q[3U\r^mUnNc>=ME1QޑdvZ1N2tb$~sB25)F@E?~wc,]ACZg'"MٔB*"v1jI@|)Si)I,uKOTݷ;\cROHp 5 !MMըඞ'$Sqc> cC7P9klF#5_6r)$MZTu9|+Q¡ބKEa_bY\ɴBA0 覆:QtVC42ҍ]paqztmxjwI Sbܙ*h!YJE$HBߍܮڋ'*@4lل~7zYvW_OVT4 kv2eX'7o8Wesl5[7\:ctˁ NwP"`br}G6/.m E@jw|Xj$W0H=+BLQ}p[@lL٤:)J!G:nqȊ A{;|rTƳ /oXð_jv@LTmeŒ&V%n;7گ|>j'&,@jk#$Bb!qȦ $S1;à&1N,3 XGOOim=YqAG;y|wfs'h8GCd|=&0u1X<"`?4CA`ی||0;' H&ٽ6:ң I)ow8&jc gb[]9+@]8kcIM4sfR0 SaFczd70X?^>~tn4X:EUܿ;WUY'@`B$fz~~vIБф6UD# Hy R\_߻׻MxNVmw~}Gmׅd|2!{q @< ղt%)Yj./ꂈ _s+yR1Q1 ɭ S)G&ӔS0˵`e"ڏ():bVHqtIw]=Ο3UAF9oB%a[n$1n7WͮWueиyABO.ldĪJ"*( o݃/0U U߆Q'iϜi_nj3kԧf5+? Q!uym }nkf؅>_ϼ?2_y&Se_͂=gGR ' S{H"RQbI9/ggݛGi2z])d1H&;7eьKc *70'Gnw6v}vCRi Bq 5n6WvhzOWv@ih=%14DļA@Dt+wakp,:0<:d/|')Zf&B~@U]%4Ԕ+*r77-[GDvmF 4 '$mbFU6ɶt O,T f)XleS4("ty"fD~qĝcsdV쨘i|ˁ˪u!bUMQ) ;OqQ#a]EcR5̬3D LӔaɢfPAjʞ];!y֗5(82 g`ΪC瞮/.oOx79з Ę̧lZ=W|J1D"l™ѷ!8$SJ74kyz=oe4雰 k4g&/d>-e<8sbTj}|((b9g\oO@"YbN19-K>d@`Ď 19=M hJΘUهk"VPj)R񴸾%&CB5ȴ״^JRMl/^4$"Ix{>^sDrӼ .i>)tQBYf2*,ix5+^=l`|YV% HUQ5yS^[7(@A!F "Ŕ>?K$q.w)!!:8~ ?~NNPsSӜ  9a|/uc瓂$ z}?\1;_|96 0\J$jHH'w1Ga##`BS0I"KBG޳27`>_B@*MY[BV*WvLRÈŜ:V_z.$M'Ѭ&e>η&^)EMB\'M{ 49Q)>!jec2O\3ckߣf9jb͎!D8j~rTRU hf}H 'F.v}FJD%[DŊvewc$y>:4]`fUػoV~{=nub%4-4yӔ%ϧ4 28 'Jr M)$f} 2Ʀ :Kn5YT%/՟?G?7>1-IV?8"nfV]}|7r?O~SC7۶k@L(1>pBC04ëMm۴7gn50@@-`Qwc{/>\ppsR) 9lRY)bpTc9S-X>oͨ"*j^viͦo?~E||#̕ ;\c)2BS=;O jJR6B 1ԗw޿.zD,Z_ra+\ZR}e,&f$&Z<+[91"h<& u/޻w6(N19aîqe}6Tdb<#9bF$B&$b׺e:LKP$fR^>ȭˋjIŘ&HC8/{ +Ycu-h*IلngK{ƙ9!o\K E+'"039I۹jwbGi8$48'fz-a11Y,-TU\3Q!eW) .U2D5HY ĸ1tfۮB!N$&R]p&jh"y'sنR,M ]5j{~Ʒݺ_dfαso5+:&GO@iѦ/S:kUDsLq/^1]p]ti68%%%HrRbrL~ oNFx q >r\E>`{:'m$IL lxsLWkpz>UTQ ]QC)8f$`w*G?s;o׾{͎Th5{_lW>S-ۍ3Bb*C5]Pvڛ!k4ED a_$2@<{ 'jV- 9&&.J=@%y6m,ac `HD%S5:vm1id\:398/.p/I{ @9p 9 ;enÁM@1ΧXp'!0cUaC`BzyKNg∂f,z&Η& ; кն|bsۮi3/i:ǻa:q%NqcZݿz8ݝ8|)9I֢bECt!t᭭JkK Pms}M_g1t=ΛzyjNi8ƔRqjHyKQylqJfڭjH/-e:LEEdZLbOpwgiyx߿|__;W?s_p9l(cT4HmfTbvn{~4LyeLǻ 99Oݪic*$$`So_9gsw~}vrS  71/**mS \}[ &ݿ|H$$X |6xN*uI)+1@LdEN?\ ^(~(!ZT}}Hm?nQ9 jVI2j<% ̎QBY=LTH_~</bjJcɆcQw4ϫU+f%ePI_|s̓"y!?wCN [= py5r:VO`Q#p*wͺs+)<`C 4@+Ig7KJ/1gadGNZt' L˃̋u@D-S m@3MsiI.ZB`lb"~1YyVYyV~j|륒*qȑfd*6gl^Pʹ=drRI)/9G1%%_9,(g0/nUcMƱ+g|_$!P]ñq1||ytv-β5K^AQ-?2̲JT@sC崬  L7Oq"5N`0M-VgV1]N2eXU\W+=U^"{G6fխ}ќx:Nab\㼧m]n[ϜR,5ɐ3/8ps0D""}h:߬haUX[QYD a>rg;U)kGS.Feӯ`~M~ 6Yo) 2)dkV:::& p9ƷW"3o_f =m~oOU?p8'=b Y.JN.}’E lW/Z߸|f# ֻdhr?p;-8&UU.ph؂sI< bjj4SBInګM1 yy(@FqM4<>W]!Cc&uX=omۧu,,`5*ZbLLj .Z 'N*sV$ƅU Ĉ1IJX񢃞n,gٿ:H ܴ @A $"#ʜNim[稦5Sk'uN4fhݵDq ɱf]E*?{!joγ%眦TVhƭkVMd#wh9]5 "3\C?@`%sMyf?߆vJ51MEҒO|\vwݮ-Sۿ#ɺZ9 WE+ ain6yZ#*Zjv ID5|RL˔z9Ns֜"qJ)($aC8vDΑ\.0j,r#0s˾JT* jI9E˒4%ƶ 2sW\#@#EsRN0<7>tmjgUԬ9 U%Oq,RtDh"S̘M EVUZ~ UhiyvFO 1cAKI~nκ$6?4'!;Vo<' V 9"3Mbq$lȭD!bn-7|1=%o@Zg_,fhMKL3L*3儦9 "uQۢR?OW.f1L}a]p9=!Q̤5*) 7nհwg9 cN6o=J"º+gNUvQfΉ֌M,2f%T(*7ȍϳH6W2~ ͌ ":q;Dd&2>Ŕrӄk% q Dںίs, )'Ik`Y,+BLK<{ɔ-ƚ`FX!.Vx9-x<5P07a}Ѯ/o=a&O5X T0g()`9~lWMYR2@aZsn﮶W/V+xMSR6wO_{u߭mo;B$USќd>-Ӝs)&I䂫VSG.qѼ̧yxNwT77']?z}ٷ/fZFqD+F.: _<~vMuz Ӝ>?̇[7~kHJH<|.fHbrꑨT NTm?;9+l `dQ&s~wH =rU~ Nn)n/듞B0/αS@&̅ ,B^$UE0 @32K7tތ7Sh_ϢIr޻=x6}3sy.zQO"yy =q0JF3 =vd:Zy&IJ1!jUQqUPeEB8>,S iu_gT 9hjͮT?hNjaɢ4mL19%`Um033wg˴ "_qyITMG*=@J_X%q\T4٪1KΙT5BVrx?4m#Y%eMux$ծ{O.f98,?s:>jZdhMmY]]v}l9')j@,Vg&7f{v"nTg̈́&*CC3.5_ڜiiɻu1gM弈U0d zWOv1gA(*[m`>ZnTɕ6Lmn}jFyv|i8U]ZR|?/i%#oV<%P +[Y; $ȔÒbf">'wokUqusYG)uQt@;4|\⒛][_D2x?ų J~inB9PC.&r")JIdB IDATɋER8kfԣz$K_YϓYn8|ZqQ!h!1H%&tqiBe>cYqMh*%(8߸UESL.!Hy8]h<9`Yv,)G!ekZȄ5l9X\Hd=f)͒7zK3['oUK9),sN)f*N)>ѐOejLF$˹"ҩFg:Lܬ |F}X 6g̮6ֳg"@&Q6KNs^ƥh:派X'Y4 LU9ͧy>Ni01qY)[K0΀8E3%8|xcvcӹ9sr8:攖)Χeyazbsm'뫾BANh ]Kєg)Λq;!joV|pٮfQ5I1Q%KY)#Mf* bm%lT+S \il+v"AЅ81(U׬ZySEa/U{Ƨ1KҼ$׸bLOy嗮\ t\"HjPMRy9_i]=ytLkwӨ9jhf:o (Y2gʎرE$2ZМ U.juv>>N;iYg\kO,B0ydILJ]ܸ2dǮqim67uڭuQ00hWf[iNqS;umMh֡ok) @3$TuI}zM,Z(-QW<5>]Eo~\9dƌu?~oWq Ȧbmò&6재;v!1eM٪-ʟG`ELȞU "7&uEMd2Z!|{&< QhwC_eR0@!#,cUMXZӞ]L\g2Oe]hZd:/1I.Ty Ҕ)l Etz}sz )( 9}~ӂ. GWFXj"uycGBbEo۫LJ %0!=`r^dwyqŇhZ%=ߜ)J7 @n8 L ( ,2:Y7ՙa3U`>;.Ǟٳ=1y&"$pD \WyXӢ"wgk(-ԲDB$ye/i9EdbE~'"f&(Iy$l6W|} Eg_56gLEa>M]ZWD'ն)W64~6zZ;=P5AFЕ4(bDتߕ)dMRB+[9ՐbW5%ҔTimjqi@L5lOv{?Yz"RjZ앯ѕ;~m[-3㰏9&rֵM0PWMS+0 #'_K's%@Xn{l_O{|6>fY9Oc@dFǥ2ʰUeUUm!(9uht^\wa/?_o.j{oڰdY zLLMlzw:r+ELgYZ8{TPK ku ^.‡([^y"5#I-/y:ʵY I}]\u͊TK&YK7L.pR=*"2q^rߺb0b+P]0!BO߻BB%E@ ys Ϲi|E%4Ӱs\Ƽg>=bsAߕ%@n(m+sc$Bc_2A)Nioi:0O%-9y,Ӝ4'dX]tO$D@ºiڭm۬ *xO~\8tRfbQ "R+,e'9I^RŚbQiYƥ]7ĽeݴmS1@ wB3rKnȹ&Nȴ1#.d)|҇!F2 1"JYhaEňijo7;x`l ^Ǵ4~hYXu/Y?r?ۯw-2/iUf\>;%b"bX9KAƸ @AE h6"aJ?kjnvEw;ØEaR[D2Zd*h!=aS 0;jE*Qԭ,IM沟Oqxr_?._X(ĮiQC*_J}U՘ f[dmG/&KՇ7t*^؆>DwzVŒO*Q4b*ia9|wЬ.Ve Wd] h9Վc%Cʉ]S"KJ^GrJ_a+@|=CwчEGg}}\m^u&a*XӸ}m̞a,uEl≂sF WϷ*:"h#RTY꪿|oӭ.ClyIq,9#ӷYP)"O6Mz*c˾l]mn4kZ:t䙗4~4yX]}ӻiQzQYETIynօUh;me}|gy^_\xmW<,D ӒӒa2EI"Q)9Iк~װ/cД5kicng=jO.'"cs@SyfR<42A {O1s`6\4 q>Κ:o67ݴ yi鶽~>.yNӧmC5I;~:G\rB>Q5BޭVgݝ_,JPpmpGB}-ڇk9]u+t-EdE>;5lQ^d4ֹzzW <4Hy+<.:[UkCF*ffFRQD$B}&RgJT5rV"On3bF@Hˡ M BtdHA &VuծVj" eҒE~N;߭ǔ>"YS00kPH[Jrn{f FUUXA(F-nx;r(0q8dYCD\ 2Ĝ7 ;BE3=;"&*yl087#m ,0#""jsJ2S3U*cȄ&v,1Pi2)z1aDVO!I]k.||3* 3bT1+fEKfo]g$Qº)jCUl$1KJtj\@7{okۖwbι)o޻EQ($1h `@аhІ6MS!S4G  IH2Ɉ|/VSjs1hy)!C)e(:=k}!bc922ZѢJ0qlV+En~pljU$H O*JzS13?&RE%0.EX[]ް 0A`a>~'I)精\c N)'ĹPU( aK з.S mh6))v,A#\b! :iשp35"po_YY6t?[ffF @~ݽ.[ ѷ*rNY㩫kDᇜ 9܂vogn(&(j@`H7Gwonr18Ygm@T).ګ{Vɑ`!ׂ?@ Ԭ 3պ#jC>_~W~:` & iݡkЬ6L(ҞsJyJ\tI6ex鹟1s<"ٷA@BidV)p'Q:u sNi7W$gĵZm2(RXulʹQfVP5Dؾ޵gmӷr4o#ܪa` Z[}ⴢ``T?n5}"!ϻAYE43mS&߷p<oBxo 8jw0`#fNPH@ûa{-H0u]ԅƘd65,y,0Oa;ǩr|U,4ˤn݇a㰛!s@\|>XO>D:%)&66]6Mӵ?0Ra?CUӝ7ݺGNA!Rb&|piRWx&E"DQ0], ~_}5`X-IGrR}.\̫igјw{1d`DxZ)'%ZŎf?K)@!nnD#2@ O2k8TÄEȅ 3T5E%6čOVc"8r|΋%f,m6vP 5*w#$[4EӌWox2Ҝ174oshbm4w_yCq8W31 UH&jibc"__uPfQacy<ގG"S9O%@[4"YeR/c)Gǟ(L0竐.Hnp lCB0fn"Vo(W,O\&$tVS#q;h)LL!3HD,aՅqwi;o!S;0 "2~<T 7HO &Z~lc~rE?[[VSj*6nqY 9خ\METd 'bvS U  X"J\ !,PLP*BsLq#&n"1Ay7}L10*.E\Bb c_ @ԞUE@ uD CoogpK1Q\$#UDauMI YW<|y:khA)E~&<ɂ&UlGu"NYojNӱxg`I$@".c0@on~73ū' ]1*@Lن^~m/Xdӯ|z՟`8ՄcE^ [M,$D˪j'ROr-X.}]!!-={~33cOssN{[ 'j7}<GC6_n*pz Ż=.%u `e*&Ư'k$*.o&](CpA)Ff7?~0Ԑ{kĵK4õ 柧/]G3cf_2T(!21vp *SRAe;K\putyO*0y<0Y%R D ђf圮::pEA?F&tB7XD"@6N )-)=~YClWi}ٟ=_.jb np ^RsIuuz!!BXDWbLXZN @PT0<~>;U;<hs1BdP?i%Kꢏ'WWe7YL{00[h_"fUNAKl 9Ǹ{DTrSy ž,`!zs]k$E͏ׯ!ݡƚF}HSى>?BLpB؅ `/?<(!bh`Ff"6ρ],jcH\SP@ KmzuFFea7DȢV8be|?cMTUS[ˬrLA |h`*ĿLw$*HnzmWcxosy Gj^5AEUGD8{F#4ERCi G IT43Om=Bt5r0W*yAb닎̛"TQ3U 4Ʀ b]"\Kdi;QE$_OMڪ˟rRlBvn94$ v{YVG+|  B0UD{BHՙUda7Oy:÷c1K_F+Z \Eo?koY~~*Dy|}SgZج N3Bu>p~3gTW& sJMT-D] !V}Σ fwz_M!Ǚ{hE`&Lj&69 6slOܩ jbi9yB+K>]]6#uH0qʾΫt4DT8<<.V;g !z3!Rd2 ݘ)wiu1tdٴNt`6(!V'i TEMMk3_f#ńL&Ն*|9T d&;ڟd`_: Y%z|G# ^KOjY] 60s1#jM%DUDfRf-.uRjE )%DRm)~TZIzC$F0lFዥeWא !KRt8_7*v}BFs8Ov^fhf~Duw~?= MRNYU^mC6踝Dex?I#VKba;Cէ-ʠ(O'r,E0bs@L3T(`bV@ytyGFfV-| L cn> ug[=|]>X2 %B45}S*8]]udp.W&.@vי\ayi91@TDruSazȇܭ5f\ME(b}eH7<(eT09T7,M<˖ ZNiV)bEc Щ̐j,DI9ÇQ qkcDwq95٤3ĔU&'Ŷl/^gV$.tQ#^5_P Q&emU6Q,(&%{y9X7SC-zddfn\ZlK1D<y1ޚNV3<^In;o@YhjJ4oH?֩lA3?ќ%#8}vņ@U-*!q;ߴ鯼J8#ؗD(R XU BBj!C Oh31Ȕ9ѳmfY8:b7ʹYEvS^&{q{C2Af a?=PL oAUKb>,^@O4=|nwA*j9( hMa7kWINĪ8Doչ֟wf" qOy"ct(Xn?>@O50Lh 0E#Uy,*r&&1tBZl]\o(dVG#F]&AАr`(j^B0_s`.'&Md) FO>TÜ'QUU,ýQ d$&<SRxC gp7G)ƑBdHqH!,N$b:n>ZN&ayDҮnYt)pc .4|T0S jR2՘UǑ3R5+4DD@]>#dUuX>%FE1_= b ƀO "P+1Cvp Zj( BFM2ݩ,ܵM>hVd쟵]E-YnÆV͸voU "hcUyN7["/?w\xg})_bxlSJ+0K]}xH<򇑙Dj| hz-7JD@:nXp??_^&9{b6kk=7<ߢ,v,gW+N b5䈀8e}6wEP|/U2σ{D0nX/8w&Lm3p3H1#@w=]Pj`&D s-*9q?px `7d$uU*;=漅€< Dͻ@/|H3DP06nލee}6"3z\J.$K.L|@CٗM9o0_\&)~_"&/Rg%1SmLfU4Mm]Ǧ "9!*EʤRtfF8E@)*6i?qfR)`M;T y4iV4bjtv"e4Zx*#122謵 ]̊){<9P!bb"0Db>R Tf|rĎ; Sy*̡o4R4%uc ЭENs޷'U ";NՀ4]|x{KEOJ}>O kfd3S)nn0^7w)CjnC+0䋯]~CݩYY^m‡yP]nr.7~y|{ Woހl~@w9A^XC7g/eu-c9뚀nk @~$ 0iT˰=@MoDs^=>Vry.m{<|+cNM8{r$&3? !ǴX.~LIEVRCH' qL ~mTjwܥi=Q= Bt俛\*Z05k' "ʮ-}nbjĤDyŃ 1׫Kg+BjP}xFkֽMrqj1㈦h^ODf YsvՆ@IEo.zR`ٙ@\E6% G"9rx@\<& R"ہbm .OCn.c9>N[65B@ "o|H5-ETfq eP!BH:YcupeY.&LD!!b 1橸Gh7bw*>EnT)D#!1D!p8'?Sg㰛bbd7 k0\"6XE4Cǜ'lf!p )]wi݂a;J%XFontyXE'(fV10ӊi:+vDt)lܤ޾剆;%r$$mjv#e? a֟{_W~.6! A$uY(4;Ͽql7 )E}a-3@ݿoꏄ`ݾoD s!V a喙+hQak_hȺ`ZP {K ɛ S< &?Ր8Fb Ȍ܇gxlKdj.c,ox;7WnܻVYQ< 3Ė),=CN,4ށ6[pԬd͓g]H13.5#G<ZS#!3ZnKvW#3BqQcFC2%73?:3^5}"@O6``1^>aw2UD6qAs2~e5`y,)u/HcCL/'ʬP/hl"rp.S+TT=Z+m Q|[CFflZ !B>t)J&$7>#hTb*qPwu<b"%" 3@̹X-z:m@u14^A1OE|UgTL*b1'5HzTBQS0br D1;aW5fs8HDqγ2Ah86N(,>lui(SO:uw6cnש/-jȼ&\ ĵn@\[f(9R;cbwS)VR&Jbl="@BqlhZH5YVj<& N0ԛ:,B5`@,nLʁ)4ٴͺkV "*e'YsaNmR6fhp{rQwn~orPZ4+j7iOKDPdDE?TDſT5ه~0ԦYmww\>!G|}~7^ *:@Q.&+6G|On릻CnjGC)fﶵJAD4b42R%7'%jc|ʺWWnY0.{N'&|%'ɨR 8x~r/Egq,qzpq?+kb|(\L5e],c(kc\DbXT\ݤM rij51+q{6x=B=" _`^W槢d=C0@`bGU#{!CRpN"DqzW]HwoT#CrP P|7.a͎CĢH ÜS#z2xu4U*Pmq~DBr%Q޻ݾ~g|麽h*]" @Dhb2|djRj12bʺD1?#xgW1#߻VP lP9`*@EQm&l<,bޟ8Kք3$xՖ=$ j^ }4Ry(ע"]6g=,P)S9YY˳GTbۻ}r~Ny 🊭X@G&Bl "UUXMeI(S>1*=,M o(Tq^]-[ʴSُ#\HwZcTt 4ޗXrƝLK1LDlbcIc`$vx'HkFۙҌˎv7!,H{#B4Z4&G-Ψ^M D ш@s}{ G,1s mM۝K EYD( VWa{v󊓧C^ TgjVۮ-Y54FfT25ab&&,J!N1k.VHvU Y%sZnW+NT,n,s#j?BVs}c/iqsv輛,+6AA@$dT/߫4d?~w_vo$f7G2.fs**(*줨gY "^b @@ ( uw/c_9ϻo9M !޾'C~3n*ƈ\C0o:>]yX|"d<~H$@U˃riĉm*5+ A/Am(u"yB&&@֤ *yemc#]( J`{y1GTGP@*Di"low1a8k2,0&'k$]!0Ne>ן\5@K=y0,={[NpK{{Ǒh4G)@tiY(1G:&bmYCB"*Y$ wfZD;!)'*}q? Ě>6.6c92M\ vP{i}ToZQ^,Y~ @bP sqM|8UXkKK".D,Si.CY-@ !Ue""TU? 4,^k *b>ؤj#B6-, ! HUqM0A J)nb<mBub!,4U"dUiem(`y>)6())v%UkV懷;k@L?WV|Cqp/*"vZL t$SbH^¥ ٯZ5J'w/_wː>a`q>yg513 G%髆e g-gzKdMۂ 96ᴛۑ1Ip^bͦI߀F^wgDPe6bvח30D%+G_T!R`Sy!ߜk155᫨K%O 8dI2U-0VWkx|xMSfR6&Y}W5bfpNHv:cM}>y*LĄҠK/N2>DlaY9 In˃ݻcՙP-U_ jL329kUeZ*nu1]"j1Xڹb>2#'22J hbFjȸsM'u8&%"f b$a܏2IKq؍&y 24%TU<AzR%Lb@EXfiHs.@Sp&`FBT@#df OJ{CD)Q5!= l \|s3޾ُ8 ۿnZZ!恷&͇K]i9tב"꓋?T~'VQ,X?ow5HLxmF o{HE* !gB%"Ww+y_L? '9 KRv:=25x[TQ0RG+*Z>Lֽh@ )O>~EaW5\x*ì,0=1 M3 kT-Yp QQ5TF*@CAgC@' ҬMUƊDLD}p*#S@bf*!h)Mm{x/aFcJh&(\VWCu`5"@d~%L"Db0uEM2UtmQEUEĊ.n’]) L*Yc  x|"ljRTT;+qE$)t ,ZD\LMEE9^āC޻Z%y#"u{9fmMJ0h ?̀ $eBІ &LhZ$lַsSUjn9gD!f:n4NZv漌̽zX4^|K5{.Ge>漹qNIꖌ5u8yKEn\!0/bZH͖fN81/}F,$jv:V .Mt YaVm~:SsJ 5g>Ln^A)̏?~}@O_[L'7ݯ3nV5S8ØDZ aKSYP{FBA%YĦ*)?o9e\ՏލcQuSǛxSla$|.*1Enxx٤:VZm խϩK˫Jf IW Sa7a?&V&PHTȴ_jn4juFH9Sdď:-v5݂!֢­{X G[QZIVm>~u3qx5ߜ7bQSWu.E U 6}q_rY Ŀ8꧇@j~̒Z,ksUX$q[:Aܡju"g)~IZ/ )IKˬ㹌tSZ'IMu:WVyXqwK"4rNtX[lK ̗HMCV=d_P~*YMC%gQu>DH`2HFDeTհz% h0^?㫏峟}o.t,yNRT)QW!^ U#{騗(sߟ<F7obk[DO =3*\-u" U\fۦe-Dżh8)pWgw|v7fp4={{ Qf=|;%q*G A4; m݋ۿ{X뇱>L^׮?Tg/JFl}beə^ͧҠ-TsI/_26D:;jm g/>k?E?}w&kuSxz>IE ]U5}[Ucc_9q?QgCJR ̽j|Ȯ"L2i3h77b%2'8`P{.'H{ӥ$2#Gݻt||Rp,+ <{#uW3r'3ajD^/ 4ܯ2::?#o"^%q7_|]3ۿ=Gw/oE(dl@4_Hʗb"IhőaVpZW5LO;oȢww;S6IFUDƋ{IEuiz^BŒԵD4AQFaa33"snO-9 ÷.HKDp2([+g0j%npf7&oJ^#  bx{wc@jta6k&R)ch[ #-Zg>Y,:ĩj?ژ*$IN1@RxS^c;&'0disw<ܗB:biI0R4٪jθ{sc!!,ROJC)ciҥ36^Pjaɖ4^s3HB%bL (ƱQ$Y7t1{4#-^tx1F-xʃ =HHN߃jF5O<,@B<[ɔ ݽl_Nꑉaow]_Z?ٖ'4گ׿5+(5Dż.n.,LD9yao)EE;00jC`)~w,[OrXj_?߭m@(^ܚ 2hyQU~ rO.MZ'עaDy{^Z_m^c 1$ OG؉\M)*EӨ=mh 2Gx"iQLVjΖ`'dr٫--CwTe^Zawc MFz8HKrcg3S]꣩3=IUm:FU4ȻѓY$q?caEibl901*:1<35@dgb63wq1nFW .CCIW|[`p&(RbQ/v&GxvXd\Ap%ՙʱSY ]'ɽakޭRJ)QbfS|t¡! ǻ8ܭ)B8ܜV ǹA*S=?Xg}*ozx(:32K+]}OYQKfwq"Jlf,^<1(b34O]gou*c0^l 6pW%nvo~buBu)ݣBQH$NB/} :m@oWw3܋:q}u"ecąZ!6J-0@ ѯ??_"t?+XţEMq:8S뾿^]TNܴ8sJf%"+f!骄(ƂWK{<ܞovy~{#"-3\[|r'BpV2]>^o=ɫ,vZ^ .Ū'߽rOf;3Wyt|{?zG0 k壏nVC,iqUS_i:yy{6:U 9ggfK b`DLFZr0 p?ҥ ܊W$DKS"hԧ4`qW~B^fƒTM7kU_P氯zLLn$)˰Λnd:w f >F fOc~ipe0d3ܝ͍U 4mm6̫I?:[Qq|1%QtNOt #d O'?{/TL33ԔIbf7i2EZ^s9t{)R2|Hl&\C ])jj^z.JP^9ƫ:)Ety⫎ht"6U=* 6*? pQú~q@Zr'.`za^4IPLݪ\gL)di NFSK$-L&SxaBT$faĜX fo"檕rKBNFIJ˥í}ha~sH "9HMvcPJLFV"䀪7m]#i.I(MDEVNOD ļtTvD?Zu6&{xu2g*e6tCbyv&qxJ& ib)fcMX`7WϒZL<绳-Eh{ )bէ 5Vݥؖ2s兯1Ϊ)!mMxL%<ԭc0I$c-\N:X ~ȉjUbJХxqx#sIh`IҡUlCHD+Hr^Zp{fM뎘,ZK|jp\ut]_qҭRVgZg:(4D(/pTs!8r!WҞNvxwzqv7W!V'bD},<. NdW;v;~+_~z[O81-A`r5bJHTUgVi=gف2$ZQs0Ykkw|sQ#/%+V%ANe;AEPf6~s<_?4;67 uNx4; ^Z*^M=l[c'fn*IɋYeIfu ܒQ"b,RCcDYui-ܰGtr6d.eDffZܑc&4\N`]egfdU?ୄ!RPԔU[|T U4#B-ZǺϿ% %uf)Ѻ(ڪ-n "T YݛG?_g\ҥǓ1sgTrɡle*$-]U3m!\ bHdtLGLTZ4btQa,cGj‚>[Hzi\:ː( 1K*Hx-r}"6Y2W_t2R+M5(P{C|KK"9XlRuNtx3>;l"Lb]9X4󋝝7fxCZ}|LPn uG r!b\V$wt{{up9]=߽dռR"""O9s5-u~$v.#8)q=9l6ih(jSҜI(_y?|Sy3On`,&O^nmަ %ss 1^|2;KxFö_G'B3χg~;U~kHh 3B9o:t/W-?}s$1IɨDE*Hd̽9gRȇ)Zjfb9%89HCl֤ݢ BX;ſ&?-DGӝGb o+so~fŸJZGu\}Ze$a.zAnUEXժ"V:_phX"o0#,<\ '"~hᥠKbD<ʄ a0k_Z!]mq0,1qb*)GR֓ZErILhkčXh|w^X-$3m.W;@/*z,,= [](G ~޿;u;ι]"8}cS1'iR[0w6h6=2uW-Ѷs0s1¿V~ql$y\B @%Eao(gߦVT è[K-C8Jlɟ_5ɫuuMHǸ` pYc~I˭̄:z!}1 (I]Sbܪ1dY0ZN&Ѹ9ď[nT߿'z9HrW꯺ǙV=- $Dn G/*P'{X=qN.dQig()N@ZuuƬL 4s>u'!B'v'OdI-`aס?7`,{m2Y'y?I[Ha7U'ΙYt:S)5 "]\(;niV'u<~6QE$ 0Y]/08ijgfn|,nf"‰gP bȔU*HXx jo A61@[_'+312GH!MXxD֊{;7Uĭf=p Q+ AnbNZY'} ,%Zɭm,zra0FS_KG9n;4PO:%a]x'?;4d B[G f2~ZUD$qXn05L YeZe1L0 fX2b0bYFje[]D;$ G-:* zg}A1KZZA_cKY D%[J|&ao:T4K:*V =IVaw2ލ3l bEB2Q#/fF- 5-!N*f"՛'txw:}~<36ޗGxm{J F~^_jpPI9:sRjGgYjp3"Ēwto6E5 G) bLp7)U ҟZiIΪ-@`s6sbu.]9`n)c90lxRjk,]>ѻzjÂkq<:q/2^30T9Qdm`)KQ:{` S` }A/c YZ\RnH@oن/!KM_.qp5Èe_ < PI\D_˛WcVInfwZqtųЯT%<ݱVkQ:ZS)aݶE/qT(ho/=O}5 kRi>׼ [ta&(8IwzhQ¤^NOZc.ͼ6t쯿= ZueIE k1?Eed,\|)W'dcݣ]\rw Sm˨XGs~si*uO3:nߎU]򐣇Wi NwA<511[ZtBo0K"!j~"8r-En.'KPx9, ]VGs0/UkT5<\/42(d0҃I"b0rmBR"SdqsS"r2*w 䬳ICILOZxeZ$r_ /bݫ}ZMV{*L͕"bl $g+:OIV4IJjA ށsgmw7]dzy3î˹Jpbmw/aeզ `ixl朩]6̪\7wpܿV#em7ˣ34FsJ~o߽VNj$l&i>ܱz7hi?# m΀Vͽ(v):rRNDtaǻ<$x7lFM">U%i%Uj v1%Cyy/$%ta:j3ki_wSt*(K83-n.XI(@!][r '=aKfF Z/"k "4Y ڤKU*W5YEnd8|Y%3 ZJb!R#_ L<͌J"IXQZI88N,6"f+*ŢlM{9܍3dBŕ$NC8\ ˿}'wf 0yylV&_d =i|rQ'>ۤ,CIs,\T VT2#Cc/ޗcIYc67m'ԘQ fCzk8ՆZ?ο_=UwPà?JwF3p}CvzR7V$~aDcQA"HX~2i PHH`PN}eq: :]ofu,gD.*֣t jY5xe`Vpچި)eEpdNgr4Ҕv:vuP$C J]pr16rc܏e6vkQ+y,pOIұ0 [)eҔihf7n_S^݀ݫ}^IZ SѪ6i6BhkXW tA ˽pկ| q?D#4WF^m.bfԺΝZ[g7bbFY8-&Nub 1&DRN+PG36powwu??M:ixCrOۧ;IM)瘉w”tQӪ}Op6O}A,ϾysQs.Yu7Y9(u@r~u&Hݣ 2@aʾ8j/QF^P_!Q #'$Q n5jhdX+"`EYy2muU5VAixO=/x3MMy8ǻS7_js,6aOڪ^Ot$l$4O4'b~ TrskKExDPC"J_ݩJjq4' ȓ($ex֘ [,ÜLAgeA)nw)n$Qn :܋W'6F=sݭdX.5P*gc-aej#kХ4u;Mr)"Ҷwu:daRް%aWu?N,tm㾌. "*_9udr &Ȕi/DAȃYh Y?IoP^`%K-貌IW*I!s5fyk%-9Bż#4B/D"Vi6Yk!Z(U.d S8jjѨffѰ:w UU#Cn)976Ͻyy7y$3p竗׷?|;90cure>^l^W_w xm:NtaZ=g=S|YDǫ]:̏T.Jy33IDz8qjd=R 1μdtQ6(Ut?p'?{^ 4LLm:ϧnVe,e,h 7ٓ/SX@p<  kUSP9p 0܍@V/̜Zpw=LwT9!n8RDgjFT&65I҂Xa(Q-fZ758M-Qyb8QXkRbm!r '|K'Yl \{@"^MўY7F])C E bK? 7جܽɳ-Yvvd*S*$(4Q̪0 cP S `*(JY%PRe/^wӸk-kb*e2#^ܸs|uz(Ս#XܹmEULՁ.oKZsț e 0\|w% UˌXjA=\qnM"ezÙ8eea^~J@V WX'J̾dc- uJq4 JJB1@F~4xܕF+AH9Nj)nsV"ى|&R i./+u)yJ ZJ")Q-„r||\ܽ;m]']3"LyPeo 2nӥ$ag.jVU ]rp\BYĒrvo3&q;QOn%HN]ogmڼW͛~/Oę^|uJ'6xǾNid^l>TrGy`'3ҫ}MM'z-gjZ\# BT:$p],^v*tEmw78SW_.?|7Mitu攬9*qSFH1;)#ǧ<ͷ_l)dO!X<$pSVpjI%4\ (-1A樳~&ý a݆yB]6KpOŚЦU'Qð Q'-#( ֒)T]e$̃&\j̍m-07WB $'o_`bx/nm?rMˤw=#!ecbNyuw4Ø4;8nw~>XR=Oc$ yJX0:R5 1S դKH ə|6X\Fכf9@hn˟Χo3Yyc5C}ztGN\6b"thxdu󧹞Ɋ.]ٝ4s9K"ݮ''#"򃛡3iKV ܁;"'N Y؂_n縼X|Oyr4NF 1?}|iw|*f7s!.f*%(5$ثV`B`ϼ"M7#PӤm0m:mlj&)X5'5&YӏF/~ r VaD95HVp2FNLK@Nvk$=K0O\qwֹ}ڷinHz8핹O皳,ZEvwJ,12:}jՂ$ëB1R蚦0:Ɲ@oV4ζb'u??ɗǾ^>!sg_d+M M]ϽA m[]gY'rkސI)INmxL%w 66IA;gҋĉ4R-A17dxm_ôYg̒e>j&~Y_2K'ʹAyq0ÄXՁ;,|$#kL-xy$Za͗7_I'`` HZ;anɐB~y : Y#5-H*[6Y",F qS6/\NlXTKn:~z:~< /olZavgh/vowaͧ+;yhu6-uO~LCOw1iA W$\uyLq@,]x]ɏ΄m^ JOt̽DPO39tiɷOPO:ϵݐ4Ac[lU#f*oB:љ;-y\DT'8)R[R.2\ 0Lϣ.l,K<&m?f%ms7b^QuU=R$K$c,oPvr3HP&8C͸cq"v30ih\r]? ڨV٤nۑ~Qb.:}]mzK#] 9N՚=pNjmԷ7+ =ilܱ4->VL4SKZ)wBAߴDf^LA ;p颚sA-a=8),!GkV/~*B1Q&Vm&\n̎\ZSRH'Ȭ}}_ȋ;m營4׌iXMdVhh8QIW9>fm;Og7fu.^-&Evr@#O}D7֏ہ"8lfuw*ui0ܿ=SgY#@?n>ϹO0GurF1ڎή ȝYUo I,i^Jܜ 3~oOm޽ݒ4tOjpNLfnIgUWGܼrF^5n4cӧH0F ԕP#UP]eƾm3% aW,J'x&fV5Gob̪ϧr>vTb C\~}ټt:f+LakMl,DfvF@irV, 9Ak\{&uiQ> j&$,2r#HD̍j:8dw7!Qx0y1k"NA PQD]xb&՘ %Y͐*> dA 5bmwptU|x?÷Sc wUEKbEA՝0Kr݃Psډ+ƻ=yqu#Z(0sX18M$k,kG7he,Ȼ 9}jV? ksk|}`݌7Jۉ%s**KS0>1Ga<}ki>Z @̘'>t &T]}Srge,sp fuKOwYV- ,B1t<ޟHK>=?~x՛my\+ Ib$ooƇ20Ts5Yǟd܋GP˞@)xvM_"ս8mu?>CuA#Wݐt/~l6$@Og#]NMHNҔPrg35ɃGE&-%B %R7u32:#_}uZLF{`, W"EUVo /$xEOHwyQ0J-g5/O2r_8=h!#)P#N}%YEJ-VB-#EӖDK*/1L׌%:tk3IxLPm΂uISjҎn"rJY/w(D@{V_ʹ޼-u9jA%bgv73;64wn)J' OR!Şd4\f>f2L)t"j%*vl{|)[Db _lq508GZ]\Sd_m:SrLU|k"xd}qݘ~ wPdV¤d[!\^nF93|UCU/LE.؊ITK˛l @Z*m$;T?NN7ׯ%1  șLMln`{_Zu4c-q~}n[2E2' ) '%bP_;# śͺ.y~;j$d?~o{|?z=tC,&g5_t?;LA]tZ`KmwB O+tr`I]%'Fu Ă pץ7Xsf[$_@տ5Nzz\g-s] 77O& C.siB9oB2=B 3> 6`BTcij dٽڝӱP7l S*>㨳>z gV]5+\׺VnQd&< 3hs뇖 ʩ)-̜Y``a|>1g()\yfK;~snHx7k-:ɬqWp4Ƨ,|Bї R|8m}U{1z q/W˖5%j,ظEf&,uɫ,:>Mx_K:M궩Z1ܩ%^/W|Ҳ>?;ȊDWK8|5{Rg[ TZ^3 OOǻo~o6jZ6zQb)D"H챉DS8ՓNO37_]Wi'B pLJN kA&V`M&բȈ!̮܇evu7 Oջ}f,wV*Qv2n9nK'>}8~D3i':g0Mǚ6 b1ݟhI{4՜j$FĠsےa@8\ H gon|8U$w| q0/bac|:/: Rzt]w85/[E5Z`|jFAE5V| qj1D?ߝ$5Z+\i1msUñhty8Tɗ{utNG<3- ̚8>639KϻmFO6a:|{m#7N8%`t)T.E:8o)Fnn`&N81wFCu)\rֹ1ݼ#G$Ͱvrt!SUBڀvhu}A{] Kz6Sz{4Ry[B먼d3%j ޝ[}׍c|Й q-cfJ)r23~9e璯:s# z|BVeh)w׿Z}>|{ -'FhGQR%fO9NR'D}|~xy:5%I/̳Pn/xd@22bɲ>{@"J-؍\$b鮇?|/?߼ق]ם5nۼ{ɶ_nczte/!uߤ&w;1B|Rfn:Y=մKL phii$B=1 ,E5F ,\ZYۇ1UW>ԯ fB/~a0ձѼi-&Ttvi-v~u;iyEc_LT_V!]rrDnFM<'5+Р94f8ߟݮ53<璶 { /x;&s%Ĥ^vaZKWs=iRBIPf%̑UNӰ%MԪ]}~~u\mpіNDYUM0ymmr8|w)[%hM:ϖz`XBpm֊z!#eG< a$qoNroǼx]/Ll^ބԗw 'Z m |  z^K&C raIr?)Ɍ͎?=&l>{=ʬZ !-80(I*k|S-s" =eo6t=?{~{놞8\RYñ  7ˡծ ᛧ7WyW67K&1k״N^5m;PLIk"<vͳCb125ỳіg"P!JvŢ._fּgժ \W sZbe*x_gNIuY@e^2ڐ/몰8j:Ep|(n1v}B׉`6 IDATύ$`P-ѣS^kƚ?0#$9a"̪d&suTj3V4(2Uwu"p"V꒜kSKob&I=7^$2ͫnnXBx YfDn?4яk 1Qj5ݫ48]LfO]Z$FBH ̩T3K,`8S'fn(@䵂@S'.w3U3e\L$0SN+?|"tC a.7ypatYF8w4H~#8A6 ~=XTD7/0ss:ӷoy1]ptAX(M)1mr U  L$к՘CCH YX52l0 h]ώiG0%4)?<puρ%E.֢j27(Z&qKIOyADUӤ^`/PX=![* ITcfrGo!l4gMKp?>Ly3@.ҀJTS8EQC֚y$q%~8Y.N`f3ElOs|'U)ʨіY\p&|%mD\׳eq X7ׂڋu,Lf̱zUNS F;сZ@fwRγ/q}eHW É%fnZɐ"tNm>;0s ,aen-Z$9#QS}-$9S3Áb![2i`٘y ˙E.>*$ I4_9I`ẌPKZ!I$A[1b|k%}}s|8ԥ~7oa:(f]:ݏyC]Fhlh>yy,\OxC%NHnEOvÉ\RPIq :ZxtBɥ:r$ ` R4bC/bͤ6u5?u̾wX%q:?wzDZ1I .&6f..F1ѰcXXMM0>ޑm˫*W53|˨ۗ[͛fuyICFS籖\qE Im{0X F31bYp*bݫ1B;R+39LM#Rw0=wos=?Dn|ߝʶHz~ueǪWS,,f;2Oݻ[8e|BĵQN 1Bfbv9"ik#Eix2w~U^vX|j+?TRm_ t&9q#%.ZX0?ia7 YVP&?=91 B ">"wA yN A0$C<.Xj%0]!:sbZrgOnw"[r ,:,V"K&S]`C$9 ur:Y} wREHܜ=Ҭ'qgP"x%nNah&q[cR3k$|KD4lX]ռT,i^_cޭdaț]0h1=ͧيf8ݝ=|IoN÷+ٲ\ǹ /*Lռ~;|w$"4M+ ܖWc b.89{eMA Y:;_zXk޽ DDX,LָAvSyxv]rA< ^,KZ점X5(su}HhOi;&X`f I@MOTNݛJTOa *Lpq_^3$A2!2\%-&vqsi[>3hi""Ss3 IWaP!){o2IO5~sz8UKXэh5f/kLO 6z7/29:xz-r@,^]qY:+g@EmUN/ QD߇~ߊZLźs3o2-UY)^ n[ Gv0,%GŭƀSd>ٯLkڬD]d;08YXXLRݹ4Zl԰0(K\9mJЪf 0t|jH#n VUW>M5v_m6m~琭('!QܫY1Ė_~c %9xJ!@yfUyY), /nEH:Dd٬s'c0 'עp5mS'@RN) ՇX ,g *Us#_>=?{ ;=ǹ [5~nt~*syZ̍.Xq]}}Z-ֹ9:\l[V3sUS0!&XTA$({7.G5'/~?{$-|ŚLZkҜZYa4BLib*n(XYL}zcѪh802x9%uwv.IJ\NƋns'.DT|wؿKNADҢ%d I7do s);zs#MMMlah:!Q#AGJ~nX~~oW_8[wfrF@) 2VS.Rlkو:"S]8PD҈_*|?Y?3XB{>:_bhv7\y&D6g#}RYH[$fa:I+,(zAYVԒvb-a<Ts @-MC] ƉuS] =em1.-j,ұ߽dh1idv)LCSS4Q p7W1gsg6\3p!֢tƪ‘SXKD^ ,b!v8X!F,yI6"D(,f0ZCS]P:${ f;,w&NUO?~Ww^!`4ّ9`f6%N|嶿~q]gйþ7yr"So~뇿xɄsz_hAjn0p; LI`lf2w&ZU -T&(?ݯ'8˻;"|z<=|xbbPʢqĜElhk8aՔN]Jn=b죖PrxL"\pFڑ6yhz>ml:7; T7n)u/8Z Q2s!y-ABc75Ɔ0;d*)MϧooھCa$czϟn_ 7 :Wki!ۧ6w{71Q@x =ΤDxnIn6d˩8=\ꤗ#n @,b+on wV1MS<$o&R^dS{@P33I0\'7)s%:s\3H}Tz" = @$Hi|s.4_+=Qh|5'oC-?4K<Tan1,m&vagvG5G".<(OD")*:@ đY+)>k鄘ண.w5ܑE-jӲ8b]mqBA!Gj:LI͈?Wk幠UsǢsѣ~~:4cpJۼbLO)%$e1ٝR"CzA6/mWGKa]OUrC9ﻈYqZ *ns’iE^sN K1W~~jOǿEp4_>#"-,!f,e,ƙ$99yw!7%ZS{]R /-ky(0z.ym;n?nۥNϓDXԡBNpDzMܔ@YI$12%GK QRqςN1M!];|LIqY U+LX~*_/]WGt^rǪ12r+R'&?cՄPݟ$\U@VΟU>}~O/vz#A]H]0 _o%Z21#Kd+IDhݘAnF܉/R-+&ܫ'6S2g31n]wױvHC"E/X6D4^yZ|Ic]hjjޜ,N$~b9Il3iwqc hM 2xPfI97[x`WO"V-.Q^%yxrUwW@Flf-6pIlL D0p2L 3RNon99;VF6]I?i#s KIV> I'jŽjH/9o:N]=V[5)V_$L)qh% Wꋯ6?flVwLM6`ZSZܑ;^S9 !`^^%"y[!fʆExl:ۂ=ƣݖ( }$'n&S̎þ㦢&,M3;TH&7$"3)14)#,BTՈAđDLS{F6oEI6\yas?,vY5?3-Ow:mo&NȌE8l@ Ns.B`}Mn#Y#$GCbvn/w/po~x}I?~wǟѾp٦bSع4oTɛ]r[Hf#x9;MEgT8,R*S2@j}s5[y(NWY[$ Ix>vA0)1!>V=2e{WZ(v2(i%IC֘?ӱWn6]bv ➍e*@Z|V$͋fբD`#(⁢YJhDܽEk@Ӥ8.1{6JD;)y 8KIvZ&뮓l؁4x?~wJ_$)wj,GNu%0F=[uʛ4>UV#aIbjǻ=:k(z@NА7hb-"fc <cmGb~ K7g?G8<'",\ b釤WxENm t[$10͚}6,51KĶ6O ~': Ƶ1sb+T0u+LM~KF<햿Hm"sDmRDTՂ œMPs?ZU$0mLiw3pni|,M^t9NUiFR E|4҉Soㆾ%2~k)N02+X#f^&mnۥM{8tFwmλZ2\};W]v$Fe[Q 2qNH ?＀vx0dˊ2$.9ku1F5Zs%8rsts~N:>*{c W/#BY8 oSΛB)kTEm>̧r\X\EO/Ū;rk,C:A  @1C$DŔ$29s(D5Nm2gQwZiY `YΓroNӻyyS Lj9yJ0a#>\#b`~/?!_Xpx5w[# k[w*n$̢fJfl^ܶw &,i:.Aaa Rrb@,ZL܌Y4 V+1UC؃HgĤf|/9ArĻM#!(t9{5f.jC %c8Ľ)Uz{x{/TpXmhiYT Ee).w#{,q۾KALB[ѷ?.s<j;](Œa/+ed&HWֱъ *<'e-vXP떆t%Xan8S׸ê..1܉/v?zbAdEOn0' ^ ƫuDL@tt55Yi IDATTPBoI ܬ߄`ȋ^2>ƛv?sKɝíLl:/ZV|a꜐[hT3OG7&X)qL(՝LeZ<3n`Z4jUB;fZ kjj0OIԏD#oGSZ͗c9-vtX0T|*:UXWO=]|\u3əF93\"q a $Sڄ'Ďц?h ws:r<}yj W  X*D990b$r]ѫ']DB,#Eϋgqu|HkՋeHɹ=?pZNH8U3"8-j R&AXciV#46+7,U夁Pc"RZiw͏) Hy&w3':Jy_Ɇ]W3wioRc=TMIO~G0 2o$4t evx5F{ ֍LZ%b|gP.jj*8;ĵwk-Upu_ D9LcbтLb sݳ31I:CUkqDyvLlzƌf&܉G,Lՙh:.jKyΘ(ΣPwݲ) ̛0Sͫ1$w͏tlU鬙,dR$bJ Lkuֻ/(.SS-u>@9 tDr"wF,Z$Mk'fc"U;>pCK\S:A'ǪŬZ<G/l-b1N[zUZK/'.x(3o>]9̍^"a.9;VW+$*{8HI$9 sPA<*ܨJJ:תpv9MfTwMpIO˰͉8q?ueRaAyOSNjڂ]k#VySLIP[ mhbL֝p]$#2$4ld OuÍUYW>gUe V3#7Ө[sWꕉmٜTf$Vk?Qt0_uG"7wCJYJ=rԽjQWmg~oog?}"Z*8sS9dIJY$BZlޟ81'Is(nso79ǵjFiʐ?uQJUi~[mN6FYP7mC #jRMh#ɚuմj]Zx~.| Yza1nRHHn+Epv^j-xIYOD]L0vn("Wתix1 GouTueuFΙ9yZOu}7_}|\U3(ՋQڝ\EஆjFtj58n|k?~?ާTJΜ ehq;1֪)Ԭ%j㲯[=bV6i4*@-KM}jj6a*3oV-w˹Mc]0A|}u鵪k!q W8p~@̩17S#q'N &>t2yA^-4%c$XMLd'IPuYU.se Y{8fv# &/1e=G_κWvr3Q9L,cIV4] 痄Is^M;hg7 :?w^,~[Hh9T㼐Me?9ΏGN$dOW## d Qh -l'+@UnѝU?Vy4Gfjy`ʔylya TqlyZtuU2fjbh9 dNZ9ݮ>Bw}9U+z"'5 &{Bx%I֙* %4w.}U%>jQØ#03R,uE7M VA@Fza'F~O<ӓ֣¨RL5!@2Fl^<=l"L=ޖi?^) \O:>?Ͼs8Â˔z@]L4T'!dwjW,b/6-='n_1kѺslBJ . ո=.iΆq%pNd?0}Z-k |]2gDgXF|jhYLmFDH{yqHW#XWDa3035jؕp sK]-vLRl7_j4L1m^|W;NnhffAquc4KJrSy=ciKyxu[nNx(_s5iՓE(a i!cȡteMkk@+~f5N6CŖJclUZ` pEI#1I ?Q~P;>OTY0?qP 0&] ҨXDkm蚫NFTu=cP5n1RNeZ{HHCվ1'? \DŽ4vFX$pNDsZ-@@չ9u[TkC簮3fqGˡS3EMA€%%ݕY.ur2 sIDp ;ipM1#Zz5h5t]gzU"g# ӓ?˱}ء"J.:.'g]ׁ4^mRNlƆ-!]zyի[TFe9&?s[(1ne2eMa Q!OLw~_$u O| ARZ*( `DOpwj%Y̝=<>lӘ+6r瓼Mچ5D0&X[3@S~8ykHUeb_8t}ҝ 4GnpY5;a]*I8LS#PUjUr?]~-Dp4r ?Ra|u+v2fV/Z4W3z7\Nuy,y_p)PV7Hu w=R3 7};phO55YI_~܋5*&+¬)GMwCLNU^o 9S\ݠOI'ū*x*{]dywQ/=l V'jyn uQN pQb:.(ld)q!0ARҲD>u>Ⱦ {ZVaX{Q6A(,I0]'o"&S/ӻCT5]HSeeU'w)&2bEĝ819k, Hnmsba5Y-Vvs+I)AsxUx(z]ddtGNGxq#s.oYT,)as3NwS-5;&.[nFFH7,,F=m! sߙd_r(oN+u%s@D&NOs.#-Lp##vOD^OPǶv;^ v[B}mӈ(ҙQ6%}l}ܥi}`0VHCъ03KJ"SY#}LϨf?∵r*vx_&0}y~ ,_t_^|e/Ҳ~S' ,c67x5X:B[ݐf1jQj7'4n3E8gNIRBaϢ Eѝ:8ڿqU#lEGO"șƎ{Cb-5<>F@2<d&RR:"!e{$:.6O\piSvk8k ,ьnƑ;Eh孺V3w$U_V67B%CH.]~ex7s(3ۃHraSi*#oU*B!ŭVFʡ<V[LD|-9!qi-j^]͙ O kȥlz{GOPGD޷[x=nmܽ.%=: +.!4q"\uuqFz+#JFdkйSs_^8^QwLa4r!Ք,?!7cg83 K"q5`$( 3Fuqp VNz*uV3qN/:zx{O ztI=ReJn2%:zeZv.I`^Px W90m-'"%v|7Ÿ0K1u%+LDaPNSwެʣ|YxKUժ}AgRȬdj-4|MTaeV3srj{PaL`V[Uի9L#kgU!X,Q w;Ag0s+ڦM'1W\(Xq jPVrx[Ȁ%/_tNjF󦜺TOSlx`f?9 mg*KNHpRgj?#wNHIU-5na{u.lĐ!Ÿ-:7bSRfY#3 +A 9@:3Vm%e`s,sS9ˆ=uY͆]h ixx@7]]݉ Kzږ:muȌx@ؼ{~:Ow4kc0A"bIml.{IM})c x[LYOyw\D,:\!l1 "?_~gS=Vb,rדΚF Xr bc7mxݷMVxhi`o|xʺP)V%g2x󘤘-WZ*^#Q u'iGFP#S2R"%rQT:tb _U6n\-dd#9lgv\uQus3TOՋqXtQu+-8H$R),8hqcӈ|z25Dr?ݟ.'.W| V\z%+??o|ܿ; 2 gLn??N/NyyZt}n6%#nt)W E0jUx`IcDѿ7>Xu5j5dYrGݫHHhm*VnmE[J:aɘ0=-~tQGo˧ 'j@'3~Y혊[6뫎Z.>6/Yn#fs <ܪ0K{Daj/M1'j2RNzc*0bg[vP_8~2tRXUkGYe[Y_¹ԙbE^ڝ-R*ث5J/K170[ DĤL Rj{ƀ )QSj1ǁ!7\v)baSSrr/j>9pM_|/wlC1(\w W].5<Ő<ݗkqyPfMDg55M'C6"&M,u-״up4w罿{;RHnP!"weZwT IDAT}\ u*ڴaI=U zc2r[ĤŰ ̉i"wBq>U[jQ#zx{A1]pe9=SExS=KT&7CX92i#ҢuՐac_?="OZU1A?}<>?|r?uzӉؑ"qI,c9?=͟>aY8KK-fE:Q֥V 9ݹpoGߞc⾳k38w7D8W 1Ŭ]|3@fdF.`vJ9],Xl[B<1atNg;.Ztdj܈dŗ5ٽny*'kJsbz*b}f^",on[[rCRJch0Rklrq,AӍx,>KV[ fIf7KvBi@q,j^՗Rj<2/e.{%y.͘IZOHS)׶y}A6rB$|^OuV7Aƫ@D.Yv.2Nv|=nmg.6]({ 7)!bd}@i> lIm?/Gwץ027{DDαeqDΰ4Goq9@r܉F-,"Ą3ԣ Rg&Fգˊ 4E^}vx{-sU6ȣjlT5lfs'wO'"cm2Ԝ"MKWy[ƛr/$lzʻgW۷߽/?m?2=Ӭp$/a@ZTj9˼Ɉ-:w"¶+1sb12p_gFaV?>9U?N:/J9V+lJOˋۦj & ת,`s #i,'$4=TT_ӇnoN:;:SbhdҥZt줨Тjȉ,p'$=kScN6D[D? MtQJj۾C$\fM UEqƛ-eTzZrԫ^.P։D,li90}نb n!_n9Ő,bZb`L0Vb'0*,M(I(BN2]#I;,ЀdK=|9ӻ͘#r(ۉۗ=n+a̚т 'JkjomNą9hy{OﱂVUgϑ_'!QxU-ժñ~~]r@CiUk&B=1f'^ ȔKIY8e`̘իsA> c5BN7)< u&A,CxU/LO5 L W gbss1+Q~<]93fpJb`|u70-帟J,F" Β :< ,hV|x<,,`ڲ:II@,< # Y]̜k K1u7.FV i8q 1kB$sZԵ"d ; EGr!'la7la$ wyKlͻT!U{>>ٟsURwob.' 9']7XnM@J 'Bh>sΠ'cBr?~ݿjJDqgdmǼ^"lj9qʹ 8[A Ӓ@["-nxGUfZ&`-UtSSNщĸL- UdnID^֜A}8^|eb"B7X*Ad4ߕ/IdwI]n9~2˟>cs{swH%G`UHJYRf̙C +YZ<%i1 [Ժ8q#8o{ gǫ~/W:Gz~ՇsO6CbEMg[e:QHt!MG$}ݘO^?sw;}Pi1#rP|DW׏9r2CNI\=1 `VSh. SK>70;í m/賤fƣu)nDF$j x4NYŃ.!Q>OZZN 4lAkhWq"8TmaȡmJ G K%wi[03H$r=hkbaNlnA$$ύ3RReb#+KbfV!8|S[1qQiU 8 7H cn"QOt;{Ֆ׊OmG4fĒ/*q@Ypg/#̌U :K]oݠ%PmkVGC=J*ZAc~ĨA{YZf)![tPZ.v!QyEKvvrhK$/wN"E$)E)sb! '4L 7/qǾ=9q5gyr02 T&eƗ=3S,$'-v?#i|.A?;:&#DCPߒRcP1$YU"? _xy^9F̵TɹaHȦ/zU2S)u5s8Vs_vbF/ [1!I[wRUS ؈sn|1QCEƗUMhq{L x3ZS`SNAjD7ssvyP- O.b%#w$Ite2+Έ Y Z}9,e/Nr*7?[hfAǛoN⺄R/nRU+BVs+Q!Kn5Ddd *uHMȜZ$v@3XݚQ?zW7?||~7Ӿ2%\myvFrsgY HD$fVֻVAڢ Y ܒ6? 搄<aAPM(K|]]Uyo+lMzd9}ΊGP,܅_TuO'Epa,]PL0PBC@#21``"⠪)!ԐU".'n[gTP\"&1BUu@ |)jLXPU@qE!pVOW= sL&Dq\?^R22"G*" V=>3H۵d;3iCIiuۗt?vN-EԼ>@+G+m^~}ɧ?UR4țMEU*,:`Ѕ2< =:γVv~^ VV$Cfem˙T)WF-C5rɌ";LZuT+w+B$tc@U[m^K"z~uGFy*.]$L@9G(U [{KX^=_vk_-(@d|jɻQڻCEŴ6Mó]'Uc*B = .!}AgAWXlɸ}7ojb_Fg5&X-`HLLTEV/WYL[ۭGBxuiLg.^m*|_p_o>?ڿ8T)XiEPcoG$w?}w"@!xᲚSHb#-RSIԯ%#,Z]> p?WBcfc-c.RwwU䒧b1s+QFӲ-M)4Sգ& DfA uewps>9o T8֋$+ V4 | !3-IiCٍ̞}"U1b#Dv'VÛ/9!9L_Ch>ft£Cd Mn#9Qzڭ;fDDNQS"`F]vB|y_,csB)] <桏h>71)μ[]<\ȕ+t3:,9i?b HĮ,,dZtu]hԘ-} 'as11{y@TM7t*DN[G,᜺~lZfc~K#bI(5J)t! GF40Hq*`8?ӹ:|E]چMG7!2f] hDQ0k8n=Nf2gŎŻLK?XYFUHՈ@u_ڃ.Ā5gO+Tt!-|(@ 3 nw]M;ﯧa/8Ͼޜd*M4e0|.B\Z[#HO[mnXU] =MDiYjfH&->-#A@y1۾_aۯnnAr-Nay84BV׽ԁưtcdhz' T%LYl"R5xICRd!116ޏm'@fJ[ѐB` bd2D2Q$W9=@ՒK uAc9?~fZإ|eCnD5$]R$KHtL$TD LYFa&SSܬzVS?DhR,$pϼw{Hحq=#Q 7YLaFdD1!ۣʆ@t75z"q6$߫/`DDU?qBf-pDɑ # [R-,SC7>+1M+ IKS0j~ZMaty1^E#EC{봎ُzDîq>eV"Dow4ja|3<<}4kria XޒwV}zJTjVTJb~ӯ~ۃ|Ej`>ךTRІ]:%zO-~Z N0oPwo:fX_8Z`~D4LeZD/`/m GCH9`sm5:9T5Yl!b**&tP̀l*(R@0d// U[X8"Ҫv͡pʜ#"bP>! n0s8` \N @k'9HO&nЪ{FP2-6vM%︉Y_˹wWi(FxIHE!|1=-]s=g+bB92؀&bBڹڹy.sqL1"@*md͸>wk?u?=)'jͅvQUu`9!*݀zma'eRTP^6$ٴzC/A LEF[098! }5Ǧ߆!rDLإPbc 3ЄT@V#d p)+IJ))Rs-!Z͕иl궹"0"v".=Y d^%m YAĜ\If~mn(mb`i7H7){?\0Ѭ:j-@d >#I =| |(i(VfGN9hg8`Lm~=sNm =j6>S!!: EORѐRJ&#uy Dw Ďyh xuؘm+z_vаV3P$qTNC]|Eb~V!]i7'?~|_)KVE$i:.(dA7_Y^5DWK']Bƈf y60j/ 8ë{b]jFAlJŽ IDATmy8==K"Y<}wkК,CbGbǜ'SUi>$(*L bb V5aMD%{- ջD]jBOU-\ jS"z4o0@MM>+JA`ZE ("I" )ap+#CRr TtpRC>R+ dV lU|:9 "`U,"C}n0dK O?uVX]%Us\%&Xy|w7!RHUb/aJEd2DHKPd5_m=Ku9 GDꢐcZES\uA3j| ivqbh14ȔTSY4\'lftm\RKr̃ yOooOr8<~5_Oǯfz Dwk9F@b|1*_qu׭vAjVmWr"hЅmffHdBHUUbCW11S`Ⱦk:U"2#7'%11rb 0k={((jUrCP C(5O"B@6r"NU#@8 )D3Gb6Uͨhγ\̥@$^t[]]Xv\=uD]$EU jf@MbZU@ bw/v秹+pӽWSͥOJD6 K2g;~__y?p~?~>31)bT>8p7D)EjSX_2RJri% Dl̴h|z̷ AF?Q 1P4%з'=5ZQsc5fJ7j~y`㛱sCh#A[5a3"t-Fw'꼯ӛ_rZc0c!! E(SDz]lnY\ Z2=MqHfp~CZ@1mf3s/D\$%ՐinKy* 5ʿ7#j\U3""oX덇qŸ `l,j6L*ZΥE\hu> &!PGeON.wNC&‘\'z# J#h%AU#) 7V><0# A` Q0r: 9Qirg?}:?rk i TPՊP !t9$,eTԑB<|sfFsj:+Mqr6N nբ1׹R [`rPU *7[4->iHL>թB10 cϖa 8jt50 a1KDPU~G /=  Cg3j(05]Кm@onXX* a$ 0\zo'$oJ]DDQ@+M[v7Ϧק{Wn;И/yj"&H첞iӔ`pYZc!'tz:wW ߑ3Tjժ@JĈdPč~7X-P 7ld*^5bEC]b@_Td 1 #VFCS0KƌLYy.3j]Ծ;D)@R%DXy<識8؅D^/3 #N=bDXݮ?>_&^.RX]1\vR洹*X @{.loHspRnȒٷl/ʺ~n5Z4E2p@ԹJDiY[jKp2\u9RTۺk UZ?AC{ iU!2ՐI[g͐4]ÆھG !kNKSA ټn;d~@NInVq,Yb)p[&[OjqINYOl:Xk슧=)`i踧0 T3,^o^-p)cI= 5i@vɀ &EK Ar$=.l>Jo>OW/ iuR\ԯ03xyP$P|>Oor5 )V jR0%FbbQE) A`]CZj ɘB+Zzli\Ju_maȃfRm{Ǽ]vſgǿg_q`)U2R$`S.EjkiH̽7)z R? / r)ai$$Sip;xDއ!B)=2"բ粏Nj>߄ /vo|3?N:aX2 +Ptp wybe ʀK ۇ*s5 4kOu:̒e&\>霻zo 'YY {w (9橎ljWDP \C/Y_rIY+M{ͻUϛ>ܬ6yP5!Y`C]pmV*Wۈ%ڸFRAK#Y*ŐB'rXp?twnB@sѩ|jD jPZ[צ.= 2P:> ß>Oʟ^nQ;J Ma<ΏNˡUuJ6+R"&Vn\05Rs0C!P26~1{|~ Z+6I죩;ʤy|]bKS)s#tBGUԱj1%}w:Ւ[wc3NuR\ٞ5Qx/قpi1\*ї bZOWWF Y8-PJd  =2Qd"&o4LU$LNrRqgbS(D˜h|񝬂TJ u*D*2ƫTA!&6vϟ^GF5> M-Oyp9 I'z\"/Urv+N.͑DH7RPUC%4\@/ORR\4U W:Wd$b?c XE14eE1Đb"Un3O`d~㥃jYתԪ 1T41P"R5"U2i7t9틝WyB%gdq?$mLqMWO~zg~y;>.Zt J?f p#n0;TAU bbWߞ\EXEiurolEZ !E"E(>z4"8: F~\jH ݒкn]P04}J9Kw@HbjzUBdDn+}&@TĸOˁNX/ͧac-*XO5Oe%wY:Uobw?K]D4yq&F@P j;Wy8ɂH-k=|j f6WC1uB"ɞF"lSf#UH:C>Ѷ\)+CG.YMdq.U%tY:!QG/0fTQ B E 곎0빋wsݠtIKe1~KC42F|u-zAi} 1մ]aq?}DZ]tΧɕ"twfh~@)*sբn@!P UXZW'S[&PZr+&lRTui/UՄ{' ۤZDk 1`BRI͢F 52d6q̤W9ϧo޻NEEv ĭ>wu iW0VRKg]9 HN^581waqMVL7S*vxǛ>-?i ?Es_Ïo?}YB qH*VfAH_W^̇c@&K̎PMq +ZWJ_k 0ld).\fƁ*_󙳕U5p@$U&yu7Ӫa;7! EI8*m<̷oC"]:ot.iƳz @9,E( -Zg)"Wn̏~CBڴ 0{KԽ}[w1!3oNJ*j 4pA GZD؛S=;<p\Z;b !>3 $]F4R֪6j 6~9%DZG6.8ӫLBb1Z*hV&^@ۻi:?_Ou 3*r>LBDt޵(b[l8U5v=\ "~"jRR}@}wHL:B۲U@3@))!\DL*ԧZ\(RG7j5!}CʣE11h@T1J\K4G"DQjI%""kQe|}&0X=߾6v hk&"2BỲꃛr<-=S`'GS~u1jgZ6t~kɛAt>Co^ TЧc4X Ԭd]";r?G^z>}1b 8t4|8\aTMCv\QF]uTgyyR&ߢ.mnB3bb"wϾ8jBjb7#,D*~eQ#i՜Ϙجn`Ġ!1q|ëզ6 >~_]RM4vb@W}ez~!-$D-\jC影[ hui513Lql `L ȉ@Q@Z"0Ef6`600p:_8?)uvަ @jfEL݈`!#NY UU_ㅄ)%Gvt·_ \)&lVr! k  6 Jw1Q=x}ar,߾_Ojtu?="BD!iHp?Cё~yLy}uO85̀Ґ\$n}9edC"f@MbH$6N/SfCoZ7w=PZ2QC@ R3Pg 0BZԹEĪi; Tͬ[$WMG<`e[4H^ )bS`DND@RF)c#TD4 S ~rt7ۏUPy"JHT H ! cOx*iw_l&TxWC DVjAO1p)10K#m"sH:2EI8@IhN]F(e.8Ǽ}x˫˫UBM1D㋨EqÜR"1xb ^Mt7 nbC9l<ݟWφc ,8wۙFbUVV+% |zji-fi秹2BSlBaf].ֹ\# Z8~hUKY70@~*aDd Kkܼ/jhh#[L0`18ȥu;e# hw~nt3N3Ǝ*ݖ|cϯ23ەM ^×{!f6T~>լ_E@@݀V-?D&ιgD<ӫQr8PZB1Y]vb`dȌHΔi.\*Wx{'0`$ 0&X4$ 4%P$01 P}Xu XE"YDj 1Uۛ<-hq$ l:lIϣLсbhŖcUH|r^-RH)YZ'lOZRC^_[&: RE"!03EDQ"d{ 2'h Թ¹$f37"9@!E:ДL8$3 DN: 7O_pn{ňqsATLLƖV1 +T;THHqz=qVZP=a 3uDIE #`co_}6H72O3|tYՓ84G}ٿR3WX_ u1㛓\Uf_yO~p"IC-`qe ) asWt&f=~&Fd& CPZ*66nRSpiWz0<+ZȴUFA@Q0S氰@h}X(h`B~iUvay6b>vőLcp8t]G KflS J:2FD HD^;>N CG^bֆaޟgo U+1E3q\̓NcO|zGZSdB."á{olYy4ʮ2Q (i"$QoF@Ql"#nmN^5o$5 `ft˗kIJJ!EZAՋ 2Iɬ) GRL8"PoGayէ!Q}ză5y7Rfĉ~(w8|;>}~}sG- VO dUC㡃c׶1ʫ||aDh^ME((( wx  51Q&0Jw|<2J}cX]]` b؀ A#5:eB# # Z2ԩhh#jT#;b۟sا ɉ]ȚXlv7L4#U:anŧO6m UM X6^]U* (yV H ,*D+`}B Zx+̀z̖f<$0pd'WO>yq*Sxqq3 >Ř!G?gPAxR/8s9Շo4(y{?zuքd`ͽ&t:djh \nB 0.%XJ,|nFI=h@U/˅FJX10vWF`cj4kZM*939!!BJlB̜YzFmin0 `A[49#"͡4M\ Sdv\KV~pz^qg=w= #$ TtP417fRE0J-#86UP t-Mӣwc! {h<(`ɏaVQtP$nP3lX%?ڢGڨ*1GݢE613wf4"$Bs4 q@@Fs'GɌ )#gq_&45pĨY.3'jg5ߢJ:+`Dԛyl.Bwvtb%\ Tɉ#ܯMV] Ac9!j2gm>[r`7mGu>˔EKTL]ƫc&@w%f&Rt|~Otp'uD@U֙ t$H"Q[N; #j@AR5I>[M u@@A`!:TvsL7Vo_ݝ0 r/e_]])ӫU"> }F0 P^]_l*Spccj^c?UAe.7Rf;rݞfFNzih%bԢ!eBf25ED`] EiSM!3" jϰ;;UC[rIZjWL]֒;y(gDYmwt?M/swB9ݯo̴|(O^Α0]cF:U8_=ʴ+K=NjuodѢLf7$`7CЅ%ūXvޗЧUPAcrN,LZ+:au8V =ޕx.,d4aⴰ93 ÐvDdVCzfD!Jx%;ُܼ?֖T ƚ 1L;ȟ'jrنlv+l&JO%!ewNͷWj͉I2W:hъݣ%"0" #؏`$x4W =g㄁3Hkp=wrp$(\{n.Z%E]z!/PtNp<̧4=xO>vik:Dž[aR(.qA[ :Ýȃ@P jN-;$:> ێ/gn':ܜ\߬ԋN6-vW)ߖq p^E⃳զNI}fȀӏoPhvц87 !?ߔ}9V}Xռ_X|qwyU犽裗@VY}ҹĬ#$pЭ# ˭a#Ic[Ď@#ʳiK N@ 5j59n}ep,9z殪9I/|NpG5uǛ !0Z Th5P工Hq~uNLQPixs鶞xWYީ][uV}漌3bzj1Dr:6k+đ$lkvq|8oᜆTk&m꾛m6UWw u) &F0'Y%vS-ڠjk r#',4 )F99JY1^X`j*2X%!`PCu4i7"gU%&9*zO(kܭfcj uTWh]+&b@>)]=dYOfcHXzq''FUE0 j"$D^Of$Ȍ X9CfX%!UsFvIJpN!eA$0 g2z u~?<r*( mY3ՓuOial>VYS6y>]⦙Zu`8xi6 ̟Y4)9(1O}m?R#̽-ؐ3_o|y3hWYgu"\ *?Y˨<ӷ>on)(ld5CxǎV >L98$zGcFn!fC9fdԠt,>\hH=$(ojNig$CVEZ$%IP% 5 hÚBY3GWBҨZ 7Gt-f1-(N; u8eXڨa^^:]|:;\J9#|Eٲ00`K'4&t{jn~f0imbp5 asyOA8iW.Z-TS}j-3ۦ\|ޟ >T hہyz{`}:=#f7 cU41g)y[as5p bIB]DD(]ʾ\߽͇e?G1swrclA[@4!l"<`,{hns6R?_m2E4.jO-? Fw\?_36QEx{<NXxT?-c442hbBȉLێLŪ6vxMYh='2 8 r96m0k:b9whw:+ 3!Z$f/ 5ACpWcaNB 1h>4W8 @Ҵ0 OSѹ9&4]mV763DjBՎ8mjGv+q#3j$!B["ɃO Tjm_$pbG(AI!27y:l}o^Ø̄5a:_b3s=:i:>}}$T DLZ&c%t"vDDZm*᳡04 ͳ[5BDnLB) pwYas1nLX:{* .޿:_ $βpշ| :_蜄^:I  U݀ @]U'^lT`V|log{}V)\TrEsg""1M[‰6,L@OA1;bnw@FEA@J&Ib\M#ncEht2},W빎@ȄD~BWX}tHc߂ӡL::zDูgZ?'tWƷt_ ཏ2j Bo2C- BYB,HZ5XH,Fe-/Wj'!f"wpwjm[ty6(oR,m]bĈ]J#@#FPjk!244y(T z'g3k[^TG$Nf6j}'CCz_u6(\o~ C 9!2C' ɉ &} 9Cǡ; " @`v|ꠇlŶ\DŽpNsJ@5ԜBQt۽~ju℀'pPsd,'DF[  䑔K7K$- 9SL͍S2?HhbUܭ;Mljx4@RyH7_i7~4MCט{"WL-15CC „EM ERj&L` #J(ZuW:08C"Nzv_xo$ Uٮ޿/~x J_\$L\R/ۗO><s[(2;8J/?-{ܽ'RJ)sM564#yU-"`vb&F!$8|  &P829YSXn:cGDabm2q qs:t7W9oS3##|jmw1lzq>0xZXN:'\iNUg:ΦdVt?Ce"w[DRTSnɐ*"J,"G!3rJB4ZpPV>pP󙍆blE݃5LQNNPn|.Ne:հj6j~bzfqf ӺOCUGäcs5ws3ID1 'P_.:ݍ:V?u$ߙB;+@!@ks 訜o`Z-:6^AJhZvb; 7qv!rVkx#<$Ӄg؜awןr1[j0["+9!!kf]cx@$:UbDZ U8N#[VGdFw*:.q8w)u:J8߿'/,`VEi ֆi?~O_N.[!c3ZBˇRF};8Ϳz*qYU:T SvnuuU 2$ X312qU (Xm et!曦.$M5׹H{&lN ckuMN :Wi-])@+I=ε|z8x+4^q %4(Zڻq?Ku),O6mp{|O7V2htR 7!@rwUתV@M ";I]^ H.BAA=3 [-V `l( 8=6MR8jm\%`$fmVRH݋Y(ǎYU%QECH7@P`Adj9!Ĥsr& F`|_߿=yLE_/WW~]3"09`xDԇ|}5OḘNvx{<]u䏃Mr=/'UGJXBy>NNz{5LQbl}74]ci03ӏ:X>pnyL$"Z9h TŊ߿?<Ι/O_oeQxneɄp?VԪQg]& Mhsuo_rjLo#~܍c Jf"uz]C:N֠',ѤթYʜO?ddI!;<$oY-6uv ,ܯR4O5%Ed݊uGo832Zm`:kk}#&" 0ⶈ :ݝP@ɀ1~̌:iU ,`sN$1A%lE@܍Й_r l\\B0[nhs"h 8 )lhr 90Ӽ|bukCZIv|_ˮ~@Dp:|$inxC^%I(J/o__57c,DTخX2wfZۑaɼXT0;9=9xU55H0$fm}jfĩ%+Z$\F"ͅ0g;gw}"r[1!npui[ꤧݔ792TmlXحs[)n:C]#$a tw/9S%I`h@ W/ J2VUZu:S%Qc)@߇H@j?*4+>`HKdU}m7ۯn?t>+Ohzly{qѽw]o gl{vO^tg^nRo1oDfzf:o8h\puW@ D dϹB*FD(||8, `Y7zCC7hˠ䀹LOBw,n5^Ԗ"*"3P!%bSu7S5Sd:9\Y1,@ ȑ% n$(D,,31\!¤̉TAu2 D]Fk]=tNPXz9F#:SD-!Z1+Z' 57D !Wbfl*VE)\kءQVLA[DݬΥܐ(Da־>Jj\tV:Ȋ%.߿@n>S!a,<;/W7_O"OFQx}w.|օ}FD-"G%ku$ fƇ1fja fbrGvplGJ"S1N:<&,A=Vp"6wIܳl}ٿ>!bXn:ޟΞc}yu=lퟠ7: 켁lABwѧA\5rM#3jD95D'9% / 0qH/TMyu7O-PFnwuEsb_uuyط V+ul҇W?:zW_|{=X! cG4aG?H7_ߜtw25 ~xsr*JǼ@(FN{bRa<=m}[1֭rD^<\ԜHaEz)s3%Jyi]"jE'+N_v"nBw%hE8[w,jU4OEU[#HR0] 812t@q"$Bai&fBDXEy7 'AD 6wTjsvBW#DGGx/s5pBL#!\@@|"Sf%ZxRgCɫUU ( lgu@L(A+2aBάSzH=ȩGS.ܧa!?&P du\oOSYY۟{WϿ] /2UN+lNC72U">f'NöH'brhEj=)oԴdG&`w$Pf2iSl'DVY2.c4*f Ph:zHCĮyu7!Uc6͘/2,#Ɉµx|=NXЫ#gJ4ּ%i9]5E!3$RõftE#)VFQ1xv=<l3qi 2 "ǵ="BTI]8YK.@di[l9Ь.%z_w܂0 ^CLRDsn0;#iCn2q7'nFyDw&P5b"@U%"05P0I]8K;ANݟITݽy-Bu9S %fJqvǭB!չk-NNV#@,PEDZ^۫mF@ :ͺN\uL:x3 iC7'W۟|[[u`n»9Q(~~sx@G(j:kBF̔<yD;BU;QY$-z"2"Ix n3fcLQdg-l\IbC@:PnJy>_i1f :8>S'  4FjŠ)c HX3R"0t\{roxТ vY2R]"SU$Vۤ.$(Y``Q@;>:NőTwp8?SpyWI2QZzKO{'x{Hקw۽ZTz(_  Cftt>}|cj6}ُWe-fjф  13!jGs\?u] E [gVp,;RQ 2lVM)Hj7Db NH]CޤExKZlw1p쩖d^B*Y*Z-NK t,J5FrjjFN`>cjfUCSAP4ϧy\tӳ̢t8D"h=, FWC&D0aD{gN i#Q!dH(Pj%$H41@gqD[:ޛVgpTBBDTAhd}aSsmΛ'gdӛx3Cٿ=jgO+>^}@@@ nx3H^c?y(·ׇiHuX*E J45*a%LV) ۫ˇr?M_$hP`cIľ7wPwj]Bk̔egE`wj'rNyDÍ]lIsZD\i !A-`|L@ЀȽeD`,5 zLq:y7U^_^֬"3{᧻i8!z Sp"T&XS\IQ]Y},@(c\U!Q]fa֩[r YUc."vZ͑n\o- b\  LPw l>UΆ땋U%ɋ_{O_g]$ȑ+ mݗWg/vYWutudg?~wpope̩uVB:dTI$\HS΀i1TMGgjX-8O0=8N~XPPg\pov?)!|T'Ah@z鮻pW6{1BBtDƝtIgs &P1Icq\߉&+ٺULP֭q5&n"DpUS3:oƦi_Q@xT0·7O6>ۦ89Uj_3q7ZB9!9P$;TSr'"(} VH"SEÀhWjDhNjVaDgu!(.mn&6v`S& pIXQ"w7W #O1$ _ۓ6#0gOwWAfp@>~ W9Ff?5? o뫵'5be|T<7UKMHmAwހ /# ,1Ţ޶[dq2e9ݞ>=!{AOnNf9#A@zz;GI7:SFƲ\62;A)?yǻbUAWu kVZ=S0F^ 8j*p' b!HdGigD"dFr)BQ):0ժ̔r8[U;'Ո;&,p80QUUՉQY05وs(Z1Bx*L,k]*_~}zTݪ! ClX;@ u?qҘka'=:TB%yp}{57i< /%q,R6us7yfڲH[X \YuYIi_q@-${x0' &Pz9-F8$ MAf[ XC<ܬA*Ef̪Ζo~vz7fS$p< _|MO޿ns/dWa7 ̗slXK岫H&*Lo;uV:RִdꣳZzr=LW[M9 9";ҽ{ĩ.0y;33Q=': ~|F 2{H] U[.z0}nܽiiS7z{/aL;|3w6響|߽!_¡|sb5WtWEC} IDAT%"tN *Q"3 pvł E['u)LB+@#@tS{x0Nt/=|*@7C$(~vSPZHi>?ݮC2^W.WOOKKYа˒ ]fnᡋY3kj({XDg":[()RXX0dηٕoߎC]|g?~6@ZѳHteD:3zhFȖ ^B4 RHP&6U p@Wo_OoKlT8``7T k~EdIyl+};>9.ۛxyV(e1 ֈV?p=揘V-&<:paCOuyhdˣѨQ{hzI ⲕ9R@;͞ W) GnrB9*< !{2||{;_a7}۷Ml6[,“MXwn/iFDo,pM9QI4| )S!B ,NiI9uh{\U#(5d8+KK ' kD Ig{ͭy{Qjs"Ars͎ u\yOEcHpս5={~ ۿz3S#"L剂q?/ˬА4`aΏJ . Xtv#7aWw B +J#qW[n{mC8Q/{ Dhp/o?ٗA:tҶ;JY/2 \&| `/ljԕ>_ǣ57ͶͪڅJJ,7bbp!dj֖>I""iE,V"VD: e:="^3OY72_Lڤ͞%u3oʘ`a4b!K̋LgA"2Fl`stM~A\,AP 1#($,{paX]#J-l,̲NI,ũ#oprU袽6>e~M2ʓO>˥^+tFb4ltݼ)UXݐ5kflҖ[,::5M0{yVv3 Yppg}1.Z5 Prz;yj{ĕ"\',fٖeri:[;.i.۬5g Df''89f &!8s .J(ɩ+ÂP0ÐDx0/Uvu$/5yVH)],B"WϮ4sN'5p)Nۛa`^͗m|V7kٗ|:Ѿ"v>,R +gXE-pW103'.Ġ7#h=oz4MgYu ,LZٶ2 B=FYa!,Uj0nC%%5K;?r4U)*"/g s! {WqL=l`!ƚY]}x"l6ڣ1-ȽZNov ܂H!(A"BZHDT6 ffɮU\rzZ3шCA@/S8R)LF)_y:/j<=La~|* {bIg 4pvg 41fRFzˌ' (afj&l13GA՜PPձЏLBTvio *dD!q%(f*PGpm%m9}`ĜJGTjr}GQuG)FAa-pp~}\wPtV)Yf@2NCv%;?~8/Ca2^^@ vi&Ӳm!i= #b>MwPrF}`l>HG,Gmvyq{9|we`)i,yιOq~;Om|eؗw!kvKhvEӭXm rrdxi$1evie( ci I6k;.Mgft>/WE,t+b@ ױ~=~Baɜy\d WO68=c^ 0po;wōP Aě*z;C;6h,GY?41"2UUEE/Q"q? yv\!d*[XsIb$P[F@3uJIPX\ѥ^|?@sJ^O9iE"|Mnne\Ruik͖yL\}UZkkQ.[S)`hӒ KS0WeӅZ/C㙐^0dB2.p] V3JaP1d)뵐F,z {d`@HJ%k&cEVi[\/ZMn x*_ W `lKLnLGIl7;{^=+bs9G)1K3W :]]ԚܖEMZV2W±b,EX$Ho?'aHtdf7䛦']WJ!&Eh3]e />^Q ݓ̻Ǚ[V <2afE [UUavSw7`JɔXG"J@w4۬)DYj@8,GEV*ớq+Mvx}ɉ:̔[]sdZk\Z;.6ͷn7Ipq_J~%%$;ݚ uW5rU̶KT$4` {3sB<l*CKD͈Y &" bp8`ͤH&%,EF23 '*`}o,$3pt4t#(xo ӕ0Ok|UF  ߟ?oӰ-2XN˸-i'yI錄)GW}0?\ 4*G@PZt E"oOz|*DDZ`Da NEux ]O O͛z8XF[6|=k<#)G`f担;BDݍ7K%^guۉE4k,3˱8293gBT L;v HS7`mʗ5jI GIviۻo]onep[6UD2Q{SRkn{ G HUr*NDi,̝5a,}$ɩ#H -+%ɃBW0?K2W`ed\kN"< Ј%HDCMby&@jT67n]??O#x7?yK<~UμgVmqEC1E5r=.|8hV.d0j3p0H̄`G^ru2?J Xщ^ϗϷށFޜ3SœM -BCἫD+ntVS^YkXQ&mKz-C+g0Ԍb=U$z1I( fZwpEY77aC嗇;_SI#,C:HCYnH7r{G4˨p:[TWusPsD@M}Ew^E"AK=v<褺?Gk`r# HIdL6븭YЗK c^J6_?|<ܗE"BQɨVVudl\p2Bِٖ`PݰlDvM9j%^.H2/4]Zlmv8T!,<ܒgyk A1pK{HW/ 6ϑ,\Q," ț H0Q,<aN !A\6wXA2#& >ƨp{kF%v5BRɛStmzXJR7@ c1b(BBL|od[3p/N 3e(s>֋W?~o`^>_q+qb9=vARXôL2܉1lnLAz #Li贑\)|jz,ڷ;D94M9|mw-E;FYYɽӹԏ?s%"(ܧwGE$+SmҺk(P_f?$b%P1 JX&z/ӐK!&RĦ"3b5Vݐhm^-֌sʴ({3g|,s_1仛Ugda&U!L%5`L8K,j ^OE!4OC?-C iVLat (ҁnF*Z{s֤YY#HH5' @¶Vx Pbj27oՉ;%ƴn960z5*e]m6/檋-S̛x{T:K-Tؚfj8!$0$ LA|d;nKX}i"@XDHwD33Kf-tꮸF^6,"aMZazd#CؚhL2Ww["0ah'."8郪NWr\'߻_~/M??_y/.墴# x8ü"hZwW[[|IҋR*a ~&b`sVuz/g[<8 A$@.Uvw܎t@ݚZS)+v3^ic "\sd§lfTv/<:[a N':#hWE]&MFǚ&f 2unmQKy67ojId:!-IJ`IQf^(a03"(%gF֩@8´rؖЬ |)"6EMҞЕ+J֋[8(pB~Tkc ~*RT&B}VmaxX];T恨 ăp$W:}=Wj`^Y:vlڌ*}\f#'㶺v W՗r0ϋ C|88/Ӣe'[JCglv*) RSJIn*@nTܲUA;PD@!d f""|䮐:0z2HYSJ6yD/&;Ng!dqӸK 4;.(x` nRr] IZca湩[t"F u,GnȞv<)e f2ՋC6ݷn^}7x|g_A+t~<q,m҆ZٓHqS/%e,6m3 \+U\(E HDڌpvK )rb>mǮCv#y0<\ a']l0$EjE$5L'-z#̪2p5ȶ8 RV.^N OEV䳟U"Rǘ6mf)鰭u+ : CuO{HHZd(z"$,"~[VrZs3 3Q D 7B؇IOUu70!6 IDATM-Cpf9,ßS#9QDx$A 2VŎK O[xri""aؗ59"Ud`HL"*٢B'NV=1(|~8i`lӉ5YW2"# ֐Bn`_<"d(f"781?(2;窈P FT{= eKPFI:pGf=eSqe27P8ps֬[vgIR]=AFWEpy9oTխM˸.O@A-B@0Ssf$8)tR"\8< {?0l/]U<>&^{^̗]l*62B&ALÇ  sf.7<(/w!qB?HL ls욽="7o.lm`5c]Cq0Y5 Tpߐ|k7 ^@վ݆͗_pZlrB% A" 1w 0L0 CHɆPXrĺ+u5=@A^X҂g,dl6.jb#|@Ie(ˣ^^O7kى,}0Ui)2?A~9 -fozE -+Y ANbKЦf"#[B5W0ԱXX`ցs|)'*] R:4Zu"dB X *BfKڪ67aỤG׏%@Y,ap)L Ss"spXbfDA̹ raGZ8SZ jdd?KrpX*.eh;0vw}w[IY}P˃amBz#;ތk08 w߸}ջ|e*}_?duaRCRXD@|؏k][gvKsX$X) HY߲6% -fd/H܉K3TVj2)ÙXKl%s3'AD0QĈre)7*4qZXX*KR sR+h.岔A@8rz"Űq.x0b1HÉWS2G)E*/)NI)R-}s¦O JP,8k?T6%SPڱmaIfq8KW"}e 6;U1eXb6ӠRّ h3_7C ehfENj>ݎ%&1"@4NEm\ 5F5Ok> ?'^g/NoχOw. &=ްL]0B 9(Ww/"$<ܼZ;u_}5Z"QI*ˡm6eoӛYvXPժ^9-88eR(-.tyKoqd^@g= Ϲ M 5^A=ۡBqZy[(GF!NM]~teK>&*Ejʤt_]s9-#>q݃,O~g'M9= 5l&7g2ampGe\m|im Nn0j͓ΗEg[γ-UrK GJXsK4rZN}ԃ$3b_rI8])Lyk-E4wR̩r7ZW#7ypUJ!&Ca3巄3add,{BKEh@~-kj*Qt%:$!u]&ɃfM~-˲9}ŋXl$2lN0@XȀ50,0l=6DbQ6;3+{m٫`"E4 UDd}^k}] #f3#̤VxlcLp!" Dՠq 03Y 1^NWϖCXJM\IuMͤZ;9 IXtԦ6Y˪u:jeI0`A-07?4gy?F.h{*:5%:Wtlo>c| ?\nmsQs+[Co@񑂮_bng씭]80[ȿ) Djr=ԸZe_M-[Lg7s #$齖Waʪ`f9C[جmRg=XEtl9Ba9Xt.=|Q/:'d4avLG +g]jeu=V| ntXhgE-9(#B֣FəI ̿sIN>V’ὥ"Ä GFaY-,S!{T:AD \K5Q^Vگ!܉8,XfU;[aÄ⸹sΑAax^,y#E8F'IC;Uy!KC;;UUltq7\>߱>-2*Prt@v@/?z/E O{wsX׵EmXU# |?TY'_]zhՓۏWC>{Z?bNSԛr)|fK(KAt|;`]/Oη:y͋ZZìs{]e_ ,󰄿8~tttI.NK-u;F#B"; MOz(@Pg5ۏEJA'/_} kܟ4E9m6Y:wRYaռũs"=?:a91HDV\n*nOvn]sfFgiV_ró֕\]3MMu6U3 ~V@ "`EXR^Πuyt!ΌT-(Pp~1 \-EˊZmT$ׄLf\$gWp^QPRn)0x#ה<,A .5u)L89xd(i3M"jBXH@I&H{]9H sH_\G-qȽ+1pQ4d(K!"ƃ7/c |Li;4lg߸^oQ.=fӦYla7Yn!ԅ"^|OɟxE'?Ouͣ/8^Ri֚ Cu0mj)3՜nYmV2TQhl<>^NiV@E]cGSqD?:p)W[ϙ6@\P| g +y2W&'SOXg"oj?bFan?W+[m5>'nvl6j9TÃ,=9dR4&r'hf2QLK3;a+6#_ ߞ8lڤ KQTBgC,H6M|I (2ʊɍDtq)tS(翙9e8-=JA?.xyYtַ2yӕLƷǍN#Pyj_O7WǺ(!`paXgpdcMtm 82݂:եͣO8+hkp J-fAY4HP)f Zz6<_71fBU.ԞT VCAN|$/P! bܔ SD0QYs;gll)BG4Sjw] H qs Ό'G/>|_ū%v+7}Y~{uK; ,B"{6EjEPn |) ӡ>;n]o.߹y|lz7w+0hX%=V+˯mn>8~0ݝe6lJTD29'PEjR SOɏa1vݻ(4[Y0,!b]_g Ҝ7fmr_{duﱈdVO#d:ߛ͵un'bN>}=I=7Ư>Y=ߘ&3΄͏ٍЈbV7% 0'UssG-_.ٚ!NybX'BIۛ&;kq9 ^XX{CZ<'yS O8?S2HTCjےp)2]t`$Ď%@ok1S,=KԬErW! 6f{:n}NVG~6Y8غ/8\JIk'-0DXd<YG C36<+q)qG_]`i[jFs8e2 u8>f؛*٬"bVjɨӖ 3 Ǧi>ߞydu 6|0)ԚTf㮎Oa$uBCxʚ;?ݝ^-v|0?ŋxlx|Wc[ycaֲ.1H;βSDXmi>)1J)`xpϧͳQgx|sơnU"LInx痟޿8>8p騧[A+% ݵDag eŻ>|7YSmMK-8ڬWDz)eU"Ҧ:k]M(X (c Ȍ4(a"EH[c) 3'@c+ DщG)xD'w>7');˽+'B͚4NJA86f3gu:7feR@AMMcvKjZx{ړՆNb 37ѡċ( _ PMkip#NL5 sq5S3 Fu:$U@Z*YLj-Ϋ e5KXrm D,QHe*|3,EzS3rDR&]]@)"Aꐰ"L4}G䤓#< IDAT.\<}~f 6W+^ Xws^"xW~t|{e0o~ b bugVgH6|(lv=7TD$4^ onF 5'ɿ{K%Ih/Ϋ݀%1A^W;t6 2 <V2sHQ A]Nc(,CrD-$Ua 0h&tjY-5O2Of2TQֹr TmL\`͸ Pt=κ{~o\1( @:[:q㬳˰[D$=>qjGӹݽ>w/'?񊲹ߐjeN"G[/OgceOev rs_`&fmF`íK@MOZW/<V?E.%\XLwy"P1N޽zgg5fX#s㿘Zw벮ߔ̢jy[ ' AyDYv F&z"50Y\"!J 0SFAFy#`)^}]x]y"4V \hە\~\nz)B~6S v͏CV4&?*JʬtTWWr_χ99]bb0"Ba)2 AvfyFk$&{>k$0%05MvG\0 T¨4x9>ʥ0@ZiIvqZjgTƒ hm!Tf<8\ݥYP@ö53srC`Cw@%"u7ݭp2r 3$z_$Lg.`ՐngZҳ#Fs-T2LHX,ɗ9R琞Hjz@B[4nn:?VCOndm2ku5/չz95kǩW _On oy?g?xxu=[XyfF "u5 B ys,6fWJ)% vb[^ f. a4$HsVF7޿כL"@$o:)A5u D0XK̭C)A@50-lr013CGswe"MBrش8F/F_XeԈf49KH*H՛{aﹻԊ;J8H C~%ֲe*vϗ0*d~T:(?D07_[_w|\-YJ\cGz\U o)Yad)8@ 'לt`a񩗁e_{Cf}vk^W+K22Wpe8a;jtSa^X̭z[8D{?{2N+얋&R`Z% #ZD!0S2ҥF\ J O&B`y"%˴ FD ]sXV9MX8Dn5$a DzRɎHNAmR=<|;v0H}Jjfi> ֻޯ\EV9aͧ6Z Fa c*ZPr?F[^nnJïլi40eUh?=|z?S "D,a,]A{A!JKL!0Ajcҡa Q}\e< o. Jq1V%mfm!y"0Јo-Lb"!@ ~z9?MM^ξ"RwS<] 7 (Ⱥ?nv7H.SQpu(5%QC;H❔'@J"N^̑$"< u^ N $6a0P4#8OtpoBxg/rD@LIpPgOm>+j3?]1fک7u)7?{>.Ț㰫QhCRyW'lجzDݤ5׾csC'wOd3xf9k $1̔h2ڠ4664%xx3&Ѫ׉#ivFT2lszVf>5ڃ:LN[wAɈڈV&E,$QԲZYar K(x=)ͷk#5"`ԫ f#p~G%W K& E>V nP yl|DT4K^^<›;gOgs t>)^_MbG^ WOs;:E-{ͧw0|;{O e?;gwal+Uڱe~~z8 k`6wxwDFCzFN@ҤաĐێ%[1(&I堘 a*2sKJO D(9aBɝd\pYnւEQYNH3S(11VHw4KAf-YUI> 6˿2sa1."p535'"acRYؙQIA-RPa"4Y4G!=]6@( rr2W8g.`zɥऽ'|-oLjtbX>/Ҡ` 0Y0 9=yy)`rHHEOz~ysBsu u99JR=ysktʌ@`lwH}yG#8#)ȅ8)e0 栮0 %Nw&׼zwƁ pLE[;.nKD ƾJ\$6'|s<VxՆ" k P$pOM'kj.W*g [pi 0N6gzqӻ[wsYޯ~$1vn^nVqSYQSi_ b0mN)"܏{<ԡEO nJ]0B}1`:ηՂvkZ )NGr=P=s 0w%فs Qn aWF!iH" 8fr58 C-u%Zv 2l.;@dDEݻ&`^&}i4.C[U ܡ l ,< 0 2Rcr/" f~$j민sĎgo&,c|CyXBt㡅zxͥ>F5t]eEfHidU^1`J, :IG,tQ9&/aR 0ONAd$}猄ŘH\ɢѝrHwdiD$\- PYՔ.&bZT6*c N(.U20ߞV񝟹>e$@*&|úna7Dd2hwkgO!O6}}DD7Ty[޿yVj1Ya;llMi0{rKN|h#Q)%ޚ;,H#}Ir!yG@JO>?R fj,FV0ĢO'^"pR(dL("Òp.492na' ٧aܬA-EΚtPpHT4X onI=AA3p Y` I)ÑP:wM &R@ {8g`T)agDn<~t;O51i`KS~| BOUd m>+I>{̼^7KڹX8 !rse4O?3 dpKHNBxApvBȝ3IKhs-h٦Y'QJ, )! 4ti;N<7w~ɦ1Jέj3k6дY] 0BF=5'׳gQVE??\]D}͟je.^}p{3Re:~dpjڿ0GsV#owvu]tFoM`i UprmDL/GupL 8>^O5G,^)&'Wt.T|wDM8jw,=wZcv9A8i#O)|6,+"!Y Eͳe::KaTulna댊 5"9YZpAY2i9*17f8Y78eKٶEjX_\(>Ov7'}Y<<Ȫf&:% MVUΠ"NnD\rJB).B f*0@Y{hӈ)Y;Cxak,&@W\s';Of7>A½MJ=G`=NY]TYmu` z>?v,Y{.w϶5f+O\qFj.~~Q%p,5-L#}fgp'Iʈ+ C(<88|r'!·v*ڎjbnJa&vOv}1HP#LN L7"Dxc՝"J2M~ C%.8Tj'pXfȐ2Sws+.%mia$C f&""0 i=з8 ̺{Q\kYπJHj``]oZz"Q2TS֫Х!R:} MBm;cm9dxmb2cEWҝA_LӤEgt@1$DRn4sNKtM673 }ٝy>e߶p57=pPQ5H8}ז ]u SNW2"X,! )#X@a$Y&#7.;}bj'7|slzޭBBֲv&bMY6{֫RL6i-ר+~:|?\_)v_ּb/QziY-| P%=;gDC8XAޯ219ޒQ OL.'_ܽ\߽<}f6eR e $a3 W*1vBhC&],0 Imш %o"6k,1H8&1;ud]HS@XY^չĦO {6d̒./jߑ\ϗNwZ(:`Isfz(#[32Y4cQdҁyWCt~K!0*cq5YUƼlZV9gf{t $>խyew[A4^~5tN~cH|9&t(uWVDm/O!"܍e}/6N4O&HfJHެH9sj,`Av})D(+`s-U]@}|8]O t\ WvV o!OyL_+N|n'\^ IDATݜS7R9G9}IUM̆(\g|gLy>(ˊGM2b_ּ{o@Sr}%j_E1&@_yŏԉ@\¨k x]z;z7lnZk:i;|n/ׇ31!V:e\ȚFX$`{9 DwfPK 7$fuϔc ]Ji*Pl_ѫYJy$Rp%t2L#&{xfSPƯO-~5Oz ӫGK~mmո9.%KRvR!N1?(u]a(7??|맫Y凯Ӽ5d\GDO -R|e/m{96KZVJrdӡMAa}(a V:+t6JL`wU2nE CA 8c恅s&5Ԥ 96#6@"4>sdNZŠd30=HzR;k 03/y<;Ȗ@"lAF"H[**R9YoaQS#"NB7-=zJ1mz0)ɺw)5)M\,KōQW5ZPq$8ݽRIK =WKӥD8|QĠko={<\ 뫑3N@J\elD@9@峋f:6k GRF"LB}(M%U7!"(phF@>- ? f! ݌#7잎3x;Yޣ zۤizs6noImVmfF8\5 tjti7~~Jݗ5ٯ]mW[""#8uN(TJI{"Û?Ki"1=`}qRu^PYb>ݍ͢;r$׊(>>hjUuŖ$'Oݴ{ =Ppx8vD̚E}lb -İe?;YONAQJݖ _Z*0@= QK U5 D.":0̃eO겗Htio p%8wޥDTF ,EMؙ"{4DOB_> 8C:|Y0v¸Nút&:<Kα}y1깰eK|h`\ x ( يo^ǯʰȎ Nذ{~`ܭ/A-:OΗ`yZ l.SɆuSwH<(@d-ٝD'f%ug"`UrDQe`Y8l ڎt{ޜJճjǕc0viZs vBHwuzY/Däss#dX@ܿx6|[?/kޯ_7~o;rB8] rx.߽CxIa@Lр7P`ݞ,ˎ󾼬VUwu`0@Q0#lXo̿ `olG=@v A 3=}۹̵ЃD$DՉSZ2~\&f,ywFYqyod䔤88Q)  ܵ9BLBY0jb#A'w , ! רn`fȩbR5 GFf2s&"BM턎!Z$z(]DT+;l rpB`n`vЊxs$RbmQ -"]Չ!2.'>fީgu }Rzo؂B逆TŽcn(T']6{EWqo2m#Fno_=?%afQ&+#10^3'…zD*( S`ӭuFOK2wH]/G;)D73w`ڍM2 pf!k@lz_ﻡu@bs3j$*2x"ZlaQ,wN Upw^:݌@ _B~~~t/UW{|'爆Őq60*>$7F & n0l*TZ7GFQC~O̘ c\XB+G4ǡܦsLϞ6KGk>,.d4Rb}N"09:TSkS'̽ӪJ\1֪'ȌT c`${a 5.ilЂiߒ}20f[uf֪R ;a#xEMzL4 ygZ&'Ii}݌wOd$&38V&,to_O%'૿jwW?rzW矹m(˟CKE#*r\av]gj`T.PV1z߉m7(Eߘݱƛ%Rx3@/5Xll6-5`1ƛHxBE##Ra4DOGUASv},133d" 9Xz *I4 hPH0h6H^'7s5nQZ.A03hCESj1!Un-%lVh7-2'@9ݲwqw=!B()3!?NAjM|է<хW8i,IU'l0V`)m%"S'Ŧo^ܼQ);Pʅ|_MJ`X ܅w͙Jǥ7w@x&$$<ꊀؕD)B)ҿ2:\UщTʱ/Njj8{|{of∥#pjhUTkE3LFBbG,Wtr$Xc -:byv,W{ռ?o?zWdXk'GNEU.3lF} ؼM}Oi<]kDOqn@/ l^,]U*s>/nIɁ`U C03p5*EiR ":NJی80TRJ]^;T- ]<%^E?& 'ctt17R.;B׹ ]lb+A"" (dC*&]lp@L(#ivhS8fGJ̑IKbyYb֣wG괗J1qA⎏W#dHzâW5гgA;a/s +e/DŇ:nW:9:"CߔlGyeI+~Q/Hޥ-U,}½n&KoN̟FV5p" 1:! @ x[Dlqj7 ,guz 2aqRvUt2˭U4(\hy2lΖjPЎƱ :^ջezyqgf?aw}b/~3'``qF :]>Rb\x3'%iJ֛+/d hV@ЩQBg'& @{=nM8!]"Fh-,2Z!{ S :!j;XKc|u1 /<8' Jm_1Q:x̔0`I(9X' ,`ӤB٪<V)$0Bա-P>U *֜}Єn#:mMF9^Njiɕg@vo0^GzpL@)0aڜ? hf gGEk+TrGnZlՠtHnfya0ޭGn"1 GH])F)@*8b(ZVvԋ;Nᄉ6e!VSV "jdƘU[7/f"!= GCϹ2 F" RMt{C 4Gr5JV$X bŬ%!9wTrAtq]~R# )6^oea,ӊ$0"Gh9@DOhS'J, SAd)@C!"yfhWB!X " \.щ>8kc1ZU)GzDƒ&L@,=/OuWce+,N'b5Sx:l2DP͹/Z:&qWW9bϤȂTK1;38 F&~%d .C#4`D:XN!T3z0]dİB b>T {D&3UN_t1;T&,_0q?C-={f7O/&w5o^-N%88x΁/^^m8Z@Cn TsRW4_>( 6};5hEɀ1|Лc?ö+j"mC;z^ŵK2B*Ɉ@5aJ9N\Tr#.LcG't]/,: ;G,4 d㵸:p6\2X!&4920rǰcieg]i?ChKa A:a>"Cr/y#tKU`H2 !M] iqFnq\STy?ԫa-O*9QHapn`.#_e%'eQGzbZ9mLWԻctZ5Ry'IΖS&uUg~cN/GGd  QXE( „BR@P`N !%qb I*G}[7N`Pс CFחIKaq̚Y<ŁC]8=٭ Yi0xKA-Lj 6N:4}w58˨-9*niXfH?ML'EXtܼ&{10*LmG4h. c ъS5,dJBetyqP)647*DXV0^L`~`yRQP397)XǣȤ=]8.-3!>$&U2,bPǷ.ۻjwW~ {Oǟj˝=:<*1 ƪ5U"xr/Cd"m$lʅMTz*CA2: P;0C/BiNFd&FH~/"UTKz;39vENJޢ[#[OS6F1 ܣ)[h@7iGSKi?&pR +i5SG|vFܸpWU,t4eHYU3J,絮y Zrfm|.HP9KFv!H?}TI}\(o }lM\zj(!*o^7_ S_-i{GIVgWrC wyVׇ%``cAG={C0pItzBB>R zv,YtWjޯbʊu?yo QKhҔS$w(C6=uPA"2wžf# ;tGNj{ׇz;g{ȧτ X("UTG1Ŷ_풽Rzc^U&A?54$z/5?fd_>Ѫg|JskӨc-8 nUV'hH#!U+80U`Nm$})I)nXU*RUi:NUT:H]cR2ӝ ةC* Fք!g+eţLEp@P;ese]7ȝ?'L\7oK#Rj3*jw &RaUUUB7bX= X FB9`)"k8tQO,ękm Y14dTPƄa""7%޷Z5ؑlf*nn k|o 2@dҪVtxIT|bYzn*C䛧(P]]UZ&1BM,'*E#Eq@+"Vzs^r|{k>IuDCefw-|T:' _?WϮ]y8?|3n/>xËG?vl!XmH)ef9xz ݲE*/m111D2n!Ճ:w=,]G]G:VjQ^i*];"`".fdh1!7n#k({@LR-:K Ex^2R'UUMO弴?LS ڶ -0a91b% ]ڔ2ҙȄ1$DRS[QHsŬ\# sNdn 2)XzUa#FWӸ;(:6a->3upt /PTݜ 8qDƭ#sTfX)DtF(w][X*&mI/m gnDt2Mu7~s:!xБ0KMWZǪZNJŰ N4A@q:Nu/* ܟ, n^c/}}ռw! v?7)~O_>=yʡdi!ˑ.1t:ӫn-6 h QܓteFLD^UplR>[hಋ+!N*ZMF::ijGppKB&.sMřH ٴMӬa "K}(/cOU5,/W< Qs|!>5lc(LŀD5IH .~[  tg%Q3×x-b‡ُy;g i.-q~4#Yu$e95 MY] SD3 {&*[teKd˗]ԃ 'm7®.]=?*[㮸Gt#vŔOTBMbpC"g43=Nzx?ۇ/<\,ͼ5=(c:IMk,6r@ ACwZJW;fW˘d{_~v tWo~tLWTxw8ȃ^$⃗a\-צD@@BaLmH1gWaXذioVf}cEw8U*TUQTh cA\YD9sOc\s)@td$8Ts@ ).qCfdtGri ܍K9ez:B&-0+ ֪jOfv77Ba!t xEq75Jl^5"#t,Fm3*aO|sjF'#@ɬMf"Q*T;'ê /2p(eYE  X^UW'.>ɡ41c`Wñ,8ܗj`t1aP# iEءNfU8MW8jk"t7ZEMqa>?xliOq2wPџه?zsw|_o|c"o}9 o;/gWMn>[*rnd]_}Ӱ9Ė+Bt!o )m@1 nJQ 0]<~y g`9Pz6ݒˡ_ :]p}3d"*n`R X),)8nZ]|f'ԍ0d\.ZRR-aYe!n1`Q][tA|?sow7ɼyVo? \?9!=İ}6bݓX1g3J-ctEǢɱ+e5>kdNSqEUcA&4dHXJ[SM]} >9JC󡉫TIk:LbU͝ ST P@]!Bw$ dڌP!*d1utrtj˭f9!Ę(B) 2hZB&f-^ܕp%~.#) z D,Ruj_CysBj!̚I5kAla~X` 2w2x g?xvw5o":gryxA@̤h,lhVerז$>|5aՕB6}+LF(FZh1,*2v5~DJkŵjEUD&ժ2)3$&",'.F Ԉ1XxK)Ktn\})Ǯ 5hn\T㕉0ш+2yMR$ᜧoڼzNu:~_ˮ B^ԯ:,b:jzm?Put/`٢@ҨZBϮ_>~%ö/tg+Pzzŋ++. nux돮iXm v$'gB D#S2xCwr][A Cϕ֣NG1~-6Cq )Bnǝcnn#˛'? *:=yc/ޗ{/~?fMn^WOCh*g# }td r/K(,a>7bϮ&HqCIJVSU]DMMDbhM(OcQ3}l`xĆ&3lDv*:ԫQ&RΥPGN!0"`8l4")!aȅLMk- RKIK W'1-PDH n7 9р)ȱ! Ft;rǮ0-S*'r3 lo2Uw f",SjZALLd-"Ҙf-@,lfNn\;坿`fbu=mlY-@ +__r=qnic>.w;g{THE ۔HWS72Z:#! Y2tP]32^_vw]ͻ{Ksw|;qοՋ^?=pսlyD$\S&gu~N؀h Op޿d e6PAcA(^M̌@4JKD1kqb)@`e&0@F*VVQ1D&CjU   a0;F@#+#gzz s %`f\[l5|N1-"6U{̝# ,v:9h6ȩq 8t 7'Ojhه&էCJL]"JΚ~)A`F)Pl B+*wq?sRz `{oxpn>DB2Ň׻g{fIpWwNN2$v~JdpW4nPs XC% wO~˽{཯nyW[}_.fq6rsDb}LЗx3Qß~}yx # iiƹFbhxH0h(jh1e*0pV{D0lņ6fl!|]z[*fX2T2ND@De`g޵k`C07eD@M[PA %umfqڙh#.qؼ}Zd dFZ^NC߷HNhE7lMx9RrdUt6ã/OWAykj\(H>C1WO^}|z`r$Xmzܵ#?g`p骾}Zex[׫%i`̜]J:4 Xwe(f`A {ghLx[tT~W']ywB 3R~=l4VKtБ#Tr+I̖dE3Z.r51E.M 1 ?)_ G~'11ssUK͌r Rպѯa eu ˤX5B3@D!1BDD1ϐE1G]G\0j*<3)c0 2 Fǧ:fw^hQ`2UDTպelMVxqcn?uLGS=\ 7xo g*Aw FNճ^| 8MB8 ,݋g"ɧ7~,"Fϟʱt2^|Yn4D&Fѣ9B(f(|D!I} u]ទ_dk?dռǿGz|zy-\-ast[/?zU'釒 -pSuJ/."d΂CoG{s[|v$4@teIg 6ٟCV75( $&B~~tG$ yMT9cax4!VU1R0MDKD@Hqa*DBJ 2GzCJ뉲R 8h .Z@dF3=M2仗Q񍨃b IDATTA Ƀm! "@rxuq8r;-AGUpTjz{=:9nlx Ћͣ-"w=ﭛjNp`Uex>:2b3D_\R*"*ʨ:_``F ݑcj&;!&»%yw{?~;{ߙ"|6EJ8aIQeYn@ԇuۺ5 BA 0pEx.pX=jruuTԌ[`X84Tt!-қnBʺr~in=4 lGG'V]Nc5:T*210QtȈE.Ca/ȅKAbjnЕ!RB@wA408yNm6u| BL{r[nvh-1zsww>pX#Xe5.ypp[zv 93"zZrT æBj߽GypnN:m*'Fb_1 fвF{IPLhQ9s[<Ҕox'2ܙ1BZL9S ]H5QQu"&PTt%K]})djlo&\#ԙs߇DkT#ӱkL,;랙Uul@";쯶`w/s#6pA"Ab[Uu{=dC2 bгOgVsD [is68<>yPj҉ږǏO9jfoXm/$~TuxR^^<$]$&ߛ&2r`]g2ׯnLMLz&IXsm!s1>mrc̛"іȐMOJmRLI!_9o8>x׍g>r{{뎈uHHyL5PfNU=T5k/zݫ y,@+\;ڬnDiQev*hCy0Z*RGǒWЕ%k)3fIs-mUP3!d?k])ճHdrrrJ+oj%DQ|om$s!1eżod_K w2gڗg'9&?N_t7lrp^T$pStL{8w?'[vG<5毒1sLRZTRY huAKJ:YaLM@LEUΈ .}})vے,A4K&-Գ1Ԍo8Iyh^~7P;y}<===p+FgL>6TCժr"5Obm$ zq`>VEK2RNŨlf6&/ b]BGg2Z ےk/|1< Gj.dh]z.ѣ{KK5M9#SH!A}x)b.cKBII+mޤc??!S˫|{vgޮ;2+F]Y_mC o}p5^jg}lvmϮ_|rxrĘM,ΛC%o(hd%v{`1;Vg15Kx2Oq_偺_ѿnQ+}۾[^ճ_>9_ѸlL<]Ig|{/M橙6D6f3p,@{D󩔪2ԃ71e~{u9^ V{*>|W72ZTs_>~ IH~/154R%Jq^m#O"&Sk$rS%璻1P` ! Ԉ$)?PM ]cFjߙ?_?RoX_~|sp0?~x0Y4,|9T*m޲fQ䲹rWp^mAāC`bǐ$LEŒͳ߾|qjRifս)6!ջj˚LF9gj?ʹG1 k8 ^ %H`QUZYLU5GmY4d -쎅a`fnns.)bZ^?0'ɏ  }d\CO橙E2N8u !7ōUFpfpSޒ.fVTK籢wԚ41IM&k:=4<=[]mێsodޜO{obL*3 *yy_,4=jzNBLBBij iy=S`hRC#fRr 1NӋ׫p2;yP(}4kYu[w%klR]o2]~cv<~/l;J-̛ɼo7R,6ܴᦦjLVیaOauih{u@a #Z9)M~{q$ġa$fP'R~~&C/4 ՘י d}N?XuR]!6Έruu$`frf*>7_x[pR$ &@BR5W%SP՜{bϟ"6!6",C3~[M4xsbnWdIWȜْqA]u'̚YjI!ZfdVzͽߌM"Uۍx,(1f,u0+z>HDiɪE/'T^)џ+mP b$Cy\"ȸY]u}dBT{Fl~}R̪O\84o2HM$ՆbtOB K=Y=54)Pz1Rc@1F?zҽk$RUf$FJDEzG>޶Vot۞4N E&/_ܼaÜtyvFzDKkaK9af"#魦*10X g(WvZy-) RvW S=~ d6-6,kͷWf=:ZܛI$ ykW/$o7})nBMX?['s7fE>lVLcdӫO/ΏJX ,Pbh),k]aZlx0o!5ٶ](F )$_}P;y`Ss)::s{̒M扙,mbg25%%] C ~bLƵg8PhblaH52~ݴFh)Zzbì*HOkV1rsm795|G5êo@Eu`T:zv}䝣<.x6UcI↩ "In^o^<}g?|+(3jIIb^-QjF$E$鑈4ꋏ^HbL4}ů^쬓DO_<9`}$:]}AOvG }jWmjb_Q3,s ?SBsiMKf! $p ]Rnw!J S0O7S2_y})Ym5THEBHC%0tpL}~ BNfG~LZl3^ȼPLw%RݏRy5GkCIzpQ.T<[YX3j,xٰ?xz}ۧK.cA<j|2jo&9HF<54hyoA]+V7ZX{ʗdhHCU:1źd[ܛԻTT`B&?wZ>{$fm<ÃI}.U I|s` ,X_{QDMIذ4l-CG gW_}DVz-Yͬ^ `+el<[eomv2qG 5扉Ģ `53R+edHW{)[C~<#%hBj8]UAlҤ~ۛVH .XocU3*Y  !vՖ\$Ձ & GR(j/~wҧ]oz0h Bx2Q_= vwph&adC U"iL8wfto^~EƬ؏˻?@4qtyD˳h] ."nWv%}[fYC2*Y4L0s "oeX3Ӣ,BLkxgCd&l>nzqigb7$5 Q۬YؔgZVfjFZD)jܖ8MސnݶkWG!S-zӋaڣgO\Og˚ٲ]yTG%wUY4w5st lrVJ65,zjfЄ ΈŽL :DːАfD1# "Ljj$ArK.%2qTV'% &Ti1 sjb-tdMCopI.>?jy==;=pY_%$ qndid4(Cx46R"X鴐^cc K2CQ5tj`sB1X۔H0HnwIăΈ}w$ !n[HxR~bd@&wE?\~uz&tj3dQ;,͛fwã_v. w4.A[^y jƁ"y-E5@&\JTLw>hV̄Ls0c͈4+'ı /$"$ddVrj.v_xszzzzvS$ew3fltpפ09hy"4L&>^HH7.x!aOTu?xS.ۮvZ#ҖwON9L2&!b"T̊XLmȲ)(ӫjV{Iί7gS^MW*vDtr #֌L(x!d>?V;Y,4uPTO^(g3Җu{g%%Ϯf%5"!3QȝwVTXՋgU.whP;OYya)v7ֵX匵l~<3̈1*3Hni@/ׯoYjf]E/͏g!IDHrMj77 ~c8jbf&ʸy@_3eP%0^.cU͈hv4cl-tk"R%"L& o}+"yyKD/onS:4-1SɚBD 4ϛڏ jyg%";IDAT[.7?ǹK3Q?1΂D4?2d^m6\t! [ =Ym6 u7'=!(h2-~vV%ˇϦn_rAc 5ehI;m+޻;Ou<qЗL3V=HbjkM$JjfjջFmubh10vm_Cy\6|3$<hMBS@hy4<@hy4<@@hy4<@hhy4<@hy4<@hyy4<@hy44<@hy4<@hy4<<@hy4<@hy4<@@hy4<@hhy4<@hy4<@߃(MPGָIENDB`PKF5l-Pictures/100000000000001A0000001B7F1F4B88.pngPNG  IHDRt<sRGBgAMA a cHRMz&u0`:pQ< pHYs oytEXtSoftwarePaint.NET v3.36%IDATHKO(DQ7&,XX vQ(6R6 eP0SdPj,H#>Ɲ39p~njkM4jkmEEI~KjfyYA+~pnḿK!eh Ldq 9<µC#p ]_{05{ŕoo ;9A]ŗl֍+-EIgJڞYo\}鴈͍q95lv#}nk(BnrҁVgduw|,\c?L4  nxط.SU?M;ܸ9pDLO+ao/@7Z47i~@/ *AőHX\ռ5G<"Aq\\XggRS{cC6c1LƛjtTb$ώUǗz}_X_*+{!?.ȑH(siBO6M[גSAs蜢&twk4w OXIENDB`PKFgrW&W&-Pictures/1000000000000206000002051568F9CF.pngPNG  IHDR&ɢY pHYs  tIME  IDATxYu߷7s[UwjN=MJ("v` yQ2<y `'ȂHNLPDR!lg6{ ߴZ+;DDCuխ[瞻^ BP(Py BP$P( E BP$P( E BP$P( E BP$P( E BP$P( E BP$P( E BP$P( E BP$P( E BP$P( E BP$P( E BP$P( E BP$P( E BP$P( E BP$P( E BP$P( E BP$P( E BP$P( E BP$P( E BP(P( " BP(P( " BP(P( " BP(P( " BP(P( " BP(P( " BP(P( " BP(P( " BP(P( " BP(P( " BP(P( " BP(P( " BP(P( " BP(P( " BP(P( #ly ?i|GgyϖP8U ?]އ33O ~S)/NP$*ԑzޅ̓UO=VP$PW $'I)rU UUW\=9>rzy E +O=>`*hJ,"*1F1N,̡ {AD',">ؓe,I(>q}׏.]D TQP`$IT#p)N Br;kgNNήWP$P#yz * H$qSC?U9[UUU* "§>$<.I(>0߽?5M",KDd@TUYЏ]?}Qqxvc:q ^;9rr|zrzڅ:e/>ۯC}kN**Y2DH DB0eh"XRqxk^핷|ݐ1Ty P(h_#}իCd qO("XyǮ5uhڇ`5""Bccak"c|E E 6/׮u d " PPQPPMI"ǾaE,mTι,#{24HYk-ŨG}>Y" /'?^7 !!¾q@aTPUvbX,v֐!B@QUP"S)1NDc!=RY*I(~`| ~WGή !!"B@ qu}Y皪jꦮ9 ! 0 %BUX 2O11N)z!2xzzҔ.I(SOãr ʨ΢1@h_Pȇ;0D~!%nzhr"|I@ @]<4Tu_TQR1):c֒%"cGG\ E P~[7o?#U B0**nnu)pZ-4z@PѼFR " S4Zg"DԳ'WNώʗC ~nݾ]:$|s{~,:X@Uq~ !jXM]U$hQ@y$(叢iM :7,@P B(9M118"!z啗x~lj5Z-T *ǧ~굳ÃYq Qfaq~)U/ETyc%D4'R$ "XUE欪0+'i1ƔR2"Oή\9=:|/O_x`'@x~PPUQPin ø\4ry!A@CH a.:eA oHSnS (*~܅PQs`UU͝gᑇ_,-}US]xTC=?';HBγ~s;/_{!Bp{UT,*"m.YKXcކshv@Py7z rޒ֬ʒ$۳r&5Z"CtvvS_~8 uݴmh=Y""DHx˗O/^<.o" ү?|ŗNOxr1^U}QGd^IQ\Vqw1ZcM]୵Ws;W  "QP>/&+("LHV 3/ Pʢʠ: `|8)M4M1!|@#!kg '~U {w{;M1Fn5HHd!"yO_e/)ia:wC yr|) UQP~_[WNDET$cb)DY13O[~sf9W7mTUL"D"d Hbad;wzEğg,P(Py׮\9}#jf>q>GE5HRfEQSLSsdB޷uU5U]ȘFx\S)!"a6OսܽV2"Z@eUEUAU@Y'qJSJ̉U1Woo~mNܴb{30 y ? Qn$))iJn^q:X>o,P(@y՗÷zO~汳,"(OA/ "œxqqnqicu]UUCw.!b"yy>enbrJH s3:40s GYfJY*"10O#'!ʪj뺪8Lo]?_>p !Q@8p)"w,XbL4ClYy:X.ڟ>ZrI?Uvw֭>[@-IGa8Ŷۦi9)4)[oB\ZH39UṪ G`-'Ϧň"@fVTe%BB\OѨ*X"*vnݲ!S *M]/m])7nţ~G p2IH$$3O3 j/Su}~pյGK@""!GtyI(~ %g8[oG\E+r^W @S7"9gT =8cmM)M4NzǸއCpW"2t^E@L:Q%QD0P59rY kun 㴞i]7M.m*cށn{WU &ˍ`PUVPN90C.j1 ygD'K\DB߽yzv wU@@cA @z iB98rZE $?1LSin!mCp!x59  |EUVP%Ȟx^ul/̈́ܩ`PI{IrS~ơ_0Z˃rad)bQ5w(zުqqC*8BTDPYR1FfcvnP9<({,k''WO;D%>xzӿl^Х* H'uMI5ܯH1}u][c۪Y.m D"4@@8ϔ""J ^)Miacι#"QUEBc'*(w yλs`^n֔c)2ı~qb yMp:phk ~\,mڦ";5_iqq)!B8R]{c]Dr۪Б aG2"b_/U(B/4N /(( 1}D SZbtv`FC;dɮ* $9y^,Ƚ`E@$G`TIकNiS#1޹*Ո~ڔZ<'zn]5k+JRa)]?n,yIUwǗȚivMS7Mc15?5V@]A`AԵQqYuftƘ·i<3!1 #{__}[$P7~8[cLw]B祡"*0DA!)SiXy:HeCDDc!*R~XLӲn~cJUElPkZ!E`l" 2;w*(3K7۾ay#U\.P5`5 eЬHd R~lDB U_(ιan{!4Hd ˜IB_FyI(KF/ݮow?| -al"L.pq~wpZM6M]%6y#+;z;!*h^h<@\7d@!C680}?i%BE$D$AN'NdJӮv$fIb.,i/pJw*mTռrfAA`oy('4 qnwf, ju·`ɀ1sꑑ'." _pJo{7n>.^lDt 4Ð8Y2mU>XrJfHHs5IQ yk Vw y8K(Ҿ5=O콹s*qJQGPBSJ8v4ICsIĄptt6mL℈ιiqi'nBND%ʼns88;U7uZGIcn tέ\wz\Βj^c)>Eڍ#ٵ?O{ ?s=#›;OV.aVQ׵]wRk>wAA]? Dl[Brc!{YORF4D f\ sOx3_zu{H7PeQդI48]cCb6b%W@tH* :{h.!ྔ$"Ҝ T5I$ܦ]tvcafI1oiGN\5ե1/7 IDAT MۂvKnikg< ;2dpvaE7REUx.^)1Squ]up{o~rR۶mx޻ӣu !PfQCGq09>Tqlf+q㔄e\\pp…iXV$Ig1Ɛr5D|˷FO"!!o٬?{.v 0ON7MfM8N}`Bu]7Ueg lX -R|e_I:",Hx>3DUT4HQuL,,144Nq6u,:*UUXDxCf _ܹZ*XEs z8!TUSU}SX^{{V/Vue<% "ja8EaceJ[os,v~'N++ {ڪifUw{DbJ,)&68kG}աHB'gs_"s֭w~㳫W]Dy[> qj -u>Xv~{Z6mۄho7v~ImT9f܆}d6o#OEDe.R؏C4hJ*]8ien{Qii릮ބ9ȳ_AChy9pI)a6a:*X0HA_~'Z߹~"($0fްfUSw7oo޺KG]7')SYuUMm!xs 7Ԥ",57} D'Jp[|/ݻ;ڵS1c""L`&bn5BPf7~aZVrhl(cR]-@ Y&}3 gQSePII38M~qbS̩dх bѶ!T}wjꦮriE<:f|=P9'釱;뭂Vu9K{k! ߼~nc_\v&"MIhJSvz{-h7nCgN"`iPWMSU!uΜ_ԗd{X"[e숌9zT$c~K_z*Tawygs}NO/7M,)XBL.<\*K)Gꦚiv`yzr84D0v#)<`{g E@\*qRp?j>ro<9n(d%g%X9 }L1 h mS-ţEpvR뀂r|UEw _ "r\GYRxT%BŚccƱqPUֹ+Vˣb ,w9I]WM]me3Ƣ1<=*/ϧ<:l($9bamCjkgʓ1Z ΢A@I _o^zuݶ/* +WYLqbqf:Uf?Xj꺩އ`-1j2/rit fYn)#[$XYr)^~ٵӓu(PQ|vukm~?X5s=zW :@ j30~a&BTu{gɽs1;yK޲"*c+hb`W yP ʜ8׹} }Sxɔ\ ?>\,z^o*TMUyB1d~Ǿ-i6|:TESȉSMȮUj|e1 9 ,!2!&o~[ry"CD6?\8 -(R,Qxδ{mmS$~xW~~y7Sۭz>|6{C^=V呛0"ݰ`6E[UyfX}AYt,Ϸ$X8;osЬ!1dϡqFI!EX,,ڦrmd6DH 2o'~6}40} o9qjq9o Qrށ!Ȁ0#nۿ‹ _ɟ|Mqx;h-J0R&2wMTu !Tg6:Tn2Sf{:j v!gD~C֖*8B2cDwyw9=LS_~pqZ6o YkrY֒3YcsygXrnм=-'_(70O"W9w||?)*HN*D)# !cl`|EļW$PA/?_}NUwn8}.^<ڗp8 ٫%4n r^XMU["눺@\[VyUs$@^9`Ua`a<*9~aY/,m[7zQ|Uy1d^2h~m9!Y6(qaݤi֙s!3Z2w +!ZkC@M"q1Nk [o* =33 몪CBpc$n}5'{샏ܧA1w/ίмN;!&2d BDUH9%U-gsc= <" KHbaCw|v|vz*?;usR} yqNM8\T{gwOsfOzB#9BMvc,8nf(7[./- f;x﫪^,a6)Ky(@MEL,D]?vz ^,+gЅѐ9d8#cD4h* BC㐒h4M"K/hwo΍7W2ǘ)ZÃPcL|r鋈  :ۉdks)|o{,Lg siM[&޾P,'R)rB$kM_.=蓈ÏI(+_yZ,[wkUKyT$89ޟ2qn MV!1wm}4\fadm9\,Syqca~VAE^:koD\6/!Y9<UVVNIcy:8uy 6MSվG1sXXoYh^Q X$ R^X8 /gB, ;c*V()M)UP{k,<兪?\ngrmYs|ːe"y.2>AuȒy  ScLiJI묵&G}?ȏա%~_~Qmql6'W&p-@J~` '͑vaVE6U\(ϝoJ  )$y)|3FvIʊ.6s\._mE_7nΆP!"̮Hٿ4;; "*y!Fau;w\u{5Cc:gs8 $2F ՘GJ XRS$ ^xŋGΘmU]9筳9g͍pøv !/s"yLosj8M휓rORYJ9 QGSmW+位:FSqL6f}ͯ]==zrvnHBpE@"k,3O4#?jHh\1"!# gUd8M0 rh*!y\ʻi"s@6ݷsL2)(DȒ$FajٮVChW9BL)3q"d-tSNsH4 cwE].*xVt7RR@)*R@phHcRkpybgzu%kL[M۴U yo P5 kª)iJ0[\{wLt $c!$ DACDAEDy[h@@:% Q$ԼH z*>5w@,cq¯{ᣏ>tJփg)7e}|rijU'9o#χ~J=W@DNeݮKq,E[y1&? av{9bPe [lb4org)|IY._:|;m޸{`Ѻ {$YDAD%"뜱cd0 3 *f1>4u &XG 񚇧,!Bhc3RI6N4)NS6⬭|UUBg#\,FC-zaq'q`^EWEDcs*=Q燐tQTlJ,"`:C [_ z^ Li_kYvߵ.{vN^R]=I#-ƺ a1"/B+>`"%B+@\gR@)K߻+3yZky%:+z^kLj)UJ-9g@ !R"ͷ.w~'Qyy{ios6 3<S*n<9v *` vVA@gH^ Di-C Bރ- L LDV]\\T8'~2:6qC?}J1@D5"G)Z Q&~ZUi/KUv#4$SJ!RL!XCT @} Z*jU5ou]T9t:]G)]?g)S [QA#LL&.fʥ,ku]ypo9L0l68L)ED iޅq4I!Bm amKcj&~ֳ^ѭ7k`R(a \3)uAH+kelJK- C#Qx8Z?WˍoMAPՑK\Hu]y~SfEd (2232RCX~TMR8 -URe"xxx_ ./7~ Lr" z&)NDV[׬4"r<~Nq0r Dh`0fhT} 䒑@Y PrRHx`lh+OtĜ7ZpOl"ќLfvzl1lEo{"q軔b F&؉;k%&\9uxvG㶟>u}R Bb H@&,DbDf-]А8"Hd9 fy>Aj)&*8v3^^\n7cwNCsg9UBL%FLMNNF]y"IR[)R)& v9Z=*UNhp#]eAR`V5\T&RDQ-S;]׾*lZZqӥ]K#>rߓv7~~NfȳϿ.>/p>'K+z؝cv3M8hsޘ(u !'l7i""rZe^Oi^֒+2Uלv?yɛo%p84ØR"Fг-o f`)]|.5y!2#"Rd2٤".cL]qH^eY*,$ hLh)*._oU*UDOx(Y|sfb8f3}OAD)I@jpΫMڒo\^]^eR?{9 8}αm*罅imWX> \&#ZJͥ,r8n>}7]Bb34BTP͂IAEs0))>a);*f9 ,z8#1fd8owTǡ !2e$5jyc7_ے7QݪZy]׼5Jw)y)6,w灣 q*f"gkJ)9ZJCr)Rka~b$3"ޯ\ j-sϴViX9K+Jq6oo?t '{=XEդ|'7~(>pM}cY5`dvBѯj1@'˱zZC"wç~vO]_\lVS#pꑊ\LfU^x _]fRP#j3@WcV͚\7%*RUJ^i>R6DŽRj#qb,j( Ɓ*bKRLjЪ%*5iil~pۡA~Zhz^Cφ W"[c Z72V0nԥ u9编K@~RT6}p2B-zu]r)1rJ'ROnߑژ}6}:QH +=j*"D[Wc?z7CIx8?wcL)圥Y|4(8 7@0+a-9ϧt8]<2bH|b> 60@ dhDIcz<Mӷy6Ru,J`n7mtZi\ 8}OpI5חg)y6ke>-nb;L]D}}ɳ/Ke1chћ؜{&f Z] \M̴hEJv*diվK}]7M ( !ykcB$3ڛ3PTBF > { )ߪ)l]s1Dԅ.8nֵ5bJ1Ĕ TQ5kEDV]YDbc,-B1cbsmB2"r=("B އxqk#}J?wA'eq H" 0:|Z.'kOp]mK}ЕDebvs J&,h^vc*5>Zѣͦvnc 32Q4#ij_V'kZ}6"ZjnϹ4L!"}Z,](*@ EAsH< ZA:oUp:̧q\2Ԣf֥4Хu]9B *˒eC@BTMWZSǁTmr.~v5اr*;N뺡ﻔHkyZv#]J]b 1.j6ZuYת99F@ DD'Tni|艫xnLm, 7KTxk/?S 77/_|kvy?g*뺬''o=y~yNO@O @TJ˒~?vD̀=_&#Uk{sݧd^xys+8qD5oӣGcp<O9Wn T B t;f"|^~~7 Ą) AS"~3&~D(@bA\v伊W)ETEk.e>ΧuQfVs)J0}?8}ץ.]3>Fȡ?3|?+~dB/zioowѹk!p4 PH΄LU9ZD?d,K6aSK JW0?}#^$]ui@PU5`xyy׾abKx8?i?~TrYk|ѓ D9ijlB} h p/U'U-ε9k.d3&C2믽>}R2/W/ovo Q~J]H)u]u1-OS*`{h`P.~eqbadBfB!ĈSOCGzޘ.SmbP* fdD$h"őuYxZfF* .a짡+ 6 Uv4'+f`mh&=73`@D#w)?|?>}뵷>bJb-tڝq.8E(FHoH_x YBצ73?>'ZŕCbNmPQ麮y95RMĐ`ӏ80tC)C Ȁ6 56[@9>Ѐ y@T Μ kCvˌ]?\^\<~_ںR-sqN\ [UU%r9t}wg ~Q 1jܦ l絸{V@)QO}NԐȐ}t\'}R}GWJI?;~:f6qߧ~..F:D fmy>i!<~&i3,ROL>gåETTu󺜖|i`>-t}q }f3j f܈TWnw"ԥqH]9"f**y8u]"3@1hrDfuY73w9\I1DbbVjYJYK'0Xidq܌iRdƖ.нմ%V0UH pp"*K *6l A mqZۅ:;yqSd2d1F"4=bc[a)"+uS111*?U<yahkj@FDjS6K4bX'>4Mfb2p<_{tՓpoO_n4+uBçI )Zs>O"zuqquǾt{o7SS"ZZ󚗲q>J"400SyM}w{3q~cdFD~U3C" Dת 9t\NS`BlUmtn3)%WSLb *U3PqC"}?Gw]ժRJDR~4C j)ěaL0)v!&rWE<С& "0Op8q8R@$\OkRu1%!2#3ɤ[X^T7 jH @fD ]=Yv By摵 BPȜsu-774}+oo/Pr[%`%<=bN)r ZEj9r^7SَZW~u=9ڶvLHD4r`1T9' !Z Rk`ni*ٳ4}}90gǃ)W,AtwB .Jɲֲ6D`D1rR|FI`-f^ox _1j?%WRt`3}ץðHf h!!DG2 }F"&Ӫ9 :}]J: /ZR!Q/./U%\uYs]4S?DBgns{oy4~k]E"4גYUb2@5p342!!j!`)r8 fKLӷn/`jUt Jͽ?WJҟRJ!&'k>{_ۍ{ wrLb9pbBb$J۩y ;.YP!.y]>___lV3ċqS :Zk$CdOw1#؁Ѻʺۻ\fb,1~3D́c`9a$Et2Q_T_,W?9nf˜Ú欢?|~HKCRS \drL^<=wAx?nAsҌ2NW?Γ38շkWŹԵB5OqU%&*04c p DR?TφDfd7sͬa̔54$LM|RFħ+"Z|<K]k_WȕhZi>!suPΗ}S߹}]R}_9_Qhqh`h-8ݑжkΧLqqhK@{ȵc^ FLD꼮km/61t<̛i78!Ei4`q t"xv23&noO4ϳPմSDLST3a$VPHomw?B:շ_8N"ZxRwC]b!ɿES$ؿdj 6AKypԮfWh 4Q9X.TABPbCK/R{قi):dJϔu3W,0pL@UT́#Y7&d2j-5g>W.FC$\ U Tz{wc4{( K_})PTJ)ۛn.ƦQH+o0kEje^5q @ ?%52LDTU̳0"*ZEN˂On~Xs}r׏.ݣŁ|fjA`jjJ{S)+b"ZTO)5wЅC(u960TE ٣rQdԊVE?9x?n<#"K)9K6@nχCp,LMg&bΈ3H8<6EmkjZU"RZuYQ? !x:-i?=^v>mMx j&Zk5r8üf]<}7&1̪Z` BR`DP뱏_YB89̧yn.u'""ӘL+ 3{XѼh72A#x!Fdt=3-TJPVEԽ#s)kQ@* 9BV9*H:ǜE㪳X @5wqbjF (q!EbZۛwf0'o^\n|otzClzGXs}ID@fab)D$#;k<™`qH!0L1vDbf˙9 E²Zj "%J]cDw%`bP"k!'A&kW_%"DmB (_u SMJYg|{PΗ|?f1`ɹՃZ{o vatg?(a[7>=^C@14F`ksSR:G9sn4nۼo bi屟'1ԟH_$shuq>kCXSdr%1u@wPr9WZ(X"9ںf9UpD@)bS~~ Aŀ)P$B g5{PsIjk'cStꇒp_}~Zk.>_ˋC@4&=g z:|8 y w)y ֜e];Nˢv~]^4m6e7/0)Dqˍ`U}TkޑZu=a(9wC#Du)Ĕ؟̀րNubݝD8#T9*Y\nS3~o6UE68ӻQ(0N 8/ #j,#x {'@ TDH.EU|7S:Uᙾ?" 7 ܈^M=AMkn4"ew7*:N%KO|<׀\f@[h'k8*b x D ԵdQ}oo<%l/.R(>/7pA*n CCUSUU˚,!xlǁ s-bf ֜i]O8/*bpj fl~wka~cpqLY` &ZkOIuY;;1"u]@ļ3fd&=obDayy=P=XUԺ4n0uT)i0n7a΍1 qܜpNlbCl!0^*|ovY[y&&5s8FU)r)zVOY6eٍᅬȁz&}AEEnQaH 1`,J`Df|<}g)uLԺ]_]CG[V=sCک*oWÔ\*͈T?ᾲ*RֵZ mm<9?w6!hʋw7_^O}?Յ ~mVg@ Hh`RK[.s^|8kHH]9]_]]?n681v}J sf-AՍ 3Pi20hEEs?v]Y!N8(0qG"B$ D 1H5ݸpv4K j?ooV2:{| Uʚ=4?}innksz @kz+jVK9Tju>뺪 Џ}؏ >xƷg\ L3=.7C."9EWȪ" q>] H`N7Gɲ,+Mۋu뷟>8ieC&xٳ%cFh! }¿%?\!{i1#TAųg]? CŤF ]Zp,281VENeR|Q}fnv,faHAĬ] }N ĻߍUIئe^>8n7;G:B0O hձKdp_l6cQ`43-V֜i^(bUbw)R-y]bg41q+?7+B`VxU0e$v,*-"˲ RC?.Ҁ8j<5΢ 2SW"A3u]fz^eLab͢bZ5D;y涩kZgwwf݌8Ć0bOQN*7q^p<6N7M_{=&2Q@}b^Fu>)sΈ?.<9_gCץ7gi5ZkUUq۪N6ln 0LC 4 c?k܍B*B ͣ ^{h~\NpLSlFW6^h:"Om{3,Ǟ*\yuab )p )dJ e$ L 8/u^/^LX.uw<u]Iյ=$ZO "*tyuTQ1u- DM@~yLumB\5+RuY缔\J.u1D=.") `В<tx5;d #S!@Ůb$d0t 39|a|뭷RB655-Qw깆\r]tioy>>}[?r)0S5Ϟ˪*HĨ`Ux8U{?n ,_!;%|9}ᴹPQD>8*/Rw~WkCwe>jnv^8<~i:77iRSVj_l =//.1Rg$35sϲvf`H*m&Vm4y]1\]n..Rh0S1U(@DqѻY瘙u&""UDjծySf(쎧7;0SX{ }in;ͧa~`f图KSw11+yMc9DHV6#iHr3b.Z*JZ1C?Lۿ=(Qj_ 66 ,e&h- C*YrOmFi`mwa]31]\la?Hա hb[Y-:TY&z9+yԪR"K)Nt>q`fFªjnn>xb臮iwq놾kB|]Z=aW` ruO- YԼt9w}t)2q Mh nK6Zk&2ZCĭbz\K)%kN0Ub:M'O""wcߧ& bc35Z6EB5wU9} &jEjε,h9awnf[lmɑBA⋗ĵ4W.l"{'+23Q33BNMqD F@b]8Fx|QD,0sJ&E5$&"@ B%vD 681qTQSd`YtV~O㶋.WOe__9nFiNS mڶM1 =fZJZjժsN'Q!m] /HH~!y4T/DyM\ҊEBaܴ!)ʀ0bY$fwNglY뚦m`C.Y"TԵ1RTNU&n#1H̸!*JHf;ژjKRJ}Qu.$zVS ⅀GL+O@CGھ6 .dt <6U:%Ej|:=ﺮ 3!p#3*fEՉ~ L(HUaUW9آz4p軞LLEیmӤ#33Q`b&B=FFԘd*%\4C`)m(0T#5Ri:R8F3)n3؂\T( (7>a`hVKf%34vyu0EXi1X`vr^^}Ji+ B r 4^$(/0R')k޾}sss%F 8/0dw8=y2J$U?υS姒bjU0_nǾo֩ʗ;w3poDW0**Uk-r4t>Vo#(ٳ60yY^EmԤmu| rKBUox+lYp@t}m(PBT@̾}T"2s2[50Ъ"!"QrT"]]1)Ĕ!@Jڶm6]QDDj@NT&Zr*UJ.YUcML)Fb,N%l4mtd'|A!ĸ:ޘSS !vQfĨK)!k4vQ8 1 @onvϟ }ߤ oߐh͸vl 1]E@ "-1T%%N5,D$&Ԉ-8[K)o"7Mjfgw% tuJR9M<nڦmČ|q̭ ѱ@jkgCn03Q11x51)(2@gmK( 3W)"՛ۻBjT *`NʨJ),}G~O7!5!d Oxa~bOs&?p8G$H0P'?g>^3C/l$<w=wkۦFyD2_#Kr[ݚjfe"UO !RΙCˑCq}a̭)~cwZU Q ezzs繼~3fp~""'q`7c>ܧl_pEEUkLRJ%y軮61c 51AVStȐ!ֈb W 5+bVz#w~?6žIM51%N8ȿς׼5 ~$CUPQ3|^.qRJݲs񲇯 w ̜V*!WC --b^[ 9C t!fERkas2[c|}yO%i_0Mja*nbhXt'"T͵,K>M8\eO31\7۱(<}41p@"ټ؃jRj,r<]m@uk$Z@/o `MmrH Z jQ3Lv>-~n1#S " q ]`3(J|>> ݰG-9 &0bLd8SUS&Zt1Rw10m LXs=NLac"j˧~oϏ7Z?f2}30rԞJWM6Mc͕ZJ9zYY۶ d_ %t8U.p\vlRi8t7M6)01LQ@15wEW2~'"^m 1"S#;1g-`(bH1!@P `Zr\r-֒p<c) i3Zx:ޛf}DEdWa/ztUuߴLLaЕG;kNaXr@ țH<`5R@t_^}sC|<&#H"{g)Eց R8\P)R]9։ )b_oqɭ"wsE aR@T֗k׿C껖C@"7g!b9a|ui]8EXs)l?v3vݐ߼j6}ۧ4ker4 1#@~`REJyô{8Ni3twMC1 d MBPY DL8(S0UIKR9|8dz BUaM?&rCl)0$)EG1!Zh{;9C̗0(G!]>3=™ N9x@ w|fu~M> D'23 TQ@T"rg :RYN9% @MJ Iw9aL(| ː"8B^R) @j>M绻yY|ݦJ-o9DMQ XjsVgד*еwjp>5֗oq߷!Ueۻlص**,bjjNUjƄ]nx潛flrַou}׵]H!DtzPu)( (o5ѪL6QO:(rw?liRsAؑ}ٍ Ar%Poub(5W1ZLT9i:E͆v܉RtA"fB_Es^W.Mצ{Cpk Y@= f _<:X>W&"! mBL" j@\9Opd{u}{Cn&T.ID.rC2y,9RKS۶rF,』9``LA\I`!8OL=QZLiBvdb"%7o_GfP"V1ERr5?_\.XQ@kc0fT_}Snc:<緷w.:9V7ŪTUT,y~q6۶vt Ðāؓq/".[+Li=t\9T-.K=a:iۮ&Q>.! E8 4T@20ZRTJ-Uk5 ꒗0N eB8*nq3tfZ#h*V^rLmj&&%"vr@B뙂J.B^ϧ5b0Fdt\ 0 ဌ?/o_61,KQ1Զø,Oj&Gd AzO<\۾lƾ);/LDE(Fx@xuJa1/sm5@qw{; C?nF&rlp05#~2Ϲ/9k{ yl"|fO%imD.\d%?ڶw1/RJE*2cƫ& \_um߶-!hO3 wJ"T`5zQTJ*s9/~wO!Ő9sа[%,b[M|Lr3SdhRs*v **j&$3 &ŝڼdpo^\gn.rjc]E\7g ]|& —T:B}q!\k=e9Mw:-/ ZqW)x2$1?ϧe3vmCsF) c#D=:+1Zzx<4}Ӷm7!p H1G`ccFr7EA| ɯ8f/`RKKET-i:y^bb@ :/s.2tf>DDٌjUbt|:DBx= T <̿7QFRUS 1%6ym#d`fM .df q%20B&S[0}w}5zo 0U D{>o2DLUMjK9)z}}նmU1TVpaÑDҵaVb# s""uYB35BRPSRב<3on>ñoD5Zke"34t{ZJxj) !!3#Xw@USy>KΥv;v][=<RW'yDFKEjg웟}q= dW\ 'vi=֗bIshY*,jwk_juͣuĈ"|8LRyŸR|Swǒfw]?t{P]]s_!*{䎩BU3PFZ;MRE4K>N];]׵>ke8p`D MP 0Z PBYJ)iYkEy9Ot^rHӤĴs۝fnڦd0ێCu͆ Rڇ p+(yo;"wP0d$neP&)vR8@dkZ.b$Y߾{x8MMkF.6[{p!r@$j>2 % <(;8q(C\*HlGIwY겔D9nU̵Xl4Tv4{ϟumC2P1!ķo!G%byYJ/fe|4x{v$—gT^׃;8p#T9jv !p%Uj\Aabo)>zGr)Spss=}6N|v1s'dF@ \sdx^yc 68v)#3 {|*k dUbĢn`-F@@]ieq]r^0'T@4,Z3 l7C8.y>Oy>qjmڔR"է'ys ?,>BѡӦ #1+fj1iCpLDer9P͈Ў4{L\uVBt)")9ZQXq5)Rq܌mC/eӗd]?DfW>&0*_g`e@"޴*Trv 0A9gǾiڦsk H{ʣ[963 3!T5"\Ӓi?<7fCDfFa1S c4"3u^h]DRUUT+hM Sk۾Q"VSkvfQ !YPɠk(0 oܞӸ"Z`z1-۷wBLtAok1* (32rsO7uWJVse9olEY{~NasNC|#~+_fw>/D7MۦCV@@2d`ߗ,zJDkeɇq۝ش01NgLw]+'AD |p "$hfH j5Z򒋷%|4:JUa3CwmR(wA#%H]8zI% V@!@յvِI5`*hȐ9&rBh Upg}.0o6׾4.\"!`eD3 T̴*˒4M4)1oDBdB68ϩmVR00*QC#"m~luVh[E}y۶}S hfm-bLj`[N!?/ G{JಱuH J߿7ՖXH~: b0q&EDn?_7gpw{%Cv]~F@iG;W \y{w"}Ϳ!(pdvW7B)f`H&P P"K1բRjsKxyLa/~YOt<?c"$! ' 0ٚ)fv*4*Rk4awN'蚴:dTb8 IDATftm׷ E)r =Ј V1/tl]DnHw擌o9+C) #{K~{j0t6GkB31:DU1L+ h.UTs^b``f1"!jJiaɌTK)jVUJo^w{y 3YJ,Umw8R,aZEMEDOt?7ۼߺK4VDŽ$.y%<_~Y_RԬֲ)|>e t& )ps ǿ})Bfvj;4m6]L!$fi` n#&>0s-2Ւ;f;vC۵)DOĈ^P NYcJ "&iH!I<iim xaԄ1F9x;qy2Xc8G3| BP42 Ʃi8ybHvPyޛ=u\~kÙP1 %Qaa!uASKԚZC@$j>;s凕Hl?8/"EA 2}ouOΘx ),붭ciO34/1BUu%M{2P}zX7!!x<:8\ښ\]bE%IĆ0 1sUdYw OQtkIl>=}zrxpHſ'O뺽qڐ(՚ U@g<lxsZ@b/V~K@}5=i&ZkÐ 4lwz U]}TE AC`0k46n=in붭+4Ycr9$ QtMW,IR)fXvNꦪk$ 1rdfTE#鵨Xګ{A$ӳL۟Wͮ[ziiȱd1@"]$&f XONOWlN1Woݺ9Of1l֫zUb_䔻.w?O[m~" baI,*"Ui˯F¿W=xS@A$aSΫr.f)Km#AW_0lCӧZ׵JhGK p""DFYSc̲rJʢfܤ./֫ 1Ѩ  rJ}d M+W=,,χyC1,?;<;;rqp?_ݼu}UB!VCbKN5T]rgYv>` cT7DZq$4D? ԕ0Vԫ}j:X̦ɌBIE12R`sȟ*`9X@BbZL))kހt2_LTQd"*ʄ1uݤ!Թf!hV(r)Fm4m%5eB'e(2d+lZ'-GUUscjZ:*oH ˢͶ?-~ PC%8zCrӗR[~5̃wl1]B ÐRΫjؐ UK*) lw3y-hfjY7Mw㶪Dķbq.R}b„XZ|͜"*9iR^7jLFd<"@8 D]A #T0TU,s֮..`/ψ9zm>4gOew^ +u=jf6>B)ZqO/HQ!k(x_(3$gdRc42MCΎ*$榮*4MSERQUQb"c`pDQ3#./%'MCj¢-yHHQDTl><::lZIcS JsWWx4LąǼZDmມODa/B/]V*3_סdfERoܜZKWh7z5>>OyR"1Vanvu̓,(i\[}mu:"`v# R@䝷 \^^l6QۄXe`fy6, gY.WۮoֱnFf#S!cP T%%BD YIVuLTUёl]Zo Wi&$⊽x)۔Tj[c:^Zdh [&k9aͮFu]7MUWрc`*'!z/q :$9{[)Jf T$M3@U)XE"L"TO#Q9?Un78L3"sai"d<r5jؐsN׀DULDM󋓓Ӯn8fmJALYoT=/hRã/R3E 6/Jן|mqxczqRJ˯F§y|4ͨm XcaHn:U~~0Z.Drvefb˫+8<:KDr6ăo?=x4O't<r0"IYS2b 9_\*MƣzT1"; v/D0BF LbL.vAq@9gIrѓnݘΦn,MSUUBޜDHm%U@1 l۴!!*}ċ "*3.@Ѱv20-wb FЈ10Q@~U2|@TcEXDQ(9ְD! 9;?.f&좬s`P bqqqq摈!`')v{ !ܿw1"j\$y YR]݇=_\WD/ Oa/"Q]>HzW#SyGmLjR&j7! it]nf³,yN#֯ZmS2nם]d2KɪU0$bQ#4D?2v>G#P\.WW'''I{C;jnچ)pb#A&ۏ댑(V1F"ngGǷ z~;_\ >99캺m"!{MYDV&˲%9bIQˡ`ȮU+m~CAOզw,fٯ> 9! `fƁ$ J;1[ꦭBUU53&1e`@+BD @n~TړSm*VjD5-|eut29;=nwM])S*8LUru͆9,la Hfx 4]ߧݮ*sTqoEob2t:l7 R,ÐNOOvn4y^W+33INrR6/{B't5n2/9b}7vm*'"09^O~3nͨQՔ23Ǫ+av'.ݺytMf1:Xc~ӳãGޯ I TBH @ȑHf(d/T/~!ڶڪ !T!ol9 XM,']N4UU$d^d}FG!|ЧaUMl8I] Ú@)2Etj5Nަ=8Y?أ%I r#VUtpy"O1t|tt)Ʋ.ik5 T<@eI1 lTPTSM}~ uɨmjU3"c L욏P|k`F(@imC43fEjLd`ҋįFm;MԾ;SLS#T3hH|[.$vangq;`,ZV}3@S~MUE)u}u_}\xFTkKLʓ`2rL{_W>ݻ}Ǔ0A[-ך/|`{|'u߸?N}do5D5Hu$}?|cx7/cL!6ʷ'e,)1Cc)#ٟGO7 FͶb`HșD"` XU0``=[APY2 1e; ˩v5]*pO&UAꪢa٧b|!@c%wd4UDP@U|q!p=<%Kn[G&LÐdH)CBE&b*Wl:!kP 8`D{!(Ǧa:0+a˖U!0@Ռu*ʑ<\/..SJmF͋\_-11a5f_}z=^9a &3Lh>?~,H/j$|df!>\?7$wlzml6p n\HDx_CC`5uU`6NcU[]xd%$IB(R^wїz"[zGO.'b1_bf/=$L9`@آ JJP_ b"f`9eʎ|>L! CRSFb$w+(Fb A@BR1(Q- DUEdrtVum =E1f' PW&* n&n}m M1aH'u SuVPa)OӟU?;?ӆ h>iO.ʆᕖo}3LSDUU#ў[c]K)v;;9oMÙ}0l>X`\_3B3.Q`"P$4T3d՜Drfsvqg>3O?w./q IDAT9˖*U['גDԌZ1n>L"Mgˣx>f'$iBC~0{ϟ̄1ԡ^R4g`vH4Ͼŷ$6`$aTr?QsVCBl@D AUtbOƣQ Imɒ0sT!4)!%QȺRxF*JȢ j義2KFU bF3DȢ@{ftSorOG|6k"v;Э6 ~/$ C:\,O?ztRx9s˯F§h|WJDQݴY톔v}^o?~T1UeϮ5H%℈F˫eJÝ;Q8@Բe*fT,]ִ,en:Z,Ba2K_/~~ulv1ɩkY]}5Ì , CؽxK2[.>³ï.:%e B̑:~zbL6#,FDb_X0S\@p8&4TP=dHy#PAt]>9<;[nm=8U*T8 q nD@Q,-CߩMLk.5ԡ!KB Xz10Tx2R_i=`+pmy@YT`2iz>e';D gLnM@jzyv]7[7 Uz?H$e0H/g4O׳^'Ta^]j>GMjF§o3 \ey2}wYu~ѸhcU'bFD) KPA<$/}'?;>.О^Faݐ/j$|VFX20s]DH3~R?1x4ٟ#xBtzx㰊+Լђ"̑x|MlC!0@|L3HTWQp[WLᨮT[ݱPK""b`mnuݓ۷?nә)Y6@C## "{Zc/zulB(J&@2z@h2SSڵ LG!)_5N+raFY@ ?lS VH1P)qC`s\k$ ^r]"9b"b"S3$B1C FTR'1p٤ b ˫t6A '|6< 2?sxmj*aa19c"YDMa|?~K,/>YIʞkN3-jxX[~5?λ}!b )X`o~~SWn1fS:S 1f&/\-ݮkQ*k @YTc b?G0K0.R2AJã^b"#!Gj9uˏ?@ (Tb`9f͒E]'?ǷTL$b1)HLXCH XQBipnOۮGwN?<"h U {y2!x`PK4 9 )AijPMUU^c)AM iVp?4 PxF0?/8{_AŊĥd*9gbM':jvp(*"juXۦD!<0ӳL?es+#gg<>Dj!dI?n?^?M&1iאsv}j Ѹ=Xs2 i<"b.fľ돟<}UlD D5C<5Y*ʲvGO8[GMS!bHQ0jߧWݓ|2() YRdHN9ΛEժncdnխL&!!2R6ߦdB;$`@SdvaHWQX,ݞ@^أ HLĺ23f/墅zL9&T1:2xNOGETҐsmnf:}:.Li&T "BaBigRfI5ʈ T L|t~~cyt^4)%()dlwޏ~ӟYG^W<CbdUS˪| + `Y+\3!u)ԧѸmQ۶tà h@FT67" 2%Z "P&`bVǓ&&R2;*iV>+.v8 !3ahTGzuPDSNGb>O"k! =}z5h_>~}C/j'$\ƣxvZW#?#w:F-ѳy "Cv]wM25͘Ԣcȴ_f9d0Lī 4A)vcfMC@b/7 d !/9XU!(XD x9А( BR6|ɨ}nڦt(#Lcfhl!VK" Č φ@ o"Cs)mc8Xgs!"D!Ah{!s/p_cj l|VD Y9=Ѕ_.) M@Enf3_̈HU ,fsJi nvt8^w>= I̻DU_s2fyPg4Jwm%1қo~{|p|-8+?G#XhDD^":z5ӡۅMa:ZUJUm۔f6oqnj@% De`olUC!; lwpΚ*!%SDC ` U?y?'F9IZBѴv][TUU 3ױva@ cqp,(^NNί.jҎc:ld}b`B4`bH0G2Rt)ѡE9KCw.xΝ|B**6 Y%8pC 'B5: ʳ>EALMNdC5 ?'ϖݺ3m*jR\V/UUnwz13Eu^כ'K"Og%AHU '''];P[-""ip-&?3Y9sK #X?G|6۟k?ѯ=X T/j$}7w1֕ܙSlw"zvv~~zz0mt:2\uBbG4o[,oHȀD15`P :N Saӓn߽w5jÍЀAIJH0`$u۟Bo?4[&IfXBȁbcbU!p H F`flTDIqv}1 [ٙN 0G yg1B!䜠bro'YDJDRM3kŜ~H9,!r9y_k j^ f2h+!/!,U{)Y T !"X#&U5U`>85Q 쪉 Hǃp?%rIPXW41b~ppж#f.W%>~^ri9KR?Ow>GE;2sBDw8~o]jbk˯F;}FD~=TS])-ӣ UEwŮl6mvzvB1F&3PK Ꚉ0}Tr!>D'42o?st:AȎQ`Cd#X&E`@Fjo٩N3 b&E6FfF"d M 6㏏N൛7߹Y BiP5Dpýf̡ҐDE3@JdFdYM fOS2b>=XLv}?0 lq$Z@yFـ̘ iV\@!) =u"Z.>!M6P0 #HdjID%FUj$wDpvi:~Y@p0, ̌ƪ- 0Rr9{ /ܚ C`P*q` CVs@TAUDZmD`4h8$m[A,J$PB+ '?vyV5paE|~7$$o20F)!P؋iXƺNNNOgtS7!Rҳ|6bkn*mWWuӥ_%|>ϾR5jO.+֦#&:y]}[—HB;|/8|?񫪮zv6*BդK~LD֦~X2>/Mct,EQ[&x-KSzGW%} 3DX,㣓ӓ׿( H82"1"DC .p@D//ݓ㳦Qac: `fbEQ^DN *RP#$'c5{ò|׊a5yxogxF1MF/ꕚ:C\*Pieu;w^Ő "2+@^ە&2BӿAlm_َ1r,$y,2#*@ "QOONΪGׯX24pm&r'oYh[MeuP3': P.=Nk530QC`l\L&Ѩ@e@1PZ"N61 " 1cw!3b21No #<b2DC%+zۦs.Ȼt*I]+fNx!`r99$KM__W]Rٽse|V}'tL]NN*2~<3M'E y?hz><8Z.OD]]b,cUU=^fUdb$"0 $r_L@#rhuF5c jYX6kōd@̨&*H(OFef IDAT"B $@C"""HTU}ttl̆͛7(<@ $`@@SBp *b \e(h($ êje5PIɀZCtZU|Q]&_B0u i c^n7!37c]P[h Bp:s'5{ꪂ*2$rD  ㄞb'ffx0!3 O' Դ6uUU|.0f& 8qʯs -$3Ot:$£l)҄זo _w, Hkf9>>\,˲F1"UULAHX._~I`vy'0 :@r+|"ZSOy[[AECj9Hb>NLD~%@~N6 $0PN%ij! %݌Ԕ@nZo}k;;[E3TLZR#'Nf[(! !F1:4JHZ)]s L4C/&Sk'.XBHƣu"ƢL&gggۛDA1q&YJ>6#Q|1ffl 2Z^c ]׉*3jn V5EB͓C.ir\ `MK ~#(4I`mol͍THȑyN@PAMP$M]<8x@ И0 /yr- _UFV-9QWj<NOVז/WAo< u]ZբZ6m{txtm{Jd.cPhHG"6z#bVaqN\*>S%$ ?ɣ'dgg޵ %L-z2+RP Ag%kڮRtX5U,dTFXI?9_pp`D"X!_LšIpF5o"\/SGԪ7HrE땓xXETڶPbb Pˢ,r˲DrK= !0A2 @"fj*H0D$&ԐWw@h A `"杗`f9k%,OwkzoV׈ 9tty]ڢVwb+CU[YR~k~wa]Ҧi˪iǻXEUU]%p2Obtm_zq2 0NH8}@jj{w}㷾^̺5LtvIk!%\Xtƫn:A@3[dh4gY~,i|DD1$/GÕ cȆ (Ik DtUw  b4UUJk4v G```\, /ČLĨ)y7G-uO0 l L "^w ղy|2$-"eY|o _= O#!M˦mw`00TDi[!8=>c<O! ة%4 !|g"C3h<" Ds|Pݏ>xgg78LU횔e 1Iғӳ1kE,q fvmNNYQĩF  P֘Z5! C ``EsiQuj])Y߻}s{<I:1lHA}@[! Rн&`UśfJ" .b@2Q#լ`PX0"A2p~ݝĐ+al4CÀb7jcB bG%ƌIkk#b9s~fL3$&5ٻ'^y:sA* &i5@fMwn?ɯ~^>!2gI4ufղzdwYU~CE:t6C(*w @1C{GZɲ܈%S0VGvS끢:GRҶK6'k"IU5Fs iDxWے%{A[Qksi:`D 5#1S#IrC]DbTM(0oV# P+QXfYgH%^p^qlD1,+0/ӔЌLLq6[6m{u"!9ɯh'dz2gV,*t]w{E~Wvڅ~UxwўhMv1oտ&CϠn|K=?p0\M.U4O?\p0 $bsmg!;& iݽW_{m65 L}H`$A3FNgh~e@iQQ k۳Ǐw ιN"9 %{l (9|o^mVU_{|5[[ 'O `UUA9( J0@H@V#50ˆfji"ɾ@N0Bu5Sh:fM4hĘ#1!*z @2̴M`!D%'MU ='?3R @dF{O9ȓ.do!!*FB hFS: WlTQ5H$ * Q3/^ H.jJ'M'[omEHa|7H4ms~6sm^ajڶ]ݴw?o޻],9veu7=5 gO8]q,.>uX0`mIUhˢ-?X> ^?}#`ydws}M5 â`%ƲןKJDGwt}T@ +Ϧ-)3;U0425M)UWcuQ/6 ` 0gcE.%w61B{|Ɓ5%ĮK'?w^x& ! Qԥ[WNGyU9 bfxKo8S{"%3q,)$Z,igeQ =4@:A04@` 폪u??+lFDf EQrSRIXV#E V!?ycȫ#! @{Y^ 7S # N#6"dʞX /&C>K&I Fˀ^-|ٻr?B֒D5WMD@M|1MM]͇FQDݲԶ$.{QwHUhw7ů2%kZ,_H˗r3FOr?xJwxʺj́_;qϷ??̃A?r?ΥXVeUAX9-PUtm-n7=\|)0p^:$ 53+i1锈܎tж~HUU@,_{c{{bկO7֦j+/}h0V3m\=?kkEU]#pJ,Dk"PgLLW}$DՓ\ `\7ix< %="@pH TEE8 !/=쾶 y6>дT@6"33V2c#foF&T =VGuUl 9r&`3%S4t0cj^r !!ux4)$P5=>:?:>E!9* `߁H:I]jULO_Sx)|f=t՞&J&4 "AB>\eіo _?]D'a@jf,щtt:)b0(#sR9?wIE.{A~h_^FT:;Đ-M f9ɩv\4cdjb1 5Mw|yƵ7w H Su]+w{}"^[[tztt|nmEXf珟޻ߞ* :UpF@@1G&)C|ĤS*rY\I 4I}`DJɈٜknF009 P.X@ٶ˺WCK`D L*B@5~ Ь:[9cqfkQb%:z+8. dB"sy| >=:zlQmnnlO<(u]ݱIUURHmj}o~Ŋ"^.9Fx%`y*^O-[NU-x~Ue0IҺ͛ʲ JfZ,+DJb~Emڦm'vo޾5f{(0"uD \2kd^΋܀"fj(+>&% lh`ra1Uh@y|Gdb7\a.S#U`"FPFC%(n20SI3QP-umZg1_(cID"Q|>Dl+&"uIR'?/銹sz/:WAO#>63RiW>tU"7O5z%|!w}?x+§ӳ[E9!USu L2~tlWp :]H;2Z,oЋ*yߵLƓ7o08r!Qi/εod4Եi׸ ۮ_[[XThf1d2 ZQK1fpŢ˄: @SeĘ,$#&@CpPHR %]Rq/I5)2JJ̪*`< YUNDȥ Bqڨ1y$& S <쵦"L#2_γf7mU,!Db?v@l>;;>O$ĖD""@dLD$I>g?e/@іۇ.OzkS `2,+Օ[—&~~=E,Dlq~>;><}:}fFĺjNO&"E+{: -uv!'7D r3A^Րmӣo"13&5hL>| /loE AMRk*DwwOO767o ̦ 8VTTԴMHwfc' IDATR'-E4 x<%CC53$m'FH~~o8($]41Ds}VdUI5gyشS?Xo!jHvHjHE,ڮV H 6 BϏ20Ad5300 q'(_ ؗA#bwJܴ@ ãywtr|5C, $uۄovH"*H M@U˷/K taIgm"!}zcyC0 {슲mZo Ϗ~=UeYM]g|q.-(/l כ˲,}]םq6˲i870Gh(i7># đ:"Խk׶nݼNĀ I˪I7ǻ+_ʠeYXRC|VhTJkk;/+nĔ11,"u m-pZ]VMt)z =CT LFˣ L8B@A 4Bb2324`V0#T D'-!6Mu], DH0ut@: !8?Uܳa ^f&@nUߝ.Y9qqΠZLВx-7hŢ>88Ï͛7wv6 TsAn PD"ԁG?v< ]\ϳNx}uxX;?K=`eeF??|%<^ĊKir<^g ƈ*zv:{>iZe4M,b0zO>ٽstcba) z=y2`8,{e`%fz.\;i[TgHT Y"۷:"{SLUR\6|׿:eQjJشl0Я5`ѓɰM&^ي4BS#y@Oi7dd4iscs{kߋ&JmݦV) c`̢0`5t%W@򼷃n: J w,㵋dJlfhRj 3fd2Mgݘ @vx6ڻLT$M?;UJ:|_y?op#S=-fFZ}] q?|[?󓟼s]" z!^̗˪cbYUp3dWEY tXĢk C7p4z~~~D767&w3g.Lzww^~3x#!B2 Ͻ)ƣk׮H$BD&㽇M'nO 4PUGww7 {"n+{L$`L1KcFnߺ5[VXFHrQ!#A`<30, "RRc4,qHfHjK*e}0sS%iSfW86TT 9fJ" AU==U5[!(z-/)`Sq uDA77ȥ BW3m]nj+T%I4{uqt.VSgʹ3"O}f GzQ??r-MϷ?*"f1`u("A׵m"ⲪceTd}c}0dvTū2,G"fNDDBxMF͍nӓ?[p2Oz^@`yDضMT>yo~3JK" 9u I͘TTo?-b}}!g7*bHkׇat h1ijQo`E0D0Sif6[R=w|h/p{T%/O=Č3#@4Wbpd%@2 $sκ3&aX`Ыf4i.q43PsWGHơ$,^jl+0ZDf"ENsŹ7cLE $#wwU}t2 rqBﺚ D-%T5mgO ٲ̋M?#wx5?pX-\glCZҭNϷOݷUa0e ֵbz[7ƓaQDʜALJǓDU74%'Oګ@̐ h4" q GM[/ݽpXh PT׾\Bj kD85HYW_? c &PQWnǢ P$ټwa kn@d04bQJvvR)L;$QC6moN>|{pWiDZb3f4qwk!AR]! x1@O!$2Z1㼶͗k# !BQ&*Ĕ@!!_ j |zeADTBD0V>̐84L&fkeYG{>PUF@qDewo)whѮkSfg?o?Z7>z#ň͞#>}{25\˝ƱXq G~[]~+H].NNB.u~(Etm۝`]@‹ДTL$vȐL!q8M㍵NOd:OElٵwޝMopd@P ;9Xt7n\wkMއoյ§1PQ']Jw^Yi~l,hl*@V$ Cdkkk|͗G+loG%S!&Ubfb&(oPDN1.Fv šR]G98'(<`m+"/fjr(Ljp@pQ! ϝI\baFۢ1" )E zSSQ5Kf>gWz{=QfggղⅨtNOOϾsxpxyC?3yژe^ϢV~fgWl^swbGoe\o O2p8 yS/q+˒-"~bQ/NN ,b?@`1q,x8x @@ -<2D~ MUzA״|{qBAbFKصڶ֍~7}o|u6aY ڮktT/~_Ս^, ,Dۮ2H+@IEfIUtgsD*H\'ǧ@A @D @ U0d1@ ݋ f;##e,C2Cͳ:,֎ΦG T8P DMA*`mŊ>2`Bphw1+({RٻDc2 .;|YD*UUs/*o_/‡YN @EDT5mηou+1{M~zZx:R>5ҝv7Arn[?O!pD RZݼ~MRbfSO|N&vvb ]N槧X"Y MjTc^hQkOwwIE/V1U5*|b@]R IUSuÍ͍g7v]IA;M`L`m2o|}M(,T&u|6;}+0B4`( -9,Ȉfk궪lX bY9NjsF&EKɛRtBϫLSc]XylZxF¬QT#pjBpWG(ϳ4~TzVgiB 'buXDSVppxx孫76/%!&deeumsN;h`hf @ j a}c3vjуbTȄ콼j`c}|Nׯ]KV5JbxBg|Ei1 b@T@cF@!CK @50Q`^` ߦGI}(ZY GYȆ i@MFtp(r@24@3}2 $Ȱ&e2UUlg{pyyR DE`DF$1`0ЀT4IYD&u]NF__{tf9ųN KpOSGs{=C:% ޣ6`}?o;oz>>?A^ j:ձX_In5EEQ3(b6+WVW&l6?$LVOcVbBJ룈lf .?-R7R)Ui>kim@"XB(Bd)F' x<;:<{o~-g`&@pxtr<._LYQ*KRR3 ͋~gkx^<7S4M|5K{%f::( L1I8KީXE4"'jEW%P"_谁 9QET1F@[d511$mp"`@%v{0&@ hbFV\4G i' ħ 4 3o3 RD$n^Z|RBy8p4 C30 +ӵwպfow(p3e'FTnsk{"/܄8̃#_j,4IQT]ӨcuKBhm0T{t:&/oNƳ^[53`iM'rNrE(""2 z?ST"F)Ih~ݹwHԼKhl^wX~Ge)|(:)UUyxxio~Mf @&kc&''xʃBDɳM gQ7D`@j116 Y+㜈Y!35<1E'+N$5P@ 58h@$TW#"[R*h8r IP\'YbbjQߏ05}t٠L=12"*!CSPT;i3 AN=f~:xV(yxFVDf{`"˺ÿ,uJ'( Gگ*X_8T,w;wo~5#_!|?Q;x卵nCēHQd8\5q8O$FC|OS0^\/ͳc-[=*>/y{,ofy㕕v[N >gwl^&!z<tVy6͖}8::|ƍV@fؘ"e -JC.Z庮n߽j++˝[EZ_dJE d"&U5][[Lwn}{0l^v8A 0QE&g|[xsmc-IVn$Fn EQ?T͌ I3#] `hf'$FkQ1S,( G6_e@Fj4WT44犏&aǤ!5v@5DBPif@qb,-˹iGHUTIzU4 7ԉw4dPtʅ!)G%LbPQfz0<ޮK7VW4gBW d088-?{}1.P390'g_zƚfx}}nT}.>;}|>BZr4,hW6UZ^< ܾ!A]L\Ϋ*U 4-2 I^"$ ^64E efjҚZ,ffe5N<|mw$!(l1I8eUOX_|e}sJ%& έ[[IW.oyDrq0>5RS|yHEj3NGy+3?bsTH DCb`aMw"9xLM "0R Q}*XUj{X뎇~NrwQ5* XU+6 M&k# yhs͚"y1&x6ſB "Zxv|4 \]nSHه l̎jX]|G= 6Dwg5u'ɯ^-&Ƚ 1['yNl>d<~˗[[3aA,lLs1vut|48JVh-L QĈY]Ԉbev.MӃ[wn@  &jVc:OG`y["DP l<b0 /_z΋VQwww[,˒GWR@ YpF@:b] !2F1yV T4SdD4 !% @Qi>-swVbUb#c ?QV DgIPQ@ĈmE\w|Zs3UHMEL&̲=z= 'ctcWhyS&B2P @DUu<98= ^Z| pHDWBS#rofYg8C=~]1E%2<)$y3^\0 !ٟ}+;x>ge5M郇67V[y^kK<̧UUr>cL*"'twt,WVv)#s(nchD$?aHTghyyu<,3a` ,&dgF˯fRQ&Uӓ{ɜ{XZ[ f~ɣǏzkNU \$j6Zhy%BB< Uġ+,OFY% %yT `S#5].48тpc(f6 $&PPĜ7kcUdj ( 6I4Iyi66D5BFHŀLLMoHn54@Cv ƣ㝺76W@F b"S+6sH`_45BdF43Lfߺu6NQy⽰,g󋄅Vi;Ul,jG_Ug/3Vn`eYM'lv+irnȀY. O&Wռ*.CIJ_BͲ]v~u.Nh^ 1%@d"23~g="z??ͯ--u٤h]ozʵW^ EQX߽sWӴUuH8=~}nQѬ_|Ƨ~u|͕eN( "0ƨYI( jV+ 5CR&YJ$``bC0QSP,8-!#L,Ue&lf@QDARY4LGfo[ DCe׀ % (UUu5 Ԍe \Q @XV;ܤ8 ݙj>/vw,\~}ie EȼlX(_PByqRQ0ͫ;wo!%gD,ϳi;@4SmyR5ߙXQbe_IZ8xZdICF XYU`:rH̐Vy̪JDVZ767íyE'Zy%R7ڑ+ Hj4oͯYzW_{ݲ,?H.m^n)?xh{{W7jݭp.ڽ^?姭gv]2ZdIMM* FNL!L2)2C 5yF3s3舜Z^12 "h (h@H=94um!4 @M[3R BiXZ_6M614/C3"iXPeh"#Pk`8 ;ZQ=^r6=[A@c% beݻ}wV>6#8eA^팉q+K[HFMA /_HHyIRVx<)ٽW-4mFd ]h"`bhhGGGI(ncRZ ' 8C-/7^|xp`gw*VW.+b4 `6qW_}%3cjmcdgwoS4J=8N !AHdxǣx0+/bٟg_GÝV䅗n|ˏNKA=Q޽-XT2(6V+sTvH!Y#"3dw03&C3h@I D" @ 15*P@a5WU[vHEcTӧ6'PüX\B%ƪI jRM[ω6Ϯ"1y1'(jӕTh1ǏUqJ|bGU)8'OĈ͙>9kSGⓂ>|uvV,oo H AD۝U'VVT+4@v>%o]"64H~w̭d>UU鴻vBh%"*(Hp0Hd:mZfE8*HP@f2B9dkkk|gwÇ̼[-ӐG[[o~RREy|^_rN)!wv>Y}W^wYuw˯ƃaHo~muu9M21NBªʆ!%hB"ZJ|vTa6$V&uf(f30&?YL9F@C+k3`Q$R,$'`@ b`DTU ;DG58PATЂW +j!12gHH9 9VF!dyhkU- D5)d^F.+"*7B>[$T:Uyrr2N{FQd@eFD5(xkسھ6("Q%x<<} !"NDxQ8ϱ8g^ ߔ7\,z>~Tm%I'yy|tzpр^~hZG @wvG=Z_[zj23Y+^~֭[N_z:EN` uD!$T".\%!,FqYt S"0i{: HuYNUqbD,f$P4ŌB@P00L `Nh| {u Fp] W%M >? 5!fYwOkȜhhHfFMOm@@ >,)(X wzn)VIvy D%JjOf}xp`lEO'wˆ ^O}d4}w߬%~>Gβ4˳l>&znyI,LTb]WU5N!uE9/W?ë1$@D6dh@ HL!NXח/_ Ni:F''m=~$oeY"BKBysڕp\[yߺ﵋|y] 3av+"@HUmf R}dnNEB՘|^ZY VUتꘇ^io UC D H hĨG@onf"F$cdhcԨ/D*" bQbRt,6=3r;Lg4a"3{^虂TD 'l6=::|j.8)9%7t~U`T3*bQ _6D0;ߕ|'Vx쩙u W~sH5}>GiUt6joum Nex5**l6뮭x4ުv:m4M(i-* 9=T5 j fn|ci{?W ׯ"_[_A k㫺}ot{I0q;褪;k{N"@$nݺ=.]:dQQ51DP$D!٬R%`/@rGE1/@ThjQ"*` yj a;NQh ޓfduiݘ `&h*U@hFo f#5#<8؛5( <XKd{{x:/> )ypʖ~>΍`,or H>?O:J,E v;EW^LUE4Fu]We9 zE+O2&ixW`0N+19' ƨ$`kFm0:<~pmm?GKZ IDAT;wSrիYB ";wVWoyuee5$'ɤ,nc{jD˗V,U bj̈́F㓣/\_\ICB4F`߯,(BqUIDLU9IMkD`JY9/;8BG<`AA\UhM<@̀TѴd9_= e*Jд"zCDRf KKK{{U, n^X,j6"N`%DB 흽^_Ȅ(bNDe)(q Uuj~t<6s|\xmw"/, Nf}_#_e%fYyEvwԡ֐м1LDcWdZ%@(jj{{~o2ow{vڭ"OJ(L  @ HUm6嫯lj1* jmzk/uz8K+Dn !pHZE(U&dfiv>kru2f!G"qF|~|vfXPUdq+jzFE)!SwkI BPNAՐNX#s+)!5]D@&BCv'i)!}JZ ֨0|Vl==ؼ.r`DHĄH+E?8u@Y 4SӺ~Y~'E ɩp-zje|6Wha],̼w蹳HuKFޝ[uqoghw)̃TƺXU|>+2,aBMҔDcA88:@p4;M*jĈfB@)8&@,0fyE5"JCӔY,%]CT3#H( FS@6SSjz@ 0K`1*HtQ1M(c&fZLI4l=andmu U5UD0BEBqHc`lur4n=ޚL&/rcss#|d]b Ѩ,k&\p@UEwN3Sg~.웟8/[TNF<,UO%bw}fY5J<::[-YZi׬Zߚyk.z>fӺۼH2 pk="k^8wvu;EB h` FI^<{5|,K4EGzMgO?W^xFfoF8I d:iYcw_{F_YF q޽V^Y]_/Հ[O@`JչY ђ4Uf7ԣVQKNU` &!t,USm:LEP S"O+ &"juYG' WWBqS41/ڏ`<#0mtF(QʪF|wi6.\eXsx>әY7([y.= _ ?yb9$1u4ekj2GL$D#YcP%j6Fp48×_1{V~j`2fi2?^{K76S2 'u7oK$W?xpy9${NJocc-IĆcHG|J !(`pژy95^ 7 0726 Ũ@T-FfXew7I$+C@n' T02P .:!9ą/ #Z/Ba84J(zz76dF0~D,4s%@"h^hYHEÓn8-=F"fv)dPŨ4 &W)"jͫh'7EP:.(v!8P*l8[3ɋ̈́&$U]D9H>9HڜN'IH[{&9|x_nn"$<&<y4R}%(v@ZQ#*j[%W@&]q+7!Ї\EThI3lwf1G]l:=<<~ڳvTS3kԽz01Mfi& 1)xُ7o'N}yz ߟ O!?aI)1dإ ,: 7)^V?+h{gL~Ǽ꾘/&ɒ +@&ՈҚY$ fW3[c 4ѣc~oXU[_ݯ}ܾswk_h4oolouuAPUJGn~t¹?w?^ιsC%8AtÃCӿ/_UG߽{[ܼՋύǥrr2988qҳ=3*EL,.D4md3M=>EZx+34-Z`*{u`Vg)eV#-vE\wZ -khDKUAQ !)#A(Ό f0 &Pq#̄@eqXiFC.؜ lu뺽C3e4GT#ӓ||n/纮M*gdr<&Si l u?_z<\ԯOlz~Nm 2g`H?ݿKE߸zJ)ՕTJmIs|~rrZάg% 2h IS0\ ⩢B0tkhl*Z?޾qEU/e2_W^;|Qj'n5ѝãwθ7v[/z_{V)*# Thc03 ֕(BR)+֫H wqmeGvnڙT¶n1_FVCWNT$ YMf\"@i8. BV{HIՒ"7%$O":p$Ĩ""q3޽(1iވL/NOnݾGǯ {f]JEDiԉEPEt2p ^?w? iF}BuY|2QW C=̰[d~rr^Wh}|GʎC(~SS?HFB B߄- T"{BiCvJtTlk<&FEeԃ*BD;x4}] J_9ꤏ:qjy\rk_ᣝm![9%" B)I5L%TbbƒnV C\)v+{c#7"Y5R4*3eP55u(:R jAA#!Q[/:5f3S ,,gU2$o)hM@@1K@!$ ݽ|^(.Dr{{Dp+]A1Q9"Ǔ4ɍ kZW~>yx $Dn!jPӿĵd|G?Wgl$f7o^7 .dwwLzduZOgt^㎾J{A6R|#4R̶$@Rrɭ' d{3Ucz2OG^ʫ?LJyG=w~GnŝځzU| IC:`fڇC4>V !D\~[㱙z=/tV3B+ŔT,UrZ#\Lhv˥&TLQR4$=ViRMHTC9tdJ&; %2ҹ,M(w۳bedǷܹsnowuD'0&Ǔd9E2\y)‘Sʟ<|6rAa87OEX<1pciO۠Q_kׯ|?n/ӡwZ}Zͦdq2wb5'ڞfEgaH:dkB*j9q2:wUr /|( e$ |6w|1rʕWt:)̩ؓ@}OB=Uc]H@TkS#A'L򉈤b,#{txdb,ED {7,&%HU:{`j%Y>_trOTX0@kO'jWuוޓy'!LK}jQ/Η !!ܶDE]J#v@ A=ݾ}d|} .߿sK_~uwk{II~jELwFf@䐓@ Aq'j3/6 3p *nbV*CEԾ@Be!@x(ZH=|]THmR`(-MTb0:pRS9̡CH=+!yveT)v(a*? IDATRk ̪պ IqPoh/`'@6e.9=|t6ɦ9yPnSOHGSv4%Z\Slg#K!|tg?Ghun4b-7hX.ã &qyI,@cf 5:04d4qH*GTd3jHd !b2|_~w޻ܳT>'}/?sxT6T)&:/P=YYHpᚧf؛C5RB[*P)E""իUA̗6QR+!uEgיZC:8Xh7V  J +`b3G'(0Z#Tcf')?:TpP_;{ar<~tt嗏{x8r+WFQ&. *!}LXapZ~6{?884&7ݤ=E~jCړtcO,q -b9g~Fz~ԨDM9ȾQ7dӿk?͎j^t&g=!ЀY (͖}"b)Y2cxN\g$@U(d3G7[7o^zέ{b:\7XnC(5!MŴ9!֠nx[UY&BghQՒN0@. 0AGJTN] AZ+f9J)RUU*0 dZ!L@!FǧW0z@8"60˰P)5?P)>2*M A̔[ ; \\W/X@Vhe)$;@_OJl?~l"d&r2߻svh4j@jLǓrN`D6˩U!lbpu"QǓ 2Q{ JdGF1*HQs7od}#S3ǻ"d XpZfP=yJ)bM*%( .vPU*YW%J;kEP;6AQkJBB! A;?/j,jJDE=X״GיQZQCDEX!(0Td։.5tci/9eۉzTdc%#pFMg)TD1":.l9Fϛ5!Z0U'VfkQD0PdW/_5J _3/E[棃cW)C?BF(VYIoA8L#B[SΠ/g~E6jQg:2i)Zp&UDrפ:C3PtE2$<'J(kbz\&78<]?ӡTMRuƛMjzE;;l6fzZW^pEq?+6uZ1n#gBvꠐ Pq-ِ @ܹM_yW7N&3K-b5PPu= D֕ ՌU-T+.)рEՀoV+H="L%?{+}\z)DjF}굧.ɞ,B~QIǭw[bE:PǡV`4:UDNI(( J-b0+z=Φ瞻z221Iיq(ϝX.?x엿JSrgivmPV:zD+}'G;!]q]S(Ј!b`Q"F*"p?MwPA&PãVҵ Nhn@Wl4>fk.5 JkM&?@FTxH@46NK&BF0R_dszP4{t 5>sJD~(2 Q>…jfEDSZnPC`Mҕ0ܗd2~ Yk婱?X6'>mpHKO~o??Sq p^X{ zZ.*ܗ#5y&+̬+EKWJZ੎Y]",VL$(~Hϫ@2(#{q=)?{;;kW?8nH w*˷JeTM!;I7C$3S ISglQ4aRJ|.ŊdX $0jUi(ѯ+@PAQEi$kTRAHLRLj8PZvD$k<.88p D{dyGݼ9_yZש@A)!@_A'o!B=}oS 8U,4[kHh03Um֬!?lzrw~_5FfŐbG~%H$!dM_8Os1Xz8\28enO]R2g`ٖ>>7lg{s.tZaj kQbHR̬hQ[Aǵz@™+F\l٪3![$Z"(PA A[o5LQvTS$*ݜL&/7~3Wgk{<|tNwă&&/84F TTBܲ2+ 41Kp-?D"B~(23Lw'Z+=ń5##+a2DTh{VQS3%O !h=.R4[/*`_QªYgfu$C$e X#-SBHNjy޽+_ bLڸF&5JhS9Яրf^uSHZǏ^~S8eZ>5X;<~8HINɄ|;t~s6> bhZ/j^{+_ǭ!11XdR-">{wΥj2eS1NߤIehޖU#䖠 QC+njj J͌hA= /޽}gkk⥽K/\8[[ݥEk%Uu ÈQuLiAM2T$J8L$B ِpXY-kP9ɨ7^̨3GNE-ЪяGer<+*M M 5cB4 ąf BDJAՊ{de GX[D0h5TSCk0QUJzl,BF&r}BIS !Yyr&2Du^ݹ}n>z4rXiZ̬c$- 52;r\_2<$v2g?~<90CsMy :=Okp*BJs ,:>y߯q΅ JJz}2'^xOCbtRJsrٍTuLn|gzSo)?Mm )E ^8qvM2 ¤M)rT2&OȤGǿoއGGKOk׮{ŭ.IeY(+Si)!!ah"5 7Wv|("RzMS%4=-8ZBkV;;Ǒ! >MQ=D!fC(jx2 T0ŠP+9RG04i\;#6N2J!ѝ[w?_~ܹs"E5۞[U@1N%r`: b2oW?SO-',O-yp}6n^~[{{{ff5|1_.r}-Õhڻi&]Xz,c-;5y)IU٤ ̶wDAWah3a]ebyd_&b1SVE5[6*}x}{5\|q}SHJ7BwPh Sx<`)l qUi`VZHُG%/bk%Ir*E}{{K'+*gyrI(jf@->h Q!ZFb37:È0 $!)!;rUG7nݾ}޹/ΩK{))AHE tOBn 0\H}';ME< OI3Su8tw{fK>>ǿ7R|>Og'#TO(6~VZ:Ri f&ާ'XZҏZ1d$7e-%khbl3f2h.nP6MG!ל7du1ή^t7=7W618 D;-'p ^6yV;ޥkDV*ժu$ ZZ$%JG]U (t2'E`&3!l(L ֲ|#0#SJ3҉PI2i(wk_˗^|~kDEf01U! ldzt|<ɮA ۏowj2D^Tu[Sv;F?֯?j}Or9;+U˦e:RtԕXuW;xT@^$J nkưÈęӐ$*GUq"Ь-S>AdD6IC!3Wݾ{ZR^|ŏo988X,Z*J޵V&o4P$ PNHAZh> I'E˨ժwja*^k)fVQTRDRCu󌪉 &RbR$24; d1׼WHh (d4T20d. ĩƁ+`kzp~ݾs͏ɸ|viTpDx2w1îNQ_p3>/3 >]񔒔f2<ࠢfL4#w= 8ݓ< Yɟ̓΃<.RfөVjZ.mX,+Q꺮lcowl4u"tkj 4%h*U`4lyԧ;#QV*PTL myE'Eb0sd1Zbqu;wONNz",Ggp$8 q ys HfŬy蘒ԥTfUu dzLl钩!^1eD8~Ծz/1LJH9] hOfcZ%Sv RD5RWĝrAG v+DS5:}ՠJ)H^'Ni jJZEx"(-#L:6:dФ{Bmpk.zlڜb IDATM Pbr<}p_k_?wHdMPX'dz|<'ۨwWXƏ6pڂK𸷀n3q ,jE#}_̓{?:>n)z̞l^DPuJ~{[̤y?y-BNɐ֊ÍSRG"s L!!XT[uчxrơ&$#f*J1lF?C;K._ܿp歓ez/:DZ@#6,ФQ68V KG+juTF Z}T hzU gӠ^-j*Q?h "+RXԶ:-SeHw :BϮI#Ny* _/ד;&ӫ\FDK@,ME鬦\'qn $#Tzݻ_<g#ןᱚ$ZGM#qndKxK/}udZdhT\JdluSsHRL j 6D]'&QZU"BD5m繈4"R׽PfQod B5o?aF 4"( Ca 1Y/G &ݝQ3u>/]"pс"LXN<4N'aA_ߓ3WJ*-3M;x/ ɲ:Zʛz@AJ(Hu#{H[ۡnIaCJ&H F=PY̬|9g^(̌sKNxu89X:RnT-&1~_]ϝ;^z?:<:u)iz-Ѧ35MLNQB^jeN>q jMpJZ7Mփ.?h3.I1VyZ2a nsN3H\-qb(WAD)Ell X+e"%b6S3,bRUWU?=C`): ɐ4 d VaV.̄"Ee@Va (MQjoDR9<W:zy݇jQ5quV֘XMZ.(7sՈ@ܳ}Rh M0BC I#d)0c&@1% Q &PYu8*˒`Yl̡,k4dn̎?*(1ɸ BBYkyz䣔ZǔTUcl7nn&a3P2%2h2>DwyM绻CLr# ++7^Qy,}jm~šf˭mvŤM_xŗ^zv hWI}'ӣ8՞ Y "McnZḪgū9w߸X2$%˽2wFYZɌ-::&ԅD`H'"Fl&Mcs(|UY͢ټzEDΜ?s8ߺ@3(<(Hj "% ȟܲN<'&:̒TG'Tc*)&U#%!0Eu*%AV )ďSA&D ح!Q͌YLuSD!H*BaTLrWD%j ДƓ{v:ݧzj0 YDc2 jl0Fx8;4t6;|v|r#Enx<1gR,G)eW7??9=[OK'}ƟGx\݂=2)0[NYE(WbLBqKg?4ݑCK.5|[ ntd'[F0!IlbY_bfj2S̓+d}>\+r?zҥ{*в+|rDp4R<\r|\9su!P H(!%2PQ&%M0RN$`6Kz|Z{fVM=戙@&ƀ 13cg!`Ð$D 6I \ht۷WkWVYBpyH^4u=pUh֨)5M\T|>wFn:i'$DW޹{rOX`Nu/&oփӒ {ͿL&Yu%h;F+f^L;e\gt:2XGo ]X[{&vXN$dG>VN֞2fmd˵kpȲ0YD%8҅ý) kȰGz,kAXA$>x2pW$zwׇKgXI1" Y؇0 s1)_-%q15H͎ۛIMib,&KQMT(i"p҈T I5$^,2HD8aWw _cQy(Fj`&OuiPM./>:8\{ʓ%k ~E6Z%jowGEs6@#iJt:_{lY,xewumbA#;έ>/nٙLE? eB⚀TU_|SezW᥿X̧ӊWܾJL,A:(iS-uFE[tbu{SVV;{G\>s3+kk %x0vs2aOB _%+ѳ&ߊ]NH`!B,u 篬u]`$q/UӪTqp8il<`:&:Fe bL`=>¥h/y̲2 Cf&R&3B`3F F@\aHXBhX LϒAj߉f^xcԂ`&Kњ1,0$TUTh|͛{O/PRF!-$>\[T娵˫RҔbѣgy;Us|WVzy6.BQAK;f"BHhгA?pbϯonjJBMe3[sgDM &`'>f.-FG5AtxrEˋyY4<n IA!8lYp4Ѥg9A@AwoQT# ^_O1j"aR )*#+1WFɌaB.2*&c1X0V"1\D%i6h8Ӣ߹swwwҥ/u;eQJ hX ptU&n=s^i)ԤXUd2{o}Ms,qv'["ln O;kw_?:u$gi$+^>SJKD>\$dhR L1P\( :wVJ-2<}|Ydpkkpmu,/*I3 .MBݬy,yky '̗U0$ㆇ8)e K`ri-bJSWL$9xycF&Ru*&?LUׇG)|锟{i#|A9D0#q,2`(+̌9#,X0*b42J˲4 hI NVs,^YQ"b&r) Hf@?el详&! Qꭗ{R]#Fhbbٿ]Ե˫aE.n[q-NyԢb1|xP^uOlDTaccO?oo|0.&( yS[7E/·zp%|דɼ(Jkwdr_߀0{+ Ab&[~E$>9͚hrSW03 ¾<vU0fpƏwg@ c&3t六R= ˍ jbJ 6+rADi' zev˲u:a8͏76 F cHf))ybyF4x7}p4ֹoܺwo(:8|  RRRtBܹ3l!#)`St$Hy*5MҤ0dLQmA 7pϾYM2 FHR8 f)R!`& #cR>Q&iT8LD uD0{%8O#gp'H scCZN^{oE' iN _k4. Q7ykgt5es?rMesSi~/o_sͣiWgNNȦ~S҅K7gd:k^|Ս~L GuSVz^3yN#Ӥa#f̌ri@ vv 090(;2xOQB"KMNj}\-@ Ϲg[u)VB|p[55`fY&5hIYJ ;ZI D&KxeeM 1cU|7f1S{D:Nݛ Y4͒!.`0')/e^9>2!9}(Ml45h[}`tG fqF>q*agw/q[{y|>?8Oy}G{R-{e޺{8̛:jFȤ**bN:]'˿~EdzpID(Kẚ&n67V˲(6nXD0Jg}cX?W4[fր^;#P\˜XX杝q.8^:FDh1SoGЉH $m.4ꓫArsU5Th8\ЃՕ4WP+=bk?U7Kq.J/iYpPOO%ma #2ؕі`Mt[pH<>fbs}SeNY/ߎ1}bm}- 9_,Tm@ympE3O_y{ϑ{ʝA#!" ;ge|M7ӓwvmfcXDX%iL0X!2nCAD4% "!@R }n@ fZDRyfCl/랑gBd`2MX8N>xxG)٧?Hط*D ?;`6żfn(@McIST5|hVWNJ=vP7q:[ĨUU/u$ 2.d‹/y:8D??ɏ۱_ݚp"?څ/씥`kwڻz̹mZ;hX BJ ,D{wSe2FJ93Nݞ ,ߴVjmB2=zx^M);ҙqN hoRL)LjLLìL=C IDATH (oh꺮Ãg}<6Mr25irЂOnTcL S (gYOIX aquN6T6iUvLd1FW^!()&_K#be/fSQS:{b,w׾EYD…ʳ.2{'Oĉ1Uuut8y[w?wqqGl(OtE41Ѳx"f5KI5Gt4=~W_;\ǰAWE^)wn|>[o^~t^'w!Pl:Y(!ތRDamX{;S?@VhJ{-˧*ܞ939liFH]N 0.oteY4WlVfvBq6VF }"Aż!QTWӟzGkKJvw^%(`k0 xtr"7 ǯ4WĜ'qQNa{ .O.b@LD R%25r2BKLn}*!c0Bc۴JJSV5ݽu'2QOn&styh>̙pZTLYV۔E{zWxNm98*Q?\7d9:oJӒ55߈i22=B@ a]t(V fE(Jğ̙յjwVә}`UIUձ)0[Ḿ9?D~$/Fu{yj q'dc)xifzFP}O !b[}U5M7 ǔ&G@f4 ALkQ M$ESW?BhO_{nߺLD,L08V:.}nbLl%TS!D IEټI^%#)J6sZLیS(6Zҷ%eaҠ)A|B'cL()` 9x1o߻OjSK jRꒁ `wgo1Nyƹb{+?~J 8'WMr['x2zp:8Y$t [!'Y.:[b:pv!a0u;)P}K3QP~IrAAsލȻvjAohrUǀUA8l_V8#Ԉ $L"RFIXDʲ rx4c"&bqR"Ld$DӃr\5[/c}ݤc7ו[vGx.OߌW~x2XќE>͑if]raeYC$ABHRh>^6jasЌXbE@lQXkmHrv Yex]=VTu*{#Ts w숙M5)Rt3_D`VaSOj)Ox4»﾿:nR.>y߾N,)!P84C3, FymLZ،THNuhLPJ0tCM!&3&5_Ƒ(gB'9kR&e@cSo}tΝn]{ 0Yfdo1>GL˶)=mb4FV|VwvgK ]4c@蛧JӒV^iY/摄* M|^ = uO}fAUhU5=,uѸYN\*'w[j9/hvf;ؖ|׽vl-'i $&0ف&oVU$B[83q&bLFrfuYb}֖; fNӤ{w+RhNaqtaj5%!Iײ?XzkHQt8wS74EY6MS06d"%DURI $̞@4J V5`6&.%%Ld +ZBYۖLSc5?xh>_<;x3 jR:!u d<H~DSSS58Ocڞ4_MSYCȮ64u/p:,:>_W;b{0(Jx|78xOVH,B ݲK)4MRm-:G+,Adn▋J>KʾW]=[҈-ȸ} ٯM B)_\U2wWao}_Yu{3M 3"0 !Ezc̞l/}޽{wblQYŬRb //(- 1(2j$I<S,IMc, Iv R:bb6F%ADY$em$jxDYxr{vBX_h ] f@1e"c)%RLFF(Sl|1-~ǪVvqVQv"EmSbQ^xO2OKoud2QFH7n>|`Tx™^Lє4e>B7"I}yu>^zM_,ڏxI#Rzn<,'5BKw<²d%m]0dÙn ^A[Uamm_[~STu|p`"Pp֞f_uy SO_}_.sȑBU5zevEdJѐ d2k(%75`H5iPuFrSbFYrR y{ 3}TUDhÃ~t֥9n $ ffnlH;˩ AfP{r9~/Fֆk!9;sGuխS2UU=R6a!eA`rd<'lll}X47OzǷ.Rŋ=z3"R[vvzJenUq8}TPPb#%jfU%,̦j*"8F#h<3MM (*j !2PRKAQ%[̪w=>8]~sg>f!(L(*LsHHGx2bGVT<9d2;:`>~2~n嗗|"^#1ө/ ?h4~f121j!nTU' @!O\<3.blU;0yj.ycVϠ>HF3lM$ntHmղd w=mMЪf0Ue"a ٽ3[ç;{fuu:XYh>_TudQ5…Q@2 QLu::]{w;8Q.:/}gobmmuEaFfcue?jR`XBe+jD@RK )*:\(86uͫYWp *f39UBJߢUS;wƳ3O?sVYP0 Xc<"yl4F[`F3OHMӌ'G{_Ճ_]*%es"7oݚ /6o(3'RGsB@x@9"(bc@nTcR` +hQO` 6c,u ˒S"_q#!"̝-o9o_=& ~N'?8 so?ygΜX][+nQNĔTjM< I59)QL drgn޸*5i6sxk֦ R4ߠPyY;ATMy4yzb28N>3v:tXYL]0%?UM fISGweϝpN8ByH8Fh9<7Mt3o Ǘ %hvQx6dW \jDrME?w-"}#t c/VKdŹ/r0&<%iGZ̥&VᶯaSzCXU@B3gzl2EUUq4vMn[ooo_{oy{G$CвPYaьJF2$SHN"3͛wDg::߿;4\U*Ȃ@a|%c63#)()Qb>}QEȈD$bb8 0Ѭ)";S,KHU蹤0M}pOD~q.gڭ YePFȖix* TcLM&N7[ov1Z' U"S[58‰2Lp$dEL -N'uSU|Q4@PLi?)뒖7o` 5bmSKǫo X[f gKi|d&i3qKCdʂ5hҘޣ1 Dknh4+cR"::w+gŲSMɕ(\ChFO<sgφQl&~hu jcȠq*!]d )B ևf^o CiI!HI@bSdh4\5"]_@$r hJ S \{?6g?p58]-kIۦJ O&;?լ岔4hkoCLj 4;0XJVC|k_;U>m%aa^9Snso p^Oy̥ [XWuiNWNgu2g_&.)f2Ŭqҳ@NuS&3oMJOVJ(̵ \Rfp:7"lbfT344ᰚ&Ԅ,'%:ar2,gaļk׮{Iw͵NKcH>W׎2N}MjD SfinGfML,Yk!cϿ##@RLt ~JMLB" 꼴e 2- Gx4im?X&d~>dgǷ t5:(%QmIi_Oo7̭xLK=>b8K[N^N|WU P[**tKXr,gEU5)itNv;A7!HDDlBha@HH PGqSUU]s}So"DL-B#=$oyfK WV< fnK0C22 Bj0eXAH)E73Xs5USLI-iqQwvv={k_Kw,@)*Qրk}`x4\ZhDcXGy1~;32`o~O[;8W^x|l'fƃ/~O">u|1/fzH0,&[fmu~pG|n0aI Pf2T,,2EED̈́fΑ~8TbQŘqQvIjMnh%ˮl1 Ero_܆H0NΞ;{wgCW`I%p>d ªSDjeee+}GHc~ FuC9 L`2bFИ{;;~kOr`fKR_T͌Qh@<7фLU5!5u3W?ʑyLD,kI6?,a]ct2:./c Ș R "T"b^flX+j蚙c3}3ZRqX(#snJ`ӮY|'rn2 O>F)3Eݲ-{N[v2ϔ$ ew3kXT|4|zTc zͩXJ3%).?yJ_e ;h|`Xgp [3TA5a8 XAD!l:LS,zj YjC55:c)TD&ld4{;wÕO>Qtn"@oP- ht4>MheR7WTUW[go>gq*Yk~#-/ h3L&+&1ͼXovNS%)~g6]3gz>ozfRM?y'O&y~婁H^1C34ĀJ#9Z0^v [߼3x,hB4qeoee "\,CR} ƴꃃGi|4=%&!Bb7XM5 v.꺶6kIuW~ʥB =ąl)k"̢-%)w%cB`fEI U@LnH8sTɢ1,)j7f$w)٢j&[=?sv ^3=}NJfJ}>.+Q6wvb%ߒbէܦc"cO)] wvM*>R:Ώ4(@Z*P\[H>suUztϟ[䙫<}O_ IDATӗg/_,6ee[v]Ǚ` ;HFj_~vߠܿHQʮݶdJ-[ELbLd;"Gsn{UU[|( 7{FNnml nQ(uej%t<"9$pEEǾyf#(Uu5q<8(JD eϘ[+̎QE^E&OD(x$& %It:zAo}_gs` D:9çG;;4:Z$VUuһ|pMcO٧rq~i7*)O|k_[W]p7h1]bcSI|V1 fEt:flw;x@c=Wh#2l?A:;ٝ_8^(;OI˲`fU &:t H%i:[,z<ObiO: mQBaV E`9/*oK?χ٧N.͍O=|.X23Y"3SlD$"L8tM5B(H,JHfDN7DTX5EL ,Fܻx: "R@EʒGd%DP$dyU?i$q4oGD/IB- <򤥖_}UV_]KpOdL*F:A1^CWo gJ!0$P*;]d>'շ{6wvLDtj4Z{_j &VDG02jOUͳb{mrY|kpP@Dÿzƃټ *e9x&SH1QQkc\̫t1j f A,m@5ns?{YPA} E7-r'Kf  *Ɣz!TU2TF"TB%3 UU FI;U#w۟;wmDVCb6NYE@ fy?[#XU?XUKȅU!L'pJ[ə_.N>M&t>;%\16${Ssr~1;ehgpOoG& 4MMhS՜~㬪XYU'x8ڢe|ksOuJI*7v]GU[q qcs0)4e%nhY bǶOܽ{;}3h2uΝ;n  4e;.SpR)!g.E$@IT$J(C&4bcN;;n\3Ϝ;w|z E@@/)(,Ý'" LrFZZ,x4}~`2bOO ˱#PX_5f?׿Uu[R 0gґ3ݑCTGW͗λg+"d:{W?QUagNS"iRDr"=7֞ߘ%x|m"194D2 12&>s7hq}$膢,$X%KESErH/f7:mbAdҗxQ+:Ey~Qb2`]MVD`3Ôd0-z8,gR&[%"c&0zl:ݲ~ƭ' QU6bTa:106(s욊RJu]~c !r& <Ϣe|k_?ο)^~›o d h: T2*Wrt2fAﭷ(.s{CCDdD1Df3LO~v`1#DIlYd1{Y@ zݲ+CA?xn,"u`)OC$г-%{٢9Foc:&и`hLLȱCP25%<odss =~-K(p@KjًGd|\@Hb@HĤ!P(9/"rPD6p޸pm}8\"t2xsaџ5 OSԼ2@i $0o}]Lkn&RLCvL3X| koY /~&`jMq33U}/p,5ƢtMrXR)z$W5̬1C"ͦJ5˙ jVg|Kgl1U4έJtNtʢn0e"drcj􊇾F͠*u^,d112s748m4w,'3A8p8* f30#2Q,7[q6\DED$*E1`d1bvZBde@&1`(;gmM&G|'a_EN=) 3CfB"n@4@gSiB[WF級xJip4~)[O0 dJ*"F|>g+huV-i /_;O^Q;kcmYmmo=ɽtGv'4D&vF<nH .5 4Ts42(\$KL\rQբB( *MatA;z1s31g+-4l"UU.eHE:f^ c ɒ;("}i-$BEEU?wǷKN?-޾sA jF.0I,cn=Y5R#7r3 $b01"!"R֜-!p78j|sc"(l̥ m ղ6dvwvU U ix<><om> nbEvhZn~:䄝As/f P&fJ>ޕ#@ BgmIK9չ1y6jB^F[]4#i>kXCJv֊sB!O _NYeYjQ;^Wv;E)"t;EQ23wPE%]DbubiUE({g>e(` f d&Hnw`mft鏮@|O>dk5 bDau{$UA4gjCv$PxWe{TFD!%Ŕ@hh tݺ.4 [~\lƹ7x[vvEp2{PhuX?sr<}1:"t dCobΪ%|\z{<dDЫ7r愺gyhOG`V4\5:;<͠@e&4DaL,w#4'hN }p/K&"3ln 76Awzݢ(80s`."{BILyut:aZGvf^u(omojFPm36d pl{{`6EQ2z۷{n-<̨CI/ns5 L&" MW@}Mn^C@A`\nP @^U>߂*j*I|QϦ.G2%ԎjDW`uV-Ž.\t钑en&#v']s/P(XT@E!d ;`XV5u>Uhא#ɚe"/!]IGff&hHfnI/>;?|Eс=z \zG~(%ר-|I%J,zmjE )<+7*e`g.p0S&" DH%R"Q|0svE|h;{u}p("Md<98_~Xڛ#KkITdf*KSW.o}__ɒWgAowd-Y@nډѺe&CrVS FIpx+kGŦgߖjN0LTDM~Ȉz$27f LϜd@)CYXWo>PeQ*ᨚCt[~u$ȈHb"1uZ,lfyjp8> 6B Sȋqt93<=<}уZ9Po\Ye'EAI9O s@Bvt<(tRBF!.ԐĈ82PF* &8 !R%yhȹv_6 0DᢪZ Rt7?iZqvh|QIN@Zy' NbGfkõ|#ƴ֝]U Bu ű6,g[vA{ӄZ|i6k?_)z[!pUKo]1bpu83eW# Zn )("!ZEnLv76N- JrF%D@% !Fv~w[ò,Nnz[PUBk"Pӭ%"ePD&dgƽ|Y&%@9zLI21d28<chha:4ꕛYG4Qx|ja61+^{[Z%Ϊ%WL z$ ?$j͝/}4__RRB @A-#&gGpɲOh)@Fs=2 J5nN*}_1/[jQη YUK.֛=|kjM|Ðsi,6.DzeR^A[Q2ul톬Kljݖb0vECG0$RWq4>ޟfb#$@)`fde7-3eMks=pS( i0J\V HT0G* @|OYN(R@3(9[kHlh<k jbN+q&{iЖvyQ}W`Z;v.@8ox^ASԌLm1Z|o%ߘ:aFrjfl1Yؒ -ߵT J@u ʐ$2PYp3EIDj>^~O:%^\[p`M+/ ˲tn?"&I"JxVTK)h#H ̨UEfd2qT: by齽*::s'Wv 3'+iHX)eG R4NiD!*,((*dlRnox4=؝FD *ijF_z,:|ȌpZT!6ZJ,D׾j }o[n:BԘcghwb@ Ri~K )IUN`9;:Xe9ӋH6kA;SbƲ 3~( k`m1tr[p""dwFLeNQ^Rl21LUULIB~"6΂R4:5*5fMAJؗqpp2U./4\w%q͒;<||>#TID~Df 0z.P`A"ݨɔTH( C=YA3G5r0e8M1%xzһW\C ~TtQ!Woj SBSFI4|3!ݩ7n< f?6 нMa FĘyERN3(i~*4VS_p)F"d":sOoU}gʒ 4AtBQp\@LE!\Щ  :"TG*kcl^f|<(ffzvz*ւ&>Q3eQV߸uSeX̹얝ν{:CD*kkJ"<$ИN{[OufeJPEM ;TqRN@Ab4ƣq EKM,d>OߺU܆ !>Ytdj6]L٬"1d8q{-f Ѣ1)("L7T,F@D nݺ7B_W~޽sn?nP!b P"IR&E&k^: * L50F&$RC?sn=:MF$r䥑|8M.]Ŗmz?5 +,%i$*n|f"kZ?YN $ד  3{P'wqyʞs~4ּU=a>IE)fR;s  1Y^pd2AZcԢN $.DhQb- Y3񊯚()IrbU 5/+崨b]GONs}U:p}CAh uunHUgϞ2ܽӷTǣ}ݵ T(ii̬Lͳ҈ #0cw@@٬B'0e"j!%Qd"CFS3x4MJGkF[owնic!g IDATH60HIۃYJjZ? vJ0d2HM~M^QU2r@LQ~&#:؊fhAD #a2QN`H5UcɡgƋ2EELXh\s`&lMD2IU35/@O@vͪA#E3I2$J!mX ꊅ5<BࢪΟ?{ڭ'7ֹJzGw676,_K767$h8nELMP)5d @CmjBv)'`o+Efλ]|U㵵|3Q|τJi3}W^7ZYs…?'[劮 g'Z0%r/eS#,GP2ױU# `yT\C^HeT&FB;qƵkǟrOSyEJ ,Ty#1iTlA?FV04x<O. LE|Q&7{:-yR–RvPiR "$V`uV-Wt^~[.-ݦp^01 SE}=dQ(/8쫀&b"$j#YDx.B#+rʱe[m2'txImCa@DQ(7<+4LDJSiŋZ¯XcSc|BA1t6\r`-R9;iLb\U(h2Ϝu4Էj ;iN@i Isܹ{(KbfS׮Nv=z) Rh<T Gdf&LDd@ 0b!&SE3Q`hWuG&4/:.սO?p$! M$)L& +Z¯\ot29E7ꪆfb괜e;1DO gJx/̓jlL2Yا1w)ʑx.9_"eE k3{vfh`(;amz~E\\.F|j8D#6*K>*YET7 T$5R>ro 0XL("h#b0ھ[ۛn1Íz/c'Ɠh2Χs&c~84t|b޻ޕNzu$"BSCU@A 4D%BL>bkVa0E_ŪNG?yV4<&6ՎLD|rZ|\YA .]nCS,<=@ݨ=5EYqvChMYL"Q%:5 )0q> A$} @ST;~w;"0U REF$TUIz9FĔN-MZ,c򌶔R.z3Lc e J)vp?? `3/n=w>Y5[>}3g&Q>xwxz!!)(({]]92AKXFXclZ*ZqO^w KY6^)@ԂE2$\VgOo\Qk]%B,H̭ K6㲎R-:1:Z2x< !2eV&<f A(E`%?Pk^/}NlsS 儘c*IhȲ(.QNIbrTwG?%HRM(YklD$"D;:uLlnkw M/gAZ,~6~,$4ڇIo|cVgοk66M$:jV2gg1mx ϵ'EA]Tl<SO]C8 L{L×^;wFNuz?Cvww767M &""ZE@H @BKc(j 4STGog{mPFmšJD.Pm,-cFr//VfuV-\xZd2=}HϱF` ^^Z0f   q,{sƒ Bڟ,u ?=|41(DQQ~# 6AR#*)&Qߧ2P%6l4NG!Z+yneNx7"eֵ} @"jnVDHy1Y`Pvݵϝww7p*૿RQ0,"ufl&dmQ,\ݪJ ABL$U)gx4ǏY),#3$ 9Er$r ,ZUK>/_xқ+#1Ⱦ5 FO еDyX* :aJDm=s܁2!IBW`'w$&,l`TE(8@UI`3wxHխO{?UCQr 34H9 ̀3<pr)3'|gT?o677_bFUNj`]-;[>IB_k>"Q2f* PdZm"Sq3ݯ iR$3$2MLJwʕxdp=RFlq " ,ZUK5ZcjM&agH[P32S7[bt 4M3K0$h`rؘ"♓*ujvvGkknHD,@EQ0S8|VQ0{x(N;Μ[6,5mV5/96>23 t6!?oܸvڍ|jЍ@ ,Sd2k\DRS2F0nD@1eO3`Ɉ1? $Cǻu];em EM@cJ`{W\m3h%י$(\RZ-Vg~ _oXPF-_ Xr@c ҝrd ˿QZta+KpTvTubcǎmv:eYЙe"xd==4ETs<#/tOw;Ll1xkQX@wD_חo_̔57wPNU"4PDB&SKNzo^E ** +C3#\Q]-<AQR21RU]r{ɲ@Q~p__1MWg~]/_x72>L}5J E2(f"Q*f-Tc"䃄*6UHFHHmO^QǷE]B;{s9կ~zPzYGh[^a)G?^MҍR[wz:k?A)M<ɹXp޹W':"IHN5RJĤdf`ILi4f#DN!d/>裫cFj"q5RX~ŋtuV-;25kKd2xy+wj 7wk?M>rpCQ q3nܸh"7n?B4l*m9[2 "$f `jE01qQW30|TѤH@@x`PtnWDʲPxYcLEПu%C>OFitʕk2U7KV)o+huV-_Ĕv$/ÀH. @#Ǝ8gy&(s2ĮL]@@B\= 4 *q>|l /p/>-d!01)\H_nn޺߼y ֍Vnz]1aRlٔ14u)w?@I^#3;f ^P3YST]! A")"eggggcc[FLl`L\o;/<\lhh6${\'*.~pu7|g0Xz=(FAUTEwLJjV;qr&x4ǙE ~b&b^{yc:%AhꫫYa .]z]B$^Z{"P9bFslYDPwu[6'%!q̌No#͑3O3ٹ%XtI4 eƋB8sL"&Y*ĢLpqARs%EY23P椲(-+=œQ "\-PTE/ n<3SdСCP/o flF;wЃ $!Nl~x5m!aXh;T{R&*6̨'Dr'n]4Um "DO'C3twFGA%-I.-۷o޸͝ǠիTuyy$<8\Uov, ׌ ʞ(N{xP(II3XYɔm*ʐl;Z C%[0I}Vg{'`J3pSҒ i%C@5 d!< ㅸy`a  IˍDBJ&'mnuf..Lf)K >ǷoOSi]tV@N%g;ݻַKd!BI;"&M~/~lBLqPYX`ƨF7Z-ڪVXY!\lD:(=UPn7慠\*%M+TGdT6IJ$^߁G@J*ٔ$!,R@d@4a/PQI"+[cL6*6`z7h](2L7)৞OFi(flL~ͳWM Y,Ed>ٙ^Y tȹ\W'NfЃ $)IÄ& fQ =b&_%LH-ͥCث1~79ad!$`bDmV [P@~XB>Af+fG7 &bz*X@sO bVB_f:PZ(k_NETTX:ʙN{8/HR~`5h_7L܍y? 6 ʢB:-bggߝ|?OƓݳd9ߗ+t]#AH£yQ(Mf޹q&q)jMmQ P!@I‘nm˱Z@lcy8CG,b'-nዿ~% Sv{ARU!yAjS$ jPIT뇰11'A@EQ*? b% J lۀ%%Rf%ݻCU3ug<3f3SUQ*9nu]s;{W @D$ hŢ$h-+Wy<<G'N֙4iY2Hd%3(Sض@H9C5WTDGwAd:!dn+0>/Ǔ3PPE$KwdgOL.jR/ALc! hr5׮ڍc9r(Xbr2+JLRq/VɞBfܒeַy<dX`_>g҈"ܪ#XH}g(lԴTS?ew91+DDYx"NCBIFyy:UHV$ )HlwB tF4ٙThs ` |2J"]|wo1?l"KXtތbQC:\GWiD#ǎ!Qb&3! 1bbf3_UUkff[yV!*ԣAhV0!ʈS. V>ojG ݂R&xYkbb FBVa%0eX* YK3Lg %tjt4z=&OU+S(\U?}GhhyY;O~{~UY^bzAH#I{kIƵ7[]V Rs7R1kSK$[]HjPuof͓BϏ~O+w{g]S2;MTC,r.l*౒'SI9=*˞YA]~[Ǐ=ziEQL Jƣno I2*lU$C$\l0T 2A1"Y( `[0&1( [?ITx#<ͭ t:Ggxߛ Ϡ.!ն-%WgUe.T[=̆͢j.绷?T4%xwVt9_r%EAN|fZPO!ŵ_yǞ=v0.Đlb. Jl=aDR^l@Y/Uj#Ei?? U"UjxK_aHeeYR_;T=,%}} -KN6>BZ`ms `B$U>ӟpwoOw?KԶ59_ tg^7?[jA;7%sT*OݸvƱcGÇ?˙h`cNg3|6TD;Sb177g[TKJ6g~Vm 'vi)hiJl˨9ԒN\hM!JLhjQ*"JdK5=PsB;]jgUO浼T#(%vmHlPǠ)1mԶ|?}9 N[w>B?4JS-ŔЕW ZQ+h (K [.'jmFJZQe1F鱞Hm"h 2^zӏvbz%Die%3`S-kh3̎Om`^->NfFkSj |fͲ-TtXܽVܹsϟA;?D4>0>181)쥛e\b~dbW6+*"coFE:zJ*/uJnQEkvxtFJɟz[ɞ!Ynr3/N%>|W~2)i=V=ɪuO iWgIھr0׿ӧO I^})QF~~x{\1cFU?:^U΃ޤebؼaG9sܹB $aE+_y4ƣCxFle8TƙPn|:҂0=|S3ӈ e NTńfq7-9ARXz[jZERU32"Z=eH`UtRdXHyns+̅9KgUqw}^`O ?.eۍg= ݸ#hW}<üY=RiSc_ يbܕE_lJ<X{07IS?҃҈/sQk_Quf~aEΜ>uԩ! x=1߼h4%4gTj/xĞR([?޷OT~D*E9QuOuӮLHEiVv֫viQԒ:WkKR ,?SYo77j[̙+WތA! *@ƣQqzeRWjg@f7xhMD(rR.E rQ]Β,YjCB=޵oV*4<`<0N%!Y|+M, $p.}bDKe6A$፯fz'8|h<sĉAb٘ĉTQ4e`T{ ıXEDس[{֖=N!}}{0"C|s7K9gWМaޤIZn?-"r(AHoI^~יhIpCFqJF#-VЫYX {{YDE.].}e8}rqZy M^U{P(r?8z?i/.ïk5s_=hHzhAHB}Wr /㍍{{ \_qlStdw=*{&^^e˗Y+9TjbR$ҵntݭbJ.V[[! +^{mDM*QJV]^:_W22\{joҖ| Xo䜛BMIKX:q:÷;c&"&w Z[<"x V9w} k>Q^Bim?'{Ds+ے-h*X9ˆx Hf/˴VhQFE+ՀKTq(A@7-C1| u<*7DMTq-TlT臔S%~A6#U,W*Two՛+:u*&M $A/a]Y*5{y3B9[Oj4p;|Z"LuW_Agmh}>_eޟD(BHaOc[`-)9u?#s{AEyߴϳ.U:W\֖ѮWmYw;YDo\bQ$h",+k+giimhݤ}ʥ N:}ʕ '/>ܹ#rrns/҄ dׁݐQ#KoܟZ?{SKQzY]nzPj =%<7VjGH@=S}m.\2͡bMGzP}aknLukvA$ -~7U}Iܮ*mmQsl/4ԉ2XKSrhY&-t౤PhA*;Q8z\8g={>VoWK}Fw,rWt0FGzk.J$2ݫ4PŢAB G)WU=ﻤ䤺NtV{ϛ*>)Zm/)6" Gnm&Fuԗ\); Md!zjh]@暦=~9i5_n3M<^۝:A+loo3sqgiЯ ZZے.+^ :C4A#z x.\0LΞ;+" .h[ LK<>hP_j@+?wzG΢4 [[A@F_P˗/sb&&bOq.'?ܩ%$i< 9h>V~zM y\fA# G/efR5JR/8:]zXv<+ExX,Q8zxt:;{\Νn3`awrN+K^9Yi! Ba BQxm4Y 5 iX93{Rt}W[['! @@% m>j(}_yMhM,BqlnAHB{19( 0ΕVptDYp3qJD<' =Ul^ܪo嵁i9gUM_I;}tLCG >\xq2={6wPSlTUDU,O$Z}@ΝTA|c0w|eFA4Wn ]`NޘXqKt2I 5Ӷڤc\2JCP 0<:XzqK+/MD8u6ԏjBK `kVLAHB` /k.P+D}Nl3VVsq4 AW^޶3^ TuYp[lkPf>s(AHB C X^b˳N̎[E%19s7ߊ7XpdZnDj/f*}ϡhVTC n ~cxe{x>y`Y &[<}tAa1\L/GjY\21-"sV9s7B n ƥW_trׅ~L' ϟ?[$,/ʫV |1 BpU|R܎ B ǎ8 B   $! I B   $! I B   $! I B   $! I B   $! I B   $! I B   $! I B   $! I B   $! I   $! I B   $! I B   $! I B   $! I B   $! I B   $! I B   $! I B   $! I B G IENDB`PKF=AA-Pictures/100000000000001A0000001B7062D2DB.pngPNG  IHDRt<sRGBgAMA a cHRMz&u0`:pQ< pHYs oytEXtSoftwarePaint.NET v3.36%IDATHK=H#AhDQE  Nvڤ HA8;#bHr€PAI6j6߼}cprBTZJИkmÁH/?(H| 72b֊ %<[zit HLa + mh;aL_,fEшBDA"6 ŭ 6J}'ҿEEkp(*B2)wPW/."'cpN8 %%p&+\x* ~m,cfl>>bwWHmbd?$m>%.M;;X]*+ͻœF($D_&+'P0ܐ!O--.jnF_lͅ#Z|R[IENDB`PKFf7--Pictures/10000000000000150000001CE128101E.pngPNG  IHDRzc sRGBgAMA a cHRMz&u0`:pQ< pHYs oytEXtSoftwarePaint.NET v3.36%`IDAT8O] `NhoD,m^4+9ƚT}>7;#f s Þ1OxL{ _l~ p9 Yi=IENDB`PKFL-Pictures/100000000000001A0000002AF9B89EEF.pngPNG  IHDR*"R4sRGBgAMA a cHRMz&u0`:pQ< pHYs oytEXtSoftwarePaint.NET v3.36%bIDATHK헻JAZ> (؈aea! A-lv^bF؟?_&g6%I֣graש;iw,]ԡ+iAEs"C&[ՆNӦM+E5^}46L:Y=Jf:$KNĆܭN+,ap"fB.SL bj+0!XΗ|>Pp!9@Ƽ|n:=+dHx'poh[0quݐ2|?ck%1gМYsAY̖VjĜ$NccIENDB`PKF\+\\-Pictures/100000000000020100000201865E3C3F.pngPNG  IHDR_6 pHYs  tIME 8|r IDATxɏgYv3{"2"f,Dk"eMQ 2{a{axXY xa KE"(ےꮬ̚z`UVefD~;sx$d2M,2~"~s9%Gs9<sy8 p9s39gs9s9<sy8 p9s39gs9s9<sy8 p9s39gs9s9<sy8 p9s39gs9 p9s39gs9s9<sy8 p9s39gs9s9<sy8 p9s39gs9s9<sy8 p9s39gs9s9<sy8gs9s9<sy8 p9s39gs9s9<sy8 p9s39gs9s9<sy8 p9s39gs9s9<sy8 p9s39<sy8 p9s39gs9s9<sy8 p9s39gs9s9<sy8 p9s39gs9s xfou0UҗL_z_hhf=l"f7 18pB$ZZM^{sQSK?_<;WUSdpR*CC`FDbfBbB@B5-RK)| "_x׿y8=e:L*M @tɥ`LSd14S33S5imyo/y8q 90*C"@\,2/ZMM d^x7LD伄į|Rj`&370ZmSDdyYtӴ0孋K4-,rs=YriEj)?Zrm"|(K}3Gׂ=PU!cx jUDBa\2Qz{/ωHDKm ]vn00MՌF\yKYiZǛ~7n⠈D9@ו*&Rk)^HTsl" B2?o}?ֹ>P3qnƾ3""2@*iɥ",fܾ1Ɣ"qX|u} TZk#00R؟@9i^jRK)kL}ݽ{q)ٴe4$EN1qV%#@HxsVqX~ᗆWy? @7#"ܿu&60?USf !o.mY24ee.Ӵ˳8i[_e`^ʲe.eΈvu3 TTm onn!vc)XsKm  AM|k//~`q?տ t_[o'&"Gw}D@H=@@@3D&Y AZ3CDB4SCDfdB 2DjRڒK앗]^3#)2H-m~H&!ລjks>y\jk]RLqn;]a!@1V\r4Oєb 9"G"' SL `Fj[1s "ZsĚkkkI30#241@Eb050VZW_}6p@fy}v1R eQQSUimgWǼr-ZEΝ[g|vO1!%l@fH΅)@-mYjeYR|9y*q^~ܟ2i)0׾|MD00x3kLY]>&o_9)}Z5Q9L2G3#b7tКvna! Lp?W~/?׆A멢a?x3 ~[i@dMʄy)M"T  9ܙR ̴zYk&/HFjAtYr<_<0D,.KTMZ)DUĚhDf0֤5UՔR:hҠqgcd401RD925U0M,RKe)i>R6fmBruuMB(u7g(Es&{w/T5uv).֚VISSd̹\Zbqwba8yZJ)b O~??~K<=x-0Dx;>BJ5m ZKAUfPRLk45$[y] bk43c&"1$ a-4]s@63DUI03Z6VKi,ߩ)%jS$[jMPf3(u V0>~髯4l 9HUkMAԶ伔rS^%/uܻs1|w~CDJ+]ǫlw䇗%q&ZW8vv7@TRenCnCvMe%>i&97~ޏWS?qׂ~oZoÇo)w5 D"hk)uS@@-<_Q\ HZJ&b*jfVk˥HS"^xDfFb&Bjήb0v]2Ӣ:HkҤ40&(t]P-qbw]TV֍n3ٺbE fιԦfZc"ZJm57q>QU&jjk~͹\9/9Oye.;..ou}׏=TiӴ%R[Ci g0$$h?G7??'_xeZrSf;Cb)Eh-Ӳazf8Kw=cmwfK˼ndq'FHf ]ď|+?3_ԣwO} 65 oJi:bdu2mfB q92!C 1F+Imb裯K_y}'(Um`d**IZ=i8qi/p{#r}8\ZS !l76P3aye)n7u朧K\.sLaaҚ``s[Rr9eR 12$0@&8Obπ !CǏ"BXVQͤq#,K-MZ]7NC PJ3TDJ1"b^KqY!Q׃R!1"J LjD!"&f`f"51S$k-%Xk[rF0f1=3"*)3q`P9MiI"Bζ]^\'1S&UMn6Dh `B`@cK.LDŦQ|'Wg1PTULUZkm,%r8.aQW^ylKiyYu]  G&UͥH;>iYbbnΔZy?}孷@ĵأw ;?LR !&V3B31"+(bkZ["*ei"f벸"Jjj@MBX_ Tt^P5&4m"˜EuaQdBZETl*|iu݆EDb,UPPZk)UU9MUjץIk l?1^GY^=U5UYO"1#sZ"÷˳;w.wu]3"Sw]JH @!m{'ׇTj YK\7Ç|?կt]1غk5z4j.Vhf z|O{㍯o"!;Y>z Df j/ 8 zZ &"̘S ̦i58rVdYUSJ0]K.V3Usɹֵ~VZΘsԥ4ǹLHiKRZ.7HCRZui&(JL@)MD(Ɛb9ʺ/Nt1Ő5挑$%`&dLpzwD]W !J?*Pl(36w^ܺu>l֪(z#BLl@`̪!MJʚ75~KD\"03??ֆEN|8"6iz: 0_]9ot ]߭H5Q%7Ddb8eYơ{rkiayc^jm3g]2M-u "Xk LݽwxRb \j&f.vCG!jaR;wn@o> 10 C_?1/G_ [7୷ALу d-e$`ZSN/jSZ REeZMMDB]T!ԥȁU4%cR䐺SBP@&ӼQTcR#AQ_Y9ĔwIy.cxf˲dDD!@hVi`(k71U]WCP"L)2Y_df:D,y-!xscσtx6 *Ff͝;ݸTO/fj" u1Qk˒R)Qry=o\g{}s:g̸RL9FViz~aM1ujɥ\=:;qHyRj ~7vqK-@a>DEjŮ?ۏ!pԤZK=Vk-"K~>&yYnnn8P:?ơ,6]b(1Ő L&*:˧O޼p7@O'>8{wo_ qd4n76 H+R LdScXU9T,jRma.]1q`d`@\0@!Đ82RQ9__MVƾKb@h2SKh  /Y4uɻ 1s>>{vTrjucUBdԥJ= ˧p9X[sZ/Z ss;Ջzja-@3c%hWĄ 0Qn[t\޶^ O}R10SjA"@Z ay.*!/Ʊ.FfTmf-XJ"k8\rit\R)jui^T$uCJq鶛>!fBnI>, IDATK~FF0Qq>&TUQ vq CC\bǭUy!ݹ}&^'5 ! )pD5*B !ٓgW1SRʼ~S طCB1VE+T MP y0: OO:#[xz[tZR #0tݾwZ @=4-}~R QDM8ED2&&178MİmaMAED6f9 8nv QԺ.jlC)0#ryݿ0-Akc"7CߧuB^*~3/>x/xp<~f#Vh!@ ՜`)ƸooMljҥt7_3㳟7^QR]a7#1E1)|b* 98ĔԬ.0v;]df1X'fq] u5uYͯuxHkm.&&fKLdf5 T3(㏟~2gn!nv],Hc!rdF !MPANuiӲr}}݌gg)u]-ӫOSrv[/|L .up|zUKH}t[^򒋈]\Ji9צGq=1rH̑>tk 1B/ϼO>|Rn6!V7Cl*!D<yѸLl󝏞>>tKo<3FEUSQPuAµz P[!T xYu>c4 3TQE]ڠ"#+ڜAEM0"4CR'&9f D*@ӆj5m A-ǟ>~WDKc\EH֣`s`h"Z͚^@ l\?&#N@3VHa=[kv:rQ֚>e)G7Od;wb;)u}SZ;N#'M$2ζ&u)n,KnYr)43H1ٲ41%Rv&J!3u}OF峛çOrv_s~?zzuYMmm @Sr81uC)VȗoVkŗ^5?cһ< ]xY'1BHf@@;jUi B@PQl֨,RK0B`ԥ.wQ[_hj&F (5.\UtC-g Đ"u9B NE>O5i.ɓ'y k?!0&d[ւFH*:X"_cDJS:dT瀥IFz"A$<) QO @L݋;w/.MDtY `}(bVbfy)"*ZఖqKp/t+^7 ]@rs3~蘩6ϋ JkRRۼ27iB)%皗1#w+7* QU4O?}+D~vf=~ey4G3ZRKmb) gKv{QJ{ӧW'Or޽sKy|#u2fi?1q0 C!q^||p RHLzS!'Xo;!4 *5BuY ɠv:Uf7"92!XNb\T9IiŀB -g q*b!$bEy穖Rr7q1 !u;3ASu?zDa3la)PZcbhM777"}UZki.)!hlƾO1@CH)H@hj8rO>y2/Ŵ>շc c!m޽;bHa(1?ߟ.J)>8z||/].HDHZ! %"tjL1$1@ie=$RML̠rHMӼXĚVQ85te.in\J["b.Rj3ubh`MS@>r0@ൾ5x.+?U:#0"sLSʆk>"=\ֶϧ%ZHDtZ?3"S"@Zu-3 ޹{lPjeuPJ% Jb`D[K^2qLs(Zr<#؏KbDo2ZI׳kϤ/hk4*!ÆiaҎfAExދ/e9H η>>qnvC: ʹv%לETdYr,>pL]b ՜ p6 S^~w/9#RYDZk*ٌcwCw3spcLD]@,&u\TN*,zn3\[I)H&AmjZ61B\>4DT* 멚=͚3lu~ hӺi9l7]ׅ| SJN&`hb Ik"J8к3,MnHFFE5\rӸ^m6CfEK.$6e>cKyW;m&̲\tw1ٽDjs[Go-f88̮3a&~0 cߧ]i,ɛ1 "Lm״0{q-@d@D45vp@!ٝuP8)YwK). |b(䌌j^aMssC;aTh~a(n,DU \器iPICq1"sN>-܊Ӑk!ZL aOn,F$6:@@SpǬj9$b"f9c%c"Jda1 s׵1mۦ{e!jvha/J&jRw#^\k FTw7&]X.fV.EC@s2UWp$f0u٤i"1Q|3OFbPsжaM)km3jR&e6~7|Bhmuܜ,wv̬U$' xX)N L9|, 0B2A,cw?we1Og;S'a{Wo6$ zyuStR6K.%% !mӶiEDB)3!XLBĭ0a" L=\/ȸ;tyv~:Xxw{EX1~/?lۦq]8z默`c\n! oRNc*VH~1L`r֢!1oTU 7IXJ)ZR}^~A$RlfjV " l84 W aBDDM`.DmIs0Bt ONW0uSwaN* aܙϺ.pιI- 2АG郼35Qͦ_n6Ҷ\59r^6t:%TsU50lRl Kmt]M $x.f% 3@PU$eptp@P5@tSt)AӒ0)B;_/G_eߙC))0͵0jI }E~( 60 (U(uM>͌ErJ"AS"&PS_۶EBw`&5-jTO1 J-LHY 9nIs(:d1@'ln^`K``p(%CQ/HX&D,$,@5"S}^e,yhϊ!0Q J?V̧Ӯ[g&21q)w6-%!!!FBF4T2eR+Wa͆n\}kn\Cⶍ1Q #;v@a><<9&ðUgO+<)iɩd-L2ibcɤADw#], f16{*ܽIy\-r$f YB"MtӮڶ !rʑ%b&f ,hw%14^dM E)4ګ} {)]@ВB ''K-\~=;v|uMI`%b¡O}ϖK\J`b(^Hfv AZ19 4Mv[#)-~j6"" \@vIQ ۡqTz%!l[@Lyx7f dbVE5bA ٫EJ10d5MZECUQn:ad-\r1-%Fn؄P5i)M?L&m5,$$C 5L؋ژFw,h?zZƕݧeE b@vfƚ`|UJp"ar'ii[ibx{mܡ޽~2iKJHc*QIj)~t:㽝j>_#rՏc&)fۖKV"!nN6##2Uf%D[ppt45ǐ,gR!%f2i!Cy}|wv~lfC)H0İ;_+>^ٝOwn2 zL$"L Pr1s@hNmc1B"z(E=(!],;͜ݝV;+ slT3s":ay,qh1YR<Q} 񈹁CfUSjjB2x!:QB +EVLTճ9&"8I)6~φf ~A6v]lۮ jqTSHSr.i&כ/>wcgg'fmoGuBhYBbѤ)WE?|tӈnMdl7]~}姟y`@>ї=>}GzrU#Wi1M" fv0l1ed:ͦm&0 iLU:Yڮik*d6v$} 329V}Xj @YiyТ!i$pvuO.km(\̝k0U,3#;0WvD^L" T~:Ef`B bT]j0|:&2 %LR%떗6C*f|uWGl O-v]IjV%Nȡz)LМdz+];;;?x׫y']g!{廞}_?`Wf<os6NsVU5]|7͘r ;wd2t0 mɤL:akD Afga@FTW+nnWn8 ͅx:v;jJDGM<<![?Ϧq9k.:l1<]!'y3_}}Lh5ΰ | VO 9lj `$D!b2-2Y̪ʙx[]u435% fA&sW5BLXVsF#&`7pۊz/xth^bH_ !n)hŒTF *&z `kV 7ТL\ c.MCd2-xMu@D-Z1vmK!ͺG6^311[)cMZm֫m"||D|:ۈQ23ν"yNwT||#?q@ 鼛N: 7_X.7z_֭'%2)t-"n'84a `Vbbh IDATqҵ]L]DbFDP8M]f@D*#024$3CaB\VRdM6t]\|if=] M.jAOj%z |?3e߁DrTh9!Ebj&2` sWD:w nݵ:bEm@LL@`HIS*& A8fnAD+9%h 'J(j7U/PU)"n Yu4;XpH9Tr*)MB u^,Er8X6-I~ǼZnFK!foi7ir^|72S15twgwvzz֝?җ6DM0+,4tW'_WØf!f ,{E^,滻;Ysh? #allx啛mүj&,M I7vCȫBIX[ "0B fZ;9@v hVmVzgoVUаbFf9=;-<ܓ[W7m{ֵxNj! Shʽ;?B?? /H*XR*VZhNc)E8H[}lHZAżvJ^cQ )\3'kEb"C H[ 'Ax;wLkT3K ʂMiSgU+pj!25PmÄHX V4s5< \ݽD͍HHU6mY/l\ D Q&2tM4!JD%8qLհZoVMNmow3onJ.YEA7Ht2&L'SS9pWi[jHJi^|6jNӴfٺGB]MZmTKΙo8x?ї}0㳳eJED&t>&(!İщ77pPRt/?_W_M#1Ҥ*i܆~lFq\13Z6aUBrrr|w>D'P@ĀH [L:!͘"Rn4sRPL@Ł)88RC[70\oԝejV#I6J̈^J C`&w-ZD74>hRhkҡ9*6RU88i\If^9gY Zr!5 ̍NĈiHpU!j}10%,,Flb3MVBl%sߏC?76n/ի rY,fMnM 8c dc׿λa Q$<mO,Z7}߯qs^j6ν ȂP; G81ó7o<ĕGOMÓ3+:6NnwwbFEMHDdK8HcM۫qH}w_{C={۵DSK%k7i~7à){bDM^MIsOe5Gw Nd$EK٤;@Bgf*Tu-X=Ă$t;vqpGIn8T2!"V{Z*ZݝLhD*L-XYnuanf__ 8rwW4>E7ъXB{q6߿g^pBTmn,UGP !sbl[= 80 Rmwb s.TJ『,h/v)77+nUx[jQ !)"1–LjNhY\MPbD5@5W(J Er/pg"7/uP mѭ긬ӖDh[<gfEaWT:}/","+[TM=gB0 \  :T4璲ҳ7?y Ln24mCUZLE0_[&&;ɏ` pr|dw1cNB#4Hܹ׫T.gyQ;|8ưX̿S?wgz|>ۛ?v,MNlMڦTeA4i\Km^nSۘɫÑ^T-gW~Gư~~?׾R8ej7"8hB!H p5FY G?WEY=C(ݔXY h. "MAsn2"`@`H``ToD[7mW0p`W\̬C䀄cE qtۘصE!y]<WVfMwP3"n;C\@ک%_a MpWȷTG0As)bwbIq뿛ͧlV}۶fVRz3yO]ykA(Px^sVum449o|M4HPS~ՏC3'w=>:~kW"\$>}yZ><)>>tۛcysgz}Bhclc mN&m `!Rt!M$pǜbDR1pJ.7?7CzWַ>[2 jős.,b֏cʥcRM'mӆC`arRJӶm8\,yn^?O=a0a-h@F$m۶q-^&B8bfVrԏ$1Ddvf \ l{ͿE+2ׄ\BG,iY^\M IHH>.FNhPKBJ9 螚UXL\]PHjgPSCSk($y 6𦸁b[5jzak(Z\kF[)n hiH$pzғRV8{{ӛ\LMm$tm4f)&چS1fLèMi:Lv^W/>5_F @z򅧞\_ۋ=||_}?|xvǏfß_̥NuWl֍#ONX}m5ML8J`q՜D,ՂdFGf#D`aw+c.8~+_Cѵk!>NώnݾůȧiW7\ ir Le2̧2RŖ1>>sO\~>{b$Doɰ&6mÂ^^Ьz 'L@="֔Zf(XC.uJiFB:`k,_Mئ02nEfUΠj8PVJ:R]AKي;3rr)΄1"&*HfVt&bwuX#lR3bK%Cߧ TMP0Y1HrF!6l|gPL6qInHXiu֔ԬzrsWX B|O}ï @.m3=*0`o%ɻ+_}dyCB.{p3qm>DCFՃ'N ۿ!rF„ "¼] nFL"3+}㍷?COP1 4mVY0TCyLYhJcW?]gӶ N^~[WۓY̽i"pGV0)H?|p糿O>+ϟO=bw"0\,'i,)cUg2!B7DL,ĦQBےyp\4*sew"W[yۮ혙HNB!7pdpR#>2x[iĠ5肃fuC%DaF (9q371mKVhq*VK1+%[>kZVJILRTy_bPw3IPYnlӱ z/`6.vgM|}~ Atnlֶmݙ4 qivoX]o':$SΗG'G97-\wN]fT&i )?d>9:z7iea&ׯ-~'Mwkw~|"!4lҵ]Lգ4mN"3P \K"pCDaR,CkOyp|w؝Ka vzn!!|ٜJMDiڮi6ش|]W53 AUtLIU2 V޼e|%JYUYNp]Y R*"I"(1cd%榍Mj4[TN2!VMB+ԍ4"8pص^ YV,QƧlV\#BJ:c.T0qa HpdǺc0l ʐ<\2mbͣw([=h55DZ1mM32Tc9w}[+-5bw3L;=]ͺWͯl6!LMSN DB*"Z|w0޹ƍk1{ZBw٭~}oo+~>AL>|K_zo:;9[-7"O|gw畗ų0 ?対tMOOM+2J L DDN2y PrdKJYʵ.fUҗ6{'GŘ"Jk']KLxzr żmZ:>:].7,!f!;Ty3*(+Yo~貒^|>?BAB!a"wpg Aԏg-9;/j\- A$sFpYGD7t!kєsHD(92+E<ݫ4vM)'UڶezAi@SSUfnF 0Z59qHЏcIjs*9RMHa# ̌resXq_D_W]ٲ3R ;+Wi۶r}zr>OD"",WzǗ_z` v ;bjJֻ?q`USɹl6fҗ>rxm޹s\nh7wED{ru| #g;O ᧞r߾0l1t:B $QjBHĺ5rz]B ibc`ijb "ЏMlW/v%p|ǔՊ8 aR-3[oϿ𬛅 Mc`Րݮkp޻8kj\`ݜIDh x|ty5pS8!l >5\EB#uF4q͔K\a4AG\ШLS.LFH$ŜinnHBPJ.R2w nDDXZ;`0Cfw0&R#mvڜOQ0ӳe? /2,vVՍP /^ʵif3޽nlM}>W޽;w<!ȳ޺۴ͷ޸;~zVxWݻn?~|6k'B|(q6~c/~?|zrxI1#]׵m4#(HHU`% ?h"rGwl.11!Mf.F /<-b1zu~rd/WviAsܝ>t@vSͣPB Rި C&&pwIsV3u]rh [ '& FZeFd'w.HD"880l%1\i)%DOgf:ibmër5vbf''gqLO>"mCb!S. af}$7O9!Vj}Σ6̋ffib|{W|-&y׮]{&g{mړwuqj<[_9؏oݻߴq>>?h@ů}MڶKu0mLh`n5ZԴoi>1rt|rpp*P߾rXw/ԟxνk{׮.?ZR׵4ABf}|rč;;;u؝̦wOݻ1 ׍W,J.揾O^'w>Դm K`溅,V:q c 9D^ȀPʅ([h\Z-ojl 8@-dÀխDzofaR3Gd-598V&)2Tm֝ a~,[35İ>SfVxYEHM Rt ~j/pa d"ET-Jd[w1r:;"Z~StP[-=V~W74EC嗚;wnO?}f|z ]i+fٵMwrHѵ{;p(߽ܶn7A@1]T-;އ&s>zfWrꌉKYJ`ݸM)aTY7v_ҧMKYm..i_6ks3==Z i4Eao_;\,괈] y$Uuݻia"%hfD,"87tr0H :%__}~>[cٓw!UF",RʅZUBjWcJI~4Q5XCFfRJŬEH1fQdLc᪊ 39rxۜS) jќKyT3̬v̵xv"!R4ۺ4#"*fZQUeO"RСmb>=<\c6ۡr)}Rs?*ZAIH)ZJQ3y+u&l:)øۍnڞ]]Jʟ|'z\.?# Y֫d2mXU7_mv7ElLy|Mx^jwmc<#9-2Z?v\+M QU9fƌyt&ti  ҒdpˇlPc+Dձ#QN9lHjR\,|&9%Uv&jbdD4K)YR.R&Un<@M*TD.**E DFX OǮLy?@gi.*58{C3ׯvm׶e2nE09'7^Nŵ7S)2i܍*@HCќ%Rlwrݵ]|pj 1nG\\\Ngvߺu歛oFy6<|x<|8r~~)Ås/o8]C!DC۴1ki" ʑ`rΣ>}L&fGV+2Q)\^\`6=-Q=/}+]>~tf3 o/aHGi4!ƨ`93hcs}pT%*(f*//wD*0bƎs5"Of7x 1$%\ jf:)"R1L<#1֘} &$E\pZJ*S187"58ZruX"תC@S}SsVSXhBHLN$g1@iaf -,KeEأ ޥ͘kuF"@E2g! PH9o(R{e>F5h6ڶmHW(.r+|b1b}K?wff6@Dسv36ͮm["7ɤ>E=x9OÇo~^{5ft!/~)zG~1W*}= 飿׈:+5~5Y1 c+L "cݽFf<.YUЏnaq06BO[k׿G?1;?=9)ob6GYov8=xz~5"j$(("7>mPCAXNNӉ&"Œ!Z)<V̈hLo~/k?t=X΀3&%* Hq9D2K\V$jžFNf&fHFĮv3"!!8b˵!ԯA c01kJZ'̞@LD sL`,&*l̆cǮFsK*&a,kD!=gBtHĆȀfRA50R%H)\J)(kblPCN)3~HݐrQ_},8P h&Zsfl7s)UIol/2~1b὏9n'|wߕRJ`⋷SGO{fLٷ~z۶Mmͻ>ʫ#b 9WAUqT 0]o6w,|8=?yoFnB+_ţ'>a7l=;u`zǫW^|*8X.&MpO:OxSLLEL=b ]E% ͣl7{wޜfiGA9TrQUǬ}?'0gUR* 0pKCXU99缪T0`f!=e(B5S8 ~kۦin}THSTǔMDDNVR)b"EK9g@RkbzUMYǔQߏn4sb2iq ׫M+fդ2Cd@&@<],6곟̝[z׏&]^O/?9η{=|<nݼD6xvvIٹl'F-rK|۵MI3L6zc쉫ʇg*A^D 66QY{sq!kz=ۡiW^::vۇ=G)]ZB4&&81'6 0Aa"R,CǎB9v|D2cȹE-Rq<99. " @5B#$`JXmRX fUK!G\A$Ba̒jhDIkmd]ky@zB5-B#jVD)%#41޸Nɤ"K)EaL㐆ax>_Lo޼>&s;笆M?4#W,YX~mb6M'˫w?'gOO>!1AD>=9WUh.'p~Ӌsf:mgi5m6ѳw\3= !z3%SL^ BDCU}fi9`'kraB|oۏ믽ppow|jRiӋ/ZO2u%ͺ733)VT*7Xc^m~LjXrˋ5 Ϣ'#7kɏdQQ){"2#D0NcASUoLĕB`(^UJF&DLEMyGΉJGQ%"ǤϤ3d Pe3'j2UUΩ{TM œ5CuKD~&mj@գ`fryu5lveBCt4@D1PiLEHU@TO;jDuSYEKf:<ΦCRjL'bSǡ׎wh6s}pNLIUpQ751I)v]DE _[+|p:r:ͷNO^{>zxWkJLfxRϿ?>a !8}tm4M q\4T "*#"2XDĐU ^`{$DfiB4-16ׯI׶`pbB3P>yti~ b{m7KD{W4i3w3Oc*~ĔsZ;`z}~/&mxvӮyɓ|8,cDZ\\\m|>iA ;_ `Ij*Uj*|*#1*ieB{v$RԌyob<28sH$9YQ0b)jay"U p2\?6Ϭ`@ GH֛ӓnL/.1C?i#w1n תH %GbՃC1Uתd]"Z'LJR.պia7cQ0 1_yqmk.2"9KPFʺ)"9MMRʀXzfehy^~?  88_XO`1^8~n,Ic )z#E>쥗8և=aiI;tm;dd#H!t9D6)E+Ȼr) zh餉fo'%frFe:iF{a!f/Wl>! !3=0Rv9h>|-S3Hc'a,m8"Ηv1; #o}^G2MU͍ Rr.)gEjt]ιਢKXL x0@& z}~UpLm?%$+BJ`@LX hLLEkk`4@"30a "B\Mc`"Ȇ諜IDK*W痏ji׽@j@Q y1)z ڦ[6!\lm&Ӯ }mlqbR#l# M*ZVE5K \˟{wtz8]""f a2튌ֳ5+(akh7au՞8M}`P]F(;#,'Ob][Y/圽wijL!z^ڮmk;D`[H ˿3oo=?p׀~@U #*T{nF"t7BZc^,к-}0 䒡+5i1#L{ 9f3{bBu+EĎWHT0LS$pQU䊺RUMEҘ/|Hb|hdmL =a?~|rDd:fZ|Fwd?}^O_UsfV;CnL+  D5k$"LVMDygVjc#v9W3,k!hZ0BD2|_"b*PPT p/>Rb$PD 44IwrT^f M|a'Qb?ݭֹI6LJ&„ g"_)j b81}6.Z?ιG3}.14Mcuա3wHITfMCU46&TJʡn|3o|s SZ`g^X޿tuunژxq~q\޺ypfޏqc񿻸Z#; wmut&>_L qZ-C0v;tv]#"攼c0W-_|t耉*{M!O.V:d1fa()+-s'OΗkj͊iR\gn1/޹eU]G$EK.8SSן׀~|Dk3!bV'ڋet>aCCcC4jݫ:UGFBbEUUc/8(D0SQpLL@LazGfEBBd4fy"0",j\o>RRJ%[czjVSq54ikTU%a NƧ'˫9?˫nwR7#*: f: q>1# P#K/'9i6mNh)*0iZHI @]nM&471H s~9 {(92"gWP'7iLcnҘ6۾ pulRJ`rtK/\?<][ Ο>߮7 *:i!3X|{Lڦdm<&蚦iB`BB ދyٳ EU=SSr\QU+Џ)vh|pHTsJ Sr?Hߗ4aȏ=EuE*^U39:<@ kC Y޻H`vg^^w%{g`U]wyc1q͑=gǮUS*9s Fjb`U~˒sƤ0lGstp+idAC$TVUS.nקa7Ca;v!02);F,Fb`" t7m8?߭wM B8Ȫ4$Y]N9DL0Cǡ}?_nn2)9K @>\\UTfcvD;GȄ\F)Eo7kzd5 ^( @BVD9F(RJSU)Eѱʎ\e@v1RE1h0c vmךA1#an>;VDޗ`r j$]Հ"U,1#!S@U\!k]7o"D RGM;/Uf/\*\4XjT""ZKݰ]}?XLf{ZZ|MJa #@*d5-"im c%K?+yqHiHC?|/ZGG8X,S=y_1"*EVn7_E?4GLJrݛkn߾5]^.]??1D@qQQ˄DHV(ޢh&B"n7ibb&AQs!R2"}mvwyz)"0g4z|${㺫I15В,'Ow]lrw0&_\^-ϟ^ڶN']?y83qq|>t]v{$LOPi1;RՒGR?'~^S__w$HqTsB5@3;vb͔C&6"Erޫ>x_dz3SyXTRj0U"O{uRn׋)-J,x}?;B,cnk̴8lXr&)yE[?\^p٧_woDZ @dv8hwp0=xH aH{.0{;Ɯd:ǴlT!j#D.<1[uu|bLd2~o9_.}^ UPMHr)z "8 (d:mw٬Z<I5L"R>TLLb)5w /1xKI!"mٱBٹm0"ppx*u` D .9K!]Soޑwo[ ǀ6W,Әs<̥R,6lcʫ宨ZJVfӗ'm\. 6ѐoG1?׮_`o_;\sjb$a ZC?1^zZ6p8Փ?xzl/;Ɣ//WR?Oclv75pu  HJB\DF+ @E GԏVRmd"b je"gP,gEf3lɓ͖710"JI1**ֽ 9TնiyNd^_~|qq9ܴm/ѵaH)申lļY~ ? "Gׯo}? 6Ua1t hN}&̤`o"3 <I M 9@f Y @5C}j#ql5x_%8zf Hc23b"iTT MfgαRcf־ apިR Hhb&C2C"Q3${ o]fHܞYB;ŷowഭng|Vh?z=vihcg84;Qt~y|ryiEqH%E\\1qLCCk&)1ỳ{G?D<U#359=9;9y S&qڵ"/|ىЧ,ݶ7^v-,";/گ1` <@@ߍGO?YJtY o=^oON6O?:yʘwM۵Z$6Ok.v~rd1񁏁s HE HH@J1S\aL0v] /9!(^]]Jbv45MSI_HTFwW]Bug/Μ+(bfI(2]mOO!ҵMrRS3Խ}^]-Kh<͜cOl`HπDصn3-D糁5\_گ_8BsL s."RL 0Yov㐚[{Ewj1d$t"**BUDǨb̡s^MFD L@L+9TK0 @Ē$IHL>#'d2C<:<#X@Ha_8~f9rC10tn(݇O_l0~_].&V8%1C,[Q9\LuVcV"{޿?gzrrv@ UA<~|ɩ㟊ȻモRNLJ]6=A)|ί׶:lZ9jɢHIHcSbypH'|uiL*#@GO[77zwrт+Va>ȮB z۷m{+bq"4\8%mv7.FPaDŽ\k.8vλ#Gb "D P{fzS׷ӔU3 4H50m"D*"EJ@!__].m7wxp4r::P?U0R)z٫nU ^Jh($ђ8NRlqt5"9B`& ?I }!Tү!("yW#wwCd$@F$$t**AmR*R:Tdڈ5O#;F0TosoL?#no?o<;o?|㭇2؎n(4.1G}5c`B& 5)Z=0'ZИ'QUg U7{gPADPjK;YB<dDoas_xe#nkw#SpJx~;~air9YoaF{8LIS !sv[,fK?KO^{[xR2C$msZN)xqz\^<;}y.Mx\Ȣe[NC>>Z<E%@w``@ϟ>贺il~7wf61zs6kb$BnV'O=<>X-47 MT2=bQi$,ϿwsuѣӋm{rqqq[Il}{7 Sj~m[4y3'O^+)7΃4/M^]<auwѣicb vL]cp5 >A11Vg/_^?<:zxcI*%f5ZfnjL 8E$gZmoWww1`Zss\-ֱ*Zѻ{\J @Ԝ!Տ hfR2m TzkN@MooV9k2SD$B༘԰$Y wm{~v6}{֫ 7!8fYALʔTJybxu4|q@U6 HAGOX\H(U&&h*|?p{?BlL 8 G0K%BpD@;2U *J)11'Ia)wwzش73sIXGE3?gW`rËn$[=q?&ƽfsY1 ݰqv9#RyulB!w]|x3o?yr|1i7ᑩin{Dd3bRőkvb1oz3*͠RJifC-w|>37̻Ͼ?MxE&|x8YzÏ~]?'uMl"1Z cpL]l|LӠHE7gNy~;GM& LMYfJBbD-YD֛zYdMh;B{a!\"@E, ݻ,c)rrqgy);elw#8i׏M۰wFD`rn&x41TjH%zzX4m,9_^ !&$43)B $DJSWWp H 񼽺bJ2"gjv/4c)yz 6%jevhdj"014U0S@,J\=7fr IDAT{(ԒEYR=>А# RaD*^ЀL{8D4mOf]vTpކe%[wv=);6GT^99'bjC];r~ǴZmۮ ;7/k=_.sFnYin/6s+J(LS1Yo.>y|Z w7[0aLI^gDDvƇc7no>>r_G🜟]O‹.~[^_m)x~Qκ6aFl߯ ~3*ElTT'ķ>7~?׿;[v=[PO(Z;`Y2YR6rsɥ4mWy3#*I.n]\ݭclv}?:-I&Yu+5>FO`ǁ#!_ 㸷3))WR!"6ivSl>mށO 1#"c9gѣϟ6!;7k;/AVHmLXEwso+̫3>"#$JML)$3CĘsBr X.P .ǚD Tm^\׆ysbY PVZ >p'33@p+Y 9BQ(**`R&`ln8=ǜJhnjSJ~|j> gA{tzڮf43ˆ~;L/<}<ۜecSm.=8z7<8L0VټM4"2f}xwpw,Pr!~My[BDGC.ݣŁ]^))iGmJy1_6]wyq6k#hqյy2.bxvq|ËuN)___/v]۵1} #| q;v>ząI)h)bE7/=x{⹩K{#*XDsJ"Ȼݴ^m_<bnږL Иެ?:hgr}#"o;1KĘK5f7rm zpyrCu %TDr5cin;Y@>kSc U=ٔr YaFDtD0_.~/ɉcRdYZZ9aUi&c@4$X[&pGWCW5~JBEM(4MÀ  (CtDLhUUT@O/bzu5SZeEDB`瀑H>LRb9y39T)B.KR)FwgFDƌ)M~7}J>k4[)_]l.a @ ٶ5qvỷۧg7ʹԶhN䰿d\Tf6A=͋lִmC?5M q$BqWB"2c2%|yy(ưZ}dټy73^zbW]\J)f)eY˳q~? `ηٟS.?>Mcٱ7@H~x{tE:퀨i=0w췾&0Pr>T}!FbPr"FQS ^՜n@ӪV#O+33C=r*uc"ZJUD,;KN)R PL;i~.rs;]_mݸxhnofSɬyq|A~M_GZ02 "xO"dKxVMaD6Ԍz\Jͼfltyӵ0Κbl.6a) caT/Әas|}"brMH)v@YKMXJٳyu<qݬכvOpXgY;u!Q5>8B]C$f6êg3%}@Λݝ<vÚ93{:CCVUђIa?;Ym|q0&dbDi7qΣ"f̄9]>6mtU*&oAJIE4)9I=ර0& M ! lӘJ@U٫hтd>8v$Eq"J?zpxvrwMq/_^]U謁㢰}4LUH_u_2~w/ϼqED} B8i/@*0 V}23 Sfgyf"d$`p )ާF#m^j@HD{f3$WewUYQT8V/w\"8u/\]LC.>^Ύ# ykF|#rj'9t:JSȩCF*EEDD6Gdzզdd9,6]D/aoaD6mlcjTryӧΧwsM2;S gD...c{E~>w":i7 @;OOOnONW$sE2F؟}'318B\JXNSlv/.ET9Օ9ˮW]ׅӺi޹j%"(8SDH~ ۦы4~IL 3JE4˫aLVp3ԝ-yن"R)D} ` >mO_^_]4>__\k*Ǐ4m{{)R狽.G-~vy9׾ _W^?ճ S! 9W& 2 JNIUE2 4!xvEP*6L[b|r*Jݩb4|{ Q>U !XŀhY؈2w-S\D!QC*ٞ(y {70 }vw_L+YyqybTF55p@رc !x ͙=iI;ィY&:K oZI;RJ !Mkuגs9=_(ww]N\HrvHMGBA" L8&)"j@YKM-܍w*j(x8GWק'z5 Փ*"1OIzy@Do9MtZ2MÜJ5jMJVK!ĉ)H }bc)e׏423#@.س+<{q6kƱ#333{bGUjM r/.5EB{%㋓!knr߯f3tO)O4ɗnWw?.TE~rߏ|r{}6m+EiLpEV+q!6 MwZ=*FՌ2DԊ(3(e5@2P"Q- FFd*š@"**!0@j:4IDk™Lή*@T@"]߯qh)> U9 "mG5s&8Z,\J4#14H9:6mB+b,{ű#GLB,SIh%OMC4Lێwwwӓk)AlZd9j>4Nc%VS1!"u>ֶjwMu8MƎAZEm{zz!n5mH@.zb1ཚ`-Ε jz]5IEUwM94LSɭnWwl171R.><1psqs}}ʿW>`JiJ9`QͶ_"fw#Dfr̎QkFh`Ij0$ĚLEФ'p1cFDJQ'9\ Y.z'"&Zo1"TKYHL?!"b>ѐҔai%$06 ib5T;?:<:vf]\ua&\Jpuqͺ*^ŋr-h1~î&E)O?|V$I)UMVwwL Ďȑz1vjMGB&$"b4UL,"~3>w]Q8اo~O=|aH{s5rg-2GvuajfbƎK.Cns)aH1li"al6kR,C-4]Ns"8ۋk31)%y׵iJ}?5]hg8v*BfhČ&D I-nEoڏ>8?YR[''lvd&D"#w;jѡoET;`Y2!"˥w}m꼧n180Ln/a/W7Wj6xvf U$j5# 90y*X5ۯV=;;4" A J.|x4{%6`in@լ=l=IE֕кR *TA$"w#F+":v 9KUT"1,{jZ;gbԚD; y0tcE ]9ovJӣLJ9n拶kgĬR^Ĭ*R unhcB'O>ۋafURV EvuvvƩi;憎ނ%kߧKhlI)e%())gv}ߏYoq(R~t]ͺ2͆b14ww~Kw̎q̹ZXC["X.fv?5 #U/f?E^m~R1DqLy(QQUwc6ճ# Rエ~}m|pY:f<"0RD޷*U'%#Cr Rri|JufhPT#j<T(0Wԟ7T Y#"#RfRL)n1w1^R)L R͌s119v޳wLDt?0g xtpHH`D(R,d1(EkleU%ДBuX́M {f b*q0D_KF>{a}U~zfAxi"Xb&J19D*[@@9!D`rHL55c]D꽒 J.C?9.$HĄͪʄUTDA:D,RrrΎ99JBm2;E `wqSʵ޹cU)9K8W"jBEմSe~g>Vl!\|qwYo+xo9oL$LbٛQUDn\oaLurzlrXtct<_4!"`lvZWz(<8nd8.q~ɃՀH.}1_/}~_ڦ88抟iJ7 4m-f16Mp P!k1FXUD*-"^_*j^LĶ|杓R>xR!jbi)MnXswjYK)Tr!s R! Dq3yfy @$}{b2XjZɒ;W%PP4U ciR΀>0!C9J`Cn* iLH֛&z듗ŬfϻbffS.H㏟mJɓcv9RBMݼ/b\!ͭwtt|0b 9޹w!6{tĦ? !,0hҵ{$aT1 x{2ݓ'Jvq7d&Dslf!ټ{h`[RG.~zvo >D\aLqcsp;DE?T1aL0mV) GF䜫 r81ޢkw%n/۾U'lDLH䜫!ۥ:"Ʀ FMf1;yvmmib 9콚r8J0l"۽O LD* kH1QfW۸K wonqε] V86qF )39~R~^mXV0iJ"Bp{S55_~ǴHх fYvMlmgmlbf5MT{HG`=,;0!4MժPiyL  0\.-f[㘢v9K"3PUƩ<cfZU)scf?i;oEx۟W"pEŪe|hb!JݾÔ<~\_xǯ^=y׵M\lBT]pB]MS2 yBP)s!Ղ0Lc9 f4aZ}~8x_آ[@U,TDB M M'$d0N8C0)skEq2j\8T8nݮoc>}1m >ƺ? *}\O!H?3Uev_~_9?)'開TBJJwHE4,8i߭:SlΆa( :DiNlCВev1ztX>D)"D!Y3=R;Ffc?:qh瑠+4y"W\1Q3g*EҜoh><,9͎I)R)-E7WWW8ߜ Yh0[Κ}: brMp=62$ߏ_=a|ڶM<cZYrek)I3<37M11"A-U y4S)b`m2"s\231W$8 dbh,R*Z@ʉA]Qb~=;0P3UbBWC-}*%湨H!r)Y?sΖi~߽!r}@q~\rasNL.F+Ŀ<94`Z"R7o5yyu9;ۜ-gB\*iUJ)iU@8"d|Ͼꫯwʀ!b&R\uru|"=nxώi*h)?Q-XKO>ygLS:߬We.j>xQ%"q>ecsOMTx׫n6Γsy|zxw;]+ykb$L(RT)\`RsL!#"+.B)e':Z 8GZ4ȜK)5ahfL` jZx]ZA!8soٹ%ճ&4 v1</^\Cp̨fs0)M7^$s"jĔr.R PQ fyG>N0 ĺ[df6̓j$"fcA#&`%#$GfԂ)Th"$# 0T7s숁@Z%gs+'y<ϥ}c$cPD@D5yfQDjVT$7+*=m.֋ǹ?QۄRd؃{OYeDkۆ{y<ĎsAFqٟ4MRfs&"/.Ve|ָ!90s HS;("*ž=Ǿ؇ج@.i!RuTfmct4MggwaHGQ"rO?ILK?뷿9 Υm,R[ C}*D'E4ūղsT!4ղ8o!?ۇiJ%Ѵ:x/Ο=hzv]#1dRDz )jDXCEd)*D|LjlDqZ b+%Dhb(Rim~ѧ!S !xB?OAJ믿M)K<{SER2཈IyN77ooO0LӔRǩK9`"Eۨj)ZJ8c|߲sTձkb.TJ1#B |\kGGϵHD&0d1)0,*"|Aks LErBR,ŤhLLo"RTEo|gLTDTL1Ȏ]5jd!Ūc^PHgS>@^JwzYԽx8#v;!{bfF!#Q)4L~}Ɖ_}O]X7y.)@"l21o߼y=DJ."v>݉3sa.%2iR 5!jL"rVQNI_,]S>>>.|maxS*?/\pg뫫f"G?G/t<`CRA&%7Yŋժk۸hc5 U&V$v Ԭr8L0|=?;[,f259Mt]ˎq\wEY* !PNYDT*JrMn!UH"bQ͹UHjYJ "b"YJ+ILVn>0 hJD.=I@tU;rZ/ሀMӜw}? ex`A? Rjo~wk.m#=C_fd`42a{>4)ذu]̴qJ}?ͻQ̥ڰ:gIs޽޾>̎VgBM~YѱU&b5.si%RDUU m@X7xk(iRJ}[Q9$_>>]r~q~q~umA˦  &m]n>/r8i*9@LMJgϟ~h#"?UBpResFN 1g18ɇ'`vC1w?PM# UM}p3!6c-9hbdQCNREM6SyHYJbnVTdN)̕=1F_y*hrR2{7_}cX. &veKէr 髯??;7LE$q?!qqXWΗ:$]I8MYhٹO[,<_\>}@i)Msi_v{}fgR*"VN 8DĩTDT\a|pE VޱiDcۏGIɀ$t=~X2bRSQдӒ`VSSC&9DkLH !zRjDME+* H QbD"tHE 4fQE@bWnHZtZ7>'o!p舜sϗ0dbjŏc?ߝVuvnI"pBTr9;[;+Υn0S9oz!aw~?RMi٦{|<6%RӜNes=2$ s-K MjZD~ZCߟ;Fyo}.Y)ȇ8 3"ww68)7?[w4 8R޾[@(EiXd֝cˋ rum SXd`*}D:3-֟52MiRw8\\~)뜒M- `Bpj5)4>=}fl9i:#4܏IĺnɎD9s;4Mb\~z8"@$Sf0es=3U# P_U]34e403r1{8Va@%M9f̥ 1;f\`1s!8QqƩ0a"0Aɥ LynPr)&L L jh3+E=>>!זcDJJɹHMӨ 9ύ|w{Fwz?pwC,im%G"" b$)ei8yvqV;i||BLBIIF̠X40 ü\vWسע)yӜѻU2Huml<⣏>]It<}_h;ӌ@1 q.HvM̎p<^_Jy/~-:vӔ߿y m}8ЊTyqa&@C ǵ@5SH5%A$tc x[c'V-rU|sD$ENH+Ǘٵ18N0s5T=Vwg&DFQuQ/z5ɱ!RJ.@H)&:rضMv8GV.]8z.9 v>wwb].Rjl6CsfR ;X•TΜ UaRQv-j' jYjT~ ddu}}mC)*͛LJ=";w j0";JY Tv9dX.-YUժ\;%8NS:_vmۨpl:M.cEa<4)K8Lۿ)oӜcw:r|s[ #x Trfj' -)KSPP%yNiaqxh88OydyNm&0Q\WW/d\RirI0vǟ|Ih&)9"<U"ϿoG?y.8mp}1a4 KR$W4@W AD:=R3D ODC"v7zLET@" jH̀9LXK;'gwTF05ČB9FvtLxPs3SXi.z= vD{1&} $r / IDATjxvWo~=7o!&:v~Y u]h HWVIGMY&V cfQ̬ZGU {ɹHvKgO}mӨZr7_q{d‹nY3I))sZc0UBXY xbI5cF*7#GTd}|^-QUBgĒu朤?vyNʮrZE].Z)͛wownw|ݷ߼O3;W)֭͋V˫|Z.09g`(3<| sN}?qxsy41zyxwX-g+ '-kM*_ sO*rqq9*àfEf-yv;M" TMS.Eڶi(EM5R4mS ޿Mm4aWǟDX=7`iʕHiC0TɅ&ШAܬJռ0RuUI1ʱv t0Wxݕ CY#3s0H%iNZ Dq5k|w'I\%18PQgC@@D9!Ţ4CiZx/_"?>ww~Ls g/xd"`z"1zuëjU1>8nDhD-VvCh[hɒSʩI.tz!x~qs0E.KriY0Nc?RԐMn\={vvCx<: `dÜD(3N4s?s.8Gfl0fq̏psUeswAݨ VH}41IED­J3$Wo8\><=}x`fmRRin'5-s`G)D?~嬨_:lf? Cd`sιp&VrF/G̍Aq ΅ʌ֋+L ! 0"cE<]żw(E[S:!yӄi~3@!Pe! T_mjQUۄR4jj%iwzLiB-~?:[1:&dv7";6xو3(UPWYZDEry8<=6!-" 0 Ze*)@ޛ+V ,\ID D` ^"`q,u9"fD0w`f5AC:2NL81@"*C!њ7b0&,e)UoK61b3i`"2 Ѕc^\˛k@l'߾g][T4mݾ{O^#`տ}߁e?" w16>g)Y[msDdDDXYYJ$Zi&DFHVʁA"#0PKEê2-"{yZt{x)En3;爉]QUCB&F&fPgbu^xcJ=XazfV%nz-g>?[/w";ׄPTs*oo/E btVQ!|]Iv!6PMVVGZ/05ycJRԏidzŢމ4Lvv7߄۶)ERSc-b\9Q9}ixC_ q<Z 4M].ERJ%,Rq_̴9?\/W Sqn\,68.iN)D`λnՍt}?s*ƜUԤ" T  QUDrќS˚;b&"SBFB$U@BBN&eGR0vĞ|͜2YJYD@k}0(,U3cvʅرyum E*եcet-?iӄmvOwo[vXDcBwf\`~?~Ӌ嬨_;}Jc۶͢uO!k9>r6{Qn"abNys)L|@\rBH!c& b&J*I*_]+5Ȍ|M۶MDbP_^>`E+[L>TjZ_Dh@hdʼj.F߾>yY]ͻ[V}zDkp~Ϟ_ϗ@KE~ͻx,Y>hEgMŴm|4&Jhb3TL$svʛV48 Ӝ|<\ns{)DxoOfsc`OC N5s.Ss!Ftfb Q E9M|8 0YcN2M)LnQJ Zjn?b ͆٫ji)Clb#QJ󟋊`{6 q,W%$;vJ!!!U 1-R{O̵cjP9C#")RFBN;;й[a6PS+ŘyXg1#i{;fd-"u <3WLT$\ ömu޹?XKyPn]<=ij&G-R'?㗯>|Ѷá 8...63PRZ6gge&8Ei)g!?f$;iw۽=a\\p;ηD.iVzgG]|٧/6 Q;4RŢ>V&' sd!QmzOISz~WqN|91A?߾[4q^i.:cӪ_|ˋfG咘bsno޼}i?֖X |qy^zZ/n? ?{ӟ1>{iiC.L/? %gx쏇~-65;&FU<߾9g1[WͺmڛR4Ւ rS"y0 XS.%L\H)2O88})ENzιlvAr>#!y*4fr#!#@R1qPp8""JObg@BhLOԜ~α\HO(bQG N<&5 "T"jzPe1wxZaPUr`@Vӄ@$T!Y>3۶#@DuY%$TVw741x>xi.8:XJUT.gQ??zˋﮁ_;n1Cۛ;qƦ]8i!eY hR??YivzwƘ{̬!].i>'d5t4p , R[8\v媬ڹ3sO#⛿wz8x"= )"oxuumwa~(G8"R(Ek]+\UU"Q}& SEA̙_8{z:yttR;Yh?@ kc)(D5ZcD(p\?(j3%BAɠ!\F!`,1D2 CCﻮODNZi}{뭀`9" ((2Fc>&kr9g~$XhɌl̳@H20SBk54gmkVd"f?j$F+1* A&RޜDFb4Y};-KN Cƕe !FKZ)IdrD E|R1m;eMib:x\Џ0}S,#R92 P5\]=ll 8/_4khj ~mN'ZBm11]L3IE |{sd&8cއ~Cc95TJk0OZLGg.'#̑7v:A綎&k2E1Faj04ic#ycjp{q6?;Û~6eHú(tEbڷfRe^o.NOl6H |뚐no7wVc}.XcR겙Zng~v}5[,R^]Ξ>{om@ꫯ^ڏ;DTEL a=}^oWmvŢ bL V֤5~޹QD)%Idw? ]1DPUsDnnV1ƪ,sdI>PVi@ R>SJ1*]UQUaP 24q=""@H!rLaa"T ZSV`IY-) )(RADJ[xS@0*"g$DQP=GHI⤙Ηs ,ARZ3FN)%vmRJlBoonph[~Hanw *8) g㧿Eʆ 2"J:?2 2)ٵ@=`/!R k"|B@R#KI# @ASx Hb+rN'!u]w<@$KA(B8"$NAL2ƻ*ɤN\ ;V++b8-I3i>lf0CUba֫v>ǡk5..OL..Κ>ޣVx62nCϮo?ٟvg,NۼmC;j*'3c&͇á- '2c9w~~ReQhX..Oٺ.]."1zx8N>~FZB9[bY8kRJ$!$ryǡ};vRBJGxCuuYUEc3F@hCbFE1=Ұ5eYCuCU_wX8i(ԏ1z@d(IӦ|$ 'J(kB҈ 4S+ESDdC&! 12#H @ET()(aL@ cįO A DQrSJ Y EZm"tȬWk`޽aUYgS ZmB9kXBM?7A}/p*La92Db5U#"CLLB|Hgb=PZhEZaֶdZn CJ (ĘR??<}Œ:'|?Eo޼[!8uPl9(6fWb߯wr~2LMj8ӫ2l 4lBEc-!qR©KDe8i*<҉OmBk&=(0*"2|T,}$TdcNpev{޹lWt:ڌÐSd8EG! 69UF"fa̷6c" AHsD$1xBb!R *B gc IDAT$HTFQBP )m+8#2 "9CUEY(Ec?VC1iV 2"DgS^XգHSQΙb~./Ϫ65!)ܼYWO֕qJ5Db{?O/o)~磏M&5o;W'@i@>a+q$9LJ Sbz@ + $̘"R0>gBR cdORc)B0[-~_}~=8N^Xr1Y]avm~={4HQ{Ñ*\-Kr>śxh..g*GCTm]Jk۶mcèex~b\Tu]o|dPoWU]N0 ~Wϵ!~0 wH,߼}CQC0ˋ9'Il>_7?}L]Uvfkt׏>'7VާaJ몴Sb1uǮm~~HBn n[1*:^k,;΅~"7Ga @>&9y$+<@} %)q@bѤD!YH"A""=rfSʄ})q@u@ 3""@L"?0PH)m8qSFgE83k3348g30)@gM11ڇLYL 1Ĉ@") xS5~_߯eSV /)R;mv![˓l2QFenm?巾_~Bb vw~+wˢ\ )$DA@`baʓ2hYu+)F"\mdfIˆ!NB/>[!6cxEN\ñ.&'̈A\] 8ͤL&zw{~fNDTVɳ"˿NI3߿ !5lY mvf٬w]7~ |:Lgىfnwo|Y3mH뢰؏~ Xg=zfya[_YmPdRUZonw]!x̯146`~tuN$ ֋il?^~WG?]wNXfӦ,ʮ?ӟ?㏟eQOjc SbalvqEǺ)R!lR`6Hv7cU9=7Yc" @A@RMr66#?HC•v|?褩ʪ0PCP*)sq?춳YCeZ > R!NQ@RF!%)IJ"`($)#@,"IB)z"sYڲ*]*F|D! ,*1B"@iԢc̜3K$c: EQ(EHI ,qzkdFWwgZ+r2~ր?uD033nyl 2-j QJd_XkdeLab8BfA mU6"HJ)rbR ˓o<7, \lnbH!H@(HZYQ+ZyիwO\ qvn\?:!_zC5٬Ǯw].eQiAL)71;ݐ}u}Y?9-:9;~b1M@X!*yFbJ!c,.+b|U lR}ۏ_.u]h`UئnwC ɤ1$mɴBKx|6o~ZZmnVόw9o4.KV2 ,.mp/Zj;~ueY_~w?G¥v:A@F@1'ɑ_E 8L'efXjUׅBq,+[:slE./1AM=oc?DżYqkcWO\5ɠH+TbQHXrq (e$.">D@ uY9B@N (@#G BV8HR: ‘AR )1Xk *އ!>%%!Ҥ4ǨԓQŌZB*IS}i]Ad Kn&_5@@CL)z_#.>Uh=P`N9j@#`JiMEi)$„Jk-c?RH99Fm-H @RLb[xY)"$NB k>>w~ϛO?}Ɖ޾YɬqjAEC^=?1_<~REH=o"kݟz}Qh?PYT9V=u"۶뺾(pr2LE=wûw/>z<[LISL( ¢. 8I @1\ɟxl/.NExwt6mJ)c{8v6N2/i.fn*RlXLӤPXbnRڢm K$_^__]_?T()Cߍ0яh)uǡ:1u{~=SIvaIN=oF (6Ma4G/&4Vhpw1J;gb?ӋGjr똠 , 0#_a's.e4VK2GED B1JI 0 >IJ1&M DD 1r7Ji"bN H∄ +<쁺agHFٍ!!̯O#"AH@kDV, H" D*%!mqc?y|$2Ha&TLe]o7wag|q?\?,BCbZ;G|/2 -já[{ax3Vٴ J)2 Đ[E3O~lYN)I"/ 9cBzw{O=;JRz}1ֹ,32XB]yٳ'L)J,7n┙7ݻW:㜳NOE ]NǾaw~4ưH~<}iɢmxh㌲ =( B[cBMWowGwum? zR+EuY^ q )%\Uœ'WXmj"cR {Ս4CEBq20m?}z">1F!ñ!Ð_bgUST76~=3DmXDldam"VdZ^>?}vl6kRDc?هaBk5H (J)0 Q,GF|a1BAXk]ORJD@kC|@3恳30!я~!(R4 Y"(ϒB( gѤ! UZ c?>)"$U!R+Ї!)bfdzX̪RZFkDV+賡/&vX,TQƨz;cYVAR~/}l Xo7*C ѤiOO>}_~dk㐍IVk=Jȇ~LJY0 :Jk1P1H)Y8"TE|폿7_LJ)1HbAeTYaXWha)e.bL~7݋gNOg(ݻ[~rCX7N2_Vٳ'\i_ֻ}Gc!wS^m_.'O^GWN/ORsECJ"]Qoܼ}ba>WCcMbNnoގ'8ιpU&Mu2c :l!у0%~-k 1n<4CUwӥsYDdGoFT׻+("TURBضeY"`n2*WLoڣ|o֋|O?_G/'WHYvh MVS DtseߵCwTɴ!")!3ߧUQMzp҃f", uR8r$%I$4J+K<aTZk-ZtuE!(1&6Ax.%SJI7HF0!FQlqt6 -61VwmGΙs"sViuwۦ*'cdb?;B!Uo>yiw'OOѫ/o>J1ZH Aey)!zDdh#P]bJ aC@')8Uo\}reMl!c-!+$馩9% 1accj~4s޿O//OMܵm٣ٴ1qvzcc @pl" f2Sŝ[j;,zԋrWƪqCwGL W:t#'QZ)2ۮIVqף @TJJ6f~2o(dmfzb$EH"ǔAD;J-YM&$PS!<!J*Rژ, Ҫ* ,d]A#DNDTDP1>v"aw(Y'U鬳Xkv-|%~7ZcnCjDYրv_B>e67|r~q>N.$g?p~s+fP%YpyjqXGH y6 [ݭ>9"-D7˳ɤ1# <~|q~" Eᗟr4alx86UDIǶeqq8 "4)+vYե)hE@ FNcUbj S<]?zQ9BJ{|}[W'=s;;wo펏C*r6.gpc;ۡt^/O&Zĉudb{TJ[Ap۪> S.H1y2F"_呐='~EI*"3Y=?˛; ͦSWXRD@ 4S8~OO3\yN:;_eR_}LWg|X={~UeU(w}{}lL p[Eb֪'O/>9uE^}ʫ+m+sAr4i< ᘘ#1e nG'I;vQ2IcI!n{8޼?ZW1a FɤֹڪN&Ťn٣pӳۛ.^>{%ua٭W{@NGMS*Hv"ӟ~76]U;k֊ gsFh=ZcKg&]CµƲ;4ZPXNsrXaQSV=~.[ɛi?;?7oWUӟ.O3ɚ}m"};?=5hpG80*T}6zD H>D$m0葅@5Ffzl5!` X0֒gi6 ML2FH)&1sE] `wF1%;\aX$W(:xzȜRSmڸi2 EDgL" ,Uag?}NN*10|૫͇ϟT..~;PZ[ C~z}Uד弪*E/1\R"9e8B`lg?}L<W4ZjwGcT~Fh:m-b8Zc /O}oR*ưU骺ͧͤ[e$1mU ""V{-)5+RqwǮiʢ~mgsQ[!!U1?NR׬ QbV^M,OO&MOx<ñ}j:uit銲m7\=z16wI1L()MR)e%%1R (&I<E OҊ ŔQBR$VH {):&F)2kY3]BEqR}},#3g$) !5ޮ?{j:qJYBR(gN)pHPX& wz~rf6lҜ,~/ @ ?}N_mCiyك 0(EV0@1hD H_2#%1&mњm߀@Y P]7bctY˓qZDP0C~ >t9͚OgI~?ƱgϞ?-Ne1ph6EH> 1vmZVFB]__UEu~LjeߐO?uZk{"f8(;@7/˫3fưu!&dRY#~{n>FB'u]eQe3iӔ&`2~6"om]i8Yq1Rh3#ƘZX^~O?6U ޿gɤQ&piei2Ȭf{ᾪs>f)҄rK~z$Ro֯=+>Ӻ7 m IDATnܓ듻6I];!:rBDc\{sJZ/Q 'NH $F' E Z"h͉S-[82AbȒk(Z;އ=iQ9Nя#56_(4 #DIF@ͧM]tX4eUjk3)p@%"c=n޾WΗz2i/ONc0>)MmԵ-\\Iֱa8EaiYו*gjv(JW75nsq-r.MCNIW?Ŭ̫Imnn!(ҬT3)D{/b F*gUe5|$Phm(|^e!V X%SR|cV@YbdO\aP,BDXP0#s*T8JnpJC1IH+(O=H BE)%D`N-!8cT ?s)1.0VDqQ7#5䧟Gc{ѿXEDc>>KQi) I%4qg@O6UdHQðH ܐm%K ˒LI'(J$sDZ˃/'4\Ѽ2 c1".Ԕt r YdQESE%;r{殚RE~sx:f/ hLfm:&6> :@Qui>KNo}6l:Te1DjSz:o QR]lZ2$1؉&5ED2">z@cFhFH~'ΒH6!C ׉QA"e` "9{ySdT#11??N~B*ѸE}۷_|f6:E]>sU"ԣD6fP_%Q,m?}ph>h08G*jsy s?^}ݮ7gG?iNO^~Փ8h *c4zZn} >33Gۜ4vy2"e13,flf}W7}Ӵ3d4m\nݿv4w#c0(_s4Yl2O|A?_2lJ Mλ}h1IHCS1#\V}=ټ]̷}v];T+X$v39 ^24`@U ;(+l @SV-Ue)9'L3fh L*à $' =!Вa 03I #'ju3W8gM}CS#,zq!+dABC|`C3L)#Q|1Ϯ?]U^D%?npswrZw|t8>}q^/K@v$s&DWC !62܉ԴP~ԇQEёcq :qUYeǘ2 HE)gn\U8>?{qUm}D J9h뛻iݮE'Ǔ8x_>0 2fW~{߶ò`6n^FλQ]y2g- m0B~ny4mdff6zclm'")}'8?Oɤ,=232:`B1(0۷UoƓ?}o<==*D1-ֻ~o}̎gq]Ux}J1!x\z"1o{@88xπطݶby<=v *zNI rO?SψƼ`k''ӫ)_:$.f^57kB'ޗeUI8{U%2Z/e{?bYUrn/fi6f?k DTr! 3gF D D;dffEY8>."9f%"$A &r2t&2LGTfّJTM! 3CfҬ U*BeqxkΟn~mFMs]eYGPQt0`"bS1@Yzg_|,JN@U?Ofu6Ըt] 7οEv}ITMY˲|rvf肏>, QUSVB Ey!s^8@w =) jMuݠ)s@lϏ拪¨1ƾﶻ2FwmO/^<:%}xX֛&~/|!p9}.}2I Qs۴{DDt`:糃Y5gg089e͉bQY/鳪*rJMcb׏Gd7m۴e#8<뺬 O#NRT.rt2[L__:99 E)"muf6M'*Ѩjbw6Ie}ӶdZUe9ۮ_mvY ii6Q Q]JUpORZV7@U7XaI]Hs:1&U}p>$ 9&Rm?ܾYmwopN!u)LdAb"_xGnxEFb622G`DI 0 ~lf1ES$frT""(=@)G5UMHSa'Q@Z)fF*&>Tu]N1*j5xZ(UM٘ۦxCSSEC!E hn<'G4_LP g~7,_kcrm_}lbg"{D쓚b>eL$}smH b.)z76f@v\-d|r|0fs9xvΉfk&*(3}/UooVm jm drJq2L*d<.hιn{}@`h\L&w>/Ν/, ض}ƾCWBp>P*>^sTPDbcumeUxs8I=*krPdI@ (l.Iʪ&>>s1V62qQ%or7ˇ]ϞgՌ-#Q5⥤&uV+7׋٬,, PCD̢UAUcn8 _;vl`و"3@S36eX)`s (]+xD"SQɂHTslCŁ P dXoSh 4C%"];r'dpEu9EI2'm1D5P@!:!B$d2yˏ^{rqvpUUP)?;f˲ȹ}SEҸڴGG# tD'4eap<)gDz 3#*Fl5 ,J8sS޹>F稬ʇv-?zUEQx7,t:Tm঳ӧӃ|ZfY]JPUO|dm~vm[L8B4Ƹoھ%kQGG y~x`1+"+ GM!#\~͛>{vp0Cvןq \]s@T0Q)r22LFdZ*c&LDČ Ǜl2fO|_˿_tG.'Ma'6{_UU]#b 4Ƭ(ݾo_߿}uݶi.i)Xm۲* i,qNŒ{1Ģ(hs2t `޹|LDʪ88gO~_ED(m)dCh:n妏)B1aLV}%u9L_ͮmڦ !'eYQӵ?UugQ, I#?<<|/Vۛ1M&cnu7mh4'l> c=;8h` ]x4NO˘# wz.v嬈?==t nzX9jŴ`:2\ `f<( ԓsQj۶f5`PVb53컦MG|/8 T L (+j&%7ow6"J|}yL̈Ypj@1I3EpT,Iwn2̔r/1e&fE]-!h@Tih;+yT]%u=NϒQL b>GU]vm[wq29y $0q$]25'mruyy#f2juopo?+ حvر1 ;4Mɾyu}qG1 T2>ŔRb$rU23 h޵ ¨ɳg*"x8/9±OouUUٶ˶_||].|{3u>oD{0Yb.UUS<8xqBq|R~x5OOfEU1"JΒ9Ċ`v_@vm#"Mg#"LMZovfm;f2S0`ǣq=M!wnrfU1f6o__ǣӃg_}{~q+?~I1ZamI |zr3hT$D{X(B E";n9Ld?RLRF @(PeYCץ!SU3>HČf6֎|q0'j2RNQ`bTSC$FtM_f$?y=׫x:gqG)jTI G@{$KH솪d&LΑ$ MTSINel AMeY "#@"9 YPd6ScsT {//Z.1%$d7tZ眂c&<"xbBF-G}w.Զ]U".x_"eDj?'w3`!w|4e&_U]a ]]LV";z+b43՘LcJXUs/Hc)Fv7Mس(sv9VE%`Q=bQJ1gCˏn׀y2.(b''w>lr9;;^>l|^n4S!}۴MWe)"sF>8TR>fOΏG3kfȳ#"t7qݮf;6v_6ny`ϓh:-泓lFab%D&r`ĻWʪ||l6ˏ_~3fLјX9Ȯi֛݃Ɯ bofG"nnVUQTEDV}h^UJaPUիw00$0":PTU9"b眫'a\rJ1x2:{reɾpeFb|^NUUY߷X= %&|ڼ{syt?ϲ3kډMÃ?~O~Ʌ+J!+sD,ޱ#duXB  "BLp=WrJbs`p3 J6A: TMSLhF@Ș: I>Y22Φͮ{̧(I]TeL]c3X faHE]X<9훺!177WUe2Mb&v 9jb?V}{D)A XWsP X#NA IDATQ29E;ͦ9P{əAM5Ӡ33$4#g\f5n7ͦYo]QsjWٳOd>Q3睊\Q(ʪ*Gyn闫9fv/"K+-9 ''x@fVɌLi0#*}U}~wmt9x2 c5p93E>h1?ysp4tŲ*! __ۛ;$Tuk %k3:CfHmt{P4 Eld1㱚Ƙ7߿.ʢ*a;"!Q( U)ksC˪:{R=ydyt7mGGٴ?էEhb˲`GɧDb^nv՛QBu<G*>eEŷ2ߞW5ˊ0甓I2@lrJPE;#b` * YAl[evH;&HĈ C 8"DAm"LՆ?SzxeYC/no0(Tz!IUri{f.37MFE]4X^_xт= k˛?c9_Ve*ArnzU(}`<,|u[ǻ](ʢ(,V``bꘝFcb61 ~@CCh`1͆H84|Q!2 :"4,9Gi6=僘vۭWشq6k&g?o}S<9=tuusdV'NuBgr4 UYMmtD̳޴;vvնSB[,|1/_-WjγdYUՒJl)?\4Gh2rLHbtL!sd|bs5Ut1#)ga1;U$A&f@!SEQ5]L}/"NQA,;gFF&Hr1FBd TC00&GEN:8eM"I9aUY;njeB98O*k۪,QW>ǣ1>/`漯2&5%AA;U$X7߾g^~h>!9O SZ}wt<&dGo]4h}w}~ͧ7_к; ?6;,1b9!,0d$" %gHl#Q~!YUTEIr*pgG9ŵm7N1/s$@].(RLl4㪪A ߵq؋h#"A˧gl>M(CU,}b@>x3xy pP?{ݴO6m۫YQ|w\..g"DBQN&Ӌx2F isCWT 7~,WcLx~~ZW#3ccLYDiirќuyx4-kvS4u{yUM1m*a'Wxϫ}ZV]3*F>1eFeLT{;V2B#VU,Ik6}mtmZج1<1y`2)x< WUbڼϷMDptzZՇ3!  Sb&&sLU"xG0X3.!cP,bM7K9< CӌLy$fLS5縨J$'j11;&$31Bg`)!+ @Kb)6Mb.ˢ*HU]p4.(mRt2r0JѠ$PSssDA9.u1M&O?]^]}bq.I*׻?w3Oc,]C@_M4MSiWonW_ߥsyE->Fg Efc4a版&qunZDBFGD!xD1:P,M AmصuU:q&tzr6YHde`_ "mwss|x8<<0"8C`fo;frޅGtvvtp0GՇ7M=8UQx)ANm9Pevo~}{sYYW]ov9gc6vlwY,gQ\(BU'Im1 CơA ѿ~ˬVVt2Oǩf1,ǘݮ7XUzgBf.#fbUں(i㧛79}&!z@ 3 !?|PF)E4@%S+Gd1kj6Rcof9P0(CpTU]W)hzSCvʲ~q8eQ}<9eb1DE32U%K9yH $lLDDF*"@ 0"pd52a@GL2&М ᧃ JHhɄ l1,"J6n߶-6\bqtS ef1)zATI'(;N)JIF^իWϟ=5;LFeIH̯?74*O<,W]۷??|ҹ1Jd<^+@ ;X<{dnMzUSDD dU@zPa vM4,Kǔ}ӵ|>nw)ӓC@H)`msUU2{82ˑnÇf\\s\ 䪬@ NnO.N'Q5. !g%3Pj&f"2~sǗ_>LGxs}^mc{fww;mGA*!GGG޳"23fUG3лwW?8rwmOgu]gIu7Sf\bץ,:br}vy Ɣr E88eAؑyneb'&A2Ld0[W@BDjR}Muku<@UU rhx1 uCFDuXJWd`4Mۛۮ:;?jlw45 D@ƒY&"xfclS0' so91P"bPLT  zah9JT 9" i97K5+CJ@XwM5 q P]񱚲wj^EQYwJ L -:TɣC5|qqr}@@0+fQ$Zon~wn]77EJX ]-'90݋9&zzpo֦>FQ ЬO4odCMSMʒ`x LH j+~u|>;BإQ5)тpEQl'?ˡ9IDdpDN0f/>9wqDUU=;Aj4}Xvϟ_Lf ;D$6{3 D4ɉo>|"|wwzXI\UzD2t}v WEQ'GGɤ, CD.pɀcpvwo_||Gj{~~ZVE=$gLY4G^ oFR" {I5I!U(Nq7ݿywS"ޅ #V ERGv l`'ACbg~$ <@uHȎ{D˒Hs`ѕgD!@6ujUyOx:͖.vd2+'gO=tFhfK6ܫgPĐ`Po LXc5vN99dB%I|" 0&-Iy9@kcL)9<e 5!vzE12dTv e`8}E$g@bMUTM߲Ȉ]6C;It6_7~VS`Efݏn<_1;$DQ2KC jD1Bh0%PѸ<xò(,0 pγs9=jP wإ//QUڶki2^;Y)\}L'tbf^O~ND9:ǫzۇ"7nDfFw/ennx2]LsC:l:"$'|ܑW?ˋY7lIlr?CqǼ9꽪j6I P[r! !W(.( DnH&5nv׫7ysiᑥ&l3d|X-4D(7ж}ޕeY|>y!œ8H1LHo^~/޾yr0R,-N!z1=,A]|>5`Ps]Jm ;|Xw"^cTŞ i"0 %#L!Or jRU!9'HH9DD5d|bZ5u$9j19'1" d=OP 4BM }l6-iAUSdLcJB!$+yqMQp"f)Ff|?c0E̪9qmfҤ[faͮh\IsBLK9#&=ڢ!EtT0&5Ḏ✩~GPxwutXO F36dB3w^c r7a)ΛCws`rӁPa3"3Y可ϙI")XF!_eNĽF+b"m2M|><>z2{x:s2'@aI-ٻ#4C5i{m]Χx4*4(B, t޾ j<ﷻGWɸt{L\ $!O!DHˇՏ߿~P֫ۅF5*8),W6ƔCw•OFd8}PD|n BC}͏$Ͽ_|b4cBV mb~2Ɋv^ˇ?Pui633 f!/WWoth`1I!&$p"f)h,D.$Y mD`,?2"b!fMf1z$(w#(jj, dAHy֡ L :~5qn:O/Njq՟ًW/ObQ5T;@/sI""&EL jԎx'D4RT !&Uf"tZ8n"0K!Fjv]lLj41s!jY1CGeZTՀQR'{c U]!Iot4w gՠ,,+".?Sm=ILJ⊢EYgݦ}۩օ560VKӳӧ''# FՔ0aRCDhf̜FǙ gĉ }1v94|L1~YBH}9BZ,'{i~zt~s{gV31m?fGa]W}/۶vMslM |4gٔ?ܞL&Ba99 fjml|uuGmnocH`6+'o?nwbL}u]eUu] /y' lmf>}q_}YQ!Gf4 þ }w]~aLFu])X2[/6& 3iڶpXMAUf\.kv4B-珛i&'85HjFB!!#XL:q"DH$bISR&v$y8–,bۡ!Ĕ8HC&TsDĒbTODi'x|hd2_,fê eY]+$K ԛ!PN] }6s"119=V;Tjmc#dĤ`F`Tx6Ky T.Dl`H1B@0 [L1A4"jhdq,|\G!oe̮,>*ue$1"αH1Ab' "Fc@DEHn}?ÿM bP5̆%jO?wXD؋.pdJ&b_HDiN@r!«7nV!#@ΉL=!@ &0A9˲,LAbOnƓiQIu1Փ˺,C&xh>fp0dho~yyZŪ*"^'t<oNOg'sE铪IcR"I<]"qh/}3(ʂyo6})iJ̶ڪ$ 1]جt>UE7jHlww7ݮybթn04hvcDծ ~?WDdm6fkC2,3Op,RNNy1""0QԤ$$0CMU5^4_¹`MԄERX\BAUU 3K IDAT9lǧҌ d#ȴN䌭%dTr:6T@̙9%;͵^d:Y.V>{v\IЧB9mVL cM"2e4Ƥ`Ĺ"&ծ}4S$;@$jh ١ >ĤEYT0BL)g [L4"&LnDjF"`DfRb/`Z5A]Oö]׋S@YMI#,HpB4"AB> 'W]3t2IEfsEq8,?S[oY,oO>F>n)9Ჰt4d>Nhxz:4YȱsNfۼyumB2eBȁHc1bu];Gh<2nkӧWgEQ]''9u>nۭ& Cӎ'礬/~dww?2v׵0NNOfʔ~uz:==l>B߫)uM9\ r_?E4!UQzo@mxwwphSXuYUYeAD!%E.D\Ϯzq<_t:z?@ZBnMwr|f UUs"^6&8J)f`z r,i& xL '2,JqeAEz1oLY|gw^3)Yw egb̸ ""B @ d5*Z2Kf|=&iӴD\9{& Āyz)x'3QؔSL(fHb5%Ua&LZL͔ SJYKc)EB`1^5RH`*BBD,dHf?A!c%HJj1Ev2B;a}_sΉ“0Uj8U Q\MfVBl{N|ᝓK7OԀ?cXCJ/!vNIS2M1LR!$U4B@ĨQooru%cf_+R~T-`0ú,Ǔl0/>x:# ɣG޹=}UU⳧x4`^x&DC8R44QJy{*$DKb"Dqۛ;U)Nwˏo eP376ѓ[jLdGZY6d)111[ (oS'iA3%H1Slf̰^ d&fyeB>Ɣ@.Ԙ 10c*Dyf$OD4jT#"%fF@$⨖<8==Ѥυ;_zGmwxǯoj _UH|!fgޗybȇ=Ѓep (M˛ͺ:>yι(ɉ QM?yb^ fί.Bnu]{zz~-|qvq$I=h2SfF$g@0EHL fᇷCOC>\6۽$UU!>=V ]ۗE1̠d< Sl{W~4˜ >4zt<_N-}`P:纾SvޅЗE hp}fC7t,B(ݾy8=uHd>lo?<~+ 0 dPֿ}ڃ1*ǎahm8.j5D)"EM@(ȱ+#&1%7wqqڣ TU􎈜Hhf~\,=:p 1)!@۵4zߴٳ+ɶm}sh^fKόA ]sxXoeƓz29'q)U߾g'N= YbfIՀTo_/˟'UUvzń虳n-պsn0 >l`Z-Ǐ/Ga6SXmv[oE廾z{;umM EߵMs@3zPjb6^ tnWgWUBqeY~0E؇L-`D A2$.#H,D &7dD"0 aJ)b&O""Ę=}(۞(HW@2`G,hEVK"DĤꈎXjPͫ^]=wg_OfMHFPK)fc Ly9YD05ȿX$C$!`QM y(>̈!á304ܽR ",ROb;!B#!0$Nf6>lVȆqUhXa2# }Dl;'Α j]{@`!FH2V#f TeLgtŏ// eufև(CbT<2$l:O)i^:W0ӡm߼o0PMBRln?l7ۮ=~}p=Ǔǭ"v]w7o^~x}\>ݡ9ЛFfwxXmB]<:a횦!}{_xROg(.킅]=U] O,$s̮D\}gH-UDz !|>F}EA"4 ]]bLy}.] DnXG.f ՔgaŨ]w]H1$dݾm]@b qھiZ$𞈘cMWmu] ZCSTE!C1i2՜iLǿ @,' bʢ)&s̙)AJ1Ő``h@IЅicJjD$;a)Y޿}/B DĈۿ  ?' IUQaR pf)HǑ8fq R}!u`BX.׿{zafSeQz4]__NgQ]U|nzw]%þEӟ~~r2sMmR 1<}zuyy6E,t>x2$v^_/"XtXټ N'u ;7 p#BTďw__ǛŃr>%!.n9`yPU]UUc{z8(˂~~eQ~ut]H}BH1'OMc'>i7LTD$mj-޹p)jilv)zPO1f!mmB<&0UӤ1MԒjtЀ,( ya +bCcCĢ(+Fd$ɹ21))3ԘDH #;1FSRL)2 (  Zi#3&q*. hq{\>e$+B  9H ; T,!дmL Rzo)@$@O06UPe:..Iv)xfR<n݃!x_ )&Dj332"!,DGR"d'yqEtX j3n FHȗsB]{}}YLԵnXcHEf''aR62T#5m?]ֆojGVItڜNOf@v]߯v۶ *.RS:~d.Ba\“ghPxqk\R3 E d:Eqb2[1¹*Yz0p"RRM#Z >׌^);G9 cfyD 5NDeQ8Aa!Ķ)IBB3D h 1D0F DBp۫*bt &M$Te,duU5Mc$foſ3?5͛7g'3D\ ZUx<:=9Zi&"VU 1Sͯ~fS:N5T$&&uٍuYzwX?l6dTTp<.;1M)*˟DhXl> "Fa^Y1Y"oӧ`vx}TC_x~m1u!cϟ|s7-UU]vݵ|>9:<~{6'^z pCFLSjfC 2,? XD0Zݲ"MY#LL2U4ޒt݊s.Gr%&UE MbLHH D82J 1ŤYtR$"Kb&@3#ӤID4Z$&)yn7mYEQ~Եwb!E e?j@!bof֫fd[TK!Ƥ f508b GE0,71dwuUgEhL1YӗhIJBLB)|UHPЅ<= 1cw*AQևiḦ>$Q , aTPR'Nra]W.³%mwx_ٌbL 3士ı'|5$%Եo޿}0_RJ!f@R* YtRUQYЖBC߶}OFAׇ}.iۖ/.NFz2փy$6fnwM-3%UYΧIYUfZ`P̰,K'oF$l`o~8;?yrc\=l޿صN|pf8LHL#$/`P?i]UmZcJ4vmC 4뻮.t>ǙNS,thCKdMx20Ls]f0Fz8(ETt?~~싲tX[IWcG@Qw" C03(lBQ0'-[,5j^/hҾhQ0?% iTՔ4奡bdto>{x69˷))*gp8( =,Vzo{bss}lPa]]^Ogc٬6h: 벪Tg=1DPBo&gWmׯWۏ׷mbLމ!^CXOC*pI1y'ua;&%o1B|yAfQSC#x|qYYBCBa9NSqـ`fL?Q 3n)112 #`YO3}.` y+D$Bjcb (c^ۑug LT# 23fWh8?]7*D}C$m5PKfR45Dj$B9O@B=3iv!̗R)Kh bBS#b1f{6 # RQQS 8.nD4awجeU&&Lcq,j))H"P:bEbH]}T *]=lq8u,Č'/~S w+a{/㧗u㢪ʾ+/@(CLb 7?×]4ڟgy\u1_$ /e]<q;`W/W՘4t}!!n7գd]w۾=]\ ǃtPׅs!~x޿y6%z|h0ԃh4#&Xh88;?hku=(A1 d48b=4@205Sv~x[gBH]s{{ZU(y,]jZMeY{Ǜzm D`P¡, -A"փA۶wۻz?zP..N*CU-44n_vaZܮ~? z, IDATFjb I]{&Zs}xmۻhPaMȒeHE8J ޱ84<\ /T ̢&3qy,L PxfIˆۨ`BL$oEwýTS`Ąa@J9;R5gt&( "#q;¨BQ`k԰(x8N6-"ԃ ńǵb2} [ą>mH>}1'F "Y 1~]_9 D)cD0ڱI5&^T@l8H"̨f3>s s''S3]=BGfZCYEwiĄ3"㤱 Ĥ4xPDzw3\fdFEmҟn>!tV낝 !DS5=nzML_^wl@4~^ @tlO/'NW?WUvyKܶm$"d2CxfkD,l6ͧ9AݮY}xwvvgp4TuɌm*Fl>7A9XX( CH1~Ү3dT_V5=1tfX. Tz5Ms!VUiz;1Ff9>bն(l~{[nvˇOgGW''SBL4>mnwv4|"u]whڛ{ ݠ.L˺z  BPp<(c>zKn|Q0q%WQEBì}t's;%vRx/ى_G>d9tdAn$iYoe!ME=EY$52!ȡijzPn{8&cDHȥs/O22{UThڵ}]o7r}X,8=ʪj!翩Oai`QzFG1'>tmfIv՟ln+PY=`a/?~vv6A_E2:F,tRMhB|XoW*d2*K?eHco޼xӳQf]L7ܠқcL}I>m3mXV۾8YjOzS }.=,7?\v͡if?l֛f7K_pp2c'mvهڶXۮ7}~~4/-!bbHދwy"M$R|a_HLl`$e0$9oR9ƦFRB(^(˒?V!}2}@Zև,\x)_&@[E!a $|2#U)ĄѤIs;BJĒExTeY]CՠxjR=2P1[@<)ٱ- "X7EB 11 a6dI!Ry[C hIc҂@Tx${KgMnw]lכ]YEaici( E<mw?~踮9DIIa߬Vwui'f,raUQeTEʢ(@2="$QT \qx}3c?˯W/_|ǺfI ۪*Mi:,4DEbec~Pɤ'%(&z" +aWēӥ, 'M H4:0"RD+DVy[T%& Pws2!L!DMʖ .$F0 sf- J@l2uʪh}ouRL hHRa"cH!QΜV=!a6@˪/\EOn_=٣3 ekqFDFA QeXh F@d`I)0FBgILI$ƠdB2%EBGIkXpR ih" 4MAþݏcњ}z=_.Iml1-6sIQ;dE?>&^7d2Q/.@ԏ;Y$7ή/>vqRY͟GI_πW0QYvC{onwm)IbLWV?C?>=>hDEٴ~l}(ˢ%S|X>崩cRFVq1r$]YkR̘da>==j+t:Ro޾|ww~~q|4L\LTBJ7}Y|vr|t|<P?$bL&eYsT "adfIA$Řn6;!@QƲB~lQ(KGH>áUM֚lRa]_˗ϟx\@r]i>Tuqq~M]98}?}vCߍz!ª,8~~7(!8}1mumQVlc V4&$@TFdcrQB1!~V²qZcm; U1]ŢY,ǧUUf(1mߵ߼ /(!2տ3?/~= ~>g77}t`hn+i2Ic$t/j*`DP6~tKT2>}xa 2WU?_xpͬF?eq/w9}v~<鴞4nwo޼^,=8:KJ0p{sYsrvv|4#kbHugIVeiK{̣c,§WŋILnT 2J[o Dp澉:k&UUr$vGnw!z06Y2hyԳIlSێCss{uy(?޳Ţ aL֙0n;{!($IQ_5LH0D<+!"e$Ysk h}~>0(T0!2cK-*)3A^,XcXRJH1$1%r(Eh&0) gadlkI IAB|>:)1 *I ̫>  檪ѿ{{ɈEQe;>쳲d$̑QDv1[!1wҝ3c@<#21#EI#SR #1D၊d1Cׇ@4o mYΕGEQ{!Ac:y{DN"H1};췛:Z(~~ܮb9} q||yxZU&Ω+tդ Qim01haqݵk~}v8wm?c>Da""1g0 a }u]{}vv^F2_D$Ň5hc)2c$J)  V2B$$NQaR(΄l%"2& r}OjjIK(˾޽dˮt-o^UQUEA1f`> JH%ŨIbP #6 JL5Jr0!*$1EyϓR>HZ$ a[ˆma kl(R̿U-džCJ#&7Ijqkpv2?z=;9/1 8 nĹbu ^v|d d,zlgNj.)zuD8=[Ӣ(:bZLz~zBNbщ1C8d>BD g@Θ *;Ccoonr棬7חu !@;C4Zcac-Yo/|yTUU87/r1{t~z}ߊH~f5M5 }r{s8kpJq=ܮao/iQ ;cU7}y?LKQ{odu{Hi޼Wnppƚ2%5~ ̔*RHILT51"t6kb-1JbJH0&%m&6UQ!C@ EgSLD o{D%$"@, +RTLԤD$*()%UexH۫<SHh0$1!fϧEY Èe)ECP$KGHǐ(Bp PP9 jOg;{p;abcEk1eY )j \Y9F TM\Yt}y ggI(Jg񿥔_ɓeQ"n!з>9;X"z=o|Bl n],/> Dtycy4fHMf,zcj\>}v!Zn͚|EU1fh;QL꺩Tl) %L)JRJҶj>,bnDְuYӶCaJ^o|1ϛGN9!0֓9_n+g۟kD !H* ?֙~RMf_lVOܣt^qTEXߪsE)lZfU3A'ҏfs(guS(* 3cU1|tQԲ䡏U{sٍާ1øן|' l;StmQ0Pºb>o.߭_]7 c7ǧ3CJnN|FHhSLAXQ2QDDƪB3I1yQ<Pjx(0k:ĔbZ@74(h3Wo|tg⣏I R^%#PY,o&*`bۀ" a1QpHq*0 bơ4$()!ȲD $8t E $haho4͔f!ddTGcOB$&eeIHhB^R6/+eF ~_w-|(<>>^X2G2>'!G#h(cnzZ*ʺ(xn~Ve1[LIm{r6m /?|9b@)>f7lf]sc>0Y~uUº߃kh AUL- 2&uͪO6E 3}{ڶ70LlE涪tR5[JAڶEyt4/fc7uٶH4Te9rwwn_~H$mn{KJO٫˻nO0tqY~D IpL9~Wv=3UQοgc#BJshGTlY{)h"Ҥǣ?z:[40FX.0VUe$Ƹ~n1>;9? IDAT}17oVqٶ~^7F&i<((KCl !"$M1$" `IS ARUy;!*@`#0d !IbIRLB`Zcc6ڧϞcw0aYլ )A0[ @H+{1ƌ`M&82A@9dÀLlkM|4U1|2q 0- =!;g3BUED uΫ:c$g/Wvc9SvwlӚM*C%.Aj0!`R  JPVIbFEBL\cǿS/g'&}D ovIbRf.sz~x:iʢ~E [!bLys@)ǹt:( A**'ڇv/ooWDd /)ՓggOi ?("c! pw:uSN&OiQC;$t19D [L+| ڤa iC߿{{u&ӌۮ|023!FgMfWUQ)SJoܜ-3c&j2}mao\.UiiO<gӧO!àϘmvfv4_ݬ޼yst|TU4G }wZ4BʙdI!EQ L@EO$D2dDѐ1cR"b$h,[C1>QX a8bBJ 2F@U0aXN(@ }D1H5|9k&SWMGvĪ8 ݷ `0EUYSQ 0PWY, la޾|QU5 )F 2cW{'[p{S2 C?z*;?{d$^1Zc˜!ܵ';:tmL'uMHއoj/类7 CL'r4,ώعv{{^U1En}*;./340taצ|xfIQ>f5A0eIVxqqƌ}߿z}ٶ!Bf{wݡk5llQf˅vɴQPkmSftxصv9Jl? )C;mUGGwUto?;1.;B|+f>XkMb>o]Jя]?􅫘<솾krۻ/?lwcNj2*\. ќEQICJ"HUW_~oFY7y~z:; C 1{4+,/._yڦ "Bͤ8}rSNf׷~noCDEAcƓe(2fzm،ȥqYz$lhW2\X+ y (DMIa dª*E$ HG3yR+Dޔ2mA9a!gVl2%QEqs,"eQ&)HY C1qPFC"H (Y!cٌȅ1llqR1X0Al& UԶR8cneU*;tѠ)A"~͗G}3?kp10Qa=HJE?O>X+kv}7֕EYc< m5̒$$]Yܰ)Ř/~3N4S ivdc<~`:kYg<}La 7%z |{bU əs&nEi뷛}81 WXf)w1(s0Dpz늉f{wusl1Wz'PQnq6URJ)=}r~-IS|^I^VeSVM]W_\n7]S5C~lYE4dVCԷ7W_}.z{@~ufgGgŲQU"*ʪ( W}g?w}Lgxb|Ӌɤ$4|~?{fv0{bHeذ>D-FEQE "!>1T3 vj!c2 s{Р|!Fؐ|vۭ"!n8S8&RM` GYHf> #JTUe℔2CDa() !ddE@䢬+Hl,0#*@T$1C`P21Ɯd$b{ADc2ZfZ3GYYsD]$hR}}T"1D.z/11„Ip޽E)7|ίπPΠ5Kv0SLdVfYmvzvj c-Ku]S5{RJ1VM7_ݿ^t^[71 ͖EY3)ෛn{dB)IJ~onV׷uNNͭ3CmWVuedztB}}S:8)˒޽^o1R ahi(< o1%!bvt:ml\<>-U&l:%.(%~oEi )XUE9zt~(1>φ QbJh96k˭ZBBH#|~FT0Y LȈeaHSJ)gkW_|~9AJKUUd⪴儙9q;|/o/mbFL+K<<:QXYsnyanjrMl"pԥAP e1153) %$EB̌*!10N""ZI){GPI%%1f3!2QU{|pƲaS&-Z'142m1|5KIn̢DyTDfn*8xE4Ĥ$ØY qIUC=R9 ł6NǾN;zF4DHABI!%)̡mEYom c˯><_r5!&CvqDZ O&8w8c ̔y #"+@Ⱥ,oo7oެ_~:dd*d /c&&y5Փa;^<2Ƙ=C߭68>_\/SW=)ź)OOT|)c0gcklێ]{hf)\֛}8BH]7 G@0TUUU)(\5Cab9+\eFzoGG9|n'yD %C͆OáZ\CLY$ A'MCH) )pu:c,#?+hSAR@5Xcqwo!j/L"u~p\y_yW*ټY"8gNE3`7y6ƨ=+w> 3 1 |3o" aP͍66﫸|Ҕ$'DBP@gV@4̆QHD8a,1)YCl7 3#3&@U'""'CΚ*c}y@ c KH >E4WcʜB^#RFD@ P)tS7 b$L@!%QQM!ۮo~|HaF\ q ]E 1J !J :LVa. aBqQR"!8m¹ %o2cW' A )jCkA}o,G36 *D_TRE?4̈́QPlw/)C|TݣG>tz?tڢnӓf6ͦuݔ*ru}L'}|>V?j-}?/1TU4av_Z5pad~tzt}}{syUUH|_m^#_o1j2ivuUlިv$[ɳɤ&A Hi2t:bT~ۥiBZ,]Q<( &oۻ뛇 1!1$Iv;n@TCl YUc٠!"a>>զ,]#"D_|nTH"*7FE񗿸ꇿ|2bL0^]md]f*@,v;ϯ_Z]]mTEAL:U@|zq|o.onUUa7wp&IV^$B@CǏ)Fb$) &R3bA! $c4ŔWrH `I! (Db&X.8x(p2`,A3L|H,HD'lrlt )$&31T@s;[bF} 4ovIc] !Ո<2e^<210j18)emZD8 ~l k>Z~14WUe3EKrfqc} D( Tl:]]9k^$W8t2AdF!IʐȲ4ӹNIEQ9SpMG"1@ߵl̤0 5f߶|}oߍont1I30\''d4eݔeUĐVwG?'|Oo/]9KG(,(nk~:k 1-"4Է0~..Ζ˹__1f;(n=E Wu)0Lgt4鴞ϧ!7obJGaID5%gl~ww_@)"n6ɤvE1MYQnwX6$0FRUd2S6u=[^^^o6bF>@F2aEjWVLyo\!? ҙ)(mg]nx|6A.a #2Q3)?;o?jG|O\xqJZX^ΙC;+ ,>Lw!|(2` ǦcOHdCCĆ28wp/ڈZl0 (d)jL1C'cYX)f&f1K0"1<2ׇ&0&UMu*~PQQ''ưd@Ш$W:g2y++ $M)adcd)(BcLݾ~>=!HUYC$JIB>lrD)Eo^ RǧGf;]Ikz՜lۭ)|Uu51 2$0sŘۍJ.yӆE/,CQ:Dꦮ꺜M(q9\]>G/>z1D?ǛwWq]BYܯweU4,#ţfYtE &_͏pyZ G!jwط")xEUIIvwwoȬbdIMTTE($Lߦ}k'אE-͖hjX"Yd dg H bzp{Pڱ,r 3-¯=8*)x> ñaMRN9 u3}`wqCa?^^>~_7|jZD,\)R}R.0mCÀ?۫f3/ś:"Bɴn'mTi[Gջ_ǿSL)~L7rIՋ|Vlv]q̒_}իxL SU$ڦ-BqDճճ6/osNLL)n{1R"~۶M'uS7MmBmp̸@: T 0w\!8Sǧq(wA\o -&qvC{UrIîƾ <OY`vM[-m;®yp j PE$)!&(J{`s@PʸqUU>_W0 D0{zڔ n@U͘׾C]׀=2੉OFR!TU&&T;RQ#,{rD)<].1v]Si B`"eɦHٝ6dǸ~ۛkͶ]om㘞]'_y9OMUMMnI,V34;S .fP009]UyWLDjn||Xw@G#oƯ;>~Q4S݄adSS3 #JSD\` %K ާ#f}_~y4/U]ދkжuS:T.FO>%iJ2|}}l8mыr6̼;C? 󥉦Wg˧fںnC]EMX‚d69[Әw1 Li}XRwNUcJu]Ӻc U55mv|[ 1vp8tCg/朗gH*nvǮSN2r!("u5Maq:k'-3_=!px|ZqsU{g3jLc))֝'R弩C! ?O( IDATùC "}vvOsc_FEd% $ c/GRB0"RQ;vf=;GcBǧ ݨJH"da%<ʹl)'rAՌ4UE-3"G1d)o_ҨFU1'GH8^pe󳦩p” 23$L\AYr(24yqiKdPۀD$@ͱ,Cf\ zF<n ffqR:PXDTj#qH51d".g˺<33 P9א1fI"V+`o~5n_OZ_yCE8֍"9 @ ^M'un+F̡nOED?o~wA_{ Ϝ%S)']|ʹlC'㨫3oURBwHo^iϿxWu4a{ Zw/^򕗿p,!ǧ8u={6[̈Pͺq^]s5TzWggi]?@M3m;IǡM'gg9c7ܥ (OӜE$Tw]ʱb1P;bءa\==n>λBtue65)vǮ9{U&mw|1Pa޾쑈tzr~>iJd!Ø֛q:.Gn,&ʞ9U!PSrL#0sff |I~رsT=eX^H؁7Kv|Oq̢qջt8v2_]T@ 3c@*)5)_4 P$6dINh&Zen`hHf1UȄ?-?D#?z{f#6I&MՍT7*I ybOUɁjB3Uf+9B0"C#dfBSMΕѓ+BIň 1B6+t+Ijմm81wuSOjf@Ε"⪮hK(8sJy DcbZuUst6I?x|x|3&c֜8 }w0qm'qА=CO9!xef9ォJMfl6RehrxZ {#UMᰏc*C"q9bPRL)gfԬE}4dC5f&bCtQ]@Jd'* [.Z9dž,KLA UsL"SQa n*uj]`r &DrߏhHf@;Q!`9'C(cH("*$PMѲJ"LxFCrb_!$*R˻i:B8q/^\("J9]`D3c"@ 64R8˙ 2VO}<Z&ʈPW>0~Uphi6CMb'pE)N6&uE% S0 CU(49&B@r~vF캮O|/_}l1΋X fI4!#DLs)*3Ǒ}߂3{@,#RbFpaw=l>g;_d cơM@Kz:[VDФP%DZ,F\*ҐsP~ʹ B\aET᫏? zǦ w~w}Jd5QTT3ΪviZS1ABW+ oxtM]L'; z~Dvzeͮ =5M]|L9:&$?|} 8^\fITM31!z}w8?usqqXγnnl!.b>3!v!uxz| ޿zy75/^[08XUucC]Jmil'T͜c]7$"LPU]ЯVR_<戆qW %_lPLH 0 G26=E/5i%>G-r& #,1f%U"Bj*YDLɜ/Ƥ`jHTj2ms;OλɬSL14*jD);"C^S&FD, 2I\2 )f՜C{6dvu)jP(I53EfS˳Elf!ZR10ZJYXA),Y"9$rY< Ę! `Ǯmlv/^\,W^*?c: z '3bRPUSށiz$ DvL`ZBڀvj)̎K}i!<~@wC6ɆBci Ld&UF8<3ys\c~1w_N'>; !y8z9Eıq̹zeKCT<0joWoeP TlZ&Wյ $)3Y]׿śa~N'x<p\gt>E$FBk4ݼjLgϟ59o~}~kx6kwժjQYoѹݐqc1ŘDm1oˢCM̠]&</cM&l;Nj>MՆs98ju{C13d2nDJH{3ҐfkM6"ȄD@$ǔUuupڊ!U6ơ Ub{OJ!HžG L Ј\9 0VK#"XԖ0u 4+S"90US$YE(J.)ުlD%*8ZY;2i.yy{Cv2$͈&̌^HP3Q5R?8nO3"䦩Ӵmc57LXU/;%D@MJ1ɓ jJٙy@>KJi| '@e(e ʗ!&͚sRFO4m <><9[.bh!BjHR%T ,%l6L |ZBKA C<:L&i}͚tZa9Mm6a)pq>fm~e]fF1ǵNf]λr:oh9leQZ1 "C(Jr~7?_L/Ο/g)c?]ӘTסgb!H&) ]]]8E7LM+_ 1)9/LE`DU}|s<3{&`uVnvC74u]7˗w8m;4USTM$;;3dzl1/v{w}ˎzeVFF&3#l9 Y:=3=7z>`۶R5bΪ; #p  9.K,2wcL~I-i1iTѫhL9Aߏu]Uu9/ƜsB,TSIJ,tEdEa̐ٱC@Q)w8l2f<><ѻ(fYXa1sɞPH1a z&2!d_.VY&cV`ŅɁDL Lͨ.w;+mϗYtbKc?TR4,b3QC"*"X)DB2F.2C#8u @@"):h@bffLZTxS3.]fﶻM&;=??UEv=b11򬩂w뺾xk~\d2 u B"I .Hp!);)zbdBtD`+&)"x!fMyG j|`ɇ:kC@Nbݱ)-S3n9糳88bbFU 9e_|= \_ݜدV,x3),Oa˫b:NB"!OO~g̻}*_5!'?ŏE˅0  l2,!JALDp膡g5l,b\,|6iq0o\M_=[beoDU#jmW*7tЏqEDiQ9UU5|(9`vu]gᄐaކ/_|tY70컮;{y0c7we<1tD tb1J>Wt|gN'l:`*:a0㾿~ozv ۼypS9gr0L?KXC[*J7~IVv fj66$?YTU*L33xS/i5>G^<8[?M|>a{xXc1Ŕj"Ni*;vISUCsDɴjٝf/_^~ |>産 rPy~nwNURt<cŌʊTl^|X ܸLmDdR{LP*Qyd`BU5j#;4bi[ht(HHvr;ĻFWPnS >1#{_TDs6SQr\xŇ `"HJHT!RnO^ٜ$IĽ.Ccj!yr@kʌTzR"F#@@ID@MD 2dhR*(fLU|pY T ׶ IDATfDEbڹsnjuwзMb$ u5|9|9kz1)'QMIs!YDg&TL ( &=WU(/r)7iL"El @*-`гCP-k.` `LQ3oU ՍK3ht:Oi,Yc1Ear匈5OWO>>M[ oon^?Qr>v㨙W"m昆{My{6wd!~1l:d:m!8n꧟*g+?nnjs:[@ fdJ־Wq'ͤþǨ 8Clm꺮~Eгd҆rqpLȞY'1_ՅQv8<;5qc<7mLqa1F(|B#,+w|L q=׷o]#7Oy &DwM>3 xa2.VMS~phIӶ_@0&=Yonoo|O>$jb|(0zτT|^ 1wǧ^ÐA~wDrJmLꊉE (OꚈn\-m='[$7;u̧ӳoc#šC3IG ϗIj_U.TzjLoǟYNt75A|*94H>bSJ1ak;Ԭ@}iR`pL* >/ĮqA$"WSuA AMS8!FL (iF$!j^ Tl`r Bwޟʊ 28Xb6$R69S (1}Ů@UCQd2A@4w2JuTu  Y) An) ]VK)ǘoT훫B\N &,jY>>ycZwaV^ڦ)\pyf&JU D+I_Ό'e U4b22@$%mf*9k|YUSUcprr܎ۂGPWQ(αsNM[sC`>pbfh)qYah ԘrRTCfL`y"2sX( #sN ! }9iQ$d}ౚ=yW3< ѹOqxmp;w>? `eR۶`ޕmLlV7UDj [< ut2YohOF}Ia_^^$VтFm{YsBTUfߟ,g'G5&Dvuw߽o|ӟͶFm! Us;c1fS.Zm>4M*_`^yѱ?g Qͻɓιbb))29ǎEz}bs>W컮O"r Q40Ř7Mnj$l@dK2FKL ._0k&8ÀXO&㪮W~o[$<Љ 0D|Q"1)JJ&U &"2U1sTЂl iHDT,S7tg !g-J•@$ВN@T5Dc&NӋutr=,ČD`d`Z|Ʌ.9(-wV~VXh`PbU|}U@qH%X5aOuS1XQ@A\~5$2rz* QɢbHR}J}7L @S61dD$*mCߧSE{o "9E&IUT L v.x#jecTNȎSηFj_Go޽{1;=`َաÊA 0 Ωz N LRjz_7*]qqh䜼wh219/W//~v5ui'Ѩ0Oڶ.LshZ2txT.s!(oMg`$纪k\5~<UaZm]]W`M> *4MH()b6q"bLJ(ɏ?l7n{SU뇈|IW63UZ^.H>1a) 2TE2@TՂ>8G*Ps:;Ɇ.}8 UɁ"IЁ =\VDf \U˫8)zy1b#!!c9&f E.rvH7U|disP5 #"0+h@58"+=UJ"qU&&,H{/R'ӮKF5h>cUTRDфٳ#I &AQNJ&4 Nr>T3W1S_ W?# dS[^$kxhm6_y헽6͛e&/6dqr4?:9K/V]~mt4EhسshTUu0;wWp~z 9W)ƔJJb3C&#J)^\!n0$l$QxsƘl6ۂ|%>yh*Ev:>v1LH@1eUx닔r#qH9kb1O"KcLT10$I"UDz Σ )g9E% LD;Z,̾cBUfS"J)!qIz13"&.""k+1P!)39l"3@ d5@b$0(}%``bh dDcfMHnE?2EZ 쪺i[5-7L51A`b$r2cY Ą50;2:C$aAEy2c9MGf<BS&50 ;IPY!33;&Yʽd!0c28%kJqYAĔ5)  (>r(e}{Oп~*5ݾ}n߾sE8 *ò(`x@DAq>mk&nOO]o|;Z7) bob.nn޼oO?N'ɸn!J)mǣC6|> {s`"9Asѵk.Cq\nwYB릩a4oB}ʒ]c)) ~iGhԶmS$Ϟh4vr* }r쪦bv SJ_T<yjgCR)"Z!(c&\9)3R17U>C6S37Qlo8NJ&dfyƀ A1,7nꦩ}ŋӳә*IBd5e,,L:8dj2 (YLH],E UAvhL"%Dă"cF$!]x!61K+H&G fIJDYٓWES˪hX,ʆT5aRڡ *w*%ڬJMRR@#b^%V@+h4@'%#B%+X9*o+ P}jh"{IQ rM YUJDI{Dr?cġ>ۢo?mw޽s.(vٸwO7*{>^Ep2_|cģ|62:( ]?!#baQŮlv8Nf^]^"=u}b4MMbߧ/.DrUUD XYTLlf)ˋn*xy}U{_Ur%ɭJ})笊&GGdҎ'sgʝF|1-i ]XDr79e";]ca=LeI@;vT{/)JM1f/ @/ADhpouGJo5jXcE٩VhjI2;>_-W1C]Wήc>0APghQbr}MNPJ iJywpI6QT4D$@%#bTrRFC" @IT$b_8f uA1 y2q[7-dSD5sDH@ D)͙IB㔜A0y/b83Ϋ ) V Ⴉ8V yF+Z\T UD/19N9C2)#S*XOyRI,P!*Q D[Yb9b)! qﳇ{0?3TT5 9G6e_`Pϙ WUuq\֓阈t6Q` wb,y4j}>&o㶪ӳ#anTO'cSf8|uCNyކ*,(oi:ѣg=g1ǘ^].-K5n|8x.`4W.(xCP.}L9"]0S1?'v4nɅQz~I]E;jFŧ~E{Y]n/_ f!ԞyTzŰ/bLYcB% d32rն5`}y1q;Zlm `EEGw"YVS3%%] J3`oX1ә{wqq_Ufɐ@.H 0&' j9gЂ͜X֔!'9 ,)@xZBU5Dhvi*??.fyGY  MPr6 9f.b lTMYW;D%] | r9rj'GD@e #C@M]q["M>kK94L ;DȁPYAEٔI-g5B`9Ů$e$KV~ ?{K@| u޾2BM >߁ow޽{1gAPLUZmfv~ʉFRAbtLnO.n4T~>xb_9Gru|z7m}651Q;&-"j ٫~n{s<*"o\]69kȈ'G7o_.qߥ2'ijqf#*t!d:۶ ءC$jcկ{vv7ӜEJ0$b)f^ﯖngjGL\Z"tn7Ef@ h!4 Sr~?pp BQ{C!{v_M%b4S0YGܹ{^;jλPW/Wb651r?D}}vDy U 뺡va_^.WYڶxڎc+e`quZ.W׮6vyJmH (wjݙX]u]>e1]CcLY4>q]*T;óg/.n8cHrNinUMj)ͦ_.W~#_SlP4l]Rz s4;&*d'G8rN/_}.if&bH!@L/k"H&b3fvNsL")hPuJƣqlrGG @TT 5˅ߺj&MUX TDMhױL'7Y 2 ꖓĜ%MDCgLDg3}vi$ô TUUl1+@'(^Ϟ@UjfS3sl~?.fϧNq?V|~_{ղQ;l6RND~qԍ_,W(Qa#>_<;;:;?_V<}|:P-ׯNN^r jڦjں As8h=j IDAT[n/ қ7W}/k9@۶}Q|9OknC*tvߛv$ `6=z7W0ܺu ՛7WD8Jnnh;'*hfu`hj DՔ5*ř5wZgf*j>9ϧwǹ(Fm_&E%{jZsrt"d3v4Wtt^mbއkׯ[}]U>8Q͆aP[oTfќCEbfEc$Z6HAiE5ă`.WaZW3vUa̐ةfGd`YfBKļ!w T(9F*[ Foi\5MUv^:v VǴl'*l{C`cKeBBgB 0"! sdH "1ZJ9 !P?޽?fpJq^\GMy}?wbӣcQf6Lq;jϫRY$,1>BU1#b>Mfw ~xղm3ӳn}p!b+&=g)CHfDvyqż~Pr}SW.t,O]y7Spyǜ :aH) 44M}rru_O=~Wmey^'db)zCeucYm6n#]o'-␲Ŝ``"K \%6jhQ;>^g]\YY#ăKV#30Uvb12Ct7NJ^XlWYɨrˋ˔x4M&D)(Q @5#SEdǥh9,9!ZSUbY >dkr<>G㦂 ٻ+Hб#R0Mh` p aVcp`rKz69ml*}WDM,!aXCyw_WBO(3Α9dbtD`r6M`JsGTӟ? ?4}ˊ 8 "!kC9Ð0T҅|RɤmaۛQ;6zsyuuvO>[w7o,]mw4Ͻsgb؈/_;G9<dz,n|]vܤ~h`92{׿뗯_|5UDURzbx=WU5nӺna2m:|ƇɸnŨ`qbc:==/&1i=?hU@ĔUru쑙!2`cRR~y'>}?.R~j=Gٗvtq49L'BIO"5d-Ü~Xc\?HwWWO^ލ˫律޺1ĸlWOC]D<$@hhuۿz}R^.W͊}cv۶o 9ZOq;r^]x_9޻W_[KJ}rڶ&+fvvZm[.&LRNW˵@.kfB0Įv0W0c7CL[o ?1TɩģcoBDɯ_{ )FD̒MM4#erEg98\?:AD꣣;o3wēYwB@bpueY %!\mY.Wn: $(>,~p;ID5(R*;^%I,,BݏiY"RMRr1e#))יŨnNNgv4hՌDH$ LsJIc΢Y}?L&"^FWʹ59&DDSʲݗHI֓ӬH=p` CD~7QSNcBL~#relLjL޹fDUF7x4jbW>j>7_6/^q㼏z[.;f\̧ͨmk, 7Քb1G`z4:㶮}bFĔzGo^r_fBmf>k,uÈ4 bCˍ$%B{[2ITwL~ݩYS}z=ġi&W`k}̀L1łF- ~f[Aؤ% EY$n9w-oy>#Zmw;=:~~YLЌ4J."Pr%ۺVW듳EN:ir<Kf!GIubJqͧxt2YGf*dr0mhqܖ+E'"4}f&<{g,"jsw~޻h5"y\x!O&mb8]?LT&*1u[7S?~۽x,n6rh6uTU]SNe=G}ٿ?XV7NkiJ9x<#7//>{BoE//V}|8PЛnބ&L&mӴWWr8m5qS7iry.Ye C̾ J[vn_rEons/W޻Ѩ?;5Ϗ||Y7߼I9%sh]u7ynDf rSRvXo}ƍ٬ @5a躋7~)%BJ@2; d&9x? :س:QzXUy3O)gЈ(x'1gBfB t&ITDPU4SSio5N#޿0^Q0$m x73QŜJbfi 6˵WKPZMt]4U=~p."(e~v:C<:ڵd> {/j "1%$@SD$SbJiYyψ"ڥ;XBj8t}wܹ{/+=?{'|߉hǾd>v$]v9I T^5MhZDSC߼y~OsO~,%l@|:iq(ӫW+3@_a_$''x R؇tg^>0sCbιnqڬVrMSuӶWޜb6U2OR`y]G`U)nK91z r޽P0{\лiʭ[NjhJOCZ* bNhs635#Ē f"쐶jD歳k7;BUv$dL4zun19&v LDLy.VMA!%n/GֱgfTՍ𫧧ݾ'N3?]U|DL.Y$IIA%+a"b*PE3#WOL9 ( 8ެ6뒩!Bz`W_uj1BnW{gZ ,aTcNM;SY6j[)gHwIU jhH?2"x7ޙM_Q c}ӌT.7]C*̘B|SD ;N Q*ik7wo[,{/_^5QpG߬8_"=w۟g쇑^su^]zttOR GG1x'?../U5 hՕ;:?us$lm5g= [NcL䝤,$O?}Rj| Llo/sA z{ˊeG o0s9Dqj0O&{ٓnqެnOjhXxر;BISU{W ;G v˾u`P9 } Hش!a~0TUմt6!ry)mvn?t]*HߥnxQE7~Za^CZ)W.{GGpzֻmC*ޞW隦BBH9@5]FXם;_nVX Dk_wo<-%JH;J"3-ѴY"bljD%,9.j&ܼѼd1YmLH숐9WB}U5rW snGt\ݘ/n<^>Qi{7T'PWTWzO#!>|Jr]80/DV޹kfw]/_ՐR7Ħiy6h]^Na$NU`v Dy; :nfu%|D,lSrUEk"< $(3YL+ϦcV*@Nykp5W IDAT8tB HNjq6Yb EeQjc$ΌneYw8PQ"r&iAgj nkgIg6Z5F@y xI G1<%A1B|=Zז‹7__M޹Se!A@TkQD4׋iYU@*eHU12s@f.<OZjv0! aC@$̮I17{יJ]S*@PRhbհyO8^Y Oubnmub}مϲ|/ܽ{WDxIJEv:-,|ǕT!DÚ/2|acؓ"ݓ"Z1zA DFy''r;ctZc4ONκ\+6A}DPZ9P{A7nmWEt4z]v|ȧvpFsgg=ysꕶ+!8S9% cVD`:sRj IkFW!Wp&:]j}76/i1F@ M"#&>DxBֿA)˚[ZY V#Hka^̧z ,A#WMuE56퇧'"ABf.Rc`(ONmkMTB G%xAaYEUSNѩM^G!{}X"&EUbdfKE(ypgΣ]xhqvyki~zjRXyss{޽f0عG{' ni8.{6J)`,*R9rrjRZYػ;{{&Zl&vv?wrW$IVmQ4"΁1(w*&ncmrv6J1(Z$0 eUlm+k~E Ctλ٢,\h9B|4!8ޯ>pzέ'ܸzl6%Ij 5d(@Dj/ 0hMTPT% 0G8j^zfn<2 H3) АzQE2|c s_k J׍q `<AAytivb1XN[0ǧ1M[: ,6ϦX%oHvN'rY‘#b{l˽nc؏YDH!D,&t"4yd8>=i2+W֯\o{+C̮viZW|p9чd_?%xt"c0O^q߸>j:X4;q%D0@}SMFQsG@h(|>y>]o\h^="ezY+3F]HX|ӓtVԵ!&qf & 1ÃCLx(|΍>72OOǏ#C'ϫ:U0 uڭք"@b,SYDhDQH+E"6|:ɼ2ST~ά3ȏBXXZNCpT Wa!BEZ) =#aIzVbqL$,xzU궓Xx]UbQqDkCZymµcc`:4cKj`iwڤTJRMbG$Ek%|1E9WDAZRzwo_+wkH7 󙛊o{pY/wc1z"BHNt\,eۭ,I2{cEQjMK,5!Fx< //<->\66{OONv>|zest<=;"41rYdž}iMg|^0R*&Iڲ(Fpٸ~mYZb}jO?it:vf(y:*ݖ1nmr|J>1l&RQA/rsJgň#s(AAD`iA5<_ 8X-˭VVLJB\#=c9eމ>ie&WWzy@9GLj0"<, PP18RO,,;ޞW#O*xp¼ e5sج,a1@)T`moow,2 aj*j_>ĘrAWtk:#3@##." .rᣙl277m^7}7o>]__++ˈf56ˬ088ټvβʰ< !(>f +Kv?XOߺu-!luyl|v_V&n1m?wr"u6.G ,ŢN"NkɳXǝnƕ% *ZۭOj0t;ݝ4Ml(]\WNC.?~tD"??g&ƍ_~bcc`S&3ksmU> IJn@,DPIZ2A=-gAVM83Yz_h-F_} DH PDs GnGe-i" z9 9<:Ey hB8@8qEqQQkt2eTg'O a?X@hܼg/|]{fQ >oo?/?gIENfѺ7ۭ_zm^ MD i}=-k*WU[[ɼgYY&lAeIYVE̘f16MR8ۊt6 MNA+dpr|-3<qR>|t{45̇ft:j|ٵ+W;D ̩iur1/뺪JR@DE9`@DQ !Ffk@i]e]WIdAW8뤭2"!F."0bˆ!>G0bd 1(JFc$E,ѻ]i ܺO39qL{x*'ZQO2ԕ/NC`akB,Y+J$ͽ("|t{XRd!zvo]ۗgx;4ܽ w7/o/.{7)<|o"G=_~*'p?W_}W~դ& |G+,M4Ĩ(V!(4v"af&reyne"|2-v$dZjk˲:Dn܈P+SV|Vxψj45Z\)i<w{[O:+˭V^ױ( "TD"q37vve ŭ< U$ke7\`Jw}7 l 4)Eb@kH>8(@(|=!D=Bm٦&& T Y ;wV^z9K(,DaE@@[MaXE e鋢Lly"RU 3BY:(A4pzF@\1{ye}cm=mRbVYU%QSdo1ٜxE My7.K]~O"T۾ԭ~H$<<'{{cůڽ,K4h=Mg| 1̦(OR:&Xenӫ˃~w<-~IMB;<:cVgbY֦J+ȢfYIZk&|v:NInF+ECBf>;t{ݧ~C~4/N |rn\[TU+qV""!4$,E؇56BMFk#10e #a穟PJ{<[[PbFmMUV(IjG3)*z6j1DzG9YfTsB@f(ƨju3:ImfSZkRZ)6Fa"Zkvw]~wи9 `4y<5ݦۼx_w~7W~Yus[wnMljZ;<.˅1닀Z,IRZkCdZ/DǧS2cY.ĢHDb,❋jĦ6I8aѦ?iƓ[o4/ӓ?9!=}5!D&2kw}VrG1=tz+z/veԮښ&)pʗ$;HqJI &6{]mwe=e -ByYfG'(8 a٤{qUUPA=ȳIbX;_ծ,|Ǹ p6ƺu]?.5"(L @,ϳ,,8rg]c༰ ;WOƳ#$?|x9_ 75ͻm^h^70NN= _xyaIh]eq}#MkBbce%{N[N)2JJ61F"y;OӄL[&̵gGʅ>p];@Ru]{6IR:pc|NJe?t:hLW֗VW4睟m=߸nq.4,ppq2ZȅIY0/ m oml ;D&uuXD)KJ (TI8FEmJiT&ծ@P),Mf/K0O:(DQXnwRcQxV&J!ʝ;;"b&Ł#VD @I,Zvnyu"EYUUdvme8>h{o`Qu5?WV6IbNo_=>naKW/|s046ibc`mlqU9>8c, aD`e57<(1bUYV˺$MӴ,]_&cŢ~'-&+9 3ll4I x9a~Z(MH"@Ynit:]*kR >IJ*A#+Jd$?[¼x K.*΃SPs cg@]/'v.uNַ% ]okև+KTg$DЀ n6cp(,C*MBBbQ:NiG JP@$t88<#T,^ U*1DR (ZyUkk(ꢨZ]Yݭ[\ﵦYQJvSOg?RۮxL؈lꏜrm+/͵k"bEѕUm-UDBU'xa!4Vg$(h ɴ,KmRfBl1GaWB@f ZmbL l$p573G#́Ft< h|vxtPGh6-?t܉H@6754v.uf"BĆ$;7gSklX !E9",--$1@  [YGw}/f&pTX:M7&s7S6iC"%m}p|/ŗ_z듓n'K=88zٜ yM{.ܻX:ERK[,"Mvsqt2G#:ib* R+KIv ۭ,u%Iremw^&Mېή\YZ[$)lu?ࡵBYV*MB\UkZsr6yO8O0ؤ2RBJ7%BPeU j. "*Ek^Y҂&^DNVu-k$ U]Dyv1z$E D$hmF"@CJqR+!r$Ğ=o\dg|C677ƥK}o|}{{xxݤ9dQOWW++R0M,eQx44EU5ZG.];=>v:"1 I)mU1B&v.Ae:XYR`zrO֕++(R,*RhVZkE{o~dn%E1037t@ E)!f(Ǧ3YP@)t8%F'l! 6d1QYy5Fl8/ "켏ʲ> $I#K>ƨjMn!r>+2Ā v1JXD>VOhbcg=|W ݻ<_Rz@w?+q1:I"EF9&Mw(ꥥ5c!%֪jk,;p<^R )2UE'7M,)"c43 9@UFpoN~7wt]i·3=I^n}leY+˔"G! h,KQ9Ȑ&i]Lg0: ONNd*9@\xyO@m6^^{K]h??ַߍ1`xom\oj<'tRkv1v+EPA0?d;@9rS~KKz%HB HDDijp(;%B H$VkD鶼B9eU@w(yjdy؄Ykiei+RB+EXX>uYNTVJ9BmLj9ƘXZ,Ha]UX86WFzY$"qH,b몮uxGU]{sF /6/잋c|Ro};;H'7ͻw7/./I~_+J4,guYHɒv1MV ǵ"cJsXԟ> 숐ZU+++1FkƦ"MSwT+0 G^bQ }s٧[{"xon ">=G$VN441YeU%!P{ CDvNCk_·y/,z<3J|ktXc ar`eYL̂whS;?E&/1F"υ?~Dkr{ڽ.K]e2^Wl4IL .QʵREDMR•,MI9:>MN."KKއ /|g YkZY\&Zѝ;7:,p4(1Dazn"&]Ԇ8x몮\Q,x2e(!y{{/܌2RY۾4F(^Oi|]^"Z)"%UYHé@(cAqiR0Ul NNN#k4)e!R hD8$6oeD/?D/_h4:!<ɒ"{K/ĜM";YbUJ, !@żkWյ" Yyv6.X2iyqJt6[yhѦ !Z#&MZsiRk+uU!u+D90 #"<>>@ȑF\8D滯{mnˢK]?G?AJ̰.VV,bJ,'EXoT4G?z?x^s|Z ;x @@iynڗeRJ)"#0ΗE}$RJ˲geY3?tIIZ䉵K HI.,"gkPXX @ ptyb}Pe(ʲOf|rrv|z'cM6 1Ʀonn@S/eo/#MBlۉ1YN+IƦX~2 |HK_6 y(Hs"nL~/}_s2v( .8gU^kwΗUX!xƓYelj77,p8X8mcǣ "PH*ѩ5OV|` |rwh4NF|rr V͇`%ĸyO)L\ԥ.{_#7&=BDyG+YHI&Cl}O{Д~!H6WVV,&Bsx "-k͏z[z~P/Un(+E RZb{>a{m/6<\ll}HURŐVA%jTHH D)HiUUUZuC`ck'k{wgs~}qgR%R|?l;ι!]qk[[[\, B`lac0HWjU'.q yffR*~aa4n2MUw!a4ҡCJ#};|0-L6X̏ FӼFx78TR;:sm-b~4H{a}ٷν;oqRV.W:U\yIV.WKRTUu֚aޑO%e3ad;֚];ҚVbk&V8Ӆ&s陼1fz:+zXȻ1hnn?844<<*R,۰qCgW1wzj| wyZ[KjR}K˗s sEh< Qs1fhh=v}董Gꮮ5855j4nxyz<߲HWWW6) 33>.b #j- $Qjg tz*¥l6rNv'ڍSY֞z=~T-[7'{"ٞk[5Flavuf0ZX^xv| ԌK\cmouu>MssVx#G؋tV" xM@;'c>Br  5 éSQ#0(5i DU9{5"$^UG'xrlcuι_8a9y'oxgwW&9JW^8?iDN6~9::rQz/Z*Ƙ|>?77OMM7'cJs|1֚Ɖa-i3c53ǝU0 Ĉz 0rZM⢈,..aAڭSS( e1ަS.qJ\UnmnرΎZqޫȫ'NGX,;rb1q5lpn2I#f| -gfZ|ԙZ5A4zkl=JΧ64R;ޏ 8}fEAXk꽨sK#^g)QJZU_w}=;?O>1b5qɋ]]9W\?9:Wǭ5\dNiH1*NS׃r+[Q>3կ}Ug3k5l16LNgԘrUfgf30&fCzxnOk xnmr%h7lrIrlEkl82'*b5F֓8nLI+eq ?=GD劎==lZT+LQ?{\}RfL&K_}?CTu`p`}.7ݞ=|g [zdM4}`I'C:Id@~G|Gٻ'U5ATբ(<ɩQ\&ة_^ZɇQtO^:ccc?ލ==a递1&0k}֦̥cNI1}[@ޓ1ׯu/mu.{ MP,^;s\5TS)WEKb1kky=###՟FQh###"ݻnVh/9ꭵ7\Ƿimk1F$N cWo6Hdy0_*Wc'ݻjttddtT >S"fс3O;猑(6mO#o;Ҕ1VxQ1*|̿?|o赍U@~s 5=7dݺlJx[6okL_W+7}x+&f Z/xyJ=[k퇿3;;w޾ Y#fLz>ȦѸĻo7wܕ{{/>2>X뽓wTDLAɎ7~  ~\tytHX ~o~w7V8ID祈sz]6m@#fQIV Edll,] |ܹ7~o(b/@4@4@4@4@4@4@4@4@4@4@4@4@4@4@4@4hhhhhhhhhhhhhhhh  dvIENDB`PKFUR-Pictures/10000000000000150000001C13B02DDB.pngPNG  IHDRzc sRGBgAMA a cHRMz&u0`:pQ< pHYs oytEXtSoftwarePaint.NET v3.36%7IDAT8Ocd"O H3X>48'J0{I5~ąL?u/ OIENDB`PKFspp-Pictures/10000000000000220000002ACAE8E28B.pngPNG  IHDR"*YvsRGBgAMA a cHRMz&u0`:pQ< pHYs oytEXtSoftwarePaint.NET v3.36%IDATXGJ@#hE*"/!hITD?|qrkۤL%LK;%[Yi_0g]IH y*[:) cSj!FB,Vq̫^02`EHTWs)ͥ0cEMbt@)R1 ;7)5E"0QsU eF!n{ wcjhohK])&uc@O3]+;mV:_$DT0ڽ:iEz,ЕI[{1LՇ+3|GsbM7>"YRł"p>;9<{ RJ.^bbDNԨ)=%Hl/mHm3,Miszo=v$IENDB`PKFnV-Pictures/10000000000000220000002A6266D5E2.pngPNG  IHDR"*YvsRGBgAMA a cHRMz&u0`:pQ< pHYs oytEXtSoftwarePaint.NET v3.36%3IDATXG헽RA Q x4K}(x3) x x*O1CNa@]M31]IU%], 슌lT_W9an\ܑZ8@!A:H%rI-GRCGb PNrabȤ#u+{ya$&v ,1V;XہDK|W}F=a$`ROh9)$z̋V'3bTv ^[+I{"8n{S/mJJtYaea<v@m7I#ZC"鎄TZ63y\a"GB-.odjN #!#!a앭0Rq; a([dI4ߓGn/'96w/$aP "Y͍u[!Gׁ!G f  Cb諓ι88ЊM>LȣZߞvÕ~&FwegVL [*݇^IENDB`PKFkd-Pictures/100002010000006200000074A897AB16.pngPNG  IHDRbt~*sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<9IDATxKNAEOEaL(ʎL aP|v@kpwursk d*sN]ڥx՘X!E`"X!Z"D,B"D,B!Zw]Q!E`"X!E`"vjD8#D,B"D,B"D,B"D,B"D,B"D,B"D,B"D,B"D,BR:H)c[vRDY/)kxH)]D#T:3LS[t 4N#-#/:Q;-G)R;L%pSxNʛ3C^9Z^8fDɄo KӐ %*IZ8i0շ ⢔6.#Ckis *%6UʞgmS 744/SSXZB[^\ɿٺ:)ݎ//QTff e`@]]p4y{aVTǟxgGlo87+C4<788p` Ǩ)yrٳpWWW/r^GK 莻Ajj7qǑg;O8"8`R +*9C82 ܌ᡪ!D|Uَ$jǘ/;G ~ *U{sd}hiAb"87B5d 2LBC~~ܜNrsQY tZH˂IENDB`PKF -Pictures/10000201000000480000007D84290FD1.pngPNG  IHDRH}Z%YsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< AIDATx?s{k~@J b(iJɽ$PB |oV| "B xl)BBmϤՠ^&X jЦ Ş'gvgfwvg gwv3=svw~cDs+3-CL6{t L<̤ed*07)OM잒AJMx9%<^l1q(y3ƬL7ȩ}%Mc[FRfc |@ZY=&4O2&VvckEd}F҃fqmٖR$($kfI1f5pg5"#ğ4ΖlD*<&yh켉cV5E?%kfoU9RxН3-N IS鼙]ibƘi߬cA`eͲh <%"dz#+pY|DeR?ܱt-'BW* R BF>[:/h] [bdy]ܨbG:灭TTVt`쁜{X嬿+C-xN-9<]oߌjM _ӳ%Ba~LB4R#DD-ɞB\ *fnNv/&jвun]Ϻ諁݁ "6@Z-g7pu:6)do!Ɵ} )hެq﯐y yoSKVht`+MzXN1* 8QyY>O]mԥ  0|pkV3`FuG߂oѼہP9ԣEtTAi%=7yd=V=Z7Ƽ\9:QsTw΋-a1S肫1-"'b m>Pq+:"( _+:+v6Ė B1ц]w5q mETtt6y06DCl?6Q9 lִ.m#!޾W!>"P/_5uOBz"{]zlbeWfͳ],Fō4ʾhcpU=\<&5*Jp CoXي:Y0\DDdxDeoP$ڇJ-z7}hb5'=%Wd>p`mb,v̪-yn蚜-7ޤQ.=j@҄2ѝ1uoKMŶmjCM4_&PNtM3d6Fe}G%ǘ 90&ȁ1A(!{$& 90&ȁ1A r`Uj#qw>,ZRD~T3GM "df1{[%43[_5 wGjk-4򹣭`tyT ڠ]}v[Fl) ȧyi=G -h=%@yD HDvN>5B>~)@] ГץlBDTWi Їզƈ91poCoD#HDH @/ckRYR[ ؠ\@lFTDO%m: G~]m6FV%ˁ i>wb(`wڈN^ UwT56?(I/Ⱦ\3TFh]!`m7@P쎁%m*0Pmܦ+ Z#HDʨ-'[FGU.h>ZA~hI >ڦ#Z%HD}5!b~i=WZد:ZC3c@u K2In @m JdNd UzNe.grS, ԧezetup2N,9L Wi/Ⱦm7;@D>,5v\uB5IENDB`PKF'I -Pictures/100002010000007100000071D57186CC.pngPNG  IHDRqq~LsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< IDATxm]E]5M@)mX5 mՊhR0+Ą?(RIZË+hj*ԗP*`i[dai޽sf朙I_9393̈ҭyUu$= m@HqZDFob"Y 6wқYA>:fN -f5 ì 6Q Hf1E-@J5*-$DF@XGʱjc?T!`0 Ddz9VW1bYĄAVs(VĜdB Z*&D@V=(4 'YAP 'xF Y 6XPOTv}+CZ;i$ls S "Sm<-dpLV 7fLdp TI`2V ClOvgV@3ٟZ 28nX4E W ȶDd 0RBrOKU@Q rl,#nsǀP.g[X6DK~A\&WvĜ {w/;.*Ǩ j;߽Ԭ2Zlwj piw7DU=YRcT׈7-wL7MHi`cTTi]&&Rath|2iUAu+8D#Edu{󝸵 U݃l{p;MmwHaU}Daz{Ddq"rpCxn9g`aC+1.gj1H8|;ư#NHU|1u?'2V+KDPoݟϫJ}I3U|\5mt߇IokF.@el+.cd ` vRD><˪%r1VID0*V"F=1`D vNS1E?qV< ed6q; ryB l mEdɁbm#f`>x6q#~Bm,Z E$0jPUoKZP[?!DFbZsnSwMLm  ?•m>`s 1*ܟO .`0JPCD[0{+1~(9~5"Y(?IY`0wE'[7 ,OcڕW0. k{zK Qh-O[f|;=lh_=aI#gu쫘Bk ?Ų_8S)`ia3D`)p(GQZ`1pُ!L—5CqP|9M0|*DL-aXfV 3`u&Y.DKNG[BĸWu6Q]v؋s:IՑr) G8lJC 22LF=Nxw}!'ٍߣ` kƇ5{Ru-m"_-`k1*4}Rt ڳ| / ela}Rca!ؽIؕY7I[h7hiTU\Y(s1% hm= x(+-@A,s00&ٌm=̖5>dibXP 1M= |mzfaNoUͺGHD`,;ؤzDۜ=]5gUD>6M@׺4+šTpUrퟣ `jz7mAVաR,[- \IENDB`PKFJ?-Pictures/10000000000000150000001CCEA3BB54.pngPNG  IHDRzc sRGBgAMA a cHRMz&u0`:pQ< pHYs oytEXtSoftwarePaint.NET v3.36%9IDAT8Oc@QvJ,?~~ EѺc~p41PO?oktרIENDB`PKF͜-Pictures/100000000000002200000029ED446F78.pngPNG  IHDR")lVsRGBgAMA a cHRMz&u0`:pQ< pHYs oytEXtSoftwarePaint.NET v3.36%&IDATXG;)Qj|D_"+ij@QN" (X'sefq̪5v8Nz=B`06ʸ^Z#hU+C>ANGHAfZEQtáҠ )nKӈZgE as>"rB]I6vT*8eIzAlX.n*,t1d2y=S%tC:۬ky^x'QfU*S DWre\x_h3-tyIF&=b8dҰB&%QeAµt~fɾ}g̳7 oi.w/6?/IY `} }w߶i4zTU7Z8uIENDB`PKFm! -Pictures/10000201000000740000007463912C7F.pngPNG  IHDRttT'sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< cIDATxmU1R&_\6F}3CɈ>D}H"-02k +@'&jthtbGk?<3}<}s}^Ю0Vrӥ # GqwHDբXv" qv`%pE\c@?/"\FiPc`-K`#"r";TDG1j-3~`Dh5tۀjI`/T-@c;GL;b $"WiHecO`{UQ<~]"M%H2C9Wǹcb)@2tiUÿl `)Wv(< ,i(g!0~KWb#7 \3+JY[])l l~Y%^3:!p/"噇rg}lN'mE(;r cKYB-կx6c ;3?`%d,Gn)gQ FSV2)m-2z+{_/( ~.hל7Pv0U4}Q L:Oȇ [Şq^_; ԑE~/؁mMi|ԼF왻$#&IS*!3ǀKrZTrؕ́RV{f;KtVtruϞ>%'MzX]^ =}' JvPr;͋S00aZc^4$ %:Gۏ&KyB,pcNcbƷnƁMvr;M6yvxΧ;9t@gNS=}{B } |7y֪JQw=}6} ]X5Q)3Pqx }A"2++wbIpۈeVa9I|XV ۻ%MueYzX)HahIR(/+aiZRq ZaŮOi9w(4M݁=Pcw*,8d Ѵhn!]c Zݞۄz@_03ӬÚnC )yn[u%[')?nl1\[AnKD^|T߯hZC{7۾("?(y& =/3/*zM(,ܧ&y&q6A;B{G'"#lVwd-/|-<|1;`2Ů3CDĒ> 5z#"Fe0smw58K;|e,uh_8krqAր?"υrn|?d[]ΘϕYƘ5~?Y AyW+jk]U0+Sq= : U CQP'@e# s> иX^C_P>r{&%qAs)7H{< PJhKW`Bh3rBʿł%WL !忐Pcbt !mfТ]]C UCf6gkصyRf #>= c4~LxzK\XWИ)U,L"u,Y<Т4.N԰'k$!TP<ǒZqq|NEr =t{8o+tc~SM#ʅi, m(̲4AS21˭1к_|1cDrMְXD.뙺۾3wљ. e1pG5\ˬ-LJnav LpHAG w%"5J&k$&$ϗVvxO*"Ї3hFwS#}ʏf0,~dJn< 9k:,ڣJYB-4'UZM3Q-Z0qv ӴhW-\@H<^UD~v+-xVn82Yq5-ZHG|W`SGR<.籾M^T$(('lUD9mY/6D:9PZLWeG[B]%vyvV[M|Xj8i u9{J=]C&]]ÁRՅlć\CW67եYåΐ칭> &{PvF34hj"lTn*~a'u E뭧I(Џ[0oGz3SOtQ1-v]+Q%g.+}E\nRgUfFLa`WxW#jdΔ=$9 Ax8g&0B]%ӧ-Bn|^-PWG- TŮz;J'U1lnl(!C}iPWGCӷ#@gAlTr \<|d4PWo`1y{oXnNߋ(ӸIinuM%%Pf"7`7iNsJ7Zx{ nQ5FbgA6K鑔97W 0Ƭ^E+&-Ncwb]{ɛe?`ǯ vوvkqjz\Ik:zhl UQ<~.P%IK-KDeN`R`( b%i=*'cL c{'=5nP:1; tqM잗}g#Ƙ9(u#S&0t?3 bCԄ6Ӎ=e $N;ԩV6]] :[84~o0$vM>%#IENDB`PKF[q[ [ -Pictures/100002010000004800000078653F831B.pngPNG  IHDRHxQsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< IDATxݝ{WU?rAI/f >HMIfR 5u,u&M+Rp|[[C'fNi( \99g\39{=߳~VJT5hF=*;8 S ``k'Q("'S"rSiRAPT] HQYYyX> WtA^Д ~"i$T{>i j/BP`I a~NTP `\^, Y1 FF6ʐ32Ն'ny >Ĭ:25nix<0KwM_&"z^Zzsau8x!zEN !8 `gl/裝MN C}t}쵣6lU S4g(%hSWpQ׀;&r$"{91VrUD`t#ji=u4l)>?G.G\zEnG/:uh`l"xB.%{$YTe;iLG$}N(g7p6E-wf~E|8QU4=-H?37l~5%ʎ(O6IUg`SO (ۡ {1e^^lTU=#Jgl^%ϗ_0RzFU6ˏ3ܮ Tup\ L1ˁu1XCyzeɓ.=9d t1;I؎t.p`?ץ@%"〗2TuyEDc;YөqrB{&<jd?سe 5?9'"u8zI^҇WG؆GW'Q'li1sE k 2iN۠%Ĺi"aU[X$"WIؘe]z_ N U]`E_|>+]^V|EQ!gŎ9g{]D p_"WCЮ'ASy?"$zuKS D8ۛ9ߪM/GaNkBbp8l \пA? {vfZ:;\7 hv=NIt5Ԡp!F2ۢ+vx6SGh"=~ X3=l*㩁V?8ILw옯" wyvY[ {9_>+nFЩ؉'cEdF?(iUMe`:SYzF❀%lzK밹]ؿ6=)ԒpmvOmPp46po<]vH YTGή PʓncClwfS3/ @FEIk)djWߩŀP11miŪ]ȑ<7#]Yi+/eu%auU t)Og%`\eqd] Uʹ"֎ʛWJ(¶KcG!k"ɞE&&xԸ4VuF]DY D|lw~1Dн-\@IENDB`PKFԍkk-Pictures/10000201000000200000001BB26B2765.pngPNG  IHDR ǍsRGB pHYs oytIME1N63IDATHǵ_HTy?3w,ʑ?A h32l!۴̓ >-!F/mAՐ4TA.S68jssw{q={'24$O eM0۸}[D[x\Bd@̊tu)]ZJH۶Ay9 BcdRPx"ÓOOՉϚE\.sVvyq'pLwW*]\a}>χW<$800v^VXI8t(ݹ_IchhRÍ, ǡ:ݹVM)b޿WfK?˗gógYY ͦ4@Kr\|Y%3ԔH2ШEN-ɰ =2`PV LXDRUyزUn>|MYavx K\X|* pX$?_d&GMʹX,}ww7TUUi33s14\`n~~A+DO[gbW[[  S{D@*+V^ZE*+EElEEENP^[[:v4ŢPޅ1EѨE;vi/oIENDB`PKFF**-Pictures/1000000000000C740000026C1263BB6E.pngPNG  IHDR tlD sRGBgAMA a cHRMz&u0`:pQ<PLTE  #,3;!%*-15:=##44#+0;?%%++335?88###+++222;;;CJSZckr|@EIMQUY]`ejmpuz} x,Q3CXXhh"HHDKS[bkos{DDJJSS[[cchhpp||BBBJJJRRR[[[cccjjjsss{{{  %#+.39KDHUX[`mpsx2E66vvOO]]ݚ綶Yrΰ pHYs."."ݒtEXtSoftwarePaint.NET v3.36%IIDATx^y`[r $,i˦Y-n)ɛH 2[ս\ͽf!~vvɛd 6wzL7lKvb[=TթS^yN:Sԧ~eA@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@P@hjVz Z<[P@P@P \^ \?@PQu^/ga/ G@xd+#( (P+ݪ;!@G. Z( ( .S؅ҕ0 AG+q(@l~f A恎Ga@+".' :ݸ6 (`m:C( h* ㅯ@+$7&~{9;oBбpqRʥtXt$FOot,Jnh:\NzE-Wn4Xۺ{* fή񉉉+WLMMOO.ҶD2m+Ҷn[umHGacgf~''Ѿ,|:;1=7n>zzyF[< QC {c Gjt,iб`TUg_@8oP$gPt,\ M8t\yG d@3 &J @ &_RFa D)Q >p `IƋ 9M;0 r/t\tyI-\*ݒ!m[VqF 8a=0(c8:OtADZ.>iW [js6@-27u(Cp:sq*?J1W-K v@:\V.H6@N"9׏9u;7Y0Ǐ5Ǝf z(]|#/ℰFl 2.x9K3_htnQ?'qб\0K4!:zUoT45tu%t,S:Hts&* 6|@r@pқPHRt-L-B#oos[s+L,+xDF !.g0b$5mC*bD"u&N_SMp˻ctzr8zݷ-A_pov o K%qGU,-b%DBY0t|lGq'N7eig7ȏ::J|D=B*R9s<+ZR/omG^́t(zTvTKa2tY$(E ؔDcGv\>teNmW4\~gmR-#┰8=wÀӛr=zIر< 6iLLM#7.Az >׊(K|Q8 ZSuCQ/NtJ 4Z.H`f;:!Ap锻(Nʆ,ʣ:Qb&nC-ǩt#GjATAؑNm]Ħ[.R8eK$c|NY+K9 Gַo~w X^-T>zZ^`O%E(NnbX׽MfɯQ:S#CdHB' I쫈U\"y ;z!]}t* cC]0:h3BE7hX}D˕oi<:؁S(E 8u5%c#΃C4cA>ny֕vP:PCJZ.CI! ?O?9r L *|pBG7t* {Y4t@+z%XPt<[īZgβx=#gNJ4i.R_Ё2(WT@VG[[CnyaXJd T8T#3ǙasQ:\K}}b9w Dt E,P:J|CUhἒA~U9 7,Qt  G$lf=\Ht8ͨK#STA|иR_ЁmxpO* V{/x=KRc|M=ıa;& %TƎsn'fLqNi2$z0|XFmPCElJ.#a20Ri2}:0؝:J|v*:X0_ E.NEgH|Hr:XQrQ:u@F(c{& ܘqdYG~;;BKv?3kYys}:¡"v$vJtWIv]@kۡ^H5t;"~RB 5xV:QFEL+J j!i^"ة0nBˑtjCt Nԧ">vvw(VP?Z xĨc>0 }߬pZ Ď>vf1m*$ːӞAe??Q rCzf"&QՏs 9f |<=84ļat9@GJk u=L)墥F1V֙N,ԬRV1fTAI#(ߖǍ!%tX=S~QC;Z8=%I<3E9--[Oâr_Ǝ~ϔ&v={Ni-2i5 ȡsV*Sem(:X}wpt䆎&G.} ZA<4:sJ#l f NG7Һ*`NdD+R_~Ё>=y䦤G{FF;WCT=F\Ё0MU'6t v )AWSR9 GsRWx|b~RC.-9 1 â'[t (0ku8X}DtDR_~ЁB=zv}_:FFS5er^_vl!OxOO9fC-Q#5Wu[ /NV)Z6(j giY qT`I-cuiĔ_UONYQ8rz$>ЁštJ ̽~amtBkTA{P`T(l8Wu4C rԋ>l~ЁJ=ɫ6([m@Dcd3/<9^5q|Gq{m#Bi R oIJ t$ls`H5,rqnJ6U(et=tДRe.HheS踸O^HvtVE 蠱(W=|lzO**`]`0zчM: ѝ3tvfukt:<S6t1'ْ/VWɨ^Ϥf69tR)w;ChPj>q AQ#}b `mO-9h*5:h8b *kSFA? :X:@]krܫ"jp"Dpz|E_hЁ:бɹ u{?Cn t mz1tX{v#Ƈt*2 cN?CYu9*u~S ^C-An@G~VLc&w+^(s БsNQB˘ҁ}p+BhH9RNcWX`k. *H{qv88 $?V?,ߧȱ[@*txq {{?NF+ Wo~*(eWY>БsPV;䘓,q'QA|KMjge9sc`*hĸ~ЁS;j#7%kY :.t(׈@'g,ʹ%5WGNɔzR4T(Hx[TftlQ9L{=Ř>Ё~F<'ŝDtMX[%tpCGIGOȍVPyeZG>H (؋шBwM>A{wб0v %WDyv G!u7@ǶV o>灙?1s'K]< gi3t1fBz{Ȏ_)ZƒGV#tXlwA*,bИ.B]rC^?'iCx 6ۨ:ibHdhhͨFLweTU`+s Q @{f8UL#qPY[LNd7,"m)=$@9K1fʩw}$AC${6A ucd4,E VQHN].tXm-(݇nSr-X"e)vOJEr^yɱ뗈ArB%挡Q@@Mzg#_6Ƶ&؎Iv}:V/r1_D& v~8 aSCH7"C z_sA u\"Tv T)5q<)S6,BN{Z5^^*#ݗmRdYH~"It:C=ǯc819SRmh~aS騠cWD\*j݈ r^'%m@蠎`(>Cyx6{U݊舶 2jDUcS£r;Tȱ<#s0X:,ILoВƌG?cvlΜFoh~"c#MwБxg C8s?;DYu"nw+ÞMG4pth4 ,ĚW^t i<ٖ7tܫdtD[!dqFy)Po;N8VY{r慎9ٿ*:CU;b\Y Bgbgu 9JC,Oz0)tTAQ"t!ʍ4b0tHEIx ͙K+ɮ頤@9Va/^v^\oB?0t()! jZ9:NU`"Y+n|۶C~"]tn8NJM") 9ƻi$M8tIHnJ Bʧ9Nh\ : {b6tk4i.吨:Y?r RZ5{ 0kh#I:-BzC2;dB/v(cd+qyg(*9:g$U#KyֲA1T3ry* j̬;_,l-{DôGHQA >(7b,`I^z[:XYq| #"4s(tXqFc5P`{ą.ðÎ(Re 8s1XCn Io1¯b wɩp=b wv䷐:rк@G="NEvAb On _NJ("GY=Bi+h>hбӱ+rtm|휹AG.v=0ŸtDZu1i4v6o\=3t&z77Rv(Z1*Eap@kgu%) ; Q[X439iFV]3bzKv/K5dTC2cO/EIUu[KGqirz{}]+/"H gأ7aaS(z#D[aP,D\;AbTKG%h^W 谬ja1QG޲iq ]3 L-R,s Zv*їe.2v hoeDI> X2!y_T[W[Y`v u&+WD )I;k|pve6`ވAal̾YX""J#0yesۂQ 1/+Bc zStXvL(t+bIPǕ:{r ׂC>)RDy~( ύdQ<^.W7pj*3|VƎx@Gڴ$QW~O=d}#>=]uW<2:{,R8aH9r$ftBv\򆥨Æ\m;чt"/*#cwh25E )Js6DYjM%kGILihY\?i6)U|?+ Q5x<Odצc: pȎOȩiA+Bc:t.~!"}r8p5ܽ5wXߖq})WfQn*] ׿(΂cG'R.)|e\{ef-a 8ZYxaY*Sq2hK/!gɻX41t\g,#BH?t5T@WhvreG,Nõ}3z9P7Jr#БE&F[K`"1C`Z`X2!YqQY,bKa1ҮX1#t\DA{j9BecsO:c#^C\CAqq7JJr`IX;fڑ#U9mpJþ#>Vy(>+ř`sQ伍 +-Bˊ0+:FO:R#3t\# ;.=p}L 빹~ணz`ƵQF] d(sXZ@oEoRX@НF3Vlyf)i0Y)δlzq;ҋ gM)A :ǎ9sI8d|~:>C:°Û!9rucOK$vdБC "]'NST!c0Y)δ$\oJƊH1vF~|/zqćmTk!ܑruuhoB0JOev6\uw}=C@&) &rpqd) C)p'Uy cgȟ?!q|K{rz#>lcP.vT9' ~W5]%OIZm`2zPJ݁XYS[" 0 HDd;xġ^B22^) Ӂ8Y \LiMDF;/=ߨvUX:PgZ ')) }ɬgM|^H;,)e9+YJCs0rZ?ȥo%(G5U]P7t"| ?;o2ꦲt0!)TKjx砕^bxvT 9P* lyKR_NZh`WP^t!BxZxؠjk:uP;(f\vf2n ':ɇ7J C4wBty(  ;*1 WM;Һ:*Ywi* 6@P(Hփq!rΓ@եzgHH9j@ة -9@P {CƎ`~)śTMcyx/Ն- e6@GgVB;Q ttϊ*U5j)7+^}C$ak a %>\[G=8ؿo+%0I)ޤj (D;Ts*:<A\ \9? ء,@GTK)YǾ74+_3#lğ𜜊Cs&c͡ p; Б3{_A;x\gpQ7T|J嶾;4sZX+WʡR<Z{ՑCײvh3l28 N xBk*AV1='Xiq \t]T Y]Q 6x,NVU1GT=d22t|U:kntyiX/$%!x 8v.N CNC ,qX̪;VvvBtjrz Y:*H‰gCVU/Jg 1 Îe@+e:.H0tJ C J[?{H1q: aeTiJea-SdXerir\7t {ޢ^Jv܍Aq|KG|@ ?ܛ:<@Uܯh|q^lqe*=ܲxjdN/2rLP~_([Sw.U euM;`Ua`r)tT9kn8t,zMnϏQf ܫ@1{ aQGa:LA/Y l"=5Oq%ce3 ;(#sP-¾+U 7r$_)ۇZ5o` BPu_Hڦ CϗKq\әUiх3q7M]Vg*±".eaZaYٝ%12JQ5>.*E"~xS}:/V:D\ ΚQ[rMJib@6:HH.ܐ]&Gqf0rW!I`?S $voH)͠yg#s߿L+T9pT(Ga˥QݬJ8 {Y %?@{`hbu7K)tPC푎,9>蛴¡cX`x1"|wJ{Fa $,@GIС!xj]ZNʹO3Ózc!K-mdZ:|:5W g)ssE̖:Z]rr`H ǹ#gQ=.bH:&}36.O.R 6)9xe1x)ruu9&RxqU sL3b-Yi;rGeCʗKYsA*W؅-Th%/`/q<u?y,xU32&Vr.<u:r|7kWBGIJgiIv,D2_ƀ\ԨykB+6vY^M27mKGWB@2zE &j{FcVYVjoQo9:gtN,(jRqRp U% RUK3_n@I*KȂSk$"HJ 4yUAst$_ 0ĞU]~6ŲeB }XGe,zb>!*kpݭFG1œT9|$@* ~UUfvC tDjh$6peG3wi S!bt:R}/`fA*gciW<,D@GQQdKTkwl,ށn^SLv m*::(g[Đ#(':Sàcȳj@Q48h!׌b9t}'鼲[&'U,U=O;Ux?7Uլ@ǟ'CclM[(c:ĠڨG"q..:R}/`AcijmbYX AJ;~>M}ur`:*e'󲃴Tud=h4o*i~:ޚ)fx7t"S91;" z_ D 2vEخ]"GEݫp+Պ:c5Mָ4U5 ?WR_jiOV Q3YM" qGxy)gxizn :NKzAqT D}0r "~uY(;IJuvT:FBzm4vO+_/-vU$m CޜjBJxV|б%%($e ,*CތrE8)Zx;dv@: Ta&t>VH(y|IWex&(G}c)0` ބ:" #@G5`> W&J,JXq}. (tMKq?{#v1\ATZ9Ιي0:ȁ.\:9n>ʗg0SXER[e%cGWOQ0SzӮNeYBʚ{]9?ЁnKT:*rѥ %Cj#^=6 :(e(#>ScX GQڗ!Aa MgPvT; f @Gaߎ ͩ:D€yB"kn5*jF+ s`iut jS:ӈQ%st8_ ׏wg6HCA;tUa rtq%=:y1oV>9r%%PƖl*t(V3_n tz NG @< áġx$r0B곬 t4O>1[/2v\3ڱ{W:A4VicMҤ^1#'=Gv>UEj j cԗc%@ !$AZt8_+m*0-W0bųT!e[Lpٴu:Ju>nC^=c+9?ۨzGeÓ5rAVuГNYB S9 qµ \H疖(čLt2e%CfpAÐI6vT:Bt$2޶OC׌ 8cR䎀ԵaJ^|!A]k!6(I1K :KGevE=:UUk2|0@UӐ#՚X 4QqUo㒣ʚ[|~Mr(9002gȊQl/ )4#2Bs,=ttc٬ /^<  S:jAyБJhuG\@E5h]ՅWܪU@1`p'䩜UI4 XX8hCW y7:sj`≾c::U MSbGDH2fEؙăc~c }#FÑ5ràcy*g,ة}i˴03vt\>< fsQE┪Q( d1RC 9"y+l:oW_0 |^H}3E J袱q]$ힻ@e^apdͭl\?` ,hq(w4VJ@8t8>Y/vXMY<̗eCG"bpex^13ɫ tHȱd#+2Ł&)tg ƎOa߻1qB2ļȚ[| d!zQ랹T !7\ZIftx|x:w&KGY(!y:`Z(6)T;"W0-\l- 7H U)knuA+VaY9Qe5g˂Kx`D$:v O^|i+)tt)]:J iOѩrQ_eU1(?tgHvdeYCoxpkAe+ "kn*ptXȃ8#k헩c?PTƖ$A˔T:\G@&CkĻ{UO%С˱ ~ŽCQ]Q!V8_tT=$.H*h蠇᪗Nʍ,,0r)@F8!L :7Ulk9 Ga2;R=6a>rt#_]eͭr\t9t 40/<:#5O/ y<$'a)AGG @cp86)?2CGC-S2a|'Rm`Ys/Wnw~. )\@-):AE"$W\) -HŘ W:X*ˍ/dT,;FTrWuzM~1T@Q`T5H\V萑AH ҔΊNAV:_n] 6gf$0LPď^^6/вbG9<U廬ٌ%J8ct=Y/VԪydQ'.g&^DZ&, <4ӥ(MQF(rtD#t3Jt2#k7p`tEpBǠ'~tQ?0h sT?@T3 G"5WEu5&(@G 5 Q#`iEZ{%Se0:6a~Q?0 H)6cme3Ra I5itPk.z=W>BrƷt xN`8`؏N۔geyœ*TPlٛ g[(M>kfh `)*P#G/Py+Wнd/)>b'*TgL-tsk0x ֜r> @G ʨW/^_#.rNa+ih9aXu=@aNRg$alIwq+\{%U襢P*`d&(]OQmIKI vncYQ=KWdJrՈ.^qM):CHue@~0^8GsuTX0 :ۜD}Emše V go5;xV?O{qJXh@ܡgM0;~`L\ 9fXEW:WUw3)Ū6Օ^Ni^0ǯ+N*HZe ea tT9:2 ԕ}8jCǏ)t~}SI:ZӟcFHb;aFYB .rtTvQ,Ū3i:dm;{-k7Zݭąх mhqv?{+-Бpw9V]I8l 䩀qattqœCx'vWǟ7l )P8vBZ:2Ry,tHr)tT7k:Na,7Aca7t @eJIX+jEq s2&@i2EC&cQ)p6T(Ѳ]@ǯ~UQKU}+s>VObSGvZn@ؑ;t 9:r ߙ 8Թ)_nճB1L,0 a3:F|U@dXwhtTq96fdzbzX !DSbGQ;:4g+y}eCΗ[n &^i Ջ/^WJhxU X:NF}t?K@Qv*ނ7csƚ5l h*P v 2rTCq@ͫ*IuT:r+57:,_S⪠}!{_nYjЁ9T@-'5 '꺏p*A}B~4z~9'H!UsaaGQKK\vr԰n? |:ޟI js@>' :H/c0:[t m@GtRUÕ8|qȗ:׻ AG٧6V+`xn$ z/؀wT'/bEÕ/YsAtP ~:|ܮ^սRqciG]3tu-A+yݼCg@l;rZ#@G6v?\9&&txV:k:A39Z1 mERI{o$F,A WӐ>dtTҁ cC^Mr^{9cG@Z+M*}[T:r5W 'IXDZb1JftJ"\kU#bݽzcCG;*qWv}9V:~B C~3{EC(t۶"tA,%\WD;#UܫCɲWm^-(|]RcBs TC#3`d3>&1^QRtwC;pė[^'er%tva:"bDJcE;{Zݛڥ:$4຀)*^tiϙ:+#׫ƨ HWg5* _PLJ:iM& :F[}㜘t(ݫLRP0dB,vC_;rR`:X `/|c7rkş}Q{> #T"ePE]pXR 3H:9hQ@g7СȚ[|hiB@(ta9Cā.z3%WD:Qxݫ: Us$?h,@@`\! (aGjȑC)>1ӼUEjU@;"h$]k;V0 *HP]N.VA8yDt:)sRǍoN\)[n?`Ϡ@$2#ҩεR`FD[2Ωɚ[|~б[ :F^1Ɏ8$_BeBBIU^e2sn7z>'Pa?89ݣæ߃f)vfY ɚ[|aN,tĴA#!a@\]5A#R#3訋z.xmu' Ese.=H;R@x W:/\H9M%K>UCEYՁ҇DGWk(&Q*%Hwah@5H;AGۤ QG]v~* ͗n*;R%1{ةCXba\u:!oB,]t ꪜ1boSaTP   7EYapdͭl\} $4Bj,U ajaK $pj"E)7:^ABqj,\c3|]-$ػϾ77k @:*vĆ@::8 9*Qe萲V7_:~G,IapVqX:Pk;Jp*~>:묻pCZg0]T#&tr:`~)` d:!eͭn\?pAxFBQh t/XJ80q:HsQ{X:Kې |?On7*0$<n4ع3 T 2rL>>tm;7kr62Q`5VCʚ[|A]'t4ќUq'%ǒuݛj4:Fd"q+O!u%t?tT :,57rz/׶AEZ2vy:ڮy!Gk$u;(˜:I`Um`Ys/WpнΏw?mo+/;,e{ބ]lOjh"1F4I vF.t3TaYWS@Go<&رv`‡V :@!씉ڃCEջ( nwɪkgqՆ5rБ:1oL5tXCH1 H:F 1cT,UtXMp7dwwa@ccJ+a(9(IE5~;]01"c& u*$kn:Z Ex "7GN0tG蠶jZ:u<<^ǯ&iRWI߃ »1ZlT蹇zsÁ x R`*$knt8y "XwC+:pU(Xl\%|ljLvaL:m Ui;ԴP:AANTC$FYO̻~ +4:L%?"*űKmv t@*v}:K_'OSuW5mMw+ z[ռ;絽ģR6Zrq! 4Oر|5B%#V:;fG\!eIә $WKT%O??WGg/5+ h@>ks7&nǫ@GPP(<.8֖BP䰎#qnQҼ (`:2x{tV7a\;-ԛfzH@ )Yc JmTttrMBNx"s{YX&@e]|: BB ^jYQnb380$(iLUWN [zMog:&sTnG>ڍ\ǐ@ŽIzmy120 9bHɪt4nml7ri$ a P {8{aWc{ɱ* @Gցt̅Az.̦/2Bx] C2jc$g^)&9X@7 6t ׫yi{lOF@ U37waPLw@_N^Uӯ?f>:s2!wZ!cn)L:E2ˢpd'R)PӅ G1owwi?/N8pYZ|.6?D= V|x/Mgqk1+(`+w8kKбpǜ3$>vg=^ߪ7+?IV%r{ c,,` ܫʿЁ=hՐ~+p| 7NuPбnto.a]1VJpBe/Ӄ9Qx啂ʰ+N_K.蠥;pKGNiq YƦ (Uw7h@Gy2Pd`dCݑtlS*:}\Wn֠@^ L򟺐@kؾwM~~}|D Q'#Kle":-2:ԎЁ(m*߉:6SRQ㟧2sch`dCb(Puz~<qnv_q(A_XY֠@ ~pP4zP!@@GոO`ȸ qށ$e4lL ȡzӱα~uH9E{8&"99 Ч^ :,tW7 R:гoE#jz 8r) *Pnjjl?DA=Lj_3(XAG7}<ǔ @?u}:t?)l v&H&0:;AnO<hPwxSH@( :Q'0SWyۣH }A<h^et+{=}\5@-] !KEU9<FΙ-/JLHQ:BG;zcuR<0(Daҟc=rR @΢w-K(+wPA˥xSQ:LU$/z@Aa]v\ g3al#Ej01T pp)|bdp~^>"S0z3.^0iP j 1t7?s5j8  d@˵蝿=s[nMqdbɚ5&)Ho::f^m = lnҧzAtkO+ŏw9jqT EH_khU;C!Qڞ 8 2׉ V9@G:ma?nB6 P KSy_W8/۱;IRa =ܸ3cMsA㞖0(ž곃che{_Ώ=N8Ittdl `Hvrp2Ŝ:,砕ƹI1UЁܹv7 UEWV?r3>0b/N s-Q::J~wE=M:B%,[/t.WS߃:zޝ~W6U`@ d 3[ݟé*Rm L\EDtEՃ=MZ8.\HTkCs:ƤΣ1M%K 8M7W>pGKG.J#LCCPw<ԇ}( rWVRFrZc: ctT:HXGq|tVfiFi"j؜ dn{:?{_ϠNGiNÓoլ'U3X8ca|uYHP]dֻctH.h%;*Ēlɾū 0u?Bp@!jƧaHP g:E܀(e'Eg ugFQQ8wJj|w5/`uѭ@Gwv ZW7 Ǟv DupaS("ck750PԠA,ܕȱj0ǹ|A{C|αp؞]+X: :ULE V7߇z`YJ 9a0 (@,P U~4._9*`(5(ݫHn&t-tX$DT9~t.hWrst'>aEΥ}Qp4[VG@\5:ׂ `z"7X!)~mg,@ժ~k ::0Aq\ti:8#EV?:fX9m긒ar˧9؎^8r 6<<a8㠎Kv,: ;.FkNh9cDTj COPU([PDuc9$OHwNog_A ouA|]rz嫅@%nn:X4;7e9dN@j9cA+Qǟvy@*򣉬nlQ@Dt:HюBǦ1'tr;u'AaDl="eTq{r#Dܐs1+( u d.m5,A5Sb ؙsӞ;@Gc/~ʃ^?Kg/Er{<'b'X89 $t(%Jt#gwu?;?-- C:#g#NW57raCM.m:vŹK6jBm4t }E{8GGYaX {)g (БD=Q6/; B߀7lܛ6|51D":Ft`}8,G]uաz~:؅[C|ZH<(`B"ޣj;r9G^UjxEYX:Ƥv}|xtH N.%J  t2V@J*7U:hRS:acPh%^E.@GF:ibhtA`~@(ɃB)[ބգHȣ8ܙ6AzKt seA7k\:=70j(`g~SUECy"םtT:β$MAx(MT:Ɖ`> on  t{j`bPΖ6{DSl;ˏL-::tXmtTk@GG$xcƍx1iGA8 OdѾ}&yR%?ُ>l\a e˝?t]b},aA yCHtt_Tu Q=]e:܌vXF G +bsx9mQO]%2̽ s촯n57djaQjPsWA*t^Un:v nrt6A(ݫW  @G~ZhOl)U* @GEcًk7yq?G"{UܻPAIwwjw׮ZFoWXm@P Zw蔔eNBz6=Ǯݸ:@G~=+Ჯ#[n@$'W_#[c*e MhL{xEus#A t|}t߬jLtT:H=r+3vٸà@q,لqS;/qJ=Y2TKv`kP@Vat^eIrmR*LtT: Mbm:X:ӑ^fĶ @ͭunh+}z^(@GF5_"t?K[EzСHd/ P;Kʋt@LJP0=ň^8~s#CלqMD6@2\ t$Z]+si#t8-rl!,8YzvDt`'*ɪяh-aq軇-@*Q3:bK6 ڮkHtK!l8o(ɉԞ:PchCf O 69#RK:>@=([X=r4{pPf}М:c$!gK,@i,tTj8:8|ukmZYM7@K<|{[L%[tO\V*\ni(ͩJi踁~=lAvi}5TRĚ,l C|_ȖC,˄yEf~5Q3 tG}RyÄ`s@3 28ieRzik({&a"-<: vtd90 ynٗ:Ei|Pȭ3cc.* b@N[6o$[tt$Y?%$'*izcx*0Ѹw2} XiQ$vHc%>C5A^R~^灎G>qL(:8S.C@Ѡ(@@@G;D\6T|W#AGt|} ? zqqGNc[F.A6ǭ}vBWPx:::_#r1Wtҝ:P g'j7*]u;وYt~JfG֪S傥԰'|_%\:<}Q{ao4Uad= ٵDƠ@HvZr =U.@@G\:JuR:NJq6(hL6 G׵d֠@Htpr/ ܫJ `л tQ== sƎՑؖzG$ \/Y$3(`+Б60sizw@> \ |Bp Cccp @~T G1ձ\G&=H @a{P tttĿ 2t@GC- T#T|P*r򦯺S%R/&숦rڵ)t>lz*NbKae+xHС∖pq%8VgZe0v6:|@QwCU_4P\ 9#ȋ.С{zn ccPA157LDI3 DvD}jUk'h{;@G@G9 ‡ ;ƦkG?lK9~G=86@p:"Uh~:ʿ:B)CuHaE\QWUp ڑ:b@@ tD#XJZVPr_s0 ::#*PeMdP ݞM:V'S@Gs AJwwצvjttC-:,TP+G(& R˕pm l=tU#Īp.}uЈ04$@G|"j7X:B5,WtT;Xra[-nی 禫/Vq:"`bUn_:^AhRotJT*n`9ңMPDJv)1Ơ@N tD#XUʓ[tqIfthR'Z X:$,Y'%tXwO< F me CVS#Xj @G(5[[& ]tB.0!\O^{?Ǐz{t y=A@y6BèS#) V%ȍttTs@G N}1J^žUSw{ٮ@ю-L8X tD #X@fX!@@G:!@elt|K>|yb+q~ƃ@b¦Q#¹ VQ{@gUDM{@Gi׆tX,+l nfvAWYXUV≷u}jʝ¤/|ǝӮABtt~d?56kaY0Z49 =7U:kĮMxbIL[NkOI_ۮ8Ю'W$@(X:8y#: n?SYu=~}?##_2Plz#@Gڐ#۫p*G3ltwA] |s;q^q8s0@tXuSEs(OH}?*@@G7@ Q[Ow@GÐ+ VC5ؿ~Z|] tt*p>Oʲ?)d0:8Z)tLswl{ƍ0Z[K}'0 (rwQ+Ռg [{QE?*75tT:_4 :N]x™ХYܴ:>=CSϱkss׆2L{@?(O8US̅@\:LD.gUg^c.Yc 7 *@. JushU_DK:־:w`sKʧ@GsA,3+W=. Q3E&5XYC5qHYr<%2G0IvFnnd}hx}7׍sUMDacCJSjD N0R#’A- Q zc;N.k 4jSB>2QU5tXPr\RD K.HН:GRR!8=Q-x2]ʰBʤei -Yg:VW>=VᬿO~-w)}6DÌ?u: CAߤ~Jl4VF8۽B X6J;"*YDtd4+<6g\TtAG@#9\u;_$m~p9ut Y$Un?Nx?PtliJa&),#p rHzw'~{x_e Ճ4ېp.0^0:f½ آ\zpHv]Kek3LfEQg@[U{}}כ_Q# 4p0|UnA:"@G!UqLa@96q*j޵Ն. õfeԉJ2$5].uIxA^~ZI?wPY:~C.Q`)20MLKG{#SAǐuMSDT"[[] eGΉ%eXiO}18X}Y<:$a#멐ʇԒC t95W RAG"DMQ YwSH (TňT;@@GU2S::2ZZ0l ]YV'BnU1`O}#}7wE>.At؍oe'Z\qu``*뉪2il?-dqEydxX][HGGqS߫jmq"F9>Mjx1wv.1Vy8R@Ǩ"uQ)(QZ:{/tAn)?cmu&EQбE#(}Q"+<0SQ)nnWWi' RgmQ= ㎆v?^ )|/FH ` RC:p[z79'9s6:tej>{Q۷o޽{ѱ{]65)TLo)9<;;T4WϵM80*GϏKd UO>~^ϧp@GMGP= d1|WCWhhAX5n{FӧOBɓÄ] va b /4&$uwF|hj#"(l}W~mk+S1WU}"{%_xWAbut=nXZ]VI=X"+_-?u: < }Ya䇓0GmM͈m]:J@(x c+9|Q]qぅ/|4gSY}NK;AٯTO2Z73\@#'t0b:M0G`;rZ= 넾o:?1!otVزDs#<+5&cf9^e_QdPw ,-r: (a9W鰉/CD43dsCq|~}J ::3ij / TN+ı8#Y_pb|7ڏ㷳Ξ БlyGOH?Nx0A?P V-(qGgQL 6hՙO[1* KelDQląQSTy`ʷZҀoO>~2嬥?br&ұO"IajEH8)@.K1NtXV46\sGzF=qkS)6jZ(ЩHns|.%*[>gettyø e;ZqkKGI<¶L% ϝ'E㮧L`:Jhl)w0r,Gz7#tt$]C} 8L !t꩏WM:׮o3_xKs2禨'x? *~aYs.vI}c|ژ=S9BG0r!UB>`]YzLj ֏f/h}-3z'{*ҡ73e)-`8zp+nܙqc/wR~O1<{tct*ׄ N\HߖiӺOQò-b.\Lt #Z}:4D2KtX}F=8Ls=H)WD&=::2YX0h"'#w6ԣCpʈ::Ϡ:R>56trQ :vqx=rXMsj-C V']`5&K<71x\k3tXOs@GyL:R>:xl_ ay ~xK^o?Ǭ9oCo"&k}:ko7z^[P,:Њ陻w6ʠzir:5@aK%$C0tt藃]3>Ucȿ@eɈ,:rX#eË!ɯ~Q엾(7(syP짾(cqO*RiVDȽWKpY+G23E1")WTJk̑IB^dd*ttTcmq2#{\0ba%A>~@)t|::#0r6,ǿ+|Vl8-F#Cp6&"oHvDaC駟Jtht_T虻dБeB0-`M)k~T MGeg y6lp^xCБU?WH ܫv#2xfK\:БKW ?&fTXL8yglCtGaN]HY^tU$܁Ct|ABu?L:r#W nXV/-׈/2؁BtJ}fGNrSNaQ%@G&9BNYT#SmJH$ʦ e<Č #ҁb+7el⠓b+$MGm̫bk#U^[?zu8:x^OUxux%@QP`EP//( "k[_q7֑h4tCg@@ݐ,Ukk3=\2xէlH5#H` ,> _ii:B/b^[WWgV!` 8&#fZC 'Ӽ 7bg|X%뵟 -Jg k=F@=اW-ƯL.6;=59xxHevR9qЁN@k Qy~64|* z=Oƛr+  T]*?m4Qp$ E?dŖ#T#)%T7@/ћJŕDxBL;CRkSG,.(R`9>Ij𶙉|Z/])l##҂ h\ )ÁndxA7woyò:>^ar+6j~t7YN_ VQò:CY$|BHC M R{#q89t.YxfQ=hiy툻a;P86r,./9x+pW+d*ܳ@@Gɖl9xEQ|Z CǧcWHrXGj%:'<\F_]:T[0B*8iԔcig,N6yʂL{(/lB::t *¡2_Reכm_whC֑Z,9@Gc-3FN^ư!(Pk㠱LpX{"wcv 4k®X(l=E%ʷ!@@GVm9f7>c5ԩEb:jp [5V ::  cJ&ʍ%B#&M4m Q%[j:T:Ġj? ; myt:QZ/v~`PN Q1iA}O<]:@}UsttTseuTmzoCpҋ.5*I-TY\t:sFгaTP Pa .PǟRCjFlY)ܣ`@߸V |/2.ׅ, ]@GJ%@G2>lC}@@ 4VA#׵U}=jN'B3󘞀2҂P=5cHpoAČ+N'q!Qʨtòi9EQ=8q"q||},T1dԷjOBG?!+.QɃyP::R9Lkr4&HtS7餓 qdZ1lY?$ZͰ1(PGNㄑJ`*$BXz$Wf7VCԌrOZ+7T7f8-cMOŁKt\/ecIc@إ4t(8e 3:S?^CRdYʑ7 ro=@Aa[T/y =XP֡Nu rKY2.E{t:*؃T A%tt'A1tiVюx̏`X[G6gTm{Mjٜz G_c4˓ ntҽvd:0tZ,eۗZ{o=ܸ3Ÿ#Q0i^*LhXG3[i\[E=c0INeR'#T7ʨS_O:I}4:J^ɍqoF(l5yۨ٫4tx>6R{ ?)M>N@SvX[AV}e9tdtdys`W% {#OYKTלŏY}>IKG#tq1ҁ@hD&xJؓxgя$GS8hgJ3Ӝl@d+(A76OAQ:*M$YsߞxHBIAMGΒ8K b+p9v?ظ\m3cI]ur#:~5azuWN'%@G IfcHʁS#AnOz#dNb1"{}+3fW ;nDq̳(..F+t#*_xQWt:.ثw/a˻Z0z+p5_spafPB>Nɗ:~Dg57L..ş`WQv<}: @tt4zHd)&>D>F*Zqp1NJI'Q/OIW2l_VnbxrC5&XR2HyO߸h5:8Zŧ=pS@GJB9Ls X\,s!tJ&*iO9 +Ճ!5q|>?Nůmoa/ʏI29AO*c~Q\]#_eRz{T|87z(Auz)G?r'YǸWѕ]XyNRs5*tLtܫDYn[7^B_\)~!PgG1wBTGKgկ>t|dcz@]WXO_h#vf18 s _O}(w"g8czr=ln_sMԙ Ҡ* Au̡^,zU{!/&xJ-LZzq:k7 - { )'È9~mv&JtBб~3^"YO[RCGF&ACf1-D&]FXĺj#s~:R6qI^hp|& 1(|-R7{UI_ΚB%8&t|o`6^[HG2HGnDjͩIsDuAEjt$Y/rOwzOZU :* {-1uHkBǯ^Nx,9onak9ׇb'<e4|MN[ӘCdB36T!G:QS#?Sx*FgLpY4-}Ce;'=_U3laM:Ø0zoSS<֯|%O|[i\̱>=ػzsNB ":s$WH8^*NLA=b6*B"Ta^ݝ _5ܫ\ȡeAք!HO{)9A(tБgEX?H~Ԧ zho~ nI}kq:) uݦ֓Pa z?O[caOq9~ t$U0c6V| łsp%="tҀ}8l쁭rHXzAŃcA~˹d[7~MJgߤ '𫷿Rdôq'Z_vэ'U\#v.f#W''z 31 pR#oE9:&}k5[_:y8F;CT͡7-#BHS_ s<~XolHaQ ǽv2¸h6Yuk!@jGXZ7W舟].OT,}بgJcv iġk9s2gǡcv: Ʒ׀/b&W<7oL1DynQQ>Y7cKGz.6V֭{X>F>wiaU|sG/?( kSȿޯy P#ķ sܾuF $3/1ĭ:氊ց p#:ڏ'4DgSRGeɎ! 3^׃=K3W!ˮ5[ܤڈQn_e W`4LwYѡc>t3usQs|gH1/ݖ#vw}<:Jt%@Rי:p*'P ݫȜq`/x^^}d *#F f..FOHh;o6Q@A?~6t/M/Px{?jϿ}T =?nDyWrlpWR[eq qIӿx㉸6l;TR,+ӓz}vY!j}FQopr:eU$fQЁ@% tqwHt/G/9UqgǖX:J,^4I#:7s4/*%t41g> "h ;)?!:-o}t(<7s%HJtT1qi=]ȳsTm!UԌО@ĎMsUq 1;H(||rn)#%!c"cYC|CtN?"tL-ⶄn*lբ=Yұ!w&ZBNjh ܰ:UbiB!#x?Ї#@Gr ?8Q Qr`u!*"@GmՇ({^@m#³1]Eܸatg^ QIq)ު.l+:J^{sՉ\qMv<tI2|aw=^@gl ?h`M9>tڀo5=*`y²>Nu@AI6 OjyʏZc|\#^fR<NRG^WG=6pI@GжmrTJV7J™j"kA϶6t,pP{W-Uc CNAtT:!ԴEH1;q;_)t:i?B%YrR!sGcڃfW0\y?#R4%Zb6Tn#n/6]YVh,[$2tԴG|0{h:cчBIXu©@M4f;^x/0X(/$?ĩR rUu}r BJ);k`c:i{͖MU 9rR8]E9:p;aT#߈0r`~[ ӿWW t+rƺ~8+th$O= n@+Jt@ɣGeu) sܾ3܎`~@ŜC[Pu 5W= MtvwVDl_Gg 3ƍGu{B^B%PimErMJe218WBŒ+3K+Y-{8}-m kOt ,H ͿA3~Et_0u$V0^歄v@/:~FhB¹S̶q{褗UB(s|[9nf#/iN2|SP*aFEAoB:nҁ{d&M2<Kn@⪾ϖ;"|qjܩ6f8eF=2G) F#'!HMٴ#4f x2笡Cˆ翴OT$WɫzcVBJ{tx Ido(lޓi}9vWy`~:nFnC?k&`X>,Uf$)nOBĦ7гlӠ E:]VU SZСyӄ!M3dž"nCE,(ڛbaTXK3S$VWu=* ͝`)ΪVYC"v:>N #˂Ic;Dym:Rz/tAq"Hy?ʁ 5Wz!?%DGq }>q=6}27|,[_4H-Ĺ4N䘓O@۷$x>`78gwٍe:4APBפD8_1O$u]}<֑W#Z:;+,/lXSWP8JU*AGa 153rk`$(Hx:A/襎gɡz=DFW%,BCҝ:kfG6NB-IaG4 GgmF4OG_N|k9ʐrM\| 7@39ԕdj,Hy먃Jx4^=:-S#JV>>ֱ]3j m;vccǎor2,ӯR>dueiBpDH? )gBC@1cv1o#䇓1W=6u:-љ~axU/ ͎oБkɥEwWYκc4@4;0Oez/1Nb?5!dc!j|MzDС[(:iA,fwl=P/G.W d" OH0J9fv3Gklp@RMQ r* !ƐA"%: Fy1\$ H5t?q 0: %#klĚ(l$@Y|@O"3. #2E ɛH6f@ d@ d Dt N:_{`cK߼͒X=dxݿqgGsX: [@B*xp?t,]4Ŀ`ۿ/!2azYB 踩[a Ko".vsi4# ՟r026bs y`Hg(3@'w *FjAZ[k c,`֜$U<щ=\;B aEq&@IzinӻG:vxK׾oH Is}h"ֻcl,mБL?tUVOc5X&\q[iY\Da_d'o覂BM{P\r漝ҸgރuF ŕ TeUE&;2i2dOV(Q儵qY i}}Y/9L68o&Rhpi*:D% Gu(+Q9ar?hW^z*"bh('̾#uC,(htX{Q9;:NGsܾ%?7͕_MٍfIq!t\%UnNGhN,L$<>Բyy:zu6 6űbq{)Mk{$%9N{_ثrWO~BAFe|}vݺDIՙ(Ud㖎w5ćχe^{rdCduVxlG tX-w3\DM EH9n*?P";8[hK7gjlv6K=ք/9 tiN؁+kz*t@8As8yTڙ8A"qqP'lV:PqGUwD^S_鍘9t?$O:^+Nn 3eZAEzC, sCve|a H<`>=z(&~s*UZجzN$u9`ydfB `uwu:NK=p620us‹MW|Nb t|ſTxcZ-3'M_<-c ^d}&)KGB ؼ^AL'% tK+.gUTo0B8'[Zt R 1"T'sn8N߫2v J<f6VqwIB}!USOfd !(S!#bYID!%QpЋ-{@:O?od.VsG{NLgxhtXAQOq"1PV)!4&6TEi4eDDCqw 44Ԛyncv?)Pe-ˆ5Ud[됡S ` q+ʄ|7aمIEbƀ|.,vLsڭmq>Yɯ)y'iNoBwr)!|$mER%҄0n?aw /Q?x_UB]?*fG8UCȱ`MJCrh;p܉ ~PIP@]\Llr;&EaO `5ݍrU@D'tƅRo!O'#wB[_V.HzAq]ڽjރ#)WZ81r^][~hqoׯV\%ݠpG> йΝG*#ojkp<918!&:/mDYs+zd0bv "y0"C1놔KN(VENhx7'$ }_!__8 3\ҳtt 9^yA9q#:.N&FJrIϵW0҉"{*᪏H ˅`BPg\UA ۂ*#:L Vu-fw$<( t3!tмU*A#jX3Nić;e&2sܾ_-_"R8\}+TP6~;׵$iC&"@3" 6Y8)AQcU)P?\S6着1k "Wz\=& sÎFD$@@a[=C SI_EZK ,JCOcpQ~tÿoxXJi&ob$a6PzWϧC:*7tto5  ꢀ0(YGL0f-nAWAň1m`Aw@'j@G4kXIJ0ORY\%E!pFָa㉝I:^¦( (_L47}iE&#"c:j8yca#സwaFc1la4etDP,k.Z)>N,΅]бؑ\ᐫ$X|a( UM 2>ǧ0:(ֶdl` vj1*L47hmpCw1"xJ4Ujq3_bY0]*8J_ #Eƃ㎳H$Ә# @%DESW» Бx1 4}j8y_Mp΍qHNFP\sFfۿ8Z~ ^<د9Zĕ7ܫP MyQS{@* ~cCx{XKm}CGr*t92bV WmF{Q ,At8 FLJDC*~uHj3P3NrB{ޤ}p 9 Xwj:Nm0( RI[M\vzOI{UC =ͱPv߿ߜ5l[: :>*bU?%^j_n޿[y2į>8Y:`p5g쾩xݨֺPuzw="oG@P UUIˑsUeim?U;VNd,A ɫP92sml,[)0ڿ '~p*tUNC񋬈 S:1}TǏxnlƳއ;J hi/aXTܯw&T5=E:>pJxs:@}>g$r*3t0"ɫ|cר,\H]Rn#:R'PM*cS;at8੣2pm35fhGr Ar*=wFcgƼ8N9>>~g=yt!OvIR(5pkZ:A л1Iq,<3[`Ow+t2:JUٛ.z( !љayrۺap8!R Gc{0u$ O :>gJtthg.-?w0"8OԮ6t7Zj)ÑgbLyCӾO7YױVH_5|vޮk|Q>x#ҁF'EqH>X&tO}F M!MQɴɗk>{Ͽ ex}E9R94sAXOnZڳԡ/x#H㓬/Wİ/Lg}焚Hf<(ÑFuA:JL ]rv%xZ|BPG @SBӿ`[tT:N d~Yb+p ]P6+ ڵ:vۃAlzΝQuX۷Gi:q 񜿳AGYϳˎ`75~qȸtW|~,"۽tc_zE,+Gwl.T<~OD <@6U Ai3ijuئ/&( tOvt :.lN?) ЁhСȓe|ّWP07's=dEg]-q~O jq8Y c܇:ZݜvIv\d]e zruy$.G;]]e=M Ӏ>zZ(5s`D/{m 3%W=H:>tY}; zR۟I&+.Kmt[|poQ'h`%2+8R=sWOOy W}&R{@GpHvM(5[zlCG'%v` I0د<|> h/(O2)Ϻ )nߖ)ǁ%h`Jhͭ~Ojؓg >o䔴|<=wv)&ŋxdٽC]wƝg;A%r}qTx ttG7gv?٧.-욞|*7x%©}MF@*zWN (y_|t_R%U$Qӻe; H6:,T#g'0~$ǂrRv VV/tXg_GXoS}8){~ 3^t8>aul3/FC@ݬ܊/!7: 1oAx:*1t<91Ow:س@G'GK[OwPCa/~ScK v8{jQH:* #Gzs&a\-xWU~[@lK{:=tfx8~1qY$IZŞU*ٽ|$trw+snCx !XUҲSE6 9/2:o[&G2`βF/ i=i .%wn[\Y>wg]X2S | ,'б =Ⱥ7tt [6{3nȲ!CzcL=X/3S$wƖ:FՃ3<<8IdC1c; Uxg)Q`T*IDATLb1;z# va'AOpE)=PG{fĢN]>?k+1&1R&4J:]CYCcoa !:DEo\:ݷP]+t[{k\ [@+Sf\Kh)o tGrD `s^ײt(= ҆n_XxHPCykq_*`ċ”9!wWGˆm{m6_T[u#JE,xT<ktT:.`Hj:s|G 4׍`\=cw1"';W9N}NN V^f3x3?EXG]<. crc߉Ov\J`J Jp*vbG| BEf~9[ xC`L$1wk=*OۓkXPoʍnӅ:P8iZ [ct;稭< j26f#:mʄqӕDGhcX\+2t&:&2ZRn6U,ttǼg.*ags=m:n΋![O[R :Ttk>!D'(@~lIt|klWQ&:sEyCɡ cy@Dtx@]ѳهZ[qT4dO-^Q+_DROG(*G cX'2@ţ\&/ѡWt![bޢC5+qJC9j:m0p,5( VDPG|ؕN w{{_ۯ hT [e^0Z0͜-:hp*)3]Ôb M^wމԜ.ZCY=sxT8]y8T dM|62RuF-bz}ɥ^M j ȱju:1eYJ?Zѧ#~؏ahki<#xq}86/g蠀X۠B˷^Ғ8g JDfuBFYqtܪ9]ѡ|ʌE$2ݫe0eՁew׺+!͔xXVih&r%\;^R9 Y!Wk9zjk KO  ?Y ]At 7/ZMoE6 #N.9 *7G=U"1Htb仩sL ݃ * Wk[sѡ܍LZNǑ_)ÇhV/:hWtrѡ$y[:eU6XhO)(Z *)-c:-5)&FmE@LTG\N<{E-\^[UzN$;bS'̍-"_<;y˔S [㕣jokFBGt5q7RB@P+:T#H/:TAEÔՁ.1{&Ft]-cqV[kq bFDԸrݛEa:gvy3q"5ZȊQ 4#:x KG~#ˏOP\R#~#8S<;pQVǧ}d#1#߽tm׷BFz{ ,$^ Z~U U P+:ZF)L,1Ci @n,RDFhXd>TN2<]ay^+{gCJ|kߛ]bUi<ת@fx.-ֺGDG3U/Pt~-ĺEG,z,;` O ;+-/p ȿ|ETvB!*yavrM@]盩3P PUOM-垼CwB+jQE_ (0Fc(]bDO&+b@I$%n:o)o:DiC%nH`@+ɤY]^~C.6]z?C@ޔge7eD;FBaƩvDWD}8LjD+jB{#PͿ\:tEILl]dCnz7}>D{O* "ab-erꊤpJ~f秼TzAHGёqR%ɥ0TH.T*!(ʼ?% Ur(n{e,FIt[}^O $WD vA Fdr+D5DGo]쵿1=`#̔sQ{o*fywCg :0tU5ftWgڦWa t*AR5H72Ƀ@m=}~,Vs;..EIĥQKUȳt&nU11ԒB"jJdRV4YS[뺍,#Ye E^x2B(u;O2:Us3T~W.gܪD_Lӳj#ijZM?Ql/ yttRc1PH^'nnZfg+\(X0#Z?+r=Oqpv:Q[koNH4D { XH}EyG":]HnFeY=.aIsF6ќ  s9uFfÍe %ӋMJAZɇt[;"qO?ѕ˯:}굏o|Q³sS/-O',i1i(e@t?Y* c! _*ߏk 4s&x/!ECnD=}q.(J-ejGgrf&T܃H*hޢc $XR"԰eM'av]Ϻh!!)9cXG-5z! Ȧ_]|vpa֓{(kDV`?}T ":TC ˽*"?J;DG=RNs_[, * h[6G'B%" o ܐ3{1͝(n^- ńt1*-I@tt;5c E_#]~8ĔJqE-+*J3s :*u,:S[mf|۰\G\8XI!$WZ.us," SܳB".T¸dŻ$;cH.bj:aH7N*i3It;9DtX#;j_|;n}f<5 Q>o/,>vV ͦňH{& zM'~FGkkuz׭t8ax|l,9ypUW U٤ci||#S7*P !:~/EJ7uml mǕʢIrCE;;)Wsb3d~Wz2nrřӓ Sjd|jzܥ |׭k'lt06x<3'#aif [w;(/6~d(Cg0 Lз8*LS P>+ @}G`+W>)L+S^-\&!%:#7 [,DBrHOUO =L `^RT2ܘo,ѡK:Qq$9yן[!6X`%II֐G Cwxԩr V>i4t PV"Ga'nLa@@ORNLN^Tf}Y_k!AiuKcIx$ P@hh=r X(@@)8`[2Pj=iNu9JwsLuK @oırh ^X 9՛b DL_"~}(~LtH_U7~n ч7֣řPq8 ĵ ɩј8ʫ(΋S-+e/[@ 4x̑   *+G"~ʋu/G/ Fx<{k{æg@M¢s#\{4@N`bII+LOQ~ &HxL5{z &&<@@hZzL:ּ$@ @0u/dA)<: @@U %| $j@ @@f pA @L`DV.l8ir\@ @ "B|kĠ#oQ@ @iB`k8a}r @#0+U\Q1.96WA @Pu A<( @@6Xu(KztoK^^R3q߿=3 {հ땬o"W4^z5zU//~&*p<{V>W83w"r?!P;vC~6իaK=UU113U1"{>C6իa >32]4oT旿=<3f߈HC`^z}('Du/|}7fӯUݻ/EoCjիׇc[/󟋙q]7`<<GmT8Dݻ/O&0jիW^?71 y*y>>>Sǽ ffӟlիW^k/_BĮ` ?ʝy{C#*q'r88]R|C6իao~_ȅ{N05.n{w?|۷oE"ib2 {հ7>~"rÛ7^D߽Og&Aիa?f_KM3=Q[f?;fS]ifo><<1w[ g FrL;fU[=>CCLO|wܭWJا"4_C`^~r xr ^5q +v9z8sE'kcri@TI)"%oثW^<ݨO@U0=л;;quK7"jvݩ4 }'TuyO"25հÞ`7Ar=c7X΁ Ҕ*f|Ю>^?v>@_~w65T?q9[{|/\WB;m0OPAaOAև7oވWCثW^H5N̘"cPdwW8'!:ݘ[@8L"vcno:qMRxN,~kc͸~#Ytܡ7 {j__|xBސ Sf99>^G];.+V'y)TR\ΙpDb/9]T=H\/gePP7o\s z5Þ8Їu ra9.) `zx\ >g>ӝ=>~:'R`lO k֦*u\bMTBGzd8 ܼe5DlG8ЦꢏBe|OC ^zޏ́bV) 0ɦ$Rsno%MWW{WC͟wK5z+ao1G0Ng@W[ߩ!~>iիa?ӛvv`DN2[d"03c??R ڧ*fql>:|( @^PQS(](i{}+gEثW^ss/ w*6{_+h9D.ZN9@wnva>`LF]0U'g ә;wt$f(yA ^z5z/&I:r{衫l&I"+z)>wMMf:.u4.;?"rww̖Eou0tOlPCg!18BߑAhC`^ {Y`sȦ15)<`0=}s;By6ځ}^&rUQꝧFfUa(ejAo5DN6I))2i!}}X P{juk}g3!꘸׻|c| >Μ⮛{X{8x̭5#( RYZcrRx|&1 z!~-AԾgRLmիa?@|e"|zww{sa[Ę5#rWD-G,\/XlUz,!HvM1ş60t5UT?DU/S3{KW 9:Goիa׀=D˘BbSfˇ.`07 FF'!5\)H$,FșMaU=g9R5yd' T3%3°hqTxH37sDrJG2<@lիa>~lgɢJNwmdlwb~ɷU6B&v̫+2b{P #%+B [m'i4DE󘈆^z/>Tͭ mfX6CWS \صD^뗃+l0+ AFH @w\y)󴇇 xvRR^쁸1쭳[6@ 1eIm i~~ˮS 8aa>.9nrJ ]y$ čr97kh=T-#/c{3).)!Wdv{swb!&f]hw4>2e1>jZ -A!%LDuZlN$?H=^i=_x$=H$lɽ¿;ZC`^ϝ;uU0{7Pwg 2#<&s&Jv1C.BH] Ȑ_9&bt zb^~ \fm~D(h%p1|<1ЍWe$݆@Cong zp3]ծ93ՙ&~+*:u%r#_Ci乂M4)ÑsJ=I"+TwYT2 1 30̜BF}L\u7mE JlDv+iMD͛0J؆^ {^πxghg(a |@R Ks&dvfVf_r?/\["[\kaV9|.^=u,{'aCQ>.Ml33ufK3,ph=bثa˅LOh[z,]D FҖr04ߚb3vM% e=t2" R,&fsޛ@Ҹ't絿n I+8QuȫWǡ?fr2_oհ9sHC$U ؂?]To9.1P]NŠxJSRΈ mN'؛1kMP Quh&&5G*k(r߽++ lհ/qZئ8v:BM9Ѕ`*a.PK"Sg8K%sP!b6#P9#* {+*׆&૿ ~/&V2{5O; \8S6841<9pMe*f,f3PY]0 g)RrKv-~ZօOkL=S0C>a`ϬLWf#5?fpeC`^Bӛ@܌tB4 PN;RD`LvE3 WZU!{$̖틱>eD@ {>"lI1"̖ا03Gd;lǙxp!Ӌ3,k*e"XLr/ZC`^?wte^"j6B_OL4D.;4p/BiG/e{499DHIx;[ y i{\QH-F b < 7+:X@F6+{~6q=<<<.c zgKsJ$$DI: \N4$LyNo YO=[ݧ3NC,IR,r҈q\ҔT@=YC" հžL27i6#=*Q6cKRol)~/>GEAq3J y΃ <93qiLIZz^33;vhtS7GwEѢ:r&CZ 9ANM,ɖ wKAh:r' YX< >bqR6Z#)[;k3Ջ tLsI{w'L=D82* 8J9h/zOUg*<`z^&5a(O_\TE! ZثaW{&rQ̇' Y.rݱt(2K5V8=V* mCG>׫'d;6k!t8=2T~}joª;@^?_f5yڟ_<ÈBhcse~UaޱJs?HaЃ-u\,jvy>OOӆ^ {1}񹸕Y060&j]-gs02gVftyM#:n-f" ;ެ(t$3|1!,ڈ L @QbH2'ȋ[|(6蔂3i& ɜY}mC{AwN!~駿ذcZ_|#}vKox8g"-9~;0 4䵶|l(r#gmϾc8<n~AC`^bCz2T;#H9QdA2 PS*-#lcf6Fɳ4* 呬`OB߄\:"ˊ:S5"_*zYbT=d{VkQ 2q x?яD!a׫g{l4!kVzyҥ眽= 84 B3nhf滻C-М :63,AE ? !J| /1= р.EFEpIr8;d2kfDBnbEk44Azӟ}+))C(#? {^%IEtig6,Sl.'q0adgK;3[J { ͪ4;& WN8yUABH#(hL̕)X=#Z\žkL5D,^}'{@^7g< K!4Xy*Hi:*=iD=UW Cil`d¤"V W#wGwa7%1ӊZn2}U㸛X9gR$GL&{&&98˜G^xP", Rjϻy^'jcT.(EqS8;ph+ѐ`>THF2z=5:Y^0G^d}g`Y IDAT%nI<9wX(/z[aT y°2:$o#Q0 c*1<?J8?YFL 8pYP\Y=3ǡgޅ|8*,:WBmٌPEN#uCPJ%#;|Pdyx{II9:Bq!a˅G/? U=搻ecNh`;L$p}'! ?%y$ދ)Ffkc!H=DGEOvZPVvƲ=HLJsTk4MpFm'Þa R8kNU7v+YM3klzaϮME rJlu/%yH+;)#dVȬF#iLbOY=dZ  LuNXWBlLA^,ʶϰw.϶ȫ Sm =ɶ>:hTq7pC {^SAʫ6WsDŽGt1Sq=I-c\?~n 6v4s ,M[˧O$]4󪹞ӛл{[$ scPv]A}dSwq & 4&T {nF'CH/u ;~B^c2KO+&-0z]W??Co5oG DD{229v&Ǝ+wW`Q Weͩ \^{ 9[rկ ۇo> BQ?nA??'' { {>,/%TJ jLYN {3'=2cpp}lM^0v (<d Ã4 #S*+wgU9 r:FI\E'yel+a?6=BsL^lHֹ|sj>NJAN"q2wNY0;dz(UTPdXL϶%Sӎ@5W@bx51_͆ #E$|x+"٧FbU)%*4X"t>@~G5PU"JװװD>}ԏ\шmKGSZFqzwc)1Y ' Jv&FCg[tc I$6N{Vh*K(j0 mA{dV trL#,^1m[lNBXn>cV^2#wTB%lc$`z cZ7*^xC;fM@}?u:4)d37 pө2Z|)>TM\ħ7G>l,LA)`Q-1\3i`Ni\ZqZҰ.urO>VkӥYͻ5eGĪP;x3 Ol= rI+$4)Qj*%ڼe[Xy/~gْ- D*a%5Yjh&E;q Ƴ/CNQ`h_/65@!/ ,p|Z|,JIl1ʨ0/=0Z;^깽9t|p#J# ]A0zy+U˲ۂla V;&lXc-z.,EQAϝEJwCQVŜ=0B趣kB9[ɓ)YIJ\M"(#xͳ"h.t i2;af}ܖJ^z}ENn/7 q7,4&e˂%7+5(T9g$3I>C4OQ@#-Q~e\ _.cv'(s4Nlm &PR鲦8cI5' i"SI:ϧs3#2Ά^.j(,17"*pL}cW3JL 7vRjՄPa wDpLtWfcY MD%5UQY7A7 *D;Ҩ"nY hmH47Ob?J?O4pZJrT^^)3i`bdcJg˵ǘzh8&*5* ž3X"Ap\G}N #k'Ye%K!\QU!^ WSmܒ{f`2@n|ZS2nVE < qec>Bs^/ 8S׾SpncxkAӽ7- .^ZLug$Yupd?ؕ& |'Db#`C%շLo& 'lXҙWLY4 /0UѴLR2|V"ȹIzR]Uhg(ARsD wR6>ݘWc4 #I G3 u^|j]7ɒ]a]Wj"l6ݣhЄ\yRHo` Pi %=c$|eƤS] ʁTo n]edc@} ʛ&/βg%n$\>^ p;~J=ϳ^/)06Ʒ-/>@gCAM)b#L[8|=֕a p[/Y$YTTȳ`p G/KJS?#IG‡}v.%Ie TKԐ;gqG)PDl %浞a upڠ!.W'ݡ-<^>ƎtC2Ơ;'a.WyЅ#^ɏS `nڂJnE淣"ϘJ['Vc}{c pX0rfLb-M5pY#waaLwKiD14Xj+i˥EXaV/0 R 3г @o5C /`@A!flWQRLKZLN$3Qa3  +ZW>oVܡtN B)w9a }6jiӼ5ف|d*.svyi4h5gmB*e[;UtNj{тt݆4ZZکT$[)] Eg EPr814י2^bT;y-!&[ߨֳ4j1"TpG^8ƞ!#ޗ6j|"uLcMp6>,R3L P|qhEM9`Uq#fNb&_KOqE ٶe I DjࣁI!X酆^zS"X:AM%:J[ VNItQυgGeD׉ZMBAbʤ16[JmFWvخʗJkuvUϢ0λ.Tf.:8xLDlG>*ivmaK"|YU$ːPXG@J4ధ5"lCN$7Σ]TDwudgkjPdlބ`ʨ{Li 33 %tUCI-,#Jմ6dzX$""\f5I*mBݰqYheWOKYTQq2 ҈dN`Fhm&7$ q,Z@gj1(8[,Uv,HewTN(.R bYuuin1'GeQ-ȷe {>'XGv34jё~]y2ca؊EO1K(Z4+i_HF22"~]g| d DjrŒ5g`I,_[oh!Ɂ M(^]3#yr%@qOZDx@ { {>p'9<&{(4}j2aUV(S[CkrPE+lzjR"5Tv sfCĨ}=̖< ~p oM r(^Ղ% :YZ?"6/{Ŵ^^|AmQ>2Sli*X DeAgjC?kj~~s mrqAR`R33KQK !JV=@Vi VhwگBOs-\(9'ʶ |s/s,S@/xn++&-lWj;OsOªy>;h˪mg qU˕+t%Thg$ӯ򱟋7[*@,|,rcG6ЭL:M].b`Zr.DCWE I[_d sRtB#0ۤ%.XSzD |sSӆg9ޚ]r maVˍ4Hq4 4Eh&3v' I0$H2ݯ,Q-T"gh2X跒r8/ȹH PA.m8=ȏN .4t[^/\u:FC/rx[,Nhjf&03Fpg1$yqn=^f D-i(ocI! ,3UByC҈YAnF[F" N*Jxb98*7jR̢7`~.-1Ҧ\wZD, {>t؛;MKhohVwB\OF18;`cdLc!{7S,,^ƢT-7{寗52 ]q*uCFaZzg 5dp)r9>~^^'ۍaGRr&-?Ua97Dt-ɼ4 8&j9]g*|%wv $Uo=: ΢_캑EV{%}2^)"3E*(JIVzW^xtO"H^0@%O) t iI&e9wHZXQtLJ");1n0`X/Aw4)QKSZ18؎0eXC.ɢ7<no_Yz} ++C*iWitg;دA;ߟh+d뾬|q򮶴aSm31N~|Fs)"F :ZWK oN|9b}z+{ka/zza@2VtF$EENTIR.`hDyp -P>+L6"22L#KFG@&JUԥ[[.| 5] Sc*Юc,)_ig{|}Zk^ {>XηdE]=Kj6mp *WB)F,6!%щ= ^ tf ћu5- k)^3heSu HH!|JԛRӈe< 5\-=՛MP)=!No=K^{r]TkRj =&W3c^ ܷ;'eNe(+D0WU-epZb˥9.L 80~/V*dCznbfguJA!+ղiWoKl&Az5/Y[e` 7lʖw8XoRh j|L5NQj@W dSK%|KWT#(ذ=,t]BǩM $1mTP;2'>`ԇ8 `Լ-hzj㝉96~듾'ϛf9j[GkPKtu6t 58o#LZglK©AS8#5I|s~džXwmˋ^nk4ۙ@R ݧ;\ѻA>MFRx|B.;r{֒|14gֳ4BpocxbDI4KcvKs7tdAƾYĨB4iOt[Tmg`rϷ2Hءkհ 8 VR񐯓/9{c +Gfit S_δy,7{ҫaׇGcCh0 T: lx m4@XJeyD2[@rk[c"6A={P+vm8>p{D9l,4^~VX)jzYZ ȃN,SAS \NzLKLDk7NWتgAt[i` ݠ0Usx^"o UӋ3}[sqLDJ < IDAT,Y`XRF*|)PB 'ҰLjJB2BƄL}kv 9xQ!O-)EW CiYm=K^zxM0$: \ɨL4I%@s#2Jx1WW3rKy6vj0/ȕf'q ֊ݯpA /C3CӃpU5-S`Naq;nK;Ah\Siχg/ *R*F,v`U(O=fT@\luAmna^\/c!ܪ-2>Hi*`'!*6c*㩊whՙ3#k<ᇘ\ հF=@839VvƸfbB}6R)(%jm_mUMU 4R(gD{sKةO0 'aS>me$G( 9KCi&2]naLUVȼFxlz^/TX.ƽeȶ 3D[Xgܒ d5FĘ&m"$5uRMn6Yqa[FWNؔld5㟊pG[Mwa0K(Z_LCO[fvtL"[Ұe YvV$1vd@j-[Sh+i\( HkkQvMdncb" G⣵!W,PPԈ Nx`*̑.D1j>ZbtrMSVY[=!q^^x(>L]Q}y&X):K@<l>X5.NYNo&_VBlz9lea<ާg m0C1NBĘPŃCuZUmiē3' 9P߫a {1'-4uM )`r^:CbQi, 59SXv̱jϹu5 %x7E*i1l.k>$3bp琌o:dqc_S3jJ9[ҰŬ^v+ro&(2ռfE-]{ 䞊mŔkFC0ߧ!l]][JbilkhrT@.7>qe O_rJ/jvὥ!bػ%**t~:6PozA { {^i]4jqXAâ28 &͐ͺ{ w_$3"(滲)*U~UEDZeq |KGq?;·WJR'UƁ|F!8,('4;J(LˠѢŗ\TZj" _cx_ /4ٰHLi@|TDcimJ`etJ_i*zYLBJԼk"vV+) =»LአhLfiHrǀL9GA[8>D,Aф蒭hH@3k8ԤW^yUR2!2X.kebT|A* STXF]gu?g-Cظ鯵O;mo]sH{twq~Ȳq3x?&uHzu8Y]ge {^?b37b)T&% Ԙ0r݇b{7}ѱ~mAXJɭVyKTOɹMJ7Oe(S=Lcw02%lͻΟHw꾠Xy\U1-5yװװ,O1i]My% ^0n;|˕T t6Xߢ"CTXuV[,En̍~5nI6i~-U.i pS25[7d۝Pz'|Ylհ6~ {\S e:.eNH3 p$aBQ]$hl\5XY|wVUsy-"׻MvAKY5r"m . +|4>Mx=L?A l(SBl6mg^ yl@e{D@WLe Sq!~ٍ_sN&Pc^}wJIޣAYbp8g$O^TAe0#E&g O\tWBO6Rt7Ã䜬d>PxzmNtE6claƹ^/mC+~iP/i:B 4qslQz -r+0@lW^SFkpD='y,W8#[fyv\lYLU?4]Y>5O?U#ey6X5_oyCPlzJ-ńbr¤Q֒2XBE2-Nwryy^vR'2U_"< AEfT`ɶƎk‚bgJ<혚Br=n_M'X( j~f/Vɍt 7K uͅP]ivxR졚;ԫa׋y|wޓ3sRGCF#e +Ql&oqh֝R#$i 4"km^p҄e5y&?f0pb2xAxys?ffBր^0rC?, {^6zf 1&L*7vu 4 Rib4cL݊KEW 33Ji'cAB|Kĥ׆0FŊT8,a-d`D |Eg,<9ǩ'*ΐXAA;=d^^Ua ؛ h,D gᔁ3 USֆϨ['z cVa?[EA{_J.*/I#CWA`Y<~V/Xٵj1Ư-+?4z]-F.v)RS9wuo%*V 9bָڇJ^bOnyj>I"4]}\`Jt" i l k m0۹Jhc۶ TG5Yd,{5zommaf r3r0J6 E3Y,s>şdXJVM6@V{SVmq) >%|BO-s:~ME3H 5XLSxXf zDMRs6yͧR($ȒQuCR,T4y>DQ,= ^SAO- mJOX&W(a0B9TM+FP2yp7ZŘZ@>Z1BzE=Ѱ./%GZKY\Dt@ 4S1qɺ*9"5F aQȃ(nU!&mG5؜ ZVT͸!=Q d/&{p|GE.]2ULf8zXГ(0/,wU&Jy7 ƤA)N8F]K /ҒH|ioč <ʯiIaI"!Q*q :`6#[apី s^1u ,@K /l顽^/VP,1@QC)*:QWchw9em-+%$=vxWV,C>ir]@泭#RqՊw@k[`'83}1-9 zagQqKTţ aUzlc:c35z^/z)IsRU~lm)uy/v@g:ԛc w Xxu3t0a$J*#ouˮbP F]% ee'E$,+aM`VԘ1M8f&Ds̸-+1J4[ח<~P=3^9hy6̠ȭ n#yR;fK=温japcVTڪz&Uzl Clbj&f>]ː%;5@ւ,$z8Lq #5#a@W4nX[kSf 7"P^X>62`hݓPۓB%k75z(7sח.j4hCPJ$"U,m2}jA5awfeI”lqTÛDDvҕ_"ڵ1=EWh+f^kd i**hiEEJբ9FzbHW;zp6{b UP!Ɋ0>UL|˄)Sh@٤u:Tް"Ek02&N8D($-}3NQ/cnyU s˒QvW8/jx 4 c0`oqD+EMzq6װtyz-GV){I Ҽؠ_4 TA*effI@qid}_,BЌT8.L] Dƞ.iWg6šB^Q#ي[5N1ÙPH3SʀAudCϟYf= L\ a◊?db j-]V/jXEG4X2UU$hnٰe<ބ tY|wYIRgp,iWo/􆋍,Ӵ[1ouCOKV*~%n9P4 C0^ ѵXcķX{X0.SЉBoc!:]2AsUuWRh=K^\մ=WjxiB G[vuHH$b,*d*6St ܼ749gnS gYaJaLJ\OxEe3#t5=аU6A v,\iZi- . \3q ysF逾ՙ~e$|q§+ny^#̝8'G,1Lݮ.aV QL|ȇ›t)NC X`ZEXHomj2O4Ubu,cۥyގJmI&4vG; )wg)iDnk ?g\;4jCY+[ MѴ"r+hv`b=_H#ߡRշ3pWdf+|ѪE*M5zE@\ _6/A[o.me`o3f_c o|X#M%RK0 z,#oG1dl#p`y&S48ڙzt.|ZJՄ+:V,)-gٮ3-62Ά^m6Pn?c_W+Ivu, Qw#3M AGq&O3.+g̈́Dy?>|]sjgE/;hb]s dx )Z 9Ue*)ԞLyRW(xs_ `ݩO}z5zul >aODA0l;ٹZw{&R;Y<HX&t[e1c. {0POJIY"$7̢2*ƒ옍b&''u!QwK?-ag1u3Z(_.@~NɣH;, {^I6ʼn'쐨.~§ׂ$kcW(혶6UB&gNDj.Wb$\0#钉Bx2x;dUF;᧪zJ@i鶐[B@?voװr>ץ&cwUDVdg-*r5iC2 et]5VJ "Qk__IIu/W]PFoуg hD,0 hkfQ]Y*dan#D;o!¬I8z"*EXJRd|-)Pa\'Ka(&3Ra6oI]$[ + IDATebn|[O59AzfCZg= jeb5.hEuZ'8O]t%ٰ뵕5ќe_X~\!5zd3͐f*xl)̸ظTȯ a)) @"X"^ Q AKA/| 60Xζ4w?˧+ba1$ɚsۄakƾHn7cBѳs~sI4~ZܩL!GRZ)Y!{4k(mNHgF2XSa:ֈ)ƞ9X1?ϼ %[STt3K.rӁ+vZ6~:S(Yzk*"sb1`n rh㖴I %վ8eeuSϖn~4am"Y:&jF|'HAB!fq͖.4X!/*glrjDaEd.S <; gQnǺMҠ\KSDoۊ k>u-l^zDSƨ8Me1ֲ|ڏSl p_!"MIw&qS*y!KIS՚UWTuJ:%:TX 4獻nf`$US=D{Aa+#|aS $ۏ"ݶ)MB N7%%(<730RZsk*R<;1kAF`s1 {S&AIR,!Hu`OZPwbf%6[qT=MY+8zօ-z̋lrIgF zW3 <ͶoŌ0mV!\ ,. _/2<؅jzJs{ 2ũH܆%=ijd3 9 舵w!wFV*slj6(8hxMYAd0xʦ0RjۜWЏycyk^h q aw]ubVZ0Iկ܃)R@NNCeӄl!7[UԭfjS5 4U7$c&uXg7_xTB.~=|F+k!}PM%65$a{=а!_^b? 2:s(PhݗL=C@Nv0l@ަaU*ih =4h(^D h1 Ϯ_Gɽ }C=ZExeo*W !}Ca]ojόp!DuoIemFQɉ\E+9~s!SdqU[yt1q) 1ՋpXp j[x(C\9c=Ok`p‰X8$N[ϠkSjN@>\b|a׫"zE5y+R%C>.gNH[}O|qWB;!GS(3ZM3)]%OtQ|ȕPe-ߓrXZ<2|N3'9xAY=ymk 0NilY\ Kk6B1Dw,auD&/z\SUXU8CP]*p|{P3.MOi)Jt1 @YdT_:ہ/XN|F5Jg^4&ӱ#]Ҕ;4Yu„\ :~,hY땙лbC0#ݱ1qěD|=˼.lЮ RyʱF k5!Zm^ʷ6G Xk#BP7z|V>6)pKOht ZUNu_K̨SntĦߣXں6CV7/'\ 7?LQmIBt{H8ՑF(v}{W1E5A}-<~bMzJ|F BfUMC-̭|X?ˬf YLhˣ1Y& k.;X/o&&g_M\εJaCTe`{@S[n?ԼL9z~[;E8d%E,$l/hˑ%$}꛺lq܌HsPH.` AK$ȣ(F+3ʘRSz%Wۄêb:X !Yfm)]Rii9s؍ȹ&:Y*TQH[Qп:%T=K\ё;o*?5eA}T){BΉ[~Dǔ8̯$CWi ެ?ܨE|v>O[~~0z9JdYpKF=8>GC$򵾖n'Y$2Hלּ6]G3v`K>gwA6yhb" tgCqDF3#qzs`orJ0S_uďWճ7n2<.%p%U.ٟ6% V}9$'IQUNS `jzlv]ۑ\Φa^(r\Ϫٷ#~3ݽ7D4+PxÕ]Zs`oߵyga 6oS{8s5v ջSzNV8#LZ<:8w\6O\11ִ؎}0wZ$rs#[)/GKlrmz-K?bQ 503;B峭ʣ݉Dd$TޑX1؛sqXYE,kӿFo J/0pq>+#ރbhI]97T8Rg:m8eP!];$[G*xOWׇ'-@m5!DFtaQޫچ.!x(RRKT}aSq \[8WYiWʩE* MN!fe|>sfOi>{zHIGxR=j t?wKq\R@~/Pr j&̮ 8IR?W[lZ}mYZEo"b5Y1"¤!Iolf}^n-[Mw-UG} 'bͲu kJayCAU-A5 ! 2BvB&8V&ދU~&.XZ+VE;6j{˔T5wBsxېj5bk2hf+"E/֬f]]OqKO3"ܔ4)s?e΢GʋG7H w@{``5SuG&޽{7o]lv)[/BQBwHmq޿q(/'i:*f/g؛w|=FtvYIO K rL7TSBRc$I\THqS!^U0r~!~a S|^afo.Is(Hz n>3 6Hq 󼗌#PX&+2{he(gK "Ma𻆱9Pۣ7C`mRy0>.(c_Ţ=ͅ4eBԻI'w}?!dV8zNk֮e\ŬY[cW] e%pS5`;g*4[+lsq{\rqVe_uMj40_9G>ad*Ɖt5R]f#} ATb}nq{S=mB*#ԁ Jc?VZ +,|><$5i*;:di\-dEۛOeC;bXtݺ`k;w^:T4yim[tެ33^bD$p-CcdA|bCZ[ށMy6D$S*:(YLJ&ܦ% j -O! (:J "Av0e0H۷-_v.Kxѳf -N]ẅTbD,6 V^8@rJ)6!M(-ϦY;߲j;Aѡ#HySѱDd_0DF@uX 5+oYffәFONzs2iߗ"%d'ULc~ܕ!o"p׽3_=] *_*/W/,͝po)'kL7N"VogK؛/rbn଄* Q5p |o/e_-٬2 RwΦGEݜL>=̤ڈ%NXzeC_>4ɌcC>Q{JWubwE77*#O#ᇿ0ĉӔY`C#aA IDAT<9c/AִS_+KT[O كzl8! z*)^47tz[ zEIU2zq rZ2h.)+bwL 0E8F(/~SS(.mUʓ$%_# w'f- /1\h3@ã~p@h(\ ! e]TFhc 8P갪x]&C(w!XI 8EY2w5/<_x^o$ ߕ$6T,ɺ/8m{{ꠞ@[=x>ޝ酁YWT(䰜o)']xM[y.GQ C/!Ux!ݎr4A}@os\_kk~lQ򘃺RWASQڵ%87+ nի-KTwN77w] .U(I:BǪF6M+wf2\[}ƾPkwIaH5L-&uqS"jʸaNZ&qt O8+Pwq32Q^o}~[ѳ X猅җ7Ms7 te }C k P8N]Q@ByH>gj}Wf ZټG=>MAk .p< <]sڋ/yf%¼2<>dbHoZbedaڨ>"C,'ڳW3%9(D4 ;48M4;ݔ[n9~@%5&58&'rи 5ijB$:Ef1jެ_u!Y {f,'Kbo ]2lWuP@(A ];} 'P8rXѹzE" y⃀\V'yPpnw AAw;d zL)Cfz?cr,v\$0?"lkWsRi U/ kQMѥMfl+de"9ĵQR*,pxOAzСN)^%.5۟Fvċq]2L&]+쩸Ugpvyy#ϯ8 5'_?&3G& o23?*r%K@+iUfHoQh5?0 U|ݿUV59UYuųfgSǻLЦ?`VQk]_Ѻn̿CGH}~2x 8z:A $^&<? :M׹'<"ļ D/F #!>o)rطɜ{ 8 ޙ$!|Z$]gs1GkXۤC C ^d(ha࠾_d۝2s[uF5حNWd' tQu`o`o/ZXxESgZ q:m hFR᎛vc]KE30*o/(M8ޱp7 "%#3z@Q _'º!&䟾T} ĞջF!Z(e(>(vag W}_ZdH&8ၶyGa@ׅ2܌WBGc2*MT*'j)82{-D>< 6 i;`HX|@si>͡z{~YY|+.|;dO/r=1M&Iӊ!28]:g-3o87[+C@XvwB%TzYw~aS&+ʕIG'aԚAs`o֯%}oNPQɄ.%7(U[B, &=Ydh$V%/cɐa>hC{ ;B y?DͧܟIaРjʈj`5B.4kL<܂_>%6o= [p(nU!5^@Ƈ@!DQ,lb'͓!Aqp{NH_z^ꤩXlҙrٌӭ8za  kav[YWJmZ7Nmi!~:ք`K rlD=,խi2%<-M}9pj*eLIK;Bpϭ;t3ZK;8vq&vɿ nE$M8wɣrIP Mn 'V8יױ8~F97ו6AوeNMԕDOM,V3RaWpFRȚXFݨJ攋r<@s&a$6ݎRF5 kFF:`ӝu/b=f$'A:u?):q%7eE ׀ެ_A^v '8^K[Ŗ:7G[b\Z~C$mV[n5ƍEʛ slPH(:8{+YPK9΀YAe٭s`옎*nz*qc?jT9 akCCZDکCRՋ3&aY?+D08a9ٶ}ܺp-a¡/ZꟋo%gN8^51Vcw+qOB0X|>^ꥂ-GU*\}uI-[qguqw\[Czqj#$v\iW}sqx\|RHwnhBA'vO_]*Qf m@J\="ެF(35-do2Db?W,"[#|>8(ɂ^Hh60S'DNr|>>E@Qß0Yy`b~WtyX W&b؛+>cSwo-ĊN!Ep"$?inyh> 'ؑ1<c{`3hRzIGpPƗAc72K0O23k`o/q2 0GphsZ'%Vw+5#xXlE|uEnLP܎vnY8w[BYÙHK Ns!a</\`z|нH"Y>'q6>T3qN4^EYo>PTK2tpD =ӂ !%ǨuF_EMoyP`ux8 [_LєXǒtUG)Ğ43G={tx:D=sgެ_T,Iuo=Z=AČ}'D$ 鄂[ِ k<з(rSRXɍ2ڐ+9f'JʌyFo72w&ld1.{~5 Js $jIs%8=NZR?xaEl$Ƥ 5C2] +\hQMCGn9f:׊_cm D)tu:[uӤπZSQS4~ВBpCoQ Pa7U9Xu̺%?cOk:9F1o2gZ^M*\,_ RdY{y+Gq` uk!;^M07DeD0LG&-CWrbP?/uVfO0@s}:ŒˮR)MTp4J(:͙57뗬6Y,s;&MЋl3:84!b/ͫ?rTHcϛſ=TG 3eQ4#sN7\,S4/g 'Ԝ#J"H>@&G|2 9T׮-|kx2.iukL#]8W)e:NJR$ʤ2O}8J~9#?fˆh Ǫ&n)Sx?AzDWr-wED(ߙ|-r&,ꁮЏXS&{iS"2#7뗲x)nCKBL w=J˧^]1E6Or["xUMʠvX;/Uv^y.6EOt ;?>SwCO8u"`?W^\nTXV\JUԋmBw8@Y؛JQz`SƊLocN*@W}*W6DϹ+ŰRd%j/4-~5L6,=ӵ/Sud(.L&E o+Hwˤd:X%Ky }`d[Hz{{~#ek:]]**} %Hh:Ǯ,Zo*P͞^2qQlجmb]&&|)5JtPZd)Vtɺ8 5od8$؅vqn>2hFO}Lg nS{$uHCf[uy[8Vž@ZE< &p'kb}3=897&ܗbl!T ѡ6 nۑe岎w[J3 L"PYS-?Pܸٚ&S2o.ؐf/ildQ1J3-U}n/lkPM}eQ`wOL_;OABcG QF \OuqDG|f^3{/sZQ5t@n9ο2%UaXJvO_n˻282ɰ? dTbέx_c1^ۍ>~h=%6lMV8e[O+|pl3Wfva`ocD`}Q[$$IφA#6#s:GP&\f \ƬmWȩlyޭ$B;vG*I꽮ƹɚAwHE}"DW}x|LݪtcNV ūhh`o`o/d{QQ"@()'AXȦR^Ξ=nLm&y٭d Ee,0z/[pVu~2ݲq8s{N 2ylQ1vcC~8ӸxI;fW3Q+؛I_uhӦk Dyz*zEi?i;D.McqD'QbȒ+H]uA(=S;ʤ-k P*XXwR?3̔waCD`#(F77htw#҂~,:$Q~ٙ]TH-*+] {NWԧ{v宿؛(E94ԄejiEϻ_DF[+n&kϗ+{{h S|qm#KK"ݔ'i>ܨˉa-Z>Ӱyny.z)rݘg?9@zpu wT}`oF :u_ tt.R ~v 0Bx&1wT J]OJNܝqs[Ul&p\w_2txl!U&@vg TxQtmr&0ʼn;#?.bmK +} S֑V۾,҈}YiRY"+Nd utP\w#JaN4y?ҧ 3.r q7 _7 سbS|3R;7]ƥϖU,"ZhfH o͆;z9EEs۴ t>4r{/ |y:8d}˓mW Tff|Jcu]#57'Zj"i0 8n F:WdJeX?~u IDAT?GiB!fMt^|;?|a$TpUS z;Bǹ;neY<(Oo(Ӣ'oaެC! SžqA݌m;g"2L\[SNǧ*Q6:t"^K0Ɏ M57W^_JH&V7PD8[If/v)iF˳=H'(ߍI[>FV 9.K40Rpл B,3&m[wvyBZ}A:5c5%St3k&ApEq~3?ȹJh,3KDᘒMh,3&ct[;F.EEu՛I˄{p`P%k0.;[ۮ3D[t;4 L `l#5h1}{"i~5cA= ^T glS^qH#^dk;!yd%d:CC4VҺ[,߾xeKu-f|;%a>;whpެ_tdӝ"UYE Y.F=.a] zU+ [S"'=QS]$Mzx^+<'S}h!E?M*eӾ 8j`SxԚHֳ ՛57d6vK@"ǂ^쀄R.uX$|q,2.-FJ{ح3|l(o"EFG顝Tٶ*RijO3ҏsJ\4t6](H#5ٞ#?6c $؉徝82Grgs$1IMyXQs c{<\[[o {@imj`7ov pt qYt#aI})̕Y_D4eż#iƮkaJg<a߂ lȘ/X\s)Js Cg,kmKv5ql ؛ELMZtĎg{ZvwU+`) p`ʤ= *Xnr{'EM)K{\/>be.cHMWq!Kehx$# .֒_?3m a>̛و[Jf ܭ-9.hbxOLC{3 _?e//tvRj2`omBvnR mG4:ى7.`I4v%=tX6$f6&Գ:hp,,`bEuAe aCun,TJ(kRV?1dKu,& ܷzz;vgٹW //yHe<C v֬Y] :|aC8i stPS+[2Uh{,ޢqA#۽es%Y(I3sEb#:N&0ۧ{d93({{lEI 1k55QeMyQO5q؛82<v8=T0vWiO!#*A`@_\90i r9D &.+pDsbmӭ3>sw{-ꅲg4(z^,h/M6tD 27k`oVS DS_$@etZDPlԊ%>P\ 9?;YHs孹ժik ΄>8ٟH@9f&AYa:ʳQMd8.uz^ y^Q蛪W ɓGZg؛}`Ӹ*9> A@ƀ=ꦧkŌ.ACe|nsu4fȍY|q|*z-[*x5+`;l 0L='X.I7uL/؛u.6J i' p,)є C'u)"u!=^1}9 za\j דּL b{}Qko TC4.8yMY{jV\(C#<.0'@7/1(yi a?UhLYyk7Y#/G8% +w`G{1Q9q|ʖ~=8uZY $x1uv><6gdPBʎ׃q&l;C)P|">sdR zwʹ@ʀF:(hـ qva:|cdz?@NKQ&,bY@+F7 <WbsZ2r==ˬa{ 5Ch:FmL<_m;G .Q!h~)vThbx^%25X5 5C=$γ q=Q u+ZSiWJT; :%Vr F;q`oެ-`mPy!'~&ܖ1Hi|:ttW7)or^)Q|t^p&d2po*RR \a>Է)3 #0ϙM(~9N2O9YfY2gubB?eKX3ݼױfrcK7AfaO.f]Ⱥ !:׈@k!HpcGL2ʾޞK]ZL&Y9'kuwrܷ֮EG9k`ol1|uv@Lk8I0J&"p\ol7fz#qzCy Ih;mC0y[tfK9"Pcis0F|+on0PKI{* dǡG9k`o )'lD|&{ ųGKAH'6o*ZyDPE1 L ͡eYR1늈]Iy9,7&&yБUS~d'Zh~F|ݻԻ&:b<4.zn1=sY{"^q<3|ZI$>hL>('90~4:r)8, tռU;]A7V87xQjp['̄lK f,hD=L>o!xXЧ~kgN{YN{ ݦĹ pꇋWyUZ&.*֌8d'e(P?aivƁN!5: ^ Bm0 _+fMiQ:g'RP k'Y%笁YVʉ=h0 zpW6.: 'w7D3yfĐQIYUNi=7ѳغ65JHv%훀K"8qpB90## :ݫ q؛eZk`oV4wB }N)neհq[犢 cZΤ}nGq98̄nDH Y= ol'="[tn~8yZ2@d`E{xq,d°mkF2k`o^kƐ=0(!!wmvd#Y[mYT&`3͊% Jx kZeegԺ#3cWJAvP9(a}wG#Y{I*H~nj7|v:yP}˥^XLu/! J7́wвp4a)L6Л뺞zO&4vx#ݜ57uuQ;!|h)%6H"d'bC] ˘V ԜqoHth %؛5=;;..y|zil %&~ ̺%83GVdD8d+ɑ&iڊbsNb<*]A;6%X1wv0B;5Hk=srVkp[V=ˬYoqqCoozWJ^6/\GF+TIA 昝E(;@7MG^̘:!9J2}ଞL]zv)pOD>J?.L6m{!>cEv|ۛكfe@4 oOa IDAT&MMe8v̦3%&ٱZ֍hIE(8VhˉB,"E $? Cq'C(:Gl/AJ}6iD=kެ#<޾XLlNQQ/>\Zo,^Buv1 {l{mC n<̜:Qe7"%&90o߀b/h5 .9g6 uz%pe9zY{=<;KHdEk+$[^<=Czi)b\&n@ Z]8 C0 ԗ6%GŞ vS)c%bm_ @ךj`5?YmԶSެY&+ib~qO?+Զ4-$!v\F9rO?उՙW%RWc?|h*c-NdQ 0}>5/Po9uuj:EˬY(Z),ّ_ʕiJjػx; MHS>VpY=<Ja,e|ִUmY[poXW]j?/؊7mO0~o 7=a}?fwƍsެloY2h![fOA͸_(j<[a ikc9yT8=j"SH 3_eDW_c&Ke/=zX=f zA>NٓKJe i-xv^57';:c|Ct-׳Jx2B[0( FDhC "MA:M@u=UY{voIvE'NQ b#]xGPdrV[ kQ;a6r֬YPtx69%|)~u0/#lA8;k,{#~pg$ 0H& M(m=-!~Fr`2m=K S aއuDؠ] b"J\xk=ˬY塞SP VU8ݕ/ݷ'?ѳ0em57KaV פ٢hrkg[bښ[?Zw ;rhN$JʭhGd> ,BVf zC\pu%9[bMC5wF0U6OrG|D@-Vz#wr`oެk4e~X8p#?Ϧ !%^\zOp?מ& BH'Xuͦ';7u:yx0sFuP\Լ:W].>9ޟ*#`c֬YupZڶǤ&E̱`wz|gatt4u.7M_ZI^_ ϸr8](9ۆ( " 0}4o e#57jq O 1)3j}Ѐr N 󝎭*˗?$ fm: 역ZǤ't]?P\(K<2f)'=:V:\ C Pƕ¬Yߪb։ȇtbkxd5g/"W)|r*Hv/6j}Heno&oD O4`@JT)Эpr&[~!Pɟqkj߂+%f&ӉBɫYh-Tj2^NgKY{~Vs64Huۼ'uhp ,#o hxv.7YZ7g߆4A%@hGJ4<^u 26I(i}t9!%k,f}#|hןU~rtx>`x4uQݼhqK糒`}cth>⟛KgK7U1>D }\Gdݻ||xm-F2>~F ؛Y) kC s:槊ܳF>*̸i^D'tw'op n.(Nԩω:Mp.ܷE*hQ$)vR dfъof5NGuPf UWHZ%[KTZ\bפd2O Lw9( zMX 8~?3 ƚQzfMgUG-wO|P7|pg zsVTg! b֬Yq*#XQ{#.&Q|Ail:A W8my at0]Y`4pZ ՏȉRSIm0lt9F0Am5/ @kDR(Z*EGÚɍos> f]FfGQ8Ɓm4J.ȘE3 ~q @[^-l 89y^➤\&F I&;-7j{$Qh6*~zkL3|eV?j8SZzF?v_gzaެcߞ[ԚRv,Rt}VC\ h,ۨCFĭI;JɰPA`r) wpPS{[9<,P ax4'^tf؛Ss 1mU q_=2L2=YI+su'I̭h%iiː#f~sr3LVwljz vY9AӮD^_I92Y{~yѫ-ҘW%\TSFlX~e{Xl#4$VҼ?bt~]ϒ>0*nCb7~٢୘Y{~3[N*=_R\LYuo=X4᪍zX C˾-Ĵ~rt0n GSeiu얺U<лb_A)×0[:9Y )Y{"K'`w ;)G_r?ra~#Sy?Q7*g6+K;3MJ4Ņ4T'Tz>ޡ һwW5H]ۙt(+ n)v^jARof-"DŽzެ?kS^WV=wzmk^Q.9)lF3TEY&KػY*-+D*I7eoPc4]p[:Tܺ8^Cެ2r72h52Y{Cl10 #}^*\3dtONbS7*oib$R\P$WvQ^!J xexð +60P? $kJ9a`޼iF^qWКV3X-Z9xٺ4>#Hݚt~SK3<=ˬY_ 9N  =PL .FVO'(dE\[W׿޴_ESF-8vd`ON47!zu$/!Na *^;ሖ3A1QzQr0/jljY{-KȨw,d1d4 k1kCop +_˄L^wTA =0L%. ϛi60'WM  b |)پӸb}Fiǘ)ER5k`o֏e@D,P{s#NW>Tf 39%y~yn7HgH._PbXXXX]m {3(6K)V-f974v9p ]tnsְY3Ǹ5#)ū= 'v=xz,^*`¨|ؔm"jppCp3+V[)Bؖ&f>:xb&;+~ROa@䒑&At^*:kެ?ż뺌vxa}ߒAK(/y+؃|cD hP 3~!ƔE[HMr#cPe7|ƛ+)oJh^o7 fȇN;/f7ܡ &QLa(|Nkn-^($A5ѻfzB(-U Π#BEA5DZ ~`ܥ"G4EJ2_cʟT(Y{8{=ˬY~a3}A_q/VA=qIdjjeJH1mlU[JzOuLC*v&Z8{Qt @|$S n@C$LR`eYn4*85k`oid;Ka;OAW^kk\C;ٽ;ˤ`V2wÆ#tɘga9<-sWuFOXn.>\_'@{ cr@RGI7{dY# ۛ57Q*9 h+@(af:L~&^xƜxضx`BRޛ%لoiBs'zZ1aκbF& |Y-L7AR `# V 4]Uo5kެ,)Q'xH.$TbAEŽ6d!h-d3340 %E=EH1!Pnry+5$ x0 #ʄyQۈ@o:<7ohE-!b 3 f9+}"*UPϾ05LNuo&nMZ4/AF4އ~a2au9pC} X4Ƀ-;̾X^~j:.G'KhsެzaF IDATs.~*H9eJ62kE"Ov(U/KH*U[peZc])! e 3|d<ܯ8Rn.i64VRXPΣ\%T:mk#鯽_?NެYiӽ헠l>Aލ? 5 wo̩Wba} 苟^GKBw[>'؞$h%M8Zj^O{f`.c8Q퐗ifnvr`ʭ^57?)oI+ "YB ,7MS{30-ru3x^G"_LI1!م߰N.na o~62#ӉAmցnA:?&U'身FU-rIo`oެ?pbfZS3}ڌ6X1:aFkJOI4kji9X( 0Xs,T;\nGqw&@t AՔ?,ɏ5:0~1CiJnh֬Y?< `EBćBW=+ͻqtsYf#3H8BWͅcvR:MF*#m1N A gD3 qҌlfD(uK~yl@r u[PBc1ڬ̩[F9k`oA߆.#57jM~z)l#:K\ ʏUS*E.PesJ n5*tb|@EIT hBI#!MF2ʦ7#|󺀩ܴ8D!g!Dk啍y&E%"sg؛ ccRG4c4G ":voPLjȵ:?BezxR]$Ӥ2}4itEQVyAwpvVqr H<흽?4 'p|e:^*1sެ?_?swSxC7L_` ]e l3u]cz9v bg5.b1!BU|$.4rVFmwM~7 //fuiGXqZhV_3 =>Qf SǕÓ<(t-amqK[%hssJE!/w!I+F* dg *f&X]C/;d&}D#43뺶sӸ\մ%1!#?F{2Po3/8ggs ~^NRJߘި`"Tp^)s2L!{ gõh| z}rR!CR߃Ͻw [~Oj-}%ϧu9:&Pj !C}O=W<އXρìZҜ~g ۛ?% &Y.uo'i"L{%"x4펛e GְS]$1GTId .(ysB!3sU S%7(@U𒲤)?G&1^M(N¬Y {Fl)X|LŢҡ>)Po@{SQh?ț=VͬAN8oO6? A<&Mc @<>P? xB9Ә><Ä|7X[ oY{ ss2|2TAzUd IX.b,|?P W12WϣTsO'%9s4~ɧWWnFA7s^pȚxnf0S+\8_D&&j`oF9^N(I#> `mS*4>‹Fd5 -og_2\>X=]`G+ U|o'?[7pvR[+}pwGY6\rS}'ނS?H[Q{g0?Q*7^>&ԳÜ:IOC'$+F" , Q6L".02̀@}aBl,][,/:lǣ~K8|l<ڞoX&LLi ݹi2/XMv|W_oHfeEH8g 2H ۣs6& G؞ 4]L}c.?T je?ݍepDZl z%,n0B]E "NmE {wtfWaO84hO183rD?+wt0V4K1c-L3/V>4 )rk}!Dyp2(z} X-*RZ9ngǶ|ocqgamK Fl([__ҮY1`ta]HKsֈ.l.3w9_ {}'!`}6<,A̛-&rWU5O7.e?;Ϛ5loݛۇaו) r0TIR<5`7)H6<. boL2W{madEd-wU^]7~^-+a\C퓆ם?ǁq.+ͪD虖d"kdf cQжi l۰xnn:foH)>BX>GodIA6o ΃]@sxIKT3mҊ:ӽ@/I~~gRJ65/hKGh}o.㚸yx2E`:JMT0L FI-wq46~ڛCUD|{ԙDr"SA"A$fٚܧ5v_TwBA#8o \C*/YԾs۸λ8o2]bkfzK2-K*Po&':66D fV`ߟZA_&ƊK?DPWϹm]Glqk 7ixaH'ǯ%~q\ џMְy x,kAm-),qh<֞ he("Q́@RSm^"f}[ɬH)6(dvC鉷rM:͟;o(ijTSԵ2 oH~OOOc +qe:,.x \:D\ v0f<@jд5Y]913il3ӓxh1ke=u](\]6$B0$8NF!i].g>7M=LuJ1,4pJ[@d Ͽ f&u?`f!G[1"_g!kWo#uyP] SdgC2_fKw/>J/\+Ax4SoPI7_{~5<˩4+6. 6ƞ39Ae,gJ65nSFZL:̘zeeyWO)\؎J-5 uT!bžMgcGBǙq=+߇33ZdbK53]'A>,^ӗSm8؁glHaiۚrDVm}z7OѰ.ƿY c~֘>YPLSTLP8A|19 eHn_%-_p||{Q8ΨKSm-R&eӀrk *|ӷ!$"b`VTz&gCZ8#FxK, 'St1mRMaf3ei^ᘯ4I="H/Mɨ-\g04/xlnE 뛏Xn qNN/D.g4P4U,W N'P/l^mHNǮA#H{wÇ?y), {q+jB|+.B&>΁g9W`=`?[ ӦʍFmy2Y6Eg#{!1@FL|\;p2kM~"r:D| XC)?uN1ѕ5/Nhl1V}ylJp!H&*I\ѮC^}8ߙEdiYIp.4c{-C擐`܉η݇=ѧ-aLn[Nb ohj|*O 1QaD %-w|<ࠃVT#Kf4ŖI^W4+}ڦ昦"B\56yzT 2,tV(:֦oN{ &8?4Z~2lT'Sվc5 Y3OHS.Wo戡SŒq:@%Bsȍrqfu-"4*<HFI{!L| .I~Ay# +[h#H{1߯eK)$~ZE1v*<2X^Ci %!N6- ddQnshS:âNTU{yQbD 'G/'#Չ׶L^ H{I~}ӷg6`88 [ H#K.S1FFՏa ~0Ԏefiet8X>TQ;r`tC(ϛLy-VI6>x-PB+g\.Z)R{_Xw]x,K_bR]e2OZ]ߤ3D[egckxM[Ѳ+ݡ?S5zڬȦ+-7x_xP);׏a 'A#?H~g7 hulET,l gH6d/1= jjuwHwU6xBg@^IDATbKASU9$U`lH =)eN'(jkMD.Ha3_T6cGnv p"3}}}4v8R-+1ft ѥN8Di,-Z;%wG Xi-N]_n%A1;:/!1cs#tm'ey2AypāՎ{4X Ŗ,: pl@\!FV3aPjԼ:;vQҍw[1j:t`.5&Au?=?LjEvFXoNxOZ"> ȍ I1F) W"V޻ YvMC(j5RR_r c7t~ _>Cq=rʗ|Do/2,x~}%`/'40o6('J^<$,D`hhGnZGeD+.Oc}W|7R=VQFCkOWD%{8Rvx%1`MqfV-8a{T;+P-t/qZDKj  'kGylXL!y\K= A#WE_{pqNqϑa9 [ ɭ=^Q9ڿm/73D-t)MDGq@?3uST'ּ{ [p<^}^u\+Lo=QתRVs.TŹ$zYOTn^8o"Y$M% W0h ;Di ޚF~+9ɤ*Maڄ~2S@тt80y)j?][jOOK4EEi ۶TnYlL.f]F-5TӸ@ 5L>aOt-Cn9ݙj)ڦ! ӈh#鶔44@%'A>N<<'x"œmf^jĦӾNJw^&®=ÊĄ |GChϰ㿦3C&h [\xBMI֊V\5&uv9igtē9U[q(zL[aOExZ*;;^+ah#H{M_e R74s.ohNx1r93Ľ0C%X-L٨_6 h&jR >}((ǡqO+2N=x^osQt10"eܬݱ4L:.n n7GR Lsr SD`Ǥkӝ)@ܷ.*ɐA|4/c VX[ W-bU$(tmٰJ)'iSK[2M$Ͷ:'n w~EV'5Yp2pfLC͆=q\'YBvv2?PP\`A_qH6A婱ջq*9 |>F=sDI$RJA*ܾg6t?S*"Y(I7dzDjVJT$=سGkQKpf58 N{L~pHg~ {LyF͝Gl&t]303!sqۈjoA$y5S,WQR^ H{_cjF$ wh*eXRo sIQ stL6&EAL7p̅Lv7ɀI{k&Њn~gx]1d 5aYwmLígvF.PX`،ll* AwQ:|r, 3h}Mйxr‹F}`pqm&&R'j8t:5YQ1$,Ң} A<>.SX;ƋK5@{ǶM 1,`<ei y;nOyuEqXOenqp*L];kSM STZ i ?@~G'ZdON) egE֟zaKkARn-B~1G1e'`ZHB8N>AJMD!Su3Zc;׈i`sD*7 p:_9s>{{O73Atq{Xq+fhڐX5AҸdxFL$Wp`Y+/w/ݮIe^TZ;pH~!ofr;|n汜09oKZ4絲fbo"Q_o .t/= $5EvcK%D14Ão9ɘ5EtA(ʴj"oJZ}e[_dE0pFEP{=`m8 !>֍'CAxz{0(n[omvF-A:|z"#⤙@'6 ~ lk47eDdfBhf`Y3/ƾ*f}vs׮3;@YN{A|yl><+ 6QCQ alO H{Azm7J|AVX.!ʊ@0&bFg~8 Ao7E%3Y|3pF{4Ԏ gi F?1Obpx=N5'A#+x0Y8*fq_"/5n"gw AZ9fRkԺ׉@ԙNcLuCߒVѻNv ˥ l|(Vq=qOaXJVG7餪epxuioU陜RjO?GۀA1.LͲLTyz6ѾGMWpJ+5^אJ^ H{A|/"crIp#x^al8{~Gp#]M~?ժL~&rww p9糨fVk-eÇHFx8?`>OxG!w7 AA:$ H{AA# AA GA=  Ai  H{AA# AA# AA GA=  Ai  H{AA# AA AA GA=  Ai  H{AA# AA GA GA=  Ai  H{A? $IENDB`PKFs-Pictures/1000000000000204000002045247C071.pngPNG  IHDR` pHYs  tIME ca IDATxygIu'sNf۪]{uC@xb$5)߾eҌmYc8ݽ<f"8Ҭ_,zWOg鿫Q) ]o;|X$cDF u|6-"[_[tcc;dSOUJ}w~߇Ʒ3Z$`1E. "BΎǣ0g,<ζv{䑫`Ye~sFx׻?W[i(ֻ>{Ac10F o4۾x~z(J !<!a'ÕGXJzKymw~o4 swj㝷~qsL 2$"^ QSK+H4tk. 8TPgf?:{(֏e ʯRRMCG׾M.쫲Z$ [g# 2lGb>ca@2 10!cWV.ߴ6nV‹j(zg}f:Gq~ؑ.lcc [߼zk_Wꕿ ?&a_auկ@%Nf%=vɐ%g$"}|ul="4u;kN{?g,M4օ'NlIZt^+"\鑍C'B={ȑcz( =[ywCDD Q $$2M l=Y2u]EƢ1aHz" 7Mԍ cmr<0tMY'Z]\}BQTˏK=๳V Q7X]Y#?YMm @06Mh|1N/^u;Af?`8X:pY..;{8dA…G8X3'bެr.@;瞹F;E62 z£O<:\]_[__?x!ٔ[~MS]_!"=B"@",@h*kB2[!DOƍ}Uft6 >88Nd1d:gk#,`hB,. {b:ح\ud:Edf}+=uW8zkD'7?pǝymdgsi ~7\^29̋ޣ?N^"xzM@ρ3 YK}n5iZ%$D@7\W 4VD5"\:la/&(^5.vI~wymG4Dp* Ұ ޼pLY+|^#;.l|Q,|UU538KpET* a4执ʓ| 4 _ikŋ&q<70]-릮}}Ulo Aiiix`릩M>狻 c"@ uBcvӺn]!xxDJA"$!@ #qlBUX5MҔqh%h7d&Jq9GaaWVVA@D@g E}g&㱰 uSƣtkk0, WƘh#W"ABh amu}W_i^ Ac$k_cGI7Aqb @&4ᦪ Mƣ3vpi? !4Mڥx4 _n<|껿u@4KS`PLa!"A<;qQ"('Q"c ["l >X @X_I2Y:|G1Xd83l<{s76'NB0 "Zr^gy97|2 Iz`<ΦYU+K˃$tʪ*ʺUh7͋qoXƒ<׼QaazG{^kF@}2HkAa@aF$Dl Alm"BDCmc!F8'[EH@ʦpQ56N]`C ,@B`r<l<W_{MYB,@<708r?\9yWEd{s+˫n'CDD` VG 0#03#3y˗Y$mvzőCiH!ࡩfwot`Ox:&kku^7_VUUuxh4CUYysE)R vw{{Ȣ"@@DaB@B@-cat m ' "e1"#+Xrq:46Q_e gmI) J)rx>s.a@Evb }8 Me|/żϜ@@@ <mn^YYM?v`?pxuee'A@k 3Q^1/ADK7~g{?&H/e/1CEϋ\Dq׍?rRu2*jo4Z,7F.d)$KS(b>/EU7iMG4K7* }>>T﹇\B."$ p0_~ , Pn$n `l:*/$YW꺌R=Gpf6f{O?4$I,Ykx|ن`9&_ `y?&x̡c6;K嵥kN Q} % laA|K{]In|Xndny v狼%^R%@4m4ƘXn$M'I737|Gz+ gw3ľ!U30!W1!,0 o@ci "p(Ufa`@mZcg=< "P3KHXUe`u%gOͲ%ǝ1,ll@kt:f8v!Qu egsso{8ƥcGEcЎ a% vM@mA/ū93[w_5?fYBB!b?Zq &i&B`#"  $4M@a`Dyx$cA^ akHdCYWs6t)8" `KvNGI+# uSv7-;{߸4|zc ZkCM]Et;^ouy JjW9 Zc7l{܉O& (@QD6mEct($PFY9sĕ.!M}3S^.ammu4H;bvN aQK!!i>\vŕK++n_xǠ^@Y{/ /y'WeU퍣Ă@Qs,M4*bQڕ啕% 44A}CDqd̼ncTej( #`$J`" 2H  ́C 0K ե.%3"DIFE>Y,+I  s0(LUY6(%qSg196".@S|'lX2FEYՓ?qu:!K]@z1).?+y^Ţj*$sED$HAm2s3+(T5 _mL's69'"D8\QxHb'IqĈ0/Xx}mmyeB" uSz1igIL=x繈={ȑ>/YU'_vSȻ>1al <Ƹk6` , "@Hcȁu%38(f6UlTU! |i],Qca.558,MP\'Y lqq]3>pgi88N`tv .rn7N㦚C'qBpd"`B3m5/'CL[ZWu(!McE>4uմ͇|hC,KWk+>0!Vee-(|㝱MTeeYEQT " wܚ'? Ch}ߺ0gϾg0Pw >0GD`k"t`!I@`A` 7  "",@@pd (Xgm2L mQ{5a!,WI5ۨˢ(*#r7uw:"B@.䣋.J&`5Yp6ysn R@-;;ɴithm[$! "1CX f~q.z^+*kJ:|Ëbb^䲰:cN'Nnx2G, `"Bh:I╕ղ, "EQTEW`#Kd˲$2"u2"4ͭ}q>/ʺr9"O}ѿ~߇DX8"ӔR6``6$"(@<CL@= A1 ``DdLlܩʪY@֠3;EؾvBȦM s1m{YX8ghwg<,bUe1K:qҍC@@dXCpþ UGi;n[/]*C\qeGn 4M"q4awg=OB|¼(rDdN^,zo|(roo\UUUVϟ9;ۻ!uhw/nn[r IDAT Oh@}|#"dDZD2 H[Z10iC`[.*k "4DH4d|=gq<`,1ā(xo3f|t(!K!R]L,m8C$̈@vʲfnccGk{(qۘb ?w+Z[_;t !U, " n@h{/EO^񟼼({{| "/yk5:AL 7O>uY!0!͠v;ز8NBI( {| sD2za}44 ~YdD$#CmDDKDDdb6PD x) Ywc slF,gigI8q0d\]vM]Fs (N睋5HHjAwww/\Pi&I,< G ¹F c;;iII@ X<@O]_gNz}|m2/Ϟ>zGN=u駟ygn/^uW_}X,سzgrL'pWUn"+ѨgO=;M .J4Pd& k7Nno_{nty>)?̯گU>_"ABX bJr"ZP ApF|` `:nhv[6  ڶ BSc|Kqv;KChRGb:ފuI%,i괻DXDS3~>i7ǣ|Qp!kL^2e%Uu- {;/>ܩCW]}9vhk*""l69O6g{Y?/ܹsϟ9t/7\u^ʷ[x4:ܽkN'k|Rk+hV5 7U]nF{cs/\+sNEqd#DuBh0X}k BއZ1-gkGFG{>1a`@a`hDIbc"F `P4Q46%ٯ1dE8AV bZhX0Xit%(N76I(oLu%)r-G[D|@/A}(ho28vNb>N[?t8M#gϟ~v:n>hg3=b#C`@b]M,;}Y>{Gay24hMX1ZA#L@i@*"oRQh0jgIXjk5 b0" E"i\ZU$턦L!z]'WDb<VWBY4n(uU&QiSf&2m,ƚFl\N;NzΟ ;.;q#G<̊uO-f-M==X8zdM8_U$}0Z}[ U=>4Iq؛8\YO~wlO}Fn+O?veIߛ'q}Y,&9,pYB8ͽ\rup'2 uPȹN@}[VW P-x_`2>ϦWK9yiՂ91@,sQ(AxAb" _8]s 0lۭ͠!!$2@MHEX[,&Qpu8#DpQ\'WF>Ԏ\l: ̥.c7֢P`6|XlI ,MݝQhkq$>ųuU'2I7gnK>檪Wܰ7«^ϟzK6~'N@Dk 9D۳E7x?;^rݕg3Xi>,"! 31F$ \1 0@ p61`cK$Z`$za߂!#`FY.|Sŝ$"s=U1˲d> K&bESQ Gfw}( f,4ͲNu{gfbk++4Y_[ϧCǃFY[xS"~sUW/V!A?O❀W5kqd:]f55N 6NP<7H Gķ흁,bTE>N Gp29D\,fqTMFB*!Ib_$ Y|9D"@Uy&fÍdL];.,Ba=UY9rY ̑y|:WeɁ/W"(Jm i|qY8Ch#"H``AD!g4CGmI`{A0P11~9&36"C ڃ30#b=Ȭ=}XBkH P^XRne?Xi?@r1O'&Tࢀ^ ,`)b+QRUeyEhlTI%E2[Vyv:UY$ ":`!:n(*fsyee!g-B,BG`omN#aYYSx78x㋮W_{f׽ۅB[o?Qͯl eH$ppۛ[w2@ۯA83??A$_?Ճ`F]GmFDkρpA @(@&,~dED-8|g_|K9yR4 >'+"!{I8!B[|/,Ta " DIDC@ڤB" dH^XlnOA2FWa˲H;KE$Ab@jyQ8M E#7Id/(k~QÕPESH"\Uu4l>u;^O8km\NlyiiwXl6_.|sN#Co~C.Lvv<~`n7M[o_dK7zK7R`nu@ԛOL E{~~x@^qU0Xj|^Ħ}F"ܖp{2ۮK'"Mh4/ ݬf<. Vå NX۟@h;FUQDIFРdi牅<  "! cu7`PXH 1 G 8ւ$a&KvDDi287.Ea"&v'"oX@LzKe06JA2&H.vN'6 ~׳'2;S`˺Ny+%. _Ëo?I",!M{܋DжgAiJM{ #:c! (A!݆ڏQ{gx] uhimu49WOvkdKD  q ((!HN*{˱b vȲelI;}vZs TQEh:ܺ{1_8?=>i"kԁ1{QS"G׋ٴQh(H$.nW⟮B4jvDh8tfn%i(#aZ+n6mTOlxK/VezgԲRYVAd8V"\ ez~;?{ɓ>_O|˷_jZKNEQ "*x2xW}o[UO>k5P-MB%YYPU9b?'I~WwvGV/]~~WЇ?xk01|GeΧfH,B *!#&$v"$51PQ32!ƠR5CJ=Kٸ^ Lhqu.*"!Q1Qcf l.,Lv>ʙw 88XYX\^^Z-.-.:iUU((0MI_y'|J#YYL ͞mmG>zhiia#c 2DITœ4Ms[iZFMg 1L<|ΟmooZZZPGϞwvڛ3H_o?6N,үO,,JD{ B̚!R(1R7>}g϶4ͳ,p e8N5bPF.(:{ٓf <_}ڥ> Gy]-tZ̰ٔѰl[ԧvvw^ R ]zxxOӟq>oc$fBFREl`DH>X;>`vf,/޾vŕK9'T 溬&IryI^wqi9O,Kg!$ Rx:z_~^o0lwY1C$q.rB 4#bs )ͷdL@qq4F="FgY۽aT5!Y@JZ>:=DbvJщ`BF-dAߧ>Mh"AwEQΊr:Ev fLxB RYKfk$rѓ'O9Fgt2Ghme򕍫7Y0&bs4OrI"l4vh4ҕ_ܾ}KRSmu#ku:W__~w]EU +F8 yҎER,$ ,KAUy =ϊbϧaF^~W]_|-M}ٸ|99̤ZRQϟAVl.onHURγw^%Le=LIGn= IDATο:[/Dy{~|s9I] YRTKS bF FF\0BPU hfqq-+(wVU.:g>!Z!B2TOKKY!PUU&20kxy|wvky9!\f<8;G鸬ʲA*a<7sDE;ZviYo{[{ׯ_B0U$4$I:&q|񊀀/FfO00Q~c Q{dQLB6vvh<<~iNYXt{_yk7oNs 2HFB20D~>D4 I٬*"IN'ݽ݅B\k!T>I4Y޸~O|O\o"8?Q0T * DUAO&$$%$2X#"!8HhP shj&& *ugӉJfYYliİS7D)QE54;k cTՌ9d< gggVonlY昕lɽnj5nwaqxs /\yr|zrt|r|R,U,?˒zl{ 1i;0YT__5 B$JkZA!YYT-Wp0O !㩼,x4Vx;:!ýWV֐7ߺzublvy h6 !0`tEUN&S`N;Իhm}lo}piiiiq9R<QG]<cd3(NmQ㷾Cz^}p1 .P?~1|\ZYSl!3ŕn41@0b2@$dSؓ A 0oIanJ 7a 4pF(@Hfw)Bg,㩑w g8I5ʚbcei('ykq6+ȋ 1CJ| EQE1,-<A]%Ft`郝Z-ۋ K <_Z_q1S}>x<Fjƌ1Ň Mfu,OkQ{;DgaLP13H'T ⌐T;12Dht Ŭ(, wvwOg_Z\ZMcKk޻Z7 KBLbf1brQeD<8RU 'O&ţ*V{yy7_hw+ : :r]]LEFcVh#D2j_rkV>yvxppΝwy/_{p1 .9`?hb,a6s" Ƕh-R@\V YLdb /4ADBQ#)@OheeBTQYr28t%h@>$4|Zk늙O2MPog޲c'" HI$͆vbϳ$ڿ~ms}{{jY[,Myd s̍z=I2\gvީk{$ZSΊ{JU1pXGh']I>=폇O?JHؼvsqil,--^|eem,ITUTm4Pca TgT'ӓh,"ӧznwagYtE4ϳVy9c$ D "H^Ԍ3*"({G>~=l{wb\j c'-(Y2FKBbC0,2;0&@0c`043R9df,Tj EY$5 (RbV$?7+@1jEۅim\q)*iD= IӺ|yBUUn1:j5y潯g7;?/Mvn7[FUo4Illn,,v~F@zI̒D*i4<vvvy铇?}*%7ڈ[PCm'Gz߾\ z> >#aGCҀ12 `@ HȌhB; X0ոP\*L(zؙ9Zoa0lt2jl6 1 +jcBF""7+J074OyDDP  @U,I<J9e9?'FlvFfYeׯ^qѝ;wz^%t4x8`f0Mi-!vLyz;#NMUFozS?3?՚AiTDFXT%0;=U;?ƳpgY[ AT«haiҥ>AゥjGQU@9Rx0TMq<GS\tozpdfiޔPtȥV$Cvd8-$KeE?c1 fEFތ@!FcFF4@@Q)ڛFL(D4E)+((xCBBUs"G̓ݧfPL"I=놠b@ DƈG9It즵FhujA,B`D޻6.r/߸2ONNnﴚfiK|`K_sn^XX;9vL'B, gadT!T! Z{ݻ$7ntnYi`FGww&hpLjyc:uڽ|W+,,b2 {k++Y^WTU *hQ®egZٰv||2h:[(¬(\F 0ޱӌqTX4E#G@ʱ3`bZ_[o^] b!ePJ`@a2`Q.shhJ`'I"lS8w?6ՈHC0Tp`bECM P"\ ѯ ($ Ro(rx\UyDhl3sd@n"CZ-7ãã;뵬,////'Y^+7ϚZYXu;4M[xO<_]YԨԳbt8̲$eGX%"j݅ŵk/5Ua_>񡪊idI>=>eVsWe)rf.K`PYh5^y׮ɳOONZƕk;drpyyiii٧ɕ+YI󚚲"ae<OON+ 4j@5Z拈ĭ"zO=T޿wvf4osK^@@hDP@cD3"E@j4R)F}tTg E17z*E׿;o~p&Dà PK <DQЂ!aU`D2@ M01D"_" j: $ X O3Jh٠T!w i-O23!uLx:,,NF/ҬJrVNdzٴ VkkIE1_ {^tzZ-OSgp4iF /GVvs(R"WǵVW}x\ê,._ܼ j sN.bHDD.j'Chş'q9==}NÃ_,IzVs ,%t>IyDFJ03B PyK$؃{wû{psIUh!%fZqp TD 1ʘ@DAL+}5iUa}| Z[*IkM3a"P+o\tW\EXb3+%)!A r~~EFBM ,*aBBd#PTC@ 5VY)RIB!2 (j0BP1cUGx $omzLNm.;=U15 %fV0fh gY[]ܨpxxpxp{f[\l'_v[gsF}V/7?/*IuUu+/NŠ?8;9:?>[/o50t1D!G3-DH$hfeQzĸ3OlNUVy_ڸh6hkF$xK@)312#g2AXT<~x~=yH$$s>K ՁS3T5@4pV"OhUSժ RU)e92͆gyzZ74f'y%BO.^Y]fTow.+^7jL;A3BBYI"dvTLAH!|ReLD% ")3DSjDT1U0(jf!#Fswd'J>:<;=Y[]ٸq9tH,ML bQ6gvHʲxr|t< A,㣣VaռKҼgy*C6r, .2HeYI+Q y?ieu`iY@v:VeDf Fs\< ΩHT/͋pq3HQ16"rB$xgdtO|saD bVj@Tdfl <ab2= 'ttB卲9Υ9Hg55$hpZ5=%A,N*TU9f$K. \*eh,]Z쬭.Oo><8?<:8kvwN^˜K4u*^Ǧb8rv˗6@T-C$Z""e9NĬ,ýfH϶敫WZvnK+H V,r,.da R%gG\(A{?$w0{0j-DZ^EηZ˳0#We40B\!YLL(QEHu4 UYN4y@zk [J^WE7n}`ҭѴ8?=?>;:>ȲҥfIHɸjO5ew˪:<8 nA]ZZZ^]%ˋK %!81SQD`8T$b5S(K$LOS7ȳ IDAT{w{{fv}PY4F[zUj*EJ|.@ {,t6 al6βj t.5):wtܞ J\*a .SSJT?GܷFqq3^??U4AtdB쓄!¯6g< !!`XsG()rfq+ 8ko?_GL C(ޑNǃ$ͫrVo/EL4y BOgQz$0?1`DLhA6!'ER=<:LY~E5ry}{gj(ťŧO7NE5'f.n\HӓÃӓK/]z'QPM fg{'n0//-u{4M7][tY8l4 2Xc !GaHDQw}$NhtW-OR9r{!⸆" LfG05ۋSZoXYkAD`5 <9"1!A1"]HYQV !&F4T*~'~ \̃aR *)ɳKo ߞ?ϨD(91: iNbkjɠՄb ܾZAQ,$y܁*$<;4 4M~Zo ? kMd j|\ C7_`k{okgs_ݏ*_a!"%ULORLUY$ zegoߺywp~:yt+KԧABU&ROS=w~<bzicիKKKP$x4Vptx>M׮^mZK/ozf2P@z'i* HL4H,~hȉ':;oؽ;ݿw{z!c׳4grDR1 D 0EC+@4Ux3 @9*A1$ϲKWeL P ؑCU̐DE9U.=QȜjRfY?H(gc Z!qfsIḨ *3#FD~'/:0Y?^:OF 95s SdDV<+·%@(!!")ͽL-."t@A PT Ab2"F1T'E1I(fi*"#jFsZ Hd`'N᳿[[~"cRD rjFبט9w[z}ڻwmDH|ꓤ !.$Y[6v5[5`;O}HGGǧi(M}YXZv4ԧAyFsd^ss~NbvINU7?H˿spweVkt|nyL5PD$﯑ isF1Ir^[vi4nC$ OH{otuas=wznt7!"N $jp)*QIVT,W$R9.Yq%;l-k>$%9(S%@FxZ ޟk bAD uC3hb„mSsf*1Xڬ #{Y:ΨW$??ώg`JLumz^cuv,54u 5DMcIV|01IN  #x2%L+h2Wb**쏊`MB6DBn/|=l\>⋙Ͽs׿=@/G^`}D |F`bQ#Z,wvNmlnE[[<}K)zút:]墪sكٳsrs{sn`8FY!P#CDd`"]dT`cBk$"B^+B$^c*GۧMG!q ͑•p8QoQplhI0fF> E#!{&w' vL+2NjBhhԵy_r6 rJ&SH @|%pATcӴ=B kuMɮp>#*|o+]?KBrLLާ-)a`z`!{ !MRe2$M19$( I8: ߞMZl塭lb:Gb>oXm $,O:G^ި"76F19']|O/5{G_G?"苎pm\8'Hޤ\\zCoݸދFɲesߟ~H("mۂrB/h,˥M l0, rb<,Fr&.x9r)w IKU4}W7տ ._. eE_T!( Q T2!QJffD)”0"1#3  $!B=׮&_fyWVa*(\ LUQ t ;-k#$ M i#D2$ix+bn]SJ캮H!|{m_ xHM?/|;j5 a+Wx#"E\ Rj9|  Άye)KQ4EPô3t#ѪzĈR#v<>'Q 1/G`chyf$^xM{txDO=˸"s0jۿ[Q5ĄW'=/Y2UUzշW.\x"S^ԧ_|TlX#c!@s;bR55(`yk HW'8~ׯګkgŐ̻+\1h Q@ș 8G@FhГ7u@$f DE b5DRjF²μ;ڻcXvΖLT!Ƀi۶maԵi$6!QM'CD_$ӈ8mpi7ڑ1vchQl硝բmmW5Bbg__3p}!T $"꩞a M߁ϡ !(% !  pL Wm#7"8i1" pԱ "ShibN>71 g+fb>ٻ糲 k9.lЩ@泍lq񦪈{\,{}~???c3x֭4"&@7nzYU^Q\|OwjjFp6yK0815kk;X9WU͔``5n**:K#fVgl8vY^c``%~!8Lwb13Y@TK2ʀ3Bt"OהL3{[o߹իFej0ϗe\ΗEH]9r08MѲ˚n<;w.\zSΩdv޳s) @Eϼ -A6$DN˗W嗽^r%{4#cObWGm.DAf&AE]0FJQ4/j  bX<ȎQLQDUⲮb6E]MkĎ!GQڼ쇮u>kn:ZwMgM[JB7',1 i(l~;f8oURUdda}ʀlFޓ( vBgĨZ`y'%m=>of?˿A{><:? QD }ՊC0Rn1%U#HdT64dTDRDK$B%BB2X)2\\W$(m8[WcEd1J[V ?p˷o\۽?Ĺ'nq;:b#/ˡ~mU׳m[L+]'Nomn.d6/6:9zB\/D"e¼FIs_$3jYTWv$Jmѕ^~믽}u&c^3* 8Т1("Ve4S:mh Q."Dﳮ6O:yޗ=v9B[L̬^Tr>]7sbZ"QROŨɑƮOU!tsKxi}N" `m om(uЅiTP$4b"b UUĐ"+d 1(wE!C\~ fs}T wBt :k`}F`|ү !p;$6Z 1?GLj Bҥ(+2ZS05ߩf1v3v]7Nd2-Y=CRDB%`8M|>[,xtb6OW!TfEhkG_lڥ1&U*2"rzk_A7opF̽[u[;SitVdhND5&h~45z9fqz1\E>8qkue)5V>sˋɃ" ͼ^,Tpm8CzhjGZ4ҮfuU!|6 qmƔ̌EV,y~'ډ)0*$DbDN1Rt!A6HFC3TG E~Wmj /:P"tf{' R'1 !:@Z@!? 2kmYz3q<$&2S0 D50SAjrb[wg3մͦ{'N~y|>wj1˳,A6bv`8PAYlmnmp͇w} Q8/|WG]p]1A5P/a2ǎV R<&I 9BbLbRL)D.MiL +dRYS A{{]W'r9\[/geٷ}on}Gh:]җ_yս}͍ш]4-Ngv>op.\}p~\HuA7}p+ گ͛tkt\k'=!1;tH0- " ;DKu3mM;?I[WP9HF[hz5+!(@,3 sL4ZP58]vMۆ9NMQ-eӄmδ]V &'pH\Q ,}V?᫯^F. cr(A:&W5]BH$"d";1!mCum: Fb&G'ϱ+)F%+{"h$u`&D%8HD *fH2dCVCt!tm[$X-V2Kx 3#oD*5{o{lkk`\jYum7 >]8:X̕NmRε]o<=:4UwnGv>_{g*z"YbU̐$5D.E ` j.fj9uXNw׷ ĢƈvM^B<Q0hVy0$qae-b $tŔ74[6m=_,U,LТJI@,rk;m `-8_t]˼* }P $&EԨ Jd]43TDT/ffP͏[bs*Gl7 1 !tv]i\e= p-F D!/1dgdrgztP,ЁE$ b\d2tN{? ?'/"(ZpYף|h @ ` !!q6 dM7xc\$j%3H$"@DQ6I$ TĈVfĸ$Es0tMƉ E9db"4j9?MTS{7_5v* 7 .v޾}g;SEyGkd9;i褉~޿uNvHն67zΝN!/oޞ383t`g0CU)84Dp鿥H7&HyX/|ub8X3HQ9({Q! TPcSKz33bUK { L -fG*<(j2%&+ݥ,~H?0zr@R3= 3"`$35DaD$4 Ѣ"fGɞc]Yge@[.+лn ҷ::3ŮQtٶ"aᝯ{Irq8Qf@fд1a62Y̫KEVS/y9gYhm(_?oƣb)l@)2#@VMzac+D"2"ST)2J}- @[0D3 Bd D"q9s>@B$$ib6ˍMϞdn8R[,roĸ<~e@G2}ۿ}{{h2Mc{@^b6Ϧբip6~|oݼ!f]zډEtmR'DbtHj Vp`04Ĥo7K2b2dwmcyk$@"$rQRk*9i6#m۴4l9P@cMDq!!x}ٍOpQՍ+m\ȠTʜLRc2%ƱHlOnY^vzEC@ejڶulkkgjmhFñ`u]b׉fSCcJ"Vժnt N?r`>ۛLd2Nq^#J{ .DsPvGSt~~'{2TC˲Dv=x5&NTQJO0E`DdFiȏP2t2(Dv `HXO_ Ra @t\Hy]Y۶Z?h4{O.rӽ;=7f/|̹$oVb>?uDWw{{'yx{/2 ׶{޲wh9ltD訪O<3W{yF$v8}Yc3;2vI( ̄D+f8 `!-I S`õ<0#̒A@ HRC3P:܈x]W7sAjfM(i2ϊm6Oõ@\9Ef!q<"CpѐS1.0DbEZmD\: b4BG{M(:k&Vu5WiǃWcQ9]lըѬa# W)߈*ii&§z&XOG{7fR5Җ-tKHR3vW}|'GO+Đ FFC7{{wԥ)F1J@SS6Bph2d!MlPhh U 1:E:{}vpx,{fb@^=*zӏ'ycsĚХI 3/W^w䉓7>}կݿm<>uԙ3?w_Ut1Yu}t0mC˯\x{=BWey3AbϮ :y_{zxw˜wڱ#*:D"Ve[h\#dTHV›DB3Jٱ!Z^̀J53nU*1 4ڈ:bVeQ%m:LAiaz'i(1Q@Y!! NWEp`J-S]Tu1g"(7QQڼ&(f18gZ~.XYF T`KMQd?Mp6gG2N5191f\gbKPdrF@H2ݻoV|p#ر*Lar~)e"M[Rc]pTpsn('E%,_=IbL? 蘐:0;i(:HĶJ[@CV-l)$95҄9t1V] ""Ih\4D`F#SM70&\Ŋ@)CaUk-We UdhA0h:[L8Nn_Qjz18Kh?C?~o=ԹG[@+KQլ(w\wc'BWDn4MN>uM/< ^W.U݇j4UW7f=De%r%;g޳+WQ&fڻw:y3] C2KAi3G-  6u4,f{@,g 5FC"E{/plj`5#W ED$d `FED*+{P hfh f)2*1w-vڶIu.c!E8^WdM2k[iy#1Ŭ@`PȉN`f^/*j2ۣ:؝/-ǧ\ *F o/?zkO_֠f`Ѭ'>|Q:DvlT PEC2rP 4qH-G0e+"%Ґdm f h@ h~`v|0\?]I쑹[FzDT m/>vMFeZ l_[.gv>WOjZnyk}ss8âL7A䉓<ܙσY7 /M'G'Cw߸J½{om[/MӨTb }}2\FE޽:q1;Kw %pkjԔۦU, ijJcǩC&FiK >df1 a1yO{q!$w=*;F"SQS%dPQf #0((Dtt-h\Er򀉚jJEN2]ی7O!鏶6fYu!*.\3@D6%qDB%] 9ATPI@qU 4V8EdFNr؅wpgsYGYoTv"o>#ٻ3`|hiF&h*3y$ "jӎ D"y-geRt`Ysbv]/Cǹg _C h$NDc-ͼA6Ih^/6`H͢zA34$ A@4$0J!ymqUZ _O%Q5S<Ű GN8v$B Ab'F VDMLRRv, &%B5Qfo|ޫGt 'p+j4U%UW5up4~Q@V @N̔p/Z̄ <-G wsU/˓;gR  o7>3xtl Ndtr=BfAU{nz~xoéraP@ R]Z/S:wrF(]nkjq^lmf'^io^72;[w_\z5NdzW_=npûDSOݿMUzRo0un4tR|Z,We^=ͭ,ܫ^kK;_߅.d~֭W^yMӍm_-[;ۓW7~S7;wܿqÇwV[jXOͽzy2m+Wj"z^{<Ҷ Nvs@5GO$W[ۏh6i+նMԥiKYVZU5w-)5f'DgBDnw/z,?$xT9es% N2As~LӓHjeUU!Pp2Wæ{9UӍW^W^u0{Ozi]f.وRnt chaʝ634no8l~Ss!7MTJ77<*D:8 qE)=m_b9l[ U!K"c "5B@ y;l,, #vn85 KO F[)eo=8}hxi4.͍uAy4n߾u'dX,ɛ=vu>/|:㣇'g7._sbhz\2lcc89 yά嫏D=#'qhˉr.,yiׂBBE UAok{k0xkӬϿPtoGfFURۖfuIDe8ȋm5U}[$1^,, 7M?M3?;hYѨW' W=՚g۳nO$U\y执͍;.ʳll]zu\>|xШmM@W]\7rlmzo|;'ܘ('jqUS_t2iJ#UN`ri\LJLxSO׮]L'ӭˏ=^W@6p\94Uh6n)U*ŘZ m\;3x\SZLvu0!5ҌiJaix]/M]/i-A jݙ*^٭;]HR ͝lQHm(b`09E jt$+J 6Tآs B EhX IDATІ! hwfq77#` J rW7sp2#J.D%"kz`_qWVO&~/_2o{T ?{z,maD艕V+2ݸzvz஽-A80x3SD)T`jP9L  @"pa^3QNNښ~^xRNH^nɕnlo޿n;sb4]׿9WGnlckmo޽{h^תVΥdǭUsO Pz<->>Y@Np3*G_m[3n?[k-a-߹1_ST`޵^jӶUĀ0q85sS Ol$=zF]'yVjZ߬M iiۦ]YQ-kVy^3OacMYh)*- ;i#j% Z8^(SD W381.bG p.@@TBByp`kfwj@A$$13%!e0AĵD-eubuhvN''8{Q1xtϏ=NQfp'+ Hܡk>9󶴃hJNY8|vt )W2Iy1?׶Q7gk׮-\?yxLq}txxW.=YfcW.=v[ok6Ġpx+Ūm?rw ?lW3̬V[d: P)8'b!S<|pm)Eu4޾4(VOFp9{Ϭ׵H*mz7m2R8bK|L @hL RvLĉ0fs7բpRSh];kKi'v(, 0gfb^Z]- j2J %IVxT('lSuN4ܫD2kuڍuܓy+1"& wn &يYQ>%yddLpJ.Dp *Ƚ<ܼ4n:w9wsa?3b=xT Ϗ%_pJBsurpq{0G=D+qA/5rp! a!C,Yaݣ#[5iSϏpxRi:|zǘ9=7]޸sGGO~m`qe4C$,>|pcq~B"S#N{ɦ)iLa-yES{iYSGݕjQ}(UUV36/\L^Z-Vul UP<?#>?=<:Ovu/+ m9X9xSr%Ens'L@J w=b2w3%ՠFMSgmrjA)q:`5޾*99VLr̦٭[苾d8m;Rjնm۶MfW^^_&j[nK[ W~w1 ɺ93ٹ,[rv[w~~~}bb"UOfWF`@̦fŹG)ˇoYu fWFɠ'GMɈ>o}"DdQ)bL M2))&Ѻ`VRI䠺)(2:Ua*rlꉥEb<#1Qi8ߣ!yGu"1k,R\x47눤C(S'c3/,l%T "<++±L_ bV A -Ƴ+߼8x~rxr??'~yѣb{`- c@zyC\v{QJk$^prR,)Bsg"H 0 ie'mWⰔ;OT]IYDr5O7g;W{ިV?Sh4eї<6&I<AUU̼\.뺮xRZVڜN=ޥjꫯI.wio<>9:ofV'NCxd"'f"69Z*09A}7~gT[U4f`6 d"ʫK\sږ&;U:EyR  ;2ūD)ynJ:l%Gq/L pnDl,/*n.Q0@! J\E9`0WL`N.4j,#&@f ѝpS! /EfAD1-."@!C{bv zkD@1sXa:.sD]̜ZCvp:8qCS#AԺf*=VNwd?wKKǏj|]/;#w[)VbM]Z-m<=R 1Y?6VsPrpq$' DcF$I^]mR[ 6/s*7ژn_~8Sڝc;kϦ{+׽iJ)f=A)`pʕpb~>'YQ_kמݭ_~i8M&3CQկ캵miTYY9"bLUͧ$%N9ݳjkYd'?߸wÇ7UKGlo0 C  @UuLVXzx1LU)19L9~b W h="]ݻf]k/lNnr&gcp%Hl[0m/Y$2LOe #Xت\  X"FĄvwv&מo\:~Q1xteΏuxbsa,r1ݻ%3ǔ#n I;2rևVJ!72{mp.[PeD@r̜bEerp$֮dYKi{W*$IdǗE.O^??Υbqv|Ǜ/rFm.˶mhooossK/tvIn޼qv_^4xW}^@mHܜPBNI8Rk1!G8 ;~7=U\Í`0Ȓ-@3KΫb~ofMS\MƓp8J7%1In%S9F*p1u&!v+^ ,1Z 5⒄asvô"$ᢱh{Bx A̍R!I,R arB搫ݠ Wl_ $8[͋0P5 q{؃Ή"'IE@(L)pnE[FfW2&v"N,`a(DXGт"8<:8Fg5Gn>|po{Ga[?ۊ{ԬJOwH$*U1{i9ŘS;XZL,;339Qk07b,K@Y7sқnwUA'w^W˹Hp/yj˞[Zz1?<ӛ3t:8f>._4+*W9SB7oޜml‹YoܼyզiU߼C vA\5=v`҆CbU[D9aU~/GNO͊p2N6A\9|z|vjv<ٙvsF!qbw0:4` f$`L%Pld*HΓMٕbYLFdN 8pBR߭\" pE2iƪ݄<4Efa^ԘX"2 %0$<"b9(!vqW4vxǨʍ{pEN%/ l[41:v#p"ftnnk&د 0̶ǻwo??wڶw?|gwtx4Jv/|8&pC#L&0sJ꦳ e]oMvX6B*D$m8 8CdM<_dd&n,O=WBthc_8n =i4 ^֟'xkO76ڶ 3|1v{g{c:z=!fNN[}-J旮\F_ַM6)Kb"V[[S6?#$pV{~GuοwG w#FGbqh]be;{(Q|)Lvw9`IA%2$,'G٩"pS]-/psow!"n7^k_*֦p:8>~gG~^G?|(+W?nluێ| xصW_~fU$ś{qFi-k-vI̦f~~ÿ;g̪?&d-RyK7qZJi6nY7_g|c|ctJڞydϽm˳R+RjX3>:<'ѨY?m{xxrl1jƍ+^i$eu z*WMiK)*qiצuktyog_{+/\UUOMwao{}+N0;ڿ=?9Xۦ\l{2 wFg;^VrW "1nNx1U 9%6%f7+gw3s7bjު0< PN,){\P`2'xAQ7S <j\!Y\YAM݊9 s+L GAP'-Vu7,ة dbr"s\gdUOgK eJ (F g`݈[uv bdZ@,{1[?\=@[YKݔ*r7k}n~x;O/f{T.l__KUųRmK7n?=?ޢ7QoO)tfSJSWii׃a?WY$'wxUaҬ֫mY?jl|EޖRf9&zLLQOpZm<5U9S"[/'+Ӷ4L?u-6fR Tޖ6]pWeKbHS"fi$"R9` C9Bى{QcfqS&@JN '$wQbFάJRAMQF<<N)fd&8#hO-(֫RUK'\';71L"ͻ 0K uH&[ckVh`hx`?$4ց¾lv/n "LD=D{rsIsz APL^k_G?oG^\AOo[5*IṔ 0`3+PD2Cw v$aBAp]/6p6h*'g^9|޸2]l m1~[?Ժ^sz}pj~[~o};$3z{w /X7k79;?~㛿x2mo{jԬڶ~oWޠmvӐ}')eRBDc]IfQ-ഹpS9s$7OuKIf"'MV4'RJN!n\Lrevܶ΁nW1qffdfjnJ,.L.7g$؅-77y13\RN|UN2b1!%=wR)H=1fQ5DE1IZwusws5VmdD*Ʌ H}E,&H((EsWГ$H& ضYd}+N9k"U+?OTt~]ܨ; ~0 Td6DBy\E59Kp"v=O{┪Ս=$zѭN59;HN+,ņpds/~7}}o#د o / s"<K".iڗ߿{z9oVs^#SC)>3#8F㍪77'[[;^oTJ0SɃO gUUիYf,9zqv|rquѦJv!m[=ׂC"Au̓Ճ:Egt,8sNx[̒h̝ ;z;4Xr d!PM 8dQf 9 CH؊5.;8AD"ݳcv+m&0Q:ul/0{$( X OUDa K798N9 ȉ[7ɂ}ۻ4uu@PaQ'f#u]W۾QgΗQ5-g'Lh5od~cl7W];BC$g EWZ-LKn_\}&]󃻇7KvcM7>uWm Sʉ3svp0vqNTԬGÓgDdN7.?5R'aIpΩT{7JZm􆳽 ^)fmˌ*$]AKI 慵Q)Ŭ(%s"5Af̡j%!rQauؽ#EaHk$%3kC2C"#[GwSs'Ffr+T2KJ`&u 0Iȼ`kcbb6?{֫csεw9|lہBHAR%ЦFRDD8!UQ"CDJ)AV!1Rlcsm_ok911&>SH[9UZ X+"eh1EӇ(U˘/DEJ>Dx=¹Lh"AKQCuҝ!DcNdv!2]K_~oM~O~#҅lG'L"&#[1ޘdH|2$; w!bf08F %C<=:;J#cz'Ki,b~sm4];qEޢtl6ds{_r%8>>y㧞z+UMgw?v<48{ޞӃ+W? Ԍ2šKi(G'}nˏmlN&%b Κ㝝 }_;Iַ.xb:Yo߀4&J/rII1"!Edc!(F Ёg~$7U#ՀT!ܭdL-Ɨ@DMpb־ +PFÑ*s x`9ٯFUtR;@*J.1JBRhEbJW3@w:eE2@6 -AUA X/ WJP)UΣYA3Y}PhU5/z\?CfE%A6E1,O TMQ%B D"k#ȹrb}1vi1kĊh Ժӽ0kSCv'LJ k i'8wam}pp)_{ՋmM&l~W_= ~{߼ycc}X]~ګ/7M/LJ{{7{j*M#mA#:F|d:4M''XQg.\f7$t Dký~~ൺ0ԵWz٭5]BU,+z6Ų9f00!5Q(5(60D!Y>\F2. Y-b Na00uHj` #B{ LySԱ;ZIhH(_ (y%֚ Ƞ14`WTT/AZCPFWLWUGu:!MLQ,D-U_hFdXMB~~W^?|իK,1j͋7Fcd{MTy6 S ܫ0(T`3qFm_.fGD3\x3Z1φ>+0[یT?c"2L d9^?rZ]vֶm)zRcz~…ӓe?ln0v.W Qa1*2)*bjYʓ!("1˳ӃÝr%pqƥm;q5-P@ Skރ;˾CuHDKMm]QSWΤXY0*9QbIߡ {jr2,il<(`ŒkbVʻz NUF?CUUA {( mS+ShD"ʑ($!łDxP3w} B *"R(1C"BB mjPDBPY֨f pyDXb2\4c ʰ^k+kLfB)-Pd'6%CmQeghА0 ?O|^̑CEijݴvo]+ʱ` 2FYIetY&` .HCpwFaq7E3R.?NJguqtpe?_֥V3;۟ 9k@ӟ~o&//>xꩧLt T]lF^rjRor;;˾BFMr𶉢&y2:ƬVϏxzr8 o]|lݤmK+a"胻 ൒׮|J)VDDeNJnMBm9~hM4 pSV"A&;6R^s!!lFI,prjp4k %Z)$۸`nzFV,OXGSƈ.tR,0ƨ.Zhs섦_TI%Q "U UFAtUU$ď㤈csn$'%ga:BER!rDX*`V`3)5+&WQ2DBlr|;?<;DimXΗGefSS * Ӈ0[DƪJ1Dڨsx4)J+RT!tVvr F O̶.agGw_:;[Mmfgn7XC~ӟ _~>zӛ&@ۦL'*?>:][ZR;^'AZ!;STR#yQ2"YUMOvyp@ʹKOl1ͺ,]<־nBqym}c6"J`djS,Qb@I1-@%=Ai#%XU% 5(g $IgGd7D=9dNOtyb԰sIq,iFʪXDۢPf6@i` U%"Xswf*#D1L\ivhh&*RM$QT#lUTG2_XQ2{^!M:DlK k^LV,wPb0hdSjݤ>sʅO_}j)uuѹˏhi3_X55dcW֩Z P4q8}poya˪ܭOu.K_h7]zda1@ o\oԦ"^}o?5m_|׿?ַ>AAXS`ǧZJ:w^;:]LsgZa8l܋eҵlh)MsWg0RJsWڮmRB:; ڹٹLMiX{ iE< "̸*,M-ˈ z>zGpG8j)`DRh+ xU)odQE Dl'"6HSn8)fFx)JX[5ZݩՔ5߼fd4&**ȕ:e0q*5EԑӤKcV:E Z@DLfdNhGny7_ylf]alz A Ѷl&C /pOw-ߺu_g u6]bEݫ{\r)$`PBjZQw.mK(bVJnrOgm m;;nb%KUhmawt T$Gmյk[n&ʶ)td!6DBLr6-Rl,G4vi0';a&@Z @%)TUat-l'4B&x$ n`dǗ{,*V^beE_2USO ,WgdHǣ ia|5 V9c* ->pG  })Li, U4OFRJWF̣Hb:a8Q\ Q ~eޘ>r~7dy7-R&n)&O~H'>3\]sE)Rk;/n~>Kh'[WnnlL BPXliwv^;}8/I67fjA R{j0u tE QiC?8P X#vPABj䡚hT,GF0ph'XU xh:0'_r'HSvQ8/QE&kM] X $TUJKAMJb*+ǻ^ SmD)QCJ~Rhrf zH0.7iwP11bH͡c]%73a&:L, B+*HQuB uG>?vk~c$b9 nLR -O/m/_?ϒi/b]Z[b9i1 ʨLQKV6V42byth> #!7o<4V$óۯ.ܺL0 _{kMۭoM'~ٿm%o}?c7_te b&h+"j2kW^ |T "q4Ux) IDATJ֔2fb޻ٽ{.}R'6J۩f)3RD],Nvwo/gPe)]unLLR :=4R #IL{p',,Q!yz):0%ᬬ)_dfb`1#H%8AkђDb@actdIPUi E ԋ4{8dWFB Ѷ &sMQ9o^#0'AhRu1фQamu23pSV$N(M#c&  %H+O51cؗܘkvtxMq妛nC=9ViN+;SAQ]?K@|w}Wg=DDtۻ^7N@4T(5=Щʷfyp,)ZZ5) ?9B]#eӋׯ?ՔRzr>ZycoY'Š=`'|㛮[cyރ{~G~K [K҂Mb^kN'Ri>pG 0#LFTU!b2ݗ0 $g6>9[[ pbM3?=۽u个TG66ϦӶm@aM49%2CY i*V%dP)tDdh'2$brv$#UF 2k;FPuaԈ:0\3PIU&e ]Ѫ_)g]AQH04D%HE jR @nE:Av΀.P Cu}NN VaIfY""jVP 2>+͏ RZ%BԬ-';t]S!BJFX24Dqk!E`" |CcZOn}?;O n74d?=ؾӍ'ed[7][nVgiJ{O=/_׿zjX ѢLH1H)}o}UH!@E!L?}YD8~rpw WoϦ8 WErO]a:;yq5ddXzn !p <<&A'b+O.ئAјD,bXvZ!9zI/‡FԚq@xgˊ JSVhZ0V. QQAITKZPD<^{$\-(^RT‡I$5ܴQ(J;a*b40TXpVI8@tNa!p%U Om%[M7C"iNjVlZAA? WbyĚ+Lw,ۋכɚIG@ctQU&ΟI+>(^4M,O_l==ZTsW観m^k!yMPt܋$<6Tvvӳs=}W^}co}3$v6&MḳߘL(| $k44+nmc'0ܝxnZ#bvz7YNE@ hX5n߹X>*giWǹ@QV^D8GZ){D2MdG@)jvZRL- QʊR5Q ֲxF9Hł!#2@MciU Ӕ1|b,̧T}yrt|z ܺu]3PkÅPQFExU|B!\VON/_Wq1 C~vM6Tl|".bl(IM5J($Ub5 ~gX tms[ʹNnC3niB/-}ML[SAO=_|w>̛^?O7o}3J[ZgTӳ|QJEF˯Տ|U+&O0Sy"E*p!`ɃΎwg5nruen6㨃qbpp^}MGm6rOV mψ a5n>Z)2ƷO*•[,eVH7N{DBOV5eP I{60Vr "(2B:2BU \$x)*fCg>]XUM[CT|He$Bejfd+V#F_$ÁdRdmA(l͹݈A. C"kdڴ"'9غtRk"YfVE ::Brx( z) ,U_|vD_l>6yi&"2 R\GG?L%*#4|X fH B@@־A T!ݸfճڬi*ӒYŸ|Y7.ˈ^xw~޽wLӴ*Zi*p|2o2@|3/~䓯 ލ0˨("N ̅.[5уW'{'.wkvME0ƻIWw?މt{Ԡxlld5MV DD&UIK^DR`W=XmTdڃ$iFL14r.dnjt@RCJR4lL<8Tqdiq0e!fIJ_ Qi، aEH 3UBQDeXp!1b6ԾR$CL^(jIgH@N{}TZ=i"Ebj#5r?'" `QsNjxIBFU59+LJýnxWnwg3ۈeul|C": a(%jJT,ujvzO}Ѱ__ L3i*z._v"֎;ʈ*(+Fʤç?4dICj1o6OZD7}rzzӵ8]>8>;صX۶LJcL(؍G/nnuѫ<_X{7m'*}-O~v$YD#19*g`4xr~f20Y8ܜL:AcaDvr}rWPkX) ͭkKPؐ#,@2U84Ɉ ]'V411**bXݛBT6P#l&%V^CK9̠JE^;*xZ iEjPj'%ٴH0҅ rϬ%<܄! ĝț/en]c<0T4<{H}^z`bh J"Ddz /1\M Дtƒ|߀ TIC k2''g$mRڦWSJ#fVpi@:Ք60VZx Ý;/=q{;w APѿY55-Go!j8lVJ)U[a$'t3QZu !˜Ln;R ك~GJׄg{ۋutu""ow؞/ExZ"tvn2Ghe%H"s@ 굊bѓPO`*:FaQ=],(;R週kxGI"5\V-oIgb"b[ۘh%\A5'C7S)*J餙D)!>0BsNJ3&JQ40wwrz׳?Cz׳/_~L Qiy]m]hgLVA5SUHHαے 0S*ZШ϶.ܮ9ٵofhWQ5IPRkhI*EFR-7"!RR (jP,ŲC,IDPhYk^)*]S^ūwHEQ apZ 5oPdzPD b::`b6vYC^Gl=":Ha&C8=J7i6yndÚ+M ٣T)PUUah4dvwq,]~{w?zODVNqH8NxE3U4ML=q[%uqRsg;N\ݤ3Ǿܹ?wpx|rHT7{g坼?wh׀/ƢE6.U':+TBNyؐ0J"FpA6HTkVp "ȓ[/.m߸~t{|13o;'BϏ@ż}q:oyd}69;[7~_|_ݏ1X1Zl8 zי8OނS"KELL& MAx_C/6ddlk5BB-vyv8ީE?ԜTAhF+bV/҂.^1"VV5KP+] #XjNjJ@KS*I`i D}J SHx.",[fܠ0!^+ )B U5^zS d_F4U|4!䢨:a"]fY©P ha{ŝjM6w)xN5 z"2ʼy$)!F,×~q?Ln(vm6=/F(STD%0~UC&N `w;_8}{zjz}1r8J)Næy+M;vP*:G(ajΰ#P"THYX5a+&lt%H;]H;(;۽rV s?x?ޞΦs/!Yw=gqX^_ tzƍd̅sb~ 6L&t:O?]9_z/+&ncȶiuBOΎt]>¯}s)f殏BDedI,<!Bä4џdZVI3Xtt2̀Ei5/f IDATrm+Uh~޶Sa*n  . HuPJjc X(WdɓΡw.?9rFڦ ýo`(4 6TB"Sb0jNI@P@#H@)FeXBHvhX )b3r|cpnb˥m+!g~5;;Z}3SsU"),ɒmUٖ3ܦr 2Н62 @z@`˹tZmv˲lQDqTM}ZX䞖E|w[z$p&GcdV0X$jAv'ۣU.^V`9&5MYJS\1>ؽpw\󾘨G4?UǛnTK-$ ͞>F<|eGa dYv6wm% $18XO7xK*)(2{8@L\A Zuֶ9̈XK@ycܡp$ܫ"i( "+XWiP8u܄N&pUӔ"9/Ll&2 Dv ~w}W@Iׂo癬ddzzDpȃɂF=88vaqwO N1pb<#sNRj=~{ݻw < vFM ޏH~ZvK {=}r:>֏d!xԬ\_Mvkk?ux4TW\ڌ܉8ꪒ\DA;o~ras7%=woW<4udгo_v.4} ~YؕNSRױp,bYKh0-(uQ)$K~@6˹%6ZɳffHV4RIHrb?z> Z8;yq!W3~F& ˶_Gf U'Bny$0AÀäy̙ۢ1h%Dў]W̘E@6ZK#tvz8[t:hi8ԴKN175s%@f[8$ffNAZp厃F򏾵+ݻʗwkx|g>2غ<ؼbwjFAB办cs3 b"[)wo@hyL{op__}l<ǣIMӐ9;Rνnݼ[o<<88:<7_yg.m]Dhf|HS~X5p$!5 Ype8S"ҳf';V5qrYr aMi,ۋIYMiA^뇶 Z/n],Ҁϧ|$JnTSrfNf3w Η g5!f,ďLMfN1Ku;s\'ȝK  ԥI .Fa'als&^ɣE7ZRW34r<NU5d"l#.AL'o6@%J] g4mKcW"$WqTEy:fӓj5-4\ZW3$YML]R)-i5s^l0eCBP٤>ɏݓU{s_{;+ _hBLNi)-r317k8RNӳ3S;_f7I /\/QJ!%wJ}SΙ=z@(MLd: /r)w{< >'/-׷(̌< LH d='+5>Uu|-@{QםLo?~7oܸxik8he8/EG拺{< 6X#E>9}owS-diQIٟ~dtcs&@o~ݼCS wI `&ʦ =^8'榑aaᅧAJk`MIR2;=-ȫ$D,GP+WK9!%b!p8CI_?WC@Y3ϴIKq;Q{RG @lb6Z]4M>?366֯\ 1H"n^J'oVjx<^Z &I۷n\\4Q,n_~Ѹr$!@nKZypOݘĭZ͈A'n+݈[ȰmR9~`xAd$q@DR=ݛ7a #q_2=[=gm6q+'?>SON]R9Σ+GÁ;Zi{iA_xuvEbR'FT K+l pv=\^y3.MN|6KNO6~^yi:9UIõf2'fInrj}|ض~ [ՊC0XjKhd49lC2gߓ`3K)VZ%UKf1SsxSK#Uɉsfs!5M_8 `N) HW|&q!ƥUUggDV>X#ʜN1&^ ì$$(,|vx*''D>y~+ƣ!W O^{]L9juwfH;L;}}wwş{X޽(=-<15mJ 0ĵ[ z{2(^8w"Sw3 D[kw8;y8{/Bn>yk7zjyyywwkWVWWF &3 Blztx;/59ooO^X[Y]on]|Rʅ[!p'%v(iUt:5 ccsS ɛkuygᮁ !<<}J˨Y]Y8П̜sCݷG͡V%QPJ5.Vr3dNaG3wD jEI/ Q"jȥ&85NFL"Jp,EY~ea.gI1\PS)GGdp"7x\AĜ*E 3 #s DF (Bv*e?D.f|r2^Ow|ae `x&2{46cW _MTODӲ8>(gN`o޺~쓟kvtt|K n;6jMrpAyU&oͭ[O<䵥Jxmueye7'oܺ:^Z^^Z/9<:lB?xܪHsO},?{ogZv--_n\%& 46ߞ'VVQ$>$ĉPڍfiYIl,f9{UMF`N"\LL0RU=Z0Hm=-;YzcW4 $naX'qxUB- 8K"NQ72533L2BEB"MfjH™Tb.r"WԘXvj]醣A-쀸q-nm+bQ% W)1ܙAb0Dyu  ΅Sq~/ow=_Q IDAT=|r&KR=p0fЩ1d#(sOe.0Ón>vjn|W K,K/9d,K, @3_?OܼqmĜ]i6t,fݿē_ع;ᄊx8##JYRU)]Q==zVNVք9%nW:{OTS"x5\[fDĒLg̎v'C5KQK0zf| `1$170"fOj\9z=j~F @P`bs9Yd$!Uk_sZ/H?k/pR4e$8ȫ$cnݍ\)q ++,=38R}1;<>>9-&`0/hYsIr¡$Ĕ Dէ:l JRCؒZ8E_|{;(/;/>o?)[73|B8 P@=qe47^S|6dqz$Rh/~׆ÖAruEyLSp{LQӿw/\X1sN֕*iF͋/`>џy`7~7GKkfr3#J-sQK;;=^,*ZN)7_ΎN'nnhcqndn2Ssw7ykzpzvW.GvWuw[;]KñQmWbDBL @LFL~]ڸ+39`n..bPHf"frstճ8E8 z@p6RV0$VD$Xn'Qv#F(DS6ǏbSa%%Lz^V|g+YONB$:?4x{iy 78I;^$+)O:D4`\W"/:˙YԍA *h?ؿt{o;wx3z{[7>4ysv?9=vRҌZ%9$zdއ:@=(rgsn^g>=:R'|}++K{]Y\vm[ղ( ~fa- ;iuSd^$teyi8f }yc0l8<ҩ'>l4J;absr?|qrzR?y擷n5M3lٿ㳓4`pemtvߚMΎpOBsELtx р9 [ubDoMX_Njz<kuaL#R9]kuZ=e(-az{x=xV(1#83KNTJD k E_zMHγږ9IJзVUG=@ ]l'NRJ9K˛F I9.r%F 8 ;`n]gMۏs0/DFO\=%w|ˏ=tjtMgbpNiڕf=;9>"hʒ7!ph)FBM sA\rbSB@ iuvrrR@{L{p TΝ;ZONV ٤[]K)SbFZ`k0m?3daJۻ/_FjARHukHJ-JwrzJ`xnwV?̻9sj26/-g>6L/ny N.|iv0h)I6ǧ|zf>2l׮`FvޞܛM$7f:$`d~v"KW.?ѕpgCuXs"2 H[/ysP s*̵!QfwFRJ[;s3Ssr(prǬCXa^p3 9Z 39XD͔z9-9iR-,M;lVf:<\k7 F^wJ @7 ]^Xj9=1V>)Cm&) (6 YXMk [rNns"(^wv$GʴŴ0M=T?r'X!`[R,f^"5H ђ4dV]Y<ܩgǜn'G&IJL奕vHJF pLD8@9KJVjsv776\DD#s4YVIOUqvvV&g'~zr&,yi|poᄀpp^Ҭ>9\0|0&ɓfqM]]Uvknuڑՙ4agəKCҙ2; RCMs>U͍.F<:=8D$BmU l5T/:Beaaf62T%]w~8sx[FXS9Hf>.uǫKkA)%+6)My[')ՍbȃW5&XT\jn2Ǡp 4c `7EmGno=5{O7O]Ô%/]>.λЪeAU KuR\ՒۆUO:(s'sj֮4O m;s;7g';e1)Lkڭ5NdB!1j(D猜0dnݫk5wQ?c!$s1{g5+LNDJ "X sZV8 QLN.л">wQL#3^f @?hUZ8uy^-lEgG9g]JaێVC4ZnZfgGǧhii0n\n.;[}%h͓3{0Rn]U/ň)\^J„㣝W^7?*Cwʗ7Sj[fxr^Zȷl6==&٬N`]^!n֝V&@ܙg>7o>9{X,x<s^̜%-'%3XJi{{ͷy+;^XZt?85)8T G"p& .iHBnkk&W^9l:=#ZfWmӴfęh7?7?/Ym6n[<0sfT+\HL.jp!eC; bvaJ h}ȣu7z)j}e{2Kr88y%%=8b"32dxAu-uRI9Uh)̦SSMΦ8q]$ximC7ml^'LOc`g+kz[#~}bgIPŪsHEA;[<ܻUP?==.]gfNlRQ3sK) K+ٺΧn6]Wύ`,-+s>ҵkWXdaH'&5''v;h.^\'ᔲ)Ar 12b۷ ݃W7WDRz!F$1٭Zx%B+0Q=ܿFYAp.ZibΒ8?[/nzhs|ˀKfb:?xMvR.=;z*&Ia7 'W-Z9HL%'!. Hȍ@4pXNI(rYƁ 0i:YrwOfp a{& 75P$YNLd ' aDDP(nXbrrOn ol^wP3^#lQז\p{t<$4]ZZ՚ZùFpJYzUNiqw #=#~0(p/ߕ_믚كbf.ĤpEQWx&9^,VK'?symyMS;-Fk+GLJyVتG)H]N+}Ztwōn J 0;u@b6,3 qr`33Qz/_ZKu0 G6˩N79fֺ,_mɒs8 !wWN!>#931z09ݭ:F LΉխ⬑BVHbLC cGvsgWyVCr5 UwU!81orrVg'fZZ>x:A/ x4,9%A4\ټn r8 dTBI4s$NպC^ww?E%w~b#p`VSvq3rDn9_Z[jAQuafL$ͷpF9 M./t:XX(1jjp;}pu W/4áC0wmjA#b) `VjO"pQG;?-|l?~-|^΋m"<tQ54kJ 䤝!&" Çy~fE>lbO'h..6mۙJP-TսO?+O{K* HD$D>CX?5T0${d>JJ!?8~{;~'LJ'z6CǪ95NG擣vzpҵsG_x~v-TJ7'X> LBd5޿ D/<9DTBm!Buj2aj ZZEaP5i3\l=Qwg**r}x 08=_/ǶRLz)899<<8=-Fn< pa#!TDqr't tMnWׂ y4"n)ESI"& 4..R9#Je&%ջ#{)7<ONJ'hyAPbytEKkӓJ/6_k/@t`hҎTZOg㓻ΤY,Ff=½ U2qq|rO_4[ۛ++KYS<:j܂&0#9:Rއ*4;I9^U&ZHFDI;c,Fz7GO T7Ѐ7 U  4Yvr˨!Ƕo$h ηwv>z0Zx[0girϭ>?IdM"’,M'K6l,,SN+GSp7G p:?-GA7;v6c8|o4TrtΐPrͼ԰,Q4 5]Z`'1*IRGb`*âDKEF0ij"2jV`*,TT"(Ep@\3a0#e0M_|d6o+CSqO2fwQhX?lm>3lF2Xl#s4*۹G& }gIuRz|Z˹U־-nݝm3uiy1|~jSsI@ er<;܃o\w7}vE3#A"I8y.]W$G;ThH.} ufMDX Q3QiҀFFKAl5#@Hޗp10ѝVpMDb碌~iaF2\xJ1GєvR^Bhqiie3xumPUU jk b<0H @(*Djq>zw׀7=ϓ9/?ٻDxUNqeQJffK/[o|굫6Y5Q(""*5-ˣNKwvvwzիgsd:kl>hR4!)_~Q wNNf{[2޹s: VVY:XXRh%L$nCXy-}R6Y!IʆD~瞻wTc?zRn4NOnfaq* }Q̓((p *< ##6T 8BĄ 7gEKj诣Z{.R_-T)T Pwc@zD dWnIjHa#,Dbc: IDATJ#PR] 9O'{'vr0MZ\j:ZMO9 pDwJ/sJIK?{kWDE"hreD" V۔_ ДRD T_~7Ҡɪ:SwzP4 0oOw`Y^Ҭ-4M3ZP5$X0S4bVœQQ j/;XD ?"k{ThUtY,.DJ ^"g+FiTiRuhJuk!*b?L [[_ʣG>~|R3H3j[b0^(I K9"!Z嫥XB!a DB>{O^8_ .~O4Z'F͊R=ދvyKIes}u8=A5S%ivyiOwvwO'+/r?5"7åݽmU99iey驧6|G;Ͽxqiw&ծ߻h<--N~?qjnRV (TT# %YhiX5I5QFN7?s^54d29`n;k, !gf³@@k~GjjJxWnNjnĊ{^,jN -)K7P*!%X $< $HD 8<$>FkB a֨Wg]g *AFX$y;NvrvqqRC""캮*) wJ p5Nf6ݭ  |2SH)D)NKneyiyyeg-.M@ۇ>ΥK߸0CPa|6LgWsonܿ߶_|vuee8T%Fp=_zz\lYBhyx(aq6%C] /owvׯ^{:`sHәȀpA[c ҳk/<ۂl2xjgn"<z{7^\40ҕK8͏vvvv^}.GMdJ;:|6oKZDӇ6._xڕ흽$8n;,x{W3Mn8S$J>=j(}ʇv~7?ͧ/]H9*0%fSrD/楍i'!_LJ&@!D"DNxM ¢,x(@@bAY ԅFqdO޹+IT0ؼ2MS˔Zx`alrrp"i+pz` FQ$.RǕ0ZbuBY\r3\ I݇Σ}Y4g݅y;/?iʰW& hq=`ia_LYqo'ɣG;;>}eccu84M&ּ+Q&Ѥ↟{7?s~|_1~vasor>^z`dj 7DGax@Ѥ0Q {_w[ Y^_i(BM|IJMyɂJi8Au:HFh%3+ٍ$z 4?˹G%Ƣi w$QPa$^$P5WCV="$( KaF낢P_MEE{r} &' wsk9$&;(va١&,kեH?F MD8~ۻfϓ}y18?"! k :+(DJӃ!KR"fe:q+O_Y]Y ͠iiŭ zfypgUӓK/K|~㋗.O>t2}St$MʩtbGut$I8+sDܿ|wʲh~r AIp]Ƭˢuub'LagY)Uc #Dfn p/R :/Ų*DߊM;XDB ʺg`V <`9BFJ @_">׽i=N..,x|xl!:=fGu5M>nζu7OJ({;?ջnl%][}y18?y[Pws"(N'd@33/HjBhDVISq{-_NNoZ?>:ӓA4>SopuT;Bzgp|~=R"4i$ H$AۀIGDD"}*a SXeQ2hR8P J-]@ǬHr{ I?ѳF^$ jZ@Ϛ֪dZqBD%

}mtL*1VǕx!>W![ H ˎ)Qe:wU*{ f4U]4a}* F[) >xt@"nEDƤgBju9g;KsuUܼ(VICpmr*n:Uw{җL{B"PBͼ@2EPAE雨>^\=%g|g+7^ F$NZ>2I!8 v:~_J̶"ĮAj8| gM^FtvU3˯?]U\*=|>1Hr= ><"fcgbLRRJM:@ !CS'yK1Gpuw I23D"@A1w_2RF)"!i*Si"ݺ󺆴sZ-^m}XCka` wDB[ڵNHXpm^;ӭAD}ή!*B|[\:@G=k` 5rUyUD zWtQ~h4w˔&ɒ|eq)%\l\ 3uT4.j5[]_HAA fsQx*G? o$clE:'`3 (/W bS[qz1o|v 5`LcQOHޅCGGYZA.ӐGm2SxCpSD?O+LߋpH׉ _~ƻ(vfzZ6D.R9Mv4ʹh)D)3۟eY,-QQj"/9XD9}qZZRn@2n^W"[Ą" u BDvRJΪj&T$0xgXje XHz(pV Ě')'ah4,ڸ^8*uUQz]Ȃ:TH]4 CŪ>úQ*P]N&&f"J))%NPABRRP-"*6k^Tr.9)9ΙּX҂zb=e#ۉ (z j D>\]Ï3 h~䑸9"{ASc2T-VGadT.=\h.LW:l#JUaC  5M6ۭ,&^O:mnj$Ag ӘZű^x_8 ҔI*Mi"U"nm v126݋#bsEdjUxVP3sQՄ ZJ!&+OٰjU͔F DtlԔoLHk^еGUz(k'I)haA5-nRyB۪+ΫPp!sG(u2+30NZ![1G=j\PW"NJ)fY2# 3$Ը|)M8\Jkf5$hYjBuB ' 0R)u (Ёe lYa0SX(u2Yf6;y|LРM×wQ5!L:/"ZHRv>,.E)kِ=PH?zvH{]4uqe󌵍J}u/v 5 @&I~,`QQ뺮+mWJe[+b+6!*=J zInJE6>R|(J4^tbKXou0 ՞Ԉ'-Z#=sd7my"J{2].tJY> z0n_`@j#J) ĝoY*~u(-TU *R./.p!ƲtJH$Xz=Drme31O9=8m3e] dBrӃ7\ZQesLަh]8*\qgf!xsw5\hQש(>q;oS_ˌ:lkE4)%D5jUBS,j ŜW,9OӔ8MsKDfN(H0kK`MEڽHH<'4%YK)R*vBK rs)%bмSc1oh ! fyu!eBZ9?=vn4N°,dVuo`ܫEjc-!BmKoz.u13G>`cJI@['o@-D`k7o6CB@pľG=O; QU$8PnB8R36F0H>}>$6+o<,df.:k*cIWm1QKFmM3)c1ij6&rWIHj<"NY_{hȀӰ w9g˺3 HdfF\l)m RJ)y 4M"zq<Ϗ? UBXGhH%H)=Dueiٍ2-蠡;L(y6LUQJU)u]r&Dt,EE穔bܩnu]y>v~*☪p:LH5Խ]aybf"l. ;ê;B}4*T|ܲn)B,jrj(o!m9 Y$ ԕA4}OoQY[n$NAb@ "]ԞR n"@x= 0sa#2GE Lϑ> aG#6eFKQ->8Z15&?6oy=0f˪<-#Ȇ}THS1E$R믿vQ:6Y }0f?Hiu!,w;lME51PJI,VUрE˺,x}Qu֝;/N V=:v*A(V6rf_.PLHJ,`3n/ٜ9k)NP@41!#=S:fstt,t~Ѩ Őۿ.CBeabc]Ñ^/#E0nce 3SZ-G<"%gCLj Mװn fMlLa dEШU"z\=4J+D%5*2EOU}$釲N(tv]֎`Q;j=Lu|sA<1~ֳzT2fmd, We@r&"Q bzI`*&"!% Qba.,TPdip5x9D@>rl@d*0q7ѢSMUA թ0 ;baKzEϘ@#nOÌF@ ;8,2QDBmʩIa\jg>s bc>V SqtO]7NVoqBx冫llGDr8jGzv>;?@1u:ƩmLɌKYcJ~&& Y:qW_#!)3Gp1A>gW T3@h㢪p_QU+kB^0lcjPJR*:WegF6cO{?"8.Ǻa(X}E=kSm+6t (ZnQK׿}! zͱ?hYDe"mufWD~v?ݸq0-V3R ٔ0lfr\^b,9d_]uvsT)DGl jZ)r)Ď}U LKio"5fIU s]C 9dW"()Ԥ 5g'~]^yZP,>J݄ q[;4hiվp0<퓞[s',h}xtHHRX׼ٮ+":9=EiyV-D-PTToj&ېqJD4("y-9뺟9ě]N 唐Ę~FKhgJ@NZ"|o`bzZJ SOu5wi4%"9RI9Pʡlm6ڝ16Tq-2 Rxxb(^x"0WogcJX5IuľAc`Cu*RI8YVц?T$q1>L׽-SWUaCE%JOkDžq ~? Fkd'/r`&؈]qcJz zXуw~ٜM]aIsA4d< l>iO3EN!$p#ixx.ќyL& Ed]6ps<|@yn7HtJ1a"MPmDS6sJ^ϙyfD)M4%f֬hKS"Nl ˺8RLU|juۻ:$FR-*E[䕽{d&kiJSDVkfyAnOo(&rh h nÁ=U&!]M<5rA@A!>^QAD i($A]`(VT(ySaؑ:{LDyW$x X!}l0g޵%/^[PF8^\k=`-?עa`0 AqG^ׁTxjjee)jvT:WRI%b4OsTGDesPXأj[с<&'j(hC@jcfэ.OAze'+A!0[A@5 _%{$>4+G˰;BMeF[NR,OTԐ12Qz`@2PLٸ[KKL޿{nJi&;NS"NNi"TfFIJZUr.R\s=ճڵkHl\E#u͹LcRHTs,3)9n7x;w^_X3BD Vo68ڝ^;3y<=<3nff7nX BHf(.yBe")n/)[cN]fGLL~9+ly(MIH)u'" <>¶ `FԱA?<`TbҤ' W/AАg9/J<{D_mƘ3>6t8o[M]mHł|F2(-ږ̈Hqf ,TS*r>YTjŚqk!4W+\ϹUk/Xl&yQ#;11M G?'`ge}A֠p-@8 XpⴺH*6(X[9FѪlҤP^l\UR:u"nZ|מ"c%B(Rԡ=蟚ڍ^xw{ ZӔBO>qAu|c~eP=|[oգ_C-^\^^/U޽{pӜLL 8d_ 6 GGu]iFv[Xk)RDw9e5,Kђ8G.fEZJXyU~yzr|~qLď/wӤ3ӻȪQc\_DgUӿ6BUIB%?)czk]Z5vh_B[W 8 ?'m s׃P vJz|߸P^TXTa&Q&"'%+zU;v`퀯%g1 #[G@]P4]Vf{!H0ـ2\A{3<B6exe\_YV]-ZK{bu~=,_h[%Uqũ~Z#hx$=C?ܨq^w€/}_o惇[ħ$J8%4}@H[WlO\^snyW?wJm;޽❼,eY챓R |Wj<;;CvvGnk"-NNn"ӼV6szD@K)'L)YͶITDu]*QN v3h)%1x)'͒R\Qq/4#fP, 伬F/DE&ʈ ȔD^97bOKh4b[ۇZ61EQ9qF;DwTkPSM;(q6PHZ'$It#@ EvŖ($ P=[ۮZ7+d n9z⤫BԞ>U-; nx?y}3=]"og^ADhSo @Ռl>Zs)Ml71Bp7NA׿3EyJX  5~?_7\Jԧ-# 7E TKv޼K'uYeANoT{RJeո)Y#]:~}xuf::>>==Mi_pU-&"E`YE}7rbHͼd.)MH,kι<)P2[< bKMEJ"EK55M70PϤ}ANq_0gp`J律VѓFB5G{Pdž&'^fMu$1AxҌ>2CB(Yb)P⊵=PC=Pnf[Y.EJ+e}b sDx~S<. 1nQjRyYu]r~y1M @xYW˕(R!MӺU88s+J|nĭX]p.!;*tC-PHhPb*jCDVt1D^bޣAuŽ]T؅8ݐ6T|B/}<cD#4!8d݃@h=ޅBk[Qx qd!U m[Es/z݅,x~'_lb=qwB9RDJ.k}[/2 HE$z%AXjDU\D8o8jݺmUʲ,Aq˲|HxrrBH ^* Kjz̔sycd"H)8H~_PT̡ELsa&)RQXZ n8 ޥ ~. qVE_z ap XBoĨFUQL1ߌY~:%0P# \BGabŕ:G Ba\| C89E_6*r?٫'n+V %aX&OÓ-JϪCWc xGî܇6PJ4.~ԅSO!-lMQm{:PB$MUږmkCOIw 5$}]&7bl f=9U'n{嗏DDQe81"rqɜՁ⭅cȹ'4"ڞ\oƮ| J(C[aOݚ]]v{j5C n'~mڏYR$s=l#نT2T: (v|^X*:ͮBVtЈ?e~KtOYoͷN`A)k=SDOIAHT^ YSPO"8Z M|~|~~kt&SnaC![ s#޽|m!DȄWwE $hc{ C;Dz΀D!XP(aӞ hP!7⪦ -  @=+fln!n~fjn(2TQPQq4@=+wTn IDAT`Fm?m Mb4h1@$9fH}H2K!^__$^۱?~ 8c&v O44m.N&!?菏Hػ)"k)GeGs>VKA|r󅛟%fJ.坷vLH.%뚗[7+\ OEΏ=v6A_/.6Dmg%]$V1MۭTG w88WRny]?? /?'/|yӬmqvve:>>纮DZ׼$Ez*Ț 1-KtV*U s-) 1%RAzX3%a4*<>c_h[uaaXwQ۷}UӇ)]}ZF\ 2VCWݙl01-!&RB($Dd2[H*ci<8k{C YH`N>sQ#5YJ)MNEoRFb7ajkJ4!*8jpA{0묰LàDw_S3sX6X`4沅d&6%(jw(]DkA:6ώ/&F逛7oB 8%)Yv˯~X!Xr.de4M{@؞ gir ǃs͛UA:֜߿wtHE/O L'/?_߸~!_qwno|7L_gOm Yos0?~|iK.˺Q./i)yZ2"^ )Ă5-bU0Q⺮XGU\u4CDjBJ*8󳂐̜INA=m+ԁSb0|0Q e ]ԝP 1*ރ-+S%*ZIjЌ! 0xX{]BJX^]-e/R4HSJ35>ST{]Q,K61[ Xʊ :3u ! ^=HSVE?Ez>ˁ=J}.=8UXU]L4H(1FQl"pI7C׀֔RJ2\č(:^MڷM:M"ojg(AD:9+"&ʛ$|͛7or\#Zb r~nvGR~2WuiP. :Q7e<|NR n߾v󅳻w +\<|plv[l'og~w|ko}?7Ͼ{ٙ=r)*{I4,e .񲮢BmeDL)MQa&2[+*H aw\W?(durGAV @Ght_#T{B!>%$E[Hݥkfe;A2*5 JMŤmlZ|w(È]4vwvL4q e U)B\_'R(l$Dȹ~ Txة-^=vy/6U/&t#A[*jE5a5V쁦738HkQ zwU.G@6/~ _x:`ؽSJH\E 1)'TTѭ[7OO#V}.gd4ϪP{Q)E Tu˚=UUyDislxyqܜ]?i6G$cYv{r|o=x=)%_s?S{ovv;Mso?<;glH@ $NJ|]wGuYe[Dr3뺮ymLsV @J)<ˋ<+x<JX[`< 3!o\~U!&4 z,"LPvYOd& ؜2<7LȸEh&vMTSEZqŋՈ jҮR,9Vb6hISH +jHF*))R9&%]Zt1B*DpqXՀak_b:$9NKTIM' h*#W (U͜KDjt,~^C:ɹ WpiL\5J~?xՏ#r:׾5J/۷l*)=>QHλk׈ փ[/9M7:2&(^^^rS%!y cȍF9gڥ ReRR*43[2۔ѱ%9p A5T#䜈W$uvzK)VWKPz;)Dz .Y@> n|_ C /oQi`ͩT'{nRCPNL`ml= R$Cӵ(z*0{@/~񷞩.*eZ8HE7`W^ydR镏}{{xͻwyţ >/G|ptz:9 @E$粮eٕc9TuP)i>ޞJ)*^~} VqOU?/~Mzn?7vGyI> Ii4Q_w0)qJӔi&T$^42U؄΀RZʃ_a0Coђ=QR=n=&lk:hvьG> whURs0P#5Ehwz"&j?nd:Q7yP"+h;&f?+]v3MV7oꫯ~%C1|'^}\T=|,g3ɗݻƋw&I)VesJZd-k^r<;a f_q$d:qc΃߿|?Gϫ=wٻJɐxn7''_W_~׾B;/{zΛIS͢`J˜u]MKc]_ +t=%*F 㠡16K-&v֖S"eRXO=94%Nd*.l]7]6쁂BjAR9MӔ4M)q{h)QHؙDy(7Lӯ;@hv"!rlP(:HdWW@ԾX_ E4( T)J|vyC[RI7Zo[yF5/:p+9Bۜ: Qh;~SEoAϥl`8Lj,"oƫ~c{edE$,E]^d椪׳w/=ڞyv=Oi`Y߯˼.ݸqΝׯ?;ZlZW~k6ܛ 6ͼ٬Ǜ͏'^}[oo K Rԧ>O~׾o|<->?~4ofyniN:s.5ZwdbΙ0]DIU%q[[(h[#7 TWRz 8i`m=oG  ;Ѱ( g!EHQAEa=D&Rf ́ iQ6JR%jeTޠ?(k)XzLgk ąO]Nl3Ω( n J٧wN 8z݂=]>a_mX(Fmg;:TWXGwWrR~~'_oi3wi/..>{Wu3ON͎??x΋>x Qozᓟ! /t]3mHB/RZ.4o~:OeNi*ye┒,dItU@yR5_'t-$!iy! U\a.I [)mb,lM}wnƩSEے@ji`joݺ'TlΧENjog=%H͍T7,E E7%&r_4MQlS1WjLi %"XV3/-hbt{#Y筵眈{~ϕ8a4-# e d@~3 jқ(?aђ ?Ȗ`bwkHCMΥ/UuϬ̌sξ,?{d gTWVfĉZSR4yS㟦H=TBU0CB ^DS͑Rs`M}/pag8y)\- "gا%`&SzFU6[ ΆR% {gyglՎNN l a?XT(LFɉG}"#[mqz*0;,ຍ <<K߽ Z6)66Tے6_Ykky{o24/N^"%=_aNIHkl'm}G~Rwg/\ʻlZ͏{n<>: { 6ՠ7ۆ_heZv,zBH%W  "@R6YJy#ƢQ¼@^r&TB6 Ux_d(nP5/%WD HD#0-%=* 3/"j٢F,"E#Q B%%1]QbdkW\>gK1F"Pʏ$!&Hc?ŜEjVɔ~,L'(pE]~0q@[r9AGH謋QbGf~n>{Ň''qhN|ߌ:kUӖS#G3b9VO=֦JsZkK~{>SY>k'LX9cƑy[7qpLg76B(%j ܿp6i.ɦQңRI/|/|;BG.\x?ΰ]O^߿qZ3sJ 0mZ,767ӢiZ'`k mAHdNTN@5([?P r30H]UجJA1[Ċm#M+q393-ءPf$lʂ|̈D1FAj/_;%Ggrwo]*UB{#11f5`h}ev91 ! 2SRu&DszepܡdPDzOIE%/} (ꦝr\"~rTz2$ R|0_HJ|2><7n޼ it;guR޻'[-Mgaၵ::>ē'^ dciFIm;)r8*28(A^GƲDW\c̦Ψ6o3 6iwXD<%yN881suQ @9fdTPK~VF$\z 1rz> <ÌK%U,1 6}i6X**/g!1O7 $]" שR})tS}EBe+~3F]cY gyW^ `MBWyGg "%j6q#壏=z9̓NDq.^r势K+޾ujFI,#M6gfjW+g5Zv:rkTHH}?,FmLgqرPpu]M'~#CֽuOVK;jݯN1M7i'Y+Wo )Xnd 66677O qG>9G&/E~.5 Q q, \ ){\τ+*ϙ' 58t IDATH3,IJ5~9d8 AžV(1e 㴍@ @q0|TBaKsVx5 1}1Zh. [ 80$,ɳ88papThisW_RPSxIؐJLZyr׵Jq"5q]{0[cN1`ŗR? f>V"j08F*e\vd'B}]kguC'cfz} rzDI8Zc1Zߢf2|#vnX.vtI&]3󃃇:_CvH\zU5W"]xmmjޘv2J@lnYK}jiRM۶MGxmAR6]$n2 C۶m 8>__֟?~cߏհXYdqO>}̬GF^>HնOA_z^jC8֞JM67.E"),sw77=~u?{$!ApMfv֐^QR!aӨVMל,Lb]ORk8Zc8qN;*<ͨio|4ZϗETӶie R@M')}Ҩ.7|?q5?^ͭ L7nʳ \6BcuJ Yi ?1H'!}ֵmc-=&< 8,o֘CTjk838"k1"v:#q శ̇@t5i&{dʏI󤒣g 4  ɭ:|i ǤsFb-tEgA)KQ=kbq"adXP*dpUоIHHl,I1 L",YZAt4c;%}jr1шPhV(6TqK7"nj!\6G5DKI VlQU͏]vڵkB IUlRJC_dL6g͘ej|-j(yaw Dˣã#'"qq335y=fW;wn(U33vFwn?M'Mzޘn-[  %UӪFY m?o~{.lO&r9dωT:"H$_ٜp'Gq23Gcbooo2z Jc@c (d )K )a!p*O|cMB >f\п-Eq>\> Ä*"J={F&1D[oG}9 j3,G8cu4mz$!%dtέ;tq{v< c6_B DGJi1S=WGZK9'Z{ QP`:r Ţ[vP9| Os{dXKL/9qb3J[p`phʮ™0q<"J]Sn\ɫ+G<%vWx  evR^ U\1|rHY{Jhѫ3 WSdjN "A S)}})X+@u돟TհO#?W,YM^QN"M\(&~~"5ls]܌ `)J %}' zML&(wtjkGJøX0@u?e_̼Wx{m_84tZG`~㸱L&MRSaH9?::<8;wn\.a[4Bcphгv?oAhqxT)Olޭ޻_H8֣T2= g]Znll:vfnefFE~!,cjeDjlB57VQ4U]_]'&,{yvoˮ:~@23:kҍɅw}/&HTTD9)3 xcyWbEeypYOuGa.@stR]Z~)gCN(J粏K)ǽ'Pak,V"=B &2$p|W,!T'N|uaSbUc.)*n)Ȼ*䁌u h-:W'a鵟$'v7xcM(S9gb{G k7` cskVkksI)ylQC߽{w1 j8q\˕5m_zz'9TfnhLl}O=&lFFf۵~}tp0?:ޚͤ,h{gàF51gmN [氿o}|oE B@$W''w|X,|FB@udZ,a1Q9)%Iq}ĪjG<mTZ2ˀDJr*uS j7aj²6Ir\ +_G"zCqeMu=LŰ3d˕B>/a(>a"RDHp/"7$1-c┃+9m(d)!!LO Caϼ7"C%EWt`$A~.Jm뽢?+Deg6VaA(uhd%vԚBaY c˥ yE -Q suMTz_Nǻ%"x귶H DB{i: t5Y-MaXsM2b ޝ1B*+yOG?9!˓MH%16C?J5Xkر5OOKm3_)qRa߀zDㄙ9dnr1Kp4 ^EsR6o8QpRARϗDpхE$KG<0ޣ>Oyp?^r^E ;?f䴈mukg S+ @Ie 5VkGkj#! 5]G$̨0|q7``= ?91o 's e.fc>Ttfv80:.^h !FQӴJ[lnnJ%)lR)$~"9]\B??ON7~,>J( ;o=E:әDݝgcCFZcXcE#5~4jOHXgw{ dr^(<;([Ũ!N=7aYĞb&&Nm 3j /"fQd0 (\. 0Z0Vv$,;,# aqBCzF[9;Ǟdu1xcB_wK[0`f@F^TK(Ur}W,{HVx,Ջ_cg,fy^YSKx*ťb=q]"^$>DȎ3<)JrVW :Ӯ׺,;Ep\8$t\fR0𫯾c]y/O1Hq eՓ$pƘqhq6VKdǤj;kr9T؟*|sz25JM֖RH~t9dvJ)(AB)m޻:>&s껅{~ܐ6Y=1ørѶ1yۄAWo^.Fnln\YKg{{Hb9ظ~: 1,ӭf21f\w{ /`e:L;2g-Xf;bEmU-bJjv#lcSM*(x%]4upwwώX9gͭ=Dx~% ;?ZFHBt/ߺs-WΞx_fk @@ZXKz[k*־*%8oHXI2*Hku`E9;(.PKǺvZ$`kPҡr {O ۝9(Cy-@c5" #ťxOS@ ` H!nl?c[爨)MQ8 Ria%a6ϣ.uTΆ* C{f1_U;*n1Y u_•| +@~qd` D% ĝ}bm5{c5"%w)䅟ȅ%֧89U/g1TBy3!U8N+*yx)?3cP"CK%;pO%C -9yi ޜY'$E|9g7QQ젒9[ .IM|ʭ`QKRǛek蝋Fj]YMcva#Ck 0Ѱs[5өR5m#|<) ߾acczBI!dZ{tjmn8fWSum7?>"|slQ ">sӗ>|W~E⍷d!tkϟ3F~r1''vֲsR*D"mKHZi1v:u6q#loD9 F':5=ij?KHb\,NtZq*敖!*ڞZ[!Ja* OU1eI s $*W_M(i S|\04+@-q"6leo^>@0OA8be X`eHj9t?ə1%J$4Y kw"z ĵuڵW_^p?{;satw4Z`{T!uleG+luzj\* !0Xc9YKR8{ڦkۄԶm #DCԴ-z=7 Auֻ@\,/19v\MdD!޺r]?~ WpM$ J*5Y.Xqܻ/wo^.Amc )B=zx)0;k02VHF$?ʄ | s_4i+M1\ Y:w`.]ә Wh){19OF\AA>~]Tʼ"5%lI%(!ff29=PTn+,w.mVd@·3'XZkBVgYhd7B)Kȷ.7N좪F0@A|Z(^>ȔACFJ sSXt),B~R|7|e;9Ylf8#{/pNK.;/Tf0,NNWf2u~W+g Y8Xdmp-SZx6 IԶT^R.x`@)Ev"%ff XvDlmI)%qZXڮK Yv?cB/^ںxw~۵0j[g8_՗[[04]{3|C!_ܨWDm0! xYmc FˆɤHG/>(=kY>.=a.©*M*LLJlm9_ g)6% Ua IDAT ӼL{Zk\YJ.W˗!|72gHptB"#8jM ^_* sNok{ônZ3z%p0~g괨ZZ$M$[B ȫ؛x@=ĹeL7!TD|*N%kh=c1<"߫ 1)as׮}JçD]{\W._~駼:*\o {;O3?'xBfaܹ׍k'>sA)ihmlc1RC #!*H N3'ꄵ0 O˸$m]Y՞j0_9Z@+AT>dA5!`)b(NdMQֆ9xJڐ;H$JNܸ xV%aQR8@?aЦUd! !q..2= yGRAn2V$VI8,4%~O m׋|zQB04rozCYRפ';M7)HȣbCRbjsO+Q_}7CG˗/kcRb+W^}קM'q\,錝0,ΘhYc1])% 5 ӥy21< @T*6Mxf%j|պZ!U\ )19R~la2NAa0ZqQ!8Fi|ѭ[K0ş1~Fxlɓ?&D"6jso_oѷnCF/ʯogZc[[ϝSJ.OBr\BH.9D(`B9Ys''dƉE \D5H*wfPV3 R%c@VPGWO#\}`"#|\m5,p} |YĎscEc"iaN[XBŜ,oO]nep#V,Z ^ TQ/pF;Yjhr*0`)W Osq3QBO^i^|㍯ͯ! OիW^}P!yG=ڣGB5h=fq;c0Q76MH@jgmJbx!eM3ݙvQt&TRv]$b޽Ɔ+ ӈE$MӸ =vj9R eӵ@QX)D:sё܉cQ]R>3?-h'3'|_}7m0 9Z h>w [VJK.]xѱh[-{!:gY)ZN)e#?qO`5&Kaa{*uX͚s m^O{m9Y䣗VҰTKfB>NK`\5MBcl H+,{vtI^#!oy a} |O>WLIPITeQ>}$ߢ< *'ی)L#^F|J_!Zq5>UFRUT@KJD"$3V #g֣AjmW2}?&~}21ֆїƘ']﫯~7O7߸y瞻*R"<ÈmD~ T.>kְu鬛NHc'1̬_*Sl7u]L[sFI)<}) %*笔J B ;&!Rj{czld쬏:tϯ_w(Y 9>8p.k=OthQHr \誠ì,kX~(ew`UgpM(bmGG^)r^r3HY 1LRtўS*)# =Ԇ+\F-gT4 %1 .k?OwYXѷKbQ,~]V 9gnI.QW0p9qQOO]XNlơƃƍu!u8ã#Mj3V~? 9Zk4m푔mY=q$!5:uJ5hPHMڮkF}xp *9}u;gZ^sDjd+Dl)߹1":]Ʀ><8Pׯ+W._ >\N80/V:=ɤLd)zv q-*<\U!TRqؿ`:MIt Gg\@'84 U?q@ĶN7l=25Do'^N#5_sowƨ . zWs._{H5rٟ,zGzM޺~S)8sRtL/v*iM>(pW,2M1*kRt|NaØ]MI9k^U֥$+|N-A!Q@O!R7ɥ(K򈯈2+CpAjFga?F1֠tNx^jd^26xmYz]\KS7|u̖0V$(\m]b*De_g@ v6οq|gnmYk|>#Ygÿ&n2RH!µQBxշX aGяs,`v0LN]PJ c@b&$?q ڱ9vN&SM|lYtmX|(.QR)fhgS='b~l}g|NHr_%4׿]Z.9kv\g{qR8m`~d8,K)RrΦ6a*E:!d߯BHzaVe7ia|SەX l}be!u;gsfrVSØC9fV=, %!J,S3 0ڛr e@q̹sk qT*]K]w ɆKZ I1 %v1y~ki}k6E0%'da\:vR*=Z먬uBJ8KBtq2B۵dmlsBR8 !1.T 19ƆH+;[k!R#[޻CDk1 !xw=1\xŋ;[}x7 ڎz9O7wC!R~{?a8::Ν?/Hhf(4Z6ʳtǞOko e`g'!;)" iRG\JH~YϝjTtVXS`qGC!@YP0εU?M?m@cS>er.YcRS\sqR2GuUTd[ TA*UWӷ|^rNXkWcTJ\u| o*J,㋠rL @ VTi'I^UZry-'1Λc|X1.zk^(ot}I!j \ :GHP\1Yo_f _{Y0),{P5U^ X>9%?}ꫯ.|Κъ@" :kƑϝb!08q-ɁRȍWp[o'a3{ yG7w;X"$+0$2x៳.,#"9g|AA`rꃷOpVs%(iV k( SBmE(Sg,Ϫ \.*qPΙbmo֜3fQI%'GCLW=YV$f x/}BR¥#[B. Zچ庞c(Jv,O2|0q1 )n!RFVFRroQ"B6) Ll_@CYL[R*A" KuN rZcv67@8 ŋ>Z!D۶Fksv?BIknjs-3 NaGma\AHmlIm lwo}x= "hgSgݸ\Ã48SoDãt5?!W_X.;6,O~?F,?i,csK\BYxb o Fc!uN ):`環bmRV342-'(gމSI( |^ƁE(qam>S,D0ʿ988Er_e18`Wc F\ReÕ~!Έ_N E! {GN+Y絛ZiS\ˁ/O.FG42{w׏NC<սõַ&GLAq䋩yuy|ŅK~}:( V+,.HP)h= V;6ww$>,>9k7w&ӍlcGTm10Z0F_cbk4"{1OJDŽњ58#L7S&Brr'AcGp[?ؘ2q*y1Ϗw55=}o>*|R>RD'ɧ૯~AaxzqA!6ϟM`FYHIVg]۩s 7MFwvONN󹐨T*=㠤RJc,)&H4.5㠇ӴfJ),[Z5,~|kZ"p# 6@č>x0tԎ#[+?o/X,)`/ǿj9:&1ZQG_zE ;ng>w"}\L^Z.= lJ50>>µR4# nE@*[dc3Yk_*ȸVK*&GEtdCrD+gpή ֕q-\,x]6egquQAnj"1(NIء@&Qϭno ʻ!t#114)+|#άRSeigf3~rٹәeU?()1(W h(օm98;;nS7ޕZM_4TipOJ__I|;d;o"9̨ٹlͦB)! ֫$ӧ1 "Vm Ķƞ?wd~LDιF)0ƌàBLrum(k ;q֚Ql{7lڭ M#ceh)[߽{;2WBÑުm.;z@]k}$sؓO^g}r1^{ o/J|ꗫٹ=%Wxg/_{k@\,aRe7 ð8w}.^4L;OKrO^y lS^ZUx Om%:l Sɗh[qM `{#o!o孺tEִ*='zo9Ut63hy J= aݒj ^&ZY凨cp[Z^n>D uG:ځF5oB>XaxG%bkPz[mIMڊ!<<vp;VQlnNYU*5(Ihu:Yc-QJ Q'BgD͔"IScRYy6dk)BIRI:LU^8VE Z4MaV~Ri:{`4jBDH{x iVodk'Y?k4kU8fk˲LxʓٳW7cwscoH'7?se_-. R*LwLHS7EKKq$d=޺r m㘄`qYR QC$\ 0( LR5.YbQS1w(!pfv2}8_ОNĤMjZ'aVlGC>kYiuA" N֮ FnRSW+ u`_7dDntn;(GoDkΠrX:|-_س%b.Ey!`hCٯJ"KJ9\'5eP29L{\+nAjBd @B{05[2hXvVvʰ|lP6>[C74Z BktVķ!I}GjF@MNa ˷WkF} "'@=[X@U kNsPV8 +f99hU0 #\tkyXZ$w4#tuXch ҝ1-v# ,lyG)yXݾBxzK+qnQj#|(9$Z+$U {s{J!P]I{}Id^we肨,KIDR p4Wr.\$It0DdE ů}csSJ)VJJH߼q/M٭FkS*ʻܽǫYq?~>ٷw_zO~r~i+Ws nlƣhq3+WD{9.ih{^^]Yx~6eYe< ᎖lzih Yk2غ;e' {gCڜܺl˺o< v0T%ﲪEgPGh  $!$n[IP0O`% V3f"TeAU iٲeְY\ZVO3Bp kmQRHTK77(bf("$i2楔hNYu%rrd:pSgDqL2*Y -‘bQJSNkko5LdJBo{ێ Oן_wf8!07"7_}x̶N`FDAd s2L7b88p+vEn'ȋ$YfHܒA!hc Hj8R-q'I*=B+1V!"?)W :n Z;(p[Xz'k^X3lD"zU=ͤ8nBٰBڎ\M@__ P ;^$)}tےKl/'jTVdx4ijtd9_8=fhn47?[y㪫mٗ^~j?srkZFQ;+T{< 3nܐ$:|,0>0_تrk(ˀlqu '|iEA[0ilm8+Satw3|!`bަzVtc510%i R1@C`4Q$VeYPs$ac^6ssv(rocLYJ(vw = JNƛ|׮22ZFo%)Gõ \ FRyxO/_澘eֿW^|O}ʭ-k70B:q|yu5D"!ZS܄xՕKx*M}:7t't?fk6.>3k\r#K)\ jsnFRBZC[^`O@ K큔wG80fTqC)ZO\ɼhT9E)H9"T3vMoiJ1 # uA' ~d}mx7xAB!6N}Z$&OX?zt_o/?XS,jJI޻!ZRH{ y@f=@ LڻźAs vZtt]$If֒nbT dha!IR)R,6*v q$I-[c@Yl"DIzrZcuE!ܳHDBD+׮]&!Ȳ,;P1 ("E[CZ'P+|=/}K/LB B̲G}^x/( qM\ڻ3KrgO>-|H)JZrŋmml4hnP(rX#b0ikwo޸[[[ss#kIHXT?:=BAk,ʽ㬵BD(BOPTY[%m6FˁǎVy֜`#ƪH겓ӎCY` ԀNq9,\̧p 1KաvN5Uc?Y=Y o,N,b =Ҫ\r Q,YԜpDmSKE!5F !-rzto.,- Pm7y-[yܜ۵Fm=پz-b.gy\H5Jq_~t2inCGZ HR8s|ig)"B6Iy^e$E$tMf6uh (R u;ܵt\lE} *kM6hہ!X S+vO"[MEwsӲhTA st'޾d ^<-ˎ0q$:lE.5m>_϶lVQ  ~E&޶ f^q\&/l.Ntt=c 6ݢdq>ˤ˥%֐.zjky_D($b!H$RָpUǠ5\YƱ*TYRk8gB,C楥q$Vi `Pbi)ւr=vyeeDEQ Ľ6lڍ=.C`k.r]-8/"% l6,L?W.|7#X'  [[h-29ufc`>.u1S|W_9y$I-s.eʇ=rꤱYfb3ٲӴW#G\@HeY`C!EJ$ Y>Gl[7\Hhm6k3"KR峺&G5t&mR :U"VNU@p,yb& ַX@r=ܘU3OB PK;Z 쭶 ^DapEv=v;xF|-(}ʸ]ɋL][_݆h4+4~J% 7R𝿼 ,VTx!%[Էxgm00a-A콈2](D$$(Kk%tyU BALTZ+Ts(.JU{칹fdtˇR2ur!bWVW^^%)F5V2 Q|̙ȳQ֎hebq߾C޻>R"*r)#_?FDl{|6no=~b wm^y`A y_{^;~xtr¥Ts[/bYD0Xl<[ϊraqm"1$z/~II$X H E͛s H5H-[]e_Z݅Ndo2Eafڍ͵l<G?kp0XYY-UO;Ϝ\>ITDeIfvW##˜gYϬŅl6u$ DͲt~a#̄Ė cL NEI.YFJ4p 5ؔZ>J$;'5nRYkA\o{V/cC[?ZLUm(&j?p资]2<^~%bݩOwǢEȱG_rȥ4N'G3Z`IItɫ/eeBH$Aŋ#mnl\@ѓ܃EEQRe)*јat{;2Z 1ص4.2f3"R(D2ڰ X+)6̬K@HYжF5,rUP(_=NC+:)sch-`6h'f-򵺅<){v葁З7&ORxҼ1O!gFUe=ؗnPāi dLd$ Wî/_5-J}'oz5>0NM- q0Ӹxnc9 ܞT[U:I yu;nQ~#9ƒoyg?9BFkh MgGDQkmXJ IBbUp]| ) $Y#DJFZq$7<*YVf IDAT0ޠ֬\X__QKl=h,.oeyj%sXRe{gz7^BgFY#?-}sͽY8Tc܁pƥKF}QO2 1zߘLˢlș;q=B IeYEY,g*K40p4Yv%"*$A"Ufi6G&8i Lj,!#wj p9;u|Gn7W'&բ.NQ=:T%@SPIZ5ޡYꪑ5,!Qp;rvc2b3OujFS3q]ٴ~s1а7\߱l`gbk 6~=+ym*^H`MDN\mjKġ W~OZ}q'.ߒolQOf}ac!"2zͱLkӵ55GVB֗WW/^<tбヒDVoаHJ$EEY>R! cUQeY>qćyG=O3*Y(HDBFQl9bD$؊,pjr e^Zi4 l"^>6DIӫ{J[IHՕt(p)|u':Z^^޳7\GY=w?yk_= HECeatCBEY},JFGΝWynriG7ceMǗ/^~l:NnQְV`c,VF*"٬s RJD4Zyd x=}@}J+džBch4rM57)_KX+p0i=0"G9BКU="ʞRWPUk+KC4K >twS})y$ {U ^b -v2cj ACV]ǼeK.,^{y(ߟK=CN47vOs˸u=9"w~w\:w+c, ,dJ5։ԡG2Ug}0H!-npK'&=-ٮy/L Na'mv,<3fO-xzN-L^ڌ;`HTɽ }w* uφ}'nbs^^ O{tM:""{6)i& 4]hTTk62Pr AxGدQA$^f߱wՋW]nT>*ޞZ>?S?V<}_??> Yw3ig-gE8'!"DZKn.JY1FWm^lQ Jt2,|{EW.]Z% !!6,^tWD z}%"*u߻޵p z*~ ҔM8,/>W޹qhUd;hn~n|k4fD,U&ҕՕW_>~󆱦Y=BzQ;>"$LӔ:}6EMeN"YssR` ds_ۭ^BF,݄]2RvrU傔*@lXnZThۚʩ2 U0MyN^O-7Z1"wTUȆKLu>00[ю'H:|oA7];Wk|#U/ [Ji;J*NzK峎S_zby뇎t6R !( [WMZk *˭ͭ7)J9c'Q , WsyVyQ+H^Y{#p8OxY6Xd{xW>r}Jm1,_\Y2gkk0 sYJ]wP2&kU1ˬՂN,e \tkΦf {PKTKQi-[@x]E(RLH.0NO{oo4'H)0kQz>yI!RQ3]aTe$q eLXrE L@-GJstnt{F[U+eʄTHqKvUxfCiv O㭽7ԬZS0:Q#Ao#;~ T,ꈋMZt*ǩ7nQnB־@u"Uel(\+ We}j/Qݐör_li>#{a͖#e *[xz71v/T K=_?BC'Jxg뻾 hy3{cmM+o߾ޠDF(52bQtfrH<_qC(#uM[[ECSO`47!rژ>Hў(Mp8LT)5 ^އ|3XkDRJl~wYnƪl&]Dlmjag.Gp=rU7|\޻7F X6&2肭UN&ETD"' )бQ4j<ϦzfzR^lӴd`T$Rʼ 2*K!*(",LTeEarXh*Pe~/#wG^UʧkK;Xs|yYȳl8=$eyߗB(WZlacTTY,MS$1ڴ(R,˲4ZY]]qNMD@2,9lon\QYHADzg~WqeY5G;_̺it|:ڕ8~رbZB/x"ϛwHI)=6Z#[3fie;"N(iqhB{}!DYq͘f٥^X_xy^'h4"(6FmJIlY)թ՝Y(NRuZ!!EƖ,3yk:vAmx/tFc+ Cz2@nSOp72Oڱh =y1dYpAWw՛xBWmnRMx6_X=<IJ+CA_~.>:E/.6k~#늃BVѴQ5!^GF-~'oFߟ]?}[-GG|#G>I*QM'$DsRDf,1eOS&iz03eu/byl@s*$Aɠ e^񮥥ss{_V{yo^s4n`Q{A^{y[Uor\i|sҳ7oi`e6͟r^ye2[02R f(d6Jp($F8Nv}⮻aǽZ,ˍ. /X+3` ֘H4M,tz8!R$Z7Bw"PT^Z""!Lo)bp?W00`w Qd@&]c&`իlۙ=x^-׃[̝8z7+hY)l/=sMnE f9T{* R+y=7A?Cw1:Fѧ״ᰇUIw*8ZݼpZp*B.VsIA￶%3Yn8m G{JnQy޻|?|  vٓy6 $1Xd?(?"V1LF$I,xkw:ރ٥1zCƆMG[͞3+B ԏІoe߉_9y[]97Cu@\G<'/UGj7V7G"7p?s1wPv'ēO]?c8%Ï4z5^\\7٘71ISGty.f{s y^x`xgنY1Fs{*yL4T1_ݸıf47?8ww=\0[h2F+UKGdϜYX_}˗Qc_cc]h1깢ZH!HTkR7We<9\vmz }Epx YNۢ<-u"^|7SQu'k{LմMM 0`XTMPe+A[3`Fӕq71oSh-d- YVPeh;U&[*AW'W\ԱX,AwĎ HdnbppeqZ:9Zzc@)[I2a'{1x7D?{g)=&SA0Hykp%Is^Mkis#`WVVl4vE$FxЁ(~"dD5Y6MHTw$IRʘ$Ҟ]x /DI:7?! IDATL>J)Y?X:rtڵiy>ll ]`QQ{^x҅ ~8=~|nqQ%JziFQ,mom8xh=N7nޔfMei7 8bŅgȤz^j-# NM7\q8V_h;fMsnv ;73ƺPю4XeG8K$2E.edDڲȭ5;mof탨kVZ _&wW;aǕ)7v+R]kM7H/DmғmV\8\~i4{i]K؅܀c5 :׊1}X(Q]V+r5wñ=-oɫkk=J9BkigKxUcξ=t2MX1Qm\_*t^Eb9xЅݻTʲ{IDq<ask E1::i~aq2~ߏw_y?mJz?dzkssQ+H# `VY6w`iirfd|re0H"@d,{/^ze:0ϽNQp&!xFkpMh' jm{̙`@‰hmlY+8I4կAÿYEIfu`Zk!IR$̳<12W M[&r:u)qˋL;5H` ׯx|0(7ĶZ<nZTdׁ?L@K ˭ǗT]s &z9HRG}`] " n=tfPi<ض}@ ة0;w_%A!y{jNlfAlp-ӎGEe K A~fRbεj-dL&qql-kcxy\"N!D"`gRkQ$(,$?={^vh5}?~~8{;t,E$ɝ7+|d:ykz<Ϛ[yI$D1ma"`MB^;{ cZ)TYƕ' Bq28?'p1Lvt1i21,,Eyj ~"7IDqLuXetg/I]&]%m #Bmr5[cf)#hBb7 a}kE6oeˈ0Bshپ aF!QGHGݩ*! m^/HJr3IvOYs2D$D$$HJB6˅ #Ygy80_#<ҟ/fD'wΗӟ80h86!?%uw2RzX*HJڎIw1@2Wp #aG$% %diF$HbGޒݎ26JBu-es9hggw2.7ne!Ta$ȹ@D{ g_`\B1<"8cvw3>?WVXc9dYy׾?á6 ![?|9{ kʣr:ef1y9{|x%lL6gO9R) I)U#3\UYrֆ=Mp/^˿YH\Μ;?L>߹R5ks!lCswX(֒x/wqO>Ed b[Z:ʧO|4LGDmDlTV{M(qm&>6h'鈄ɱ L1/k۱˚ҙ.kɊ)4=I~FY$6[JcEDgHo,K[T,cLhA@єng:R3QE]tFWL'QK&gI.q81A/^퓒B}"oo0v3gώG|^UyBjkf'8o6x@Ș6F20&8y-<ŢX4SgN_{W~W'_fȜh4ſ;8xO@dSJumB7ODޑ[?:<b1xcs+٬+<|\7ϝkDxp;d ;F׌X',R)c\0[g NUz !aSx!n eFt -n--? y3ӥN]"'9#շ7ilO),}u)h~%>rɏ tpS]?Gx|8~%## \: v6Or 2^H$Jc&=kQksѣBzW9{>\| aO)_D^)Y^Ѽ(6OH%BSgܽ}ɗ^?/r"S ~ڵo^? ϝ`,ʀ#sW{_B<_-ka޻BoݸEa]p wY'8c1΍1Y]U#B"BTq||<`4R su]Yc hzx|ƇXk-c8lƓlѡ1Fp9 gq`&={v o#Qf\eIT& ǎ,SDh-0H9 )W&;ڽd`lU:=.SpǚZbBW!Zʶ$sk:+goa"hF/}k!E.+;w O=Dg/^Dkl. Xv ܽ}?:E<^|-*"%߻sGk]Up4G*v1 !@ s9CN@QU( qIZc,??{V)x h>dyO&ڪ,7cG 睔;BXh3[k-M:~.)@='sl},5]E.)ˎh}l()!PM[Fz2L)?MkҥgΧ-3pX}M0$$Yu'krʸ OG"8DJC̉Ed-3^a-1֘Q)BzZt8Է,z|;]@4|ч^Yv>Iᰩ2.5qhR)ktxV\] Ru/|_wͿɜV5y_g7~W,R <`88uTe_yﵖR"Q5y〡;D_]l>{{UY퍍ɋ_< .dB'Pu|zL׵ͧG!A'/}KM5phz||,p<ؽW|1d YEYy烡r9yr3[,7_9s÷oocƸ'̋/ܿs.lD A@ȘBkcC@u΅%ZK! o M9gq1g}' h3PpFt#YW{M|jI56K^$Žn"uA …Uk,N{LAm)y2m\ &_}<% t5愭1-(f#-yᕇBflT`۞R\\\R#B۝=j'y)1j)KrZԯBrkI\?مDw:ֶqV"LB[%8k2 e!cBJ`q!8"RY&283??ş\ʹsg{?彇j9} :jTX8K#8f)9k1X>ȵ6~1&!O]jmC h8?y:-S1&b/(;wn޼1ǍW^}I (pWe=%[c]k \Ju{n1β,˜Θã^,1 yL9 ,3kdsS)s:KPm{~rz>6&hx…ӏ]}/=/3du񟟿dqs> 6w_xa6L`e !DhibƒD =cZQClދTQ~yvGHJē9;ƍ$s+H-G jWn()oUU2ON&FЇ{΃UYK_"5% #8C{J1Zl<-:+u/_AཷygsV==}q1{GAMMAxƐq&q9|:e@g'O>{M!'iզb6/Oommo' s1Xk}έ,g] sa&hW(\uYX{ؘ0/\K/m88[k 6mԋbS3<ql]*8*WK)1"f|#s>e=b8ژrϞpaB7ߏIպ0!EZKyc&76^Jħ草 Ф>D@O )uA|!G"D;j>dP`zW]~ݻ7TRe\o-06eOd!1Ti ?}[kV^@vZOg .Y}5B{ͺ.:ϾY1QpNa0P y0뭵b1Nuu> .]̋/lln[02x6r>wfQ 뭫T>3*d2O&ϹPBxO୷J[gRBpTи9O59'O\ @np2!/]ϦGABp"؜L*]Z9"308,3"Rk]ye:)D!ZH h_7/#ʊQ(QQQJ+וZ҇HkTtyYeů;Ih x{OBz\rX&)pU:,+v-. 3K`(D0%O%^+otJ]. S\{\Mc\ܙvqt 7䮊azcbFjQݹt믿O;o[Z{uY2Ƽ ODycw1F2ƄRs` uZTu(9BḦ́d(=' 3dݽcƶ9D!`ч޾uvB=wvccCIEшH3. yub6;>80Z{OB0Fl8_x^zicI@{MYVZk6Z;ݾ(Kș G 4L`ZJ%E1.`h0KΝsl:ͧsk m#'9s@|=wJn[B.kR cPkXuZ J:k-yRJ ѶHp.b\`{ %*aw}Umi|6D?oL8(5oxvm>c"B%5H"b@̯|kv`5hl"r1v6 UE"?Oetb|kUJ=dxS)FB֣W{.duLnFiovӑPlu< sQbFkΘreY!2oå0#B Xc1&|d/f:=}~Um\376TJ\ʺFTղ9B<dYk/<9%r:jCmMHeYQpNZU'OJ!nq.hSkTJ'j{ޑNmtEDag79_k΅Ȕǜ5Qcd EQxqp8 "x$ k 99KuK$);#+a/zQR( ^(0q@%m[LQX'L+"dNNZ%&R7)7K[=l#zj52p$O/"CA`Kg`V R>4D"?+#Eʕ+w>rȥ`\J.x`yq"#ctUwHT ]0s0ι|뺽u]w%gӣw~C.FY瀰E@eU5 k9֚lQfRC)^&[[qd蜷XkB XS-ʽ۷޼]U,K8Lg+P%l͚o{[k59Ih,)%gRJgιF1`Y3ι8T5D'XYq$㑔a Vy j?>-K`@ђ#U;IJ~VZ{j^Jӡ*/]QMa6[G$glŶqSb (n<=%X;3zZkXf"*E]2IzN{!/h2,~|u?!-!B0ȳ\Jic3. r147բ䥒\J8y|t@mk#ٽ[Zבi7t:%& -w~k1;k0h9cI>0p$35X[.㩵&4J^|M6Im1:sUQ޿2dy6s>=rwNdy.yֹpΆ\)6|pޚԃ-B*2TgRwh4<$1&89y* DASP Ϥ14"\xgRum1LlVVR*&gٕGpM`G5T+Ϫf X;))JEVk@qM0" SE#vmbʻXXxVFeWDAӀ4ׄmM="t:I_b|L%իI>G_M\7xWE`oR)=B=P(YDhppG/_BƼ֦G&;wE97Z^ ͏<[UYký;&a(Z[UeUe'M<+f(,sc ~s{'zo"3FVg{w̦SPxssc4r~>|3Ɯs 7̓go @ "wys)a_0x"wBJxHpcHޝ=wwD",UUy3D|k)s{-d CJȈ|&+.4ꭵPYi~LhL?+bAEOiݭ? 6Ӣ.Ieԣ:?#D90")GtKSF*ļ# 'ڤӣ؏R`1]nq#B4wffa?b'V\r(BHisvW8Wj+zU;15RJt2fQz]D xc"Y蝳Z *ˀ<9+8ꪮj^e5:j!x1_9{g"p8 y/;'O[WUw+D")%|ks녗^R)]WU {unzpݪT֓.gnnmYvzt4Ǜ)Uz@Z>PJ7FsC g\J!8"'ϹG7?!|c2 m.>Ì!y/JI1)E@ Yk 1&' c5:[eY\"!8&0Vh) Q{wVrڭ&OfkxLt#PЏv ;W_Uvys~d]G*ƳƺBk^j|5:m}%}cgiߝ X6k߄h#GT =Z&#&ȣU=2)ԣrz"D6TB w<cqTr0xw@$uGYǹ@nO1xW9|s/bTQ󲮲LmnI!onnq):.x2 9q׿g %&:kVk]UUfw+m|]kg3gT{睷|1oln1"+Rl9ɸByD sDbsDA굷{ќp9D;;'ˢzέV,hBmSUTŢ6Z;$yN4QZu:0;/KRDJHz=lcaԡ\QJ1aF{αBEs39b"th hG?XsXM9~ 3*b$U_W|m_!XFzզSǭHRSW^~G}DKI.xXbP;2&3)ex2!Owu[~#( ugϝBJ\;:<Y[3]J|UŖ&z J]t7;@\Nކ4몪wJN93Ž*J3.8!'Wx<31΄"]"⺵gsK*^BԺf1%,ϲέx aBJ)'c2Xk¶XKa{6aVRtʩX mE]wi9*-1zb`->`௮;h+l?5躇Hݼ.$!5"zg!>0RDo0RJvd]mJWq ni}Y{웋$oy+=xRGw~W9RUl`ZΑ!ι\ ᬵ:r+:罵1z&]\e5 lh3D54 /"gAGIYh +6+W Fc 2g_//kG19muYӃ12UY O=SWuU ūWju]aQT @()D& #88Oȸ2gvy"!0Щ
Tw䚉J,̙3 DΡκ'O: |gg!ǭmaDצ䩉O/|] Ɏ*@kkiNFy`{W4 =?I$ބlr %9KRΣWߺ몤=6'=^$`LfQ#ϧrⷲ%jƕД 6}^[,** suY6Bp92K꺒J 58oFS3}X,![ 0@i]C="K=cs()g1c1 $A=wH*E}_}gU>h9[׵*ã"o'ϜVkr!evih4B&p>4o<.9ֆϑ  XXSW: % 5gR1{'8}w1w>H9TYp.PL0"ڽsw6 18gA7C;X ^ ZYÞ{ ;u_pkveќ-kk?LaCU:JI Lb﵍h٣+T:}ŷp'cC$Jzf8~okPݽe.>r#L Uq꿳}[zfu?Yu?Oֈx+_s2ru] !sdBO {@X"uA"K54[Bg <;u1hRH yOFWϞ=s#]Kd1d5u]UU{eQ"c mno#BOp}r{g: !gJf-,T>X1: %rs!o'q26րc?><dݹgB1$Oݽ UYc- {(TVsAxasK4<cvOG_{m>&P Ehb`c%Y+?$x֠'b?uEҸթ6Yj+'VB>Dc}A/]'R ^'VkC=LDV| Lf}أt*p\z{":8cR,Z"b !QWѕi5پ'}NYqݴ91_Adf Ѹ,Z pέuH)왳gN7N;O O:ZWUQݽ9[ʼnS'Op)E}rG)V P*[jq, 2NK$K z @,6EcȜs|޽vi]0㍍[7n #P] ԓ)Cuڶ+gOm`,dm*~yxtXQe ~\ԥ.)$:o %iڟk$A`6zp-+x`w$ A" IDAT7X;ks[;'Z[|6=qh2iqֈ T&'BhK9\";Bo+TARp9c{$t&gΟg{owwƤ,۷>"Od;iekÖ9眷 z#a2Vw}l_d`N$DSӣMJ$uDv(x 2ͯc;&ҏaǺ5"T !J˧ >_\1lKxc0&'l5}:2ƾ<6O?`Iu]/拻nWE^]@83 !\pW2Z8/]j7Ҫn|".m "$1ag[ۗ._ RReEQh8>.ft:/XGŢ?8f޻ɦ{[gX>-UȄb#rH鈜qDZ@s.CB !8<]sŜu֑)uR}|7/_?yc>!hD6ZFm9-i$f;PtN2Z!;|V{Ք~L( m>G߅F̚Z%r\]Hj^̨oMSAjKjSlOi#7kV"M?D41yq}zWv!1Gsͩ׽ o1їDr2aGSEk_wJ%a̖82^k!#`sqg-Gc@Gn% ȃ]}'yqtPHjtd !q<UGRJ YsNH ޻d19UU`zx*p0> |Zps$,݄DPK% 1Dg9 s;B kBR x %́-\)b8ή=teK;nG1]|3>IL¹ހ>Fɬe6Gq5:z ?1 ' MKkA?+%L'Iah%1uvY6dkQozQS1 —ԕ/S8BD4Jl[Ŷy2ƿ;?HRMls_ Gε*@FU)7?xDSO]W^BiQ4bA:cRJ8CUj6=JI9 /_v( M=4WRJ)@5u!gC9p~x{/Ra)mbj6͟h<=.gw={`h6 -{5*Lvv3PI5YeJ gD BI)GNZ\g=qes8Ta:+ pi ~fؗD{52ujrxc>Ov{Z)N&zڷ>$5'"mS/qԇFűLX3O+ SZkG1Pe]h\g )7,Gx*S  AHecMq!8[k'eYZ)ID,g!1Ẋaك{Y,ϲ<5WO~0L,7;bSԶS0BSN-CD}qԪ7p`}BH=>b5gP"esl.1MYsۿ۷oE11O[mhcss4qs&3O9C_ dz8 Y炘jD!\{r:u+slW?{z>?ԓ:9׾?aO&^x[{{6,GB\,df&OKC2 YW[ᙯ?Tw-ǡ50 buk=i%Ĉ p !)QdVӿY &nDn$&+ =ZJyZS~n4Hd'Q;ʏ}dΟ9ssT!y;2~g'ʪ>uu\,Á'?gi]Vlo=zO& uY'+,BzOowA`@~Qa+km.޾3aXܹuǡeJ 2@l!uKIJ.3ƚrl=JFF d5P2җg,.+5($ Da^#c5>Z`V\HVܓ2wǚ= ~>k4Dr1&Sjɲ*u(2@伙j{{Ν=spxXp4Bx睳J,_ؓO|S['v @DS׳LHTBB&lz w^0k=ϞxɳjȐc2Vݿ>*0=>>>:5Z Һwct"0ΤJsuUWu <Rțn ( 1!-W*7~~7ORs=s 523~ʕ+p|ݻa:v:E֍ϟ(MS)%h(P:PIL'D0-J!eR HXkCzEe|;wng{wgw_CIذSlZ9ٳgp{+ۻƚu"ʍRJ!޹ǎ3AGDBI!TJ!D"ܷq*HI; A= zn6z8γƍowJ<9I,f:?ѩζXE12 Ǐy.PDq JDEqE! Z?Jj=jRHsٱG@)ν{r}9C>q6NH̳'"fIo-gozι2BLTه(r(4K*RߨS{d gadȕz!3IZ='6/0 UhkC 06b`lXAjFbA1&U(BTJ,`d?Iq2ƪ8t8FqiMBR RJ! = (ֺ`"dyR )A[<˂;׎.?A$oq;y'h<8q+33:uu]lR :> TBeJH!uDN~cE!# cOO|?ZƇd0HG#9Jg]~!s炥??җ8>7@ª[Okҕ]+/iTV3]T22i+MS"JjҶ*7\YIPP֚ݺ'Y#52k*ĖJ>r}\dExҥCXZBT/Y5[^淜waCGw'iK0;r!nC]Mq ΤM9R̋jKռuS>Umy[l7GÜqk}fr@o>(7ay `OQTvZA(ʝ4&wlnn^z>k駟&NCI:NX4Vq0RyP 4SZi2QLBYf\yq֥㑔*dgN?G(Z{ۻ*҃`)[wH#GUTRc3!zZk.x"yƃL>q8~Dt9(۝ND)HJX*RB({{}'^q#{$r޽WqtǩOܓG>@^c57 J 1萩UؾR*¦9>S7b{Y~wSuhƼʴN3HŽĵnrߚZۮ~.vV 8ati Q~y晗\Aģǎ=:#JEiߵ$MB,HxR)Ãs&<7, ")ef aɓ4A9}ģ}ُґwh0Zs3vv`o_Qev;X?bS}+D%+tN;vH<23-ϼ-T*RZiD&"B|o3.<9ªK_r"c;w_};q(羈meMbᡘjpQἂQ K<\rC݇}b7O]kR]"N>j6wl_hYӃZDA]u9+Zyxqҥ%aZB{>~gΝ;{89zX8Iڝ RH$p*3!16Jk) y`g5[(!`&>3h4ƣш8rduh4cggoRX;(XX'{̲aEA\[_'IRHG6diIZ+")"Pzg^[x}ﭬFQDDιK'?ͯ_`gٗ>cXD8UF UkUnJ$ya5\"bMvq8XVM[Ou~nJMkaf5Gn)hBUz\lpp~G/^K[&~VVVpƑ #G |2M3\BL {A;)if$cg{﬷?;%#:GAn|nͳx4 {޹޻vzpBĭvFH1F ["fYY#}p0p7,Γy}cceu{OH@&XG'+HI;{1L⩣_x@IOm8WߐB|o~~.c7W_O#)bCh&=2*P!h]$#.m^U:>arY `j1 S) iQfWe-xRë2sc~Q VkK\sk£&1CQjUW|T%":vdAIFZWBH3٣YŭT Cl8Zkkp$4Y.J{9ͭ51?JvڝxYi&QYkl=qW7zK )(1˲p`F؝]fHq`0Gщ[qmB|i((2Ut!7?}oG[W{f :kGRYM98R Aׯs i< (`Q>,!"SgΜ'^omme4N7.mP i@R<TJjjڐGe){w=t<GR*us.IV+>w}ǎl ÷x;($H$)&43V HJ5m߽{Xf2n￿oM&իW ӖL]Qq@I0<3"Y ߿} c^]xǺ> C$wvxp`խ=o]{Wy`s:5|9 %f-* ɧryʃrIĪ^;^sYvhl:թsQA   0TzƼO8zq^[jZVuJoϓO>>>,!v._֤VWWzZbt<^[[B8yF IDAT!E؊u !'^B xv֌FcBfN;QgIzԩGLh`o8$4IS)JE<#L4!D(YJ"hZEQk^fO쭬Ss";wiȺY潟 )B13xDso;+Js_x9@ޮH<'峼ayiU(NLXՆ#,bN}Oǰr"δhSgK9" EߠFJ+<PE>ܛXiozKu|{C @\ m]rE*uԩ=rZYYa84 GH%B&cޱsP8ggϜy ;$A$YWqz7wt, ;!p$"TZck͍3d;_,wnl%ásn4 ɽZk, LNne fooV@}pum-\,+v텍gk޻AA,9*ʕ<\FfjhV.U+_sܢPX +O bC, $.l5ұa|3L]RqU(ň4"b\ sEdWQ3jbp6&0Џ\sҥ=10{R #f^]Y%,M$QJ(ʳ9JfRL>OӟGN={qYy۸8Zn׿!BRi<3 ll.BB(VI1ƝvzgܵGcu.{/if >rZオ ` n0 V#QݚUXCgw^[_7&$iybǧp΅aSCotͿ(7m@hMk$D?7mr.vnjPM>ylY AHr8;A<Gp8X(l )Lg1ev8Vĉ~p ܷn޼嬝5%-N;//쬝.)YSi@20ssZP볟t SZ)1ʃJɷ Gٷ6 ajj'nhAû2+* by\a S?-hXG5eos|yo[~?[#PIABҺ{`35KfjQvF*ϊ~tj_*~W겿Y3we׫w.aiɢy+W:Ιsg?1fk9뼵vǁxi2?sARRVJGJ Bd;7o[I E$5aJK%mn<7zk[v L.GD眵;VJjVi(ײdsEk)7HuBĕՕ>HcA7nޚ!?vheu}۷_D]hLV X翮ݙ\Y|aV,{Uy+c u."l^l*A"SlģfUl6.BV [* K O2O*+%>$LK{)B:zCxޯCk >rj=CΜZ[kpR*%Һ$H"_7e*dP)9EDB@g0H(g6(NH0s[}`0 aT+jniG*"BY`䭵YgYfo߹}};]w>h8nݼy=f'Yfn;O8zܼy´ۥD}>u,N~@Ƥ^\+Pzh(LY:"`bࡢ |ءݾyD52pVB-hϴkO\2*bq0-! ˼:ҧO 1Ij;6"B8Bmng.lo߹=ZkRJG:lѾWnޔJ*BT ${$$B"lbɒVxkmںκnp!cNGʉ8&Sq"Zcwv>,1u5n<&{w0`aXc66Wi1YoTƒgN`sK|E4JrMlJ A yo`$ZϜ%J!hἵn]f9!ynz^jRI▎4"20b)[kgBJ !dF8MBQӓR1yt4B@ƩCD% BJt`x8R!>${4{} ]żʃMV}}p7j7:\B6Um|n^X~7RO(zdpEљvp96L}vsLoR.ΥKzꩽN;v]`;ϒ… Ooَ !k@Θ^}R)$;GD&̤ {wy4˜;+}EaM$M)[#WVBJ݊VJIA(B"f$!{F9$[Hu k,d4"" J)WVWxowX{5И\I!ai<`rYBNTʽ!>aUZۄE3)p$En HUԚ648)"_ Zƿw-7O 0Zl;z.5#C+ O{`޶NHJъ;UZj=4e5!J)T. I{ +޻h<:(ܾsGHys+R~mGQ,$M7(d;ks{VZ#1;/$Bos.'рh4p<I +<ݻWj {rJF9ˉ)!5ȡx HUEd7hXK.T4lBS7iLՔ9%и`\p?%8-!}D\__?}Ʊ57]O?$IEQi!T:cBRR!wz3kH"s8D$A'v|ӥUos.MF8mzB$4Y$IK%ZNDA7N9g:R! 8]kLnrfi:VJ]gIh8JE:<;1"wwvvgwVJ9͌B'7UR))"9[fʱ{,ҪS鐖bnfk`D{UT̑-ņ.7qE#b*lUlҜ<^"gz\O+Nb)@Ldֆ2&kb2jZG ~<,6z8.Mey,魬tz}\(uR`1쯮mTR)hA:,7xCHADI{sPMݱI KzՒ ņT1^% 7 "]̴cJ([t,F(>|srΝpٿ-N+\soo_tܹhɢ]}_WVzO}:Dݺ~M(yn#U;w|k_s %0R:| kcD}'~c( ־oSBg-RsdcI#t+Bw.4Kz+N9gFYܽ猕RI= .J ) $ѽ_}PDk'!P(IvI>s >~7 UV ,b$;aaPK m X;CTVSҡV6 ?fM-pŚF}IagڭVjol<?qĉ^h<>q 9]噊#%L HH9$1 IB(j"D4x0v$I:gln;wfI}N$! N=}ϫ(J ykd`ptu֍FC={ؼb|I28rT}ur TJK)w D@V %׳K_R M*fhSIY\h\ =bUx%Y,Y|᳟][]KW\y7?#ǎ3&J8xex4@Nl4<Ð&hٹ8[6дn y2I!RJ0{{{Y&i: XIDAT<ϜV::uL!1y$$D"v. alVRdPz "Gl4&Ѻz\ c]cu977j ²_m9p/H#EJW͋KdZ%DPﭯ /\|ӟ4"z6΍4Iјv;V;Z[`vvxvww;;&I=Xi%H"sw[笽i$HSۘ`l+N'ĸjl4 @GRJ!RkM$BO&ش{7|7Z9L牄"WKY(؍.ŚeÜPp2v9\΍qs`fnhx  A]դ7X*85%vQ_^BLJ^ڠ*GJ  ͋KpZ%D'ȿ_)~=lFm)e+۝VHqd_Yq94ˬ@k&ˣ8q0;wsf$H#yQJHEJiA ;kw3&7Bl.٢g>ux.IFx7aO;j;*H$t;Dcw` yw_󜈒$a')Pr4,h[-r/_fO!^GFa yJ|PHq!x4}v) rֵmAv8Z ҂{{cvj'yss#=q|ȱv0O{wC޹#ǎVV8yBw{sxسͭkWZk֘92I~*^{Pt<>7H^ayfxQ r|qSQW%fLAfg`/xY3zmL˳vo'_|≿ٍ<203BHo>'$$@<~vem ǭXZQ+nEQ$B{dHJJ$ -?\i2MGs.O M]RH鼷Y[HkEZ+uX '9ֳYtY:qyݾy&7ipa߂nWEUJZ f}HRD  bݽ4K[^N(/*ռ ]96!`jZ6-n=?|O}jn Ǒ0\e9sO8AR*'^"x HB {RgT*UMt488?QDZ{%: B% ZHgfk$IκiP錱dōo޸ᙓqټ %?=kwC9(-^VZ/LWKhęۆ*TQes]`eI;w,wA˳yqL˳ݛ[>9ྺv(Rsn4UΜ=D'NaRBJJH 67֙<3q+Wy ;mBbVi_]UZ+C QiI$i"s{RI!8{aF`u}VIx'M>fMf2XD-`Ε./ԫ~cl YTiCB!J 5BVbMrPڳ"H1cnVp͋O^z XևќG_= >`(tJ3_vsg9!IJ-rcR)b`f7VC"oM`x0عw/=7.Ҫ_R|BA$$ R{ `M{޳~ƖZ;&Ͱ%H(4OgT<Et4j78ù0F+@Ei$ GU0.5|3B  W eU -y-W$H?M˳dQ?c_(ҁ@; Μ= Og!2\8;g;nRx<ܹ; hKEQ]YB(B Ifb[YKHyd6Ybwoٺq=ϲ4M'9#wN1̓+*B.FCyǍ kV_[ Ah@ؤ)ΐ ‚x\,"הt rtH3r_″i U}pSxɥby,jyXUXcpz'_zYiyn\vYXذJ!׏lt=;x;҈ >qQ Fk]CE䙵;Ϟ=;ong=Y1Õ"UNOk[ :`fdʽis z|E=5۱ qňownOJg@gbBQt&{֚X[Hdl}^ v 6Y.~ ux[h;dUW(.fۍDL&VK(WIcpfP7\wXU ¥E!JxgvIg[F/ZIRJ"t:]@|ǾSG~S_~!BqRg;giiFq" 1JNDHfD$R Ck"سswqG`5y_pe3$h=&H54R+Ѭk?ũZ(Ry4yVOrMlnq^#O5T#,6[[c '|rsb0[~g QS=ԧӟtI! !6l"}g?{gX`!̱&{IJ* t*u:^tQ,HXk1J)"1h'>шU/] MCRJG+V*;\הP FB+K0ݏBb |h,8rۤު^j]Q+' z[ެvd7pEݽㅅN(|zk<Wu#*Iw%2cL+migM<eƈ޽8N;9{(=3UJ ӑ֑}|,bM{K(m/zIh.t vW <!8;ӈҫKJUE~uiv׾!@Dዲ[oy~)m5+vIVκ_Ed<ZVcl`fiq$>-J<57{{afصCiUc3:ql?8z`(~|ŠBW%N~ V|`R vfB/?&mmwHg{NQ]Ju:.{z d˗c/--Yc~MW8gIJ]D GpqGv;>n'i+'h4fC1f "Q$V1rttN{{{^/ 5n_;>ڴuImeOztL#R:RS֑*poñ4~H&#J\Ng=O2ںr;V5̆񸈨(v[D:N4MiDI$'ZikĉHiGqۋ[ãQ6ґ>1c7 t&fW6|3Bl/CyR>TTUW_U~IGtާ<P)Dry^k˜mիW1oݝxvl8 2;אָ*On/mi͝Zq<{b6c;6Y1`hwwjeƘzyE}KK(oQ`X UsΚ ,Da%S>wlNm%M( *ړޤ,+%." -wnxe{ 㼀pZvuu|m]wuf٢1m^koyf=aReK (~~⹳gOXYYY]]/^#* Tsʅ~r6Ӎ"U(/!G1c(ҕ$jhKmB=E:J:".IO͛\~WT8mZ,!f gWŝƃBmC``0NF2M)d6$g/qum_|˟%+++?w 6 nUo4eVƔb'BC6AX.PZkUe? 7ފH b$gՕ7<ٳǭAuiQF񥔷#7DDy (5s8$hhXkb%" w?kWps> cX-.r{ZSX{s^W!iuCۈ%%"2?w?s'7ws|Nb}ܲ". _}',:䯦DkUkԴt:֒I/絵we} .KD\x|  C+45Wt:%O" _?ч~ k[JԪ>}ɒ+̻Ln)D\qGDy?ʞ[ ~NNJ=K&D \\]𧹱!b76nfE֒I'(( DDQQ" " @D@D@D(( DDQQ" " " @D@D(( DDDQQQwIENDB`PKFO/-Pictures/10000000000001EC000001ECBC46FA65.pngPNG  IHDRH 7 pHYs  tIME 3 IDATx[\u&n{UH$j#Cln#GX3#Q(R8ƣCI Bo?j4%NI%@Ka,@3cX7T%'η-XbŊߝ%XbŊWXbJ+VXb%+VXI|Ŋ+V$bŊ+V_bŊ+XbJ+VXb%+VX+V$bŊ+V_bŊ+XbŊWXb%+VX+VXI|Ŋ+V$bŊ+XbŊWXbJ+VX+VXI|Ŋ+V$bŊ+V_bŊWXbJ+VXb%+VX KߺL\_+V_]ww" 0Tf˷nb"@[+XF*;wg?}313!""" "F^z歗">2+7?wH 31 #"11 " @"wBu6-¸yb%+Vpkzzz  u888DbG@3GDxF[/ݺ! ͛7?[b%+3q7xBBBba@zc."L`Y}9{xD b8,z G$GK o~ nݼ2WO{/gbbL …Ej-B A@DȈH)GD>d|E@{/&/#ĭW$b[RH, $"fႄ!`R-"$u3 @ElBD-W2"@D[8DxAEDB=\o޾}%"D[nݼy{7]/["(M'p0@A!p#pwK$ ""ZwH "!!"Ddޖ(RriGDC`@Yn6t@$}6/Z}J+p__xo u!#wW gP HIWtdM cV% /xny "GXxXxdv?|]C@)W^A[_b۷_W$⻏?7GoH)ED 9 wA;Ulvl_`c!8#F2" E5@vDEWśs 0 GA"d!EA_X \_KLo^+-fF b@+x3[@", #JD(GxGaYGD@2 @ @Żqmp,[onĄH@Ȁ%lQ=|tj ~a("/۷^W$_a"C`p(dm ȸ)dq[-nL1ppG.T*Qp0w=!L#)-< "{3T E 9 < ]gC_5̾1IaHDx+~n0QJ1W7[0KYh7 ewh"0dA.\*#ׁJ2Dϡ:e)숌TP8ـ L:nanfq57tspw eXDuʞR}/<HK.@N""{tLGREJfsIfy`ѵ[_1sT W~E "|̾b%]菘,RYwY rݐT[.`! DiΙJ$^LTAHBᑾĜH#a!}pϑ4{)k@ڲ`_B"@ dpܟ#-z&!%"f&$=};o0GQJ\ ;řJJƫ*@C8Wec<%âD8 %iW74aw3u@rJ $牁|YXװ={Plro@TlM6L[J\BZ<"L 2ţ,|3R`uwg&3;yD,^I|8;}߻wsIQNkzŨ&L\yr&=L]&=y~3/z${R믝;eZ S6Ph0!0HB+hgޒo.FdՅEӣ Eh6fFx\<DHppq` q=aq"Gsc>w/ZȀX p FcBR:lai{q -S?fwk7.j@XJCjLtw`an?"%"9p1 |k_܅qo%^Ja޵7^0H-\\XXjӽ1Ucr<s MK"xؤ^{_~iJ.Es$ 3yDf9U-!y;I/|nFshyb S@"BLpj=YwD!"nAFe)x{{kWc" !`nmgBB ?}[DXː zbu|?'}ov!&W` %*P+;6$,H9CXtY/DV)D@tJ S|DR83#"tx:c!8pyV_U% #(/_H#I#0c0{k-mS5efٌGDXJ$)21G U3sĄL3/'UMgN>"JRgy޹{)%"PC]?k_k%⻙ƽHNBuH#W'r=>Yx7ԙ{_&y9-CLKTf.!0;:[ѶVQ$G25# 圦,.Mb3nBMye KVLᆺj,YGk-b"en3 1ZD;*5d۟'w}/$⻎O_{1s)BDnc ZJ]Exn2.mW @C9KWF,Mm׮ H˲wBD)"E-X"\p reI!cO:_tyX0p!N2/y "K D@aFTUlKLk׶x9#\^Xܽ<3~V aqA Gk=#2l$,-Pja (`gޭ5MŸLFx"XC7z];)HTsjqZb6S0f$J4b*@JbnwNGv%srRUK)[BO8Bn 3aD fvyq9Cw)>|RB &]"8jf򽁵F߯~]&*23#6Pp,(u(&d"^$_N38RǕ)|٠V=~vW%1!ZYJ)̌D~M%jFxhu+Sat}yoKJ`:LR xԓa(Znf63Z]QhR,AH,lMsc$N"w'e( /j]3DA 4Ml"K0f܊2!>x[kV[fW;9=}ED03EE:nZˎ|΢ ]/S9rܗ~Xd(a$$lx03"뗢"Bh>5o6¾9zHqaD2 ðsʓ6kAUӤַ @H{#"%FkiV150RgͰ֡,RKޚyn]{D)U8lvJyYw $7[v"BH9Ycx[)Ǿ̌K-nm8mZ=|O^'yR8 a"TՔHS7'L~ݵ.r捒աn6 ?O|r}$~swlva;F cT74n0 ּ\>+a܍B<)pM: ~kN%wR,մBȴBXB@'d}n!Łn 00,Gİzקּ{޻ s)Rj)Lf&BR+[8 ]N_$s{{@ֲͦOjM9:h}M޻I)G g+x֚Y6M229x7$ugO_xᆚӌ0:9r%db6ٻj}B5gB H~00RrwQ 1/ 2`~:O46kWap5)fHO |V_I|}ܝϾycTL *H4;)uRټeͺE/%Ⲱ*=ćВŀ-fQ] :CyfPY;U zXRƲ!B…Jqq>ͳ%j-'c)R*T@U=B< !j`B?[xwzK{{J1YCttnyssOv"ppUd7^;j׌<|R+i#ZUzt|("FjK6x%1!Iae[nƮJk'?mmurʊy=}e0Dk X SR [s ~Re?|,;x笅΄hU*RK/͆bbR4DJ)*,U-j]Yq$: #Yp[@k`݆Y? }yCY V)R{8DX-ZɻÔ~n tzPX$'=Xh^Ξ`-e@~߈~?[ٗGɵ<ܚl84!m" z?ɵ_}x?Mvⴟ./r||dssGf2jeOUNY~;vKfj= #QZ $9bNvPNra*~xKl)R_&j~Dp @ѽ\@lݑZx3!P}ק+ytrDv9U򛰈ԗmH_AZϼtF"QI Enrx ݂Xxݦ-b3E,ӢfDHNJcfD "+ zZ 9ӳy6ۍ{ u_Z/./(64M,&F@==z~H L;`&1-óo u:l64@HHͭoLt6M3 #!p)ˤRa[6S[?-eeՎH9ߏˆi"faZ+e0Kn\3wEPB| V{AHZE 1C.qo]S&c2Gzn^jn7%%%2nnSXw ?}dwtT:uyn4߸~ h|睇n>n~a_r / Us뭵!uWZk;%fS}Lgt۰dEciqK˄drDe1B\ڇG&փ{YzkFZ}q8žUfvTkea"l>y`s,DBDEJڵkoGjLHW<^/8ύ,k ^J6wavӚvLc7c0 `a2[kDB&ѣwɺ20TM{ I|,f//aZk1(ܦ@`,ÀO~>_^ߛ+;޹n#޻u7c yqqWnzHqG[x:E0>s6—qp)MWõA֧'#[4ٰ 2K07s ﭻjg$汔:T)HH@yS*cslB""K /\W1Rnfgj+w0;BDlspnEDJFKfaϫyq.E[A(һp)e1o5=xHHA_\;"7n\o==;Oۧ==C$3+~Gїu)bf:Cu6,ܦa@1Mj`g>eeSV_{n" Gi)t>@P"Rx;'_K-W¢_Z!=@Dhko=Lr96qd|G@ѻ?rrr/|_nLk)y֖"<E)Դ}ݸZ@:m\W~~q_I󹻧wf("yi_)6!8qooC ]X.~eD&%2; ɦ3lXCkL2n69T1dLHw)eٔRҎTYH~Hbv3cmM[A[fĈ@ĕ9 gL&*<iYXnJ)ZSfgOy;RJK67۱<|n`fvvJ)C,\kE$P/ˀئ nܸ!/|G>CFi_mCin7ô}QwCVu#{RWܦI^^?wݿ_DNnPkںi#]\ H [wT6Kh3 /&,B̵F1m:vڴO:_Wa$ivrʍhNW7]Lf{FDv-!bqE//}ִ,$$,hĸ7o"]{)`]#½^{N)i)tD;/lb*ąH%HKf): Mu3m Ł̡;l8f]EX4f,2"L{a=s3wu@s[L$WyRIsB@R1!ۄvҌIxe,㑁W W5rtgD$lf.6p"0\L1nݴ>vg>e7USD ąIJ- 1i6vMlay5wIhzﵔ͸^eKD4YU\@`b)6qa? /..hiUۋyqQz"VhSSE{ O^I{~;c DM#j 5A-EKj , HH@@,RZ+LM[yo:Y@ő'Yр0Qf2 k޻vͶdDBDnT"3<'^Hgt,HVvܰ 'KErXRXeĴ+3swk֑i1GΘ CRo/}+'"]=vX%^~ܦ쎎NON}6 񓳹yD1,"v3D4LK0%N%s~ڏ8Ԛ&"J'W3M 7^Yv}i+GD݆/.ͬBjl6vnM{lf͝ (%wWޚ_to;w^ɵcB` / 1P'an6Rkk}OptQ ;b EdU$ Pݻ=c21 CZ1L4-6 ] baW8l;[f=!p?%bLw~Iy=/ʭVUIHxo6<|pv @D&$ xpv?L!bu+y@y Jkn0Q&UZHJp8.vtnQʡ}+*X_"g8.gtO9 øG!j9 i);X^֚4BF"5 ̥+dff1DUi珟|Z3,C=u4M%L%@PHflۓ5Hne|}M[fՋfpBVBm/N_{&Dd776yDQIu-#"ƘR߶b BM33#hfWó'O6iJMEMf6^е6i0tc B3f% if0 q*7{~ _wu?77ALi>DM8eĐR`bMp8XT"!E9*Դ( 3S1DDgZrNi\a^MeuOhS (902;%ȗ4M t䇘E&#|讛VFmG?k}m|Y6ѓjܬV%\k&9jU nnnTϮ?)ҚIS$|gO/@ \6g/^{sNEAB!DOP[N)F^,PktZ5&"@i-4 x8?gX,ON/)&6l}WNSsWOpF+GU.J)8ZK5iJ! *(BE! "|;ۿ;o}8wA1Rdԕ؎HTJ9Z.RhMGk-DLpE13Γ :kG]wvmqB_uQ1$82v?QOu#~ݵ1iP E_]]_]]7iL'+-?z!Fb=~jk.tx8)a8 QjJ>z~1Qxvlwz+/M93sUDVJ;iJ)n6[DSb^CуgbG:G ͙XCbBSO/r.ty9ӓ]mmF]KO.#""dzq\JJhZ tNwqeK4OR5U BTVR!1 vHV՚kO$0{@cU>[35 IDAT˟|x=?Q@ f25` P5&&&]/*fZ-eZq)8!!R4QmD@mZtM6"#LRx }/&o~|@!8{Q,)Bfb欸JkMݢV!Bch\&d2Nm\L0iLnHS ޯ(0xϟٗ!2۝v5RҤiUz=0(M`nsJdO&BBTt1,wtqxS_^T͚4BBEDiMTcJ@b\V?QC1#8-׫! 8\qbjR4a*ŐD=~&s3;b܀R1J͋x;cfb{fBjGȊ)4i }2uFMDl,\**0nqLDVDUzxIzS&mHoKMjMrDBavnC R&VR&iUJ7ݏYt*:_T z{yӜCEґIUU8{m_cj\-JPP5VsJrs}iA՚9l7R<~cWaKB) R6Hkk뛛n=r~0 TVr>|9Y,{$[,4 CL`1{)GҌ+EY@kJ-UUax7W584ܭ<s&D[RjH0 0.j@`b3yɿ[ n#{ācm 9%z&&b4nSQ tzM)>~|nf:ĵ*&jb}kjj2>bllI-$D f{vzݞHte)6iUGXt?ݤ{8m'Kϳ/` j=v8C ]i+ʁ8(`:Ý? z&s^/giBCfBCbS,Nb00fF'΢Qr?nooDTḊї)%A2V^_ qF3竂[4aH)50 S'6FLؕBBy" )2Mӯگ_뿼-EA~CHc9a֪xbPSTP1\]jmZ{(^)%WI=R@jqo7ꢞu03bt!D1[FOB3fX,Z T4e`"i8ٞ\^^v b\-IEO|Ḿ^ͣRk+xdm]$h((ӳOiUjQ;{k/h^{2!*BS x\qeҦK?m61gsDӱ)\G?~d_?Y 2*CU$,9g҄9aHݺ$ׁͫ]L f?B87 z*Xnoo[ky:.XkG BUrb 4Xk (_.8؄j)9{}r{O~wߙDs}IP|.RsZ-RcHܬ svyⓚ lrp7R *؅!ݭ{c$#Xe$/~gizA.7HO/>}sf/nizvi*@׵V7璡1;{nd H9|\NRgc__" afܞ L@I ŰXaiq &}pU@ dmt)Y|ǰ^F Risz~SMSi0a06D"7-۪b ~rzh)̀99v7u?g:htnҘxc(g=9M"XC%&pa򗿒s/}/ۿ{GZEcOG>=3mMh}HHޚH>iΦyt$"bH'y"R\  yh*0OVz4Pg '8s Qb}}lӱ,h!xT J1UD$&(!"gyz~"Y@4P~h"0Ã=5Dkzq鍈⡸;7FZf_)IM>VDf{! Ƙ0,B1T-j\DljvsӰZa8$:(kk cSbHwQ ~9.^3c;DÐr΀: 1Ƥ"F]@s[~ʓ [k}y{b<;@dCjbzqq!8O6kZ}`jǙKhQa*DBPtGK ÃDJPqYdZkmE|H)  hAI"V+p( XG("|ηTފ+R>& !gL4oψQx;tag&&Wxp k~ ZVx$#DE4bLklIU4rX @$dZ1f0(B[Pʍ)q LI4Q oZccS154b6QLߜ>C [S3G"BF"֪39oB'ۀv񣧇ֈ(8×^J)婳陹anB`ў!@|D:2cmuf6%|,[/ܻ:[o~;Fi ]–3M <GʹiO2ES@j{HCP5NA+=~&u4Brɂ"6D),;QI f_X-K-<~Y URJ)5"Gzݜv''@-k9#RUmq<8#GCQ@i'$Cx.Pw[L{x5 "0p v6g"!LbobS`bG`^1mm&U *R"TBDC߷1Ƈ3?m|;Tx/ʵ5!4D᰿Qx," `fZUA@H9NIӸ@Bi`/~Ʒ-U(H9,iY/SS V&X"jbTͼX^ǁ@qZ[]s*O#lRkDgʪ"5:t޵"p|܃fXkjq=wz1G3a!*lB4d/!0SZ@YERJ"n Q@FL~ d2n7ndN)>zd}?:Y}lo) ܘSiMoUq'hXm!D9\AͦZRX4Euʔ!#n鍷|^r|_7o*aDqFvۑ{֪-h8PH!=؅WW7xtjV_3Y9`۽p̗fLl"sSl BB2ޡ \njڤ\j9gRXOooq-Yjm:}{[&r_e$0a=={+WBZk09L3ALc"K~q)"ڰN2am㣡Of塇ջXLEz!Zf`!cxŒ/}9s )s~H)LKDbdf3O?.$y !(?{&M`&R3Sy4ϯRJ0FE_ޯyw <{vq{}?DzzussH8D*#J`R5&dtk`YTRF$f/Wܽd^;oοֿ`fԻ&k%2I>&&b\.V焌)ȫqzY|SVqB$F!pk5tݝ=$k"o{M%kCZJ.ڬZ@al7n^ZIA[n2Z |߷R\'"RL!-8 L@̌8uF&71777ٔ[/iz0˫D3cݙ1n+EQJIE&!UDdm)djVky*֧pB!(V` +HB)˹{_"[oW~40S8DhU=t(pDn8{u9b7k b}`n  6Vf#]j|78}4UfB̢FgggnZZrnjo$M>nHaXPBL(jҪ&o*qrlMu'~G{9a93}>:K}`*HܿQ C}]C:֓D9κ A{~ WlsL+ C@<oL8r?̓C.f: Lnk^ IDAT)O3U#S=F1)3\^(.J|r1Op3R!Ce=;zD!g(*1 pssC6j-.ccTՒ `0@h@a"5vv{ TU5.i dfo|h&3?31=} cDš!2$rݩLRhk9o1PCK)Q.T81.0Sϸ8.*ڇfM3յR2bOv.2id:H9Jxpf?};P351ϋ2uR#tQo\C~W5i278k+iTvU(1*w@`kQ 9y]~%vnڊ|'_\\aȁypyylq1afzGWQROR sX,4Zic`?6S53k SJDRzs/??6@^Rhs4Mk&K˔1jN%iLj*hj-SZ2pL@*S~UUkk/~ οo|50mOOWH#q~J)͒N7ՎFN).qʹr!hvΝPAL|1DSLD/$^Ue̓|jߚx?"Jz?tZ6k4iUji8LJsF@"Ɓ8i)͔f! 1C@Y[#:lOď޿G:M~h x;o!2橺Bis[ ]H]쎜E=ņK Usܻj?1#}3mhR\-O>VV3_`}xru}դ C7v&8%bİ +3a! f:T"byZ Q !}~睷o/r?oVUm_k?cUu_J1CGrL392z\ L̡OLDb L$BtjPwY" VU};MDDűԽt5Y6asr9ٜ MQaHZ?3`d1PvQ^ǐ#V,! ML K *48M6S F6tIeuz~nb&`gBW01x.TD2SQAizI_"NC"CDfrA+BD!8LԝR9(4gcwF Ц2yS,"L(&BMǃ:"xgc<+O& h"`0UsTEѣǭNdY~vJ% (?Z8-8 ZO p}}{"ŘsqC*!U͹59iL֔WXbm,̌)ZR1R`HzfD7R/WE\ DhGC {x ͬ: ]Z&ňr!xfm6iLӧnx29 a n2ԭ6םfs8 ֲtpɀbxmE )K)ӎ{ s.Ę>y-x#4Ǐ"j>.A 'f $h6'(i`?R!C-{lg8Cwv}͌>I_V7O~ R^kĻrcqcBN)nHncb&k4@d1"Li0>_ Z-WU{@˜wjMEB䞩^Gp 3  TT*xJ|X?xx\(ѿ0İmJĬ"6Z0+bLb@f]K!r@zM1*9;mRJS)93f+ث/=./Ots1^i hgV}Y]"*?yD.[vK*(X:UcD]4va4ƯOq.w/&ԇ?49i,H^z)"]@* ejDbz@&֟1E"3mbsLwpz*2$x` Ws[B໐e|fDlZq gr%3yDx< Բ;phE*fhwR4n =R#*'o4֧߬Ǥ*4"vR޻He}9'"2#oU׹px)-=6`XO~f Co`_"9Hр7p{"eSUuȈ8Zk5&f鞮8kNx8MӶpL)7)wn7s2d%sD[E4! (S5 T2u5" ?~o$+݊P%3p_S]_\u lfEրa۵˿o ȕƧ**L_3pٴMk[:c`"&Q}ho)9ds{oKe9URm"Uj8H^#:j#W 1^oX^Ѭij=M QJ2?˳ %z@:Gqy1"Ut ׫؄ұP4|*,O*cܿP07|"'= PGO">~ʴ͎H*`}xlj(0 FiƩ뺦mLk2S(ZTv"JSl6>|!̠4\RIj*ET`%Dj3&3FTUN^_ޠ)`0#ifmӥ)j=n/.s΄6/ٙA4zzջ=iJǝ]R%{:NE{)9 MOm:{B`/ukK="^Ɠ'W/ގ:QDdd\Cݼ;e 13-kHUWk̓"" V,C  lD~?M1,їť3JLi=i_=U\Bj?ik`!" ,\_{s_]s۶LâR)M *bfmHJD|eHZܤUS/@)M8F\Vva%W !uGO"OOaofT>3Y m2ܛ!$IA ףBNX|.$v!H"PDp<׿u5;ᄅqJᨚf>z!cVB7kK9fdb@"hv QZU-ETJC8l]fVx Tnj̧>7^umњ˷^=I}X  juMhסmChpvRJQ,&5RLG0XsqUUGhlK_7#C(2) ؔ2ؔ1 Rjg'|dån*6'49C7"Pg]8o%㈪V>FHiB1x q "C0qyJC=sEnwp~qE"䚚9[]HJ)PTD3MG.9`#FW`%b%r;/^"TiddD$d13liil!8zӸB9o7EՀ>ڮ0S$5f`DxEhbiS]0o߸?E sɦ|۴ѕdSJDCChBv9|ٕ|4M;8pE(S&86M ]..Ed]nW?K[VX璯 I ĢY1 = :tk1#EiLd):'yGo3Jb@*DDAdWXX$1F$;U b,j  Ni. @:UC]-2Rpqi !h_8NV^o3- 0@DZd`(m9W /_\$ZĊ<1 fwuF>@z)"]+ ԴfݍʪRu yfxFNT}p8#j)`N 4@F@1fm[Rc4M26%Mff]h5%B+259X!4alW!uw/)"njڴk3;>?/\ŘU514mOUnwfjt;}8PxhM^S9.8\TDSa:q#{oX:~TYoINb'q@iDɑ Yp.QW .4A Dr1 %;qo ̘iaP-31LDTjYr@f&!-hp%ɓjLAUR2A%Gj=QDhnS:1{r^OJ=ጘ8SJnD@Eڶ4%QkcZbӔ$n㬱xD|^8gln\ABNuf,h 6oV߻ )i}ݞӤE*NJTCSζww{):vmt^CJweŐ8Hu]2iȅ(_$E$u"nSFio/M^u~)īgggMZh$Gz(o_#""qʷ+ݏ}Bٶn< th}` "+ x/DD۲BdYm8$ŋ b>Ymh˨IJn8ϟ\\lp?fY\(">`V{THgVmCHR |0U|"ː*cyLB8|qo 5݉r{}cS[3"c>h6r)1xuչUfwD"s+!V;:ާ]gn{nC{kOڠ|]*h5?9n^|PJ/=1ܱnu]롃)6\*`E}+"3v3`3:?'Tw>}r_e/|Ӕ慌K <ۈnVm۵90 ٭:y-7XzQ j3*j|M<3m׭Vm׭Qz(힃DA DOYh۷?sڳhuǢCJ4 4NSN[9,e9 8ɥж="v=jEe;!&dԙ# P؜v PȬQAamBj?Q|~=~_L T T64s4Mpv~8 )R 77pZLs=Ж`^809MV﾿nqXr^0uWKu/z{!4Y{ʊ[^ ^eDM}kuj`kv{RDҔz9hגsV;uGq*jc\Տ?|\Rlm:]C8=LcNS:nELc45&\D<{̟1M)is=SmFnoJ.!˻#MzE%\4VUϕ0ǏapjԬNc:{Ynt&EDED֫U1\J1]7#$oVp8QVWQ!AARՒZWa ay߿|FQ4 IDAT{Ӊ*H͹Z抨~fB YH1J.92\ *b)^{g?O@ivYuI>ܔgt-'Il\G4ѧ{w[lK/ztzxTV-/H),(ux Eſ)"au1Ō>yZFDq*, ck!!x$zѨY)΀" 8w.SQV3+g巩[LeXkmOО=ziɹik~'&Ƴ홙MDJ¼.DH۴VuHN|RZ%iL j"svBSPyX4'\T !o6q4¬D$6'n!3菃rݎ9Lcиcu8̍Y@EW۸9* HR&~K;_7E6OUmEֆ&=~nGfio!iJDtq~b ]񂌋]=bRkA'^|YrQinow9'˹E˹CśT],9爵#3ړKjVC߻T]c` TJIZYn)E14<_ZB C()ތ:h30#楥3"J)_O4;_+ ɾpcC@L1"8L9e%"q4M5K20s۶Mt:&ff1]N EJ֛}8OxbՌ]f*bɺwlqHsB}Z]Kâ^j'Qx?s2,~1Px]T< Yڋss.EuU;޽i斞Y$dkZᆎ[3H&*`Jg|;F1Ʀic]]9PY7¿,ԴUNnOJU%b?ƈkηoi.~#312~;3fnnP`3M#9皷 pzzm}zrun*%|8}}}cRTTmty"޳yavڠ{FZ@1RԼvrx)廿R7Z!:^}O=sq\4Dz@\oSɌS֢aPy0IT)Rۛ94Ns*m۲? ?Kԫ'r{{;ne:JV|w 5S}|xa 1^>|xSl͉Fb^w4rI)q`b&ľ?n6lÒTjI 1kv8gn"N) f`BgR !03D~MdR5ñ7_Z$)F߱m:Ā]۩4L>m~hΜbg9aa`6g#ۯ]UOyp8zt;C\\9s fRY2>+3Rqar ^p,0k.8L ^7@^.ϡ;|<C]׸k qD }Tv":mÊX|yy>0Rtu2y4P=S^>N؅E R#Bq橩iUwvv^\xB.DIYr޻@0OݿF. &5M89P0D]N挙4GU\S #2}_}S?!&,u|{Y\Z]a}33fnmZߴ0?9">xo|'Mdq!yԼ(,08;=2~g(Up@$@1m7ms/ V:ND*ƨfrSMGƁcl~g>V9!ƭ]^{~HsZNҸ7z SUCVnvwwu`Mnko\Ԅ =c̯M"j` /9V802 <]9MZ,4^M@h:^rB 9p"b-p$ ZC^ ުi}v=mv&)=;>ݵ/TjIumK)'Dlb }?P"Kc2rpCUZ6b?k .4k^o NZ:Qt^#W7>s7.'U.$$rd޻1F5Ӕ`խ@y+=AR9!^_ 2w"^SlDNj${K)Bd^:Cs@@<뒍*QM|eX#iJ*X@D^T⟙dE 'u=ZԐg֟U ,cd<D\ Qۛ;@уNbj5@s i4'ޛD.; !$? [:jvf0 CJSu*<mxOTșq@t?l϶OI\ae8{dj<q*3m3cf 4)XNyC0.}HFD&M6M,|k_"7DwhgDt^TA= GC-bLަ`AL mǏ=o]o\N2g&_a%b޶8%ƿyfq3M;yZ.Z9[JY}ˇC7ޞTX#u2>C+- 0M ~GnP:0arM:0i*;Q%3QyǺܹ=:Ň|~Y W<7TDRx+rw{?}s'W؂U: PlӇ/_"mU#"*N}K)ǡljsJ18Fj6E)mo}g?KD!F`}; ^^\tŸf-Dq- E`L9 p)2jVas lX5o_n7'mە,wwwϞ=?;ߊo Į'>i۶V<p?63XChRӅɈx1ʘS25C$bnƛN˔:a""H͇?l֛͉<醙 J/b4C`V Y+TTV!/!B]{z%fdJ5.`6TUBDAE;ȠE}< ;YSB3E#d3#be&0+>CgcEy``76zk;ٟ>z2.̔ns1Y)Ba8|1Haf 2)U4(X5^0EB"}??ض&p:ފf;|<Av̧>ӧ#ÇW0|u4zZӭZSkR?\.RJ999%$0;ndd")\^=*Í "Lt']g'D2^J1\Mof!Ԝ\_p i-WWWO<<z0 H$10@-"Yϯ   ̷^`F"*RJN%"L$v_hze.iZDl&6/x=/ӓUqZIQ}V F Ddw[cJYrn&`gOzfYe_]]ViNOO73DRLmF31!>f0vFoUy콄CXT\*Ǖ"1$НR<`e2C 4!koOP;4j*=NNNTlw!Ī[ٓgHXq2CNH)0Ti۶91_<ݝ<,r,RE5|V<mElԞB`L!nj.#%5bl۶i K%O$_U sp)EEUӔrY?ZL؏"*&ൺ">TK-56G/菾T?)]+TZi^ ŋ RU#Gw \HNEs"Rv 2ɕ#ݽp@w06m4+2/_PJ9s}4@qLfU:7+Jgw82g9zT/x*J!z"hkn*>n/}OmN'"PkN)ht/..?w;g7Ϳ/~fmJ)iJ)qX}Iw/^8Qf[\ǀK0J0f(H$RXVqO<{Ҕ|ل%]aic͠gp{6L0Ƴ\BQ` ǜAӇ}ض?~㋿/23rh\RT B `hL$rfyB(b/jBS )f>,o  |PD`bu10ǃ"F$9Y̺1?zթ#!&RttTWݯ|w;6`2 n^c1! ޾|KBMcKbI577__h00m" Q0P f̡1)M0ǟ-`9e/+1Fr{Y'n|ҁ l͠@@V35J9PC  IUwwC/eϿ2>|xKSx?~2, ̜R*G=RJnnP>_PfL$hXTCK?d h<_ ˠ"6]҈t?x))D'׊FEEgO\B ܘ8Myq#gt)rz QD>я{gWS|PDLtѿN1k[b'UHaE%{r .ť%QM$ch0S[!rWIQ-*YT\.W)A7's!,N$rGT 3^mOO80^wioDT-CDfpzӶMpDb۴jv"U24iSbj696sd"mP`{b͏cjS6ʰW-)9TzלȎTCW %%Bթooz\Ŀ?qh!HK2O+X)E_܀C+9 w9D D4a{(,qcSncS;b5ڌb$$t=}6[=2ٴ ύbۀ!b5{b^# Y%ZB5 n2sI) |g>CK];Ʌa0q>wsٺ!$EϷu#;~~F &E)b@5:|<4S\W'k$\Wz3C;S?ԮZ ^//.cl"iGUmdV)y ij^RL]q`t6q zj8N..˸ג+5h!)ݬπp`uǧ59ZU$:zTZaU};g'Tu&)Es:ozyxfEm;0UZ+7ʝ\7y%Prm*>ja]E}Ȑ( 1 ^%A2 MSm~XJ9ң68Y? "B@Q)*Vh>ͮ>>}q):?f,1A4^!v/^E>#{ЬJM~rxdխ<FOɋ]ҁȋ!5 _jIլT)4+vF2Z`IDfхWJ·v? ZC19vy)]X4sN9!}-ƦYWeߜ8ESO 73zeͬH)lQCͦ53zK -ݹnPr_'?ӯ|wX IDATñgd+R<m}?\8pC}3Lma.![qü`D=LFW1Vraۨ0"k[UCnrD5Jm_/w39__ }Kyy%Vk M.9rg }},@#LSZ\0nL* M%4@EsIUbmˆ)MprzڴM99M2oZA;-;P\*dG(ppPA<ʑ^bR$D auKPƨq }_S)UI䧧8 Ճɐ Bh<}nM\FSJ/_40n_!:af6w>{ŋf2W=\\DL/..PqKODN($RbP=_}0?Vg d`Ԝ\~WmE˻ݔR5c,UmIImt&شtΔ=o.wDh`Hj R*jȰ8v2 HfNEbC.$ַkko)?/}4 iOD̜sr ^0Mػ/QDJa4N\DʒSf/_ҝ?fPx8&UvCS6<3:c؋n[U7]36rT4D e۵uQmLnSH@wZ!hUզ Dx{{W?_ puu1(Yq)R5)OKQa *"E$a'Vu突yԆrnZXoC>80sB۔&ÚQ$0Du+$1n^]R[o29 10J.dܛ,q~B'Xa.O_͛蕙Zv<fs*,/.c=wP+BA$*{짽}n3r86MiF"NE͹뺹 k[h13sΩr pr`eԒU#曙O/#Y Yx 9~l֏>tމGՓ'r.(xp82شMH?]<' *=wL4իcxzVsO2ᶿ}&m ~+7Dg@ "DbViͦm%VL<9LJTnkOEPV.ZMl)D929s93NJ)>zlv8G$t|T|U6a{īk^\L$*L€m^KJ:UcH4ID,^iq`ծr&͖%};z@7 "!@F ĮL+dZK[ď i'3%@$i 讪^Ngwܗ`u[媬:xxj*DjI<b$M,S10i}?\R=>o1g1JQ̖(7ˆOjWM@iPHggbd *i{^MUaqqā&.-QT5PDs^,9%3m׀YuF!\Dc0RΘjtď| R M*[نlTPC\1|hu8g "ιt/c }pe<'bhV{9Dn9-@[\$\-cllǶH1ӞBṔ˒OqOŅHbj̐,P)-fZ/CEDĦ9?=i 9pԼ&*qv%'fSNǎ;KiOC:ԚOoNW侧n}9~MȖe_ Ai[GEqqhs6j%ga! [A^!uJHqjo~^α>p:!<;9;zw?q{{9%]VC/0QSn8c i}7MX.ww?{7@MðlݢZr/'7׮A&YtؼiY-!Dp`uo-:(圆oF] ̴\,xMO9r̚ &o1Ⱥ6R݃O`-4SN!LbUTjR[&uI)~s)5a#.˾]GX7q"ů֯MN4(}?N撝|kWݢRN< fvuj%ϥuurF`բ;S$*Lcˋ R}Tq3ণ4xR%I D=fw?+sH&R4UaÞUwrYy&8ir+`0g;0j[B7M״m~h6j4 Ɇ6p8'h3 fP b+jdzN+gjXbQ)>mE$,*LD1LxuudzK=[?܅eFp%xە,EJ\ZrEDi3\s"ĘR"^z^|+.V\V[$@8ʴ9-xX_=?jef >8'00r4a+P#O14ԮEm·b*=jw}}giDZi[)% "w}Ps8==4N(gRqr"r>{+BIZ֭/vz(H0?~~ۏTIZJqaHOh)YSOۡ$u^^^WrJ&Bmc`%a+4M4R'^݃N5% 59;bTk}ZT &&0)N`"0_ܯRŠ!rzj% O l6R<%\?1ZdRs8}!ۦefN?EFc6Yʏ7S!I>p8Z8|vw_MBȋ\t__\\\Pa޿{w{'z闾g8;=k.)df⎙k2cR(l#`DsaV3:@p/ s&K)nsӳ53𦖦IDco~O?[X|ntJDhE͟_n^ cdiw0Mc !>g3Q臑bf_:|Xhu~#,{?SyN닋:PBN09\֚s1+w Unˋ"RhWgHU}'Db aBuvsF1N5G=ـi5 `SyI9??i!,˶i)j2 Cd!r03PA+Lc(R}û[HDn4rI!453Щ'\b۵'YC GAb&9LZ`8Oݢm ϿfDDrF@a+DRNH!,K80"̻Ht]GL9eQ}fOnB7j]R=;'!l1qX-٥侧~=K<q`O*$ 53C-{8еib*Yy!0r ]XV!Ħm)23r^FG6aw"#yT !A5j 'p`P6<fNw8 =0Ŧ w@",'60NDe1uz49qU)ZOnR$,'医\cNdr8:gUO=?Z4g z"1qu1FN9Q[_Ph8ov'W8[@Ei !s)RҔs)$5#國ǂ&jDD t Q0Qu`0M ayD&l=>\.EAظIwf4Sy۔RdO4N< 09狋)q^}@ Z Dz8Rnyx84i25Lxo9#I7o^S^^g@qb64˓'>Ț&<((93`M$ M  ?bj@mB"i?zZvyyquu:_xy}O?YtW/D|TLЏ8jR2Čuo)b ӓ?_=BBg>خJ66ɰyGEݺ}NpnXyv^itJ!c۶ *EDL-.34*j(#w)%}bwHQӭb)yO9km>Q_E ȳBt۔efbgؑB!f4zyq8Ѯg1NgU _ 1clJ.C\pprz֧vm4K|BR3u+P}TM"Bf(esZ?M'1Ctz-za`j"{g7RjU͹/~?qssDK)ވv;ƅ=iFQ0=??l6:8Gj_}!Yszʎyg/mſf30 Ps,L]֫H1Q@4s9PDb;3Ua^ e S*K O4JaAUTe\|{>;?w~Z-jxhsHzWQO`D*&R"|G?_xF7lM)*);"8裛ɺhn?25%jɁ#5߽7S"T"up.f}MtQ@Ĝ_\$ɣCf9NSux[۝oufcվ!*S8@1~PP_sv!kncJ4RN8XR$5Qwt94M?X,v%#֕JM-J[KXakC; "4D{Wq\{6Yg2 i``9禉>r8Mw!\ށ*QɩjB,<<_ܨjAT$K 'hCC}rPra+<&||y~ Q˔{g/^%+1o6AZzD)"`zJY5oz3zu"[@=tmH_: 7̼y70p@v4 rdLS?Jy!po$O9.1.܅\u/[޽_1˺ 0a$71攙Bb@fDN67նm|8J1O uW^_]7-Fe\dN&"˚0*IMJjwŋ8ʦ^NRd8`QIil40q\a7LPl//C?c7Cܩ@m)QieYEXv۔F{OU,w_ eS 8FIJ@3v ﴿._ (6qBJ)(MrRZtn11oVeQOn+Qh0MiBZ.1ĘRm `y,\抭O̮{l6G/>FJM<=C:,*hCD}' %|Um[4E*Y8zixPx3V_-9ާMHR0ΖnԽ?`\EcHhɂսsI"@/U/߀*'חxZb)e3D+n6 o~ŏ-fuX1U9sBv0[f13$"u]V=)жmB>z`.011!"~Ҭ.VG7ן,DEM|.a|ƦmAilm!O<(3,j!PxՉ^3v)Ӟ0GA$x84Nw\.>7|ָ۱"lV흙J1`b)s&~? ~'a܁ `?\YXAq"0Ps6qu-"^~!Rwo7w~:nS;H)vD؎Q) *5U?`XJ)sof%ڬRpDUS`ƍ-jʹ,sTE)`hi/_|%k\."LKA4 NBtY矿z"v=8f=NMv9Wg)kQ2N݆>Ji6*b2b лwfgWX՚0♻9c!{v A Usz=jTXOuc 9UnҜu}s8k<QHetJQhu 8N;E,.EiX䜧qp!6MV(ДqGz\fs/*Zբ&H]"Gdj@TDils~q^tD`ybjdly.E]V5Q%0!xm,ôD>avfsvg$*@84%S-GZ.WX|~}uCPRvy{{Vs*i8vwb8QF!9IZԐf؋8v<`'@Z*[98!hjxqyED6Sl>ɯ;z?yܽ~n$7U#'TKZ䜑3e6B61I6\UM .NG.E?9曀WI+}?,bDȁU}w))'w o}wW=`#hӶJJy .ס[]ח]A98*G Ir+oɩǽUsȸLlDӼEE0\y~Ƙɴ{Z4W%<9dN5s]"XribdqTmTr2GG1~ "%׳͠Y_ 2yp'_Яnæk[Qjj+1e?N]Onwޡ$KxWHz>?%EDUmRr2,nd^.:D)ܴm r}q{vvu>wBffe:}y899A W X)3 KG;,(@s֌I!  Mhtw*a\1"Z-/G/_ZA1ƌ<d2嘐qε@.bږz!RHHR <ND8~PJiczڑ'38 H(Eb,g @Da6 =lNN/|1SՏf*z [SILool:HIǻQYJ֔c+0dZQU!iQ'!K`:28fa,iY)Qs "ESkRN Dr9;=irssZ/}_a Blچސqw]gɦ8o84 LDqBP%iLS vyȡm\\ˌk66Q5g/ ?bmCOW땯TKi@7"x!h&)ayҊ"x3c&Ja:{ح6*i& 5Sqw{GWy rԟgT'8jI4z8Qhͩ=ED8R?O_zDbC"*ay(޳dLP7Ȉ@M"R5̬~7ܲ)Rrw 17:Y)-WW;jC'rp2$"&C4@ՊsM]]Ds3.=^As"< ]{&sL5I`ەB.GKSKFyKi:z" \c^Mu!"*Lu"ʼ^98'mJ˸p3P݊ۥY}ؐseSت,Oj+Ǎn PK`NwDw ϻO{]8?qwvvVD R4MTѾ1mb(9;>6ߋiQњ92b'xv{*]@#u+;EG d1 Kj\.Nc!xxx`@$Z.O֫@DjU?<$'NVlqwiJD)@b1RZ-W%z|x9Dt b尹֛5f;yEiɦj.[Nyj_XS.bSnc?$Ek{8R'<"/`XU8.`k7x næKEfVMhDX.KaGd̥GBM$&b (\n\R? dbj]8@M7c"ηmR:L/0MIM"LB MwԐxR+< Pոs/3ٹm/a9ú:ЏnoJ㰫F=kǠ.NHkȸ8==-E򔊔~LJSB  R&m/%Cm.X"e&W1S3 L!Rr ?zlTDT&6\NGpFfjv95Ӕ>nՎv$gd_h k3 3B"[.y1&ljF&ğ'϶p6cIxf✲ =Wn{vl/>|MLf}GOu?߾};MIb s.49mmZt}~qffE1s01/؜hgQQƾ|+u̿-띺:[WaFk]BA 8p3n@&bdD& dsxIO(8=?:"Btq_j%:?c&khf'UߚT*R̊۶ \ෑ":E4r۴ȑݫ<><:\TEJuMlFiD#,ղ7SAح[O* k8{܅(at2AUӱ\8å\ֿd~0W}`73"),p^4N.gf\{UmږqT>kOZ}\@r˨ZB Ÿj?8V$ 62W*!V l \4D˺' YWOh9]۵GJ&7\_C)Ⰷi=ēMb)Tҋ-R姟~o~Z/t]q߼} ._S@Mow(Ӣ['ɨRؐ.̥-Fz-J)> RD0͕_.g'N:9j9*!K15D88ߧXG/ʟլFl_S <>+Q b{1Lv IDAT/"܄A /=S.ImXJ1)pM_""mq~qlWPXQ*"U)'qy8v!zTTxc` ` ūX)"R~(zů yv\U 1qwIZ6Cpeʁ 64v}@uC'1D yO"vxY Dm6!6(lGoa)VkEכdu2w0`f[g~݉ `}/^< bM{!Ruf *94%!"pƱi!"yv7>NٽR$eʎ͸SrSʻ;Цm ,؏U)YNNֱi9İ\.Θ!7}䒛!nwkFE`:FVGmA5=\MKH9S*`fXs<"/dasm$@,tb@"  fP9_v]3)9.&f#%wmK՟X2jbSJݮm"Jȁ``9s0\f#`p|\Tiʥ./S͞>qrv>(%#"$GcdIi ":1u q;5GHJLjY^YԿ8~u>Z{l.,4MA} k+7rEd3M!Bɹ],nwӔOtzZѠhx*BTQ%h?EySDA_j>l{5ݽ)uL1O0j T۵MWW|Xt^D5>ra k89)v,)%>Xr*UH։*ÛiXDF=1 "`rD ޿y^zgon.W]kedJz)%[`8ڽwZR> (\r*f!t1&Fw\tuÉoߝnڦifVZ[zbhV.̹dę>`;rȑCaX*2Yga-\D@Y2(J^Y4h|(牊̀Y ZX7[HΙ:(0O|{{m\Ta3!2mn`[8o۶1P=ts2b4teB~E=lWPAK VnZ|i ^}]=v6C)~;o=R8?6t{"f,Z`t9B hX[#9C K,I\__]ckS-id]N-y<>z.%RL!nv{(E,yo4a>gnB\0C_r"lTs38`yDpPlS] ?/K@Zk#j\_ف|%Wx+뜣nriƖuXXaہZ"me:S1tJ9xݣY^*0 S)Yra[3K)4  ξhPu+|PBDú&%aY`av&1vt!; 沚$wwe8CЉbuQ"ySJ{(&]AsJjl'tO` _/Պ䰉 -/₯`yfZuJ٬/. `9%y8 uM.dۣcy148b Rj8Tpp\5CfVUbdiv'T?ԂD;,1tW%}Cu޻Uq iضm4MEQAUOGCqZ9G~TbB-fNOaA 4Gxi3g !b)k[)#9B0cᢨ&@y aviFAyK?=?ٜRrn6h"pŸdGK%⢍^rnnşsUh)%g'*l̠sf睊x-glއC \GDH67&OUq1l5].ח,9o;]R2O$*rVOSZ);rܟXm¹?#Rl]K<=`.l\쑴_E6T+"Oay,wk*4M4//.D ׯZ>Z5΢ffA2AGZ0#xe"b? 044kPDr)0W t޽}UZbx-?lbkNxxUZB {EV T"ha[#]"[6Wќ"JsV]G_O#JxFj]TQ,pYu[C h@6-Iݽ]VSs߫\iݍ96)pqTԢgʟ:sɦ,mwbSVM;)5m\9Gp.%*www2U8D{{{uiUhVl:@@B> wdE;E R.ΰFyc3XжCQӒ#9f "6_V%wmf8Y¥mЏ4VWX?q{uy44\X%]9<:*9iڦknkiq!2*sjiC#CXwDDg"WRl |= 0ÐSnwȥRVXVu] OOۜnKS:_q2k. uE̿`C+Wח ~*8Z+"wqD[*N8MTz8QR& Wfe?2|ج׷SսY1Gb\*IVfVu@ D51\  8dBfVli[0S q%tsVrT(wlZ9TϧYλVz^ݺ)O窦 mַcq=6v6xxjNl,j.g=)l[Zq*)[>$ >>m~ظQl/_^\nDK%K1^_^tRҔ߽{_ |4U+ όUMS.,"4AsQU9h ur\?S1ENRY.q9^@wlgQ%B|Smt6Ud1[Û~IM{ 33&J.9z8ONG/oS骏jiPDsZ 9/_/|Y,9DD5""|򊜩uJ)HΏ)橡:TPއXriڦmׯ6Nӫf ݲ*ڄ0Vi Q6~3k3kE;ESNӔ2Zh09Bm۱0"lϯ߼aoctEҷ &`) HWoך[q($eZ!-> RO>*%˼J.=`S)l1{ɝRrMv]͛. 0Tt~z3b±@a_$5׻5^|bvBkn2>Ƹ>Fc]V6,w"wDjFDRUh".2!L_}Zډܟ{ !;_}8 RG S)|*,)e<:lFD`2p)\muͪkjjեB>}<Ŷppy+*|C\?$x\yg+BʀQ|*!*IuйU(O,L8Ƴ(Ćrybjq1@o"!Mm=ԫQDAq!`򗺝&tGF& 1@x<+/L ݏRJUIcrcbpN)E"Bu9o6T25z Ub__Q$V&ӳ [S̥T#(l;1Z"tƮ[سGD"zssWMgt5-^] SVTHaqJfk.Ѝ㘦T" 2MSic9[W!8[r߬·ӻWD UP碯 >Ùˁ\BW1!JHTS51"!Α3B5WS \瀆;B9{IyO\|t8RJMӘIDYVRjv>klY\ 咿:בls΅qT.o^O\ nF16ҩh5D`I"l>RُoRLA6#2uMTg<ئ..7ӟ,ͦZ A-[iw./64Md!MVot6i#f6>x㒇}>O|DӨ9~e{U V,#r# yݙw(sX!Yg.T]` D(,U5mcd@̬cl8iyzzN){E9nn& q-s¤eHvu9\xϥf}yաq1^A)9̅}D^b@o߼NJP5LD]HmK5_J!e9B<7Dpf4HߟSDԒrwŬ4f::,h޸QE!EPAX7MNyԑsHÞ}H!vED2#blr%slQ5_`fv9&FWOs;8aM)1x~_+{1YL+]1!ru]l.UFvhT`yo|ݡ.XRxrsy},ϴwnWb1R[JOחWMiJmCuB4Sh16!qn5f8ewXA:S=Dd5' /&Q8e)0hba7Ն"bxaU% +,SbCħ?8`Pv IDAT~յaog_ԉK'n-ޟ\7 Os2x[#4MR,<<ٚpǔ*zbӚ/ˋL!0{ x8?m;X*b6?ٱ]0 'SP%7ob/۪9Aѧ\6FtB$3zË;߈{άF6tvM ~:<[ܳ|PnRڶm{ljd GRQ.|> F3`h W,_ΖU1Rjj R¼^VmSs)%UmۮMK6Qld/)&҉;?4\Ķ`mf‘<jB08awVƾK)s/EdzapK3D5d@FA!3wq}mVTi㇫q¯ !Dd9qnn P6d*/{"B] t0g}t c>[%:tDӔBlEs̒(zȖkMdtZ}n|{`g3!̠a0|>羟Ҕr.Wb.9׶M۴o_^\-w_чTUZȱf `S) 1Poʢb^ Rsʩ_ Te-<8C}LcHf̥$ȔbUk՘\r.نf5`3 av$9.9_־/Ϣ*</kGhLn֫mlʼ8GsiJ)r>9#@9}&J)\bVUbhr_ JmG8͵Gh+:eBEsXo۶=_0hxk\Мt8`6M؆s^KZ2T" ,LT[9 3!ӈ?~']v Z`M~SWDXk+ww!1un|84N9?+iGV+]5a_B%?h8tXBD<}.YTyR C "lۦn&͛߼OBcmBe\sygEouPKe|P׹0di:3Yd,er٠g8ZJҡB._~!fc/re 笯&8f^H-͍loy-@Ҿn8NĂ!YV6hTmnqeQ>5 ^`k4aSʶem>:(7W|sNtx !hӳ2(L8M0 MlB |>y*/;2{&K }B"{kմ*@15I$T56AUϧ>dcVAVxUFqtlr޻qqRڦHqյ.K$B1߾zu}}e\Sr/T)+^):9!a.paV%ζIV BMZ:O|[i}җe~7V`խ6KL(`QZfH* X^s.hJ6>&\2" TQia: kE[w?.cS&5sϪM&xR#UɩmvX.j)fb:WJ]vx*}<VYV*,' J 3'v^z9u>’rʜ7 #Q!(~syclb%31:K)f5,whV7Oqm+ )^mLS5.Dh1fsDd: vѤvsX_MYn7 6 Y3َw3+4/ɝ%4M%/ݪ?\O8"JS*9Ch<noCIC>=9g|79Xs1zhE]Vs)6B8qZZ4g)e'!:>S"zU3TЉ4FXzpZ"ۿnrY;G38ia@?>>0c 8Dj BD޹CyڶB{]o?&t:KKkmWzڽӗؽm Ma!z$[DC&YܸU3|yπGҢc. U/.V]ߟr.4o *9'@R`nS^.Hܴqk^ސ̸+:Cp~7QUMSL(1u׶ҭ-lk %3fsxWf9P]w|í%~٧WYZ֜3۳ #X@!Ñ%V܂$vT%>!7G+GYDJ ΐ":77q}U@ t{{8YҶg}uPm6|\bۦt:]_]nGM99a@·HZ?w!7+,^qF%~T1 @x~lc C 9Gk8@>( |Eiwpyu1rUK=c bMqlf+4sfs ίV`\~>~p'y ٵ*+t2ooyAsqu4"r1!:G3vĀG.y*p:SN&%9xJh]-*6tn5|xHFWM  cҜz`gN)O8^ܤDē7u4Mr̅cIUK*_ˬͫ2FU4&.|ww'Y86r!Oo'~,Efssss:OOlyK@jkI8Xh @MDJxS,A sbjW5J0ꥧ*( \c1sv]u^9.0m&0_]')%L8bvmr8onnGe؅*@"UQQU="*9t1xJ=@(77wNy$aeDf0 \__~Zs1]{}t6t %OsU>C_v6/iQ <$<4b~ma8Coc( ꈂBk=\|GYAEUpyo~dzA$6xc3KUu9@ UvYY`RщH)eҹ?!\^\Tr%|#9zF)M 0kѐ)ST+iqs%&"⼫[a g}M׆Dy' DKa^2ԶfKZԜ݄n3M8[G9D {׭PB|lcTH&"r<Ujfu46CtlWAc6%f08La?D2=!0VWeR8MY "wP1|>o>dI.@c ȹ{a16!a2uyu: X5|Nnſ{E?翆%Br |"Zf4̴4k///o#8stG`*U$'P%*g-!V܊\zrƑ_{fr3 UmXR?YFD$O*3'r7|l6 Aj,F#sDG1}9D/""SN|{{W"Uv޷k)Mg۳xQ??ɬ&/h* &7܂bQfaJ&X"t1ݢy״ AdT5la`Jl+sDfs8 O97%Tx:><<\79g^mKVi"r<A56q q:mZH <9I+?O"zU22PȲ|16q̌ zAqP#9 ?g T#g>zXsEDصׄ/0}͛w޹k k%c{3i8@p):h%Ud1L 8ui/Q~@c]-AXʄ0Nox8^l.CVnuJi@wڑshi@Dଡ଼\7/&#+Zd$ci[G8sJ/Hőjr;)k Ĭ C?UĄMӪ#gfnLDs1C3(@5(9%¹ O'ЮB9a>l;:O4kg 9 ?Mi?uZ Z1G[S@EK.0\./6śF"L\AUڑSfBWP/DzgBUQ]h_ٹe:PpaQq.ؕ4M0Bۚ:6gkc:?yGv5T{^gim31!w,7IUfmיFŖlM >>==3KӶ,R֫~^um`v/e}4h!?V~1V9}U2sޅk6d:*YRXFEi}NITصfK)_}[߬n?!^g=y?Qny ݪ1(pZٽu[ْ̘N/.W\K QJ{ >-,MYc~fUq|6i./ Ӕ b$ rT 眙ٚ_Ue.‚d -T"TșҪMTAARA^sPXk_JDђ̹P/ 孳8owz@iтXoObeAK )MW 4,^g uoό%z,RŭMtzt4 f% ZqrcS)%C {Mxـ޼+9w^42N?kcjZBzzzz~uFpU(U"·7{U"V a\b؟qA>cEx:wGc/fj!9(0y8.5KœL~}{i|M1 T ]JGV;B~0Q.\ 7M7”LT)r9eI9,7koß ٓ=\3TΌEW;a@$PTȹgUOxw[fIr9="؜!oF,kTYNm2n#]Ve ͸@sqNTK.20#q!ITՑE1Mh~G4k~IB]\㠎G#W=|Nb7li-ie"O)HohoNYm[R9E~2=DLZra]bT aDK-ERY&/ XIB8i2?;B41#a@8}*YpY:G$4M^-]yh^uݒ-RoNibh6❻i/>bjX/("0g ڶi%}ɦIUAЬ!Dq\\ /ٸ}mYL|:] tTţ33739Pe)ݳ)K)msHH* uq.4%]OUhW)'0Ѐ gM%~<K`2W8/`'$@7߼N50дŌz_$#횋ʲ^^]ϔNCͦFmErG*fE 1 |'"^T݌hΣ3SoIC4{sN”rZoV ѷp80$N!ř1B(DX :5x܎+'ӌa@$z"A4Ti\"S)hfcm A]KvѦ9k 2(2TmyOt]Na*z AB8*)'m۵ջasiKhF;{EqE`~0X7sg\,?@WK.XIAyn1/r8j9)Ũϣi0t:`6f&cMbp:N9P$9XkBz:rl?uCEwׯ] Q5*BUrYVuA(Y"g4iO",Wh/xc3ZeB +S[8 sJUqJL@l9a Ҿ(eN9Y"kM۶MbJ{mŝ^Z=j$}37|:dOx1FSЌ?[>q|x,gFfN {ґ I< ׀`S 2[]Nc4fw91VoYSuV23#"7&q w2fp9ci@@\z$bbܬW]'~ZM4MH]dT;۹97hHJiGKlL}NYԭx8d5NShFk:-Ck=ՕnѦ-62dn~W__14NG,ֺ S`)*p/ .,/^t Kt&4OΊY ]̄sg4@ bL/@obwV V ,!7eǒAVo Y8S#B~pJ@}VSrʧݫ_lW98I50@ؿ/ӈp !V#n{x9o -n<~N@ "¤Qrԏ}ߓA|֒9g"0O!䜬sas<vۭn89b kn-,\uf#l6kG9--E|4bJuHfWz":B)$Z$L9?qΈO'cJd9h':XpBhbL֨#9SDϡ * S8߸zu(Y)Bf [}%ZJ9ǜ&O鹅w t|j 1i{sy(@ih>H4Ab2D@ 0Ut409"d@M6@@-^J cO81ͺ YRJλ`{DE땤 4Sfu9sQ>^Bn|7omq qkμ8D5pSHWN@M㝵j2NCT8 ~EZa!C.序33p)K2Jho|)~IZR Լ<;5j׋©WO_vx=ǡZvnLyZHַqAQ8e#.hOc *U8 Y&fRΜRv"Du”])s6uO ѵ1ĹXd6&;gLekQfDxsZiݸL gU\`xr<Ѥ:m:URban]YRN on9h 1(H@+q̹?1F16M<`84F8N=5HD{k= GyGsjO5b$ 9 RXo 9K훷v@9z͂vT՛Mp8Yk}|޿S0믿IC$Gg "nu5e8PkmuhضfrsbB!cbȊuƒYwΙ97S bɨF_ձsN93ަ9jXwP";=>N*NgZ9sLo8fd򔦣V4bYtM8V_FUeQj9a݅M&P'\Oj8&fK&Ӌ vZzko..9&a qY KhuE|I:WO. UQqI<8\{6 S-ŠF|Tנ* c~/ŝ,h"c?=>S֭uz$C"4mu] C:~,u$a̒rYPY)sF Bysm#e:ެWqq1jxkCsޫ`4B)&]`HԨ҆#GU#6Xy/\?'Ű.u#j t'Ehٔ;;`QCn :3$sl~Z+A$4 o݌pc85JYq>P3oY!+OT̨4 iBaxuJD$ I0?ڮ6UXXg}Xk` vjW /AJԫyn;Sk*=I{mh;~T5˜3F2FLmY SІrP~޳MOO[vɔ=;柊r*[a4)@qFHٞz"Z.9%䨺9'݅?쳜YrOOۜyzR ))Rfi?N("vK&Mj=N8>q3CZ#1fZ,W ksmreawDZxe |.b|Vx1>U;(ȑ]A(ZL(pJsz,hڋ~WOœf9o~/~˧0)4NmŐ53iHIY8sv[c4LKHk@:1Fs,Q\E6K\I˗U#ec和yg,Yg4x.Uӵb ,کŦ{2&3O!@IQv)L)Ƙ/o)%2?B;l|<a{"=/Hj;C*Hx8ҧgl9?08qf,ra867{i|qH_BQ&Cѳ|v͉̋??PݪHOdNn"~}}NLBr`<(b4B#8ks)'a8zaFdnT? 믿y^':+fTC+<ca0c{$Zm[㰆{B,rcCm4"b)kB⛠YĒu̱D կY]tlM3A|=Owwos]}6:1Hiϥ#oaTf)8`ɒSww߽0_uwFo?:俾Tv~13O:_"$=)@:| ngCrMڊaBpx)inooMh)a\NS@n :/=O+"N{I@} /_'*5MkKVNs!apughl.E-Bs8i?s@]I-=>杻H;ERD10'ͫ"YG}}/Y)B6޵mcifc ٬[?wx< b! X%\^71mFN!iw0Hr_j[]q:s<_NrN%.W3=s zjj;nۜlTjHHaXX? 2jȺ2Tv1sn5f7?r[ʁ U$ ϯ.{ֶRrs c՗I@d3`az~ US8ga1Tqq~/sS 0X%@iZٔ%QC[G}[<%}#K_]umO$"i8P..8i \+*;:'5Pnai64 Fph{ ͈7OQCh3qKK}s8<ס4\rNãns&kDdƼss |u훶mBo^?nl˗8%SVs bH0n{Gu:BKkۭ"IfB!@4:(U-`20јmhOjW\+߂a1f48YPy6 $RT WUٗoVֲh5i΄ 7 Z.0:$z|6K"V){bƹ;u*Z7hQ/F cCPܦzJ괪"OD-!u}xIafR >3 CNb1:9OcL'kMc9?%ɶs' :fYg??{G\饾\L֭Dx"Rz*"%\a!kAukAa߯bq8޹~t8C1u?Rhڠ^G6^*:<kC?a[2s؟DȊ {0NAj%aⲻ|yvphn/$Rt4~77zbI;HkmOx`ɍWחx,8'YSXTg,2R;frOOzO)眛T?bfBtptl.7m۲pJ7M A8U;7bZ;)lPS积t~Tɧg?:c*2[_|3DnZusaj$տ7M4(Wjr!Ua9w(Ri&kF#cճx "v9A-u: NE~E@+-H8J8^\^T";PtfUX {hM2Xk (Ijg'6g8٣`kۮ3iŢ#¶kych*z@ŢAf G9篮/w)Md@vΊ0̧2Rp~SߒC$,ǮUx"0q!'vqdr$ 1Ds*=Zkmf٬Kcwk3CUAfs !0\\,vbznA$B0 `B6H1JEE4ڶECΐVv̜Rtw4BZJչЏc*iI\_X85@VO٫.59Pu1Zہ COSͯQ 4 V1A}{*snyYҕsF C8nk{rfuBuXh/ّ]"GQRRAfBK%Ds ǧU4!Mt%LY@tE6)iɐiZ/,1&k7BÆ$ BTOOn^a]\lo7_!0I;v9e4RDDөmktidaJ)}_W[ CYnoo沌1 S6Kh5f Ӊ*@i]faE08ݏcOSH1kݽ!"kL$t1Y1cB(䖅w@#A$ k훷޹bmzG a΋nq^EyFGdm]*5L柑~EJcR˟mzj] `o#$-at)ji3f,ß00깅 zn٥QO!c)YِgAչh}cH3OPi(DF3Q^jםƉڶܜG\ʅl@$Yl4k9AeX"5~\ SH!2δ(pe,$r?+?= tPG sq0n6+]@ h@D,bA8-9 Cdt^\]lnRzzچqp8^]_}F[,hif ,+͒*j-_M2pfY╤BIEͯƉsFB"S0Cyvz?/o_OHId>#?ag5y4y#INĹ߭kn>/?\|4@~_~sͫov0om72T8sϪJU'RۮSs9w۝1FfRq{wE'dH5)%f)(7=+ë ]]QN9$9+|% jO|Y%S5sa& BNUJId.6/pr͸3vz-iZ9a*BMӔ甧3;c ]unɉU#iRD8Ƞ> ״P !&]nS]%~zy:>.Y_SJ/TDө~Spmr)'XW,@$#9S:DmgY=?i~81J}(RD<Դrted ǝz7rΧ^Xc^̰DȝPR"d  !̭ ("chgE8s2C)e ΋DzxgĔs~E)tk@\?>m:7_',_@yH/[,\ӊpSا \}% kh&a9sXyOG'2o-i_0W8m3Z} &N!V8jv})d4*|JqDasX%R5D0A, ;jۉrǝ^2x^X:q%%3n{^3BNʧƻ0!̬]\# IDAT%28EMLLxw0 @N!)"\x IR$RQs(-/°/mEuϙisdX2z\fȐbx:?˯mYv5Ƙs}ƍȌLgYvRFEQ.[ aD5h}$ڸ(U%!Al خ̈L(#ƍydujaĜOO.AbL |_H3K_TGRJ+)?E@97&*"TOD9[4h 2GJfm3f8anۧ+]N bŞ0dbf*FXWX~1ǘtlNNN./w9Yk,]hs`/?@!@V+^圖z|yܻsV"{u󍯾Gzsvc 8s00fi<#䈒@X9޻]笝)ǔf ]cP"BJj"a!f !xq1e"4弹ZV mV\__=$]lK !gDF߲o"0z-*B/ќsBl4?. Nh ,<*K9Z.АĤӬb/|/ړOy/Ƅ( QRJe䫫jumu_mo׿//|{?0g"1/sfa3կuvvS"s=|BDPf13d`cM뭵LZsN|uyHa1䓧GˣrNF;0 A[o ø^oB 82ӓS#9AyIzة %2\MX!5,{b1̬"PH@rUoicJ2fk Kmbf=hJ t sb22_Ձ1Ȣ!҆M4rZ]o1wYJS@"P}vFIb@.= Ki@?2CK2neAJ +CHk]yx!0 a D6%l9G)1[:Exo1+hOAVG)+q/t1W2\koQfSz/!QS\7o`@$=zx9al6I„`8Ðb9ڳ=hJI 8&Lqu1 0_ǐ mL]b෾|m39٘rn7)&n O>}rZ!䘸Xy7u9?Kd,|l]k "~뵱]$ƪWn3MK)eRރ@Q[Z˜/YY;zV//EǤ0<=ɅE?zr|t]Suza \~z\bnNCS>zT"Ν:CR1TMR%u H@mVuNj''YDY,_{@D diP2[/<9nP~j V'k rƘ1 RNZϔ% ZpJ;n%90==٢4[NJ?==y'9BH)v=8gd^A zdڬc:tQhgVhylZE -~9,`Lqgb T|1nzDmv= mK9Fˍ1,kzve6@Jr9)u8f_4pE2$, !͜4>W_U$ ^\\!rݬw1EIlbI@nmHe^MZ?}B޻n; ŷ+=yB -ԑ2ۀ1ZkdXXsN9sNB:ܵdUCk=3#zA}5b\"]ۤeAku=2q# b/,DFU6B=o0J#yo/W`s 6RSwM 8f1gMd#Cmq1AZ,$iR8HLAeՖu 4Bz:+E"dGhK[眣8#ק;rzc1LƼxq^  r>usn1~ K2{k$1XDc ~f9+'H Dݏ K+!Da.1j7NuX*6xzr2u٬hQ򔗶n1 0 ~ aZ]]C4S$D@aw4ƸVx S~81II59a*zڒqSJϟ>}'v!ʬkgsyC;os9Wэ5[Zo2vb13Yi, AU1'~v+,SĘEYH$"ju8oN9 Zgzc{w??xpy/w܁emsOu"Ħ1r.D%=y2{ƌABh_dqdr6DD|RDC7!4pZAAȸKQ44 QwM&ckTB"ilҏJ] z_3'B_5r`>#p:wWu?9ˋK@i:v-P 2R;`/WM#2'=dh+#B9 v{<0VjR泬}HRsXoYoB٬imfNJ}~'"mz= NR$؆7b;Su-޻n<>cqT.yusYmΙ$;uё5KMDYS*eV ж-G~Zlꯆo:9={  5!gc@(Lf g}%ť֒)jyqqyOol״i8 UWf_RQUU.V}Yݳ{2T}\@@dS㳧/GDHF:0B%"Y^J]!RNXdT CxTJx61QR8H[Ι?oCe8@?99%( ˞gBD@|a 04M%hԝ7 &Qd9}/NiU ԰jp(_y^v=bEcdcК}^k^XgyLɐ@AJPKazs^@QD(HATud7T5bWIffQvLv$>?;mw* ͻj= Ci:s.S̳y{|wy8 ZAg]UJb)ۤq't|Pcv՚*fS !:CH3 rWW̬6W+do{wsJz*ī%w;kur*a9RQEx?5&gbǒbj\cj[`N:GpTTktzdrZgC^1m-޵aN HYe;QwV=IXNcfc++F1 슩0^ 04mv:pHnh+iS"b64NўcEl#HefgKZqj>3֮z~XjGFVƠa<t>X+mS/!<2ZS |dau$FDB)Vp9; ѿwzXl4{w!>*+%JjX:}62aZ5MMPP7sN9@CeBuh9; mLGY?|iלJ X=H(M IR:$x4 oZu*0BL1o6GGGsBn>B-K[OCO9Uօ9K$N/v [!#)H!6z 125s4nD3dMqC5":XD ^v,.)xvY;:)J}J)syO1Asd,cӟd<-b(ɰL]ӭꗈK 4uHg1ø:Od^{ݻtB5Ar۶o-Gsb4!K-!<ݳ@EJ8X~IE1~蹪1bմM말ryp.sM:=Zcgk?hnB&rň2[c/=WI?,l~8 0>efNy]y=³>ɏ?ԞԻ,)}/! FtH̸&䜥$:"BB[{E2 ɐ99>Ͻݶ>{ܿw7v+sy gȠu9[JcǙvEZSJקڢhɢ7f9gBjv:^*ާĉwD1:Zi'˔ UE%ݬmj0& IYv1&4 EbN Cl uR7oN魷t~˫0fEYD.=l-|A2P(q5qMp([ @ E€d@T ʶ$핔!Sos@==c!4l%ǃ{#@{pCwѬ޽۷fV i߻۶mN18u@B8lX{}ow`&%?9OrL5]k bjטYcƆ1m[@!׫shch[|F"'mnpy,"PAS!Sr4ZڷPx.G%xP#|XMB A~u?q2 MN9iF<{C_yn9dV v!s5~k_y7tlj׫5^_R\36zU.^1)[E唇WF۵q xuuM1FCm^jL)6} bc)NZ/H[.2KSHzI;YUu5Cw~w>O_8qW}J"zF飡}n=SHU5Ф$:mT`2=Sƚnψ4دsNU<5Nu %T@9 0>RDhn6c Vc`Y $ ?>7)rͺ&ҖZ2JoX3wL1Xu{8] 9-GSǾߥ8λY1"ܽs[kzTlSٶm,P#}kr 1W7jC :v ZtvzI.ڜrہ!YUZ۷o2iI_Qq.ݐRzG?!*Hzq+K` Q0ӧuHXkw.|rmYaF[0L9d bLz)g]/EҐS:9=&AOԛ)1ĢUKo6!5 Oֲa'Q$DR& 2 wƦkWG?Vgyy%S@P٤$ `!$Rr R~Q Kg4&2HrT15)0+y +ɑSt^zZ62n7ObLYg?Sʆ gN)v<F-59gcM5N+Y's d'ʔLS_B̩@u\'f)0 :ƫw/~;ש|FLfs(&HœRҽLWywoٷRJ}?^[e7$v9a,7Jt]BL9sf@2duwԮ]?~Ot)irYYf!"ݬ}:=#E_Kp+ܻwryVϪ LuY![L8g@hTiUF g昘su  1%(HZ.y8D@W[<ҽYa8!̴@) 2 A$,)5'?YIkCƚD +E=J(!2P T?JKBJ"|Q0 Qbݬ<3sXSʳI) ܋J[|sN)ϢpcL!_<;Ss.4#ܸ@iS_cA&4,DFAm"1dQCM9h4-¶IJMS90 A5s8kg,1ɰYo18|1׷cL) i|eyRFV%HRrS,WO&T0a ]תFQͤ=)7o:<~-SPeeb=Ƹ)3[k4ZXK1$y6=^X/ )jbY4M$9D0NBE[Р!YuKb "m" (.72&N1&' XbC"̜̰٬G,0 | %#JZd9c5;tx`mڋD(Amm"r$Ǐ>r:g+s){)f3)ۍ.7OC?t. q5ZbJjSc}ҶMI@0E,,w'Jk.Z!*EJ1ʨ˟*Pxyzzj{ O>ySrW_qOFHkU1٬7ƒf#,.8%Kā22Ur[F" ch<NKau&qu3*Brm=2蝭y.#DΙ Q͝:ѾD)U!ھc^T׭Q%1fQ%XS7VD|P2XIU#XqmԢHY။mVpJ]zæd p(2%i#ACo_5%kz{uMqZ3o7Yۻ6B̻nr v}a]hzEuvw]+l *1k~I>=V+%SqyBRG2)['ڛ10/1m۝[9mepnJkʡtj;pbç3.n-tNnJR8uMZ*:-pݝŴlb*Ume7GpT1/!295O!ӟ%rg%9eެ9kXV[²ZTCu9EͅFUi"yJ s>=9MÆSPe RD崻>G`Sd 8gu`v34Mc-ť%aYjrK8!-ܿk qR aȉpWg &I\7B&<`欂<j!5aNI`j?&B"ؓ5,:}Hs1o'Y~;pU2=*bL%}o{-p<.T SM94βHLiI)cYuC9%5ذm)jEL(VW ID@RTjG#./@$A''cRJJț,ӵ-M]j((Phm:Kl0kՌh 9n,L֫s4D8 c?H^Q a)3sΜKH]y*RBA nݽsw[Y4%YxYNchdo|7먨Pq*۞EE3ob9Zw3^Jіs9oB@L)!@QDqF[}_0+VOnq-eCWuXgcL2h =1B3g~>("')%kM6Kd"4LjyZzȅBB2@D`ᾯ?Ս9ʴז42qRN:xꚅwS9f r_;ߨueP'IGmJ |~?/__[,fS<aIs!@~sE(Y6G4UZ`{at"s^.SN)2h3l"74޴Xuz%.n𗡜fH$I37mֳ=bI*hZs8g[lKrxfSo^ M'EmA5ն7O@{D!=A=x:Q_9=M<`?*ϖcL1D%jUgmSgk`"Lc6g9gCnH2ĉ 81Dռk?~SlWP^-gMXzO졦q7ucW`s.F;fFd PRS޻[N' 1KG{kb "8OR:}O'spϛ+w4bq28!1d=\7'P۷ƿ_zU˜RʜL,)|^{_g///^כӄE)8'afZ_#8 bU-3!fsyu=:XTxT=kWDIlyJ9Z E$Ӷ&="b qg_;EiDJd4"3ګbd!RXo*.~@?$&Xr95룶T^8IAP#iO}lTWqA=QV=>$ٮYAnHa/nB#nK8|1ohͺvuC甘}NGsjdCqk`YzGۀT"޾{g\2F'hc~LAg=|8 iё!mfkB&YWT[er;R0Q]3&A%|uyǭ 9* ^4:^}SBsڣbbcncSscwƐ^wֵjT͆}dc^}uV%7}hXk9KNɒ.iYZe)%͍alZ_aJ,X~lR,]EIR."XD QjE5 mWzNQ'1{Z|VG"g ^o&pJu&|Y&\2PΓUU8%lp8J U8V@טpYkjZ#=kRgZa!{vv4Cv ?dow<cYڶQf䒌ۉ0!7Dy]7 nE*uv_,?l|0ZBBHҾӌqԗR \?(DBCb !nf`IpL1 >=9}+h-9M?Q}- *ŴS=pc{oX1?JxgZ]P/e+/ejh.;}߫PuʗbM8sٖ$~?п_ov]'%sf7w̼򴲨Y sE, ˣ%pf@B b;oD=Sb(+4)5HoJRe(dC eg@vcl!zn7h !%KIW(bVb>@Q@ra*-3}` 7SNz8s9se)yM80LA9ZIuVD㴙GmkM۶sП8/sAfbsɡG ?;BL9֗۶}lK$r+qd:8?=xpOK=kmaۅ)'.j$"a?7ƨ~0\o։rWˇ] SR)i'(1//W>,q#l%C8gYu18^*׆adiqظԣ{%I^GG A.S NféfP'ƘmNR0e2 6^j%ǰnc;1K c >G}Ua ok͜(B<19!_NO>yː"鬑H,bҏ5fZ$N Qš4Q]ő*i8=dsP+1LQA@Ұîi[ Cϟ8;?ǚFISʺ٨PS SJ`dXJUeU}:կLp%bJYs$mX`Mz'VzQ5Hk H*s,Wiu2 RV9c_흟 fiw~Wl6{>IJԏi|4m[e7W]]]"! su xn5}/aIB>ta Q !Lr0Qh^,/˷  v\!ɐYy65#dz4@hqrz|h.1fmm 4ß1@l>s%lS.i\l=nW׊q(uƩ ">!& 8¬b)$3g.':Sb aQEc9%5cHJ!mh6b tެ]_Pŷ Jd $f??1Ϋ>4vUUOؖgV/?3# 뜲DRu" SRy_uf8>Ydǧ}F:匪4fSUzڡ*qj[mSeK a=jш PN*)-y)n(iN`J'mЯ%JlF8 gy+W+E)Tra?|cC4V2?޽sdi}7G "wo]7Ok.b c0Xcqc*gy J )<{T$-$X h|6#Q!kKB,#c˫[1IɨEN*|B̫QGA}*$tέgeNJBnFP0c}Ja3JaԄIrn1o.U"❇dm\Eh =}/}ɷ[+t[t4B}wzRD$qNvi)M}*9[kpBڊ6mbcmKV!m۲p` QKҼ@JkPH3NUH`8njJ8g}:&D``9CK9$#5m8'ݾI>z;C%{hh"v 8g}?4MÉ @J73qfmqJy"PsVsaHb40x )N/*hta0Fdqb 5;Z.Y;پpI ֧QbL)MSJ"luQQ#4 YcIo`1?D`M;܇̬tk1㜝w7S?,ZN U _1\nW_YYCPel9V zs)s9{)?Qu̩{]Ԛ8)v9x֭/]Z&49q.?XsttWll lkƵ+oSjVg LF{%ݻˣbX/O٬evkf\]-5Mx纶?u:k[Zm/1zn(29+`h/Ԇ5{%8WAJBZ8c ό3IG(wc/a95 [7Y`RhEamw=n qm[BMmbJzfŏnhژh|efS, 5g ÈlHYP,[? ` Y1g+pn<^G4}JG5,NeY$LΒ;|ÏEp{V`7##R1u]Ӵ`@Tu94M{mMn74 n#p?ck g/?(_r49'`AF * !v_ Zsv-3[͙'a/.4&tuyfB΂ /_^9j`fdrn!s/f2%@ [mֺk䔕Qѳ'/G+ %1s6Coqg:7DwXbE!#F. qP XO3YIt+^L5 M(]׫ͽw״mRW 6 TZf=qCǧmCxP[8 { Hf)nzQi%>\pFcd׾u|⿫[n#svGD+9cLNrK0]fUa Řq4dRN4)/-kb7 !(1@c*Ofkr9_'bPQ~t¢LcO֭֫wݽ8?^_j =T~V-T'Qh ʌjuId4k"g;VXIH& @GD%` @4o͵mfCYb (IJ;@KA3MЛBmv@=!2 %׿4S)eVrDd}Sn&Ֆ83MƘ*.d/I}V9d 3,&|aN\%X\O.kN᳓Qi@YdGv81y睯8em"C^o?}nǙ~RP!}qk<E {)co}Q@"swhW7iwUzr$DlSټ[u%׍8v;hOJ !CW|"_::> c 3" vMAQ)b 3e*V&`{4~ZP9JQ%޹cM-ˎLo4.#AER"%@2MJƷ()M2fjZ*MFDC@@m"mt9^@RU,#'@fčs=ۗ/  2 7n_.%"ZkdSh. Mt HJYD q&Y_]]/fs0Q@cUt<泹<{jHճ#nB&4C#Zի~8vTQkzS08 OF]ǟ}lKU1"U;BLiQ¸pjB')}F怠HR1DUYS{g V?̠& %4Ps4}Fü[h{ O4.B]y\* )\J/^\}𓏮on w6쓪RaŔƘPBZ֎r͊Sb0]9a?.C9w]r^..1?m)ێnN@U磎*xrek~bmᦾv;r7!`GBo}D5 y?&wp~vlȈPeܡ|6+xͯ>z$8pO:2dfDDr|uGZƢBu>Zt2]jh='啩E\21u]?9<9HI xկ~?"DɦtaCJ?; 2j`ԖPz?VƱqdS&gC4k+]"s^ow{j;{h ٓ)^SCz[q`5>h3SZ|^޻mwTTV?U{B06fUt6jJ!ri<Te4Ę@qy軙J`/L߬z$ǩ|%)8FI Nbg7p.ujϳſ~s8 DJ%T]H1eq>_vRJ5vRh-%"'r8?(UK =0i8z& :["zN<׍~X,ҐBr;Gѿa@s ` 1qMls!h=ј7D!b؇wb̥"Qa*v72~jVJ]_]/sb!Z"f3B`jU+R!٫@ld1HAD凇n"Ղ!Xd09Zl Oՙ!8ZW* Ѱ=SyB1gD"<cll1a0 ]W[(g˳~_rI9}_C oT89R b|ӷ*8^9i@4){:7t)0$3%&*4 <~󉪂nC~wz 58\\1bY?ctV:(NhVkq UQyz<W]kRx{{ˁU%("*g ]ۿ;h-9i(9 ŇGTp wv%Y?4VnZ/Upqq~[SJ׷^:?_;Q]|'T0"Pڤ^gwM T怗#3`ZhJ$~ѣD(R]J|yqq\/Cn8;vF@Qŧ#nZF7z1OR$qܺ)ԦE:FulrrBFG]XOuON&&1_˾tۮcNK' kh"N1( IH rnsnޟ?\03o_] C.;c[.?|1n'\<~uv=|+Xq)cR;y[!? WH45}X X L5VGAJbq>=7c0 gEĜW"RKe&, j hH9RaAfe۔hoN}Bbr(03?ίkb;Iv(Nhf$B_<sU5l AaӫWWQ-]9.N);دrrU>=YIU6# Rb{FrȟuN7\Cǟ^\vr: D]}g~?4r:v6fgydZڮkOA<-G9^+f]S:tZJyoL 6j'vܜu ]U Alcn €7|I!XcJuRC?Z؟ `&Bpne=1pcqRBlMBT.F%V՛vg`rΖnHr)}߇ͶB!J`V5"]f>LF_CYS* ϊs7i@ϯ2*D7#Z#3jkdV0ď> b~wwIo<~n7 v{wWÐ%kK1N(w2s48әRw}Qժ֖kO*q, Y,~,NKʥC.1 0g 1_~_%@TMLՕگʫWW?Ꮱe0;ET~`N!c!2~/ݿw[fۂޮnoV2 "]7J}셔bެ~/C6=TOwrdafD#,*&sbmUB0Hp JD#V!*c'"wb N0q*"x%uJ ]ڣ;+XK Lj9+rj-!tɉƎrT2bIRuUl<WTLm bp$#jI 4AИS"Uj-x,# p8]5Bȹ~_,1vd"iƏc6.ǝ,(t "1:FtR#W~XlL) 3Yjl6<`Tnod LD n)gkVy3 ` N $]q7K|KΫW/ZkJkuܰvJPW@|>'bfo|{.Ps"Iv\u1 $no~/^җng<_穡TK4,Ej)PDWWWˋV "3zDbJ!ϑJ&@Cl5cd& '$WY'ǿn):ʇ)ɭY@lP %"L8Fxޅ_aw )]HX,Ԁnnoݻ胏Ϟ={-YB\)q0wxT٬W5y%T/xGqi~;5SH4"l=S 9O>_0ځ-hm;6ݵ@^u|Ddڂ|D9r!nG@zvp?ϖvY)!!f`mnjH&`̼ZΟ?\ޮ8t"ShR뤖:Q+"-QUbhSTNjggaŰvy>;՚#RRlzP" qjHa%~7.va!`v fYm(3rK*7(UfSku]ΟFכb3B(0Pj !HU $";Je<*TDsJzruw33$AU?j$FʸLW;& v1Z\h'rb=*DDRt]|?cۏOt;/c9gʖZ_ϊ_giK><,J;3Y)%8(nbB1bDt˗UΩV!]K.qqryR:SX%G' 3iiHw]Ocu'V;ᓵMS76:*}_X-ZKk}@]RJl>#tGoY( C\se>;Q,(Rҷ^ `V!Ri̿A vWR)uHwp0$'N&3#F5Dp"QbtnŋˋHD́[s@0&WU?;m Y^,Ά):W3@󺬢C~_ޮJ/fZB[wSK׾Y~Ӷљr8cpG2ɖyE [B30EFl\X,^*<{jV2Ino.]co~>|7K=|j-X]ԘT ֊Hy!*z5ޢV&5Ǒ\w:o0,%@Dvg<uD|Ç޻^dydZry#D ̐DLU])٬Wjlr; p@vgggg]2m"jKaұpwԮBibJ-7{HR"uJ-6͗DLd_W/?+NєF'CZrl_sn2P! CZ.9cJ3XU5"9*MAD_Ͼ>|@(0@;D3$Ip<>≌?=aG('pB]daG,PԳNݙ}<V5O1gx3O{poƟ[!Ƈ߿7qـK.j8`{@ȩ`s x7A&y7nw+SQaR?81LLUOt;FSk-G d5z#R  3W&Ub"nXcf@ypwNfs SӒ~<'L*'&/.Ν.y\4/KW#!Dޙe#|0bM121mM  cd&8a?YNM\m Av緾ߺY;CKHF=',\撻Q &ZOO?m4wvkFĄR~>*4~tZ3!B9U)c =??ﺰnE|`i?fWwna?x2n,f.jΫa8<`;+ƳQWLJug)̖|“'V'>Ϲ}U??|5dnDj`'7@;Y|T<_$NظP1c1zJ/bxpvf9ݐ5R||yP} ÐKͷlV딳D|vEݿ8 QUDib}a6}@ͥ޽W/jZrIDOug1f8^1 ҃_x\.o6f:lz!Ǯ; ɷ)HXJmjDO^oBO}{uzZ*t}WJ9??w4{" hiw}G 79pjZURJ1D]Ϳ7ϊϷ2tL.^]_yvnLu]blq`{⦅lq*-Wķ^W; iӣ!q>Rl6oooCSZ\; "mQI U?@D!2 XI!!  /_|:0Bq#-_|oXp`?wzެL}sp*Jb^.>6pGYՅ8Q~`ԫH`<ӌ[`ɰnډNbF TTg]_KUY͊y=}YMzss; sO}-fv~Ubok/>TR@Kh7HCOpu(|<`{Wf @w9g+*/9w?Ol>? '2*llfS(Pu>ͭÛҐ..C ~jRҘbrʩH-}ߗR/c HB "/|/_'3QEU.p?~W>ӗ7+'_]݀:Jp&L]䔫}v;z;'2,"Oqb|m閄iDT_pRN}~KC ev G0zU0_,S?}w-~e/_,."Z,fXK-/X]`&R??WYyWqw:}ֳg/(*o󗱋,mbYoOA"Ϣ]o;Z@6ݕNT t(N L#ZU?!)*q nf3  sTk>pBQPaz1 b[fnR,a1ΗE`f"vQZk}7_?o6!i>1uD[DÑZ4aSOyu/M"O=$UZɍF$91 l_Rku4{Dv5kjVE<ƓcuGG#a5PRj bp,?UD쭷|f#z2`;4&0jΣ5b׀䭇F"`` zO<>hb1sB;RUyUDRj`jMWUT QuUg DfR迫r_ɗebٖĝ>íH8j$"t15y1w,m{JR8Up? ÐY7sl\߸<1~}fZŧWaQiNEbx4`R0$-}H193'$R?hw~Z}\s&$NٽO*n`0Z6T%ĴYomD_&Qmgo7|H{%*2sZ⒙ 6=+yRsl7Ԙ .(h )"l7ƨ&*81rJL0B_.ZkQbATE y\ValX0яB[B a6s)#l֗R'9"\ܔ&DИ? w׫u#w -`EXvsd]M *{FwG/vwgY)2X|,n 6 2C>b!0ad#TConlu&SU uyѩx4Ԋ0$HvfLjr>ekU{L3D_3Lo$RX=X;So|^_]T\a8Rps 2 n3H)iujbfUl6H!n ?DCet-35<85hir⦒Ƃ?RP3.LǓWMӐj#>j?/ w{DTQbF&`&Ásp:a_\ o/ˡcFf ^u}NF(xt2ujcAL]Ⱦn1\Yɓ)w1"bYJb.f>x#׬S`Fu2fOиqj;M1$ܧ!kÇGq,vz޹ªTԜY<T:La "f21!zCřRjx唆4@ݬ8nVkR) Ζ) d0\Y&/f)Yѹ#pMo݊˿s'5C* E׆>eg r.Pr2RGUv_03roScŁy֋/onVK󘞬qZrm4ZጪZ\ig[υ87[f_@-fݮwRn B3h"J/3+UT]]l?Ud S&=J9ܦLAMh#)wyf?[Gӡ#r{v G _Dòiu*uZ(PBw_ n@MI,TJlvgg!\f5L@Dפ8બi" o铧k1Ҝs}uwX,^BBz7j}VmORڴԃOc iwO4[ݬ=~C-R$~$ #,MCw^mdl:.4OwEJ5bNMjj X.dP/z0c:4 7˿+{!f#{l㱿N ?Z_H :dՌq ggj:"hKKHw]33LvhP;U:s@UAR*~}ǏA1`mʈf jch[hOl*ݥŌ:<10$*HdURC\ ",Df!pF f(q}sT@,cwEk_ڗϊ3q t>3lwPn1w? p:ryq1j:VkAWHhC~޽k^T ;6d满'y>`Uduv3$dz# $R6^J݊'^nK {p̺U}Opu"*1v[ݮq4>i-dxG!,4F?lAD7׷xyOoopшjf܂po뢢nwh+?o0#yTr4BIK~"oԑuS& 4#D6-aϟw]1pb2zeBk٬99~nW7i5cRܗԾQ"֕*b Z),&˄+,-4\4gϟ%K)ZwO;] 0>zx{v'&uvzSj^m7[Z\>6T){= ;ISgRx.G{ X 1YwN04} ڠ)~чLY+: .[" b[ڔdwBUq㕷i80֜②ĬD%FD΢Z朻.*\ogBB@\Jq]M,IJ`bkPE\Ӧ*C)9T{%!4~,qLa.wǟiS"ifVqB+6RD3rg ~q[_nිIy Y.bZ\>ӉDĹW|>#ĝȨ|c!;Q3lx r9x{DVл6麮B.koS!F* 0c}ND N􋔚4%7)`.|e-˫~0uݞ.qjĭcx odtm96ت*GEDng]Jn0 BJGN-%.2Q)q`; Jހ0C$ъDDTvf"``d.$).* Y HATK 1ZiLk !4M}MD;`]1Ef{L}'Q@ 1Uŀ똋mh| U5*zcFqÐT~} qȥqbtgE)ou2{c#uCf 6*Q D MvPq&Vīc{~vl\<}N>'HsbʕReRE [ٶsa">xlz2cjj3ݍł. ]q{t\f3fӯjj9Zi0#m7 CZ.N6%w>|<{sVpo!!"#b\KO>ytLܛM1Gpͥn0f? ģ=:S&r+8c0=yٳ9Z E.2bTZc^U|&iamd9B`:% ZSGD,dG‰0B Ug3:jmjKlϘZlR f12G؜GPri_uYSƨ|rn4IJYƩ/!KT6'O>+??N 5Yx1tw\lNȆ#.ʚ8tiő+BCZRmek@[. |{?"vA{l&R u*5[ fvo)d0ګn>xf9Pk}ǟ<;jfo!>nw}y8 0Ze P&Dh|ɛoՄ7ż3> vJu}]uڄoٳdBrqKlztjZ7sU{R:Ka*DTԀ}H>1KZOQɥbjv.Sg~RB")TkgU%y:bUb$&fjՋ;SPAm{wdBBy9p-!X|?s)Aa 4cb5iBf*BNi|i9c鴠l^P DzWTŮm5FNi3[A53ׅGsƽi;s2ԯ??Q(];ksG4bC5$Q6ytwr <\X+ !wH91f3QrFD7`"fi.a)~aJj)}RJ)e| `o՝ Nt:vmn' 6]em1^>|^$s *q/.XTQL)q1ƾ뺮oyZW09 U}b*Q1O0U+wKC %qifUuʑpj8#]-p0}o11pWn7ļ\,Vh7~`T#b"?U4vTc>@r|Xz ZՇ9mV>d#F t6ʧq`Nɴ#_FR,$ B$<™!_]l~9jB <*ԑjWkBr? PEDdXY$hi`=Dln߹øB;gbғGp:(ISfiUVD D5nW*  =E}$F]ؗ|K #˵=,(xxqq_xc˳yg:?T&MS %wN{@ysȎw6|B$u*MBJ)J~8$mH$yLd] *E@,1![T*OIJ>9oajv'"j><'[IQ〇et:tnk n7FZ)F< `j=J:}}rVchYu@;` MIDs6Rtbk).>kJ?J4D޿/%!Zcʂ"&^ss$!x@ BT/Yڐ֡`tҭ+>+Z) ( k-^D1ur]ZK譌nғRX b.k9AQL38ZdGeYJ䵱сr1Ip]`) l6[ {WT9#K0 sɑ P`"pTP VJH@pcu#' j` )wh!@x01Η{!Տ|1ųgҍV>6*8 tj˷@$3"' Rdv=Zo.;Bvh,,e=8guBJɪMM n=|<xj,68G8ˍkU12'n5y/؜7y%h87@i-SŠج7`4.K}gFk31a!{[vY !fqc?7 v !U/ Nf+L2O;8閨#b'h ͽwsg (6*%rUdCnGJ)ƥpKR6QyP B?#GStcE .\ 9,%:@Ep,& *#؇!m⺽{__sfz=# l=P$GKw6)^rѯn ?Q+K1R\knhG+ޥGHDnz:dy/>f:uOeFjsp }zhO Md@x_cHZ@D ab >Z薽!)n!+9[4M.ŧH߸}{(ym1lYQ7B e\?7+,t8n(5:ТZB%RYPLKvgDѹ&r6ش))Aq[o}m3G9&5U(=ճ[c+:b/-g $@w`}[qd ߷eukӌTkBHwn?~p8pјO69װQyZn=z—1Ye~vu, p  ށaGWXc eY$I]L=Yguosζ1E+B`{O>@=MhYs6|4 ! 0)wqK9}`;[XF`9FEyapQkҏ]в1=x]ckCamj %<0E169c qBXz~'Fio7ч#b!PS @#9P};!(:P0hC:IlPђXD7drT /ah\Vns\bOD*TWov j)j\uX%9 "J%$$6M㽷UGhhId<I)fig idYZuW|UUЦXuujW]$*M;g$QAQuh~?'#tcZK#9IlQjIA<1DK`>N{1GQP9yv3K/څα beGp?C`9S-²][ga.H!#ž᧛IU=,xqXo>D9Ttn{\a&}BN &6{#wHs"GmC;DH&n4vkpxHl/;9 qr2O<# AKؒ*Ւ|Bw++PXW~ %p\:d Fq7Cjݤ:>=B3cQyxdtue؃4Ehp(B&JjŭӓzIN&MuQ2ow0[JgR kg-iZ=wfi5rBK: Y]&LdM>(JʣHQ)}&`:*%뺪[UCRP+mԊQʔ: 誰}v!pVE)E^m-rK@]aCÍ'w!i/*\B[EM?j(+1zOEmYFTUEz͚)f{ڐ n癔r/!HBv눘$xV /5tlLɝ;gͶ>_ tꤕ,uqﰷkBk=E^čDZku.Vy3 3vnJJ(j4q2UbrƘ=TٔMFI:^,Hi5RjlbP}YWM c]7k5& 0ğh1y5>^7ZJ9>m7;yi{__UUODDi"PԦ/ne&҆r6g<lI$4JuY-, xݧV1J":.,f9a#u* !e[]or3+&RUU:[ rK%/I),c|>ޭxR \U@猛7irޝ?Z;GA]7Xch)ĭ[UUۖV=~4K~2۟}v#n~ܓ5uaa@`\`Ub9=Ucx9M0n>Nu@BGݾ$ 6lb0hcQՖDhwN%j~|o>@J1|#TZ)XGj5RgX۵hK`q(~k<^ LXy޺ur~~ɦX缐=x"~cD&J1%1MbP4.t6M ѳg/-D! i@5Y}Ћ"`d_;00V#P!"5+Gfxy_"w CtVf.Cz@5oeUu3N,隞K$GK![.l@8;;N'ϟ Py (^MRn@JD_W72^/ (?9떋5/_(inWJRj޼xqaR>y%?*UBh uyܚ/kms*+hjRGmr(j>i ZPv@ ^ yG)#D!p8PJˍvQ~ݾsYc3֦i:O=}"ȥ[tp9|S*"BM弗^B-߼R&nY|D>R$QږlneY9,ED 7)ӧ֖el0<~nBUs4n׫`?r]Do{] !22*I9TE)7{"f[2`{&1ƣF qODhqvh!-7!s#,˝WMm wV;jNkgDr_YzEaPEWU'Ev@DDsLk4~  "OӤjh $|qk|vX׺,L&z7Ǐ࿵YoU< g?.T$\e® wRU|ij8kF"d<#<״fkRAUs/AYQZgR"Vu][Wl6)xnyL'$OxDC yؤ(ULA,j< djC"|(aXl<uU{OCz޶~81sFn!Wt >D;Jbl. 霧VM*-܁Bv1x V1h^Ti!!zCaSt FJ)nwYRH!gut!MmK^% !o1\ȧp0ؗeȡ6sbx"ݽ'9sUfGu?шM!7!s)[I⽯X,@4Ç~;G If3bZ3=y/S^w${Ok!h<'"jsTUpx51귱cě:$FbSkxu RrH`z#TUVF@ gjYUPƓG#6U$J[Bziֻjf:`~@4uH)Z`I&Isr::=6r&KYJ)'r_uޅZ @Di Hj':/r!r^_dZXLA>G le#Eo曯8(HE Vf]R))!rȺf_$i2MOn3o ZF`w@D[K0D ~":*}_/5Dm~+~NQt͗nX!P))RH5$JBF"~_8(DYVa1)[yqJsn\'Iڰ9AQ>.̑<8-UI)5OM]ѥ-Ys[Cy^&E{HQ*)RJ Evm5\Wëv/ 68n(Ż66EJVt>|H)$Z[df@* x<ld׍'PY0v3D&Bfgm(pYֲjcC OZߕJJn %R"zsyq郱g߷4Mr˜i=yoZ0|xϧƽ٣߬UYFDczP (SO=?}1ڳەTbb)TA )Ȳl6 G@,O< ^(}Y8ۼ;9W=[t4w]yV yʋTykmx/X$`%g;mFt:!*x$fo?|gp4~]UUUiΏϾxFiI b4pw>UnC$$Wy2(lD9%y`Pe:>9R)Zoy&O @Ʋ͋|4YI=-lZ23;;!F6VRJjcֱC['JBv "P>Ѻ+j[{Ty%E`5oCgu<qL#[lG,\ZVP`n8LgX"˲ۣjvydy'%I”l `MD絮w~٦i$-G|BR~ޗeUWuwJ%jZaٰ :fzߚ#ccLY’ą~c"c "I=YwI"9]9yOZk5ՠR%Ad;TNL-Rr^w12M,tZQhx,@((sDNˋj'XJ U۵P5,ˢ(,KɊBx<}t2(h<:u{Y mmoHK9/ܾ}yS7劍P$,[y=\)I `">,7f͖?"'OaIAULP||H'Գx3\1V.PT-B|ueZ. s_; |ߗu2^F!5K3pP2#%z" kf_UY&l/#L9tQCʧ{B׉h*<8.hڄX+7*ig],Vպ݃"{!{Fes(z`oX,hLoEE^oّT>~xUF[cl^gy*iu(?W@~Ջ~̊<1D+% v<͕w9'4*$MJ죏 ZB/ B]UJ:k weʋyDd 5M}yx,yu{Ï? YM訮z&h8,`* 2Q7yRvU _ }l̎=M9_׍uZ$Y,VB:Z4)z0,@ͱ5iqƣӕTc2M68:nQaMk,gBKh@bYշ+AVEkͯc?3h=/vAhX@< IDATUn70):ܨXW.#uUdo]b@tiB-dvD\q򇔚vH9ﯠ썤R aoimd)BHQߌvLOpnvRxxrʲT*$,82rWկF'.nԅ9+vw*O)ZȀ nWu)vرmcT޻z&39g //T њ1nKz>;Kc !S%Ϟ=`0t`䚵?ػtmf!K66I4Ɔ9J`/ h6tEާR4΃[ 'hd{D%ٳW]yWQ& _l"L 6>9[..GV`迶M416]KֈZ[q<7-a<,vu{ D n/8N$E}G[wnb>&_?NyhȾ@d;v?P_ 1Gges֘N&p0,ҍ~#쨮u4s&u]o[1w^ P(˺ Kfҭqq]3߳mSmװʦiR1ac֛Rk5sDZr$I@Mmy7h5 ᜕2c^r\kpy<2i2./.xfn2Xkۭs^J9w\<y䛦3/^ TJM /aSk)q6~3﩮6j|>SRZk]U5 2"#F> j~Rբ%v pXn6[\S qZ6&Þ6}2 x:HQ\{L ;9dtqe1-ؼy1V)%i²ݮ$lJ(b4Zo{k|&JJd`&R)9sJ)(2˳0(iaJ 8lZ ,˫F!>>.*(HdPv{y]׋4K$QPxtm@}u@D\P9|myD*n{c_oKu\g5}~aܹacp('6@嘁Av Wuhzpc" Z>v9FOB{ 4 G#yh#ʺ#D%ogor2Z3;^[]lBa( j˫ǏZkӓ񈛞'OW5Q̄m?D%^&„HWNzy)Ams)(3mtkԎB@eMU:כ4I^PJl,Mbφ?k[P`UPt }SWӺ.Ry6τ~ݵԄgfܿӟ~f3NREQxOyF[k`PE~PHڠW6f't(Ieni@4I [6u#@")j:4CvWέ"X ^*2 @}h UN&DR y_v+YS77Du}09 EG[."aОc7n "ڣ8A V~5m$ǘIKǾkL8kݐG'!B0U^ݹs~mwE*,OC+RJJɪF8MeU>b\#rrY6Mw͛wX7o$˿뺪4^C7=Nw{/R2dm%w {89wr|Y-$8%L}&@lڍLӔ! : XNΌoB`6iĶ?K{,M59(Pf/^\Fs{pε؃q :T*%8h@'ruQ;F/X]k3 (p} R6thd ѝgW|b&ZW yenSJ`lw=iZ@ؠ"wK6 )Lsko#D82}]i'wR*-9Έ@k,(`Cot^ݾӧt]h8L&cmTgRHݘ$M@J%̲lᇃAlE޿oƴU sknሼo%ibݩD!B$J֕'y[kZ@RqYOR*͋u*{veO;fqX8 IZ|#>#˃8'ORW]ġk@noz_7M H)<[$LuR7[k!0˳r_z*ثz~ЅxQI(} KX.1\npV౻^sLJ{n4;*6I5/{җ$j8>7IO>4KG*ARòC3B^{CRr_L 隦iҪ몶UWc_h>;9;BR:`|E,X$,Tغ1q7 7q2.؅ l ,H!>{gy1D%~f0(Pc&MAq|/q(N3aM])ubhgi^;D7fu3^4JZ\2(%Y UT]Wn8{X PI)w*Ig{??&InWJF5nS )3چIfXcݺ7xqa/ث% M4REߗi"6<q ԭo,ֻ coE/.㧑8/!D%iفU9쮒Yґ"e} _||c<~ə$Yge zAp$nϳLy%+W?:pz)$Ii ;td캾HJ)k)nڼ1L ,ϫ" gt24у),W˳w߻wyq$x<GWW''s@DyQ O>h$J:;O*5= ٴ+ĉ|bً$~7?$ 7Ymm A i|T%R`ȅ+gYUBfLP`c D*"hn4khs!% Uj{)ejUy/}$|H) ޱ~Í຀$Iyh$F[ZMUo~"MFHIt=!jqˈGǣG/;"CHĮF )pH$*U1M0@{_׍^1 C "K)B)IϞ>lv_ccAm$l1N߿Vz`խ&~b zYX- bQ /x 7GZK/))TTu%'}ɓgM]#wnެ7k^9x'򕶋Ѥ~t)͎ (dTl>zɠ0H֓ݢnQ=˫O_H!./>4̉L6=h%i|=DkmUՌ$I45&lg伫 6LL'[b$Od/ g]Q䧧't4X߹ť)+@d tfs2ӟb:IF1LIlץĻ!bYM냟gۧFrodr@eYyo<-BBnAc QHa9A`̉p%jC6?=g8E!?zَtm/Cyi_u3z o{@QGGfk\pE jcYf-H|_C>^` 7>=7]F*bPwcgs}UF< j#%!::[k'jwgϿuvVE]5vۺfUgzo|HGm_syu鶻մ^;ȲoJe]7 /v+d1 lRjXYcz* G˺`[#zO"[cp+RRH4Mιŏ>:+9WVfet:95<~Z ?)Q!RǏc0Oe\ J"1=3f'%ҵNiz9__ړTmLi†TB%!Ԕ<GqF@Jt5N'4K3(rԠ佋&@R*"ʚC"[,(L K#!˲:K|mimN*)D0{![gӓפG#~THEO/.r79:*!1A%&ih]e١8?ᣇ?y\{lov޸~26t]@i*xqm.OC*L8Pvᰪ*s!x@P bɕs,{r\@^7Wb] ?z+8>??~bZ&,ٕjΑu O^]:"Fvh>TUgs.M);[Mu/vy"B:L!!N'3Jɢ;JIǧlX#@gGwN4Mf'nծ6 &家֝Gѓ/^]^[NN>)Սr cRE)gr29}vaTk4w޹}tXrWo5 ADY:˦RJ& Y_RK N$xa'R'9lOd;ow7x}5ZQej8e_fU2:evF"ϻ#|H lסTHiE.Dqa -HCX?jF`4bOF@jOх(if[\-yRf|LӴ,+kmW +{O{ovI6(|kJ9ڂ!yh JI(e|uy=b8 L& jy!l:yübfiY햗4 d2N֫->;2B'Ѹ9kiR p~lXbZzI1|L_{}.A$ip4gvUNsR;8#~_h4ʊY7>G>}Z-@AME?cUQQ MӺ}žbED@)|5uCu]fSagN'ݑл3a"m&3 k+x&HDA@J)H@`9rm;}_3* /0)sSUA;*ѣmCr8(Q%*˲n?Wveiqd"c{KR pۗe٫v@YC!}f59D x7n=|(˲o}ό3¿WOkmZ;/yUֈ"y!/tZ][cbSn6fijɋTlRRHӖͤ$J7wmt<گdv)!R*9ih<|vo?G>zۗ5wNN(tP޻*I,E~vkk_*ˋv=y7Ƅ Z?yx|tk|xgi&~+}\uc-̲џŇO8///oJNOqѨj, ct(UdBLe_ bY//ܗͦDI `y =QƘj4&Zmsn6jn!VUr(ZWU+y1>=uyqU9^B.A,tB@(tBl,HT%ѣ'nPcL\[" fʤiH!ۃA{Ƙk")7O&ɳg/"%@J\Ӹ-['R sN IDAT(9 SVԁ-8b; (˪wL(Q g=Bob>H:,x @Bdi*'F=ie7⿴ U/?؋GHҺ Ѹ-%briGM-!7Ѽ9eYs9.kݧQ}|q:fO~:N~ӻgwNOF/WjVogpXO^ vL׫eVeY)lۻwofS%孓T<-ޏGû~$Mv]ev4oDxYq+$FkdRԕ`NNOoc^mxdz鱔xr|h],6M,k",MgUYiG nvi$JicJ췡WLTpf[o_|QJJtY%2@ gg^{1z8|"gyYo߾k_h:URZc})DݺurqyO@[gw,ŋˋ˷~cݿt|4ϧY8@L&Gd:x$r: !$IaJieih@Bcuc4I<ژi4hЛH1+5,:. [F7v A)E1@6-aD!h4ɳL*Oz~ מv_,uݳ!{&K<[b=[t Aa#+ɤ!3 h`x#ь&B" `2r9Kl8qjHLwLWrdR)տD(;1>(Ya&E;䰕I{?W9C68`z.gVӽ/xLz`)&1)l(=8?}!+|1s8>92tm$?Ŭ?Eꇯ^h<Ɣ&H>:'Dٳ'iV*MD)k=x>O7w6y"VɓG΄R |!f%VGgGe^i^ :Xro.>u$҅8/.//,j$h(X頖`/iiei%}Rj2v&>o<팇{{ ̲OYיRR-߼|g54mݮrv?,[,f RrRGG+\}أ &eYhN~tc?O!K} Q0'<|!TQYb!e +v0@6z"N+#o7M#Ҕr1㏟ݮ[#=RH{tuig:/Cta"RU5J)|$!u5VO3-nYVy^ݞvD'+jƕ?n42[xww]ɢ,Ͽzݞ{oomOTyQ&Y։z]b>3vi.IR%KQ*)b>zIB۱3<Ykl փp "I)ICa9ʼq,x?I^2EŋWO\l6[v^~M1rKȺdibc(#5-˪qHcl#&Z;yHAD痩b>bx+H{no h o#%U^]ׅ8dq`1'9$C7 ;μK5dr 1* R<ϔ]S4MϮoCqUoF'/~_(yL+lBoñ+DTyiFڶͲkNF PeZ-@JcTJZc؊RJԙTr17jy~z^rH"°S"{x6eZ'Dʛ;3=|ՋGoo7WoY O>XEwe]rf37D0ǫ(_}e1ƖeAZk1h1?H`?XŘS*fJ (2!JrLh6pxrщ6'R 9Hu A)%PN`$@N1":ghB)Oa"/r@>ijI"fOt7ǫ?MӦYVٳX{A[ID*#~:'}SU&OSo_e_~*8:_=pEgBlַ7Jb>/femYcTY^U:^-aWջvZct"7Ƙ_8=;($!6R7L {A^HJDRIPr@:R"pNpbr8Q9:#)n?LR$5 B:tC#6; RB}JcbaPvD0>ˇ7@̇v^|J=8 <>(b SgB"(#%ZK%wk\|c#~I*u'_I!BH,wIGM&RJg"*]uDa81tbLS[Z۵mѣs]Egحϕ"^9R!nmj-et}.yk4MYo6M״^>r6=8+A]wsr^뼓Jkj>ݮn;ӵ!RJ+Zk]mwwBs3M3Ji!F!JN{G{%eYBJdx?` c abbϚ:cΫo"PRB]וev⸵:!TZ'( +ԜM}1feey:q$Ie)Wp,sEQ9)fVYYbO0D8,~pLIҔ缵65#ꃫ18cx/?κ,Kxi5_ މbb$>.rLS<8ǗV <[3ZiutcI9Eץa4GIn8:@e .˓]U߼CuL]onoeE"=裪?89b`kE':A EFmUyYo>?mlycFX`ߙ{d a '$,c̸4>lN8@ʻއݮN:UBEvs~?w^Rxza< tֽ~}e 4ɻ @Cþm鯴Vl!"SIHNDZוb,(%cv9_~l3E9+1=o;3o-RImЯK%$@c~0t4_Q+坟< uw:$ɉ]g1ъyU~kbbU@oY)`a޸>B8VpB^oXg?SR KXD`3_sN'j"v:2Ate R,Ys۲,ZecJeI':MHh:iQXJr#iJtRY9-/zu|RW5BTZy}?R퍒$M$Kd\åV+yr҉ fZ#Eb9c-"IIXmwPRqSd}r/Exc}`q> Mkb sJYYv>R& +B,B h3Jg&'ǻmUUU1;H+*^x1c"OCJ"_sBU]3 Q[PhHe!jWl.FD쌩DJ#~`tTYJn'dQ/Iua?t?p; Վ(?$3F?aҁ81"1el~*7_Oؓid*8OII/1eYdYZUdL4N'n9'${D b(s7z^.ZޞjLach1䱍R\fm@чE]/Ggx%]gwb,L y:idyڦ8+$MP`lv.\4vSt^|Mqذ$@4M>#ܥ9S 9wzv,i6ya·\۶knGpX$0?** ;Tk=ٖolRJ>Y& ^ahD'j(" Qjz8FX|1@(`49Z;'~7.+z@{RV}AK|^{Q/~?J$I]EÅq<)}A*-]ͮps2F^?{?i.38'@CܩEV`g, ) TJ"ϲ,oچyk5ǫ~g''oon>{,Qۛ??7"۶Ae*U D몮8,1O?y5 >(x ,u֏)EsMTi@4!n&#` YufM> 7{mrxh<6("o֑G_z#u]G{gB T]gDŽ #`[hv{VrDK&W-$&"0&ՕH Ʈ 4F!ӜA(=xx>|# A!LT%Ya_Q8G }`aKaX"$IAJG_@%ZZ-<"a۴'9L0ooHNtvYfi\)=\^^e*T`+kHJ"#B]k vutIg:\4!zX̚bw6{4{*Df,τT]U-$I#獱4uS;B1vo^fpRu *tH$d:c^wp$`ӤC x]w],fD$SX]ND9٭`coV!"3ir*~b ǏϥT"M2Qp ? Z 1 :RI 2/@)C`܏ u77 T#{i m9'( &zoC~EU?ד)9a2N1ISpq&+"QD>Db >|p OioU"e?r IځqÈuHzh;7$0늁1+!"peYֶrmw==^׶= ^F2ֶm1mnUu- AHIV'e\]7fsv4tQi6ۍ R):ۦ*Ƹ>յRJkfR~yE1B ~# ;#1L޽bPiZc̃&S]7l"R}$d3HK$`/06N bUEigi"qvfC ij̨PB w2h@ P*iM/M(7NԒ$I"g=h߸q4MqLiIo~x&:8fx(-`Rb"҆<ۿۻ:yo94 z}|1+$m>WfY:mg$I`Lޮ7_×W;~ ˺R, ۶m#B )'NKء.}k8[xeu WUm! IDAT^  EC+-tA,g Gy }-쯗_ sN!!h=Rh'1Lyo SlR&i` svbuWWv]Ek{uO>z*! *XMz[SU79v[}@Yo??"ϲ\iUX)E@- quv͕AJ$ɬ,ڮBum^"o&/(۶nv[`qB ~fG98Ehx/>9'$:3N%BfnW;`lUt; D;c]Ւ {$n<˥zp!0RWw?#~#16RĢȁfrJSj|-,@>G '+__kտ QktM[c R(rjx c~V"HaB9!,M$ !¸6\I G)Pp I \@>Dk]]޹_L(%6MW/}R1b!kO77xt2 biy(V'/^l;NRJ}sZkDi3&j%ۮ ٮE)$¦m9c9>9)bl&\H),EfĿ7qftNCq]yuuyvfhm.Q~O!e6+lv6Y6{K $#AE=3 v q~agm:Ӹ=zcK5,޼c gi&[9لNH)f'|UWW$IOhg5X EGU}'__#L R!%vE ^.yF&Ccw!j,gn m&tRmٟ<y^YS9LBfy4x{" :"N_@lU!x ^*,,?vJ(6ifjg"2.&_߯_ϟodiN4 gO"B3;h7oo<}ҙrX/gۛ[)ţGyY_~,I!7IzVIR1F/ RZqj4T뛛;oLJ7!BWI¡DJ[b~ɟ0Fb:{mWl" ,   { Jb)R;yy= ضz"Aɜz3ouW/w"+IϞC@JȞRI"6$xqc?N ['C9cC  X␪8NGz!e~[Q8BqGb=RJiYJ:e\4ѼCE,7J;sRrȷR R'O?裧o\R^˻\ѣ㣕,x 3(Bش[\mvM-RۦSRy}s>( ڶ`ͦsnhڀˆ=qebBm^8>QT=AܡoX9tZL s=!D)=51 aTAāEo] 1Z#RiGWQ|H e0\J!i_ˁMq;[&Ou,˳_bj|r[d+;::UD'U]VR*!7?/~յNO,z۵x&LҤVBټ4VU-r!˲hFCA IDg)1DRJ~W>ztqq9ںӷv%|#QGGG?xm&h\ >!M]7_~ꫯae#w8:>RHιjW9޾yEO7oov lRy*.2PQ )9]˟,už%!@De= KKI9&<{AKqR79#D"Roc߸J%ZA DZ_t45?XF0]Љ^U40[q4JޒB$''Zk>V*vB|K*Q {` ^):4uYD8:^eYvuycJQ-J,Fbx(^s QpYiuU$ӇnWsRn&<a)}ڮE@P)ʲnwH$`s|Vmek"E^1FtŬBEQ 6(d۶D!U,!෻9\:1C(i*%ʲ\>?ԍRmCg\}TBۻ/wX7gόqm}Re*n$RugYLm֋j\i1ϳ٬Tn6Ͳ4NObvS:0_{Rb.:p+N,wܾ8dpZ@@R c|{O#0(3HX' brVewUt9ZI%>ifY*1:( dnwB̲4C~`Z' N4t(`|#sC$Y&Ip]fBZ!!DR iQ ,ώVJX֦5}Xs @cLɓ k )zDMA}@;;ߖoTGJk) I%"J)ϲyS,~rr\׵'$>1b1gvfY (wH$2tbRj6c{F6<>HDgYvq(M_|%CdCΘnW__,+"g2i*%X1uwaH&}{{oRя~PI)RqrrYoyQ+UΊNtU:skl1X,Vfk !cL }c۶m1\!0Vu^oIb:3ǫ^u':5ּx/>v4M;c///ޣ'I֚WU^@P}&D:RH)ڶmbnvMJ!4I(zS׍q!MtmP@\y3ft< 7CC`]u"F?`6f슡8cB 1ws9ށ =JLg-j4^:F"Rpm90ddzv6AJ@gqօ!)9=)iR 06ȸǙqUP@k  Dfg,Om9h4-NN֦3yi J iJ$zyQ dθR*]-I{ gB0#T.y;t,Mv;9#0_̵oo5Θի(B?hU7b1瀞H0#}w~[EY^ 4IXlZs{B,Ik@)6M;w^p~~ߟ5u1y~ӽO 8Bȶm}䖐R1f}>==Y,J"Zow7Wo~ŗcZ;纮sum˳VRc''1 ?l N뵵:QGI"zsqt]UweY$ga}1 !7?`CO< 8TٽrrLݽb}s) H<ևPA!Pnqz20W{!%Lؑmi!zj|qrqcު]E1*S4===!a {< G:BJk!М[[+['OWZ"O?_vfOMP0I4Mxpz{{_Wv[YcC O_H%۶A]ĕ֜oE|C,v`>䣧OVUu\xg7AD:mӲqEHZ. @1"VUtM(@}"~V9878uODAA= cy"|飝0ڗ$wRiޱ /P|>=v$REy24MXJJ)Y1`?%LiZkO5%(f&=ļA$j6 P8ܛbZk[h">c(҃xRmwwӓ'O/{1`&|:;;fZ!MBQGGˢ,fVćDJ]yw^xeYhcQ-4MnߚQ{QwwweQ =JJqt!xu16a 諀Thk?۟n;g/ad3^yؓEQm fY Љ_UM8>xAS7WW/_Uf1BI̊㣺,nv8_yW.O휿]yw]@D)4FOñĉL`H4!},rx 0{zʄdB`0N5P,9iX-a#R@ 5wa]ohKIJHȼH&!dqcZvmGV@! 3e8 #re >xa:k;`Xڮ{[@1:R$bo=Oyb?}C !X˦軮{GUZbPR$I|& !!@£ϟ|9m~o+/`G?a<G@wnW![c։,xumI'c,hSBӶDDIî&;w!kmӴ'\-(HD/}(ۺڙbPbSLS RB !xU9۬7NRl6c0,ʏ˷mj߾}($nj7?Pi"r7ӊ/w[ؿAc͢wb"_2o 0=5H=mAJAf ;`o#_KZ~;tDޖ9!dOt^Yz|rw8p"g[d DR,O>xXE:.Ӣ&wf,eyʹeQYڵF)u|r䭱]&,G D#z6u=oWVJڦk۶Oo@߉M1zx އCw.8͋RQUuh! QHZ,Y+u[gW'|iݯכ-HD1@1J%)W|"D,KŨ>H |>?:ZUU""ڶ6RG ј舧i.h%xtqAWtI):/GgW׈Mg1 v3B@"Pj/:c_E]b a}v 8&ţk0ԭHtBMIM%eP AH܌)U ۠!){"Jj1?LMBE4MvpUC}>#R,Iv/}<@r=_3,Ju݈===.ge^Z':V7a:RcV?zkYk"F!@@0]}9+zubV8.wjt>pKضl> xtttyyeaLi~}ZD']ˁBH@BA|4 kM |H*h!H,s\`g4S0@Yh8lR~ :&i "hǩ@GDtg,I4+Rуigg{VUMp̊/cf7y7$RX몺:|?DH;C(tN\J!A>:x6OEIN4"8Bgg':uWds\{ ַϿ$< u{DAƘDmyӴ}adY*8q'ӈo%C廢iZ9fi^RI"N VoWLx8hhno1:B L{_E:GcHB;v|Vb[-1F [@QrA]Sti;..vJpVi/d~j cFc_Ʀ/Ƙ$l^Fn&}sOEc|RJ0 lf((RJ0wF5_!a B`")qr1^ fvXD\gGG[|᨝sĿODvPi?D\agEӇ<6ORq|wk5ft07M$KSjnK o[G~pJJw^ Bd|cӴ!Dk8A[}) bD,WG˶6wv&Nz!@"0W`ڕRR:Lgzk,z0_qI`5Gi$IbKIg@,x}N07VO1ofYQuUכ, κoTJYcj˫,Uj!|џK6~<{+>sRօJ2 i: ۬^wgq|="&*%Ooo}mQ`DȪ0A 8a+>φoGD!F>\r~[d`!YxpvRH%Oq#iF2! ;bJIwNIً%3ެɲ:[kt*z% ~BW?ءYz0faE$GH t=,?}f6,[Dý̛7𝵿 _c liZښ?6a^׵uNUIDdQRnBE '>L)殳euCJkT5M'ԹֺOG32esYۧv~D*i"wޏJ)OIetG1|1վprZƘm9=xX){<.ru>JPD![dY5ɘY 3NN6ۭH;QU Tzrݮ}=ykۛ;9WVNH:57!2? +8)[k۝I`D}tD8LrzMM1?5iau"#Ȥz_sH.1;^~=wwF&'!RƘiD 19q+zG0{'bB+ϫ:EcZ\L=go?䓟UU6{"?Aw+fHs(s$1p,3UUPL!8y.]j8;;ݿ|vu};Hs 4Ȭ8Fi]*J?R)6R!ZGR7FepZ}p֝B`giZ6:2"}\V(˲ȟ={m$ HEN;87a7a%h$pJ)Қ#@PK5&ȡdB@iNظ*+aXI!bpS:0Ä"}Їȟܓ(?uĔ_ wnH0 W B/ZGAԬ*C?֤''˷zz}} GI%b!y!KB(Zids}rE'٬Z?_zdUV8 m'@"~/l6R4^>ҁyȤw Xm;UEletzl'0DzӓU۶Mӵ]笃{QA"?.:kR*sH :`n `n3ĝ8ﶛmQdZZn\Elvs{ z#דӓmnw`Dт|%3q@e>j9o i^m!eyط1jYJ)FKhf輻HdƋʀ;"_WՈeGDMUլ\߮yu$ Jֻ~i[QUV@)xeYVɳfmO]FӓnY;n{¬@RK(iJ-L6"OW#jZA:$ ȄJ@C !i aL4 oFEj"!Cx&y|PD Y}zgS5d L , !3m'wC82}!HӸ h^_H!J*r1m;CtJr7$ܮND!Z[ Ϟ='E!UYn,B1J)s`S&iRlىp([?goeY~n.#5tD"~n6Dq" 4u@k]]]kG#TeTeO(1|r99kVpe>dr9W>~?˳l-6("Z9Lȩ&(-`f{0x0k ##'AП'vFL=N/E7mz=8<%޷xcFO'%gHI-+%9qi HD¥REJ9`۲ʲuӹkRYR*~k>wq4&|":"o|`;?im2 MZHDYv;+C׵w>3f\ʒݾnvڪ*?>/R2I$VW/_wW^ DN>X _ qvvZVd"vS|y9-v(s`)z68$eC>ٛo>v7Bf麮(v yKbWlh:yIp< !xKKNNM݈ hhb؄W 8}-b;n8ކ#}.4 '$gk(|{Q?D=3cXch6Xڦ, kmeJCt> ]`m6WR:2)hwm7~|铧G$iDyNV&w{ ""ۺdCY2 κfYu< ȓᄧ!~ͷ"o)Avd1&0E^jm8A~g$Nn MM CZI)Vj6 W7]gb>$.qH`>$0H|ҷw>{뺦!Q7.Z*CH&]5M8ɗxqZԻuhmVe5]_]#fm(o>eUݭm"*d1r &Oٯ<`{EiCӴYs. m8 c102_̗)e u8>)* ‡~O錈:Y7@ fqzN_p֦?tL1,Smz)vΉ^V \6{."]`E Ίz= ey朳VH?鲈D"!Մ +۶?mw`ԬV%3m;\h+"( YiILfi$C fiW壋3T-uuӶh$#Y$Ʈ$ {ۻ]mO+!)Zk013$3$\̰Z-guwA3BY_0^c'rLJqhIMymdE !ޙvMe|Xy J0<:"Tu4q5SFV$Kk3k{NK L-W)ᐃ 9QmÖRf!" <3F"Gg\/'O?}F4R'e _އ9_i/?`?ZV\YnnOym;b>V BZ7*M sH咐Hu];a9%AKt$f/&˖օۻF0l"x[>:|eeU:k6Z/?iv ]ӧOY4_|~iRDHX"PQsJyy/ܩg&L;ND(a[`(%].+ )>AD}JT}TBn0_U`C نy:Ѐ MfW< S^۬HsZi(&~A;ow۶ιmCDYEnێY*r DYl[;kEjo;#8 .~iO۵\'C},чG?NG}trb,VdnoD!PEmH 2eQtQxAO-k)LSU%u|`\Iߨw7lA)Ru ֏-1L]g["=~oo,=}iUU/|/HncmWǏWWW7wZ)"RA PvFHvFHCʲ/J)&d`g`PJ!Q4 2u.1Wx\N̡d&C8"G-reUHH,)Y;¸ƯCZX̥f}q ";eY,ʠγl_ͷ:koooגv5!p`vܭeHDsi(qyWiD'| ĪkuYJdY:Y)oOdu~v$<(-i{jg1s]~_K}(7g˲ D-F|=:'O( *2\̖Y,">vnZl<7w£7.p0n/9M{F^B5s "LYQAsG^z~G!ǣCz'Kq#I4!A0`ҟ,a&bgHhq9($SQ 006NP{xpy;oTosnެכ%]`m2DnFvٳӓ}]_6u 4_̀h@u}-?NjǏv uH#'gbޓM`_#FDk%"F `LkݵmuCkHL$DhlUUU|y9ư:PZV˶$@Q9 o, fv2 Q뽛fUUmR!8dژ@E^Jͭ\]Rg'*W_|9)Jcgxtl<֗@IgB 2wa-IE f gAJi$bUQhdi6]\uO/i]ׁY2î . lȍIs}tr?p<,vpw 6*}]G]Vr+}1W_dBl^~vm+ꬳ{_|1[mU~X|u/p/·~Ȣ'Pie2#Үj>?;?,Z+Rv:VgYC:&C.dŎ9u#l  IDAT3^Y[&:Gvw7={i}Hs{uε#[1W'+"n+[˴ӓӳ(on"ʑh48e8X?$cHD(Ӣ ']?/x]teմmNX y[>sR>SO)$w"֋ysdi)';(;ku!iO~%0P mCHtZUt!?zmoxD$K̟?#bDV7WW/.'u ,<ϯ^_'C=t8"%5QuE<u#|^u#p*ed^J+~*Ec`@r=0yeyVkjd19Ce0͌ђuvX>y駟|[:g~)?ωPR bR@oKa[ m5<9N%"FĈQ.H17PN1&3¡no&gqM, !i rԕ_o `pCc:qg<8!*EBR|1k.ϲ֛-0dyol֛ݭJ{q{1zdb6,ϔR]zGcyPyUU&"$\]_]]G#@E>JO_~uicL!$Y/*0_!?0iH(=Xkr!6:8=+z;M#+M Ro%wZ>zx~睵HL7Ǐ]tmӟij"8ĨTRQ.>оQ8 ?Rfs8w?g/orۻ_9??RZ,d]$H d#_*Zkۦ?S{(2Nebconq8[Q~fMd2ƶd%կ&q'^h9;;_o"D@Hf"h5ϲXg5F֫}& .}xǏ=־|Fa*I\uޣE}Ҙ'sw,YR.ZYB$)]1gT&u,9fy4R>1ýrˆR+P"maS=&C"+02 ԫ=β9ĜB<|aśS\'x„VIZEYk;7{6J٬x}y.X>͹URd#JA?9|hxRZ)ɦ40>Tҋ܌1Yuބib.}eCӑ9ܳ*DZ ƌQCH`|x )Rw}AJkHEGѹ\H1Z+/=yDhf뜏 aj&R:yޤMRԫnmAr z\EIZ+5WD ;vOއ,7>'{hp/kCDl5R}= Ѻ*NOW/^қ;ϳL)ն{ qdQ kp~ŋdJZ! KoOk@J-*wG@N◦1eI >C+=G$"n[+%=VS,nNz/ʜ'O0 KIŋ9m_vmVPI@Zk@U) j,k}/(Dq<5Y[j]{@[ HdԾ/VK?0&wՉSO\QQ"0m4P(&EEKdbz|pULC }{a@xho6m"Na)? CJaH.SXF3p  FJ)"; r`V1 }nK}\ӧOn7Me7wR!5mu|Pw=Z+02g HdeޟyꫯʢҧQ,/֛mt?2y&)wyE^/ dJk!G!(eBms:%2ca };! $麮 L֎1p躮wG9qeU>ztU;A e,Y*+/RDyN\)ϕs&5!1UJ));5Y bz."B$X~yD#b$Ӱ+E&}H3}lL^5(L"eiyCQl>.n}-T") 9xRZgn2yU}լtz2$zwιȁڦbp HM^VUY7׷NR;&a6 f;((;o o.ka1eٻo}WFu<?_i9qq9~rshbDwI}]Ri̍&vyw)0{s3&8$ L!ʖtXm._^l6["F?z'__,#JRU|{urYyęHxɃY$,WLD་}kMJUU}!JxQ׍tJy(L7o )mmfuFYUɽHGp]ar '+0&ӻȢiϟx"EA"`! C>O8X렜a.O7gQ D|A Y( i;#2*Xk#ݸonnnŅϿ@,Λ'WQQm*H> # w!,ޗ<8f_ypo1'6k~½GQ8B&(2m={7$%ҒL+1~= j` Zø&ZiIk%k;k-{hQ{(I⿹P|GahT &1jS1?"4U:G=gfѝO.phaA+噳V##ӅĨ=ʟEJyn̜٣GoRDpGDRJ)cRʺPus0h:O!r`~@v$̲dYEݴwwwl?u]3ǀiGFS[2QaƾCJZ&pYA"J5MgyamEjf(ߢ_;=OT׍ !]Cu:CJQLJy:TMTÏ }Vџ^(ҦU 0F@,) RuyvzrJJM#lY5u[׵lv I!VywG ꩪr>;*;}s Eo00<τVj[7Mo7_?zS4L"HD!^; ӌ!F<ϗ˅dEyVP)qo |"N_:#'ߖGϟ/;Jq[Ǩ 'FM#YhޛӠю, <-r ")h Bb+"LD>D5 7 tmBynywa*no۝hdHy坯z'Ok)Z)toPDy1w.˲rnl9JcCvxIi:X7OS, 38|!EVvm18O?H; ]dL׶mik*N'p}B;keo  (&Z>9wJ5aqO^ aTD΋<&!ibqp8[Yfx6EyxD__{{gOzGP<2;kn\l iP;W C!2R ЯJ I|b"96p1óu΋\;|!7F~V人J>9=UJǏݖeYyQ:\`&#!۬wn@ڋ#DvICoplaGL{~:+kqw!NRH*]`Ƶ`M0!|NONVDZ &eB𗗯gNgB(( "8ȉo.A=Z}%eH=ԾgýN4z0A>2CJ 1DĨngyg}o TJG:ڻ)Mvy7~o>)owTUcEtyy%\j'uj!!$a>#Hvt-'/1Hn@eyK 3>$1q8t!e~{ 1<˖ s~LQ\,:+L$"E>?8"N9&t9L:\(Sk֋Z[ ) > $&\iaeMk[Bn!e\C ~IVb*kvVU˪Ufɲ\ͷfYW?YHҲHTyZa}H$ cf[f0{7^_eI` 98 CGX)RJur`D ,Oۦ#T:q,חMRwe~Ozw(Q! CGri W.3W}߁ܶ-DcOlGYg,3{7@1!яȻwY!G[rE /K2t[$65[J:3;QZ~#ߊcLA[|7C8nbSB,NbiJϨ)K!PaY^J|p8(Jй9~/ z IUJ+}ss" .h 'ra(moЄ&ZZ)YhPxdT"iH0&=(/Vw:P$ɫ֮k;1@xcfttm'|8"q?8zez$8p),z+q#< j䑚ID8;T3prB`sDfݝmM]#QY=ykգi]9MEƓ'eYzmtNWW7WD #TI0)4zM[֔VUƙ|`TPJ#2cfvV&> F4*`1L1{`Fu02a(ڦvjB5ExUJ+m"o^x@DZ'd^EFKlS:^"8c6`ρHa01<w>Mbhf#p)d2!4M zD"f>==qmo%45yQ恹kW}L{-T@z8 {aY";bgzJU23z9gmAkm""q*Ѩ&g8CRJԄ;0q`ir`{3^:E nw.Wm= RІWUH]Yִ(Vmi}wW_z}dω@YpU'NTփp@G.( IDAT x DN#TXbsR$)r{`&R1ΎrWA6@jO9?FN$kmTȲ,20(pȹ#qa}R(~i<"ďR(\.D '%u,%sIiDϖyI0x=m666PnB&)\n+iYUY˲=/ۛKYuX`q$bK8O|3B$z筵0jf=@JLB"TI`8 ) ӫy^َ^GIQy$YKkP?~d{}uY#Axe/^@@I0u 'SAZ!D9C-\icHzO9</q!RZ e2^o@a ^P \1Bw4Mkόl[~_;?xۻ'|*B4B$.{HE%>1&HF'/C&KC>{"LemZ0Nuc΀8U>n$3J,RuΉ?SIq"60/w*/GN S&AN6@zJE{k8h$ ')cK 4̬"RQZ,uݬVz45+dEB_Po]@y*bÜ( ؿ1kzgR433r|``ȕc6urwKkLerl1sֵM}vk|>{wիW.Ug@"aR KV=(W/_8~Ի:1ړ+Q$"8EV/Fnlvss#Ķ넣0ZC$"!! z*Rjx= ~*NB8G#C®ڶaO^v10ޥA:|`\6MS׵>> sAҲ,[NZb5ժi4cLtFH"5w'q"Yi5"A"F`C~=E.Їq 9Y-XZ59jc`Yۯ?q`LH p7WUu1Ejd zyBh\M;6ZiBc!Z1 9VNOݝҪ*Ki8=Ymw>,`#"xH κ3i%Äy7C(U@\t?]?UPi30 LGN)! T4򤣺8zC"MbHiS`Rnmڐ8~׎cy11/:E#_4ˇG 8v%G__eκ,qZI { \eYZ}ݴ(^.noJ9u8/.f ~Oc.!r q\0] iXKhW_'?'f^Y6M۴1J\?{f<7\!Lw\b1WZm7v'$Fb# ϸ1F닋i^z=n2 ~zO HA;ǰ(x=ߧoAdMk}D'Og~XkR씙onn;*3PHGcL(.//g*˲vk,,nnnǠ>Pfiq50dLAq»"ι ϳw}[zF DJ6]zSGk]U6/^{dT$hn-EVJfU4G?/^H<#:D "]N)pVgdF7ޒHmuRJ+]Vb|9?sduϹÛrP"Kò-=D/-((-!ݻވ:{Ү{kҜ (;^2d)%J37OalYG `(48#|Z*J)ƘTM9@j5P4FT>a^|85֞=:ۘ[ʿ8::|r>g yEt뤜&;g6p$C<"-Kvuv F}6/>F9W)Ɉ>Byf!,sCl|!f/ }k3CbnG)l}SpAiH b$0ZhTccs>ƨP+<ˇ\)|H)5MunoO)u-+]q\j_|S<'x2n/\FDӽp5o &`:# (, nPiY:ӧ+Z"JlgYJ)ĸpR*DqJͲ e 1&B\VNr ro>־'"4&\NCC䶆n״+E?HM2͉zb!0ͺ5d4jjy]r3WJݿW+wf*H1Fߐs0Z+yEoDQɸ*6_^\q" o E7DoYӺ@h& J#dYկ*Yg&>xM\D\Xv Dw,]ZghRh)L&Mmo o)ʢ(|cI8d*$ΆDDEą/(''ǧo୵C@Dv#X9vk`,J@ j*X՝ 1~坕تf\jzR@|>~$TU%ܽ{տO~OE7u;¹}G|>!>R"⩉J.]&B(qGZOo:OD|tI.ۦ) 1|s^ܽcc9og½^Àq0N=/Nv4Li87SV,ᐧmh/h#5"͉kk0xB1BpiqyF n>o[ڞ߸ ?maG?RD}D嫢"₰۪O-l*bn̈CYvg~_:i8m>m^J6y&)%Tuɤiќ>Rg+/_,B1ƻw︮[V= 0WA?X:֚z3q蝐D5FMxxBţFmj64 &ЄImB,b6j/_6MӇ( V)ߞQqJJē.6@`AybZ)cCyM!|krbZm$D$0{u]8s(_~_xtwޭt:裏,#JR@kmu睋]_b >^cD<&qӴ1F-gGZ+5EǝOqH$i)%cuUUιޔ~(EY|zcJhg IRJ BN D9p:R}<ɓB0`L B@JkRO:" 1{GՕRfq}ɝ;Qчc 5x4_mW; DTgv<uεmWW# i573l8aƤxkAN٩^׃88[ m6!DE1>c)Q\06sBT)ާƲWڅA ;wO5D0Mxnx;GPJkJYQW|Ѹ7#{瞽sӟ"tn+#a" 1hRk";[\?oMJC JWh66{~ yL) Up#"YU<3J( ".xÇOOO QHc4[mR?PyKsC9A#k1&-|>_V{)r4ͭ|~#Jk"s;N\<ӚWCƣ{ޛ_,VEy'"G JEכa*MmVZ)ȷI)'"/DͯM"ύ> ?k̏OOOʪYk [xbSJhmaw "x 'y5z'SL&j0Ed2vZMgӮunᥗ^yv*H)%kt:Yօ(%F+RL͠'@ !vޑBC< YGNo_KUMVFkDUfGiꫯ\~R~rr\Ue1sQuއ{ tBO 9cBb!d-m#f3r@JFAnu/.4]`m\_p0PC"{/@kuѓ'UӠZ; ݜa=Xi)q:6p~H۱((,{C|rWJ@\~RTUyJBM]7`50x!a ܲ"ԯZk:W |o9Z ;{>su<*d;t:i{WVeJia3Q{w9<{tUG_/9Ǐ/8Q[<^!r\_wm R~HPYVV%h4 !{(J̳<|M;,ٔ(0,[nwjۦ@fyFD]q=u. BPF/N&cr- . ЃI5x3Iؘ^iM,"2"b] uՕ FUUU#V*/ TQq.Ҋ2kXE+MCW̨jr>H}ñR7vOIU\j`/tr^s/Ox4Uu]w]cЍڲ,7׋i`(}կ "`~zz2bLes@ SLMuñ$#r`",0dVcj!Rk&I a7><>`Eb{[1F"O6ȗѽVp@@J"%z/_O>9)rP|>{ΣGmyuz]Y`A)UUeUO릁aq-?Ç?qUyo'(b麎["Wm&%p!Ƅj7x6h6uիuRJ1E"NmxF7"ȟ1F|P͛CfsQ_~z_Ffx< 1MWJD\.VF%jf1&[q#piUy*K_F6m۵J"sfvP IN^u岿f~(/,!(gqoR~gO?OqXrK}:lnVsn5 Dq'''ը,<76-91p>MRFi 9A3`5b.B }"RH PG ULq2t]WhT}c,G6?8|M~ݨOmBvV^|o^WuP^)@XMrdm׹sH\>R^1,aD]׹S )c,:ھk[@:88<~8x4&EQVjFTDjcP] |<*d!KX9ehр ?UJnas hR(Egy"R}]d2 nIDATi"),ό֭OyQͿܳ٣s5|vyy5okl9fYvuy_SocLY4f].dHC%Ɓ˝`Bt> }I)lzQsK/=fC@e/#}QpAD\KDprrRE>εm@Zl6QuAmQRj:xjDJI!)xR4mƓrlv'R³,.Q\.4cPUsҗGqN/+;TUtr5|kDqO7xd4ySJ)!`6)%\Y*m*b +ǣq~<8疫%i$FB.2hCX.gsN{ Wi3f[fRG!rZbbTU;j9](',8d{2I4} />m"X9^(@ )'̋UUW?lSlzyqZa߇Ẫ,JV5,޽ruqq)x('/=99ͲL)Mog!pҪ,n_콇DyQ(T771"&lGbFo @DYmMN G{bL1@8kgpNJt9qTeUZk)%eFkU]7jH5I)J,{{/:ױs2`]&(h\J5R`o>Fmۉ "Ÿ3?я>|ȖĢ(޹n{?s?Dc>h5@ppx^֫RJk=MR5EQ՛uLcbs:96q4k-?/Kl)Dyr( ".ONNYA+e!㬒qcZkEDd+- . _()?>>F,ˬ5eUm} 0SZymz63%D}%9U(!Pgyfպ1r|,|Cr *,>Xkfi4uryoE'E]sZN ".|w ''*XV":ȯMBelƒ/! <@_S9"_E!|H dYcL(ͨ$3c r:0D*%ɾsђ" _t)c4FC D,=P׵R9"ymh,`iڳs6&PJ 1/`޾.c3WD}!7!ۢ j8RK= k0%n FDhT۶Mzn6Rj21*B;a "i)q6i1@JM$`5Fƣn:$n0ŔĽ?G>9oV nQZDI\p|)BReD6 5"8SQ@X#rt(b2/Ws4l+VI|Gω^#=|@wl:ٛOy'=L5=!X­qP~P SJ2>w\ >q>*7 'qA<|{DHË/=_Nq>>{O>~ZCe8N=#&PDY6|:~mQpAD\D@ eiuê'"rV;(&ǟ5uÎ'qΕ%1jd+ ";H1yzB6eUYMq+@Pl>}76-m *ú=8ZM'ȗUvZs&Jo]\uޛє"QeyVz j1smʐ^.B+B윣bLE)"( ".ɱVRBFz8nZ7>2(BGqNN( ".RYRB;o (B+v1ZkuN)L>@ط7dnK8P\S1""$˔CƘ/}ٺn./\\Ti=mA*THD?D tzr@H,l͍Z)ebXu'4y["B&Qp@oPvr|ܺ;TjFkˢLD.Dw;9(mVaJ?oyz QMD0FBK1ihYKo3)  6( )fS l۶(^uQpAD\>7R~z(yf]P*!|׿-QpAD\>OH)UA =zAD\>R@eqAwDS  ". " "   ". "    ". " "   ". "    ". " "   ". "    ".  &[IENDB`PKFs-Pictures/100000000000020D0000020D02A68297.pngPNG  IHDR  2z pHYs  tIME n IDATx$u%W_뙞2HH /e$`0?([If07KŽYVUYy=Ca+l+HË^ˬS{E3l6#hl6l681f9Nfll6l681f9Nfll6l6fsf'fl6ljl6qb6fsf'fl6ljl6qb6fKng>p>{~Oz[o}grR9E`_׾6_'fYnz"BPֱ2"G% 6lp՟B)΁*b*g"CDҘS¾_Kśo}^EKf"""JAB^SB8@ *dL?^W9+qo#$5'0%yMIsR#BQr".a{KN)a͵w/|+qb6[o}ߺ!HD]hiI.^9}U.]'E%'UKmUr*y~?Ͽ|gs~!7g.ٱwq:9Rk914-z/)*k)%gpn.iexpQ7/8_'f_ ozA!k"*9q7UPE"IqHTUs23l6m6R)y,gGw8BU_x~9Nf?޹ƿƞ8vED|>b!hΚ Bf_7yYJќP͸rEܦ6uS7SNc<ݼAPϽF81|zꫯz78w`n1gP%} DD43Ү41`Tun@li0+wJ)W/۳9Nf?ݽ~zP T 1E+øn1R ɳ1" ;>{o1xzj"E 됩o!EMMHb"Ev1C|_?6ߣ'f_kw@-.RdI(3{$DSR1>QBITLdUL>CMSWL,*d'&|K_ҧw5l7ݻ7Pr{31  }U!#=>3vOLD4 CM{isQ!f>;=sPuLiݎJI)xYm,%)%o'GO6]瘣q4/ۿ=߻/{sڻ~{߻{&*>*B1xUz!|CT1ܺ~r*%x1Seooaf"b9E|#qv='qac%`>R0'0c[3[yym |w~g9:;/UEd%E˰\VuM! EL"B@$BBɥFqH19;"9圳!q$R)0Cn R"r90l)|b sb1,q޺u rYr%8^\yZi!x3+EĤ"""MUE0@ Q.L|`L$h\gIJ>#)Hs*GcCj bw\ )(7D>|b6O|;މh 놼)j'~c3j.ETDMS)yf4R2>2 Hdj.X*㽽v\㳓aH"RL y"cqCb" *RMٌhJd@L$ usJQJ[eLZM-uP5ysz;9U !GTJ9 ĝ;+g8PErbtuۏCcpnөj.9!Z.RDJ1)gp@)S)lؿCT5U53D"x03O'"PJʹvT XN$jwrUtnwR6MӘjN)Tv'Gk#0iwIů~w>u2}~13.\]IE\U5æn<{UьMbTdSr> 7uCUմmj@=9:l6[<3iLAfiVJQ5SPDt1uݾ}yB0vṳEл1nڶmLsK2@]y"&D<goS7NIq_#$ErJ;D%irմ-1R d$bLLDDfZҘA#(bU叏NJ9&.n]CD6+3D3\~4MDD$@fGۮ?{<̦roTJ. 1 d fZJQUB4\D HNCy{xL]Uczݘ;o;;Vڦ],2Jl&M[b&vhRRNCw#K_ggs&n}߹UKU1VuFɹ;>(8X,)i1l*E궪 I9</|9N~ݽso -=ԠC궒F50q r,EeG$"iŨkB>*:vgf HɌ:ηZ)EDvX}R 1)(' h!F숝w~ZtBRTԊb AgɱJV"$7kFUmL%;ovުnrʩGnLBB!;Gf"1vỳ>>8͛7_k~335 4Mcls x&K۶eD"]CRiRbC*Dc)H0T<@0"4S5)g}놮!F 11`a)9bضm( {o;`*^oVmJv1XEBM>)88YYXgR7kr~{rG^S7)eoiH.8牐JJy0HN;oz!8ýwoDH''C=\˥U`"IrZ\rv\ʹ9ޫ'1Խ " UἡcBM!bQ^>xMB7 HDn+ð;[g4k"K<,S *"31 ȀWw]_DF4 fӝvkf%%g_2-q0q]rlZUK\)yW+ fy0sLҘ]|v9s@ܺkWWn&6}UpF"̩\,TuLiވJUbc;D)buNLaz>RT  wv!q*"tkhfo"c>#X:THB11{*G{{n1؟۝ CS 0L`85]glw9S59~o;9b-{WDD_ݸ+|W~eo|b7{w~uT%C)bn1xpxqqyEǺEzuUS}ui6_MӘX.T3OW+޳s 1Mm8(4~ w?3tgsu'_O~~YrFd>slqoj_!q۝mb].Z"nSrۦ Oi̔H9̦&9R y`fR!"!2 C\$zn/^yzqxsɒun4`•+GĩT1M!4 2"rDUTsJz~@Kad:H./Z fs6i[3ErJ5xt䫪Zw8sfӅ"B0)NJz+/P9Nz7^}Cӻ0m+hB?>ô\3 #,-;ǪfY$cb@)xRcBB12Oo|ĄՈ6uթ"(!0!gQM@@AD6.|pAtѓ{wGU50fWǡ LUL`>2}ޞrK&203i.]C42}ߟʰ+ØE*WDI׵m\,)T;ON(PU"3$UPJ/ZƱ._K3qbW͛gJ)윚lff<H>0MgAU:ѱN0O7a:EϻؘTD=Oh B"˴~5}`Pv8&F UOt"\zj2"P-)C YTU=C45ݜzr9PWix2 PHU3CS0-9_9D4-uղ4dI)YGaPkXo-6M]@,uU- y]LFĎ1jfʄ !韊GY|b͛|GQDXR*9M[~j:|:܏0 fCuMD1j/Sg;< 3J.ZDKX9RDmOO>DS;S{93Ebrޙ8 XX[?^,>*Dijєs)8qƍu!\|%DPrC}h2||{B.=ҥz{uf>ՒٙNEl,b@1 {{.D0SP81TU]bn>?Jʈ;1ObBbsG3%bD*"c%D`D8W~sN7oo`D \dT#&$\sa}txqYKQ).ruzoH JHc!úm QT- RB`Dʩ-hSO~2T;!ؾikNyF4@v[BRnг{tᆵPꂚ4Sj) `d>zt2{G}0;8p17x:Cn}(#0$=HSTSd ELL$9#+i"g볺Pr)9 Q6c?*U-3RBp IDATknIC*^ٜOO +(C@#^\^د/Mo9C+Er9Yv j8 @ ͈id҄sgbMs/^8zr|||RJY,;nu#1% 9{=3zzjSѴ?wzѮ.^lV{.2ܻ+$"ݽm|*nZTΏCDln=m, "baԫ}D7ۜ0UUhږrJ*: #R·XUj*9+)"dBwDs/cfs>wo)@iUPA$vUX.>x1pibUU"{b)%$T@, pGsMeLn:}h̳N5UL !Ԍʶ}0cJ){w}XLls^wpn۳Ӓ^z~"?{ɽ{c1.WUӆ9{9w+j_PMLXpQ4`Cvj j`&p>jpDU0g ݻv,,6 trľSS7UӦ,8V*a5=D)1{0*F]v~;bֿ=?'9x/ݺk߹֛<vw$9i)Lb^2KEJ4!U8BL6dE~Ȓ0ƨ.= 6msruĈRB{G?M:ӯQݗ^xϿ TUmٔ?lWއpa?z9>Co:Tvjfb\ե]T5a>wwmG>!%5~|v~2Hc33`60qW7YIRʘRmiX\fӶ0;@YR?rp`!4H)E/|b6lj۷n}ﻯ^{KR֒)myQ9SmWWfׇW+<3#S1qWM<8m;ͳ(UKn/_>t!&jfS/CS3"x窶vp]j*b`Dq|1SO_|G̮*"r c< MԐ(ŋ,VUwz]KWpx8vz=;ݞ8G>:?tե|35˒!cOeRrdY^i>9:s;>̇7b&$uu|*ZB0RJSZIcǎ}n7􃨲sR$ F3FZ8h}'-f/yw^}9R%0M Cu%6>a@RPST;DHf:]II bSXBm`4#n.~pҡ";@,"qf D2@VRQv\~L>sDEEĺ~s)iLB8::Qi l@K1G#UȠ;9si~>Fffa/n{vzzt ˋվsNT@Urݻzჺ]\~CZ LԜs ZD Rt\8r 2NN1dFaj&4<-xEsǒ>ŒCX5 "}Ƒ >N7q5,wK%[7oU8?M9%@?[!"0gk3iʷ-U0l8P?usެ.^zbl{r==]\Ї 1D9::}ۻx噸XI5%"3* d"LDS`$0!D64M1"ȴSJ13&{g~dbfNi_")#cfZ=!.c] Uu~*rSEU2kW''fHDR{BT7UI)lf;>>}HTD$o:T Uk0\U=LymX/MUhɥD־iվ|(9oכc\,Hyc5qz#cK—^ٜOR$λo ncuVULUU\>WмY" J)RA}bp916YARuŕCf- U> njԀ wsRDSHaB4L"kdB0)E_d3x7oOȇ{0)=BKl3-4UUfqML@4uTևܿ2wRç>XN'r3314– a.hj07ޫ2v8Ba,(, nm"D%<+fqiJW]rtyp "DGzê6m  $uMۆB '/TʋS6_ ۯ~wxNLm'%e@@TE{!:gMC@HDh`9 :aY?yR-4WĎ!""a8{;=EKW9xdBD;}p}vfO=^arf*f84zDH&B] C":vf"!1s4 T ?C.RJU媘qު`bSVC@!brw9ݴjSg''HSw;ߺ8D&R3Uim.s"Zth NJ!v8||rZTuc ےsv1A.1!q,MS5Rr4:9JʈuӓS"b1Pt~B8׾^ٜO"%W]wr-FU%Ͱۥ3reZ}.*8͓*ԈIs9}p=s, Ȧ/2v[3E˪]C so0tupLM94OPŕrr^GM$3""e)ØKm:±}pam-~sFPM9i.'KAqU6e.@QD D'p!,/\ D_&Gi)͢h:̐1OU]KJyp”wHf2vݸs}a,Tifܻ6,#fP"OsdqLOjo"RFBL[UMmE ."0lr^\!RKUuacxw|7~c~g_q\ ȱ,C+EI-;Znk;y[uoU$J{bq|si@[+݆SJ90}CA2s)crJ46ۻմ~CyC1x 볳I kP=8x8~*V˳'gON˥;_a^͛٩uv/}kkW~Gr9#c,9eU<80se"^DfMc89DE.>$OJPH[$ \ 1"0$$(뇇XØs{"n]S@JYRq <,h)B$4)"Q"F&b)N]g4y睪5w]?S8w@|[n@@X "9gc򕪌$*Ҵb1w@Bݼ sdY,Zl ü\''U۰1yn^9w;ruC:q2Lq9vΦ_x>f^2SN4]/T$*0%"P-+ePZ3*4 1)Ч¢)ڜo(wwwW2Hgg?Tq09sl_,\׶UZZ-J N7"S|?&ٳD%!Џ7/_O咍 ."Bm˫r%Co m|/ByqlzZ62vuMf&C 0I}k:"J"׷6}~}lo~wfkUEU攱num܈950"֢2f͠`a$zg<Ջnb%(ZDXpKn/.x_BOMU>pw{)O%J"w0 Lۦ5M̀SBU%ce33!Bɪ*sl7ˋBeɓ/%SH"UAQ gPBdP<0 9on77,R[WVʾi\ϞqT7m۴l&{0xq[W&TZ"QcJP)49BR8Dֺ#qanϟ>UAe9<fviDa}MwªJrhD8Ī'k<#i>v4ټ~}e@<>?_Q~GºnpN{_+}/wSm >x7W|2e /DYVfmX-W#=W[9爐Xl-!W:&2D "kWy_WUo@Ȩ`-zE$ 򀬠<8S?\泗4SJd]tXC뺩ۮ]uMQYyk[4΋/7mZ IDATcj}fb$)CλVڮ sp;( 8ʌY,f2Sq.:(!JyO>}Gy qUU@(dWIErV0V&8y1]=BB )+ !2ΰA(b6{os#8)ݝf4Mh]4 2__ yO)m ~/ޖɴ)#(j+@"*cG{cM4$T(IH1X@)c "LTucgHI) YSIgOE@=nap|ɘ)qwK ¡0MmX.Mӱ1lZrꜳq[Hd !B"BsXMlE PK"gBUUD4 %q *"dDȰ!¦inywqq67o. }Chs9 5uszz19lrN16,*2gb΁BNY5kλosI|$eFd$DNNwS of]^ {|8?_^iu3<3KvΟwF:xA&k =QF\.b9eBb6٪ ZcIQ : [g !YlQ>$hLѰmq: @%f*_8aq <;YTsJ꺿y֔C~R~Ǿzrzry}<>ѹ2ՠΙ2QVՔhcooooC~RT|朆y>y^fs $@ȶPCR`P朳<~m;46%""s?_rJw ƸNДJfnq^,m]kTY*D$ noqXc8Q]3uwr*9BjGo\*s @Z7LnvWNϟڏ_}7a|t^.ir.{k? ~xO<moeAU2L0挈yId1MnnbkL<6LlJP4!2("b[U壊* C%(TSΈ~m tgȦiٹIU4HD)1lͳwsX8_Ms\ڒ:WER&r5*1iʌ֘ݶmj yܾ8A1#QIa"D禩hR!@EkcLETH))iWח*{cw7M]{<}?C#.PwUum].9c( g/o/.Rof1λժ jucwwwpP0UԵN# 0}iMN u# R!vQ]פjVNOO>^S^.OOi1I&b w+x!<։@|oo|wGƱn8GdcMqw1wiO}琙-EBf6-WdZ{癹h攌1@̈D$)ueo9Ŵ?mb!NZ?9_=I9' pww[$1 媮G뫛Db &$bnZF>YDQJTD9*wWBHTv# jF0<D4zZR$dÄՀIr)sX&kPUM2C( k- JZA6!b R5M^4 LpyhDHR;BrQMa+[/**bX(1lm6z:=]-ONLB n/.^}48NO>b nHDB!&Q-h1lg񣊊)M"~s64lw~tɧ}rss`A/UBIaݢ5Ss1FcL.%lL)83&PYDR 9'be+c} 6֪fmUukʖ Bi^ @L>qq~G~t 5KiT`"oݸ_}\͓/|!Zc1\ ftRQB@g(qqsekB'c//S29$g㜳wFUNTKU;oYrX,N$4 #jqqC]Wu]{')=ǹӟo_w@Hzd683!ˣٓi83006!/X>qUzk1( fx[$;_$|x U%!k@C __ܾ~ӜcJwYfkf\Yc@j9W0@ӒMR b*cQpsy}1Ms AQ2Hźׁ 1~_'i ~RP"^?}」gBTQU/Yx"j #-SF"FBv<7뫧;Cg Yk{Y;+9ONc?Hs EP"""!FX-t{q||uu9ych!)B߇aSaSB`*[YQxp 1snʧao*""PI$)&9H)y{ddO9K 8'Qm[.7βsz筫l%ci|޷Mc*שּׂ*5뺮m ՛O>܄j,1Ƙb!% "dƊ 0O'Hp"c!TMm!ZXB4TdE;D (Zdf)Z2VBgswh\.a LE&жF.>XӭVSX$I*ZT] ݛ4ew%1,R,)g`SDSJd%DdrεU5+|igbxoXRSHh8NS1KV=iNM<́]qQTJ6npѶ뻻+??u~gg *"HFAUwX[WarV}7"fzEcX5nG6l!b|p7#6l`{*gi+AS⣏9!Xw&PE3xx)W13!AD5 Fb> L\N - \N r022KYpO"lʼ %0NEW$C " D\&N\ @$DD$Ru]npU6uYAg~*d* ?H0pY)03T bq0 / Bas)T?E,1*@MYdYYU%*yɺ8M SlPZvƖ%48Cak@*81MpusaSTTc$ʸ){yKP23HFUfFDE‡u)w<@¢n46lHFhE~;2jquDsN19l.a\N<χ/4 #B&, j8Ĉ L8hkk 0JZK bECQ"[%ӤoNq#'[^Yd.YB2DP优j_}OƜ&o.^+Db. "޹f v;}\)LSY׫߶cTUɢ:)e&Iv,STP*k]y"W-Fvyb̦i滋71̨:9ߵuoiZf֭ϟ ..q):ڪ:v.L]=}j]߄q6eUqu]Uuv~[wc?g=桨(sBإ8Tm6MXTMvDdy8 36تMwpD蜵U%9qYk+p^-MLo/>{60M<7OKEh}vz⽯;6mS;65OVD |$U5%h)&OX>xgFTc1FIOD!+禜1n*DOD̊@v"ETkkΠ L̈˓d@ؔCLˆ޸Pz$)'Q!k ΠbXxU@ f O};_XA"{GZD"R9+eXYls?[) w)pH!q^b8AC<0aSz5߽z# jf0V޳"Dv9< %~MO=:YvZv]1y$*#32ysd)"(C4n E X"J)vLΙi s[slo޼U<;3qJ1L훋յ)KR®[}EӵF Yk5L @Y4K2YUU< %HffcT)\_;I T—aN2極el<) j0)!ffUXklU~kZY$,)'TMeb$fɔO><ɏs8Gm#R7{oˤ3nf,'OweQ!p܎}BL1n6[􎹨r)%)_U$I}gMeDRIE(&c9D]7)wyrz;B C/瘚aDg9>F?ӘOKXPRY[uN΍4 *Ү8@1R YuPj\ _~OğCLڲ7Q{+,'*?=S\,A!gt *%¦;>fUmi}U-<"s2$^Ş!f}t- kX[E@" [AiUUˆAۛ؏bX1qՔJU\O_<===6.i0("1Ȫ:SH&UB+! E JD*")#-3s_z 1)K1,9L0jS2RJQX ܣHma''ۻ@얫mrB۪u%JxlCi6;[W@/cyOߞkG1o  {Pc,[S׵5WURVQ"lQtPW{}no iꪩ޿}.o TSEy#b-nqL7:0su6'ǀԶr)ac-cm&DT\P|t'`W/?$)n_|'ɚ\S/Ow׻. $„9AT@,:(2*+w67'Or 'S-!9 fcED9kv{{q9T4ӺNDGG'ϟ'Pk]c̢ZdZId%G|Qژ8Ls9HB# 昦04Udcs@1n UpEoyǛ?zQ}扝.cNi/$VݢZǾErLưe8MWs)$æ36 BT D"( /ʍ IDATrsNDֻg_zo|9绋˳=~9K"ZPT2QP$,ĒA˰iU}CUj.A4HP"SH)؏~o6#bikbFkIIb4XkCPQI@2ÇSưXgI!$@u;{okY!"p;5WwuH [&-;l B_H@miQmYK5tY;{}8I‌BUsoŊ>oC5"@% C(Gc :t`6Lw=Y}Dӑ(X5Xo[S jʡ0溙LǛaqw W=:>[+u$&Qr)\](#)R tD"8 #T℘ǏY닋g1>x@@x2Jf"1B@"V29檪...jjL17mmYcq`R\m7"Q8֓IQb.~;Lgf*&P @ycdY0Pܺguxq:)BRWW@r.="{7OnONwm4mm7Hsd75]'b뮯sJ:El>owux]'|1#u\o,ŜRT Gw0n={v6 $'Ugv9|hc5ٶ[O_UU5ϘqwjD9qKNb"v;q}qv2 po~bo}&_`&1ՋaB u/,?+Q-v5bJY|{?|E61fi ~զ|rrֆ@9'3+v!DCʈ`953"4#眯jGt7s"b_Mus9-uN*s(eFR8֤L~~?bqyL圜wqBIMDD-ƋOc?"P4LtvUCxI͙CD& ޥq,3db@C()4Ƙ0F35 BB_5F4j۔U}Y{{8"b[dZϲѺ0(E}<1ݿ34n'co7z2K}[~H1HzM]7M>܏q Ux.ğ_oL'۫+TUHh2R`9GPBy:\wUea=MyW6cQr[,çϞC狃g2vmkj2Y?_?`XxCdĴcKBLƾ_ο|zSE5MÐhj"fb;>:^t77Uj\F,_"&jzԔȋH.,3dB6]G̡,e}}32cHXv !T3]MWݽcgOS{w1/{{oqͷu8M~ Un$~tgT;o>Oou3|uUaQtQFm㭌o L%a]7Lsm->⛧m MmB#LD38m]WMp&~?G޽|ymw7MU7wΕ4B f<;[no$$vp&ӳoY&q9λ<|ZZa\S,]L<9)愈iJ%nTI5$@0PGH">w߹vk)1J.jF|."2 ;,b!Dc4E@&D,LE;hGoGY GI'mGRbwxH,A~L9>lW{w{oiuyu!jΙ",gADT8 `0MC 97rIW!`{xL$CQoV"48!T{_}|2if듓:vU%)ۭ޷< 9Sq[jw{/_۶,>x=azޥO1H3/o[_ȾP9T!kZ{:6%{3ibLCMP{WhJtCd2b!aiV>o6˥"Dl)8|ɓ'q|BYU 8[BUOX`r!ݟ߼h{͉='٘Į1;w9mmtjݐ2{O,Pd:^_mVA\Cvk0oorJWAbԜ 9z]Ϧ|o?ץuwO>ӻ{v"F!#g YwC*03:"P,givL&v$£n6]uBL&Dtg5@]U_0 Ͷ<}3@b]rygtr9nE$%pLEҳs=8P3G4Wg_ͧ͵ )IJÑb).9JN)Ʊ.qS)^]]V,L\7\awy"&f"6X=$ۋqn|yr~rws7?Wr3  P{5G1}"zߞlVs.X[l1X$EM DMUrÐ>m6cQQrL+"ajƨ|!\q*@wy1\aDW7ĈN$\im<;8 k:gǿwsuzNSQeLФ$yP7;XU*TjSD"d4gD___0 !8, )ӓ1>2 sD0_쓏o^f;CIWcwbo3L"u!x/?;Iw}ӪjL}'' bfd}RRr̞a$!Yqo^ E#k`3toc499qD54EFL7tuu}zzfnuu9t[T,YrLwf&UB00"7q`2i*-v! 20{ͤu|9@*ცR `&*Y**vUa;{w;;: gZr5RWs c !1ۘRN  =:ԁ]1+^^\t]v$yܬM,^@%%ح[̐iYD{ԕA>8hi*W wᑯmD} b.=WrLӣ~ۧ_~f~ˇYCfQ&WkAYCq2W!Uqy);T%KfBTnWWq7ݰ餝-$1q05&BaZUc޽w?|YGs!B܎]rA63ۅU+S w0Do,L8BY3Uu% 7(q""|VHL&2h_Jnc›~oy~,Mm\fUUݿ{O߻{wv/_{1e_7U79&w{oVx|ds//c) p fP,а "Üs8NŠy~ ̀n>ŔS2̆q,͗FDP&%; *}LQmԭ79'T!1=9Tʧa?n֩"ڇO)C[}M<.zgW_$=8mz a}{/W'/W'ĔbDfsl듗2ops~|ܬ/A' '?3-NB]{ au9e1ssL4 13iT%MSqۇ*L! l6?SOۀ;/V!ʀ&ѬI0 nFd0 9x?],q{sYLr֜TU3_o7./.WWWvP3@Ԝ8hΚTǮcjLEf*S*q0,"{fifA딒nzN'U]B*T$@rEtu 7@@NT SCBIIUh/S}S7vj^qh`egC4cV a!n8T{h%(LIlƎ+mzg|}]feU W/LhJ={RN Q!wdon'Ӄ~uxO)?wι=UmS]q$@ VA!2,.=DLΩiv 9\OuȡLTdYWUp)/onHqL7sM3)ћoO^^xfQ_EWnN&lcavnΉ?Z,TY_]8IB2vfC@i.xD%:bh)1Q r5ŽߔgLY nny㧳a>;=?_\ǘxYsqҼHR=|jQ P+G읫B9}_U5LS˳1F]yhǘ1˖Jn&ebhfYXvѪ*"9\$"ƄU廮[wio{:Ղ%Ѻ뺔"t۾XsuBUi4Luۧ< :̬b <^Vފ'}aßnsS/jɤnkooS֣wu^Ůu*E)·`m˯xt^6Mw??! ğ~fM3C.-sN̮jB2՜3933)j / dB㐔R͌ αv,d1| >f[UM)>ۓy$uW!`'-_ U-$`YDE̲nS9{B%9Νު^~mwsf)m}BATavH" yD{/.v.5.Ng;w /N>Ena(̎qA^b bJ'Y3hI.[i^M^x\^_HYIԓ`p>x_w#+}ڭ RWS@(""R1Mm:vy~..!3#sޱSw>0z4}ۚhq@Q 04mBU}Э]]MG"N9ɤκ~ zڶ֫=4 }]'9˵snn~^HğΡǟӽPuUb6W DI_0=Q(;& fUc1U5{,nn_pJo>Y)@H}؜|vyƨ" PT徦,5vsBfd'UMS.E g`Eɓ1ֳ0,ﻫu DYv AUd))tfGUy@ь.{3N\Ua'#3xO MJ dZ,%BȲ(b\B̺D51mbFbL1%B3"VӔr)fi6x- z2qu.f;&B33s uD+Z.zH4~kqL)K naHc)sKbLЬ䕚iyu)H. j)+-rUbYbNԶfbڬ'|<,w;@c3"m[u6ᑯMiܬA !؈dY1.];w^Kğs 0O鬮Pr4mbUW(P|EHƴ?P)!`Q3ՄPd:=䖜 ő\Qs>;;r.%Ua@)h !"@3$D$P-F1;SMw~/4iCq_./>9yMU fo/v[0N6jL k % $qټk+D95%<ܳڱMIVHRUYٌ̫ꪪJR9@-Ĉ,!dl]mq;dIs]v2D1d)!wu]+XFMr%%RrOrE$妖tDO̪:sc/ cם|U:#*b\KHu4tYr]5 ޳9c1y~v:T?W:g?y1TPT7BuݐBU (םsjo0߻oղ_d 1{_WW<`s^#3!tUדٴnZ}SUi՜S1\Ԫ*`I-fUUUQ)t<*dZ9e!vȹCe&SUT%&{{#!9~`zx'Sn[_7դ9Cִ9`$gBd%'p!޿w_j>,3#Ջ/8}duz] Ø$mqHa’2'څX =1aQW7 sʒc)R"Gx~7;t:LgSUH)hdnf10E IDAT"RdED׫jyMfZ-)tzǡmlDr]Wb%m*Xqu0Dne"D/{LA̸$gcuI|UP5s쪺9o6zy"cfu/~2bXЇP8Flu!d2M!HL )Ţ t1\_\{wSz]*^ ~ð4|1@MBX_X!E*s;2>9[C6{׳Ĉ)n7G302`7˯7e^tor%_ݫ'51I9u{_EjY=ٮC3(H?1CC,8bf3&h䜂d 6 QQ4LgF$%4*{U042fєqИ8Z0ۭZI {4l)QJ'O͠Vl\@޹) YtGf' ,*w^D  9eɬJe\M|waN4Ŝ$0K)kS#GL j fmqFёc7^EsJC_].drU'qդo|zsy~UTIӂwHK+s68VU]9nsPJsB(*U]:GD>圶HCH]!0;0;S}2d6ݟ=;~\EdoLZ𭷜_uGNꫯg4|p1¬i%fdw\x eKLuUNoLu7=<&-.og y7ӹ$XW$/ι(b"+fwuh'E;f5",JfG|0${.2'5EDQsu=5єESSUgBUUYXĔqyTc?|QElVtUww>}~usNW9q\_FD08W\i"|e&35ƪu( vY ޅfٌ,!V2NO/==dR7-2QUl_WK{"9"DLdHXO>:gj Pfe "mK۞TKZ`8W=o] ,.oO^>}9fL@L9<R;i]9rJ٬i=U 6-Gpyyuyu}xx~UWl6'ܷM=q:^pqݩ e{G$+S$1J]΍suډ5:/9ӴU59Ɍxz~?*w1QbTUS@f&$ PCB$"ߞ1)|6kxݷ^>?q>{:=S4jik Møxѝ__uOj9~W+gZgi6k{{SΪLs#Q)K4Ͽ0v,y4888wC0xجqǔcn{v~k_Y.f| T}~,yWU1:Gc6}Ϥ0^i%C, e Y__}&:Odjq.NRtޣG}7VhOe U:h̴n~N}|tqrִ?w3x=z(}>*vΣ qsJ;O}sE%Xy߼p:o.U,8d|꺩n;"4q9y1}AIndPHDC*Z`|Z3Bz%<*?|?hf}|Wi4 j*"H9uۘR.1J5tn3v~өc?hZ"0!Ne@T JU, aal@ , "|$USI M9B)뫪'?h6`:Lwfn+r8Mē|±#˜ڶ9 *+YZXT ̼YQR+<"f̼\UH4W=Uu'lꦬ+`sN?c wrރT""G 1'c=*V~f=A4mQم*TU]U51lVf],ժHBӖ=ID {zoO?$:o>XuLm21nvk"HF%FS͹nz:qλ*\qRio#8y^~ĹR4}>K//NC;f5U˓0[s%Ɗմ&lGCtr!pq1|nۗ_|짟1Z;% )9vs j`Z,úiرwWzX_^a-KTA6l gqaV3EhZ6i=z?`OOeIMTxƔ b ΁l;sAy+M{{Ƹ>;_1*3!@8d ']Y1 $@ b&vCYDcZD{]!-poUꪲ4Lڪmf:BZO ~:Φ3$f˷hp %^TsʢbAEƸ+9Sap +q1M)և{"@=sSc$tf6hw)'[bVr>trF@_Wv6sD??ޔ7{짠)Fm"c)O4ƓDoϚo޷ә~4H{͙!DŽ)˥wxl;l˳ia t2<ݺNwBU6#1.o;7j`yJy/1JE#稬%EUl00*8"w:;wX}Ǐc!s O5,u坋9ADEU(ALLQզS9Ct9E$D#[u Fpޗ]!ZCq/Q,25˲>R)%©0"9_73_kf٫Cq<::>?}}dL&e?BDFh"uݨi? cvz3wM7("@TlHli -/}#V6G,uc QzimB DʨyO?Zw.Csl)AywYR$@I.+<6x2_xf՚*|RV&hHއ+[o]x1j=)Iau)]~ݓw/^-wo (S'28vD$MbNCιh4[l!"OfɀUd{ea>7E@Ԝc9d: U]AL=v1DhdBQ8v(cf"jG͢#bou9'?W2#AY2,7ClƳ+CkJP{[we&B@Lm|xĖsYR Ѓ 2Lr*Q!;&ZvD˛ˋ??G{{7_wÏ>uk"]WfLb{rルMr;'Ds lSB 7 aѨnGiW!r\.cJbs_NBg.{']\Mp"0BrDXD!&jh Dvj_d?|DKo24C{"raT|H$ǜPLV}Ϟoݻ7=8h&r&B +x"Fr(2!u!-ePAF$vaUU~7>~0ġs>ׯ//z=t}@Z/{o?vT0YDJDU#d֬6%K1S`)&0`|s{R~HSJ{{;":Af$)!W_" ={K;^@fEIxDU_\+jm;rJJ+BSχ'TTG-WYUU]גҦhf[9XTʯB\mjvB_^mێ?7M?tяVr*siFL'eA+ウS,9_:Ԁ$4jAvb+,=ou 샽 w<,ZnXr4%D$&~@­i+Oգ{ɇx"hh"eFE`n[ 3>B}N]uX kIq%YXq3D <䲰G2+ے2 fV,Xa1R:sն{'Sl!4FU ߵ{Ngby)Vc]6ˢm-1_d͙d]G@C'P*àP\ PDRf(Tu:p}WzO5UU1Q]9Vuszn/ y0ꑰL87*kb٣ 1smb2?_c. (X?$X,cͨxr^ezϺ!" ]d*;?oƛcMWAaSs䜈fftÃ|o6oMxS'=;C̷wgs&m{PUoZMӴUPB oj Qɠ@313a LKƑ"{ۍ IDATU@j"_m uUU5;&H|ʬ\đ7|Um.siw!Hr(@"pq~꣏WgiL5˰#Y*CsSQ EP9Ȑegbya"]U<4mf3h3IX7)S`f%\QoM+2!a聰KrgqE2Pފy%25&28,2zOƟ/~M rxtW5Xyk# BP UdNq82L -8&̄XyMMUaU@TD̀nޘw.߸TnB;cU1h:r]/|y56>4umU DMT?KTsyM PsbdL$Q"b)澧P>vu DOEB!cv}Ʌpqj&h4Nٻdg!{Z }_7!KW_<m6jg?ɯқԛĿo!Xe lafF#jd4MU20^—i)&"B&󮭪l_`*b*)G3jyܡh0b/RmP;BdW@D#ˉƘQL@a*jfhsKm6M@p7ѽq-/.>^N1EFr4`ms9f 1h[nJB轟ֵo_'_}g_o3jG7]zՔ DYS)xb̒ɷMpD>8fB@P03xO@!wfL1*pvvSpmucը;9;([2DW!ӨiQL'{{mAsinHESJ1Ȑ)8XJ2^!*Bʚ_-}ۺEjX.F{}\jT'b"YTo971%e/Rj@e)1 ΅*ABF SE31!@CH)uWi0j̻vXM&f"0fbY(lLjKQaU;3I1s3" `;?tk0#@ף᷿{d=^0BLd(8ՆʃUKLf!3`z~Y,LU5f"i+FbւKn.v̄J@0yII,ߺ$STz\y=R[V@XO(LwrYFwyN4):Ue"$gi$b)WBfD),mo!rMC؇*VSZ/ДcnMADbNCV~}zcPє Ţ"l%{`'"` }N bH Djt^d!"TC./_^+~Ӵ!T8=Cu?m[5dwqvv{PUzXAnGqbuѧq8_ݺ=7:/?DĻ*H "{#R,礦9 t2vc1YavE\ ,>~xU\SgoGMUc=w90mYf4a)*QPUSyBF  ̹0b:r;hyvg?[\]ֱWjgQ7(l!"UC< -~d0pXAW5:: ٓ'TYEs12!*aqGqUa·XBU.8oݻsg*䜳s|>M_?6bU;Mwvygf9 s"SDHUup9\v)Ʋrj攜wn3b_4v3Qr Lgpgt(\xjD2ƘVFL  s.uC{df`&"9dBMYU c9F$uMSrm#CUCSTOlzW*DcDSbW*hTLJ_x~prg3j~MԭVs;z2tѧÃ4 9{~o曃_bIp&6lN7>|kgwnjr- )1q_zE֦Tr.zqs6t:>~ kҀDfrqz1@1q!"4 f(R,fG(O>_G܈l g"S#9&-ݭ l@V^FMUܝ|kiO3@4n*jc@pmS5 0$QPC2ʬ,ɹ@")%orfU#JSBRSO֫l6yg>"+[7E9.$eDsJ9KybY0E5CLqP(L[;WgB[b|Zm'}}#놘 - `׏>Y EbˬBD՘3 )60 [i 'n#WB!\שn~o W_|Gm3#i.]@srι"Ktjz:)^ӰTuUƚTKJG~gnzsܿ?;?pK C5(Ms7:CԊP{qqPiUn@F`9w_|4m=;wO~X-س+B=l˳3P`뽯vjj4rU"u'DBu5w}~7Oٹ`h](P,HfJjZ)[Mt^K:Q$eQ0Tޕ-+"Ν;`_/Ξ<)J'Bd$'BaDs?vAz3qD!sXVݯm|V{Dt`)ƜSagoUnM3}6""T''oY{_Q0жwӢB)PRR!c[՜34 1cJYrEh9!.>ŵVN{4wn;%f1'O^=zDL{)Y"2vƜs3*j7oAU އtrrF]UU;/S:j@Wl9G& H(Ðb4BB]zK hG^,y<rfSG,)֣b}7ߜoNTu}{2I%vf~n:'wNTP4dM!eCU%bZ0 0ijNώ1jgoh@TQ.XsRp,0#!4̘!zZI5-qBn4έ[;wǯ>}PT2"Qi̠R$kL sg]LTa`}Ys(^|t,`J([}l!jY _0o>W),!Ո?VWriO9)3BL)O>ٝ//f쌈B8<>B.<֡CFCfWdaRIK-w29'j*jgiʈ& SNZ0ijuWsUCԈH dY>rwCfu PP!᭣SAQSu[(0R6軼\~0}r;'ѧ~unA!TՓgrՓǟwCSjY(L&xËӗϟΎ]S_?^,9;ׯױ/?}4{LWuu򕷿!7o/+jyJD`+2?cU.PU{HdL,Bm^>xBEFәQvm PXo]Y_^PLЧtxyd:iG P!Mbɷ(t{|9Ŝs\jʓɉ'Z\kJȄOywrpvONmvwѻ\|KM bʢbh YI*/)Dr|r^svM]UΩiR{?Duv:!3@}&g ^|I:׶qx$mg?̊OX;!c4ſhYqpu !nkASH+t&"BJCL4m[uMΉI 9%4`D&h8ߎx4=q.LԭV RyO^|ZqI$#B鹈Yd8aqΑs͝w2:諸bb"E" !0qJŋW#"fĺmO8f=!jUW[)iSUf!b20-[I 10"aT4LUrN=9À`n ϥTsB}H̜wE#Si嫳Ǐ] ڎib6=a3KY@-8jk0i|ESdvnBĜ9WFmw:f4u1'1-8 vËƭOi*Lgu{{USzݭ'˟Agz}uz6Mu)"2 '?я'Z'|'Oz5/mn0sޏ&j*.Ul`|@l?9* ".2)V6@I٧ D U5>9ZwnLSd" vJ d# / ֜mgkR(vg74ÐM:˩ܾ5~qݯ׶ͺ"-@)$ry TFܩK52/[r@1KHBj \FWޮ79@$ =s3h:ͦ$om*xڹnW;v[ǡ)S6`ZǜdɚSLqȢ3;$Z.W9˫T)yvD>}ly,+&"ގT31rfjRJ9u ܼo]UUPE9}_+\.Q QHǡw{"*dY(!9ǔ_?gh 1X3B-a^W󋋫ݽ_ڻ_{ٓDsڅP}ׅ 4om9L{_*xilcfy#ܪD7g~VUU\@H}!wtu9iv H^__h:-[.ICbǓrO=0{X' D0 h8Le LZsaY{{+8fEh@`ܫ_..jO[=Q/WTb,y XJd) U\jNL5٭NaԄ鴝a~}z`Dr\FGo5;9q;u{X%20!s*[ UnUդv8`YZ}*1%fD"!4(šTr]svʸٹ*-d0Kb4@3c;Ad*蜵~;ӓ;(eޱ[V1F$BYMpq~*s!Lnݚ #1* ;P-( 9gsΣDTD DrR&fbv^U%%5Q wWklJ҃HPޞfJ1!l1Ƴϟ~1)%ݽ݃Wt6Zݬvt~:YQE3PМٕQs Ƥ"j)s3J^RFw!(>??wW@&f"ܤظ*N uǏ[Gqx}qc9{-'vzk})9"d%(,As'Ṇ ;_ fFHeG#F['c:RP@j_J-K0| Cu=No601ыGsD h}>:}xXc1vn Rץ [U5!j06RVP7uqT>u͸bѧn>߻[bd&_<njUUx:MY\s%OuBPa(]h׿\\-GnڶmPm/MTU_O&z=u7"WIAbJy,m[z6ns{TU<&T{b RPDv(5M- s:8w^,VqS;R)UDDp9Z,_CUq:;;{Tc,9Zf#8::fWW``;\^]]w]gf%֛ ,*hެ7AqU%2BMd {H\U \hvror5͘]"EPtjEuCfx]ܾ=ݍtuu݄.)!#:&?Rވӏ~L:9:>^֦&!0l'KhM]7H0,k U9oV۬sH;&bt aVDԣe&"YXY9Df`vwTRקsvՎ?_RJ,9#^*/^Gz4޿w/O)C`PM$B0j[ו vcO c!eEw!7۹s'J&,[^N) LEQ*?RbfdޕtbVU͙ngoI  Vʚc!%{g]ד٬F5ݻ{wq;i@ R]]9(2ƕ hV( ]Ũ*1'Q_ebdbJDޜHF4Α?L Z. eLkC@L92c)^~'<W%b_9&ndF.xy17+ӊ#H%6퐢sT&\Xj6 84+sbJa47}VaGDm~tuM6W?__K;<D&9g[G19^\]n;>Ӿۜz1;8l'Ī97뜛B?O~M%xS'?#vB ލvv LTvv@6saIw$FD2TT>fAZ2 IDAT;!_w*~k#.n`%1°Z%}] 7j CzI_oTlSp @S0{xW}έnSM p[n$bjrss`.&L*붍}OhFDUUjv۾mLRR5@te$( LF-\|`P4܌&\UUU*_19a0mwn$',?q薋EnWįOOzej2Pa Fe[@[IS6RP`qD%If! !v6-ld֛Ū_7UZ.h3's,YT3;DR3a]]XR]){nYZ@tG g5S0PP+13o{-Qdh"R"B2@$:sɓS2Ia5 EUO F2Вt=!$q TUo^y0xV]ַ*1eUg u`D}ZH[9XDD,@T ~')^SY4D"""n.K&WE3*9wNs6C GQUl0;蘉= "BYs4u_TE$D):2 P z4qڪ!)31"/b?~VS d|;n}O4W/_߿szkyȱc0JRO?|}DxzS'߫:1vc&6LgB圚Y/NUi1(!q# 13;#T XȪ K;h\7(v/ETna="V.\z9i@5 ?H'VnݦP4J#DvMTQ֗W?ET&lg8=j3P0Drks!vFEPxtɽs|\F9Ombj47F;Fuh:9H׾VF}]o1ッ I%wljU5TM-xTıj׿˿ǟ{4>K3;?{^K+=o  {zz*?oUJ60d&ɉo)kDO'L̅G37\PuS;_>Ub W!\<ޯVi{7㣇:s=ƥ-O{4M]w{.mnWqE | *$v5i L|];3C}-? ԅI.Rˬ yF޹.υ%O#}Jb[f=|ɉfY_\\|߿|_sZ"()ѝ#GowK5&v<9>yBRL?Ջ8Qbvx?"&)ZE_KhǏvyE\o b~ú3\*Z5;6@U{݌ *c*%t`$*cCk|ʒbN'?w&v/u!TBVu**:39_|';! DNCb$aj|%O^K,M9)h)?,/D9E! Q!3cf]y# >6cmASUsڴ+HlfG VWXW;U]aqeCUkf Ggo=~K"l8WW59:TE3՜cJ@mA#DhĎ 9W7g?8^M&uUW$"sU T D̈)%ޜ./5eq0J1YQS2 t@aB 琹,LT%3Bk~8Z`mE“c"q&އRж'{oӻtj)_>'ۋ<\Uz޳n\)ŜFC3ifw{[ɖeq|7}JLg~MUX:wO}r9ƴ*s.AE?csNV+f?=LASG M08J,8Ɯ<˰P15M110҅`7ճWdYD?E!RMU]Mڶ1qH)\\s%?!QU]s.TUTq(*TV#nc8*eꉩP!rbqZPن 2;[5P-u(*; JJ4*9CU B44 B56ۛ{`&?{W)Z]aq㨪{tC Qݴzqyrj:LB]t?ǚ27ypn7 HRHV>+9 EYZN޹sttYlhM') մmE`d0]pXHԐHM4&̚URJVx8."g_ دݗ/]1Q1l 1g1]G\ (9SMn`6c. a9I8`)*GF`@"*ޕ0#VWBDc B,ZضWcJq0,}݊h_ p^Z)PUհZ{*4ꘆ>88{*,΅!pݰZ$ n|ƛlw{6 Gm; nge;iggwEs~iC"dUW7@BQLjfo]AxiL ()L!rݙ*y)jE9MŮ m;vq)h{US6֫O YTon lh'xqrr6v6quv4#1e7ɝDu#4pfyRMs΋]ovf3vôm$g98:88^./>zp M~u{z:y-5={z\.ON}hHPTBZ*|1=zܾ|iB2;>T۶y4d*q Ѭbۛ'O31CA䪚a: 8ƔH9abTUu2i]@f@biC1#8~x|t~~T`BD X \̱,BT, W2@EK%sNEB'# ۍ }MjZtP31ENl:&4YcV4L&Wgvc_fÜkIy}s=/*?!2'̄.!` #EPl6y_vc%=QEb*K ^Т`朣RazxtU,gaFI)\^3"ɛ9*T\rU]M/;5PU(I f'"記jZ΃AE_*I93%hf8daSٍ9!b2HL*<_]^>zc\2 9dFq67Up.xD1.E67..ǮVhlժM+w?o'd (LHLy26M]"Y%jB ~1b!\ec֛}{3H3;9cڹC`qy۫#2;L:z>wUn@h*<vn>cN10٪!0QMIE$gBff7C ޽wYM'?˛'O/He1QLEq f9FtD;o`MӦU5D{`xLT_|ŧq0bj}{c,T Q3@;YtDcL)'~vzN1 c8>Ե%7Փ4gg1嶪/?|28<|;}hyrrpz8>>ww2_ 32u)8>*ޓMKSSfoϞ^c%S4’௮}}M~O?PUsDUUۉ|]RpQ_'&u5k5M#ĈZ"<3!f<Ţmb rRZnL iju~SD"v \PE秧-{4b̗Ս0vDds=]'s1%$v>M6WgT |oflq˗_~#!:v 91sjnXlfZMg9jնjy^od|\_S'8ń{wA7 qߠiY{OSjrfqwmatv{%xpr|tdGO>U;ʉ]LHFvԴyWR|lv9ƃb'97Td i{y{2U4P0_DGnrA&)F/-ـ h2mSq&P(D !:Y,YԲ\?},c|oV>00?R&y 9 Dh}s. f%i"L "_~~9Bf>Lt5RF|qp4_. h!'^^//9㨒C ';wsJJkC8|XBRU!g } ^~h@.ı\0T ދ*{n-uy/>aǀ!$f9/1J"8/#h"hUmUC`p"-9Ma]!8bd 0N!iɕ9$QfĨj\Xܿ?=>ӫǏ]^*h;7~{ U B(**Pk "qn'h-#캣ɬmv:s;!f*67 `vp?Tua`NI͊)d{[qYT*H֬wcV TCC@|ۭ 1kVC 8ƛ>a$" b-(?Wf6^?[vZ Lcr<9W7 }z\hhY رt3T-l=DM nhi\yFR3HfHP)ԇj'2l/D4&zn8{vegN1nڣaޮ64=XVMݮq:A??3:*~?_9!Vam Ph<縿*B0={|sqYMbBUz)c.5*1$۫˾9st}}C5'*x_fC"GVe}-'D_׈41jI@@C$$j]0R;$H,ÀbZLDM,d&6ǚ&nB* eKP*5DИ#Hǜ%|>98D ;'On22?tIv$(*LPrJ1),9|d9<\nͮ! =rT2ۛќ6TAL nu}R,l*q<ƾGc]ތ)bP{Jv[VZlG[ѻC`,schvh M;qLu]#@f":f"$  4DĎ9MӪ b2 BrH5sΒ"9WnޥǷd6 2Hx 0bMQݴal "C3&07X ^>{2oHCSWv7>Z$m]&37f+f󃥯)nw]!U!EŸ>tCer>nְ,vw~ϫq60 IDATBUM _@C`vfIJt}vw"cJ{Q9=9 yo 哧9"f\8v*l..yWMUNQD $園HN9;S-a-&U=Y,NzLgO/gTzs^o7]?x&\f*T~Λd0%"&LqLeh120l//k d>C#cws=Ngznݤ;M✟̦q {I1Օ+;:Q{vuUſ|='~s^?㟜_Ϫ*ZVPU5nd\Sy$^n\.? YE*c3̎˹eK *29/_7CPw>T/|@tt7($z'ie9}·MmNψ]2OXd톯fZޓ yor[IӮ5n<)d)j}Y.ڪrŅU e@Rl>ӱn7vە*Wt~uyIHGF{tlEy|+)fqDD5W//~~ѭM3o[D MK{9=-`dPUT1M88B//᷾5Y.c9{..Ο=~\{Ȍ{K)onWϞnkbM}riLʱT_y%KL@Dc?>{w``TSP5yӶT_E:.yL#Zy}O& PKAfuUZVwh."ǮKFUq Ja ojbh D JL.}0QԚSuPͦ)g:ΗK0c e&ȕלUm6@FE ,#9wC7 }'wMxDwX>\|N.x]t̲dI9J"],4gIr^mU;.1MR,9%*Ŝf&Ĕ@IEd99"^q@XZe/\=ͪ*0Bs=+`ȹ@LCd"$6ŢnuȪuڢ!\|vZa(n!,X^ ̲399kΥ|aA䲱Np{)H'٬煵8 ء0(PT@Ȼ37Ͷ+~gQ;ߺs~`OuND'E}so뛛nc"=zp?eA6DOmYyocM $vs}]WjrLٖ3mʇ8Ffd&1f_ZNeCՊ )# WMUUH%:_6ݶRgš$Q*3:q} ;~ABwD;)|(xG-L`{0-8=ǬkXumY)s(v˪PYBq}뺻Y0\~)ͷ,uae{r2MC]6똒wwkP3lOOwks/gD|<~ s)Ln$ u Jq۶N QmcWd躜33M:f_Uc3q,%dL'ebH%GeH7Hl$'>Xn\Wr͹^,v1{Rʒ#Ҝ}SDHZgbsNY0e+Rv6;}[ kaH?;r!K܏cr m'dP%Ex|ˊXd""eW@:u]KĨRJLD0[oŘr&f?K8*w~o|o#~NDeޮ[G\ۆnfPUj캪mE\5"v]OwƗΉOPD&R(9CUmc{}7kk_yuf])$".BS{溜Eh꬚ Kc,i3DUf)}U z.9]Nxs2-#fG9KBWSB سBUa跛ҥU`)E0PŽm*tyysN7W: LT jGdjwX,^h6%dگGD25#RGn*hF|nȌ"Ttg@h)dث/_7Wgn=88<=iFĎ@?{o<:*%T<=9w8l+Ր\}[<|{FU&}zW4Ĝ !Kj'aq$"I_P5qWt:u2w_27l.`$;鴙LFsrgO:rY5MӶ|!Br+QBd ȹjfݓ˫(Q3U?6iƸWWι$B2 BD̐bϒWޛ 44)"kGN30Tv;kG#RߏdL0t.JN"޹@}(!N-'KYWH02]HX98TGLmޜ9NYv#kLNLbE~խ637 Kfr!TEou՗g,O\|Y̬%%.DYr}뭦 ,}u]{r{Ra(Y4MAS90u[0# s!} 8Os4ơd2) 5"省1_!αh9$&,m}s.wizvu]3;&B#b9h*)#ѾW&C2 g궞/U=N8C׉H]*.TTuvm&x1/?zNs:1߮VGifXttCY m=[,BNz2Q-1.ʏXD^HN)ei/];sQPzw7fvq7X:s 9"MqTSM- TrH"9ĨJ.onu8dm-n;{|nS)U]UfBgdBdĬ9s#YH@!h fZZ4&5kG?r I劶7߫5ODqںue*7(7 bgs8j̜nGԈ)"Qy߬ٳ77Bqvh`R/*(dF>}+`UUq쇗% f("W7|PW{41(t)dRлo0-u)ewYDR(B@Ħ}D`LD(JXFQjD ;"45)~,a뺦B0甫.2/nz+۹I̤4Rq Ęԕ3!q`b1D&W4;8 M=[,wIrz:VlH*hJ)tssD Sʦf=MXwbU9_|놾SPLEU`Ҍ*wX(TW+&E0"`<99m6W*; iN6?GRRR:6+XڼdyhHF0~ߺP?g?N|h3W5  4 ;!M}%BԿjuZMzr^WWW77id\p90 YbJ)žnv)%Q- @l7I[%7ŎRߚ%PVJbe\/{Lev痾i2f z]"@]Uq3̦ӺCڇfH͒HٍvqcSN9*H?{oYw=;s~fެBH ( 򖅁bc2lk, cj,"@BT]U]C7|9睞喢ή67~}~m7MΩFjtw|_}5H\+fP@ *dH)S*Ǜϟ\_O'm6lAa}{փ"EJuL!BPU㬨@֡qX"1&Uh&b]^nx{q)RO MSӴ z΁B01~vi^D4>K)~/+HT߿հVXwkٙE;8ťf:22xQ=4ecm)E 9QJPx'"c9֕"10:ke6&vXSLuiTDڦ)ٰެ4 $C5p%kE"67'j=@@"Fָ -T7<`zb\֪qvx\}+a\2H!1aDZ_yږvk0SttZ6Em=٬Ϻ[zC$T(")'(:g (JaRUM!j%u& 4P buQ7%9sRGs)F)oJSu\RNI$qɧƏ{H@靛Lt>#BIICT9RcFW)g,k]㛮;ym "ZPB*L,XB*g9ys갛zDfuooLcذ`O }_#՝mD0($ U!" ,*`Ifzl|gqdmd\ [#Rj! >h=޿KM|7.XkndٚO~@+RHfl&̈:#2 %ۯ}?DmSovOA?!3!}ՃgsS %UD=]TbØSfs1ŦkhNy۶L*B1 M4 8WDdY=O)k U jp{y,9l0֮nSHVZ[`!\_UUK_?RN.$F<3qXPFZEbJ9'ǤpmU\^!踛q!PiqSrfڍc`fSYl.-ˊEJ9Cu_рŸzEcR R6JId-B!v[@̥"KN0xy;oD q,EJJ1 9GlX-H <5ƴMNYb }o4q%0onQV΃oV+ ")*o<]]\*49k+U6"9Rf}waxݷ7~i)gnU<~߰䜌qQ9DZkO˦,J'adkT[aDPQj.*1cumۋ2\^@wlfQ5'Rr)9ĘRqf}+\嬢ooh!/!1\T)CuRAB4XAU( IDAT+eĺv0\I7Y,/aCW)sɱYsrgEB̜RAC&ݤ~cD4M]m#dw4%Zad{u9a՘Kɉ ۦmڒR~$Gv!"Q#=> SۖKt2uy!F)49@ցq: گ}__?4RM8">!@1CR!_7GDia&]?zlVA/U,.ӧ[$~X{|Z1^>{ڶ`#Q1sa &3}~DRrqE%8"k^@4%PT~z 2ɈH{gGǓٜ9[Y5UkZxL/ Af*W:#*9Ϟ?ۮWHl;wi<ֲ?37TRݝ<[,k/HكUZZA 06Tvq Ca w4~3lD0b18}G|돱6lIa)XJ&m6n 1la"fva'?s'|Yz!I j7ְk\ZDEDP-b(zsurJ'd_BDo^<aXs<0"B cIU1kY7鴔euqcTU=˟gSuP C}4þ,Pf&VDۮiDZv7Q&ZEN']}ÖQ|gXBtm9͵AKf߿?}ti%qc @`\m@R}PHXbkF{x$36fQ!8*sJٮ7t|t?OLD !c٘ ӊ`7z&L%n6_nnSL7Ӈ)e$ժĘV;uy~s}sXG0="XbFPm"mJ*Ču1jvZ"d "%$C$ZKEJ#-PR"`& \ V0̸Y KkY} EP(B?JE9%iw{S=ۮ: = p- vpk|1kssJniq\A !眤dKP)ˍ\^=QT(n.$͘g,n%AQ !Z"`MS+;oK7W{쟠_$Eh 9s7 TVRՕƾSQEln]׶b=h}}}Yul7~H lR2<8 C%K451ra}u9m"PDH 1`ffl3[DVq6v郹e M @`y0M'ob{q~9-Hu㉈{%o'*kT!Ci^]88V^$Ř^_M3b1>h8aZtưmr6JQ0\6g{kR8at1os!ReG6buvI"BLZgE-eW}c,)]4UK*"$ÌM+}7/RJ:xd!k쳮.}2m#fUi%L&f#D9vqƌ!ֳ O@m:oEBoWA4-c[)EbP $qǾsN]zXs$7Ұw U~$(E0O?k{WIEEBd꺶lkadgڮ'9˄jIhD@\tooEJa PI 5P(0Y|d ;Wl+߶il1q~ EN6*Kw;MiVȜb-x\$=\ :no~g  dTJԂbr.ED%n6ۻã?;2W$jA㣷筷^]/^\ıќsc];XkHѣv:Rr\Njόua,%* w8cX߭J]= u#k͹~R?Y*Sm蜙L 2TG-$D<^; }ƛ8`3/ݳ@Ueè %1Sc fs{k@eߐa6/at~p 0~AcMJin729ED*%ÍMR 'ז"^oB[g՘ͮ<sQUrǑi [QƱL&}+ aΙ9d~,GG|`;g*0bDQ`ݯWO;1֔R^8Prcؚn6UB[g+\^ɰbSE+hIqZ_<{0E7_TD8糈T<3[R|lv> ;g@ qGk.Ԑpe鑐S,EU-[fgssLQ|{bfy޶^}g~;B 2Oc8\b.]\Z{N@ +meځvoZBhfSqnu{_~ָ+)Aؐ4m_0}LHPQhRqv}_~?^ wiZk톭KL7yJ^c 3cߣ [P Y]۪PAκʳ>k| 1ĘS:=쎎g"#2T\2M;Ŧ­!P6ǔg^_vhRA5Lnhs}?M1jPA Pl}u"D$9:bv]WBRcp)!Rns룇lK'V%{"J/Ȅ)(D5m689i]Ӱ5^Ewu tRA DHEj0 P Aoc5.3qBE;>71\'coM(ϳ'Q;mM0딻rb.<<ΦI5'!Dva{h큶ʺ>Wذ٬SpHN:pIֶ;)aI1zo]߭gqtD}STjCYum/CP+1˹Hhq|ؘf:,Z6l7ZTDPWe{mHY 5`ؼmdvT8Jl?h.9NjOH7:q3 [BL&ZTH)1se'ß< KjRvXk&})EKRr]GYB-Qтf DdjRb9k,Mbu}]R&D?yrxzRL ¬G>8};E3[ QjM4M4ZYfn+wv 1C(9k. j,!) JԤ"dp޷S_ѷ=FM':ƄÀ)uiQ]`UރkLn_h|5bxjMMsuqקD"azD'bdfspRRrX#RJ*%SqI4]9MOg9gMl!@ v+I0S0 9R1%|cq@ 3œTݍXI;I93gR1qO>gƘ gnFkdĺE HR$DUaf@+xC@J1%FP kh:qs%O!1)sRWg/BhngG{~+.}\cכ.QRT0inڗ+NM(%(|1 _fLJ\ohփ evmkλ$;Dj62sCvZ6 mrY!۶# CHͬi6e<;8lg6"Z 1 bw{9/'[״ [r "!Ϟ_ų?zc=` @_ub1/4MS1"sZń6Dl s9ƘBArJ)`6!-w=_!RgM7mDRNC>垻RU99|"ɤ=>YQ5UUͺ7aP B,R,(E~7T"!pqttݰ5U"{/]zV`y~ܿH>_0H$"%uȥT:v7 Ch&~LfRJN)lw,' aDcM)ޤ?kKY\Z$k1S7s_+\7Iְ,>|e8yCG~"8 R१UƱDh_yFJ0lDm/.=zEDK)`Ch$Rlvz/=zt\_o[`"cLh'Ģ^J)y_R*GT iF界I d2OO4cгk6p>!8 "R"iOz(B)1%l8jcuMLz͍gDuR,Ҿ?Mc!=6#91ΛpbP[|1^{RJTO>]W}5C?0k`Or fzAvXU/6k熸BDg&M,Qg$B,9c|ĕ2o&B"3H)eHqY陸VTjOE1ZfWxD(9k~Akv10#!6Wub9+"c*]U @Ģar,TN u-1cJ!:4*V`}[yNiqppTy$o@{Z#b"@Gq q]8+1vij<`<ƴi:;Os1*cyp:7nG-YTr hQo/zw~睯?$xҤNT{2O&Ӈl1yaݩ!!B"Nf弝MmK BNL|(14өiVwjU˨ln~wn)8ڶeu\$d{o:RDRIrEj/fڶ0a6xu}彿6vmr 1d)d"V{1:SLiwsK( 1ڰZ#B1c/mT5!#TK~ 3莏c1s0֜Teݜ_zɤn7WlquyN&/gǏ^m:ƘHQ2˫'Ojb9lm4PmlFJʠLîG"_(wOξ/0`"Q)5*y 9g.c$`c~k!Ǿ֧mMǦka&`6@I9̈n,e IDATdJL!=mU&Jq C/i%#Td*j c^n U1OsE4xqqptDQ?DDX]]}׶SOSkQVHAsL%Ds4P53ƺ\ۺJð)\q9"H\f3c̃31CB8c0+h M2QTΛYc$4<],umnհq7ݾ123"a?Wğ~~`>˗P+9v6kUZJk;#5̦RBUDu''''dZ.ݶtm7ucPaF*u )ZcUUlT|TwLaDт{*K)RogUwT!YS77ι"ڶronoPYV#R#9cL3"иIJq׫ժX3Jwrr֚RJggOڮY.f/^?p"L`{HuKl]|ﭯ0_.s58:U:(k]mϟ?9AbDn|wJa"1خk:RRƾ߭.?t"J |,Ե-@ mt:8?0jKFT;p L)dT'm}h 1721[ǰYjQ ghZ_خ7o[cm BkvXQ%^e3/馢?3NlZJbޭ=3zZn Pr.EH\n~Z*1ygbIt0XOH0}s3QUTT 0"ZN ܴ+CfJd?ig1RaSI.{xMgW.-aN!fcm6pxdN6w{kR)ѽjnfTWMaZWb8}WTs$tZslMVV51Bd!\Dsʋlw7k$RGm\\.ǘs6E j#"1vcM)m[D %D\rd26"oI۝kZPyz*r11$X?1l褿a/D 1TSL?o_ʯ5c± MF-]c唄XD>`1ĞՊN"`-5#"yуScmSJ\܄R8u~qY'w*:MEqT&,ErE 4>sǑ&s.84ĥb,|6)mEHQP2s2̊ovrtҌZrIS(!\ЇNz1Ǩi^}|TB R*%n6m\̮ŵMy^|~~j2Yk I)9kv/7ͤJ(k fıgxxm[S)%4cJ{'j3-k-K)%<?}2@!Ȗ;V1SR$psq~ "ZYb *3%[S"xoQQ%&P!dDE2]ЫkEewmqؘRam6^NEuK\ĜUl/!nnbmVכ]tRr8U-9WE_Nغ"DdNKN~l)ׅG%gϟ;4{_fRc,ld@8%5a!QrN1jUg b6{\6"Řnϯ4옹'QGZ㗒 Rn.ί?V^R " Jfa3*z"Xc7X']OCRE'x k.i]lL͂`G4k+ Jq)9 PkJnr.9qr%뽔Ҵ-acQzGRon6ˢ劫jmwn%"lsac*s;DyWWe\ޛښeyc}jvwh3322"2Xؔl ҥo,!1@6P+)`2 Tiwo}lƘkRJHq bsk5cȅ%IkcSlNYSh1eǞsnF)X[9GDi8ggi vQf޿|UZ+8zuUM@&RHr  s' }/.;gz9l6R1'˧OdZl6ے*▔p*⒫*_$(B,XoA%o_z(0Bxʰ`im>/^:#":K)ƔN;FCu1r!!=mJ/_F=znBhڶmVeTgJ.%a$|8!WbA2h X5X$H 8O7׮innWv b\O[gRbaNǻW.)]<{ݏ?6M-Y$DB~Is[ϙS0FbJ1K a'RA,"Yc;0M88M9n_|Z^/quv1^__Wy91! ys% 8C. D,9eJkkqGETʕ6.6ƲHy# "8;@V9T>ր&CA N">ts}9#8[9Y <_»M]i.׫ٺn/ #ƤEQ5!̢BE eE\AwRVxا^=G¼h[cL@)/>]4>LRDLT\3\^02i򞳵6Ř8Mv|"HX*+ ivun8%ə!(̥|^#iTFA2]UHfDdNU Kcu,Y[瘢UU~21ZN^cLZe@FjRss\Rv7zeT66k!dn8$Due{)ED| TsfH,'/"Y"B֥H)%DtI) T* :aP佗,r9w\Y1+ˤLG׶u샰HLdq9p:{,꺱օvk]#ZkOff }ѷuDk|yƩբ+kf8k_MMJˈXC'DkARh 9Ʃl]f>zzt6ǡ?Ja8S˧OVUXkYRs<~0bRcY§Շys}؏VjBjh-,ATh}@zP@.i\ZSL1gjm6ʔ9%9R0`f\t̜2d椕~"s ,1zfci<e!zRFBǡ$(rӵWWWđcH6p,/$JAnmB@IQhww/U!{jTDE>yoxIJkm,H\jHH]ID1FP`ϵ?%턈Tfl.9BO6dںj 2Y*|Rb[k^9QF=1<9рes^ w08rS΄*B$؝KF*)5MRJcJ96T)z #cWW@h8Oպ[-521!cHfNaDUNK Db甴֋3m4UuF4׮2uR$}IgmAñ)V5"<'yNѰ"$ܿ|чMDVqLߤmp?U@H#d_o}{֕s^]ZɊH Rd-'6qsu=N"Z+t]q:D ~wf:γѓwVŇ c䔲H\5"cUZv~N z^>znۛ[B*\t*sER3(s4!7˥dUUkK]gUZRdҦ DE$XD9Cɼ1S) #KeQ~׾?s[7NdYϮ*8C]=|r(ra&Ed^x7X1(&RJ+Bt*Re+K'?1ri7oޤ˝G.ϮʬpwEl]׋ecLلy6..Ai8 1}-Z[%5HQ+R$0 #rS&*S׊ȵmf RJiUU!P`f2Ψ%[IWHΐ%+։?o*g1붕Aw{r9"*s2BLs3jBHM}7vST?Y?żp82@$Ҷ__]^]hEJ1#3qaޏÔ*z5~{";?;o$/X."?*96r,̠d tzb|H1@}o6/_BO3TlhPyyfFZ=G>S4][׵ {r{f1fuS,;*胉fVTmӞ}Ö9Vĕs9ͫ}&Ӑc@ÐsNܧhN:c뻯*|W nC@V fڨE8Q"*I%T\'e! \.үG?^v}eL.Vu]hۆIYt>ZkiE(#0 7ϟ_uz|gui\j3 dda\E$BL6\'xFqo[ œa:_릪]MD+1AX[ !}HaŢ:kygɹmBj‡tiƒ/9a}h]- Ĕ2dN%-XIq#"e )TpGTԮWhkޤ]S/.$bK̋e3Γu8WF|)J+TY. Q;W@9_DaWըZk1%;! GP*J!d0Z#)I)?:!vƂprn)eTAЉ0UZ*BeturY-9()qn;cΠF)jeuN^ʵѣ(Are56dum.ιz$ówx8u.ۛoyIiy^ 4MTI3sk B O8J(}14S~__{8qRӟ0:B-V))Ɛ"ه4mk,' ,Yj~GeO?λܬ@Gºi 1s#_\&rcaJcI(C%ͱ?zy1R$ >bɮӡ=Nq"tL)]iUi9q4ZoVs|"7a, Գ1$|(UX^!6m;S$wawyBι^,TDD2,>[~T{*[[tI)~jjFuJNjYic:3غaqM[0=ovry48ERW69DSfVJR.1ia `"VrJXL󬬅q̩],h*ܶXWU8(2 h%|wտmWzΟg)T,󧏼UYRYj dp+3V84">ci[c @&TژrQ(oZGm0>s\'ˋ :HBc fژmMS3dfU4R9ҪrN:kIa 1x~q\.Ed">6= srCLZkfyB Ga9qAk25m]UU[ _c߽y| m+9WWUZc51rQ׵rUS7J"4(E9s%%$DuJ3ZbPJ+QiJD=*99OǁSJ1c#d./1,\C`Y}i(PW2~4RjsULmSH)\0CZ_\mlR|.0}T~0"i`uqI *@ EǥĀ`]8O)e$ˠMӜ85sm*m g$TڶTbh "Tm+̋q jSgJ)lY.>zlrLT4V ? ӂ p<Ӕ>U/VsTF$.|ߔ Dۻ͋hD̒sJ"0\UVM׾=R iE*sTV3J)NR"R%Zaf>YK"Yr jj/^yIkR"+$Sʒ-B\bY"E3}bn[U])1&$ v]QKRZRȞ`9<ϦSbSUD8ͳwHXff?OUj몞/7Vk׶9gRJ嬵NJnHv߿z !v.x/.΍1Zi81sU] OT9qZcL tW9û.Vk1Chz}*MeÏou_X[ےJRLU1F|;e0 RJ SJs~m.<#iݸET3 HӴ9e] f\18)L]ݽ0g"(eù Iy>l6o~[MR =9CXrN̅'`crH]36 sh}K:g*W' 5B/Ųٓ'~`e+J)exš ,R %{|!˅2&3[kp4fSn{]P -J=C Rӧ|"P1U<#3bH`LZ*c;#i:n6Z휩ֺN၊;y8?붚I+ui-V>p4t! ü۫كY)FLIDPX hO>-_mMSgJ~cFuvv?ޏ?w!Nw痿ULi欬DZWI'ڃu8%RBD  OzI@@uU/ /gOcR!Q ";?Lz,r YD>,Z)CdU=Е9̳!2o) ` zٴuUa ﺅm68ke6G^UUeRJ0Y]}T~V]:tcw=S"nwߗ7(U~_W>gAztvqqyyqvvɓn*纮s( ?saH;sC9wwN [ wŝ/$K:O*d!5v;x!I%Zkml"Qz(XkYm3I,1ƛ7on1S,rde\syzn۷|@|pΉ90oJص1V^R2ƚJDr)WHFx*0c!c1\&.k"jcd?|o8A1iK IqSs6y<}ljcrgtY&Qy])c ΅Ќ@/0046)z6֞=yZlL3&aFLiSHy4MY|h5r ReT׵H,ԂDWlSLq9b(ڼM Z,TE8)rʒ`ۖS}sm~=^u1*e 4̜S(EB)m%8.Fx|Cp9cm]UGO8f&#*e5M|Gwˋ~zrjm 8DFyo]>xqCO9~R^ V43"-Y\U4Y"sZ-9x } n96KBd>\WUrko~A CcZRꊲYKjknjɹ?s.h!dǺm6ZjYPژ8!cA|ںAȟm)͝'?g! q) JR _QrD$Jk@f3}UWVqj}qgqH11 Mñ:7[4} 0NsnBu^^~QUmRZj+k7/_NAS3Yq3@1PJ1}~ "vݾ etuWϞJicgt"L1"Y)Bo/0P$9CaM)e{St4D*Wp1|o U bHbe]JI5!p4 9bJ)(Q1B*B"EuHC7qe*D|Bb!ƺNCG$"%9OЧyx̜*c0 H֎ Vl.9'¢((9v䜣 6xU3XQ2fqLOnjF붪+( e΄((9h۬"9R̬jWnVOi}PNDqc泫Gm'?k:N@8m1"7]9xJCއF0s<鏇mdVRiz3ښxX60z!׍vDooĿG\UYmʈϳ^:)I@hV|HHO> d`q6:BL"Z+9k2sJ+Dq3 a&HH`W9Csf^]\6RP/:gkP U6__4;1~oN{Ǘ/4R9[2P4yfD([נ¤\'VT1 LUVh>!%h}G>&<\.MۅCvqnz}fI{>R:/'8hc]۴>Y7~q8}qO{ל%^{w~'"9eն."T" ̌D8KGd*LLdN%I]Hsj{,"7k^R!JbrYU8MMS͊6N6!-=dI1Qm(V²HQ,WWU2;%d<0K42sODN(a.ivJߑVSZ+˒|84M4z"nXMc!$B՝Njuɘ9)!n@.wynˋ'Oۛ~ffJE4^M#U77wHjX 77~!(~r8h]5^D4&NQ᫟"j"m7Yz>U~~Νwi"Uʘ mRhQ)$N1qb,rth.p컮aN8O,WK{J+[W*B]U-lݔ|F$ |qUكX&ȤB/W/S X$I!jPkEDdvƒRM6µk0?iiDHiRHTVJDY\^J?ӱ'afŋ.h-+Z?~?y98h LdQZ7MuBEDZbIVE)0GWWϞ=ՕrV,~Zs}(:xcLyaAkDHag?\cW|4M1Ʋa)Mè h6ׯUf2ǂ+Ŝ)O19g]6E[b̉qϿiTHƺĞ}F@fyʚm(ޛ57 1{̡cIk;DC@Uo.78*f՝9t.'HP͑͆Z[M&l769S݄sG̎O\Qp*C]an"dbbV22VT9+*K1Ę mH(1&HcͰL1&n6p)diʯΉo?K_UngJZGTx5᜷Ƥ d} Zcs+8%NUUՓXCQZmՕ/{w}]'/%aN{ՓeY'1FmS ^={zQz_Ur:%DFd1i}Hh3DlXܹsͷ޹uhz|RfmLpkoOI>{g m7unzV? ˋgO6Y ../7]w?[o޻{db'$k n;8coEa-M&5VUEᣇCc۽z]N{S4d44n┌1|9,!:-+e攀9|\xu8 =ǔ,فݴBsbDnwwkeIO[UNT4]v]Gm"TcTxbQ߹8>Β53fs$R@2hAC~ֺjD0RT3r cn\WuRb}id}BTYVs)wB3=/ΛtVuUO;UVEIE" %>% £w}X½;ۧg?8W{)t>gC#盡O}|Q ƃ^pEXxa0$@p2Klz6!4jC1 ›$^,X$W\dhnk%Nd 2HLD4x›ZgqΥι+\>r$bÁY )M}b hJBZ]xƖAAc'ˣ~}-C1ŅO?_%*['&W1gE84g3d.*]b0ĔMr&Dgd2!Cc,1檉7UJ3],Ģy,9EUzROg/^\x߬SEݽ{rv׺B#%4E@2uU*b< M'fI;nq.GܭׇݶiZcqv Oj:HwwsG,:RU"cWҹ*WۋݮԧgHQxC[o!pLt?p2jh\r^!pJ-#!e/a-˂ _": DKo:yGHE" MsȲv4á`UEU%)1s1$5x7h>thHbw?}Bs/-ͷOq11_.~xϿ:t1umkDnD!4f8Tnȑ@$B<>ubI JMU5m t!+9IM ! U,Jkw)M&CS.oh Q(e)7RUglYouݾxTw.9-|nJD&keJ5ds!;TA|QĔbBlo* JB68{e8:.n )%!Tڮ=}U,Νf}Gށ~\)vr^ib+rROˢ2HZk%ja/C?B  C(z'.^j6s|pvB1v}nz} g.k4Y1b;Aj_~dnO :y㬝L[ʪ"uU1_=~=)ձʦ_CbȐ+6ƔMA&uqzgtwuzK,]>vIw Jk fPT$)}o9k-d E+ 0|"/ U"֥! ɤ\Q"!rPc5Å:QoLjzsΥY m-䊢ryv,34'O?2Avb9ͣ{S۶ouG? 蓏?ӟ_}|"!hJ1:2 :%oPѼh\ېǔ(Di:QL'YI)a8"2N|2d_,'V@KNb:ώOcxa'N٢n֗Ϟm.s{N)зmžftj  'eIᯯ˲,ۧ)[߽NjɉBF'["ge(\ehfQWefl:?>9^ kI5}9ڲ,zUV$e*5]ׅwUt>8byzttyz$U5u- sV@߿yvw@|8˥/Aa2msλ<0"b=?`f$"Ƹ2jJ,Rb[ljOo|᭱isj<4MmЧ(K%כ"28\ c#ey,.,Ffި *d0+=9C׶񰗮,*sC` wPO?<{pr9/իfκ*O~>}l;TUbJ''GWWoփg鋮8o gOf- yc<DP3GcEu:s6[DcvY9"*آsԛacbbD|1c'. O:ABc (7+31,( G[L!Zٌ>$:Y,4e3D%CMTE:'~W_l$2uE`򔌟NNH7X6aȩib=§ĨXյ16[.MVEUt>#c,ee$/|DCb\kazӡmf'GΦ'*z![ϦEY{ӳd]ޢb.㄂PUU7֚YUꊐl6%2 b&&k% ͚y"c5 1 3Qn>PNIa0 Y**fKV5 -4H{Sx lw璉 oA"Uy92'H$"D!šiSLc ]S}?dJK|bOi wYUq+b˚TP$0 ݐB<{.`cZ>u quCS,96әsn݅Dj2af/kڙcĹw1k061NcaH+@wS[v8 ﭱH`E|z:-j}|zqUWחWz![M6[o%aNOO}0B֜S"]wzzBZ__^[c!Ơ*A!ˢ>N&BbFO!Q?t:I d1usGT fxv+ $!-ZG78ɰjI13 I *X`.b:UU={7OD*U0! ݅~Y_{qd&2#{og;y_0s 3rJSb(QQ]>sV+?y;\Qc9)EC9h%fqڲ*!QcPE!"onw["|qw#UcL]GZeA\f 爈U'CEPC۶EQf:kl=}+'(!.WP( h|' T㜱6sk>uHxCW@CmxAeQ"asIT?DUfө5&ƔRŋa͏M11ȗCNI pbQi6,ld7icA*F1=8'˥Įwa`:w{•U%$sC䪊!#A "Pբ(+`wyPMZ{Xb?p}Ls\1#6'Tqfn1! NYN{Z,,UQ5Fr6=skq|z?{?vW _xݶHf:M3DLjcLJLHDtΥl.g uʹZD4)s>p! ){#󮮫_#H&o#Gݽ{wp $fQI,D-$Z`MU_+D^^$j2]ܺ5?TsCNlJ?"[ #TMhMTQk-uG5GGG}@J!T*0'BtEf'|C"d3C < ** Ȫ֘\i]Q`NrٔbUN&ۉ~Ukm]|ld޾9dj c@!DygRmedSnN)̖"{ !Y|Ya6Rƒ581*bϿU+4)FДzj@j k sPv"~bR۪@c !cNa?;>( "[=I`2?@E 4n?1AKo]r( 2t>ӏmlRJW/?_?ج7ae) Ε;Kg]+|Ӷ1*Ze@͌`2vd7+رIsbUPU1=Đ;^(b:m?zb,#30!`hp2j:**JRM" #0*˙7h@%r-8fda UD:/LVr'__zq ^*QpYUjmh1v_b(Ksr;5_gwg"E\U9z{zM3<a CshUzFuUgeQYӜP# Kw dk&\.^^CR[X "!<8 /*"8FM슒e>8 hvrmZ 08ghͦzZ!Eb܇eP^y7v )@Cr#lQ9qLɐ!yL/Yh6Ŧyf"['VV2U}g%YFI1-?RUNݻ[F&h;AUĜ+'ѼaQ}c.IZ%ЄQWuEHqlV8˗zMt2YMU53ӣ+JVT۝*2~u7 Ջ8m%ơn1&X3CF-8rUQ׶/>pEY;$[?ܽu|wWgW??!.K_ hE᜷֒1 ,)q]UXI`h~wd*C #BPG"zC꺪'ӺD;? ?pߴ#1brEalݚ[c4y8[XrRs9?4D XWc*!p޼|qsʄ2DDțn08MFY?_zZUQcE;9_}U ؠUe5S᛫]"c "sqH{Q!'9aB9TY1f)CUU=;uwgn @ADg e= @1ɕ !V~Gf3;>:{6ŔWUUM&SCfٖEbCb4MJSٗ<2 FL)?h:FEBgggݡYpUT@C@UZo}w̒R+~aAPя s)*Q&w;w'ֹ$6-SU!3 ϢvWWaA#̏0ȼnQI 1 b5d'ut^^Hз IDATSh8{tD`$D nY]^a.ɝ6/ Ջſۓe5W~75IUU,8pАGZE$X1؞sEKcX}Qc3tƕ"Pj11( X6C7/..9")0 Dʌ7ZczN,)5&E c{vsWf>bIכ<-JGt$5MP:"dLݹ>3!*IXs}O>9;N33 u9_׉% @U<~몪2YgcQRh^=!1rypx>iqdMtqX,0S̪D &U}QO(j8w_ESzų>д]/cNC''ƚl{-'@֜SLycB\i&ł$,fuƒ"AzCHaG\D-%fdƀbD!BJeYPeLYϟ<>El8=4_)@), )P-nm65nԵ54?>:{ v=hےeY5V@F^E@Ŋ1MfAMl z|u٬i!ɐL)Ѳd8]c (b?!]?۞SCP9%"hzRBN,EB$cSJ} 'aQ漓剷| :&+5Ƶ"0!1 d:fDܮWK;KTL'4'KSCLG?/p糗ز(wtdћӃP<)EEv3_i+XFL! ,Ơ&ORu?fSV۷.NN àAȂJ/K,Ƞ4Y]Z2Y_DRKbV1٨@DD4֦cņs"f߼?ɗ]K!sd |;({ŏO>K}_IY^L'a%kh.BαpsEQ0|>i6В!j5[kegCHy,$j> zωκ؟,Юׯ}f)2жW]dޛ߾7sc!ʗ۷WX'WC\xoaQd"! Ao0F3'N)?~X{tr]U&Ӿip8>{CB1!r2ul.ظuvG1!㬱֩B! !9AuAD5( f0\Uϟܮa[ H )\OZ}Qe 1;XȜa8xA S[&\m]nߞ9zXDBU2tج (dHh'ߍ:R aq;糶iΟ={7ウ4jˈ:v[֚wo1qYUeYf%CH #29?cl=^|=?0 mX˒smk~" CpΨw!2g "jF yHEG֛mQ|KSEvI Rd.2;b_ Lg1䀋uGcnfY8U35aB̍B-paPƏ'NWW=֡'cb7$DKHYc9\57&_\ߙԕap"VcD"\ASM1TT@,|Q8o, UdΐtJHsh6 aY4BLDTn (! Nj)Ɯ.*AQ8IaƜH1I!PYcuCYȉ(1r(6U5#[j;w󊹏}@~]CocMd;]TU8_kmEs0h{kn$4'qx8= .Cvlhzp2'N9BqP% "|e*yXݔ<+QFYZDB,C0Ƅ!J^cb=]ׯ״^CJbau-̼e8mnGd0lϷ/_j+5()E!*gr@aS(U{61?a  z6~LE@5!v:lH8[߹mb 2hLk!" "rHxP¸Y+|vt>|zhd=f}K5c.)7|F"ɡ~֔B @l9Ue}!7Hq\(z],ℕАA V !|n,ZmqkGi&EQOD6a<ފJdYӏ?KzN~Q|y:G`ӷBGk>|{ðw}/̛_2&O ƍ"("g$) NO HKFkm}$R~+f C}JC~bvEq]rncà,2[,כ ĘCQ:UD eYX뜳Qc\GGY, 8ngBIR.LLID90ci=yWL#lٵi{"N"CBqD|37MX=zRT/ D4D9wcɒ R.1o'$̂-,ؔRBbF~il) &W,Hc 3#`۵mV+!k]]ZWUiF[UTe})h@˪  <_N}]r JqL wt7>Z2f4+kI) ͺ٬ kG'"?pڜ_5WOٔn2{kMnb(fG0"0ǔ{f U`f$>@`@Eb( j碚b4Fvѝa0XkSc c'{oQ;g '!(ZUPau*)H4["gOTܭ;(qG&ov" MUZeiNɸi12F3Qg-~w˿oeYaCue sB:HFIJ(zh&mBC7Ň(F% G2n%Ac%㏔E5!>oSLZCA"QJ_d`1d}5t]v+)֓zqɃ75#Ð51ENi:Zcos. TT*B~sáĔU (SRNnhA%kO/|J,3&w`Sc-!o]UeB)T&Mzn۲Q9WS{3ވ gJ0HL&-@Bm72(pbR322"]f1Ơ1>H)8#{b>/ W'Դicb n!֪=WJ iVk91uvs2uF@NK)J݋|BH+Z`P"J&_*LTJIqM^8Jk-nI|4ͳX^zV<}[B?,c~ UbZ30"T9TtYǏ)\2i)`*B032\C8:o#e=.CFZՀ|<ʶ!M7nJ+^x!Ӏ]kfR,K{d)f<|8t8 C?nFU@"&&KXd\WQ(c`>=?{4DM%B9?}nrs›DBSf!FMM]e̦@nB!T:550!eRL,}1T}ġG ATRV7[*g2HZtYTBZ U+pUClJL&LoF}f:Z3njiuZL*?ݒ5)%sJm6wO!vSZ4m #(,#jlsΟ>|BEp "36:<:'131Km9/>`!4[k)D#+5  \7\]yOg0Uq®A15'ic t>{_dJHV)\[K1j)'6J"j&yUtUzcliNmxS`퀰zx 0ϯěW/CǸƔ(!&'L4iDmDBt 9O7o>@  W?ɟտӟwnWrUtª\0020_RDD`6?=={_3"RS<Sa3E@ZJ)977vP*bz"994 x qt?|P5b5M_կÇN0jXmm6#d$5q131Ȧִ,ń*y *@?]E>(5cn Q)RDĜxr_3=O)!nLm2lS9,SmjkaF!bS[zO92ьy[n^묵UlMk?lS7;lǏXj9+T[s> R%;%D+DLDtܟOG@aҰ黎C *Ϙ:IMJy:Ua٤!"Y&yě0 ,  LGr6iSL1%Y*2OR*Ŕ6׭IJ-/~}rsujT-S뒩7 ZT49k)"Ð6&!r:\'c&JH1&"<@dOA!8oiJY4].'5347WRUǏo~Ky2W?7aYr1L@2:uM"V]eަB>|vgX3&dȬUs.T @p>[O_51B#~T 3"0^BtgX@*363 a ώvUZ 蒗ԹrI3PWpAc'V]g"o˕YP} B> 6)9THi"`Tk90- 1`VR(Fimyޫل.AըVz}&V iS5JZ܌|R(_Jb@YBDLB`D, /6USJ4K 1DL55!2x*Uć4yITvի/HJID4 1M쳣6eIMuɹZK1!?6c&.4$"Ri>.HqBYELz8[19OA%<>1`uS h.5-RޥqRZ)m,bjnt>%@,HuqD$tAFpuo޴Zʲp8ow;?E .mwc105FN|8x1*g)T&$pE?NXKc7gWϜ ~Od&m@~U\"%/W_onC?(ce֘k0402mI<)Tjǁ\ץC ͛_|iߥ7_AD\ q9Un}s'H}?nLh@YqboV(\ (Udk~ Yr j߾}_K%|NvXj&1??0~? v~&B-K|:H&Љjhp jlO7m^0VKK#a-9\vke},07՘bLԲ?쟟C^z5p 7OYR nno9L8䜛1vIr˧L[7כ.D95ĈL *py̘=LJ֪ (^HBZ,ȁAZK5Ph"2b%fEkԾ@DZ1ښ\T% He~`at>=?=ccnyYW^n\aVGE&B]L2weΜI룦CgfSVk9K,MBZbL}?Km,eH]I+i>omMLWVU蛟ٟXke~2Ry10OrVAyx 6m!2Ԣ"pOIkpfԹ}1!h̋ [D(|'NsŠ󳦂`@x4)z˹7{x͏|?8F.z U &$l"Q9xs )1# 1q |~x@U҂B "66wa\[ 䂸]Okh2\eYƛ:l1\Īb 50R+s`Io\ 0ҼqPK_~;1ju! Urf{}Lq}WMN3.9צYD9ٷux!b?Ѽ?R=$͇WxZрPr 0s<F[vOycx=T% 3ƑNsΧ63BUDU> D ӼM:Bp>7/_\]sJ  X5Ro?˯nKTQEƀLTv],yĤ BE#T!a"|:^__~pu{2["TVkeZa8HEWoÇ/\9I OlƁa@8NEyhCyV1 q꺛<Ϻd c_2Z(7x<'Ѱ 8 럸"Q9D]U)X0]u 9GbUZ[.tUHjz:J.&%S~zlr>òPwp5ǘP՘`}?hkH" afg}oTC`""`Cm\p}}33{Lʡ9 @ NR M?~7/Y #s4qXg3["2ZVIv[CrtD] eFׄW"'Ũ"p{q/:wi#x 4A&$Cq-6)a?vf iJvC*,b|~T9U1Z<9|<~mB{CKqÇOq܌+-P]&!!QHL3QMRVzᔴ6_;ܚt#pV o sk |R@O-Fw<0GFDeݽӚ'"]ߧCajw.NV "r0=ŔyK@L 8-0Ry{{xx +Uႃ\GlXt 03 LqjMrWhF|t:!Bb u:+v1%~Ng??>q$cip8PMa;vVB ֢@6O!ڍp{[i9|&jdmgq`k-1YB@83!\|<.hMKU`PJ}|| H&m9wɤNLl6*H`PKIz ZXExqs Z3ծCn.㄄$*< pC"Z[ M?0O缜E%p0iUR /?D*(S5@z~SRk1ҪgҚ:nooYFdxzּ`w}6pp!Jk`HhbVJ=>>|_9=)!\Տ~Oϟa@v<?}~@9P"byc!vP (&nhOb|6RٌW>[Uφ;ݽyT`/.9! O-P4\xfj##eytQj]}s4`HQk@ɻYO.7~~^F7wmӸGS6`m9t<_]]K[17@-T'j)<^]?B@EMjZ"#)Vy&-˲@4&ԚffDqYf˙}Th!2*V1kݟ|#ԙq.80`O|wKn75 7 lUCU )|FHn1BCq9uZɹGF2TKW>'| ԧOԇdD`"<#woߥvw}&%|pQ󅈐B2FBSUiQٓ>PW""GN H(i1*^]kx4Y+q :6AE8D?=}+8Ii[iZ'}v`CT#U. .aY\r  4``f$306uGs |9tݟ @s>o{23h`|)RؽSCC~ED(";lURJ.4/~*KY}RJbO0n @NJԧDuY7(x_򟿊IB("!<KTmMV`/{V KS5Ԫ͵r<&NUs6U[O1H>="`+YE\\SVռYgiq& E)&{N]٫G~'?w^|YJ㓨n;Bkm*x8Nbw1%bYEZk6J3:ZMwZL1Ġ53b.%bZgEȓ<;Z!bLx<^]#eI)aPgqrs}Mh |:a0VH˰%Di:CZ#" S1S_RfSglR6WZUϧ0 !8fKbD.cEжOp}Dhcc?G7y^7y9˫6"6[<ÇoD9e;pPizD=Fצ6 s3"V6 ,Û7~ӭu-m?P$# S0B5c,^zJ5A %k{NQZ4 @Lž/!h?MƑ-!JRJ@nMQvZ*b+ )0H_N&`닾@LlD#"qH>q`y^<< =3U>#eC6Z#.]dRWߡˍ8l6 >\F9Qsir4!X]0!qo+oD%vqzz"z:F/ZNr1;\SPBsZמ]!<Ґ@ @5dLDTd 1"Pv'6iS- ~\*I+M`/D3~ 24{w߿L",K-T2Mu͈ґs{C]]q<D0n=r<l6f6n78S@-ͻ휢ZedU9pq4uʄDj "k1 iմ,ja WZ>SU3)93i Dȯ) *3hLtԵlY%jnh{Ղob?ZD |63G \N3!)!w"2jM!V|Ĕ5f&ȥyLE$&`̢|װ6֪\"GS`伄PJYrN1h,%2s_SߛpDZ[Jf87y/ipJ[k9Zi0طqu Ow"_b?tyTKLմ]*Ze8^͸ # I&yY 4Qi4Ʈ aur «޾}wu}nc=řĺf.VK t>, :zg{a5 {HJ0 dM4+^KQ6Tm5WtʇbS>pQuۚiRK<ܤ?!1TbSDGoM8:Oeqt~5@tQw]]o>?տ˗/=BL8'?#bS&dҀZZZR c.A|:h?𻫫v7i50ڊ Ƙօ lLXcu`" "4=ԃ<Ֆs^]_σlq Fd"!y2CeOu-@ C2m-9jk .q?V:M'-J0qLl77׷wwt.孧aa tFmZA2k*r|`9\_{:MͮK}&Jɋc,sk|:Z7%wNwo2nJR@nښe?=>~-Hl|v:ċoSѐOfPE*5U !)1y(<Ϫu>a ׻ +ɽWFxLlOoC)5$(q7_cuWWח7\"eQkD(֙ӾI?VB _ \Amk*'qm^.KE$c⇀%mb43\+w믾L1t= n53W R)֕:C Q]߷֎c|x|<|{_˲/p{oʬ8ږnvA$ۀ $ m iMVUw 9޼֊?'~Afee9{X}@!mժe z2}9Y_=M8yBtK"^]``Du߭u|jL)$C;Af э'y,C4K޾y?6-W9'bDs&tȚ% ݐɇ .0CrϨUHa' &{Rn]Y%q,E ȷUl4)ΏRBn6?*^ ~SU̖Fi65@-JXp8?u]7ر;CΕ,c)hZͰ0` UUEu-E\w}wZ/^RNt>WutmӎuQQ66)$b,9CBDU`1KIT5'9;{`B6Yؔd뺪>h!7ۻ`>R u6`jS 1"C]7%qlZq݄94tUhƁUu ?=> sL QC@뚦{wTյc;*1"H38pNk̳h9T%%the70*reJTK!un*Gǡi$ @JU |7N8/f)#/ױ&fv<NҎn7n{uMf9t @}j0<đ b.d 8<>={)9e3{v}4MyP)IH@ % Fؒ!S) |9aDhQM0ܝrv57-jnE܌U?޿_S1Z!Z8&(ZS*Y{Sr:]׍8 cUULH Qs YUUy1$RI2%@8tUU3;շLc(L)K·w_}%sȌabBaBE^o]߷MB40q R!")ogi%p}79|n۩ ct˼6P3{,%g0#GV^MDx®-5U۬vc߫)jp?iwCzlj4k8 &mvL `hۥ髯^^__aN@,PtG EtR?v;w0?}v[W:Lsuq6y$U#Df/? njdf %2 YjF>. /e?o߼~eXo6zD 1~Y19̿ 4 R9lΉ#D6y$*lAyi&3{0rmsĪNٷ"TEDzNSNYJsBaCU0S٬)!(Fª9#Rk3eCK Kbc<~ޕK)5,~sV3kp8)BMݰj:aT4JN2>>DX SʫU;{UD )t`jI٘x:McW#S,F0]24@(RysCiyp3dҒ4XD%u%e3'J))^NϾi &MHƺa9TLnKL.E$@Y~aoan/>WMIǃs3_~0nb>'Ӑ|":MS΂@"IT"+W"̾:PsdUMC0bN@)bXoQ\jXzn$TB7^@|:9YlLWjavoˮӖp/pT2"O+?zlj4ʦA346T !dET.0mqVqSFS`*")OHhGev}m>z]Ҥx%~n8Miͣ]M0}issW-}KOS3M)h 6So49KNLL۾??=pTvG*DLBUPLnO~bT$zcvtMW~mu+}\it2w})Pk)%rHV+OhdR:"g(*LtoxE'2'3e:af߽qMSDUSתf^oGosD=5ETJ$lFQTURǔ/_UE1W"Ch7Ӈ)MhY"9ijF!_at#6mn ElsPU$n6kbfw4jXf{NEz!Ri,9X}0MkTJJ)0f1͠Cd7!; .yI 1Xb&&gJ TơUwYΝ WSjͅhK+`^ `R ؇Պ2CL^*Ǚ[> FMgjfެN')@Fr@E%ǟ<<<\__GUP:Rյ>><_>==w{_L-4 Lnӧ~\7Ed".*B&6ǀA)10Pģ*TqJ}`T]D^jl Gqv`9E<zޟdo_eۭw;o>2" Qy A˽¼(d"AC E,0~i""=DDE]wϞiJXG+Wkb\"jLnfR q xldReƾ/TUvƳ.U%u ֻD8A`EM"ZJlu{Pu}35y̡̧\ !YlF%A->5V#PӹϏJK)L9O9~9S^DTcz?O>yR?~sibi67b/taCsGb>n{:]fJRJ)"p20S$Ef3De@REL*bTE0]2as秇4[3 \Muy;L\ed3Waw:GxȶzAn^k4S03-%׮!M23H4^K M"eJpYxUqr5> ~=ow~쇜R#9ziRSR3"fDeBJ!m9X,3 h *CP2/VԜ IU~:B@i (ĶrV] cUޗq"^$d_ibOT$XWSJR VU]EZEH{0-9XU]vzxa<WU,|"BzUM40bUsf U%"E882%ȥ*G "q,mӄNn@3G B`BRT϶]z'QE Rؾ|ߟ޿/)1) UYT? O$UTqE82VX1#3"h^ MDK6XW,!?wWX:ŧDfnwa02yAOO$S~B|vO R=O Ev{>wzw5|&䪮Dg5ګoa f_R%!rݩIM6! WvOw! aΥr("%!Btug?n?pU ̈cC iVZ풼hBE1[@C"&Nt2/NemnZUs~كzD'b(XDC[PeLsd~ͩdY؀!2 IDAT-[ou5VcP cըB*"#"F,R!*Q.1PHkH}h T%Zɹcն94fF!HQSUyuZZơ#4_H݂jdFf)5sར_-ydC2 x 0畃ٳw^EB` R^MtǓmX|!Y??A^djU9=4)%5GeAUAMdS-y6ULsnrM339E%;_\2VuU9u&*vwU H2e4q`ywI!eaj͌Cpą?,uc9>,-Z>ƝY|>׎ n6uHC_ߠ}J1VS`&FXEb&"ɷT&B("X֋  1JEUKǩb#|2Ǩj&h.ԬTT9Tu>ժjZ) 6o'!cR$^DU 9FiLnS- !f)"bR8*WlF! y3pcja9usaef@$μOB&02NNG 櫫_\xA4"4ެaJ)L@" c~_bM#R4B85*"lg؉u>Lgw<asgk߳UDJ plX=b,D1gF b1Ѷs mQ1_< dDdrQ3BMmYmji0 Ed$6$Ij`.כNzR8ZzyQdA4Fƫ mKD)jՀGիt!@`%6mn0J)u~r"}f~c4 \&g1W5W`b Z?@ݣG~{}#[!1R2 ͎%Ra|W6w׻m6*ߵ+mR!'D{|zzzȮBwKf;#i"&)b%>_U|/mzy "3\\ "!=Ȫ>~ZJ)N's2"2sn;URrNi_c @;bJ1ftVqmR6ſ?O<ww/RzcTRr/ f\*Fl2:@\j3%" ^p/쯆)BCDUN׉˳-] SlqH.FfMϡѯ:# Z&0u/֌>,i4sT̬?}ZeydzsXϟ"lU"EDpsowl |zA'\iB"Uv6BD9cx|: OOeI!oCԫ?CUH]wYSZ21CE ){uQ3RTJݞ8Sn:)\m6WpwxLD̔'NQS`VR0YUM%U#$AG?WOSj"0GjuuO[ӟ?OOxz|w7O"RM ˖tgzRRv3.SŃ4h6޼9a D@ğg'~”h")qQiDUsۿ(zx|eMEg鯂v&vNY=_R4BKʼgLg\uxw-"~PmHӘb<^T}=n 2@,(UK)>áRQUR7UT*)RX1JaҝCbnh:+ιqauas46iɥ]]۶u]q -:&BD,B]t< $fU5xQJ#""}3BAƾ686q/^ȴ Ҹ[/g׺NCnm#E? T--a a7UnMI?&l:ק^O}DY`ċ\Mݤa 9$%#!|{05-HTu7ߤqK|W,rɗY]UáM*B g7oys}3MC4yL01?O߼n*6FQ$)3um8RD7/^<ާtSN`PUےuEE~Яz"DH0mݔ"Ԇ0n}vxa\6L %k3Bb `A>y}^KןoZTX칞 "6oYU)I5HJ s?X.әug<,|:׳ew l~bJS9+#+WOu qk%WCUUJ7 羯1gϚ !5(tXU 3V)Tu9NyJXURb]&*5NcQUnzt<$"b6Jq]F$`"o|>4lX桡B4s/ vnBPuk; 8B!T͋1MmWNh^х50?T| i/^2g j3^jmq}{[C"@Hɿ_X4yJiպijD: wiaYmSifFLNO%f86EgY*2͇攂wUEq~<śj3ghoU-I\^[ ~pB"/,K:\:t\t<}OWNl7MS_ 1&W!Z.+ '-\>)6Y9E5Mi0 msMS;xzD JVG4%Q!n7k0r*EќG˒"CTSAf2'}2aPxR2UUC?o&t,۩;yޛ:H"Suj600yu"cx||d2?n77:0J*nG_g={Kfߘ|$y3 SDd@&*)/U4tBTZfЮz[ow/Z "IJ(JHr.9լ"&b*Ȉ- 5ww_Qc)E4j~?n PE< C:M.HNw]̍J DEE6Ƿ^_yЏUn޽/9?~-c"7 LV1b :%3DVDJoiG5%D۹"_>r¼5SӊPm.s6?B, t4  \o7Kqvb~jDz:kbA,Esu0m2 R^"]t7.ɩ\jssS;C U{ެsNL|>=M%%BĮy>#"3&bty,Up!1Fem)?ki%dWk|gGwXSUϟ?zJ Hڶ_CDSAH"Zǚ#1a \1|<'B9l6oR?ُ,JTion=\>@D Sfb s IJq/].RfO"|~ PRFĦi4%H\7fz63.Ÿ0K]몪Dslܕ~-=![w4d>v@ qޗe"FD̠& nğ4P|y9 %jLxNsJ`ELfr<r6<ŷ n,v}w~~nfUjCH ً{2L\Y@E+pL)#C TU& i 1yEC:Z3UC(TiqFQ0ϼ0_WT8k{ea9Ma}}7y\Ff"R- 9MfIǮk.z桊f4f] Y IJᘆe7 NcWzn3t~|PS4i2˄UU gSCU%ScLe|nGfnNǻqj6a8w ~(M~!]wr49%3F@!)JhΥ"kQ ZJJ)_>/v/WV1r)u?}״[ \UUcwЄT 6dle?Brݶ1!mRN2NjXir)O(N}PY Ks*}ˌa1BdSuEΫ({zͧ_J Md0Ǽ!⒃D>A-⳯i{ e3dW=CCc ̜3oh.6|>N-"vvvv9gD,"rͿID֗. &*"\riԈqZo71t"ucu0B\(D2M׊)8. 1Μ IB" `l(r^`G2nw{ cTE@^KM9gϘݽy"j !0n{l%E_3LƗ1=zMI-zOf0]4fw ֔o߽-.!Պ)V"Pz D* Y QUTeL 9PY9KMf]R{eے-BHٴFH"*RDϷnJQRbZPaw IDAT%Ϸ<DVe ԔR/7SJ`Yizt/ ",/rDJ+*KR @yBK\ko߽;!bDUTAM]dA@"tL3VQ j-%^\Nj@38U׹Ъc~quw{s!DSrA,s=@ų*)f#5`;55Y>ρ[l}1Fc^?t)=yLg`i_3{hLANBN-| iٻ_eG8ãS@‹˫vnPU?OEEe%` !D"Х aY(h84J_*.Dn|4HEj:N#C &x;O&u+x4ǚsuy%3qRk{xC?\nJd']q5cð|^t6XW 1ǜX4u>< }2M74Xn\G!XyCNj֚w`"`VKvrISeRku kNig" ġOo_}QǨ{YDOyIrcU1K&.F/ CHԌrxܾ|oY<<><qow̴../ "ZKyRj^2#SW8N|:2#Z2"S f3DZ֪R9蕯HeZyԺmnnk\ɓ-Ud{}`")ԍZ]J4!&c,LJ= ]_R47Շpٮ߂յݦݶ{"f~iomDPkRj)ًv"2?-_\㚗*Z+HUJR*/K͹yjRwE> UOL"eծ74xYj;O^Z|8L#6@CtoLnRꘋR"%B];mDVk٤-xPQiG~iS\\#@neaum6%~Lcj9JvW PJmB `Xl3Eh.(bƁH~7: D44C4|Z'IiؒoT֪.~ƊzxϘ6+g-3듡ϯg6n& p1(c 7?4Ue{IhbEϩ@70#Q qZ3o{Z9G8wn(Qnc΋T>R|y6Ld,H)BDnW3"iŋ&m-wl߿k*e^4;shҧgS=h-G]7yhFfK^f9k1C7~ZKfh,Sf",`CJ.ik,#UC &n*2s3R~7ĐK9v9&1wo,/˻ׯ=ãBpa{^=a ?Gma EUKf:>>0!r c_{B `ZCVy2qZY M鑚:=C`mէkK&z\O~gB? yca| Z{__oq'I|oA5v-"ɹ[EZi&Ջ:lq-;ҳ<S)|u<} *}|bU庤.D*w2! NKZk^<{9"5&o|o6&:N. y<\}@>i*ZQ hHjV6ƈ!v1Nn)H1 ~ (^Hxv >kde훛_7ʷٟ͟* 2*l3\Σ:9|{r" -r?Va5}C=ClwD0sK/R N14W̛ACRƐ:1n7:[ES-@{LJڳU]Hj W1Rt:nl1FbhyN*TS #4yTnkᘗ@c׻kU4-#yPT]c}޼y1F&a1 }p<{w[ky,DFHPJ%w]<ru`caU8 |:Ӱ߇BL1& ×S:wƔHnowM)6GJ.1qc\J_RhE1xT)q> i!DCpt<>^9Z+u]i#hxŷK@Ʈawv(4 ol`x:>9E<*r PDrjnSBn3,qJ K.`8c`&@%"$q))ѯ/_^^:QA#20l SŞn߾3RHɧۋb*BR]d܋="r<)3CRs&${ o.: +t6x-;/nUDшǬӜEMݾ=tvnn?gPˇ9h:}jǞUM(@_rHn(u+pT M3͸]&>*ReogSD:ܧBs~o[ kw <0Wr~''?9?=4PDDbD 1m`&xP0o3.uȓ(9RDDUrx<˛wwLn~8NU$J=}3sb*]g.0o6nBF! kH2N۾UP6#bayvZo}*0‡cP.,/SZ Dmf0rz|xTdJ}`u)R+q:1RիywZַ_n6x&7[L4nO1pyBťyYT5vYxr r`:NqܤL Uaj$!u)////?'뒯H5^\|~yRK&W.k:?џZKHIE(&&z|< h{KiyᇪRstۇTq DֳjABBH]GR!$?H~0YQ;کA5_.//J)G"5#$TJ|m;g>WԼvèf."|^p'7[ Jt~R=<4;Ոdl>rM]Z݊]TkyVYj؊ PZKo/irm"vf]=mPCݾ}DPi3tlY:G̀ZКPW񫙥.Aה/&m\JUB)94~i&edUgRJق]eefХ.~Wf*.yxUGݕ@߁Ѻ3%';̥K Cd"en-D~~OS)3Z䜗{hՃ4<>7؆gx9ƜGONP8_eƅLɇşVkpw3'jeywJ8mjD =T131:w[>=5Q!@D~l=#Z+Nt-Į'DH!*vԔfWW7wU&BLj]ű~_r"$n8}Rb`lt:M/.P=ERSSVJ=tgyY ~:\PJ>|SS`!0u,3K@%"p??T+Wl쒊I .3BK0OnK )4tD$bI @}mR "SfbN|֚ h+F.C_z><}fOKh8&&"\0U W 1"RUEPV>FZͰ^E3?),V y#/UQU]{8~蒙9* 'LVKٓ5Sz7򪊌@8e_e#<=WD ñCGSa Tmb+>{oYZ,w38ϟ$;\wh-늈5)Ew4!q`V6܋':AQ˹bh< z"ZʺATDND%gk$Wbb&W 1.UST%gcRRbO>s\L)3o5IUr^aERLDUe c4asc,ˋ9g3=͠˼h*9.P4Mbp^^) @LO)+(UL$xB:O桷%iK6cLι,3ݘxHj+MUk(F0;l.!5h$8!|<sp{{}ZkRJcyLo1(DS0wWQ&%vW6 C=ؐRIڦGw%gU8b]'煞=f%\'_Һ^]yFk .)nwT{o~%R(Ztȍe<<<}<soa]ׇk$\)\Fj6Sa`9vIKw`! `ts.DyiuDDDMC9u)vqZk_@"]q^mr&Z\K57 RŠR0p{4B)"".v|e6 A)bx4D-xGWYUph<D$3[k.61 CdD*_xzr"bdU#dG=. ;ki6K&PPSJQN*""&vDN?q:}OTEC07?ͧd4"sn#q 0"f!pJ%5OI SxRTk^K*`)؛ilPV־0sUB@DS @f 6/y'Yf5m/?kCgD4.RJH#,BzGC3V:(VpmbLWqЎEN10LfPJjAB@8ox=d,j@HϙΖGYm}pla훛&R%/i_##1q-e&5~٘Y}`D?p\`4(1s/3ow/{Q>><^]\JaxTS31P`]9DXJnnt]X> q1iG@HL V251ABDs/?Q<H@@7c3*R?~Ço0qJB6[ykĴ*S<3U@{;03Q$_XTi^f3 +MbJ^}~o?Te9`+_P˗/xPykX8 L@F?z<]-.s>W*gsfru1}4ni+fC(クq4S}@Ro,=m>c8t]J)!`J 7ÊSR r;[b`ADjM"ȴp,3m{$ZnT)4b< (aU)M-vڀĵx>P !æ<8ƾxƅ  UT[TU5j棝nY"@Ԕ}+j=2ownq}MԲ,HhݰqSL) Ai8{znԔBЅi{M^r%)ۡXE$F\?']π~<ipp]b !KU '8D+zzJY-uEKj]^omՐMcmv{r.!n_y)%*'R[ o6i?= + VUU{V4řz\&*R ߨ<>ix Mlotc{ dg4ֺBN(Z}gڕ+m3UԲcJww}Ǔ.3}BZۙo?rYrp9ArqOₚZ8/)bL-,uIEy)JƮkTEfOjslRjZ,lzwZ,k- MYp^e!4֊1j-VD,rRUAC{t"03$ *4e&R̤յrWΝ*󪀘CJeYBB}ݿc`N}򲈊Z VKE<#iyӤjH}C ad>eIt y"p]jZY?" i:UJg;\ 5l \HB0vSPcbDUf%X:+0fykLf!GjGv{[/U plg}?OPsyyuwf5-jd>\.Ԟa ϔvOfD\aR(B/%{Rx+^9.c,T#"~>л%/Ld]?LLEm骺ʋ۾ZJ%B(9PDD=+,(, pEԘ"*@jUS$WiKuIK \a"MC RBDdRcy 1sdA61:fH|kfZ A>i1yD4RhSo8?ٶ޼_rF$5!.K1,CzC:wz kBc Ù&bla]!lvTAH˜~SҮ_1QETEiL`ŅbgnB$)LK6]a~| +8cRpĔ7xufr8cmtJ^ԗ !Hbʈ҆fK&,/_͐KVY *>DPy D9|m``DR*v*D1iTe"\q裗)뫫RkJykDP5vZ)lao}Ų,6:P\b byRDjlT!}Kd6n V<;+ǃ2-TEc$ļ,#ZjkB]ڌZ&nbL0Bd"\BF>B@EK)-UK{Md]X^n?jurf.i 5gM]絹lMJY>Of3@W8%?|MަG5ރ, ϯ&g*a!C Uy|S39Juz392/\@*rhbfaTA5R}\ 8#ǠUCJD؏TrV`-wTEc`BXD6W~%?Qvn_\ 8ZS BVKVϽ)L!{AO̤@D%a*Uy/VV1Wȟ^,D7%Ilq8K.ު^Hz(jn#@@OwJOHPE$1$4ivu^uof%w73=Gd5]]ݷ2ωp37NO?F3SDy@L:Ɣww !:8_wʲWbyf>?N}DqXh7fAkZ d0Cbȁ<#V+}6-VgaH eɞC1f1ġK\O,KR\J1],|%t݁Վ_}7"ebJPBO<4Zd[}"n~?K3rEa?M[sUUVjgE_um Pɥ?+"FHjuE ``@Ab%2*R  DH%9@V"`ptj:oG@n`\ՃyY2.Zk-˥ڧdR]@/.˻wnG Upsr!U]qC$fZs1J)U7C&9/y E*GÛ7jdwTK֒k렉TT4I5o*L:"#By:_ ۏ?ˢ?J,2*Bmz,[I!Bh9Х3PCZa^03B?t:.֟.Y$LNNMa{}bdVUR^Zﻆ}"6MeY4ռ"+(RڹS ^g=i6\"R&+?9ƚ#%dAU#B: 2/mef&+^Y)gK !uQ_T$]R x-TNkTQlj FAk)ˢc;[r; PD7O_}wOy@DV* -i9}t~zn84lQT9" S<   Pj@UXRZ)y䜧TcqRyʲ$ZDSWrt1@ 8u?r>_w]>"GN}H]M9ĠʈZM*.1@t< ϳab@RϗQkiVlHL1x>cc }2 8Xr66Ic<w=Bfؘ%NwjUu1DCY:&ꆡ IDAT,OC!zoB:iB"B>/c>1ӻqL̾zxp!PrQZ%fJ8Χ2#٤Dj`+b↝&"(9#Q:].)T1=Q31"ڌ%J{?0i}WdU% )6UQH=>UC ZE> " @DhD3rP*(B1n'5@՚ aH/5UMɐY`}oC`$b1/ݭ/~q]\]*By`u []a&SDM49 0ǟ|2ݧ? ZCR3T"!xƮoӿ0!5 8[;u:w}pJr]_\ݝn_Չot>x6MQ)wWe˲^J CRЋy+> ϖb4{m θVDD*5gPp!4oNAKmP:_(u~? -Bv4ZKɥoXJR=^kͪ!ХSQ3rC8s}ӴzNVg6<4cگ#S#/Kmя/ %yvT@B4fHTk `uRRa߽})$3 ; H UQC-CUTDU(3 eͰŵm:wffxz:}˂1`wW }}]N'BpaXmE95bq E A`ٸdOL@ w/," irC90/g_|i/onns) j,3"8iqG$J]@ &3.\B  ˼zk-fju^àj5QgTi/nbh1444==MUjY>t]w.H_vSPJ_!oomq; hrwst斉eY0xOG>ќˆo`[.V*\Z<9gM/SKь-2i6?v;Kβ,Dd""IM7<Y8!"WڣUs-NCȞj-M>SJ)XJ%"4.Jˢ"kE/^ `YK85m⦅f+FMS !BĵV'!km!\j!3OD̉%1]jG ؓVC@("fHC ,Uk xVs)E8-zUwnHRmN3!s꼠UUQk8Rdȑ3IYDTQq0]S|eTKsV#_Z;`1C̮솁iDk~\EQT#Z*!L&fBFZJ )vy;8i\K'\@I&CBP'[%2. G!*x͛}Ԣfq(4SwNLxzz2Z?VTA! eM1r. V^6z;K-!ĒyѺ,"QEl}/*ߜ9me"3V-nLU #]E1༻6D]g%`o[ĴR]]t{i"ľhgGEcR!"n|@fak76J@SUQgKYG bY*̳"D}ww>Η|̆(pwg{iGq+#QպePw:Z +af1Ւ3[]lq! !.{cZÙBӒC Z5"1E 1biͽ53M1""1y0+*hͧTrWE"&뺮SsEK}Y!41v|x)i)xkʾ 94/xկUj|1Ql3kkrvDkr0Vh/a [kX#_/*^V&`!랞8uAD)R##  SR߻ ͺTJz)Sf8!0/Rq{C<8;= -Ue !ݢE6iY/5~kE#WELk5MSVA 7cC9Hd7$@yM/s_gZ&DqW?*5mnwQ֨Q֮/77~@ͥ"URҜiԻ4ՑıWb1b_9l_`=>O[6w^?x#pDDVy"1:r0hj% J-oZq*1)b"ZEZETRkOz0/ÁbDzYFB]J!Nuo{]sy!uq!@ /;" DȌTDij=.IR\LE)Ʈe\?ڠbF`c!78{3IDȝH7ЦBU5Z\%Jxr)mw~3O pR+(pJr= }RrO'0 QsЫ*1N:˜6} Y)6,Yеr+3ʕ-@!` \'1Ʈ}L?kc|8oGIwm4jRiN@߈z35?axsw)!Ƈbbb_(d9[)_A}!-NѤ)3[m=n-۴UG%|Լ_ l#@< utC.n麮,whՐ|mVWEBK-ڥ8`lEU5xz.. C.uyc BBO>wqi29$ ) -OOXZjp1\*X]W!Rdd)0 ĘaEBJ##KP]:UyM?4SF}VFu#J-./:i6t""E; .E;Ǔo10is`FNȳ+8ۜ3_~O"@\JIz fRKv3;eղʦ5v-?5@o^Ua7잞~:A!)VUUFD;݊բ LY}nu AI ¨ʍ7~Ria1DleixrCS&`VVGZD,O8p8#>==NӄҶ܎"rG@*RRnRh.'ZABhEΏOkO'ejva'Ḝ_)DVJ1s޽{onn`_2twss\435G2wc 8l>Ht⊘(0rM;BD ʒ_ }F 7DZ0BZuAA+Um֪N5M3nwx^OgIk!4RpZ!u2MWbJܥϾӔ/|ٮ 1Jv(_kL~g8t] wPUH1I~<_8ĒĈcRWsq_cG& NJ5Z23bJ Ktbkj_Fkox{wx5&".vbUS]g:E8m&CeYDL3l'Q͐|vUT$K5mM2dk9b-c XZלXHg,!D9l8c? 3bbQHqɥe^tƔX{!%* )P|O|^ߔBD]LzN2xe >#fs)EUDEų?wPbD!`A 8$eۃZ *Vh ?5ˢiu^d(:Z)Ro?;^ӓ p؟~/!gojׇSe\3RW=1;k2H(K68^Oo?g"eQS3?bzew<.1^/7^Ib\NOZ.Kkw9KoiCí ؋dCd6ғoz5=8N9ڽmkZˁCJi^s5qI텀}[؊Ȋk(9]5; +eHe $$\Xkh&M*!x9a7`V,Kt:KVӺmMzmb矷l@i=~_߅p\K`b!ƍsfjDs|>>0y%O1TrC25m50$" jĬHԒTq~[b߇aPG:dP*ib4ۻ#;w,- 2B۸Rvl^Z ,y-꼀Bbt2kɛr8Qڞ^/Kv6m'Tnb o@)=2W%DN՚$(o^R-b"y>=-DLY ԜAuS!?CC6pBaO_en7wUSWEeR.狩 ]۩9y s)j0ކ %Z֜izz1!@SG!X ѷ? ռR5:͛nIfTޢgkWEt׮1}4S9iTQu ۀuY.2Og<6/!}à>-F]I" !}T:~g%/Dñ?٘i?V|?L`?#sK m5nJT7Uنj/s6YE]B8Ik)Ru)UcD&@ -V!qd>6J-fcS8iKŪjRݽޫ|ǡ"u3;e]P@ww%& !/G1#$R]F!uYh`%gf~UK4Ӥ!*"sۧ!6I"Zkm68VB )[HKD/K#CM5WM~_Չܩɡ[V6: P:u23YD~y\2!=Ͷ,uk3V9UO~΍ ryKOiJ)^k6H(/n1׈Rk+v_u;oa- aߦZ|z|S"B]Q IDAT?ktj>L*c79GN֦ancG_ߗRsԞɉk [yk)<'6 !/*b]"0ڡ*UjιVgdc@EW"4|d!ueG>Wb ͛7إz'@*"HLUyԻ#WEt^nRS1%96O y2!2w}o 칰^=gtc'LjT5v!.K󜧩 Q벜TѧǜemY\È.iBTFc> n; ^_BB&!Z0|Q8mSjZثϰ@UDիHߍ ]'?y^BJPU.y8TzO~Jaն0p!hYr iIE"Uom[7-MUԦ֦&, Fbrݽυݸ&UԔyvyu!0m_ό̾~BؼoCֲ&b *Gb!S`~vuCsytf[|^TBhqce~[K1S |swGyb߿JOR=EaW1 釿?f8-qCp`k-$BI8[~BkYx8xjGYK >U6[7ZI^>Owe|zR,WB׏ӨA1w ̗V&UeZrs-%l˟;}|v֦LһBzy {D6[:C8gՖAM ૰^ |+[C-NK-~;;LIL}|2>j@U0vH,'_r]7'K+5y:v}YC1Q+|]J]sM=C3 u(Xig 1D&\nW!Ak0cB_O' cL4y1R<&ŨR a RbnNDUa.U0׌H0{,c'TScx֚bĵ\ ȑ#VgK%K)"s@@l PP&]s=B@R51%:Ţ֒Bl0wo/Ƨyp wws'G!*Rݎ>G@+OU6Ԕb گ /o'*RKCqVe)P^ .%Qc[R/>xb85ӭ| FD1@lb6:9_xkW, p@&Q5P0m O|^ySꐙ`ӚoM ,iֆ5rb.?BZ wl[vgs^j7R ~ty39Tv"E<#xv=@B;2rA0M3K(ĐKV5?w+@"R|80-XRj.D!" qf5g^Kġ&,ap:SK)teiJce`]j2/^PB0Yۼ sngه]ۻ~v>zϟe'e{7ϓ`޿b\.ձDZCTB$R TkSbW~'w~釸9H9ae)umȦj_kN9Urv^^:59_9a> Zؐ/~\WG"3{h7^lGM$\ yS*|gD#u/dDPUi9s-%j-rى}/ (m^nK53H!,~6KxE/VF,6UyM~˚]] b}|:i-bH-9vRN*ۭ8dchp!ݝSE"bӏIqdSM}?iOL>ώ1u[@J)E:wK!&6@$B"9Rj6ӻi{7|b`R20" 1qvjc쏇<^k-XjUx(l Zr:v% x1-|N m j`6B9RZ m|CD^UȑO!סJz:GɋX2˒ǮO՗P PnGZEE뒝 DvcJLV܍%Ώ8k@ww߿yĮ !D#Dk{ ڗ@<!ScBǘ"8)vbor[niL35b{:޷ <2ϳ!6;P@ AJM7LT֌?sp%N%)5<6XSR8E{~p&pNs)a|'fr31Kh&`V3i)!.R\|š]38Mo;r-!\QEbTu]r }mgo xv3M+\!bTg+xe[(/D2Ct+# p[vs/]ZzOHD^?pS־ffUimUPwȬ6Cm8U ;̼ZmeZ8WjMM#8;^31D{o>3h.zyK)y^<ʇPiuoޠYlJt{{_}wCag_|~:].Kh^D@+fSP՜K ew(cf )4p*b@̢q<ͫ{NIjnn<0CeZJ9Sozȵ "vqwzlD4璋R M8f؇)1բ}߻upcɧ/h?DT+[Ke{wZ2M\;w!mʭTI5NPS[>99!aLAD_\9%Ld*M[/kSfK%ǙGCNg*$&iMI+Hz IW2kt'kHЌ25E"H5!=)"]kgo뢎Y]TY̽wiT!MqD؉"_oLpӟ:O;j%vؖ3\Rz. )t( \6q"FdU 삿\+,,t>)FNIq6LveF!NvyoH У)F]ǧ`ۊ~Yf6sMSOdΣׯǣ0 U UQRfi"8 ۰19>)3!.aCv!_tvΉs94Gk Nش-FD\yPuOdvΑ`LF^E/S󋡄ؔ rUUOپ}#R N)eJ7ufQڻ:[ks6 |t*w! s?=y.3 3: س"#)Wn:djwi뮳lA^'pγ0.zt/^ȕ4||v;i9﫦hm[U64ERXgE.N8`Aќΰ^1ӟ:O' IM vzelW ؅̝Jv`:aѧ˒ "D)^X,ؠR8ϖmm̠ ca~I(rjfڥ[ϛ5A9KvQ&q.469XP"ʋfd:uX=9u]Df,PՇ (UB0kL1m IQ'aMk*nylo_[s>ՊңXI)̰fyTU5ذ@s.N4*XNu6f9^ dάf"$j2KE>tTjgpjg΄"m79%E%_0cCXUU cJ azKR2YTY>9窪67M7)FOԬ7>W_fn7tOOOՓ^aؽ~ןe窪Cμ٬-YxzzڿyE fɓEN HS#}XUXoooo>ԇJz{p1 XHMY -Cͷ}Y|:aDeq.w<3b e0[؎sa|!RpX( +v80*4 y9ǁ'$49Es{;r0N&}/ϻKo.卷9,ϨX$ !;, ˆ)++{&@#ot2lo3rř\>-gړ `j!h UUuI8gnn?d`gzoLJKy$Waq kcs~ԓr9[dKP8w᱅:)JTv̥24Eɉs2dss9DϙFBܽy[>0idNs?ڬ0C]WSuն)6mԮڔrkUVr VG6?"cLU1|m4VM3s߇*`&8Sr|.*{<;yp 0 -VU!&ɢDXyOJY44۬ > j*Pm^lLeydQ5C]6<)V~t~qMn"$9.zf9OVu- {Bf”mY,r \`F-hfX,bm(KqPDrLVd]9u@+g;\Vhǂ|s_OgT!n&,N1ϳlqX#eo,8Ǩ"VQv̚'aH8OP@EU*8{91g"Dpy6%# vp}}V-H16D\N֯~w!~foy[,#IYDx9iڇ8Nt*.k#< cUU"ʅ`sT*~PaI9sRg_7ޣwE].zWaQQpI;.<-}aVP[I$,c.fy iBӈuλmlMǠx4,TִT~|c=l?Nuy1^jSqበ3,~h^М@9JӭsYyKxb`/KCwQfh^MpfF yW]SN)!ubF"CHSUE"B sߴ-8xA#G.#Ͱ1L9u@M-d"eșln#kB0x"‘mB(K+E2BT'B{ABi}]ۙhV6'X9gl 2r0نEDJ,Llӭ> )*T0H!>-ݍ1@O_/i\9h1?l$"G*JES,&A|;MKr@]9sBF]!"rS`("yJ9s\3%.~vU9mi򠠜'<ݫx<^x V K'(!ss.-ujxzz:O7WڤtvPsE ^&0 8}]wy4iJnWwf8@zbz _S lsH@T?ƾ?=v{z撷Q8B@ *9[Gr2o׿{7oX<|Gɹ\ZBOD` 0jMԶ&73 Yq4s䓰jj>G8\涀W׻ۛ?V+ !sWWWlCׯ^w c纮-b޿GnΥ\x8޽s-5`<hkO}/g?8*(j.2ѿͣ@Ed;lM9&yA3&>Pk*QEH58R\Km.4E\@Oi] {rSlPZ3}wᄉ۷,,Z Im"R2 x*=t?U_p/9h %*qHDIc,%K"Զ9bHYVӶ8je -!Lc۴˧ZX[\FSEJUtr:98|e+Ѩuێ`%9Ev!>$ @)Y1FEfj7MJ A@ՋqlR0N'BT@RuN >~z1qm= $frn7wwv{1dTgRpPq"MګA, -j1pȲQq8>=*1Xס?u1 9cT[z_'frvD6 X&Pٙm]`Y$E]9o+}9_\9R$Ç` /i(;FBݾ< t<9 C;gw1H~gC<ߚ_(LqApИfCa}曖_䕜 S-UUe!^uɇG$sNI -U/t"Y ߖ%m etIE b g @{B@dK#DbYsہ UP[N9uxWDc/?2 .Dtv} g9gBZ8i|EĦo|hv;m8E0Yij=8ZB1'4dc%|Tɡ~8>|/G bmSOOqYo'Avku?I^eJJEDB;*jRQS4Nsq1 THUS(䝉iJ1V{"ɜ0BsYow.Tǫm(P׷?quiϏ4SYڱ0xo^8Tȹ <Š֌&~ DyK}ø~@U$|a8s}WwUۈj- \$g~U`N9#öiaogrD+a=}'*eD\"p_ miQUY  KՆ|m;[ѮKڒ˞oCrTJEζyF!pZbR.Ԑa?%jdL,/D.{& iD.\DzhsnK((AA^L]2$ !XL^DKXX,W.a|TU)'pFo9FDDGVޘ&OvO|'オ=vRۧT8\ gwI"N;_/t)fATx:c[EVpWՔxy4"2rr61)γB#8ϻ*NDŽUU]7П dNz׹UJș y*KSO_%O M{B]j^<,)t|xo-):Mmc%hZxlcιk_MEx$ ATK`Z{DG6af%T 6g,JIsJ"|i.K~e_ï9wHKUBEWL91t]-Tܴ]E"kP{eqޕ4Zʉ@ʜ+۵y.$AEB%)ϓL5:>T ⫰{ʑ*Dyigf{cU[U|.4PePqf!TD(,&/8w# jXl6qXn״iHܶ-~{wgz:ϧsFɻCt)0VGR%9!1gFpN mvv{{)ۻ[$R4·r#bSUڦ{[A S[ȶh$ŽȗcF ,RmˈX|,׻4GdYmW ժm&S~}w)i"͟["̈́mfv:Ǩ unnغҟ.`aD B*-I΢PtqxN0qƾ:T,,/i}D8P!Qn74yzo*FCv_}E)7 ^z~0# d"rK.bij+Fia>qsU7폇[mfsU MU+qe3sj1Mðn?(T g&bfu>>@suq)GyH΅em|y$aBKVʖM!pL%lwy}ΪKȞ.wNe67j۶[W !(?s71H1&5sw?NU#'=_#?OSwk?qXm~pY^pVЈsM/a/֞zRh"fi9 ͇amcwe .b6` +KqbE1|J93K"byi[ %-By!Zٰc ^LVulLds&pf6}vY.DE\!3W:Zju`!kVv["a)AKaAr%glcd3alT։Ns'լ"K.8!ﲙ sfSOKYAn6$ q*GĻ7o|UӘ稪OLJGo&L)qʜ6Z )a"]1vn!xUaa$r j5ġo#A]Rfq@ 'Jj zGe9m]M~=<<=|_jxziEC *ľH9ǜ͔cJÀ|X{YC R쥄 $ a6b1^C!œ䔘yFMq_}`7Qhj{S6!i<<>". sJ9$"uӼUNN,fQhTmV?NsBXDJslCqg~苼hPPG ~R1]UuMy.@%956/ Qx.,sJ><|7p6qL[Wu|"̖Zjq9&Bh4⹨BEtE:`>(bpDyg-\`ڦ*d#SJ9?c uW;a{sÜX <}x\_/5+ ;q=8p}(;Ď`? x]0($n!^6Ұ,`ɏ4CGr@U D@)cw;0z7,i uͺ[{-fl lZ kPei?;9hM1?՝[T p) 39Gf?3Vm|w$*Ml  Te(9y,SM4YA:N1gׁ9&Dβ˯әE]5TQH y$y|i6KJ1ƙsp:Uu4[g뛛ZRNцEqQ!$d\] `&zsk6EBQjyTf?Oc4N?X7f!GWJD67U5w9?~t>9MsgAP"DmU"P̠- L7u|۵|U/>/VDP-z^wT.gUà\wV]U7`}QQszOޑ\R^@XFc`雯t<|SJ1~tZv}Kh0"jf8ͯTօsLnBM^$XLj6`}bBʂ~1maf<H%άD9 9o")d}{XS.E^gQi(| IDAT?(Lcayz{3 eYG/{,[`j{%F}UWl".,,ޑ¶& rT{R"n9/b*B1bK3?=mV-A2Ce;bna)MQIaN4NJyhU)4MJʜ麛 AnibEuݬ;O$Jn6Mu5 ,Xu9Fr`1vo%J>KrԶ9 uRZ5m65MsaꚦztWy~]dqӭsʋ9|qFGQ @n^(ޣKsN 1jnA?onĉC7i+4<'ʢ`-0KNeZ@34nRF5Z.h}Qպ]"4*ʬ"}0KS}*YUjk UZv!l"CDN}xKr 9sKP7 kΣ]?nO9|Hidxv~`7ժmۖc gQvwߦso4L9fr7Zj8M 14*Eljc4'8XDH#qfI`,K6ҒBvoR@y:͜w>eb,,o"u`b`2䜋1R]W4`^@כM*(XR#6RVxZczl|Sf)NG,CEA#:"I5tn -KNu,x2U*qwm/nlZre!!E@SVPU/TTR眻4j%ȢlGDDU//߼ywww+PB8R"|{{ܶUMuq^,2s4V뺪|:ǔuU16 Al4] TA+dX3Ub8m]ӳ:zHA3IhBt^8[8Of]85ii/dKDNr@-PEojk`@J[ت.BpDͦP,sBTafaK(~ykDD y_)\gѾ[Hs4" HƊ7o~Zuq0TtB-1?Ŀ@c*[]He;a^$"$tYd 0> TDCrζ9%oS-L "oYHjfX9[iݻwޕo,l?<E+8~xa?/݌v"B o#r!o8SiXĚ IZri iPW2nɛ4]nts-n[r.`".YO?~|x˯n{:$٪}5pn!/(M)6U%90 ;_5}8\kZEm~nƓ75VN)䝫!rJ;NpzHԮ;QvEY׵~1-KfeUt"9bLP55jrW#v5ʈ5EsflUSqCUabgeΉ XS)PE!Pt,"oX͙*BV=9g)u&M39 \GU۰"rLI {$! :\ZrDΙPD\;PmW[3gg&F)9kTDD>~?]~8%m7n#r9v^YXi\5yN )U!9l }ONSZ/B"Jt1e$*vsRn/|d^`j⺲-,qN. wm!VCKu͉n~諸n ^}Ӷo].Q YΊTV ZD\fBt#ۻo?yt:ki%Tre$]Ubfto$ن6F%g;W0N8VAO^\}CM)U!hS7 EvLHUkDDH3yEVo,1-\UU %ݨ PBQKsqdg9.I2;_׀P!TU`ssfU>nnn?Tiz^/n= 2N'Ѫ~x~F)>M)1 "TUya`Ns1&kfZݻcSmƕŒ¢Pu\9xOZ6̅flS",;\Wn8gPݪczO1)6[g.-IBj$%*x˙Ϊ `=ՓB!74=Evn!8 !X"YR^q樀O߾??=:4qۭwۺ]֬S"挀uU{Eta"CU .׉ɐmsO3-veT@@ bl.^FFY(DP6㜦945w95TaeXΜYl$ )l_j_Ou>U.{8G[^Ё@yn tI,! 9Bf>NZ;&oT h+s lI|}UUGI{AQDV#829RJ!Mi JAytbΪ_2ܬiBDpQŏ甈ɤW~߮Vm!C){Ih}ԜVy!1<$M71G?wI&۱>ӳ*s-I}kgA KV8YfZ-8 {F'(89vK4Rԏ8on4JrDr ĺ;0'޹s~yy9+j^.>r.q<>9x*λw]48)gA ory Zc"N("@&@R wnnUwo"H s9y_㜀C)X}wXJ) +z{4Dyi tsbҔ2,~p1uZ 9k SU)gXe)Xo6Wi!2D"nY5umxre@jpu5.nץU8vY"BJ!e6_`P!"wC|:, rLS"k\U9_!}89]Tmc=ܮM)wW-d(I\kcaߴj" c&ĒT!@%s,9")%uJ?Ѿ>ktz̀uSWUeMƪXӴiǮvB̹{rKԚ1=ӏs̼hhWcd9BltSB 2ӯ@P\30_]7dHJq ZF3]]qReKjP|`K/Og>A r{` ֹY)fy$?[rL1EdOsb6p#ep@u+[M7֔ޜ!f~ᇜy9g1xKJd|Y)+0on5ldq(c]9N!NΝw!k9%Ȭ*gUbέξGj.&X78!(Ԟ.˙YX[",D:!D2l 1k-4riYk:E]ƔE4OScC@ DEOoo:AiJP>EʙSqS"ӧcc90u9 D|Had-!YpI%HxǘuRzCNκ)j&csc?ĠY:jN9cɰ)C{"2!)YuyRM}VE!XW[N|΄2k9 8, 0 bj>˜F &(-8 k3,@J)F 䎲z2Vd'Ԧ>NwRoGZ;Pյn۱^0O"m# 9/ON0Bbh991服1@D k*k1GfXW''88bJc JeMNyY$pBϑ0s?k창}g7ۭ'r:| pbJ.4OW{ǘses.[Ĝ!IF5* uvB4,Z꺎cK4mS!H:kKVXfcP*x((Ls+FfED}+@Z &-Ci4u; 6DU4Z MYS5Q)L!L/OOz]S pùY-e e,bxqfI).ABw:au~9ċGIӇ th>fuio!jgmrz8 [K)ED׭-}QgHQ^h%k}wzz*n^HsVw}X4Mkg?~KP&ݻw*'_|;=;yɯ>)e4fRDںQO|CHf׺!%Q@.&&cH}+Hi%'q֘BlE]YX.aa![X=ր>eZc,淩be@23pfTyuw'%F iuN-0_cBu횆8WʙM)1h,C VwLdv7b!Bju!2yXDg Zf8z a wuC=ZaDryQޏdU]\_]5Γ%Dfw!!5։ Y9 gdf.RC0w|qZkכ՛yoc-;SJĜRv9a>.LS~[)d8N2J}irm[7"=qP}3Llq=CI1|4G%-F%Y>y_/Jw)ƜC 39yX`D4 ̙DY"zSdis<]__0jn6 /`wo}O_zi b_E~r.U! ڜsNBd̙aI!mJY]j/pN`˜%H*bWQ8).B8SyB0N;ks1Xcw4i"lJ 3K<5oP@)#>68 vd7e!iŢj6@jsX6I!){\]"DdMJaǶL kZmpv? #2K LazznVZTYqE(F@MAhwc_%A0έW0N9qE=2=gW2q#e^`Y݇s{|5v }]>1aXHfcRCΎQCD1@-Jx12!9)f-; b味6Sj2 Ŝ!snwכ!8}vQ𔆀8łRN: Hc4UsΏuvg>=L#c+Q),"SG s*ٚ!s%G5edy;*`ܺeC j! y &F"TFJ uC/ܬ6?sW/=z%NT"PahۏG-B9s?g ~ePeLK>^Џ8ͤ"Zk ֈʠr9v[5pu1ƄKTSMĒT< &`fB+ OӸۋ@ s~^RbVYɬmd j]3wTHh./s iֶ9EzVL3甓.` Sql?ObG˥.\nNN2SXۮV b;=R!$``f}[X.6k[fѪ1cz%c!cY AP+x_6Ow+g=YaHގJ ێ2uSڊRB4(P!+^:O|;ߙƑEB8(5nK͙B ]?{`߽[j}zBfgL4ϽybW?XD8e^%^Bʾ|tJCe\^da($R~L7o>poNnw9i> "iga9ߑrh࢛G<!s^fs &-~Bi3[gu-"@hE+ZG@RPV91vu3ku{_M7771ƦnE/BVHY*뜳4Ma뺮cqc 3 yYQvLF_WfJ9m\!4cM=;)e%ZcZ$)[2Td53!lE5^Ǿs_ͧrgg()%=:\SvU:9uu}6_^1dNhW>G8ӝtW9k'DܟodavC&d o03KBr֥ÝʗbnɃV=nj?|bhjU ޻W335^ s&|31?޽|eVͲQ8}dwz n[& 2gy,ͣܺ h+qAԈ<$K D_@@ S_Sen+13 >X< !]+tΜo!bUC(ޜ["* 7tcp{zg}sN.sU7ڹff_BRԮN{h(8e3w3by^k0n71ƪ;$J9;/Z8>?vQӠXu8ƘR8 7 4:ʣ,#ke"$":c. |T6LԮ&!L!XkdS ̬[carn8g5UUZ-6MN P;,|0ΖbL)gABx200 lPCexX/Ybd {cFx^A>2'Q4:~jXoI=Are˙O)mpCDm$ bsgg. 土6g,,_1e A 5KfȐ=!a_srJhSOS:TU^^Wկ6_닪Wz !7Hֺ}o^wz}ztiz{ɔf~^xᥗ^fNU~R7u5XYk mKH0+Z Uݤhqv#@PHۻRg}]P Sbf_W>iDz\CMT7y6٨4f1[Us+N)Nc:]bაS꺽*MD{})1gaw4M&37s&xrP<_ q Clm69WeAJ"A}eP:Gd^V1i 99%tUmGq4:gw13Casŵzqr dd1^,_ 8>uX]0ǬglQ$י@8  Ե! K ARfS>i^쮮8ɺu!4zrœR!OoKl//o.^=z,1-Y5a䔜mTGdRNЇiU:5sJiW_]lF|P< hmCӁvkCŲ*qs}DKte6qwΣc0L_k:c]NY,nRJZ_NmtÇq>Z/ /4N1&f˛iV8CK33jmnPb } k䜓nnx舵VfXUA49\(پrC C|QqHjZWzBg"Fq2D^rYa-7w{tw]\\)pw38뼯>9>ћ2mj9Zzo&͏+\Մ a 82b)q (xެڶ].M[])^xt;EZ(Rs䔫:{x4zKDl9NqӬ84H/I ѷm^/OOcW׫ݳO|+[m:lqsNjƤY3C>:2:V"qNO*CC($31J)O)Y}b HmUiP/gw:JMT5cXxٜ=j[$!ӧӋ뮮8S QUo}n7ld("U*ZU=_x|q:\Ҙ0oԊLR=M_ o.b8.O)"٦b9`qֆq,.0U-gv`eSu.v^K#RK`i1s]׫^e0YˤzPg]; 1+m SLJFĮNNdL]KnnN )>}p^ oڶÄD֐*U7P?m켯[G]z>\{W{=7n,B9gj窶77ۣcC lk-$fqҬV"ksG5 DVul4E.`UHDds,rl$s~J_] 'M "},6d0;f*#ZKΥH@,2&e40c1H99BP2dfgX@u{bf?xuUM k41mSH0NR1k;^:%`0"YlU1M`v :gĔs9y^1 s^:mi)#rDT?@4fNckbJݪ !^|x^0CH] zå`nwmۗ^~_|n?z$'nj笯.o4߻ka,B[7%:sחWa&}Ke<1DDtZkgUYYtn6buX-D1TUU )w.(, cQDv[WWHZkuKDW˫Go~]?zޘ,"`H~X2RrBTTyL1Xhpm]OHMS߽ww{}:61vu|<>>9?+ FB\ǫǏ"nisƴ՝_ts]ow13B X m4Rήrb3fO򹣣nA\-G!BF͔/294M9KjNB֑s)&=Y<3kDuPRq ժ*6Z;k].lBI~4qaW`f!DdibCH)u]wo6W^0)i GGGDF@(d],Ƙnl~) 0#c:;?M)zUR!f{ږG^m?%3h^lo> ko @b/8+!cP9j7 .}SjRdxɐ]?H8loNη!.6UӄKSsސ$#D%@kz]19NN"d`'O߿흵98C?,-&"/;O)u}|r" mD5nZ]cWyO9zq:;qq -@pD(s6hY2H^5E'՚iLuHΉV08M9dS`F@=hlc u]Wi}0 ]4 43 ]]ڇOØW^,x#9 un` ȂVSD9p_nHd89ow^}* bjYVFkC9h ΜR]ǻ/`M+&,ɬj }|8 YKS5, H(x &"cUsHyB}'GÈI2C)%%릃Ue2n .tBP^#3{>x>:~饗?z<9=>?;9B[L2)(9g@:"᫯}Ç.f1h)<}@f0Di%$RI!/5BbPa.ih*v7 edB\ZTX)p ԤVsh*h~~1HJikM %;Cfǩ)H,&J~Xu~NYS}*Bqz=1%g 3WnW9_:zj>s6֤SS| ; ɤ,WwOϘ3쮯>|d S]sAĹ-(2.i}'ndfcmNWn7e IDATr&h· T/>8*J\y6D[T N[WCyS2_~饇|KG>x'T}"pbOw|b)z*A1MX,a1DOӛ?ެg:l0B->.0}+/g6rfkm?Ƙb1 H9k!LjRZ[Y E0%iEάdE1%^RG11&J4lOabVlMY钙UZ 00"v}߶ D+BcuhSfpUmb~.D9 % ~B۶m RJ9{jB1'd溪XD%Fٺ00.0oV+gځLJ㓣)~7Ɛhm ,^=f]RefWWdHGa xU6$:BS@$Ӛ3L7USWq 3 Q: U=d,Ԯcq(T^m1dOcZkɹz ~jrj6c71TyzUT?;] cX<;]`eaBJ tEAnݞ3}c$K\QQ9sؙ')e98笕k0k鵯||O|TNַx+~)4NcTv4i 3R]%~ȔC %I *QV8yxkӧMӾ/ܽ/_\\ à rqϼ_tj񍯿~^_]1)̉=zuί~i"2;Jxԟ6/rDr>b}vDcIYiL,Q,P@ti3Co39T*|ЧEX1$D C)E" @vr yKh@y$*GAdr))!!DQ C@\]uMTUʲB!g֥sT~iYcsBHM}|nf * 3NnڱSJd͢׆9c)%کK(?aE$0f{\~~kq?w@Rd%G߿Y= ᳸'8lOP2rɘrbV豞914m 8d}7ƝOO>}\tb7/ ۛgG=w]?f3b9cJqu7ɓ7~cX8orsKd˩2cPQj=YZO~#TQ.'pk 29sJSD0\3ZpoDf\QrrWQTq)f 8LTU(!)CTU̳rk`4U;+ș2ByIΜȨ+k:XkV\_];"ȡZ.~h^kڦFsd"Q ٬b14M)D3^Cza5!l@RNJ4ٜX0+XW´XGGiŢ]X`}~Dfjy~nUuunDWΖl)eU Co5ZS@ج7M]ͮD֫{w}UNSJzS Dc׏]/GG`@bJj"}OԴ0 (S\bK4  Ay?;qR:g`!ڨu Hcroc23ͱ@#C^B4hȜs63 VSѢfyvp/I Fkm9Cx8z6u۴ob~^|92?ONŜ✛R%ѿwNΜb*C'تM DS4u$EdBi8( _"+?{)٪ V֝Ud <.H T-bbַ-_4]n9e柜 ,!ڨUI |. ! ETF'C7Hd5)2sJa@H1qJIm5XK֘QNaA ķ: *[c3+5'YY,c?ðh3Zk^mV_f[tV3Ngd ɓeqgÛŐs&&$T2%ڪVUlkRQ6hR5) 1$9D;O/]ʌxݿQBj6۾iuQ(kH)vv+/Znt` Qm4%?Uo[mmL76ZutN)F]rOBOԇд- tYΊbqzTEu﻾q1Rι"ջN+5uv;D, bz_ޚ.<EQ\Z3%0pP*"HanCbwཝNTaƇW4(%$s}Lq:nONCG劓Ş r3l͝Jbix+|6(|V=6 *>؈H 1"t{!7 Zp ŵr+0r@Dģk{W?u @k:?>]{έ1~W^ AbJkfV+MJ/RJWu~~qr|BdF%G .k]JC*=~mg?~8M8ĈQd`Rs Z pdN1DNѪcJJiqr9mRz>eV! <'i:mBRuY*k9%}i|OLl."K,.=BPJ)vm1BJFk2VFYq1hR䳓{c6HQ.R;ZY_ yi+ }M#7RCN 4K4 Ih%iQ.J+`NC)FJpuYؿ}^5e9?:*&5'P Rޔٔe1!RJVD>"E4 EԵ;!9#>xӇ'e)Kroo>K<\Cg)NOVpX]y"n1mH<)OWJ\-fWX]\ĮD­INUՉ!Nž8M1"g rR ꒑$uraLq|/1 qj3s:mnM,o׾'^>=9MDkeƆx8)DR2˔Qw;1vbOtbu3Ł )qb1y_4 )@1Fі1kZ~(}אFFW a(aS<9H7eq۶%$eR*hLM#bYڵCf( ,Z$a[e ."TQWY %Y.F0n܌ژf״M3 AtNX iZBqآ Re<:`0Sk׶ǘ6X7XT68N|dj5])hb:um* >2X)a)?dg9)5dw!;wvv&ֺ®9[jٺ ?^oB>'Ȓ1ڷ,8ۿ~=N3N))qd)ƲZHڨٵm_h՜ +ʒH)ktNc 1F@Ðj]pIG9ufC Q&[R#-II{rPDF . YiC,2OHyo_Y%X6ݸu't>C9%Tgoy)s"EhmɽGBۿr~h? 7ąMjcdf:˻$NҨ!r50֨`(o/`LBC"E0\R:B y<' aA9 dBx(ۜF<@<dvr2{C>Bu%#;Hط-!Ҫ賟̵kG]찺ieTOZ+ VPZI絵EUicd$ֲE1 PntjWvyR)mLH!QJ٘JwE,\a*)8:Ĥ1zʢ\b#[22;t΅#)WECJ 0@”nLj"BE9\p9˻GIJHQa2[:6M'VuԊ ݸ)i[c$Uډ Ƨ}Bܿq !r,11CޖUmZV*a8玟>{i]U'DTt[z6N9_;J 6^f|puYӢ0"H)e @TWʐ$Rc9MU4M (Z+Ƿm{?/'ofnm4M}৓ɤwoݰZ7..֚u|vo?g:5&8׷w}QHQZ)^xqe>/9`#=PR#3(Ejp{Xas5ڒ2($C Hpr_ůƘQ40 7J)҂L:uV%&]VUF 2$eAfɦm9q Q<ѦB;*E9fnj7w ;Z $DҬ8t*ƷB(U-?H931$~fնPJ]"œ2)$/x7m "G\/!Dc bu]WM$ ĕv4q%#.IkXJZ]z4;(5Zi⸒J/&Y( z߷ czBq_e o zctm׾4lőj˜]t6˲z 4SKjW+8 ?%ƛ[ȸd4)]OJIX.ܽSͧr6hhȅGlcEB*D9?bHdqqqQz{g•ڮuWݮ٬k׬-ɄR*tRI4 J0|PJ|j֫f1^wyOC_G,IJ)R)rO#3*-P)*ce |V͛7U'uBF5P6 IȄcd'3c"E f\dE~]>"aw&BÐ2?{˪*OON1?9~/r>jM't9$}Z'?g^mʄ9Bi޻>yωsDl޿/}sƗdȚ.%E?c߉V)ʪj 3* K9[bG'YNYiE9e̢|Ϥ, RȚh%gMVD ie0[׏^]p9j  WmM2K*bܑ57n\_.[Z!s2&1:/ {"*ncV*xO y# " nD=л( $s]C@HHʖ)vv)nßMӶm]U(!@B"%TWNiu6kݮ Za)1i*錈R޵^,fGd)+ ]Y!^)zHejp 佋B4x)蚾=y?ovr]H3zIJi >U&2cY펙g˥ H+حVh"P`,ya\ﶘrRZtJ j^Yx.?,@HJF$Ș"q+F 7> 0LI2Ҙ2j\ˌGH $ s߾s_\Em)۹v6Ek;"MHդ><8 D4F+mPOUD|?bdc]׶][υכ^|zomWf>XV+3'RDHƚvA%{V)0C" lty6-H1AʗMӕXnF ґ""bD@DVJ`0EBܗqW$ FIk69ȊkA|SehwZSԙܻ>qJgO̹vMphm2+†,EɅA,͵1zmsO=Ǔ3C_,f{6Qh#Bn|۸ͺr 3Jl,'=S{׮/o,[}Ut}QXeMU3LJB~2bDi5LP" baY+kdf&`jZ.B|ط85M1xBe;oWtn 5iNƔq|R IDAT kڵ|\t}볳ޢZc~)s&đ!% vp]DϾ󛿪:1ǗGB 3sli4LX|UJПv(??NGѽGE6ep-Qm9"FdFnGW_|9]t&(f[ea닲MeY-f5FK~W o lQ<ϼzZoTb(Ixy{ݫڮԽ Ai0)¶m:j k1%T$Vh)e xt'{Di @\BeD R 1捓(L,'s.(OA ))sm߻/r>?=>6Zu])UT=,nvZK1&U1!6j21-Жpj6ۻqc~tJ$)bw*{;^ A.Yކy %?K푲R.%ማFzДRL*Z=|ެc|RiYU 5!L&G7oMfSk5V"< Qr>(v ޻2"^ 'n"HX|12@0h0%8RV+vN)\7w '=sua?1hAʠ᪒= D<+,xTJь;IE! R}P 䍡 /jkI fv7ӿu]{xt4N>{￷[^\'0w@[]&ѺЪ,K">{O~SggEYWvo~m RJZR[om^v@V]׏>)*jZRI> d;@&%t'a([S>[ȲZ3/TH) 6$͡g*W2h 1T_z#DJ-0FˆcMTAkEZ;Qkmm]׋Ţ*QP"fs[D+K$ԫHkMZ)iZtm7J߶Z,񳯿b:$!9#4x3 .MX$lYMAnH).genӀ7c"Ai@C AįrAجW}Veiӓ'ߺ{GBn gglRkt1+mdN߼o+N?(iێ ZS"~x~ŗ?tuÔK#^{=xNmiA+-#@伕 NρH}B`DTUN-2.U. ڈGRrѣG'g̱vUc /~8oVB~ _|{9͇?+t:gIkm4XiZ1ehl5.Cdv$ĘKch8,pJ|7ȇ=(nA|YJH9eN3QLWl*q"ļ%GWeIDHA{g\b h4$${J)j@H0`!Gac4F+-;Nfιw;kluCmכo}rrvޅs 19cMy ix*LHVX6,HD&C 2C.%JsBB i7vEkeYVuuqv{7N˪]blv>mdDx:׿t$ED4h@x,rhN2#9{/2(q10h#^m vG)imE"2(gxwd$]WRz%gQl) i2eP-g{ {R qks(X|⍟iN )91.o{N66FBBvkj~Dl%k a,,ft\vLnTVDJpSR6o69(И;:1vX'FuDہr2T &GZlKN8s |Z $nP g]S"Kt]gm{5|X_x 7M@k{ݾ [p1[fK˒NfWdZ߸uO'^~ZRd1 o~g󃃃|QՓ,֋ _+/c;wpuB5 z/J0fxI !DXkR4~gI`)ąf{F7ÇOcZrçFpb]5BZgOfG$sL&.Zc)rUU""bQEQ׵ge&' P H9 ON٥#g¬V&5Էk(9cݿ7g綰 kMJ2$@M՜ Ү@0RZqDHF+mǒ(Đ8SJCSK{gu]Acbuvz9ل4YUe%^HDv#r_׿~C͔)Hi7\q>䝚\R@sБMPEQxpf}l;,N2 fg.'_5][եoV_mw.X5MDFMׯW+"*&[wyz^oX.֛{Ji#z&(ڶi&qz...ίxØМdo[ܑ8!qаlHq!;HL9(_x2nt`1jcGoZcćy-fsI%PQ]v:UMD{]QƘGURpdo\)БPg;:BLyI9,>5uvM׵j!΅>zlRL)tmVkN֊d;EOJp98$Z?'y9Ĝ1ΰ0G1e]ФT ;0~&wZNflٹ{w||֬7dj%d29TUY%PNz2beȉcDd^,XSJ%C.'"1zwjhmj6'oşŹtq!~ppdt{\DL&"af=ko$| }9*1n(I ?ѽ41!DFF_|8VwO>L&oROCL&fCDz|4ƶMsq~u=M9fX#;w/˲\'mvC9Ɂ?C8;?cw$vsvRv">}z"+xFrd1{eD9D P:aNə%~`{\yc ژ4HH9Qy>8}>,RK..YsCDmYkcXzljP)c^>|&ȥIPHƹrӰQj=+bfSud{5MY(\NzBwnWz4;/3̟Tߍ,%_Z% eRkvjakP2$ C{C1|15U]t}.uZ^RJsbN~Q2#/R)JZ)x5UU˪\o6EY *T !A8ASB5QtEvmщ# RYRU+>_ՉŸ?:!o˜qF5VHq,#]3̋9J$nx)p ] pOW,҃UYNcJ|vz|lOWJļX@_{mz[خUyzv}Yj;gnO|㍶i_{{զo[L)r n}-Wi EUi;9=j>MsM|rz*da kgJޓ,v$b# DϟRy(,N,c2 .,Qyuѕ"q̠RWA!cadڶN #)97)ӀZ!$ɒu18zpt^2:)A (ֶK& r~.M֚rw]th꺨jɤ뺇=n_]7M S1 FQD# 'B?呐"RZK kI,IC CXy&K($w]}^&YVq}H)L5Z[ΦG7oA@Dƨ!%$cLQTr {c4 j|xP 2E&?9dEVVr@%f3uCP[ߺ̋:gOmc1Cg7o8;9=:;oow_xѻ.xެ}9Uhl]~_/]?:2ھOVJi٭/ $n1jc9/坻w&jz>eλnݬ߸n6˽="uzvvu-4rudIK(n\ﳱ:Ba䗊aH!0h*BR?`З ^~dBz[ihbL}/vRʖeĮۖtaX#7mɨ2JIbMUv.IĄ4={,C :T<%}۝}EYuH:pvz+K;x\7v״-ȕhLȁ9A\Rc mUU)+"Kd{͐B2s<>>n|OƏуct4!jl{`d{wY]k[l sw{ッ_{2&?9]b]k \R}_ZPJ3*lOM'OJcf|ʄF)яf4t }̨Tp!YY3{3HԌxe` ^A/^H)0v8J!z2Qc^GR,(\p}LHeRR(ˁ>Jkq2>\z)y~Ń3 zUV011)Mro`a8R9HZ6Ǡ9xIi(!da00D"n''])C<<:(O~[o+mj"K"UBdSW7=v-kIA:$y$1Js3""N9Cmׯ=~TÑVq@_fْž&+[( E$*>D4MC늢J1$fo~~:seFB~,R Z+.raȘJJk5]1Rq~dVZ'lYC?qU+أl x#u}_uJʪ*Ągblv1_(RhCtrz^7o_L@DX~eYw}=Š@ އ;w?9vxdoI=L˴ZazVZYJW%iB4 $e>l6KfRe6zO*g cUtteB-X#AIJkyHQvB2cN]ךTptb $tW)HG,nNƷGܙ`&[\%NnNUR$Ɏ9q6XY٢fv`M@3|},tYhv%nN+\2?!sw=sJ2މ6zMRD]ls?YWv۝sZO~lVI&Dq~xxݲ%3j\OIQ@8"$fkYcRm[M/~7~Z tɒI)yL(ۓ)"J!8ݺ}k=}r}W!0Z{NOC`r!^k}m25]]Ob֛n k=]5#* c$Hϼ= IDATiQWܵ-Ip1I)S^x?Ts'uݮn޽nw|Nζ]JV7r[AêsVTAՀXR2|Jd35^̓7{طQZrK^°;|)mHRZ_+eB.dv[\0 $TJ9."e )2FlCL8dď,[\8% )*OsRe o;w3kc潽6FkzqwλYl1Q㸹֊JS 18d$ @1lw'v[.n޼\.?9ެ׊h1^<=y%U6y9S1@_1?iّc% Mfm!38? VkRZQ~cb{i7r4ejj>qnvZ]/KRW_U_f se"QEcbsYKm\_ȩh}? BR83`2g/y%l>_GGGJlֶowoߞf'''1k׏fIYyTd%J?}ӓ)oe=F:BD||:]?yllYO&}~O<_,R|jj%W4:?yeb^RDYKZW"!Buyrt69>~JH!ӳsxsrvRVI1v]t;<4e2J̘zdk@Q#ഽ8?{f{{qm5!}׶dRku~ݽoEQ6(sy`'O{:kwlW+׶?umiiYsGwd$KWecEV4Fݔ8%(WYFe|ْ'[9K mD8Ƽ"- BQ] Y%whtYy=v}8A}hxDH9dq=> O2OJ-dQ5-ǰ\$R!D!ز,Iγ!x@$ ?~Z]uy-D~s5ݣG-)vwggƚSl]uնfYV1mmeY-O`J/Ŷmw]fۭ7*4ZpAo[ߺ5;חeDGpŞ%,ԬӔLeC ֫uQyP#G1Z #TJ]@lcJC]f;98P,O?&:1R)z~.'= UWYY%B$2HuJHslBJ8|Ј82CxTx|m_BRcadR to9ύlD}lZP:bg}!FͮA=B_5*àVJɮ=xbeytRUw!M+EG2@Z~NJVf4y^V0orf $)N)I%8U@Wq:!Q3e{hAH wm30. D2rL"ɐT&Z"IGJkE*ElP4犿:[gM~mˎ3XnN2˗=E*54PTWe*o(2 CÞzd {hTʤ$K%IU2ƒX{˚I@Lw9X}I" 9'Zm-Sw% N,,)ZJ+(9}u;oXyBGLJUU>yk\|n{ED(yGϞ>wzG?~C~ &3\{OOϺϟI[_\ZOդACHZR>x㍣W_g_=yE]WQ \Dr)ĈZiɅm{urL||Re"2N)={b\ew}Q.4777\2yT D!|q`Cwr}5G߫Or?F@4#nM !kA2'-JI bkGL)Z8[ x!q#w䁃,0jZVʲt2ɤmLrMfl\EYEuKc./mU!w>zgڦy1$NX6]Zk1gO]X,zغrjyx\\^\|?w$D:PLf$q[TH\)DW&Je Y<3lnfL AwBcw!4n{IUE9s $|hm]sB۶"&Đ"Ef53}4|"lN] ;!P$r PHkѾ5TVuuJ+d !(e:T$,cc@fdReY)EfiNY=}ӟ~;9:>䔬-=zo!<𽻹tmK!]ӔLm!uUEAnPCM; ${бҶcf|LI+5ܵ3BHEr,6o)٨ˎٗ4sW5SLUU]_]y⪹c@nϦӣËrea2V|O.ϕVZ@ f/`0J;_e4z_\6u}=>|7m`+0sikYW-gb aT6 L!h]|ӈ)ŬMwbN&B= vn&8릳|L"{?%AJ1&a`O)Id9}yEW#`V\LTl5)Ȩ+SX{,}!n}ӷd6cYpR򔒢b ,.f77SJ!d.ZC jRhnCxu} ㏞ bk2D>o nVpRWO>zv~~n7ki-bLi]ϧ}tVcl>r:e"`ypZH92dYBNWϞy۲XRjۮwnX?//_ɃWV?|7ruP0\]]*O d30Rĉ$#(St(G`*rA@k%mL^HJ%ny ɹd 6НwqP 뺺S4楫CNk7`mt}BTIiԚS Fu{c4rbmT]ՊT2J{G 9TB ;@̜8*DVkIƢRQ B{gmDZR˶RHAXc&53+cܾB"TcIt8D>1+k!$Cysy!7 s#i& ?Ue1;B d$9HJҐ/<8ǓKȝJLQv =bHER!<Gzjuxμ_G};(8u3*2FE9{C4M֓zg~Sf9ݽfߔUcl&ĸXR,߿ngt?~ٷo;G k/OOE4%sDԄH1GҘ#MDƘ;:}$1ƀ![&́Dw*P6d15]YW;HP01ub }1L)yY8%7$Ƙb4LvŠ1c6on^:xg"<>U I4lawKzM5INg/y_UV*ޕU\\u7{׵N6֖eCpmgD';c4,H~'蒔B;?/?-k-<{QAݖB\wOiqr<[-|jYJ$H6Z;mz6[JJ+DBVba^"J̙~}n2 k=7:- b *"S0$כ !J޿_ՉnǸ~+P;A̓CXCrQk%p '7#0nfy I.rZGUpdr2@9x(mgy]g|ӫUQڿo~ˋ e,)|"Jwn?㳳(KB"M?zу?c]KJޅYQ!.V?7?⤪MQN'aLo!HND ,D y}p3dOpₔ6Z>$~<K>а_Chtz euk*wֵ"Zke9omc-ŋb&O1vmWj)ugOm =nvmn3ZU-2'um.ʲ'4ZLCڔe9Ʋm]sbm6),s"ѝf zuWhm4'RyL&ڔ1G'tܓ'ϮnrVz')v5(Ik[*9% ]uN1(Rd 3fM1&\۶k(L2t%߂-FĘrB(Oz?I$5EXE%&h62@>]j4PJvu) a!;O1*ECS)%z>::׮i6ffk Ĩ,mu}=֓Zk&Mz1s#fu1++$e މ$nw}JcgX{r^YOEQhG U-tb !wcu''/e$9aR1D:RlDAX(ʫ˫*v}N*̦eYp ]$Y29D}:4Y(M1DRk/~:y?~,h%MS!^5^ߍ$c 38w${')!kH-\/襕qNncx78Ђ&ݢenbk/o;-W9g"CZfm.1.ٴ믵m !mn./>|Cku2Ãtb&ão pZ.9eDE*q7MabZ^G?ÃC Tãis/mҰԣ8abi笂,򟘓LJih1>91Fc-(G! G6uv``հbCY(96NQmD]ژf֦ д$ڬ7baFV:!2cnSD 8Bsy׶Dj\ԋe۲&1x]uzqtwKcv){߶}iB-Ca@m6J+cLUZYuJ 5Ծi}ﺮC6&rBMoUt]9u) Slzr2g岬ʺȷExE]Ր$XbV%ޥ$Sv3.pj7 IDAT".ͥ)mGEY@iQe2Rb|^a8bĢhcSZk[^2TUY+ݵ*1)I(6 `뺘1Cدo8d*m)5ʪ,%)UԵ)!k'L@=`UORIYcX#${^jggnN_ֆHkTbA) P Z,s)z`K@i0Zs\z蝹+5 REaֺf|J{z B 20'"5ONه}4D2wۭLUY*cWN]r+ cG5&8RJ1#QRRIHSZkcp32Ę +cDFqʉ8HJѰ2 c~ʃ6Mw1z:i6bLeY7MU)aUuU!w)Xk8>>&1M[r,r>)v\Vu/~{'OeNv6Ɯu=`I=9?{yyq]V)Ų*lhMa*qpviXX#0d,\1 scQ{M`!xR)jWFMdw^uQіFb fNbd rY+2͆@>#)e kAH=Ze(2QUwFt! $*%|pXkl:R1K[Hhe&uQO}trnXL9:>|7_yp_Gc%wZI.afq_"@TJwӧMshun+j2/̜BK*+R19Yo]$/1^22~ى>^/ʪThmҊ 'D}pv\ʲ$"锬J׿/>V؟:׿{~%<)GF纘|p+!C&&30 1VeHJq-!A9%P6#I0 CRIx\c}eMLvi'-'Dpyy]O/_YߤMÊxvT=`nq>_]GEQ2p18cn6d- KS )My5wn2AJ^~BpAה c!=6s9f̿ˑ׀(oc2FX[;2N4!F"e[Ip9FPČ ]?`28??TYJ[m FH ncSRZEw]*&˥)˔ 8"1d6.RK-v}vƜs(^}imB+N0l 1D2c01*EfH ɖٷc OݵrgRq@$0,I=n ~AT3[]If26-Mra0(żL)EĄ}uZ,f{$A$mWū^Y?hHկ~g>ĸ.O_#!1@:u'uR{ᅬ1EB(wCpb`Nއ ,1$s DR jm4b,kR0tLI+0$s{pǡ!F6z17MĒ#|6F_}XO'ټ*gOVUo~ _}o{n+RJMS1UvBhn6[6_Go3Dlz* k-T!UU'aws:==%ɈC(X&Q`TJ`QI G>@|eMZ Z>X%p4&"RZIiw y(ҬacX[haH DӵLi!F$%()-LQZ[(1̹C iQc0s=)[ɛq/_U5;X\\tMb,~j !cY o)c3SweYjc\P-99@*EJ)Pk0Efǻr> 8!X&VȠdRƘiIlM&S>CȦۄ1ʛRWbd b-*`0}R 1SdҤ&l5D_y^J^ow}ʁ՘R@`E~ _GԉW|+_I梮RIsr30f]v?‹, e6( R:wlErgU|+»m@Pt*3mXv[`I_ RLmMmtӶ7}-v,_}Jw^]۲UUjk% I:s!x 0p3t6~5pr[++ї_^^]]mBUUMnM#鋇/n},Uea85pLW>VƆCN$9k:3bL q8­#L!)0KHULj,)cA!JS=0+mH.+SUzǜ" > B8}#a˲zʪ Zk߶ zS˓{EUe:QL77)&luJA򈰾 YY!F{uUӗ~v9EMXC!t+}Y&F Zc}﫪Fxp4nkz]V\Ӝ?}*ۓEvZٲDv%6B)bb[l~Cd IO:$ŊyѿRFEvSBp0Ŕb_NHW/uqIy(6"6n7bC& {Cbd B"~L589s;;{]۶]۹FiCZI碍!$#y{3!ZcI"E)[eYAA˜볳Sbp{}VZ޿:>f`DeU]8M1ڪ2e)>Sْ<\"BkɦA]"Z*sbJH90 B |L(P} TCM0C3j?IT*>)&6 Btz۟L`ÛͰ>N XBHEak"}I )whw$W5X;1%_ſsSxJ"Hj() av6D''|:ͫjrpx|~~vyy޶mYU?Lm:DPz_Cv];bM;?;8DF< ]suWCz9qq"vmw|||vvFF/\ - FN1&c {ڶB&32ڔeuxtr|rRV{Χ>igK$ܴs }^ΛG>=Xk˲b|AJ(s 1ew|$x/{RjvpPLRJCT\ÇJ(%!RjqpXN*"1^|#EZr:YJlwp;SuvWZ)uE;v5@B 5f}YQrkSb(nuْ"uR>{S.泃q('fs7ʖqQsΏ Ek 0R8鎯.S)EѰm !Q+E5ZDro6WSk)T"-O/@w]ۑVU]#b^xpΚpI 4:+c)-CpNEUȡJIqHkΐGZ &3C h8X3ZpHΧK$wҚw}=?ӛfs?8<8:>jR1gWWO?i?{lԓiv~C`f׻gϞQY}t6znwfu^<;QƸ'(%L h 1d.`ne'cP ,)RCQ6 0(#2!j= +qZ#B92$;@:%5bdԚE!@N} >SӇ)Pi-sBJ3CzUrUN&Rq˗)BHJiWk0EYN&B4,  t}*,Kcs._ABãǒwV)RJ#Bsf훾mIf:=;Fa)UN& LZ+RUYK", Hi"*RJe]KRL8Lk1vmbt4 ]$R C\ !J&k9i[|b7kۘM?؍ecLi Bƈ0~mBZ[MD1` h4B?'=|k_\˲$B)FD&BQ\O2m[mFcH>ڶ9rawbx˺^!"kwkc''}k}Dx}^qE Q*:E7XLN"u]JIۉ!q QP+Jc }JI1r۴!mX)U*eLY漓Hmb@אm91R*!eU*Y4JUCoqg&%@1eM1q"j6fl#GÈ8H)SNyE"B(:EY}!"-a9eS)*e䗐9)IUJ/k;˃v'}Czcb?:ĐbW_qL1ݐ1\\\_[nҙHbFs4[gp1 `) z3m61/'Ţ#oaT͍{9sQ/^L'5)Vl:%R/Όocp 3/ 1:Z]?Z̧S@/]\\a/j\ cTvm b۵^#x_UU:C+ 3c;5jOʼVNJ!ric]߇sg@ы&N:v;%҃Efc◯J.{Z( cYB,}X$di0AJ8֑UFkm˪ fe R"Z_^p2Z)tX.RLJvzyo'!DOQi`m:PTtNkP.8[d)z^'S$-EUv]o,ƐBx~xPԵ'1~v>xK1V-ﻶImQD47dB$7:Ȼ I0SA^JS`MZԴwKC BO)ž 'fS[pRh"*mK% IDATtYTk}K_OR y1ZfvIp>vpt8xt6fHJQ8iGrq(YSbJ8Q?ӦiJ}/сw@\za "B )J)hmqeiypdla?y"zn[JN, ETϦ]}o";{ttݬbaj,E׶><<177 T[36Q0rDAJ"1wNbCLQ-Ko!!tE\`>SYU޹*urQ5l ;*cE̤Hi% zye7#Zm lFJ)k#'/ 0n߯]uV+qpjc2nRܴ-3̖b:a$D-D"nf8VtcF5L|f7-!͏X)DM #z3 `rytr9?a]Q2}CtRf xfǛR!2dLNKrV1 !t!"[ƉFBFžO1rdZ[z6Wei򵏝T'RܦEY0;yq Wv p'r0ň1F=,`瀻5FR HsӠ0# >ɗqtbMĜ& TA5Ia"J)E[zn<)-۶~_{zLXC7jQu۶Z7__2"=yӟbebIJ<ʹ1ηonno#49WVOwJ+㤘 qaD9tݘR5mQfE|CAdD)D㑋#6+%$1y$sME }u{""[1t4T`4hDc H L0H`$B(`ؔ(RgYUi¸[׶)HI88E y#$6ٯ//e Xk]>!o8FeM9ac!o8<䔴5G2μs}זdZF13p.חWd2[.r2q}/XYS˓m@$DQ&qG PCDUj%y``xcF̹1e1 RLHmVR˺RbQ`߶d~_|N:B)nG7x0#G3 b8PZ-<1J>9Eo7y-;Yk}۾WTIAlC"Q +$H 1')plgpx`Ez氅*0B!ElT4YM>=*\w9\}Ϻb?.~rU}vu0 p4.m Wx;Rd& dd$Dl~/WkT} K/}cИ>˃YdGEg?WWo'>'K9wm7MCygwn{^6 # +ɼc5hl6-.[ww޹vl\U!-#oWUBeAD'hS*q1_ԠU 5B?h!au u)p\12d= "Q+(Ir6])-K"1GzZݢq˅Z̠󂕜,:g?Se(bl`;EzZR۶MloonI !^z ~Q}Y'Z_gt˅ JE U7<|@)j'ߴm44MvMi I+v_rюr9wȶg֌Gt^t@׶DBY0!z.W'd7O}S2 [Oďd?sN)ŧON׿aڏ~}}㟟\.o{aɻ}5i1h׶m>b".oLR~|/Rb*6C2s\>NT*xδFv$٢JfH^α !m[2:s֔йj5۶}JDEuE`F2 aIJUQ$Y,^_dcL0LS ! Ƞܴ\-Uqs}}}m#Alesr*(Kq rqzy)%R0J9 2[X۴__!ܷ0`Db !%^s8 Ŕ HYRʹh̆ѱ;DG"')tb*bhW( cә  D,b:GRH4M~QCac*9!`Q%c#v9?#ON.f@v@TzPxX E[ɒa\.9 *;\YB׬ce "Rơ(,E `b7f /DZXLz[sDl:jDwzm+^vmۼxrco?{6C"#O<}__???|ia軮UNs7[۶Ec IJp.W o,,sq1ƱTs3;'*J\$Qm@7T>y!IESIJ]e;@a}rj˄ʓ1zb]Lе-TkDP3Ӹy>/'vT)J5#JJfK.OOS.WiP`&$JӸlU[o{m] ]b4Mm7}&Brާ4mnq$/ԝS4s:BĜ"쟕*HvRs $KLMN)lOSaSv 2reu"۳s9ou^}Ǐk_i7*eIp!C* !ӺNX,o'I)dY"+N:PМ'~}Aftm |D˜RD*`K\\۵Q!H>{~onf2⳧>;ң0N0nmLS=TU1c vrfVQ}8Λef.X8giX*d`]`MÊT31nUr,{41&9 NH ιƗ]@t}dA$쮯ͧo[_bVWq`Dj0MCLi캪P₪RTk,yXΖɖ ]yvBD)zdL)ճ^kkpH율w rlw͢V Q^U&~w{srM:=m 3)\r?I1,OOu6}Cs(#}l.)) CM wÇ}{ZJR1&LjW * y<1s9z9y^/7sl@<Sgg9uFL"q@kfH(4~zk۟I΂8X,.9=q,f~QuM^JbPDbxYL?bӟ 1ixv}-:֧>}C/>"ze?#?l&#fV^~*cМ%c3Jd cKѮV3rYwY7TpAXGVت0RVM1S1X҆ qgkB:9]*+{m-U8XerBigK"߿'q?a_ǯ[nN rӔR+;̵Ģ[tm[j8ɔ .-*X0 Ex+͑f} j(@|`mgaY=i8ZyWi%a׭ĸNOs0{'lH$]f̼X,7iD|Ï} !YYrNS~虽9(d "/%SٱifaqBEP`爸UD$v:Ni;T1usq{p@ !朻s/?9Ků꯾~ztEh@p!&l(̎WUӴ&ci|19YT}͞Ɨ`fU#tdZKǹC9QʀlBD^zycLPM6Cf^vK$"0>{?;=/~{{ww~3-H; ϼ~;`&6O35)rJULF`_3@TŢVk<7U ܮE$THPMGv76,K)VF#3j Be#97g@ۍNam!ĘlUR,-"qS)LaFŤM%],NN\۬/קg)eVy3@u}s=n/خ٬@eΆ/Xhyu)n<0Ztb)B@C!)Da[5`#ndju͢kmI-._~.1aRJĬsm+R~zOn,ai؈"Y9bJxzzrRu6#R$g p ˤIiq~>nS㋌HU%P|SNg__ob aK߶T$3ܣl]CŽCN]t E%folaG@<]cN]@8 DAŒ WnXYY,9WF4"g?C:8) 9UDi]ߟ_\"ܼ;8)F9a6lB(9 OyGM9#!9ork-H.A@9f^Wև7(X)mWbZ,W5+Zp a8Y/813ZTrX8,MR}*_4ڶMfPhNi04eg8Nͦ:S HqiK)0+"vh1hY{mי]YRxnLi13R`̪΃Hir?f[5eNIb{w|u~rfvմ/$]wrq?}pUbO_5m~˺#ƭWKݣ^omXHYĐ秧'w7FZK9eI1o;drMÍbS&H΄yqg[$>?S?Jůo|CUc [.TuC0cJ w]+YNCR)gdΒTmi~ DcMUaʪ\euv-9'"J1:f9:\=t`#(ʵZEs6{sq;o=귻j)Cp 1L~o}kcJ9YvWϟo-" ~ncY>:m.]dBR;j$9[mHD|*šo3ʱ3m@BJ뜊]Nd;G#kZc]TYn$"q\8NrY py%K6t,f9}o+9nvZY 2 /)SEE>qi~u/y\ĔL&TlSyNy>k۶lYlb4rp M :\R1gHhU*ƨ!ĘRʑ1)Oԇmfuz ϟ_9s6smf6ʢ&NLE6sCBiLC)irAq [$"II b$@fw'e?7u,<AN"q*Ʃk[ !c6mqU&ԶyLjoi @#7^ٮd:k 5cu ݬ!,KIz12iꇞ Uuϟ?S포'46<߭dvTν9%ɆB4k!KVϜmHءtqZXU†`+*VB9j,NVSAnn݉%\z}rzz 8X@BsEX,|T0PTFÕSG@Q٢E (UDgIVJ%IF-=W 33ͫjG)%盦i c@c[#g] rNC,vAكK%ZY *bb&g(z"qw;my*&U&$'*4 )v8YMuW.H)}rΦPjy/\w8ϯ77qݭ:_/>꫟ii;~oگB,f3߅Cqɘ233 C,bM+W}4Ř [CCX' o mv"M`z}+S#8oHN.ĔjZζ9bOv!r-[n6b|G4VU;/"W<`i|DxUMNA!noRS>'(iۙ5nd\R5FiȾd [8VQ),Z<2sC!*ɭX\]ߪ؉B5h응[u 7x,yj@1o!j6kK6P89fjI)2I&Ȓe*AGHb@ =̜Yʑ''8=:DS&`y$r<1:v(A37 !5mcvzܫJ]ߏY:v9''ܴmYQbXNQR]zXC&HiHعjW"e f1{]7KE)c}%SifWh{S@iڶjQ&Y )ƾg&E5$Y,W]8耋NbNY{6Gp٦mDǼb"d$T@")2* NdTs״/>bjB)W c$dGEۀqݳɺi;94Y.Pfs?yhDb\r@6 !d!vo>zËac9.<涿0NsuE0M~D$)Lyp~Odv͂*"$MBnYgqQj/3C['_Gq⁂PՍ̋EW5QG;M;$gbRИt]1J"λ~~j(R,*ٶ*zzrr{?MAEOOOj"zqqLO\]]hhe?]Fgf3ǙxrCAvqjS*6/ScDX.W77ևd%\(EY],u8ĎmSTrdDQC41UeTYv:'?Ryu!"RM1.V+g~^jO@fHr֖M[Y|DG"=Y\TԒ0e_ j(,/a "lRQ!IcN m"5F4fcLÀSI!m:0X\*5^^ %@%hRDiPrnYsȏѱ 2XIU3צ1msYTRL6$PwknYTkv\Og~ۢ#9./>Yf{R ew{s''Τ_@;瞼4ps}wq_ߤqiJ^C0@`Vl9E[N#1܌,Biڴn &qC4vއr>s?]Ѯu" SKT.L2t\I9U횆>Kv2PX̊mM;#ꈸmq |JayEix@bt<  B0T3 W8Sb"֭ίJahѵ:"Q%&Ihd zqܜs\T5ؔT"&lu~0tV;97/iH~?g۟ckۈ ~S4UE`5SJ *!$B䜬}O+m~_Yڶq3qO:n0ٹ S.9[PX {&'_)Y1q:gW{=i$'D>X,qFD{uvlۭIP$nU iU["$CD=DXnZ U,!M w1´ƤRQC z”*4''ʇ S@=~kH8#"_>nݤs̒uUPէHʭoڮ!(qvιn[m].W8rpCt}۴ f%ޏ#%)\ P`OgA:4 Վ9$.ͱa T^vB]ۚѬRDEyZ~ bcgi+,f> ;slkKQf.h'1"L$'7ִo)?DlqPK1{_Gr!eA#UES"D"V#0! W'qNskmMCC8fffb*0cqŗ^d1%`r.&yD`vFL.(-bD$QD|@3YQ) CBgESV/"!ċjjb9- p-Yg{{?gD؜sqwLBD9ўahp]"s,KtcuwRWիTVtVR7KLR'׾%R[ONػB]28:fNA9)40ژ~aƪ!4E咉Nҹբ{k)9]UNN?21U?2#nZAF,)!<]&X*HbZtA~j^BpJj9y1m^᜔'cU 1 p18ADe-ΕM٩z$+88jm-AyW攖J_K_77"ZCwI&40bRr~9 bp@M(D{={6_EHF/p}}}wċ% n2:B6'_/onon<켥&BWrIJ9 1Tz z5r!KȎIjD2ԴNY#a\q__җƟ"t\0]]g43_ KaJ̾mrsmG/笕d F=,Ǵ.PTY,LFF&s;R:+mL*MAՆNu< U)Ԑ1D}B}}_~#v丄ۈe,u'sx*4FԂ hV'684lw6ΘD9o[=W+8u*+Hz,Ϡnc1EKH^ONϽo* ip}E̦?2aLiryA{C)!SN)-Z&6|} 7.giTgE}bͿ䔳3ZTlal evwqݝ_]]V7k9;vmO_g^w #"fO4wz'OncT XODT2 ;b1jQM| `Z1 )mӣ: +22#Vό+iI֍\"/77?GN/ʗH輿L!\{v\4>XP5<9Ffnη6]ŇC4Meor_cD)_mG; E( ASISu桪U qUio6XçVPF,el?RcfNfà̂CC Ƒw*4oZ~ i `=mשBN1D"5m9EI:+fxt3_peWȜbJ)u][*/4M(c1rq`riG@캖җeH0#3 d{GD캎9_2T;|Y01mM«;&|yFlGT(DljEwX.[gONNa\9$esMn~޵cL!S}ENU4eT$x%3"PP-)]rͷGnZELqD߼!mqViT?B Y0Zf'SᲠBXWRS6 4Mi]$&.MVբi S9%4sueT^r.irg| wC@JLM}\8m6.>W/uZlu ar}1CNiۭ2ߏC?lwi ww^;M R"B5Tq9c R xuv3CJNlOUAG0,ŀ3lmÝ/C5!?~w? E#]' _:Mr&Peʽ8-Wi  XYP,(-[_3 $:FuprrRcA]XCX#Hsli D5@@TmݍinEoʥhY\%kI(Iv!R9)/[*Tef)"wբV&l&;u\E;ka\r)#Qծ칷ƃ۲Vj@Q)c] /Y,vu'DAFlqZ;.+h{ՠ Y EVh\U>8J0αFJn13ksJ9Ea 2d1*Xpαw0|8Oi)hPT!"$rڜQa˂P NӦةP}P gJiZѫU99qd ŒT`"STYrITY,1eۦrND,c;sy{:F2ldI92K`Zc?^M31q~$v@DCq5%D^}-b],9B! uB͢tv޿nBL"Q+ڒd %MրUC¼G~$,#tYT6Ѭ_?R礃W"8_(4 q̪>B7je 2_ƍ2$ҽY IDAT25""PJ͸ i~fqu+KkX(,3DI\ LUa-3|FMU" !Y4O!@/v@<6MN90fBIdb.7n̹-F!i.{ʅ:Vʿ d:osЩʴRlU^)ovlhq՟XVĀ}VT 4dAԑmm@D~UTBoD` y((eS&Y_}4,wdB5H_~mQqX)䔘q" ) Y@4D*@uF:/oGn'2d) w]h {T7gDXs/@.HLPYZ;Df sR fn1(џ??_3\aX,b1yj}x`"2N$ hбP[դ2:a-ٴu̓ˋ[xN<'?2w5`R(T-|?9 ZC";~&|*Jc4 H͡ fT4&eRܹE.E"AaW?4Mш欲?VUfC )8[#Ȧ\m^2Y0E+`{KӐsW4Mk"r/ RN)rI}A*Q4?**voȚ'4Q!q{o-N~~pqi DKtPj "fm8/ѳ Vy#7t_c`|bȢ&*Up- Arcԛ_ιcd,Ӱ-]%TG''ٿwYk_'ao֧*lWQTD >|!\VUsd1h.Pϋ(sIZ#0J栴1P$L rWA9eyEiֹjޛ hą>d1ae,%MDcSND}* icLN\#*rMCـ?F1arι*Qί=0[,k=hY5Mc^*` ;f{R )\Ɯap5Mk"g "l8d<)r" ڛh^0dpEmFibJ}/ӄ)"lRaf {uP[ pyI\Y:A~3IvцjY^B"YBԜ!gqhc/uCa7v5K՗/+=P'FxC])6r$~c4z-cvd #Nӱk *"xd 翣QWINZX] ^pءuLUly2zj=w%B4ֆDlP(rˁ啬s{T[\B5Ԇ M,B71vgrfuVm@(m헪[Z01kcÄ1gt(;9?ٶ%qif>DgL Zr;`?As$o@߷V=GPHHսVH!sf;JE;E@ fX5䜝yN9y6vЀcP[A#$ny5O'9E^xu}%D  : EP%UDWd$PU&sUŅ(9].QQ89ʯǙ)\BlW, Isf5zW!.aXjGqeDc|.+Yag/8{~!«!|Q 7&Nwxs%D ^ˆ6-- x3THu"wo%>򆱫gFY&ߕmui,n v.\"jǫ!X볚b!DyG4 N3 UP CtiqjʏRA]8q$"fΤcx`5@" Q^<͚Jb yw,\fWY4<{?ݴ?l=EycE̊Mv #W-Mz/gy?mRqus=i{}l8wusa8';xUVg4̄X ®:9- ΍-'la?&) gS VvѩDi.˺E#s(3foՆcZmPȳNw/"HIY: !i4XV/C |2Y "Į7-1g!>|{wgZ}ϫ#l7(oS"+kb$)%u]aG1XWiyB0w< 3b'Y$v;O΅omH9 p-;(4~su-Tmv}n_N\"6lJB0 ׀fEA!~9K| ]lJ F0&K*>/u&ϵW[('O◿@7O?񸹹}Ǽ6!}z̦+6<DZ̉B [b'\Qj3XJ]éDW "UDEŝ`SZ^g ,)2.mQـ6ydsQuD(Įdw}x_% TWI47*bbȖsx)iC9r1Mm/X] ͖4N(, fͧ?]בw*2-`)xxT*9yyzBrL<iJS"`>{Gu~nT;Wda-?Y1 |!8獧WDmuRe ճ[Vf(DQOv)~U|ŏ:vs{ẍ߽>37-\+hǩCPT59J #g?@Zl}2u ֩{[vnI^*Ζ\=֘C!!B /Hj6Q]cf9k\HN :#DpNyU#}9q-|t&,S$' ]Ǐ8aT%S4H98<']gpfl}wf),yceN. !N2**d"%L,sJGͶna3vs룑@A@!W[tTa}yNߩ(@sq<!% {I ѹr&,4"rAE<*ǔGEPU^؏;\z,nZF`kęǯ"tQ:CKGv*4/?e?Uǯ_{ww:l>wS裏~_FWFY+ 9 ׎rE09j.<"BޓsdY'DLipͦ'%hZᴈGE&S, iF.fI2v*s_ansr)~ " sYpLZ ts9ʜÙ2'Np\mqQf@ޘm&v۷ET{SF1\k>ו (IT& 1RzQGsZq޻t@tN{g2*D+vP(!+Clceƪ_*9AYFK)[l5Ojr)D} 1_cAU!3?дB܅WqZ9K6W)+tD54^9@{MQ9#95;] i>R"ĮcibBqCP"C!{Ko64wwz8DxX,B{`*&$CF+$iFr&P iqZɓl \+/<iUF, jl" ЕRk2n`l.7 B˸|Oz9.s/x;"GnV5A,u[:dj]_]dwgÂ2r$7s"$ j#ubK^0H ȵ8/ nVޛ@k}1X%|Y+0gY ws2Dhuu9mWTshyJf]ׇsJ+ax/YEXCGRN İyTEÕ}hvh4W#yUDrdWWW8 (,D3+ #GF/Ι3"z͛w!Ay"yNry@gNŇyh>4.t.O )@NƎ@`5\Z~׿Z_5+N̰NSJi6E7Uϴ.PWو*g?{]κKG=_|71g{UNt5($.SR0}ߧw4."%h f12+HT߲daCAX䄔JbQT0rh>Ά df ]V`LsHC̙mn2v dХ60;0",i"΢&*~o97ɂsΙ3CÛ֝n}햧sdzYY*rYn;l9'Np 3âSUfFeQyFT>)IPǸv|XSozE ,K{{(7:{ PS* ޅ`_O-[R} T.[f!X"qVp~g[Y$P+ 樥*\Ugy%ҙ4A3_rVfOD]XsdAr rqiB7 X6H>D+9eᄤz3ӴļDtqٮ02x}>v] * U# Z͈N"a1UsΨ3. jnFa.K?AӜٿH\dbeW'[:,q߇i䜇Vէ4f3 Cy~h<,cȉ8M1u3jf2lN@R2o8 Ҕ{ÐXEǃ=,ՒJߠ9gU! +HT|mTy$ 0dFĚ 29ymԆ'777Bm׍+FlXO~?6<4ꪈ4Dh(kvJ̜-q=z;;$9v:zpp:%!" ,vyr[g#)ӌ%<9FREE8KE`@.D(d1snf;N2Je`+SFZmct:Vx@M VP ŸM%*QUmWaMika/R8h?/?{F~BUй8 D"uT!9*:ru}&)g (f {XЂ%j:B 2D65p bPzCcQ8gDTs m.|/r!Mb989)Ts ^/6?tij^gAGս"zSv 18G&"] ݆U+ovLKQݽsnP8<બ@ |74ՐV-T=Cqʶ_Cy@~dqt5EಪU*!ڑ!z @ Uw0]a23*OWUBj.Q,a+,XR'YK|" dOUU.4E_b} @8Y ,,{׼ Nxs?S1~bph 4>OBB]V EU(Kl+e jؿVMZ- 7%$Je4O&p:X/׶R!f8??vRs p Y*19o[j1*hJ)#8vWǂFBDN )FgDp143"9v1m BEW)1q~O1v*Ya#,T, h7C 9a-a'JGDr @wVUml)a3c6-1Zi"&CbTPO4_|ꕙ-qӢ v2R3t2ofBp4Hx:wUAc1˳ZG9gJV5cΧ\ +ⷪfF0lipZ\;/섴 W.s67{UJ` lŶvQ)-团 އhţɓ@Zre?4O -["v!xAŌ8tPB!j9U Uܫ Mͺ a%MJ~k;PŌSA_Rz ~.(cgRe[:htVU簚h֡g`~U|ݟpV)Tj[#-R0CЂs-?@ W">a5mv%'HHlo"篁%BDkf*VQL5ӴȍavFDtɶ.겲nR0:SUL33| k% 9o!$"9Z֣,\ 퀢siX VQh+9m$qbCfK8a]>MHm?d&k[P*/Bϟ_ӿ9r+j5D̹X.b7776 Sx:sJ~v^\4W.pxJ *eȺy[.`ԠW5ŴTsN*ꗥ!FJ]VQJeyofdn5 1JlgM+*rc }:8甜w8}_" χ9r5֡NatN1ecEL^T$Mrα~?4hJY8ۍ)f޿7.hF>lh))i ZM9RSjs6Vl#6* ]RE1 ҇JVm|rJVغ-}ʒy_ƁLnZm] TDKӧ/~g?强ԉR?[\+aa0v1}?vG\Hꫯ/gjyVLsd#W-HMSCpjvCOb!i޽甜wawwυW7߫Z ] Jm:GհCyX3MS}Z6b cg եpQ)H> Ю7- xp !T14;DYbqMjJ[YEzYP,ZV9I@%ֵPlLgfbnUa+aB/:5uQmeо^)~zl7l6qOǚɻr%+@ ;sΜ'1>\7C}}=lnݽ˰ ahʥU*PK s%8^TTԶP9ZU5&%ۺ#W={Y0BckAvw#nCoH)tnf۵ZBƺ!P] 󶞪Z^*@ڈZ¾9/\VX|sBu{U탇a*9CĜS]wt8di+`C5J"E]T[JA?u . cfRrY94~qL\xJwY3ҡzePf.̺+O=/R'U=_}믿FiΧt:ڭ@>@" z pgD<}}nzw:?((d#iqۤUX/oij2jxղ PVU.TJ)0@>PECv{{4e! ySzݟ !C^kv/XeJtF-qN3zqhED{X7^KS":ϓuι鸸VnE"YmuS#BUg"U&{Y$TN30+y L*9~LF;O)Wj\rG5vf1No7h1Ӝ~9%;*!n?S=u&2 s e,Ry>Zf]I=ЪM,&1}93sf&sq|2O#gq:4 lx6nD;RҢoԪ˫#Fc2nla6+x\=ij˰棏?BuDۤa&*YzOVMQϖhZ|sK:b#UTiԫ-a-hӫT*!Ml.V^GBMs踴x&xO>}ӟ^jåN)Eѣp~<v>kf]/Mro>}^o~N43+p`~gk#nj⅚ \hQ.H9,{_npo\PU$(S >.szl[]Tip8O)6h'@íN@YoT*g%˨.kzA+h -B GV *M˪;ϸEUc vuSL5pP?Uqr߄VaQ=ՉPKYZ(l; Xlx-kȾNZKZq>BcsխQ!9W f_L+duᴂӧOK7X*zׄ$*>r>޿?R~GW+u1ƫkEnwC׉Z[iu)eynt*C#t9xwIM "&jܫ_X#sf4lC= Ŏ@cO[@ɷMU#QMyBtJsww!@]24_rnwW9.^`"RY\uß3pC*%r|:tnתRno6m@!aK =O (0fOg YFʺ&W[P9bU+K)cf@B`(n,jz̿)K-.z@ӏ D@ɓ'R'~Jū XǟwT8g9"m86rΜfA Cfb!!9u=:c?l D%;Ҝ|fy,r934M"Y@!5`%+dˉ[h"u1T9l[V #x""q<ݮ Z*ڒ?a5R/_NK 87YGy`ؑ v-Q'v:RvpfHCv7+,X_ճjKm) V uRCϭBzXDm詽 Qc!6-Դ9=2 R T'/R'~Jqon7ݛ,P/"1 >x 6Cv]O '!XTp8ӈo߾͙̐1ƻ;a,bemP+MzA^]_?c9*iNsT!]}MeSJs.`YPKFٳgϟ?r):;"׷as޾!$tـn֛ZPvrJʰْwf 7} !DHasOfLzx4M3 :"t q<48ZF#˶㵱|XLEjSl9DȜEdn6>Dx:f[ 9x:iZ\`6+)n7Qˡte KhToj븂Qr+8˾M(7쫈(h{pUnyQ ZyQSB8͡$Mgx]WN"]jYepjCc4} >tq<3fdЯ* ɓ_xR.uT|׎HAonpݿ!w|aKię`qrB9o}w蜏Bls*th޿;Nt4kM L\www.(voնn36!)]]_`.n6[P xyvns8-zEJN`>nj)լG!Fy")%ls41 MAKy|+K΍VMsE@Z? ֌8UA]HueHU\g^{y?l3<3/5RhUӧϟ?K< G\]O1ipZ%Ct} U{ʒpVYhvN9;Gf㽿!~BwSN>Db>Ǝ-8wo߀Ya'G~yYC"״h6ђSO> #8McNx؍ɨ+b2YXSITQwPو"g<"jwm9Xr[9. sbLTMeOdq/Ԗ g|7FdšӰvKz.oR4R\.Zx,[UvJxow]=*rx[t%eϞ>ɓ'3R'.U*^s+zuuNe>?)ƨ' b P8kbԫ뻫g罏v ][PLi|s8N!FD9{qiyG{3[q캫뛾s)iͥ-ct-qJ);HS0saɉ:c0.S=(pZЗpl;͘PETaqqZWTM+ e2K#Rt!.šBVV y9$(ڂȂng?/R'.߫T|p}{6^)В ޽cwo j vG"iB 77D4$"އ=!(P8q|>sTz| ar)}TYVPg-*;rDaOXReJF:CP-,!5bi(WB] u.6F[.zBkU:cp6d"j۲T"BMLHOm5}):qyϏ>Cf˜o83O P"bWW5joV[ Kg1 e.yN*BN.b[s!ͰafU=9i׿r΍SI.YylV=D3[losNpaOT D\&TT~j#\a hssw݉5"Z`CwBڔ4Oj\ *OD5kmS8 pI[8bHzjQuf?˻֙XALZY%hM+϶(̢JQ,m}>t:qywbu=twG> "bcP1i6(VQ !BEeƑYy<޽{SHC4].dC T hÒоDN֤6=U6k9C 0 0Igg3cy KjHk7pЖ`^o=D5;ʽmr=A EU?Ziq9'E[C>-muASb,gϞ.u=_|by:}Лtix K(43hCTe,Gnsus>q=Osx<:R,l33\*'9M4~SpݛΖj(&%<֖TdIDr$rΓ9㜥lE"͆UXT. !m6[ 7O' aJ)3N 2Ϥ/Ws#hvI_.`ohlʜBDd+Y閕fajJ^ZVu[P@LedW<wX}+"BV y'>šB#Cn+8uS6OVc0ǟ=FKyԉQ*@7 xl*̒;2!#+uk),0'͠D /sJfn0-GP6}oElVG>pC[І~ hmAc 1irtP[10V VmTc|3@>]?&UV4[. 4>.'y4 [T0Sq+FWQ!m$̏_ԉ߭RիW_;DjǓs.4'5hym-ɊB~'"g`Q@u-wv9Ia;c53WVrKMRj?]f-17Hlͧ Ulz(O=/!gǏˋ{IDAT\?~իȑ anOibB3e(M?k]\y{92Ć^@DE"gbG^3P] YG$5.W〴Njz XȺ:] !9$<3xY%We=6qjz*堯tnl9T6 BKh 趴]D5F~k3pq W!{YAOݴ j0X.ϟ?s]˗/mbڄ9w6# $сI!w)庮-/l7!g8DIuZJBKVjt,"ښɌ4PEez@e6RIEHc "YizP;笐|0(#*T<$-~%s:Wȉr-Fh--(ճ 5eB\[MxRCg}\Ŀoz{u߿7mR2۸@XT.vsnu6/t ӧ QVyRES)W]MY7r0 !xn8.j` :n6HΉpS\櫘8.k\tJf4?#7A 2%[|8:{[i mV6r<Ջ"lr{@}3IOOȹKy<:T؊79V[L\FW\pYj&n_m+IJZn{jPВa_r ԴI]߇\pi fЉ{B4@"[Zh;s g8N^fW5 dK.TYbM{G`%eh?j2)%O?F.ϥN[)_|}vC; mdjWgr Rk(* b] ubkN@rZV0\]mi&3-x@d.F }gA jSizFԪBv9~-1K3ªkYtS逘ů Y嵔i[K~K5Gt!:\)L5>+REKy<:o/^|>t]N)[⩦ /i!T"g"p*kZ?B%gP.鶑^ J M״J]ӰD&ǫim b0jN)唫Wk r*Ě+'D֜?7a9h%6-*D lF503su3Vݖ`VV [WcYʓue@⏟|MAuSpi[zp2_d>׹J, ^P-p1RqRXOއFT@p<c `. **D-%A TUZ*AdW HTP'u39טʻœeqY핸8_ވ-q՘T 3H ,{Z[ZB˕#f_*1n - #a)ACkWZm榶lm88oy֭V 39pт9 o,2l%uۇ% $f*\Qv1ҋp=\KBww{o X2 gƒBtڂwJgw-oɓXwVlB+ ek3Pm666="x<HU hsٚYS VVyA+J!S5q0V\=J4>r9’*5:RZި|"̜5Ͱ=e!ٓ'ϟ?p\Y^ono p8NhATI % COAUT>$U?9U?Ǻ(22Ke!C*9-vIJ D׈e_&iZzTREZLVi Zвm..*մ %*+> D lE,>{vYR+0V_ԉW*z!j? a8O9ZKS%մV8r6b6|YE}ц+m0TtE-EC2g5D) W*".vsD'9y̞vlصF@oQ|MN1zM|ZAXbjʴV"I^q1 0<\_^usxHo aSU=4ii MW_;%8 ~(: i9gP/9+a>+Cb4s>9'D>19*h>4B5Hգs\ Ֆe,Ze_۾hqzmrJt>$<Hia{u9qjI[n;ʉȇho+VATLU<7( <=*BRJC2}zќwx&JPk `qbFƹdX=y̢`SԽg}:\P/_z~0;ۀ6<"9#bj9 Z8Q Ո$Om!Q PlC 4F@Ml؎'Yn *L}֥:xe cKu欢XCɮ͋dE224I0gak,cK+B2gɗ?R.ϥN\o_wAtP6n/y%fhm; +DoQ@E(8mW,"luui:"D[dtW9Y\ȇl꺮itbʒ&jQ.SJvpI|z][VHVye%=؊Uw?QOmBw߽}vVtRL!m\8/&z[ggW78$r,fj,HӃI($27;y,8N8j`>TJ49r=~Y QѲ,xCmvi\Ǝ꥜{KJZ{\feN3?!ސ]q2 Uv$N,ߘ\EߩȨXGBvۛM]i/~iO̮X]܁E2֒윯 bG!Ievegz$$Sa! 2|s>SHs/MӼz3M|)PʹK_bb4( Buvϰ| 'h=b(EI"jWSqi{T:,HMlwq/{f,-Aj(M}y/Q.~tZTTtV#rm]H^p>xTF=sAj$J@56V5br-dʃePfIJ-zAl0sy J ]it:y],@OY"Qif}?Lh 4-XU@uQH*VtOŖíwOZ5`Il塸"MW5RYHOG$"*"}u m_3y||^b4":$f;=m+({{~8pfV#TV4_)4 spfeg`a>b)c睦( vlUePl-ˬCu"kNDHO J! 16ͦi_NgQi-~ӼO]q#ӕ鷶HŸSzuYRε XW&H9G(Eԥ=Skۮm9/;u]_ii;nl~FΦ9V43@N (ضPi]oMKfWYIs.E80sٶ^մ^8jVztR|Ll[8NuUu| 0O3;}0]eS6G,BBB.#X|tH"q:4!HiIA:j˒|wODBuiN>h$v#z+yNk(C$3&Qbv6Bf#ck٢R7 ]uLt׶ 燇^1Uy?{ 8e5=Z#43̔uyQImkĐwplbX9}sؽ*bcjy*1ƔEGj$_Vws DLB❯|"-*B2gی PzP O"@ ===3tsqޠl8q^LBOJ;5t|;&9]:ʼ0SuzFm[I:pNNNNNNNNN :::::::::@'@'@'@'@'@'|v <"IENDB`PKF$&-Pictures/10000000000001BF000001BFE8CE7056.pngPNG  IHDRGqN% pHYs  tIME 5J5 IDATx[mU6.?\}}NVQ1H2`*kbhI`<$ˀMP!T26-$]сjs۷ZscasuT%[tk91}hf0bĈ#1bȞ#F1#F9bĈ#{1bĈ=G1bd#FsĈ#F1bĈ=G1bȞ#F1#F9bĈ#{1bȞ#F1bd#FK07n~|ƍx;[o>fzD'=uh?bd_'>}B' "2!Wf[  џoi 1爯֒O,hfFDDP3S305@D$DZ3++}͓מz Ik>ox7@DUE^l!#"9E OTļ6ED DDj[kNja{*>~ff`h`Ġ洧 US,#$Kd5$dUBn6sĈ7*>~ӿOOЧKH'!  ]Q"o?E Hi=]}G=GxCR'>``VUEDDRr`3BX0?@ͼX%?ăݽ *_7F1 SϾH`@DE fU5S5S-R1+?ELET9DH&.q1o$}G 0!)"有 KIrLT|DC0D(Tuv2=hȞ#FQݮ'w>g̲!>A>}Y$dSTbD"F @f&9,uUUUcȞ#F'K/lP"j2!<}Y3pHEԐL8,I a1!}_#{=?OuX,LՅjfEoDdRzLMM̀]O@u+D)eQd> PF*;fxwĨ~'^,EHTa8pJ33PS-k8iT< 44Ki۶9KBP凾_7sĈ7u!f3!V!8{"hPPX &¡U Y ƀʎ_׾=GxcP<|9"B (*Iڐ"""jM ԫt2)#{xC?;"Rvm7PD GHYUEՀw|DBlfYPUMRL Y3?8#FF}sΫj:舘Xu55#z|N}D́T|sSRbP7iI ڔ?k؋[|?狊qj4 @]1׮픝"S.w=;$B(07~+2c9`ym FBw=l߻kO|mWp>O|`JHΧ[HG>!/=$ "45UY$眳I9C UUq19!!@ TSm<`1ão,RUCߏH?wvG>Oo^Vwb,DDxڵ'xk?F:爯v|Th\-+dDabw=vN,s(U"9eL4i&UU)"!Q`*# T>vȁEb'g_}rrnsrN9gE47GDf=_]z  e{:ny Th?爯V|}3jooZo!*ab'$ndɒsVI9 J{vj$!V U888qVw}*s@Dl[@|FŻJ߸䟹" t\`f:XVSNΧsWu25#| %@UiCսAB Xs)}ʒU)IC*̬mB D$UQcӻ_~wb4DM󝙩*؎_p&?s]&/s@YM|fO<ĵƠ=Gq??{K9&BG}TDi\q"nd;aR(9nH* ?YsDU0s F"f0I̡KNS{DފÓQo(zy7|ۿϿ~g^˶!%2lǤj;=SwYȞ#hJߎ|T?k6dD@;"*]6&DB43Mb"!K,I;۶[uT2s7ͩӫ1o1<mmӲnvHc>ȥ9d;R1(6bYx^KbX ]w=sd_U=U'!""]̌uE2q F D;1qR>꫟;sf6*z'$d I1!9W DL8|pmF7T7m~~#Գ_w#. pUˆ&X4\<"x7_G8Ϝw4U-f"DSD̺< ȼ#gWl6?{\n-}u)l)I!1(M- mGD`E?=p}>m2S!;`Nuz2"~r'oeZ|Wnzoߨc9+ }?O{=(Y"26"`h" Lu3*VgUp `}9<<<8)ƒ `$dfP-w@PU! Ć zh X Y}._|7}7~}]W~;Ĉ9rN))x~ j  21z u קރ|R"'^:爯,|wdB"+{{BD|]yLjr;aB"H &MݻZ.4İ>߿׷=#Z8|ԬZBAg6m۶fֶϯvI9  t2+oy…s J!p۶V̚w䨠èv77Ӆno"wBUUG9g~oO#M3]K9@.#1) x?`"ʉ؆ao2]ojD,")d۞lNOO֒R@@BPBF@ 1,)K c07o=7M;/?35'M#'` L Ѭ)(0i׀zٜ?Or>:::Y UEPDkrA)S.@oD=( TX6 f24KuIT}fFSQLʜ]Uh4M]Yuֲ ")[C#'%t륗nzqv4/;~O&BRU,iI]/ju)p0)obZ8Dl qGqnΆ8rm7L] B  jDdH2cPDT8;Dh,u}/I4d\~|>q<#&)KZfkF] jihZ@`oE4}J$Yb (g@@콏OȞ#{2ٟy]g`֧d _|s2Lgt6kڇ&br. b"bD6m`RJ]LD7]ٶ'A)M)ۧ'r"&"24U3UDC7 ՜ rHTiSŇ/]:{nej**e *"aHw9g`j$f}e";<)nͻJJYD*f_>U"qX~9|?4zQ5D!N7MU=h;?̋/1tO }(:QŬUA]'")e\h!0)nt9nT5:G|Y>6>1_|v妘Us꽍s~:3g拙!BR%gM L$ǧPTbC8P۶fu]U1%%xYfj9+"xadϑ=G|jmu]/,YDEr b3g^pC8?<88?:~v:.rYյ3S(tE NDR(NاnڜRNL  d{Ƞ/=P!Pf LU}H]&*~"@H "z)^¹gϞ;,E)HQ8u)~a+]SoUhTX]~fňW0cKY͛Oghd_3{UׄԵ-Ūb|>[,̌? m._y7/"!|65"Y,KU~ (ʶmۮwU ɘ4}(MXdRj@Q鲙jv<UMu!3id6"&l6i^ STcTg!$& Dlp}6->wS` 3 1B@Dd"kQf Fp녗nܼum.j_r|{[, ob@%r]0!dBfc U<<<8<8ؿ|t4_,jNbvgmI %԰ {Փzsz[I*GaP^|*!'g&,b)Q^Xi]3Ѷ"l:]LՒTskL {4 v*"Br%7;3+P fTTSڶ ~O`DT؝DW}G=yd_R'INnO9%B}ﲮ9"P@h">LSb*IJbL9mČQ)jJUUj] bV|1G|kϼ/Vu+$jxj Kdƫ*ݺdHΒSAU7gΝ}衇b޽m |#110`bԷt7 :NEDU5vC"3a(74o8^ҏ&nҾ%b1kB#T䴮jBL>ӓ㓓is,z#"b DoP -"Bd$4Uhedd&Y1228*H^xidϑ=G|WI3uwD-@/`FH*DBLܕ#i1Hw]' HoR@UU?rƭ߿|2"#n mןnͶk[SWBC4"? YS;8"" ޚfĪfE_DUEsγif6i*Ff$3?U̢z^4UT 5Uđ*h6bO̥{S,db̭U)̌nsė'WLUU!FfFĜ"&++A(% зiJ}v+Wnݼu||t9BPSlzIDUDUѤ.fΒR؆3!1RĦ"껖:#l>4|6m"*fM9*CeP4YoW:朏[@U!Djj05J2k_bL .P]R5C@r&? fDzoo:_\sນl6[(3Q`6R2r$"VGvNuwf9ɶiLrpΒ!&dn[`AxWvQS/"ȐLEhȺPEāiÆXh"`)bSW٬UDvc>310@#`O7Yϝ=+Ţd59 몪ꚉ̼Z,z{c(Y=X)2/ވzVUA11*9b'M3.曥Wwc2Q@3 f65#"@*踫Lt] Ĝ*BUA@50twxUKcU=q jvUI:$"8s0OE."\,gl2ib^h#`@"Te9g* ݻL&uU$v^L&u]UC^ D&b*3̔R1jf``-` b`g^zϑ=G|q_}7?0Ba4u`%-E֡hNJ<@b>9fbB@<z>::j&SZ6T6^N㝔@ hV+HJa@U}30!s aZg|J̮Y'sJԋ0uQ)_,Ŭړun<a,,DT,L @ԔKb/ '_Ti*r `ZE]bh" Ğ< hIL1Uu^>C̏?mmɦm: J1sF8M-u}cIb!BLD()I30D2l6-ΝًBAC*nu>~ SBsoٟ={||>9%m{xxԷ-ZٳߍѽAH~Bb!eaNߍN Ǔ__}/3uCDLV&/܅@]r*FiFrEBdڮw8@}7usNu'+ lS; IDATKKp`B6"e9VAXժF)`C `XLb6-RO)BK`^f1׫)Ɍ'/̠ݶ}tz`MJٴv{93SwrvHs *Q$D!9bL\ ھ{89Lt6 , tw 07W3(3W}&!~ !,W7n+#d5 ќ ]QTE(L3rixr1 _oM'/In7DQ,j^Q]dfRh մ;!8Sm,![ ߞ۶sb[LTT"תCUYfi-DEꪚ4UrX-!qpMb DqL* N"3 Gx}lv…]%<ܹwoj^\K<^M=]X;d≨dh7\X{6M4@!@h3 cKL@$U3?37d?CI2 t jbl`9%lfrix cLDT5s s(n=H蛒Ćdم@@CěZ]ՄCMgԠT:#'$a`YTq{ezHU۶'K.O{-Tnx,wAm4\ 7n9bK7?Ϧ \܎.rB  k0sLMT"[.Cv!`wOdBmmt9KY)Vq@"!2Ѿ :.9DXF'*U1SXbKa!.ZPQ7DBR˗/],`] U!}lĭDLL L`h Z*DZid#`x3;*Ү5cϪZ]7|Sd?1?If"tdUY8PCڜn{O@ޤGv{p>03M噫Px:Z<=5:$Y)0!\JTrZ?jAEU G&&"%%L|-T2D83 fNŔ/=CMUȈ3GbTZ@Ħ8lvnɧ`ЧlXM8u]ڑ=GߛJ.dSRN]LL;7fH9Bl:>_~3t:OIxk)}8KܛN[l`eDjΦTuKPծm/]>#,2 VayBFJ3@,kIbGÿ?>sy@VIJrUJ4眳نI:s˗h;]O't +CEQ@]s.h`1!0[i'''q DȒ@ @E'< %o:SD #%Ik㪩l>[,ͤy55+޾ٗ_Fb>gk gKDtw"QUIYw[N`TaZOϞ??DP$fwŋ U]uejMU0&YT$b1@\ ƭ=IG#{=?]rų7s6U!ʼQg&j>q躮kiUW|oSO>uxppw担lƝQ\)& "FH"m[ZG*n9ggU0ua# ;QD$KS ah,0u=OtN]Vs[~wnkl:'MS5!xED[~8M QX_y0;wj+ꀒބ9jL9V1TR YC`b,f&NFd@AA#0g?\|! t])1Q*0[,|ؔu^SzpcضG7r:{!m%{R!eZ+;::} C`f":yY9rw`&S˒E"1cBY :,r869%bC޽sÃ|X.&ћ<U2`juQaf,N8I4x:a@ܶ.r~lDr"2z I$ٷ}7o<9qc9 ٟ3rfҜ;QӦibm/W+SEO[ٮhQ_"iu-2a5xM=?xpxUCb"GWntx9Eb\TS8Dc"dF@Q %L1z m+'ͤi鴙N*EUۆn)tsHM]_}=^ܽs˳|6-IUy#1KIl%;s̙g,K@x97t>-t&ɽBIM׮D,݋RbT‹c9b牯7=裗=F MSϴlZ糒FsH48̀ ѐ\X`j|G}c9bSx=wj(4 LbtRB9A \ޙHHHrZ"c=v>{+W {!H^x}||x$"9kR&>q;s %2AhY{wP"_ocX޼a*"lR/il"R|̃(dY% Zu[ݚ*";{Ň{_gp>Mgtrir!O`nOE @Dr@1ȒԌ|Yc)"Eqs:~/;wO&ZE=(qgg9 ޥ#9bV5,w}͏sjm۞z QVEqH껾bdǑ, Jjf8 (&6F`h0 Ls|>ټk@{,' shEDWp 0˒ BVdK)3޵<߿wݻ>׿GRiwX5͢@vzXEji'­B`اPF~l@LY%=˚#{/]ȹ{w7ăr^BjZ@q[ 8mWB z)/]zh٤7>ӓ㜳"r A)KfHT1UumjYRNi۝p1G K3)z0:ZI43 VҜz5[-UY]E0\P圏̒4s-$@fd D$)m$3`]WW{Wݻz6q r H%c !CV !*i]7ulNԇV6FI"!8]aef 0&sĈ{3m6}uմ `"DUl x:d|#ؐ( V) dєSvÄGլxf;88笪FnQҍr9ުq6*`L}GQ-}!3$J(00Pw)G'M/v{왜v+Y|>\8@xHoS30POE>::{fo^ ykZVyA١K#c9?WY懇ۺK ӆuU"\.>v[v%0x>M驈0@*>޿㵙isb`ԉjjS/8L&h92b$eQQSB!TeLU ffr!F=*I$ d&& !1X""jhh`jȄꯟ|SU|YޙWo߮jzHwEU-u&rK*fT՜fnU⅋EX*zgHR`E@Uq*wvs{gT`uz0%.+7TUPP4p{5 dW!` =cУ|9v,SXv &:::9dw`RSBB͂TN! @   n/]1S2"3K9'Mi&1FGMkȗ wBLSi nVPt24% ⥇6;wy1)' "!qI]۶m^z8"CRRR+ȃ)O#>3t:xlCb4DdP)0#;uy`>9#<=!q%wCV"rj{S*voS*b6_ffcU{ sFB"뺞L\xY$>hٰGJvkMo5*Nn]dvm>zi]b3m%,W˃5 [豷f(z>buGRWvy|!b]7 Y {ؙjUp"#|"jpNȠ4#Fgj6uaTQE/RFTK.?44fRzC@d,j@[XY"*"%t,iF D1ۜ)#!)!2t2CT<HScW_|sg6u]A͛(82lڶSK{gδv2i PD("f5@FHOϝ9Z.1VX5Kr wDģL{SKBܐ(mL"Gf)#:Yi3)TSUTRקU8ue3 D, ]#`CrbX {I\t@VK !U*zזR… `p{.K/35L ZK"EHb @b]é3e,@(vBT_6my|AnBdbUmv^7kAJrk <|jёr!P0O.{sy;wŃ,fUEN3N'۬ ߕ;рk[Jۺ~y"$&$Uj[5mF5jk1@X!$Cohvbf"֒^Hq2,ne 4QZq'88Ec@h(YI^GmBY^nAw#"$d&$e]e.xE5}.& U{GX6%@*[q<|?+?8<ͳ<%Y)+ 2U-%|G": t &UgΫ !Fߢ_-<<ؐllQ 13njYUe)}2#C9qȢ 1p`f)xˊ,AUXj$I*bamK Y2k:,  ڙMb96S3EpS pmY]8@L"l/2iUdooo[(PU h*ɫwӝD>d4q`3&g`<(;Eh͒J|)+rg"j{~vV۲ r IC4aH{p`b蠗!BNVoTPTՠ L CE~rr\,Fx4L&yQgHb;5׸"BLi1uɍ[* =x2yy+GYVv ]"v>Vz8~a2uc:=䓏b8w8$qа/YH@*jB ""`HyD1L=ߝ U`[nٳ<ч/S-%SͦcP3v40ÏߛQ` tZ0&V TQ` OݽsY=/}?~爘y59`B%M&SA~G0#>t1 . MF@U]D HUWuYU]yz "$A_w:0i۶VD7Y5EE xgQU'x8$FiS )7N= <]u\`hijmj׫t:vrqp44Eכvˁzҳal:m 3iα;ZQ &UC*a=z^ӟ p@Ȳ }f*>Ky1|^I) 1"$NU6MLDیSWU̖bZo6mےD@L[ DUETŃ%Fkj9 C0$fȄHU'QdB:~4 p0 Y@D&@T$US@.mRr3"DDi!֛uYeٶɃ= tRy .=맲Eo蒤l9:M&p0G&013tP4ۺ1YdY6vӳGl:LgY\T`D1bL%]4LBp(8@MM_$u??E܌㲪mE]?<򸳻4-礄`($MwL C]J䷏z^o~fYywwV4Դmq:i۶eh8 ryrrzvzGy1!s`Dd!mJI6!`d߃w&&f\-(I2@ /tiISE@RR4 3#Di۔y`Gx:4WLkcT)lY۷'''?drtxpD}sBP*YvjF. ރ9D=?3DоW((hUUmyt1#Tս*t\]y/x_,NNONNNOOCx<n(IMc!.gj[ݶjYuS"N](ijއj6&@bDQ_uewaԣ> I&I)_դ*m.(V)"1fb^.N^9<:,*2G"Qlfm`RJ.tݒ|FImS]M}h4*aIL#HUů}8|Wǁ,'n򻽁Ő8P DdU 1V|.{ٿ "f!F$d<eYfy^4R< 'SO]/ElpM [9x< ,sgwzY\H(0۪J$FGOyvdž }Pe'7}/@"l6u`V5s9yeD>E x\Vp4"3$FT41Cv{.Tn$lz^o}Wf`܊(vA>CMTզT˅I[ɵট+nT'M=z)1! vgtmmSSev"p^ϧDFɈ*u{P/!q##BPjqf?"Xj[p%vNQEv Īʒ(y6d2bHN* JT5Pc m ۪.=ȈڽA L=*Pڪھ[_|yy3M&cM)%IB']PG _ '̬M)d1zQXS,7oܹ}Ǐ=zxvn ɝf&R̞yqw9m 7ֻ{iww"M+Vq;"3`:ۉ!֫nu <3q` M]JH,*ͺK |1X\,٦lp4t: "SU4B  ́m_s=uH°(Ed6W+<::bw5]93 h&B >ѤX.66ܶ-oiX DK~@֤v8vMHA5GxKbڤX,r_}Y=/|g+?gf G{ׯ|hHl0@6O&@zH{g|$^p޲uHSc| 1ƫGGׯ\iUOOO9j4S3D& J41A4Gv#.v|BأٮW<t:1)SۼBȳiZDȧĴC1U t~G!oL8(0-Qz[Cm7C@E1g`4,0gf늰;:\HWvjU֡'p! M;hd-`)˅&Mg#9jl!$SDL^1Ku}>8֯KeĎI mY{{;;;^C#6muĮpRnE_ n^ޅ_"z hۦ Dttxxp3,ONOOggw8]MT 1nʲGG;ݝ2Lf oE}R5vQ%M,bj#P5$(`IEݻVkԹV LivX !h_x<ш) 9!k(БTVZoyQkMUYWkv [/yy7?s'X.`&Hz#,\CHO3uSUM"&v){wg (>OXJ̨`{?zt||r|zrz~>h L73"U#$X̜,l2 vwv"`gwGRmgg׺6rf`rOPJlM !DUEDJ|txXm+;:)= 0wT,G''&OK*b֛ܬw/ ]e<9'x>y{1^\:S#!bP0D DuN=g8΃ٸ SUOvP0`NDww ڶat:Ξy|~vv~||!f5BQ$,$! {QAkW Gbhᾦ vAS'DC$fӤmJRjl*fCFG !J-5`a̟͝1uS/W+3z| z,M=5 3Ds$)Il4ug&bV\M`چZMDXY/o~{"[7<͏?>#dj5$B>}wn߹3-1AM|籕NK&wZ-)%0 UKfJnnJD@IH({;_x.[-W?:99;K1V kOD63hsն2pP d*>0w $E$ f 1v`.,y~=SdN6tC!Bݴ\^v =|ǚ劈UD q}qP#(8?_ cIB`F sWڶD:"!H6jJJmPCW|ys<733kGO?yԪD" ifBO~|㏑_q;wlLځ=YX< ͺ`3"cɔD:U&w9QSm ђ*9\.4~lH+͋;w޽XNgvBlf^r<O&Adƀh ޺&*f@n,bLOBZncT "e,*)Hӷ=FS3֒\9:6Mh@%W|F:trwppPms*ؑ{s" R @+r~mFTxWy\~""^9<{G/݁)˲dXww?}Rͻ{o#Ͽ;O>l2F( CB.<9\ =1 2 s'33!BMQQdOܸGgg'bP D8fn =UETWÕzꊞS Y:)0cߊ+G[Q3E4@2s"L^9:裏˪ cд|Mw`B0zV4S HLX7m EQܱ5zVeZoP$.rfZÜw8i>/c}W6xzl-՘c?v<WKbB{o_K #/U U:r;\0GH`fQvSK"ur8׭_$`b&+GGuS/iYuDBDc*! ;ب!/\zMbDFgkJ# dj`JDj3 8$gphsV=kc fܿ)fFlClXGc(v.k'Rh-:D$-QUmqIalV%D'oyY=/o}޸qn[r[a b165=zaVdmv`5{w~ ‹OܾO`8ȘQPPo9#I0d$e&Qc$DRSr|L]f e],T]zkuӜzX AQEnj)] =%`jb3:i+s ֦mQ/|҂poC^ϺunO?ٙ!:[1c`ٿXDPh)1n}=*IDȲg).kYI"M32wSe|LϿ?p twoM^YVPhDBa4K<9H̞Hqqo[`/e1?p8 1z y#$UCSe)vrś<2yP0Bz T҅TMBW^yM$|t|rr|rrzzr|2t]^PQקa># I&̌ bԥt[P;CH)ͶOfSp1 L,"1R>%lۄYT|K"Ṙgެ9?=[,οqJ)ոZuS'նi}^IM7K/^VG{2On߹vѻ$<~ʪ<>>IM?XOzL8w_if^R_Ǔ(mr|+"PD;;%?<Ǜ;ZUwШl:yţӳӳӼ( /ŎhiM5e`Kˆe&#YCu_#5CPc$IJ96vv۴h,r 6-X& q q~6b^Uӧ ,kU|'MY'mu23>NF󱼳;?uWybs)e̶MU25pD>΋b8 a>M"NT::=I ImugR,TS! [M13m=#y,bD Yuc!իWmхKMa'44Ta)GqXWjy4MӶw2yy87bdGO=uT?\F;s9ڦ\RսlB{/fa"g& Гog_~Wϊ"O1)XWRU`"j* @)T #3$mJReO*v?Z\o qlow? ϶z%ZJgy56_lMTܲJ4`"dPCD"I|/f8?O)mJ"ReY̳lPdY^ "BL}n gYִm&]T2SC#\\9zSngMjiVݓDA@_˛<|@D{Ǧct%)CZUUVSw=̍{O6e@!f]7UoޭHTXط0Zi*"~4O4˲x\9}v{t\-[ _t3J%1@IΓwY7ȶ:˦d:{ɻl~5DS聡!ET0Q M)-i|x^9  IDAT۫zܙc< Y 1 ϲNrC<˚:m#ֈpSMNƣ uejSj Y%5ըDM[z>Fo?,Ƙݽ{#Woݧ=Kڶn|bpI;ǓA㦪u[7)z\.խGu\oo{*IدUS_o‹_!}8E^y:ܴj|7fƄf/֫ Eȫ)zh4c|!L&CHItu $h&fj5㝡#/M_jWIիW>ߤDD!pȳ"1wEpd:8xLf ! L@՜D7E L6s@LMj`@>1WK:=ǣ;7񣳲,c("7'f4L=zp|H4 Ea>oO]ٙy$flnWf7fSwySIV:HDEw~&W_Kyp8G@DDTe6.&~Y&f"rId _xrkӎ>B/un6%޹sM,"S̟ oڈjZE5Rp\ 8o ]XmJŢm[1H]]"/b{:<2lRWp @4X.׿CCɔ(8҈WhhFr^oʪlgmJ9L|~ϋ}o]VGufYLulYMK/tFT>iJ0b!fM5+ucAAs,hSuӦVm{֭+gMӤG﫚.Pn`HHyWo‹/X|/(x0mJ)*$tC$&&%2B$;9{.;a]WFdfQM"jL+mȀPDo}y0)F>P>|rJ$M r]6u~cS!*710e{^9~I+cm`>88\?(jMՀCI""kRtt6Z/CA:ޠͷ$b~L(q11\v~_S$gH+INNOʪ̲c Mt0E˭<:k`? #dyriwŲ|h٢:?_߼yZ|>_E8z=M5x/}y8_u12zڶTL7 zL޴^HECP34eof:b5fÏ>*֊13 MMm)1h@e^նe41ѕ?iO4+`5,OHQ$%q3{˩HmjvZVe)"M]5uvxxxY[{];W b"Gg?(<qONᐉ~C`gU]wwGo c` /"3BY!@ڋ2-2vkvΗ|}z>;,VO\_owyw~vYkfW0D3Pno"K_y9ˋ_y͐fHJ=mH+J)G6MӶ "u(~֊1< VNl],DDRJuTնWB"6m3+ƃb"&Lz[ygIEq}mN' Q~q}j^oknVea4QS%FuLzdTyxl|ߏ1GpptZ>^|ψPU3b`ht~>k#Ϟ} _r5lG iM]Q0#>~H?J!NwFӝ.z]-eXԧrܼu\n֛yܬT;\A&H@~g?}+/ǘg1vHOdvtu4xM}0R{BUGm EiVD?]03O?ۿ^.;Yqn^3Q%Bjwaj۶eYeW2GDHTVcJ4y/+QCj%JLGWf<Y2P>{X.ۦq]"IM1rmsT`oC)[wzo|[?,;N{lF`Eft4/M8o"+r3Fhg:"6mQChR< s,/|#j7z曓zq~`[M=ϫ!+G{"[oj?ɏE<dYc$]<u. DD R Ju4'06* r9͛7߿4eŀA7HM>m^˪rt9Ayn >~U6ެrފjӊ/R p8HIRbnxoDCѣW^305Iնa63E0U$RM9z1oc<^=̿oweF3$D+`mSӘcU|;W_ YBڀtӤm5T h0h$GGUj~>;ۜ˺,nz]DA#]IuDC_ O~_1h8,"fJ$F; @#m$1Y4RB诫 clv||٬,$D`# Hƈj ar* @L#a73`l4)FiR)T߾q G ?]ojID?Z-Ijv2 P-כMUn:r`ǞF`x>?I64>U35D31=z^b0B`px3ɋ/-r0`z1fy>Ϸ$M0fT rx^Chkⅽ0D2m PFh8ɋI^m]Wf\ol||OOVe;ݲD TUxUٿ_yy8}*2P<}KEкnʪt_ytG* ~|gg/Rޟ[RDvRIlN wDBSvժw>;_}@M'?ᾊ5U ߿kX7MlkP&PAX4նqJ BgK;ڈ?sǼ1 Ql7+7t-Vj`zp8-Mga6 ,%TJt~y%޴i coE y>=uY rlh\rY޻a(y_- 8Zi^Sv^$108.WDD|π.`RM"ouY=/_?,cxrx[Y_?]nÛ7o>|pZ<+G; @ŀ@ʚy#X$@Y$礀IS%$eS.+ DfŰفElubX\Z.%hZ_ERPD믚O/_WB1/b77I)2m LA{mzݙL6*()ub%4$r&vԙ;JQnDkD jL P}nd<7r8xSj]MդUݴzJVN)WǗqJ)1'{o$Yv݇so˗w6ݳ2A-["Mub(Gٖ-I/H3bZs{˽8e7qS7&&*2wmy1Oղ( B<th ~\>/7UUe1N곳w>yaX2@Y,,ϝs&,gqY9 QBad] -&Tb! ']/F"+LAeUoo-9=[b޼dz  *IE'&$_}Y/><?mU鲬S pdW=0 HT?5}sooﭷnk5pSMRTZU׶Rcp[KÅ a | 31VCil2kV(38mG>]۾ZhZ8q]e\D9 %AH~DU"X$Arc0l_V;_W@,sxkk5;{?MCzvDj:9~Zf'a[Tjh- Kc,mP//l.e߸1,F{,ϷzsY,|yq1-l^\,.f˷~|vNM30D¯g˲sYeHG$D!B ȖQXDXk)EȚ޽{O~g#"!rvDrIh"F48"#ev<ܹ{bnfvcCB1Ce7|ou(]C*KεyL"nTEai~w,/}[X\6Nvw&~,quʢbUl֐5P22sY'bׅ]w}{3bЇ5ԐSJ6DHF8%f.feeX8}/U^HAu0HŮ̋5/{ϟ}or"Stwyi_-۳dyrWWfrѼLRzGdM?W56=y iXcc7iVeQd"DeZM%J2s6$m03G2 kpg3g͸0LC^#r\fm}f !'Y+o 9< JKHZgYDQ8Ɨ~߼#so4mb:olGw«"l$6.+"FmnˌH2Y4Yf@6ta|t0E $ t]lxT&M:2l^gaV~*A{bp[yQVZ7{˳tqz\.k;oݷFl6kC} "292˲IӶ]Eh h[EfywzdE]`X/p . l !9w</}/_d'_ ۷,яEH}[sgv{joT磪4&s,Qqe]]J|@5B#pn߅sB{ROPݎn{BLF \C `ʕUۜ_,b~v8h.żmVtlջo}r|rvz,E驧?moQ:'!D$A#%'p")iu|cs:_ұa]u]o;ƨY!,1&rt>˳<1 Q5LWsXC!Q0}MR:@=G6$!12S }pO6F 3 Z21cD$d|Y=/G/|W9"SUt\O&dZ]~kw0H ;_̢˦Vm }+UUVEU6sQ^@"zkMl=z||n}nDƌCuPF=r@o@(CkR7qSlp^̖ˋj>o/Ys>k泲,lެwydv~ig{G48YN l!sRxa4D0K"]%g:^vw9==fmc4| $ܳ3B }^:@eYxJBtԬjAɘ0yi|B&Kꤐ F" {P + 1F0RY>x}V2u{ ֡4ҽ^~|4׾fDt:G GVUw,sC2Z˲Gڲb>;YYV9PQ,Fh6@ dYVZ WR;?{,:d`,) }%, x>3}bb DgΊQugh.ggÇtbcxyW1Ҥ睠@F$]Vd12L$֭w㓪,˲ZHhhX8cmI0NGVͻ1b]J@JseYrv9H.ϻȒO 0 2@r*YQZc{@2":X ̠LZ^oe<+_563c'I]x9k\9l"ln<_5BцȳB`sƕ3k3 d 1D}l.] @^)ʨvi1TX3 -@(,B[ЅyՆyX< ZCDZgPdXTT|gkQʇ@5CHozj!{S丆#Cdh(!$ #"YkUBD٨͜r r>G5V#A2Dbe%&%!+2}ǣ͍re\Yc`BȄJOpQDDwMH 1N  F,ˊjԴm)9 Df&B;cg.ݑ/χGx<;o,Oeֱ#H+׮]N&|9֣*beyѴtTUzbU5@|<n <5qnP@rfP&-Κ`MȲ(pqXƠ1xY2+ScF;qw7ʲ:ǘ;,QI$(.vHD{ipb .;;[yEF w IDAT;;dXQ'1>!!Mf&‹X .FVMlV}x6 b baeKbV7`@QK 3Yu#p^濴"aɸr>S'3gZ ˝)"ϲ~S}>|px:lWdEl9Mv7\ټvu{hmM%܅<}^ C-@oD9 HQ>n=@|R 0&HP9u\UlCG hͭGZWW\ӆQ}f'b1fؠ::F$`(%)+Sv5p ]hH ɸi!g-sxk7o_?)ʺz%9,9f !&9-)Qb!Ckbx<ч|^D8DhjH$I )I}uܧdv2{]7!.c0@fKz~ϗ򂵖1N'Ӻ999888::9XM;͍F= ?iȇ @+{gX <TS͟X #.Ƶb- ..RGuy@{wN-s "C 29ӝH}pXb"e;!rw!u]Uq2Y-.sC_O\4'r%EDsƚ#r,0Hh41}zhWfՆ4$.}QǴ9)D# MeʩcmQVjQJcm >QEcD6cgyY=?/}kc-"h<Urݽͳ!;!q=sLk%uO}ʻwܿݫWwvw6|p1;|kk3ޚllL+J3nhu!s.n( TxDT3໢ j`ٲ=?_|W-Uӷm\fgG~yGYk  Qfj!UR=It#c Rq[Ct:i?(_  YkB kZ}Hu2ݘdM'gWMղiW˵ D$I3A @r:NEd:AIJ,b"DE-hc/<1/G|{or㋼NeQf&"dQE!rNRf|l~X mmm}g(P^ ŘY1t1CB,W,d)'/"@,@ >rl c$`" A$3 KʵWa#,c"R?s$c8F`Ceh|Z%mlnw~|ѨցSd~ƛ>7NOf'rwoꕭ +KzlWQ]M C8` bf<E拰Z>ٕ=3DڍJ;*nZNʍI~{,&B`dIi)G>xn&%'o{?_,4Gen_ee1Y}UfnSd$"s"}%%Hݗ)摒bSԨtP,^TOf1ˊx__c Lywzݷz[sO'*#" ,"qmۊnwo'^[=Uptt޽zTnnwv{8?m&?V"i׌cͧuebӱ{3^6:9LGU~}l4&۸~Z>0MHj̢F"jADc0  QlND"8b113ϧ^޸yZ\ucr HQ"" _2ԝ$"C8;=K:xD "DyJ=Te-4Q+wGB} (~2Ͽůs@gf}-흽'|f2*J= u=q=mt1zCڟu}׵mUeqgyz6/Hk[wU1x}$nH:ꇽecF_L&)ҕeBjίVGG?yTl"'Cj$IW#(xB}*pQ-H}bE$Dg=~S')F\d c@0!kP B$cx)m^w;_qY0Oq]y6ﭵ;֘4 "mmm@='pPR"!!C {CDyySO>yrrdyyd2wokWo޿=ʷ!8Uz!"TdTed8/ [$?;__gs޷Kmfu1׮ڌ!ާ0!BeE},9gc`u=VwBTX1p bI4p#!> B?=->8_u_7߼۵>sgM B8ؤXW:@T}!sz~Von6͊cC(R_)dDYP8Y(c@%7¬pD sX>cgz~fouq;۹ˬ1n]jHtjȄt9*Dږ&~.40sj6FJ}pppxpxgóNvv6vw6&ۓjc uzfDbrW-c59km̩,R%|w&u>NlK竽kըLcfAceR4"Ƌp\ Zoz?z⦅g鶆X{,!xX,~"N׷|;g!d?1 *P%@t\,12d s5@!$e:t>IX+P@~czJź:e1c/G|wvuVͲ]tc5GvL&41C"z%(ZcCRMdD~bD ,Zf6˲q]~>I5}׼[ӭd5ٞllNLSe2e޷'QaME\2g1UAEaXvg {z88M7v˲5M( >jV@ d"ˣp] {冶Qc4Iibr "{{{µn]rcL MQ[ۻۮf5.2'h(0 @ N&=$|JP@8F2#,/X-?uY=/χrfW6p\OF('KU[9(d״Ec̀{(ˆ֚Z&5fX sLZcTߦyXs[[7o|{?ͷ\ϟ۶ގx{k=ٞmmO' t}lO.(j.W5!3ч5~>k̚{V.^Oo|,jcm0'0 xkdmY=:[CcX,1d2(2Ub\5Im]f:|?xzrRV5CPKѿ9fY>fXy#sZ" ᘲt(@0sY^IHkMA2 30'論s"v,y^k9ďeh/}`3 v~z[UYYkEcHX*`]C)m][N@5H$dY JЇ9ZFRc 1n-Vm4b(ʕ+7o\V~!ͭdcs1Lw67"J$VUUUŴrϗWKi۾m|, эOnnlb^'47IFVK:@SF!9f`¤e2ཷoumjƶ{WyC0t <>x}mE_H6.FZ+7 &,b1D` Xo{'z ѫ_Lj@/e<}V lof,еm=LS+WXFu=L28jW0Ef X"$R ZC)6@S Q5LBh{5kn=X.=-o_{I5ɴN㺬FeYeFmLJdrTff>of5;oy;[tw~YM]" MJWO!$M Q _< Ni#tf!t=CYb~;Ww@8::j9eYICZf(@A x15 bRk8QGH1m AtKd˞DFA $܄琲Xk]Ϊzl w^D~z^ \oh4,1狙sn\ 8-umlT,lCPB@iCQHP E(t Qrb `B7([n=3^y}veXEYMƣd4rR6&E]e"R.4*%9<'c>&"Fu׶y^gDV`I.Bƪ_zQ ݌F`7E1hS[AͳE!L1hAD;czoÇG߻1ݝfYyeY^鸚Lmmԛִ&(2gٮ 9/'Uۥe"XuH"دsއ># slF7Gt~]qե  AX33wBĻwUQWKtگ͟36C\d࿯!(QU-u61 ɼP}HUխ.mH%cn> 3]2t҂q[}^ם_ 2e]nomq]b^E],}kt{}FDu@m>|bK$Ad!A4d1$)IB`2m@t$Lbrc ۦifng{gÓ]gM]/NOgEs>f|uqڦmlkэO] !Ɛ1(!3%ViL!=$6흽Q=N"L %&y^U;yss`[;^˲7@V?Iwth}2`F운J!CA)3y]Naaۀج,*DeyyW~yQjgy]GUE `YAdb#ЬbUUE:c r1D HBXv)Xc 1 ϒ:D "&L48K ,ˋ'zΎOݻ\\vQժ=;WeV*">p Ó^{gEhX5"]דa>tb6u"!sz|<'H"F]O ;>aajݻn[wبxOXk 6Ȉf_7H䀊PNᨦ!1 @ c""! W;_樸Y"**D$˲>6m[dMz~xUJQtZE!BYU3Y{2 }Ze!ʜͲP@8"j8?r,m"aQuUe X#A$ & ђ k?G`DŔ,i93 L0iIz{{O<{rrݻwyhXZku#{'n߸q="AYF$@hcD$r=a"JHD:'h-'"CCCmׯ+kl; {l?O. r$PKM1p"x\yʰ%IKm3 Y5>|AIPN Ҿ@g.m3a _ot~oKf񸪪I=fiHraH␺:faVH"M!ƾ"*/ ">0K3u= !]{;ʲUu74h՚EZϺ%ꖉD"Jvilgxss[g'<,X.zA3:88U^YV>9b̲\B9kֺ"ϝurڶUjqR1 c>(EQiLk/y]uʵD8p2.^x 2ǠDd}1FcKNOou/Fnn@۟z_$scIK ߐ#+UU5m, C j<\T153T D>H 15֘t7W/'7p~2kssss:uY9&Co LDF:;%Cj@*,ˌ1]Yk6S5u]oN7zM. m`ͺQf|6_V]ߋH9cF{sdH$aN௪?ɐ5NR#m$ I1 Nb(r9_, Ddh`Jn8=k:I%!W_zǯ{']Wv\? j\޸ACjQ6h[Gι޻37o=}wX !ȢJ²47]p?+Ā=粮|eQ g>3?_Vy_Njx2' pĚF20cL@6coAozȝc@N&UAR[ dY6LiY,\,E*2!H 0A,fEu"!֯딴!^sVH $"`}]k 6J;%m`cY~KoZ:|9k85UU]'knxUjPT"Q>y'ǰ7~agӕ+EYFL}rS0^ B,bjڮՐD[L3AD<˼P*byQd.ˊ<2Ձv j65՘XDo 6]RZ'H䈂h\h 56Ĉ}_~噻yk7%e΍b5qQ=?#w^r} ,@,Q%rE=ןמ{/Fwd j)zGJD"!pQMI0}dV"Gi=0@ }}LV2)#G% 3.?7>xh\r2fP}h<c(3=dPb6a| !FdY-P"ebHC]`4M&ާ?7ח}X>Bc}$Ӗҷ11ڥckR jIWJ~2wîgHQUqD. ,|1mlnE1/v:a9OKUUxss3!*>2׳N" Ɇɠu]FJZTpM@|>-Ç]`=vFf;U 0BHָ,ˍ11CT>/Jpҹn A$#\])QAyM1rZ ]˝QSNDFUZD>D~핿|yԳ\y+]YdW{Ogw>ɤ~ep!f=bV#~B2DmUAk.7ό7޼޻|LBDL9Sb-=*|\Ȅ { d-zd>!e`4~КI3YKײ8a=NY5p%)  m40lmiF8Բn6`nMH,HU;sϸ?Z R2~ʪ̼ùgǎ}]ywЯ?/f;][txB>?xbDj[!PWUSז+F|#dCBfpX|XܿK/]/Wz}nc 1* :%w΅DH@8'ΛI"J4!,TsS /Q%Ddv"@^F6`}?[_񇯾tfKzoHlc |:yc{(1!Аnn7__~j[;}>~n=m hТCIs"A4 2xľ1)3-FDdj@&{=*Bfvn^aCDkdzͯ\>/|'8L&]45 "Q!B6Y>iA.= &Bpz|&S]tcU$8 þӐwyTױTO֫OXRUblDj))moUE~vzt}zyov{s$J)Z 9bUMsqumb!:F^MM )># ȊKFSvQ+fS T컓CnB<cU"sL{io,lpHc6U]D\"r/0 }@iںn馓icl\^\l6QɃy! M8&S5Ӕ3릓b`?Uk1b!APAjeюDʄ)aT$ }Lj$es/Y|97D$ӐD^ި+Pm|ó}^>::9hw<}vZmyGQosQdHÓy`8{_\ov̛nMAXӏ˾)GfƂ/'J12VNM]ŔvTj89"ʐ(Ƹo9 'wMyp| pss3 VUu*Vn"aL>ԢTUGq:y34Iz)1Q13TLz׿o}_릪"O'K>?/̪zs8{"\J u&́K-"14>ؘT׾gM=">?2=TTDmۡHtmҎO0WLh 3[d(LUӐ0z!2f2𠪣8geClB۶W˥zmL?[seì+xjspnc3Ssi&$4Qbд{~z-_ϊ]f) h\M)iC!, Uq7s-m֍| * Č=l߯7gId:OTj 4ِ'ݤiaWzfՐ0/fZ^OLYFp/7 H-ʩsލLE%޷1&Lb>| ^O|Ci:mOO`,L\kt>6M `U6QI?n)23osBE3!Ut~G W8>z1u?匙1bb\.wxZf3QřDF⍤0$ \ GSBف)ܛfvzPJ{xyW=6_~>Y!T7t:Z RJjJD"n^4'I0[!CJdj0"3ҰZw}gXuM3}\SYz>! ݤvnT 9=9au\nOOO!#!4[MD 〪0!3֕}LLfB#Y8vǗL|x|=|4#p9Uӎ"zqA ϛ, 2W!xwmn[~s8woA$}~Qh%tȷGH#|c$D4AfܬX(Ͻ B9d@qX"ozm^WUU5u5NSV8U1V >h`0?{¡8|ާ:r" CaJD`P!K/x}^.|Og3 bf>==8z:;>6Ɗw]H2*6[Jt4 CJsP*b_6g1Ah>իTԀ`Hiv;ؿsƟqCUŮOO/xWOf bIiWCݧNgr` "B"zvt|*cܜ $!0w};~უW_Y_=YM7 h ጪy# $kLڧ-9qw6/LϮ1l"*&Kͦib #1 iLu]8nV$TӮ%b2!b^AC`HbjZW?^onVr7Q7bqr|< Bf~RMٳQbLI={wlULb&)p_W+@ ^=I% aH<]Urncމ+η;#TLE,cv ~#{݇/Mսf:mLiI@DMX0@d$tP }{w8ǪljyݝTQ! =a6-zc2MS7)!".7oWUB e%jl)z/nC&yECX+IGZD<ͯ./].)WNwǿI7U"']@nڶ*瀝 5&]? Gv:o:tm1ɐ.Zv *)r@́@Da>f^Y]__/֯n'О3ԔˋˋhXlO$ɬG2e9;=̎hH2 $QQ@ʢ4@BMY&^[UHf 1Be˹\fؔ,@Mr앁1% LM] t=yx;ׯ}sf8!a9z:4MKNO Ign!BB ]Ӕ7yJ/3qC LLDǧ')_|ie]D4T ~Y 7MK4 ![‡p84u8K3_ 24(!;tT&/Ifl$M\X7Q\n}2ؚiM @Wj!ٽ{ggg=ƒwD TMT` xH!@4%tyK5E$C@܄t:N]l KԀyb9 QţU}k"W8#ICc!g~yw`Oy/q)\L̚tvc>{H@Hѐ`PEmDm11f|fo73mtRu@&$b0K@pzzrv^\_! ʈT2w't)R6u]UC(iXKJp3a&Cm7$Lc!rs!K StlI8KO}U+/N&ӆ *6pxv]^oT|Tl\__4$$b*mg =_^n:Ȑ9*(k@jNO֟j00U2-)f5 Zȥi#dJMy"truqro|w'p? "1u=TEnvtXSG 2. ųnw8^,6uƨܳ:D7~mZ-={:f|6 v `]ĘRJI̳Ctz"a"uI#zv٬*~Qԏ.aU IDAT=hIwMl7#JBdr?|%a2 vU!#5 I_\უiUax4^mM*IW g_|}M{_?9=?xTf9)?! ,'U8IBvFPPzUՌ:fj8d b޽ՕO?/U (VοKD|!F ?oHBDc89>L%m'IU׎es]۶UUe)9ZXC}`nꚉ@2? gQbjݛΦMӴMݶmU">\`ehhL0xT!yUuy}}uy9N %ਜ਼Ĉhq8 Ð*fTsb(BrIi8o˪T"Olg׾ټY,fNjITq.Zmmt|ҷ}dFfyGw~Oed=tJi )Hn%oDMр*gYfNn^/!,!s&fqWD?:Y*ʌ>Sq |pw8$IuӺ-Xss'l:fC?Mqw#@@!r:*Tuv)9vHU't>;^̛Q3BjꪊM)Ч$6lK~n'Ӷiw rWE~{eE`TC}Y^sƑ t͠dHa^3(<}J@"D>{G_ f>~7bѝ.g Ϧuլ˧}[&A@ ?G0Ī//B93gL HM`YMEM2Գ<|؜xB4&θu@@Pwjӫ7kaGwo~?)U%bbMU6m6 )3@&+D*ڧl%b101qHC5 nj8ol:s.*, ʭfEp8v۫equ.'\`B!2j2 SͺX4u;n=BRUU cϔҡaX b`톀Db.MM)y'!-NgCJՈ`>fdz)^Cdh6vƉ:mr0 ,ͺ lAc,hi3luEB`7<4lU,A+vYDsd |In<iEsƛg~~wOi@#q8>:|fN.Ka#dR׵m& =RU7Mqu.?dR{! m۶|B&lmȸ[Z,4O^|d]  \)cx~8PUGvbM,G'''ggg_x0NEeCJ'-SԒ$mwզm}"g@x GDS9}P.S7Sb bwIryZ.rv{Aͧ~(0e'a's`"3i$!1Zëz%:@\zGXUS .Ο5=g` " S`faGU2OEBX@]ǯj8Á o~嫯wo_T$!!l:Sm\Ւ!LC?HN'!x2„ 8-T_Dg```{$̦ĩ]S512{L/"'|nn"t]*P&k˭i{x/["j }$"lbfہO7ȼBfF"עXGLS`APaH\Oϗu] /؇?u5͵_iLD H𪊀,ĸl]0&i(x"8v{q檗up)3NA]?=Ԗ>5efxu,&X*V]7qGjyxUϻxY4 }NU'g!䡳߳dRWUI3?O [ea[i QAQh&[_@L=BEtzrrt|)Cm\_]'IM]"G5uXr.V Oʽ4]D?..LDaF^T <-۾G/!p\8_>'zTWO'=[UUxz~i=C O {SUqMl3av]#:t;SA^r"3E"S}!TUU2zU`fDHȘwLLft|gMS@ l`WWW 1S,J!{10jv$')P +6kR#b ̀ frl.V(1w[r&5E!xyp X7_RCb<9xQͧ¿E8rv-jMK 䊊Vjfus_Ϣ h]U.h1~AD9鏐D"l6}BP.0(CߧBtl`D2 Y>YVUMP$o|˯i<3\;r6A}3Q]7ϏnVW˪3UAE2U'ޝj"i _ #B`m #1I1 3vӚ;";@~cb`.)DiT%;F5ےFDBfBFgnVC?vw?>7U﫪>w{H \re` 5t/O_?O. L_|0?;;=0k+o WF߫V']GLVLЫ[1mA6"a(#"VZ %v 8Mo_Џfu5a *?[zE'뺪VvM&1O4Uͦbd&B|wyCJnz3!fFb* kFIE̒Zf)D0MP m2-2>!3?X7D%Icd-w۽U}<έ!{ @NZE\,DAT$璒 "zeLd"}2=vupX 2ޟ7}dQ7!'7á/'gO>~SyxYd>:QB5G#@=1r΁S,T,H0ͻS5(e;"Z%-IBr:jlJZ"U4Ir^ګ@"p sW=﮿G{{cյ]S꧊Dj6߲l!"q,nFy %g^}ۦ۩C@$2a׎6_x2 `lϞ>}*ðLh31#3p)jb2N]<,1tyO#m6;$z5 p0D.V-?wl3ON_׾fƶ&&o~l4DrR!| 1?(he:n]I4ͤa(ѡ%rWEs%hb1Q !cF5+o vy/O(B< J$PU{cBLvvcƛ]S/q:aತBu6s#t60>5cPAL!FC (;!p)%<.!ܣ2pw9MGD$;KPFb&bq|3t! )<-})%0ow%?O")fn9:}>>°ܹy! tCz ⑜~vw^}~Zڷ_{/}tq8@ Hp66uD"* T%[ˉ}\'JiE4_Ÿ瓥T"j]*,QaPD&dM,GC߻ec?[sW=﮿^fuۦ-mFlfCtԩHKfIsK(ıX6Þ"7%r3]U 77?ǏݬobݴGGL&,#,L ^d\D!rpSH裾$Ua )IzYoKxyyDIEU9-Οw[of{8 d+cW'O?R_[Cԙ c"tX>ϠGy;E|wt|u105ܳ4Χ˳aza\C =-R#Ѽ`{?ṑ*@.@yt r&4m[W[|6s)1 _&+^w{kP̛͊ mEn"'$h$sk9"!ð]Lbp12dsWaC6EV #@2$9( w{_~O)!"}3M8pXo6"u>EkU,p8 $ SXuMnal>/C&I-`yAr"^"Q:4UN0M"AD0D"L"D(}]7'''7&m`c84IR]uSl,gU#$ŠJ4"H=DDu}yu9"&]!^Cru1cC35MSgSffsUs]QQې$it-$wB4%5*J!? 0 LL,3-%ؼ9=z~w84'invaUc8d&Z!No!Gâ+M:Cpޡ_vAzÄ0HBv#)Ԓ{ Mɩw7 I#(:9ڦUUs4 C"~h'@IEW?횚9M ՌiUUͺn*FelYa9b2C0:gs@!h|\ i|3n)M#?~Uɡ60O~wR*<`mӸKįE Yq:L.0GCJcV&HYdˋg|8>:tx36? ̽%hjEMye]>ެ֫1 ݦii&fU] CO)|G CP0 d v;3!~o:DcCr;dz:A*PԂ4)]C"`>gg#—_'oZ]Wy DJacP3%^ϪZ@+dNOOWns!C #!"l6k'*i|s؂Y10!2义+8+]ǾmtrDL *&s"RMI;M~aޜ?=靈B1F` 1i@C*ABn6 PQȈ*e? *qxʹUXU=<&G[iҗ}~=xᕷBh MWu 1xg~TtJ(@Υ/bʼLGGGIT&^{@3Gp:&$Uu<_l7qa|t)%W;e_+cFR}#Op%Il9dZ#vtW=(~O*œѶM]*FE~tt8:B~,xuBg1+j($UU t2Nnt2N'ټk3 c l= ƞBAQP1F6 Hݳ+}+׳K4c{novҵ2AS D0F$d%C}`{P^; b|̙;z5:T_I4SC0L gX +"K/ٳV$hqtvzKܧY~xPr):T j8J|L@S3o@ň|eBD-0;ϑ#<>/{ub" cKН#_ي=Qԏ'9ح beH Df2&!~w/ʧ  YUUU]MC+? -K;- ؏T(0IA"@s;P]5t:!gVա{aumBHonJiFґ @99'hf;-1RJC8UqZ< V8pU*AF/!sߗWTZLD86Wzӓ3(\<+dvccƎ)<=?Į6ɛܺZ6;S.KnDLj5$ax0@"bVϰ(E߰S5FsT"1&'dn`<% E G`OwWY' x&i\Wu]ժ6|v@@Ʋ@ $ "C ש@u*nzqtzh6M&pz=92~hXU-S0G3&dqwY]7>1(Cw&!$2 ҡ#Ϋ?kdqzw9\L+Q0H8p*[ѳ1Ti0C?P.0t<*vsME?S:USLaD 52ĩC̍TܩN䍪!0%u↹{Fc'=}zn`mRN4G9~X| y4 1Vuۮ) ?p7f ȣ\M-{QX O$!Js $!>9?؋~$w_tm몮+B4Mx{l8BB"QSSTfOL>h6k=u6Duʐ;EDfnvߟg>ݾXUUJiZ(01" j9B{nu)Lފ!@E:cN}ݔR4uvvDi%gIRe\#JR(Rmr%TxuX+qR  0DL(p ) $?u]w˫aHXEsRGUoH0[. LEH{ln[c IM0G߽1>v8X97?]Od7uTex4,/lF`0шc@1"/ ,4/ b $I!VlZь|xIru$3r|$n13w Iv>KI8L6`/"ٛedX^}:%mn'áa! N"$bTܖsA&ZLj"p2MYy)(pf󂨂!܎Eޚ(l@(Lᘾ w&HI6 -YݮMPDZN adzxR`d#W8]&g CeIP NI`f.S3-gz:mk#Fs\Og2MSmc]Lk~"@ L=I=@(TfeC7tm*fn >iM˫z{rr\u ,t(1AH1p !2iPTDr=6G LU`H1VbqٳOϟaw dγ<5ˑh 9}o_z٬cC]答M=x(ѬE`"C) Yn١OG̒{¤iS?nn`"2{F|C(1 @',1.8I9p{b]DR3'm{ZmA9Zo~M]\g~jUUu]ŦmFH۶MdQ 3|&J/9 yꪢ*:1DHi(i2%/DDLdNM>}suyY{|H4/a)VN,Pw!f2RևJ%j^onnCDd^dOBqz21 Uc ŜCc !u-D9CDĝnl42tgggn!HmWU5WϦyDcNF9]\S@ּ[オ<\ؓu$mc.^vLQME#T4Mk2<\64QQf lo\XUQr.(&#F#N㴦=T]DqrQc.؊U)t-*OTa^3ZIlg0,A6-[`QD|ne)Kjw{߭4캮i0[7춘u50 UxEfl -ý%kŖ+K"4w(Rr.E@"<{ EXHhw"1b .d;wpM&3gtdkE=`ƟDiUEʚqBu]eF=YiiӚ:!]SRp9 uMP7Gm2^Η%*! Y,+)EtP8m1Gx٫*8])pͽ 2cڻ^JTK{Ix8rtw?BPhVE  9gBMz0젲\s򖚩>t"E,˲.4M>>O!X? CObmֲ0H6sի+h0iJkBt@n]!2;*r4e뱮|bW{D CKO_?йu+<'d[6M41n. a{64vI߷g8F&[E&qaFi9=B0\ sqfU}y_ ٖQJPTi9P&נ%@̄*dw&1S @ Em nwy0KZS"UiBz8 2"!28>1O#}f!8 IhQP`DԀ{*!r@25ݬzx?BiZtzXrFfRV,(y󏞷],3`wmS:Z/l-o@OFYt隶xY5"zs؇˺&-y.L1 %*JATm**뚖ya&0mWR5ɾ,JdR6sFTb奨/YgpV(H0s H/rӿ ccO{뻮@ŀ}wf5Z:&6f xŖP>.e )3H*DJJ"BOFTߢhVSQ\@|h܊-PP@ j+Os9Ox^e]j~&؜u<ߟN8r)LqA.V5ZTHm,n:2? ٌ40ۧPA}60b\777+bވ +lƞxX 4TkǑr)e5D1elb#BH!'vnG,@M;<}60={m[Q\TJQdiH|䵒*ݨ>aAT%b%?/뷾}Ӯ&6mt4M׶Dq i=RāI֘(nJ)E}xQfؐBR@vK"P䄆D$0  "!V(1 &49qe K_#aQ+~hZc %YzF.00|N)ZJm.ؙɄbSZL>)V]L NyND]ZquMW$b 2ȴKNY 4s۶]׌Ӕr點i ΁uϩf"!`1ƾ r UU9H= [YD 7zzgBHL?btk^%%9='mZ[__߰+m`m'n#CbD %ni n1U}9gfv>Ly>L"@u.GPTD,oe[Ե@ 6Qqu%!P ؖ( E@Ztb{qg뺺71?Or*4nNr^68f)ɺɒҶ)!6M)?2 CtQ<zhͮ&'ff?h.2FxRСYS۴]`]?4 OZT*Q *nno.:`[u ŪH ]rT@-_.RLy]}}7A!ݻLCz:pDE 'bb*ULW~-}@ ʂ"XDH~ІK%ŭA5qOFEBDruXeaR5ݓ'C$ddtNnno*rbT&gO.Jօ" 5:plDO$6T)PֆFLlc 怄?>9w#JknqNT=U DLD*%]bNL Ycf{3C}"om "FA68y1.EJ*U.%9$fU:KJ",;jmcK΀"YAuIjJe(h*2_Cxc v;}bc{G.'pU\j$]E<)A, 1{yTS)lUnRJ\5 -Txdni(QZ *vm d6J"tlG-p:2˲pN7V50։ki^um=}p/1x^@~lja@&ƭv8Q ! Ŗn" j hQCVtpI/bVfj(UX 7kZRJ4ȒBb7}6dhAuYq|8,‚\`#U{yYy~m28(QPI?Je.=ךg4[KT~.QAq!"z [uBCx9ƦȞ_D*e(]w &>P !ڵ. MTpE9m޷m״dp??O~_ч//~s4Dw]Y54phμ vGϟ}^<V4H<g,Y5ز6]TH]b`BMvyM8/5&9O n@u @)p"撧eZ׵t>QVp[EA^DJU,mpl\,Y[+E…RN3 H4qeCHZ"9e\/^|=Kw`" }O##%$&u>O/71@;JJ3P!̊`nVOxY8e~민xq9ýbfYqR IDATbtm#DR[Q$ȣ͠kGPJ.LJmO>?__g%n00X aj4ҳgϘLT̄cEєu-kΎ߭ ݊d2-/F 4uA)pb܂,j)`s. u]e9kdy"eS.8X5p^gDxpGi59661Zr9(1x9Ǧ>pM ߾y3^F JOvW;$n S gDt 洺|h%H6eP!mc10Qw\˻wBަb: ",p:eKA$by!+$[4^[j?AS?fb[f[źy&9C`)h6:,-W_"" α Wg-! 8v6:c^VVl+$ 1.kҺ 0Imi{"BD)._=ΪRJB"&O4I!;_$@J QrVi=>|)"ڶݎp!6~^~~:P)PjOP-$/*@W+U8Umg; 9?]y^T44BDh'{՝crӴ6. S,4Nǔ fL-Ыv!&#m_V@=_@/Zy1JQflb6cM)D"ECŸ\!s_p8웦O4ðU& ihwց:ZȢ+ Q35'Gm:Fu:~l(uLG2sIa1